Path: blob/main/src/vs/workbench/contrib/browserView/electron-browser/tools/runPlaywrightCodeTool.ts
13405 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import type { CancellationToken } from '../../../../../base/common/cancellation.js';6import { Codicon } from '../../../../../base/common/codicons.js';7import { MarkdownString } from '../../../../../base/common/htmlContent.js';8import { localize } from '../../../../../nls.js';9import { IPlaywrightService } from '../../../../../platform/browserView/common/playwrightService.js';10import { ToolDataSource, type CountTokensCallback, type IPreparedToolInvocation, type IToolData, type IToolImpl, type IToolInvocation, type IToolInvocationPreparationContext, type IToolResult, type ToolProgress } from '../../../chat/common/tools/languageModelToolsService.js';11import { errorResult, invokeFunctionResultToToolResult } from './browserToolHelpers.js';12import { BrowserChatToolReferenceName } from '../../common/browserChatToolReferenceNames.js';13import { OpenPageToolId } from './openBrowserTool.js';1415export const RunPlaywrightCodeToolData: IToolData = {16id: 'run_playwright_code',17toolReferenceName: BrowserChatToolReferenceName.RunPlaywrightCode,18displayName: localize('runPlaywrightCodeTool.displayName', 'Run Playwright Code'),19userDescription: localize('runPlaywrightCodeTool.userDescription', 'Run a Playwright code snippet against a browser page'),20modelDescription: `Run a Playwright code snippet to control a browser page. Only use this if other browser tools are insufficient.`,21icon: Codicon.terminal,22source: ToolDataSource.Internal,23inputSchema: {24type: 'object',25properties: {26pageId: {27type: 'string',28description: `The browser page ID, acquired from context or the open tool.`29},30code: {31type: 'string',32description: `The Playwright code to execute. The code must be concise, serve one clear purpose, and be self-contained. You **must not** directly access \`document\` or \`window\` using this tool. You must access it via the provided \`page\` object, e.g. "return page.evaluate(() => document.title)". Omit this when resuming a deferred execution via deferredResultId.`33},34deferredResultId: {35type: 'string',36description: `If a previous call returned a deferredResultId, pass it here to continue waiting for that execution to complete.`37},38timeoutMs: {39type: 'number',40description: `Maximum time in milliseconds to wait for the code to complete. Defaults to 5000 (5 seconds).`41},42},43required: ['pageId'],44$comment: 'Either "code" or "deferredResultId" must be provided.',45},46};4748interface IRunPlaywrightCodeToolParams {49pageId: string;50code?: string;51deferredResultId?: string;52timeoutMs?: number;53}5455export class RunPlaywrightCodeTool implements IToolImpl {56constructor(57@IPlaywrightService private readonly playwrightService: IPlaywrightService,58) { }5960async prepareToolInvocation(context: IToolInvocationPreparationContext, _token: CancellationToken): Promise<IPreparedToolInvocation | undefined> {61const params = context.parameters as IRunPlaywrightCodeToolParams;6263if (params.deferredResultId) {64return {65invocationMessage: new MarkdownString(localize('browser.runCode.waitInvocation', "Waiting for Playwright code to complete...")),66pastTenseMessage: new MarkdownString(localize('browser.runCode.waitPast', "Waited for Playwright code")),67};68}6970const code = params.code ?? '';71return {72invocationMessage: new MarkdownString(localize('browser.runCode.invocation', "Running Playwright code...")),73pastTenseMessage: new MarkdownString(localize('browser.runCode.past', "Ran Playwright code")),74confirmationMessages: {75title: localize('browser.runCode.confirmTitle', 'Run Playwright Code?'),76message: new MarkdownString(`\`\`\`javascript\n${code.trim()}\n\`\`\``),77disclaimer: localize('browser.runCode.confirmDisclaimer', 'Make sure you trust the code before continuing.'),78allowAutoConfirm: true,79}80};81}8283async invoke(invocation: IToolInvocation, _countTokens: CountTokensCallback, _progress: ToolProgress, _token: CancellationToken): Promise<IToolResult> {84const params = invocation.parameters as IRunPlaywrightCodeToolParams;8586if (!params.pageId) {87return errorResult(`No page ID provided. Use '${OpenPageToolId}' first.`);88}8990// Resume waiting for a deferred execution91if (params.deferredResultId) {92try {93const result = await this.playwrightService.waitForDeferredResult(params.deferredResultId, params.timeoutMs ?? 5_000);94return invokeFunctionResultToToolResult(result);95} catch (e) {96return errorResult(e instanceof Error ? e.message : String(e));97}98}99100if (!params.code) {101return errorResult('Either "code" or "deferredResultId" must be provided.');102}103104let result;105try {106result = await this.playwrightService.invokeFunction(params.pageId, `async (page) => { ${params.code} }`, undefined, params.timeoutMs ?? 5_000);107} catch (e) {108const message = e instanceof Error ? e.message : String(e);109return errorResult(`Code execution failed: ${message}`);110}111112return invokeFunctionResultToToolResult(result, params.code.trim());113}114}115116117