@juspay/neurolink 9.61.2 → 9.62.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.
Files changed (133) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +23 -17
  3. package/dist/adapters/tts/googleTTSHandler.js +1 -1
  4. package/dist/browser/neurolink.min.js +373 -355
  5. package/dist/cli/commands/serve.js +9 -0
  6. package/dist/cli/commands/voiceServer.d.ts +7 -0
  7. package/dist/cli/commands/voiceServer.js +9 -1
  8. package/dist/cli/factories/commandFactory.js +136 -11
  9. package/dist/cli/loop/optionsSchema.d.ts +1 -1
  10. package/dist/cli/utils/audioFileUtils.d.ts +3 -3
  11. package/dist/cli/utils/audioFileUtils.js +5 -1
  12. package/dist/core/baseProvider.js +29 -6
  13. package/dist/factories/providerRegistry.d.ts +14 -0
  14. package/dist/factories/providerRegistry.js +141 -2
  15. package/dist/lib/adapters/tts/googleTTSHandler.js +1 -1
  16. package/dist/lib/core/baseProvider.js +29 -6
  17. package/dist/lib/factories/providerRegistry.d.ts +14 -0
  18. package/dist/lib/factories/providerRegistry.js +141 -2
  19. package/dist/lib/neurolink.d.ts +19 -0
  20. package/dist/lib/neurolink.js +248 -12
  21. package/dist/lib/observability/exporters/laminarExporter.js +1 -0
  22. package/dist/lib/observability/exporters/posthogExporter.js +1 -0
  23. package/dist/lib/observability/utils/spanSerializer.js +1 -0
  24. package/dist/lib/server/voice/tokenCompare.d.ts +14 -0
  25. package/dist/lib/server/voice/tokenCompare.js +23 -0
  26. package/dist/lib/server/voice/voiceServerApp.js +62 -3
  27. package/dist/lib/server/voice/voiceWebSocketHandler.d.ts +20 -3
  28. package/dist/lib/server/voice/voiceWebSocketHandler.js +555 -435
  29. package/dist/lib/types/generate.d.ts +47 -0
  30. package/dist/lib/types/index.d.ts +1 -1
  31. package/dist/lib/types/index.js +1 -1
  32. package/dist/lib/types/realtime.d.ts +243 -0
  33. package/dist/lib/types/realtime.js +70 -0
  34. package/dist/lib/types/server.d.ts +68 -0
  35. package/dist/lib/types/span.d.ts +2 -0
  36. package/dist/lib/types/span.js +2 -0
  37. package/dist/lib/types/stream.d.ts +36 -14
  38. package/dist/lib/types/stt.d.ts +585 -0
  39. package/dist/lib/types/stt.js +90 -0
  40. package/dist/lib/types/tts.d.ts +23 -11
  41. package/dist/lib/types/tts.js +7 -0
  42. package/dist/lib/types/voice.d.ts +272 -0
  43. package/dist/lib/types/voice.js +137 -0
  44. package/dist/lib/utils/audioFormatDetector.d.ts +15 -0
  45. package/dist/lib/utils/audioFormatDetector.js +34 -0
  46. package/dist/lib/utils/sttProcessor.d.ts +115 -0
  47. package/dist/lib/utils/sttProcessor.js +295 -0
  48. package/dist/lib/voice/RealtimeVoiceAPI.d.ts +183 -0
  49. package/dist/lib/voice/RealtimeVoiceAPI.js +439 -0
  50. package/dist/lib/voice/audio-utils.d.ts +135 -0
  51. package/dist/lib/voice/audio-utils.js +435 -0
  52. package/dist/lib/voice/errors.d.ts +123 -0
  53. package/dist/lib/voice/errors.js +386 -0
  54. package/dist/lib/voice/index.d.ts +26 -0
  55. package/dist/lib/voice/index.js +55 -0
  56. package/dist/lib/voice/providers/AzureSTT.d.ts +47 -0
  57. package/dist/lib/voice/providers/AzureSTT.js +345 -0
  58. package/dist/lib/voice/providers/AzureTTS.d.ts +59 -0
  59. package/dist/lib/voice/providers/AzureTTS.js +349 -0
  60. package/dist/lib/voice/providers/DeepgramSTT.d.ts +40 -0
  61. package/dist/lib/voice/providers/DeepgramSTT.js +550 -0
  62. package/dist/lib/voice/providers/ElevenLabsTTS.d.ts +53 -0
  63. package/dist/lib/voice/providers/ElevenLabsTTS.js +311 -0
  64. package/dist/lib/voice/providers/GeminiLive.d.ts +52 -0
  65. package/dist/lib/voice/providers/GeminiLive.js +372 -0
  66. package/dist/lib/voice/providers/GoogleSTT.d.ts +60 -0
  67. package/dist/lib/voice/providers/GoogleSTT.js +454 -0
  68. package/dist/lib/voice/providers/OpenAIRealtime.d.ts +47 -0
  69. package/dist/lib/voice/providers/OpenAIRealtime.js +412 -0
  70. package/dist/lib/voice/providers/OpenAISTT.d.ts +41 -0
  71. package/dist/lib/voice/providers/OpenAISTT.js +286 -0
  72. package/dist/lib/voice/providers/OpenAITTS.d.ts +49 -0
  73. package/dist/lib/voice/providers/OpenAITTS.js +271 -0
  74. package/dist/lib/voice/stream-handler.d.ts +166 -0
  75. package/dist/lib/voice/stream-handler.js +514 -0
  76. package/dist/neurolink.d.ts +19 -0
  77. package/dist/neurolink.js +248 -12
  78. package/dist/observability/exporters/laminarExporter.js +1 -0
  79. package/dist/observability/exporters/posthogExporter.js +1 -0
  80. package/dist/observability/utils/spanSerializer.js +1 -0
  81. package/dist/server/voice/tokenCompare.d.ts +14 -0
  82. package/dist/server/voice/tokenCompare.js +22 -0
  83. package/dist/server/voice/voiceServerApp.js +62 -3
  84. package/dist/server/voice/voiceWebSocketHandler.d.ts +20 -3
  85. package/dist/server/voice/voiceWebSocketHandler.js +555 -435
  86. package/dist/types/generate.d.ts +47 -0
  87. package/dist/types/index.d.ts +1 -1
  88. package/dist/types/index.js +1 -1
  89. package/dist/types/realtime.d.ts +243 -0
  90. package/dist/types/realtime.js +69 -0
  91. package/dist/types/server.d.ts +68 -0
  92. package/dist/types/span.d.ts +2 -0
  93. package/dist/types/span.js +2 -0
  94. package/dist/types/stream.d.ts +36 -14
  95. package/dist/types/stt.d.ts +585 -0
  96. package/dist/types/stt.js +89 -0
  97. package/dist/types/tts.d.ts +23 -11
  98. package/dist/types/tts.js +7 -0
  99. package/dist/types/voice.d.ts +272 -0
  100. package/dist/types/voice.js +136 -0
  101. package/dist/utils/audioFormatDetector.d.ts +15 -0
  102. package/dist/utils/audioFormatDetector.js +33 -0
  103. package/dist/utils/sttProcessor.d.ts +115 -0
  104. package/dist/utils/sttProcessor.js +294 -0
  105. package/dist/voice/RealtimeVoiceAPI.d.ts +183 -0
  106. package/dist/voice/RealtimeVoiceAPI.js +438 -0
  107. package/dist/voice/audio-utils.d.ts +135 -0
  108. package/dist/voice/audio-utils.js +434 -0
  109. package/dist/voice/errors.d.ts +123 -0
  110. package/dist/voice/errors.js +385 -0
  111. package/dist/voice/index.d.ts +26 -0
  112. package/dist/voice/index.js +54 -0
  113. package/dist/voice/providers/AzureSTT.d.ts +47 -0
  114. package/dist/voice/providers/AzureSTT.js +344 -0
  115. package/dist/voice/providers/AzureTTS.d.ts +59 -0
  116. package/dist/voice/providers/AzureTTS.js +348 -0
  117. package/dist/voice/providers/DeepgramSTT.d.ts +40 -0
  118. package/dist/voice/providers/DeepgramSTT.js +549 -0
  119. package/dist/voice/providers/ElevenLabsTTS.d.ts +53 -0
  120. package/dist/voice/providers/ElevenLabsTTS.js +310 -0
  121. package/dist/voice/providers/GeminiLive.d.ts +52 -0
  122. package/dist/voice/providers/GeminiLive.js +371 -0
  123. package/dist/voice/providers/GoogleSTT.d.ts +60 -0
  124. package/dist/voice/providers/GoogleSTT.js +453 -0
  125. package/dist/voice/providers/OpenAIRealtime.d.ts +47 -0
  126. package/dist/voice/providers/OpenAIRealtime.js +411 -0
  127. package/dist/voice/providers/OpenAISTT.d.ts +41 -0
  128. package/dist/voice/providers/OpenAISTT.js +285 -0
  129. package/dist/voice/providers/OpenAITTS.d.ts +49 -0
  130. package/dist/voice/providers/OpenAITTS.js +270 -0
  131. package/dist/voice/stream-handler.d.ts +166 -0
  132. package/dist/voice/stream-handler.js +513 -0
  133. package/package.json +3 -1
@@ -107,7 +107,7 @@ export class GoogleTTSHandler {
107
107
  const languageCodes = voice.languageCodes;
108
108
  const primaryLanguageCode = languageCodes[0];
109
109
  const voiceType = this.detectVoiceType(voiceName);
110
- // Map Google's ssmlGender → internal Gender
110
+ // Map Google's ssmlGender → internal TTSGender
111
111
  const gender = voice.ssmlGender === "MALE"
112
112
  ? "male"
113
113
  : voice.ssmlGender === "FEMALE"
@@ -249,6 +249,11 @@ export class BaseProvider {
249
249
  excludeTools: options.excludeTools,
250
250
  skipToolPromptInjection: options.skipToolPromptInjection,
251
251
  timeout: options.timeout,
252
+ stt: options.stt,
253
+ // Forward TTS options too — without this, the fake-streaming fallback
254
+ // path silently drops `tts` and the resulting StreamResult never
255
+ // produces a `tts_audio` chunk even when synthesis was requested.
256
+ tts: options.tts,
252
257
  };
253
258
  logger.debug(`Calling generate for fake streaming`, {
254
259
  provider: this.providerName,
@@ -299,6 +304,23 @@ export class BaseProvider {
299
304
  imageOutput: result.imageOutput,
300
305
  };
301
306
  }
307
+ // Yield synthesized audio so callers using stream() with tts.enabled
308
+ // still receive a tts_audio chunk on the fake-streaming fallback
309
+ // path (matches the discriminator used by the real streaming path).
310
+ if (result?.audio) {
311
+ yield {
312
+ type: "tts_audio",
313
+ audio: {
314
+ data: result.audio.buffer,
315
+ format: result.audio.format,
316
+ index: 0,
317
+ isFinal: true,
318
+ cumulativeSize: result.audio.size,
319
+ voice: result.audio.voice,
320
+ sampleRate: result.audio.sampleRate,
321
+ },
322
+ };
323
+ }
302
324
  })(),
303
325
  usage: result?.usage,
304
326
  provider: result?.provider,
@@ -587,7 +609,7 @@ export class BaseProvider {
587
609
  if (!options.tts) {
588
610
  return this.enhanceResult(baseResult, options, startTime);
589
611
  }
590
- baseResult.audio = await TTSProcessor.synthesize(textToSynthesize, options.provider ?? this.providerName, options.tts);
612
+ baseResult.audio = await TTSProcessor.synthesize(textToSynthesize, options.tts.provider ?? options.provider ?? this.providerName, options.tts);
591
613
  }
592
614
  catch (ttsError) {
593
615
  logger.error(`TTS synthesis failed in Mode 1 (direct input synthesis):`, ttsError);
@@ -691,20 +713,21 @@ export class BaseProvider {
691
713
  const { toolsUsed, toolExecutions } = this.extractToolInformation(generateResult);
692
714
  let enhancedResult = this.formatEnhancedResult(generateResult, tools, toolsUsed, toolExecutions, options);
693
715
  enhancedResult = await this.synthesizeAIResponseIfNeeded(enhancedResult, options);
694
- return this.enhanceResult(enhancedResult, options, startTime);
716
+ const finalResult = await this.enhanceResult(enhancedResult, options, startTime);
717
+ return finalResult;
695
718
  }
696
719
  async synthesizeAIResponseIfNeeded(enhancedResult, options) {
697
720
  if (!options.tts?.enabled || !options.tts?.useAiResponse) {
698
721
  return enhancedResult;
699
722
  }
700
723
  const aiResponse = enhancedResult.content;
701
- const provider = options.provider ?? this.providerName;
702
- if (!aiResponse || !provider) {
724
+ const ttsProvider = options.tts?.provider ?? options.provider ?? this.providerName;
725
+ if (!aiResponse || !ttsProvider) {
703
726
  logger.warn(`TTS synthesis skipped despite being enabled`, {
704
727
  provider: this.providerName,
705
728
  hasAiResponse: !!aiResponse,
706
729
  aiResponseLength: aiResponse?.length ?? 0,
707
- hasProvider: !!provider,
730
+ hasProvider: !!ttsProvider,
708
731
  ttsConfig: {
709
732
  enabled: options.tts?.enabled,
710
733
  useAiResponse: options.tts?.useAiResponse,
@@ -716,7 +739,7 @@ export class BaseProvider {
716
739
  return enhancedResult;
717
740
  }
718
741
  try {
719
- const ttsResult = await TTSProcessor.synthesize(aiResponse, provider, options.tts);
742
+ const ttsResult = await TTSProcessor.synthesize(aiResponse, ttsProvider, options.tts);
720
743
  return {
721
744
  ...enhancedResult,
722
745
  audio: ttsResult,
@@ -7,6 +7,20 @@ export declare class ProviderRegistry {
7
7
  private static registered;
8
8
  private static registrationPromise;
9
9
  private static options;
10
+ /**
11
+ * NEW4: per-handler registration outcomes for the realtime voice
12
+ * providers. `"ok"` = registered; any other string = the error message.
13
+ * Empty until the first `registerAllProviders()` call.
14
+ */
15
+ static realtimeRegistration: Record<string, "ok" | string>;
16
+ /**
17
+ * Returns a snapshot of voice provider registration outcomes so callers
18
+ * can detect at runtime which voice handlers are usable. Useful in
19
+ * health-check endpoints and CI startup probes.
20
+ */
21
+ static getRegistrationReport(): {
22
+ realtime: Record<string, "ok" | string>;
23
+ };
10
24
  /**
11
25
  * Register all providers with the factory
12
26
  */
@@ -11,6 +11,20 @@ export class ProviderRegistry {
11
11
  static options = {
12
12
  enableManualMCP: false, // Default to disabled for safety
13
13
  };
14
+ /**
15
+ * NEW4: per-handler registration outcomes for the realtime voice
16
+ * providers. `"ok"` = registered; any other string = the error message.
17
+ * Empty until the first `registerAllProviders()` call.
18
+ */
19
+ static realtimeRegistration = {};
20
+ /**
21
+ * Returns a snapshot of voice provider registration outcomes so callers
22
+ * can detect at runtime which voice handlers are usable. Useful in
23
+ * health-check endpoints and CI startup probes.
24
+ */
25
+ static getRegistrationReport() {
26
+ return { realtime: { ...this.realtimeRegistration } };
27
+ }
14
28
  /**
15
29
  * Register all providers with the factory
16
30
  */
@@ -152,8 +166,7 @@ export class ProviderRegistry {
152
166
  const { LlamaCppProvider } = await import("../providers/llamaCpp.js");
153
167
  return new LlamaCppProvider(modelName, sdk, undefined, llamaCppCreds);
154
168
  }, process.env.LLAMACPP_MODEL || undefined, ["llamacpp", "llama.cpp", "llama-cpp"]);
155
- logger.debug("All providers registered successfully");
156
- this.registered = true;
169
+ logger.debug("All AI providers registered successfully");
157
170
  // ===== TTS HANDLER REGISTRATION =====
158
171
  try {
159
172
  // Create handler instance and register explicitly
@@ -172,6 +185,128 @@ export class ProviderRegistry {
172
185
  });
173
186
  // Don't throw - TTS is optional functionality
174
187
  }
188
+ // New TTS providers
189
+ try {
190
+ const { TTSProcessor } = await import("../utils/ttsProcessor.js");
191
+ const { OpenAITTS } = await import("../voice/providers/OpenAITTS.js");
192
+ TTSProcessor.registerHandler("openai-tts", new OpenAITTS());
193
+ }
194
+ catch (err) {
195
+ logger.debug(`[ProviderRegistry] openai-tts registration skipped: ${err instanceof Error ? err.message : String(err)}`);
196
+ }
197
+ try {
198
+ const { TTSProcessor } = await import("../utils/ttsProcessor.js");
199
+ const { ElevenLabsTTS } = await import("../voice/providers/ElevenLabsTTS.js");
200
+ const elevenLabsHandler = new ElevenLabsTTS();
201
+ TTSProcessor.registerHandler("elevenlabs", elevenLabsHandler);
202
+ TTSProcessor.registerHandler("elevenlabs-tts", elevenLabsHandler);
203
+ }
204
+ catch (err) {
205
+ logger.debug(`[ProviderRegistry] elevenlabs registration skipped: ${err instanceof Error ? err.message : String(err)}`);
206
+ }
207
+ try {
208
+ const { TTSProcessor } = await import("../utils/ttsProcessor.js");
209
+ const { AzureTTS } = await import("../voice/providers/AzureTTS.js");
210
+ TTSProcessor.registerHandler("azure-tts", new AzureTTS());
211
+ }
212
+ catch (err) {
213
+ logger.debug(`[ProviderRegistry] azure-tts registration skipped: ${err instanceof Error ? err.message : String(err)}`);
214
+ }
215
+ // ===== STT HANDLER REGISTRATION =====
216
+ try {
217
+ const { STTProcessor } = await import("../utils/sttProcessor.js");
218
+ try {
219
+ const { OpenAISTT } = await import("../voice/providers/OpenAISTT.js");
220
+ const openAISTT = new OpenAISTT();
221
+ STTProcessor.registerHandler("whisper", openAISTT);
222
+ STTProcessor.registerHandler("openai-stt", openAISTT);
223
+ }
224
+ catch (err) {
225
+ logger.debug(`[ProviderRegistry] whisper/openai-stt registration skipped: ${err instanceof Error ? err.message : String(err)}`);
226
+ }
227
+ try {
228
+ const { DeepgramSTT } = await import("../voice/providers/DeepgramSTT.js");
229
+ STTProcessor.registerHandler("deepgram", new DeepgramSTT());
230
+ }
231
+ catch (err) {
232
+ logger.debug(`[ProviderRegistry] deepgram registration skipped: ${err instanceof Error ? err.message : String(err)}`);
233
+ }
234
+ try {
235
+ const { GoogleSTT } = await import("../voice/providers/GoogleSTT.js");
236
+ STTProcessor.registerHandler("google-stt", new GoogleSTT());
237
+ }
238
+ catch (err) {
239
+ logger.debug(`[ProviderRegistry] google-stt registration skipped: ${err instanceof Error ? err.message : String(err)}`);
240
+ }
241
+ try {
242
+ const { AzureSTT } = await import("../voice/providers/AzureSTT.js");
243
+ STTProcessor.registerHandler("azure-stt", new AzureSTT());
244
+ }
245
+ catch (err) {
246
+ logger.debug(`[ProviderRegistry] azure-stt registration skipped: ${err instanceof Error ? err.message : String(err)}`);
247
+ }
248
+ logger.debug("STT handlers registered successfully", {
249
+ providers: ["whisper", "deepgram", "google-stt", "azure-stt"],
250
+ });
251
+ }
252
+ catch (sttError) {
253
+ logger.warn("Failed to register STT handlers - STT functionality will be unavailable", {
254
+ error: sttError instanceof Error ? sttError.message : String(sttError),
255
+ });
256
+ }
257
+ // ===== REALTIME HANDLER REGISTRATION =====
258
+ try {
259
+ const { RealtimeProcessor } = await import("../voice/RealtimeVoiceAPI.js");
260
+ // M9 + NEW4: track per-handler registration outcomes so the final
261
+ // log accurately reflects which voice providers succeeded vs which
262
+ // were skipped — instead of unconditionally claiming "registered
263
+ // successfully" or hiding failures at debug level.
264
+ const realtimeOutcomes = {};
265
+ try {
266
+ const { OpenAIRealtime } = await import("../voice/providers/OpenAIRealtime.js");
267
+ RealtimeProcessor.registerHandler("openai-realtime", new OpenAIRealtime());
268
+ realtimeOutcomes["openai-realtime"] = "ok";
269
+ }
270
+ catch (err) {
271
+ const msg = err instanceof Error ? err.message : String(err);
272
+ realtimeOutcomes["openai-realtime"] = msg;
273
+ // M9: promote per-handler failures to error level so users can
274
+ // see which shipped voice provider failed to register at startup.
275
+ logger.error(`[ProviderRegistry] openai-realtime registration failed: ${msg}`);
276
+ }
277
+ try {
278
+ const { GeminiLive } = await import("../voice/providers/GeminiLive.js");
279
+ RealtimeProcessor.registerHandler("gemini-live", new GeminiLive());
280
+ realtimeOutcomes["gemini-live"] = "ok";
281
+ }
282
+ catch (err) {
283
+ const msg = err instanceof Error ? err.message : String(err);
284
+ realtimeOutcomes["gemini-live"] = msg;
285
+ logger.error(`[ProviderRegistry] gemini-live registration failed: ${msg}`);
286
+ }
287
+ // NEW4: report the actual per-handler outcomes instead of an
288
+ // unconditional success log. Stored on the registry so callers can
289
+ // introspect via getRegistrationReport().
290
+ ProviderRegistry.realtimeRegistration = realtimeOutcomes;
291
+ const skipped = Object.entries(realtimeOutcomes).filter(([, v]) => v !== "ok");
292
+ if (skipped.length === 0) {
293
+ logger.info("[ProviderRegistry] Realtime handlers registered: openai-realtime, gemini-live");
294
+ }
295
+ else {
296
+ logger.warn(`[ProviderRegistry] Realtime handlers partial: ${skipped.length} skipped`, { outcomes: realtimeOutcomes });
297
+ }
298
+ }
299
+ catch (realtimeError) {
300
+ logger.warn("Failed to register Realtime handlers - Realtime functionality will be unavailable", {
301
+ error: realtimeError instanceof Error
302
+ ? realtimeError.message
303
+ : String(realtimeError),
304
+ });
305
+ }
306
+ // Mark registered ONLY after all blocks (AI + voice) attempted, so a
307
+ // subsequent registerAllProviders() call does not short-circuit when an
308
+ // optional handler block silently failed.
309
+ this.registered = true;
175
310
  }
176
311
  catch (error) {
177
312
  logger.error("Failed to register providers:", error);
@@ -191,6 +326,10 @@ export class ProviderRegistry {
191
326
  ProviderFactory.clearRegistrations();
192
327
  this.registered = false;
193
328
  this.registrationPromise = null;
329
+ // Reset realtime registration too — otherwise getRegistrationReport()
330
+ // can surface stale data from a previous run if the realtime block
331
+ // failed before reaching `realtimeRegistration = realtimeOutcomes`.
332
+ ProviderRegistry.realtimeRegistration = {};
194
333
  }
195
334
  /**
196
335
  * Set registry options (should be called before initialization)
@@ -764,6 +764,25 @@ export declare class NeuroLink {
764
764
  private validateStreamRequestOptions;
765
765
  private maybeHandleWorkflowStreamRequest;
766
766
  private runStandardStreamRequest;
767
+ /**
768
+ * TTS Mode 2 synthesis helper for the stream() pipeline.
769
+ *
770
+ * m5 — extracted from runStandardStreamRequest so the surrounding generator
771
+ * stays under the max-lines-per-function lint budget. Behaviour preserved
772
+ * exactly:
773
+ * - When Mode 2 is enabled (`tts.enabled && tts.useAiResponse`) AND the
774
+ * model produced non-empty content: synthesises one final audio buffer
775
+ * and returns it as an `audioChunk` for the caller to `yield`. Resolves
776
+ * `ttsResolver` with the `TTSResult`.
777
+ * - When Mode 2 is enabled but synthesis fails: logs a warning and resolves
778
+ * `ttsResolver` with `undefined`.
779
+ * - When Mode 2 is requested but skipped (empty content / wrong mode):
780
+ * resolves `ttsResolver` with `undefined` early so callers awaiting
781
+ * `result.audio` unblock before the surrounding `finally` cleanup
782
+ * completes (Issue 7 latency micro-opt — the finally block also resolves
783
+ * defensively, so this is a redundant early signal, not a coverage fix).
784
+ */
785
+ private synthesizeStreamModeTwo;
767
786
  /**
768
787
  * Prepare stream options: initialize memory, MCP, retrieval, orchestration,
769
788
  * Ollama tool auto-disable, factory processing, and tool detection.