From 792e77fc5acaee13c3f2db49aac3082f312761b0 Mon Sep 17 00:00:00 2001 From: JinhaoSong322 Date: Wed, 19 Aug 2026 15:24:35 +0800 Subject: [PATCH] feat(provider): add OrcaRouter as a first-class provider Adds OrcaRouter (OpenAI-compatible LLM routing gateway) alongside the other named OpenAI-compatible providers: - llm: register the orcarouter profile and export the named facade, mirroring groq/deepseek/togetherai - core: add OrcaRouterPlugin so bcode attribution headers are applied, mirroring the existing Zenmux/LLMGateway gateway plugins --- packages/core/src/plugin/provider.ts | 2 + .../core/src/plugin/provider/orcarouter.ts | 21 ++++ .../test/plugin/provider-orcarouter.test.ts | 100 ++++++++++++++++++ .../providers/openai-compatible-profile.ts | 1 + .../llm/src/providers/openai-compatible.ts | 1 + 5 files changed, 125 insertions(+) create mode 100644 packages/core/src/plugin/provider/orcarouter.ts create mode 100644 packages/core/test/plugin/provider-orcarouter.test.ts diff --git a/packages/core/src/plugin/provider.ts b/packages/core/src/plugin/provider.ts index 1749b474ed..2217fd6c98 100644 --- a/packages/core/src/plugin/provider.ts +++ b/packages/core/src/plugin/provider.ts @@ -23,6 +23,7 @@ import { SnowflakeCortexPlugin } from "./provider/snowflake-cortex" import { OpenAICompatiblePlugin } from "./provider/openai-compatible" import { OpencodePlugin } from "./provider/opencode" import { OpenRouterPlugin } from "./provider/openrouter" +import { OrcaRouterPlugin } from "./provider/orcarouter" import { PerplexityPlugin } from "./provider/perplexity" import { SapAICorePlugin } from "./provider/sap-ai-core" import { TogetherAIPlugin } from "./provider/togetherai" @@ -60,6 +61,7 @@ export const ProviderPlugins: PluginInternal.Plugin { + provider.request.headers["HTTP-Referer"] ??= "https://bcode.sh/" + provider.request.headers["X-Title"] ??= "bcode" + }) + } + }), + ) + }), +}) diff --git a/packages/core/test/plugin/provider-orcarouter.test.ts b/packages/core/test/plugin/provider-orcarouter.test.ts new file mode 100644 index 0000000000..a9aa405841 --- /dev/null +++ b/packages/core/test/plugin/provider-orcarouter.test.ts @@ -0,0 +1,100 @@ +import { describe, expect } from "bun:test" +import { Effect } from "effect" +import { Catalog } from "@opencode-ai/core/catalog" +import { PluginV2 } from "@opencode-ai/core/plugin" +import { PluginHost } from "@opencode-ai/core/plugin/host" +import { ProviderPlugins } from "@opencode-ai/core/plugin/provider" +import { OrcaRouterPlugin } from "@opencode-ai/core/plugin/provider/orcarouter" +import { ProviderV2 } from "@opencode-ai/core/provider" +import { testEffect } from "../lib/effect" +import { PluginTestLayer } from "./fixture" + +const it = testEffect(PluginTestLayer) + +const addPlugin = Effect.fn(function* () { + const plugin = yield* PluginV2.Service + const host = yield* PluginHost.make(plugin) + yield* OrcaRouterPlugin.effect(host) +}) + +function required(value: T | undefined): T { + if (value === undefined) throw new Error("Expected value") + return value +} + +const withOrcaRouterProvider = Effect.fn(function* (mutate?: (headers: Record) => void) { + const catalog = yield* Catalog.Service + yield* catalog.transform((catalog) => { + catalog.provider.update(ProviderV2.ID.make("orcarouter"), (provider) => { + provider.api = { + type: "aisdk", + package: "@ai-sdk/openai-compatible", + url: "https://api.orcarouter.ai/v1", + } + mutate?.(provider.request.headers) + }) + }) + return catalog +}) + +describe("OrcaRouterPlugin", () => { + it.effect("is registered so attribution headers can be applied", () => + Effect.sync(() => expect(ProviderPlugins.map((item) => item.id)).toContain(PluginV2.ID.make("orcarouter"))), + ) + + it.effect("applies the bcode attribution headers", () => + Effect.gen(function* () { + const catalog = yield* withOrcaRouterProvider() + yield* addPlugin() + const result = required(yield* catalog.provider.get(ProviderV2.ID.make("orcarouter"))) + expect(result.request.headers).toEqual({ "HTTP-Referer": "https://bcode.sh/", "X-Title": "bcode" }) + }), + ) + + it.effect("merges attribution headers with existing headers", () => + Effect.gen(function* () { + const catalog = yield* withOrcaRouterProvider((headers) => { + headers.Existing = "value" + }) + yield* addPlugin() + + expect(required(yield* catalog.provider.get(ProviderV2.ID.make("orcarouter"))).request.headers).toEqual({ + Existing: "value", + "HTTP-Referer": "https://bcode.sh/", + "X-Title": "bcode", + }) + }), + ) + + it.effect("lets configured headers override the defaults", () => + Effect.gen(function* () { + const catalog = yield* withOrcaRouterProvider((headers) => { + headers["HTTP-Referer"] = "https://example.com/" + headers["X-Title"] = "custom-title" + }) + yield* addPlugin() + + expect(required(yield* catalog.provider.get(ProviderV2.ID.make("orcarouter"))).request.headers).toEqual({ + "HTTP-Referer": "https://example.com/", + "X-Title": "custom-title", + }) + }), + ) + + it.effect("guards attribution headers to the orcarouter base URL", () => + Effect.gen(function* () { + const catalog = yield* Catalog.Service + yield* catalog.transform((catalog) => { + catalog.provider.update(ProviderV2.ID.openrouter, (provider) => { + provider.request.headers = { "HTTP-Referer": "https://example.com/", "X-Title": "custom-title" } + }) + }) + yield* addPlugin() + + expect(required(yield* catalog.provider.get(ProviderV2.ID.openrouter)).request.headers).toEqual({ + "HTTP-Referer": "https://example.com/", + "X-Title": "custom-title", + }) + }), + ) +}) diff --git a/packages/llm/src/providers/openai-compatible-profile.ts b/packages/llm/src/providers/openai-compatible-profile.ts index 30770c9671..5da740f1c8 100644 --- a/packages/llm/src/providers/openai-compatible-profile.ts +++ b/packages/llm/src/providers/openai-compatible-profile.ts @@ -11,6 +11,7 @@ export const profiles = { fireworks: { provider: "fireworks", baseURL: "https://api.fireworks.ai/inference/v1" }, groq: { provider: "groq", baseURL: "https://api.groq.com/openai/v1" }, openrouter: { provider: "openrouter", baseURL: "https://openrouter.ai/api/v1" }, + orcarouter: { provider: "orcarouter", baseURL: "https://api.orcarouter.ai/v1" }, togetherai: { provider: "togetherai", baseURL: "https://api.together.xyz/v1" }, xai: { provider: "xai", baseURL: "https://api.x.ai/v1" }, } as const satisfies Record diff --git a/packages/llm/src/providers/openai-compatible.ts b/packages/llm/src/providers/openai-compatible.ts index a79f65f6df..ea9a597caf 100644 --- a/packages/llm/src/providers/openai-compatible.ts +++ b/packages/llm/src/providers/openai-compatible.ts @@ -62,4 +62,5 @@ export const deepinfra = define(profiles.deepinfra) export const deepseek = define(profiles.deepseek) export const fireworks = define(profiles.fireworks) export const groq = define(profiles.groq) +export const orcarouter = define(profiles.orcarouter) export const togetherai = define(profiles.togetherai)