llmist 5.0.0 → 6.0.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/cli.cjs CHANGED
@@ -3870,6 +3870,8 @@ var init_stream_processor = __esm({
3870
3870
  completedResults = /* @__PURE__ */ new Map();
3871
3871
  /** Invocation IDs of gadgets that have failed (error or skipped due to dependency) */
3872
3872
  failedInvocations = /* @__PURE__ */ new Set();
3873
+ /** Promises for independent gadgets currently executing (fire-and-forget) */
3874
+ inFlightExecutions = /* @__PURE__ */ new Map();
3873
3875
  constructor(options) {
3874
3876
  this.iteration = options.iteration;
3875
3877
  this.registry = options.registry;
@@ -3978,6 +3980,16 @@ var init_stream_processor = __esm({
3978
3980
  }
3979
3981
  }
3980
3982
  }
3983
+ const inFlightResults = await this.collectInFlightResults();
3984
+ for (const evt of inFlightResults) {
3985
+ yield evt;
3986
+ if (evt.type === "gadget_result") {
3987
+ didExecuteGadgets = true;
3988
+ if (evt.result.breaksLoop) {
3989
+ shouldBreakLoop = true;
3990
+ }
3991
+ }
3992
+ }
3981
3993
  for await (const evt of this.processPendingGadgetsGenerator()) {
3982
3994
  yield evt;
3983
3995
  if (evt.type === "gadget_result") {
@@ -4163,12 +4175,24 @@ var init_stream_processor = __esm({
4163
4175
  this.gadgetsAwaitingDependencies.set(call.invocationId, call);
4164
4176
  return;
4165
4177
  }
4178
+ for await (const evt of this.executeGadgetGenerator(call)) {
4179
+ yield evt;
4180
+ }
4181
+ for await (const evt of this.processPendingGadgetsGenerator()) {
4182
+ yield evt;
4183
+ }
4184
+ return;
4166
4185
  }
4167
- for await (const evt of this.executeGadgetGenerator(call)) {
4168
- yield evt;
4169
- }
4170
- for await (const evt of this.processPendingGadgetsGenerator()) {
4171
- yield evt;
4186
+ if (this.stopOnGadgetError) {
4187
+ for await (const evt of this.executeGadgetGenerator(call)) {
4188
+ yield evt;
4189
+ }
4190
+ for await (const evt of this.processPendingGadgetsGenerator()) {
4191
+ yield evt;
4192
+ }
4193
+ } else {
4194
+ const executionPromise = this.executeGadgetAndCollect(call);
4195
+ this.inFlightExecutions.set(call.invocationId, executionPromise);
4172
4196
  }
4173
4197
  }
4174
4198
  /**
@@ -4480,6 +4504,36 @@ var init_stream_processor = __esm({
4480
4504
  }
4481
4505
  }
4482
4506
  }
4507
+ /**
4508
+ * Execute a gadget and collect all events into an array (non-blocking).
4509
+ * Used for fire-and-forget parallel execution of independent gadgets.
4510
+ */
4511
+ async executeGadgetAndCollect(call) {
4512
+ const events = [];
4513
+ for await (const evt of this.executeGadgetGenerator(call)) {
4514
+ events.push(evt);
4515
+ }
4516
+ return events;
4517
+ }
4518
+ /**
4519
+ * Collect results from all fire-and-forget (in-flight) gadget executions.
4520
+ * Called at stream end to await parallel independent gadgets.
4521
+ * Clears the inFlightExecutions map after collection.
4522
+ * @returns Array of all events from completed gadgets
4523
+ */
4524
+ async collectInFlightResults() {
4525
+ if (this.inFlightExecutions.size === 0) {
4526
+ return [];
4527
+ }
4528
+ this.logger.debug("Collecting in-flight gadget results", {
4529
+ count: this.inFlightExecutions.size,
4530
+ invocationIds: Array.from(this.inFlightExecutions.keys())
4531
+ });
4532
+ const promises = Array.from(this.inFlightExecutions.values());
4533
+ const results = await Promise.all(promises);
4534
+ this.inFlightExecutions.clear();
4535
+ return results.flat();
4536
+ }
4483
4537
  /**
4484
4538
  * Handle a gadget that cannot execute because a dependency failed.
4485
4539
  * Calls the onDependencySkipped controller to allow customization.
@@ -9479,13 +9533,24 @@ ${endPrefix}`
9479
9533
  observers: {
9480
9534
  ...hooks?.observers,
9481
9535
  onLLMCallStart: async (context) => {
9536
+ let inputTokens;
9537
+ try {
9538
+ if (this.client) {
9539
+ inputTokens = await this.client.countTokens(
9540
+ context.options.model,
9541
+ context.options.messages
9542
+ );
9543
+ }
9544
+ } catch {
9545
+ }
9482
9546
  onSubagentEvent({
9483
9547
  type: "llm_call_start",
9484
9548
  gadgetInvocationId: invocationId,
9485
9549
  depth,
9486
9550
  event: {
9487
9551
  iteration: context.iteration,
9488
- model: context.options.model
9552
+ model: context.options.model,
9553
+ inputTokens
9489
9554
  }
9490
9555
  });
9491
9556
  if (existingOnLLMCallStart) {
@@ -9958,7 +10023,7 @@ var import_commander2 = require("commander");
9958
10023
  // package.json
9959
10024
  var package_default = {
9960
10025
  name: "llmist",
9961
- version: "5.0.0",
10026
+ version: "5.1.0",
9962
10027
  description: "TypeScript LLM client with streaming tool execution. Tools fire mid-stream. Built-in function calling works with any model\u2014no structured outputs or native tool support required.",
9963
10028
  type: "module",
9964
10029
  main: "dist/index.cjs",
@@ -12849,7 +12914,8 @@ function formatCost(cost) {
12849
12914
  }
12850
12915
  function formatLLMCallLine(info) {
12851
12916
  const parts = [];
12852
- parts.push(`${import_chalk3.default.cyan(`#${info.iteration}`)} ${import_chalk3.default.magenta(info.model)}`);
12917
+ const callNumber = info.parentCallNumber ? `#${info.parentCallNumber}.${info.iteration}` : `#${info.iteration}`;
12918
+ parts.push(`${import_chalk3.default.cyan(callNumber)} ${import_chalk3.default.magenta(info.model)}`);
12853
12919
  if (info.contextPercent !== void 0 && info.contextPercent !== null) {
12854
12920
  const formatted = `${Math.round(info.contextPercent)}%`;
12855
12921
  if (info.contextPercent >= 80) {
@@ -12954,7 +13020,7 @@ function getRawValue(value) {
12954
13020
  function truncateValue(str, maxLen) {
12955
13021
  if (maxLen <= 0) return "";
12956
13022
  if (str.length <= maxLen) return str;
12957
- return `${str.slice(0, maxLen)}\u2026`;
13023
+ return `${str.slice(0, maxLen - 1)}\u2026`;
12958
13024
  }
12959
13025
  function formatParametersInline(params, maxWidth) {
12960
13026
  if (!params || Object.keys(params).length === 0) {
@@ -12982,6 +13048,11 @@ function formatParametersInline(params, maxWidth) {
12982
13048
  const proportion = v.length / totalRawLength;
12983
13049
  return Math.max(minPerValue, Math.floor(proportion * availableForValues));
12984
13050
  });
13051
+ const totalLimits = limits.reduce((sum, l) => sum + l, 0);
13052
+ if (totalLimits > availableForValues) {
13053
+ const scale = availableForValues / totalLimits;
13054
+ limits = limits.map((l) => Math.max(1, Math.floor(l * scale)));
13055
+ }
12985
13056
  }
12986
13057
  }
12987
13058
  } else {
@@ -12997,8 +13068,8 @@ function formatGadgetLine(info, maxWidth) {
12997
13068
  const gadgetLabel = import_chalk3.default.magenta.bold(info.name);
12998
13069
  const timeStr = `${info.elapsedSeconds.toFixed(1)}s`;
12999
13070
  const timeLabel = import_chalk3.default.dim(timeStr);
13000
- const fixedLength = 2 + info.name.length + 2 + 1 + timeStr.length;
13001
- const availableForParams = Math.max(40, terminalWidth - fixedLength - 2);
13071
+ const fixedLength = 3 + info.name.length + 2 + 1 + timeStr.length;
13072
+ const availableForParams = Math.max(40, terminalWidth - fixedLength - 3);
13002
13073
  const paramsStr = formatParametersInline(info.parameters, availableForParams);
13003
13074
  const paramsLabel = paramsStr ? `${import_chalk3.default.dim("(")}${paramsStr}${import_chalk3.default.dim(")")}` : "";
13004
13075
  if (info.error) {
@@ -13008,17 +13079,21 @@ function formatGadgetLine(info, maxWidth) {
13008
13079
  if (!info.isComplete) {
13009
13080
  return `${import_chalk3.default.blue("\u23F5")} ${gadgetLabel}${paramsLabel} ${timeLabel}`;
13010
13081
  }
13011
- let outputStr;
13082
+ let outputLabel;
13012
13083
  if (info.tokenCount !== void 0 && info.tokenCount > 0) {
13013
- outputStr = `${formatTokens(info.tokenCount)} tokens`;
13084
+ outputLabel = import_chalk3.default.dim("\u2193") + import_chalk3.default.green(` ${formatTokens(info.tokenCount)} `);
13014
13085
  } else if (info.outputBytes !== void 0 && info.outputBytes > 0) {
13015
- outputStr = formatBytes(info.outputBytes);
13086
+ outputLabel = import_chalk3.default.green(formatBytes(info.outputBytes)) + " ";
13016
13087
  } else {
13017
- outputStr = "";
13088
+ outputLabel = "";
13018
13089
  }
13019
13090
  const icon = info.breaksLoop ? import_chalk3.default.yellow("\u23F9") : import_chalk3.default.green("\u2713");
13020
- const outputLabel = outputStr ? ` ${import_chalk3.default.dim("\u2192")} ${import_chalk3.default.green(outputStr)}` : "";
13021
- return `${icon} ${gadgetLabel}${paramsLabel}${outputLabel} ${timeLabel}`;
13091
+ const nameRef = import_chalk3.default.magenta(info.name);
13092
+ const line1 = `${icon} ${gadgetLabel}${paramsLabel}`;
13093
+ const line2Prefix = ` ${import_chalk3.default.dim("\u2192")} ${nameRef} ${outputLabel}`;
13094
+ const line2 = `${line2Prefix}${timeLabel}`;
13095
+ return `${line1}
13096
+ ${line2}`;
13022
13097
  }
13023
13098
  function formatBytes(bytes) {
13024
13099
  if (bytes < 1024) {
@@ -13029,6 +13104,11 @@ function formatBytes(bytes) {
13029
13104
  }
13030
13105
  return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
13031
13106
  }
13107
+ function truncateOutputPreview(output, maxWidth) {
13108
+ const normalized = output.replace(/\s+/g, " ").trim();
13109
+ if (normalized.length <= maxWidth) return normalized;
13110
+ return normalized.slice(0, maxWidth - 1) + "\u2026";
13111
+ }
13032
13112
  function getMediaIcon(kind) {
13033
13113
  switch (kind) {
13034
13114
  case "image":
@@ -13056,37 +13136,99 @@ function formatGadgetSummary2(result) {
13056
13136
  const gadgetLabel = import_chalk3.default.magenta.bold(result.gadgetName);
13057
13137
  const timeStr = result.executionTimeMs >= 1e3 ? `${(result.executionTimeMs / 1e3).toFixed(1)}s` : `${Math.round(result.executionTimeMs)}ms`;
13058
13138
  const timeLabel = import_chalk3.default.dim(timeStr);
13059
- let outputStr;
13060
- if (result.tokenCount !== void 0 && result.tokenCount > 0) {
13061
- outputStr = `${formatTokens(result.tokenCount)} tokens`;
13062
- } else if (result.result) {
13139
+ const fixedLength = 3 + result.gadgetName.length + 2;
13140
+ const availableForParams = Math.max(40, terminalWidth - fixedLength - 3);
13141
+ const paramsStr = formatParametersInline(result.parameters, availableForParams);
13142
+ const paramsLabel = paramsStr ? `${import_chalk3.default.dim("(")}${paramsStr}${import_chalk3.default.dim(")")}` : "";
13143
+ const icon = result.breaksLoop ? import_chalk3.default.yellow("\u23F9") : result.error ? import_chalk3.default.red("\u2717") : import_chalk3.default.green("\u2713");
13144
+ const line1 = `${icon} ${gadgetLabel}${paramsLabel}`;
13145
+ const nameRef = import_chalk3.default.magenta(result.gadgetName);
13146
+ const hasSubagentMetrics = result.subagentMetrics && result.subagentMetrics.callCount > 0;
13147
+ let outputLabel;
13148
+ let outputStrRaw;
13149
+ if (!hasSubagentMetrics && result.tokenCount !== void 0 && result.tokenCount > 0) {
13150
+ const tokenStr = formatTokens(result.tokenCount);
13151
+ outputLabel = import_chalk3.default.dim("\u2193") + import_chalk3.default.green(` ${tokenStr} `);
13152
+ outputStrRaw = `\u2193 ${tokenStr} `;
13153
+ } else if (!hasSubagentMetrics && result.result) {
13063
13154
  const outputBytes = Buffer.byteLength(result.result, "utf-8");
13064
- outputStr = outputBytes > 0 ? formatBytes(outputBytes) : "no output";
13155
+ if (outputBytes > 0) {
13156
+ const bytesStr = formatBytes(outputBytes);
13157
+ outputLabel = import_chalk3.default.green(bytesStr) + " ";
13158
+ outputStrRaw = bytesStr + " ";
13159
+ } else {
13160
+ outputLabel = "";
13161
+ outputStrRaw = "";
13162
+ }
13065
13163
  } else {
13066
- outputStr = "no output";
13164
+ outputLabel = "";
13165
+ outputStrRaw = "";
13067
13166
  }
13068
- const fixedLength = 2 + result.gadgetName.length + 2 + 3 + outputStr.length + 1 + timeStr.length;
13069
- const availableForParams = Math.max(40, terminalWidth - fixedLength - 2);
13070
- const paramsStr = formatParametersInline(result.parameters, availableForParams);
13071
- const paramsLabel = paramsStr ? `${import_chalk3.default.dim("(")}${paramsStr}${import_chalk3.default.dim(")")}` : "";
13072
13167
  if (result.error) {
13073
13168
  const errorMsg = result.error.length > 50 ? `${result.error.slice(0, 50)}\u2026` : result.error;
13074
- return `${import_chalk3.default.red("\u2717")} ${gadgetLabel}${paramsLabel} ${import_chalk3.default.red("error:")} ${errorMsg} ${timeLabel}`;
13169
+ const line22 = ` ${import_chalk3.default.dim("\u2192")} ${nameRef} ${import_chalk3.default.red("error:")} ${errorMsg} ${timeLabel}`;
13170
+ return `${line1}
13171
+ ${line22}`;
13172
+ }
13173
+ const previewWidth = Math.floor(terminalWidth * 0.6);
13174
+ const prefixLength = 4 + result.gadgetName.length + 1 + outputStrRaw.length + 1 + timeStr.length + 2;
13175
+ const availablePreview = Math.max(20, previewWidth - prefixLength);
13176
+ let customPreview;
13177
+ if (result.gadgetName === "TodoUpsert" && result.parameters?.content) {
13178
+ const statusEmoji = result.parameters.status === "done" ? "\u2705" : result.parameters.status === "in_progress" ? "\u{1F504}" : "\u2B1C";
13179
+ const content = String(result.parameters.content);
13180
+ customPreview = `${statusEmoji} ${truncateOutputPreview(content, availablePreview - 3)}`;
13181
+ }
13182
+ if (result.gadgetName === "GoogleSearch" && result.parameters?.query) {
13183
+ const query = String(result.parameters.query);
13184
+ const countMatch = result.result?.match(/\((\d+)\s+of\s+[\d,]+\s+results?\)/i) || // "(10 of 36400000 results)"
13185
+ result.result?.match(/(\d+)\s+results?\s+found/i) || // "10 results found"
13186
+ result.result?.match(/found\s+(\d+)\s+results?/i);
13187
+ const count = countMatch?.[1] ?? (result.parameters.maxResults ? String(result.parameters.maxResults) : null);
13188
+ const countStr = count ? ` \u2192 ${count} results` : "";
13189
+ const queryPreview = truncateOutputPreview(query, availablePreview - 5 - countStr.length);
13190
+ customPreview = `\u{1F50D} "${queryPreview}"${countStr}`;
13191
+ }
13192
+ let subagentMetricsStr = "";
13193
+ if (result.subagentMetrics && result.subagentMetrics.callCount > 0) {
13194
+ const parts = [];
13195
+ const m = result.subagentMetrics;
13196
+ if (m.inputTokens > 0) {
13197
+ parts.push(import_chalk3.default.dim("\u2191") + import_chalk3.default.yellow(` ${formatTokens(m.inputTokens)}`));
13198
+ }
13199
+ if (m.cachedInputTokens > 0) {
13200
+ parts.push(import_chalk3.default.dim("\u27F3") + import_chalk3.default.blue(` ${formatTokens(m.cachedInputTokens)}`));
13201
+ }
13202
+ if (m.outputTokens > 0) {
13203
+ parts.push(import_chalk3.default.dim("\u2193") + import_chalk3.default.green(` ${formatTokens(m.outputTokens)}`));
13204
+ }
13205
+ if (m.cost > 0) {
13206
+ parts.push(import_chalk3.default.cyan(`$${formatCost(m.cost)}`));
13207
+ }
13208
+ if (parts.length > 0) {
13209
+ subagentMetricsStr = parts.join(import_chalk3.default.dim(" | ")) + import_chalk3.default.dim(" | ");
13210
+ }
13211
+ }
13212
+ let line2;
13213
+ const previewContent = customPreview ?? (result.result?.trim() ? truncateOutputPreview(result.result, availablePreview) : null);
13214
+ if (previewContent) {
13215
+ line2 = ` ${import_chalk3.default.dim("\u2192")} ${nameRef} ${outputLabel}${subagentMetricsStr}${timeLabel}${import_chalk3.default.dim(":")} ${import_chalk3.default.dim(previewContent)}`;
13216
+ } else {
13217
+ line2 = ` ${import_chalk3.default.dim("\u2192")} ${nameRef} ${outputLabel}${subagentMetricsStr}${timeLabel}`;
13075
13218
  }
13076
- const outputLabel = outputStr === "no output" ? import_chalk3.default.dim(outputStr) : import_chalk3.default.green(outputStr);
13077
- const icon = result.breaksLoop ? import_chalk3.default.yellow("\u23F9") : import_chalk3.default.green("\u2713");
13078
- let summaryLine = `${icon} ${gadgetLabel}${paramsLabel} ${import_chalk3.default.dim("\u2192")} ${outputLabel} ${timeLabel}`;
13219
+ let output = `${line1}
13220
+ ${line2}`;
13079
13221
  if (result.media && result.media.length > 0) {
13080
13222
  const mediaLines = result.media.map(formatMediaLine);
13081
- summaryLine += "\n" + mediaLines.join("\n");
13223
+ output += "\n" + mediaLines.join("\n");
13082
13224
  }
13083
13225
  if (result.gadgetName === "TellUser" && result.parameters?.message) {
13084
13226
  const message = String(result.parameters.message);
13085
13227
  const rendered = renderMarkdownWithSeparators(message);
13086
- return `${summaryLine}
13228
+ return `${output}
13087
13229
  ${rendered}`;
13088
13230
  }
13089
- return summaryLine;
13231
+ return output;
13090
13232
  }
13091
13233
 
13092
13234
  // src/cli/utils.ts
@@ -13279,18 +13421,60 @@ var StreamProgress = class {
13279
13421
  hasInFlightGadgets() {
13280
13422
  return this.inFlightGadgets.size > 0;
13281
13423
  }
13424
+ /**
13425
+ * Mark a gadget as completed (keeps it visible with ✓ indicator).
13426
+ * Records completion time to freeze the elapsed timer.
13427
+ * The gadget and its nested operations remain visible until clearCompletedGadgets() is called.
13428
+ */
13429
+ completeGadget(invocationId) {
13430
+ const gadget = this.inFlightGadgets.get(invocationId);
13431
+ if (gadget) {
13432
+ gadget.completed = true;
13433
+ gadget.completedTime = Date.now();
13434
+ if (this.isRunning && this.isTTY) {
13435
+ this.render();
13436
+ }
13437
+ }
13438
+ }
13439
+ /**
13440
+ * Clear all completed gadgets from the display.
13441
+ * Called when new text output arrives to clean up the finished gadget section.
13442
+ */
13443
+ clearCompletedGadgets() {
13444
+ for (const [id, gadget] of this.inFlightGadgets) {
13445
+ if (gadget.completed) {
13446
+ this.inFlightGadgets.delete(id);
13447
+ for (const [nestedId, nested] of this.nestedAgents) {
13448
+ if (nested.parentInvocationId === id) {
13449
+ this.nestedAgents.delete(nestedId);
13450
+ }
13451
+ }
13452
+ for (const [nestedId, nested] of this.nestedGadgets) {
13453
+ if (nested.parentInvocationId === id) {
13454
+ this.nestedGadgets.delete(nestedId);
13455
+ }
13456
+ }
13457
+ }
13458
+ }
13459
+ if (this.isRunning && this.isTTY) {
13460
+ this.render();
13461
+ }
13462
+ }
13282
13463
  /**
13283
13464
  * Add a nested agent LLM call (called when nested llm_call_start event received).
13284
13465
  * Used to display hierarchical progress for subagent gadgets.
13466
+ * @param parentCallNumber - Top-level call number for hierarchical display (e.g., #1.2)
13285
13467
  */
13286
- addNestedAgent(id, parentInvocationId, depth, model, iteration, inputTokens) {
13468
+ addNestedAgent(id, parentInvocationId, depth, model, iteration, info, parentCallNumber) {
13287
13469
  this.nestedAgents.set(id, {
13288
13470
  parentInvocationId,
13289
13471
  depth,
13290
13472
  model,
13291
13473
  iteration,
13474
+ parentCallNumber,
13292
13475
  startTime: Date.now(),
13293
- inputTokens
13476
+ inputTokens: info?.inputTokens,
13477
+ cachedInputTokens: info?.cachedInputTokens
13294
13478
  });
13295
13479
  if (this.isRunning && this.isTTY) {
13296
13480
  this.render();
@@ -13304,22 +13488,23 @@ var StreamProgress = class {
13304
13488
  updateNestedAgent(id, info) {
13305
13489
  const agent = this.nestedAgents.get(id);
13306
13490
  if (agent) {
13307
- agent.inputTokens = info.inputTokens;
13308
- agent.outputTokens = info.outputTokens;
13309
- agent.cachedInputTokens = info.cachedInputTokens;
13310
- agent.cacheCreationInputTokens = info.cacheCreationInputTokens;
13311
- agent.finishReason = info.finishReason;
13491
+ if (info.inputTokens !== void 0) agent.inputTokens = info.inputTokens;
13492
+ if (info.outputTokens !== void 0) agent.outputTokens = info.outputTokens;
13493
+ if (info.cachedInputTokens !== void 0) agent.cachedInputTokens = info.cachedInputTokens;
13494
+ if (info.cacheCreationInputTokens !== void 0)
13495
+ agent.cacheCreationInputTokens = info.cacheCreationInputTokens;
13496
+ if (info.finishReason !== void 0) agent.finishReason = info.finishReason;
13312
13497
  if (info.cost !== void 0) {
13313
13498
  agent.cost = info.cost;
13314
- } else if (this.modelRegistry && agent.model && info.outputTokens) {
13499
+ } else if (this.modelRegistry && agent.model && agent.outputTokens) {
13315
13500
  try {
13316
13501
  const modelName = agent.model.includes(":") ? agent.model.split(":")[1] : agent.model;
13317
13502
  const costResult = this.modelRegistry.estimateCost(
13318
13503
  modelName,
13319
- info.inputTokens ?? 0,
13320
- info.outputTokens,
13321
- info.cachedInputTokens,
13322
- info.cacheCreationInputTokens
13504
+ agent.inputTokens ?? 0,
13505
+ agent.outputTokens,
13506
+ agent.cachedInputTokens,
13507
+ agent.cacheCreationInputTokens
13323
13508
  );
13324
13509
  agent.cost = costResult?.totalCost;
13325
13510
  } catch {
@@ -13341,6 +13526,27 @@ var StreamProgress = class {
13341
13526
  this.render();
13342
13527
  }
13343
13528
  }
13529
+ /**
13530
+ * Get aggregated metrics from all nested agents for a parent gadget.
13531
+ * Used to show total token counts and cost for subagent gadgets like BrowseWeb.
13532
+ */
13533
+ getAggregatedSubagentMetrics(parentInvocationId) {
13534
+ let inputTokens = 0;
13535
+ let outputTokens = 0;
13536
+ let cachedInputTokens = 0;
13537
+ let cost = 0;
13538
+ let callCount = 0;
13539
+ for (const [, nested] of this.nestedAgents) {
13540
+ if (nested.parentInvocationId === parentInvocationId) {
13541
+ inputTokens += nested.inputTokens ?? 0;
13542
+ outputTokens += nested.outputTokens ?? 0;
13543
+ cachedInputTokens += nested.cachedInputTokens ?? 0;
13544
+ cost += nested.cost ?? 0;
13545
+ callCount++;
13546
+ }
13547
+ }
13548
+ return { inputTokens, outputTokens, cachedInputTokens, cost, callCount };
13549
+ }
13344
13550
  /**
13345
13551
  * Add a nested gadget call (called when nested gadget_call event received).
13346
13552
  */
@@ -13508,20 +13714,23 @@ var StreamProgress = class {
13508
13714
  this.clearRenderedLines();
13509
13715
  const spinner = SPINNER_FRAMES[this.frameIndex++ % SPINNER_FRAMES.length];
13510
13716
  const lines = [];
13511
- if (this.mode === "streaming") {
13512
- lines.push(this.formatStreamingLine(spinner));
13513
- } else {
13514
- lines.push(this.formatCumulativeLine(spinner));
13515
- }
13717
+ const activeNestedStreams = [];
13516
13718
  if (this.isTTY) {
13517
13719
  for (const [gadgetId, gadget] of this.inFlightGadgets) {
13518
- const elapsedSeconds = (Date.now() - gadget.startTime) / 1e3;
13519
- const gadgetLine = ` ${formatGadgetLine({
13520
- name: gadget.name,
13521
- parameters: gadget.params,
13522
- elapsedSeconds,
13523
- isComplete: false
13524
- })}`;
13720
+ const endTime = gadget.completedTime ?? Date.now();
13721
+ const elapsedSeconds = (endTime - gadget.startTime) / 1e3;
13722
+ const termWidth = process.stdout.columns ?? 80;
13723
+ const gadgetIndent = " ";
13724
+ const line = formatGadgetLine(
13725
+ {
13726
+ name: gadget.name,
13727
+ parameters: gadget.params,
13728
+ elapsedSeconds,
13729
+ isComplete: gadget.completed ?? false
13730
+ },
13731
+ termWidth - gadgetIndent.length
13732
+ );
13733
+ const gadgetLine = line.split("\n").map((l) => gadgetIndent + l).join("\n");
13525
13734
  lines.push(gadgetLine);
13526
13735
  const nestedOps = [];
13527
13736
  for (const [_agentId, nested] of this.nestedAgents) {
@@ -13531,6 +13740,7 @@ var StreamProgress = class {
13531
13740
  startTime: nested.startTime,
13532
13741
  depth: nested.depth,
13533
13742
  iteration: nested.iteration,
13743
+ parentCallNumber: nested.parentCallNumber,
13534
13744
  model: nested.model,
13535
13745
  inputTokens: nested.inputTokens,
13536
13746
  cachedInputTokens: nested.cachedInputTokens,
@@ -13540,6 +13750,19 @@ var StreamProgress = class {
13540
13750
  completed: nested.completed,
13541
13751
  completedTime: nested.completedTime
13542
13752
  });
13753
+ if (!nested.completed) {
13754
+ activeNestedStreams.push({
13755
+ depth: nested.depth,
13756
+ iteration: nested.iteration,
13757
+ parentCallNumber: nested.parentCallNumber,
13758
+ model: nested.model,
13759
+ inputTokens: nested.inputTokens,
13760
+ cachedInputTokens: nested.cachedInputTokens,
13761
+ outputTokens: nested.outputTokens,
13762
+ cost: nested.cost,
13763
+ startTime: nested.startTime
13764
+ });
13765
+ }
13543
13766
  }
13544
13767
  }
13545
13768
  for (const [_nestedId, nestedGadget] of this.nestedGadgets) {
@@ -13557,12 +13780,16 @@ var StreamProgress = class {
13557
13780
  }
13558
13781
  nestedOps.sort((a, b) => a.startTime - b.startTime);
13559
13782
  for (const op of nestedOps) {
13560
- const indent = " ".repeat(op.depth + 1);
13561
- const endTime = op.completedTime ?? Date.now();
13562
- const elapsedSeconds2 = (endTime - op.startTime) / 1e3;
13783
+ if (op.type === "agent" && !op.completed) {
13784
+ continue;
13785
+ }
13786
+ const indent = " ".repeat(op.depth + 2);
13787
+ const endTime2 = op.completedTime ?? Date.now();
13788
+ const elapsedSeconds2 = (endTime2 - op.startTime) / 1e3;
13563
13789
  if (op.type === "agent") {
13564
- const line = formatLLMCallLine({
13790
+ const line2 = formatLLMCallLine({
13565
13791
  iteration: op.iteration ?? 0,
13792
+ parentCallNumber: op.parentCallNumber,
13566
13793
  model: op.model ?? "",
13567
13794
  inputTokens: op.inputTokens,
13568
13795
  cachedInputTokens: op.cachedInputTokens,
@@ -13573,21 +13800,49 @@ var StreamProgress = class {
13573
13800
  isStreaming: !op.completed,
13574
13801
  spinner
13575
13802
  });
13576
- lines.push(`${indent}${line}`);
13803
+ lines.push(`${indent}${line2}`);
13577
13804
  } else {
13578
- const line = formatGadgetLine({
13579
- name: op.name ?? "",
13580
- parameters: op.parameters,
13581
- elapsedSeconds: elapsedSeconds2,
13582
- isComplete: op.completed ?? false
13583
- });
13584
- lines.push(`${indent}${line}`);
13585
- }
13586
- }
13587
- }
13805
+ const termWidth2 = process.stdout.columns ?? 80;
13806
+ const line2 = formatGadgetLine(
13807
+ {
13808
+ name: op.name ?? "",
13809
+ parameters: op.parameters,
13810
+ elapsedSeconds: elapsedSeconds2,
13811
+ isComplete: op.completed ?? false
13812
+ },
13813
+ termWidth2 - indent.length
13814
+ );
13815
+ const indentedLine = line2.split("\n").map((l) => indent + l).join("\n");
13816
+ lines.push(indentedLine);
13817
+ }
13818
+ }
13819
+ }
13820
+ }
13821
+ for (const stream2 of activeNestedStreams) {
13822
+ const indent = " ".repeat(stream2.depth + 2);
13823
+ const elapsedSeconds = (Date.now() - stream2.startTime) / 1e3;
13824
+ const line = formatLLMCallLine({
13825
+ iteration: stream2.iteration,
13826
+ parentCallNumber: stream2.parentCallNumber,
13827
+ model: stream2.model,
13828
+ inputTokens: stream2.inputTokens,
13829
+ cachedInputTokens: stream2.cachedInputTokens,
13830
+ outputTokens: stream2.outputTokens,
13831
+ elapsedSeconds,
13832
+ cost: stream2.cost,
13833
+ isStreaming: true,
13834
+ spinner
13835
+ });
13836
+ lines.push(`${indent}${line}`);
13588
13837
  }
13589
- this.lastRenderLineCount = lines.length;
13590
- this.target.write("\r" + lines.join("\n"));
13838
+ if (this.mode === "streaming") {
13839
+ lines.push(this.formatStreamingLine(spinner));
13840
+ } else {
13841
+ lines.push(this.formatCumulativeLine(spinner));
13842
+ }
13843
+ const output = lines.join("\n");
13844
+ this.lastRenderLineCount = (output.match(/\n/g) || []).length + 1;
13845
+ this.target.write("\r" + output);
13591
13846
  this.hasRendered = true;
13592
13847
  }
13593
13848
  /**
@@ -14199,6 +14454,7 @@ async function executeAgent(promptArg, options, env) {
14199
14454
  env.stderr.write(`${summary}
14200
14455
  `);
14201
14456
  }
14457
+ env.stderr.write("\n");
14202
14458
  }
14203
14459
  if (llmSessionDir) {
14204
14460
  const filename = `${formatCallNumber(llmCallCounter)}.response`;
@@ -14305,8 +14561,7 @@ Denied: ${result.reason ?? "by user"}`
14305
14561
  builder.withTrailingMessage(
14306
14562
  (ctx) => [
14307
14563
  `[Iteration ${ctx.iteration + 1}/${ctx.maxIterations}]`,
14308
- "Think carefully: what gadget invocations can you make in parallel right now?",
14309
- "Maximize efficiency by batching independent operations in a single response."
14564
+ "Think carefully in two steps: 1. what gadget invocations we should be making next? 2. how do they depend on one another so we can run all of them in the right order? Then respond with all the gadget invocations you are able to do now."
14310
14565
  ].join(" ")
14311
14566
  );
14312
14567
  if (!options.quiet) {
@@ -14319,8 +14574,14 @@ Denied: ${result.reason ?? "by user"}`
14319
14574
  subagentEvent.gadgetInvocationId,
14320
14575
  subagentEvent.depth,
14321
14576
  info.model,
14322
- info.iteration,
14323
- info.inputTokens
14577
+ info.iteration + 1,
14578
+ // Make 1-indexed like main agent
14579
+ {
14580
+ inputTokens: info.usage?.inputTokens ?? info.inputTokens,
14581
+ cachedInputTokens: info.usage?.cachedInputTokens
14582
+ },
14583
+ llmCallCounter
14584
+ // Parent call number for hierarchical display (e.g., #1.2)
14324
14585
  );
14325
14586
  } else if (subagentEvent.type === "llm_call_end") {
14326
14587
  const info = subagentEvent.event;
@@ -14364,6 +14625,9 @@ Denied: ${result.reason ?? "by user"}`
14364
14625
  let textBuffer = "";
14365
14626
  const flushTextBuffer = () => {
14366
14627
  if (textBuffer) {
14628
+ if (!options.quiet) {
14629
+ progress.clearCompletedGadgets();
14630
+ }
14367
14631
  const output = options.quiet ? textBuffer : renderMarkdownWithSeparators(textBuffer);
14368
14632
  printer.write(output);
14369
14633
  textBuffer = "";
@@ -14386,7 +14650,7 @@ Denied: ${result.reason ?? "by user"}`
14386
14650
  } else if (event.type === "gadget_result") {
14387
14651
  flushTextBuffer();
14388
14652
  if (!options.quiet) {
14389
- progress.removeGadget(event.result.invocationId);
14653
+ progress.completeGadget(event.result.invocationId);
14390
14654
  }
14391
14655
  progress.pause();
14392
14656
  if (options.quiet) {
@@ -14397,10 +14661,23 @@ Denied: ${result.reason ?? "by user"}`
14397
14661
  }
14398
14662
  } else {
14399
14663
  const tokenCount = await countGadgetOutputTokens(event.result.result);
14400
- env.stderr.write(
14401
- `${formatGadgetSummary2({ ...event.result, tokenCount, media: event.result.storedMedia })}
14402
- `
14664
+ const subagentMetrics = progress.getAggregatedSubagentMetrics(
14665
+ event.result.invocationId
14403
14666
  );
14667
+ const summary = formatGadgetSummary2({
14668
+ ...event.result,
14669
+ tokenCount,
14670
+ media: event.result.storedMedia,
14671
+ subagentMetrics: subagentMetrics.callCount > 0 ? subagentMetrics : void 0
14672
+ });
14673
+ if (event.result.gadgetName === "TellUser") {
14674
+ env.stderr.write(`${summary}
14675
+ `);
14676
+ } else {
14677
+ const indentedSummary = summary.split("\n").map((line) => " " + line).join("\n");
14678
+ env.stderr.write(`${indentedSummary}
14679
+ `);
14680
+ }
14404
14681
  }
14405
14682
  if (progress.hasInFlightGadgets()) {
14406
14683
  progress.start();