fix(pi): start Pi terminal launches on the model picked in the launcher (#41005)

Pi had no launch catalog, so a picked model compiled to no arguments.
A launch-only catalog maps it to `pi --model <provider/id>`. A --model
in Pi's Arguments stays literal: Pi's CLI fuzzy-matches it, while Pi
chats need an exact provider/id.

Co-authored-by: lucas-wu-wakakak <8309814+lucas-wu-wakakak@users.noreply.github.com>
This commit is contained in:
Kelvin Amoaba
2026-11-11 03:55:39 +00:00
committed by GitHub
co-authored by lucas-wu-wakakak
parent 59fd2dac7c
commit bb1d4789c4
6 changed files with 90 additions and 14 deletions
@@ -12,20 +12,10 @@ import {
type AgentStartupShell
} from '../startup/tui-agent-startup-shell'
import type { TuiAgent } from '../catalog/tui-agent'
import type { ChoiceOccurrence } from './agent-launch-choices'
import { agentChoiceConfigString, agentChoiceConfigValue } from './agent-choice-cli-apply'
export type ChoiceOccurrence = {
key: string
value: string
valueType?: 'string' | 'boolean'
source?: boolean
words: string[]
start: number
end: number
lift: boolean
owned: boolean
replacement?: string
}
export type { ChoiceOccurrence } from './agent-launch-choices'
/** Every cut uses the original spans and preserves operand roles in its consuming grammar. */
export function analyzeChoiceArgs(
@@ -137,6 +127,10 @@ export function analyzeChoiceArgs(
(!inline || name.startsWith('--')) &&
!!value &&
!value.startsWith('-')
if (form.kind === 'value' && form.recognizes?.(value) === false) {
valueType = undefined
lift = false
}
if (form.kind === 'settings') {
const effective = effectiveSettings.get(form)
if (effective?.index !== index || opaqueKeys.has(key)) {
@@ -92,3 +92,25 @@ it.each(['true', '42', '["high"]', '{key="high"}'])(
expect(result.userArgs).toBe(userArgs)
}
)
it('keeps a Pi model in Arguments literal so chats never receive a CLI pattern', () => {
const lift = (userArgs: string) =>
reconcileAgentChoiceArgsDetailed({
agent: 'pi',
userArgs,
choices: {},
mode: 'arguments-edit',
keys
})
for (const userArgs of [
'--model sonnet:high',
'--model anthropic/sonnet',
'--model anthropic/claude-opus-5-5',
'--provider openai --model gpt-6'
]) {
const result = lift(userArgs)
expect(result.recognized).toEqual([])
expect(result.choices).toEqual({})
expect(result.userArgs).toBe(userArgs)
}
})
@@ -25,11 +25,31 @@ export function terminalAgentChoices(
export type AgentLaunchChoices = Partial<Record<TuiAgent, AgentModelOptionChoices>>
export type AgentChoiceCliForm =
| { kind: 'value'; names: readonly string[]; lift: 'separate-and-long-equals' | 'never' }
| {
kind: 'value'
names: readonly string[]
lift: 'separate-and-long-equals' | 'never'
/** Values outside the choice's grammar stay literal arguments instead of becoming choices. */
recognizes?: (value: string) => boolean
}
| { kind: 'config'; names: readonly string[]; key: string; lift: 'codex-bare-string' | 'never' }
| { kind: 'settings'; names: readonly string[]; key: string; lift: 'merged-json' }
| { kind: 'recipe'; words: readonly string[]; value: AgentPermissionChoice }
/** One choice flag found in free-form agent arguments. */
export type ChoiceOccurrence = {
key: string
value: string
valueType?: 'string' | 'boolean'
source?: boolean
words: string[]
start: number
end: number
lift: boolean
owned: boolean
replacement?: string
}
export function normalizeAgentModelOptionChoices(
value: unknown
): AgentModelOptionChoices | undefined {
@@ -41,6 +41,20 @@ describe('tui agent startup session options', () => {
expect(plan?.sessionOptions).toEqual({ model: 'zai-coding-plan/glm-5.3-flash' })
})
it('starts Pi on the picked model in place of its configured one', () => {
const plan = buildAgentStartupPlan({
agent: 'pi',
prompt: '',
cmdOverrides: {},
platform: 'linux',
allowEmptyPromptLaunch: true,
sessionOptions: { model: 'anthropic/claude-opus-5-5' },
sessionOptionsOverrideAgentArgs: true,
agentArgs: '--model openai/gpt-6 --no-skills'
})
expect(plan?.launchCommand).toBe("pi '--no-skills' '--model' 'anthropic/claude-opus-5-5'")
})
it('rejects conflicting singleton flags in an agent command override', () => {
expect(
resolveAgentLaunchCommand({
@@ -0,0 +1,21 @@
import { agentChoiceCliApply } from '../../agent-launch/choices/agent-choice-cli-apply'
import type { AgentSessionOptionCatalog } from './agent-session-option-catalog-types'
// Why: terminal launches need Pi's `--model`; Pi chats keep applying picks over RPC.
export const PI_LAUNCH_OPTION_CATALOG: AgentSessionOptionCatalog = {
// Pi models depend on configured providers; ids are opaque `provider/id` selectors.
models: [],
modelApply: agentChoiceCliApply(
'pi',
[
{
kind: 'value',
names: ['--model'],
lift: 'separate-and-long-equals',
// Pi's CLI fuzzy-matches `--model`; chats need exact ids, so Arguments never become a model choice.
recognizes: () => false
}
],
'--model'
)
}
@@ -13,6 +13,7 @@ import {
import { GROK_SESSION_OPTION_CATALOG } from './agent-session-option-catalog-grok'
import { MUSE_SESSION_OPTION_CATALOG } from './agent-session-option-catalog-muse'
import { OMP_SESSION_OPTION_CATALOG } from './agent-session-option-catalog-omp'
import { PI_LAUNCH_OPTION_CATALOG } from './agent-session-option-catalog-pi'
import type {
AgentSessionOptionCatalog,
AgentSessionOptionCatalogMap,
@@ -91,5 +92,9 @@ export function sessionOptionValueIsValid(value: unknown): value is SessionOptio
export function getAgentSessionOptionLaunchCatalog(
agent: AgentType
): AgentSessionOptionCatalog | null {
return agent === 'opencode' ? OPENCODE_LAUNCH_OPTION_CATALOG : getAgentSessionOptionCatalog(agent)
return agent === 'opencode'
? OPENCODE_LAUNCH_OPTION_CATALOG
: agent === 'pi'
? PI_LAUNCH_OPTION_CATALOG
: getAgentSessionOptionCatalog(agent)
}