@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
@@ -14,7 +14,7 @@ export * from "./pipeline/index.js";
14
14
  export * from "./reporting/index.js";
15
15
  // Re-export scorers
16
16
  export * from "./scorers/index.js";
17
- // Re-export Factory and Registry (Mastra-inspired patterns)
17
+ // Re-export Factory and Registry
18
18
  export { BatchEvaluator } from "./BatchEvaluator.js";
19
19
  export { EvaluationAggregator } from "./EvaluationAggregator.js";
20
20
  export { EvaluatorFactory, getEvaluatorFactory } from "./EvaluatorFactory.js";
package/dist/lib/index.js CHANGED
@@ -37,7 +37,7 @@ export { AIProviderFactory };
37
37
  export { GoogleTTSHandler } from "./adapters/tts/googleTTSHandler.js";
38
38
  // Config Manager export
39
39
  export { NeuroLinkConfigManager as ConfigManager } from "./config/configManager.js";
40
- // Core Infrastructure exports (Mastra-inspired patterns)
40
+ // Core Infrastructure exports (factory + registry patterns)
41
41
  export { BaseFactory, BaseRegistry, NeuroLinkFeatureError, createErrorFactory, withRetry, TypedEventEmitter, } from "./core/infrastructure/index.js";
42
42
  // ============================================================================
43
43
  // CLIENT SDK EXPORTS - Type-safe API access for browser and Node.js
@@ -107,6 +107,23 @@ export { SpanSerializer } from "./observability/utils/spanSerializer.js";
107
107
  // Middleware exports
108
108
  // Version
109
109
  export const VERSION = "1.0.0";
110
+ // ============================================================================
111
+ // Dynamic Arguments
112
+ // ============================================================================
113
+ //
114
+ // Dynamic arguments let you pass functions instead of static values to
115
+ // generate() and stream(). Resolution happens automatically before
116
+ // provider dispatch. Pass dynamicContext inline for per-request
117
+ // user/tenant/session context that dynamic functions can read.
118
+ //
119
+ // Example:
120
+ // await neurolink.generate({
121
+ // input: { text: "Hello" },
122
+ // model: (ctx) => ctx.requestContext.tenant?.plan === "enterprise"
123
+ // ? "gpt-4o" : "gpt-4o-mini",
124
+ // dynamicContext: { tenant: { id: "t1", plan: "enterprise" } },
125
+ // });
126
+ // ============================================================================
110
127
  /**
111
128
  * Quick start factory function for creating AI provider instances.
112
129
  *
@@ -348,7 +365,7 @@ export async function getTelemetryStatus() {
348
365
  export {
349
366
  // Main Evaluator
350
367
  Evaluator,
351
- // Factory and Registry (Mastra-inspired patterns)
368
+ // Factory and Registry
352
369
  EvaluationAggregator, EvaluatorFactory, getEvaluatorFactory, getEvaluatorRegistry,
353
370
  // Error utilities
354
371
  evaluationErrors, isRetryableEvaluationError, isEvaluationError, createEvaluationFailedError, createParseError, createStrategyNotFoundError, createProviderError, createMaxRetriesExceededError, createBatchEvaluationError, createConfigurationError, contextToErrorContext,
@@ -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;
@@ -47,6 +47,8 @@ import { ToolRouter } from "./mcp/routing/index.js";
47
47
  import { directToolsServer } from "./mcp/servers/agent/directToolsServer.js";
48
48
  import { inferAnnotations, isSafeToRetry } from "./mcp/toolAnnotations.js";
49
49
  import { MCPToolRegistry } from "./mcp/toolRegistry.js";
50
+ // Dynamic argument resolution imports
51
+ import { resolveDynamicArgument } from "./dynamic/dynamicResolver.js";
50
52
  import { initializeHippocampus } from "./memory/hippocampusInitializer.js";
51
53
  import { createMemoryRetrievalTools } from "./memory/memoryRetrievalTools.js";
52
54
  import { getMetricsAggregator, MetricsAggregator, } from "./observability/metricsAggregator.js";
@@ -2322,11 +2324,26 @@ Current user's request: ${currentInput}`;
2322
2324
  if (traceCtx) {
2323
2325
  span.parentSpanId = traceCtx.parentSpanId;
2324
2326
  }
2325
- // Mark failed generations with ERROR status so metrics count them correctly
2326
- const spanStatus = data.success === false || data.error
2327
- ? SpanStatus.ERROR
2328
- : SpanStatus.OK;
2329
- span = SpanSerializer.endSpan(span, spanStatus, data.error ? String(data.error) : undefined);
2327
+ // Mark failed generations with ERROR status so metrics count them
2328
+ // correctly. Client aborts (data.aborted === true) are NOT failures —
2329
+ // they are user-initiated cancellations and must not pollute the
2330
+ // failure rate. Map them to WARNING with the canonical
2331
+ // "Generation aborted by client" message (matches the Langfuse
2332
+ // ContextEnricher mapping for outer/internal generation spans).
2333
+ let spanStatus;
2334
+ let statusMessage;
2335
+ if (data.aborted === true) {
2336
+ spanStatus = SpanStatus.WARNING;
2337
+ statusMessage = "Generation aborted by client";
2338
+ }
2339
+ else if (data.success === false || data.error) {
2340
+ spanStatus = SpanStatus.ERROR;
2341
+ statusMessage = data.error ? String(data.error) : undefined;
2342
+ }
2343
+ else {
2344
+ spanStatus = SpanStatus.OK;
2345
+ }
2346
+ span = SpanSerializer.endSpan(span, spanStatus, statusMessage);
2330
2347
  span.durationMs = responseTime;
2331
2348
  // G2 fix: Check finishReason and escalate to WARNING for partial failures
2332
2349
  const finishReason = result?.finishReason ??
@@ -2658,8 +2675,10 @@ Current user's request: ${currentInput}`;
2658
2675
  return metricsTraceContextStorage.run(this.createMetricsTraceContext(), () => this.executeGenerateRequest(optionsOrPrompt, generateSpan));
2659
2676
  }
2660
2677
  async executeGenerateRequest(optionsOrPrompt, generateSpan) {
2678
+ let resolvedOptions;
2661
2679
  try {
2662
2680
  const { options, originalPrompt } = await this.prepareGenerateRequest(optionsOrPrompt, generateSpan);
2681
+ resolvedOptions = options;
2663
2682
  const earlyResult = await this.maybeHandleEarlyGenerateResult(options, generateSpan);
2664
2683
  if (earlyResult) {
2665
2684
  generateSpan.setStatus({ code: SpanStatusCode.OK });
@@ -2670,17 +2689,29 @@ Current user's request: ${currentInput}`;
2670
2689
  return result;
2671
2690
  }
2672
2691
  catch (error) {
2673
- generateSpan.setStatus({
2674
- code: SpanStatusCode.ERROR,
2675
- message: error instanceof Error ? error.message : String(error),
2676
- });
2692
+ // Match the inner-span discrimination: client aborts are user-initiated
2693
+ // cancellations, not faults. Mark with finishReason=aborted and skip
2694
+ // ERROR status so ContextEnricher routes the outer trace to
2695
+ // langfuse.level=WARNING (matches Curator telemetry-gaps Issue 5a). All
2696
+ // other errors keep the existing ERROR status + recordException pair.
2697
+ if (isAbortError(error)) {
2698
+ generateSpan.setAttribute("ai.finishReason", "aborted");
2699
+ generateSpan.setAttribute("neurolink.aborted", true);
2700
+ }
2701
+ else {
2702
+ generateSpan.recordException(error instanceof Error ? error : new Error(String(error)));
2703
+ generateSpan.setStatus({
2704
+ code: SpanStatusCode.ERROR,
2705
+ message: error instanceof Error ? error.message : String(error),
2706
+ });
2707
+ }
2677
2708
  // G7 fix: Distinguish context overflow errors with dedicated attributes
2678
2709
  if (error instanceof ContextBudgetExceededError) {
2679
2710
  generateSpan.setAttribute("neurolink.error.type", "context_overflow");
2680
2711
  generateSpan.setAttribute("neurolink.context.estimated_tokens", error.estimatedTokens);
2681
2712
  generateSpan.setAttribute("neurolink.context.available_tokens", error.availableTokens);
2682
2713
  }
2683
- this.emitGenerateErrorEvent(optionsOrPrompt, error);
2714
+ this.emitGenerateErrorEvent((resolvedOptions ?? optionsOrPrompt), error);
2684
2715
  throw error;
2685
2716
  }
2686
2717
  finally {
@@ -2693,6 +2724,8 @@ Current user's request: ${currentInput}`;
2693
2724
  const options = typeof optionsOrPrompt === "string"
2694
2725
  ? { input: { text: optionsOrPrompt } }
2695
2726
  : { ...optionsOrPrompt };
2727
+ // Dynamic argument resolution — resolve any function-valued options before downstream use
2728
+ await this.resolveDynamicOptions(options);
2696
2729
  options.model = resolveModel(options.model, this.modelAliasConfig);
2697
2730
  this._disableToolCacheForCurrentRequest = !!options.disableToolCache;
2698
2731
  generateSpan.setAttribute("neurolink.provider", options.provider || "default");
@@ -2839,6 +2872,7 @@ Current user's request: ${currentInput}`;
2839
2872
  maxSteps: options.maxSteps,
2840
2873
  toolChoice: options.toolChoice,
2841
2874
  prepareStep: options.prepareStep,
2875
+ enabledToolNames: options.enabledToolNames,
2842
2876
  enableAnalytics: options.enableAnalytics,
2843
2877
  enableEvaluation: options.enableEvaluation,
2844
2878
  context: options.context,
@@ -2965,6 +2999,11 @@ Current user's request: ${currentInput}`;
2965
2999
  const errModel = typeof optionsOrPrompt === "object"
2966
3000
  ? optionsOrPrompt.model || "unknown"
2967
3001
  : "unknown";
3002
+ // Distinguish client aborts from real failures so consumers (and Langfuse)
3003
+ // can route them differently. `aborted: true` is additive — `success`
3004
+ // remains false for backwards-compat with existing listeners that only
3005
+ // branch on the boolean.
3006
+ const aborted = isAbortError(error);
2968
3007
  try {
2969
3008
  this.emitter.emit("generation:end", {
2970
3009
  provider: errProvider,
@@ -2972,6 +3011,7 @@ Current user's request: ${currentInput}`;
2972
3011
  responseTime: 0,
2973
3012
  error: error instanceof Error ? error.message : String(error),
2974
3013
  success: false,
3014
+ aborted,
2975
3015
  });
2976
3016
  }
2977
3017
  catch (emitError) {
@@ -3319,10 +3359,23 @@ Current user's request: ${currentInput}`;
3319
3359
  return await this.runGenerateTextInternalFlow(options, internalSpan, context);
3320
3360
  }
3321
3361
  catch (error) {
3322
- internalSpan.setStatus({
3323
- code: SpanStatusCode.ERROR,
3324
- message: error instanceof Error ? error.message : String(error),
3325
- });
3362
+ // Client aborts are user-initiated cancellations, not system faults.
3363
+ // Setting status=ERROR forces Langfuse to level=ERROR (see
3364
+ // ContextEnricher.onEnd instrumentation.ts:691). Instead leave status
3365
+ // unset and stamp ai.finishReason=aborted so applyNonErrorLangfuseLevel
3366
+ // maps it to level=WARNING with the canonical "Generation aborted by
3367
+ // client" status_message. Matches Curator telemetry-gaps Issue 5a.
3368
+ if (isAbortError(error)) {
3369
+ internalSpan.setAttribute("ai.finishReason", "aborted");
3370
+ internalSpan.setAttribute("neurolink.aborted", true);
3371
+ }
3372
+ else {
3373
+ internalSpan.recordException(error instanceof Error ? error : new Error(String(error)));
3374
+ internalSpan.setStatus({
3375
+ code: SpanStatusCode.ERROR,
3376
+ message: error instanceof Error ? error.message : String(error),
3377
+ });
3378
+ }
3326
3379
  throw error;
3327
3380
  }
3328
3381
  finally {
@@ -3378,6 +3431,13 @@ Current user's request: ${currentInput}`;
3378
3431
  if (recoveredResult) {
3379
3432
  return recoveredResult;
3380
3433
  }
3434
+ // Convert raw DOMException AbortErrors (and other untyped abort shapes)
3435
+ // into NeuroLinkError(ABORT) so callers can branch on
3436
+ // `error.category === ErrorCategory.ABORT` instead of message matching.
3437
+ // Skipped if the error is already a typed abort to avoid double-wrap.
3438
+ if (isAbortError(error) && !(error instanceof NeuroLinkError)) {
3439
+ throw ErrorFactory.aborted(error instanceof Error ? error : new Error(String(error)));
3440
+ }
3381
3441
  throw error;
3382
3442
  }
3383
3443
  }
@@ -3435,28 +3495,24 @@ Current user's request: ${currentInput}`;
3435
3495
  return recoveredResult;
3436
3496
  }
3437
3497
  if (isAbortError(error)) {
3438
- logger.info(`[${context.functionTag}] Generation aborted storing conversation turn for title generation`, {
3498
+ // Aborted generations DO NOT write to conversation memory.
3499
+ // Fabricating an assistant turn out of an error condition (the previous
3500
+ // "[generation was interrupted]" sentinel) pollutes the next prompt and
3501
+ // — at the right shape — causes the model to echo the sentinel as its
3502
+ // response. See Curator SI-069 / SI-071. Aborts are signalled to
3503
+ // callers via the thrown error and the "error" emitter event below;
3504
+ // there is nothing to persist, so persisting nothing is correct.
3505
+ //
3506
+ // Title generation continues to work: it reads the user message of the
3507
+ // first *successful* turn (RedisConversationMemoryManager
3508
+ // .generateConversationTitle) and never required a fabricated assistant
3509
+ // turn — the previous comment claiming otherwise was inaccurate.
3510
+ logger.info(`[${context.functionTag}] Generation aborted — skipping memory write (aborts must not pollute conversation history)`, {
3439
3511
  hasMemory: !!this.conversationMemory,
3440
3512
  memoryType: this.conversationMemory?.constructor?.name || "NONE",
3441
3513
  sessionId: options.context?.sessionId ||
3442
3514
  "unknown",
3443
3515
  });
3444
- try {
3445
- const abortedResult = {
3446
- content: "[generation was interrupted]",
3447
- provider: options.provider || "unknown",
3448
- model: options.model || "unknown",
3449
- responseTime: Date.now() - context.generateInternalStartTime,
3450
- };
3451
- await withTimeout(storeConversationTurn(this.conversationMemory, options, abortedResult, new Date(context.generateInternalStartTime), context.requestId), 5000);
3452
- }
3453
- catch (storeError) {
3454
- logger.warn(`[${context.functionTag}] Failed to store conversation turn after abort`, {
3455
- error: storeError instanceof Error
3456
- ? storeError.message
3457
- : String(storeError),
3458
- });
3459
- }
3460
3516
  }
3461
3517
  else {
3462
3518
  logger.error(`[${context.functionTag}] All generation methods failed`, {
@@ -3464,7 +3520,14 @@ Current user's request: ${currentInput}`;
3464
3520
  });
3465
3521
  }
3466
3522
  this.emitter.emit("response:end", "");
3467
- this.emitter.emit("error", error instanceof Error ? error : new Error(String(error)));
3523
+ // Node EventEmitter rethrows the original error from emit("error", e) if
3524
+ // there is no listener registered, which would short-circuit the caller's
3525
+ // catch block and prevent the abort-typed-error wrap from running. Only
3526
+ // emit when a consumer is listening; non-listening callers receive the
3527
+ // error via the thrown rejection instead, which is the canonical path.
3528
+ if (this.emitter.listenerCount("error") > 0) {
3529
+ this.emitter.emit("error", error instanceof Error ? error : new Error(String(error)));
3530
+ }
3468
3531
  return null;
3469
3532
  }
3470
3533
  async tryRecoverGenerateTextOverflow(options, functionTag, error) {
@@ -4331,13 +4394,18 @@ Current user's request: ${currentInput}`;
4331
4394
  * Used to filter the tool list before building the system prompt.
4332
4395
  */
4333
4396
  applyToolInfoFiltering(tools, options) {
4334
- if ((!options.toolFilter || options.toolFilter.length === 0) &&
4397
+ // enabledToolNames is an additional whitelist — merged into toolFilter
4398
+ const whitelist = [
4399
+ ...(options.toolFilter ?? []),
4400
+ ...(options.enabledToolNames ?? []),
4401
+ ];
4402
+ if (whitelist.length === 0 &&
4335
4403
  (!options.excludeTools || options.excludeTools.length === 0)) {
4336
4404
  return tools;
4337
4405
  }
4338
4406
  let filtered = tools;
4339
- if (options.toolFilter && options.toolFilter.length > 0) {
4340
- const allowSet = new Set(options.toolFilter);
4407
+ if (whitelist.length > 0) {
4408
+ const allowSet = new Set(whitelist);
4341
4409
  filtered = filtered.filter((t) => allowSet.has(t.name));
4342
4410
  }
4343
4411
  if (options.excludeTools && options.excludeTools.length > 0) {
@@ -4493,12 +4561,16 @@ Current user's request: ${currentInput}`;
4493
4561
  disableTools: options.disableTools,
4494
4562
  enableAnalytics: options.enableAnalytics,
4495
4563
  enableEvaluation: options.enableEvaluation,
4496
- contextKeys: options.context ? Object.keys(options.context) : [],
4564
+ contextKeys: options.context
4565
+ ? Object.keys(options.context ?? {})
4566
+ : [],
4497
4567
  optionKeys: Object.keys(options),
4498
4568
  });
4499
4569
  return metricsTraceContextStorage.run(this.createMetricsTraceContext(), () => this.executeStreamRequest({ ...options }));
4500
4570
  }
4501
4571
  async executeStreamRequest(options) {
4572
+ // Dynamic argument resolution — resolve any function-valued options before downstream use
4573
+ await this.resolveDynamicOptions(options);
4502
4574
  const streamSpan = tracers.sdk.startSpan("neurolink.stream", {
4503
4575
  kind: SpanKind.INTERNAL,
4504
4576
  attributes: {
@@ -5685,8 +5757,12 @@ Current user's request: ${currentInput}`;
5685
5757
  * **Generation Events:**
5686
5758
  * - `generation:start` - Fired when text generation begins
5687
5759
  * - `{ provider: string, timestamp: number }`
5688
- * - `generation:end` - Fired when text generation completes
5689
- * - `{ provider: string, responseTime: number, toolsUsed?: string[], timestamp: number }`
5760
+ * - `generation:end` - Fired when text generation completes (or fails / is aborted)
5761
+ * - `{ provider: string, responseTime: number, toolsUsed?: string[], timestamp: number, success?: boolean, aborted?: boolean, error?: string }`
5762
+ * - `success` is `false` for both failures and client aborts; `aborted: true`
5763
+ * distinguishes the latter so consumers can route cancellations
5764
+ * differently from real errors. Pipeline B's metrics span maps
5765
+ * `aborted: true` events to `SpanStatus.WARNING` (not ERROR).
5690
5766
  *
5691
5767
  * **Streaming Events:**
5692
5768
  * - `stream:start` - Fired when streaming begins
@@ -6627,7 +6703,13 @@ Current user's request: ${currentInput}`;
6627
6703
  prepared.metrics.errorCategories[category] =
6628
6704
  (prepared.metrics.errorCategories[category] || 0) + 1;
6629
6705
  this.emitToolEndEvent(toolName, executionContext.executionStartTime, false, undefined, structuredError);
6630
- this.emitter.emit("error", structuredError);
6706
+ // Gate on listenerCount: Node EventEmitter rethrows the original error
6707
+ // from emit("error", e) when no listener is registered, which would
6708
+ // short-circuit the surrounding flow and surface as an unhandled
6709
+ // rejection. Same pattern as handleGenerateTextInternalFailure.
6710
+ if (this.emitter.listenerCount("error") > 0) {
6711
+ this.emitter.emit("error", structuredError);
6712
+ }
6631
6713
  structuredError = new NeuroLinkError({
6632
6714
  ...structuredError,
6633
6715
  context: {
@@ -6790,13 +6872,19 @@ Current user's request: ${currentInput}`;
6790
6872
  result.success === false) {
6791
6873
  const errorMessage = result.error || "Tool execution failed";
6792
6874
  const errorToEmit = new Error(errorMessage);
6793
- this.emitter.emit("error", errorToEmit);
6875
+ // Gate on listenerCount — see handleGenerateTextInternalFailure for
6876
+ // the rationale (Node EventEmitter rethrows on no listener).
6877
+ if (this.emitter.listenerCount("error") > 0) {
6878
+ this.emitter.emit("error", errorToEmit);
6879
+ }
6794
6880
  }
6795
6881
  return result;
6796
6882
  }
6797
6883
  catch (error) {
6798
6884
  const errorToEmit = error instanceof Error ? error : new Error(String(error));
6799
- this.emitter.emit("error", errorToEmit);
6885
+ if (this.emitter.listenerCount("error") > 0) {
6886
+ this.emitter.emit("error", errorToEmit);
6887
+ }
6800
6888
  // Check if tool was not found
6801
6889
  if (error instanceof Error && error.message.includes("not found")) {
6802
6890
  const availableTools = await this.getAllAvailableTools();
@@ -8974,7 +9062,7 @@ Current user's request: ${currentInput}`;
8974
9062
  async initializeAuthProviderFromConfig(config) {
8975
9063
  let provider;
8976
9064
  let providerType;
8977
- // Duck-type check: direct MastraAuthProvider instance
9065
+ // Duck-type check: direct AuthProvider instance
8978
9066
  if ("authenticateToken" in config &&
8979
9067
  typeof config.authenticateToken === "function") {
8980
9068
  provider = config;
@@ -9075,6 +9163,63 @@ Current user's request: ${currentInput}`;
9075
9163
  getExternalServerManager() {
9076
9164
  return this.externalServerManager;
9077
9165
  }
9166
+ // ==========================================================================
9167
+ // Dynamic Argument Resolution
9168
+ // ==========================================================================
9169
+ buildResolutionContext(signal, inlineContext) {
9170
+ return {
9171
+ requestContext: inlineContext || {},
9172
+ signal,
9173
+ };
9174
+ }
9175
+ /**
9176
+ * Resolve dynamic arguments in GenerateOptions, mutating the options in place.
9177
+ * Only resolves fields that are functions; static values pass through unchanged.
9178
+ */
9179
+ async resolveDynamicOptions(options) {
9180
+ const dynamicFields = [
9181
+ "model",
9182
+ "provider",
9183
+ "temperature",
9184
+ "maxTokens",
9185
+ "systemPrompt",
9186
+ "timeout",
9187
+ "thinkingLevel",
9188
+ "disableTools",
9189
+ "enableAnalytics",
9190
+ "enableEvaluation",
9191
+ ];
9192
+ const hasDynamic = dynamicFields.some((f) => typeof options[f] === "function") ||
9193
+ typeof options.tools === "function";
9194
+ if (!hasDynamic) {
9195
+ return;
9196
+ }
9197
+ const inlineCtx = options.dynamicContext;
9198
+ await this.resolveDynamicFields(options, dynamicFields, inlineCtx);
9199
+ }
9200
+ async resolveDynamicFields(options, dynamicFields, inlineContext) {
9201
+ const resolutionContext = this.buildResolutionContext(options.abortSignal, inlineContext);
9202
+ logger.debug("[NeuroLink] Resolving dynamic arguments");
9203
+ await Promise.all(dynamicFields.map(async (field) => {
9204
+ if (typeof options[field] === "function") {
9205
+ const result = await resolveDynamicArgument(options[field], resolutionContext);
9206
+ options[field] = result.value;
9207
+ logger.debug(`[NeuroLink] Resolved dynamic ${field}: ${result.resolutionType}`);
9208
+ }
9209
+ }));
9210
+ // Handle dynamic tools → enabledToolNames mapping.
9211
+ // Per DynamicOptions.tools: DynamicArgument<string[]>, the resolver
9212
+ // must return an array of tool names. Anything else is a contract
9213
+ // violation — fail fast rather than silently disabling tooling.
9214
+ if (typeof options.tools === "function") {
9215
+ const result = await resolveDynamicArgument(options.tools, resolutionContext);
9216
+ if (!Array.isArray(result.value)) {
9217
+ throw new TypeError(`Dynamic tools resolver must return string[] (tool names), got ${typeof result.value === "object" ? "object" : typeof result.value}`);
9218
+ }
9219
+ options.enabledToolNames = result.value;
9220
+ delete options.tools;
9221
+ }
9222
+ }
9078
9223
  }
9079
9224
  // Create default instance
9080
9225
  export const neurolink = new NeuroLink();
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Abstract base class for all observability exporters
3
- * Follows NeuroLink's factory pattern and Mastra's unified exporter interface
3
+ * Follows NeuroLink's factory pattern with a unified exporter interface
4
4
  */
5
5
  import type { ExporterConfig, ExporterHealthStatus, ExportResult, SpanData } from "../../types/index.js";
6
6
  /**
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Abstract base class for all observability exporters
3
- * Follows NeuroLink's factory pattern and Mastra's unified exporter interface
3
+ * Follows NeuroLink's factory pattern with a unified exporter interface
4
4
  */
5
5
  import { logger } from "../../utils/logger.js";
6
6
  /**
@@ -490,7 +490,7 @@ export type PermissionDefinition = {
490
490
  */
491
491
  export type AuthMiddlewareOptions = {
492
492
  /** Auth provider instance */
493
- provider: MastraAuthProvider;
493
+ provider: AuthProvider;
494
494
  /** Routes to exclude from authentication */
495
495
  excludePaths?: string[];
496
496
  /** Whether auth is optional (continue if no token) */
@@ -729,7 +729,7 @@ export type CustomAuthConfig = {
729
729
  createSession?: (user: AuthUser, context?: AuthRequestContext) => Promise<AuthSession>;
730
730
  };
731
731
  /**
732
- * Configuration for MastraAuthProvider.
732
+ * Configuration for AuthProvider.
733
733
  *
734
734
  * Discriminated union of base + each provider-specific config so that
735
735
  * provider factories receive the properly typed config without requiring
@@ -819,7 +819,7 @@ export type AuthEventHandler = (event: AuthEventData) => void | Promise<void>;
819
819
  /**
820
820
  * Auth provider factory function type
821
821
  */
822
- export type AuthProviderFactoryFn = (config: AuthProviderConfig) => Promise<MastraAuthProvider>;
822
+ export type AuthProviderFactoryFn = (config: AuthProviderConfig) => Promise<AuthProvider>;
823
823
  /**
824
824
  * Auth health check result
825
825
  */
@@ -973,7 +973,7 @@ export type AuthLifecycle = {
973
973
  * Composed from focused sub-types so consumers can depend on only the
974
974
  * slice they need (e.g. `AuthTokenValidator` for token-only middleware).
975
975
  *
976
- * Implements Mastra-style auth provider pattern with unified methods for:
976
+ * Unified auth provider interface covering:
977
977
  * - Token validation (AuthTokenValidator)
978
978
  * - User authorization (AuthUserAuthorizer)
979
979
  * - Session management (AuthSessionManager)
@@ -981,7 +981,7 @@ export type AuthLifecycle = {
981
981
  * - User management (AuthUserManager)
982
982
  * - Lifecycle (AuthLifecycle)
983
983
  */
984
- export type MastraAuthProvider = AuthTokenValidator & AuthUserAuthorizer & AuthSessionManager & AuthRequestHandler & AuthUserManager & AuthLifecycle & {
984
+ export type AuthProvider = AuthTokenValidator & AuthUserAuthorizer & AuthSessionManager & AuthRequestHandler & AuthUserManager & AuthLifecycle & {
985
985
  /** Provider type identifier */
986
986
  readonly type: AuthProviderType;
987
987
  /** Provider configuration */
@@ -1019,7 +1019,7 @@ export type AuthJWKSCacheEntry = {
1019
1019
  expiresAt: number;
1020
1020
  };
1021
1021
  /** Async constructor for an auth provider given its config. */
1022
- export type AuthProviderConstructor = (config: AuthProviderConfig) => Promise<MastraAuthProvider>;
1022
+ export type AuthProviderConstructor = (config: AuthProviderConfig) => Promise<AuthProvider>;
1023
1023
  /** Registration row for an auth provider in AuthProviderFactory. */
1024
1024
  export type AuthProviderRegistration = {
1025
1025
  factory: AuthProviderConstructor;
@@ -7,7 +7,7 @@ import type { TaskManagerConfig } from "./task.js";
7
7
  import type { HITLConfig } from "../types/hitl.js";
8
8
  import type { ConversationMemoryConfig } from "./conversation.js";
9
9
  import type { ObservabilityConfig } from "./observability.js";
10
- import type { MastraAuthProvider, AuthProviderType, AuthProviderConfig, Auth0Config, ClerkConfig, FirebaseConfig, SupabaseConfig, WorkOSConfig, BetterAuthConfig, JWTConfig, OAuth2Config, CognitoConfig, KeycloakConfig, AuthenticatedContext } from "./auth.js";
10
+ import type { AuthProvider, AuthProviderType, AuthProviderConfig, Auth0Config, ClerkConfig, FirebaseConfig, SupabaseConfig, WorkOSConfig, BetterAuthConfig, JWTConfig, OAuth2Config, CognitoConfig, KeycloakConfig, AuthenticatedContext } from "./auth.js";
11
11
  import type { NeurolinkCredentials } from "./providers.js";
12
12
  /**
13
13
  * Main NeuroLink configuration type
@@ -130,8 +130,8 @@ export type MCPEnhancementsConfig = {
130
130
  /**
131
131
  * Authentication configuration for NeuroLink SDK
132
132
  */
133
- export type NeuroLinkAuthConfig = MastraAuthProvider | {
134
- provider: MastraAuthProvider;
133
+ export type NeuroLinkAuthConfig = AuthProvider | {
134
+ provider: AuthProvider;
135
135
  } | {
136
136
  type: "auth0";
137
137
  config: Auth0Config;
@@ -169,7 +169,7 @@ export type NeuroLinkAuthConfig = MastraAuthProvider | {
169
169
  /**
170
170
  * Re-export auth types for convenience
171
171
  */
172
- export type { MastraAuthProvider, AuthProviderType, AuthProviderConfig, AuthenticatedContext, };
172
+ export type { AuthProvider, AuthProviderType, AuthProviderConfig, AuthenticatedContext, };
173
173
  /**
174
174
  * Provider-specific configuration
175
175
  */