import test from "node:test";
import assert from "node:assert/strict";
import { buildClaudeSseFrames } from "../../open-sse/executors/devin-agentic/anthropicResponse.ts";
import {
MAX_TOOL_RESULT_CHARS,
serializeAnthropicForDevin,
} from "../../open-sse/executors/devin-agentic/serializer.ts";
import { parseDevinToolRequest } from "../../open-sse/executors/devin-agentic/toolParser.ts";
const readTool = {
name: "Read",
description: "Read a file",
input_schema: {
type: "object",
required: ["file_path"],
additionalProperties: false,
properties: {
file_path: { type: "string" },
},
},
};
test("devin agentic serializer preserves Anthropic tool history and schemas", () => {
const prompt = serializeAnthropicForDevin({
system: [{ type: "text", text: "Follow CLAUDE.md" }],
tools: [readTool],
messages: [
{ role: "user", content: [{ type: "text", text: "Inspect the file" }] },
{
role: "assistant",
content: [{ type: "tool_use", id: "toolu_1", name: "Read", input: { file_path: "a.ts" } }],
},
{
role: "user",
content: [{ type: "tool_result", tool_use_id: "toolu_1", content: "export {}" }],
},
],
});
assert.match(prompt.text, /\[System\]\nFollow CLAUDE\.md/);
assert.match(prompt.text, /\[Tool\] Read/);
assert.match(prompt.text, /Do not execute tools inside Devin/);
assert.match(prompt.text, /\[Assistant Tool Use\]/);
assert.match(prompt.text, /\[Tool Result\]/);
assert.equal(prompt.tools[0].name, "Read");
assert.match(prompt.idSeed, /^[a-f0-9]{24}$/);
});
test("devin agentic serializer preserves tool choice and validates tool-result association", () => {
const prompt = serializeAnthropicForDevin({
tools: [readTool],
tool_choice: { type: "tool", name: "Read" },
messages: [{ role: "user", content: "Read it" }],
});
assert.match(prompt.text, /Call exactly this tool: Read/);
assert.throws(
() =>
serializeAnthropicForDevin({
tools: [readTool],
messages: [
{
role: "user",
content: [{ type: "tool_result", tool_use_id: "toolu_missing", content: "nope" }],
},
],
}),
/unknown tool_use id/
);
});
test("devin agentic serializer marks bounded tool-result truncation explicitly", () => {
const prompt = serializeAnthropicForDevin({
tools: [readTool],
messages: [
{
role: "assistant",
content: [{ type: "tool_use", id: "toolu_big", name: "Read", input: { file_path: "a" } }],
},
{
role: "user",
content: [
{
type: "tool_result",
tool_use_id: "toolu_big",
content: "x".repeat(MAX_TOOL_RESULT_CHARS + 9),
},
],
},
],
});
assert.match(prompt.text, /\[TRUNCATED 9 CHARACTERS BY OMNIROUTE\]/);
});
test("devin agentic serializer rejects images explicitly", () => {
assert.throws(
() =>
serializeAnthropicForDevin({
messages: [{ role: "user", content: [{ type: "image", source: { type: "base64" } }] }],
}),
/image blocks are not supported/
);
});
test("devin agentic parser validates known tool and arguments", () => {
const parsed = parseDevinToolRequest(
'{"name":"Read","arguments":{"file_path":"src/index.ts"}}',
[readTool],
"request-a"
);
assert.equal(parsed?.name, "Read");
assert.deepEqual(parsed?.input, { file_path: "src/index.ts" });
assert.match(parsed?.id || "", /^tool_devin_/);
});
test("devin agentic tool ids are stable per request and distinct across turns", () => {
const text = '{"name":"Read","arguments":{"file_path":"src/index.ts"}}';
const first = parseDevinToolRequest(text, [readTool], "request-a");
const retry = parseDevinToolRequest(text, [readTool], "request-a");
const laterTurn = parseDevinToolRequest(text, [readTool], "request-b");
assert.equal(first?.id, retry?.id);
assert.notEqual(first?.id, laterTurn?.id);
});
test("devin agentic parser rejects unknown tools and invalid arguments", () => {
assert.throws(
() => parseDevinToolRequest('{"name":"Write","arguments":{}}', [readTool]),
/unknown tool/
);
assert.throws(
() => parseDevinToolRequest('{"name":"Read","arguments":{}}', [readTool]),
/file_path is required/
);
assert.throws(
() =>
parseDevinToolRequest(
'{"name":"Read","arguments":{"file_path":"a","extra":true}}',
[readTool]
),
/extra is not allowed/
);
});
test("devin agentic parser leaves narrative text as text, not a tool", () => {
assert.equal(parseDevinToolRequest("I read the file and it passes.", [readTool]), null);
});
test("devin agentic parser rejects mixed narrative and tool action", () => {
assert.throws(
() =>
parseDevinToolRequest(
'I will read it. {"name":"Read","arguments":{"file_path":"a.ts"}}',
[readTool]
),
/standalone tool envelope/
);
});
test("devin agentic SSE renders Anthropic tool lifecycle", () => {
const sse = buildClaudeSseFrames({
id: "msg_1",
type: "message",
role: "assistant",
model: "swe-1-7",
content: [{ type: "tool_use", id: "tool_devin_1", name: "Read", input: { file_path: "a.ts" } }],
stop_reason: "tool_use",
stop_sequence: null,
usage: { input_tokens: 10, output_tokens: 2 },
});
assert.match(sse, /event: message_start/);
assert.match(sse, /event: content_block_start/);
assert.match(sse, /input_json_delta/);
assert.match(sse, /event: message_delta/);
assert.match(sse, /"stop_reason":"tool_use"/);
assert.match(sse, /event: message_stop/);
});