feat(vercel-ai-gateway): support embeddings, images and credit usage

Extend Vercel AI Gateway beyond chat: add OpenAI-compatible embeddings
and image generation endpoints, credit balance fetch on the usage
dashboard, retry on 429, and models catalog fetcher.

Thinking/reasoning mapping is omitted pending a project-wide refactor.

Co-authored-by: Ngô Tấn Tài <tantai@newnol.io.vn>
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ngô Tấn Tài
2026-06-13 10:54:51 +07:00
committed by decolua
co-authored by Cursor
parent d9b030011f
commit b33cbb0280
10 changed files with 164 additions and 2 deletions
+15
View File
@@ -222,6 +222,21 @@ describe("buildEmbeddingsUrl", () => {
expect(url).toBe("https://openrouter.ai/api/v1/embeddings");
});
it("vercel-ai-gateway → https://ai-gateway.vercel.sh/v1/embeddings", async () => {
vi.mocked(fetch).mockResolvedValueOnce(makeProviderResponse(VALID_EMBEDDING_RESPONSE));
await handleEmbeddingsCore(makeOptions({
modelInfo: { provider: "vercel-ai-gateway", model: "openai/text-embedding-3-small" },
credentials: { apiKey: "vag-test-key" },
}));
const [url, init] = vi.mocked(fetch).mock.calls[0];
const sent = JSON.parse(init.body);
expect(url).toBe("https://ai-gateway.vercel.sh/v1/embeddings");
expect(init.headers.Authorization).toBe("Bearer vag-test-key");
expect(sent.model).toBe("openai/text-embedding-3-small");
});
it("openai-compatible-* → uses baseUrl from providerSpecificData", async () => {
vi.mocked(fetch).mockResolvedValueOnce(makeProviderResponse(VALID_EMBEDDING_RESPONSE));
+32
View File
@@ -263,6 +263,38 @@ describe("handleImageGenerationCore", () => {
);
});
it("handles Vercel AI Gateway image generation as OpenAI-compatible", async () => {
global.fetch.mockResolvedValueOnce(
new Response(
JSON.stringify({
created: 1234567890,
data: [{ url: "https://example.com/vercel-image.png" }],
}),
{ status: 200, headers: { "Content-Type": "application/json" } }
)
);
const result = await handleImageGenerationCore({
body: { prompt: "A watercolor castle", n: 1, size: "1024x1024" },
modelInfo: { provider: "vercel-ai-gateway", model: "openai/gpt-image-1" },
credentials: { apiKey: "vag-test-key" },
log: null,
});
expect(result.success).toBe(true);
expect(global.fetch).toHaveBeenCalledWith(
"https://ai-gateway.vercel.sh/v1/images/generations",
expect.objectContaining({
method: "POST",
headers: expect.objectContaining({
"Content-Type": "application/json",
Authorization: "Bearer vag-test-key",
}),
body: expect.stringContaining('"model":"openai/gpt-image-1"'),
})
);
});
it("handles HuggingFace binary response", async () => {
const imageBuffer = new Uint8Array([0x89, 0x50, 0x4e, 0x47]); // PNG header
global.fetch.mockResolvedValueOnce(