From a94b3453137ed690d2cae7090aecf8ce3bda028b Mon Sep 17 00:00:00 2001 From: Neil Date: Sat, 19 Sep 2026 00:16:57 -0700 Subject: [PATCH] fix(antigravity): discover current source-control models and use CLI defaults --- .../source-control-action-recipe-options.ts | 1 + src/shared/antigravity-source-control.test.ts | 65 +++++++++++++++++++ src/shared/commit-message-agent-spec.test.ts | 37 ++++++++++- .../commit-message-agent-specs-secondary.ts | 16 +++-- src/shared/commit-message-model-parsers.ts | 15 ++++- 5 files changed, 122 insertions(+), 12 deletions(-) create mode 100644 src/shared/antigravity-source-control.test.ts diff --git a/src/renderer/src/components/settings/source-control-action-recipe-options.ts b/src/renderer/src/components/settings/source-control-action-recipe-options.ts index dfeaeda128b..89fa901eba1 100644 --- a/src/renderer/src/components/settings/source-control-action-recipe-options.ts +++ b/src/renderer/src/components/settings/source-control-action-recipe-options.ts @@ -59,6 +59,7 @@ export const getActionDescriptions = createLocalizedCatalog( const FALLBACK_AGENT_ARGS_PLACEHOLDER = '--model sonnet' const AGENT_ARGS_PLACEHOLDER_OVERRIDES: Partial> = { + antigravity: '--effort low', // Why: Source Control AI action prompts are short, reviewable tasks; the // mini Codex model is a better default hint than the frontier model. codex: '--model gpt-5.4-mini', diff --git a/src/shared/antigravity-source-control.test.ts b/src/shared/antigravity-source-control.test.ts new file mode 100644 index 00000000000..ed35713df29 --- /dev/null +++ b/src/shared/antigravity-source-control.test.ts @@ -0,0 +1,65 @@ +import { describe, expect, it } from 'vitest' +import { getDefaultSettings } from './constants' +import { resolveSourceControlAiForOperation } from './source-control-ai' +import { planCommitMessageGeneration } from './commit-message-plan' + +describe('Antigravity source-control model selection', () => { + for (const host of ['local', 'ssh:verification-host']) { + for (const operation of ['commitMessage', 'branchName', 'pullRequest'] as const) { + it(`${operation} on ${host} falls back from a retired saved model to the CLI configuration`, () => { + const settings = getDefaultSettings('/tmp') + settings.sourceControlAi = { + ...settings.sourceControlAi!, + enabled: true, + agentId: 'antigravity', + selectedModelByAgent: { antigravity: 'Gemini 3.5 Flash (Medium)' }, + selectedModelByAgentByHost: { + [host]: { antigravity: 'Gemini 3.5 Flash (Medium)' } + }, + discoveredModelsByAgent: {}, + discoveredModelsByAgentByHost: {} + } + const result = resolveSourceControlAiForOperation({ + settings, + repo: null, + operation, + discoveryHostKey: host + }) + expect(result.ok).toBe(true) + if (!result.ok) { + throw new Error(result.error) + } + expect(result.value.params.model).toBe('default') + const plan = planCommitMessageGeneration(result.value.params, 'Write Git text') + expect(plan.ok).toBe(true) + if (!plan.ok) { + throw new Error(plan.error) + } + expect(plan.plan.args).toEqual(['--print=Write Git text', '--sandbox']) + expect(plan.plan.stdinPayload).toBeNull() + }) + } + } + + it('lets explicit recipe arguments override the discovered model and requested effort once', () => { + const result = planCommitMessageGeneration( + { + agentId: 'antigravity', + model: 'gemini-3.8-flash-medium', + thinkingLevel: 'medium', + agentArgs: '--model gemini-3.7-flash-low --effort low' + }, + 'Write Git text' + ) + expect(result.ok).toBe(true) + if (!result.ok) { + throw new Error(result.error) + } + expect(result.plan.args.filter((arg) => arg === '--model')).toHaveLength(1) + expect(result.plan.args.filter((arg) => arg === '--effort')).toHaveLength(1) + expect(result.plan.args).not.toContain('gemini-3.8-flash-medium') + expect(result.plan.args).not.toContain('medium') + expect(result.plan.args).toContain('gemini-3.7-flash-low') + expect(result.plan.args).toContain('low') + }) +}) diff --git a/src/shared/commit-message-agent-spec.test.ts b/src/shared/commit-message-agent-spec.test.ts index de5cc14529b..00d88162c49 100644 --- a/src/shared/commit-message-agent-spec.test.ts +++ b/src/shared/commit-message-agent-spec.test.ts @@ -607,7 +607,40 @@ describe('buildArgs (Antigravity)', () => { expect(spec.modelDiscovery?.args).toEqual(['models']) }) - it('uses Gemini 3.5 Flash (Medium) as default model', () => { - expect(COMMIT_MESSAGE_AGENT_SPECS.antigravity?.defaultModelId).toBe('Gemini 3.5 Flash (Medium)') + it('uses the configured CLI model instead of a bundled model that can retire', () => { + expect(spec.defaultModelId).toBe('default') + expect( + spec.buildArgs({ prompt: 'Generate a commit message', model: spec.defaultModelId }) + ).toEqual(['--print=Generate a commit message', '--sandbox']) + }) + + it('passes only a nonempty requested effort', () => { + expect(spec.buildArgs({ prompt: 'P', model: 'default', thinkingLevel: '' })).not.toContain( + '--effort' + ) + expect(spec.buildArgs({ prompt: 'P', model: 'default', thinkingLevel: 'high' })).toEqual([ + '--print=P', + '--sandbox', + '--effort', + 'high' + ]) + }) + + it('parses current tab-separated IDs without treating progress text as a model', () => { + expect( + parseAntigravityModels( + [ + 'Fetching available models...', + 'id\tLabel', + 'gemini-3.8-flash-medium\tGemini 3.8 Flash (Medium)', + 'claude-sonnet-4-6\tClaude Sonnet 4.6 (Thinking)', + 'gemini-3.8-flash-medium\tGemini 3.8 Flash (Medium)', + '' + ].join('\r\n') + ) + ).toEqual([ + { id: 'gemini-3.8-flash-medium', label: 'Gemini 3.8 Flash (Medium)' }, + { id: 'claude-sonnet-4-6', label: 'Claude Sonnet 4.6 (Thinking)' } + ]) }) }) diff --git a/src/shared/commit-message-agent-specs-secondary.ts b/src/shared/commit-message-agent-specs-secondary.ts index 2ad7691ac36..53992143d19 100644 --- a/src/shared/commit-message-agent-specs-secondary.ts +++ b/src/shared/commit-message-agent-specs-secondary.ts @@ -216,15 +216,17 @@ export function buildSecondaryCommitMessageAgentSpecs({ // using `--print=` so a leading-dash prompt binds to the flag instead of // being parsed as its own option, and --sandbox/--model stay separate options. promptDelivery: 'argv', - buildArgs: ({ prompt, model }) => [`--print=${prompt}`, '--sandbox', '--model', model], + buildArgs: ({ prompt, model, thinkingLevel }) => [ + `--print=${prompt}`, + '--sandbox', + ...(model && model !== 'default' ? ['--model', model] : []), + ...(thinkingLevel ? ['--effort', thinkingLevel] : []) + ], + singletonOptions: [['--model'], ['--effort']], modelSource: 'dynamic', modelDiscovery: { binary: 'agy', args: ['models'], parse: parseAntigravityModels }, - models: [ - { id: 'Gemini 3.5 Flash (Medium)', label: 'Gemini 3.5 Flash (Medium)' }, - { id: 'Gemini 3.5 Flash (High)', label: 'Gemini 3.5 Flash (High)' }, - { id: 'Gemini 3.5 Flash (Low)', label: 'Gemini 3.5 Flash (Low)' } - ], - defaultModelId: 'Gemini 3.5 Flash (Medium)' + models: [{ id: 'default', label: 'Config default' }], + defaultModelId: 'default' } } } diff --git a/src/shared/commit-message-model-parsers.ts b/src/shared/commit-message-model-parsers.ts index 036557d5b89..072033e305e 100644 --- a/src/shared/commit-message-model-parsers.ts +++ b/src/shared/commit-message-model-parsers.ts @@ -235,13 +235,22 @@ export function parseCursorModels(stdout: string): CommitMessageModel[] { export function parseAntigravityModels(stdout: string): CommitMessageModel[] { const models: CommitMessageModel[] = [] for (const rawLine of iterateModelOutputLines(stdout)) { - const id = rawLine.trim() - if (id.length === 0) { + const line = rawLine.trim() + const separator = line.indexOf('\t') + const id = (separator === -1 ? line : line.slice(0, separator)).trim() + const label = separator === -1 ? id : line.slice(separator + 1).trim() + // Older agy versions list display names; current versions emit idlabel. + if ( + !id || + !label || + (separator === -1 && !/^.+ \((?:low|medium|high|thinking)\)$/i.test(id)) || + (separator !== -1 && (/\s/.test(id) || /^(?:id|model)$/i.test(id))) + ) { continue } models.push({ id, - label: id + label }) } return uniqueModels(models)