feat(console): route migrated Go inference (#48123)

This commit is contained in:
Victor Navarro 2026-09-09 19:45:38 +02:00 committed by GitHub
parent f69beceaff
commit 9f8db119fc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 47 additions and 44 deletions

View file

@ -8,11 +8,17 @@ const paths: Record<string, string | undefined> = {
"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": "/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("/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" } },
)
}

View file

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

View file

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

View file

@ -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<ReturnType<typeof ZenData.list>>
type PreparedBody = Awaited<ReturnType<typeof prepareRequestBody>>
@ -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
}

View file

@ -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" })
}