agents
Version:
A home for your AI agents
162 lines (161 loc) • 6.25 kB
JavaScript
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