UNPKG

playwright-core

Version:

A high-level API to automate web browsers

205 lines (201 loc) • 9.12 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var pageAgent_exports = {}; __export(pageAgent_exports, { pageAgentExpect: () => pageAgentExpect, pageAgentExtract: () => pageAgentExtract, pageAgentPerform: () => pageAgentPerform }); module.exports = __toCommonJS(pageAgent_exports); var import_fs = __toESM(require("fs")); var import_path = __toESM(require("path")); var import_tool = require("./tool"); var import_utilsBundle = require("../../utilsBundle"); var import_mcpBundle = require("../../mcpBundle"); var import_actionRunner = require("./actionRunner"); var import_performTools = __toESM(require("./performTools")); var import_expectTools = __toESM(require("./expectTools")); var actions = __toESM(require("./actions")); async function pageAgentPerform(progress, context, userTask, callParams) { const cacheKey = (callParams.cacheKey ?? userTask).trim(); if (await cachedPerform(progress, context, cacheKey)) return; const task = ` ### Instructions - Perform the following task on the page. - Your reply should be a tool call that performs action the page". ### Task ${userTask} `; progress.disableTimeout(); await runLoop(progress, context, import_performTools.default, task, void 0, callParams); await updateCache(context, cacheKey); } async function pageAgentExpect(progress, context, expectation, callParams) { const cacheKey = (callParams.cacheKey ?? expectation).trim(); if (await cachedPerform(progress, context, cacheKey)) return; const task = ` ### Instructions - Call one of the "browser_expect_*" tools to verify / assert the condition. - You can call exactly one tool and it can't be report_results, must be one of the assertion tools. ### Expectation ${expectation} `; progress.disableTimeout(); await runLoop(progress, context, import_expectTools.default, task, void 0, callParams); await updateCache(context, cacheKey); } async function pageAgentExtract(progress, context, query, schema, callParams) { const task = ` ### Instructions Extract the following information from the page. Do not perform any actions, just extract the information. ### Query ${query}`; const { result } = await runLoop(progress, context, [], task, schema, callParams); return result; } async function runLoop(progress, context, toolDefinitions, userTask, resultSchema, params) { if (!context.agentParams.api || !context.agentParams.model) throw new Error(`This action requires the API and API key to be set on the page agent. Did you mean to --run-agents=missing?`); if (!context.agentParams.apiKey) throw new Error(`This action requires API key to be set on the page agent.`); if (context.agentParams.apiEndpoint && !URL.canParse(context.agentParams.apiEndpoint)) throw new Error(`Agent API endpoint "${context.agentParams.apiEndpoint}" is not a valid URL.`); const snapshot = await context.takeSnapshot(progress); const { tools, callTool, reportedResult, refusedToPerformReason } = (0, import_tool.toolsForLoop)(progress, context, toolDefinitions, { resultSchema, refuseToPerform: "allow" }); const secrets = Object.fromEntries((context.agentParams.secrets || [])?.map((s) => [s.name, s.value])); const apiCacheTextBefore = context.agentParams.apiCacheFile ? await import_fs.default.promises.readFile(context.agentParams.apiCacheFile, "utf-8").catch(() => "{}") : "{}"; const apiCacheBefore = JSON.parse(apiCacheTextBefore || "{}"); const loop = new import_mcpBundle.Loop({ api: context.agentParams.api, apiEndpoint: context.agentParams.apiEndpoint, apiKey: context.agentParams.apiKey, apiTimeout: context.agentParams.apiTimeout ?? 0, model: context.agentParams.model, maxTokens: params.maxTokens ?? context.maxTokensRemaining(), maxToolCalls: params.maxActions ?? context.agentParams.maxActions ?? 10, maxToolCallRetries: params.maxActionRetries ?? context.agentParams.maxActionRetries ?? 3, summarize: true, debug: import_utilsBundle.debug, callTool, tools, secrets, cache: apiCacheBefore, ...context.events }); const task = []; if (context.agentParams.systemPrompt) { task.push("### System"); task.push(context.agentParams.systemPrompt); task.push(""); } task.push("### Task"); task.push(userTask); if (context.history().length) { task.push("### Context history"); task.push(context.history().map((h) => `- ${h.type}: ${h.description}`).join("\n")); task.push(""); } task.push("### Page snapshot"); task.push(snapshot); task.push(""); const { error, usage } = await loop.run(task.join("\n"), { signal: progress.signal }); context.consumeTokens(usage.input + usage.output); if (context.agentParams.apiCacheFile) { const apiCacheAfter = { ...apiCacheBefore, ...loop.cache() }; const sortedCache = Object.fromEntries(Object.entries(apiCacheAfter).sort(([a], [b]) => a.localeCompare(b))); const apiCacheTextAfter = JSON.stringify(sortedCache, void 0, 2); if (apiCacheTextAfter !== apiCacheTextBefore) { await import_fs.default.promises.mkdir(import_path.default.dirname(context.agentParams.apiCacheFile), { recursive: true }); await import_fs.default.promises.writeFile(context.agentParams.apiCacheFile, apiCacheTextAfter); } } if (refusedToPerformReason()) throw new Error(`Agent refused to perform action: ${refusedToPerformReason()}`); if (error) throw new Error(`Agentic loop failed: ${error}`); return { result: reportedResult ? reportedResult() : void 0 }; } async function cachedPerform(progress, context, cacheKey) { if (!context.agentParams?.cacheFile) return; const cache = await cachedActions(context.agentParams?.cacheFile); const entry = cache.actions[cacheKey]; if (!entry) return; for (const action of entry.actions) await (0, import_actionRunner.runAction)(progress, "run", context.page, action, context.agentParams.secrets ?? []); return entry.actions; } async function updateCache(context, cacheKey) { const cacheFile = context.agentParams?.cacheFile; const cacheOutFile = context.agentParams?.cacheOutFile; const cacheFileKey = cacheFile ?? cacheOutFile; const cache = cacheFileKey ? await cachedActions(cacheFileKey) : { actions: {}, newActions: {} }; const newEntry = { actions: context.actions() }; cache.actions[cacheKey] = newEntry; cache.newActions[cacheKey] = newEntry; if (cacheOutFile) { const entries = Object.entries(cache.newActions); entries.sort((e1, e2) => e1[0].localeCompare(e2[0])); await import_fs.default.promises.writeFile(cacheOutFile, JSON.stringify(Object.fromEntries(entries), void 0, 2)); } else if (cacheFile) { const entries = Object.entries(cache.actions); entries.sort((e1, e2) => e1[0].localeCompare(e2[0])); await import_fs.default.promises.writeFile(cacheFile, JSON.stringify(Object.fromEntries(entries), void 0, 2)); } } const allCaches = /* @__PURE__ */ new Map(); async function cachedActions(cacheFile) { let cache = allCaches.get(cacheFile); if (!cache) { const content = await import_fs.default.promises.readFile(cacheFile, "utf-8").catch(() => ""); let json; try { json = JSON.parse(content.trim() || "{}"); } catch (error) { throw new Error(`Failed to parse cache file ${cacheFile}: ${error.message}`); } const parsed = actions.cachedActionsSchema.safeParse(json); if (parsed.error) throw new Error(`Failed to parse cache file ${cacheFile}: ${import_mcpBundle.z.prettifyError(parsed.error)}`); cache = { actions: parsed.data, newActions: {} }; allCaches.set(cacheFile, cache); } return cache; } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { pageAgentExpect, pageAgentExtract, pageAgentPerform });