diff --git a/src/main/text-generation/commit-message-model-discovery-policy.ts b/src/main/text-generation/commit-message-model-discovery-policy.ts index 94fb6c81e49..88b8e1680e1 100644 --- a/src/main/text-generation/commit-message-model-discovery-policy.ts +++ b/src/main/text-generation/commit-message-model-discovery-policy.ts @@ -57,7 +57,10 @@ export function finalizeModelDiscoveryOutput( } return { success: false, error: `${spec.label} returned no available models.` } } - const defaultModelId = models.some((model) => model.id === spec.defaultModelId) + // A sentinel model in the static spec (for example `default`) means the CLI + // should keep its configured provider even when discovery lists concrete models. + const defaultModelId = + spec.defaultModelId === 'default' || models.some((model) => model.id === spec.defaultModelId) ? spec.defaultModelId : models[0].id return staticModelDiscoveryResult(spec, models, defaultModelId, 'probe') diff --git a/src/main/text-generation/commit-message-text-generation-model-discovery.test.ts b/src/main/text-generation/commit-message-text-generation-model-discovery.test.ts index 9a1f932f0a0..d935dc33c92 100644 --- a/src/main/text-generation/commit-message-text-generation-model-discovery.test.ts +++ b/src/main/text-generation/commit-message-text-generation-model-discovery.test.ts @@ -273,8 +273,32 @@ describe('discoverCommitMessageModelsLocal', () => { await expect(pending).resolves.toMatchObject({ success: true, - defaultModelId: 'github-copilot/gpt-5.4-mini', - models: [{ id: 'github-copilot/gpt-5.4-mini' }] + defaultModelId: 'default', + models: [{ id: 'default' }] + }) + }) + + it('falls back to the first discovered non-Pi model when its static default is unavailable', async () => { + const listeners = new Map void>() + const child = { + pid: 123, + kill: vi.fn(), + stdout: { on: vi.fn((event, callback) => listeners.set(`stdout:${event}`, callback)) }, + stderr: { on: vi.fn((event, callback) => listeners.set(`stderr:${event}`, callback)) }, + stdin: { end: vi.fn() }, + on: vi.fn((event, callback) => listeners.set(event, callback)) + } + spawnMock.mockReturnValue(child as never) + + const pending = discoverCommitMessageModelsLocal('cursor', undefined) + + listeners.get('stdout:data')?.(Buffer.from('gpt-5.2 - GPT-5.2\n')) + listeners.get('close')?.(0) + + await expect(pending).resolves.toMatchObject({ + success: true, + defaultModelId: 'gpt-5.2', + models: [{ id: 'gpt-5.2' }] }) }) @@ -305,7 +329,7 @@ describe('discoverCommitMessageModelsLocal', () => { await expect(pending).resolves.toMatchObject({ success: true, - defaultModelId: 'github-copilot/gpt-5.4-mini', + defaultModelId: 'default', models: [{ id: 'github-copilot/gpt-5.4-mini' }, { id: 'openai-codex/gpt-5.5' }] }) }) diff --git a/src/renderer/src/lib/source-control-generation-plan.test.ts b/src/renderer/src/lib/source-control-generation-plan.test.ts index e769c3f3ef0..ada86abc2cc 100644 --- a/src/renderer/src/lib/source-control-generation-plan.test.ts +++ b/src/renderer/src/lib/source-control-generation-plan.test.ts @@ -88,6 +88,20 @@ describe('planSourceControlCommitMessageGeneration', () => { expect(result.ok && result.commandLabel).toBe('echo issue {linkedIssue}') }) + it('omits Pi configured-default sentinel from dry-run command labels', () => { + const result = planSourceControlTextGeneration('commitMessage', { + agentId: 'pi', + model: 'default', + commandInputTemplate: '{basePrompt}' + }) + + expect(result.ok).toBe(true) + if (result.ok) { + expect(result.commandLabel).not.toContain('--model') + expect(result.commandLabel).not.toContain('default') + } + }) + it('shows per-action CLI arguments in dry-run command labels', () => { const result = planSourceControlTextGeneration('pullRequest', { agentId: 'codex', diff --git a/src/shared/commit-message-agent-spec.test.ts b/src/shared/commit-message-agent-spec.test.ts index ae068ad8759..ffaec29c605 100644 --- a/src/shared/commit-message-agent-spec.test.ts +++ b/src/shared/commit-message-agent-spec.test.ts @@ -46,7 +46,7 @@ describe('COMMIT_MESSAGE_AGENT_SPECS', () => { it('uses the strongest available defaults for core agents', () => { expect(COMMIT_MESSAGE_AGENT_SPECS.claude?.defaultModelId).toBe('sonnet') expect(COMMIT_MESSAGE_AGENT_SPECS.codex?.defaultModelId).toBe('gpt-5.5') - expect(COMMIT_MESSAGE_AGENT_SPECS.pi?.defaultModelId).toBe('github-copilot/gpt-5.4-mini') + expect(COMMIT_MESSAGE_AGENT_SPECS.pi?.defaultModelId).toBe('default') }) it('uses --prompt (not Claude --print) for Kimi non-interactive generation', () => { @@ -612,3 +612,24 @@ describe('buildArgs (Antigravity)', () => { expect(COMMIT_MESSAGE_AGENT_SPECS.antigravity?.defaultModelId).toBe('Gemini 3.5 Flash (Medium)') }) }) + + +describe('Pi Source Control AI model selection', () => { + it('leaves provider selection to Pi for the config default', () => { + const args = getCommitMessageAgentSpec('pi')!.buildArgs({ + prompt: 'Name a branch', + model: 'default' + }) + expect(args).not.toContain('--model') + }) + + it('passes an explicit discovered Pi model through', () => { + const args = getCommitMessageAgentSpec('pi')!.buildArgs({ + prompt: 'Name a branch', + model: 'openai-codex/gpt-5.5' + }) + const modelFlagIndex = args.indexOf('--model') + expect(modelFlagIndex).toBeGreaterThanOrEqual(0) + expect(args[modelFlagIndex + 1]).toBe('openai-codex/gpt-5.5') + }) +}) diff --git a/src/shared/commit-message-agent-specs-primary.ts b/src/shared/commit-message-agent-specs-primary.ts index 3e42a4c5865..7327b3216dc 100644 --- a/src/shared/commit-message-agent-specs-primary.ts +++ b/src/shared/commit-message-agent-specs-primary.ts @@ -201,22 +201,20 @@ export function buildPrimaryCommitMessageAgentSpecs({ '--no-context-files', '--mode', 'text', - '--model', - model, + ...(model && model !== 'default' ? ['--model', model] : []), ...(thinkingLevel ? ['--thinking', thinkingLevel] : []) ], modelSource: 'dynamic', modelDiscovery: { binary: 'pi', args: ['--list-models'], parse: parsePiModels }, models: [ { - // Why: Pi commonly authenticates through GitHub Copilot locally; using - // that provider avoids selecting a raw OpenAI model when no key exists. - id: 'github-copilot/gpt-5.4-mini', - label: 'Github Copilot GPT 5.4 Mini', - ...withOpenAiThinking('gpt-5.4-mini') + // Why: the unqualified choice lets Pi use its configured provider and + // avoids forcing GitHub Copilot credentials during automation. + id: 'default', + label: 'Config default' } ], - defaultModelId: 'github-copilot/gpt-5.4-mini' + defaultModelId: 'default' } } }