From 7c0f3ecef90a8fc0ac607487dd41e932090e3b6b Mon Sep 17 00:00:00 2001 From: Akhilesh Kumar Date: Thu, 16 Apr 2026 21:14:05 +0000 Subject: [PATCH] feat: add support for Gemma 4 model --- packages/cli/src/acp/acpClient.ts | 5 ++++ .../src/ui/components/ModelDialog.test.tsx | 28 +++++++++++++++++++ .../cli/src/ui/components/ModelDialog.tsx | 7 +++++ .../core/src/config/defaultModelConfigs.ts | 27 ++++++++++++++++++ packages/core/src/config/models.test.ts | 14 +++++++++- packages/core/src/config/models.ts | 9 ++++++ 6 files changed, 89 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/acp/acpClient.ts b/packages/cli/src/acp/acpClient.ts index bd5a52f126..5983e8d784 100644 --- a/packages/cli/src/acp/acpClient.ts +++ b/packages/cli/src/acp/acpClient.ts @@ -40,6 +40,7 @@ import { DEFAULT_GEMINI_MODEL, DEFAULT_GEMINI_FLASH_MODEL, DEFAULT_GEMINI_FLASH_LITE_MODEL, + DEFAULT_GEMMA_4_MODEL, PREVIEW_GEMINI_MODEL, PREVIEW_GEMINI_3_1_MODEL, PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL, @@ -1654,6 +1655,10 @@ function buildAvailableModels( value: DEFAULT_GEMINI_FLASH_LITE_MODEL, title: getDisplayString(DEFAULT_GEMINI_FLASH_LITE_MODEL), }, + { + value: DEFAULT_GEMMA_4_MODEL, + title: getDisplayString(DEFAULT_GEMMA_4_MODEL), + }, ]; if (shouldShowPreviewModels) { diff --git a/packages/cli/src/ui/components/ModelDialog.test.tsx b/packages/cli/src/ui/components/ModelDialog.test.tsx index 2f1fde86b9..bc4fe33f77 100644 --- a/packages/cli/src/ui/components/ModelDialog.test.tsx +++ b/packages/cli/src/ui/components/ModelDialog.test.tsx @@ -15,6 +15,7 @@ import { DEFAULT_GEMINI_MODEL_AUTO, DEFAULT_GEMINI_FLASH_MODEL, DEFAULT_GEMINI_FLASH_LITE_MODEL, + DEFAULT_GEMMA_4_MODEL, PREVIEW_GEMINI_MODEL, PREVIEW_GEMINI_3_1_MODEL, PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL, @@ -212,6 +213,33 @@ describe('', () => { unmount(); }); + it('shows Gemma 4 in manual view', async () => { + mockGetDisplayString.mockImplementation((val: string) => { + if (val === DEFAULT_GEMMA_4_MODEL) return 'Gemma 4 (26B)'; + return val; + }); + + const { lastFrame, stdin, waitUntilReady, unmount } = + await renderComponent(); + + // Select "Manual" (index 1) + await act(async () => { + stdin.write('\u001B[B'); // Arrow Down + }); + await waitUntilReady(); + await act(async () => { + stdin.write('\r'); + }); + await waitUntilReady(); + + // Should now show manual options including Gemma 4 + await waitFor(() => { + const output = lastFrame(); + expect(output).toContain('Gemma 4 (26B)'); + }); + unmount(); + }); + it('sets model and closes when a model is selected in "main" view', async () => { const { stdin, waitUntilReady, unmount } = await renderComponent(); diff --git a/packages/cli/src/ui/components/ModelDialog.tsx b/packages/cli/src/ui/components/ModelDialog.tsx index b8ff3f251a..32caa71acf 100644 --- a/packages/cli/src/ui/components/ModelDialog.tsx +++ b/packages/cli/src/ui/components/ModelDialog.tsx @@ -17,6 +17,7 @@ import { DEFAULT_GEMINI_FLASH_MODEL, DEFAULT_GEMINI_FLASH_LITE_MODEL, DEFAULT_GEMINI_MODEL_AUTO, + DEFAULT_GEMMA_4_MODEL, ModelSlashCommandEvent, logModelSlashCommand, getDisplayString, @@ -72,6 +73,7 @@ export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element { DEFAULT_GEMINI_MODEL, DEFAULT_GEMINI_FLASH_MODEL, DEFAULT_GEMINI_FLASH_LITE_MODEL, + DEFAULT_GEMMA_4_MODEL, PREVIEW_GEMINI_MODEL, PREVIEW_GEMINI_3_1_MODEL, PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL, @@ -152,6 +154,11 @@ export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element { title: getDisplayString(DEFAULT_GEMINI_FLASH_LITE_MODEL), key: DEFAULT_GEMINI_FLASH_LITE_MODEL, }, + { + value: DEFAULT_GEMMA_4_MODEL, + title: getDisplayString(DEFAULT_GEMMA_4_MODEL), + key: DEFAULT_GEMMA_4_MODEL, + }, ]; if (shouldShowPreviewModels) { diff --git a/packages/core/src/config/defaultModelConfigs.ts b/packages/core/src/config/defaultModelConfigs.ts index 4a9315359b..999433ef24 100644 --- a/packages/core/src/config/defaultModelConfigs.ts +++ b/packages/core/src/config/defaultModelConfigs.ts @@ -89,6 +89,17 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = { model: 'gemini-2.5-flash-lite', }, }, + gemma4: { + extends: 'base', + modelConfig: { + model: 'gemma-4-26b-a4b-it', + generateContentConfig: { + temperature: 1, + topP: 0.95, + topK: 64, + }, + }, + }, // Bases for the internal model configs. 'gemini-2.5-flash-base': { extends: 'base', @@ -300,6 +311,13 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = { isVisible: true, features: { thinking: false, multimodalToolUse: false }, }, + 'gemma-4-26b-a4b-it': { + tier: 'pro', + family: 'gemma-4', + isPreview: false, + isVisible: true, + features: { thinking: false, multimodalToolUse: false }, + }, // Aliases auto: { tier: 'auto', @@ -307,6 +325,12 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = { isVisible: false, features: { thinking: true, multimodalToolUse: false }, }, + gemma4: { + tier: 'pro', + isPreview: false, + isVisible: true, + features: { thinking: false, multimodalToolUse: false }, + }, pro: { tier: 'pro', isPreview: false, @@ -416,6 +440,9 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = { 'flash-lite': { default: 'gemini-2.5-flash-lite', }, + gemma4: { + default: 'gemma-4-26b-a4b-it', + }, }, classifierIdResolutions: { flash: { diff --git a/packages/core/src/config/models.test.ts b/packages/core/src/config/models.test.ts index 9aa1e00058..4cd8594824 100644 --- a/packages/core/src/config/models.test.ts +++ b/packages/core/src/config/models.test.ts @@ -15,12 +15,14 @@ import { isAutoModel, getDisplayString, DEFAULT_GEMINI_MODEL, + DEFAULT_GEMMA_4_MODEL, PREVIEW_GEMINI_MODEL, DEFAULT_GEMINI_FLASH_MODEL, DEFAULT_GEMINI_FLASH_LITE_MODEL, supportsMultimodalFunctionResponse, GEMINI_MODEL_ALIAS_PRO, GEMINI_MODEL_ALIAS_FLASH, + GEMMA_MODEL_ALIAS_4, GEMINI_MODEL_ALIAS_AUTO, PREVIEW_GEMINI_FLASH_MODEL, PREVIEW_GEMINI_MODEL_AUTO, @@ -336,6 +338,10 @@ describe('getDisplayString', () => { ); }); + it('should return the correct display string for Gemma 4', () => { + expect(getDisplayString(DEFAULT_GEMMA_4_MODEL)).toBe('Gemma 4 (26B)'); + }); + it('should return the model name as is for other models', () => { expect(getDisplayString('custom-model')).toBe('custom-model'); expect(getDisplayString(DEFAULT_GEMINI_FLASH_LITE_MODEL)).toBe( @@ -392,6 +398,11 @@ describe('resolveModel', () => { ); }); + it('should return Gemma 4 when gemma4 alias is requested', () => { + const model = resolveModel(GEMMA_MODEL_ALIAS_4); + expect(model).toBe(DEFAULT_GEMMA_4_MODEL); + }); + it('should return a custom model name when requested', () => { const customModel = 'custom-model-v1'; const model = resolveModel(customModel); @@ -537,9 +548,10 @@ describe('isActiveModel', () => { expect(isActiveModel(PREVIEW_GEMINI_MODEL)).toBe(true); expect(isActiveModel(DEFAULT_GEMINI_FLASH_MODEL)).toBe(true); expect(isActiveModel(PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL)).toBe(true); + expect(isActiveModel(DEFAULT_GEMMA_4_MODEL)).toBe(true); }); - it('should return true for unknown models and aliases', () => { + it('should return false for unknown models and aliases', () => { expect(isActiveModel('invalid-model')).toBe(false); expect(isActiveModel(GEMINI_MODEL_ALIAS_AUTO)).toBe(false); }); diff --git a/packages/core/src/config/models.ts b/packages/core/src/config/models.ts index 7e1a57c5c3..b5077f8f30 100644 --- a/packages/core/src/config/models.ts +++ b/packages/core/src/config/models.ts @@ -59,6 +59,7 @@ export const PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL = export const DEFAULT_GEMINI_MODEL = 'gemini-2.5-pro'; export const DEFAULT_GEMINI_FLASH_MODEL = 'gemini-2.5-flash'; export const DEFAULT_GEMINI_FLASH_LITE_MODEL = 'gemini-2.5-flash-lite'; +export const DEFAULT_GEMMA_4_MODEL = 'gemma-4-26b-a4b-it'; export const VALID_GEMINI_MODELS = new Set([ PREVIEW_GEMINI_MODEL, @@ -69,6 +70,7 @@ export const VALID_GEMINI_MODELS = new Set([ DEFAULT_GEMINI_MODEL, DEFAULT_GEMINI_FLASH_MODEL, DEFAULT_GEMINI_FLASH_LITE_MODEL, + DEFAULT_GEMMA_4_MODEL, ]); export const PREVIEW_GEMINI_MODEL_AUTO = 'auto-gemini-3'; @@ -79,6 +81,7 @@ export const GEMINI_MODEL_ALIAS_AUTO = 'auto'; export const GEMINI_MODEL_ALIAS_PRO = 'pro'; export const GEMINI_MODEL_ALIAS_FLASH = 'flash'; export const GEMINI_MODEL_ALIAS_FLASH_LITE = 'flash-lite'; +export const GEMMA_MODEL_ALIAS_4 = 'gemma4'; export const DEFAULT_GEMINI_EMBEDDING_MODEL = 'gemini-embedding-001'; @@ -136,6 +139,10 @@ export function resolveModel( resolved = DEFAULT_GEMINI_FLASH_LITE_MODEL; break; } + case GEMMA_MODEL_ALIAS_4: { + resolved = DEFAULT_GEMMA_4_MODEL; + break; + } default: { resolved = requestedModel; break; @@ -236,6 +243,8 @@ export function getDisplayString( return PREVIEW_GEMINI_FLASH_MODEL; case PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL: return PREVIEW_GEMINI_3_1_MODEL; + case DEFAULT_GEMMA_4_MODEL: + return 'Gemma 4 (26B)'; default: return model; }