@agent-native/core 0.208.0 → 0.208.1-nightly-20261013212700

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.
@@ -7,9 +7,18 @@ async function loadTokenizer() {
7
7
  // browser fallback, while the documented estimate below is sufficient
8
8
  // when an edge runtime cannot resolve the tokenizer.
9
9
  tokenizerPromise = import(/* @vite-ignore */ TOKENIZER_MODULE_ID)
10
- .then((mod) => typeof mod.countTokens === "function"
11
- ? mod.countTokens
12
- : null)
10
+ .then((mod) => {
11
+ // The package's countTokens rebuilds the BPE encoder on every call
12
+ // (~20 ms); counting a whole context part by part before each model
13
+ // call turned that into seconds of dead time per turn.
14
+ if (typeof mod.getTokenizer === "function") {
15
+ const encoder = mod.getTokenizer();
16
+ return (text) => encoder.encode(text.normalize("NFKC"), "all").length;
17
+ }
18
+ return typeof mod.countTokens === "function"
19
+ ? mod.countTokens
20
+ : null;
21
+ })
13
22
  .catch(() => null);
14
23
  }
15
24
  return tokenizerPromise;
@@ -10,7 +10,7 @@ import { classifyTerminalErrorCode, describeErrorWithCauses, isProviderConnectio
10
10
  import { runErrorTelemetryProperties } from "./engine/error-telemetry.js";
11
11
  import { getAgentEngineEntry } from "./engine/registry.js";
12
12
  import { EngineError } from "./engine/types.js";
13
- import { insertRun, insertRunEvent, updateRunStatusIfRunning, markRunAborted, getRunAbortState, getRunStatus, getRunEventsSince, getCurrentTurnRunEventsForThread, getRunById, getRunByThread, getRunTurnRef, markTurnAborted, cleanupOldRuns, updateRunHeartbeat, bumpRunProgress, setRunInFlightMarker, reapIfStale, reapUnclaimedBackgroundRun, shouldRedispatchUnclaimedBackgroundRun, reconcileTerminalRunFromEvents, ensureTerminalRunEvent, getLastTerminalRunEvent, resolveErroredRunTerminalEvent, setRunError, setRunTerminalReason, persistRunCheckpointEvent, recordRunDiagnostic, RUN_DIAG_STAGE, terminalEventForAbortReason, RUN_RECORD_MISSING_ERROR_EVENT, RUN_RECORD_MISSING_GRACE_MS, RUN_TERMINAL_LOOKUP_FAILED_ERROR_EVENT, UNKNOWN_RUN_STATUS_ERROR_EVENT, } from "./run-store.js";
13
+ import { insertRun, insertRunEvent, insertRunEvents, updateRunStatusIfRunning, markRunAborted, getRunAbortState, getRunStatus, getRunEventsSince, getCurrentTurnRunEventsForThread, getRunById, getRunByThread, getRunTurnRef, markTurnAborted, cleanupOldRuns, updateRunHeartbeat, bumpRunProgress, setRunInFlightMarker, reapIfStale, reapUnclaimedBackgroundRun, shouldRedispatchUnclaimedBackgroundRun, reconcileTerminalRunFromEvents, ensureTerminalRunEvent, getLastTerminalRunEvent, resolveErroredRunTerminalEvent, setRunError, setRunTerminalReason, persistRunCheckpointEvent, recordRunDiagnostic, RUN_DIAG_STAGE, terminalEventForAbortReason, RUN_RECORD_MISSING_ERROR_EVENT, RUN_RECORD_MISSING_GRACE_MS, RUN_TERMINAL_LOOKUP_FAILED_ERROR_EVENT, UNKNOWN_RUN_STATUS_ERROR_EVENT, } from "./run-store.js";
14
14
  import { isContinuationTerminalReason } from "./types.js";
15
15
  const activeRuns = new Map();
16
16
  const threadToRun = new Map();
@@ -232,6 +232,11 @@ function isTerminalRunEvent(event) {
232
232
  event.type === "loop_limit" ||
233
233
  event.type === "auto_continue");
234
234
  }
235
+ function isBatchableRunEvent(event) {
236
+ return (event.type !== "tool_start" &&
237
+ event.type !== "tool_done" &&
238
+ !isTerminalRunEvent(event));
239
+ }
235
240
  /**
236
241
  * A tool result with no later assistant text is an unfinished turn, not a
237
242
  * successful terminal response. Keep this predicate beside the run-manager's
@@ -570,6 +575,10 @@ export function startRun(runId, threadId, runFn, onComplete, options) {
570
575
  throw error;
571
576
  });
572
577
  let persistenceChain = Promise.resolve();
578
+ // Streamed deltas arrive hundreds per tool call, and a write tool waits for
579
+ // every queued event to persist first; one round trip per event put tens of
580
+ // seconds between the model finishing and the tool starting.
581
+ let openEventBatch = null;
573
582
  const PROGRESS_BUMP_INTERVAL_MS = 1_000;
574
583
  let lastProgressBumpAt = 0;
575
584
  let progressWriteInFlight = null;
@@ -1029,18 +1038,20 @@ export function startRun(runId, threadId, runFn, onComplete, options) {
1029
1038
  },
1030
1039
  });
1031
1040
  };
1032
- const persistRunEvent = async (runEvent, eventData) => {
1041
+ const persistEventHook = options?.persistEvent;
1042
+ const persistRunEvent = async (runEvent, eventData, eventAt) => {
1033
1043
  if (runEvent.event.type === "tool_start")
1034
1044
  await insertRunPromise;
1035
1045
  const write = () => runEvent.event.type === "tool_start" ||
1036
1046
  runEvent.event.type === "tool_done"
1037
1047
  ? insertRunEvent(runId, runEvent.seq, eventData, {
1048
+ eventAt,
1038
1049
  toolInputSource: "execution",
1039
1050
  ...(runEvent.event.type === "tool_start"
1040
1051
  ? { requireInserted: true }
1041
1052
  : {}),
1042
1053
  })
1043
- : insertRunEvent(runId, runEvent.seq, eventData);
1054
+ : insertRunEvent(runId, runEvent.seq, eventData, { eventAt });
1044
1055
  return options?.persistEvent
1045
1056
  ? options.persistEvent(write, {
1046
1057
  terminal: isTerminalRunEvent(runEvent.event),
@@ -1052,6 +1063,7 @@ export function startRun(runId, threadId, runFn, onComplete, options) {
1052
1063
  };
1053
1064
  const emitRunEvent = (runEvent, options) => {
1054
1065
  const eventData = JSON.stringify(runEvent.event);
1066
+ const eventAt = Date.now();
1055
1067
  run.events.push(runEvent);
1056
1068
  for (const subscriber of run.subscribers) {
1057
1069
  try {
@@ -1066,9 +1078,9 @@ export function startRun(runId, threadId, runFn, onComplete, options) {
1066
1078
  lastRealProgressAt = Date.now();
1067
1079
  bumpProgressIfDue();
1068
1080
  }
1069
- const thisInsert = persistenceChain.then(async () => {
1081
+ const retryOnce = async (write) => {
1070
1082
  try {
1071
- await persistRunEvent(runEvent, eventData);
1083
+ await write();
1072
1084
  }
1073
1085
  catch (error) {
1074
1086
  if (!eventPersistenceErrorCaptured) {
@@ -1078,11 +1090,36 @@ export function startRun(runId, threadId, runFn, onComplete, options) {
1078
1090
  eventType: runEvent.event.type,
1079
1091
  });
1080
1092
  }
1081
- await persistRunEvent(runEvent, eventData);
1093
+ await write();
1082
1094
  }
1083
- });
1084
- persistenceChain = thisInsert;
1085
- const persistence = thisInsert;
1095
+ };
1096
+ let persistence;
1097
+ if (isBatchableRunEvent(runEvent.event) && !persistEventHook) {
1098
+ if (!openEventBatch) {
1099
+ const batch = {
1100
+ events: [],
1101
+ write: persistenceChain.then(() => {
1102
+ if (openEventBatch === batch)
1103
+ openEventBatch = null;
1104
+ return retryOnce(() => insertRunEvents(runId, batch.events));
1105
+ }),
1106
+ };
1107
+ openEventBatch = batch;
1108
+ persistenceChain = batch.write;
1109
+ }
1110
+ openEventBatch.events.push({
1111
+ seq: runEvent.seq,
1112
+ eventAt,
1113
+ eventData,
1114
+ });
1115
+ persistence = openEventBatch.write;
1116
+ }
1117
+ else {
1118
+ // Anything emitted after this event must not persist before it.
1119
+ openEventBatch = null;
1120
+ persistence = persistenceChain.then(() => retryOnce(() => persistRunEvent(runEvent, eventData, eventAt)));
1121
+ persistenceChain = persistence;
1122
+ }
1086
1123
  if (!options?.surfacePersistenceError) {
1087
1124
  persistence.catch(() => { });
1088
1125
  }
@@ -1275,7 +1312,7 @@ export function startRun(runId, threadId, runFn, onComplete, options) {
1275
1312
  console.error("[run-manager] terminal event persistence error:", err instanceof Error ? err.message : err);
1276
1313
  if (!eventPersistenceError) {
1277
1314
  try {
1278
- await persistRunEvent(terminal, JSON.stringify(terminal.event));
1315
+ await persistRunEvent(terminal, JSON.stringify(terminal.event), Date.now());
1279
1316
  terminalPersistenceError = null;
1280
1317
  }
1281
1318
  catch (retryError) {
@@ -285,7 +285,17 @@ export declare class AgentRunEventNotPersistedError extends Error {
285
285
  export declare function insertRunEvent(runId: string, seq: number, eventData: string, options?: {
286
286
  toolInputSource?: "execution";
287
287
  requireInserted?: boolean;
288
+ eventAt?: number;
288
289
  }): Promise<void>;
290
+ /**
291
+ * Same guarantees as `insertRunEvent` without execution-input fingerprints,
292
+ * so tool_start/tool_done must keep going through `insertRunEvent`.
293
+ */
294
+ export declare function insertRunEvents(runId: string, events: ReadonlyArray<{
295
+ seq: number;
296
+ eventAt: number;
297
+ eventData: string;
298
+ }>): Promise<void>;
289
299
  export declare const CHECKPOINT_TERMINAL_EVENT_SEQ = 1000000000;
290
300
  export declare function persistRunCheckpointEvent(runId: string, event: AgentChatEvent, terminalReason: string): Promise<void>;
291
301
  export declare function getRunEventsSince(runId: string, fromSeq: number, opts?: {
@@ -1910,7 +1910,7 @@ export async function insertRunEvent(runId, seq, eventData, options) {
1910
1910
  args: [
1911
1911
  runId,
1912
1912
  seq,
1913
- Date.now(),
1913
+ options?.eventAt ?? Date.now(),
1914
1914
  storedEventData,
1915
1915
  ...(options?.requireInserted ? [] : [runId]),
1916
1916
  ],
@@ -1918,6 +1918,42 @@ export async function insertRunEvent(runId, seq, eventData, options) {
1918
1918
  if (options?.requireInserted && rowsAffected !== 1)
1919
1919
  throw new AgentRunEventNotPersistedError(runId, seq);
1920
1920
  }
1921
+ // Stays well under PostgreSQL's 65,535 bind-parameter limit at 4 per row.
1922
+ const RUN_EVENT_BATCH_ROWS = 500;
1923
+ /**
1924
+ * Same guarantees as `insertRunEvent` without execution-input fingerprints,
1925
+ * so tool_start/tool_done must keep going through `insertRunEvent`.
1926
+ */
1927
+ export async function insertRunEvents(runId, events) {
1928
+ if (events.length === 0)
1929
+ return;
1930
+ await ensureRunTables();
1931
+ const client = getDbExec();
1932
+ for (let start = 0; start < events.length; start += RUN_EVENT_BATCH_ROWS) {
1933
+ const chunk = events.slice(start, start + RUN_EVENT_BATCH_ROWS);
1934
+ await client.execute({
1935
+ sql: `INSERT INTO agent_run_events (run_id, seq, event_at, event_data)
1936
+ SELECT v.run_id, v.seq, v.event_at, v.event_data
1937
+ FROM (VALUES ${chunk
1938
+ .map(() => "(CAST(? AS TEXT), CAST(? AS BIGINT), CAST(? AS BIGINT), CAST(? AS TEXT))")
1939
+ .join(", ")}) AS v(run_id, seq, event_at, event_data)
1940
+ WHERE NOT EXISTS (
1941
+ SELECT 1 FROM agent_runs
1942
+ WHERE id = ? AND status <> 'running'
1943
+ )
1944
+ ON CONFLICT (run_id, seq) DO NOTHING`,
1945
+ args: [
1946
+ ...chunk.flatMap(({ seq, eventAt, eventData }) => [
1947
+ runId,
1948
+ seq,
1949
+ eventAt,
1950
+ persistedRunEventData(eventData),
1951
+ ]),
1952
+ runId,
1953
+ ],
1954
+ });
1955
+ }
1956
+ }
1921
1957
  export const CHECKPOINT_TERMINAL_EVENT_SEQ = 1_000_000_000;
1922
1958
  export async function persistRunCheckpointEvent(runId, event, terminalReason) {
1923
1959
  await insertRunEvent(runId, CHECKPOINT_TERMINAL_EVENT_SEQ, JSON.stringify(event));
@@ -38,19 +38,19 @@ export declare const postAwareness: import("h3").EventHandlerWithFetch<import("h
38
38
  error: string;
39
39
  states?: undefined;
40
40
  } | {
41
- error?: undefined;
42
41
  states: {
43
42
  clientId: number;
44
43
  state: string;
45
44
  }[];
45
+ error?: undefined;
46
46
  }>>;
47
47
  export declare const getActiveUsers: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
48
48
  error: string;
49
49
  users?: undefined;
50
50
  } | {
51
- error?: undefined;
52
51
  users: {
53
52
  clientId: number;
54
53
  lastSeen: number;
55
54
  }[];
55
+ error?: undefined;
56
56
  }>>;
@@ -13,8 +13,8 @@ export declare const getCollabState: import("h3").EventHandlerWithFetch<import("
13
13
  };
14
14
  }>>;
15
15
  export declare const postCollabUpdate: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
16
- error: string;
17
16
  ok?: undefined;
17
+ error: string;
18
18
  } | {
19
19
  error?: undefined;
20
20
  ok: boolean;
@@ -1,6 +1,6 @@
1
1
  export declare const postCollabJson: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
2
- ok?: undefined;
3
2
  error: string;
3
+ ok?: undefined;
4
4
  } | {
5
5
  error?: undefined;
6
6
  ok: boolean;
@@ -10,10 +10,12 @@
10
10
  * the raw secret never enters the model's context.
11
11
  */
12
12
  import { fail, isActionContractError } from "../action.js";
13
+ import { SAVE_TO_FILE_MAX_BYTES } from "../workspace-files/constants.js";
13
14
  import { collectSecretValues, MAX_EXTENSION_PROXY_RESPONSE_SIZE, normalizeExtensionProxyMethod, readResponseBytesWithLimit, readResponseTextWithLimit, redactSecrets, redactString, sanitizeOutboundHeaders, } from "./proxy-security.js";
14
15
  import { createSsrfSafeDispatcher, isBlockedExtensionUrlWithDns, } from "./url-safety.js";
15
- import { formatWebContentResult, parseWebContentSearchOptions, processWebContent, } from "./web-content.js";
16
+ import { formatWebContentResult, hasWebContentSearch, parseWebContentSearchOptions, processWebContent, } from "./web-content.js";
16
17
  const DEFAULT_TIMEOUT_MS = 15_000;
18
+ const MAX_WEB_REQUEST_HTML_BYTES = SAVE_TO_FILE_MAX_BYTES;
17
19
  const VISION_IMAGE_CONTENT_TYPES = new Set([
18
20
  "image/jpeg",
19
21
  "image/png",
@@ -21,6 +23,20 @@ const VISION_IMAGE_CONTENT_TYPES = new Set([
21
23
  "image/webp",
22
24
  ]);
23
25
  const CREDENTIAL_NAME_RE = /(?:^|[-_])(access[-_]?token|api[-_]?key|auth(?:orization)?|credential|expires?|key[-_]?pair[-_]?id|key|password|passwd|policy|secret|session|signature|sig|token|jwt|assertion)(?:$|[-_])/i;
26
+ function isHtmlResponseContentType(contentType) {
27
+ const mediaType = contentType.toLowerCase();
28
+ return (mediaType === "text/html" ||
29
+ mediaType === "application/xhtml+xml" ||
30
+ mediaType.endsWith("+html"));
31
+ }
32
+ function truncateToolText(value, maxChars) {
33
+ if (value.length <= maxChars)
34
+ return value;
35
+ const marker = "\n...[truncated]";
36
+ if (maxChars <= marker.length)
37
+ return marker.slice(0, maxChars);
38
+ return `${value.slice(0, maxChars - marker.length)}${marker}`;
39
+ }
24
40
  function uint8ArrayToBase64(bytes) {
25
41
  if (typeof Buffer !== "undefined")
26
42
  return Buffer.from(bytes).toString("base64");
@@ -85,7 +101,7 @@ export function createFetchToolEntry(opts = {}) {
85
101
  return {
86
102
  "web-request": {
87
103
  tool: {
88
- 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.`,
104
+ description: `Make an outbound HTTP request to an external URL: APIs, webhooks, or web pages. HTML/XHTML responses up to 20 MB are extracted before maxChars limits the response body; optional metadata and links can add characters. Raw and other inline responses remain capped at 1 MB. saveToFile can stage responses up to 20 MB. For HTML, it saves extracted Markdown or text unless responseMode is raw; with search, it returns bounded matches instead of a preview. Sends realistic Chrome-on-macOS headers by default; explicit headers override them. Supports \${keys.NAME} placeholders in url, headers, and body, resolved server-side so raw secrets never enter context. Never use this for internal /_agent-native/ endpoints or localhost action URLs; call registered actions directly.`,
89
105
  parameters: {
90
106
  type: "object",
91
107
  properties: {
@@ -112,7 +128,7 @@ export function createFetchToolEntry(opts = {}) {
112
128
  },
113
129
  maxChars: {
114
130
  type: "number",
115
- description: "Maximum response body characters to return. Default: 32000. Max: 200000. Increase when you need to read a large document, API response, or dataset.",
131
+ description: "Maximum response-body characters returned to context. Default: 32000; max: 200000. Metadata and optional links are additional. This limits extracted content, not the HTML download. Use saveToFile for a longer extracted document.",
116
132
  },
117
133
  responseMode: {
118
134
  type: "string",
@@ -134,11 +150,11 @@ export function createFetchToolEntry(opts = {}) {
134
150
  },
135
151
  includeLinks: {
136
152
  type: "boolean",
137
- description: "Whether extracted HTML responses should include a compact links list. Default: true for extracted pages.",
153
+ description: "Whether extracted HTML responses should include a compact links list. Default: true for inline responses and false when saveToFile is set.",
138
154
  },
139
155
  search: {
140
156
  type: "object",
141
- description: "Optional post-fetch search over extracted content by default. Supports {query, queries, terms, regex, regexFlags, source:'extracted'|'raw', maxMatches, contextChars, caseSensitive}. Regex is safety-checked and bounded; prefer query/terms for simple grep-like searches.",
157
+ description: "Optional post-fetch search over extracted content by default. Searches at most the first 500,000 characters of the selected source; saved results report searchSourceTruncated when this limit is reached. Supports {query, queries, terms, regex, regexFlags, source:'extracted'|'raw', maxMatches, contextChars, caseSensitive}. Regex is safety-checked and bounded; prefer query/terms for simple grep-like searches.",
142
158
  properties: {
143
159
  query: { type: "string" },
144
160
  queries: { type: "array", items: { type: "string" } },
@@ -153,7 +169,7 @@ export function createFetchToolEntry(opts = {}) {
153
169
  },
154
170
  saveToFile: {
155
171
  type: "string",
156
- description: "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.",
172
+ description: "Workspace path to stage a response up to 20 MB (e.g. 'scratch/research/page.md'). HTML saves full extracted Markdown/text unless responseMode is raw; non-HTML and raw mode save the response body. With search, returns matches bounded by maxChars and reports when source scanning was capped; without search, returns a preview.",
157
173
  },
158
174
  },
159
175
  required: ["url"],
@@ -295,6 +311,9 @@ export function createFetchToolEntry(opts = {}) {
295
311
  }
296
312
  const contentType = response.headers.get("content-type")?.split(";")[0].trim() ??
297
313
  "text/plain";
314
+ const requestedResponseMode = String(args.responseMode ?? "auto");
315
+ const normalizedResponseMode = requestedResponseMode.toLowerCase();
316
+ const isHtmlContentType = isHtmlResponseContentType(contentType);
298
317
  const httpErrorCode = response.ok
299
318
  ? undefined
300
319
  : `http_${response.status}`;
@@ -335,10 +354,11 @@ export function createFetchToolEntry(opts = {}) {
335
354
  }
336
355
  let body;
337
356
  let bodyResult;
357
+ const readLimit = saveToFilePath ||
358
+ (isHtmlContentType && normalizedResponseMode !== "raw")
359
+ ? MAX_WEB_REQUEST_HTML_BYTES
360
+ : MAX_EXTENSION_PROXY_RESPONSE_SIZE;
338
361
  try {
339
- const readLimit = saveToFilePath
340
- ? 20 * 1024 * 1024
341
- : MAX_EXTENSION_PROXY_RESPONSE_SIZE;
342
362
  bodyResult = await readResponseTextWithLimit(response, readLimit);
343
363
  }
344
364
  catch {
@@ -347,8 +367,15 @@ export function createFetchToolEntry(opts = {}) {
347
367
  });
348
368
  }
349
369
  if (bodyResult.truncated) {
350
- fail(`HTTP ${response.status} ${response.statusText}\n\n${bodyResult.text}`, {
351
- errorCode: httpErrorCode ?? "web_request_response_too_large",
370
+ const declaredBytes = Number(response.headers.get("content-length"));
371
+ const exceedsSaveLimit = readLimit === MAX_WEB_REQUEST_HTML_BYTES ||
372
+ (Number.isFinite(declaredBytes) &&
373
+ declaredBytes > MAX_WEB_REQUEST_HTML_BYTES);
374
+ const recovery = exceedsSaveLimit
375
+ ? `The response exceeds web-request's ${MAX_WEB_REQUEST_HTML_BYTES}-byte limit.`
376
+ : `Retry with saveToFile to stage responses up to ${MAX_WEB_REQUEST_HTML_BYTES} bytes.`;
377
+ fail(`HTTP ${response.status} ${response.statusText}\n\nResponse too large: at least ${bodyResult.size} bytes observed; maximum ${readLimit} bytes. ${recovery}`, {
378
+ errorCode: "web_request_response_too_large",
352
379
  });
353
380
  }
354
381
  body = bodyResult.text;
@@ -361,21 +388,69 @@ export function createFetchToolEntry(opts = {}) {
361
388
  }
362
389
  let displayBody;
363
390
  let processedMode = "raw";
391
+ let searchResultsTruncated = false;
392
+ let searchSourceTruncated = false;
393
+ let fileContent = body;
394
+ let fileContentType = contentType;
395
+ const search = parseWebContentSearchOptions(args.search);
396
+ const hasSearch = hasWebContentSearch(search);
397
+ const includeLinks = args.includeLinks === undefined
398
+ ? !saveToFilePath
399
+ : parseBooleanArg(args.includeLinks);
400
+ const saveContentFormat = saveToFilePath &&
401
+ isHtmlContentType &&
402
+ normalizedResponseMode !== "raw"
403
+ ? normalizedResponseMode === "text"
404
+ ? "text"
405
+ : "markdown"
406
+ : undefined;
364
407
  try {
365
408
  const processed = await processWebContent({
366
409
  url: resolvedUrl,
367
410
  body,
368
411
  contentType,
369
- responseMode: String(args.responseMode ?? "auto"),
412
+ responseMode: saveToFilePath && hasSearch ? "matches" : requestedResponseMode,
370
413
  extract: String(args.extract ?? "readability"),
371
- includeLinks: args.includeLinks === undefined
372
- ? true
373
- : parseBooleanArg(args.includeLinks),
374
- search: parseWebContentSearchOptions(args.search),
375
- maxChars,
414
+ includeLinks,
415
+ search,
416
+ maxChars: saveContentFormat ? null : maxChars,
417
+ saveContentFormat,
376
418
  });
377
419
  processedMode = processed.mode;
420
+ searchSourceTruncated = processed.searchTruncated ?? false;
378
421
  displayBody = formatWebContentResult(processed);
422
+ if (saveToFilePath && hasSearch && displayBody.length > maxChars) {
423
+ searchResultsTruncated = true;
424
+ displayBody = truncateToolText(displayBody, maxChars);
425
+ }
426
+ if (saveContentFormat) {
427
+ if (processed.fullContent === undefined) {
428
+ throw new Error("HTML extraction did not produce saved content");
429
+ }
430
+ fileContent = formatWebContentResult({
431
+ ...processed,
432
+ mode: saveContentFormat,
433
+ content: processed.fullContent,
434
+ fullContent: undefined,
435
+ matches: undefined,
436
+ totalMatches: undefined,
437
+ omittedMatches: undefined,
438
+ searchSource: undefined,
439
+ searchTruncated: undefined,
440
+ truncated: false,
441
+ });
442
+ fileContentType =
443
+ saveContentFormat === "markdown"
444
+ ? "text/markdown"
445
+ : "text/plain";
446
+ }
447
+ if (!saveToFilePath &&
448
+ isHtmlContentType &&
449
+ normalizedResponseMode !== "raw" &&
450
+ processed.truncated &&
451
+ processed.contentLength !== undefined) {
452
+ displayBody += `\n\nFull extracted content: ${processed.contentLength} characters. Retry with saveToFile (up to ${MAX_WEB_REQUEST_HTML_BYTES} bytes), then use workspace-files grep or read with offsets.`;
453
+ }
379
454
  }
380
455
  catch (err) {
381
456
  fail(`web-request post-processing error: ${err?.message ?? String(err)}`, { errorCode: httpErrorCode ?? "web_request_processing_failed" });
@@ -383,7 +458,7 @@ export function createFetchToolEntry(opts = {}) {
383
458
  console.log(`[fetch-tool] ${method} ${rawUrl} → ${response.status} (${elapsed}ms, keys: ${allUsedKeys.join(",") || "none"})`);
384
459
  if (saveToFilePath) {
385
460
  try {
386
- const { writeWorkspaceFile, SAVE_TO_FILE_MAX_BYTES, isScratchWorkspacePath, toWorkspaceFileCard, } = await import("../workspace-files/store.js");
461
+ const { writeWorkspaceFile, isScratchWorkspacePath, toWorkspaceFileCard, } = await import("../workspace-files/store.js");
387
462
  const { getRequestOrgId, getRequestUserEmail } = await import("../server/request-context.js");
388
463
  const orgId = getRequestOrgId();
389
464
  const email = getRequestUserEmail();
@@ -398,21 +473,37 @@ export function createFetchToolEntry(opts = {}) {
398
473
  if (!response.ok && !scratchPath) {
399
474
  throw new Error(`Refusing to save a failed response to durable workspace file "${saveToFilePath}" (status ${response.status}). Use a scratch/... path for diagnostics.`);
400
475
  }
401
- const meta = await writeWorkspaceFile(scope, saveToFilePath, body, contentType, {
476
+ const savedFormat = saveContentFormat ?? "raw";
477
+ const meta = await writeWorkspaceFile(scope, saveToFilePath, fileContent, fileContentType, {
402
478
  maxFileBytes: SAVE_TO_FILE_MAX_BYTES,
403
479
  });
404
- const bytes = Buffer.byteLength(body, "utf8");
405
- const preview = displayBody.slice(0, 2000);
480
+ const bytes = Buffer.byteLength(fileContent, "utf8");
481
+ const previewSource = saveContentFormat
482
+ ? fileContent
483
+ : displayBody;
484
+ const preview = previewSource.slice(0, 2000);
406
485
  if (!response.ok)
407
- fail(`HTTP ${response.status} ${response.statusText}\n\n${displayBody}`, { errorCode: `http_${response.status}` });
486
+ fail(`HTTP ${response.status} ${response.statusText}\n\n${truncateToolText(displayBody, maxChars)}`, { errorCode: `http_${response.status}` });
408
487
  return JSON.stringify({
409
488
  savedToFile: true,
410
489
  savedTo: saveToFilePath,
411
490
  status: response.status,
412
491
  bytes,
492
+ chars: fileContent.length,
413
493
  contentType,
494
+ savedFormat,
414
495
  responseMode: processedMode,
415
- preview: preview.length < displayBody.length ? `${preview}…` : preview,
496
+ ...(hasSearch
497
+ ? {
498
+ searchResults: displayBody,
499
+ searchResultsTruncated,
500
+ searchSourceTruncated,
501
+ }
502
+ : {
503
+ preview: preview.length < previewSource.length
504
+ ? `${preview}…`
505
+ : preview,
506
+ }),
416
507
  ...(scratchPath ? {} : { file: toWorkspaceFileCard(meta) }),
417
508
  });
418
509
  }
@@ -20,7 +20,9 @@ export interface WebContentProcessOptions {
20
20
  extract?: string;
21
21
  includeLinks?: boolean;
22
22
  search?: WebContentSearchOptions | null;
23
- maxChars?: number;
23
+ /** `null` keeps the complete content available for a saved workspace file. */
24
+ maxChars?: number | null;
25
+ saveContentFormat?: "markdown" | "text";
24
26
  }
25
27
  export interface WebContentLink {
26
28
  text: string;
@@ -45,6 +47,8 @@ export interface WebContentResult {
45
47
  publishedTime?: string;
46
48
  agentMetadata?: unknown[];
47
49
  content?: string;
50
+ fullContent?: string;
51
+ contentLength?: number;
48
52
  links?: WebContentLink[];
49
53
  matches?: WebContentMatch[];
50
54
  totalMatches?: number;
@@ -68,16 +68,33 @@ export async function processWebContent(options) {
68
68
  const html = isHtmlResponse(contentType, options.body);
69
69
  const mode = resolveMode(requestedMode, html, search);
70
70
  const emptyHtmlBody = html && !options.body.trim();
71
- const maxChars = normalizeBoundedNumber(options.maxChars, DEFAULT_MAX_CONTENT_CHARS, 1, 200_000);
72
- const extracted = html && !emptyHtmlBody && (mode !== "raw" || hasWebContentSearch(search))
73
- ? await extractHtml(options.body, options.url, extract, mode === "markdown" || mode === "matches")
71
+ const maxChars = options.maxChars === null
72
+ ? Number.POSITIVE_INFINITY
73
+ : normalizeBoundedNumber(options.maxChars, DEFAULT_MAX_CONTENT_CHARS, 1, 200_000);
74
+ const extracted = html &&
75
+ !emptyHtmlBody &&
76
+ (mode !== "raw" || hasWebContentSearch(search) || options.saveContentFormat)
77
+ ? await extractHtml(options.body, options.url, extract, mode === "markdown" ||
78
+ mode === "matches" ||
79
+ options.saveContentFormat === "markdown")
74
80
  : null;
75
81
  const baseContent = contentForMode(mode, options.body, extracted);
82
+ const fullContent = options.saveContentFormat
83
+ ? options.saveContentFormat === "text"
84
+ ? (extracted?.text ?? (extracted ? "" : options.body))
85
+ : extracted?.markdown ||
86
+ extracted?.text ||
87
+ (extracted ? "" : options.body)
88
+ : undefined;
76
89
  const result = {
77
90
  mode,
78
91
  extract,
79
92
  contentType,
80
93
  ...metadataFromExtraction(extracted),
94
+ ...(fullContent !== undefined ? { fullContent } : {}),
95
+ ...(mode !== "links" && mode !== "metadata"
96
+ ? { contentLength: baseContent.length }
97
+ : {}),
81
98
  };
82
99
  if (emptyHtmlBody &&
83
100
  mode !== "raw" &&
@@ -183,9 +200,10 @@ function resolveMode(requestedMode, html, search) {
183
200
  }
184
201
  function isHtmlResponse(contentType, body) {
185
202
  if (contentType) {
186
- return (contentType === "text/html" ||
187
- contentType === "application/xhtml+xml" ||
188
- contentType.endsWith("+html"));
203
+ const mediaType = contentType.toLowerCase();
204
+ return (mediaType === "text/html" ||
205
+ mediaType === "application/xhtml+xml" ||
206
+ mediaType.endsWith("+html"));
189
207
  }
190
208
  return /<!doctype html|<html[\s>]|<body[\s>]|<article[\s>]/i.test(body.slice(0, 2_000));
191
209
  }
@@ -27,39 +27,39 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
27
27
  thumbsUpRate: number;
28
28
  avgEvalScore: number;
29
29
  } | {
30
+ ok?: undefined;
30
31
  error: string;
31
32
  message: string;
32
33
  summary?: undefined;
33
34
  spans?: undefined;
34
- ok?: undefined;
35
35
  } | {
36
36
  error?: undefined;
37
+ ok?: undefined;
37
38
  message?: undefined;
38
39
  summary: import("./types.js").TraceSummary;
39
40
  spans: import("./types.js").TraceSpan[];
40
- ok?: undefined;
41
41
  } | {
42
42
  error?: undefined;
43
+ ok?: undefined;
43
44
  message?: undefined;
44
45
  summary?: undefined;
45
46
  spans?: undefined;
46
47
  traceMissing: true;
47
48
  id: string;
48
- ok?: undefined;
49
49
  } | {
50
50
  error?: undefined;
51
+ ok?: undefined;
51
52
  message?: undefined;
52
53
  summary?: undefined;
53
54
  spans?: undefined;
54
55
  traceMissing?: undefined;
55
56
  id: string;
56
- ok?: undefined;
57
57
  } | {
58
+ ok?: undefined;
58
59
  message?: undefined;
59
60
  summary?: undefined;
60
61
  spans?: undefined;
61
62
  error: any;
62
- ok?: undefined;
63
63
  } | {
64
64
  error?: undefined;
65
65
  message?: undefined;
@@ -75,10 +75,10 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
75
75
  user: "user";
76
76
  }>>;
77
77
  }, z.core.$strip>>, {
78
- message?: undefined;
79
78
  id?: undefined;
80
79
  provider?: undefined;
81
80
  deleted?: undefined;
81
+ message?: undefined;
82
82
  providers: {
83
83
  id: string;
84
84
  label: string;
@@ -93,9 +93,9 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
93
93
  registered?: undefined;
94
94
  label?: undefined;
95
95
  } | {
96
- message?: undefined;
97
96
  id?: undefined;
98
97
  deleted?: undefined;
98
+ message?: undefined;
99
99
  providers?: undefined;
100
100
  count?: undefined;
101
101
  found: boolean;
@@ -103,9 +103,9 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
103
103
  registered?: undefined;
104
104
  label?: undefined;
105
105
  } | {
106
- message?: undefined;
107
106
  provider?: undefined;
108
107
  deleted?: undefined;
108
+ message?: undefined;
109
109
  providers?: undefined;
110
110
  count?: undefined;
111
111
  found: boolean;
@@ -113,8 +113,8 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
113
113
  registered?: undefined;
114
114
  label?: undefined;
115
115
  } | {
116
- message?: undefined;
117
116
  provider?: undefined;
117
+ message?: undefined;
118
118
  providers?: undefined;
119
119
  count?: undefined;
120
120
  found?: undefined;
@@ -39,8 +39,8 @@ export declare function handleUpdateResource(event: any): Promise<import("./stor
39
39
  error: string;
40
40
  }>;
41
41
  export declare function handleDeleteResource(event: any): Promise<{
42
- error: string;
43
42
  ok?: undefined;
43
+ error: string;
44
44
  } | {
45
45
  error?: undefined;
46
46
  ok: boolean;
@@ -57,39 +57,39 @@ export declare function createWriteSecretHandler(): import("h3").EventHandlerWit
57
57
  } | {
58
58
  error: string;
59
59
  errorCode?: undefined;
60
- status?: undefined;
61
60
  ok?: undefined;
61
+ status?: undefined;
62
62
  } | {
63
63
  error: string;
64
64
  errorCode: string;
65
- status?: undefined;
66
65
  ok?: undefined;
66
+ status?: undefined;
67
67
  } | {
68
- error?: undefined;
69
68
  errorCode?: undefined;
70
69
  ok: boolean;
71
70
  status: string;
71
+ error?: undefined;
72
72
  } | {
73
+ ok?: undefined;
73
74
  error: string;
74
75
  removed?: undefined;
75
- ok?: undefined;
76
76
  } | {
77
- error?: undefined;
78
77
  ok: boolean;
79
78
  removed: boolean;
79
+ error?: undefined;
80
80
  }>>;
81
81
  export declare function createTestSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
82
+ ok?: undefined;
82
83
  error: string;
83
84
  note?: undefined;
84
- ok?: undefined;
85
85
  } | {
86
- error?: undefined;
87
86
  ok: boolean;
88
87
  note?: undefined;
89
- } | {
90
88
  error?: undefined;
89
+ } | {
91
90
  ok: boolean;
92
91
  note: string;
92
+ error?: undefined;
93
93
  } | {
94
94
  note?: undefined;
95
95
  ok: boolean;
@@ -117,11 +117,11 @@ export declare function createAdHocSecretHandler(): import("h3").EventHandlerWit
117
117
  } | {
118
118
  error: string;
119
119
  } | {
120
- error?: undefined;
121
120
  ok: boolean;
122
121
  key: string;
123
- } | {
124
122
  error?: undefined;
123
+ } | {
125
124
  ok: boolean;
126
125
  removed: boolean;
126
+ error?: undefined;
127
127
  }>>;
@@ -9,16 +9,16 @@ export declare const AGENT_ENGINE_API_KEY_ACTION_NAME = "agent-engine-api-key";
9
9
  */
10
10
  export declare function createAgentEngineDisconnectHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
11
11
  error: string;
12
- scope?: undefined;
13
12
  ok?: undefined;
13
+ scope?: undefined;
14
14
  } | {
15
15
  ok: boolean;
16
16
  error: any;
17
17
  scope?: undefined;
18
18
  } | {
19
+ error?: undefined;
19
20
  ok: boolean;
20
21
  scope: "org" | "user";
21
- error?: undefined;
22
22
  }>>;
23
23
  export interface DefaultModelSelectionRequest {
24
24
  engine: string;
@@ -1,7 +1,7 @@
1
1
  export declare function createAgentEngineOllamaModelsHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
2
2
  error: string;
3
- models?: undefined;
4
3
  ok?: undefined;
4
+ models?: undefined;
5
5
  } | {
6
6
  error?: undefined;
7
7
  ok: boolean;
@@ -1,6 +1,7 @@
1
1
  export { readComposerWebsiteSource, type ComposerWebsiteExtraction, } from "./composer-website-source.js";
2
2
  export { isAgentKitFigmaSourceAvailable, listAgentKitCapabilities, readAgentKitIntegrationIntent, type AgentKitCapabilityAppId, type AgentKitCapabilityCatalog, type AgentKitIntegrationCapability, } from "../agentkit/capabilities.js";
3
3
  export { defineAppConfig, getAppConfig, resolveAppHomePath, resetAppConfigForTests, appConfigSchema, type AppConfig, type AppConfigInput, } from "../app-config/index.js";
4
+ export { platformWaitUntil } from "./background-work.js";
4
5
  export { resolveDeployEnvironment } from "./deploy-environment.js";
5
6
  export { isTestIdentity, testIdentitySql } from "./test-identity.js";
6
7
  export { inferWorkspaceAppRootHomePath, readConfiguredWorkspaceAppHomePath, } from "../workspace-app-config.js";
@@ -16,7 +17,7 @@ export { createSSEHandler, type SSEHandlerOptions } from "./sse.js";
16
17
  export { isCredentialMembershipUnavailable } from "./credential-membership-unavailable.js";
17
18
  export { mountAuthMiddleware, autoMountAuth, registerAuthPublicPaths, getSession, isSessionResolutionUnavailable, getMcpOAuthBearerSession, logout, COOKIE_NAME, addSession, removeSession, getSessionEmail, getFrameworkSessionCookieValues, setFrameworkSessionCookie, setFirstRunOnboardingCookie, clearFrameworkSessionCookies, runAuthGuard, registerDesktopExchange, prepareDesktopOAuthBrowserBinding, matchesDesktopOAuthBrowserBinding, DESKTOP_OAUTH_BROWSER_BINDING_COOKIE, setDesktopExchange, setDesktopExchangeError, safeReturnPath, type DesktopExchangeErrorPayload, type AuthSession, type AuthOptions, } from "./auth.js";
18
19
  export { handleIdentitySso, ensureIdentityUser, getIdentityHubUrl, isIdentitySsoEnabled, isIdentitySsoBypassPath, identitySsoLoginButtonHtml, IDENTITY_SSO_BOOTSTRAP_ACTIVATE_PATH, IDENTITY_SSO_BOOTSTRAP_BINDING_COOKIE, clearIdentitySsoBootstrapBindingCookie, getIdentitySsoBootstrapBindingCookie, setIdentitySsoBootstrapBindingCookie, IDENTITY_SSO_PROVIDER_ID, IDENTITY_SSO_SCOPE, IDENTITY_SSO_DESKTOP_COMPLETE_PATH, } from "./identity-sso.js";
19
- export { createBetterAuthSessionForEmail, ensureGoogleAuthIdentity, hasGoogleAuthIdentity, setBetterAuthSessionCookie, } from "./better-auth-instance.js";
20
+ export { createBetterAuthSessionForEmail, ensureGoogleAuthIdentity, getBetterAuthUserIdForEmail, hasGoogleAuthIdentity, setBetterAuthSessionCookie, } from "./better-auth-instance.js";
20
21
  export { setIdentityGoogleAuthCookie } from "./identity-auth-provider.js";
21
22
  export { requireEnvKey, type MissingKeyResponse } from "./missing-key.js";
22
23
  export { assertCurrentRequestUserIsOrgAdmin, currentRequestUserIsOrgAdmin, } from "./org-admin.js";
@@ -1,6 +1,7 @@
1
1
  export { readComposerWebsiteSource, } from "./composer-website-source.js";
2
2
  export { isAgentKitFigmaSourceAvailable, listAgentKitCapabilities, readAgentKitIntegrationIntent, } from "../agentkit/capabilities.js";
3
3
  export { defineAppConfig, getAppConfig, resolveAppHomePath, resetAppConfigForTests, appConfigSchema, } from "../app-config/index.js";
4
+ export { platformWaitUntil } from "./background-work.js";
4
5
  export { resolveDeployEnvironment } from "./deploy-environment.js";
5
6
  export { isTestIdentity, testIdentitySql } from "./test-identity.js";
6
7
  export { inferWorkspaceAppRootHomePath, readConfiguredWorkspaceAppHomePath, } from "../workspace-app-config.js";
@@ -16,7 +17,7 @@ export { createSSEHandler } from "./sse.js";
16
17
  export { isCredentialMembershipUnavailable } from "./credential-membership-unavailable.js";
17
18
  export { mountAuthMiddleware, autoMountAuth, registerAuthPublicPaths, getSession, isSessionResolutionUnavailable, getMcpOAuthBearerSession, logout, COOKIE_NAME, addSession, removeSession, getSessionEmail, getFrameworkSessionCookieValues, setFrameworkSessionCookie, setFirstRunOnboardingCookie, clearFrameworkSessionCookies, runAuthGuard, registerDesktopExchange, prepareDesktopOAuthBrowserBinding, matchesDesktopOAuthBrowserBinding, DESKTOP_OAUTH_BROWSER_BINDING_COOKIE, setDesktopExchange, setDesktopExchangeError, safeReturnPath, } from "./auth.js";
18
19
  export { handleIdentitySso, ensureIdentityUser, getIdentityHubUrl, isIdentitySsoEnabled, isIdentitySsoBypassPath, identitySsoLoginButtonHtml, IDENTITY_SSO_BOOTSTRAP_ACTIVATE_PATH, IDENTITY_SSO_BOOTSTRAP_BINDING_COOKIE, clearIdentitySsoBootstrapBindingCookie, getIdentitySsoBootstrapBindingCookie, setIdentitySsoBootstrapBindingCookie, IDENTITY_SSO_PROVIDER_ID, IDENTITY_SSO_SCOPE, IDENTITY_SSO_DESKTOP_COMPLETE_PATH, } from "./identity-sso.js";
19
- export { createBetterAuthSessionForEmail, ensureGoogleAuthIdentity, hasGoogleAuthIdentity, setBetterAuthSessionCookie, } from "./better-auth-instance.js";
20
+ export { createBetterAuthSessionForEmail, ensureGoogleAuthIdentity, getBetterAuthUserIdForEmail, hasGoogleAuthIdentity, setBetterAuthSessionCookie, } from "./better-auth-instance.js";
20
21
  export { setIdentityGoogleAuthCookie } from "./identity-auth-provider.js";
21
22
  export { requireEnvKey } from "./missing-key.js";
22
23
  export { assertCurrentRequestUserIsOrgAdmin, currentRequestUserIsOrgAdmin, } from "./org-admin.js";
@@ -50,5 +50,5 @@ export const SELECTION_CONTEXT_UNAVAILABLE_NOTE = wrapNote("Selected text unavai
50
50
  /** `partial` when another source did deliver context this turn. */
51
51
  export function preloadedContextUnavailableNote(status, options = {}) {
52
52
  const reason = status === "timed_out" ? "timed out" : "failed";
53
- return wrapNote(`${options.partial ? "Some preloaded context" : "Preloaded context"} was unavailable this turn (${reason}); search for anything relevant with the available tools before answering.`);
53
+ return wrapNote(`${options.partial ? "Some preloaded context" : "Preloaded context"} was unavailable this turn (${reason}); if the request depends on workspace knowledge, such as prior work, team docs, brand defaults, or people, search for it with the available tools before answering; otherwise continue without it.`);
54
54
  }
@@ -2,7 +2,6 @@ declare const _default: import("../../action.js").ActionDefinition<{
2
2
  resourceType: string;
3
3
  resourceId: string;
4
4
  }, {
5
- role?: undefined;
6
5
  ownerEmail: any;
7
6
  visibility: any;
8
7
  shares: any[];
@@ -12,6 +11,7 @@ declare const _default: import("../../action.js").ActionDefinition<{
12
11
  supportsGroupShares?: boolean;
13
12
  };
14
13
  orgId?: undefined;
14
+ role?: undefined;
15
15
  agentReadable?: undefined;
16
16
  } | {
17
17
  ownerEmail: string;
@@ -1,5 +1,6 @@
1
1
  export { track, identify, flushTracking, registerTrackingProvider, unregisterTrackingProvider, listTrackingProviders, type TrackingMeta, type TrackingSource, } from "./registry.js";
2
2
  export { registerBuiltinProviders } from "./providers.js";
3
+ export { readAnalyticsAnonymousId } from "../server/attribution.js";
3
4
  export { AGENT_NATIVE_ACTION_EVENTS, AGENT_NATIVE_LIFECYCLE_EVENTS, trackActionEvent, trackLifecycleEvent, type AgentNativeActionEventName, type AgentNativeLifecycleEventName, } from "./lifecycle.js";
4
5
  export { captureException, type TrackingExceptionContext, type TrackingExceptionLevel, } from "./error-capture.js";
5
6
  export { classifyTrackingFailure, type TrackingFailureCategory, } from "./failure-category.js";
@@ -1,5 +1,6 @@
1
1
  export { track, identify, flushTracking, registerTrackingProvider, unregisterTrackingProvider, listTrackingProviders, } from "./registry.js";
2
2
  export { registerBuiltinProviders } from "./providers.js";
3
+ export { readAnalyticsAnonymousId } from "../server/attribution.js";
3
4
  export { AGENT_NATIVE_ACTION_EVENTS, AGENT_NATIVE_LIFECYCLE_EVENTS, trackActionEvent, trackLifecycleEvent, } from "./lifecycle.js";
4
5
  export { captureException, } from "./error-capture.js";
5
6
  export { classifyTrackingFailure, } from "./failure-category.js";
@@ -29,8 +29,8 @@ declare const _default: import("../../action.js").ActionDefinition<{
29
29
  executionEngine?: undefined;
30
30
  executionCwd?: undefined;
31
31
  } | {
32
- created?: undefined;
33
32
  scope?: undefined;
33
+ created?: undefined;
34
34
  triggerType?: undefined;
35
35
  event?: undefined;
36
36
  deleted: boolean;
@@ -45,8 +45,8 @@ declare const _default: import("../../action.js").ActionDefinition<{
45
45
  nextRun?: undefined;
46
46
  webhookPath?: undefined;
47
47
  } | {
48
- created?: undefined;
49
48
  scope?: undefined;
49
+ created?: undefined;
50
50
  triggerType?: undefined;
51
51
  event?: undefined;
52
52
  deleted?: undefined;
@@ -0,0 +1,3 @@
1
+ export declare const MAX_FILE_BYTES: number;
2
+ export declare const MAX_SCOPE_BYTES: number;
3
+ export declare const SAVE_TO_FILE_MAX_BYTES: number;
@@ -0,0 +1,3 @@
1
+ export const MAX_FILE_BYTES = 2 * 1024 * 1024;
2
+ export const MAX_SCOPE_BYTES = 200 * 1024 * 1024;
3
+ export const SAVE_TO_FILE_MAX_BYTES = 20 * 1024 * 1024;
@@ -1,6 +1,5 @@
1
- export declare const MAX_FILE_BYTES: number;
2
- export declare const MAX_SCOPE_BYTES: number;
3
- export declare const SAVE_TO_FILE_MAX_BYTES: number;
1
+ import { MAX_FILE_BYTES, MAX_SCOPE_BYTES, SAVE_TO_FILE_MAX_BYTES } from "./constants.js";
2
+ export { MAX_FILE_BYTES, MAX_SCOPE_BYTES, SAVE_TO_FILE_MAX_BYTES };
4
3
  export interface WorkspaceFilesScope {
5
4
  scope: "user" | "org";
6
5
  scopeId: string;
@@ -2,9 +2,8 @@ import { getOrgRoleForEmail } from "../mcp/actions/service-token-access.js";
2
2
  import { canManageOrg } from "../org/permissions.js";
3
3
  import { SHARED_OWNER, isLegacyOrganizationWorkspaceFile, sharedResourceOwner, resourceDeleteIfCurrent, resourceGetByPath, resourceList, resourcePut, } from "../resources/store.js";
4
4
  import { getRequestUserEmail } from "../server/request-context.js";
5
- export const MAX_FILE_BYTES = 2 * 1024 * 1024;
6
- export const MAX_SCOPE_BYTES = 200 * 1024 * 1024;
7
- export const SAVE_TO_FILE_MAX_BYTES = 20 * 1024 * 1024;
5
+ import { MAX_FILE_BYTES, MAX_SCOPE_BYTES, SAVE_TO_FILE_MAX_BYTES, } from "./constants.js";
6
+ export { MAX_FILE_BYTES, MAX_SCOPE_BYTES, SAVE_TO_FILE_MAX_BYTES };
8
7
  function ownerForScope(scope) {
9
8
  return scope.scope === "org"
10
9
  ? sharedResourceOwner(scope.scopeId)
@@ -290,19 +290,25 @@ Placeholders are resolved **server-side** after the agent emits the tool call
290
290
 
291
291
  ### Parameters {#web-request-params}
292
292
 
293
- | Parameter | Type | Default | Description |
294
- | -------------- | ------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
295
- | `url` | string | — | Full URL. May contain `${keys.NAME}` references. |
296
- | `method` | string | `GET` | HTTP method (GET, POST, PUT, PATCH, DELETE, HEAD). |
297
- | `headers` | string | `{}` | JSON object of headers. May contain `${keys.NAME}`. |
298
- | `body` | string | — | Request body. May contain `${keys.NAME}`. |
299
- | `timeout_ms` | number | 15000 | Timeout in milliseconds (max 30000). |
300
- | `maxChars` | number | 32000 | Maximum response characters to return (max 200000). Raise it to read a large page or dataset in full. |
301
- | `responseMode` | string | `auto` | How to return the response: `auto` (HTML becomes markdown, JSON/text stays raw), `raw`, `text`, `markdown`, `links`, `metadata`, or `matches` (with `search`). |
302
- | `extract` | string | `readability` | HTML extraction strategy: `readability`, `all-visible` (full visible body text), or `none` (full document). |
303
- | `includeLinks` | boolean | `true` | Whether extracted HTML responses include a compact links list. |
304
- | `search` | object | — | Optional post-fetch search over the extracted content: `{ query, queries, terms, regex, regexFlags, source, maxMatches, contextChars, caseSensitive }`. |
305
- | `saveToFile` | string | — | Workspace file path to save the full response to instead of returning it inline — useful for large pages or API responses. |
293
+ | Parameter | Type | Default | Description |
294
+ | -------------- | ------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
295
+ | `url` | string | — | Full URL. May contain `${keys.NAME}` references. |
296
+ | `method` | string | `GET` | HTTP method (GET, POST, PUT, PATCH, DELETE, HEAD). |
297
+ | `headers` | string | `{}` | JSON object of headers. May contain `${keys.NAME}`. |
298
+ | `body` | string | — | Request body. May contain `${keys.NAME}`. |
299
+ | `timeout_ms` | number | 15000 | Timeout in milliseconds (max 30000). |
300
+ | `maxChars` | number | 32000 | Maximum response-body characters returned in the agent's context (max 200000). It caps body characters, not the whole formatted tool response, so optional metadata and links can add characters. This does not limit a saved file. |
301
+ | `responseMode` | string | `auto` | How to return the response: `auto` (HTML becomes markdown, JSON/text stays raw), `raw`, `text`, `markdown`, `links`, `metadata`, or `matches` with `search`. |
302
+ | `extract` | string | `readability` | HTML extraction strategy: `readability`, `all-visible` (full visible body text), or `none` (full document). |
303
+ | `includeLinks` | boolean | varies | Whether extracted HTML responses include a compact links list. Defaults to `true` for inline responses and `false` when `saveToFile` is set. |
304
+ | `search` | object | — | Optional post-fetch search over the extracted content: `{ query, queries, terms, regex, regexFlags, source, maxMatches, contextChars, caseSensitive }`. |
305
+ | `saveToFile` | string | — | Workspace file path for saving the full response, up to 20 MiB. With `search`, returns full matches up to `maxChars`; otherwise returns a 2,000-character preview. |
306
+
307
+ By default, `maxChars` caps only response-body characters returned in the agent's context. It does not cap the whole formatted tool response, so optional metadata and links can add characters. Inline responses are limited to 1 MiB, except extracted HTML or XHTML, which can be up to 20 MiB. Raw HTML and other inline responses remain limited to 1 MiB.
308
+
309
+ `saveToFile` supports responses up to 20 MiB. HTML or XHTML saved with a non-raw `responseMode` is written as extracted Markdown or text. Raw mode and non-HTML responses are saved as decoded response body text without HTML extraction. When `saveToFile` is used with `search`, the tool returns full search matches bounded by `maxChars`. Without `search`, it returns a 2,000-character preview. `searchResultsTruncated` means `maxChars` cut the formatted result. `searchSourceTruncated` means the tool scanned only the first 500,000 characters of the selected search source.
310
+
311
+ An over-limit error reports the observed size and applicable cap, and explains how to recover. Use `saveToFile` for responses up to 20 MiB, or request a smaller response if it exceeds that limit.
306
312
 
307
313
  ## Keys {#keys}
308
314
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.208.0",
3
+ "version": "0.208.1-nightly-20261013212700",
4
4
  "description": "The agentic application framework for building autonomous agents with intuitive UIs",
5
5
  "homepage": "https://github.com/BuilderIO/agent-native#readme",
6
6
  "bugs": {
@@ -703,7 +703,7 @@
703
703
  "yaml": "^2.9.0",
704
704
  "yjs": "^13.6.33",
705
705
  "zod": "^4.3.6",
706
- "@agent-native/agentkit": "^0.208.0",
706
+ "@agent-native/agentkit": "^0.208.1-nightly-20261013212700",
707
707
  "@agent-native/recap-cli": "^0.5.71"
708
708
  },
709
709
  "devDependencies": {
@@ -774,7 +774,7 @@
774
774
  "xlsx": "^0.18.5"
775
775
  },
776
776
  "peerDependencies": {
777
- "@agent-native/core-corpus": ">=0.196.0",
777
+ "@agent-native/core-corpus": "0.208.1-nightly-20261013212700",
778
778
  "@ai-sdk/anthropic": ">=3",
779
779
  "@ai-sdk/cohere": ">=3",
780
780
  "@ai-sdk/google": ">=3",