diff --git a/extensions/levelcode-ai/providers/catalog.js b/extensions/levelcode-ai/providers/catalog.js index af2af3c..dc31580 100644 --- a/extensions/levelcode-ai/providers/catalog.js +++ b/extensions/levelcode-ai/providers/catalog.js @@ -29,9 +29,19 @@ const CAPS = { 'claude-opus-4-8': { context: 200000, tools: true, vision: true, caching: true }, 'claude-sonnet-4-6': { context: 200000, tools: true, vision: true, caching: true }, 'claude-haiku-4-5-20251001': { context: 200000, tools: true, vision: true, fast: true }, + // Fable reaches the composer two ways: through the gateway as the OpenRouter basename (dotted + // 5.1), and BYOK-native as the dashed id. Neither had a row, so both fell to the claude-* + // heuristic's 200k — a fifth of the real window. Fable 5 / 5.1 are Pro-tier from 2026-09-08. + 'claude-fable-5': { context: 1000000, tools: true, vision: true, caching: true }, + 'claude-fable-5.1': { context: 1000000, tools: true, vision: true, caching: true }, + 'claude-fable-5-1': { context: 1000000, tools: true, vision: true, caching: true }, // OpenAI 'gpt-4o': { context: 128000, tools: true, vision: true }, 'gpt-4o-mini': { context: 128000, tools: true, vision: true, fast: true }, + // Not covered by the gpt-4/gpt-5 heuristic below, which would also give it a 128k window; the + // real one is 1.05M (OpenRouter models API, 2026-09-06) and it reads images. Without this row the + // gateway picker offers it while the composer refuses attachments and the meter sizes it wrong. + 'gpt-6-astra': { context: 1050000, tools: true, vision: true }, 'o3-mini': { context: 200000, tools: true, reasoning: true }, 'o1': { context: 200000, tools: true, reasoning: true }, 'o1-mini': { context: 128000, tools: false, reasoning: true }, // o1-mini has no function calling diff --git a/extensions/levelcode-ai/test/catalog.test.js b/extensions/levelcode-ai/test/catalog.test.js index 30aaefd..a68019a 100644 --- a/extensions/levelcode-ai/test/catalog.test.js +++ b/extensions/levelcode-ai/test/catalog.test.js @@ -19,6 +19,20 @@ test('modelCaps: exact table hit', () => { test('modelCaps: OpenRouter vendor/model matches on basename', () => { assert.deepStrictEqual(C.modelCaps('openai/gpt-4o'), C.modelCaps('gpt-4o')); }); +// GPT-6 Astra is a gateway model; the picker offers whatever /account/models returns, so the caps +// table must already know it or the composer refuses images and the meter assumes a 200k window. +test('modelCaps: gpt-6-astra is an explicit row, not the gpt-4/5 heuristic', () => { + assert.deepStrictEqual(C.modelCaps('gpt-6-astra'), { context: 1050000, tools: true, vision: true }); + assert.deepStrictEqual(C.modelCaps('openai/gpt-6-astra'), C.modelCaps('gpt-6-astra')); + assert.strictEqual(C.supportsVisionForModel('openai', 'gpt-6-astra'), true); + assert.strictEqual(C.contextWindowFor('openai', 'gpt-6-astra'), 1050000); +}); +test('modelCaps: Fable 5 / 5.1 carry the 1M window under every id they arrive as', () => { + for (const id of ['anthropic/claude-fable-5', 'anthropic/claude-fable-5.1', 'claude-fable-5-1']) { + assert.deepStrictEqual(C.modelCaps(id), { context: 1000000, tools: true, vision: true, caching: true }, id); + assert.strictEqual(C.contextWindowFor('openai', id), 1000000, id); + } +}); test('modelCaps: family heuristics for unknown ids', () => { assert.strictEqual(C.modelCaps('o4-mini').reasoning, true); // o-series assert.strictEqual(C.modelCaps('claude-3-5-haiku-latest').vision, true);