From 24c0c3dbced92ae3518ec85d56eb3b25cd07fc51 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Sun, 8 Mar 2026 17:10:16 +1100 Subject: [PATCH] Update github copilot npm (#4274) * Update github copilot extension * Update tool calls * Updates --- extensions/copilot/package-lock.json | 56 +++---- extensions/copilot/package.json | 2 +- .../copilotcli/common/copilotCLITools.ts | 147 +++++++++--------- .../common/test/copilotCLITools.spec.ts | 38 ++++- extensions/copilot/test/e2e/cli.stest.ts | 2 + 5 files changed, 137 insertions(+), 108 deletions(-) diff --git a/extensions/copilot/package-lock.json b/extensions/copilot/package-lock.json index b53bed90c83..03b245671ca 100644 --- a/extensions/copilot/package-lock.json +++ b/extensions/copilot/package-lock.json @@ -13,7 +13,7 @@ "@anthropic-ai/claude-agent-sdk": "^0.2.63", "@anthropic-ai/sdk": "^0.78.0", "@github/blackbird-external-ingest-utils": "^0.3.0", - "@github/copilot": "^0.0.421", + "@github/copilot": "^1.0.2", "@google/genai": "^1.22.0", "@humanwhocodes/gitignore-to-minimatch": "1.0.2", "@microsoft/tiktokenizer": "^1.0.10", @@ -3182,26 +3182,26 @@ "license": "MIT" }, "node_modules/@github/copilot": { - "version": "0.0.421", - "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-0.0.421.tgz", - "integrity": "sha512-nDUt9f5al7IgBOTc7AwLpqvaX61VsRDYDQ9D5iR0QQzHo4pgDcyOXIjXUQUKsJwObXHfh6qR+Jm1vnlbw5cacg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.2.tgz", + "integrity": "sha512-716SIZMYftldVcJay2uZOzsa9ROGGb2Mh2HnxbDxoisFsWNNgZlQXlV7A+PYoGsnAo2Zk/8e1i5SPTscGf2oww==", "license": "SEE LICENSE IN LICENSE.md", "bin": { "copilot": "npm-loader.js" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "0.0.421", - "@github/copilot-darwin-x64": "0.0.421", - "@github/copilot-linux-arm64": "0.0.421", - "@github/copilot-linux-x64": "0.0.421", - "@github/copilot-win32-arm64": "0.0.421", - "@github/copilot-win32-x64": "0.0.421" + "@github/copilot-darwin-arm64": "1.0.2", + "@github/copilot-darwin-x64": "1.0.2", + "@github/copilot-linux-arm64": "1.0.2", + "@github/copilot-linux-x64": "1.0.2", + "@github/copilot-win32-arm64": "1.0.2", + "@github/copilot-win32-x64": "1.0.2" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "0.0.421", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-0.0.421.tgz", - "integrity": "sha512-S4plFsxH7W8X1gEkGNcfyKykIji4mNv8BP/GpPs2Ad84qWoJpZzfZsjrjF0BQ8mvFObWp6Ft2SZOnJzFZW1Ftw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.2.tgz", + "integrity": "sha512-dYoeaTidsphRXyMjvAgpjEbBV41ipICnXURrLFEiATcjC4IY6x2BqPOocrExBYW/Tz2VZvDw51iIZaf6GXrTmw==", "cpu": [ "arm64" ], @@ -3215,9 +3215,9 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "0.0.421", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-0.0.421.tgz", - "integrity": "sha512-h+Dbfq8ByAielLYIeJbjkN/9Abs6AKHFi+XuuzEy4YA9jOA42uKMFsWYwaoYH8ZLK9Y+4wagYI9UewVPnyIWPA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.2.tgz", + "integrity": "sha512-8+Z9dYigEfXf0wHl9c2tgFn8Cr6v4RAY8xTgHMI9mZInjQyxVeBXCxbE2VgzUtDUD3a705Ka2d8ZOz05aYtGsg==", "cpu": [ "x64" ], @@ -3231,9 +3231,9 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "0.0.421", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-0.0.421.tgz", - "integrity": "sha512-cxlqDRR/wKfbdzd456N2h7sZOZY069wU2ycSYSmo7cC75U5DyhMGYAZwyAhvQ7UKmS5gJC/wgSgye0njuK22Xg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.2.tgz", + "integrity": "sha512-ik0Y5aTXOFRPLFrNjZJdtfzkozYqYeJjVXGBAH3Pp1nFZRu/pxJnrnQ1HrqO/LEgQVbJzAjQmWEfMbXdQIxE4Q==", "cpu": [ "arm64" ], @@ -3247,9 +3247,9 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "0.0.421", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-0.0.421.tgz", - "integrity": "sha512-7np5b6EEemJ3U3jnl92buJ88nlpqOAIrLaJxx3pJGrP9SVFMBD/6EAlfIQ5m5QTfs+/vIuTKWBrq1wpFVZZUcQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.2.tgz", + "integrity": "sha512-mHSPZjH4nU9rwbfwLxYJ7CQ90jK/Qu1v2CmvBCUPfmuGdVwrpGPHB5FrB+f+b0NEXjmemDWstk2zG53F7ppHfw==", "cpu": [ "x64" ], @@ -3263,9 +3263,9 @@ } }, "node_modules/@github/copilot-win32-arm64": { - "version": "0.0.421", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-0.0.421.tgz", - "integrity": "sha512-T6qCqOnijD5pmC0ytVsahX3bpDnXtLTgo9xFGo/BGaPEvX02ePkzcRZkfkOclkzc8QlkVji6KqZYB+qMZTliwg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.2.tgz", + "integrity": "sha512-tLW2CY/vg0fYLp8EuiFhWIHBVzbFCDDpohxT/F/XyMAdTVSZLnopCcxQHv2BOu0CVGrYjlf7YOIwPfAKYml1FA==", "cpu": [ "arm64" ], @@ -3279,9 +3279,9 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "0.0.421", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-0.0.421.tgz", - "integrity": "sha512-KDfy3wsRQFIcOQDdd5Mblvh+DWRq+UGbTQ34wyW36ws1BsdWkV++gk9bTkeJRsPbQ51wsJ0V/jRKEZv4uK5dTA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.2.tgz", + "integrity": "sha512-cFlc3xMkKKFRIYR00EEJ2XlYAemeh5EZHsGA8Ir2G0AH+DOevJbomdP1yyCC5gaK/7IyPkHX3sGie5sER2yPvQ==", "cpu": [ "x64" ], diff --git a/extensions/copilot/package.json b/extensions/copilot/package.json index d2152662937..d77bac382cd 100644 --- a/extensions/copilot/package.json +++ b/extensions/copilot/package.json @@ -6062,7 +6062,7 @@ "@anthropic-ai/claude-agent-sdk": "^0.2.63", "@anthropic-ai/sdk": "^0.78.0", "@github/blackbird-external-ingest-utils": "^0.3.0", - "@github/copilot": "^0.0.421", + "@github/copilot": "^1.0.2", "@google/genai": "^1.22.0", "@humanwhocodes/gitignore-to-minimatch": "1.0.2", "@microsoft/tiktokenizer": "^1.0.10", diff --git a/extensions/copilot/src/extension/chatSessions/copilotcli/common/copilotCLITools.ts b/extensions/copilot/src/extension/chatSessions/copilotcli/common/copilotCLITools.ts index 83cf76a1c92..13256f4244d 100644 --- a/extensions/copilot/src/extension/chatSessions/copilotcli/common/copilotCLITools.ts +++ b/extensions/copilot/src/extension/chatSessions/copilotcli/common/copilotCLITools.ts @@ -38,6 +38,7 @@ interface ViewTool { arguments: { path: string; view_range?: [number, number]; + forceReadLargeFiles?: boolean; }; } @@ -50,13 +51,6 @@ interface EditTool { }; } -interface UndoEditTool { - toolName: 'undo_edit'; - arguments: { - path: string; - }; -} - interface StrReplaceTool { toolName: 'str_replace'; arguments: { @@ -121,13 +115,13 @@ interface GrepTool { arguments: { pattern: string; path?: string; - output_mode: 'content' | 'files_with_matches' | 'count'; + output_mode?: 'content' | 'files_with_matches' | 'count'; glob?: string; type?: string; '-i'?: boolean; - '-A'?: boolean; - '-B'?: boolean; - '-C'?: boolean; + '-A'?: number; + '-B'?: number; + '-C'?: number; '-n'?: boolean; head_limit?: number; multiline?: boolean; @@ -187,26 +181,10 @@ type WebSearchTool = { }; }; -type SearchTool = { - toolName: 'search'; +type SearchCodeSubagentTool = { + toolName: 'search_code_subagent'; arguments: { - question: string; - reason: string; - searchCommand: string; - }; -}; - -type SearchBashTool = { - toolName: 'search_bash'; - arguments: { - command: string; - }; -}; - -type SemanticCodeSearchTool = { - toolName: 'semantic_code_search'; - arguments: { - question: string; + query: string; }; }; @@ -230,7 +208,7 @@ type ShowFileTool = { toolName: 'show_file'; arguments: { path: string; - view_range?: [number, number]; + view_range?: number[]; diff?: boolean; }; }; @@ -258,10 +236,18 @@ type TaskCompleteTool = { type AskUserTool = { toolName: 'ask_user'; - arguments: { + arguments: + | { question: string; choices?: string[]; allow_freeform?: boolean; + } + | { + message: string; + requestedSchema: { + properties: Record; + required?: string[]; + }; }; }; @@ -331,10 +317,19 @@ type LspTool = { }; }; +type CreatePullRequestTool = { + toolName: 'create_pull_request'; + arguments: { + title: string; + description?: string; + draft?: boolean; + }; +}; + type DependencyCheckerTool = { toolName: 'gh-advisory-database'; arguments: { - dependencies: { version: string; name: string }[]; + dependencies: { version: string; name: string; ecosystem: string }[]; }; }; @@ -345,6 +340,7 @@ type StoreMemoryTool = { fact: string; citations: string; reason: string; + category: string; }; }; @@ -354,24 +350,24 @@ type ParallelValidationTool = { }; -type StringReplaceArgumentTypes = CreateTool | ViewTool | StrReplaceTool | EditTool | InsertTool | UndoEditTool; +type StringReplaceArgumentTypes = CreateTool | ViewTool | StrReplaceTool | EditTool | InsertTool; type ToStringReplaceEditorArguments = { command: T['toolName']; } & T['arguments']; type StringReplaceEditorTool = { toolName: 'str_replace_editor'; arguments: ToStringReplaceEditorArguments | ToStringReplaceEditorArguments | ToStringReplaceEditorArguments | ToStringReplaceEditorArguments | - ToStringReplaceEditorArguments | ToStringReplaceEditorArguments; + ToStringReplaceEditorArguments; }; -export type ToolInfo = StringReplaceEditorTool | EditTool | CreateTool | ViewTool | UndoEditTool | InsertTool | +export type ToolInfo = StringReplaceEditorTool | EditTool | CreateTool | ViewTool | InsertTool | ShellTool | WriteShellTool | ReadShellTool | StopShellTool | ListShellTool | GrepTool | GLobTool | ReportIntentTool | ThinkTool | ReportProgressTool | - SearchTool | SearchBashTool | SemanticCodeSearchTool | + SearchCodeSubagentTool | ReplyToCommentTool | CodeReviewTool | WebFetchTool | UpdateTodoTool | WebSearchTool | ShowFileTool | FetchCopilotCliDocumentationTool | ProposeWorkTool | TaskCompleteTool | AskUserTool | SkillTool | TaskTool | ListAgentsTool | ReadAgentTool | - ExitPlanModeTool | SqlTool | LspTool | DependencyCheckerTool | StoreMemoryTool | ParallelValidationTool; + ExitPlanModeTool | SqlTool | LspTool | CreatePullRequestTool | DependencyCheckerTool | StoreMemoryTool | ParallelValidationTool; export type ToolCall = ToolInfo & { toolCallId: string; @@ -420,7 +416,7 @@ export function getAffectedUrisForEditTool(data: { toolName: string; arguments?: return [URI.file(toolCall.arguments.path)]; } - if ((toolCall.toolName === 'create' || toolCall.toolName === 'edit' || toolCall.toolName === 'undo_edit') && typeof toolCall.arguments.path === 'string') { + if ((toolCall.toolName === 'create' || toolCall.toolName === 'edit') && typeof toolCall.arguments.path === 'string') { return [URI.file(toolCall.arguments.path)]; } @@ -812,6 +808,13 @@ export function createCopilotCLIToolInvocation(data: { } return undefined; } + if (toolCall.toolName === 'show_file') { + // Currently there's no good way to render this to the user. + // Its a way to draw users attention to a file/code block. + // Generally models render the codeblock in the response, but here we have a tool call. + // Its a WIP, no clear way to render in CLI either, hence decided to hide in VS Code. + return undefined; + } const [friendlyToolName, formatter] = ToolFriendlyNameAndHandlers[toolCall.toolName]; const invocation = new ChatToolInvocationPart(friendlyToolName ?? toolCall.toolName ?? 'unknown', toolCall.toolCallId ?? '', false as unknown as string); @@ -827,14 +830,12 @@ type PostInvocationFormatter = (invocation: ChatToolInvocationPart, toolCall: To type ToolCallFor = Extract; type ToolCallResult = ToolExecutionCompleteEvent['data']; - const ToolFriendlyNameAndHandlers: { [K in ToolCall['toolName']]: [title: string, pre: (invocation: ChatToolInvocationPart, toolCall: ToolCallFor, editId?: string, workingDirectory?: URI) => void, post: (invocation: ChatToolInvocationPart, toolCall: ToolCallFor, result: ToolCallResult, workingDirectory?: URI) => void] } = { 'str_replace_editor': [l10n.t('Edit File'), formatStrReplaceEditorInvocation, genericToolInvocationCompleted], 'edit': [l10n.t('Edit File'), formatEditToolInvocation, genericToolInvocationCompleted], 'str_replace': [l10n.t('Edit File'), formatEditToolInvocation, genericToolInvocationCompleted], 'create': [l10n.t('Create File'), formatCreateToolInvocation, genericToolInvocationCompleted], 'insert': [l10n.t('Edit File'), formatInsertToolInvocation, genericToolInvocationCompleted], - 'undo_edit': [l10n.t('Edit File'), formatUndoEdit, genericToolInvocationCompleted], 'view': [l10n.t('Read'), formatViewToolInvocation, genericToolInvocationCompleted], 'bash': [l10n.t('Run Shell Command'), formatShellInvocation, formatShellInvocationCompleted], 'powershell': [l10n.t('Run Shell Command'), formatShellInvocation, formatShellInvocationCompleted], @@ -844,12 +845,10 @@ const ToolFriendlyNameAndHandlers: { [K in ToolCall['toolName']]: [title: string 'read_powershell': [l10n.t('Read Terminal'), emptyInvocation, genericToolInvocationCompleted], 'stop_bash': [l10n.t('Stop Terminal Session'), emptyInvocation, genericToolInvocationCompleted], 'stop_powershell': [l10n.t('Stop Terminal Session'), emptyInvocation, genericToolInvocationCompleted], - 'search': [l10n.t('Search'), formatSearchToolInvocation, genericToolInvocationCompleted], 'grep': [l10n.t('Search'), formatSearchToolInvocation, formatSearchToolInvocationCompleted], 'rg': [l10n.t('Search'), formatSearchToolInvocation, formatSearchToolInvocationCompleted], 'glob': [l10n.t('Search'), formatSearchToolInvocation, formatSearchToolInvocationCompleted], - 'search_bash': [l10n.t('Search'), formatSearchToolInvocation, genericToolInvocationCompleted], - 'semantic_code_search': [l10n.t('Search'), formatSearchToolInvocation, genericToolInvocationCompleted], + 'search_code_subagent': [l10n.t('Search Code'), formatSearchToolInvocation, genericToolInvocationCompleted], 'reply_to_comment': [l10n.t('Reply to Comment'), formatReplyToCommentInvocation, genericToolInvocationCompleted], 'code_review': [l10n.t('Code Review'), formatCodeReviewInvocation, genericToolInvocationCompleted], 'report_intent': [l10n.t('Report Intent'), emptyInvocation, genericToolInvocationCompleted], @@ -870,6 +869,7 @@ const ToolFriendlyNameAndHandlers: { [K in ToolCall['toolName']]: [title: string 'exit_plan_mode': [l10n.t('Exit Plan Mode'), formatExitPlanModeInvocation, genericToolInvocationCompleted], 'sql': [l10n.t('Execute SQL'), formatSqlInvocation, genericToolInvocationCompleted], 'lsp': [l10n.t('Language Server'), formatLspInvocation, genericToolInvocationCompleted], + 'create_pull_request': [l10n.t('Create Pull Request'), formatCreatePullRequestInvocation, genericToolInvocationCompleted], 'gh-advisory-database': [l10n.t('Check Dependencies'), emptyInvocation, genericToolInvocationCompleted], 'store_memory': [l10n.t('Store Memory'), formatStoreMemoryInvocation, genericToolInvocationCompleted], 'list_bash': [l10n.t('List Shell Sessions'), emptyInvocation, genericToolInvocationCompleted], @@ -893,7 +893,7 @@ function formatViewToolInvocation(invocation: ChatToolInvocationPart, toolCall: if (!args.path) { return; - } else if (args.view_range && args.view_range[1] >= args.view_range[0] && args.view_range[0] >= 0) { + } else if (args.view_range && args.view_range.length === 2 && args.view_range[1] >= args.view_range[0] && args.view_range[0] >= 0) { const [start, end] = args.view_range; const location = new Location(Uri.file(args.path), new Range(start === 0 ? start : start - 1, 0, end, 0)); const display = formatUriForFileWidget(location); @@ -930,9 +930,6 @@ function formatStrReplaceEditorInvocation(invocation: ChatToolInvocationPart, to case 'create': formatCreateToolInvocation(invocation, { toolName: 'create', arguments: args } as CreateTool); break; - case 'undo_edit': - formatUndoEdit(invocation, { toolName: 'undo_edit', arguments: args } as UndoEditTool); - break; default: invocation.invocationMessage = new MarkdownString(l10n.t("Modified {0}", display)); } @@ -945,14 +942,6 @@ function formatInsertToolInvocation(invocation: ChatToolInvocationPart, toolCall } } -function formatUndoEdit(invocation: ChatToolInvocationPart, toolCall: UndoEditTool): void { - const args = toolCall.arguments; - if (args.path) { - invocation.invocationMessage = new MarkdownString(l10n.t("Undoing edit in {0}", formatUriForFileWidget(Uri.file(args.path)))); - invocation.pastTenseMessage = new MarkdownString(l10n.t("Undid edit in {0}", formatUriForFileWidget(Uri.file(args.path)))); - } -} - function formatEditToolInvocation(invocation: ChatToolInvocationPart, toolCall: EditTool, editId?: string): void { const args = toolCall.arguments; const display = args.path ? formatUriForFileWidget(Uri.file(args.path)) : ''; @@ -1054,14 +1043,8 @@ function formatShellInvocationCompleted(invocation: ChatToolInvocationPart, tool }; invocation.toolSpecificData = toolSpecificData; } -function formatSearchToolInvocation(invocation: ChatToolInvocationPart, toolCall: SearchTool | GLobTool | GrepTool | SearchBashTool | SemanticCodeSearchTool): void { - if (toolCall.toolName === 'search') { - invocation.invocationMessage = `Criteria: ${toolCall.arguments.question} \nReason: ${toolCall.arguments.reason}`; - } else if (toolCall.toolName === 'semantic_code_search') { - invocation.invocationMessage = `Criteria: ${toolCall.arguments.question}`; - } else if (toolCall.toolName === 'search_bash') { - invocation.invocationMessage = `Command: \`${toolCall.arguments.command}\``; - } else if (toolCall.toolName === 'glob') { +function formatSearchToolInvocation(invocation: ChatToolInvocationPart, toolCall: SearchCodeSubagentTool | GLobTool | GrepTool): void { + if (toolCall.toolName === 'glob') { const searchInPath = toolCall.arguments.path ? ` in \`${toolCall.arguments.path}\`` : ''; invocation.invocationMessage = `Search for files matching \`${toolCall.arguments.pattern}\`${searchInPath}`; invocation.pastTenseMessage = `Searched for files matching \`${toolCall.arguments.pattern}\`${searchInPath}`; @@ -1069,17 +1052,14 @@ function formatSearchToolInvocation(invocation: ChatToolInvocationPart, toolCall const searchInPath = toolCall.arguments.path ? ` in \`${toolCall.arguments.path}\`` : ''; invocation.invocationMessage = `Search for files matching \`${toolCall.arguments.pattern}\`${searchInPath}`; invocation.pastTenseMessage = `Searched for files matching \`${toolCall.arguments.pattern}\`${searchInPath}`; + } else if (toolCall.toolName === 'search_code_subagent') { + invocation.invocationMessage = `Criteria: ${toolCall.arguments.query}`; + invocation.pastTenseMessage = `Searched code for: ${toolCall.arguments.query}`; } } -function formatSearchToolInvocationCompleted(invocation: ChatToolInvocationPart, toolCall: SearchTool | GLobTool | GrepTool | SearchBashTool | SemanticCodeSearchTool, result: ToolCallResult, workingDirectory?: URI): void { - if (toolCall.toolName === 'search') { - // invocation.invocationMessage = `Criteria: ${toolCall.arguments.question} \nReason: ${toolCall.arguments.reason}`; - } else if (toolCall.toolName === 'semantic_code_search') { - // invocation.invocationMessage = `Criteria: ${toolCall.arguments.question}`; - } else if (toolCall.toolName === 'search_bash') { - // invocation.invocationMessage = `Command: \`${toolCall.arguments.command}\``; - } else if (toolCall.toolName === 'glob' || toolCall.toolName === 'grep' || toolCall.toolName === 'rg') { +function formatSearchToolInvocationCompleted(invocation: ChatToolInvocationPart, toolCall: SearchCodeSubagentTool | GLobTool | GrepTool, result: ToolCallResult, workingDirectory?: URI): void { + if (toolCall.toolName === 'glob' || toolCall.toolName === 'grep' || toolCall.toolName === 'rg') { const messagesIndicatingNoMatches = ['Pattern matched but no output generated', 'Pattern matched but no files found', 'No matches found', 'no files matched the pattern'].map(msg => msg.toLowerCase()); let searchPath = toolCall.arguments.path ? Uri.file(toolCall.arguments.path) : workingDirectory; @@ -1130,10 +1110,14 @@ function formatShowFileInvocation(invocation: ChatToolInvocationPart, toolCall: if (args.diff) { invocation.invocationMessage = new MarkdownString(l10n.t("Showing diff of {0}", display)); invocation.pastTenseMessage = new MarkdownString(l10n.t("Showed diff of {0}", display)); - } else if (args.view_range && args.view_range.length === 2) { + } else if (args.view_range && args.view_range.length >= 2) { const [start, end] = args.view_range; invocation.invocationMessage = new MarkdownString(l10n.t("Showing {0}, lines {1} to {2}", display, start, end)); invocation.pastTenseMessage = new MarkdownString(l10n.t("Showed {0}, lines {1} to {2}", display, start, end)); + } else if (args.view_range && args.view_range.length === 1) { + const [line] = args.view_range; + invocation.invocationMessage = new MarkdownString(l10n.t("Showing {0}, line {1}", display, line)); + invocation.pastTenseMessage = new MarkdownString(l10n.t("Showed {0}, line {1}", display, line)); } else { invocation.invocationMessage = new MarkdownString(l10n.t("Showing {0}", display)); invocation.pastTenseMessage = new MarkdownString(l10n.t("Showed {0}", display)); @@ -1151,7 +1135,14 @@ function formatTaskCompleteInvocation(invocation: ChatToolInvocationPart, toolCa } function formatAskUserInvocation(invocation: ChatToolInvocationPart, toolCall: AskUserTool): void { - invocation.invocationMessage = toolCall.arguments.question || l10n.t('Asking user a question'); + if ('question' in toolCall.arguments) { + invocation.invocationMessage = toolCall.arguments.question || l10n.t('Asking user a question'); + invocation.pastTenseMessage = toolCall.arguments.question || l10n.t('Asked user a question'); + return; + } + + invocation.invocationMessage = toolCall.arguments.message || l10n.t('Asking user for input'); + invocation.pastTenseMessage = toolCall.arguments.message || l10n.t('Asked user for input'); } function formatSkillInvocation(invocation: ChatToolInvocationPart, toolCall: SkillTool): void { @@ -1190,6 +1181,14 @@ function formatLspInvocation(invocation: ChatToolInvocationPart, toolCall: LspTo } } +function formatCreatePullRequestInvocation(invocation: ChatToolInvocationPart, toolCall: CreatePullRequestTool): void { + invocation.invocationMessage = toolCall.arguments.title || l10n.t('Creating pull request'); + invocation.pastTenseMessage = toolCall.arguments.title || l10n.t('Created pull request'); + if (toolCall.arguments.description) { + invocation.originMessage = toolCall.arguments.description; + } +} + function formatStoreMemoryInvocation(invocation: ChatToolInvocationPart, toolCall: StoreMemoryTool): void { invocation.invocationMessage = l10n.t("Storing memory: {0}", toolCall.arguments.subject); invocation.pastTenseMessage = l10n.t("Stored memory: {0}", toolCall.arguments.subject); diff --git a/extensions/copilot/src/extension/chatSessions/copilotcli/common/test/copilotCLITools.spec.ts b/extensions/copilot/src/extension/chatSessions/copilotcli/common/test/copilotCLITools.spec.ts index 65126e430d4..3808049f357 100644 --- a/extensions/copilot/src/extension/chatSessions/copilotcli/common/test/copilotCLITools.spec.ts +++ b/extensions/copilot/src/extension/chatSessions/copilotcli/common/test/copilotCLITools.spec.ts @@ -244,17 +244,17 @@ describe('CopilotCLITools', () => { const msg = getInvocationMessageText(part as ChatToolInvocationPart); expect(msg).toMatch(/Creat/); }); - it('formats show_file invocation with path', () => { + it.skip('formats show_file invocation with path', () => { const part = createCopilotCLIToolInvocation({ toolName: 'show_file', toolCallId: 'sf1', arguments: { path: '/tmp/file.ts' } }); expect(part).toBeInstanceOf(ChatToolInvocationPart); expect(getInvocationMessageText(part as ChatToolInvocationPart)).toMatch(/Showing.*file\.ts/); }); - it('formats show_file invocation with diff mode', () => { + it.skip('formats show_file invocation with diff mode', () => { const part = createCopilotCLIToolInvocation({ toolName: 'show_file', toolCallId: 'sf2', arguments: { path: '/tmp/file.ts', diff: true } }); expect(part).toBeInstanceOf(ChatToolInvocationPart); expect(getInvocationMessageText(part as ChatToolInvocationPart)).toMatch(/diff/i); }); - it('formats show_file invocation with view_range', () => { + it.skip('formats show_file invocation with view_range', () => { const part = createCopilotCLIToolInvocation({ toolName: 'show_file', toolCallId: 'sf3', arguments: { path: '/tmp/file.ts', view_range: [10, 20] } }); expect(part).toBeInstanceOf(ChatToolInvocationPart); const msg = getInvocationMessageText(part as ChatToolInvocationPart); @@ -281,6 +281,23 @@ describe('CopilotCLITools', () => { expect(part).toBeInstanceOf(ChatToolInvocationPart); expect(getInvocationMessageText(part as ChatToolInvocationPart)).toContain('Which DB?'); }); + it('formats ask_user invocation with structured message', () => { + const part = createCopilotCLIToolInvocation({ + toolName: 'ask_user', + toolCallId: 'au2', + arguments: { + message: 'Pick a deployment target', + requestedSchema: { + properties: { + target: { type: 'string', enum: ['staging', 'prod'] } + }, + required: ['target'] + } + } + }); + expect(part).toBeInstanceOf(ChatToolInvocationPart); + expect(getInvocationMessageText(part as ChatToolInvocationPart)).toContain('Pick a deployment target'); + }); it('formats skill invocation', () => { const part = createCopilotCLIToolInvocation({ toolName: 'skill', toolCallId: 'sk1', arguments: { skill: 'pdf' } }); expect(part).toBeInstanceOf(ChatToolInvocationPart); @@ -318,8 +335,19 @@ describe('CopilotCLITools', () => { expect(part).toBeInstanceOf(ChatToolInvocationPart); expect(getInvocationMessageText(part as ChatToolInvocationPart)).toContain('workspaceSymbol'); }); + it('formats create_pull_request invocation', () => { + const part = createCopilotCLIToolInvocation({ toolName: 'create_pull_request', toolCallId: 'pr1', arguments: { title: 'Fix auth flow', description: 'Summary of changes', draft: false } }); + expect(part).toBeInstanceOf(ChatToolInvocationPart); + expect(getInvocationMessageText(part as ChatToolInvocationPart)).toContain('Fix auth flow'); + expect((part as ChatToolInvocationPart).originMessage).toContain('Summary of changes'); + }); + it('formats search_code_subagent invocation', () => { + const part = createCopilotCLIToolInvocation({ toolName: 'search_code_subagent', toolCallId: 'sc1', arguments: { query: 'find auth middleware' } }); + expect(part).toBeInstanceOf(ChatToolInvocationPart); + expect(getInvocationMessageText(part as ChatToolInvocationPart)).toContain('find auth middleware'); + }); it('formats store_memory invocation', () => { - const part = createCopilotCLIToolInvocation({ toolName: 'store_memory', toolCallId: 'sm1', arguments: { subject: 'naming', fact: 'Use camelCase', citations: 'src/foo.ts:1', reason: 'consistency' } }); + const part = createCopilotCLIToolInvocation({ toolName: 'store_memory', toolCallId: 'sm1', arguments: { subject: 'naming', fact: 'Use camelCase', citations: 'src/foo.ts:1', reason: 'consistency', category: 'general' } }); expect(part).toBeInstanceOf(ChatToolInvocationPart); expect(getInvocationMessageText(part as ChatToolInvocationPart)).toContain('naming'); }); @@ -340,7 +368,7 @@ describe('CopilotCLITools', () => { expect(part).toBeInstanceOf(ChatToolInvocationPart); }); it('creates invocation for gh-advisory-database', () => { - const part = createCopilotCLIToolInvocation({ toolName: 'gh-advisory-database', toolCallId: 'gh1', arguments: { dependencies: [{ name: 'lodash', version: '4.17.0' }] } }); + const part = createCopilotCLIToolInvocation({ toolName: 'gh-advisory-database', toolCallId: 'gh1', arguments: { dependencies: [{ name: 'lodash', version: '4.17.0', ecosystem: 'npm' }] } }); expect(part).toBeInstanceOf(ChatToolInvocationPart); }); it('creates invocation for parallel_validation', () => { diff --git a/extensions/copilot/test/e2e/cli.stest.ts b/extensions/copilot/test/e2e/cli.stest.ts index 2033d2f63b8..3622d95ac09 100644 --- a/extensions/copilot/test/e2e/cli.stest.ts +++ b/extensions/copilot/test/e2e/cli.stest.ts @@ -17,6 +17,7 @@ import { ChatDelegationSummaryService, IChatDelegationSummaryService } from '../ import { emptyWorkspaceInfo, IWorkspaceInfo } from '../../src/extension/chatSessions/common/workspaceInfo'; import { CopilotCLIAgents, CopilotCLIModels, CopilotCLISDK, CopilotCLISessionOptions, ICopilotCLIAgents, ICopilotCLIModels, ICopilotCLISDK } from '../../src/extension/chatSessions/copilotcli/node/copilotCli'; import { CopilotCLIImageSupport, ICopilotCLIImageSupport } from '../../src/extension/chatSessions/copilotcli/node/copilotCLIImageSupport'; +import { CopilotCLISkills, ICopilotCLISkills } from '../../src/extension/chatSessions/copilotcli/node/copilotCLISkills'; import { CopilotCLIPromptResolver } from '../../src/extension/chatSessions/copilotcli/node/copilotcliPromptResolver'; import { ICopilotCLISession } from '../../src/extension/chatSessions/copilotcli/node/copilotcliSession'; import { CopilotCLISessionService, ICopilotCLISessionService } from '../../src/extension/chatSessions/copilotcli/node/copilotcliSessionService'; @@ -231,6 +232,7 @@ async function registerChatServices(testingServiceCollection: TestingServiceColl let instaService = accessor.get(IInstantiationService); const summarizer = instaService.createInstance(ChatSummarizerProvider); const delegatingSummarizerProvider = instaService.createInstance(ChatDelegationSummaryService, summarizer); + testingServiceCollection.define(ICopilotCLISkills, new SyncDescriptor(CopilotCLISkills)); testingServiceCollection.define(ICopilotCLISessionService, new SyncDescriptor(TestCopilotCLISessionService)); testingServiceCollection.define(ITestSessionOptionsProvider, new SyncDescriptor(TestSessionOptionsProvider)); testingServiceCollection.define(ILanguageModelServer, new SyncDescriptor(TestLanguageModelServer));