mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-09-15 23:32:46 +00:00
The first feature payoff of the gateway refactor series (#481–#496): OpenRouter lands as **pure configuration over the `openai_compatible` codec** — no new adapter, no new `AdapterKind`, no OpenRouter codec fork. Redone from #438, which prototyped this pre-refactor as ~2,500 lines including a dedicated adapter and parallel codec plumbing; this PR's fabro-llm diff is the usage-superset decode plus a TOML file. ## What's here (3 commits) **Per-model `billing_policy` override (fabro-model)** — a model row may override its provider's billing family: the aggregator case, where Claude served through an OpenAI-compatible provider bills Anthropic-style cache reads/writes. `pricing_for`/`billing_facts_for` and the resolved `Route` read the model-effective policy; unknown passthrough model ids keep the provider policy. Pinned by a pricing test (cache writes bill at 1.25× input under the override, $0 under the provider's OpenAI default). **Aggregator usage superset in the `openai_compatible` codec** — the wire usage struct gains tolerant optional fields: - `prompt_tokens_details.cached_tokens` / `cache_write_tokens` and `completion_tokens_details.reasoning_tokens` normalize into their disjoint `TokenCounts` buckets with the same subtraction convention as the `openai_responses` codec - in-band `usage.cost` (OpenRouter returns it on every response) surfaces as `Response.cost_usd` with `cost_source = authoritative`, on both blocking and streamed responses — #494's client-side estimate stamping already defers to it by construction - **deliberate behavior change owned here**: compat providers that report cached-token details now see them split out of `input_tokens` (previously ignored — the wire pin placed in PR 0 anticipating exactly this change flips, and two new OpenRouter-shaped wire pins land) **The provider package** — `openrouter.toml` (disabled by default, the Ollama opt-in pattern; curated vendor-namespaced model list; Claude rows set `billing_policy = "anthropic"`; attribution headers deliberately not sent unless the operator opts in via `extra_headers`), `OPENROUTER_API_KEY` env/secret registry entries, a gitleaks rule for `sk-or-v1-` keys, a live e2e test asserting authoritative cost, and docs (integration guide + models concept + config reference). ## Deliberate scope cuts (fidelity follow-ups, per the plan) - `reasoning_details[]` parse + verbatim multi-turn echo, `cache_control` multipart emission, `provider`/`native_finish_reason` field reads — the new wire pin proves they're tolerated and ignored today - Typed reasoning-param-style / routing codec params — no catalog row can request reasoning effort yet (no `controls.reasoning_effort` declared), and routing prefs already pass through `provider_options.openrouter` verbatim via the existing adapter-name-keyed merge; typed params land when an operator-level knob actually needs them - The OpenRouter Anthropic skin (`/api/v1/messages`) — a future pure config row pairing the existing `anthropic_messages` codec with bearer transport ## Verification - `cargo nextest run --workspace --no-fail-fast`: 6724 passed; only the known 5 pre-existing environment-dependent fabro-workflow failures (identical on main) - Wire snapshots: one deliberate flip (`decode_usage_ignores_token_details` → `decode_usage_parses_token_details`) + two new OpenRouter pins (blocking cost/cache-write, streamed cost); all other snapshots unmodified - clippy `-D warnings` + pinned-nightly fmt clean - Builtin catalog unchanged for existing providers: OpenRouter is `enabled = false`, so the #493 route-equivalence table is untouched Credit to #438 for the provider research, catalog curation, gitleaks rule, and docs structure. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| crates | ||
| packages/fabro-api-client | ||