@@ -1,301 +1,218 @@
import axios from "axios" ;
import { nanoid } from "nanoid" ;
import { consumeJsonEvents } from "@/lib/json-event-stream" ;
import type {
ChatCompletionPayload ,
ChatCompletionStreamState ,
GeminiPart ,
GeminiPayload ,
GeminiStreamState ,
ImageApiResponse ,
ResponseApiOutputItem ,
ResponseApiPayload ,
ResponseStreamState ,
ResponseToolCall ,
ToolResponseResult ,
ChatCompletionPayload , ChatCompletionStreamState , GeminiPart , GeminiPayload , GeminiStreamState ,
ImageApiResponse , ResponseApiPayload , ResponseStreamState , ResponseToolCall , ToolResponseResult ,
} from "@/services/api/image-contracts" ;
function resolveImageDataUrl ( i tem : Record < string, unknown > ) {
if ( typeof item . b64_json === "string" && item . b64_json ) return ` data:image/png;base64, ${ item . b64_json } ` ;
if ( typeof item . url === "string" && item . url ) return item . url ;
return null ;
type TextSink = ( text : string ) = > void ;
type TextState = { text : string ; reasoning : string } ;
function record ( value : unknown ) : Record < string , unknown > {
return value !== null && typeof value === "object" && ! Array . isArray ( value ) ? value as Record < string , unknown > : { } ;
}
export function parseImagePayload ( payload : ImageApiResponse ) {
if ( typeof payload . code === "number" && payload . code !== 0 ) throw new Error ( payload . msg || "请求失败" ) ;
const images =
payload . data
? . map ( resolveImageDataUrl )
. filter ( ( value ) : value is string = > Boolean ( value ) )
. map ( ( dataUrl ) = > ( { id : nanoid ( ) , dataUrl } ) ) || [ ] ;
if ( images . length === 0 ) throw new Error ( "接口没有返回图片" ) ;
return images ;
}
export function readAxiosError ( error : unknown , fallback : string ) {
if ( axios . isCancel ( error ) ) return "请求已取消" ;
if ( axios . isAxiosError < { error ? : { message? : string } ; msg? : string ; code? : number } > ( error ) ) {
const responseData = error . response ? . data ;
return responseData ? . msg || responseData ? . error ? . message || readStatusError ( error . response ? . status , fallback ) ;
}
if ( error instanceof DOMException && error . name === "AbortError" ) return "请求已取消" ;
return error instanceof Error ? error.message : fallback ;
}
export function readStatusError ( status : number | undefined , fallback : string ) {
if ( status === 401 || status === 403 ) return "鉴权失败,请检查 API Key、套餐权限或模型权限" ;
if ( status === 429 ) return "请求被限流或额度不足,请稍后重试" ;
return status ? ` ${ fallback } : ${ status } ` : fallback ;
}
export function parseChatCompletionPayload ( payload : ChatCompletionPayload ) : ToolResponseResult {
if ( typeof payload . code === "number" && payload . code !== 0 ) throw new Error ( payload . msg || "请求失败" ) ;
if ( payload . error ? . message ) throw new Error ( payload . error . message ) ;
const message = payload . choices ? . [ 0 ] ? . message ;
const toolCalls = ( message ? . tool_calls || [ ] )
. map ( ( call ) = > ( {
id : call.id || "" ,
type : "function" as const ,
function : { name : call.function?.name || "" , arguments : call.function?.arguments || "{}" } ,
} ) )
. filter ( ( call ) = > call . id && call . function . name ) ;
return { content : message?.content || "" , toolCalls , . . . ( message ? . reasoning_content ? { reasoning : message.reasoning_content } : { } ) } ;
}
export function parseToolResponse ( payload : ResponseApiPayload ) : ToolResponseResult {
const output = payload . output || [ ] ;
const content =
payload . output_text ||
output
. flatMap ( ( item ) = > ( item . type === "message" ? item . content || [ ] : [ ] ) )
. map ( ( item ) = > item . text || "" )
. join ( "" ) ;
const toolCalls = output
. filter ( ( item ) : item is Extract < ResponseApiOutputItem , { type ? : "function_call" } > = > item . type === "function_call" )
. map ( ( item ) = > ( {
id : item.call_id || item . id || "" ,
type : "function" as const ,
function : { name : item.name || "" , arguments : item.arguments || "{}" } ,
} ) )
. filter ( ( item ) = > item . id && item . function . name ) ;
return { content , toolCalls } ;
}
function isRecord ( value : unknown ) : value is Record < string , unknown > {
return Boolean ( value && typeof value === "object" && ! Array . isArray ( value ) ) ;
}
function stringValue ( value : unknown ) {
function text ( value : unknown ) : string {
return typeof value === "string" ? value : "" ;
}
export function responseErrorMessage ( value : unknown ) {
if ( ! isRecord ( value ) ) return "" ;
const error = isRecord ( value . error ) ? value.error : undefined ;
const response = isRecord ( value . response ) ? value.response : undefined ;
const responseError = response && isRecord ( response . error ) ? response.error : undefined ;
return stringValue ( value . msg ) || stringValue ( error ? . message ) || stringValue ( responseError ? . message ) ;
}
export function validateResponsePayload ( payload : ResponseApiPayload ) {
function assertEnvelope ( payload : { code? : number ; msg? : string } ) {
if ( typeof payload . code === "number" && payload . code !== 0 ) throw new Error ( payload . msg || "请求失败" ) ;
if ( payload . error ? . message ) throw new Error ( payload . error . message ) ;
}
export function validateGeminiPayload ( payload : GeminiPayload ) {
if ( payload . error ? . message ) throw new Error ( payload . error . message ) ;
i f ( payload . promptFeedback ? . blockReason ) throw new Error ( ` Gemini 拒绝了本次请求: ${ payload . promptFeedback . blockReason } ` ) ;
function imagesFrom ( sources : Iterable < string | undefined | null > , emptyMessage : string ) {
const images : { id : string ; dataUrl : string } [ ] = [ ] ;
for ( const dataUrl of sources ) if ( dataUrl ) images . push ( { id : nanoid ( ) , dataUrl } ) ;
if ( ! images . length ) throw new Error ( emptyMessage ) ;
return images ;
}
export async function readFetchError ( response : Response , fallback : string ) {
const text = await response . text ( ) ;
if ( ! text ) return readStatusError ( response . status , fallback ) ;
try {
return responseErrorMessage ( JSON . parse ( text ) ) || readStatusError ( response . stat us, fallback ) ;
} catch {
return text . slice ( 0 , 300 ) || readStatusError ( response . status , fallback ) ;
}
function addTool ( result : ToolResponseResult , id : string | undefined , name : string | undefined , args : string | undefined , signature? : string ) {
if ( ! id || ! name ) return ;
const call : ResponseToolCall = { type : "function" , id , function : { name , arguments : args || "{}" } } ;
if ( signature ) call . thoughtSignature = signature ;
result . toolCalls . p ush ( call ) ;
}
export async function readJsonPayload < T > ( response : Response , fallback : string ) : Promise < T > {
const text = await response . text ( ) ;
try {
return JSON . parse ( text ) as T ;
} catch {
if ( /^\s*(?:<!doctype|<html)/i . test ( text ) ) throw new Error ( "后端代理返回了前端网页,请检查 VITE_CANVAS_BACKEND_URL 和反向代理配置" ) ;
throw new Error ( ` ${ fallback } :接口没有返回有效 JSON ` ) ;
}
function * geminiParts ( payload : GeminiPayload ) : Generator < GeminiPart > {
for ( const candidate of payload . candidates ? ? [ ] ) yield * candidate . content ? . parts ? ? [ ] ;
}
function parseEventBlock ( block : string ) {
const data = block
. split ( /\r?\n/ )
. filte r( ( line ) = > line . startsWith ( "data:" ) )
. map ( ( line ) = > line . slice ( 5 ) . replace ( /^ / , "" ) )
. join ( "\n" )
. trim ( ) ;
return data && data !== "[DONE]" ? data : undefined ;
export function parseImagePayload ( payload : ImageApiResponse ) {
assertEnvelope ( payload ) ;
function * sources() {
fo r ( const item of payload . data ? ? [ ] ) {
const encoded = text ( item . b64_json ) ;
yield encoded ? ` data:image/png;base64, ${ encoded } ` : text ( item . url ) ;
}
}
return imagesFrom ( sources ( ) , "接口没有返回图片" ) ;
}
function consumeResponseStreamBlock ( block : string , state : ResponseStreamState , onDelta ? : ( text : string ) = > void , onReasoning ? : ( text : string ) = > void ) {
const data = parseEventBlock ( block ) ;
if ( ! data ) return ;
const event = JSON . parse ( data ) as Record < string , unknown > ;
const type = stringValue ( event . type ) ;
const errorMessage = responseErrorMessage ( ev ent) ;
if ( errorMessage ) state . error = errorMessage ;
if ( type === "response.output_text.delta" && typeof event . delta === "string" ) {
state . text += event . delta ;
onDelta ? . ( state . text ) ;
}
if ( type === "response.output_text.done" && ! state . text && typeof event . text === "string" ) {
state . text = event . text ;
onDelta ? . ( state . text ) ;
}
if ( [ "response.reasoning.delta" , "response.reasoning_text.delta" , "response.reasoning_summary_text.delta" ] . includes ( type ) && typeof event . delta === "string" ) {
state . reasoning += event . delta ;
onReasoning ? . ( state . reasoning ) ;
}
if ( [ "response.reasoning.done" , "response.reasoning_text.done" , "response.reasoning_summary_text.done" ] . includes ( type ) && ! state . reasoning && typeof event . text === "string" ) {
state . reasoning = event . text ;
onReasoning ? . ( state . reasoning ) ;
}
if ( type === "response.completed" && isRecord ( event . response ) ) state . payload = event . response as ResponseApiPayload ;
else if ( Array . isArray ( event . output ) ) state . payload = event as ResponseApiPayload ;
export function parseChatCompletionPayload ( payload : ChatCompletionPayload ) : ToolResponseResult {
validateResponsePayload ( payload ) ;
const message = payload . choices ? . [ 0 ] ? . message ;
const result : ToolResponseResult = { content : message?.content || "" , toolCalls : [ ] } ;
if ( message ? . reasoning_content ) result . reasoning = message . reasoning_content ;
for ( const call of message ? . tool_calls ? ? [ ] ) addTool ( result , call . id , call . function ? . name , call . function ? . argum ents ) ;
return result ;
}
export function consumeResponseStreamText ( state : ResponseStreamState , text : string , onDelta ? : ( text : string ) = > void , onReasoning ? : ( text : string ) = > void , flush = false ) {
state . buffer + = text ;
for ( ; ; ) {
const match = sta te. buffer . match ( /\r?\n\r?\n/ ) ;
if ( ! match ) break ;
const index = match . index ? ? 0 ;
consumeResponseStreamBlock ( state . buffer . slice ( 0 , index ) , state , onDelta , onReasoning ) ;
state . buffer = state . buffer . slice ( index + match [ 0 ] . length ) ;
}
if ( flush && state . buffer . trim ( ) ) {
consumeResponseStreamBlock ( state . buffer , state , onDelta , onReasoning ) ;
state . buffer = "" ;
}
}
function consumeChatCompletionStreamBlock ( block : string , state : ChatCompletionStreamState , onDelta ? : ( text : string ) = > void , onReasoning ? : ( text : string ) = > void ) {
const data = parseEventBlock ( block ) ;
if ( ! data ) return ;
const event = JSON . parse ( data ) as Record < string , unknown > ;
const errorMessage = responseErrorMessage ( event ) ;
if ( errorMessage ) state . error = errorMessage ;
const choices = Array . isArray ( event . choices ) ? event . choices : [ ] ;
const choice = isRecord ( choices [ 0 ] ) ? choices [ 0 ] : undefined ;
const delta = choice && isRecord ( choice . delta ) ? choice.delta : undefined ;
if ( ! delta ) return ;
if ( typeof delta . content === "string" ) {
state . text += delta . content ;
onDelta ? . ( state . text ) ;
}
const reasoningDelta = stringValue ( delta . reasoning_content ) || stringValue ( delta . reasoning ) || stringValue ( delta . reasoning_text ) ;
if ( reasoningDelta ) {
state . reasoning += reasoningDelta ;
onReasoning ? . ( state . reasoning ) ;
}
const chunks = Array . isArray ( delta . tool_calls ) ? delta . tool_calls : [ ] ;
chunks . forEach ( ( value , fallbackIndex ) = > {
if ( ! isRecord ( value ) ) return ;
const callIndex = typeof value . index === "number" ? value.index : fallbackIndex ;
const current = state . toolCalls . get ( callIndex ) || { id : "" , name : "" , arguments : "" } ;
const fn = isRecord ( value . function ) ? value.function : undefined ;
state . toolCalls . set ( callIndex , {
id : stringValue ( value . id ) || current . id ,
name : stringValue ( fn ? . name ) || current . name ,
arguments : current.arguments + stringValue ( fn ? . arguments ) ,
} ) ;
} ) ;
}
export function consumeChatCompletionStreamText ( state : ChatCompletionStreamState , text : string , onDelta ? : ( text : string ) = > void , onReasoning ? : ( text : string ) = > void , flush = false ) {
state . buffer += text ;
for ( ; ; ) {
const match = state . buffer . match ( /\r?\n\r?\n/ ) ;
if ( ! match ) break ;
const index = match . index ? ? 0 ;
consumeChatCompletionStreamBlock ( state . buffer . slice ( 0 , index ) , state , onDelta , onReasoning ) ;
state . buffer = state . buffer . slice ( index + match [ 0 ] . length ) ;
}
if ( flush && state . buffer . trim ( ) ) {
consumeChatCompletionStreamBlock ( state . buffer , state , onDelta , onReasoning ) ;
state . buffer = "" ;
}
}
function consumeGeminiStreamBlock ( block : string , state : GeminiStreamState , onDelta ? : ( text : string ) = > void , onReasoning ? : ( text : string ) = > void ) {
const data = parseEventBlock ( block ) ;
if ( ! data ) return ;
const result = parseGeminiToolResponse ( JSON . parse ( data ) as GeminiPayload ) ;
if ( result . reasoning ) {
state . reasoning += result . reasoning ;
onReasoning ? . ( state . reasoning ) ;
}
if ( result . content ) {
state . text += result . content ;
onDelta ? . ( state . text ) ;
}
state . toolCalls . push ( . . . result . toolCalls ) ;
}
export function consumeGeminiStreamText ( state : GeminiStreamState , text : string , onDelta ? : ( text : string ) = > void , onReasoning ? : ( text : string ) = > void , flush = false ) {
state . buffer += text ;
for ( ; ; ) {
const match = state . buffer . match ( /\r?\n\r?\n/ ) ;
if ( ! match ) break ;
const index = match . index ? ? 0 ;
consumeGeminiStreamBlock ( state . buffer . slice ( 0 , index ) , state , onDelta , onReasoning ) ;
state . buffer = state . buffer . slice ( index + match [ 0 ] . length ) ;
}
if ( flush && state . buffer . trim ( ) ) {
consumeGeminiStreamBlock ( state . buffer , state , onDelta , onReasoning ) ;
state . buffer = "" ;
export function parseToolResponse ( payload : ResponseApiPayload ) : ToolResponseResult {
const result : ToolResponseResult = { content : "" , toolCalls : [ ] } ;
for ( const item of payload . output ? ? [ ] ) {
switch ( i tem . type ) {
case "message" :
for ( const part of item . content ? ? [ ] ) result . content += part . text || "" ;
break ;
case "function_call" :
addTool ( result , item . call_id || item . id , item . name , item . arguments ) ;
break ;
}
}
if ( payload . output_text ) result . content = payload . output_text ;
return result ;
}
export function parseGeminiToolResponse ( payload : GeminiPayload ) : ToolResponseResult {
validateGeminiPayload ( payload ) ;
const parts = payload . candidates ? . flatMap ( ( candidate ) = > candidate . content ? . parts || [ ] ) || [ ] ;
const content = parts
. filte r ( ( part ) = > ! p art. thought )
. map ( ( part ) = > part . text || "" )
. join ( "" ) ;
const reasoning = parts
. filter ( ( part ) = > part . thought)
. map ( ( part ) = > part . text || "" )
. jo in( "" ) ;
const toolCalls = parts
. map ( ( part ) = > part . functionCall )
. filter ( ( call ) : call is NonNullable < GeminiPart [ "functionCall" ] > = > Boolean ( call ? . name ) )
. map ( ( call ) = > {
const part = parts . find ( ( item ) = > item . functionCall === call ) ;
const thoughtSignature = part ? . thoughtSignature || part ? . thought_signature ;
return {
id : call.id || nanoid ( ) ,
type : "function" as const ,
function : { name : call.name || "" , arguments : JSON.stringify ( call . args || { } ) } ,
. . . ( thoughtSignature ? { thoughtSignature } : { } ) ,
} ;
} ) ;
return { content , toolCalls , . . . ( reasoning ? { reasoning } : { } ) } ;
const result : ToolResponseResult = { content : "" , toolCalls : [ ] } ;
let reasoning = "" ;
fo r ( const part of geminiP arts ( payload ) ) {
if ( part . thought ) reasoning += part . text || "" ;
else result . content += part . text || "" ;
const call = part. functionCall ;
if ( call ? . name ) addTool ( result , call . id || nanoid ( ) , call . name , JSON . stringify ( call . args || { } ) , part . thoughtSignature || part . thought_signature ) ;
}
if ( reason ing ) result . reasoning = reasoning ;
return result ;
}
export function parseGeminiImagePayload ( payload : GeminiPayload ) {
validateGeminiPayload ( payload ) ;
const images =
payload . candidates
? . flatMap ( ( candidate ) = > candidate . content ? . parts || [ ] )
. map ( ( part ) = > {
const inlineData = part . inlineData || ( part . inline_data ? { mimeType : part.inline_data.mimeType || part . inline_data . mime_type , data : part.inline_data.data } : undefined ) ;
if ( inlineData ? . data ) return ` data: ${ inlineData . mimeType || "image/png" } ;base64, ${ inlineData . data } ` ;
return part . fileData ? . fileUri || null ;
} )
. filter ( ( value ) : value is string = > Boolean ( value ) )
. map ( ( dataUrl ) = > ( { id : nanoid ( ) , dataUrl } ) ) || [ ] ;
if ( ! images . length ) throw new Error ( "Gemini 接口没有返回图片" ) ;
return images ;
function * sources() {
for ( const part of geminiParts ( payload ) ) {
const inline = part . inlineData ? ? part . inline_data ;
const mime = part . inlineData ? part.inlineData.mimeType : part.inline_data?.mimeType || part . inline_data ? . mime_type ;
yield inline ? . data ? ` data: $ {mime || "image/png" } ;base64, ${ inline . data } ` : part . fileData ? . fileUri ;
}
}
return imagesFrom ( sources ( ) , "Gemini 接口没有返回图片" ) ;
}
export function responseErrorMessage ( value : unknown ) {
const payload = record ( value ) ;
return text ( payload . msg ) || text ( record ( payload . error ) . message ) || text ( record ( record ( payload . response ) . error ) . message ) ;
}
export function validateResponsePayload ( payload : ResponseApiPayload ) {
assertEnvelope ( payload ) ;
if ( payload . error ? . message ) throw new Error ( payload . error . message ) ;
}
export function validateGeminiPayload ( payload : GeminiPayload ) {
const failure = payload . error ? . message || ( payload . promptFeedback ? . blockReason ? ` Gemini 拒绝了本次请求: ${ payload . promptFeedback . blockReason } ` : "" ) ;
if ( failure ) throw new Error ( failure ) ;
}
export function readStatusError ( status : number | undefined , fallback : string ) {
const known : Record < number , string > = {
401 : "鉴权失败,请检查 API Key、套餐权限或模型权限" ,
403 : "鉴权失败,请检查 API Key、套餐权限或模型权限" ,
429 : "请求被限流或额度不足,请稍后重试" ,
} ;
return status ? known [ status ] || ` ${ fallback } : ${ status } ` : fallback ;
}
export function readAxiosError ( error : unknown , fallback : string ) {
if ( axios . isCancel ( error ) || ( error instanceof DOMException && error . name === "AbortError" ) ) return "请求已取消" ;
if ( ! axios . isAxiosError ( error ) ) return error instanceof Error ? error.message : fallback ;
const payload = record ( error . response ? . data ) ;
return text ( payload . msg ) || text ( record ( payload . error ) . message ) || readStatusError ( error . response ? . status , fallback ) ;
}
export async function readFetchError ( response : Response , fallback : string ) {
const body = await response . text ( ) ;
const status = readStatusError ( response . status , fallback ) ;
if ( ! body ) return status ;
let payload : unknown ;
try { payload = JSON . parse ( body ) ; } catch { return body . slice ( 0 , 300 ) || status ; }
return responseErrorMessage ( payload ) || status ;
}
export async function readJsonPayload < T > ( response : Response , fallback : string ) : Promise < T > {
const body = await response . text ( ) ;
try { return JSON . parse ( body ) as T ; } catch {
const detail = /^\s*(?:<!doctype|<html)/i . test ( body )
? "后端代理返回了前端网页,请检查 VITE_CANVAS_BACKEND_URL 和反向代理配置"
: ` ${ fallback } :接口没有返回有效 JSON ` ;
throw new Error ( detail ) ;
}
}
function updateText ( state : TextState , field : keyof TextState , value : unknown , sink : TextSink | undefined , final = false ) {
if ( typeof value !== "string" || ( final && state [ field ] ) ) return ;
state [ field ] = final ? value : state [ field ] + value ;
sink ? . ( state [ field ] ) ;
}
const responseChannels : Record < string , keyof TextState > = {
"response.output_text" : "text" ,
"response.reasoning" : "reasoning" ,
"response.reasoning_text" : "reasoning" ,
"response.reasoning_summary_text" : "reasoning" ,
} ;
export function consumeResponseStreamText ( state : ResponseStreamState , chunk : string , onDelta? : TextSink , onReasoning? : TextSink , flush = false ) {
consumeJsonEvents ( state , chunk , ( payload ) = > {
const event = record ( payload ) ;
const failure = responseErrorMessage ( event ) ;
if ( failure ) state . error = failure ;
const type = text ( event . type ) ;
const separator = type . lastIndexOf ( "." ) ;
const field = Object . hasOwn ( responseChannels , type . slice ( 0 , separator ) ) ? responseChannels [ type . slice ( 0 , separator ) ] : undefined ;
const action = type . slice ( separator + 1 ) ;
if ( field && ( action === "delta" || action === "done" ) ) {
updateText ( state , field , action === "done" ? event.text : event.delta , field === "text" ? onDelta : onReasoning , action === "done" ) ;
}
if ( type === "response.completed" && event . response !== null && typeof event . response === "object" && ! Array . isArray ( event . response ) ) {
state . payload = event . response as ResponseApiPayload ;
} else if ( Array . isArray ( event . output ) ) state . payload = event as ResponseApiPayload ;
} , flush ) ;
}
export function consumeChatCompletionStreamText ( state : ChatCompletionStreamState , chunk : string , onDelta? : TextSink , onReasoning? : TextSink , flush = false ) {
consumeJsonEvents ( state , chunk , ( payload ) = > {
const event = record ( payload ) ;
const failure = responseErrorMessage ( event ) ;
if ( failure ) state . error = failure ;
const first = Array . isArray ( event . choices ) ? record ( event . choices [ 0 ] ) : { } ;
const delta = record ( first . delta ) ;
updateText ( state , "text" , delta . content , onDelta ) ;
const reasoning = text ( delta . reasoning_content ) || text ( delta . reasoning ) || text ( delta . reasoning_text ) ;
if ( reasoning ) updateText ( state , "reasoning" , reasoning , onReasoning ) ;
if ( ! Array . isArray ( delta . tool_calls ) ) return ;
for ( const [ fallback , raw ] of delta . tool_calls . entries ( ) ) {
if ( raw === null || typeof raw !== "object" || Array . isArray ( raw ) ) continue ;
const call = record ( raw ) ;
const index = typeof call . index === "number" ? call.index : fallback ;
const previous = state . toolCalls . get ( index ) ;
const fn = record ( call . function ) ;
state . toolCalls . set ( index , {
id : text ( call . id ) || previous ? . id || "" ,
name : text ( fn . name ) || previous ? . name || "" ,
arguments : ( previous ? . arguments || "" ) + text ( fn . arguments ) ,
} ) ;
}
} , flush ) ;
}
export function consumeGeminiStreamText ( state : GeminiStreamState , chunk : string , onDelta? : TextSink , onReasoning? : TextSink , flush = false ) {
consumeJsonEvents ( state , chunk , ( payload ) = > {
const result = parseGeminiToolResponse ( payload as GeminiPayload ) ;
if ( result . reasoning ) updateText ( state , "reasoning" , result . reasoning , onReasoning ) ;
if ( result . content ) updateText ( state , "text" , result . content , onDelta ) ;
state . toolCalls . push ( . . . result . toolCalls ) ;
} , flush ) ;
}