Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
08b3f95
Fix option display labels in enum quick picks for language model conf…
vritant24 May 21, 2026
4f6c530
itemProvider.provideCustomAgents to get agents from SessionCustomizat…
aeschli May 22, 2026
cd2d198
Add new chat tip for opening the Agents window (#317953)
benibenj May 22, 2026
89c168b
Polish multiDiffEntry and diff-hidden-lines styles (#317976)
mrleemurray May 22, 2026
bb678fe
sessions: add rich hover for titlebar and session list items (#317975)
sandy081 May 22, 2026
c583494
Update modal background opacity to 50% for improved visibility (#317973)
mrleemurray May 22, 2026
db199ee
sessions: extract local chat sessions into standalone provider (#317979)
sandy081 May 22, 2026
9ae0c33
Update sandbox rg path
Copilot May 22, 2026
d42239e
Fix MCP sandbox ripgrep-universal path
Copilot May 22, 2026
fc83818
Fix TS2367 in mcpSandboxService: simplify rg path after Windows early…
Copilot May 22, 2026
2134ff0
fix: preserve gallery field in MCP protocol install fallback path (#3…
digitarald May 22, 2026
bf51c48
sessions: scope navigation keybindings to non-editor focus (#317986)
sandy081 May 22, 2026
f32c055
Sandboxing for Copilot SDK integration (#317981)
chrmarti May 22, 2026
d95071e
Discover user-level Copilot instructions in ~/.copilot (#317968)
Copilot May 22, 2026
2e196b5
fix(chat): use Array.isArray guard for enumItemLabels in promptForEnum
vritant24 May 22, 2026
500ac81
customization UI: all itemProvider accesses go through the IAICustomi…
aeschli May 22, 2026
1bc55c9
Merge branch 'main' into agents/fix-option-display-labels
vritant24 May 22, 2026
d701555
give margin to visibility action item
vritant24 May 22, 2026
a32f113
Context picker selector (#317997)
lramos15 May 22, 2026
4d46872
Merge pull request #317883 from microsoft/agents/fix-option-display-l…
vritant24 May 22, 2026
cc0ee7d
sessions: only show provider name for duplicated session types in pic…
sandy081 May 22, 2026
b523dd8
Merge pull request #317999 from microsoft/dev/vritant24/vsibilityPadding
vritant24 May 22, 2026
7b57c95
Keep emulation toolbar visibility synchronized with browsers (#318003)
kycutler May 22, 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
22 changes: 19 additions & 3 deletions extensions/copilot/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1992,10 +1992,15 @@
"responses",
"messages"
],
"enumItemLabels": [
"Chat Completions",
"Responses",
"Messages"
],
"enumDescriptions": [
"Chat Completions API.",
"Responses API.",
"Messages API."
"Chat Completions API",
"Responses API",
"Messages API"
],
"default": "chat-completions",
"title": "API Type",
Expand Down Expand Up @@ -2044,6 +2049,17 @@
"responses",
"messages"
],
"enumItemLabels": [
"Chat Completions",
"Responses",
"Messages"
],
"enumDescriptions": [
"Chat Completions API",
"Responses API",
"Messages API"
],
"title": "API Type",
"markdownDescription": "Request/response format used to talk to this endpoint:\n- `chat-completions`: Chat Completions API (default).\n- `responses`: Responses API.\n- `messages`: Messages API.\n\nWhen omitted, falls back to the group-level `apiType`, then to the URL path."
},
"toolCalling": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import { EmbeddingType, getWellKnownEmbeddingTypeInfo, IEmbeddingsComputer } fro
import { ChatEndpointFamily, IEndpointProvider } from '../../../platform/endpoint/common/endpointProvider';
import { CustomDataPartMimeTypes } from '../../../platform/endpoint/common/endpointTypes';
import { encodeStatefulMarker } from '../../../platform/endpoint/common/statefulMarkerContainer';
import { isAnthropicFamily, isGeminiFamily } from '../../../platform/endpoint/common/chatModelCapabilities';
import { AutoChatEndpoint } from '../../../platform/endpoint/node/autoChatEndpoint';
import { IAutomodeService } from '../../../platform/endpoint/node/automodeService';
import { CopilotChatEndpoint, CopilotUtilitySmallChatEndpoint } from '../../../platform/endpoint/node/copilotChatEndpoint';
Expand Down Expand Up @@ -59,21 +58,42 @@ const experimentalAutoModelHintMarkers = ['minimax', 'mp3yn0h7', 'yaqq2gxh'];
* Returns the available context size options for a model, or undefined if the
* model does not support configurable context sizes.
*
* For opus models with a large context window (>= 900K tokens), offers a
* standard 200K option and the model's full context size.
* Driven entirely by CAPI billing metadata:
* - When CAPI returns a `long_context` tier, offers `default.context_max` as
* the default option and `modelMaxPromptTokens` as an opt-in larger option.
* - When the long-context tier has higher prices, the larger option includes a
* cost indicator so the user knows they are opting into higher billing.
* - When there is no `long_context` tier, no selector is shown.
*/
function getContextSizeOptions(endpoint: IChatEndpoint): { value: number; description: string; isDefault: boolean }[] | undefined {
const maxTokens = endpoint.modelMaxPromptTokens;
const pricing = endpoint.tokenPricing;

// Claude Opus models with a large context window (~1M or more) get a 200K/full toggle
if (isAnthropicFamily(endpoint) && endpoint.family.startsWith('claude-opus') && maxTokens > 900_000) {
return [
{ value: 200_000, description: vscode.l10n.t('Balanced default'), isDefault: true },
{ value: maxTokens, description: vscode.l10n.t('Longer sessions without compaction'), isDefault: false },
];
// Only offer a selector when CAPI provides a default context max,
// which indicates a meaningful distinction between default and long context tiers.
if (!pricing?.default.contextMax) {
return undefined;
}

return undefined;
const defaultMax = pricing.default.contextMax;
const fullMax = endpoint.modelMaxPromptTokens;

// No point showing a selector if the default is already the full context
if (defaultMax >= fullMax) {
return undefined;
}

const hasLongContextSurcharge = !!pricing.longContext;

return [
{ value: defaultMax, description: vscode.l10n.t('Default pricing'), isDefault: true },
{
value: fullMax,
description: hasLongContextSurcharge
? vscode.l10n.t('Longer sessions (higher cost)')
: vscode.l10n.t('Longer sessions without compaction'),
isDefault: false,
},
];
}

function formatTokenCount(count: number): string {
Expand All @@ -92,17 +112,12 @@ function buildConfigurationSchema(endpoint: IChatEndpoint): { configurationSchem
return {};
}

const family = endpoint.family.toLowerCase();
if (isGeminiFamily(endpoint)) {
return {};
}

const properties: Record<string, NonNullable<vscode.LanguageModelConfigurationSchema['properties']>[string]> = {};

// Reasoning effort config
const effortLevels = endpoint.supportsReasoningEffort;
if (effortLevels && effortLevels.length > 1) {
properties.reasoningEffort = buildReasoningEffortSchemaProperty(effortLevels, family);
properties.reasoningEffort = buildReasoningEffortSchemaProperty(effortLevels, endpoint.family.toLowerCase());
}

// Context size config
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { IFileSystemService } from '../../filesystem/common/fileSystemService';
import { ILogService } from '../../log/common/logService';
import { IPromptPathRepresentationService } from '../../prompts/common/promptPathRepresentationService';
import { IWorkspaceService } from '../../workspace/common/workspaceService';
import { COPILOT_INSTRUCTIONS_PATH, INSTRUCTION_FILE_EXTENSION, INSTRUCTIONS_LOCATION_KEY, PERSONAL_SKILL_FOLDERS, PromptsType, SKILLS_LOCATION_KEY, USE_AGENT_SKILLS_SETTING, WORKSPACE_SKILL_FOLDERS } from './promptTypes';
import { COPILOT_INSTRUCTIONS_PATH, COPILOT_PERSONAL_INSTRUCTIONS_PATH, INSTRUCTION_FILE_EXTENSION, INSTRUCTIONS_LOCATION_KEY, PERSONAL_SKILL_FOLDERS, PromptsType, SKILLS_LOCATION_KEY, USE_AGENT_SKILLS_SETTING, WORKSPACE_SKILL_FOLDERS } from './promptTypes';

declare const TextDecoder: {
decode(input: Uint8Array): string;
Expand Down Expand Up @@ -317,6 +317,14 @@ export class CustomInstructionsService extends Disposable implements ICustomInst
// ignore non-existing instruction files
}
}
try {
const uri = extUriBiasedIgnorePathCase.joinPath(this.envService.userHome, COPILOT_PERSONAL_INSTRUCTIONS_PATH);
if ((await this.fileSystemService.stat(uri)).type === FileType.File) {
result.push(uri);
}
} catch (e) {
// ignore non-existing instruction files
}
}
return result;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const USE_AGENT_SKILLS_SETTING = 'chat.useAgentSkills';
export const USE_SKILL_ADHERENCE_PROMPT_SETTING = 'chat.experimental.useSkillAdherencePrompt';

export const COPILOT_INSTRUCTIONS_PATH = '.github/copilot-instructions.md';
export const COPILOT_PERSONAL_INSTRUCTIONS_PATH = '.copilot/copilot-instructions.md';

/**
* File extension for the reusable prompt files.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,22 @@
import { afterEach, beforeEach, expect, suite, test } from 'vitest';
import { URI } from '../../../../util/vs/base/common/uri';
import { SyncDescriptor } from '../../../../util/vs/platform/instantiation/common/descriptors';
import { IConfigurationService } from '../../../configuration/common/configurationService';
import { ConfigKey, IConfigurationService } from '../../../configuration/common/configurationService';
import { DefaultsOnlyConfigurationService } from '../../../configuration/common/defaultsOnlyConfigurationService';
import { InMemoryConfigurationService } from '../../../configuration/test/common/inMemoryConfigurationService';
import { MockFileSystemService } from '../../../filesystem/node/test/mockFileSystemService';
import { createPlatformServices, ITestingServicesAccessor } from '../../../test/node/services';
import { TestWorkspaceService } from '../../../test/node/testWorkspaceService';
import { IWorkspaceService } from '../../../workspace/common/workspaceService';
import { ICustomInstructionsService } from '../../common/customInstructionsService';
import { IFileSystemService } from '../../../filesystem/common/fileSystemService';
import { mockFiles } from '../../../promptFiles/test/node/mockFiles';

suite('CustomInstructionsService - Skills', () => {
let accessor: ITestingServicesAccessor;
let customInstructionsService: ICustomInstructionsService;
let configService: InMemoryConfigurationService;
let fileSystemService: MockFileSystemService;

beforeEach(async () => {
const services = createPlatformServices();
Expand All @@ -38,6 +42,7 @@ suite('CustomInstructionsService - Skills', () => {

accessor = services.createTestingAccessor();
customInstructionsService = accessor.get(ICustomInstructionsService);
fileSystemService = accessor.get(IFileSystemService) as MockFileSystemService;
});

afterEach(() => {
Expand Down Expand Up @@ -323,6 +328,28 @@ suite('CustomInstructionsService - Skills', () => {
});

suite('chat.instructionsFilesLocations config', () => {
test('should return workspace and home copilot instruction files from getAgentInstructions', async () => {
await configService.setConfig(ConfigKey.UseInstructionFiles, true);
await mockFiles(fileSystemService, [
{ path: '/workspace/.github/copilot-instructions.md', contents: ['Workspace instructions'] },
{ path: '/home/testuser/.copilot/copilot-instructions.md', contents: ['Home instructions'] },
]);

expect((await customInstructionsService.getAgentInstructions()).map(uri => uri.path)).toEqual([
'/workspace/.github/copilot-instructions.md',
'/home/testuser/.copilot/copilot-instructions.md',
]);
});

test('should skip home copilot instruction files from getAgentInstructions when disabled', async () => {
await configService.setConfig(ConfigKey.UseInstructionFiles, false);
await mockFiles(fileSystemService, [
{ path: '/home/testuser/.copilot/copilot-instructions.md', contents: ['Home instructions'] },
]);

expect(await customInstructionsService.getAgentInstructions()).toEqual([]);
});

test('should recognize instruction file in absolute path location', async () => {
await configService.setNonExtensionConfig('chat.instructionsFilesLocations', {
'/custom/instructions': true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,12 @@ export interface IModelTokenPriceTier {
input_price: number;
output_price: number;
cache_price: number;
context_max: number;
/**
* The maximum context window size (in tokens) for this pricing tier.
* Present on the `default` tier only when a `long_context` tier also
* exists; always present on the `long_context` tier itself.
*/
context_max?: number;
}

export interface IModelTokenPrices {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ function normalizePriceTier(tier: IModelTokenPriceTier, scale: number): ITokenPr
inputPrice: tier.input_price * scale,
outputPrice: tier.output_price * scale,
cacheReadTokenPrice: tier.cache_price * scale,
contextMax: tier.context_max,
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,15 @@ export class ModelMetadataFetcher extends Disposable implements IModelMetadataFe
return modelLimit;
}

// When a long context tier exists, use max_context_window_tokens as the
// prompt token basis so users can opt into the full context window via
// the model picker. The configurationSchema default (defaultContextMax)
// ensures users aren't billed at the long-context rate without explicit opt-in.
if (chatModelInfo.billing?.token_prices?.long_context && chatModelInfo.capabilities?.limits?.max_context_window_tokens) {
modelLimit += chatModelInfo.capabilities.limits.max_context_window_tokens;
return modelLimit;
}

// Check if CAPI has prompt token limits and return those
if (chatModelInfo.capabilities?.limits?.max_prompt_tokens) {
modelLimit += chatModelInfo.capabilities.limits.max_prompt_tokens;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,12 @@ export interface ITokenPriceTier {
readonly outputPrice: number;
/** Cost in AICs per million cached (read) tokens */
readonly cacheReadTokenPrice: number;
/**
* The largest prompt size (in tokens) billed at this tier's rates.
* Derived from CAPI `billing.token_prices.<tier>.context_max`.
* Present only when CAPI provides a `long_context` tier.
*/
readonly contextMax?: number;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,24 @@ suite('AgentInstructionsLocator', () => {
expect(paths).not.toContain(`${userHome}/.claude/CLAUDE.md`);
});

test('should collect ~/.copilot/copilot-instructions.md when enabled', async () => {
const userHome = '/home/testuser';
await mockFiles(fileSystem, [
{ path: `${userHome}/.copilot/copilot-instructions.md`, contents: ['Copilot guidelines from home'] },
{ path: `${rootFolder}/src/file.ts`, contents: ['console.log("test");'] },
]);

await configService.setConfig(ConfigKey.UseInstructionFiles, true);
let result = await locator.listAgentInstructions(CancellationToken.None);
let paths = result.map(f => f.uri.path);
expect(paths).toContain(`${userHome}/.copilot/copilot-instructions.md`);

await configService.setConfig(ConfigKey.UseInstructionFiles, false);
result = await locator.listAgentInstructions(CancellationToken.None);
paths = result.map(f => f.uri.path);
expect(paths).not.toContain(`${userHome}/.copilot/copilot-instructions.md`);
});

test('should collect parent folder CLAUDE configurations when includeWorkspaceFolderParents is enabled', async () => {
await mockFiles(fileSystem, [
// `.git/HEAD` marks the parent folder as a repository root.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const AGENT_MD_FILENAME = 'AGENTS.md';
const CLAUDE_MD_FILENAME = 'CLAUDE.md';
const CLAUDE_LOCAL_MD_FILENAME = 'CLAUDE.local.md';
const CLAUDE_CONFIG_FOLDER = '.claude';
const COPILOT_CONFIG_FOLDER = '.copilot';
const COPILOT_CUSTOM_INSTRUCTIONS_FILENAME = 'copilot-instructions.md';
const GITHUB_CONFIG_FOLDER = '.github';

Expand Down Expand Up @@ -93,14 +94,16 @@ export class AgentInstructionsLocator extends Disposable {
promises.push(this.findFilesInRoots([this.envService.userHome], CLAUDE_CONFIG_FOLDER, [claudeMdFile], token, resolvedAgentFiles));
}

// `useCopilotInstructionsFiles` gates only `.github/copilot-instructions.md`.
// `useCopilotInstructionsFiles` gates both workspace and personal
// `copilot-instructions.md` discovery.
// Reuses the existing extension config (default true) instead of hard-coding the qualified key.
const useCopilotInstructionsFiles = this.configurationService.getConfig(ConfigKey.UseInstructionFiles) !== false;
if (!useCopilotInstructionsFiles) {
logger?.logInfo('Copilot instructions files are disabled via configuration.');
} else {
const githubConfigFiles: IWorkspaceInstructionFile[] = [{ fileName: COPILOT_CUSTOM_INSTRUCTIONS_FILENAME, type: AgentInstructionFileType.copilotInstructionsMd }];
promises.push(this.findFilesInRoots(rootFolders, GITHUB_CONFIG_FOLDER, githubConfigFiles, token, resolvedAgentFiles));
const copilotInstructionsFile: IWorkspaceInstructionFile = { fileName: COPILOT_CUSTOM_INSTRUCTIONS_FILENAME, type: AgentInstructionFileType.copilotInstructionsMd };
promises.push(this.findFilesInRoots(rootFolders, GITHUB_CONFIG_FOLDER, [copilotInstructionsFile], token, resolvedAgentFiles)); // copilot-instructions.md in .github folder under workspace root
promises.push(this.findFilesInRoots([this.envService.userHome], COPILOT_CONFIG_FOLDER, [copilotInstructionsFile], token, resolvedAgentFiles)); // copilot-instructions.md in ~/.copilot folder
}

// Files at the workspace root itself (AGENTS.md / CLAUDE.md / CLAUDE.local.md).
Expand Down
2 changes: 1 addition & 1 deletion src/vs/base/browser/ui/dialog/dialog.css
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
}

.monaco-dialog-modal-block.dimmed {
background: rgba(0, 0, 0, 0.3);
background: rgba(0, 0, 0, 0.5);
}

/** Dialog: Container */
Expand Down
Loading
Loading