mirror of
https://github.com/stablyai/orca.git
synced 2026-11-15 18:37:24 +00:00
fix(claude): verify model switches when terminal echoes omit vendor prefix (#40064)
Claude echoes "Set model to Opus 5.5" without the "Claude" vendor prefix, and never echoes the picker-only " alias" suffix or a raw "claude-" id prefix. The confirmation observer required every picker-label token, so a successful switch resolved to unknown and showed "Could not verify the model change". Normalize the expected label before matching; context tokens such as 1M are still required. Fixes #40058 Co-authored-by: Neil <neil@stably.ai>
This commit is contained in:
+32
@@ -4,6 +4,38 @@ import { describe, expect, it, vi } from 'vitest'
|
||||
import { createClaudeModelSwitchConfirmationObserver } from './claude-model-switch-confirmation'
|
||||
|
||||
describe('Claude model switch confirmation detection', () => {
|
||||
it.each([
|
||||
[
|
||||
'quoted model name',
|
||||
'Claude Opus 5.5',
|
||||
'Set model to `Opus 5.5` and saved as your default for new sessions'
|
||||
],
|
||||
[
|
||||
'matching requested context variant',
|
||||
'Claude Opus 5.5 (1M context)',
|
||||
'Set model to `Opus 5.5 (1M context)` and saved as your default for new sessions'
|
||||
],
|
||||
['raw model id fallback', 'claude-opus-5-5', 'Set model to Opus 5.5'],
|
||||
['alias picker row', 'Opus alias', 'Set model to Opus 5.5']
|
||||
])('confirms a picker label with %s', async (_name, label, output) => {
|
||||
const dataObserver = { current: (_data: string): void => {} }
|
||||
const observer = createClaudeModelSwitchConfirmationObserver({
|
||||
ptyId: 'pty-1',
|
||||
expectedModelLabel: label,
|
||||
subscribeToData: (watcher) => {
|
||||
dataObserver.current = watcher
|
||||
return () => {}
|
||||
}
|
||||
})
|
||||
|
||||
await observer.ready
|
||||
observer.arm()
|
||||
observer.startDetection()
|
||||
dataObserver.current(output)
|
||||
|
||||
await expect(observer.result).resolves.toBe('applied')
|
||||
})
|
||||
|
||||
it('reports matching success output split across PTY chunks', async () => {
|
||||
const dataObserver = { current: (_data: string): void => {} }
|
||||
const unsubscribe = vi.fn(() => {})
|
||||
|
||||
+4
-2
@@ -33,12 +33,14 @@ function compactTerminalText(buffer: string): string {
|
||||
|
||||
function hasClaudeModelSwitchSuccess(buffer: string, modelLabel: string): boolean {
|
||||
const text = compactTerminalText(buffer)
|
||||
const marker = `setmodelto${modelLabel.replace(/\s+/g, '').toLowerCase()}`
|
||||
// Claude echoes neither the vendor prefix nor Orca's picker-only `alias` suffix.
|
||||
const label = modelLabel.replace(/^claude[\s-]+/i, '').replace(/\s+alias$/i, '')
|
||||
const marker = `setmodelto${label.replace(/\s+/g, '').toLowerCase()}`
|
||||
if (text.includes(marker)) {
|
||||
return true
|
||||
}
|
||||
// Match every picker token so a versioned echo cannot confirm the wrong context variant.
|
||||
const labelTokens = modelLabel.toLowerCase().match(/[a-z]+|\d+[a-z]*/g) ?? []
|
||||
const labelTokens = label.toLowerCase().match(/[a-z]+|\d+[a-z]*/g) ?? []
|
||||
const successStart = text.lastIndexOf('setmodelto')
|
||||
if (successStart === -1 || labelTokens.length === 0) {
|
||||
return false
|
||||
|
||||
Reference in New Issue
Block a user