@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.
- package/CHANGELOG.md +12 -0
- package/dist/auth/AuthProviderFactory.d.ts +3 -3
- package/dist/auth/providers/BaseAuthProvider.d.ts +2 -2
- package/dist/auth/providers/BaseAuthProvider.js +1 -1
- package/dist/auth/serverBridge.d.ts +2 -2
- package/dist/browser/neurolink.min.js +285 -285
- package/dist/cli/factories/commandFactory.js +32 -8
- package/dist/cli/loop/optionsSchema.js +4 -0
- package/dist/cli/parser.js +3 -3
- package/dist/constants/enums.d.ts +8 -1
- package/dist/constants/enums.js +7 -0
- package/dist/dynamic/dynamicResolver.d.ts +282 -0
- package/dist/dynamic/dynamicResolver.js +633 -0
- package/dist/dynamic/index.d.ts +10 -0
- package/dist/dynamic/index.js +12 -0
- package/dist/dynamic/resolution.d.ts +17 -0
- package/dist/dynamic/resolution.js +21 -0
- package/dist/evaluation/index.js +1 -1
- package/dist/index.js +19 -2
- package/dist/lib/auth/AuthProviderFactory.d.ts +3 -3
- package/dist/lib/auth/providers/BaseAuthProvider.d.ts +2 -2
- package/dist/lib/auth/providers/BaseAuthProvider.js +1 -1
- package/dist/lib/auth/serverBridge.d.ts +2 -2
- package/dist/lib/constants/enums.d.ts +8 -1
- package/dist/lib/constants/enums.js +7 -0
- package/dist/lib/dynamic/dynamicResolver.d.ts +282 -0
- package/dist/lib/dynamic/dynamicResolver.js +634 -0
- package/dist/lib/dynamic/index.d.ts +10 -0
- package/dist/lib/dynamic/index.js +13 -0
- package/dist/lib/dynamic/resolution.d.ts +17 -0
- package/dist/lib/dynamic/resolution.js +22 -0
- package/dist/lib/evaluation/index.js +1 -1
- package/dist/lib/index.js +19 -2
- package/dist/lib/mcp/mcpServerBase.d.ts +1 -1
- package/dist/lib/mcp/mcpServerBase.js +1 -1
- package/dist/lib/neurolink.d.ts +18 -6
- package/dist/lib/neurolink.js +187 -42
- package/dist/lib/observability/exporters/baseExporter.d.ts +1 -1
- package/dist/lib/observability/exporters/baseExporter.js +1 -1
- package/dist/lib/types/auth.d.ts +6 -6
- package/dist/lib/types/config.d.ts +4 -4
- package/dist/lib/types/dynamic.d.ts +98 -0
- package/dist/lib/types/dynamic.js +10 -0
- package/dist/lib/types/generate.d.ts +29 -0
- package/dist/lib/types/index.d.ts +1 -0
- package/dist/lib/types/index.js +2 -0
- package/dist/lib/types/scorer.d.ts +1 -1
- package/dist/lib/types/scorer.js +1 -1
- package/dist/lib/types/span.d.ts +1 -1
- package/dist/lib/types/span.js +1 -1
- package/dist/lib/types/stream.d.ts +6 -0
- package/dist/lib/utils/conversationMemory.d.ts +10 -0
- package/dist/lib/utils/conversationMemory.js +185 -1
- package/dist/lib/utils/errorHandling.d.ts +13 -0
- package/dist/lib/utils/errorHandling.js +31 -0
- package/dist/mcp/mcpServerBase.d.ts +1 -1
- package/dist/mcp/mcpServerBase.js +1 -1
- package/dist/neurolink.d.ts +18 -6
- package/dist/neurolink.js +187 -42
- package/dist/observability/exporters/baseExporter.d.ts +1 -1
- package/dist/observability/exporters/baseExporter.js +1 -1
- package/dist/types/auth.d.ts +6 -6
- package/dist/types/config.d.ts +4 -4
- package/dist/types/dynamic.d.ts +98 -0
- package/dist/types/dynamic.js +9 -0
- package/dist/types/generate.d.ts +29 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/scorer.d.ts +1 -1
- package/dist/types/scorer.js +1 -1
- package/dist/types/span.d.ts +1 -1
- package/dist/types/span.js +1 -1
- package/dist/types/stream.d.ts +6 -0
- package/dist/utils/conversationMemory.d.ts +10 -0
- package/dist/utils/conversationMemory.js +185 -1
- package/dist/utils/errorHandling.d.ts +13 -0
- package/dist/utils/errorHandling.js +31 -0
- package/package.json +2 -1
package/dist/neurolink.js
CHANGED
|
@@ -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
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
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
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
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
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
4340
|
-
const allowSet = new Set(
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
3
|
+
* Follows NeuroLink's factory pattern with a unified exporter interface
|
|
4
4
|
*/
|
|
5
5
|
import { logger } from "../../utils/logger.js";
|
|
6
6
|
/**
|
package/dist/types/auth.d.ts
CHANGED
|
@@ -490,7 +490,7 @@ export type PermissionDefinition = {
|
|
|
490
490
|
*/
|
|
491
491
|
export type AuthMiddlewareOptions = {
|
|
492
492
|
/** Auth provider instance */
|
|
493
|
-
provider:
|
|
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
|
|
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<
|
|
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
|
-
*
|
|
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
|
|
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<
|
|
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;
|
package/dist/types/config.d.ts
CHANGED
|
@@ -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 {
|
|
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 =
|
|
134
|
-
provider:
|
|
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 {
|
|
172
|
+
export type { AuthProvider, AuthProviderType, AuthProviderConfig, AuthenticatedContext, };
|
|
173
173
|
/**
|
|
174
174
|
* Provider-specific configuration
|
|
175
175
|
*/
|
|
@@ -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
|
+
};
|
package/dist/types/generate.d.ts
CHANGED
|
@@ -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;
|
package/dist/types/index.d.ts
CHANGED
package/dist/types/index.js
CHANGED
package/dist/types/scorer.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file Scorer type definitions for NeuroLink evaluation system
|
|
3
|
-
*
|
|
3
|
+
* Modular scorer interfaces and types
|
|
4
4
|
*/
|
|
5
5
|
import type { JsonObject } from "./common.js";
|
|
6
6
|
import type { EnhancedEvaluationContext } from "./evaluation.js";
|