@roxybrowser/playwright 2.0.2-beta.1 → 2.0.2-beta.4
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/browser.d.ts +33 -5
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +127 -15
- package/dist/browser.js.map +1 -1
- package/dist/browserType.d.ts.map +1 -1
- package/dist/browserType.js +2 -1
- package/dist/browserType.js.map +1 -1
- package/dist/mcp/backend/connect.d.ts +1 -0
- package/dist/mcp/backend/connect.d.ts.map +1 -1
- package/dist/mcp/backend/connect.js +4 -2
- package/dist/mcp/backend/connect.js.map +1 -1
- package/dist/mcp/backend/network.d.ts.map +1 -1
- package/dist/mcp/backend/network.js +30 -4
- package/dist/mcp/backend/network.js.map +1 -1
- package/dist/mcp/backend/response.d.ts +2 -0
- package/dist/mcp/backend/response.d.ts.map +1 -1
- package/dist/mcp/backend/response.js +52 -6
- package/dist/mcp/backend/response.js.map +1 -1
- package/dist/mcp/connectedBrowser.d.ts.map +1 -1
- package/dist/mcp/connectedBrowser.js +355 -25
- package/dist/mcp/connectedBrowser.js.map +1 -1
- package/dist/mcp/runtime.d.ts +3 -0
- package/dist/mcp/runtime.d.ts.map +1 -1
- package/dist/mcp/runtime.js +62 -8
- package/dist/mcp/runtime.js.map +1 -1
- package/dist/mcp/transports/inMemory.d.ts.map +1 -1
- package/dist/mcp/transports/inMemory.js +1 -0
- package/dist/mcp/transports/inMemory.js.map +1 -1
- package/dist/mcp/types.d.ts +5 -0
- package/dist/mcp/types.d.ts.map +1 -1
- package/dist/page.d.ts +2 -0
- package/dist/page.d.ts.map +1 -1
- package/dist/page.js +12 -0
- package/dist/page.js.map +1 -1
- package/dist/protocol/bidi/backend.d.ts +1 -1
- package/dist/protocol/bidi/backend.d.ts.map +1 -1
- package/dist/protocol/bidi/backend.js +6 -2
- package/dist/protocol/bidi/backend.js.map +1 -1
- package/dist/roxybrowser.bundle.js +516 -60
- package/dist/roxybrowser.bundle.js.map +1 -1
- package/dist/types/api.d.ts +21 -2
- package/dist/types/api.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -5124,6 +5124,7 @@ var RoxyPage = class RoxyPage {
|
|
|
5124
5124
|
closed = false;
|
|
5125
5125
|
closePromise = null;
|
|
5126
5126
|
closeReason;
|
|
5127
|
+
ownedContext;
|
|
5127
5128
|
defaultTimeoutMs = DEFAULT_EVENT_TIMEOUT_MS;
|
|
5128
5129
|
defaultNavigationTimeoutMs = DEFAULT_EVENT_TIMEOUT_MS;
|
|
5129
5130
|
currentViewportSize = null;
|
|
@@ -6111,6 +6112,10 @@ waiting for navigation${navigationTargetDescription} until "${waitUntil}"\n=====
|
|
|
6111
6112
|
}
|
|
6112
6113
|
this.closeReason = options.reason;
|
|
6113
6114
|
this.closePromise = (async () => {
|
|
6115
|
+
if (this.ownedContext) {
|
|
6116
|
+
await this.ownedContext.close().catch(() => {});
|
|
6117
|
+
return;
|
|
6118
|
+
}
|
|
6114
6119
|
await this.dismissActiveDialogsForClose();
|
|
6115
6120
|
await this.finalizeVideoRecording();
|
|
6116
6121
|
await this.adapter.close(options).catch(() => {});
|
|
@@ -6122,6 +6127,9 @@ waiting for navigation${navigationTargetDescription} until "${waitUntil}"\n=====
|
|
|
6122
6127
|
this.closePromise = null;
|
|
6123
6128
|
}
|
|
6124
6129
|
}
|
|
6130
|
+
setOwnedContext(context) {
|
|
6131
|
+
this.ownedContext = context;
|
|
6132
|
+
}
|
|
6125
6133
|
defaultTimeout() {
|
|
6126
6134
|
return this.defaultTimeoutMs;
|
|
6127
6135
|
}
|
|
@@ -10650,12 +10658,74 @@ var RoxyBrowser = class {
|
|
|
10650
10658
|
session;
|
|
10651
10659
|
adapter;
|
|
10652
10660
|
humanDefaults;
|
|
10653
|
-
|
|
10654
|
-
|
|
10661
|
+
_browserName;
|
|
10662
|
+
_browserType;
|
|
10663
|
+
_version;
|
|
10664
|
+
_contexts = [];
|
|
10665
|
+
_listeners = /* @__PURE__ */ new Map();
|
|
10666
|
+
_connected = true;
|
|
10667
|
+
constructor(session, adapter, humanDefaults, _browserName, _browserType, _version) {
|
|
10655
10668
|
this.session = session;
|
|
10656
10669
|
this.adapter = adapter;
|
|
10657
10670
|
this.humanDefaults = humanDefaults;
|
|
10658
|
-
this.
|
|
10671
|
+
this._browserName = _browserName;
|
|
10672
|
+
this._browserType = _browserType;
|
|
10673
|
+
this._version = _version;
|
|
10674
|
+
}
|
|
10675
|
+
on(event, listener) {
|
|
10676
|
+
return this._addListenerInternal(event, listener);
|
|
10677
|
+
}
|
|
10678
|
+
once(event, listener) {
|
|
10679
|
+
const wrapped = (payload) => {
|
|
10680
|
+
this._removeListenerInternal(event, listener);
|
|
10681
|
+
listener(payload);
|
|
10682
|
+
};
|
|
10683
|
+
this._ensureListenerSet(event).add({
|
|
10684
|
+
original: listener,
|
|
10685
|
+
wrapped
|
|
10686
|
+
});
|
|
10687
|
+
return this;
|
|
10688
|
+
}
|
|
10689
|
+
addListener(event, listener) {
|
|
10690
|
+
return this._addListenerInternal(event, listener);
|
|
10691
|
+
}
|
|
10692
|
+
removeListener(event, listener) {
|
|
10693
|
+
return this._removeListenerInternal(event, listener);
|
|
10694
|
+
}
|
|
10695
|
+
off(event, listener) {
|
|
10696
|
+
return this._removeListenerInternal(event, listener);
|
|
10697
|
+
}
|
|
10698
|
+
prependListener(event, listener) {
|
|
10699
|
+
const entry = {
|
|
10700
|
+
original: listener,
|
|
10701
|
+
wrapped: listener
|
|
10702
|
+
};
|
|
10703
|
+
const existing = this._listeners.get(event);
|
|
10704
|
+
if (!existing) {
|
|
10705
|
+
const s = new Set([entry]);
|
|
10706
|
+
this._listeners.set(event, s);
|
|
10707
|
+
} else {
|
|
10708
|
+
const s = new Set([entry, ...Array.from(existing)]);
|
|
10709
|
+
this._listeners.set(event, s);
|
|
10710
|
+
}
|
|
10711
|
+
return this;
|
|
10712
|
+
}
|
|
10713
|
+
removeAllListeners(type) {
|
|
10714
|
+
if (type === void 0) this._listeners.clear();
|
|
10715
|
+
else this._listeners.delete(type);
|
|
10716
|
+
return this;
|
|
10717
|
+
}
|
|
10718
|
+
browserType() {
|
|
10719
|
+
return this._browserType;
|
|
10720
|
+
}
|
|
10721
|
+
contexts() {
|
|
10722
|
+
return [...this._contexts];
|
|
10723
|
+
}
|
|
10724
|
+
isConnected() {
|
|
10725
|
+
return this._connected;
|
|
10726
|
+
}
|
|
10727
|
+
version() {
|
|
10728
|
+
return this._version;
|
|
10659
10729
|
}
|
|
10660
10730
|
async newContext(options = {}) {
|
|
10661
10731
|
const normalizedOptions = {
|
|
@@ -10666,26 +10736,66 @@ var RoxyBrowser = class {
|
|
|
10666
10736
|
...options.recordVideo.dir ? { dir: resolve(options.recordVideo.dir) } : {}
|
|
10667
10737
|
} } : {}
|
|
10668
10738
|
};
|
|
10669
|
-
|
|
10670
|
-
|
|
10671
|
-
|
|
10672
|
-
|
|
10739
|
+
const context = new RoxyBrowserContext(await this.session.newContext(normalizedOptions), resolveHumanizationOptions(normalizedOptions.human, this.humanDefaults), normalizedOptions, this._browserName);
|
|
10740
|
+
this._contexts.push(context);
|
|
10741
|
+
context.on("close", () => {
|
|
10742
|
+
const index = this._contexts.indexOf(context);
|
|
10743
|
+
if (index !== -1) this._contexts.splice(index, 1);
|
|
10744
|
+
});
|
|
10745
|
+
this._emit("context", context);
|
|
10746
|
+
return context;
|
|
10747
|
+
}
|
|
10748
|
+
async newPage(options) {
|
|
10749
|
+
const context = await this.newContext(options);
|
|
10750
|
+
const page = await context.newPage();
|
|
10751
|
+
const roxyPage = page;
|
|
10752
|
+
if (typeof roxyPage.setOwnedContext === "function") roxyPage.setOwnedContext(context);
|
|
10753
|
+
else page.once("close", () => context.close().catch(() => {}));
|
|
10754
|
+
return page;
|
|
10673
10755
|
}
|
|
10674
|
-
async close() {
|
|
10756
|
+
async close(options) {
|
|
10757
|
+
if (!this._connected) return;
|
|
10758
|
+
this._connected = false;
|
|
10675
10759
|
try {
|
|
10676
10760
|
await withCloseTimeout$1(this.session.close(), BROWSER_SESSION_CLOSE_TIMEOUT_MS);
|
|
10677
10761
|
} finally {
|
|
10678
10762
|
await this.adapter.close();
|
|
10763
|
+
this._emit("disconnected", this);
|
|
10679
10764
|
}
|
|
10680
10765
|
}
|
|
10766
|
+
_addListenerInternal(event, listener) {
|
|
10767
|
+
this._ensureListenerSet(event).add({
|
|
10768
|
+
original: listener,
|
|
10769
|
+
wrapped: listener
|
|
10770
|
+
});
|
|
10771
|
+
return this;
|
|
10772
|
+
}
|
|
10773
|
+
_removeListenerInternal(event, listener) {
|
|
10774
|
+
const entries = this._listeners.get(event);
|
|
10775
|
+
if (!entries) return this;
|
|
10776
|
+
for (const entry of Array.from(entries)) if (entry.original === listener) entries.delete(entry);
|
|
10777
|
+
if (entries.size === 0) this._listeners.delete(event);
|
|
10778
|
+
return this;
|
|
10779
|
+
}
|
|
10780
|
+
_ensureListenerSet(event) {
|
|
10781
|
+
const existing = this._listeners.get(event);
|
|
10782
|
+
if (existing) return existing;
|
|
10783
|
+
const created = /* @__PURE__ */ new Set();
|
|
10784
|
+
this._listeners.set(event, created);
|
|
10785
|
+
return created;
|
|
10786
|
+
}
|
|
10787
|
+
_emit(event, payload) {
|
|
10788
|
+
const entries = this._listeners.get(event);
|
|
10789
|
+
if (!entries?.size) return false;
|
|
10790
|
+
for (const entry of Array.from(entries)) entry.wrapped(payload);
|
|
10791
|
+
return true;
|
|
10792
|
+
}
|
|
10681
10793
|
};
|
|
10682
10794
|
async function withCloseTimeout$1(promise, timeoutMs) {
|
|
10683
10795
|
let timer;
|
|
10684
10796
|
try {
|
|
10685
10797
|
return await Promise.race([promise, new Promise((_, reject) => {
|
|
10686
|
-
timer = setTimeout(() => {
|
|
10687
|
-
reject(/* @__PURE__ */ new Error(`Timed out closing browser session after ${timeoutMs}ms.`));
|
|
10688
|
-
}, timeoutMs);
|
|
10798
|
+
timer = setTimeout(() => reject(/* @__PURE__ */ new Error(`Timed out closing browser session after ${timeoutMs}ms.`)), timeoutMs);
|
|
10689
10799
|
})]);
|
|
10690
10800
|
} finally {
|
|
10691
10801
|
if (timer) clearTimeout(timer);
|
|
@@ -16005,7 +16115,7 @@ var BidiPageAdapter = class BidiPageAdapter {
|
|
|
16005
16115
|
resultOwnership: "none"
|
|
16006
16116
|
}));
|
|
16007
16117
|
if (response.type === "exception") throw new Error(response.exceptionDetails.text || "BiDi evaluation failed.");
|
|
16008
|
-
return parseSerializedEvaluationResult(extractBiDiValue(response.result));
|
|
16118
|
+
return parseSerializedEvaluationResult(extractBiDiValue$1(response.result));
|
|
16009
16119
|
}
|
|
16010
16120
|
async evaluateFunction(expression, arg) {
|
|
16011
16121
|
const serializedArg = arg === void 0 ? "" : serializeForEvaluation$1(arg);
|
|
@@ -17249,11 +17359,11 @@ var BidiElementHandleAdapter = class BidiElementHandleAdapter {
|
|
|
17249
17359
|
return this.page.selectOptionReference(this.reference(), values, options);
|
|
17250
17360
|
}
|
|
17251
17361
|
};
|
|
17252
|
-
function extractBiDiValue(value) {
|
|
17253
|
-
if (value.type === "array" && Array.isArray(value.value)) return value.value.map((entry) => extractBiDiValue(entry));
|
|
17362
|
+
function extractBiDiValue$1(value) {
|
|
17363
|
+
if (value.type === "array" && Array.isArray(value.value)) return value.value.map((entry) => extractBiDiValue$1(entry));
|
|
17254
17364
|
if (value.type === "object" && Array.isArray(value.value)) {
|
|
17255
17365
|
const obj = {};
|
|
17256
|
-
for (const [key, val] of value.value) obj[key] = extractBiDiValue(val);
|
|
17366
|
+
for (const [key, val] of value.value) obj[key] = extractBiDiValue$1(val);
|
|
17257
17367
|
return obj;
|
|
17258
17368
|
}
|
|
17259
17369
|
return value.value;
|
|
@@ -17645,9 +17755,9 @@ function buildFirefoxLaunchArgs(options, userDataDir, port) {
|
|
|
17645
17755
|
...options.args ?? []
|
|
17646
17756
|
];
|
|
17647
17757
|
}
|
|
17648
|
-
function resolveFirefoxExecutableCandidates(options, platform = currentPlatform$1(), fileExistsFn = fileExists$1) {
|
|
17758
|
+
function resolveFirefoxExecutableCandidates(options, platform = currentPlatform$1(), playwrightFirefoxExecutablePath, fileExistsFn = fileExists$1) {
|
|
17649
17759
|
if (options.executablePath) return [options.executablePath];
|
|
17650
|
-
return filterExistingFirefoxExecutableCandidates(defaultFirefoxExecutableCandidates(platform), platform, fileExistsFn);
|
|
17760
|
+
return filterExistingFirefoxExecutableCandidates([...playwrightFirefoxExecutablePath ? [playwrightFirefoxExecutablePath] : [], ...defaultFirefoxExecutableCandidates(platform)], platform, fileExistsFn);
|
|
17651
17761
|
}
|
|
17652
17762
|
function defaultFirefoxExecutableCandidates(platform) {
|
|
17653
17763
|
const candidates = [];
|
|
@@ -53334,7 +53444,9 @@ var RoxyBrowserType = class {
|
|
|
53334
53444
|
protocol
|
|
53335
53445
|
});
|
|
53336
53446
|
await adapter.connect();
|
|
53337
|
-
|
|
53447
|
+
const session = await adapter.browser();
|
|
53448
|
+
const versionStr = await session.version();
|
|
53449
|
+
return new RoxyBrowser(session, adapter, resolveHumanizationOptions(options.human), options.browserName ?? this.browserName, this, versionStr);
|
|
53338
53450
|
}
|
|
53339
53451
|
};
|
|
53340
53452
|
var chromium = new RoxyBrowserType("chromium", {
|
|
@@ -73317,6 +73429,7 @@ var Response$1 = class {
|
|
|
73317
73429
|
includeSnapshot = "none";
|
|
73318
73430
|
fullSnapshot;
|
|
73319
73431
|
isClose = false;
|
|
73432
|
+
rawResults = false;
|
|
73320
73433
|
constructor(context, toolName, toolArgs) {
|
|
73321
73434
|
this.context = context;
|
|
73322
73435
|
this.toolName = toolName;
|
|
@@ -73340,6 +73453,9 @@ var Response$1 = class {
|
|
|
73340
73453
|
setClose() {
|
|
73341
73454
|
this.isClose = true;
|
|
73342
73455
|
}
|
|
73456
|
+
setRawResults() {
|
|
73457
|
+
this.rawResults = true;
|
|
73458
|
+
}
|
|
73343
73459
|
setIncludeSnapshot() {
|
|
73344
73460
|
this.includeSnapshot = this.context.config.snapshot?.mode ?? "full";
|
|
73345
73461
|
}
|
|
@@ -73355,6 +73471,21 @@ var Response$1 = class {
|
|
|
73355
73471
|
async serialize() {
|
|
73356
73472
|
const sections = [];
|
|
73357
73473
|
if (this.errors.length) sections.push("### Error", ...this.errors);
|
|
73474
|
+
if (this.rawResults) {
|
|
73475
|
+
if (this.results.length) sections.push(...this.results);
|
|
73476
|
+
return {
|
|
73477
|
+
content: [{
|
|
73478
|
+
type: "text",
|
|
73479
|
+
text: sections.join("\n")
|
|
73480
|
+
}, ...this.images.map((image) => ({
|
|
73481
|
+
type: "image",
|
|
73482
|
+
data: image.data,
|
|
73483
|
+
mimeType: image.mimeType
|
|
73484
|
+
}))],
|
|
73485
|
+
...this.isClose ? { isClose: true } : {},
|
|
73486
|
+
...this.errors.length ? { isError: true } : {}
|
|
73487
|
+
};
|
|
73488
|
+
}
|
|
73358
73489
|
if (this.results.length) {
|
|
73359
73490
|
if (sections.length) sections.push("");
|
|
73360
73491
|
sections.push("### Result", ...this.results);
|
|
@@ -73364,16 +73495,23 @@ var Response$1 = class {
|
|
|
73364
73495
|
sections.push("### Code", "```js", ...this.code, "```");
|
|
73365
73496
|
}
|
|
73366
73497
|
if (this.includeSnapshot === "full") {
|
|
73367
|
-
|
|
73368
|
-
|
|
73369
|
-
|
|
73498
|
+
if (!await this.context.runtime.hasDialog()) {
|
|
73499
|
+
const snapshot = await reconcileSnapshotWithTabs(this.context, await this.context.runtime.snapshot());
|
|
73500
|
+
if (sections.length) sections.push("");
|
|
73501
|
+
sections.push(formatSnapshot(snapshot));
|
|
73502
|
+
}
|
|
73370
73503
|
}
|
|
73371
73504
|
if (this.fullSnapshot) {
|
|
73372
|
-
|
|
73505
|
+
let snapshot = await reconcileSnapshotWithTabs(this.context, await this.context.runtime.snapshot({
|
|
73373
73506
|
...this.fullSnapshot.target !== void 0 ? { target: this.fullSnapshot.target } : {},
|
|
73374
73507
|
...this.fullSnapshot.depth !== void 0 ? { depth: this.fullSnapshot.depth } : {},
|
|
73375
73508
|
...this.fullSnapshot.boxes !== void 0 ? { boxes: this.fullSnapshot.boxes } : {}
|
|
73376
|
-
});
|
|
73509
|
+
}));
|
|
73510
|
+
if (!this.fullSnapshot.filename && snapshot.retryable && snapshot.text.trim().length === 0 && snapshot.url && snapshot.url !== "about:blank") snapshot = await reconcileSnapshotWithTabs(this.context, await this.context.runtime.snapshot({
|
|
73511
|
+
...this.fullSnapshot.target !== void 0 ? { target: this.fullSnapshot.target } : {},
|
|
73512
|
+
...this.fullSnapshot.depth !== void 0 ? { depth: this.fullSnapshot.depth } : {},
|
|
73513
|
+
...this.fullSnapshot.boxes !== void 0 ? { boxes: this.fullSnapshot.boxes } : {}
|
|
73514
|
+
}));
|
|
73377
73515
|
if (this.fullSnapshot.filename) {
|
|
73378
73516
|
const resolvedFilename = await this.context.resolveOutputFile(this.fullSnapshot.filename);
|
|
73379
73517
|
await writeFile(resolvedFilename, snapshot.text);
|
|
@@ -73400,6 +73538,15 @@ var Response$1 = class {
|
|
|
73400
73538
|
};
|
|
73401
73539
|
}
|
|
73402
73540
|
};
|
|
73541
|
+
async function reconcileSnapshotWithTabs(context, snapshot) {
|
|
73542
|
+
const activeTab = (await context.runtime.listTabs()).find((tab) => tab.active);
|
|
73543
|
+
if (!activeTab) return snapshot;
|
|
73544
|
+
return {
|
|
73545
|
+
...snapshot,
|
|
73546
|
+
title: activeTab.title || snapshot.title,
|
|
73547
|
+
url: activeTab.url || snapshot.url
|
|
73548
|
+
};
|
|
73549
|
+
}
|
|
73403
73550
|
//#endregion
|
|
73404
73551
|
//#region src/mcp/backend/tool.ts
|
|
73405
73552
|
function defineTool$1(tool) {
|
|
@@ -73498,7 +73645,8 @@ var connect_default = [defineTool$1({
|
|
|
73498
73645
|
description: "Attach to an existing browser and seed the active tab snapshot.",
|
|
73499
73646
|
inputSchema: object({
|
|
73500
73647
|
endpoint: string().min(1),
|
|
73501
|
-
browser: _enum(["chrome", "firefox"]).default("chrome")
|
|
73648
|
+
browser: _enum(["chrome", "firefox"]).default("chrome"),
|
|
73649
|
+
sessionId: string().min(1).optional()
|
|
73502
73650
|
}),
|
|
73503
73651
|
type: "action"
|
|
73504
73652
|
},
|
|
@@ -73507,7 +73655,8 @@ var connect_default = [defineTool$1({
|
|
|
73507
73655
|
const result = await context.runtime.connect({
|
|
73508
73656
|
protocol,
|
|
73509
73657
|
endpoint: params.endpoint,
|
|
73510
|
-
browser: params.browser === "chrome" ? "chromium" : params.browser
|
|
73658
|
+
browser: params.browser === "chrome" ? "chromium" : params.browser,
|
|
73659
|
+
...params.sessionId ? { sessionId: params.sessionId } : {}
|
|
73511
73660
|
});
|
|
73512
73661
|
response.addTextResult(formatConnectResult({
|
|
73513
73662
|
...result,
|
|
@@ -73952,7 +74101,17 @@ var networkRequest = defineTool$1({
|
|
|
73952
74101
|
response.addError(`Request #${args.index} not found. Use browser_network_requests to see available indexes.`);
|
|
73953
74102
|
return;
|
|
73954
74103
|
}
|
|
73955
|
-
|
|
74104
|
+
if (args.part) {
|
|
74105
|
+
response.setRawResults();
|
|
74106
|
+
const partText = args.part === "response-body" ? await context.runtime.fetchResponseBody(args.index) ?? "" : renderRequestPart(request, args.part);
|
|
74107
|
+
if (args.filename) {
|
|
74108
|
+
const resolvedFilename = await context.resolveOutputFile(args.filename);
|
|
74109
|
+
await writeFile(resolvedFilename, partText);
|
|
74110
|
+
response.addTextResult(`Saved network request to "${resolvedFilename}".`);
|
|
74111
|
+
} else response.addTextResult(partText);
|
|
74112
|
+
return;
|
|
74113
|
+
}
|
|
74114
|
+
const text = renderRequestDetails(request);
|
|
73956
74115
|
if (args.filename) {
|
|
73957
74116
|
const resolvedFilename = await context.resolveOutputFile(args.filename);
|
|
73958
74117
|
await writeFile(resolvedFilename, text);
|
|
@@ -73986,8 +74145,10 @@ function renderRequestDetails(request) {
|
|
|
73986
74145
|
if (request.mimeType) lines.push(` mimeType: ${request.mimeType}`);
|
|
73987
74146
|
appendHeaders(lines, "Request headers", request.requestHeaders);
|
|
73988
74147
|
if (request.responseHeaders) appendHeaders(lines, "Response headers", request.responseHeaders);
|
|
73989
|
-
|
|
73990
|
-
if (request.
|
|
74148
|
+
const hints = [];
|
|
74149
|
+
if (request.requestBody) hints.push(`Call browser_network_request with part="request-body" to read the request body.`);
|
|
74150
|
+
if (canHaveResponseBody(request)) hints.push(`Call browser_network_request with part="response-body" to read the response body.`);
|
|
74151
|
+
if (hints.length) lines.push("", ...hints);
|
|
73991
74152
|
return lines.join("\n");
|
|
73992
74153
|
}
|
|
73993
74154
|
function renderRequestPart(request, part) {
|
|
@@ -74006,6 +74167,10 @@ function appendHeaders(lines, title, headers) {
|
|
|
74006
74167
|
function renderHeaders(headers) {
|
|
74007
74168
|
return Object.entries(headers).map(([key, value]) => `${key}: ${value}`).join("\n");
|
|
74008
74169
|
}
|
|
74170
|
+
function canHaveResponseBody(request) {
|
|
74171
|
+
if (request.failureText || request.status === void 0) return false;
|
|
74172
|
+
return request.status !== 204 && request.status !== 304 && !(request.status >= 100 && request.status < 200);
|
|
74173
|
+
}
|
|
74009
74174
|
var network_default = [networkRequests, networkRequest];
|
|
74010
74175
|
var runCode_default = [defineTool$1({
|
|
74011
74176
|
capability: "devtools",
|
|
@@ -74666,13 +74831,13 @@ var CDP_KEY_MAP = {
|
|
|
74666
74831
|
async function evaluateBiDi(client, contextId, functionSource, arg) {
|
|
74667
74832
|
const expression = arg === void 0 ? `(${functionSource})()` : `(${functionSource})(${JSON.stringify(arg)})`;
|
|
74668
74833
|
const response = await client.scriptEvaluate({
|
|
74669
|
-
expression,
|
|
74834
|
+
expression: wrapWithSerializedEvaluationResult(expression),
|
|
74670
74835
|
target: { context: contextId },
|
|
74671
74836
|
awaitPromise: true,
|
|
74672
74837
|
resultOwnership: "none"
|
|
74673
74838
|
});
|
|
74674
74839
|
if (response.type === "exception") throw new Error(response.exceptionDetails?.text || "BiDi runtime evaluation failed.");
|
|
74675
|
-
return response.result
|
|
74840
|
+
return parseSerializedEvaluationResult(extractBiDiValue(response.result));
|
|
74676
74841
|
}
|
|
74677
74842
|
async function evaluateBiDiRef(client, contextId, functionSource, arg) {
|
|
74678
74843
|
const expression = arg === void 0 ? `(${functionSource})()` : `(${functionSource})(${JSON.stringify(arg)})`;
|
|
@@ -74694,6 +74859,16 @@ async function evaluateBiDiRef(client, contextId, functionSource, arg) {
|
|
|
74694
74859
|
...response.result?.value?.handle !== void 0 ? { handle: response.result.value.handle } : {}
|
|
74695
74860
|
};
|
|
74696
74861
|
}
|
|
74862
|
+
function extractBiDiValue(value) {
|
|
74863
|
+
if (!value) return;
|
|
74864
|
+
if (value.type === "array" && Array.isArray(value.value)) return value.value.map((entry) => extractBiDiValue(entry));
|
|
74865
|
+
if (value.type === "object" && Array.isArray(value.value)) {
|
|
74866
|
+
const obj = {};
|
|
74867
|
+
for (const [key, val] of value.value) obj[key] = extractBiDiValue(val);
|
|
74868
|
+
return obj;
|
|
74869
|
+
}
|
|
74870
|
+
return value.value;
|
|
74871
|
+
}
|
|
74697
74872
|
function toAriaSnapshotPayload(request = {}) {
|
|
74698
74873
|
return {
|
|
74699
74874
|
options: normalizeAriaSnapshotOptions({
|
|
@@ -74733,6 +74908,7 @@ var CdpConnectedBrowserSession = class CdpConnectedBrowserSession {
|
|
|
74733
74908
|
pageConsoleStates = /* @__PURE__ */ new Map();
|
|
74734
74909
|
pageNetworkStates = /* @__PURE__ */ new Map();
|
|
74735
74910
|
pageDialogStates = /* @__PURE__ */ new Map();
|
|
74911
|
+
dialogWaiters = /* @__PURE__ */ new Map();
|
|
74736
74912
|
activeTabId;
|
|
74737
74913
|
versionString = "Chromium/unknown";
|
|
74738
74914
|
constructor(browserClient, connection) {
|
|
@@ -74748,7 +74924,7 @@ var CdpConnectedBrowserSession = class CdpConnectedBrowserSession {
|
|
|
74748
74924
|
});
|
|
74749
74925
|
const session = new CdpConnectedBrowserSession(await chromeRemoteInterface({ target: connection.browserWsEndpoint }), connection);
|
|
74750
74926
|
session.versionString = version.Browser;
|
|
74751
|
-
|
|
74927
|
+
await session.refreshTabs();
|
|
74752
74928
|
await session.getActivePageClient().catch(() => void 0);
|
|
74753
74929
|
return session;
|
|
74754
74930
|
}
|
|
@@ -74800,6 +74976,7 @@ var CdpConnectedBrowserSession = class CdpConnectedBrowserSession {
|
|
|
74800
74976
|
async click(target, options) {
|
|
74801
74977
|
const pageClient = await this.getActivePageClient();
|
|
74802
74978
|
const contextId = await this.getActiveUtilityContextId(pageClient);
|
|
74979
|
+
const tabId = await this.getActiveTabId();
|
|
74803
74980
|
const point = await evaluateCdp(pageClient, "nodeToken" in target ? ACTION_POINT_EVALUATE_SOURCE : ACTION_POINT_BY_SELECTOR_SOURCE, "nodeToken" in target ? { nodeToken: target.nodeToken } : { selector: target.selector }, contextId);
|
|
74804
74981
|
if (!point.ok || point.x === void 0 || point.y === void 0) {
|
|
74805
74982
|
const isSelector = "selector" in target;
|
|
@@ -74833,7 +75010,7 @@ var CdpConnectedBrowserSession = class CdpConnectedBrowserSession {
|
|
|
74833
75010
|
modifiers: modifiersMask
|
|
74834
75011
|
});
|
|
74835
75012
|
await delay$1(options.clickHoldMs);
|
|
74836
|
-
|
|
75013
|
+
const releasePromise = pageClient.Input.dispatchMouseEvent({
|
|
74837
75014
|
type: "mouseReleased",
|
|
74838
75015
|
x: point.x,
|
|
74839
75016
|
y: point.y,
|
|
@@ -74841,6 +75018,7 @@ var CdpConnectedBrowserSession = class CdpConnectedBrowserSession {
|
|
|
74841
75018
|
clickCount,
|
|
74842
75019
|
modifiers: modifiersMask
|
|
74843
75020
|
});
|
|
75021
|
+
await Promise.race([releasePromise, this.waitForDialog(tabId, options.clickHoldMs + 1e3)]);
|
|
74844
75022
|
}
|
|
74845
75023
|
}
|
|
74846
75024
|
async drag(start, end, options) {
|
|
@@ -75096,24 +75274,72 @@ var CdpConnectedBrowserSession = class CdpConnectedBrowserSession {
|
|
|
75096
75274
|
}
|
|
75097
75275
|
}
|
|
75098
75276
|
async handleDialog(accept, promptText) {
|
|
75099
|
-
const tabId =
|
|
75277
|
+
const tabId = this.dialogTabId();
|
|
75100
75278
|
if (!this.pageDialogStates.has(tabId)) throw new McpToolError("no_dialog", "No dialog visible.");
|
|
75101
|
-
const pageClient = await this.getActivePageClient();
|
|
75279
|
+
const pageClient = this.pageClients.get(tabId) ?? await this.getActivePageClient();
|
|
75102
75280
|
this.pageDialogStates.delete(tabId);
|
|
75103
75281
|
await pageClient.Page.handleJavaScriptDialog({
|
|
75104
75282
|
accept,
|
|
75105
75283
|
...promptText !== void 0 ? { promptText } : {}
|
|
75106
75284
|
});
|
|
75107
75285
|
}
|
|
75286
|
+
async hasDialog() {
|
|
75287
|
+
return this.pageDialogStates.size > 0;
|
|
75288
|
+
}
|
|
75289
|
+
waitForDialog(tabId, timeoutMs) {
|
|
75290
|
+
if (this.pageDialogStates.has(tabId)) return Promise.resolve();
|
|
75291
|
+
return new Promise((resolve) => {
|
|
75292
|
+
const waiter = { resolve: () => {
|
|
75293
|
+
if (waiter.timer) clearTimeout(waiter.timer);
|
|
75294
|
+
this.removeDialogWaiter(tabId, waiter);
|
|
75295
|
+
resolve();
|
|
75296
|
+
} };
|
|
75297
|
+
waiter.timer = setTimeout(() => waiter.resolve(), timeoutMs);
|
|
75298
|
+
const waiters = this.dialogWaiters.get(tabId) ?? /* @__PURE__ */ new Set();
|
|
75299
|
+
waiters.add(waiter);
|
|
75300
|
+
this.dialogWaiters.set(tabId, waiters);
|
|
75301
|
+
});
|
|
75302
|
+
}
|
|
75303
|
+
resolveDialogWaiters(tabId) {
|
|
75304
|
+
const waiters = this.dialogWaiters.get(tabId);
|
|
75305
|
+
if (!waiters) return;
|
|
75306
|
+
this.dialogWaiters.delete(tabId);
|
|
75307
|
+
for (const waiter of waiters) waiter.resolve();
|
|
75308
|
+
}
|
|
75309
|
+
removeDialogWaiter(tabId, waiter) {
|
|
75310
|
+
const waiters = this.dialogWaiters.get(tabId);
|
|
75311
|
+
if (!waiters) return;
|
|
75312
|
+
waiters.delete(waiter);
|
|
75313
|
+
if (waiters.size === 0) this.dialogWaiters.delete(tabId);
|
|
75314
|
+
}
|
|
75108
75315
|
async networkRequests() {
|
|
75109
75316
|
const tabId = await this.getActiveTabId();
|
|
75317
|
+
await this.hydratePerformanceResourceRequests(tabId);
|
|
75110
75318
|
return this.ensureNetworkState(tabId).requests.map(cloneNetworkRequest);
|
|
75111
75319
|
}
|
|
75112
75320
|
async networkRequest(index) {
|
|
75113
75321
|
const tabId = await this.getActiveTabId();
|
|
75322
|
+
await this.hydratePerformanceResourceRequests(tabId);
|
|
75114
75323
|
const request = this.ensureNetworkState(tabId).requests[index - 1];
|
|
75115
75324
|
return request ? cloneNetworkRequest(request) : void 0;
|
|
75116
75325
|
}
|
|
75326
|
+
async fetchResponseBody(index) {
|
|
75327
|
+
const tabId = await this.getActiveTabId();
|
|
75328
|
+
const state = this.ensureNetworkState(tabId);
|
|
75329
|
+
const request = state.requests[index - 1];
|
|
75330
|
+
if (!request || !request.requestId) return request?.responseBody;
|
|
75331
|
+
if (!canReadResponseBody(request)) return;
|
|
75332
|
+
if (request.responseBody !== void 0) return request.responseBody;
|
|
75333
|
+
await waitForLoadingDone(state, request.requestId, 5e3).catch(() => void 0);
|
|
75334
|
+
if (request.responseBody !== void 0) return request.responseBody;
|
|
75335
|
+
if (state.bodyRead.has(request.requestId)) return;
|
|
75336
|
+
state.bodyRead.add(request.requestId);
|
|
75337
|
+
const clientNetwork = (this.pageClients.get(tabId) ?? await this.getActivePageClient()).Network;
|
|
75338
|
+
if (!clientNetwork) return;
|
|
75339
|
+
const body = await clientNetwork.getResponseBody({ requestId: request.requestId }).catch(() => void 0);
|
|
75340
|
+
if (body) request.responseBody = body.base64Encoded ? Buffer.from(body.body, "base64").toString("utf8") : body.body;
|
|
75341
|
+
return request.responseBody;
|
|
75342
|
+
}
|
|
75117
75343
|
async runCodeUnsafe(code) {
|
|
75118
75344
|
const pageClient = await this.getActivePageClient();
|
|
75119
75345
|
const contextId = await this.getActiveUtilityContextId(pageClient);
|
|
@@ -75173,6 +75399,12 @@ var CdpConnectedBrowserSession = class CdpConnectedBrowserSession {
|
|
|
75173
75399
|
if (!activeTab) throw new McpToolError("no_active_tab", "No active tab is available.");
|
|
75174
75400
|
return activeTab.id;
|
|
75175
75401
|
}
|
|
75402
|
+
dialogTabId() {
|
|
75403
|
+
if (this.activeTabId && this.pageDialogStates.has(this.activeTabId)) return this.activeTabId;
|
|
75404
|
+
const tabId = this.pageDialogStates.keys().next().value;
|
|
75405
|
+
if (!tabId) throw new McpToolError("no_dialog", "No dialog visible.");
|
|
75406
|
+
return tabId;
|
|
75407
|
+
}
|
|
75176
75408
|
async getActivePageClient() {
|
|
75177
75409
|
const activeTab = (await this.refreshTabs()).find((tab) => tab.active);
|
|
75178
75410
|
if (!activeTab) throw new McpToolError("no_active_tab", "No active tab is available.");
|
|
@@ -75254,6 +75486,7 @@ var CdpConnectedBrowserSession = class CdpConnectedBrowserSession {
|
|
|
75254
75486
|
...event.defaultPrompt !== void 0 ? { defaultPrompt: event.defaultPrompt } : {},
|
|
75255
75487
|
...event.url !== void 0 ? { url: event.url } : {}
|
|
75256
75488
|
});
|
|
75489
|
+
this.resolveDialogWaiters(tabId);
|
|
75257
75490
|
});
|
|
75258
75491
|
this.installNetworkCollection(tabId, client);
|
|
75259
75492
|
}
|
|
@@ -75292,20 +75525,29 @@ var CdpConnectedBrowserSession = class CdpConnectedBrowserSession {
|
|
|
75292
75525
|
});
|
|
75293
75526
|
client.Network.loadingFinished(async (event) => {
|
|
75294
75527
|
const request = state.byRequestId.get(event.requestId);
|
|
75295
|
-
if (!request)
|
|
75528
|
+
if (!request) {
|
|
75529
|
+
resolveLoadingDone(state, event.requestId, true);
|
|
75530
|
+
return;
|
|
75531
|
+
}
|
|
75296
75532
|
const startedAt = state.startedAt.get(event.requestId);
|
|
75297
75533
|
if (startedAt !== void 0 && event.timestamp !== void 0) request.durationMs = Math.round(event.timestamp * 1e3 - startedAt);
|
|
75298
|
-
if (canReadResponseBody(request)) {
|
|
75534
|
+
if (canReadResponseBody(request) && !state.bodyRead.has(event.requestId)) {
|
|
75535
|
+
state.bodyRead.add(event.requestId);
|
|
75299
75536
|
const body = await client.Network?.getResponseBody({ requestId: event.requestId }).catch(() => void 0);
|
|
75300
75537
|
if (body) request.responseBody = body.base64Encoded ? Buffer.from(body.body, "base64").toString("utf8") : body.body;
|
|
75301
75538
|
}
|
|
75539
|
+
resolveLoadingDone(state, event.requestId, true);
|
|
75302
75540
|
});
|
|
75303
75541
|
client.Network.loadingFailed((event) => {
|
|
75304
75542
|
const request = state.byRequestId.get(event.requestId);
|
|
75305
|
-
if (!request)
|
|
75543
|
+
if (!request) {
|
|
75544
|
+
resolveLoadingDone(state, event.requestId, false);
|
|
75545
|
+
return;
|
|
75546
|
+
}
|
|
75306
75547
|
request.failureText = event.errorText ?? "Unknown error";
|
|
75307
75548
|
const startedAt = state.startedAt.get(event.requestId);
|
|
75308
75549
|
if (startedAt !== void 0 && event.timestamp !== void 0) request.durationMs = Math.round(event.timestamp * 1e3 - startedAt);
|
|
75550
|
+
resolveLoadingDone(state, event.requestId, false);
|
|
75309
75551
|
});
|
|
75310
75552
|
}
|
|
75311
75553
|
ensureConsoleState(tabId) {
|
|
@@ -75327,7 +75569,10 @@ var CdpConnectedBrowserSession = class CdpConnectedBrowserSession {
|
|
|
75327
75569
|
state = {
|
|
75328
75570
|
requests: [],
|
|
75329
75571
|
byRequestId: /* @__PURE__ */ new Map(),
|
|
75330
|
-
startedAt: /* @__PURE__ */ new Map()
|
|
75572
|
+
startedAt: /* @__PURE__ */ new Map(),
|
|
75573
|
+
hydratedPerformanceResources: false,
|
|
75574
|
+
loadingDone: /* @__PURE__ */ new Map(),
|
|
75575
|
+
bodyRead: /* @__PURE__ */ new Set()
|
|
75331
75576
|
};
|
|
75332
75577
|
this.pageNetworkStates.set(tabId, state);
|
|
75333
75578
|
}
|
|
@@ -75343,10 +75588,79 @@ var CdpConnectedBrowserSession = class CdpConnectedBrowserSession {
|
|
|
75343
75588
|
this.pageNetworkStates.set(tabId, {
|
|
75344
75589
|
requests: [],
|
|
75345
75590
|
byRequestId: /* @__PURE__ */ new Map(),
|
|
75346
|
-
startedAt: /* @__PURE__ */ new Map()
|
|
75591
|
+
startedAt: /* @__PURE__ */ new Map(),
|
|
75592
|
+
hydratedPerformanceResources: false,
|
|
75593
|
+
loadingDone: /* @__PURE__ */ new Map(),
|
|
75594
|
+
bodyRead: /* @__PURE__ */ new Set()
|
|
75347
75595
|
});
|
|
75348
75596
|
this.pageDialogStates.delete(tabId);
|
|
75349
75597
|
}
|
|
75598
|
+
async hydratePerformanceResourceRequests(tabId) {
|
|
75599
|
+
const state = this.ensureNetworkState(tabId);
|
|
75600
|
+
if (state.hydratedPerformanceResources) return;
|
|
75601
|
+
state.hydratedPerformanceResources = true;
|
|
75602
|
+
const pageClient = await this.getActivePageClient();
|
|
75603
|
+
const contextId = await this.getActiveUtilityContextId(pageClient);
|
|
75604
|
+
const documentRequest = await evaluateCdp(pageClient, String.raw`() => {
|
|
75605
|
+
const navigation = performance.getEntriesByType("navigation")[0];
|
|
75606
|
+
return {
|
|
75607
|
+
url: String(location.href || ""),
|
|
75608
|
+
duration: navigation ? Math.round(Number(navigation.duration || 0)) : undefined
|
|
75609
|
+
};
|
|
75610
|
+
}`, void 0, contextId).catch(() => void 0);
|
|
75611
|
+
if (documentRequest?.url && !Array.from(state.byRequestId.values()).some((request) => request.url === documentRequest.url)) {
|
|
75612
|
+
const requestId = `performance:navigation:${documentRequest.url}`;
|
|
75613
|
+
const request = {
|
|
75614
|
+
index: state.requests.length + 1,
|
|
75615
|
+
requestId,
|
|
75616
|
+
method: "GET",
|
|
75617
|
+
url: documentRequest.url,
|
|
75618
|
+
resourceType: "document",
|
|
75619
|
+
requestHeaders: {},
|
|
75620
|
+
status: 200,
|
|
75621
|
+
statusText: "OK",
|
|
75622
|
+
...documentRequest.duration !== void 0 ? { durationMs: documentRequest.duration } : {}
|
|
75623
|
+
};
|
|
75624
|
+
state.requests.push(request);
|
|
75625
|
+
state.byRequestId.set(requestId, request);
|
|
75626
|
+
}
|
|
75627
|
+
const resources = await evaluateCdp(pageClient, String.raw`() => performance.getEntriesByType("resource").map((entry) => ({
|
|
75628
|
+
name: String(entry.name || ""),
|
|
75629
|
+
initiatorType: String(entry.initiatorType || "other"),
|
|
75630
|
+
duration: Math.round(Number(entry.duration || 0)),
|
|
75631
|
+
responseStatus: typeof entry.responseStatus === "number" ? entry.responseStatus : undefined
|
|
75632
|
+
}))`, void 0, contextId).catch(() => []);
|
|
75633
|
+
for (const resource of resources) {
|
|
75634
|
+
if (!resource.name || Array.from(state.byRequestId.values()).some((request) => request.url === resource.name)) continue;
|
|
75635
|
+
const status = resource.responseStatus && resource.responseStatus > 0 ? resource.responseStatus : await this.probeResourceStatus(pageClient, contextId, resource.name);
|
|
75636
|
+
const requestId = `performance:${resource.name}`;
|
|
75637
|
+
const request = {
|
|
75638
|
+
index: state.requests.length + 1,
|
|
75639
|
+
requestId,
|
|
75640
|
+
method: "GET",
|
|
75641
|
+
url: resource.name,
|
|
75642
|
+
resourceType: normalizeResourceType(resource.initiatorType),
|
|
75643
|
+
requestHeaders: {},
|
|
75644
|
+
...status !== void 0 ? {
|
|
75645
|
+
status,
|
|
75646
|
+
statusText: statusTextForStatus(status)
|
|
75647
|
+
} : {},
|
|
75648
|
+
...resource.duration !== void 0 ? { durationMs: resource.duration } : {}
|
|
75649
|
+
};
|
|
75650
|
+
state.requests.push(request);
|
|
75651
|
+
state.byRequestId.set(requestId, request);
|
|
75652
|
+
}
|
|
75653
|
+
}
|
|
75654
|
+
async probeResourceStatus(pageClient, contextId, url) {
|
|
75655
|
+
return evaluateCdp(pageClient, String.raw`async (url) => {
|
|
75656
|
+
try {
|
|
75657
|
+
const response = await fetch(url, { method: "HEAD", cache: "no-store" });
|
|
75658
|
+
return response.status;
|
|
75659
|
+
} catch {
|
|
75660
|
+
return undefined;
|
|
75661
|
+
}
|
|
75662
|
+
}`, url, contextId).catch(() => void 0);
|
|
75663
|
+
}
|
|
75350
75664
|
addConsoleMessage(tabId, message) {
|
|
75351
75665
|
const state = this.ensureConsoleState(tabId);
|
|
75352
75666
|
if (!shouldIncludeConsoleMessage(message.type)) return;
|
|
@@ -75480,6 +75794,45 @@ function canReadResponseBody(request) {
|
|
|
75480
75794
|
if (request.failureText || request.status === void 0) return false;
|
|
75481
75795
|
return request.status !== 204 && request.status !== 304 && !(request.status >= 100 && request.status < 200);
|
|
75482
75796
|
}
|
|
75797
|
+
function loadingDoneEntry(state, requestId) {
|
|
75798
|
+
let entry = state.loadingDone.get(requestId);
|
|
75799
|
+
if (!entry) {
|
|
75800
|
+
let resolve;
|
|
75801
|
+
let reject;
|
|
75802
|
+
entry = {
|
|
75803
|
+
promise: new Promise((res, rej) => {
|
|
75804
|
+
resolve = res;
|
|
75805
|
+
reject = rej;
|
|
75806
|
+
}),
|
|
75807
|
+
resolve,
|
|
75808
|
+
reject
|
|
75809
|
+
};
|
|
75810
|
+
state.loadingDone.set(requestId, entry);
|
|
75811
|
+
}
|
|
75812
|
+
return entry;
|
|
75813
|
+
}
|
|
75814
|
+
function resolveLoadingDone(state, requestId, success) {
|
|
75815
|
+
const entry = state.loadingDone.get(requestId);
|
|
75816
|
+
if (!entry) return;
|
|
75817
|
+
state.loadingDone.delete(requestId);
|
|
75818
|
+
if (success) entry.resolve();
|
|
75819
|
+
else entry.reject(/* @__PURE__ */ new Error("Request failed before the response body was available."));
|
|
75820
|
+
}
|
|
75821
|
+
async function waitForLoadingDone(state, requestId, timeoutMs) {
|
|
75822
|
+
const entry = loadingDoneEntry(state, requestId);
|
|
75823
|
+
await Promise.race([entry.promise, new Promise((resolve) => setTimeout(resolve, timeoutMs))]);
|
|
75824
|
+
}
|
|
75825
|
+
function statusTextForStatus(status) {
|
|
75826
|
+
if (status === 200) return "OK";
|
|
75827
|
+
if (status === 204) return "No Content";
|
|
75828
|
+
if (status === 304) return "Not Modified";
|
|
75829
|
+
if (status === 400) return "Bad Request";
|
|
75830
|
+
if (status === 401) return "Unauthorized";
|
|
75831
|
+
if (status === 403) return "Forbidden";
|
|
75832
|
+
if (status === 404) return "Not Found";
|
|
75833
|
+
if (status === 500) return "Internal Server Error";
|
|
75834
|
+
return "";
|
|
75835
|
+
}
|
|
75483
75836
|
function cloneNetworkRequest(request) {
|
|
75484
75837
|
return {
|
|
75485
75838
|
...request,
|
|
@@ -75558,6 +75911,7 @@ var BidiConnectedBrowserSession = class BidiConnectedBrowserSession {
|
|
|
75558
75911
|
pageConsoleStates = /* @__PURE__ */ new Map();
|
|
75559
75912
|
pageNetworkStates = /* @__PURE__ */ new Map();
|
|
75560
75913
|
pageDialogStates = /* @__PURE__ */ new Map();
|
|
75914
|
+
dialogWaiters = /* @__PURE__ */ new Map();
|
|
75561
75915
|
bidiListeners = /* @__PURE__ */ new Map();
|
|
75562
75916
|
responseDataCollector;
|
|
75563
75917
|
activeTabId;
|
|
@@ -75571,12 +75925,12 @@ var BidiConnectedBrowserSession = class BidiConnectedBrowserSession {
|
|
|
75571
75925
|
if (parsed.protocol !== "ws:" && parsed.protocol !== "wss:") throw new McpToolError("unsupported_protocol_input", `BiDi endpoint must be a ws(s) URL. Received "${parsed.protocol}".`);
|
|
75572
75926
|
const client = await getBidiClientFactory()({
|
|
75573
75927
|
browserName: "firefox",
|
|
75574
|
-
webSocketUrl: normalizeFirefoxBidiEndpoint(args.endpoint)
|
|
75928
|
+
webSocketUrl: normalizeFirefoxBidiEndpoint(args.endpoint, args.sessionId)
|
|
75575
75929
|
});
|
|
75576
75930
|
const session = new BidiConnectedBrowserSession(client);
|
|
75577
|
-
session.ownsSession = await ensureMcpBiDiSession(client, args.endpoint);
|
|
75931
|
+
session.ownsSession = await ensureMcpBiDiSession(client, args.endpoint, args.sessionId);
|
|
75578
75932
|
await session.initialize();
|
|
75579
|
-
|
|
75933
|
+
await session.refreshTabs();
|
|
75580
75934
|
return session;
|
|
75581
75935
|
}
|
|
75582
75936
|
async version() {
|
|
@@ -75619,10 +75973,13 @@ var BidiConnectedBrowserSession = class BidiConnectedBrowserSession {
|
|
|
75619
75973
|
}
|
|
75620
75974
|
async snapshot(request = {}) {
|
|
75621
75975
|
const tabId = await this.getActiveTabId();
|
|
75622
|
-
return
|
|
75623
|
-
|
|
75624
|
-
|
|
75625
|
-
|
|
75976
|
+
return {
|
|
75977
|
+
...toBrowserSnapshot(await retryUntilReady(() => evaluateBiDi(this.client, tabId, PLAYWRIGHT_ARIA_SNAPSHOT_EVALUATE_SOURCE, toAriaSnapshotPayload(request))), request, {
|
|
75978
|
+
console: this.consoleSummary(tabId),
|
|
75979
|
+
consoleLink: await this.takeConsoleLink(tabId)
|
|
75980
|
+
}),
|
|
75981
|
+
retryable: true
|
|
75982
|
+
};
|
|
75626
75983
|
}
|
|
75627
75984
|
async consoleMessages(level = "info", all = false) {
|
|
75628
75985
|
const activeTabId = await this.getActiveTabId();
|
|
@@ -76029,7 +76386,7 @@ var BidiConnectedBrowserSession = class BidiConnectedBrowserSession {
|
|
|
76029
76386
|
}
|
|
76030
76387
|
}
|
|
76031
76388
|
async handleDialog(accept, promptText) {
|
|
76032
|
-
const tabId =
|
|
76389
|
+
const tabId = this.dialogTabId();
|
|
76033
76390
|
if (!this.pageDialogStates.has(tabId)) throw new McpToolError("no_dialog", "No dialog visible.");
|
|
76034
76391
|
this.pageDialogStates.delete(tabId);
|
|
76035
76392
|
await this.client.browsingContextHandleUserPrompt({
|
|
@@ -76038,6 +76395,9 @@ var BidiConnectedBrowserSession = class BidiConnectedBrowserSession {
|
|
|
76038
76395
|
...promptText !== void 0 ? { userText: promptText } : {}
|
|
76039
76396
|
});
|
|
76040
76397
|
}
|
|
76398
|
+
async hasDialog() {
|
|
76399
|
+
return this.pageDialogStates.size > 0;
|
|
76400
|
+
}
|
|
76041
76401
|
async networkRequests() {
|
|
76042
76402
|
const tabId = await this.getActiveTabId();
|
|
76043
76403
|
return this.ensureNetworkState(tabId).requests.map(cloneNetworkRequest);
|
|
@@ -76047,6 +76407,15 @@ var BidiConnectedBrowserSession = class BidiConnectedBrowserSession {
|
|
|
76047
76407
|
const request = this.ensureNetworkState(tabId).requests[index - 1];
|
|
76048
76408
|
return request ? cloneNetworkRequest(request) : void 0;
|
|
76049
76409
|
}
|
|
76410
|
+
async fetchResponseBody(index) {
|
|
76411
|
+
const tabId = await this.getActiveTabId();
|
|
76412
|
+
const request = this.ensureNetworkState(tabId).requests[index - 1];
|
|
76413
|
+
if (!request || !request.requestId) return request?.responseBody;
|
|
76414
|
+
if (request.responseBody !== void 0) return request.responseBody;
|
|
76415
|
+
const body = await this.getResponseBody(request.requestId).catch(() => void 0);
|
|
76416
|
+
if (body !== void 0) request.responseBody = body;
|
|
76417
|
+
return request.responseBody;
|
|
76418
|
+
}
|
|
76050
76419
|
async runCodeUnsafe(code) {
|
|
76051
76420
|
return this.evaluate(`async () => {
|
|
76052
76421
|
const fn = eval(${JSON.stringify(`(${code})`)});
|
|
@@ -76110,6 +76479,45 @@ var BidiConnectedBrowserSession = class BidiConnectedBrowserSession {
|
|
|
76110
76479
|
targetArg(target) {
|
|
76111
76480
|
return "nodeToken" in target ? { nodeToken: target.nodeToken } : { selector: target.selector };
|
|
76112
76481
|
}
|
|
76482
|
+
dialogTabId() {
|
|
76483
|
+
if (this.activeTabId && this.pageDialogStates.has(this.activeTabId)) return this.activeTabId;
|
|
76484
|
+
const tabId = this.pageDialogStates.keys().next().value;
|
|
76485
|
+
if (!tabId) throw new McpToolError("no_dialog", "No dialog visible.");
|
|
76486
|
+
return tabId;
|
|
76487
|
+
}
|
|
76488
|
+
waitForDialog(tabId, timeoutMs) {
|
|
76489
|
+
if (this.pageDialogStates.has(tabId)) return Promise.resolve();
|
|
76490
|
+
return new Promise((resolve, reject) => {
|
|
76491
|
+
const waiter = {
|
|
76492
|
+
resolve: () => {
|
|
76493
|
+
if (waiter.timer) clearTimeout(waiter.timer);
|
|
76494
|
+
this.removeDialogWaiter(tabId, waiter);
|
|
76495
|
+
resolve();
|
|
76496
|
+
},
|
|
76497
|
+
reject: (error) => {
|
|
76498
|
+
if (waiter.timer) clearTimeout(waiter.timer);
|
|
76499
|
+
this.removeDialogWaiter(tabId, waiter);
|
|
76500
|
+
reject(error);
|
|
76501
|
+
}
|
|
76502
|
+
};
|
|
76503
|
+
waiter.timer = setTimeout(() => waiter.reject?.(/* @__PURE__ */ new Error("Timed out waiting for dialog.")), timeoutMs);
|
|
76504
|
+
const waiters = this.dialogWaiters.get(tabId) ?? /* @__PURE__ */ new Set();
|
|
76505
|
+
waiters.add(waiter);
|
|
76506
|
+
this.dialogWaiters.set(tabId, waiters);
|
|
76507
|
+
});
|
|
76508
|
+
}
|
|
76509
|
+
resolveDialogWaiters(tabId) {
|
|
76510
|
+
const waiters = this.dialogWaiters.get(tabId);
|
|
76511
|
+
if (!waiters) return;
|
|
76512
|
+
this.dialogWaiters.delete(tabId);
|
|
76513
|
+
for (const waiter of waiters) waiter.resolve();
|
|
76514
|
+
}
|
|
76515
|
+
removeDialogWaiter(tabId, waiter) {
|
|
76516
|
+
const waiters = this.dialogWaiters.get(tabId);
|
|
76517
|
+
if (!waiters) return;
|
|
76518
|
+
waiters.delete(waiter);
|
|
76519
|
+
if (waiters.size === 0) this.dialogWaiters.delete(tabId);
|
|
76520
|
+
}
|
|
76113
76521
|
async actionPoint(tabId, target) {
|
|
76114
76522
|
const source = "nodeToken" in target ? ACTION_POINT_EVALUATE_SOURCE : ACTION_POINT_BY_SELECTOR_SOURCE;
|
|
76115
76523
|
const point = await evaluateBiDi(this.client, tabId, source, this.targetArg(target));
|
|
@@ -76268,7 +76676,10 @@ var BidiConnectedBrowserSession = class BidiConnectedBrowserSession {
|
|
|
76268
76676
|
state = {
|
|
76269
76677
|
requests: [],
|
|
76270
76678
|
byRequestId: /* @__PURE__ */ new Map(),
|
|
76271
|
-
startedAt: /* @__PURE__ */ new Map()
|
|
76679
|
+
startedAt: /* @__PURE__ */ new Map(),
|
|
76680
|
+
hydratedPerformanceResources: false,
|
|
76681
|
+
loadingDone: /* @__PURE__ */ new Map(),
|
|
76682
|
+
bodyRead: /* @__PURE__ */ new Set()
|
|
76272
76683
|
};
|
|
76273
76684
|
this.pageNetworkStates.set(tabId, state);
|
|
76274
76685
|
}
|
|
@@ -76284,7 +76695,10 @@ var BidiConnectedBrowserSession = class BidiConnectedBrowserSession {
|
|
|
76284
76695
|
this.pageNetworkStates.set(tabId, {
|
|
76285
76696
|
requests: [],
|
|
76286
76697
|
byRequestId: /* @__PURE__ */ new Map(),
|
|
76287
|
-
startedAt: /* @__PURE__ */ new Map()
|
|
76698
|
+
startedAt: /* @__PURE__ */ new Map(),
|
|
76699
|
+
hydratedPerformanceResources: false,
|
|
76700
|
+
loadingDone: /* @__PURE__ */ new Map(),
|
|
76701
|
+
bodyRead: /* @__PURE__ */ new Set()
|
|
76288
76702
|
});
|
|
76289
76703
|
this.pageDialogStates.delete(tabId);
|
|
76290
76704
|
}
|
|
@@ -76322,14 +76736,18 @@ var BidiConnectedBrowserSession = class BidiConnectedBrowserSession {
|
|
|
76322
76736
|
return `${path.relative(process.cwd(), state.logFile)}${fromLine === state.logLine ? `#L${fromLine}` : `#L${fromLine}-L${state.logLine}`}`;
|
|
76323
76737
|
}
|
|
76324
76738
|
};
|
|
76325
|
-
function normalizeFirefoxBidiEndpoint(endpoint) {
|
|
76739
|
+
function normalizeFirefoxBidiEndpoint(endpoint, sessionId) {
|
|
76326
76740
|
const url = new URL(endpoint);
|
|
76741
|
+
if (sessionId) {
|
|
76742
|
+
url.pathname = `/session/${sessionId}`;
|
|
76743
|
+
return url.toString();
|
|
76744
|
+
}
|
|
76327
76745
|
if (url.pathname === "/" || url.pathname === "") url.pathname = "/session";
|
|
76328
76746
|
return url.toString();
|
|
76329
76747
|
}
|
|
76330
|
-
async function ensureMcpBiDiSession(client, endpoint) {
|
|
76748
|
+
async function ensureMcpBiDiSession(client, endpoint, sessionId) {
|
|
76331
76749
|
await client.sessionStatus({});
|
|
76332
|
-
if (isSessionSpecificFirefoxBidiEndpoint(endpoint)) return false;
|
|
76750
|
+
if (sessionId || isSessionSpecificFirefoxBidiEndpoint(endpoint)) return false;
|
|
76333
76751
|
try {
|
|
76334
76752
|
await client.browsingContextGetTree({});
|
|
76335
76753
|
return false;
|
|
@@ -76414,6 +76832,7 @@ var McpRuntime = class {
|
|
|
76414
76832
|
this.invalidateSnapshot();
|
|
76415
76833
|
this.pendingFileUploadTarget = void 0;
|
|
76416
76834
|
this.tabs = await session.newTab(url);
|
|
76835
|
+
if (this.snapshotMode === "none") return { tabs: this.tabs };
|
|
76417
76836
|
const snapshot = this.tabs.some((tab) => tab.active) ? await this.snapshot() : void 0;
|
|
76418
76837
|
return snapshot ? {
|
|
76419
76838
|
tabs: this.tabs,
|
|
@@ -76427,6 +76846,7 @@ var McpRuntime = class {
|
|
|
76427
76846
|
this.invalidateSnapshot();
|
|
76428
76847
|
this.pendingFileUploadTarget = void 0;
|
|
76429
76848
|
this.tabs = await session.selectTab(tab.id);
|
|
76849
|
+
if (this.snapshotMode === "none") return { tabs: this.tabs };
|
|
76430
76850
|
const snapshot = await this.snapshot();
|
|
76431
76851
|
return {
|
|
76432
76852
|
tabs: this.tabs,
|
|
@@ -76440,6 +76860,7 @@ var McpRuntime = class {
|
|
|
76440
76860
|
this.invalidateSnapshot();
|
|
76441
76861
|
this.pendingFileUploadTarget = void 0;
|
|
76442
76862
|
this.tabs = await session.closeTab(tab.id);
|
|
76863
|
+
if (this.snapshotMode === "none") return { tabs: this.tabs };
|
|
76443
76864
|
const snapshot = this.tabs.some((candidate) => candidate.active) ? await this.snapshot() : void 0;
|
|
76444
76865
|
return snapshot ? {
|
|
76445
76866
|
tabs: this.tabs,
|
|
@@ -76448,26 +76869,49 @@ var McpRuntime = class {
|
|
|
76448
76869
|
}
|
|
76449
76870
|
async snapshot(args = {}) {
|
|
76450
76871
|
const session = this.requireConnected();
|
|
76451
|
-
this.tabs = await session.listTabs();
|
|
76452
|
-
const activeTab = this.requireActiveTab();
|
|
76453
76872
|
const requestKey = this.snapshotRequestKey(args);
|
|
76454
76873
|
const request = {
|
|
76455
76874
|
...args.boxes !== void 0 ? { boxes: args.boxes } : {},
|
|
76456
76875
|
...args.depth !== void 0 ? { depth: args.depth } : {},
|
|
76457
76876
|
...args.target ? { target: this.resolveSnapshotTarget(args.target) } : {}
|
|
76458
76877
|
};
|
|
76459
|
-
const snapshot = await
|
|
76878
|
+
const { activeTab, currentActiveTab, snapshot } = await this.captureStableSnapshot(session, request);
|
|
76460
76879
|
this.snapshotCache = {
|
|
76461
|
-
tabId:
|
|
76880
|
+
tabId: currentActiveTab.id,
|
|
76462
76881
|
requestKey,
|
|
76463
76882
|
text: snapshot.text,
|
|
76464
76883
|
refs: { ...snapshot.refs },
|
|
76465
|
-
title: snapshot.title,
|
|
76466
|
-
url: snapshot.url,
|
|
76884
|
+
title: currentActiveTab.title || snapshot.title,
|
|
76885
|
+
url: currentActiveTab.url || snapshot.url,
|
|
76467
76886
|
...snapshot.console ? { console: { ...snapshot.console } } : {},
|
|
76468
76887
|
...snapshot.consoleLink ? { consoleLink: snapshot.consoleLink } : {}
|
|
76469
76888
|
};
|
|
76470
|
-
return
|
|
76889
|
+
return {
|
|
76890
|
+
...snapshot,
|
|
76891
|
+
title: currentActiveTab.title || snapshot.title,
|
|
76892
|
+
url: currentActiveTab.url || snapshot.url
|
|
76893
|
+
};
|
|
76894
|
+
}
|
|
76895
|
+
async captureStableSnapshot(session, request) {
|
|
76896
|
+
let lastAttempt;
|
|
76897
|
+
for (let attempt = 0; attempt < 4; attempt += 1) {
|
|
76898
|
+
this.tabs = await session.listTabs();
|
|
76899
|
+
const activeTab = this.requireActiveTab();
|
|
76900
|
+
const snapshot = await session.snapshot(request);
|
|
76901
|
+
const refreshedTabs = await session.listTabs();
|
|
76902
|
+
this.tabs = refreshedTabs;
|
|
76903
|
+
const currentActiveTab = refreshedTabs.find((tab) => tab.active) ?? refreshedTabs.find((tab) => tab.id === activeTab.id) ?? activeTab;
|
|
76904
|
+
const captured = {
|
|
76905
|
+
activeTab,
|
|
76906
|
+
currentActiveTab,
|
|
76907
|
+
snapshot
|
|
76908
|
+
};
|
|
76909
|
+
lastAttempt = captured;
|
|
76910
|
+
if (!snapshot.retryable || snapshot.text.trim().length > 0 || currentActiveTab.url === "about:blank") return captured;
|
|
76911
|
+
await delay(150 * (attempt + 1));
|
|
76912
|
+
}
|
|
76913
|
+
if (!lastAttempt) throw new McpToolError("action_failed", "Unable to capture page snapshot.");
|
|
76914
|
+
return lastAttempt;
|
|
76471
76915
|
}
|
|
76472
76916
|
async click(target, opts) {
|
|
76473
76917
|
const session = this.requireConnected();
|
|
@@ -76486,6 +76930,7 @@ var McpRuntime = class {
|
|
|
76486
76930
|
this.invalidateSnapshot();
|
|
76487
76931
|
this.pendingFileUploadTarget = opensFileChooser ? resolved : void 0;
|
|
76488
76932
|
if (this.snapshotMode === "none") return;
|
|
76933
|
+
if (await session.hasDialog()) return;
|
|
76489
76934
|
return this.snapshot();
|
|
76490
76935
|
}
|
|
76491
76936
|
async hover(target) {
|
|
@@ -76495,13 +76940,16 @@ var McpRuntime = class {
|
|
|
76495
76940
|
this.invalidateSnapshot();
|
|
76496
76941
|
this.pendingFileUploadTarget = void 0;
|
|
76497
76942
|
if (this.snapshotMode === "none") return;
|
|
76943
|
+
if (await session.hasDialog()) return;
|
|
76498
76944
|
return this.snapshot();
|
|
76499
76945
|
}
|
|
76500
76946
|
async navigate(url) {
|
|
76501
|
-
|
|
76947
|
+
const session = this.requireConnected();
|
|
76948
|
+
await session.navigate(normalizeNavigationUrl(url));
|
|
76502
76949
|
this.invalidateSnapshot();
|
|
76503
76950
|
this.pendingFileUploadTarget = void 0;
|
|
76504
76951
|
if (this.snapshotMode === "none") return;
|
|
76952
|
+
if (await session.hasDialog()) return;
|
|
76505
76953
|
return this.snapshot();
|
|
76506
76954
|
}
|
|
76507
76955
|
async type(ref, text, opts) {
|
|
@@ -76650,6 +77098,9 @@ var McpRuntime = class {
|
|
|
76650
77098
|
async networkRequest(index) {
|
|
76651
77099
|
return this.requireConnected().networkRequest(index);
|
|
76652
77100
|
}
|
|
77101
|
+
async fetchResponseBody(index) {
|
|
77102
|
+
return this.requireConnected().fetchResponseBody(index);
|
|
77103
|
+
}
|
|
76653
77104
|
async runCodeUnsafe(code) {
|
|
76654
77105
|
const result = await this.requireConnected().runCodeUnsafe(code);
|
|
76655
77106
|
this.invalidateSnapshot();
|
|
@@ -76686,6 +77137,10 @@ var McpRuntime = class {
|
|
|
76686
77137
|
hasPendingFileUploadTarget() {
|
|
76687
77138
|
return !!this.pendingFileUploadTarget;
|
|
76688
77139
|
}
|
|
77140
|
+
async hasDialog() {
|
|
77141
|
+
if (!this.connection) return false;
|
|
77142
|
+
return this.connection.session.hasDialog();
|
|
77143
|
+
}
|
|
76689
77144
|
async close() {
|
|
76690
77145
|
this.invalidateSnapshot();
|
|
76691
77146
|
this.pendingFileUploadTarget = void 0;
|
|
@@ -78306,6 +78761,7 @@ async function createRoxyBrowserMcpInMemory(options = {}) {
|
|
|
78306
78761
|
await bundle.server.connect(serverTransport);
|
|
78307
78762
|
return {
|
|
78308
78763
|
server: bundle.server,
|
|
78764
|
+
runtimeManager: bundle.runtimeManager,
|
|
78309
78765
|
serverTransport,
|
|
78310
78766
|
clientTransport,
|
|
78311
78767
|
close: async () => {
|