Skip to content

Commit 54da910

Browse files
committed
fix(settings): show resolved CLI model (#6180)
1 parent 2523669 commit 54da910

5 files changed

Lines changed: 121 additions & 4 deletions

File tree

apps/daemon/src/connectionTest.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1726,6 +1726,7 @@ interface AgentSink {
17261726
appendRawStdout: (chunk: string) => void;
17271727
getRawStdout: () => string;
17281728
getRawStdoutTail: () => string;
1729+
getResolvedModel: () => string | null;
17291730
sawTerminalCompletion: () => boolean;
17301731
dispose: () => void;
17311732
}
@@ -1773,6 +1774,7 @@ export function createAgentSink(): AgentSink {
17731774
let stderrTail = '';
17741775
let rawStdout = '';
17751776
let rawStdoutTail = '';
1777+
let resolvedModel: string | null = null;
17761778
let terminalCompletionSeen = false;
17771779
let debounceTimer: ReturnType<typeof setTimeout> | null = null;
17781780
let resolveResult!: (value: AgentSinkResult) => void;
@@ -1845,6 +1847,14 @@ export function createAgentSink(): AgentSink {
18451847
publishStreamError(new Error(message));
18461848
return;
18471849
}
1850+
if (
1851+
type === 'status' &&
1852+
data.label === 'initializing' &&
1853+
typeof data.model === 'string' &&
1854+
data.model.trim()
1855+
) {
1856+
resolvedModel = data.model.trim();
1857+
}
18481858
const delta = data.delta;
18491859
const text = data.text;
18501860
if (type === 'text_delta' && typeof delta === 'string') {
@@ -1887,6 +1897,7 @@ export function createAgentSink(): AgentSink {
18871897
appendRawStdout,
18881898
getRawStdout: () => rawStdout,
18891899
getRawStdoutTail: () => rawStdoutTail,
1900+
getResolvedModel: () => resolvedModel,
18901901
sawTerminalCompletion: () => terminalCompletionSeen,
18911902
dispose: () => {
18921903
if (debounceTimer) {
@@ -2217,6 +2228,7 @@ async function testAgentConnectionInternal(
22172228
const latencyMs = Date.now() - start;
22182229
const rawSample = truncateSample(text);
22192230
const sample = redactSecrets(rawSample);
2231+
const resolvedModel = sink.getResolvedModel();
22202232
if (rawSample && isLikelyModelErrorText(rawSample)) {
22212233
const detail = redactSecrets(smokeFailureDetail(rawSample));
22222234
console.warn(
@@ -2254,6 +2266,7 @@ async function testAgentConnectionInternal(
22542266
kind: 'success',
22552267
latencyMs,
22562268
model,
2269+
...(resolvedModel ? { resolvedModel } : {}),
22572270
agentName: def.name,
22582271
sample,
22592272
diagnostics: buildDiagnostics(

apps/daemon/tests/connection-test.test.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3034,6 +3034,48 @@ process.stdin.on('end', () => {
30343034
}
30353035
});
30363036

3037+
it('reports the concrete model resolved from a Claude alias', async () => {
3038+
await withFakeClaude(
3039+
`
3040+
console.log(JSON.stringify({
3041+
type: 'system',
3042+
subtype: 'init',
3043+
model: 'claude-opus-5',
3044+
session_id: 'test-session',
3045+
}));
3046+
let input = '';
3047+
process.stdin.setEncoding('utf8');
3048+
process.stdin.on('data', (chunk) => { input += chunk; });
3049+
process.stdin.on('end', () => {
3050+
JSON.parse(input.trim());
3051+
console.log(JSON.stringify({
3052+
type: 'assistant',
3053+
message: {
3054+
id: 'msg_1',
3055+
content: [{ type: 'text', text: 'ok' }],
3056+
stop_reason: 'end_turn',
3057+
},
3058+
}));
3059+
});
3060+
`,
3061+
async () => {
3062+
const result = await testAgentConnection({
3063+
agentId: 'claude',
3064+
model: 'opus',
3065+
});
3066+
3067+
expect(result).toMatchObject({
3068+
ok: true,
3069+
kind: 'success',
3070+
model: 'opus',
3071+
resolvedModel: 'claude-opus-5',
3072+
agentName: 'Claude Code',
3073+
sample: 'ok',
3074+
});
3075+
},
3076+
);
3077+
});
3078+
30373079
it('waits for the Codex process before accepting early success text', async () => {
30383080
await withFakeCodex(
30393081
`

apps/web/src/components/SettingsDialog.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2822,20 +2822,26 @@ export function SettingsDialog({
28222822
const baseMessage = kindForSuccess === 'api'
28232823
? t('settings.testSuccessApi', { ms, sample })
28242824
: t('settings.testSuccessCli', { agentName, ms, sample });
2825+
const successMessage =
2826+
kindForSuccess === 'cli' &&
2827+
result.resolvedModel &&
2828+
result.resolvedModel !== testedModel
2829+
? `${baseMessage} ${t('settings.model')}: ${result.resolvedModel}`
2830+
: baseMessage;
28252831
if (kindForSuccess === 'cli' && cfg.agentId === 'codex') {
28262832
const codexStrings = codexPathStrings(locale);
28272833
if (
28282834
result.usedExecutableSource === 'configured' &&
28292835
result.configuredExecutablePath
28302836
) {
2831-
return `${baseMessage} ${codexStrings.configuredSuccess(result.configuredExecutablePath)}`;
2837+
return `${successMessage} ${codexStrings.configuredSuccess(result.configuredExecutablePath)}`;
28322838
}
28332839
if (
28342840
result.usedExecutableSource === 'fallback_invalid' &&
28352841
result.configuredExecutablePath &&
28362842
result.detectedExecutablePath
28372843
) {
2838-
return `${baseMessage} ${codexStrings.invalidFallback(
2844+
return `${successMessage} ${codexStrings.invalidFallback(
28392845
result.configuredExecutablePath,
28402846
result.detectedExecutablePath,
28412847
)}`;
@@ -2845,13 +2851,13 @@ export function SettingsDialog({
28452851
result.configuredExecutablePath &&
28462852
result.detectedExecutablePath
28472853
) {
2848-
return `${baseMessage} ${codexStrings.failedFallback(
2854+
return `${successMessage} ${codexStrings.failedFallback(
28492855
result.configuredExecutablePath,
28502856
result.detectedExecutablePath,
28512857
)}`;
28522858
}
28532859
}
2854-
return result.detail ? `${baseMessage} ${result.detail}` : baseMessage;
2860+
return result.detail ? `${successMessage} ${result.detail}` : successMessage;
28552861
}
28562862
switch (result.kind) {
28572863
case 'auth_failed':

apps/web/tests/components/SettingsDialog.execution.test.tsx

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3132,6 +3132,60 @@ describe('SettingsDialog execution settings Local CLI interactions', () => {
31323132
expect(screen.getByRole('button', { name: 'Test' })).toBeTruthy();
31333133
});
31343134

3135+
it('shows the concrete model reported by a Local CLI connection test', async () => {
3136+
const claudeAgent: AgentInfo = {
3137+
id: 'claude',
3138+
name: 'Claude Code',
3139+
bin: 'claude',
3140+
available: true,
3141+
version: '2.1.220',
3142+
models: [
3143+
{ id: 'default', label: 'Default' },
3144+
{ id: 'opus', label: 'Opus (alias)' },
3145+
],
3146+
};
3147+
vi.stubGlobal('fetch', vi.fn(async (input: RequestInfo | URL) => {
3148+
const url = input.toString();
3149+
if (url === '/api/memory') {
3150+
return new Response(
3151+
JSON.stringify({ enabled: true, memories: [], extraction: null }),
3152+
{ status: 200, headers: { 'content-type': 'application/json' } },
3153+
);
3154+
}
3155+
if (url === '/api/test/connection') {
3156+
return new Response(
3157+
JSON.stringify({
3158+
ok: true,
3159+
kind: 'success',
3160+
latencyMs: 42,
3161+
model: 'opus',
3162+
resolvedModel: 'claude-opus-5',
3163+
agentName: 'Claude Code',
3164+
sample: 'ok',
3165+
}),
3166+
{ status: 200, headers: { 'content-type': 'application/json' } },
3167+
);
3168+
}
3169+
throw new Error(`Unexpected fetch: ${url}`);
3170+
}));
3171+
3172+
renderSettingsDialog(
3173+
{
3174+
mode: 'daemon',
3175+
agentId: 'claude',
3176+
agentModels: { claude: { model: 'opus' } },
3177+
},
3178+
{ agents: [claudeAgent] },
3179+
);
3180+
3181+
fireEvent.click(screen.getByRole('tab', { name: /Local CLI.*1 installed/i }));
3182+
fireEvent.click(screen.getByRole('button', { name: 'Test' }));
3183+
3184+
expect(
3185+
await screen.findByText(/Claude Code replied in 42 ms.*Model: claude-opus-5/i),
3186+
).toBeTruthy();
3187+
});
3188+
31353189
it('renders the AMR local agent without vela branding and with the Local CLI test action', async () => {
31363190
const fetchMock = vi.fn(async (input: RequestInfo | URL) => {
31373191
const url = input.toString();

packages/contracts/src/api/connectionTest.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,8 @@ export interface ConnectionTestResponse {
265265
latencyMs: number;
266266
// Model id or CLI default slot that this test exercised.
267267
model?: string;
268+
// Concrete model reported by a local agent when an alias or default slot resolves.
269+
resolvedModel?: string;
268270
// Truncated assistant reply (≤ 120 chars) on success.
269271
sample?: string;
270272
// Upstream HTTP status when relevant (provider tests).

0 commit comments

Comments
 (0)