Skip to content

Commit 4c51de0

Browse files
committed
Merge origin/main into automations new chat input migration
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 83b4f23f-9ae8-4cb3-be91-f383660edc25
2 parents 05614ad + 3aa5403 commit 4c51de0

104 files changed

Lines changed: 4697 additions & 1537 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/skills/agent-host-chat-contributions/SKILL.md

Lines changed: 105 additions & 33 deletions
Large diffs are not rendered by default.

extensions/copilot/package.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4024,6 +4024,15 @@
40244024
],
40254025
"description": "%github.copilot.config.gemini35FlashReducedToolUsePrompt.enabled%"
40264026
},
4027+
"github.copilot.chat.geminiFlashPromptAdditions.enabled": {
4028+
"type": "boolean",
4029+
"default": false,
4030+
"tags": [
4031+
"experimental",
4032+
"onExp"
4033+
],
4034+
"description": "%github.copilot.config.geminiFlashPromptAdditions.enabled%"
4035+
},
40274036
"github.copilot.chat.anthropic.contextEditing.mode": {
40284037
"type": "string",
40294038
"default": "off",

extensions/copilot/package.nls.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@
163163
"github.copilot.config.alternateGptPrompt.enabled": "Enables an experimental alternate prompt for GPT models instead of the default prompt.",
164164
"github.copilot.config.alternateGeminiModelFPrompt.enabled": "Enables an experimental alternate prompt for Gemini Model F instead of the default prompt.",
165165
"github.copilot.config.gemini35FlashReducedToolUsePrompt.enabled": "Enables an experimental prompt for Gemini 3.5 Flash that instructs the model to minimize tool calls to reduce token usage.",
166+
"github.copilot.config.geminiFlashPromptAdditions.enabled": "Enables experimental additional prompt guidance for Gemini Flash 3.6 and 3.7 models.",
166167
"github.copilot.config.gpt5CodexAlternatePrompt": "Specifies an experimental alternate prompt to use for the GPT-5-Codex model.",
167168
"github.copilot.command.fixTestFailure": "Fix Test Failure",
168169
"copilot.description": "Ask or edit in context",

extensions/copilot/src/extension/chatSessions/vscode-node/copilotCloudSessionsProvider.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,25 @@ const OPEN_PULL_REQUEST_COMMAND_ID = 'github.copilot.chat.cloudSessions.openPull
228228
const CLEAR_CACHES_COMMAND_ID = 'github.copilot.chat.cloudSessions.clearCaches';
229229
const CREATE_PULL_REQUEST_FOR_TASK_COMMAND_ID = 'github.copilot.chat.cloudSessions.createPullRequestForTask';
230230
const OPEN_PULL_REQUEST_FOR_TASK_COMMAND_ID = 'github.copilot.chat.cloudSessions.openPullRequestForTask';
231+
232+
export function parseGitHubContextUrl(value: string, kind: 'issue' | 'pullRequest'): { readonly repoId: string; readonly url: string; readonly label: string } | undefined {
233+
const match = /^https:\/\/(?:www\.)?github\.com\/(?<owner>[^/?#]+)\/(?<repository>[^/?#]+)\/(?<resource>issues|pull)\/(?<number>[1-9]\d*)\/?(?:[?#].*)?$/i.exec(value.trim());
234+
if (!match?.groups) {
235+
return undefined;
236+
}
237+
const resource = match.groups.resource.toLowerCase();
238+
if ((resource === 'issues') !== (kind === 'issue')) {
239+
return undefined;
240+
}
241+
242+
const repoId = `${match.groups.owner}/${match.groups.repository}`;
243+
return {
244+
repoId,
245+
url: `https://github.com/${repoId}/${resource}/${match.groups.number}`,
246+
label: `${repoId}#${match.groups.number}`,
247+
};
248+
}
249+
231250
/** Context key gating the chat-input "Create pull request" toolbar action: true while the viewed cloud task is settled and has no PR yet. */
232251
const CAN_CREATE_PULL_REQUEST_CONTEXT_KEY = 'github.copilot.chat.cloudTaskCanCreatePullRequest';
233252
/** Context key gating the chat-input "Open pull request" toolbar action: true once the viewed cloud task has a pull request. */
@@ -706,6 +725,18 @@ export class CopilotCloudSessionsProvider extends Disposable implements vscode.C
706725
clearTimeout(searchTimeout);
707726
}
708727
const query = value.trim();
728+
const pastedSelection = parseGitHubContextUrl(query, kind);
729+
if (pastedSelection) {
730+
searchGeneration++;
731+
quickPick.busy = false;
732+
quickPick.items = [{
733+
label: pastedSelection.label,
734+
description: pastedSelection.repoId,
735+
alwaysShow: true,
736+
selection: pastedSelection,
737+
}];
738+
return;
739+
}
709740
if (query.length < 2) {
710741
if (query.length === 0) {
711742
void search('', ++searchGeneration);

extensions/copilot/src/extension/chatSessions/vscode-node/test/copilotCloudSessionsProvider.spec.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { mock } from '../../../../util/common/test/simpleMock';
1212
import { ChatRequestTurn2, ChatResponseMarkdownPart, ChatResponseTurn2, ChatToolInvocationPart } from '../../../../vscodeTypes';
1313
import { ITaskApiClient, ListTaskEventsOptions, ListTasksOptions } from '../../common/taskApiTypes';
1414
import { ChatSessionContentBuilder, extractTaskErrorDetail, formatTaskStoppedMessage } from '../copilotCloudSessionContentBuilder';
15-
import { formatNewSessionContextReference, getCloudSessionItemMetadata, getCloudSessionResources, normalizeInitialSessionOptions, taskStateToChatSessionStatus } from '../copilotCloudSessionsProvider';
15+
import { formatNewSessionContextReference, getCloudSessionItemMetadata, getCloudSessionResources, normalizeInitialSessionOptions, parseGitHubContextUrl, taskStateToChatSessionStatus } from '../copilotCloudSessionsProvider';
1616
import { TaskApiBackend, parseRepoFromTaskUrl, isCloudCodingAgentTask } from '../taskApiBackend';
1717
import { isActiveTaskState, isFailedTaskState } from '../../vscode/copilotCodingAgentUtils';
1818
import { NullCloudBackendInstrumentation } from '../cloudBackendTelemetry';
@@ -60,6 +60,28 @@ describe('copilotCloudSessionsProvider helpers', () => {
6060
]);
6161
});
6262

63+
it('parses pasted GitHub issue and pull request URLs for the matching picker', () => {
64+
expect({
65+
issue: parseGitHubContextUrl(' https://github.com/microsoft/vscode/ISSUES/333149#issuecomment-1 ', 'issue'),
66+
pullRequest: parseGitHubContextUrl('https://www.github.com/microsoft/vscode/pull/333149/', 'pullRequest'),
67+
wrongPicker: parseGitHubContextUrl('https://github.com/microsoft/vscode/pull/333149', 'issue'),
68+
unrelated: parseGitHubContextUrl('https://example.com/microsoft/vscode/issues/333149', 'issue'),
69+
}).toEqual({
70+
issue: {
71+
repoId: 'microsoft/vscode',
72+
url: 'https://github.com/microsoft/vscode/issues/333149',
73+
label: 'microsoft/vscode#333149',
74+
},
75+
pullRequest: {
76+
repoId: 'microsoft/vscode',
77+
url: 'https://github.com/microsoft/vscode/pull/333149',
78+
label: 'microsoft/vscode#333149',
79+
},
80+
wrongPicker: undefined,
81+
unrelated: undefined,
82+
});
83+
});
84+
6385
it('coerces object-shaped initialSessionOptions into option entries', () => {
6486
const logService = new RecordingLogService();
6587
const sessionResource = vscode.Uri.parse('copilot-cloud-agent:/1');

extensions/copilot/src/extension/inlineEdits/vscode-node/inlineEditProviderFeature.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { ConfigKey, IConfigurationService } from '../../../platform/configuratio
99
import { IEnvService } from '../../../platform/env/common/envService';
1010
import { IVSCodeExtensionContext } from '../../../platform/extContext/common/extensionContext';
1111
import { InlineEditRequestLogContext } from '../../../platform/inlineEdits/common/inlineEditLogContext';
12+
import { IInlineEditsModelService } from '../../../platform/inlineEdits/common/inlineEditsModelService';
1213
import { ObservableGit } from '../../../platform/inlineEdits/common/observableGit';
1314
import { NesHistoryContextProvider } from '../../../platform/inlineEdits/common/workspaceEditTracker/nesHistoryContextProvider';
1415
import { ILogService } from '../../../platform/log/common/logService';
@@ -63,6 +64,9 @@ export class InlineEditProviderFeature {
6364
private readonly _yieldToCopilot = this._configurationService.getExperimentBasedConfigObservable(ConfigKey.TeamInternal.InlineEditsYieldToCopilot, this._expService);
6465
private readonly _excludedProviders = this._configurationService.getExperimentBasedConfigObservable(ConfigKey.TeamInternal.InlineEditsExcludedProviders, this._expService).map(v => v ? v.split(',').map(v => v.trim()).filter(v => v !== '') : []);
6566
private readonly _copilotToken = observableFromEvent(this, this._authenticationService.onDidCopilotTokenChange, () => this._authenticationService.copilotToken);
67+
// Read reactively: on a fetched `/models` deployment this resolves async, so a brief cold-start
68+
// window can emit completions until `onModelListUpdated` fires and re-registers with the excludes.
69+
private readonly _supportsUnifiedCompletions = observableFromEvent(this, this._modelService.onModelListUpdated, () => this._modelService.selectedModelConfiguration().supportsUnifiedCompletions ?? false);
6670

6771
public readonly inlineEditsEnabled = derived(this, (reader) => {
6872
const copilotToken = this._copilotToken.read(reader);
@@ -92,6 +96,7 @@ export class InlineEditProviderFeature {
9296
@IExperimentationService private readonly _expService: IExperimentationService,
9397
@IEnvService private readonly _envService: IEnvService,
9498
@IInstantiationService private readonly _instantiationService: IInstantiationService,
99+
@IInlineEditsModelService private readonly _modelService: IInlineEditsModelService,
95100
) {
96101
}
97102

@@ -158,8 +163,11 @@ export class InlineEditProviderFeature {
158163
const provider = this._instantiationService.createInstance(InlineCompletionProviderImpl, model, logger, logContextRecorder, inlineEditDebugComponent, telemetrySender, expectedEditCaptureController);
159164

160165
const unificationStateValue = unificationState.read(reader);
166+
// Unify when the selected model's strategy bakes in `supportsUnifiedCompletions`, or when the
167+
// core deployment/ExP `modelUnification` toggle is set.
168+
const modelUnification = this._supportsUnifiedCompletions.read(reader) || (unificationStateValue?.modelUnification ?? false);
161169
let excludes = this._excludedProviders.read(reader);
162-
if (unificationStateValue?.modelUnification) {
170+
if (modelUnification) {
163171
excludes = excludes.slice(0);
164172
if (!excludes.includes('completions')) {
165173
excludes.push('completions');

extensions/copilot/src/extension/prompts/node/agent/geminiPrompts.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,14 @@ export class DefaultGeminiAgentPrompt extends PromptElement<DefaultAgentPromptPr
4444
&& !isExcludedGeminiFamily
4545
&& this.configurationService.getExperimentBasedConfig(ConfigKey.EnableGemini3ReducedToolUsePrompt, this.experimentationService);
4646

47+
// Experiment to study additional Gemini Flash 3.6/3.7 prompt guidance on the metrics.
48+
const isGeminiFlash36Or37 = !!modelFamily && (
49+
modelFamily.includes('gemini-3.6-flash')
50+
|| modelFamily.includes('gemini-3.7-flash')
51+
);
52+
const enableFlashPromptAdditions = isGeminiFlash36Or37
53+
&& this.configurationService.getExperimentBasedConfig(ConfigKey.EnableGeminiFlashPromptAdditions, this.experimentationService);
54+
4755
return <InstructionMessage>
4856
<Tag name='instructions'>
4957
You are a highly sophisticated automated coding agent with expert-level knowledge across many different programming languages and frameworks.<br />
@@ -55,6 +63,7 @@ export class DefaultGeminiAgentPrompt extends PromptElement<DefaultAgentPromptPr
5563
? <>Tool calls to read files or search are expensive. Minimize their use by solving tasks in the fewest steps and tool calls possible.<br /></>
5664
: <>If you aren't sure which tool is relevant, you can call multiple tools. You can call tools repeatedly to take actions or gather as much context as needed until you have completed the task fully. Don't give up unless you are sure the request cannot be fulfilled with the tools you have. It's YOUR RESPONSIBILITY to make sure that you have done all you can to collect necessary context.<br /></>}
5765
When reading files, prefer reading large meaningful chunks rather than consecutive small sections to minimize tool calls and gain better context.<br />
66+
{enableFlashPromptAdditions && <>**Read ast/definitions first**: When inspecting a new codebase, prioritize reading configuration files (e.g. package.json, tsconfig.json) or directory listings before searching for source files blindly.<br /></>}
5867
Don't make assumptions about the situation- gather context first, then perform the task or answer the question.<br />
5968
{!this.props.codesearchMode && <>Think creatively and explore the workspace in order to make a complete fix.<br /></>}
6069
Don't repeat yourself after a tool call, pick up where you left off.<br />
@@ -75,6 +84,8 @@ export class DefaultGeminiAgentPrompt extends PromptElement<DefaultAgentPromptPr
7584
{tools[ToolName.FindTextInFiles] && <>You can use the {ToolName.FindTextInFiles} to get an overview of a file by searching for a string within that one file, instead of using {ToolName.ReadFile} many times.<br /></>}
7685
{tools[ToolName.Codebase] && <>If you don't know exactly the string or filename pattern you're looking for, use {ToolName.Codebase} to do a semantic search across the workspace.<br /></>}
7786
{tools[ToolName.CoreRunInTerminal] && <>Don't call the {ToolName.CoreRunInTerminal} tool multiple times in parallel. Instead, run one command and wait for the output before running the next command.<br /></>}
87+
{enableFlashPromptAdditions && tools[ToolName.CoreRunInTerminal] && <>**Tool Batching**: Combine terminal execution steps. If you need to verify multiple files or run multiple tests, chain them into a single terminal command (e.g., pytest tests/a.py tests/b.py).<br /></>}
88+
{enableFlashPromptAdditions && tools[ToolName.FindTextInFiles] && <>**Search Precision**: Refine {ToolName.FindTextInFiles} patterns to target exact definitions rather than running broad, high-volume keyword searches that clutter the context window.<br /></>}
7889
When invoking a tool that takes a file path, always use the absolute file path. If the file has a scheme like untitled: or vscode-userdata:, then use a URI with the scheme.<br />
7990
{tools[ToolName.CoreRunInTerminal] && <>NEVER try to edit a file by running terminal commands unless the user specifically asks for it.<br /></>}
8091
{!tools.hasSomeEditTool && <>You don't currently have any tools available for editing files. If the user asks you to edit a file, you can ask the user to enable editing tools or print a codeblock with the suggested changes.<br /></>}

extensions/copilot/src/extension/prompts/node/agent/test/agentPrompt.spec.tsx

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { createTextDocumentData } from '../../../../../util/common/test/shims/te
1818
import { URI } from '../../../../../util/vs/base/common/uri';
1919
import { SyncDescriptor } from '../../../../../util/vs/platform/instantiation/common/descriptors';
2020
import { IInstantiationService } from '../../../../../util/vs/platform/instantiation/common/instantiation';
21+
import type { LanguageModelToolInformation } from 'vscode';
2122
import { ChatRequestEditedFileEventKind, LanguageModelTextPart, LanguageModelToolResult } from '../../../../../vscodeTypes';
2223
import { addCacheBreakpoints } from '../../../../intents/node/cacheBreakpoints';
2324
import { ChatVariablesCollection } from '../../../../prompt/common/chatVariablesCollection';
@@ -385,3 +386,90 @@ testFamilies.forEach(family => {
385386
});
386387
});
387388
});
389+
390+
suite('AgentPrompt - Gemini Flash prompt additions experiment', () => {
391+
let accessor: ITestingServicesAccessor;
392+
393+
beforeAll(() => {
394+
const services = createExtensionUnitTestingServices();
395+
services.define(IWorkspaceService, new SyncDescriptor(TestWorkspaceService, [[URI.file('/workspace')]]));
396+
services.define(IChatMLFetcher, new StaticChatMLFetcher([]));
397+
accessor = services.createTestingAccessor();
398+
});
399+
400+
afterAll(() => {
401+
accessor.dispose();
402+
});
403+
404+
async function renderForFamily(family: string, includeToolSpecificTools = true): Promise<string> {
405+
const instaService = accessor.get(IInstantiationService);
406+
const endpoint = instaService.createInstance(MockEndpoint, family);
407+
const toolsService = accessor.get(IToolsService);
408+
const turn = new Turn('turnId', { type: 'user', message: 'hello' });
409+
const conversation = new Conversation('sessionId', [turn]);
410+
const customizations = await PromptRegistry.resolveAllCustomizations(instaService, endpoint);
411+
const syntheticTool = (name: ToolName): LanguageModelToolInformation => ({
412+
name,
413+
description: '',
414+
inputSchema: undefined,
415+
tags: [],
416+
source: undefined,
417+
});
418+
const availableTools = includeToolSpecificTools
419+
? [...toolsService.tools, syntheticTool(ToolName.CoreRunInTerminal), syntheticTool(ToolName.FindTextInFiles)]
420+
: toolsService.tools.filter(t => t.name !== ToolName.CoreRunInTerminal && t.name !== ToolName.FindTextInFiles);
421+
const props: AgentPromptProps = {
422+
priority: 1,
423+
endpoint,
424+
location: ChatLocation.Panel,
425+
promptContext: {
426+
chatVariables: new ChatVariablesCollection(),
427+
history: [],
428+
query: 'hello',
429+
conversation,
430+
tools: {
431+
availableTools,
432+
toolInvocationToken: null as never,
433+
toolReferences: [],
434+
},
435+
},
436+
customizations,
437+
};
438+
const renderer = PromptRenderer.create(instaService, endpoint, AgentPrompt, props);
439+
const r = await renderer.render();
440+
return r.messages.map(m => messageToMarkdown(m)).join('\n\n');
441+
}
442+
443+
function assertAdditions(rendered: string, expected: { readAst: boolean; toolBatching: boolean; searchPrecision: boolean }) {
444+
expect({
445+
readAst: rendered.includes('Read ast/definitions first'),
446+
toolBatching: rendered.includes('**Tool Batching**'),
447+
searchPrecision: rendered.includes('**Search Precision**'),
448+
}).toEqual(expected);
449+
}
450+
451+
test('additions appear for Gemini Flash 3.6 when experiment is enabled', async () => {
452+
accessor.get(IConfigurationService).setConfig(ConfigKey.EnableGeminiFlashPromptAdditions, true);
453+
assertAdditions(await renderForFamily('gemini-3.6-flash'), { readAst: true, toolBatching: true, searchPrecision: true });
454+
});
455+
456+
test('additions appear for Gemini Flash 3.7 when experiment is enabled', async () => {
457+
accessor.get(IConfigurationService).setConfig(ConfigKey.EnableGeminiFlashPromptAdditions, true);
458+
assertAdditions(await renderForFamily('gemini-3.7-flash'), { readAst: true, toolBatching: true, searchPrecision: true });
459+
});
460+
461+
test('tool-specific additions are gated on tool availability', async () => {
462+
accessor.get(IConfigurationService).setConfig(ConfigKey.EnableGeminiFlashPromptAdditions, true);
463+
assertAdditions(await renderForFamily('gemini-3.6-flash', /* includeToolSpecificTools */ false), { readAst: true, toolBatching: false, searchPrecision: false });
464+
});
465+
466+
test('additions are omitted for Gemini Flash 3.6 when experiment is disabled', async () => {
467+
accessor.get(IConfigurationService).setConfig(ConfigKey.EnableGeminiFlashPromptAdditions, false);
468+
assertAdditions(await renderForFamily('gemini-3.6-flash'), { readAst: false, toolBatching: false, searchPrecision: false });
469+
});
470+
471+
test('additions are omitted for other Gemini families even when experiment is enabled', async () => {
472+
accessor.get(IConfigurationService).setConfig(ConfigKey.EnableGeminiFlashPromptAdditions, true);
473+
assertAdditions(await renderForFamily('gemini-2.0-flash'), { readAst: false, toolBatching: false, searchPrecision: false });
474+
});
475+
});

extensions/copilot/src/platform/configuration/common/configurationService.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1072,6 +1072,7 @@ export namespace ConfigKey {
10721072
export const EnableAlternateGptPrompt = defineSetting<boolean>('chat.alternateGptPrompt.enabled', ConfigType.ExperimentBased, false);
10731073
export const EnableAlternateGeminiModelFPrompt = defineSetting<boolean>('chat.alternateGeminiModelFPrompt.enabled', ConfigType.ExperimentBased, false);
10741074
export const EnableGemini3ReducedToolUsePrompt = defineSetting<boolean>('chat.gemini35FlashReducedToolUsePrompt.enabled', ConfigType.ExperimentBased, true);
1075+
export const EnableGeminiFlashPromptAdditions = defineSetting<boolean>('chat.geminiFlashPromptAdditions.enabled', ConfigType.ExperimentBased, false);
10751076

10761077
export const EnableOrganizationCustomAgents = defineSetting<boolean>('chat.organizationCustomAgents.enabled', ConfigType.Simple, true);
10771078
export const EnableOrganizationInstructions = defineSetting<boolean>('chat.organizationInstructions.enabled', ConfigType.Simple, true);

0 commit comments

Comments
 (0)