import test from "node:test"; import assert from "node:assert/strict"; // Port of 9router#2196 (fixes #2195): Claude's tool schema requires each tool to // carry an explicit `type` discriminator. Anthropic's first-party API infers // "custom" when omitted, but strict Anthropic-compatible gateways (e.g. MiniMax) // reject the payload with HTTP 400. defaultClaudeToolType() backfills the missing // `type` so legacy Claude-format tool definitions survive strict gateways. const { defaultClaudeToolType } = await import( "../../open-sse/handlers/chatCore/claudeToolDefaults.ts" ); test("backfills type:'custom' on a Claude tool missing the type field", () => { const tools = [ { name: "get_weather", description: "Get weather", input_schema: { type: "object" } }, ]; const out = defaultClaudeToolType(tools) as Array>; assert.equal(out[0].type, "custom"); // Other fields are preserved untouched. assert.equal(out[0].name, "get_weather"); assert.equal(out[0].description, "Get weather"); assert.deepEqual(out[0].input_schema, { type: "object" }); }); test("leaves tools that already declare a type untouched", () => { const tools = [ { type: "custom", name: "a", input_schema: {} }, { type: "computer_20241022", name: "computer" }, { type: "bash_20241022", name: "bash" }, ]; const out = defaultClaudeToolType(tools) as Array>; assert.deepEqual( out.map((t) => t.type), ["custom", "computer_20241022", "bash_20241022"] ); // Non-custom built-in tool types must be preserved, not overwritten. assert.equal(out[1].type, "computer_20241022"); }); test("normalizes a mixed array — only the type-less entries get defaulted", () => { const tools = [ { type: "computer_20241022", name: "computer" }, { name: "get_weather", input_schema: {} }, ]; const out = defaultClaudeToolType(tools) as Array>; assert.equal(out[0].type, "computer_20241022"); assert.equal(out[1].type, "custom"); }); test("returns non-array input unchanged (no tools / undefined)", () => { assert.equal(defaultClaudeToolType(undefined), undefined); assert.equal(defaultClaudeToolType(null), null); const obj = { not: "an array" }; assert.equal(defaultClaudeToolType(obj), obj); }); test("does not mutate the original tool objects (returns new entries for defaulted tools)", () => { const original = { name: "x", input_schema: {} }; const tools = [original]; const out = defaultClaudeToolType(tools) as Array>; assert.equal(original.type, undefined, "original tool must stay untouched"); assert.equal(out[0].type, "custom"); }); test("passes non-object array entries through unchanged (no garbage wrapping)", () => { // A null/primitive entry must NOT be spread into a fabricated tool like // { type: "custom", '0': 'h' } — pass it through as-is. const tools = [ { name: "real_tool", input_schema: {} }, // object → defaulted null, "weird", 42, ]; const out = defaultClaudeToolType(tools) as unknown[]; assert.equal((out[0] as Record).type, "custom", "real object gets defaulted"); assert.equal(out[1], null, "null passes through unchanged"); assert.equal(out[2], "weird", "string passes through unchanged"); assert.equal(out[3], 42, "number passes through unchanged"); });