@@ -43,18 +43,41 @@ type AnthropicContentBlock = AnthropicTextBlock | AnthropicToolUseBlock;
4343
4444const DEFAULT_TIMEOUT = 600000 ; // 10 minutes — LLM relay servers can be slow; user can cancel manually
4545
46- function extractResponsesOutputText ( output : Array < { type : string ; content ?: Array < { type : string ; text : string } > } > ) : string {
46+ interface ResponsesOutputItem {
47+ type : string ;
48+ content ?: Array < { type : string ; text ?: unknown } > ;
49+ }
50+
51+ function extractResponsesOutputText ( output : ResponsesOutputItem [ ] ) : string {
4752 let content = "" ;
4853 for ( const item of output ) {
4954 if ( item . type === "message" && Array . isArray ( item . content ) ) {
5055 content += item . content
51- . filter ( ( c ) => c . type === "output_text" )
52- . map ( ( c ) => c . text )
56+ . filter ( ( c ) => c . type === "output_text" && typeof c . text === "string" )
57+ . map ( ( c ) => c . text as string )
5358 . join ( "" ) ;
5459 }
5560 }
5661 return content ;
5762}
63+
64+ function readResponsesOutputText ( data : {
65+ output_text ?: unknown ;
66+ output ?: ResponsesOutputItem [ ] ;
67+ } ) : string {
68+ const content =
69+ typeof data . output_text === "string" && data . output_text . length > 0
70+ ? data . output_text
71+ : Array . isArray ( data . output )
72+ ? extractResponsesOutputText ( data . output )
73+ : "" ;
74+
75+ if ( content . length === 0 ) {
76+ throw new Error ( `LLM 响应格式异常: 缺少 output_text 字段 — ${ JSON . stringify ( data ) . slice ( 0 , 200 ) } ` ) ;
77+ }
78+
79+ return content ;
80+ }
5881
5982/**
6083 * Sanitize string content in LLM request body to remove control characters
@@ -475,16 +498,14 @@ export class LLMRouter {
475498 status ?: string ;
476499 incomplete_details ?: ResponsesIncompleteDetails ;
477500 error ?: { message ?: string } ;
478- output : Array < {
479- type : string ;
501+ output : Array < ResponsesOutputItem & {
480502 id ?: string ;
481503 name ?: string ;
482504 arguments ?: string ;
483- content ?: Array < { type : string ; text : string } > ;
484- } > ;
485- output_text ?: string ;
486- usage ?: { input_tokens : number ; output_tokens : number } ;
487- } > ( response ) ;
505+ } > ;
506+ output_text ?: string ;
507+ usage ?: { input_tokens : number ; output_tokens : number } ;
508+ } > ( response ) ;
488509
489510 totalPromptTokens += data . usage ?. input_tokens || 0 ;
490511 totalCompletionTokens += data . usage ?. output_tokens || 0 ;
@@ -527,19 +548,14 @@ export class LLMRouter {
527548 } ) ;
528549 }
529550 continue ;
530- }
531-
532- // No function calls → extract text
533- let content = extractResponsesOutputText ( data . output ) ;
534-
535- // Fallback: check output_text at top level
536- if ( ! content && typeof data . output_text === "string" ) {
537- content = data . output_text ;
538- }
539-
540- if ( onChunk && content ) onChunk ( content ) ;
541- return {
542- content,
551+ }
552+
553+ // No function calls → extract text
554+ const content = readResponsesOutputText ( data ) ;
555+
556+ if ( onChunk && content ) onChunk ( content ) ;
557+ return {
558+ content,
543559 promptTokens : totalPromptTokens ,
544560 completionTokens : totalCompletionTokens ,
545561 } ;
@@ -627,10 +643,7 @@ export class LLMRouter {
627643 incomplete_details ?: ResponsesIncompleteDetails ;
628644 error ?: { message ?: string } ;
629645 output_text ?: string ;
630- output ?: Array < {
631- type : string ;
632- content ?: Array < { type : string ; text : string } > ;
633- } > ;
646+ output ?: ResponsesOutputItem [ ] ;
634647 usage ?: { input_tokens : number ; output_tokens : number } ;
635648 } > ( response ) ;
636649 if ( data . status === "incomplete" ) {
@@ -642,7 +655,7 @@ export class LLMRouter {
642655 if ( typeof data . output_text !== "string" && ! Array . isArray ( data . output ) ) {
643656 throw new Error ( `LLM 响应格式异常: 缺少 output 字段 — ${ JSON . stringify ( data ) . slice ( 0 , 200 ) } ` ) ;
644657 }
645- const content = data . output_text || ( Array . isArray ( data . output ) ? extractResponsesOutputText ( data . output ) : "" ) ;
658+ const content = readResponsesOutputText ( data ) ;
646659 return {
647660 content,
648661 promptTokens : data . usage ?. input_tokens || 0 ,
0 commit comments