diff --git a/src/vs/platform/agentHost/node/copilot/copilotToolDisplay.ts b/src/vs/platform/agentHost/node/copilot/copilotToolDisplay.ts index e8ca42915ceb7..8d4eb71292ae9 100644 --- a/src/vs/platform/agentHost/node/copilot/copilotToolDisplay.ts +++ b/src/vs/platform/agentHost/node/copilot/copilotToolDisplay.ts @@ -6,7 +6,7 @@ import type { PermissionRequest } from '@github/copilot-sdk'; import { hasKey } from '../../../../base/common/types.js'; import { URI } from '../../../../base/common/uri.js'; -import { appendEscapedMarkdownInlineCode, escapeMarkdownLinkLabel } from '../../../../base/common/htmlContent.js'; +import { appendEscapedMarkdownInlineCode, escapeMarkdownLinkLabel, MarkdownString } from '../../../../base/common/htmlContent.js'; import { hash } from '../../../../base/common/hash.js'; import { localize } from '../../../../nls.js'; import type { IAgentToolPendingConfirmationSignal } from '../../common/agentService.js'; @@ -187,6 +187,11 @@ interface ICopilotSqlToolArgs { query?: string; } +/** Parameters for the `web_fetch` tool. */ +interface ICopilotWebFetchToolArgs { + url: string; +} + /** * Parameters for the `apply_patch` / `git_apply_patch` tools. The patch text * itself lives in `input` using the V4A diff format (file headers like @@ -403,6 +408,10 @@ function formatPathAsMarkdownLink(path: string): string { return `[${basename(uri)}](${uri})`; } +function formatUrlAsMarkdownLink(url: string): string { + return new MarkdownString().appendLink(url, truncate(url, 80)).value; +} + /** * Wraps a localized message containing a markdown file link into a * `StringOrMarkdown` object so the renderer treats it as markdown. @@ -562,6 +571,13 @@ export function getInvocationMessage(toolName: string, displayName: string, para const args = parameters as ICopilotSqlToolArgs | undefined; return args?.description || localize('toolInvoke.sql', "Executing SQL query"); } + case CopilotToolName.WebFetch: { + const args = parameters as ICopilotWebFetchToolArgs | undefined; + if (args?.url) { + return md(localize('toolInvoke.webFetch', "Fetching {0}", formatUrlAsMarkdownLink(args.url))); + } + return localize('toolInvoke.webFetchGeneric', "Fetching URL"); + } case CopilotToolName.ExitPlanMode: return localize('toolInvoke.exitPlanMode', "Presenting plan"); default: @@ -665,6 +681,13 @@ export function getPastTenseMessage(toolName: string, displayName: string, param const args = parameters as ICopilotSqlToolArgs | undefined; return args?.description || localize('toolComplete.sql', "Executed SQL query"); } + case CopilotToolName.WebFetch: { + const args = parameters as ICopilotWebFetchToolArgs | undefined; + if (args?.url) { + return md(localize('toolComplete.webFetch', "Fetched {0}", formatUrlAsMarkdownLink(args.url))); + } + return localize('toolComplete.webFetchGeneric', "Fetched URL"); + } case CopilotToolName.ExitPlanMode: return localize('toolComplete.exitPlanMode', "Exited plan mode"); default: @@ -786,6 +809,10 @@ export function getToolInputString(toolName: string, parameters: Record { }); }); +suite('web_fetch tool display', () => { + + ensureNoDisposablesAreLeakedInTestSuite(); + + function text(msg: ReturnType | ReturnType): string { + return typeof msg === 'string' ? msg : msg.markdown; + } + + test('uses the fetched URL for invocation and completion messages', () => { + const parameters = { url: 'https://example.com/docs' }; + assert.deepStrictEqual({ + invocation: text(getInvocationMessage('web_fetch', 'Fetch Web Content', parameters)), + pastTense: text(getPastTenseMessage('web_fetch', 'Fetch Web Content', parameters, true)), + input: getToolInputString('web_fetch', parameters, undefined), + }, { + invocation: 'Fetching [https://example.com/docs](https://example.com/docs)', + pastTense: 'Fetched [https://example.com/docs](https://example.com/docs)', + input: 'https://example.com/docs', + }); + }); + + test('falls back to generic URL wording when the URL is absent', () => { + assert.deepStrictEqual({ + invocation: text(getInvocationMessage('web_fetch', 'Fetch Web Content', undefined)), + pastTense: text(getPastTenseMessage('web_fetch', 'Fetch Web Content', undefined, true)), + failure: text(getPastTenseMessage('web_fetch', 'Fetch Web Content', { url: 'https://example.com/docs' }, false)), + }, { + invocation: 'Fetching URL', + pastTense: 'Fetched URL', + failure: '"Fetch Web Content" failed', + }); + }); +}); + suite('sql tool display', () => { ensureNoDisposablesAreLeakedInTestSuite();