mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-06 15:22:12 +03:00
fix(sse): gate Kiro image attachments behind a Claude-capability check (#4763)
Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; tests green.
This commit is contained in:
committed by
GitHub
parent
2c8921fe34
commit
0d9029905e
@@ -128,6 +128,12 @@ function convertMessages(messages, tools, model) {
|
||||
let currentRole = null;
|
||||
let toolsAttached = false;
|
||||
|
||||
// Only Claude models support images in Kiro. Kiro also routes non-Claude
|
||||
// models (deepseek, minimax, glm, qwen3-coder-next, auto-kiro) that do not
|
||||
// accept image attachments — gate image extraction behind a Claude check so
|
||||
// we never attach images those models would reject.
|
||||
const supportsImages = typeof model === "string" && model.toLowerCase().includes("claude");
|
||||
|
||||
const flushPending = () => {
|
||||
if (currentRole === "user") {
|
||||
const content = pendingUserContent.join("\n\n").trim() || "(empty)";
|
||||
@@ -250,9 +256,10 @@ function convertMessages(messages, tools, model) {
|
||||
.map((c) => c.text || "");
|
||||
content = textParts.join("\n");
|
||||
|
||||
// Extract images (OpenAI image_url and Anthropic image formats)
|
||||
// Extract images (OpenAI image_url and Anthropic image formats).
|
||||
// Skip entirely for models that do not support images — see supportsImages.
|
||||
for (const block of msg.content) {
|
||||
if (block.type === "image_url") {
|
||||
if (supportsImages && block.type === "image_url") {
|
||||
const url: string = block.image_url?.url || "";
|
||||
if (url.startsWith("data:")) {
|
||||
// data:image/jpeg;base64,<data>
|
||||
@@ -261,11 +268,11 @@ function convertMessages(messages, tools, model) {
|
||||
const format = mediaType.split("/")[1] || "jpeg";
|
||||
if (bytes) pendingImages.push({ format, source: { bytes } });
|
||||
}
|
||||
} else if (block.type === "image" && block.source?.type === "base64") {
|
||||
} else if (supportsImages && block.type === "image" && block.source?.type === "base64") {
|
||||
const format = (block.source.media_type || "image/jpeg").split("/")[1] || "jpeg";
|
||||
if (block.source.data)
|
||||
pendingImages.push({ format, source: { bytes: block.source.data } });
|
||||
} else if (block.type === "image" && typeof block.image === "string") {
|
||||
} else if (supportsImages && block.type === "image" && typeof block.image === "string") {
|
||||
// AI SDK-style image part: { type: "image", image: "data:...;base64,..." } (#1330)
|
||||
const url = block.image;
|
||||
if (url.startsWith("data:")) {
|
||||
|
||||
@@ -915,3 +915,65 @@ test("OpenAI -> Kiro serializes non-string role:tool content to non-empty text (
|
||||
assert.notEqual(text, "", "non-string tool content must not collapse to empty string");
|
||||
assert.match(text, /entry A/, "serialized content preserves the structured text blocks");
|
||||
});
|
||||
|
||||
// Only Claude models support images in Kiro. Non-Claude Kiro models
|
||||
// (deepseek-3.2, minimax-m2.5, glm-5, qwen3-coder-next, auto-kiro) must NOT
|
||||
// receive image attachments — attaching them is wrong for those models.
|
||||
const PNG_DATA_URL = "data:image/png;base64,aGVsbG8=";
|
||||
|
||||
function buildImageRequest(model: string) {
|
||||
return buildKiroPayload(
|
||||
model,
|
||||
{
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{ type: "text", text: "Describe this picture" },
|
||||
{ type: "image_url", image_url: { url: PNG_DATA_URL } },
|
||||
{ type: "image", source: { type: "base64", media_type: "image/png", data: "aGk=" } },
|
||||
{ type: "image", image: PNG_DATA_URL },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
false,
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
test("OpenAI -> Kiro attaches images for Claude models", () => {
|
||||
const result = buildImageRequest("claude-sonnet-4.6");
|
||||
const images = result.conversationState.currentMessage.userInputMessage.images;
|
||||
assert.ok(Array.isArray(images), "Claude models must keep image attachments");
|
||||
// Three image blocks (image_url + Anthropic base64 + AI SDK-style) → 3 entries
|
||||
assert.equal(images.length, 3, "all three supported image part shapes are attached");
|
||||
assert.equal(images[0].format, "png");
|
||||
assert.ok(images[0].source.bytes, "image bytes are preserved for Claude");
|
||||
});
|
||||
|
||||
test("OpenAI -> Kiro drops images for non-Claude models (deepseek)", () => {
|
||||
const result = buildImageRequest("deepseek-3.2");
|
||||
const images = result.conversationState.currentMessage.userInputMessage.images;
|
||||
assert.ok(
|
||||
images === undefined || images.length === 0,
|
||||
`non-Claude Kiro models must NOT receive image attachments, got: ${JSON.stringify(images)}`
|
||||
);
|
||||
// The accompanying text must still survive.
|
||||
assert.match(
|
||||
result.conversationState.currentMessage.userInputMessage.content,
|
||||
/Describe this picture/,
|
||||
"text content is preserved even when images are dropped"
|
||||
);
|
||||
});
|
||||
|
||||
test("OpenAI -> Kiro drops images for non-Claude models (glm / auto-kiro)", () => {
|
||||
for (const model of ["glm-5", "minimax-m2.5", "qwen3-coder-next", "auto-kiro"]) {
|
||||
const result = buildImageRequest(model);
|
||||
const images = result.conversationState.currentMessage.userInputMessage.images;
|
||||
assert.ok(
|
||||
images === undefined || images.length === 0,
|
||||
`${model} must NOT receive image attachments, got: ${JSON.stringify(images)}`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user