@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.mjs CHANGED
@@ -8,7 +8,99 @@ import { nanoid } from 'nanoid';
8
8
  import crypto, { randomUUID, createHash, createHmac } from 'crypto';
9
9
  import AjvModule from 'ajv';
10
10
 
11
- // src/adapters/langchain.ts
11
+ var __defProp = Object.defineProperty;
12
+ var __getOwnPropNames = Object.getOwnPropertyNames;
13
+ var __esm = (fn, res) => function __init() {
14
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
15
+ };
16
+ var __export = (target, all) => {
17
+ for (var name in all)
18
+ __defProp(target, name, { get: all[name], enumerable: true });
19
+ };
20
+
21
+ // src/utils/utilTokens.ts
22
+ var utilTokens_exports = {};
23
+ __export(utilTokens_exports, {
24
+ countApproxTokens: () => countApproxTokens,
25
+ countMessagesTokens: () => countMessagesTokens,
26
+ defaultTokenCounter: () => defaultTokenCounter,
27
+ getTokenCounter: () => getTokenCounter,
28
+ setTokenCounter: () => setTokenCounter
29
+ });
30
+ function defaultTokenCounter(text) {
31
+ if (!text) return 0;
32
+ const nonAsciiCount = (text.match(/[^\x00-\x7F]/g) || []).length;
33
+ const asciiCount = text.length - nonAsciiCount;
34
+ return Math.ceil(asciiCount / 4 + nonAsciiCount / 1.5);
35
+ }
36
+ function setTokenCounter(counter) {
37
+ activeTokenCounter = typeof counter === "function" ? counter : defaultTokenCounter;
38
+ }
39
+ function getTokenCounter() {
40
+ return activeTokenCounter;
41
+ }
42
+ function countApproxTokens(text) {
43
+ return activeTokenCounter(text || "");
44
+ }
45
+ function extractMessageText(m) {
46
+ if (!m) return "";
47
+ const parts = [];
48
+ parts.push(m.role || "");
49
+ if (m.name) parts.push(m.name);
50
+ if (typeof m.content === "string") {
51
+ parts.push(m.content);
52
+ } else if (Array.isArray(m.content)) {
53
+ for (const c of m.content) {
54
+ if (typeof c === "string") parts.push(c);
55
+ else if (c?.text) parts.push(c.text);
56
+ else if (c?.content) parts.push(c.content);
57
+ else if (c?.type === "tool_use" && c?.input) {
58
+ parts.push(c.name || "");
59
+ parts.push(typeof c.input === "string" ? c.input : JSON.stringify(c.input));
60
+ } else if (c != null && typeof c === "object") {
61
+ parts.push(JSON.stringify(c));
62
+ }
63
+ }
64
+ } else if (m.content != null && typeof m.content === "object") {
65
+ parts.push(JSON.stringify(m.content));
66
+ }
67
+ if (Array.isArray(m.tool_calls)) {
68
+ for (const tc of m.tool_calls) {
69
+ if (tc?.function?.name) parts.push(tc.function.name);
70
+ if (tc?.function?.arguments) {
71
+ parts.push(
72
+ typeof tc.function.arguments === "string" ? tc.function.arguments : JSON.stringify(tc.function.arguments)
73
+ );
74
+ }
75
+ if (tc?.name && !tc?.function?.name) parts.push(tc.name);
76
+ if (tc?.args && !tc?.function?.arguments) {
77
+ parts.push(typeof tc.args === "string" ? tc.args : JSON.stringify(tc.args));
78
+ }
79
+ }
80
+ }
81
+ if (Array.isArray(m.additional_kwargs?.tool_calls)) {
82
+ for (const tc of m.additional_kwargs.tool_calls) {
83
+ if (tc?.function?.name) parts.push(tc.function.name);
84
+ if (tc?.function?.arguments) {
85
+ parts.push(
86
+ typeof tc.function.arguments === "string" ? tc.function.arguments : JSON.stringify(tc.function.arguments)
87
+ );
88
+ }
89
+ }
90
+ }
91
+ return parts.join("\n");
92
+ }
93
+ function countMessagesTokens(messages) {
94
+ if (!Array.isArray(messages) || messages.length === 0) return 0;
95
+ const allText = messages.map(extractMessageText).join("\n");
96
+ return countApproxTokens(allText) + messages.length * 4;
97
+ }
98
+ var activeTokenCounter;
99
+ var init_utilTokens = __esm({
100
+ "src/utils/utilTokens.ts"() {
101
+ activeTokenCounter = defaultTokenCounter;
102
+ }
103
+ });
12
104
  var moduleName = "@langchain/core/tools";
13
105
  var cachedToolFactory;
14
106
  var cachedHasAttemptedFactory = false;
@@ -316,7 +408,9 @@ function createTool({
316
408
  needsApproval,
317
409
  approvalPrompt,
318
410
  approvalDefaults,
319
- maxExecutionsPerRun
411
+ maxExecutionsPerRun,
412
+ cache,
413
+ retry
320
414
  }) {
321
415
  const execute = async (input) => func(input);
322
416
  const toolRecord = {
@@ -341,6 +435,12 @@ function createTool({
341
435
  if (maxExecutionsPerRun !== void 0) {
342
436
  toolRecord.maxExecutionsPerRun = maxExecutionsPerRun;
343
437
  }
438
+ if (cache !== void 0) {
439
+ toolRecord.cache = cache;
440
+ }
441
+ if (retry !== void 0) {
442
+ toolRecord.retry = retry;
443
+ }
344
444
  toolRecord.__source = toolRecord.__source || "smart";
345
445
  toolRecord.__impl = func;
346
446
  return toolRecord;
@@ -643,6 +743,7 @@ function createResolverNode() {
643
743
  toolHistory: state.toolHistory || [],
644
744
  toolHistoryArchived: state.toolHistoryArchived || [],
645
745
  pendingApprovals: state.pendingApprovals || [],
746
+ pendingUserQuestions: state.pendingUserQuestions || [],
646
747
  plan: state.plan || null,
647
748
  planVersion: state.planVersion || 0
648
749
  };
@@ -1882,7 +1983,8 @@ function createAgentCoreNode(opts) {
1882
1983
  todoListPrompt: opts.todoListPrompt,
1883
1984
  limits: opts.limits,
1884
1985
  outputSchema: opts.outputSchema,
1885
- tracing: opts.tracing
1986
+ tracing: opts.tracing,
1987
+ responseFormat: void 0
1886
1988
  };
1887
1989
  const tools = runtime.tools ?? [];
1888
1990
  const shouldUseStrictToolCalling = Boolean(
@@ -2084,7 +2186,7 @@ function createAgentCoreNode(opts) {
2084
2186
  // src/smart/runtimeConfig.ts
2085
2187
  var DEFAULT_CRITICAL_TOOLS = ["response", "manage_todo_list", "get_tool_response"];
2086
2188
  var BASE_DEFAULTS = {
2087
- limits: { maxToolCalls: 8, maxParallelTools: 2, maxContextTokens: 24e3 },
2189
+ limits: { maxToolCalls: 8, maxParallelTools: 2, maxContextTokens: 24e3, maxTotalOutputTokens: 0, maxCostUsd: 0, maxWallClockMs: 0 },
2088
2190
  summarization: {
2089
2191
  enable: true,
2090
2192
  maxTokens: 24e3,
@@ -2211,7 +2313,10 @@ function mergeLimits(base, override) {
2211
2313
  return {
2212
2314
  maxToolCalls: override?.maxToolCalls ?? base.maxToolCalls,
2213
2315
  maxParallelTools: override?.maxParallelTools ?? base.maxParallelTools,
2214
- maxContextTokens: override?.maxContextTokens ?? base.maxContextTokens
2316
+ maxContextTokens: override?.maxContextTokens ?? base.maxContextTokens,
2317
+ maxTotalOutputTokens: override?.maxTotalOutputTokens ?? base.maxTotalOutputTokens ?? 0,
2318
+ maxCostUsd: override?.maxCostUsd ?? base.maxCostUsd ?? 0,
2319
+ maxWallClockMs: override?.maxWallClockMs ?? base.maxWallClockMs ?? 0
2215
2320
  };
2216
2321
  }
2217
2322
  function resolveBaseProfile(opts, runtimeProfile) {
@@ -2289,66 +2394,8 @@ function getResolvedSmartConfig(opts, runtime) {
2289
2394
  return runtime?.smart || normalizeSmartAgentOptions(opts);
2290
2395
  }
2291
2396
 
2292
- // src/utils/utilTokens.ts
2293
- function countApproxTokens(text) {
2294
- if (!text) return 0;
2295
- const nonAsciiCount = (text.match(/[^\x00-\x7F]/g) || []).length;
2296
- const asciiCount = text.length - nonAsciiCount;
2297
- return Math.ceil(asciiCount / 4 + nonAsciiCount / 1.5);
2298
- }
2299
- function extractMessageText(m) {
2300
- if (!m) return "";
2301
- const parts = [];
2302
- parts.push(m.role || "");
2303
- if (m.name) parts.push(m.name);
2304
- if (typeof m.content === "string") {
2305
- parts.push(m.content);
2306
- } else if (Array.isArray(m.content)) {
2307
- for (const c of m.content) {
2308
- if (typeof c === "string") parts.push(c);
2309
- else if (c?.text) parts.push(c.text);
2310
- else if (c?.content) parts.push(c.content);
2311
- else if (c?.type === "tool_use" && c?.input) {
2312
- parts.push(c.name || "");
2313
- parts.push(typeof c.input === "string" ? c.input : JSON.stringify(c.input));
2314
- } else if (c != null && typeof c === "object") {
2315
- parts.push(JSON.stringify(c));
2316
- }
2317
- }
2318
- } else if (m.content != null && typeof m.content === "object") {
2319
- parts.push(JSON.stringify(m.content));
2320
- }
2321
- if (Array.isArray(m.tool_calls)) {
2322
- for (const tc of m.tool_calls) {
2323
- if (tc?.function?.name) parts.push(tc.function.name);
2324
- if (tc?.function?.arguments) {
2325
- parts.push(
2326
- typeof tc.function.arguments === "string" ? tc.function.arguments : JSON.stringify(tc.function.arguments)
2327
- );
2328
- }
2329
- if (tc?.name && !tc?.function?.name) parts.push(tc.name);
2330
- if (tc?.args && !tc?.function?.arguments) {
2331
- parts.push(typeof tc.args === "string" ? tc.args : JSON.stringify(tc.args));
2332
- }
2333
- }
2334
- }
2335
- if (Array.isArray(m.additional_kwargs?.tool_calls)) {
2336
- for (const tc of m.additional_kwargs.tool_calls) {
2337
- if (tc?.function?.name) parts.push(tc.function.name);
2338
- if (tc?.function?.arguments) {
2339
- parts.push(
2340
- typeof tc.function.arguments === "string" ? tc.function.arguments : JSON.stringify(tc.function.arguments)
2341
- );
2342
- }
2343
- }
2344
- }
2345
- return parts.join("\n");
2346
- }
2347
- function countMessagesTokens(messages) {
2348
- if (!Array.isArray(messages) || messages.length === 0) return 0;
2349
- const allText = messages.map(extractMessageText).join("\n");
2350
- return countApproxTokens(allText) + messages.length * 4;
2351
- }
2397
+ // src/smart/toolResponses.ts
2398
+ init_utilTokens();
2352
2399
 
2353
2400
  // src/utils/content.ts
2354
2401
  function contentToString(content) {
@@ -2538,16 +2585,20 @@ function createToolsNode(initialTools, opts) {
2538
2585
  const traceSession = state.ctx?.__traceSession;
2539
2586
  const pendingApprovals = Array.isArray(state.pendingApprovals) ? state.pendingApprovals.map((entry) => ({ ...entry })) : [];
2540
2587
  const pendingByCallId = new Map(pendingApprovals.map((entry) => [entry.toolCallId, entry]));
2588
+ const pendingUserQuestions = Array.isArray(state.pendingUserQuestions) ? state.pendingUserQuestions.map((entry) => ({ ...entry })) : [];
2541
2589
  let awaitingApproval = false;
2590
+ let awaitingUserQuestion = false;
2542
2591
  for (const tool of toolByName.values()) {
2543
2592
  const anyTool = tool;
2544
2593
  if (anyTool._stateRef && typeof anyTool._stateRef === "object") {
2545
2594
  anyTool._stateRef.toolHistory = state.toolHistory;
2546
2595
  anyTool._stateRef.toolHistoryArchived = state.toolHistoryArchived;
2547
2596
  anyTool._stateRef.pendingApprovals = pendingApprovals;
2597
+ anyTool._stateRef.pendingUserQuestions = pendingUserQuestions;
2548
2598
  anyTool._stateRef.messages = state.messages;
2549
2599
  anyTool._stateRef.ctx = state.ctx || (state.ctx = {});
2550
2600
  anyTool._stateRef.__onEvent = onEvent;
2601
+ anyTool._stateRef.parentRuntime = runtime;
2551
2602
  }
2552
2603
  }
2553
2604
  const last = state.messages[state.messages.length - 1];
@@ -2558,6 +2609,7 @@ function createToolsNode(initialTools, opts) {
2558
2609
  const remaining = Math.max(0, limits.maxToolCalls - toolCount);
2559
2610
  const planned = toolCalls.slice(0, remaining);
2560
2611
  const skipped = toolCalls.slice(remaining);
2612
+ const orderedSlots = planned.map(() => []);
2561
2613
  const isCancelled = () => {
2562
2614
  if (abortSignal?.aborted) return { cancelled: true, reason: "aborted" };
2563
2615
  if (cancellationToken && cancellationToken.isCancellationRequested) return { cancelled: true, reason: "cancelled" };
@@ -2574,7 +2626,9 @@ function createToolsNode(initialTools, opts) {
2574
2626
  }
2575
2627
  state.ctx = ctx;
2576
2628
  };
2577
- const runOne = async (tc) => {
2629
+ const runOne = async (tc, slot = appended) => {
2630
+ var _a;
2631
+ const push = (msg) => slot.push(msg);
2578
2632
  const cancelState = isCancelled();
2579
2633
  if (cancelState.cancelled) {
2580
2634
  const ctx = state.ctx = state.ctx || {};
@@ -2585,7 +2639,7 @@ function createToolsNode(initialTools, opts) {
2585
2639
  }
2586
2640
  const tool = toolByName.get(tc.name);
2587
2641
  if (!tool) {
2588
- appended.push({ role: "tool", content: `Tool not found: ${tc.name}`, tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
2642
+ push({ role: "tool", content: `Tool not found: ${tc.name}`, tool_call_id: tc.id || `${tc.name}_unknown`, name: tc.name });
2589
2643
  onEvent?.({ type: "tool_call", phase: "error", name: tc.name, id: tc.id, args: tc.args, error: { message: "Tool not found" } });
2590
2644
  toolCount += 1;
2591
2645
  return { status: "error" };
@@ -2601,7 +2655,7 @@ function createToolsNode(initialTools, opts) {
2601
2655
  const validatedArgs = validateToolArgs(tool, args);
2602
2656
  if (!validatedArgs.ok && resolved.toolResponses.schemaValidation === "strict") {
2603
2657
  const errorMessage = `Tool argument validation failed for ${tc.name}: ${validatedArgs.message}`;
2604
- appended.push({ role: "tool", content: errorMessage, tool_call_id: toolCallId, name: tc.name });
2658
+ push({ role: "tool", content: errorMessage, tool_call_id: toolCallId, name: tc.name });
2605
2659
  markToolFailure(errorMessage, tc.name, toolCallId);
2606
2660
  onEvent?.({ type: "tool_call", phase: "error", name: tc.name, id: tc.id, args, error: { message: errorMessage } });
2607
2661
  toolCount += 1;
@@ -2614,6 +2668,7 @@ function createToolsNode(initialTools, opts) {
2614
2668
  const toolStateRef = tool._stateRef && typeof tool._stateRef === "object" ? tool._stateRef : null;
2615
2669
  if (toolStateRef) {
2616
2670
  toolStateRef.pendingApprovals = pendingApprovals;
2671
+ toolStateRef.pendingUserQuestions = pendingUserQuestions;
2617
2672
  toolStateRef.ctx = state.ctx || (state.ctx = {});
2618
2673
  toolStateRef.__currentToolCallId = toolCallId;
2619
2674
  delete toolStateRef.__awaitingApproval;
@@ -2623,7 +2678,7 @@ function createToolsNode(initialTools, opts) {
2623
2678
  const currentExecutions = countSuccessfulToolExecutions(toolHistory, toolName);
2624
2679
  if (currentExecutions >= maxExecutionsPerRun) {
2625
2680
  const limitMessage = `Skipped tool due to per-tool execution limit: ${toolName} (${maxExecutionsPerRun}/run)`;
2626
- appended.push({ role: "tool", content: limitMessage, tool_call_id: toolCallId, name: tc.name });
2681
+ push({ role: "tool", content: limitMessage, tool_call_id: toolCallId, name: tc.name });
2627
2682
  onEvent?.({ type: "tool_call", phase: "skipped", name: toolName, id: tc.id, args });
2628
2683
  recordTraceEvent(traceSession, {
2629
2684
  type: "tool_call",
@@ -2683,7 +2738,7 @@ function createToolsNode(initialTools, opts) {
2683
2738
  }
2684
2739
  if (approvalEntry.status === "rejected") {
2685
2740
  const rejectionMessage = approvalEntry.comment || "Tool call rejected by reviewer.";
2686
- appended.push({ role: "tool", content: `Tool call rejected: ${rejectionMessage}`, tool_call_id: toolCallId, name: tc.name });
2741
+ push({ role: "tool", content: `Tool call rejected: ${rejectionMessage}`, tool_call_id: toolCallId, name: tc.name });
2687
2742
  approvalEntry.metadata = { ...approvalEntry.metadata || {}, resolution: "rejected" };
2688
2743
  approvalEntry.status = "executed";
2689
2744
  approvalEntry.resolvedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -2701,23 +2756,102 @@ function createToolsNode(initialTools, opts) {
2701
2756
  const start = Date.now();
2702
2757
  const sanitizedArgs = sanitizeTracePayload(args);
2703
2758
  const inputBytes = traceSession?.resolvedConfig.logData ? estimatePayloadBytes(sanitizedArgs) : void 0;
2759
+ const cacheCfg = tool.cache;
2760
+ const cacheEnabled = cacheCfg === true || cacheCfg && typeof cacheCfg === "object";
2761
+ const cacheKeyFn = cacheEnabled && typeof cacheCfg === "object" ? cacheCfg.keyFn : void 0;
2762
+ const stableJson = (value) => {
2763
+ try {
2764
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
2765
+ if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
2766
+ const keys = Object.keys(value).sort();
2767
+ return `{${keys.map((k) => JSON.stringify(k) + ":" + stableJson(value[k])).join(",")}}`;
2768
+ } catch {
2769
+ return String(value);
2770
+ }
2771
+ };
2772
+ const cacheKey = cacheEnabled ? `${toolName}|${cacheKeyFn ? cacheKeyFn(args) : stableJson(args)}` : void 0;
2773
+ const toolCache = state.toolCache = state.toolCache || {};
2774
+ let cachedHit = void 0;
2775
+ if (cacheKey && Object.prototype.hasOwnProperty.call(toolCache, cacheKey)) {
2776
+ const entry = toolCache[cacheKey];
2777
+ const ttl = cacheEnabled && typeof cacheCfg === "object" ? cacheCfg.ttlMs : void 0;
2778
+ if (!ttl || entry?.timestamp && Date.now() - entry.timestamp <= ttl) {
2779
+ cachedHit = entry?.value;
2780
+ }
2781
+ }
2782
+ const retryCfg = tool.retry;
2783
+ const failureStats = (_a = state.ctx).__toolFailureCounts || (_a.__toolFailureCounts = {});
2784
+ if (retryCfg?.circuitBreakerThreshold && failureStats[toolName] >= retryCfg.circuitBreakerThreshold) {
2785
+ const breakerMsg = `Tool ${toolName} circuit breaker open after ${failureStats[toolName]} consecutive failures`;
2786
+ push({ role: "tool", content: breakerMsg, tool_call_id: toolCallId, name: tc.name });
2787
+ markToolFailure(breakerMsg, toolName, toolCallId);
2788
+ onEvent?.({ type: "tool_call", phase: "error", name: toolName, id: tc.id, args, error: { message: breakerMsg } });
2789
+ toolCount += 1;
2790
+ return { status: "error" };
2791
+ }
2704
2792
  try {
2793
+ if (cachedHit !== void 0) {
2794
+ const executionId2 = nanoid();
2795
+ const responsePolicy2 = applyToolResponseHardCap(toolName, cachedHit, executionId2, resolved);
2796
+ toolHistory.push({
2797
+ executionId: executionId2,
2798
+ toolName,
2799
+ args,
2800
+ output: cachedHit,
2801
+ rawOutput: cachedHit,
2802
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2803
+ tool_call_id: tc.id,
2804
+ summarized: false,
2805
+ originalTokenCount: responsePolicy2.tokenCount,
2806
+ classification: responsePolicy2.classification,
2807
+ retentionPolicy: "keep_full",
2808
+ fromCache: true,
2809
+ status: "success"
2810
+ });
2811
+ push({ role: "tool", content: responsePolicy2.content, tool_call_id: tc.id || `${tc.name}_${toolCount}`, name: tc.name });
2812
+ onEvent?.({ type: "tool_call", phase: "success", name: toolName, id: tc.id, args, result: cachedHit, durationMs: 0 });
2813
+ toolCount += 1;
2814
+ return { status: "success" };
2815
+ }
2705
2816
  onEvent?.({ type: "tool_call", phase: "start", name: toolName, id: tc.id, args });
2706
2817
  onProgress?.({ stage: "tools", message: `Running tool ${tc.name}` });
2707
2818
  const anyTool = tool;
2708
2819
  const callOptions = { cancellationToken, signal: abortSignal };
2820
+ const invokeOnce = async () => {
2821
+ if (typeof anyTool.func === "function") return anyTool.func(args, callOptions);
2822
+ if (typeof anyTool.invoke === "function") return anyTool.invoke(args, callOptions);
2823
+ if (typeof anyTool.call === "function") return anyTool.call(args, callOptions);
2824
+ if (typeof anyTool._call === "function") return anyTool._call(args, callOptions);
2825
+ if (typeof anyTool.run === "function") return anyTool.run(args, callOptions);
2826
+ throw new Error("Tool is not invokable");
2827
+ };
2709
2828
  let output;
2710
- if (typeof anyTool.func === "function") output = await anyTool.func(args, callOptions);
2711
- else if (typeof anyTool.invoke === "function") output = await anyTool.invoke(args, callOptions);
2712
- else if (typeof anyTool.call === "function") output = await anyTool.call(args, callOptions);
2713
- else if (typeof anyTool._call === "function") output = await anyTool._call(args, callOptions);
2714
- else if (typeof anyTool.run === "function") output = await anyTool.run(args, callOptions);
2715
- else throw new Error("Tool is not invokable");
2829
+ const maxRetries = Math.max(0, Number(retryCfg?.maxRetries) || 0);
2830
+ const backoffMs = Math.max(0, Number(retryCfg?.backoffMs) || 250);
2831
+ let attempt = 0;
2832
+ while (true) {
2833
+ try {
2834
+ output = await invokeOnce();
2835
+ failureStats[toolName] = 0;
2836
+ break;
2837
+ } catch (retryErr) {
2838
+ attempt += 1;
2839
+ const shouldRetry = retryCfg?.shouldRetry ? retryCfg.shouldRetry(retryErr, attempt) : true;
2840
+ if (attempt > maxRetries || !shouldRetry) {
2841
+ failureStats[toolName] = (failureStats[toolName] || 0) + 1;
2842
+ throw retryErr;
2843
+ }
2844
+ await new Promise((r) => setTimeout(r, backoffMs * Math.pow(2, attempt - 1)));
2845
+ }
2846
+ }
2847
+ if (cacheKey) {
2848
+ toolCache[cacheKey] = { value: output, timestamp: Date.now() };
2849
+ }
2716
2850
  const durationMs = Date.now() - start;
2717
2851
  const executionId = nanoid();
2718
2852
  if (output && typeof output === "object" && output.__handoff && output.__handoff.runtime) {
2719
2853
  toolHistory.push({ executionId, toolName, args, output: "handoff:ok", rawOutput: output, timestamp: (/* @__PURE__ */ new Date()).toISOString(), tool_call_id: tc.id, status: "handoff" });
2720
- appended.push({ role: "tool", content: "ok", tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
2854
+ push({ role: "tool", content: "ok", tool_call_id: tc.id || `${tc.name}_${toolCount}`, name: tc.name });
2721
2855
  state.agent = output.__handoff.runtime;
2722
2856
  onEvent?.({ type: "handoff", from: runtime.name, to: state.agent?.name, toolName });
2723
2857
  onEvent?.({ type: "tool_call", phase: "success", name: toolName, id: tc.id, args, result: "handoff", durationMs });
@@ -2746,6 +2880,22 @@ function createToolsNode(initialTools, opts) {
2746
2880
  approval: pendingApproval
2747
2881
  };
2748
2882
  }
2883
+ if (output && typeof output === "object" && output.__awaitingUserQuestion) {
2884
+ if (toolStateRef?.ctx && typeof toolStateRef.ctx === "object") {
2885
+ state.ctx = toolStateRef.ctx;
2886
+ }
2887
+ const pendingQuestion = pendingUserQuestions.find(
2888
+ (entry) => entry.toolCallId === output.toolCallId || entry.id === output.id
2889
+ );
2890
+ if (!pendingQuestion) {
2891
+ throw new Error(`Tool ${toolName} requested a user question but no pending question entry was recorded.`);
2892
+ }
2893
+ awaitingUserQuestion = true;
2894
+ return {
2895
+ status: "awaiting_user_question",
2896
+ userQuestion: pendingQuestion
2897
+ };
2898
+ }
2749
2899
  if (output && typeof output === "object" && output.__finalStructuredOutput) {
2750
2900
  if (!state.ctx) state.ctx = {};
2751
2901
  state.ctx.__structuredOutputParsed = output.data;
@@ -2769,7 +2919,7 @@ function createToolsNode(initialTools, opts) {
2769
2919
  archiveId: void 0,
2770
2920
  status: "success"
2771
2921
  });
2772
- appended.push({ role: "tool", content: responsePolicy.content, tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
2922
+ push({ role: "tool", content: responsePolicy.content, tool_call_id: tc.id || `${tc.name}_${toolCount}`, name: tc.name });
2773
2923
  onEvent?.({ type: "tool_call", phase: "success", name: toolName, id: tc.id, args, result: output, durationMs });
2774
2924
  onProgress?.({ stage: "tools", message: `Tool ${tc.name} completed`, detail: { durationMs } });
2775
2925
  if (needsApproval && approvalEntry) {
@@ -2825,7 +2975,7 @@ function createToolsNode(initialTools, opts) {
2825
2975
  const executionId = nanoid();
2826
2976
  const message = error?.message || String(error);
2827
2977
  toolHistory.push({ executionId, toolName, args, output: `Error executing tool: ${message}`, rawOutput: null, timestamp: (/* @__PURE__ */ new Date()).toISOString(), tool_call_id: tc.id, status: "error" });
2828
- appended.push({ role: "tool", content: `Error executing tool: ${message}`, tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
2978
+ push({ role: "tool", content: `Error executing tool: ${message}`, tool_call_id: tc.id || `${tc.name}_${toolCount}`, name: tc.name });
2829
2979
  markToolFailure(message, toolName, toolCallId);
2830
2980
  onEvent?.({ type: "tool_call", phase: "error", name: toolName, id: tc.id, args, error: { message } });
2831
2981
  onProgress?.({ stage: "tools", message: `Tool ${tc.name} failed`, detail: { error: message } });
@@ -2862,21 +3012,74 @@ function createToolsNode(initialTools, opts) {
2862
3012
  return { status: "error", approval: approvalEntry };
2863
3013
  }
2864
3014
  };
2865
- for (const tc of planned) {
2866
- if (awaitingApproval) break;
3015
+ const approvalIndices = [];
3016
+ const parallelIndices = [];
3017
+ planned.forEach((tc, idx) => {
3018
+ const t = toolByName.get(tc.name);
3019
+ if (t && t.needsApproval) approvalIndices.push(idx);
3020
+ else parallelIndices.push(idx);
3021
+ });
3022
+ let shouldStop = false;
3023
+ const checkCancellationStop = () => {
2867
3024
  const cancelState = isCancelled();
2868
3025
  if (cancelState.cancelled) {
2869
3026
  const ctx = state.ctx = state.ctx || {};
2870
3027
  ctx.__cancelled = { stage: "tools", reason: cancelState.reason, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
2871
3028
  onEvent?.({ type: "cancelled", stage: "tools", reason: cancelState.reason });
2872
3029
  onProgress?.({ stage: "tools", message: "Cancelled", detail: { reason: cancelState.reason } });
2873
- break;
3030
+ shouldStop = true;
3031
+ return true;
2874
3032
  }
2875
- const result = await runOne(tc);
3033
+ return false;
3034
+ };
3035
+ for (const idx of approvalIndices) {
3036
+ if (awaitingApproval || awaitingUserQuestion || shouldStop) break;
3037
+ if (checkCancellationStop()) break;
3038
+ const result = await runOne(planned[idx], orderedSlots[idx]);
2876
3039
  if (result.status === "awaiting_approval") {
2877
3040
  awaitingApproval = true;
2878
3041
  break;
2879
3042
  }
3043
+ if (result.status === "awaiting_user_question") {
3044
+ awaitingUserQuestion = true;
3045
+ break;
3046
+ }
3047
+ }
3048
+ if (!awaitingApproval && !awaitingUserQuestion && !shouldStop && parallelIndices.length > 0) {
3049
+ const concurrency = Math.max(1, Math.min(limits.maxParallelTools, parallelIndices.length));
3050
+ let cursor = 0;
3051
+ const workerError = {};
3052
+ const workers = [];
3053
+ for (let w = 0; w < concurrency; w++) {
3054
+ workers.push((async () => {
3055
+ while (true) {
3056
+ if (workerError.err || shouldStop || awaitingApproval || awaitingUserQuestion) return;
3057
+ const i = cursor++;
3058
+ if (i >= parallelIndices.length) return;
3059
+ if (checkCancellationStop()) return;
3060
+ const idx = parallelIndices[i];
3061
+ try {
3062
+ const result = await runOne(planned[idx], orderedSlots[idx]);
3063
+ if (result.status === "awaiting_approval") {
3064
+ awaitingApproval = true;
3065
+ return;
3066
+ }
3067
+ if (result.status === "awaiting_user_question") {
3068
+ awaitingUserQuestion = true;
3069
+ return;
3070
+ }
3071
+ } catch (err) {
3072
+ workerError.err = err;
3073
+ return;
3074
+ }
3075
+ }
3076
+ })());
3077
+ }
3078
+ await Promise.all(workers);
3079
+ if (workerError.err) throw workerError.err;
3080
+ }
3081
+ for (const slot of orderedSlots) {
3082
+ if (slot.length > 0) appended.push(...slot);
2880
3083
  }
2881
3084
  for (const tc of skipped) {
2882
3085
  onEvent?.({ type: "tool_call", phase: "skipped", name: tc.name, id: tc.id, args: tc.args });
@@ -2913,13 +3116,26 @@ function createToolsNode(initialTools, opts) {
2913
3116
  }
2914
3117
  state.ctx = Object.keys(ctx).length > 0 ? ctx : void 0;
2915
3118
  }
3119
+ const appendedHasToolResult = appended.some((m) => m.role === "tool");
3120
+ if (appendedHasToolResult && state.ctx?.__summarizationExhausted) {
3121
+ const ctx = { ...state.ctx };
3122
+ delete ctx.__summarizationExhausted;
3123
+ state.ctx = Object.keys(ctx).length > 0 ? ctx : void 0;
3124
+ }
2916
3125
  return {
2917
3126
  messages: [...state.messages, ...appended],
2918
3127
  toolCallCount: toolCount,
2919
3128
  toolHistory,
2920
3129
  toolHistoryArchived,
3130
+ toolCache: state.toolCache,
3131
+ // Explicitly propagate ctx so callers using `{ ...state, ...delta }`
3132
+ // do not silently capture a stale ctx reference. Mutations to
3133
+ // __summarizationExhausted / __awaitingApproval / __awaitingUserQuestion /
3134
+ // __structuredOutputParsed are made on this object during execution.
3135
+ ctx: state.ctx,
2921
3136
  agent: state.agent,
2922
- pendingApprovals
3137
+ pendingApprovals,
3138
+ pendingUserQuestions
2923
3139
  };
2924
3140
  };
2925
3141
  }
@@ -3154,7 +3370,8 @@ function resolveReflection(input, preset) {
3154
3370
  maxChars: preset.maxChars,
3155
3371
  keepLast: 3,
3156
3372
  summarize: false,
3157
- emitEvents: true
3373
+ emitEvents: true,
3374
+ everyNTurns: 1
3158
3375
  };
3159
3376
  }
3160
3377
  const cfg = input ?? {};
@@ -3169,7 +3386,9 @@ function resolveReflection(input, preset) {
3169
3386
  keepLast: cfg.keepLast ?? 3,
3170
3387
  summarize: cfg.summarize ?? false,
3171
3388
  promptTemplate: cfg.promptTemplate,
3172
- emitEvents: cfg.emitEvents ?? true
3389
+ emitEvents: cfg.emitEvents ?? true,
3390
+ maxPerRun: typeof cfg.maxPerRun === "number" && cfg.maxPerRun >= 0 ? cfg.maxPerRun : void 0,
3391
+ everyNTurns: Math.max(1, Math.trunc(cfg.everyNTurns ?? 1))
3173
3392
  };
3174
3393
  }
3175
3394
  function ensureArray(value) {
@@ -3382,6 +3601,229 @@ function resolveToolApprovalState(state, resolution) {
3382
3601
  };
3383
3602
  }
3384
3603
 
3604
+ // src/utils/userQuestions.ts
3605
+ var cloneQuestions = (questions) => Array.isArray(questions) ? questions.map((entry) => ({ ...entry })) : [];
3606
+ function validateAnswers(entry, answers, cancelled) {
3607
+ if (cancelled) {
3608
+ return answers || {};
3609
+ }
3610
+ const allowFreeText = entry.allowFreeText !== false;
3611
+ const out = {};
3612
+ const provided = answers || {};
3613
+ for (const question of entry.questions) {
3614
+ const required = question.required !== false;
3615
+ const key = question.question;
3616
+ const answer = provided[key] ?? provided[question.header || ""];
3617
+ if (!answer) {
3618
+ if (required) {
3619
+ throw new Error(`Missing answer for required question: "${key}".`);
3620
+ }
3621
+ continue;
3622
+ }
3623
+ const values = Array.isArray(answer.values) ? answer.values.filter((v) => typeof v === "string") : [];
3624
+ const freeText = typeof answer.freeText === "string" ? answer.freeText.trim() : "";
3625
+ const notes = typeof answer.notes === "string" ? answer.notes : void 0;
3626
+ if (freeText && !allowFreeText) {
3627
+ throw new Error(
3628
+ `Free-text answers are disabled for this agent (question: "${key}"). Pick one of the provided options.`
3629
+ );
3630
+ }
3631
+ if (values.length === 0 && !freeText) {
3632
+ if (required) {
3633
+ throw new Error(`Question "${key}" requires either a selected option or free-text answer.`);
3634
+ }
3635
+ continue;
3636
+ }
3637
+ if (values.length > 1 && !question.multiSelect) {
3638
+ throw new Error(`Question "${key}" is single-select but received multiple values.`);
3639
+ }
3640
+ if (Array.isArray(question.options) && question.options.length > 0 && values.length > 0) {
3641
+ const allowedValues = new Set(
3642
+ question.options.map((option) => option.value ?? option.label)
3643
+ );
3644
+ for (const value of values) {
3645
+ if (!allowedValues.has(value)) {
3646
+ if (!allowFreeText) {
3647
+ throw new Error(
3648
+ `Value "${value}" is not one of the allowed options for question "${key}".`
3649
+ );
3650
+ }
3651
+ }
3652
+ }
3653
+ }
3654
+ out[key] = { values, ...freeText ? { freeText } : {}, ...notes ? { notes } : {} };
3655
+ }
3656
+ return out;
3657
+ }
3658
+ function buildToolResponseContent(entry, answers, cancelled, notes) {
3659
+ if (cancelled) {
3660
+ return JSON.stringify({
3661
+ status: "cancelled",
3662
+ message: notes || "User dismissed the question without answering."
3663
+ });
3664
+ }
3665
+ const payload = {
3666
+ status: "answered",
3667
+ answers: entry.questions.map((question) => {
3668
+ const key = question.question;
3669
+ const ans = answers[key];
3670
+ return {
3671
+ question: key,
3672
+ header: question.header,
3673
+ values: ans?.values ?? [],
3674
+ freeText: ans?.freeText,
3675
+ notes: ans?.notes
3676
+ };
3677
+ }),
3678
+ ...notes ? { reviewerNotes: notes } : {}
3679
+ };
3680
+ return JSON.stringify(payload);
3681
+ }
3682
+ function resolveUserQuestionState(state, resolution) {
3683
+ const pending = cloneQuestions(state.pendingUserQuestions);
3684
+ const matchIndex = pending.findIndex(
3685
+ (entry2) => entry2.id === resolution.id || entry2.toolCallId === resolution.id
3686
+ );
3687
+ if (matchIndex === -1) {
3688
+ throw new Error(`Pending user question not found for id: ${resolution.id}`);
3689
+ }
3690
+ const entry = { ...pending[matchIndex] };
3691
+ if (entry.status === "answered" || entry.status === "executed" || entry.status === "cancelled") {
3692
+ throw new Error(`User question ${entry.id} already resolved (status: ${entry.status}).`);
3693
+ }
3694
+ const cancelled = resolution.cancelled === true;
3695
+ const validated = validateAnswers(entry, resolution.answers, cancelled);
3696
+ entry.status = cancelled ? "cancelled" : "answered";
3697
+ entry.answers = validated;
3698
+ entry.cancelled = cancelled || void 0;
3699
+ entry.answeredBy = resolution.answeredBy;
3700
+ entry.notes = resolution.notes;
3701
+ entry.answeredAt = (/* @__PURE__ */ new Date()).toISOString();
3702
+ pending[matchIndex] = entry;
3703
+ const ctx = { ...state.ctx || {} };
3704
+ delete ctx.__awaitingUserQuestion;
3705
+ if (ctx.__resumeStage === "tools") {
3706
+ delete ctx.__resumeStage;
3707
+ }
3708
+ ctx.__userQuestionResolved = { id: entry.id, status: entry.status };
3709
+ const toolMessage = {
3710
+ role: "tool",
3711
+ name: "ask_user_question",
3712
+ tool_call_id: entry.toolCallId,
3713
+ content: buildToolResponseContent(entry, validated, cancelled, resolution.notes)
3714
+ };
3715
+ return {
3716
+ ...state,
3717
+ pendingUserQuestions: pending,
3718
+ messages: [...state.messages || [], toolMessage],
3719
+ ctx
3720
+ };
3721
+ }
3722
+ 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.";
3723
+ 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.";
3724
+ function buildOptionSchema() {
3725
+ return z.object({
3726
+ label: z.string().min(1).describe("Display text the user sees"),
3727
+ value: z.string().min(1).optional().describe("Value returned in the answer; defaults to label"),
3728
+ description: z.string().optional(),
3729
+ preview: z.string().optional().describe("Optional code / ASCII preview rendered when this option is focused")
3730
+ }).strict();
3731
+ }
3732
+ function buildQuestionSchema(allowFreeText) {
3733
+ const optionSchema = buildOptionSchema();
3734
+ const baseShape = {
3735
+ question: z.string().min(1).describe("Full question shown to the user"),
3736
+ header: z.string().max(40).optional().describe("Short chip / tag rendered next to the question (max ~12 chars recommended)"),
3737
+ multiSelect: z.boolean().optional().describe("Allow multiple selections. Default: false"),
3738
+ placeholder: z.string().optional(),
3739
+ required: z.boolean().optional().describe("Default: true")
3740
+ };
3741
+ if (allowFreeText) {
3742
+ return z.object({
3743
+ ...baseShape,
3744
+ options: z.array(optionSchema).min(2).max(12).optional()
3745
+ }).strict();
3746
+ }
3747
+ return z.object({
3748
+ ...baseShape,
3749
+ options: z.array(optionSchema).min(2).max(12).describe("Required: free-text answers are disabled for this agent")
3750
+ }).strict();
3751
+ }
3752
+ function createAskUserQuestionTool(stateRef, config) {
3753
+ const allowFreeText = config.allowFreeText !== false;
3754
+ const description = config.promptOverride ?? (allowFreeText ? DEFAULT_DESCRIPTION_WITH_FREETEXT : DEFAULT_DESCRIPTION_NO_FREETEXT);
3755
+ const questionSchema = buildQuestionSchema(allowFreeText);
3756
+ const askUser = createTool({
3757
+ name: "ask_user_question",
3758
+ description,
3759
+ schema: z.object({
3760
+ questions: z.array(questionSchema).min(1).max(4).describe("1-4 related questions to ask in a single pause.")
3761
+ }).strict(),
3762
+ func: async ({ questions }) => {
3763
+ const ref = askUser._stateRef || (askUser._stateRef = {});
3764
+ const onEvent = ref.__onEvent;
3765
+ if (!ref.ctx) ref.ctx = {};
3766
+ const ctx = ref.ctx;
3767
+ const toolCallId = ref.__currentToolCallId || `ask_user_${nanoid(6)}`;
3768
+ if (!ref.pendingUserQuestions) ref.pendingUserQuestions = [];
3769
+ const pendingList = ref.pendingUserQuestions;
3770
+ const entry = {
3771
+ id: nanoid(),
3772
+ toolCallId,
3773
+ toolName: "ask_user_question",
3774
+ questions,
3775
+ status: "pending",
3776
+ requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
3777
+ allowFreeText
3778
+ };
3779
+ pendingList.push(entry);
3780
+ ctx.__awaitingUserQuestion = {
3781
+ id: entry.id,
3782
+ toolCallId: entry.toolCallId,
3783
+ requestedAt: entry.requestedAt
3784
+ };
3785
+ ctx.__resumeStage = "tools";
3786
+ onEvent?.({
3787
+ type: "user_question",
3788
+ status: "pending",
3789
+ id: entry.id,
3790
+ toolCallId: entry.toolCallId,
3791
+ questions: entry.questions,
3792
+ allowFreeText
3793
+ });
3794
+ recordTraceEvent(ctx.__traceSession, {
3795
+ type: "user_question",
3796
+ label: "Ask User",
3797
+ actor: { scope: "agent", name: "ask_user_question", role: "user_interaction" },
3798
+ status: "success",
3799
+ sections: [
3800
+ {
3801
+ kind: "metadata",
3802
+ label: "Pending Question",
3803
+ data: {
3804
+ id: entry.id,
3805
+ toolCallId: entry.toolCallId,
3806
+ questions: entry.questions,
3807
+ allowFreeText
3808
+ }
3809
+ }
3810
+ ]
3811
+ });
3812
+ return {
3813
+ __awaitingUserQuestion: true,
3814
+ id: entry.id,
3815
+ toolCallId: entry.toolCallId
3816
+ };
3817
+ }
3818
+ });
3819
+ askUser._stateRef = stateRef;
3820
+ askUser.__source = "human-in-the-loop";
3821
+ return askUser;
3822
+ }
3823
+
3824
+ // src/agent.ts
3825
+ init_utilTokens();
3826
+
3385
3827
  // src/utils/syntheticMessages.ts
3386
3828
  function isSyntheticSummaryMessage(message) {
3387
3829
  if (!message) return false;
@@ -3560,6 +4002,21 @@ function getActiveSummarizationThreshold(opts) {
3560
4002
  }
3561
4003
  return summarization.summaryTriggerTokens || summarization.maxTokens || opts.limits?.maxContextTokens || 5e4;
3562
4004
  }
4005
+ function shouldSignalSummarization(state, threshold) {
4006
+ const tokCountMessages = (state.messages || []).filter((message) => {
4007
+ if (isSyntheticSummaryMessage(message)) return false;
4008
+ if (message.role === "system" && (message.name === "context_summary" || message.name === "memory_context")) return false;
4009
+ return true;
4010
+ });
4011
+ const tokenCount = countMessagesTokens(tokCountMessages);
4012
+ if (tokenCount <= threshold) return "skip";
4013
+ if (state.ctx?.__summarizationExhausted) return "skip";
4014
+ const hasFreshSummary = (state.messages || []).some(
4015
+ (m) => m.role === "tool" && typeof m.content === "string" && (m.content === "SUMMARIZED" || m.content.startsWith("SUMMARIZED_TOOL_RESPONSE"))
4016
+ );
4017
+ if (hasFreshSummary && tokenCount <= threshold * 1.15) return "skip";
4018
+ return "trigger";
4019
+ }
3563
4020
  function clearNeedsSummarization(state) {
3564
4021
  if (!state.ctx?.__needsSummarization) {
3565
4022
  return state;
@@ -3575,6 +4032,21 @@ function createAgent(opts) {
3575
4032
  const resolver = createResolverNode();
3576
4033
  const agentCore = createAgentCoreNode(opts);
3577
4034
  const toolsBase = [...opts.tools ?? []];
4035
+ const askUserConfig = (() => {
4036
+ const raw = opts.humanInTheLoop?.askUser;
4037
+ if (!raw) return void 0;
4038
+ if (raw === true) return { enabled: true, allowFreeText: true };
4039
+ return {
4040
+ enabled: true,
4041
+ allowFreeText: raw.allowFreeText !== false,
4042
+ promptOverride: raw.promptOverride
4043
+ };
4044
+ })();
4045
+ const askUserOnQuestion = typeof opts.humanInTheLoop?.askUser === "object" ? opts.humanInTheLoop?.askUser?.onQuestion : void 0;
4046
+ const askUserStateRef = askUserConfig ? { pendingUserQuestions: void 0, ctx: void 0, __onEvent: void 0, __currentToolCallId: void 0 } : void 0;
4047
+ if (askUserConfig && askUserStateRef) {
4048
+ toolsBase.push(createAskUserQuestionTool(askUserStateRef, askUserConfig));
4049
+ }
3578
4050
  const soManager = opts.outputSchema ? new StructuredOutputManager(opts.outputSchema, resolveStrategy(opts.model)) : void 0;
3579
4051
  if (soManager) {
3580
4052
  const modelCapabilities = getModelCapabilities(opts.model);
@@ -3619,7 +4091,8 @@ function createAgent(opts) {
3619
4091
  useTodoList: void 0,
3620
4092
  outputSchema: opts.outputSchema,
3621
4093
  responseFormat: soManager?.getResponseFormat(),
3622
- tracing: opts.tracing
4094
+ tracing: opts.tracing,
4095
+ humanInTheLoop: askUserConfig ? { askUser: askUserConfig } : void 0
3623
4096
  };
3624
4097
  const summarizationThreshold = getActiveSummarizationThreshold(opts);
3625
4098
  async function runLoop(initial, config, emit) {
@@ -3654,6 +4127,45 @@ function createAgent(opts) {
3654
4127
  };
3655
4128
  const maxToolCalls = mergedLimits?.maxToolCalls === void 0 ? 50 : mergedLimits?.maxToolCalls;
3656
4129
  const iterationLimit = maxToolCalls === Infinity ? 100 : Math.max(maxToolCalls * 3 + 10, 40);
4130
+ const wallClockStart = Date.now();
4131
+ const maxWallClockMs = mergedLimits?.maxWallClockMs && mergedLimits.maxWallClockMs > 0 ? mergedLimits.maxWallClockMs : void 0;
4132
+ const maxTotalOutputTokens = mergedLimits?.maxTotalOutputTokens && mergedLimits.maxTotalOutputTokens > 0 ? mergedLimits.maxTotalOutputTokens : void 0;
4133
+ const maxCostUsd = mergedLimits?.maxCostUsd && mergedLimits.maxCostUsd > 0 ? mergedLimits.maxCostUsd : void 0;
4134
+ const costEstimator = opts.costEstimator;
4135
+ const checkBudgetBreached = () => {
4136
+ if (maxWallClockMs && Date.now() - wallClockStart > maxWallClockMs) {
4137
+ return { breached: true, reason: `maxWallClockMs (${maxWallClockMs}ms) exceeded` };
4138
+ }
4139
+ if (maxTotalOutputTokens) {
4140
+ const totals = state.usage?.totals;
4141
+ if (totals) {
4142
+ let outputSum = 0;
4143
+ for (const v of Object.values(totals)) outputSum += Number(v?.output) || 0;
4144
+ if (outputSum > maxTotalOutputTokens) {
4145
+ return { breached: true, reason: `maxTotalOutputTokens (${maxTotalOutputTokens}) exceeded` };
4146
+ }
4147
+ }
4148
+ }
4149
+ if (maxCostUsd && costEstimator) {
4150
+ const perRequest = state.usage?.perRequest;
4151
+ if (perRequest) {
4152
+ let cost = 0;
4153
+ for (const r of perRequest) {
4154
+ cost += costEstimator({
4155
+ modelName: r.modelName,
4156
+ inputTokens: Number(r.usage?.prompt_tokens) || 0,
4157
+ outputTokens: Number(r.usage?.completion_tokens) || 0,
4158
+ cachedInputTokens: Number(r.usage?.prompt_tokens_details?.cached_tokens) || 0,
4159
+ reasoningTokens: Number(r.usage?.completion_tokens_details?.reasoning_tokens) || 0
4160
+ }) || 0;
4161
+ }
4162
+ if (cost > maxCostUsd) {
4163
+ return { breached: true, reason: `maxCostUsd ($${maxCostUsd}) exceeded (actual: $${cost.toFixed(4)})` };
4164
+ }
4165
+ }
4166
+ }
4167
+ return { breached: false };
4168
+ };
3657
4169
  let iterations = 0;
3658
4170
  const onStateChange = config?.onStateChange;
3659
4171
  const checkpointReason = config?.checkpointReason;
@@ -3702,6 +4214,13 @@ function createAgent(opts) {
3702
4214
  };
3703
4215
  while (iterations < iterationLimit) {
3704
4216
  iterations++;
4217
+ const budget = checkBudgetBreached();
4218
+ if (budget.breached) {
4219
+ emit?.({ type: "metadata", limitBreached: budget.reason });
4220
+ const ctx = { ...state.ctx || {}, __limitBreached: budget.reason };
4221
+ state = { ...state, ctx };
4222
+ break;
4223
+ }
3705
4224
  const traceSession = state.ctx?.__traceSession;
3706
4225
  if (traceSession) {
3707
4226
  const iterEvent = recordTraceEvent(traceSession, {
@@ -3757,22 +4276,11 @@ function createAgent(opts) {
3757
4276
  }
3758
4277
  }
3759
4278
  if (summarizationThreshold !== void 0) {
3760
- const tokCountMessages = (state.messages || []).filter((message) => {
3761
- if (isSyntheticSummaryMessage(message)) return false;
3762
- if (message.role === "system" && (message.name === "context_summary" || message.name === "memory_context")) return false;
3763
- return true;
3764
- });
3765
- const tokenCount = countMessagesTokens(tokCountMessages);
3766
- if (tokenCount > summarizationThreshold) {
3767
- const summarizationExhausted = !!state.ctx?.__summarizationExhausted;
3768
- const hasFreshSummary = (state.messages || []).some(
3769
- (m) => m.role === "tool" && typeof m.content === "string" && (m.content === "SUMMARIZED" || m.content.startsWith("SUMMARIZED_TOOL_RESPONSE"))
3770
- );
3771
- if (hasFreshSummary && tokenCount <= summarizationThreshold * 1.15) ; else if (summarizationExhausted) ; else {
3772
- const ctx = { ...state.ctx || {}, __needsSummarization: true };
3773
- state = { ...state, ctx };
3774
- break;
3775
- }
4279
+ const verdict = shouldSignalSummarization(state, summarizationThreshold);
4280
+ if (verdict === "trigger") {
4281
+ const ctx = { ...state.ctx || {}, __needsSummarization: true };
4282
+ state = { ...state, ctx };
4283
+ break;
3776
4284
  }
3777
4285
  }
3778
4286
  onProgress?.({ stage: "agent", message: "Invoking model" });
@@ -3874,11 +4382,18 @@ function createAgent(opts) {
3874
4382
  state = { ...state, ...await toolsNode(state) };
3875
4383
  onProgress?.({ stage: "tools", message: "Tools finished" });
3876
4384
  if (state.ctx?.__awaitingApproval) break;
4385
+ if (state.ctx?.__awaitingUserQuestion) break;
3877
4386
  if (checkpointIfRequested("after_tools")) break;
3878
4387
  if (state.ctx?.__finalizedDueToStructuredOutput) break;
3879
4388
  if (reflectNode && resolvedReasoning?.reflection.enabled) {
3880
4389
  const cadence = resolvedReasoning.reflection.cadence;
3881
- if (shouldRunReflection(cadence, state, true)) {
4390
+ const refl = resolvedReasoning.reflection;
4391
+ const reflectionsSoFar = state.reflections?.length ?? 0;
4392
+ const lastTurn = state.reflections?.at(-1)?.turn ?? -Infinity;
4393
+ const currentTurn = state.toolCallCount || 0;
4394
+ const budgetExceeded = typeof refl.maxPerRun === "number" && reflectionsSoFar >= refl.maxPerRun;
4395
+ const cadenceGap = currentTurn - (Number.isFinite(lastTurn) ? lastTurn : -refl.everyNTurns) < refl.everyNTurns;
4396
+ if (!budgetExceeded && !cadenceGap && shouldRunReflection(cadence, state, true)) {
3882
4397
  try {
3883
4398
  const patch = await reflectNode(state, cadence);
3884
4399
  if (patch && Object.keys(patch).length > 0) {
@@ -3938,7 +4453,7 @@ function createAgent(opts) {
3938
4453
  }
3939
4454
  const lastAfterLoop = state.messages[state.messages.length - 1];
3940
4455
  if (lastAfterLoop?.role === "tool" && !pausedStage) {
3941
- const isExpectedExit = state.ctx?.__awaitingApproval || state.ctx?.__cancelled || state.ctx?.__finalizedDueToStructuredOutput || state.ctx?.__finalizedDueToToolLimit || state.ctx?.__needsSummarization;
4456
+ const isExpectedExit = state.ctx?.__awaitingApproval || state.ctx?.__awaitingUserQuestion || state.ctx?.__cancelled || state.ctx?.__finalizedDueToStructuredOutput || state.ctx?.__finalizedDueToToolLimit || state.ctx?.__needsSummarization || state.ctx?.__limitBreached;
3942
4457
  if (!isExpectedExit) {
3943
4458
  throw new Error(
3944
4459
  "Agent loop terminated with a pending tool response but no subsequent model invocation. This usually indicates a model provider error or exhausted iteration budget."
@@ -3951,7 +4466,22 @@ function createAgent(opts) {
3951
4466
  return state;
3952
4467
  }
3953
4468
  const invokeAgent = async (input, config) => {
3954
- const onEvent = config?.onEvent;
4469
+ const userTokenCounter = opts.tokenCounter;
4470
+ if (userTokenCounter) {
4471
+ const { setTokenCounter: setTokenCounter2 } = await Promise.resolve().then(() => (init_utilTokens(), utilTokens_exports));
4472
+ setTokenCounter2(userTokenCounter);
4473
+ }
4474
+ const callerOnEvent = config?.onEvent;
4475
+ const onEvent = askUserOnQuestion ? (event) => {
4476
+ if (event && event.type === "user_question") {
4477
+ try {
4478
+ askUserOnQuestion(event);
4479
+ } catch (err) {
4480
+ console.warn("[agent-sdk] humanInTheLoop.askUser.onQuestion error:", err);
4481
+ }
4482
+ }
4483
+ callerOnEvent?.(event);
4484
+ } : callerOnEvent;
3955
4485
  const onProgress = config?.onProgress;
3956
4486
  const onStream = config?.onStream;
3957
4487
  const streamEnabled = config?.stream === true;
@@ -3995,6 +4525,7 @@ function createAgent(opts) {
3995
4525
  metadata: input.metadata,
3996
4526
  ctx,
3997
4527
  pendingApprovals: input.pendingApprovals || [],
4528
+ pendingUserQuestions: input.pendingUserQuestions || [],
3998
4529
  agent: input.agent || runtimeWithInvokeLimits,
3999
4530
  usage: input.usage || { perRequest: [], totals: {} }
4000
4531
  };
@@ -4008,7 +4539,19 @@ function createAgent(opts) {
4008
4539
  status: "error",
4009
4540
  error: { message: err?.message, stack: err?.stack }
4010
4541
  });
4542
+ if (userTokenCounter) {
4543
+ const { setTokenCounter: setTokenCounter2 } = await Promise.resolve().then(() => (init_utilTokens(), utilTokens_exports));
4544
+ setTokenCounter2(void 0);
4545
+ }
4011
4546
  throw err;
4547
+ } finally {
4548
+ if (userTokenCounter) {
4549
+ try {
4550
+ const { setTokenCounter: setTokenCounter2 } = await Promise.resolve().then(() => (init_utilTokens(), utilTokens_exports));
4551
+ setTokenCounter2(void 0);
4552
+ } catch {
4553
+ }
4554
+ }
4012
4555
  }
4013
4556
  await finalizeTraceSession(traceSession, {
4014
4557
  agentRuntime: res.agent || runtime,
@@ -4059,19 +4602,67 @@ function createAgent(opts) {
4059
4602
  return invokeAgent(restoredState, config);
4060
4603
  };
4061
4604
  const resolveToolApproval = (state, resolution) => resolveToolApprovalState(state, resolution);
4605
+ const resolveUserQuestion = (state, resolution) => resolveUserQuestionState(state, resolution);
4062
4606
  const instance = {
4063
4607
  invoke: invokeAgent,
4064
4608
  snapshot: snapshotState,
4065
4609
  resume: resumeAgent,
4066
4610
  resolveToolApproval,
4611
+ resolveUserQuestion,
4067
4612
  asTool: ({ toolName, description, inputDescription }) => {
4068
4613
  const schema = z.object({ input: z.string().describe(inputDescription || "Input for delegated agent") });
4069
- return createTool({
4614
+ const delegatedTool = createTool({
4070
4615
  name: toolName,
4071
4616
  description: description || `Delegate task to agent ${opts.name || "Agent"}`,
4072
4617
  schema,
4073
4618
  func: async ({ input }) => {
4074
- const res = await instance.invoke({ messages: [{ role: "user", content: input }] });
4619
+ const ref = delegatedTool._stateRef;
4620
+ const parentCtx = ref.ctx || {};
4621
+ const parentMessages = Array.isArray(ref.messages) ? ref.messages : [];
4622
+ const parentRuntime = ref.parentRuntime;
4623
+ const resolvedSmart = parentRuntime?.smart || opts.__resolvedSmart;
4624
+ const delegationPolicy = resolvedSmart?.delegation || {
4625
+ mode: "role_based",
4626
+ maxDelegationDepth: 2,
4627
+ maxChildCalls: 4,
4628
+ childContextPolicy: "scoped"
4629
+ };
4630
+ const currentDepth = Number(parentCtx.__delegationDepth) || 0;
4631
+ if (delegationPolicy.mode === "off") {
4632
+ return { error: "Delegation disabled by runtime policy." };
4633
+ }
4634
+ if (currentDepth >= delegationPolicy.maxDelegationDepth) {
4635
+ return {
4636
+ error: `Delegation depth limit reached (${delegationPolicy.maxDelegationDepth}). Refusing nested call.`
4637
+ };
4638
+ }
4639
+ parentCtx.__delegatedCallCount = (parentCtx.__delegatedCallCount || 0) + 1;
4640
+ if (parentCtx.__delegatedCallCount > delegationPolicy.maxChildCalls) {
4641
+ return {
4642
+ error: `Child-call budget exhausted (${delegationPolicy.maxChildCalls}).`
4643
+ };
4644
+ }
4645
+ let childMessages;
4646
+ switch (delegationPolicy.childContextPolicy) {
4647
+ case "full":
4648
+ childMessages = [...parentMessages, { role: "user", content: input }];
4649
+ break;
4650
+ case "scoped": {
4651
+ const systemPrefix = parentMessages[0]?.role === "system" ? [parentMessages[0]] : [];
4652
+ const lastUser = [...parentMessages].reverse().find((m) => m?.role === "user");
4653
+ childMessages = [
4654
+ ...systemPrefix,
4655
+ ...lastUser ? [lastUser] : [],
4656
+ { role: "user", content: input }
4657
+ ];
4658
+ break;
4659
+ }
4660
+ case "minimal":
4661
+ default:
4662
+ childMessages = [{ role: "user", content: input }];
4663
+ }
4664
+ const childCtx = { __delegationDepth: currentDepth + 1 };
4665
+ const res = await instance.invoke({ messages: childMessages, ctx: childCtx });
4075
4666
  return {
4076
4667
  content: res.content,
4077
4668
  output: res.output,
@@ -4079,6 +4670,8 @@ function createAgent(opts) {
4079
4670
  };
4080
4671
  }
4081
4672
  });
4673
+ delegatedTool._stateRef = {};
4674
+ return delegatedTool;
4082
4675
  },
4083
4676
  asHandoff: ({ toolName, description, schema }) => {
4084
4677
  const finalName = toolName || `handoff_to_${runtime.name || "agent"}`;
@@ -4218,7 +4811,11 @@ function createAgentNode(opts) {
4218
4811
  };
4219
4812
  }
4220
4813
 
4814
+ // src/nodes/contextSummarize.ts
4815
+ init_utilTokens();
4816
+
4221
4817
  // src/smart/contextPolicy.ts
4818
+ init_utilTokens();
4222
4819
  function collectRecentTurns(messages, lastTurnsToKeep) {
4223
4820
  if (lastTurnsToKeep <= 0) return [];
4224
4821
  const systemPrefix = messages.filter((message, index) => index === 0 && message.role === "system");
@@ -4521,7 +5118,7 @@ function createContextSummarizeNode(opts) {
4521
5118
  const resolved = getResolvedSmartConfig(opts, state.agent);
4522
5119
  const config = resolved.summarization;
4523
5120
  if (!config.enable) return {};
4524
- const model = opts.model;
5121
+ const model = state.agent?.model || opts.model;
4525
5122
  const messages = state.messages || [];
4526
5123
  const onEvent = state.ctx?.__onEvent;
4527
5124
  const traceSession = state.ctx?.__traceSession;
@@ -4840,6 +5437,10 @@ ${canonicalFacts.length > 0 ? canonicalFacts.map((fact) => `- ${fact.key}: ${fac
4840
5437
  };
4841
5438
  };
4842
5439
  }
5440
+
5441
+ // src/index.ts
5442
+ init_utilTokens();
5443
+ init_utilTokens();
4843
5444
  var TOOL_RESPONSE_RECOVERY_MARKERS = [
4844
5445
  /ARCHIVED_TOOL_RESPONSE\s*\[/,
4845
5446
  /STRUCTURED_TOOL_RESPONSE\s*\[/,
@@ -5158,6 +5759,7 @@ function createContextTools(stateRef, opts) {
5158
5759
  }
5159
5760
 
5160
5761
  // src/graph/decisions.ts
5762
+ init_utilTokens();
5161
5763
  function needsSummarization(state, opts, summarizationEnabled) {
5162
5764
  if (!summarizationEnabled) {
5163
5765
  return false;
@@ -5320,12 +5922,38 @@ function createSmartAgent(opts) {
5320
5922
  toolResponses: resolved.toolResponses,
5321
5923
  useTodoList: planningEnabled
5322
5924
  };
5323
- const stateRef = { toolHistory: void 0, toolHistoryArchived: void 0, todoList: void 0, planVersion: 0, adherenceScore: 0 };
5324
- const contextTools = createContextTools(stateRef, { planningEnabled, includeGetToolResponse: false });
5325
- const getToolResponseTool = createGetToolResponseTool(stateRef);
5326
- const toolsWithoutRecovery = [...opts.tools ?? [], ...contextTools];
5327
- const toolsWithRecovery = [...toolsWithoutRecovery, getToolResponseTool];
5328
- const base = createAgent({ ...runtimeOpts, tools: toolsWithoutRecovery });
5925
+ const userTools = opts.tools ?? [];
5926
+ const factoryStateRef = { toolHistory: void 0, toolHistoryArchived: void 0, todoList: void 0, planVersion: 0, adherenceScore: 0 };
5927
+ const factoryContextTools = createContextTools(factoryStateRef, { planningEnabled, includeGetToolResponse: false });
5928
+ const askUserConfig = (() => {
5929
+ const raw = opts.humanInTheLoop?.askUser;
5930
+ if (!raw) return void 0;
5931
+ if (raw === true) return { enabled: true, allowFreeText: true };
5932
+ return {
5933
+ enabled: true,
5934
+ allowFreeText: raw.allowFreeText !== false,
5935
+ promptOverride: raw.promptOverride
5936
+ };
5937
+ })();
5938
+ const factoryToolsWithoutRecovery = [...userTools, ...factoryContextTools];
5939
+ if (askUserConfig) {
5940
+ factoryToolsWithoutRecovery.push(createAskUserQuestionTool(factoryStateRef, askUserConfig));
5941
+ }
5942
+ const soManagerFactory = opts.outputSchema ? new StructuredOutputManager(opts.outputSchema, resolveStrategy(opts.model)) : void 0;
5943
+ const modelCapabilities = opts.outputSchema ? getModelCapabilities(opts.model) : void 0;
5944
+ const responseFinalizeTool = soManagerFactory && modelCapabilities?.structuredOutput !== "native" ? soManagerFactory.getResponseTool() : void 0;
5945
+ const buildInvokeToolSet = () => {
5946
+ const ref = { toolHistory: void 0, toolHistoryArchived: void 0, todoList: void 0, planVersion: 0, adherenceScore: 0 };
5947
+ const ctxTools = createContextTools(ref, { planningEnabled, includeGetToolResponse: false });
5948
+ const recoveryTool = createGetToolResponseTool(ref);
5949
+ const base2 = [...userTools, ...ctxTools];
5950
+ if (responseFinalizeTool) base2.push(responseFinalizeTool);
5951
+ if (askUserConfig) base2.push(createAskUserQuestionTool(ref, askUserConfig));
5952
+ const without = base2;
5953
+ const withRec = [...without, recoveryTool];
5954
+ return { stateRef: ref, toolsWithoutRecovery: without, toolsWithRecovery: withRec };
5955
+ };
5956
+ const base = createAgent({ ...runtimeOpts, tools: factoryToolsWithoutRecovery });
5329
5957
  base.__runtime.runtimeProfile = resolved.runtimeProfile;
5330
5958
  base.__runtime.smart = resolved;
5331
5959
  const summarizationEnabled = resolved.summarization.enable !== false;
@@ -5361,17 +5989,17 @@ function createSmartAgent(opts) {
5361
5989
  const memoryFacts = await writeSummaryFactsToMemory(memoryStore, resolved, latestSummary, Math.max(1, (state.messages || []).filter((message) => message.role === "user").length));
5362
5990
  return { ...state, memoryFacts };
5363
5991
  }
5364
- function syncPlanState(state) {
5365
- if (!Array.isArray(stateRef.todoList)) return state;
5992
+ function syncPlanStateWith(ref, state) {
5993
+ if (!Array.isArray(ref.todoList)) return state;
5366
5994
  return {
5367
5995
  ...state,
5368
5996
  plan: {
5369
- version: stateRef.planVersion || state.planVersion || 1,
5370
- steps: stateRef.todoList,
5997
+ version: ref.planVersion || state.planVersion || 1,
5998
+ steps: ref.todoList,
5371
5999
  lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
5372
- adherenceScore: stateRef.adherenceScore || 0
6000
+ adherenceScore: ref.adherenceScore || 0
5373
6001
  },
5374
- planVersion: stateRef.planVersion || state.planVersion || 1
6002
+ planVersion: ref.planVersion || state.planVersion || 1
5375
6003
  };
5376
6004
  }
5377
6005
  function systemMessage2() {
@@ -5383,9 +6011,9 @@ function createSmartAgent(opts) {
5383
6011
  );
5384
6012
  return { role: "system", content: sys };
5385
6013
  }
5386
- function syncRuntimeTools(currentState, currentMessages) {
6014
+ function syncRuntimeTools(currentState, currentMessages, toolSet) {
5387
6015
  const needsRecoveryTool = hasToolResponseRecoveryReference(currentMessages);
5388
- const nextTools = needsRecoveryTool ? toolsWithRecovery : toolsWithoutRecovery;
6016
+ const nextTools = needsRecoveryTool ? toolSet.toolsWithRecovery : toolSet.toolsWithoutRecovery;
5389
6017
  const currentRuntime = currentState.agent || base.__runtime;
5390
6018
  if (currentRuntime.tools === nextTools) {
5391
6019
  return currentState;
@@ -5404,7 +6032,7 @@ function createSmartAgent(opts) {
5404
6032
  const toolCalls = Array.isArray(lastAssistant.tool_calls) ? lastAssistant.tool_calls : [];
5405
6033
  return toolCalls.length === 0;
5406
6034
  }
5407
- async function trySummarize(currentState, currentRawMessages) {
6035
+ async function trySummarize(currentState, currentRawMessages, ref) {
5408
6036
  if (!summarizer) return { state: currentState, rawMessages: currentRawMessages, compressed: false };
5409
6037
  const delta = await summarizer(currentState);
5410
6038
  if (!delta || Object.keys(delta).length === 0) {
@@ -5413,28 +6041,31 @@ function createSmartAgent(opts) {
5413
6041
  }
5414
6042
  const ctx = { ...currentState.ctx || {} };
5415
6043
  delete ctx.__summarizationExhausted;
5416
- const updated = await persistLatestSummary(syncPlanState({ ...currentState, ...delta, ctx }));
6044
+ const updated = await persistLatestSummary(syncPlanStateWith(ref, { ...currentState, ...delta, ctx }));
5417
6045
  return { state: updated, rawMessages: [...updated.messages || currentRawMessages], compressed: true };
5418
6046
  }
5419
6047
  const instance = {
5420
6048
  invoke: async (input, config) => {
6049
+ const toolSet = buildInvokeToolSet();
6050
+ const stateRef = toolSet.stateRef;
5421
6051
  stateRef.toolHistory = input.toolHistory;
5422
6052
  stateRef.toolHistoryArchived = input.toolHistoryArchived;
5423
6053
  stateRef.todoList = input.plan?.steps;
5424
6054
  stateRef.planVersion = input.planVersion || input.plan?.version || 0;
5425
6055
  stateRef.adherenceScore = input.plan?.adherenceScore || 0;
6056
+ const syncPlanFromRef = (state2) => syncPlanStateWith(stateRef, state2);
5426
6057
  const alreadyHasSystem = Array.isArray(input.messages) && input.messages[0]?.role === "system";
5427
6058
  const seedMessages = alreadyHasSystem ? [...input.messages || []] : [systemMessage2(), ...input.messages || []];
5428
- let state = syncRuntimeTools(await syncMemory({ ...input, messages: seedMessages }), seedMessages);
6059
+ let state = syncRuntimeTools(await syncMemory({ ...input, messages: seedMessages }), seedMessages, toolSet);
5429
6060
  let lastResult = null;
5430
6061
  let rawMessages = [...seedMessages];
5431
6062
  const effectiveMaxToolCalls = config?.limits?.maxToolCalls ?? resolved.limits.maxToolCalls ?? 10;
5432
6063
  const iterationLimit = Math.max(effectiveMaxToolCalls * 3 + 5, 30);
5433
6064
  for (let i = 0; i < iterationLimit; i++) {
5434
- state = syncRuntimeTools({ ...state, messages: rawMessages }, rawMessages);
6065
+ state = syncRuntimeTools({ ...state, messages: rawMessages }, rawMessages, toolSet);
5435
6066
  const next = summarizationEnabled ? decideBefore(state) : "agent";
5436
6067
  if (next === "contextSummarize" && summarizer) {
5437
- const result = await trySummarize(state, rawMessages);
6068
+ const result = await trySummarize(state, rawMessages, stateRef);
5438
6069
  state = result.state;
5439
6070
  rawMessages = result.rawMessages;
5440
6071
  if (result.compressed) continue;
@@ -5458,14 +6089,14 @@ function createSmartAgent(opts) {
5458
6089
  if (currentPlan && !state.plan) {
5459
6090
  state = { ...state, plan: currentPlan };
5460
6091
  }
5461
- state = syncRuntimeTools(syncPlanState(await syncMemory(state)), rawMessages);
6092
+ state = syncRuntimeTools(syncPlanFromRef(await syncMemory(state)), rawMessages, toolSet);
5462
6093
  stateRef.toolHistory = state.toolHistory;
5463
6094
  stateRef.toolHistoryArchived = state.toolHistoryArchived;
5464
6095
  if (state.ctx?.__needsSummarization && summarizer) {
5465
6096
  const ctx = { ...state.ctx || {} };
5466
6097
  delete ctx.__needsSummarization;
5467
6098
  state = { ...state, ctx };
5468
- const result = await trySummarize(state, rawMessages);
6099
+ const result = await trySummarize(state, rawMessages, stateRef);
5469
6100
  state = result.state;
5470
6101
  rawMessages = result.rawMessages;
5471
6102
  continue;
@@ -5476,7 +6107,7 @@ function createSmartAgent(opts) {
5476
6107
  if (summarizationEnabled) {
5477
6108
  const after = decideAfter(state);
5478
6109
  if (after === "contextSummarize" && summarizer) {
5479
- const result = await trySummarize(state, rawMessages);
6110
+ const result = await trySummarize(state, rawMessages, stateRef);
5480
6111
  state = result.state;
5481
6112
  rawMessages = result.rawMessages;
5482
6113
  if (result.compressed) continue;
@@ -5502,6 +6133,7 @@ function createSmartAgent(opts) {
5502
6133
  snapshot: base.snapshot,
5503
6134
  resume: base.resume,
5504
6135
  resolveToolApproval: base.resolveToolApproval,
6136
+ resolveUserQuestion: base.resolveUserQuestion,
5505
6137
  asTool: base.asTool,
5506
6138
  asHandoff: base.asHandoff,
5507
6139
  __runtime: base.__runtime
@@ -5915,7 +6547,50 @@ var ProviderError = class extends Error {
5915
6547
  };
5916
6548
 
5917
6549
  // src/providers/base.ts
6550
+ var DEFAULT_RETRY = {
6551
+ maxRetries: 3,
6552
+ baseDelayMs: 500,
6553
+ maxDelayMs: 8e3
6554
+ };
5918
6555
  var BaseProvider = class {
6556
+ constructor() {
6557
+ this.retryPolicy = DEFAULT_RETRY;
6558
+ }
6559
+ /**
6560
+ * Helper concrete providers can wrap their fetch with. Retries 429/5xx
6561
+ * with exponential backoff and respects Retry-After if the response
6562
+ * carries one.
6563
+ */
6564
+ async doFetchWithRetry(execute) {
6565
+ const policy = this.retryPolicy;
6566
+ const maxRetries = Math.max(0, policy.maxRetries ?? DEFAULT_RETRY.maxRetries);
6567
+ const baseDelay = Math.max(0, policy.baseDelayMs ?? DEFAULT_RETRY.baseDelayMs);
6568
+ const maxDelay = Math.max(baseDelay, policy.maxDelayMs ?? DEFAULT_RETRY.maxDelayMs);
6569
+ let lastResp;
6570
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
6571
+ const res = await execute();
6572
+ const isRetryable = res.status === 429 || res.status >= 500 && res.status < 600;
6573
+ if (!isRetryable) return res;
6574
+ const predicate = policy.shouldRetry;
6575
+ if (predicate && !predicate({ status: res.status }, attempt)) return res;
6576
+ if (attempt === maxRetries) {
6577
+ lastResp = res;
6578
+ break;
6579
+ }
6580
+ const retryAfter = res.headers.get("retry-after");
6581
+ let delay = Math.min(maxDelay, baseDelay * Math.pow(2, attempt));
6582
+ if (retryAfter) {
6583
+ const asNumber = Number(retryAfter);
6584
+ if (!Number.isNaN(asNumber)) delay = Math.min(maxDelay, Math.max(delay, asNumber * 1e3));
6585
+ }
6586
+ try {
6587
+ await res.text();
6588
+ } catch {
6589
+ }
6590
+ await new Promise((r) => setTimeout(r, delay));
6591
+ }
6592
+ return lastResp;
6593
+ }
5919
6594
  };
5920
6595
 
5921
6596
  // src/providers/utils/sse.ts
@@ -6042,6 +6717,7 @@ var OpenAIProvider = class extends BaseProvider {
6042
6717
  ...config.defaultHeaders,
6043
6718
  ...config.organization ? { "OpenAI-Organization": config.organization } : {}
6044
6719
  };
6720
+ if (config.retry) this.retryPolicy = { ...this.retryPolicy, ...config.retry };
6045
6721
  }
6046
6722
  async complete(request) {
6047
6723
  const body = this.buildRequestBody(request, false);
@@ -6189,15 +6865,13 @@ var OpenAIProvider = class extends BaseProvider {
6189
6865
  }
6190
6866
  async doFetch(body) {
6191
6867
  const url = `${this.baseURL}/chat/completions`;
6192
- const res = await fetch(url, {
6193
- method: "POST",
6194
- headers: {
6195
- "Content-Type": "application/json",
6196
- Authorization: `Bearer ${this.apiKey}`,
6197
- ...this.defaultHeaders
6198
- },
6199
- body: JSON.stringify(body)
6200
- });
6868
+ const headers = {
6869
+ "Content-Type": "application/json",
6870
+ Authorization: `Bearer ${this.apiKey}`,
6871
+ ...this.defaultHeaders
6872
+ };
6873
+ const payload = JSON.stringify(body);
6874
+ const res = await this.doFetchWithRetry(() => fetch(url, { method: "POST", headers, body: payload }));
6201
6875
  if (!res.ok) {
6202
6876
  const text = await res.text().catch(() => "");
6203
6877
  throw new ProviderError(
@@ -6267,6 +6941,8 @@ var AnthropicProvider = class extends BaseProvider {
6267
6941
  this.defaultModel = config.defaultModel ?? "claude-sonnet-4-20250514";
6268
6942
  this.defaultHeaders = config.defaultHeaders ?? {};
6269
6943
  this.anthropicVersion = config.anthropicVersion ?? "2023-06-01";
6944
+ this.promptCachingEnabled = config.prompt_caching?.enabled === true;
6945
+ if (config.retry) this.retryPolicy = { ...this.retryPolicy, ...config.retry };
6270
6946
  }
6271
6947
  async complete(request) {
6272
6948
  const body = this.buildRequestBody(request, false);
@@ -6385,12 +7061,22 @@ var AnthropicProvider = class extends BaseProvider {
6385
7061
  max_tokens: request.maxTokens ?? 4096,
6386
7062
  stream
6387
7063
  };
6388
- if (system) body.system = system;
7064
+ if (system) {
7065
+ if (this.promptCachingEnabled) {
7066
+ body.system = [{ type: "text", text: system, cache_control: { type: "ephemeral" } }];
7067
+ } else {
7068
+ body.system = system;
7069
+ }
7070
+ }
6389
7071
  if (request.temperature != null) body.temperature = request.temperature;
6390
7072
  if (request.topP != null) body.top_p = request.topP;
6391
7073
  if (request.stop) body.stop_sequences = request.stop;
6392
7074
  if (request.tools?.length) {
6393
- body.tools = request.tools.map((t) => this.toAnthropicTool(t));
7075
+ const tools = request.tools.map((t) => this.toAnthropicTool(t));
7076
+ if (this.promptCachingEnabled && tools.length > 0) {
7077
+ tools[tools.length - 1] = { ...tools[tools.length - 1], cache_control: { type: "ephemeral" } };
7078
+ }
7079
+ body.tools = tools;
6394
7080
  }
6395
7081
  if (request.toolChoice != null) {
6396
7082
  if (request.toolChoice === "auto") {
@@ -6489,16 +7175,14 @@ var AnthropicProvider = class extends BaseProvider {
6489
7175
  }
6490
7176
  async doFetch(body) {
6491
7177
  const url = `${this.baseURL}/v1/messages`;
6492
- const res = await fetch(url, {
6493
- method: "POST",
6494
- headers: {
6495
- "Content-Type": "application/json",
6496
- "x-api-key": this.apiKey,
6497
- "anthropic-version": this.anthropicVersion,
6498
- ...this.defaultHeaders
6499
- },
6500
- body: JSON.stringify(body)
6501
- });
7178
+ const headers = {
7179
+ "Content-Type": "application/json",
7180
+ "x-api-key": this.apiKey,
7181
+ "anthropic-version": this.anthropicVersion,
7182
+ ...this.defaultHeaders
7183
+ };
7184
+ const payload = JSON.stringify(body);
7185
+ const res = await this.doFetchWithRetry(() => fetch(url, { method: "POST", headers, body: payload }));
6502
7186
  if (!res.ok) {
6503
7187
  const text = await res.text().catch(() => "");
6504
7188
  throw new ProviderError(
@@ -6710,6 +7394,8 @@ var BedrockProvider = class extends BaseProvider {
6710
7394
  this.region = config.region;
6711
7395
  this.defaultModel = config.defaultModel ?? "anthropic.claude-sonnet-4-20250514-v1:0";
6712
7396
  this.defaultHeaders = config.defaultHeaders ?? {};
7397
+ this.promptCachingEnabled = config.prompt_caching?.enabled === true;
7398
+ if (config.retry) this.retryPolicy = { ...this.retryPolicy, ...config.retry };
6713
7399
  this.credentials = {
6714
7400
  accessKeyId: config.accessKeyId ?? process.env.AWS_ACCESS_KEY_ID ?? "",
6715
7401
  secretAccessKey: config.secretAccessKey ?? process.env.AWS_SECRET_ACCESS_KEY ?? "",
@@ -6748,7 +7434,9 @@ var BedrockProvider = class extends BaseProvider {
6748
7434
  messages: messages.map((m) => this.toBedrockMessage(m))
6749
7435
  };
6750
7436
  if (system.length > 0) {
6751
- body.system = system.map((s) => ({ text: s }));
7437
+ const systemBlocks = system.map((s) => ({ text: s }));
7438
+ if (this.promptCachingEnabled) systemBlocks.push({ cachePoint: { type: "default" } });
7439
+ body.system = systemBlocks;
6752
7440
  }
6753
7441
  const inferenceConfig = {};
6754
7442
  if (request.maxTokens != null) inferenceConfig.maxTokens = request.maxTokens;
@@ -6757,8 +7445,12 @@ var BedrockProvider = class extends BaseProvider {
6757
7445
  if (request.stop) inferenceConfig.stopSequences = request.stop;
6758
7446
  if (Object.keys(inferenceConfig).length > 0) body.inferenceConfig = inferenceConfig;
6759
7447
  if (request.tools?.length) {
7448
+ const tools = request.tools.map((t) => this.toBedrockTool(t));
7449
+ if (this.promptCachingEnabled && tools.length > 0) {
7450
+ tools.push({ cachePoint: { type: "default" } });
7451
+ }
6760
7452
  body.toolConfig = {
6761
- tools: request.tools.map((t) => this.toBedrockTool(t))
7453
+ tools
6762
7454
  };
6763
7455
  if (request.toolChoice != null) {
6764
7456
  if (request.toolChoice === "auto") {
@@ -6855,23 +7547,21 @@ var BedrockProvider = class extends BaseProvider {
6855
7547
  const host = `bedrock-runtime.${this.region}.amazonaws.com`;
6856
7548
  const url = `https://${host}/model/${encodeURIComponent(modelId)}/converse`;
6857
7549
  const bodyStr = JSON.stringify(body);
6858
- const headers = signRequest({
6859
- method: "POST",
6860
- url,
6861
- headers: {
6862
- "Content-Type": "application/json",
6863
- Accept: "application/json",
6864
- ...this.defaultHeaders
6865
- },
6866
- body: bodyStr,
6867
- region: this.region,
6868
- service: "bedrock",
6869
- credentials: this.credentials
6870
- });
6871
- const res = await fetch(url, {
6872
- method: "POST",
6873
- headers,
6874
- body: bodyStr
7550
+ const res = await this.doFetchWithRetry(() => {
7551
+ const headers = signRequest({
7552
+ method: "POST",
7553
+ url,
7554
+ headers: {
7555
+ "Content-Type": "application/json",
7556
+ Accept: "application/json",
7557
+ ...this.defaultHeaders
7558
+ },
7559
+ body: bodyStr,
7560
+ region: this.region,
7561
+ service: "bedrock",
7562
+ credentials: this.credentials
7563
+ });
7564
+ return fetch(url, { method: "POST", headers, body: bodyStr });
6875
7565
  });
6876
7566
  if (!res.ok) {
6877
7567
  const text = await res.text().catch(() => "");
@@ -7746,6 +8436,6 @@ function createProvider(config) {
7746
8436
  }
7747
8437
  }
7748
8438
 
7749
- export { AnthropicProvider, AzureProvider, BaseProvider, BedrockProvider, DEFAULT_PROFILE_CONFIGS, GuardrailPhase, InMemoryMemoryStore, NativeJsonSchemaStrategy, OpenAICompatibleProvider, OpenAIProvider, ProviderError, StructuredOutputManager, ToolBasedStrategy, VertexProvider, agentVerdictRule, buildSystemPrompt, captureSnapshot, codePresenceRule, cognipeerSink, countApproxTokens, countMessagesTokens, createAgent, createAgentNode, createCodeGuardrail, createContextSummarizeNode, createContextTools, createGetToolResponseTool, createGuardrail, createJsonGuardrail, createManageTodoTool, createProvider, createRegexGuardrail, createResolverNode, createSmartAgent, createTool, createToolLimitFinalizeNode, createToolsNode, customCallbackRule, customSink, emptyUsage, evaluateGuardrails, fileSink, formatZodError, fromLangchainModel, fromLangchainTools, fromNativeProvider, generateSpanId, generateTraceId, getModelCapabilities, getNestedValue, getResolvedSmartConfig, hasToolResponseRecoveryReference, httpSink, isSmartModel, jsonSchemaRule, normalizeSmartAgentOptions, otlpSink, readMemoryFacts, regexRule, resolveMemoryStore, resolveStrategy, resolveToolApprovalState, restoreSnapshot, runSmartAgentEvalHarness, startStreamingSession, traceSessionToOtlp, withTools, writeSummaryFactsToMemory };
8439
+ export { AnthropicProvider, AzureProvider, BaseProvider, BedrockProvider, DEFAULT_PROFILE_CONFIGS, GuardrailPhase, InMemoryMemoryStore, NativeJsonSchemaStrategy, OpenAICompatibleProvider, OpenAIProvider, ProviderError, StructuredOutputManager, ToolBasedStrategy, VertexProvider, agentVerdictRule, buildSystemPrompt, captureSnapshot, codePresenceRule, cognipeerSink, countApproxTokens, countMessagesTokens, createAgent, createAgentNode, createAskUserQuestionTool, createCodeGuardrail, createContextSummarizeNode, createContextTools, createGetToolResponseTool, createGuardrail, createJsonGuardrail, createManageTodoTool, createProvider, createRegexGuardrail, createResolverNode, createSmartAgent, createTool, createToolLimitFinalizeNode, createToolsNode, customCallbackRule, customSink, defaultTokenCounter, emptyUsage, evaluateGuardrails, fileSink, formatZodError, fromLangchainModel, fromLangchainTools, fromNativeProvider, generateSpanId, generateTraceId, getModelCapabilities, getNestedValue, getResolvedSmartConfig, getTokenCounter, hasToolResponseRecoveryReference, httpSink, isSmartModel, jsonSchemaRule, normalizeSmartAgentOptions, otlpSink, readMemoryFacts, regexRule, resolveMemoryStore, resolveStrategy, resolveToolApprovalState, resolveUserQuestionState, restoreSnapshot, runSmartAgentEvalHarness, setTokenCounter, startStreamingSession, traceSessionToOtlp, withTools, writeSummaryFactsToMemory };
7750
8440
  //# sourceMappingURL=index.mjs.map
7751
8441
  //# sourceMappingURL=index.mjs.map