@@ -38,6 +38,7 @@ import type { BillingAttributionSnapshot } from '@/lib/billing/core/attribution'
3838import { resetMidRunUsageCaches } from '@/lib/billing/core/mid-run-usage'
3939import { NEAR_LIMIT_USAGE_GATE_TTL_MS } from '@/lib/billing/core/usage-gate-cache'
4040import { isNonRetryableExecutionError } from '@/lib/execution/non-retryable-error'
41+ import { AgentTurnStateMachine } from '@/lib/memory/turn-state'
4142import type { ExecutionContext } from '@/executor/types'
4243import { readTrustedExecutionCost } from '@/executor/utils/errors'
4344import { executeProviderRequest } from '@/providers'
@@ -160,6 +161,40 @@ describe('run usage limit', () => {
160161 expect ( parentCost ) . toEqual ( { input : 0.5 , output : 0.25 , total : 0.75 } )
161162 } )
162163
164+ it ( 'bills a stopped attempt for the journaled steps a successful one would have billed' , async ( ) => {
165+ const session = new AgentTurnStateMachine (
166+ { save : vi . fn ( ) } ,
167+ {
168+ version : 1 ,
169+ steps : [
170+ {
171+ id : 'failed-attempt-step' ,
172+ assistant : { role : 'assistant' , content : 'Partial answer' } ,
173+ calls : [ ] ,
174+ results : [ ] ,
175+ usage : { input : 1 , output : 1 } ,
176+ cost : { input : 1 , output : 1 , total : 2 } ,
177+ } ,
178+ ] ,
179+ }
180+ )
181+ const run = startRun ( )
182+ run . unrecordedModelCost = { total : 2 }
183+ mockExecuteRequest . mockImplementation ( async ( request : ProviderRequest ) => {
184+ await prepareConversationGeneration ( request , 'anthropic' , { messages : [ ] } )
185+ return { content : '' , model : request . model }
186+ } )
187+
188+ const failure = await executeProviderRequest (
189+ 'anthropic' ,
190+ { model : 'model-a' , messages : [ { role : 'user' , content : 'Continue' } ] } ,
191+ { executionContext : run , agentConversation : session }
192+ ) . catch ( ( caught : unknown ) => caught )
193+
194+ expect ( isNonRetryableExecutionError ( failure ) ) . toBe ( true )
195+ expect ( readTrustedExecutionCost ( failure ) ) . toEqual ( { input : 1 , output : 1 , total : 2 } )
196+ } )
197+
163198 it ( 'counts every block of the run, not only the request that is running' , async ( ) => {
164199 const run = startRun ( )
165200 await modelStep ( startRequest ( run ) , 1 , 1 )
0 commit comments