diff --git a/src/cliproxy/__tests__/model-catalog-compat.test.ts b/src/cliproxy/__tests__/model-catalog-compat.test.ts index aaaa01d6..e0c17565 100644 --- a/src/cliproxy/__tests__/model-catalog-compat.test.ts +++ b/src/cliproxy/__tests__/model-catalog-compat.test.ts @@ -46,21 +46,62 @@ describe('model-catalog compatibility lookups', () => { expect(PROVIDER_TO_CHANNEL.xai).toBe('xai'); }); - it('merges live xAI model metadata while preserving the static coding default', () => { + it('merges live Grok 4.6 metadata while preserving the static coding default', () => { const catalog = mergeCatalog('xai', [ { id: 'grok-build-0.1', display_name: 'Grok Build 0.1' }, { - id: 'grok-4.5', - display_name: 'Grok 4.5', - thinking: { levels: ['low', 'medium', 'high'] }, + id: 'grok-4.6', + display_name: 'Grok 4.6', + context_length: 500_000, + thinking: { + levels: ['low', 'medium', 'high', 'xhigh'], + zero_allowed: false, + }, }, ]); expect(catalog?.defaultModel).toBe('grok-build-0.1'); - expect(catalog?.models[1]?.thinking).toMatchObject({ - type: 'levels', - levels: ['low', 'medium', 'high'], + expect(catalog?.models[1]).toMatchObject({ + id: 'grok-4.6', + contextWindow: 500_000, + thinking: { + type: 'levels', + levels: ['low', 'medium', 'high', 'xhigh'], + zeroAllowed: false, + }, }); + expect(catalog?.models[1]?.extendedContext).toBeUndefined(); + }); + + it('preserves static zeroAllowed when live level metadata omits it', () => { + const catalog = mergeCatalog('xai', [ + { + id: 'grok-4.3', + display_name: 'Grok 4.3', + thinking: { levels: ['none', 'low', 'medium', 'high'] }, + }, + ]); + + expect(catalog?.models[0]?.thinking).toMatchObject({ + type: 'levels', + levels: ['none', 'low', 'medium', 'high'], + zeroAllowed: true, + }); + }); + + it('prefers explicit live zeroAllowed over static level metadata', () => { + const catalog = mergeCatalog('xai', [ + { + id: 'grok-4.3', + display_name: 'Grok 4.3', + thinking: { + levels: ['low', 'medium', 'high'], + zero_allowed: false, + }, + }, + ]); + + expect(catalog?.models[0]?.thinking?.zeroAllowed).toBe(false); }); it('does not export extended-context capability from live xAI context length', () => { diff --git a/src/cliproxy/__tests__/model-catalog.test.js b/src/cliproxy/__tests__/model-catalog.test.js index ca020e79..e8044d46 100644 --- a/src/cliproxy/__tests__/model-catalog.test.js +++ b/src/cliproxy/__tests__/model-catalog.test.js @@ -77,6 +77,7 @@ describe('Model Catalog', () => { const ids = MODEL_CATALOG.xai.models.map((model) => model.id); assert.deepStrictEqual(ids, [ 'grok-build-0.1', + 'grok-4.6', 'grok-4.5', 'grok-4.3', 'grok-4.20-0309-reasoning', @@ -88,6 +89,18 @@ describe('Model Catalog', () => { ]); }); + it('includes Grok 4.6 with upstream reasoning metadata', () => { + const { MODEL_CATALOG } = modelCatalog; + const grok46 = MODEL_CATALOG.xai.models.find((model) => model.id === 'grok-4.6'); + + assert(grok46, 'Should include grok-4.6'); + assert.strictEqual(grok46.name, 'Grok 4.6'); + assert.strictEqual(grok46.contextWindow, 500000); + assert.strictEqual(grok46.thinking.type, 'levels'); + assert.deepStrictEqual(grok46.thinking.levels, ['low', 'medium', 'high', 'xhigh']); + assert.strictEqual(grok46.thinking.zeroAllowed, false); + }); + it('does not expose Claude [1m] suffix support for xAI model IDs', () => { const { MODEL_CATALOG, supportsExtendedContext } = modelCatalog; diff --git a/src/cliproxy/model-catalog.ts b/src/cliproxy/model-catalog.ts index 942730bb..ff2d632b 100644 --- a/src/cliproxy/model-catalog.ts +++ b/src/cliproxy/model-catalog.ts @@ -323,6 +323,18 @@ export const MODEL_CATALOG: Partial> = description: 'Fast coding model for agentic software engineering workflows', contextWindow: 256000, }, + { + id: 'grok-4.6', + name: 'Grok 4.6', + description: + "SpaceXAI's smartest model built for long-running agents, interactive and visual work.", + contextWindow: 500000, + thinking: { + type: 'levels', + levels: ['low', 'medium', 'high', 'xhigh'], + zeroAllowed: false, + }, + }, { id: 'grok-4.5', name: 'Grok 4.5', diff --git a/src/cliproxy/services/catalog-cache.ts b/src/cliproxy/services/catalog-cache.ts index bfbf0560..aafc4316 100644 --- a/src/cliproxy/services/catalog-cache.ts +++ b/src/cliproxy/services/catalog-cache.ts @@ -198,6 +198,7 @@ function mapThinking(remote?: RemoteThinkingSupport): ThinkingSupport | undefine return { type: 'levels', levels: remote.levels, + zeroAllowed: remote.zero_allowed, dynamicAllowed: remote.dynamic_allowed, }; } @@ -279,6 +280,7 @@ export function mergeCatalog( const mergedThinking = remoteEntry.thinking ? { ...remoteEntry.thinking, + zeroAllowed: remoteEntry.thinking.zeroAllowed ?? staticEntry.thinking?.zeroAllowed, maxLevel: remoteEntry.thinking.maxLevel ?? staticEntry.thinking?.maxLevel, } : staticEntry.thinking; diff --git a/ui/src/lib/model-catalogs.ts b/ui/src/lib/model-catalogs.ts index b209a653..32c3d523 100644 --- a/ui/src/lib/model-catalogs.ts +++ b/ui/src/lib/model-catalogs.ts @@ -398,6 +398,13 @@ export const MODEL_CATALOGS: Record = { haiku: 'grok-composer-2.5-fast', }, }, + { + id: 'grok-4.6', + name: 'Grok 4.6', + description: + "SpaceXAI's smartest model built for long-running agents, interactive and visual work.", + reasoningLevels: ['low', 'medium', 'high', 'xhigh'], + }, { id: 'grok-4.5', name: 'Grok 4.5', diff --git a/ui/tests/unit/ui/lib/model-catalogs-xai.test.ts b/ui/tests/unit/ui/lib/model-catalogs-xai.test.ts index bb88a8c5..6568d504 100644 --- a/ui/tests/unit/ui/lib/model-catalogs-xai.test.ts +++ b/ui/tests/unit/ui/lib/model-catalogs-xai.test.ts @@ -11,6 +11,7 @@ describe('xAI model catalog defaults', () => { expect(catalog.defaultModel).toBe('grok-build-0.1'); expect(ids).toEqual([ 'grok-build-0.1', + 'grok-4.6', 'grok-4.5', 'grok-4.3', 'grok-4.20-0309-reasoning', @@ -20,6 +21,12 @@ describe('xAI model catalog defaults', () => { 'grok-3-mini-fast', 'grok-composer-2.5-fast', ]); + expect(catalog.models.find((model) => model.id === 'grok-4.6')?.reasoningLevels).toEqual([ + 'low', + 'medium', + 'high', + 'xhigh', + ]); expect(defaultModel?.presetMapping).toEqual({ default: 'grok-build-0.1', opus: 'grok-4.5', diff --git a/ui/tests/unit/ui/lib/reasoning-control.test.ts b/ui/tests/unit/ui/lib/reasoning-control.test.ts index a31f815f..a301070b 100644 --- a/ui/tests/unit/ui/lib/reasoning-control.test.ts +++ b/ui/tests/unit/ui/lib/reasoning-control.test.ts @@ -13,6 +13,11 @@ const xaiCatalog: ProviderCatalog = { defaultModel: 'grok-build-0.1', models: [ { id: 'grok-build-0.1', name: 'Grok Build 0.1' }, + { + id: 'grok-4.6', + name: 'Grok 4.6', + reasoningLevels: ['low', 'medium', 'high', 'xhigh'], + }, { id: 'grok-4.5', name: 'Grok 4.5', reasoningLevels: ['low', 'medium', 'high'] }, { id: 'grok-4.3', name: 'Grok 4.3', reasoningLevels: ['none', 'low', 'medium', 'high'] }, ], @@ -96,6 +101,7 @@ describe('xai adapter', () => { const adapter = requireAdapter('xai'); it('returns per-model levels from catalog metadata', () => { + expect(adapter.getOptions('grok-4.6', xaiCatalog)).toEqual(['low', 'medium', 'high', 'xhigh']); expect(adapter.getOptions('grok-4.5', xaiCatalog)).toEqual(['low', 'medium', 'high']); expect(adapter.getOptions('grok-4.3', xaiCatalog)).toEqual(['none', 'low', 'medium', 'high']); }); @@ -107,6 +113,7 @@ describe('xai adapter', () => { }); it('writes and clears suffixed values through apply', () => { + expect(adapter.apply('grok-4.6', 'xhigh')).toBe('grok-4.6(xhigh)'); expect(adapter.apply('grok-4.5', 'high')).toBe('grok-4.5(high)'); expect(adapter.apply('grok-4.5(high)', undefined)).toBe('grok-4.5'); expect(adapter.apply('grok-4.3', 'none')).toBe('grok-4.3(none)');