@@ -11,6 +11,7 @@ import {
1111} from '@openai/agents-core' ;
1212import * as utils from '../src/utils' ;
1313import type { TransportToolCallEvent } from '../src/transportLayerEvents' ;
14+ import { OpenAIRealtimeBase } from '../src/openaiRealtimeBase' ;
1415
1516function createMessage ( id : string , text : string ) : RealtimeItem {
1617 return {
@@ -316,4 +317,85 @@ describe('RealtimeSession', () => {
316317 expect ( last . inputAudioFormat ) . toBe ( 'g711_ulaw' ) ;
317318 expect ( last . outputAudioFormat ) . toBe ( 'g711_ulaw' ) ;
318319 } ) ;
320+
321+ it ( 'defaults item status to completed for done output items without status' , async ( ) => {
322+ class TestTransport extends OpenAIRealtimeBase {
323+ status : 'connected' | 'disconnected' | 'connecting' | 'disconnecting' =
324+ 'connected' ;
325+ connect = vi . fn ( async ( ) => { } ) ;
326+ sendEvent = vi . fn ( ) ;
327+ mute = vi . fn ( ) ;
328+ close = vi . fn ( ) ;
329+ interrupt = vi . fn ( ) ;
330+ get muted ( ) {
331+ return false ;
332+ }
333+ }
334+ const transport = new TestTransport ( ) ;
335+ const agent = new RealtimeAgent ( { name : 'A' , handoffs : [ ] } ) ;
336+ const session = new RealtimeSession ( agent , { transport } ) ;
337+ await session . connect ( { apiKey : 'test' } ) ;
338+ const historyEvents : RealtimeItem [ ] [ ] = [ ] ;
339+ session . on ( 'history_updated' , ( h ) => historyEvents . push ( [ ...h ] ) ) ;
340+ ( transport as any ) . _onMessage ( {
341+ data : JSON . stringify ( {
342+ type : 'response.output_item.done' ,
343+ event_id : 'e' ,
344+ item : {
345+ id : 'm1' ,
346+ type : 'message' ,
347+ role : 'assistant' ,
348+ content : [ { type : 'text' , text : 'hi' } ] ,
349+ } ,
350+ output_index : 0 ,
351+ response_id : 'r1' ,
352+ } ) ,
353+ } ) ;
354+ const latest = historyEvents . at ( - 1 ) ! ;
355+ const msg = latest . find (
356+ ( i ) : i is Extract < RealtimeItem , { type : 'message' ; role : 'assistant' } > =>
357+ i . type === 'message' && i . role === 'assistant' && ( i as any ) . itemId === 'm1'
358+ ) ;
359+ expect ( msg ) . toBeDefined ( ) ;
360+ expect ( msg ! . status ) . toBe ( 'completed' ) ;
361+ } ) ;
362+
363+ it ( 'preserves explicit completed status on done' , async ( ) => {
364+ class TestTransport extends OpenAIRealtimeBase {
365+ status : 'connected' | 'disconnected' | 'connecting' | 'disconnecting' = 'connected' ;
366+ connect = vi . fn ( async ( ) => { } ) ;
367+ sendEvent = vi . fn ( ) ; mute = vi . fn ( ) ; close = vi . fn ( ) ; interrupt = vi . fn ( ) ;
368+ get muted ( ) { return false ; }
369+ }
370+ const transport = new TestTransport ( ) ;
371+ const session = new RealtimeSession ( new RealtimeAgent ( { name : 'A' , handoffs : [ ] } ) , { transport } ) ;
372+ await session . connect ( { apiKey : 'test' } ) ;
373+
374+ const historyEvents : RealtimeItem [ ] [ ] = [ ] ;
375+ session . on ( 'history_updated' , ( h ) => historyEvents . push ( [ ...h ] ) ) ;
376+
377+ ( transport as any ) . _onMessage ( {
378+ data : JSON . stringify ( {
379+ type : 'response.output_item.done' ,
380+ event_id : 'e' ,
381+ item : {
382+ id : 'm2' ,
383+ type : 'message' ,
384+ role : 'assistant' ,
385+ status : 'completed' ,
386+ content : [ { type : 'text' , text : 'hi again' } ] ,
387+ } ,
388+ output_index : 0 ,
389+ response_id : 'r2' ,
390+ } ) ,
391+ } ) ;
392+
393+ const latest = historyEvents . at ( - 1 ) ! ;
394+ const msg = latest . find (
395+ ( i ) : i is Extract < RealtimeItem , { type : 'message' ; role : 'assistant' } > =>
396+ i . type === 'message' && i . role === 'assistant' && ( i as any ) . itemId === 'm2'
397+ ) ;
398+ expect ( msg ) . toBeDefined ( ) ;
399+ expect ( msg ! . status ) . toBe ( 'completed' ) ; // ensure we didn't overwrite server status
319400} ) ;
401+ } ) ;
0 commit comments