@@ -163,12 +163,8 @@ export class AgentBlockHandler implements BlockHandler {
163163
164164 const result = await this . executeProviderRequest ( ctx , providerRequest , block , responseFormat )
165165
166- // Routing cost lands on non-streaming outputs only for now; streaming
167- // outputs assemble cost at stream end where there is no hook yet. The
168- // charge is gated server-side by mothership's bill-model-router flag
169- // (default off), so this asymmetry currently bills nobody.
170- if ( autoRouting && autoRouting . billableRoutingCost > 0 && ! this . isStreamingExecution ( result ) ) {
171- this . applyRoutingCost ( result as BlockOutput , autoRouting . billableRoutingCost )
166+ if ( autoRouting && autoRouting . billableRoutingCost > 0 ) {
167+ this . applyRoutingCost ( result , autoRouting . billableRoutingCost )
172168 }
173169
174170 if ( autoRouting ) {
@@ -289,17 +285,44 @@ export class AgentBlockHandler implements BlockHandler {
289285 }
290286
291287 /**
292- * Adds the billable sim-auto routing charge to a non-streaming output's
293- * cost breakdown as a distinct `routing` component.
288+ * Adds the billable sim-auto routing charge to the output's cost breakdown
289+ * as a distinct `routing` component.
290+ *
291+ * A non-streaming cost is final, so it is mutated in place. A streaming
292+ * output's cost settles at stream end — written through the accessor
293+ * `installStreamingCostPolicy` installed — so the charge is layered as a
294+ * read-time overlay on the property instead: whatever the drain writes,
295+ * every later read (trace spans, cost summary, usage ledger) sees the
296+ * routing component on top.
294297 */
295- private applyRoutingCost ( output : BlockOutput , routingCost : number ) : void {
298+ private applyRoutingCost ( result : BlockOutput | StreamingExecution , routingCost : number ) : void {
299+ const output = this . isStreamingExecution ( result )
300+ ? ( result as StreamingExecution ) . execution ?. output
301+ : ( result as BlockOutput )
302+ if ( ! output || typeof output !== 'object' ) return
296303 const target = output as { cost ?: Record < string , number > }
297- if ( target . cost && typeof target . cost . total === 'number' ) {
298- target . cost . routing = routingCost
299- target . cost . total += routingCost
300- } else {
301- target . cost = { input : 0 , output : 0 , routing : routingCost , total : routingCost }
304+
305+ const withRouting = ( cost : Record < string , number > | undefined ) : Record < string , number > =>
306+ cost && typeof cost . total === 'number'
307+ ? { ...cost , routing : routingCost , total : cost . total + routingCost }
308+ : { input : 0 , output : 0 , routing : routingCost , total : routingCost }
309+
310+ if ( ! this . isStreamingExecution ( result ) ) {
311+ target . cost = withRouting ( target . cost )
312+ return
302313 }
314+
315+ const prior = Object . getOwnPropertyDescriptor ( target , 'cost' )
316+ let raw = prior ?. get ? undefined : ( target . cost as Record < string , number > | undefined )
317+ Object . defineProperty ( target , 'cost' , {
318+ get : ( ) => withRouting ( prior ?. get ? ( prior . get . call ( target ) as never ) : raw ) ,
319+ set : ( value : Record < string , number > | undefined ) => {
320+ if ( prior ?. set ) prior . set . call ( target , value )
321+ else raw = value
322+ } ,
323+ configurable : true ,
324+ enumerable : true ,
325+ } )
303326 }
304327
305328 private async validateToolPermissions ( ctx : ExecutionContext , tools : ToolInput [ ] ) : Promise < void > {
0 commit comments