genkitx-playwright 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js ADDED
@@ -0,0 +1,803 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ ALL_TOOL_NAMES: () => ALL_TOOL_NAMES,
24
+ BrowserSession: () => BrowserSession,
25
+ PlaywrightOptionsSchema: () => PlaywrightOptionsSchema,
26
+ createPlaywrightTools: () => createPlaywrightTools,
27
+ playwright: () => playwright
28
+ });
29
+ module.exports = __toCommonJS(index_exports);
30
+
31
+ // src/playwright.ts
32
+ var import_genkit2 = require("genkit");
33
+
34
+ // src/browser.ts
35
+ var import_playwright = require("playwright");
36
+ var browserLaunchers = { chromium: import_playwright.chromium, firefox: import_playwright.firefox, webkit: import_playwright.webkit };
37
+ var BrowserSession = class {
38
+ constructor(options = {}) {
39
+ this.options = options;
40
+ }
41
+ options;
42
+ browser;
43
+ context;
44
+ launchPromise;
45
+ /** Captured console messages for the active page. */
46
+ consoleLogs = [];
47
+ /** The most recent dialog the page raised, awaiting handling. */
48
+ pendingDialog;
49
+ /** Ensures the browser + context are launched (idempotent). */
50
+ async ensureLaunched() {
51
+ if (this.context) return;
52
+ if (!this.launchPromise) {
53
+ this.launchPromise = (async () => {
54
+ const type = this.options.browser ?? "chromium";
55
+ const launcher = browserLaunchers[type];
56
+ if (!launcher) {
57
+ throw new Error(`Unsupported browser type: ${type}`);
58
+ }
59
+ this.browser = await launcher.launch({
60
+ headless: this.options.headless ?? true,
61
+ ...this.options.launchOptions
62
+ });
63
+ this.context = await this.browser.newContext(
64
+ this.options.contextOptions
65
+ );
66
+ })();
67
+ }
68
+ await this.launchPromise;
69
+ }
70
+ /** Returns the active page, creating one (and the browser) if needed. */
71
+ async getPage() {
72
+ await this.ensureLaunched();
73
+ const pages = this.context.pages();
74
+ let page = pages[pages.length - 1];
75
+ if (!page) {
76
+ page = await this.newPage();
77
+ }
78
+ return page;
79
+ }
80
+ /** Creates and wires up a new page (tab). */
81
+ async newPage() {
82
+ await this.ensureLaunched();
83
+ const page = await this.context.newPage();
84
+ this.attachListeners(page);
85
+ return page;
86
+ }
87
+ /** Returns all open pages (tabs). */
88
+ async getPages() {
89
+ await this.ensureLaunched();
90
+ return this.context.pages();
91
+ }
92
+ attachListeners(page) {
93
+ page.on("console", (msg) => {
94
+ this.consoleLogs.push({ type: msg.type(), text: msg.text() });
95
+ });
96
+ page.on("dialog", (dialog) => {
97
+ this.pendingDialog = {
98
+ type: dialog.type(),
99
+ message: dialog.message(),
100
+ defaultValue: dialog.defaultValue()
101
+ };
102
+ this.activeDialog = dialog;
103
+ });
104
+ }
105
+ /** The raw Playwright dialog object awaiting a response, if any. */
106
+ activeDialog;
107
+ /** Closes the browser and releases all resources. Safe to call repeatedly. */
108
+ async close() {
109
+ try {
110
+ await this.context?.close();
111
+ } catch {
112
+ }
113
+ try {
114
+ await this.browser?.close();
115
+ } catch {
116
+ }
117
+ this.context = void 0;
118
+ this.browser = void 0;
119
+ this.launchPromise = void 0;
120
+ }
121
+ };
122
+
123
+ // src/tools.ts
124
+ var import_genkit = require("genkit");
125
+ var import_beta = require("genkit/beta");
126
+ var ALL_TOOL_NAMES = [
127
+ // navigation
128
+ "browser_navigate",
129
+ "browser_navigate_back",
130
+ "browser_navigate_forward",
131
+ // observation
132
+ "browser_snapshot",
133
+ "browser_screenshot",
134
+ "browser_get_console_logs",
135
+ "browser_get_network_requests",
136
+ // interaction
137
+ "browser_click",
138
+ "browser_type",
139
+ "browser_fill",
140
+ "browser_press_key",
141
+ "browser_hover",
142
+ "browser_select_option",
143
+ "browser_scroll",
144
+ "browser_drag",
145
+ "browser_file_upload",
146
+ // synchronization
147
+ "browser_wait_for",
148
+ // tabs
149
+ "browser_tab_list",
150
+ "browser_tab_new",
151
+ "browser_tab_select",
152
+ "browser_tab_close",
153
+ // dialogs
154
+ "browser_handle_dialog",
155
+ // escape hatch
156
+ "browser_evaluate"
157
+ ];
158
+ var WRITE_TOOLS = [
159
+ "browser_click",
160
+ "browser_type",
161
+ "browser_fill",
162
+ "browser_press_key",
163
+ "browser_hover",
164
+ "browser_select_option",
165
+ "browser_scroll",
166
+ "browser_drag",
167
+ "browser_file_upload",
168
+ "browser_handle_dialog"
169
+ ];
170
+ function hostnameAllowed(url, allowed, blocked) {
171
+ let host;
172
+ try {
173
+ host = new URL(url).hostname;
174
+ } catch {
175
+ return true;
176
+ }
177
+ const matches = (domain) => host === domain || host.endsWith(`.${domain}`);
178
+ if (blocked?.some(matches)) return false;
179
+ if (allowed && allowed.length > 0) return allowed.some(matches);
180
+ return true;
181
+ }
182
+ function locatorForRef(page, ref) {
183
+ return page.locator(`aria-ref=${ref}`);
184
+ }
185
+ async function snapshotForAI(page) {
186
+ try {
187
+ return await page.ariaSnapshot({ mode: "ai" });
188
+ } catch {
189
+ return page.locator("body").ariaSnapshot();
190
+ }
191
+ }
192
+ function createPlaywrightTools(opts) {
193
+ const { session } = opts;
194
+ const prefix = opts.toolNamePrefix ?? "";
195
+ const name = (n) => `${prefix}${n}`;
196
+ const enabled = new Set(opts.tools ?? ALL_TOOL_NAMES);
197
+ if (opts.readOnly) {
198
+ for (const t of WRITE_TOOLS) enabled.delete(t);
199
+ }
200
+ if (!opts.allowEvaluate) {
201
+ enabled.delete("browser_evaluate");
202
+ }
203
+ const refSchema = import_genkit.z.string().describe('Element ref from the latest browser_snapshot, e.g. "e12".');
204
+ const enqueueParts = (parts, toolName) => {
205
+ const queue = opts.messageQueue;
206
+ if (!queue) return;
207
+ const last = queue[queue.length - 1];
208
+ if (last && last.role === "user") {
209
+ last.content.push(...parts);
210
+ } else {
211
+ queue.push({
212
+ role: "user",
213
+ content: parts,
214
+ metadata: { playwrightMiddlewareTool: toolName }
215
+ });
216
+ }
217
+ };
218
+ const tools = [];
219
+ const add = (toolName, factory) => {
220
+ if (enabled.has(toolName)) tools.push(factory());
221
+ };
222
+ add(
223
+ "browser_navigate",
224
+ () => (0, import_beta.tool)(
225
+ {
226
+ name: name("browser_navigate"),
227
+ description: "Navigate the current page to a URL.",
228
+ inputSchema: import_genkit.z.object({
229
+ url: import_genkit.z.string().describe("Absolute URL to navigate to.")
230
+ }),
231
+ outputSchema: import_genkit.z.string()
232
+ },
233
+ async ({ url }) => {
234
+ if (!hostnameAllowed(url, opts.allowedDomains, opts.blockedDomains)) {
235
+ throw new Error(`Navigation to "${url}" is not allowed by policy.`);
236
+ }
237
+ const page = await session.getPage();
238
+ await page.goto(url, { waitUntil: "domcontentloaded" });
239
+ return `Navigated to ${page.url()} (title: "${await page.title()}").`;
240
+ }
241
+ )
242
+ );
243
+ add(
244
+ "browser_navigate_back",
245
+ () => (0, import_beta.tool)(
246
+ {
247
+ name: name("browser_navigate_back"),
248
+ description: "Go back to the previous page in history.",
249
+ inputSchema: import_genkit.z.object({}),
250
+ outputSchema: import_genkit.z.string()
251
+ },
252
+ async () => {
253
+ const page = await session.getPage();
254
+ await page.goBack({ waitUntil: "domcontentloaded" });
255
+ return `Went back to ${page.url()}.`;
256
+ }
257
+ )
258
+ );
259
+ add(
260
+ "browser_navigate_forward",
261
+ () => (0, import_beta.tool)(
262
+ {
263
+ name: name("browser_navigate_forward"),
264
+ description: "Go forward to the next page in history.",
265
+ inputSchema: import_genkit.z.object({}),
266
+ outputSchema: import_genkit.z.string()
267
+ },
268
+ async () => {
269
+ const page = await session.getPage();
270
+ await page.goForward({ waitUntil: "domcontentloaded" });
271
+ return `Went forward to ${page.url()}.`;
272
+ }
273
+ )
274
+ );
275
+ add(
276
+ "browser_snapshot",
277
+ () => (0, import_beta.tool)(
278
+ {
279
+ name: name("browser_snapshot"),
280
+ description: "Capture an accessibility snapshot of the current page. Each interactive element is tagged with a [ref=eN] id you can pass to interaction tools (click, type, etc.). Prefer this over screenshots for deciding what to interact with.",
281
+ inputSchema: import_genkit.z.object({}),
282
+ outputSchema: import_genkit.z.object({
283
+ url: import_genkit.z.string(),
284
+ title: import_genkit.z.string(),
285
+ snapshot: import_genkit.z.string()
286
+ })
287
+ },
288
+ async () => {
289
+ const page = await session.getPage();
290
+ return {
291
+ url: page.url(),
292
+ title: await page.title(),
293
+ snapshot: await snapshotForAI(page)
294
+ };
295
+ }
296
+ )
297
+ );
298
+ add(
299
+ "browser_screenshot",
300
+ () => (0, import_beta.tool)(
301
+ {
302
+ name: name("browser_screenshot"),
303
+ description: "Take a screenshot of the current page. The image is added to the conversation so you can view it on your next turn.",
304
+ inputSchema: import_genkit.z.object({
305
+ fullPage: import_genkit.z.boolean().optional().describe("Capture the full scrollable page. Defaults to false.")
306
+ }),
307
+ outputSchema: import_genkit.z.string()
308
+ },
309
+ async ({ fullPage }) => {
310
+ const page = await session.getPage();
311
+ const buffer = await page.screenshot({
312
+ fullPage: fullPage ?? false,
313
+ type: "png"
314
+ });
315
+ const dataUri = `data:image/png;base64,${buffer.toString("base64")}`;
316
+ enqueueParts(
317
+ [
318
+ { text: `
319
+
320
+ browser_screenshot result image/png (${page.url()})` },
321
+ { media: { url: dataUri, contentType: "image/png" } }
322
+ ],
323
+ name("browser_screenshot")
324
+ );
325
+ return "Screenshot captured. The image is shown below.";
326
+ }
327
+ )
328
+ );
329
+ add(
330
+ "browser_get_console_logs",
331
+ () => (0, import_beta.tool)(
332
+ {
333
+ name: name("browser_get_console_logs"),
334
+ description: "Return console messages collected from the page.",
335
+ inputSchema: import_genkit.z.object({}),
336
+ outputSchema: import_genkit.z.array(
337
+ import_genkit.z.object({ type: import_genkit.z.string(), text: import_genkit.z.string() })
338
+ )
339
+ },
340
+ async () => session.consoleLogs
341
+ )
342
+ );
343
+ add(
344
+ "browser_get_network_requests",
345
+ () => (0, import_beta.tool)(
346
+ {
347
+ name: name("browser_get_network_requests"),
348
+ description: "List network requests made by the current page (URL, method, and response status when available).",
349
+ inputSchema: import_genkit.z.object({}),
350
+ outputSchema: import_genkit.z.array(
351
+ import_genkit.z.object({
352
+ url: import_genkit.z.string(),
353
+ method: import_genkit.z.string(),
354
+ status: import_genkit.z.number().optional()
355
+ })
356
+ )
357
+ },
358
+ async () => {
359
+ const page = await session.getPage();
360
+ const entries = await page.evaluate(() => {
361
+ const list = performance.getEntriesByType(
362
+ "resource"
363
+ );
364
+ return list.map((e) => ({
365
+ url: e.name,
366
+ method: "GET",
367
+ status: void 0
368
+ }));
369
+ });
370
+ return entries;
371
+ }
372
+ )
373
+ );
374
+ add(
375
+ "browser_click",
376
+ () => (0, import_beta.tool)(
377
+ {
378
+ name: name("browser_click"),
379
+ description: "Click an element identified by its snapshot ref.",
380
+ inputSchema: import_genkit.z.object({
381
+ ref: refSchema,
382
+ doubleClick: import_genkit.z.boolean().optional().describe("Perform a double click instead of a single click.")
383
+ }),
384
+ outputSchema: import_genkit.z.string()
385
+ },
386
+ async ({ ref, doubleClick }) => {
387
+ const page = await session.getPage();
388
+ const locator = locatorForRef(page, ref);
389
+ if (doubleClick) {
390
+ await locator.dblclick();
391
+ } else {
392
+ await locator.click();
393
+ }
394
+ return `Clicked element ${ref}.`;
395
+ }
396
+ )
397
+ );
398
+ add(
399
+ "browser_type",
400
+ () => (0, import_beta.tool)(
401
+ {
402
+ name: name("browser_type"),
403
+ description: "Type text into an element by ref, character by character (does not clear existing content).",
404
+ inputSchema: import_genkit.z.object({
405
+ ref: refSchema,
406
+ text: import_genkit.z.string().describe("Text to type."),
407
+ submit: import_genkit.z.boolean().optional().describe("Press Enter after typing.")
408
+ }),
409
+ outputSchema: import_genkit.z.string()
410
+ },
411
+ async ({ ref, text, submit }) => {
412
+ const page = await session.getPage();
413
+ const locator = locatorForRef(page, ref);
414
+ await locator.pressSequentially(text);
415
+ if (submit) await locator.press("Enter");
416
+ return `Typed into element ${ref}.`;
417
+ }
418
+ )
419
+ );
420
+ add(
421
+ "browser_fill",
422
+ () => (0, import_beta.tool)(
423
+ {
424
+ name: name("browser_fill"),
425
+ description: "Set the value of an input/textarea by ref, clearing it first.",
426
+ inputSchema: import_genkit.z.object({
427
+ ref: refSchema,
428
+ value: import_genkit.z.string().describe("Value to set.")
429
+ }),
430
+ outputSchema: import_genkit.z.string()
431
+ },
432
+ async ({ ref, value }) => {
433
+ const page = await session.getPage();
434
+ await locatorForRef(page, ref).fill(value);
435
+ return `Filled element ${ref}.`;
436
+ }
437
+ )
438
+ );
439
+ add(
440
+ "browser_press_key",
441
+ () => (0, import_beta.tool)(
442
+ {
443
+ name: name("browser_press_key"),
444
+ description: 'Press a keyboard key (e.g. "Enter", "Tab", "ArrowDown", "a"). Optionally targets an element by ref.',
445
+ inputSchema: import_genkit.z.object({
446
+ key: import_genkit.z.string().describe("Key to press."),
447
+ ref: refSchema.optional()
448
+ }),
449
+ outputSchema: import_genkit.z.string()
450
+ },
451
+ async ({ key, ref }) => {
452
+ const page = await session.getPage();
453
+ if (ref) {
454
+ await locatorForRef(page, ref).press(key);
455
+ } else {
456
+ await page.keyboard.press(key);
457
+ }
458
+ return `Pressed key "${key}".`;
459
+ }
460
+ )
461
+ );
462
+ add(
463
+ "browser_hover",
464
+ () => (0, import_beta.tool)(
465
+ {
466
+ name: name("browser_hover"),
467
+ description: "Hover the mouse over an element by ref.",
468
+ inputSchema: import_genkit.z.object({ ref: refSchema }),
469
+ outputSchema: import_genkit.z.string()
470
+ },
471
+ async ({ ref }) => {
472
+ const page = await session.getPage();
473
+ await locatorForRef(page, ref).hover();
474
+ return `Hovered element ${ref}.`;
475
+ }
476
+ )
477
+ );
478
+ add(
479
+ "browser_select_option",
480
+ () => (0, import_beta.tool)(
481
+ {
482
+ name: name("browser_select_option"),
483
+ description: "Select option(s) in a <select> element by ref.",
484
+ inputSchema: import_genkit.z.object({
485
+ ref: refSchema,
486
+ values: import_genkit.z.array(import_genkit.z.string()).describe("Option values or labels to select.")
487
+ }),
488
+ outputSchema: import_genkit.z.string()
489
+ },
490
+ async ({ ref, values }) => {
491
+ const page = await session.getPage();
492
+ await locatorForRef(page, ref).selectOption(values);
493
+ return `Selected ${values.length} option(s) in ${ref}.`;
494
+ }
495
+ )
496
+ );
497
+ add(
498
+ "browser_scroll",
499
+ () => (0, import_beta.tool)(
500
+ {
501
+ name: name("browser_scroll"),
502
+ description: "Scroll the page up or down, or scroll a specific element into view.",
503
+ inputSchema: import_genkit.z.object({
504
+ direction: import_genkit.z.enum(["up", "down"]).optional().describe('Direction to scroll the window. Defaults to "down".'),
505
+ ref: refSchema.optional().describe("If provided, scroll this element into view instead.")
506
+ }),
507
+ outputSchema: import_genkit.z.string()
508
+ },
509
+ async ({ direction, ref }) => {
510
+ const page = await session.getPage();
511
+ if (ref) {
512
+ await locatorForRef(page, ref).scrollIntoViewIfNeeded();
513
+ return `Scrolled element ${ref} into view.`;
514
+ }
515
+ const dir = direction ?? "down";
516
+ await page.evaluate((d) => {
517
+ window.scrollBy(0, d === "down" ? window.innerHeight : -window.innerHeight);
518
+ }, dir);
519
+ return `Scrolled ${dir}.`;
520
+ }
521
+ )
522
+ );
523
+ add(
524
+ "browser_drag",
525
+ () => (0, import_beta.tool)(
526
+ {
527
+ name: name("browser_drag"),
528
+ description: "Drag one element onto another, by their refs.",
529
+ inputSchema: import_genkit.z.object({
530
+ startRef: refSchema.describe("Ref of the element to drag."),
531
+ endRef: refSchema.describe("Ref of the drop target.")
532
+ }),
533
+ outputSchema: import_genkit.z.string()
534
+ },
535
+ async ({ startRef, endRef }) => {
536
+ const page = await session.getPage();
537
+ await locatorForRef(page, startRef).dragTo(locatorForRef(page, endRef));
538
+ return `Dragged ${startRef} onto ${endRef}.`;
539
+ }
540
+ )
541
+ );
542
+ add(
543
+ "browser_file_upload",
544
+ () => (0, import_beta.tool)(
545
+ {
546
+ name: name("browser_file_upload"),
547
+ description: "Set files on a file input element by ref (provide local file paths).",
548
+ inputSchema: import_genkit.z.object({
549
+ ref: refSchema,
550
+ paths: import_genkit.z.array(import_genkit.z.string()).describe("Local file paths to upload.")
551
+ }),
552
+ outputSchema: import_genkit.z.string()
553
+ },
554
+ async ({ ref, paths }) => {
555
+ const page = await session.getPage();
556
+ await locatorForRef(page, ref).setInputFiles(paths);
557
+ return `Uploaded ${paths.length} file(s) to ${ref}.`;
558
+ }
559
+ )
560
+ );
561
+ add(
562
+ "browser_wait_for",
563
+ () => (0, import_beta.tool)(
564
+ {
565
+ name: name("browser_wait_for"),
566
+ description: "Wait for text to appear/disappear on the page, or for a fixed time.",
567
+ inputSchema: import_genkit.z.object({
568
+ text: import_genkit.z.string().optional().describe("Text to wait to appear."),
569
+ textGone: import_genkit.z.string().optional().describe("Text to wait to disappear."),
570
+ time: import_genkit.z.number().optional().describe("Seconds to wait. Capped at 30.")
571
+ }),
572
+ outputSchema: import_genkit.z.string()
573
+ },
574
+ async ({ text, textGone, time }) => {
575
+ const page = await session.getPage();
576
+ if (text) {
577
+ await page.getByText(text).first().waitFor({ state: "visible" });
578
+ return `Text "${text}" appeared.`;
579
+ }
580
+ if (textGone) {
581
+ await page.getByText(textGone).first().waitFor({ state: "hidden" });
582
+ return `Text "${textGone}" disappeared.`;
583
+ }
584
+ const seconds = Math.min(time ?? 1, 30);
585
+ await page.waitForTimeout(seconds * 1e3);
586
+ return `Waited ${seconds}s.`;
587
+ }
588
+ )
589
+ );
590
+ add(
591
+ "browser_tab_list",
592
+ () => (0, import_beta.tool)(
593
+ {
594
+ name: name("browser_tab_list"),
595
+ description: "List open tabs with their index, URL, and title.",
596
+ inputSchema: import_genkit.z.object({}),
597
+ outputSchema: import_genkit.z.array(
598
+ import_genkit.z.object({
599
+ index: import_genkit.z.number(),
600
+ url: import_genkit.z.string(),
601
+ title: import_genkit.z.string()
602
+ })
603
+ )
604
+ },
605
+ async () => {
606
+ const pages = await session.getPages();
607
+ return Promise.all(
608
+ pages.map(async (p, index) => ({
609
+ index,
610
+ url: p.url(),
611
+ title: await p.title()
612
+ }))
613
+ );
614
+ }
615
+ )
616
+ );
617
+ add(
618
+ "browser_tab_new",
619
+ () => (0, import_beta.tool)(
620
+ {
621
+ name: name("browser_tab_new"),
622
+ description: "Open a new tab, optionally navigating it to a URL.",
623
+ inputSchema: import_genkit.z.object({
624
+ url: import_genkit.z.string().optional().describe("URL to open in the new tab.")
625
+ }),
626
+ outputSchema: import_genkit.z.string()
627
+ },
628
+ async ({ url }) => {
629
+ const page = await session.newPage();
630
+ if (url) {
631
+ if (!hostnameAllowed(url, opts.allowedDomains, opts.blockedDomains)) {
632
+ throw new Error(`Navigation to "${url}" is not allowed by policy.`);
633
+ }
634
+ await page.goto(url, { waitUntil: "domcontentloaded" });
635
+ }
636
+ const pages = await session.getPages();
637
+ return `Opened tab ${pages.length - 1}${url ? ` at ${url}` : ""}.`;
638
+ }
639
+ )
640
+ );
641
+ add(
642
+ "browser_tab_select",
643
+ () => (0, import_beta.tool)(
644
+ {
645
+ name: name("browser_tab_select"),
646
+ description: "Bring a tab to the front by its index.",
647
+ inputSchema: import_genkit.z.object({
648
+ index: import_genkit.z.number().describe("Tab index from browser_tab_list.")
649
+ }),
650
+ outputSchema: import_genkit.z.string()
651
+ },
652
+ async ({ index }) => {
653
+ const pages = await session.getPages();
654
+ const page = pages[index];
655
+ if (!page) throw new Error(`No tab at index ${index}.`);
656
+ await page.bringToFront();
657
+ return `Selected tab ${index}.`;
658
+ }
659
+ )
660
+ );
661
+ add(
662
+ "browser_tab_close",
663
+ () => (0, import_beta.tool)(
664
+ {
665
+ name: name("browser_tab_close"),
666
+ description: "Close a tab by index (defaults to the current/last tab).",
667
+ inputSchema: import_genkit.z.object({
668
+ index: import_genkit.z.number().optional().describe("Tab index to close.")
669
+ }),
670
+ outputSchema: import_genkit.z.string()
671
+ },
672
+ async ({ index }) => {
673
+ const pages = await session.getPages();
674
+ const page = index === void 0 ? pages[pages.length - 1] : pages[index];
675
+ if (!page) throw new Error(`No tab at index ${index}.`);
676
+ await page.close();
677
+ return `Closed tab ${index ?? pages.length - 1}.`;
678
+ }
679
+ )
680
+ );
681
+ add(
682
+ "browser_handle_dialog",
683
+ () => (0, import_beta.tool)(
684
+ {
685
+ name: name("browser_handle_dialog"),
686
+ description: "Accept or dismiss a pending JavaScript dialog (alert/confirm/prompt).",
687
+ inputSchema: import_genkit.z.object({
688
+ accept: import_genkit.z.boolean().describe("Accept (true) or dismiss (false)."),
689
+ promptText: import_genkit.z.string().optional().describe("Text to enter for prompt dialogs.")
690
+ }),
691
+ outputSchema: import_genkit.z.string()
692
+ },
693
+ async ({ accept, promptText }) => {
694
+ const dialog = session.activeDialog;
695
+ if (!dialog) return "No pending dialog to handle.";
696
+ if (accept) {
697
+ await dialog.accept(promptText);
698
+ } else {
699
+ await dialog.dismiss();
700
+ }
701
+ session.activeDialog = void 0;
702
+ session.pendingDialog = void 0;
703
+ return `Dialog ${accept ? "accepted" : "dismissed"}.`;
704
+ }
705
+ )
706
+ );
707
+ add(
708
+ "browser_evaluate",
709
+ () => (0, import_beta.tool)(
710
+ {
711
+ name: name("browser_evaluate"),
712
+ description: "Evaluate arbitrary JavaScript in the page context and return the JSON-serializable result. Use only when other tools are insufficient.",
713
+ inputSchema: import_genkit.z.object({
714
+ script: import_genkit.z.string().describe(
715
+ 'A JS expression or function body, e.g. "document.title".'
716
+ )
717
+ }),
718
+ outputSchema: import_genkit.z.any()
719
+ },
720
+ async ({ script }) => {
721
+ const page = await session.getPage();
722
+ const result = await page.evaluate(
723
+ (code) => {
724
+ const fn = new Function(`return (async () => { ${code} })()`);
725
+ return fn();
726
+ },
727
+ /(\breturn\b|;)/.test(script) ? script : `return (${script});`
728
+ );
729
+ return result ?? null;
730
+ }
731
+ )
732
+ );
733
+ return tools;
734
+ }
735
+
736
+ // src/playwright.ts
737
+ var PlaywrightOptionsSchema = import_genkit2.z.object({
738
+ readOnly: import_genkit2.z.boolean().optional().describe(
739
+ "If true, only observe-only tools are injected (no clicking, typing, etc.)."
740
+ ),
741
+ allowEvaluate: import_genkit2.z.boolean().optional().describe(
742
+ "If true, the browser_evaluate JS escape-hatch tool is enabled. Off by default."
743
+ ),
744
+ tools: import_genkit2.z.array(import_genkit2.z.enum(ALL_TOOL_NAMES)).optional().describe("Allow-list of tools to inject. Defaults to all tools."),
745
+ allowedDomains: import_genkit2.z.array(import_genkit2.z.string()).optional().describe("If set, navigation is restricted to these hostnames."),
746
+ blockedDomains: import_genkit2.z.array(import_genkit2.z.string()).optional().describe("Navigation to these hostnames is blocked."),
747
+ toolNamePrefix: import_genkit2.z.string().optional().describe("Prefix prepended to every injected tool name.")
748
+ });
749
+ var playwright = (0, import_genkit2.generateMiddleware)(
750
+ {
751
+ name: "playwright",
752
+ description: "Injects browser automation tools (navigate, snapshot, click, type, etc.) backed by a Playwright browser.",
753
+ configSchema: PlaywrightOptionsSchema
754
+ },
755
+ ({ config, pluginConfig }) => {
756
+ const session = new BrowserSession(pluginConfig ?? {});
757
+ const messageQueue = [];
758
+ const tools = createPlaywrightTools({
759
+ session,
760
+ messageQueue,
761
+ toolNamePrefix: config?.toolNamePrefix,
762
+ readOnly: config?.readOnly,
763
+ allowEvaluate: config?.allowEvaluate,
764
+ tools: config?.tools,
765
+ allowedDomains: config?.allowedDomains,
766
+ blockedDomains: config?.blockedDomains
767
+ });
768
+ return {
769
+ tools,
770
+ generate: async (envelope, ctx, next) => {
771
+ try {
772
+ const { request } = envelope;
773
+ let { messageIndex } = envelope;
774
+ if (messageQueue.length > 0) {
775
+ if (ctx.onChunk) {
776
+ for (const msg of messageQueue) {
777
+ ctx.onChunk({
778
+ role: msg.role,
779
+ index: messageIndex++,
780
+ content: msg.content
781
+ });
782
+ }
783
+ }
784
+ request.messages.push(...messageQueue);
785
+ messageQueue.length = 0;
786
+ }
787
+ return await next({ ...envelope, request, messageIndex }, ctx);
788
+ } finally {
789
+ await session.close();
790
+ }
791
+ }
792
+ };
793
+ }
794
+ );
795
+ // Annotate the CommonJS export names for ESM import in node:
796
+ 0 && (module.exports = {
797
+ ALL_TOOL_NAMES,
798
+ BrowserSession,
799
+ PlaywrightOptionsSchema,
800
+ createPlaywrightTools,
801
+ playwright
802
+ });
803
+ //# sourceMappingURL=index.js.map