llmist 15.18.1 → 16.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
@@ -360,7 +360,8 @@ var init_execution_tree = __esm({
360
360
  result: params.result ?? "",
361
361
  executionTimeMs: params.executionTimeMs ?? 0,
362
362
  cost: params.cost,
363
- media: params.media
363
+ media: params.media,
364
+ storedMedia: params.storedMedia
364
365
  });
365
366
  }
366
367
  }
@@ -1278,8 +1279,9 @@ Produces: { "items": ["first", "second"] }`);
1278
1279
  * @param invocationId - Invocation ID (shown to LLM so it can reference for dependencies)
1279
1280
  * @param media - Optional media outputs from the gadget
1280
1281
  * @param mediaIds - Optional IDs for the media outputs
1282
+ * @param storedMedia - Optional stored media info including file paths
1281
1283
  */
1282
- addGadgetCallResult(gadget, parameters, result, invocationId, media, mediaIds) {
1284
+ addGadgetCallResult(gadget, parameters, result, invocationId, media, mediaIds, storedMedia) {
1283
1285
  const paramStr = this.formatBlockParameters(parameters, "");
1284
1286
  this.messages.push({
1285
1287
  role: "assistant",
@@ -1288,7 +1290,11 @@ ${paramStr}
1288
1290
  ${this.endPrefix}`
1289
1291
  });
1290
1292
  if (media && media.length > 0 && mediaIds && mediaIds.length > 0) {
1291
- const idRefs = media.map((m, i) => `[Media: ${mediaIds[i]} (${m.kind})]`).join("\n");
1293
+ const idRefs = media.map((m, i) => {
1294
+ const path = storedMedia?.[i]?.path;
1295
+ const pathInfo = path ? ` \u2192 saved to: ${path}` : "";
1296
+ return `[Media: ${mediaIds[i]} (${m.kind})${pathInfo}]`;
1297
+ }).join("\n");
1292
1298
  const textWithIds = `Result (${invocationId}): ${result}
1293
1299
  ${idRefs}`;
1294
1300
  const parts = [text(textWithIds)];
@@ -2347,7 +2353,7 @@ function createLogger(options = {}) {
2347
2353
  }
2348
2354
  }
2349
2355
  const useFileLogging = Boolean(sharedLogFileStream);
2350
- const logger = new import_tslog.Logger({
2356
+ const logger2 = new import_tslog.Logger({
2351
2357
  name,
2352
2358
  minLevel,
2353
2359
  type: useFileLogging ? "pretty" : defaultType,
@@ -2373,7 +2379,7 @@ function createLogger(options = {}) {
2373
2379
  }
2374
2380
  } : void 0
2375
2381
  });
2376
- return logger;
2382
+ return logger2;
2377
2383
  }
2378
2384
  var import_node_fs, import_node_path2, import_tslog, LEVEL_NAME_TO_ID, sharedLogFilePath, sharedLogFileStream, logFileInitialized, writeErrorCount, writeErrorReported, MAX_WRITE_ERRORS_BEFORE_DISABLE, LOG_TEMPLATE, defaultLogger;
2379
2385
  var init_logger = __esm({
@@ -2765,6 +2771,17 @@ var init_gadget = __esm({
2765
2771
  * ```
2766
2772
  */
2767
2773
  maxConcurrent;
2774
+ /**
2775
+ * If true, this gadget must execute alone — no other gadgets in the same
2776
+ * LLM response can run in parallel. When an exclusive gadget arrives and
2777
+ * other gadgets are already in-flight, it is deferred until they complete.
2778
+ *
2779
+ * Use for gadgets that terminate the agent loop (e.g., Finish), where
2780
+ * sibling tool results must be visible to the LLM before the loop ends.
2781
+ *
2782
+ * This is a safety floor: external config cannot weaken it.
2783
+ */
2784
+ exclusive;
2768
2785
  /**
2769
2786
  * Throws an AbortException if the execution has been aborted.
2770
2787
  *
@@ -3601,14 +3618,15 @@ var init_conversation_manager = __esm({
3601
3618
  addAssistantMessage(content) {
3602
3619
  this.historyBuilder.addAssistant(content);
3603
3620
  }
3604
- addGadgetCallResult(gadgetName, parameters, result, invocationId, media, mediaIds) {
3621
+ addGadgetCallResult(gadgetName, parameters, result, invocationId, media, mediaIds, storedMedia) {
3605
3622
  this.historyBuilder.addGadgetCallResult(
3606
3623
  gadgetName,
3607
3624
  parameters,
3608
3625
  result,
3609
3626
  invocationId,
3610
3627
  media,
3611
- mediaIds
3628
+ mediaIds,
3629
+ storedMedia
3612
3630
  );
3613
3631
  }
3614
3632
  getMessages() {
@@ -9638,6 +9656,106 @@ var init_openrouter_models = __esm({
9638
9656
  }
9639
9657
  });
9640
9658
 
9659
+ // src/providers/openrouter-speech-models.ts
9660
+ function getOpenRouterSpeechModelSpec(modelId) {
9661
+ return openrouterSpeechModels.find((m) => m.modelId === modelId);
9662
+ }
9663
+ function isOpenRouterSpeechModel(modelId) {
9664
+ return openrouterSpeechModels.some((m) => m.modelId === modelId);
9665
+ }
9666
+ function calculateOpenRouterSpeechCost(modelId, characterCount, estimatedMinutes) {
9667
+ const spec = getOpenRouterSpeechModelSpec(modelId);
9668
+ if (!spec) return void 0;
9669
+ if (spec.pricing.perMinute !== void 0) {
9670
+ const minutes = estimatedMinutes ?? characterCount / 750;
9671
+ return minutes * spec.pricing.perMinute;
9672
+ }
9673
+ if (spec.pricing.perInputToken !== void 0) {
9674
+ const estimatedTokens = Math.ceil(characterCount / 4);
9675
+ const inputCost = estimatedTokens * spec.pricing.perInputToken;
9676
+ if (spec.pricing.perAudioOutputToken !== void 0 && estimatedMinutes !== void 0) {
9677
+ const audioTokens = estimatedMinutes * 60 * 100;
9678
+ return inputCost + audioTokens * spec.pricing.perAudioOutputToken;
9679
+ }
9680
+ return inputCost;
9681
+ }
9682
+ return void 0;
9683
+ }
9684
+ var OPENROUTER_TTS_VOICES, OPENROUTER_TTS_FORMATS, openrouterSpeechModels;
9685
+ var init_openrouter_speech_models = __esm({
9686
+ "src/providers/openrouter-speech-models.ts"() {
9687
+ "use strict";
9688
+ OPENROUTER_TTS_VOICES = ["alloy", "echo", "fable", "onyx", "nova", "shimmer"];
9689
+ OPENROUTER_TTS_FORMATS = ["pcm16"];
9690
+ openrouterSpeechModels = [
9691
+ {
9692
+ provider: "openrouter",
9693
+ modelId: "openai/gpt-4o-audio-preview",
9694
+ displayName: "GPT-4o Audio Preview (via OpenRouter)",
9695
+ pricing: {
9696
+ perInputToken: 25e-7,
9697
+ perAudioOutputToken: 1e-5,
9698
+ perMinute: 0.06
9699
+ },
9700
+ voices: [...OPENROUTER_TTS_VOICES],
9701
+ formats: OPENROUTER_TTS_FORMATS,
9702
+ maxInputLength: 128e3,
9703
+ defaultVoice: "alloy",
9704
+ defaultFormat: "pcm16",
9705
+ features: {
9706
+ voiceInstructions: true
9707
+ }
9708
+ },
9709
+ {
9710
+ provider: "openrouter",
9711
+ modelId: "openai/gpt-audio",
9712
+ displayName: "GPT Audio (via OpenRouter)",
9713
+ pricing: {
9714
+ // Token-based pricing with audio output
9715
+ perInputToken: 25e-7,
9716
+ // $2.50 per 1M
9717
+ perAudioOutputToken: 64e-6,
9718
+ // ~$64 per 1M audio tokens
9719
+ // Approximate per-minute cost for estimation
9720
+ perMinute: 0.08
9721
+ },
9722
+ voices: [...OPENROUTER_TTS_VOICES],
9723
+ formats: OPENROUTER_TTS_FORMATS,
9724
+ maxInputLength: 128e3,
9725
+ // 128K context
9726
+ defaultVoice: "alloy",
9727
+ defaultFormat: "pcm16",
9728
+ features: {
9729
+ voiceInstructions: true
9730
+ }
9731
+ },
9732
+ {
9733
+ provider: "openrouter",
9734
+ modelId: "openai/gpt-audio-mini",
9735
+ displayName: "GPT Audio Mini (via OpenRouter)",
9736
+ pricing: {
9737
+ // More affordable option
9738
+ perInputToken: 6e-7,
9739
+ // $0.60 per 1M
9740
+ perAudioOutputToken: 6e-7,
9741
+ // $0.60 per 1M audio tokens
9742
+ // Approximate per-minute cost for estimation
9743
+ perMinute: 0.015
9744
+ },
9745
+ voices: [...OPENROUTER_TTS_VOICES],
9746
+ formats: OPENROUTER_TTS_FORMATS,
9747
+ maxInputLength: 128e3,
9748
+ // 128K context
9749
+ defaultVoice: "alloy",
9750
+ defaultFormat: "pcm16",
9751
+ features: {
9752
+ voiceInstructions: true
9753
+ }
9754
+ }
9755
+ ];
9756
+ }
9757
+ });
9758
+
9641
9759
  // src/providers/openrouter.ts
9642
9760
  function createOpenRouterProviderFromEnv() {
9643
9761
  const apiKey = readEnvVar("OPENROUTER_API_KEY");
@@ -9658,14 +9776,18 @@ function createOpenRouterProviderFromEnv() {
9658
9776
  });
9659
9777
  return new OpenRouterProvider(client, config);
9660
9778
  }
9661
- var import_openai4, OPENROUTER_EFFORT_MAP, OpenRouterProvider;
9779
+ var import_openai4, logger, OPENROUTER_EFFORT_MAP, OpenRouterProvider;
9662
9780
  var init_openrouter = __esm({
9663
9781
  "src/providers/openrouter.ts"() {
9664
9782
  "use strict";
9665
9783
  import_openai4 = __toESM(require("openai"), 1);
9784
+ init_model_shortcuts();
9785
+ init_logger();
9666
9786
  init_openai_compatible_provider();
9667
9787
  init_openrouter_models();
9788
+ init_openrouter_speech_models();
9668
9789
  init_utils();
9790
+ logger = createLogger({ name: "openrouter" });
9669
9791
  OPENROUTER_EFFORT_MAP = {
9670
9792
  none: "none",
9671
9793
  low: "low",
@@ -9771,6 +9893,128 @@ Original error: ${error.message}`
9771
9893
  }
9772
9894
  return error;
9773
9895
  }
9896
+ // =========================================================================
9897
+ // Speech Generation (TTS via Chat Completions with Audio Modality)
9898
+ // =========================================================================
9899
+ /**
9900
+ * Get speech model specifications for OpenRouter.
9901
+ */
9902
+ getSpeechModelSpecs() {
9903
+ return openrouterSpeechModels;
9904
+ }
9905
+ /**
9906
+ * Check if this provider supports speech generation for a given model.
9907
+ * Handles both prefixed (openrouter:openai/gpt-audio-mini) and unprefixed model IDs.
9908
+ */
9909
+ supportsSpeechGeneration(modelId) {
9910
+ const bareModelId = getModelId(modelId);
9911
+ return isOpenRouterSpeechModel(bareModelId);
9912
+ }
9913
+ /**
9914
+ * Generate speech audio from text using OpenRouter's audio-capable models.
9915
+ *
9916
+ * OpenRouter TTS works via chat completions with audio modality, not a
9917
+ * dedicated TTS endpoint. The model receives a prompt asking it to say
9918
+ * the text, and returns audio data via streaming.
9919
+ *
9920
+ * @param options - Speech generation options
9921
+ * @returns Promise resolving to the generation result with audio and cost
9922
+ * @throws Error if model is unknown, voice/format are invalid, or no audio is returned
9923
+ */
9924
+ async generateSpeech(options) {
9925
+ const client = this.client;
9926
+ const bareModelId = getModelId(options.model);
9927
+ const spec = getOpenRouterSpeechModelSpec(bareModelId);
9928
+ if (!spec) {
9929
+ throw new Error(`Unknown OpenRouter TTS model: ${options.model}`);
9930
+ }
9931
+ const voice = options.voice ?? spec.defaultVoice ?? "alloy";
9932
+ if (!spec.voices.includes(voice)) {
9933
+ throw new Error(
9934
+ `Invalid voice "${voice}" for ${options.model}. Valid voices: ${spec.voices.join(", ")}`
9935
+ );
9936
+ }
9937
+ const format2 = options.responseFormat ?? spec.defaultFormat ?? "mp3";
9938
+ if (!spec.formats.includes(format2)) {
9939
+ throw new Error(
9940
+ `Invalid format "${format2}" for ${options.model}. Valid formats: ${spec.formats.join(", ")}`
9941
+ );
9942
+ }
9943
+ logger.debug("TTS request", {
9944
+ model: bareModelId,
9945
+ voice,
9946
+ format: format2,
9947
+ inputLength: options.input.length
9948
+ });
9949
+ try {
9950
+ const response = await client.chat.completions.create({
9951
+ model: bareModelId,
9952
+ messages: [
9953
+ {
9954
+ role: "user",
9955
+ content: `Please say the following text exactly: "${options.input}"`
9956
+ }
9957
+ ],
9958
+ // OpenRouter-specific parameters for audio output
9959
+ // Note: OpenRouter TTS via chat completions does NOT support the speed parameter
9960
+ // (unlike OpenAI's dedicated /audio/speech endpoint which does)
9961
+ modalities: ["text", "audio"],
9962
+ audio: {
9963
+ voice,
9964
+ format: format2
9965
+ },
9966
+ stream: true
9967
+ });
9968
+ const audioChunks = [];
9969
+ for await (const chunk of response) {
9970
+ const delta = chunk.choices[0]?.delta;
9971
+ if (!delta || typeof delta !== "object") continue;
9972
+ const audioObj = delta.audio;
9973
+ if (!audioObj || typeof audioObj !== "object") continue;
9974
+ const audioData = audioObj.data;
9975
+ if (typeof audioData !== "string" || audioData.length === 0) continue;
9976
+ const decoded = Buffer.from(audioData, "base64");
9977
+ if (decoded.length === 0) {
9978
+ throw new Error("Invalid base64 audio data received from OpenRouter");
9979
+ }
9980
+ audioChunks.push(decoded);
9981
+ }
9982
+ const audioBuffer = Buffer.concat(audioChunks);
9983
+ if (audioBuffer.length === 0) {
9984
+ throw new Error(
9985
+ "OpenRouter TTS returned no audio data. The model may have failed to generate audio or the stream was interrupted."
9986
+ );
9987
+ }
9988
+ const cost = calculateOpenRouterSpeechCost(bareModelId, options.input.length);
9989
+ return {
9990
+ // Use Uint8Array for clean ArrayBuffer extraction (safer than buffer.slice for Node.js Buffer)
9991
+ audio: new Uint8Array(audioBuffer).buffer,
9992
+ model: options.model,
9993
+ usage: {
9994
+ characterCount: options.input.length
9995
+ },
9996
+ cost,
9997
+ format: format2
9998
+ };
9999
+ } catch (error) {
10000
+ const err = error;
10001
+ const apiError = err.error?.message || err.error?.code || "";
10002
+ const bodyInfo = err.body ? JSON.stringify(err.body) : "";
10003
+ const details = apiError || bodyInfo || err.message || "Unknown error";
10004
+ logger.debug("TTS error", {
10005
+ model: bareModelId,
10006
+ voice,
10007
+ format: format2,
10008
+ status: err.status,
10009
+ error: err.error,
10010
+ body: err.body,
10011
+ message: err.message
10012
+ });
10013
+ throw new Error(
10014
+ `OpenRouter TTS failed for model ${bareModelId}: ${details}` + (err.status ? ` (HTTP ${err.status})` : "")
10015
+ );
10016
+ }
10017
+ }
9774
10018
  };
9775
10019
  }
9776
10020
  });
@@ -10749,8 +10993,8 @@ var init_builder = __esm({
10749
10993
  * @param logger - Logger instance
10750
10994
  * @returns This builder for chaining
10751
10995
  */
10752
- withLogger(logger) {
10753
- this.logger = logger;
10996
+ withLogger(logger2) {
10997
+ this.logger = logger2;
10754
10998
  return this;
10755
10999
  }
10756
11000
  /**
@@ -12785,6 +13029,7 @@ function Gadget(config) {
12785
13029
  timeoutMs = config.timeoutMs;
12786
13030
  examples = config.examples;
12787
13031
  maxConcurrent = config.maxConcurrent;
13032
+ exclusive = config.exclusive;
12788
13033
  /**
12789
13034
  * Type helper property for accessing inferred parameter type.
12790
13035
  * This is used in the execute method signature: `execute(params: this['params'])`
@@ -12834,7 +13079,7 @@ var init_executor = __esm({
12834
13079
  init_parser();
12835
13080
  init_typed_gadget();
12836
13081
  GadgetExecutor = class {
12837
- constructor(registry, requestHumanInput, logger, defaultGadgetTimeoutMs, errorFormatterOptions, client, mediaStore, agentConfig, subagentConfig, tree, parentNodeId, baseDepth, parentObservers, currentObservers, rateLimitTracker, retryConfig) {
13082
+ constructor(registry, requestHumanInput, logger2, defaultGadgetTimeoutMs, errorFormatterOptions, client, mediaStore, agentConfig, subagentConfig, tree, parentNodeId, baseDepth, parentObservers, currentObservers, rateLimitTracker, retryConfig) {
12838
13083
  this.registry = registry;
12839
13084
  this.requestHumanInput = requestHumanInput;
12840
13085
  this.defaultGadgetTimeoutMs = defaultGadgetTimeoutMs;
@@ -12849,7 +13094,7 @@ var init_executor = __esm({
12849
13094
  this.currentObservers = currentObservers;
12850
13095
  this.rateLimitTracker = rateLimitTracker;
12851
13096
  this.retryConfig = retryConfig;
12852
- this.logger = logger ?? createLogger({ name: "llmist:executor" });
13097
+ this.logger = logger2 ?? createLogger({ name: "llmist:executor" });
12853
13098
  this.errorFormatter = new GadgetExecutionErrorFormatter(errorFormatterOptions);
12854
13099
  this.argPrefix = errorFormatterOptions?.argPrefix ?? GADGET_ARG_PREFIX;
12855
13100
  }
@@ -13249,22 +13494,22 @@ function getSubagentContextForNode(tree, nodeId) {
13249
13494
  depth: node.depth
13250
13495
  };
13251
13496
  }
13252
- async function safeObserve(fn, logger, eventType) {
13497
+ async function safeObserve(fn, logger2, eventType) {
13253
13498
  try {
13254
13499
  await fn();
13255
13500
  } catch (error) {
13256
- logger.warn(`Observer error in ${eventType}:`, error);
13501
+ logger2.warn(`Observer error in ${eventType}:`, error);
13257
13502
  }
13258
13503
  }
13259
- function chainObserverCall(chainMap, key, fn, logger, eventType, cleanup = false) {
13504
+ function chainObserverCall(chainMap, key, fn, logger2, eventType, cleanup = false) {
13260
13505
  const previousPromise = chainMap.get(key) ?? Promise.resolve();
13261
- const newPromise = previousPromise.then(() => safeObserve(fn, logger, eventType));
13506
+ const newPromise = previousPromise.then(() => safeObserve(fn, logger2, eventType));
13262
13507
  chainMap.set(key, newPromise);
13263
13508
  if (cleanup) {
13264
13509
  newPromise.finally(() => chainMap.delete(key));
13265
13510
  }
13266
13511
  }
13267
- function bridgeTreeToHooks(tree, hooks, logger) {
13512
+ function bridgeTreeToHooks(tree, hooks, logger2) {
13268
13513
  const gadgetPromiseChains = /* @__PURE__ */ new Map();
13269
13514
  const llmPromiseChains = /* @__PURE__ */ new Map();
13270
13515
  return tree.onAll((event) => {
@@ -13286,14 +13531,14 @@ function bridgeTreeToHooks(tree, hooks, logger) {
13286
13531
  gadgetName: gadgetEvent.name,
13287
13532
  invocationId: gadgetEvent.invocationId,
13288
13533
  parameters: gadgetNode?.parameters ?? {},
13289
- logger,
13534
+ logger: logger2,
13290
13535
  subagentContext
13291
13536
  };
13292
13537
  chainObserverCall(
13293
13538
  gadgetPromiseChains,
13294
13539
  gadgetEvent.invocationId,
13295
13540
  () => hooks.observers?.onGadgetExecutionStart?.(context),
13296
- logger,
13541
+ logger2,
13297
13542
  "onGadgetExecutionStart",
13298
13543
  false
13299
13544
  // Don't cleanup - wait for completion event
@@ -13313,14 +13558,14 @@ function bridgeTreeToHooks(tree, hooks, logger) {
13313
13558
  finalResult: gadgetEvent.result,
13314
13559
  executionTimeMs: gadgetEvent.executionTimeMs,
13315
13560
  cost: gadgetEvent.cost,
13316
- logger,
13561
+ logger: logger2,
13317
13562
  subagentContext
13318
13563
  };
13319
13564
  chainObserverCall(
13320
13565
  gadgetPromiseChains,
13321
13566
  gadgetEvent.invocationId,
13322
13567
  () => hooks.observers?.onGadgetExecutionComplete?.(context),
13323
- logger,
13568
+ logger2,
13324
13569
  "onGadgetExecutionComplete",
13325
13570
  true
13326
13571
  // Cleanup after completion
@@ -13339,14 +13584,14 @@ function bridgeTreeToHooks(tree, hooks, logger) {
13339
13584
  parameters: gadgetNode?.parameters ?? {},
13340
13585
  error: gadgetEvent.error,
13341
13586
  executionTimeMs: gadgetEvent.executionTimeMs,
13342
- logger,
13587
+ logger: logger2,
13343
13588
  subagentContext
13344
13589
  };
13345
13590
  chainObserverCall(
13346
13591
  gadgetPromiseChains,
13347
13592
  gadgetEvent.invocationId,
13348
13593
  () => hooks.observers?.onGadgetExecutionComplete?.(context),
13349
- logger,
13594
+ logger2,
13350
13595
  "onGadgetExecutionComplete",
13351
13596
  true
13352
13597
  // Cleanup after error
@@ -13365,14 +13610,14 @@ function bridgeTreeToHooks(tree, hooks, logger) {
13365
13610
  parameters: gadgetNode?.parameters ?? {},
13366
13611
  failedDependency: gadgetEvent.failedDependency ?? "",
13367
13612
  failedDependencyError: gadgetEvent.failedDependencyError ?? gadgetEvent.error,
13368
- logger,
13613
+ logger: logger2,
13369
13614
  subagentContext
13370
13615
  };
13371
13616
  chainObserverCall(
13372
13617
  gadgetPromiseChains,
13373
13618
  gadgetEvent.invocationId,
13374
13619
  () => hooks.observers?.onGadgetSkipped?.(context),
13375
- logger,
13620
+ logger2,
13376
13621
  "onGadgetSkipped",
13377
13622
  true
13378
13623
  // Cleanup after skipped
@@ -13397,14 +13642,14 @@ function bridgeTreeToHooks(tree, hooks, logger) {
13397
13642
  temperature: void 0,
13398
13643
  maxTokens: void 0
13399
13644
  },
13400
- logger,
13645
+ logger: logger2,
13401
13646
  subagentContext
13402
13647
  };
13403
13648
  chainObserverCall(
13404
13649
  llmPromiseChains,
13405
13650
  event.nodeId,
13406
13651
  () => hooks.observers?.onLLMCallStart?.(context),
13407
- logger,
13652
+ logger2,
13408
13653
  "onLLMCallStart",
13409
13654
  false
13410
13655
  // Don't cleanup - wait for completion event
@@ -13429,14 +13674,14 @@ function bridgeTreeToHooks(tree, hooks, logger) {
13429
13674
  rawResponse: llmEvent.response,
13430
13675
  // Use rawResponse as finalMessage since interceptor modifications aren't available from tree events
13431
13676
  finalMessage: llmEvent.response,
13432
- logger,
13677
+ logger: logger2,
13433
13678
  subagentContext
13434
13679
  };
13435
13680
  chainObserverCall(
13436
13681
  llmPromiseChains,
13437
13682
  event.nodeId,
13438
13683
  () => hooks.observers?.onLLMCallComplete?.(context),
13439
- logger,
13684
+ logger2,
13440
13685
  "onLLMCallComplete",
13441
13686
  true
13442
13687
  // Cleanup after completion
@@ -13458,14 +13703,14 @@ function bridgeTreeToHooks(tree, hooks, logger) {
13458
13703
  },
13459
13704
  error: llmEvent.error,
13460
13705
  recovered: llmEvent.recovered,
13461
- logger,
13706
+ logger: logger2,
13462
13707
  subagentContext
13463
13708
  };
13464
13709
  chainObserverCall(
13465
13710
  llmPromiseChains,
13466
13711
  event.nodeId,
13467
13712
  () => hooks.observers?.onLLMCallError?.(context),
13468
- logger,
13713
+ logger2,
13469
13714
  "onLLMCallError",
13470
13715
  true
13471
13716
  // Cleanup after error
@@ -13525,6 +13770,9 @@ var init_stream_processor = __esm({
13525
13770
  activeCountByGadget = /* @__PURE__ */ new Map();
13526
13771
  /** Queue of gadgets waiting for a concurrency slot (per gadget name) */
13527
13772
  concurrencyQueue = /* @__PURE__ */ new Map();
13773
+ // Exclusive gadget support
13774
+ /** Queue of exclusive gadgets deferred until in-flight gadgets complete */
13775
+ exclusiveQueue = [];
13528
13776
  // Cross-iteration dependency tracking
13529
13777
  /** Invocation IDs completed in previous iterations (read-only reference from Agent) */
13530
13778
  priorCompletedInvocations;
@@ -13878,6 +14126,16 @@ var init_stream_processor = __esm({
13878
14126
  if (limitResult.value === true) {
13879
14127
  return;
13880
14128
  }
14129
+ const gadget = this.registry.get(call.gadgetName);
14130
+ if (gadget?.exclusive && this.inFlightExecutions.size > 0) {
14131
+ this.logger.debug("Deferring exclusive gadget until in-flight gadgets complete", {
14132
+ gadgetName: call.gadgetName,
14133
+ invocationId: call.invocationId,
14134
+ inFlightCount: this.inFlightExecutions.size
14135
+ });
14136
+ this.exclusiveQueue.push(call);
14137
+ return;
14138
+ }
13881
14139
  const limit = this.getConcurrencyLimit(call.gadgetName);
13882
14140
  const activeCount = this.activeCountByGadget.get(call.gadgetName) ?? 0;
13883
14141
  if (limit > 0 && activeCount >= limit) {
@@ -14091,7 +14349,8 @@ var init_stream_processor = __esm({
14091
14349
  result: result.result,
14092
14350
  executionTimeMs: result.executionTimeMs,
14093
14351
  cost: result.cost,
14094
- media: result.media
14352
+ media: result.media,
14353
+ storedMedia: result.storedMedia
14095
14354
  });
14096
14355
  }
14097
14356
  }
@@ -14163,7 +14422,7 @@ var init_stream_processor = __esm({
14163
14422
  * Clears the inFlightExecutions map after all gadgets complete.
14164
14423
  */
14165
14424
  async *waitForInFlightExecutions() {
14166
- if (this.inFlightExecutions.size === 0 && !this.hasQueuedGadgets()) {
14425
+ if (this.inFlightExecutions.size === 0 && !this.hasQueuedGadgets() && this.exclusiveQueue.length === 0) {
14167
14426
  return;
14168
14427
  }
14169
14428
  this.logger.debug("Waiting for in-flight gadget executions", {
@@ -14184,6 +14443,18 @@ var init_stream_processor = __esm({
14184
14443
  }
14185
14444
  }
14186
14445
  this.inFlightExecutions.clear();
14446
+ if (this.exclusiveQueue.length > 0) {
14447
+ this.logger.debug("Processing deferred exclusive gadgets", {
14448
+ count: this.exclusiveQueue.length
14449
+ });
14450
+ const queue = this.exclusiveQueue;
14451
+ this.exclusiveQueue = [];
14452
+ for (const call of queue) {
14453
+ for await (const evt of this.executeGadgetGenerator(call)) {
14454
+ yield evt;
14455
+ }
14456
+ }
14457
+ }
14187
14458
  }
14188
14459
  /**
14189
14460
  * Check if there are any gadgets waiting in concurrency queues.
@@ -15672,7 +15943,8 @@ var init_agent = __esm({
15672
15943
  gadgetResult.error ?? gadgetResult.result ?? "",
15673
15944
  gadgetResult.invocationId,
15674
15945
  gadgetResult.media,
15675
- gadgetResult.mediaIds
15946
+ gadgetResult.mediaIds,
15947
+ gadgetResult.storedMedia
15676
15948
  );
15677
15949
  }
15678
15950
  }