From 6859571b6aa00a39dee79e023d9c3edd8b3f0509 Mon Sep 17 00:00:00 2001 From: putraperdana1207-pixel Date: Sun, 30 Aug 2026 00:15:04 +0000 Subject: [PATCH] feat(agent-langgraph): add orcarouter as a named BOT_PROVIDER Add orcarouter as a fourth provider for the framework Bot, wired exactly like the existing openai branch: its own key (ORCAROUTER_API_KEY), a default endpoint (ORCAROUTER_BASE_URL, https://api.orcarouter.ai/v1) and a namespaced default model (orcarouter/fusion). OrcaRouter is an OpenAI-compatible gateway, so the ChatOpenAI integration carries it. Document the new provider in .env.example, docs/configuration.md and the README, forward the variables through docker-compose, and note the new option in the changelog. Co-Authored-By: Claude --- .env.example | 10 ++++++++-- CHANGELOG.md | 9 +++++++++ README.md | 2 +- agent-langgraph/src/index.ts | 28 ++++++++++++++++++++++++++-- docker-compose.yml | 2 ++ docs/configuration.md | 6 +++++- 6 files changed, 51 insertions(+), 6 deletions(-) diff --git a/.env.example b/.env.example index cb3c4ac6..cf27f6d8 100644 --- a/.env.example +++ b/.env.example @@ -144,15 +144,21 @@ OPENAI_API_KEY= # ANTHROPIC_BASE_URL= # GOOGLE_GENERATIVE_AI_BASE_URL= -# Framework Bot provider: openai, anthropic or google. It reads that provider's own +# Framework Bot provider: openai, anthropic, google or orcarouter. It reads that provider's own # key and refuses to start without it, so a deployment on Anthropic never needs an OpenAI key for it. # The proof-of-concept Bot is OpenAI only by construction: it speaks that API directly. +# +# orcarouter is an OpenAI-compatible gateway: it serves many providers under namespaced model names +# over the same endpoint, so the OpenAI integration carries it. Set BOT_MODEL to the catalogue name +# (for example orcarouter/fusion), and ORCAROUTER_BASE_URL if you self-host the gateway. # BOT_PROVIDER=openai # ANTHROPIC_API_KEY= # GOOGLE_API_KEY= +# ORCAROUTER_API_KEY= +# ORCAROUTER_BASE_URL=https://api.orcarouter.ai/v1 # Which model the framework Bot uses. Defaults per provider: gpt-5.5, claude-sonnet-4-5, -# gemini-2.5-flash. A 5.6 tier works here: set one and the Responses API is switched on +# gemini-2.5-flash, orcarouter/fusion. A 5.6 tier works here: set one and the Responses API is switched on # automatically. It used to answer nothing at all on those models — RUN_STARTED, RUN_FINISHED, no # text — because that API streams content blocks rather than a string and the run read only the # string. The default is left at 5.5 so the two shipped Bots stay comparable out of the box. diff --git a/CHANGELOG.md b/CHANGELOG.md index c851fcd3..85015a81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,15 @@ Newest first. `Unreleased` is what is on `main` and not yet tagged. ## Unreleased +### The LangGraph Bot has a fourth provider: orcarouter + +`BOT_PROVIDER=orcarouter` points `agent-langgraph` at [OrcaRouter](https://www.orcarouter.ai), an +OpenAI-compatible gateway that fronts many providers behind one key. It is wired exactly like the +existing `openai` provider — the same integration, a key of its own (`ORCAROUTER_API_KEY`), a +namespaced `BOT_MODEL` such as `orcarouter/fusion`, and a default `ORCAROUTER_BASE_URL` of +`https://api.orcarouter.ai/v1` that a self-hosted gateway can override. Existing providers are +unchanged. + ### A Bot's shell can no longer reach the embedded database without a password In the all-in-one image the cluster was `trust`-auth on loopback, and the Bot's shell runs in the diff --git a/README.md b/README.md index 9bb0801c..482cea4b 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ A Bot is any endpoint speaking [AG-UI](https://github.com/ag-ui-protocol/ag-ui), - Docker, for PostgreSQL and the shipped Bots. - [Bun](https://bun.sh) 1.3+, for the app and API server. - A CopilotKit Intelligence project and license. A free plan is available, and Intelligence can be self-hosted. -- A model key. The proof-of-concept Bot uses OpenAI; the LangGraph Bot can use OpenAI, Anthropic, or Google. +- A model key. The proof-of-concept Bot uses OpenAI; the LangGraph Bot can use OpenAI, Anthropic, Google, or OrcaRouter. ## Quick start diff --git a/agent-langgraph/src/index.ts b/agent-langgraph/src/index.ts index 9b439a17..7cbc7bec 100644 --- a/agent-langgraph/src/index.ts +++ b/agent-langgraph/src/index.ts @@ -98,6 +98,13 @@ const OPENAI_BASE_URL = process.env.OPENAI_BASE_URL?.trim() || undefined; const ANTHROPIC_BASE_URL = process.env.ANTHROPIC_BASE_URL?.trim() || undefined; const GOOGLE_BASE_URL = process.env.GOOGLE_GENERATIVE_AI_BASE_URL?.trim() || undefined; +/** + * OrcaRouter's endpoint. It speaks the OpenAI API, so this is the same shape as `OPENAI_BASE_URL`, + * with a default that names the public gateway rather than OpenAI. A self-hosted gateway can point + * it somewhere else, which is why it is a variable at all. + */ +const ORCAROUTER_BASE_URL = + process.env.ORCAROUTER_BASE_URL?.trim() || "https://api.orcarouter.ai/v1"; /** * OpenAI only, and Responses API only: how hard this Bot is allowed to think. @@ -120,7 +127,7 @@ if (REASONING_PROBLEM) { * configuration that goes nowhere is worse than configuration that is absent, because the Bot looks * configured either way. Both messages name the variable that would make it work. */ -if (REASONING_EFFORT && PROVIDER !== "openai") { +if (REASONING_EFFORT && PROVIDER !== "openai" && PROVIDER !== "orcarouter") { console.error( `BOT_REASONING_EFFORT is OpenAI's setting, and BOT_PROVIDER=${PROVIDER}. Unset it, or set BOT_PROVIDER=openai.`, ); @@ -136,6 +143,7 @@ if (REASONING_EFFORT && !USE_RESPONSES_API) { function defaultModelFor(provider: string): string { if (provider === "anthropic") return "claude-sonnet-4-5"; if (provider === "google") return "gemini-2.5-flash"; + if (provider === "orcarouter") return "orcarouter/fusion"; return "gpt-5.5"; } @@ -150,12 +158,13 @@ const KEY_VARIABLE: Record = { openai: "OPENAI_API_KEY", anthropic: "ANTHROPIC_API_KEY", google: "GOOGLE_API_KEY", + orcarouter: "ORCAROUTER_API_KEY", }; const keyVariable = KEY_VARIABLE[PROVIDER]; if (!keyVariable) { console.error( - `BOT_PROVIDER=${PROVIDER} is not one this Bot knows. Use openai, anthropic or google.`, + `BOT_PROVIDER=${PROVIDER} is not one this Bot knows. Use openai, anthropic, google or orcarouter.`, ); process.exit(1); } @@ -191,6 +200,21 @@ function toBoundTools(input: RunAgentInput) { * rest of this file does not know which one it got. */ function buildModel() { + if (PROVIDER === "orcarouter") { + /* + * OpenAI-compatible, like the `openai` branch: OrcaRouter exposes a provider/model namespace + * over the same endpoint, so the same integration serves it. `ORCAROUTER_API_KEY` is its own + * key, and `BOT_MODEL` carries the namespaced name the catalogue publishes. + */ + return new ChatOpenAI({ + model: MODEL, + apiKey: API_KEY, + streaming: true, + configuration: { baseURL: ORCAROUTER_BASE_URL }, + ...(USE_RESPONSES_API ? { useResponsesApi: true } : {}), + ...(REASONING_EFFORT ? { reasoning: { effort: REASONING_EFFORT } } : {}), + }); + } if (PROVIDER === "anthropic") { return new ChatAnthropic({ model: MODEL, diff --git a/docker-compose.yml b/docker-compose.yml index cdfe4e35..3d2ded5f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -260,6 +260,8 @@ services: ANTHROPIC_BASE_URL: ${ANTHROPIC_BASE_URL:-} GOOGLE_API_KEY: ${GOOGLE_API_KEY:-} GOOGLE_GENERATIVE_AI_BASE_URL: ${GOOGLE_GENERATIVE_AI_BASE_URL:-} + ORCAROUTER_API_KEY: ${ORCAROUTER_API_KEY:-} + ORCAROUTER_BASE_URL: ${ORCAROUTER_BASE_URL:-https://api.orcarouter.ai/v1} # gpt-5.5, to stay comparable with agent-bot above rather than because 5.6 does not work: # set BOT_MODEL to one and the Responses API is switched on for it automatically. BOT_MODEL: ${BOT_MODEL:-gpt-5.5} diff --git a/docs/configuration.md b/docs/configuration.md index e9ec67d1..3191d0bb 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -44,11 +44,13 @@ at `agent-langgraph` on a laptop. | `DEPLOYMENT_ID` | the tenant package's id | Names this deployment inside a shared Intelligence project. | | `OPENAI_API_KEY` | unset | Default model key for built-in agents and both shipped Bots. | | `OPENAI_BASE_URL` | unset | OpenAI-compatible endpoint that key is spent against. See below. | -| `BOT_PROVIDER` | `openai` | Provider for `agent-langgraph`: `openai`, `anthropic`, or `google`. | +| `BOT_PROVIDER` | `openai` | Provider for `agent-langgraph`: `openai`, `anthropic`, `google`, or `orcarouter`. | | `ANTHROPIC_API_KEY` | unset | Anthropic key when `BOT_PROVIDER=anthropic`. | | `ANTHROPIC_BASE_URL` | unset | Anthropic-compatible endpoint that key is spent against. | | `GOOGLE_API_KEY` | unset | Google key when `BOT_PROVIDER=google`. | | `GOOGLE_GENERATIVE_AI_BASE_URL` | unset | Google-compatible endpoint that key is spent against. | +| `ORCAROUTER_API_KEY` | unset | OrcaRouter key when `BOT_PROVIDER=orcarouter`. | +| `ORCAROUTER_BASE_URL`| `https://api.orcarouter.ai/v1` | OrcaRouter endpoint that key is spent against. | | `BOT_MODEL` | provider default from Bot code/env | Model used by the shipped Bots. | | `BOT_RESPONSES_API` | `false` | Makes `agent-langgraph` use the OpenAI Responses API. | | `AGENT_STALL_TIMEOUT_MS` | unset (off) | How long a Bot's stream may produce nothing before the turn is ended for it. | @@ -111,6 +113,8 @@ It moves the whole deployment rather than one Bot. The API server reads it for p The other two providers work the same way under their own names, because they are different APIs rather than different URLs for this one: `ANTHROPIC_BASE_URL` and `GOOGLE_GENERATIVE_AI_BASE_URL`. All three are the names the API server already reads, so one line moves the built-in agents and the Bots together and a deployment cannot end up with half of itself pointed somewhere else. +`orcarouter` is a fourth `BOT_PROVIDER` for `agent-langgraph`, and it is the first one that is not a model vendor. OrcaRouter is an OpenAI-compatible gateway that fronts many providers behind one key, so the `openai` integration carries it and the endpoint has a default rather than being unset: `ORCAROUTER_BASE_URL=https://api.orcarouter.ai/v1`. Set `BOT_PROVIDER=orcarouter` with `ORCAROUTER_API_KEY` and a namespaced `BOT_MODEL` the catalogue publishes, for example `orcarouter/fusion`. + Model names travel verbatim, so use whatever the endpoint publishes. An endpoint that namespaces its catalogue wants both halves of the name, in `BOT_MODEL` and in the tenant package's `default_model` alike. A gateway that fronts several providers behind one key is addressed the usual way: