diff --git a/src/claude/claude-kiro.js b/src/claude/claude-kiro.js index e30b48d..0ba0bac 100644 --- a/src/claude/claude-kiro.js +++ b/src/claude/claude-kiro.js @@ -637,7 +637,16 @@ async initializeAuth(forceRefresh = false) { userInputMessage.images = images; } if (toolResults.length > 0) { - userInputMessage.userInputMessageContext = { toolResults }; + // 去重 toolResults - Kiro API 不接受重复的 toolUseId + const uniqueToolResults = []; + const seenIds = new Set(); + for (const tr of toolResults) { + if (!seenIds.has(tr.toolUseId)) { + seenIds.add(tr.toolUseId); + uniqueToolResults.push(tr); + } + } + userInputMessage.userInputMessageContext = { toolResults: uniqueToolResults }; } history.push({ userInputMessage }); @@ -742,8 +751,9 @@ async initializeAuth(forceRefresh = false) { currentContent = this.getContentText(currentMessage); } - if (!currentContent && currentToolResults.length === 0 && currentToolUses.length === 0) { - currentContent = 'Continue'; + // Kiro API 要求 content 不能为空,即使有 toolResults + if (!currentContent) { + currentContent = currentToolResults.length > 0 ? 'Tool results provided.' : 'Continue'; } } @@ -751,10 +761,14 @@ async initializeAuth(forceRefresh = false) { conversationState: { chatTriggerType: KIRO_CONSTANTS.CHAT_TRIGGER_TYPE_MANUAL, conversationId: conversationId, - currentMessage: {}, // Will be populated as userInputMessage - history: history + currentMessage: {} // Will be populated as userInputMessage } }; + + // 只有当 history 非空时才添加(API 可能不接受空数组) + if (history.length > 0) { + request.conversationState.history = history; + } // currentMessage 始终是 userInputMessage 类型 // 注意:API 不接受 null 值,空字段应该完全不包含 @@ -772,7 +786,16 @@ async initializeAuth(forceRefresh = false) { // 构建 userInputMessageContext,只包含非空字段 const userInputMessageContext = {}; if (currentToolResults.length > 0) { - userInputMessageContext.toolResults = currentToolResults; + // 去重 toolResults - Kiro API 不接受重复的 toolUseId + const uniqueToolResults = []; + const seenToolUseIds = new Set(); + for (const tr of currentToolResults) { + if (!seenToolUseIds.has(tr.toolUseId)) { + seenToolUseIds.add(tr.toolUseId); + uniqueToolResults.push(tr); + } + } + userInputMessageContext.toolResults = uniqueToolResults; } if (Object.keys(toolsContext).length > 0 && toolsContext.tools) { userInputMessageContext.tools = toolsContext.tools; @@ -1022,25 +1045,67 @@ async initializeAuth(forceRefresh = false) { let searchStart = 0; while (true) { - // 查找 {"content": 或 {"toolUse" 的起始位置 + // 查找真正的 JSON payload 起始位置 + // AWS Event Stream 包含二进制头部,我们只搜索有效的 JSON 模式 + // Kiro 返回格式: {"content":"..."} 或 {"name":"xxx","toolUseId":"xxx",...} 或 {"followupPrompt":"..."} + + // 搜索所有可能的 JSON payload 开头模式 + // Kiro 返回的 toolUse 可能分多个事件: + // 1. {"name":"xxx","toolUseId":"xxx"} - 开始 + // 2. {"input":"..."} - input 数据(可能多次) + // 3. {"stop":true} - 结束 const contentStart = remaining.indexOf('{"content":', searchStart); - const toolUseStart = remaining.indexOf('{"toolUse":', searchStart); + const nameStart = remaining.indexOf('{"name":', searchStart); + const followupStart = remaining.indexOf('{"followupPrompt":', searchStart); + const inputStart = remaining.indexOf('{"input":', searchStart); + const stopStart = remaining.indexOf('{"stop":', searchStart); - let jsonStart = -1; - if (contentStart >= 0 && toolUseStart >= 0) { - jsonStart = Math.min(contentStart, toolUseStart); - } else if (contentStart >= 0) { - jsonStart = contentStart; - } else if (toolUseStart >= 0) { - jsonStart = toolUseStart; - } + // 找到最早出现的有效 JSON 模式 + const candidates = [contentStart, nameStart, followupStart, inputStart, stopStart].filter(pos => pos >= 0); + if (candidates.length === 0) break; + const jsonStart = Math.min(...candidates); if (jsonStart < 0) break; - // 查找对应的 } 结束位置 - const jsonEnd = remaining.indexOf('}', jsonStart); + // 正确处理嵌套的 {} - 使用括号计数法 + let braceCount = 0; + let jsonEnd = -1; + let inString = false; + let escapeNext = false; + + for (let i = jsonStart; i < remaining.length; i++) { + const char = remaining[i]; + + if (escapeNext) { + escapeNext = false; + continue; + } + + if (char === '\\') { + escapeNext = true; + continue; + } + + if (char === '"') { + inString = !inString; + continue; + } + + if (!inString) { + if (char === '{') { + braceCount++; + } else if (char === '}') { + braceCount--; + if (braceCount === 0) { + jsonEnd = i; + break; + } + } + } + } + if (jsonEnd < 0) { - // 不完整的 JSON,保留在缓冲区 + // 不完整的 JSON,保留在缓冲区等待更多数据 remaining = remaining.substring(jsonStart); break; } @@ -1048,13 +1113,45 @@ async initializeAuth(forceRefresh = false) { const jsonStr = remaining.substring(jsonStart, jsonEnd + 1); try { const parsed = JSON.parse(jsonStr); - if (parsed.content !== undefined) { - events.push({ type: 'content', data: parsed.content }); - } else if (parsed.toolUse !== undefined) { - events.push({ type: 'toolUse', data: parsed.toolUse }); + // 处理 content 事件 + if (parsed.content !== undefined && !parsed.followupPrompt) { + // 处理转义字符 + let decodedContent = parsed.content; + decodedContent = decodedContent.replace(/(? 0) { + for (const btc of bracketToolCalls) { + toolCalls.push({ + toolUseId: btc.id || `tool_${uuidv4()}`, + name: btc.function.name, + input: JSON.parse(btc.function.arguments || '{}') + }); } } @@ -1233,11 +1407,13 @@ async initializeAuth(forceRefresh = false) { index: blockIndex, delta: { type: "input_json_delta", - partial_json: JSON.stringify(tc.input || {}) + partial_json: typeof tc.input === 'string' ? tc.input : JSON.stringify(tc.input || {}) } }; yield { type: "content_block_stop", index: blockIndex }; + + outputTokens += this.countTextTokens(JSON.stringify(tc.input || {})); } }