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,
generateContentPartDone,
generateOutputItemDone,
generateResponseCompleted
generateResponseCompleted,
generateOutputTextDelta,
streamStateManager,
startToolCall,
finishToolCall,
generateFunctionCallArgsDelta,
generateFunctionCallArgsDone,
generateFunctionCallOutputItemDone
} 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) {
case MODEL_PROTOCOL_PREFIX.OPENAI:
return this.toOpenAIStreamChunk(chunk, model);
case MODEL_PROTOCOL_PREFIX.GEMINI:
return this.toGeminiStreamChunk(chunk, model);
case MODEL_PROTOCOL_PREFIX.OPENAI_RESPONSES:
return this.toOpenAIResponsesStreamChunk(chunk, model);
return this.toOpenAIResponsesStreamChunk(chunk, model, requestId);
case MODEL_PROTOCOL_PREFIX.CODEX:
return this.toCodexStreamChunk(chunk, model);
default:
@ -1694,9 +1701,11 @@ export class ClaudeConverter extends BaseConverter {
// 对于 tool_use 类型,添加工具调用项
if (contentBlock && contentBlock.type === 'tool_use') {
startToolCall(responseId, contentBlock.id, contentBlock.name);
events.push({
item: {
id: contentBlock.id,
call_id: contentBlock.id,
type: "function_call",
name: contentBlock.name,
arguments: "",
@ -1715,13 +1724,7 @@ export class ClaudeConverter extends BaseConverter {
// 处理文本增量
if (delta && delta.type === 'text_delta') {
events.push({
delta: delta.text || "",
item_id: `msg_${uuidv4().replace(/-/g, '')}`,
output_index: claudeChunk.index || 0,
sequence_number: 3,
type: "response.output_text.delta"
});
events.push(generateOutputTextDelta(responseId, delta.text || ""));
}
// 处理推理内容增量
else if (delta && delta.type === 'thinking_delta') {
@ -1735,61 +1738,51 @@ export class ClaudeConverter extends BaseConverter {
}
// 处理工具调用参数增量
else if (delta && delta.type === 'input_json_delta') {
events.push({
delta: delta.partial_json || "",
item_id: `call_${uuidv4().replace(/-/g, '')}`,
output_index: claudeChunk.index || 0,
sequence_number: 3,
type: "response.custom_tool_call_input.delta"
});
const state = streamStateManager.getOrCreateState(responseId);
const itemId = state.currentToolCall ? state.currentToolCall.id : 'unknown';
events.push(generateFunctionCallArgsDelta(
responseId, itemId, claudeChunk.index || 0, delta.partial_json || ""
));
}
}
// content_block_stop 事件
if (claudeChunk.type === 'content_block_stop') {
events.push({
item_id: `msg_${uuidv4().replace(/-/g, '')}`,
output_index: claudeChunk.index || 0,
sequence_number: 4,
type: "response.output_item.done"
});
const state = streamStateManager.getOrCreateState(responseId);
if (state.currentToolCall) {
const itemId = state.currentToolCall.id;
const outputIdx = claudeChunk.index || 0;
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') {
// events.push(
// generateOutputTextDone(responseId),
// generateContentPartDone(responseId),
// generateOutputItemDone(responseId),
// generateResponseCompleted(responseId)
// );
// 如果有 usage 信息,更新最后一个事件
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)
};
}
if (claudeChunk.usage) {
const state = streamStateManager.getOrCreateState(responseId);
state.savedUsage = {
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 事件
if (claudeChunk.type === 'message_stop') {
const state = streamStateManager.getOrCreateState(responseId);
const savedUsage = state.savedUsage || null;
events.push(
generateOutputTextDone(responseId),
generateContentPartDone(responseId),
generateOutputItemDone(responseId),
generateResponseCompleted(responseId)
generateResponseCompleted(responseId, savedUsage)
);
}

View file

@ -44,7 +44,7 @@ const KIRO_CONSTANTS = {
AUTH_METHOD_SOCIAL: 'social',
CHAT_TRIGGER_TYPE_MANUAL: 'MANUAL',
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 获取支持的模型列表
@ -1597,6 +1597,7 @@ async saveCredentialsToFile(filePath, newData) {
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);
throw error;
}

View file

@ -16,7 +16,9 @@ class StreamState {
sequenceNumber: 0,
model: null,
status: 'in_progress',
startTime: Math.floor(Date.now() / 1000)
startTime: Math.floor(Date.now() / 1000),
toolCalls: [],
currentToolCall: null
});
}
return this.states.get(requestId);
@ -73,7 +75,7 @@ function generateResponseCreated(requestId, model) {
incomplete_details: null,
instructions: '',
max_output_tokens: null,
model: state.model || 'gpt-4.1-2025-04-14',
model: state.model || 'claude-sonnet-4-6',
output: [],
parallel_tool_calls: true,
previous_response_id: null,
@ -110,7 +112,7 @@ function generateResponseInProgress(requestId) {
incomplete_details: null,
instructions: '',
max_output_tokens: null,
model: state.model || 'gpt-4.1-2025-04-14',
model: state.model || 'claude-sonnet-4-6',
output: [],
parallel_tool_calls: true,
previous_response_id: null,
@ -269,25 +271,32 @@ function generateResponseCompleted(requestId, usage) {
max_output_tokens: null,
max_tool_calls: null,
metadata: {},
model: state.model || 'gpt-4.1-2025-04-14',
model: state.model || 'claude-sonnet-4-6',
object: 'response',
output: [
{
id: state.msgId,
summary: [],
type: 'message',
role: 'assistant',
status: 'completed',
content: [
{
type: 'output_text',
text: state.fullText,
annotations: [],
logprobs: []
}
]
output: (() => {
const items = [];
if (state.fullText) {
items.push({
id: state.msgId, summary: [], type: 'message', role: 'assistant', 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,
previous_response_id: 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,
generateOutputItemAdded, generateContentPartAdded, generateOutputTextDelta,
generateOutputTextDone, generateContentPartDone, generateOutputItemDone,
generateResponseCompleted };
generateResponseCompleted, startToolCall, appendToolCallArgs, finishToolCall,
generateFunctionCallArgsDelta, generateFunctionCallArgsDone, generateFunctionCallOutputItemDone };