feat(providers): support Vercel AI Gateway embeddings and images (#5968)

* 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 <tantai@newnol.io.vn>
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 <tantai@newnol.io.vn>
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-07-03 00:09:52 -03:00
committed by GitHub
parent 1264d1f882
commit b253d8a74a
4 changed files with 80 additions and 0 deletions

View File

@@ -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

View File

@@ -93,6 +93,17 @@ export const EMBEDDING_PROVIDERS: Record<string, EmbeddingProvider> = {
],
},
"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",

View File

@@ -175,6 +175,20 @@ export const IMAGE_PROVIDERS: Record<string, ImageProviderConfig> = {
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",

View File

@@ -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}`);
});
});
});