SysDeck/klanker-gate/tests/integration/provider_breadth_test.ts

439 lines
14 KiB
TypeScript
Executable File

// Wave-3 provider breadth: OpenAI-wire vendors through the shared adapter,
// native cohere/bedrock/vertex/elevenlabs adapters, and /v1/count_tokens.
import { assert, assertEquals } from "@std/assert";
import {
BedrockAdapter,
CohereAdapter,
ElevenLabsAdapter,
OPENAI_COMPAT_BASE_URLS,
ProviderManager,
VertexAdapter,
} from "../../packages/providers/src/mod.ts";
import type { ProviderAccountConfig } from "../../packages/contracts/src/mod.ts";
import { createHandler } from "../../apps/gateway/main.ts";
import {
type AppContext,
NullToolExecutor,
VERSION,
} from "../../apps/gateway/context.ts";
import { Metrics } from "../../packages/telemetry/src/metrics.ts";
import { LogBus } from "../../packages/telemetry/src/logbus.ts";
import { VirtualKeyManager } from "../../packages/governance/src/virtual_keys.ts";
import { MCPRegistry } from "../../packages/mcp/src/registry.ts";
import { PluginManager } from "../../packages/plugins/src/lifecycle.ts";
import {
jsonResponse,
MockProvider,
openAIChatBody,
readSSE,
sseResponse,
} from "../../packages/testing/src/mod.ts";
function makeContext(providers: ProviderManager): AppContext {
return {
providers,
metrics: new Metrics(),
logBus: new LogBus(),
virtualKeys: new VirtualKeyManager(),
mcp: new MCPRegistry(),
plugins: new PluginManager(),
toolExecutor: new NullToolExecutor(),
version: VERSION,
};
}
Deno.test("openai-compat vendors dispatch through the OpenAI wire", async (t) => {
const types: ProviderAccountConfig["type"][] = [
"groq",
"mistral",
"ollama",
"xai",
"perplexity",
"cerebras",
"nebius",
"sgl",
"parasail",
"huggingface",
];
// Every compat vendor has a default base URL registered.
for (const type of types) {
assert(OPENAI_COMPAT_BASE_URLS[type], `missing default baseUrl: ${type}`);
}
const mock = new MockProvider(() => jsonResponse(openAIChatBody("pong")));
try {
for (const type of types) {
await t.step(type, async () => {
const manager = new ProviderManager([{
id: type,
type,
apiKey: `key-${type}`,
baseUrl: mock.url,
enabled: true,
models: ["m1"],
priority: 0,
retry: { maxRetries: 0 },
}]);
const target = manager.resolve(`${type}/m1`);
const res = await target.adapter.chatCompletions({
model: target.model,
messages: [{ role: "user", content: "ping" }],
});
const body = await res.json() as {
choices: Array<{ message: { content: string } }>;
};
assertEquals(body.choices[0].message.content, "pong");
const call = mock.calls.at(-1)!;
assertEquals(call.path, "/chat/completions");
assertEquals(
call.headers.get("Authorization"),
`Bearer key-${type}`,
);
});
}
} finally {
await mock.close();
}
});
Deno.test("cohere: v2/chat translation both directions", async () => {
const mock = new MockProvider(() =>
jsonResponse({
id: "ch_1",
message: { content: [{ type: "text", text: "Hei fra Cohere" }] },
finish_reason: "COMPLETE",
usage: { billed_units: { input_tokens: 7, output_tokens: 3 } },
})
);
try {
const adapter = new CohereAdapter("co-key", mock.url);
const res = await adapter.chatCompletions({
model: "command-r-plus",
messages: [
{ role: "system", content: "Vær hyggelig." },
{ role: "user", content: "Hei" },
],
max_tokens: 100,
top_p: 0.9,
});
const chat = await res.json() as {
object: string;
choices: Array<{ message: { content: string }; finish_reason: string }>;
usage: { prompt_tokens: number; completion_tokens: number };
};
assertEquals(chat.object, "chat.completion");
assertEquals(chat.choices[0].message.content, "Hei fra Cohere");
assertEquals(chat.choices[0].finish_reason, "stop");
assertEquals(chat.usage.prompt_tokens, 7);
const sent = mock.calls[0].body as {
messages: Array<{ role: string; content: string }>;
max_tokens: number;
p: number;
};
assertEquals(mock.calls[0].path, "/v2/chat");
assertEquals(sent.messages[0], {
role: "system",
content: "Vær hyggelig.",
});
assertEquals(sent.max_tokens, 100);
assertEquals(sent.p, 0.9);
assertEquals(mock.calls[0].headers.get("Authorization"), "Bearer co-key");
} finally {
await mock.close();
}
});
Deno.test("cohere: stream events normalize to canonical chunks", async () => {
const frames = [
`data: ${JSON.stringify({ type: "message-start" })}\n\n`,
`data: ${
JSON.stringify({
type: "content-delta",
delta: { message: { content: { text: "Str" } } },
})
}\n\n`,
`data: ${
JSON.stringify({
type: "content-delta",
delta: { message: { content: { text: "øm" } } },
})
}\n\n`,
`data: ${
JSON.stringify({
type: "message-end",
delta: {
finish_reason: "COMPLETE",
usage: { billed_units: { input_tokens: 2, output_tokens: 5 } },
},
})
}\n\n`,
];
const mock = new MockProvider(() => sseResponse(frames));
try {
const adapter = new CohereAdapter("co-key", mock.url);
const res = await adapter.chatCompletions({
model: "command-r-plus",
messages: [{ role: "user", content: "hei" }],
stream: true,
});
const events = await readSSE(res);
assertEquals(events.at(-1), "[DONE]");
const chunks = events.slice(0, -1) as Array<{
choices: Array<{
delta: { content?: string };
finish_reason: string | null;
}>;
usage?: { completion_tokens: number };
}>;
const text = chunks.map((c) => c.choices[0].delta.content ?? "").join("");
assertEquals(text, "Strøm");
assertEquals(chunks.at(-1)!.choices[0].finish_reason, "stop");
assertEquals(chunks.at(-1)!.usage?.completion_tokens, 5);
} finally {
await mock.close();
}
});
Deno.test("bedrock: signed converse call translates to canonical chat", async () => {
const mock = new MockProvider(() =>
jsonResponse({
output: { message: { content: [{ text: "Fra Bedrock" }] } },
stopReason: "end_turn",
usage: { inputTokens: 11, outputTokens: 4, totalTokens: 15 },
})
);
try {
const adapter = new BedrockAdapter({
region: "us-east-1",
accessKeyId: "AKIDTEST",
secretAccessKey: "secret",
endpoint: mock.url,
});
const res = await adapter.chatCompletions({
model: "anthropic.claude-3-haiku",
messages: [
{ role: "system", content: "Kort." },
{ role: "user", content: "Hei" },
],
max_tokens: 64,
});
const chat = await res.json() as {
choices: Array<{ message: { content: string }; finish_reason: string }>;
usage: { prompt_tokens: number; total_tokens: number };
};
assertEquals(chat.choices[0].message.content, "Fra Bedrock");
assertEquals(chat.choices[0].finish_reason, "stop");
assertEquals(chat.usage.total_tokens, 15);
const call = mock.calls[0];
assertEquals(call.path, "/model/anthropic.claude-3-haiku/converse");
const auth = call.headers.get("Authorization") ?? "";
assert(
/^AWS4-HMAC-SHA256 Credential=AKIDTEST\/\d{8}\/us-east-1\/bedrock\/aws4_request, SignedHeaders=.*host.*x-amz-date.*, Signature=[0-9a-f]{64}$/
.test(auth),
`unexpected Authorization shape: ${auth}`,
);
const sent = call.body as {
system: Array<{ text: string }>;
messages: Array<{ role: string; content: Array<{ text: string }> }>;
inferenceConfig: { maxTokens: number };
};
assertEquals(sent.system[0].text, "Kort.");
assertEquals(sent.messages[0].content[0].text, "Hei");
assertEquals(sent.inferenceConfig.maxTokens, 64);
} finally {
await mock.close();
}
});
async function pemPrivateKey(): Promise<string> {
const pair = await crypto.subtle.generateKey(
{
name: "RSASSA-PKCS1-v1_5",
modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1]),
hash: "SHA-256",
},
true,
["sign", "verify"],
);
const pkcs8 = await crypto.subtle.exportKey("pkcs8", pair.privateKey);
const b64 = btoa(String.fromCharCode(...new Uint8Array(pkcs8)));
const lines = b64.match(/.{1,64}/g)!.join("\n");
return `-----BEGIN PRIVATE KEY-----\n${lines}\n-----END PRIVATE KEY-----\n`;
}
Deno.test("vertex: SA-JWT token exchange + OpenAI-compat dispatch + cache", async () => {
let tokenCalls = 0;
const tokenServer = Deno.serve(
{ port: 0, onListen: () => {} },
async (req) => {
tokenCalls++;
const form = new URLSearchParams(await req.text());
assertEquals(
form.get("grant_type"),
"urn:ietf:params:oauth:grant-type:jwt-bearer",
);
// A structurally valid JWS: three base64url segments.
assertEquals(form.get("assertion")!.split(".").length, 3);
return Response.json({ access_token: "tok-vertex", expires_in: 3600 });
},
);
const tokenUrl = `http://127.0.0.1:${
(tokenServer.addr as Deno.NetAddr).port
}/token`;
const mock = new MockProvider(() => jsonResponse(openAIChatBody("vertex!")));
try {
const adapter = new VertexAdapter({
projectId: "proj-1",
location: "europe-west1",
serviceAccountJson: JSON.stringify({
client_email: "svc@proj-1.iam.gserviceaccount.com",
private_key: await pemPrivateKey(),
}),
baseUrl: mock.url,
tokenUrl,
});
for (let i = 0; i < 2; i++) {
const res = await adapter.chatCompletions({
model: "gemini-2.5-pro",
messages: [{ role: "user", content: "hei" }],
});
await res.body?.cancel();
}
assertEquals(tokenCalls, 1); // token cached across calls
const call = mock.calls[0];
assertEquals(
call.path,
"/v1/projects/proj-1/locations/europe-west1/endpoints/openapi/chat/completions",
);
assertEquals(call.headers.get("Authorization"), "Bearer tok-vertex");
assertEquals(
(call.body as { model: string }).model,
"google/gemini-2.5-pro",
);
} finally {
await mock.close();
await tokenServer.shutdown();
}
});
Deno.test("elevenlabs: OpenAI speech body translates to the EL wire", async () => {
const mock = new MockProvider(() =>
new Response(new Uint8Array([1, 2, 3]), {
headers: { "Content-Type": "audio/mpeg" },
})
);
try {
const adapter = new ElevenLabsAdapter("el-key", mock.url);
const res = await adapter.rawProxy(
"/audio/speech",
new Request("http://internal/v1/audio/speech", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
model: "eleven_multilingual_v2",
input: "Hei verden",
voice: "Rachel",
}),
}),
);
assertEquals(res.headers.get("Content-Type"), "audio/mpeg");
await res.body?.cancel();
const call = mock.calls[0];
assertEquals(call.path, "/v1/text-to-speech/Rachel");
assertEquals(call.headers.get("xi-api-key"), "el-key");
assertEquals(call.body, {
text: "Hei verden",
model_id: "eleven_multilingual_v2",
});
} finally {
await mock.close();
}
});
Deno.test("/v1/count_tokens: native where the provider offers it, estimate otherwise", async () => {
const anthMock = new MockProvider((call) => {
if (call.path === "/messages/count_tokens") {
return jsonResponse({ input_tokens: 42 });
}
return jsonResponse({}, 404);
});
// Real OpenAI counts natively via /responses/input_tokens.
const openaiMock = new MockProvider((call) => {
if (call.path === "/responses/input_tokens") {
return jsonResponse({ input_tokens: 5 });
}
return jsonResponse({}, 404);
});
try {
const providers = new ProviderManager([
{
id: "anthropic",
type: "anthropic",
apiKey: "a-key",
baseUrl: anthMock.url,
enabled: true,
models: ["claude-x"],
priority: 0,
retry: { maxRetries: 0 },
},
{
id: "openai",
type: "openai",
apiKey: "o-key",
baseUrl: openaiMock.url,
enabled: true,
models: ["gpt-x"],
priority: 0,
retry: { maxRetries: 0 },
},
{
// groq reuses the shared OpenAI adapter but has no native counter,
// so it keeps the documented chars/4 estimate (no upstream call).
id: "groq",
type: "groq",
apiKey: "g-key",
enabled: true,
models: ["mixtral"],
priority: 0,
retry: { maxRetries: 0 },
},
]);
const handler = createHandler(makeContext(providers));
const countTokens = (model: string, content: string) =>
handler(
new Request("http://gateway.test/v1/count_tokens", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
model,
messages: [{ role: "user", content }],
}),
}),
);
// Anthropic: native /messages/count_tokens.
const anthropic = await countTokens("anthropic/claude-x", "tell noe");
assertEquals(await anthropic.json(), {
input_tokens: 42,
estimated: false,
});
// OpenAI: native /responses/input_tokens.
const openai = await countTokens("openai/gpt-x", "tell noe");
assertEquals(await openai.json(), { input_tokens: 5, estimated: false });
// Groq: no native surface -> chars/4 estimate (8 chars -> 2 tokens).
const estimated = await countTokens("groq/mixtral", "abcdefgh");
assertEquals(await estimated.json(), { input_tokens: 2, estimated: true });
} finally {
await anthMock.close();
await openaiMock.close();
}
});