@@ -53,9 +53,9 @@ configure({
5353
5454// ── 调试日志 — 保存原始 stderr 引用,不受 SDK log filter 影响 ────────────────
5555const _rawStderr = process . stderr . write . bind ( process . stderr )
56- function debugLog ( msg : string ) : void {
56+ function debugLog ( msg : string , traceId ?: string ) : void {
5757 if ( process . env . QODER_DEBUG === '1' ) {
58- _rawStderr ( `[QODER_DEBUG] ${ msg } \n` )
58+ _rawStderr ( `[QODER_DEBUG${ traceId ? ` ${ traceId } ` : '' } ] ${ msg } \n` )
5959 }
6060}
6161
@@ -514,9 +514,10 @@ export class QoderLanguageModel implements LanguageModelV2 {
514514 const cliPath = resolveQoderCLI ( )
515515 const qoderOptions = buildQoderQueryOptions ( options , this . modelId , cliPath , this . providerOptions )
516516
517- debugLog ( `doStream() called, modelId=${ this . modelId } , cliPath=${ cliPath } ` )
518- debugLog ( `doStream() prompt length=${ typeof prompt === 'string' ? prompt . length : 'non-string' } ` )
519- debugLog ( `doStream() qoderOptions.mcpServers=[${ Object . keys ( qoderOptions . mcpServers ?? { } ) . join ( ',' ) } ]` )
517+ const streamTraceId = randomUUID ( ) . slice ( 0 , 8 )
518+ debugLog ( `doStream() called, modelId=${ this . modelId } , cliPath=${ cliPath } ` , streamTraceId )
519+ debugLog ( `doStream() prompt length=${ typeof prompt === 'string' ? prompt . length : 'non-string' } ` , streamTraceId )
520+ debugLog ( `doStream() qoderOptions.mcpServers=[${ Object . keys ( qoderOptions . mcpServers ?? { } ) . join ( ',' ) } ]` , streamTraceId )
520521
521522 // opencode function 工具名集合(由 opencode 管理并执行,如 bash、read、context7_resolve-library-id 等)
522523 // 这些工具调用不带 providerExecuted,让 opencode 负责执行
@@ -529,7 +530,7 @@ export class QoderLanguageModel implements LanguageModelV2 {
529530 . filter ( ( t ) => t . type === 'function' )
530531 . map ( ( t ) => normalizeToolName ( t . name ) )
531532 )
532- debugLog ( `doStream() hasTools=${ hasTools } , functionToolNames=[${ [ ...functionToolNames ] . join ( ',' ) } ]` )
533+ debugLog ( `doStream() hasTools=${ hasTools } , functionToolNames=[${ [ ...functionToolNames ] . join ( ',' ) } ]` , streamTraceId )
533534
534535 // 每次 query 独立的 AbortController,用于中断后台 qodercli 进程
535536 const abortController = new AbortController ( )
@@ -591,6 +592,10 @@ export class QoderLanguageModel implements LanguageModelV2 {
591592 // 典型值:tool_use / end_turn
592593 let lastAssistantStopReason : string | undefined
593594
595+ // 一旦 task 这类需要外部完成的 function tool 已经发出并收到 SDK 回放的 tool_result,
596+ // 当前轮后续 assistant/text/tool 输出都应被抑制;上层必须等真实工具结果后再发起下一轮。
597+ let suppressFurtherAssistantContent = false
598+
594599 try {
595600 // query() 是单次查询的最优路径(QoderAgentSDKClient 是双向交互会话,每次 connect() 冷启动更慢)
596601 const qoderQuery = query ( { prompt, options : { ...qoderOptions , abortController } } )
@@ -599,15 +604,26 @@ export class QoderLanguageModel implements LanguageModelV2 {
599604 for await ( const msg of qoderQuery ) {
600605 sdkMsgCount ++
601606 const m = msg as Record < string , unknown >
602- debugLog ( `SDK msg #${ sdkMsgCount } : type=${ m . type } ${ m . subtype ? ` subtype=${ m . subtype } ` : '' } ` )
607+ debugLog ( `SDK msg #${ sdkMsgCount } : type=${ m . type } ${ m . subtype ? ` subtype=${ m . subtype } ` : '' } ` , streamTraceId )
603608 if ( m . type === 'system' && m . subtype === 'init' ) {
604609 const tools = Array . isArray ( m . tools ) ? m . tools . join ( ',' ) : ''
605610 const mcpServers = Array . isArray ( m . mcp_servers ) ? JSON . stringify ( m . mcp_servers ) : '[]'
606- debugLog ( `SDK init: tools=[${ tools } ] mcp_servers=${ mcpServers } ` )
611+ debugLog ( `SDK init: tools=[${ tools } ] mcp_servers=${ mcpServers } ` , streamTraceId )
607612 }
608613
609614 // ── stream_event:增量文本 / 增量工具输入(流式 CLI 支持时) ──
610615 if ( m . type === 'stream_event' ) {
616+ if ( suppressFurtherAssistantContent ) {
617+ const ev = ( m as { event : Record < string , unknown > } ) . event
618+ if ( ev . type === 'message_delta' && isRecord ( ev . delta ) && typeof ev . delta . stop_reason === 'string' ) {
619+ lastAssistantStopReason = ev . delta . stop_reason
620+ debugLog ( `message_delta (suppressed): stop_reason=${ ev . delta . stop_reason } ` , streamTraceId )
621+ } else {
622+ debugLog ( `suppressed stream_event after external-wait state: ${ String ( ev . type ) } ` , streamTraceId )
623+ }
624+ continue
625+ }
626+
611627 const ev = ( m as { event : Record < string , unknown > } ) . event
612628
613629 if ( ev . type === 'content_block_start' && isRecord ( ev . content_block ) ) {
@@ -619,7 +635,7 @@ export class QoderLanguageModel implements LanguageModelV2 {
619635 const toolName = normalizeToolName ( block . name )
620636 // normalizeToolName 统一处理:大小写 + AskUserQuestion→question + mcp__server__tool→server_tool
621637 const isProviderExecuted = hasTools && ! functionToolNames . has ( toolName )
622- debugLog ( `tool_use block_start: raw=${ block . name } → normalized=${ toolName } , inFunctionTools=${ functionToolNames . has ( toolName ) } , isProviderExecuted=${ isProviderExecuted } ` )
638+ debugLog ( `tool_use block_start: raw=${ block . name } → normalized=${ toolName } , inFunctionTools=${ functionToolNames . has ( toolName ) } , isProviderExecuted=${ isProviderExecuted } ` , streamTraceId )
623639 streamToolBlocks . set ( idx , { id : block . id , name : toolName , input : '' , isProviderExecuted } )
624640 if ( ! isProviderExecuted ) {
625641 controller . enqueue ( {
@@ -681,8 +697,13 @@ export class QoderLanguageModel implements LanguageModelV2 {
681697 input : normalizedInput ,
682698 } as LanguageModelV2StreamPart )
683699 emittedFunctionToolCall = true
684- debugLog ( `emitted tool-call to opencode: toolName=${ toolBlock . name } , id=${ toolBlock . id } ` )
700+ debugLog ( `emitted tool-call to opencode: toolName=${ toolBlock . name } , id=${ toolBlock . id } ` , streamTraceId )
685701 toolBlock . input = normalizedInput
702+
703+ if ( waitForExternalCompletionToolNames . has ( toolBlock . name ) ) {
704+ suppressFurtherAssistantContent = true
705+ debugLog ( `enter external-wait state immediately after tool-call for toolName=${ toolBlock . name } , tool_use_id=${ toolBlock . id } ` , streamTraceId )
706+ }
686707 }
687708 pendingToolCalls . set ( toolBlock . id , {
688709 toolName : toolBlock . name ,
@@ -699,11 +720,16 @@ export class QoderLanguageModel implements LanguageModelV2 {
699720 }
700721 } else if ( ev . type === 'message_delta' && isRecord ( ev . delta ) && typeof ev . delta . stop_reason === 'string' ) {
701722 lastAssistantStopReason = ev . delta . stop_reason
702- debugLog ( `message_delta: stop_reason=${ ev . delta . stop_reason } ` )
723+ debugLog ( `message_delta: stop_reason=${ ev . delta . stop_reason } ` , streamTraceId )
703724 }
704725
705726 // ── assistant:完整消息块(CLI 不支持流式时走此路径) ──────────
706727 } else if ( m . type === 'assistant' ) {
728+ if ( suppressFurtherAssistantContent ) {
729+ debugLog ( 'suppressed assistant message after external-wait state' , streamTraceId )
730+ continue
731+ }
732+
707733 const rawContent = ( m . message as Record < string , unknown > | undefined ) ?. content
708734 const content = Array . isArray ( rawContent ) ? rawContent : [ ]
709735 for ( const block of content ) {
@@ -747,6 +773,11 @@ export class QoderLanguageModel implements LanguageModelV2 {
747773 emittedFunctionToolCall = true
748774 // 更新 input 到 pendingToolCalls
749775 pendingToolCalls . set ( block . id , { toolName, input : inputJson , isProviderExecuted } )
776+
777+ if ( waitForExternalCompletionToolNames . has ( toolName ) ) {
778+ suppressFurtherAssistantContent = true
779+ debugLog ( `enter external-wait state immediately after assistant tool-call for toolName=${ toolName } , tool_use_id=${ block . id } ` , streamTraceId )
780+ }
750781 }
751782 }
752783 }
@@ -760,7 +791,7 @@ export class QoderLanguageModel implements LanguageModelV2 {
760791 if ( block . type !== 'tool_result' || typeof block . tool_use_id !== 'string' ) continue
761792
762793 const toolCall = pendingToolCalls . get ( block . tool_use_id )
763- debugLog ( `tool_result: tool_use_id=${ block . tool_use_id } , found=${ ! ! toolCall } , toolName=${ toolCall ?. toolName } , isProviderExecuted=${ toolCall ?. isProviderExecuted } ` )
794+ debugLog ( `tool_result: tool_use_id=${ block . tool_use_id } , found=${ ! ! toolCall } , toolName=${ toolCall ?. toolName } , isProviderExecuted=${ toolCall ?. isProviderExecuted } ` , streamTraceId )
764795 if ( ! toolCall ) continue
765796
766797 // CLI 内置工具(isProviderExecuted=true)的结果由 provider 自己消费,
@@ -778,9 +809,9 @@ export class QoderLanguageModel implements LanguageModelV2 {
778809 // ── result:会话结束 ────────────────────────────────────────
779810 } else if ( m . type === 'result' ) {
780811 const outstandingToolCallCount = pendingToolCalls . size
781- debugLog ( `result: subtype=${ m . subtype } , is_error=${ m . is_error } , pendingToolCalls=${ outstandingToolCallCount } , emittedFunctionToolCall=${ emittedFunctionToolCall } , lastStopReason=${ lastAssistantStopReason } ` )
812+ debugLog ( `result: subtype=${ m . subtype } , is_error=${ m . is_error } , pendingToolCalls=${ outstandingToolCallCount } , emittedFunctionToolCall=${ emittedFunctionToolCall } , lastStopReason=${ lastAssistantStopReason } ` , streamTraceId )
782813 if ( outstandingToolCallCount > 0 ) {
783- debugLog ( `result: outstanding tools: ${ [ ...pendingToolCalls . entries ( ) ] . map ( ( [ id , t ] ) => `${ t . toolName } (${ id } ,prov=${ t . isProviderExecuted } )` ) . join ( ', ' ) } ` )
814+ debugLog ( `result: outstanding tools: ${ [ ...pendingToolCalls . entries ( ) ] . map ( ( [ id , t ] ) => `${ t . toolName } (${ id } ,prov=${ t . isProviderExecuted } )` ) . join ( ', ' ) } ` , streamTraceId )
784815 }
785816
786817 // 关闭所有未关闭的文本块和推理块
@@ -819,7 +850,7 @@ export class QoderLanguageModel implements LanguageModelV2 {
819850 const hasOutstandingFunctionToolCalls = emittedFunctionToolCall && outstandingToolCallCount > 0
820851 const mappedFinishReason : LanguageModelV2FinishReason =
821852 hasOutstandingFunctionToolCalls ? 'tool-calls' : 'stop'
822- debugLog ( `finish: mappedFinishReason=${ mappedFinishReason } (emittedFunctionToolCall=${ emittedFunctionToolCall } , outstandingToolCallCount=${ outstandingToolCallCount } )` )
853+ debugLog ( `finish: mappedFinishReason=${ mappedFinishReason } (emittedFunctionToolCall=${ emittedFunctionToolCall } , outstandingToolCallCount=${ outstandingToolCallCount } )` , streamTraceId )
823854 controller . enqueue ( {
824855 type : 'finish' ,
825856 finishReason : decorateFinishReason ( mappedFinishReason ) ,
@@ -840,14 +871,14 @@ export class QoderLanguageModel implements LanguageModelV2 {
840871 }
841872
842873 if ( ! hasFinish ) {
843- debugLog ( 'stream ended without result message, emitting fallback finish=stop' )
874+ debugLog ( 'stream ended without result message, emitting fallback finish=stop' , streamTraceId )
844875 controller . enqueue ( {
845876 type : 'finish' ,
846877 finishReason : decorateFinishReason ( 'stop' ) ,
847878 usage : { inputTokens : 0 , outputTokens : 0 , totalTokens : 0 } ,
848879 } )
849880 }
850- debugLog ( 'stream complete, closing controller' )
881+ debugLog ( 'stream complete, closing controller' , streamTraceId )
851882 cleanup ( )
852883 controller . close ( )
853884 } catch ( err ) {
0 commit comments