feat: add OrcaRouter provider with API-key and PKCE login - #161
Open
lovejones2914-spec wants to merge 1 commit into
Open
feat: add OrcaRouter provider with API-key and PKCE login#161lovejones2914-spec wants to merge 1 commit into
lovejones2914-spec wants to merge 1 commit into
Conversation
Signed-off-by: lovejones2914-spec <lovejones2914-spec@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 to
WHartTest, with two explicit authentication choices and a capability-filtered
model catalog.
OrcaRouter 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
WHartTest_Django/langgraph_integration/models.py(
LLMConfig.PROVIDER_CHOICES):orcarouter— OrcaRouter - API (user pastes an existingsk-orca-…key)orcarouter_oauth— OrcaRouter - Auth (browser login, OAuth 2.0 + PKCE)https://api.orcarouter.ai/v1(OpenAI-compatible wireformat, so the existing
ChatOpenAIadapter is reused unchanged)WHartTest is self-hosted software whose install address differs on every
deployment, and the browser that approves the request is frequently not on the
machine running Django, so a loopback listener on the Django host would often
be unreachable. Flow B needs no predictable address and no callback
registration.
(
knowledge/models.py), reusing the project's existingCustomAPIEmbeddingsadapter against
https://api.orcarouter.ai/v1/embeddings.Affiliation disclosure: I'm an engineer on the OrcaRouter team. This change
is made on behalf of OrcaRouter.
How the credential works
The key belongs to the user, not to this project: it is billed to their
OrcaRouter account, listed in their console, and revocable by them at any time.
No client secret is involved — PKCE binds the authorization code to this
process, so an intercepted code cannot be redeemed by anyone else.
Both entries are adapters on one small credential seam
(
CredentialProvider→CredentialResultinlanggraph_integration/orcarouter.py). The pasted-key adapter and the PKCEadapter produce the same object, so the LLM adapter, the model catalog and every
AI entry point are indifferent to where a credential came from.
LLMConfig.api_keycolumn — the same column the other providers use, whichLLMConfigSerializeralready markswrite_onlyand never returns over theAPI. No new secret store was introduced, and no plaintext side file exists.
refresh token. Nothing schedules a proactive refresh and no refresh grant is
invented.
classify_auth_failure()treats401/403as terminalreauthentication and
orcarouter.should_transition_to_needs_reauth()isgeneration-guarded, so a late failure from an old request can never mark a
newly reauthorized credential as broken.
secrets.token_bytesper attempt, never enter a URL, log, telemetry or error string;
mask_secret()/scrub()back the redaction path.PKCE specifics
base64url(sha256(verifier)), unpadded) on every flow, includingFlow A-shaped usage, because the consent screen also lets a user choose
"show me a code".
stateis compared in constant time (hmac.compare_digest) before a code isredeemed, and the attempt is single-use.
https://www.orcarouter.ai/authand exchange athttps://www.orcarouter.ai/api/v1/auth/keys; inference and discovery athttps://api.orcarouter.ai/v1. The two origins are never derived from oneanother;
ORCA_BASE_URLis the shared self-hosted fallback with explicitORCA_AUTH_BASE_URL/ORCA_API_BASE_URLoverrides winning. Non-loopbackorigins must be HTTPS.
scopeis read back and enforced: a grantnarrower than
apiis refused with an actionable message instead of beingassumed.
Model discovery and capability filtering
The model picker is generated from the live catalog at
GET https://api.orcarouter.ai/v1/models, fetched server-side — the browsernever holds an API key. The backend returns only minimal model metadata
(
id,name,context_length,input_modalities,endpoint_types,reasoning_efforts), and the UI renders a searchablerole="listbox"picker.Model IDs keep their
vendor/modelnamespace verbatim.The list endpoint advertises
supported_endpoint_types; input modalities live onthe per-model detail record, so modalities are enriched from
GET /v1/models/{id}under a bounded worker pool and wall-clock budget, thencached with a TTL.
Filtering is capability-specific and fails closed:
openai/anthropic/gemini/openai-response;image-generation,openai-video,jina-rerankexcludedarchitecture.input_modalitiesmust declare the attached non-text modality?capability=embedding/ strictembeddingsendpointimage-generationendpointopenai-videoendpointjina-rerankendpointA model that declares no modality metadata never appears in a multimodal picker.
Changing the provider, or toggling the multimodal switch, recomputes the options;
a selection that is no longer compatible is cleared with a visible prompt rather
than silently retained. Capability filtering happens on the options handed to the
selector, so a send-time guard would be a second layer, not a substitute.
On live-discovery failure the project keeps a small verified seed (the five cited
models, with
openai/gpt-5.5retaining its low/medium/high/xhigh reasoningladder) and marks the result
degradedso the UI says so. A successful liveresult is authoritative and never mixes the seed in. There is no fallback to free
text.
AI input entry points covered
LLMConfig, so they route through the new provider automaticallyorcarouterembedding service)xinferenceand a generic custom URL), so there is no seam to add a named entry to.CAPABILITY_RERANKis implemented and tested in the catalog layer so it can be wired if such an entry point is added.Testing
Backend (SQLite; the repo's own PG-only migrations cannot run on SQLite and no
PostgreSQL server is available in this environment, so the schema is built from
the models via a throwaway settings module outside the repo):
The 61 tests cover: PKCE verifier/challenge/state generation and freshness,
authorize-URL shape (auth origin,
callback_url=oob, S256, verifier absent fromthe URL), the full authorize → code → exchange → persist flow against a local
fake auth server asserting the exact
/api/v1/auth/keyspath and request body,denial/reused-code/expired (
403), wrong challenge method (400),429, scopedowngrade, timeout and network failure, constant-time state comparison,
single-use attempts, cancel, origin policy (separate defaults, explicit overrides
winning, HTTP only for loopback), redaction, capability filters for text-only /
image-input chat / embedding / image / video / rerank with modality fail-closed,
bounded/oversized catalogs, degraded fallback with reasoning metadata intact,
live results never mixing in the seed, generation-safe
needs_reauth, that nofake refresh grant is modelled, and that the browser never receives the key.
Fixtures use only fake keys and fake codes.
Frontend:
npx vue-tsc --noEmitpasses clean.npm run buildfails with 9pre-existing TypeScript errors in files this change does not touch — verified
by running
vue-tsc -bagainst a pristineHEADcheckout, which reports theidentical 9 errors (
TestCaseMindmap.vue,DatabaseConfigPanel.vue,InterfacesPanel.vue,ChatMessages.vue, and a duplicate key insrc/i18n/index.ts). This change introduces zero new build errors.Live verification
create_llm_instance(config)with the OrcaRouter provider returnedChatOpenAIwithbase_url=https://api.orcarouter.ai/v1, and a realinvoke()returnedORCA_OK.GET https://api.orcarouter.ai/v1/models?capability=chatreturned 167 chat models;
?capability=embeddingreturned 5;?capability=imagereturned 8.which declare
imageinput; a text-only model present in the chat list(
deepseek/deepseek-v4-pro) is absent, andopenai/gpt-5.5is retained.GET /api/lg/providers/(orcarouter,orcarouter_oauth) andorcarouterappears in the knowledge-base embeddingservices list.
UI evidence (real automation, not mockups)
Playwright drove the real UI (Vite dev server + Django backend, a real login
through the actual login form, and the real provider config modal). 29/29
assertions passed; the run is reproducible with
python3 e2e_orcarouter.py.auth-methods.png— the OrcaRouter config panel shows both entries atonce: the API-key entry (password-type, masked) and the "Connect with
OrcaRouter" PKCE entry, both enabled. Asserts the key control is
type="password".text-model-dropdown.png— the real text picker expanded with 167 itemsfrom the live catalog.
multimodal-model-dropdown.png— after attaching an image, the pickerreopens with 122 image-capable models.
The dropdown panel is asserted from the DOM:
role="listbox"present,aria-expanded="true"on the trigger, right-edge delta vs. the trigger0.00px, opaque background (
rgb(255,255,255)), and a visible1px solid rgb(229,230,235)border. Screenshots are 1600×1000 and contain onlythe dedicated test account, with no API key, token or personal data visible.
Not implemented
Flow C (device grant) is not implemented; Flow A (loopback) is not used, for the
reason given above. Neither is required for this client.
Provider evidence
Verified on 2026-09-11 by direct request:
Authorization: Bearer sk-orca-…):https://api.orcarouter.ai/v1/chat/completions— exercised live, see above.GET https://api.orcarouter.ai/v1/models(returns 200; accepts anoptional
?capability=filter) — the catalog source used by this PR.https://www.orcarouter.ai/authandPOST https://www.orcarouter.ai/api/v1/auth/keys; notarized live by thediscovery document
https://www.orcarouter.ai/.well-known/openid-configuration(200,
code_challenge_methods_supported: ["S256","plain"]).Note
https://api.orcarouter.ai/v1/auth/keysis a 404 — the relay is at/v1and the auth endpoints are not.
https://www.orcarouter.ai/console/authorized-apps(200) — revoking the appdeletes every key it was issued, which is why a
401is treated as terminalreauthentication rather than a retry.
https://docs.orcarouter.ai(308 redirect, exists).Maintenance owner: the OrcaRouter team (this contribution is made on behalf
of OrcaRouter); verification date 2026-09-11.
Not verified, therefore not claimed: I could not confirm a terms-of-service
URL or a named operating legal entity (
/terms,/privacyand/legalallreturn 404 from this environment), nor a separate aggregator routing/resale
authorization document. I am flagging that gap rather than asserting something I
did not check; happy to supply these from the maintainers' preferred source.
Notes
hashlib,hmac,base64andsecretsfrom the standard library, and the HTTP calls reuse the
requestsdependencythe project already ships.
configuration, licence, release process or unrelated formatting was changed.