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.
@@ -722,6 +722,44 @@ ${this.endPrefix}`
722
722
  }
723
723
  });
724
724
 
725
+ // src/gadgets/exceptions.ts
726
+ var BreakLoopException, HumanInputException, TimeoutException, AbortError;
727
+ var init_exceptions = __esm({
728
+ "src/gadgets/exceptions.ts"() {
729
+ "use strict";
730
+ BreakLoopException = class extends Error {
731
+ constructor(message) {
732
+ super(message ?? "Agent loop terminated by gadget");
733
+ this.name = "BreakLoopException";
734
+ }
735
+ };
736
+ HumanInputException = class extends Error {
737
+ question;
738
+ constructor(question) {
739
+ super(`Human input required: ${question}`);
740
+ this.name = "HumanInputException";
741
+ this.question = question;
742
+ }
743
+ };
744
+ TimeoutException = class extends Error {
745
+ timeoutMs;
746
+ gadgetName;
747
+ constructor(gadgetName, timeoutMs) {
748
+ super(`Gadget '${gadgetName}' execution exceeded timeout of ${timeoutMs}ms`);
749
+ this.name = "TimeoutException";
750
+ this.gadgetName = gadgetName;
751
+ this.timeoutMs = timeoutMs;
752
+ }
753
+ };
754
+ AbortError = class extends Error {
755
+ constructor(message) {
756
+ super(message || "Gadget execution was aborted");
757
+ this.name = "AbortError";
758
+ }
759
+ };
760
+ }
761
+ });
762
+
725
763
  // src/gadgets/schema-to-json.ts
726
764
  function schemaToJSONSchema(schema, options) {
727
765
  const jsonSchema = z2.toJSONSchema(schema, options ?? { target: "draft-7" });
@@ -915,6 +953,7 @@ var init_gadget = __esm({
915
953
  "src/gadgets/gadget.ts"() {
916
954
  "use strict";
917
955
  init_constants();
956
+ init_exceptions();
918
957
  init_schema_to_json();
919
958
  init_schema_validator();
920
959
  BaseGadget = class {
@@ -944,6 +983,42 @@ var init_gadget = __esm({
944
983
  * while maintaining runtime compatibility.
945
984
  */
946
985
  examples;
986
+ /**
987
+ * Throws an AbortError if the execution has been aborted.
988
+ *
989
+ * Call this at key checkpoints in long-running gadgets to allow early exit
990
+ * when the gadget has been cancelled (e.g., due to timeout). This enables
991
+ * resource cleanup and prevents unnecessary work after cancellation.
992
+ *
993
+ * @param ctx - The execution context containing the abort signal
994
+ * @throws AbortError if ctx.signal.aborted is true
995
+ *
996
+ * @example
997
+ * ```typescript
998
+ * class DataProcessor extends Gadget({
999
+ * description: 'Processes data in multiple steps',
1000
+ * schema: z.object({ items: z.array(z.string()) }),
1001
+ * }) {
1002
+ * async execute(params: this['params'], ctx?: ExecutionContext): Promise<string> {
1003
+ * const results: string[] = [];
1004
+ *
1005
+ * for (const item of params.items) {
1006
+ * // Check before each expensive operation
1007
+ * this.throwIfAborted(ctx);
1008
+ *
1009
+ * results.push(await this.processItem(item));
1010
+ * }
1011
+ *
1012
+ * return results.join(', ');
1013
+ * }
1014
+ * }
1015
+ * ```
1016
+ */
1017
+ throwIfAborted(ctx) {
1018
+ if (ctx?.signal?.aborted) {
1019
+ throw new AbortError();
1020
+ }
1021
+ }
947
1022
  /**
948
1023
  * Auto-generated instruction text for the LLM.
949
1024
  * Combines name, description, and parameter schema into a formatted instruction.
@@ -1011,8 +1086,8 @@ function createGadget(config) {
1011
1086
  parameterSchema = config.schema;
1012
1087
  timeoutMs = config.timeoutMs;
1013
1088
  examples = config.examples;
1014
- execute(params) {
1015
- return config.execute(params);
1089
+ execute(params, ctx) {
1090
+ return config.execute(params, ctx);
1016
1091
  }
1017
1092
  }
1018
1093
  return new DynamicGadget();
@@ -2318,6 +2393,162 @@ var init_block_params = __esm({
2318
2393
  }
2319
2394
  });
2320
2395
 
2396
+ // src/gadgets/cost-reporting-client.ts
2397
+ var CostReportingLLMistWrapper;
2398
+ var init_cost_reporting_client = __esm({
2399
+ "src/gadgets/cost-reporting-client.ts"() {
2400
+ "use strict";
2401
+ init_model_shortcuts();
2402
+ CostReportingLLMistWrapper = class {
2403
+ constructor(client, reportCost) {
2404
+ this.client = client;
2405
+ this.reportCost = reportCost;
2406
+ }
2407
+ /**
2408
+ * Access to model registry for cost estimation.
2409
+ */
2410
+ get modelRegistry() {
2411
+ return this.client.modelRegistry;
2412
+ }
2413
+ /**
2414
+ * Quick completion with automatic cost reporting.
2415
+ *
2416
+ * Streams internally to track token usage, then reports the calculated cost.
2417
+ *
2418
+ * @param prompt - User prompt
2419
+ * @param options - Optional configuration (model, temperature, etc.)
2420
+ * @returns Complete text response
2421
+ */
2422
+ async complete(prompt, options) {
2423
+ const model = resolveModel(options?.model ?? "haiku");
2424
+ let result = "";
2425
+ let inputTokens = 0;
2426
+ let outputTokens = 0;
2427
+ let cachedInputTokens = 0;
2428
+ let cacheCreationInputTokens = 0;
2429
+ const messages = [
2430
+ ...options?.systemPrompt ? [{ role: "system", content: options.systemPrompt }] : [],
2431
+ { role: "user", content: prompt }
2432
+ ];
2433
+ for await (const chunk of this.client.stream({
2434
+ model,
2435
+ messages,
2436
+ temperature: options?.temperature,
2437
+ maxTokens: options?.maxTokens
2438
+ })) {
2439
+ result += chunk.text ?? "";
2440
+ if (chunk.usage) {
2441
+ inputTokens = chunk.usage.inputTokens;
2442
+ outputTokens = chunk.usage.outputTokens;
2443
+ cachedInputTokens = chunk.usage.cachedInputTokens ?? 0;
2444
+ cacheCreationInputTokens = chunk.usage.cacheCreationInputTokens ?? 0;
2445
+ }
2446
+ }
2447
+ this.reportCostFromUsage(model, inputTokens, outputTokens, cachedInputTokens, cacheCreationInputTokens);
2448
+ return result;
2449
+ }
2450
+ /**
2451
+ * Quick streaming with automatic cost reporting when stream completes.
2452
+ *
2453
+ * Yields text chunks as they arrive, then reports cost in finally block.
2454
+ *
2455
+ * @param prompt - User prompt
2456
+ * @param options - Optional configuration (model, temperature, etc.)
2457
+ * @returns Async generator yielding text chunks
2458
+ */
2459
+ async *streamText(prompt, options) {
2460
+ const model = resolveModel(options?.model ?? "haiku");
2461
+ let inputTokens = 0;
2462
+ let outputTokens = 0;
2463
+ let cachedInputTokens = 0;
2464
+ let cacheCreationInputTokens = 0;
2465
+ const messages = [
2466
+ ...options?.systemPrompt ? [{ role: "system", content: options.systemPrompt }] : [],
2467
+ { role: "user", content: prompt }
2468
+ ];
2469
+ try {
2470
+ for await (const chunk of this.client.stream({
2471
+ model,
2472
+ messages,
2473
+ temperature: options?.temperature,
2474
+ maxTokens: options?.maxTokens
2475
+ })) {
2476
+ if (chunk.text) {
2477
+ yield chunk.text;
2478
+ }
2479
+ if (chunk.usage) {
2480
+ inputTokens = chunk.usage.inputTokens;
2481
+ outputTokens = chunk.usage.outputTokens;
2482
+ cachedInputTokens = chunk.usage.cachedInputTokens ?? 0;
2483
+ cacheCreationInputTokens = chunk.usage.cacheCreationInputTokens ?? 0;
2484
+ }
2485
+ }
2486
+ } finally {
2487
+ this.reportCostFromUsage(model, inputTokens, outputTokens, cachedInputTokens, cacheCreationInputTokens);
2488
+ }
2489
+ }
2490
+ /**
2491
+ * Low-level stream access with automatic cost reporting.
2492
+ *
2493
+ * Returns a wrapped stream that reports costs when iteration completes.
2494
+ *
2495
+ * @param options - Full LLM generation options
2496
+ * @returns Wrapped LLM stream that auto-reports costs
2497
+ */
2498
+ stream(options) {
2499
+ return this.createCostReportingStream(options);
2500
+ }
2501
+ /**
2502
+ * Creates a wrapped stream that tracks usage and reports costs on completion.
2503
+ */
2504
+ createCostReportingStream(options) {
2505
+ const innerStream = this.client.stream(options);
2506
+ const reportCostFromUsage = this.reportCostFromUsage.bind(this);
2507
+ const model = options.model;
2508
+ async function* costReportingWrapper() {
2509
+ let inputTokens = 0;
2510
+ let outputTokens = 0;
2511
+ let cachedInputTokens = 0;
2512
+ let cacheCreationInputTokens = 0;
2513
+ try {
2514
+ for await (const chunk of innerStream) {
2515
+ if (chunk.usage) {
2516
+ inputTokens = chunk.usage.inputTokens;
2517
+ outputTokens = chunk.usage.outputTokens;
2518
+ cachedInputTokens = chunk.usage.cachedInputTokens ?? 0;
2519
+ cacheCreationInputTokens = chunk.usage.cacheCreationInputTokens ?? 0;
2520
+ }
2521
+ yield chunk;
2522
+ }
2523
+ } finally {
2524
+ if (inputTokens > 0 || outputTokens > 0) {
2525
+ reportCostFromUsage(model, inputTokens, outputTokens, cachedInputTokens, cacheCreationInputTokens);
2526
+ }
2527
+ }
2528
+ }
2529
+ return costReportingWrapper();
2530
+ }
2531
+ /**
2532
+ * Calculates and reports cost from token usage.
2533
+ */
2534
+ reportCostFromUsage(model, inputTokens, outputTokens, cachedInputTokens = 0, cacheCreationInputTokens = 0) {
2535
+ if (inputTokens === 0 && outputTokens === 0) return;
2536
+ const modelName = model.includes(":") ? model.split(":")[1] : model;
2537
+ const estimate = this.client.modelRegistry.estimateCost(
2538
+ modelName,
2539
+ inputTokens,
2540
+ outputTokens,
2541
+ cachedInputTokens,
2542
+ cacheCreationInputTokens
2543
+ );
2544
+ if (estimate && estimate.totalCost > 0) {
2545
+ this.reportCost(estimate.totalCost);
2546
+ }
2547
+ }
2548
+ };
2549
+ }
2550
+ });
2551
+
2321
2552
  // src/gadgets/error-formatter.ts
2322
2553
  var GadgetErrorFormatter;
2323
2554
  var init_error_formatter = __esm({
@@ -2401,38 +2632,6 @@ var init_error_formatter = __esm({
2401
2632
  }
2402
2633
  });
2403
2634
 
2404
- // src/gadgets/exceptions.ts
2405
- var BreakLoopException, HumanInputException, TimeoutException;
2406
- var init_exceptions = __esm({
2407
- "src/gadgets/exceptions.ts"() {
2408
- "use strict";
2409
- BreakLoopException = class extends Error {
2410
- constructor(message) {
2411
- super(message ?? "Agent loop terminated by gadget");
2412
- this.name = "BreakLoopException";
2413
- }
2414
- };
2415
- HumanInputException = class extends Error {
2416
- question;
2417
- constructor(question) {
2418
- super(`Human input required: ${question}`);
2419
- this.name = "HumanInputException";
2420
- this.question = question;
2421
- }
2422
- };
2423
- TimeoutException = class extends Error {
2424
- timeoutMs;
2425
- gadgetName;
2426
- constructor(gadgetName, timeoutMs) {
2427
- super(`Gadget '${gadgetName}' execution exceeded timeout of ${timeoutMs}ms`);
2428
- this.name = "TimeoutException";
2429
- this.gadgetName = gadgetName;
2430
- this.timeoutMs = timeoutMs;
2431
- }
2432
- };
2433
- }
2434
- });
2435
-
2436
2635
  // src/gadgets/parser.ts
2437
2636
  function stripMarkdownFences(content) {
2438
2637
  let cleaned = content.trim();
@@ -2618,14 +2817,16 @@ var init_executor = __esm({
2618
2817
  init_constants();
2619
2818
  init_logger();
2620
2819
  init_block_params();
2820
+ init_cost_reporting_client();
2621
2821
  init_error_formatter();
2622
2822
  init_exceptions();
2623
2823
  init_parser();
2624
2824
  GadgetExecutor = class {
2625
- constructor(registry, onHumanInputRequired, logger, defaultGadgetTimeoutMs, errorFormatterOptions) {
2825
+ constructor(registry, onHumanInputRequired, logger, defaultGadgetTimeoutMs, errorFormatterOptions, client) {
2626
2826
  this.registry = registry;
2627
2827
  this.onHumanInputRequired = onHumanInputRequired;
2628
2828
  this.defaultGadgetTimeoutMs = defaultGadgetTimeoutMs;
2829
+ this.client = client;
2629
2830
  this.logger = logger ?? createLogger({ name: "llmist:executor" });
2630
2831
  this.errorFormatter = new GadgetErrorFormatter(errorFormatterOptions);
2631
2832
  this.argPrefix = errorFormatterOptions?.argPrefix ?? GADGET_ARG_PREFIX;
@@ -2635,14 +2836,27 @@ var init_executor = __esm({
2635
2836
  argPrefix;
2636
2837
  /**
2637
2838
  * Creates a promise that rejects with a TimeoutException after the specified timeout.
2839
+ * Aborts the provided AbortController before rejecting, allowing gadgets to clean up.
2638
2840
  */
2639
- createTimeoutPromise(gadgetName, timeoutMs) {
2841
+ createTimeoutPromise(gadgetName, timeoutMs, abortController) {
2640
2842
  return new Promise((_, reject) => {
2641
2843
  setTimeout(() => {
2642
- reject(new TimeoutException(gadgetName, timeoutMs));
2844
+ const timeoutError = new TimeoutException(gadgetName, timeoutMs);
2845
+ abortController.abort(timeoutError.message);
2846
+ reject(timeoutError);
2643
2847
  }, timeoutMs);
2644
2848
  });
2645
2849
  }
2850
+ /**
2851
+ * Normalizes gadget execute result to consistent format.
2852
+ * Handles both string returns (backwards compat) and object returns with cost.
2853
+ */
2854
+ normalizeExecuteResult(raw) {
2855
+ if (typeof raw === "string") {
2856
+ return { result: raw, cost: 0 };
2857
+ }
2858
+ return { result: raw.result, cost: raw.cost ?? 0 };
2859
+ }
2646
2860
  // Execute a gadget call asynchronously
2647
2861
  async execute(call) {
2648
2862
  const startTime = Date.now();
@@ -2737,30 +2951,53 @@ var init_executor = __esm({
2737
2951
  validatedParameters = schemaAwareParameters;
2738
2952
  }
2739
2953
  const timeoutMs = gadget.timeoutMs ?? this.defaultGadgetTimeoutMs;
2740
- let result;
2954
+ const abortController = new AbortController();
2955
+ let callbackCost = 0;
2956
+ const reportCost = (amount) => {
2957
+ if (amount > 0) {
2958
+ callbackCost += amount;
2959
+ this.logger.debug("Gadget reported cost via callback", {
2960
+ gadgetName: call.gadgetName,
2961
+ amount,
2962
+ totalCallbackCost: callbackCost
2963
+ });
2964
+ }
2965
+ };
2966
+ const ctx = {
2967
+ reportCost,
2968
+ llmist: this.client ? new CostReportingLLMistWrapper(this.client, reportCost) : void 0,
2969
+ signal: abortController.signal
2970
+ };
2971
+ let rawResult;
2741
2972
  if (timeoutMs && timeoutMs > 0) {
2742
2973
  this.logger.debug("Executing gadget with timeout", {
2743
2974
  gadgetName: call.gadgetName,
2744
2975
  timeoutMs
2745
2976
  });
2746
- result = await Promise.race([
2747
- Promise.resolve(gadget.execute(validatedParameters)),
2748
- this.createTimeoutPromise(call.gadgetName, timeoutMs)
2977
+ rawResult = await Promise.race([
2978
+ Promise.resolve(gadget.execute(validatedParameters, ctx)),
2979
+ this.createTimeoutPromise(call.gadgetName, timeoutMs, abortController)
2749
2980
  ]);
2750
2981
  } else {
2751
- result = await Promise.resolve(gadget.execute(validatedParameters));
2982
+ rawResult = await Promise.resolve(gadget.execute(validatedParameters, ctx));
2752
2983
  }
2984
+ const { result, cost: returnCost } = this.normalizeExecuteResult(rawResult);
2985
+ const totalCost = callbackCost + returnCost;
2753
2986
  const executionTimeMs = Date.now() - startTime;
2754
2987
  this.logger.info("Gadget executed successfully", {
2755
2988
  gadgetName: call.gadgetName,
2756
2989
  invocationId: call.invocationId,
2757
- executionTimeMs
2990
+ executionTimeMs,
2991
+ cost: totalCost > 0 ? totalCost : void 0,
2992
+ callbackCost: callbackCost > 0 ? callbackCost : void 0,
2993
+ returnCost: returnCost > 0 ? returnCost : void 0
2758
2994
  });
2759
2995
  this.logger.debug("Gadget result", {
2760
2996
  gadgetName: call.gadgetName,
2761
2997
  invocationId: call.invocationId,
2762
2998
  parameters: validatedParameters,
2763
2999
  result,
3000
+ cost: totalCost,
2764
3001
  executionTimeMs
2765
3002
  });
2766
3003
  return {
@@ -2768,7 +3005,8 @@ var init_executor = __esm({
2768
3005
  invocationId: call.invocationId,
2769
3006
  parameters: validatedParameters,
2770
3007
  result,
2771
- executionTimeMs
3008
+ executionTimeMs,
3009
+ cost: totalCost
2772
3010
  };
2773
3011
  } catch (error) {
2774
3012
  if (error instanceof BreakLoopException) {
@@ -2799,6 +3037,19 @@ var init_executor = __esm({
2799
3037
  executionTimeMs: Date.now() - startTime
2800
3038
  };
2801
3039
  }
3040
+ if (error instanceof AbortError) {
3041
+ this.logger.info("Gadget execution was aborted", {
3042
+ gadgetName: call.gadgetName,
3043
+ executionTimeMs: Date.now() - startTime
3044
+ });
3045
+ return {
3046
+ gadgetName: call.gadgetName,
3047
+ invocationId: call.invocationId,
3048
+ parameters: validatedParameters,
3049
+ error: error.message,
3050
+ executionTimeMs: Date.now() - startTime
3051
+ };
3052
+ }
2802
3053
  if (error instanceof HumanInputException) {
2803
3054
  this.logger.info("Gadget requested human input", {
2804
3055
  gadgetName: call.gadgetName,
@@ -2925,7 +3176,8 @@ var init_stream_processor = __esm({
2925
3176
  options.onHumanInputRequired,
2926
3177
  this.logger.getSubLogger({ name: "executor" }),
2927
3178
  options.defaultGadgetTimeoutMs,
2928
- { argPrefix: options.gadgetArgPrefix }
3179
+ { argPrefix: options.gadgetArgPrefix },
3180
+ options.client
2929
3181
  );
2930
3182
  }
2931
3183
  /**
@@ -3192,6 +3444,7 @@ var init_stream_processor = __esm({
3192
3444
  error: result.error,
3193
3445
  executionTimeMs: result.executionTimeMs,
3194
3446
  breaksLoop: result.breaksLoop,
3447
+ cost: result.cost,
3195
3448
  logger: this.logger
3196
3449
  };
3197
3450
  await this.hooks.observers.onGadgetExecutionComplete(context);
@@ -3568,7 +3821,8 @@ var init_agent = __esm({
3568
3821
  onHumanInputRequired: this.onHumanInputRequired,
3569
3822
  stopOnGadgetError: this.stopOnGadgetError,
3570
3823
  shouldContinueAfterError: this.shouldContinueAfterError,
3571
- defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs
3824
+ defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
3825
+ client: this.client
3572
3826
  });
3573
3827
  const result = await processor.process(stream2);
3574
3828
  for (const output of result.outputs) {
@@ -6700,10 +6954,18 @@ async function testGadget(gadget, params, options) {
6700
6954
  validatedParams = validationResult.data;
6701
6955
  }
6702
6956
  try {
6703
- const result = await Promise.resolve(gadget.execute(validatedParams));
6957
+ const rawResult = await Promise.resolve(gadget.execute(validatedParams));
6958
+ if (typeof rawResult === "string") {
6959
+ return {
6960
+ result: rawResult,
6961
+ validatedParams,
6962
+ cost: 0
6963
+ };
6964
+ }
6704
6965
  return {
6705
- result,
6706
- validatedParams
6966
+ result: rawResult.result,
6967
+ validatedParams,
6968
+ cost: rawResult.cost ?? 0
6707
6969
  };
6708
6970
  } catch (error) {
6709
6971
  return {