Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
68161d9
Reconcile external sessions in a single catalog pass (#331472)
benibenj Aug 18, 2026
7233723
sessions: support deep session and chat links (#331492)
sandy081 Aug 18, 2026
813910b
Agents - refactor "New Session" and "New Session From" actions into a…
lszomoru Aug 18, 2026
8610a5c
Modern UI: Increase margin-top for activity bar items (#331459)
mrleemurray Aug 18, 2026
dd45748
Modern UI: Update pane header colors and separator behavior for moder…
mrleemurray Aug 18, 2026
0d07085
agentHost: attribute default turns to bound model (#330971)
amunger Aug 18, 2026
c898226
agentHost: Reject unavailable session catalogs (#331510)
vijayupadya Aug 18, 2026
4b9b867
Use "No workspace" label for automation quick-chat target (#331495)
benvillalobos Aug 18, 2026
d992642
Update distro commit (main) (#331520)
vs-code-engineering[bot] Aug 18, 2026
580ac1c
Add active session context to voice requests (#331503)
meganrogge Aug 18, 2026
5484a62
Chat: Hide debug log export without an active session (#331523)
roblourens Aug 18, 2026
04e0eae
Support info message and fix deprecations (#331505)
lramos15 Aug 18, 2026
678b215
Add mute-mic button and transcript quick-toggle to Agents Voice Mode …
Copilot Aug 18, 2026
262a87b
Force the Agent Host harness when the sandbox is managed (#331298)
joshspicer Aug 18, 2026
e0b73c2
Fix the quota data missing from the agent window (#331536)
lramos15 Aug 18, 2026
ccc9323
Fix flaky agent host E2E tests on Windows (#331555)
roblourens Aug 18, 2026
f675815
Add agent workspace discovery tool (#331525)
meganrogge Aug 18, 2026
d402578
Fix hover race (#331556)
lramos15 Aug 18, 2026
b68a9f8
Add agent session OS notifications (#331549)
meganrogge Aug 18, 2026
1e2449f
agentHost: expand the legacy enterprise settings bridge (#331415)
joshspicer Aug 18, 2026
1c26465
Polish mock policy schema table layout (#331512)
joshspicer Aug 18, 2026
b019241
managed settings: fix stale cache case when moving to unavailable (#3…
joshspicer Aug 18, 2026
2b3436e
Fix Agent Host startup with invalid telemetry level (#331557)
roblourens Aug 18, 2026
be6741d
fix: use fresh service accessor when lazily initializing chat attachm…
srikanthananthula63053 Aug 18, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/


import { IChatEndpoint, IChatEndpointTokenPricing } from '../../../platform/networking/common/networking';
import { IChatEndpoint, IChatEndpointTokenPricing, PENDING_DEPRECATION_CODE } from '../../../platform/networking/common/networking';
import * as l10n from '@vscode/l10n';
import type { LanguageModelChatInformation, LanguageModelConfigurationSchema } from 'vscode';

Expand Down Expand Up @@ -129,6 +129,23 @@ export function buildAutoModeTierSchemaProperty(tiers: readonly string[], defaul
};
}

/**
* Resolves the model picker's warning presentation. All warnings show as hover banners,
* but only a degradation or a pending deprecation flags the row, and `rowWarning` is the
* message explaining it. Callers must skip the synthetic Auto model, which wraps another
* endpoint and must not inherit its warnings.
*/
export function resolveModelWarnings(endpoint: Pick<IChatEndpoint, 'warningText' | 'degradationReason'>): { texts: Record<string, string>; rowWarning: string | undefined } | undefined {
const texts: Record<string, string> = { ...endpoint.warningText };
if (endpoint.degradationReason) {
texts['degradation'] = endpoint.degradationReason;
}
if (Object.keys(texts).length === 0) {
return undefined;
}
return { texts, rowWarning: endpoint.degradationReason ?? texts[PENDING_DEPRECATION_CODE] };
}

/**
* Returns a description of the model's capabilities and intended use cases.
* This is shown in the rich hover when selecting models.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ import { IExtensionContribution } from '../../common/contributions';
import { PromptRenderer } from '../../prompts/node/base/promptRenderer';
import { isImageDataPart } from '../common/languageModelChatMessageHelpers';
import { LanguageModelAccessPrompt } from './languageModelAccessPrompt';
import { formatPricingLabel, formatTokenCount, getAutoModelDescription, getAutoModelDiscountLabel, getModelCapabilitiesDescription, buildReasoningEffortSchemaProperty, buildAutoModeTierSchemaProperty } from '../common/languageModelAccess';
import { formatPricingLabel, formatTokenCount, getAutoModelDescription, getAutoModelDiscountLabel, getModelCapabilitiesDescription, resolveModelWarnings, buildReasoningEffortSchemaProperty, buildAutoModeTierSchemaProperty } from '../common/languageModelAccess';

/**
* Builds a configurationSchema for the model picker based on the endpoint's supported capabilities.
Expand Down Expand Up @@ -339,9 +339,13 @@ export class LanguageModelAccess extends Disposable implements IExtensionContrib
const sanitizedModelName = endpoint.name
.replace(/\([^)]*\bcontext\)/gi, '')
.trim();

// Auto wraps another endpoint, so it must not inherit that model's warnings.
const warnings = endpoint instanceof AutoChatEndpoint ? undefined : resolveModelWarnings(endpoint);

let modelTooltip: string | undefined;
if (endpoint.degradationReason) {
modelTooltip = endpoint.degradationReason;
if (warnings?.rowWarning) {
modelTooltip = warnings.rowWarning;
} else if (endpoint instanceof AutoChatEndpoint) {
modelTooltip = getAutoModelDescription(endpoint.discountRange);
} else {
Expand Down Expand Up @@ -384,7 +388,7 @@ export class LanguageModelAccess extends Disposable implements IExtensionContrib
priceCategory: endpoint instanceof AutoChatEndpoint ? undefined : endpoint.priceCategory,
category: endpoint instanceof AutoChatEndpoint ? undefined : endpoint.modelPickerCategory,
detail: modelDetail,
statusIcon: endpoint.degradationReason ? new vscode.ThemeIcon('warning') : undefined,
statusIcon: warnings?.rowWarning ? new vscode.ThemeIcon('warning') : undefined,
version: endpoint.version,
maxInputTokens: endpoint.modelMaxPromptTokens - baseCount - BaseTokensPerCompletion,
maxOutputTokens: endpoint.maxOutputTokens,
Expand All @@ -396,13 +400,8 @@ export class LanguageModelAccess extends Disposable implements IExtensionContrib
[ApiChatLocation.Editor]: endpoint instanceof AutoChatEndpoint, // inline chat gets 'Auto' by default
},
isUserSelectable: endpoint.showInModelPicker,
warningText: endpoint instanceof AutoChatEndpoint ? undefined : (() => {
const texts: Record<string, string> = { ...endpoint.warningText };
if (endpoint.degradationReason) {
texts['degradation'] = endpoint.degradationReason;
}
return Object.keys(texts).length > 0 ? texts : undefined;
})(),
warningText: warnings?.texts,
infoText: endpoint instanceof AutoChatEndpoint ? undefined : endpoint.infoText,
promo: endpoint instanceof AutoChatEndpoint ? undefined : endpoint.promo,
capabilities: {
imageInput: endpoint instanceof AutoChatEndpoint ? true : endpoint.supportsVision,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { describe, expect, it } from 'vitest';
import { resolveModelWarnings } from '../../common/languageModelAccess';

const DEPRECATION = 'Claude Sonnet 4.6 has a planned deprecation date of 2026-09-01.';
const DEGRADATION = 'This model is currently degraded.';
const RETENTION = 'Prompts are retained for 30 days.';

describe('resolveModelWarnings', () => {
it('flags a pending deprecation even though it arrives without a degradation', () => {
expect(resolveModelWarnings({ warningText: { model_pending_deprecation: DEPRECATION } })).toEqual({
texts: { model_pending_deprecation: DEPRECATION },
rowWarning: DEPRECATION,
});
});

it('shows a banner-only warning without flagging the row', () => {
expect(resolveModelWarnings({ warningText: { data_retention: RETENTION } })).toEqual({
texts: { data_retention: RETENTION },
rowWarning: undefined,
});
});

it('lets a degradation explain the model even when other warnings are present', () => {
expect(resolveModelWarnings({
warningText: { data_retention: RETENTION },
degradationReason: DEGRADATION,
})).toEqual({
texts: { data_retention: RETENTION, degradation: DEGRADATION },
rowWarning: DEGRADATION,
});
});

it('has no warning presentation when the model carries no warnings', () => {
expect(resolveModelWarnings({})).toBeUndefined();
});
});
24 changes: 22 additions & 2 deletions extensions/copilot/src/platform/endpoint/node/chatEndpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { ILogService } from '../../log/common/logService';
import { isAnthropicContextEditingEnabled, isExtendedCacheTtlEnabled } from '../../networking/common/anthropic';
import { FinishedCallback, getRequestId, ICopilotToolCall, OptionalChatRequestParams } from '../../networking/common/fetch';
import { IFetcherService, Response } from '../../networking/common/fetcherService';
import { createCapiRequestBody, IChatEndpoint, IChatEndpointTokenPricing, ICreateEndpointBodyOptions, IEndpointBody, IMakeChatRequestOptions, InteractionTypeOverride } from '../../networking/common/networking';
import { createCapiRequestBody, IChatEndpoint, IChatEndpointTokenPricing, ICreateEndpointBodyOptions, IEndpointBody, IMakeChatRequestOptions, InteractionTypeOverride, PENDING_DEPRECATION_CODE } from '../../networking/common/networking';
import { CAPIChatMessage, ChatCompletion, FinishedCompletionReason, RawMessageConversionCallback } from '../../networking/common/openai';
import { prepareChatCompletionForReturn } from '../../networking/node/chatStream';
import { IChatWebSocketManager } from '../../networking/node/chatWebSocketManager';
Expand Down Expand Up @@ -152,6 +152,23 @@ export async function defaultNonStreamChatResponseProcessor(response: Response,
return AsyncIterableObject.fromArray(completions);
}

/** Splits CAPI `info_messages` into warning and info banners keyed by their code. */
function splitInfoMessages(infoMessages: { code: string; message: string }[] | undefined): { warningText: Record<string, string>; infoText: Record<string, string> } {
const warningText: Record<string, string> = {};
const infoText: Record<string, string> = {};
for (const { code, message } of infoMessages ?? []) {
if (message) {
const target = code === PENDING_DEPRECATION_CODE ? warningText : infoText;
target[code || 'info'] = message;
}
}
return { warningText, infoText };
}

function undefinedIfEmpty(record: Record<string, string>): Record<string, string> | undefined {
return Object.keys(record).length > 0 ? record : undefined;
}

export class ChatEndpoint implements IChatEndpoint {
private readonly _maxTokens: number;
private readonly _maxOutputTokens: number;
Expand Down Expand Up @@ -182,6 +199,7 @@ export class ChatEndpoint implements IChatEndpoint {
public readonly customModel?: CustomModel | undefined;
public readonly maxPromptImages?: number | undefined;
public readonly warningText?: Record<string, string> | undefined;
public readonly infoText?: Record<string, string> | undefined;
public readonly promo?: { id: string; discountPercent: number; endsAt?: string; message: string } | undefined;

private readonly _supportsStreaming: boolean;
Expand Down Expand Up @@ -233,7 +251,9 @@ export class ChatEndpoint implements IChatEndpoint {
this._supportsStreaming = !!modelMetadata.capabilities.supports.streaming;
this.customModel = modelMetadata.custom_model;
this.maxPromptImages = modelMetadata.capabilities.limits?.vision?.max_prompt_images;
this.warningText = modelMetadata.warning_text;
const infoMessages = splitInfoMessages(modelMetadata.info_messages);
this.warningText = undefinedIfEmpty({ ...modelMetadata.warning_text, ...infoMessages.warningText });
this.infoText = undefinedIfEmpty(infoMessages.infoText);
this.promo = modelMetadata.billing?.promo ? {
id: modelMetadata.billing.promo.id,
discountPercent: modelMetadata.billing.promo.discount_percent,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -664,3 +664,50 @@ describe('ChatEndpoint - CAPI reasoning effort', () => {
expect(body.reasoning_effort).toBeUndefined();
});
});

describe('ChatEndpoint - model picker notices', () => {
let mockServices: ReturnType<typeof createMockServices>;

beforeEach(() => {
mockServices = createMockServices();
});

const createEndpoint = (metadata: IChatModelInformation) =>
new ChatEndpoint(
metadata,
mockServices.domainService,
mockServices.chatMLFetcher,
mockServices.tokenizerProvider,
mockServices.instantiationService,
mockServices.configurationService,
mockServices.expService,
mockServices.chatWebSocketService,
mockServices.logService
);

it('shows a pending deprecation as a warning and other info messages as info', () => {
const endpoint = createEndpoint({
...createNonAnthropicModelMetadata('gpt-4.1'),
warning_text: { data_retention: 'Prompts are retained for 30 days.' },
warning_messages: [{ code: 'model_degraded', message: 'GPT-4.1 is currently degraded.' }],
info_messages: [
{ code: 'model_pending_deprecation', message: 'GPT-4.1 has a planned deprecation date of 2026-06-01.' },
{ code: 'model_relocated', message: 'GPT-4.1 now serves from a new region.' },
],
});

expect({ warningText: endpoint.warningText, infoText: endpoint.infoText, degradationReason: endpoint.degradationReason }).toEqual({
warningText: {
data_retention: 'Prompts are retained for 30 days.',
model_pending_deprecation: 'GPT-4.1 has a planned deprecation date of 2026-06-01.',
},
infoText: { model_relocated: 'GPT-4.1 now serves from a new region.' },
degradationReason: 'GPT-4.1 is currently degraded.',
});
});

it('has no notices when CAPI sends none', () => {
const endpoint = createEndpoint({ ...createNonAnthropicModelMetadata('gpt-4.1'), info_messages: [] });
expect({ warningText: endpoint.warningText, infoText: endpoint.infoText }).toEqual({ warningText: undefined, infoText: undefined });
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,9 @@ export interface IChatEndpointTokenPricing {
readonly longContext?: ITokenPriceTier;
}

/** CAPI notice code that shows as a warning banner and also flags the model picker row. */
export const PENDING_DEPRECATION_CODE = 'model_pending_deprecation';

export interface IChatEndpoint extends IEndpoint {
readonly maxOutputTokens: number;
/** The model ID- this may change and will be `copilot-utility` for the utility (fallback) model. Use `family` to switch behavior based on model type. */
Expand All @@ -341,7 +344,10 @@ export interface IChatEndpoint extends IEndpoint {
readonly showInModelPicker: boolean;
readonly isPremium?: boolean;
readonly degradationReason?: string;
/** Category-keyed warning banners for the model picker. */
readonly warningText?: Record<string, string>;
/** Category-keyed info banners for the model picker. Unlike {@link warningText} these never signal a problem. */
readonly infoText?: Record<string, string>;
readonly promo?: { id: string; discountPercent: number; endsAt?: string; message: string };
readonly multiplier?: number;
readonly restrictedToSkus?: string[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -766,7 +766,7 @@ export class MarkdownEditorProvider extends Disposable implements vscode.CustomT
id: rule.id,
source: rule.uriPattern.source,
flags: rule.uriPattern.flags,
initialKind: rule.initialKind,
initialKind: rule.initialKind === 'chat' ? 'session' : rule.initialKind,
})),
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,15 +106,26 @@ class ApiLinkPresentationEntry extends Disposable {
}

const watcher = this._register(vscode.window.createLinkPresentationWatcher(rule.id, resource));
publishPresentation(watcher.presentation);
this._register(watcher.onDidChangePresentation(() => publishPresentation(watcher.presentation)));
publishPresentation(toMarkdownEditorPresentation(watcher.presentation));
this._register(watcher.onDidChangePresentation(() => publishPresentation(toMarkdownEditorPresentation(watcher.presentation))));
} catch (error) {
logger.trace('Markdown rich link', `Failed to resolve ${href}`, error);
if (!this.isDisposed) {
publishPresentation(undefined);
}
}

}
}

function toMarkdownEditorPresentation(presentation: vscode.LinkPresentationData | undefined): LinkPresentation | undefined {
if (!presentation) {
return undefined;
}
return {
...presentation,
kind: presentation.kind === 'chat' ? 'session' : presentation.kind,
};
}

async function resolveLinkResource(href: string, documentUri: vscode.Uri, linkOpener: MdLinkOpener): Promise<vscode.Uri | undefined> {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "code-oss-dev",
"version": "1.135.0",
"distro": "5475972d5042caa842cdccf21488c4d0728ca0c1",
"distro": "c842171bd42ca4aef20b0a186c94d99edd763842",
"author": {
"name": "Microsoft Corporation"
},
Expand Down
20 changes: 17 additions & 3 deletions scripts/mock-policy-server/public/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -631,11 +631,18 @@ declare const MOCK_POLICY_ENDPOINTS: EndpointDef[];

const table = document.createElement('table');
table.className = 'validation-table';
const columns = document.createElement('colgroup');
for (const columnName of ['key', 'status', 'description']) {
const column = document.createElement('col');
column.className = `validation-column-${columnName}`;
columns.appendChild(column);
}
const head = document.createElement('thead');
const headRow = document.createElement('tr');
for (const heading of ['Key', 'Status', 'Description']) {
const th = document.createElement('th');
th.textContent = heading;
th.scope = 'col';
headRow.appendChild(th);
}
head.appendChild(headRow);
Expand Down Expand Up @@ -669,15 +676,22 @@ declare const MOCK_POLICY_ENDPOINTS: EndpointDef[];
}
keyCell.appendChild(keyCode);
const statusCell = document.createElement('td');
statusCell.className = cls;
statusCell.classList.add('validation-status');
if (cls) {
statusCell.classList.add(cls);
}
statusCell.textContent = statusText;
const descCell = document.createElement('td');
descCell.className = 'validation-description';
descCell.textContent = (validation.schema?.description || '').split('.')[0];
row.append(keyCell, statusCell, descCell);
tbody.appendChild(row);
}

table.append(head, tbody);
table.append(columns, head, tbody);
const tableContainer = document.createElement('div');
tableContainer.className = 'validation-table-container';
tableContainer.appendChild(table);

const schemaRows = rows.filter(row => row.inSchema && !row.dynamic);
const presentCount = schemaRows.filter(row => row.inBody).length;
Expand All @@ -690,7 +704,7 @@ declare const MOCK_POLICY_ENDPOINTS: EndpointDef[];
summary.classList.add('validation-warn');
}

container.replaceChildren(table, summary);
container.replaceChildren(tableContainer, summary);
container.hidden = false;
setStatus(unknownCount ? `${unknownCount} key${unknownCount > 1 ? 's' : ''} not in schema.` : '', unknownCount ? 'warn' : '');
}
Expand Down
Loading
Loading