commit
55cabe8f7c
1 changed files with 211 additions and 35 deletions
|
|
@ -637,7 +637,16 @@ async initializeAuth(forceRefresh = false) {
|
||||||
userInputMessage.images = images;
|
userInputMessage.images = images;
|
||||||
}
|
}
|
||||||
if (toolResults.length > 0) {
|
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 });
|
history.push({ userInputMessage });
|
||||||
|
|
@ -742,8 +751,9 @@ async initializeAuth(forceRefresh = false) {
|
||||||
currentContent = this.getContentText(currentMessage);
|
currentContent = this.getContentText(currentMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!currentContent && currentToolResults.length === 0 && currentToolUses.length === 0) {
|
// Kiro API 要求 content 不能为空,即使有 toolResults
|
||||||
currentContent = 'Continue';
|
if (!currentContent) {
|
||||||
|
currentContent = currentToolResults.length > 0 ? 'Tool results provided.' : 'Continue';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -751,10 +761,14 @@ async initializeAuth(forceRefresh = false) {
|
||||||
conversationState: {
|
conversationState: {
|
||||||
chatTriggerType: KIRO_CONSTANTS.CHAT_TRIGGER_TYPE_MANUAL,
|
chatTriggerType: KIRO_CONSTANTS.CHAT_TRIGGER_TYPE_MANUAL,
|
||||||
conversationId: conversationId,
|
conversationId: conversationId,
|
||||||
currentMessage: {}, // Will be populated as userInputMessage
|
currentMessage: {} // Will be populated as userInputMessage
|
||||||
history: history
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 只有当 history 非空时才添加(API 可能不接受空数组)
|
||||||
|
if (history.length > 0) {
|
||||||
|
request.conversationState.history = history;
|
||||||
|
}
|
||||||
|
|
||||||
// currentMessage 始终是 userInputMessage 类型
|
// currentMessage 始终是 userInputMessage 类型
|
||||||
// 注意:API 不接受 null 值,空字段应该完全不包含
|
// 注意:API 不接受 null 值,空字段应该完全不包含
|
||||||
|
|
@ -772,7 +786,16 @@ async initializeAuth(forceRefresh = false) {
|
||||||
// 构建 userInputMessageContext,只包含非空字段
|
// 构建 userInputMessageContext,只包含非空字段
|
||||||
const userInputMessageContext = {};
|
const userInputMessageContext = {};
|
||||||
if (currentToolResults.length > 0) {
|
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) {
|
if (Object.keys(toolsContext).length > 0 && toolsContext.tools) {
|
||||||
userInputMessageContext.tools = toolsContext.tools;
|
userInputMessageContext.tools = toolsContext.tools;
|
||||||
|
|
@ -1022,25 +1045,67 @@ async initializeAuth(forceRefresh = false) {
|
||||||
let searchStart = 0;
|
let searchStart = 0;
|
||||||
|
|
||||||
while (true) {
|
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 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;
|
// 找到最早出现的有效 JSON 模式
|
||||||
if (contentStart >= 0 && toolUseStart >= 0) {
|
const candidates = [contentStart, nameStart, followupStart, inputStart, stopStart].filter(pos => pos >= 0);
|
||||||
jsonStart = Math.min(contentStart, toolUseStart);
|
if (candidates.length === 0) break;
|
||||||
} else if (contentStart >= 0) {
|
|
||||||
jsonStart = contentStart;
|
|
||||||
} else if (toolUseStart >= 0) {
|
|
||||||
jsonStart = toolUseStart;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const jsonStart = Math.min(...candidates);
|
||||||
if (jsonStart < 0) break;
|
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) {
|
if (jsonEnd < 0) {
|
||||||
// 不完整的 JSON,保留在缓冲区
|
// 不完整的 JSON,保留在缓冲区等待更多数据
|
||||||
remaining = remaining.substring(jsonStart);
|
remaining = remaining.substring(jsonStart);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -1048,13 +1113,45 @@ async initializeAuth(forceRefresh = false) {
|
||||||
const jsonStr = remaining.substring(jsonStart, jsonEnd + 1);
|
const jsonStr = remaining.substring(jsonStart, jsonEnd + 1);
|
||||||
try {
|
try {
|
||||||
const parsed = JSON.parse(jsonStr);
|
const parsed = JSON.parse(jsonStr);
|
||||||
if (parsed.content !== undefined) {
|
// 处理 content 事件
|
||||||
events.push({ type: 'content', data: parsed.content });
|
if (parsed.content !== undefined && !parsed.followupPrompt) {
|
||||||
} else if (parsed.toolUse !== undefined) {
|
// 处理转义字符
|
||||||
events.push({ type: 'toolUse', data: parsed.toolUse });
|
let decodedContent = parsed.content;
|
||||||
|
decodedContent = decodedContent.replace(/(?<!\\)\\n/g, '\n');
|
||||||
|
events.push({ type: 'content', data: decodedContent });
|
||||||
|
}
|
||||||
|
// 处理结构化工具调用事件 - 开始事件(包含 name 和 toolUseId)
|
||||||
|
else if (parsed.name && parsed.toolUseId) {
|
||||||
|
events.push({
|
||||||
|
type: 'toolUse',
|
||||||
|
data: {
|
||||||
|
name: parsed.name,
|
||||||
|
toolUseId: parsed.toolUseId,
|
||||||
|
input: parsed.input || '',
|
||||||
|
stop: parsed.stop || false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 处理工具调用的 input 续传事件(只有 input 字段)
|
||||||
|
else if (parsed.input !== undefined && !parsed.name) {
|
||||||
|
events.push({
|
||||||
|
type: 'toolUseInput',
|
||||||
|
data: {
|
||||||
|
input: parsed.input
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 处理工具调用的结束事件(只有 stop 字段)
|
||||||
|
else if (parsed.stop !== undefined) {
|
||||||
|
events.push({
|
||||||
|
type: 'toolUseStop',
|
||||||
|
data: {
|
||||||
|
stop: parsed.stop
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// JSON 解析失败,可能是不完整的,继续搜索
|
// JSON 解析失败,跳过这个位置继续搜索
|
||||||
}
|
}
|
||||||
|
|
||||||
searchStart = jsonEnd + 1;
|
searchStart = jsonEnd + 1;
|
||||||
|
|
@ -1098,7 +1195,7 @@ async initializeAuth(forceRefresh = false) {
|
||||||
|
|
||||||
const stream = response.data;
|
const stream = response.data;
|
||||||
let buffer = '';
|
let buffer = '';
|
||||||
const processedPositions = new Set(); // 避免重复处理
|
let lastContentEvent = null; // 用于检测连续重复的 content 事件
|
||||||
|
|
||||||
for await (const chunk of stream) {
|
for await (const chunk of stream) {
|
||||||
buffer += chunk.toString();
|
buffer += chunk.toString();
|
||||||
|
|
@ -1107,16 +1204,22 @@ async initializeAuth(forceRefresh = false) {
|
||||||
const { events, remaining } = this.parseAwsEventStreamBuffer(buffer);
|
const { events, remaining } = this.parseAwsEventStreamBuffer(buffer);
|
||||||
buffer = remaining;
|
buffer = remaining;
|
||||||
|
|
||||||
// 只 yield 新的事件
|
// yield 所有事件,但过滤连续完全相同的 content 事件(Kiro API 有时会重复发送)
|
||||||
for (const event of events) {
|
for (const event of events) {
|
||||||
const eventKey = `${event.type}:${event.data}`;
|
if (event.type === 'content' && event.data) {
|
||||||
if (!processedPositions.has(eventKey)) {
|
// 检查是否与上一个 content 事件完全相同
|
||||||
processedPositions.add(eventKey);
|
if (lastContentEvent === event.data) {
|
||||||
if (event.type === 'content' && event.data) {
|
// 跳过重复的内容
|
||||||
yield { type: 'content', content: event.data };
|
continue;
|
||||||
} else if (event.type === 'toolUse') {
|
|
||||||
yield { type: 'toolUse', toolUse: event.data };
|
|
||||||
}
|
}
|
||||||
|
lastContentEvent = event.data;
|
||||||
|
yield { type: 'content', content: event.data };
|
||||||
|
} else if (event.type === 'toolUse') {
|
||||||
|
yield { type: 'toolUse', toolUse: event.data };
|
||||||
|
} else if (event.type === 'toolUseInput') {
|
||||||
|
yield { type: 'toolUseInput', input: event.data.input };
|
||||||
|
} else if (event.type === 'toolUseStop') {
|
||||||
|
yield { type: 'toolUseStop', stop: event.data.stop };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1191,6 +1294,7 @@ async initializeAuth(forceRefresh = false) {
|
||||||
let totalContent = '';
|
let totalContent = '';
|
||||||
let outputTokens = 0;
|
let outputTokens = 0;
|
||||||
const toolCalls = [];
|
const toolCalls = [];
|
||||||
|
let currentToolCall = null; // 用于累积结构化工具调用
|
||||||
|
|
||||||
// 3. 流式接收并发送每个 content_block_delta
|
// 3. 流式接收并发送每个 content_block_delta
|
||||||
for await (const event of this.streamApiReal('', finalModel, requestBody)) {
|
for await (const event of this.streamApiReal('', finalModel, requestBody)) {
|
||||||
|
|
@ -1204,7 +1308,77 @@ async initializeAuth(forceRefresh = false) {
|
||||||
delta: { type: "text_delta", text: event.content }
|
delta: { type: "text_delta", text: event.content }
|
||||||
};
|
};
|
||||||
} else if (event.type === 'toolUse') {
|
} else if (event.type === 'toolUse') {
|
||||||
toolCalls.push(event.toolUse);
|
const tc = event.toolUse;
|
||||||
|
// 工具调用事件(包含 name 和 toolUseId)
|
||||||
|
if (tc.name && tc.toolUseId) {
|
||||||
|
// 检查是否是同一个工具调用的续传(相同 toolUseId)
|
||||||
|
if (currentToolCall && currentToolCall.toolUseId === tc.toolUseId) {
|
||||||
|
// 同一个工具调用,累积 input
|
||||||
|
currentToolCall.input += tc.input || '';
|
||||||
|
} else {
|
||||||
|
// 不同的工具调用
|
||||||
|
// 如果有未完成的工具调用,先保存它
|
||||||
|
if (currentToolCall) {
|
||||||
|
try {
|
||||||
|
currentToolCall.input = JSON.parse(currentToolCall.input);
|
||||||
|
} catch (e) {
|
||||||
|
// input 不是有效 JSON,保持原样
|
||||||
|
}
|
||||||
|
toolCalls.push(currentToolCall);
|
||||||
|
}
|
||||||
|
// 开始新的工具调用
|
||||||
|
currentToolCall = {
|
||||||
|
toolUseId: tc.toolUseId,
|
||||||
|
name: tc.name,
|
||||||
|
input: tc.input || ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// 如果这个事件包含 stop,完成工具调用
|
||||||
|
if (tc.stop) {
|
||||||
|
try {
|
||||||
|
currentToolCall.input = JSON.parse(currentToolCall.input);
|
||||||
|
} catch (e) {}
|
||||||
|
toolCalls.push(currentToolCall);
|
||||||
|
currentToolCall = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (event.type === 'toolUseInput') {
|
||||||
|
// 工具调用的 input 续传事件
|
||||||
|
if (currentToolCall) {
|
||||||
|
currentToolCall.input += event.input || '';
|
||||||
|
}
|
||||||
|
} else if (event.type === 'toolUseStop') {
|
||||||
|
// 工具调用结束事件
|
||||||
|
if (currentToolCall && event.stop) {
|
||||||
|
try {
|
||||||
|
currentToolCall.input = JSON.parse(currentToolCall.input);
|
||||||
|
} catch (e) {
|
||||||
|
// input 不是有效 JSON,保持原样
|
||||||
|
}
|
||||||
|
toolCalls.push(currentToolCall);
|
||||||
|
currentToolCall = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理未完成的工具调用(如果流提前结束)
|
||||||
|
if (currentToolCall) {
|
||||||
|
try {
|
||||||
|
currentToolCall.input = JSON.parse(currentToolCall.input);
|
||||||
|
} catch (e) {}
|
||||||
|
toolCalls.push(currentToolCall);
|
||||||
|
currentToolCall = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查文本内容中的 bracket 格式工具调用
|
||||||
|
const bracketToolCalls = parseBracketToolCalls(totalContent);
|
||||||
|
if (bracketToolCalls && bracketToolCalls.length > 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,
|
index: blockIndex,
|
||||||
delta: {
|
delta: {
|
||||||
type: "input_json_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 };
|
yield { type: "content_block_stop", index: blockIndex };
|
||||||
|
|
||||||
|
outputTokens += this.countTextTokens(JSON.stringify(tc.input || {}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue