feat: add OrcaRouter as a first-class provider with API-key and PKCE login - #1866
Open
beilsteinernest215-cmd wants to merge 1 commit into
Open
beilsteinernest215-cmd wants to merge 1 commit into
beilsteinernest215-cmd wants to merge 1 commit into
Conversation
…login Signed-off-by: beilsteinernest215-cmd <beilsteinernest215-cmd@users.noreply.github.com>
This branch has not been deployed
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 this adds
This adds OrcaRouter as a first-class, named model provider for the action, with two independent ways to sign in. OrcaRouter is an OpenAI-compatible AI gateway that routes many providers behind one endpoint. I'm an engineer on the OrcaRouter team.
Neither entry point is a fallback for the other — a user who already holds a key pastes it, and a user who does not runs the connect command. Both land on the same credential the rest of the code already understands.
OrcaRouter — APIorcarouter_api_key(sk-orca-…)ORCAROUTER_API_KEYOrcaRouter — Authorcarouter_auth: truebun base-action/src/orcarouter/setup-cli.ts --connect, OAuth 2.0 + PKCE (S256)OrcaRouter is only used when it is explicitly asked for (
orcarouter_provider,orcarouter_authororcarouter_api_key). A bareORCAROUTER_API_KEYin the ambient environment is deliberately not enough — this action is often run with credentials that belong to a different tool, and silently re-routing an unrelated run to a new gateway would be a bad trade for convenience.base-action/test/orcarouter/provider.test.tspins that behaviour.The credential is a seam, not a fork
base-action/src/orcarouter/credentials.tsexposes oneOrcaRouterCredentialSourceinterface.apiKeyCredentialSource()andpkceCredentialSource()are the two adapters; everything downstream — provider wiring inprovider.ts, model discovery incatalog.ts, and terminal 401 recovery inconnect.ts— consumes a resolved credential and never learns which adapter produced it.provider.test.tsasserts both adapters return the same result shape and reach the same inference endpoint with the same wire.PKCE follows Flow B (out-of-band code) rather than Flow A. This is a GitHub Action: its natural runtime is a headless runner with no browser and no loopback listener, so an
oobcallback is the only shape that works everywhere the action runs.createPkceAttempt()mints a fresh 32-byte verifier and a 16-bytestatefromcryptoon every attempt, sendsbase64url(sha256(verifier))as an unpadded challenge with a mandatoryS256method, and keeps the verifier in-process until the exchange. When a callback echoes state it is compared in constant time; a mismatch aborts before the code is redeemed. Denial, state mismatch, an expired or reused code, a 400 method downgrade, a 403 scope shortfall, 429 and network errors each end the attempt with an actionable message rather than a hang or a retry loop.Two origins, never derived from each other
Authentication and inference are separate public origins and are configured separately:
https://www.orcarouter.ai/authhttps://www.orcarouter.ai/api/v1/auth/keyshttps://api.orcarouter.ai/v1ORCA_AUTH_BASE_URLandORCA_API_BASE_URLoverride a sharedORCA_BASE_URL; explicit values win. Non-loopback origins are forced to HTTPS. No code path derives one origin from the other by swapping a hostname or appending/v1—endpoints.test.tshas a test named for exactly that mistake. Verified on 2026-09-28:POST https://www.orcarouter.ai/api/v1/auth/keysanswers403 {"error":"Invalid code or code_verifier"}for a fake code, whilePOST https://api.orcarouter.ai/v1/auth/keysanswers301rather than serving the endpoint.Model selection comes from the catalog, not from a text box
When the provider is OrcaRouter the model control is populated from
GET /v1/modelson the configured origin, using the user's own key as the Bearer token. The live response is authoritative; the small hand-verified seed incatalog.tsis used only when discovery fails, is marked degraded, and is never merged into a live result. Model ids keep theirvendor/modelnamespace verbatim.Each entry point filters the same catalog by its own requirement, fail-closed:
?capability=chat, must advertise one ofopenai/anthropic/gemini/openai-response, and is excluded if it is a dedicatedimage-generation/openai-video/jina-rerankmodel;architecture.input_modalitiesmust name the modality the entry point actually accepts; an undeclared modality fails closed;Capability is never guessed from a model's name. When the provider changes, or an attachment or task type changes, the options handed to the selector are recomputed and a selection that is no longer compatible is cleared with a prompt instead of being silently kept. The verified seed keeps its metadata, including the reasoning ladder on
openai/gpt-5.5(low/medium/high/xhigh, defaultmedium).Coverage
This action has one AI entry point: the agent run itself, configured through
action.ymlinputs and written intosettings.envbysetup-claude-code-settings.ts. That is the surface wired here, for both credential adapters.validate-env.tslearns about the provider and reports the missing-credential case clearly,sanitizer.tsredactssk-orca-…tokens from logs, andsetup-cli.tsadds--connect/--logout/--statusso both entry points are reachable from a terminal.Testing
Run against a clean checkout of this branch with a fresh install:
bun test: 1078 pass, 4 skip, 0 fail across 64 files;base-action/test/orcarouter/live.test.ts, with a key in the environment: 4 pass, 0 fail — the 4 skips above are these tests skipping when no key is present in the ambient environment;bun run format:check— clean;bun run typecheck— clean.The live run is the one worth quoting: it discovers 16 text chat models from
GET https://api.orcarouter.ai/v1/models, narrows them to 2 once an image input is required, and completes a real inference call through the provider's own environment againsthttps://api.orcarouter.ai/v1/messagesondeepseek/deepseek-v4-pro. The catalog and inference assertions go through the code added here — they are not a standalonecurl. No test, log line or error message contains a credential;connect.test.tsandprovider.test.tsassert that for the PKCE and API-key paths respectively.Why there are no screenshots
This repository has no rendered interface to screenshot: no
react-dom/vue/svelte/next/electrondependency in anypackage.json, no tracked.vueor.sveltefile, and the only HTML in the tree is documentation underdocs/. The integration is therefore demonstrated through the CLI surface and the test suite above.Provider evidence
All URLs below were fetched and confirmed reachable on 2026-09-28.
https://api.orcarouter.ai/v1/models(openai-compatible, Bearer auth; returns the workspace's callable models).https://www.orcarouter.ai/auth(consent screen); exchange —https://www.orcarouter.ai/api/v1/auth/keys.https://www.orcarouter.ai/.well-known/openid-configurationadvertisesauthorization_endpoint,token_endpoint,code_challenge_methods_supported: [S256, plain]andtoken_endpoint_auth_methods_supported: [none], i.e. PKCE without a client secret. This implementation pinsS256; it never sendsplain.https://www.orcarouter.ai/console/authorized-apps.https://www.orcarouter.ai/pricingandhttps://www.orcarouter.ai/providers/anthropic.Not implemented
Flow A (loopback redirect) and Flow C (device grant) are both valid flows that this change does not add. Flow B covers the action's headless runtime; device grant would be a useful addition for interactive terminals and is left as a follow-up rather than folded in untested here.