@juspay/neurolink 9.14.0 → 9.16.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 (241) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +15 -15
  3. package/dist/adapters/video/videoAnalyzer.d.ts +1 -1
  4. package/dist/adapters/video/videoAnalyzer.js +10 -8
  5. package/dist/auth/anthropicOAuth.d.ts +377 -0
  6. package/dist/auth/anthropicOAuth.js +914 -0
  7. package/dist/auth/index.d.ts +20 -0
  8. package/dist/auth/index.js +29 -0
  9. package/dist/auth/tokenStore.d.ts +225 -0
  10. package/dist/auth/tokenStore.js +521 -0
  11. package/dist/cli/commands/auth.d.ts +50 -0
  12. package/dist/cli/commands/auth.js +1115 -0
  13. package/dist/cli/commands/setup-anthropic.js +1 -14
  14. package/dist/cli/commands/setup-azure.js +1 -12
  15. package/dist/cli/commands/setup-bedrock.js +1 -9
  16. package/dist/cli/commands/setup-google-ai.js +1 -12
  17. package/dist/cli/commands/setup-openai.js +1 -14
  18. package/dist/cli/commands/workflow.d.ts +27 -0
  19. package/dist/cli/commands/workflow.js +216 -0
  20. package/dist/cli/factories/authCommandFactory.d.ts +52 -0
  21. package/dist/cli/factories/authCommandFactory.js +146 -0
  22. package/dist/cli/factories/commandFactory.d.ts +6 -0
  23. package/dist/cli/factories/commandFactory.js +171 -22
  24. package/dist/cli/index.js +0 -1
  25. package/dist/cli/parser.js +14 -2
  26. package/dist/cli/utils/maskCredential.d.ts +11 -0
  27. package/dist/cli/utils/maskCredential.js +23 -0
  28. package/dist/constants/contextWindows.js +107 -16
  29. package/dist/constants/enums.d.ts +119 -15
  30. package/dist/constants/enums.js +182 -22
  31. package/dist/constants/index.d.ts +3 -1
  32. package/dist/constants/index.js +11 -1
  33. package/dist/context/budgetChecker.js +1 -1
  34. package/dist/context/contextCompactor.js +31 -4
  35. package/dist/context/emergencyTruncation.d.ts +21 -0
  36. package/dist/context/emergencyTruncation.js +88 -0
  37. package/dist/context/errorDetection.d.ts +16 -0
  38. package/dist/context/errorDetection.js +48 -1
  39. package/dist/context/errors.d.ts +19 -0
  40. package/dist/context/errors.js +21 -0
  41. package/dist/context/stages/slidingWindowTruncator.d.ts +6 -0
  42. package/dist/context/stages/slidingWindowTruncator.js +159 -24
  43. package/dist/core/baseProvider.js +306 -200
  44. package/dist/core/conversationMemoryManager.js +104 -61
  45. package/dist/core/evaluationProviders.js +16 -33
  46. package/dist/core/factory.js +237 -164
  47. package/dist/core/modules/GenerationHandler.js +175 -116
  48. package/dist/core/modules/MessageBuilder.js +222 -170
  49. package/dist/core/modules/StreamHandler.d.ts +1 -0
  50. package/dist/core/modules/StreamHandler.js +95 -27
  51. package/dist/core/modules/TelemetryHandler.d.ts +10 -1
  52. package/dist/core/modules/TelemetryHandler.js +25 -7
  53. package/dist/core/modules/ToolsManager.js +115 -191
  54. package/dist/core/redisConversationMemoryManager.js +418 -282
  55. package/dist/factories/providerRegistry.d.ts +5 -0
  56. package/dist/factories/providerRegistry.js +20 -2
  57. package/dist/index.d.ts +3 -3
  58. package/dist/index.js +4 -2
  59. package/dist/lib/adapters/video/videoAnalyzer.d.ts +1 -1
  60. package/dist/lib/adapters/video/videoAnalyzer.js +10 -8
  61. package/dist/lib/auth/anthropicOAuth.d.ts +377 -0
  62. package/dist/lib/auth/anthropicOAuth.js +915 -0
  63. package/dist/lib/auth/index.d.ts +20 -0
  64. package/dist/lib/auth/index.js +30 -0
  65. package/dist/lib/auth/tokenStore.d.ts +225 -0
  66. package/dist/lib/auth/tokenStore.js +522 -0
  67. package/dist/lib/constants/contextWindows.js +107 -16
  68. package/dist/lib/constants/enums.d.ts +119 -15
  69. package/dist/lib/constants/enums.js +182 -22
  70. package/dist/lib/constants/index.d.ts +3 -1
  71. package/dist/lib/constants/index.js +11 -1
  72. package/dist/lib/context/budgetChecker.js +1 -1
  73. package/dist/lib/context/contextCompactor.js +31 -4
  74. package/dist/lib/context/emergencyTruncation.d.ts +21 -0
  75. package/dist/lib/context/emergencyTruncation.js +89 -0
  76. package/dist/lib/context/errorDetection.d.ts +16 -0
  77. package/dist/lib/context/errorDetection.js +48 -1
  78. package/dist/lib/context/errors.d.ts +19 -0
  79. package/dist/lib/context/errors.js +22 -0
  80. package/dist/lib/context/stages/slidingWindowTruncator.d.ts +6 -0
  81. package/dist/lib/context/stages/slidingWindowTruncator.js +159 -24
  82. package/dist/lib/core/baseProvider.js +306 -200
  83. package/dist/lib/core/conversationMemoryManager.js +104 -61
  84. package/dist/lib/core/evaluationProviders.js +16 -33
  85. package/dist/lib/core/factory.js +237 -164
  86. package/dist/lib/core/modules/GenerationHandler.js +175 -116
  87. package/dist/lib/core/modules/MessageBuilder.js +222 -170
  88. package/dist/lib/core/modules/StreamHandler.d.ts +1 -0
  89. package/dist/lib/core/modules/StreamHandler.js +95 -27
  90. package/dist/lib/core/modules/TelemetryHandler.d.ts +10 -1
  91. package/dist/lib/core/modules/TelemetryHandler.js +25 -7
  92. package/dist/lib/core/modules/ToolsManager.js +115 -191
  93. package/dist/lib/core/redisConversationMemoryManager.js +418 -282
  94. package/dist/lib/factories/providerRegistry.d.ts +5 -0
  95. package/dist/lib/factories/providerRegistry.js +20 -2
  96. package/dist/lib/index.d.ts +3 -3
  97. package/dist/lib/index.js +4 -2
  98. package/dist/lib/mcp/externalServerManager.js +66 -0
  99. package/dist/lib/mcp/mcpCircuitBreaker.js +24 -0
  100. package/dist/lib/mcp/mcpClientFactory.js +16 -0
  101. package/dist/lib/mcp/toolDiscoveryService.js +32 -6
  102. package/dist/lib/mcp/toolRegistry.js +193 -123
  103. package/dist/lib/models/anthropicModels.d.ts +267 -0
  104. package/dist/lib/models/anthropicModels.js +528 -0
  105. package/dist/lib/neurolink.d.ts +6 -0
  106. package/dist/lib/neurolink.js +1162 -646
  107. package/dist/lib/providers/amazonBedrock.d.ts +1 -1
  108. package/dist/lib/providers/amazonBedrock.js +521 -319
  109. package/dist/lib/providers/anthropic.d.ts +123 -2
  110. package/dist/lib/providers/anthropic.js +873 -27
  111. package/dist/lib/providers/anthropicBaseProvider.js +77 -17
  112. package/dist/lib/providers/googleAiStudio.d.ts +1 -1
  113. package/dist/lib/providers/googleAiStudio.js +292 -227
  114. package/dist/lib/providers/googleVertex.d.ts +36 -1
  115. package/dist/lib/providers/googleVertex.js +553 -260
  116. package/dist/lib/providers/ollama.js +329 -278
  117. package/dist/lib/providers/openAI.js +77 -19
  118. package/dist/lib/providers/sagemaker/parsers.js +3 -3
  119. package/dist/lib/providers/sagemaker/streaming.js +3 -3
  120. package/dist/lib/proxy/proxyFetch.js +81 -48
  121. package/dist/lib/rag/ChunkerFactory.js +1 -1
  122. package/dist/lib/rag/chunkers/MarkdownChunker.d.ts +22 -0
  123. package/dist/lib/rag/chunkers/MarkdownChunker.js +213 -9
  124. package/dist/lib/rag/chunking/markdownChunker.d.ts +16 -0
  125. package/dist/lib/rag/chunking/markdownChunker.js +174 -2
  126. package/dist/lib/rag/pipeline/contextAssembly.js +2 -1
  127. package/dist/lib/rag/ragIntegration.d.ts +18 -1
  128. package/dist/lib/rag/ragIntegration.js +94 -14
  129. package/dist/lib/rag/retrieval/vectorQueryTool.js +21 -4
  130. package/dist/lib/server/abstract/baseServerAdapter.js +4 -1
  131. package/dist/lib/server/adapters/fastifyAdapter.js +35 -30
  132. package/dist/lib/services/server/ai/observability/instrumentation.d.ts +32 -0
  133. package/dist/lib/services/server/ai/observability/instrumentation.js +39 -0
  134. package/dist/lib/telemetry/attributes.d.ts +52 -0
  135. package/dist/lib/telemetry/attributes.js +61 -0
  136. package/dist/lib/telemetry/index.d.ts +3 -0
  137. package/dist/lib/telemetry/index.js +3 -0
  138. package/dist/lib/telemetry/telemetryService.d.ts +6 -0
  139. package/dist/lib/telemetry/telemetryService.js +6 -0
  140. package/dist/lib/telemetry/tracers.d.ts +15 -0
  141. package/dist/lib/telemetry/tracers.js +17 -0
  142. package/dist/lib/telemetry/withSpan.d.ts +9 -0
  143. package/dist/lib/telemetry/withSpan.js +35 -0
  144. package/dist/lib/types/contextTypes.d.ts +10 -0
  145. package/dist/lib/types/errors.d.ts +62 -0
  146. package/dist/lib/types/errors.js +107 -0
  147. package/dist/lib/types/index.d.ts +2 -1
  148. package/dist/lib/types/index.js +2 -0
  149. package/dist/lib/types/providers.d.ts +107 -0
  150. package/dist/lib/types/providers.js +69 -0
  151. package/dist/lib/types/streamTypes.d.ts +14 -0
  152. package/dist/lib/types/subscriptionTypes.d.ts +893 -0
  153. package/dist/lib/types/subscriptionTypes.js +8 -0
  154. package/dist/lib/utils/conversationMemory.js +121 -82
  155. package/dist/lib/utils/logger.d.ts +5 -0
  156. package/dist/lib/utils/logger.js +50 -2
  157. package/dist/lib/utils/messageBuilder.js +22 -42
  158. package/dist/lib/utils/modelDetection.js +3 -3
  159. package/dist/lib/utils/providerConfig.d.ts +167 -0
  160. package/dist/lib/utils/providerConfig.js +619 -9
  161. package/dist/lib/utils/providerRetry.d.ts +41 -0
  162. package/dist/lib/utils/providerRetry.js +114 -0
  163. package/dist/lib/utils/retryability.d.ts +14 -0
  164. package/dist/lib/utils/retryability.js +23 -0
  165. package/dist/lib/utils/sanitizers/svg.js +4 -5
  166. package/dist/lib/utils/tokenEstimation.d.ts +11 -1
  167. package/dist/lib/utils/tokenEstimation.js +19 -4
  168. package/dist/lib/utils/videoAnalysisProcessor.js +7 -3
  169. package/dist/mcp/externalServerManager.js +66 -0
  170. package/dist/mcp/mcpCircuitBreaker.js +24 -0
  171. package/dist/mcp/mcpClientFactory.js +16 -0
  172. package/dist/mcp/toolDiscoveryService.js +32 -6
  173. package/dist/mcp/toolRegistry.js +193 -123
  174. package/dist/models/anthropicModels.d.ts +267 -0
  175. package/dist/models/anthropicModels.js +527 -0
  176. package/dist/neurolink.d.ts +6 -0
  177. package/dist/neurolink.js +1162 -646
  178. package/dist/providers/amazonBedrock.d.ts +1 -1
  179. package/dist/providers/amazonBedrock.js +521 -319
  180. package/dist/providers/anthropic.d.ts +123 -2
  181. package/dist/providers/anthropic.js +873 -27
  182. package/dist/providers/anthropicBaseProvider.js +77 -17
  183. package/dist/providers/googleAiStudio.d.ts +1 -1
  184. package/dist/providers/googleAiStudio.js +292 -227
  185. package/dist/providers/googleVertex.d.ts +36 -1
  186. package/dist/providers/googleVertex.js +553 -260
  187. package/dist/providers/ollama.js +329 -278
  188. package/dist/providers/openAI.js +77 -19
  189. package/dist/providers/sagemaker/parsers.js +3 -3
  190. package/dist/providers/sagemaker/streaming.js +3 -3
  191. package/dist/proxy/proxyFetch.js +81 -48
  192. package/dist/rag/ChunkerFactory.js +1 -1
  193. package/dist/rag/chunkers/MarkdownChunker.d.ts +22 -0
  194. package/dist/rag/chunkers/MarkdownChunker.js +213 -9
  195. package/dist/rag/chunking/markdownChunker.d.ts +16 -0
  196. package/dist/rag/chunking/markdownChunker.js +174 -2
  197. package/dist/rag/pipeline/contextAssembly.js +2 -1
  198. package/dist/rag/ragIntegration.d.ts +18 -1
  199. package/dist/rag/ragIntegration.js +94 -14
  200. package/dist/rag/retrieval/vectorQueryTool.js +21 -4
  201. package/dist/server/abstract/baseServerAdapter.js +4 -1
  202. package/dist/server/adapters/fastifyAdapter.js +35 -30
  203. package/dist/services/server/ai/observability/instrumentation.d.ts +32 -0
  204. package/dist/services/server/ai/observability/instrumentation.js +39 -0
  205. package/dist/telemetry/attributes.d.ts +52 -0
  206. package/dist/telemetry/attributes.js +60 -0
  207. package/dist/telemetry/index.d.ts +3 -0
  208. package/dist/telemetry/index.js +3 -0
  209. package/dist/telemetry/telemetryService.d.ts +6 -0
  210. package/dist/telemetry/telemetryService.js +6 -0
  211. package/dist/telemetry/tracers.d.ts +15 -0
  212. package/dist/telemetry/tracers.js +16 -0
  213. package/dist/telemetry/withSpan.d.ts +9 -0
  214. package/dist/telemetry/withSpan.js +34 -0
  215. package/dist/types/contextTypes.d.ts +10 -0
  216. package/dist/types/errors.d.ts +62 -0
  217. package/dist/types/errors.js +107 -0
  218. package/dist/types/index.d.ts +2 -1
  219. package/dist/types/index.js +2 -0
  220. package/dist/types/providers.d.ts +107 -0
  221. package/dist/types/providers.js +69 -0
  222. package/dist/types/streamTypes.d.ts +14 -0
  223. package/dist/types/subscriptionTypes.d.ts +893 -0
  224. package/dist/types/subscriptionTypes.js +7 -0
  225. package/dist/utils/conversationMemory.js +121 -82
  226. package/dist/utils/logger.d.ts +5 -0
  227. package/dist/utils/logger.js +50 -2
  228. package/dist/utils/messageBuilder.js +22 -42
  229. package/dist/utils/modelDetection.js +3 -3
  230. package/dist/utils/providerConfig.d.ts +167 -0
  231. package/dist/utils/providerConfig.js +619 -9
  232. package/dist/utils/providerRetry.d.ts +41 -0
  233. package/dist/utils/providerRetry.js +113 -0
  234. package/dist/utils/retryability.d.ts +14 -0
  235. package/dist/utils/retryability.js +22 -0
  236. package/dist/utils/sanitizers/svg.js +4 -5
  237. package/dist/utils/tokenEstimation.d.ts +11 -1
  238. package/dist/utils/tokenEstimation.js +19 -4
  239. package/dist/utils/videoAnalysisProcessor.js +7 -3
  240. package/dist/workflow/config.d.ts +26 -26
  241. package/package.json +2 -1
@@ -12,8 +12,45 @@
12
12
  *
13
13
  * @module core/modules/MessageBuilder
14
14
  */
15
+ import { tracers, ATTR, withSpan } from "../../telemetry/index.js";
15
16
  import { logger } from "../../utils/logger.js";
16
17
  import { buildMessagesArray, buildMultimodalMessagesArray, } from "../../utils/messageBuilder.js";
18
+ /**
19
+ * Compute total content length across all messages for span attributes.
20
+ */
21
+ function computeTotalContentLength(messages) {
22
+ let total = 0;
23
+ for (const msg of messages) {
24
+ if (typeof msg.content === "string") {
25
+ total += msg.content.length;
26
+ }
27
+ else if (Array.isArray(msg.content)) {
28
+ for (const part of msg.content) {
29
+ if ("text" in part &&
30
+ typeof part.text === "string") {
31
+ total += part.text.length;
32
+ }
33
+ }
34
+ }
35
+ }
36
+ return total;
37
+ }
38
+ /**
39
+ * Check whether input contains multimodal content (images, files, PDFs, CSVs).
40
+ */
41
+ function detectMultimodal(opts) {
42
+ const input = opts.input;
43
+ const hasImages = !!input?.images?.length;
44
+ const hasContent = !!input?.content?.length;
45
+ const hasCSVFiles = !!input?.csvFiles?.length;
46
+ const hasPdfFiles = !!input?.pdfFiles?.length;
47
+ const hasFiles = !!input?.files?.length;
48
+ return {
49
+ isMultimodal: hasImages || hasContent || hasCSVFiles || hasPdfFiles || hasFiles,
50
+ hasImages,
51
+ hasFiles: hasCSVFiles || hasPdfFiles || hasFiles,
52
+ };
53
+ }
17
54
  /**
18
55
  * MessageBuilder class - Handles message construction for AI providers
19
56
  */
@@ -29,93 +66,101 @@ export class MessageBuilder {
29
66
  * Detects multimodal input and routes to appropriate message builder
30
67
  */
31
68
  async buildMessages(options) {
32
- const hasMultimodalInput = (opts) => {
33
- const input = opts.input;
34
- const hasImages = !!input?.images?.length;
35
- const hasContent = !!input?.content?.length;
36
- const hasCSVFiles = !!input?.csvFiles?.length;
37
- const hasPdfFiles = !!input?.pdfFiles?.length;
38
- const hasFiles = !!input?.files?.length;
39
- return hasImages || hasContent || hasCSVFiles || hasPdfFiles || hasFiles;
40
- };
41
- let messages;
42
- if (hasMultimodalInput(options)) {
43
- if (process.env.NEUROLINK_DEBUG === "true") {
44
- logger.debug("Detected multimodal input, using multimodal message builder");
45
- }
46
- const input = options.input;
47
- const multimodalOptions = {
48
- input: {
49
- text: options.prompt || options.input?.text || "",
50
- images: input?.images,
51
- content: input?.content,
52
- csvFiles: input?.csvFiles,
53
- pdfFiles: input?.pdfFiles,
54
- files: input?.files,
55
- },
56
- csvOptions: options.csvOptions,
57
- provider: options.provider,
58
- model: options.model,
59
- temperature: options.temperature,
60
- maxTokens: options.maxTokens,
61
- systemPrompt: options.systemPrompt,
62
- enableAnalytics: options.enableAnalytics,
63
- enableEvaluation: options.enableEvaluation,
64
- context: options.context,
65
- conversationHistory: options.conversationMessages,
66
- schema: options.schema,
67
- output: options.output,
68
- fileRegistry: options.fileRegistry,
69
- };
70
- messages = await buildMultimodalMessagesArray(multimodalOptions, this.providerName, this.modelName);
71
- }
72
- else {
73
- if (process.env.NEUROLINK_DEBUG === "true") {
74
- logger.debug("No multimodal input detected, using standard message builder");
75
- }
76
- messages = await buildMessagesArray(options);
77
- }
78
- // Convert messages to Vercel AI SDK format
79
- // Preserve providerOptions (e.g. Anthropic cache_control) through conversion
80
- return messages.map((msg) => {
81
- const providerOptions = msg
82
- .providerOptions;
83
- if (typeof msg.content === "string") {
84
- return {
85
- role: msg.role,
86
- content: msg.content,
87
- ...(providerOptions && { providerOptions }),
69
+ return withSpan({
70
+ name: "neurolink.message.build",
71
+ tracer: tracers.sdk,
72
+ attributes: {
73
+ [ATTR.NL_PROVIDER]: this.providerName,
74
+ [ATTR.NL_MODEL]: this.modelName,
75
+ },
76
+ }, async (span) => {
77
+ const { isMultimodal, hasImages, hasFiles } = detectMultimodal(options);
78
+ span.setAttribute(ATTR.MSG_IS_MULTIMODAL, isMultimodal);
79
+ let messages;
80
+ if (isMultimodal) {
81
+ if (process.env.NEUROLINK_DEBUG === "true") {
82
+ logger.debug("Detected multimodal input, using multimodal message builder");
83
+ }
84
+ const input = options.input;
85
+ const multimodalOptions = {
86
+ input: {
87
+ text: options.prompt || options.input?.text || "",
88
+ images: input?.images,
89
+ content: input?.content,
90
+ csvFiles: input?.csvFiles,
91
+ pdfFiles: input?.pdfFiles,
92
+ files: input?.files,
93
+ },
94
+ csvOptions: options.csvOptions,
95
+ provider: options.provider,
96
+ model: options.model,
97
+ temperature: options.temperature,
98
+ maxTokens: options.maxTokens,
99
+ systemPrompt: options.systemPrompt,
100
+ enableAnalytics: options.enableAnalytics,
101
+ enableEvaluation: options.enableEvaluation,
102
+ context: options.context,
103
+ conversationHistory: options.conversationMessages,
104
+ schema: options.schema,
105
+ output: options.output,
106
+ fileRegistry: options.fileRegistry,
88
107
  };
108
+ messages = await buildMultimodalMessagesArray(multimodalOptions, this.providerName, this.modelName);
89
109
  }
90
110
  else {
91
- return {
92
- role: msg.role,
93
- content: msg.content.map((item) => {
94
- const itemProviderOptions = item
95
- .providerOptions;
96
- if (item.type === "text") {
97
- return {
98
- type: "text",
99
- text: item.text || "",
100
- ...(itemProviderOptions && {
101
- providerOptions: itemProviderOptions,
102
- }),
103
- };
104
- }
105
- else if (item.type === "image") {
106
- return {
107
- type: "image",
108
- image: item.image || "",
109
- ...(itemProviderOptions && {
110
- providerOptions: itemProviderOptions,
111
- }),
112
- };
113
- }
114
- return item;
115
- }),
116
- ...(providerOptions && { providerOptions }),
117
- };
111
+ if (process.env.NEUROLINK_DEBUG === "true") {
112
+ logger.debug("No multimodal input detected, using standard message builder");
113
+ }
114
+ messages = await buildMessagesArray(options);
118
115
  }
116
+ // Convert messages to Vercel AI SDK format
117
+ // Preserve providerOptions (e.g. Anthropic cache_control) through conversion
118
+ const coreMessages = messages.map((msg) => {
119
+ const providerOptions = msg
120
+ .providerOptions;
121
+ if (typeof msg.content === "string") {
122
+ return {
123
+ role: msg.role,
124
+ content: msg.content,
125
+ ...(providerOptions && { providerOptions }),
126
+ };
127
+ }
128
+ else {
129
+ return {
130
+ role: msg.role,
131
+ content: msg.content.map((item) => {
132
+ const itemProviderOptions = item
133
+ .providerOptions;
134
+ if (item.type === "text") {
135
+ return {
136
+ type: "text",
137
+ text: item.text || "",
138
+ ...(itemProviderOptions && {
139
+ providerOptions: itemProviderOptions,
140
+ }),
141
+ };
142
+ }
143
+ else if (item.type === "image") {
144
+ return {
145
+ type: "image",
146
+ image: item.image || "",
147
+ ...(itemProviderOptions && {
148
+ providerOptions: itemProviderOptions,
149
+ }),
150
+ };
151
+ }
152
+ return item;
153
+ }),
154
+ ...(providerOptions && { providerOptions }),
155
+ };
156
+ }
157
+ });
158
+ span.setAttribute(ATTR.MSG_COUNT, coreMessages.length);
159
+ span.setAttribute(ATTR.MSG_HAS_IMAGES, hasImages);
160
+ span.setAttribute(ATTR.MSG_HAS_FILES, hasFiles);
161
+ span.setAttribute(ATTR.MSG_HAS_SYSTEM_PROMPT, !!options.systemPrompt);
162
+ span.setAttribute(ATTR.MSG_TOTAL_CONTENT_LENGTH, computeTotalContentLength(coreMessages));
163
+ return coreMessages;
119
164
  });
120
165
  }
121
166
  /**
@@ -127,97 +172,104 @@ export class MessageBuilder {
127
172
  * @returns Promise resolving to CoreMessage array ready for AI SDK
128
173
  */
129
174
  async buildMessagesForStream(options) {
130
- // Detect multimodal input
131
- const hasMultimodalInput = (opts) => {
132
- const input = opts.input;
133
- const hasImages = !!input?.images?.length;
134
- const hasContent = !!input?.content?.length;
135
- const hasCSVFiles = !!input?.csvFiles?.length;
136
- const hasPdfFiles = !!input?.pdfFiles?.length;
137
- const hasFiles = !!input?.files?.length;
138
- return hasImages || hasContent || hasCSVFiles || hasPdfFiles || hasFiles;
139
- };
140
- let messages;
141
- if (hasMultimodalInput(options)) {
142
- if (process.env.NEUROLINK_DEBUG === "true") {
143
- logger.debug(`${this.providerName}: Detected multimodal input, using multimodal message builder`);
144
- }
145
- const input = options.input;
146
- const multimodalOptions = {
147
- input: {
148
- text: options.prompt ||
149
- options.input?.text ||
150
- "",
151
- images: input?.images,
152
- content: input?.content,
153
- csvFiles: input?.csvFiles,
154
- pdfFiles: input?.pdfFiles,
155
- files: input?.files,
156
- },
157
- csvOptions: options.csvOptions,
158
- provider: options.provider,
159
- model: options.model,
160
- temperature: options.temperature,
161
- maxTokens: options.maxTokens,
162
- systemPrompt: options.systemPrompt,
163
- enableAnalytics: options.enableAnalytics,
164
- enableEvaluation: options.enableEvaluation,
165
- context: options.context,
166
- conversationHistory: options
167
- .conversationMessages,
168
- schema: options.schema,
169
- output: options.output,
170
- fileRegistry: options.fileRegistry,
171
- };
172
- messages = await buildMultimodalMessagesArray(multimodalOptions, this.providerName, this.modelName);
173
- }
174
- else {
175
- if (process.env.NEUROLINK_DEBUG === "true") {
176
- logger.debug(`${this.providerName}: No multimodal input detected, using standard message builder`);
177
- }
178
- messages = await buildMessagesArray(options);
179
- }
180
- // Convert messages to Vercel AI SDK format
181
- // Preserve providerOptions (e.g. Anthropic cache_control) through conversion
182
- return messages.map((msg) => {
183
- const providerOptions = msg
184
- .providerOptions;
185
- if (typeof msg.content === "string") {
186
- return {
187
- role: msg.role,
188
- content: msg.content,
189
- ...(providerOptions && { providerOptions }),
175
+ return withSpan({
176
+ name: "neurolink.message.build_for_stream",
177
+ tracer: tracers.sdk,
178
+ attributes: {
179
+ [ATTR.NL_PROVIDER]: this.providerName,
180
+ [ATTR.NL_MODEL]: this.modelName,
181
+ },
182
+ }, async (span) => {
183
+ const { isMultimodal, hasImages, hasFiles } = detectMultimodal(options);
184
+ span.setAttribute(ATTR.MSG_IS_MULTIMODAL, isMultimodal);
185
+ let messages;
186
+ if (isMultimodal) {
187
+ if (process.env.NEUROLINK_DEBUG === "true") {
188
+ logger.debug(`${this.providerName}: Detected multimodal input, using multimodal message builder`);
189
+ }
190
+ const input = options.input;
191
+ const multimodalOptions = {
192
+ input: {
193
+ text: options.prompt ||
194
+ options.input?.text ||
195
+ "",
196
+ images: input?.images,
197
+ content: input?.content,
198
+ csvFiles: input?.csvFiles,
199
+ pdfFiles: input?.pdfFiles,
200
+ files: input?.files,
201
+ },
202
+ csvOptions: options.csvOptions,
203
+ provider: options.provider,
204
+ model: options.model,
205
+ temperature: options.temperature,
206
+ maxTokens: options.maxTokens,
207
+ systemPrompt: options.systemPrompt,
208
+ enableAnalytics: options.enableAnalytics,
209
+ enableEvaluation: options.enableEvaluation,
210
+ context: options.context,
211
+ conversationHistory: options
212
+ .conversationMessages,
213
+ schema: options.schema,
214
+ output: options.output,
215
+ fileRegistry: options.fileRegistry,
190
216
  };
217
+ messages = await buildMultimodalMessagesArray(multimodalOptions, this.providerName, this.modelName);
191
218
  }
192
219
  else {
193
- return {
194
- role: msg.role,
195
- content: msg.content.map((item) => {
196
- const itemProviderOptions = item
197
- .providerOptions;
198
- if (item.type === "text") {
199
- return {
200
- type: "text",
201
- text: item.text || "",
202
- ...(itemProviderOptions && {
203
- providerOptions: itemProviderOptions,
204
- }),
205
- };
206
- }
207
- else if (item.type === "image") {
208
- return {
209
- type: "image",
210
- image: item.image || "",
211
- ...(itemProviderOptions && {
212
- providerOptions: itemProviderOptions,
213
- }),
214
- };
215
- }
216
- return item;
217
- }),
218
- ...(providerOptions && { providerOptions }),
219
- };
220
+ if (process.env.NEUROLINK_DEBUG === "true") {
221
+ logger.debug(`${this.providerName}: No multimodal input detected, using standard message builder`);
222
+ }
223
+ messages = await buildMessagesArray(options);
220
224
  }
225
+ // Convert messages to Vercel AI SDK format
226
+ // Preserve providerOptions (e.g. Anthropic cache_control) through conversion
227
+ const coreMessages = messages.map((msg) => {
228
+ const providerOptions = msg
229
+ .providerOptions;
230
+ if (typeof msg.content === "string") {
231
+ return {
232
+ role: msg.role,
233
+ content: msg.content,
234
+ ...(providerOptions && { providerOptions }),
235
+ };
236
+ }
237
+ else {
238
+ return {
239
+ role: msg.role,
240
+ content: msg.content.map((item) => {
241
+ const itemProviderOptions = item
242
+ .providerOptions;
243
+ if (item.type === "text") {
244
+ return {
245
+ type: "text",
246
+ text: item.text || "",
247
+ ...(itemProviderOptions && {
248
+ providerOptions: itemProviderOptions,
249
+ }),
250
+ };
251
+ }
252
+ else if (item.type === "image") {
253
+ return {
254
+ type: "image",
255
+ image: item.image || "",
256
+ ...(itemProviderOptions && {
257
+ providerOptions: itemProviderOptions,
258
+ }),
259
+ };
260
+ }
261
+ return item;
262
+ }),
263
+ ...(providerOptions && { providerOptions }),
264
+ };
265
+ }
266
+ });
267
+ span.setAttribute(ATTR.MSG_COUNT, coreMessages.length);
268
+ span.setAttribute(ATTR.MSG_HAS_IMAGES, hasImages);
269
+ span.setAttribute(ATTR.MSG_HAS_FILES, hasFiles);
270
+ span.setAttribute(ATTR.MSG_HAS_SYSTEM_PROMPT, !!options.systemPrompt);
271
+ span.setAttribute(ATTR.MSG_TOTAL_CONTENT_LENGTH, computeTotalContentLength(coreMessages));
272
+ return coreMessages;
221
273
  });
222
274
  }
223
275
  }
@@ -28,6 +28,7 @@ export declare class StreamHandler {
28
28
  validateStreamOptions(options: StreamOptions): void;
29
29
  /**
30
30
  * Create text stream transformation - consolidates identical logic from 7/10 providers
31
+ * Tracks TTFC (Time To First Chunk), chunk count, and total bytes streamed.
31
32
  */
32
33
  createTextStream(result: {
33
34
  textStream: AsyncIterable<string>;
@@ -12,6 +12,8 @@
12
12
  *
13
13
  * @module core/modules/StreamHandler
14
14
  */
15
+ import { trace, context as otelContext, SpanStatusCode, } from "@opentelemetry/api";
16
+ import { tracers, ATTR, withSpan } from "../../telemetry/index.js";
15
17
  import { logger } from "../../utils/logger.js";
16
18
  import { validateStreamOptions as validateStreamOpts, ValidationError, createValidationSummary, } from "../../utils/parameterValidation.js";
17
19
  import { STEP_LIMITS } from "../constants.js";
@@ -31,33 +33,87 @@ export class StreamHandler {
31
33
  * Validate stream options - consolidates validation from 7/10 providers
32
34
  */
33
35
  validateStreamOptions(options) {
34
- const validation = validateStreamOpts(options);
35
- if (!validation.isValid) {
36
- const summary = createValidationSummary(validation);
37
- throw new ValidationError(`Stream options validation failed: ${summary}`, "options", "VALIDATION_FAILED", validation.suggestions);
36
+ const span = tracers.stream.startSpan("neurolink.stream.validate", {
37
+ attributes: {
38
+ [ATTR.NL_PROVIDER]: this.providerName,
39
+ [ATTR.NL_MODEL]: this.modelName,
40
+ "stream.has_max_steps": options.maxSteps !== undefined,
41
+ },
42
+ });
43
+ try {
44
+ const validation = validateStreamOpts(options);
45
+ if (!validation.isValid) {
46
+ const summary = createValidationSummary(validation);
47
+ span.setAttribute("stream.validation_errors", validation.errors?.length ?? 0);
48
+ throw new ValidationError(`Stream options validation failed: ${summary}`, "options", "VALIDATION_FAILED", validation.suggestions);
49
+ }
50
+ // Log warnings if any
51
+ if (validation.warnings.length > 0) {
52
+ logger.warn("Stream options validation warnings:", validation.warnings);
53
+ span.addEvent("stream.validation.warnings", {
54
+ "warning.count": validation.warnings.length,
55
+ warnings: validation.warnings.join("; ").substring(0, 500),
56
+ });
57
+ }
58
+ // Additional BaseProvider-specific validation
59
+ if (options.maxSteps !== undefined) {
60
+ if (options.maxSteps < STEP_LIMITS.min ||
61
+ options.maxSteps > STEP_LIMITS.max) {
62
+ throw new ValidationError(`maxSteps must be between ${STEP_LIMITS.min} and ${STEP_LIMITS.max}`, "maxSteps", "OUT_OF_RANGE", [
63
+ `Use a value between ${STEP_LIMITS.min} and ${STEP_LIMITS.max} for optimal performance`,
64
+ ]);
65
+ }
66
+ }
38
67
  }
39
- // Log warnings if any
40
- if (validation.warnings.length > 0) {
41
- logger.warn("Stream options validation warnings:", validation.warnings);
68
+ catch (error) {
69
+ span.recordException(error instanceof Error ? error : new Error(String(error)));
70
+ // NLK-GAP-006 fix: set error status alongside recordException
71
+ span.setStatus({
72
+ code: SpanStatusCode.ERROR,
73
+ message: error instanceof Error ? error.message : String(error),
74
+ });
75
+ throw error;
42
76
  }
43
- // Additional BaseProvider-specific validation
44
- if (options.maxSteps !== undefined) {
45
- if (options.maxSteps < STEP_LIMITS.min ||
46
- options.maxSteps > STEP_LIMITS.max) {
47
- throw new ValidationError(`maxSteps must be between ${STEP_LIMITS.min} and ${STEP_LIMITS.max}`, "maxSteps", "OUT_OF_RANGE", [
48
- `Use a value between ${STEP_LIMITS.min} and ${STEP_LIMITS.max} for optimal performance`,
49
- ]);
50
- }
77
+ finally {
78
+ span.end();
51
79
  }
52
80
  }
53
81
  /**
54
82
  * Create text stream transformation - consolidates identical logic from 7/10 providers
83
+ * Tracks TTFC (Time To First Chunk), chunk count, and total bytes streamed.
55
84
  */
56
85
  createTextStream(result) {
86
+ const providerName = this.providerName;
57
87
  return (async function* () {
88
+ let chunkCount = 0;
89
+ let totalBytes = 0;
90
+ const streamStart = Date.now();
91
+ let firstChunkTime;
58
92
  for await (const chunk of result.textStream) {
93
+ chunkCount++;
94
+ totalBytes += chunk.length;
95
+ if (!firstChunkTime) {
96
+ firstChunkTime = Date.now();
97
+ const activeSpan = trace.getSpan(otelContext.active());
98
+ if (activeSpan) {
99
+ activeSpan.addEvent("stream.first_chunk", {
100
+ "stream.ttfc_ms": firstChunkTime - streamStart,
101
+ "stream.provider": providerName,
102
+ });
103
+ }
104
+ }
59
105
  yield { content: chunk };
60
106
  }
107
+ // Record completion metrics on the active span
108
+ const activeSpan = trace.getSpan(otelContext.active());
109
+ if (activeSpan) {
110
+ activeSpan.addEvent("stream.complete", {
111
+ "stream.chunk_count": chunkCount,
112
+ "stream.total_bytes": totalBytes,
113
+ "stream.duration_ms": Date.now() - streamStart,
114
+ "stream.ttfc_ms": firstChunkTime ? firstChunkTime - streamStart : -1,
115
+ });
116
+ }
61
117
  })();
62
118
  }
63
119
  /**
@@ -75,18 +131,30 @@ export class StreamHandler {
75
131
  * Create stream analytics - consolidates analytics from 4/10 providers
76
132
  */
77
133
  async createStreamAnalytics(result, startTime, options) {
78
- try {
79
- const analytics = createAnalytics(this.providerName, this.modelName, result, Date.now() - startTime, {
80
- requestId: `${this.providerName}-stream-${nanoid()}`,
81
- streamingMode: true,
82
- ...options.context,
83
- });
84
- return analytics;
85
- }
86
- catch (error) {
87
- logger.warn(`Analytics creation failed for ${this.providerName}:`, error);
88
- return undefined;
89
- }
134
+ return withSpan({
135
+ name: "neurolink.stream.analytics",
136
+ tracer: tracers.stream,
137
+ attributes: {
138
+ [ATTR.NL_PROVIDER]: this.providerName,
139
+ [ATTR.NL_MODEL]: this.modelName,
140
+ [ATTR.NL_STREAM_MODE]: true,
141
+ },
142
+ }, async (span) => {
143
+ try {
144
+ const durationMs = Date.now() - startTime;
145
+ span.setAttribute("stream.duration_ms", durationMs);
146
+ const analytics = createAnalytics(this.providerName, this.modelName, result, durationMs, {
147
+ requestId: `${this.providerName}-stream-${nanoid()}`,
148
+ streamingMode: true,
149
+ ...options.context,
150
+ });
151
+ return analytics;
152
+ }
153
+ catch (error) {
154
+ logger.warn(`Analytics creation failed for ${this.providerName}:`, error);
155
+ return undefined;
156
+ }
157
+ });
90
158
  }
91
159
  /**
92
160
  * Validate streaming-only options (called before executeStream)
@@ -41,7 +41,16 @@ export declare class TelemetryHandler {
41
41
  totalTokens: number;
42
42
  } | undefined, responseTime: number): Promise<void>;
43
43
  /**
44
- * Calculate actual cost based on token usage and provider configuration
44
+ * Calculate actual cost based on token usage and provider configuration.
45
+ *
46
+ * Uses the per-model pricing table first (which has accurate rates for
47
+ * specific models like Claude on Vertex AI), then falls back to the
48
+ * provider-level default cost from modelConfiguration.
49
+ *
50
+ * Previously this only used modelConfig.getCostInfo() which returns
51
+ * provider-level defaults (e.g. Gemini rates for the "vertex" provider),
52
+ * causing a ~1,780x under-estimate when the actual model was Claude Sonnet
53
+ * on Vertex AI ($0.000060 vs $0.106895 for the same request).
45
54
  */
46
55
  calculateActualCost(usage: {
47
56
  promptTokens?: number;