Files
hapi/shared/src/models.ts
T
Junmo KimandGitHub ec08959f07 fix(agy): show Gemini 3.7 Flash in the agy model list (#1585)
* refactor(agy): extract the agy models probe from the fetch flow

One invocation and the decision of what to do with its output were
tangled in a single promise. Split them so the probe can be given
different arguments, and run more than once, without duplicating the
stream and timeout handling.

No behaviour change: the same argument vector, timeout, stream
handling and fallbacks remain, and the existing tests are untouched.

* fix(agy): read the model list from agy's structured output

The picker recovered ids and labels from `agy models`, whose table is
meant for people to read: it has emitted display names only, then
bare ids, then tab-separated id/label pairs over the 1.1.x line, and
each shape change silently sent the probe back to the hardcoded
mirror. Since agy started offering Gemini 3.7 Flash, that mirror is
what users see, so the three new entries never appear.

agy publishes the same listing as a structured payload, so read that
instead: `agy --output-format=json models` returns
`command.data.models[] = {id, label}`. The flag is global, so it goes
before the subcommand and takes its `=` form.

Releases that predate the flag ignore it and print the table, so the
text parser stays as the fallback, and it now also understands the
tab-separated shape. Every release checked, 1.0.16 through 1.1.13,
ignores the flag rather than rejecting it; a build that rejected it
would emit no models at all, so ask once more without the flag when
the first invocation yields nothing either parser can read.

* fix(agy): follow the Gemini 3.7 Flash row in the model picker

agy now lists Gemini 3.7 Flash as the top row of the `/model` TUI
picker, but the hardcoded row table still starts at 3.6 Flash. A
session whose current model is that row can never change its model:
the picker cannot identify the current row and the change is
rejected.

Add the 3.7 row to MODEL_ROWS/TARGETS. Every existing row shifts
down by one, which leaves their relative deltas, and therefore the
navigation keys emitted between them, unchanged. Mirror the three
new ids into AGY_MODEL_LABELS so the session pickers offer them too.
2026-08-26 09:37:58 +08:00

69 lines
2.9 KiB
TypeScript

export const CLAUDE_MODEL_LABELS = {
sonnet: 'Sonnet',
'sonnet[1m]': 'Sonnet 1M',
opus: 'Opus',
'opus[1m]': 'Opus 1M',
fable: 'Fable',
'fable[1m]': 'Fable 1M'
} as const
export type ClaudeModelPreset = keyof typeof CLAUDE_MODEL_LABELS
export const CLAUDE_MODEL_PRESETS = Object.keys(CLAUDE_MODEL_LABELS) as ClaudeModelPreset[]
export const GEMINI_MODEL_LABELS = {
'gemini-3.1-pro-preview': 'Gemini 3.1 Pro Preview',
'gemini-3-flash-preview': 'Gemini 3 Flash Preview',
'gemini-2.5-pro': 'Gemini 2.5 Pro',
'gemini-2.5-flash': 'Gemini 2.5 Flash',
'gemini-2.5-flash-lite': 'Gemini 2.5 Flash Lite',
} as const
export type GeminiModelPreset = keyof typeof GEMINI_MODEL_LABELS
export const GEMINI_MODEL_PRESETS = Object.keys(GEMINI_MODEL_LABELS) as GeminiModelPreset[]
export const DEFAULT_GEMINI_MODEL: GeminiModelPreset = 'gemini-2.5-pro'
// Order and labels mirror `agy models` output (the agy CLI's own listing) so the
// HAPI picker matches what users see in the terminal. IDs follow agy's
// `<model>-<effort>` convention (e.g. `gemini-3.5-flash-low` verified accepted by
// `agy --model`). NOTE: agy fetches the live list server-side and `agy models`
// needs an interactive keyring unlock, so this stays a hand-maintained mirror —
// update it when agy's listing changes.
export const AGY_MODEL_LABELS = {
'gemini-3.7-flash-high': 'Gemini 3.7 Flash (High)',
'gemini-3.7-flash-medium': 'Gemini 3.7 Flash (Medium)',
'gemini-3.7-flash-low': 'Gemini 3.7 Flash (Low)',
'gemini-3.6-flash-high': 'Gemini 3.6 Flash (High)',
'gemini-3.6-flash-medium': 'Gemini 3.6 Flash (Medium)',
'gemini-3.6-flash-low': 'Gemini 3.6 Flash (Low)',
'gemini-3.5-flash-medium': 'Gemini 3.5 Flash (Medium)',
'gemini-3.5-flash-high': 'Gemini 3.5 Flash (High)',
'gemini-3.5-flash-low': 'Gemini 3.5 Flash (Low)',
'gemini-3.1-pro-low': 'Gemini 3.1 Pro (Low)',
'gemini-3.1-pro-high': 'Gemini 3.1 Pro (High)',
'claude-sonnet-4-6': 'Claude Sonnet 4.6 (Thinking)',
'claude-opus-4-6-thinking': 'Claude Opus 4.6 (Thinking)',
'gpt-oss-120b-medium': 'GPT-OSS 120B (Medium)',
} as const
export type AgyModelPreset = keyof typeof AGY_MODEL_LABELS
export const AGY_MODEL_PRESETS = Object.keys(AGY_MODEL_LABELS) as AgyModelPreset[]
export function getAgyModelLabel(model: string): string | null {
const trimmedModel = model.trim()
if (!trimmedModel) return null
return AGY_MODEL_LABELS[trimmedModel as AgyModelPreset] ?? null
}
export function isClaudeModelPreset(model: string | null | undefined): model is ClaudeModelPreset {
return typeof model === 'string' && Object.hasOwn(CLAUDE_MODEL_LABELS, model)
}
export function getClaudeModelLabel(model: string): string | null {
const trimmedModel = model.trim()
if (!trimmedModel) {
return null
}
return CLAUDE_MODEL_LABELS[trimmedModel as ClaudeModelPreset] ?? null
}