Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions apps/daemon/src/connectionTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1726,6 +1726,7 @@ interface AgentSink {
appendRawStdout: (chunk: string) => void;
getRawStdout: () => string;
getRawStdoutTail: () => string;
getResolvedModel: () => string | null;
sawTerminalCompletion: () => boolean;
dispose: () => void;
}
Expand Down Expand Up @@ -1773,6 +1774,7 @@ export function createAgentSink(): AgentSink {
let stderrTail = '';
let rawStdout = '';
let rawStdoutTail = '';
let resolvedModel: string | null = null;
let terminalCompletionSeen = false;
let debounceTimer: ReturnType<typeof setTimeout> | null = null;
let resolveResult!: (value: AgentSinkResult) => void;
Expand Down Expand Up @@ -1845,6 +1847,14 @@ export function createAgentSink(): AgentSink {
publishStreamError(new Error(message));
return;
}
if (
type === 'status' &&
data.label === 'initializing' &&
typeof data.model === 'string' &&
data.model.trim()
) {
resolvedModel = data.model.trim();
}
const delta = data.delta;
const text = data.text;
if (type === 'text_delta' && typeof delta === 'string') {
Expand Down Expand Up @@ -1887,6 +1897,7 @@ export function createAgentSink(): AgentSink {
appendRawStdout,
getRawStdout: () => rawStdout,
getRawStdoutTail: () => rawStdoutTail,
getResolvedModel: () => resolvedModel,
sawTerminalCompletion: () => terminalCompletionSeen,
dispose: () => {
if (debounceTimer) {
Expand Down Expand Up @@ -2217,6 +2228,7 @@ async function testAgentConnectionInternal(
const latencyMs = Date.now() - start;
const rawSample = truncateSample(text);
const sample = redactSecrets(rawSample);
const resolvedModel = sink.getResolvedModel();
if (rawSample && isLikelyModelErrorText(rawSample)) {
const detail = redactSecrets(smokeFailureDetail(rawSample));
console.warn(
Expand Down Expand Up @@ -2254,6 +2266,7 @@ async function testAgentConnectionInternal(
kind: 'success',
latencyMs,
model,
...(resolvedModel ? { resolvedModel } : {}),
agentName: def.name,
sample,
diagnostics: buildDiagnostics(
Expand Down
42 changes: 42 additions & 0 deletions apps/daemon/tests/connection-test.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3034,6 +3034,48 @@ process.stdin.on('end', () => {
}
});

it('reports the concrete model resolved from a Claude alias', async () => {
await withFakeClaude(
`
console.log(JSON.stringify({
type: 'system',
subtype: 'init',
model: 'claude-opus-5',
session_id: 'test-session',
}));
let input = '';
process.stdin.setEncoding('utf8');
process.stdin.on('data', (chunk) => { input += chunk; });
process.stdin.on('end', () => {
JSON.parse(input.trim());
console.log(JSON.stringify({
type: 'assistant',
message: {
id: 'msg_1',
content: [{ type: 'text', text: 'ok' }],
stop_reason: 'end_turn',
},
}));
});
`,
async () => {
const result = await testAgentConnection({
agentId: 'claude',
model: 'opus',
});

expect(result).toMatchObject({
ok: true,
kind: 'success',
model: 'opus',
resolvedModel: 'claude-opus-5',
agentName: 'Claude Code',
sample: 'ok',
});
},
);
});

it('waits for the Codex process before accepting early success text', async () => {
await withFakeCodex(
`
Expand Down
14 changes: 10 additions & 4 deletions apps/web/src/components/SettingsDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2814,20 +2814,26 @@ export function SettingsDialog({
const baseMessage = kindForSuccess === 'api'
? t('settings.testSuccessApi', { ms, sample })
: t('settings.testSuccessCli', { agentName, ms, sample });
const successMessage =
kindForSuccess === 'cli' &&
result.resolvedModel &&
result.resolvedModel !== testedModel
? `${baseMessage} ${t('settings.model')}: ${result.resolvedModel}`
: baseMessage;
if (kindForSuccess === 'cli' && cfg.agentId === 'codex') {
const codexStrings = codexPathStrings(locale);
if (
result.usedExecutableSource === 'configured' &&
result.configuredExecutablePath
) {
return `${baseMessage} ${codexStrings.configuredSuccess(result.configuredExecutablePath)}`;
return `${successMessage} ${codexStrings.configuredSuccess(result.configuredExecutablePath)}`;
}
if (
result.usedExecutableSource === 'fallback_invalid' &&
result.configuredExecutablePath &&
result.detectedExecutablePath
) {
return `${baseMessage} ${codexStrings.invalidFallback(
return `${successMessage} ${codexStrings.invalidFallback(
result.configuredExecutablePath,
result.detectedExecutablePath,
)}`;
Expand All @@ -2837,13 +2843,13 @@ export function SettingsDialog({
result.configuredExecutablePath &&
result.detectedExecutablePath
) {
return `${baseMessage} ${codexStrings.failedFallback(
return `${successMessage} ${codexStrings.failedFallback(
result.configuredExecutablePath,
result.detectedExecutablePath,
)}`;
}
}
return result.detail ? `${baseMessage} ${result.detail}` : baseMessage;
return result.detail ? `${successMessage} ${result.detail}` : successMessage;
}
switch (result.kind) {
case 'auth_failed':
Expand Down
54 changes: 54 additions & 0 deletions apps/web/tests/components/SettingsDialog.execution.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3116,6 +3116,60 @@ describe('SettingsDialog execution settings Local CLI interactions', () => {
expect(screen.getByRole('button', { name: 'Test' })).toBeTruthy();
});

it('shows the concrete model reported by a Local CLI connection test', async () => {
const claudeAgent: AgentInfo = {
id: 'claude',
name: 'Claude Code',
bin: 'claude',
available: true,
version: '2.1.220',
models: [
{ id: 'default', label: 'Default' },
{ id: 'opus', label: 'Opus (alias)' },
],
};
vi.stubGlobal('fetch', vi.fn(async (input: RequestInfo | URL) => {
const url = input.toString();
if (url === '/api/memory') {
return new Response(
JSON.stringify({ enabled: true, memories: [], extraction: null }),
{ status: 200, headers: { 'content-type': 'application/json' } },
);
}
if (url === '/api/test/connection') {
return new Response(
JSON.stringify({
ok: true,
kind: 'success',
latencyMs: 42,
model: 'opus',
resolvedModel: 'claude-opus-5',
agentName: 'Claude Code',
sample: 'ok',
}),
{ status: 200, headers: { 'content-type': 'application/json' } },
);
}
throw new Error(`Unexpected fetch: ${url}`);
}));

renderSettingsDialog(
{
mode: 'daemon',
agentId: 'claude',
agentModels: { claude: { model: 'opus' } },
},
{ agents: [claudeAgent] },
);

fireEvent.click(screen.getByRole('tab', { name: /Local CLI.*1 installed/i }));
fireEvent.click(screen.getByRole('button', { name: 'Test' }));

expect(
await screen.findByText(/Claude Code replied in 42 ms.*Model: claude-opus-5/i),
).toBeTruthy();
});

it('renders the AMR local agent without vela branding and with the Local CLI test action', async () => {
const fetchMock = vi.fn(async (input: RequestInfo | URL) => {
const url = input.toString();
Expand Down
2 changes: 2 additions & 0 deletions packages/contracts/src/api/connectionTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,8 @@ export interface ConnectionTestResponse {
latencyMs: number;
// Model id or CLI default slot that this test exercised.
model?: string;
// Concrete model reported by a local agent when an alias or default slot resolves.
resolvedModel?: string;
// Truncated assistant reply (≤ 120 chars) on success.
sample?: string;
// Upstream HTTP status when relevant (provider tests).
Expand Down
Loading