From 1bb261ff26251480cd28489c8a39c42aeaeb344c Mon Sep 17 00:00:00 2001 From: XiaoHuo888 Date: Wed, 12 Aug 2026 13:49:58 +0800 Subject: [PATCH] feat(plugin): add OrcaRouter as a named summarizer provider --- apps/memos-local-openclaw/README.md | 3 ++ .../src/ingest/providers/index.ts | 9 ++++++ .../src/shared/llm-call.ts | 1 + apps/memos-local-openclaw/src/types.ts | 1 + .../tests/llm-call.test.ts | 29 +++++++++++++++++++ 5 files changed, 43 insertions(+) diff --git a/apps/memos-local-openclaw/README.md b/apps/memos-local-openclaw/README.md index f851316b8..5c43bf3fe 100644 --- a/apps/memos-local-openclaw/README.md +++ b/apps/memos-local-openclaw/README.md @@ -192,6 +192,9 @@ Add the plugin config to `~/.openclaw/openclaw.json`: | Anthropic | `anthropic` | `claude-3-haiku-20240307` | | Gemini | `gemini` | `gemini-1.5-flash` | | AWS Bedrock | `bedrock` | `anthropic.claude-3-haiku-20240307-v1:0` | +| OrcaRouter | `orcarouter` | `anthropic/claude-haiku-4.5` | + +For [OrcaRouter](https://www.orcarouter.ai), use `provider: "orcarouter"` with `endpoint: "https://api.orcarouter.ai/v1"` and an `sk-orca-`-prefixed API key. > **No summarizer config?** The plugin automatically falls back to the OpenClaw native model (auto-detected from `~/.openclaw/openclaw.json`). If that is also unavailable, a rule-based fallback generates summaries from the first sentence + key entities. Good enough to start. diff --git a/apps/memos-local-openclaw/src/ingest/providers/index.ts b/apps/memos-local-openclaw/src/ingest/providers/index.ts index 972930f9e..d1bfcd2ba 100644 --- a/apps/memos-local-openclaw/src/ingest/providers/index.ts +++ b/apps/memos-local-openclaw/src/ingest/providers/index.ts @@ -36,6 +36,7 @@ function detectProvider( return "gemini"; } if (key.includes("bedrock") || url.includes("bedrock")) return "bedrock"; + if (key.includes("orcarouter") || url.includes("orcarouter.ai")) return "orcarouter"; return "openai_compatible"; } @@ -553,6 +554,7 @@ function callSummarize(cfg: SummarizerConfig, text: string, log: Logger): Promis switch (cfg.provider) { case "openai": case "openai_compatible": + case "orcarouter": case "azure_openai": case "zhipu": case "siliconflow": @@ -578,6 +580,7 @@ function callSummarizeTask(cfg: SummarizerConfig, text: string, log: Logger): Pr switch (cfg.provider) { case "openai": case "openai_compatible": + case "orcarouter": case "azure_openai": case "zhipu": case "siliconflow": @@ -603,6 +606,7 @@ function callGenerateTaskTitle(cfg: SummarizerConfig, text: string, log: Logger) switch (cfg.provider) { case "openai": case "openai_compatible": + case "orcarouter": case "azure_openai": case "zhipu": case "siliconflow": @@ -628,6 +632,7 @@ function callTopicJudge(cfg: SummarizerConfig, currentContext: string, newMessag switch (cfg.provider) { case "openai": case "openai_compatible": + case "orcarouter": case "azure_openai": case "zhipu": case "siliconflow": @@ -653,6 +658,7 @@ function callFilterRelevant(cfg: SummarizerConfig, query: string, candidates: Ar switch (cfg.provider) { case "openai": case "openai_compatible": + case "orcarouter": case "azure_openai": case "zhipu": case "siliconflow": @@ -678,6 +684,7 @@ function callJudgeDedup(cfg: SummarizerConfig, newSummary: string, candidates: A switch (cfg.provider) { case "openai": case "openai_compatible": + case "orcarouter": case "azure_openai": case "zhipu": case "siliconflow": @@ -703,6 +710,7 @@ function callTopicClassifier(cfg: SummarizerConfig, taskState: string, newMessag switch (cfg.provider) { case "openai": case "openai_compatible": + case "orcarouter": case "azure_openai": case "zhipu": case "siliconflow": @@ -728,6 +736,7 @@ function callTopicArbitration(cfg: SummarizerConfig, taskState: string, newMessa switch (cfg.provider) { case "openai": case "openai_compatible": + case "orcarouter": case "azure_openai": case "zhipu": case "siliconflow": diff --git a/apps/memos-local-openclaw/src/shared/llm-call.ts b/apps/memos-local-openclaw/src/shared/llm-call.ts index e28a26344..adc87d107 100644 --- a/apps/memos-local-openclaw/src/shared/llm-call.ts +++ b/apps/memos-local-openclaw/src/shared/llm-call.ts @@ -28,6 +28,7 @@ function detectProvider(providerKey: string | undefined, baseUrl: string): Summa return "gemini"; } if (key.includes("bedrock") || url.includes("bedrock")) return "bedrock"; + if (key.includes("orcarouter") || url.includes("orcarouter.ai")) return "orcarouter"; return "openai_compatible"; } diff --git a/apps/memos-local-openclaw/src/types.ts b/apps/memos-local-openclaw/src/types.ts index 0eec78cbe..1028455c0 100644 --- a/apps/memos-local-openclaw/src/types.ts +++ b/apps/memos-local-openclaw/src/types.ts @@ -144,6 +144,7 @@ export interface RankedCandidate { export type SummaryProvider = | "openai" | "openai_compatible" + | "orcarouter" | "anthropic" | "gemini" | "azure_openai" diff --git a/apps/memos-local-openclaw/tests/llm-call.test.ts b/apps/memos-local-openclaw/tests/llm-call.test.ts index a599e5816..8114067bd 100644 --- a/apps/memos-local-openclaw/tests/llm-call.test.ts +++ b/apps/memos-local-openclaw/tests/llm-call.test.ts @@ -107,4 +107,33 @@ describe("shared/llm-call", () => { const body = JSON.parse(cap.init!.body as string); expect("provider" in body).toBe(false); }); + + it("calls OrcaRouter for the orcarouter provider", async () => { + const cap: { url?: string; init?: RequestInit } = {}; + vi.stubGlobal( + "fetch", + vi.fn(async (url: unknown, init?: unknown) => { + cap.url = String(url); + cap.init = init as RequestInit; + return new Response( + JSON.stringify({ choices: [{ message: { content: "ok" } }] }), + { status: 200 }, + ); + }), + ); + + const cfg: SummarizerConfig = { + provider: "orcarouter", + endpoint: "https://api.orcarouter.ai/v1", + apiKey: "sk-orca-test", + model: "anthropic/claude-haiku-4.5", + }; + + const result = await callLLMOnce(cfg, "summarize this"); + + expect(result).toBe("ok"); + expect(cap.url).toBe("https://api.orcarouter.ai/v1/chat/completions"); + const body = JSON.parse(cap.init!.body as string); + expect(body.model).toBe("anthropic/claude-haiku-4.5"); + }); });