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/index.cjs CHANGED
@@ -671,6 +671,44 @@ ${this.endPrefix}`
671
671
  }
672
672
  });
673
673
 
674
+ // src/gadgets/exceptions.ts
675
+ var BreakLoopException, HumanInputException, TimeoutException, AbortError;
676
+ var init_exceptions = __esm({
677
+ "src/gadgets/exceptions.ts"() {
678
+ "use strict";
679
+ BreakLoopException = class extends Error {
680
+ constructor(message) {
681
+ super(message ?? "Agent loop terminated by gadget");
682
+ this.name = "BreakLoopException";
683
+ }
684
+ };
685
+ HumanInputException = class extends Error {
686
+ question;
687
+ constructor(question) {
688
+ super(`Human input required: ${question}`);
689
+ this.name = "HumanInputException";
690
+ this.question = question;
691
+ }
692
+ };
693
+ TimeoutException = class extends Error {
694
+ timeoutMs;
695
+ gadgetName;
696
+ constructor(gadgetName, timeoutMs) {
697
+ super(`Gadget '${gadgetName}' execution exceeded timeout of ${timeoutMs}ms`);
698
+ this.name = "TimeoutException";
699
+ this.gadgetName = gadgetName;
700
+ this.timeoutMs = timeoutMs;
701
+ }
702
+ };
703
+ AbortError = class extends Error {
704
+ constructor(message) {
705
+ super(message || "Gadget execution was aborted");
706
+ this.name = "AbortError";
707
+ }
708
+ };
709
+ }
710
+ });
711
+
674
712
  // src/logging/logger.ts
675
713
  function parseLogLevel(value) {
676
714
  if (!value) {
@@ -943,6 +981,7 @@ var init_gadget = __esm({
943
981
  "src/gadgets/gadget.ts"() {
944
982
  "use strict";
945
983
  init_constants();
984
+ init_exceptions();
946
985
  init_schema_to_json();
947
986
  init_schema_validator();
948
987
  BaseGadget = class {
@@ -972,6 +1011,42 @@ var init_gadget = __esm({
972
1011
  * while maintaining runtime compatibility.
973
1012
  */
974
1013
  examples;
1014
+ /**
1015
+ * Throws an AbortError if the execution has been aborted.
1016
+ *
1017
+ * Call this at key checkpoints in long-running gadgets to allow early exit
1018
+ * when the gadget has been cancelled (e.g., due to timeout). This enables
1019
+ * resource cleanup and prevents unnecessary work after cancellation.
1020
+ *
1021
+ * @param ctx - The execution context containing the abort signal
1022
+ * @throws AbortError if ctx.signal.aborted is true
1023
+ *
1024
+ * @example
1025
+ * ```typescript
1026
+ * class DataProcessor extends Gadget({
1027
+ * description: 'Processes data in multiple steps',
1028
+ * schema: z.object({ items: z.array(z.string()) }),
1029
+ * }) {
1030
+ * async execute(params: this['params'], ctx?: ExecutionContext): Promise<string> {
1031
+ * const results: string[] = [];
1032
+ *
1033
+ * for (const item of params.items) {
1034
+ * // Check before each expensive operation
1035
+ * this.throwIfAborted(ctx);
1036
+ *
1037
+ * results.push(await this.processItem(item));
1038
+ * }
1039
+ *
1040
+ * return results.join(', ');
1041
+ * }
1042
+ * }
1043
+ * ```
1044
+ */
1045
+ throwIfAborted(ctx) {
1046
+ if (ctx?.signal?.aborted) {
1047
+ throw new AbortError();
1048
+ }
1049
+ }
975
1050
  /**
976
1051
  * Auto-generated instruction text for the LLM.
977
1052
  * Combines name, description, and parameter schema into a formatted instruction.
@@ -1039,8 +1114,8 @@ function createGadget(config) {
1039
1114
  parameterSchema = config.schema;
1040
1115
  timeoutMs = config.timeoutMs;
1041
1116
  examples = config.examples;
1042
- execute(params) {
1043
- return config.execute(params);
1117
+ execute(params, ctx) {
1118
+ return config.execute(params, ctx);
1044
1119
  }
1045
1120
  }
1046
1121
  return new DynamicGadget();
@@ -2376,6 +2451,162 @@ var init_block_params = __esm({
2376
2451
  }
2377
2452
  });
2378
2453
 
2454
+ // src/gadgets/cost-reporting-client.ts
2455
+ var CostReportingLLMistWrapper;
2456
+ var init_cost_reporting_client = __esm({
2457
+ "src/gadgets/cost-reporting-client.ts"() {
2458
+ "use strict";
2459
+ init_model_shortcuts();
2460
+ CostReportingLLMistWrapper = class {
2461
+ constructor(client, reportCost) {
2462
+ this.client = client;
2463
+ this.reportCost = reportCost;
2464
+ }
2465
+ /**
2466
+ * Access to model registry for cost estimation.
2467
+ */
2468
+ get modelRegistry() {
2469
+ return this.client.modelRegistry;
2470
+ }
2471
+ /**
2472
+ * Quick completion with automatic cost reporting.
2473
+ *
2474
+ * Streams internally to track token usage, then reports the calculated cost.
2475
+ *
2476
+ * @param prompt - User prompt
2477
+ * @param options - Optional configuration (model, temperature, etc.)
2478
+ * @returns Complete text response
2479
+ */
2480
+ async complete(prompt, options) {
2481
+ const model = resolveModel(options?.model ?? "haiku");
2482
+ let result = "";
2483
+ let inputTokens = 0;
2484
+ let outputTokens = 0;
2485
+ let cachedInputTokens = 0;
2486
+ let cacheCreationInputTokens = 0;
2487
+ const messages = [
2488
+ ...options?.systemPrompt ? [{ role: "system", content: options.systemPrompt }] : [],
2489
+ { role: "user", content: prompt }
2490
+ ];
2491
+ for await (const chunk of this.client.stream({
2492
+ model,
2493
+ messages,
2494
+ temperature: options?.temperature,
2495
+ maxTokens: options?.maxTokens
2496
+ })) {
2497
+ result += chunk.text ?? "";
2498
+ if (chunk.usage) {
2499
+ inputTokens = chunk.usage.inputTokens;
2500
+ outputTokens = chunk.usage.outputTokens;
2501
+ cachedInputTokens = chunk.usage.cachedInputTokens ?? 0;
2502
+ cacheCreationInputTokens = chunk.usage.cacheCreationInputTokens ?? 0;
2503
+ }
2504
+ }
2505
+ this.reportCostFromUsage(model, inputTokens, outputTokens, cachedInputTokens, cacheCreationInputTokens);
2506
+ return result;
2507
+ }
2508
+ /**
2509
+ * Quick streaming with automatic cost reporting when stream completes.
2510
+ *
2511
+ * Yields text chunks as they arrive, then reports cost in finally block.
2512
+ *
2513
+ * @param prompt - User prompt
2514
+ * @param options - Optional configuration (model, temperature, etc.)
2515
+ * @returns Async generator yielding text chunks
2516
+ */
2517
+ async *streamText(prompt, options) {
2518
+ const model = resolveModel(options?.model ?? "haiku");
2519
+ let inputTokens = 0;
2520
+ let outputTokens = 0;
2521
+ let cachedInputTokens = 0;
2522
+ let cacheCreationInputTokens = 0;
2523
+ const messages = [
2524
+ ...options?.systemPrompt ? [{ role: "system", content: options.systemPrompt }] : [],
2525
+ { role: "user", content: prompt }
2526
+ ];
2527
+ try {
2528
+ for await (const chunk of this.client.stream({
2529
+ model,
2530
+ messages,
2531
+ temperature: options?.temperature,
2532
+ maxTokens: options?.maxTokens
2533
+ })) {
2534
+ if (chunk.text) {
2535
+ yield chunk.text;
2536
+ }
2537
+ if (chunk.usage) {
2538
+ inputTokens = chunk.usage.inputTokens;
2539
+ outputTokens = chunk.usage.outputTokens;
2540
+ cachedInputTokens = chunk.usage.cachedInputTokens ?? 0;
2541
+ cacheCreationInputTokens = chunk.usage.cacheCreationInputTokens ?? 0;
2542
+ }
2543
+ }
2544
+ } finally {
2545
+ this.reportCostFromUsage(model, inputTokens, outputTokens, cachedInputTokens, cacheCreationInputTokens);
2546
+ }
2547
+ }
2548
+ /**
2549
+ * Low-level stream access with automatic cost reporting.
2550
+ *
2551
+ * Returns a wrapped stream that reports costs when iteration completes.
2552
+ *
2553
+ * @param options - Full LLM generation options
2554
+ * @returns Wrapped LLM stream that auto-reports costs
2555
+ */
2556
+ stream(options) {
2557
+ return this.createCostReportingStream(options);
2558
+ }
2559
+ /**
2560
+ * Creates a wrapped stream that tracks usage and reports costs on completion.
2561
+ */
2562
+ createCostReportingStream(options) {
2563
+ const innerStream = this.client.stream(options);
2564
+ const reportCostFromUsage = this.reportCostFromUsage.bind(this);
2565
+ const model = options.model;
2566
+ async function* costReportingWrapper() {
2567
+ let inputTokens = 0;
2568
+ let outputTokens = 0;
2569
+ let cachedInputTokens = 0;
2570
+ let cacheCreationInputTokens = 0;
2571
+ try {
2572
+ for await (const chunk of innerStream) {
2573
+ if (chunk.usage) {
2574
+ inputTokens = chunk.usage.inputTokens;
2575
+ outputTokens = chunk.usage.outputTokens;
2576
+ cachedInputTokens = chunk.usage.cachedInputTokens ?? 0;
2577
+ cacheCreationInputTokens = chunk.usage.cacheCreationInputTokens ?? 0;
2578
+ }
2579
+ yield chunk;
2580
+ }
2581
+ } finally {
2582
+ if (inputTokens > 0 || outputTokens > 0) {
2583
+ reportCostFromUsage(model, inputTokens, outputTokens, cachedInputTokens, cacheCreationInputTokens);
2584
+ }
2585
+ }
2586
+ }
2587
+ return costReportingWrapper();
2588
+ }
2589
+ /**
2590
+ * Calculates and reports cost from token usage.
2591
+ */
2592
+ reportCostFromUsage(model, inputTokens, outputTokens, cachedInputTokens = 0, cacheCreationInputTokens = 0) {
2593
+ if (inputTokens === 0 && outputTokens === 0) return;
2594
+ const modelName = model.includes(":") ? model.split(":")[1] : model;
2595
+ const estimate = this.client.modelRegistry.estimateCost(
2596
+ modelName,
2597
+ inputTokens,
2598
+ outputTokens,
2599
+ cachedInputTokens,
2600
+ cacheCreationInputTokens
2601
+ );
2602
+ if (estimate && estimate.totalCost > 0) {
2603
+ this.reportCost(estimate.totalCost);
2604
+ }
2605
+ }
2606
+ };
2607
+ }
2608
+ });
2609
+
2379
2610
  // src/gadgets/error-formatter.ts
2380
2611
  var GadgetErrorFormatter;
2381
2612
  var init_error_formatter = __esm({
@@ -2459,38 +2690,6 @@ var init_error_formatter = __esm({
2459
2690
  }
2460
2691
  });
2461
2692
 
2462
- // src/gadgets/exceptions.ts
2463
- var BreakLoopException, HumanInputException, TimeoutException;
2464
- var init_exceptions = __esm({
2465
- "src/gadgets/exceptions.ts"() {
2466
- "use strict";
2467
- BreakLoopException = class extends Error {
2468
- constructor(message) {
2469
- super(message ?? "Agent loop terminated by gadget");
2470
- this.name = "BreakLoopException";
2471
- }
2472
- };
2473
- HumanInputException = class extends Error {
2474
- question;
2475
- constructor(question) {
2476
- super(`Human input required: ${question}`);
2477
- this.name = "HumanInputException";
2478
- this.question = question;
2479
- }
2480
- };
2481
- TimeoutException = class extends Error {
2482
- timeoutMs;
2483
- gadgetName;
2484
- constructor(gadgetName, timeoutMs) {
2485
- super(`Gadget '${gadgetName}' execution exceeded timeout of ${timeoutMs}ms`);
2486
- this.name = "TimeoutException";
2487
- this.gadgetName = gadgetName;
2488
- this.timeoutMs = timeoutMs;
2489
- }
2490
- };
2491
- }
2492
- });
2493
-
2494
2693
  // src/gadgets/parser.ts
2495
2694
  function stripMarkdownFences(content) {
2496
2695
  let cleaned = content.trim();
@@ -2676,14 +2875,16 @@ var init_executor = __esm({
2676
2875
  init_constants();
2677
2876
  init_logger();
2678
2877
  init_block_params();
2878
+ init_cost_reporting_client();
2679
2879
  init_error_formatter();
2680
2880
  init_exceptions();
2681
2881
  init_parser();
2682
2882
  GadgetExecutor = class {
2683
- constructor(registry, onHumanInputRequired, logger, defaultGadgetTimeoutMs, errorFormatterOptions) {
2883
+ constructor(registry, onHumanInputRequired, logger, defaultGadgetTimeoutMs, errorFormatterOptions, client) {
2684
2884
  this.registry = registry;
2685
2885
  this.onHumanInputRequired = onHumanInputRequired;
2686
2886
  this.defaultGadgetTimeoutMs = defaultGadgetTimeoutMs;
2887
+ this.client = client;
2687
2888
  this.logger = logger ?? createLogger({ name: "llmist:executor" });
2688
2889
  this.errorFormatter = new GadgetErrorFormatter(errorFormatterOptions);
2689
2890
  this.argPrefix = errorFormatterOptions?.argPrefix ?? GADGET_ARG_PREFIX;
@@ -2693,14 +2894,27 @@ var init_executor = __esm({
2693
2894
  argPrefix;
2694
2895
  /**
2695
2896
  * Creates a promise that rejects with a TimeoutException after the specified timeout.
2897
+ * Aborts the provided AbortController before rejecting, allowing gadgets to clean up.
2696
2898
  */
2697
- createTimeoutPromise(gadgetName, timeoutMs) {
2899
+ createTimeoutPromise(gadgetName, timeoutMs, abortController) {
2698
2900
  return new Promise((_, reject) => {
2699
2901
  setTimeout(() => {
2700
- reject(new TimeoutException(gadgetName, timeoutMs));
2902
+ const timeoutError = new TimeoutException(gadgetName, timeoutMs);
2903
+ abortController.abort(timeoutError.message);
2904
+ reject(timeoutError);
2701
2905
  }, timeoutMs);
2702
2906
  });
2703
2907
  }
2908
+ /**
2909
+ * Normalizes gadget execute result to consistent format.
2910
+ * Handles both string returns (backwards compat) and object returns with cost.
2911
+ */
2912
+ normalizeExecuteResult(raw) {
2913
+ if (typeof raw === "string") {
2914
+ return { result: raw, cost: 0 };
2915
+ }
2916
+ return { result: raw.result, cost: raw.cost ?? 0 };
2917
+ }
2704
2918
  // Execute a gadget call asynchronously
2705
2919
  async execute(call) {
2706
2920
  const startTime = Date.now();
@@ -2795,30 +3009,53 @@ var init_executor = __esm({
2795
3009
  validatedParameters = schemaAwareParameters;
2796
3010
  }
2797
3011
  const timeoutMs = gadget.timeoutMs ?? this.defaultGadgetTimeoutMs;
2798
- let result;
3012
+ const abortController = new AbortController();
3013
+ let callbackCost = 0;
3014
+ const reportCost = (amount) => {
3015
+ if (amount > 0) {
3016
+ callbackCost += amount;
3017
+ this.logger.debug("Gadget reported cost via callback", {
3018
+ gadgetName: call.gadgetName,
3019
+ amount,
3020
+ totalCallbackCost: callbackCost
3021
+ });
3022
+ }
3023
+ };
3024
+ const ctx = {
3025
+ reportCost,
3026
+ llmist: this.client ? new CostReportingLLMistWrapper(this.client, reportCost) : void 0,
3027
+ signal: abortController.signal
3028
+ };
3029
+ let rawResult;
2799
3030
  if (timeoutMs && timeoutMs > 0) {
2800
3031
  this.logger.debug("Executing gadget with timeout", {
2801
3032
  gadgetName: call.gadgetName,
2802
3033
  timeoutMs
2803
3034
  });
2804
- result = await Promise.race([
2805
- Promise.resolve(gadget.execute(validatedParameters)),
2806
- this.createTimeoutPromise(call.gadgetName, timeoutMs)
3035
+ rawResult = await Promise.race([
3036
+ Promise.resolve(gadget.execute(validatedParameters, ctx)),
3037
+ this.createTimeoutPromise(call.gadgetName, timeoutMs, abortController)
2807
3038
  ]);
2808
3039
  } else {
2809
- result = await Promise.resolve(gadget.execute(validatedParameters));
3040
+ rawResult = await Promise.resolve(gadget.execute(validatedParameters, ctx));
2810
3041
  }
3042
+ const { result, cost: returnCost } = this.normalizeExecuteResult(rawResult);
3043
+ const totalCost = callbackCost + returnCost;
2811
3044
  const executionTimeMs = Date.now() - startTime;
2812
3045
  this.logger.info("Gadget executed successfully", {
2813
3046
  gadgetName: call.gadgetName,
2814
3047
  invocationId: call.invocationId,
2815
- executionTimeMs
3048
+ executionTimeMs,
3049
+ cost: totalCost > 0 ? totalCost : void 0,
3050
+ callbackCost: callbackCost > 0 ? callbackCost : void 0,
3051
+ returnCost: returnCost > 0 ? returnCost : void 0
2816
3052
  });
2817
3053
  this.logger.debug("Gadget result", {
2818
3054
  gadgetName: call.gadgetName,
2819
3055
  invocationId: call.invocationId,
2820
3056
  parameters: validatedParameters,
2821
3057
  result,
3058
+ cost: totalCost,
2822
3059
  executionTimeMs
2823
3060
  });
2824
3061
  return {
@@ -2826,7 +3063,8 @@ var init_executor = __esm({
2826
3063
  invocationId: call.invocationId,
2827
3064
  parameters: validatedParameters,
2828
3065
  result,
2829
- executionTimeMs
3066
+ executionTimeMs,
3067
+ cost: totalCost
2830
3068
  };
2831
3069
  } catch (error) {
2832
3070
  if (error instanceof BreakLoopException) {
@@ -2857,6 +3095,19 @@ var init_executor = __esm({
2857
3095
  executionTimeMs: Date.now() - startTime
2858
3096
  };
2859
3097
  }
3098
+ if (error instanceof AbortError) {
3099
+ this.logger.info("Gadget execution was aborted", {
3100
+ gadgetName: call.gadgetName,
3101
+ executionTimeMs: Date.now() - startTime
3102
+ });
3103
+ return {
3104
+ gadgetName: call.gadgetName,
3105
+ invocationId: call.invocationId,
3106
+ parameters: validatedParameters,
3107
+ error: error.message,
3108
+ executionTimeMs: Date.now() - startTime
3109
+ };
3110
+ }
2860
3111
  if (error instanceof HumanInputException) {
2861
3112
  this.logger.info("Gadget requested human input", {
2862
3113
  gadgetName: call.gadgetName,
@@ -2983,7 +3234,8 @@ var init_stream_processor = __esm({
2983
3234
  options.onHumanInputRequired,
2984
3235
  this.logger.getSubLogger({ name: "executor" }),
2985
3236
  options.defaultGadgetTimeoutMs,
2986
- { argPrefix: options.gadgetArgPrefix }
3237
+ { argPrefix: options.gadgetArgPrefix },
3238
+ options.client
2987
3239
  );
2988
3240
  }
2989
3241
  /**
@@ -3250,6 +3502,7 @@ var init_stream_processor = __esm({
3250
3502
  error: result.error,
3251
3503
  executionTimeMs: result.executionTimeMs,
3252
3504
  breaksLoop: result.breaksLoop,
3505
+ cost: result.cost,
3253
3506
  logger: this.logger
3254
3507
  };
3255
3508
  await this.hooks.observers.onGadgetExecutionComplete(context);
@@ -3626,7 +3879,8 @@ var init_agent = __esm({
3626
3879
  onHumanInputRequired: this.onHumanInputRequired,
3627
3880
  stopOnGadgetError: this.stopOnGadgetError,
3628
3881
  shouldContinueAfterError: this.shouldContinueAfterError,
3629
- defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs
3882
+ defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
3883
+ client: this.client
3630
3884
  });
3631
3885
  const result = await processor.process(stream2);
3632
3886
  for (const output of result.outputs) {
@@ -6672,6 +6926,7 @@ ${endPrefix}`
6672
6926
  // src/index.ts
6673
6927
  var index_exports = {};
6674
6928
  __export(index_exports, {
6929
+ AbortError: () => AbortError,
6675
6930
  AgentBuilder: () => AgentBuilder,
6676
6931
  AnthropicMessagesProvider: () => AnthropicMessagesProvider,
6677
6932
  BaseGadget: () => BaseGadget,
@@ -7109,6 +7364,7 @@ var HookPresets = class _HookPresets {
7109
7364
  let totalInputTokens = 0;
7110
7365
  let totalOutputTokens = 0;
7111
7366
  let totalCost = 0;
7367
+ let totalGadgetCost = 0;
7112
7368
  const startTime = Date.now();
7113
7369
  return {
7114
7370
  observers: {
@@ -7146,7 +7402,7 @@ var HookPresets = class _HookPresets {
7146
7402
  totalInputTokens,
7147
7403
  totalOutputTokens,
7148
7404
  totalTokens: totalInputTokens + totalOutputTokens,
7149
- totalCost,
7405
+ totalCost: totalCost + totalGadgetCost,
7150
7406
  elapsedSeconds: Number(((Date.now() - startTime) / 1e3).toFixed(1))
7151
7407
  };
7152
7408
  if (onProgress) {
@@ -7159,6 +7415,12 @@ var HookPresets = class _HookPresets {
7159
7415
  `\u{1F4CA} Progress: Iteration #${stats.currentIteration} | ${formattedTokens} tokens | ${formattedCost} | ${stats.elapsedSeconds}s`
7160
7416
  );
7161
7417
  }
7418
+ },
7419
+ // Track gadget execution costs
7420
+ onGadgetExecutionComplete: async (ctx) => {
7421
+ if (ctx.cost && ctx.cost > 0) {
7422
+ totalGadgetCost += ctx.cost;
7423
+ }
7162
7424
  }
7163
7425
  }
7164
7426
  };
@@ -8402,6 +8664,7 @@ init_gadget();
8402
8664
  var import_node_stream = require("stream");
8403
8665
  // Annotate the CommonJS export names for ESM import in node:
8404
8666
  0 && (module.exports = {
8667
+ AbortError,
8405
8668
  AgentBuilder,
8406
8669
  AnthropicMessagesProvider,
8407
8670
  BaseGadget,