Files
OmniRoute/tests/unit/kiro-tool-call-validation.test.ts
Diego Rodrigues de Sa e Souza 01a66e26c6 fix(ci): clear two base-reds on release/v3.8.51 — mutation-coverage gate + image-only-model guard (#12945)
Merged as part of the owner batch of 2026-09-11.

This PR had a live worktree in another session, so it sat outside the main 39. Merged on your explicit call, validated first rather than taken on trust: boarded with the other 10 worktree-held PRs into a consolidated worktree off `release/v3.8.51`.

- ESLint over every changed file: no errors
- `typecheck:core` clean; `check:dashboard-typecheck` OK; `check:changelog-integrity` OK
- complexity 2821 / baseline 3218 and cognitive-complexity 1272 / baseline 1437
- 203 of 208 assertions green. The 5 remaining (`guide-settings-route` ×4, `hard-session-lease-bypass-inventory` ×1) reproduce on the pure tip with nothing from this batch applied.
- `imageGeneration.ts` rebaselined 3259 → 3293 for #12945's image-only-model guard, landed separately in #13392 so nothing was pushed onto a live branch.

⚠️ base-red inherited: #12732 — provider count 356 vs 358 and `open-sse/utils/stream.ts` 3115 > frozen 3098, both reproducing on the pure tip.
2026-09-11 22:28:50 -03:00

283 lines
9.7 KiB
TypeScript

import test from "node:test";
import assert from "node:assert/strict";
import { KiroExecutor } from "../../open-sse/executors/kiro.ts";
import { FORMATS } from "../../open-sse/translator/formats.ts";
import { createSSETransformStreamWithLogger } from "../../open-sse/utils/stream.ts";
const textEncoder = new TextEncoder();
function crc32(bytes: Uint8Array): number {
const table = new Uint32Array(256);
for (let i = 0; i < 256; i++) {
let value = i;
for (let bit = 0; bit < 8; bit++) {
value = value & 1 ? 0xedb88320 ^ (value >>> 1) : value >>> 1;
}
table[i] = value >>> 0;
}
let value = 0xffffffff;
for (const byte of bytes) {
value = table[(value ^ byte) & 0xff] ^ (value >>> 8);
}
return (value ^ 0xffffffff) >>> 0;
}
function encodeHeader(name: string, value: string): Uint8Array {
const nameBytes = textEncoder.encode(name);
const valueBytes = textEncoder.encode(value);
const header = new Uint8Array(1 + nameBytes.length + 1 + 2 + valueBytes.length);
let offset = 0;
header[offset++] = nameBytes.length;
header.set(nameBytes, offset);
offset += nameBytes.length;
header[offset++] = 7;
header[offset++] = (valueBytes.length >> 8) & 0xff;
header[offset++] = valueBytes.length & 0xff;
header.set(valueBytes, offset);
return header;
}
function encodeEventFrame(eventType: string, payload: Record<string, unknown>): Uint8Array {
const headers = encodeHeader(":event-type", eventType);
const payloadBytes = textEncoder.encode(JSON.stringify(payload));
const totalLength = 12 + headers.length + payloadBytes.length + 4;
const frame = new Uint8Array(totalLength);
const view = new DataView(frame.buffer);
view.setUint32(0, totalLength, false);
view.setUint32(4, headers.length, false);
view.setUint32(8, crc32(frame.slice(0, 8)), false);
frame.set(headers, 12);
frame.set(payloadBytes, 12 + headers.length);
view.setUint32(totalLength - 4, crc32(frame.slice(0, totalLength - 4)), false);
return frame;
}
function buildEventStreamResponse(frames: Uint8Array[]): Response {
return new Response(
new ReadableStream<Uint8Array>({
start(controller) {
for (const frame of frames) controller.enqueue(frame);
controller.close();
},
}),
{ status: 200 }
);
}
function collectSseJson(text: string): Array<Record<string, unknown>> {
return text
.split("\n")
.filter((line) => line.startsWith("data: "))
.map((line) => line.slice(6).trim())
.filter((line) => line && line !== "[DONE]")
.map((line) => JSON.parse(line) as Record<string, unknown>);
}
function toolDeltas(text: string): Array<Record<string, unknown>> {
return collectSseJson(text).flatMap((chunk) => {
const choices = Array.isArray(chunk.choices) ? chunk.choices : [];
const choice = choices[0] as Record<string, unknown> | undefined;
const delta = choice?.delta as Record<string, unknown> | undefined;
return Array.isArray(delta?.tool_calls)
? (delta.tool_calls as Array<Record<string, unknown>>)
: [];
});
}
test("Kiro rejects a completed malformed tool_call wrapper without emitting a fake function", async () => {
const executor = new KiroExecutor();
const response = buildEventStreamResponse([
encodeEventFrame("toolUseEvent", {
toolUseId: "wrapper_1",
name: "tool_call",
input: { arguments: { query: "router" } },
}),
encodeEventFrame("messageStopEvent", {}),
]);
const text = await executor.transformEventStreamToSSE(response, "kiro-model").text();
assert.match(text, /invalid_kiro_tool_call/);
assert.match(text, /missing nested MCP tool name/);
assert.doesNotMatch(text, /"name":"tool_call"/);
assert.match(text, /data: \[DONE\]/);
});
test("Kiro validates a wrapper only after string fragments are complete", async () => {
const executor = new KiroExecutor();
const response = buildEventStreamResponse([
encodeEventFrame("toolUseEvent", { toolUseId: "wrapper_1", name: "tool_call" }),
encodeEventFrame("toolUseEvent", {
toolUseId: "wrapper_1",
name: "tool_call",
input: '{"name":"mcp_search",',
}),
encodeEventFrame("toolUseEvent", {
toolUseId: "wrapper_1",
name: "tool_call",
input: '"arguments":{"query":"router"}}',
}),
encodeEventFrame("messageStopEvent", {}),
]);
const text = await executor.transformEventStreamToSSE(response, "kiro-model").text();
const deltas = toolDeltas(text);
const args = deltas
.map((delta) => {
const fn = delta.function as Record<string, unknown> | undefined;
return typeof fn?.arguments === "string" ? fn.arguments : "";
})
.join("");
assert.doesNotMatch(text, /invalid_kiro_tool_call/);
assert.equal((deltas[0].function as Record<string, unknown>).name, "tool_call");
assert.deepEqual(JSON.parse(args), { name: "mcp_search", arguments: { query: "router" } });
});
test("Kiro waits for the final growing object before validating a wrapper", async () => {
const executor = new KiroExecutor();
const response = buildEventStreamResponse([
encodeEventFrame("toolUseEvent", {
toolUseId: "wrapper_1",
name: "tool_call",
input: { arguments: { query: "router" } },
}),
encodeEventFrame("toolUseEvent", {
toolUseId: "wrapper_1",
name: "tool_call",
input: { name: "mcp_search", arguments: { query: "router" } },
}),
encodeEventFrame("messageStopEvent", {}),
]);
const text = await executor.transformEventStreamToSSE(response, "kiro-model").text();
const deltas = toolDeltas(text);
const args = deltas
.map((delta) => {
const fn = delta.function as Record<string, unknown> | undefined;
return typeof fn?.arguments === "string" ? fn.arguments : "";
})
.join("");
assert.doesNotMatch(text, /invalid_kiro_tool_call/);
assert.deepEqual(JSON.parse(args), { name: "mcp_search", arguments: { query: "router" } });
});
test("Kiro assigns direct tools before buffered wrappers when interleaved", async () => {
const executor = new KiroExecutor();
const response = buildEventStreamResponse([
encodeEventFrame("toolUseEvent", {
toolUseId: "wrapper_1",
name: "tool_call",
input: { name: "mcp_search", arguments: { query: "router" } },
}),
encodeEventFrame("toolUseEvent", {
toolUseId: "direct_1",
name: "read_file",
input: { path: "README.md" },
}),
encodeEventFrame("messageStopEvent", {}),
]);
const text = await executor.transformEventStreamToSSE(response, "kiro-model").text();
const starts = toolDeltas(text).filter((delta) => typeof delta.id === "string");
assert.deepEqual(
starts.map((delta) => (delta.function as Record<string, unknown>).name),
["read_file", "tool_call"]
);
assert.deepEqual(
starts.map((delta) => delta.index),
[0, 1]
);
});
test("Kiro cancels the upstream body after an invalid wrapper", async () => {
const executor = new KiroExecutor();
let cancelled = false;
let resolveCancelled: (() => void) | undefined;
const cancelledPromise = new Promise<void>((resolve) => {
resolveCancelled = resolve;
});
const response = new Response(
new ReadableStream<Uint8Array>({
start(controller) {
controller.enqueue(
encodeEventFrame("toolUseEvent", {
toolUseId: "wrapper_1",
name: "tool_call",
input: { arguments: { query: "router" } },
})
);
controller.enqueue(encodeEventFrame("messageStopEvent", {}));
},
cancel() {
cancelled = true;
resolveCancelled?.();
},
})
);
const textPromise = executor.transformEventStreamToSSE(response, "kiro-model").text();
await Promise.race([cancelledPromise, new Promise((resolve) => setTimeout(resolve, 250))]);
await textPromise;
assert.equal(cancelled, true);
});
test("Kiro stream errors become Responses response.failed events", async () => {
const transform = createSSETransformStreamWithLogger(
FORMATS.KIRO,
FORMATS.OPENAI_RESPONSES,
"kiro",
null,
null,
"kiro-model"
);
// Drive the transform the way production does — `response.body.pipeThrough(transform)`
// read chunk by chunk — instead of `new Response(transform.readable).text()`.
// createStreamFailureAborter forwards the translated failure event and then errors the
// controller on purpose, so a translated upstream error can never end as a clean,
// successful-looking stream (open-sse/utils/streamFailureBoundary.ts). `.text()` cannot
// observe that: it discards the forwarded bytes and rejects, and the abandoned
// rejection lands as an unhandledRejection after the test ends. A reader keeps the
// event that was already delivered and still sees the termination.
const upstream = new ReadableStream<Uint8Array>({
start(controller) {
controller.enqueue(
textEncoder.encode(
`data: ${JSON.stringify({
error: {
message: "Invalid Kiro tool_call payload: missing nested MCP tool name at input.name",
type: "invalid_request_error",
code: "invalid_kiro_tool_call",
},
})}\n\n`
)
);
controller.close();
},
});
const reader = upstream.pipeThrough(transform).getReader();
let text = "";
let streamError: unknown = null;
try {
for (;;) {
const chunk = await reader.read();
if (chunk.done) break;
text += new TextDecoder().decode(chunk.value);
}
} catch (caught) {
streamError = caught;
}
assert.ok(streamError, "a translated upstream error must terminate the stream");
assert.match(text, /event: response\.failed/);
assert.match(text, /invalid_kiro_tool_call/);
assert.match(text, /missing nested MCP tool name/);
assert.doesNotMatch(text, /response\.output_item\.added/);
});