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:
parent
8e0541d766
commit
4ee01706aa
3 changed files with 115 additions and 70 deletions
|
|
@ -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)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
Loading…
Reference in a new issue