donobu 5.60.0 → 5.60.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/tools/RunInlineJavaScriptCodeTool.js +43 -2
- package/dist/esm/tools/RunSandboxedJavaScriptCodeTool.js +24 -2
- package/dist/esm/utils/PlaywrightUtils.d.ts +75 -0
- package/dist/esm/utils/PlaywrightUtils.js +122 -0
- package/dist/tools/RunInlineJavaScriptCodeTool.js +43 -2
- package/dist/tools/RunSandboxedJavaScriptCodeTool.js +24 -2
- package/dist/utils/PlaywrightUtils.d.ts +75 -0
- package/dist/utils/PlaywrightUtils.js +122 -0
- package/package.json +1 -1
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.RunInlineJavaScriptCodeTool = exports.RunInlineJavaScriptCodeGptSchema = exports.RunInlineJavaScriptCodeCoreSchema = void 0;
|
|
4
4
|
const v4_1 = require("zod/v4");
|
|
5
5
|
const ToolSchema_1 = require("../models/ToolSchema");
|
|
6
|
+
const PlaywrightUtils_1 = require("../utils/PlaywrightUtils");
|
|
6
7
|
const TargetUtils_1 = require("../utils/TargetUtils");
|
|
7
8
|
const Tool_1 = require("./Tool");
|
|
8
9
|
exports.RunInlineJavaScriptCodeCoreSchema = v4_1.z.object({
|
|
@@ -24,10 +25,50 @@ as well as the overall environment of the webpage itself. The JS code must be of
|
|
|
24
25
|
\`\`\``, exports.RunInlineJavaScriptCodeCoreSchema, exports.RunInlineJavaScriptCodeGptSchema, false, undefined, ['web']);
|
|
25
26
|
}
|
|
26
27
|
async call(context, parameters) {
|
|
27
|
-
|
|
28
|
+
let result;
|
|
29
|
+
try {
|
|
30
|
+
result = await (0, TargetUtils_1.webPage)(context).evaluate(PlaywrightUtils_1.PlaywrightUtils.asSizeGuardedJavaScriptExpression(parameters.javaScriptCode, { stashOversizedResult: true }));
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
// Stash variables are wiped by navigation/reload, which surfaces as a
|
|
34
|
+
// cryptic "cannot read properties of undefined" — give the model the
|
|
35
|
+
// real story so it re-extracts instead of retrying blind.
|
|
36
|
+
if (parameters.javaScriptCode.includes('__dnb_result_') &&
|
|
37
|
+
!PlaywrightUtils_1.PlaywrightUtils.isPageClosedError(error)) {
|
|
38
|
+
return {
|
|
39
|
+
isSuccessful: false,
|
|
40
|
+
forLlm: `FAILED! ${error.message}\n` +
|
|
41
|
+
'Note: window.__dnb_result_* variables are cleared when the page ' +
|
|
42
|
+
'navigates or reloads, and only the most recent ' +
|
|
43
|
+
`${PlaywrightUtils_1.PlaywrightUtils.MAX_STASHED_RESULTS} are kept. If the variable ` +
|
|
44
|
+
'no longer exists, re-run the code that originally produced it.',
|
|
45
|
+
metadata: null,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
if (result.oversized) {
|
|
51
|
+
const sizeNote = result.size === null
|
|
52
|
+
? 'it is not JSON-serializable'
|
|
53
|
+
: `${result.size} characters of JSON`;
|
|
54
|
+
return {
|
|
55
|
+
isSuccessful: true,
|
|
56
|
+
forLlm: `The code ran successfully, but its result was too large to return directly (${sizeNote}). ` +
|
|
57
|
+
`The result is ${result.summary}.\n` +
|
|
58
|
+
`The full result is saved in the page as \`window.${result.stashedAt}\` and remains ` +
|
|
59
|
+
'available until the page navigates or reloads.\n' +
|
|
60
|
+
`Retrieve what you need with further ${RunInlineJavaScriptCodeTool.NAME} calls that ` +
|
|
61
|
+
'slice, filter, or aggregate it, for example:\n' +
|
|
62
|
+
`() => { return window.${result.stashedAt}.slice(0, 100); }\n` +
|
|
63
|
+
`Preview of the result:\n${result.preview}`,
|
|
64
|
+
metadata: null,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
28
67
|
return {
|
|
29
68
|
isSuccessful: true,
|
|
30
|
-
forLlm:
|
|
69
|
+
forLlm: result.value === undefined
|
|
70
|
+
? 'The code ran successfully and returned undefined.'
|
|
71
|
+
: '' + JSON.stringify(result.value),
|
|
31
72
|
metadata: null,
|
|
32
73
|
};
|
|
33
74
|
}
|
|
@@ -4,6 +4,7 @@ exports.RunSandboxedJavaScriptCodeTool = exports.RunSandboxedJavaScriptCodeGptSc
|
|
|
4
4
|
const playwright_1 = require("playwright");
|
|
5
5
|
const v4_1 = require("zod/v4");
|
|
6
6
|
const ToolSchema_1 = require("../models/ToolSchema");
|
|
7
|
+
const PlaywrightUtils_1 = require("../utils/PlaywrightUtils");
|
|
7
8
|
const Tool_1 = require("./Tool");
|
|
8
9
|
exports.RunSandboxedJavaScriptCodeCoreSchema = v4_1.z.object({
|
|
9
10
|
javaScriptCode: v4_1.z.string().describe('The JavaScript code to run.'),
|
|
@@ -30,10 +31,31 @@ webpage's DOM is *not* available. The JS code must be of the form:
|
|
|
30
31
|
});
|
|
31
32
|
const sandBoxedPage = await sandBoxedBrowser.newPage();
|
|
32
33
|
try {
|
|
33
|
-
|
|
34
|
+
// No stash on overflow: the sandboxed browser is discarded below, so a
|
|
35
|
+
// page-side stash could never be read back. Re-running refined code is
|
|
36
|
+
// free here (it is self-contained computation), so the model is told to
|
|
37
|
+
// do that instead.
|
|
38
|
+
const result = await sandBoxedPage.evaluate(PlaywrightUtils_1.PlaywrightUtils.asSizeGuardedJavaScriptExpression(parameters.javaScriptCode, { stashOversizedResult: false }));
|
|
39
|
+
if (result.oversized) {
|
|
40
|
+
const sizeNote = result.size === null
|
|
41
|
+
? 'it is not JSON-serializable'
|
|
42
|
+
: `${result.size} characters of JSON`;
|
|
43
|
+
return {
|
|
44
|
+
isSuccessful: true,
|
|
45
|
+
forLlm: `The code ran successfully, but its result was too large to return (${sizeNote}). ` +
|
|
46
|
+
`The result was ${result.summary}.\n` +
|
|
47
|
+
'This sandboxed environment is discarded after every call, so the result cannot be ' +
|
|
48
|
+
'retrieved later — re-run this tool with code modified to return less data ' +
|
|
49
|
+
'(aggregate, filter, slice, or count instead).\n' +
|
|
50
|
+
`Preview of the result:\n${result.preview}`,
|
|
51
|
+
metadata: null,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
34
54
|
return {
|
|
35
55
|
isSuccessful: true,
|
|
36
|
-
forLlm:
|
|
56
|
+
forLlm: result.value === undefined
|
|
57
|
+
? 'The code ran successfully and returned undefined.'
|
|
58
|
+
: '' + JSON.stringify(result.value),
|
|
37
59
|
metadata: null,
|
|
38
60
|
};
|
|
39
61
|
}
|
|
@@ -1,9 +1,47 @@
|
|
|
1
1
|
import type { BrowserContext, Locator, Page, PageScreenshotOptions } from 'playwright';
|
|
2
|
+
/**
|
|
3
|
+
* Envelope resolved by expressions built with
|
|
4
|
+
* {@link PlaywrightUtils.asSizeGuardedJavaScriptExpression}. Small results
|
|
5
|
+
* come back inline in `value`; oversized (or non-JSON-serializable) results
|
|
6
|
+
* come back as a structural summary plus preview, with `stashedAt` naming the
|
|
7
|
+
* `window.__dnb_result_<n>` variable holding the full value when stashing was
|
|
8
|
+
* requested.
|
|
9
|
+
*/
|
|
10
|
+
export type SizeGuardedEvaluationResult = {
|
|
11
|
+
__dnbGuard: true;
|
|
12
|
+
oversized: false;
|
|
13
|
+
value: unknown;
|
|
14
|
+
} | {
|
|
15
|
+
__dnbGuard: true;
|
|
16
|
+
oversized: true;
|
|
17
|
+
/** Serialized size in JSON characters, or null if not JSON-serializable. */
|
|
18
|
+
size: number | null;
|
|
19
|
+
summary: string;
|
|
20
|
+
preview: string;
|
|
21
|
+
stashedAt: string | null;
|
|
22
|
+
};
|
|
2
23
|
/**
|
|
3
24
|
* Miscellaneous utility functions for working with the Playwright SDK. If you are looking to
|
|
4
25
|
* instantiate a Playwright instance, see PlaywrightSetup instead.
|
|
5
26
|
*/
|
|
6
27
|
export declare class PlaywrightUtils {
|
|
28
|
+
/**
|
|
29
|
+
* Maximum serialized size (in JSON characters) of an evaluation result that
|
|
30
|
+
* {@link asSizeGuardedJavaScriptExpression} returns inline. Results above
|
|
31
|
+
* this go into the conversation history verbatim, so this bounds prompt
|
|
32
|
+
* growth per tool call.
|
|
33
|
+
*/
|
|
34
|
+
static readonly MAX_INLINE_RESULT_JSON_CHARS = 32000;
|
|
35
|
+
/**
|
|
36
|
+
* Number of characters of the serialized result included as a preview when
|
|
37
|
+
* a result is too large to return inline.
|
|
38
|
+
*/
|
|
39
|
+
static readonly OVERSIZED_RESULT_PREVIEW_CHARS = 2000;
|
|
40
|
+
/**
|
|
41
|
+
* Number of oversized results kept alive in the page via
|
|
42
|
+
* `window.__dnb_result_<n>` stash variables before the oldest is released.
|
|
43
|
+
*/
|
|
44
|
+
static readonly MAX_STASHED_RESULTS = 3;
|
|
7
45
|
private static _blankJpeg;
|
|
8
46
|
private static _blankPng;
|
|
9
47
|
private static readonly _browserInstallPromises;
|
|
@@ -49,6 +87,43 @@ export declare class PlaywrightUtils {
|
|
|
49
87
|
* scripts.
|
|
50
88
|
*/
|
|
51
89
|
static setupBasicBrowserContext(browserContext: BrowserContext): Promise<void>;
|
|
90
|
+
/**
|
|
91
|
+
* Wraps arbitrary JavaScript code so that it can be passed as a string to
|
|
92
|
+
* Playwright's evaluate(). Playwright evaluates string arguments as bare
|
|
93
|
+
* expressions, so function-form code like `() => {...}` would evaluate to
|
|
94
|
+
* an (unserializable) function object that is never invoked, and the caller
|
|
95
|
+
* would get back undefined. The returned expression invokes function-form
|
|
96
|
+
* code and passes plain expressions through unchanged. Promise results
|
|
97
|
+
* (e.g. from async function-form code) are awaited by evaluate() itself.
|
|
98
|
+
*
|
|
99
|
+
* The wrapped code is placed on its own lines so that a trailing `//`
|
|
100
|
+
* comment cannot swallow the closing parenthesis.
|
|
101
|
+
*/
|
|
102
|
+
static asInvokedJavaScriptExpression(code: string): string;
|
|
103
|
+
/**
|
|
104
|
+
* Like {@link asInvokedJavaScriptExpression}, but the result is measured
|
|
105
|
+
* page-side before it crosses the Playwright wire, so an enormous result
|
|
106
|
+
* never gets serialized over CDP into this process. The returned expression
|
|
107
|
+
* always resolves to a {@link SizeGuardedEvaluationResult} envelope.
|
|
108
|
+
*
|
|
109
|
+
* When the serialized result exceeds `maxInlineChars`, the envelope carries
|
|
110
|
+
* a structural summary and a short preview instead of the value. With
|
|
111
|
+
* `stashOversizedResult`, the value itself (a reference, not a copy) is
|
|
112
|
+
* additionally kept in the page as `window.__dnb_result_<n>` so follow-up
|
|
113
|
+
* evaluations can slice or aggregate it. The stash counter lives on
|
|
114
|
+
* `window`, so it resets with the page — names stay deterministic across
|
|
115
|
+
* cached replays because navigations replay too. Only the most recent
|
|
116
|
+
* {@link MAX_STASHED_RESULTS} stashes are kept alive.
|
|
117
|
+
*
|
|
118
|
+
* Results that fail JSON serialization (circular structures, BigInt, etc.)
|
|
119
|
+
* are treated as oversized with a `null` size, since they could not be
|
|
120
|
+
* returned through the tool result either.
|
|
121
|
+
*/
|
|
122
|
+
static asSizeGuardedJavaScriptExpression(code: string, options: {
|
|
123
|
+
stashOversizedResult: boolean;
|
|
124
|
+
maxInlineChars?: number;
|
|
125
|
+
previewChars?: number;
|
|
126
|
+
}): string;
|
|
52
127
|
/**
|
|
53
128
|
* Returned true IFF the given error is a Playwright error regarding page closing,
|
|
54
129
|
* of if the given error is an instance of {@link PageClosedException}.
|
|
@@ -133,6 +133,111 @@ class PlaywrightUtils {
|
|
|
133
133
|
await browserContext.addInitScript(dialog_prompt_tracker_1.installDialogPromptTracker);
|
|
134
134
|
await browserContext.addInitScript(smart_selector_generator_1.installSmartSelectorGenerator);
|
|
135
135
|
}
|
|
136
|
+
/**
|
|
137
|
+
* Wraps arbitrary JavaScript code so that it can be passed as a string to
|
|
138
|
+
* Playwright's evaluate(). Playwright evaluates string arguments as bare
|
|
139
|
+
* expressions, so function-form code like `() => {...}` would evaluate to
|
|
140
|
+
* an (unserializable) function object that is never invoked, and the caller
|
|
141
|
+
* would get back undefined. The returned expression invokes function-form
|
|
142
|
+
* code and passes plain expressions through unchanged. Promise results
|
|
143
|
+
* (e.g. from async function-form code) are awaited by evaluate() itself.
|
|
144
|
+
*
|
|
145
|
+
* The wrapped code is placed on its own lines so that a trailing `//`
|
|
146
|
+
* comment cannot swallow the closing parenthesis.
|
|
147
|
+
*/
|
|
148
|
+
static asInvokedJavaScriptExpression(code) {
|
|
149
|
+
return `(() => {
|
|
150
|
+
const __donobuEvalResult = (
|
|
151
|
+
${code}
|
|
152
|
+
);
|
|
153
|
+
return typeof __donobuEvalResult === 'function'
|
|
154
|
+
? __donobuEvalResult()
|
|
155
|
+
: __donobuEvalResult;
|
|
156
|
+
})()`;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Like {@link asInvokedJavaScriptExpression}, but the result is measured
|
|
160
|
+
* page-side before it crosses the Playwright wire, so an enormous result
|
|
161
|
+
* never gets serialized over CDP into this process. The returned expression
|
|
162
|
+
* always resolves to a {@link SizeGuardedEvaluationResult} envelope.
|
|
163
|
+
*
|
|
164
|
+
* When the serialized result exceeds `maxInlineChars`, the envelope carries
|
|
165
|
+
* a structural summary and a short preview instead of the value. With
|
|
166
|
+
* `stashOversizedResult`, the value itself (a reference, not a copy) is
|
|
167
|
+
* additionally kept in the page as `window.__dnb_result_<n>` so follow-up
|
|
168
|
+
* evaluations can slice or aggregate it. The stash counter lives on
|
|
169
|
+
* `window`, so it resets with the page — names stay deterministic across
|
|
170
|
+
* cached replays because navigations replay too. Only the most recent
|
|
171
|
+
* {@link MAX_STASHED_RESULTS} stashes are kept alive.
|
|
172
|
+
*
|
|
173
|
+
* Results that fail JSON serialization (circular structures, BigInt, etc.)
|
|
174
|
+
* are treated as oversized with a `null` size, since they could not be
|
|
175
|
+
* returned through the tool result either.
|
|
176
|
+
*/
|
|
177
|
+
static asSizeGuardedJavaScriptExpression(code, options) {
|
|
178
|
+
const maxInlineChars = options.maxInlineChars ?? PlaywrightUtils.MAX_INLINE_RESULT_JSON_CHARS;
|
|
179
|
+
const previewChars = options.previewChars ?? PlaywrightUtils.OVERSIZED_RESULT_PREVIEW_CHARS;
|
|
180
|
+
const stashSnippet = options.stashOversizedResult
|
|
181
|
+
? `{
|
|
182
|
+
const __w = window;
|
|
183
|
+
__w.__dnb_stash_count = (__w.__dnb_stash_count || 0) + 1;
|
|
184
|
+
__stashedAt = '__dnb_result_' + __w.__dnb_stash_count;
|
|
185
|
+
__w[__stashedAt] = __value;
|
|
186
|
+
__w.__dnb_stash_names = __w.__dnb_stash_names || [];
|
|
187
|
+
__w.__dnb_stash_names.push(__stashedAt);
|
|
188
|
+
while (__w.__dnb_stash_names.length > ${PlaywrightUtils.MAX_STASHED_RESULTS}) {
|
|
189
|
+
delete __w[__w.__dnb_stash_names.shift()];
|
|
190
|
+
}
|
|
191
|
+
}`
|
|
192
|
+
: '';
|
|
193
|
+
return `(() => {
|
|
194
|
+
const __pending = Promise.resolve(
|
|
195
|
+
${PlaywrightUtils.asInvokedJavaScriptExpression(code)}
|
|
196
|
+
);
|
|
197
|
+
return __pending.then((__value) => {
|
|
198
|
+
if (__value === undefined) {
|
|
199
|
+
return { __dnbGuard: true, oversized: false, value: __value };
|
|
200
|
+
}
|
|
201
|
+
let __json;
|
|
202
|
+
try {
|
|
203
|
+
__json = JSON.stringify(__value);
|
|
204
|
+
} catch {
|
|
205
|
+
__json = undefined;
|
|
206
|
+
}
|
|
207
|
+
if (__json !== undefined && __json.length <= ${maxInlineChars}) {
|
|
208
|
+
return { __dnbGuard: true, oversized: false, value: __value };
|
|
209
|
+
}
|
|
210
|
+
const __summary = (() => {
|
|
211
|
+
if (Array.isArray(__value)) {
|
|
212
|
+
const __first = __value.find((el) => el && typeof el === 'object');
|
|
213
|
+
const __keys = __first ? Object.keys(__first).slice(0, 12).join(', ') : null;
|
|
214
|
+
return 'an array of ' + __value.length + ' elements' +
|
|
215
|
+
(__keys ? ' (element keys: ' + __keys + ')' : '');
|
|
216
|
+
}
|
|
217
|
+
if (typeof __value === 'object' && __value !== null) {
|
|
218
|
+
return 'an object with keys: ' + Object.keys(__value).slice(0, 24).join(', ');
|
|
219
|
+
}
|
|
220
|
+
return 'a ' + typeof __value;
|
|
221
|
+
})();
|
|
222
|
+
let __preview = '';
|
|
223
|
+
try {
|
|
224
|
+
__preview = (__json !== undefined ? __json : String(__value)).slice(0, ${previewChars});
|
|
225
|
+
} catch {
|
|
226
|
+
// Leave the preview empty if the value cannot be stringified at all.
|
|
227
|
+
}
|
|
228
|
+
let __stashedAt = null;
|
|
229
|
+
${stashSnippet}
|
|
230
|
+
return {
|
|
231
|
+
__dnbGuard: true,
|
|
232
|
+
oversized: true,
|
|
233
|
+
size: __json === undefined ? null : __json.length,
|
|
234
|
+
summary: __summary,
|
|
235
|
+
preview: __preview,
|
|
236
|
+
stashedAt: __stashedAt,
|
|
237
|
+
};
|
|
238
|
+
});
|
|
239
|
+
})()`;
|
|
240
|
+
}
|
|
136
241
|
/**
|
|
137
242
|
* Returned true IFF the given error is a Playwright error regarding page closing,
|
|
138
243
|
* of if the given error is an instance of {@link PageClosedException}.
|
|
@@ -297,6 +402,23 @@ class PlaywrightUtils {
|
|
|
297
402
|
}
|
|
298
403
|
}
|
|
299
404
|
exports.PlaywrightUtils = PlaywrightUtils;
|
|
405
|
+
/**
|
|
406
|
+
* Maximum serialized size (in JSON characters) of an evaluation result that
|
|
407
|
+
* {@link asSizeGuardedJavaScriptExpression} returns inline. Results above
|
|
408
|
+
* this go into the conversation history verbatim, so this bounds prompt
|
|
409
|
+
* growth per tool call.
|
|
410
|
+
*/
|
|
411
|
+
PlaywrightUtils.MAX_INLINE_RESULT_JSON_CHARS = 32_000;
|
|
412
|
+
/**
|
|
413
|
+
* Number of characters of the serialized result included as a preview when
|
|
414
|
+
* a result is too large to return inline.
|
|
415
|
+
*/
|
|
416
|
+
PlaywrightUtils.OVERSIZED_RESULT_PREVIEW_CHARS = 2_000;
|
|
417
|
+
/**
|
|
418
|
+
* Number of oversized results kept alive in the page via
|
|
419
|
+
* `window.__dnb_result_<n>` stash variables before the oldest is released.
|
|
420
|
+
*/
|
|
421
|
+
PlaywrightUtils.MAX_STASHED_RESULTS = 3;
|
|
300
422
|
// Per-browser in-flight install promises — deduplicate concurrent requests.
|
|
301
423
|
PlaywrightUtils._browserInstallPromises = new Map();
|
|
302
424
|
//# sourceMappingURL=PlaywrightUtils.js.map
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.RunInlineJavaScriptCodeTool = exports.RunInlineJavaScriptCodeGptSchema = exports.RunInlineJavaScriptCodeCoreSchema = void 0;
|
|
4
4
|
const v4_1 = require("zod/v4");
|
|
5
5
|
const ToolSchema_1 = require("../models/ToolSchema");
|
|
6
|
+
const PlaywrightUtils_1 = require("../utils/PlaywrightUtils");
|
|
6
7
|
const TargetUtils_1 = require("../utils/TargetUtils");
|
|
7
8
|
const Tool_1 = require("./Tool");
|
|
8
9
|
exports.RunInlineJavaScriptCodeCoreSchema = v4_1.z.object({
|
|
@@ -24,10 +25,50 @@ as well as the overall environment of the webpage itself. The JS code must be of
|
|
|
24
25
|
\`\`\``, exports.RunInlineJavaScriptCodeCoreSchema, exports.RunInlineJavaScriptCodeGptSchema, false, undefined, ['web']);
|
|
25
26
|
}
|
|
26
27
|
async call(context, parameters) {
|
|
27
|
-
|
|
28
|
+
let result;
|
|
29
|
+
try {
|
|
30
|
+
result = await (0, TargetUtils_1.webPage)(context).evaluate(PlaywrightUtils_1.PlaywrightUtils.asSizeGuardedJavaScriptExpression(parameters.javaScriptCode, { stashOversizedResult: true }));
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
// Stash variables are wiped by navigation/reload, which surfaces as a
|
|
34
|
+
// cryptic "cannot read properties of undefined" — give the model the
|
|
35
|
+
// real story so it re-extracts instead of retrying blind.
|
|
36
|
+
if (parameters.javaScriptCode.includes('__dnb_result_') &&
|
|
37
|
+
!PlaywrightUtils_1.PlaywrightUtils.isPageClosedError(error)) {
|
|
38
|
+
return {
|
|
39
|
+
isSuccessful: false,
|
|
40
|
+
forLlm: `FAILED! ${error.message}\n` +
|
|
41
|
+
'Note: window.__dnb_result_* variables are cleared when the page ' +
|
|
42
|
+
'navigates or reloads, and only the most recent ' +
|
|
43
|
+
`${PlaywrightUtils_1.PlaywrightUtils.MAX_STASHED_RESULTS} are kept. If the variable ` +
|
|
44
|
+
'no longer exists, re-run the code that originally produced it.',
|
|
45
|
+
metadata: null,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
if (result.oversized) {
|
|
51
|
+
const sizeNote = result.size === null
|
|
52
|
+
? 'it is not JSON-serializable'
|
|
53
|
+
: `${result.size} characters of JSON`;
|
|
54
|
+
return {
|
|
55
|
+
isSuccessful: true,
|
|
56
|
+
forLlm: `The code ran successfully, but its result was too large to return directly (${sizeNote}). ` +
|
|
57
|
+
`The result is ${result.summary}.\n` +
|
|
58
|
+
`The full result is saved in the page as \`window.${result.stashedAt}\` and remains ` +
|
|
59
|
+
'available until the page navigates or reloads.\n' +
|
|
60
|
+
`Retrieve what you need with further ${RunInlineJavaScriptCodeTool.NAME} calls that ` +
|
|
61
|
+
'slice, filter, or aggregate it, for example:\n' +
|
|
62
|
+
`() => { return window.${result.stashedAt}.slice(0, 100); }\n` +
|
|
63
|
+
`Preview of the result:\n${result.preview}`,
|
|
64
|
+
metadata: null,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
28
67
|
return {
|
|
29
68
|
isSuccessful: true,
|
|
30
|
-
forLlm:
|
|
69
|
+
forLlm: result.value === undefined
|
|
70
|
+
? 'The code ran successfully and returned undefined.'
|
|
71
|
+
: '' + JSON.stringify(result.value),
|
|
31
72
|
metadata: null,
|
|
32
73
|
};
|
|
33
74
|
}
|
|
@@ -4,6 +4,7 @@ exports.RunSandboxedJavaScriptCodeTool = exports.RunSandboxedJavaScriptCodeGptSc
|
|
|
4
4
|
const playwright_1 = require("playwright");
|
|
5
5
|
const v4_1 = require("zod/v4");
|
|
6
6
|
const ToolSchema_1 = require("../models/ToolSchema");
|
|
7
|
+
const PlaywrightUtils_1 = require("../utils/PlaywrightUtils");
|
|
7
8
|
const Tool_1 = require("./Tool");
|
|
8
9
|
exports.RunSandboxedJavaScriptCodeCoreSchema = v4_1.z.object({
|
|
9
10
|
javaScriptCode: v4_1.z.string().describe('The JavaScript code to run.'),
|
|
@@ -30,10 +31,31 @@ webpage's DOM is *not* available. The JS code must be of the form:
|
|
|
30
31
|
});
|
|
31
32
|
const sandBoxedPage = await sandBoxedBrowser.newPage();
|
|
32
33
|
try {
|
|
33
|
-
|
|
34
|
+
// No stash on overflow: the sandboxed browser is discarded below, so a
|
|
35
|
+
// page-side stash could never be read back. Re-running refined code is
|
|
36
|
+
// free here (it is self-contained computation), so the model is told to
|
|
37
|
+
// do that instead.
|
|
38
|
+
const result = await sandBoxedPage.evaluate(PlaywrightUtils_1.PlaywrightUtils.asSizeGuardedJavaScriptExpression(parameters.javaScriptCode, { stashOversizedResult: false }));
|
|
39
|
+
if (result.oversized) {
|
|
40
|
+
const sizeNote = result.size === null
|
|
41
|
+
? 'it is not JSON-serializable'
|
|
42
|
+
: `${result.size} characters of JSON`;
|
|
43
|
+
return {
|
|
44
|
+
isSuccessful: true,
|
|
45
|
+
forLlm: `The code ran successfully, but its result was too large to return (${sizeNote}). ` +
|
|
46
|
+
`The result was ${result.summary}.\n` +
|
|
47
|
+
'This sandboxed environment is discarded after every call, so the result cannot be ' +
|
|
48
|
+
'retrieved later — re-run this tool with code modified to return less data ' +
|
|
49
|
+
'(aggregate, filter, slice, or count instead).\n' +
|
|
50
|
+
`Preview of the result:\n${result.preview}`,
|
|
51
|
+
metadata: null,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
34
54
|
return {
|
|
35
55
|
isSuccessful: true,
|
|
36
|
-
forLlm:
|
|
56
|
+
forLlm: result.value === undefined
|
|
57
|
+
? 'The code ran successfully and returned undefined.'
|
|
58
|
+
: '' + JSON.stringify(result.value),
|
|
37
59
|
metadata: null,
|
|
38
60
|
};
|
|
39
61
|
}
|
|
@@ -1,9 +1,47 @@
|
|
|
1
1
|
import type { BrowserContext, Locator, Page, PageScreenshotOptions } from 'playwright';
|
|
2
|
+
/**
|
|
3
|
+
* Envelope resolved by expressions built with
|
|
4
|
+
* {@link PlaywrightUtils.asSizeGuardedJavaScriptExpression}. Small results
|
|
5
|
+
* come back inline in `value`; oversized (or non-JSON-serializable) results
|
|
6
|
+
* come back as a structural summary plus preview, with `stashedAt` naming the
|
|
7
|
+
* `window.__dnb_result_<n>` variable holding the full value when stashing was
|
|
8
|
+
* requested.
|
|
9
|
+
*/
|
|
10
|
+
export type SizeGuardedEvaluationResult = {
|
|
11
|
+
__dnbGuard: true;
|
|
12
|
+
oversized: false;
|
|
13
|
+
value: unknown;
|
|
14
|
+
} | {
|
|
15
|
+
__dnbGuard: true;
|
|
16
|
+
oversized: true;
|
|
17
|
+
/** Serialized size in JSON characters, or null if not JSON-serializable. */
|
|
18
|
+
size: number | null;
|
|
19
|
+
summary: string;
|
|
20
|
+
preview: string;
|
|
21
|
+
stashedAt: string | null;
|
|
22
|
+
};
|
|
2
23
|
/**
|
|
3
24
|
* Miscellaneous utility functions for working with the Playwright SDK. If you are looking to
|
|
4
25
|
* instantiate a Playwright instance, see PlaywrightSetup instead.
|
|
5
26
|
*/
|
|
6
27
|
export declare class PlaywrightUtils {
|
|
28
|
+
/**
|
|
29
|
+
* Maximum serialized size (in JSON characters) of an evaluation result that
|
|
30
|
+
* {@link asSizeGuardedJavaScriptExpression} returns inline. Results above
|
|
31
|
+
* this go into the conversation history verbatim, so this bounds prompt
|
|
32
|
+
* growth per tool call.
|
|
33
|
+
*/
|
|
34
|
+
static readonly MAX_INLINE_RESULT_JSON_CHARS = 32000;
|
|
35
|
+
/**
|
|
36
|
+
* Number of characters of the serialized result included as a preview when
|
|
37
|
+
* a result is too large to return inline.
|
|
38
|
+
*/
|
|
39
|
+
static readonly OVERSIZED_RESULT_PREVIEW_CHARS = 2000;
|
|
40
|
+
/**
|
|
41
|
+
* Number of oversized results kept alive in the page via
|
|
42
|
+
* `window.__dnb_result_<n>` stash variables before the oldest is released.
|
|
43
|
+
*/
|
|
44
|
+
static readonly MAX_STASHED_RESULTS = 3;
|
|
7
45
|
private static _blankJpeg;
|
|
8
46
|
private static _blankPng;
|
|
9
47
|
private static readonly _browserInstallPromises;
|
|
@@ -49,6 +87,43 @@ export declare class PlaywrightUtils {
|
|
|
49
87
|
* scripts.
|
|
50
88
|
*/
|
|
51
89
|
static setupBasicBrowserContext(browserContext: BrowserContext): Promise<void>;
|
|
90
|
+
/**
|
|
91
|
+
* Wraps arbitrary JavaScript code so that it can be passed as a string to
|
|
92
|
+
* Playwright's evaluate(). Playwright evaluates string arguments as bare
|
|
93
|
+
* expressions, so function-form code like `() => {...}` would evaluate to
|
|
94
|
+
* an (unserializable) function object that is never invoked, and the caller
|
|
95
|
+
* would get back undefined. The returned expression invokes function-form
|
|
96
|
+
* code and passes plain expressions through unchanged. Promise results
|
|
97
|
+
* (e.g. from async function-form code) are awaited by evaluate() itself.
|
|
98
|
+
*
|
|
99
|
+
* The wrapped code is placed on its own lines so that a trailing `//`
|
|
100
|
+
* comment cannot swallow the closing parenthesis.
|
|
101
|
+
*/
|
|
102
|
+
static asInvokedJavaScriptExpression(code: string): string;
|
|
103
|
+
/**
|
|
104
|
+
* Like {@link asInvokedJavaScriptExpression}, but the result is measured
|
|
105
|
+
* page-side before it crosses the Playwright wire, so an enormous result
|
|
106
|
+
* never gets serialized over CDP into this process. The returned expression
|
|
107
|
+
* always resolves to a {@link SizeGuardedEvaluationResult} envelope.
|
|
108
|
+
*
|
|
109
|
+
* When the serialized result exceeds `maxInlineChars`, the envelope carries
|
|
110
|
+
* a structural summary and a short preview instead of the value. With
|
|
111
|
+
* `stashOversizedResult`, the value itself (a reference, not a copy) is
|
|
112
|
+
* additionally kept in the page as `window.__dnb_result_<n>` so follow-up
|
|
113
|
+
* evaluations can slice or aggregate it. The stash counter lives on
|
|
114
|
+
* `window`, so it resets with the page — names stay deterministic across
|
|
115
|
+
* cached replays because navigations replay too. Only the most recent
|
|
116
|
+
* {@link MAX_STASHED_RESULTS} stashes are kept alive.
|
|
117
|
+
*
|
|
118
|
+
* Results that fail JSON serialization (circular structures, BigInt, etc.)
|
|
119
|
+
* are treated as oversized with a `null` size, since they could not be
|
|
120
|
+
* returned through the tool result either.
|
|
121
|
+
*/
|
|
122
|
+
static asSizeGuardedJavaScriptExpression(code: string, options: {
|
|
123
|
+
stashOversizedResult: boolean;
|
|
124
|
+
maxInlineChars?: number;
|
|
125
|
+
previewChars?: number;
|
|
126
|
+
}): string;
|
|
52
127
|
/**
|
|
53
128
|
* Returned true IFF the given error is a Playwright error regarding page closing,
|
|
54
129
|
* of if the given error is an instance of {@link PageClosedException}.
|
|
@@ -133,6 +133,111 @@ class PlaywrightUtils {
|
|
|
133
133
|
await browserContext.addInitScript(dialog_prompt_tracker_1.installDialogPromptTracker);
|
|
134
134
|
await browserContext.addInitScript(smart_selector_generator_1.installSmartSelectorGenerator);
|
|
135
135
|
}
|
|
136
|
+
/**
|
|
137
|
+
* Wraps arbitrary JavaScript code so that it can be passed as a string to
|
|
138
|
+
* Playwright's evaluate(). Playwright evaluates string arguments as bare
|
|
139
|
+
* expressions, so function-form code like `() => {...}` would evaluate to
|
|
140
|
+
* an (unserializable) function object that is never invoked, and the caller
|
|
141
|
+
* would get back undefined. The returned expression invokes function-form
|
|
142
|
+
* code and passes plain expressions through unchanged. Promise results
|
|
143
|
+
* (e.g. from async function-form code) are awaited by evaluate() itself.
|
|
144
|
+
*
|
|
145
|
+
* The wrapped code is placed on its own lines so that a trailing `//`
|
|
146
|
+
* comment cannot swallow the closing parenthesis.
|
|
147
|
+
*/
|
|
148
|
+
static asInvokedJavaScriptExpression(code) {
|
|
149
|
+
return `(() => {
|
|
150
|
+
const __donobuEvalResult = (
|
|
151
|
+
${code}
|
|
152
|
+
);
|
|
153
|
+
return typeof __donobuEvalResult === 'function'
|
|
154
|
+
? __donobuEvalResult()
|
|
155
|
+
: __donobuEvalResult;
|
|
156
|
+
})()`;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Like {@link asInvokedJavaScriptExpression}, but the result is measured
|
|
160
|
+
* page-side before it crosses the Playwright wire, so an enormous result
|
|
161
|
+
* never gets serialized over CDP into this process. The returned expression
|
|
162
|
+
* always resolves to a {@link SizeGuardedEvaluationResult} envelope.
|
|
163
|
+
*
|
|
164
|
+
* When the serialized result exceeds `maxInlineChars`, the envelope carries
|
|
165
|
+
* a structural summary and a short preview instead of the value. With
|
|
166
|
+
* `stashOversizedResult`, the value itself (a reference, not a copy) is
|
|
167
|
+
* additionally kept in the page as `window.__dnb_result_<n>` so follow-up
|
|
168
|
+
* evaluations can slice or aggregate it. The stash counter lives on
|
|
169
|
+
* `window`, so it resets with the page — names stay deterministic across
|
|
170
|
+
* cached replays because navigations replay too. Only the most recent
|
|
171
|
+
* {@link MAX_STASHED_RESULTS} stashes are kept alive.
|
|
172
|
+
*
|
|
173
|
+
* Results that fail JSON serialization (circular structures, BigInt, etc.)
|
|
174
|
+
* are treated as oversized with a `null` size, since they could not be
|
|
175
|
+
* returned through the tool result either.
|
|
176
|
+
*/
|
|
177
|
+
static asSizeGuardedJavaScriptExpression(code, options) {
|
|
178
|
+
const maxInlineChars = options.maxInlineChars ?? PlaywrightUtils.MAX_INLINE_RESULT_JSON_CHARS;
|
|
179
|
+
const previewChars = options.previewChars ?? PlaywrightUtils.OVERSIZED_RESULT_PREVIEW_CHARS;
|
|
180
|
+
const stashSnippet = options.stashOversizedResult
|
|
181
|
+
? `{
|
|
182
|
+
const __w = window;
|
|
183
|
+
__w.__dnb_stash_count = (__w.__dnb_stash_count || 0) + 1;
|
|
184
|
+
__stashedAt = '__dnb_result_' + __w.__dnb_stash_count;
|
|
185
|
+
__w[__stashedAt] = __value;
|
|
186
|
+
__w.__dnb_stash_names = __w.__dnb_stash_names || [];
|
|
187
|
+
__w.__dnb_stash_names.push(__stashedAt);
|
|
188
|
+
while (__w.__dnb_stash_names.length > ${PlaywrightUtils.MAX_STASHED_RESULTS}) {
|
|
189
|
+
delete __w[__w.__dnb_stash_names.shift()];
|
|
190
|
+
}
|
|
191
|
+
}`
|
|
192
|
+
: '';
|
|
193
|
+
return `(() => {
|
|
194
|
+
const __pending = Promise.resolve(
|
|
195
|
+
${PlaywrightUtils.asInvokedJavaScriptExpression(code)}
|
|
196
|
+
);
|
|
197
|
+
return __pending.then((__value) => {
|
|
198
|
+
if (__value === undefined) {
|
|
199
|
+
return { __dnbGuard: true, oversized: false, value: __value };
|
|
200
|
+
}
|
|
201
|
+
let __json;
|
|
202
|
+
try {
|
|
203
|
+
__json = JSON.stringify(__value);
|
|
204
|
+
} catch {
|
|
205
|
+
__json = undefined;
|
|
206
|
+
}
|
|
207
|
+
if (__json !== undefined && __json.length <= ${maxInlineChars}) {
|
|
208
|
+
return { __dnbGuard: true, oversized: false, value: __value };
|
|
209
|
+
}
|
|
210
|
+
const __summary = (() => {
|
|
211
|
+
if (Array.isArray(__value)) {
|
|
212
|
+
const __first = __value.find((el) => el && typeof el === 'object');
|
|
213
|
+
const __keys = __first ? Object.keys(__first).slice(0, 12).join(', ') : null;
|
|
214
|
+
return 'an array of ' + __value.length + ' elements' +
|
|
215
|
+
(__keys ? ' (element keys: ' + __keys + ')' : '');
|
|
216
|
+
}
|
|
217
|
+
if (typeof __value === 'object' && __value !== null) {
|
|
218
|
+
return 'an object with keys: ' + Object.keys(__value).slice(0, 24).join(', ');
|
|
219
|
+
}
|
|
220
|
+
return 'a ' + typeof __value;
|
|
221
|
+
})();
|
|
222
|
+
let __preview = '';
|
|
223
|
+
try {
|
|
224
|
+
__preview = (__json !== undefined ? __json : String(__value)).slice(0, ${previewChars});
|
|
225
|
+
} catch {
|
|
226
|
+
// Leave the preview empty if the value cannot be stringified at all.
|
|
227
|
+
}
|
|
228
|
+
let __stashedAt = null;
|
|
229
|
+
${stashSnippet}
|
|
230
|
+
return {
|
|
231
|
+
__dnbGuard: true,
|
|
232
|
+
oversized: true,
|
|
233
|
+
size: __json === undefined ? null : __json.length,
|
|
234
|
+
summary: __summary,
|
|
235
|
+
preview: __preview,
|
|
236
|
+
stashedAt: __stashedAt,
|
|
237
|
+
};
|
|
238
|
+
});
|
|
239
|
+
})()`;
|
|
240
|
+
}
|
|
136
241
|
/**
|
|
137
242
|
* Returned true IFF the given error is a Playwright error regarding page closing,
|
|
138
243
|
* of if the given error is an instance of {@link PageClosedException}.
|
|
@@ -297,6 +402,23 @@ class PlaywrightUtils {
|
|
|
297
402
|
}
|
|
298
403
|
}
|
|
299
404
|
exports.PlaywrightUtils = PlaywrightUtils;
|
|
405
|
+
/**
|
|
406
|
+
* Maximum serialized size (in JSON characters) of an evaluation result that
|
|
407
|
+
* {@link asSizeGuardedJavaScriptExpression} returns inline. Results above
|
|
408
|
+
* this go into the conversation history verbatim, so this bounds prompt
|
|
409
|
+
* growth per tool call.
|
|
410
|
+
*/
|
|
411
|
+
PlaywrightUtils.MAX_INLINE_RESULT_JSON_CHARS = 32_000;
|
|
412
|
+
/**
|
|
413
|
+
* Number of characters of the serialized result included as a preview when
|
|
414
|
+
* a result is too large to return inline.
|
|
415
|
+
*/
|
|
416
|
+
PlaywrightUtils.OVERSIZED_RESULT_PREVIEW_CHARS = 2_000;
|
|
417
|
+
/**
|
|
418
|
+
* Number of oversized results kept alive in the page via
|
|
419
|
+
* `window.__dnb_result_<n>` stash variables before the oldest is released.
|
|
420
|
+
*/
|
|
421
|
+
PlaywrightUtils.MAX_STASHED_RESULTS = 3;
|
|
300
422
|
// Per-browser in-flight install promises — deduplicate concurrent requests.
|
|
301
423
|
PlaywrightUtils._browserInstallPromises = new Map();
|
|
302
424
|
//# sourceMappingURL=PlaywrightUtils.js.map
|