Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion extensions/levelcode-ai/providers/catalog.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,17 @@ 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 },
// 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'). */
Expand Down
1 change: 1 addition & 0 deletions extensions/levelcode-ai/providers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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' },
Expand Down
5 changes: 4 additions & 1 deletion extensions/levelcode-ai/sketch.js
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
5 changes: 5 additions & 0 deletions extensions/levelcode-ai/sketch/pricing.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand Down
8 changes: 8 additions & 0 deletions extensions/levelcode-ai/test/catalog.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,14 @@ 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);
// 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 ----
Expand Down
2 changes: 1 addition & 1 deletion extensions/levelcode-ai/test/promptCaching.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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', '']) {
Expand Down
4 changes: 4 additions & 0 deletions extensions/levelcode-ai/test/sketch.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
Expand Down