Skip to content
Draft
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import { Codicon } from '../../../../../base/common/codicons.js';
import { fromNow } from '../../../../../base/common/date.js';
import { MarkdownString } from '../../../../../base/common/htmlContent.js';
import { isUriComponents, URI } from '../../../../../base/common/uri.js';
import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js';
import { localize2 } from '../../../../../nls.js';
Expand All @@ -14,7 +15,7 @@ import { IInstantiationService } from '../../../../../platform/instantiation/com
import { IEditorService } from '../../../../services/editor/common/editorService.js';
import { IAgentSessionsService } from '../agentSessions/agentSessionsService.js';
import { ChatContextKeys } from '../../common/actions/chatContextKeys.js';
import { IChatService } from '../../common/chatService/chatService.js';
import { IChatAutoModelRoutingPart, IChatProgress, IChatService } from '../../common/chatService/chatService.js';
import { ILanguageModelsService } from '../../common/languageModels.js';
import { IChatWidgetService } from '../chat.js';
import { IStorageService, StorageScope } from '../../../../../platform/storage/common/storage.js';
Expand All @@ -40,6 +41,7 @@ export function registerChatDeveloperActions() {
registerAction2(InspectChatModelReferencesAction);
registerAction2(ClearRecentlyUsedLanguageModelsAction);
registerAction2(ResetChatPermissionWarningDialogsAction);
registerAction2(SimulateAutoModelRoutingAction);
}

function formatChatModelReferenceInspection(accessor: ServicesAccessor): string {
Expand Down Expand Up @@ -256,3 +258,67 @@ class ResetChatPermissionWarningDialogsAction extends Action2 {
storageService.remove(AUTO_APPROVE_DONT_SHOW_AGAIN_KEY, StorageScope.PROFILE);
}
}

/**
* Prototype dev command: injects a sample "Auto Model Routing" card into the
* currently focused chat session, so we can preview how this UI would look
* inside the chat without wiring up a real router yet.
*/
class SimulateAutoModelRoutingAction extends Action2 {
static readonly ID = 'workbench.action.chat.simulateAutoModelRouting';

constructor() {
super({
id: SimulateAutoModelRoutingAction.ID,
title: localize2('workbench.action.chat.simulateAutoModelRouting.label', "Simulate Auto Model Selection Routing (Prototype)"),
category: Categories.Developer,
f1: true,
precondition: ChatContextKeys.enabled
});
}

override async run(accessor: ServicesAccessor): Promise<void> {
const chatWidgetService = accessor.get(IChatWidgetService);
const chatService = accessor.get(IChatService);
const widget = chatWidgetService.lastFocusedWidget;

if (!widget?.viewModel) {
return;
}

const sessionResource = widget.viewModel.model.sessionResource;

const routingPart: IChatAutoModelRoutingPart = {
kind: 'autoModelRouting',
selectedModel: 'Claude Opus 4.6',
selectionReason: 'Best match for deep reasoning and long-context debugging tasks',
intent: 'Complex debugging',
confidence: 0.91,
capabilities: [
{ name: 'Reasoning', score: 0.92 },
{ name: 'Code Gen', score: 0.61 },
{ name: 'Debugging', score: 0.88 },
{ name: 'Tool Use', score: 0.34 },
],
candidates: [
{ modelName: 'GPT-5', score: 0.84, reason: 'Strong general purpose, slightly lower on long-context reasoning' },
{ modelName: 'Claude Sonnet 4.5', score: 0.76, reason: 'Fast, but less capable on complex multi-step debugging' },
{ modelName: 'GPT-4.1', score: 0.61, reason: 'Lower cost option, weaker on deep reasoning tasks' },
],
};

const message: IChatProgress[] = [
{ kind: 'markdownContent', content: new MarkdownString('Routing your request through Copilot Auto…') },
routingPart,
{ kind: 'markdownContent', content: new MarkdownString('Done — using **Claude Opus 4.6** for this turn.') },
];
Comment on lines +310 to +314
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The injected chat markdown strings ("Routing your request through Copilot Auto…", "Done — using …") are user-visible but not localized. Please wrap these in localize(...) (or otherwise externalize them) even though the command is Developer-only, to keep the codebase’s localization guarantees consistent.

Copilot uses AI. Check for mistakes.

await chatService.addCompleteRequest(
sessionResource,
'/dev simulate auto model routing',
undefined,
0,
{ message }
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { $ } from '../../../../../../base/browser/dom.js';
import { Codicon } from '../../../../../../base/common/codicons.js';
import { localize } from '../../../../../../nls.js';
import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js';
import { IHoverService } from '../../../../../../platform/hover/browser/hover.js';
import { IChatAutoModelRoutingPart } from '../../../common/chatService/chatService.js';
import { IChatRendererContent } from '../../../common/model/chatViewModel.js';
import { ChatTreeItem } from '../../chat.js';
import { ChatCollapsibleContentPart } from './chatCollapsibleContentPart.js';
import { IChatContentPart, IChatContentPartRenderContext } from './chatContentParts.js';
import './media/chatAutoModelRoutingContentPart.css';

export class ChatAutoModelRoutingContentPart extends ChatCollapsibleContentPart implements IChatContentPart {

constructor(
private readonly routingPart: IChatAutoModelRoutingPart,
context: IChatContentPartRenderContext,
@IHoverService hoverService: IHoverService,
@IConfigurationService configurationService: IConfigurationService,
) {
const title = localize('autoModelRouting.title', "Routed to {0}", routingPart.selectedModel);
super(title, context, undefined, hoverService, configurationService);

this.icon = Codicon.sparkle;
this.domNode.classList.add('chat-auto-model-routing');
this.setExpanded(false);
}

protected override initContent(): HTMLElement {
const container = $('.chat-auto-model-routing-body');

// Build capability sentence from top 2 capabilities by score
const caps = this.routingPart.capabilities;
let capSentence: string;
if (caps && caps.length >= 2) {
const top = [...caps].sort((a, b) => b.score - a.score).slice(0, 2).map(c => c.name.toLowerCase());
capSentence = localize('autoModelRouting.capSentence', "{0} is selected for high {1} and {2} capability. ",
this.routingPart.selectedModel, top[0], top[1]);
} else if (caps && caps.length === 1) {
capSentence = localize('autoModelRouting.capSentenceSingle', "{0} is selected for high {1} capability. ",
this.routingPart.selectedModel, caps[0].name.toLowerCase());
} else {
capSentence = '';
}

const para = $('.chat-auto-model-routing-footer');
para.appendChild($('span', undefined, capSentence));
para.appendChild($('span', undefined,
localize('autoModelRouting.footer', "Auto routes based on your task and real-time system health and model performance. ")));
const learnMore = $('a.chat-auto-model-routing-learn-more', { href: 'https://aka.ms/copilot-auto-model', target: '_blank' },
localize('autoModelRouting.learnMore', "Learn more"));
para.appendChild(learnMore);
container.appendChild(para);

// Capability score list
if (this.routingPart.capabilities && this.routingPart.capabilities.length > 0) {
container.appendChild($('span.chat-auto-model-routing-section-label', undefined,
localize('autoModelRouting.capabilities', "Task requirements")));
const list = $('ul.chat-auto-model-routing-scores');
for (const cap of this.routingPart.capabilities) {
const li = $('li.chat-auto-model-routing-score-row');
li.appendChild($('span.chat-auto-model-routing-score-name', undefined, cap.name));
li.appendChild($('span.chat-auto-model-routing-score-pct', undefined, `${Math.round(cap.score * 100)}%`));
list.appendChild(li);
}
container.appendChild(list);
}
Comment on lines +34 to +72
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IChatAutoModelRoutingPart carries fields like selectionReason, intent, confidence, costTier, and candidates, but the renderer currently only shows a generic footer + capabilities list and never surfaces those fields. This doesn’t match the PR description/UI goals and makes the injected sample data mostly unused; either render the additional fields (reason/intent/metadata/candidate ranking) or trim the part shape/sample to what is actually displayed.

This issue also appears in the following locations of the same file:

  • line 55
  • line 85

Copilot uses AI. Check for mistakes.

return container;
}

hasSameContent(other: IChatRendererContent, _followingContent: IChatRendererContent[], _element: ChatTreeItem): boolean {
if (other.kind !== 'autoModelRouting') {
return false;
}
if (other.selectedModel !== this.routingPart.selectedModel
|| other.selectionReason !== this.routingPart.selectionReason) {
return false;
}
const a = this.routingPart.candidates ?? [];
const b = other.candidates ?? [];
if (a.length !== b.length) {
return false;
}
for (let i = 0; i < a.length; i++) {
if (a[i].modelName !== b[i].modelName) {
return false;
}
}
const ca = this.routingPart.capabilities ?? [];
const cb = other.capabilities ?? [];
if (ca.length !== cb.length) {
return false;
}
for (let i = 0; i < ca.length; i++) {
if (ca[i].name !== cb[i].name || ca[i].score !== cb[i].score) {
return false;
}
}
return true;
}
}


Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

.chat-auto-model-routing-body {
display: flex;
flex-direction: column;
gap: 8px;
padding: 4px 12px 10px 12px;
font-size: var(--vscode-chat-font-size-body-s);
}

/* Prose sentence */
.chat-auto-model-routing-prose {
color: var(--vscode-descriptionForeground);
line-height: 1.5;
}

/* Curved connector line when expanded (matches Thinking style) */
.chat-auto-model-routing {
position: relative;
}

.chat-auto-model-routing:not(.chat-used-context-collapsed)::after {
content: '';
position: absolute;
left: 3px;
top: 22px;
height: 16px;
width: 5px;
border-left: 1px solid var(--vscode-chat-requestBorder);
border-bottom: 1px solid var(--vscode-chat-requestBorder);
border-bottom-left-radius: 5px;
}

/* Footer / prose paragraph */
.chat-auto-model-routing-footer {
color: var(--vscode-descriptionForeground);
font-size: var(--vscode-chat-font-size-body-s);
line-height: 1.5;
}

.chat-auto-model-routing-learn-more {
color: var(--vscode-textLink-foreground);
text-decoration: none;
}

.chat-auto-model-routing-learn-more:hover {
text-decoration: underline;
}

/* Section label */
.chat-auto-model-routing-section-label {
font-size: 10px;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--vscode-descriptionForeground);
opacity: 0.6;
}

/* Capability score list */
.chat-auto-model-routing-scores {
list-style: none;
margin: 0;
padding: 0 0 0 10px;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 2px 12px;
border-left: 1px solid var(--vscode-chat-requestBorder);
}

.chat-auto-model-routing-score-row {
display: flex;
align-items: center;
gap: 4px;
color: var(--vscode-descriptionForeground);
padding: 1px 0;
}
color: var(--vscode-descriptionForeground);
}
Comment on lines +80 to +81
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CSS has a stray color: declaration and an extra closing } after .chat-auto-model-routing-score-row, which makes the stylesheet invalid and can cause the remaining rules to be ignored by the CSS parser. Remove the dangling property/brace so subsequent selectors (e.g. -score-name, -score-pct) apply reliably.

Suggested change
color: var(--vscode-descriptionForeground);
}

Copilot uses AI. Check for mistakes.

.chat-auto-model-routing-score-name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

.chat-auto-model-routing-score-pct {
font-variant-numeric: tabular-nums;
font-size: 11px;
white-space: nowrap;
color: var(--vscode-foreground);
font-weight: 500;
}
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ import { isEqual } from '../../../../../base/common/resources.js';
import { IAccessibilityService } from '../../../../../platform/accessibility/common/accessibility.js';
import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js';
import { ChatHookContentPart } from './chatContentParts/chatHookContentPart.js';
import { ChatAutoModelRoutingContentPart } from './chatContentParts/chatAutoModelRoutingContentPart.js';
import { ChatPendingDragController } from './chatPendingDragAndDrop.js';
import { HookType } from '../../common/promptSyntax/hookTypes.js';
import { IWorkbenchEnvironmentService } from '../../../../services/environment/common/environmentService.js';
Expand Down Expand Up @@ -2196,6 +2197,8 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
return this.instantiationService.createInstance(ChatErrorContentPart, ChatErrorLevel.Warning, content.content, content, this.chatContentMarkdownRenderer);
} else if (content.kind === 'hook') {
return this.renderHookPart(content, context, templateData);
} else if (content.kind === 'autoModelRouting') {
return this.instantiationService.createInstance(ChatAutoModelRoutingContentPart, content, context);
} else if (content.kind === 'markdownContent') {
return this.renderMarkdown(content, templateData, context);
} else if (content.kind === 'references') {
Expand Down
39 changes: 39 additions & 0 deletions src/vs/workbench/contrib/chat/common/chatService/chatService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,44 @@ export interface IChatThinkingPart {
* Aligned with the hook output JSON structure: { stopReason, systemMessage, hookSpecificOutput }.
* If {@link stopReason} is set, the hook blocked/denied the operation.
*/
/**
* Prototype: a part describing how the "auto" model selector routed a request
* to a specific underlying language model. Rendered as a collapsible card,
* similar to thinking and tool-call parts.
*/
export interface IChatAutoModelRoutingCandidate {
readonly modelName: string;
readonly score: number;
readonly reason?: string;
}

export interface IChatAutoModelRoutingCapability {
/** Human-readable capability name, e.g. "Reasoning". */
readonly name: string;
/** How much this task needs this capability (0-1). */
readonly score: number;
}

export interface IChatAutoModelRoutingPart {
kind: 'autoModelRouting';
/** The model that was selected (e.g. "GPT-5"). */
readonly selectedModel: string;
/** A short, user-facing reason for the selection. */
readonly selectionReason: string;
/** Optional confidence (0-1) reported by the router. */
readonly confidence?: number;
/** Optional latency the router predicted for the chosen model, in ms. */
readonly predictedLatencyMs?: number;
/** Optional relative cost tier of the chosen model. */
readonly costTier?: 'low' | 'medium' | 'high';
/** Categorized intent the router detected for the prompt. */
readonly intent?: string;
/** Other models that were considered and not chosen. */
readonly candidates?: ReadonlyArray<IChatAutoModelRoutingCandidate>;
/** Predicted capability requirements for this task. */
readonly capabilities?: ReadonlyArray<IChatAutoModelRoutingCapability>;
}

export interface IChatHookPart {
kind: 'hook';
/** The type of hook that was executed */
Expand Down Expand Up @@ -1126,6 +1164,7 @@ export type IChatProgress =
| IChatMcpServersStarting
| IChatMcpServersStartingSerialized
| IChatHookPart
| IChatAutoModelRoutingPart
| IChatExternalToolInvocationUpdate
| IChatDisabledClaudeHooksPart;

Expand Down
4 changes: 3 additions & 1 deletion src/vs/workbench/contrib/chat/common/model/chatModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import { CellUri, ICellEditOperation } from '../../../notebook/common/notebookCo
import { ChatRequestToolReferenceEntry, IChatRequestVariableEntry, isImplicitVariableEntry, isStringImplicitContextValue, isStringVariableEntry } from '../attachments/chatVariableEntries.js';
import { migrateLegacyTerminalToolSpecificData } from '../chat.js';
import { ChatPerfMark, markChat } from '../chatPerf.js';
import { ChatAgentVoteDirection, ChatRequestQueueKind, ChatResponseClearToPreviousToolInvocationReason, ElicitationState, IChatAgentMarkdownContentWithVulnerability, IChatClearToPreviousToolInvocation, IChatCodeCitation, IChatCommandButton, IChatConfirmation, IChatContentInlineReference, IChatContentReference, IChatDisabledClaudeHooksPart, IChatEditingSessionAction, IChatElicitationRequest, IChatElicitationRequestSerialized, IChatExternalToolInvocationUpdate, IChatExtensionsContent, IChatFollowup, IChatHookPart, IChatLocationData, IChatMarkdownContent, IChatMcpServersStarting, IChatMcpServersStartingSerialized, IChatModelReference, IChatMultiDiffData, IChatMultiDiffDataSerialized, IChatNotebookEdit, IChatProgress, IChatPlanReview, IChatProgressMessage, IChatPullRequestContent, IChatQuestionCarousel, IChatResponseCodeblockUriPart, IChatResponseProgressFileTreeData, IChatSendRequestOptions, IChatService, IChatSessionTiming, IChatTask, IChatTaskSerialized, IChatTextEdit, IChatThinkingPart, IChatToolInvocation, IChatToolInvocationSerialized, IChatTreeData, IChatUndoStop, IChatUsage, IChatUsedContext, IChatWarningMessage, IChatWorkspaceEdit, ResponseModelState, ToolConfirmKind, isIUsedContext } from '../chatService/chatService.js';
import { ChatAgentVoteDirection, ChatRequestQueueKind, ChatResponseClearToPreviousToolInvocationReason, ElicitationState, IChatAgentMarkdownContentWithVulnerability, IChatAutoModelRoutingPart, IChatClearToPreviousToolInvocation, IChatCodeCitation, IChatCommandButton, IChatConfirmation, IChatContentInlineReference, IChatContentReference, IChatDisabledClaudeHooksPart, IChatEditingSessionAction, IChatElicitationRequest, IChatElicitationRequestSerialized, IChatExternalToolInvocationUpdate, IChatExtensionsContent, IChatFollowup, IChatHookPart, IChatLocationData, IChatMarkdownContent, IChatMcpServersStarting, IChatMcpServersStartingSerialized, IChatModelReference, IChatMultiDiffData, IChatMultiDiffDataSerialized, IChatNotebookEdit, IChatProgress, IChatPlanReview, IChatProgressMessage, IChatPullRequestContent, IChatQuestionCarousel, IChatResponseCodeblockUriPart, IChatResponseProgressFileTreeData, IChatSendRequestOptions, IChatService, IChatSessionTiming, IChatTask, IChatTaskSerialized, IChatTextEdit, IChatThinkingPart, IChatToolInvocation, IChatToolInvocationSerialized, IChatTreeData, IChatUndoStop, IChatUsage, IChatUsedContext, IChatWarningMessage, IChatWorkspaceEdit, ResponseModelState, ToolConfirmKind, isIUsedContext } from '../chatService/chatService.js';
import { ChatAgentLocation, ChatModeKind, ChatPermissionLevel } from '../constants.js';
import { ChatToolInvocation } from './chatProgressTypes/chatToolInvocation.js';
import { ToolDataSource, IToolData } from '../tools/languageModelToolsService.js';
Expand Down Expand Up @@ -199,6 +199,7 @@ export type IChatProgressHistoryResponseContent =
| IChatExtensionsContent
| IChatThinkingPart
| IChatHookPart
| IChatAutoModelRoutingPart
| IChatPullRequestContent
| IChatWorkspaceEdit;

Expand Down Expand Up @@ -601,6 +602,7 @@ class AbstractResponse implements IResponse {
case 'elicitationSerialized':
case 'thinking':
case 'hook':
case 'autoModelRouting':
case 'multiDiffData':
case 'mcpServersStarting':
case 'questionCarousel':
Expand Down
Loading
Loading