fix: Responses API 工具调用事件格式错误导致 Codex CLI 无法多轮工作

修复 6 个问题:

1. requestId 未传入 convertStreamChunk, 每个 chunk 生成新 UUID,
   导致 state 不共享, response.completed 时 fullText 为空
2. TOTAL_CONTEXT_TOKENS=1000000 但 Claude 实际上下文只有 200K,
   token 数虚报 5 倍触发异常压缩
3. 默认模型名硬编码为 gpt-4.1-2025-04-14
4. 工具调用参数事件类型用了 response.custom_tool_call_input.delta,
   应为 response.function_call_arguments.delta
5. response.completed 的 output 只有空 message, 缺少 function_call 项,
   Codex 收不到工具调用结果无法进入多轮
6. 缺少 response.function_call_arguments.done 和工具专属
   response.output_item.done 事件
This commit is contained in:
wsyh4567 2026-03-28 11:40:29 +08:00
parent 8e0541d766
commit 4ee01706aa
3 changed files with 115 additions and 70 deletions

View file

@ -28,7 +28,14 @@ import {
generateOutputTextDone, generateOutputTextDone,
generateContentPartDone, generateContentPartDone,
generateOutputItemDone, generateOutputItemDone,
generateResponseCompleted generateResponseCompleted,
generateOutputTextDelta,
streamStateManager,
startToolCall,
finishToolCall,
generateFunctionCallArgsDelta,
generateFunctionCallArgsDone,
generateFunctionCallOutputItemDone
} from '../../providers/openai/openai-responses-core.mjs'; } from '../../providers/openai/openai-responses-core.mjs';
/** /**
@ -81,14 +88,14 @@ export class ClaudeConverter extends BaseConverter {
/** /**
* 转换流式响应块 * 转换流式响应块
*/ */
convertStreamChunk(chunk, targetProtocol, model) { convertStreamChunk(chunk, targetProtocol, model, requestId) {
switch (targetProtocol) { switch (targetProtocol) {
case MODEL_PROTOCOL_PREFIX.OPENAI: case MODEL_PROTOCOL_PREFIX.OPENAI:
return this.toOpenAIStreamChunk(chunk, model); return this.toOpenAIStreamChunk(chunk, model);
case MODEL_PROTOCOL_PREFIX.GEMINI: case MODEL_PROTOCOL_PREFIX.GEMINI:
return this.toGeminiStreamChunk(chunk, model); return this.toGeminiStreamChunk(chunk, model);
case MODEL_PROTOCOL_PREFIX.OPENAI_RESPONSES: case MODEL_PROTOCOL_PREFIX.OPENAI_RESPONSES:
return this.toOpenAIResponsesStreamChunk(chunk, model); return this.toOpenAIResponsesStreamChunk(chunk, model, requestId);
case MODEL_PROTOCOL_PREFIX.CODEX: case MODEL_PROTOCOL_PREFIX.CODEX:
return this.toCodexStreamChunk(chunk, model); return this.toCodexStreamChunk(chunk, model);
default: default:
@ -1694,9 +1701,11 @@ export class ClaudeConverter extends BaseConverter {
// 对于 tool_use 类型,添加工具调用项 // 对于 tool_use 类型,添加工具调用项
if (contentBlock && contentBlock.type === 'tool_use') { if (contentBlock && contentBlock.type === 'tool_use') {
startToolCall(responseId, contentBlock.id, contentBlock.name);
events.push({ events.push({
item: { item: {
id: contentBlock.id, id: contentBlock.id,
call_id: contentBlock.id,
type: "function_call", type: "function_call",
name: contentBlock.name, name: contentBlock.name,
arguments: "", arguments: "",
@ -1715,13 +1724,7 @@ export class ClaudeConverter extends BaseConverter {
// 处理文本增量 // 处理文本增量
if (delta && delta.type === 'text_delta') { if (delta && delta.type === 'text_delta') {
events.push({ events.push(generateOutputTextDelta(responseId, delta.text || ""));
delta: delta.text || "",
item_id: `msg_${uuidv4().replace(/-/g, '')}`,
output_index: claudeChunk.index || 0,
sequence_number: 3,
type: "response.output_text.delta"
});
} }
// 处理推理内容增量 // 处理推理内容增量
else if (delta && delta.type === 'thinking_delta') { else if (delta && delta.type === 'thinking_delta') {
@ -1735,61 +1738,51 @@ export class ClaudeConverter extends BaseConverter {
} }
// 处理工具调用参数增量 // 处理工具调用参数增量
else if (delta && delta.type === 'input_json_delta') { else if (delta && delta.type === 'input_json_delta') {
events.push({ const state = streamStateManager.getOrCreateState(responseId);
delta: delta.partial_json || "", const itemId = state.currentToolCall ? state.currentToolCall.id : 'unknown';
item_id: `call_${uuidv4().replace(/-/g, '')}`, events.push(generateFunctionCallArgsDelta(
output_index: claudeChunk.index || 0, responseId, itemId, claudeChunk.index || 0, delta.partial_json || ""
sequence_number: 3, ));
type: "response.custom_tool_call_input.delta"
});
} }
} }
// content_block_stop 事件 // content_block_stop 事件
if (claudeChunk.type === 'content_block_stop') { if (claudeChunk.type === 'content_block_stop') {
events.push({ const state = streamStateManager.getOrCreateState(responseId);
item_id: `msg_${uuidv4().replace(/-/g, '')}`, if (state.currentToolCall) {
output_index: claudeChunk.index || 0, const itemId = state.currentToolCall.id;
sequence_number: 4, const outputIdx = claudeChunk.index || 0;
type: "response.output_item.done" events.push(generateFunctionCallArgsDone(responseId, itemId, outputIdx));
}); const finished = finishToolCall(responseId);
if (finished) {
events.push(generateFunctionCallOutputItemDone(responseId, finished, outputIdx));
}
}
} }
// message_delta 事件 - 流结束 // message_delta 事件 - 保存 usage 供 message_stop 使用
if (claudeChunk.type === 'message_delta') { if (claudeChunk.type === 'message_delta') {
// events.push( if (claudeChunk.usage) {
// generateOutputTextDone(responseId), const state = streamStateManager.getOrCreateState(responseId);
// generateContentPartDone(responseId), state.savedUsage = {
// generateOutputItemDone(responseId), input_tokens: claudeChunk.usage.input_tokens || 0,
// generateResponseCompleted(responseId) input_tokens_details: { cached_tokens: claudeChunk.usage.cache_read_input_tokens || 0 },
// ); output_tokens: claudeChunk.usage.output_tokens || 0,
output_tokens_details: { reasoning_tokens: 0 },
// 如果有 usage 信息,更新最后一个事件 total_tokens: (claudeChunk.usage.input_tokens || 0) + (claudeChunk.usage.output_tokens || 0)
if (claudeChunk.usage && events.length > 0) { };
const lastEvent = events[events.length - 1];
if (lastEvent.response) {
lastEvent.response.usage = {
input_tokens: claudeChunk.usage.input_tokens || 0,
input_tokens_details: {
cached_tokens: claudeChunk.usage.cache_read_input_tokens || 0
},
output_tokens: claudeChunk.usage.output_tokens || 0,
output_tokens_details: {
reasoning_tokens: 0
},
total_tokens: (claudeChunk.usage.input_tokens || 0) + (claudeChunk.usage.output_tokens || 0)
};
}
} }
} }
// message_stop 事件 // message_stop 事件
if (claudeChunk.type === 'message_stop') { if (claudeChunk.type === 'message_stop') {
const state = streamStateManager.getOrCreateState(responseId);
const savedUsage = state.savedUsage || null;
events.push( events.push(
generateOutputTextDone(responseId), generateOutputTextDone(responseId),
generateContentPartDone(responseId), generateContentPartDone(responseId),
generateOutputItemDone(responseId), generateOutputItemDone(responseId),
generateResponseCompleted(responseId) generateResponseCompleted(responseId, savedUsage)
); );
} }

View file

@ -44,7 +44,7 @@ const KIRO_CONSTANTS = {
AUTH_METHOD_SOCIAL: 'social', AUTH_METHOD_SOCIAL: 'social',
CHAT_TRIGGER_TYPE_MANUAL: 'MANUAL', CHAT_TRIGGER_TYPE_MANUAL: 'MANUAL',
ORIGIN_AI_EDITOR: 'AI_EDITOR', ORIGIN_AI_EDITOR: 'AI_EDITOR',
TOTAL_CONTEXT_TOKENS: 1000000, // Kiro now supports 1M context window TOTAL_CONTEXT_TOKENS: 200000, // Claude Sonnet 4.5 actual context is 200K
}; };
// 从 provider-models.js 获取支持的模型列表 // 从 provider-models.js 获取支持的模型列表
@ -1597,6 +1597,7 @@ async saveCredentialsToFile(filePath, newData) {
return this.callApi(method, model, body, isRetry, retryCount + 1); return this.callApi(method, model, body, isRetry, retryCount + 1);
} }
if (error.response && error.response.data) { logger.error('[Kiro] 400 Response body:', typeof error.response.data === 'string' ? error.response.data.substring(0, 500) : JSON.stringify(error.response.data).substring(0, 500)); }
logger.error(`[Kiro] API call failed (Status: ${status}, Code: ${errorCode}):`, error.message); logger.error(`[Kiro] API call failed (Status: ${status}, Code: ${errorCode}):`, error.message);
throw error; throw error;
} }

View file

@ -16,7 +16,9 @@ class StreamState {
sequenceNumber: 0, sequenceNumber: 0,
model: null, model: null,
status: 'in_progress', status: 'in_progress',
startTime: Math.floor(Date.now() / 1000) startTime: Math.floor(Date.now() / 1000),
toolCalls: [],
currentToolCall: null
}); });
} }
return this.states.get(requestId); return this.states.get(requestId);
@ -73,7 +75,7 @@ function generateResponseCreated(requestId, model) {
incomplete_details: null, incomplete_details: null,
instructions: '', instructions: '',
max_output_tokens: null, max_output_tokens: null,
model: state.model || 'gpt-4.1-2025-04-14', model: state.model || 'claude-sonnet-4-6',
output: [], output: [],
parallel_tool_calls: true, parallel_tool_calls: true,
previous_response_id: null, previous_response_id: null,
@ -110,7 +112,7 @@ function generateResponseInProgress(requestId) {
incomplete_details: null, incomplete_details: null,
instructions: '', instructions: '',
max_output_tokens: null, max_output_tokens: null,
model: state.model || 'gpt-4.1-2025-04-14', model: state.model || 'claude-sonnet-4-6',
output: [], output: [],
parallel_tool_calls: true, parallel_tool_calls: true,
previous_response_id: null, previous_response_id: null,
@ -269,25 +271,32 @@ function generateResponseCompleted(requestId, usage) {
max_output_tokens: null, max_output_tokens: null,
max_tool_calls: null, max_tool_calls: null,
metadata: {}, metadata: {},
model: state.model || 'gpt-4.1-2025-04-14', model: state.model || 'claude-sonnet-4-6',
object: 'response', object: 'response',
output: [ output: (() => {
{ const items = [];
id: state.msgId, if (state.fullText) {
summary: [], items.push({
type: 'message', id: state.msgId, summary: [], type: 'message', role: 'assistant', status: 'completed',
role: 'assistant', content: [{ type: 'output_text', text: state.fullText, annotations: [], logprobs: [] }]
status: 'completed', });
content: [
{
type: 'output_text',
text: state.fullText,
annotations: [],
logprobs: []
}
]
} }
], if (state.toolCalls && state.toolCalls.length > 0) {
for (const tc of state.toolCalls) {
items.push({
id: tc.id, call_id: tc.call_id || tc.id, type: 'function_call',
name: tc.name, arguments: tc.arguments || '{}', status: 'completed'
});
}
}
if (items.length === 0) {
items.push({
id: state.msgId, summary: [], type: 'message', role: 'assistant', status: 'completed',
content: [{ type: 'output_text', text: '', annotations: [], logprobs: [] }]
});
}
return items;
})(),
parallel_tool_calls: true, parallel_tool_calls: true,
previous_response_id: null, previous_response_id: null,
prompt_cache_key: null, prompt_cache_key: null,
@ -322,8 +331,50 @@ function generateResponseCompleted(requestId, usage) {
}; };
} }
function startToolCall(requestId, toolCallId, name) {
const state = streamStateManager.getOrCreateState(requestId);
state.currentToolCall = { id: toolCallId, call_id: toolCallId, name: name, arguments: '' };
}
function appendToolCallArgs(requestId, delta) {
const state = streamStateManager.getOrCreateState(requestId);
if (state.currentToolCall) state.currentToolCall.arguments += delta;
}
function finishToolCall(requestId) {
const state = streamStateManager.getOrCreateState(requestId);
if (state.currentToolCall) {
state.toolCalls.push({...state.currentToolCall});
const finished = state.currentToolCall;
state.currentToolCall = null;
return finished;
}
return null;
}
function generateFunctionCallArgsDelta(requestId, itemId, outputIndex, delta) {
appendToolCallArgs(requestId, delta);
return { type: 'response.function_call_arguments.delta', item_id: itemId, output_index: outputIndex, delta: delta };
}
function generateFunctionCallArgsDone(requestId, itemId, outputIndex) {
const state = streamStateManager.getOrCreateState(requestId);
const args = state.currentToolCall ? state.currentToolCall.arguments : '{}';
return { type: 'response.function_call_arguments.done', item_id: itemId, output_index: outputIndex, arguments: args };
}
function generateFunctionCallOutputItemDone(requestId, toolCall, outputIndex) {
return {
type: 'response.output_item.done', output_index: outputIndex,
item: { id: toolCall.id, call_id: toolCall.call_id || toolCall.id, type: 'function_call',
name: toolCall.name, arguments: toolCall.arguments || '{}', status: 'completed' }
};
}
// 导出流式状态管理器以供外部使用 // 导出流式状态管理器以供外部使用
export { streamStateManager, generateResponseCreated, generateResponseInProgress, export { streamStateManager, generateResponseCreated, generateResponseInProgress,
generateOutputItemAdded, generateContentPartAdded, generateOutputTextDelta, generateOutputItemAdded, generateContentPartAdded, generateOutputTextDelta,
generateOutputTextDone, generateContentPartDone, generateOutputItemDone, generateOutputTextDone, generateContentPartDone, generateOutputItemDone,
generateResponseCompleted }; generateResponseCompleted, startToolCall, appendToolCallArgs, finishToolCall,
generateFunctionCallArgsDelta, generateFunctionCallArgsDone, generateFunctionCallOutputItemDone };