@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
@@ -13,9 +13,14 @@
13
13
  * @module core/modules/GenerationHandler
14
14
  */
15
15
  import { generateText, Output, NoObjectGeneratedError } from "ai";
16
+ import { SpanKind, SpanStatusCode } from "@opentelemetry/api";
17
+ import { tracers } from "../../telemetry/tracers.js";
16
18
  import { logger } from "../../utils/logger.js";
17
19
  import { extractTokenUsage, extractCacheCreationTokens, extractCacheReadTokens, calculateCacheSavingsPercent, } from "../../utils/tokenUtils.js";
20
+ import { withProviderRetry } from "../../utils/providerRetry.js";
21
+ import { calculateCost } from "../../utils/pricing.js";
18
22
  import { DEFAULT_MAX_STEPS } from "../constants.js";
23
+ const genTracer = tracers.generation;
19
24
  /**
20
25
  * Safely preview-serialize a value for debug logging.
21
26
  * Handles undefined, circular references, and non-serializable values.
@@ -59,10 +64,14 @@ export class GenerationHandler {
59
64
  const isAnthropicProvider = this.providerName === "anthropic" ||
60
65
  this.providerName === "bedrock" ||
61
66
  (this.providerName === "vertex" && this.modelName?.startsWith("claude-"));
62
- const useStructuredOutput = includeStructuredOutput &&
67
+ // Gemini 2.5 and earlier cannot use tools + structured JSON output simultaneously.
68
+ // When both are requested on a Google provider, disable structured output (tools take priority).
69
+ const wantsStructuredOutput = includeStructuredOutput &&
63
70
  !!options.schema &&
64
71
  (options.output?.format === "json" ||
65
72
  options.output?.format === "structured");
73
+ const useStructuredOutput = wantsStructuredOutput &&
74
+ !(isGoogleProvider && shouldUseTools && Object.keys(tools).length > 0);
66
75
  // Annotate the last tool with cache_control so the full tool-definition
67
76
  // block becomes a cache breakpoint for Anthropic-family providers.
68
77
  // Non-Anthropic providers harmlessly ignore unknown providerOptions.
@@ -98,6 +107,7 @@ export class GenerationHandler {
98
107
  }),
99
108
  temperature: options.temperature,
100
109
  maxTokens: options.maxTokens,
110
+ maxRetries: 0, // NL11: Disable AI SDK's invisible internal retries; we handle retries with OTel instrumentation
101
111
  abortSignal: options.abortSignal,
102
112
  ...(useStructuredOutput &&
103
113
  options.schema && {
@@ -153,108 +163,59 @@ export class GenerationHandler {
153
163
  * Execute the generation with AI SDK
154
164
  */
155
165
  async executeGeneration(model, messages, tools, options) {
156
- const shouldUseTools = !options.disableTools && this.supportsToolsFn();
157
- const useStructuredOutput = !!options.schema &&
158
- (options.output?.format === "json" ||
159
- options.output?.format === "structured");
160
- const requestId = options.requestId ||
161
- options.context?.requestId ||
162
- "unknown";
163
- logger.info("[GenerationHandler] Calling generateText", {
164
- requestId,
165
- model: model.modelId || "unknown",
166
- messageCount: messages.length,
167
- toolCount: Object.keys(tools || {}).length,
168
- maxSteps: options.maxSteps,
169
- temperature: options.temperature,
170
- });
171
- if (logger.shouldLog("debug")) {
172
- try {
173
- logger.debug("[Observability] Full generateText parameters", {
174
- requestId,
175
- model: model.modelId || "unknown",
176
- messageCount: messages.length,
177
- messages: messages.map((msg, i) => ({
178
- index: i,
179
- role: msg.role,
180
- contentLength: typeof msg.content === "string"
181
- ? msg.content.length
182
- : safePreview(msg.content).length,
183
- contentPreview: typeof msg.content === "string"
184
- ? msg.content.substring(0, 200)
185
- : "[multimodal]",
186
- })),
187
- toolNames: Object.keys(tools || {}),
188
- toolCount: Object.keys(tools || {}).length,
189
- maxSteps: options.maxSteps,
190
- temperature: options.temperature,
191
- maxTokens: options.maxTokens,
192
- });
193
- }
194
- catch {
195
- // Ignore serialization errors in debug logging
196
- }
197
- }
198
- const genStartTime = Date.now();
199
- try {
200
- const result = await this.callGenerateText(model, messages, tools, options, shouldUseTools, true);
201
- logger.info("[GenerationHandler] generateText returned", {
166
+ return genTracer.startActiveSpan("neurolink.executeGeneration", { kind: SpanKind.INTERNAL }, async (span) => {
167
+ const shouldUseTools = !options.disableTools && this.supportsToolsFn();
168
+ const toolCount = Object.keys(tools || {}).length;
169
+ const useStructuredOutput = !!options.schema &&
170
+ (options.output?.format === "json" ||
171
+ options.output?.format === "structured");
172
+ span.setAttribute("gen_ai.system", this.providerName || "unknown");
173
+ span.setAttribute("neurolink.structured_output", useStructuredOutput);
174
+ span.setAttribute("neurolink.tool_count", toolCount);
175
+ span.setAttribute("neurolink.message_count", messages.length);
176
+ span.setAttribute("gen_ai.request.model", model.modelId || this.modelName || "unknown");
177
+ const requestId = options.requestId ||
178
+ options.context?.requestId ||
179
+ "unknown";
180
+ logger.info("[GenerationHandler] Calling generateText", {
202
181
  requestId,
203
- durationMs: Date.now() - genStartTime,
204
- finishReason: result.finishReason,
205
- steps: result.steps?.length || 1,
206
- toolCallsTotal: result.toolCalls?.length || 0,
207
- responseChars: result.text?.length || 0,
182
+ model: model.modelId || "unknown",
183
+ messageCount: messages.length,
184
+ toolCount,
185
+ maxSteps: options.maxSteps,
186
+ temperature: options.temperature,
208
187
  });
209
188
  if (logger.shouldLog("debug")) {
210
- logger.debug("[Observability] Full LLM response", {
211
- requestId,
212
- finishReason: result.finishReason,
213
- responseTextPreview: result.text?.substring(0, 200) || "",
214
- responseTextLength: result.text?.length || 0,
215
- toolCalls: result.toolCalls?.map((tc) => ({
216
- toolName: tc.toolName,
217
- argsPreview: safePreview(tc.args),
218
- })),
219
- toolResults: result.toolResults?.map((tr) => ({
220
- toolName: tr.toolName,
221
- resultPreview: safePreview(tr.result),
222
- })),
223
- steps: result.steps?.map((step, i) => ({
224
- stepIndex: i,
225
- stepType: step.stepType,
226
- textPreview: step.text?.substring(0, 200),
227
- textLength: step.text?.length || 0,
228
- toolCalls: step.toolCalls?.map((tc) => ({
229
- toolName: tc.toolName,
230
- argsPreview: safePreview(tc.args),
189
+ try {
190
+ logger.debug("[Observability] Full generateText parameters", {
191
+ requestId,
192
+ model: model.modelId || "unknown",
193
+ messageCount: messages.length,
194
+ messages: messages.map((msg, i) => ({
195
+ index: i,
196
+ role: msg.role,
197
+ contentLength: typeof msg.content === "string"
198
+ ? msg.content.length
199
+ : safePreview(msg.content).length,
200
+ contentPreview: typeof msg.content === "string"
201
+ ? msg.content.substring(0, 200)
202
+ : "[multimodal]",
231
203
  })),
232
- toolResults: step.toolResults?.map((tr) => ({
233
- toolName: tr.toolName,
234
- resultPreview: safePreview(tr.result),
235
- })),
236
- finishReason: step.finishReason,
237
- })),
238
- usage: result.usage,
239
- providerMetadata: result.experimental_providerMetadata ||
240
- result.providerMetadata,
241
- });
204
+ toolNames: Object.keys(tools || {}),
205
+ toolCount,
206
+ maxSteps: options.maxSteps,
207
+ temperature: options.temperature,
208
+ maxTokens: options.maxTokens,
209
+ });
210
+ }
211
+ catch {
212
+ // Ignore serialization errors in debug logging
213
+ }
242
214
  }
243
- return result;
244
- }
245
- catch (error) {
246
- // If NoObjectGeneratedError is thrown when using schema + tools together,
247
- // fall back to generating without experimental_output and extract JSON manually
248
- if (error instanceof NoObjectGeneratedError && useStructuredOutput) {
249
- logger.debug("[GenerationHandler] NoObjectGeneratedError caught - falling back to manual JSON extraction", {
250
- provider: this.providerName,
251
- model: this.modelName,
252
- error: error.message,
253
- });
254
- // Retry without experimental_output - the formatEnhancedResult method
255
- // will extract JSON from the text response
256
- const result = await this.callGenerateText(model, messages, tools, options, shouldUseTools, false);
257
- logger.info("[GenerationHandler] generateText returned (fallback)", {
215
+ const genStartTime = Date.now();
216
+ try {
217
+ const result = await withProviderRetry(() => this.callGenerateText(model, messages, tools, options, shouldUseTools, true), span, "generateText");
218
+ logger.info("[GenerationHandler] generateText returned", {
258
219
  requestId,
259
220
  durationMs: Date.now() - genStartTime,
260
221
  finishReason: result.finishReason,
@@ -262,11 +223,107 @@ export class GenerationHandler {
262
223
  toolCallsTotal: result.toolCalls?.length || 0,
263
224
  responseChars: result.text?.length || 0,
264
225
  });
226
+ if (logger.shouldLog("debug")) {
227
+ logger.debug("[Observability] LLM response metadata", {
228
+ requestId,
229
+ responseLength: result.text?.length || 0,
230
+ hasToolCalls: !!(result.toolCalls && result.toolCalls.length > 0),
231
+ toolCallCount: result.toolCalls?.length || 0,
232
+ toolNames: result.toolCalls?.map((tc) => tc.toolName),
233
+ finishReason: result.finishReason,
234
+ stepCount: result.steps?.length || 0,
235
+ steps: result.steps?.map((step, i) => ({
236
+ stepIndex: i,
237
+ stepType: step.stepType,
238
+ textLength: step.text?.length || 0,
239
+ toolCallCount: step.toolCalls?.length || 0,
240
+ toolNames: step.toolCalls?.map((tc) => tc.toolName),
241
+ toolResultCount: step.toolResults?.length || 0,
242
+ finishReason: step.finishReason,
243
+ })),
244
+ usage: result.usage,
245
+ });
246
+ }
247
+ // Set token usage and completion attributes on span
248
+ if (result.usage) {
249
+ span.setAttribute("gen_ai.usage.input_tokens", result.usage.promptTokens || 0);
250
+ span.setAttribute("gen_ai.usage.output_tokens", result.usage.completionTokens || 0);
251
+ // Cost on span so users can query "what did this trace cost?"
252
+ const cost = calculateCost(this.providerName, this.modelName, {
253
+ input: result.usage.promptTokens || 0,
254
+ output: result.usage.completionTokens || 0,
255
+ total: (result.usage.promptTokens || 0) +
256
+ (result.usage.completionTokens || 0),
257
+ });
258
+ span.setAttribute("neurolink.cost", cost ?? 0);
259
+ }
260
+ if (result.finishReason) {
261
+ span.setAttribute("gen_ai.response.finish_reason", result.finishReason);
262
+ }
263
+ span.setStatus({ code: SpanStatusCode.OK });
265
264
  return result;
266
265
  }
267
- // Re-throw other errors
268
- throw error;
269
- }
266
+ catch (error) {
267
+ // If NoObjectGeneratedError is thrown when using schema + tools together,
268
+ // fall back to generating without experimental_output and extract JSON manually
269
+ if (error instanceof NoObjectGeneratedError && useStructuredOutput) {
270
+ span.setAttribute("neurolink.has_fallback", true);
271
+ // NLK-GAP-007: Record initial failure event before fallback retry
272
+ span.addEvent("retry.initial_failure", {
273
+ "error.message": error.message,
274
+ "retry.attempt": 1,
275
+ "retry.reason": "NoObjectGeneratedError_structured_output_fallback",
276
+ });
277
+ logger.debug("[GenerationHandler] NoObjectGeneratedError caught - falling back to manual JSON extraction", {
278
+ provider: this.providerName,
279
+ model: this.modelName,
280
+ error: error.message,
281
+ });
282
+ // Retry without experimental_output - the formatEnhancedResult method
283
+ // will extract JSON from the text response
284
+ const result = await withProviderRetry(() => this.callGenerateText(model, messages, tools, options, shouldUseTools, false), span, "generateText(fallback)");
285
+ // NLK-GAP-007: Record recovery event after successful fallback
286
+ span.addEvent("retry.recovered", {
287
+ "retry.attempts": 2,
288
+ "retry.strategy": "structured_output_disabled",
289
+ });
290
+ span.setAttribute("retry.count", 1);
291
+ logger.info("[GenerationHandler] generateText returned (fallback)", {
292
+ requestId,
293
+ durationMs: Date.now() - genStartTime,
294
+ finishReason: result.finishReason,
295
+ steps: result.steps?.length || 1,
296
+ toolCallsTotal: result.toolCalls?.length || 0,
297
+ responseChars: result.text?.length || 0,
298
+ });
299
+ if (result.usage) {
300
+ span.setAttribute("gen_ai.usage.input_tokens", result.usage.promptTokens || 0);
301
+ span.setAttribute("gen_ai.usage.output_tokens", result.usage.completionTokens || 0);
302
+ const fallbackCost = calculateCost(this.providerName, this.modelName, {
303
+ input: result.usage.promptTokens || 0,
304
+ output: result.usage.completionTokens || 0,
305
+ total: (result.usage.promptTokens || 0) +
306
+ (result.usage.completionTokens || 0),
307
+ });
308
+ span.setAttribute("neurolink.cost", fallbackCost ?? 0);
309
+ }
310
+ if (result.finishReason) {
311
+ span.setAttribute("gen_ai.response.finish_reason", result.finishReason);
312
+ }
313
+ span.setStatus({ code: SpanStatusCode.OK });
314
+ return result;
315
+ }
316
+ span.setStatus({
317
+ code: SpanStatusCode.ERROR,
318
+ message: error instanceof Error ? error.message : String(error),
319
+ });
320
+ // Re-throw other errors
321
+ throw error;
322
+ }
323
+ finally {
324
+ span.end();
325
+ }
326
+ });
270
327
  }
271
328
  /**
272
329
  * Extract cache metrics from provider metadata (e.g. Anthropic's providerMetadata.anthropic)
@@ -297,21 +354,23 @@ export class GenerationHandler {
297
354
  */
298
355
  logGenerationComplete(generateResult) {
299
356
  const cacheMetrics = this.extractCacheMetricsFromProviderMetadata(generateResult);
300
- logger.debug(`generateText completed`, {
301
- provider: this.providerName,
302
- model: this.modelName,
303
- responseLength: generateResult.text?.length || 0,
304
- toolResultsCount: generateResult.toolResults?.length || 0,
305
- finishReason: generateResult.finishReason,
306
- usage: generateResult.usage,
307
- ...(cacheMetrics.cacheCreationTokens !== undefined && {
308
- cacheCreationTokens: cacheMetrics.cacheCreationTokens,
309
- }),
310
- ...(cacheMetrics.cacheReadTokens !== undefined && {
311
- cacheReadTokens: cacheMetrics.cacheReadTokens,
312
- }),
313
- timestamp: Date.now(),
314
- });
357
+ if (logger.shouldLog("debug")) {
358
+ logger.debug(`generateText completed`, {
359
+ provider: this.providerName,
360
+ model: this.modelName,
361
+ responseLength: generateResult.text?.length || 0,
362
+ toolResultsCount: generateResult.toolResults?.length || 0,
363
+ finishReason: generateResult.finishReason,
364
+ usage: generateResult.usage,
365
+ ...(cacheMetrics.cacheCreationTokens !== undefined && {
366
+ cacheCreationTokens: cacheMetrics.cacheCreationTokens,
367
+ }),
368
+ ...(cacheMetrics.cacheReadTokens !== undefined && {
369
+ cacheReadTokens: cacheMetrics.cacheReadTokens,
370
+ }),
371
+ timestamp: Date.now(),
372
+ });
373
+ }
315
374
  }
316
375
  /**
317
376
  * Extract tool information from generation result