@melony/plugin-browser 0.1.0 → 0.1.1
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 +210 -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,114 @@ 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) => {
|
|
62
132
|
builder.on("action:browser_navigate", async function* (event) {
|
|
63
133
|
const { url, waitUntil, toolCallId } = event.data;
|
|
134
|
+
yield {
|
|
135
|
+
type: "ui",
|
|
136
|
+
data: {
|
|
137
|
+
type: "text",
|
|
138
|
+
props: {
|
|
139
|
+
value: `Navigating to: ${url}`
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
};
|
|
64
143
|
try {
|
|
65
144
|
const page2 = await ensurePage();
|
|
66
145
|
await page2.goto(url, { waitUntil });
|
|
146
|
+
yield {
|
|
147
|
+
type: "ui",
|
|
148
|
+
data: {
|
|
149
|
+
type: "text",
|
|
150
|
+
props: {
|
|
151
|
+
value: `Navigated to: ${page2.url()}`
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
};
|
|
67
155
|
yield {
|
|
68
156
|
type: "action:result",
|
|
69
157
|
data: {
|
|
@@ -77,10 +165,28 @@ var browserPlugin = (options = {}) => {
|
|
|
77
165
|
type: "action:result",
|
|
78
166
|
data: { action: "browser_navigate", toolCallId, result: { error: error.message } }
|
|
79
167
|
};
|
|
168
|
+
yield {
|
|
169
|
+
type: "ui",
|
|
170
|
+
data: {
|
|
171
|
+
type: "text",
|
|
172
|
+
props: {
|
|
173
|
+
value: `Navigation failed: ${error.message}`
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
};
|
|
80
177
|
}
|
|
81
178
|
});
|
|
82
179
|
builder.on("action:browser_screenshot", async function* (event) {
|
|
83
180
|
const { fullPage, toolCallId } = event.data;
|
|
181
|
+
yield {
|
|
182
|
+
type: "ui",
|
|
183
|
+
data: {
|
|
184
|
+
type: "text",
|
|
185
|
+
props: {
|
|
186
|
+
value: `Taking screenshot`
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
};
|
|
84
190
|
try {
|
|
85
191
|
const page2 = await ensurePage();
|
|
86
192
|
const buffer = await page2.screenshot({ fullPage, type: "png" });
|
|
@@ -90,24 +196,18 @@ var browserPlugin = (options = {}) => {
|
|
|
90
196
|
data: {
|
|
91
197
|
action: "browser_screenshot",
|
|
92
198
|
toolCallId,
|
|
93
|
-
result: { success: true, format: "png",
|
|
199
|
+
result: { success: true, format: "png", message: "Screenshot taken successfully" }
|
|
94
200
|
}
|
|
95
201
|
};
|
|
96
202
|
yield {
|
|
97
203
|
type: "ui",
|
|
98
204
|
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
|
-
]
|
|
205
|
+
type: "image",
|
|
206
|
+
props: {
|
|
207
|
+
src: `data:image/png;base64,${base64}`,
|
|
208
|
+
alt: "Browser Screenshot",
|
|
209
|
+
width: "full"
|
|
210
|
+
}
|
|
111
211
|
}
|
|
112
212
|
};
|
|
113
213
|
} catch (error) {
|
|
@@ -115,10 +215,28 @@ var browserPlugin = (options = {}) => {
|
|
|
115
215
|
type: "action:result",
|
|
116
216
|
data: { action: "browser_screenshot", toolCallId, result: { error: error.message } }
|
|
117
217
|
};
|
|
218
|
+
yield {
|
|
219
|
+
type: "ui",
|
|
220
|
+
data: {
|
|
221
|
+
type: "text",
|
|
222
|
+
props: {
|
|
223
|
+
value: `Screenshot failed: ${error.message}`
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
};
|
|
118
227
|
}
|
|
119
228
|
});
|
|
120
229
|
builder.on("action:browser_click", async function* (event) {
|
|
121
230
|
const { selector, toolCallId } = event.data;
|
|
231
|
+
yield {
|
|
232
|
+
type: "ui",
|
|
233
|
+
data: {
|
|
234
|
+
type: "text",
|
|
235
|
+
props: {
|
|
236
|
+
value: `Clicking: ${selector}`
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
};
|
|
122
240
|
try {
|
|
123
241
|
const page2 = await ensurePage();
|
|
124
242
|
await page2.click(selector);
|
|
@@ -126,6 +244,15 @@ var browserPlugin = (options = {}) => {
|
|
|
126
244
|
type: "action:result",
|
|
127
245
|
data: { action: "browser_click", toolCallId, result: { success: true } }
|
|
128
246
|
};
|
|
247
|
+
yield {
|
|
248
|
+
type: "ui",
|
|
249
|
+
data: {
|
|
250
|
+
type: "text",
|
|
251
|
+
props: {
|
|
252
|
+
value: `Clicked: ${selector}`
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
};
|
|
129
256
|
} catch (error) {
|
|
130
257
|
yield {
|
|
131
258
|
type: "action:result",
|
|
@@ -135,6 +262,15 @@ var browserPlugin = (options = {}) => {
|
|
|
135
262
|
});
|
|
136
263
|
builder.on("action:browser_type", async function* (event) {
|
|
137
264
|
const { selector, text, delay, toolCallId } = event.data;
|
|
265
|
+
yield {
|
|
266
|
+
type: "ui",
|
|
267
|
+
data: {
|
|
268
|
+
type: "text",
|
|
269
|
+
props: {
|
|
270
|
+
value: `Typing: ${selector}`
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
};
|
|
138
274
|
try {
|
|
139
275
|
const page2 = await ensurePage();
|
|
140
276
|
await page2.type(selector, text, { delay });
|
|
@@ -142,15 +278,42 @@ var browserPlugin = (options = {}) => {
|
|
|
142
278
|
type: "action:result",
|
|
143
279
|
data: { action: "browser_type", toolCallId, result: { success: true } }
|
|
144
280
|
};
|
|
281
|
+
yield {
|
|
282
|
+
type: "ui",
|
|
283
|
+
data: {
|
|
284
|
+
type: "text",
|
|
285
|
+
props: {
|
|
286
|
+
value: `Typed: ${selector}`
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
};
|
|
145
290
|
} catch (error) {
|
|
146
291
|
yield {
|
|
147
292
|
type: "action:result",
|
|
148
293
|
data: { action: "browser_type", toolCallId, result: { error: error.message } }
|
|
149
294
|
};
|
|
295
|
+
yield {
|
|
296
|
+
type: "ui",
|
|
297
|
+
data: {
|
|
298
|
+
type: "text",
|
|
299
|
+
props: {
|
|
300
|
+
value: `Typed: ${selector}`
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
};
|
|
150
304
|
}
|
|
151
305
|
});
|
|
152
306
|
builder.on("action:browser_getContent", async function* (event) {
|
|
153
307
|
const { format, toolCallId } = event.data;
|
|
308
|
+
yield {
|
|
309
|
+
type: "ui",
|
|
310
|
+
data: {
|
|
311
|
+
type: "text",
|
|
312
|
+
props: {
|
|
313
|
+
value: `Getting content: ${format}`
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
};
|
|
154
317
|
try {
|
|
155
318
|
const page2 = await ensurePage();
|
|
156
319
|
let content = "";
|
|
@@ -163,13 +326,38 @@ var browserPlugin = (options = {}) => {
|
|
|
163
326
|
}
|
|
164
327
|
yield {
|
|
165
328
|
type: "action:result",
|
|
166
|
-
data: {
|
|
329
|
+
data: {
|
|
330
|
+
action: "browser_getContent",
|
|
331
|
+
toolCallId,
|
|
332
|
+
result: {
|
|
333
|
+
success: true,
|
|
334
|
+
content: truncate(content, maxContentLength)
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
yield {
|
|
339
|
+
type: "ui",
|
|
340
|
+
data: {
|
|
341
|
+
type: "text",
|
|
342
|
+
props: {
|
|
343
|
+
value: `Content: ${content}`
|
|
344
|
+
}
|
|
345
|
+
}
|
|
167
346
|
};
|
|
168
347
|
} catch (error) {
|
|
169
348
|
yield {
|
|
170
349
|
type: "action:result",
|
|
171
350
|
data: { action: "browser_getContent", toolCallId, result: { error: error.message } }
|
|
172
351
|
};
|
|
352
|
+
yield {
|
|
353
|
+
type: "ui",
|
|
354
|
+
data: {
|
|
355
|
+
type: "text",
|
|
356
|
+
props: {
|
|
357
|
+
value: `Content failed: ${error.message}`
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
};
|
|
173
361
|
}
|
|
174
362
|
});
|
|
175
363
|
builder.on("action:browser_evaluate", async function* (event) {
|
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 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 {\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 // 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 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 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 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"]}
|