Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
cf87260
remove references to old setting `github.copilot.chat.advanced.inline…
jrieken Oct 9, 2025
b6e1f19
Merge branch 'main' into joh/slow-meerkat
jrieken Oct 9, 2025
72b6356
play with `InlineChatIntent`
jrieken Oct 9, 2025
b02a448
wip
jrieken Oct 15, 2025
4085ea7
Merge branch 'main' into joh/slow-meerkat
jrieken Oct 15, 2025
5556a00
move things, better/simpler prompt
jrieken Oct 15, 2025
f7d0a5a
cleanup, renames, stuff
jrieken Oct 15, 2025
6d732ea
more wip
jrieken Oct 15, 2025
bb040f3
done after tool call
jrieken Oct 16, 2025
0126eb3
Merge branch 'main' into joh/slow-meerkat
jrieken Oct 17, 2025
25f8dde
edit and generate stest for new InlineChatIntent
jrieken Oct 17, 2025
9865c84
Merge branch 'main' into joh/slow-meerkat
jrieken Oct 17, 2025
0040d85
Merge branch 'main' into joh/slow-meerkat
jrieken Oct 20, 2025
7b9f67b
use codebook for diagnostics
jrieken Oct 21, 2025
dfcba83
inline chat fixing stests
jrieken Oct 22, 2025
f34534e
stest run
jrieken Oct 22, 2025
5136b8d
Merge branch 'main' into joh/slow-meerkat
jrieken Oct 22, 2025
f729e03
Merge branch 'main' into joh/slow-meerkat
jrieken Oct 23, 2025
4758bbe
remove old Inline2 tests
jrieken Oct 23, 2025
c33d8c0
remove slash commands for v2, remove the editCodeIntent path for v2
jrieken Oct 23, 2025
b6c276f
:lipstick:
jrieken Oct 23, 2025
346b154
:lipstick:
jrieken Oct 23, 2025
62b0721
Don't use `diagnosticsTimeout` when with inline chat because the new …
jrieken Oct 23, 2025
2034394
Merge branch 'main' into joh/slow-meerkat
jrieken Oct 23, 2025
e057802
fix compile error
jrieken Oct 23, 2025
e33fa25
stest run
jrieken Oct 23, 2025
11a34db
Merge branch 'main' into joh/slow-meerkat
jrieken Oct 23, 2025
468458e
Merge branch 'main' into joh/slow-meerkat
jrieken Oct 24, 2025
ce2c9e4
update baseline
jrieken Oct 24, 2025
51cfbd6
prevent some JSON errors from empty output
jrieken Oct 24, 2025
03365ca
Merge branch 'main' into joh/slow-meerkat
jrieken Oct 24, 2025
d6fb384
unfresh baseline.json
jrieken Oct 24, 2025
10e76e5
use `MockGithubAvailableEmbeddingTypesService` in stests
jrieken Oct 24, 2025
d838954
back to hamfisted skipping of stests
jrieken Oct 24, 2025
4a09808
Merge branch 'main' into joh/slow-meerkat
jrieken Oct 27, 2025
fe45eaf
send telemetry from inline chat intent
jrieken Oct 27, 2025
c93da1d
tweak some stests
jrieken Oct 27, 2025
04380d0
Merge branch 'main' into joh/slow-meerkat
jrieken Oct 29, 2025
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
48 changes: 2 additions & 46 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1189,53 +1189,9 @@
"fullName": "GitHub Copilot",
"description": "%copilot.edits.description%",
"isDefault": true,
"when": "config.inlineChat.enableV2 || config.github.copilot.chat.advanced.inlineChat2",
"when": "config.inlineChat.enableV2",
"locations": [
"editor"
],
"commands": [
{
"name": "fix",
"description": "%copilot.workspace.fix.description%",
"when": "config.inlineChat.enableV2 || config.github.copilot.chat.advanced.inlineChat2",
"disambiguation": [
{
"category": "fix",
"description": "Propose a fix for the problems in the selected code",
"examples": [
"There is a problem in this code. Rewrite the code to show it with the bug fixed."
]
}
]
},
{
"name": "tests",
"description": "%copilot.workspace.tests.description%",
"when": "config.inlineChat.enableV2 || config.github.copilot.chat.advanced.inlineChat2",
"disambiguation": [
{
"category": "tests",
"description": "Help writing tests for the selected code",
"examples": [
"Help me write tests for the selected code."
]
}
]
},
{
"name": "doc",
"description": "%copilot.workspace.doc.description%",
"when": "config.inlineChat.enableV2 || config.github.copilot.chat.advanced.inlineChat2",
"disambiguation": [
{
"category": "doc",
"description": "Add documentation comment for this symbol",
"examples": [
"Add jsdoc to this method"
]
}
]
}
]
},
{
Expand Down Expand Up @@ -1287,7 +1243,7 @@
"locations": [
"editor"
],
"when": "!config.inlineChat.enableV2 && !config.github.copilot.chat.advanced.inlineChat2",
"when": "!config.inlineChat.enableV2",
"disambiguation": [
{
"category": "unknown",
Expand Down
1 change: 1 addition & 0 deletions src/extension/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const enum Intent {
New = 'new',
NewNotebook = 'newNotebook',
notebookEditor = 'notebookEditor',
InlineChat = 'inlineChat',
Search = 'search',
SemanticSearch = 'semanticSearch',
Terminal = 'terminal',
Expand Down
4 changes: 2 additions & 2 deletions src/extension/conversation/vscode-node/chatParticipants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ class ChatParticipants implements IDisposable {
}

private registerEditingAgentEditor(): IDisposable {
const editingAgent = this.createAgent(editingSessionAgentEditorName, Intent.Edit);
const editingAgent = this.createAgent(editingSessionAgentEditorName, Intent.InlineChat);
editingAgent.iconPath = new vscode.ThemeIcon('copilot');
editingAgent.additionalWelcomeMessage = this.additionalWelcomeMessage;
return editingAgent;
Expand Down Expand Up @@ -364,4 +364,4 @@ Learn more about [GitHub Copilot](https://docs.github.com/copilot/using-github-c
}
}

type IntentOrGetter = Intent | ((request: vscode.ChatRequest) => Intent);
type IntentOrGetter = Intent | ((request: vscode.ChatRequest) => Intent);
223 changes: 223 additions & 0 deletions src/extension/inlineChat/node/inlineChatIntent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import type * as vscode from 'vscode';
import { IAuthenticationService } from '../../../platform/authentication/common/authentication';
import { ChatFetchResponseType, ChatLocation, getErrorDetailsFromChatFetchError } from '../../../platform/chat/common/commonTypes';
import { IEndpointProvider } from '../../../platform/endpoint/common/endpointProvider';
import { IIgnoreService } from '../../../platform/ignore/common/ignoreService';
import { ILogService } from '../../../platform/log/common/logService';
import { isNonEmptyArray } from '../../../util/vs/base/common/arrays';
import { CancellationToken } from '../../../util/vs/base/common/cancellation';
import { Event } from '../../../util/vs/base/common/event';
import { assertType } from '../../../util/vs/base/common/types';
import { localize } from '../../../util/vs/nls';
import { IInstantiationService } from '../../../util/vs/platform/instantiation/common/instantiation';
import { ChatRequestEditorData } from '../../../vscodeTypes';
import { Intent } from '../../common/constants';
import { getAgentTools } from '../../intents/node/agentIntent';
import { ChatVariablesCollection } from '../../prompt/common/chatVariablesCollection';
import { Conversation } from '../../prompt/common/conversation';
import { IToolCall } from '../../prompt/common/intents';
import { ToolCallRound } from '../../prompt/common/toolCallRound';
import { ChatTelemetryBuilder } from '../../prompt/node/chatParticipantTelemetry';
import { IDocumentContext } from '../../prompt/node/documentContext';
import { IIntent } from '../../prompt/node/intents';
import { PromptRenderer } from '../../prompts/node/base/promptRenderer';
import { InlineChat2Prompt } from '../../prompts/node/inline/inlineChat2Prompt';
import { ToolName } from '../../tools/common/toolNames';
import { normalizeToolSchema } from '../../tools/common/toolSchemaNormalizer';
import { CopilotToolMode } from '../../tools/common/toolsRegistry';
import { isToolValidationError, isValidatedToolInput, IToolsService } from '../../tools/common/toolsService';
import { InteractionOutcomeComputer } from './promptCraftingTypes';


const INLINE_CHAT_EXIT_TOOL_NAME = 'inline_chat_exit';

export class InlineChatIntent implements IIntent {

static readonly ID = Intent.InlineChat;

private static readonly _EDIT_TOOLS = new Set<string>([
ToolName.ApplyPatch,
ToolName.EditFile,
ToolName.ReplaceString,
ToolName.MultiReplaceString,
]);

readonly id = InlineChatIntent.ID;

readonly locations = [ChatLocation.Editor];

readonly description: string = '';

constructor(
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IEndpointProvider private readonly _endpointProvider: IEndpointProvider,
@IAuthenticationService private readonly _authenticationService: IAuthenticationService,
@ILogService private readonly _logService: ILogService,
@IToolsService private readonly _toolsService: IToolsService,
@IIgnoreService private readonly _ignoreService: IIgnoreService,
) { }

async handleRequest(conversation: Conversation, request: vscode.ChatRequest, stream: vscode.ChatResponseStream, token: CancellationToken, documentContext: IDocumentContext | undefined, agentName: string, _location: ChatLocation, chatTelemetry: ChatTelemetryBuilder, onPaused: Event<boolean>): Promise<vscode.ChatResult> {

assertType(request.location2 instanceof ChatRequestEditorData);

if (await this._ignoreService.isCopilotIgnored(request.location2.document.uri, token)) {
return {
errorDetails: {
message: localize('inlineChat.ignored', "Copilot is disabled for this file."),
}
};
}

const endpoint = await this._endpointProvider.getChatEndpoint(request);

if (!endpoint.supportsToolCalls) {
return {
errorDetails: {
message: localize('inlineChat.model', "{0} cannot be used for inline chat", endpoint.name),
}
};
}

const inlineChatTools = await this._getAvailableTools(request);

const chatVariables = new ChatVariablesCollection([...request.references]);

const renderer = PromptRenderer.create(this._instantiationService, endpoint, InlineChat2Prompt, {
request,
data: request.location2,
exitToolName: INLINE_CHAT_EXIT_TOOL_NAME
});

const renderResult = await renderer.render(undefined, token, { trace: true });

const telemetry = chatTelemetry.makeRequest(this, ChatLocation.Editor, conversation, renderResult.messages, renderResult.tokenCount, renderResult.references, endpoint, [], inlineChatTools.length);
const outcomeComputer = new InteractionOutcomeComputer(request.location2.document.uri);

stream = outcomeComputer.spyOnStream(stream);
const toolCalls: IToolCall[] = [];

const fetchResult = await endpoint.makeChatRequest2({
debugName: 'InlineChat2Intent',
messages: renderResult.messages,
userInitiatedRequest: true,
location: ChatLocation.Editor,
finishedCb: async (_text, _index, delta) => {

let doneAfterToolCalls = false;

if (isNonEmptyArray(delta.copilotToolCalls)) {
for (const toolCall of delta.copilotToolCalls) {

toolCalls.push(toolCall);

doneAfterToolCalls = doneAfterToolCalls
|| InlineChatIntent._EDIT_TOOLS.has(toolCall.name)
|| toolCall.name === INLINE_CHAT_EXIT_TOOL_NAME;

const validationResult = this._toolsService.validateToolInput(toolCall.name, toolCall.arguments);

if (isToolValidationError(validationResult)) {
this._logService.warn(`Tool ${toolCall.name} invocation failed validation: ${validationResult}`);
break;
}

const input = isValidatedToolInput(validationResult)
? validationResult.inputObj
: JSON.parse(toolCall.arguments);

const copilotTool = this._toolsService.getCopilotTool(toolCall.name as ToolName);
if (copilotTool?.resolveInput) {
copilotTool.resolveInput(input, {
request,
stream,
query: request.prompt,
chatVariables,
history: [],
}, CopilotToolMode.FullContext);
}

await this._toolsService.invokeTool(toolCall.name, {
input,
toolInvocationToken: request.toolInvocationToken,
}, token);
}
}

if (doneAfterToolCalls) {
return 1; // stop generating further
}

return undefined;
},
requestOptions: {
tool_choice: 'auto',
tools: normalizeToolSchema(
endpoint.family,
inlineChatTools.map(tool => ({
type: 'function',
function: {
name: tool.name,
description: tool.description,
parameters: tool.inputSchema && Object.keys(tool.inputSchema).length ? tool.inputSchema : undefined
},
})),
(tool, rule) => {
this._logService.warn(`Tool ${tool} failed validation: ${rule}`);
},
)
}
}, token);

// telemetry
{
const responseText = fetchResult.type === ChatFetchResponseType.Success ? fetchResult.value : '';
const toolCallRound = ToolCallRound.create({
response: responseText,
toolCalls: toolCalls,
toolInputRetry: 0
});

telemetry.sendToolCallingTelemetry([toolCallRound], inlineChatTools, fetchResult.type);

telemetry.sendTelemetry(
fetchResult.requestId, fetchResult.type, responseText,
outcomeComputer.interactionOutcome,
toolCalls
);
}

if (fetchResult.type !== ChatFetchResponseType.Success) {
const details = getErrorDetailsFromChatFetchError(fetchResult, (await this._authenticationService.getCopilotToken()).copilotPlan);
return {
errorDetails: {
message: details.message,
responseIsFiltered: details.responseIsFiltered
}
};
}

return {};

}

private async _getAvailableTools(request: vscode.ChatRequest): Promise<vscode.LanguageModelToolInformation[]> {

const exitTool = this._toolsService.getTool(INLINE_CHAT_EXIT_TOOL_NAME);
assertType(exitTool);

const agentTools = await getAgentTools(this._instantiationService, request);
const editTools = agentTools.filter(tool => InlineChatIntent._EDIT_TOOLS.has(tool.name));

return [exitTool, ...editTools];
}

invoke(): Promise<never> {
throw new TypeError();
}
}
2 changes: 2 additions & 0 deletions src/extension/intents/node/allIntents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@


import { SyncDescriptor } from '../../../util/vs/platform/instantiation/common/descriptors';
import { InlineChatIntent } from '../../inlineChat/node/inlineChatIntent';
import { IntentRegistry } from '../../prompt/node/intentRegistry';
import { AgentIntent } from './agentIntent';
import { AskAgentIntent } from './askAgentIntent';
Expand Down Expand Up @@ -53,5 +54,6 @@ IntentRegistry.setIntents([
new SyncDescriptor(SearchKeywordsIntent),
new SyncDescriptor(AskAgentIntent),
new SyncDescriptor(NotebookEditorIntent),
new SyncDescriptor(InlineChatIntent),
new SyncDescriptor(ChatReplayIntent)
]);
3 changes: 1 addition & 2 deletions src/extension/intents/node/editCodeIntent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,7 @@ export class EditCodeIntent implements IIntent {
const { location, documentContext, request } = invocationContext;
const endpoint = await this.endpointProvider.getChatEndpoint(request);

if (location === ChatLocation.Panel || location === ChatLocation.Notebook
|| (location === ChatLocation.Editor && this.configurationService.getNonExtensionConfig('inlineChat.enableV2'))) {
if (location === ChatLocation.Panel || location === ChatLocation.Notebook) {
return this.instantiationService.createInstance(this.intentOptions.intentInvocation, this, location, endpoint, request, this.intentOptions);
}

Expand Down
4 changes: 3 additions & 1 deletion src/extension/prompt/node/chatParticipantTelemetry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { IChatEndpoint } from '../../../platform/networking/common/networking';
import { ITelemetryService } from '../../../platform/telemetry/common/telemetry';
import { isNotebookCellOrNotebookChatInput } from '../../../util/common/notebooks';
import { IInstantiationService } from '../../../util/vs/platform/instantiation/common/instantiation';
import { Intent } from '../../common/constants';
import { DiagnosticsTelemetryData, findDiagnosticsTelemetry } from '../../inlineChat/node/diagnosticsTelemetry';
import { InteractionOutcome } from '../../inlineChat/node/promptCraftingTypes';
import { AgentIntent } from '../../intents/node/agentIntent';
Expand Down Expand Up @@ -416,7 +417,8 @@ export abstract class ChatTelemetry<C extends IDocumentContext | undefined = IDo
return this._request.modeInstructions2 ? 'custom' :
this._intent.id === AgentIntent.ID ? 'agent' :
(this._intent.id === EditCodeIntent.ID || this._intent.id === EditCode2Intent.ID) ? 'edit' :
'ask';
(this._intent.id === Intent.InlineChat) ? 'inlineChatIntent' :
'ask';
}

public sendToolCallingTelemetry(toolCallRounds: IToolCallRound[], availableTools: readonly vscode.LanguageModelToolInformation[], responseType: ChatFetchResponseType | 'cancelled' | 'maxToolCalls'): void {
Expand Down
4 changes: 2 additions & 2 deletions src/extension/prompts/node/inline/diagnosticsContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -218,11 +218,11 @@ export class DiagnosticRelatedInfo extends PromptElement<DiagnosticRelatedInfoPr

// #region DiagnosticSuggestedFix

interface DiagnosticSuggestedFixProps extends BasePromptElementProps {
export interface DiagnosticSuggestedFixProps extends BasePromptElementProps {
readonly cookbook: Cookbook;
}

class DiagnosticSuggestedFix extends PromptElement<DiagnosticSuggestedFixProps> {
export class DiagnosticSuggestedFix extends PromptElement<DiagnosticSuggestedFixProps> {

render(state: void, sizing: PromptSizing) {
const suggestedFixes = this.props.cookbook.fixes;
Expand Down
2 changes: 1 addition & 1 deletion src/extension/prompts/node/inline/fixCookbookService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export type ManualSuggestedFix = {
additionalContext?: ContextLocation;
};

export class FixCookbookService {
export class FixCookbookService implements IFixCookbookService {
readonly _serviceBrand: undefined;
constructor(
@ITelemetryService private readonly telemetryService: ITelemetryService
Expand Down
Loading
Loading