diff --git a/lib/constants.ts b/lib/constants.ts index a6012d40..3ae8f722 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -55,7 +55,18 @@ export const API_CONFIG = { // OpenRouter OPENROUTER_API_URL: 'https://openrouter.ai/api/v1/chat/completions', - OPENROUTER_DEFAULT_MODEL: 'anthropic/claude-sonnet-5', + // Must be a FREE id. OpenRouter is the FALLBACK here — reached when Groq's + // free tier is spent, i.e. only when nobody is watching. This default was + // `anthropic/claude-sonnet-5`, a premium paid model, so "the free tier ran + // out" produced a bill rather than a degraded answer — a spending decision + // made by an outage instead of by a person. (It also broke the standing rule + // that Anthropic is never a primary or fallback provider in this fleet.) + // + // `openai/gpt-oss-20b:free` reports pricing.prompt = 0 in OpenRouter's own + // catalogue (checked 2026-08-16) and was probed live for tool support on + // 2026-08-15. Free catalogues rot — when it does, replace it with another + // `:free` id, never by dropping the suffix. + OPENROUTER_DEFAULT_MODEL: 'openai/gpt-oss-20b:free', // Token limits MAX_CONTEXT_CHARS: 8000, diff --git a/tests/__tests__/lib/free-fallback.test.ts b/tests/__tests__/lib/free-fallback.test.ts new file mode 100644 index 00000000..2f4e9632 --- /dev/null +++ b/tests/__tests__/lib/free-fallback.test.ts @@ -0,0 +1,27 @@ +/** + * The OpenRouter fallback must never be a paid model. + * + * Pinned as a test rather than trusted to review because this path is invisible + * in normal operation: OpenRouter is only reached when Groq's free tier is + * spent, so a paid id there bills precisely when nobody is watching. The + * previous default was `anthropic/claude-sonnet-5` — a premium model, and a + * breach of the standing fleet rule that Anthropic is never a primary or + * fallback provider. + * + * The rule mirrors `modelCost` in the shared `ai-ration` package: a routed id + * (`vendor/model`) is free only with the `:free` suffix. That suffix is the + * entire difference between free routing and a per-call charge for identical + * weights, which is why an id one token away from correct kept slipping through. + */ +import { API_CONFIG } from '@/lib/constants'; + +describe('free-first fallback', () => { + it('uses a :free OpenRouter model as the fallback default', () => { + expect(API_CONFIG.OPENROUTER_DEFAULT_MODEL).toMatch(/:free$/); + }); + + it('never falls back to Anthropic', () => { + // Standing fleet rule: Anthropic is paid and is not a fallback anywhere. + expect(API_CONFIG.OPENROUTER_DEFAULT_MODEL).not.toMatch(/anthropic/i); + }); +});