@roxybrowser/playwright 2.0.2-beta.0 → 2.0.2-beta.10
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/README.md +85 -7
- package/dist/bin/roxybrowser-mcp.js +72 -2
- package/dist/bin/roxybrowser-mcp.js.map +1 -1
- package/dist/browser.d.ts +33 -5
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +134 -15
- package/dist/browser.js.map +1 -1
- package/dist/browserType.d.ts +23 -1
- package/dist/browserType.d.ts.map +1 -1
- package/dist/browserType.js +33 -5
- 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/context.d.ts +3 -0
- package/dist/mcp/backend/context.d.ts.map +1 -1
- package/dist/mcp/backend/context.js +9 -1
- package/dist/mcp/backend/context.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 +53 -7
- package/dist/mcp/backend/response.js.map +1 -1
- package/dist/mcp/connectedBrowser.d.ts.map +1 -1
- package/dist/mcp/connectedBrowser.js +494 -37
- package/dist/mcp/connectedBrowser.js.map +1 -1
- package/dist/mcp/output.d.ts +5 -0
- package/dist/mcp/output.d.ts.map +1 -1
- package/dist/mcp/output.js +17 -1
- package/dist/mcp/output.js.map +1 -1
- package/dist/mcp/runtime.d.ts +7 -0
- package/dist/mcp/runtime.d.ts.map +1 -1
- package/dist/mcp/runtime.js +84 -9
- package/dist/mcp/runtime.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +3 -1
- package/dist/mcp/server.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 +7 -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/adapter.d.ts +1 -0
- package/dist/protocol/adapter.d.ts.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/protocol/bidi/client.d.ts +1 -0
- package/dist/protocol/bidi/client.d.ts.map +1 -1
- package/dist/protocol/bidi/client.js +48 -1
- package/dist/protocol/bidi/client.js.map +1 -1
- package/dist/protocol/cdp/backend.d.ts.map +1 -1
- package/dist/protocol/cdp/backend.js +122 -8
- package/dist/protocol/cdp/backend.js.map +1 -1
- package/dist/roxybrowser.bundle.js +1060 -467
- package/dist/roxybrowser.bundle.js.map +1 -1
- package/dist/types/api.d.ts +23 -4
- package/dist/types/api.d.ts.map +1 -1
- package/package.json +11 -4
|
@@ -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);
|
|
@@ -14532,8 +14642,10 @@ var WebSocketBidiClient = class {
|
|
|
14532
14642
|
eventListeners = /* @__PURE__ */ new Map();
|
|
14533
14643
|
pendingCommands = /* @__PURE__ */ new Map();
|
|
14534
14644
|
socket;
|
|
14645
|
+
webSocketUrl;
|
|
14535
14646
|
constructor(options) {
|
|
14536
14647
|
this.capabilities = { browserName: options.browserName };
|
|
14648
|
+
this.webSocketUrl = options.webSocketUrl;
|
|
14537
14649
|
this.socket = new globalThis.WebSocket(options.webSocketUrl);
|
|
14538
14650
|
this.socket.addEventListener("message", (event) => this.handleMessage(event.data));
|
|
14539
14651
|
this.socket.addEventListener("close", () => this.handleClose());
|
|
@@ -14543,7 +14655,7 @@ var WebSocketBidiClient = class {
|
|
|
14543
14655
|
return new Promise((resolve, reject) => {
|
|
14544
14656
|
this.socket.addEventListener("open", resolve, { once: true });
|
|
14545
14657
|
this.socket.addEventListener("error", (event) => {
|
|
14546
|
-
reject(
|
|
14658
|
+
reject(new Error(formatBidiConnectError(event, this.webSocketUrl)));
|
|
14547
14659
|
}, { once: true });
|
|
14548
14660
|
});
|
|
14549
14661
|
}
|
|
@@ -14714,6 +14826,28 @@ var WebSocketBidiClient = class {
|
|
|
14714
14826
|
this.pendingCommands.clear();
|
|
14715
14827
|
}
|
|
14716
14828
|
};
|
|
14829
|
+
function formatBidiConnectError(event, webSocketUrl) {
|
|
14830
|
+
const details = extractBidiConnectErrorDetails(event);
|
|
14831
|
+
return details ? `Failed to establish a WebDriver BiDi connection to ${webSocketUrl}: ${details}` : `Failed to establish a WebDriver BiDi connection to ${webSocketUrl}.`;
|
|
14832
|
+
}
|
|
14833
|
+
function extractBidiConnectErrorDetails(event) {
|
|
14834
|
+
if (event instanceof Error) return event.message;
|
|
14835
|
+
if (!event || typeof event !== "object") return typeof event === "string" ? event : void 0;
|
|
14836
|
+
const candidate = event;
|
|
14837
|
+
const parts = [];
|
|
14838
|
+
if (typeof candidate.message === "string" && candidate.message) parts.push(candidate.message);
|
|
14839
|
+
if (typeof candidate.error === "string" && candidate.error) parts.push(candidate.error);
|
|
14840
|
+
else if (candidate.error instanceof Error && candidate.error.message) parts.push(candidate.error.message);
|
|
14841
|
+
const socketLike = candidate.target ?? candidate.currentTarget;
|
|
14842
|
+
if (socketLike && typeof socketLike === "object") {
|
|
14843
|
+
const socketParts = [];
|
|
14844
|
+
if (typeof socketLike.url === "string" && socketLike.url) socketParts.push(`url=${socketLike.url}`);
|
|
14845
|
+
if (typeof socketLike.readyState === "number") socketParts.push(`readyState=${socketLike.readyState}`);
|
|
14846
|
+
if (socketParts.length > 0) parts.push(`socket(${socketParts.join(", ")})`);
|
|
14847
|
+
}
|
|
14848
|
+
if (parts.length > 0) return parts.join("; ");
|
|
14849
|
+
if (typeof candidate.type === "string" && candidate.type) return `event type=${candidate.type}`;
|
|
14850
|
+
}
|
|
14717
14851
|
var bidiClientFactory = createBidiClient;
|
|
14718
14852
|
function getBidiClientFactory() {
|
|
14719
14853
|
return bidiClientFactory;
|
|
@@ -15981,7 +16115,7 @@ var BidiPageAdapter = class BidiPageAdapter {
|
|
|
15981
16115
|
resultOwnership: "none"
|
|
15982
16116
|
}));
|
|
15983
16117
|
if (response.type === "exception") throw new Error(response.exceptionDetails.text || "BiDi evaluation failed.");
|
|
15984
|
-
return parseSerializedEvaluationResult(extractBiDiValue(response.result));
|
|
16118
|
+
return parseSerializedEvaluationResult(extractBiDiValue$1(response.result));
|
|
15985
16119
|
}
|
|
15986
16120
|
async evaluateFunction(expression, arg) {
|
|
15987
16121
|
const serializedArg = arg === void 0 ? "" : serializeForEvaluation$1(arg);
|
|
@@ -17225,11 +17359,11 @@ var BidiElementHandleAdapter = class BidiElementHandleAdapter {
|
|
|
17225
17359
|
return this.page.selectOptionReference(this.reference(), values, options);
|
|
17226
17360
|
}
|
|
17227
17361
|
};
|
|
17228
|
-
function extractBiDiValue(value) {
|
|
17229
|
-
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));
|
|
17230
17364
|
if (value.type === "object" && Array.isArray(value.value)) {
|
|
17231
17365
|
const obj = {};
|
|
17232
|
-
for (const [key, val] of value.value) obj[key] = extractBiDiValue(val);
|
|
17366
|
+
for (const [key, val] of value.value) obj[key] = extractBiDiValue$1(val);
|
|
17233
17367
|
return obj;
|
|
17234
17368
|
}
|
|
17235
17369
|
return value.value;
|
|
@@ -17590,13 +17724,13 @@ function buildFirefoxBidiEndpoint(wsEndpoint, sessionId) {
|
|
|
17590
17724
|
if (url.pathname === "/" || url.pathname === "") url.pathname = "/session";
|
|
17591
17725
|
return url.toString();
|
|
17592
17726
|
}
|
|
17593
|
-
function isSessionSpecificFirefoxBidiEndpoint(wsEndpoint) {
|
|
17727
|
+
function isSessionSpecificFirefoxBidiEndpoint$1(wsEndpoint) {
|
|
17594
17728
|
const pathname = new URL(wsEndpoint).pathname;
|
|
17595
17729
|
return /^\/session\/[^/]+$/.test(pathname);
|
|
17596
17730
|
}
|
|
17597
17731
|
async function ensureBiDiSession(client, sessionId, wsEndpoint) {
|
|
17598
17732
|
await client.sessionStatus({});
|
|
17599
|
-
if (sessionId || isSessionSpecificFirefoxBidiEndpoint(wsEndpoint)) return false;
|
|
17733
|
+
if (sessionId || isSessionSpecificFirefoxBidiEndpoint$1(wsEndpoint)) return false;
|
|
17600
17734
|
try {
|
|
17601
17735
|
await client.browsingContextGetTree({});
|
|
17602
17736
|
return false;
|
|
@@ -17621,9 +17755,9 @@ function buildFirefoxLaunchArgs(options, userDataDir, port) {
|
|
|
17621
17755
|
...options.args ?? []
|
|
17622
17756
|
];
|
|
17623
17757
|
}
|
|
17624
|
-
function resolveFirefoxExecutableCandidates(options, platform = currentPlatform$1(), fileExistsFn = fileExists$1) {
|
|
17758
|
+
function resolveFirefoxExecutableCandidates(options, platform = currentPlatform$1(), playwrightFirefoxExecutablePath, fileExistsFn = fileExists$1) {
|
|
17625
17759
|
if (options.executablePath) return [options.executablePath];
|
|
17626
|
-
return filterExistingFirefoxExecutableCandidates(defaultFirefoxExecutableCandidates(platform), platform, fileExistsFn);
|
|
17760
|
+
return filterExistingFirefoxExecutableCandidates([...playwrightFirefoxExecutablePath ? [playwrightFirefoxExecutablePath] : [], ...defaultFirefoxExecutableCandidates(platform)], platform, fileExistsFn);
|
|
17627
17761
|
}
|
|
17628
17762
|
function defaultFirefoxExecutableCandidates(platform) {
|
|
17629
17763
|
const candidates = [];
|
|
@@ -53274,11 +53408,34 @@ var RoxyBrowserType = class {
|
|
|
53274
53408
|
protocol: options.protocol ?? (this.browserName === "firefox" ? "bidi" : "cdp")
|
|
53275
53409
|
});
|
|
53276
53410
|
}
|
|
53277
|
-
|
|
53411
|
+
/**
|
|
53412
|
+
* Connect to an already-running browser over a WebSocket endpoint.
|
|
53413
|
+
*
|
|
53414
|
+
* ⚠️ DIVERGENCE FROM PLAYWRIGHT — DO NOT "FIX" THIS BACK TO CDP-ONLY:
|
|
53415
|
+
*
|
|
53416
|
+
* In upstream Playwright, `BrowserType.connect()` is a CDP-only operation: it
|
|
53417
|
+
* connects to a running Chromium over the DevTools Protocol. Firefox/WebKit are
|
|
53418
|
+
* not supported here (Playwright routes BiDi through `connectOverCDP`-style
|
|
53419
|
+
* helpers or its own internal plumbing, not this method).
|
|
53420
|
+
*
|
|
53421
|
+
* Roxy intentionally changes that semantics. This method dispatches on
|
|
53422
|
+
* `browserName` so a single entry point serves both browser families:
|
|
53423
|
+
* - chromium → CDP (ws:// DevTools endpoint)
|
|
53424
|
+
* - firefox → BiDi (ws:// WebDriver BiDi endpoint)
|
|
53425
|
+
*
|
|
53426
|
+
* This is deliberate, not a bug. If you are an agent "normalizing" this code to
|
|
53427
|
+
* match Playwright upstream, STOP — the dual-protocol dispatch is the feature.
|
|
53428
|
+
* To force CDP specifically, call `connectOverCDP()` instead, which remains
|
|
53429
|
+
* chromium-only and CDP-only by design (see the guard below).
|
|
53430
|
+
*
|
|
53431
|
+
* `options.protocol` may still be passed to override the per-browser default.
|
|
53432
|
+
*/
|
|
53433
|
+
async connect(endpointURL, options) {
|
|
53278
53434
|
return this.connectBrowser({
|
|
53279
|
-
|
|
53280
|
-
|
|
53281
|
-
|
|
53435
|
+
browserName: this.browserName,
|
|
53436
|
+
protocol: this.browserName === "chromium" ? "cdp" : "bidi",
|
|
53437
|
+
wsEndpoint: endpointURL,
|
|
53438
|
+
...options
|
|
53282
53439
|
});
|
|
53283
53440
|
}
|
|
53284
53441
|
async connectOverCDP(progressOrEndpointURL, endpointURLOrOptions, maybeOptions = {}) {
|
|
@@ -53310,7 +53467,9 @@ var RoxyBrowserType = class {
|
|
|
53310
53467
|
protocol
|
|
53311
53468
|
});
|
|
53312
53469
|
await adapter.connect();
|
|
53313
|
-
|
|
53470
|
+
const session = await adapter.browser();
|
|
53471
|
+
const versionStr = await session.version();
|
|
53472
|
+
return new RoxyBrowser(session, adapter, resolveHumanizationOptions(options.human), options.browserName ?? this.browserName, this, versionStr);
|
|
53314
53473
|
}
|
|
53315
53474
|
};
|
|
53316
53475
|
var chromium = new RoxyBrowserType("chromium", {
|
|
@@ -73280,11 +73439,6 @@ function formatConnectResult(input) {
|
|
|
73280
73439
|
if (input.snapshot) parts.push(formatSnapshot(input.snapshot));
|
|
73281
73440
|
return parts.join("\n\n");
|
|
73282
73441
|
}
|
|
73283
|
-
function formatTabsWithOptionalSnapshot(tabs, snapshot) {
|
|
73284
|
-
const parts = [formatTabs(tabs)];
|
|
73285
|
-
if (snapshot) parts.push(formatSnapshot(snapshot));
|
|
73286
|
-
return parts.join("\n\n");
|
|
73287
|
-
}
|
|
73288
73442
|
//#endregion
|
|
73289
73443
|
//#region src/mcp/backend/response.ts
|
|
73290
73444
|
var Response$1 = class {
|
|
@@ -73294,9 +73448,11 @@ var Response$1 = class {
|
|
|
73294
73448
|
results = [];
|
|
73295
73449
|
errors = [];
|
|
73296
73450
|
code = [];
|
|
73451
|
+
images = [];
|
|
73297
73452
|
includeSnapshot = "none";
|
|
73298
73453
|
fullSnapshot;
|
|
73299
73454
|
isClose = false;
|
|
73455
|
+
rawResults = false;
|
|
73300
73456
|
constructor(context, toolName, toolArgs) {
|
|
73301
73457
|
this.context = context;
|
|
73302
73458
|
this.toolName = toolName;
|
|
@@ -73311,9 +73467,18 @@ var Response$1 = class {
|
|
|
73311
73467
|
addCode(code) {
|
|
73312
73468
|
this.code.push(code);
|
|
73313
73469
|
}
|
|
73470
|
+
addImageResult(data, mimeType) {
|
|
73471
|
+
this.images.push({
|
|
73472
|
+
data,
|
|
73473
|
+
mimeType
|
|
73474
|
+
});
|
|
73475
|
+
}
|
|
73314
73476
|
setClose() {
|
|
73315
73477
|
this.isClose = true;
|
|
73316
73478
|
}
|
|
73479
|
+
setRawResults() {
|
|
73480
|
+
this.rawResults = true;
|
|
73481
|
+
}
|
|
73317
73482
|
setIncludeSnapshot() {
|
|
73318
73483
|
this.includeSnapshot = this.context.config.snapshot?.mode ?? "full";
|
|
73319
73484
|
}
|
|
@@ -73329,6 +73494,21 @@ var Response$1 = class {
|
|
|
73329
73494
|
async serialize() {
|
|
73330
73495
|
const sections = [];
|
|
73331
73496
|
if (this.errors.length) sections.push("### Error", ...this.errors);
|
|
73497
|
+
if (this.rawResults) {
|
|
73498
|
+
if (this.results.length) sections.push(...this.results);
|
|
73499
|
+
return {
|
|
73500
|
+
content: [{
|
|
73501
|
+
type: "text",
|
|
73502
|
+
text: sections.join("\n")
|
|
73503
|
+
}, ...this.images.map((image) => ({
|
|
73504
|
+
type: "image",
|
|
73505
|
+
data: image.data,
|
|
73506
|
+
mimeType: image.mimeType
|
|
73507
|
+
}))],
|
|
73508
|
+
...this.isClose ? { isClose: true } : {},
|
|
73509
|
+
...this.errors.length ? { isError: true } : {}
|
|
73510
|
+
};
|
|
73511
|
+
}
|
|
73332
73512
|
if (this.results.length) {
|
|
73333
73513
|
if (sections.length) sections.push("");
|
|
73334
73514
|
sections.push("### Result", ...this.results);
|
|
@@ -73338,16 +73518,23 @@ var Response$1 = class {
|
|
|
73338
73518
|
sections.push("### Code", "```js", ...this.code, "```");
|
|
73339
73519
|
}
|
|
73340
73520
|
if (this.includeSnapshot === "full") {
|
|
73341
|
-
|
|
73342
|
-
|
|
73343
|
-
|
|
73521
|
+
if (!await this.context.runtime.hasDialog()) {
|
|
73522
|
+
const snapshot = await reconcileSnapshotWithTabs(this.context, await this.context.runtime.snapshot());
|
|
73523
|
+
if (sections.length) sections.push("");
|
|
73524
|
+
sections.push(formatSnapshot(snapshot));
|
|
73525
|
+
}
|
|
73344
73526
|
}
|
|
73345
73527
|
if (this.fullSnapshot) {
|
|
73346
|
-
|
|
73528
|
+
let snapshot = await reconcileSnapshotWithTabs(this.context, await this.context.runtime.snapshot({
|
|
73347
73529
|
...this.fullSnapshot.target !== void 0 ? { target: this.fullSnapshot.target } : {},
|
|
73348
73530
|
...this.fullSnapshot.depth !== void 0 ? { depth: this.fullSnapshot.depth } : {},
|
|
73349
73531
|
...this.fullSnapshot.boxes !== void 0 ? { boxes: this.fullSnapshot.boxes } : {}
|
|
73350
|
-
});
|
|
73532
|
+
}));
|
|
73533
|
+
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({
|
|
73534
|
+
...this.fullSnapshot.target !== void 0 ? { target: this.fullSnapshot.target } : {},
|
|
73535
|
+
...this.fullSnapshot.depth !== void 0 ? { depth: this.fullSnapshot.depth } : {},
|
|
73536
|
+
...this.fullSnapshot.boxes !== void 0 ? { boxes: this.fullSnapshot.boxes } : {}
|
|
73537
|
+
}));
|
|
73351
73538
|
if (this.fullSnapshot.filename) {
|
|
73352
73539
|
const resolvedFilename = await this.context.resolveOutputFile(this.fullSnapshot.filename);
|
|
73353
73540
|
await writeFile(resolvedFilename, snapshot.text);
|
|
@@ -73364,17 +73551,30 @@ var Response$1 = class {
|
|
|
73364
73551
|
content: [{
|
|
73365
73552
|
type: "text",
|
|
73366
73553
|
text: sections.join("\n")
|
|
73367
|
-
}
|
|
73554
|
+
}, ...this.images.map((image) => ({
|
|
73555
|
+
type: "image",
|
|
73556
|
+
data: image.data,
|
|
73557
|
+
mimeType: image.mimeType
|
|
73558
|
+
}))],
|
|
73368
73559
|
...this.isClose ? { isClose: true } : {},
|
|
73369
73560
|
...this.errors.length ? { isError: true } : {}
|
|
73370
73561
|
};
|
|
73371
73562
|
}
|
|
73372
73563
|
};
|
|
73373
|
-
|
|
73374
|
-
|
|
73375
|
-
|
|
73564
|
+
async function reconcileSnapshotWithTabs(context, snapshot) {
|
|
73565
|
+
const activeTab = (await context.runtime.listTabs()).find((tab) => tab.active);
|
|
73566
|
+
if (!activeTab) return snapshot;
|
|
73567
|
+
return {
|
|
73568
|
+
...snapshot,
|
|
73569
|
+
title: activeTab.title || snapshot.title,
|
|
73570
|
+
url: activeTab.url || snapshot.url
|
|
73571
|
+
};
|
|
73572
|
+
}
|
|
73376
73573
|
//#endregion
|
|
73377
73574
|
//#region src/mcp/backend/tool.ts
|
|
73575
|
+
function defineTool$1(tool) {
|
|
73576
|
+
return tool;
|
|
73577
|
+
}
|
|
73378
73578
|
function defineTabTool(tool) {
|
|
73379
73579
|
return {
|
|
73380
73580
|
...tool,
|
|
@@ -73391,6 +73591,146 @@ function missingModalStateMessage(tool) {
|
|
|
73391
73591
|
if (tool.clearsModalState === "fileChooser") return "[no_file_chooser] No file chooser visible.";
|
|
73392
73592
|
return `Error: The tool "${tool.schema.name}" can only be used when there is related modal state present.`;
|
|
73393
73593
|
}
|
|
73594
|
+
var common_default = [defineTool$1({
|
|
73595
|
+
capability: "core-tabs",
|
|
73596
|
+
schema: {
|
|
73597
|
+
name: "browser_close",
|
|
73598
|
+
title: "Close browser",
|
|
73599
|
+
description: "Close the current browser session.",
|
|
73600
|
+
inputSchema: object({}),
|
|
73601
|
+
type: "action"
|
|
73602
|
+
},
|
|
73603
|
+
handle: async (context, _params, response) => {
|
|
73604
|
+
await context.runtime.close();
|
|
73605
|
+
response.setClose();
|
|
73606
|
+
response.addTextResult("Browser session closed.");
|
|
73607
|
+
}
|
|
73608
|
+
}), defineTool$1({
|
|
73609
|
+
capability: "core",
|
|
73610
|
+
schema: {
|
|
73611
|
+
name: "browser_resize",
|
|
73612
|
+
title: "Resize browser",
|
|
73613
|
+
description: "Resize the active page viewport.",
|
|
73614
|
+
inputSchema: object({
|
|
73615
|
+
width: number().int().positive(),
|
|
73616
|
+
height: number().int().positive()
|
|
73617
|
+
}),
|
|
73618
|
+
type: "action"
|
|
73619
|
+
},
|
|
73620
|
+
handle: async (context, params, response) => {
|
|
73621
|
+
const snapshot = await context.runtime.resize(params.width, params.height);
|
|
73622
|
+
response.setIncludeSnapshot();
|
|
73623
|
+
response.addCode(`await page.setViewportSize({ width: ${params.width}, height: ${params.height} });`);
|
|
73624
|
+
if (!snapshot) response.addTextResult(`Resized viewport to ${params.width}x${params.height}.`);
|
|
73625
|
+
}
|
|
73626
|
+
})];
|
|
73627
|
+
var console_default = [defineTool$1({
|
|
73628
|
+
capability: "core",
|
|
73629
|
+
schema: {
|
|
73630
|
+
name: "browser_console_messages",
|
|
73631
|
+
title: "Get console messages",
|
|
73632
|
+
description: "Returns all console messages",
|
|
73633
|
+
inputSchema: object({
|
|
73634
|
+
level: _enum([
|
|
73635
|
+
"error",
|
|
73636
|
+
"warning",
|
|
73637
|
+
"info",
|
|
73638
|
+
"debug"
|
|
73639
|
+
]).default("info").describe("Level of the console messages to return. Each level includes the messages of more severe levels. Defaults to \"info\"."),
|
|
73640
|
+
all: boolean().optional().describe("Return all console messages since the beginning of the session, not just since the last navigation. Defaults to false."),
|
|
73641
|
+
filename: string().optional().describe("Filename to save the console messages to. If not provided, messages are returned as text.")
|
|
73642
|
+
}),
|
|
73643
|
+
type: "readOnly"
|
|
73644
|
+
},
|
|
73645
|
+
handle: async (context, params, response) => {
|
|
73646
|
+
const messages = await context.runtime.consoleMessages(params.level, params.all);
|
|
73647
|
+
const errors = messages.filter((message) => message.type === "error" || message.type === "assert").length;
|
|
73648
|
+
const warnings = messages.filter((message) => message.type === "warning").length;
|
|
73649
|
+
const text = [
|
|
73650
|
+
`Total messages: ${messages.length} (Errors: ${errors}, Warnings: ${warnings})`,
|
|
73651
|
+
"",
|
|
73652
|
+
...messages.map((message) => message.formattedText)
|
|
73653
|
+
].join("\n");
|
|
73654
|
+
if (params.filename) {
|
|
73655
|
+
const resolvedFilename = await context.resolveOutputFile(params.filename);
|
|
73656
|
+
await writeFile(resolvedFilename, text);
|
|
73657
|
+
response.addTextResult(`Saved console messages to "${resolvedFilename}".`);
|
|
73658
|
+
return;
|
|
73659
|
+
}
|
|
73660
|
+
response.addTextResult(text);
|
|
73661
|
+
}
|
|
73662
|
+
})];
|
|
73663
|
+
var connect_default = [defineTool$1({
|
|
73664
|
+
capability: "config",
|
|
73665
|
+
schema: {
|
|
73666
|
+
name: "roxy_browser_connect",
|
|
73667
|
+
title: "Roxy Browser Connect",
|
|
73668
|
+
description: "Attach to an existing browser and seed the active tab snapshot.",
|
|
73669
|
+
inputSchema: object({
|
|
73670
|
+
endpoint: string().min(1),
|
|
73671
|
+
browser: _enum(["chrome", "firefox"]).default("chrome"),
|
|
73672
|
+
sessionId: string().min(1).optional()
|
|
73673
|
+
}),
|
|
73674
|
+
type: "action"
|
|
73675
|
+
},
|
|
73676
|
+
handle: async (context, params, response) => {
|
|
73677
|
+
const protocol = params.browser === "firefox" ? "bidi" : "cdp";
|
|
73678
|
+
const result = await context.runtime.connect({
|
|
73679
|
+
protocol,
|
|
73680
|
+
endpoint: params.endpoint,
|
|
73681
|
+
browser: params.browser === "chrome" ? "chromium" : params.browser,
|
|
73682
|
+
...params.sessionId ? { sessionId: params.sessionId } : {}
|
|
73683
|
+
});
|
|
73684
|
+
response.addTextResult(formatConnectResult({
|
|
73685
|
+
...result,
|
|
73686
|
+
browserName: result.browserName === "chromium" ? "chrome" : result.browserName
|
|
73687
|
+
}));
|
|
73688
|
+
}
|
|
73689
|
+
})];
|
|
73690
|
+
var dialogs_default = [defineTool$1({
|
|
73691
|
+
capability: "core",
|
|
73692
|
+
schema: {
|
|
73693
|
+
name: "browser_handle_dialog",
|
|
73694
|
+
title: "Handle a dialog",
|
|
73695
|
+
description: "Handle a dialog",
|
|
73696
|
+
inputSchema: object({
|
|
73697
|
+
accept: boolean().describe("Whether to accept the dialog."),
|
|
73698
|
+
promptText: string().optional().describe("The text of the prompt in case of a prompt dialog.")
|
|
73699
|
+
}),
|
|
73700
|
+
type: "action"
|
|
73701
|
+
},
|
|
73702
|
+
handle: async (context, params, response) => {
|
|
73703
|
+
const snapshot = await context.runtime.handleDialog(params.accept, params.promptText);
|
|
73704
|
+
response.setIncludeSnapshot();
|
|
73705
|
+
if (!snapshot) response.addTextResult(params.accept ? "Accepted dialog." : "Dismissed dialog.");
|
|
73706
|
+
}
|
|
73707
|
+
})];
|
|
73708
|
+
var evaluate_default = [defineTool$1({
|
|
73709
|
+
capability: "core",
|
|
73710
|
+
schema: {
|
|
73711
|
+
name: "browser_evaluate",
|
|
73712
|
+
title: "Evaluate JavaScript",
|
|
73713
|
+
description: "Evaluate JavaScript expression on page or element",
|
|
73714
|
+
inputSchema: object({
|
|
73715
|
+
element: string().optional().describe("Human-readable element description used to obtain permission to interact with the element"),
|
|
73716
|
+
target: string().optional().describe("Exact target element reference from the page snapshot, or a unique element selector"),
|
|
73717
|
+
function: string().describe("() => { /* code */ } or (element) => { /* code */ } when element is provided"),
|
|
73718
|
+
filename: string().optional().describe("Filename to save the result to. If not provided, result is returned as text.")
|
|
73719
|
+
}),
|
|
73720
|
+
type: "action"
|
|
73721
|
+
},
|
|
73722
|
+
handle: async (context, params, response) => {
|
|
73723
|
+
const result = await context.runtime.evaluate(params.function, params.target);
|
|
73724
|
+
const text = JSON.stringify(result, null, 2) ?? "undefined";
|
|
73725
|
+
if (params.filename) {
|
|
73726
|
+
const resolvedFilename = await context.resolveOutputFile(params.filename);
|
|
73727
|
+
await writeFile(resolvedFilename, text);
|
|
73728
|
+
response.addTextResult(`Saved evaluation result to "${resolvedFilename}".`);
|
|
73729
|
+
return;
|
|
73730
|
+
}
|
|
73731
|
+
response.addTextResult(text);
|
|
73732
|
+
}
|
|
73733
|
+
})];
|
|
73394
73734
|
var files_default = [defineTabTool({
|
|
73395
73735
|
capability: "core",
|
|
73396
73736
|
schema: {
|
|
@@ -73602,7 +73942,7 @@ var typeSchema = elementSchema$2.extend({
|
|
|
73602
73942
|
submit: boolean().optional().describe("Whether to submit entered text (press Enter after)"),
|
|
73603
73943
|
slowly: boolean().optional().describe("Whether to type one character at a time. Useful for triggering key handlers in the page. By default entire text is filled in at once.")
|
|
73604
73944
|
});
|
|
73605
|
-
var keyboard_default
|
|
73945
|
+
var keyboard_default = [defineTabTool({
|
|
73606
73946
|
capability: "core-input",
|
|
73607
73947
|
schema: {
|
|
73608
73948
|
name: "browser_press_key",
|
|
@@ -73648,150 +73988,55 @@ var keyboard_default$1 = [defineTabTool({
|
|
|
73648
73988
|
});
|
|
73649
73989
|
}
|
|
73650
73990
|
})];
|
|
73651
|
-
|
|
73652
|
-
|
|
73653
|
-
|
|
73654
|
-
|
|
73655
|
-
|
|
73656
|
-
|
|
73657
|
-
|
|
73658
|
-
|
|
73659
|
-
|
|
73660
|
-
|
|
73661
|
-
|
|
73662
|
-
|
|
73663
|
-
|
|
73664
|
-
|
|
73665
|
-
|
|
73666
|
-
this.name = "McpToolError";
|
|
73667
|
-
}
|
|
73668
|
-
};
|
|
73669
|
-
function isMcpToolError(error) {
|
|
73670
|
-
return error instanceof McpToolError;
|
|
73671
|
-
}
|
|
73672
|
-
//#endregion
|
|
73673
|
-
//#region src/mcp/tool.ts
|
|
73674
|
-
function defineTool(tool) {
|
|
73675
|
-
return tool;
|
|
73676
|
-
}
|
|
73677
|
-
function textResult(text, isError = false) {
|
|
73678
|
-
return {
|
|
73679
|
-
content: [{
|
|
73680
|
-
type: "text",
|
|
73681
|
-
text
|
|
73682
|
-
}],
|
|
73683
|
-
...isError ? { isError: true } : {}
|
|
73684
|
-
};
|
|
73685
|
-
}
|
|
73686
|
-
var connect_default = [defineTool({
|
|
73687
|
-
schema: {
|
|
73688
|
-
name: "roxy_browser_connect",
|
|
73689
|
-
title: "Roxy Browser Connect",
|
|
73690
|
-
description: "Attach to an existing browser over CDP or BiDi and seed the active tab snapshot.",
|
|
73691
|
-
inputSchema: object({
|
|
73692
|
-
protocol: _enum(["cdp", "bidi"]),
|
|
73693
|
-
endpoint: string().min(1),
|
|
73694
|
-
browser: _enum(["chromium", "firefox"]).optional()
|
|
73695
|
-
})
|
|
73696
|
-
},
|
|
73697
|
-
handle: async (args, runtime) => {
|
|
73698
|
-
return textResult(formatConnectResult(await runtime.connect({
|
|
73699
|
-
protocol: args.protocol,
|
|
73700
|
-
endpoint: args.endpoint,
|
|
73701
|
-
...args.browser ? { browser: args.browser } : {}
|
|
73702
|
-
})));
|
|
73703
|
-
}
|
|
73704
|
-
})];
|
|
73705
|
-
var common_default = [defineTool({
|
|
73706
|
-
schema: {
|
|
73707
|
-
name: "browser_close",
|
|
73708
|
-
title: "Close browser",
|
|
73709
|
-
description: "Close the page",
|
|
73710
|
-
inputSchema: object({})
|
|
73711
|
-
},
|
|
73712
|
-
handle: async (_args, runtime) => {
|
|
73713
|
-
await runtime.close();
|
|
73714
|
-
return textResult(formatTabs([]));
|
|
73715
|
-
}
|
|
73716
|
-
}), defineTool({
|
|
73717
|
-
schema: {
|
|
73718
|
-
name: "browser_resize",
|
|
73719
|
-
title: "Resize browser window",
|
|
73720
|
-
description: "Resize the browser window",
|
|
73721
|
-
inputSchema: object({
|
|
73722
|
-
width: number().describe("Width of the browser window"),
|
|
73723
|
-
height: number().describe("Height of the browser window")
|
|
73724
|
-
})
|
|
73725
|
-
},
|
|
73726
|
-
handle: async (args, runtime) => {
|
|
73727
|
-
const snap = await runtime.resize(args.width, args.height);
|
|
73728
|
-
if (!snap) return textResult(`Resized browser window to ${args.width}x${args.height}.`);
|
|
73729
|
-
return textResult(formatSnapshot(snap));
|
|
73730
|
-
}
|
|
73731
|
-
})];
|
|
73732
|
-
var tabs_default = [defineTool({
|
|
73733
|
-
schema: {
|
|
73734
|
-
name: "browser_tabs",
|
|
73735
|
-
title: "Browser Tabs",
|
|
73736
|
-
description: "List, create, select, and close browser tabs for the current MCP browser session.",
|
|
73737
|
-
inputSchema: object({
|
|
73738
|
-
action: _enum([
|
|
73739
|
-
"list",
|
|
73740
|
-
"new",
|
|
73741
|
-
"select",
|
|
73742
|
-
"close"
|
|
73743
|
-
]),
|
|
73744
|
-
index: number().int().nonnegative().optional(),
|
|
73745
|
-
url: string().url().optional()
|
|
73746
|
-
})
|
|
73747
|
-
},
|
|
73748
|
-
handle: async (args, runtime) => {
|
|
73749
|
-
if (args.action === "list") return textResult(formatTabs(await runtime.listTabs()));
|
|
73750
|
-
if (args.action === "new") {
|
|
73751
|
-
const result = await runtime.newTab(args.url);
|
|
73752
|
-
return textResult(formatTabsWithOptionalSnapshot(result.tabs, result.snapshot));
|
|
73991
|
+
var navigate_default = [
|
|
73992
|
+
defineTool$1({
|
|
73993
|
+
capability: "core-navigation",
|
|
73994
|
+
schema: {
|
|
73995
|
+
name: "browser_navigate",
|
|
73996
|
+
title: "Navigate to a URL",
|
|
73997
|
+
description: "Navigate to a URL",
|
|
73998
|
+
inputSchema: object({ url: string().describe("The URL to navigate to") }),
|
|
73999
|
+
type: "action"
|
|
74000
|
+
},
|
|
74001
|
+
handle: async (context, params, response) => {
|
|
74002
|
+
await context.ensureTab();
|
|
74003
|
+
await context.runtime.navigate(params.url);
|
|
74004
|
+
response.setIncludeSnapshot();
|
|
74005
|
+
response.addCode(`await page.goto('${params.url.startsWith("http") ? params.url : params.url.startsWith("localhost") ? `http://${params.url}` : `https://${params.url}`}');`);
|
|
73753
74006
|
}
|
|
73754
|
-
|
|
73755
|
-
|
|
73756
|
-
|
|
74007
|
+
}),
|
|
74008
|
+
defineTool$1({
|
|
74009
|
+
capability: "core-navigation",
|
|
74010
|
+
schema: {
|
|
74011
|
+
name: "browser_navigate_back",
|
|
74012
|
+
title: "Go back",
|
|
74013
|
+
description: "Go back to the previous page in the history",
|
|
74014
|
+
inputSchema: object({}),
|
|
74015
|
+
type: "action"
|
|
74016
|
+
},
|
|
74017
|
+
handle: async (context, _params, response) => {
|
|
74018
|
+
await context.runtime.goBack();
|
|
74019
|
+
response.setIncludeSnapshot();
|
|
74020
|
+
response.addCode("await page.goBack();");
|
|
73757
74021
|
}
|
|
73758
|
-
|
|
73759
|
-
|
|
73760
|
-
|
|
73761
|
-
|
|
73762
|
-
|
|
73763
|
-
|
|
73764
|
-
|
|
73765
|
-
|
|
73766
|
-
|
|
73767
|
-
|
|
73768
|
-
|
|
73769
|
-
|
|
73770
|
-
|
|
73771
|
-
|
|
73772
|
-
|
|
73773
|
-
|
|
73774
|
-
|
|
73775
|
-
|
|
73776
|
-
});
|
|
73777
|
-
var goBack = defineTool({
|
|
73778
|
-
schema: {
|
|
73779
|
-
name: "browser_navigate_back",
|
|
73780
|
-
title: "Go back",
|
|
73781
|
-
description: "Go back to the previous page in the history",
|
|
73782
|
-
inputSchema: object({})
|
|
73783
|
-
},
|
|
73784
|
-
handle: async (_args, runtime) => {
|
|
73785
|
-
const snap = await runtime.goBack();
|
|
73786
|
-
if (!snap) return textResult("Navigated back.");
|
|
73787
|
-
return textResult(formatSnapshot(snap));
|
|
73788
|
-
}
|
|
73789
|
-
});
|
|
73790
|
-
object({});
|
|
73791
|
-
var navigate_default = [
|
|
73792
|
-
navigate,
|
|
73793
|
-
goBack,
|
|
73794
|
-
defineTool({
|
|
74022
|
+
}),
|
|
74023
|
+
defineTool$1({
|
|
74024
|
+
capability: "core-navigation",
|
|
74025
|
+
schema: {
|
|
74026
|
+
name: "browser_navigate_forward",
|
|
74027
|
+
title: "Go forward",
|
|
74028
|
+
description: "Go forward to the next page in the history",
|
|
74029
|
+
inputSchema: object({}),
|
|
74030
|
+
type: "action"
|
|
74031
|
+
},
|
|
74032
|
+
handle: async (context, _params, response) => {
|
|
74033
|
+
await context.runtime.goForward();
|
|
74034
|
+
response.setIncludeSnapshot();
|
|
74035
|
+
response.addCode("await page.goForward();");
|
|
74036
|
+
}
|
|
74037
|
+
}),
|
|
74038
|
+
defineTool$1({
|
|
74039
|
+
capability: "core-navigation",
|
|
73795
74040
|
schema: {
|
|
73796
74041
|
name: "browser_wait_for",
|
|
73797
74042
|
title: "Wait for",
|
|
@@ -73800,228 +74045,31 @@ var navigate_default = [
|
|
|
73800
74045
|
time: number().optional().describe("The time to wait in seconds"),
|
|
73801
74046
|
text: string().optional().describe("The text to wait for"),
|
|
73802
74047
|
textGone: string().optional().describe("The text to wait for to disappear")
|
|
73803
|
-
})
|
|
74048
|
+
}),
|
|
74049
|
+
type: "action"
|
|
73804
74050
|
},
|
|
73805
|
-
handle: async (
|
|
73806
|
-
if (!
|
|
73807
|
-
|
|
73808
|
-
|
|
73809
|
-
|
|
73810
|
-
...
|
|
73811
|
-
|
|
74051
|
+
handle: async (context, params, response) => {
|
|
74052
|
+
if (!params.text && !params.textGone && !params.time) throw new Error("Either time, text or textGone must be provided");
|
|
74053
|
+
const waitSeconds = params.time;
|
|
74054
|
+
if (waitSeconds !== void 0) await new Promise((resolve) => setTimeout(resolve, Math.min(3e4, waitSeconds * 1e3)));
|
|
74055
|
+
if (params.text || params.textGone) await context.runtime.waitFor({
|
|
74056
|
+
...params.text !== void 0 ? { text: params.text } : {},
|
|
74057
|
+
...params.textGone !== void 0 ? { textGone: params.textGone } : {}
|
|
74058
|
+
}, 5e3);
|
|
74059
|
+
response.setIncludeSnapshot();
|
|
73812
74060
|
}
|
|
73813
74061
|
})
|
|
73814
74062
|
];
|
|
73815
|
-
object({
|
|
73816
|
-
element: string().optional().describe("Human-readable element description used to obtain permission to interact with the element"),
|
|
73817
|
-
target: string().describe("Exact target element reference from the page snapshot, or a unique element selector")
|
|
73818
|
-
}).extend({
|
|
73819
|
-
doubleClick: boolean().optional().describe("Whether to perform a double click instead of a single click"),
|
|
73820
|
-
button: _enum([
|
|
73821
|
-
"left",
|
|
73822
|
-
"right",
|
|
73823
|
-
"middle"
|
|
73824
|
-
]).optional().describe("Button to click, defaults to left"),
|
|
73825
|
-
modifiers: array(_enum([
|
|
73826
|
-
"Alt",
|
|
73827
|
-
"Control",
|
|
73828
|
-
"ControlOrMeta",
|
|
73829
|
-
"Meta",
|
|
73830
|
-
"Shift"
|
|
73831
|
-
])).optional().describe("Modifier keys to press during the click"),
|
|
73832
|
-
human: object({ profile: _enum([
|
|
73833
|
-
"cautious",
|
|
73834
|
-
"balanced",
|
|
73835
|
-
"fast"
|
|
73836
|
-
]).optional().describe("Humanization timing profile, defaults to balanced") }).optional().describe("Humanization settings for this click")
|
|
73837
|
-
});
|
|
73838
|
-
object({
|
|
73839
|
-
startElement: string().optional().describe("Human-readable source element description used to obtain the permission to interact with the element"),
|
|
73840
|
-
startTarget: string().describe("Exact target element reference from the page snapshot, or a unique element selector"),
|
|
73841
|
-
endElement: string().optional().describe("Human-readable target element description used to obtain the permission to interact with the element"),
|
|
73842
|
-
endTarget: string().describe("Exact target element reference from the page snapshot, or a unique element selector")
|
|
73843
|
-
});
|
|
73844
|
-
object({
|
|
73845
|
-
element: string().optional().describe("Human-readable element description used to obtain permission"),
|
|
73846
|
-
ref: string().optional().describe("Element to scroll; omit to scroll the whole page"),
|
|
73847
|
-
deltaX: number().optional().describe("Horizontal scroll delta in pixels (default 0)"),
|
|
73848
|
-
deltaY: number().optional().describe("Vertical scroll delta in pixels (default 0)")
|
|
73849
|
-
});
|
|
73850
|
-
var mouse_default = [];
|
|
73851
74063
|
//#endregion
|
|
73852
|
-
//#region src/mcp/
|
|
73853
|
-
object({
|
|
73854
|
-
element: string().optional().describe("Human-readable element description used to obtain permission"),
|
|
73855
|
-
target: string().describe("Exact target element reference from the page snapshot, or a unique element selector"),
|
|
73856
|
-
text: string().describe("Text to type into the element"),
|
|
73857
|
-
submit: boolean().optional().describe("Whether to submit entered text (press Enter after)"),
|
|
73858
|
-
slowly: boolean().optional().describe("Whether to type one character at a time. Useful for triggering key handlers in the page. By default entire text is filled in at once.")
|
|
73859
|
-
});
|
|
73860
|
-
object({ key: string().describe("Key to press, e.g. Enter, Escape, Tab, ArrowLeft, Backspace, Delete, or printable characters") });
|
|
73861
|
-
var keyboard_default = [];
|
|
73862
|
-
object({
|
|
73863
|
-
element: string().optional().describe("Human-readable element description used to obtain permission"),
|
|
73864
|
-
target: string().describe("Exact target element reference from the page snapshot, or a unique element selector")
|
|
73865
|
-
}).extend({ values: array(string()).describe("Option values or visible labels to select") });
|
|
73866
|
-
object({ paths: array(string()).optional().describe("The absolute paths to the files to upload. Can be single file or multiple files. If omitted, file chooser is cancelled.") });
|
|
73867
|
-
var form_default = [defineTool({
|
|
73868
|
-
schema: {
|
|
73869
|
-
name: "browser_fill_form",
|
|
73870
|
-
title: "Fill form",
|
|
73871
|
-
description: "Fill multiple form fields",
|
|
73872
|
-
inputSchema: object({ fields: array(object({
|
|
73873
|
-
name: string().describe("Human-readable field name"),
|
|
73874
|
-
type: _enum([
|
|
73875
|
-
"textbox",
|
|
73876
|
-
"checkbox",
|
|
73877
|
-
"radio",
|
|
73878
|
-
"combobox",
|
|
73879
|
-
"slider"
|
|
73880
|
-
]).describe("Type of the field"),
|
|
73881
|
-
target: string().describe("Exact target element reference from the page snapshot, or a unique element selector"),
|
|
73882
|
-
value: string().describe("Value to fill in the field. If the field is a checkbox, the value should be `true` or `false`. If the field is a combobox, the value should be the text of the option.")
|
|
73883
|
-
})).describe("Fields to fill in") })
|
|
73884
|
-
},
|
|
73885
|
-
handle: async (args, runtime) => {
|
|
73886
|
-
const snap = await runtime.fillForm(args.fields);
|
|
73887
|
-
if (!snap) return textResult("Filled form.");
|
|
73888
|
-
return textResult(formatSnapshot(snap));
|
|
73889
|
-
}
|
|
73890
|
-
}), defineTool({
|
|
73891
|
-
schema: {
|
|
73892
|
-
name: "browser_drop",
|
|
73893
|
-
title: "Drop files or data onto an element",
|
|
73894
|
-
description: "Drop files or MIME-typed data onto an element, as if dragged from outside the page. At least one of paths or data must be provided.",
|
|
73895
|
-
inputSchema: object({
|
|
73896
|
-
element: string().optional().describe("Human-readable element description used to obtain permission to interact with the element"),
|
|
73897
|
-
target: string().describe("Exact target element reference from the page snapshot, or a unique element selector"),
|
|
73898
|
-
paths: array(string()).optional().describe("Absolute paths to files to drop onto the element."),
|
|
73899
|
-
data: record(string(), string()).optional().describe("Data to drop, as a map of MIME type to string value.")
|
|
73900
|
-
})
|
|
73901
|
-
},
|
|
73902
|
-
handle: async (args, runtime) => {
|
|
73903
|
-
if (!args.paths?.length && !args.data) throw new Error("At least one of \"paths\" or \"data\" must be provided.");
|
|
73904
|
-
const snap = await runtime.drop(args.target, {
|
|
73905
|
-
...args.paths !== void 0 ? { paths: args.paths } : {},
|
|
73906
|
-
...args.data !== void 0 ? { data: args.data } : {}
|
|
73907
|
-
});
|
|
73908
|
-
if (!snap) return textResult(`Dropped data onto "${args.element ?? args.target}".`);
|
|
73909
|
-
return textResult(formatSnapshot(snap));
|
|
73910
|
-
}
|
|
73911
|
-
})];
|
|
73912
|
-
var screenshot_default = [defineTool({
|
|
73913
|
-
schema: {
|
|
73914
|
-
name: "browser_take_screenshot",
|
|
73915
|
-
title: "Browser Take Screenshot",
|
|
73916
|
-
description: "Capture a full-page screenshot of the active tab as a base64-encoded PNG.",
|
|
73917
|
-
inputSchema: object({
|
|
73918
|
-
element: string().optional().describe("Human-readable description of the area to screenshot"),
|
|
73919
|
-
target: string().optional().describe("Element reference or CSS selector to clip screenshot to; omit for full page"),
|
|
73920
|
-
type: _enum(["png", "jpeg"]).default("png").describe("Image format for the screenshot. Default is png."),
|
|
73921
|
-
filename: string().optional().describe("File name to save the screenshot to."),
|
|
73922
|
-
fullPage: boolean().optional().describe("When true, takes a screenshot of the full scrollable page, instead of the currently visible viewport. Cannot be used with element screenshots.")
|
|
73923
|
-
})
|
|
73924
|
-
},
|
|
73925
|
-
handle: async (args, runtime) => {
|
|
73926
|
-
const target = args.target;
|
|
73927
|
-
const result = await runtime.takeScreenshot({
|
|
73928
|
-
type: args.type,
|
|
73929
|
-
...args.fullPage !== void 0 ? { fullPage: args.fullPage } : {},
|
|
73930
|
-
...target !== void 0 ? { target } : {}
|
|
73931
|
-
});
|
|
73932
|
-
if (args.filename) {
|
|
73933
|
-
const resolvedFilename = await resolveOutputFilePath(args.filename, { outputDir: runtime.getOutputDir() });
|
|
73934
|
-
await writeFile(resolvedFilename, Buffer.from(result.data, "base64"));
|
|
73935
|
-
return textResult(`Screenshot saved to "${resolvedFilename}".`);
|
|
73936
|
-
}
|
|
73937
|
-
return { content: [{
|
|
73938
|
-
type: "image",
|
|
73939
|
-
data: result.data,
|
|
73940
|
-
mimeType: result.mimeType
|
|
73941
|
-
}] };
|
|
73942
|
-
}
|
|
73943
|
-
})];
|
|
73944
|
-
var console_default = [defineTool({
|
|
73945
|
-
schema: {
|
|
73946
|
-
name: "browser_console_messages",
|
|
73947
|
-
title: "Get console messages",
|
|
73948
|
-
description: "Returns all console messages",
|
|
73949
|
-
inputSchema: object({
|
|
73950
|
-
level: _enum([
|
|
73951
|
-
"error",
|
|
73952
|
-
"warning",
|
|
73953
|
-
"info",
|
|
73954
|
-
"debug"
|
|
73955
|
-
]).default("info").describe("Level of the console messages to return. Each level includes the messages of more severe levels. Defaults to \"info\"."),
|
|
73956
|
-
all: boolean().optional().describe("Return all console messages since the beginning of the session, not just since the last navigation. Defaults to false."),
|
|
73957
|
-
filename: string().optional().describe("Filename to save the console messages to. If not provided, messages are returned as text.")
|
|
73958
|
-
})
|
|
73959
|
-
},
|
|
73960
|
-
handle: async (args, runtime) => {
|
|
73961
|
-
const messages = await runtime.consoleMessages(args.level, args.all);
|
|
73962
|
-
const errors = messages.filter((message) => message.type === "error" || message.type === "assert").length;
|
|
73963
|
-
const warnings = messages.filter((message) => message.type === "warning").length;
|
|
73964
|
-
const text = [
|
|
73965
|
-
`Total messages: ${messages.length} (Errors: ${errors}, Warnings: ${warnings})`,
|
|
73966
|
-
"",
|
|
73967
|
-
...messages.map((message) => message.formattedText)
|
|
73968
|
-
].join("\n");
|
|
73969
|
-
if (args.filename) {
|
|
73970
|
-
const resolvedFilename = await resolveOutputFilePath(args.filename, { outputDir: runtime.getOutputDir() });
|
|
73971
|
-
await writeFile(resolvedFilename, text);
|
|
73972
|
-
return textResult(`Saved console messages to "${resolvedFilename}".`);
|
|
73973
|
-
}
|
|
73974
|
-
return textResult(text);
|
|
73975
|
-
}
|
|
73976
|
-
})];
|
|
73977
|
-
var evaluate_default = [defineTool({
|
|
73978
|
-
schema: {
|
|
73979
|
-
name: "browser_evaluate",
|
|
73980
|
-
title: "Evaluate JavaScript",
|
|
73981
|
-
description: "Evaluate JavaScript expression on page or element",
|
|
73982
|
-
inputSchema: object({
|
|
73983
|
-
element: string().optional().describe("Human-readable element description used to obtain permission to interact with the element"),
|
|
73984
|
-
target: string().optional().describe("Exact target element reference from the page snapshot, or a unique element selector"),
|
|
73985
|
-
function: string().describe("() => { /* code */ } or (element) => { /* code */ } when element is provided"),
|
|
73986
|
-
filename: string().optional().describe("Filename to save the result to. If not provided, result is returned as text.")
|
|
73987
|
-
})
|
|
73988
|
-
},
|
|
73989
|
-
handle: async (args, runtime) => {
|
|
73990
|
-
const result = await runtime.evaluate(args.function, args.target);
|
|
73991
|
-
const text = JSON.stringify(result, null, 2) ?? "undefined";
|
|
73992
|
-
if (args.filename) {
|
|
73993
|
-
const resolvedFilename = await resolveOutputFilePath(args.filename, { outputDir: runtime.getOutputDir() });
|
|
73994
|
-
await writeFile(resolvedFilename, text);
|
|
73995
|
-
return textResult(`Saved evaluation result to "${resolvedFilename}".`);
|
|
73996
|
-
}
|
|
73997
|
-
return textResult(text);
|
|
73998
|
-
}
|
|
73999
|
-
})];
|
|
74000
|
-
var dialog_default = [defineTool({
|
|
74001
|
-
schema: {
|
|
74002
|
-
name: "browser_handle_dialog",
|
|
74003
|
-
title: "Handle a dialog",
|
|
74004
|
-
description: "Handle a dialog",
|
|
74005
|
-
inputSchema: object({
|
|
74006
|
-
accept: boolean().describe("Whether to accept the dialog."),
|
|
74007
|
-
promptText: string().optional().describe("The text of the prompt in case of a prompt dialog.")
|
|
74008
|
-
})
|
|
74009
|
-
},
|
|
74010
|
-
handle: async (args, runtime) => {
|
|
74011
|
-
const snap = await runtime.handleDialog(args.accept, args.promptText);
|
|
74012
|
-
if (!snap) return textResult(args.accept ? "Accepted dialog." : "Dismissed dialog.");
|
|
74013
|
-
return textResult(formatSnapshot(snap));
|
|
74014
|
-
}
|
|
74015
|
-
})];
|
|
74016
|
-
//#endregion
|
|
74017
|
-
//#region src/mcp/tools/network.ts
|
|
74064
|
+
//#region src/mcp/backend/network.ts
|
|
74018
74065
|
var requestParts = [
|
|
74019
74066
|
"request-headers",
|
|
74020
74067
|
"request-body",
|
|
74021
74068
|
"response-headers",
|
|
74022
74069
|
"response-body"
|
|
74023
74070
|
];
|
|
74024
|
-
var networkRequests = defineTool({
|
|
74071
|
+
var networkRequests = defineTool$1({
|
|
74072
|
+
capability: "core",
|
|
74025
74073
|
schema: {
|
|
74026
74074
|
name: "browser_network_requests",
|
|
74027
74075
|
title: "List network requests",
|
|
@@ -74030,10 +74078,11 @@ var networkRequests = defineTool({
|
|
|
74030
74078
|
static: boolean().default(false).describe("Whether to include successful static resources like images, fonts, scripts, etc. Defaults to false."),
|
|
74031
74079
|
filter: string().optional().describe("Only return requests whose URL matches this regexp (e.g. \"/api/.*user\")."),
|
|
74032
74080
|
filename: string().optional().describe("Filename to save the network requests to. If not provided, requests are returned as text.")
|
|
74033
|
-
})
|
|
74081
|
+
}),
|
|
74082
|
+
type: "readOnly"
|
|
74034
74083
|
},
|
|
74035
|
-
handle: async (args,
|
|
74036
|
-
const requests = await runtime.networkRequests();
|
|
74084
|
+
handle: async (context, args, response) => {
|
|
74085
|
+
const requests = await context.runtime.networkRequests();
|
|
74037
74086
|
const filter = args.filter ? new RegExp(args.filter) : void 0;
|
|
74038
74087
|
const lines = [];
|
|
74039
74088
|
let hiddenStaticCount = 0;
|
|
@@ -74048,14 +74097,16 @@ var networkRequests = defineTool({
|
|
|
74048
74097
|
if (hiddenStaticCount > 0) lines.push(`\nNote: ${hiddenStaticCount} static request${hiddenStaticCount === 1 ? "" : "s"} not shown, run with "static" option to see ${hiddenStaticCount === 1 ? "it" : "them"}.`);
|
|
74049
74098
|
const text = lines.join("\n");
|
|
74050
74099
|
if (args.filename) {
|
|
74051
|
-
const resolvedFilename = await
|
|
74100
|
+
const resolvedFilename = await context.resolveOutputFile(args.filename);
|
|
74052
74101
|
await writeFile(resolvedFilename, text);
|
|
74053
|
-
|
|
74102
|
+
response.addTextResult(`Saved network requests to "${resolvedFilename}".`);
|
|
74103
|
+
return;
|
|
74054
74104
|
}
|
|
74055
|
-
|
|
74105
|
+
response.addTextResult(text);
|
|
74056
74106
|
}
|
|
74057
74107
|
});
|
|
74058
|
-
var networkRequest = defineTool({
|
|
74108
|
+
var networkRequest = defineTool$1({
|
|
74109
|
+
capability: "core",
|
|
74059
74110
|
schema: {
|
|
74060
74111
|
name: "browser_network_request",
|
|
74061
74112
|
title: "Show network request details",
|
|
@@ -74064,18 +74115,33 @@ var networkRequest = defineTool({
|
|
|
74064
74115
|
index: number().int().min(1).describe("1-based index of the request, as printed by browser_network_requests."),
|
|
74065
74116
|
part: _enum(requestParts).optional().describe("Return only this part of the request. Omit to return full details."),
|
|
74066
74117
|
filename: string().optional().describe("Filename to save the result to. If not provided, output is returned as text.")
|
|
74067
|
-
})
|
|
74118
|
+
}),
|
|
74119
|
+
type: "readOnly"
|
|
74068
74120
|
},
|
|
74069
|
-
handle: async (args,
|
|
74070
|
-
const request = await runtime.networkRequest(args.index);
|
|
74071
|
-
if (!request)
|
|
74072
|
-
|
|
74121
|
+
handle: async (context, args, response) => {
|
|
74122
|
+
const request = await context.runtime.networkRequest(args.index);
|
|
74123
|
+
if (!request) {
|
|
74124
|
+
response.addError(`Request #${args.index} not found. Use browser_network_requests to see available indexes.`);
|
|
74125
|
+
return;
|
|
74126
|
+
}
|
|
74127
|
+
if (args.part) {
|
|
74128
|
+
response.setRawResults();
|
|
74129
|
+
const partText = args.part === "response-body" ? await context.runtime.fetchResponseBody(args.index) ?? "" : renderRequestPart(request, args.part);
|
|
74130
|
+
if (args.filename) {
|
|
74131
|
+
const resolvedFilename = await context.resolveOutputFile(args.filename);
|
|
74132
|
+
await writeFile(resolvedFilename, partText);
|
|
74133
|
+
response.addTextResult(`Saved network request to "${resolvedFilename}".`);
|
|
74134
|
+
} else response.addTextResult(partText);
|
|
74135
|
+
return;
|
|
74136
|
+
}
|
|
74137
|
+
const text = renderRequestDetails(request);
|
|
74073
74138
|
if (args.filename) {
|
|
74074
|
-
const resolvedFilename = await
|
|
74139
|
+
const resolvedFilename = await context.resolveOutputFile(args.filename);
|
|
74075
74140
|
await writeFile(resolvedFilename, text);
|
|
74076
|
-
|
|
74141
|
+
response.addTextResult(`Saved network request to "${resolvedFilename}".`);
|
|
74142
|
+
return;
|
|
74077
74143
|
}
|
|
74078
|
-
|
|
74144
|
+
response.addTextResult(text);
|
|
74079
74145
|
}
|
|
74080
74146
|
});
|
|
74081
74147
|
function isSuccessfulResponse(request) {
|
|
@@ -74102,8 +74168,10 @@ function renderRequestDetails(request) {
|
|
|
74102
74168
|
if (request.mimeType) lines.push(` mimeType: ${request.mimeType}`);
|
|
74103
74169
|
appendHeaders(lines, "Request headers", request.requestHeaders);
|
|
74104
74170
|
if (request.responseHeaders) appendHeaders(lines, "Response headers", request.responseHeaders);
|
|
74105
|
-
|
|
74106
|
-
if (request.
|
|
74171
|
+
const hints = [];
|
|
74172
|
+
if (request.requestBody) hints.push(`Call browser_network_request with part="request-body" to read the request body.`);
|
|
74173
|
+
if (canHaveResponseBody(request)) hints.push(`Call browser_network_request with part="response-body" to read the response body.`);
|
|
74174
|
+
if (hints.length) lines.push("", ...hints);
|
|
74107
74175
|
return lines.join("\n");
|
|
74108
74176
|
}
|
|
74109
74177
|
function renderRequestPart(request, part) {
|
|
@@ -74122,46 +74190,247 @@ function appendHeaders(lines, title, headers) {
|
|
|
74122
74190
|
function renderHeaders(headers) {
|
|
74123
74191
|
return Object.entries(headers).map(([key, value]) => `${key}: ${value}`).join("\n");
|
|
74124
74192
|
}
|
|
74193
|
+
function canHaveResponseBody(request) {
|
|
74194
|
+
if (request.failureText || request.status === void 0) return false;
|
|
74195
|
+
return request.status !== 204 && request.status !== 304 && !(request.status >= 100 && request.status < 200);
|
|
74196
|
+
}
|
|
74125
74197
|
var network_default = [networkRequests, networkRequest];
|
|
74126
|
-
var runCode_default = [defineTool({
|
|
74198
|
+
var runCode_default = [defineTool$1({
|
|
74199
|
+
capability: "devtools",
|
|
74127
74200
|
schema: {
|
|
74128
74201
|
name: "browser_run_code_unsafe",
|
|
74129
|
-
title: "Run
|
|
74130
|
-
description: "Run
|
|
74202
|
+
title: "Run code (unsafe)",
|
|
74203
|
+
description: "Run arbitrary code against the current browser session.",
|
|
74204
|
+
inputSchema: object({ code: string().describe("JavaScript code to run against the browser session.") }),
|
|
74205
|
+
type: "action"
|
|
74206
|
+
},
|
|
74207
|
+
handle: async (context, args, response) => {
|
|
74208
|
+
const result = await context.runtime.runCodeUnsafe(args.code);
|
|
74209
|
+
response.addTextResult(JSON.stringify(result, null, 2) ?? "undefined");
|
|
74210
|
+
}
|
|
74211
|
+
})];
|
|
74212
|
+
var screenshot_default = [defineTool$1({
|
|
74213
|
+
capability: "core",
|
|
74214
|
+
schema: {
|
|
74215
|
+
name: "browser_take_screenshot",
|
|
74216
|
+
title: "Browser Take Screenshot",
|
|
74217
|
+
description: "Capture a full-page screenshot of the active tab as a base64-encoded PNG.",
|
|
74131
74218
|
inputSchema: object({
|
|
74132
|
-
|
|
74133
|
-
|
|
74134
|
-
|
|
74219
|
+
element: string().optional().describe("Human-readable description of the area to screenshot"),
|
|
74220
|
+
target: string().optional().describe("Element reference or CSS selector to clip screenshot to; omit for full page"),
|
|
74221
|
+
type: _enum(["png", "jpeg"]).default("png").describe("Image format for the screenshot. Default is png."),
|
|
74222
|
+
filename: string().optional().describe("File name to save the screenshot to."),
|
|
74223
|
+
fullPage: boolean().optional().describe("When true, takes a screenshot of the full scrollable page, instead of the currently visible viewport. Cannot be used with element screenshots.")
|
|
74224
|
+
}),
|
|
74225
|
+
type: "readOnly"
|
|
74135
74226
|
},
|
|
74136
|
-
handle: async (args,
|
|
74137
|
-
const
|
|
74138
|
-
|
|
74139
|
-
|
|
74140
|
-
|
|
74227
|
+
handle: async (context, args, response) => {
|
|
74228
|
+
const result = await context.runtime.takeScreenshot({
|
|
74229
|
+
type: args.type,
|
|
74230
|
+
...args.fullPage !== void 0 ? { fullPage: args.fullPage } : {},
|
|
74231
|
+
...args.target !== void 0 ? { target: args.target } : {}
|
|
74232
|
+
});
|
|
74233
|
+
const requestedFilename = args.filename?.trim();
|
|
74234
|
+
const resolvedFilename = await context.resolveOutputFile(requestedFilename || `page-${(/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-")}.${args.type}`);
|
|
74235
|
+
await writeFile(resolvedFilename, Buffer.from(result.data, "base64"));
|
|
74236
|
+
if (requestedFilename) {
|
|
74237
|
+
response.addTextResult(`Screenshot saved to "${resolvedFilename}".`);
|
|
74238
|
+
return;
|
|
74239
|
+
}
|
|
74240
|
+
response.addTextResult(resolvedFilename);
|
|
74241
|
+
response.addImageResult(result.data, result.mimeType);
|
|
74242
|
+
}
|
|
74243
|
+
})];
|
|
74244
|
+
var tabs_default = [defineTool$1({
|
|
74245
|
+
capability: "core-tabs",
|
|
74246
|
+
schema: {
|
|
74247
|
+
name: "browser_tabs",
|
|
74248
|
+
title: "Browser Tabs",
|
|
74249
|
+
description: "List, create, select, and close browser tabs for the current MCP browser session.",
|
|
74250
|
+
inputSchema: object({
|
|
74251
|
+
action: _enum([
|
|
74252
|
+
"list",
|
|
74253
|
+
"new",
|
|
74254
|
+
"select",
|
|
74255
|
+
"close"
|
|
74256
|
+
]).describe("Operation to perform"),
|
|
74257
|
+
index: number().optional().describe("Tab index, used for close/select. If omitted for close, current tab is closed."),
|
|
74258
|
+
url: string().optional().describe("URL to navigate to in the new tab, used for new.")
|
|
74259
|
+
}),
|
|
74260
|
+
type: "action"
|
|
74261
|
+
},
|
|
74262
|
+
handle: async (context, params, response) => {
|
|
74263
|
+
switch (params.action) {
|
|
74264
|
+
case "list":
|
|
74265
|
+
await context.ensureTab();
|
|
74266
|
+
break;
|
|
74267
|
+
case "new":
|
|
74268
|
+
await context.runtime.newTab(params.url);
|
|
74269
|
+
if (params.url) {
|
|
74270
|
+
response.setIncludeSnapshot();
|
|
74271
|
+
response.addCode(`await page.goto('${params.url}');`);
|
|
74272
|
+
}
|
|
74273
|
+
break;
|
|
74274
|
+
case "close":
|
|
74275
|
+
await context.runtime.closeTab(params.index ?? 0);
|
|
74276
|
+
break;
|
|
74277
|
+
case "select":
|
|
74278
|
+
if (params.index === void 0) throw new Error("Tab index is required");
|
|
74279
|
+
await context.runtime.selectTab(params.index);
|
|
74280
|
+
break;
|
|
74281
|
+
}
|
|
74282
|
+
const tabs = await context.runtime.listTabs();
|
|
74283
|
+
response.addTextResult(formatTabs(tabs));
|
|
74141
74284
|
}
|
|
74142
74285
|
})];
|
|
74143
74286
|
//#endregion
|
|
74144
|
-
//#region src/mcp/tools
|
|
74145
|
-
var
|
|
74146
|
-
...connect_default,
|
|
74287
|
+
//#region src/mcp/backend/tools.ts
|
|
74288
|
+
var browserTools = [
|
|
74147
74289
|
...common_default,
|
|
74148
|
-
...tabs_default,
|
|
74149
|
-
...navigate_default,
|
|
74150
|
-
...mouse_default,
|
|
74151
|
-
...keyboard_default,
|
|
74152
|
-
...form_default,
|
|
74153
|
-
...screenshot_default,
|
|
74154
74290
|
...console_default,
|
|
74291
|
+
...connect_default,
|
|
74292
|
+
...dialogs_default,
|
|
74155
74293
|
...evaluate_default,
|
|
74156
|
-
...
|
|
74294
|
+
...files_default,
|
|
74295
|
+
...keyboard_default,
|
|
74296
|
+
...navigate_default,
|
|
74157
74297
|
...network_default,
|
|
74158
|
-
...runCode_default
|
|
74298
|
+
...runCode_default,
|
|
74299
|
+
...screenshot_default,
|
|
74300
|
+
...snapshot_default,
|
|
74301
|
+
...tabs_default
|
|
74159
74302
|
];
|
|
74160
74303
|
//#endregion
|
|
74304
|
+
//#region src/mcp/errors.ts
|
|
74305
|
+
var McpToolError = class extends Error {
|
|
74306
|
+
code;
|
|
74307
|
+
constructor(code, message) {
|
|
74308
|
+
super(message);
|
|
74309
|
+
this.code = code;
|
|
74310
|
+
this.name = "McpToolError";
|
|
74311
|
+
}
|
|
74312
|
+
};
|
|
74313
|
+
function isMcpToolError(error) {
|
|
74314
|
+
return error instanceof McpToolError;
|
|
74315
|
+
}
|
|
74316
|
+
//#endregion
|
|
74317
|
+
//#region src/mcp/tool.ts
|
|
74318
|
+
function defineTool(tool) {
|
|
74319
|
+
return tool;
|
|
74320
|
+
}
|
|
74321
|
+
function textResult(text, isError = false) {
|
|
74322
|
+
return {
|
|
74323
|
+
content: [{
|
|
74324
|
+
type: "text",
|
|
74325
|
+
text
|
|
74326
|
+
}],
|
|
74327
|
+
...isError ? { isError: true } : {}
|
|
74328
|
+
};
|
|
74329
|
+
}
|
|
74330
|
+
object({
|
|
74331
|
+
element: string().optional().describe("Human-readable element description used to obtain permission to interact with the element"),
|
|
74332
|
+
target: string().describe("Exact target element reference from the page snapshot, or a unique element selector")
|
|
74333
|
+
}).extend({
|
|
74334
|
+
doubleClick: boolean().optional().describe("Whether to perform a double click instead of a single click"),
|
|
74335
|
+
button: _enum([
|
|
74336
|
+
"left",
|
|
74337
|
+
"right",
|
|
74338
|
+
"middle"
|
|
74339
|
+
]).optional().describe("Button to click, defaults to left"),
|
|
74340
|
+
modifiers: array(_enum([
|
|
74341
|
+
"Alt",
|
|
74342
|
+
"Control",
|
|
74343
|
+
"ControlOrMeta",
|
|
74344
|
+
"Meta",
|
|
74345
|
+
"Shift"
|
|
74346
|
+
])).optional().describe("Modifier keys to press during the click"),
|
|
74347
|
+
human: object({ profile: _enum([
|
|
74348
|
+
"cautious",
|
|
74349
|
+
"balanced",
|
|
74350
|
+
"fast"
|
|
74351
|
+
]).optional().describe("Humanization timing profile, defaults to balanced") }).optional().describe("Humanization settings for this click")
|
|
74352
|
+
});
|
|
74353
|
+
object({
|
|
74354
|
+
startElement: string().optional().describe("Human-readable source element description used to obtain the permission to interact with the element"),
|
|
74355
|
+
startTarget: string().describe("Exact target element reference from the page snapshot, or a unique element selector"),
|
|
74356
|
+
endElement: string().optional().describe("Human-readable target element description used to obtain the permission to interact with the element"),
|
|
74357
|
+
endTarget: string().describe("Exact target element reference from the page snapshot, or a unique element selector")
|
|
74358
|
+
});
|
|
74359
|
+
object({
|
|
74360
|
+
element: string().optional().describe("Human-readable element description used to obtain permission"),
|
|
74361
|
+
ref: string().optional().describe("Element to scroll; omit to scroll the whole page"),
|
|
74362
|
+
deltaX: number().optional().describe("Horizontal scroll delta in pixels (default 0)"),
|
|
74363
|
+
deltaY: number().optional().describe("Vertical scroll delta in pixels (default 0)")
|
|
74364
|
+
});
|
|
74365
|
+
var mouse_default = [];
|
|
74366
|
+
object({
|
|
74367
|
+
element: string().optional().describe("Human-readable element description used to obtain permission"),
|
|
74368
|
+
target: string().describe("Exact target element reference from the page snapshot, or a unique element selector")
|
|
74369
|
+
}).extend({ values: array(string()).describe("Option values or visible labels to select") });
|
|
74370
|
+
object({ paths: array(string()).optional().describe("The absolute paths to the files to upload. Can be single file or multiple files. If omitted, file chooser is cancelled.") });
|
|
74371
|
+
var form_default = [defineTool({
|
|
74372
|
+
schema: {
|
|
74373
|
+
name: "browser_fill_form",
|
|
74374
|
+
title: "Fill form",
|
|
74375
|
+
description: "Fill multiple form fields",
|
|
74376
|
+
inputSchema: object({ fields: array(object({
|
|
74377
|
+
name: string().describe("Human-readable field name"),
|
|
74378
|
+
type: _enum([
|
|
74379
|
+
"textbox",
|
|
74380
|
+
"checkbox",
|
|
74381
|
+
"radio",
|
|
74382
|
+
"combobox",
|
|
74383
|
+
"slider"
|
|
74384
|
+
]).describe("Type of the field"),
|
|
74385
|
+
target: string().describe("Exact target element reference from the page snapshot, or a unique element selector"),
|
|
74386
|
+
value: string().describe("Value to fill in the field. If the field is a checkbox, the value should be `true` or `false`. If the field is a combobox, the value should be the text of the option.")
|
|
74387
|
+
})).describe("Fields to fill in") })
|
|
74388
|
+
},
|
|
74389
|
+
handle: async (args, runtime) => {
|
|
74390
|
+
const snap = await runtime.fillForm(args.fields);
|
|
74391
|
+
if (!snap) return textResult("Filled form.");
|
|
74392
|
+
return textResult(formatSnapshot(snap));
|
|
74393
|
+
}
|
|
74394
|
+
}), defineTool({
|
|
74395
|
+
schema: {
|
|
74396
|
+
name: "browser_drop",
|
|
74397
|
+
title: "Drop files or data onto an element",
|
|
74398
|
+
description: "Drop files or MIME-typed data onto an element, as if dragged from outside the page. At least one of paths or data must be provided.",
|
|
74399
|
+
inputSchema: object({
|
|
74400
|
+
element: string().optional().describe("Human-readable element description used to obtain permission to interact with the element"),
|
|
74401
|
+
target: string().describe("Exact target element reference from the page snapshot, or a unique element selector"),
|
|
74402
|
+
paths: array(string()).optional().describe("Absolute paths to files to drop onto the element."),
|
|
74403
|
+
data: record(string(), string()).optional().describe("Data to drop, as a map of MIME type to string value.")
|
|
74404
|
+
})
|
|
74405
|
+
},
|
|
74406
|
+
handle: async (args, runtime) => {
|
|
74407
|
+
if (!args.paths?.length && !args.data) throw new Error("At least one of \"paths\" or \"data\" must be provided.");
|
|
74408
|
+
const snap = await runtime.drop(args.target, {
|
|
74409
|
+
...args.paths !== void 0 ? { paths: args.paths } : {},
|
|
74410
|
+
...args.data !== void 0 ? { data: args.data } : {}
|
|
74411
|
+
});
|
|
74412
|
+
if (!snap) return textResult(`Dropped data onto "${args.element ?? args.target}".`);
|
|
74413
|
+
return textResult(formatSnapshot(snap));
|
|
74414
|
+
}
|
|
74415
|
+
})];
|
|
74416
|
+
//#endregion
|
|
74417
|
+
//#region src/mcp/tools/index.ts
|
|
74418
|
+
var allTools = [...mouse_default, ...form_default];
|
|
74419
|
+
//#endregion
|
|
74161
74420
|
//#region src/mcp/connectedBrowser.ts
|
|
74162
74421
|
function delay$1(ms) {
|
|
74163
74422
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
74164
74423
|
}
|
|
74424
|
+
async function withBiDiTimeout(promise, timeoutMs) {
|
|
74425
|
+
let timer;
|
|
74426
|
+
try {
|
|
74427
|
+
return await Promise.race([promise, new Promise((_, reject) => {
|
|
74428
|
+
timer = setTimeout(() => reject(/* @__PURE__ */ new Error(`Timed out after ${timeoutMs}ms.`)), timeoutMs);
|
|
74429
|
+
})]);
|
|
74430
|
+
} finally {
|
|
74431
|
+
if (timer) clearTimeout(timer);
|
|
74432
|
+
}
|
|
74433
|
+
}
|
|
74165
74434
|
var chromeRemoteInterface = "default" in import_chrome_remote_interface ? import_chrome_remote_interface.default : import_chrome_remote_interface;
|
|
74166
74435
|
function buildConnectionFromWsEndpoint(browserWsEndpoint) {
|
|
74167
74436
|
const parsed = new URL(browserWsEndpoint);
|
|
@@ -74595,13 +74864,13 @@ var CDP_KEY_MAP = {
|
|
|
74595
74864
|
async function evaluateBiDi(client, contextId, functionSource, arg) {
|
|
74596
74865
|
const expression = arg === void 0 ? `(${functionSource})()` : `(${functionSource})(${JSON.stringify(arg)})`;
|
|
74597
74866
|
const response = await client.scriptEvaluate({
|
|
74598
|
-
expression,
|
|
74867
|
+
expression: wrapWithSerializedEvaluationResult(expression),
|
|
74599
74868
|
target: { context: contextId },
|
|
74600
74869
|
awaitPromise: true,
|
|
74601
74870
|
resultOwnership: "none"
|
|
74602
74871
|
});
|
|
74603
74872
|
if (response.type === "exception") throw new Error(response.exceptionDetails?.text || "BiDi runtime evaluation failed.");
|
|
74604
|
-
return response.result
|
|
74873
|
+
return parseSerializedEvaluationResult(extractBiDiValue(response.result));
|
|
74605
74874
|
}
|
|
74606
74875
|
async function evaluateBiDiRef(client, contextId, functionSource, arg) {
|
|
74607
74876
|
const expression = arg === void 0 ? `(${functionSource})()` : `(${functionSource})(${JSON.stringify(arg)})`;
|
|
@@ -74623,6 +74892,16 @@ async function evaluateBiDiRef(client, contextId, functionSource, arg) {
|
|
|
74623
74892
|
...response.result?.value?.handle !== void 0 ? { handle: response.result.value.handle } : {}
|
|
74624
74893
|
};
|
|
74625
74894
|
}
|
|
74895
|
+
function extractBiDiValue(value) {
|
|
74896
|
+
if (!value) return;
|
|
74897
|
+
if (value.type === "array" && Array.isArray(value.value)) return value.value.map((entry) => extractBiDiValue(entry));
|
|
74898
|
+
if (value.type === "object" && Array.isArray(value.value)) {
|
|
74899
|
+
const obj = {};
|
|
74900
|
+
for (const [key, val] of value.value) obj[key] = extractBiDiValue(val);
|
|
74901
|
+
return obj;
|
|
74902
|
+
}
|
|
74903
|
+
return value.value;
|
|
74904
|
+
}
|
|
74626
74905
|
function toAriaSnapshotPayload(request = {}) {
|
|
74627
74906
|
return {
|
|
74628
74907
|
options: normalizeAriaSnapshotOptions({
|
|
@@ -74662,6 +74941,7 @@ var CdpConnectedBrowserSession = class CdpConnectedBrowserSession {
|
|
|
74662
74941
|
pageConsoleStates = /* @__PURE__ */ new Map();
|
|
74663
74942
|
pageNetworkStates = /* @__PURE__ */ new Map();
|
|
74664
74943
|
pageDialogStates = /* @__PURE__ */ new Map();
|
|
74944
|
+
dialogWaiters = /* @__PURE__ */ new Map();
|
|
74665
74945
|
activeTabId;
|
|
74666
74946
|
versionString = "Chromium/unknown";
|
|
74667
74947
|
constructor(browserClient, connection) {
|
|
@@ -74677,7 +74957,7 @@ var CdpConnectedBrowserSession = class CdpConnectedBrowserSession {
|
|
|
74677
74957
|
});
|
|
74678
74958
|
const session = new CdpConnectedBrowserSession(await chromeRemoteInterface({ target: connection.browserWsEndpoint }), connection);
|
|
74679
74959
|
session.versionString = version.Browser;
|
|
74680
|
-
|
|
74960
|
+
await session.refreshTabs();
|
|
74681
74961
|
await session.getActivePageClient().catch(() => void 0);
|
|
74682
74962
|
return session;
|
|
74683
74963
|
}
|
|
@@ -74729,6 +75009,7 @@ var CdpConnectedBrowserSession = class CdpConnectedBrowserSession {
|
|
|
74729
75009
|
async click(target, options) {
|
|
74730
75010
|
const pageClient = await this.getActivePageClient();
|
|
74731
75011
|
const contextId = await this.getActiveUtilityContextId(pageClient);
|
|
75012
|
+
const tabId = await this.getActiveTabId();
|
|
74732
75013
|
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);
|
|
74733
75014
|
if (!point.ok || point.x === void 0 || point.y === void 0) {
|
|
74734
75015
|
const isSelector = "selector" in target;
|
|
@@ -74762,7 +75043,7 @@ var CdpConnectedBrowserSession = class CdpConnectedBrowserSession {
|
|
|
74762
75043
|
modifiers: modifiersMask
|
|
74763
75044
|
});
|
|
74764
75045
|
await delay$1(options.clickHoldMs);
|
|
74765
|
-
|
|
75046
|
+
const releasePromise = pageClient.Input.dispatchMouseEvent({
|
|
74766
75047
|
type: "mouseReleased",
|
|
74767
75048
|
x: point.x,
|
|
74768
75049
|
y: point.y,
|
|
@@ -74770,6 +75051,7 @@ var CdpConnectedBrowserSession = class CdpConnectedBrowserSession {
|
|
|
74770
75051
|
clickCount,
|
|
74771
75052
|
modifiers: modifiersMask
|
|
74772
75053
|
});
|
|
75054
|
+
await Promise.race([releasePromise, this.waitForDialog(tabId, options.clickHoldMs + 1e3)]);
|
|
74773
75055
|
}
|
|
74774
75056
|
}
|
|
74775
75057
|
async drag(start, end, options) {
|
|
@@ -75025,24 +75307,72 @@ var CdpConnectedBrowserSession = class CdpConnectedBrowserSession {
|
|
|
75025
75307
|
}
|
|
75026
75308
|
}
|
|
75027
75309
|
async handleDialog(accept, promptText) {
|
|
75028
|
-
const tabId =
|
|
75310
|
+
const tabId = this.dialogTabId();
|
|
75029
75311
|
if (!this.pageDialogStates.has(tabId)) throw new McpToolError("no_dialog", "No dialog visible.");
|
|
75030
|
-
const pageClient = await this.getActivePageClient();
|
|
75312
|
+
const pageClient = this.pageClients.get(tabId) ?? await this.getActivePageClient();
|
|
75031
75313
|
this.pageDialogStates.delete(tabId);
|
|
75032
75314
|
await pageClient.Page.handleJavaScriptDialog({
|
|
75033
75315
|
accept,
|
|
75034
75316
|
...promptText !== void 0 ? { promptText } : {}
|
|
75035
75317
|
});
|
|
75036
75318
|
}
|
|
75319
|
+
async hasDialog() {
|
|
75320
|
+
return this.pageDialogStates.size > 0;
|
|
75321
|
+
}
|
|
75322
|
+
waitForDialog(tabId, timeoutMs) {
|
|
75323
|
+
if (this.pageDialogStates.has(tabId)) return Promise.resolve();
|
|
75324
|
+
return new Promise((resolve) => {
|
|
75325
|
+
const waiter = { resolve: () => {
|
|
75326
|
+
if (waiter.timer) clearTimeout(waiter.timer);
|
|
75327
|
+
this.removeDialogWaiter(tabId, waiter);
|
|
75328
|
+
resolve();
|
|
75329
|
+
} };
|
|
75330
|
+
waiter.timer = setTimeout(() => waiter.resolve(), timeoutMs);
|
|
75331
|
+
const waiters = this.dialogWaiters.get(tabId) ?? /* @__PURE__ */ new Set();
|
|
75332
|
+
waiters.add(waiter);
|
|
75333
|
+
this.dialogWaiters.set(tabId, waiters);
|
|
75334
|
+
});
|
|
75335
|
+
}
|
|
75336
|
+
resolveDialogWaiters(tabId) {
|
|
75337
|
+
const waiters = this.dialogWaiters.get(tabId);
|
|
75338
|
+
if (!waiters) return;
|
|
75339
|
+
this.dialogWaiters.delete(tabId);
|
|
75340
|
+
for (const waiter of waiters) waiter.resolve();
|
|
75341
|
+
}
|
|
75342
|
+
removeDialogWaiter(tabId, waiter) {
|
|
75343
|
+
const waiters = this.dialogWaiters.get(tabId);
|
|
75344
|
+
if (!waiters) return;
|
|
75345
|
+
waiters.delete(waiter);
|
|
75346
|
+
if (waiters.size === 0) this.dialogWaiters.delete(tabId);
|
|
75347
|
+
}
|
|
75037
75348
|
async networkRequests() {
|
|
75038
75349
|
const tabId = await this.getActiveTabId();
|
|
75350
|
+
await this.hydratePerformanceResourceRequests(tabId);
|
|
75039
75351
|
return this.ensureNetworkState(tabId).requests.map(cloneNetworkRequest);
|
|
75040
75352
|
}
|
|
75041
75353
|
async networkRequest(index) {
|
|
75042
75354
|
const tabId = await this.getActiveTabId();
|
|
75355
|
+
await this.hydratePerformanceResourceRequests(tabId);
|
|
75043
75356
|
const request = this.ensureNetworkState(tabId).requests[index - 1];
|
|
75044
75357
|
return request ? cloneNetworkRequest(request) : void 0;
|
|
75045
75358
|
}
|
|
75359
|
+
async fetchResponseBody(index) {
|
|
75360
|
+
const tabId = await this.getActiveTabId();
|
|
75361
|
+
const state = this.ensureNetworkState(tabId);
|
|
75362
|
+
const request = state.requests[index - 1];
|
|
75363
|
+
if (!request || !request.requestId) return request?.responseBody;
|
|
75364
|
+
if (!canReadResponseBody(request)) return;
|
|
75365
|
+
if (request.responseBody !== void 0) return request.responseBody;
|
|
75366
|
+
await waitForLoadingDone(state, request.requestId, 5e3).catch(() => void 0);
|
|
75367
|
+
if (request.responseBody !== void 0) return request.responseBody;
|
|
75368
|
+
if (state.bodyRead.has(request.requestId)) return;
|
|
75369
|
+
state.bodyRead.add(request.requestId);
|
|
75370
|
+
const clientNetwork = (this.pageClients.get(tabId) ?? await this.getActivePageClient()).Network;
|
|
75371
|
+
if (!clientNetwork) return;
|
|
75372
|
+
const body = await clientNetwork.getResponseBody({ requestId: request.requestId }).catch(() => void 0);
|
|
75373
|
+
if (body) request.responseBody = body.base64Encoded ? Buffer.from(body.body, "base64").toString("utf8") : body.body;
|
|
75374
|
+
return request.responseBody;
|
|
75375
|
+
}
|
|
75046
75376
|
async runCodeUnsafe(code) {
|
|
75047
75377
|
const pageClient = await this.getActivePageClient();
|
|
75048
75378
|
const contextId = await this.getActiveUtilityContextId(pageClient);
|
|
@@ -75102,6 +75432,12 @@ var CdpConnectedBrowserSession = class CdpConnectedBrowserSession {
|
|
|
75102
75432
|
if (!activeTab) throw new McpToolError("no_active_tab", "No active tab is available.");
|
|
75103
75433
|
return activeTab.id;
|
|
75104
75434
|
}
|
|
75435
|
+
dialogTabId() {
|
|
75436
|
+
if (this.activeTabId && this.pageDialogStates.has(this.activeTabId)) return this.activeTabId;
|
|
75437
|
+
const tabId = this.pageDialogStates.keys().next().value;
|
|
75438
|
+
if (!tabId) throw new McpToolError("no_dialog", "No dialog visible.");
|
|
75439
|
+
return tabId;
|
|
75440
|
+
}
|
|
75105
75441
|
async getActivePageClient() {
|
|
75106
75442
|
const activeTab = (await this.refreshTabs()).find((tab) => tab.active);
|
|
75107
75443
|
if (!activeTab) throw new McpToolError("no_active_tab", "No active tab is available.");
|
|
@@ -75183,6 +75519,7 @@ var CdpConnectedBrowserSession = class CdpConnectedBrowserSession {
|
|
|
75183
75519
|
...event.defaultPrompt !== void 0 ? { defaultPrompt: event.defaultPrompt } : {},
|
|
75184
75520
|
...event.url !== void 0 ? { url: event.url } : {}
|
|
75185
75521
|
});
|
|
75522
|
+
this.resolveDialogWaiters(tabId);
|
|
75186
75523
|
});
|
|
75187
75524
|
this.installNetworkCollection(tabId, client);
|
|
75188
75525
|
}
|
|
@@ -75221,20 +75558,29 @@ var CdpConnectedBrowserSession = class CdpConnectedBrowserSession {
|
|
|
75221
75558
|
});
|
|
75222
75559
|
client.Network.loadingFinished(async (event) => {
|
|
75223
75560
|
const request = state.byRequestId.get(event.requestId);
|
|
75224
|
-
if (!request)
|
|
75561
|
+
if (!request) {
|
|
75562
|
+
resolveLoadingDone(state, event.requestId, true);
|
|
75563
|
+
return;
|
|
75564
|
+
}
|
|
75225
75565
|
const startedAt = state.startedAt.get(event.requestId);
|
|
75226
75566
|
if (startedAt !== void 0 && event.timestamp !== void 0) request.durationMs = Math.round(event.timestamp * 1e3 - startedAt);
|
|
75227
|
-
if (canReadResponseBody(request)) {
|
|
75567
|
+
if (canReadResponseBody(request) && !state.bodyRead.has(event.requestId)) {
|
|
75568
|
+
state.bodyRead.add(event.requestId);
|
|
75228
75569
|
const body = await client.Network?.getResponseBody({ requestId: event.requestId }).catch(() => void 0);
|
|
75229
75570
|
if (body) request.responseBody = body.base64Encoded ? Buffer.from(body.body, "base64").toString("utf8") : body.body;
|
|
75230
75571
|
}
|
|
75572
|
+
resolveLoadingDone(state, event.requestId, true);
|
|
75231
75573
|
});
|
|
75232
75574
|
client.Network.loadingFailed((event) => {
|
|
75233
75575
|
const request = state.byRequestId.get(event.requestId);
|
|
75234
|
-
if (!request)
|
|
75576
|
+
if (!request) {
|
|
75577
|
+
resolveLoadingDone(state, event.requestId, false);
|
|
75578
|
+
return;
|
|
75579
|
+
}
|
|
75235
75580
|
request.failureText = event.errorText ?? "Unknown error";
|
|
75236
75581
|
const startedAt = state.startedAt.get(event.requestId);
|
|
75237
75582
|
if (startedAt !== void 0 && event.timestamp !== void 0) request.durationMs = Math.round(event.timestamp * 1e3 - startedAt);
|
|
75583
|
+
resolveLoadingDone(state, event.requestId, false);
|
|
75238
75584
|
});
|
|
75239
75585
|
}
|
|
75240
75586
|
ensureConsoleState(tabId) {
|
|
@@ -75256,7 +75602,10 @@ var CdpConnectedBrowserSession = class CdpConnectedBrowserSession {
|
|
|
75256
75602
|
state = {
|
|
75257
75603
|
requests: [],
|
|
75258
75604
|
byRequestId: /* @__PURE__ */ new Map(),
|
|
75259
|
-
startedAt: /* @__PURE__ */ new Map()
|
|
75605
|
+
startedAt: /* @__PURE__ */ new Map(),
|
|
75606
|
+
hydratedPerformanceResources: false,
|
|
75607
|
+
loadingDone: /* @__PURE__ */ new Map(),
|
|
75608
|
+
bodyRead: /* @__PURE__ */ new Set()
|
|
75260
75609
|
};
|
|
75261
75610
|
this.pageNetworkStates.set(tabId, state);
|
|
75262
75611
|
}
|
|
@@ -75272,10 +75621,79 @@ var CdpConnectedBrowserSession = class CdpConnectedBrowserSession {
|
|
|
75272
75621
|
this.pageNetworkStates.set(tabId, {
|
|
75273
75622
|
requests: [],
|
|
75274
75623
|
byRequestId: /* @__PURE__ */ new Map(),
|
|
75275
|
-
startedAt: /* @__PURE__ */ new Map()
|
|
75624
|
+
startedAt: /* @__PURE__ */ new Map(),
|
|
75625
|
+
hydratedPerformanceResources: false,
|
|
75626
|
+
loadingDone: /* @__PURE__ */ new Map(),
|
|
75627
|
+
bodyRead: /* @__PURE__ */ new Set()
|
|
75276
75628
|
});
|
|
75277
75629
|
this.pageDialogStates.delete(tabId);
|
|
75278
75630
|
}
|
|
75631
|
+
async hydratePerformanceResourceRequests(tabId) {
|
|
75632
|
+
const state = this.ensureNetworkState(tabId);
|
|
75633
|
+
if (state.hydratedPerformanceResources) return;
|
|
75634
|
+
state.hydratedPerformanceResources = true;
|
|
75635
|
+
const pageClient = await this.getActivePageClient();
|
|
75636
|
+
const contextId = await this.getActiveUtilityContextId(pageClient);
|
|
75637
|
+
const documentRequest = await evaluateCdp(pageClient, String.raw`() => {
|
|
75638
|
+
const navigation = performance.getEntriesByType("navigation")[0];
|
|
75639
|
+
return {
|
|
75640
|
+
url: String(location.href || ""),
|
|
75641
|
+
duration: navigation ? Math.round(Number(navigation.duration || 0)) : undefined
|
|
75642
|
+
};
|
|
75643
|
+
}`, void 0, contextId).catch(() => void 0);
|
|
75644
|
+
if (documentRequest?.url && !Array.from(state.byRequestId.values()).some((request) => request.url === documentRequest.url)) {
|
|
75645
|
+
const requestId = `performance:navigation:${documentRequest.url}`;
|
|
75646
|
+
const request = {
|
|
75647
|
+
index: state.requests.length + 1,
|
|
75648
|
+
requestId,
|
|
75649
|
+
method: "GET",
|
|
75650
|
+
url: documentRequest.url,
|
|
75651
|
+
resourceType: "document",
|
|
75652
|
+
requestHeaders: {},
|
|
75653
|
+
status: 200,
|
|
75654
|
+
statusText: "OK",
|
|
75655
|
+
...documentRequest.duration !== void 0 ? { durationMs: documentRequest.duration } : {}
|
|
75656
|
+
};
|
|
75657
|
+
state.requests.push(request);
|
|
75658
|
+
state.byRequestId.set(requestId, request);
|
|
75659
|
+
}
|
|
75660
|
+
const resources = await evaluateCdp(pageClient, String.raw`() => performance.getEntriesByType("resource").map((entry) => ({
|
|
75661
|
+
name: String(entry.name || ""),
|
|
75662
|
+
initiatorType: String(entry.initiatorType || "other"),
|
|
75663
|
+
duration: Math.round(Number(entry.duration || 0)),
|
|
75664
|
+
responseStatus: typeof entry.responseStatus === "number" ? entry.responseStatus : undefined
|
|
75665
|
+
}))`, void 0, contextId).catch(() => []);
|
|
75666
|
+
for (const resource of resources) {
|
|
75667
|
+
if (!resource.name || Array.from(state.byRequestId.values()).some((request) => request.url === resource.name)) continue;
|
|
75668
|
+
const status = resource.responseStatus && resource.responseStatus > 0 ? resource.responseStatus : await this.probeResourceStatus(pageClient, contextId, resource.name);
|
|
75669
|
+
const requestId = `performance:${resource.name}`;
|
|
75670
|
+
const request = {
|
|
75671
|
+
index: state.requests.length + 1,
|
|
75672
|
+
requestId,
|
|
75673
|
+
method: "GET",
|
|
75674
|
+
url: resource.name,
|
|
75675
|
+
resourceType: normalizeResourceType(resource.initiatorType),
|
|
75676
|
+
requestHeaders: {},
|
|
75677
|
+
...status !== void 0 ? {
|
|
75678
|
+
status,
|
|
75679
|
+
statusText: statusTextForStatus(status)
|
|
75680
|
+
} : {},
|
|
75681
|
+
...resource.duration !== void 0 ? { durationMs: resource.duration } : {}
|
|
75682
|
+
};
|
|
75683
|
+
state.requests.push(request);
|
|
75684
|
+
state.byRequestId.set(requestId, request);
|
|
75685
|
+
}
|
|
75686
|
+
}
|
|
75687
|
+
async probeResourceStatus(pageClient, contextId, url) {
|
|
75688
|
+
return evaluateCdp(pageClient, String.raw`async (url) => {
|
|
75689
|
+
try {
|
|
75690
|
+
const response = await fetch(url, { method: "HEAD", cache: "no-store" });
|
|
75691
|
+
return response.status;
|
|
75692
|
+
} catch {
|
|
75693
|
+
return undefined;
|
|
75694
|
+
}
|
|
75695
|
+
}`, url, contextId).catch(() => void 0);
|
|
75696
|
+
}
|
|
75279
75697
|
addConsoleMessage(tabId, message) {
|
|
75280
75698
|
const state = this.ensureConsoleState(tabId);
|
|
75281
75699
|
if (!shouldIncludeConsoleMessage(message.type)) return;
|
|
@@ -75409,6 +75827,45 @@ function canReadResponseBody(request) {
|
|
|
75409
75827
|
if (request.failureText || request.status === void 0) return false;
|
|
75410
75828
|
return request.status !== 204 && request.status !== 304 && !(request.status >= 100 && request.status < 200);
|
|
75411
75829
|
}
|
|
75830
|
+
function loadingDoneEntry(state, requestId) {
|
|
75831
|
+
let entry = state.loadingDone.get(requestId);
|
|
75832
|
+
if (!entry) {
|
|
75833
|
+
let resolve;
|
|
75834
|
+
let reject;
|
|
75835
|
+
entry = {
|
|
75836
|
+
promise: new Promise((res, rej) => {
|
|
75837
|
+
resolve = res;
|
|
75838
|
+
reject = rej;
|
|
75839
|
+
}),
|
|
75840
|
+
resolve,
|
|
75841
|
+
reject
|
|
75842
|
+
};
|
|
75843
|
+
state.loadingDone.set(requestId, entry);
|
|
75844
|
+
}
|
|
75845
|
+
return entry;
|
|
75846
|
+
}
|
|
75847
|
+
function resolveLoadingDone(state, requestId, success) {
|
|
75848
|
+
const entry = state.loadingDone.get(requestId);
|
|
75849
|
+
if (!entry) return;
|
|
75850
|
+
state.loadingDone.delete(requestId);
|
|
75851
|
+
if (success) entry.resolve();
|
|
75852
|
+
else entry.reject(/* @__PURE__ */ new Error("Request failed before the response body was available."));
|
|
75853
|
+
}
|
|
75854
|
+
async function waitForLoadingDone(state, requestId, timeoutMs) {
|
|
75855
|
+
const entry = loadingDoneEntry(state, requestId);
|
|
75856
|
+
await Promise.race([entry.promise, new Promise((resolve) => setTimeout(resolve, timeoutMs))]);
|
|
75857
|
+
}
|
|
75858
|
+
function statusTextForStatus(status) {
|
|
75859
|
+
if (status === 200) return "OK";
|
|
75860
|
+
if (status === 204) return "No Content";
|
|
75861
|
+
if (status === 304) return "Not Modified";
|
|
75862
|
+
if (status === 400) return "Bad Request";
|
|
75863
|
+
if (status === 401) return "Unauthorized";
|
|
75864
|
+
if (status === 403) return "Forbidden";
|
|
75865
|
+
if (status === 404) return "Not Found";
|
|
75866
|
+
if (status === 500) return "Internal Server Error";
|
|
75867
|
+
return "";
|
|
75868
|
+
}
|
|
75412
75869
|
function cloneNetworkRequest(request) {
|
|
75413
75870
|
return {
|
|
75414
75871
|
...request,
|
|
@@ -75487,9 +75944,11 @@ var BidiConnectedBrowserSession = class BidiConnectedBrowserSession {
|
|
|
75487
75944
|
pageConsoleStates = /* @__PURE__ */ new Map();
|
|
75488
75945
|
pageNetworkStates = /* @__PURE__ */ new Map();
|
|
75489
75946
|
pageDialogStates = /* @__PURE__ */ new Map();
|
|
75947
|
+
dialogWaiters = /* @__PURE__ */ new Map();
|
|
75490
75948
|
bidiListeners = /* @__PURE__ */ new Map();
|
|
75491
75949
|
responseDataCollector;
|
|
75492
75950
|
activeTabId;
|
|
75951
|
+
ownsSession = false;
|
|
75493
75952
|
constructor(client) {
|
|
75494
75953
|
this.client = client;
|
|
75495
75954
|
}
|
|
@@ -75497,12 +75956,14 @@ var BidiConnectedBrowserSession = class BidiConnectedBrowserSession {
|
|
|
75497
75956
|
if (args.browser && args.browser !== "firefox") throw new McpToolError("unsupported_protocol_input", "BiDi attach only supports browser \"firefox\" in v1.");
|
|
75498
75957
|
const parsed = new URL(args.endpoint);
|
|
75499
75958
|
if (parsed.protocol !== "ws:" && parsed.protocol !== "wss:") throw new McpToolError("unsupported_protocol_input", `BiDi endpoint must be a ws(s) URL. Received "${parsed.protocol}".`);
|
|
75500
|
-
const
|
|
75959
|
+
const client = await getBidiClientFactory()({
|
|
75501
75960
|
browserName: "firefox",
|
|
75502
|
-
webSocketUrl: args.endpoint
|
|
75503
|
-
})
|
|
75961
|
+
webSocketUrl: normalizeFirefoxBidiEndpoint(args.endpoint, args.sessionId)
|
|
75962
|
+
});
|
|
75963
|
+
const session = new BidiConnectedBrowserSession(client);
|
|
75964
|
+
session.ownsSession = await ensureMcpBiDiSession(client, args.endpoint, args.sessionId);
|
|
75504
75965
|
await session.initialize();
|
|
75505
|
-
|
|
75966
|
+
await session.refreshTabs();
|
|
75506
75967
|
return session;
|
|
75507
75968
|
}
|
|
75508
75969
|
async version() {
|
|
@@ -75545,10 +76006,13 @@ var BidiConnectedBrowserSession = class BidiConnectedBrowserSession {
|
|
|
75545
76006
|
}
|
|
75546
76007
|
async snapshot(request = {}) {
|
|
75547
76008
|
const tabId = await this.getActiveTabId();
|
|
75548
|
-
return
|
|
75549
|
-
|
|
75550
|
-
|
|
75551
|
-
|
|
76009
|
+
return {
|
|
76010
|
+
...toBrowserSnapshot(await retryUntilReady(() => evaluateBiDi(this.client, tabId, PLAYWRIGHT_ARIA_SNAPSHOT_EVALUATE_SOURCE, toAriaSnapshotPayload(request))), request, {
|
|
76011
|
+
console: this.consoleSummary(tabId),
|
|
76012
|
+
consoleLink: await this.takeConsoleLink(tabId)
|
|
76013
|
+
}),
|
|
76014
|
+
retryable: true
|
|
76015
|
+
};
|
|
75552
76016
|
}
|
|
75553
76017
|
async consoleMessages(level = "info", all = false) {
|
|
75554
76018
|
const activeTabId = await this.getActiveTabId();
|
|
@@ -75651,10 +76115,12 @@ var BidiConnectedBrowserSession = class BidiConnectedBrowserSession {
|
|
|
75651
76115
|
parameters: { pointerType: "mouse" },
|
|
75652
76116
|
actions: pointerActions
|
|
75653
76117
|
});
|
|
75654
|
-
|
|
76118
|
+
const performPromise = this.client.inputPerformActions({
|
|
75655
76119
|
context: tabId,
|
|
75656
76120
|
actions
|
|
75657
76121
|
});
|
|
76122
|
+
await Promise.race([performPromise, this.waitForDialog(tabId, options.clickHoldMs + 5e3)]);
|
|
76123
|
+
performPromise.catch(() => {});
|
|
75658
76124
|
await this.client.inputReleaseActions({ context: tabId }).catch(() => {});
|
|
75659
76125
|
}
|
|
75660
76126
|
async drag(start, end, options) {
|
|
@@ -75747,7 +76213,7 @@ var BidiConnectedBrowserSession = class BidiConnectedBrowserSession {
|
|
|
75747
76213
|
await this.client.networkRemoveDataCollector({ collector: this.responseDataCollector }).catch(() => {});
|
|
75748
76214
|
this.responseDataCollector = void 0;
|
|
75749
76215
|
}
|
|
75750
|
-
await this.client.sessionEnd({}).catch(() => {});
|
|
76216
|
+
if (this.ownsSession) await this.client.sessionEnd({}).catch(() => {});
|
|
75751
76217
|
this.client.close();
|
|
75752
76218
|
}
|
|
75753
76219
|
async navigate(url) {
|
|
@@ -75955,14 +76421,17 @@ var BidiConnectedBrowserSession = class BidiConnectedBrowserSession {
|
|
|
75955
76421
|
}
|
|
75956
76422
|
}
|
|
75957
76423
|
async handleDialog(accept, promptText) {
|
|
75958
|
-
const tabId =
|
|
76424
|
+
const tabId = this.dialogTabId();
|
|
75959
76425
|
if (!this.pageDialogStates.has(tabId)) throw new McpToolError("no_dialog", "No dialog visible.");
|
|
75960
76426
|
this.pageDialogStates.delete(tabId);
|
|
75961
|
-
await this.client.browsingContextHandleUserPrompt({
|
|
76427
|
+
await withBiDiTimeout(this.client.browsingContextHandleUserPrompt({
|
|
75962
76428
|
context: tabId,
|
|
75963
76429
|
accept,
|
|
75964
76430
|
...promptText !== void 0 ? { userText: promptText } : {}
|
|
75965
|
-
});
|
|
76431
|
+
}), 5e3);
|
|
76432
|
+
}
|
|
76433
|
+
async hasDialog() {
|
|
76434
|
+
return this.pageDialogStates.size > 0;
|
|
75966
76435
|
}
|
|
75967
76436
|
async networkRequests() {
|
|
75968
76437
|
const tabId = await this.getActiveTabId();
|
|
@@ -75973,6 +76442,15 @@ var BidiConnectedBrowserSession = class BidiConnectedBrowserSession {
|
|
|
75973
76442
|
const request = this.ensureNetworkState(tabId).requests[index - 1];
|
|
75974
76443
|
return request ? cloneNetworkRequest(request) : void 0;
|
|
75975
76444
|
}
|
|
76445
|
+
async fetchResponseBody(index) {
|
|
76446
|
+
const tabId = await this.getActiveTabId();
|
|
76447
|
+
const request = this.ensureNetworkState(tabId).requests[index - 1];
|
|
76448
|
+
if (!request || !request.requestId) return request?.responseBody;
|
|
76449
|
+
if (request.responseBody !== void 0) return request.responseBody;
|
|
76450
|
+
const body = await this.getResponseBody(request.requestId).catch(() => void 0);
|
|
76451
|
+
if (body !== void 0) request.responseBody = body;
|
|
76452
|
+
return request.responseBody;
|
|
76453
|
+
}
|
|
75976
76454
|
async runCodeUnsafe(code) {
|
|
75977
76455
|
return this.evaluate(`async () => {
|
|
75978
76456
|
const fn = eval(${JSON.stringify(`(${code})`)});
|
|
@@ -75988,6 +76466,7 @@ var BidiConnectedBrowserSession = class BidiConnectedBrowserSession {
|
|
|
75988
76466
|
}`);
|
|
75989
76467
|
}
|
|
75990
76468
|
async initialize() {
|
|
76469
|
+
this.attachBiDiListeners();
|
|
75991
76470
|
await this.client.sessionSubscribe({ events: [
|
|
75992
76471
|
"browsingContext.userPromptOpened",
|
|
75993
76472
|
"log.entryAdded",
|
|
@@ -76001,7 +76480,6 @@ var BidiConnectedBrowserSession = class BidiConnectedBrowserSession {
|
|
|
76001
76480
|
maxEncodedDataSize: 1e7
|
|
76002
76481
|
}).catch(() => void 0);
|
|
76003
76482
|
this.responseDataCollector = collectorResult?.collector;
|
|
76004
|
-
this.attachBiDiListeners();
|
|
76005
76483
|
}
|
|
76006
76484
|
attachBiDiListeners() {
|
|
76007
76485
|
this.attachBiDiListener("log.entryAdded", (payload) => this.handleLogEntry(payload));
|
|
@@ -76036,6 +76514,45 @@ var BidiConnectedBrowserSession = class BidiConnectedBrowserSession {
|
|
|
76036
76514
|
targetArg(target) {
|
|
76037
76515
|
return "nodeToken" in target ? { nodeToken: target.nodeToken } : { selector: target.selector };
|
|
76038
76516
|
}
|
|
76517
|
+
dialogTabId() {
|
|
76518
|
+
if (this.activeTabId && this.pageDialogStates.has(this.activeTabId)) return this.activeTabId;
|
|
76519
|
+
const tabId = this.pageDialogStates.keys().next().value;
|
|
76520
|
+
if (!tabId) throw new McpToolError("no_dialog", "No dialog visible.");
|
|
76521
|
+
return tabId;
|
|
76522
|
+
}
|
|
76523
|
+
waitForDialog(tabId, timeoutMs) {
|
|
76524
|
+
if (this.pageDialogStates.has(tabId)) return Promise.resolve();
|
|
76525
|
+
return new Promise((resolve, reject) => {
|
|
76526
|
+
const waiter = {
|
|
76527
|
+
resolve: () => {
|
|
76528
|
+
if (waiter.timer) clearTimeout(waiter.timer);
|
|
76529
|
+
this.removeDialogWaiter(tabId, waiter);
|
|
76530
|
+
resolve();
|
|
76531
|
+
},
|
|
76532
|
+
reject: (error) => {
|
|
76533
|
+
if (waiter.timer) clearTimeout(waiter.timer);
|
|
76534
|
+
this.removeDialogWaiter(tabId, waiter);
|
|
76535
|
+
reject(error);
|
|
76536
|
+
}
|
|
76537
|
+
};
|
|
76538
|
+
waiter.timer = setTimeout(() => waiter.reject?.(/* @__PURE__ */ new Error("Timed out waiting for dialog.")), timeoutMs);
|
|
76539
|
+
const waiters = this.dialogWaiters.get(tabId) ?? /* @__PURE__ */ new Set();
|
|
76540
|
+
waiters.add(waiter);
|
|
76541
|
+
this.dialogWaiters.set(tabId, waiters);
|
|
76542
|
+
});
|
|
76543
|
+
}
|
|
76544
|
+
resolveDialogWaiters(tabId) {
|
|
76545
|
+
const waiters = this.dialogWaiters.get(tabId);
|
|
76546
|
+
if (!waiters) return;
|
|
76547
|
+
this.dialogWaiters.delete(tabId);
|
|
76548
|
+
for (const waiter of waiters) waiter.resolve();
|
|
76549
|
+
}
|
|
76550
|
+
removeDialogWaiter(tabId, waiter) {
|
|
76551
|
+
const waiters = this.dialogWaiters.get(tabId);
|
|
76552
|
+
if (!waiters) return;
|
|
76553
|
+
waiters.delete(waiter);
|
|
76554
|
+
if (waiters.size === 0) this.dialogWaiters.delete(tabId);
|
|
76555
|
+
}
|
|
76039
76556
|
async actionPoint(tabId, target) {
|
|
76040
76557
|
const source = "nodeToken" in target ? ACTION_POINT_EVALUATE_SOURCE : ACTION_POINT_BY_SELECTOR_SOURCE;
|
|
76041
76558
|
const point = await evaluateBiDi(this.client, tabId, source, this.targetArg(target));
|
|
@@ -76092,6 +76609,7 @@ var BidiConnectedBrowserSession = class BidiConnectedBrowserSession {
|
|
|
76092
76609
|
type: event.type ?? "alert",
|
|
76093
76610
|
...event.defaultValue !== void 0 ? { defaultPrompt: event.defaultValue } : {}
|
|
76094
76611
|
});
|
|
76612
|
+
this.resolveDialogWaiters(event.context);
|
|
76095
76613
|
}
|
|
76096
76614
|
handleBeforeRequestSent(payload) {
|
|
76097
76615
|
const event = parseBidiNetworkEvent(payload);
|
|
@@ -76194,7 +76712,10 @@ var BidiConnectedBrowserSession = class BidiConnectedBrowserSession {
|
|
|
76194
76712
|
state = {
|
|
76195
76713
|
requests: [],
|
|
76196
76714
|
byRequestId: /* @__PURE__ */ new Map(),
|
|
76197
|
-
startedAt: /* @__PURE__ */ new Map()
|
|
76715
|
+
startedAt: /* @__PURE__ */ new Map(),
|
|
76716
|
+
hydratedPerformanceResources: false,
|
|
76717
|
+
loadingDone: /* @__PURE__ */ new Map(),
|
|
76718
|
+
bodyRead: /* @__PURE__ */ new Set()
|
|
76198
76719
|
};
|
|
76199
76720
|
this.pageNetworkStates.set(tabId, state);
|
|
76200
76721
|
}
|
|
@@ -76210,7 +76731,10 @@ var BidiConnectedBrowserSession = class BidiConnectedBrowserSession {
|
|
|
76210
76731
|
this.pageNetworkStates.set(tabId, {
|
|
76211
76732
|
requests: [],
|
|
76212
76733
|
byRequestId: /* @__PURE__ */ new Map(),
|
|
76213
|
-
startedAt: /* @__PURE__ */ new Map()
|
|
76734
|
+
startedAt: /* @__PURE__ */ new Map(),
|
|
76735
|
+
hydratedPerformanceResources: false,
|
|
76736
|
+
loadingDone: /* @__PURE__ */ new Map(),
|
|
76737
|
+
bodyRead: /* @__PURE__ */ new Set()
|
|
76214
76738
|
});
|
|
76215
76739
|
this.pageDialogStates.delete(tabId);
|
|
76216
76740
|
}
|
|
@@ -76248,6 +76772,36 @@ var BidiConnectedBrowserSession = class BidiConnectedBrowserSession {
|
|
|
76248
76772
|
return `${path.relative(process.cwd(), state.logFile)}${fromLine === state.logLine ? `#L${fromLine}` : `#L${fromLine}-L${state.logLine}`}`;
|
|
76249
76773
|
}
|
|
76250
76774
|
};
|
|
76775
|
+
function normalizeFirefoxBidiEndpoint(endpoint, sessionId) {
|
|
76776
|
+
const url = new URL(endpoint);
|
|
76777
|
+
if (sessionId) {
|
|
76778
|
+
url.pathname = `/session/${sessionId}`;
|
|
76779
|
+
return url.toString();
|
|
76780
|
+
}
|
|
76781
|
+
if (url.pathname === "/" || url.pathname === "") url.pathname = "/session";
|
|
76782
|
+
return url.toString();
|
|
76783
|
+
}
|
|
76784
|
+
async function ensureMcpBiDiSession(client, endpoint, sessionId) {
|
|
76785
|
+
await client.sessionStatus({});
|
|
76786
|
+
if (sessionId || isSessionSpecificFirefoxBidiEndpoint(endpoint)) return false;
|
|
76787
|
+
try {
|
|
76788
|
+
await client.browsingContextGetTree({});
|
|
76789
|
+
return false;
|
|
76790
|
+
} catch (error) {
|
|
76791
|
+
const message = String(error instanceof Error ? error.message : error);
|
|
76792
|
+
if (!message.includes("session does not exist") && !message.includes("invalid session id") && !message.includes("not active")) throw error;
|
|
76793
|
+
}
|
|
76794
|
+
try {
|
|
76795
|
+
await client.sessionNew({ capabilities: { alwaysMatch: { acceptInsecureCerts: true } } });
|
|
76796
|
+
return true;
|
|
76797
|
+
} catch (error) {
|
|
76798
|
+
if (String(error instanceof Error ? error.message : error).includes("Maximum number of active sessions")) throw new Error("Maximum number of active BiDi sessions. Reuse an existing one with sessionId or close the current session first.");
|
|
76799
|
+
throw error;
|
|
76800
|
+
}
|
|
76801
|
+
}
|
|
76802
|
+
function isSessionSpecificFirefoxBidiEndpoint(endpoint) {
|
|
76803
|
+
return /^\/session\/[^/]+$/.test(new URL(endpoint).pathname);
|
|
76804
|
+
}
|
|
76251
76805
|
async function connectBrowserSession(args) {
|
|
76252
76806
|
if (args.protocol === "cdp") return CdpConnectedBrowserSession.connect(args);
|
|
76253
76807
|
return BidiConnectedBrowserSession.connect(args);
|
|
@@ -76279,7 +76833,7 @@ var McpRuntime = class {
|
|
|
76279
76833
|
constructor(sessionFactory = connectBrowserSession, options = {}) {
|
|
76280
76834
|
this.sessionFactory = sessionFactory;
|
|
76281
76835
|
this.snapshotMode = options.snapshotMode ?? "full";
|
|
76282
|
-
this.outputDir = configuredOutputDir({ outputDir: options.outputDir });
|
|
76836
|
+
this.outputDir = configuredOutputDir({ ...options.outputDir !== void 0 ? { outputDir: options.outputDir } : {} });
|
|
76283
76837
|
}
|
|
76284
76838
|
getOutputDir() {
|
|
76285
76839
|
return this.outputDir;
|
|
@@ -76314,6 +76868,7 @@ var McpRuntime = class {
|
|
|
76314
76868
|
this.invalidateSnapshot();
|
|
76315
76869
|
this.pendingFileUploadTarget = void 0;
|
|
76316
76870
|
this.tabs = await session.newTab(url);
|
|
76871
|
+
if (this.snapshotMode === "none") return { tabs: this.tabs };
|
|
76317
76872
|
const snapshot = this.tabs.some((tab) => tab.active) ? await this.snapshot() : void 0;
|
|
76318
76873
|
return snapshot ? {
|
|
76319
76874
|
tabs: this.tabs,
|
|
@@ -76327,6 +76882,7 @@ var McpRuntime = class {
|
|
|
76327
76882
|
this.invalidateSnapshot();
|
|
76328
76883
|
this.pendingFileUploadTarget = void 0;
|
|
76329
76884
|
this.tabs = await session.selectTab(tab.id);
|
|
76885
|
+
if (this.snapshotMode === "none") return { tabs: this.tabs };
|
|
76330
76886
|
const snapshot = await this.snapshot();
|
|
76331
76887
|
return {
|
|
76332
76888
|
tabs: this.tabs,
|
|
@@ -76340,6 +76896,7 @@ var McpRuntime = class {
|
|
|
76340
76896
|
this.invalidateSnapshot();
|
|
76341
76897
|
this.pendingFileUploadTarget = void 0;
|
|
76342
76898
|
this.tabs = await session.closeTab(tab.id);
|
|
76899
|
+
if (this.snapshotMode === "none") return { tabs: this.tabs };
|
|
76343
76900
|
const snapshot = this.tabs.some((candidate) => candidate.active) ? await this.snapshot() : void 0;
|
|
76344
76901
|
return snapshot ? {
|
|
76345
76902
|
tabs: this.tabs,
|
|
@@ -76348,25 +76905,49 @@ var McpRuntime = class {
|
|
|
76348
76905
|
}
|
|
76349
76906
|
async snapshot(args = {}) {
|
|
76350
76907
|
const session = this.requireConnected();
|
|
76351
|
-
const activeTab = this.requireActiveTab();
|
|
76352
76908
|
const requestKey = this.snapshotRequestKey(args);
|
|
76353
76909
|
const request = {
|
|
76354
76910
|
...args.boxes !== void 0 ? { boxes: args.boxes } : {},
|
|
76355
76911
|
...args.depth !== void 0 ? { depth: args.depth } : {},
|
|
76356
76912
|
...args.target ? { target: this.resolveSnapshotTarget(args.target) } : {}
|
|
76357
76913
|
};
|
|
76358
|
-
const snapshot = await
|
|
76914
|
+
const { activeTab, currentActiveTab, snapshot } = await this.captureStableSnapshot(session, request);
|
|
76359
76915
|
this.snapshotCache = {
|
|
76360
|
-
tabId:
|
|
76916
|
+
tabId: currentActiveTab.id,
|
|
76361
76917
|
requestKey,
|
|
76362
76918
|
text: snapshot.text,
|
|
76363
76919
|
refs: { ...snapshot.refs },
|
|
76364
|
-
title: snapshot.title,
|
|
76365
|
-
url: snapshot.url,
|
|
76920
|
+
title: currentActiveTab.title || snapshot.title,
|
|
76921
|
+
url: currentActiveTab.url || snapshot.url,
|
|
76366
76922
|
...snapshot.console ? { console: { ...snapshot.console } } : {},
|
|
76367
76923
|
...snapshot.consoleLink ? { consoleLink: snapshot.consoleLink } : {}
|
|
76368
76924
|
};
|
|
76369
|
-
return
|
|
76925
|
+
return {
|
|
76926
|
+
...snapshot,
|
|
76927
|
+
title: currentActiveTab.title || snapshot.title,
|
|
76928
|
+
url: currentActiveTab.url || snapshot.url
|
|
76929
|
+
};
|
|
76930
|
+
}
|
|
76931
|
+
async captureStableSnapshot(session, request) {
|
|
76932
|
+
let lastAttempt;
|
|
76933
|
+
for (let attempt = 0; attempt < 4; attempt += 1) {
|
|
76934
|
+
this.tabs = await session.listTabs();
|
|
76935
|
+
const activeTab = this.requireActiveTab();
|
|
76936
|
+
const snapshot = await session.snapshot(request);
|
|
76937
|
+
const refreshedTabs = await session.listTabs();
|
|
76938
|
+
this.tabs = refreshedTabs;
|
|
76939
|
+
const currentActiveTab = refreshedTabs.find((tab) => tab.active) ?? refreshedTabs.find((tab) => tab.id === activeTab.id) ?? activeTab;
|
|
76940
|
+
const captured = {
|
|
76941
|
+
activeTab,
|
|
76942
|
+
currentActiveTab,
|
|
76943
|
+
snapshot
|
|
76944
|
+
};
|
|
76945
|
+
lastAttempt = captured;
|
|
76946
|
+
if (!snapshot.retryable || snapshot.text.trim().length > 0 || currentActiveTab.url === "about:blank") return captured;
|
|
76947
|
+
await delay(150 * (attempt + 1));
|
|
76948
|
+
}
|
|
76949
|
+
if (!lastAttempt) throw new McpToolError("action_failed", "Unable to capture page snapshot.");
|
|
76950
|
+
return lastAttempt;
|
|
76370
76951
|
}
|
|
76371
76952
|
async click(target, opts) {
|
|
76372
76953
|
const session = this.requireConnected();
|
|
@@ -76385,6 +76966,7 @@ var McpRuntime = class {
|
|
|
76385
76966
|
this.invalidateSnapshot();
|
|
76386
76967
|
this.pendingFileUploadTarget = opensFileChooser ? resolved : void 0;
|
|
76387
76968
|
if (this.snapshotMode === "none") return;
|
|
76969
|
+
if (await session.hasDialog()) return;
|
|
76388
76970
|
return this.snapshot();
|
|
76389
76971
|
}
|
|
76390
76972
|
async hover(target) {
|
|
@@ -76394,13 +76976,16 @@ var McpRuntime = class {
|
|
|
76394
76976
|
this.invalidateSnapshot();
|
|
76395
76977
|
this.pendingFileUploadTarget = void 0;
|
|
76396
76978
|
if (this.snapshotMode === "none") return;
|
|
76979
|
+
if (await session.hasDialog()) return;
|
|
76397
76980
|
return this.snapshot();
|
|
76398
76981
|
}
|
|
76399
76982
|
async navigate(url) {
|
|
76400
|
-
|
|
76983
|
+
const session = this.requireConnected();
|
|
76984
|
+
await session.navigate(normalizeNavigationUrl(url));
|
|
76401
76985
|
this.invalidateSnapshot();
|
|
76402
76986
|
this.pendingFileUploadTarget = void 0;
|
|
76403
76987
|
if (this.snapshotMode === "none") return;
|
|
76988
|
+
if (await session.hasDialog()) return;
|
|
76404
76989
|
return this.snapshot();
|
|
76405
76990
|
}
|
|
76406
76991
|
async type(ref, text, opts) {
|
|
@@ -76549,6 +77134,9 @@ var McpRuntime = class {
|
|
|
76549
77134
|
async networkRequest(index) {
|
|
76550
77135
|
return this.requireConnected().networkRequest(index);
|
|
76551
77136
|
}
|
|
77137
|
+
async fetchResponseBody(index) {
|
|
77138
|
+
return this.requireConnected().fetchResponseBody(index);
|
|
77139
|
+
}
|
|
76552
77140
|
async runCodeUnsafe(code) {
|
|
76553
77141
|
const result = await this.requireConnected().runCodeUnsafe(code);
|
|
76554
77142
|
this.invalidateSnapshot();
|
|
@@ -76585,6 +77173,10 @@ var McpRuntime = class {
|
|
|
76585
77173
|
hasPendingFileUploadTarget() {
|
|
76586
77174
|
return !!this.pendingFileUploadTarget;
|
|
76587
77175
|
}
|
|
77176
|
+
async hasDialog() {
|
|
77177
|
+
if (!this.connection) return false;
|
|
77178
|
+
return this.connection.session.hasDialog();
|
|
77179
|
+
}
|
|
76588
77180
|
async close() {
|
|
76589
77181
|
this.invalidateSnapshot();
|
|
76590
77182
|
this.pendingFileUploadTarget = void 0;
|
|
@@ -78205,6 +78797,7 @@ async function createRoxyBrowserMcpInMemory(options = {}) {
|
|
|
78205
78797
|
await bundle.server.connect(serverTransport);
|
|
78206
78798
|
return {
|
|
78207
78799
|
server: bundle.server,
|
|
78800
|
+
runtimeManager: bundle.runtimeManager,
|
|
78208
78801
|
serverTransport,
|
|
78209
78802
|
clientTransport,
|
|
78210
78803
|
close: async () => {
|