llmist 3.0.0 → 4.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.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import "./chunk-NBPKLSXJ.js";
2
+ import "./chunk-Q6NQRMYD.js";
3
3
  import {
4
4
  AbstractGadget,
5
5
  AgentBuilder,
@@ -34,7 +34,7 @@ import {
34
34
  schemaToJSONSchema,
35
35
  text,
36
36
  validateGadgetSchema
37
- } from "./chunk-67MMSOAT.js";
37
+ } from "./chunk-RHR2M6T6.js";
38
38
 
39
39
  // src/cli/constants.ts
40
40
  var CLI_NAME = "llmist";
@@ -123,7 +123,7 @@ import { Command, InvalidArgumentError as InvalidArgumentError2 } from "commande
123
123
  // package.json
124
124
  var package_default = {
125
125
  name: "llmist",
126
- version: "3.0.0",
126
+ version: "3.1.0",
127
127
  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.",
128
128
  type: "module",
129
129
  main: "dist/index.cjs",
@@ -3063,20 +3063,53 @@ function renderOverallSummary(metadata) {
3063
3063
  }
3064
3064
  return parts.join(chalk3.dim(" | "));
3065
3065
  }
3066
- function formatParametersInline(params) {
3066
+ function getRawValue(value) {
3067
+ if (typeof value === "string") {
3068
+ return value;
3069
+ }
3070
+ if (typeof value === "boolean" || typeof value === "number") {
3071
+ return String(value);
3072
+ }
3073
+ return JSON.stringify(value);
3074
+ }
3075
+ function truncateValue(str, maxLen) {
3076
+ if (maxLen <= 0) return "";
3077
+ if (str.length <= maxLen) return str;
3078
+ return `${str.slice(0, maxLen)}\u2026`;
3079
+ }
3080
+ function formatParametersInline(params, maxWidth) {
3067
3081
  if (!params || Object.keys(params).length === 0) {
3068
3082
  return "";
3069
3083
  }
3070
- return Object.entries(params).map(([key, value]) => {
3071
- let formatted;
3072
- if (typeof value === "string") {
3073
- formatted = value.length > 30 ? `${value.slice(0, 30)}\u2026` : value;
3074
- } else if (typeof value === "boolean" || typeof value === "number") {
3075
- formatted = String(value);
3084
+ const entries = Object.entries(params);
3085
+ const defaultLimit = 30;
3086
+ const rawValues = entries.map(([, value]) => getRawValue(value));
3087
+ const overhead = entries.reduce((sum, [key], i) => {
3088
+ return sum + key.length + 1 + (i > 0 ? 2 : 0);
3089
+ }, 0);
3090
+ let limits;
3091
+ if (maxWidth && maxWidth > overhead) {
3092
+ const availableForValues = maxWidth - overhead;
3093
+ const totalRawLength = rawValues.reduce((sum, v) => sum + v.length, 0);
3094
+ if (totalRawLength <= availableForValues) {
3095
+ limits = rawValues.map(() => Infinity);
3076
3096
  } else {
3077
- const json = JSON.stringify(value);
3078
- formatted = json.length > 30 ? `${json.slice(0, 30)}\u2026` : json;
3097
+ const minPerValue = 10;
3098
+ const minTotal = entries.length * minPerValue;
3099
+ if (availableForValues <= minTotal) {
3100
+ limits = rawValues.map(() => Math.max(1, Math.floor(availableForValues / entries.length)));
3101
+ } else {
3102
+ limits = rawValues.map((v) => {
3103
+ const proportion = v.length / totalRawLength;
3104
+ return Math.max(minPerValue, Math.floor(proportion * availableForValues));
3105
+ });
3106
+ }
3079
3107
  }
3108
+ } else {
3109
+ limits = rawValues.map(() => defaultLimit);
3110
+ }
3111
+ return entries.map(([key, _], i) => {
3112
+ const formatted = truncateValue(rawValues[i], limits[i]);
3080
3113
  return `${chalk3.dim(key)}${chalk3.dim("=")}${chalk3.cyan(formatted)}`;
3081
3114
  }).join(chalk3.dim(", "));
3082
3115
  }
@@ -3112,23 +3145,28 @@ function formatMediaLine(media) {
3112
3145
  return `${chalk3.dim("[")}${icon} ${id} ${mimeType} ${size}${chalk3.dim("]")} ${chalk3.dim("\u2192")} ${path6}`;
3113
3146
  }
3114
3147
  function formatGadgetSummary2(result) {
3148
+ const terminalWidth = process.stdout.columns || 80;
3115
3149
  const gadgetLabel = chalk3.magenta.bold(result.gadgetName);
3116
- const timeLabel = chalk3.dim(`${Math.round(result.executionTimeMs)}ms`);
3117
- const paramsStr = formatParametersInline(result.parameters);
3118
- const paramsLabel = paramsStr ? `${chalk3.dim("(")}${paramsStr}${chalk3.dim(")")}` : "";
3119
- if (result.error) {
3120
- const errorMsg = result.error.length > 50 ? `${result.error.slice(0, 50)}\u2026` : result.error;
3121
- return `${chalk3.red("\u2717")} ${gadgetLabel}${paramsLabel} ${chalk3.red("error:")} ${errorMsg} ${timeLabel}`;
3122
- }
3123
- let outputLabel;
3150
+ const timeStr = result.executionTimeMs >= 1e3 ? `${(result.executionTimeMs / 1e3).toFixed(1)}s` : `${Math.round(result.executionTimeMs)}ms`;
3151
+ const timeLabel = chalk3.dim(timeStr);
3152
+ let outputStr;
3124
3153
  if (result.tokenCount !== void 0 && result.tokenCount > 0) {
3125
- outputLabel = chalk3.green(`${formatTokens(result.tokenCount)} tokens`);
3154
+ outputStr = `${formatTokens(result.tokenCount)} tokens`;
3126
3155
  } else if (result.result) {
3127
3156
  const outputBytes = Buffer.byteLength(result.result, "utf-8");
3128
- outputLabel = outputBytes > 0 ? chalk3.green(formatBytes(outputBytes)) : chalk3.dim("no output");
3157
+ outputStr = outputBytes > 0 ? formatBytes(outputBytes) : "no output";
3129
3158
  } else {
3130
- outputLabel = chalk3.dim("no output");
3159
+ outputStr = "no output";
3131
3160
  }
3161
+ const fixedLength = 2 + result.gadgetName.length + 2 + 3 + outputStr.length + 1 + timeStr.length;
3162
+ const availableForParams = Math.max(40, terminalWidth - fixedLength - 2);
3163
+ const paramsStr = formatParametersInline(result.parameters, availableForParams);
3164
+ const paramsLabel = paramsStr ? `${chalk3.dim("(")}${paramsStr}${chalk3.dim(")")}` : "";
3165
+ if (result.error) {
3166
+ const errorMsg = result.error.length > 50 ? `${result.error.slice(0, 50)}\u2026` : result.error;
3167
+ return `${chalk3.red("\u2717")} ${gadgetLabel}${paramsLabel} ${chalk3.red("error:")} ${errorMsg} ${timeLabel}`;
3168
+ }
3169
+ const outputLabel = outputStr === "no output" ? chalk3.dim(outputStr) : chalk3.green(outputStr);
3132
3170
  const icon = result.breaksLoop ? chalk3.yellow("\u23F9") : chalk3.green("\u2713");
3133
3171
  let summaryLine = `${icon} ${gadgetLabel}${paramsLabel} ${chalk3.dim("\u2192")} ${outputLabel} ${timeLabel}`;
3134
3172
  if (result.media && result.media.length > 0) {
@@ -3281,6 +3319,8 @@ var StreamProgress = class {
3281
3319
  delayTimeout = null;
3282
3320
  isRunning = false;
3283
3321
  hasRendered = false;
3322
+ lastRenderLineCount = 0;
3323
+ // Track lines rendered for multi-line clearing
3284
3324
  // Current call stats (streaming mode)
3285
3325
  mode = "cumulative";
3286
3326
  model = "";
@@ -3300,6 +3340,111 @@ var StreamProgress = class {
3300
3340
  totalCost = 0;
3301
3341
  iterations = 0;
3302
3342
  currentIteration = 0;
3343
+ // In-flight gadget tracking for concurrent status display
3344
+ inFlightGadgets = /* @__PURE__ */ new Map();
3345
+ // Nested agent tracking for hierarchical subagent display
3346
+ nestedAgents = /* @__PURE__ */ new Map();
3347
+ // Nested gadget tracking for hierarchical subagent display
3348
+ nestedGadgets = /* @__PURE__ */ new Map();
3349
+ /**
3350
+ * Add a gadget to the in-flight tracking (called when gadget_call event received).
3351
+ * Triggers re-render to show the gadget in the status display.
3352
+ */
3353
+ addGadget(invocationId, name, params) {
3354
+ this.inFlightGadgets.set(invocationId, { name, params, startTime: Date.now() });
3355
+ if (this.isRunning && this.isTTY) {
3356
+ this.render();
3357
+ }
3358
+ }
3359
+ /**
3360
+ * Remove a gadget from in-flight tracking (called when gadget_result event received).
3361
+ * Triggers re-render to update the status display.
3362
+ */
3363
+ removeGadget(invocationId) {
3364
+ this.inFlightGadgets.delete(invocationId);
3365
+ if (this.isRunning && this.isTTY) {
3366
+ this.render();
3367
+ }
3368
+ }
3369
+ /**
3370
+ * Check if there are any gadgets currently in flight.
3371
+ */
3372
+ hasInFlightGadgets() {
3373
+ return this.inFlightGadgets.size > 0;
3374
+ }
3375
+ /**
3376
+ * Add a nested agent LLM call (called when nested llm_call_start event received).
3377
+ * Used to display hierarchical progress for subagent gadgets.
3378
+ */
3379
+ addNestedAgent(id, parentInvocationId, depth, model, iteration, inputTokens) {
3380
+ this.nestedAgents.set(id, {
3381
+ parentInvocationId,
3382
+ depth,
3383
+ model,
3384
+ iteration,
3385
+ startTime: Date.now(),
3386
+ inputTokens
3387
+ });
3388
+ if (this.isRunning && this.isTTY) {
3389
+ this.render();
3390
+ }
3391
+ }
3392
+ /**
3393
+ * Update a nested agent with completion info (called when nested llm_call_end event received).
3394
+ */
3395
+ updateNestedAgent(id, outputTokens) {
3396
+ const agent = this.nestedAgents.get(id);
3397
+ if (agent) {
3398
+ agent.outputTokens = outputTokens;
3399
+ if (this.isRunning && this.isTTY) {
3400
+ this.render();
3401
+ }
3402
+ }
3403
+ }
3404
+ /**
3405
+ * Remove a nested agent (called when the nested LLM call completes).
3406
+ */
3407
+ removeNestedAgent(id) {
3408
+ this.nestedAgents.delete(id);
3409
+ if (this.isRunning && this.isTTY) {
3410
+ this.render();
3411
+ }
3412
+ }
3413
+ /**
3414
+ * Add a nested gadget call (called when nested gadget_call event received).
3415
+ */
3416
+ addNestedGadget(id, depth, parentInvocationId, name) {
3417
+ this.nestedGadgets.set(id, {
3418
+ depth,
3419
+ parentInvocationId,
3420
+ name,
3421
+ startTime: Date.now()
3422
+ });
3423
+ if (this.isRunning && this.isTTY) {
3424
+ this.render();
3425
+ }
3426
+ }
3427
+ /**
3428
+ * Remove a nested gadget (called when nested gadget_result event received).
3429
+ */
3430
+ removeNestedGadget(id) {
3431
+ this.nestedGadgets.delete(id);
3432
+ if (this.isRunning && this.isTTY) {
3433
+ this.render();
3434
+ }
3435
+ }
3436
+ /**
3437
+ * Mark a nested gadget as completed (keeps it visible with ✓ indicator).
3438
+ */
3439
+ completeNestedGadget(id) {
3440
+ const gadget = this.nestedGadgets.get(id);
3441
+ if (gadget) {
3442
+ gadget.completed = true;
3443
+ if (this.isRunning && this.isTTY) {
3444
+ this.render();
3445
+ }
3446
+ }
3447
+ }
3303
3448
  /**
3304
3449
  * Starts a new LLM call. Switches to streaming mode.
3305
3450
  * @param model - Model name being used
@@ -3426,15 +3571,58 @@ var StreamProgress = class {
3426
3571
  this.isStreaming = true;
3427
3572
  }
3428
3573
  render() {
3574
+ this.clearRenderedLines();
3429
3575
  const spinner = SPINNER_FRAMES[this.frameIndex++ % SPINNER_FRAMES.length];
3576
+ const lines = [];
3430
3577
  if (this.mode === "streaming") {
3431
- this.renderStreamingMode(spinner);
3578
+ lines.push(this.formatStreamingLine(spinner));
3432
3579
  } else {
3433
- this.renderCumulativeMode(spinner);
3580
+ lines.push(this.formatCumulativeLine(spinner));
3581
+ }
3582
+ if (this.isTTY) {
3583
+ for (const [gadgetId, gadget] of this.inFlightGadgets) {
3584
+ const elapsed = ((Date.now() - gadget.startTime) / 1e3).toFixed(1);
3585
+ const gadgetLine = ` ${chalk4.blue("\u23F5")} ${chalk4.magenta.bold(gadget.name)}${chalk4.dim("(...)")} ${chalk4.dim(elapsed + "s")}`;
3586
+ lines.push(gadgetLine);
3587
+ for (const [_agentId, nested] of this.nestedAgents) {
3588
+ if (nested.parentInvocationId !== gadgetId) continue;
3589
+ const indent = " ".repeat(nested.depth + 1);
3590
+ const nestedElapsed = ((Date.now() - nested.startTime) / 1e3).toFixed(1);
3591
+ const tokens = nested.inputTokens ? ` ${chalk4.dim("\u2191")}${chalk4.yellow(formatTokens(nested.inputTokens))}` : "";
3592
+ const outTokens = nested.outputTokens ? ` ${chalk4.dim("\u2193")}${chalk4.green(formatTokens(nested.outputTokens))}` : "";
3593
+ const nestedLine = `${indent}${chalk4.cyan(`#${nested.iteration}`)} ${chalk4.dim(nested.model)}${tokens}${outTokens} ${chalk4.dim(nestedElapsed + "s")} ${chalk4.cyan(spinner)}`;
3594
+ lines.push(nestedLine);
3595
+ }
3596
+ for (const [nestedId, nestedGadget] of this.nestedGadgets) {
3597
+ if (nestedGadget.parentInvocationId === gadgetId) {
3598
+ const indent = " ".repeat(nestedGadget.depth + 1);
3599
+ const nestedElapsed = ((Date.now() - nestedGadget.startTime) / 1e3).toFixed(1);
3600
+ const icon = nestedGadget.completed ? chalk4.green("\u2713") : chalk4.blue("\u23F5");
3601
+ const nestedGadgetLine = `${indent}${icon} ${chalk4.dim(nestedGadget.name + "(...)")} ${chalk4.dim(nestedElapsed + "s")}`;
3602
+ lines.push(nestedGadgetLine);
3603
+ }
3604
+ }
3605
+ }
3434
3606
  }
3607
+ this.lastRenderLineCount = lines.length;
3608
+ this.target.write("\r" + lines.join("\n"));
3435
3609
  this.hasRendered = true;
3436
3610
  }
3437
- renderStreamingMode(spinner) {
3611
+ /**
3612
+ * Clears the previously rendered lines (for multi-line status display).
3613
+ */
3614
+ clearRenderedLines() {
3615
+ if (!this.hasRendered || this.lastRenderLineCount === 0) return;
3616
+ this.target.write("\r\x1B[K");
3617
+ for (let i = 1; i < this.lastRenderLineCount; i++) {
3618
+ this.target.write("\x1B[1A\x1B[K");
3619
+ }
3620
+ this.target.write("\r");
3621
+ }
3622
+ /**
3623
+ * Format the streaming mode progress line (returns string, doesn't write).
3624
+ */
3625
+ formatStreamingLine(spinner) {
3438
3626
  const elapsed = ((Date.now() - this.callStartTime) / 1e3).toFixed(1);
3439
3627
  const outTokens = this.callOutputTokensEstimated ? Math.round(this.callOutputChars / FALLBACK_CHARS_PER_TOKEN) : this.callOutputTokens;
3440
3628
  const parts = [];
@@ -3468,7 +3656,7 @@ var StreamProgress = class {
3468
3656
  if (callCost > 0) {
3469
3657
  parts.push(chalk4.cyan(`$${formatCost(callCost)}`));
3470
3658
  }
3471
- this.target.write(`\r${parts.join(chalk4.dim(" | "))} ${chalk4.cyan(spinner)}`);
3659
+ return `${parts.join(chalk4.dim(" | "))} ${chalk4.cyan(spinner)}`;
3472
3660
  }
3473
3661
  /**
3474
3662
  * Calculates live cost estimate for the current streaming call.
@@ -3505,7 +3693,10 @@ var StreamProgress = class {
3505
3693
  }
3506
3694
  return this.callInputTokens / limits.contextWindow * 100;
3507
3695
  }
3508
- renderCumulativeMode(spinner) {
3696
+ /**
3697
+ * Format the cumulative mode progress line (returns string, doesn't write).
3698
+ */
3699
+ formatCumulativeLine(spinner) {
3509
3700
  const elapsed = ((Date.now() - this.totalStartTime) / 1e3).toFixed(1);
3510
3701
  const parts = [];
3511
3702
  if (this.model) {
@@ -3521,10 +3712,10 @@ var StreamProgress = class {
3521
3712
  parts.push(chalk4.dim("cost:") + chalk4.cyan(` $${formatCost(this.totalCost)}`));
3522
3713
  }
3523
3714
  parts.push(chalk4.dim(`${elapsed}s`));
3524
- this.target.write(`\r${parts.join(chalk4.dim(" | "))} ${chalk4.cyan(spinner)}`);
3715
+ return `${parts.join(chalk4.dim(" | "))} ${chalk4.cyan(spinner)}`;
3525
3716
  }
3526
3717
  /**
3527
- * Pauses the progress indicator and clears the line.
3718
+ * Pauses the progress indicator and clears all rendered lines.
3528
3719
  * Can be resumed with start().
3529
3720
  */
3530
3721
  pause() {
@@ -3538,10 +3729,9 @@ var StreamProgress = class {
3538
3729
  this.interval = null;
3539
3730
  }
3540
3731
  this.isRunning = false;
3541
- if (this.hasRendered) {
3542
- this.target.write("\r\x1B[K\x1B[0G");
3543
- this.hasRendered = false;
3544
- }
3732
+ this.clearRenderedLines();
3733
+ this.hasRendered = false;
3734
+ this.lastRenderLineCount = 0;
3545
3735
  }
3546
3736
  /**
3547
3737
  * Completes the progress indicator and clears the line.
@@ -4118,6 +4308,38 @@ Denied: ${result.reason ?? "by user"}`
4118
4308
  "Maximize efficiency by batching independent operations in a single response."
4119
4309
  ].join(" ")
4120
4310
  );
4311
+ if (!options.quiet) {
4312
+ builder.withSubagentEventCallback((subagentEvent) => {
4313
+ if (subagentEvent.type === "llm_call_start") {
4314
+ const info = subagentEvent.event;
4315
+ const subagentId = `${subagentEvent.gadgetInvocationId}:${info.iteration}`;
4316
+ progress.addNestedAgent(
4317
+ subagentId,
4318
+ subagentEvent.gadgetInvocationId,
4319
+ subagentEvent.depth,
4320
+ info.model,
4321
+ info.iteration,
4322
+ info.inputTokens
4323
+ );
4324
+ } else if (subagentEvent.type === "llm_call_end") {
4325
+ const info = subagentEvent.event;
4326
+ const subagentId = `${subagentEvent.gadgetInvocationId}:${info.iteration}`;
4327
+ progress.updateNestedAgent(subagentId, info.outputTokens);
4328
+ setTimeout(() => progress.removeNestedAgent(subagentId), 100);
4329
+ } else if (subagentEvent.type === "gadget_call") {
4330
+ const gadgetEvent = subagentEvent.event;
4331
+ progress.addNestedGadget(
4332
+ gadgetEvent.call.invocationId,
4333
+ subagentEvent.depth,
4334
+ subagentEvent.gadgetInvocationId,
4335
+ gadgetEvent.call.gadgetName
4336
+ );
4337
+ } else if (subagentEvent.type === "gadget_result") {
4338
+ const resultEvent = subagentEvent.event;
4339
+ progress.completeNestedGadget(resultEvent.result.invocationId);
4340
+ }
4341
+ });
4342
+ }
4121
4343
  let agent;
4122
4344
  if (options.image || options.audio) {
4123
4345
  const parts = [text(prompt)];
@@ -4142,10 +4364,22 @@ Denied: ${result.reason ?? "by user"}`
4142
4364
  try {
4143
4365
  for await (const event of agent.run()) {
4144
4366
  if (event.type === "text") {
4145
- progress.pause();
4146
4367
  textBuffer += event.content;
4368
+ } else if (event.type === "gadget_call") {
4369
+ flushTextBuffer();
4370
+ if (!options.quiet) {
4371
+ progress.addGadget(
4372
+ event.call.invocationId,
4373
+ event.call.gadgetName,
4374
+ event.call.parameters
4375
+ );
4376
+ progress.start();
4377
+ }
4147
4378
  } else if (event.type === "gadget_result") {
4148
4379
  flushTextBuffer();
4380
+ if (!options.quiet) {
4381
+ progress.removeGadget(event.result.invocationId);
4382
+ }
4149
4383
  progress.pause();
4150
4384
  if (options.quiet) {
4151
4385
  if (event.result.gadgetName === "TellUser" && event.result.parameters?.message) {
@@ -4160,6 +4394,10 @@ Denied: ${result.reason ?? "by user"}`
4160
4394
  `
4161
4395
  );
4162
4396
  }
4397
+ if (progress.hasInFlightGadgets()) {
4398
+ progress.start();
4399
+ }
4400
+ } else if (event.type === "subagent_event") {
4163
4401
  }
4164
4402
  }
4165
4403
  } catch (error) {