mirror of
https://github.com/stablyai/orca.git
synced 2026-09-28 08:02:43 +00:00
Track Claude models from the installed CLI per host (STA-3330) (#12369)
* feat(native-chat): track Claude models from the installed CLI per host (STA-3330) The Claude seed no longer pins version labels to aliases that resolve differently across CLI versions, and the catalog now defines listModels backed by a one-shot list_models control request over --print stream-json. Hosts whose CLI predates the request answer with a control error and keep the seed. Discovery also feeds Source Control AI via the commit-message spec, and the /model echo detector matches resolved model names. * fix(native-chat): preserve discovered Claude capabilities * fix(native-chat): tolerate malformed Claude model entries * fix(native-chat): discover models in folder workspaces * fix(native-chat): trust discovered Claude capabilities * fix(native-chat): remove Claude model fallbacks * fix(native-chat): keep the Claude model picker rendered The Claude picker rendered nothing until the per-host `list_models` probe returned, so it popped in ~1s after mount and never appeared at all when the probe failed — an old CLI without `list_models`, no `claude` on PATH, or an older remote runtime whose response omits `catalogOrigin`. Restore the version-neutral family seed as the starting list; discovery still replaces it wholesale on success, so a host with a real catalog never shows an obsolete hardcoded row. Separately, the tracked model could fall outside the active list: the terminal header scrape yields family ids (`opus`) while a current CLI lists `opus[1m]` and no plain `opus`. That blanked the picker trigger and dropped the model's effort and fast-mode controls. Reconcile the tracked id into the active list once, so the snapshot, the appliers, and typed command recording all see a labelled, operable row for it.
This commit is contained in:
@@ -1,6 +1,11 @@
|
||||
import type { TuiAgent } from './types'
|
||||
import { isTuiAgentEnabled } from './tui-agent-selection'
|
||||
import { assertJsonTextStructureWithinLimits } from './json-text-structure-limit'
|
||||
import {
|
||||
CLAUDE_MODEL_LIST_ARGS,
|
||||
CLAUDE_MODEL_LIST_STDIN,
|
||||
parseClaudeModelList
|
||||
} from './claude-model-list-probe'
|
||||
|
||||
/* eslint-disable max-lines -- Why: this is the single registry for non-interactive commit-message agents, their model discovery parsers, and UI capabilities. */
|
||||
|
||||
@@ -16,10 +21,14 @@ export type CommitMessageModel = {
|
||||
id: string
|
||||
/** Visible label in the model dropdown. */
|
||||
label: string
|
||||
/** Discovery-provided detail, e.g. what a CLI alias resolves to on this host. */
|
||||
description?: string
|
||||
/** Omit when the model does not expose an effort selector — the UI then hides the dropdown. */
|
||||
thinkingLevels?: ThinkingLevel[]
|
||||
/** Required when thinkingLevels is present. */
|
||||
defaultThinkingLevel?: string
|
||||
/** Whether the model exposes Claude's mid-session Fast mode toggle. */
|
||||
supportsFastMode?: boolean
|
||||
}
|
||||
|
||||
export type CommitMessageAgentSpec = {
|
||||
@@ -37,6 +46,8 @@ export type CommitMessageAgentSpec = {
|
||||
modelDiscovery?: {
|
||||
binary: string
|
||||
args: string[]
|
||||
/** Written to the CLI's stdin, for CLIs whose listing is request-driven. */
|
||||
stdinPayload?: string
|
||||
parse: (stdout: string) => CommitMessageModel[]
|
||||
}
|
||||
models: CommitMessageModel[]
|
||||
@@ -46,8 +57,10 @@ export type CommitMessageAgentSpec = {
|
||||
export type CommitMessageModelCapability = {
|
||||
id: string
|
||||
label: string
|
||||
description?: string
|
||||
thinkingLevels?: ThinkingLevel[]
|
||||
defaultThinkingLevel?: string
|
||||
supportsFastMode?: boolean
|
||||
}
|
||||
|
||||
export type CommitMessageAgentCapability = {
|
||||
@@ -139,6 +152,30 @@ function withOpenAiThinking(
|
||||
: {}
|
||||
}
|
||||
|
||||
export function parseClaudeModels(stdout: string): CommitMessageModel[] {
|
||||
return uniqueModels(
|
||||
parseClaudeModelList(stdout).map((model) => {
|
||||
const thinkingLevels = CLAUDE_THINKING_LEVELS.filter((level) =>
|
||||
model.effortLevels.includes(level.id)
|
||||
)
|
||||
return {
|
||||
id: model.id,
|
||||
label: model.label,
|
||||
...(model.description ? { description: model.description } : {}),
|
||||
...(thinkingLevels.length > 0
|
||||
? {
|
||||
thinkingLevels,
|
||||
defaultThinkingLevel: thinkingLevels.some((level) => level.id === 'low')
|
||||
? 'low'
|
||||
: thinkingLevels[0].id
|
||||
}
|
||||
: {}),
|
||||
...(model.supportsFastMode ? { supportsFastMode: true } : {})
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
export function parseCodexModels(stdout: string): CommitMessageModel[] {
|
||||
try {
|
||||
assertJsonTextStructureWithinLimits(stdout, COMMIT_MESSAGE_MODEL_JSON_STRUCTURE_LIMITS)
|
||||
@@ -311,7 +348,16 @@ export const COMMIT_MESSAGE_AGENT_SPECS: Partial<Record<TuiAgent, CommitMessageA
|
||||
'plan',
|
||||
...(thinkingLevel ? ['--effort', thinkingLevel] : [])
|
||||
],
|
||||
modelSource: 'static',
|
||||
modelSource: 'dynamic',
|
||||
// Why: the Claude CLI has no listing subcommand; one list_models control
|
||||
// request over --print stream-json returns the /model picker catalog.
|
||||
// Older CLIs answer with a control error and exit 0, keeping the fallback.
|
||||
modelDiscovery: {
|
||||
binary: 'claude',
|
||||
args: [...CLAUDE_MODEL_LIST_ARGS],
|
||||
stdinPayload: CLAUDE_MODEL_LIST_STDIN,
|
||||
parse: parseClaudeModels
|
||||
},
|
||||
models: [
|
||||
{
|
||||
// Why: Claude Code aliases track the account/provider's supported
|
||||
@@ -744,8 +790,10 @@ function toCommitMessageAgentCapability(
|
||||
models: spec.models.map((model) => ({
|
||||
id: model.id,
|
||||
label: model.label,
|
||||
...(model.description ? { description: model.description } : {}),
|
||||
...(model.thinkingLevels ? { thinkingLevels: [...model.thinkingLevels] } : {}),
|
||||
...(model.defaultThinkingLevel ? { defaultThinkingLevel: model.defaultThinkingLevel } : {})
|
||||
...(model.defaultThinkingLevel ? { defaultThinkingLevel: model.defaultThinkingLevel } : {}),
|
||||
...(model.supportsFastMode ? { supportsFastMode: true } : {})
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user