From 43fa53a986639913bb59781e1e3b492bbabbfdf1 Mon Sep 17 00:00:00 2001 From: bangla24bdrang-lab Date: Wed, 26 Aug 2026 17:29:14 +0000 Subject: [PATCH] feat(llm): add OrcaRouter as a first-class model provider Adds scorpio.llm.orcarouter, a named provider package mirroring the existing scorpio.llm.openai wiring. OrcaRouter is an OpenAI-compatible gateway for models and agents, so the provider reuses OpenAIModelService against https://api.orcarouter.ai/v1 with model listing enabled. Registered in registerBuiltInLlmProviders() so it shows up in the admin UI provider list alongside the existing providers, and documented in the models guide (EN + zh). --- package.json | 2 +- packages/apps/docs/guide/models.md | 1 + packages/apps/docs/zh/guide/models.md | 1 + .../llm/scorpio.llm.orcarouter/.gitignore | 4 + .../llm/scorpio.llm.orcarouter/package.json | 20 +++++ .../llm/scorpio.llm.orcarouter/src/index.ts | 30 ++++++++ .../llm/scorpio.llm.orcarouter/tsconfig.json | 9 +++ packages/backend/sbot/package.json | 1 + .../backend/sbot/src/Core/LlmProviders.ts | 2 + pnpm-lock.yaml | 74 +++++++++++++------ 10 files changed, 119 insertions(+), 25 deletions(-) create mode 100644 packages/backend/llm/scorpio.llm.orcarouter/.gitignore create mode 100644 packages/backend/llm/scorpio.llm.orcarouter/package.json create mode 100644 packages/backend/llm/scorpio.llm.orcarouter/src/index.ts create mode 100644 packages/backend/llm/scorpio.llm.orcarouter/tsconfig.json diff --git a/package.json b/package.json index 2825ffe6..4c39e7aa 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "test": "pnpm -r --if-present run test", "build:base": "pnpm --filter scorpio.di run build && pnpm --filter scorpio.command run build && pnpm --filter scorpio.ai run build && pnpm --filter scorpio.tools.filesystem run build && pnpm --filter scorpio.tools.command run build", - "build:llm": "node scripts/fetch-models-dev.js && pnpm --filter scorpio.llm run build && pnpm --filter scorpio.llm.openai run build && pnpm --filter scorpio.llm.anthropic run build && pnpm --filter scorpio.llm.gemini run build && pnpm --filter scorpio.llm.ollama run build && pnpm --filter scorpio.llm.cohere run build && pnpm --filter scorpio.llm.voyage run build", + "build:llm": "node scripts/fetch-models-dev.js && pnpm --filter scorpio.llm run build && pnpm --filter scorpio.llm.openai run build && pnpm --filter scorpio.llm.anthropic run build && pnpm --filter scorpio.llm.gemini run build && pnpm --filter scorpio.llm.ollama run build && pnpm --filter scorpio.llm.cohere run build && pnpm --filter scorpio.llm.voyage run build && pnpm --filter scorpio.llm.orcarouter run build", "build:saver": "pnpm --filter scorpio.saver run build && pnpm --filter scorpio.saver.file run build && pnpm --filter scorpio.saver.sqlite run build && pnpm --filter scorpio.saver.postgres run build", "build:schema": "pnpm --filter plugin.schema run build && pnpm --filter @sbot/shared run build", "build:agents": "pnpm --filter agent.skill run build && pnpm --filter agent.agenda run build && pnpm --filter agent.memory run build && pnpm --filter agent.note run build && pnpm --filter agent.wiki run build", diff --git a/packages/apps/docs/guide/models.md b/packages/apps/docs/guide/models.md index 53506177..0e4efc7e 100644 --- a/packages/apps/docs/guide/models.md +++ b/packages/apps/docs/guide/models.md @@ -14,6 +14,7 @@ Fill in provider, API key, base URL, and model name. The same connection can be | Google Gemini | Gemini 2.0 / 2.5 Pro, Flash | | Gemini Image | Gemini image-generation models | | Ollama | Local models via Ollama runtime | +| OrcaRouter | OpenAI-compatible gateway for models and agents (adaptive routing, failover, zero-markup inference) | | OpenAI-compatible | Azure OpenAI, Groq, Mistral, DeepSeek, Qwen, Together, etc. | Any endpoint that implements the OpenAI chat completions API can be used as a provider — pick **OpenAI-compatible** and override the base URL. diff --git a/packages/apps/docs/zh/guide/models.md b/packages/apps/docs/zh/guide/models.md index a5ce7f0e..3ad12f20 100644 --- a/packages/apps/docs/zh/guide/models.md +++ b/packages/apps/docs/zh/guide/models.md @@ -14,6 +14,7 @@ | Google Gemini | Gemini 2.0 / 2.5 Pro、Flash | | Gemini Image | Gemini 图像生成模型 | | Ollama | 通过 Ollama 运行时使用本地模型 | +| OrcaRouter | 面向模型与 Agent 的 OpenAI 兼容网关(自适应路由、故障转移、零加成推理) | | OpenAI 兼容 | Azure OpenAI、Groq、Mistral、DeepSeek、Qwen、Together 等 | 任何实现了 OpenAI Chat Completions 接口的服务都可以作为供应商使用 —— 选择 **OpenAI 兼容** 并覆盖 Base URL 即可。 diff --git a/packages/backend/llm/scorpio.llm.orcarouter/.gitignore b/packages/backend/llm/scorpio.llm.orcarouter/.gitignore new file mode 100644 index 00000000..f1797789 --- /dev/null +++ b/packages/backend/llm/scorpio.llm.orcarouter/.gitignore @@ -0,0 +1,4 @@ +/node_modules +/dist +/package-lock.json +/tsconfig.tsbuildinfo diff --git a/packages/backend/llm/scorpio.llm.orcarouter/package.json b/packages/backend/llm/scorpio.llm.orcarouter/package.json new file mode 100644 index 00000000..011a5bcb --- /dev/null +++ b/packages/backend/llm/scorpio.llm.orcarouter/package.json @@ -0,0 +1,20 @@ +{ + "name": "scorpio.llm.orcarouter", + "version": "0.0.1", + "description": "OrcaRouter model provider for scorpio.llm", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { "build": "rimraf dist && tsc -b --force" }, + "dependencies": { + "@langchain/core": "catalog:", + "@langchain/openai": "^1.4.7", + "scorpio.llm": "workspace:*", + "scorpio.llm.openai": "workspace:*" + }, + "private": true, + "devDependencies": { + "@types/node": "catalog:", + "rimraf": "catalog:", + "typescript": "catalog:" + } +} diff --git a/packages/backend/llm/scorpio.llm.orcarouter/src/index.ts b/packages/backend/llm/scorpio.llm.orcarouter/src/index.ts new file mode 100644 index 00000000..d4678a61 --- /dev/null +++ b/packages/backend/llm/scorpio.llm.orcarouter/src/index.ts @@ -0,0 +1,30 @@ +import { type LlmProviderRegistry, llmProviderRegistry } from "scorpio.llm"; +import { OpenAIModelService } from "scorpio.llm.openai"; + +/** OrcaRouter provider id, kept stable so saved model configs keep working. */ +export const OrcaRouterProvider = "orcarouter"; + +export function registerOrcaRouterProvider(registry: LlmProviderRegistry = llmProviderRegistry): void { + registry.registerModel({ + type: OrcaRouterProvider, + label: "OrcaRouter", + configSchema: {}, + defaults: { baseURL: "https://api.orcarouter.ai/v1", model: "orcarouter/fusion" }, + apiKeyRequired: true, + createModel: config => { + const service = new OpenAIModelService(config); + service.initialize(); + return service; + }, + listModels: async config => { + if (!config.apiKey) throw new Error("apiKey is required for OrcaRouter"); + const base = (config.baseURL || "https://api.orcarouter.ai/v1").replace(/\/$/, ""); + const res = await fetch(`${base}/models`, { + headers: { Authorization: `Bearer ${config.apiKey}` }, + }); + if (!res.ok) throw new Error(`Models request failed: ${res.status}`); + const data: any = await res.json(); + return (data.data || []).map((model: any) => model.id as string); + }, + }); +} diff --git a/packages/backend/llm/scorpio.llm.orcarouter/tsconfig.json b/packages/backend/llm/scorpio.llm.orcarouter/tsconfig.json new file mode 100644 index 00000000..5f8b8852 --- /dev/null +++ b/packages/backend/llm/scorpio.llm.orcarouter/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { "outDir": "dist", "rootDir": "src" }, + "include": ["src"], + "references": [ + { "path": "../scorpio.llm" }, + { "path": "../scorpio.llm.openai" } + ] +} diff --git a/packages/backend/sbot/package.json b/packages/backend/sbot/package.json index a97b35c6..a1873534 100644 --- a/packages/backend/sbot/package.json +++ b/packages/backend/sbot/package.json @@ -76,6 +76,7 @@ "scorpio.llm.gemini": "workspace:*", "scorpio.llm.ollama": "workspace:*", "scorpio.llm.openai": "workspace:*", + "scorpio.llm.orcarouter": "workspace:*", "scorpio.llm.voyage": "workspace:*", "scorpio.saver": "workspace:*", "scorpio.saver.file": "workspace:*", diff --git a/packages/backend/sbot/src/Core/LlmProviders.ts b/packages/backend/sbot/src/Core/LlmProviders.ts index 082b1cb7..d896b093 100644 --- a/packages/backend/sbot/src/Core/LlmProviders.ts +++ b/packages/backend/sbot/src/Core/LlmProviders.ts @@ -3,6 +3,7 @@ import { registerCohereProvider } from "scorpio.llm.cohere"; import { registerGeminiProvider } from "scorpio.llm.gemini"; import { registerOllamaProvider } from "scorpio.llm.ollama"; import { registerOpenAIProvider } from "scorpio.llm.openai"; +import { registerOrcaRouterProvider } from "scorpio.llm.orcarouter"; import { registerVoyageProvider } from "scorpio.llm.voyage"; let registered = false; @@ -16,5 +17,6 @@ export function registerBuiltInLlmProviders(): void { registerOllamaProvider(); registerCohereProvider(); registerVoyageProvider(); + registerOrcaRouterProvider(); registered = true; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d872d9e3..af65c89e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -300,6 +300,25 @@ importers: specifier: 'catalog:' version: 3.5.38(typescript@6.0.3) + packages/backend/command/scorpio.command: + dependencies: + '@langchain/core': + specifier: 'catalog:' + version: 1.1.49(openai@6.43.0(ws@8.21.0)(zod@4.4.3)) + zod: + specifier: 'catalog:' + version: 4.4.3 + devDependencies: + '@types/node': + specifier: 'catalog:' + version: 25.9.3 + rimraf: + specifier: 'catalog:' + version: 6.1.3 + typescript: + specifier: 'catalog:' + version: 6.0.3 + packages/backend/llm/scorpio.llm: dependencies: '@langchain/core': @@ -417,6 +436,31 @@ importers: specifier: 'catalog:' version: 6.0.3 + packages/backend/llm/scorpio.llm.orcarouter: + dependencies: + '@langchain/core': + specifier: 'catalog:' + version: 1.1.49(openai@6.43.0(ws@8.21.0)(zod@4.4.3)) + '@langchain/openai': + specifier: ^1.4.7 + version: 1.4.7(@langchain/core@1.1.49(openai@6.43.0(ws@8.21.0)(zod@4.4.3)))(ws@8.21.0) + scorpio.llm: + specifier: workspace:* + version: link:../scorpio.llm + scorpio.llm.openai: + specifier: workspace:* + version: link:../scorpio.llm.openai + devDependencies: + '@types/node': + specifier: 'catalog:' + version: 25.9.3 + rimraf: + specifier: 'catalog:' + version: 6.1.3 + typescript: + specifier: 'catalog:' + version: 6.0.3 + packages/backend/llm/scorpio.llm.voyage: dependencies: '@langchain/openai': @@ -1097,6 +1141,9 @@ importers: scorpio.llm.openai: specifier: workspace:* version: link:../llm/scorpio.llm.openai + scorpio.llm.orcarouter: + specifier: workspace:* + version: link:../llm/scorpio.llm.orcarouter scorpio.llm.voyage: specifier: workspace:* version: link:../llm/scorpio.llm.voyage @@ -1109,12 +1156,12 @@ importers: scorpio.saver.sqlite: specifier: workspace:* version: link:../saver/scorpio.saver.sqlite - scorpio.tools.filesystem: - specifier: workspace:* - version: link:../tools/scorpio.tools.filesystem scorpio.tools.command: specifier: workspace:* version: link:../tools/scorpio.tools.command + scorpio.tools.filesystem: + specifier: workspace:* + version: link:../tools/scorpio.tools.filesystem sequelize: specifier: ^6.37.8 version: 6.37.8(sqlite3@6.0.1) @@ -1246,25 +1293,6 @@ importers: specifier: 'catalog:' version: 6.0.3 - packages/backend/command/scorpio.command: - dependencies: - '@langchain/core': - specifier: 'catalog:' - version: 1.1.49(openai@6.43.0(ws@8.21.0)(zod@4.4.3)) - zod: - specifier: 'catalog:' - version: 4.4.3 - devDependencies: - '@types/node': - specifier: 'catalog:' - version: 25.9.3 - rimraf: - specifier: 'catalog:' - version: 6.1.3 - typescript: - specifier: 'catalog:' - version: 6.0.3 - packages/backend/tools/scorpio.tools.command: dependencies: '@langchain/core': @@ -1312,8 +1340,6 @@ importers: specifier: 'catalog:' version: 6.0.3 - packages/shared/assets: {} - packages/shared/chat-ui: dependencies: '@codemirror/lang-javascript':