mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 16:02:32 +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:
@@ -12,6 +12,7 @@ import {
|
||||
listCommitMessageAgentCapabilities,
|
||||
listCommitMessageAgentIds,
|
||||
parseAntigravityModels,
|
||||
parseClaudeModels,
|
||||
parseCodexModels,
|
||||
parseCursorModels,
|
||||
parseLineModels,
|
||||
@@ -194,6 +195,81 @@ describe('buildArgs (Claude)', () => {
|
||||
})
|
||||
|
||||
describe('model discovery parsers', () => {
|
||||
it('parses Claude list_models output into commit-message models', () => {
|
||||
const stdout = `${JSON.stringify({
|
||||
type: 'control_response',
|
||||
response: {
|
||||
subtype: 'success',
|
||||
request_id: 'orca-model-discovery',
|
||||
response: {
|
||||
models: [
|
||||
{
|
||||
value: 'default',
|
||||
displayName: 'Default (recommended)',
|
||||
supportsEffort: true,
|
||||
supportedEffortLevels: ['low', 'medium', 'high', 'xhigh', 'max']
|
||||
},
|
||||
{
|
||||
value: 'opus[1m]',
|
||||
displayName: 'Opus (1M context)',
|
||||
description: 'Opus 5 with 1M context · $5/$25 per Mtok',
|
||||
supportsEffort: true,
|
||||
supportedEffortLevels: ['low', 'medium', 'high', 'xhigh', 'max'],
|
||||
supportsFastMode: true
|
||||
},
|
||||
{ value: 'haiku', displayName: 'Haiku' }
|
||||
]
|
||||
}
|
||||
}
|
||||
})}\n`
|
||||
expect(parseClaudeModels(stdout)).toEqual([
|
||||
{
|
||||
id: 'opus[1m]',
|
||||
label: 'Opus (1M context)',
|
||||
description: 'Opus 5 with 1M context · $5/$25 per Mtok',
|
||||
thinkingLevels: [
|
||||
{ id: 'low', label: 'Low' },
|
||||
{ id: 'medium', label: 'Medium' },
|
||||
{ id: 'high', label: 'High' },
|
||||
{ id: 'xhigh', label: 'Extra High' },
|
||||
{ id: 'max', label: 'Max' }
|
||||
],
|
||||
defaultThinkingLevel: 'low',
|
||||
supportsFastMode: true
|
||||
},
|
||||
{ id: 'haiku', label: 'Haiku' }
|
||||
])
|
||||
})
|
||||
|
||||
it('returns no Claude models when the CLI lacks list_models so the seed stays', () => {
|
||||
expect(
|
||||
parseClaudeModels(
|
||||
'{"type":"control_response","response":{"subtype":"error","request_id":"orca-model-discovery","error":"Unsupported control request subtype: list_models"}}\n'
|
||||
)
|
||||
).toEqual([])
|
||||
})
|
||||
|
||||
it('declares stdin-driven dynamic discovery for Claude', () => {
|
||||
const discovery = COMMIT_MESSAGE_AGENT_SPECS.claude?.modelDiscovery
|
||||
expect(COMMIT_MESSAGE_AGENT_SPECS.claude?.modelSource).toBe('dynamic')
|
||||
expect(discovery?.binary).toBe('claude')
|
||||
expect(discovery?.args).toEqual([
|
||||
'-p',
|
||||
'--input-format',
|
||||
'stream-json',
|
||||
'--output-format',
|
||||
'stream-json',
|
||||
'--verbose'
|
||||
])
|
||||
const payload = JSON.parse(discovery?.stdinPayload ?? '') as {
|
||||
type?: string
|
||||
request?: { subtype?: string }
|
||||
}
|
||||
expect(payload.type).toBe('control_request')
|
||||
expect(payload.request?.subtype).toBe('list_models')
|
||||
expect(discovery?.stdinPayload?.endsWith('\n')).toBe(true)
|
||||
})
|
||||
|
||||
it('parses Codex model JSON', () => {
|
||||
expect(
|
||||
parseCodexModels(
|
||||
|
||||
Reference in New Issue
Block a user