@melony/plugin-browser 0.1.2 → 0.1.3
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 +16 -17
- package/dist/index.js +259 -445
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -50,28 +50,27 @@ declare const browserToolDefinitions: {
|
|
|
50
50
|
script: z.ZodString;
|
|
51
51
|
}, z.core.$strip>;
|
|
52
52
|
};
|
|
53
|
+
browser_openVisible: {
|
|
54
|
+
description: string;
|
|
55
|
+
inputSchema: z.ZodObject<{
|
|
56
|
+
url: z.ZodOptional<z.ZodString>;
|
|
57
|
+
}, z.core.$strip>;
|
|
58
|
+
};
|
|
59
|
+
browser_listTabs: {
|
|
60
|
+
description: string;
|
|
61
|
+
inputSchema: z.ZodObject<{}, z.core.$strip>;
|
|
62
|
+
};
|
|
63
|
+
browser_closeTab: {
|
|
64
|
+
description: string;
|
|
65
|
+
inputSchema: z.ZodObject<{
|
|
66
|
+
index: z.ZodNumber;
|
|
67
|
+
}, z.core.$strip>;
|
|
68
|
+
};
|
|
53
69
|
};
|
|
54
70
|
interface BrowserPluginOptions {
|
|
55
71
|
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
72
|
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
73
|
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
74
|
channel?: string;
|
|
76
75
|
}
|
|
77
76
|
declare const browserPlugin: (options?: BrowserPluginOptions) => MelonyPlugin<any, any>;
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
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();
|
|
6
4
|
var browserToolDefinitions = {
|
|
7
5
|
browser_navigate: {
|
|
8
6
|
description: "Navigate to a URL and wait for the page to load",
|
|
@@ -12,7 +10,7 @@ var browserToolDefinitions = {
|
|
|
12
10
|
})
|
|
13
11
|
},
|
|
14
12
|
browser_screenshot: {
|
|
15
|
-
description: "Take a screenshot of the current page
|
|
13
|
+
description: "Take a screenshot of the current page",
|
|
16
14
|
inputSchema: z.object({
|
|
17
15
|
fullPage: z.boolean().default(false).describe("Whether to take a screenshot of the full scrollable page")
|
|
18
16
|
})
|
|
@@ -42,483 +40,299 @@ var browserToolDefinitions = {
|
|
|
42
40
|
inputSchema: z.object({
|
|
43
41
|
script: z.string().describe("The JavaScript code to execute")
|
|
44
42
|
})
|
|
43
|
+
},
|
|
44
|
+
browser_openVisible: {
|
|
45
|
+
description: "Open the browser in a visible window (non-headless) for manual interaction",
|
|
46
|
+
inputSchema: z.object({
|
|
47
|
+
url: z.string().optional().describe("An optional URL to open immediately")
|
|
48
|
+
})
|
|
49
|
+
},
|
|
50
|
+
browser_listTabs: {
|
|
51
|
+
description: "List all open browser tabs",
|
|
52
|
+
inputSchema: z.object({})
|
|
53
|
+
},
|
|
54
|
+
browser_closeTab: {
|
|
55
|
+
description: "Close a browser tab by its index",
|
|
56
|
+
inputSchema: z.object({
|
|
57
|
+
index: z.number().describe("The index of the tab to close")
|
|
58
|
+
})
|
|
45
59
|
}
|
|
46
60
|
};
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
61
|
+
var BrowserManager = class {
|
|
62
|
+
constructor(options) {
|
|
63
|
+
this.options = options;
|
|
64
|
+
this.browser = null;
|
|
65
|
+
this.context = null;
|
|
66
|
+
this.page = null;
|
|
67
|
+
this.activeHeadless = null;
|
|
68
|
+
this.defaultHeadless = options.headless !== false;
|
|
69
|
+
}
|
|
70
|
+
async ensurePage(forceHeadless) {
|
|
71
|
+
const targetHeadless = forceHeadless !== void 0 ? forceHeadless : this.options.headless ?? this.defaultHeadless;
|
|
72
|
+
const isHealthy = async () => {
|
|
73
|
+
try {
|
|
74
|
+
if (!this.page || this.page.isClosed()) return false;
|
|
75
|
+
if (!this.context) return false;
|
|
76
|
+
await this.page.url();
|
|
77
|
+
return true;
|
|
78
|
+
} catch {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
let currentUrl;
|
|
83
|
+
if ((this.browser || this.context) && this.activeHeadless !== targetHeadless) {
|
|
84
|
+
if (this.page && !this.page.isClosed()) {
|
|
69
85
|
try {
|
|
70
|
-
|
|
71
|
-
|
|
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();
|
|
86
|
+
currentUrl = this.page.url();
|
|
87
|
+
} catch (e) {
|
|
101
88
|
}
|
|
102
89
|
}
|
|
90
|
+
await this.close();
|
|
91
|
+
}
|
|
92
|
+
if (await isHealthy()) return this.page;
|
|
93
|
+
if (this.page || this.context || this.browser) {
|
|
94
|
+
await this.close();
|
|
95
|
+
}
|
|
96
|
+
const { userDataDir, channel } = this.options;
|
|
97
|
+
this.activeHeadless = targetHeadless;
|
|
98
|
+
if (userDataDir) {
|
|
99
|
+
this.context = await chromium.launchPersistentContext(userDataDir, {
|
|
100
|
+
headless: targetHeadless,
|
|
101
|
+
channel,
|
|
102
|
+
viewport: { width: 1280, height: 720 },
|
|
103
|
+
ignoreDefaultArgs: ["--enable-automation"],
|
|
104
|
+
args: [
|
|
105
|
+
"--disable-blink-features=AutomationControlled",
|
|
106
|
+
"--no-sandbox",
|
|
107
|
+
"--disable-infobars",
|
|
108
|
+
"--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"
|
|
109
|
+
]
|
|
110
|
+
});
|
|
111
|
+
this.context.on("close", () => this.context = null);
|
|
112
|
+
this.page = this.context.pages()[0] || await this.context.newPage();
|
|
103
113
|
} else {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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"
|
|
114
|
+
if (!this.browser || !this.browser.isConnected()) {
|
|
115
|
+
this.browser = await chromium.launch({
|
|
116
|
+
headless: targetHeadless,
|
|
117
|
+
channel,
|
|
118
|
+
ignoreDefaultArgs: ["--enable-automation"],
|
|
119
|
+
args: ["--disable-blink-features=AutomationControlled"]
|
|
125
120
|
});
|
|
126
|
-
|
|
121
|
+
this.browser.on("disconnected", () => this.browser = null);
|
|
127
122
|
}
|
|
123
|
+
this.context = await this.browser.newContext({
|
|
124
|
+
viewport: { width: 1280, height: 720 },
|
|
125
|
+
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"
|
|
126
|
+
});
|
|
127
|
+
this.context.on("close", () => this.context = null);
|
|
128
|
+
this.page = await this.context.newPage();
|
|
129
|
+
}
|
|
130
|
+
this.page.on("close", () => this.page = null);
|
|
131
|
+
if (currentUrl && currentUrl !== "about:blank") {
|
|
132
|
+
await this.page.goto(currentUrl).catch(() => {
|
|
133
|
+
});
|
|
128
134
|
}
|
|
129
|
-
return page;
|
|
135
|
+
return this.page;
|
|
136
|
+
}
|
|
137
|
+
async close() {
|
|
138
|
+
try {
|
|
139
|
+
if (this.context) await this.context.close().catch(() => {
|
|
140
|
+
});
|
|
141
|
+
if (this.browser) await this.browser.close().catch(() => {
|
|
142
|
+
});
|
|
143
|
+
} finally {
|
|
144
|
+
this.context = null;
|
|
145
|
+
this.browser = null;
|
|
146
|
+
this.page = null;
|
|
147
|
+
this.activeHeadless = null;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
async getPages() {
|
|
151
|
+
if (!this.context) return this.page ? [this.page] : [];
|
|
152
|
+
return await this.context.pages();
|
|
130
153
|
}
|
|
154
|
+
getActiveHeadless() {
|
|
155
|
+
return this.activeHeadless;
|
|
156
|
+
}
|
|
157
|
+
setHeadless(headless) {
|
|
158
|
+
this.options.headless = headless;
|
|
159
|
+
}
|
|
160
|
+
setPage(page) {
|
|
161
|
+
this.page = page;
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
var browserPlugin = (options = {}) => {
|
|
165
|
+
const manager = new BrowserManager(options);
|
|
166
|
+
const maxContentLength = options.maxContentLength || 1e4;
|
|
131
167
|
return (builder) => {
|
|
132
|
-
async function*
|
|
168
|
+
async function* yieldState(page, options2 = { screenshot: true }) {
|
|
133
169
|
try {
|
|
134
|
-
const url =
|
|
135
|
-
const title = await
|
|
136
|
-
const
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
170
|
+
const url = page.url();
|
|
171
|
+
const title = await page.title();
|
|
172
|
+
const pages = await manager.getPages();
|
|
173
|
+
const tabs = await Promise.all(
|
|
174
|
+
pages.map(async (p, i) => ({
|
|
175
|
+
index: i,
|
|
176
|
+
title: await p.title().catch(() => "Untitled") || "Untitled",
|
|
177
|
+
url: p.url(),
|
|
178
|
+
isActive: p === page
|
|
179
|
+
}))
|
|
180
|
+
);
|
|
181
|
+
let base64;
|
|
182
|
+
if (options2.screenshot) {
|
|
183
|
+
const screenshot = await page.screenshot({ type: "jpeg", quality: 60 });
|
|
184
|
+
base64 = screenshot.toString("base64");
|
|
185
|
+
}
|
|
145
186
|
yield {
|
|
146
187
|
type: "browser:state-update",
|
|
147
|
-
data: {
|
|
148
|
-
url,
|
|
149
|
-
title,
|
|
150
|
-
screenshot: base64,
|
|
151
|
-
tabCount: pages.length,
|
|
152
|
-
tabs
|
|
153
|
-
}
|
|
188
|
+
data: { url, title, screenshot: base64, tabCount: pages.length, tabs }
|
|
154
189
|
};
|
|
190
|
+
const children = [];
|
|
191
|
+
if (base64) {
|
|
192
|
+
children.push({
|
|
193
|
+
type: "image",
|
|
194
|
+
props: { src: `data:image/jpeg;base64,${base64}`, alt: "Screenshot", width: "full" }
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
children.push({
|
|
198
|
+
type: "row",
|
|
199
|
+
props: { justify: "between", padding: "sm" },
|
|
200
|
+
children: [
|
|
201
|
+
{
|
|
202
|
+
type: "text",
|
|
203
|
+
props: { value: `Tabs: ${pages.length}`, size: "xs", color: "muted" }
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
type: "row",
|
|
207
|
+
props: { gap: "xs" },
|
|
208
|
+
children: [
|
|
209
|
+
{
|
|
210
|
+
type: "button",
|
|
211
|
+
props: {
|
|
212
|
+
label: "Open Visible",
|
|
213
|
+
size: "xs",
|
|
214
|
+
variant: "outline",
|
|
215
|
+
onClickAction: { type: "browser:show" }
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
type: "button",
|
|
220
|
+
props: {
|
|
221
|
+
label: "Cleanup",
|
|
222
|
+
size: "xs",
|
|
223
|
+
variant: "outline",
|
|
224
|
+
onClickAction: { type: "browser:cleanup" }
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
]
|
|
228
|
+
}
|
|
229
|
+
]
|
|
230
|
+
});
|
|
155
231
|
yield {
|
|
156
232
|
type: "ui",
|
|
157
233
|
data: {
|
|
158
234
|
type: "card",
|
|
159
|
-
props: {
|
|
160
|
-
|
|
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
|
-
]
|
|
235
|
+
props: { title: title || "Browser View", subtitle: url, padding: "none" },
|
|
236
|
+
children
|
|
193
237
|
}
|
|
194
238
|
};
|
|
195
239
|
} catch (e) {
|
|
196
|
-
console.error("
|
|
240
|
+
console.error("Browser state update failed", e);
|
|
197
241
|
}
|
|
198
242
|
}
|
|
199
|
-
builder.on("browser:
|
|
200
|
-
|
|
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
|
-
}
|
|
243
|
+
builder.on("browser:show", async function* () {
|
|
244
|
+
yield* yieldState(await manager.ensurePage(false), { screenshot: false });
|
|
241
245
|
});
|
|
242
|
-
builder.on("
|
|
243
|
-
const
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
};
|
|
246
|
+
builder.on("browser:cleanup", async function* () {
|
|
247
|
+
const page = await manager.ensurePage();
|
|
248
|
+
const pages = await manager.getPages();
|
|
249
|
+
for (const p of pages) {
|
|
250
|
+
if (p !== page && !p.isClosed()) await p.close().catch(() => {
|
|
251
|
+
});
|
|
269
252
|
}
|
|
253
|
+
yield* yieldState(page, { screenshot: false });
|
|
270
254
|
});
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
255
|
+
const handleAction = (name, handler, options2 = { screenshot: true }) => {
|
|
256
|
+
builder.on(`action:${name}`, async function* (event) {
|
|
257
|
+
const { toolCallId, ...data } = event.data;
|
|
258
|
+
try {
|
|
259
|
+
const page = await manager.ensurePage();
|
|
260
|
+
const result = await handler(page, data);
|
|
261
|
+
yield* yieldState(page, options2);
|
|
262
|
+
yield {
|
|
263
|
+
type: "action:result",
|
|
264
|
+
data: { action: name, toolCallId, result: { success: true, ...result } }
|
|
265
|
+
};
|
|
266
|
+
} catch (error) {
|
|
267
|
+
yield {
|
|
268
|
+
type: "action:result",
|
|
269
|
+
data: { action: name, toolCallId, result: { error: error.message } }
|
|
270
|
+
};
|
|
280
271
|
}
|
|
281
|
-
};
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
yield {
|
|
287
|
-
type: "ui",
|
|
288
|
-
data: {
|
|
289
|
-
type: "text",
|
|
290
|
-
props: {
|
|
291
|
-
value: `Navigated to: ${page2.url()}`
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
};
|
|
295
|
-
yield {
|
|
296
|
-
type: "action:result",
|
|
297
|
-
data: {
|
|
298
|
-
action: "browser_navigate",
|
|
299
|
-
toolCallId,
|
|
300
|
-
result: { success: true, url: page2.url(), title: await page2.title() }
|
|
301
|
-
}
|
|
302
|
-
};
|
|
303
|
-
} catch (error) {
|
|
304
|
-
yield {
|
|
305
|
-
type: "action:result",
|
|
306
|
-
data: { action: "browser_navigate", toolCallId, result: { error: error.message } }
|
|
307
|
-
};
|
|
308
|
-
yield {
|
|
309
|
-
type: "ui",
|
|
310
|
-
data: {
|
|
311
|
-
type: "text",
|
|
312
|
-
props: {
|
|
313
|
-
value: `Navigation failed: ${error.message}`
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
};
|
|
317
|
-
}
|
|
272
|
+
});
|
|
273
|
+
};
|
|
274
|
+
handleAction("browser_navigate", async (page, { url, waitUntil }) => {
|
|
275
|
+
await page.goto(url, { waitUntil });
|
|
276
|
+
return { url: page.url(), title: await page.title() };
|
|
318
277
|
});
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
type: "ui",
|
|
323
|
-
data: {
|
|
324
|
-
type: "text",
|
|
325
|
-
props: {
|
|
326
|
-
value: `Taking screenshot`
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
};
|
|
330
|
-
try {
|
|
331
|
-
const page2 = await ensurePage();
|
|
332
|
-
const buffer = await page2.screenshot({ fullPage, type: "png" });
|
|
333
|
-
const base64 = buffer.toString("base64");
|
|
334
|
-
yield* yieldStateUpdate(page2);
|
|
335
|
-
yield {
|
|
336
|
-
type: "action:result",
|
|
337
|
-
data: {
|
|
338
|
-
action: "browser_screenshot",
|
|
339
|
-
toolCallId,
|
|
340
|
-
result: { success: true, format: "png", message: "Screenshot taken successfully" }
|
|
341
|
-
}
|
|
342
|
-
};
|
|
343
|
-
yield {
|
|
344
|
-
type: "ui",
|
|
345
|
-
data: {
|
|
346
|
-
type: "image",
|
|
347
|
-
props: {
|
|
348
|
-
src: `data:image/png;base64,${base64}`,
|
|
349
|
-
alt: "Browser Screenshot",
|
|
350
|
-
width: "full"
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
};
|
|
354
|
-
} catch (error) {
|
|
355
|
-
yield {
|
|
356
|
-
type: "action:result",
|
|
357
|
-
data: { action: "browser_screenshot", toolCallId, result: { error: error.message } }
|
|
358
|
-
};
|
|
359
|
-
yield {
|
|
360
|
-
type: "ui",
|
|
361
|
-
data: {
|
|
362
|
-
type: "text",
|
|
363
|
-
props: {
|
|
364
|
-
value: `Screenshot failed: ${error.message}`
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
};
|
|
368
|
-
}
|
|
278
|
+
handleAction("browser_screenshot", async (page, { fullPage }) => {
|
|
279
|
+
await page.screenshot({ fullPage, type: "png" });
|
|
280
|
+
return { message: "Screenshot taken" };
|
|
369
281
|
});
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
type: "ui",
|
|
374
|
-
data: {
|
|
375
|
-
type: "text",
|
|
376
|
-
props: {
|
|
377
|
-
value: `Clicking: ${selector}`
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
};
|
|
381
|
-
try {
|
|
382
|
-
const page2 = await ensurePage();
|
|
383
|
-
await page2.click(selector);
|
|
384
|
-
yield* yieldStateUpdate(page2);
|
|
385
|
-
yield {
|
|
386
|
-
type: "action:result",
|
|
387
|
-
data: { action: "browser_click", toolCallId, result: { success: true } }
|
|
388
|
-
};
|
|
389
|
-
yield {
|
|
390
|
-
type: "ui",
|
|
391
|
-
data: {
|
|
392
|
-
type: "text",
|
|
393
|
-
props: {
|
|
394
|
-
value: `Clicked: ${selector}`
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
};
|
|
398
|
-
} catch (error) {
|
|
399
|
-
yield {
|
|
400
|
-
type: "action:result",
|
|
401
|
-
data: { action: "browser_click", toolCallId, result: { error: error.message } }
|
|
402
|
-
};
|
|
403
|
-
}
|
|
282
|
+
handleAction("browser_click", async (page, { selector }) => {
|
|
283
|
+
await page.click(selector);
|
|
284
|
+
return {};
|
|
404
285
|
});
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
type: "ui",
|
|
409
|
-
data: {
|
|
410
|
-
type: "text",
|
|
411
|
-
props: {
|
|
412
|
-
value: `Typing: ${selector}`
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
};
|
|
416
|
-
try {
|
|
417
|
-
const page2 = await ensurePage();
|
|
418
|
-
await page2.type(selector, text, { delay });
|
|
419
|
-
yield* yieldStateUpdate(page2);
|
|
420
|
-
yield {
|
|
421
|
-
type: "action:result",
|
|
422
|
-
data: { action: "browser_type", toolCallId, result: { success: true } }
|
|
423
|
-
};
|
|
424
|
-
yield {
|
|
425
|
-
type: "ui",
|
|
426
|
-
data: {
|
|
427
|
-
type: "text",
|
|
428
|
-
props: {
|
|
429
|
-
value: `Typed: ${selector}`
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
};
|
|
433
|
-
} catch (error) {
|
|
434
|
-
yield {
|
|
435
|
-
type: "action:result",
|
|
436
|
-
data: { action: "browser_type", toolCallId, result: { error: error.message } }
|
|
437
|
-
};
|
|
438
|
-
yield {
|
|
439
|
-
type: "ui",
|
|
440
|
-
data: {
|
|
441
|
-
type: "text",
|
|
442
|
-
props: {
|
|
443
|
-
value: `Typed: ${selector}`
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
};
|
|
447
|
-
}
|
|
286
|
+
handleAction("browser_type", async (page, { selector, text, delay }) => {
|
|
287
|
+
await page.type(selector, text, { delay });
|
|
288
|
+
return {};
|
|
448
289
|
});
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
try {
|
|
461
|
-
const page2 = await ensurePage();
|
|
462
|
-
let content = "";
|
|
463
|
-
if (format === "html") {
|
|
464
|
-
content = await page2.content();
|
|
465
|
-
} else if (format === "text") {
|
|
466
|
-
content = await page2.evaluate(() => document.body.innerText);
|
|
467
|
-
} else if (format === "markdown") {
|
|
468
|
-
content = await page2.evaluate(() => document.body.innerText);
|
|
469
|
-
}
|
|
470
|
-
yield {
|
|
471
|
-
type: "action:result",
|
|
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
|
-
}
|
|
489
|
-
};
|
|
490
|
-
} catch (error) {
|
|
491
|
-
yield {
|
|
492
|
-
type: "action:result",
|
|
493
|
-
data: { action: "browser_getContent", toolCallId, result: { error: error.message } }
|
|
494
|
-
};
|
|
495
|
-
yield {
|
|
496
|
-
type: "ui",
|
|
497
|
-
data: {
|
|
498
|
-
type: "text",
|
|
499
|
-
props: {
|
|
500
|
-
value: `Content failed: ${error.message}`
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
};
|
|
290
|
+
handleAction("browser_getContent", async (page, { format }) => {
|
|
291
|
+
let content = "";
|
|
292
|
+
if (format === "html") content = await page.content();
|
|
293
|
+
else content = await page.evaluate(() => document.body.innerText);
|
|
294
|
+
if (content.length > maxContentLength) {
|
|
295
|
+
const half = Math.floor(maxContentLength / 2);
|
|
296
|
+
content = `${content.slice(0, half)}
|
|
297
|
+
|
|
298
|
+
[... Truncated ...]
|
|
299
|
+
|
|
300
|
+
${content.slice(-half)}`;
|
|
504
301
|
}
|
|
302
|
+
return { content };
|
|
303
|
+
}, { screenshot: false });
|
|
304
|
+
handleAction("browser_evaluate", async (page, { script }) => {
|
|
305
|
+
const result = await page.evaluate(script);
|
|
306
|
+
return { result };
|
|
505
307
|
});
|
|
506
|
-
|
|
507
|
-
const
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
308
|
+
handleAction("browser_listTabs", async (page) => {
|
|
309
|
+
const pages = await manager.getPages();
|
|
310
|
+
const tabs = await Promise.all(
|
|
311
|
+
pages.map(async (p, i) => ({
|
|
312
|
+
index: i,
|
|
313
|
+
title: await p.title().catch(() => "Untitled") || "Untitled",
|
|
314
|
+
url: p.url(),
|
|
315
|
+
isActive: p === page
|
|
316
|
+
}))
|
|
317
|
+
);
|
|
318
|
+
return { tabs };
|
|
319
|
+
}, { screenshot: false });
|
|
320
|
+
handleAction("browser_closeTab", async (page, { index }) => {
|
|
321
|
+
const pages = await manager.getPages();
|
|
322
|
+
if (index >= 0 && index < pages.length) {
|
|
323
|
+
const tab = pages[index];
|
|
324
|
+
if (tab === page) manager.setPage(null);
|
|
325
|
+
await tab.close();
|
|
521
326
|
}
|
|
327
|
+
return {};
|
|
328
|
+
}, { screenshot: false });
|
|
329
|
+
handleAction("browser_openVisible", async (_page, { url }) => {
|
|
330
|
+
const p = await manager.ensurePage(false);
|
|
331
|
+
if (url) await p.goto(url);
|
|
332
|
+
return { message: "Browser is now visible" };
|
|
333
|
+
}, { screenshot: false });
|
|
334
|
+
builder.on("browser:poll_state", async function* () {
|
|
335
|
+
yield* yieldState(await manager.ensurePage(manager.getActiveHeadless() ?? void 0));
|
|
522
336
|
});
|
|
523
337
|
};
|
|
524
338
|
};
|
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, 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"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { MelonyPlugin, Event } from \"melony\";\nimport { z } from \"zod\";\nimport { chromium, Browser, BrowserContext, 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\",\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 browser_openVisible: {\n description: \"Open the browser in a visible window (non-headless) for manual interaction\",\n inputSchema: z.object({\n url: z.string().optional().describe(\"An optional URL to open immediately\"),\n }),\n },\n browser_listTabs: {\n description: \"List all open browser tabs\",\n inputSchema: z.object({}),\n },\n browser_closeTab: {\n description: \"Close a browser tab by its index\",\n inputSchema: z.object({\n index: z.number().describe(\"The index of the tab to close\"),\n }),\n },\n};\n\nexport interface BrowserPluginOptions {\n headless?: boolean;\n maxContentLength?: number;\n userDataDir?: string;\n channel?: string;\n}\n\n/**\n * Manages the Playwright browser lifecycle and provides high-level helpers\n */\nclass BrowserManager {\n private browser: Browser | null = null;\n private context: BrowserContext | null = null;\n private page: Page | null = null;\n private defaultHeadless: boolean;\n private activeHeadless: boolean | null = null;\n\n constructor(private options: BrowserPluginOptions) {\n this.defaultHeadless = options.headless !== false;\n }\n\n async ensurePage(forceHeadless?: boolean): Promise<Page> {\n const targetHeadless = forceHeadless !== undefined ? forceHeadless : (this.options.headless ?? this.defaultHeadless);\n\n const isHealthy = async () => {\n try {\n if (!this.page || this.page.isClosed()) return false;\n if (!this.context) return false;\n await this.page.url();\n return true;\n } catch {\n return false;\n }\n };\n\n let currentUrl: string | undefined;\n\n // If current browser exists but is in the wrong mode, close it to switch\n if ((this.browser || this.context) && this.activeHeadless !== targetHeadless) {\n if (this.page && !this.page.isClosed()) {\n try {\n currentUrl = this.page.url();\n } catch (e) {\n // ignore\n }\n }\n await this.close();\n }\n\n if (await isHealthy()) return this.page!;\n\n // Cleanup before launch if unhealthy\n if (this.page || this.context || this.browser) {\n await this.close();\n }\n\n const { userDataDir, channel } = this.options;\n this.activeHeadless = targetHeadless;\n\n if (userDataDir) {\n this.context = await chromium.launchPersistentContext(userDataDir, {\n headless: targetHeadless,\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 \"--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 this.context.on(\"close\", () => (this.context = null));\n this.page = this.context.pages()[0] || (await this.context.newPage());\n } else {\n if (!this.browser || !this.browser.isConnected()) {\n this.browser = await chromium.launch({\n headless: targetHeadless,\n channel,\n ignoreDefaultArgs: [\"--enable-automation\"],\n args: [\"--disable-blink-features=AutomationControlled\"],\n });\n this.browser.on(\"disconnected\", () => (this.browser = null));\n }\n\n this.context = await this.browser.newContext({\n viewport: { width: 1280, height: 720 },\n userAgent:\n \"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 this.context.on(\"close\", () => (this.context = null));\n this.page = await this.context.newPage();\n }\n\n this.page.on(\"close\", () => (this.page = null));\n\n if (currentUrl && currentUrl !== \"about:blank\") {\n await this.page.goto(currentUrl).catch(() => {});\n }\n\n return this.page!;\n }\n\n async close() {\n try {\n if (this.context) await this.context.close().catch(() => {});\n if (this.browser) await this.browser.close().catch(() => {});\n } finally {\n this.context = null;\n this.browser = null;\n this.page = null;\n this.activeHeadless = null;\n }\n }\n\n async getPages() {\n if (!this.context) return this.page ? [this.page] : [];\n return await this.context.pages();\n }\n\n getActiveHeadless() {\n return this.activeHeadless;\n }\n\n setHeadless(headless: boolean) {\n this.options.headless = headless;\n }\n\n setPage(page: Page | null) {\n this.page = page;\n }\n}\n\nexport const browserPlugin = (options: BrowserPluginOptions = {}): MelonyPlugin<any, any> => {\n const manager = new BrowserManager(options);\n const maxContentLength = options.maxContentLength || 10000;\n\n return (builder) => {\n async function* yieldState(page: Page, options: { screenshot?: boolean } = { screenshot: true }) {\n try {\n const url = page.url();\n const title = await page.title();\n const pages = await manager.getPages();\n const tabs = await Promise.all(\n pages.map(async (p, i) => ({\n index: i,\n title: (await p.title().catch(() => \"Untitled\")) || \"Untitled\",\n url: p.url(),\n isActive: p === page,\n }))\n );\n\n let base64: string | undefined;\n if (options.screenshot) {\n const screenshot = await page.screenshot({ type: \"jpeg\", quality: 60 });\n base64 = screenshot.toString(\"base64\");\n }\n\n yield {\n type: \"browser:state-update\",\n data: { url, title, screenshot: base64, tabCount: pages.length, tabs },\n };\n\n const children: any[] = [];\n if (base64) {\n children.push({\n type: \"image\",\n props: { src: `data:image/jpeg;base64,${base64}`, alt: \"Screenshot\", width: \"full\" },\n });\n }\n\n children.push({\n type: \"row\",\n props: { justify: \"between\", padding: \"sm\" },\n children: [\n {\n type: \"text\",\n props: { value: `Tabs: ${pages.length}`, size: \"xs\", color: \"muted\" },\n },\n {\n type: \"row\",\n props: { gap: \"xs\" },\n children: [\n {\n type: \"button\",\n props: {\n label: \"Open Visible\",\n size: \"xs\",\n variant: \"outline\",\n onClickAction: { type: \"browser:show\" },\n },\n },\n {\n type: \"button\",\n props: {\n label: \"Cleanup\",\n size: \"xs\",\n variant: \"outline\",\n onClickAction: { type: \"browser:cleanup\" },\n },\n },\n ],\n },\n ],\n });\n\n yield {\n type: \"ui\",\n data: {\n type: \"card\",\n props: { title: title || \"Browser View\", subtitle: url, padding: \"none\" },\n children,\n },\n };\n } catch (e) {\n console.error(\"Browser state update failed\", e);\n }\n }\n\n builder.on(\"browser:show\", async function* () {\n yield* yieldState(await manager.ensurePage(false), { screenshot: false });\n });\n\n builder.on(\"browser:cleanup\", async function* () {\n const page = await manager.ensurePage();\n const pages = await manager.getPages();\n for (const p of pages) {\n if (p !== page && !p.isClosed()) await p.close().catch(() => {});\n }\n yield* yieldState(page, { screenshot: false });\n });\n\n const handleAction = (\n name: string,\n handler: (page: Page, data: any) => Promise<any>,\n options: { screenshot?: boolean } = { screenshot: true }\n ) => {\n builder.on(`action:${name}` as any, async function* (event) {\n const { toolCallId, ...data } = event.data;\n try {\n const page = await manager.ensurePage();\n const result = await handler(page, data);\n yield* yieldState(page, options);\n yield {\n type: \"action:result\",\n data: { action: name, toolCallId, result: { success: true, ...result } },\n };\n } catch (error: any) {\n yield {\n type: \"action:result\",\n data: { action: name, toolCallId, result: { error: error.message } },\n };\n }\n });\n };\n\n handleAction(\"browser_navigate\", async (page, { url, waitUntil }) => {\n await page.goto(url, { waitUntil });\n return { url: page.url(), title: await page.title() };\n });\n\n handleAction(\"browser_screenshot\", async (page, { fullPage }) => {\n await page.screenshot({ fullPage, type: \"png\" });\n return { message: \"Screenshot taken\" };\n });\n\n handleAction(\"browser_click\", async (page, { selector }) => {\n await page.click(selector);\n return {};\n });\n\n handleAction(\"browser_type\", async (page, { selector, text, delay }) => {\n await page.type(selector, text, { delay });\n return {};\n });\n\n handleAction(\"browser_getContent\", async (page, { format }) => {\n let content = \"\";\n if (format === \"html\") content = await page.content();\n else content = await page.evaluate(() => document.body.innerText);\n\n if (content.length > maxContentLength) {\n const half = Math.floor(maxContentLength / 2);\n content = `${content.slice(0, half)}\\n\\n[... Truncated ...]\\n\\n${content.slice(-half)}`;\n }\n return { content };\n }, { screenshot: false });\n\n handleAction(\"browser_evaluate\", async (page, { script }) => {\n const result = await page.evaluate(script);\n return { result };\n });\n\n handleAction(\"browser_listTabs\", async (page) => {\n const pages = await manager.getPages();\n const tabs = await Promise.all(\n pages.map(async (p, i) => ({\n index: i,\n title: (await p.title().catch(() => \"Untitled\")) || \"Untitled\",\n url: p.url(),\n isActive: p === page,\n }))\n );\n return { tabs };\n }, { screenshot: false });\n\n handleAction(\"browser_closeTab\", async (page, { index }) => {\n const pages = await manager.getPages();\n if (index >= 0 && index < pages.length) {\n const tab = pages[index];\n if (tab === page) manager.setPage(null);\n await tab.close();\n }\n return {};\n }, { screenshot: false });\n\n handleAction(\"browser_openVisible\", async (_page, { url }) => {\n const p = await manager.ensurePage(false);\n if (url) await p.goto(url);\n return { message: \"Browser is now visible\" };\n }, { screenshot: false });\n\n builder.on(\"browser:poll_state\", async function* () {\n yield* yieldState(await manager.ensurePage(manager.getActiveHeadless() ?? undefined));\n });\n };\n};\n"],"mappings":";AACA,SAAS,SAAS;AAClB,SAAS,gBAA+C;AAEjD,IAAM,yBAAyB;AAAA,EACpC,kBAAkB;AAAA,IAChB,aAAa;AAAA,IACb,aAAa,EAAE,OAAO;AAAA,MACpB,KAAK,EAAE,OAAO,EAAE,SAAS,wBAAwB;AAAA,MACjD,WAAW,EAAE,KAAK,CAAC,QAAQ,oBAAoB,eAAe,QAAQ,CAAC,EAAE,QAAQ,MAAM,EAAE,SAAS,sCAAsC;AAAA,IAC1I,CAAC;AAAA,EACH;AAAA,EACA,oBAAoB;AAAA,IAClB,aAAa;AAAA,IACb,aAAa,EAAE,OAAO;AAAA,MACpB,UAAU,EAAE,QAAQ,EAAE,QAAQ,KAAK,EAAE,SAAS,0DAA0D;AAAA,IAC1G,CAAC;AAAA,EACH;AAAA,EACA,eAAe;AAAA,IACb,aAAa;AAAA,IACb,aAAa,EAAE,OAAO;AAAA,MACpB,UAAU,EAAE,OAAO,EAAE,SAAS,+CAA+C;AAAA,IAC/E,CAAC;AAAA,EACH;AAAA,EACA,cAAc;AAAA,IACZ,aAAa;AAAA,IACb,aAAa,EAAE,OAAO;AAAA,MACpB,UAAU,EAAE,OAAO,EAAE,SAAS,mDAAmD;AAAA,MACjF,MAAM,EAAE,OAAO,EAAE,SAAS,kBAAkB;AAAA,MAC5C,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,0CAA0C;AAAA,IAClF,CAAC;AAAA,EACH;AAAA,EACA,oBAAoB;AAAA,IAClB,aAAa;AAAA,IACb,aAAa,EAAE,OAAO;AAAA,MACpB,QAAQ,EAAE,KAAK,CAAC,QAAQ,QAAQ,UAAU,CAAC,EAAE,QAAQ,MAAM,EAAE,SAAS,qCAAqC;AAAA,IAC7G,CAAC;AAAA,EACH;AAAA,EACA,kBAAkB;AAAA,IAChB,aAAa;AAAA,IACb,aAAa,EAAE,OAAO;AAAA,MACpB,QAAQ,EAAE,OAAO,EAAE,SAAS,gCAAgC;AAAA,IAC9D,CAAC;AAAA,EACH;AAAA,EACA,qBAAqB;AAAA,IACnB,aAAa;AAAA,IACb,aAAa,EAAE,OAAO;AAAA,MACpB,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,qCAAqC;AAAA,IAC3E,CAAC;AAAA,EACH;AAAA,EACA,kBAAkB;AAAA,IAChB,aAAa;AAAA,IACb,aAAa,EAAE,OAAO,CAAC,CAAC;AAAA,EAC1B;AAAA,EACA,kBAAkB;AAAA,IAChB,aAAa;AAAA,IACb,aAAa,EAAE,OAAO;AAAA,MACpB,OAAO,EAAE,OAAO,EAAE,SAAS,+BAA+B;AAAA,IAC5D,CAAC;AAAA,EACH;AACF;AAYA,IAAM,iBAAN,MAAqB;AAAA,EAOnB,YAAoB,SAA+B;AAA/B;AANpB,SAAQ,UAA0B;AAClC,SAAQ,UAAiC;AACzC,SAAQ,OAAoB;AAE5B,SAAQ,iBAAiC;AAGvC,SAAK,kBAAkB,QAAQ,aAAa;AAAA,EAC9C;AAAA,EAEA,MAAM,WAAW,eAAwC;AACvD,UAAM,iBAAiB,kBAAkB,SAAY,gBAAiB,KAAK,QAAQ,YAAY,KAAK;AAEpG,UAAM,YAAY,YAAY;AAC5B,UAAI;AACF,YAAI,CAAC,KAAK,QAAQ,KAAK,KAAK,SAAS,EAAG,QAAO;AAC/C,YAAI,CAAC,KAAK,QAAS,QAAO;AAC1B,cAAM,KAAK,KAAK,IAAI;AACpB,eAAO;AAAA,MACT,QAAQ;AACN,eAAO;AAAA,MACT;AAAA,IACF;AAEA,QAAI;AAGJ,SAAK,KAAK,WAAW,KAAK,YAAY,KAAK,mBAAmB,gBAAgB;AAC5E,UAAI,KAAK,QAAQ,CAAC,KAAK,KAAK,SAAS,GAAG;AACtC,YAAI;AACF,uBAAa,KAAK,KAAK,IAAI;AAAA,QAC7B,SAAS,GAAG;AAAA,QAEZ;AAAA,MACF;AACA,YAAM,KAAK,MAAM;AAAA,IACnB;AAEA,QAAI,MAAM,UAAU,EAAG,QAAO,KAAK;AAGnC,QAAI,KAAK,QAAQ,KAAK,WAAW,KAAK,SAAS;AAC7C,YAAM,KAAK,MAAM;AAAA,IACnB;AAEA,UAAM,EAAE,aAAa,QAAQ,IAAI,KAAK;AACtC,SAAK,iBAAiB;AAEtB,QAAI,aAAa;AACf,WAAK,UAAU,MAAM,SAAS,wBAAwB,aAAa;AAAA,QACjE,UAAU;AAAA,QACV;AAAA,QACA,UAAU,EAAE,OAAO,MAAM,QAAQ,IAAI;AAAA,QACrC,mBAAmB,CAAC,qBAAqB;AAAA,QACzC,MAAM;AAAA,UACJ;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MACF,CAAC;AACD,WAAK,QAAQ,GAAG,SAAS,MAAO,KAAK,UAAU,IAAK;AACpD,WAAK,OAAO,KAAK,QAAQ,MAAM,EAAE,CAAC,KAAM,MAAM,KAAK,QAAQ,QAAQ;AAAA,IACrE,OAAO;AACL,UAAI,CAAC,KAAK,WAAW,CAAC,KAAK,QAAQ,YAAY,GAAG;AAChD,aAAK,UAAU,MAAM,SAAS,OAAO;AAAA,UACnC,UAAU;AAAA,UACV;AAAA,UACA,mBAAmB,CAAC,qBAAqB;AAAA,UACzC,MAAM,CAAC,+CAA+C;AAAA,QACxD,CAAC;AACD,aAAK,QAAQ,GAAG,gBAAgB,MAAO,KAAK,UAAU,IAAK;AAAA,MAC7D;AAEA,WAAK,UAAU,MAAM,KAAK,QAAQ,WAAW;AAAA,QAC3C,UAAU,EAAE,OAAO,MAAM,QAAQ,IAAI;AAAA,QACrC,WACE;AAAA,MACJ,CAAC;AACD,WAAK,QAAQ,GAAG,SAAS,MAAO,KAAK,UAAU,IAAK;AACpD,WAAK,OAAO,MAAM,KAAK,QAAQ,QAAQ;AAAA,IACzC;AAEA,SAAK,KAAK,GAAG,SAAS,MAAO,KAAK,OAAO,IAAK;AAE9C,QAAI,cAAc,eAAe,eAAe;AAC9C,YAAM,KAAK,KAAK,KAAK,UAAU,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IACjD;AAEA,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,QAAQ;AACZ,QAAI;AACF,UAAI,KAAK,QAAS,OAAM,KAAK,QAAQ,MAAM,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAC3D,UAAI,KAAK,QAAS,OAAM,KAAK,QAAQ,MAAM,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IAC7D,UAAE;AACA,WAAK,UAAU;AACf,WAAK,UAAU;AACf,WAAK,OAAO;AACZ,WAAK,iBAAiB;AAAA,IACxB;AAAA,EACF;AAAA,EAEA,MAAM,WAAW;AACf,QAAI,CAAC,KAAK,QAAS,QAAO,KAAK,OAAO,CAAC,KAAK,IAAI,IAAI,CAAC;AACrD,WAAO,MAAM,KAAK,QAAQ,MAAM;AAAA,EAClC;AAAA,EAEA,oBAAoB;AAClB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,YAAY,UAAmB;AAC7B,SAAK,QAAQ,WAAW;AAAA,EAC1B;AAAA,EAEA,QAAQ,MAAmB;AACzB,SAAK,OAAO;AAAA,EACd;AACF;AAEO,IAAM,gBAAgB,CAAC,UAAgC,CAAC,MAA8B;AAC3F,QAAM,UAAU,IAAI,eAAe,OAAO;AAC1C,QAAM,mBAAmB,QAAQ,oBAAoB;AAErD,SAAO,CAAC,YAAY;AAClB,oBAAgB,WAAW,MAAYA,WAAoC,EAAE,YAAY,KAAK,GAAG;AAC/F,UAAI;AACF,cAAM,MAAM,KAAK,IAAI;AACrB,cAAM,QAAQ,MAAM,KAAK,MAAM;AAC/B,cAAM,QAAQ,MAAM,QAAQ,SAAS;AACrC,cAAM,OAAO,MAAM,QAAQ;AAAA,UACzB,MAAM,IAAI,OAAO,GAAG,OAAO;AAAA,YACzB,OAAO;AAAA,YACP,OAAQ,MAAM,EAAE,MAAM,EAAE,MAAM,MAAM,UAAU,KAAM;AAAA,YACpD,KAAK,EAAE,IAAI;AAAA,YACX,UAAU,MAAM;AAAA,UAClB,EAAE;AAAA,QACJ;AAEA,YAAI;AACJ,YAAIA,SAAQ,YAAY;AACtB,gBAAM,aAAa,MAAM,KAAK,WAAW,EAAE,MAAM,QAAQ,SAAS,GAAG,CAAC;AACtE,mBAAS,WAAW,SAAS,QAAQ;AAAA,QACvC;AAEA,cAAM;AAAA,UACJ,MAAM;AAAA,UACN,MAAM,EAAE,KAAK,OAAO,YAAY,QAAQ,UAAU,MAAM,QAAQ,KAAK;AAAA,QACvE;AAEA,cAAM,WAAkB,CAAC;AACzB,YAAI,QAAQ;AACV,mBAAS,KAAK;AAAA,YACZ,MAAM;AAAA,YACN,OAAO,EAAE,KAAK,0BAA0B,MAAM,IAAI,KAAK,cAAc,OAAO,OAAO;AAAA,UACrF,CAAC;AAAA,QACH;AAEA,iBAAS,KAAK;AAAA,UACZ,MAAM;AAAA,UACN,OAAO,EAAE,SAAS,WAAW,SAAS,KAAK;AAAA,UAC3C,UAAU;AAAA,YACR;AAAA,cACE,MAAM;AAAA,cACN,OAAO,EAAE,OAAO,SAAS,MAAM,MAAM,IAAI,MAAM,MAAM,OAAO,QAAQ;AAAA,YACtE;AAAA,YACA;AAAA,cACE,MAAM;AAAA,cACN,OAAO,EAAE,KAAK,KAAK;AAAA,cACnB,UAAU;AAAA,gBACR;AAAA,kBACE,MAAM;AAAA,kBACN,OAAO;AAAA,oBACL,OAAO;AAAA,oBACP,MAAM;AAAA,oBACN,SAAS;AAAA,oBACT,eAAe,EAAE,MAAM,eAAe;AAAA,kBACxC;AAAA,gBACF;AAAA,gBACA;AAAA,kBACE,MAAM;AAAA,kBACN,OAAO;AAAA,oBACL,OAAO;AAAA,oBACP,MAAM;AAAA,oBACN,SAAS;AAAA,oBACT,eAAe,EAAE,MAAM,kBAAkB;AAAA,kBAC3C;AAAA,gBACF;AAAA,cACF;AAAA,YACF;AAAA,UACF;AAAA,QACF,CAAC;AAED,cAAM;AAAA,UACJ,MAAM;AAAA,UACN,MAAM;AAAA,YACJ,MAAM;AAAA,YACN,OAAO,EAAE,OAAO,SAAS,gBAAgB,UAAU,KAAK,SAAS,OAAO;AAAA,YACxE;AAAA,UACF;AAAA,QACF;AAAA,MACF,SAAS,GAAG;AACV,gBAAQ,MAAM,+BAA+B,CAAC;AAAA,MAChD;AAAA,IACF;AAEA,YAAQ,GAAG,gBAAgB,mBAAmB;AAC5C,aAAO,WAAW,MAAM,QAAQ,WAAW,KAAK,GAAG,EAAE,YAAY,MAAM,CAAC;AAAA,IAC1E,CAAC;AAED,YAAQ,GAAG,mBAAmB,mBAAmB;AAC/C,YAAM,OAAO,MAAM,QAAQ,WAAW;AACtC,YAAM,QAAQ,MAAM,QAAQ,SAAS;AACrC,iBAAW,KAAK,OAAO;AACrB,YAAI,MAAM,QAAQ,CAAC,EAAE,SAAS,EAAG,OAAM,EAAE,MAAM,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AAAA,MACjE;AACA,aAAO,WAAW,MAAM,EAAE,YAAY,MAAM,CAAC;AAAA,IAC/C,CAAC;AAED,UAAM,eAAe,CACnB,MACA,SACAA,WAAoC,EAAE,YAAY,KAAK,MACpD;AACH,cAAQ,GAAG,UAAU,IAAI,IAAW,iBAAiB,OAAO;AAC1D,cAAM,EAAE,YAAY,GAAG,KAAK,IAAI,MAAM;AACtC,YAAI;AACF,gBAAM,OAAO,MAAM,QAAQ,WAAW;AACtC,gBAAM,SAAS,MAAM,QAAQ,MAAM,IAAI;AACvC,iBAAO,WAAW,MAAMA,QAAO;AAC/B,gBAAM;AAAA,YACJ,MAAM;AAAA,YACN,MAAM,EAAE,QAAQ,MAAM,YAAY,QAAQ,EAAE,SAAS,MAAM,GAAG,OAAO,EAAE;AAAA,UACzE;AAAA,QACF,SAAS,OAAY;AACnB,gBAAM;AAAA,YACJ,MAAM;AAAA,YACN,MAAM,EAAE,QAAQ,MAAM,YAAY,QAAQ,EAAE,OAAO,MAAM,QAAQ,EAAE;AAAA,UACrE;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH;AAEA,iBAAa,oBAAoB,OAAO,MAAM,EAAE,KAAK,UAAU,MAAM;AACnE,YAAM,KAAK,KAAK,KAAK,EAAE,UAAU,CAAC;AAClC,aAAO,EAAE,KAAK,KAAK,IAAI,GAAG,OAAO,MAAM,KAAK,MAAM,EAAE;AAAA,IACtD,CAAC;AAED,iBAAa,sBAAsB,OAAO,MAAM,EAAE,SAAS,MAAM;AAC/D,YAAM,KAAK,WAAW,EAAE,UAAU,MAAM,MAAM,CAAC;AAC/C,aAAO,EAAE,SAAS,mBAAmB;AAAA,IACvC,CAAC;AAED,iBAAa,iBAAiB,OAAO,MAAM,EAAE,SAAS,MAAM;AAC1D,YAAM,KAAK,MAAM,QAAQ;AACzB,aAAO,CAAC;AAAA,IACV,CAAC;AAED,iBAAa,gBAAgB,OAAO,MAAM,EAAE,UAAU,MAAM,MAAM,MAAM;AACtE,YAAM,KAAK,KAAK,UAAU,MAAM,EAAE,MAAM,CAAC;AACzC,aAAO,CAAC;AAAA,IACV,CAAC;AAED,iBAAa,sBAAsB,OAAO,MAAM,EAAE,OAAO,MAAM;AAC7D,UAAI,UAAU;AACd,UAAI,WAAW,OAAQ,WAAU,MAAM,KAAK,QAAQ;AAAA,UAC/C,WAAU,MAAM,KAAK,SAAS,MAAM,SAAS,KAAK,SAAS;AAEhE,UAAI,QAAQ,SAAS,kBAAkB;AACrC,cAAM,OAAO,KAAK,MAAM,mBAAmB,CAAC;AAC5C,kBAAU,GAAG,QAAQ,MAAM,GAAG,IAAI,CAAC;AAAA;AAAA;AAAA;AAAA,EAA8B,QAAQ,MAAM,CAAC,IAAI,CAAC;AAAA,MACvF;AACA,aAAO,EAAE,QAAQ;AAAA,IACnB,GAAG,EAAE,YAAY,MAAM,CAAC;AAExB,iBAAa,oBAAoB,OAAO,MAAM,EAAE,OAAO,MAAM;AAC3D,YAAM,SAAS,MAAM,KAAK,SAAS,MAAM;AACzC,aAAO,EAAE,OAAO;AAAA,IAClB,CAAC;AAED,iBAAa,oBAAoB,OAAO,SAAS;AAC/C,YAAM,QAAQ,MAAM,QAAQ,SAAS;AACrC,YAAM,OAAO,MAAM,QAAQ;AAAA,QACzB,MAAM,IAAI,OAAO,GAAG,OAAO;AAAA,UACzB,OAAO;AAAA,UACP,OAAQ,MAAM,EAAE,MAAM,EAAE,MAAM,MAAM,UAAU,KAAM;AAAA,UACpD,KAAK,EAAE,IAAI;AAAA,UACX,UAAU,MAAM;AAAA,QAClB,EAAE;AAAA,MACJ;AACA,aAAO,EAAE,KAAK;AAAA,IAChB,GAAG,EAAE,YAAY,MAAM,CAAC;AAExB,iBAAa,oBAAoB,OAAO,MAAM,EAAE,MAAM,MAAM;AAC1D,YAAM,QAAQ,MAAM,QAAQ,SAAS;AACrC,UAAI,SAAS,KAAK,QAAQ,MAAM,QAAQ;AACtC,cAAM,MAAM,MAAM,KAAK;AACvB,YAAI,QAAQ,KAAM,SAAQ,QAAQ,IAAI;AACtC,cAAM,IAAI,MAAM;AAAA,MAClB;AACA,aAAO,CAAC;AAAA,IACV,GAAG,EAAE,YAAY,MAAM,CAAC;AAExB,iBAAa,uBAAuB,OAAO,OAAO,EAAE,IAAI,MAAM;AAC5D,YAAM,IAAI,MAAM,QAAQ,WAAW,KAAK;AACxC,UAAI,IAAK,OAAM,EAAE,KAAK,GAAG;AACzB,aAAO,EAAE,SAAS,yBAAyB;AAAA,IAC7C,GAAG,EAAE,YAAY,MAAM,CAAC;AAExB,YAAQ,GAAG,sBAAsB,mBAAmB;AAClD,aAAO,WAAW,MAAM,QAAQ,WAAW,QAAQ,kBAAkB,KAAK,MAAS,CAAC;AAAA,IACtF,CAAC;AAAA,EACH;AACF;","names":["options"]}
|