@@ -4,8 +4,8 @@ import type { CallModelInput } from './async-params.js';
44import type { EventStream } from './event-streams.js' ;
55import type { RequestOptions } from './sdks.js' ;
66import type {
7- ChatStreamEvent ,
8- EnhancedResponseStreamEvent ,
7+ ResponseStreamEvent ,
8+ InferToolEventsUnion ,
99 ParsedToolCall ,
1010 StopWhen ,
1111 Tool ,
@@ -52,24 +52,6 @@ function isEventStream(value: unknown): value is EventStream<models.OpenResponse
5252 ) ;
5353}
5454
55- /**
56- * Type guard for response.output_text.delta events
57- */
58- function isOutputTextDeltaEvent (
59- event : models . OpenResponsesStreamEvent ,
60- ) : event is models . OpenResponsesStreamEventResponseOutputTextDelta {
61- return 'type' in event && event . type === 'response.output_text.delta' ;
62- }
63-
64- /**
65- * Type guard for response.completed events
66- */
67- function isResponseCompletedEvent (
68- event : models . OpenResponsesStreamEvent ,
69- ) : event is models . OpenResponsesStreamEventResponseCompleted {
70- return 'type' in event && event . type === 'response.completed' ;
71- }
72-
7355/**
7456 * Type guard for output items with a type property
7557 */
@@ -88,13 +70,13 @@ function hasTypeProperty(item: unknown): item is {
8870 ) ;
8971}
9072
91- export interface GetResponseOptions {
73+ export interface GetResponseOptions < TOOLS extends readonly Tool [ ] > {
9274 // Request can have async functions that will be resolved before sending to API
93- request : CallModelInput ;
75+ request : CallModelInput < TOOLS > ;
9476 client : OpenRouterCore ;
9577 options ?: RequestOptions ;
96- tools ?: Tool [ ] ;
97- stopWhen ?: StopWhen ;
78+ tools ?: TOOLS ;
79+ stopWhen ?: StopWhen < TOOLS > ;
9880}
9981
10082/**
@@ -113,26 +95,28 @@ export interface GetResponseOptions {
11395 *
11496 * All consumption patterns can be used concurrently thanks to the underlying
11597 * ReusableReadableStream implementation.
98+ *
99+ * @template TOOLS - The tools array type to enable typed tool calls and results
116100 */
117- export class ModelResult {
101+ export class ModelResult < TOOLS extends readonly Tool [ ] > {
118102 private reusableStream : ReusableReadableStream < models . OpenResponsesStreamEvent > | null = null ;
119103 private streamPromise : Promise < EventStream < models . OpenResponsesStreamEvent > > | null = null ;
120104 private textPromise : Promise < string > | null = null ;
121- private options : GetResponseOptions ;
105+ private options : GetResponseOptions < TOOLS > ;
122106 private initPromise : Promise < void > | null = null ;
123107 private toolExecutionPromise : Promise < void > | null = null ;
124108 private finalResponse : models . OpenResponsesNonStreamingResponse | null = null ;
125109 private preliminaryResults : Map < string , unknown [ ] > = new Map ( ) ;
126110 private allToolExecutionRounds : Array < {
127111 round : number ;
128- toolCalls : ParsedToolCall [ ] ;
112+ toolCalls : ParsedToolCall < Tool > [ ] ;
129113 response : models . OpenResponsesNonStreamingResponse ;
130114 toolResults : Array < models . OpenResponsesFunctionCallOutput > ;
131115 } > = [ ] ;
132116 // Track resolved request after async function resolution
133117 private resolvedRequest : models . OpenResponsesRequest | null = null ;
134118
135- constructor ( options : GetResponseOptions ) {
119+ constructor ( options : GetResponseOptions < TOOLS > ) {
136120 this . options = options ;
137121 }
138122
@@ -498,8 +482,8 @@ export class ModelResult {
498482 * Multiple consumers can iterate over this stream concurrently.
499483 * Includes preliminary tool result events after tool execution.
500484 */
501- getFullResponsesStream ( ) : AsyncIterableIterator < EnhancedResponseStreamEvent > {
502- return async function * ( this : ModelResult ) {
485+ getFullResponsesStream ( ) : AsyncIterableIterator < ResponseStreamEvent < InferToolEventsUnion < TOOLS > > > {
486+ return async function * ( this : ModelResult < TOOLS > ) {
503487 await this . initStream ( ) ;
504488 if ( ! this . reusableStream ) {
505489 throw new Error ( 'Stream not initialized' ) ;
@@ -521,7 +505,7 @@ export class ModelResult {
521505 yield {
522506 type : 'tool.preliminary_result' as const ,
523507 toolCallId,
524- result,
508+ result : result as InferToolEventsUnion < TOOLS > ,
525509 timestamp : Date . now ( ) ,
526510 } ;
527511 }
@@ -534,7 +518,7 @@ export class ModelResult {
534518 * This filters the full event stream to only yield text content.
535519 */
536520 getTextStream ( ) : AsyncIterableIterator < string > {
537- return async function * ( this : ModelResult ) {
521+ return async function * ( this : ModelResult < TOOLS > ) {
538522 await this . initStream ( ) ;
539523 if ( ! this . reusableStream ) {
540524 throw new Error ( 'Stream not initialized' ) ;
@@ -553,7 +537,7 @@ export class ModelResult {
553537 getNewMessagesStream ( ) : AsyncIterableIterator <
554538 models . ResponsesOutputMessage | models . OpenResponsesFunctionCallOutput
555539 > {
556- return async function * ( this : ModelResult ) {
540+ return async function * ( this : ModelResult < TOOLS > ) {
557541 await this . initStream ( ) ;
558542 if ( ! this . reusableStream ) {
559543 throw new Error ( 'Stream not initialized' ) ;
@@ -576,7 +560,7 @@ export class ModelResult {
576560 if ( this . finalResponse && this . allToolExecutionRounds . length > 0 ) {
577561 // Check if the final response contains a message
578562 const hasMessage = this . finalResponse . output . some (
579- ( item ) => hasTypeProperty ( item ) && item . type === 'message' ,
563+ ( item : unknown ) => hasTypeProperty ( item ) && item . type === 'message' ,
580564 ) ;
581565 if ( hasMessage ) {
582566 yield extractResponsesMessageFromResponse ( this . finalResponse ) ;
@@ -585,12 +569,13 @@ export class ModelResult {
585569 } . call ( this ) ;
586570 }
587571
572+
588573 /**
589574 * Stream only reasoning deltas as they arrive.
590575 * This filters the full event stream to only yield reasoning content.
591576 */
592577 getReasoningStream ( ) : AsyncIterableIterator < string > {
593- return async function * ( this : ModelResult ) {
578+ return async function * ( this : ModelResult < TOOLS > ) {
594579 await this . initStream ( ) ;
595580 if ( ! this . reusableStream ) {
596581 throw new Error ( 'Stream not initialized' ) ;
@@ -606,8 +591,8 @@ export class ModelResult {
606591 * - Tool call argument deltas as { type: "delta", content: string }
607592 * - Preliminary results as { type: "preliminary_result", toolCallId, result }
608593 */
609- getToolStream ( ) : AsyncIterableIterator < ToolStreamEvent > {
610- return async function * ( this : ModelResult ) {
594+ getToolStream ( ) : AsyncIterableIterator < ToolStreamEvent < InferToolEventsUnion < TOOLS > > > {
595+ return async function * ( this : ModelResult < TOOLS > ) {
611596 await this . initStream ( ) ;
612597 if ( ! this . reusableStream ) {
613598 throw new Error ( 'Stream not initialized' ) ;
@@ -630,67 +615,7 @@ export class ModelResult {
630615 yield {
631616 type : 'preliminary_result' as const ,
632617 toolCallId,
633- result,
634- } ;
635- }
636- }
637- } . call ( this ) ;
638- }
639-
640- /**
641- * Stream events in chat format (compatibility layer).
642- * Note: This transforms responses API events into a chat-like format.
643- * Includes preliminary tool result events after tool execution.
644- *
645- * @remarks
646- * This is a compatibility method that attempts to transform the responses API
647- * stream into a format similar to the chat API. Due to differences in the APIs,
648- * this may not be a perfect mapping.
649- */
650- getFullChatStream ( ) : AsyncIterableIterator < ChatStreamEvent > {
651- return async function * ( this : ModelResult ) {
652- await this . initStream ( ) ;
653- if ( ! this . reusableStream ) {
654- throw new Error ( 'Stream not initialized' ) ;
655- }
656-
657- const consumer = this . reusableStream . createConsumer ( ) ;
658-
659- for await ( const event of consumer ) {
660- if ( ! ( 'type' in event ) ) {
661- continue ;
662- }
663-
664- // Transform responses events to chat-like format using type guards
665- if ( isOutputTextDeltaEvent ( event ) ) {
666- yield {
667- type : 'content.delta' as const ,
668- delta : event . delta ,
669- } ;
670- } else if ( isResponseCompletedEvent ( event ) ) {
671- yield {
672- type : 'message.complete' as const ,
673- response : event . response ,
674- } ;
675- } else {
676- // Pass through other events
677- yield {
678- type : event . type ,
679- event,
680- } ;
681- }
682- }
683-
684- // After stream completes, check if tools were executed and emit preliminary results
685- await this . executeToolsIfNeeded ( ) ;
686-
687- // Emit all preliminary results
688- for ( const [ toolCallId , results ] of this . preliminaryResults ) {
689- for ( const result of results ) {
690- yield {
691- type : 'tool.preliminary_result' as const ,
692- toolCallId,
693- result,
618+ result : result as InferToolEventsUnion < TOOLS > ,
694619 } ;
695620 }
696621 }
@@ -703,28 +628,28 @@ export class ModelResult {
703628 * and this will return the tool calls from the initial response.
704629 * Returns structured tool calls with parsed arguments.
705630 */
706- async getToolCalls ( ) : Promise < ParsedToolCall [ ] > {
631+ async getToolCalls ( ) : Promise < ParsedToolCall < TOOLS [ number ] > [ ] > {
707632 await this . initStream ( ) ;
708633 if ( ! this . reusableStream ) {
709634 throw new Error ( 'Stream not initialized' ) ;
710635 }
711636
712637 const completedResponse = await consumeStreamForCompletion ( this . reusableStream ) ;
713- return extractToolCallsFromResponse ( completedResponse ) ;
638+ return extractToolCallsFromResponse ( completedResponse ) as ParsedToolCall < TOOLS [ number ] > [ ] ;
714639 }
715640
716641 /**
717642 * Stream structured tool call objects as they're completed.
718643 * Each iteration yields a complete tool call with parsed arguments.
719644 */
720- getToolCallsStream ( ) : AsyncIterableIterator < ParsedToolCall > {
721- return async function * ( this : ModelResult ) {
645+ getToolCallsStream ( ) : AsyncIterableIterator < ParsedToolCall < TOOLS [ number ] > > {
646+ return async function * ( this : ModelResult < TOOLS > ) {
722647 await this . initStream ( ) ;
723648 if ( ! this . reusableStream ) {
724649 throw new Error ( 'Stream not initialized' ) ;
725650 }
726651
727- yield * buildToolCallStream ( this . reusableStream ) ;
652+ yield * buildToolCallStream ( this . reusableStream ) as AsyncIterableIterator < ParsedToolCall < TOOLS [ number ] > > ;
728653 } . call ( this ) ;
729654 }
730655
0 commit comments