@juspay/neurolink 9.41.0 → 9.42.1

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 (212) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +7 -1
  3. package/dist/auth/anthropicOAuth.d.ts +18 -3
  4. package/dist/auth/anthropicOAuth.js +149 -4
  5. package/dist/auth/providers/firebase.js +5 -1
  6. package/dist/auth/providers/jwt.js +5 -1
  7. package/dist/auth/providers/workos.js +5 -1
  8. package/dist/auth/sessionManager.d.ts +1 -1
  9. package/dist/auth/sessionManager.js +58 -27
  10. package/dist/browser/neurolink.min.js +354 -334
  11. package/dist/cli/commands/mcp.d.ts +6 -0
  12. package/dist/cli/commands/mcp.js +188 -181
  13. package/dist/cli/commands/proxy.d.ts +2 -1
  14. package/dist/cli/commands/proxy.js +713 -431
  15. package/dist/cli/commands/task.js +3 -0
  16. package/dist/cli/factories/commandFactory.d.ts +2 -0
  17. package/dist/cli/factories/commandFactory.js +38 -0
  18. package/dist/cli/parser.js +4 -3
  19. package/dist/client/aiSdkAdapter.js +3 -0
  20. package/dist/client/streamingClient.js +30 -10
  21. package/dist/core/baseProvider.d.ts +6 -1
  22. package/dist/core/baseProvider.js +208 -230
  23. package/dist/core/factory.d.ts +3 -0
  24. package/dist/core/factory.js +138 -188
  25. package/dist/core/modules/GenerationHandler.js +3 -2
  26. package/dist/core/redisConversationMemoryManager.js +7 -3
  27. package/dist/evaluation/BatchEvaluator.js +4 -1
  28. package/dist/evaluation/hooks/observabilityHooks.js +5 -3
  29. package/dist/evaluation/pipeline/evaluationPipeline.d.ts +3 -2
  30. package/dist/evaluation/pipeline/evaluationPipeline.js +24 -9
  31. package/dist/evaluation/pipeline/strategies/batchStrategy.js +6 -3
  32. package/dist/evaluation/pipeline/strategies/samplingStrategy.js +18 -10
  33. package/dist/evaluation/scorers/scorerRegistry.d.ts +3 -0
  34. package/dist/evaluation/scorers/scorerRegistry.js +353 -282
  35. package/dist/lib/auth/anthropicOAuth.d.ts +18 -3
  36. package/dist/lib/auth/anthropicOAuth.js +149 -4
  37. package/dist/lib/auth/providers/firebase.js +5 -1
  38. package/dist/lib/auth/providers/jwt.js +5 -1
  39. package/dist/lib/auth/providers/workos.js +5 -1
  40. package/dist/lib/auth/sessionManager.d.ts +1 -1
  41. package/dist/lib/auth/sessionManager.js +58 -27
  42. package/dist/lib/client/aiSdkAdapter.js +3 -0
  43. package/dist/lib/client/streamingClient.js +30 -10
  44. package/dist/lib/core/baseProvider.d.ts +6 -1
  45. package/dist/lib/core/baseProvider.js +208 -230
  46. package/dist/lib/core/factory.d.ts +3 -0
  47. package/dist/lib/core/factory.js +138 -188
  48. package/dist/lib/core/modules/GenerationHandler.js +3 -2
  49. package/dist/lib/core/redisConversationMemoryManager.js +7 -3
  50. package/dist/lib/evaluation/BatchEvaluator.js +4 -1
  51. package/dist/lib/evaluation/hooks/observabilityHooks.js +5 -3
  52. package/dist/lib/evaluation/pipeline/evaluationPipeline.d.ts +3 -2
  53. package/dist/lib/evaluation/pipeline/evaluationPipeline.js +24 -9
  54. package/dist/lib/evaluation/pipeline/strategies/batchStrategy.js +6 -3
  55. package/dist/lib/evaluation/pipeline/strategies/samplingStrategy.js +18 -10
  56. package/dist/lib/evaluation/scorers/scorerRegistry.d.ts +3 -0
  57. package/dist/lib/evaluation/scorers/scorerRegistry.js +353 -282
  58. package/dist/lib/mcp/toolRegistry.d.ts +2 -0
  59. package/dist/lib/mcp/toolRegistry.js +32 -31
  60. package/dist/lib/neurolink.d.ts +41 -2
  61. package/dist/lib/neurolink.js +1616 -1681
  62. package/dist/lib/observability/otelBridge.d.ts +2 -2
  63. package/dist/lib/observability/otelBridge.js +12 -3
  64. package/dist/lib/providers/amazonBedrock.js +2 -4
  65. package/dist/lib/providers/anthropic.d.ts +9 -5
  66. package/dist/lib/providers/anthropic.js +19 -14
  67. package/dist/lib/providers/anthropicBaseProvider.d.ts +3 -3
  68. package/dist/lib/providers/anthropicBaseProvider.js +5 -4
  69. package/dist/lib/providers/azureOpenai.d.ts +1 -1
  70. package/dist/lib/providers/azureOpenai.js +5 -4
  71. package/dist/lib/providers/googleAiStudio.js +30 -6
  72. package/dist/lib/providers/googleVertex.d.ts +10 -0
  73. package/dist/lib/providers/googleVertex.js +437 -423
  74. package/dist/lib/providers/huggingFace.d.ts +3 -3
  75. package/dist/lib/providers/huggingFace.js +6 -8
  76. package/dist/lib/providers/litellm.d.ts +1 -0
  77. package/dist/lib/providers/litellm.js +76 -55
  78. package/dist/lib/providers/mistral.js +2 -1
  79. package/dist/lib/providers/ollama.js +93 -23
  80. package/dist/lib/providers/openAI.d.ts +2 -0
  81. package/dist/lib/providers/openAI.js +141 -141
  82. package/dist/lib/providers/openRouter.js +2 -1
  83. package/dist/lib/providers/openaiCompatible.d.ts +4 -4
  84. package/dist/lib/providers/openaiCompatible.js +4 -4
  85. package/dist/lib/proxy/claudeFormat.d.ts +3 -2
  86. package/dist/lib/proxy/claudeFormat.js +27 -14
  87. package/dist/lib/proxy/cloaking/plugins/sessionIdentity.d.ts +2 -6
  88. package/dist/lib/proxy/cloaking/plugins/sessionIdentity.js +9 -33
  89. package/dist/lib/proxy/modelRouter.js +3 -0
  90. package/dist/lib/proxy/oauthFetch.d.ts +1 -1
  91. package/dist/lib/proxy/oauthFetch.js +289 -316
  92. package/dist/lib/proxy/proxyConfig.js +46 -24
  93. package/dist/lib/proxy/proxyEnv.d.ts +19 -0
  94. package/dist/lib/proxy/proxyEnv.js +73 -0
  95. package/dist/lib/proxy/proxyFetch.js +291 -217
  96. package/dist/lib/proxy/proxyTracer.d.ts +133 -0
  97. package/dist/lib/proxy/proxyTracer.js +645 -0
  98. package/dist/lib/proxy/rawStreamCapture.d.ts +10 -0
  99. package/dist/lib/proxy/rawStreamCapture.js +83 -0
  100. package/dist/lib/proxy/requestLogger.d.ts +32 -5
  101. package/dist/lib/proxy/requestLogger.js +503 -47
  102. package/dist/lib/proxy/sseInterceptor.d.ts +97 -0
  103. package/dist/lib/proxy/sseInterceptor.js +427 -0
  104. package/dist/lib/proxy/usageStats.d.ts +4 -3
  105. package/dist/lib/proxy/usageStats.js +25 -12
  106. package/dist/lib/rag/chunkers/MarkdownChunker.js +13 -5
  107. package/dist/lib/rag/chunking/markdownChunker.js +15 -6
  108. package/dist/lib/server/routes/claudeProxyRoutes.d.ts +17 -3
  109. package/dist/lib/server/routes/claudeProxyRoutes.js +3032 -1349
  110. package/dist/lib/services/server/ai/observability/instrumentation.d.ts +7 -1
  111. package/dist/lib/services/server/ai/observability/instrumentation.js +337 -161
  112. package/dist/lib/tasks/backends/bullmqBackend.d.ts +1 -0
  113. package/dist/lib/tasks/backends/bullmqBackend.js +35 -22
  114. package/dist/lib/tasks/store/redisTaskStore.d.ts +1 -0
  115. package/dist/lib/tasks/store/redisTaskStore.js +54 -39
  116. package/dist/lib/tasks/taskManager.d.ts +5 -0
  117. package/dist/lib/tasks/taskManager.js +158 -30
  118. package/dist/lib/telemetry/index.d.ts +2 -1
  119. package/dist/lib/telemetry/index.js +2 -1
  120. package/dist/lib/telemetry/telemetryService.d.ts +3 -0
  121. package/dist/lib/telemetry/telemetryService.js +69 -5
  122. package/dist/lib/types/cli.d.ts +10 -0
  123. package/dist/lib/types/proxyTypes.d.ts +160 -5
  124. package/dist/lib/types/streamTypes.d.ts +25 -3
  125. package/dist/lib/utils/messageBuilder.js +3 -2
  126. package/dist/lib/utils/providerHealth.d.ts +19 -0
  127. package/dist/lib/utils/providerHealth.js +279 -33
  128. package/dist/lib/utils/providerUtils.js +17 -22
  129. package/dist/lib/utils/toolChoice.d.ts +4 -0
  130. package/dist/lib/utils/toolChoice.js +7 -0
  131. package/dist/mcp/toolRegistry.d.ts +2 -0
  132. package/dist/mcp/toolRegistry.js +32 -31
  133. package/dist/neurolink.d.ts +41 -2
  134. package/dist/neurolink.js +1616 -1681
  135. package/dist/observability/otelBridge.d.ts +2 -2
  136. package/dist/observability/otelBridge.js +12 -3
  137. package/dist/providers/amazonBedrock.js +2 -4
  138. package/dist/providers/anthropic.d.ts +9 -5
  139. package/dist/providers/anthropic.js +19 -14
  140. package/dist/providers/anthropicBaseProvider.d.ts +3 -3
  141. package/dist/providers/anthropicBaseProvider.js +5 -4
  142. package/dist/providers/azureOpenai.d.ts +1 -1
  143. package/dist/providers/azureOpenai.js +5 -4
  144. package/dist/providers/googleAiStudio.js +30 -6
  145. package/dist/providers/googleVertex.d.ts +10 -0
  146. package/dist/providers/googleVertex.js +437 -423
  147. package/dist/providers/huggingFace.d.ts +3 -3
  148. package/dist/providers/huggingFace.js +6 -7
  149. package/dist/providers/litellm.d.ts +1 -0
  150. package/dist/providers/litellm.js +76 -55
  151. package/dist/providers/mistral.js +2 -1
  152. package/dist/providers/ollama.js +93 -23
  153. package/dist/providers/openAI.d.ts +2 -0
  154. package/dist/providers/openAI.js +141 -141
  155. package/dist/providers/openRouter.js +2 -1
  156. package/dist/providers/openaiCompatible.d.ts +4 -4
  157. package/dist/providers/openaiCompatible.js +4 -3
  158. package/dist/proxy/claudeFormat.d.ts +3 -2
  159. package/dist/proxy/claudeFormat.js +27 -14
  160. package/dist/proxy/cloaking/plugins/sessionIdentity.d.ts +2 -6
  161. package/dist/proxy/cloaking/plugins/sessionIdentity.js +9 -33
  162. package/dist/proxy/modelRouter.js +3 -0
  163. package/dist/proxy/oauthFetch.d.ts +1 -1
  164. package/dist/proxy/oauthFetch.js +289 -316
  165. package/dist/proxy/proxyConfig.js +46 -24
  166. package/dist/proxy/proxyEnv.d.ts +19 -0
  167. package/dist/proxy/proxyEnv.js +72 -0
  168. package/dist/proxy/proxyFetch.js +291 -217
  169. package/dist/proxy/proxyTracer.d.ts +133 -0
  170. package/dist/proxy/proxyTracer.js +644 -0
  171. package/dist/proxy/rawStreamCapture.d.ts +10 -0
  172. package/dist/proxy/rawStreamCapture.js +82 -0
  173. package/dist/proxy/requestLogger.d.ts +32 -5
  174. package/dist/proxy/requestLogger.js +503 -47
  175. package/dist/proxy/sseInterceptor.d.ts +97 -0
  176. package/dist/proxy/sseInterceptor.js +426 -0
  177. package/dist/proxy/usageStats.d.ts +4 -3
  178. package/dist/proxy/usageStats.js +25 -12
  179. package/dist/rag/chunkers/MarkdownChunker.js +13 -5
  180. package/dist/rag/chunking/markdownChunker.js +15 -6
  181. package/dist/server/routes/claudeProxyRoutes.d.ts +17 -3
  182. package/dist/server/routes/claudeProxyRoutes.js +3032 -1349
  183. package/dist/services/server/ai/observability/instrumentation.d.ts +7 -1
  184. package/dist/services/server/ai/observability/instrumentation.js +337 -161
  185. package/dist/tasks/backends/bullmqBackend.d.ts +1 -0
  186. package/dist/tasks/backends/bullmqBackend.js +35 -22
  187. package/dist/tasks/store/redisTaskStore.d.ts +1 -0
  188. package/dist/tasks/store/redisTaskStore.js +54 -39
  189. package/dist/tasks/taskManager.d.ts +5 -0
  190. package/dist/tasks/taskManager.js +158 -30
  191. package/dist/telemetry/index.d.ts +2 -1
  192. package/dist/telemetry/index.js +2 -1
  193. package/dist/telemetry/telemetryService.d.ts +3 -0
  194. package/dist/telemetry/telemetryService.js +69 -5
  195. package/dist/types/cli.d.ts +10 -0
  196. package/dist/types/proxyTypes.d.ts +160 -5
  197. package/dist/types/streamTypes.d.ts +25 -3
  198. package/dist/utils/messageBuilder.js +3 -2
  199. package/dist/utils/providerHealth.d.ts +19 -0
  200. package/dist/utils/providerHealth.js +279 -33
  201. package/dist/utils/providerUtils.js +18 -22
  202. package/dist/utils/toolChoice.d.ts +4 -0
  203. package/dist/utils/toolChoice.js +6 -0
  204. package/docs/assets/dashboards/neurolink-proxy-observability-dashboard.json +6609 -0
  205. package/docs/changelog.md +252 -0
  206. package/package.json +19 -2
  207. package/scripts/observability/check-proxy-telemetry.mjs +235 -0
  208. package/scripts/observability/docker-compose.proxy-observability.yaml +55 -0
  209. package/scripts/observability/import-openobserve-dashboard.mjs +240 -0
  210. package/scripts/observability/manage-local-openobserve.sh +215 -0
  211. package/scripts/observability/otel-collector.proxy-observability.yaml +78 -0
  212. package/scripts/observability/proxy-observability.env.example +23 -0
@@ -6,9 +6,9 @@ import { MiddlewareFactory } from "../middleware/factory.js";
6
6
  import { SpanStatus, SpanType } from "../observability/types/spanTypes.js";
7
7
  import { SpanSerializer } from "../observability/utils/spanSerializer.js";
8
8
  import { ATTR, tracers } from "../telemetry/index.js";
9
- import { calculateCost } from "../utils/pricing.js";
10
9
  import { isAbortError } from "../utils/errorHandling.js";
11
10
  import { logger } from "../utils/logger.js";
11
+ import { calculateCost } from "../utils/pricing.js";
12
12
  import { composeAbortSignals, createTimeoutController, TimeoutError, } from "../utils/timeout.js";
13
13
  import { shouldDisableBuiltinTools } from "../utils/toolUtils.js";
14
14
  import { getKeyCount, getKeysAsString } from "../utils/transformationUtils.js";
@@ -522,242 +522,220 @@ export class BaseProvider {
522
522
  });
523
523
  // Set this span as the active context so child spans (GenerationHandler, etc.) become descendants
524
524
  const activeCtx = trace.setSpan(context.active(), otelSpan);
525
- let otelSpanEnded = false;
526
- return await context.with(activeCtx, async () => {
525
+ const otelSpanState = { ended: false };
526
+ return await context.with(activeCtx, async () => this.runGenerateInActiveContext(options, startTime, metricsSpan, otelSpan, otelSpanState));
527
+ }
528
+ /**
529
+ * Alias for generate method - implements AIProvider interface
530
+ */
531
+ async gen(optionsOrPrompt, analysisSchema) {
532
+ return this.generate(optionsOrPrompt, analysisSchema);
533
+ }
534
+ async runGenerateInActiveContext(options, startTime, metricsSpan, otelSpan, otelSpanState) {
535
+ try {
536
+ if (options.output?.mode === "video") {
537
+ return await this.handleVideoGeneration(options, startTime);
538
+ }
539
+ const isImageModel = IMAGE_GENERATION_MODELS.some((m) => this.modelName.includes(m));
540
+ if (isImageModel) {
541
+ logger.info(`Image generation model detected, routing to executeImageGeneration`, {
542
+ provider: this.providerName,
543
+ model: this.modelName,
544
+ });
545
+ const imageResult = await this.executeImageGeneration(options);
546
+ return await this.enhanceResult(imageResult, options, startTime);
547
+ }
548
+ if (options.tts?.enabled && !options.tts?.useAiResponse) {
549
+ return this.handleDirectTTSSynthesis(options, startTime);
550
+ }
551
+ const { tools, model } = await this.prepareGenerationContext(options);
552
+ const messages = await this.buildMessages(options);
553
+ const videoFrameResult = await this.handleVideoFrameGeneration(options, messages, model, startTime);
554
+ if (videoFrameResult) {
555
+ return videoFrameResult;
556
+ }
557
+ return await this.executeStandardGenerateFlow(options, startTime, metricsSpan, model, messages, tools);
558
+ }
559
+ catch (error) {
560
+ SpanSerializer.endSpan(metricsSpan, SpanStatus.ERROR, error instanceof Error ? error.message : String(error));
561
+ otelSpan.setStatus({
562
+ code: SpanStatusCode.ERROR,
563
+ message: error instanceof Error ? error.message : String(error),
564
+ });
565
+ otelSpan.end();
566
+ otelSpanState.ended = true;
567
+ if (isAbortError(error)) {
568
+ logger.info(`Generate aborted for ${this.providerName}`, {
569
+ error: error instanceof Error ? error.message : String(error),
570
+ });
571
+ }
572
+ else {
573
+ logger.error(`Generate failed for ${this.providerName}:`, error);
574
+ }
575
+ throw this.handleProviderError(error);
576
+ }
577
+ finally {
578
+ if (!otelSpanState.ended) {
579
+ otelSpan.setStatus({ code: SpanStatusCode.OK });
580
+ otelSpan.end();
581
+ }
582
+ }
583
+ }
584
+ async handleDirectTTSSynthesis(options, startTime) {
585
+ const textToSynthesize = options.prompt ?? options.input?.text ?? "";
586
+ const baseResult = {
587
+ content: textToSynthesize,
588
+ provider: options.provider ?? this.providerName,
589
+ model: this.modelName,
590
+ usage: { input: 0, output: 0, total: 0 },
591
+ };
592
+ try {
593
+ if (!options.tts) {
594
+ return this.enhanceResult(baseResult, options, startTime);
595
+ }
596
+ baseResult.audio = await TTSProcessor.synthesize(textToSynthesize, options.provider ?? this.providerName, options.tts);
597
+ }
598
+ catch (ttsError) {
599
+ logger.error(`TTS synthesis failed in Mode 1 (direct input synthesis):`, ttsError);
600
+ }
601
+ return this.enhanceResult(baseResult, options, startTime);
602
+ }
603
+ async handleVideoFrameGeneration(options, messages, model, startTime) {
604
+ if (!hasVideoFrames(messages)) {
605
+ return null;
606
+ }
607
+ const videoAnalysisResult = await executeVideoAnalysis(messages, {
608
+ provider: options.provider,
609
+ providerName: this.providerName,
610
+ region: options.region,
611
+ });
612
+ const userText = messages
613
+ .filter((m) => m.role === "user")
614
+ .flatMap((m) => Array.isArray(m.content)
615
+ ? m.content
616
+ .filter((p) => p.type === "text")
617
+ .map((p) => p.text)
618
+ : [typeof m.content === "string" ? m.content : ""])
619
+ .filter(Boolean)
620
+ .join("\n")
621
+ .trim();
622
+ let formattedContent = videoAnalysisResult;
623
+ let usage = { input: 0, output: 0, total: 0 };
624
+ if (options.systemPrompt) {
527
625
  try {
528
- // ===== VIDEO GENERATION MODE =====
529
- // Generate video from image + prompt using Veo 3.1
530
- if (options.output?.mode === "video") {
531
- return await this.handleVideoGeneration(options, startTime);
532
- }
533
- // ===== IMAGE GENERATION MODE =====
534
- // Route to executeImageGeneration for image generation models
535
- const isImageModel = IMAGE_GENERATION_MODELS.some((m) => this.modelName.includes(m));
536
- if (isImageModel) {
537
- logger.info(`Image generation model detected, routing to executeImageGeneration`, {
538
- provider: this.providerName,
539
- model: this.modelName,
540
- });
541
- const imageResult = await this.executeImageGeneration(options);
542
- return await this.enhanceResult(imageResult, options, startTime);
543
- }
544
- // ===== TTS MODE 1: Direct Input Synthesis (useAiResponse=false) =====
545
- // Synthesize input text directly without AI generation
546
- // This is optimal for simple read-aloud scenarios
547
- if (options.tts?.enabled && !options.tts?.useAiResponse) {
548
- const textToSynthesize = options.prompt ?? options.input?.text ?? "";
549
- // Build base result structure - common to both paths
550
- const baseResult = {
551
- content: textToSynthesize,
552
- provider: options.provider ?? this.providerName,
553
- model: this.modelName,
554
- usage: { input: 0, output: 0, total: 0 },
555
- };
556
- try {
557
- const ttsResult = await TTSProcessor.synthesize(textToSynthesize, options.provider ?? this.providerName, options.tts);
558
- baseResult.audio = ttsResult;
559
- }
560
- catch (ttsError) {
561
- logger.error(`TTS synthesis failed in Mode 1 (direct input synthesis):`, ttsError);
562
- // baseResult remains without audio - graceful degradation
563
- }
564
- // Call enhanceResult for consistency - enables analytics/evaluation for TTS-only requests
565
- return await this.enhanceResult(baseResult, options, startTime);
566
- }
567
- // ===== Normal AI Generation Flow =====
568
- const { tools, model } = await this.prepareGenerationContext(options);
569
- const messages = await this.buildMessages(options);
570
- // ===== VIDEO ANALYSIS FROM MESSAGES CONTENT =====
571
- // Check if video files are present in messages content array
572
- // If video analysis is needed, perform it via Gemini, then pass through Claude for formatting
573
- if (hasVideoFrames(messages)) {
574
- const videoAnalysisResult = await executeVideoAnalysis(messages, {
575
- provider: options.provider,
576
- providerName: this.providerName,
577
- region: options.region,
578
- // Don't pass the main conversation model — video analysis uses
579
- // Google's Gemini API (generateContent) which only supports Gemini models.
580
- // Let videoAnalysisProcessor use its own default (gemini-2.5-flash).
581
- });
582
- // Extract user's original text from messages (excluding image parts)
583
- const userTextParts = messages
584
- .filter((m) => m.role === "user")
585
- .flatMap((m) => Array.isArray(m.content)
586
- ? m.content
587
- .filter((p) => p.type === "text")
588
- .map((p) => p.text)
589
- : [typeof m.content === "string" ? m.content : ""])
590
- .filter(Boolean);
591
- const userText = userTextParts.join("\n").trim();
592
- // Pass Gemini's analysis through Claude for structured JSON formatting
593
- // The system prompt (from Curator) includes JSON_REPORT_PROMPT_SUFFIX
594
- // which instructs Claude to output {"summary": "...", "details": "..."}
595
- let formattedContent = videoAnalysisResult;
596
- let usage = { input: 0, output: 0, total: 0 };
597
- if (options.systemPrompt) {
598
- try {
599
- const formattingPrompt = userText
600
- ? `The user asked: "${userText}"\n\nHere is the video/image analysis result from the visual analysis system:\n\n${videoAnalysisResult}\n\nBased on this analysis, provide your response.`
601
- : `Here is a video/image analysis result from the visual analysis system:\n\n${videoAnalysisResult}\n\nBased on this analysis, provide your response.`;
602
- logger.debug("[VideoAnalysis] Formatting via Claude", {
603
- userTextLength: userText.length,
604
- analysisLength: videoAnalysisResult.length,
605
- });
606
- const formattedResult = await generateText({
607
- model,
608
- system: options.systemPrompt,
609
- messages: [
610
- { role: "user", content: formattingPrompt },
611
- ],
612
- maxOutputTokens: options.maxTokens || 8192,
613
- temperature: 0.3,
614
- abortSignal: options.abortSignal,
615
- experimental_telemetry: this.telemetryHandler?.getTelemetryConfig(options, "generate"),
616
- });
617
- formattedContent = formattedResult.text;
618
- usage = {
619
- input: formattedResult.usage?.inputTokens || 0,
620
- output: formattedResult.usage?.outputTokens || 0,
621
- total: (formattedResult.usage?.inputTokens || 0) +
622
- (formattedResult.usage?.outputTokens || 0),
623
- };
624
- logger.debug("[VideoAnalysis] Claude formatting complete", {
625
- formattedLength: formattedContent.length,
626
- usage,
627
- });
628
- }
629
- catch (error) {
630
- logger.warn("[VideoAnalysis] Claude formatting failed, using raw Gemini output", {
631
- error: error instanceof Error ? error.message : String(error),
632
- });
633
- // formattedContent remains as raw videoAnalysisResult (graceful degradation)
634
- }
635
- }
636
- const videoResult = {
637
- content: formattedContent,
638
- provider: options.provider ?? this.providerName,
639
- model: this.modelName,
640
- usage,
641
- };
642
- return await this.enhanceResult(videoResult, options, startTime);
643
- }
644
- // Compose timeout signal with user-provided abort signal (mirrors stream path)
645
- const timeoutController = createTimeoutController(options.timeout, this.providerName, "generate");
646
- const composedSignal = composeAbortSignals(options.abortSignal, timeoutController?.controller.signal);
647
- const composedOptions = composedSignal
648
- ? { ...options, abortSignal: composedSignal }
649
- : options;
650
- let generateResult;
651
- try {
652
- generateResult = await this.executeGeneration(model, messages, tools, composedOptions);
653
- }
654
- finally {
655
- timeoutController?.cleanup();
656
- }
657
- this.analyzeAIResponse(generateResult);
658
- this.logGenerationComplete(generateResult);
659
- const responseTime = Date.now() - startTime;
660
- await this.recordPerformanceMetrics(generateResult.usage, responseTime);
661
- const { toolsUsed, toolExecutions } = this.extractToolInformation(generateResult);
662
- let enhancedResult = this.formatEnhancedResult(generateResult, tools, toolsUsed, toolExecutions, options);
663
- // ===== TTS MODE 2: AI Response Synthesis (useAiResponse=true) =====
664
- // Synthesize AI-generated response after generation completes
665
- if (options.tts?.enabled && options.tts?.useAiResponse) {
666
- const aiResponse = enhancedResult.content;
667
- const provider = options.provider ?? this.providerName;
668
- // Validate AI response and provider before synthesis
669
- if (aiResponse && provider) {
670
- try {
671
- const ttsResult = await TTSProcessor.synthesize(aiResponse, provider, options.tts);
672
- // Add audio to enhanced result (TTSProcessor already includes latency in metadata)
673
- enhancedResult = {
674
- ...enhancedResult,
675
- audio: ttsResult,
676
- };
677
- }
678
- catch (ttsError) {
679
- // Log TTS error but continue with text-only result
680
- logger.error(`TTS synthesis failed in Mode 2 (AI response synthesis):`, ttsError);
681
- // enhancedResult remains unchanged (no audio field added)
682
- }
683
- }
684
- else {
685
- logger.warn(`TTS synthesis skipped despite being enabled`, {
686
- provider: this.providerName,
687
- hasAiResponse: !!aiResponse,
688
- aiResponseLength: aiResponse?.length ?? 0,
689
- hasProvider: !!provider,
690
- ttsConfig: {
691
- enabled: options.tts?.enabled,
692
- useAiResponse: options.tts?.useAiResponse,
693
- },
694
- reason: !aiResponse
695
- ? "AI response is empty or undefined"
696
- : "Provider is missing",
697
- });
698
- }
699
- }
700
- // Observability: record successful generate span with token/cost data
701
- let enrichedGenerateSpan = { ...metricsSpan };
702
- if (enhancedResult?.usage) {
703
- enrichedGenerateSpan = SpanSerializer.enrichWithTokenUsage(enrichedGenerateSpan, {
704
- promptTokens: enhancedResult.usage.input || 0,
705
- completionTokens: enhancedResult.usage.output || 0,
706
- totalTokens: enhancedResult.usage.total || 0,
707
- });
708
- const cost = calculateCost(this.providerName, this.modelName, {
709
- input: enhancedResult.usage.input || 0,
710
- output: enhancedResult.usage.output || 0,
711
- total: enhancedResult.usage.total || 0,
712
- });
713
- if (cost && cost > 0) {
714
- enrichedGenerateSpan = SpanSerializer.enrichWithCost(enrichedGenerateSpan, {
715
- totalCost: cost,
716
- });
717
- }
718
- }
719
- const _endedGenerateSpan = SpanSerializer.endSpan(enrichedGenerateSpan, SpanStatus.OK);
720
- // Note: Do NOT record to getMetricsAggregator() here — the neurolink.ts
721
- // generation:end listener creates an authoritative span with richer context
722
- // (provider name, model, input/output) and records to both aggregators.
723
- // Recording here would double-count cost and token metrics.
724
- return await this.enhanceResult(enhancedResult, options, startTime);
626
+ const formattingPrompt = userText
627
+ ? `The user asked: "${userText}"\n\nHere is the video/image analysis result from the visual analysis system:\n\n${videoAnalysisResult}\n\nBased on this analysis, provide your response.`
628
+ : `Here is a video/image analysis result from the visual analysis system:\n\n${videoAnalysisResult}\n\nBased on this analysis, provide your response.`;
629
+ logger.debug("[VideoAnalysis] Formatting via Claude", {
630
+ userTextLength: userText.length,
631
+ analysisLength: videoAnalysisResult.length,
632
+ });
633
+ const formattedResult = await generateText({
634
+ model,
635
+ system: options.systemPrompt,
636
+ messages: [{ role: "user", content: formattingPrompt }],
637
+ maxOutputTokens: options.maxTokens || 8192,
638
+ temperature: 0.3,
639
+ abortSignal: options.abortSignal,
640
+ experimental_telemetry: this.telemetryHandler?.getTelemetryConfig(options, "generate"),
641
+ });
642
+ formattedContent = formattedResult.text;
643
+ usage = {
644
+ input: formattedResult.usage?.inputTokens || 0,
645
+ output: formattedResult.usage?.outputTokens || 0,
646
+ total: (formattedResult.usage?.inputTokens || 0) +
647
+ (formattedResult.usage?.outputTokens || 0),
648
+ };
649
+ logger.debug("[VideoAnalysis] Claude formatting complete", {
650
+ formattedLength: formattedContent.length,
651
+ usage,
652
+ });
725
653
  }
726
654
  catch (error) {
727
- // Observability: record failed generate span
728
- const _endedGenerateSpan = SpanSerializer.endSpan(metricsSpan, SpanStatus.ERROR, error instanceof Error ? error.message : String(error));
729
- // Note: Do NOT record to getMetricsAggregator() here — neurolink.ts
730
- // handles authoritative metrics recording to avoid double-counting.
731
- otelSpan.setStatus({
732
- code: SpanStatusCode.ERROR,
733
- message: error instanceof Error ? error.message : String(error),
655
+ logger.warn("[VideoAnalysis] Claude formatting failed, using raw Gemini output", {
656
+ error: error instanceof Error ? error.message : String(error),
734
657
  });
735
- otelSpan.end();
736
- otelSpanEnded = true;
737
- // Abort errors are expected when a generation is cancelled — log at info, not error
738
- if (isAbortError(error)) {
739
- logger.info(`Generate aborted for ${this.providerName}`, {
740
- error: error instanceof Error ? error.message : String(error),
741
- });
742
- }
743
- else {
744
- logger.error(`Generate failed for ${this.providerName}:`, error);
745
- }
746
- throw this.handleProviderError(error);
747
658
  }
748
- finally {
749
- if (!otelSpanEnded) {
750
- otelSpan.setStatus({ code: SpanStatusCode.OK });
751
- otelSpan.end();
752
- }
659
+ }
660
+ return this.enhanceResult({
661
+ content: formattedContent,
662
+ provider: options.provider ?? this.providerName,
663
+ model: this.modelName,
664
+ usage,
665
+ }, options, startTime);
666
+ }
667
+ async executeStandardGenerateFlow(options, startTime, metricsSpan, model, messages, tools) {
668
+ const timeoutController = createTimeoutController(options.timeout, this.providerName, "generate");
669
+ const composedSignal = composeAbortSignals(options.abortSignal, timeoutController?.controller.signal);
670
+ const composedOptions = composedSignal
671
+ ? { ...options, abortSignal: composedSignal }
672
+ : options;
673
+ let generateResult;
674
+ try {
675
+ generateResult = await this.executeGeneration(model, messages, tools, composedOptions);
676
+ }
677
+ finally {
678
+ timeoutController?.cleanup();
679
+ }
680
+ this.analyzeAIResponse(generateResult);
681
+ this.logGenerationComplete(generateResult);
682
+ const responseTime = Date.now() - startTime;
683
+ await this.recordPerformanceMetrics(generateResult.usage, responseTime);
684
+ const { toolsUsed, toolExecutions } = this.extractToolInformation(generateResult);
685
+ let enhancedResult = this.formatEnhancedResult(generateResult, tools, toolsUsed, toolExecutions, options);
686
+ enhancedResult = await this.synthesizeAIResponseIfNeeded(enhancedResult, options);
687
+ let enrichedGenerateSpan = { ...metricsSpan };
688
+ if (enhancedResult?.usage) {
689
+ enrichedGenerateSpan = SpanSerializer.enrichWithTokenUsage(enrichedGenerateSpan, {
690
+ promptTokens: enhancedResult.usage.input || 0,
691
+ completionTokens: enhancedResult.usage.output || 0,
692
+ totalTokens: enhancedResult.usage.total || 0,
693
+ });
694
+ const cost = calculateCost(this.providerName, this.modelName, {
695
+ input: enhancedResult.usage.input || 0,
696
+ output: enhancedResult.usage.output || 0,
697
+ total: enhancedResult.usage.total || 0,
698
+ });
699
+ if (cost && cost > 0) {
700
+ enrichedGenerateSpan = SpanSerializer.enrichWithCost(enrichedGenerateSpan, { totalCost: cost });
753
701
  }
754
- }); // end context.with
702
+ }
703
+ SpanSerializer.endSpan(enrichedGenerateSpan, SpanStatus.OK);
704
+ return this.enhanceResult(enhancedResult, options, startTime);
755
705
  }
756
- /**
757
- * Alias for generate method - implements AIProvider interface
758
- */
759
- async gen(optionsOrPrompt, analysisSchema) {
760
- return this.generate(optionsOrPrompt, analysisSchema);
706
+ async synthesizeAIResponseIfNeeded(enhancedResult, options) {
707
+ if (!options.tts?.enabled || !options.tts?.useAiResponse) {
708
+ return enhancedResult;
709
+ }
710
+ const aiResponse = enhancedResult.content;
711
+ const provider = options.provider ?? this.providerName;
712
+ if (!aiResponse || !provider) {
713
+ logger.warn(`TTS synthesis skipped despite being enabled`, {
714
+ provider: this.providerName,
715
+ hasAiResponse: !!aiResponse,
716
+ aiResponseLength: aiResponse?.length ?? 0,
717
+ hasProvider: !!provider,
718
+ ttsConfig: {
719
+ enabled: options.tts?.enabled,
720
+ useAiResponse: options.tts?.useAiResponse,
721
+ },
722
+ reason: !aiResponse
723
+ ? "AI response is empty or undefined"
724
+ : "Provider is missing",
725
+ });
726
+ return enhancedResult;
727
+ }
728
+ try {
729
+ const ttsResult = await TTSProcessor.synthesize(aiResponse, provider, options.tts);
730
+ return {
731
+ ...enhancedResult,
732
+ audio: ttsResult,
733
+ };
734
+ }
735
+ catch (ttsError) {
736
+ logger.error(`TTS synthesis failed in Mode 2 (AI response synthesis):`, ttsError);
737
+ return enhancedResult;
738
+ }
761
739
  }
762
740
  /**
763
741
  * BACKWARD COMPATIBILITY: Legacy generateText method
@@ -15,6 +15,9 @@ export declare class AIProviderFactory {
15
15
  * Prevents hanging on non-responsive endpoints
16
16
  */
17
17
  private static initializeDynamicProviderWithTimeout;
18
+ private static resolveModelFromEnvironment;
19
+ private static resolveDynamicModelName;
20
+ private static createResolvedProvider;
18
21
  /**
19
22
  * Create a provider instance for the specified provider type
20
23
  * @param providerName - Name of the provider ('vertex', 'bedrock', 'openai')