// Advanced API families through the real handler: embeddings, images, // speech, transcription, files, batches — with capability enforcement. import { assert, assertEquals } from "@std/assert"; import { createHandler } from "../../apps/gateway/main.ts"; import { type AppContext, NullToolExecutor, VERSION, } from "../../apps/gateway/context.ts"; import { ProviderManager } from "../../packages/providers/src/mod.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 } from "../../packages/testing/src/mod.ts"; import { ImageGenerationRequestSchema, MAX_TTS_INPUT_CHARS, } from "../../packages/contracts/src/mod.ts"; const base = "http://gateway.test"; // A real RSA private key so VertexAdapter.getToken() can sign the SA JWT // offline; token_uri points at the in-process mock's /token route. async function serviceAccountJson(tokenUri: string): Promise { const kp = 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 = new Uint8Array( await crypto.subtle.exportKey("pkcs8", kp.privateKey), ); let binary = ""; for (const b of pkcs8) { binary += String.fromCharCode(b); } const b64 = btoa(binary).match(/.{1,64}/g)!.join("\n"); const pem = `-----BEGIN PRIVATE KEY-----\n${b64}\n-----END PRIVATE KEY-----\n`; return JSON.stringify({ client_email: "svc@test.iam.gserviceaccount.com", private_key: pem, token_uri: tokenUri, }); } function makeContext(mockUrl: string, vertexSaJson: string): AppContext { return { providers: new ProviderManager([ { id: "openai", type: "openai", apiKey: "sk-adv", baseUrl: mockUrl, enabled: true, models: ["gpt-4o"], priority: 0, retry: { maxRetries: 0 }, }, { id: "anthropic", type: "anthropic", apiKey: "sk-ant", baseUrl: mockUrl, enabled: true, models: ["claude-x"], priority: 0, retry: { maxRetries: 0 }, }, { // Gemini Imagen via the native generativelanguage :predict surface. id: "gemini", type: "gemini", apiKey: "g-key", baseUrl: `${mockUrl}/v1beta/openai`, enabled: true, models: ["imagen-3.0-generate-002"], priority: 0, retry: { maxRetries: 0 }, }, { // Vertex Imagen via the publishers/google :predict surface (OAuth). id: "vertex", type: "vertex", baseUrl: mockUrl, projectId: "proj", location: "us-central1", serviceAccountJson: vertexSaJson, enabled: true, models: ["imagen-3.0-generate-002"], priority: 0, retry: { maxRetries: 0 }, }, ], "openai"), metrics: new Metrics(), logBus: new LogBus(), virtualKeys: new VirtualKeyManager(), mcp: new MCPRegistry(), plugins: new PluginManager(), toolExecutor: new NullToolExecutor(), version: VERSION, }; } Deno.test("advanced APIs: embeddings, images, speech, files, batches", async (t) => { const mock = new MockProvider((call) => { // Vertex OAuth token exchange (SA JWT -> access token). if (call.path === "/token") { return jsonResponse({ access_token: "vertex-token", expires_in: 3600 }); } // Google Imagen :predict, shared by the gemini (native generativelanguage) // and vertex (publishers/google) surfaces; both return base64 image bytes. if (call.path.endsWith(":predict")) { return jsonResponse({ predictions: [ { bytesBase64Encoded: "aW1hZ2VieXRlcw==", mimeType: "image/png" }, ], }); } switch (call.path) { case "/embeddings": return jsonResponse({ object: "list", data: [{ object: "embedding", index: 0, embedding: [0.1, 0.2] }], model: "text-embedding-3-small", usage: { prompt_tokens: 2, total_tokens: 2 }, }); case "/images/generations": return jsonResponse({ created: 1, data: [{ url: "https://img.example/1.png" }], }); case "/audio/speech": return new Response(new Uint8Array([1, 2, 3]), { headers: { "Content-Type": "audio/mpeg" }, }); case "/audio/transcriptions": return jsonResponse({ text: "transcribed words" }); case "/files": return jsonResponse({ id: "file-1", object: "file", bytes: 10, created_at: 1, filename: "data.jsonl", purpose: "batch", }); case "/batches": return jsonResponse({ id: "batch-1", object: "batch", input_file_id: "file-1", endpoint: "/v1/chat/completions", status: "validating", }); default: return jsonResponse({ error: `unexpected path ${call.path}` }, 500); } }); const handler = createHandler( makeContext(mock.url, await serviceAccountJson(`${mock.url}/token`)), ); try { await t.step("embeddings dispatch with model prefix stripped", async () => { const res = await handler( new Request(`${base}/v1/embeddings`, { method: "POST", body: JSON.stringify({ model: "openai/text-embedding-3-small", input: "hello", }), }), ); assertEquals(res.status, 200); const body = await res.json(); assertEquals(body.data[0].embedding, [0.1, 0.2]); const sent = mock.calls.at(-1)!; assertEquals(sent.path, "/embeddings"); assertEquals( (sent.body as { model: string }).model, "text-embedding-3-small", ); assertEquals(sent.headers.get("Authorization"), "Bearer sk-adv"); }); await t.step( "embeddings rejected for providers without support", async () => { const res = await handler( new Request(`${base}/v1/embeddings`, { method: "POST", body: JSON.stringify({ model: "anthropic/claude-x", input: "x" }), }), ); assertEquals(res.status, 400); assert( (await res.json()).error.message.includes("does not support"), ); }, ); await t.step("image generation (OpenAI rawProxy passthrough)", async () => { const res = await handler( new Request(`${base}/v1/images/generations`, { method: "POST", body: JSON.stringify({ model: "openai/gpt-image-1", prompt: "a frosty fjord", // The five vendor fields the request schema stripped before it // became .passthrough(). Asserted at the WIRE below, not at the // schema: a parse-level assertion passes while the widening is // still being dropped somewhere between parse and egress. quality: "high", style: "vivid", background: "transparent", output_format: "webp", moderation: "low", }), }), ); assertEquals(res.status, 200); assertEquals( (await res.json()).data[0].url, "https://img.example/1.png", ); // Byte-identical passthrough: forwarded to the OpenAI /images surface. const sentImage = mock.calls.at(-1)!; assertEquals(sentImage.path, "/images/generations"); assertEquals(sentImage.body, { model: "gpt-image-1", prompt: "a frosty fjord", quality: "high", style: "vivid", background: "transparent", output_format: "webp", moderation: "low", }); }); // D1-T28, envelope half: the bound has to surface as the canonical 400 // naming the offending field, and it has to refuse BEFORE the provider is // reached - an over-n request that still dispatches has bought the memory // the bound exists to deny. await t.step("D1-T28: over-bound n and sampleCount are 400", async () => { for (const field of ["n", "sampleCount"]) { const before = mock.calls.length; const res = await handler( new Request(`${base}/v1/images/generations`, { method: "POST", body: JSON.stringify({ model: "openai/gpt-image-1", prompt: "a frosty fjord", [field]: 50, }), }), ); assertEquals(res.status, 400); const body = await res.json(); assertEquals(body.error.type, "invalid_request_error"); assertEquals(body.error.param, field); assert(body.error.message.includes(field)); assertEquals(mock.calls.length, before); } }); await t.step( "D1-T28: n = 10 is admitted, n = 0 / -1 / 2.5 are not", async () => { const ok = await handler( new Request(`${base}/v1/images/generations`, { method: "POST", body: JSON.stringify({ model: "openai/gpt-image-1", prompt: "a frosty fjord", n: 10, }), }), ); assertEquals(ok.status, 200); await ok.body?.cancel(); for (const value of [0, -1, 2.5]) { const res = await handler( new Request(`${base}/v1/images/generations`, { method: "POST", body: JSON.stringify({ model: "openai/gpt-image-1", prompt: "a frosty fjord", n: value, }), }), ); assertEquals(res.status, 400, `n = ${value} must be refused`); assertEquals((await res.json()).error.param, "n"); } }, ); // D1-T28's companion on the TTS surface: the same envelope, from the same // class of gateway-set quantity ceiling. await t.step("speech input over MAX_TTS_INPUT_CHARS is 400", async () => { const before = mock.calls.length; const res = await handler( new Request(`${base}/v1/audio/speech`, { method: "POST", body: JSON.stringify({ model: "openai/tts-1", input: "a".repeat(MAX_TTS_INPUT_CHARS + 1), voice: "alloy", }), }), ); assertEquals(res.status, 400); const body = await res.json(); assertEquals(body.error.type, "invalid_request_error"); assertEquals(body.error.param, "input"); assertEquals(mock.calls.length, before); }); await t.step("image generation (Gemini Imagen :predict)", async () => { const res = await handler( new Request(`${base}/v1/images/generations`, { method: "POST", body: JSON.stringify({ model: "gemini/imagen-3.0-generate-002", prompt: "a frosty fjord", }), }), ); assertEquals(res.status, 200); // Imagen returns base64 bytes -> mapped to b64_json (no hosted URL). assertEquals((await res.json()).data[0].b64_json, "aW1hZ2VieXRlcw=="); assertEquals( mock.calls.at(-1)!.path, "/v1beta/models/imagen-3.0-generate-002:predict", ); }); await t.step("image generation (Vertex Imagen :predict)", async () => { const res = await handler( new Request(`${base}/v1/images/generations`, { method: "POST", body: JSON.stringify({ model: "vertex/imagen-3.0-generate-002", prompt: "a frosty fjord", }), }), ); assertEquals(res.status, 200); assertEquals((await res.json()).data[0].b64_json, "aW1hZ2VieXRlcw=="); assertEquals( mock.calls.at(-1)!.path, "/v1/projects/proj/locations/us-central1" + "/publishers/google/models/imagen-3.0-generate-002:predict", ); }); await t.step("speech returns binary audio", async () => { const res = await handler( new Request(`${base}/v1/audio/speech`, { method: "POST", body: JSON.stringify({ model: "openai/tts-1", input: "hello", voice: "alloy", }), }), ); assertEquals(res.status, 200); assertEquals(res.headers.get("Content-Type"), "audio/mpeg"); assertEquals((await res.arrayBuffer()).byteLength, 3); }); await t.step("transcription multipart passthrough", async () => { const form = new FormData(); form.set("model", "whisper-1"); form.set("file", new Blob([new Uint8Array(4)]), "audio.mp3"); const res = await handler( new Request(`${base}/v1/audio/transcriptions?provider=openai`, { method: "POST", body: form, }), ); assertEquals(res.status, 200); assertEquals((await res.json()).text, "transcribed words"); const sent = mock.calls.at(-1)!; assert(sent.headers.get("Content-Type")?.includes("multipart/form-data")); }); await t.step("file upload and batch creation", async () => { const form = new FormData(); form.set("purpose", "batch"); form.set("file", new Blob([new Uint8Array(4)]), "data.jsonl"); const upload = await handler( new Request(`${base}/v1/files?provider=openai`, { method: "POST", body: form, }), ); assertEquals(upload.status, 200); assertEquals((await upload.json()).id, "file-1"); const batch = await handler( new Request(`${base}/v1/batches?provider=openai`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ input_file_id: "file-1", endpoint: "/v1/chat/completions", completion_window: "24h", }), }), ); assertEquals(batch.status, 200); assertEquals((await batch.json()).status, "validating"); }); await t.step("files rejected for providers without support", async () => { const form = new FormData(); form.set("purpose", "batch"); const res = await handler( new Request(`${base}/v1/files?provider=anthropic`, { method: "POST", body: form, }), ); assertEquals(res.status, 400); await res.body?.cancel(); }); } finally { await mock.close(); } }); Deno.test("ImageGenerationRequest: legacy shape parses; Imagen params optional", () => { // Backward compatibility: the original {model?, prompt, n?, size?} shape // still parses with none of the new fields present. const legacy = ImageGenerationRequestSchema.safeParse({ model: "openai/gpt-image-1", prompt: "a frosty fjord", n: 2, size: "1024x1024", }); assert(legacy.success); if (legacy.success) { assertEquals(legacy.data.aspectRatio, undefined); assertEquals(legacy.data.sampleCount, undefined); assertEquals(legacy.data.negativePrompt, undefined); assertEquals(legacy.data.seed, undefined); } // The additive Imagen parameters are accepted when supplied. const imagen = ImageGenerationRequestSchema.safeParse({ prompt: "a frosty fjord", aspectRatio: "16:9", sampleCount: 3, negativePrompt: "blurry", seed: 42, }); assert(imagen.success); if (imagen.success) { assertEquals(imagen.data.aspectRatio, "16:9"); assertEquals(imagen.data.sampleCount, 3); assertEquals(imagen.data.seed, 42); } });