feat: add OrcaRouter provider with API-key and PKCE login - #461
Open
nissrin2020ali-ux wants to merge 1 commit into
Open
nissrin2020ali-ux wants to merge 1 commit into
nissrin2020ali-ux wants to merge 1 commit into
Conversation
Signed-off-by: nissrin2020ali-ux <nissrin2020ali-ux@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds OrcaRouter as a first-class model provider, plus a connect flow so a user can either paste an existing API key or authorize with their own OrcaRouter account.
apps/worker/src/ai/orcarouter/provider.ts—baseUrl = https://api.orcarouter.ai/v1, registered as anopenai-completionsextension provider,apiKey: $ORCAROUTER_API_KEYapps/worker/src/ai/orcarouter/catalog.ts— liveGET /v1/modelsread with the run's own key, bounded and capability-filtered, with a verified seed fallbackorcarouteradded toCURATED_PROVIDERSandPROVIDER_API_KEY_ENVinapps/worker/src/ai/models.ts, mirrored inapps/cli/src/model-spec.tsresolveModelSelectionregisters the provider before resolving, soSHANNON_AI_MODEL=orcarouter:<vendor/model>resolves against the live catalog instead of pi's builtin registryorcarouter— key read fromORCAROUTER_API_KEY(aliasesORCA_API_KEY,ORCA_KEY,SHANNON_AI_API_KEY)shannon connect), with Flow B out-of-band available asshannon connect --pkce --oob. Flow A is the default because Shannon's client is a local process that can bind127.0.0.1:<port>; Flow B exists for a headless or sandboxed shell where it cannotshannon connect [--api-key|--pkce], documented inshannon help connectand the dispatcher's command listAffiliation disclosure: I'm an engineer on the OrcaRouter team.
How the credential works
The key belongs to the user, not to this project: it is issued to their OrcaRouter account, billed to them, listed in their console, and revocable at any time from https://www.orcarouter.ai/console/authorized-apps. No client secret is involved — PKCE binds the auth code to this process, so an intercepted code cannot be redeemed by anyone else. The discovery document confirms it:
token_endpoint_auth_methods_supported: ["none"].Both entry points produce the same thing — an ordinary
sk-orca-…API key — so they resolve through one seam:methodis display and support metadata only. The request path, the catalog, and the model descriptors never branch on it —provider.test.tsdrives both entries through the registeredstreamSimpleand asserts the resulting HTTP request is byte-identical apart from the reported provenance.A PKCE-issued key is durable but is not a refresh token, and no code here pretends otherwise. A relay
401takes a terminal reauthentication path:recordOrcaRelayRejection(orcaGeneration)marks only the exact account and credential generation that made the rejected request. A late failure from a request issued before a re-login names a generation that is no longer current, soappliedis false and the freshly reauthorized credential is left alone. The old secret is not deleted before a successful replacement.Storage: the project's existing secret mechanism, not a new store —
~/.shannon/config.toml(0o600) in npx mode,./.envin a clone. Empty/absent key is a normal starting state, not an error.Origins: auth on
https://www.orcarouter.ai(/authauthorize,/api/v1/auth/keysexchange), inference and discovery onhttps://api.orcarouter.ai/v1. Neither is derived from the other by hostname substitution or appending/v1.ORCA_BASE_URLis a shared self-hosted fallback withORCA_AUTH_BASE_URL/ORCA_API_BASE_URLoverrides taking precedence; non-loopback origins must be HTTPS.Model discovery and capability filtering
GET <apiBase>/modelsis the only source of truth, read with the user's key so the list is what that workspace can call. Model IDs keep theirvendor/modelnamespace verbatim. The response is bounded (10 s timeout, 512 KiB, 1000 entries, accepted item shape, supported endpoint types) so a hostile or broken response cannot consume unbounded memory or advertise a route the adapter cannot speak.Each entry point filters independently:
?capability=chat,supported_endpoint_types∩ {openai, openai-response, anthropic, gemini}, non-text endpoints excludedarchitecture.input_modalitiesmust explicitly declare the modality — undeclared fails closed?capability=embedding/ strictembeddingsmatch?capability=image/ strictimage-generationmatchopenai-videojina-rerankA live response is authoritative and the seed is never mixed into it. On discovery failure the small verified seed is used and the result is reported as degraded, so a fresh install survives an outage instead of appearing to support no models. Capability is never inferred from a model name, and the seed keeps its verified metadata — including the
low/medium/high/xhighreasoning ladder onopenai/gpt-5.5.Live catalog observed (
GET https://api.orcarouter.ai/v1/models?capability=chat, 2026-09-16, HTTP 200): 16 chat models, of which 2 declare image input.Scope
Covered AI input entry points: the scan's text prompt path (
runPiPrompt→createAgentSession) is the repository's only model-facing entry point — the prompt is a plain string and there is no image, audio, video, embedding, rerank, or attachment control anywhere in the tree. So the text/agent selector is the one wired to a real control; the other capability filters are implemented and tested in the shared catalog layer for the entry points this repository has.Multimodal is therefore not applicable to a UI here — there is no attachment control to drive a dropdown, and no rendered interface at all (no
react-dom/vue/svelte/electron/nextdependency in any trackedpackage.json, no.vue/.sveltefile, nogradio/streamlit). This is a CLI + Temporal worker repository, so the evidence is CLI/server integration, not screenshots.Testing
Every check below was run on a fresh clone of
mainat25b90b0with this patch applied, in a clean environment (env -i, freshHOME), afternpm install --no-package-lock --no-save --ignore-scripts ./apps/cli ./apps/worker.Focused suites — 146 passed, 3 skipped, 0 failed (149):
provider.test.ts+wiring.test.tspkce.test.tscatalog.test.ts+catalog.mirror.test.ts+endpoints.test.ts+selection.test.tscredentials.test.ts+env.orcarouter.test.tslive.test.tsORCAROUTER_API_KEY; skips without it)Whole suite: 9 files passed, 1 skipped; 146 passed, 3 skipped, 0 failed (149).
What the tests actually prove, beyond "it compiles":
streamSimpleand produce the same request; onlycredential.methoddiffers. One input path or a single connect button would not satisfy this.www.orcarouter.ai(or an explicit override), inference and discovery only toapi.orcarouter.ai/v1. Nothing derives one origin from the other.base64url(sha256(verifier)), S256 on every flow, constant-timestatecomparison, and assertions that the verifier never appears on the authorize URL, in an error message, or in a request URL.Skipped/limited: 3 skipped tests are the live suite in the runs where
ORCAROUTER_API_KEYis unset. The live suite ran green here with a real key.pnpmis unavailable in this environment, so dependency installation usednpm install --no-package-lock --no-save; the repository'spnpm-basedturbo run build/checkwere not run, and both packages were typechecked directly withtsc --noEmit(clean) and linted withbiome check(output identical to the unmodified base — 6 warnings, 2 infos, no errors).Live run observed: discovery returned HTTP 200 with 16 chat models; text filtering kept only chat-capable entries; the image-input subset was a strict subset of the chat list; a real completion through the registered provider returned text. Discovery origin
https://api.orcarouter.ai/v1, auth originhttps://www.orcarouter.ai.One note on
wiring.test.ts: it replaces the@earendil-works/pi-coding-agentmodule with a registry stand-in, because pi's own module graph requires Node ≥ 22.19 while the suite runs on whatevernodethe developer has. Nothing under test is replaced — the catalog is read, the alias is adopted, and the provider is registered through the sameregisterProvidercall a run makes; only the registry that call lands in is local.Provider evidence
GET https://api.orcarouter.ai/v1/models(authenticated; verified 2026-09-16)noneclient auth): https://www.orcarouter.ai/.well-known/openid-configurationOrcaRouter is an OpenAI-compatible AI gateway built for both models and agents, with adaptive routing, automatic failover, zero-markup inference, observability, guardrails, and agent-tool governance. It also runs gateway-level, zero-trust security for AI agents on the same endpoint — screening every prompt/response and governing every tool call on a default-deny basis, with no application code changes.
Discord: discord.gg/YEubt8enRA · X: https://x.com/OrcaRouter
Notes for review
This repository has no
CONTRIBUTING.md,AGENTS.md,CODEOWNERS, PR template, or test workflow, so there was no contributor contract to follow beyondCLAUDE.md. If an authentication-boundary change like this needs a maintainer sponsor or a label, could a maintainer take a look at the current head?