llmist 1.6.2 → 2.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
@@ -644,6 +644,44 @@ ${this.endPrefix}`
644
644
  }
645
645
  });
646
646
 
647
+ // src/gadgets/exceptions.ts
648
+ var BreakLoopException, HumanInputException, TimeoutException, AbortError;
649
+ var init_exceptions = __esm({
650
+ "src/gadgets/exceptions.ts"() {
651
+ "use strict";
652
+ BreakLoopException = class extends Error {
653
+ constructor(message) {
654
+ super(message ?? "Agent loop terminated by gadget");
655
+ this.name = "BreakLoopException";
656
+ }
657
+ };
658
+ HumanInputException = class extends Error {
659
+ question;
660
+ constructor(question) {
661
+ super(`Human input required: ${question}`);
662
+ this.name = "HumanInputException";
663
+ this.question = question;
664
+ }
665
+ };
666
+ TimeoutException = class extends Error {
667
+ timeoutMs;
668
+ gadgetName;
669
+ constructor(gadgetName, timeoutMs) {
670
+ super(`Gadget '${gadgetName}' execution exceeded timeout of ${timeoutMs}ms`);
671
+ this.name = "TimeoutException";
672
+ this.gadgetName = gadgetName;
673
+ this.timeoutMs = timeoutMs;
674
+ }
675
+ };
676
+ AbortError = class extends Error {
677
+ constructor(message) {
678
+ super(message || "Gadget execution was aborted");
679
+ this.name = "AbortError";
680
+ }
681
+ };
682
+ }
683
+ });
684
+
647
685
  // src/logging/logger.ts
648
686
  function parseLogLevel(value) {
649
687
  if (!value) {
@@ -916,6 +954,7 @@ var init_gadget = __esm({
916
954
  "src/gadgets/gadget.ts"() {
917
955
  "use strict";
918
956
  init_constants();
957
+ init_exceptions();
919
958
  init_schema_to_json();
920
959
  init_schema_validator();
921
960
  BaseGadget = class {
@@ -945,6 +984,42 @@ var init_gadget = __esm({
945
984
  * while maintaining runtime compatibility.
946
985
  */
947
986
  examples;
987
+ /**
988
+ * Throws an AbortError if the execution has been aborted.
989
+ *
990
+ * Call this at key checkpoints in long-running gadgets to allow early exit
991
+ * when the gadget has been cancelled (e.g., due to timeout). This enables
992
+ * resource cleanup and prevents unnecessary work after cancellation.
993
+ *
994
+ * @param ctx - The execution context containing the abort signal
995
+ * @throws AbortError if ctx.signal.aborted is true
996
+ *
997
+ * @example
998
+ * ```typescript
999
+ * class DataProcessor extends Gadget({
1000
+ * description: 'Processes data in multiple steps',
1001
+ * schema: z.object({ items: z.array(z.string()) }),
1002
+ * }) {
1003
+ * async execute(params: this['params'], ctx?: ExecutionContext): Promise<string> {
1004
+ * const results: string[] = [];
1005
+ *
1006
+ * for (const item of params.items) {
1007
+ * // Check before each expensive operation
1008
+ * this.throwIfAborted(ctx);
1009
+ *
1010
+ * results.push(await this.processItem(item));
1011
+ * }
1012
+ *
1013
+ * return results.join(', ');
1014
+ * }
1015
+ * }
1016
+ * ```
1017
+ */
1018
+ throwIfAborted(ctx) {
1019
+ if (ctx?.signal?.aborted) {
1020
+ throw new AbortError();
1021
+ }
1022
+ }
948
1023
  /**
949
1024
  * Auto-generated instruction text for the LLM.
950
1025
  * Combines name, description, and parameter schema into a formatted instruction.
@@ -1012,8 +1087,8 @@ function createGadget(config) {
1012
1087
  parameterSchema = config.schema;
1013
1088
  timeoutMs = config.timeoutMs;
1014
1089
  examples = config.examples;
1015
- execute(params) {
1016
- return config.execute(params);
1090
+ execute(params, ctx) {
1091
+ return config.execute(params, ctx);
1017
1092
  }
1018
1093
  }
1019
1094
  return new DynamicGadget();
@@ -2319,6 +2394,162 @@ var init_block_params = __esm({
2319
2394
  }
2320
2395
  });
2321
2396
 
2397
+ // src/gadgets/cost-reporting-client.ts
2398
+ var CostReportingLLMistWrapper;
2399
+ var init_cost_reporting_client = __esm({
2400
+ "src/gadgets/cost-reporting-client.ts"() {
2401
+ "use strict";
2402
+ init_model_shortcuts();
2403
+ CostReportingLLMistWrapper = class {
2404
+ constructor(client, reportCost) {
2405
+ this.client = client;
2406
+ this.reportCost = reportCost;
2407
+ }
2408
+ /**
2409
+ * Access to model registry for cost estimation.
2410
+ */
2411
+ get modelRegistry() {
2412
+ return this.client.modelRegistry;
2413
+ }
2414
+ /**
2415
+ * Quick completion with automatic cost reporting.
2416
+ *
2417
+ * Streams internally to track token usage, then reports the calculated cost.
2418
+ *
2419
+ * @param prompt - User prompt
2420
+ * @param options - Optional configuration (model, temperature, etc.)
2421
+ * @returns Complete text response
2422
+ */
2423
+ async complete(prompt, options) {
2424
+ const model = resolveModel(options?.model ?? "haiku");
2425
+ let result = "";
2426
+ let inputTokens = 0;
2427
+ let outputTokens = 0;
2428
+ let cachedInputTokens = 0;
2429
+ let cacheCreationInputTokens = 0;
2430
+ const messages = [
2431
+ ...options?.systemPrompt ? [{ role: "system", content: options.systemPrompt }] : [],
2432
+ { role: "user", content: prompt }
2433
+ ];
2434
+ for await (const chunk of this.client.stream({
2435
+ model,
2436
+ messages,
2437
+ temperature: options?.temperature,
2438
+ maxTokens: options?.maxTokens
2439
+ })) {
2440
+ result += chunk.text ?? "";
2441
+ if (chunk.usage) {
2442
+ inputTokens = chunk.usage.inputTokens;
2443
+ outputTokens = chunk.usage.outputTokens;
2444
+ cachedInputTokens = chunk.usage.cachedInputTokens ?? 0;
2445
+ cacheCreationInputTokens = chunk.usage.cacheCreationInputTokens ?? 0;
2446
+ }
2447
+ }
2448
+ this.reportCostFromUsage(model, inputTokens, outputTokens, cachedInputTokens, cacheCreationInputTokens);
2449
+ return result;
2450
+ }
2451
+ /**
2452
+ * Quick streaming with automatic cost reporting when stream completes.
2453
+ *
2454
+ * Yields text chunks as they arrive, then reports cost in finally block.
2455
+ *
2456
+ * @param prompt - User prompt
2457
+ * @param options - Optional configuration (model, temperature, etc.)
2458
+ * @returns Async generator yielding text chunks
2459
+ */
2460
+ async *streamText(prompt, options) {
2461
+ const model = resolveModel(options?.model ?? "haiku");
2462
+ let inputTokens = 0;
2463
+ let outputTokens = 0;
2464
+ let cachedInputTokens = 0;
2465
+ let cacheCreationInputTokens = 0;
2466
+ const messages = [
2467
+ ...options?.systemPrompt ? [{ role: "system", content: options.systemPrompt }] : [],
2468
+ { role: "user", content: prompt }
2469
+ ];
2470
+ try {
2471
+ for await (const chunk of this.client.stream({
2472
+ model,
2473
+ messages,
2474
+ temperature: options?.temperature,
2475
+ maxTokens: options?.maxTokens
2476
+ })) {
2477
+ if (chunk.text) {
2478
+ yield chunk.text;
2479
+ }
2480
+ if (chunk.usage) {
2481
+ inputTokens = chunk.usage.inputTokens;
2482
+ outputTokens = chunk.usage.outputTokens;
2483
+ cachedInputTokens = chunk.usage.cachedInputTokens ?? 0;
2484
+ cacheCreationInputTokens = chunk.usage.cacheCreationInputTokens ?? 0;
2485
+ }
2486
+ }
2487
+ } finally {
2488
+ this.reportCostFromUsage(model, inputTokens, outputTokens, cachedInputTokens, cacheCreationInputTokens);
2489
+ }
2490
+ }
2491
+ /**
2492
+ * Low-level stream access with automatic cost reporting.
2493
+ *
2494
+ * Returns a wrapped stream that reports costs when iteration completes.
2495
+ *
2496
+ * @param options - Full LLM generation options
2497
+ * @returns Wrapped LLM stream that auto-reports costs
2498
+ */
2499
+ stream(options) {
2500
+ return this.createCostReportingStream(options);
2501
+ }
2502
+ /**
2503
+ * Creates a wrapped stream that tracks usage and reports costs on completion.
2504
+ */
2505
+ createCostReportingStream(options) {
2506
+ const innerStream = this.client.stream(options);
2507
+ const reportCostFromUsage = this.reportCostFromUsage.bind(this);
2508
+ const model = options.model;
2509
+ async function* costReportingWrapper() {
2510
+ let inputTokens = 0;
2511
+ let outputTokens = 0;
2512
+ let cachedInputTokens = 0;
2513
+ let cacheCreationInputTokens = 0;
2514
+ try {
2515
+ for await (const chunk of innerStream) {
2516
+ if (chunk.usage) {
2517
+ inputTokens = chunk.usage.inputTokens;
2518
+ outputTokens = chunk.usage.outputTokens;
2519
+ cachedInputTokens = chunk.usage.cachedInputTokens ?? 0;
2520
+ cacheCreationInputTokens = chunk.usage.cacheCreationInputTokens ?? 0;
2521
+ }
2522
+ yield chunk;
2523
+ }
2524
+ } finally {
2525
+ if (inputTokens > 0 || outputTokens > 0) {
2526
+ reportCostFromUsage(model, inputTokens, outputTokens, cachedInputTokens, cacheCreationInputTokens);
2527
+ }
2528
+ }
2529
+ }
2530
+ return costReportingWrapper();
2531
+ }
2532
+ /**
2533
+ * Calculates and reports cost from token usage.
2534
+ */
2535
+ reportCostFromUsage(model, inputTokens, outputTokens, cachedInputTokens = 0, cacheCreationInputTokens = 0) {
2536
+ if (inputTokens === 0 && outputTokens === 0) return;
2537
+ const modelName = model.includes(":") ? model.split(":")[1] : model;
2538
+ const estimate = this.client.modelRegistry.estimateCost(
2539
+ modelName,
2540
+ inputTokens,
2541
+ outputTokens,
2542
+ cachedInputTokens,
2543
+ cacheCreationInputTokens
2544
+ );
2545
+ if (estimate && estimate.totalCost > 0) {
2546
+ this.reportCost(estimate.totalCost);
2547
+ }
2548
+ }
2549
+ };
2550
+ }
2551
+ });
2552
+
2322
2553
  // src/gadgets/error-formatter.ts
2323
2554
  var GadgetErrorFormatter;
2324
2555
  var init_error_formatter = __esm({
@@ -2402,38 +2633,6 @@ var init_error_formatter = __esm({
2402
2633
  }
2403
2634
  });
2404
2635
 
2405
- // src/gadgets/exceptions.ts
2406
- var BreakLoopException, HumanInputException, TimeoutException;
2407
- var init_exceptions = __esm({
2408
- "src/gadgets/exceptions.ts"() {
2409
- "use strict";
2410
- BreakLoopException = class extends Error {
2411
- constructor(message) {
2412
- super(message ?? "Agent loop terminated by gadget");
2413
- this.name = "BreakLoopException";
2414
- }
2415
- };
2416
- HumanInputException = class extends Error {
2417
- question;
2418
- constructor(question) {
2419
- super(`Human input required: ${question}`);
2420
- this.name = "HumanInputException";
2421
- this.question = question;
2422
- }
2423
- };
2424
- TimeoutException = class extends Error {
2425
- timeoutMs;
2426
- gadgetName;
2427
- constructor(gadgetName, timeoutMs) {
2428
- super(`Gadget '${gadgetName}' execution exceeded timeout of ${timeoutMs}ms`);
2429
- this.name = "TimeoutException";
2430
- this.gadgetName = gadgetName;
2431
- this.timeoutMs = timeoutMs;
2432
- }
2433
- };
2434
- }
2435
- });
2436
-
2437
2636
  // src/gadgets/parser.ts
2438
2637
  function stripMarkdownFences(content) {
2439
2638
  let cleaned = content.trim();
@@ -2619,14 +2818,16 @@ var init_executor = __esm({
2619
2818
  init_constants();
2620
2819
  init_logger();
2621
2820
  init_block_params();
2821
+ init_cost_reporting_client();
2622
2822
  init_error_formatter();
2623
2823
  init_exceptions();
2624
2824
  init_parser();
2625
2825
  GadgetExecutor = class {
2626
- constructor(registry, onHumanInputRequired, logger, defaultGadgetTimeoutMs, errorFormatterOptions) {
2826
+ constructor(registry, onHumanInputRequired, logger, defaultGadgetTimeoutMs, errorFormatterOptions, client) {
2627
2827
  this.registry = registry;
2628
2828
  this.onHumanInputRequired = onHumanInputRequired;
2629
2829
  this.defaultGadgetTimeoutMs = defaultGadgetTimeoutMs;
2830
+ this.client = client;
2630
2831
  this.logger = logger ?? createLogger({ name: "llmist:executor" });
2631
2832
  this.errorFormatter = new GadgetErrorFormatter(errorFormatterOptions);
2632
2833
  this.argPrefix = errorFormatterOptions?.argPrefix ?? GADGET_ARG_PREFIX;
@@ -2636,14 +2837,27 @@ var init_executor = __esm({
2636
2837
  argPrefix;
2637
2838
  /**
2638
2839
  * Creates a promise that rejects with a TimeoutException after the specified timeout.
2840
+ * Aborts the provided AbortController before rejecting, allowing gadgets to clean up.
2639
2841
  */
2640
- createTimeoutPromise(gadgetName, timeoutMs) {
2842
+ createTimeoutPromise(gadgetName, timeoutMs, abortController) {
2641
2843
  return new Promise((_, reject) => {
2642
2844
  setTimeout(() => {
2643
- reject(new TimeoutException(gadgetName, timeoutMs));
2845
+ const timeoutError = new TimeoutException(gadgetName, timeoutMs);
2846
+ abortController.abort(timeoutError.message);
2847
+ reject(timeoutError);
2644
2848
  }, timeoutMs);
2645
2849
  });
2646
2850
  }
2851
+ /**
2852
+ * Normalizes gadget execute result to consistent format.
2853
+ * Handles both string returns (backwards compat) and object returns with cost.
2854
+ */
2855
+ normalizeExecuteResult(raw) {
2856
+ if (typeof raw === "string") {
2857
+ return { result: raw, cost: 0 };
2858
+ }
2859
+ return { result: raw.result, cost: raw.cost ?? 0 };
2860
+ }
2647
2861
  // Execute a gadget call asynchronously
2648
2862
  async execute(call) {
2649
2863
  const startTime = Date.now();
@@ -2738,30 +2952,53 @@ var init_executor = __esm({
2738
2952
  validatedParameters = schemaAwareParameters;
2739
2953
  }
2740
2954
  const timeoutMs = gadget.timeoutMs ?? this.defaultGadgetTimeoutMs;
2741
- let result;
2955
+ const abortController = new AbortController();
2956
+ let callbackCost = 0;
2957
+ const reportCost = (amount) => {
2958
+ if (amount > 0) {
2959
+ callbackCost += amount;
2960
+ this.logger.debug("Gadget reported cost via callback", {
2961
+ gadgetName: call.gadgetName,
2962
+ amount,
2963
+ totalCallbackCost: callbackCost
2964
+ });
2965
+ }
2966
+ };
2967
+ const ctx = {
2968
+ reportCost,
2969
+ llmist: this.client ? new CostReportingLLMistWrapper(this.client, reportCost) : void 0,
2970
+ signal: abortController.signal
2971
+ };
2972
+ let rawResult;
2742
2973
  if (timeoutMs && timeoutMs > 0) {
2743
2974
  this.logger.debug("Executing gadget with timeout", {
2744
2975
  gadgetName: call.gadgetName,
2745
2976
  timeoutMs
2746
2977
  });
2747
- result = await Promise.race([
2748
- Promise.resolve(gadget.execute(validatedParameters)),
2749
- this.createTimeoutPromise(call.gadgetName, timeoutMs)
2978
+ rawResult = await Promise.race([
2979
+ Promise.resolve(gadget.execute(validatedParameters, ctx)),
2980
+ this.createTimeoutPromise(call.gadgetName, timeoutMs, abortController)
2750
2981
  ]);
2751
2982
  } else {
2752
- result = await Promise.resolve(gadget.execute(validatedParameters));
2983
+ rawResult = await Promise.resolve(gadget.execute(validatedParameters, ctx));
2753
2984
  }
2985
+ const { result, cost: returnCost } = this.normalizeExecuteResult(rawResult);
2986
+ const totalCost = callbackCost + returnCost;
2754
2987
  const executionTimeMs = Date.now() - startTime;
2755
2988
  this.logger.info("Gadget executed successfully", {
2756
2989
  gadgetName: call.gadgetName,
2757
2990
  invocationId: call.invocationId,
2758
- executionTimeMs
2991
+ executionTimeMs,
2992
+ cost: totalCost > 0 ? totalCost : void 0,
2993
+ callbackCost: callbackCost > 0 ? callbackCost : void 0,
2994
+ returnCost: returnCost > 0 ? returnCost : void 0
2759
2995
  });
2760
2996
  this.logger.debug("Gadget result", {
2761
2997
  gadgetName: call.gadgetName,
2762
2998
  invocationId: call.invocationId,
2763
2999
  parameters: validatedParameters,
2764
3000
  result,
3001
+ cost: totalCost,
2765
3002
  executionTimeMs
2766
3003
  });
2767
3004
  return {
@@ -2769,7 +3006,8 @@ var init_executor = __esm({
2769
3006
  invocationId: call.invocationId,
2770
3007
  parameters: validatedParameters,
2771
3008
  result,
2772
- executionTimeMs
3009
+ executionTimeMs,
3010
+ cost: totalCost
2773
3011
  };
2774
3012
  } catch (error) {
2775
3013
  if (error instanceof BreakLoopException) {
@@ -2800,6 +3038,19 @@ var init_executor = __esm({
2800
3038
  executionTimeMs: Date.now() - startTime
2801
3039
  };
2802
3040
  }
3041
+ if (error instanceof AbortError) {
3042
+ this.logger.info("Gadget execution was aborted", {
3043
+ gadgetName: call.gadgetName,
3044
+ executionTimeMs: Date.now() - startTime
3045
+ });
3046
+ return {
3047
+ gadgetName: call.gadgetName,
3048
+ invocationId: call.invocationId,
3049
+ parameters: validatedParameters,
3050
+ error: error.message,
3051
+ executionTimeMs: Date.now() - startTime
3052
+ };
3053
+ }
2803
3054
  if (error instanceof HumanInputException) {
2804
3055
  this.logger.info("Gadget requested human input", {
2805
3056
  gadgetName: call.gadgetName,
@@ -2926,7 +3177,8 @@ var init_stream_processor = __esm({
2926
3177
  options.onHumanInputRequired,
2927
3178
  this.logger.getSubLogger({ name: "executor" }),
2928
3179
  options.defaultGadgetTimeoutMs,
2929
- { argPrefix: options.gadgetArgPrefix }
3180
+ { argPrefix: options.gadgetArgPrefix },
3181
+ options.client
2930
3182
  );
2931
3183
  }
2932
3184
  /**
@@ -3193,6 +3445,7 @@ var init_stream_processor = __esm({
3193
3445
  error: result.error,
3194
3446
  executionTimeMs: result.executionTimeMs,
3195
3447
  breaksLoop: result.breaksLoop,
3448
+ cost: result.cost,
3196
3449
  logger: this.logger
3197
3450
  };
3198
3451
  await this.hooks.observers.onGadgetExecutionComplete(context);
@@ -3569,7 +3822,8 @@ var init_agent = __esm({
3569
3822
  onHumanInputRequired: this.onHumanInputRequired,
3570
3823
  stopOnGadgetError: this.stopOnGadgetError,
3571
3824
  shouldContinueAfterError: this.shouldContinueAfterError,
3572
- defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs
3825
+ defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
3826
+ client: this.client
3573
3827
  });
3574
3828
  const result = await processor.process(stream2);
3575
3829
  for (const output of result.outputs) {
@@ -5852,6 +6106,7 @@ var init_builder = __esm({
5852
6106
  gadgetOutputLimitPercent;
5853
6107
  compactionConfig;
5854
6108
  signal;
6109
+ trailingMessage;
5855
6110
  constructor(client) {
5856
6111
  this.client = client;
5857
6112
  }
@@ -6327,6 +6582,31 @@ var init_builder = __esm({
6327
6582
  this.signal = signal;
6328
6583
  return this;
6329
6584
  }
6585
+ /**
6586
+ * Add an ephemeral trailing message that appears at the end of each LLM request.
6587
+ *
6588
+ * The message is NOT persisted to conversation history - it only appears in the
6589
+ * current LLM call. This is useful for injecting context-specific instructions
6590
+ * or reminders without polluting the conversation history.
6591
+ *
6592
+ * @param message - Static string or function that generates the message
6593
+ * @returns This builder for chaining
6594
+ *
6595
+ * @example
6596
+ * ```typescript
6597
+ * // Static message
6598
+ * .withTrailingMessage("Always respond in JSON format.")
6599
+ *
6600
+ * // Dynamic message based on iteration
6601
+ * .withTrailingMessage((ctx) =>
6602
+ * `[Iteration ${ctx.iteration}/${ctx.maxIterations}] Stay focused on the task.`
6603
+ * )
6604
+ * ```
6605
+ */
6606
+ withTrailingMessage(message) {
6607
+ this.trailingMessage = message;
6608
+ return this;
6609
+ }
6330
6610
  /**
6331
6611
  * Add a synthetic gadget call to the conversation history.
6332
6612
  *
@@ -6368,6 +6648,36 @@ ${endPrefix}`
6368
6648
  });
6369
6649
  return this;
6370
6650
  }
6651
+ /**
6652
+ * Compose the final hooks, including trailing message if configured.
6653
+ */
6654
+ composeHooks() {
6655
+ if (!this.trailingMessage) {
6656
+ return this.hooks;
6657
+ }
6658
+ const trailingMsg = this.trailingMessage;
6659
+ const existingBeforeLLMCall = this.hooks?.controllers?.beforeLLMCall;
6660
+ const trailingMessageController = async (ctx) => {
6661
+ const result = existingBeforeLLMCall ? await existingBeforeLLMCall(ctx) : { action: "proceed" };
6662
+ if (result.action === "skip") {
6663
+ return result;
6664
+ }
6665
+ const messages = [...result.modifiedOptions?.messages || ctx.options.messages];
6666
+ const content = typeof trailingMsg === "function" ? trailingMsg({ iteration: ctx.iteration, maxIterations: ctx.maxIterations }) : trailingMsg;
6667
+ messages.push({ role: "user", content });
6668
+ return {
6669
+ action: "proceed",
6670
+ modifiedOptions: { ...result.modifiedOptions, messages }
6671
+ };
6672
+ };
6673
+ return {
6674
+ ...this.hooks,
6675
+ controllers: {
6676
+ ...this.hooks?.controllers,
6677
+ beforeLLMCall: trailingMessageController
6678
+ }
6679
+ };
6680
+ }
6371
6681
  /**
6372
6682
  * Format parameters as block format with JSON Pointer paths.
6373
6683
  */
@@ -6429,7 +6739,7 @@ ${endPrefix}`
6429
6739
  maxIterations: this.maxIterations,
6430
6740
  temperature: this.temperature,
6431
6741
  logger: this.logger,
6432
- hooks: this.hooks,
6742
+ hooks: this.composeHooks(),
6433
6743
  promptConfig: this.promptConfig,
6434
6744
  initialMessages: this.initialMessages,
6435
6745
  onHumanInputRequired: this.onHumanInputRequired,
@@ -6533,7 +6843,7 @@ ${endPrefix}`
6533
6843
  maxIterations: this.maxIterations,
6534
6844
  temperature: this.temperature,
6535
6845
  logger: this.logger,
6536
- hooks: this.hooks,
6846
+ hooks: this.composeHooks(),
6537
6847
  promptConfig: this.promptConfig,
6538
6848
  initialMessages: this.initialMessages,
6539
6849
  onHumanInputRequired: this.onHumanInputRequired,
@@ -6615,7 +6925,7 @@ var import_commander2 = require("commander");
6615
6925
  // package.json
6616
6926
  var package_default = {
6617
6927
  name: "llmist",
6618
- version: "1.6.1",
6928
+ version: "1.7.0",
6619
6929
  description: "Universal TypeScript LLM client with streaming-first agent framework. Works with any model - no structured outputs or native tool calling required. Implements its own flexible grammar for function calling.",
6620
6930
  type: "module",
6621
6931
  main: "dist/index.cjs",
@@ -7478,38 +7788,46 @@ error: ${message}`;
7478
7788
  var import_zod8 = require("zod");
7479
7789
  var runCommand = createGadget({
7480
7790
  name: "RunCommand",
7481
- description: "Execute a shell command and return its output. Returns both stdout and stderr combined with the exit status.",
7791
+ description: "Execute a command with arguments and return its output. Uses argv array to bypass shell - arguments are passed directly without interpretation. Returns stdout/stderr combined with exit status.",
7482
7792
  schema: import_zod8.z.object({
7483
- command: import_zod8.z.string().describe("The shell command to execute"),
7793
+ argv: import_zod8.z.array(import_zod8.z.string()).describe("Command and arguments as array (e.g., ['git', 'commit', '-m', 'message'])"),
7484
7794
  cwd: import_zod8.z.string().optional().describe("Working directory for the command (default: current directory)"),
7485
7795
  timeout: import_zod8.z.number().default(3e4).describe("Timeout in milliseconds (default: 30000)")
7486
7796
  }),
7487
7797
  examples: [
7488
7798
  {
7489
- params: { command: "ls -la", timeout: 3e4 },
7799
+ params: { argv: ["ls", "-la"], timeout: 3e4 },
7490
7800
  output: "status=0\n\ntotal 24\ndrwxr-xr-x 5 user staff 160 Nov 27 10:00 .\ndrwxr-xr-x 3 user staff 96 Nov 27 09:00 ..\n-rw-r--r-- 1 user staff 1024 Nov 27 10:00 package.json",
7491
7801
  comment: "List directory contents with details"
7492
7802
  },
7493
7803
  {
7494
- params: { command: "echo 'Hello World'", timeout: 3e4 },
7804
+ params: { argv: ["echo", "Hello World"], timeout: 3e4 },
7495
7805
  output: "status=0\n\nHello World",
7496
- comment: "Simple echo command"
7806
+ comment: "Echo without shell - argument passed directly"
7497
7807
  },
7498
7808
  {
7499
- params: { command: "cat nonexistent.txt", timeout: 3e4 },
7809
+ params: { argv: ["cat", "nonexistent.txt"], timeout: 3e4 },
7500
7810
  output: "status=1\n\ncat: nonexistent.txt: No such file or directory",
7501
7811
  comment: "Command that fails returns non-zero status"
7502
7812
  },
7503
7813
  {
7504
- params: { command: "pwd", cwd: "/tmp", timeout: 3e4 },
7814
+ params: { argv: ["pwd"], cwd: "/tmp", timeout: 3e4 },
7505
7815
  output: "status=0\n\n/tmp",
7506
7816
  comment: "Execute command in a specific directory"
7817
+ },
7818
+ {
7819
+ params: { argv: ["gh", "pr", "review", "123", "--comment", "--body", "Review with `backticks` and 'quotes'"], timeout: 3e4 },
7820
+ output: "status=0\n\n(no output)",
7821
+ comment: "Complex arguments with special characters - no escaping needed"
7507
7822
  }
7508
7823
  ],
7509
- execute: async ({ command, cwd, timeout }) => {
7824
+ execute: async ({ argv, cwd, timeout }) => {
7510
7825
  const workingDir = cwd ?? process.cwd();
7826
+ if (argv.length === 0) {
7827
+ return "status=1\n\nerror: argv array cannot be empty";
7828
+ }
7511
7829
  try {
7512
- const proc = Bun.spawn(["sh", "-c", command], {
7830
+ const proc = Bun.spawn(argv, {
7513
7831
  cwd: workingDir,
7514
7832
  stdout: "pipe",
7515
7833
  stderr: "pipe"
@@ -10417,9 +10735,11 @@ ${issues}`);
10417
10735
  env.stderr.write(import_chalk7.default.dim("\nExecuting...\n"));
10418
10736
  const startTime = Date.now();
10419
10737
  let result;
10738
+ let cost;
10420
10739
  try {
10740
+ let rawResult;
10421
10741
  if (gadget.timeoutMs && gadget.timeoutMs > 0) {
10422
- result = await Promise.race([
10742
+ rawResult = await Promise.race([
10423
10743
  Promise.resolve(gadget.execute(params)),
10424
10744
  new Promise(
10425
10745
  (_, reject) => setTimeout(
@@ -10429,15 +10749,18 @@ ${issues}`);
10429
10749
  )
10430
10750
  ]);
10431
10751
  } else {
10432
- result = await Promise.resolve(gadget.execute(params));
10752
+ rawResult = await Promise.resolve(gadget.execute(params));
10433
10753
  }
10754
+ result = typeof rawResult === "string" ? rawResult : rawResult.result;
10755
+ cost = typeof rawResult === "object" ? rawResult.cost : void 0;
10434
10756
  } catch (error) {
10435
10757
  const message = error instanceof Error ? error.message : String(error);
10436
10758
  throw new Error(`Execution failed: ${message}`);
10437
10759
  }
10438
10760
  const elapsed = Date.now() - startTime;
10761
+ const costInfo = cost !== void 0 && cost > 0 ? ` (Cost: $${cost.toFixed(6)})` : "";
10439
10762
  env.stderr.write(import_chalk7.default.green(`
10440
- \u2713 Completed in ${elapsed}ms
10763
+ \u2713 Completed in ${elapsed}ms${costInfo}
10441
10764
 
10442
10765
  `));
10443
10766
  formatOutput(result, options, env.stdout);