@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.
- package/CHANGELOG.md +8 -0
- package/README.md +7 -1
- package/dist/auth/anthropicOAuth.d.ts +18 -3
- package/dist/auth/anthropicOAuth.js +149 -4
- package/dist/auth/providers/firebase.js +5 -1
- package/dist/auth/providers/jwt.js +5 -1
- package/dist/auth/providers/workos.js +5 -1
- package/dist/auth/sessionManager.d.ts +1 -1
- package/dist/auth/sessionManager.js +58 -27
- package/dist/browser/neurolink.min.js +354 -334
- package/dist/cli/commands/mcp.d.ts +6 -0
- package/dist/cli/commands/mcp.js +188 -181
- package/dist/cli/commands/proxy.d.ts +2 -1
- package/dist/cli/commands/proxy.js +713 -431
- package/dist/cli/commands/task.js +3 -0
- package/dist/cli/factories/commandFactory.d.ts +2 -0
- package/dist/cli/factories/commandFactory.js +38 -0
- package/dist/cli/parser.js +4 -3
- package/dist/client/aiSdkAdapter.js +3 -0
- package/dist/client/streamingClient.js +30 -10
- package/dist/core/baseProvider.d.ts +6 -1
- package/dist/core/baseProvider.js +208 -230
- package/dist/core/factory.d.ts +3 -0
- package/dist/core/factory.js +138 -188
- package/dist/core/modules/GenerationHandler.js +3 -2
- package/dist/core/redisConversationMemoryManager.js +7 -3
- package/dist/evaluation/BatchEvaluator.js +4 -1
- package/dist/evaluation/hooks/observabilityHooks.js +5 -3
- package/dist/evaluation/pipeline/evaluationPipeline.d.ts +3 -2
- package/dist/evaluation/pipeline/evaluationPipeline.js +24 -9
- package/dist/evaluation/pipeline/strategies/batchStrategy.js +6 -3
- package/dist/evaluation/pipeline/strategies/samplingStrategy.js +18 -10
- package/dist/evaluation/scorers/scorerRegistry.d.ts +3 -0
- package/dist/evaluation/scorers/scorerRegistry.js +353 -282
- package/dist/lib/auth/anthropicOAuth.d.ts +18 -3
- package/dist/lib/auth/anthropicOAuth.js +149 -4
- package/dist/lib/auth/providers/firebase.js +5 -1
- package/dist/lib/auth/providers/jwt.js +5 -1
- package/dist/lib/auth/providers/workos.js +5 -1
- package/dist/lib/auth/sessionManager.d.ts +1 -1
- package/dist/lib/auth/sessionManager.js +58 -27
- package/dist/lib/client/aiSdkAdapter.js +3 -0
- package/dist/lib/client/streamingClient.js +30 -10
- package/dist/lib/core/baseProvider.d.ts +6 -1
- package/dist/lib/core/baseProvider.js +208 -230
- package/dist/lib/core/factory.d.ts +3 -0
- package/dist/lib/core/factory.js +138 -188
- package/dist/lib/core/modules/GenerationHandler.js +3 -2
- package/dist/lib/core/redisConversationMemoryManager.js +7 -3
- package/dist/lib/evaluation/BatchEvaluator.js +4 -1
- package/dist/lib/evaluation/hooks/observabilityHooks.js +5 -3
- package/dist/lib/evaluation/pipeline/evaluationPipeline.d.ts +3 -2
- package/dist/lib/evaluation/pipeline/evaluationPipeline.js +24 -9
- package/dist/lib/evaluation/pipeline/strategies/batchStrategy.js +6 -3
- package/dist/lib/evaluation/pipeline/strategies/samplingStrategy.js +18 -10
- package/dist/lib/evaluation/scorers/scorerRegistry.d.ts +3 -0
- package/dist/lib/evaluation/scorers/scorerRegistry.js +353 -282
- package/dist/lib/mcp/toolRegistry.d.ts +2 -0
- package/dist/lib/mcp/toolRegistry.js +32 -31
- package/dist/lib/neurolink.d.ts +41 -2
- package/dist/lib/neurolink.js +1616 -1681
- package/dist/lib/observability/otelBridge.d.ts +2 -2
- package/dist/lib/observability/otelBridge.js +12 -3
- package/dist/lib/providers/amazonBedrock.js +2 -4
- package/dist/lib/providers/anthropic.d.ts +9 -5
- package/dist/lib/providers/anthropic.js +19 -14
- package/dist/lib/providers/anthropicBaseProvider.d.ts +3 -3
- package/dist/lib/providers/anthropicBaseProvider.js +5 -4
- package/dist/lib/providers/azureOpenai.d.ts +1 -1
- package/dist/lib/providers/azureOpenai.js +5 -4
- package/dist/lib/providers/googleAiStudio.js +30 -6
- package/dist/lib/providers/googleVertex.d.ts +10 -0
- package/dist/lib/providers/googleVertex.js +437 -423
- package/dist/lib/providers/huggingFace.d.ts +3 -3
- package/dist/lib/providers/huggingFace.js +6 -8
- package/dist/lib/providers/litellm.d.ts +1 -0
- package/dist/lib/providers/litellm.js +76 -55
- package/dist/lib/providers/mistral.js +2 -1
- package/dist/lib/providers/ollama.js +93 -23
- package/dist/lib/providers/openAI.d.ts +2 -0
- package/dist/lib/providers/openAI.js +141 -141
- package/dist/lib/providers/openRouter.js +2 -1
- package/dist/lib/providers/openaiCompatible.d.ts +4 -4
- package/dist/lib/providers/openaiCompatible.js +4 -4
- package/dist/lib/proxy/claudeFormat.d.ts +3 -2
- package/dist/lib/proxy/claudeFormat.js +27 -14
- package/dist/lib/proxy/cloaking/plugins/sessionIdentity.d.ts +2 -6
- package/dist/lib/proxy/cloaking/plugins/sessionIdentity.js +9 -33
- package/dist/lib/proxy/modelRouter.js +3 -0
- package/dist/lib/proxy/oauthFetch.d.ts +1 -1
- package/dist/lib/proxy/oauthFetch.js +289 -316
- package/dist/lib/proxy/proxyConfig.js +46 -24
- package/dist/lib/proxy/proxyEnv.d.ts +19 -0
- package/dist/lib/proxy/proxyEnv.js +73 -0
- package/dist/lib/proxy/proxyFetch.js +291 -217
- package/dist/lib/proxy/proxyTracer.d.ts +133 -0
- package/dist/lib/proxy/proxyTracer.js +645 -0
- package/dist/lib/proxy/rawStreamCapture.d.ts +10 -0
- package/dist/lib/proxy/rawStreamCapture.js +83 -0
- package/dist/lib/proxy/requestLogger.d.ts +32 -5
- package/dist/lib/proxy/requestLogger.js +503 -47
- package/dist/lib/proxy/sseInterceptor.d.ts +97 -0
- package/dist/lib/proxy/sseInterceptor.js +427 -0
- package/dist/lib/proxy/usageStats.d.ts +4 -3
- package/dist/lib/proxy/usageStats.js +25 -12
- package/dist/lib/rag/chunkers/MarkdownChunker.js +13 -5
- package/dist/lib/rag/chunking/markdownChunker.js +15 -6
- package/dist/lib/server/routes/claudeProxyRoutes.d.ts +17 -3
- package/dist/lib/server/routes/claudeProxyRoutes.js +3032 -1349
- package/dist/lib/services/server/ai/observability/instrumentation.d.ts +7 -1
- package/dist/lib/services/server/ai/observability/instrumentation.js +337 -161
- package/dist/lib/tasks/backends/bullmqBackend.d.ts +1 -0
- package/dist/lib/tasks/backends/bullmqBackend.js +35 -22
- package/dist/lib/tasks/store/redisTaskStore.d.ts +1 -0
- package/dist/lib/tasks/store/redisTaskStore.js +54 -39
- package/dist/lib/tasks/taskManager.d.ts +5 -0
- package/dist/lib/tasks/taskManager.js +158 -30
- package/dist/lib/telemetry/index.d.ts +2 -1
- package/dist/lib/telemetry/index.js +2 -1
- package/dist/lib/telemetry/telemetryService.d.ts +3 -0
- package/dist/lib/telemetry/telemetryService.js +69 -5
- package/dist/lib/types/cli.d.ts +10 -0
- package/dist/lib/types/proxyTypes.d.ts +160 -5
- package/dist/lib/types/streamTypes.d.ts +25 -3
- package/dist/lib/utils/messageBuilder.js +3 -2
- package/dist/lib/utils/providerHealth.d.ts +19 -0
- package/dist/lib/utils/providerHealth.js +279 -33
- package/dist/lib/utils/providerUtils.js +17 -22
- package/dist/lib/utils/toolChoice.d.ts +4 -0
- package/dist/lib/utils/toolChoice.js +7 -0
- package/dist/mcp/toolRegistry.d.ts +2 -0
- package/dist/mcp/toolRegistry.js +32 -31
- package/dist/neurolink.d.ts +41 -2
- package/dist/neurolink.js +1616 -1681
- package/dist/observability/otelBridge.d.ts +2 -2
- package/dist/observability/otelBridge.js +12 -3
- package/dist/providers/amazonBedrock.js +2 -4
- package/dist/providers/anthropic.d.ts +9 -5
- package/dist/providers/anthropic.js +19 -14
- package/dist/providers/anthropicBaseProvider.d.ts +3 -3
- package/dist/providers/anthropicBaseProvider.js +5 -4
- package/dist/providers/azureOpenai.d.ts +1 -1
- package/dist/providers/azureOpenai.js +5 -4
- package/dist/providers/googleAiStudio.js +30 -6
- package/dist/providers/googleVertex.d.ts +10 -0
- package/dist/providers/googleVertex.js +437 -423
- package/dist/providers/huggingFace.d.ts +3 -3
- package/dist/providers/huggingFace.js +6 -7
- package/dist/providers/litellm.d.ts +1 -0
- package/dist/providers/litellm.js +76 -55
- package/dist/providers/mistral.js +2 -1
- package/dist/providers/ollama.js +93 -23
- package/dist/providers/openAI.d.ts +2 -0
- package/dist/providers/openAI.js +141 -141
- package/dist/providers/openRouter.js +2 -1
- package/dist/providers/openaiCompatible.d.ts +4 -4
- package/dist/providers/openaiCompatible.js +4 -3
- package/dist/proxy/claudeFormat.d.ts +3 -2
- package/dist/proxy/claudeFormat.js +27 -14
- package/dist/proxy/cloaking/plugins/sessionIdentity.d.ts +2 -6
- package/dist/proxy/cloaking/plugins/sessionIdentity.js +9 -33
- package/dist/proxy/modelRouter.js +3 -0
- package/dist/proxy/oauthFetch.d.ts +1 -1
- package/dist/proxy/oauthFetch.js +289 -316
- package/dist/proxy/proxyConfig.js +46 -24
- package/dist/proxy/proxyEnv.d.ts +19 -0
- package/dist/proxy/proxyEnv.js +72 -0
- package/dist/proxy/proxyFetch.js +291 -217
- package/dist/proxy/proxyTracer.d.ts +133 -0
- package/dist/proxy/proxyTracer.js +644 -0
- package/dist/proxy/rawStreamCapture.d.ts +10 -0
- package/dist/proxy/rawStreamCapture.js +82 -0
- package/dist/proxy/requestLogger.d.ts +32 -5
- package/dist/proxy/requestLogger.js +503 -47
- package/dist/proxy/sseInterceptor.d.ts +97 -0
- package/dist/proxy/sseInterceptor.js +426 -0
- package/dist/proxy/usageStats.d.ts +4 -3
- package/dist/proxy/usageStats.js +25 -12
- package/dist/rag/chunkers/MarkdownChunker.js +13 -5
- package/dist/rag/chunking/markdownChunker.js +15 -6
- package/dist/server/routes/claudeProxyRoutes.d.ts +17 -3
- package/dist/server/routes/claudeProxyRoutes.js +3032 -1349
- package/dist/services/server/ai/observability/instrumentation.d.ts +7 -1
- package/dist/services/server/ai/observability/instrumentation.js +337 -161
- package/dist/tasks/backends/bullmqBackend.d.ts +1 -0
- package/dist/tasks/backends/bullmqBackend.js +35 -22
- package/dist/tasks/store/redisTaskStore.d.ts +1 -0
- package/dist/tasks/store/redisTaskStore.js +54 -39
- package/dist/tasks/taskManager.d.ts +5 -0
- package/dist/tasks/taskManager.js +158 -30
- package/dist/telemetry/index.d.ts +2 -1
- package/dist/telemetry/index.js +2 -1
- package/dist/telemetry/telemetryService.d.ts +3 -0
- package/dist/telemetry/telemetryService.js +69 -5
- package/dist/types/cli.d.ts +10 -0
- package/dist/types/proxyTypes.d.ts +160 -5
- package/dist/types/streamTypes.d.ts +25 -3
- package/dist/utils/messageBuilder.js +3 -2
- package/dist/utils/providerHealth.d.ts +19 -0
- package/dist/utils/providerHealth.js +279 -33
- package/dist/utils/providerUtils.js +18 -22
- package/dist/utils/toolChoice.d.ts +4 -0
- package/dist/utils/toolChoice.js +6 -0
- package/docs/assets/dashboards/neurolink-proxy-observability-dashboard.json +6609 -0
- package/docs/changelog.md +252 -0
- package/package.json +19 -2
- package/scripts/observability/check-proxy-telemetry.mjs +235 -0
- package/scripts/observability/docker-compose.proxy-observability.yaml +55 -0
- package/scripts/observability/import-openobserve-dashboard.mjs +240 -0
- package/scripts/observability/manage-local-openobserve.sh +215 -0
- package/scripts/observability/otel-collector.proxy-observability.yaml +78 -0
- 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
|
-
|
|
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
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
const
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
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
|
-
|
|
728
|
-
|
|
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
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
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
|
-
}
|
|
702
|
+
}
|
|
703
|
+
SpanSerializer.endSpan(enrichedGenerateSpan, SpanStatus.OK);
|
|
704
|
+
return this.enhanceResult(enhancedResult, options, startTime);
|
|
755
705
|
}
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
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')
|