llmist 1.7.0 → 2.1.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,136 @@ 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
+ }
1022
+ /**
1023
+ * Register a cleanup function to run when execution is aborted (timeout or cancellation).
1024
+ * The cleanup function is called immediately if the signal is already aborted.
1025
+ * Errors thrown by the cleanup function are silently ignored.
1026
+ *
1027
+ * Use this to clean up resources like browser instances, database connections,
1028
+ * or child processes when the gadget is cancelled due to timeout.
1029
+ *
1030
+ * @param ctx - The execution context containing the abort signal
1031
+ * @param cleanup - Function to run on abort (can be sync or async)
1032
+ *
1033
+ * @example
1034
+ * ```typescript
1035
+ * class BrowserGadget extends Gadget({
1036
+ * description: 'Fetches web page content',
1037
+ * schema: z.object({ url: z.string() }),
1038
+ * }) {
1039
+ * async execute(params: this['params'], ctx?: ExecutionContext): Promise<string> {
1040
+ * const browser = await chromium.launch();
1041
+ * this.onAbort(ctx, () => browser.close());
1042
+ *
1043
+ * const page = await browser.newPage();
1044
+ * this.onAbort(ctx, () => page.close());
1045
+ *
1046
+ * await page.goto(params.url);
1047
+ * const content = await page.content();
1048
+ *
1049
+ * await browser.close();
1050
+ * return content;
1051
+ * }
1052
+ * }
1053
+ * ```
1054
+ */
1055
+ onAbort(ctx, cleanup) {
1056
+ if (!ctx?.signal) return;
1057
+ const safeCleanup = () => {
1058
+ try {
1059
+ const result = cleanup();
1060
+ if (result && typeof result === "object" && "catch" in result) {
1061
+ result.catch(() => {
1062
+ });
1063
+ }
1064
+ } catch {
1065
+ }
1066
+ };
1067
+ if (ctx.signal.aborted) {
1068
+ safeCleanup();
1069
+ return;
1070
+ }
1071
+ ctx.signal.addEventListener("abort", safeCleanup, { once: true });
1072
+ }
1073
+ /**
1074
+ * Create an AbortController linked to the execution context's signal.
1075
+ * When the parent signal aborts, the returned controller also aborts with the same reason.
1076
+ *
1077
+ * Useful for passing abort signals to child operations like fetch() while still
1078
+ * being able to abort them independently if needed.
1079
+ *
1080
+ * @param ctx - The execution context containing the parent abort signal
1081
+ * @returns A new AbortController linked to the parent signal
1082
+ *
1083
+ * @example
1084
+ * ```typescript
1085
+ * class FetchGadget extends Gadget({
1086
+ * description: 'Fetches data from URL',
1087
+ * schema: z.object({ url: z.string() }),
1088
+ * }) {
1089
+ * async execute(params: this['params'], ctx?: ExecutionContext): Promise<string> {
1090
+ * const controller = this.createLinkedAbortController(ctx);
1091
+ *
1092
+ * // fetch() will automatically abort when parent times out
1093
+ * const response = await fetch(params.url, { signal: controller.signal });
1094
+ * return response.text();
1095
+ * }
1096
+ * }
1097
+ * ```
1098
+ */
1099
+ createLinkedAbortController(ctx) {
1100
+ const controller = new AbortController();
1101
+ if (ctx?.signal) {
1102
+ if (ctx.signal.aborted) {
1103
+ controller.abort(ctx.signal.reason);
1104
+ } else {
1105
+ ctx.signal.addEventListener(
1106
+ "abort",
1107
+ () => {
1108
+ controller.abort(ctx.signal.reason);
1109
+ },
1110
+ { once: true }
1111
+ );
1112
+ }
1113
+ }
1114
+ return controller;
1115
+ }
947
1116
  /**
948
1117
  * Auto-generated instruction text for the LLM.
949
1118
  * Combines name, description, and parameter schema into a formatted instruction.
@@ -1011,8 +1180,8 @@ function createGadget(config) {
1011
1180
  parameterSchema = config.schema;
1012
1181
  timeoutMs = config.timeoutMs;
1013
1182
  examples = config.examples;
1014
- execute(params) {
1015
- return config.execute(params);
1183
+ execute(params, ctx) {
1184
+ return config.execute(params, ctx);
1016
1185
  }
1017
1186
  }
1018
1187
  return new DynamicGadget();
@@ -2318,6 +2487,162 @@ var init_block_params = __esm({
2318
2487
  }
2319
2488
  });
2320
2489
 
2490
+ // src/gadgets/cost-reporting-client.ts
2491
+ var CostReportingLLMistWrapper;
2492
+ var init_cost_reporting_client = __esm({
2493
+ "src/gadgets/cost-reporting-client.ts"() {
2494
+ "use strict";
2495
+ init_model_shortcuts();
2496
+ CostReportingLLMistWrapper = class {
2497
+ constructor(client, reportCost) {
2498
+ this.client = client;
2499
+ this.reportCost = reportCost;
2500
+ }
2501
+ /**
2502
+ * Access to model registry for cost estimation.
2503
+ */
2504
+ get modelRegistry() {
2505
+ return this.client.modelRegistry;
2506
+ }
2507
+ /**
2508
+ * Quick completion with automatic cost reporting.
2509
+ *
2510
+ * Streams internally to track token usage, then reports the calculated cost.
2511
+ *
2512
+ * @param prompt - User prompt
2513
+ * @param options - Optional configuration (model, temperature, etc.)
2514
+ * @returns Complete text response
2515
+ */
2516
+ async complete(prompt, options) {
2517
+ const model = resolveModel(options?.model ?? "haiku");
2518
+ let result = "";
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
+ for await (const chunk of this.client.stream({
2528
+ model,
2529
+ messages,
2530
+ temperature: options?.temperature,
2531
+ maxTokens: options?.maxTokens
2532
+ })) {
2533
+ result += chunk.text ?? "";
2534
+ if (chunk.usage) {
2535
+ inputTokens = chunk.usage.inputTokens;
2536
+ outputTokens = chunk.usage.outputTokens;
2537
+ cachedInputTokens = chunk.usage.cachedInputTokens ?? 0;
2538
+ cacheCreationInputTokens = chunk.usage.cacheCreationInputTokens ?? 0;
2539
+ }
2540
+ }
2541
+ this.reportCostFromUsage(model, inputTokens, outputTokens, cachedInputTokens, cacheCreationInputTokens);
2542
+ return result;
2543
+ }
2544
+ /**
2545
+ * Quick streaming with automatic cost reporting when stream completes.
2546
+ *
2547
+ * Yields text chunks as they arrive, then reports cost in finally block.
2548
+ *
2549
+ * @param prompt - User prompt
2550
+ * @param options - Optional configuration (model, temperature, etc.)
2551
+ * @returns Async generator yielding text chunks
2552
+ */
2553
+ async *streamText(prompt, options) {
2554
+ const model = resolveModel(options?.model ?? "haiku");
2555
+ let inputTokens = 0;
2556
+ let outputTokens = 0;
2557
+ let cachedInputTokens = 0;
2558
+ let cacheCreationInputTokens = 0;
2559
+ const messages = [
2560
+ ...options?.systemPrompt ? [{ role: "system", content: options.systemPrompt }] : [],
2561
+ { role: "user", content: prompt }
2562
+ ];
2563
+ try {
2564
+ for await (const chunk of this.client.stream({
2565
+ model,
2566
+ messages,
2567
+ temperature: options?.temperature,
2568
+ maxTokens: options?.maxTokens
2569
+ })) {
2570
+ if (chunk.text) {
2571
+ yield chunk.text;
2572
+ }
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
+ }
2580
+ } finally {
2581
+ this.reportCostFromUsage(model, inputTokens, outputTokens, cachedInputTokens, cacheCreationInputTokens);
2582
+ }
2583
+ }
2584
+ /**
2585
+ * Low-level stream access with automatic cost reporting.
2586
+ *
2587
+ * Returns a wrapped stream that reports costs when iteration completes.
2588
+ *
2589
+ * @param options - Full LLM generation options
2590
+ * @returns Wrapped LLM stream that auto-reports costs
2591
+ */
2592
+ stream(options) {
2593
+ return this.createCostReportingStream(options);
2594
+ }
2595
+ /**
2596
+ * Creates a wrapped stream that tracks usage and reports costs on completion.
2597
+ */
2598
+ createCostReportingStream(options) {
2599
+ const innerStream = this.client.stream(options);
2600
+ const reportCostFromUsage = this.reportCostFromUsage.bind(this);
2601
+ const model = options.model;
2602
+ async function* costReportingWrapper() {
2603
+ let inputTokens = 0;
2604
+ let outputTokens = 0;
2605
+ let cachedInputTokens = 0;
2606
+ let cacheCreationInputTokens = 0;
2607
+ try {
2608
+ for await (const chunk of innerStream) {
2609
+ if (chunk.usage) {
2610
+ inputTokens = chunk.usage.inputTokens;
2611
+ outputTokens = chunk.usage.outputTokens;
2612
+ cachedInputTokens = chunk.usage.cachedInputTokens ?? 0;
2613
+ cacheCreationInputTokens = chunk.usage.cacheCreationInputTokens ?? 0;
2614
+ }
2615
+ yield chunk;
2616
+ }
2617
+ } finally {
2618
+ if (inputTokens > 0 || outputTokens > 0) {
2619
+ reportCostFromUsage(model, inputTokens, outputTokens, cachedInputTokens, cacheCreationInputTokens);
2620
+ }
2621
+ }
2622
+ }
2623
+ return costReportingWrapper();
2624
+ }
2625
+ /**
2626
+ * Calculates and reports cost from token usage.
2627
+ */
2628
+ reportCostFromUsage(model, inputTokens, outputTokens, cachedInputTokens = 0, cacheCreationInputTokens = 0) {
2629
+ if (inputTokens === 0 && outputTokens === 0) return;
2630
+ const modelName = model.includes(":") ? model.split(":")[1] : model;
2631
+ const estimate = this.client.modelRegistry.estimateCost(
2632
+ modelName,
2633
+ inputTokens,
2634
+ outputTokens,
2635
+ cachedInputTokens,
2636
+ cacheCreationInputTokens
2637
+ );
2638
+ if (estimate && estimate.totalCost > 0) {
2639
+ this.reportCost(estimate.totalCost);
2640
+ }
2641
+ }
2642
+ };
2643
+ }
2644
+ });
2645
+
2321
2646
  // src/gadgets/error-formatter.ts
2322
2647
  var GadgetErrorFormatter;
2323
2648
  var init_error_formatter = __esm({
@@ -2401,38 +2726,6 @@ var init_error_formatter = __esm({
2401
2726
  }
2402
2727
  });
2403
2728
 
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
2729
  // src/gadgets/parser.ts
2437
2730
  function stripMarkdownFences(content) {
2438
2731
  let cleaned = content.trim();
@@ -2618,14 +2911,16 @@ var init_executor = __esm({
2618
2911
  init_constants();
2619
2912
  init_logger();
2620
2913
  init_block_params();
2914
+ init_cost_reporting_client();
2621
2915
  init_error_formatter();
2622
2916
  init_exceptions();
2623
2917
  init_parser();
2624
2918
  GadgetExecutor = class {
2625
- constructor(registry, onHumanInputRequired, logger, defaultGadgetTimeoutMs, errorFormatterOptions) {
2919
+ constructor(registry, onHumanInputRequired, logger, defaultGadgetTimeoutMs, errorFormatterOptions, client) {
2626
2920
  this.registry = registry;
2627
2921
  this.onHumanInputRequired = onHumanInputRequired;
2628
2922
  this.defaultGadgetTimeoutMs = defaultGadgetTimeoutMs;
2923
+ this.client = client;
2629
2924
  this.logger = logger ?? createLogger({ name: "llmist:executor" });
2630
2925
  this.errorFormatter = new GadgetErrorFormatter(errorFormatterOptions);
2631
2926
  this.argPrefix = errorFormatterOptions?.argPrefix ?? GADGET_ARG_PREFIX;
@@ -2635,14 +2930,27 @@ var init_executor = __esm({
2635
2930
  argPrefix;
2636
2931
  /**
2637
2932
  * Creates a promise that rejects with a TimeoutException after the specified timeout.
2933
+ * Aborts the provided AbortController before rejecting, allowing gadgets to clean up.
2638
2934
  */
2639
- createTimeoutPromise(gadgetName, timeoutMs) {
2935
+ createTimeoutPromise(gadgetName, timeoutMs, abortController) {
2640
2936
  return new Promise((_, reject) => {
2641
2937
  setTimeout(() => {
2642
- reject(new TimeoutException(gadgetName, timeoutMs));
2938
+ const timeoutError = new TimeoutException(gadgetName, timeoutMs);
2939
+ abortController.abort(timeoutError.message);
2940
+ reject(timeoutError);
2643
2941
  }, timeoutMs);
2644
2942
  });
2645
2943
  }
2944
+ /**
2945
+ * Normalizes gadget execute result to consistent format.
2946
+ * Handles both string returns (backwards compat) and object returns with cost.
2947
+ */
2948
+ normalizeExecuteResult(raw) {
2949
+ if (typeof raw === "string") {
2950
+ return { result: raw, cost: 0 };
2951
+ }
2952
+ return { result: raw.result, cost: raw.cost ?? 0 };
2953
+ }
2646
2954
  // Execute a gadget call asynchronously
2647
2955
  async execute(call) {
2648
2956
  const startTime = Date.now();
@@ -2737,30 +3045,53 @@ var init_executor = __esm({
2737
3045
  validatedParameters = schemaAwareParameters;
2738
3046
  }
2739
3047
  const timeoutMs = gadget.timeoutMs ?? this.defaultGadgetTimeoutMs;
2740
- let result;
3048
+ const abortController = new AbortController();
3049
+ let callbackCost = 0;
3050
+ const reportCost = (amount) => {
3051
+ if (amount > 0) {
3052
+ callbackCost += amount;
3053
+ this.logger.debug("Gadget reported cost via callback", {
3054
+ gadgetName: call.gadgetName,
3055
+ amount,
3056
+ totalCallbackCost: callbackCost
3057
+ });
3058
+ }
3059
+ };
3060
+ const ctx = {
3061
+ reportCost,
3062
+ llmist: this.client ? new CostReportingLLMistWrapper(this.client, reportCost) : void 0,
3063
+ signal: abortController.signal
3064
+ };
3065
+ let rawResult;
2741
3066
  if (timeoutMs && timeoutMs > 0) {
2742
3067
  this.logger.debug("Executing gadget with timeout", {
2743
3068
  gadgetName: call.gadgetName,
2744
3069
  timeoutMs
2745
3070
  });
2746
- result = await Promise.race([
2747
- Promise.resolve(gadget.execute(validatedParameters)),
2748
- this.createTimeoutPromise(call.gadgetName, timeoutMs)
3071
+ rawResult = await Promise.race([
3072
+ Promise.resolve(gadget.execute(validatedParameters, ctx)),
3073
+ this.createTimeoutPromise(call.gadgetName, timeoutMs, abortController)
2749
3074
  ]);
2750
3075
  } else {
2751
- result = await Promise.resolve(gadget.execute(validatedParameters));
3076
+ rawResult = await Promise.resolve(gadget.execute(validatedParameters, ctx));
2752
3077
  }
3078
+ const { result, cost: returnCost } = this.normalizeExecuteResult(rawResult);
3079
+ const totalCost = callbackCost + returnCost;
2753
3080
  const executionTimeMs = Date.now() - startTime;
2754
3081
  this.logger.info("Gadget executed successfully", {
2755
3082
  gadgetName: call.gadgetName,
2756
3083
  invocationId: call.invocationId,
2757
- executionTimeMs
3084
+ executionTimeMs,
3085
+ cost: totalCost > 0 ? totalCost : void 0,
3086
+ callbackCost: callbackCost > 0 ? callbackCost : void 0,
3087
+ returnCost: returnCost > 0 ? returnCost : void 0
2758
3088
  });
2759
3089
  this.logger.debug("Gadget result", {
2760
3090
  gadgetName: call.gadgetName,
2761
3091
  invocationId: call.invocationId,
2762
3092
  parameters: validatedParameters,
2763
3093
  result,
3094
+ cost: totalCost,
2764
3095
  executionTimeMs
2765
3096
  });
2766
3097
  return {
@@ -2768,7 +3099,8 @@ var init_executor = __esm({
2768
3099
  invocationId: call.invocationId,
2769
3100
  parameters: validatedParameters,
2770
3101
  result,
2771
- executionTimeMs
3102
+ executionTimeMs,
3103
+ cost: totalCost
2772
3104
  };
2773
3105
  } catch (error) {
2774
3106
  if (error instanceof BreakLoopException) {
@@ -2799,6 +3131,19 @@ var init_executor = __esm({
2799
3131
  executionTimeMs: Date.now() - startTime
2800
3132
  };
2801
3133
  }
3134
+ if (error instanceof AbortError) {
3135
+ this.logger.info("Gadget execution was aborted", {
3136
+ gadgetName: call.gadgetName,
3137
+ executionTimeMs: Date.now() - startTime
3138
+ });
3139
+ return {
3140
+ gadgetName: call.gadgetName,
3141
+ invocationId: call.invocationId,
3142
+ parameters: validatedParameters,
3143
+ error: error.message,
3144
+ executionTimeMs: Date.now() - startTime
3145
+ };
3146
+ }
2802
3147
  if (error instanceof HumanInputException) {
2803
3148
  this.logger.info("Gadget requested human input", {
2804
3149
  gadgetName: call.gadgetName,
@@ -2925,7 +3270,8 @@ var init_stream_processor = __esm({
2925
3270
  options.onHumanInputRequired,
2926
3271
  this.logger.getSubLogger({ name: "executor" }),
2927
3272
  options.defaultGadgetTimeoutMs,
2928
- { argPrefix: options.gadgetArgPrefix }
3273
+ { argPrefix: options.gadgetArgPrefix },
3274
+ options.client
2929
3275
  );
2930
3276
  }
2931
3277
  /**
@@ -3192,6 +3538,7 @@ var init_stream_processor = __esm({
3192
3538
  error: result.error,
3193
3539
  executionTimeMs: result.executionTimeMs,
3194
3540
  breaksLoop: result.breaksLoop,
3541
+ cost: result.cost,
3195
3542
  logger: this.logger
3196
3543
  };
3197
3544
  await this.hooks.observers.onGadgetExecutionComplete(context);
@@ -3548,6 +3895,17 @@ var init_agent = __esm({
3548
3895
  llmOptions = { ...llmOptions, ...action.modifiedOptions };
3549
3896
  }
3550
3897
  }
3898
+ await this.safeObserve(async () => {
3899
+ if (this.hooks.observers?.onLLMCallReady) {
3900
+ const context = {
3901
+ iteration: currentIteration,
3902
+ maxIterations: this.maxIterations,
3903
+ options: llmOptions,
3904
+ logger: this.logger
3905
+ };
3906
+ await this.hooks.observers.onLLMCallReady(context);
3907
+ }
3908
+ });
3551
3909
  this.logger.info("Calling LLM", { model: this.model });
3552
3910
  this.logger.silly("LLM request details", {
3553
3911
  model: llmOptions.model,
@@ -3568,7 +3926,8 @@ var init_agent = __esm({
3568
3926
  onHumanInputRequired: this.onHumanInputRequired,
3569
3927
  stopOnGadgetError: this.stopOnGadgetError,
3570
3928
  shouldContinueAfterError: this.shouldContinueAfterError,
3571
- defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs
3929
+ defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
3930
+ client: this.client
3572
3931
  });
3573
3932
  const result = await processor.process(stream2);
3574
3933
  for (const output of result.outputs) {
@@ -6700,10 +7059,18 @@ async function testGadget(gadget, params, options) {
6700
7059
  validatedParams = validationResult.data;
6701
7060
  }
6702
7061
  try {
6703
- const result = await Promise.resolve(gadget.execute(validatedParams));
7062
+ const rawResult = await Promise.resolve(gadget.execute(validatedParams));
7063
+ if (typeof rawResult === "string") {
7064
+ return {
7065
+ result: rawResult,
7066
+ validatedParams,
7067
+ cost: 0
7068
+ };
7069
+ }
6704
7070
  return {
6705
- result,
6706
- validatedParams
7071
+ result: rawResult.result,
7072
+ validatedParams,
7073
+ cost: rawResult.cost ?? 0
6707
7074
  };
6708
7075
  } catch (error) {
6709
7076
  return {