@@ -37,6 +37,13 @@ import { withAgentSpan } from '../src/tracing/createSpans';
3737
3838import { TraceProvider } from '../src/tracing/provider' ;
3939
40+ import { Runner } from '../src/run' ;
41+ import { Agent } from '../src/agent' ;
42+ import { FakeModel , fakeModelMessage , FakeModelProvider } from './stubs' ;
43+ import { Usage } from '../src/usage' ;
44+ import * as protocol from '../src/types/protocol' ;
45+ import { setDefaultModelProvider } from '../src/providers' ;
46+
4047class TestExporter implements TracingExporter {
4148 public exported : Array < ( Trace | Span < any > ) [ ] > = [ ] ;
4249
@@ -259,6 +266,97 @@ describe('withTrace & span helpers (integration)', () => {
259266 expect ( startedIds ) . toContain ( capturedSpanId ) ;
260267 expect ( endedIds ) . toContain ( capturedSpanId ) ;
261268 } ) ;
269+
270+ it ( 'streaming run waits for stream loop to complete before calling onTraceEnd' , async ( ) => {
271+ // Set up model provider
272+ setDefaultModelProvider ( new FakeModelProvider ( ) ) ;
273+
274+ const traceStartTimes : number [ ] = [ ] ;
275+ const traceEndTimes : number [ ] = [ ] ;
276+ const spanEndTimes : number [ ] = [ ] ;
277+
278+ class OrderTrackingProcessor implements TracingProcessor {
279+ async onTraceStart ( _trace : Trace ) : Promise < void > {
280+ traceStartTimes . push ( Date . now ( ) ) ;
281+ }
282+ async onTraceEnd ( _trace : Trace ) : Promise < void > {
283+ traceEndTimes . push ( Date . now ( ) ) ;
284+ }
285+ async onSpanStart ( _span : Span < any > ) : Promise < void > {
286+ // noop
287+ }
288+ async onSpanEnd ( _span : Span < any > ) : Promise < void > {
289+ spanEndTimes . push ( Date . now ( ) ) ;
290+ }
291+ async shutdown ( ) : Promise < void > {
292+ /* noop */
293+ }
294+ async forceFlush ( ) : Promise < void > {
295+ /* noop */
296+ }
297+ }
298+
299+ const orderProcessor = new OrderTrackingProcessor ( ) ;
300+ setTraceProcessors ( [ orderProcessor ] ) ;
301+
302+ // Create a fake model that supports streaming
303+ class StreamingFakeModel extends FakeModel {
304+ async * getStreamedResponse (
305+ _request : any ,
306+ ) : AsyncIterable < protocol . StreamEvent > {
307+ const response = await this . getResponse ( _request ) ;
308+ yield {
309+ type : 'response_done' ,
310+ response : {
311+ id : 'resp-1' ,
312+ usage : {
313+ requests : 1 ,
314+ inputTokens : 0 ,
315+ outputTokens : 0 ,
316+ totalTokens : 0 ,
317+ } ,
318+ output : response . output ,
319+ } ,
320+ } as any ;
321+ }
322+ }
323+
324+ const agent = new Agent ( {
325+ name : 'TestAgent' ,
326+ model : new StreamingFakeModel ( [
327+ {
328+ output : [ fakeModelMessage ( 'Final output' ) ] ,
329+ usage : new Usage ( ) ,
330+ } ,
331+ ] ) ,
332+ } ) ;
333+
334+ const runner = new Runner ( {
335+ tracingDisabled : false ,
336+ } ) ;
337+
338+ // Run with streaming
339+ const result = await runner . run ( agent , 'test input' , { stream : true } ) ;
340+
341+ // Consume the stream
342+ for await ( const _event of result ) {
343+ // consume all events
344+ }
345+
346+ // Wait for completion
347+ await result . completed ;
348+
349+ // onTraceEnd should be called after all spans have ended
350+ expect ( traceStartTimes . length ) . toBe ( 1 ) ;
351+ expect ( traceEndTimes . length ) . toBe ( 1 ) ;
352+ expect ( spanEndTimes . length ) . toBeGreaterThan ( 0 ) ;
353+
354+ // The trace should end after all spans have ended
355+ const lastSpanEndTime = Math . max ( ...spanEndTimes ) ;
356+ const traceEndTime = traceEndTimes [ 0 ] ;
357+
358+ expect ( traceEndTime ) . toBeGreaterThanOrEqual ( lastSpanEndTime ) ;
359+ } ) ;
262360} ) ;
263361
264362// -----------------------------------------------------------------------------------------
0 commit comments