Skip to content

feat(orcarouter): OAuth 2.0 + PKCE connect and a live chat catalog alongside the API key - #6867

Merged
Hmbown merged 1 commit into
codewhale-hq:wave/0.10.1-nextfrom
hodeswildsmith455-boop:orcarouter/task-8228
Oct 6, 2026
Merged

Hmbown merged 1 commit into
codewhale-hq:wave/0.10.1-nextfrom
hodeswildsmith455-boop:orcarouter/task-8228

Conversation

@hodeswildsmith455-boop

Copy link
Copy Markdown

What and why

Codewhale already routes OrcaRouter as a first-class provider — kind, config, defaults, and pricing surface all landed earlier — but it only accepted a pasted ORCAROUTER_API_KEY. This adds the second credential entry point an OrcaRouter user can be missing (a browser sign-in), and makes the model control render the live OrcaRouter catalog instead of trusting the provider row alone.

  • Provider: orcarouter (crates/config/src/provider_kind.rs), inference at https://api.orcarouter.ai/v1
  • API-key choice: codewhale auth set --provider orcarouter, /provider, or ORCAROUTER_API_KEY, stored in the existing orcarouter secret-store slot
  • Connect flow: OAuth 2.0 + PKCE, Flow A (loopback redirect), chosen because this client runs on the user's own machine and can bind 127.0.0.1; Flow B/C were not needed and are not implemented
  • crates/tui/src/oauth.rs — one credential seam, two adapters
  • crates/tui/src/client.rs — chat-scoped, fail-closed catalog filter
  • docs/PROVIDERS.md, docs/features.toml (orcarouter-sign-in), all shipped locales

Affiliation disclosure: I'm an engineer on the OrcaRouter team. This work is made on behalf of OrcaRouter, in coordination with 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 and no pre-registered redirect URI; PKCE binds the auth code to this process, so an intercepted code cannot be redeemed by anyone else.

Both entry points are adapters on one seam. OrcaCredential::from_api_key and exchange_orcarouter_code produce the same value, and activate_orcarouter_credential stores either under the existing orcarouter slot with auth_mode = "api_key" metadata. Nothing downstream, including catalog discovery, can tell which adapter ran. A PKCE-issued key is a durable API key, not refreshable token material, so OrcaRouter is deliberately not one of the owned OAuthProvider generations: Codewhale reuses the stored key across restarts and never sends a refresh grant, and a 401 marks only the credential generation that actually failed as needing re-authentication.

Authentication and inference stay on separate origins: authorize at https://www.orcarouter.ai/auth, exchange at POST https://www.orcarouter.ai/api/v1/auth/keys (never /v1/auth/keys on the API host), models and chat at https://api.orcarouter.ai/v1. Self-hosted deployments can override each origin (ORCA_AUTH_BASE_URL, ORCA_API_BASE_URL / ORCAROUTER_BASE_URL); the explicit value wins. Remote origins must be HTTPS; plain HTTP is accepted only on loopback. S256 is used with a fresh verifier and state per attempt, and the callback state is compared in constant time before the code is used.

Model discovery and capability filtering

crates/tui/src/client.rs requests GET https://api.orcarouter.ai/v1/models?capability=chat with the configured key. Rows are kept only when supported_endpoint_types includes openai, anthropic, gemini, or openai-response; image-generation, openai-video, jina-rerank, and embeddings rows are dropped instead of being guessed from a model name. A row that declares no endpoint type fails closed out of the chat selector rather than being served as text. architecture.input_modalities drives the image-input selector: only rows that explicitly declare image join it. Model ids keep their vendor/model namespace (for example deepseek/deepseek-v4-pro or the orcarouter/auto router).

Testing

  • python3 scripts/check_orcarouter_contract.py — PASS. Reads the sources named in the plan plus the live gateway: 16 chat rows, 2 image-input, live chat probe HTTP 200.
  • cargo test -p codewhale-tui --lib --locked orcarouter — 18 passed, 1 ignored, 0 failed. Covers the API-key adapter shape, the shared credential result produced by both adapters, the S256 verifier/challenge pair, the authorize URL, the exchange origin/path/body, granted-scope handling, callback state mismatch and denial, terminal 400, secret redaction, the separate HTTPS origins, and an end-to-end connect test (orcarouter_connect_adapter_runs_authorize_callback_exchange_end_to_end) over a local fake auth server using fake key and code only, asserting the verifier never appears in logs or errors.
  • crates/tui/src/client/test_cases_08.rs — catalog tests for chat rows and their image-input fact, a fail-closed empty-roster case, malformed rows and 401 typing, plus the opt-in live test orcarouter_live_catalog_and_chat_through_the_provider_path.

Limitation recorded honestly: the full Codewhale suite was not run end to end in this session — the codewhale-tui build alone runs far past the session budget. The OrcaRouter-focused checks above are what was run and observed.

UI

Local GUI evidence was generated with the repository's own provider settings surface driven by Playwright against the live catalog:

auth methods
chat model dropdown
image-input model dropdown

  • auth-methods.png — the API-key and "Connect with OrcaRouter" entries side by side, key masked
  • text-model-dropdown.png — the open chat model selector (16 rows)
  • multimodal-model-dropdown.png — the open image-input selector (2 rows)

Provider evidence

  • Inference / OpenAI-compatible endpoint: OrcaRouter documents base_url = https://api.orcarouter.ai/v1 for the OpenAI SDK.
  • Authorization: https://www.orcarouter.ai/auth; exchange POST /api/v1/auth/keys; revocation and account management at https://www.orcarouter.ai/console/authorized-apps.
  • Model list: GET https://api.orcarouter.ai/v1/models (verified live in this session).
  • Billing / routing authorization: OrcaRouter pricing states the zero-markup pass-through model, and the OrcaRouter home page lists providers@orcarouter.ai for provider onboarding. The maintainers may ask me for the current terms URL and operating legal entity if those are required for registry promotion.
  • Maintenance owner: OrcaRouter team (providers@orcarouter.ai); verification date 2026-10-05.

I'm an engineer on the OrcaRouter team. OrcaRouter is an OpenAI-compatible AI gateway that routes many providers behind one endpoint: OrcaRouter.

Issue

No-Issue: adds the OAuth 2.0 + PKCE credential path and live catalog filter to the OrcaRouter provider that already ships in main; no tracking issue exists for this follow-up.

…ongside the API key

Signed-off-by: hodeswildsmith455-boop <hodeswildsmith455-boop@users.noreply.github.com>
@github-actions github-actions Bot added the contribution-gate Author not yet in .github/APPROVED_CONTRIBUTORS; a maintainer grants access with /lgtm label Oct 5, 2026
@Hmbown Hmbown added this to the v0.11 milestone Oct 5, 2026

@Hmbown Hmbown left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you so much for this!!!

@Hmbown
Hmbown changed the base branch from main to wave/0.10.1-next October 6, 2026 05:52
Hmbown pushed a commit that referenced this pull request Oct 6, 2026
Reconcile the complete contributions from #6832 (@aboimpinto), #6867
(@hodeswildsmith455-boop), and #6805 (@LIghtJUNction) with the current
Engine, provider identities, reviewed-plugin policy, and task lifecycle.
Original contributor histories are recorded by subsequent resolved merges.

Fix the already integrated contributor cases: snapshot corruption is an
explicit unavailable/error result (#6817), malformed locales cannot select
an incidental script (#6860), image metadata uses the exact uploaded bytes
and respects available decoders (#6858), automation deletion waits for
actual scheduler reconciliation (#6864), and blocking trust/skill reads stay
off the async executor (#6869). Preserve #6857's compaction regression.

Extend #6872's human-wait lifecycle guard to approval/elevation cards;
retire only the matching ended parent request and refresh activity only
after a delivered decision. Enforce configured finite approval deadlines in
the Engine, including deadline/cancellation races and durable receipts.

Serialize Native Windows ACL admission/retirement across Core processes
with a logon-scoped kernel mutex, preserving exact SID/object validation.
Add an actual child-process lock test; serialize DSH host tests in the
existing extension-host lane. Windows execution proof remains hosted CI.

Reconcile the vendored computer-use plugin with canonical main a656f67455fc
while preserving Core's 0.12.1 embedding contract. Canonical b47/a656 tree
passed Ubuntu/macOS/Windows source and package gates, including 28/28
Windows-focused tests and the controlled desktop fixture; this is separate
from the new Engine head's CI verdict.

Partial adaptation of the discovery-cache priority portion from PR #6393
by @AdityaVG13 (original ac33dd4). Preserve the best match under count
and byte limits without importing the unfinished echolocation/fork design;
the broader draft remains open.

Validation:
- npm test: 1286 passed, 0 failed, 7 skipped; web 767/767.
- npm run check:web: lint, typecheck and production build passed.
- Affected Rust selection: 801/803 initially passed; the two fixture/lifecycle
  expectation failures were corrected and each passed a focused rerun.
- Additional focused Rust: approval 28/28, discovery cache 11/11,
  OrcaRouter synthetic catalog 3/3, and 33/33 lifecycle/API/routing checks.
- Final CI-repair selection: 37/39 initially passed; the BMP feature-proxy
  and feature-registry summary failures were corrected; both corrected tests passed (2/2, 0 failures).
- Qualified Clippy: six packages, all targets/all features, passed with
  the CI style allowances. Portable no-default-feature check passed;
  portable policy verifier 6/6 passed; formatting and diff checks passed.
- Runtime contract: 55 measured metrics passed after explicit remeasurement;
  all 21 structural identities were unchanged. Twelve byte/token-estimate
  budgets account for bounded child-wait disclosure and contributor locale
  descriptions; no runtime field/prompt was removed to lower the budget.
- Persistence budget was not qualified locally: the checker requires a
  clean tree and this shared checkout retains an unrelated operator file.
  Clean hosted CI, fresh stamped build and real DeepSeek TUI acceptance
  remain required before the integration PR can merge to main.

Refs #6872, #6843, #6795.

Co-authored-by: Paulo Aboim Pinto <paulo.aboim.pinto@gmail.com>
Co-authored-by: hodeswildsmith455-boop <hodeswildsmith455-boop@users.noreply.github.com>
Co-authored-by: LIghtJUNction <lightjunction.me@gmail.com>
Co-authored-by: AdityaVG13 <adityavgcode@gmail.com>
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
@Hmbown
Hmbown merged commit c147152 into codewhale-hq:wave/0.10.1-next Oct 6, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contribution-gate Author not yet in .github/APPROVED_CONTRIBUTORS; a maintainer grants access with /lgtm

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants