Something went wrong. Try again.
Something went wrong. Try again.
39 kB · 888 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889import assert from "node:assert/strict"import fs from "node:fs"import path from "node:path"import test from "node:test"import { accountCredentialPath, configureKeyRouting } from "./accounts"import { loadApiKeys, probeApiKeysFile } from "./auth"import type { BridgeConfig } from "./config"import { getSession } from "./oauth-login"import { createServer, discoverModels } from "./server"import { closeTokenUsageDatabases } from "./token-usage"
// Failure modes documented before implementation.//// A pasted OpenCode key is an API key, not an Anthropic OAuth grant: treating it// as one routes the request through the subscription transport and reads the// credential with the wrong reader. The catalog ids are bare (no provider// prefix), so the wire cannot be inferred from the name: OpenCode Go serves most// of its models over chat completions and three of them (minimax-m3, qwen3.7-*)// over Anthropic Messages, and nothing may silently fall back to chat// completions for a model that needs Messages.//// This test drives the real admin login routes and the real inference routes on// a loopback Fastify server, captures upstream requests through a stubbed global// fetch (pi-ai's SDK clients resolve fetch at request time), and writes a// credential-free artifact.const artifactPath = "/tmp/llm-bridge-opencode-artifacts/e2e.json"const adminKey = "11111111-2222-4333-8444-555555555555"const clientKey = "99999999-8888-4777-8666-555555555555"const goKey = "sk-opencode-go-e2e-fixture"const envKey = "sk-opencode-env-e2e-fixture"const authUrl = "https://opencode.ai/auth"const goBase = "https://opencode.ai/zen/go/v1"const goAnthropicBase = "https://opencode.ai/zen/go"const messagesModel = "minimax-m3"const completionsModel = "deepseek-v4-flash"const responsesModel = "gpt-5.6-luna"const goModelListUrl = "https://opencode.ai/zen/go/v1/models"/** * What the public Go model list reports in this test. Deliberately shorter than * the committed catalog, so a listed id proves discovery used the live list. */const liveGoIds = [ completionsModel, messagesModel, responsesModel, "glm-5.2", "grok-4.6", "claude-haiku-5-5", "gpt-6-luna", "kimi-k2.6", "kimi-k3",]
type CapturedCall = { url: string model?: string stream?: boolean authorization?: string xApiKey?: string session?: string body?: Record<string, unknown>}
function upstreamShape(raw: unknown): { model?: string; stream?: boolean; body?: Record<string, unknown> } { if (typeof raw !== "string") return {} let parsed: unknown try { parsed = JSON.parse(raw) } catch { return {} } if (!parsed || typeof parsed !== "object") return {} const model = "model" in parsed ? parsed.model : undefined const stream = "stream" in parsed ? parsed.stream : undefined return { ...(typeof model === "string" ? { model } : {}), ...(typeof stream === "boolean" ? { stream } : {}), body: parsed as Record<string, unknown>, }}
function openAiSse(model: string): string { const chunks: Array<Record<string, unknown>> = [ { id: "chatcmpl-opencode-e2e", object: "chat.completion.chunk", created: 0, model, choices: [{ index: 0, delta: { role: "assistant", content: "hello from opencode" }, finish_reason: null }], }, { id: "chatcmpl-opencode-e2e", object: "chat.completion.chunk", created: 0, model, choices: [{ index: 0, delta: {}, finish_reason: "stop" }], usage: { prompt_tokens: 5, completion_tokens: 4, total_tokens: 9 }, }, ] return `${chunks.map((chunk) => `data: ${JSON.stringify(chunk)}\n\n`).join("")}data: [DONE]\n\n`}
/** * A minimal OpenAI Responses stream: pi-ai needs an item slot, a text delta and * a terminal event, and rejects a stream that ends without one. */function responsesSse(model: string): string { const events: Array<Record<string, unknown>> = [ { type: "response.created", response: { id: "resp_opencode_e2e", model, status: "in_progress" } }, { type: "response.output_item.added", output_index: 0, item: { type: "message", id: "msg_opencode_e2e", role: "assistant", content: [] }, }, { type: "response.output_text.delta", output_index: 0, delta: "hello from opencode luna" }, { type: "response.completed", response: { id: "resp_opencode_e2e", model, status: "completed", usage: { input_tokens: 5, output_tokens: 4, total_tokens: 9 }, }, }, ] return events.map((event) => `data: ${JSON.stringify(event)}\n\n`).join("")}
function anthropicSse(): string { const frames: Array<Record<string, unknown>> = [ { type: "message_start", message: { id: "msg_opencode_e2e", type: "message", role: "assistant", model: "claude-opus-4-5", content: [], stop_reason: null, stop_sequence: null, usage: { input_tokens: 5, output_tokens: 0 }, }, }, { type: "content_block_start", index: 0, content_block: { type: "text", text: "" } }, { type: "content_block_delta", index: 0, delta: { type: "text_delta", text: "hello from opencode zen" } }, { type: "content_block_stop", index: 0 }, { type: "message_delta", delta: { stop_reason: "end_turn", stop_sequence: null }, usage: { output_tokens: 5 } }, { type: "message_stop" }, ] return frames.map((frame) => `event: ${frame.type}\ndata: ${JSON.stringify(frame)}\n\n`).join("")}
/** * Serves the three OpenCode wires: the public model list, chat completions, * Anthropic messages and OpenAI Responses. Records every upstream call. */function stubUpstream(calls: CapturedCall[]): typeof fetch { return async (input, init) => { const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url const headers = new Headers(init?.headers ?? (input instanceof Request ? input.headers : undefined)) const raw: unknown = init?.body ?? (input instanceof Request ? await input.clone().text() : undefined) const shape = upstreamShape(raw) calls.push({ url, ...shape, authorization: headers.get("authorization") ?? undefined, xApiKey: headers.get("x-api-key") ?? undefined, session: headers.get("x-opencode-session") ?? undefined, }) if (url === goModelListUrl) { return new Response(JSON.stringify({ object: "list", data: liveGoIds.map((id) => ({ id, object: "model" })) }), { headers: { "content-type": "application/json" }, }) } if (url.includes("/chat/completions")) { return new Response(openAiSse(shape.model ?? "unknown"), { headers: { "content-type": "text/event-stream" }, }) } if (url.includes("/responses")) { return new Response(responsesSse(shape.model ?? "unknown"), { headers: { "content-type": "text/event-stream" }, }) } if (url.includes("/messages")) { return new Response(anthropicSse(), { headers: { "content-type": "text/event-stream" } }) } return new Response(JSON.stringify({ error: { message: `unexpected upstream call: ${url}` } }), { status: 404, headers: { "content-type": "application/json" }, }) }}
/** Every upstream call the stub has served, reset per test. */const upstreamCalls: CapturedCall[] = []const upstreamStub = stubUpstream(upstreamCalls)
/** The account id from a JSON response, or a failed expectation. */function idOf(value: unknown): string { if (value && typeof value === "object" && "id" in value && typeof value.id === "string") return value.id assert.fail("expected a response object carrying a string id")}
type ListedModel = { id: string owned_by?: string supported_reasoning_levels?: string[] default_reasoning_level?: string}
/** The client-facing catalog as `GET /v1/models` reports it. */function modelList(body: unknown): ListedModel[] { if (!body || typeof body !== "object" || !("data" in body) || !Array.isArray(body.data)) return [] return body.data.flatMap((entry) => { if (!entry || typeof entry !== "object") return [] const id = "id" in entry ? entry.id : undefined const ownedBy = "owned_by" in entry ? entry.owned_by : undefined const rawLevels = "supported_reasoning_levels" in entry ? entry.supported_reasoning_levels : undefined const levels = Array.isArray(rawLevels) ? rawLevels.flatMap((level) => { if (!level || typeof level !== "object" || !("effort" in level) || typeof level.effort !== "string") return [] return [level.effort] }) : undefined const defaultLevel = "default_reasoning_level" in entry ? entry.default_reasoning_level : undefined return typeof id === "string" ? [ { id, ...(typeof ownedBy === "string" ? { owned_by: ownedBy } : {}), ...(levels && levels.length > 0 ? { supported_reasoning_levels: levels } : {}), ...(typeof defaultLevel === "string" ? { default_reasoning_level: defaultLevel } : {}), }, ] : [] })}
type LoginProviderStatus = { id: string; name?: string; kind?: string; connected?: boolean }
/** One `GET /admin/api/providers` entry. */function providerStatusEntry(value: unknown): LoginProviderStatus | null { if (!value || typeof value !== "object") return null const id = "id" in value ? value.id : undefined if (typeof id !== "string") return null const name = "name" in value ? value.name : undefined const kind = "kind" in value ? value.kind : undefined const connected = "connected" in value ? value.connected : undefined return { id, ...(typeof name === "string" ? { name } : {}), ...(typeof kind === "string" ? { kind } : {}), ...(typeof connected === "boolean" ? { connected } : {}), }}
/** JSON.parse hands back `unknown`; the credential file is a provider-id map. */function credentialEntry(file: string, provider: string): unknown { const parsed: unknown = JSON.parse(fs.readFileSync(file, "utf8")) if (!parsed || typeof parsed !== "object") return undefined return provider in parsed ? (parsed as Record<string, unknown>)[provider] : undefined}
test("OpenCode accounts log in with a pasted API key and serve their catalog on the wire each model needs", async (t) => { const directory = fs.mkdtempSync("/tmp/bridge-opencode-e2e-") const previousStateDir = process.env.BRIDGE_STATE_DIR const previousFetch = globalThis.fetch const previousKeys = { OPENCODE_API_KEY: process.env.OPENCODE_API_KEY, ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY, OPENAI_API_KEY: process.env.OPENAI_API_KEY, } process.env.BRIDGE_STATE_DIR = directory // Registry providers register when an env key resolves; keep this catalog to // the two OpenCode accounts under test. delete process.env.OPENCODE_API_KEY delete process.env.ANTHROPIC_API_KEY delete process.env.OPENAI_API_KEY
const keysPath = path.join(directory, "keys.json") fs.writeFileSync( keysPath, JSON.stringify([ { key: adminKey, allowedModels: ["*"], caps: ["admin"] }, { key: clientKey, allowedModels: ["*"], caps: [] }, ]), )
const calls = upstreamCalls calls.length = 0 globalThis.fetch = upstreamStub
const config: BridgeConfig = { host: "127.0.0.1", port: 0, providers: {}, models: {}, keysFile: probeApiKeysFile(keysPath), } const app = createServer(config, loadApiKeys(keysPath), { fetchImpl: globalThis.fetch }) t.after(async () => { await app.close() closeTokenUsageDatabases() globalThis.fetch = previousFetch for (const [name, value] of Object.entries(previousKeys)) { if (value === undefined) delete process.env[name] else process.env[name] = value } if (previousStateDir === undefined) delete process.env.BRIDGE_STATE_DIR else process.env.BRIDGE_STATE_DIR = previousStateDir fs.rmSync(directory, { recursive: true, force: true }) }) await app.listen({ host: "127.0.0.1", port: 0 }) const base = app.listeningOrigin
// Bridge calls use the untouched fetch; only upstream traffic is stubbed. const api = (url: string, method = "GET", body?: unknown, key = adminKey) => previousFetch(base + url, { method, headers: { authorization: `Bearer ${key}`, ...(body === undefined ? {} : { "content-type": "application/json" }), }, ...(body === undefined ? {} : { body: JSON.stringify(body) }), }) const json = async (result: Response, expected = 200): Promise<Record<string, unknown>> => { assert.equal(result.status, expected, await result.clone().text()) const body: unknown = await result.json() assert.ok(body && typeof body === "object", "expected a JSON object") return body as Record<string, unknown> }
const createAccount = async (provider: string): Promise<string> => { const body = await json(await api("/admin/api/accounts", "POST", { provider }), 201) return idOf(body.account) } const login = async (provider: string, accountId: string, key: string): Promise<void> => { const session = await json(await api(`/admin/api/login/${provider}`, "POST", { accountId })) assert.equal(session.kind, "paste", `${provider} login must be a paste flow`) assert.equal(session.authUrl, authUrl) assert.match(String(session.authInstructions), /API key/) const sessionId = idOf(session) assert.equal( (await json(await api(`/admin/api/session/${sessionId}/code`, "POST", { code: key }))).status, "polling", ) // The credential write and the model refresh run behind the session; await // the real completion signal instead of polling on a wall-clock timer. await getSession(sessionId)?.settled const settled = await json(await api(`/admin/api/session/${sessionId}`)) assert.equal(settled.status, "complete", String(settled.error ?? "")) }
const goAccount = await createAccount("opencode-go") await login("opencode-go", goAccount, goKey)
// The pasted key lands in the account credential file in pi's api_key shape. const authPath = accountCredentialPath(directory, goAccount, "opencode-go") assert.ok(authPath, "opencode-go credential path must resolve") assert.deepEqual(credentialEntry(authPath, "opencode-go"), { type: "api_key", key: goKey })
const statuses = await json(await api("/admin/api/providers")) const providerList = Array.isArray(statuses.providers) ? statuses.providers.map(providerStatusEntry).filter((entry) => entry !== null) : [] const status = providerList.find((entry) => entry.id === "opencode-go") assert.ok(status, "opencode-go must be reported by /admin/api/providers") assert.equal(status.connected, true) assert.equal(status.name, "OpenCode Go") assert.equal(status.kind, "paste")
// Inference is scoped by the client key's routing pool, like every other // account-backed provider. configureKeyRouting(directory, clientKey, { usageScheme: "fill-first", accounts: { "opencode-go": [goAccount] }, preferredAccounts: {}, })
const listed = modelList(await json(await api("/v1/models", "GET", undefined, clientKey))) const modelIds = listed.map((model) => model.id) assert.ok(modelIds.includes(completionsModel), "OpenCode Go models must be listed under their bare id") assert.ok(modelIds.includes(messagesModel), "OpenCode Go's Anthropic-wire models must be listed too") assert.ok(modelIds.includes(responsesModel), "OpenCode Go's Responses-wire models must be listed too") assert.ok(!modelIds.some((id) => id.startsWith("opencode")), "OpenCode ids must not be namespaced") assert.equal(listed.find((model) => model.id === completionsModel)?.owned_by, "opencode-go") assert.equal(listed.find((model) => model.id === messagesModel)?.owned_by, "opencode-go") // The live list, not the committed catalog, decides what is registered: ids // the list omits must not appear even though the catalog describes them. const goIds = listed.filter((model) => model.owned_by === "opencode-go").map((model) => model.id) assert.deepEqual([...goIds].sort(), [...liveGoIds].sort())
// The advertised levels are the ones the runtime model accepts, derived from // the same catalog the transport uses. pi-ai's registry entry wins for ids it // ships, so deepseek-v4-flash is [off,high,max] even though the committed // table maps "low". No model has an invented default. const expectedLevels: Record<string, string[]> = { [completionsModel]: ["off", "high", "max"], [messagesModel]: ["off", "minimal", "low", "medium", "high"], [responsesModel]: ["low", "medium", "high", "xhigh", "max"], "glm-5.2": ["high", "max"], "grok-4.6": ["low", "medium", "high", "xhigh"], "claude-haiku-5-5": ["off", "minimal", "low", "medium", "high", "xhigh", "max"], "gpt-6-luna": ["off", "low", "medium", "high", "xhigh", "max"], "kimi-k2.6": ["off", "high"], "kimi-k3": ["max"], } const adminBody = await json(await api("/admin/api/models")) const adminModels: Array<{ id?: string supportedReasoningEfforts?: string[] defaultReasoningEffort?: string }> = Array.isArray(adminBody.models) ? adminBody.models.filter((entry): entry is { id?: string } => !!entry && typeof entry === "object") : [] for (const [id, levels] of Object.entries(expectedLevels)) { const listedModel = listed.find((model) => model.id === id) assert.deepEqual(listedModel?.supported_reasoning_levels, levels, `${id} advertised levels`) assert.equal(listedModel?.default_reasoning_level, undefined, `${id} must not advertise a fabricated default`) const adminModel = adminModels.find((model) => model.id === id) assert.deepEqual(adminModel?.supportedReasoningEfforts, levels, `${id} admin levels must match /v1/models`) assert.equal(adminModel?.defaultReasoningEffort, undefined, `${id} admin must not invent a default`) }
// Chat completions: Authorization bearer and the Go wire base URL. calls.length = 0 const chat = await previousFetch(base + "/v1/chat/completions", { method: "POST", headers: { authorization: `Bearer ${clientKey}`, "content-type": "application/json" }, body: JSON.stringify({ model: completionsModel, stream: true, messages: [{ role: "user", content: "hi" }] }), }) assert.equal(chat.status, 200, await chat.clone().text()) assert.match(await chat.text(), /hello from opencode/) const goCall = calls.at(-1) assert.ok(goCall, "the Go upstream must receive the chat completion") assert.deepEqual( { url: goCall.url, model: goCall.model, authorization: goCall.authorization }, { url: `${goBase}/chat/completions`, model: completionsModel, authorization: `Bearer ${goKey}` }, ) // Go rejects any completion without a routing session, so every wire carries one. assert.ok(goCall.session, "the chat wire must send x-opencode-session") // No effort selection: the pre-existing DeepSeek payload is untouched. assert.equal(goCall.body?.reasoning_effort, undefined, "an absent effort must not add reasoning_effort") assert.deepEqual(goCall.body?.thinking, { type: "disabled" }, "DeepSeek keeps its existing disabled-thinking payload")
// The session is per conversation: the same opening content repeats it, a new // conversation gets a new one. const chatRequest = (content: string) => previousFetch(base + "/v1/chat/completions", { method: "POST", headers: { authorization: `Bearer ${clientKey}`, "content-type": "application/json" }, body: JSON.stringify({ model: completionsModel, stream: true, messages: [{ role: "user", content }] }), }) const repeat = await chatRequest("hi") assert.equal(repeat.status, 200, await repeat.clone().text()) await repeat.text() const other = await chatRequest("different opening") assert.equal(other.status, 200, await other.clone().text()) await other.text() const [repeatCall, otherCall] = calls.slice(-2) assert.equal(repeatCall?.session, goCall.session, "a repeated conversation must reuse its session id") assert.notEqual(otherCall?.session, goCall.session, "a new conversation must get a new session id")
// The same bare-id catalog must switch to Anthropic Messages and X-Api-Key for // the ids pi-ai declares on that wire, on both the Messages and Chat // Completions routes. calls.length = 0 const messages = await previousFetch(base + "/v1/messages", { method: "POST", headers: { authorization: `Bearer ${clientKey}`, "content-type": "application/json" }, body: JSON.stringify({ model: messagesModel, max_tokens: 64, stream: true, messages: [{ role: "user", content: "hi" }], }), }) assert.equal(messages.status, 200, await messages.clone().text()) assert.match(await messages.text(), /hello from opencode zen/) const messagesCall = calls.at(-1) assert.ok(messagesCall, "the Go upstream must receive the message request") assert.deepEqual( { url: messagesCall.url, model: messagesCall.model, xApiKey: messagesCall.xApiKey }, { url: `${goAnthropicBase}/v1/messages`, model: messagesModel, xApiKey: goKey }, ) assert.equal(messagesCall.authorization, undefined, "the Messages wire authenticates with x-api-key, not bearer") assert.ok(messagesCall.session, "the Messages wire must send x-opencode-session") // No effort selection: Minimax keeps receiving no thinking field at all. assert.equal(messagesCall.body?.thinking, undefined, "an absent effort must not add a thinking control") assert.equal(messagesCall.body?.output_config, undefined, "an absent effort must not add output_config")
calls.length = 0 const translated = await previousFetch(base + "/v1/chat/completions", { method: "POST", headers: { authorization: `Bearer ${clientKey}`, "content-type": "application/json" }, body: JSON.stringify({ model: messagesModel, stream: true, messages: [{ role: "user", content: "hi" }] }), }) assert.equal(translated.status, 200, await translated.clone().text()) assert.equal(calls.at(-1)?.url, `${goAnthropicBase}/v1/messages`) assert.ok(calls.at(-1)?.session, "the translated chat request must still carry a session")
// Responses wire: a bare Go id that pi-ai's Go catalog does not ship, served // over the Responses API on the same provider and key. calls.length = 0 const responses = await previousFetch(base + "/v1/responses", { method: "POST", headers: { authorization: `Bearer ${clientKey}`, "content-type": "application/json" }, body: JSON.stringify({ model: responsesModel, stream: true, input: "hi" }), }) assert.equal(responses.status, 200, await responses.clone().text()) const responsesWire = await responses.text() assert.match(responsesWire, /hello from opencode luna/) assert.match(responsesWire, /response\.completed/) const responsesCall = calls.at(-1) assert.ok(responsesCall, "the Go upstream must receive the Responses request") assert.deepEqual( { url: responsesCall.url, model: responsesCall.model, authorization: responsesCall.authorization }, { url: `${goBase}/responses`, model: responsesModel, authorization: `Bearer ${goKey}` }, ) assert.ok(responsesCall.session, "the Responses wire must send x-opencode-session")
// The same Responses-wire id reached through the chat route keeps its wire. calls.length = 0 const responsesViaChat = await previousFetch(base + "/v1/chat/completions", { method: "POST", headers: { authorization: `Bearer ${clientKey}`, "content-type": "application/json" }, body: JSON.stringify({ model: responsesModel, stream: true, messages: [{ role: "user", content: "hi" }] }), }) assert.equal(responsesViaChat.status, 200, await responsesViaChat.clone().text()) assert.match(await responsesViaChat.text(), /hello from opencode/) assert.deepEqual( { url: calls.at(-1)?.url, model: calls.at(-1)?.model }, { url: `${goBase}/responses`, model: responsesModel }, ) const responsesViaChatCall = calls.at(-1)
// And through the Anthropic Messages route: ingest shape must not change the // upstream wire, which is still Responses for this id. calls.length = 0 const responsesViaMessages = await previousFetch(base + "/v1/messages", { method: "POST", headers: { authorization: `Bearer ${clientKey}`, "content-type": "application/json" }, body: JSON.stringify({ model: responsesModel, max_tokens: 64, stream: true, messages: [{ role: "user", content: "hi" }], }), }) assert.equal(responsesViaMessages.status, 200, await responsesViaMessages.clone().text()) assert.match(await responsesViaMessages.text(), /hello from opencode luna/) const responsesViaMessagesCall = calls.at(-1) assert.deepEqual( { url: responsesViaMessagesCall?.url, model: responsesViaMessagesCall?.model, authorization: responsesViaMessagesCall?.authorization, }, { url: `${goBase}/responses`, model: responsesModel, authorization: `Bearer ${goKey}` }, ) assert.ok(responsesViaMessagesCall?.session, "the Messages ingest must carry a session to a Responses model")
// ── Thinking effort forwarding ──────────────────────────────────────────── // Every case below records the real upstream body: the effort a client asks // for must arrive on the wire the model actually uses, unsupported levels // must not be silently enabled, and an explicit disable must win over any // effort field in the same request. const callBridge = (url: string, body: Record<string, unknown>, method = "POST") => previousFetch(base + url, { method, headers: { authorization: `Bearer ${clientKey}`, "content-type": "application/json" }, body: JSON.stringify(body), }) const upstreamBody = (call: CapturedCall | undefined): Record<string, unknown> => call?.body ?? {} const upstreamThinking = (call: CapturedCall | undefined): Record<string, unknown> | undefined => { const thinking = upstreamBody(call).thinking return thinking && typeof thinking === "object" ? (thinking as Record<string, unknown>) : undefined } const upstreamReasoningEffort = (reasoning: unknown): unknown => reasoning && typeof reasoning === "object" && "effort" in reasoning ? reasoning.effort : undefined
// OpenAI chat wire: a mapped level is forwarded as reasoning_effort, and a // non-streaming client request still produces the same upstream controls. calls.length = 0 const chatHigh = await callBridge("/v1/chat/completions", { model: completionsModel, reasoning_effort: "high", messages: [{ role: "user", content: "hi" }], }) assert.equal(chatHigh.status, 200, await chatHigh.clone().text()) await chatHigh.json() const chatHighCall = calls.at(-1) assert.equal(chatHighCall?.body?.reasoning_effort, "high") assert.equal(upstreamThinking(chatHighCall)?.type, "enabled")
// The catalog claims deepseek-v4-flash supports "low", but the runtime model // (pi-ai's registry entry) does not. It must be rejected before any upstream // call rather than silently clamped to another level. calls.length = 0 const chatLow = await callBridge("/v1/chat/completions", { model: completionsModel, reasoning_effort: "low", stream: true, messages: [{ role: "user", content: "hi" }], }) assert.equal(chatLow.status, 400, await chatLow.clone().text()) const chatLowBody = (await chatLow.json()) as { error?: { type?: string; message?: string } } assert.equal(chatLowBody.error?.type, "invalid_request_error") assert.match(String(chatLowBody.error?.message), /high/) assert.equal(calls.length, 0, "an unsupported effort must not reach upstream")
// Explicit off is a supported level when the map leaves it absent: DeepSeek // receives thinking disabled, exactly like the absent case, with no effort. calls.length = 0 const chatOff = await callBridge("/v1/chat/completions", { model: completionsModel, reasoning_effort: "off", stream: true, messages: [{ role: "user", content: "hi" }], }) assert.equal(chatOff.status, 200, await chatOff.clone().text()) await chatOff.text() const chatOffCall = calls.at(-1) assert.equal(chatOffCall?.body?.reasoning_effort, undefined) assert.deepEqual(upstreamThinking(chatOffCall), { type: "disabled" })
// Anthropic wire 1: output_config.effort is forwarded as pi-ai's budget // thinking controls, and temperature is dropped while thinking is on. calls.length = 0 const messagesEffort = await callBridge("/v1/messages", { model: messagesModel, max_tokens: 64, temperature: 0.7, stream: true, output_config: { effort: "high" }, messages: [{ role: "user", content: "hi" }], }) assert.equal(messagesEffort.status, 200, await messagesEffort.clone().text()) await messagesEffort.text() const messagesEffortCall = calls.at(-1) const budgetThinking = upstreamThinking(messagesEffortCall) assert.equal(budgetThinking?.type, "enabled") assert.ok(typeof budgetThinking?.budget_tokens === "number" && budgetThinking.budget_tokens > 0) assert.equal(messagesEffortCall?.body?.temperature, undefined, "thinking requests must not send temperature")
// Anthropic wire 2: the existing thinking.budget_tokens convention maps // through the same thresholds (>=8192 is high), producing the same budget as // the equivalent output_config.effort. calls.length = 0 const messagesBudget = await callBridge("/v1/messages", { model: messagesModel, max_tokens: 64, stream: true, thinking: { type: "enabled", budget_tokens: 8192 }, messages: [{ role: "user", content: "hi" }], }) assert.equal(messagesBudget.status, 200, await messagesBudget.clone().text()) await messagesBudget.text() const messagesBudgetCall = calls.at(-1) assert.deepEqual(upstreamThinking(messagesBudgetCall), budgetThinking)
// Explicit thinking.disabled must win over output_config.effort instead of // accidentally enabling thinking. calls.length = 0 const messagesDisabled = await callBridge("/v1/messages", { model: messagesModel, max_tokens: 64, stream: true, thinking: { type: "disabled" }, output_config: { effort: "high" }, messages: [{ role: "user", content: "hi" }], }) assert.equal(messagesDisabled.status, 200, await messagesDisabled.clone().text()) await messagesDisabled.text() const messagesDisabledCall = calls.at(-1) assert.deepEqual(upstreamThinking(messagesDisabledCall), { type: "disabled" }) assert.equal(messagesDisabledCall?.body?.output_config, undefined)
// Adaptive Anthropic model: the mapped xhigh level reaches output_config. calls.length = 0 const haikuEffort = await callBridge("/v1/messages", { model: "claude-haiku-5-5", max_tokens: 64, stream: true, output_config: { effort: "xhigh" }, messages: [{ role: "user", content: "hi" }], }) assert.equal(haikuEffort.status, 200, await haikuEffort.clone().text()) await haikuEffort.text() const haikuEffortCall = calls.at(-1) assert.equal(upstreamThinking(haikuEffortCall)?.type, "adaptive") assert.deepEqual(upstreamBody(haikuEffortCall).output_config, { effort: "xhigh" })
// Responses wire: reasoning.effort is forwarded, including mapped xhigh. const responsesEffort = async (effort: string) => { calls.length = 0 const result = await callBridge("/v1/responses", { model: responsesModel, stream: true, input: "hi", reasoning: { effort }, }) return { result, call: calls.at(-1) } } const responsesMedium = await responsesEffort("medium") assert.equal(responsesMedium.result.status, 200, await responsesMedium.result.clone().text()) await responsesMedium.result.text() const mediumReasoning = upstreamBody(responsesMedium.call).reasoning assert.equal(upstreamReasoningEffort(mediumReasoning), "medium") const responsesXhigh = await responsesEffort("xhigh") assert.equal(responsesXhigh.result.status, 200, await responsesXhigh.result.clone().text()) await responsesXhigh.result.text() const xhighReasoning = upstreamBody(responsesXhigh.call).reasoning assert.equal(upstreamReasoningEffort(xhighReasoning), "xhigh")
// off/null on this model is unsupported, so it must fail closed. calls.length = 0 const responsesOff = await callBridge("/v1/responses", { model: responsesModel, stream: true, input: "hi", reasoning: { effort: "off" }, }) assert.equal(responsesOff.status, 400, await responsesOff.clone().text()) assert.equal(calls.length, 0, "off on an off:null model must not reach upstream")
// Unknown vocabulary is rejected, not passed through. calls.length = 0 const responsesUnknown = await callBridge("/v1/responses", { model: responsesModel, stream: true, input: "hi", reasoning: { effort: "turbo" }, }) assert.equal(responsesUnknown.status, 400, await responsesUnknown.clone().text()) assert.equal(calls.length, 0)
// A model whose map turns off into "none" forwards that explicit value. calls.length = 0 const lunaOff = await callBridge("/v1/responses", { model: "gpt-6-luna", stream: true, input: "hi", reasoning: { effort: "off" }, }) assert.equal(lunaOff.status, 200, await lunaOff.clone().text()) await lunaOff.text() const lunaOffCall = calls.at(-1) const lunaOffReasoning = upstreamBody(lunaOffCall).reasoning assert.equal(upstreamReasoningEffort(lunaOffReasoning), "none")
// Malformed types cannot be treated as an absent selection. calls.length = 0 const malformed = await callBridge("/v1/chat/completions", { model: completionsModel, reasoning_effort: 5, stream: true, messages: [{ role: "user", content: "hi" }], }) assert.equal(malformed.status, 400, await malformed.clone().text()) assert.equal(calls.length, 0)
// Signing out clears the credential it was read from. const loggedOut = await json(await api("/admin/api/logout/opencode-go", "POST", { accountId: goAccount })) assert.equal(loggedOut.connected, false) const goAuthPath = accountCredentialPath(directory, goAccount, "opencode-go") assert.ok(goAuthPath) assert.equal(credentialEntry(goAuthPath, "opencode-go"), undefined)
fs.mkdirSync(path.dirname(artifactPath), { recursive: true }) fs.writeFileSync( artifactPath, `${JSON.stringify( { authUrl, loginKind: "paste", bases: { "opencode-go": goBase, "opencode-go (Anthropic wire)": goAnthropicBase }, namespaced: false, authHeaders: { "chat completions": "authorization: Bearer", "anthropic messages": "x-api-key", responses: "authorization: Bearer", }, sessionHeader: "x-opencode-session", wires: { [completionsModel]: "chat/completions", [messagesModel]: "messages", [responsesModel]: "responses" }, liveList: goModelListUrl, registeredIds: liveGoIds, reasoningLevels: Object.fromEntries( Object.keys(expectedLevels).map((id) => { const metadata = listed.find((model) => model.id === id) const adminModel = adminModels.find((model) => model.id === id) return [ id, { "v1/models": metadata?.supported_reasoning_levels ?? null, "admin/api/models": adminModel?.supportedReasoningEfforts ?? null, default: metadata?.default_reasoning_level ?? adminModel?.defaultReasoningEffort ?? null, }, ] }), ), reasoningRequests: [ chatHighCall, chatOffCall, messagesEffortCall, messagesBudgetCall, messagesDisabledCall, haikuEffortCall, responsesMedium.call, responsesXhigh.call, lunaOffCall, ].flatMap((call) => call ? [ { url: call.url, model: call.model, reasoning_effort: call.body?.reasoning_effort, thinking: call.body?.thinking, reasoning: call.body?.reasoning, output_config: call.body?.output_config, temperature: call.body?.temperature, }, ] : [], ), observed: [goCall, messagesCall, responsesCall, responsesViaChatCall, responsesViaMessagesCall].flatMap( (call) => (call ? [{ url: call.url, model: call.model, stream: call.stream, session: call.session }] : []), ), }, null, 2, )}\n`, ) console.log(`[opencode-e2e] artifact written to ${artifactPath}`)})
test("an OPENCODE_API_KEY registers the OpenCode catalog with no account", async (t) => { const directory = fs.mkdtempSync("/tmp/bridge-opencode-env-e2e-") const previousStateDir = process.env.BRIDGE_STATE_DIR const previousFetch = globalThis.fetch const previousEnvKey = process.env.OPENCODE_API_KEY process.env.BRIDGE_STATE_DIR = directory process.env.OPENCODE_API_KEY = envKey
const calls = upstreamCalls calls.length = 0 globalThis.fetch = upstreamStub const config: BridgeConfig = { host: "127.0.0.1", port: 0, providers: {}, models: {} } const app = createServer(config, new Map()) t.after(async () => { await app.close() closeTokenUsageDatabases() globalThis.fetch = previousFetch if (previousEnvKey === undefined) delete process.env.OPENCODE_API_KEY else process.env.OPENCODE_API_KEY = previousEnvKey if (previousStateDir === undefined) delete process.env.BRIDGE_STATE_DIR else process.env.BRIDGE_STATE_DIR = previousStateDir fs.rmSync(directory, { recursive: true, force: true }) }) await app.listen({ host: "127.0.0.1", port: 0 }) const base = app.listeningOrigin await discoverModels(config)
// Open mode: no bridge keys, so the request is not scoped to an account pool. const listed = modelList(await (await previousFetch(base + "/v1/models")).json()) assert.ok( listed.some((model) => model.id === completionsModel && model.owned_by === "opencode-go"), "the environment key must register the Go catalog", ) assert.ok( listed.some((model) => model.id === messagesModel && model.owned_by === "opencode-go"), "the environment key must register every wire in the Go catalog", )
const completion = await previousFetch(base + "/v1/chat/completions", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ model: completionsModel, stream: true, messages: [{ role: "user", content: "hi" }] }), }) assert.equal(completion.status, 200, await completion.clone().text()) assert.deepEqual( { url: calls.at(-1)?.url, authorization: calls.at(-1)?.authorization }, { url: `${goBase}/chat/completions`, authorization: `Bearer ${envKey}` }, ) assert.ok(calls.at(-1)?.session, "the environment-key path must send x-opencode-session too")})