@juspay/neurolink 9.56.2 → 9.57.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/auth/AuthProviderFactory.d.ts +3 -3
  3. package/dist/auth/providers/BaseAuthProvider.d.ts +2 -2
  4. package/dist/auth/providers/BaseAuthProvider.js +1 -1
  5. package/dist/auth/serverBridge.d.ts +2 -2
  6. package/dist/browser/neurolink.min.js +285 -285
  7. package/dist/cli/factories/commandFactory.js +32 -8
  8. package/dist/cli/loop/optionsSchema.js +4 -0
  9. package/dist/cli/parser.js +3 -3
  10. package/dist/constants/enums.d.ts +8 -1
  11. package/dist/constants/enums.js +7 -0
  12. package/dist/dynamic/dynamicResolver.d.ts +282 -0
  13. package/dist/dynamic/dynamicResolver.js +633 -0
  14. package/dist/dynamic/index.d.ts +10 -0
  15. package/dist/dynamic/index.js +12 -0
  16. package/dist/dynamic/resolution.d.ts +17 -0
  17. package/dist/dynamic/resolution.js +21 -0
  18. package/dist/evaluation/index.js +1 -1
  19. package/dist/index.js +19 -2
  20. package/dist/lib/auth/AuthProviderFactory.d.ts +3 -3
  21. package/dist/lib/auth/providers/BaseAuthProvider.d.ts +2 -2
  22. package/dist/lib/auth/providers/BaseAuthProvider.js +1 -1
  23. package/dist/lib/auth/serverBridge.d.ts +2 -2
  24. package/dist/lib/constants/enums.d.ts +8 -1
  25. package/dist/lib/constants/enums.js +7 -0
  26. package/dist/lib/dynamic/dynamicResolver.d.ts +282 -0
  27. package/dist/lib/dynamic/dynamicResolver.js +634 -0
  28. package/dist/lib/dynamic/index.d.ts +10 -0
  29. package/dist/lib/dynamic/index.js +13 -0
  30. package/dist/lib/dynamic/resolution.d.ts +17 -0
  31. package/dist/lib/dynamic/resolution.js +22 -0
  32. package/dist/lib/evaluation/index.js +1 -1
  33. package/dist/lib/index.js +19 -2
  34. package/dist/lib/mcp/mcpServerBase.d.ts +1 -1
  35. package/dist/lib/mcp/mcpServerBase.js +1 -1
  36. package/dist/lib/neurolink.d.ts +18 -6
  37. package/dist/lib/neurolink.js +187 -42
  38. package/dist/lib/observability/exporters/baseExporter.d.ts +1 -1
  39. package/dist/lib/observability/exporters/baseExporter.js +1 -1
  40. package/dist/lib/types/auth.d.ts +6 -6
  41. package/dist/lib/types/config.d.ts +4 -4
  42. package/dist/lib/types/dynamic.d.ts +98 -0
  43. package/dist/lib/types/dynamic.js +10 -0
  44. package/dist/lib/types/generate.d.ts +29 -0
  45. package/dist/lib/types/index.d.ts +1 -0
  46. package/dist/lib/types/index.js +2 -0
  47. package/dist/lib/types/scorer.d.ts +1 -1
  48. package/dist/lib/types/scorer.js +1 -1
  49. package/dist/lib/types/span.d.ts +1 -1
  50. package/dist/lib/types/span.js +1 -1
  51. package/dist/lib/types/stream.d.ts +6 -0
  52. package/dist/lib/utils/conversationMemory.d.ts +10 -0
  53. package/dist/lib/utils/conversationMemory.js +185 -1
  54. package/dist/lib/utils/errorHandling.d.ts +13 -0
  55. package/dist/lib/utils/errorHandling.js +31 -0
  56. package/dist/mcp/mcpServerBase.d.ts +1 -1
  57. package/dist/mcp/mcpServerBase.js +1 -1
  58. package/dist/neurolink.d.ts +18 -6
  59. package/dist/neurolink.js +187 -42
  60. package/dist/observability/exporters/baseExporter.d.ts +1 -1
  61. package/dist/observability/exporters/baseExporter.js +1 -1
  62. package/dist/types/auth.d.ts +6 -6
  63. package/dist/types/config.d.ts +4 -4
  64. package/dist/types/dynamic.d.ts +98 -0
  65. package/dist/types/dynamic.js +9 -0
  66. package/dist/types/generate.d.ts +29 -0
  67. package/dist/types/index.d.ts +1 -0
  68. package/dist/types/index.js +2 -0
  69. package/dist/types/scorer.d.ts +1 -1
  70. package/dist/types/scorer.js +1 -1
  71. package/dist/types/span.d.ts +1 -1
  72. package/dist/types/span.js +1 -1
  73. package/dist/types/stream.d.ts +6 -0
  74. package/dist/utils/conversationMemory.d.ts +10 -0
  75. package/dist/utils/conversationMemory.js +185 -1
  76. package/dist/utils/errorHandling.d.ts +13 -0
  77. package/dist/utils/errorHandling.js +31 -0
  78. package/package.json +2 -1
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Dynamic Arguments Type Definitions
3
+ *
4
+ * Pass functions instead of static values to generate() and stream().
5
+ * Functions are resolved at runtime before provider dispatch.
6
+ *
7
+ * @module types/dynamic
8
+ */
9
+ import type { AIProviderName } from "../constants/enums.js";
10
+ /**
11
+ * Context passed to context-aware dynamic argument functions.
12
+ * `requestContext` is whatever the consumer passed as `dynamicContext` —
13
+ * NeuroLink does not prescribe its shape.
14
+ */
15
+ export type DynamicResolutionContext = {
16
+ /** Consumer-provided context (any shape) */
17
+ requestContext: Record<string, unknown>;
18
+ /** Abort signal for cancellation */
19
+ signal?: AbortSignal;
20
+ };
21
+ /**
22
+ * A value that can be static, a function, or a context-aware function.
23
+ *
24
+ * @example
25
+ * ```typescript
26
+ * // Static
27
+ * model: "gpt-4o"
28
+ *
29
+ * // Function
30
+ * model: () => process.env.MODEL || "gpt-4o"
31
+ *
32
+ * // Context-aware
33
+ * model: (ctx) => ctx.requestContext.plan === "enterprise" ? "gpt-4o" : "gpt-4o-mini"
34
+ * ```
35
+ */
36
+ export type DynamicArgument<T> = T | (() => T) | (() => Promise<T>) | ((context: DynamicResolutionContext) => T) | ((context: DynamicResolutionContext) => Promise<T>);
37
+ /**
38
+ * Dynamic options for generate() and stream() — pass functions
39
+ * instead of static values for context-aware resolution.
40
+ */
41
+ export type DynamicOptions = {
42
+ model?: DynamicArgument<string>;
43
+ provider?: DynamicArgument<AIProviderName | string>;
44
+ temperature?: DynamicArgument<number>;
45
+ maxTokens?: DynamicArgument<number>;
46
+ systemPrompt?: DynamicArgument<string>;
47
+ /**
48
+ * Resolves to a `string[]` of tool names to enable.
49
+ * The resolved array is merged into `enabledToolNames` (and from there
50
+ * into `toolFilter`) — it does NOT replace `GenerateOptions.tools`,
51
+ * which is a `Record<string, Tool>` map of tool definitions.
52
+ */
53
+ tools?: DynamicArgument<string[]>;
54
+ timeout?: DynamicArgument<number>;
55
+ thinkingLevel?: DynamicArgument<"minimal" | "low" | "medium" | "high">;
56
+ disableTools?: DynamicArgument<boolean>;
57
+ enableAnalytics?: DynamicArgument<boolean>;
58
+ enableEvaluation?: DynamicArgument<boolean>;
59
+ input: {
60
+ text: string;
61
+ images?: Array<Buffer | string>;
62
+ files?: Array<Buffer | string>;
63
+ };
64
+ /**
65
+ * Context passed to dynamic resolver functions — any shape you want.
66
+ *
67
+ * This is intentionally separate from `GenerateOptions.context` (which is
68
+ * for telemetry/tracing metadata). If your resolvers need values from
69
+ * telemetry context (sessionId, userId, etc.), pass them here as well.
70
+ */
71
+ dynamicContext?: Record<string, unknown>;
72
+ };
73
+ export type ResolutionOptions = {
74
+ timeout?: number;
75
+ cache?: boolean;
76
+ cacheKey?: string;
77
+ cacheTtl?: number;
78
+ defaultValue?: unknown;
79
+ throwOnError?: boolean;
80
+ };
81
+ export type ResolutionResult<T> = {
82
+ value: T;
83
+ fromCache: boolean;
84
+ resolutionTime: number;
85
+ resolutionType: "static" | "sync-function" | "async-function" | "context-aware";
86
+ };
87
+ export type DynamicConfig<T> = {
88
+ [K in keyof T]: DynamicArgument<T[K]>;
89
+ };
90
+ export type ResolvedConfig<T> = {
91
+ [K in keyof T]: T[K] extends DynamicArgument<infer U> ? U : T[K];
92
+ };
93
+ export type DynamicCacheEntry<T> = {
94
+ value: T;
95
+ resolvedAt: number;
96
+ expiresAt: number;
97
+ key: string;
98
+ };
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Dynamic Arguments Type Definitions
3
+ *
4
+ * Pass functions instead of static values to generate() and stream().
5
+ * Functions are resolved at runtime before provider dispatch.
6
+ *
7
+ * @module types/dynamic
8
+ */
9
+ export {};
10
+ //# sourceMappingURL=dynamic.js.map
@@ -230,6 +230,20 @@ export type GenerateOptions = {
230
230
  */
231
231
  schema?: ValidationSchema;
232
232
  tools?: Record<string, Tool>;
233
+ /**
234
+ * Filter available tools by name.
235
+ * Only tools with names in this array will be made available.
236
+ * Used by dynamic arguments to dynamically select which tools to enable.
237
+ *
238
+ * @example
239
+ * ```typescript
240
+ * await neurolink.generate({
241
+ * input: { text: "Search for information" },
242
+ * enabledToolNames: ["websearchGrounding", "readFile"]
243
+ * });
244
+ * ```
245
+ */
246
+ enabledToolNames?: string[];
233
247
  timeout?: number | string;
234
248
  /** AbortSignal for external cancellation of the AI call */
235
249
  abortSignal?: AbortSignal;
@@ -738,6 +752,21 @@ export type TextGenerationOptions = {
738
752
  director?: DirectorModeOptions;
739
753
  };
740
754
  tools?: Record<string, Tool>;
755
+ /**
756
+ * Filter available tools by name.
757
+ * Only tools with names in this array will be made available.
758
+ * Used by dynamic arguments to dynamically select which tools to enable.
759
+ * Merged into `toolFilter` before tool filtering runs.
760
+ *
761
+ * @example
762
+ * ```typescript
763
+ * await neurolink.generate({
764
+ * input: { text: "Search for information" },
765
+ * enabledToolNames: ["websearchGrounding", "readFile"]
766
+ * });
767
+ * ```
768
+ */
769
+ enabledToolNames?: string[];
741
770
  timeout?: number | string;
742
771
  /** AbortSignal for external cancellation of the AI call */
743
772
  abortSignal?: AbortSignal;
@@ -56,3 +56,4 @@ export * from "./exporter.js";
56
56
  export * from "./span.js";
57
57
  export * from "./imageGen.js";
58
58
  export * from "./elicitation.js";
59
+ export * from "./dynamic.js";
@@ -58,4 +58,6 @@ export * from "./exporter.js";
58
58
  export * from "./span.js";
59
59
  export * from "./imageGen.js";
60
60
  export * from "./elicitation.js";
61
+ // Dynamic Arguments types
62
+ export * from "./dynamic.js";
61
63
  //# sourceMappingURL=index.js.map
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @file Scorer type definitions for NeuroLink evaluation system
3
- * Mastra-style modular scorer interfaces and types
3
+ * Modular scorer interfaces and types
4
4
  */
5
5
  import type { JsonObject } from "./common.js";
6
6
  import type { EnhancedEvaluationContext } from "./evaluation.js";
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @file Scorer type definitions for NeuroLink evaluation system
3
- * Mastra-style modular scorer interfaces and types
3
+ * Modular scorer interfaces and types
4
4
  */
5
5
  export {};
6
6
  //# sourceMappingURL=scorer.js.map
@@ -4,7 +4,7 @@
4
4
  */
5
5
  /**
6
6
  * Span types for AI operations
7
- * Following Mastra's span categorization and OTel GenAI conventions
7
+ * Following OTel GenAI conventions for span categorization
8
8
  */
9
9
  export declare enum SpanType {
10
10
  /** Agent execution run (reserved for future multi-agent support) */
@@ -4,7 +4,7 @@
4
4
  */
5
5
  /**
6
6
  * Span types for AI operations
7
- * Following Mastra's span categorization and OTel GenAI conventions
7
+ * Following OTel GenAI conventions for span categorization
8
8
  */
9
9
  export var SpanType;
10
10
  (function (SpanType) {
@@ -330,6 +330,12 @@ export type StreamOptions = {
330
330
  } | undefined>;
331
331
  /** Include only these tools by name (whitelist). If set, only matching tools are available. */
332
332
  toolFilter?: string[];
333
+ /**
334
+ * Filter available tools by name.
335
+ * Used by dynamic arguments to dynamically select which tools to enable.
336
+ * Merged into `toolFilter` before tool filtering runs.
337
+ */
338
+ enabledToolNames?: string[];
333
339
  /** Exclude these tools by name (blacklist). Applied after toolFilter. */
334
340
  excludeTools?: string[];
335
341
  /** Disable tool result caching for this request (overrides global mcp.cache.enabled) */
@@ -5,6 +5,16 @@
5
5
  import type { ConversationMemoryManager } from "../core/conversationMemoryManager.js";
6
6
  import type { RedisConversationMemoryManager } from "../core/redisConversationMemoryManager.js";
7
7
  import type { ChatMessage, ConversationMemoryConfig, SessionMemory, TextGenerationOptions, TextGenerationResult } from "../types/index.js";
8
+ /**
9
+ * Legacy sentinel string formerly written by the abort branch of
10
+ * handleGenerateTextInternalFailure (Curator SI-069 / SI-071). The producer is
11
+ * removed in this fix, but historical Redis sessions may still contain entries
12
+ * with this content. Filtered at the prompt-builder boundary so they never
13
+ * reach the provider — sessions self-heal on the next read without any
14
+ * migration. Keep in sync with any future renames; do not remove without a
15
+ * cross-repo grep.
16
+ */
17
+ export declare const ABORT_LEGACY_SENTINEL = "[generation was interrupted]";
8
18
  /**
9
19
  * Apply conversation memory defaults to user configuration
10
20
  * Merges user config with environment variables and default values
@@ -10,6 +10,85 @@ import { getAvailableInputTokens } from "../constants/contextWindows.js";
10
10
  import { buildSummarizationPrompt } from "../context/prompts/summarizationPrompt.js";
11
11
  import { logger } from "./logger.js";
12
12
  const memoryTracer = tracers.memory;
13
+ /**
14
+ * Legacy sentinel string formerly written by the abort branch of
15
+ * handleGenerateTextInternalFailure (Curator SI-069 / SI-071). The producer is
16
+ * removed in this fix, but historical Redis sessions may still contain entries
17
+ * with this content. Filtered at the prompt-builder boundary so they never
18
+ * reach the provider — sessions self-heal on the next read without any
19
+ * migration. Keep in sync with any future renames; do not remove without a
20
+ * cross-repo grep.
21
+ */
22
+ export const ABORT_LEGACY_SENTINEL = "[generation was interrupted]";
23
+ /**
24
+ * Tracks session IDs that have already emitted the
25
+ * "Dropped polluted assistant turns" warn log so we log once per session
26
+ * (not on every retrieval). The span attribute
27
+ * `neurolink.memory.polluted_turns_dropped` is still set every call, so
28
+ * Langfuse traces show the cleanup happening continuously even after the
29
+ * log is suppressed. Bounded to avoid unbounded growth on busy services —
30
+ * when capacity is reached the set is cleared (cheap) and warning resumes
31
+ * as if those sessions are new, which is acceptable behaviour.
32
+ */
33
+ const POLLUTED_WARN_DEDUP_MAX = 1024;
34
+ const pollutedWarnedSessions = new Set();
35
+ /**
36
+ * True if a stored assistant turn looks like it was carrying tool activity
37
+ * (and is therefore safe to keep even with empty text content). storeTurn
38
+ * paths historically populate one of several fields depending on which
39
+ * provider/codepath wrote it, so this checks all of them. Mirrored across
40
+ * read filter + storage guard for symmetry.
41
+ *
42
+ * - `msg.events` — stream-path event sequence (`tool:start`, `tool:end`)
43
+ * - `msg.tool` / `msg.args` — assistant turn that invoked a tool by name
44
+ * - `msg.result` — tool result attached to the assistant turn
45
+ *
46
+ * If none of these are set, the assistant turn is text-only.
47
+ *
48
+ * Named with the `message` prefix to avoid shadowing the local
49
+ * `hasToolActivity` boolean inside `storeConversationTurn` below — the two
50
+ * answer different questions (one inspects a stored message, the other
51
+ * inspects a live result object).
52
+ */
53
+ function messageHasToolActivity(msg) {
54
+ if (msg.tool || msg.args || msg.result) {
55
+ return true;
56
+ }
57
+ const events = msg.events;
58
+ if (!Array.isArray(events)) {
59
+ return false;
60
+ }
61
+ return events.some((e) => {
62
+ const type = e?.type;
63
+ return type === "tool:start" || type === "tool:end";
64
+ });
65
+ }
66
+ /**
67
+ * Decides whether an assistant turn loaded from conversation memory is safe to
68
+ * include in the prompt sent to the provider. Drops:
69
+ * - empty / whitespace-only text content with no tool activity
70
+ * - the legacy abort sentinel — but only when the turn carries no tool
71
+ * activity, mirroring the storeConversationTurn upper-layer guard so a
72
+ * hypothetical tool-call-then-aborted turn doesn't lose its tool half
73
+ * tool_call and tool_result role messages are always preserved — they
74
+ * legitimately carry empty `content` (see redisConversationMemoryManager.ts:1870
75
+ * "Can be empty for tool calls"). Filtering them would break tool-pair
76
+ * semantics that downstream `repairToolPairs` relies on.
77
+ */
78
+ function isPollutedAssistantTurn(msg) {
79
+ if (msg.role !== "assistant") {
80
+ return false;
81
+ }
82
+ const content = typeof msg.content === "string" ? msg.content : "";
83
+ const trimmed = content.trim();
84
+ if (trimmed === ABORT_LEGACY_SENTINEL) {
85
+ return !messageHasToolActivity(msg);
86
+ }
87
+ if (trimmed === "") {
88
+ return !messageHasToolActivity(msg);
89
+ }
90
+ return false;
91
+ }
13
92
  // Cached NeuroLink instance for summarization to avoid creating a new instance per call
14
93
  let cachedSummarizer = null;
15
94
  /**
@@ -66,12 +145,49 @@ export async function getConversationMessages(conversationMemory, options) {
66
145
  span.setAttribute("user.id", userId);
67
146
  }
68
147
  const enableSummarization = options.enableSummarization ?? undefined;
69
- const messages = await conversationMemory.buildContextMessages(sessionId, userId, enableSummarization);
148
+ const rawMessages = await conversationMemory.buildContextMessages(sessionId, userId, enableSummarization);
149
+ // Read-time filter: drop assistant turns that are empty/whitespace or
150
+ // carry the legacy abort sentinel before they reach the provider.
151
+ // Self-heals historical Redis sessions polluted by the now-removed
152
+ // abort-path memory write (Curator SI-069 / SI-071) and defends
153
+ // against any future "fabricate-on-error" regression. Telemetry
154
+ // attributes record how many turns were dropped so polluted sessions
155
+ // are visible in Langfuse traces.
156
+ const messages = rawMessages.filter((msg) => !isPollutedAssistantTurn(msg));
157
+ const droppedCount = rawMessages.length - messages.length;
158
+ if (droppedCount > 0) {
159
+ // Span attribute is always set so polluted sessions stay visible in
160
+ // Langfuse traces on every read — that's the persistent debugging
161
+ // signal. The warn log is deduped per session so a long-lived
162
+ // polluted conversation only generates one log line, not one per
163
+ // turn (would otherwise be noisy at scale).
164
+ span.setAttribute("neurolink.memory.polluted_turns_dropped", droppedCount);
165
+ const alreadyWarned = pollutedWarnedSessions.has(sessionId);
166
+ if (!alreadyWarned) {
167
+ if (pollutedWarnedSessions.size >= POLLUTED_WARN_DEDUP_MAX) {
168
+ pollutedWarnedSessions.clear();
169
+ }
170
+ pollutedWarnedSessions.add(sessionId);
171
+ logger.warn("[conversationMemoryUtils] Dropped polluted assistant turns from prompt context (logged once per session — span attribute records every read)", {
172
+ sessionId,
173
+ droppedCount,
174
+ remainingCount: messages.length,
175
+ });
176
+ }
177
+ else {
178
+ logger.debug("[conversationMemoryUtils] Dropped polluted assistant turns (warn already logged for this session)", {
179
+ sessionId,
180
+ droppedCount,
181
+ remainingCount: messages.length,
182
+ });
183
+ }
184
+ }
70
185
  span.setAttribute("message.count", messages.length);
71
186
  if (logger.shouldLog("debug")) {
72
187
  logger.debug("[conversationMemoryUtils] Conversation messages retrieved successfully", {
73
188
  sessionId,
74
189
  messageCount: messages.length,
190
+ droppedPollutedCount: droppedCount,
75
191
  messageTypes: messages.map((m) => m.role),
76
192
  });
77
193
  }
@@ -147,6 +263,19 @@ export async function storeConversationTurn(conversationMemory, originalOptions,
147
263
  });
148
264
  return;
149
265
  }
266
+ // Belt-and-braces guard against the abort sentinel (Curator SI-069 / SI-071).
267
+ // The abort path itself was fixed in handleGenerateTextInternalFailure to
268
+ // never call this function, but we reject the legacy sentinel here too so a
269
+ // future regression cannot re-introduce the same pollution. Tool-bearing
270
+ // turns are explicitly preserved (the model may call a tool then abort).
271
+ if (aiResponse.trim() === ABORT_LEGACY_SENTINEL && !hasToolActivity) {
272
+ logger.warn("[conversationMemoryUtils] Refusing to store legacy abort sentinel — see Curator SI-069 / SI-071", {
273
+ sessionId,
274
+ userId,
275
+ userMessageLength: userMessage.length,
276
+ });
277
+ return;
278
+ }
150
279
  let providerDetails;
151
280
  if (result.provider && result.model) {
152
281
  providerDetails = {
@@ -154,6 +283,60 @@ export async function storeConversationTurn(conversationMemory, originalOptions,
154
283
  model: result.model,
155
284
  };
156
285
  }
286
+ // Persist a minimal `events` marker only on tool-bearing assistant turns
287
+ // whose surface text would otherwise trigger the read-time filter (empty /
288
+ // whitespace-only content). Turns that already have substantive text are
289
+ // never dropped by isPollutedAssistantTurn, so attaching synthesised events
290
+ // to them would change the stored shape and token estimation for no
291
+ // benefit. Sentinel-content turns never reach this point — the upper-layer
292
+ // guard at line 340 short-circuits them.
293
+ let toolActivityEvents;
294
+ if (hasToolActivity && !aiResponse.trim()) {
295
+ const now = Date.now();
296
+ const usedNames = new Set();
297
+ if (Array.isArray(result.toolsUsed)) {
298
+ for (const t of result.toolsUsed) {
299
+ if (typeof t === "string" && t) {
300
+ usedNames.add(t);
301
+ }
302
+ }
303
+ }
304
+ if (Array.isArray(result.toolExecutions)) {
305
+ for (const exec of result.toolExecutions) {
306
+ const name = exec?.toolName;
307
+ if (typeof name === "string" && name) {
308
+ usedNames.add(name);
309
+ }
310
+ }
311
+ }
312
+ toolActivityEvents = [];
313
+ let seq = 0;
314
+ for (const name of usedNames) {
315
+ // Match the canonical ToolExecutionEvent shape (src/lib/types/tools.ts):
316
+ // `tool` is the required field, `toolName` is the documented compat
317
+ // alias. Populate both so downstream consumers reading either name
318
+ // work uniformly.
319
+ toolActivityEvents.push({
320
+ type: "tool:start",
321
+ seq: seq++,
322
+ timestamp: now,
323
+ tool: name,
324
+ toolName: name,
325
+ });
326
+ }
327
+ if (toolActivityEvents.length === 0) {
328
+ // Tool activity reported but no names extractable — still leave a
329
+ // marker so retrieval doesn't drop the turn. Both `tool` and
330
+ // `toolName` are populated for the same compat reason.
331
+ toolActivityEvents.push({
332
+ type: "tool:start",
333
+ seq: 0,
334
+ timestamp: now,
335
+ tool: "unknown",
336
+ toolName: "unknown",
337
+ });
338
+ }
339
+ }
157
340
  await memoryTracer.startActiveSpan("neurolink.conversation.storeTurn", {
158
341
  kind: SpanKind.INTERNAL,
159
342
  attributes: {
@@ -174,6 +357,7 @@ export async function storeConversationTurn(conversationMemory, originalOptions,
174
357
  providerDetails,
175
358
  enableSummarization: originalOptions.enableSummarization,
176
359
  requestId,
360
+ events: toolActivityEvents,
177
361
  tokenUsage: result.usage
178
362
  ? {
179
363
  inputTokens: result.usage.input,
@@ -17,6 +17,7 @@ export declare const ERROR_CODES: {
17
17
  readonly PROVIDER_NOT_AVAILABLE: "PROVIDER_NOT_AVAILABLE";
18
18
  readonly PROVIDER_AUTH_FAILED: "PROVIDER_AUTH_FAILED";
19
19
  readonly PROVIDER_QUOTA_EXCEEDED: "PROVIDER_QUOTA_EXCEEDED";
20
+ readonly OPERATION_ABORTED: "OPERATION_ABORTED";
20
21
  readonly INVALID_CONFIGURATION: "INVALID_CONFIGURATION";
21
22
  readonly MISSING_CONFIGURATION: "MISSING_CONFIGURATION";
22
23
  readonly INVALID_VIDEO_RESOLUTION: "INVALID_VIDEO_RESOLUTION";
@@ -106,6 +107,18 @@ export declare class ErrorFactory {
106
107
  * Create a memory exhaustion error
107
108
  */
108
109
  static memoryExhausted(toolName: string, memoryUsageMB: number): NeuroLinkError;
110
+ /**
111
+ * Create a typed abort error preserving the originating exception. Callers
112
+ * can switch on `error.category === ErrorCategory.ABORT` and
113
+ * `error.code === ERROR_CODES.OPERATION_ABORTED` instead of message-string
114
+ * matching DOMException / AI SDK error wrappers.
115
+ *
116
+ * `error.name` is intentionally set to "AbortError" (overriding the default
117
+ * "NeuroLinkError") so existing callers that branch on
118
+ * `err.name === "AbortError"` keep working without code changes — the new
119
+ * structured fields (category, code, retriable) are additive.
120
+ */
121
+ static aborted(originalError?: Error): NeuroLinkError;
109
122
  /**
110
123
  * Create a missing configuration error (e.g., missing API key)
111
124
  */
@@ -23,6 +23,8 @@ export const ERROR_CODES = {
23
23
  PROVIDER_NOT_AVAILABLE: "PROVIDER_NOT_AVAILABLE",
24
24
  PROVIDER_AUTH_FAILED: "PROVIDER_AUTH_FAILED",
25
25
  PROVIDER_QUOTA_EXCEEDED: "PROVIDER_QUOTA_EXCEEDED",
26
+ // Cancellation
27
+ OPERATION_ABORTED: "OPERATION_ABORTED",
26
28
  // Configuration errors
27
29
  INVALID_CONFIGURATION: "INVALID_CONFIGURATION",
28
30
  MISSING_CONFIGURATION: "MISSING_CONFIGURATION",
@@ -201,6 +203,30 @@ export class ErrorFactory {
201
203
  toolName,
202
204
  });
203
205
  }
206
+ /**
207
+ * Create a typed abort error preserving the originating exception. Callers
208
+ * can switch on `error.category === ErrorCategory.ABORT` and
209
+ * `error.code === ERROR_CODES.OPERATION_ABORTED` instead of message-string
210
+ * matching DOMException / AI SDK error wrappers.
211
+ *
212
+ * `error.name` is intentionally set to "AbortError" (overriding the default
213
+ * "NeuroLinkError") so existing callers that branch on
214
+ * `err.name === "AbortError"` keep working without code changes — the new
215
+ * structured fields (category, code, retriable) are additive.
216
+ */
217
+ static aborted(originalError) {
218
+ const err = new NeuroLinkError({
219
+ code: ERROR_CODES.OPERATION_ABORTED,
220
+ message: originalError?.message || "The operation was aborted",
221
+ category: ErrorCategory.ABORT,
222
+ severity: ErrorSeverity.LOW,
223
+ retriable: false,
224
+ context: {},
225
+ originalError,
226
+ });
227
+ err.name = "AbortError";
228
+ return err;
229
+ }
204
230
  // ============================================================================
205
231
  // CONFIGURATION ERRORS
206
232
  // ============================================================================
@@ -904,6 +930,11 @@ export function isAbortError(error) {
904
930
  if (error instanceof Error && error.name === "AbortError") {
905
931
  return true;
906
932
  }
933
+ // Typed NeuroLinkError abort - canonical from-now-on shape.
934
+ if (error instanceof NeuroLinkError &&
935
+ error.category === ErrorCategory.ABORT) {
936
+ return true;
937
+ }
907
938
  if (error instanceof Error &&
908
939
  (error.message?.includes("This operation was aborted") ||
909
940
  error.message?.includes("The operation was aborted") ||
@@ -4,7 +4,7 @@
4
4
  * Abstract base class for creating custom MCP servers with consistent patterns
5
5
  * for tool registration, execution, and lifecycle management.
6
6
  *
7
- * Implements Mastra-style MCPServerBase features including:
7
+ * Implements MCPServerBase features including:
8
8
  * - Tool annotation support (readOnlyHint, destructiveHint, idempotentHint)
9
9
  * - Lifecycle hooks (onInit, onStart, onStop)
10
10
  * - Event emission for tool operations
@@ -4,7 +4,7 @@
4
4
  * Abstract base class for creating custom MCP servers with consistent patterns
5
5
  * for tool registration, execution, and lifecycle management.
6
6
  *
7
- * Implements Mastra-style MCPServerBase features including:
7
+ * Implements MCPServerBase features including:
8
8
  * - Tool annotation support (readOnlyHint, destructiveHint, idempotentHint)
9
9
  * - Lifecycle hooks (onInit, onStart, onStop)
10
10
  * - Event emission for tool operations
@@ -5,11 +5,12 @@
5
5
  * Enhanced AI provider system with natural MCP tool access.
6
6
  * Uses real MCP infrastructure for tool discovery and execution.
7
7
  */
8
- import type { CompactionConfig, CompactionResult, SpanData, ObservabilityConfig, MetricsSummary, MCPToolAnnotations, TraceView, AuthenticatedContext, MastraAuthProvider, JsonObject, NeuroLinkEvents, TypedEventEmitter, MCPEnhancementsConfig, NeuroLinkAuthConfig, NeurolinkConstructorConfig, ChatMessage, ExternalMCPOperationResult, ExternalMCPServerInstance, ExternalMCPToolInfo, GenerateOptions, GenerateResult, ProviderStatus, TextGenerationOptions, TextGenerationResult, MCPExecutableTool, MCPServerInfo, MCPStatus, StreamOptions, StreamResult, ToolExecutionContext, ToolExecutionSummary, ToolInfo, ToolRegistrationOptions, BatchOperationResult } from "./types/index.js";
8
+ import type { CompactionConfig, CompactionResult, SpanData, ObservabilityConfig, MetricsSummary, MCPToolAnnotations, TraceView, AuthenticatedContext, AuthProvider, JsonObject, NeuroLinkEvents, TypedEventEmitter, MCPEnhancementsConfig, NeuroLinkAuthConfig, NeurolinkConstructorConfig, ChatMessage, ExternalMCPOperationResult, ExternalMCPServerInstance, ExternalMCPToolInfo, GenerateOptions, GenerateResult, ProviderStatus, TextGenerationOptions, TextGenerationResult, MCPExecutableTool, MCPServerInfo, MCPStatus, StreamOptions, StreamResult, ToolExecutionContext, ToolExecutionSummary, ToolInfo, ToolRegistrationOptions, BatchOperationResult } from "./types/index.js";
9
9
  import { ConversationMemoryManager } from "./core/conversationMemoryManager.js";
10
10
  import type { RedisConversationMemoryManager } from "./core/redisConversationMemoryManager.js";
11
11
  import { ExternalServerManager } from "./mcp/externalServerManager.js";
12
12
  import { MCPToolRegistry } from "./mcp/toolRegistry.js";
13
+ import type { DynamicOptions } from "./types/index.js";
13
14
  import { TaskManager } from "./tasks/taskManager.js";
14
15
  export declare class NeuroLink {
15
16
  private mcpInitialized;
@@ -539,7 +540,7 @@ export declare class NeuroLink {
539
540
  * @see {@link stream} for streaming generation
540
541
  * @since 1.0.0
541
542
  */
542
- generate(optionsOrPrompt: GenerateOptions | string): Promise<GenerateResult>;
543
+ generate(optionsOrPrompt: GenerateOptions | DynamicOptions | string): Promise<GenerateResult>;
543
544
  private executeGenerateWithMetricsContext;
544
545
  private executeGenerateRequest;
545
546
  private prepareGenerateRequest;
@@ -695,7 +696,7 @@ export declare class NeuroLink {
695
696
  * @throws {Error} When all providers fail to generate content
696
697
  * @throws {Error} When conversation memory operations fail (if enabled)
697
698
  */
698
- stream(options: StreamOptions): Promise<StreamResult>;
699
+ stream(options: StreamOptions | DynamicOptions): Promise<StreamResult>;
699
700
  private executeStreamRequest;
700
701
  private validateStreamRequestOptions;
701
702
  private maybeHandleWorkflowStreamRequest;
@@ -880,8 +881,12 @@ export declare class NeuroLink {
880
881
  * **Generation Events:**
881
882
  * - `generation:start` - Fired when text generation begins
882
883
  * - `{ provider: string, timestamp: number }`
883
- * - `generation:end` - Fired when text generation completes
884
- * - `{ provider: string, responseTime: number, toolsUsed?: string[], timestamp: number }`
884
+ * - `generation:end` - Fired when text generation completes (or fails / is aborted)
885
+ * - `{ provider: string, responseTime: number, toolsUsed?: string[], timestamp: number, success?: boolean, aborted?: boolean, error?: string }`
886
+ * - `success` is `false` for both failures and client aborts; `aborted: true`
887
+ * distinguishes the latter so consumers can route cancellations
888
+ * differently from real errors. Pipeline B's metrics span maps
889
+ * `aborted: true` events to `SpanStatus.WARNING` (not ERROR).
885
890
  *
886
891
  * **Streaming Events:**
887
892
  * - `stream:start` - Fired when streaming begins
@@ -1959,7 +1964,7 @@ export declare class NeuroLink {
1959
1964
  /**
1960
1965
  * Get the currently configured authentication provider
1961
1966
  */
1962
- getAuthProvider(): MastraAuthProvider | undefined;
1967
+ getAuthProvider(): AuthProvider | undefined;
1963
1968
  /**
1964
1969
  * Lazily initialize the auth provider from pendingAuthConfig.
1965
1970
  * Called on first use (generate/stream with auth token) to avoid
@@ -1993,6 +1998,13 @@ export declare class NeuroLink {
1993
1998
  * @returns The ExternalServerManager instance
1994
1999
  */
1995
2000
  getExternalServerManager(): ExternalServerManager;
2001
+ private buildResolutionContext;
2002
+ /**
2003
+ * Resolve dynamic arguments in GenerateOptions, mutating the options in place.
2004
+ * Only resolves fields that are functions; static values pass through unchanged.
2005
+ */
2006
+ private resolveDynamicOptions;
2007
+ private resolveDynamicFields;
1996
2008
  }
1997
2009
  export declare const neurolink: NeuroLink;
1998
2010
  export default neurolink;