@cognipeer/agent-sdk 0.6.5 → 0.7.0

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
@@ -52,7 +52,7 @@ function getTokenCounter() {
52
52
  function countApproxTokens(text) {
53
53
  return activeTokenCounter(text || "");
54
54
  }
55
- function extractMessageText(m) {
55
+ function extractMessageText2(m) {
56
56
  if (!m) return "";
57
57
  const parts = [];
58
58
  parts.push(m.role || "");
@@ -102,7 +102,7 @@ function extractMessageText(m) {
102
102
  }
103
103
  function countMessagesTokens(messages) {
104
104
  if (!Array.isArray(messages) || messages.length === 0) return 0;
105
- const allText = messages.map(extractMessageText).join("\n");
105
+ const allText = messages.map(extractMessageText2).join("\n");
106
106
  return countApproxTokens(allText) + messages.length * 4;
107
107
  }
108
108
  var activeTokenCounter;
@@ -753,6 +753,7 @@ function createResolverNode() {
753
753
  toolHistory: state.toolHistory || [],
754
754
  toolHistoryArchived: state.toolHistoryArchived || [],
755
755
  pendingApprovals: state.pendingApprovals || [],
756
+ pendingUserQuestions: state.pendingUserQuestions || [],
756
757
  plan: state.plan || null,
757
758
  planVersion: state.planVersion || 0
758
759
  };
@@ -1271,6 +1272,7 @@ function eventToOtlpSpan(event) {
1271
1272
  stringAttr("cognipeer.actor.role", event.actor?.role),
1272
1273
  stringAttr("cognipeer.model", event.model),
1273
1274
  stringAttr("cognipeer.provider", event.provider),
1275
+ stringAttr("cognipeer.tool.details", event.toolDetails ? JSON.stringify(event.toolDetails) : void 0),
1274
1276
  intAttr("cognipeer.tokens.input", event.inputTokens),
1275
1277
  intAttr("cognipeer.tokens.output", event.outputTokens),
1276
1278
  intAttr("cognipeer.tokens.total", event.totalTokens),
@@ -1742,6 +1744,7 @@ function recordTraceEvent(session, event) {
1742
1744
  responseBytes,
1743
1745
  model: resolvedModel,
1744
1746
  provider: resolvedProvider,
1747
+ toolDetails: session.resolvedConfig.logData ? sanitizeTracePayload(event.toolDetails) : void 0,
1745
1748
  toolExecutionId: event.toolExecutionId,
1746
1749
  retryOf: event.retryOf,
1747
1750
  error: event.error ?? (status === "error" ? { message: "Unknown error" } : void 0) ?? void 0,
@@ -2192,16 +2195,99 @@ function createAgentCoreNode(opts) {
2192
2195
  };
2193
2196
  }
2194
2197
 
2198
+ // src/utils/content.ts
2199
+ function contentToString(content) {
2200
+ try {
2201
+ if (content == null) return "";
2202
+ if (typeof content === "string") return content;
2203
+ if (Array.isArray(content)) {
2204
+ return content.map((p) => {
2205
+ if (!p || typeof p !== "object") return String(p);
2206
+ if (p.type === "text") return String(p.text ?? "");
2207
+ if (p.type === "image_url") {
2208
+ const img = p.image_url;
2209
+ const url = typeof img === "string" ? img : img?.url;
2210
+ const detail = typeof img === "string" ? void 0 : img?.detail;
2211
+ return `[image:${url || "unknown"}${detail ? ` detail=${detail}` : ""}]`;
2212
+ }
2213
+ return JSON.stringify(p);
2214
+ }).join("\n");
2215
+ }
2216
+ return JSON.stringify(content);
2217
+ } catch {
2218
+ return String(content);
2219
+ }
2220
+ }
2221
+ function mergeContentsToString(contents) {
2222
+ return contents.map((c) => contentToString(c)).join("\n");
2223
+ }
2224
+ function safeStringify(value, pretty = false) {
2225
+ if (value == null) return "";
2226
+ if (typeof value === "string") return value;
2227
+ try {
2228
+ return JSON.stringify(value, null, pretty ? 2 : void 0) ?? String(value);
2229
+ } catch {
2230
+ return String(value);
2231
+ }
2232
+ }
2233
+ function extractMessageText(message) {
2234
+ if (!message) return "";
2235
+ const content = message.content;
2236
+ if (typeof content === "string") return content;
2237
+ if (Array.isArray(content)) {
2238
+ return content.map((part) => typeof part === "string" ? part : part?.text ?? part?.content ?? "").join("");
2239
+ }
2240
+ return "";
2241
+ }
2242
+
2243
+ // src/utils/traceSections.ts
2244
+ function truncate(text, max = 500) {
2245
+ if (!text) return "";
2246
+ return text.length > max ? `${text.slice(0, max)}\u2026` : text;
2247
+ }
2248
+ function summarizeToolOutput(output) {
2249
+ if (output == null) return "";
2250
+ if (typeof output === "string") return truncate(output.trim());
2251
+ if (Array.isArray(output)) {
2252
+ const preview = output.slice(0, 3).map((item, index) => `${index + 1}. ${truncate(safeStringify(item))}`).join("\n");
2253
+ const suffix = output.length > 3 ? `
2254
+ \u2026 (+${output.length - 3} more)` : "";
2255
+ return `${preview}${suffix}`.trim();
2256
+ }
2257
+ if (typeof output === "object") {
2258
+ if (Array.isArray(output.items)) {
2259
+ return summarizeToolOutput(output.items);
2260
+ }
2261
+ return truncate(safeStringify(output));
2262
+ }
2263
+ return truncate(String(output));
2264
+ }
2265
+ function extractToolItems(output) {
2266
+ const source = Array.isArray(output) ? output : Array.isArray(output?.items) ? output.items : void 0;
2267
+ if (!Array.isArray(source) || source.length === 0) return void 0;
2268
+ return source.slice(0, 5).map((item) => {
2269
+ if (item && typeof item === "object") {
2270
+ const snippetSource = item.content ?? item.snippet ?? item.description ?? item.text ?? item.summary ?? item;
2271
+ return {
2272
+ title: typeof item.title === "string" ? item.title : void 0,
2273
+ url: typeof item.url === "string" ? item.url : void 0,
2274
+ snippet: truncate(safeStringify(snippetSource))
2275
+ };
2276
+ }
2277
+ return { snippet: truncate(safeStringify(item)) };
2278
+ });
2279
+ }
2280
+
2195
2281
  // src/smart/runtimeConfig.ts
2196
2282
  var DEFAULT_CRITICAL_TOOLS = ["response", "manage_todo_list", "get_tool_response"];
2197
2283
  var BASE_DEFAULTS = {
2198
- limits: { maxToolCalls: 8, maxParallelTools: 2, maxContextTokens: 24e3, maxTotalOutputTokens: 0, maxCostUsd: 0, maxWallClockMs: 0 },
2284
+ limits: { maxToolCalls: 20, maxParallelTools: 5, maxContextTokens: 96e3, maxTotalOutputTokens: 0, maxCostUsd: 0, maxWallClockMs: 0 },
2199
2285
  summarization: {
2200
2286
  enable: true,
2201
- maxTokens: 24e3,
2202
- summaryTriggerTokens: 17e3,
2203
- summaryPromptMaxTokens: 7e3,
2204
- summaryCompressionRatioTarget: 0.35,
2287
+ maxTokens: 96e3,
2288
+ summaryTriggerTokens: 72e3,
2289
+ summaryPromptMaxTokens: 12e3,
2290
+ summaryCompressionRatioTarget: 0.45,
2205
2291
  summaryMode: "incremental",
2206
2292
  promptTemplate: "",
2207
2293
  toolFreeCall: true,
@@ -2209,13 +2295,13 @@ var BASE_DEFAULTS = {
2209
2295
  },
2210
2296
  context: {
2211
2297
  policy: "hybrid",
2212
- lastTurnsToKeep: 8,
2298
+ lastTurnsToKeep: 16,
2213
2299
  toolResponsePolicy: "summarize_archive",
2214
2300
  budget: {
2215
- systemReserveTokens: 1200,
2216
- goalsReserveTokens: 1200,
2217
- recentTurnsReserveTokens: 5200,
2218
- toolResponseReserveTokens: 2800
2301
+ systemReserveTokens: 2e3,
2302
+ goalsReserveTokens: 2e3,
2303
+ recentTurnsReserveTokens: 18e3,
2304
+ toolResponseReserveTokens: 9e3
2219
2305
  }
2220
2306
  },
2221
2307
  planning: {
@@ -2232,14 +2318,14 @@ var BASE_DEFAULTS = {
2232
2318
  delegation: {
2233
2319
  mode: "role_based",
2234
2320
  maxDelegationDepth: 2,
2235
- maxChildCalls: 4,
2236
- maxParallelChild: 2,
2321
+ maxChildCalls: 6,
2322
+ maxParallelChild: 3,
2237
2323
  childContextPolicy: "scoped",
2238
2324
  requireJsonOutputContract: true
2239
2325
  },
2240
2326
  toolResponses: {
2241
- maxToolResponseChars: 12e3,
2242
- maxToolResponseTokens: 3e3,
2327
+ maxToolResponseChars: 32e3,
2328
+ maxToolResponseTokens: 8e3,
2243
2329
  defaultPolicy: "summarize_archive",
2244
2330
  toolResponseRetentionByTool: {},
2245
2331
  criticalTools: [...DEFAULT_CRITICAL_TOOLS],
@@ -2268,54 +2354,54 @@ function buildProfile(overrides) {
2268
2354
  }
2269
2355
  var DEFAULT_PROFILE_CONFIGS = {
2270
2356
  fast: buildProfile({
2271
- limits: { maxToolCalls: 4, maxParallelTools: 1, maxContextTokens: 12e3 },
2357
+ limits: { maxToolCalls: 8, maxParallelTools: 3, maxContextTokens: 32e3 },
2272
2358
  summarization: {
2273
- maxTokens: 12e3,
2274
- summaryTriggerTokens: 9e3,
2275
- summaryPromptMaxTokens: 5e3,
2276
- summaryCompressionRatioTarget: 0.4
2359
+ maxTokens: 32e3,
2360
+ summaryTriggerTokens: 24e3,
2361
+ summaryPromptMaxTokens: 6e3,
2362
+ summaryCompressionRatioTarget: 0.5
2277
2363
  },
2278
2364
  context: {
2279
- lastTurnsToKeep: 6,
2365
+ lastTurnsToKeep: 8,
2280
2366
  toolResponsePolicy: "keep_structured",
2281
- budget: { systemReserveTokens: 1e3, goalsReserveTokens: 900, recentTurnsReserveTokens: 3200, toolResponseReserveTokens: 1800 }
2367
+ budget: { systemReserveTokens: 1500, goalsReserveTokens: 1200, recentTurnsReserveTokens: 6e3, toolResponseReserveTokens: 3500 }
2282
2368
  },
2283
2369
  memory: { readPolicy: "recent_only" },
2284
2370
  delegation: { mode: "off", maxDelegationDepth: 1, maxChildCalls: 2, maxParallelChild: 1, childContextPolicy: "minimal" },
2285
- toolResponses: { maxToolResponseChars: 8e3, maxToolResponseTokens: 2e3 }
2371
+ toolResponses: { maxToolResponseChars: 16e3, maxToolResponseTokens: 4e3 }
2286
2372
  }),
2287
2373
  balanced: buildProfile({}),
2288
2374
  deep: buildProfile({
2289
- limits: { maxToolCalls: 14, maxParallelTools: 3, maxContextTokens: 42e3 },
2375
+ limits: { maxToolCalls: 40, maxParallelTools: 8, maxContextTokens: 2e5 },
2290
2376
  summarization: {
2291
- maxTokens: 42e3,
2292
- summaryTriggerTokens: 3e4,
2293
- summaryPromptMaxTokens: 9e3,
2294
- summaryCompressionRatioTarget: 0.3
2377
+ maxTokens: 2e5,
2378
+ summaryTriggerTokens: 15e4,
2379
+ summaryPromptMaxTokens: 16e3,
2380
+ summaryCompressionRatioTarget: 0.4
2295
2381
  },
2296
2382
  context: {
2297
- lastTurnsToKeep: 12,
2298
- budget: { systemReserveTokens: 1400, goalsReserveTokens: 1600, recentTurnsReserveTokens: 7e3, toolResponseReserveTokens: 3600 }
2383
+ lastTurnsToKeep: 30,
2384
+ budget: { systemReserveTokens: 2500, goalsReserveTokens: 2500, recentTurnsReserveTokens: 36e3, toolResponseReserveTokens: 18e3 }
2299
2385
  },
2300
2386
  memory: { scope: "workspace" },
2301
- delegation: { maxDelegationDepth: 3, maxChildCalls: 6 },
2302
- toolResponses: { maxToolResponseChars: 16e3, maxToolResponseTokens: 4e3 }
2387
+ delegation: { maxDelegationDepth: 3, maxChildCalls: 10, maxParallelChild: 4 },
2388
+ toolResponses: { maxToolResponseChars: 64e3, maxToolResponseTokens: 16e3 }
2303
2389
  }),
2304
2390
  research: buildProfile({
2305
- limits: { maxToolCalls: 20, maxParallelTools: 4, maxContextTokens: 56e3 },
2391
+ limits: { maxToolCalls: 80, maxParallelTools: 10, maxContextTokens: 4e5 },
2306
2392
  summarization: {
2307
- maxTokens: 56e3,
2308
- summaryTriggerTokens: 42e3,
2309
- summaryPromptMaxTokens: 1e4,
2310
- summaryCompressionRatioTarget: 0.28
2393
+ maxTokens: 4e5,
2394
+ summaryTriggerTokens: 3e5,
2395
+ summaryPromptMaxTokens: 24e3,
2396
+ summaryCompressionRatioTarget: 0.35
2311
2397
  },
2312
2398
  context: {
2313
- lastTurnsToKeep: 20,
2314
- budget: { systemReserveTokens: 1600, goalsReserveTokens: 1800, recentTurnsReserveTokens: 9e3, toolResponseReserveTokens: 4800 }
2399
+ lastTurnsToKeep: 60,
2400
+ budget: { systemReserveTokens: 3e3, goalsReserveTokens: 3e3, recentTurnsReserveTokens: 72e3, toolResponseReserveTokens: 32e3 }
2315
2401
  },
2316
2402
  memory: { scope: "workspace" },
2317
- delegation: { mode: "automatic", maxDelegationDepth: 4, maxChildCalls: 8, maxParallelChild: 3, childContextPolicy: "full" },
2318
- toolResponses: { maxToolResponseChars: 24e3, maxToolResponseTokens: 6e3 }
2403
+ delegation: { mode: "automatic", maxDelegationDepth: 4, maxChildCalls: 16, maxParallelChild: 6, childContextPolicy: "full" },
2404
+ toolResponses: { maxToolResponseChars: 96e3, maxToolResponseTokens: 24e3 }
2319
2405
  })
2320
2406
  };
2321
2407
  function mergeLimits(base, override) {
@@ -2405,53 +2491,6 @@ function getResolvedSmartConfig(opts, runtime) {
2405
2491
 
2406
2492
  // src/smart/toolResponses.ts
2407
2493
  init_utilTokens();
2408
-
2409
- // src/utils/content.ts
2410
- function contentToString(content) {
2411
- try {
2412
- if (content == null) return "";
2413
- if (typeof content === "string") return content;
2414
- if (Array.isArray(content)) {
2415
- return content.map((p) => {
2416
- if (!p || typeof p !== "object") return String(p);
2417
- if (p.type === "text") return String(p.text ?? "");
2418
- if (p.type === "image_url") {
2419
- const img = p.image_url;
2420
- const url = typeof img === "string" ? img : img?.url;
2421
- const detail = typeof img === "string" ? void 0 : img?.detail;
2422
- return `[image:${url || "unknown"}${detail ? ` detail=${detail}` : ""}]`;
2423
- }
2424
- return JSON.stringify(p);
2425
- }).join("\n");
2426
- }
2427
- return JSON.stringify(content);
2428
- } catch {
2429
- return String(content);
2430
- }
2431
- }
2432
- function mergeContentsToString(contents) {
2433
- return contents.map((c) => contentToString(c)).join("\n");
2434
- }
2435
- function safeStringify(value, pretty = false) {
2436
- if (value == null) return "";
2437
- if (typeof value === "string") return value;
2438
- try {
2439
- return JSON.stringify(value, null, pretty ? 2 : void 0) ?? String(value);
2440
- } catch {
2441
- return String(value);
2442
- }
2443
- }
2444
- function extractMessageText2(message) {
2445
- if (!message) return "";
2446
- const content = message.content;
2447
- if (typeof content === "string") return content;
2448
- if (Array.isArray(content)) {
2449
- return content.map((part) => typeof part === "string" ? part : part?.text ?? part?.content ?? "").join("");
2450
- }
2451
- return "";
2452
- }
2453
-
2454
- // src/smart/toolResponses.ts
2455
2494
  function summarizeObject(value) {
2456
2495
  if (value == null) return "null";
2457
2496
  if (typeof value === "string") {
@@ -2566,6 +2605,97 @@ function normalizeToolCall(call) {
2566
2605
  args: call.args ?? call.arguments ?? call.input ?? call.function?.arguments ?? call.function_call?.arguments
2567
2606
  };
2568
2607
  }
2608
+ function normalizeToolSchema(tool, toolName) {
2609
+ const schema = tool.schema;
2610
+ if (!schema) return void 0;
2611
+ if (typeof schema === "object" && schema._def) {
2612
+ try {
2613
+ return sanitizeTracePayload(zodToJsonSchema.zodToJsonSchema(schema, { name: toolName }));
2614
+ } catch {
2615
+ }
2616
+ }
2617
+ if (schema && typeof schema === "object" && typeof schema.toJSON === "function") {
2618
+ try {
2619
+ return sanitizeTracePayload(schema.toJSON());
2620
+ } catch {
2621
+ }
2622
+ }
2623
+ return sanitizeTracePayload(schema);
2624
+ }
2625
+ function buildToolDetails(tool, toolName) {
2626
+ if (!tool) return void 0;
2627
+ const details = { name: toolName };
2628
+ if (typeof tool.description === "string" && tool.description.trim().length > 0) {
2629
+ details.description = tool.description;
2630
+ }
2631
+ const inputSchema = normalizeToolSchema(tool, toolName);
2632
+ if (inputSchema !== void 0) details.inputSchema = inputSchema;
2633
+ const needsApproval = typeof tool.needsApproval === "boolean" ? tool.needsApproval : void 0;
2634
+ if (needsApproval !== void 0 || tool.approvalPrompt !== void 0 || tool.approvalDefaults !== void 0) {
2635
+ details.approval = {
2636
+ required: needsApproval,
2637
+ prompt: typeof tool.approvalPrompt === "string" ? tool.approvalPrompt : void 0,
2638
+ defaults: tool.approvalDefaults !== void 0 ? sanitizeTracePayload(tool.approvalDefaults) : void 0
2639
+ };
2640
+ }
2641
+ const cache = tool.cache;
2642
+ if (cache !== void 0) {
2643
+ details.cache = {
2644
+ enabled: Boolean(cache),
2645
+ scope: cache && typeof cache === "object" && typeof cache.scope === "string" ? cache.scope : void 0,
2646
+ ttlMs: cache && typeof cache === "object" && typeof cache.ttlMs === "number" ? cache.ttlMs : void 0,
2647
+ hasKeyFn: Boolean(cache && typeof cache === "object" && typeof cache.keyFn === "function")
2648
+ };
2649
+ }
2650
+ const retry = tool.retry;
2651
+ if (retry && typeof retry === "object") {
2652
+ details.retry = {
2653
+ maxRetries: typeof retry.maxRetries === "number" ? retry.maxRetries : void 0,
2654
+ backoffMs: typeof retry.backoffMs === "number" ? retry.backoffMs : void 0,
2655
+ circuitBreakerThreshold: typeof retry.circuitBreakerThreshold === "number" ? retry.circuitBreakerThreshold : void 0,
2656
+ hasShouldRetry: typeof retry.shouldRetry === "function"
2657
+ };
2658
+ }
2659
+ if (tool.maxExecutionsPerRun !== void 0) {
2660
+ details.limits = { maxExecutionsPerRun: tool.maxExecutionsPerRun };
2661
+ }
2662
+ if (typeof tool.__source === "string") {
2663
+ details.source = tool.__source;
2664
+ }
2665
+ return details;
2666
+ }
2667
+ function buildToolTraceSections(params) {
2668
+ const sanitizedArgs = sanitizeTracePayload(params.args ?? {});
2669
+ const sanitizedOutput = sanitizeTracePayload(params.output);
2670
+ const summary = params.summary ?? summarizeToolOutput(sanitizedOutput);
2671
+ const items = extractToolItems(sanitizedOutput);
2672
+ return [
2673
+ {
2674
+ kind: "tool_call",
2675
+ label: `Tool Call: ${params.toolName}`,
2676
+ tool: params.toolName,
2677
+ arguments: sanitizedArgs,
2678
+ toolDetails: params.toolDetails
2679
+ },
2680
+ {
2681
+ kind: "tool_result",
2682
+ label: `Tool Result: ${params.toolName}`,
2683
+ tool: params.toolName,
2684
+ summary,
2685
+ items,
2686
+ output: sanitizedOutput,
2687
+ toolDetails: params.toolDetails,
2688
+ execution: {
2689
+ id: params.executionId,
2690
+ status: params.status,
2691
+ durationMs: params.durationMs,
2692
+ fromCache: params.fromCache
2693
+ },
2694
+ classification: params.classification,
2695
+ retentionPolicy: params.retentionPolicy
2696
+ }
2697
+ ];
2698
+ }
2569
2699
  function createToolsNode(initialTools, opts) {
2570
2700
  return async (state) => {
2571
2701
  const runtime = state.agent || {
@@ -2594,13 +2724,16 @@ function createToolsNode(initialTools, opts) {
2594
2724
  const traceSession = state.ctx?.__traceSession;
2595
2725
  const pendingApprovals = Array.isArray(state.pendingApprovals) ? state.pendingApprovals.map((entry) => ({ ...entry })) : [];
2596
2726
  const pendingByCallId = new Map(pendingApprovals.map((entry) => [entry.toolCallId, entry]));
2727
+ const pendingUserQuestions = Array.isArray(state.pendingUserQuestions) ? state.pendingUserQuestions.map((entry) => ({ ...entry })) : [];
2597
2728
  let awaitingApproval = false;
2729
+ let awaitingUserQuestion = false;
2598
2730
  for (const tool of toolByName.values()) {
2599
2731
  const anyTool = tool;
2600
2732
  if (anyTool._stateRef && typeof anyTool._stateRef === "object") {
2601
2733
  anyTool._stateRef.toolHistory = state.toolHistory;
2602
2734
  anyTool._stateRef.toolHistoryArchived = state.toolHistoryArchived;
2603
2735
  anyTool._stateRef.pendingApprovals = pendingApprovals;
2736
+ anyTool._stateRef.pendingUserQuestions = pendingUserQuestions;
2604
2737
  anyTool._stateRef.messages = state.messages;
2605
2738
  anyTool._stateRef.ctx = state.ctx || (state.ctx = {});
2606
2739
  anyTool._stateRef.__onEvent = onEvent;
@@ -2671,9 +2804,11 @@ function createToolsNode(initialTools, opts) {
2671
2804
  args = validatedArgs.value;
2672
2805
  }
2673
2806
  const toolName = tool.name || tc.name;
2807
+ const toolDetails = buildToolDetails(tool, toolName);
2674
2808
  const toolStateRef = tool._stateRef && typeof tool._stateRef === "object" ? tool._stateRef : null;
2675
2809
  if (toolStateRef) {
2676
2810
  toolStateRef.pendingApprovals = pendingApprovals;
2811
+ toolStateRef.pendingUserQuestions = pendingUserQuestions;
2677
2812
  toolStateRef.ctx = state.ctx || (state.ctx = {});
2678
2813
  toolStateRef.__currentToolCallId = toolCallId;
2679
2814
  delete toolStateRef.__awaitingApproval;
@@ -2690,21 +2825,17 @@ function createToolsNode(initialTools, opts) {
2690
2825
  label: `Tool Skipped - ${toolName}`,
2691
2826
  actor: { scope: "tool", name: toolName, role: "tool" },
2692
2827
  status: "skipped",
2828
+ toolDetails,
2693
2829
  toolExecutionId: tc.id,
2694
- messageList: [
2695
- {
2696
- role: "assistant",
2697
- name: toolName,
2698
- content: limitMessage,
2699
- tool_calls: [
2700
- {
2701
- id: tc.id,
2702
- type: "function",
2703
- function: { name: toolName, arguments: sanitizeTracePayload(args) }
2704
- }
2705
- ]
2706
- }
2707
- ]
2830
+ sections: buildToolTraceSections({
2831
+ args,
2832
+ executionId: tc.id,
2833
+ output: limitMessage,
2834
+ status: "skipped",
2835
+ summary: limitMessage,
2836
+ toolDetails,
2837
+ toolName
2838
+ })
2708
2839
  });
2709
2840
  toolCount += 1;
2710
2841
  return { status: "error" };
@@ -2791,6 +2922,25 @@ function createToolsNode(initialTools, opts) {
2791
2922
  push({ role: "tool", content: breakerMsg, tool_call_id: toolCallId, name: tc.name });
2792
2923
  markToolFailure(breakerMsg, toolName, toolCallId);
2793
2924
  onEvent?.({ type: "tool_call", phase: "error", name: toolName, id: tc.id, args, error: { message: breakerMsg } });
2925
+ recordTraceEvent(traceSession, {
2926
+ type: "tool_call",
2927
+ label: `Tool Error - ${toolName}`,
2928
+ actor: { scope: "tool", name: toolName, role: "tool" },
2929
+ status: "error",
2930
+ requestBytes: inputBytes,
2931
+ toolDetails,
2932
+ toolExecutionId: toolCallId,
2933
+ error: { message: breakerMsg },
2934
+ sections: buildToolTraceSections({
2935
+ args,
2936
+ executionId: toolCallId,
2937
+ output: { message: breakerMsg },
2938
+ status: "error",
2939
+ summary: breakerMsg,
2940
+ toolDetails,
2941
+ toolName
2942
+ })
2943
+ });
2794
2944
  toolCount += 1;
2795
2945
  return { status: "error" };
2796
2946
  }
@@ -2815,6 +2965,38 @@ function createToolsNode(initialTools, opts) {
2815
2965
  });
2816
2966
  push({ role: "tool", content: responsePolicy2.content, tool_call_id: tc.id || `${tc.name}_${toolCount}`, name: tc.name });
2817
2967
  onEvent?.({ type: "tool_call", phase: "success", name: toolName, id: tc.id, args, result: cachedHit, durationMs: 0 });
2968
+ const sanitizedCachedHit = sanitizeTracePayload(cachedHit);
2969
+ const outputBytes2 = traceSession?.resolvedConfig.logData ? estimatePayloadBytes(sanitizedCachedHit) : void 0;
2970
+ recordTraceEvent(traceSession, {
2971
+ type: "tool_call",
2972
+ label: `Tool Execution - ${toolName}`,
2973
+ actor: { scope: "tool", name: toolName, role: "tool" },
2974
+ durationMs: 0,
2975
+ requestBytes: inputBytes,
2976
+ responseBytes: outputBytes2,
2977
+ toolDetails,
2978
+ toolExecutionId: executionId2,
2979
+ sections: buildToolTraceSections({
2980
+ args,
2981
+ classification: responsePolicy2.classification,
2982
+ durationMs: 0,
2983
+ executionId: executionId2,
2984
+ fromCache: true,
2985
+ output: sanitizedCachedHit,
2986
+ retentionPolicy: "keep_full",
2987
+ status: "cached",
2988
+ summary: responsePolicy2.content,
2989
+ toolDetails,
2990
+ toolName
2991
+ }),
2992
+ debug: {
2993
+ classification: responsePolicy2.classification,
2994
+ retentionPolicy: "keep_full",
2995
+ originalTokenCount: responsePolicy2.tokenCount,
2996
+ truncated: responsePolicy2.truncated,
2997
+ fromCache: true
2998
+ }
2999
+ });
2818
3000
  toolCount += 1;
2819
3001
  return { status: "success" };
2820
3002
  }
@@ -2885,6 +3067,22 @@ function createToolsNode(initialTools, opts) {
2885
3067
  approval: pendingApproval
2886
3068
  };
2887
3069
  }
3070
+ if (output && typeof output === "object" && output.__awaitingUserQuestion) {
3071
+ if (toolStateRef?.ctx && typeof toolStateRef.ctx === "object") {
3072
+ state.ctx = toolStateRef.ctx;
3073
+ }
3074
+ const pendingQuestion = pendingUserQuestions.find(
3075
+ (entry) => entry.toolCallId === output.toolCallId || entry.id === output.id
3076
+ );
3077
+ if (!pendingQuestion) {
3078
+ throw new Error(`Tool ${toolName} requested a user question but no pending question entry was recorded.`);
3079
+ }
3080
+ awaitingUserQuestion = true;
3081
+ return {
3082
+ status: "awaiting_user_question",
3083
+ userQuestion: pendingQuestion
3084
+ };
3085
+ }
2888
3086
  if (output && typeof output === "object" && output.__finalStructuredOutput) {
2889
3087
  if (!state.ctx) state.ctx = {};
2890
3088
  state.ctx.__structuredOutputParsed = output.data;
@@ -2919,28 +3117,6 @@ function createToolsNode(initialTools, opts) {
2919
3117
  }
2920
3118
  const sanitizedOutput = sanitizeTracePayload(output);
2921
3119
  const outputBytes = traceSession?.resolvedConfig.logData ? estimatePayloadBytes(sanitizedOutput) : void 0;
2922
- const messageList = [
2923
- {
2924
- role: "assistant",
2925
- name: toolName,
2926
- content: "",
2927
- tool_calls: [
2928
- {
2929
- id: tc.id || executionId,
2930
- type: "function",
2931
- function: {
2932
- name: toolName,
2933
- arguments: sanitizedArgs
2934
- }
2935
- }
2936
- ]
2937
- },
2938
- {
2939
- role: "tool",
2940
- name: toolName,
2941
- content: responsePolicy.content
2942
- }
2943
- ];
2944
3120
  recordTraceEvent(traceSession, {
2945
3121
  type: "tool_call",
2946
3122
  label: `Tool Execution - ${toolName}`,
@@ -2948,8 +3124,20 @@ function createToolsNode(initialTools, opts) {
2948
3124
  durationMs,
2949
3125
  requestBytes: inputBytes,
2950
3126
  responseBytes: outputBytes,
3127
+ toolDetails,
2951
3128
  toolExecutionId: executionId,
2952
- messageList,
3129
+ sections: buildToolTraceSections({
3130
+ args: sanitizedArgs,
3131
+ classification: responsePolicy.classification,
3132
+ durationMs,
3133
+ executionId,
3134
+ output: sanitizedOutput,
3135
+ retentionPolicy: "keep_full",
3136
+ status: "success",
3137
+ summary: responsePolicy.content,
3138
+ toolDetails,
3139
+ toolName
3140
+ }),
2953
3141
  debug: {
2954
3142
  classification: responsePolicy.classification,
2955
3143
  retentionPolicy: "keep_full",
@@ -2968,6 +3156,7 @@ function createToolsNode(initialTools, opts) {
2968
3156
  markToolFailure(message, toolName, toolCallId);
2969
3157
  onEvent?.({ type: "tool_call", phase: "error", name: toolName, id: tc.id, args, error: { message } });
2970
3158
  onProgress?.({ stage: "tools", message: `Tool ${tc.name} failed`, detail: { error: message } });
3159
+ const errorOutput = { message, stack: error?.stack };
2971
3160
  recordTraceEvent(traceSession, {
2972
3161
  type: "tool_call",
2973
3162
  label: `Tool Error - ${toolName}`,
@@ -2975,27 +3164,20 @@ function createToolsNode(initialTools, opts) {
2975
3164
  status: "error",
2976
3165
  durationMs,
2977
3166
  requestBytes: inputBytes,
3167
+ responseBytes: traceSession?.resolvedConfig.logData ? estimatePayloadBytes(errorOutput) : void 0,
3168
+ toolDetails,
2978
3169
  toolExecutionId: executionId,
2979
3170
  error: { message, stack: error?.stack },
2980
- messageList: [
2981
- {
2982
- role: "assistant",
2983
- name: toolName,
2984
- content: "",
2985
- tool_calls: [
2986
- {
2987
- id: tc.id || executionId,
2988
- type: "function",
2989
- function: { name: toolName, arguments: sanitizedArgs }
2990
- }
2991
- ]
2992
- },
2993
- {
2994
- role: "tool",
2995
- name: toolName,
2996
- content: `Error executing tool: ${message}`
2997
- }
2998
- ]
3171
+ sections: buildToolTraceSections({
3172
+ args: sanitizedArgs,
3173
+ durationMs,
3174
+ executionId,
3175
+ output: errorOutput,
3176
+ status: "error",
3177
+ summary: `Error executing tool: ${message}`,
3178
+ toolDetails,
3179
+ toolName
3180
+ })
2999
3181
  });
3000
3182
  toolCount += 1;
3001
3183
  return { status: "error", approval: approvalEntry };
@@ -3022,15 +3204,19 @@ function createToolsNode(initialTools, opts) {
3022
3204
  return false;
3023
3205
  };
3024
3206
  for (const idx of approvalIndices) {
3025
- if (awaitingApproval || shouldStop) break;
3207
+ if (awaitingApproval || awaitingUserQuestion || shouldStop) break;
3026
3208
  if (checkCancellationStop()) break;
3027
3209
  const result = await runOne(planned[idx], orderedSlots[idx]);
3028
3210
  if (result.status === "awaiting_approval") {
3029
3211
  awaitingApproval = true;
3030
3212
  break;
3031
3213
  }
3214
+ if (result.status === "awaiting_user_question") {
3215
+ awaitingUserQuestion = true;
3216
+ break;
3217
+ }
3032
3218
  }
3033
- if (!awaitingApproval && !shouldStop && parallelIndices.length > 0) {
3219
+ if (!awaitingApproval && !awaitingUserQuestion && !shouldStop && parallelIndices.length > 0) {
3034
3220
  const concurrency = Math.max(1, Math.min(limits.maxParallelTools, parallelIndices.length));
3035
3221
  let cursor = 0;
3036
3222
  const workerError = {};
@@ -3038,7 +3224,7 @@ function createToolsNode(initialTools, opts) {
3038
3224
  for (let w = 0; w < concurrency; w++) {
3039
3225
  workers.push((async () => {
3040
3226
  while (true) {
3041
- if (workerError.err || shouldStop || awaitingApproval) return;
3227
+ if (workerError.err || shouldStop || awaitingApproval || awaitingUserQuestion) return;
3042
3228
  const i = cursor++;
3043
3229
  if (i >= parallelIndices.length) return;
3044
3230
  if (checkCancellationStop()) return;
@@ -3049,6 +3235,10 @@ function createToolsNode(initialTools, opts) {
3049
3235
  awaitingApproval = true;
3050
3236
  return;
3051
3237
  }
3238
+ if (result.status === "awaiting_user_question") {
3239
+ awaitingUserQuestion = true;
3240
+ return;
3241
+ }
3052
3242
  } catch (err) {
3053
3243
  workerError.err = err;
3054
3244
  return;
@@ -3064,29 +3254,26 @@ function createToolsNode(initialTools, opts) {
3064
3254
  }
3065
3255
  for (const tc of skipped) {
3066
3256
  onEvent?.({ type: "tool_call", phase: "skipped", name: tc.name, id: tc.id, args: tc.args });
3067
- const sanitizedArgs = sanitizeTracePayload(tc.args);
3257
+ const toolDetails = buildToolDetails(toolByName.get(tc.name), tc.name);
3258
+ const skipMessage = `Skipped tool due to tool-call limit: ${tc.name}`;
3068
3259
  recordTraceEvent(traceSession, {
3069
3260
  type: "tool_call",
3070
3261
  label: `Tool Skipped - ${tc.name}`,
3071
3262
  actor: { scope: "tool", name: tc.name, role: "tool" },
3072
3263
  status: "skipped",
3264
+ toolDetails,
3073
3265
  toolExecutionId: tc.id,
3074
- messageList: [
3075
- {
3076
- role: "assistant",
3077
- name: tc.name,
3078
- content: `Skipped tool due to tool-call limit: ${tc.name}`,
3079
- tool_calls: [
3080
- {
3081
- id: tc.id,
3082
- type: "function",
3083
- function: { name: tc.name, arguments: sanitizedArgs }
3084
- }
3085
- ]
3086
- }
3087
- ]
3266
+ sections: buildToolTraceSections({
3267
+ args: tc.args,
3268
+ executionId: tc.id,
3269
+ output: skipMessage,
3270
+ status: "skipped",
3271
+ summary: skipMessage,
3272
+ toolDetails,
3273
+ toolName: tc.name
3274
+ })
3088
3275
  });
3089
- appended.push({ role: "tool", content: `Skipped tool due to tool-call limit: ${tc.name}`, tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
3276
+ appended.push({ role: "tool", content: skipMessage, tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
3090
3277
  toolCount += 1;
3091
3278
  }
3092
3279
  if (!awaitingApproval && state.ctx?.__awaitingApproval) {
@@ -3111,11 +3298,12 @@ function createToolsNode(initialTools, opts) {
3111
3298
  toolCache: state.toolCache,
3112
3299
  // Explicitly propagate ctx so callers using `{ ...state, ...delta }`
3113
3300
  // do not silently capture a stale ctx reference. Mutations to
3114
- // __summarizationExhausted / __awaitingApproval / __structuredOutputParsed
3115
- // are made on this object during execution.
3301
+ // __summarizationExhausted / __awaitingApproval / __awaitingUserQuestion /
3302
+ // __structuredOutputParsed are made on this object during execution.
3116
3303
  ctx: state.ctx,
3117
3304
  agent: state.agent,
3118
- pendingApprovals
3305
+ pendingApprovals,
3306
+ pendingUserQuestions
3119
3307
  };
3120
3308
  };
3121
3309
  }
@@ -3581,6 +3769,226 @@ function resolveToolApprovalState(state, resolution) {
3581
3769
  };
3582
3770
  }
3583
3771
 
3772
+ // src/utils/userQuestions.ts
3773
+ var cloneQuestions = (questions) => Array.isArray(questions) ? questions.map((entry) => ({ ...entry })) : [];
3774
+ function validateAnswers(entry, answers, cancelled) {
3775
+ if (cancelled) {
3776
+ return answers || {};
3777
+ }
3778
+ const allowFreeText = entry.allowFreeText !== false;
3779
+ const out = {};
3780
+ const provided = answers || {};
3781
+ for (const question of entry.questions) {
3782
+ const required = question.required !== false;
3783
+ const key = question.question;
3784
+ const answer = provided[key] ?? provided[question.header || ""];
3785
+ if (!answer) {
3786
+ if (required) {
3787
+ throw new Error(`Missing answer for required question: "${key}".`);
3788
+ }
3789
+ continue;
3790
+ }
3791
+ const values = Array.isArray(answer.values) ? answer.values.filter((v) => typeof v === "string") : [];
3792
+ const freeText = typeof answer.freeText === "string" ? answer.freeText.trim() : "";
3793
+ const notes = typeof answer.notes === "string" ? answer.notes : void 0;
3794
+ if (freeText && !allowFreeText) {
3795
+ throw new Error(
3796
+ `Free-text answers are disabled for this agent (question: "${key}"). Pick one of the provided options.`
3797
+ );
3798
+ }
3799
+ if (values.length === 0 && !freeText) {
3800
+ if (required) {
3801
+ throw new Error(`Question "${key}" requires either a selected option or free-text answer.`);
3802
+ }
3803
+ continue;
3804
+ }
3805
+ if (values.length > 1 && !question.multiSelect) {
3806
+ throw new Error(`Question "${key}" is single-select but received multiple values.`);
3807
+ }
3808
+ if (Array.isArray(question.options) && question.options.length > 0 && values.length > 0) {
3809
+ const allowedValues = new Set(
3810
+ question.options.map((option) => option.value ?? option.label)
3811
+ );
3812
+ for (const value of values) {
3813
+ if (!allowedValues.has(value)) {
3814
+ if (!allowFreeText) {
3815
+ throw new Error(
3816
+ `Value "${value}" is not one of the allowed options for question "${key}".`
3817
+ );
3818
+ }
3819
+ }
3820
+ }
3821
+ }
3822
+ out[key] = { values, ...freeText ? { freeText } : {}, ...notes ? { notes } : {} };
3823
+ }
3824
+ return out;
3825
+ }
3826
+ function buildToolResponseContent(entry, answers, cancelled, notes) {
3827
+ if (cancelled) {
3828
+ return JSON.stringify({
3829
+ status: "cancelled",
3830
+ message: notes || "User dismissed the question without answering."
3831
+ });
3832
+ }
3833
+ const payload = {
3834
+ status: "answered",
3835
+ answers: entry.questions.map((question) => {
3836
+ const key = question.question;
3837
+ const ans = answers[key];
3838
+ return {
3839
+ question: key,
3840
+ header: question.header,
3841
+ values: ans?.values ?? [],
3842
+ freeText: ans?.freeText,
3843
+ notes: ans?.notes
3844
+ };
3845
+ }),
3846
+ ...notes ? { reviewerNotes: notes } : {}
3847
+ };
3848
+ return JSON.stringify(payload);
3849
+ }
3850
+ function resolveUserQuestionState(state, resolution) {
3851
+ const pending = cloneQuestions(state.pendingUserQuestions);
3852
+ const matchIndex = pending.findIndex(
3853
+ (entry2) => entry2.id === resolution.id || entry2.toolCallId === resolution.id
3854
+ );
3855
+ if (matchIndex === -1) {
3856
+ throw new Error(`Pending user question not found for id: ${resolution.id}`);
3857
+ }
3858
+ const entry = { ...pending[matchIndex] };
3859
+ if (entry.status === "answered" || entry.status === "executed" || entry.status === "cancelled") {
3860
+ throw new Error(`User question ${entry.id} already resolved (status: ${entry.status}).`);
3861
+ }
3862
+ const cancelled = resolution.cancelled === true;
3863
+ const validated = validateAnswers(entry, resolution.answers, cancelled);
3864
+ entry.status = cancelled ? "cancelled" : "answered";
3865
+ entry.answers = validated;
3866
+ entry.cancelled = cancelled || void 0;
3867
+ entry.answeredBy = resolution.answeredBy;
3868
+ entry.notes = resolution.notes;
3869
+ entry.answeredAt = (/* @__PURE__ */ new Date()).toISOString();
3870
+ pending[matchIndex] = entry;
3871
+ const ctx = { ...state.ctx || {} };
3872
+ delete ctx.__awaitingUserQuestion;
3873
+ if (ctx.__resumeStage === "tools") {
3874
+ delete ctx.__resumeStage;
3875
+ }
3876
+ ctx.__userQuestionResolved = { id: entry.id, status: entry.status };
3877
+ const toolMessage = {
3878
+ role: "tool",
3879
+ name: "ask_user_question",
3880
+ tool_call_id: entry.toolCallId,
3881
+ content: buildToolResponseContent(entry, validated, cancelled, resolution.notes)
3882
+ };
3883
+ return {
3884
+ ...state,
3885
+ pendingUserQuestions: pending,
3886
+ messages: [...state.messages || [], toolMessage],
3887
+ ctx
3888
+ };
3889
+ }
3890
+ 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.";
3891
+ 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.";
3892
+ function buildOptionSchema() {
3893
+ return zod.z.object({
3894
+ label: zod.z.string().min(1).describe("Display text the user sees"),
3895
+ value: zod.z.string().min(1).optional().describe("Value returned in the answer; defaults to label"),
3896
+ description: zod.z.string().optional(),
3897
+ preview: zod.z.string().optional().describe("Optional code / ASCII preview rendered when this option is focused")
3898
+ }).strict();
3899
+ }
3900
+ function buildQuestionSchema(allowFreeText) {
3901
+ const optionSchema = buildOptionSchema();
3902
+ const baseShape = {
3903
+ question: zod.z.string().min(1).describe("Full question shown to the user"),
3904
+ header: zod.z.string().max(40).optional().describe("Short chip / tag rendered next to the question (max ~12 chars recommended)"),
3905
+ multiSelect: zod.z.boolean().optional().describe("Allow multiple selections. Default: false"),
3906
+ placeholder: zod.z.string().optional(),
3907
+ required: zod.z.boolean().optional().describe("Default: true")
3908
+ };
3909
+ if (allowFreeText) {
3910
+ return zod.z.object({
3911
+ ...baseShape,
3912
+ options: zod.z.array(optionSchema).min(2).max(12).optional()
3913
+ }).strict();
3914
+ }
3915
+ return zod.z.object({
3916
+ ...baseShape,
3917
+ options: zod.z.array(optionSchema).min(2).max(12).describe("Required: free-text answers are disabled for this agent")
3918
+ }).strict();
3919
+ }
3920
+ function createAskUserQuestionTool(stateRef, config) {
3921
+ const allowFreeText = config.allowFreeText !== false;
3922
+ const description = config.promptOverride ?? (allowFreeText ? DEFAULT_DESCRIPTION_WITH_FREETEXT : DEFAULT_DESCRIPTION_NO_FREETEXT);
3923
+ const questionSchema = buildQuestionSchema(allowFreeText);
3924
+ const askUser = createTool({
3925
+ name: "ask_user_question",
3926
+ description,
3927
+ schema: zod.z.object({
3928
+ questions: zod.z.array(questionSchema).min(1).max(4).describe("1-4 related questions to ask in a single pause.")
3929
+ }).strict(),
3930
+ func: async ({ questions }) => {
3931
+ const ref = askUser._stateRef || (askUser._stateRef = {});
3932
+ const onEvent = ref.__onEvent;
3933
+ if (!ref.ctx) ref.ctx = {};
3934
+ const ctx = ref.ctx;
3935
+ const toolCallId = ref.__currentToolCallId || `ask_user_${nanoid.nanoid(6)}`;
3936
+ if (!ref.pendingUserQuestions) ref.pendingUserQuestions = [];
3937
+ const pendingList = ref.pendingUserQuestions;
3938
+ const entry = {
3939
+ id: nanoid.nanoid(),
3940
+ toolCallId,
3941
+ toolName: "ask_user_question",
3942
+ questions,
3943
+ status: "pending",
3944
+ requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
3945
+ allowFreeText
3946
+ };
3947
+ pendingList.push(entry);
3948
+ ctx.__awaitingUserQuestion = {
3949
+ id: entry.id,
3950
+ toolCallId: entry.toolCallId,
3951
+ requestedAt: entry.requestedAt
3952
+ };
3953
+ ctx.__resumeStage = "tools";
3954
+ onEvent?.({
3955
+ type: "user_question",
3956
+ status: "pending",
3957
+ id: entry.id,
3958
+ toolCallId: entry.toolCallId,
3959
+ questions: entry.questions,
3960
+ allowFreeText
3961
+ });
3962
+ recordTraceEvent(ctx.__traceSession, {
3963
+ type: "user_question",
3964
+ label: "Ask User",
3965
+ actor: { scope: "agent", name: "ask_user_question", role: "user_interaction" },
3966
+ status: "success",
3967
+ sections: [
3968
+ {
3969
+ kind: "metadata",
3970
+ label: "Pending Question",
3971
+ data: {
3972
+ id: entry.id,
3973
+ toolCallId: entry.toolCallId,
3974
+ questions: entry.questions,
3975
+ allowFreeText
3976
+ }
3977
+ }
3978
+ ]
3979
+ });
3980
+ return {
3981
+ __awaitingUserQuestion: true,
3982
+ id: entry.id,
3983
+ toolCallId: entry.toolCallId
3984
+ };
3985
+ }
3986
+ });
3987
+ askUser._stateRef = stateRef;
3988
+ askUser.__source = "human-in-the-loop";
3989
+ return askUser;
3990
+ }
3991
+
3584
3992
  // src/agent.ts
3585
3993
  init_utilTokens();
3586
3994
 
@@ -3792,6 +4200,21 @@ function createAgent(opts) {
3792
4200
  const resolver = createResolverNode();
3793
4201
  const agentCore = createAgentCoreNode(opts);
3794
4202
  const toolsBase = [...opts.tools ?? []];
4203
+ const askUserConfig = (() => {
4204
+ const raw = opts.humanInTheLoop?.askUser;
4205
+ if (!raw) return void 0;
4206
+ if (raw === true) return { enabled: true, allowFreeText: true };
4207
+ return {
4208
+ enabled: true,
4209
+ allowFreeText: raw.allowFreeText !== false,
4210
+ promptOverride: raw.promptOverride
4211
+ };
4212
+ })();
4213
+ const askUserOnQuestion = typeof opts.humanInTheLoop?.askUser === "object" ? opts.humanInTheLoop?.askUser?.onQuestion : void 0;
4214
+ const askUserStateRef = askUserConfig ? { pendingUserQuestions: void 0, ctx: void 0, __onEvent: void 0, __currentToolCallId: void 0 } : void 0;
4215
+ if (askUserConfig && askUserStateRef) {
4216
+ toolsBase.push(createAskUserQuestionTool(askUserStateRef, askUserConfig));
4217
+ }
3795
4218
  const soManager = opts.outputSchema ? new StructuredOutputManager(opts.outputSchema, resolveStrategy(opts.model)) : void 0;
3796
4219
  if (soManager) {
3797
4220
  const modelCapabilities = getModelCapabilities(opts.model);
@@ -3836,7 +4259,8 @@ function createAgent(opts) {
3836
4259
  useTodoList: void 0,
3837
4260
  outputSchema: opts.outputSchema,
3838
4261
  responseFormat: soManager?.getResponseFormat(),
3839
- tracing: opts.tracing
4262
+ tracing: opts.tracing,
4263
+ humanInTheLoop: askUserConfig ? { askUser: askUserConfig } : void 0
3840
4264
  };
3841
4265
  const summarizationThreshold = getActiveSummarizationThreshold(opts);
3842
4266
  async function runLoop(initial, config, emit) {
@@ -4100,7 +4524,7 @@ function createAgent(opts) {
4100
4524
  if (toolCalls.length === 0) {
4101
4525
  if (soManager && !state.ctx?.__finalizedDueToStructuredOutput && !state.ctx?.__structuredOutputForceFinalize) {
4102
4526
  if (soManager.strategy.kind === "native") {
4103
- const assistantText = extractMessageText2(lastMsg);
4527
+ const assistantText = extractMessageText(lastMsg);
4104
4528
  if (assistantText) {
4105
4529
  const parsed = soManager.parseFromContent(assistantText);
4106
4530
  if (parsed.success) {
@@ -4126,6 +4550,7 @@ function createAgent(opts) {
4126
4550
  state = { ...state, ...await toolsNode(state) };
4127
4551
  onProgress?.({ stage: "tools", message: "Tools finished" });
4128
4552
  if (state.ctx?.__awaitingApproval) break;
4553
+ if (state.ctx?.__awaitingUserQuestion) break;
4129
4554
  if (checkpointIfRequested("after_tools")) break;
4130
4555
  if (state.ctx?.__finalizedDueToStructuredOutput) break;
4131
4556
  if (reflectNode && resolvedReasoning?.reflection.enabled) {
@@ -4150,7 +4575,7 @@ function createAgent(opts) {
4150
4575
  if (soManager && !state.ctx?.__finalizedDueToStructuredOutput) {
4151
4576
  const lastForParse = state.messages[state.messages.length - 1];
4152
4577
  if (lastForParse?.role === "assistant") {
4153
- const assistantText = extractMessageText2(lastForParse);
4578
+ const assistantText = extractMessageText(lastForParse);
4154
4579
  if (assistantText) {
4155
4580
  const parsed = soManager.parseFromContent(assistantText);
4156
4581
  if (parsed.success) {
@@ -4196,7 +4621,7 @@ function createAgent(opts) {
4196
4621
  }
4197
4622
  const lastAfterLoop = state.messages[state.messages.length - 1];
4198
4623
  if (lastAfterLoop?.role === "tool" && !pausedStage) {
4199
- const isExpectedExit = state.ctx?.__awaitingApproval || state.ctx?.__cancelled || state.ctx?.__finalizedDueToStructuredOutput || state.ctx?.__finalizedDueToToolLimit || state.ctx?.__needsSummarization || state.ctx?.__limitBreached;
4624
+ const isExpectedExit = state.ctx?.__awaitingApproval || state.ctx?.__awaitingUserQuestion || state.ctx?.__cancelled || state.ctx?.__finalizedDueToStructuredOutput || state.ctx?.__finalizedDueToToolLimit || state.ctx?.__needsSummarization || state.ctx?.__limitBreached;
4200
4625
  if (!isExpectedExit) {
4201
4626
  throw new Error(
4202
4627
  "Agent loop terminated with a pending tool response but no subsequent model invocation. This usually indicates a model provider error or exhausted iteration budget."
@@ -4214,7 +4639,17 @@ function createAgent(opts) {
4214
4639
  const { setTokenCounter: setTokenCounter2 } = await Promise.resolve().then(() => (init_utilTokens(), utilTokens_exports));
4215
4640
  setTokenCounter2(userTokenCounter);
4216
4641
  }
4217
- const onEvent = config?.onEvent;
4642
+ const callerOnEvent = config?.onEvent;
4643
+ const onEvent = askUserOnQuestion ? (event) => {
4644
+ if (event && event.type === "user_question") {
4645
+ try {
4646
+ askUserOnQuestion(event);
4647
+ } catch (err) {
4648
+ console.warn("[agent-sdk] humanInTheLoop.askUser.onQuestion error:", err);
4649
+ }
4650
+ }
4651
+ callerOnEvent?.(event);
4652
+ } : callerOnEvent;
4218
4653
  const onProgress = config?.onProgress;
4219
4654
  const onStream = config?.onStream;
4220
4655
  const streamEnabled = config?.stream === true;
@@ -4258,6 +4693,7 @@ function createAgent(opts) {
4258
4693
  metadata: input.metadata,
4259
4694
  ctx,
4260
4695
  pendingApprovals: input.pendingApprovals || [],
4696
+ pendingUserQuestions: input.pendingUserQuestions || [],
4261
4697
  agent: input.agent || runtimeWithInvokeLimits,
4262
4698
  usage: input.usage || { perRequest: [], totals: {} }
4263
4699
  };
@@ -4292,7 +4728,7 @@ function createAgent(opts) {
4292
4728
  const isTransientSummarizationExit = !!res.ctx?.__needsSummarization;
4293
4729
  const finalAssistantMsg = isTransientSummarizationExit ? void 0 : getLastAssistantMessage(res.messages);
4294
4730
  const finalIsSyntheticSummary = !isTransientSummarizationExit && finalAssistantMsg && isSyntheticSummaryMessage(finalAssistantMsg);
4295
- const content = isTransientSummarizationExit || finalIsSyntheticSummary ? "" : extractMessageText2(finalAssistantMsg);
4731
+ const content = isTransientSummarizationExit || finalIsSyntheticSummary ? "" : extractMessageText(finalAssistantMsg);
4296
4732
  let parsed = void 0;
4297
4733
  let outputError = void 0;
4298
4734
  if (soManager) {
@@ -4334,11 +4770,13 @@ function createAgent(opts) {
4334
4770
  return invokeAgent(restoredState, config);
4335
4771
  };
4336
4772
  const resolveToolApproval = (state, resolution) => resolveToolApprovalState(state, resolution);
4773
+ const resolveUserQuestion = (state, resolution) => resolveUserQuestionState(state, resolution);
4337
4774
  const instance = {
4338
4775
  invoke: invokeAgent,
4339
4776
  snapshot: snapshotState,
4340
4777
  resume: resumeAgent,
4341
4778
  resolveToolApproval,
4779
+ resolveUserQuestion,
4342
4780
  asTool: ({ toolName, description, inputDescription }) => {
4343
4781
  const schema = zod.z.object({ input: zod.z.string().describe(inputDescription || "Input for delegated agent") });
4344
4782
  const delegatedTool = createTool({
@@ -4598,7 +5036,7 @@ function renderMemoryBlock(facts) {
4598
5036
  function clampToBudget(messages, maxContextTokens) {
4599
5037
  let working = [...messages];
4600
5038
  while (working.length > 2) {
4601
- const tokenCount = countApproxTokens(working.map(extractMessageText2).join("\n"));
5039
+ const tokenCount = countApproxTokens(working.map(extractMessageText).join("\n"));
4602
5040
  if (tokenCount <= maxContextTokens) return working;
4603
5041
  const firstNonSystem = working.findIndex((message, index) => !(index === 0 && message.role === "system"));
4604
5042
  if (firstNonSystem < 0) break;
@@ -5655,7 +6093,20 @@ function createSmartAgent(opts) {
5655
6093
  const userTools = opts.tools ?? [];
5656
6094
  const factoryStateRef = { toolHistory: void 0, toolHistoryArchived: void 0, todoList: void 0, planVersion: 0, adherenceScore: 0 };
5657
6095
  const factoryContextTools = createContextTools(factoryStateRef, { planningEnabled, includeGetToolResponse: false });
6096
+ const askUserConfig = (() => {
6097
+ const raw = opts.humanInTheLoop?.askUser;
6098
+ if (!raw) return void 0;
6099
+ if (raw === true) return { enabled: true, allowFreeText: true };
6100
+ return {
6101
+ enabled: true,
6102
+ allowFreeText: raw.allowFreeText !== false,
6103
+ promptOverride: raw.promptOverride
6104
+ };
6105
+ })();
5658
6106
  const factoryToolsWithoutRecovery = [...userTools, ...factoryContextTools];
6107
+ if (askUserConfig) {
6108
+ factoryToolsWithoutRecovery.push(createAskUserQuestionTool(factoryStateRef, askUserConfig));
6109
+ }
5659
6110
  const soManagerFactory = opts.outputSchema ? new StructuredOutputManager(opts.outputSchema, resolveStrategy(opts.model)) : void 0;
5660
6111
  const modelCapabilities = opts.outputSchema ? getModelCapabilities(opts.model) : void 0;
5661
6112
  const responseFinalizeTool = soManagerFactory && modelCapabilities?.structuredOutput !== "native" ? soManagerFactory.getResponseTool() : void 0;
@@ -5665,6 +6116,7 @@ function createSmartAgent(opts) {
5665
6116
  const recoveryTool = createGetToolResponseTool(ref);
5666
6117
  const base2 = [...userTools, ...ctxTools];
5667
6118
  if (responseFinalizeTool) base2.push(responseFinalizeTool);
6119
+ if (askUserConfig) base2.push(createAskUserQuestionTool(ref, askUserConfig));
5668
6120
  const without = base2;
5669
6121
  const withRec = [...without, recoveryTool];
5670
6122
  return { stateRef: ref, toolsWithoutRecovery: without, toolsWithRecovery: withRec };
@@ -5687,7 +6139,7 @@ function createSmartAgent(opts) {
5687
6139
  ].join("\n");
5688
6140
  function latestUserPrompt(messages) {
5689
6141
  const latest = [...messages].reverse().find((message) => message.role === "user");
5690
- return latest ? extractMessageText2(latest) : "";
6142
+ return latest ? extractMessageText(latest) : "";
5691
6143
  }
5692
6144
  async function syncMemory(state) {
5693
6145
  const query = latestUserPrompt(state.messages || []);
@@ -5849,6 +6301,7 @@ function createSmartAgent(opts) {
5849
6301
  snapshot: base.snapshot,
5850
6302
  resume: base.resume,
5851
6303
  resolveToolApproval: base.resolveToolApproval,
6304
+ resolveUserQuestion: base.resolveUserQuestion,
5852
6305
  asTool: base.asTool,
5853
6306
  asHandoff: base.asHandoff,
5854
6307
  __runtime: base.__runtime
@@ -8174,6 +8627,7 @@ exports.countApproxTokens = countApproxTokens;
8174
8627
  exports.countMessagesTokens = countMessagesTokens;
8175
8628
  exports.createAgent = createAgent;
8176
8629
  exports.createAgentNode = createAgentNode;
8630
+ exports.createAskUserQuestionTool = createAskUserQuestionTool;
8177
8631
  exports.createCodeGuardrail = createCodeGuardrail;
8178
8632
  exports.createContextSummarizeNode = createContextSummarizeNode;
8179
8633
  exports.createContextTools = createContextTools;
@@ -8215,6 +8669,7 @@ exports.regexRule = regexRule;
8215
8669
  exports.resolveMemoryStore = resolveMemoryStore;
8216
8670
  exports.resolveStrategy = resolveStrategy;
8217
8671
  exports.resolveToolApprovalState = resolveToolApprovalState;
8672
+ exports.resolveUserQuestionState = resolveUserQuestionState;
8218
8673
  exports.restoreSnapshot = restoreSnapshot;
8219
8674
  exports.runSmartAgentEvalHarness = runSmartAgentEvalHarness;
8220
8675
  exports.setTokenCounter = setTokenCounter;