import { test } from "node:test"; import assert from "node:assert/strict"; import { parseAnthropicToolUses } from "./anthropic"; import { parseOpenAIToolCalls } from "./openai"; import { parseGeminiFunctionCalls } from "./gemini"; import { FETCH_TOOL_NAME, REPORT_TOOL_NAME, SEARCH_TOOL_NAME } from "./shared"; test("parseAnthropicToolUses reads search and fetch_context calls", () => { const calls = parseAnthropicToolUses({ content: [ { type: "text", text: "thinking" }, { type: "tool_use", id: "a", name: SEARCH_TOOL_NAME, input: { query: "platner" } }, { type: "tool_use", id: "b", name: FETCH_TOOL_NAME, input: { video: "chan/xyz", aroundSeconds: 125 }, }, ], }); assert.equal(calls.length, 2); assert.deepEqual( { ...calls[0] }, { id: "a", name: SEARCH_TOOL_NAME, query: "platner", video: "", aroundSeconds: undefined }, ); assert.deepEqual( { ...calls[1] }, { id: "b", name: FETCH_TOOL_NAME, query: "", video: "chan/xyz", aroundSeconds: 125 }, ); }); test("parseOpenAIToolCalls reads search and fetch_context calls", () => { const calls = parseOpenAIToolCalls({ choices: [ { message: { tool_calls: [ { id: "1", function: { name: SEARCH_TOOL_NAME, arguments: JSON.stringify({ query: "platner" }) }, }, { id: "2", function: { name: FETCH_TOOL_NAME, arguments: JSON.stringify({ video: "chan/xyz", aroundSeconds: 60 }), }, }, ], }, }, ], }); assert.equal(calls.length, 2); assert.equal(calls[0].name, SEARCH_TOOL_NAME); assert.equal(calls[0].query, "platner"); assert.equal(calls[1].name, FETCH_TOOL_NAME); assert.equal(calls[1].video, "chan/xyz"); assert.equal(calls[1].aroundSeconds, 60); }); test("parseOpenAIToolCalls tolerates malformed arguments", () => { const calls = parseOpenAIToolCalls({ choices: [ { message: { tool_calls: [{ id: "1", function: { name: SEARCH_TOOL_NAME, arguments: "{bad" } }] } }, ], }); assert.equal(calls[0].query, ""); assert.equal(calls[0].video, ""); }); test("parseAnthropicToolUses reads update_report calls (section + content)", () => { const calls = parseAnthropicToolUses({ content: [ { type: "tool_use", id: "r1", name: REPORT_TOOL_NAME, input: { section: "Findings", content: "Alpha is discussed." }, }, ], }); assert.equal(calls.length, 1); assert.equal(calls[0].name, REPORT_TOOL_NAME); assert.equal(calls[0].section, "Findings"); assert.equal(calls[0].content, "Alpha is discussed."); // A search call carries no section/content keys (deep-equality with the older // shape still holds). const search = parseAnthropicToolUses({ content: [{ type: "tool_use", id: "s", name: SEARCH_TOOL_NAME, input: { query: "x" } }], }); assert.equal("section" in search[0], false); assert.equal("content" in search[0], false); }); test("parseOpenAIToolCalls reads update_report calls (section + content)", () => { const calls = parseOpenAIToolCalls({ choices: [ { message: { tool_calls: [ { id: "r1", function: { name: REPORT_TOOL_NAME, arguments: JSON.stringify({ section: "Timeline", content: "- 2020: a thing", }), }, }, ], }, }, ], }); assert.equal(calls[0].name, REPORT_TOOL_NAME); assert.equal(calls[0].section, "Timeline"); assert.equal(calls[0].content, "- 2020: a thing"); }); test("parseGeminiFunctionCalls reads update_report calls (section + content)", () => { const calls = parseGeminiFunctionCalls({ candidates: [ { content: { parts: [ { functionCall: { name: REPORT_TOOL_NAME, args: { section: "Sources", content: "[1] video" }, }, }, ], }, }, ], }); assert.equal(calls[0].name, REPORT_TOOL_NAME); assert.equal(calls[0].section, "Sources"); assert.equal(calls[0].content, "[1] video"); }); test("parseGeminiFunctionCalls reads search and fetch_context calls", () => { const calls = parseGeminiFunctionCalls({ candidates: [ { content: { parts: [ { functionCall: { name: SEARCH_TOOL_NAME, args: { query: "platner" } } }, { functionCall: { name: FETCH_TOOL_NAME, args: { video: "chan/xyz", aroundSeconds: 30 } } }, ], }, }, ], }); assert.equal(calls.length, 2); assert.equal(calls[0].query, "platner"); assert.equal(calls[1].name, FETCH_TOOL_NAME); assert.equal(calls[1].video, "chan/xyz"); assert.equal(calls[1].aroundSeconds, 30); });