ai-chatbot/tests/integration/agent-model-gateway.test.ts

65 lines
4.3 KiB
TypeScript

import assert from "node:assert/strict";
import { test } from "node:test";
import { createZcodeModelGateway } from "@/lib/ai/zcode-agent/model-gateway";
import type { AgentModelEntry, AgentCircuitStore } from "@/lib/ai/runtime/agent-model-failover";
const body = { model: "primary", stream: true, messages: [{ role: "user", content: "request" }], tools: [{ type: "function", function: { name: "readCanvas", parameters: { type: "object" } } }] };
function entry(id: string, responses = false): AgentModelEntry {
return { model: { id, modelId: id, capabilities: responses ? { vip98k: { invocation: { path: "/v1/responses", headers: { "X-Relay-Route-Option": "route-proof" } } } } : null }, provider: { type: responses ? "vip98k" : "openai", baseUrl: "https://fixture.invalid", apiKey: "synthetic-only" } } as AgentModelEntry;
}
function fixture() {
const paused = new Set<string>(); const calls: Array<{ url: string; request: any }> = [];
const circuit: AgentCircuitStore = { isPaused: async e => paused.has(e.model.id), fail: async e => { paused.add(e.model.id); } };
return { paused, calls, circuit };
}
test("a broken primary and a Responses-only backup work through one stable native protocol", async () => {
const s = fixture(); const primary = entry("primary"); const backup = entry("backup", true);
const original = global.fetch;
global.fetch = async (url, init) => {
const request = JSON.parse(String(init?.body)); s.calls.push({ url: String(url), request });
if (request.model === "primary") return new Response("invalid authentication", { status: 401 });
assert.equal(request.stream, false); assert.equal(request.tools[0].name, "readCanvas");
assert.equal((init?.headers as Record<string, string>)["X-Relay-Route-Option"], "route-proof");
return Response.json({ status: "completed", output: [{ type: "function_call", call_id: "once", name: "readCanvas", arguments: "{}" }] });
};
try {
const gateway = await createZcodeModelGateway(primary, undefined, [primary, backup], s.circuit);
assert.equal(gateway.apiType, "openai-chat-completions");
const response = await gateway.forward(body, "/v1/chat/completions", new AbortController().signal);
assert.equal(response.status, 200); assert.match(await response.text(), /"id":"once"/);
assert.deepEqual([...s.paused], ["primary"]);
const nextGateway = await createZcodeModelGateway(primary, undefined, [primary, backup], s.circuit);
await nextGateway.forward(body, "/v1/chat/completions", new AbortController().signal);
assert.deepEqual(s.calls.map(x => x.request.model), ["primary", "backup", "backup"]);
assert.equal(s.calls[1].url, "https://fixture.invalid/v1/responses");
assert.deepEqual(body.messages, [{ role: "user", content: "request" }]);
} finally { global.fetch = original; }
});
test("truncated tool output cannot escape before fallback supplies a complete replacement", async () => {
const s = fixture(); const primary = entry("primary"); const backup = entry("backup");
const original = global.fetch;
global.fetch = async (_url, init) => {
const request = JSON.parse(String(init?.body)); s.calls.push({ url: "", request });
return Response.json({ choices: [{ finish_reason: "tool_calls", message: { tool_calls: [{ id: "once", type: "function", function: { name: "readCanvas", arguments: request.model === "primary" ? "{bad" : "{}" } }] } }] });
};
try {
const gateway = await createZcodeModelGateway(primary, undefined, [primary, backup], s.circuit);
const response = await gateway.forward(body, "/v1/chat/completions", new AbortController().signal);
const output = await response.text();
assert.equal(response.status, 200); assert(!output.includes("{bad"));
assert.deepEqual(s.calls.map(x => x.request.model), ["primary", "backup"]);
assert.deepEqual([...s.paused], ["primary"]);
} finally { global.fetch = original; }
});
test("already paused models receive no HTTP call even in a new gateway", async () => {
const s = fixture(); s.paused.add("primary");
const original = global.fetch; global.fetch = async () => { throw new Error("must not contact paused model"); };
try {
const gateway = await createZcodeModelGateway(entry("primary"), undefined, [entry("primary")], s.circuit);
assert.equal((await gateway.forward(body, "/v1/chat/completions", new AbortController().signal)).status, 503);
} finally { global.fetch = original; }
});