Skip to content

Add OrcaRouter as a first-class Provider with API-key and OAuth 2.0 + PKCE login - #196

Closed
clementguarino06510-glitch wants to merge 1 commit into
hypit-ai:mainfrom
clementguarino06510-glitch:orcarouter/task-6545
Closed

clementguarino06510-glitch wants to merge 1 commit into
hypit-ai:mainfrom
clementguarino06510-glitch:orcarouter/task-6545

Conversation

@clementguarino06510-glitch

Copy link
Copy Markdown

What this adds

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 · I'm an engineer on the OrcaRouter team.

This PR registers OrcaRouter as a first-class Endpoint next to HypiHub, reachable through both of the repository's existing seams:

  • @hypit/provider-orcarouter — the Provider. Inference and model discovery on https://api.orcarouter.ai/v1; authorization on https://www.orcarouter.ai. Registers orca:Generate through defineEndpointPackage, and carries its own apiKey credential slot with the OrcaRouter label.
  • @hypit/orcarouter — the Model package: a sealed chat request (catalogue model ID, prompt Text, up to 8 optional image Artifact references) and the reply Text.
  • Distribution default, docs, locales, testspackages/video-cli/src/distribution.ts lists orcarouter.default; docs/guide/providers.md, docs/guide/runtime.md and their zh counterparts describe it; packages/studio/locales/{en,zh-CN}.json carry the panel strings and pass the repository's locale parity check.

Nothing existing changes behaviour: HypiHub keeps its media-generation routes untouched.

One credential slot, two authentication entry points

Both entry points resolve to the same OrcaRouterCredential (packages/provider-orcarouter/src/credentials.ts), and token() is the only thing any downstream code sees — the chat endpoint, the catalogue reader and Studio never branch on how the key was obtained.

Entry point Adapter How it is reached
OrcaRouter - API apiKeyCredentialAdapter Paste an sk-orca-… key into the slot; the repository's own credential store (@hypit/credential-store-os / -env) holds it
OrcaRouter - Auth pkceCredentialAdapter hypit auth login orcarouter.default, or the Studio panel's authorize button, over the existing CredentialAcquisition seam

Flow choice: B (out-of-band code). Flow A (loopback redirect) is not reachable for the seam this repository already owns: packages/cli/src/oauth.ts sends the RFC 6749 response_type/client_id/redirect_uri parameter set to whatever acquireOAuthCredential is given, and /auth does not accept it. Rather than add a second, bespoke browser-callback server beside the shipped one, the adapter uses the out-of-band delivery the seam supports: the authorization URL is opened, the code is shown to the person, and the exchange happens in the same process that holds the verifier. S256 is mandatory on both the authorize URL and the exchange; no client secret and no pre-registered redirect URI are required. Flow C (device grant) is not implemented and is not a substitute for PKCE.

PKCE details, all covered by tests below: a fresh verifier and state come from randomBytes per attempt; the challenge is unpadded base64url(sha256(verifier)); the verifier never leaves the process, never enters a URL, a log or an error; the granted scope in the exchange response is read and a narrower grant is refused rather than assumed.

Credential lifecycle. A PKCE-issued key is a durable OrcaRouter API key, not a refresh token: there is no refresh path anywhere in the change. A 401 marks that exact credential generation needsReauth and later generations are unaffected, so a late failure cannot contaminate a newer login; the stored key is never deleted before a replacement succeeds. Revocation lives at the OrcaRouter console, and the panel links to it.

Model catalogue and capability filtering

The model control is a catalogue-backed selector, never a free-text field. GET /v1/models on the configured inference origin is the only source of truth, read server-side in packages/studio/src/server.ts with the account's own key, so the browser never receives the credential. packages/provider-orcarouter/src/catalog.ts holds the filters:

Control Rule
text chat/agent supported_endpoint_types contains openai / anthropic / gemini / openai-response, and none of image-generation, openai-video, jina-rerank, embeddings, rerank, moderation, audio
multimodal understanding the chat rule, plus architecture.input_modalities explicitly containing the modality the control uploads — a model that declares nothing fails closed
embedding / image / video / rerank the exact embeddings / image-generation / openai-video / jina-rerank endpoint type

Changing provider, toggling the image attachment, or changing the modality recomputes the selector's options; a selection that is no longer compatible is cleared, not silently kept. Live discovery is authoritative; when it fails the panel shows a small verified cold-start seed marked as degraded (never merged into a successful live read, never a fallback to free text), and a restored model ID is re-validated against the current compatible list before it is kept.

filterCatalog lives in one place and every entry point uses it — the Studio panel, the CLI and the tests all read the same function.

AI input entry points covered

Traced from the code, not from file names: the repository's remote generation flows through defineEndpointPackage Endpoints. The others are provider-hypihub (media-generation jobs in HypiHub's own shape, unchanged), provider-whisperx-local (local speech-to-text) and the local rendering providers, which call no remote AI service. There is no chat/completion, embedding, rerank or agent-tool call site in the tree (grep -rn "chat/completions" packages services finds only a HypiHub test fixture string).

So the entry points OrcaRouter can serve are covered, and the ones it cannot are left exactly as they were:

  • text chatorca:Generate, through the new Provider.
  • multimodal understanding (image input to chat) — the Model package's optional images port, offered only for catalogue entries that declare image input.
  • CLIhypit auth login orcarouter.default (PKCE) and hypit environment (key entry), plus the existing run command against the new Endpoint.
  • Studio — the account panel: both entry points at once, the masked key, the catalogue state, and the model selector.
  • Not routed through OrcaRouter: image/video generation, embedding and rerank. The live catalogue the account can see advertises no model whose metadata proves those endpoints, and HypiHub's media APIs speak its own job/asset shape rather than an OpenAI wire format. HypiHub behaviour is unchanged.

Evidence

Automated tests — run through the Provider's own code paths, not around them. 65 new/updated cases in the files below (all passing locally on the repository's declared Node >= 22.15 runtime):

File Cases What it proves
packages/provider-orcarouter/test/credentials.test.ts 8 A pasted key and a PKCE-issued key resolve to the same credential result; store/read/clear round-trip; generation advance; a rejected key is terminal and never refreshed; the declared exchange is exactly /api/v1/auth/keys on the auth origin; non-loopback HTTP is refused
packages/provider-orcarouter/test/connect.test.ts 6 authorize → displayed code → exchange → persist through the adapter against a local fake auth server; narrower granted scope refused; refused/expired code ends cleanly; decline and missing code make no request; a fresh verifier and state per attempt; the minted key reaches inference on the relay origin, never the auth origin
packages/provider-orcarouter/test/catalog.test.ts 6 Parsing keeps the vendor namespace and drops unusable records; per-capability filters; image input only for entries that declare it; the verified seed keeps its metadata and stays recognisable as an outage fallback
packages/provider-orcarouter/test/provider.test.ts 10 Both entry points take the same inference path; images travel as data URLs; a missing local image fails the attempt; a rejected credential marks its exact generation; malformed credentials and unnamespaced model IDs are refused before sending; auth stays on www and inference on api; the catalogue is read with the credential and filtered to the control; a failed catalogue is degraded, not repaired with the seed
packages/provider-orcarouter/test/live.test.ts 4 Live: origins on their own hosts; the live catalogue keeps vendor namespaces and equals the filter's output; a real chat completion through the implemented client; the multimodal control never exceeds the chat control and an empty one means no model declared image input
packages/cli/test/oauth.test.ts 5 The out-of-band acquisition shows the code, exchanges it with S256 and never starts a listener; a narrower granted scope is refused
packages/studio/test/accounts.test.ts, connect-lifecycle.test.ts 10 One slot for both keys; both choices always offered; a stale generation cannot finish a replaced authorization; a failed exchange stores nothing; cancel releases; degraded catalogue is reported; needsReauth is never used for discovery; pagehide releases the panel so a second authorization starts without remounting

Live checks — run against the real service through the new Provider with a configured account key: GET https://api.orcarouter.ai/v1/models returns the account's own catalogue with vendor namespaces preserved and exactly the chat models the text control offers, and a real chat completion returns non-empty text. Auth requests go to www.orcarouter.ai, inference and discovery to api.orcarouter.ai/v1.

Real UI — the screenshots below are the repository's own Studio page in a real browser (Playwright against /usr/bin/chromium, 1440x1000), driven against a live catalogue of 16 chat models:

  • auth-methods.png — API Key and Connect-with-OrcaRouter offered side by side, the stored key shown masked (sk-orca-…), controls enabled, and the key itself absent from the page text.
  • text-model-dropdown.png — the text dropdown open with all 16 live chat models.
  • multimodal-model-dropdown.png — after attaching an image: the previous text-only selection is cleared and the dropdown lists exactly the two chat entries whose catalogue records declare image input.

The three captures above are the campaign's archived evidence for this submission: 1440x1000 PNGs, sha256-bound in orca-evidence/manifest.json, produced by Playwright against the real Studio server started from this checkout. They are attached by the submission's evidence job rather than linked inline, because this integration has no URL to host them at.

Verification status

  • tsc --noEmit over the whole workspace: clean.
  • pnpm test on the declared runtime: 900 tests, 872 pass, 0 fail, 28 skipped (the skips are the repository's own environment-gated media/ffmpeg cases plus the OrcaRouter live cases when no key is configured).
  • On the Node 20 binary this image ships, 33 pre-existing test files fail in node:module's registerHooks before any assertion runs; the same 33 fail identically on the untouched base commit, so none of them come from this change.

OrcaRouter primary sources

Verified 2026-09-15:

… PKCE login

Signed-off-by: clementguarino06510-glitch <clementguarino06510-glitch@users.noreply.github.com>
@laurunshen laurunshen closed this Sep 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants