From 9f8db119fcbd4999379129ac7734375ac23460fb Mon Sep 17 00:00:00 2001 From: Victor Navarro Date: Wed, 9 Sep 2026 19:45:38 +0200 Subject: [PATCH] feat(console): route migrated Go inference (#48123) --- .../console/app/src/lib/inference-proxy.ts | 35 ++++++++++++++++--- .../app/src/routes/zen/go/v1/models.ts | 5 ++- .../console/app/src/routes/zen/go/v1/usage.ts | 3 ++ .../app/src/routes/zen/util/handler.ts | 16 ++++----- .../console/app/src/routes/zen/v1/models.ts | 32 ++--------------- 5 files changed, 47 insertions(+), 44 deletions(-) diff --git a/packages/console/app/src/lib/inference-proxy.ts b/packages/console/app/src/lib/inference-proxy.ts index 7de73a961ac8..b572c361b655 100644 --- a/packages/console/app/src/lib/inference-proxy.ts +++ b/packages/console/app/src/lib/inference-proxy.ts @@ -8,11 +8,17 @@ const paths: Record = { "POST /zen/v1/chat/completions": "/openai/v1/chat/completions", "POST /zen/v1/responses": "/openai/v1/responses", "POST /zen/v1/messages": "/anthropic/v1/messages", + "POST /zen/go/v1/chat/completions": "/go/openai/v1/chat/completions", + "POST /zen/go/v1/responses": "/go/openai/v1/responses", + "POST /zen/go/v1/messages": "/go/anthropic/v1/messages", + "GET /zen/v1/models": "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/v1/models", + "GET /zen/go/v1/models": "/go/v1/models", + "GET /zen/go/v1/usage": "/go/v1/usage", } export async function proxyInference( request: Request, - generation: { + generation?: { provider?: "openai" | "anthropic" | "google" /** The provider's native model ID, not the public Zen alias. */ model?: string @@ -28,7 +34,8 @@ export async function proxyInference( : undefined) if (!path) return undefined - const key = path.startsWith("/anthropic/") + const go = url.pathname.startsWith("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/zen/go/") + const key = url.pathname.endsWith("/messages") ? request.headers.get("x-api-key") : path.startsWith("/google/") ? request.headers.get("x-goog-api-key") @@ -47,7 +54,7 @@ export async function proxyInference( .innerJoin(WorkspaceTable, eq(WorkspaceTable.id, KeyTable.workspaceID)) .leftJoin( ProviderTable, - generation.provider + !go && generation?.provider ? and( eq(ProviderTable.workspaceID, KeyTable.workspaceID), eq(ProviderTable.provider, generation.provider), @@ -61,7 +68,7 @@ export async function proxyInference( .then((rows) => rows[0]), ) if (!workspace?.migratedAt) return undefined - const model = workspace.provider ? generation.model : undefined + const model = workspace.provider ? generation?.model : undefined if (workspace.provider && !model) throw new Error("Legacy BYOK model mapping is unavailable") const destination = new URL(Resource.ConsoleMigration.inferenceUrl) @@ -80,8 +87,19 @@ export async function proxyInference( // Model extraction has already read part of the body; forward its replay stream. const forwarded = new Request( destination, - new Request(request, { method: request.method, body: generation.body(model) }), + generation ? new Request(request, { method: request.method, body: generation.body(model) }) : request, ) + // Migrated requests use ordinary destination authentication and accounting. + for (const name of [ + "x-zen", + "x-zen-model", + "x-zen-ip", + "cf-access-client-id", + "cf-access-client-secret", + "host", + "content-length", + ]) + forwarded.headers.delete(name) forwarded.headers.set("authorization", `Bearer ${key}`) const ip = request.headers.get("cf-connecting-ip") if (ip) forwarded.headers.set("x-real-ip", ip) @@ -90,3 +108,10 @@ export async function proxyInference( return fetch(forwarded, { redirect: "manual" }) } + +export function inferenceUnavailable() { + return Response.json( + { error: { type: "api_error", message: "Inference routing is unavailable. Please retry later." } }, + { status: 503, headers: { "Cache-Control": "no-store" } }, + ) +} diff --git a/packages/console/app/src/routes/zen/go/v1/models.ts b/packages/console/app/src/routes/zen/go/v1/models.ts index 83ef74fb6148..dfc7b87ec34a 100644 --- a/packages/console/app/src/routes/zen/go/v1/models.ts +++ b/packages/console/app/src/routes/zen/go/v1/models.ts @@ -1,12 +1,15 @@ import type { APIEvent } from "@solidjs/start/server" import { ZenData } from "@opencode-ai/console-core/model.js" import { buildModelsResponse, buildOptionsResponse } from "../../util/modelsHandler" +import { inferenceUnavailable, proxyInference } from "~/lib/inference-proxy" export async function OPTIONS(_input: APIEvent) { return buildOptionsResponse() } -export async function GET(_input: APIEvent) { +export async function GET(input: APIEvent) { + const response = await proxyInference(input.request).catch(inferenceUnavailable) + if (response) return response const models = Object.keys(ZenData.list("lite").models) return buildModelsResponse(models) } diff --git a/packages/console/app/src/routes/zen/go/v1/usage.ts b/packages/console/app/src/routes/zen/go/v1/usage.ts index c16851dc78ff..a41373f1aae6 100644 --- a/packages/console/app/src/routes/zen/go/v1/usage.ts +++ b/packages/console/app/src/routes/zen/go/v1/usage.ts @@ -6,8 +6,11 @@ import { UserTable } from "@opencode-ai/console-core/schema/user.sql.js" import { WorkspaceTable } from "@opencode-ai/console-core/schema/workspace.sql.js" import { LiteData } from "@opencode-ai/console-core/lite.js" import { Subscription } from "@opencode-ai/console-core/subscription.js" +import { inferenceUnavailable, proxyInference } from "~/lib/inference-proxy" export async function GET(input: APIEvent) { + const response = await proxyInference(input.request).catch(inferenceUnavailable) + if (response) return response const apiKey = input.request.headers.get("authorization")?.match(/^Bearer (\S+)$/)?.[1] if (!apiKey) { diff --git a/packages/console/app/src/routes/zen/util/handler.ts b/packages/console/app/src/routes/zen/util/handler.ts index d5e2fd811ea2..ee223498cae9 100644 --- a/packages/console/app/src/routes/zen/util/handler.ts +++ b/packages/console/app/src/routes/zen/util/handler.ts @@ -50,7 +50,7 @@ import { countryFromRequest, isModelCountryRestricted } from "~/lib/request-coun import { isPeakPricing } from "./pricing" import { prepareRequestBody } from "./requestBody" import { requiresGoTrainingConsent } from "./trainingConsent" -import { proxyInference } from "~/lib/inference-proxy" +import { inferenceUnavailable, proxyInference } from "~/lib/inference-proxy" type ZenData = Awaited> type PreparedBody = Awaited> @@ -102,22 +102,22 @@ export async function handler( const rawZenApiKey = opts.parseApiKey(input.request.headers) const zenApiKey = rawZenApiKey === "public" ? undefined : rawZenApiKey const zenData = ZenData.list(opts.modelList) - if (opts.modelList === "full" && model) { + if (model) { // Read routing metadata without running legacy model, auth, or balance checks. const configured = zenData.models[model] const entry = Array.isArray(configured) ? configured.find((entry) => entry.formatFilter === opts.format) : configured const response = await proxyInference(input.request, { - provider: entry?.byokProvider, - model: entry?.providers.find((provider) => provider.id === entry.byokProvider)?.model, + provider: opts.modelList === "full" ? entry?.byokProvider : undefined, + model: + opts.modelList === "full" + ? entry?.providers.find((provider) => provider.id === entry.byokProvider)?.model + : undefined, body: (providerModel) => requestBody?.stream(providerModel ?? model, false) ?? body, }).catch(() => { void (requestBody ? requestBody.cancel() : body.cancel()).catch(() => {}) - return Response.json( - { error: { type: "api_error", message: "Inference routing is unavailable. Please retry later." } }, - { status: 503, headers: { "Cache-Control": "no-store" } }, - ) + return inferenceUnavailable() }) if (response) return response } diff --git a/packages/console/app/src/routes/zen/v1/models.ts b/packages/console/app/src/routes/zen/v1/models.ts index 262a1bb349fe..c1505b899cf3 100644 --- a/packages/console/app/src/routes/zen/v1/models.ts +++ b/packages/console/app/src/routes/zen/v1/models.ts @@ -5,7 +5,7 @@ import { KeyTable } from "@opencode-ai/console-core/schema/key.sql.js" import { WorkspaceTable } from "@opencode-ai/console-core/schema/workspace.sql.js" import { ModelTable } from "@opencode-ai/console-core/schema/model.sql.js" import { buildOptionsResponse, buildModelsResponse } from "~/routes/zen/util/modelsHandler" -import { Resource } from "@opencode-ai/console-resource" +import { inferenceUnavailable, proxyInference } from "~/lib/inference-proxy" export async function OPTIONS(_input: APIEvent) { return buildOptionsResponse() @@ -14,12 +14,7 @@ export async function OPTIONS(_input: APIEvent) { export async function GET(input: APIEvent) { const apiKey = input.request.headers.get("authorization")?.split(" ")[1] if (apiKey && apiKey !== "public") { - const response = await proxyModels(input, apiKey).catch(() => - Response.json( - { error: { type: "api_error", message: "Inference routing is unavailable. Please retry later." } }, - { status: 503, headers: { "Cache-Control": "no-store" } }, - ), - ) + const response = await proxyInference(input.request).catch(inferenceUnavailable) if (response) return response } @@ -45,26 +40,3 @@ export async function GET(input: APIEvent) { return buildModelsResponse(models) } - -async function proxyModels(input: APIEvent, apiKey: string) { - // No legacy revocation or model-policy checks before destination authentication. - const workspace = await Database.use((tx) => - tx - .select({ migratedAt: WorkspaceTable.migrated_at }) - .from(KeyTable) - .innerJoin(WorkspaceTable, eq(WorkspaceTable.id, KeyTable.workspaceID)) - .where(eq(KeyTable.key, apiKey)) - .limit(1) - .then((rows) => rows[0]), - ) - if (!workspace?.migratedAt) return undefined - - const destination = new URL(Resource.ConsoleMigration.inferenceUrl) - destination.pathname = `${destination.pathname.replace(/\/$/, "")}/v1/models` - destination.search = new URL(input.request.url).search - destination.hash = "" - const headers = new Headers({ authorization: `Bearer ${apiKey}` }) - const ip = input.request.headers.get("cf-connecting-ip") - if (ip) headers.set("x-real-ip", ip) - return fetch(destination, { headers, signal: input.request.signal, redirect: "manual" }) -}