llmist 1.7.0 → 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) {
@@ -6671,7 +6925,7 @@ var import_commander2 = require("commander");
6671
6925
  // package.json
6672
6926
  var package_default = {
6673
6927
  name: "llmist",
6674
- version: "1.6.2",
6928
+ version: "1.7.0",
6675
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.",
6676
6930
  type: "module",
6677
6931
  main: "dist/index.cjs",
@@ -7534,38 +7788,46 @@ error: ${message}`;
7534
7788
  var import_zod8 = require("zod");
7535
7789
  var runCommand = createGadget({
7536
7790
  name: "RunCommand",
7537
- 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.",
7538
7792
  schema: import_zod8.z.object({
7539
- 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'])"),
7540
7794
  cwd: import_zod8.z.string().optional().describe("Working directory for the command (default: current directory)"),
7541
7795
  timeout: import_zod8.z.number().default(3e4).describe("Timeout in milliseconds (default: 30000)")
7542
7796
  }),
7543
7797
  examples: [
7544
7798
  {
7545
- params: { command: "ls -la", timeout: 3e4 },
7799
+ params: { argv: ["ls", "-la"], timeout: 3e4 },
7546
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",
7547
7801
  comment: "List directory contents with details"
7548
7802
  },
7549
7803
  {
7550
- params: { command: "echo 'Hello World'", timeout: 3e4 },
7804
+ params: { argv: ["echo", "Hello World"], timeout: 3e4 },
7551
7805
  output: "status=0\n\nHello World",
7552
- comment: "Simple echo command"
7806
+ comment: "Echo without shell - argument passed directly"
7553
7807
  },
7554
7808
  {
7555
- params: { command: "cat nonexistent.txt", timeout: 3e4 },
7809
+ params: { argv: ["cat", "nonexistent.txt"], timeout: 3e4 },
7556
7810
  output: "status=1\n\ncat: nonexistent.txt: No such file or directory",
7557
7811
  comment: "Command that fails returns non-zero status"
7558
7812
  },
7559
7813
  {
7560
- params: { command: "pwd", cwd: "/tmp", timeout: 3e4 },
7814
+ params: { argv: ["pwd"], cwd: "/tmp", timeout: 3e4 },
7561
7815
  output: "status=0\n\n/tmp",
7562
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"
7563
7822
  }
7564
7823
  ],
7565
- execute: async ({ command, cwd, timeout }) => {
7824
+ execute: async ({ argv, cwd, timeout }) => {
7566
7825
  const workingDir = cwd ?? process.cwd();
7826
+ if (argv.length === 0) {
7827
+ return "status=1\n\nerror: argv array cannot be empty";
7828
+ }
7567
7829
  try {
7568
- const proc = Bun.spawn(["sh", "-c", command], {
7830
+ const proc = Bun.spawn(argv, {
7569
7831
  cwd: workingDir,
7570
7832
  stdout: "pipe",
7571
7833
  stderr: "pipe"
@@ -10473,9 +10735,11 @@ ${issues}`);
10473
10735
  env.stderr.write(import_chalk7.default.dim("\nExecuting...\n"));
10474
10736
  const startTime = Date.now();
10475
10737
  let result;
10738
+ let cost;
10476
10739
  try {
10740
+ let rawResult;
10477
10741
  if (gadget.timeoutMs && gadget.timeoutMs > 0) {
10478
- result = await Promise.race([
10742
+ rawResult = await Promise.race([
10479
10743
  Promise.resolve(gadget.execute(params)),
10480
10744
  new Promise(
10481
10745
  (_, reject) => setTimeout(
@@ -10485,15 +10749,18 @@ ${issues}`);
10485
10749
  )
10486
10750
  ]);
10487
10751
  } else {
10488
- result = await Promise.resolve(gadget.execute(params));
10752
+ rawResult = await Promise.resolve(gadget.execute(params));
10489
10753
  }
10754
+ result = typeof rawResult === "string" ? rawResult : rawResult.result;
10755
+ cost = typeof rawResult === "object" ? rawResult.cost : void 0;
10490
10756
  } catch (error) {
10491
10757
  const message = error instanceof Error ? error.message : String(error);
10492
10758
  throw new Error(`Execution failed: ${message}`);
10493
10759
  }
10494
10760
  const elapsed = Date.now() - startTime;
10761
+ const costInfo = cost !== void 0 && cost > 0 ? ` (Cost: $${cost.toFixed(6)})` : "";
10495
10762
  env.stderr.write(import_chalk7.default.green(`
10496
- \u2713 Completed in ${elapsed}ms
10763
+ \u2713 Completed in ${elapsed}ms${costInfo}
10497
10764
 
10498
10765
  `));
10499
10766
  formatOutput(result, options, env.stdout);