feat(cli): add the OrcaRouter Cyber Inference model provider

BE
12h ago
+3325-2

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/orcarouter holds 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 to auth.token in the CLI's existing devel config profile and cleared by logout.
    • Connect with OrcaRouter (OAuth 2.0 + PKCE) — Flow A loopback redirect, chosen because the CLI can bind 127.0.0.1 and receives the code without copy/paste. S256 challenge from a per-attempt crypto/rand verifier, fresh state per attempt, and a constant-time state comparison 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.
  • The two origins stay apart: consent and exchange on https://www.orcarouter.ai (/auth, /api/v1/auth/keys), inference and model discovery on https://api.orcarouter.ai/v1.
  • chainloop orcarouter login|connect|status|models|chat|logout exposes both paths; connect --no-browser prints the authorization URL for headless machines.

Models

  • The model control is a selector built from live GET /v1/models, never free text. chat filters to chat-capable models and --image-input narrows to models whose architecture.input_modalities explicitly declare image; undeclared models fail closed.
  • Recommended order: orcarouter/auto is 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/fusion is 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/models returned 21 models, an --image-input run narrowed to 3, and a chat completion returned a non-empty answer. The verification key is not entitled to orcarouter/auto or orcarouter/fusion yet (HTTP 403 model_access_denied), so the live chat used deepseek/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; TestChatModelNotAllowed covers it.

Limits

  • Only the Codex harness and the new chat command 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.