fix(pi): use Pi configured provider for Source Control AI defaults (#21693)

* fix(pi): let Source Control AI use Pi configured default

When Orca runs Pi for automatic branch names or commit messages without an explicit model override, omit --model so Pi resolves its configured provider. Preserve explicit discovered model selection and add regression coverage.

* fix(pi): preserve discovered fallback for non-Pi agents

Keep the configured-default sentinel behavior limited to agents whose default is the explicit CLI sentinel. Other dynamic agents still fall back to the first discovered model when their static default is unavailable.

* test(pi): pin configured-default dry-run arguments

Prove Source Control AI does not render the Pi configured-default sentinel as a literal model argument, and assert explicit model flag pairing positionally.
This commit is contained in:
Neil
2026-09-19 10:14:51 -07:00
committed by GitHub
parent 3ad6b7e46e
commit b0cbb919ba
5 changed files with 73 additions and 13 deletions
@@ -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')
@@ -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<string, (value: unknown) => 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' }]
})
})
@@ -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',
+22 -1
View File
@@ -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')
})
})
@@ -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'
}
}
}