@juspay/neurolink 9.23.0 → 9.25.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 (225) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +10 -13
  3. package/dist/adapters/tts/googleTTSHandler.js +26 -1
  4. package/dist/adapters/video/vertexVideoHandler.js +23 -17
  5. package/dist/cli/commands/config.d.ts +3 -3
  6. package/dist/cli/commands/observability.d.ts +53 -0
  7. package/dist/cli/commands/observability.js +453 -0
  8. package/dist/cli/commands/telemetry.d.ts +63 -0
  9. package/dist/cli/commands/telemetry.js +689 -0
  10. package/dist/cli/factories/commandFactory.d.ts +34 -0
  11. package/dist/cli/factories/commandFactory.js +321 -116
  12. package/dist/cli/parser.js +6 -9
  13. package/dist/cli/utils/formatters.d.ts +13 -0
  14. package/dist/cli/utils/formatters.js +23 -0
  15. package/dist/constants/contextWindows.js +6 -0
  16. package/dist/constants/enums.d.ts +6 -0
  17. package/dist/constants/enums.js +8 -2
  18. package/dist/context/budgetChecker.js +75 -48
  19. package/dist/context/contextCompactor.js +135 -127
  20. package/dist/core/baseProvider.d.ts +5 -0
  21. package/dist/core/baseProvider.js +158 -102
  22. package/dist/core/conversationMemoryInitializer.js +7 -4
  23. package/dist/core/conversationMemoryManager.d.ts +2 -0
  24. package/dist/core/conversationMemoryManager.js +6 -2
  25. package/dist/core/modules/GenerationHandler.d.ts +2 -2
  26. package/dist/core/modules/GenerationHandler.js +12 -12
  27. package/dist/evaluation/ragasEvaluator.js +39 -19
  28. package/dist/evaluation/scoring.js +46 -20
  29. package/dist/features/ppt/index.d.ts +1 -1
  30. package/dist/features/ppt/index.js +1 -1
  31. package/dist/features/ppt/presentationOrchestrator.js +23 -0
  32. package/dist/features/ppt/slideGenerator.js +13 -0
  33. package/dist/features/ppt/slideRenderers.d.ts +1 -1
  34. package/dist/features/ppt/slideRenderers.js +6 -4
  35. package/dist/features/ppt/slideTypeInference.d.ts +1 -1
  36. package/dist/features/ppt/slideTypeInference.js +75 -73
  37. package/dist/files/fileTools.d.ts +6 -6
  38. package/dist/index.d.ts +46 -12
  39. package/dist/index.js +79 -17
  40. package/dist/lib/adapters/tts/googleTTSHandler.js +26 -1
  41. package/dist/lib/adapters/video/vertexVideoHandler.js +23 -17
  42. package/dist/lib/constants/contextWindows.js +6 -0
  43. package/dist/lib/constants/enums.d.ts +6 -0
  44. package/dist/lib/constants/enums.js +8 -2
  45. package/dist/lib/context/budgetChecker.js +75 -48
  46. package/dist/lib/context/contextCompactor.js +135 -127
  47. package/dist/lib/core/baseProvider.d.ts +5 -0
  48. package/dist/lib/core/baseProvider.js +158 -102
  49. package/dist/lib/core/conversationMemoryInitializer.js +7 -4
  50. package/dist/lib/core/conversationMemoryManager.d.ts +2 -0
  51. package/dist/lib/core/conversationMemoryManager.js +6 -2
  52. package/dist/lib/core/modules/GenerationHandler.d.ts +2 -2
  53. package/dist/lib/core/modules/GenerationHandler.js +12 -12
  54. package/dist/lib/evaluation/ragasEvaluator.js +39 -19
  55. package/dist/lib/evaluation/scoring.js +46 -20
  56. package/dist/lib/features/ppt/index.d.ts +1 -1
  57. package/dist/lib/features/ppt/index.js +1 -1
  58. package/dist/lib/features/ppt/presentationOrchestrator.js +23 -0
  59. package/dist/lib/features/ppt/slideGenerator.js +13 -0
  60. package/dist/lib/features/ppt/slideRenderers.d.ts +1 -1
  61. package/dist/lib/features/ppt/slideRenderers.js +6 -4
  62. package/dist/lib/features/ppt/slideTypeInference.d.ts +1 -1
  63. package/dist/lib/features/ppt/slideTypeInference.js +75 -73
  64. package/dist/lib/files/fileTools.d.ts +6 -6
  65. package/dist/lib/index.d.ts +46 -12
  66. package/dist/lib/index.js +79 -17
  67. package/dist/lib/mcp/httpRateLimiter.js +39 -12
  68. package/dist/lib/mcp/httpRetryHandler.js +22 -1
  69. package/dist/lib/mcp/mcpClientFactory.js +13 -15
  70. package/dist/lib/memory/memoryRetrievalTools.js +22 -0
  71. package/dist/lib/neurolink.d.ts +64 -72
  72. package/dist/lib/neurolink.js +984 -566
  73. package/dist/lib/observability/exporterRegistry.d.ts +152 -0
  74. package/dist/lib/observability/exporterRegistry.js +414 -0
  75. package/dist/lib/observability/exporters/arizeExporter.d.ts +32 -0
  76. package/dist/lib/observability/exporters/arizeExporter.js +139 -0
  77. package/dist/lib/observability/exporters/baseExporter.d.ts +117 -0
  78. package/dist/lib/observability/exporters/baseExporter.js +191 -0
  79. package/dist/lib/observability/exporters/braintrustExporter.d.ts +30 -0
  80. package/dist/lib/observability/exporters/braintrustExporter.js +155 -0
  81. package/dist/lib/observability/exporters/datadogExporter.d.ts +37 -0
  82. package/dist/lib/observability/exporters/datadogExporter.js +197 -0
  83. package/dist/lib/observability/exporters/index.d.ts +13 -0
  84. package/dist/lib/observability/exporters/index.js +14 -0
  85. package/dist/lib/observability/exporters/laminarExporter.d.ts +48 -0
  86. package/dist/lib/observability/exporters/laminarExporter.js +303 -0
  87. package/dist/lib/observability/exporters/langfuseExporter.d.ts +47 -0
  88. package/dist/lib/observability/exporters/langfuseExporter.js +200 -0
  89. package/dist/lib/observability/exporters/langsmithExporter.d.ts +26 -0
  90. package/dist/lib/observability/exporters/langsmithExporter.js +124 -0
  91. package/dist/lib/observability/exporters/otelExporter.d.ts +39 -0
  92. package/dist/lib/observability/exporters/otelExporter.js +165 -0
  93. package/dist/lib/observability/exporters/posthogExporter.d.ts +48 -0
  94. package/dist/lib/observability/exporters/posthogExporter.js +288 -0
  95. package/dist/lib/observability/exporters/sentryExporter.d.ts +32 -0
  96. package/dist/lib/observability/exporters/sentryExporter.js +166 -0
  97. package/dist/lib/observability/index.d.ts +25 -0
  98. package/dist/lib/observability/index.js +32 -0
  99. package/dist/lib/observability/metricsAggregator.d.ts +260 -0
  100. package/dist/lib/observability/metricsAggregator.js +553 -0
  101. package/dist/lib/observability/otelBridge.d.ts +49 -0
  102. package/dist/lib/observability/otelBridge.js +132 -0
  103. package/dist/lib/observability/retryPolicy.d.ts +192 -0
  104. package/dist/lib/observability/retryPolicy.js +384 -0
  105. package/dist/lib/observability/sampling/index.d.ts +4 -0
  106. package/dist/lib/observability/sampling/index.js +5 -0
  107. package/dist/lib/observability/sampling/samplers.d.ts +116 -0
  108. package/dist/lib/observability/sampling/samplers.js +217 -0
  109. package/dist/lib/observability/spanProcessor.d.ts +129 -0
  110. package/dist/lib/observability/spanProcessor.js +288 -0
  111. package/dist/lib/observability/tokenTracker.d.ts +156 -0
  112. package/dist/lib/observability/tokenTracker.js +414 -0
  113. package/dist/lib/observability/types/exporterTypes.d.ts +250 -0
  114. package/dist/lib/observability/types/exporterTypes.js +6 -0
  115. package/dist/lib/observability/types/index.d.ts +6 -0
  116. package/dist/lib/observability/types/index.js +5 -0
  117. package/dist/lib/observability/types/spanTypes.d.ts +244 -0
  118. package/dist/lib/observability/types/spanTypes.js +93 -0
  119. package/dist/lib/observability/utils/index.d.ts +4 -0
  120. package/dist/lib/observability/utils/index.js +5 -0
  121. package/dist/lib/observability/utils/spanSerializer.d.ts +115 -0
  122. package/dist/lib/observability/utils/spanSerializer.js +287 -0
  123. package/dist/lib/providers/amazonSagemaker.d.ts +5 -4
  124. package/dist/lib/providers/amazonSagemaker.js +3 -4
  125. package/dist/lib/providers/googleVertex.d.ts +7 -0
  126. package/dist/lib/providers/googleVertex.js +80 -2
  127. package/dist/lib/rag/pipeline/RAGPipeline.d.ts +0 -5
  128. package/dist/lib/rag/pipeline/RAGPipeline.js +122 -87
  129. package/dist/lib/rag/ragIntegration.js +30 -0
  130. package/dist/lib/rag/retrieval/hybridSearch.js +22 -0
  131. package/dist/lib/server/abstract/baseServerAdapter.js +51 -19
  132. package/dist/lib/server/middleware/common.js +44 -12
  133. package/dist/lib/services/server/ai/observability/instrumentation.d.ts +2 -2
  134. package/dist/lib/services/server/ai/observability/instrumentation.js +10 -5
  135. package/dist/lib/types/cli.d.ts +18 -2
  136. package/dist/lib/types/conversationMemoryInterface.d.ts +2 -0
  137. package/dist/lib/types/generateTypes.d.ts +2 -2
  138. package/dist/lib/types/modelTypes.d.ts +18 -18
  139. package/dist/lib/types/providers.d.ts +5 -0
  140. package/dist/lib/utils/pricing.js +25 -1
  141. package/dist/lib/utils/ttsProcessor.js +74 -59
  142. package/dist/lib/workflow/config.d.ts +36 -36
  143. package/dist/lib/workflow/core/ensembleExecutor.js +10 -0
  144. package/dist/lib/workflow/core/judgeScorer.js +20 -2
  145. package/dist/lib/workflow/core/workflowRunner.js +34 -1
  146. package/dist/mcp/httpRateLimiter.js +39 -12
  147. package/dist/mcp/httpRetryHandler.js +22 -1
  148. package/dist/mcp/mcpClientFactory.js +13 -15
  149. package/dist/memory/memoryRetrievalTools.js +22 -0
  150. package/dist/neurolink.d.ts +64 -72
  151. package/dist/neurolink.js +984 -566
  152. package/dist/observability/FEATURE-STATUS.md +269 -0
  153. package/dist/observability/exporterRegistry.d.ts +152 -0
  154. package/dist/observability/exporterRegistry.js +413 -0
  155. package/dist/observability/exporters/arizeExporter.d.ts +32 -0
  156. package/dist/observability/exporters/arizeExporter.js +138 -0
  157. package/dist/observability/exporters/baseExporter.d.ts +117 -0
  158. package/dist/observability/exporters/baseExporter.js +190 -0
  159. package/dist/observability/exporters/braintrustExporter.d.ts +30 -0
  160. package/dist/observability/exporters/braintrustExporter.js +154 -0
  161. package/dist/observability/exporters/datadogExporter.d.ts +37 -0
  162. package/dist/observability/exporters/datadogExporter.js +196 -0
  163. package/dist/observability/exporters/index.d.ts +13 -0
  164. package/dist/observability/exporters/index.js +13 -0
  165. package/dist/observability/exporters/laminarExporter.d.ts +48 -0
  166. package/dist/observability/exporters/laminarExporter.js +302 -0
  167. package/dist/observability/exporters/langfuseExporter.d.ts +47 -0
  168. package/dist/observability/exporters/langfuseExporter.js +199 -0
  169. package/dist/observability/exporters/langsmithExporter.d.ts +26 -0
  170. package/dist/observability/exporters/langsmithExporter.js +123 -0
  171. package/dist/observability/exporters/otelExporter.d.ts +39 -0
  172. package/dist/observability/exporters/otelExporter.js +164 -0
  173. package/dist/observability/exporters/posthogExporter.d.ts +48 -0
  174. package/dist/observability/exporters/posthogExporter.js +287 -0
  175. package/dist/observability/exporters/sentryExporter.d.ts +32 -0
  176. package/dist/observability/exporters/sentryExporter.js +165 -0
  177. package/dist/observability/index.d.ts +25 -0
  178. package/dist/observability/index.js +31 -0
  179. package/dist/observability/metricsAggregator.d.ts +260 -0
  180. package/dist/observability/metricsAggregator.js +552 -0
  181. package/dist/observability/otelBridge.d.ts +49 -0
  182. package/dist/observability/otelBridge.js +131 -0
  183. package/dist/observability/retryPolicy.d.ts +192 -0
  184. package/dist/observability/retryPolicy.js +383 -0
  185. package/dist/observability/sampling/index.d.ts +4 -0
  186. package/dist/observability/sampling/index.js +4 -0
  187. package/dist/observability/sampling/samplers.d.ts +116 -0
  188. package/dist/observability/sampling/samplers.js +216 -0
  189. package/dist/observability/spanProcessor.d.ts +129 -0
  190. package/dist/observability/spanProcessor.js +287 -0
  191. package/dist/observability/tokenTracker.d.ts +156 -0
  192. package/dist/observability/tokenTracker.js +413 -0
  193. package/dist/observability/types/exporterTypes.d.ts +250 -0
  194. package/dist/observability/types/exporterTypes.js +5 -0
  195. package/dist/observability/types/index.d.ts +6 -0
  196. package/dist/observability/types/index.js +4 -0
  197. package/dist/observability/types/spanTypes.d.ts +244 -0
  198. package/dist/observability/types/spanTypes.js +92 -0
  199. package/dist/observability/utils/index.d.ts +4 -0
  200. package/dist/observability/utils/index.js +4 -0
  201. package/dist/observability/utils/spanSerializer.d.ts +115 -0
  202. package/dist/observability/utils/spanSerializer.js +286 -0
  203. package/dist/providers/amazonSagemaker.d.ts +5 -4
  204. package/dist/providers/amazonSagemaker.js +3 -4
  205. package/dist/providers/googleVertex.d.ts +7 -0
  206. package/dist/providers/googleVertex.js +80 -2
  207. package/dist/rag/pipeline/RAGPipeline.d.ts +0 -5
  208. package/dist/rag/pipeline/RAGPipeline.js +122 -87
  209. package/dist/rag/ragIntegration.js +30 -0
  210. package/dist/rag/retrieval/hybridSearch.js +22 -0
  211. package/dist/server/abstract/baseServerAdapter.js +51 -19
  212. package/dist/server/middleware/common.js +44 -12
  213. package/dist/services/server/ai/observability/instrumentation.d.ts +2 -2
  214. package/dist/services/server/ai/observability/instrumentation.js +10 -5
  215. package/dist/types/cli.d.ts +18 -2
  216. package/dist/types/conversationMemoryInterface.d.ts +2 -0
  217. package/dist/types/generateTypes.d.ts +2 -2
  218. package/dist/types/providers.d.ts +5 -0
  219. package/dist/utils/pricing.js +25 -1
  220. package/dist/utils/ttsProcessor.js +74 -59
  221. package/dist/workflow/config.d.ts +52 -52
  222. package/dist/workflow/core/ensembleExecutor.js +10 -0
  223. package/dist/workflow/core/judgeScorer.js +20 -2
  224. package/dist/workflow/core/workflowRunner.js +34 -1
  225. package/package.json +1 -1
@@ -1,9 +1,12 @@
1
+ import { context, SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
1
2
  import { generateText } from "ai";
2
- import { SpanKind, SpanStatusCode } from "@opentelemetry/api";
3
- import { tracers } from "../telemetry/tracers.js";
4
3
  import { directAgentTools } from "../agent/directTools.js";
5
4
  import { IMAGE_GENERATION_MODELS } from "../core/constants.js";
6
5
  import { MiddlewareFactory } from "../middleware/factory.js";
6
+ import { getMetricsAggregator } from "../observability/metricsAggregator.js";
7
+ import { SpanStatus, SpanType } from "../observability/types/spanTypes.js";
8
+ import { SpanSerializer } from "../observability/utils/spanSerializer.js";
9
+ import { ATTR, tracers } from "../telemetry/index.js";
7
10
  import { isAbortError } from "../utils/errorHandling.js";
8
11
  import { logger } from "../utils/logger.js";
9
12
  import { calculateCost } from "../utils/pricing.js";
@@ -11,7 +14,7 @@ import { composeAbortSignals, createTimeoutController, TimeoutError, } from "../
11
14
  import { shouldDisableBuiltinTools } from "../utils/toolUtils.js";
12
15
  import { getKeyCount, getKeysAsString } from "../utils/transformationUtils.js";
13
16
  import { TTSProcessor } from "../utils/ttsProcessor.js";
14
- import { hasVideoFrames, executeVideoAnalysis, } from "../utils/videoAnalysisProcessor.js";
17
+ import { executeVideoAnalysis, hasVideoFrames, } from "../utils/videoAnalysisProcessor.js";
15
18
  import { GenerationHandler } from "./modules/GenerationHandler.js";
16
19
  // Import modules for composition
17
20
  import { MessageBuilder } from "./modules/MessageBuilder.js";
@@ -19,7 +22,6 @@ import { StreamHandler } from "./modules/StreamHandler.js";
19
22
  import { TelemetryHandler } from "./modules/TelemetryHandler.js";
20
23
  import { ToolsManager } from "./modules/ToolsManager.js";
21
24
  import { Utilities } from "./modules/Utilities.js";
22
- const providerTracer = tracers.provider;
23
25
  /**
24
26
  * Abstract base class for all AI providers
25
27
  * Tools are integrated as first-class citizens - always available by default
@@ -39,6 +41,8 @@ export class BaseProvider {
39
41
  sessionId;
40
42
  userId;
41
43
  neurolink; // Reference to actual NeuroLink instance for MCP tools
44
+ /** Trace context propagated from NeuroLink SDK for span hierarchy */
45
+ _traceContext = null;
42
46
  // Composition modules - Single Responsibility Principle
43
47
  messageBuilder;
44
48
  streamHandler;
@@ -80,10 +84,26 @@ export class BaseProvider {
80
84
  * When tools are involved, falls back to generate() with synthetic streaming
81
85
  */
82
86
  async stream(optionsOrPrompt, analysisSchema) {
83
- return providerTracer.startActiveSpan("neurolink.provider.stream", { kind: SpanKind.INTERNAL }, async (span) => {
84
- let options = this.normalizeStreamOptions(optionsOrPrompt);
85
- span.setAttribute("gen_ai.system", this.providerName || "unknown");
86
- span.setAttribute("gen_ai.request.model", this.modelName || options.model || "unknown");
87
+ let options = this.normalizeStreamOptions(optionsOrPrompt);
88
+ // Observability: create metrics span for provider.stream
89
+ const metricsSpan = SpanSerializer.createSpan(SpanType.MODEL_GENERATION, "provider.stream", {
90
+ "ai.provider": this.providerName || "unknown",
91
+ "ai.model": this.modelName || options.model || "unknown",
92
+ "ai.temperature": options.temperature,
93
+ "ai.max_tokens": options.maxTokens,
94
+ }, this._traceContext?.parentSpanId, this._traceContext?.traceId);
95
+ let metricsSpanRecorded = false;
96
+ // OTEL span for provider-level stream tracing
97
+ const otelStreamSpan = tracers.provider.startSpan("neurolink.provider.stream", {
98
+ kind: SpanKind.CLIENT,
99
+ attributes: {
100
+ [ATTR.GEN_AI_SYSTEM]: this.providerName || "unknown",
101
+ [ATTR.GEN_AI_MODEL]: this.modelName || options.model || "unknown",
102
+ [ATTR.GEN_AI_OPERATION]: "stream",
103
+ [ATTR.NL_PROVIDER]: this.providerName || "unknown",
104
+ },
105
+ });
106
+ try {
87
107
  logger.info(`Starting stream`, {
88
108
  provider: this.providerName,
89
109
  hasTools: !options.disableTools && this.supportsTools(),
@@ -94,93 +114,97 @@ export class BaseProvider {
94
114
  temperature: options.temperature,
95
115
  timestamp: Date.now(),
96
116
  });
97
- try {
98
- // ===== EARLY MULTIMODAL DETECTION =====
99
- const hasFileInput = !!options.input?.files?.length ||
100
- !!options.input?.videoFiles?.length;
101
- if (hasFileInput) {
102
- // ===== VIDEO ANALYSIS DETECTION =====
103
- // Check if video frames are present and handle with fake streaming
104
- const messages = await this.buildMessagesForStream(options);
105
- if (hasVideoFrames(messages)) {
106
- logger.info(`Video frames detected in stream, using fake streaming for video analysis`, {
107
- provider: this.providerName,
108
- model: this.modelName,
109
- });
110
- span.setAttribute("neurolink.stream_mode", "fake");
111
- return await this.executeFakeStreaming(options, analysisSchema);
112
- }
113
- }
114
- // Image generation models don't support real streaming
115
- // Force fake streaming for image models to ensure image output is yielded
116
- const isImageModel = IMAGE_GENERATION_MODELS.some((m) => this.modelName.includes(m));
117
- if (isImageModel) {
118
- logger.info(`Image model detected, forcing fake streaming`, {
117
+ // ===== EARLY MULTIMODAL DETECTION =====
118
+ const hasFileInput = !!options.input?.files?.length || !!options.input?.videoFiles?.length;
119
+ if (hasFileInput) {
120
+ // ===== VIDEO ANALYSIS DETECTION =====
121
+ // Check if video frames are present and handle with fake streaming
122
+ const messages = await this.buildMessagesForStream(options);
123
+ if (hasVideoFrames(messages)) {
124
+ logger.info(`Video frames detected in stream, using fake streaming for video analysis`, {
119
125
  provider: this.providerName,
120
126
  model: this.modelName,
121
- reason: "Image generation requires fake streaming to yield image output",
122
127
  });
123
- // Skip real streaming, go directly to fake streaming
124
- span.setAttribute("neurolink.stream_mode", "fake");
125
128
  return await this.executeFakeStreaming(options, analysisSchema);
126
129
  }
127
- // Central tool merge: Pre-merge base tools (MCP/built-in) with user-provided
128
- // tools (e.g. RAG tools) into options.tools. This way, every provider's
129
- // executeStream() can simply use options.tools (or getAllTools() + options.tools)
130
- // and get the complete tool set without needing per-provider merge logic.
130
+ }
131
+ // CRITICAL: Image generation models don't support real streaming
132
+ // Force fake streaming for image models to ensure image output is yielded
133
+ const isImageModel = IMAGE_GENERATION_MODELS.some((m) => this.modelName.includes(m));
134
+ if (isImageModel) {
135
+ logger.info(`Image model detected, forcing fake streaming`, {
136
+ provider: this.providerName,
137
+ model: this.modelName,
138
+ reason: "Image generation requires fake streaming to yield image output",
139
+ });
140
+ // Skip real streaming, go directly to fake streaming
141
+ return await this.executeFakeStreaming(options, analysisSchema);
142
+ }
143
+ // Central tool merge: Pre-merge base tools (MCP/built-in) with user-provided
144
+ // tools (e.g. RAG tools) into options.tools. This way, every provider's
145
+ // executeStream() can simply use options.tools (or getAllTools() + options.tools)
146
+ // and get the complete tool set without needing per-provider merge logic.
147
+ if (!options.disableTools && this.supportsTools()) {
148
+ const mergedTools = await this.getToolsForStream(options);
149
+ options = { ...options, tools: mergedTools };
150
+ }
151
+ else {
152
+ options = { ...options, tools: {} };
153
+ }
154
+ // CRITICAL FIX: Always prefer real streaming over fake streaming
155
+ // Try real streaming first, use fake streaming only as fallback
156
+ try {
157
+ logger.debug(`Attempting real streaming`, {
158
+ provider: this.providerName,
159
+ timestamp: Date.now(),
160
+ });
161
+ const realStreamResult = await this.executeStream(options, analysisSchema);
162
+ logger.info(`Real streaming succeeded`, {
163
+ provider: this.providerName,
164
+ timestamp: Date.now(),
165
+ });
166
+ // If real streaming succeeds, return it (with tools support via Vercel AI SDK)
167
+ return realStreamResult;
168
+ }
169
+ catch (realStreamError) {
170
+ logger.warn(`Real streaming failed for ${this.providerName}, falling back to fake streaming:`, {
171
+ error: realStreamError instanceof Error
172
+ ? realStreamError.message
173
+ : String(realStreamError),
174
+ timestamp: Date.now(),
175
+ });
176
+ // Fallback to fake streaming only if real streaming fails AND tools are enabled
131
177
  if (!options.disableTools && this.supportsTools()) {
132
- const mergedTools = await this.getToolsForStream(options);
133
- options = { ...options, tools: mergedTools };
178
+ return await this.executeFakeStreaming(options, analysisSchema);
134
179
  }
135
180
  else {
136
- options = { ...options, tools: {} };
181
+ // If real streaming failed and no tools are enabled, re-throw the original error
182
+ logger.error(`Real streaming failed for ${this.providerName}:`, realStreamError);
183
+ throw this.handleProviderError(realStreamError);
137
184
  }
138
- // CRITICAL FIX: Always prefer real streaming over fake streaming
139
- // Try real streaming first, use fake streaming only as fallback
140
- try {
141
- logger.debug(`Attempting real streaming`, {
142
- provider: this.providerName,
143
- timestamp: Date.now(),
144
- });
145
- const realStreamResult = await this.executeStream(options, analysisSchema);
146
- logger.info(`Real streaming succeeded`, {
147
- provider: this.providerName,
148
- timestamp: Date.now(),
149
- });
150
- span.setAttribute("neurolink.stream_mode", "real");
151
- // If real streaming succeeds, return it (with tools support via Vercel AI SDK)
152
- return realStreamResult;
153
- }
154
- catch (realStreamError) {
155
- logger.warn(`Real streaming failed for ${this.providerName}, falling back to fake streaming:`, {
156
- error: realStreamError instanceof Error
157
- ? realStreamError.message
158
- : String(realStreamError),
159
- timestamp: Date.now(),
160
- });
161
- // Fallback to fake streaming only if real streaming fails AND tools are enabled
162
- if (!options.disableTools && this.supportsTools()) {
163
- span.setAttribute("neurolink.stream_mode", "fake");
164
- return await this.executeFakeStreaming(options, analysisSchema);
165
- }
166
- else {
167
- // If real streaming failed and no tools are enabled, re-throw the original error
168
- logger.error(`Real streaming failed for ${this.providerName}:`, realStreamError);
169
- throw this.handleProviderError(realStreamError);
170
- }
171
- }
172
- }
173
- catch (error) {
174
- span.setStatus({
175
- code: SpanStatusCode.ERROR,
176
- message: error instanceof Error ? error.message : String(error),
177
- });
178
- throw error;
179
185
  }
180
- finally {
181
- span.end();
186
+ }
187
+ catch (error) {
188
+ // Observability: record failed stream span
189
+ metricsSpanRecorded = true;
190
+ const endedStreamSpan = SpanSerializer.endSpan(metricsSpan, SpanStatus.ERROR, error instanceof Error ? error.message : String(error));
191
+ getMetricsAggregator().recordSpan(endedStreamSpan);
192
+ otelStreamSpan.setStatus({
193
+ code: SpanStatusCode.ERROR,
194
+ message: error instanceof Error ? error.message : String(error),
195
+ });
196
+ otelStreamSpan.end();
197
+ throw error;
198
+ }
199
+ finally {
200
+ // Observability: record successful stream span (only if not already ended via error path)
201
+ if (!metricsSpanRecorded) {
202
+ const endedStreamSpan = SpanSerializer.endSpan(metricsSpan, SpanStatus.OK);
203
+ getMetricsAggregator().recordSpan(endedStreamSpan);
204
+ otelStreamSpan.setStatus({ code: SpanStatusCode.OK });
205
+ otelStreamSpan.end();
182
206
  }
183
- });
207
+ }
184
208
  }
185
209
  /**
186
210
  * Execute fake streaming - extracted method for reusability
@@ -468,12 +492,31 @@ export class BaseProvider {
468
492
  * for consistency and better performance
469
493
  */
470
494
  async generate(optionsOrPrompt, _analysisSchema) {
471
- return providerTracer.startActiveSpan("neurolink.provider.generate", { kind: SpanKind.INTERNAL }, async (span) => {
472
- const options = this.normalizeTextOptions(optionsOrPrompt);
473
- this.validateOptions(options);
474
- const startTime = Date.now();
475
- span.setAttribute("gen_ai.system", this.providerName || "unknown");
476
- span.setAttribute("gen_ai.request.model", this.modelName || options.model || "unknown");
495
+ const options = this.normalizeTextOptions(optionsOrPrompt);
496
+ this.validateOptions(options);
497
+ const startTime = Date.now();
498
+ // Observability: create metrics span for provider.generate
499
+ const metricsSpan = SpanSerializer.createSpan(SpanType.MODEL_GENERATION, "provider.generate", {
500
+ "ai.provider": this.providerName || "unknown",
501
+ "ai.model": this.modelName || options.model || "unknown",
502
+ "ai.temperature": options.temperature,
503
+ "ai.max_tokens": options.maxTokens,
504
+ }, this._traceContext?.parentSpanId, this._traceContext?.traceId);
505
+ // OTEL span for provider-level generate tracing
506
+ // Use startActiveSpan pattern via context.with() so child spans become descendants
507
+ const otelSpan = tracers.provider.startSpan("neurolink.provider.generate", {
508
+ kind: SpanKind.CLIENT,
509
+ attributes: {
510
+ [ATTR.GEN_AI_SYSTEM]: this.providerName || "unknown",
511
+ [ATTR.GEN_AI_MODEL]: this.modelName || options.model || "unknown",
512
+ [ATTR.GEN_AI_OPERATION]: "generate",
513
+ [ATTR.NL_PROVIDER]: this.providerName || "unknown",
514
+ },
515
+ });
516
+ // Set this span as the active context so child spans (GenerationHandler, etc.) become descendants
517
+ const activeCtx = trace.setSpan(context.active(), otelSpan);
518
+ let otelSpanEnded = false;
519
+ return await context.with(activeCtx, async () => {
477
520
  try {
478
521
  // ===== VIDEO GENERATION MODE =====
479
522
  // Generate video from image + prompt using Veo 3.1
@@ -647,29 +690,39 @@ export class BaseProvider {
647
690
  });
648
691
  }
649
692
  }
650
- // Set token usage on span from the result
693
+ // Observability: record successful generate span with token/cost data
694
+ let enrichedGenerateSpan = { ...metricsSpan };
651
695
  if (enhancedResult?.usage) {
652
- span.setAttribute("gen_ai.usage.input_tokens", enhancedResult.usage.input || 0);
653
- span.setAttribute("gen_ai.usage.output_tokens", enhancedResult.usage.output || 0);
654
- // Cost on span so users can query "what did this trace cost?"
696
+ enrichedGenerateSpan = SpanSerializer.enrichWithTokenUsage(enrichedGenerateSpan, {
697
+ promptTokens: enhancedResult.usage.input || 0,
698
+ completionTokens: enhancedResult.usage.output || 0,
699
+ totalTokens: enhancedResult.usage.total || 0,
700
+ });
655
701
  const cost = calculateCost(this.providerName, this.modelName, {
656
702
  input: enhancedResult.usage.input || 0,
657
703
  output: enhancedResult.usage.output || 0,
658
704
  total: enhancedResult.usage.total || 0,
659
705
  });
660
- span.setAttribute("neurolink.cost", cost ?? 0);
661
- }
662
- if (enhancedResult?.finishReason) {
663
- span.setAttribute("gen_ai.response.finish_reason", enhancedResult.finishReason);
706
+ if (cost && cost > 0) {
707
+ enrichedGenerateSpan = SpanSerializer.enrichWithCost(enrichedGenerateSpan, {
708
+ totalCost: cost,
709
+ });
710
+ }
664
711
  }
665
- span.setStatus({ code: SpanStatusCode.OK });
712
+ const endedGenerateSpan = SpanSerializer.endSpan(enrichedGenerateSpan, SpanStatus.OK);
713
+ getMetricsAggregator().recordSpan(endedGenerateSpan);
666
714
  return await this.enhanceResult(enhancedResult, options, startTime);
667
715
  }
668
716
  catch (error) {
669
- span.setStatus({
717
+ // Observability: record failed generate span
718
+ const endedGenerateSpan = SpanSerializer.endSpan(metricsSpan, SpanStatus.ERROR, error instanceof Error ? error.message : String(error));
719
+ getMetricsAggregator().recordSpan(endedGenerateSpan);
720
+ otelSpan.setStatus({
670
721
  code: SpanStatusCode.ERROR,
671
722
  message: error instanceof Error ? error.message : String(error),
672
723
  });
724
+ otelSpan.end();
725
+ otelSpanEnded = true;
673
726
  // Abort errors are expected when a generation is cancelled — log at info, not error
674
727
  if (isAbortError(error)) {
675
728
  logger.info(`Generate aborted for ${this.providerName}`, {
@@ -682,9 +735,12 @@ export class BaseProvider {
682
735
  throw this.handleProviderError(error);
683
736
  }
684
737
  finally {
685
- span.end();
738
+ if (!otelSpanEnded) {
739
+ otelSpan.setStatus({ code: SpanStatusCode.OK });
740
+ otelSpan.end();
741
+ }
686
742
  }
687
- });
743
+ }); // end context.with
688
744
  }
689
745
  /**
690
746
  * Alias for generate method - implements AIProvider interface
@@ -2,9 +2,9 @@
2
2
  * Conversation Memory Initializer
3
3
  * Provides integration with Redis storage for conversation memory
4
4
  */
5
- import { createConversationMemoryManager, getStorageType, getRedisConfigFromEnv, } from "./conversationMemoryFactory.js";
6
5
  import { applyConversationMemoryDefaults } from "../utils/conversationMemory.js";
7
6
  import { logger } from "../utils/logger.js";
7
+ import { createConversationMemoryManager, getRedisConfigFromEnv, getStorageType, } from "./conversationMemoryFactory.js";
8
8
  /**
9
9
  * Initialize conversation memory for NeuroLink
10
10
  * This function decides whether to use in-memory or Redis storage
@@ -30,11 +30,14 @@ export async function initializeConversationMemory(config) {
30
30
  maxTurnsPerSession: memoryConfig.maxTurnsPerSession,
31
31
  enableSummarization: memoryConfig.enableSummarization,
32
32
  });
33
- // Determine storage type from environment
34
- const storageType = getStorageType();
33
+ // Determine storage type: if redisConfig is passed in the SDK config, use Redis
34
+ // regardless of STORAGE_TYPE env var. This lets consumers configure Redis via the API.
35
+ const hasRedisConfig = !!config.conversationMemory?.redisConfig;
36
+ const storageType = hasRedisConfig ? "redis" : getStorageType();
35
37
  logger.debug("[conversationMemoryInitializer] Storage type determined", {
36
38
  storageType,
37
- fromEnv: !!process.env.STORAGE_TYPE,
39
+ fromConfig: hasRedisConfig,
40
+ fromEnv: !hasRedisConfig && !!process.env.STORAGE_TYPE,
38
41
  });
39
42
  if (storageType === "redis") {
40
43
  logger.info("[conversationMemoryInitializer] Initializing Redis-based conversation memory manager");
@@ -71,4 +71,6 @@ export declare class ConversationMemoryManager implements IConversationMemoryMan
71
71
  * Resets summary pointers since old pointers may reference messages that no longer exist.
72
72
  */
73
73
  setSessionMessages(sessionId: string, messages: ChatMessage[], userId?: string): Promise<void>;
74
+ /** Close/shutdown — no-op for in-memory manager (no external connections to release) */
75
+ close(): Promise<void>;
74
76
  }
@@ -6,11 +6,11 @@ import { randomUUID } from "crypto";
6
6
  import { DEFAULT_MAX_SESSIONS, MEMORY_THRESHOLD_PERCENTAGE, MESSAGES_PER_TURN, } from "../config/conversationMemory.js";
7
7
  import { TokenUtils } from "../constants/tokens.js";
8
8
  import { SummarizationEngine } from "../context/summarizationEngine.js";
9
+ import { runWithCurrentLangfuseContext } from "../services/server/ai/observability/instrumentation.js";
10
+ import { tracers, withSpan } from "../telemetry/index.js";
9
11
  import { ConversationMemoryError } from "../types/conversation.js";
10
12
  import { buildContextFromPointer, getEffectiveTokenThreshold, } from "../utils/conversationMemory.js";
11
- import { runWithCurrentLangfuseContext } from "../services/server/ai/observability/instrumentation.js";
12
13
  import { logger } from "../utils/logger.js";
13
- import { tracers, withSpan } from "../telemetry/index.js";
14
14
  export class ConversationMemoryManager {
15
15
  sessions = new Map();
16
16
  config;
@@ -337,5 +337,9 @@ export class ConversationMemoryManager {
337
337
  session.lastCountedAt = undefined;
338
338
  session.lastActivity = Date.now();
339
339
  }
340
+ /** Close/shutdown — no-op for in-memory manager (no external connections to release) */
341
+ async close() {
342
+ // In-memory manager has nothing to close
343
+ }
340
344
  }
341
345
  //# sourceMappingURL=conversationMemoryManager.js.map
@@ -12,9 +12,9 @@
12
12
  *
13
13
  * @module core/modules/GenerationHandler
14
14
  */
15
- import type { LanguageModelV1, CoreMessage, Tool } from "ai";
15
+ import type { CoreMessage, LanguageModelV1, Tool } from "ai";
16
16
  import { generateText } from "ai";
17
- import type { TextGenerationOptions, EnhancedGenerateResult, AIProviderName, StandardRecord } from "../../types/index.js";
17
+ import type { AIProviderName, EnhancedGenerateResult, StandardRecord, TextGenerationOptions } from "../../types/index.js";
18
18
  /**
19
19
  * GenerationHandler class - Handles text generation operations for AI providers
20
20
  */
@@ -12,13 +12,13 @@
12
12
  *
13
13
  * @module core/modules/GenerationHandler
14
14
  */
15
- import { generateText, Output, NoObjectGeneratedError } from "ai";
16
15
  import { SpanKind, SpanStatusCode } from "@opentelemetry/api";
16
+ import { generateText, NoObjectGeneratedError, Output } from "ai";
17
17
  import { tracers } from "../../telemetry/tracers.js";
18
18
  import { logger } from "../../utils/logger.js";
19
- import { extractTokenUsage, extractCacheCreationTokens, extractCacheReadTokens, calculateCacheSavingsPercent, } from "../../utils/tokenUtils.js";
20
- import { withProviderRetry } from "../../utils/providerRetry.js";
21
19
  import { calculateCost } from "../../utils/pricing.js";
20
+ import { withProviderRetry } from "../../utils/providerRetry.js";
21
+ import { calculateCacheSavingsPercent, extractCacheCreationTokens, extractCacheReadTokens, extractTokenUsage, } from "../../utils/tokenUtils.js";
22
22
  import { DEFAULT_MAX_STEPS } from "../constants.js";
23
23
  const genTracer = tracers.generation;
24
24
  /**
@@ -67,8 +67,8 @@ export class GenerationHandler {
67
67
  // Gemini 2.5 and earlier cannot use tools + structured JSON output simultaneously.
68
68
  // When both are requested on a Google provider, disable structured output (tools take priority).
69
69
  const wantsStructuredOutput = includeStructuredOutput &&
70
- !!options.schema &&
71
- (options.output?.format === "json" ||
70
+ (!!options.schema ||
71
+ options.output?.format === "json" ||
72
72
  options.output?.format === "structured");
73
73
  const useStructuredOutput = wantsStructuredOutput &&
74
74
  !(isGoogleProvider && shouldUseTools && Object.keys(tools).length > 0);
@@ -166,9 +166,9 @@ export class GenerationHandler {
166
166
  return genTracer.startActiveSpan("neurolink.executeGeneration", { kind: SpanKind.INTERNAL }, async (span) => {
167
167
  const shouldUseTools = !options.disableTools && this.supportsToolsFn();
168
168
  const toolCount = Object.keys(tools || {}).length;
169
- const useStructuredOutput = !!options.schema &&
170
- (options.output?.format === "json" ||
171
- options.output?.format === "structured");
169
+ const useStructuredOutput = !!options.schema ||
170
+ options.output?.format === "json" ||
171
+ options.output?.format === "structured";
172
172
  span.setAttribute("gen_ai.system", this.providerName || "unknown");
173
173
  span.setAttribute("neurolink.structured_output", useStructuredOutput);
174
174
  span.setAttribute("neurolink.tool_count", toolCount);
@@ -452,10 +452,10 @@ export class GenerationHandler {
452
452
  * Format the enhanced result
453
453
  */
454
454
  formatEnhancedResult(generateResult, tools, toolsUsed, toolExecutions, options) {
455
- // Structured output check
456
- const useStructuredOutput = !!options.schema &&
457
- (options.output?.format === "json" ||
458
- options.output?.format === "structured");
455
+ // Structured output check — schema alone is sufficient to activate
456
+ const useStructuredOutput = !!options.schema ||
457
+ options.output?.format === "json" ||
458
+ options.output?.format === "structured";
459
459
  let content;
460
460
  if (useStructuredOutput) {
461
461
  try {
@@ -1,6 +1,8 @@
1
1
  import { AIProviderFactory } from "../core/factory.js";
2
2
  import { PromptBuilder } from "./prompts.js";
3
3
  import { logger } from "../utils/logger.js";
4
+ import { SpanSerializer, SpanType, SpanStatus, } from "../observability/index.js";
5
+ import { getMetricsAggregator } from "../observability/index.js";
4
6
  /**
5
7
  * Implements a RAGAS-style evaluator that uses a "judge" LLM to score the
6
8
  * quality of an AI response based on rich, contextual information.
@@ -32,27 +34,45 @@ export class RAGASEvaluator {
32
34
  * @returns A promise that resolves to a detailed `EvaluationResult`.
33
35
  */
34
36
  async evaluate(context) {
35
- const startTime = Date.now();
36
- const prompt = this.promptBuilder.buildEvaluationPrompt(context, this.promptGenerator);
37
- const provider = await AIProviderFactory.createProvider(this.providerName, this.evaluationModel);
38
- const result = await provider.generate({
39
- input: { text: prompt },
37
+ const span = SpanSerializer.createSpan(SpanType.EVALUATION, "evaluation.ragas", {
38
+ "evaluation.dimension": "relevance|accuracy|completeness",
39
+ "ai.provider": this.providerName,
40
+ "ai.model": this.evaluationModel,
40
41
  });
41
- if (!result) {
42
- throw new Error("Evaluation generation failed to return a result.");
42
+ const startTime = Date.now();
43
+ try {
44
+ const prompt = this.promptBuilder.buildEvaluationPrompt(context, this.promptGenerator);
45
+ const provider = await AIProviderFactory.createProvider(this.providerName, this.evaluationModel);
46
+ const result = await provider.generate({
47
+ input: { text: prompt },
48
+ });
49
+ if (!result) {
50
+ throw new Error("Evaluation generation failed to return a result.");
51
+ }
52
+ const rawEvaluationResponse = result.content;
53
+ const parsedResult = this.parseEvaluationResponse(rawEvaluationResponse);
54
+ const evaluationTime = Date.now() - startTime;
55
+ const finalResult = {
56
+ ...parsedResult,
57
+ isPassing: parsedResult.finalScore >= this.threshold, // This will be recalculated, but is needed for the type
58
+ evaluationModel: this.evaluationModel,
59
+ evaluationTime,
60
+ attemptNumber: context.attemptNumber,
61
+ rawEvaluationResponse,
62
+ };
63
+ span.durationMs = Date.now() - startTime;
64
+ const endedSpan = SpanSerializer.endSpan(span, SpanStatus.OK);
65
+ getMetricsAggregator().recordSpan(endedSpan);
66
+ return finalResult;
67
+ }
68
+ catch (error) {
69
+ span.durationMs = Date.now() - startTime;
70
+ const endedSpan = SpanSerializer.endSpan(span, SpanStatus.ERROR);
71
+ endedSpan.statusMessage =
72
+ error instanceof Error ? error.message : String(error);
73
+ getMetricsAggregator().recordSpan(endedSpan);
74
+ throw error;
43
75
  }
44
- const rawEvaluationResponse = result.content;
45
- const parsedResult = this.parseEvaluationResponse(rawEvaluationResponse);
46
- const evaluationTime = Date.now() - startTime;
47
- const finalResult = {
48
- ...parsedResult,
49
- isPassing: parsedResult.finalScore >= this.threshold, // This will be recalculated, but is needed for the type
50
- evaluationModel: this.evaluationModel,
51
- evaluationTime,
52
- attemptNumber: context.attemptNumber,
53
- rawEvaluationResponse,
54
- };
55
- return finalResult;
56
76
  }
57
77
  /**
58
78
  * Parses the raw JSON string from the judge LLM into a structured `EvaluationResult` object.
@@ -1,6 +1,8 @@
1
1
  /**
2
2
  * @file Contains the logic for mapping raw evaluation results to the structured EvaluationData type.
3
3
  */
4
+ import { SpanSerializer, SpanType, SpanStatus, } from "../observability/index.js";
5
+ import { getMetricsAggregator } from "../observability/index.js";
4
6
  /**
5
7
  * Maps a raw `EvaluationResult` to the structured `EvaluationData` format.
6
8
  * This includes calculating derived fields like `isOffTopic` and `alertSeverity`.
@@ -12,25 +14,49 @@
12
14
  * @returns A structured `EvaluationData` object.
13
15
  */
14
16
  export function mapToEvaluationData(evalContext, result, threshold, offTopicThreshold = 5, highSeverityThreshold = 4) {
15
- const isPassing = result.finalScore >= threshold;
16
- return {
17
- relevance: result.relevanceScore,
18
- accuracy: result.accuracyScore,
19
- completeness: result.completenessScore,
20
- overall: result.finalScore,
21
- isOffTopic: result.finalScore < offTopicThreshold,
22
- alertSeverity: isPassing
23
- ? "none"
24
- : result.finalScore < highSeverityThreshold
25
- ? "high"
26
- : "medium",
27
- reasoning: result.reasoning,
28
- suggestedImprovements: result.suggestedImprovements,
29
- evaluationModel: result.evaluationModel,
30
- evaluationTime: result.evaluationTime,
31
- evaluationAttempt: result.attemptNumber,
32
- responseContent: evalContext.aiResponse,
33
- queryContent: evalContext.userQuery,
34
- };
17
+ const span = SpanSerializer.createSpan(SpanType.EVALUATION, "evaluation.score", {
18
+ "evaluation.dimension": "relevance|accuracy|completeness|overall",
19
+ scores: {
20
+ relevance: result.relevanceScore,
21
+ accuracy: result.accuracyScore,
22
+ completeness: result.completenessScore,
23
+ overall: result.finalScore,
24
+ },
25
+ });
26
+ const startTime = Date.now();
27
+ try {
28
+ const isPassing = result.finalScore >= threshold;
29
+ const evaluationData = {
30
+ relevance: result.relevanceScore,
31
+ accuracy: result.accuracyScore,
32
+ completeness: result.completenessScore,
33
+ overall: result.finalScore,
34
+ isOffTopic: result.finalScore < offTopicThreshold,
35
+ alertSeverity: isPassing
36
+ ? "none"
37
+ : result.finalScore < highSeverityThreshold
38
+ ? "high"
39
+ : "medium",
40
+ reasoning: result.reasoning,
41
+ suggestedImprovements: result.suggestedImprovements,
42
+ evaluationModel: result.evaluationModel,
43
+ evaluationTime: result.evaluationTime,
44
+ evaluationAttempt: result.attemptNumber,
45
+ responseContent: evalContext.aiResponse,
46
+ queryContent: evalContext.userQuery,
47
+ };
48
+ span.durationMs = Date.now() - startTime;
49
+ const endedSpan = SpanSerializer.endSpan(span, SpanStatus.OK);
50
+ getMetricsAggregator().recordSpan(endedSpan);
51
+ return evaluationData;
52
+ }
53
+ catch (error) {
54
+ span.durationMs = Date.now() - startTime;
55
+ const endedSpan = SpanSerializer.endSpan(span, SpanStatus.ERROR);
56
+ endedSpan.statusMessage =
57
+ error instanceof Error ? error.message : String(error);
58
+ getMetricsAggregator().recordSpan(endedSpan);
59
+ throw error;
60
+ }
35
61
  }
36
62
  //# sourceMappingURL=scoring.js.map
@@ -22,5 +22,5 @@ export { SlideGenerator, createSlideGenerator, generateSlidesFromPlan, PptxGenJS
22
22
  export { generatePresentation } from "./presentationOrchestrator.js";
23
23
  export { validatePPTGenerationInput, validatePPTOutputOptions, validatePPTProvider, } from "../../utils/parameterValidation.js";
24
24
  export type { EnhancedValidationResult as PPTValidationResult } from "../../types/tools.js";
25
- export { PPT_VALID_PROVIDERS, getEffectivePPTProvider, generateOutputPath, ensureOutputDirectory, normalizeLogoConfig, getLayoutName, getFailureStage, toError, isObject, isLogoConfig, } from "./utils.js";
25
+ export { PPT_VALID_PROVIDERS, getEffectivePPTProvider, generateOutputPath, ensureOutputDirectory, normalizeLogoConfig, getLayoutName, getFailureStage, toError, isObject, isLogoConfig, validateImageBuffer, } from "./utils.js";
26
26
  export type { EffectivePPTProviderResult } from "./types.js";