feat(cli): add the OrcaRouter Cyber Inference model provider
What this adds
Chainloop Trace records AI coding sessions, and its Codex harness is the one place in the tree that names a model provider (app/cli/internal/trace/e2e/codex.go builds model_providers.openrouter today). This adds OrcaRouter Cyber Inference as a named, first-class provider next to it, with both credential paths in one place. OrcaRouter is an OpenAI-compatible AI gateway that routes many providers behind one endpoint. OrcaRouter's cyber tier is aimed at security teams doing authorized, scoped work: https://www.orcarouter.ai/cyber and https://www.orcarouter.ai/models?tier=cyber.
Provider and credentials
internal/orcarouterholds the provider definition (BaseURL = "https://api.orcarouter.ai/v1",EnvKey = "ORCAROUTER_API_KEY"), the credential seam, its lifetime rules, the model catalog with capability filters, and the chat client.- Two adapters end at the same durable OrcaRouter API key, and nothing downstream knows which one produced it:
- API key — a pasted
sk-orca-…key, stored next toauth.tokenin the CLI's existing devel config profile and cleared bylogout. - Connect with OrcaRouter (OAuth 2.0 + PKCE) — Flow A loopback redirect, chosen because the CLI can bind
127.0.0.1and receives the code without copy/paste. S256 challenge from a per-attemptcrypto/randverifier, freshstateper attempt, and a constant-timestatecomparison before the code is used. The minted key is durable, not a refresh token, so a 401 marks exactly the account/generation that made the request as needing reauthentication; there is no fake refresh grant.
- API key — a pasted
- The two origins stay apart: consent and exchange on
https://www.orcarouter.ai(/auth,/api/v1/auth/keys), inference and model discovery onhttps://api.orcarouter.ai/v1. chainloop orcarouter login|connect|status|models|chat|logoutexposes both paths;connect --no-browserprints the authorization URL for headless machines.
Models
- The model control is a selector built from live
GET /v1/models, never free text.chatfilters to chat-capable models and--image-inputnarrows to models whosearchitecture.input_modalitiesexplicitly declare image; undeclared models fail closed. - Recommended order:
orcarouter/autois the default; OrcaCyber Ultra (orcacyber-ultra-1.0, security code scanning and multi-step reasoning over large repositories) and OrcaCyber Zero (orcacyber-zero-1.5, OrcaCyber-harness driven) are the dedicated cyber models for teams with business verification;orcarouter/fusionis the general fallback. The small cold-start seed exists only for outage; a successful live fetch is authoritative.
Trace harness
The Codex harness accepts orcarouter:<model id> and passes a named model_providers.orcarouter={name=…,base_url=…,env_key=…}, so a Trace session can run on orcarouter/auto or a cyber model. No other provider's behavior changes.
Verification
All checks run through the provider code in this patch in a scrubbed environment:
go test ./internal/orcarouter/— provider, both credential adapters, PKCE (verifier/challenge/state, authorize URL, exchange path and body, rejection, state mismatch, code reuse, scope read back from the response, 401 generation handling, 429, network failure), catalog and capability filters, error classification. Tests assert the key and verifier never appear in errors or logs.go test ./app/cli/cmd/ -run 'TestOrcaRouter|TestDescribeConnectError'— command wiring for both paths.go test ./app/cli/internal/trace/e2e/ -run 'TestCodex…'— the harness still emits the OpenRouter definition unchanged and now emits the OrcaRouter one.- Live: through the implemented client,
GET /v1/modelsreturned 21 models, an--image-inputrun narrowed to 3, and a chat completion returned a non-empty answer. The verification key is not entitled toorcarouter/autoororcarouter/fusionyet (HTTP 403model_access_denied), so the live chat useddeepseek/deepseek-v4.1-flash— a connectivity probe only, not a user-facing recommendation. That 403 is handled explicitly as a model-permission error rather than a rejected credential, since a new login would not fix it;TestChatModelNotAllowedcovers it.
Limits
- Only the Codex harness and the new
chatcommand are model entry points in this tree, and both are text-only, so no multimodal selector is wired to a UI. - Flow C device grant is not implemented; Flow A covers headless use via the printed URL.
I'm an engineer on the OrcaRouter team.
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.