Files
diegosouzapw--omniroute/tests/unit/translator-tools-anthropic-shape.test.ts
2026-07-13 13:39:12 +08:00

120 lines
4.6 KiB
TypeScript

// Port of upstream decolua/9router@45240c19.
// Anthropic-compatible Claude-shape providers (MiniMax and friends) reject
// tools that carry a `type` field with error code 2013 ("invalid tool type").
// When a client sends Anthropic-format requests but the tools are still in
// OpenAI wire shape — `{ type: "function", function: { name, description,
// parameters } }` — prepareClaudeRequest() must normalize them to the
// Anthropic-native shape `{ name, description, input_schema }` before
// forwarding to non-Anthropic providers. For Anthropic itself ("claude")
// the shape is left untouched so first-party server-side tools like
// web_search_20250305 keep flowing.
import { describe, test } from "node:test";
import assert from "node:assert/strict";
import { prepareClaudeRequest } from "../../open-sse/translator/helpers/claudeHelper.ts";
describe("prepareClaudeRequest tool-shape normalization for non-Anthropic providers", () => {
const buildBody = () => ({
model: "MiniMax-M2.7",
messages: [{ role: "user", content: [{ type: "text", text: "hi" }] }],
max_tokens: 256,
tools: [
// OpenAI wire shape inside an Anthropic-format request.
{
type: "function",
function: {
name: "get_weather",
description: "Get the weather",
parameters: {
type: "object",
properties: { city: { type: "string" } },
required: ["city"],
},
},
},
// Anthropic-native shape (no `type` field at all) — must pass through unchanged.
{
name: "lookup_user",
description: "Find a user",
input_schema: {
type: "object",
properties: { id: { type: "string" } },
required: ["id"],
},
},
// Built-in Anthropic server-side tool that non-Anthropic providers
// must NOT receive at all.
{
type: "web_search_20250305",
name: "web_search",
},
],
});
test("folds function.{name,description,parameters} and drops `type` for minimax", () => {
const body = buildBody();
const result = prepareClaudeRequest(body as any, "minimax");
assert.ok(Array.isArray(result.tools));
// Built-in web_search_20250305 must be stripped for non-Anthropic providers.
assert.equal(result.tools!.length, 2);
const weather = result.tools!.find((t: any) => t.name === "get_weather") as any;
assert.ok(weather, "get_weather tool must survive normalization");
assert.equal(weather.type, undefined, "`type` must be stripped");
assert.equal(weather.function, undefined, "`function` wrapper must be folded");
assert.equal(weather.description, "Get the weather");
assert.deepEqual(weather.input_schema, {
type: "object",
properties: { city: { type: "string" } },
required: ["city"],
});
const lookup = result.tools!.find((t: any) => t.name === "lookup_user") as any;
assert.ok(lookup, "lookup_user tool must survive normalization");
assert.equal(lookup.type, undefined, "stray `type` must be stripped");
assert.deepEqual(lookup.input_schema, {
type: "object",
properties: { id: { type: "string" } },
required: ["id"],
});
});
test("does NOT rewrite tool shape for first-party claude (keeps built-ins)", () => {
const body = {
model: "claude-sonnet-4-5",
messages: [{ role: "user", content: [{ type: "text", text: "hi" }] }],
max_tokens: 256,
tools: [
// Anthropic-native shape.
{
name: "lookup_user",
description: "Find a user",
input_schema: {
type: "object",
properties: { id: { type: "string" } },
required: ["id"],
},
},
// Built-in Anthropic server-side tool — MUST be preserved for first-party claude.
{
type: "web_search_20250305",
name: "web_search",
},
],
};
const result = prepareClaudeRequest(body as any, "claude");
assert.ok(Array.isArray(result.tools));
// Built-in web_search_20250305 must NOT be stripped for Anthropic-native upstream.
const webSearch = result.tools!.find((t: any) => t.type === "web_search_20250305");
assert.ok(webSearch, "web_search_20250305 must be preserved for claude");
assert.equal(webSearch!.type, "web_search_20250305");
// Anthropic-shape tool is untouched.
const lookup = result.tools!.find((t: any) => t.name === "lookup_user") as any;
assert.ok(lookup);
assert.deepEqual(lookup.input_schema, {
type: "object",
properties: { id: { type: "string" } },
required: ["id"],
});
});
});