From b253d8a74a4cca14a176252e6c04fe3ac22cc88c Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> Date: Fri, 3 Jul 2026 00:09:52 -0300 Subject: [PATCH] feat(providers): support Vercel AI Gateway embeddings and images (#5968) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(providers): support Vercel AI Gateway embeddings and images Extends the existing vercel-ai-gateway (alias vag) provider — currently chat-only — with embeddings and image generation support, since the gateway's OpenAI-compatible /v1 API also exposes /embeddings and /images/generations. Adds entries to EMBEDDING_PROVIDERS (embeddingRegistry.ts) and IMAGE_PROVIDERS (imageRegistry.ts) modeled on the existing openai entries. Out of scope for this PR (tracked as follow-ups): the /v1/credits usage reader, retry:{429:2} tuning, and claude->reasoning_effort mapping. Co-authored-by: Ngô Tấn Tài Inspired-by: https://github.com/decolua/9router/pull/1704 * chore(changelog): restore release entries + add vercel-gateway media bullet --------- Co-authored-by: Ngô Tấn Tài --- CHANGELOG.md | 1 + open-sse/config/embeddingRegistry.ts | 11 +++++ open-sse/config/imageRegistry.ts | 14 ++++++ tests/unit/vercel-ai-gateway-media.test.ts | 54 ++++++++++++++++++++++ 4 files changed, 80 insertions(+) create mode 100644 tests/unit/vercel-ai-gateway-media.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index aa940b405a..afc097369b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ - **Discovery tool (Phase 2):** add the `discoveryResults` DB module (CRUD over the `discovery_results` table, migration 074) and wire the opt-in provider-discovery service to persist and read findings through it (`persistDiscoveryResult`, `getDiscoveryResults`, `getDiscoveryResultById`, `markVerified`, `deleteDiscoveryResult`) with `(provider, method, endpoint)` upsert de-duplication. Adds the `/api/discovery/*` HTTP surface — `GET /results`, `GET|DELETE /results/:id`, `POST /scan`, `POST /verify/:id` — under **strict loopback-only** authorization (`/api/discovery/` is in `LOCAL_ONLY_API_PREFIXES` and is NOT manage-scope-bypassable, so the `scan` route's outbound probes can never be reached from a tunnel/remote origin). Adds a **dashboard UI tab** (Tools → Discovery, `/dashboard/discovery`) to run scans and review, verify, or delete findings. The service stays **opt-in / default-off**. - **feat(proxy):** add Webshare proxy pool import and sync — a `WebshareProvider` (`FreeProxyProvider`) that paginates `proxy.webshare.io/api/v2/proxy/list/` gated on `FREE_PROXY_WEBSHARE_API_KEY`, SSRF-guards imported hosts, and tombstones retired proxy IDs via `pruneStaleFreeProxies()`. (thanks @ricatix) - **feat(api-keys):** track devices/connections per API key — an in-memory, TTL-evicted device fingerprint tracker (SHA-256 of masked IP + truncated user-agent) wired non-blocking into the chat path and surfaced via `GET /api/keys/[id]/devices` with a dashboard device-count chip. (thanks @mugnimaestra) +- **feat(providers):** support Vercel AI Gateway embeddings and image generation. (thanks @newnol) ### 🔧 Bug Fixes diff --git a/open-sse/config/embeddingRegistry.ts b/open-sse/config/embeddingRegistry.ts index d7ba0777f5..16ffcd68ab 100644 --- a/open-sse/config/embeddingRegistry.ts +++ b/open-sse/config/embeddingRegistry.ts @@ -93,6 +93,17 @@ export const EMBEDDING_PROVIDERS: Record = { ], }, + "vercel-ai-gateway": { + id: "vercel-ai-gateway", + baseUrl: "https://ai-gateway.vercel.sh/v1/embeddings", + authType: "apikey", + authHeader: "bearer", + models: [ + { id: "text-embedding-3-small", name: "Text Embedding 3 Small", dimensions: 1536 }, + { id: "text-embedding-3-large", name: "Text Embedding 3 Large", dimensions: 3072 }, + ], + }, + upstage: { id: "upstage", baseUrl: "https://api.upstage.ai/v1/embeddings", diff --git a/open-sse/config/imageRegistry.ts b/open-sse/config/imageRegistry.ts index bc83b37c84..c40d50cee6 100644 --- a/open-sse/config/imageRegistry.ts +++ b/open-sse/config/imageRegistry.ts @@ -175,6 +175,20 @@ export const IMAGE_PROVIDERS: Record = { supportedSizes: ["1024x1024", "2048x2048"], }, + "vercel-ai-gateway": { + id: "vercel-ai-gateway", + alias: "vag", + baseUrl: "https://ai-gateway.vercel.sh/v1/images/generations", + authType: "apikey", + authHeader: "bearer", + format: "openai", + models: [ + { id: "gpt-image-1", name: "GPT Image 1" }, + { id: "black-forest-labs/flux-1.1-pro", name: "FLUX 1.1 Pro" }, + ], + supportedSizes: ["1024x1024", "1024x1792", "1792x1024"], + }, + together: { id: "together", baseUrl: "https://api.together.xyz/v1/images/generations", diff --git a/tests/unit/vercel-ai-gateway-media.test.ts b/tests/unit/vercel-ai-gateway-media.test.ts new file mode 100644 index 0000000000..b223235aea --- /dev/null +++ b/tests/unit/vercel-ai-gateway-media.test.ts @@ -0,0 +1,54 @@ +import { describe, it } from "node:test"; +import assert from "node:assert/strict"; +import { + EMBEDDING_PROVIDERS, + getEmbeddingProvider, +} from "../../open-sse/config/embeddingRegistry.ts"; +import { IMAGE_PROVIDERS, getImageProvider } from "../../open-sse/config/imageRegistry.ts"; + +describe("vercel-ai-gateway media registry entries (upstream #1704)", () => { + describe("embeddingRegistry — vercel-ai-gateway", () => { + it("is registered in EMBEDDING_PROVIDERS", () => { + assert.ok( + EMBEDDING_PROVIDERS["vercel-ai-gateway"], + "vercel-ai-gateway should be in EMBEDDING_PROVIDERS" + ); + }); + + it("resolves vercel-ai-gateway provider config", () => { + const p = getEmbeddingProvider("vercel-ai-gateway"); + assert.ok(p, "getEmbeddingProvider should resolve vercel-ai-gateway"); + assert.equal(p.id, "vercel-ai-gateway"); + assert.equal(p.baseUrl, "https://ai-gateway.vercel.sh/v1/embeddings"); + assert.equal(p.authType, "apikey"); + assert.equal(p.authHeader, "bearer"); + }); + + it("has at least one embedding model", () => { + const p = getEmbeddingProvider("vercel-ai-gateway"); + assert.ok(p.models.length >= 1, `Expected ≥1 models, got ${p.models.length}`); + }); + }); + + describe("imageRegistry — vercel-ai-gateway", () => { + it("is registered in IMAGE_PROVIDERS", () => { + assert.ok(IMAGE_PROVIDERS["vercel-ai-gateway"], "vercel-ai-gateway should be in IMAGE_PROVIDERS"); + }); + + it("resolves vercel-ai-gateway image provider config", () => { + const p = getImageProvider("vercel-ai-gateway"); + assert.ok(p, "getImageProvider should resolve vercel-ai-gateway"); + assert.equal(p.id, "vercel-ai-gateway"); + assert.equal(p.alias, "vag"); + assert.equal(p.baseUrl, "https://ai-gateway.vercel.sh/v1/images/generations"); + assert.equal(p.authType, "apikey"); + assert.equal(p.authHeader, "bearer"); + assert.equal(p.format, "openai"); + }); + + it("has at least one image model", () => { + const p = getImageProvider("vercel-ai-gateway"); + assert.ok(p.models.length >= 1, `Expected ≥1 models, got ${p.models.length}`); + }); + }); +});