@@ -149,7 +149,17 @@ function textDelta(sessionId: string, text: string) {
149149 } ;
150150}
151151
152- function assistantMessage ( sessionId : string , apiId : string , text : string ) {
152+ function assistantMessage (
153+ sessionId : string ,
154+ apiId : string ,
155+ text : string ,
156+ usage ?: {
157+ input_tokens : number ;
158+ output_tokens : number ;
159+ cache_read_input_tokens : number ;
160+ cache_creation_input_tokens : number ;
161+ } ,
162+ ) {
153163 return {
154164 type : "assistant" ,
155165 parent_tool_use_id : null ,
@@ -159,6 +169,20 @@ function assistantMessage(sessionId: string, apiId: string, text: string) {
159169 id : apiId ,
160170 role : "assistant" ,
161171 content : [ { type : "text" , text } ] ,
172+ ...( usage ? { usage } : { } ) ,
173+ } ,
174+ } ;
175+ }
176+
177+ function compactBoundary ( sessionId : string ) {
178+ return {
179+ type : "system" ,
180+ subtype : "compact_boundary" ,
181+ session_id : sessionId ,
182+ uuid : "compact-1" ,
183+ compact_metadata : {
184+ trigger : "auto" ,
185+ pre_tokens : 434_000 ,
162186 } ,
163187 } ;
164188}
@@ -192,6 +216,23 @@ function messageChunkTexts(
192216 . map ( ( update ) => update ?. content ?. text ?? "" ) ;
193217}
194218
219+ function usageUpdates (
220+ calls : ClientMocks [ "sessionUpdate" ] [ "mock" ] [ "calls" ] ,
221+ ) : Array < { used : number ; size : number } > {
222+ return calls . flatMap ( ( [ call ] ) => {
223+ const update = (
224+ call as {
225+ update ?: { sessionUpdate ?: string ; used ?: number ; size ?: number } ;
226+ }
227+ ) . update ;
228+ return update ?. sessionUpdate === "usage_update" &&
229+ typeof update . used === "number" &&
230+ typeof update . size === "number"
231+ ? [ { used : update . used , size : update . size } ]
232+ : [ ] ;
233+ } ) ;
234+ }
235+
195236describe ( "ClaudeAcpAgent.prompt — streamed assistant text wiring" , ( ) => {
196237 beforeEach ( ( ) => {
197238 vi . clearAllMocks ( ) ;
@@ -243,6 +284,48 @@ describe("ClaudeAcpAgent.prompt — streamed assistant text wiring", () => {
243284 ] ) ;
244285 } ) ;
245286
287+ it ( "does not replace known context usage with incomplete gateway snapshots" , async ( ) => {
288+ const { agent, client } = makeAgent ( ) ;
289+ const sessionId = "s-context-usage" ;
290+ const { query, input } = installFakeSession ( agent , sessionId ) ;
291+ const session = (
292+ agent as unknown as {
293+ session : { contextUsed ?: number ; lastContextWindowSize ?: number } ;
294+ }
295+ ) . session ;
296+ session . contextUsed = 434_000 ;
297+ session . lastContextWindowSize = 1_000_000 ;
298+ vi . mocked ( query . getContextUsage ) . mockRejectedValue (
299+ new Error ( "context usage unavailable" ) ,
300+ ) ;
301+
302+ const promptPromise = agent . prompt ( {
303+ sessionId,
304+ prompt : [ { type : "text" , text : "continue" } ] ,
305+ } ) ;
306+ await tick ( ) ;
307+
308+ await echoUserMessage ( query , input ) ;
309+ await send ( query , messageStart ( sessionId , "msg-context" ) ) ;
310+ await send ( query , compactBoundary ( sessionId ) ) ;
311+ await send (
312+ query ,
313+ assistantMessage ( sessionId , "msg-context" , "done" , {
314+ input_tokens : 440_000 ,
315+ output_tokens : 100 ,
316+ cache_read_input_tokens : 0 ,
317+ cache_creation_input_tokens : 0 ,
318+ } ) ,
319+ ) ;
320+ await send ( query , resultSuccess ( sessionId ) ) ;
321+ await promptPromise ;
322+
323+ const updates = usageUpdates ( client . sessionUpdate . mock . calls ) ;
324+ expect ( updates . length ) . toBeGreaterThan ( 0 ) ;
325+ expect ( updates . every ( ( { used } ) => used >= 434_000 ) ) . toBe ( true ) ;
326+ expect ( updates . every ( ( { size } ) => size === 1_000_000 ) ) . toBe ( true ) ;
327+ } ) ;
328+
246329 it ( "keeps the original turn open until a pending steer is consumed" , async ( ) => {
247330 const { agent, client } = makeAgent ( ) ;
248331 const sessionId = "s-steer-ordering" ;
0 commit comments