fix(translator): keep tool-result images, restore Kiro tool names, preserve thinking display
Forward images inside tool_result to OpenAI and Kiro upstreams via following user messages, restore original client tool names on Kiro responses via _toolNameMap, and preserve thinking display settings across translations.
This commit is contained in:
@@ -97,11 +97,21 @@ function convertClaudeMessagesToKiro(messages, model) {
|
||||
if (typeof block.content === "string") {
|
||||
resultContent = block.content;
|
||||
} else if (Array.isArray(block.content)) {
|
||||
// Images a tool returned (screenshots) ride along as user images;
|
||||
// Kiro tool results are text-only.
|
||||
let hasImage = false;
|
||||
for (const c of block.content) {
|
||||
if (c?.type === CLAUDE_BLOCK.IMAGE && c.source?.type === "base64") {
|
||||
hasImage = true;
|
||||
const imageType = c.source.media_type || DEFAULT_IMAGE_MIME;
|
||||
pendingImages.push({ format: imageType.split("/")[1] || imageType, source: { bytes: c.source.data } });
|
||||
}
|
||||
}
|
||||
resultContent =
|
||||
block.content
|
||||
.filter((c) => c.type === CLAUDE_BLOCK.TEXT)
|
||||
.map((c) => c.text)
|
||||
.join("\n") || JSON.stringify(block.content);
|
||||
.join("\n") || (hasImage ? "(image attached)" : JSON.stringify(block.content));
|
||||
} else if (block.content) {
|
||||
resultContent = JSON.stringify(block.content);
|
||||
}
|
||||
@@ -341,6 +351,13 @@ export function claudeToKiroRequest(model, body, stream, credentials) {
|
||||
enumerable: false,
|
||||
});
|
||||
|
||||
// Kiro tool specs get sanitized names (`mcp__a__b` → `mcp_a_b`); keep the
|
||||
// reverse map so tool calls stream back under the client's own names.
|
||||
const restoredToolNames = new Map();
|
||||
for (const [original, sanitized] of nameMap) {
|
||||
if (original !== sanitized) restoredToolNames.set(sanitized, original);
|
||||
}
|
||||
if (restoredToolNames.size) payload._toolNameMap = restoredToolNames;
|
||||
return payload;
|
||||
}
|
||||
|
||||
|
||||
@@ -196,25 +196,41 @@ function convertClaudeMessage(msg) {
|
||||
});
|
||||
break;
|
||||
|
||||
case CLAUDE_BLOCK.TOOL_RESULT:
|
||||
case CLAUDE_BLOCK.TOOL_RESULT: {
|
||||
let resultContent = "";
|
||||
const resultImages = [];
|
||||
if (typeof block.content === "string") {
|
||||
resultContent = block.content;
|
||||
} else if (Array.isArray(block.content)) {
|
||||
resultContent = block.content
|
||||
.filter(c => c.type === CLAUDE_BLOCK.TEXT)
|
||||
.map(c => c.text)
|
||||
.join("\n") || JSON.stringify(block.content);
|
||||
for (const c of block.content) {
|
||||
if (c?.type === CLAUDE_BLOCK.IMAGE && c.source?.type === "base64") {
|
||||
resultImages.push({
|
||||
type: OPENAI_BLOCK.IMAGE_URL,
|
||||
image_url: { url: encodeDataUri(c.source.media_type, c.source.data) }
|
||||
});
|
||||
}
|
||||
}
|
||||
const textOnly = block.content.filter(c => c?.type === CLAUDE_BLOCK.TEXT);
|
||||
resultContent = textOnly.map(c => c.text).join("\n")
|
||||
|| (resultImages.length ? "" : JSON.stringify(block.content));
|
||||
} else if (block.content) {
|
||||
resultContent = JSON.stringify(block.content);
|
||||
}
|
||||
|
||||
|
||||
toolResults.push({
|
||||
role: ROLE.TOOL,
|
||||
tool_call_id: block.tool_use_id,
|
||||
content: resultContent
|
||||
});
|
||||
// The OpenAI tool role is text-only, so a screenshot or any other image a
|
||||
// tool returned would otherwise vanish. Hand it to the model in the user
|
||||
// turn that follows the tool messages, tagged with the call it came from.
|
||||
if (resultImages.length) {
|
||||
parts.push({ type: OPENAI_BLOCK.TEXT, text: `[Image from tool result ${block.tool_use_id}]` });
|
||||
parts.push(...resultImages);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,16 +42,19 @@ function toNativeImageBlock(part) {
|
||||
type: OPENAI_BLOCK.IMAGE,
|
||||
image: encodeDataUri(parsed.mimeType, parsed.base64),
|
||||
mimeType: parsed.mimeType,
|
||||
mediaType: parsed.mimeType,
|
||||
};
|
||||
}
|
||||
|
||||
if (part.type === OPENAI_BLOCK.IMAGE || part.type === CLAUDE_BLOCK.IMAGE) {
|
||||
if (typeof part.image === "string" && part.image.startsWith("data:")) {
|
||||
const parsed = parseDataUri(part.image);
|
||||
const mime = part.mimeType || parsed?.mimeType || "image/png";
|
||||
return {
|
||||
type: OPENAI_BLOCK.IMAGE,
|
||||
image: part.image,
|
||||
mimeType: part.mimeType || parsed?.mimeType || "image/png",
|
||||
mimeType: mime,
|
||||
mediaType: mime,
|
||||
};
|
||||
}
|
||||
const source = part.source;
|
||||
@@ -61,6 +64,7 @@ function toNativeImageBlock(part) {
|
||||
type: OPENAI_BLOCK.IMAGE,
|
||||
image: encodeDataUri(mime, source.data),
|
||||
mimeType: mime,
|
||||
mediaType: mime,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -434,6 +434,13 @@ export function openaiToKiroRequest(model, body, stream, credentials) {
|
||||
enumerable: false
|
||||
});
|
||||
|
||||
// Kiro tool specs get sanitized names (`mcp__a__b` → `mcp_a_b`); keep the
|
||||
// reverse map so tool calls stream back under the client's own names.
|
||||
const restoredToolNames = new Map();
|
||||
for (const [original, sanitized] of nameMap) {
|
||||
if (original !== sanitized) restoredToolNames.set(sanitized, original);
|
||||
}
|
||||
if (restoredToolNames.size) payload._toolNameMap = restoredToolNames;
|
||||
return payload;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user