@cognipeer/agent-sdk 0.6.4 → 0.6.6

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/index.cjs CHANGED
@@ -18,7 +18,99 @@ var path__default = /*#__PURE__*/_interopDefault(path);
18
18
  var crypto__default = /*#__PURE__*/_interopDefault(crypto);
19
19
  var AjvModule__default = /*#__PURE__*/_interopDefault(AjvModule);
20
20
 
21
- // src/adapters/langchain.ts
21
+ var __defProp = Object.defineProperty;
22
+ var __getOwnPropNames = Object.getOwnPropertyNames;
23
+ var __esm = (fn, res) => function __init() {
24
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
25
+ };
26
+ var __export = (target, all) => {
27
+ for (var name in all)
28
+ __defProp(target, name, { get: all[name], enumerable: true });
29
+ };
30
+
31
+ // src/utils/utilTokens.ts
32
+ var utilTokens_exports = {};
33
+ __export(utilTokens_exports, {
34
+ countApproxTokens: () => countApproxTokens,
35
+ countMessagesTokens: () => countMessagesTokens,
36
+ defaultTokenCounter: () => defaultTokenCounter,
37
+ getTokenCounter: () => getTokenCounter,
38
+ setTokenCounter: () => setTokenCounter
39
+ });
40
+ function defaultTokenCounter(text) {
41
+ if (!text) return 0;
42
+ const nonAsciiCount = (text.match(/[^\x00-\x7F]/g) || []).length;
43
+ const asciiCount = text.length - nonAsciiCount;
44
+ return Math.ceil(asciiCount / 4 + nonAsciiCount / 1.5);
45
+ }
46
+ function setTokenCounter(counter) {
47
+ activeTokenCounter = typeof counter === "function" ? counter : defaultTokenCounter;
48
+ }
49
+ function getTokenCounter() {
50
+ return activeTokenCounter;
51
+ }
52
+ function countApproxTokens(text) {
53
+ return activeTokenCounter(text || "");
54
+ }
55
+ function extractMessageText(m) {
56
+ if (!m) return "";
57
+ const parts = [];
58
+ parts.push(m.role || "");
59
+ if (m.name) parts.push(m.name);
60
+ if (typeof m.content === "string") {
61
+ parts.push(m.content);
62
+ } else if (Array.isArray(m.content)) {
63
+ for (const c of m.content) {
64
+ if (typeof c === "string") parts.push(c);
65
+ else if (c?.text) parts.push(c.text);
66
+ else if (c?.content) parts.push(c.content);
67
+ else if (c?.type === "tool_use" && c?.input) {
68
+ parts.push(c.name || "");
69
+ parts.push(typeof c.input === "string" ? c.input : JSON.stringify(c.input));
70
+ } else if (c != null && typeof c === "object") {
71
+ parts.push(JSON.stringify(c));
72
+ }
73
+ }
74
+ } else if (m.content != null && typeof m.content === "object") {
75
+ parts.push(JSON.stringify(m.content));
76
+ }
77
+ if (Array.isArray(m.tool_calls)) {
78
+ for (const tc of m.tool_calls) {
79
+ if (tc?.function?.name) parts.push(tc.function.name);
80
+ if (tc?.function?.arguments) {
81
+ parts.push(
82
+ typeof tc.function.arguments === "string" ? tc.function.arguments : JSON.stringify(tc.function.arguments)
83
+ );
84
+ }
85
+ if (tc?.name && !tc?.function?.name) parts.push(tc.name);
86
+ if (tc?.args && !tc?.function?.arguments) {
87
+ parts.push(typeof tc.args === "string" ? tc.args : JSON.stringify(tc.args));
88
+ }
89
+ }
90
+ }
91
+ if (Array.isArray(m.additional_kwargs?.tool_calls)) {
92
+ for (const tc of m.additional_kwargs.tool_calls) {
93
+ if (tc?.function?.name) parts.push(tc.function.name);
94
+ if (tc?.function?.arguments) {
95
+ parts.push(
96
+ typeof tc.function.arguments === "string" ? tc.function.arguments : JSON.stringify(tc.function.arguments)
97
+ );
98
+ }
99
+ }
100
+ }
101
+ return parts.join("\n");
102
+ }
103
+ function countMessagesTokens(messages) {
104
+ if (!Array.isArray(messages) || messages.length === 0) return 0;
105
+ const allText = messages.map(extractMessageText).join("\n");
106
+ return countApproxTokens(allText) + messages.length * 4;
107
+ }
108
+ var activeTokenCounter;
109
+ var init_utilTokens = __esm({
110
+ "src/utils/utilTokens.ts"() {
111
+ activeTokenCounter = defaultTokenCounter;
112
+ }
113
+ });
22
114
  var moduleName = "@langchain/core/tools";
23
115
  var cachedToolFactory;
24
116
  var cachedHasAttemptedFactory = false;
@@ -326,7 +418,9 @@ function createTool({
326
418
  needsApproval,
327
419
  approvalPrompt,
328
420
  approvalDefaults,
329
- maxExecutionsPerRun
421
+ maxExecutionsPerRun,
422
+ cache,
423
+ retry
330
424
  }) {
331
425
  const execute = async (input) => func(input);
332
426
  const toolRecord = {
@@ -351,6 +445,12 @@ function createTool({
351
445
  if (maxExecutionsPerRun !== void 0) {
352
446
  toolRecord.maxExecutionsPerRun = maxExecutionsPerRun;
353
447
  }
448
+ if (cache !== void 0) {
449
+ toolRecord.cache = cache;
450
+ }
451
+ if (retry !== void 0) {
452
+ toolRecord.retry = retry;
453
+ }
354
454
  toolRecord.__source = toolRecord.__source || "smart";
355
455
  toolRecord.__impl = func;
356
456
  return toolRecord;
@@ -653,6 +753,7 @@ function createResolverNode() {
653
753
  toolHistory: state.toolHistory || [],
654
754
  toolHistoryArchived: state.toolHistoryArchived || [],
655
755
  pendingApprovals: state.pendingApprovals || [],
756
+ pendingUserQuestions: state.pendingUserQuestions || [],
656
757
  plan: state.plan || null,
657
758
  planVersion: state.planVersion || 0
658
759
  };
@@ -1892,7 +1993,8 @@ function createAgentCoreNode(opts) {
1892
1993
  todoListPrompt: opts.todoListPrompt,
1893
1994
  limits: opts.limits,
1894
1995
  outputSchema: opts.outputSchema,
1895
- tracing: opts.tracing
1996
+ tracing: opts.tracing,
1997
+ responseFormat: void 0
1896
1998
  };
1897
1999
  const tools = runtime.tools ?? [];
1898
2000
  const shouldUseStrictToolCalling = Boolean(
@@ -2094,7 +2196,7 @@ function createAgentCoreNode(opts) {
2094
2196
  // src/smart/runtimeConfig.ts
2095
2197
  var DEFAULT_CRITICAL_TOOLS = ["response", "manage_todo_list", "get_tool_response"];
2096
2198
  var BASE_DEFAULTS = {
2097
- limits: { maxToolCalls: 8, maxParallelTools: 2, maxContextTokens: 24e3 },
2199
+ limits: { maxToolCalls: 8, maxParallelTools: 2, maxContextTokens: 24e3, maxTotalOutputTokens: 0, maxCostUsd: 0, maxWallClockMs: 0 },
2098
2200
  summarization: {
2099
2201
  enable: true,
2100
2202
  maxTokens: 24e3,
@@ -2221,7 +2323,10 @@ function mergeLimits(base, override) {
2221
2323
  return {
2222
2324
  maxToolCalls: override?.maxToolCalls ?? base.maxToolCalls,
2223
2325
  maxParallelTools: override?.maxParallelTools ?? base.maxParallelTools,
2224
- maxContextTokens: override?.maxContextTokens ?? base.maxContextTokens
2326
+ maxContextTokens: override?.maxContextTokens ?? base.maxContextTokens,
2327
+ maxTotalOutputTokens: override?.maxTotalOutputTokens ?? base.maxTotalOutputTokens ?? 0,
2328
+ maxCostUsd: override?.maxCostUsd ?? base.maxCostUsd ?? 0,
2329
+ maxWallClockMs: override?.maxWallClockMs ?? base.maxWallClockMs ?? 0
2225
2330
  };
2226
2331
  }
2227
2332
  function resolveBaseProfile(opts, runtimeProfile) {
@@ -2299,66 +2404,8 @@ function getResolvedSmartConfig(opts, runtime) {
2299
2404
  return runtime?.smart || normalizeSmartAgentOptions(opts);
2300
2405
  }
2301
2406
 
2302
- // src/utils/utilTokens.ts
2303
- function countApproxTokens(text) {
2304
- if (!text) return 0;
2305
- const nonAsciiCount = (text.match(/[^\x00-\x7F]/g) || []).length;
2306
- const asciiCount = text.length - nonAsciiCount;
2307
- return Math.ceil(asciiCount / 4 + nonAsciiCount / 1.5);
2308
- }
2309
- function extractMessageText(m) {
2310
- if (!m) return "";
2311
- const parts = [];
2312
- parts.push(m.role || "");
2313
- if (m.name) parts.push(m.name);
2314
- if (typeof m.content === "string") {
2315
- parts.push(m.content);
2316
- } else if (Array.isArray(m.content)) {
2317
- for (const c of m.content) {
2318
- if (typeof c === "string") parts.push(c);
2319
- else if (c?.text) parts.push(c.text);
2320
- else if (c?.content) parts.push(c.content);
2321
- else if (c?.type === "tool_use" && c?.input) {
2322
- parts.push(c.name || "");
2323
- parts.push(typeof c.input === "string" ? c.input : JSON.stringify(c.input));
2324
- } else if (c != null && typeof c === "object") {
2325
- parts.push(JSON.stringify(c));
2326
- }
2327
- }
2328
- } else if (m.content != null && typeof m.content === "object") {
2329
- parts.push(JSON.stringify(m.content));
2330
- }
2331
- if (Array.isArray(m.tool_calls)) {
2332
- for (const tc of m.tool_calls) {
2333
- if (tc?.function?.name) parts.push(tc.function.name);
2334
- if (tc?.function?.arguments) {
2335
- parts.push(
2336
- typeof tc.function.arguments === "string" ? tc.function.arguments : JSON.stringify(tc.function.arguments)
2337
- );
2338
- }
2339
- if (tc?.name && !tc?.function?.name) parts.push(tc.name);
2340
- if (tc?.args && !tc?.function?.arguments) {
2341
- parts.push(typeof tc.args === "string" ? tc.args : JSON.stringify(tc.args));
2342
- }
2343
- }
2344
- }
2345
- if (Array.isArray(m.additional_kwargs?.tool_calls)) {
2346
- for (const tc of m.additional_kwargs.tool_calls) {
2347
- if (tc?.function?.name) parts.push(tc.function.name);
2348
- if (tc?.function?.arguments) {
2349
- parts.push(
2350
- typeof tc.function.arguments === "string" ? tc.function.arguments : JSON.stringify(tc.function.arguments)
2351
- );
2352
- }
2353
- }
2354
- }
2355
- return parts.join("\n");
2356
- }
2357
- function countMessagesTokens(messages) {
2358
- if (!Array.isArray(messages) || messages.length === 0) return 0;
2359
- const allText = messages.map(extractMessageText).join("\n");
2360
- return countApproxTokens(allText) + messages.length * 4;
2361
- }
2407
+ // src/smart/toolResponses.ts
2408
+ init_utilTokens();
2362
2409
 
2363
2410
  // src/utils/content.ts
2364
2411
  function contentToString(content) {
@@ -2548,16 +2595,20 @@ function createToolsNode(initialTools, opts) {
2548
2595
  const traceSession = state.ctx?.__traceSession;
2549
2596
  const pendingApprovals = Array.isArray(state.pendingApprovals) ? state.pendingApprovals.map((entry) => ({ ...entry })) : [];
2550
2597
  const pendingByCallId = new Map(pendingApprovals.map((entry) => [entry.toolCallId, entry]));
2598
+ const pendingUserQuestions = Array.isArray(state.pendingUserQuestions) ? state.pendingUserQuestions.map((entry) => ({ ...entry })) : [];
2551
2599
  let awaitingApproval = false;
2600
+ let awaitingUserQuestion = false;
2552
2601
  for (const tool of toolByName.values()) {
2553
2602
  const anyTool = tool;
2554
2603
  if (anyTool._stateRef && typeof anyTool._stateRef === "object") {
2555
2604
  anyTool._stateRef.toolHistory = state.toolHistory;
2556
2605
  anyTool._stateRef.toolHistoryArchived = state.toolHistoryArchived;
2557
2606
  anyTool._stateRef.pendingApprovals = pendingApprovals;
2607
+ anyTool._stateRef.pendingUserQuestions = pendingUserQuestions;
2558
2608
  anyTool._stateRef.messages = state.messages;
2559
2609
  anyTool._stateRef.ctx = state.ctx || (state.ctx = {});
2560
2610
  anyTool._stateRef.__onEvent = onEvent;
2611
+ anyTool._stateRef.parentRuntime = runtime;
2561
2612
  }
2562
2613
  }
2563
2614
  const last = state.messages[state.messages.length - 1];
@@ -2568,6 +2619,7 @@ function createToolsNode(initialTools, opts) {
2568
2619
  const remaining = Math.max(0, limits.maxToolCalls - toolCount);
2569
2620
  const planned = toolCalls.slice(0, remaining);
2570
2621
  const skipped = toolCalls.slice(remaining);
2622
+ const orderedSlots = planned.map(() => []);
2571
2623
  const isCancelled = () => {
2572
2624
  if (abortSignal?.aborted) return { cancelled: true, reason: "aborted" };
2573
2625
  if (cancellationToken && cancellationToken.isCancellationRequested) return { cancelled: true, reason: "cancelled" };
@@ -2584,7 +2636,9 @@ function createToolsNode(initialTools, opts) {
2584
2636
  }
2585
2637
  state.ctx = ctx;
2586
2638
  };
2587
- const runOne = async (tc) => {
2639
+ const runOne = async (tc, slot = appended) => {
2640
+ var _a;
2641
+ const push = (msg) => slot.push(msg);
2588
2642
  const cancelState = isCancelled();
2589
2643
  if (cancelState.cancelled) {
2590
2644
  const ctx = state.ctx = state.ctx || {};
@@ -2595,7 +2649,7 @@ function createToolsNode(initialTools, opts) {
2595
2649
  }
2596
2650
  const tool = toolByName.get(tc.name);
2597
2651
  if (!tool) {
2598
- appended.push({ role: "tool", content: `Tool not found: ${tc.name}`, tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
2652
+ push({ role: "tool", content: `Tool not found: ${tc.name}`, tool_call_id: tc.id || `${tc.name}_unknown`, name: tc.name });
2599
2653
  onEvent?.({ type: "tool_call", phase: "error", name: tc.name, id: tc.id, args: tc.args, error: { message: "Tool not found" } });
2600
2654
  toolCount += 1;
2601
2655
  return { status: "error" };
@@ -2611,7 +2665,7 @@ function createToolsNode(initialTools, opts) {
2611
2665
  const validatedArgs = validateToolArgs(tool, args);
2612
2666
  if (!validatedArgs.ok && resolved.toolResponses.schemaValidation === "strict") {
2613
2667
  const errorMessage = `Tool argument validation failed for ${tc.name}: ${validatedArgs.message}`;
2614
- appended.push({ role: "tool", content: errorMessage, tool_call_id: toolCallId, name: tc.name });
2668
+ push({ role: "tool", content: errorMessage, tool_call_id: toolCallId, name: tc.name });
2615
2669
  markToolFailure(errorMessage, tc.name, toolCallId);
2616
2670
  onEvent?.({ type: "tool_call", phase: "error", name: tc.name, id: tc.id, args, error: { message: errorMessage } });
2617
2671
  toolCount += 1;
@@ -2624,6 +2678,7 @@ function createToolsNode(initialTools, opts) {
2624
2678
  const toolStateRef = tool._stateRef && typeof tool._stateRef === "object" ? tool._stateRef : null;
2625
2679
  if (toolStateRef) {
2626
2680
  toolStateRef.pendingApprovals = pendingApprovals;
2681
+ toolStateRef.pendingUserQuestions = pendingUserQuestions;
2627
2682
  toolStateRef.ctx = state.ctx || (state.ctx = {});
2628
2683
  toolStateRef.__currentToolCallId = toolCallId;
2629
2684
  delete toolStateRef.__awaitingApproval;
@@ -2633,7 +2688,7 @@ function createToolsNode(initialTools, opts) {
2633
2688
  const currentExecutions = countSuccessfulToolExecutions(toolHistory, toolName);
2634
2689
  if (currentExecutions >= maxExecutionsPerRun) {
2635
2690
  const limitMessage = `Skipped tool due to per-tool execution limit: ${toolName} (${maxExecutionsPerRun}/run)`;
2636
- appended.push({ role: "tool", content: limitMessage, tool_call_id: toolCallId, name: tc.name });
2691
+ push({ role: "tool", content: limitMessage, tool_call_id: toolCallId, name: tc.name });
2637
2692
  onEvent?.({ type: "tool_call", phase: "skipped", name: toolName, id: tc.id, args });
2638
2693
  recordTraceEvent(traceSession, {
2639
2694
  type: "tool_call",
@@ -2693,7 +2748,7 @@ function createToolsNode(initialTools, opts) {
2693
2748
  }
2694
2749
  if (approvalEntry.status === "rejected") {
2695
2750
  const rejectionMessage = approvalEntry.comment || "Tool call rejected by reviewer.";
2696
- appended.push({ role: "tool", content: `Tool call rejected: ${rejectionMessage}`, tool_call_id: toolCallId, name: tc.name });
2751
+ push({ role: "tool", content: `Tool call rejected: ${rejectionMessage}`, tool_call_id: toolCallId, name: tc.name });
2697
2752
  approvalEntry.metadata = { ...approvalEntry.metadata || {}, resolution: "rejected" };
2698
2753
  approvalEntry.status = "executed";
2699
2754
  approvalEntry.resolvedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -2711,23 +2766,102 @@ function createToolsNode(initialTools, opts) {
2711
2766
  const start = Date.now();
2712
2767
  const sanitizedArgs = sanitizeTracePayload(args);
2713
2768
  const inputBytes = traceSession?.resolvedConfig.logData ? estimatePayloadBytes(sanitizedArgs) : void 0;
2769
+ const cacheCfg = tool.cache;
2770
+ const cacheEnabled = cacheCfg === true || cacheCfg && typeof cacheCfg === "object";
2771
+ const cacheKeyFn = cacheEnabled && typeof cacheCfg === "object" ? cacheCfg.keyFn : void 0;
2772
+ const stableJson = (value) => {
2773
+ try {
2774
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
2775
+ if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
2776
+ const keys = Object.keys(value).sort();
2777
+ return `{${keys.map((k) => JSON.stringify(k) + ":" + stableJson(value[k])).join(",")}}`;
2778
+ } catch {
2779
+ return String(value);
2780
+ }
2781
+ };
2782
+ const cacheKey = cacheEnabled ? `${toolName}|${cacheKeyFn ? cacheKeyFn(args) : stableJson(args)}` : void 0;
2783
+ const toolCache = state.toolCache = state.toolCache || {};
2784
+ let cachedHit = void 0;
2785
+ if (cacheKey && Object.prototype.hasOwnProperty.call(toolCache, cacheKey)) {
2786
+ const entry = toolCache[cacheKey];
2787
+ const ttl = cacheEnabled && typeof cacheCfg === "object" ? cacheCfg.ttlMs : void 0;
2788
+ if (!ttl || entry?.timestamp && Date.now() - entry.timestamp <= ttl) {
2789
+ cachedHit = entry?.value;
2790
+ }
2791
+ }
2792
+ const retryCfg = tool.retry;
2793
+ const failureStats = (_a = state.ctx).__toolFailureCounts || (_a.__toolFailureCounts = {});
2794
+ if (retryCfg?.circuitBreakerThreshold && failureStats[toolName] >= retryCfg.circuitBreakerThreshold) {
2795
+ const breakerMsg = `Tool ${toolName} circuit breaker open after ${failureStats[toolName]} consecutive failures`;
2796
+ push({ role: "tool", content: breakerMsg, tool_call_id: toolCallId, name: tc.name });
2797
+ markToolFailure(breakerMsg, toolName, toolCallId);
2798
+ onEvent?.({ type: "tool_call", phase: "error", name: toolName, id: tc.id, args, error: { message: breakerMsg } });
2799
+ toolCount += 1;
2800
+ return { status: "error" };
2801
+ }
2714
2802
  try {
2803
+ if (cachedHit !== void 0) {
2804
+ const executionId2 = nanoid.nanoid();
2805
+ const responsePolicy2 = applyToolResponseHardCap(toolName, cachedHit, executionId2, resolved);
2806
+ toolHistory.push({
2807
+ executionId: executionId2,
2808
+ toolName,
2809
+ args,
2810
+ output: cachedHit,
2811
+ rawOutput: cachedHit,
2812
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2813
+ tool_call_id: tc.id,
2814
+ summarized: false,
2815
+ originalTokenCount: responsePolicy2.tokenCount,
2816
+ classification: responsePolicy2.classification,
2817
+ retentionPolicy: "keep_full",
2818
+ fromCache: true,
2819
+ status: "success"
2820
+ });
2821
+ push({ role: "tool", content: responsePolicy2.content, tool_call_id: tc.id || `${tc.name}_${toolCount}`, name: tc.name });
2822
+ onEvent?.({ type: "tool_call", phase: "success", name: toolName, id: tc.id, args, result: cachedHit, durationMs: 0 });
2823
+ toolCount += 1;
2824
+ return { status: "success" };
2825
+ }
2715
2826
  onEvent?.({ type: "tool_call", phase: "start", name: toolName, id: tc.id, args });
2716
2827
  onProgress?.({ stage: "tools", message: `Running tool ${tc.name}` });
2717
2828
  const anyTool = tool;
2718
2829
  const callOptions = { cancellationToken, signal: abortSignal };
2830
+ const invokeOnce = async () => {
2831
+ if (typeof anyTool.func === "function") return anyTool.func(args, callOptions);
2832
+ if (typeof anyTool.invoke === "function") return anyTool.invoke(args, callOptions);
2833
+ if (typeof anyTool.call === "function") return anyTool.call(args, callOptions);
2834
+ if (typeof anyTool._call === "function") return anyTool._call(args, callOptions);
2835
+ if (typeof anyTool.run === "function") return anyTool.run(args, callOptions);
2836
+ throw new Error("Tool is not invokable");
2837
+ };
2719
2838
  let output;
2720
- if (typeof anyTool.func === "function") output = await anyTool.func(args, callOptions);
2721
- else if (typeof anyTool.invoke === "function") output = await anyTool.invoke(args, callOptions);
2722
- else if (typeof anyTool.call === "function") output = await anyTool.call(args, callOptions);
2723
- else if (typeof anyTool._call === "function") output = await anyTool._call(args, callOptions);
2724
- else if (typeof anyTool.run === "function") output = await anyTool.run(args, callOptions);
2725
- else throw new Error("Tool is not invokable");
2839
+ const maxRetries = Math.max(0, Number(retryCfg?.maxRetries) || 0);
2840
+ const backoffMs = Math.max(0, Number(retryCfg?.backoffMs) || 250);
2841
+ let attempt = 0;
2842
+ while (true) {
2843
+ try {
2844
+ output = await invokeOnce();
2845
+ failureStats[toolName] = 0;
2846
+ break;
2847
+ } catch (retryErr) {
2848
+ attempt += 1;
2849
+ const shouldRetry = retryCfg?.shouldRetry ? retryCfg.shouldRetry(retryErr, attempt) : true;
2850
+ if (attempt > maxRetries || !shouldRetry) {
2851
+ failureStats[toolName] = (failureStats[toolName] || 0) + 1;
2852
+ throw retryErr;
2853
+ }
2854
+ await new Promise((r) => setTimeout(r, backoffMs * Math.pow(2, attempt - 1)));
2855
+ }
2856
+ }
2857
+ if (cacheKey) {
2858
+ toolCache[cacheKey] = { value: output, timestamp: Date.now() };
2859
+ }
2726
2860
  const durationMs = Date.now() - start;
2727
2861
  const executionId = nanoid.nanoid();
2728
2862
  if (output && typeof output === "object" && output.__handoff && output.__handoff.runtime) {
2729
2863
  toolHistory.push({ executionId, toolName, args, output: "handoff:ok", rawOutput: output, timestamp: (/* @__PURE__ */ new Date()).toISOString(), tool_call_id: tc.id, status: "handoff" });
2730
- appended.push({ role: "tool", content: "ok", tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
2864
+ push({ role: "tool", content: "ok", tool_call_id: tc.id || `${tc.name}_${toolCount}`, name: tc.name });
2731
2865
  state.agent = output.__handoff.runtime;
2732
2866
  onEvent?.({ type: "handoff", from: runtime.name, to: state.agent?.name, toolName });
2733
2867
  onEvent?.({ type: "tool_call", phase: "success", name: toolName, id: tc.id, args, result: "handoff", durationMs });
@@ -2756,6 +2890,22 @@ function createToolsNode(initialTools, opts) {
2756
2890
  approval: pendingApproval
2757
2891
  };
2758
2892
  }
2893
+ if (output && typeof output === "object" && output.__awaitingUserQuestion) {
2894
+ if (toolStateRef?.ctx && typeof toolStateRef.ctx === "object") {
2895
+ state.ctx = toolStateRef.ctx;
2896
+ }
2897
+ const pendingQuestion = pendingUserQuestions.find(
2898
+ (entry) => entry.toolCallId === output.toolCallId || entry.id === output.id
2899
+ );
2900
+ if (!pendingQuestion) {
2901
+ throw new Error(`Tool ${toolName} requested a user question but no pending question entry was recorded.`);
2902
+ }
2903
+ awaitingUserQuestion = true;
2904
+ return {
2905
+ status: "awaiting_user_question",
2906
+ userQuestion: pendingQuestion
2907
+ };
2908
+ }
2759
2909
  if (output && typeof output === "object" && output.__finalStructuredOutput) {
2760
2910
  if (!state.ctx) state.ctx = {};
2761
2911
  state.ctx.__structuredOutputParsed = output.data;
@@ -2779,7 +2929,7 @@ function createToolsNode(initialTools, opts) {
2779
2929
  archiveId: void 0,
2780
2930
  status: "success"
2781
2931
  });
2782
- appended.push({ role: "tool", content: responsePolicy.content, tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
2932
+ push({ role: "tool", content: responsePolicy.content, tool_call_id: tc.id || `${tc.name}_${toolCount}`, name: tc.name });
2783
2933
  onEvent?.({ type: "tool_call", phase: "success", name: toolName, id: tc.id, args, result: output, durationMs });
2784
2934
  onProgress?.({ stage: "tools", message: `Tool ${tc.name} completed`, detail: { durationMs } });
2785
2935
  if (needsApproval && approvalEntry) {
@@ -2835,7 +2985,7 @@ function createToolsNode(initialTools, opts) {
2835
2985
  const executionId = nanoid.nanoid();
2836
2986
  const message = error?.message || String(error);
2837
2987
  toolHistory.push({ executionId, toolName, args, output: `Error executing tool: ${message}`, rawOutput: null, timestamp: (/* @__PURE__ */ new Date()).toISOString(), tool_call_id: tc.id, status: "error" });
2838
- appended.push({ role: "tool", content: `Error executing tool: ${message}`, tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
2988
+ push({ role: "tool", content: `Error executing tool: ${message}`, tool_call_id: tc.id || `${tc.name}_${toolCount}`, name: tc.name });
2839
2989
  markToolFailure(message, toolName, toolCallId);
2840
2990
  onEvent?.({ type: "tool_call", phase: "error", name: toolName, id: tc.id, args, error: { message } });
2841
2991
  onProgress?.({ stage: "tools", message: `Tool ${tc.name} failed`, detail: { error: message } });
@@ -2872,21 +3022,74 @@ function createToolsNode(initialTools, opts) {
2872
3022
  return { status: "error", approval: approvalEntry };
2873
3023
  }
2874
3024
  };
2875
- for (const tc of planned) {
2876
- if (awaitingApproval) break;
3025
+ const approvalIndices = [];
3026
+ const parallelIndices = [];
3027
+ planned.forEach((tc, idx) => {
3028
+ const t = toolByName.get(tc.name);
3029
+ if (t && t.needsApproval) approvalIndices.push(idx);
3030
+ else parallelIndices.push(idx);
3031
+ });
3032
+ let shouldStop = false;
3033
+ const checkCancellationStop = () => {
2877
3034
  const cancelState = isCancelled();
2878
3035
  if (cancelState.cancelled) {
2879
3036
  const ctx = state.ctx = state.ctx || {};
2880
3037
  ctx.__cancelled = { stage: "tools", reason: cancelState.reason, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
2881
3038
  onEvent?.({ type: "cancelled", stage: "tools", reason: cancelState.reason });
2882
3039
  onProgress?.({ stage: "tools", message: "Cancelled", detail: { reason: cancelState.reason } });
2883
- break;
3040
+ shouldStop = true;
3041
+ return true;
2884
3042
  }
2885
- const result = await runOne(tc);
3043
+ return false;
3044
+ };
3045
+ for (const idx of approvalIndices) {
3046
+ if (awaitingApproval || awaitingUserQuestion || shouldStop) break;
3047
+ if (checkCancellationStop()) break;
3048
+ const result = await runOne(planned[idx], orderedSlots[idx]);
2886
3049
  if (result.status === "awaiting_approval") {
2887
3050
  awaitingApproval = true;
2888
3051
  break;
2889
3052
  }
3053
+ if (result.status === "awaiting_user_question") {
3054
+ awaitingUserQuestion = true;
3055
+ break;
3056
+ }
3057
+ }
3058
+ if (!awaitingApproval && !awaitingUserQuestion && !shouldStop && parallelIndices.length > 0) {
3059
+ const concurrency = Math.max(1, Math.min(limits.maxParallelTools, parallelIndices.length));
3060
+ let cursor = 0;
3061
+ const workerError = {};
3062
+ const workers = [];
3063
+ for (let w = 0; w < concurrency; w++) {
3064
+ workers.push((async () => {
3065
+ while (true) {
3066
+ if (workerError.err || shouldStop || awaitingApproval || awaitingUserQuestion) return;
3067
+ const i = cursor++;
3068
+ if (i >= parallelIndices.length) return;
3069
+ if (checkCancellationStop()) return;
3070
+ const idx = parallelIndices[i];
3071
+ try {
3072
+ const result = await runOne(planned[idx], orderedSlots[idx]);
3073
+ if (result.status === "awaiting_approval") {
3074
+ awaitingApproval = true;
3075
+ return;
3076
+ }
3077
+ if (result.status === "awaiting_user_question") {
3078
+ awaitingUserQuestion = true;
3079
+ return;
3080
+ }
3081
+ } catch (err) {
3082
+ workerError.err = err;
3083
+ return;
3084
+ }
3085
+ }
3086
+ })());
3087
+ }
3088
+ await Promise.all(workers);
3089
+ if (workerError.err) throw workerError.err;
3090
+ }
3091
+ for (const slot of orderedSlots) {
3092
+ if (slot.length > 0) appended.push(...slot);
2890
3093
  }
2891
3094
  for (const tc of skipped) {
2892
3095
  onEvent?.({ type: "tool_call", phase: "skipped", name: tc.name, id: tc.id, args: tc.args });
@@ -2923,13 +3126,26 @@ function createToolsNode(initialTools, opts) {
2923
3126
  }
2924
3127
  state.ctx = Object.keys(ctx).length > 0 ? ctx : void 0;
2925
3128
  }
3129
+ const appendedHasToolResult = appended.some((m) => m.role === "tool");
3130
+ if (appendedHasToolResult && state.ctx?.__summarizationExhausted) {
3131
+ const ctx = { ...state.ctx };
3132
+ delete ctx.__summarizationExhausted;
3133
+ state.ctx = Object.keys(ctx).length > 0 ? ctx : void 0;
3134
+ }
2926
3135
  return {
2927
3136
  messages: [...state.messages, ...appended],
2928
3137
  toolCallCount: toolCount,
2929
3138
  toolHistory,
2930
3139
  toolHistoryArchived,
3140
+ toolCache: state.toolCache,
3141
+ // Explicitly propagate ctx so callers using `{ ...state, ...delta }`
3142
+ // do not silently capture a stale ctx reference. Mutations to
3143
+ // __summarizationExhausted / __awaitingApproval / __awaitingUserQuestion /
3144
+ // __structuredOutputParsed are made on this object during execution.
3145
+ ctx: state.ctx,
2931
3146
  agent: state.agent,
2932
- pendingApprovals
3147
+ pendingApprovals,
3148
+ pendingUserQuestions
2933
3149
  };
2934
3150
  };
2935
3151
  }
@@ -3164,7 +3380,8 @@ function resolveReflection(input, preset) {
3164
3380
  maxChars: preset.maxChars,
3165
3381
  keepLast: 3,
3166
3382
  summarize: false,
3167
- emitEvents: true
3383
+ emitEvents: true,
3384
+ everyNTurns: 1
3168
3385
  };
3169
3386
  }
3170
3387
  const cfg = input ?? {};
@@ -3179,7 +3396,9 @@ function resolveReflection(input, preset) {
3179
3396
  keepLast: cfg.keepLast ?? 3,
3180
3397
  summarize: cfg.summarize ?? false,
3181
3398
  promptTemplate: cfg.promptTemplate,
3182
- emitEvents: cfg.emitEvents ?? true
3399
+ emitEvents: cfg.emitEvents ?? true,
3400
+ maxPerRun: typeof cfg.maxPerRun === "number" && cfg.maxPerRun >= 0 ? cfg.maxPerRun : void 0,
3401
+ everyNTurns: Math.max(1, Math.trunc(cfg.everyNTurns ?? 1))
3183
3402
  };
3184
3403
  }
3185
3404
  function ensureArray(value) {
@@ -3392,6 +3611,229 @@ function resolveToolApprovalState(state, resolution) {
3392
3611
  };
3393
3612
  }
3394
3613
 
3614
+ // src/utils/userQuestions.ts
3615
+ var cloneQuestions = (questions) => Array.isArray(questions) ? questions.map((entry) => ({ ...entry })) : [];
3616
+ function validateAnswers(entry, answers, cancelled) {
3617
+ if (cancelled) {
3618
+ return answers || {};
3619
+ }
3620
+ const allowFreeText = entry.allowFreeText !== false;
3621
+ const out = {};
3622
+ const provided = answers || {};
3623
+ for (const question of entry.questions) {
3624
+ const required = question.required !== false;
3625
+ const key = question.question;
3626
+ const answer = provided[key] ?? provided[question.header || ""];
3627
+ if (!answer) {
3628
+ if (required) {
3629
+ throw new Error(`Missing answer for required question: "${key}".`);
3630
+ }
3631
+ continue;
3632
+ }
3633
+ const values = Array.isArray(answer.values) ? answer.values.filter((v) => typeof v === "string") : [];
3634
+ const freeText = typeof answer.freeText === "string" ? answer.freeText.trim() : "";
3635
+ const notes = typeof answer.notes === "string" ? answer.notes : void 0;
3636
+ if (freeText && !allowFreeText) {
3637
+ throw new Error(
3638
+ `Free-text answers are disabled for this agent (question: "${key}"). Pick one of the provided options.`
3639
+ );
3640
+ }
3641
+ if (values.length === 0 && !freeText) {
3642
+ if (required) {
3643
+ throw new Error(`Question "${key}" requires either a selected option or free-text answer.`);
3644
+ }
3645
+ continue;
3646
+ }
3647
+ if (values.length > 1 && !question.multiSelect) {
3648
+ throw new Error(`Question "${key}" is single-select but received multiple values.`);
3649
+ }
3650
+ if (Array.isArray(question.options) && question.options.length > 0 && values.length > 0) {
3651
+ const allowedValues = new Set(
3652
+ question.options.map((option) => option.value ?? option.label)
3653
+ );
3654
+ for (const value of values) {
3655
+ if (!allowedValues.has(value)) {
3656
+ if (!allowFreeText) {
3657
+ throw new Error(
3658
+ `Value "${value}" is not one of the allowed options for question "${key}".`
3659
+ );
3660
+ }
3661
+ }
3662
+ }
3663
+ }
3664
+ out[key] = { values, ...freeText ? { freeText } : {}, ...notes ? { notes } : {} };
3665
+ }
3666
+ return out;
3667
+ }
3668
+ function buildToolResponseContent(entry, answers, cancelled, notes) {
3669
+ if (cancelled) {
3670
+ return JSON.stringify({
3671
+ status: "cancelled",
3672
+ message: notes || "User dismissed the question without answering."
3673
+ });
3674
+ }
3675
+ const payload = {
3676
+ status: "answered",
3677
+ answers: entry.questions.map((question) => {
3678
+ const key = question.question;
3679
+ const ans = answers[key];
3680
+ return {
3681
+ question: key,
3682
+ header: question.header,
3683
+ values: ans?.values ?? [],
3684
+ freeText: ans?.freeText,
3685
+ notes: ans?.notes
3686
+ };
3687
+ }),
3688
+ ...notes ? { reviewerNotes: notes } : {}
3689
+ };
3690
+ return JSON.stringify(payload);
3691
+ }
3692
+ function resolveUserQuestionState(state, resolution) {
3693
+ const pending = cloneQuestions(state.pendingUserQuestions);
3694
+ const matchIndex = pending.findIndex(
3695
+ (entry2) => entry2.id === resolution.id || entry2.toolCallId === resolution.id
3696
+ );
3697
+ if (matchIndex === -1) {
3698
+ throw new Error(`Pending user question not found for id: ${resolution.id}`);
3699
+ }
3700
+ const entry = { ...pending[matchIndex] };
3701
+ if (entry.status === "answered" || entry.status === "executed" || entry.status === "cancelled") {
3702
+ throw new Error(`User question ${entry.id} already resolved (status: ${entry.status}).`);
3703
+ }
3704
+ const cancelled = resolution.cancelled === true;
3705
+ const validated = validateAnswers(entry, resolution.answers, cancelled);
3706
+ entry.status = cancelled ? "cancelled" : "answered";
3707
+ entry.answers = validated;
3708
+ entry.cancelled = cancelled || void 0;
3709
+ entry.answeredBy = resolution.answeredBy;
3710
+ entry.notes = resolution.notes;
3711
+ entry.answeredAt = (/* @__PURE__ */ new Date()).toISOString();
3712
+ pending[matchIndex] = entry;
3713
+ const ctx = { ...state.ctx || {} };
3714
+ delete ctx.__awaitingUserQuestion;
3715
+ if (ctx.__resumeStage === "tools") {
3716
+ delete ctx.__resumeStage;
3717
+ }
3718
+ ctx.__userQuestionResolved = { id: entry.id, status: entry.status };
3719
+ const toolMessage = {
3720
+ role: "tool",
3721
+ name: "ask_user_question",
3722
+ tool_call_id: entry.toolCallId,
3723
+ content: buildToolResponseContent(entry, validated, cancelled, resolution.notes)
3724
+ };
3725
+ return {
3726
+ ...state,
3727
+ pendingUserQuestions: pending,
3728
+ messages: [...state.messages || [], toolMessage],
3729
+ ctx
3730
+ };
3731
+ }
3732
+ var DEFAULT_DESCRIPTION_WITH_FREETEXT = "Pause the agent and surface a structured prompt to the human user when you genuinely cannot proceed without their input. Group up to 4 closely related questions into a single call. Prefer concrete options the user can pick over open-ended free-text \u2014 list `options` whenever you can anticipate the choices. The user may type a custom answer for any question. Do not use this tool for things you can deduce from the conversation, the system prompt, or another tool. After the user answers, their selections come back as the tool result.";
3733
+ var DEFAULT_DESCRIPTION_NO_FREETEXT = "Pause the agent and surface a structured multi-choice prompt to the human user when you genuinely cannot proceed without their input. Group up to 4 closely related questions into a single call. Every question MUST include at least two `options` \u2014 free-text answers are disabled for this agent. Do not use this tool for things you can deduce from the conversation, the system prompt, or another tool. After the user picks, their selections come back as the tool result.";
3734
+ function buildOptionSchema() {
3735
+ return zod.z.object({
3736
+ label: zod.z.string().min(1).describe("Display text the user sees"),
3737
+ value: zod.z.string().min(1).optional().describe("Value returned in the answer; defaults to label"),
3738
+ description: zod.z.string().optional(),
3739
+ preview: zod.z.string().optional().describe("Optional code / ASCII preview rendered when this option is focused")
3740
+ }).strict();
3741
+ }
3742
+ function buildQuestionSchema(allowFreeText) {
3743
+ const optionSchema = buildOptionSchema();
3744
+ const baseShape = {
3745
+ question: zod.z.string().min(1).describe("Full question shown to the user"),
3746
+ header: zod.z.string().max(40).optional().describe("Short chip / tag rendered next to the question (max ~12 chars recommended)"),
3747
+ multiSelect: zod.z.boolean().optional().describe("Allow multiple selections. Default: false"),
3748
+ placeholder: zod.z.string().optional(),
3749
+ required: zod.z.boolean().optional().describe("Default: true")
3750
+ };
3751
+ if (allowFreeText) {
3752
+ return zod.z.object({
3753
+ ...baseShape,
3754
+ options: zod.z.array(optionSchema).min(2).max(12).optional()
3755
+ }).strict();
3756
+ }
3757
+ return zod.z.object({
3758
+ ...baseShape,
3759
+ options: zod.z.array(optionSchema).min(2).max(12).describe("Required: free-text answers are disabled for this agent")
3760
+ }).strict();
3761
+ }
3762
+ function createAskUserQuestionTool(stateRef, config) {
3763
+ const allowFreeText = config.allowFreeText !== false;
3764
+ const description = config.promptOverride ?? (allowFreeText ? DEFAULT_DESCRIPTION_WITH_FREETEXT : DEFAULT_DESCRIPTION_NO_FREETEXT);
3765
+ const questionSchema = buildQuestionSchema(allowFreeText);
3766
+ const askUser = createTool({
3767
+ name: "ask_user_question",
3768
+ description,
3769
+ schema: zod.z.object({
3770
+ questions: zod.z.array(questionSchema).min(1).max(4).describe("1-4 related questions to ask in a single pause.")
3771
+ }).strict(),
3772
+ func: async ({ questions }) => {
3773
+ const ref = askUser._stateRef || (askUser._stateRef = {});
3774
+ const onEvent = ref.__onEvent;
3775
+ if (!ref.ctx) ref.ctx = {};
3776
+ const ctx = ref.ctx;
3777
+ const toolCallId = ref.__currentToolCallId || `ask_user_${nanoid.nanoid(6)}`;
3778
+ if (!ref.pendingUserQuestions) ref.pendingUserQuestions = [];
3779
+ const pendingList = ref.pendingUserQuestions;
3780
+ const entry = {
3781
+ id: nanoid.nanoid(),
3782
+ toolCallId,
3783
+ toolName: "ask_user_question",
3784
+ questions,
3785
+ status: "pending",
3786
+ requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
3787
+ allowFreeText
3788
+ };
3789
+ pendingList.push(entry);
3790
+ ctx.__awaitingUserQuestion = {
3791
+ id: entry.id,
3792
+ toolCallId: entry.toolCallId,
3793
+ requestedAt: entry.requestedAt
3794
+ };
3795
+ ctx.__resumeStage = "tools";
3796
+ onEvent?.({
3797
+ type: "user_question",
3798
+ status: "pending",
3799
+ id: entry.id,
3800
+ toolCallId: entry.toolCallId,
3801
+ questions: entry.questions,
3802
+ allowFreeText
3803
+ });
3804
+ recordTraceEvent(ctx.__traceSession, {
3805
+ type: "user_question",
3806
+ label: "Ask User",
3807
+ actor: { scope: "agent", name: "ask_user_question", role: "user_interaction" },
3808
+ status: "success",
3809
+ sections: [
3810
+ {
3811
+ kind: "metadata",
3812
+ label: "Pending Question",
3813
+ data: {
3814
+ id: entry.id,
3815
+ toolCallId: entry.toolCallId,
3816
+ questions: entry.questions,
3817
+ allowFreeText
3818
+ }
3819
+ }
3820
+ ]
3821
+ });
3822
+ return {
3823
+ __awaitingUserQuestion: true,
3824
+ id: entry.id,
3825
+ toolCallId: entry.toolCallId
3826
+ };
3827
+ }
3828
+ });
3829
+ askUser._stateRef = stateRef;
3830
+ askUser.__source = "human-in-the-loop";
3831
+ return askUser;
3832
+ }
3833
+
3834
+ // src/agent.ts
3835
+ init_utilTokens();
3836
+
3395
3837
  // src/utils/syntheticMessages.ts
3396
3838
  function isSyntheticSummaryMessage(message) {
3397
3839
  if (!message) return false;
@@ -3570,6 +4012,21 @@ function getActiveSummarizationThreshold(opts) {
3570
4012
  }
3571
4013
  return summarization.summaryTriggerTokens || summarization.maxTokens || opts.limits?.maxContextTokens || 5e4;
3572
4014
  }
4015
+ function shouldSignalSummarization(state, threshold) {
4016
+ const tokCountMessages = (state.messages || []).filter((message) => {
4017
+ if (isSyntheticSummaryMessage(message)) return false;
4018
+ if (message.role === "system" && (message.name === "context_summary" || message.name === "memory_context")) return false;
4019
+ return true;
4020
+ });
4021
+ const tokenCount = countMessagesTokens(tokCountMessages);
4022
+ if (tokenCount <= threshold) return "skip";
4023
+ if (state.ctx?.__summarizationExhausted) return "skip";
4024
+ const hasFreshSummary = (state.messages || []).some(
4025
+ (m) => m.role === "tool" && typeof m.content === "string" && (m.content === "SUMMARIZED" || m.content.startsWith("SUMMARIZED_TOOL_RESPONSE"))
4026
+ );
4027
+ if (hasFreshSummary && tokenCount <= threshold * 1.15) return "skip";
4028
+ return "trigger";
4029
+ }
3573
4030
  function clearNeedsSummarization(state) {
3574
4031
  if (!state.ctx?.__needsSummarization) {
3575
4032
  return state;
@@ -3585,6 +4042,21 @@ function createAgent(opts) {
3585
4042
  const resolver = createResolverNode();
3586
4043
  const agentCore = createAgentCoreNode(opts);
3587
4044
  const toolsBase = [...opts.tools ?? []];
4045
+ const askUserConfig = (() => {
4046
+ const raw = opts.humanInTheLoop?.askUser;
4047
+ if (!raw) return void 0;
4048
+ if (raw === true) return { enabled: true, allowFreeText: true };
4049
+ return {
4050
+ enabled: true,
4051
+ allowFreeText: raw.allowFreeText !== false,
4052
+ promptOverride: raw.promptOverride
4053
+ };
4054
+ })();
4055
+ const askUserOnQuestion = typeof opts.humanInTheLoop?.askUser === "object" ? opts.humanInTheLoop?.askUser?.onQuestion : void 0;
4056
+ const askUserStateRef = askUserConfig ? { pendingUserQuestions: void 0, ctx: void 0, __onEvent: void 0, __currentToolCallId: void 0 } : void 0;
4057
+ if (askUserConfig && askUserStateRef) {
4058
+ toolsBase.push(createAskUserQuestionTool(askUserStateRef, askUserConfig));
4059
+ }
3588
4060
  const soManager = opts.outputSchema ? new StructuredOutputManager(opts.outputSchema, resolveStrategy(opts.model)) : void 0;
3589
4061
  if (soManager) {
3590
4062
  const modelCapabilities = getModelCapabilities(opts.model);
@@ -3629,7 +4101,8 @@ function createAgent(opts) {
3629
4101
  useTodoList: void 0,
3630
4102
  outputSchema: opts.outputSchema,
3631
4103
  responseFormat: soManager?.getResponseFormat(),
3632
- tracing: opts.tracing
4104
+ tracing: opts.tracing,
4105
+ humanInTheLoop: askUserConfig ? { askUser: askUserConfig } : void 0
3633
4106
  };
3634
4107
  const summarizationThreshold = getActiveSummarizationThreshold(opts);
3635
4108
  async function runLoop(initial, config, emit) {
@@ -3664,6 +4137,45 @@ function createAgent(opts) {
3664
4137
  };
3665
4138
  const maxToolCalls = mergedLimits?.maxToolCalls === void 0 ? 50 : mergedLimits?.maxToolCalls;
3666
4139
  const iterationLimit = maxToolCalls === Infinity ? 100 : Math.max(maxToolCalls * 3 + 10, 40);
4140
+ const wallClockStart = Date.now();
4141
+ const maxWallClockMs = mergedLimits?.maxWallClockMs && mergedLimits.maxWallClockMs > 0 ? mergedLimits.maxWallClockMs : void 0;
4142
+ const maxTotalOutputTokens = mergedLimits?.maxTotalOutputTokens && mergedLimits.maxTotalOutputTokens > 0 ? mergedLimits.maxTotalOutputTokens : void 0;
4143
+ const maxCostUsd = mergedLimits?.maxCostUsd && mergedLimits.maxCostUsd > 0 ? mergedLimits.maxCostUsd : void 0;
4144
+ const costEstimator = opts.costEstimator;
4145
+ const checkBudgetBreached = () => {
4146
+ if (maxWallClockMs && Date.now() - wallClockStart > maxWallClockMs) {
4147
+ return { breached: true, reason: `maxWallClockMs (${maxWallClockMs}ms) exceeded` };
4148
+ }
4149
+ if (maxTotalOutputTokens) {
4150
+ const totals = state.usage?.totals;
4151
+ if (totals) {
4152
+ let outputSum = 0;
4153
+ for (const v of Object.values(totals)) outputSum += Number(v?.output) || 0;
4154
+ if (outputSum > maxTotalOutputTokens) {
4155
+ return { breached: true, reason: `maxTotalOutputTokens (${maxTotalOutputTokens}) exceeded` };
4156
+ }
4157
+ }
4158
+ }
4159
+ if (maxCostUsd && costEstimator) {
4160
+ const perRequest = state.usage?.perRequest;
4161
+ if (perRequest) {
4162
+ let cost = 0;
4163
+ for (const r of perRequest) {
4164
+ cost += costEstimator({
4165
+ modelName: r.modelName,
4166
+ inputTokens: Number(r.usage?.prompt_tokens) || 0,
4167
+ outputTokens: Number(r.usage?.completion_tokens) || 0,
4168
+ cachedInputTokens: Number(r.usage?.prompt_tokens_details?.cached_tokens) || 0,
4169
+ reasoningTokens: Number(r.usage?.completion_tokens_details?.reasoning_tokens) || 0
4170
+ }) || 0;
4171
+ }
4172
+ if (cost > maxCostUsd) {
4173
+ return { breached: true, reason: `maxCostUsd ($${maxCostUsd}) exceeded (actual: $${cost.toFixed(4)})` };
4174
+ }
4175
+ }
4176
+ }
4177
+ return { breached: false };
4178
+ };
3667
4179
  let iterations = 0;
3668
4180
  const onStateChange = config?.onStateChange;
3669
4181
  const checkpointReason = config?.checkpointReason;
@@ -3712,6 +4224,13 @@ function createAgent(opts) {
3712
4224
  };
3713
4225
  while (iterations < iterationLimit) {
3714
4226
  iterations++;
4227
+ const budget = checkBudgetBreached();
4228
+ if (budget.breached) {
4229
+ emit?.({ type: "metadata", limitBreached: budget.reason });
4230
+ const ctx = { ...state.ctx || {}, __limitBreached: budget.reason };
4231
+ state = { ...state, ctx };
4232
+ break;
4233
+ }
3715
4234
  const traceSession = state.ctx?.__traceSession;
3716
4235
  if (traceSession) {
3717
4236
  const iterEvent = recordTraceEvent(traceSession, {
@@ -3767,22 +4286,11 @@ function createAgent(opts) {
3767
4286
  }
3768
4287
  }
3769
4288
  if (summarizationThreshold !== void 0) {
3770
- const tokCountMessages = (state.messages || []).filter((message) => {
3771
- if (isSyntheticSummaryMessage(message)) return false;
3772
- if (message.role === "system" && (message.name === "context_summary" || message.name === "memory_context")) return false;
3773
- return true;
3774
- });
3775
- const tokenCount = countMessagesTokens(tokCountMessages);
3776
- if (tokenCount > summarizationThreshold) {
3777
- const summarizationExhausted = !!state.ctx?.__summarizationExhausted;
3778
- const hasFreshSummary = (state.messages || []).some(
3779
- (m) => m.role === "tool" && typeof m.content === "string" && (m.content === "SUMMARIZED" || m.content.startsWith("SUMMARIZED_TOOL_RESPONSE"))
3780
- );
3781
- if (hasFreshSummary && tokenCount <= summarizationThreshold * 1.15) ; else if (summarizationExhausted) ; else {
3782
- const ctx = { ...state.ctx || {}, __needsSummarization: true };
3783
- state = { ...state, ctx };
3784
- break;
3785
- }
4289
+ const verdict = shouldSignalSummarization(state, summarizationThreshold);
4290
+ if (verdict === "trigger") {
4291
+ const ctx = { ...state.ctx || {}, __needsSummarization: true };
4292
+ state = { ...state, ctx };
4293
+ break;
3786
4294
  }
3787
4295
  }
3788
4296
  onProgress?.({ stage: "agent", message: "Invoking model" });
@@ -3884,11 +4392,18 @@ function createAgent(opts) {
3884
4392
  state = { ...state, ...await toolsNode(state) };
3885
4393
  onProgress?.({ stage: "tools", message: "Tools finished" });
3886
4394
  if (state.ctx?.__awaitingApproval) break;
4395
+ if (state.ctx?.__awaitingUserQuestion) break;
3887
4396
  if (checkpointIfRequested("after_tools")) break;
3888
4397
  if (state.ctx?.__finalizedDueToStructuredOutput) break;
3889
4398
  if (reflectNode && resolvedReasoning?.reflection.enabled) {
3890
4399
  const cadence = resolvedReasoning.reflection.cadence;
3891
- if (shouldRunReflection(cadence, state, true)) {
4400
+ const refl = resolvedReasoning.reflection;
4401
+ const reflectionsSoFar = state.reflections?.length ?? 0;
4402
+ const lastTurn = state.reflections?.at(-1)?.turn ?? -Infinity;
4403
+ const currentTurn = state.toolCallCount || 0;
4404
+ const budgetExceeded = typeof refl.maxPerRun === "number" && reflectionsSoFar >= refl.maxPerRun;
4405
+ const cadenceGap = currentTurn - (Number.isFinite(lastTurn) ? lastTurn : -refl.everyNTurns) < refl.everyNTurns;
4406
+ if (!budgetExceeded && !cadenceGap && shouldRunReflection(cadence, state, true)) {
3892
4407
  try {
3893
4408
  const patch = await reflectNode(state, cadence);
3894
4409
  if (patch && Object.keys(patch).length > 0) {
@@ -3948,7 +4463,7 @@ function createAgent(opts) {
3948
4463
  }
3949
4464
  const lastAfterLoop = state.messages[state.messages.length - 1];
3950
4465
  if (lastAfterLoop?.role === "tool" && !pausedStage) {
3951
- const isExpectedExit = state.ctx?.__awaitingApproval || state.ctx?.__cancelled || state.ctx?.__finalizedDueToStructuredOutput || state.ctx?.__finalizedDueToToolLimit || state.ctx?.__needsSummarization;
4466
+ const isExpectedExit = state.ctx?.__awaitingApproval || state.ctx?.__awaitingUserQuestion || state.ctx?.__cancelled || state.ctx?.__finalizedDueToStructuredOutput || state.ctx?.__finalizedDueToToolLimit || state.ctx?.__needsSummarization || state.ctx?.__limitBreached;
3952
4467
  if (!isExpectedExit) {
3953
4468
  throw new Error(
3954
4469
  "Agent loop terminated with a pending tool response but no subsequent model invocation. This usually indicates a model provider error or exhausted iteration budget."
@@ -3961,7 +4476,22 @@ function createAgent(opts) {
3961
4476
  return state;
3962
4477
  }
3963
4478
  const invokeAgent = async (input, config) => {
3964
- const onEvent = config?.onEvent;
4479
+ const userTokenCounter = opts.tokenCounter;
4480
+ if (userTokenCounter) {
4481
+ const { setTokenCounter: setTokenCounter2 } = await Promise.resolve().then(() => (init_utilTokens(), utilTokens_exports));
4482
+ setTokenCounter2(userTokenCounter);
4483
+ }
4484
+ const callerOnEvent = config?.onEvent;
4485
+ const onEvent = askUserOnQuestion ? (event) => {
4486
+ if (event && event.type === "user_question") {
4487
+ try {
4488
+ askUserOnQuestion(event);
4489
+ } catch (err) {
4490
+ console.warn("[agent-sdk] humanInTheLoop.askUser.onQuestion error:", err);
4491
+ }
4492
+ }
4493
+ callerOnEvent?.(event);
4494
+ } : callerOnEvent;
3965
4495
  const onProgress = config?.onProgress;
3966
4496
  const onStream = config?.onStream;
3967
4497
  const streamEnabled = config?.stream === true;
@@ -4005,6 +4535,7 @@ function createAgent(opts) {
4005
4535
  metadata: input.metadata,
4006
4536
  ctx,
4007
4537
  pendingApprovals: input.pendingApprovals || [],
4538
+ pendingUserQuestions: input.pendingUserQuestions || [],
4008
4539
  agent: input.agent || runtimeWithInvokeLimits,
4009
4540
  usage: input.usage || { perRequest: [], totals: {} }
4010
4541
  };
@@ -4018,7 +4549,19 @@ function createAgent(opts) {
4018
4549
  status: "error",
4019
4550
  error: { message: err?.message, stack: err?.stack }
4020
4551
  });
4552
+ if (userTokenCounter) {
4553
+ const { setTokenCounter: setTokenCounter2 } = await Promise.resolve().then(() => (init_utilTokens(), utilTokens_exports));
4554
+ setTokenCounter2(void 0);
4555
+ }
4021
4556
  throw err;
4557
+ } finally {
4558
+ if (userTokenCounter) {
4559
+ try {
4560
+ const { setTokenCounter: setTokenCounter2 } = await Promise.resolve().then(() => (init_utilTokens(), utilTokens_exports));
4561
+ setTokenCounter2(void 0);
4562
+ } catch {
4563
+ }
4564
+ }
4022
4565
  }
4023
4566
  await finalizeTraceSession(traceSession, {
4024
4567
  agentRuntime: res.agent || runtime,
@@ -4069,19 +4612,67 @@ function createAgent(opts) {
4069
4612
  return invokeAgent(restoredState, config);
4070
4613
  };
4071
4614
  const resolveToolApproval = (state, resolution) => resolveToolApprovalState(state, resolution);
4615
+ const resolveUserQuestion = (state, resolution) => resolveUserQuestionState(state, resolution);
4072
4616
  const instance = {
4073
4617
  invoke: invokeAgent,
4074
4618
  snapshot: snapshotState,
4075
4619
  resume: resumeAgent,
4076
4620
  resolveToolApproval,
4621
+ resolveUserQuestion,
4077
4622
  asTool: ({ toolName, description, inputDescription }) => {
4078
4623
  const schema = zod.z.object({ input: zod.z.string().describe(inputDescription || "Input for delegated agent") });
4079
- return createTool({
4624
+ const delegatedTool = createTool({
4080
4625
  name: toolName,
4081
4626
  description: description || `Delegate task to agent ${opts.name || "Agent"}`,
4082
4627
  schema,
4083
4628
  func: async ({ input }) => {
4084
- const res = await instance.invoke({ messages: [{ role: "user", content: input }] });
4629
+ const ref = delegatedTool._stateRef;
4630
+ const parentCtx = ref.ctx || {};
4631
+ const parentMessages = Array.isArray(ref.messages) ? ref.messages : [];
4632
+ const parentRuntime = ref.parentRuntime;
4633
+ const resolvedSmart = parentRuntime?.smart || opts.__resolvedSmart;
4634
+ const delegationPolicy = resolvedSmart?.delegation || {
4635
+ mode: "role_based",
4636
+ maxDelegationDepth: 2,
4637
+ maxChildCalls: 4,
4638
+ childContextPolicy: "scoped"
4639
+ };
4640
+ const currentDepth = Number(parentCtx.__delegationDepth) || 0;
4641
+ if (delegationPolicy.mode === "off") {
4642
+ return { error: "Delegation disabled by runtime policy." };
4643
+ }
4644
+ if (currentDepth >= delegationPolicy.maxDelegationDepth) {
4645
+ return {
4646
+ error: `Delegation depth limit reached (${delegationPolicy.maxDelegationDepth}). Refusing nested call.`
4647
+ };
4648
+ }
4649
+ parentCtx.__delegatedCallCount = (parentCtx.__delegatedCallCount || 0) + 1;
4650
+ if (parentCtx.__delegatedCallCount > delegationPolicy.maxChildCalls) {
4651
+ return {
4652
+ error: `Child-call budget exhausted (${delegationPolicy.maxChildCalls}).`
4653
+ };
4654
+ }
4655
+ let childMessages;
4656
+ switch (delegationPolicy.childContextPolicy) {
4657
+ case "full":
4658
+ childMessages = [...parentMessages, { role: "user", content: input }];
4659
+ break;
4660
+ case "scoped": {
4661
+ const systemPrefix = parentMessages[0]?.role === "system" ? [parentMessages[0]] : [];
4662
+ const lastUser = [...parentMessages].reverse().find((m) => m?.role === "user");
4663
+ childMessages = [
4664
+ ...systemPrefix,
4665
+ ...lastUser ? [lastUser] : [],
4666
+ { role: "user", content: input }
4667
+ ];
4668
+ break;
4669
+ }
4670
+ case "minimal":
4671
+ default:
4672
+ childMessages = [{ role: "user", content: input }];
4673
+ }
4674
+ const childCtx = { __delegationDepth: currentDepth + 1 };
4675
+ const res = await instance.invoke({ messages: childMessages, ctx: childCtx });
4085
4676
  return {
4086
4677
  content: res.content,
4087
4678
  output: res.output,
@@ -4089,6 +4680,8 @@ function createAgent(opts) {
4089
4680
  };
4090
4681
  }
4091
4682
  });
4683
+ delegatedTool._stateRef = {};
4684
+ return delegatedTool;
4092
4685
  },
4093
4686
  asHandoff: ({ toolName, description, schema }) => {
4094
4687
  const finalName = toolName || `handoff_to_${runtime.name || "agent"}`;
@@ -4228,7 +4821,11 @@ function createAgentNode(opts) {
4228
4821
  };
4229
4822
  }
4230
4823
 
4824
+ // src/nodes/contextSummarize.ts
4825
+ init_utilTokens();
4826
+
4231
4827
  // src/smart/contextPolicy.ts
4828
+ init_utilTokens();
4232
4829
  function collectRecentTurns(messages, lastTurnsToKeep) {
4233
4830
  if (lastTurnsToKeep <= 0) return [];
4234
4831
  const systemPrefix = messages.filter((message, index) => index === 0 && message.role === "system");
@@ -4531,7 +5128,7 @@ function createContextSummarizeNode(opts) {
4531
5128
  const resolved = getResolvedSmartConfig(opts, state.agent);
4532
5129
  const config = resolved.summarization;
4533
5130
  if (!config.enable) return {};
4534
- const model = opts.model;
5131
+ const model = state.agent?.model || opts.model;
4535
5132
  const messages = state.messages || [];
4536
5133
  const onEvent = state.ctx?.__onEvent;
4537
5134
  const traceSession = state.ctx?.__traceSession;
@@ -4850,6 +5447,10 @@ ${canonicalFacts.length > 0 ? canonicalFacts.map((fact) => `- ${fact.key}: ${fac
4850
5447
  };
4851
5448
  };
4852
5449
  }
5450
+
5451
+ // src/index.ts
5452
+ init_utilTokens();
5453
+ init_utilTokens();
4853
5454
  var TOOL_RESPONSE_RECOVERY_MARKERS = [
4854
5455
  /ARCHIVED_TOOL_RESPONSE\s*\[/,
4855
5456
  /STRUCTURED_TOOL_RESPONSE\s*\[/,
@@ -5168,6 +5769,7 @@ function createContextTools(stateRef, opts) {
5168
5769
  }
5169
5770
 
5170
5771
  // src/graph/decisions.ts
5772
+ init_utilTokens();
5171
5773
  function needsSummarization(state, opts, summarizationEnabled) {
5172
5774
  if (!summarizationEnabled) {
5173
5775
  return false;
@@ -5330,12 +5932,38 @@ function createSmartAgent(opts) {
5330
5932
  toolResponses: resolved.toolResponses,
5331
5933
  useTodoList: planningEnabled
5332
5934
  };
5333
- const stateRef = { toolHistory: void 0, toolHistoryArchived: void 0, todoList: void 0, planVersion: 0, adherenceScore: 0 };
5334
- const contextTools = createContextTools(stateRef, { planningEnabled, includeGetToolResponse: false });
5335
- const getToolResponseTool = createGetToolResponseTool(stateRef);
5336
- const toolsWithoutRecovery = [...opts.tools ?? [], ...contextTools];
5337
- const toolsWithRecovery = [...toolsWithoutRecovery, getToolResponseTool];
5338
- const base = createAgent({ ...runtimeOpts, tools: toolsWithoutRecovery });
5935
+ const userTools = opts.tools ?? [];
5936
+ const factoryStateRef = { toolHistory: void 0, toolHistoryArchived: void 0, todoList: void 0, planVersion: 0, adherenceScore: 0 };
5937
+ const factoryContextTools = createContextTools(factoryStateRef, { planningEnabled, includeGetToolResponse: false });
5938
+ const askUserConfig = (() => {
5939
+ const raw = opts.humanInTheLoop?.askUser;
5940
+ if (!raw) return void 0;
5941
+ if (raw === true) return { enabled: true, allowFreeText: true };
5942
+ return {
5943
+ enabled: true,
5944
+ allowFreeText: raw.allowFreeText !== false,
5945
+ promptOverride: raw.promptOverride
5946
+ };
5947
+ })();
5948
+ const factoryToolsWithoutRecovery = [...userTools, ...factoryContextTools];
5949
+ if (askUserConfig) {
5950
+ factoryToolsWithoutRecovery.push(createAskUserQuestionTool(factoryStateRef, askUserConfig));
5951
+ }
5952
+ const soManagerFactory = opts.outputSchema ? new StructuredOutputManager(opts.outputSchema, resolveStrategy(opts.model)) : void 0;
5953
+ const modelCapabilities = opts.outputSchema ? getModelCapabilities(opts.model) : void 0;
5954
+ const responseFinalizeTool = soManagerFactory && modelCapabilities?.structuredOutput !== "native" ? soManagerFactory.getResponseTool() : void 0;
5955
+ const buildInvokeToolSet = () => {
5956
+ const ref = { toolHistory: void 0, toolHistoryArchived: void 0, todoList: void 0, planVersion: 0, adherenceScore: 0 };
5957
+ const ctxTools = createContextTools(ref, { planningEnabled, includeGetToolResponse: false });
5958
+ const recoveryTool = createGetToolResponseTool(ref);
5959
+ const base2 = [...userTools, ...ctxTools];
5960
+ if (responseFinalizeTool) base2.push(responseFinalizeTool);
5961
+ if (askUserConfig) base2.push(createAskUserQuestionTool(ref, askUserConfig));
5962
+ const without = base2;
5963
+ const withRec = [...without, recoveryTool];
5964
+ return { stateRef: ref, toolsWithoutRecovery: without, toolsWithRecovery: withRec };
5965
+ };
5966
+ const base = createAgent({ ...runtimeOpts, tools: factoryToolsWithoutRecovery });
5339
5967
  base.__runtime.runtimeProfile = resolved.runtimeProfile;
5340
5968
  base.__runtime.smart = resolved;
5341
5969
  const summarizationEnabled = resolved.summarization.enable !== false;
@@ -5371,17 +5999,17 @@ function createSmartAgent(opts) {
5371
5999
  const memoryFacts = await writeSummaryFactsToMemory(memoryStore, resolved, latestSummary, Math.max(1, (state.messages || []).filter((message) => message.role === "user").length));
5372
6000
  return { ...state, memoryFacts };
5373
6001
  }
5374
- function syncPlanState(state) {
5375
- if (!Array.isArray(stateRef.todoList)) return state;
6002
+ function syncPlanStateWith(ref, state) {
6003
+ if (!Array.isArray(ref.todoList)) return state;
5376
6004
  return {
5377
6005
  ...state,
5378
6006
  plan: {
5379
- version: stateRef.planVersion || state.planVersion || 1,
5380
- steps: stateRef.todoList,
6007
+ version: ref.planVersion || state.planVersion || 1,
6008
+ steps: ref.todoList,
5381
6009
  lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
5382
- adherenceScore: stateRef.adherenceScore || 0
6010
+ adherenceScore: ref.adherenceScore || 0
5383
6011
  },
5384
- planVersion: stateRef.planVersion || state.planVersion || 1
6012
+ planVersion: ref.planVersion || state.planVersion || 1
5385
6013
  };
5386
6014
  }
5387
6015
  function systemMessage2() {
@@ -5393,9 +6021,9 @@ function createSmartAgent(opts) {
5393
6021
  );
5394
6022
  return { role: "system", content: sys };
5395
6023
  }
5396
- function syncRuntimeTools(currentState, currentMessages) {
6024
+ function syncRuntimeTools(currentState, currentMessages, toolSet) {
5397
6025
  const needsRecoveryTool = hasToolResponseRecoveryReference(currentMessages);
5398
- const nextTools = needsRecoveryTool ? toolsWithRecovery : toolsWithoutRecovery;
6026
+ const nextTools = needsRecoveryTool ? toolSet.toolsWithRecovery : toolSet.toolsWithoutRecovery;
5399
6027
  const currentRuntime = currentState.agent || base.__runtime;
5400
6028
  if (currentRuntime.tools === nextTools) {
5401
6029
  return currentState;
@@ -5414,7 +6042,7 @@ function createSmartAgent(opts) {
5414
6042
  const toolCalls = Array.isArray(lastAssistant.tool_calls) ? lastAssistant.tool_calls : [];
5415
6043
  return toolCalls.length === 0;
5416
6044
  }
5417
- async function trySummarize(currentState, currentRawMessages) {
6045
+ async function trySummarize(currentState, currentRawMessages, ref) {
5418
6046
  if (!summarizer) return { state: currentState, rawMessages: currentRawMessages, compressed: false };
5419
6047
  const delta = await summarizer(currentState);
5420
6048
  if (!delta || Object.keys(delta).length === 0) {
@@ -5423,28 +6051,31 @@ function createSmartAgent(opts) {
5423
6051
  }
5424
6052
  const ctx = { ...currentState.ctx || {} };
5425
6053
  delete ctx.__summarizationExhausted;
5426
- const updated = await persistLatestSummary(syncPlanState({ ...currentState, ...delta, ctx }));
6054
+ const updated = await persistLatestSummary(syncPlanStateWith(ref, { ...currentState, ...delta, ctx }));
5427
6055
  return { state: updated, rawMessages: [...updated.messages || currentRawMessages], compressed: true };
5428
6056
  }
5429
6057
  const instance = {
5430
6058
  invoke: async (input, config) => {
6059
+ const toolSet = buildInvokeToolSet();
6060
+ const stateRef = toolSet.stateRef;
5431
6061
  stateRef.toolHistory = input.toolHistory;
5432
6062
  stateRef.toolHistoryArchived = input.toolHistoryArchived;
5433
6063
  stateRef.todoList = input.plan?.steps;
5434
6064
  stateRef.planVersion = input.planVersion || input.plan?.version || 0;
5435
6065
  stateRef.adherenceScore = input.plan?.adherenceScore || 0;
6066
+ const syncPlanFromRef = (state2) => syncPlanStateWith(stateRef, state2);
5436
6067
  const alreadyHasSystem = Array.isArray(input.messages) && input.messages[0]?.role === "system";
5437
6068
  const seedMessages = alreadyHasSystem ? [...input.messages || []] : [systemMessage2(), ...input.messages || []];
5438
- let state = syncRuntimeTools(await syncMemory({ ...input, messages: seedMessages }), seedMessages);
6069
+ let state = syncRuntimeTools(await syncMemory({ ...input, messages: seedMessages }), seedMessages, toolSet);
5439
6070
  let lastResult = null;
5440
6071
  let rawMessages = [...seedMessages];
5441
6072
  const effectiveMaxToolCalls = config?.limits?.maxToolCalls ?? resolved.limits.maxToolCalls ?? 10;
5442
6073
  const iterationLimit = Math.max(effectiveMaxToolCalls * 3 + 5, 30);
5443
6074
  for (let i = 0; i < iterationLimit; i++) {
5444
- state = syncRuntimeTools({ ...state, messages: rawMessages }, rawMessages);
6075
+ state = syncRuntimeTools({ ...state, messages: rawMessages }, rawMessages, toolSet);
5445
6076
  const next = summarizationEnabled ? decideBefore(state) : "agent";
5446
6077
  if (next === "contextSummarize" && summarizer) {
5447
- const result = await trySummarize(state, rawMessages);
6078
+ const result = await trySummarize(state, rawMessages, stateRef);
5448
6079
  state = result.state;
5449
6080
  rawMessages = result.rawMessages;
5450
6081
  if (result.compressed) continue;
@@ -5468,14 +6099,14 @@ function createSmartAgent(opts) {
5468
6099
  if (currentPlan && !state.plan) {
5469
6100
  state = { ...state, plan: currentPlan };
5470
6101
  }
5471
- state = syncRuntimeTools(syncPlanState(await syncMemory(state)), rawMessages);
6102
+ state = syncRuntimeTools(syncPlanFromRef(await syncMemory(state)), rawMessages, toolSet);
5472
6103
  stateRef.toolHistory = state.toolHistory;
5473
6104
  stateRef.toolHistoryArchived = state.toolHistoryArchived;
5474
6105
  if (state.ctx?.__needsSummarization && summarizer) {
5475
6106
  const ctx = { ...state.ctx || {} };
5476
6107
  delete ctx.__needsSummarization;
5477
6108
  state = { ...state, ctx };
5478
- const result = await trySummarize(state, rawMessages);
6109
+ const result = await trySummarize(state, rawMessages, stateRef);
5479
6110
  state = result.state;
5480
6111
  rawMessages = result.rawMessages;
5481
6112
  continue;
@@ -5486,7 +6117,7 @@ function createSmartAgent(opts) {
5486
6117
  if (summarizationEnabled) {
5487
6118
  const after = decideAfter(state);
5488
6119
  if (after === "contextSummarize" && summarizer) {
5489
- const result = await trySummarize(state, rawMessages);
6120
+ const result = await trySummarize(state, rawMessages, stateRef);
5490
6121
  state = result.state;
5491
6122
  rawMessages = result.rawMessages;
5492
6123
  if (result.compressed) continue;
@@ -5512,6 +6143,7 @@ function createSmartAgent(opts) {
5512
6143
  snapshot: base.snapshot,
5513
6144
  resume: base.resume,
5514
6145
  resolveToolApproval: base.resolveToolApproval,
6146
+ resolveUserQuestion: base.resolveUserQuestion,
5515
6147
  asTool: base.asTool,
5516
6148
  asHandoff: base.asHandoff,
5517
6149
  __runtime: base.__runtime
@@ -5925,7 +6557,50 @@ var ProviderError = class extends Error {
5925
6557
  };
5926
6558
 
5927
6559
  // src/providers/base.ts
6560
+ var DEFAULT_RETRY = {
6561
+ maxRetries: 3,
6562
+ baseDelayMs: 500,
6563
+ maxDelayMs: 8e3
6564
+ };
5928
6565
  var BaseProvider = class {
6566
+ constructor() {
6567
+ this.retryPolicy = DEFAULT_RETRY;
6568
+ }
6569
+ /**
6570
+ * Helper concrete providers can wrap their fetch with. Retries 429/5xx
6571
+ * with exponential backoff and respects Retry-After if the response
6572
+ * carries one.
6573
+ */
6574
+ async doFetchWithRetry(execute) {
6575
+ const policy = this.retryPolicy;
6576
+ const maxRetries = Math.max(0, policy.maxRetries ?? DEFAULT_RETRY.maxRetries);
6577
+ const baseDelay = Math.max(0, policy.baseDelayMs ?? DEFAULT_RETRY.baseDelayMs);
6578
+ const maxDelay = Math.max(baseDelay, policy.maxDelayMs ?? DEFAULT_RETRY.maxDelayMs);
6579
+ let lastResp;
6580
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
6581
+ const res = await execute();
6582
+ const isRetryable = res.status === 429 || res.status >= 500 && res.status < 600;
6583
+ if (!isRetryable) return res;
6584
+ const predicate = policy.shouldRetry;
6585
+ if (predicate && !predicate({ status: res.status }, attempt)) return res;
6586
+ if (attempt === maxRetries) {
6587
+ lastResp = res;
6588
+ break;
6589
+ }
6590
+ const retryAfter = res.headers.get("retry-after");
6591
+ let delay = Math.min(maxDelay, baseDelay * Math.pow(2, attempt));
6592
+ if (retryAfter) {
6593
+ const asNumber = Number(retryAfter);
6594
+ if (!Number.isNaN(asNumber)) delay = Math.min(maxDelay, Math.max(delay, asNumber * 1e3));
6595
+ }
6596
+ try {
6597
+ await res.text();
6598
+ } catch {
6599
+ }
6600
+ await new Promise((r) => setTimeout(r, delay));
6601
+ }
6602
+ return lastResp;
6603
+ }
5929
6604
  };
5930
6605
 
5931
6606
  // src/providers/utils/sse.ts
@@ -6052,6 +6727,7 @@ var OpenAIProvider = class extends BaseProvider {
6052
6727
  ...config.defaultHeaders,
6053
6728
  ...config.organization ? { "OpenAI-Organization": config.organization } : {}
6054
6729
  };
6730
+ if (config.retry) this.retryPolicy = { ...this.retryPolicy, ...config.retry };
6055
6731
  }
6056
6732
  async complete(request) {
6057
6733
  const body = this.buildRequestBody(request, false);
@@ -6199,15 +6875,13 @@ var OpenAIProvider = class extends BaseProvider {
6199
6875
  }
6200
6876
  async doFetch(body) {
6201
6877
  const url = `${this.baseURL}/chat/completions`;
6202
- const res = await fetch(url, {
6203
- method: "POST",
6204
- headers: {
6205
- "Content-Type": "application/json",
6206
- Authorization: `Bearer ${this.apiKey}`,
6207
- ...this.defaultHeaders
6208
- },
6209
- body: JSON.stringify(body)
6210
- });
6878
+ const headers = {
6879
+ "Content-Type": "application/json",
6880
+ Authorization: `Bearer ${this.apiKey}`,
6881
+ ...this.defaultHeaders
6882
+ };
6883
+ const payload = JSON.stringify(body);
6884
+ const res = await this.doFetchWithRetry(() => fetch(url, { method: "POST", headers, body: payload }));
6211
6885
  if (!res.ok) {
6212
6886
  const text = await res.text().catch(() => "");
6213
6887
  throw new ProviderError(
@@ -6277,6 +6951,8 @@ var AnthropicProvider = class extends BaseProvider {
6277
6951
  this.defaultModel = config.defaultModel ?? "claude-sonnet-4-20250514";
6278
6952
  this.defaultHeaders = config.defaultHeaders ?? {};
6279
6953
  this.anthropicVersion = config.anthropicVersion ?? "2023-06-01";
6954
+ this.promptCachingEnabled = config.prompt_caching?.enabled === true;
6955
+ if (config.retry) this.retryPolicy = { ...this.retryPolicy, ...config.retry };
6280
6956
  }
6281
6957
  async complete(request) {
6282
6958
  const body = this.buildRequestBody(request, false);
@@ -6395,12 +7071,22 @@ var AnthropicProvider = class extends BaseProvider {
6395
7071
  max_tokens: request.maxTokens ?? 4096,
6396
7072
  stream
6397
7073
  };
6398
- if (system) body.system = system;
7074
+ if (system) {
7075
+ if (this.promptCachingEnabled) {
7076
+ body.system = [{ type: "text", text: system, cache_control: { type: "ephemeral" } }];
7077
+ } else {
7078
+ body.system = system;
7079
+ }
7080
+ }
6399
7081
  if (request.temperature != null) body.temperature = request.temperature;
6400
7082
  if (request.topP != null) body.top_p = request.topP;
6401
7083
  if (request.stop) body.stop_sequences = request.stop;
6402
7084
  if (request.tools?.length) {
6403
- body.tools = request.tools.map((t) => this.toAnthropicTool(t));
7085
+ const tools = request.tools.map((t) => this.toAnthropicTool(t));
7086
+ if (this.promptCachingEnabled && tools.length > 0) {
7087
+ tools[tools.length - 1] = { ...tools[tools.length - 1], cache_control: { type: "ephemeral" } };
7088
+ }
7089
+ body.tools = tools;
6404
7090
  }
6405
7091
  if (request.toolChoice != null) {
6406
7092
  if (request.toolChoice === "auto") {
@@ -6499,16 +7185,14 @@ var AnthropicProvider = class extends BaseProvider {
6499
7185
  }
6500
7186
  async doFetch(body) {
6501
7187
  const url = `${this.baseURL}/v1/messages`;
6502
- const res = await fetch(url, {
6503
- method: "POST",
6504
- headers: {
6505
- "Content-Type": "application/json",
6506
- "x-api-key": this.apiKey,
6507
- "anthropic-version": this.anthropicVersion,
6508
- ...this.defaultHeaders
6509
- },
6510
- body: JSON.stringify(body)
6511
- });
7188
+ const headers = {
7189
+ "Content-Type": "application/json",
7190
+ "x-api-key": this.apiKey,
7191
+ "anthropic-version": this.anthropicVersion,
7192
+ ...this.defaultHeaders
7193
+ };
7194
+ const payload = JSON.stringify(body);
7195
+ const res = await this.doFetchWithRetry(() => fetch(url, { method: "POST", headers, body: payload }));
6512
7196
  if (!res.ok) {
6513
7197
  const text = await res.text().catch(() => "");
6514
7198
  throw new ProviderError(
@@ -6720,6 +7404,8 @@ var BedrockProvider = class extends BaseProvider {
6720
7404
  this.region = config.region;
6721
7405
  this.defaultModel = config.defaultModel ?? "anthropic.claude-sonnet-4-20250514-v1:0";
6722
7406
  this.defaultHeaders = config.defaultHeaders ?? {};
7407
+ this.promptCachingEnabled = config.prompt_caching?.enabled === true;
7408
+ if (config.retry) this.retryPolicy = { ...this.retryPolicy, ...config.retry };
6723
7409
  this.credentials = {
6724
7410
  accessKeyId: config.accessKeyId ?? process.env.AWS_ACCESS_KEY_ID ?? "",
6725
7411
  secretAccessKey: config.secretAccessKey ?? process.env.AWS_SECRET_ACCESS_KEY ?? "",
@@ -6758,7 +7444,9 @@ var BedrockProvider = class extends BaseProvider {
6758
7444
  messages: messages.map((m) => this.toBedrockMessage(m))
6759
7445
  };
6760
7446
  if (system.length > 0) {
6761
- body.system = system.map((s) => ({ text: s }));
7447
+ const systemBlocks = system.map((s) => ({ text: s }));
7448
+ if (this.promptCachingEnabled) systemBlocks.push({ cachePoint: { type: "default" } });
7449
+ body.system = systemBlocks;
6762
7450
  }
6763
7451
  const inferenceConfig = {};
6764
7452
  if (request.maxTokens != null) inferenceConfig.maxTokens = request.maxTokens;
@@ -6767,8 +7455,12 @@ var BedrockProvider = class extends BaseProvider {
6767
7455
  if (request.stop) inferenceConfig.stopSequences = request.stop;
6768
7456
  if (Object.keys(inferenceConfig).length > 0) body.inferenceConfig = inferenceConfig;
6769
7457
  if (request.tools?.length) {
7458
+ const tools = request.tools.map((t) => this.toBedrockTool(t));
7459
+ if (this.promptCachingEnabled && tools.length > 0) {
7460
+ tools.push({ cachePoint: { type: "default" } });
7461
+ }
6770
7462
  body.toolConfig = {
6771
- tools: request.tools.map((t) => this.toBedrockTool(t))
7463
+ tools
6772
7464
  };
6773
7465
  if (request.toolChoice != null) {
6774
7466
  if (request.toolChoice === "auto") {
@@ -6865,23 +7557,21 @@ var BedrockProvider = class extends BaseProvider {
6865
7557
  const host = `bedrock-runtime.${this.region}.amazonaws.com`;
6866
7558
  const url = `https://${host}/model/${encodeURIComponent(modelId)}/converse`;
6867
7559
  const bodyStr = JSON.stringify(body);
6868
- const headers = signRequest({
6869
- method: "POST",
6870
- url,
6871
- headers: {
6872
- "Content-Type": "application/json",
6873
- Accept: "application/json",
6874
- ...this.defaultHeaders
6875
- },
6876
- body: bodyStr,
6877
- region: this.region,
6878
- service: "bedrock",
6879
- credentials: this.credentials
6880
- });
6881
- const res = await fetch(url, {
6882
- method: "POST",
6883
- headers,
6884
- body: bodyStr
7560
+ const res = await this.doFetchWithRetry(() => {
7561
+ const headers = signRequest({
7562
+ method: "POST",
7563
+ url,
7564
+ headers: {
7565
+ "Content-Type": "application/json",
7566
+ Accept: "application/json",
7567
+ ...this.defaultHeaders
7568
+ },
7569
+ body: bodyStr,
7570
+ region: this.region,
7571
+ service: "bedrock",
7572
+ credentials: this.credentials
7573
+ });
7574
+ return fetch(url, { method: "POST", headers, body: bodyStr });
6885
7575
  });
6886
7576
  if (!res.ok) {
6887
7577
  const text = await res.text().catch(() => "");
@@ -7779,6 +8469,7 @@ exports.countApproxTokens = countApproxTokens;
7779
8469
  exports.countMessagesTokens = countMessagesTokens;
7780
8470
  exports.createAgent = createAgent;
7781
8471
  exports.createAgentNode = createAgentNode;
8472
+ exports.createAskUserQuestionTool = createAskUserQuestionTool;
7782
8473
  exports.createCodeGuardrail = createCodeGuardrail;
7783
8474
  exports.createContextSummarizeNode = createContextSummarizeNode;
7784
8475
  exports.createContextTools = createContextTools;
@@ -7795,6 +8486,7 @@ exports.createToolLimitFinalizeNode = createToolLimitFinalizeNode;
7795
8486
  exports.createToolsNode = createToolsNode;
7796
8487
  exports.customCallbackRule = customCallbackRule;
7797
8488
  exports.customSink = customSink;
8489
+ exports.defaultTokenCounter = defaultTokenCounter;
7798
8490
  exports.emptyUsage = emptyUsage;
7799
8491
  exports.evaluateGuardrails = evaluateGuardrails;
7800
8492
  exports.fileSink = fileSink;
@@ -7807,6 +8499,7 @@ exports.generateTraceId = generateTraceId;
7807
8499
  exports.getModelCapabilities = getModelCapabilities;
7808
8500
  exports.getNestedValue = getNestedValue;
7809
8501
  exports.getResolvedSmartConfig = getResolvedSmartConfig;
8502
+ exports.getTokenCounter = getTokenCounter;
7810
8503
  exports.hasToolResponseRecoveryReference = hasToolResponseRecoveryReference;
7811
8504
  exports.httpSink = httpSink;
7812
8505
  exports.isSmartModel = isSmartModel;
@@ -7818,8 +8511,10 @@ exports.regexRule = regexRule;
7818
8511
  exports.resolveMemoryStore = resolveMemoryStore;
7819
8512
  exports.resolveStrategy = resolveStrategy;
7820
8513
  exports.resolveToolApprovalState = resolveToolApprovalState;
8514
+ exports.resolveUserQuestionState = resolveUserQuestionState;
7821
8515
  exports.restoreSnapshot = restoreSnapshot;
7822
8516
  exports.runSmartAgentEvalHarness = runSmartAgentEvalHarness;
8517
+ exports.setTokenCounter = setTokenCounter;
7823
8518
  exports.startStreamingSession = startStreamingSession;
7824
8519
  exports.traceSessionToOtlp = traceSessionToOtlp;
7825
8520
  exports.withTools = withTools;