From b0f5bd0f73d4492a517f74908bc4c2e2a28581db Mon Sep 17 00:00:00 2001 From: Universe Date: Sat, 6 Jun 2026 18:59:05 +0900 Subject: [PATCH] =?UTF-8?q?refactor(ai):=20rename=20provider=20id=20ai-gat?= =?UTF-8?q?eway=20=E2=86=92=20vercel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The BYOK provider id "ai-gateway" was misleading — it's a service name, not a provider identity. Renamed to "vercel" to match how the other entry ("openrouter") names its provider. Also renames the routing label on @grida/ai-models cards from "gateway" to "vercel" for the same reason. - ByokProviderId: "ai-gateway" → "vercel" (label "AI Gateway" → "Vercel") - models.Provider: "gateway" → "vercel" (1 type + 10 image-model cards) - makeAiGatewayFactory → makeVercelFactory - Sandbox policy key renamed; allowlisted hosts unchanged (ai-gateway.vercel.sh, *.vercel-ai.com — these are real URLs) - BYOK_AI_GATEWAY_API_KEY env var preserved (mirrors Vercel's own AI_GATEWAY_API_KEY convention); "AI Gateway" prose preserved where it names the product Breaking — by design, no compat shim: - Local auth.json entries keyed "ai-gateway" are orphaned; desktop users will need to re-enter their Vercel BYOK key - /secrets/* HTTP routes will 400 on provider_id: "ai-gateway" - @grida/ai-models consumers will see provider: "vercel" on cards --- docs/contributing/billing.md | 2 +- docs/wg/ai/grida/architecture.md | 6 ++-- editor/.env.example | 2 +- editor/lib/ai/models.ts | 2 +- editor/lib/desktop/bridge.test.ts | 2 +- .../grida-ai-agent/src/__public-api__.test.ts | 8 ++--- packages/grida-ai-agent/src/auth/file.ts | 6 ++-- .../grida-ai-agent/src/http/routes/secrets.ts | 2 +- .../src/protocol/provider-ids.ts | 4 +-- packages/grida-ai-agent/src/providers/byok.ts | 2 +- .../src/providers/index.test.ts | 16 +++++----- .../grida-ai-agent/src/providers/index.ts | 8 ++--- packages/grida-ai-agent/src/sandbox/policy.ts | 2 +- packages/grida-ai-agent/src/secrets.ts | 2 +- .../grida-ai-models/__tests__/models.test.ts | 4 +-- packages/grida-ai-models/src/models.ts | 30 +++++++++---------- 16 files changed, 48 insertions(+), 50 deletions(-) diff --git a/docs/contributing/billing.md b/docs/contributing/billing.md index 631a813da..a4a9751b4 100644 --- a/docs/contributing/billing.md +++ b/docs/contributing/billing.md @@ -26,7 +26,7 @@ BYOK_OPENROUTER_API_KEY=sk-or-v1-... # https://openrouter.ai/keys - Bypasses **billing only — never auth.** Still sign in (`insider@grida.co` / `password`); a resolvable org is still required (an unauthenticated request still 401s). - **Text/chat only** — BYOK swaps the AI-SDK provider, so only the text path is unbilled. Image/audio go through Replicate (`withTransaction`) and **still gate + bill even under BYOK** — those features need the full billing setup. (OpenRouter also exposes no image/audio models.) Catalog model IDs are unchanged; use IDs your provider accepts (edit `editor/lib/ai/models.ts` locally if one 404s). -- Precedence if both are set: OpenRouter, then AI Gateway. Fail-closed — an empty/unset (or whitespace-only) key falls back to the billed path. +- Precedence if both are set: OpenRouter, then Vercel. Fail-closed — an empty/unset (or whitespace-only) key falls back to the billed path. - **Never set `BYOK_*` on a hosted or preview deploy.** It disables billing **and** the org-id sanity gate for every org. Contributor / self-host / local only. See [SECURITY.md](https://github.com/gridaco/grida/blob/main/SECURITY.md) (`GRIDA-SEC-003`, BYOK carve-out). Working on billing itself? Ignore BYOK and continue with the full setup below. diff --git a/docs/wg/ai/grida/architecture.md b/docs/wg/ai/grida/architecture.md index 34e8b6b34..e08dd3e5d 100644 --- a/docs/wg/ai/grida/architecture.md +++ b/docs/wg/ai/grida/architecture.md @@ -122,7 +122,7 @@ There is intentionally no `/secrets/get`, no `/auth/*`, and no V1 provider resolution is BYOK-only: 1. `openrouter` -2. `ai-gateway` +2. `vercel` 3. unavailable (`provider_down`) `AgentRunOptions.providerId` accepts only `ByokProviderId`. The package root @@ -215,8 +215,8 @@ describe("handshake", () => { }); describe("provider resolution", () => { - it("prefers OpenRouter BYOK over AI Gateway BYOK"); - it("falls back to AI Gateway BYOK"); + it("prefers OpenRouter BYOK over Vercel BYOK"); + it("falls back to Vercel BYOK"); it("throws provider_down when no BYOK key is present"); it("validates explicit BYOK provider ids"); }); diff --git a/editor/.env.example b/editor/.env.example index 7d18eae79..60add717a 100644 --- a/editor/.env.example +++ b/editor/.env.example @@ -38,7 +38,7 @@ OPENAI_API_KEY='sk-xxx' # route through that provider and BYPASS the billing layer (no credit # gate, no metering). does NOT bypass auth (login + org still required). # text/chat only; catalog model IDs unchanged (use IDs the provider -# accepts). precedence: openrouter, then ai-gateway. +# accepts). precedence: openrouter, then vercel. # BYOK_OPENROUTER_API_KEY=sk-or-v1-xxx # https://openrouter.ai/keys # BYOK_AI_GATEWAY_API_KEY=... # dedicated Vercel AI Gateway key diff --git a/editor/lib/ai/models.ts b/editor/lib/ai/models.ts index daaebeec1..2206ad164 100644 --- a/editor/lib/ai/models.ts +++ b/editor/lib/ai/models.ts @@ -101,7 +101,7 @@ export const gateway = createGateway({ // active only when a key env var is a non-empty string after trim // (whitespace-only secrets fall back to the billed path). // -// Implementations (precedence: OpenRouter first, then AI Gateway). A +// Implementations (precedence: OpenRouter first, then Vercel). A // third BYOK key is a new branch here — no registry. // --------------------------------------------------------------------------- function resolveByokProvider() { diff --git a/editor/lib/desktop/bridge.test.ts b/editor/lib/desktop/bridge.test.ts index 72dfc626b..4a7ca11f7 100644 --- a/editor/lib/desktop/bridge.test.ts +++ b/editor/lib/desktop/bridge.test.ts @@ -45,7 +45,7 @@ describe("desktop bridge client contract", () => { it("uses producer-owned BYOK provider metadata for settings", () => { expect(secrets.byokProviderMetadata()).toBe(BYOK_PROVIDER_METADATA); - expect(secrets.byokProviders()).toEqual(["openrouter", "ai-gateway"]); + expect(secrets.byokProviders()).toEqual(["openrouter", "vercel"]); }); it("rejects empty keys before calling the bridge", async () => { diff --git a/packages/grida-ai-agent/src/__public-api__.test.ts b/packages/grida-ai-agent/src/__public-api__.test.ts index 8efe4a827..9bc1670b6 100644 --- a/packages/grida-ai-agent/src/__public-api__.test.ts +++ b/packages/grida-ai-agent/src/__public-api__.test.ts @@ -100,14 +100,14 @@ describe("@grida/agent public API", () => { }); it("exposes BYOK provider identity, wire vocab, tiers, and session-row types", () => { - expect(BYOK_PROVIDER_IDS).toEqual(["openrouter", "ai-gateway"]); + expect(BYOK_PROVIDER_IDS).toEqual(["openrouter", "vercel"]); expect(BYOK_PROVIDER_METADATA.map((provider) => provider.label)).toEqual([ "OpenRouter", - "AI Gateway", + "Vercel", ]); - const byok: ByokProviderId = "ai-gateway"; + const byok: ByokProviderId = "vercel"; const metadata: ByokProviderMetadata = BYOK_PROVIDER_METADATA[0]; - expect(byok).toBe("ai-gateway"); + expect(byok).toBe("vercel"); expect(metadata.id).toBe("openrouter"); // Tier constants. diff --git a/packages/grida-ai-agent/src/auth/file.ts b/packages/grida-ai-agent/src/auth/file.ts index ee54c5f4d..2cb851c96 100644 --- a/packages/grida-ai-agent/src/auth/file.ts +++ b/packages/grida-ai-agent/src/auth/file.ts @@ -11,8 +11,8 @@ * * ```json * { - * "openrouter": { "type": "api", "key": "sk-or-..." }, - * "ai-gateway": { "type": "api", "key": "..." } + * "openrouter": { "type": "api", "key": "sk-or-..." }, + * "vercel": { "type": "api", "key": "..." } * } * ``` * @@ -97,7 +97,7 @@ export class AuthStore { * Serializes `set` / `remove` so two concurrent mutations can't * lose-update each other. Both ops follow read-modify-write on a * shared file; without this chain a parallel set('openrouter', …) - * and set('ai-gateway', …) would both `readAll` the same starting + * and set('vercel', …) would both `readAll` the same starting * state, both `writeAll`, and the second `rename` wins — silently * dropping the first key. The `.catch(() => undefined)` on the * chain swallows rejection so a single failed write doesn't strand diff --git a/packages/grida-ai-agent/src/http/routes/secrets.ts b/packages/grida-ai-agent/src/http/routes/secrets.ts index 36c17cf43..9b23965d1 100644 --- a/packages/grida-ai-agent/src/http/routes/secrets.ts +++ b/packages/grida-ai-agent/src/http/routes/secrets.ts @@ -12,7 +12,7 @@ * * Allowed provider ids — a closed set: * - `openrouter` - * - `ai-gateway` + * - `vercel` * * Any other id is rejected with a 400 so a typo doesn't silently create a * never-used auth.json entry. diff --git a/packages/grida-ai-agent/src/protocol/provider-ids.ts b/packages/grida-ai-agent/src/protocol/provider-ids.ts index fad29db83..bfa3b89da 100644 --- a/packages/grida-ai-agent/src/protocol/provider-ids.ts +++ b/packages/grida-ai-agent/src/protocol/provider-ids.ts @@ -11,8 +11,8 @@ export const BYOK_PROVIDER_METADATA = [ label: "OpenRouter", }, { - id: "ai-gateway", - label: "AI Gateway", + id: "vercel", + label: "Vercel", }, ] as const; diff --git a/packages/grida-ai-agent/src/providers/byok.ts b/packages/grida-ai-agent/src/providers/byok.ts index 3a1e303db..1e60ce24d 100644 --- a/packages/grida-ai-agent/src/providers/byok.ts +++ b/packages/grida-ai-agent/src/providers/byok.ts @@ -32,7 +32,7 @@ export function makeOpenRouterFactory(apiKey: string): ModelFactory { return (tier, modelId) => provider(modelId ?? MODEL_BY_TIER[tier]); } -export function makeAiGatewayFactory(apiKey: string): ModelFactory { +export function makeVercelFactory(apiKey: string): ModelFactory { const provider = createGateway({ apiKey }); return (tier, modelId) => provider(modelId ?? MODEL_BY_TIER[tier]); } diff --git a/packages/grida-ai-agent/src/providers/index.test.ts b/packages/grida-ai-agent/src/providers/index.test.ts index 25ca73af9..a461db3ae 100644 --- a/packages/grida-ai-agent/src/providers/index.test.ts +++ b/packages/grida-ai-agent/src/providers/index.test.ts @@ -15,11 +15,11 @@ function deps(keys: Record = {}) { } describe("resolveProvider", () => { - it("prefers OpenRouter over AI Gateway when both BYOK keys exist", async () => { + it("prefers OpenRouter over Vercel when both BYOK keys exist", async () => { const provider = await resolveProvider( deps({ openrouter: " sk-or ", - "ai-gateway": "ai-gateway-key", + vercel: "vercel-key", }) ); @@ -28,12 +28,10 @@ describe("resolveProvider", () => { expect(provider.model_factory).toBeTypeOf("function"); }); - it("falls back to AI Gateway when OpenRouter is absent", async () => { - const provider = await resolveProvider( - deps({ "ai-gateway": "ai-gateway-key" }) - ); + it("falls back to Vercel when OpenRouter is absent", async () => { + const provider = await resolveProvider(deps({ vercel: "vercel-key" })); - expect(provider.provider_id).toBe("ai-gateway"); + expect(provider.provider_id).toBe("vercel"); expect(provider.kind).toBe("byok"); }); @@ -46,9 +44,9 @@ describe("resolveProvider", () => { it("throws when an explicit BYOK provider has no key", async () => { await expect( resolveProvider(deps({ openrouter: "sk-or" }), { - explicit: "ai-gateway", + explicit: "vercel", }) - ).rejects.toMatchObject({ provider_id: "ai-gateway" }); + ).rejects.toMatchObject({ provider_id: "vercel" }); }); it("BYOK factory honors an explicit modelId over the tier model", async () => { diff --git a/packages/grida-ai-agent/src/providers/index.ts b/packages/grida-ai-agent/src/providers/index.ts index e8a2e669c..ade93d5fe 100644 --- a/packages/grida-ai-agent/src/providers/index.ts +++ b/packages/grida-ai-agent/src/providers/index.ts @@ -8,7 +8,7 @@ * the factory, so it's cheap on the hot path and easy to test. * * This is the providers layer, not a generic model-provider router. V1 is - * BYOK-only: OpenRouter takes precedence over AI Gateway, and a missing + * BYOK-only: OpenRouter takes precedence over Vercel, and a missing * key throws `ProviderUnavailableError`. */ @@ -20,7 +20,7 @@ import { BYOK_PROVIDER_METADATA, type ByokProviderId, } from "../protocol/provider-ids"; -import { makeAiGatewayFactory, makeOpenRouterFactory } from "./byok"; +import { makeOpenRouterFactory, makeVercelFactory } from "./byok"; /** Canonical tier->catalog-model map. One table, sourced from @grida/ai-models. */ export const MODEL_BY_TIER: Record = TIER_MODEL_IDS; @@ -98,11 +98,11 @@ function makeResolvedProvider( kind: "byok", model_factory: makeOpenRouterFactory(key.trim()), }; - case "ai-gateway": + case "vercel": return { provider_id: providerId, kind: "byok", - model_factory: makeAiGatewayFactory(key.trim()), + model_factory: makeVercelFactory(key.trim()), }; } const _exhaustive: never = providerId; diff --git a/packages/grida-ai-agent/src/sandbox/policy.ts b/packages/grida-ai-agent/src/sandbox/policy.ts index e60f9d215..b6acca503 100644 --- a/packages/grida-ai-agent/src/sandbox/policy.ts +++ b/packages/grida-ai-agent/src/sandbox/policy.ts @@ -40,7 +40,7 @@ export type AgentHostSandboxPolicy = { const BYOK_PROVIDER_NETWORK_HOSTS = { openrouter: ["openrouter.ai"], - "ai-gateway": ["ai-gateway.vercel.sh", "*.vercel-ai.com"], + vercel: ["ai-gateway.vercel.sh", "*.vercel-ai.com"], } as const satisfies Record; const ALWAYS_ALLOWED_HOSTS: readonly string[] = Object.values( diff --git a/packages/grida-ai-agent/src/secrets.ts b/packages/grida-ai-agent/src/secrets.ts index 6386c02fe..af8cf0d16 100644 --- a/packages/grida-ai-agent/src/secrets.ts +++ b/packages/grida-ai-agent/src/secrets.ts @@ -2,7 +2,7 @@ * GRIDA-SEC-004 — BYOK secret store. * * Thin façade over `AuthStore` for the `ApiKeyEntry`-shaped records - * (`openrouter`, `ai-gateway`). The agent host's `/secrets/*` HTTP routes + * (`openrouter`, `vercel`). The agent host's `/secrets/*` HTTP routes * call this; the BYOK provider path in `runtime.ts` * calls this internally to pull the key when constructing the * @ai-sdk client. diff --git a/packages/grida-ai-models/__tests__/models.test.ts b/packages/grida-ai-models/__tests__/models.test.ts index ceb24ab6f..73eaafbbd 100644 --- a/packages/grida-ai-models/__tests__/models.test.ts +++ b/packages/grida-ai-models/__tests__/models.test.ts @@ -1,7 +1,7 @@ import models, { TIER_MODEL_IDS } from ".."; describe("models.image.findImageModelCard", () => { - it("resolves a full gateway id", () => { + it("resolves a full vercel id", () => { const card = models.image.findImageModelCard("bfl/flux-pro-1.1"); expect(card?.id).toBe("bfl/flux-pro-1.1"); expect(card?.label).toBe("Flux Pro 1.1"); @@ -9,7 +9,7 @@ describe("models.image.findImageModelCard", () => { it("resolves the deprecated ProviderModel wrapper", () => { const card = models.image.findImageModelCard({ - provider: "gateway", + provider: "vercel", modelId: "bfl/flux-2-pro", }); expect(card?.id).toBe("bfl/flux-2-pro"); diff --git a/packages/grida-ai-models/src/models.ts b/packages/grida-ai-models/src/models.ts index 18139695a..70f7f7395 100644 --- a/packages/grida-ai-models/src/models.ts +++ b/packages/grida-ai-models/src/models.ts @@ -19,7 +19,7 @@ * modules — keeping the full `namespace models` declaration in a * single source file is the workaround. * - * `provider: "gateway"` and `provider: "replicate"` on the cards are + * `provider: "vercel"` and `provider: "replicate"` on the cards are * data labels only — see the README for the full contract. * * @module @@ -31,11 +31,11 @@ export namespace models { // ── Shared discriminators ───────────────────────────────────────── /** - * Routing label for hosted-provider calls. `"gateway"` indicates - * the model is served via a hosted AI gateway (e.g. Vercel AI - * Gateway); the label is data, not an SDK directive. + * Routing label for hosted-provider calls. `"vercel"` indicates + * the model is served via the Vercel AI Gateway; the label is + * data, not an SDK directive. */ - export type Provider = "gateway"; + export type Provider = "vercel"; /** * Model vendor (the organization that produced the weights). @@ -226,7 +226,7 @@ export namespace models { * a `provider` field on its own. */ export type ProviderModel = { - provider: "gateway"; + provider: "vercel"; modelId: ImageModelId; }; @@ -461,7 +461,7 @@ export namespace models { short_description: "State-of-the-art image generation and editing with flexible resolutions", vendor: "openai", - provider: "gateway", + provider: "vercel", speed_label: "medium", speed_max: "1m", styles: null, @@ -514,7 +514,7 @@ export namespace models { short_description: "Previous-generation image model. Superseded by GPT Image 2.", vendor: "openai", - provider: "gateway", + provider: "vercel", speed_label: "medium", speed_max: "1m", styles: null, @@ -561,7 +561,7 @@ export namespace models { deprecated: false, short_description: "Cost-efficient image generation model", vendor: "openai", - provider: "gateway", + provider: "vercel", speed_label: "slow", speed_max: "1m", styles: null, @@ -613,7 +613,7 @@ export namespace models { short_description: "Fast, efficient multimodal model with native image generation", vendor: "google", - provider: "gateway", + provider: "vercel", speed_label: "fast", speed_max: "15s", styles: null, @@ -636,7 +636,7 @@ export namespace models { short_description: "High-quality multimodal model with native image generation", vendor: "google", - provider: "gateway", + provider: "vercel", speed_label: "medium", speed_max: "30s", styles: null, @@ -662,7 +662,7 @@ export namespace models { short_description: "Latest Flux model with best-in-class image quality and prompt adherence", vendor: "black-forest-labs", - provider: "gateway", + provider: "vercel", speed_label: "medium", speed_max: "30s", styles: null, @@ -683,7 +683,7 @@ export namespace models { short_description: "Highest quality Flux model for context-aware image generation and editing", vendor: "black-forest-labs", - provider: "gateway", + provider: "vercel", speed_label: "slow", speed_max: "30s", styles: null, @@ -703,7 +703,7 @@ export namespace models { deprecated: false, short_description: "Fast context-aware image generation and editing", vendor: "black-forest-labs", - provider: "gateway", + provider: "vercel", speed_label: "medium", speed_max: "20s", styles: null, @@ -724,7 +724,7 @@ export namespace models { short_description: "Faster, better FLUX Pro. Text-to-image model with excellent image quality and output diversity.", vendor: "black-forest-labs", - provider: "gateway", + provider: "vercel", speed_label: "slow", speed_max: "30s", styles: null,