Keep Assistant route labels provider-explicit

This commit is contained in:
rcourtman
2026-06-07 23:25:26 +01:00
parent 92f08a1f16
commit a4c5a85bef
4 changed files with 50 additions and 6 deletions
@@ -1471,11 +1471,14 @@ timers when the stream already has a newer workflow/tool state.
route. Assistant transcript rows must carry and render the effective model
route that produced each live or restored response so route recovery and
mixed-provider sessions remain auditable. Direct provider models must not
gain a gateway label. Gateway route strings must still render as readable
provider/model labels before catalog hydration; this mirrors OpenCode's
source pattern of retaining structured `providerID`/`modelID` metadata and
presenting catalog names with ID fallbacks rather than leaking raw route
storage IDs into primary chat chrome. Assistant model selector actions must
gain a gateway label, but explicit direct route strings such as
`deepseek:deepseek-v4-pro` must still render with provider identity rather
than collapsing to a bare-looking model payload. Gateway route strings must
still render as readable provider/model labels before catalog hydration; this
mirrors OpenCode's source pattern of retaining structured
`providerID`/`modelID` metadata and presenting catalog names with ID
fallbacks rather than leaking raw route storage IDs into primary chat chrome.
Assistant model selector actions must
remain route-distinct: if the configured chat override resolves to the same
route as the effective default or the already selected session model, the
drawer must not render a duplicate override action.
@@ -4921,6 +4921,29 @@ describe('AIChat', () => {
expect(mockChat.setModel).toHaveBeenCalledWith('openrouter:qwen/qwen3.7-plus');
});
it('labels direct provider recent routes with provider identity', () => {
localStorage.setItem(
'pulse:ai_chat_recent_models',
JSON.stringify(['deepseek:deepseek-v4-pro']),
);
mockChat.model.mockReturnValue('openrouter:qwen/qwen3.7-plus');
renderChat();
fireEvent.click(
screen.getByRole('button', {
name: /Cycle recent Assistant model: DeepSeek: DeepSeek V4 Pro/,
}),
);
expect(
screen.queryByRole('button', {
name: 'Cycle recent Assistant model: deepseek-v4-pro',
}),
).not.toBeInTheDocument();
expect(mockChat.setModel).toHaveBeenCalledWith('deepseek:deepseek-v4-pro');
});
it('disables recent model cycling when the active route is the only recent route', () => {
localStorage.setItem(
'pulse:ai_chat_recent_models',
@@ -42,6 +42,10 @@ describe('aiProviderPresentation', () => {
provider: 'deepseek',
}),
).toBe('DeepSeek: DeepSeek V4 Pro');
expect(formatAIModelRouteLabel('deepseek:deepseek-v4-pro')).toBe(
'DeepSeek: DeepSeek V4 Pro',
);
});
it('does not duplicate an existing OpenRouter route label', () => {
@@ -191,12 +191,26 @@ const gatewayFallbackLabel = (model: AIModelRouteLabelInput): string => {
return `${upstreamProvider}: ${modelLabel}`;
};
const directProviderRouteFallbackLabel = (model: AIModelRouteLabelInput): string => {
if (explicitModelNameForLabel(model)) return '';
const id = modelIdForLabel(model);
if (isPulseOwnedLocalModelRoute(id)) return '';
const separator = id.indexOf(':');
if (separator <= 0 || separator === id.length - 1) return '';
const provider = id.slice(0, separator);
if (GATEWAY_MODEL_PROVIDERS.has(provider)) return '';
const providerName = getAIProviderDisplayName(provider);
const modelLabel = titleizeModelRouteId(id.slice(separator + 1));
if (!providerName || !modelLabel) return '';
return `${providerName}: ${modelLabel}`;
};
export const formatAIModelRouteLabel = (model: AIModelRouteLabelInput): string => {
const provider = transportProviderForLabel(model);
const label =
provider && GATEWAY_MODEL_PROVIDERS.has(provider)
? gatewayFallbackLabel(model) || baseModelLabel(model)
: baseModelLabel(model);
: directProviderRouteFallbackLabel(model) || baseModelLabel(model);
if (!provider || !GATEWAY_MODEL_PROVIDERS.has(provider)) {
return label;
}