diff --git a/README.md b/README.md index 2e99e53356..a53f30bac8 100644 --- a/README.md +++ b/README.md @@ -281,6 +281,7 @@ Advanced and source-build guides: | Z.AI GLM Coding Plan | `/provider` or OpenAI-compatible env vars | Uses `OPENAI_API_KEY` at `https://api.z.ai/api/coding/paas/v4` and defaults to `glm-5.2` | | AI/ML API | `/provider` or `AIMLAPI_API_KEY` ([setup guide](docs/aimlapi-setup.md)) | Uses `https://api.aimlapi.com/v1`, auto-detects the OpenAI-compatible route from `AIMLAPI_API_KEY`, sends OpenClaude attribution headers, and discovers chat-capable models from the public `/models` catalog | | ApiSmart | `/provider` or `APISMART_API_KEY` | Uses `https://gw.apismart.ai/v1`, defaults to `DEEPSEEK_V4_FLASH`, and supports optional `APISMART_MODEL` plus authenticated model discovery | +| OrcaRouter | `/provider` or `ORCAROUTER_API_KEY` | OpenAI-compatible model routing gateway at `https://api.orcarouter.ai/v1`, defaults to `openai/gpt-5.5`, with `orcarouter/auto` smart routing and authenticated model discovery | | Hicap | `/provider` or OpenAI-compatible env vars | Uses `api-key` auth, discovers models from unauthenticated `/models`, and supports Responses mode for `gpt-` models | | Fireworks AI | `/provider` or env vars | First-class provider with 276 curated models (DeepSeek, Qwen, Llama, Gemma, and more); uses `FIREWORKS_API_KEY` | | LongCat | `/provider` or env vars | Meituan LongCat OpenAI-compatible API at `https://api.longcat.chat/openai/v1`; uses `LONGCAT_API_KEY` and defaults to `LongCat-2.0` | diff --git a/src/integrations/compatibility.test.ts b/src/integrations/compatibility.test.ts index 4b135d5714..ec75336dad 100644 --- a/src/integrations/compatibility.test.ts +++ b/src/integrations/compatibility.test.ts @@ -33,6 +33,7 @@ const EXPECTED_PRESETS = [ 'hicap', 'azure-openai', 'openrouter', + 'orcarouter', 'lmstudio', 'dashscope-cn', 'dashscope-intl', diff --git a/src/integrations/gateways/orcarouter.test.ts b/src/integrations/gateways/orcarouter.test.ts new file mode 100644 index 0000000000..d820ecde74 --- /dev/null +++ b/src/integrations/gateways/orcarouter.test.ts @@ -0,0 +1,27 @@ +import { describe, expect, test } from 'bun:test' + +import orcarouter from './orcarouter.js' + +describe('orcarouter gateway', () => { + test('uses hybrid discovery with dedicated credentials', () => { + expect(orcarouter.id).toBe('orcarouter') + expect(orcarouter.catalog?.source).toBe('hybrid') + expect(orcarouter.catalog?.discovery?.kind).toBe('openai-compatible') + expect(orcarouter.setup.dedicatedCredentialsOnly).toBe(true) + expect(orcarouter.setup.credentialEnvVars).toEqual(['ORCAROUTER_API_KEY']) + expect(orcarouter.defaultBaseUrl).toBe('https://api.orcarouter.ai/v1') + expect(orcarouter.defaultModel).toBe('openai/gpt-5.5') + }) + + test('curated catalog exposes auto routing plus flagship models', () => { + const models = orcarouter.catalog?.models ?? [] + expect(models.some(model => model.apiName === 'orcarouter/auto')).toBe(true) + expect(models.some(model => model.apiName === 'openai/gpt-5.5')).toBe(true) + expect( + models.some(model => model.apiName === 'anthropic/claude-sonnet-4.6'), + ).toBe(true) + expect( + models.some(model => model.apiName === 'google/gemini-3.5-flash'), + ).toBe(true) + }) +}) diff --git a/src/integrations/gateways/orcarouter.ts b/src/integrations/gateways/orcarouter.ts new file mode 100644 index 0000000000..e862087ba4 --- /dev/null +++ b/src/integrations/gateways/orcarouter.ts @@ -0,0 +1,65 @@ +import { defineGateway } from '../define.js' + +export default defineGateway({ + id: 'orcarouter', + label: 'OrcaRouter', + category: 'aggregating', + defaultBaseUrl: 'https://api.orcarouter.ai/v1', + defaultModel: 'openai/gpt-5.5', + supportsModelRouting: true, + setup: { + requiresAuth: true, + authMode: 'api-key', + credentialEnvVars: ['ORCAROUTER_API_KEY'], + dedicatedCredentialsOnly: true, + }, + startup: { + probeReadiness: 'openai-compatible-models', + }, + transportConfig: { + kind: 'openai-compatible', + openaiShim: { + supportsAuthHeaders: true, + }, + }, + preset: { + id: 'orcarouter', + description: 'OrcaRouter OpenAI-compatible endpoint', + apiKeyEnvVars: ['ORCAROUTER_API_KEY'], + vendorId: 'openai', + }, + catalog: { + source: 'hybrid', + discovery: { kind: 'openai-compatible' }, + discoveryCacheTtl: '1d', + discoveryRefreshMode: 'background-if-stale', + allowManualRefresh: true, + models: [ + { + id: 'orcarouter-auto', + apiName: 'orcarouter/auto', + label: 'Auto — Smart Routing (via OrcaRouter)', + notes: 'Gateway routes to the best model for each request', + }, + { + id: 'orcarouter-gpt-5.5', + apiName: 'openai/gpt-5.5', + label: 'GPT-5.5 (via OrcaRouter)', + modelDescriptorId: 'gpt-5.5', + }, + { + id: 'orcarouter-claude-sonnet-4-6', + apiName: 'anthropic/claude-sonnet-4.6', + label: 'Claude Sonnet 4.6 (via OrcaRouter)', + modelDescriptorId: 'claude-sonnet-4-6', + }, + { + id: 'orcarouter-gemini-3.5-flash', + apiName: 'google/gemini-3.5-flash', + label: 'Gemini 3.5 Flash (via OrcaRouter)', + modelDescriptorId: 'gemini-3.5-flash', + }, + ], + }, + usage: { supported: false }, +}) diff --git a/src/integrations/generated/integrationArtifacts.generated.ts b/src/integrations/generated/integrationArtifacts.generated.ts index 87a002174e..564a917eb8 100644 --- a/src/integrations/generated/integrationArtifacts.generated.ts +++ b/src/integrations/generated/integrationArtifacts.generated.ts @@ -43,6 +43,7 @@ import gatewayOllama from '../gateways/ollama.js' import gatewayOpencodeGo from '../gateways/opencode-go.js' import gatewayOpencode from '../gateways/opencode.js' import gatewayOpenrouter from '../gateways/openrouter.js' +import gatewayOrcarouter from '../gateways/orcarouter.js' import gatewayTogether from '../gateways/together.js' import gatewayVertex from '../gateways/vertex.js' import gatewayXiaomiMimoToken from '../gateways/xiaomi-mimo-token.js' @@ -90,7 +91,7 @@ import modelXai from '../models/xai.js' import modelXiaomiMimo from '../models/xiaomi-mimo.js' export const VENDOR_DESCRIPTORS = [vendorAnthropic, vendorBankr, vendorDeepseek, vendorFireworks, vendorGemini, vendorLongcat, vendorMinimax, vendorMoonshot, vendorNearai, vendorOpenai, vendorVenice, vendorXai, vendorXiaomiMimo, vendorZai] as const satisfies readonly VendorDescriptor[] -export const GATEWAY_DESCRIPTORS = [gatewayAimlapi, gatewayApismart, gatewayAtlasCloud, gatewayAtomicChat, gatewayAzureOpenai, gatewayBedrock, gatewayClinepass, gatewayCloudflare, gatewayCustom, gatewayDashscopeCn, gatewayDashscopeIntl, gatewayGithubEnterprise, gatewayGithub, gatewayGitlawbOpengateway, gatewayGroq, gatewayHicap, gatewayKimiCode, gatewayLmstudio, gatewayMistral, gatewayNvidiaNim, gatewayOllama, gatewayOpencodeGo, gatewayOpencode, gatewayOpenrouter, gatewayTogether, gatewayVertex, gatewayXiaomiMimoToken] as const satisfies readonly GatewayDescriptor[] +export const GATEWAY_DESCRIPTORS = [gatewayAimlapi, gatewayApismart, gatewayAtlasCloud, gatewayAtomicChat, gatewayAzureOpenai, gatewayBedrock, gatewayClinepass, gatewayCloudflare, gatewayCustom, gatewayDashscopeCn, gatewayDashscopeIntl, gatewayGithubEnterprise, gatewayGithub, gatewayGitlawbOpengateway, gatewayGroq, gatewayHicap, gatewayKimiCode, gatewayLmstudio, gatewayMistral, gatewayNvidiaNim, gatewayOllama, gatewayOpencodeGo, gatewayOpencode, gatewayOpenrouter, gatewayOrcarouter, gatewayTogether, gatewayVertex, gatewayXiaomiMimoToken] as const satisfies readonly GatewayDescriptor[] export const ANTHROPIC_PROXY_DESCRIPTORS = [anthropicproxyCustom] as const satisfies readonly AnthropicProxyDescriptor[] export const BRAND_DESCRIPTORS = [brandClaude, brandDeepseek, brandFireworks, brandGemini, brandGlm, brandGpt, brandKimi, brandLing, brandLlama, brandLongcat, brandMacaron, brandMinimax, brandMistral, brandNearai, brandNemotron, brandOpenaiCompatibleAlias, brandQwen, brandTencent, brandXai, brandXiaomiMimo] as const satisfies readonly BrandDescriptor[] export const MODEL_DESCRIPTOR_GROUPS = [modelClaude, modelDeepseek, modelFireworksMerged, modelGemini, modelGlm, modelGpt, modelKimi, modelLing, modelLlama, modelLongcat, modelMacaron, modelMinimax, modelMistral, modelNearai, modelNemotron, modelOpenaiCompatibleAlias, modelOpencode, modelQwen, modelTencent, modelXai, modelXiaomiMimo] as const satisfies readonly (readonly ModelDescriptor[])[] diff --git a/src/integrations/generated/integrationManifest.generated.ts b/src/integrations/generated/integrationManifest.generated.ts index 2ad565a0fb..088cd10669 100644 --- a/src/integrations/generated/integrationManifest.generated.ts +++ b/src/integrations/generated/integrationManifest.generated.ts @@ -400,6 +400,17 @@ export const PROVIDER_PRESET_MANIFEST = [ "OPENROUTER_API_KEY" ] }, + { + "preset": "orcarouter", + "routeKind": "gateway", + "routeId": "orcarouter", + "vendorId": "openai", + "gatewayId": "orcarouter", + "description": "OrcaRouter OpenAI-compatible endpoint", + "apiKeyEnvVars": [ + "ORCAROUTER_API_KEY" + ] + }, { "preset": "together", "routeKind": "gateway", @@ -567,6 +578,7 @@ export const ORDERED_PROVIDER_PRESETS = [ "opencode-go", "opencode", "openrouter", + "orcarouter", "together", "venice", "xai", diff --git a/src/integrations/index.test.ts b/src/integrations/index.test.ts index 23fd0c485a..b8cb2f72c8 100644 --- a/src/integrations/index.test.ts +++ b/src/integrations/index.test.ts @@ -67,6 +67,9 @@ describe('loaded registry validation', () => { // Virtual model — the gateway's smart router resolves it server-side, // so there is no concrete model descriptor to reference. 'gitlawb-opengateway:opengateway-auto', + // Virtual model — OrcaRouter's `orcarouter/auto` router resolves the + // serving model server-side, so there is no concrete descriptor. + 'orcarouter:orcarouter-auto', // Cloudflare Workers AI serves provider-specific quantized builds // (`@cf/...`) with no shared cross-provider model descriptor, the same // situation as azure-deployment above. See gateways/cloudflare.ts (#1100). diff --git a/src/utils/envFile.ts b/src/utils/envFile.ts index a7093b48d3..da04281eb4 100644 --- a/src/utils/envFile.ts +++ b/src/utils/envFile.ts @@ -93,6 +93,7 @@ const ALLOWED_ENV_FILE_KEYS = new Set([ 'OPENGATEWAY_BASE_URL', 'OPENCLAUDE_OLLAMA_NUM_CTX', 'OPENROUTER_API_KEY', + 'ORCAROUTER_API_KEY', 'OPENAI_API_BASE', 'OPENAI_API_FORMAT', 'OPENAI_API_KEYS', diff --git a/web/src/data/providers.ts b/web/src/data/providers.ts index 93f7f64a37..2684bc4443 100644 --- a/web/src/data/providers.ts +++ b/web/src/data/providers.ts @@ -140,6 +140,14 @@ export const providers: Provider[] = [ envVars: ['OPENROUTER_API_KEY'], notes: 'OpenAI-compatible aggregation across hundreds of hosted models.', }, + { + id: 'orcarouter', + name: 'OrcaRouter', + group: 'gateways', + setup: '/provider or env vars', + envVars: ['ORCAROUTER_API_KEY'], + notes: 'OpenAI-compatible model routing gateway with named auto routing.', + }, { id: 'near-ai', name: 'NEAR AI',