@agent-native/core 0.220.0 → 0.220.1-nightly-20261024152749

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/action.d.ts CHANGED
@@ -23,8 +23,26 @@ export interface ActionAutomationContext {
23
23
  triggerName: string;
24
24
  policyId?: string;
25
25
  }
26
+ export declare const DEFAULT_TOOL_RESULT_CHARS = 50000;
27
+ /**
28
+ * The most characters of a pretty-printed result the agent keeps for a call:
29
+ * the action's own limit, else the agent-wide limit, else the default, bounded
30
+ * by any hard limit. The agent loop and actions that fit their own results
31
+ * resolve it here so they cannot disagree.
32
+ */
33
+ export declare function resolveToolMaxResultChars(action: {
34
+ maxResultChars?: number;
35
+ }, limits?: {
36
+ maxResultChars?: number;
37
+ hardMaxResultChars?: number;
38
+ }): number;
26
39
  export interface ActionRunContext {
27
40
  send?: (event: AgentChatEvent) => void;
41
+ /**
42
+ * Result cap the agent loop enforces on this call, from
43
+ * `resolveToolMaxResultChars`. Absent outside the agent loop.
44
+ */
45
+ maxResultChars?: number;
28
46
  userEmail?: string;
29
47
  /**
30
48
  * Request headers for server-only actions that must delegate to an
package/dist/action.js CHANGED
@@ -48,6 +48,21 @@ export async function recordInvalidActionCall(entry, args, ctx, error) {
48
48
  }
49
49
  return true;
50
50
  }
51
+ export const DEFAULT_TOOL_RESULT_CHARS = 50_000;
52
+ /**
53
+ * The most characters of a pretty-printed result the agent keeps for a call:
54
+ * the action's own limit, else the agent-wide limit, else the default, bounded
55
+ * by any hard limit. The agent loop and actions that fit their own results
56
+ * resolve it here so they cannot disagree.
57
+ */
58
+ export function resolveToolMaxResultChars(action, limits) {
59
+ const configured = action.maxResultChars ??
60
+ limits?.maxResultChars ??
61
+ DEFAULT_TOOL_RESULT_CHARS;
62
+ return typeof limits?.hardMaxResultChars === "number"
63
+ ? Math.min(configured, limits.hardMaxResultChars)
64
+ : configured;
65
+ }
51
66
  export class ActionContractError extends Error {
52
67
  actionContractError = true;
53
68
  errorCode;
@@ -9,7 +9,7 @@ import { parseA2AAgentActivityPart } from "../a2a/activity.js";
9
9
  import { actionCallIsRetrySafe, actionChangeResource, classifyActionCall, } from "../action-call-classification.js";
10
10
  import { runOutsideActionInvocation, vouchedOutcome } from "../action-frame.js";
11
11
  import { ACTION_CHAT_UI_RECORD_CHANGE_RENDERER, normalizeActionChangeResult, } from "../action-ui.js";
12
- import { AgentConnectionRequiredError, describeToolParameterSignature, isActionContractError, isActionHiddenFromEveryAgentSurface, isAgentActionStopError, isAgentConnectionRequiredError, actionCallIntent, ActionInputValidationError, invocationEffect, runPreparedActionCall, boundActionCallIntent, preparedCallArguments, preparedCallNeedsApproval, recordInvalidActionCall, stripUnsupportedSchemaKeywords, } from "../action.js";
12
+ import { AgentConnectionRequiredError, describeToolParameterSignature, isActionContractError, isActionHiddenFromEveryAgentSurface, isAgentActionStopError, isAgentConnectionRequiredError, actionCallIntent, ActionInputValidationError, invocationEffect, runPreparedActionCall, boundActionCallIntent, preparedCallArguments, preparedCallNeedsApproval, recordInvalidActionCall, resolveToolMaxResultChars, stripUnsupportedSchemaKeywords, } from "../action.js";
13
13
  import { getAppConfig } from "../app-config/index.js";
14
14
  import { MAX_BACKGROUND_RUN_CONTINUATIONS, MAX_CONSECUTIVE_NO_PROGRESS_CONTINUATIONS, MAX_TURN_WALL_CLOCK_MS, } from "../app-config/run-lifecycle-invariants.js";
15
15
  import { readAppState } from "../application-state/script-helpers.js";
@@ -1239,6 +1239,7 @@ const VISIBLE_RETRY_THRESHOLD_MS = 10_000;
1239
1239
  */
1240
1240
  const FOREGROUND_FIRST_MODEL_EVENT_TIMEOUT_MS = 25_000;
1241
1241
  const EMPTY_FINAL_RESPONSE_RETRY_LIMIT = 2;
1242
+ const MAX_CONSECUTIVE_MAX_TOKENS_CONTINUATIONS = 3;
1242
1243
  const TRUNCATED_TOOL_CALL_RETRY_LIMIT = 2;
1243
1244
  const MAIN_CHAT_INTERNAL_CONTINUATION_LIMIT = 6;
1244
1245
  const RUN_BUDGET_EXHAUSTED_ERROR_CODE = "run_budget_exhausted";
@@ -5151,6 +5152,8 @@ export async function runAgentLoop(opts) {
5151
5152
  const blockedA2ATargets = new Map();
5152
5153
  let finalGuardRetries = 0;
5153
5154
  let emptyFinalResponseRetries = 0;
5155
+ let preserveReasoningEffortForEmptyResponseRetries = false;
5156
+ let consecutiveMaxTokensContinuations = 0;
5154
5157
  let truncatedToolCallRetries = 0;
5155
5158
  let iterations = 0;
5156
5159
  let endedAtLoopLimit = false;
@@ -5888,21 +5891,42 @@ export async function runAgentLoop(opts) {
5888
5891
  send({ type: "text", text });
5889
5892
  };
5890
5893
  if (toolCallParts.length === 0) {
5894
+ const hasEmptyFinalResponse = collectTextParts(assistantContentForHistory).trim().length === 0 &&
5895
+ streamedAssistantText.trim().length === 0;
5891
5896
  if (followUpResults.length > 0)
5892
5897
  messages.push({ role: "user", content: followUpResults });
5893
5898
  if (terminalStopReason === "max_tokens") {
5894
- flushUnstreamedAssistantText();
5895
- appendAgentLoopContinuation(messages, "max_tokens");
5896
- continue;
5899
+ if (hasEmptyFinalResponse) {
5900
+ preserveReasoningEffortForEmptyResponseRetries = true;
5901
+ }
5902
+ else if (consecutiveMaxTokensContinuations <
5903
+ MAX_CONSECUTIVE_MAX_TOKENS_CONTINUATIONS) {
5904
+ consecutiveMaxTokensContinuations += 1;
5905
+ flushUnstreamedAssistantText();
5906
+ appendAgentLoopContinuation(messages, "max_tokens");
5907
+ continue;
5908
+ }
5909
+ else {
5910
+ flushUnstreamedAssistantText();
5911
+ terminalActionStop = {
5912
+ message: "This response kept reaching the output-token limit after several continuations and may be incomplete. Ask for a shorter or narrower answer.",
5913
+ errorCode: "max_tokens_continuation_limit",
5914
+ };
5915
+ sendTerminalActionStop(terminalActionStop);
5916
+ break;
5917
+ }
5918
+ }
5919
+ else {
5920
+ consecutiveMaxTokensContinuations = 0;
5897
5921
  }
5898
- const hasEmptyFinalResponse = collectTextParts(assistantContentForHistory).trim().length === 0 &&
5899
- streamedAssistantText.trim().length === 0;
5900
5922
  if (followUpResults.length > 0 && terminalStopReason === "tool_use") {
5901
5923
  if (streamedAssistantText)
5902
5924
  send({ type: "clear" });
5903
5925
  continue;
5904
5926
  }
5905
- if (followUpResults.length > 0 && hasEmptyFinalResponse) {
5927
+ if (followUpResults.length > 0 &&
5928
+ hasEmptyFinalResponse &&
5929
+ terminalStopReason !== "max_tokens") {
5906
5930
  flushUnstreamedAssistantText();
5907
5931
  continue;
5908
5932
  }
@@ -5910,9 +5934,13 @@ export async function runAgentLoop(opts) {
5910
5934
  completedFollowUpSuggestions = undefined;
5911
5935
  if (emptyFinalResponseRetries < EMPTY_FINAL_RESPONSE_RETRY_LIMIT) {
5912
5936
  emptyFinalResponseRetries += 1;
5937
+ const previousMaxOutputTokens = resolveMaxOutputTokensForEngine(engine.name, effectiveMaxOutputTokens, model);
5913
5938
  effectiveMaxOutputTokens =
5914
5939
  resolveEmptyResponseRetryMaxOutputTokens(model);
5915
- effectiveReasoningEffort = stepDownReasoningEffort(effectiveReasoningEffort);
5940
+ if (!preserveReasoningEffortForEmptyResponseRetries ||
5941
+ effectiveMaxOutputTokens <= previousMaxOutputTokens) {
5942
+ effectiveReasoningEffort = stepDownReasoningEffort(effectiveReasoningEffort);
5943
+ }
5916
5944
  send({ type: "clear" });
5917
5945
  appendAgentLoopContinuation(messages, "max_tokens");
5918
5946
  continue;
@@ -6006,6 +6034,8 @@ export async function runAgentLoop(opts) {
6006
6034
  }
6007
6035
  }
6008
6036
  emptyFinalResponseRetries = 0;
6037
+ preserveReasoningEffortForEmptyResponseRetries = false;
6038
+ consecutiveMaxTokensContinuations = 0;
6009
6039
  effectiveMaxOutputTokens = opts.maxOutputTokens;
6010
6040
  effectiveReasoningEffort = opts.reasoningEffort;
6011
6041
  if (loopBreakerCloseout)
@@ -6014,6 +6044,8 @@ export async function runAgentLoop(opts) {
6014
6044
  }
6015
6045
  finalGuardRetries = 0;
6016
6046
  emptyFinalResponseRetries = 0;
6047
+ preserveReasoningEffortForEmptyResponseRetries = false;
6048
+ consecutiveMaxTokensContinuations = 0;
6017
6049
  const toolCallTruncatedByOutputCap = terminalStopReason === "max_tokens";
6018
6050
  if (toolCallTruncatedByOutputCap) {
6019
6051
  if (truncatedToolCallRetries < TRUNCATED_TOOL_CALL_RETRY_LIMIT) {
@@ -6600,14 +6632,8 @@ export async function runAgentLoop(opts) {
6600
6632
  };
6601
6633
  }
6602
6634
  }
6603
- const DEFAULT_TOOL_RESULT_CHARS = 50_000;
6604
6635
  const toolTimeoutMs = toolTimeoutMsFor(actionEntry);
6605
- const configuredToolMaxResultChars = actionEntry.maxResultChars ??
6606
- opts.toolLimits?.maxResultChars ??
6607
- DEFAULT_TOOL_RESULT_CHARS;
6608
- const toolMaxResultChars = typeof opts.toolLimits?.hardMaxResultChars === "number"
6609
- ? Math.min(configuredToolMaxResultChars, opts.toolLimits.hardMaxResultChars)
6610
- : configuredToolMaxResultChars;
6636
+ const toolMaxResultChars = resolveToolMaxResultChars(actionEntry, opts.toolLimits);
6611
6637
  const executionFence = currentExecutionFence();
6612
6638
  const logicalExecutionStepId = logicalExecutionStepIds.get(originalToolCall);
6613
6639
  const interruptedStep = executionFence && toolCallJournal
@@ -7201,6 +7227,7 @@ export async function runAgentLoop(opts) {
7201
7227
  signal,
7202
7228
  actionName: toolCall.name,
7203
7229
  toolCallId: toolCall.id,
7230
+ maxResultChars: toolMaxResultChars,
7204
7231
  ...(wasApproved ? { approvedToolCallKey: approvalKey } : {}),
7205
7232
  };
7206
7233
  actionInvoked = true;
@@ -5,6 +5,7 @@ import { recordAgentRun } from "../observability/metrics.js";
5
5
  import { parseServiceIdentityEmail } from "../org/service-identity.js";
6
6
  import { ServicePrincipalRefusedError } from "../org/service-principal-guard.js";
7
7
  import { captureError } from "../server/capture-error.js";
8
+ import { isNonProgressEventType } from "../shared/progress-events.js";
8
9
  import { EXECUTION_LEASE_LOST_ABORT_REASON } from "./abort-reasons.js";
9
10
  import { isLlmCredentialError, LLM_MISSING_CREDENTIALS_ERROR_CODE, LLM_MISSING_CREDENTIALS_MESSAGE, } from "./engine/credential-errors.js";
10
11
  import { classifyTerminalErrorCode, describeErrorWithCauses, isProviderConnectionError, } from "./engine/error-detail.js";
@@ -86,6 +87,7 @@ export function resolveRunNoProgressTimeoutMs(params) {
86
87
  return ceiling;
87
88
  return override === 0 ? 0 : Math.min(override, ceiling);
88
89
  }
90
+ const MAX_CONSECUTIVE_EMPTY_MODEL_CALLS = 3;
89
91
  function inFlightWorkDelta(event) {
90
92
  switch (event.type) {
91
93
  case "tool_start":
@@ -577,6 +579,7 @@ export function startRun(runId, threadId, runFn, onComplete, options) {
577
579
  chunkAbort = new AbortController();
578
580
  armChunkSoftTimeout();
579
581
  lastRealProgressAt = Date.now();
582
+ consecutiveEmptyModelCalls = 0;
580
583
  return chunkAbort.signal;
581
584
  },
582
585
  };
@@ -888,8 +891,6 @@ export function startRun(runId, threadId, runFn, onComplete, options) {
888
891
  scheduleProgressWrite();
889
892
  };
890
893
  const shouldBumpProgressForEvent = (event) => {
891
- if (event.type === "stream_keepalive")
892
- return false;
893
894
  if (event.type === "clear") {
894
895
  for (const [key, bytes] of preparingActivityBytes) {
895
896
  const toolKey = preparingActivityTools.get(key) ?? key;
@@ -897,8 +898,9 @@ export function startRun(runId, threadId, runFn, onComplete, options) {
897
898
  }
898
899
  preparingActivityBytes.clear();
899
900
  preparingActivityTools.clear();
900
- return false;
901
901
  }
902
+ if (isNonProgressEventType(event.type))
903
+ return false;
902
904
  if (event.type === "activity" && isPreparingActionActivityEvent(event)) {
903
905
  const toolKey = event.tool?.trim() || event.label.trim();
904
906
  const activityKey = `${toolKey}:${event.id?.trim() || "no-id"}`;
@@ -954,6 +956,11 @@ export function startRun(runId, threadId, runFn, onComplete, options) {
954
956
  return true;
955
957
  };
956
958
  let lastRealProgressAt = Date.now();
959
+ // Every completed model call refreshes the silence clock (its result is still
960
+ // being processed), so a loop of calls that produce no text, tool call or
961
+ // result is caught by counting consecutive empty calls instead.
962
+ let consecutiveEmptyModelCalls = 0;
963
+ let modelCallHadProgress = false;
957
964
  let inFlightWorkCount = 0;
958
965
  let inFlightMarkerSince = null;
959
966
  let inFlightMarkerWrite = Promise.resolve();
@@ -1163,7 +1170,8 @@ export function startRun(runId, threadId, runFn, onComplete, options) {
1163
1170
  if (inFlightWorkCount > 0)
1164
1171
  return;
1165
1172
  const silentForMs = Date.now() - lastRealProgressAt;
1166
- if (silentForMs < noProgressTimeoutMs)
1173
+ const emptyCallLoop = consecutiveEmptyModelCalls > MAX_CONSECUTIVE_EMPTY_MODEL_CALLS;
1174
+ if (!emptyCallLoop && silentForMs < noProgressTimeoutMs)
1167
1175
  return;
1168
1176
  const lastEventType = run.events.at(-1)?.event.type;
1169
1177
  if (!chunkAbort) {
@@ -1410,8 +1418,20 @@ export function startRun(runId, threadId, runFn, onComplete, options) {
1410
1418
  trackInFlightWork(runEvent.event);
1411
1419
  if (shouldBumpProgressForEvent(runEvent.event)) {
1412
1420
  lastRealProgressAt = Date.now();
1421
+ consecutiveEmptyModelCalls = 0;
1422
+ modelCallHadProgress = true;
1413
1423
  bumpProgressIfDue();
1414
1424
  }
1425
+ else if (runEvent.event.type === "model_stream") {
1426
+ if (runEvent.event.status === "start") {
1427
+ modelCallHadProgress = false;
1428
+ }
1429
+ else {
1430
+ lastRealProgressAt = Date.now();
1431
+ if (!modelCallHadProgress)
1432
+ consecutiveEmptyModelCalls++;
1433
+ }
1434
+ }
1415
1435
  };
1416
1436
  const queueRunEvent = (pendingWrite, runEvent) => {
1417
1437
  let resolve;
@@ -297,6 +297,13 @@ export type PreparingActionState = {
297
297
  entries?: Map<string, PreparingActionEntry>;
298
298
  toolEntries?: Map<string, PreparingActionEntry>;
299
299
  };
300
+ /**
301
+ * The browser's durable liveness cursor must mirror the server's
302
+ * `last_progress_at` predicate. The stream watchdog intentionally has a
303
+ * broader background policy, so it cannot be reused for this cursor: raw
304
+ * keepalives and repeated zero-byte preparation are not proof of real work.
305
+ */
306
+ export declare function isDurableProgressEvent(ev: SSEEvent, actionPreparationProgress?: boolean): boolean;
300
307
  /**
301
308
  * Locate the tool call an `approval_required` event refers to.
302
309
  *
@@ -1,6 +1,7 @@
1
1
  import { LLM_MISSING_CREDENTIALS_ERROR_CODE, LLM_MISSING_CREDENTIALS_MESSAGE, } from "../agent/engine/credential-errors.js";
2
2
  import { BUILDER_GATEWAY_INTERNAL_ERROR_CODE, PROVIDER_TRANSIENT_REJECTION_ERROR_CODE, } from "../agent/engine/error-detail.js";
3
3
  import { normalizeConnectRequiredResult } from "../shared/connect-required.js";
4
+ import { isNonProgressEventType } from "../shared/progress-events.js";
4
5
  import { toolInputsMatch } from "../shared/tool-inputs.js";
5
6
  import { emitChatFirstOpenApp } from "./chat-first-state.js";
6
7
  import { formatChatErrorText, normalizeChatError } from "./error-format.js";
@@ -205,8 +206,8 @@ function isMeaningfulProgressEvent(ev, actionPreparationProgress, options) {
205
206
  * broader background policy, so it cannot be reused for this cursor: raw
206
207
  * keepalives and repeated zero-byte preparation are not proof of real work.
207
208
  */
208
- function isDurableProgressEvent(ev, actionPreparationProgress) {
209
- if (ev.type === "stream_keepalive" || ev.type === "clear")
209
+ export function isDurableProgressEvent(ev, actionPreparationProgress) {
210
+ if (isNonProgressEventType(ev.type))
210
211
  return false;
211
212
  if (ev.type === "activity" && isPreparingActionActivity(ev)) {
212
213
  return actionPreparationProgress === true;
@@ -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
- ok?: undefined;
17
16
  error: string;
17
+ ok?: undefined;
18
18
  } | {
19
19
  error?: undefined;
20
20
  liveCopy: "derived" | "not_derived" | "unchanged";
@@ -16,6 +16,6 @@ export declare function createNotificationsHandler(): import("h3").EventHandlerW
16
16
  } | {
17
17
  count?: undefined;
18
18
  updated?: undefined;
19
- ok: boolean;
20
19
  error?: undefined;
20
+ ok: boolean;
21
21
  }>>;
@@ -27,43 +27,43 @@ 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
+ error?: undefined;
37
+ ok?: undefined;
36
38
  message?: undefined;
37
39
  summary: import("./types.js").TraceSummary;
38
40
  spans: import("./types.js").TraceSpan[];
41
+ } | {
39
42
  error?: undefined;
40
43
  ok?: undefined;
41
- } | {
42
44
  message?: undefined;
43
45
  summary?: undefined;
44
46
  spans?: undefined;
45
47
  traceMissing: true;
46
48
  id: string;
49
+ } | {
47
50
  error?: undefined;
48
51
  ok?: undefined;
49
- } | {
50
52
  message?: undefined;
51
53
  summary?: undefined;
52
54
  spans?: undefined;
53
55
  traceMissing?: undefined;
54
56
  id: string;
55
- error?: undefined;
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
+ error?: undefined;
64
65
  message?: undefined;
65
66
  summary?: undefined;
66
67
  spans?: undefined;
67
- error?: undefined;
68
68
  ok: boolean;
69
69
  }>>;
@@ -73,9 +73,9 @@ export declare const setWorkspaceAppDefaultVisibilityHandler: import("h3").Event
73
73
  visibility: WorkspaceAppDefaultVisibility;
74
74
  }>>;
75
75
  export declare const createOrgHandler: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
76
+ name?: undefined;
76
77
  success: boolean;
77
78
  id?: undefined;
78
- name?: undefined;
79
79
  role?: undefined;
80
80
  } | {
81
81
  success?: undefined;
@@ -75,7 +75,6 @@ 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
  deleted?: undefined;
81
80
  provider?: undefined;
@@ -92,8 +91,8 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
92
91
  found?: undefined;
93
92
  registered?: undefined;
94
93
  label?: undefined;
95
- } | {
96
94
  message?: undefined;
95
+ } | {
97
96
  id?: undefined;
98
97
  deleted?: undefined;
99
98
  count?: undefined;
@@ -102,8 +101,8 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
102
101
  provider: import("../custom-registry.js").CustomProviderConfig;
103
102
  registered?: undefined;
104
103
  label?: undefined;
105
- } | {
106
104
  message?: undefined;
105
+ } | {
107
106
  deleted?: undefined;
108
107
  provider?: undefined;
109
108
  count?: undefined;
@@ -112,8 +111,8 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
112
111
  id: string;
113
112
  registered?: undefined;
114
113
  label?: undefined;
115
- } | {
116
114
  message?: undefined;
115
+ } | {
117
116
  provider?: undefined;
118
117
  count?: undefined;
119
118
  providers?: undefined;
@@ -122,6 +121,7 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
122
121
  id: string;
123
122
  registered?: undefined;
124
123
  label?: undefined;
124
+ message?: undefined;
125
125
  } | {
126
126
  deleted?: undefined;
127
127
  provider?: undefined;
@@ -3,6 +3,7 @@ import { z } from "zod";
3
3
  import { defineAction } from "../action.js";
4
4
  import { getCredentialContext } from "../server/request-context.js";
5
5
  import { appendProviderCorpusJobHits, createProviderCorpusJob, deleteProviderCorpusJob, getProviderCorpusJob, getProviderCorpusJobHits, listProviderCorpusJobs, updateProviderCorpusJob, } from "./corpus-jobs-store.js";
6
+ import { setBodyValueAtPath } from "./request-body.js";
6
7
  import { getStagedDatasetMeta, getStagedDatasetRows, stagedDatasetVersion, } from "./staged-datasets-store.js";
7
8
  const MethodSchema = z.enum(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"]);
8
9
  const ProviderRequestSchema = z.object({
@@ -745,7 +746,7 @@ function buildPaginatedRequest(request, pagination, state) {
745
746
  query = { ...query, [pagination.cursorParam]: state.cursor };
746
747
  }
747
748
  if (pagination.cursorBodyPath) {
748
- body = setAtPath(body, pagination.cursorBodyPath, state.cursor);
749
+ body = setBodyValueAtPath(body, pagination.cursorBodyPath, state.cursor);
749
750
  }
750
751
  }
751
752
  return { ...request, query, body };
@@ -758,7 +759,7 @@ function buildBatchRequest(request, batch, values) {
758
759
  query = { ...query, [batch.itemQueryParam]: payload };
759
760
  }
760
761
  if (batch.itemBodyPath) {
761
- body = setAtPath(body, batch.itemBodyPath, values);
762
+ body = setBodyValueAtPath(body, batch.itemBodyPath, values);
762
763
  }
763
764
  return { ...request, query, body };
764
765
  }
@@ -1114,25 +1115,6 @@ function getAtPath(value, path) {
1114
1115
  }
1115
1116
  return current;
1116
1117
  }
1117
- function setAtPath(base, path, value) {
1118
- const parts = pathParts(path);
1119
- if (!parts.length)
1120
- return value;
1121
- const root = base && typeof base === "object" && !Array.isArray(base)
1122
- ? JSON.parse(JSON.stringify(base))
1123
- : {};
1124
- let current = root;
1125
- for (const part of parts.slice(0, -1)) {
1126
- const existing = current[part];
1127
- const next = existing && typeof existing === "object" && !Array.isArray(existing)
1128
- ? { ...existing }
1129
- : {};
1130
- current[part] = next;
1131
- current = next;
1132
- }
1133
- current[parts[parts.length - 1]] = value;
1134
- return root;
1135
- }
1136
1118
  function collectSearchStrings(item, textPaths, maxFields) {
1137
1119
  const paths = (textPaths ?? []).filter((path) => path.trim());
1138
1120
  if (!paths.length)
@@ -14,6 +14,7 @@ import { resolveWorkspaceConnectionCredentialForApp } from "../workspace-connect
14
14
  import { resolveWorkspaceConnectionForApp, } from "../workspace-connections/store.js";
15
15
  import { createProviderQuotaIdentity, createProviderRequestDedupeKey, executeWithProviderQuota, } from "./quota-governor.js";
16
16
  export { upsertCustomProvider, deleteCustomProvider, listCustomProviders, getCustomProvider, validateCustomBaseUrl, assertCanMutateCustomProviderScope, CustomProviderAuthError, } from "./custom-registry.js";
17
+ import { setBodyValueAtPath } from "./request-body.js";
17
18
  export const PROVIDER_API_IDS = [
18
19
  "amplitude",
19
20
  "apollo",
@@ -1583,7 +1584,7 @@ export async function executeProviderApiRequest(args, runtime) {
1583
1584
  query: queryWithCursor,
1584
1585
  });
1585
1586
  const bodyWithCursor = extra?.bodyCursor
1586
- ? setValueAtPath(substituteUnknown(args.body, placeholders), extra.bodyCursor.path, extra.bodyCursor.value)
1587
+ ? setBodyValueAtPath(substituteUnknown(args.body, placeholders), extra.bodyCursor.path, extra.bodyCursor.value)
1587
1588
  : substituteUnknown(args.body, placeholders);
1588
1589
  const pageBody = prepareBody(bodyWithCursor, headers);
1589
1590
  const requestKey = createProviderRequestDedupeKey({
@@ -2371,7 +2372,7 @@ async function executeCustomProviderApiRequest(args, customConfig, runtime) {
2371
2372
  query: queryWithCursor,
2372
2373
  });
2373
2374
  const bodyWithCursor = extra?.bodyCursor
2374
- ? setValueAtPath(args.body, extra.bodyCursor.path, extra.bodyCursor.value)
2375
+ ? setBodyValueAtPath(args.body, extra.bodyCursor.path, extra.bodyCursor.value)
2375
2376
  : args.body;
2376
2377
  const pageBody = prepareBody(bodyWithCursor, headers);
2377
2378
  const requestKey = createProviderRequestDedupeKey({
@@ -4594,9 +4595,21 @@ function sanitizeOutboundHeaders(value) {
4594
4595
  function prepareBody(body, headers) {
4595
4596
  if (body === undefined || body === null)
4596
4597
  return undefined;
4597
- if (typeof body === "string")
4598
- return body;
4599
4598
  const hasContentType = Object.keys(headers).some((name) => name.toLowerCase() === "content-type");
4599
+ if (typeof body === "string") {
4600
+ let isJson = false;
4601
+ try {
4602
+ JSON.parse(body);
4603
+ isJson = true;
4604
+ }
4605
+ catch {
4606
+ // coercion-ok: JSON.parse is a probe; a throw means plain text, which keeps its existing content type.
4607
+ }
4608
+ if (isJson && !hasContentType) {
4609
+ headers["Content-Type"] = "application/json";
4610
+ }
4611
+ return body;
4612
+ }
4600
4613
  if (!hasContentType)
4601
4614
  headers["Content-Type"] = "application/json";
4602
4615
  return JSON.stringify(body);
@@ -4919,25 +4932,6 @@ function mergeQueryObjects(base, extra) {
4919
4932
  }
4920
4933
  return extra;
4921
4934
  }
4922
- function setValueAtPath(base, path, value) {
4923
- const root = base && typeof base === "object" && !Array.isArray(base)
4924
- ? { ...base }
4925
- : {};
4926
- const parts = path.split(".").filter(Boolean);
4927
- if (!parts.length)
4928
- return root;
4929
- let current = root;
4930
- for (const part of parts.slice(0, -1)) {
4931
- const existing = current[part];
4932
- const next = existing && typeof existing === "object" && !Array.isArray(existing)
4933
- ? { ...existing }
4934
- : {};
4935
- current[part] = next;
4936
- current = next;
4937
- }
4938
- current[parts[parts.length - 1]] = value;
4939
- return root;
4940
- }
4941
4935
  function clampTimeout(timeoutMs) {
4942
4936
  if (!Number.isFinite(timeoutMs))
4943
4937
  return DEFAULT_TIMEOUT_MS;
@@ -0,0 +1 @@
1
+ export declare function setBodyValueAtPath(base: unknown, path: string, value: unknown): unknown;
@@ -0,0 +1,50 @@
1
+ const JSON_OBJECT_BODY_REQUIRED = "Body cursor pagination requires a JSON object request body.";
2
+ // The one place a pagination cursor or batch is written into a request body.
3
+ // A JSON-string body keeps its other fields and is re-serialized as a string.
4
+ export function setBodyValueAtPath(base, path, value) {
5
+ const wasString = typeof base === "string";
6
+ let parsed = base;
7
+ // A blank string body is an empty body.
8
+ if (typeof parsed === "string" && parsed.trim() === "")
9
+ parsed = undefined;
10
+ if (typeof parsed === "string") {
11
+ try {
12
+ parsed = JSON.parse(parsed);
13
+ }
14
+ catch {
15
+ throw new Error(JSON_OBJECT_BODY_REQUIRED);
16
+ }
17
+ }
18
+ if (parsed !== undefined &&
19
+ parsed !== null &&
20
+ (typeof parsed !== "object" || Array.isArray(parsed))) {
21
+ throw new Error(JSON_OBJECT_BODY_REQUIRED);
22
+ }
23
+ const root = parsed
24
+ ? { ...parsed }
25
+ : {};
26
+ // `ids[0]` and `ids.0` are the same path.
27
+ const parts = path
28
+ .replace(/\[(\d+)\]/g, ".$1")
29
+ .split(".")
30
+ .map((part) => part.trim())
31
+ .filter(Boolean);
32
+ const isIndex = (part) => part !== undefined && /^\d+$/.test(part);
33
+ let current = root;
34
+ parts.slice(0, -1).forEach((part, index) => {
35
+ const existing = current[part];
36
+ const indexNext = isIndex(parts[index + 1]);
37
+ const next = Array.isArray(existing) && indexNext
38
+ ? [...existing]
39
+ : existing && typeof existing === "object" && !Array.isArray(existing)
40
+ ? { ...existing }
41
+ : indexNext
42
+ ? []
43
+ : {};
44
+ current[part] = next;
45
+ current = next;
46
+ });
47
+ if (parts.length)
48
+ current[parts[parts.length - 1]] = value;
49
+ return wasString ? JSON.stringify(root) : root;
50
+ }
@@ -24,6 +24,7 @@
24
24
  * here; redaction is applied to the response returned to the model.
25
25
  */
26
26
  import { fail } from "../action.js";
27
+ import { setBodyValueAtPath } from "./request-body.js";
27
28
  import { rejectFailedProviderResult } from "./result-outcome.js";
28
29
  import { upsertStagedDataset, deriveColumns, legacyStagedDatasetId, stagedDatasetId, MAX_ROWS_PER_OWNER, } from "./staged-datasets-store.js";
29
30
  function getAtPath(obj, path) {
@@ -64,25 +65,6 @@ function extractNextCursor(body, path) {
64
65
  return null;
65
66
  return String(val);
66
67
  }
67
- function setAtPath(base, path, value) {
68
- const root = base && typeof base === "object" && !Array.isArray(base)
69
- ? { ...base }
70
- : {};
71
- const parts = path.split(".").filter(Boolean);
72
- if (!parts.length)
73
- return root;
74
- let current = root;
75
- for (const part of parts.slice(0, -1)) {
76
- const existing = current[part];
77
- const next = existing && typeof existing === "object" && !Array.isArray(existing)
78
- ? { ...existing }
79
- : {};
80
- current[part] = next;
81
- current = next;
82
- }
83
- current[parts[parts.length - 1]] = value;
84
- return root;
85
- }
86
68
  async function sleepMs(ms) {
87
69
  await new Promise((resolve) => setTimeout(resolve, ms));
88
70
  }
@@ -153,7 +135,7 @@ export async function stagingExecuteRequest(args, execute, ctx) {
153
135
  extraQuery[pagination.cursorParam] = lastCursor;
154
136
  }
155
137
  if (pagination.cursorBodyPath && lastCursor !== null) {
156
- nextBody = setAtPath(baseArgs.body, pagination.cursorBodyPath, lastCursor);
138
+ nextBody = setBodyValueAtPath(baseArgs.body, pagination.cursorBodyPath, lastCursor);
157
139
  }
158
140
  const hasCursorMode = Boolean(pagination.cursorParam) || Boolean(pagination.cursorBodyPath);
159
141
  if (pagination.pageParam && !hasCursorMode) {
@@ -57,31 +57,31 @@ export declare function createWriteSecretHandler(): import("h3").EventHandlerWit
57
57
  } | {
58
58
  error: string;
59
59
  errorCode?: undefined;
60
- ok?: undefined;
61
60
  status?: undefined;
61
+ ok?: undefined;
62
62
  } | {
63
63
  error: string;
64
64
  errorCode: string;
65
- ok?: undefined;
66
65
  status?: undefined;
66
+ ok?: undefined;
67
67
  } | {
68
68
  error?: undefined;
69
69
  errorCode?: undefined;
70
70
  ok: boolean;
71
71
  status: string;
72
72
  } | {
73
- ok?: undefined;
74
73
  error: string;
75
74
  removed?: undefined;
75
+ ok?: undefined;
76
76
  } | {
77
77
  error?: undefined;
78
78
  ok: boolean;
79
79
  removed: boolean;
80
80
  }>>;
81
81
  export declare function createTestSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
82
- ok?: undefined;
83
82
  error: string;
84
83
  note?: undefined;
84
+ ok?: undefined;
85
85
  } | {
86
86
  error?: undefined;
87
87
  ok: boolean;
@@ -67,12 +67,12 @@ export declare function createAgentEngineApiKeyHandler(): import("h3").EventHand
67
67
  code: any;
68
68
  } | {
69
69
  errorCode?: undefined;
70
+ error?: undefined;
70
71
  code?: undefined;
71
72
  ok: boolean;
72
73
  key: string;
73
74
  baseUrlKey?: string;
74
75
  scope: AgentEngineApiKeyScope;
75
76
  defaultModel?: import("./agent-engine-default-model-route.js").SavedKeyDefaultModelOutcome;
76
- error?: undefined;
77
77
  }>>;
78
78
  export {};
@@ -16,9 +16,9 @@ export declare function createAgentEngineDisconnectHandler(): import("h3").Event
16
16
  error: any;
17
17
  scope?: undefined;
18
18
  } | {
19
- error?: undefined;
20
19
  ok: boolean;
21
20
  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
- ok?: undefined;
3
2
  error: string;
4
3
  models?: undefined;
4
+ ok?: undefined;
5
5
  } | {
6
6
  error?: undefined;
7
7
  ok: boolean;
@@ -54,8 +54,8 @@ export declare function createRealtimeTokenHandler(): import("h3").EventHandlerW
54
54
  expiresAt?: undefined;
55
55
  ttlSeconds?: undefined;
56
56
  } | {
57
- error?: undefined;
58
57
  token: string;
59
58
  expiresAt: string;
60
59
  ttlSeconds: number;
60
+ error?: undefined;
61
61
  }>>;
@@ -0,0 +1 @@
1
+ export declare function isNonProgressEventType(type: string): boolean;
@@ -0,0 +1,13 @@
1
+ // Event types that never count as real run progress. The server's
2
+ // `last_progress_at` write (run-manager) and the browser's durable liveness
3
+ // cursor (sse-event-processor) both start from this list, so a reattached
4
+ // client cannot see progress the server never persisted. Both layers add their
5
+ // own stateful rule for action-preparation activity on top.
6
+ const NON_PROGRESS_EVENT_TYPES = new Set([
7
+ "stream_keepalive",
8
+ "model_stream",
9
+ "clear",
10
+ ]);
11
+ export function isNonProgressEventType(type) {
12
+ return NON_PROGRESS_EVENT_TYPES.has(type);
13
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.220.0",
3
+ "version": "0.220.1-nightly-20261024152749",
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": {
@@ -730,7 +730,7 @@
730
730
  "yaml": "^2.9.0",
731
731
  "yjs": "^13.6.33",
732
732
  "zod": "^4.3.6",
733
- "@agent-native/agentkit": "^0.220.0",
733
+ "@agent-native/agentkit": "^0.220.1-nightly-20261024152749",
734
734
  "@agent-native/recap-cli": "^0.5.89"
735
735
  },
736
736
  "devDependencies": {
@@ -801,7 +801,7 @@
801
801
  "xlsx": "^0.18.5"
802
802
  },
803
803
  "peerDependencies": {
804
- "@agent-native/core-corpus": ">=0.196.0",
804
+ "@agent-native/core-corpus": "0.220.1-nightly-20261024152749",
805
805
  "@ai-sdk/anthropic": ">=3",
806
806
  "@ai-sdk/cohere": ">=3",
807
807
  "@ai-sdk/google": ">=3",