diff --git a/CHANGELOG.md b/CHANGELOG.md index b8c311c..c12d359 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,116 @@ # Changelog +## Franklin Agent 3.40.0 — priced but unreachable: the catalog gets a front door + +**3.39.0 taught Franklin what the new models cost. It never taught anyone how +to ask for them.** That release added eleven chat models to `src/pricing.ts` +and stopped there — no shortcut, no picker row, no router wiring. The models +were live on the gateway, correctly priced, and reachable only by typing the +full `provider/model` id from memory. This release finishes the job. + +**38 new shortcuts (107 → 145), covering every model 3.39.0 priced:** + +| family | now reachable as | +|---|---| +| GPT-5.6 pro reasoning tier | `sol-pro`, `terra-pro`, `luna-pro` | +| GPT-5.5 Pro / ChatGPT default | `gpt-5.5-pro`, `chatgpt` / `instant` | +| Gemini 3.6 Flash + the Flash Lite line | `flash` (now 3.6), `flash-lite`, `gemini-3.1-flash-lite` | +| Qwen3.7 Plus / Flash | `qwen-plus`, `qwen-flash` | +| Tencent, Xiaomi (new providers) | `hy3` / `tencent`, `mimo` / `xiaomi` | +| GPT-4o + 4.1 mini/nano, o3-mini | `4o`, `gpt-4o-mini`, `gpt-4.1-mini`, `o3-mini` | + +Worth knowing about the pro tier: **Terra Pro ($1/$6) and Luna Pro +($0.1/$0.6) undercut their own base tiers** while adding pro reasoning mode. +Sol Pro matches Sol exactly. `gpt` stays pinned to Sol — bare aliases follow +the gateway's flagship, not the cheapest sibling. + +**New arrivals since 3.39.0.** `zai/glm-5.3` is Z.AI's flagship (1M context, +always-on reasoning, priced at 5.2's $1.4/$4.4) and `xai/grok-imagine-video-1.5` +joins VideoGen at $0.08/s. + +One catalog fact worth writing down: **GLM-5.3 is Base-only.** The Solana +gateway lists 92 of Base's 93 models and 5.3 is the one it's missing (prices +are otherwise identical across chains). So `glm` stays pinned to 5.2 rather +than following the flagship — a bare alias is what people type from muscle +memory and has to resolve on *both* chains; pointing it at a Base-only id +would hand every Solana user an HTTP 400. 5.3 is one keystroke away as +`glm-5.3` and has its own picker row, which reconciliation drops automatically +for Solana users. + +**The router's LLM classifier has been dead since 2026-07-27, silently.** +Its default model was `nvidia/qwen3-next-80b-a3b-instruct` — the same id +NVIDIA EOL'd, which the gateway now rides on `nemotron-3-super-120b`. That +substitute opens with *"Okay, let's see. The user wants to…"* and never +reaches a verdict inside the classifier's 16-token budget, so every +classification failed the strict parse and fell through to keyword-only +routing. The fallback is invisible by design, which is exactly why this went +three weeks without anyone noticing. Re-probed the free pool on the real +classifier prompt: + +| free model | reply to a classification prompt | +|---|---| +| `qwen3-next` (old default) | prose, served by `nemotron-3-super-120b` | +| `nemotron-nano-9b-v2` | prose — *"Okay, let's see…"* | +| `nemotron-3-nano-omni` | **`MEDIUM`** — one bare word, served by itself | + +`nemotron-3-nano-omni` is the new classifier. The lesson worth keeping: "good +free chat model" and "answers in one bare word under a tight cap" are +different requirements, and the free default is not automatically both. + +**The 30B omni model started serving itself again.** In August it came back +as `gpt-oss-120b` — the pooled-substitute trap — which is why it had no alias +and led no chain. Re-probed 2026-08-19 on both the streaming and non-streaming +paths, it now answers as itself. It gets a shortcut (`omni`), a free picker +row, and second place in every free chain; the still-degraded +`mistral-nemotron` slides to third but stays as a genuinely different family. +`nvidia/step-3.7-flash` remains deliberately unaliased — it is billed at $0 +and listed in the catalog, but live probes come back served by +`nemotron-3-super-120b`, and an alias that promises a model you don't get is +worse than no alias. + +**Picker stays at its 24-row cap — new models displaced old ones.** GLM-5.3 +takes GLM-5.2's slot. GPT-5.6 Terra Pro takes the row +`grok-4-1-fast-reasoning` held, which the gateway hides and reconciliation +dropped on every live render anyway. Qwen3.7 Flash ($0.03/$0.13, 1M context, +the cheapest paid model on the gateway) takes GLM-5's budget slot. Gemini 2.5 +Pro's row retires from directly under 3.1 Pro. Every retired row keeps its +shortcut, per the long-standing hide-the-row-keep-the-alias rule, and `gemini` +now tracks the 3.1 Pro flagship like `gpt`, `grok` and `glm` already did. + +**Also:** the offline picker fallback had stale prices (deepseek at +$0.20/$0.40, corrected to $0.14/$0.28 — live renders were already right since +the gateway is the source of truth for price). Brand numbers re-synced: 95 +models visible, 8 video, 5 free. Model-family guidance now recognises +`chat-latest` as a strong model and routes Tencent/Xiaomi to balanced +guidance. + +**Second pass — the tables the aliases exposed.** Surfacing the models turned +up three hand-curated tables that had drifted behind the catalog: + +- **`xai/grok-4.5` was missing from the vision allowlist.** It is what `grok` + resolves to, and it is vision-capable — so every image turn on the xAI + flagship was quietly rerouted to a "vision sibling" the user never asked + for. Eleven other vision models were missing too (the GPT-5.6 pro tier, + `chat-latest`, `gpt-5.3`, `gpt-4o`, Gemini 3.6 Flash, the free omni model). + A new test pins the invariant: every bare flagship alias must be in the + allowlist, so the next flagship fails CI instead of someone's session. +- **Context windows.** `grok-4.5` (500K), `grok-4.3` (1M) and `glm-5.3` (1M) + had no entry and match no inference pattern, so a cold catalog cache fell + through to the blind 128k default — compacting a 1M window eight times too + early. Twelve entries added. +- **Model-family guidance.** The weak-model branch matched on bare `glm`, + which dates from GLM-4.x. `glm` now resolves to GLM-5.3 — 1M context, + always-on reasoning, priced above Gemini 3.1 Pro on input — and it was being + told to make ONE tool call and stay under 300 words. GLM-5.x moves to + balanced guidance. +- The Brain's third extraction fallback was `nvidia/nemotron-super-49b`, gone + from the catalog; now the current free default. + +All 647 local tests pass. The five hidden-but-resolvable ids Franklin still +pins (`opus-4.6`, `gpt-5-nano`, `grok-3`, `grok-4-0709`, +`grok-4-1-fast-reasoning`) were each re-probed and still return 402, not 400 — +they stay. + ## Franklin Agent 3.39.0 — the gateway moved for three weeks and Franklin didn't **Franklin's free default was a dead model, and the whole catalog had drifted.** diff --git a/brand-numbers.json b/brand-numbers.json index 4395f23..0d6a68e 100644 --- a/brand-numbers.json +++ b/brand-numbers.json @@ -3,22 +3,22 @@ "version": 1, "models": { "chatVisible": 71, - "totalVisible": 93, - "free": 6, - "freeWithheld": 19, + "totalVisible": 95, + "free": 5, + "freeWithheld": 20, "image": 9, - "video": 6, + "video": 8, "music": 1, "speech": 5, "soundfx": 1, - "withFallback": 46, - "withFallbackAllEntries": 79 + "withFallback": 44, + "withFallbackAllEntries": 78 }, "clawrouter": { "dimensions": 15, "tiers": 4, "profiles": 4, - "aliases": 202 + "aliases": 229 }, "mcp": { "tools": 20 diff --git a/package-lock.json b/package-lock.json index 14deb89..34d4f75 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@blockrun/franklin", - "version": "3.39.0", + "version": "3.40.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@blockrun/franklin", - "version": "3.39.0", + "version": "3.40.0", "license": "Apache-2.0", "dependencies": { "@blockrun/llm": "^3.13.1", diff --git a/package.json b/package.json index 80ee3a5..35b8b59 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@blockrun/franklin", - "version": "3.39.0", + "version": "3.40.0", "description": "Franklin Agent — The AI agent with a wallet. Spends USDC autonomously to get real work done. Pay per action, no subscriptions.", "type": "module", "exports": { diff --git a/src/agent/context.ts b/src/agent/context.ts index 2dbd4bc..728fbc3 100644 --- a/src/agent/context.ts +++ b/src/agent/context.ts @@ -254,7 +254,7 @@ You run on the BlockRun AI Gateway. When the user asks you to "test the BlockRun - \`GET /.well-known/x402\` — x402 resource list with prices **LLM (POST, x402-paid)** -- \`POST /v1/chat/completions\` — OpenAI-compatible. Body: \`{ model, messages, stream?, tools?, max_tokens?, temperature? }\`. \`model\` MUST come from \`GET /v1/models\` (real frontier examples on the gateway, verified live 2026-08-12: \`anthropic/claude-sonnet-5\`, \`anthropic/claude-opus-5\`, \`deepseek/deepseek-v4-pro\`, \`zai/glm-5.2\`, \`xai/grok-4.5\`, \`nvidia/nemotron-nano-9b-v2\` (free)). Do NOT invent versions like \`openai/gpt-5.1\` or \`xai/grok-5\` — those don't exist; the gateway 400s with the valid list in the error body, so when in doubt fetch \`GET /v1/models\` first. +- \`POST /v1/chat/completions\` — OpenAI-compatible. Body: \`{ model, messages, stream?, tools?, max_tokens?, temperature? }\`. \`model\` MUST come from \`GET /v1/models\` (real frontier examples on the gateway, verified live 2026-08-19: \`anthropic/claude-sonnet-5\`, \`anthropic/claude-opus-5\`, \`openai/gpt-5.6-sol\`, \`deepseek/deepseek-v4-pro\`, \`zai/glm-5.3\`, \`xai/grok-4.5\`, \`qwen/qwen3.7-flash\`, \`nvidia/nemotron-nano-9b-v2\` (free)). Do NOT invent versions like \`openai/gpt-5.1\` or \`xai/grok-5\` — those don't exist; the gateway 400s with the valid list in the error body, so when in doubt fetch \`GET /v1/models\` first. - \`POST /v1/messages\` — Anthropic-compatible. Body: \`{ model, messages, max_tokens, system?, tools? }\`. **Media (POST, x402-paid; GET to poll async jobs)** @@ -548,8 +548,14 @@ export function assembleInstructions(workingDir: string, model?: string): string export function getModelGuidance(model: string): string { const m = model.toLowerCase(); - // Weak/cheap models: strict discipline to prevent looping and hallucination - if (m.includes('glm') || m.includes('gpt-oss') || m.includes('nemotron') || + // Weak/cheap models: strict discipline to prevent looping and hallucination. + // The bare `glm` match dates from the GLM-4.x era. The paid Z.AI GLM-5 line + // is a different animal — 1M context, always-on reasoning, priced above + // Gemini 3.1 Pro on input — and `glm` now resolves to 5.3, so matching it + // here was telling a flagship to make ONE tool call and stay under 300 + // words. GLM-5.x moves to the balanced branch; glm-4.7 and friends stay. + if ((m.includes('glm') && !m.includes('glm-5')) || + m.includes('gpt-oss') || m.includes('nemotron') || m.includes('minimax') || m.includes('devstral') || m.includes('llama-4')) { return `# Execution Discipline (strict — this model requires guardrails) - Make ONE tool call per task. Do NOT retry the same tool with query variations. @@ -560,10 +566,13 @@ export function getModelGuidance(model: string): string { } // Medium models: balanced guidance. The bare `qwen` match dates from when - // every qwen id on the gateway was a free NVIDIA SKU — qwen3.7-max is a paid - // 1M-context flagship and belongs in the strong branch below. + // every qwen id on the gateway was a free NVIDIA SKU. The paid Qwen line is + // now Max + Plus + Flash — all 1M-context with reasoning — so only the Max + // flagship graduates to the strong branch; Plus and Flash stay here, and the + // legacy free `nvidia/qwen*` ids keep matching as before. if (m.includes('kimi') || m.includes('grok') || m.includes('flash') || m.includes('haiku') || m.includes('deepseek') || + m.includes('hy3') || m.includes('mimo') || m.includes('glm-5') || (m.includes('qwen') && !m.includes('qwen3.7-max'))) { return `# Execution Guidance - Use tools to verify facts before stating them. Do not answer from memory when a tool can confirm. @@ -576,7 +585,7 @@ export function getModelGuidance(model: string): string { if (m.includes('claude') || m.includes('gpt-5') || m.includes('opus') || m.includes('sonnet') || m.includes('gemini-2.5-pro') || m.includes('gemini-3') || m.includes('o3') || m.includes('o1') || m.includes('codex') || - m.includes('qwen3.7-max')) { + m.includes('chat-latest') || m.includes('qwen3.7-max')) { return `# Quality Standards (strong model) - Keep calling tools until the task is complete AND the result is verified. Don't stop at "this should work" — prove it works. - Before finalizing: check correctness, grounding in tool output, and formatting. diff --git a/src/agent/tokens.ts b/src/agent/tokens.ts index e5464b9..f035aab 100644 --- a/src/agent/tokens.ts +++ b/src/agent/tokens.ts @@ -249,8 +249,15 @@ const MODEL_CONTEXT_WINDOWS: Record = { 'openai/gpt-5.2': 128_000, 'openai/gpt-5-mini': 128_000, 'openai/gpt-5-nano': 128_000, + 'openai/gpt-5.2-pro': 400_000, 'openai/gpt-4.1': 1_000_000, + 'openai/gpt-4.1-mini': 128_000, + 'openai/gpt-4.1-nano': 128_000, + 'openai/gpt-4o': 128_000, + 'openai/gpt-4o-mini': 128_000, + 'openai/o1': 200_000, 'openai/o3': 200_000, + 'openai/o3-mini': 128_000, 'openai/o4-mini': 200_000, // Google 'google/gemini-2.5-pro': 1_000_000, @@ -259,18 +266,27 @@ const MODEL_CONTEXT_WINDOWS: Record = { 'google/gemini-3.1-pro': 1_000_000, 'google/gemini-3.5-flash': 1_000_000, 'google/gemini-3.1-flash-lite': 1_000_000, + 'google/gemini-3-flash-preview': 1_048_576, // DeepSeek (V4 family — gateway aliased deepseek-chat / -reasoner to V4 // Flash on 2026-05-03; context bumped 128K → 1M for both, 65K out) 'deepseek/deepseek-chat': 1_000_000, 'deepseek/deepseek-reasoner': 1_000_000, 'deepseek/deepseek-v4-pro': 1_000_000, - // xAI + // xAI. grok-4.5 / 4.3 / build were missing until 2026-08-20: neither matches + // any inference pattern below, so a cold catalog cache fell through to the + // blind 128k default and compacted a 500K–1M window ~4-8x too early. + 'xai/grok-4.5': 500_000, + 'xai/grok-4.3': 1_000_000, + 'xai/grok-build-0.1': 256_000, 'xai/grok-3': 131_072, 'xai/grok-4-0709': 131_072, 'xai/grok-4-1-fast-reasoning': 131_072, // Others + 'zai/glm-5.3': 1_000_000, // flagship 2026-08 — 1M context, always-on reasoning 'zai/glm-5.2': 1_000_000, // flagship bump 2026-06 — context jumped 200K → 1M 'zai/glm-5.1': 200_000, + 'zai/glm-5': 200_000, + 'zai/glm-5-turbo': 200_000, 'moonshot/kimi-k3': 1_048_576, 'moonshot/kimi-k2.7': 256_000, 'moonshot/kimi-k2.6': 256_000, diff --git a/src/brain/extract.ts b/src/brain/extract.ts index b5c3370..8bf6e30 100644 --- a/src/brain/extract.ts +++ b/src/brain/extract.ts @@ -11,10 +11,15 @@ import { addObservation, upsertRelation, isJunkEntityName, } from './store.js'; +// Last entry is the free safety net. nvidia/nemotron-super-49b held it until +// 2026-08-20, by which point it had left the catalog entirely — the free pool +// answers for it with a substitute, so the "free fallback" was neither the +// model named nor reliably shaped. nemotron-nano-9b-v2 is the current free +// default and verifiably serves itself. const EXTRACTION_MODELS = [ 'google/gemini-2.5-flash-lite', 'google/gemini-2.5-flash', - 'nvidia/nemotron-super-49b', + 'nvidia/nemotron-nano-9b-v2', ]; const VALID_TYPES = new Set(['person', 'project', 'company', 'product', 'concept']); diff --git a/src/pricing.ts b/src/pricing.ts index 0818da6..5e96f83 100644 --- a/src/pricing.ts +++ b/src/pricing.ts @@ -145,6 +145,7 @@ export const MODEL_PRICING: Record = { - coding: ['nvidia/nemotron-nano-9b-v2', 'nvidia/mistral-nemotron'], - trading: ['nvidia/nemotron-nano-9b-v2', 'nvidia/mistral-nemotron'], - research: ['nvidia/nemotron-nano-9b-v2', 'nvidia/mistral-nemotron'], - reasoning: ['nvidia/nemotron-nano-9b-v2', 'nvidia/mistral-nemotron'], - chat: ['nvidia/nemotron-nano-9b-v2', 'nvidia/mistral-nemotron'], - creative: ['nvidia/nemotron-nano-9b-v2', 'nvidia/mistral-nemotron'], + coding: ['nvidia/nemotron-nano-9b-v2', OMNI, 'nvidia/mistral-nemotron'], + trading: ['nvidia/nemotron-nano-9b-v2', OMNI, 'nvidia/mistral-nemotron'], + research: ['nvidia/nemotron-nano-9b-v2', OMNI, 'nvidia/mistral-nemotron'], + reasoning: ['nvidia/nemotron-nano-9b-v2', OMNI, 'nvidia/mistral-nemotron'], + chat: ['nvidia/nemotron-nano-9b-v2', OMNI, 'nvidia/mistral-nemotron'], + creative: ['nvidia/nemotron-nano-9b-v2', OMNI, 'nvidia/mistral-nemotron'], }; const DEFAULT_FREE_CHAIN: string[] = [ 'nvidia/nemotron-nano-9b-v2', + OMNI, 'nvidia/mistral-nemotron', ]; diff --git a/src/router/vision.ts b/src/router/vision.ts index de508e4..5f0d147 100644 --- a/src/router/vision.ts +++ b/src/router/vision.ts @@ -36,21 +36,37 @@ const VISION_MODELS = new Set([ 'openai/gpt-5.6-sol', 'openai/gpt-5.6-terra', 'openai/gpt-5.6-luna', + // The pro reasoning tier is the same multimodal base with thinking on. + 'openai/gpt-5.6-sol-pro', + 'openai/gpt-5.6-terra-pro', + 'openai/gpt-5.6-luna-pro', 'openai/gpt-5.5', + 'openai/gpt-5.5-pro', + // The rolling ChatGPT default — vision-tagged in the catalog. + 'openai/chat-latest', 'openai/gpt-5.4', 'openai/gpt-5.4-pro', 'openai/gpt-5.4-mini', 'openai/gpt-5.2', 'openai/gpt-5.2-pro', 'openai/gpt-5-mini', + 'openai/gpt-5.3', 'openai/gpt-4.1', + 'openai/gpt-4o', 'openai/o3', // Google — vision baked into every Gemini SKU we surface (flash-lite excepted) 'google/gemini-3.1-pro', + 'google/gemini-3.6-flash', 'google/gemini-3.5-flash', + 'google/gemini-3-flash-preview', 'google/gemini-2.5-pro', 'google/gemini-2.5-flash', - // xAI — only Grok 4 base supports vision; grok-4-1-fast-reasoning is text-only + // xAI — grok-4-1-fast-reasoning stays out (text-only). Grok 4.5 and 4.3 are + // both vision-capable in the catalog and were missing here until 2026-08-20: + // `grok` resolves to 4.5, so every image turn on the xAI flagship was being + // rerouted to a "vision sibling" the user never asked for. + 'xai/grok-4.5', + 'xai/grok-4.3', 'xai/grok-4-0709', 'xai/grok-3', // Moonshot — K3 is the Solana flagship; the K2.x compatibility line remains @@ -64,6 +80,10 @@ const VISION_MODELS = new Set([ // listing it here contradicted routeRequest()'s own "maverick is text-only" // note — the free profile would route a vision turn to a text-only model. 'nvidia/nemotron-nano-12b-v2-vl', + // Nemotron 3 Nano Omni accepts text, images, video and audio, and now serves + // itself (2026-08-19 probe) — it is the strongest free vision option in the + // catalog on ChartQA / DocVQA / MMMU. + 'nvidia/nemotron-3-nano-omni-30b-a3b-reasoning', ]); /** Does this concrete gateway model accept image input? */ diff --git a/src/tools/videogen.ts b/src/tools/videogen.ts index 668add6..deb12de 100644 --- a/src/tools/videogen.ts +++ b/src/tools/videogen.ts @@ -608,10 +608,11 @@ export function createVideoGenCapability(deps: VideoGenDeps = {}): CapabilityHan type: 'string', description: 'Video model. Default: xai/grok-imagine-video ($0.05/s). Known-valid models on the BlockRun gateway as of 2026-08: ' + - 'xai/grok-imagine-video, bytedance/seedance-1.5-pro ($0.070/s), bytedance/seedance-2.0-mini ($0.0797/s), ' + + 'xai/grok-imagine-video, xai/grok-imagine-video-1.5 ($0.08/s), bytedance/seedance-1.5-pro ($0.070/s), bytedance/seedance-2.0-mini ($0.0797/s), ' + 'bytedance/seedance-2.0-fast ($0.165/s), bytedance/seedance-2.0 ($0.227/s), bytedance/seedance-2.5 ($0.315/s), azure/sora-2 ($0.10/s). ' + 'Pick from this list; the gateway rejects unknown names with HTTP 400 (no money charged on rejection). ' + 'Speak "Seedance" → bytedance/seedance-2.5 (newest flagship); "Seedance Pro" → bytedance/seedance-2.0; ' + + '"Grok video" → xai/grok-imagine-video-1.5 (newer Grok clip model, $0.08/s vs $0.05/s); ' + '"Seedance fast" → bytedance/seedance-2.0-fast; "Seedance mini" → bytedance/seedance-2.0-mini (cheapest).', }, image_url: { type: 'string', description: 'Optional seed image (image-to-video). Accepts http(s) URL, data: URI, or local file path — local paths get inlined as base64 data URIs automatically.' }, diff --git a/src/ui/model-picker.ts b/src/ui/model-picker.ts index b98a17e..f6fc451 100644 --- a/src/ui/model-picker.ts +++ b/src/ui/model-picker.ts @@ -44,7 +44,26 @@ export const MODEL_SHORTCUTS: Record = { 'gpt-5.6-sol': 'openai/gpt-5.6-sol', 'gpt-5.6-terra': 'openai/gpt-5.6-terra', 'gpt-5.6-luna': 'openai/gpt-5.6-luna', + // GPT-5.6 pro reasoning tier (gateway, 2026-08). Same base models with pro + // reasoning mode on: Sol Pro matches Sol at $5/$30, while Terra Pro ($1/$6) + // and Luna Pro ($0.1/$0.6) come in UNDER their own base tiers — so the pro + // ids are the better pick for anything reasoning-shaped. `gpt` stays pinned + // to Sol: bare aliases track the gateway's flagship, not the cheapest + // sibling. + 'gpt-5.6-sol-pro': 'openai/gpt-5.6-sol-pro', + 'sol-pro': 'openai/gpt-5.6-sol-pro', + 'gpt-5.6-terra-pro': 'openai/gpt-5.6-terra-pro', + 'terra-pro': 'openai/gpt-5.6-terra-pro', + 'gpt-5.6-luna-pro': 'openai/gpt-5.6-luna-pro', + 'luna-pro': 'openai/gpt-5.6-luna-pro', 'gpt-5.5': 'openai/gpt-5.5', + 'gpt-5.5-pro': 'openai/gpt-5.5-pro', + // The rolling `chat-latest` alias — whatever ChatGPT currently serves as its + // default (GPT-5.5 Instant today), tuned for speed and concision. Priced + // like GPT-5.5 but capped at 128K context. + 'chat-latest': 'openai/chat-latest', + chatgpt: 'openai/chat-latest', + instant: 'openai/chat-latest', 'gpt-5.4': 'openai/gpt-5.4', 'gpt-5.4-pro': 'openai/gpt-5.4-pro', 'gpt-5.4-mini': 'openai/gpt-5.4-mini', @@ -53,19 +72,43 @@ export const MODEL_SHORTCUTS: Record = { 'gpt-5.2': 'openai/gpt-5.2', 'gpt-5.2-pro': 'openai/gpt-5.2-pro', 'gpt-4.1': 'openai/gpt-4.1', + 'gpt-4.1-mini': 'openai/gpt-4.1-mini', + 'gpt-4.1-nano': 'openai/gpt-4.1-nano', + '4o': 'openai/gpt-4o', + 'gpt-4o': 'openai/gpt-4o', + 'gpt-4o-mini': 'openai/gpt-4o-mini', codex: 'openai/gpt-5.3-codex', nano: 'openai/gpt-5-nano', mini: 'openai/gpt-5-mini', o3: 'openai/o3', + 'o3-mini': 'openai/o3-mini', o4: 'openai/o4-mini', 'o4-mini': 'openai/o4-mini', o1: 'openai/o1', // Google - gemini: 'google/gemini-2.5-pro', + // `gemini` follows the flagship Pro build (3.1 since 2026-08), matching the + // bare-alias-tracks-flagship rule `gpt`, `grok` and `glm` already use. 2.5 + // Pro stays reachable as `gemini-2.5` — it is cheaper on input but a + // generation behind. + gemini: 'google/gemini-3.1-pro', 'gemini-2.5': 'google/gemini-2.5-pro', - flash: 'google/gemini-3.5-flash', - 'gemini-flash': 'google/gemini-3.5-flash', + 'gemini-2.5-pro': 'google/gemini-2.5-pro', + // `flash` follows the newest Flash generation (3.6 since 2026-08). 3.5 stays + // reachable by its explicit id — it is dearer on output ($9 vs $7.5) with no + // capability edge, so nothing should be pinned to it deliberately. + flash: 'google/gemini-3.6-flash', + 'gemini-flash': 'google/gemini-3.6-flash', + 'gemini-3.6': 'google/gemini-3.6-flash', + 'gemini-3.6-flash': 'google/gemini-3.6-flash', 'gemini-3.5-flash': 'google/gemini-3.5-flash', + 'gemini-2.5-flash': 'google/gemini-2.5-flash', + 'gemini-3-flash-preview': 'google/gemini-3-flash-preview', + // Flash Lite — thinking-mode Gemini for high-throughput work. `flash-lite` + // follows the newest (3.5); 3.1 is cheaper still and stays explicit. + 'flash-lite': 'google/gemini-3.5-flash-lite', + 'gemini-3.5-flash-lite': 'google/gemini-3.5-flash-lite', + 'gemini-3.1-flash-lite': 'google/gemini-3.1-flash-lite', + 'gemini-2.5-flash-lite': 'google/gemini-2.5-flash-lite', 'gemini-3': 'google/gemini-3.1-pro', 'gemini-3.1': 'google/gemini-3.1-pro', // xAI — grok-4.5 is the public flagship since 2026-07-14; `grok` follows it @@ -123,6 +166,17 @@ export const MODEL_SHORTCUTS: Record = { 'nano-9b': 'nvidia/nemotron-nano-9b-v2', 'nano-vl': 'nvidia/nemotron-nano-12b-v2-vl', 'free-vision': 'nvidia/nemotron-nano-12b-v2-vl', + // Nemotron 3 Nano Omni started answering as ITSELF (re-probed 2026-08-19, + // stream and non-stream) — it was pooled behind gpt-oss-120b when it was + // last checked, which is why it had no alias until now. 31B/3.2B MoE, + // text + image + video + audio in, 256K context. + omni: 'nvidia/nemotron-3-nano-omni-30b-a3b-reasoning', + 'free-omni': 'nvidia/nemotron-3-nano-omni-30b-a3b-reasoning', + 'nano-omni': 'nvidia/nemotron-3-nano-omni-30b-a3b-reasoning', + // Deliberately NOT aliased: nvidia/step-3.7-flash. It is in the catalog and + // billed at $0, but live probes (2026-08-19) come back served by + // nvidia/nemotron-3-super-120b — pointing users at it would promise a model + // they don't get. Same rule that retired the maverick and qwen3-next ids. // Maverick left the gateway catalog on/before 2026-07-14. The id still // answers, but only because the free pool silently substitutes another model // for it — so pointing users at it would be promising a model they don't get. @@ -150,14 +204,38 @@ export const MODEL_SHORTCUTS: Record = { 'qwen-max': 'qwen/qwen3.7-max', 'qwen3.7-max': 'qwen/qwen3.7-max', 'qwen-3.7-max': 'qwen/qwen3.7-max', + // Plus and Flash round out the paid Qwen line — both 1M context with + // reasoning. Flash at $0.03/$0.13 is the cheapest paid model on the gateway. + 'qwen-plus': 'qwen/qwen3.7-plus', + 'qwen3.7-plus': 'qwen/qwen3.7-plus', + 'qwen-flash': 'qwen/qwen3.7-flash', + 'qwen3.7-flash': 'qwen/qwen3.7-flash', + // GLM-5.3 (2026-08) is Z.AI's flagship — same $1.4/$4.4 as 5.2 with 1M + // context and always-on reasoning — but it ships on the BASE gateway ONLY + // (sol.blockrun.ai lists 92 of Base's 93 models, and 5.3 is the one it's + // missing; verified 2026-08-20). + // + // So `glm` deliberately does NOT follow the flagship here. A bare alias is + // what people type from muscle memory, and it has to resolve on BOTH chains + // — pointing it at a Base-only id would hand every Solana user an HTTP 400 + // on `/model glm`. 5.2 is on both, identically priced, one generation back. + // Promote this line the day 5.3 lands on Solana. glm: 'zai/glm-5.2', 'glm-5': 'zai/glm-5', + 'glm-5.3': 'zai/glm-5.3', 'glm-5.2': 'zai/glm-5.2', // GLM-5.1 demoted to a back-compat pin 2026-06 (flagship is 5.2) — still // routes for anyone who wants the 200K-context build explicitly. 'glm-5.1': 'zai/glm-5.1', 'glm-turbo': 'zai/glm-5-turbo', 'glm5': 'zai/glm-5.2', + // Tencent + Xiaomi joined the gateway in 2026-08 — cheap reasoning at long + // context, below the frontier tier. + hy3: 'tencent/hy3', + tencent: 'tencent/hy3', + mimo: 'xiaomi/mimo-v2.5-pro', + 'mimo-v2.5-pro': 'xiaomi/mimo-v2.5-pro', + xiaomi: 'xiaomi/mimo-v2.5-pro', kimi: 'moonshot/kimi-k3', k3: 'moonshot/kimi-k3', // The K2.x line was retired by the gateway (2026-07, replaced by K3). @@ -245,6 +323,8 @@ const PROVIDER_ORDER = [ 'minimax', 'qwen', 'deepseek', + 'tencent', + 'xiaomi', 'nvidia', ]; @@ -258,6 +338,8 @@ const PROVIDER_LABELS: Record = { minimax: 'MiniMax', qwen: 'Qwen / Alibaba', deepseek: 'DeepSeek', + tencent: 'Tencent / Hunyuan', + xiaomi: 'Xiaomi / MiMo', nvidia: 'Free / NVIDIA', }; @@ -301,8 +383,10 @@ export const PICKER_CATEGORIES: ModelCategory[] = [ { id: 'anthropic/claude-sonnet-5', shortcut: 'sonnet', label: 'Claude Sonnet 5', price: '$3/$15' }, { id: 'qwen/qwen3.7-max', shortcut: 'qwen-max', label: 'Qwen3.7 Max', price: '$1.475/$4.425', highlight: true }, { id: 'openai/gpt-5.6-sol', shortcut: 'gpt', label: 'GPT-5.6 Sol', price: '$5/$30', highlight: true }, - { id: 'google/gemini-3.1-pro', shortcut: 'gemini-3', label: 'Gemini 3.1 Pro', price: '$2/$12' }, - { id: 'google/gemini-2.5-pro', shortcut: 'gemini', label: 'Gemini 2.5 Pro', price: '$1.25/$10' }, + // Gemini 2.5 Pro's row retired here the same way Opus 4.8's did: a + // superseded sibling listed directly under its successor is choice + // paralysis, not choice. `gemini-2.5` still resolves to it. + { id: 'google/gemini-3.1-pro', shortcut: 'gemini', label: 'Gemini 3.1 Pro', price: '$2/$12' }, { id: 'xai/grok-4.5', shortcut: 'grok', label: 'Grok 4.5', price: '$2.5/$9' }, // Kimi K3 (2026-07): 2.8T open MoE, 1M context, multimodal + reasoning. // Replaced the budget K2.7 line — now premium-priced ($3/$15). @@ -319,10 +403,14 @@ export const PICKER_CATEGORIES: ModelCategory[] = [ // on hard tasks at <1/10 the price. { id: 'deepseek/deepseek-v4-pro', shortcut: 'deepseek-v4-pro', label: 'DeepSeek V4 Pro', price: '$0.435/$0.87', highlight: true }, { id: 'deepseek/deepseek-reasoner', shortcut: 'r1', label: 'DeepSeek V4 Flash R.', price: '$0.2/$0.4' }, - { id: 'xai/grok-4-1-fast-reasoning', shortcut: 'grok-fast', label: 'Grok 4.1 Fast R.', price: '$0.2/$0.5' }, - // GLM-5.2: Z.AI's new flagship — 1M context, top open-source on + // Terra Pro took the row grok-4-1-fast-reasoning used to hold: the xAI + // fast family is hidden from /v1/models, so reconcilePicker dropped that + // row on every live render anyway (`grok-fast` still resolves). Terra + // Pro is GPT-5.6 Terra with pro reasoning on, at HALF Terra's price. + { id: 'openai/gpt-5.6-terra-pro', shortcut: 'terra-pro', label: 'GPT-5.6 Terra Pro', price: '$1/$6', highlight: true }, + // GLM-5.3: Z.AI's flagship — 1M context, always-on reasoning, strong on // long-horizon coding. `glm`/`glm5` shortcuts pin it. - { id: 'zai/glm-5.2', shortcut: 'glm', label: 'GLM-5.2', price: '$1.4/$4.4' }, + { id: 'zai/glm-5.3', shortcut: 'glm-5.3', label: 'GLM-5.3', price: '$1.4/$4.4' }, ], }, { @@ -330,13 +418,16 @@ export const PICKER_CATEGORIES: ModelCategory[] = [ models: [ { id: 'anthropic/claude-haiku-4.5', shortcut: 'haiku', label: 'Claude Haiku 4.5', price: '$1/$5' }, { id: 'openai/gpt-5-mini', shortcut: 'mini', label: 'GPT-5 Mini', price: '$0.25/$2' }, - { id: 'google/gemini-2.5-flash', shortcut: 'flash', label: 'Gemini 2.5 Flash', price: '$0.3/$2.5' }, + // `flash` now follows Gemini 3.6; this row keeps the cheap 2.5 build and + // labels itself with the explicit shortcut so the two can't drift apart. + { id: 'google/gemini-2.5-flash', shortcut: 'gemini-2.5-flash', label: 'Gemini 2.5 Flash', price: '$0.3/$2.5' }, // Re-aliased to V4 Flash Chat upstream — context 1M, price 30% lower. - { id: 'deepseek/deepseek-chat', shortcut: 'deepseek', label: 'DeepSeek V4 Flash Chat', price: '$0.2/$0.4' }, - // GLM flat-rate promos fully ended 2026-06-06 — whole family per-token - // now (glm-5 $0.60/$1.92; `glm` shortcut pins flagship glm-5.2, listed - // in Reasoning above). - { id: 'zai/glm-5', shortcut: 'glm-5', label: 'GLM-5', price: '$0.6/$1.92' }, + { id: 'deepseek/deepseek-chat', shortcut: 'deepseek', label: 'DeepSeek V4 Flash Chat', price: '$0.14/$0.28' }, + // Cheapest paid model on the gateway, and it still carries 1M context + // with reasoning — the budget slot GLM-5 used to hold (its flat-rate + // promo ended 2026-06-06 and it now lists at $1/$3.2, no longer a budget + // number; the `glm-5` shortcut stays live). + { id: 'qwen/qwen3.7-flash', shortcut: 'qwen-flash', label: 'Qwen3.7 Flash', price: '$0.03/$0.13', highlight: true }, // Minimax M2.7 hidden to make room for V4 Pro in Reasoning + V4 Flash // (free) without exceeding the picker's 24-entry cap. Shortcut `minimax` // still resolves to it. @@ -351,13 +442,18 @@ export const PICKER_CATEGORIES: ModelCategory[] = [ // back to paid. // // Caveat worth knowing before editing this list: the NVIDIA free pool - // silently substitutes or degrades. The 30B omni model currently answers - // as `nvidia/gpt-oss-120b`, and mistral-nemotron 400s on streaming calls - // (DEGRADED upstream; non-stream rides a disclosed gateway fallback) — - // both verified live 2026-08-12. The nano rows lead precisely because - // they serve themselves, consistently, on every path. + // silently substitutes or degrades, so every row here is re-probed + // before it ships. Re-probed 2026-08-19: the nano pair and the 30B omni + // model all answer as THEMSELVES on both the streaming and non-streaming + // paths (omni was pooled behind gpt-oss-120b in August and has since + // been fixed upstream — hence its new row). mistral-nemotron still + // comes back served by `nvidia/nemotron-3-super-120b`; it keeps its row + // and its `nemotron` alias but must never lead a chain. The catalog's + // fifth free id, nvidia/step-3.7-flash, is substituted the same way and + // is deliberately absent from both this list and MODEL_SHORTCUTS. { id: 'nvidia/nemotron-nano-9b-v2', shortcut: 'free', label: 'Nemotron Nano 9B', price: 'FREE', highlight: true }, { id: 'nvidia/nemotron-nano-12b-v2-vl', shortcut: 'nano-vl', label: 'Nemotron Nano VL', price: 'FREE' }, + { id: 'nvidia/nemotron-3-nano-omni-30b-a3b-reasoning', shortcut: 'omni', label: 'Nemotron 3 Nano Omni', price: 'FREE' }, { id: 'nvidia/mistral-nemotron', shortcut: 'nemotron', label: 'Mistral Nemotron', price: 'FREE' }, ], }, diff --git a/test/local.mjs b/test/local.mjs index 56cd6f4..118ff0c 100644 --- a/test/local.mjs +++ b/test/local.mjs @@ -3193,14 +3193,16 @@ test('agent context: chat-completions example uses real model names (no fictiona 'utf-8', ); // Real names that should appear as illustrative examples. Refreshed - // 2026-07-24: every id below returned 402 (exists, needs payment) rather + // 2026-08-19: every id below returned 402 (exists, needs payment) rather // than 400 on a live POST /api/v1/chat/completions probe. for (const real of [ 'anthropic/claude-sonnet-5', 'anthropic/claude-opus-5', + 'openai/gpt-5.6-sol', 'deepseek/deepseek-v4-pro', - 'zai/glm-5.2', + 'zai/glm-5.3', 'xai/grok-4.5', + 'qwen/qwen3.7-flash', ]) { assert.ok(src.includes(real), `chat-completions example list must include real model "${real}"`); } @@ -6027,6 +6029,10 @@ test('free routing profile stays free across router entry points', async () => { 'nvidia/mistral-nemotron', 'nvidia/nemotron-nano-9b-v2', 'nvidia/nemotron-nano-12b-v2-vl', + // Added 2026-08-19 with the chain promotion. Billed $0 by the gateway and + // live-probed serving itself; the guard's job is to keep a PAID id out of + // the free chain, not to freeze the roster. + 'nvidia/nemotron-3-nano-omni-30b-a3b-reasoning', ]); for (const tier of ['SIMPLE', 'MEDIUM', 'COMPLEX', 'REASONING']) { const resolved = resolveTierToModel(tier, 'free'); @@ -7128,18 +7134,23 @@ test('picker trim: hero shortcuts (opus, sonnet, gpt, gemini-3, grok) still in v assert.ok(ids.includes('anthropic/claude-sonnet-5')); assert.ok(ids.includes('openai/gpt-5.6-sol')); assert.ok(ids.includes('google/gemini-3.1-pro')); - assert.ok(ids.includes('google/gemini-2.5-pro')); + // Gemini 2.5 Pro lost its row in the 2026-08-19 sync (superseded sibling + // directly under 3.1 Pro). `gemini-2.5` still resolves — same "hide the row, + // keep the shortcut" pattern the rest of this trim uses. + assert.ok(!ids.includes('google/gemini-2.5-pro')); assert.ok(ids.includes('xai/grok-4.5')); // grok-4-0709 hidden on gateway; 4.5 is the public flagship row }); test('picker trim: total visible entries dropped meaningfully', async () => { const { PICKER_CATEGORIES } = await import('../dist/ui/model-picker.js'); const total = PICKER_CATEGORIES.reduce((sum, c) => sum + c.models.length, 0); - // Sanity bounds. Flat-rate GLM category removed 2026-06-06 (promos fully - // ended); GLM-5 moved into Budget — current shape is 1 routing + 6 premium - // + 5 reasoning + 6 budget + 3 free = 21. + // Sanity bounds. Current shape after the 2026-08-19 catalog sync is + // 1 routing + 8 premium + 6 reasoning + 5 budget + 4 free = 24. The upper + // bound is the point: new models earn a row by displacing one (GLM-5 and + // Gemini 2.5 Pro made way for Qwen3.7 Flash and the free omni model), never + // by growing the list. assert.ok(total >= 20, `expected >= 20 visible entries, got ${total}`); - assert.ok(total <= 24, `expected <= 24 visible entries (33 → ~21), got ${total}`); + assert.ok(total <= 24, `expected <= 24 visible entries (33 → ~24), got ${total}`); }); // ─── picker ↔ gateway reconciliation ────────────────────────────────────── @@ -8805,12 +8816,13 @@ test('pickFreeFallback: research / chat / creative also skip coder first', async test('pickFreeFallback: respects alreadyFailed set', async () => { const { pickFreeFallback } = await import('../dist/router/index.js'); // Coding starts with nemotron-nano-9b-v2. After it fails, next is the - // mistral-nemotron secondary (2026-08-12 refresh). + // nano-omni secondary (2026-08-19 refresh — it started serving itself again, + // so it displaced the still-degraded mistral-nemotron, which slid to third). const failed = new Set(['nvidia/nemotron-nano-9b-v2']); const pick = pickFreeFallback('coding', failed); assert.notEqual(pick, 'nvidia/nemotron-nano-9b-v2'); - assert.equal(pick, 'nvidia/mistral-nemotron', - `after nemotron-nano-9b-v2 fails, coding should fall to mistral-nemotron, got ${pick}`); + assert.equal(pick, 'nvidia/nemotron-3-nano-omni-30b-a3b-reasoning', + `after nemotron-nano-9b-v2 fails, coding should fall to nano-omni, got ${pick}`); }); test('pickFreeFallback: unknown category uses default chain (general model first)', async () => { @@ -8826,6 +8838,7 @@ test('pickFreeFallback: returns undefined when every candidate failed', async () const failed = new Set([ 'nvidia/mistral-nemotron', 'nvidia/nemotron-nano-9b-v2', + 'nvidia/nemotron-3-nano-omni-30b-a3b-reasoning', ]); const pick = pickFreeFallback('trading', failed); assert.equal(pick, undefined); @@ -10590,8 +10603,13 @@ test('vision routing: pickVisionSibling stays within the user-chosen family', as assert.ok(isVisionModel(pickVisionSibling('deepseek/deepseek-v4-pro'))); // xai/grok-4-1-fast-reasoning (text-only) → must stay in xai family if any - // xai vision sibling exists. Currently xai/grok-4-0709 is vision-capable. - assert.equal(pickVisionSibling('xai/grok-4-1-fast-reasoning'), 'xai/grok-4-0709'); + // xai vision sibling exists. Asserted by family + capability rather than by + // id: the pick moved from grok-4-0709 to grok-4.5 on 2026-08-20 when the + // allowlist gained the vision-capable flagship, and pinning the exact id + // just made a strictly better answer look like a regression. + const grokSwap = pickVisionSibling('xai/grok-4-1-fast-reasoning'); + assert.ok(grokSwap.startsWith('xai/'), `expected xai sibling, got ${grokSwap}`); + assert.ok(isVisionModel(grokSwap)); // openai/gpt-5.3-codex (text-only) → must stay in openai family const codexSwap = pickVisionSibling('openai/gpt-5.3-codex'); @@ -10599,6 +10617,27 @@ test('vision routing: pickVisionSibling stays within the user-chosen family', as assert.ok(isVisionModel(codexSwap)); }); +test('vision routing: every flagship bare alias is in the vision allowlist', async () => { + const { isVisionModel } = await import('../dist/router/vision.js'); + const { MODEL_SHORTCUTS } = await import('../dist/ui/model-picker.js'); + + // The allowlist in src/router/vision.ts is hand-curated, so it drifts when + // the gateway ships a new flagship: grok-4.5 was vision-capable and missing + // for weeks, which silently rerouted every image turn on `grok` to a model + // the user never chose. The bare aliases are the ones people actually type, + // so they are the ones worth pinning — a new flagship that lands without a + // vision entry fails here instead of in someone's session. + for (const alias of ['claude', 'opus', 'sonnet', 'gpt', 'gemini', 'grok', 'kimi']) { + const id = MODEL_SHORTCUTS[alias]; + assert.ok(id, `bare alias "${alias}" disappeared from MODEL_SHORTCUTS`); + assert.ok( + isVisionModel(id), + `bare alias "${alias}" resolves to ${id}, which is missing from VISION_MODELS — ` + + `image turns on it get rerouted to another model`, + ); + } +}); + // ─── journal-quality scorer ───────────────────────────────────────────── // Non-outcome trade discipline metric ported from AI-Trader's signal_quality // model. Five weighted components produce a 0–5 total. These tests pin the