From ea9407e9ccb98079b5f962c84565628db2ec6552 Mon Sep 17 00:00:00 2001 From: Sergii Demianchuk Date: Sat, 25 Jul 2026 10:36:38 -0400 Subject: [PATCH 1/2] feat(ai): offer Claude Opus 5 on OpenRouter (BYOK) + 1M-context caps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gateway (LevelCode Cloud) users get Opus 5 from the server roster with no editor release — that side is thin.ly. This is the BYOK half: the OpenRouter picker, the local caps table, and the sketch cost meter. - providers/index.js — a picker row for `anthropic/claude-opus-5`. Placed second, under the flagship, since it is the frontier pick most people reach for. - providers/catalog.js — an explicit CAPS row, for the same reason Kimi K3 has one: the `claude-` heuristic reports 200K, so a 1M window would drive the in-run context meter to cry "full" at a fifth of the real budget. - sketch/pricing.js — Opus 5 ties Opus 4.8's $5/$25, which the `claude-opus` family fallback already produced. The FAST variant does NOT: it is $10/$50, and the fallback was quietly pricing it at half. Both are pinned now, so the meter is honest whichever slug a BYOK user types. - sketch.js — MODEL_TIERS openrouter.powerful moves 4.8 → 5. Same price, newer model. It also fixes a latent wart: the old id was never in the OpenRouter registry, so a per-node model override naming it failed validation and silently fell back. Revert this one line if you'd rather pin the tier to 4.8. Prices/context confirmed against the OpenRouter models API (2026-07-24): $5/M in · $25/M out · $0.50/M cached · 1M ctx (fast variant: $10/$50). Verified at runtime, not just in tests: the row renders in the picker, caps resolve to 1,000,000, tools/vision on, and isAnthropicFamily matches so explicit prompt-cache breakpoints are sent. Full gate green (24 suites). --- extensions/levelcode-ai/providers/catalog.js | 6 +++++- extensions/levelcode-ai/providers/index.js | 1 + extensions/levelcode-ai/sketch.js | 5 ++++- extensions/levelcode-ai/sketch/pricing.js | 5 +++++ extensions/levelcode-ai/test/catalog.test.js | 4 ++++ extensions/levelcode-ai/test/promptCaching.test.js | 2 +- extensions/levelcode-ai/test/sketch.test.js | 4 ++++ 7 files changed, 24 insertions(+), 3 deletions(-) diff --git a/extensions/levelcode-ai/providers/catalog.js b/extensions/levelcode-ai/providers/catalog.js index ac3b554..e6a89f7 100644 --- a/extensions/levelcode-ai/providers/catalog.js +++ b/extensions/levelcode-ai/providers/catalog.js @@ -57,7 +57,11 @@ const CAPS = { // warns "full" 5× too early (the roster feeds the PICKER's ctx, but the meter reads this local CAPS). // No `fast` (a 2.8T reasoning model is a poor ghost-text completer). tools:true matches its agentic // intent; flip to false ONLY if the reasoning-loop spike (docs/KIMI-K3.md §3) shows it breaks the loop. - 'moonshotai/kimi-k3': { context: 1048576, tools: true, reasoning: true } + 'moonshotai/kimi-k3': { context: 1048576, tools: true, reasoning: true }, + // Same reason as K3: a 1M window that the `claude-` heuristic would otherwise report as 200K, + // making the in-run context meter cry "full" 5× too early. Listed under its OpenRouter slug + // because that is how it is reached (BYOK here, and the gateway roster uses the same id). + 'anthropic/claude-opus-5': { context: 1000000, tools: true, vision: true, caching: true } }; /** The basename of a model id ('openai/gpt-4o' → 'gpt-4o', 'gpt-4o' → 'gpt-4o'). */ diff --git a/extensions/levelcode-ai/providers/index.js b/extensions/levelcode-ai/providers/index.js index 44b7826..d9d7f51 100644 --- a/extensions/levelcode-ai/providers/index.js +++ b/extensions/levelcode-ai/providers/index.js @@ -45,6 +45,7 @@ const PROVIDERS = { caps: { tools: true, vision: true }, headers: { 'HTTP-Referer': 'https://levelcode.ai', 'X-Title': 'LevelCode' }, models: [ { id: 'moonshotai/kimi-k2.7-code', label: 'Kimi K2.7 Code', detail: 'Moonshot · coding, 262K · via OpenRouter' }, + { id: 'anthropic/claude-opus-5', label: 'Claude Opus 5', detail: 'Anthropic · frontier, 1M · via OpenRouter' }, { id: 'anthropic/claude-sonnet-4-6', label: 'Claude Sonnet 4.6', detail: 'via OpenRouter' }, { id: 'openai/gpt-4o', label: 'GPT-4o', detail: 'via OpenRouter' }, { id: 'google/gemini-2.0-flash-001', label: 'Gemini 2.0 Flash', detail: 'via OpenRouter' }, diff --git a/extensions/levelcode-ai/sketch.js b/extensions/levelcode-ai/sketch.js index 4b37a57..17b5444 100644 --- a/extensions/levelcode-ai/sketch.js +++ b/extensions/levelcode-ai/sketch.js @@ -41,7 +41,10 @@ const MODEL_TIERS = { openrouter: { fast: "google/gemini-2.0-flash-001", balanced: "anthropic/claude-sonnet-4-6", - powerful: "anthropic/claude-opus-4-8", + // Opus 5 costs exactly what 4.8 did ($5/$25), so "powerful" gets the newer model for the same + // money. It is also in the OpenRouter registry now, which the old id never was — so a per-node + // model override naming it now validates instead of silently falling back to the tier default. + powerful: "anthropic/claude-opus-5", }, groq: { fast: "llama-3.1-8b-instant", diff --git a/extensions/levelcode-ai/sketch/pricing.js b/extensions/levelcode-ai/sketch/pricing.js index 32fb724..d4d4876 100644 --- a/extensions/levelcode-ai/sketch/pricing.js +++ b/extensions/levelcode-ai/sketch/pricing.js @@ -19,6 +19,11 @@ const { baseName } = require('../providers/catalog'); const PRICING = { // Anthropic (published) 'claude-opus-4-8': { inM: 5.00, outM: 25.00 }, + // Opus 5 ties 4.8's sheet, so the `claude-opus` family fallback below already gets it right — but + // the FAST variant is double ($10/$50), and that fallback would silently under-report it by 2×. + // Pinning both keeps the cost meter honest for whichever slug a BYOK user types. + 'claude-opus-5': { inM: 5.00, outM: 25.00 }, + 'claude-opus-5-fast': { inM: 10.00, outM: 50.00 }, 'claude-sonnet-4-6': { inM: 3.00, outM: 15.00 }, 'claude-haiku-4-5': { inM: 1.00, outM: 5.00 }, 'claude-haiku-4-5-20251001': { inM: 1.00, outM: 5.00 }, diff --git a/extensions/levelcode-ai/test/catalog.test.js b/extensions/levelcode-ai/test/catalog.test.js index 31216b7..bce9d36 100644 --- a/extensions/levelcode-ai/test/catalog.test.js +++ b/extensions/levelcode-ai/test/catalog.test.js @@ -61,6 +61,10 @@ test('contextWindowFor: known model wins; fallback then 200000 when unknown', () assert.strictEqual(C.contextWindowFor('openai', 'unknown-x'), 200000); // Gateway Kimi K3 — its 1M window must beat the 200000 fallback, or the meter warns 5× too early. assert.strictEqual(C.contextWindowFor('openai', 'moonshotai/kimi-k3', 200000), 1048576); + // Same trap for Opus 5: the `claude-` heuristic would hand back 200K and shrink a 1M window. + assert.strictEqual(C.contextWindowFor('openai', 'anthropic/claude-opus-5', 200000), 1000000); + // …and the heuristic is still what covers Claude ids we DON'T list (200K is the right default). + assert.strictEqual(C.contextWindowFor('openai', 'anthropic/claude-opus-4-8', 200000), 200000); }); // ---- fastCompletionModel ---- diff --git a/extensions/levelcode-ai/test/promptCaching.test.js b/extensions/levelcode-ai/test/promptCaching.test.js index 76fcaa3..f627647 100644 --- a/extensions/levelcode-ai/test/promptCaching.test.js +++ b/extensions/levelcode-ai/test/promptCaching.test.js @@ -94,7 +94,7 @@ test('splitOutCachedTokens: zero cached_tokens does not subtract', () => { // ---- Anthropic-family gating for OpenRouter explicit caching ---- test('isAnthropicFamily: identifies Claude upstreams on OpenRouter, IDs only Anthropic routes', () => { - for (const id of ['claude-opus-4-8', 'anthropic/claude-sonnet-4-6', 'claude-sonnet-4-6']) { + for (const id of ['claude-opus-4-8', 'anthropic/claude-sonnet-4-6', 'claude-sonnet-4-6', 'anthropic/claude-opus-5']) { assert.strictEqual(O.isAnthropicFamily(id), true, id); } for (const id of ['openai/gpt-4o', 'deepseek/deepseek-chat', 'gpt-4o', 'moonshotai/kimi-k2.7-code', '']) { diff --git a/extensions/levelcode-ai/test/sketch.test.js b/extensions/levelcode-ai/test/sketch.test.js index ee8d8dd..5a3077d 100644 --- a/extensions/levelcode-ai/test/sketch.test.js +++ b/extensions/levelcode-ai/test/sketch.test.js @@ -72,6 +72,10 @@ test('modelPricing: exact, basename (openrouter prefix), family fallback, unknow assert.strictEqual(P.modelPricing('claude-opus-4-8').inM, 5.0); assert.strictEqual(P.modelPricing('anthropic/claude-sonnet-4-6').outM, 15.0); // basename via vendor/model assert.strictEqual(P.modelPricing('claude-sonnet-4-6-preview').outM, 15.0); // family prefix + // Opus 5 matches 4.8's sheet; its FAST variant is double. Without the explicit rows the + // `claude-opus` family fallback prices the fast one at half what it costs. + assert.deepStrictEqual(P.modelPricing('anthropic/claude-opus-5'), { inM: 5.0, outM: 25.0 }); + assert.deepStrictEqual(P.modelPricing('anthropic/claude-opus-5-fast'), { inM: 10.0, outM: 50.0 }); assert.strictEqual(P.modelPricing('totally-unknown-model'), null); assert.strictEqual(P.modelPricing(''), null); }); From a4777a838cec59d7d510ace6dc2674845a04ed64 Mon Sep 17 00:00:00 2001 From: Sergii Demianchuk Date: Sat, 25 Jul 2026 10:49:43 -0400 Subject: [PATCH 2/2] =?UTF-8?q?fix(ai):=20Opus=204.8=20is=201M=20on=20Open?= =?UTF-8?q?Router=20=E2=80=94=20split=20the=20caps=20row=20by=20route?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The CAPS row for Opus 4.8 said 200K, so the in-run context meter warned "full" at a fifth of the real budget for anyone on the OpenRouter/gateway route. Every OpenRouter endpoint for that model (Anthropic first-party, Bedrock, Azure, Google) advertises 1M/128K. Fixed under the OpenRouter slug ONLY, and deliberately not under the bare id: 'claude-opus-4-8' → 200000 direct Anthropic provider 'anthropic/claude-opus-4-8' → 1000000 OpenRouter / gateway Those are two different routes sharing one key today, and I can only vouch for one of them. This extension sends no `anthropic-beta` header on the native path, so I have not confirmed the long window is reachable there — and overstating it would be the worse failure, telling the user they have room the API will refuse. Exact-id lookup beats basename, so the rows coexist and each route reports its own truth. If the native path does support 1M unheadered, the bare row is a one-line follow-up. Tests pin both sides of the split plus the unlisted-Claude heuristic default. Full gate green (24 suites). --- extensions/levelcode-ai/providers/catalog.js | 8 +++++++- extensions/levelcode-ai/test/catalog.test.js | 8 ++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/extensions/levelcode-ai/providers/catalog.js b/extensions/levelcode-ai/providers/catalog.js index e6a89f7..3ee6c76 100644 --- a/extensions/levelcode-ai/providers/catalog.js +++ b/extensions/levelcode-ai/providers/catalog.js @@ -61,7 +61,13 @@ const CAPS = { // Same reason as K3: a 1M window that the `claude-` heuristic would otherwise report as 200K, // making the in-run context meter cry "full" 5× too early. Listed under its OpenRouter slug // because that is how it is reached (BYOK here, and the gateway roster uses the same id). - 'anthropic/claude-opus-5': { context: 1000000, tools: true, vision: true, caching: true } + 'anthropic/claude-opus-5': { context: 1000000, tools: true, vision: true, caching: true }, + // Opus 4.8 is ALSO 1M — but only stated here under the OpenRouter slug, where every endpoint + // (Anthropic first-party, Bedrock, Azure, Google) advertises 1M/128K. The bare `claude-opus-4-8` + // row above stays at 200K on purpose: that key serves the DIRECT Anthropic provider, and this + // extension sends no `anthropic-beta` header, so the long window is not confirmed on that route. + // Exact-id lookup beats basename, so the two coexist and each route reports its own truth. + 'anthropic/claude-opus-4-8': { context: 1000000, tools: true, vision: true, caching: true } }; /** The basename of a model id ('openai/gpt-4o' → 'gpt-4o', 'gpt-4o' → 'gpt-4o'). */ diff --git a/extensions/levelcode-ai/test/catalog.test.js b/extensions/levelcode-ai/test/catalog.test.js index bce9d36..30aaefd 100644 --- a/extensions/levelcode-ai/test/catalog.test.js +++ b/extensions/levelcode-ai/test/catalog.test.js @@ -63,8 +63,12 @@ test('contextWindowFor: known model wins; fallback then 200000 when unknown', () assert.strictEqual(C.contextWindowFor('openai', 'moonshotai/kimi-k3', 200000), 1048576); // Same trap for Opus 5: the `claude-` heuristic would hand back 200K and shrink a 1M window. assert.strictEqual(C.contextWindowFor('openai', 'anthropic/claude-opus-5', 200000), 1000000); - // …and the heuristic is still what covers Claude ids we DON'T list (200K is the right default). - assert.strictEqual(C.contextWindowFor('openai', 'anthropic/claude-opus-4-8', 200000), 200000); + // Opus 4.8 is route-dependent, and exact-id lookup is what keeps the two apart: the OpenRouter + // slug is a confirmed 1M, while the bare id (direct Anthropic, no beta header sent) stays 200K. + assert.strictEqual(C.contextWindowFor('openrouter', 'anthropic/claude-opus-4-8', 200000), 1000000); + assert.strictEqual(C.contextWindowFor('claude', 'claude-opus-4-8', 200000), 200000); + // Claude ids we DON'T list still fall to the heuristic's 200K default. + assert.strictEqual(C.contextWindowFor('claude', 'claude-something-unreleased', 200000), 200000); }); // ---- fastCompletionModel ----