@melony/plugin-browser 0.1.0 → 0.1.2
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/dist/index.d.ts +20 -0
- package/dist/index.js +354 -22
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -53,6 +53,26 @@ declare const browserToolDefinitions: {
|
|
|
53
53
|
};
|
|
54
54
|
interface BrowserPluginOptions {
|
|
55
55
|
headless?: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Maximum number of characters to keep when getting page content.
|
|
58
|
+
* If exceeded, the content will be truncated from the middle.
|
|
59
|
+
* Default: 10000
|
|
60
|
+
*/
|
|
61
|
+
maxContentLength?: number;
|
|
62
|
+
/**
|
|
63
|
+
* Path to user data directory for persistent browser sessions.
|
|
64
|
+
* When provided, the browser will use launchPersistentContext which
|
|
65
|
+
* preserves cookies, localStorage, and login sessions across runs.
|
|
66
|
+
*
|
|
67
|
+
* First run with headless: false to manually log in, then subsequent
|
|
68
|
+
* runs will automatically have your session.
|
|
69
|
+
*/
|
|
70
|
+
userDataDir?: string;
|
|
71
|
+
/**
|
|
72
|
+
* Browser channel to use (e.g. 'chrome', 'chrome-beta', 'msedge').
|
|
73
|
+
* Using 'chrome' can help bypass "browser not safe" errors on Google/YouTube.
|
|
74
|
+
*/
|
|
75
|
+
channel?: string;
|
|
56
76
|
}
|
|
57
77
|
declare const browserPlugin: (options?: BrowserPluginOptions) => MelonyPlugin<any, any>;
|
|
58
78
|
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { chromium } from "playwright";
|
|
4
|
+
var persistentContexts = /* @__PURE__ */ new Map();
|
|
5
|
+
var sharedBrowsers = /* @__PURE__ */ new Map();
|
|
4
6
|
var browserToolDefinitions = {
|
|
5
7
|
browser_navigate: {
|
|
6
8
|
description: "Navigate to a URL and wait for the page to load",
|
|
@@ -42,28 +44,254 @@ var browserToolDefinitions = {
|
|
|
42
44
|
})
|
|
43
45
|
}
|
|
44
46
|
};
|
|
47
|
+
function truncate(str, maxChars) {
|
|
48
|
+
if (!str || str.length <= maxChars) return str;
|
|
49
|
+
const half = Math.floor(maxChars / 2);
|
|
50
|
+
const truncatedCount = str.length - maxChars;
|
|
51
|
+
return `${str.slice(0, half)}
|
|
52
|
+
|
|
53
|
+
[... ${truncatedCount} characters truncated ...]
|
|
54
|
+
|
|
55
|
+
${str.slice(-half)}`;
|
|
56
|
+
}
|
|
45
57
|
var browserPlugin = (options = {}) => {
|
|
46
58
|
let browser = null;
|
|
59
|
+
let context = null;
|
|
47
60
|
let page = null;
|
|
48
|
-
const { headless = true } = options;
|
|
61
|
+
const { headless = true, maxContentLength = 1e4, userDataDir, channel } = options;
|
|
49
62
|
async function ensurePage() {
|
|
50
|
-
if (
|
|
51
|
-
|
|
63
|
+
if (page && page.isClosed()) {
|
|
64
|
+
page = null;
|
|
52
65
|
}
|
|
53
|
-
if (
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
66
|
+
if (userDataDir) {
|
|
67
|
+
const isContextUsable = async (ctx) => {
|
|
68
|
+
if (!ctx) return false;
|
|
69
|
+
try {
|
|
70
|
+
await ctx.pages();
|
|
71
|
+
return true;
|
|
72
|
+
} catch {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
if (!context || !await isContextUsable(context)) {
|
|
77
|
+
if (persistentContexts.has(userDataDir)) {
|
|
78
|
+
context = persistentContexts.get(userDataDir);
|
|
79
|
+
}
|
|
80
|
+
if (!context || !await isContextUsable(context)) {
|
|
81
|
+
context = await chromium.launchPersistentContext(userDataDir, {
|
|
82
|
+
headless,
|
|
83
|
+
channel,
|
|
84
|
+
viewport: { width: 1280, height: 720 },
|
|
85
|
+
ignoreDefaultArgs: ["--enable-automation"],
|
|
86
|
+
args: [
|
|
87
|
+
"--disable-blink-features=AutomationControlled",
|
|
88
|
+
"--no-sandbox",
|
|
89
|
+
"--disable-infobars",
|
|
90
|
+
"--window-position=0,0",
|
|
91
|
+
"--ignore-certifcate-errors",
|
|
92
|
+
"--ignore-certifcate-errors-spki-list",
|
|
93
|
+
"--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"
|
|
94
|
+
]
|
|
95
|
+
});
|
|
96
|
+
context.on("close", () => persistentContexts.delete(userDataDir));
|
|
97
|
+
persistentContexts.set(userDataDir, context);
|
|
98
|
+
}
|
|
99
|
+
if (!page || page.isClosed()) {
|
|
100
|
+
page = context.pages()[0] || await context.newPage();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
const isBrowserUsable = (b) => b && b.isConnected();
|
|
105
|
+
if (!browser || !isBrowserUsable(browser)) {
|
|
106
|
+
const browserKey = `${channel || "default"}-${headless}`;
|
|
107
|
+
if (sharedBrowsers.has(browserKey)) {
|
|
108
|
+
browser = sharedBrowsers.get(browserKey);
|
|
109
|
+
}
|
|
110
|
+
if (!browser || !isBrowserUsable(browser)) {
|
|
111
|
+
browser = await chromium.launch({
|
|
112
|
+
headless,
|
|
113
|
+
channel,
|
|
114
|
+
ignoreDefaultArgs: ["--enable-automation"],
|
|
115
|
+
args: ["--disable-blink-features=AutomationControlled"]
|
|
116
|
+
});
|
|
117
|
+
browser.on("disconnected", () => sharedBrowsers.delete(browserKey));
|
|
118
|
+
sharedBrowsers.set(browserKey, browser);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (!page || page.isClosed()) {
|
|
122
|
+
context = await browser.newContext({
|
|
123
|
+
viewport: { width: 1280, height: 720 },
|
|
124
|
+
userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"
|
|
125
|
+
});
|
|
126
|
+
page = await context.newPage();
|
|
127
|
+
}
|
|
58
128
|
}
|
|
59
129
|
return page;
|
|
60
130
|
}
|
|
61
131
|
return (builder) => {
|
|
132
|
+
async function* yieldStateUpdate(page2) {
|
|
133
|
+
try {
|
|
134
|
+
const url = page2.url();
|
|
135
|
+
const title = await page2.title();
|
|
136
|
+
const screenshot = await page2.screenshot({ type: "jpeg", quality: 60 });
|
|
137
|
+
const base64 = screenshot.toString("base64");
|
|
138
|
+
const pages = context ? await context.pages() : [page2];
|
|
139
|
+
const tabs = await Promise.all(pages.map(async (p, i) => ({
|
|
140
|
+
index: i,
|
|
141
|
+
title: await p.title().catch(() => "Untitled"),
|
|
142
|
+
url: p.url(),
|
|
143
|
+
isActive: p === page2
|
|
144
|
+
})));
|
|
145
|
+
yield {
|
|
146
|
+
type: "browser:state-update",
|
|
147
|
+
data: {
|
|
148
|
+
url,
|
|
149
|
+
title,
|
|
150
|
+
screenshot: base64,
|
|
151
|
+
tabCount: pages.length,
|
|
152
|
+
tabs
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
yield {
|
|
156
|
+
type: "ui",
|
|
157
|
+
data: {
|
|
158
|
+
type: "card",
|
|
159
|
+
props: {
|
|
160
|
+
title: title || "Browser View",
|
|
161
|
+
subtitle: url,
|
|
162
|
+
padding: "none"
|
|
163
|
+
},
|
|
164
|
+
children: [
|
|
165
|
+
{
|
|
166
|
+
type: "image",
|
|
167
|
+
props: {
|
|
168
|
+
src: `data:image/jpeg;base64,${base64}`,
|
|
169
|
+
alt: "Browser Screenshot",
|
|
170
|
+
width: "full"
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
type: "row",
|
|
175
|
+
props: { justify: "between", padding: "sm" },
|
|
176
|
+
children: [
|
|
177
|
+
{
|
|
178
|
+
type: "text",
|
|
179
|
+
props: { value: `Tabs open: ${pages.length}`, size: "xs", color: "muted" }
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
type: "button",
|
|
183
|
+
props: {
|
|
184
|
+
label: "Cleanup Tabs",
|
|
185
|
+
size: "xs",
|
|
186
|
+
variant: "outline",
|
|
187
|
+
onClickAction: { type: "browser:cleanup" }
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
]
|
|
191
|
+
}
|
|
192
|
+
]
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
} catch (e) {
|
|
196
|
+
console.error("Failed to yield state update", e);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
builder.on("browser:poll_state", async function* (event) {
|
|
200
|
+
const p = await ensurePage();
|
|
201
|
+
yield* yieldStateUpdate(p);
|
|
202
|
+
});
|
|
203
|
+
builder.on("browser:cleanup", async function* (event) {
|
|
204
|
+
if (context) {
|
|
205
|
+
const pages = await context.pages();
|
|
206
|
+
for (const p2 of pages) {
|
|
207
|
+
if (p2 !== page && !p2.isClosed()) {
|
|
208
|
+
await p2.close().catch(() => {
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
const p = await ensurePage();
|
|
214
|
+
yield* yieldStateUpdate(p);
|
|
215
|
+
});
|
|
216
|
+
builder.on("action:browser_listTabs", async function* (event) {
|
|
217
|
+
const { toolCallId } = event.data;
|
|
218
|
+
try {
|
|
219
|
+
await ensurePage();
|
|
220
|
+
const pages = context ? await context.pages() : [page];
|
|
221
|
+
const tabs = await Promise.all(pages.map(async (p, i) => ({
|
|
222
|
+
index: i,
|
|
223
|
+
title: await p.title().catch(() => "Untitled"),
|
|
224
|
+
url: p.url(),
|
|
225
|
+
isActive: p === page
|
|
226
|
+
})));
|
|
227
|
+
yield {
|
|
228
|
+
type: "action:result",
|
|
229
|
+
data: {
|
|
230
|
+
action: "browser_listTabs",
|
|
231
|
+
toolCallId,
|
|
232
|
+
result: { success: true, tabs }
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
} catch (error) {
|
|
236
|
+
yield {
|
|
237
|
+
type: "action:result",
|
|
238
|
+
data: { action: "browser_listTabs", toolCallId, result: { error: error.message } }
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
builder.on("action:browser_closeTab", async function* (event) {
|
|
243
|
+
const { index, toolCallId } = event.data;
|
|
244
|
+
try {
|
|
245
|
+
await ensurePage();
|
|
246
|
+
const pages = context ? await context.pages() : [page];
|
|
247
|
+
if (index >= 0 && index < pages.length) {
|
|
248
|
+
const tabToClose = pages[index];
|
|
249
|
+
if (tabToClose === page) {
|
|
250
|
+
page = null;
|
|
251
|
+
}
|
|
252
|
+
await tabToClose.close();
|
|
253
|
+
}
|
|
254
|
+
const p = await ensurePage();
|
|
255
|
+
yield* yieldStateUpdate(p);
|
|
256
|
+
yield {
|
|
257
|
+
type: "action:result",
|
|
258
|
+
data: {
|
|
259
|
+
action: "browser_closeTab",
|
|
260
|
+
toolCallId,
|
|
261
|
+
result: { success: true }
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
} catch (error) {
|
|
265
|
+
yield {
|
|
266
|
+
type: "action:result",
|
|
267
|
+
data: { action: "browser_closeTab", toolCallId, result: { error: error.message } }
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
});
|
|
62
271
|
builder.on("action:browser_navigate", async function* (event) {
|
|
63
272
|
const { url, waitUntil, toolCallId } = event.data;
|
|
273
|
+
yield {
|
|
274
|
+
type: "ui",
|
|
275
|
+
data: {
|
|
276
|
+
type: "text",
|
|
277
|
+
props: {
|
|
278
|
+
value: `Navigating to: ${url}`
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
};
|
|
64
282
|
try {
|
|
65
283
|
const page2 = await ensurePage();
|
|
66
284
|
await page2.goto(url, { waitUntil });
|
|
285
|
+
yield* yieldStateUpdate(page2);
|
|
286
|
+
yield {
|
|
287
|
+
type: "ui",
|
|
288
|
+
data: {
|
|
289
|
+
type: "text",
|
|
290
|
+
props: {
|
|
291
|
+
value: `Navigated to: ${page2.url()}`
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
};
|
|
67
295
|
yield {
|
|
68
296
|
type: "action:result",
|
|
69
297
|
data: {
|
|
@@ -77,37 +305,50 @@ var browserPlugin = (options = {}) => {
|
|
|
77
305
|
type: "action:result",
|
|
78
306
|
data: { action: "browser_navigate", toolCallId, result: { error: error.message } }
|
|
79
307
|
};
|
|
308
|
+
yield {
|
|
309
|
+
type: "ui",
|
|
310
|
+
data: {
|
|
311
|
+
type: "text",
|
|
312
|
+
props: {
|
|
313
|
+
value: `Navigation failed: ${error.message}`
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
};
|
|
80
317
|
}
|
|
81
318
|
});
|
|
82
319
|
builder.on("action:browser_screenshot", async function* (event) {
|
|
83
320
|
const { fullPage, toolCallId } = event.data;
|
|
321
|
+
yield {
|
|
322
|
+
type: "ui",
|
|
323
|
+
data: {
|
|
324
|
+
type: "text",
|
|
325
|
+
props: {
|
|
326
|
+
value: `Taking screenshot`
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
};
|
|
84
330
|
try {
|
|
85
331
|
const page2 = await ensurePage();
|
|
86
332
|
const buffer = await page2.screenshot({ fullPage, type: "png" });
|
|
87
333
|
const base64 = buffer.toString("base64");
|
|
334
|
+
yield* yieldStateUpdate(page2);
|
|
88
335
|
yield {
|
|
89
336
|
type: "action:result",
|
|
90
337
|
data: {
|
|
91
338
|
action: "browser_screenshot",
|
|
92
339
|
toolCallId,
|
|
93
|
-
result: { success: true, format: "png",
|
|
340
|
+
result: { success: true, format: "png", message: "Screenshot taken successfully" }
|
|
94
341
|
}
|
|
95
342
|
};
|
|
96
343
|
yield {
|
|
97
344
|
type: "ui",
|
|
98
345
|
data: {
|
|
99
|
-
type: "
|
|
100
|
-
props: {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
src: `data:image/png;base64,${base64}`,
|
|
106
|
-
alt: "Browser Screenshot",
|
|
107
|
-
width: "full"
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
]
|
|
346
|
+
type: "image",
|
|
347
|
+
props: {
|
|
348
|
+
src: `data:image/png;base64,${base64}`,
|
|
349
|
+
alt: "Browser Screenshot",
|
|
350
|
+
width: "full"
|
|
351
|
+
}
|
|
111
352
|
}
|
|
112
353
|
};
|
|
113
354
|
} catch (error) {
|
|
@@ -115,17 +356,45 @@ var browserPlugin = (options = {}) => {
|
|
|
115
356
|
type: "action:result",
|
|
116
357
|
data: { action: "browser_screenshot", toolCallId, result: { error: error.message } }
|
|
117
358
|
};
|
|
359
|
+
yield {
|
|
360
|
+
type: "ui",
|
|
361
|
+
data: {
|
|
362
|
+
type: "text",
|
|
363
|
+
props: {
|
|
364
|
+
value: `Screenshot failed: ${error.message}`
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
};
|
|
118
368
|
}
|
|
119
369
|
});
|
|
120
370
|
builder.on("action:browser_click", async function* (event) {
|
|
121
371
|
const { selector, toolCallId } = event.data;
|
|
372
|
+
yield {
|
|
373
|
+
type: "ui",
|
|
374
|
+
data: {
|
|
375
|
+
type: "text",
|
|
376
|
+
props: {
|
|
377
|
+
value: `Clicking: ${selector}`
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
};
|
|
122
381
|
try {
|
|
123
382
|
const page2 = await ensurePage();
|
|
124
383
|
await page2.click(selector);
|
|
384
|
+
yield* yieldStateUpdate(page2);
|
|
125
385
|
yield {
|
|
126
386
|
type: "action:result",
|
|
127
387
|
data: { action: "browser_click", toolCallId, result: { success: true } }
|
|
128
388
|
};
|
|
389
|
+
yield {
|
|
390
|
+
type: "ui",
|
|
391
|
+
data: {
|
|
392
|
+
type: "text",
|
|
393
|
+
props: {
|
|
394
|
+
value: `Clicked: ${selector}`
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
};
|
|
129
398
|
} catch (error) {
|
|
130
399
|
yield {
|
|
131
400
|
type: "action:result",
|
|
@@ -135,22 +404,59 @@ var browserPlugin = (options = {}) => {
|
|
|
135
404
|
});
|
|
136
405
|
builder.on("action:browser_type", async function* (event) {
|
|
137
406
|
const { selector, text, delay, toolCallId } = event.data;
|
|
407
|
+
yield {
|
|
408
|
+
type: "ui",
|
|
409
|
+
data: {
|
|
410
|
+
type: "text",
|
|
411
|
+
props: {
|
|
412
|
+
value: `Typing: ${selector}`
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
};
|
|
138
416
|
try {
|
|
139
417
|
const page2 = await ensurePage();
|
|
140
418
|
await page2.type(selector, text, { delay });
|
|
419
|
+
yield* yieldStateUpdate(page2);
|
|
141
420
|
yield {
|
|
142
421
|
type: "action:result",
|
|
143
422
|
data: { action: "browser_type", toolCallId, result: { success: true } }
|
|
144
423
|
};
|
|
424
|
+
yield {
|
|
425
|
+
type: "ui",
|
|
426
|
+
data: {
|
|
427
|
+
type: "text",
|
|
428
|
+
props: {
|
|
429
|
+
value: `Typed: ${selector}`
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
};
|
|
145
433
|
} catch (error) {
|
|
146
434
|
yield {
|
|
147
435
|
type: "action:result",
|
|
148
436
|
data: { action: "browser_type", toolCallId, result: { error: error.message } }
|
|
149
437
|
};
|
|
438
|
+
yield {
|
|
439
|
+
type: "ui",
|
|
440
|
+
data: {
|
|
441
|
+
type: "text",
|
|
442
|
+
props: {
|
|
443
|
+
value: `Typed: ${selector}`
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
};
|
|
150
447
|
}
|
|
151
448
|
});
|
|
152
449
|
builder.on("action:browser_getContent", async function* (event) {
|
|
153
450
|
const { format, toolCallId } = event.data;
|
|
451
|
+
yield {
|
|
452
|
+
type: "ui",
|
|
453
|
+
data: {
|
|
454
|
+
type: "text",
|
|
455
|
+
props: {
|
|
456
|
+
value: `Getting content: ${format}`
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
};
|
|
154
460
|
try {
|
|
155
461
|
const page2 = await ensurePage();
|
|
156
462
|
let content = "";
|
|
@@ -163,13 +469,38 @@ var browserPlugin = (options = {}) => {
|
|
|
163
469
|
}
|
|
164
470
|
yield {
|
|
165
471
|
type: "action:result",
|
|
166
|
-
data: {
|
|
472
|
+
data: {
|
|
473
|
+
action: "browser_getContent",
|
|
474
|
+
toolCallId,
|
|
475
|
+
result: {
|
|
476
|
+
success: true,
|
|
477
|
+
content: truncate(content, maxContentLength)
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
yield {
|
|
482
|
+
type: "ui",
|
|
483
|
+
data: {
|
|
484
|
+
type: "text",
|
|
485
|
+
props: {
|
|
486
|
+
value: `Content: ${content}`
|
|
487
|
+
}
|
|
488
|
+
}
|
|
167
489
|
};
|
|
168
490
|
} catch (error) {
|
|
169
491
|
yield {
|
|
170
492
|
type: "action:result",
|
|
171
493
|
data: { action: "browser_getContent", toolCallId, result: { error: error.message } }
|
|
172
494
|
};
|
|
495
|
+
yield {
|
|
496
|
+
type: "ui",
|
|
497
|
+
data: {
|
|
498
|
+
type: "text",
|
|
499
|
+
props: {
|
|
500
|
+
value: `Content failed: ${error.message}`
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
};
|
|
173
504
|
}
|
|
174
505
|
});
|
|
175
506
|
builder.on("action:browser_evaluate", async function* (event) {
|
|
@@ -177,6 +508,7 @@ var browserPlugin = (options = {}) => {
|
|
|
177
508
|
try {
|
|
178
509
|
const page2 = await ensurePage();
|
|
179
510
|
const result = await page2.evaluate(script);
|
|
511
|
+
yield* yieldStateUpdate(page2);
|
|
180
512
|
yield {
|
|
181
513
|
type: "action:result",
|
|
182
514
|
data: { action: "browser_evaluate", toolCallId, result: { success: true, result } }
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { MelonyPlugin } from \"melony\";\nimport { z } from \"zod\";\nimport { chromium, Browser, Page } from \"playwright\";\n\nexport const browserToolDefinitions = {\n browser_navigate: {\n description: \"Navigate to a URL and wait for the page to load\",\n inputSchema: z.object({\n url: z.string().describe(\"The URL to navigate to\"),\n waitUntil: z.enum([\"load\", \"domcontentloaded\", \"networkidle\", \"commit\"]).default(\"load\").describe(\"When to consider navigation finished\"),\n }),\n },\n browser_screenshot: {\n description: \"Take a screenshot of the current page and return it as a base64 image\",\n inputSchema: z.object({\n fullPage: z.boolean().default(false).describe(\"Whether to take a screenshot of the full scrollable page\"),\n }),\n },\n browser_click: {\n description: \"Click an element on the page\",\n inputSchema: z.object({\n selector: z.string().describe(\"CSS selector or XPath of the element to click\"),\n }),\n },\n browser_type: {\n description: \"Type text into an input field\",\n inputSchema: z.object({\n selector: z.string().describe(\"CSS selector or XPath of the element to type into\"),\n text: z.string().describe(\"The text to type\"),\n delay: z.number().optional().describe(\"Delay between keystrokes in milliseconds\"),\n }),\n },\n browser_getContent: {\n description: \"Get the content of the current page\",\n inputSchema: z.object({\n format: z.enum([\"text\", \"html\", \"markdown\"]).default(\"text\").describe(\"The format to return the content in\"),\n }),\n },\n browser_evaluate: {\n description: \"Execute JavaScript code in the context of the page\",\n inputSchema: z.object({\n script: z.string().describe(\"The JavaScript code to execute\"),\n }),\n },\n};\n\nexport interface BrowserPluginOptions {\n headless?: boolean;\n}\n\nexport const browserPlugin = (options: BrowserPluginOptions = {}): MelonyPlugin<any, any> => {\n let browser: Browser | null = null;\n let page: Page | null = null;\n\n const { headless = true } = options;\n\n async function ensurePage(): Promise<Page> {\n if (!browser) {\n browser = await chromium.launch({ headless });\n }\n if (!page) {\n const context = await browser.newContext({\n viewport: { width: 1280, height: 720 },\n });\n page = await context.newPage();\n }\n return page;\n }\n\n return (builder) => {\n builder.on(\"action:browser_navigate\", async function* (event) {\n const { url, waitUntil, toolCallId } = event.data;\n try {\n const page = await ensurePage();\n await page.goto(url, { waitUntil });\n yield {\n type: \"action:result\",\n data: {\n action: \"browser_navigate\",\n toolCallId,\n result: { success: true, url: page.url(), title: await page.title() },\n },\n };\n } catch (error: any) {\n yield {\n type: \"action:result\",\n data: { action: \"browser_navigate\", toolCallId, result: { error: error.message } },\n };\n }\n });\n\n builder.on(\"action:browser_screenshot\", async function* (event) {\n const { fullPage, toolCallId } = event.data;\n try {\n const page = await ensurePage();\n const buffer = await page.screenshot({ fullPage, type: \"png\" });\n const base64 = buffer.toString(\"base64\");\n \n // Return both the raw result and a UI event to show the screenshot\n yield {\n type: \"action:result\",\n data: {\n action: \"browser_screenshot\",\n toolCallId,\n result: { success: true, format: \"png\", base64: `data:image/png;base64,${base64}` },\n },\n };\n\n yield {\n type: \"ui\",\n data: {\n type: \"card\",\n props: { title: \"Page Screenshot\" },\n children: [\n {\n type: \"image\",\n props: {\n src: `data:image/png;base64,${base64}`,\n alt: \"Browser Screenshot\",\n width: \"full\",\n },\n },\n ],\n },\n };\n } catch (error: any) {\n yield {\n type: \"action:result\",\n data: { action: \"browser_screenshot\", toolCallId, result: { error: error.message } },\n };\n }\n });\n\n builder.on(\"action:browser_click\", async function* (event) {\n const { selector, toolCallId } = event.data;\n try {\n const page = await ensurePage();\n await page.click(selector);\n yield {\n type: \"action:result\",\n data: { action: \"browser_click\", toolCallId, result: { success: true } },\n };\n } catch (error: any) {\n yield {\n type: \"action:result\",\n data: { action: \"browser_click\", toolCallId, result: { error: error.message } },\n };\n }\n });\n\n builder.on(\"action:browser_type\", async function* (event) {\n const { selector, text, delay, toolCallId } = event.data;\n try {\n const page = await ensurePage();\n await page.type(selector, text, { delay });\n yield {\n type: \"action:result\",\n data: { action: \"browser_type\", toolCallId, result: { success: true } },\n };\n } catch (error: any) {\n yield {\n type: \"action:result\",\n data: { action: \"browser_type\", toolCallId, result: { error: error.message } },\n };\n }\n });\n\n builder.on(\"action:browser_getContent\", async function* (event) {\n const { format, toolCallId } = event.data;\n try {\n const page = await ensurePage();\n let content = \"\";\n if (format === \"html\") {\n content = await page.content();\n } else if (format === \"text\") {\n content = await page.evaluate(() => document.body.innerText);\n } else if (format === \"markdown\") {\n // Basic markdown conversion if needed, for now just text\n content = await page.evaluate(() => document.body.innerText);\n }\n yield {\n type: \"action:result\",\n data: { action: \"browser_getContent\", toolCallId, result: { success: true, content } },\n };\n } catch (error: any) {\n yield {\n type: \"action:result\",\n data: { action: \"browser_getContent\", toolCallId, result: { error: error.message } },\n };\n }\n });\n\n builder.on(\"action:browser_evaluate\", async function* (event) {\n const { script, toolCallId } = event.data;\n try {\n const page = await ensurePage();\n const result = await page.evaluate(script);\n yield {\n type: \"action:result\",\n data: { action: \"browser_evaluate\", toolCallId, result: { success: true, result } },\n };\n } catch (error: any) {\n yield {\n type: \"action:result\",\n data: { action: \"browser_evaluate\", toolCallId, result: { error: error.message } },\n };\n }\n });\n };\n};\n"],"mappings":"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","names":["page"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { MelonyPlugin } from \"melony\";\nimport { z } from \"zod\";\nimport { chromium, Browser, BrowserContext, Page } from \"playwright\";\n\n// Shared state to avoid \"ProcessSingleton\" errors in long-running processes (like Express)\n// mapping userDataDir -> Context\nconst persistentContexts = new Map<string, BrowserContext>();\n// mapping channel/options -> Browser\nconst sharedBrowsers = new Map<string, Browser>();\n\nexport const browserToolDefinitions = {\n browser_navigate: {\n description: \"Navigate to a URL and wait for the page to load\",\n inputSchema: z.object({\n url: z.string().describe(\"The URL to navigate to\"),\n waitUntil: z.enum([\"load\", \"domcontentloaded\", \"networkidle\", \"commit\"]).default(\"load\").describe(\"When to consider navigation finished\"),\n }),\n },\n browser_screenshot: {\n description: \"Take a screenshot of the current page and return it as a base64 image\",\n inputSchema: z.object({\n fullPage: z.boolean().default(false).describe(\"Whether to take a screenshot of the full scrollable page\"),\n }),\n },\n browser_click: {\n description: \"Click an element on the page\",\n inputSchema: z.object({\n selector: z.string().describe(\"CSS selector or XPath of the element to click\"),\n }),\n },\n browser_type: {\n description: \"Type text into an input field\",\n inputSchema: z.object({\n selector: z.string().describe(\"CSS selector or XPath of the element to type into\"),\n text: z.string().describe(\"The text to type\"),\n delay: z.number().optional().describe(\"Delay between keystrokes in milliseconds\"),\n }),\n },\n browser_getContent: {\n description: \"Get the content of the current page\",\n inputSchema: z.object({\n format: z.enum([\"text\", \"html\", \"markdown\"]).default(\"text\").describe(\"The format to return the content in\"),\n }),\n },\n browser_evaluate: {\n description: \"Execute JavaScript code in the context of the page\",\n inputSchema: z.object({\n script: z.string().describe(\"The JavaScript code to execute\"),\n }),\n }\n};\n\nexport interface BrowserPluginOptions {\n headless?: boolean;\n /**\n * Maximum number of characters to keep when getting page content.\n * If exceeded, the content will be truncated from the middle.\n * Default: 10000\n */\n maxContentLength?: number;\n /**\n * Path to user data directory for persistent browser sessions.\n * When provided, the browser will use launchPersistentContext which\n * preserves cookies, localStorage, and login sessions across runs.\n * \n * First run with headless: false to manually log in, then subsequent\n * runs will automatically have your session.\n */\n userDataDir?: string;\n /**\n * Browser channel to use (e.g. 'chrome', 'chrome-beta', 'msedge').\n * Using 'chrome' can help bypass \"browser not safe\" errors on Google/YouTube.\n */\n channel?: string;\n}\n\n/**\n * Truncates a string by keeping the first and last N characters.\n */\nfunction truncate(str: string | undefined | null, maxChars: number): string | undefined | null {\n if (!str || str.length <= maxChars) return str;\n const half = Math.floor(maxChars / 2);\n const truncatedCount = str.length - maxChars;\n return `${str.slice(0, half)}\\n\\n[... ${truncatedCount} characters truncated ...]\\n\\n${str.slice(-half)}`;\n}\n\nexport const browserPlugin = (options: BrowserPluginOptions = {}): MelonyPlugin<any, any> => {\n let browser: Browser | null = null;\n let context: BrowserContext | null = null;\n let page: Page | null = null;\n\n const { headless = true, maxContentLength = 10000, userDataDir, channel } = options;\n\n async function ensurePage(): Promise<Page> {\n // If page exists but is closed, clear it\n if (page && page.isClosed()) {\n page = null;\n }\n\n if (userDataDir) {\n // Use persistent context for session persistence (cookies, localStorage, etc.)\n const isContextUsable = async (ctx: BrowserContext | null) => {\n if (!ctx) return false;\n try {\n await ctx.pages();\n return true;\n } catch {\n return false;\n }\n };\n\n if (!context || !(await isContextUsable(context))) {\n // Check if we already have a context for this directory in this process\n if (persistentContexts.has(userDataDir)) {\n context = persistentContexts.get(userDataDir)!;\n }\n\n // If context is still null or not usable, launch it\n if (!context || !(await isContextUsable(context))) {\n context = await chromium.launchPersistentContext(userDataDir, {\n headless,\n channel,\n viewport: { width: 1280, height: 720 },\n ignoreDefaultArgs: [\"--enable-automation\"],\n args: [\n \"--disable-blink-features=AutomationControlled\",\n \"--no-sandbox\",\n \"--disable-infobars\",\n \"--window-position=0,0\",\n \"--ignore-certifcate-errors\",\n \"--ignore-certifcate-errors-spki-list\",\n \"--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36\",\n ],\n });\n context.on(\"close\", () => persistentContexts.delete(userDataDir));\n persistentContexts.set(userDataDir, context);\n }\n\n // Ensure we have a page\n if (!page || page.isClosed()) {\n page = context.pages()[0] || await context.newPage();\n }\n }\n } else {\n // Standard non-persistent browser\n const isBrowserUsable = (b: Browser | null) => b && b.isConnected();\n\n if (!browser || !isBrowserUsable(browser)) {\n const browserKey = `${channel || \"default\"}-${headless}`;\n if (sharedBrowsers.has(browserKey)) {\n browser = sharedBrowsers.get(browserKey)!;\n }\n\n if (!browser || !isBrowserUsable(browser)) {\n browser = await chromium.launch({\n headless,\n channel,\n ignoreDefaultArgs: [\"--enable-automation\"],\n args: [\"--disable-blink-features=AutomationControlled\"],\n });\n browser.on(\"disconnected\", () => sharedBrowsers.delete(browserKey));\n sharedBrowsers.set(browserKey, browser);\n }\n }\n\n if (!page || page.isClosed()) {\n context = await browser.newContext({\n viewport: { width: 1280, height: 720 },\n userAgent: \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36\",\n });\n page = await context.newPage();\n }\n }\n return page!;\n }\n\n return (builder) => {\n async function* yieldStateUpdate(page: Page) {\n try {\n const url = page.url();\n const title = await page.title();\n const screenshot = await page.screenshot({ type: \"jpeg\", quality: 60 });\n const base64 = screenshot.toString(\"base64\");\n \n // Get all pages to show tab count in UI\n const pages = context ? await context.pages() : [page];\n const tabs = await Promise.all(pages.map(async (p, i) => ({\n index: i,\n title: await p.title().catch(() => \"Untitled\"),\n url: p.url(),\n isActive: p === page\n })));\n\n // Yield for potential sidebars/background state\n yield {\n type: \"browser:state-update\",\n data: {\n url,\n title,\n screenshot: base64,\n tabCount: pages.length,\n tabs,\n },\n };\n\n // Yield as a UI element for the chat bubble\n yield {\n type: \"ui\",\n data: {\n type: \"card\",\n props: {\n title: title || \"Browser View\",\n subtitle: url,\n padding: \"none\",\n },\n children: [\n {\n type: \"image\",\n props: {\n src: `data:image/jpeg;base64,${base64}`,\n alt: \"Browser Screenshot\",\n width: \"full\",\n },\n },\n {\n type: \"row\",\n props: { justify: \"between\", padding: \"sm\" },\n children: [\n {\n type: \"text\",\n props: { value: `Tabs open: ${pages.length}`, size: \"xs\", color: \"muted\" }\n },\n {\n type: \"button\",\n props: { \n label: \"Cleanup Tabs\", \n size: \"xs\", \n variant: \"outline\",\n onClickAction: { type: \"browser:cleanup\" }\n }\n }\n ]\n }\n ],\n },\n };\n } catch (e) {\n console.error(\"Failed to yield state update\", e);\n }\n }\n\n builder.on(\"browser:poll_state\", async function* (event) {\n const p = await ensurePage();\n yield* yieldStateUpdate(p);\n });\n\n builder.on(\"browser:cleanup\", async function* (event) {\n if (context) {\n const pages = await context.pages();\n // Keep the current page, close all others\n for (const p of pages) {\n if (p !== page && !p.isClosed()) {\n await p.close().catch(() => {});\n }\n }\n }\n const p = await ensurePage();\n yield* yieldStateUpdate(p);\n });\n\n builder.on(\"action:browser_listTabs\", async function* (event) {\n const { toolCallId } = event.data;\n try {\n await ensurePage();\n const pages = context ? await context.pages() : [page!];\n const tabs = await Promise.all(pages.map(async (p, i) => ({\n index: i,\n title: await p.title().catch(() => \"Untitled\"),\n url: p.url(),\n isActive: p === page\n })));\n\n yield {\n type: \"action:result\",\n data: {\n action: \"browser_listTabs\",\n toolCallId,\n result: { success: true, tabs },\n },\n };\n } catch (error: any) {\n yield {\n type: \"action:result\",\n data: { action: \"browser_listTabs\", toolCallId, result: { error: error.message } },\n };\n }\n });\n\n builder.on(\"action:browser_closeTab\", async function* (event) {\n const { index, toolCallId } = event.data;\n try {\n await ensurePage();\n const pages = context ? await context.pages() : [page!];\n \n if (index >= 0 && index < pages.length) {\n const tabToClose = pages[index];\n if (tabToClose === page) {\n // If we're closing the active page, we need to pick a new one\n page = null;\n }\n await tabToClose.close();\n }\n\n const p = await ensurePage();\n yield* yieldStateUpdate(p);\n\n yield {\n type: \"action:result\",\n data: {\n action: \"browser_closeTab\",\n toolCallId,\n result: { success: true },\n },\n };\n } catch (error: any) {\n yield {\n type: \"action:result\",\n data: { action: \"browser_closeTab\", toolCallId, result: { error: error.message } },\n };\n }\n });\n\n builder.on(\"action:browser_navigate\", async function* (event) {\n const { url, waitUntil, toolCallId } = event.data;\n\n yield {\n type: \"ui\",\n data: {\n type: 'text',\n props: {\n value: `Navigating to: ${url}`,\n }\n },\n };\n\n try {\n const page = await ensurePage();\n await page.goto(url, { waitUntil });\n\n yield* yieldStateUpdate(page);\n\n yield {\n type: \"ui\",\n data: {\n type: 'text',\n props: {\n value: `Navigated to: ${page.url()}`,\n }\n },\n };\n\n yield {\n type: \"action:result\",\n data: {\n action: \"browser_navigate\",\n toolCallId,\n result: { success: true, url: page.url(), title: await page.title() },\n },\n };\n } catch (error: any) {\n yield {\n type: \"action:result\",\n data: { action: \"browser_navigate\", toolCallId, result: { error: error.message } },\n };\n\n yield {\n type: \"ui\",\n data: {\n type: 'text',\n props: {\n value: `Navigation failed: ${error.message}`,\n }\n },\n };\n }\n });\n\n builder.on(\"action:browser_screenshot\", async function* (event) {\n const { fullPage, toolCallId } = event.data;\n\n yield {\n type: \"ui\",\n data: {\n type: 'text',\n props: {\n value: `Taking screenshot`,\n }\n },\n };\n\n try {\n const page = await ensurePage();\n const buffer = await page.screenshot({ fullPage, type: \"png\" });\n const base64 = buffer.toString(\"base64\");\n\n yield* yieldStateUpdate(page);\n\n // Return both the raw result and a UI event to show the screenshot\n yield {\n type: \"action:result\",\n data: {\n action: \"browser_screenshot\",\n toolCallId,\n result: { success: true, format: \"png\", message: \"Screenshot taken successfully\" },\n },\n };\n\n yield {\n type: \"ui\",\n data: {\n type: \"image\",\n props: {\n src: `data:image/png;base64,${base64}`,\n alt: \"Browser Screenshot\",\n width: \"full\",\n }\n },\n };\n } catch (error: any) {\n yield {\n type: \"action:result\",\n data: { action: \"browser_screenshot\", toolCallId, result: { error: error.message } },\n };\n\n yield {\n type: \"ui\",\n data: {\n type: 'text',\n props: {\n value: `Screenshot failed: ${error.message}`,\n }\n },\n };\n }\n });\n\n builder.on(\"action:browser_click\", async function* (event) {\n const { selector, toolCallId } = event.data;\n\n yield {\n type: \"ui\",\n data: {\n type: 'text',\n props: {\n value: `Clicking: ${selector}`,\n }\n },\n };\n\n try {\n const page = await ensurePage();\n await page.click(selector);\n\n yield* yieldStateUpdate(page);\n\n yield {\n type: \"action:result\",\n data: { action: \"browser_click\", toolCallId, result: { success: true } },\n };\n\n yield {\n type: \"ui\",\n data: {\n type: 'text',\n props: {\n value: `Clicked: ${selector}`,\n }\n },\n };\n } catch (error: any) {\n yield {\n type: \"action:result\",\n data: { action: \"browser_click\", toolCallId, result: { error: error.message } },\n };\n }\n });\n\n builder.on(\"action:browser_type\", async function* (event) {\n const { selector, text, delay, toolCallId } = event.data;\n\n yield {\n type: \"ui\",\n data: {\n type: 'text',\n props: {\n value: `Typing: ${selector}`,\n }\n },\n };\n\n try {\n const page = await ensurePage();\n await page.type(selector, text, { delay });\n\n yield* yieldStateUpdate(page);\n\n yield {\n type: \"action:result\",\n data: { action: \"browser_type\", toolCallId, result: { success: true } },\n };\n\n yield {\n type: \"ui\",\n data: {\n type: 'text',\n props: {\n value: `Typed: ${selector}`,\n }\n },\n };\n } catch (error: any) {\n yield {\n type: \"action:result\",\n data: { action: \"browser_type\", toolCallId, result: { error: error.message } },\n };\n\n yield {\n type: \"ui\",\n data: {\n type: 'text',\n props: {\n value: `Typed: ${selector}`,\n }\n },\n };\n }\n });\n\n builder.on(\"action:browser_getContent\", async function* (event) {\n const { format, toolCallId } = event.data;\n\n yield {\n type: \"ui\",\n data: {\n type: 'text',\n props: {\n value: `Getting content: ${format}`,\n }\n },\n };\n\n try {\n const page = await ensurePage();\n let content = \"\";\n if (format === \"html\") {\n content = await page.content();\n } else if (format === \"text\") {\n content = await page.evaluate(() => document.body.innerText);\n } else if (format === \"markdown\") {\n // Basic markdown conversion if needed, for now just text\n content = await page.evaluate(() => document.body.innerText);\n }\n yield {\n type: \"action:result\",\n data: {\n action: \"browser_getContent\",\n toolCallId,\n result: {\n success: true,\n content: truncate(content, maxContentLength)\n }\n },\n };\n\n yield {\n type: \"ui\",\n data: {\n type: 'text',\n props: {\n value: `Content: ${content}`,\n }\n },\n };\n } catch (error: any) {\n yield {\n type: \"action:result\",\n data: { action: \"browser_getContent\", toolCallId, result: { error: error.message } },\n };\n\n yield {\n type: \"ui\",\n data: {\n type: 'text',\n props: {\n value: `Content failed: ${error.message}`,\n }\n },\n };\n }\n });\n\n builder.on(\"action:browser_evaluate\", async function* (event) {\n const { script, toolCallId } = event.data;\n try {\n const page = await ensurePage();\n const result = await page.evaluate(script);\n\n yield* yieldStateUpdate(page);\n\n yield {\n type: \"action:result\",\n data: { action: \"browser_evaluate\", toolCallId, result: { success: true, result } },\n };\n } catch (error: any) {\n yield {\n type: \"action:result\",\n data: { action: \"browser_evaluate\", toolCallId, result: { error: error.message } },\n };\n }\n });\n };\n};\n"],"mappings":"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","names":["page","p"]}
|