@@ -69,6 +69,20 @@ export class ResponseWrapper {
6969 this . options = options ;
7070 }
7171
72+ /**
73+ * Type guard to check if a value is a non-streaming response
74+ */
75+ private isNonStreamingResponse ( value : unknown ) : value is models . OpenResponsesNonStreamingResponse {
76+ return (
77+ value !== null &&
78+ typeof value === "object" &&
79+ "id" in value &&
80+ "object" in value &&
81+ "output" in value &&
82+ ! ( "toReadableStream" in value )
83+ ) ;
84+ }
85+
7286 /**
7387 * Initialize the stream if not already started
7488 * This is idempotent - multiple calls will return the same promise
@@ -284,19 +298,57 @@ export class ResponseWrapper {
284298 value as EventStream < models . OpenResponsesStreamEvent > ,
285299 ) ;
286300 currentResponse = await consumeStreamForCompletion ( stream ) ;
301+ } else if ( this . isNonStreamingResponse ( value ) ) {
302+ currentResponse = value ;
287303 } else {
288- currentResponse = value as models . OpenResponsesNonStreamingResponse ;
304+ throw new Error ( "Unexpected response type from API" ) ;
289305 }
290306
291307 currentRound ++ ;
292308 }
293309
310+ // Validate the final response has required fields
311+ if ( ! currentResponse || ! currentResponse . id || ! currentResponse . output ) {
312+ throw new Error ( "Invalid final response: missing required fields" ) ;
313+ }
314+
315+ // Ensure the response is in a completed state (has output content)
316+ if ( ! Array . isArray ( currentResponse . output ) || currentResponse . output . length === 0 ) {
317+ throw new Error ( "Invalid final response: empty or invalid output" ) ;
318+ }
319+
294320 this . finalResponse = currentResponse ;
295321 } ) ( ) ;
296322
297323 return this . toolExecutionPromise ;
298324 }
299325
326+ /**
327+ * Internal helper to get the message after tool execution
328+ */
329+ private async getMessageInternal ( ) : Promise < models . AssistantMessage > {
330+ await this . executeToolsIfNeeded ( ) ;
331+
332+ if ( ! this . finalResponse ) {
333+ throw new Error ( "Response not available" ) ;
334+ }
335+
336+ return extractMessageFromResponse ( this . finalResponse ) ;
337+ }
338+
339+ /**
340+ * Internal helper to get the text after tool execution
341+ */
342+ private async getTextInternal ( ) : Promise < string > {
343+ await this . executeToolsIfNeeded ( ) ;
344+
345+ if ( ! this . finalResponse ) {
346+ throw new Error ( "Response not available" ) ;
347+ }
348+
349+ return extractTextFromResponse ( this . finalResponse ) ;
350+ }
351+
300352 /**
301353 * Get the completed message from the response.
302354 * This will consume the stream until completion, execute any tools, and extract the first message.
@@ -307,16 +359,7 @@ export class ResponseWrapper {
307359 return this . messagePromise ;
308360 }
309361
310- this . messagePromise = ( async ( ) : Promise < models . AssistantMessage > => {
311- await this . executeToolsIfNeeded ( ) ;
312-
313- if ( ! this . finalResponse ) {
314- throw new Error ( 'Response not available' ) ;
315- }
316-
317- return extractMessageFromResponse ( this . finalResponse ) ;
318- } ) ( ) ;
319-
362+ this . messagePromise = this . getMessageInternal ( ) ;
320363 return this . messagePromise ;
321364 }
322365
@@ -329,16 +372,7 @@ export class ResponseWrapper {
329372 return this . textPromise ;
330373 }
331374
332- this . textPromise = ( async ( ) => {
333- await this . executeToolsIfNeeded ( ) ;
334-
335- if ( ! this . finalResponse ) {
336- throw new Error ( 'Response not available' ) ;
337- }
338-
339- return extractTextFromResponse ( this . finalResponse ) ;
340- } ) ( ) ;
341-
375+ this . textPromise = this . getTextInternal ( ) ;
342376 return this . textPromise ;
343377 }
344378
0 commit comments