@agent-native/core 0.49.25 → 0.49.26
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/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +8 -1
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/cli/recap.d.ts.map +1 -1
- package/dist/cli/recap.js +43 -11
- package/dist/cli/recap.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +2 -1
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/blocks/library/AnnotatedCodeBlock.js +7 -7
- package/dist/client/blocks/library/AnnotatedCodeBlock.js.map +1 -1
- package/dist/client/blocks/library/DiffBlock.js +3 -3
- package/dist/client/blocks/library/DiffBlock.js.map +1 -1
- package/dist/client/blocks/library/annotation-rail.d.ts +4 -3
- package/dist/client/blocks/library/annotation-rail.d.ts.map +1 -1
- package/dist/client/blocks/library/annotation-rail.js +16 -9
- package/dist/client/blocks/library/annotation-rail.js.map +1 -1
- package/dist/client/blocks/types.d.ts +2 -2
- package/dist/client/blocks/types.js.map +1 -1
- package/dist/coding-tools/run-code.d.ts.map +1 -1
- package/dist/coding-tools/run-code.js +198 -15
- package/dist/coding-tools/run-code.js.map +1 -1
- package/dist/extensions/fetch-tool.js +1 -1
- package/dist/extensions/fetch-tool.js.map +1 -1
- package/dist/mcp/build-server.d.ts.map +1 -1
- package/dist/mcp/build-server.js +1 -0
- package/dist/mcp/build-server.js.map +1 -1
- package/dist/mcp/builtin-tools.d.ts +8 -4
- package/dist/mcp/builtin-tools.d.ts.map +1 -1
- package/dist/mcp/builtin-tools.js +247 -13
- package/dist/mcp/builtin-tools.js.map +1 -1
- package/dist/provider-api/actions/query-staged-dataset.d.ts.map +1 -1
- package/dist/provider-api/actions/query-staged-dataset.js +1 -0
- package/dist/provider-api/actions/query-staged-dataset.js.map +1 -1
- package/dist/provider-api/index.d.ts +9 -4
- package/dist/provider-api/index.d.ts.map +1 -1
- package/dist/provider-api/index.js +164 -33
- package/dist/provider-api/index.js.map +1 -1
- package/dist/provider-api/staged-datasets-store.d.ts.map +1 -1
- package/dist/provider-api/staged-datasets-store.js +29 -6
- package/dist/provider-api/staged-datasets-store.js.map +1 -1
- package/dist/provider-api/staging.d.ts +6 -1
- package/dist/provider-api/staging.d.ts.map +1 -1
- package/dist/provider-api/staging.js +35 -6
- package/dist/provider-api/staging.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +157 -80
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/prompts/shared-rules.d.ts +1 -1
- package/dist/server/prompts/shared-rules.d.ts.map +1 -1
- package/dist/server/prompts/shared-rules.js +5 -7
- package/dist/server/prompts/shared-rules.js.map +1 -1
- package/dist/server/schema-prompt.js +1 -1
- package/dist/server/schema-prompt.js.map +1 -1
- package/dist/templates/default/.agents/skills/actions/SKILL.md +16 -4
- package/dist/templates/workspace-core/.agents/skills/actions/SKILL.md +16 -4
- package/package.json +1 -1
- package/src/templates/default/.agents/skills/actions/SKILL.md +16 -4
- package/src/templates/workspace-core/.agents/skills/actions/SKILL.md +16 -4
|
@@ -92,8 +92,8 @@ export function createRunCodeEntry(getActions, opts = {}) {
|
|
|
92
92
|
const extraBridgeTools = new Set(opts.bridgeTools ?? []);
|
|
93
93
|
return {
|
|
94
94
|
readOnly: true,
|
|
95
|
-
// Allow a generous per-call timeout so large
|
|
96
|
-
// agent-loop's default 60 s cap.
|
|
95
|
+
// Allow a generous per-call timeout so large data-processing jobs don't hit
|
|
96
|
+
// the agent-loop's default 60 s cap.
|
|
97
97
|
timeoutMs: MAX_TIMEOUT_MS,
|
|
98
98
|
maxResultChars: MAX_OUTPUT_CHARS,
|
|
99
99
|
tool: {
|
|
@@ -106,7 +106,10 @@ export function createRunCodeEntry(getActions, opts = {}) {
|
|
|
106
106
|
" Use this to loop over app data readers and compose multi-source analyses without forcing every intermediate result into chat.",
|
|
107
107
|
" - `providerFetch(provider, path, init?)` — authenticated call to a registered provider via the provider-api-request action.",
|
|
108
108
|
" Returns the parsed JSON result (or throws on error).",
|
|
109
|
-
"
|
|
109
|
+
" Supports stageAs/saveToFile/fetchAllPages; use cursorBodyPath for POST-body pagination.",
|
|
110
|
+
" Example: `const data = await providerFetch('<provider-id>', '/records', { query: { limit: 100 } });`",
|
|
111
|
+
" - `providerRequest(provider, path, init?)` — same authenticated call, but returns the full provider-api envelope with request, response status/headers, truncation, and body metadata.",
|
|
112
|
+
" - `providerFetchAll(provider, path, init?)` — generic pagination helper for cursor, page, and offset APIs. Pass `pagination: { itemsPath, cursorPath or nextCursorPath, cursorParam or cursorBodyPath, pageParam, offsetParam, pageSize, maxPages }`. Returns `{ items, pages, pageCount, itemCount, hasMore, lastCursor, stoppedReason }`.",
|
|
110
113
|
" - `webFetch(url, init?)` — outbound HTTP request via the web-request action.",
|
|
111
114
|
" Returns `{ status, body }` where body is the response text.",
|
|
112
115
|
" Example: `const { body } = await webFetch('https://api.example.com/data');`",
|
|
@@ -152,7 +155,7 @@ export function createRunCodeEntry(getActions, opts = {}) {
|
|
|
152
155
|
const actions = getActions();
|
|
153
156
|
const bridgeToken = crypto.randomBytes(32).toString("hex");
|
|
154
157
|
// Start bridge server — resolves once the server is listening.
|
|
155
|
-
const { server, bridgePort, cleanup: cleanupBridge, } = await startBridgeServer(bridgeToken, actions, context, DEFAULT_BRIDGE_TOOLS, extraBridgeTools);
|
|
158
|
+
const { server, bridgePort, getUsedTools, cleanup: cleanupBridge, } = await startBridgeServer(bridgeToken, actions, context, DEFAULT_BRIDGE_TOOLS, extraBridgeTools);
|
|
156
159
|
let tmpDir;
|
|
157
160
|
let tmpFile;
|
|
158
161
|
try {
|
|
@@ -232,6 +235,9 @@ export function createRunCodeEntry(getActions, opts = {}) {
|
|
|
232
235
|
lines.push(`timedOut: true (${timeoutMs}ms)`);
|
|
233
236
|
if (exitCode !== 0 && exitCode !== null)
|
|
234
237
|
lines.push(`exitCode: ${exitCode}`);
|
|
238
|
+
const usedTools = getUsedTools();
|
|
239
|
+
if (usedTools.length)
|
|
240
|
+
lines.push(`bridgeToolsUsed: ${usedTools.join(", ")}`);
|
|
235
241
|
lines.push(combined);
|
|
236
242
|
const full = lines.join("\n\n");
|
|
237
243
|
if (full.length > maxOutputChars) {
|
|
@@ -256,6 +262,7 @@ export function createRunCodeEntry(getActions, opts = {}) {
|
|
|
256
262
|
};
|
|
257
263
|
}
|
|
258
264
|
async function startBridgeServer(token, actions, context, defaultTools, extraTools) {
|
|
265
|
+
const usedTools = new Set();
|
|
259
266
|
const server = http.createServer((req, res) => {
|
|
260
267
|
if (req.method !== "POST" || req.url !== "/tool") {
|
|
261
268
|
res.writeHead(404);
|
|
@@ -286,7 +293,7 @@ async function startBridgeServer(token, actions, context, defaultTools, extraToo
|
|
|
286
293
|
req.on("end", () => {
|
|
287
294
|
if (rejected)
|
|
288
295
|
return;
|
|
289
|
-
handleBridgeRequest(body, actions, context, defaultTools, extraTools, res);
|
|
296
|
+
handleBridgeRequest(body, actions, context, defaultTools, extraTools, usedTools, res);
|
|
290
297
|
});
|
|
291
298
|
req.on("error", () => {
|
|
292
299
|
res.writeHead(500);
|
|
@@ -305,9 +312,14 @@ async function startBridgeServer(token, actions, context, defaultTools, extraToo
|
|
|
305
312
|
}
|
|
306
313
|
catch { }
|
|
307
314
|
};
|
|
308
|
-
return {
|
|
315
|
+
return {
|
|
316
|
+
server,
|
|
317
|
+
bridgePort,
|
|
318
|
+
getUsedTools: () => Array.from(usedTools).sort(),
|
|
319
|
+
cleanup,
|
|
320
|
+
};
|
|
309
321
|
}
|
|
310
|
-
function handleBridgeRequest(rawBody, actions, context, defaultTools, extraTools, res) {
|
|
322
|
+
function handleBridgeRequest(rawBody, actions, context, defaultTools, extraTools, usedTools, res) {
|
|
311
323
|
let parsed;
|
|
312
324
|
try {
|
|
313
325
|
parsed = JSON.parse(rawBody);
|
|
@@ -343,6 +355,7 @@ function handleBridgeRequest(rawBody, actions, context, defaultTools, extraTools
|
|
|
343
355
|
return;
|
|
344
356
|
}
|
|
345
357
|
const toolArgs = parsed.args ?? {};
|
|
358
|
+
usedTools.add(toolName);
|
|
346
359
|
// Run the tool with the parent request context so auth/org/owner resolution
|
|
347
360
|
// works exactly as it does in the normal agent loop.
|
|
348
361
|
entry
|
|
@@ -363,7 +376,8 @@ function handleBridgeRequest(rawBody, actions, context, defaultTools, extraTools
|
|
|
363
376
|
// ---------------------------------------------------------------------------
|
|
364
377
|
/**
|
|
365
378
|
* Wrap the user's code in an ESM module that:
|
|
366
|
-
* 1. Defines `providerFetch`
|
|
379
|
+
* 1. Defines `providerFetch`, `providerRequest`, `providerFetchAll`, and
|
|
380
|
+
* `webFetch` helpers via the bridge.
|
|
367
381
|
* 2. Runs the user's code as top-level await in an async IIFE.
|
|
368
382
|
*/
|
|
369
383
|
function buildSandboxModule(userCode, bridgePort, bridgeToken) {
|
|
@@ -423,11 +437,7 @@ async function appAction(name, args = {}) {
|
|
|
423
437
|
return _parseBridgeResult(await _bridgeCall(name, args));
|
|
424
438
|
}
|
|
425
439
|
|
|
426
|
-
|
|
427
|
-
* Call a provider API via the authenticated provider-api-request action.
|
|
428
|
-
* Returns the parsed JSON response body (or throws on error).
|
|
429
|
-
*/
|
|
430
|
-
async function providerFetch(provider, apiPath, init = {}) {
|
|
440
|
+
async function providerRequest(provider, apiPath, init = {}) {
|
|
431
441
|
const method = (init.method || "GET").toUpperCase();
|
|
432
442
|
const rawResult = await _bridgeCall("provider-api-request", {
|
|
433
443
|
provider,
|
|
@@ -447,8 +457,15 @@ async function providerFetch(provider, apiPath, init = {}) {
|
|
|
447
457
|
...(init.saveToFile ? { saveToFile: init.saveToFile } : {}),
|
|
448
458
|
...(init.fetchAllPages ? { fetchAllPages: init.fetchAllPages } : {}),
|
|
449
459
|
});
|
|
450
|
-
|
|
451
|
-
|
|
460
|
+
return _parseBridgeResult(rawResult);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Call a provider API via the authenticated provider-api-request action.
|
|
465
|
+
* Returns the parsed JSON response body (or throws on error).
|
|
466
|
+
*/
|
|
467
|
+
async function providerFetch(provider, apiPath, init = {}) {
|
|
468
|
+
const parsed = await providerRequest(provider, apiPath, init);
|
|
452
469
|
// Unwrap the provider-api-request envelope ({ provider, request, response, guidance })
|
|
453
470
|
// so callers get the actual response body. fetchAllPages / saveToFile results
|
|
454
471
|
// (which have no \`response\` field) are returned as-is.
|
|
@@ -463,6 +480,172 @@ async function providerFetch(provider, apiPath, init = {}) {
|
|
|
463
480
|
return parsed;
|
|
464
481
|
}
|
|
465
482
|
|
|
483
|
+
function _cloneJson(value) {
|
|
484
|
+
if (value === undefined || value === null) return value;
|
|
485
|
+
return JSON.parse(JSON.stringify(value));
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function _pathParts(path) {
|
|
489
|
+
if (!path || typeof path !== "string") return [];
|
|
490
|
+
return path
|
|
491
|
+
.replace(/\\[(\\d+)\\]/g, ".$1")
|
|
492
|
+
.split(".")
|
|
493
|
+
.map((part) => part.trim())
|
|
494
|
+
.filter(Boolean);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function _getByPath(value, path) {
|
|
498
|
+
let current = value;
|
|
499
|
+
for (const part of _pathParts(path)) {
|
|
500
|
+
if (current === undefined || current === null) return undefined;
|
|
501
|
+
current = current[part];
|
|
502
|
+
}
|
|
503
|
+
return current;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function _setByPath(value, path, nextValue) {
|
|
507
|
+
const parts = _pathParts(path);
|
|
508
|
+
if (!parts.length) return value;
|
|
509
|
+
const root = value && typeof value === "object" ? _cloneJson(value) : {};
|
|
510
|
+
let current = root;
|
|
511
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
512
|
+
const part = parts[i];
|
|
513
|
+
if (!current[part] || typeof current[part] !== "object") current[part] = {};
|
|
514
|
+
current = current[part];
|
|
515
|
+
}
|
|
516
|
+
current[parts[parts.length - 1]] = nextValue;
|
|
517
|
+
return root;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function _extractItems(page, itemsPath) {
|
|
521
|
+
if (itemsPath) {
|
|
522
|
+
const value = _getByPath(page, itemsPath);
|
|
523
|
+
return Array.isArray(value) ? value : [];
|
|
524
|
+
}
|
|
525
|
+
if (Array.isArray(page)) return page;
|
|
526
|
+
if (!page || typeof page !== "object") return [];
|
|
527
|
+
for (const key of ["data", "results", "items", "records", "rows", "calls", "messages", "tickets", "issues", "deals"]) {
|
|
528
|
+
if (Array.isArray(page[key])) return page[key];
|
|
529
|
+
}
|
|
530
|
+
return [];
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
function _withoutProviderFetchAllOptions(init) {
|
|
534
|
+
const {
|
|
535
|
+
pagination: _pagination,
|
|
536
|
+
fetchAllPages: _fetchAllPages,
|
|
537
|
+
stageAs: _stageAs,
|
|
538
|
+
itemsPath: _itemsPath,
|
|
539
|
+
saveToFile: _saveToFile,
|
|
540
|
+
...rest
|
|
541
|
+
} = init || {};
|
|
542
|
+
return rest;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* Fetch every page from a provider API using generic cursor, page-number, or
|
|
547
|
+
* offset pagination. Prefer this inside run-code when the answer depends on a
|
|
548
|
+
* broad provider corpus rather than a single bounded request.
|
|
549
|
+
*/
|
|
550
|
+
async function providerFetchAll(provider, apiPath, init = {}) {
|
|
551
|
+
const pagination = init.pagination || init.fetchAllPages || {};
|
|
552
|
+
const itemsPath = pagination.itemsPath || init.itemsPath;
|
|
553
|
+
const cursorPath = pagination.nextCursorPath || pagination.cursorPath;
|
|
554
|
+
const maxPagesRaw = Number(pagination.maxPages || init.maxPages || 50);
|
|
555
|
+
const maxPages = Math.max(1, Math.min(Number.isFinite(maxPagesRaw) ? maxPagesRaw : 50, 200));
|
|
556
|
+
const baseInit = _withoutProviderFetchAllOptions(init);
|
|
557
|
+
let query = _cloneJson(init.query || {});
|
|
558
|
+
let body = _cloneJson(init.body);
|
|
559
|
+
let pageNumber = Number(pagination.startPage || 1);
|
|
560
|
+
let offset = Number(pagination.startOffset || 0);
|
|
561
|
+
const pages = [];
|
|
562
|
+
const items = [];
|
|
563
|
+
let lastCursor = null;
|
|
564
|
+
let stoppedReason = "completed";
|
|
565
|
+
|
|
566
|
+
for (let pageIndex = 0; pageIndex < maxPages; pageIndex++) {
|
|
567
|
+
if (pagination.pageParam) {
|
|
568
|
+
query = { ...(query || {}), [pagination.pageParam]: pageNumber };
|
|
569
|
+
}
|
|
570
|
+
if (pagination.offsetParam) {
|
|
571
|
+
query = { ...(query || {}), [pagination.offsetParam]: offset };
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
const page = await providerFetch(provider, apiPath, {
|
|
575
|
+
...baseInit,
|
|
576
|
+
query,
|
|
577
|
+
...(body !== undefined ? { body } : {}),
|
|
578
|
+
});
|
|
579
|
+
pages.push(page);
|
|
580
|
+
const pageItems = _extractItems(page, itemsPath);
|
|
581
|
+
items.push(...pageItems);
|
|
582
|
+
|
|
583
|
+
const nextCursor = cursorPath ? _getByPath(page, cursorPath) : undefined;
|
|
584
|
+
if (nextCursor !== undefined && nextCursor !== null && String(nextCursor) !== "") {
|
|
585
|
+
if (lastCursor !== null && String(nextCursor) === String(lastCursor)) {
|
|
586
|
+
stoppedReason = "repeated-cursor";
|
|
587
|
+
break;
|
|
588
|
+
}
|
|
589
|
+
lastCursor = nextCursor;
|
|
590
|
+
if (pagination.cursorBodyPath) {
|
|
591
|
+
body = _setByPath(body || {}, pagination.cursorBodyPath, nextCursor);
|
|
592
|
+
} else if (pagination.cursorParam) {
|
|
593
|
+
query = { ...(query || {}), [pagination.cursorParam]: nextCursor };
|
|
594
|
+
} else {
|
|
595
|
+
stoppedReason = "cursor-found-without-destination";
|
|
596
|
+
break;
|
|
597
|
+
}
|
|
598
|
+
continue;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
lastCursor = null;
|
|
602
|
+
if (pagination.pageParam) {
|
|
603
|
+
if (pageItems.length === 0) {
|
|
604
|
+
stoppedReason = "empty-page";
|
|
605
|
+
break;
|
|
606
|
+
}
|
|
607
|
+
pageNumber += 1;
|
|
608
|
+
continue;
|
|
609
|
+
}
|
|
610
|
+
if (pagination.offsetParam) {
|
|
611
|
+
if (pageItems.length === 0) {
|
|
612
|
+
stoppedReason = "empty-page";
|
|
613
|
+
break;
|
|
614
|
+
}
|
|
615
|
+
const step = Number(pagination.pageSize || pageItems.length);
|
|
616
|
+
if (!Number.isFinite(step) || step <= 0) {
|
|
617
|
+
stoppedReason = "invalid-page-size";
|
|
618
|
+
break;
|
|
619
|
+
}
|
|
620
|
+
offset += step;
|
|
621
|
+
if (pagination.pageSize && pageItems.length < Number(pagination.pageSize)) {
|
|
622
|
+
stoppedReason = "short-page";
|
|
623
|
+
break;
|
|
624
|
+
}
|
|
625
|
+
continue;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
break;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
const hitPageOrOffsetLimit =
|
|
632
|
+
Boolean(pagination.pageParam || pagination.offsetParam) &&
|
|
633
|
+
stoppedReason === "completed" &&
|
|
634
|
+
pages.length >= maxPages;
|
|
635
|
+
const hasMore =
|
|
636
|
+
(lastCursor !== null && pages.length >= maxPages) || hitPageOrOffsetLimit;
|
|
637
|
+
if (hasMore) stoppedReason = "max-pages";
|
|
638
|
+
return {
|
|
639
|
+
items,
|
|
640
|
+
pages,
|
|
641
|
+
pageCount: pages.length,
|
|
642
|
+
itemCount: items.length,
|
|
643
|
+
hasMore,
|
|
644
|
+
lastCursor,
|
|
645
|
+
stoppedReason,
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
|
|
466
649
|
/**
|
|
467
650
|
* Make an outbound HTTP request via the web-request action.
|
|
468
651
|
* Returns an object \`{ status, body }\` where \`body\` is the response text.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-code.js","sourceRoot":"","sources":["../../src/coding-tools/run-code.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Sandboxed JavaScript execution tool for the agent.\n *\n * Executes user-supplied JavaScript in an isolated child process with:\n * - A scrubbed environment (no app secrets or env vars; only PATH/HOME/TMPDIR).\n * - A fresh temporary working directory.\n * - An ephemeral bridge HTTP server on 127.0.0.1 so the child can call\n * allowlisted registered tools (provider-api-request, web-request, etc.)\n * with the same request context as the parent — without leaking secrets.\n *\n * Security notes:\n * - The bridge token is a 32-byte random hex string generated per invocation.\n * - The bridge binds to 127.0.0.1 only; no external exposure.\n * - The allowlist of callable bridge tools is enforced server-side.\n * - Secret values are NEVER included in the env passed to the child.\n * - When the Node permission model is available (`--permission`, or\n * `--experimental-permission` on Node 20), the child is denied filesystem\n * access outside its own temp dir, child processes, workers, and native\n * addons. Outbound network from the child is NOT blocked by the permission\n * model; the env scrub means such requests carry no credentials, and all\n * authenticated calls must go through the bridge (which applies the\n * registered tools' host allowlists and SSRF guards).\n */\n\nimport crypto from \"node:crypto\";\nimport fs from \"node:fs\";\nimport http from \"node:http\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport { spawn, spawnSync } from \"node:child_process\";\n\nimport type { ActionEntry } from \"../agent/production-agent.js\";\nimport type { ActionRunContext } from \"../action.js\";\n\nconst DEFAULT_TIMEOUT_MS = 120_000;\nconst MAX_TIMEOUT_MS = 600_000;\nconst DEFAULT_MAX_OUTPUT_CHARS = 50_000;\nconst MAX_OUTPUT_CHARS = 200_000;\n/** Hard cap on bridge request bodies so sandboxed code can't exhaust parent memory. */\nconst BRIDGE_MAX_BODY_BYTES = 10 * 1024 * 1024;\n\nfunction sandboxReadAllowPaths(tmpDir: string): string[] {\n const paths = new Set<string>([tmpDir]);\n try {\n paths.add(fs.realpathSync(tmpDir));\n } catch {}\n return [...paths];\n}\n\nfunction sandboxWriteAllowPaths(tmpDir: string): string[] {\n const paths = new Set<string>([tmpDir]);\n try {\n paths.add(fs.realpathSync(tmpDir));\n } catch {}\n return [...paths];\n}\n\n/**\n * Resolve the Node permission-model flag supported by the current runtime,\n * probing once and caching. Returns null when the permission model is\n * unavailable (the sandbox then falls back to env-scrub isolation only).\n */\nlet cachedPermissionFlag: string | null | undefined;\nfunction resolvePermissionFlag(): string | null {\n if (cachedPermissionFlag !== undefined) return cachedPermissionFlag;\n for (const flag of [\"--permission\", \"--experimental-permission\"]) {\n try {\n const probe = spawnSync(\n process.execPath,\n [flag, \"-e\", \"process.exit(0)\"],\n { timeout: 10_000, stdio: \"ignore\" },\n );\n if (probe.status === 0) {\n cachedPermissionFlag = flag;\n return flag;\n }\n } catch {\n // Probe failure means the flag is unsupported; try the next one.\n }\n }\n cachedPermissionFlag = null;\n return null;\n}\n\n/** Tools callable via the sandbox bridge by default. */\nconst DEFAULT_BRIDGE_TOOLS = new Set([\n \"provider-api-request\",\n \"provider-api-docs\",\n \"provider-api-catalog\",\n \"web-request\",\n \"workspace-files\",\n]);\n\nexport interface RunCodeOptions {\n /**\n * Extra tool names (beyond the default set) that the sandbox bridge will\n * forward to the registered action registry.\n */\n bridgeTools?: string[];\n}\n\n/**\n * Create a `run-code` ActionEntry.\n *\n * @param getActions Supplier that returns the current action registry (called\n * at invocation time so updates are reflected).\n * @param opts Optional configuration.\n */\nexport function createRunCodeEntry(\n getActions: () => Record<string, ActionEntry>,\n opts: RunCodeOptions = {},\n): ActionEntry {\n const extraBridgeTools = new Set(opts.bridgeTools ?? []);\n\n return {\n readOnly: true,\n // Allow a generous per-call timeout so large analytics jobs don't hit the\n // agent-loop's default 60 s cap.\n timeoutMs: MAX_TIMEOUT_MS,\n maxResultChars: MAX_OUTPUT_CHARS,\n tool: {\n description: [\n \"Execute JavaScript (Node.js, ESM, top-level await supported) in an isolated sandbox.\",\n \"Use this to fetch, join, aggregate, and reduce large datasets, returning only printed output to the conversation.\",\n \"The sandbox runs with a scrubbed environment (no secrets) and, where the Node permission model is available, no filesystem access outside its own temp dir, no child processes, and no workers. Authenticated calls must go through the provided globals; direct network requests carry no credentials. Note: isolation is process-level (env scrub + Node permission model), not an OS-level container — outbound network from sandbox code is not blocked.\",\n \"Available globals:\",\n \" - `appAction(name, args?)` — call any registered agent-exposed read-only app action/tool and get its parsed result.\",\n \" Use this to loop over app data readers and compose multi-source analyses without forcing every intermediate result into chat.\",\n \" - `providerFetch(provider, path, init?)` — authenticated call to a registered provider via the provider-api-request action.\",\n \" Returns the parsed JSON result (or throws on error).\",\n \" Example: `const data = await providerFetch('hubspot', '/crm/v3/objects/contacts');`\",\n \" - `webFetch(url, init?)` — outbound HTTP request via the web-request action.\",\n \" Returns `{ status, body }` where body is the response text.\",\n \" Example: `const { body } = await webFetch('https://api.example.com/data');`\",\n \" - `workspaceRead(path, opts?)` — read a workspace file by path. Returns content string or null. opts: { offset?, maxChars? }.\",\n \" - `workspaceReadMeta(path, opts?)` — read a workspace file with metadata such as sizeBytes, truncated, and nextOffset.\",\n \" - `workspaceWrite(path, content, contentType?)` — create or overwrite a workspace file.\",\n \" - `workspaceAppend(path, content)` — append text to a workspace file.\",\n \" - `workspaceList(prefix?)` — list workspace files, returns [{ path, sizeBytes, contentType, updatedAt }].\",\n \"Print results with `console.log()`; only stdout+stderr are returned.\",\n \"Timeout defaults to 120 s (max 600 s). Output is truncated to 50 000 chars by default (max 200 000).\",\n ].join(\" \"),\n parameters: {\n type: \"object\",\n properties: {\n code: {\n type: \"string\",\n description:\n \"JavaScript source to execute. ESM syntax, top-level await allowed.\",\n },\n timeoutMs: {\n type: \"number\",\n description: `Execution timeout in milliseconds. Default: ${DEFAULT_TIMEOUT_MS}. Max: ${MAX_TIMEOUT_MS}.`,\n },\n maxOutputChars: {\n type: \"number\",\n description: `Maximum combined stdout+stderr characters to return. Default: ${DEFAULT_MAX_OUTPUT_CHARS}. Max: ${MAX_OUTPUT_CHARS}.`,\n },\n },\n required: [\"code\"],\n },\n },\n run: async (args: Record<string, string>, context?: ActionRunContext) => {\n const code = typeof args.code === \"string\" ? args.code : \"\";\n if (!code.trim()) return \"Error: code is required.\";\n\n const requestedTimeout = Number(args.timeoutMs);\n const timeoutMs =\n Number.isFinite(requestedTimeout) && requestedTimeout > 0\n ? Math.min(requestedTimeout, MAX_TIMEOUT_MS)\n : DEFAULT_TIMEOUT_MS;\n\n const requestedMaxOutput = Number(args.maxOutputChars);\n const maxOutputChars =\n Number.isFinite(requestedMaxOutput) && requestedMaxOutput > 0\n ? Math.min(requestedMaxOutput, MAX_OUTPUT_CHARS)\n : DEFAULT_MAX_OUTPUT_CHARS;\n\n const actions = getActions();\n const bridgeToken = crypto.randomBytes(32).toString(\"hex\");\n\n // Start bridge server — resolves once the server is listening.\n const {\n server,\n bridgePort,\n cleanup: cleanupBridge,\n } = await startBridgeServer(\n bridgeToken,\n actions,\n context,\n DEFAULT_BRIDGE_TOOLS,\n extraBridgeTools,\n );\n\n let tmpDir: string | undefined;\n let tmpFile: string | undefined;\n try {\n // Write code to a temp ESM file (top-level await needs a module).\n const tmpBaseDir = fs.realpathSync(os.tmpdir());\n tmpDir = fs.mkdtempSync(path.join(tmpBaseDir, \"agent-run-code-\"));\n tmpFile = path.join(tmpDir, \"sandbox.mjs\");\n fs.writeFileSync(\n tmpFile,\n buildSandboxModule(code, bridgePort, bridgeToken),\n \"utf8\",\n );\n\n // Build scrubbed env — only safe POSIX vars, no secrets.\n const safeEnv: Record<string, string> = {};\n for (const key of [\n \"PATH\",\n \"HOME\",\n \"TMPDIR\",\n \"TEMP\",\n \"TMP\",\n \"LANG\",\n \"LC_ALL\",\n ]) {\n if (process.env[key]) safeEnv[key] = process.env[key]!;\n }\n // Point TMPDIR inside the sandbox dir so in-sandbox temp writes stay\n // within the permission-model allow list.\n safeEnv.TMPDIR = tmpDir;\n safeEnv.TEMP = tmpDir;\n safeEnv.TMP = tmpDir;\n\n // Lock the child down with the Node permission model when available:\n // filesystem restricted to the sandbox temp dir, and child processes,\n // workers, and native addons denied entirely.\n const permissionFlag = resolvePermissionFlag();\n const nodeArgs = permissionFlag\n ? [\n permissionFlag,\n ...sandboxReadAllowPaths(tmpDir).map(\n (allowedPath) => `--allow-fs-read=${allowedPath}`,\n ),\n ...sandboxWriteAllowPaths(tmpDir).map(\n (allowedPath) => `--allow-fs-write=${allowedPath}`,\n ),\n tmpFile,\n ]\n : [tmpFile];\n\n const child = spawn(process.execPath, nodeArgs, {\n cwd: tmpDir,\n env: safeEnv,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n let stdout = \"\";\n let stderr = \"\";\n let timedOut = false;\n\n const timer = setTimeout(() => {\n timedOut = true;\n child.kill(\"SIGTERM\");\n setTimeout(() => {\n try {\n child.kill(\"SIGKILL\");\n } catch {}\n }, 2_000);\n }, timeoutMs);\n\n child.stdout?.on(\"data\", (chunk: Buffer) => {\n stdout += chunk.toString();\n });\n child.stderr?.on(\"data\", (chunk: Buffer) => {\n stderr += chunk.toString();\n });\n\n const exitCode = await new Promise<number | null>((resolve, reject) => {\n child.once(\"error\", reject);\n child.once(\"exit\", resolve);\n });\n clearTimeout(timer);\n\n const combined =\n [\n stdout ? `stdout:\\n${stdout}` : \"\",\n stderr ? `stderr:\\n${stderr}` : \"\",\n ]\n .filter(Boolean)\n .join(\"\\n\\n\") || \"(no output)\";\n\n const lines: string[] = [];\n if (timedOut) lines.push(`timedOut: true (${timeoutMs}ms)`);\n if (exitCode !== 0 && exitCode !== null)\n lines.push(`exitCode: ${exitCode}`);\n lines.push(combined);\n\n const full = lines.join(\"\\n\\n\");\n if (full.length > maxOutputChars) {\n const truncated = full.slice(0, maxOutputChars);\n return `${truncated}\\n\\n...[truncated ${(full.length - maxOutputChars).toLocaleString()} chars]`;\n }\n return full;\n } finally {\n cleanupBridge();\n server.close();\n // Clean up temp files (best-effort).\n try {\n if (tmpFile) fs.rmSync(tmpFile, { force: true });\n if (tmpDir) fs.rmSync(tmpDir, { recursive: true, force: true });\n } catch {}\n }\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Bridge server\n// ---------------------------------------------------------------------------\n\ninterface BridgeResult {\n server: http.Server;\n bridgePort: number;\n cleanup: () => void;\n}\n\nasync function startBridgeServer(\n token: string,\n actions: Record<string, ActionEntry>,\n context: ActionRunContext | undefined,\n defaultTools: Set<string>,\n extraTools: Set<string>,\n): Promise<BridgeResult> {\n const server = http.createServer((req, res) => {\n if (req.method !== \"POST\" || req.url !== \"/tool\") {\n res.writeHead(404);\n res.end(\"Not found\");\n return;\n }\n\n // Validate bearer token — must match exactly.\n const authHeader = req.headers.authorization ?? \"\";\n if (authHeader !== `Bearer ${token}`) {\n res.writeHead(401);\n res.end(\"Unauthorized\");\n return;\n }\n\n let body = \"\";\n let receivedBytes = 0;\n let rejected = false;\n req.on(\"data\", (chunk: Buffer) => {\n receivedBytes += chunk.length;\n if (receivedBytes > BRIDGE_MAX_BODY_BYTES) {\n rejected = true;\n res.writeHead(413);\n res.end(\"Payload too large\");\n req.destroy();\n return;\n }\n body += chunk.toString();\n });\n req.on(\"end\", () => {\n if (rejected) return;\n handleBridgeRequest(\n body,\n actions,\n context,\n defaultTools,\n extraTools,\n res,\n );\n });\n req.on(\"error\", () => {\n res.writeHead(500);\n res.end(\"Request error\");\n });\n });\n\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(0, \"127.0.0.1\", () => resolve());\n });\n\n const addr = server.address() as { port: number };\n const bridgePort = addr.port;\n\n const cleanup = () => {\n try {\n server.close();\n } catch {}\n };\n\n return { server, bridgePort, cleanup };\n}\n\nfunction handleBridgeRequest(\n rawBody: string,\n actions: Record<string, ActionEntry>,\n context: ActionRunContext | undefined,\n defaultTools: Set<string>,\n extraTools: Set<string>,\n res: http.ServerResponse,\n): void {\n let parsed: { tool?: string; args?: Record<string, string> };\n try {\n parsed = JSON.parse(rawBody);\n } catch {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Invalid JSON body\" }));\n return;\n }\n\n const toolName = typeof parsed.tool === \"string\" ? parsed.tool.trim() : \"\";\n if (!toolName) {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Missing tool name\" }));\n return;\n }\n\n // Enforce allowlist.\n const entry = actions[toolName];\n const isReadOnlyAction =\n entry?.readOnly === true &&\n entry.agentTool !== false &&\n entry.toolCallable !== false;\n if (\n !defaultTools.has(toolName) &&\n !extraTools.has(toolName) &&\n !isReadOnlyAction\n ) {\n res.writeHead(403, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: `Tool \"${toolName}\" is not an agent-exposed read-only action or sandbox bridge allowlisted tool.`,\n }),\n );\n return;\n }\n\n if (!entry) {\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: `Tool \"${toolName}\" is not registered.` }));\n return;\n }\n\n const toolArgs = parsed.args ?? {};\n // Run the tool with the parent request context so auth/org/owner resolution\n // works exactly as it does in the normal agent loop.\n entry\n .run(toolArgs, context)\n .then((result: unknown) => {\n const body =\n typeof result === \"string\" ? result : JSON.stringify(result, null, 2);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ result: body }));\n })\n .catch((err: unknown) => {\n const message = err instanceof Error ? err.message : String(err);\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: message }));\n });\n}\n\n// ---------------------------------------------------------------------------\n// Sandbox module template\n// ---------------------------------------------------------------------------\n\n/**\n * Wrap the user's code in an ESM module that:\n * 1. Defines `providerFetch` and `webFetch` helpers via the bridge.\n * 2. Runs the user's code as top-level await in an async IIFE.\n */\nfunction buildSandboxModule(\n userCode: string,\n bridgePort: number,\n bridgeToken: string,\n): string {\n return `\nimport { createRequire } from \"node:module\";\nconst require = createRequire(import.meta.url);\n\nconst _bridgeBase = \"http://127.0.0.1:${bridgePort}/tool\";\nconst _bridgeToken = \"${bridgeToken}\";\n\nasync function _bridgeCall(tool, args) {\n const http = await import(\"node:http\");\n return new Promise((resolve, reject) => {\n const body = JSON.stringify({ tool, args });\n const options = {\n hostname: \"127.0.0.1\",\n port: ${bridgePort},\n path: \"/tool\",\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Content-Length\": Buffer.byteLength(body),\n \"Authorization\": \"Bearer \" + _bridgeToken,\n },\n };\n const req = http.request(options, (res) => {\n let data = \"\";\n res.on(\"data\", (chunk) => { data += chunk; });\n res.on(\"end\", () => {\n try {\n const parsed = JSON.parse(data);\n if (parsed.error) {\n reject(new Error(parsed.error));\n } else {\n resolve(parsed.result);\n }\n } catch (e) {\n reject(new Error(\"Bridge response parse error: \" + e.message));\n }\n });\n });\n req.on(\"error\", reject);\n req.end(body);\n });\n}\n\nfunction _parseBridgeResult(rawResult) {\n if (typeof rawResult !== \"string\") return rawResult;\n try { return JSON.parse(rawResult); } catch { return rawResult; }\n}\n\n/**\n * Call any registered agent-exposed read-only app action/tool via the sandbox bridge.\n * Mutating and explicitly hidden actions are blocked by the parent bridge.\n */\nasync function appAction(name, args = {}) {\n return _parseBridgeResult(await _bridgeCall(name, args));\n}\n\n/**\n * Call a provider API via the authenticated provider-api-request action.\n * Returns the parsed JSON response body (or throws on error).\n */\nasync function providerFetch(provider, apiPath, init = {}) {\n const method = (init.method || \"GET\").toUpperCase();\n const rawResult = await _bridgeCall(\"provider-api-request\", {\n provider,\n path: apiPath,\n method,\n ...(init.query ? { query: init.query } : {}),\n ...(init.body ? { body: init.body } : {}),\n ...(init.headers ? { headers: init.headers } : {}),\n ...(init.auth ? { auth: init.auth } : {}),\n ...(init.connectionId ? { connectionId: init.connectionId } : {}),\n ...(init.accountId ? { accountId: init.accountId } : {}),\n ...(init.timeoutMs ? { timeoutMs: init.timeoutMs } : {}),\n ...(init.maxBytes ? { maxBytes: init.maxBytes } : {}),\n ...(init.stageAs ? { stageAs: init.stageAs } : {}),\n ...(init.itemsPath ? { itemsPath: init.itemsPath } : {}),\n ...(init.pagination ? { pagination: init.pagination } : {}),\n ...(init.saveToFile ? { saveToFile: init.saveToFile } : {}),\n ...(init.fetchAllPages ? { fetchAllPages: init.fetchAllPages } : {}),\n });\n // rawResult is the action's string output; parse it if it looks like JSON\n let parsed = _parseBridgeResult(rawResult);\n // Unwrap the provider-api-request envelope ({ provider, request, response, guidance })\n // so callers get the actual response body. fetchAllPages / saveToFile results\n // (which have no \\`response\\` field) are returned as-is.\n if (parsed && typeof parsed === \"object\" && parsed.response && typeof parsed.response === \"object\") {\n const r = parsed.response;\n if (typeof r.status === \"number\" && r.status >= 400) {\n const detail = typeof r.text === \"string\" ? r.text : JSON.stringify(r.json ?? \"\");\n throw new Error(\\`Provider request failed (\\${r.status}): \\${String(detail).slice(0, 500)}\\`);\n }\n return r.json !== undefined ? r.json : r.text;\n }\n return parsed;\n}\n\n/**\n * Make an outbound HTTP request via the web-request action.\n * Returns an object \\`{ status, body }\\` where \\`body\\` is the response text.\n */\nasync function webFetch(url, init = {}) {\n const method = (init.method || \"GET\").toUpperCase();\n const rawResult = await _bridgeCall(\"web-request\", {\n url,\n method,\n ...(init.headers ? { headers: typeof init.headers === \"string\" ? init.headers : JSON.stringify(init.headers) } : {}),\n ...(init.body ? { body: typeof init.body === \"string\" ? init.body : JSON.stringify(init.body) } : {}),\n });\n // rawResult is \"HTTP <status> <statusText>\\\\n\\\\n<body>\"\n const statusMatch = typeof rawResult === \"string\" ? rawResult.match(/^HTTP (\\\\d+) [^\\\\n]*\\\\n\\\\n/) : null;\n if (statusMatch) {\n return {\n status: Number(statusMatch[1]),\n body: rawResult.slice(statusMatch[0].length),\n };\n }\n return { status: 0, body: rawResult };\n}\n\n/**\n * Read a workspace file by path. Returns the file content as a string, or null if not found.\n * Supports optional offset and maxChars for paging large files.\n */\nasync function workspaceRead(path, opts = {}) {\n const parsed = await workspaceReadMeta(path, opts);\n if (parsed && parsed.ok === false) return null;\n return parsed && typeof parsed.content === \"string\" ? parsed.content : null;\n}\n\n/**\n * Read a workspace file by path and return the full metadata envelope.\n * Use this when offset/maxChars paging or truncation status matters.\n */\nasync function workspaceReadMeta(path, opts = {}) {\n const rawResult = await _bridgeCall(\"workspace-files\", {\n action: \"read\",\n path,\n ...(opts.offset !== undefined ? { offset: opts.offset } : {}),\n ...(opts.maxChars !== undefined ? { maxChars: opts.maxChars } : {}),\n });\n return _parseBridgeResult(rawResult);\n}\n\n/**\n * Write (create or overwrite) a workspace file.\n * \\`content\\` must be a string. Returns metadata { path, sizeBytes, updatedAt }.\n */\nasync function workspaceWrite(path, content, contentType = \"text/plain\") {\n const rawResult = await _bridgeCall(\"workspace-files\", {\n action: \"write\",\n path,\n content: typeof content === \"string\" ? content : JSON.stringify(content),\n contentType,\n });\n try { return typeof rawResult === \"string\" ? JSON.parse(rawResult) : rawResult; } catch { return rawResult; }\n}\n\n/**\n * Append text to a workspace file (creates if absent).\n */\nasync function workspaceAppend(path, content) {\n const rawResult = await _bridgeCall(\"workspace-files\", {\n action: \"append\",\n path,\n content: typeof content === \"string\" ? content : JSON.stringify(content),\n });\n try { return typeof rawResult === \"string\" ? JSON.parse(rawResult) : rawResult; } catch { return rawResult; }\n}\n\n/**\n * List workspace files, optionally filtered by path prefix.\n * Returns an array of { path, sizeBytes, contentType, updatedAt }.\n */\nasync function workspaceList(prefix) {\n const rawResult = await _bridgeCall(\"workspace-files\", {\n action: \"list\",\n ...(prefix ? { path: prefix } : {}),\n });\n const parsed = typeof rawResult === \"string\" ? JSON.parse(rawResult) : rawResult;\n if (parsed && Array.isArray(parsed.files)) return parsed.files;\n if (Array.isArray(parsed)) return parsed;\n throw new Error(\"workspaceList: unexpected result shape: \" + JSON.stringify(parsed).slice(0, 200));\n}\n\n// Run user code\n(async () => {\n${userCode}\n})().catch((err) => {\n console.error(\"Unhandled error:\", err?.message ?? String(err));\n process.exit(1);\n});\n`;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"run-code.js","sourceRoot":"","sources":["../../src/coding-tools/run-code.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Sandboxed JavaScript execution tool for the agent.\n *\n * Executes user-supplied JavaScript in an isolated child process with:\n * - A scrubbed environment (no app secrets or env vars; only PATH/HOME/TMPDIR).\n * - A fresh temporary working directory.\n * - An ephemeral bridge HTTP server on 127.0.0.1 so the child can call\n * allowlisted registered tools (provider-api-request, web-request, etc.)\n * with the same request context as the parent — without leaking secrets.\n *\n * Security notes:\n * - The bridge token is a 32-byte random hex string generated per invocation.\n * - The bridge binds to 127.0.0.1 only; no external exposure.\n * - The allowlist of callable bridge tools is enforced server-side.\n * - Secret values are NEVER included in the env passed to the child.\n * - When the Node permission model is available (`--permission`, or\n * `--experimental-permission` on Node 20), the child is denied filesystem\n * access outside its own temp dir, child processes, workers, and native\n * addons. Outbound network from the child is NOT blocked by the permission\n * model; the env scrub means such requests carry no credentials, and all\n * authenticated calls must go through the bridge (which applies the\n * registered tools' host allowlists and SSRF guards).\n */\n\nimport crypto from \"node:crypto\";\nimport fs from \"node:fs\";\nimport http from \"node:http\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport { spawn, spawnSync } from \"node:child_process\";\n\nimport type { ActionEntry } from \"../agent/production-agent.js\";\nimport type { ActionRunContext } from \"../action.js\";\n\nconst DEFAULT_TIMEOUT_MS = 120_000;\nconst MAX_TIMEOUT_MS = 600_000;\nconst DEFAULT_MAX_OUTPUT_CHARS = 50_000;\nconst MAX_OUTPUT_CHARS = 200_000;\n/** Hard cap on bridge request bodies so sandboxed code can't exhaust parent memory. */\nconst BRIDGE_MAX_BODY_BYTES = 10 * 1024 * 1024;\n\nfunction sandboxReadAllowPaths(tmpDir: string): string[] {\n const paths = new Set<string>([tmpDir]);\n try {\n paths.add(fs.realpathSync(tmpDir));\n } catch {}\n return [...paths];\n}\n\nfunction sandboxWriteAllowPaths(tmpDir: string): string[] {\n const paths = new Set<string>([tmpDir]);\n try {\n paths.add(fs.realpathSync(tmpDir));\n } catch {}\n return [...paths];\n}\n\n/**\n * Resolve the Node permission-model flag supported by the current runtime,\n * probing once and caching. Returns null when the permission model is\n * unavailable (the sandbox then falls back to env-scrub isolation only).\n */\nlet cachedPermissionFlag: string | null | undefined;\nfunction resolvePermissionFlag(): string | null {\n if (cachedPermissionFlag !== undefined) return cachedPermissionFlag;\n for (const flag of [\"--permission\", \"--experimental-permission\"]) {\n try {\n const probe = spawnSync(\n process.execPath,\n [flag, \"-e\", \"process.exit(0)\"],\n { timeout: 10_000, stdio: \"ignore\" },\n );\n if (probe.status === 0) {\n cachedPermissionFlag = flag;\n return flag;\n }\n } catch {\n // Probe failure means the flag is unsupported; try the next one.\n }\n }\n cachedPermissionFlag = null;\n return null;\n}\n\n/** Tools callable via the sandbox bridge by default. */\nconst DEFAULT_BRIDGE_TOOLS = new Set([\n \"provider-api-request\",\n \"provider-api-docs\",\n \"provider-api-catalog\",\n \"web-request\",\n \"workspace-files\",\n]);\n\nexport interface RunCodeOptions {\n /**\n * Extra tool names (beyond the default set) that the sandbox bridge will\n * forward to the registered action registry.\n */\n bridgeTools?: string[];\n}\n\n/**\n * Create a `run-code` ActionEntry.\n *\n * @param getActions Supplier that returns the current action registry (called\n * at invocation time so updates are reflected).\n * @param opts Optional configuration.\n */\nexport function createRunCodeEntry(\n getActions: () => Record<string, ActionEntry>,\n opts: RunCodeOptions = {},\n): ActionEntry {\n const extraBridgeTools = new Set(opts.bridgeTools ?? []);\n\n return {\n readOnly: true,\n // Allow a generous per-call timeout so large data-processing jobs don't hit\n // the agent-loop's default 60 s cap.\n timeoutMs: MAX_TIMEOUT_MS,\n maxResultChars: MAX_OUTPUT_CHARS,\n tool: {\n description: [\n \"Execute JavaScript (Node.js, ESM, top-level await supported) in an isolated sandbox.\",\n \"Use this to fetch, join, aggregate, and reduce large datasets, returning only printed output to the conversation.\",\n \"The sandbox runs with a scrubbed environment (no secrets) and, where the Node permission model is available, no filesystem access outside its own temp dir, no child processes, and no workers. Authenticated calls must go through the provided globals; direct network requests carry no credentials. Note: isolation is process-level (env scrub + Node permission model), not an OS-level container — outbound network from sandbox code is not blocked.\",\n \"Available globals:\",\n \" - `appAction(name, args?)` — call any registered agent-exposed read-only app action/tool and get its parsed result.\",\n \" Use this to loop over app data readers and compose multi-source analyses without forcing every intermediate result into chat.\",\n \" - `providerFetch(provider, path, init?)` — authenticated call to a registered provider via the provider-api-request action.\",\n \" Returns the parsed JSON result (or throws on error).\",\n \" Supports stageAs/saveToFile/fetchAllPages; use cursorBodyPath for POST-body pagination.\",\n \" Example: `const data = await providerFetch('<provider-id>', '/records', { query: { limit: 100 } });`\",\n \" - `providerRequest(provider, path, init?)` — same authenticated call, but returns the full provider-api envelope with request, response status/headers, truncation, and body metadata.\",\n \" - `providerFetchAll(provider, path, init?)` — generic pagination helper for cursor, page, and offset APIs. Pass `pagination: { itemsPath, cursorPath or nextCursorPath, cursorParam or cursorBodyPath, pageParam, offsetParam, pageSize, maxPages }`. Returns `{ items, pages, pageCount, itemCount, hasMore, lastCursor, stoppedReason }`.\",\n \" - `webFetch(url, init?)` — outbound HTTP request via the web-request action.\",\n \" Returns `{ status, body }` where body is the response text.\",\n \" Example: `const { body } = await webFetch('https://api.example.com/data');`\",\n \" - `workspaceRead(path, opts?)` — read a workspace file by path. Returns content string or null. opts: { offset?, maxChars? }.\",\n \" - `workspaceReadMeta(path, opts?)` — read a workspace file with metadata such as sizeBytes, truncated, and nextOffset.\",\n \" - `workspaceWrite(path, content, contentType?)` — create or overwrite a workspace file.\",\n \" - `workspaceAppend(path, content)` — append text to a workspace file.\",\n \" - `workspaceList(prefix?)` — list workspace files, returns [{ path, sizeBytes, contentType, updatedAt }].\",\n \"Print results with `console.log()`; only stdout+stderr are returned.\",\n \"Timeout defaults to 120 s (max 600 s). Output is truncated to 50 000 chars by default (max 200 000).\",\n ].join(\" \"),\n parameters: {\n type: \"object\",\n properties: {\n code: {\n type: \"string\",\n description:\n \"JavaScript source to execute. ESM syntax, top-level await allowed.\",\n },\n timeoutMs: {\n type: \"number\",\n description: `Execution timeout in milliseconds. Default: ${DEFAULT_TIMEOUT_MS}. Max: ${MAX_TIMEOUT_MS}.`,\n },\n maxOutputChars: {\n type: \"number\",\n description: `Maximum combined stdout+stderr characters to return. Default: ${DEFAULT_MAX_OUTPUT_CHARS}. Max: ${MAX_OUTPUT_CHARS}.`,\n },\n },\n required: [\"code\"],\n },\n },\n run: async (args: Record<string, string>, context?: ActionRunContext) => {\n const code = typeof args.code === \"string\" ? args.code : \"\";\n if (!code.trim()) return \"Error: code is required.\";\n\n const requestedTimeout = Number(args.timeoutMs);\n const timeoutMs =\n Number.isFinite(requestedTimeout) && requestedTimeout > 0\n ? Math.min(requestedTimeout, MAX_TIMEOUT_MS)\n : DEFAULT_TIMEOUT_MS;\n\n const requestedMaxOutput = Number(args.maxOutputChars);\n const maxOutputChars =\n Number.isFinite(requestedMaxOutput) && requestedMaxOutput > 0\n ? Math.min(requestedMaxOutput, MAX_OUTPUT_CHARS)\n : DEFAULT_MAX_OUTPUT_CHARS;\n\n const actions = getActions();\n const bridgeToken = crypto.randomBytes(32).toString(\"hex\");\n\n // Start bridge server — resolves once the server is listening.\n const {\n server,\n bridgePort,\n getUsedTools,\n cleanup: cleanupBridge,\n } = await startBridgeServer(\n bridgeToken,\n actions,\n context,\n DEFAULT_BRIDGE_TOOLS,\n extraBridgeTools,\n );\n\n let tmpDir: string | undefined;\n let tmpFile: string | undefined;\n try {\n // Write code to a temp ESM file (top-level await needs a module).\n const tmpBaseDir = fs.realpathSync(os.tmpdir());\n tmpDir = fs.mkdtempSync(path.join(tmpBaseDir, \"agent-run-code-\"));\n tmpFile = path.join(tmpDir, \"sandbox.mjs\");\n fs.writeFileSync(\n tmpFile,\n buildSandboxModule(code, bridgePort, bridgeToken),\n \"utf8\",\n );\n\n // Build scrubbed env — only safe POSIX vars, no secrets.\n const safeEnv: Record<string, string> = {};\n for (const key of [\n \"PATH\",\n \"HOME\",\n \"TMPDIR\",\n \"TEMP\",\n \"TMP\",\n \"LANG\",\n \"LC_ALL\",\n ]) {\n if (process.env[key]) safeEnv[key] = process.env[key]!;\n }\n // Point TMPDIR inside the sandbox dir so in-sandbox temp writes stay\n // within the permission-model allow list.\n safeEnv.TMPDIR = tmpDir;\n safeEnv.TEMP = tmpDir;\n safeEnv.TMP = tmpDir;\n\n // Lock the child down with the Node permission model when available:\n // filesystem restricted to the sandbox temp dir, and child processes,\n // workers, and native addons denied entirely.\n const permissionFlag = resolvePermissionFlag();\n const nodeArgs = permissionFlag\n ? [\n permissionFlag,\n ...sandboxReadAllowPaths(tmpDir).map(\n (allowedPath) => `--allow-fs-read=${allowedPath}`,\n ),\n ...sandboxWriteAllowPaths(tmpDir).map(\n (allowedPath) => `--allow-fs-write=${allowedPath}`,\n ),\n tmpFile,\n ]\n : [tmpFile];\n\n const child = spawn(process.execPath, nodeArgs, {\n cwd: tmpDir,\n env: safeEnv,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n let stdout = \"\";\n let stderr = \"\";\n let timedOut = false;\n\n const timer = setTimeout(() => {\n timedOut = true;\n child.kill(\"SIGTERM\");\n setTimeout(() => {\n try {\n child.kill(\"SIGKILL\");\n } catch {}\n }, 2_000);\n }, timeoutMs);\n\n child.stdout?.on(\"data\", (chunk: Buffer) => {\n stdout += chunk.toString();\n });\n child.stderr?.on(\"data\", (chunk: Buffer) => {\n stderr += chunk.toString();\n });\n\n const exitCode = await new Promise<number | null>((resolve, reject) => {\n child.once(\"error\", reject);\n child.once(\"exit\", resolve);\n });\n clearTimeout(timer);\n\n const combined =\n [\n stdout ? `stdout:\\n${stdout}` : \"\",\n stderr ? `stderr:\\n${stderr}` : \"\",\n ]\n .filter(Boolean)\n .join(\"\\n\\n\") || \"(no output)\";\n\n const lines: string[] = [];\n if (timedOut) lines.push(`timedOut: true (${timeoutMs}ms)`);\n if (exitCode !== 0 && exitCode !== null)\n lines.push(`exitCode: ${exitCode}`);\n const usedTools = getUsedTools();\n if (usedTools.length)\n lines.push(`bridgeToolsUsed: ${usedTools.join(\", \")}`);\n lines.push(combined);\n\n const full = lines.join(\"\\n\\n\");\n if (full.length > maxOutputChars) {\n const truncated = full.slice(0, maxOutputChars);\n return `${truncated}\\n\\n...[truncated ${(full.length - maxOutputChars).toLocaleString()} chars]`;\n }\n return full;\n } finally {\n cleanupBridge();\n server.close();\n // Clean up temp files (best-effort).\n try {\n if (tmpFile) fs.rmSync(tmpFile, { force: true });\n if (tmpDir) fs.rmSync(tmpDir, { recursive: true, force: true });\n } catch {}\n }\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Bridge server\n// ---------------------------------------------------------------------------\n\ninterface BridgeResult {\n server: http.Server;\n bridgePort: number;\n getUsedTools: () => string[];\n cleanup: () => void;\n}\n\nasync function startBridgeServer(\n token: string,\n actions: Record<string, ActionEntry>,\n context: ActionRunContext | undefined,\n defaultTools: Set<string>,\n extraTools: Set<string>,\n): Promise<BridgeResult> {\n const usedTools = new Set<string>();\n const server = http.createServer((req, res) => {\n if (req.method !== \"POST\" || req.url !== \"/tool\") {\n res.writeHead(404);\n res.end(\"Not found\");\n return;\n }\n\n // Validate bearer token — must match exactly.\n const authHeader = req.headers.authorization ?? \"\";\n if (authHeader !== `Bearer ${token}`) {\n res.writeHead(401);\n res.end(\"Unauthorized\");\n return;\n }\n\n let body = \"\";\n let receivedBytes = 0;\n let rejected = false;\n req.on(\"data\", (chunk: Buffer) => {\n receivedBytes += chunk.length;\n if (receivedBytes > BRIDGE_MAX_BODY_BYTES) {\n rejected = true;\n res.writeHead(413);\n res.end(\"Payload too large\");\n req.destroy();\n return;\n }\n body += chunk.toString();\n });\n req.on(\"end\", () => {\n if (rejected) return;\n handleBridgeRequest(\n body,\n actions,\n context,\n defaultTools,\n extraTools,\n usedTools,\n res,\n );\n });\n req.on(\"error\", () => {\n res.writeHead(500);\n res.end(\"Request error\");\n });\n });\n\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(0, \"127.0.0.1\", () => resolve());\n });\n\n const addr = server.address() as { port: number };\n const bridgePort = addr.port;\n\n const cleanup = () => {\n try {\n server.close();\n } catch {}\n };\n\n return {\n server,\n bridgePort,\n getUsedTools: () => Array.from(usedTools).sort(),\n cleanup,\n };\n}\n\nfunction handleBridgeRequest(\n rawBody: string,\n actions: Record<string, ActionEntry>,\n context: ActionRunContext | undefined,\n defaultTools: Set<string>,\n extraTools: Set<string>,\n usedTools: Set<string>,\n res: http.ServerResponse,\n): void {\n let parsed: { tool?: string; args?: Record<string, string> };\n try {\n parsed = JSON.parse(rawBody);\n } catch {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Invalid JSON body\" }));\n return;\n }\n\n const toolName = typeof parsed.tool === \"string\" ? parsed.tool.trim() : \"\";\n if (!toolName) {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Missing tool name\" }));\n return;\n }\n\n // Enforce allowlist.\n const entry = actions[toolName];\n const isReadOnlyAction =\n entry?.readOnly === true &&\n entry.agentTool !== false &&\n entry.toolCallable !== false;\n if (\n !defaultTools.has(toolName) &&\n !extraTools.has(toolName) &&\n !isReadOnlyAction\n ) {\n res.writeHead(403, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: `Tool \"${toolName}\" is not an agent-exposed read-only action or sandbox bridge allowlisted tool.`,\n }),\n );\n return;\n }\n\n if (!entry) {\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: `Tool \"${toolName}\" is not registered.` }));\n return;\n }\n\n const toolArgs = parsed.args ?? {};\n usedTools.add(toolName);\n // Run the tool with the parent request context so auth/org/owner resolution\n // works exactly as it does in the normal agent loop.\n entry\n .run(toolArgs, context)\n .then((result: unknown) => {\n const body =\n typeof result === \"string\" ? result : JSON.stringify(result, null, 2);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ result: body }));\n })\n .catch((err: unknown) => {\n const message = err instanceof Error ? err.message : String(err);\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: message }));\n });\n}\n\n// ---------------------------------------------------------------------------\n// Sandbox module template\n// ---------------------------------------------------------------------------\n\n/**\n * Wrap the user's code in an ESM module that:\n * 1. Defines `providerFetch`, `providerRequest`, `providerFetchAll`, and\n * `webFetch` helpers via the bridge.\n * 2. Runs the user's code as top-level await in an async IIFE.\n */\nfunction buildSandboxModule(\n userCode: string,\n bridgePort: number,\n bridgeToken: string,\n): string {\n return `\nimport { createRequire } from \"node:module\";\nconst require = createRequire(import.meta.url);\n\nconst _bridgeBase = \"http://127.0.0.1:${bridgePort}/tool\";\nconst _bridgeToken = \"${bridgeToken}\";\n\nasync function _bridgeCall(tool, args) {\n const http = await import(\"node:http\");\n return new Promise((resolve, reject) => {\n const body = JSON.stringify({ tool, args });\n const options = {\n hostname: \"127.0.0.1\",\n port: ${bridgePort},\n path: \"/tool\",\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Content-Length\": Buffer.byteLength(body),\n \"Authorization\": \"Bearer \" + _bridgeToken,\n },\n };\n const req = http.request(options, (res) => {\n let data = \"\";\n res.on(\"data\", (chunk) => { data += chunk; });\n res.on(\"end\", () => {\n try {\n const parsed = JSON.parse(data);\n if (parsed.error) {\n reject(new Error(parsed.error));\n } else {\n resolve(parsed.result);\n }\n } catch (e) {\n reject(new Error(\"Bridge response parse error: \" + e.message));\n }\n });\n });\n req.on(\"error\", reject);\n req.end(body);\n });\n}\n\nfunction _parseBridgeResult(rawResult) {\n if (typeof rawResult !== \"string\") return rawResult;\n try { return JSON.parse(rawResult); } catch { return rawResult; }\n}\n\n/**\n * Call any registered agent-exposed read-only app action/tool via the sandbox bridge.\n * Mutating and explicitly hidden actions are blocked by the parent bridge.\n */\nasync function appAction(name, args = {}) {\n return _parseBridgeResult(await _bridgeCall(name, args));\n}\n\nasync function providerRequest(provider, apiPath, init = {}) {\n const method = (init.method || \"GET\").toUpperCase();\n const rawResult = await _bridgeCall(\"provider-api-request\", {\n provider,\n path: apiPath,\n method,\n ...(init.query ? { query: init.query } : {}),\n ...(init.body ? { body: init.body } : {}),\n ...(init.headers ? { headers: init.headers } : {}),\n ...(init.auth ? { auth: init.auth } : {}),\n ...(init.connectionId ? { connectionId: init.connectionId } : {}),\n ...(init.accountId ? { accountId: init.accountId } : {}),\n ...(init.timeoutMs ? { timeoutMs: init.timeoutMs } : {}),\n ...(init.maxBytes ? { maxBytes: init.maxBytes } : {}),\n ...(init.stageAs ? { stageAs: init.stageAs } : {}),\n ...(init.itemsPath ? { itemsPath: init.itemsPath } : {}),\n ...(init.pagination ? { pagination: init.pagination } : {}),\n ...(init.saveToFile ? { saveToFile: init.saveToFile } : {}),\n ...(init.fetchAllPages ? { fetchAllPages: init.fetchAllPages } : {}),\n });\n return _parseBridgeResult(rawResult);\n}\n\n/**\n * Call a provider API via the authenticated provider-api-request action.\n * Returns the parsed JSON response body (or throws on error).\n */\nasync function providerFetch(provider, apiPath, init = {}) {\n const parsed = await providerRequest(provider, apiPath, init);\n // Unwrap the provider-api-request envelope ({ provider, request, response, guidance })\n // so callers get the actual response body. fetchAllPages / saveToFile results\n // (which have no \\`response\\` field) are returned as-is.\n if (parsed && typeof parsed === \"object\" && parsed.response && typeof parsed.response === \"object\") {\n const r = parsed.response;\n if (typeof r.status === \"number\" && r.status >= 400) {\n const detail = typeof r.text === \"string\" ? r.text : JSON.stringify(r.json ?? \"\");\n throw new Error(\\`Provider request failed (\\${r.status}): \\${String(detail).slice(0, 500)}\\`);\n }\n return r.json !== undefined ? r.json : r.text;\n }\n return parsed;\n}\n\nfunction _cloneJson(value) {\n if (value === undefined || value === null) return value;\n return JSON.parse(JSON.stringify(value));\n}\n\nfunction _pathParts(path) {\n if (!path || typeof path !== \"string\") return [];\n return path\n .replace(/\\\\[(\\\\d+)\\\\]/g, \".$1\")\n .split(\".\")\n .map((part) => part.trim())\n .filter(Boolean);\n}\n\nfunction _getByPath(value, path) {\n let current = value;\n for (const part of _pathParts(path)) {\n if (current === undefined || current === null) return undefined;\n current = current[part];\n }\n return current;\n}\n\nfunction _setByPath(value, path, nextValue) {\n const parts = _pathParts(path);\n if (!parts.length) return value;\n const root = value && typeof value === \"object\" ? _cloneJson(value) : {};\n let current = root;\n for (let i = 0; i < parts.length - 1; i++) {\n const part = parts[i];\n if (!current[part] || typeof current[part] !== \"object\") current[part] = {};\n current = current[part];\n }\n current[parts[parts.length - 1]] = nextValue;\n return root;\n}\n\nfunction _extractItems(page, itemsPath) {\n if (itemsPath) {\n const value = _getByPath(page, itemsPath);\n return Array.isArray(value) ? value : [];\n }\n if (Array.isArray(page)) return page;\n if (!page || typeof page !== \"object\") return [];\n for (const key of [\"data\", \"results\", \"items\", \"records\", \"rows\", \"calls\", \"messages\", \"tickets\", \"issues\", \"deals\"]) {\n if (Array.isArray(page[key])) return page[key];\n }\n return [];\n}\n\nfunction _withoutProviderFetchAllOptions(init) {\n const {\n pagination: _pagination,\n fetchAllPages: _fetchAllPages,\n stageAs: _stageAs,\n itemsPath: _itemsPath,\n saveToFile: _saveToFile,\n ...rest\n } = init || {};\n return rest;\n}\n\n/**\n * Fetch every page from a provider API using generic cursor, page-number, or\n * offset pagination. Prefer this inside run-code when the answer depends on a\n * broad provider corpus rather than a single bounded request.\n */\nasync function providerFetchAll(provider, apiPath, init = {}) {\n const pagination = init.pagination || init.fetchAllPages || {};\n const itemsPath = pagination.itemsPath || init.itemsPath;\n const cursorPath = pagination.nextCursorPath || pagination.cursorPath;\n const maxPagesRaw = Number(pagination.maxPages || init.maxPages || 50);\n const maxPages = Math.max(1, Math.min(Number.isFinite(maxPagesRaw) ? maxPagesRaw : 50, 200));\n const baseInit = _withoutProviderFetchAllOptions(init);\n let query = _cloneJson(init.query || {});\n let body = _cloneJson(init.body);\n let pageNumber = Number(pagination.startPage || 1);\n let offset = Number(pagination.startOffset || 0);\n const pages = [];\n const items = [];\n let lastCursor = null;\n let stoppedReason = \"completed\";\n\n for (let pageIndex = 0; pageIndex < maxPages; pageIndex++) {\n if (pagination.pageParam) {\n query = { ...(query || {}), [pagination.pageParam]: pageNumber };\n }\n if (pagination.offsetParam) {\n query = { ...(query || {}), [pagination.offsetParam]: offset };\n }\n\n const page = await providerFetch(provider, apiPath, {\n ...baseInit,\n query,\n ...(body !== undefined ? { body } : {}),\n });\n pages.push(page);\n const pageItems = _extractItems(page, itemsPath);\n items.push(...pageItems);\n\n const nextCursor = cursorPath ? _getByPath(page, cursorPath) : undefined;\n if (nextCursor !== undefined && nextCursor !== null && String(nextCursor) !== \"\") {\n if (lastCursor !== null && String(nextCursor) === String(lastCursor)) {\n stoppedReason = \"repeated-cursor\";\n break;\n }\n lastCursor = nextCursor;\n if (pagination.cursorBodyPath) {\n body = _setByPath(body || {}, pagination.cursorBodyPath, nextCursor);\n } else if (pagination.cursorParam) {\n query = { ...(query || {}), [pagination.cursorParam]: nextCursor };\n } else {\n stoppedReason = \"cursor-found-without-destination\";\n break;\n }\n continue;\n }\n\n lastCursor = null;\n if (pagination.pageParam) {\n if (pageItems.length === 0) {\n stoppedReason = \"empty-page\";\n break;\n }\n pageNumber += 1;\n continue;\n }\n if (pagination.offsetParam) {\n if (pageItems.length === 0) {\n stoppedReason = \"empty-page\";\n break;\n }\n const step = Number(pagination.pageSize || pageItems.length);\n if (!Number.isFinite(step) || step <= 0) {\n stoppedReason = \"invalid-page-size\";\n break;\n }\n offset += step;\n if (pagination.pageSize && pageItems.length < Number(pagination.pageSize)) {\n stoppedReason = \"short-page\";\n break;\n }\n continue;\n }\n\n break;\n }\n\n const hitPageOrOffsetLimit =\n Boolean(pagination.pageParam || pagination.offsetParam) &&\n stoppedReason === \"completed\" &&\n pages.length >= maxPages;\n const hasMore =\n (lastCursor !== null && pages.length >= maxPages) || hitPageOrOffsetLimit;\n if (hasMore) stoppedReason = \"max-pages\";\n return {\n items,\n pages,\n pageCount: pages.length,\n itemCount: items.length,\n hasMore,\n lastCursor,\n stoppedReason,\n };\n}\n\n/**\n * Make an outbound HTTP request via the web-request action.\n * Returns an object \\`{ status, body }\\` where \\`body\\` is the response text.\n */\nasync function webFetch(url, init = {}) {\n const method = (init.method || \"GET\").toUpperCase();\n const rawResult = await _bridgeCall(\"web-request\", {\n url,\n method,\n ...(init.headers ? { headers: typeof init.headers === \"string\" ? init.headers : JSON.stringify(init.headers) } : {}),\n ...(init.body ? { body: typeof init.body === \"string\" ? init.body : JSON.stringify(init.body) } : {}),\n });\n // rawResult is \"HTTP <status> <statusText>\\\\n\\\\n<body>\"\n const statusMatch = typeof rawResult === \"string\" ? rawResult.match(/^HTTP (\\\\d+) [^\\\\n]*\\\\n\\\\n/) : null;\n if (statusMatch) {\n return {\n status: Number(statusMatch[1]),\n body: rawResult.slice(statusMatch[0].length),\n };\n }\n return { status: 0, body: rawResult };\n}\n\n/**\n * Read a workspace file by path. Returns the file content as a string, or null if not found.\n * Supports optional offset and maxChars for paging large files.\n */\nasync function workspaceRead(path, opts = {}) {\n const parsed = await workspaceReadMeta(path, opts);\n if (parsed && parsed.ok === false) return null;\n return parsed && typeof parsed.content === \"string\" ? parsed.content : null;\n}\n\n/**\n * Read a workspace file by path and return the full metadata envelope.\n * Use this when offset/maxChars paging or truncation status matters.\n */\nasync function workspaceReadMeta(path, opts = {}) {\n const rawResult = await _bridgeCall(\"workspace-files\", {\n action: \"read\",\n path,\n ...(opts.offset !== undefined ? { offset: opts.offset } : {}),\n ...(opts.maxChars !== undefined ? { maxChars: opts.maxChars } : {}),\n });\n return _parseBridgeResult(rawResult);\n}\n\n/**\n * Write (create or overwrite) a workspace file.\n * \\`content\\` must be a string. Returns metadata { path, sizeBytes, updatedAt }.\n */\nasync function workspaceWrite(path, content, contentType = \"text/plain\") {\n const rawResult = await _bridgeCall(\"workspace-files\", {\n action: \"write\",\n path,\n content: typeof content === \"string\" ? content : JSON.stringify(content),\n contentType,\n });\n try { return typeof rawResult === \"string\" ? JSON.parse(rawResult) : rawResult; } catch { return rawResult; }\n}\n\n/**\n * Append text to a workspace file (creates if absent).\n */\nasync function workspaceAppend(path, content) {\n const rawResult = await _bridgeCall(\"workspace-files\", {\n action: \"append\",\n path,\n content: typeof content === \"string\" ? content : JSON.stringify(content),\n });\n try { return typeof rawResult === \"string\" ? JSON.parse(rawResult) : rawResult; } catch { return rawResult; }\n}\n\n/**\n * List workspace files, optionally filtered by path prefix.\n * Returns an array of { path, sizeBytes, contentType, updatedAt }.\n */\nasync function workspaceList(prefix) {\n const rawResult = await _bridgeCall(\"workspace-files\", {\n action: \"list\",\n ...(prefix ? { path: prefix } : {}),\n });\n const parsed = typeof rawResult === \"string\" ? JSON.parse(rawResult) : rawResult;\n if (parsed && Array.isArray(parsed.files)) return parsed.files;\n if (Array.isArray(parsed)) return parsed;\n throw new Error(\"workspaceList: unexpected result shape: \" + JSON.stringify(parsed).slice(0, 200));\n}\n\n// Run user code\n(async () => {\n${userCode}\n})().catch((err) => {\n console.error(\"Unhandled error:\", err?.message ?? String(err));\n process.exit(1);\n});\n`;\n}\n"]}
|
|
@@ -52,7 +52,7 @@ export function createFetchToolEntry(opts = {}) {
|
|
|
52
52
|
return {
|
|
53
53
|
"web-request": {
|
|
54
54
|
tool: {
|
|
55
|
-
description: `Make an outbound HTTP request to any EXTERNAL URL — APIs, webhooks, and arbitrary web pages (HTML, RSS, JSON, etc.). Use this to fetch the contents of a URL the user pastes in chat. Sends realistic Chrome-on-macOS headers by default (User-Agent, Accept, Sec-Fetch-*) so most sites that block obvious bots will respond normally; pass an explicit header to override any default. Supports \${keys.NAME} placeholders in url, headers, and body — these are resolved server-side from the user's saved keys (the raw value never enters your context). Example: \${keys.SLACK_WEBHOOK} in the url field. IMPORTANT: Never use this to call internal /_agent-native/ endpoints or localhost action URLs — use the registered actions directly (e.g. \`
|
|
55
|
+
description: `Make an outbound HTTP request to any EXTERNAL URL — APIs, webhooks, and arbitrary web pages (HTML, RSS, JSON, etc.). Use this to fetch the contents of a URL the user pastes in chat. Sends realistic Chrome-on-macOS headers by default (User-Agent, Accept, Sec-Fetch-*) so most sites that block obvious bots will respond normally; pass an explicit header to override any default. Supports \${keys.NAME} placeholders in url, headers, and body — these are resolved server-side from the user's saved keys (the raw value never enters your context). Example: \${keys.SLACK_WEBHOOK} in the url field. IMPORTANT: Never use this to call internal /_agent-native/ endpoints or localhost action URLs — use the registered actions directly (e.g. \`search-records\`, \`provider-api-request\`, \`update-resource\`). Actions are already available as native tools; calling them via HTTP is slower and bypasses validation.`,
|
|
56
56
|
parameters: {
|
|
57
57
|
type: "object",
|
|
58
58
|
properties: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fetch-tool.js","sourceRoot":"","sources":["../../src/extensions/fetch-tool.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Fetch tool — outbound HTTP for automations and agent use.\n *\n * NOTE: this is an *agent* tool (LLM function call), not an *extension* (the\n * sandboxed Alpine.js mini-app primitive). It lives in this directory because\n * it shares SSRF-safe URL/proxy helpers with the extension iframe proxy.\n *\n * Supports ${keys.NAME} reference substitution in URL, headers, and body.\n * Values are resolved server-side AFTER the model emits the tool call —\n * the raw secret never enters the model's context.\n */\n\nimport type { ActionEntry } from \"../agent/production-agent.js\";\nimport {\n collectSecretValues,\n MAX_EXTENSION_PROXY_RESPONSE_SIZE,\n normalizeExtensionProxyMethod,\n readResponseTextWithLimit,\n redactSecrets,\n redactString,\n sanitizeOutboundHeaders,\n} from \"./proxy-security.js\";\nimport {\n createSsrfSafeDispatcher,\n isBlockedExtensionUrlWithDns,\n} from \"./url-safety.js\";\n\nconst DEFAULT_TIMEOUT_MS = 15_000;\n\n/**\n * Headers that mimic a current Chrome on macOS so anti-bot middleware (Cloudflare,\n * PerimeterX, Akamai) treats the request as a real user. We only fill in fields\n * the caller hasn't supplied — explicit headers (e.g. an `Authorization` header\n * for an API call) always win.\n *\n * `Accept-Encoding` deliberately omits `zstd` because Node's undici fetch only\n * decompresses `gzip`, `deflate`, and `br`. Advertising `zstd` would let some\n * servers send bytes we can't decode.\n */\nconst BROWSER_DEFAULT_HEADERS: Record<string, string> = {\n \"User-Agent\":\n \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36\",\n Accept:\n \"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8\",\n \"Accept-Language\": \"en-US,en;q=0.9\",\n \"Accept-Encoding\": \"gzip, deflate, br\",\n \"Sec-Ch-Ua\":\n '\"Google Chrome\";v=\"131\", \"Chromium\";v=\"131\", \"Not_A Brand\";v=\"24\"',\n \"Sec-Ch-Ua-Mobile\": \"?0\",\n \"Sec-Ch-Ua-Platform\": '\"macOS\"',\n \"Sec-Fetch-Dest\": \"document\",\n \"Sec-Fetch-Mode\": \"navigate\",\n \"Sec-Fetch-Site\": \"none\",\n \"Sec-Fetch-User\": \"?1\",\n \"Upgrade-Insecure-Requests\": \"1\",\n};\n\nfunction applyBrowserDefaults(\n headers: Record<string, string>,\n): Record<string, string> {\n const seen = new Set(Object.keys(headers).map((k) => k.toLowerCase()));\n const merged = { ...headers };\n for (const [name, value] of Object.entries(BROWSER_DEFAULT_HEADERS)) {\n if (!seen.has(name.toLowerCase())) merged[name] = value;\n }\n return merged;\n}\n\nexport interface FetchToolOptions {\n /** Resolve ${keys.NAME} references. Injected by the plugin at setup time. */\n resolveKeys?: (text: string) => Promise<{\n resolved: string;\n usedKeys: string[];\n secretValues?: string[];\n }>;\n /** Validate URL against per-key allowlists. */\n validateUrl?: (url: string, usedKeys: string[]) => Promise<boolean>;\n}\n\n/**\n * Create the fetch tool entry for the agent tool registry.\n */\nexport function createFetchToolEntry(\n opts: FetchToolOptions = {},\n): Record<string, ActionEntry> {\n return {\n \"web-request\": {\n tool: {\n description: `Make an outbound HTTP request to any EXTERNAL URL — APIs, webhooks, and arbitrary web pages (HTML, RSS, JSON, etc.). Use this to fetch the contents of a URL the user pastes in chat. Sends realistic Chrome-on-macOS headers by default (User-Agent, Accept, Sec-Fetch-*) so most sites that block obvious bots will respond normally; pass an explicit header to override any default. Supports \\${keys.NAME} placeholders in url, headers, and body — these are resolved server-side from the user's saved keys (the raw value never enters your context). Example: \\${keys.SLACK_WEBHOOK} in the url field. IMPORTANT: Never use this to call internal /_agent-native/ endpoints or localhost action URLs — use the registered actions directly (e.g. \\`log-meal\\`, \\`bigquery\\`, \\`hubspot-deals\\`). Actions are already available as native tools; calling them via HTTP is slower and bypasses validation.`,\n parameters: {\n type: \"object\" as const,\n properties: {\n url: {\n type: \"string\",\n description:\n 'Full URL. May contain ${keys.NAME} references, e.g. \"${keys.SLACK_WEBHOOK}\".',\n },\n method: {\n type: \"string\",\n description: \"HTTP method. Default: GET.\",\n enum: [\"GET\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\", \"HEAD\"],\n },\n headers: {\n type: \"string\",\n description:\n 'JSON object of headers. May contain ${keys.NAME} references. Example: \\'{\"Authorization\": \"Bearer ${keys.API_TOKEN}\"}\\'.',\n },\n body: {\n type: \"string\",\n description:\n \"Request body (for POST/PUT/PATCH). May contain ${keys.NAME} references.\",\n },\n timeout_ms: {\n type: \"number\",\n description: `Timeout in milliseconds. Default: ${DEFAULT_TIMEOUT_MS}. Max: 30000.`,\n },\n maxChars: {\n type: \"number\",\n description:\n \"Maximum response body characters to return. Default: 32000. Max: 200000. Increase when you need to read a large document, API response, or dataset.\",\n },\n saveToFile: {\n type: \"string\",\n description:\n \"Workspace file path to save the full response body to instead of returning it in context (e.g. 'analysis/page.html'). When set, returns only a compact summary {savedTo, status, bytes, preview}. Useful for large web pages or API responses that would overflow context.\",\n },\n },\n required: [\"url\"],\n },\n },\n run: async (args: Record<string, string>) => {\n const startTime = Date.now();\n const rawUrl = args.url;\n const method = normalizeExtensionProxyMethod(args.method || \"GET\");\n if (!method) {\n return \"Unsupported HTTP method. Allowed methods: GET, POST, PUT, PATCH, DELETE, HEAD.\";\n }\n const rawHeaders = args.headers || \"{}\";\n const rawBody = args.body;\n const timeoutMs = Math.min(\n Number(args.timeout_ms) || DEFAULT_TIMEOUT_MS,\n 30_000,\n );\n const requestedMaxChars = Number(args.maxChars);\n const maxChars =\n Number.isFinite(requestedMaxChars) && requestedMaxChars > 0\n ? Math.min(requestedMaxChars, 200_000)\n : 32_000;\n\n // Resolve key references\n let resolvedUrl = rawUrl;\n let resolvedHeaders = rawHeaders;\n let resolvedBody = rawBody;\n const allUsedKeys: string[] = [];\n const allSecretValues: string[] = [];\n\n if (opts.resolveKeys) {\n try {\n const urlResult = await opts.resolveKeys(rawUrl);\n resolvedUrl = urlResult.resolved;\n allUsedKeys.push(...urlResult.usedKeys);\n allSecretValues.push(...(urlResult.secretValues ?? []));\n\n const headerResult = await opts.resolveKeys(rawHeaders);\n resolvedHeaders = headerResult.resolved;\n allUsedKeys.push(...headerResult.usedKeys);\n allSecretValues.push(...(headerResult.secretValues ?? []));\n\n if (rawBody) {\n const bodyResult = await opts.resolveKeys(rawBody);\n resolvedBody = bodyResult.resolved;\n allUsedKeys.push(...bodyResult.usedKeys);\n allSecretValues.push(...(bodyResult.secretValues ?? []));\n }\n } catch (err: any) {\n return `Error resolving key references: ${err?.message ?? err}`;\n }\n }\n const secretValues = collectSecretValues(allSecretValues);\n\n // Block SSRF targets regardless of key usage\n if (await isBlockedExtensionUrlWithDns(resolvedUrl)) {\n return `Requests to private/internal addresses are not allowed: \"${rawUrl}\".`;\n }\n\n // Validate URL against per-key allowlists\n if (opts.validateUrl && allUsedKeys.length > 0) {\n try {\n const allowed = await opts.validateUrl(resolvedUrl, allUsedKeys);\n if (!allowed) {\n return `URL \"${rawUrl}\" is not in the allowlist for the referenced keys. Check your key settings.`;\n }\n } catch (err: any) {\n return `URL validation error: ${err?.message ?? err}`;\n }\n }\n\n // Parse headers, then merge in browser-like defaults for any header the\n // caller didn't already specify. Real-browser headers (User-Agent,\n // Accept, Sec-Fetch-*) are what gets you past Cloudflare / PerimeterX /\n // generic UA-sniffing middleware on sites the user pastes in chat;\n // explicit caller headers always win so API calls keep their auth\n // headers untouched.\n let headers: Record<string, string>;\n try {\n headers = sanitizeOutboundHeaders(JSON.parse(resolvedHeaders));\n } catch {\n return `Invalid headers JSON: ${rawHeaders}`;\n }\n headers = applyBrowserDefaults(headers);\n\n // Make the request\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n const dispatcher = (await createSsrfSafeDispatcher()) ?? undefined;\n const fetchOpts: RequestInit & { dispatcher?: unknown } = {\n method,\n headers,\n signal: controller.signal,\n redirect: \"manual\",\n };\n if (dispatcher) fetchOpts.dispatcher = dispatcher;\n if (resolvedBody && [\"POST\", \"PUT\", \"PATCH\"].includes(method)) {\n fetchOpts.body = resolvedBody;\n if (!headers[\"content-type\"] && !headers[\"Content-Type\"]) {\n headers[\"Content-Type\"] = \"application/json\";\n }\n }\n\n const response = await fetch(resolvedUrl, fetchOpts);\n const elapsed = Date.now() - startTime;\n\n if (response.status >= 300 && response.status < 400) {\n const location = response.headers.get(\"location\");\n const redirectUrl = location\n ? new URL(location, resolvedUrl).href\n : null;\n if (\n redirectUrl &&\n (await isBlockedExtensionUrlWithDns(redirectUrl))\n ) {\n return \"Redirect to private/internal address blocked.\";\n }\n if (redirectUrl && opts.validateUrl && allUsedKeys.length > 0) {\n const allowed = await opts.validateUrl(redirectUrl, allUsedKeys);\n if (!allowed) {\n return \"Redirect URL is not in the allowlist for the referenced keys.\";\n }\n }\n return `HTTP ${response.status} ${response.statusText}\\n\\nRedirect: ${\n redirectUrl ? redactString(redirectUrl, secretValues) : \"(none)\"\n }`;\n }\n\n // Check if caller wants to save to workspace file (before truncation).\n const saveToFilePath =\n typeof (args as Record<string, unknown>).saveToFile === \"string\"\n ? ((args as Record<string, unknown>).saveToFile as string).trim()\n : \"\";\n\n let body: string;\n try {\n // When saving to file allow larger reads (20MB), otherwise cap at proxy limit.\n const readLimit = saveToFilePath\n ? 20 * 1024 * 1024\n : MAX_EXTENSION_PROXY_RESPONSE_SIZE;\n const result = await readResponseTextWithLimit(response, readLimit);\n body = result.text;\n } catch {\n body = \"(could not read response body)\";\n }\n body = redactString(body, secretValues);\n\n // Audit log\n console.log(\n `[fetch-tool] ${method} ${rawUrl} → ${response.status} (${elapsed}ms, keys: ${allUsedKeys.join(\",\") || \"none\"})`,\n );\n\n // saveToFile: write full body to workspace and return compact summary.\n if (saveToFilePath) {\n try {\n const { writeWorkspaceFile, SAVE_TO_FILE_MAX_BYTES } =\n await import(\"../workspace-files/store.js\");\n const { getRequestOrgId, getRequestUserEmail } =\n await import(\"../server/request-context.js\");\n const orgId = getRequestOrgId();\n const email = getRequestUserEmail();\n const scope = orgId\n ? { scope: \"org\" as const, scopeId: orgId }\n : email\n ? { scope: \"user\" as const, scopeId: email }\n : null;\n if (!scope)\n throw new Error(\"No authenticated context for saveToFile\");\n const contentType =\n response.headers.get(\"content-type\")?.split(\";\")[0].trim() ??\n \"text/plain\";\n await writeWorkspaceFile(\n scope,\n saveToFilePath,\n body,\n contentType,\n {\n maxFileBytes: SAVE_TO_FILE_MAX_BYTES,\n },\n );\n const bytes = Buffer.byteLength(body, \"utf8\");\n const preview = body.slice(0, 2000);\n return JSON.stringify({\n savedToFile: true,\n savedTo: saveToFilePath,\n status: response.status,\n bytes,\n contentType,\n preview: preview.length < body.length ? `${preview}…` : preview,\n });\n } catch (saveErr: any) {\n return `saveToFile error: ${saveErr?.message ?? String(saveErr)}\\n\\nHTTP ${response.status} ${response.statusText}\\n\\n${body.slice(0, maxChars)}`;\n }\n }\n\n // Truncate very long responses for the agent. Default cap is 32 k\n // chars (~8 k tokens), enough to read a full article or scrape a\n // stats table without blowing out the model's context window. The\n // caller may request up to 200 000 chars via the maxChars input.\n if (body.length > maxChars) {\n body = body.slice(0, maxChars) + \"\\n... (truncated)\";\n }\n\n return `HTTP ${response.status} ${response.statusText}\\n\\n${body}`;\n } catch (err: any) {\n const elapsed = Date.now() - startTime;\n if (err?.name === \"AbortError\") {\n console.log(\n `[fetch-tool] ${method} ${rawUrl} → TIMEOUT (${elapsed}ms)`,\n );\n return `Request timed out after ${timeoutMs}ms.`;\n }\n const message = redactSecrets(\n err?.message ?? String(err),\n secretValues,\n );\n console.log(\n `[fetch-tool] ${method} ${rawUrl} → ERROR: ${message} (${elapsed}ms)`,\n );\n return `Request failed: ${message}`;\n } finally {\n clearTimeout(timeout);\n }\n },\n readOnly: true,\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"fetch-tool.js","sourceRoot":"","sources":["../../src/extensions/fetch-tool.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Fetch tool — outbound HTTP for automations and agent use.\n *\n * NOTE: this is an *agent* tool (LLM function call), not an *extension* (the\n * sandboxed Alpine.js mini-app primitive). It lives in this directory because\n * it shares SSRF-safe URL/proxy helpers with the extension iframe proxy.\n *\n * Supports ${keys.NAME} reference substitution in URL, headers, and body.\n * Values are resolved server-side AFTER the model emits the tool call —\n * the raw secret never enters the model's context.\n */\n\nimport type { ActionEntry } from \"../agent/production-agent.js\";\nimport {\n collectSecretValues,\n MAX_EXTENSION_PROXY_RESPONSE_SIZE,\n normalizeExtensionProxyMethod,\n readResponseTextWithLimit,\n redactSecrets,\n redactString,\n sanitizeOutboundHeaders,\n} from \"./proxy-security.js\";\nimport {\n createSsrfSafeDispatcher,\n isBlockedExtensionUrlWithDns,\n} from \"./url-safety.js\";\n\nconst DEFAULT_TIMEOUT_MS = 15_000;\n\n/**\n * Headers that mimic a current Chrome on macOS so anti-bot middleware (Cloudflare,\n * PerimeterX, Akamai) treats the request as a real user. We only fill in fields\n * the caller hasn't supplied — explicit headers (e.g. an `Authorization` header\n * for an API call) always win.\n *\n * `Accept-Encoding` deliberately omits `zstd` because Node's undici fetch only\n * decompresses `gzip`, `deflate`, and `br`. Advertising `zstd` would let some\n * servers send bytes we can't decode.\n */\nconst BROWSER_DEFAULT_HEADERS: Record<string, string> = {\n \"User-Agent\":\n \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36\",\n Accept:\n \"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8\",\n \"Accept-Language\": \"en-US,en;q=0.9\",\n \"Accept-Encoding\": \"gzip, deflate, br\",\n \"Sec-Ch-Ua\":\n '\"Google Chrome\";v=\"131\", \"Chromium\";v=\"131\", \"Not_A Brand\";v=\"24\"',\n \"Sec-Ch-Ua-Mobile\": \"?0\",\n \"Sec-Ch-Ua-Platform\": '\"macOS\"',\n \"Sec-Fetch-Dest\": \"document\",\n \"Sec-Fetch-Mode\": \"navigate\",\n \"Sec-Fetch-Site\": \"none\",\n \"Sec-Fetch-User\": \"?1\",\n \"Upgrade-Insecure-Requests\": \"1\",\n};\n\nfunction applyBrowserDefaults(\n headers: Record<string, string>,\n): Record<string, string> {\n const seen = new Set(Object.keys(headers).map((k) => k.toLowerCase()));\n const merged = { ...headers };\n for (const [name, value] of Object.entries(BROWSER_DEFAULT_HEADERS)) {\n if (!seen.has(name.toLowerCase())) merged[name] = value;\n }\n return merged;\n}\n\nexport interface FetchToolOptions {\n /** Resolve ${keys.NAME} references. Injected by the plugin at setup time. */\n resolveKeys?: (text: string) => Promise<{\n resolved: string;\n usedKeys: string[];\n secretValues?: string[];\n }>;\n /** Validate URL against per-key allowlists. */\n validateUrl?: (url: string, usedKeys: string[]) => Promise<boolean>;\n}\n\n/**\n * Create the fetch tool entry for the agent tool registry.\n */\nexport function createFetchToolEntry(\n opts: FetchToolOptions = {},\n): Record<string, ActionEntry> {\n return {\n \"web-request\": {\n tool: {\n description: `Make an outbound HTTP request to any EXTERNAL URL — APIs, webhooks, and arbitrary web pages (HTML, RSS, JSON, etc.). Use this to fetch the contents of a URL the user pastes in chat. Sends realistic Chrome-on-macOS headers by default (User-Agent, Accept, Sec-Fetch-*) so most sites that block obvious bots will respond normally; pass an explicit header to override any default. Supports \\${keys.NAME} placeholders in url, headers, and body — these are resolved server-side from the user's saved keys (the raw value never enters your context). Example: \\${keys.SLACK_WEBHOOK} in the url field. IMPORTANT: Never use this to call internal /_agent-native/ endpoints or localhost action URLs — use the registered actions directly (e.g. \\`search-records\\`, \\`provider-api-request\\`, \\`update-resource\\`). Actions are already available as native tools; calling them via HTTP is slower and bypasses validation.`,\n parameters: {\n type: \"object\" as const,\n properties: {\n url: {\n type: \"string\",\n description:\n 'Full URL. May contain ${keys.NAME} references, e.g. \"${keys.SLACK_WEBHOOK}\".',\n },\n method: {\n type: \"string\",\n description: \"HTTP method. Default: GET.\",\n enum: [\"GET\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\", \"HEAD\"],\n },\n headers: {\n type: \"string\",\n description:\n 'JSON object of headers. May contain ${keys.NAME} references. Example: \\'{\"Authorization\": \"Bearer ${keys.API_TOKEN}\"}\\'.',\n },\n body: {\n type: \"string\",\n description:\n \"Request body (for POST/PUT/PATCH). May contain ${keys.NAME} references.\",\n },\n timeout_ms: {\n type: \"number\",\n description: `Timeout in milliseconds. Default: ${DEFAULT_TIMEOUT_MS}. Max: 30000.`,\n },\n maxChars: {\n type: \"number\",\n description:\n \"Maximum response body characters to return. Default: 32000. Max: 200000. Increase when you need to read a large document, API response, or dataset.\",\n },\n saveToFile: {\n type: \"string\",\n description:\n \"Workspace file path to save the full response body to instead of returning it in context (e.g. 'analysis/page.html'). When set, returns only a compact summary {savedTo, status, bytes, preview}. Useful for large web pages or API responses that would overflow context.\",\n },\n },\n required: [\"url\"],\n },\n },\n run: async (args: Record<string, string>) => {\n const startTime = Date.now();\n const rawUrl = args.url;\n const method = normalizeExtensionProxyMethod(args.method || \"GET\");\n if (!method) {\n return \"Unsupported HTTP method. Allowed methods: GET, POST, PUT, PATCH, DELETE, HEAD.\";\n }\n const rawHeaders = args.headers || \"{}\";\n const rawBody = args.body;\n const timeoutMs = Math.min(\n Number(args.timeout_ms) || DEFAULT_TIMEOUT_MS,\n 30_000,\n );\n const requestedMaxChars = Number(args.maxChars);\n const maxChars =\n Number.isFinite(requestedMaxChars) && requestedMaxChars > 0\n ? Math.min(requestedMaxChars, 200_000)\n : 32_000;\n\n // Resolve key references\n let resolvedUrl = rawUrl;\n let resolvedHeaders = rawHeaders;\n let resolvedBody = rawBody;\n const allUsedKeys: string[] = [];\n const allSecretValues: string[] = [];\n\n if (opts.resolveKeys) {\n try {\n const urlResult = await opts.resolveKeys(rawUrl);\n resolvedUrl = urlResult.resolved;\n allUsedKeys.push(...urlResult.usedKeys);\n allSecretValues.push(...(urlResult.secretValues ?? []));\n\n const headerResult = await opts.resolveKeys(rawHeaders);\n resolvedHeaders = headerResult.resolved;\n allUsedKeys.push(...headerResult.usedKeys);\n allSecretValues.push(...(headerResult.secretValues ?? []));\n\n if (rawBody) {\n const bodyResult = await opts.resolveKeys(rawBody);\n resolvedBody = bodyResult.resolved;\n allUsedKeys.push(...bodyResult.usedKeys);\n allSecretValues.push(...(bodyResult.secretValues ?? []));\n }\n } catch (err: any) {\n return `Error resolving key references: ${err?.message ?? err}`;\n }\n }\n const secretValues = collectSecretValues(allSecretValues);\n\n // Block SSRF targets regardless of key usage\n if (await isBlockedExtensionUrlWithDns(resolvedUrl)) {\n return `Requests to private/internal addresses are not allowed: \"${rawUrl}\".`;\n }\n\n // Validate URL against per-key allowlists\n if (opts.validateUrl && allUsedKeys.length > 0) {\n try {\n const allowed = await opts.validateUrl(resolvedUrl, allUsedKeys);\n if (!allowed) {\n return `URL \"${rawUrl}\" is not in the allowlist for the referenced keys. Check your key settings.`;\n }\n } catch (err: any) {\n return `URL validation error: ${err?.message ?? err}`;\n }\n }\n\n // Parse headers, then merge in browser-like defaults for any header the\n // caller didn't already specify. Real-browser headers (User-Agent,\n // Accept, Sec-Fetch-*) are what gets you past Cloudflare / PerimeterX /\n // generic UA-sniffing middleware on sites the user pastes in chat;\n // explicit caller headers always win so API calls keep their auth\n // headers untouched.\n let headers: Record<string, string>;\n try {\n headers = sanitizeOutboundHeaders(JSON.parse(resolvedHeaders));\n } catch {\n return `Invalid headers JSON: ${rawHeaders}`;\n }\n headers = applyBrowserDefaults(headers);\n\n // Make the request\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n const dispatcher = (await createSsrfSafeDispatcher()) ?? undefined;\n const fetchOpts: RequestInit & { dispatcher?: unknown } = {\n method,\n headers,\n signal: controller.signal,\n redirect: \"manual\",\n };\n if (dispatcher) fetchOpts.dispatcher = dispatcher;\n if (resolvedBody && [\"POST\", \"PUT\", \"PATCH\"].includes(method)) {\n fetchOpts.body = resolvedBody;\n if (!headers[\"content-type\"] && !headers[\"Content-Type\"]) {\n headers[\"Content-Type\"] = \"application/json\";\n }\n }\n\n const response = await fetch(resolvedUrl, fetchOpts);\n const elapsed = Date.now() - startTime;\n\n if (response.status >= 300 && response.status < 400) {\n const location = response.headers.get(\"location\");\n const redirectUrl = location\n ? new URL(location, resolvedUrl).href\n : null;\n if (\n redirectUrl &&\n (await isBlockedExtensionUrlWithDns(redirectUrl))\n ) {\n return \"Redirect to private/internal address blocked.\";\n }\n if (redirectUrl && opts.validateUrl && allUsedKeys.length > 0) {\n const allowed = await opts.validateUrl(redirectUrl, allUsedKeys);\n if (!allowed) {\n return \"Redirect URL is not in the allowlist for the referenced keys.\";\n }\n }\n return `HTTP ${response.status} ${response.statusText}\\n\\nRedirect: ${\n redirectUrl ? redactString(redirectUrl, secretValues) : \"(none)\"\n }`;\n }\n\n // Check if caller wants to save to workspace file (before truncation).\n const saveToFilePath =\n typeof (args as Record<string, unknown>).saveToFile === \"string\"\n ? ((args as Record<string, unknown>).saveToFile as string).trim()\n : \"\";\n\n let body: string;\n try {\n // When saving to file allow larger reads (20MB), otherwise cap at proxy limit.\n const readLimit = saveToFilePath\n ? 20 * 1024 * 1024\n : MAX_EXTENSION_PROXY_RESPONSE_SIZE;\n const result = await readResponseTextWithLimit(response, readLimit);\n body = result.text;\n } catch {\n body = \"(could not read response body)\";\n }\n body = redactString(body, secretValues);\n\n // Audit log\n console.log(\n `[fetch-tool] ${method} ${rawUrl} → ${response.status} (${elapsed}ms, keys: ${allUsedKeys.join(\",\") || \"none\"})`,\n );\n\n // saveToFile: write full body to workspace and return compact summary.\n if (saveToFilePath) {\n try {\n const { writeWorkspaceFile, SAVE_TO_FILE_MAX_BYTES } =\n await import(\"../workspace-files/store.js\");\n const { getRequestOrgId, getRequestUserEmail } =\n await import(\"../server/request-context.js\");\n const orgId = getRequestOrgId();\n const email = getRequestUserEmail();\n const scope = orgId\n ? { scope: \"org\" as const, scopeId: orgId }\n : email\n ? { scope: \"user\" as const, scopeId: email }\n : null;\n if (!scope)\n throw new Error(\"No authenticated context for saveToFile\");\n const contentType =\n response.headers.get(\"content-type\")?.split(\";\")[0].trim() ??\n \"text/plain\";\n await writeWorkspaceFile(\n scope,\n saveToFilePath,\n body,\n contentType,\n {\n maxFileBytes: SAVE_TO_FILE_MAX_BYTES,\n },\n );\n const bytes = Buffer.byteLength(body, \"utf8\");\n const preview = body.slice(0, 2000);\n return JSON.stringify({\n savedToFile: true,\n savedTo: saveToFilePath,\n status: response.status,\n bytes,\n contentType,\n preview: preview.length < body.length ? `${preview}…` : preview,\n });\n } catch (saveErr: any) {\n return `saveToFile error: ${saveErr?.message ?? String(saveErr)}\\n\\nHTTP ${response.status} ${response.statusText}\\n\\n${body.slice(0, maxChars)}`;\n }\n }\n\n // Truncate very long responses for the agent. Default cap is 32 k\n // chars (~8 k tokens), enough to read a full article or scrape a\n // stats table without blowing out the model's context window. The\n // caller may request up to 200 000 chars via the maxChars input.\n if (body.length > maxChars) {\n body = body.slice(0, maxChars) + \"\\n... (truncated)\";\n }\n\n return `HTTP ${response.status} ${response.statusText}\\n\\n${body}`;\n } catch (err: any) {\n const elapsed = Date.now() - startTime;\n if (err?.name === \"AbortError\") {\n console.log(\n `[fetch-tool] ${method} ${rawUrl} → TIMEOUT (${elapsed}ms)`,\n );\n return `Request timed out after ${timeoutMs}ms.`;\n }\n const message = redactSecrets(\n err?.message ?? String(err),\n secretValues,\n );\n console.log(\n `[fetch-tool] ${method} ${rawUrl} → ERROR: ${message} (${elapsed}ms)`,\n );\n return `Request failed: ${message}`;\n } finally {\n clearTimeout(timeout);\n }\n },\n readOnly: true,\n },\n };\n}\n"]}
|