UNPKG

agents

Version:

A home for your AI agents

162 lines (161 loc) • 6.25 kB
import { a as conversationScope } from "../../session-CRY0bGiI.js"; import { n as browserScreenshotOutput, t as browserExecuteModelOutput } from "../../tool-helpers-F3_Wy2YX.js"; import { t as createBrowserToolCore } from "../../browser-tool-DOqT1oSD.js"; import { obj, oneOf, opt, str, unknown } from "@earendil-works/pi-schema"; //#region src/browser/tools/pi.ts const browserToolParameters = obj({ code: str({ description: "An async arrow function that drives the browser with `browser`" }) }, { open: true }); /** * What a script gets from `tools.browser()` in pi's code mode: the run as an * object, with a screenshot as an image block it can pass to `image()`. */ const browserToolOutputSchema = obj({ status: oneOf(["completed", "error"]), executionId: str(), result: opt(unknown({ description: "What the code returned. A screenshot is an image block: pass it to `image()`." })), error: opt(str({ description: "Why the code failed." })), logs: opt(unknown({ description: "What the code wrote to the console." })), notice: opt(str({ description: "What happened to the browser and its tabs." })) }, { open: true }); function details(output) { return { executionId: output.executionId, status: output.status }; } function text(value) { return { type: "text", text: typeof value === "string" ? value : JSON.stringify(value) }; } /** * The model projection of the run (no `calls` log, bounded `logs`) as an * object, for scripts. */ function structuredOf(output, executionId) { const model = browserExecuteModelOutput(output); return model.type === "json" && typeof model.value === "object" && model.value !== null && !Array.isArray(model.value) ? model.value : { status: "error", executionId, error: typeof model.value === "string" ? model.value : "No result." }; } /** * The tool result pi stores and sends: the model projection of the run as * JSON (no `calls` log, bounded `logs`), and a screenshot as an image part * after it. pi-ai only sends the image to models that accept images. Scripts * get the same projection as an object, with the screenshot in `result`. */ function browserToolResult(output) { const isError = output.status === "error"; const screenshot = browserScreenshotOutput(output); if (!screenshot) return { output: [text(browserExecuteModelOutput(output).value)], structuredOutput: structuredOf(output, output.executionId), details: details(output), ...isError ? { isError } : {} }; const bytes = Math.floor(screenshot.data.length * 3 / 4).toLocaleString(); const summary = browserExecuteModelOutput({ ...output, result: `Screenshot attached as an image (${screenshot.mediaType}, approximately ${bytes} bytes). If you can't see it, read the page with Runtime.evaluate instead.` }).value; const image = { type: "image", data: screenshot.data, mimeType: screenshot.mediaType }; return { output: [text(summary), image], structuredOutput: { ...structuredOf({ ...output, result: void 0 }, output.executionId), result: { type: "image", data: image.data, mimeType: image.mimeType } }, details: details(output) }; } /** The default scope: the calling conversation's. */ function callConversationScope(api) { return conversationScope(String(api.conversationId)); } /** * Create a pi-durable tool that lets the model drive a persistent browser * with JavaScript and the Chrome DevTools Protocol. * * Works like `browserTool` in `agents/browser/ai-sdk`: tabs, cookies, and * logins carry over between runs, `sessionId: "active"` addresses the tab the * model last worked in, and a replaced browser is reported in the * result's `notice`. A returned screenshot comes back as an image part that * the model sees, when its model accepts images. * * Scripts in pi's code mode call it as `tools.browser({ code })`, which * resolves to the run as an object (`status`, `result`, `error`, `notice`), * on errors too. A screenshot's `result` is an image block, for `image()`. * * Each conversation gets its own active tab (see * {@link PiBrowserToolOptions.scope}). A conversation's calls run one at a * time, since they share its tab, and the tool doesn't rerun after an * eviction (its code may have clicked or * submitted something): pi gives the model an interrupted result instead. * * @example * ```ts * import { Browser, browserRun } from "agents/browser"; * import { browserTool } from "agents/browser/pi"; * * export class MyAgent extends DurableObject<Env> { * readonly browser = new Browser({ provider: browserRun(this.env.BROWSER) }); * readonly registry = createRegistry(); * readonly harness = new PiHarness({ * harness: ({ storage, context, onWake }) => { * this.registry.install({ * name: "browser", * tools: [ * browserTool({ * browser: this.browser, * loader: this.env.LOADER * }) * ] * }); * return Harness.open( * storage, * { models, registry: this.registry, onWake }, * context * ); * } * }); * readonly lifecycle = Lifecycle.install(this) * .use(this.browser) * .use(this.harness); * } * ``` */ function browserTool(options) { const { scope = callConversationScope, ...coreOptions } = options; const core = createBrowserToolCore(coreOptions, { screenshotHint: "To see a screenshot, return { type: 'browser_screenshot', mediaType, data } with data from Page.captureScreenshot and mediaType 'image/png', or 'image/jpeg' if you captured with format: 'jpeg'. The image is attached to the result. In a codemode script, `tools.browser()` resolves to the result as an object, and the screenshot in its `result` is an image block: pass it to `image()`." }); return { name: options.name ?? "browser", description: core.description, parameters: browserToolParameters, structuredOutputSchema: browserToolOutputSchema, executionMode: "sequential", replay: "unsafe", async execute(args, api, context) { const callScope = typeof scope === "string" ? scope : scope(api); return browserToolResult(await core.execute({ code: args.code }, { scope: callScope, signal: context.abortSignal })); } }; } //#endregion export { browserTool }; //# sourceMappingURL=pi.js.map