@juspay/neurolink 12.49.17 → 12.49.19
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 +2 -2
- package/dist/agent/agent.js +80 -4
- package/dist/browser/neurolink.min.js +349 -349
- package/dist/core/analytics.js +3 -18
- package/dist/core/evaluationProviders.d.ts +2 -7
- package/dist/core/evaluationProviders.js +1 -1
- package/dist/core/modules/TelemetryHandler.d.ts +3 -11
- package/dist/core/modules/TelemetryHandler.js +6 -29
- package/dist/neurolink.d.ts +5 -0
- package/dist/neurolink.js +115 -10
- package/dist/types/agentNetwork.d.ts +29 -1
- package/dist/types/common.d.ts +5 -0
- package/dist/types/evaluationProviders.d.ts +6 -0
- package/dist/types/generate.d.ts +3 -1
- package/dist/types/toolRouting.d.ts +6 -0
- package/dist/utils/conversationMemory.js +1 -1
- package/dist/utils/pricing.js +16 -1
- package/package.json +4 -1
package/dist/core/analytics.js
CHANGED
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
* including tokens, costs, performance metrics, and custom context.
|
|
6
6
|
*/
|
|
7
7
|
import { logger } from "../utils/logger.js";
|
|
8
|
-
import { modelConfig } from "./modelConfiguration.js";
|
|
9
8
|
import { extractTokenUsage as extractTokenUsageUtil } from "../utils/tokenUtils.js";
|
|
10
9
|
import { calculateCost, hasPricing } from "../utils/pricing.js";
|
|
11
10
|
/**
|
|
@@ -84,8 +83,8 @@ function extractTokenUsage(result) {
|
|
|
84
83
|
}
|
|
85
84
|
/**
|
|
86
85
|
* Estimate cost based on provider, model, and token usage.
|
|
87
|
-
* Uses
|
|
88
|
-
*
|
|
86
|
+
* Uses known per-model rates, including cache tiers. An unpriced model has
|
|
87
|
+
* unknown cost; another model's provider default cannot estimate its bill.
|
|
89
88
|
*/
|
|
90
89
|
function estimateCost(provider, model, tokens) {
|
|
91
90
|
try {
|
|
@@ -93,21 +92,7 @@ function estimateCost(provider, model, tokens) {
|
|
|
93
92
|
if (hasPricing(provider, model)) {
|
|
94
93
|
return calculateCost(provider, model, tokens);
|
|
95
94
|
}
|
|
96
|
-
|
|
97
|
-
const costInfo = modelConfig.getCostInfo(provider.toLowerCase(), model);
|
|
98
|
-
if (!costInfo) {
|
|
99
|
-
return undefined;
|
|
100
|
-
}
|
|
101
|
-
// Calculate cost using the configuration system (per-1K-token rates).
|
|
102
|
-
// costInfo has no cache tiers, so cache tokens are billed at the input
|
|
103
|
-
// rate — the pre-split total a cache-blind provider would report, never $0.
|
|
104
|
-
const inputCost = ((tokens.input +
|
|
105
|
-
(tokens.cacheReadTokens ?? 0) +
|
|
106
|
-
(tokens.cacheCreationTokens ?? 0)) /
|
|
107
|
-
1000) *
|
|
108
|
-
costInfo.input;
|
|
109
|
-
const outputCost = (tokens.output / 1000) * costInfo.output;
|
|
110
|
-
return Math.round((inputCost + outputCost) * 1_000_000) / 1_000_000; // Round to 6 decimal places
|
|
95
|
+
return undefined;
|
|
111
96
|
}
|
|
112
97
|
catch (error) {
|
|
113
98
|
logger.debug("Cost estimation failed", { provider, model, error });
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ProviderModelConfig } from "../types/index.js";
|
|
1
|
+
import type { ProviderModelConfig, ProviderPerformanceSample } from "../types/index.js";
|
|
2
2
|
/**
|
|
3
3
|
* Dynamic provider configurations for evaluation
|
|
4
4
|
* Now uses configurable system instead of hardcoded values
|
|
@@ -35,12 +35,7 @@ export declare function getBestAvailableProvider(preferCheap?: boolean): Provide
|
|
|
35
35
|
/**
|
|
36
36
|
* Record actual provider performance for optimization
|
|
37
37
|
*/
|
|
38
|
-
export declare function recordProviderPerformanceFromMetrics(providerName: string, metrics:
|
|
39
|
-
responseTime: number;
|
|
40
|
-
tokensGenerated: number;
|
|
41
|
-
cost: number;
|
|
42
|
-
success: boolean;
|
|
43
|
-
}): void;
|
|
38
|
+
export declare function recordProviderPerformanceFromMetrics(providerName: string, metrics: ProviderPerformanceSample): void;
|
|
44
39
|
/**
|
|
45
40
|
* Get performance-optimized provider based on real metrics
|
|
46
41
|
*/
|
|
@@ -142,7 +142,7 @@ export function recordProviderPerformanceFromMetrics(providerName, metrics) {
|
|
|
142
142
|
// If responseTime is 0, skip updating tokenThroughput for this sample
|
|
143
143
|
// but still update other metrics
|
|
144
144
|
// Update cost efficiency (tokens per dollar)
|
|
145
|
-
if (metrics.cost > 0) {
|
|
145
|
+
if (metrics.cost !== undefined && metrics.cost > 0) {
|
|
146
146
|
const tokensPerDollar = metrics.tokensGenerated / metrics.cost;
|
|
147
147
|
existing.costEfficiency =
|
|
148
148
|
(existing.costEfficiency * existing.sampleCount + tokensPerDollar) /
|
|
@@ -40,18 +40,10 @@ export declare class TelemetryHandler {
|
|
|
40
40
|
*/
|
|
41
41
|
recordTTSFailure(ttsProvider: string, error: NonNullable<TTSMetadata["error"]>, latency: number): void;
|
|
42
42
|
/**
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* Uses the per-model pricing table first (which has accurate rates for
|
|
46
|
-
* specific models like Claude on Vertex AI), then falls back to the
|
|
47
|
-
* provider-level default cost from modelConfiguration.
|
|
48
|
-
*
|
|
49
|
-
* Previously this only used modelConfig.getCostInfo() which returns
|
|
50
|
-
* provider-level defaults (e.g. Gemini rates for the "vertex" provider),
|
|
51
|
-
* causing a ~1,780x under-estimate when the actual model was Claude Sonnet
|
|
52
|
-
* on Vertex AI ($0.000060 vs $0.106895 for the same request).
|
|
43
|
+
* Estimate cost only from known per-model rates. Unknown pricing remains
|
|
44
|
+
* absent from cost metrics instead of using another model's default rate.
|
|
53
45
|
*/
|
|
54
|
-
calculateActualCost(usage: TokenUsage): Promise<number>;
|
|
46
|
+
calculateActualCost(usage: TokenUsage): Promise<number | undefined>;
|
|
55
47
|
/**
|
|
56
48
|
* Create telemetry configuration for Vercel AI SDK experimental_telemetry
|
|
57
49
|
* This enables automatic OpenTelemetry tracing when telemetry is enabled
|
|
@@ -17,7 +17,6 @@ import { nanoid } from "nanoid";
|
|
|
17
17
|
import { extractTokenUsage } from "../../utils/tokenUtils.js";
|
|
18
18
|
import { logger } from "../../utils/logger.js";
|
|
19
19
|
import { recordProviderPerformanceFromMetrics } from "../evaluationProviders.js";
|
|
20
|
-
import { modelConfig } from "../modelConfiguration.js";
|
|
21
20
|
import { TelemetryService } from "../../telemetry/telemetryService.js";
|
|
22
21
|
import { calculateCost, hasPricing } from "../../utils/pricing.js";
|
|
23
22
|
import { getLangfuseContext } from "../../services/server/ai/observability/instrumentation.js";
|
|
@@ -89,11 +88,11 @@ export class TelemetryHandler {
|
|
|
89
88
|
success: true,
|
|
90
89
|
});
|
|
91
90
|
// Wire TelemetryService metrics so OTEL counters/histograms are populated
|
|
92
|
-
TelemetryService.getInstance().recordAIRequest(this.providerName, this.modelName, totalTokens, responseTime, actualCost > 0 ? actualCost : undefined);
|
|
91
|
+
TelemetryService.getInstance().recordAIRequest(this.providerName, this.modelName, totalTokens, responseTime, actualCost !== undefined && actualCost > 0 ? actualCost : undefined);
|
|
93
92
|
logger.debug(`Performance recorded for ${this.providerName}`, {
|
|
94
93
|
responseTime: `${responseTime}ms`,
|
|
95
94
|
tokens: totalTokens,
|
|
96
|
-
estimatedCost: `$${actualCost.toFixed(6)}`,
|
|
95
|
+
estimatedCost: actualCost === undefined ? "unknown" : `$${actualCost.toFixed(6)}`,
|
|
97
96
|
});
|
|
98
97
|
}
|
|
99
98
|
catch (perfError) {
|
|
@@ -121,16 +120,8 @@ export class TelemetryHandler {
|
|
|
121
120
|
}
|
|
122
121
|
}
|
|
123
122
|
/**
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
* Uses the per-model pricing table first (which has accurate rates for
|
|
127
|
-
* specific models like Claude on Vertex AI), then falls back to the
|
|
128
|
-
* provider-level default cost from modelConfiguration.
|
|
129
|
-
*
|
|
130
|
-
* Previously this only used modelConfig.getCostInfo() which returns
|
|
131
|
-
* provider-level defaults (e.g. Gemini rates for the "vertex" provider),
|
|
132
|
-
* causing a ~1,780x under-estimate when the actual model was Claude Sonnet
|
|
133
|
-
* on Vertex AI ($0.000060 vs $0.106895 for the same request).
|
|
123
|
+
* Estimate cost only from known per-model rates. Unknown pricing remains
|
|
124
|
+
* absent from cost metrics instead of using another model's default rate.
|
|
134
125
|
*/
|
|
135
126
|
async calculateActualCost(usage) {
|
|
136
127
|
try {
|
|
@@ -139,25 +130,11 @@ export class TelemetryHandler {
|
|
|
139
130
|
if (hasPricing(this.providerName, this.modelName)) {
|
|
140
131
|
return calculateCost(this.providerName, this.modelName, usage);
|
|
141
132
|
}
|
|
142
|
-
|
|
143
|
-
const costInfo = modelConfig.getCostInfo(this.providerName, this.modelName);
|
|
144
|
-
if (!costInfo) {
|
|
145
|
-
return 0; // No cost info available
|
|
146
|
-
}
|
|
147
|
-
// Calculate cost per 1K tokens. costInfo has no cache tiers, so cache
|
|
148
|
-
// tokens are billed at the input rate — the same total a provider
|
|
149
|
-
// without cache-aware splitting would have reported, never $0.
|
|
150
|
-
const inputCost = ((usage.input +
|
|
151
|
-
(usage.cacheReadTokens ?? 0) +
|
|
152
|
-
(usage.cacheCreationTokens ?? 0)) /
|
|
153
|
-
1000) *
|
|
154
|
-
costInfo.input;
|
|
155
|
-
const outputCost = (usage.output / 1000) * costInfo.output;
|
|
156
|
-
return inputCost + outputCost;
|
|
133
|
+
return undefined;
|
|
157
134
|
}
|
|
158
135
|
catch (error) {
|
|
159
136
|
logger.debug(`Cost calculation failed for ${this.providerName}:`, error);
|
|
160
|
-
return
|
|
137
|
+
return undefined;
|
|
161
138
|
}
|
|
162
139
|
}
|
|
163
140
|
/**
|
package/dist/neurolink.d.ts
CHANGED
|
@@ -66,6 +66,8 @@ export declare class NeuroLink {
|
|
|
66
66
|
private mcpSkipped;
|
|
67
67
|
private mcpInitPromise;
|
|
68
68
|
private emitter;
|
|
69
|
+
/** Request ownership for conversation capture; unscoped emitter events are ignored. */
|
|
70
|
+
private readonly conversationEventContext;
|
|
69
71
|
/**
|
|
70
72
|
* Process-unique id used to attribute this instance's log events. The SDK
|
|
71
73
|
* entry points run their bodies inside `logger.runInInstanceScope(this.logInstanceId, …)`,
|
|
@@ -1047,6 +1049,7 @@ export declare class NeuroLink {
|
|
|
1047
1049
|
*/
|
|
1048
1050
|
private generateTextInternal;
|
|
1049
1051
|
private executeGenerateTextInternalWithSpan;
|
|
1052
|
+
private executeGenerateTextInternalInEventScope;
|
|
1050
1053
|
private initializeGenerateTextInternalContext;
|
|
1051
1054
|
private runGenerateTextInternalFlow;
|
|
1052
1055
|
private captureOriginalConversationMessagesForRecovery;
|
|
@@ -1196,6 +1199,8 @@ export declare class NeuroLink {
|
|
|
1196
1199
|
* @throws {Error} When conversation memory operations fail (if enabled)
|
|
1197
1200
|
*/
|
|
1198
1201
|
stream(options: StreamOptions | DynamicOptions): Promise<StreamResult>;
|
|
1202
|
+
/** Re-enter request ownership when the caller drives a deferred async iterator. */
|
|
1203
|
+
private bindConversationEventStream;
|
|
1199
1204
|
/**
|
|
1200
1205
|
* Body of {@link stream}, always entered through the instance log scope.
|
|
1201
1206
|
*
|
package/dist/neurolink.js
CHANGED
|
@@ -370,6 +370,8 @@ export class NeuroLink {
|
|
|
370
370
|
mcpSkipped = false;
|
|
371
371
|
mcpInitPromise = null;
|
|
372
372
|
emitter = createTypedEmitter();
|
|
373
|
+
/** Request ownership for conversation capture; unscoped emitter events are ignored. */
|
|
374
|
+
conversationEventContext = new AsyncLocalStorage();
|
|
373
375
|
/**
|
|
374
376
|
* Process-unique id used to attribute this instance's log events. The SDK
|
|
375
377
|
* entry points run their bodies inside `logger.runInInstanceScope(this.logInstanceId, …)`,
|
|
@@ -4724,6 +4726,7 @@ Current user's request: ${currentInput}`;
|
|
|
4724
4726
|
stallTimeoutMs: options.stallTimeoutMs,
|
|
4725
4727
|
wrapupTimeLeadMs: options.wrapupTimeLeadMs,
|
|
4726
4728
|
toolTimeoutMs: options.toolTimeoutMs,
|
|
4729
|
+
toolExecutionCapture: options.toolExecutionCapture,
|
|
4727
4730
|
abortSignal: options.abortSignal,
|
|
4728
4731
|
skipToolPromptInjection: options.skipToolPromptInjection,
|
|
4729
4732
|
middleware: options.middleware,
|
|
@@ -4946,7 +4949,7 @@ Current user's request: ${currentInput}`;
|
|
|
4946
4949
|
responseTime: textResult.responseTime,
|
|
4947
4950
|
toolsUsed: textResult.toolsUsed,
|
|
4948
4951
|
toolCalls: textResult.toolCalls ?? [],
|
|
4949
|
-
toolExecutions: toToolExecutionRecords(textResult.toolExecutions),
|
|
4952
|
+
toolExecutions: toToolExecutionRecords(textResult.toolExecutions, options.toolExecutionCapture),
|
|
4950
4953
|
enhancedWithTools: textResult.enhancedWithTools,
|
|
4951
4954
|
availableTools: transformAvailableTools(textResult.availableTools),
|
|
4952
4955
|
analytics: textResult.analytics,
|
|
@@ -5483,8 +5486,15 @@ Current user's request: ${currentInput}`;
|
|
|
5483
5486
|
return tracers.sdk.startActiveSpan("neurolink.generateTextInternal", { kind: SpanKind.INTERNAL }, (internalSpan) => this.executeGenerateTextInternalWithSpan(options, internalSpan));
|
|
5484
5487
|
}
|
|
5485
5488
|
async executeGenerateTextInternalWithSpan(options, internalSpan) {
|
|
5489
|
+
return this.conversationEventContext.run(Symbol("generate-conversation"), () => this.executeGenerateTextInternalInEventScope(options, internalSpan));
|
|
5490
|
+
}
|
|
5491
|
+
async executeGenerateTextInternalInEventScope(options, internalSpan) {
|
|
5492
|
+
const { eventSequence, cleanup } = this.setupStreamEventListeners();
|
|
5486
5493
|
try {
|
|
5487
|
-
const context =
|
|
5494
|
+
const context = {
|
|
5495
|
+
...this.initializeGenerateTextInternalContext(options),
|
|
5496
|
+
eventSequence,
|
|
5497
|
+
};
|
|
5488
5498
|
internalSpan.setAttribute("neurolink.request_id", context.requestId);
|
|
5489
5499
|
internalSpan.setAttribute("neurolink.has_conversation_memory", !!this.conversationMemory);
|
|
5490
5500
|
internalSpan.setAttribute("neurolink.provider", options.provider || "auto");
|
|
@@ -5514,6 +5524,7 @@ Current user's request: ${currentInput}`;
|
|
|
5514
5524
|
throw error;
|
|
5515
5525
|
}
|
|
5516
5526
|
finally {
|
|
5527
|
+
cleanup();
|
|
5517
5528
|
internalSpan.end();
|
|
5518
5529
|
}
|
|
5519
5530
|
}
|
|
@@ -5544,6 +5555,7 @@ Current user's request: ${currentInput}`;
|
|
|
5544
5555
|
internalSpan,
|
|
5545
5556
|
requestId: context.requestId,
|
|
5546
5557
|
startTime: context.generateInternalStartTime,
|
|
5558
|
+
events: context.eventSequence,
|
|
5547
5559
|
});
|
|
5548
5560
|
}
|
|
5549
5561
|
if (options.abortSignal?.aborted) {
|
|
@@ -5559,6 +5571,7 @@ Current user's request: ${currentInput}`;
|
|
|
5559
5571
|
internalSpan,
|
|
5560
5572
|
requestId: context.requestId,
|
|
5561
5573
|
startTime: context.generateInternalStartTime,
|
|
5574
|
+
events: context.eventSequence,
|
|
5562
5575
|
});
|
|
5563
5576
|
}
|
|
5564
5577
|
catch (error) {
|
|
@@ -5586,7 +5599,11 @@ Current user's request: ${currentInput}`;
|
|
|
5586
5599
|
: undefined;
|
|
5587
5600
|
}
|
|
5588
5601
|
async finalizeGenerateTextInternalResult(params) {
|
|
5589
|
-
const { path, result, options, internalSpan, requestId, startTime } = params;
|
|
5602
|
+
const { path, result, options, internalSpan, requestId, startTime, events, } = params;
|
|
5603
|
+
const historyEvents = events?.filter((event) => event.type !== "response:chunk");
|
|
5604
|
+
if (historyEvents?.length) {
|
|
5605
|
+
result.events = historyEvents;
|
|
5606
|
+
}
|
|
5590
5607
|
logger.info(`[NeuroLink.generateTextInternal] generate() - COMPLETE SUCCESS${path === "mcp" ? " (MCP path)" : ""}`, {
|
|
5591
5608
|
provider: result.provider,
|
|
5592
5609
|
model: result.model,
|
|
@@ -7347,7 +7364,36 @@ Current user's request: ${currentInput}`;
|
|
|
7347
7364
|
* @throws {Error} When conversation memory operations fail (if enabled)
|
|
7348
7365
|
*/
|
|
7349
7366
|
async stream(options) {
|
|
7350
|
-
|
|
7367
|
+
const eventScope = Symbol("stream-conversation");
|
|
7368
|
+
return this.conversationEventContext.run(eventScope, () => logger.runInInstanceScope(this.logInstanceId, async () => {
|
|
7369
|
+
const result = await this.streamInInstanceScope(options);
|
|
7370
|
+
return preserveLiveStreamAccessors(result, {
|
|
7371
|
+
...result,
|
|
7372
|
+
stream: this.bindConversationEventStream(result.stream, eventScope),
|
|
7373
|
+
});
|
|
7374
|
+
}));
|
|
7375
|
+
}
|
|
7376
|
+
/** Re-enter request ownership when the caller drives a deferred async iterator. */
|
|
7377
|
+
bindConversationEventStream(stream, eventScope) {
|
|
7378
|
+
const scope = this.conversationEventContext;
|
|
7379
|
+
return (async function* () {
|
|
7380
|
+
const iterator = stream[Symbol.asyncIterator]();
|
|
7381
|
+
try {
|
|
7382
|
+
while (true) {
|
|
7383
|
+
const next = await scope.run(eventScope, () => iterator.next());
|
|
7384
|
+
if (next.done) {
|
|
7385
|
+
return;
|
|
7386
|
+
}
|
|
7387
|
+
yield next.value;
|
|
7388
|
+
}
|
|
7389
|
+
}
|
|
7390
|
+
finally {
|
|
7391
|
+
const close = iterator.return?.bind(iterator);
|
|
7392
|
+
if (close) {
|
|
7393
|
+
await scope.run(eventScope, () => close());
|
|
7394
|
+
}
|
|
7395
|
+
}
|
|
7396
|
+
})();
|
|
7351
7397
|
}
|
|
7352
7398
|
/**
|
|
7353
7399
|
* Body of {@link stream}, always entered through the instance log scope.
|
|
@@ -7909,7 +7955,11 @@ Current user's request: ${currentInput}`;
|
|
|
7909
7955
|
// (Finding 1 fix). It is cleared by setToolRoutingServers() when the
|
|
7910
7956
|
// catalog changes so stale vectors are never used after an update.
|
|
7911
7957
|
let routingEmbedFn;
|
|
7912
|
-
|
|
7958
|
+
// A host-owned routing callback owns every inference account. The
|
|
7959
|
+
// instance's embedding fast-path would otherwise borrow turn credentials.
|
|
7960
|
+
const embeddingCfg = routingConfig.generateFn
|
|
7961
|
+
? undefined
|
|
7962
|
+
: routingConfig.embedding;
|
|
7913
7963
|
if (embeddingCfg?.enabled === true) {
|
|
7914
7964
|
try {
|
|
7915
7965
|
// Resolve the embedding provider: use the explicitly configured one
|
|
@@ -7959,7 +8009,7 @@ Current user's request: ${currentInput}`;
|
|
|
7959
8009
|
timeoutMs: routingConfig.timeoutMs ?? DEFAULT_TOOL_ROUTING_TIMEOUT_MS,
|
|
7960
8010
|
// Forward the abort signal so a cancelled turn aborts the router
|
|
7961
8011
|
// call promptly instead of waiting out the routing timeout.
|
|
7962
|
-
generateFn: (generateOptions) => this.preservingTurnState(() => this.generate({
|
|
8012
|
+
generateFn: (generateOptions) => this.preservingTurnState(() => (routingConfig.generateFn ?? ((input) => this.generate(input)))({
|
|
7963
8013
|
...generateOptions,
|
|
7964
8014
|
abortSignal: options.abortSignal,
|
|
7965
8015
|
})),
|
|
@@ -7967,7 +8017,11 @@ Current user's request: ${currentInput}`;
|
|
|
7967
8017
|
// configured. siteDecide returns null without one, so the resolver
|
|
7968
8018
|
// falls straight through to the generative router as before. The
|
|
7969
8019
|
// outer request's credentials, abort signal and ids ride along.
|
|
7970
|
-
|
|
8020
|
+
// A host-owned routing callback owns the inference account too.
|
|
8021
|
+
// Bypass the instance's decision fast-path in that explicit mode.
|
|
8022
|
+
decideFn: routingConfig.generateFn
|
|
8023
|
+
? undefined
|
|
8024
|
+
: (decisionOptions) => this.siteDecide(decisionOptions, this.decisionSiteContext(options, sessionId || undefined)),
|
|
7971
8025
|
decisionMinDropConfidence: routingConfig.minDropConfidence,
|
|
7972
8026
|
emitDecision: captureDecision,
|
|
7973
8027
|
// L2 / ITEM D — only populated when embedding is configured.
|
|
@@ -8948,7 +9002,30 @@ Current user's request: ${currentInput}`;
|
|
|
8948
9002
|
setupStreamEventListeners() {
|
|
8949
9003
|
const eventSequence = [];
|
|
8950
9004
|
let eventSeqCounter = 0;
|
|
8951
|
-
const
|
|
9005
|
+
const eventScope = this.conversationEventContext.getStore();
|
|
9006
|
+
const ownedConfirmations = new Set();
|
|
9007
|
+
const confirmationId = (event) => {
|
|
9008
|
+
if (!event ||
|
|
9009
|
+
typeof event !== "object" ||
|
|
9010
|
+
!("payload" in event) ||
|
|
9011
|
+
!event.payload ||
|
|
9012
|
+
typeof event.payload !== "object" ||
|
|
9013
|
+
!("confirmationId" in event.payload) ||
|
|
9014
|
+
typeof event.payload.confirmationId !== "string") {
|
|
9015
|
+
return undefined;
|
|
9016
|
+
}
|
|
9017
|
+
return event.payload.confirmationId;
|
|
9018
|
+
};
|
|
9019
|
+
const captureEvent = (type, data, ownedConfirmation) => {
|
|
9020
|
+
// Never infer ownership from whichever listeners happen to be active.
|
|
9021
|
+
// This also fails closed where async context propagation is unavailable.
|
|
9022
|
+
const belongsToTurn = eventScope !== undefined &&
|
|
9023
|
+
this.conversationEventContext.getStore() === eventScope;
|
|
9024
|
+
const belongsToConfirmation = ownedConfirmation !== undefined &&
|
|
9025
|
+
ownedConfirmations.has(ownedConfirmation);
|
|
9026
|
+
if (!belongsToTurn && !belongsToConfirmation) {
|
|
9027
|
+
return;
|
|
9028
|
+
}
|
|
8952
9029
|
eventSequence.push({
|
|
8953
9030
|
type,
|
|
8954
9031
|
seq: eventSeqCounter++,
|
|
@@ -8990,19 +9067,46 @@ Current user's request: ${currentInput}`;
|
|
|
8990
9067
|
});
|
|
8991
9068
|
}
|
|
8992
9069
|
};
|
|
9070
|
+
const onHostConversationEvent = (...args) => {
|
|
9071
|
+
const event = args[0];
|
|
9072
|
+
if (event &&
|
|
9073
|
+
typeof event === "object" &&
|
|
9074
|
+
"type" in event &&
|
|
9075
|
+
typeof event.type === "string" &&
|
|
9076
|
+
"data" in event &&
|
|
9077
|
+
event.data &&
|
|
9078
|
+
typeof event.data === "object" &&
|
|
9079
|
+
!Array.isArray(event.data)) {
|
|
9080
|
+
captureEvent(event.type, { data: event.data });
|
|
9081
|
+
}
|
|
9082
|
+
};
|
|
8993
9083
|
const onUIComponent = (...args) => {
|
|
8994
9084
|
captureEvent("ui-component", args[0]);
|
|
8995
9085
|
};
|
|
8996
9086
|
const onHITLRequest = (...args) => {
|
|
8997
|
-
|
|
9087
|
+
if (eventScope !== undefined &&
|
|
9088
|
+
this.conversationEventContext.getStore() === eventScope) {
|
|
9089
|
+
const id = confirmationId(args[0]);
|
|
9090
|
+
if (id !== undefined) {
|
|
9091
|
+
ownedConfirmations.add(id);
|
|
9092
|
+
}
|
|
9093
|
+
captureEvent("hitl:confirmation-request", args[0]);
|
|
9094
|
+
}
|
|
8998
9095
|
};
|
|
8999
9096
|
const onHITLResponse = (...args) => {
|
|
9000
|
-
|
|
9097
|
+
const id = confirmationId(args[0]);
|
|
9098
|
+
// A host answers from its own HTTP/async context. The issued confirmation
|
|
9099
|
+
// id carries ownership without admitting arbitrary global emitter data.
|
|
9100
|
+
if (id !== undefined && ownedConfirmations.has(id)) {
|
|
9101
|
+
captureEvent("hitl:confirmation-response", args[0], id);
|
|
9102
|
+
ownedConfirmations.delete(id);
|
|
9103
|
+
}
|
|
9001
9104
|
};
|
|
9002
9105
|
this.emitter.on("response:chunk", onResponseChunk);
|
|
9003
9106
|
this.emitter.on("tool:start", onToolStart);
|
|
9004
9107
|
this.emitter.on("tool:end", onToolEnd);
|
|
9005
9108
|
this.emitter.on("ui-component", onUIComponent);
|
|
9109
|
+
this.emitter.on("host:conversation-event", onHostConversationEvent);
|
|
9006
9110
|
this.emitter.on("hitl:confirmation-request", onHITLRequest);
|
|
9007
9111
|
this.emitter.on("hitl:confirmation-response", onHITLResponse);
|
|
9008
9112
|
const cleanup = () => {
|
|
@@ -9010,6 +9114,7 @@ Current user's request: ${currentInput}`;
|
|
|
9010
9114
|
this.emitter.off("tool:start", onToolStart);
|
|
9011
9115
|
this.emitter.off("tool:end", onToolEnd);
|
|
9012
9116
|
this.emitter.off("ui-component", onUIComponent);
|
|
9117
|
+
this.emitter.off("host:conversation-event", onHostConversationEvent);
|
|
9013
9118
|
this.emitter.off("hitl:confirmation-request", onHITLRequest);
|
|
9014
9119
|
this.emitter.off("hitl:confirmation-response", onHITLResponse);
|
|
9015
9120
|
};
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import type { z } from "zod";
|
|
8
8
|
import type { AIProviderName } from "../constants/enums.js";
|
|
9
9
|
import type { TokenUsage } from "./analytics.js";
|
|
10
|
-
import type { GenerateStopReason, ToolExecutionRecord } from "./generate.js";
|
|
10
|
+
import type { GenerateStopReason, ToolExecutionRecord, ToolExecutionCaptureOptions } from "./generate.js";
|
|
11
11
|
/**
|
|
12
12
|
* Agent definition for creating agents in the network
|
|
13
13
|
*/
|
|
@@ -49,6 +49,8 @@ export type AgentInput = string | Record<string, unknown>;
|
|
|
49
49
|
* Result of agent execution
|
|
50
50
|
*/
|
|
51
51
|
export type AgentResult = {
|
|
52
|
+
/** SDK estimate in USD; undefined when the model has no known pricing. */
|
|
53
|
+
cost?: number;
|
|
52
54
|
/** Generated content */
|
|
53
55
|
content: string;
|
|
54
56
|
/** Structured output if schema was provided */
|
|
@@ -80,6 +82,24 @@ export type AgentResult = {
|
|
|
80
82
|
* Options for agent execution
|
|
81
83
|
*/
|
|
82
84
|
export type AgentExecutionOptions = {
|
|
85
|
+
/** Enable SDK usage and cost analytics for this execution. */
|
|
86
|
+
enableAnalytics?: boolean;
|
|
87
|
+
/** Cumulative budget for the NeuroLink instance, not a per-run cap. */
|
|
88
|
+
maxBudgetUsd?: number;
|
|
89
|
+
/** Per-turn tool opt-out; cannot expand the agent tool scope. */
|
|
90
|
+
disableTools?: boolean;
|
|
91
|
+
/** Provider region override for this turn. */
|
|
92
|
+
region?: string;
|
|
93
|
+
/** Output bound for this turn. */
|
|
94
|
+
maxTokens?: number;
|
|
95
|
+
/** Opt out of session history and user-memory reads/writes for this turn, retaining stored state. */
|
|
96
|
+
useMemory?: boolean;
|
|
97
|
+
/** Conversation memory identity; never included in the model prompt. */
|
|
98
|
+
sessionId?: string;
|
|
99
|
+
/** Refuse SDK-internal provider/model fallback for this execution. */
|
|
100
|
+
disableInternalFallback?: boolean;
|
|
101
|
+
/** Bounds and callback for the underlying SDK's tool execution capture. */
|
|
102
|
+
toolExecutionCapture?: ToolExecutionCaptureOptions;
|
|
83
103
|
/** Additional context for the agent */
|
|
84
104
|
context?: Record<string, unknown>;
|
|
85
105
|
/** Override max steps for this execution */
|
|
@@ -469,6 +489,8 @@ export type AgentStreamChunkType = "agent-start" | "agent-thinking" | "agent-tex
|
|
|
469
489
|
* Agent stream chunk
|
|
470
490
|
*/
|
|
471
491
|
export type AgentStreamChunk = {
|
|
492
|
+
/** SDK cost estimate in USD (for complete chunks), when priced. */
|
|
493
|
+
cost?: number;
|
|
472
494
|
/** Chunk type */
|
|
473
495
|
type: AgentStreamChunkType;
|
|
474
496
|
/** Agent ID */
|
|
@@ -485,6 +507,12 @@ export type AgentStreamChunk = {
|
|
|
485
507
|
usage?: TokenUsage;
|
|
486
508
|
/** Duration in ms (for complete chunks) */
|
|
487
509
|
duration?: number;
|
|
510
|
+
/** Complete-turn status; errors remain failures even after partial text. */
|
|
511
|
+
status?: AgentResult["status"];
|
|
512
|
+
/** Final, bounded per-call records in the same shape as AgentResult. */
|
|
513
|
+
toolExecutions?: ToolExecutionRecord[];
|
|
514
|
+
/** Terminal reason from the underlying stream. */
|
|
515
|
+
stopReason?: GenerateStopReason;
|
|
488
516
|
/** Error message (for error chunks) */
|
|
489
517
|
error?: string;
|
|
490
518
|
/** Tool name (for tool chunks) */
|
package/dist/types/common.d.ts
CHANGED
|
@@ -93,6 +93,11 @@ export type InternalStreamEvent = {
|
|
|
93
93
|
* Flexible type to support both typed and legacy event patterns
|
|
94
94
|
*/
|
|
95
95
|
export type NeuroLinkEvents = {
|
|
96
|
+
/** Application-owned events captured in ordered conversation history. */
|
|
97
|
+
"host:conversation-event": {
|
|
98
|
+
type: string;
|
|
99
|
+
data: Record<string, unknown>;
|
|
100
|
+
};
|
|
96
101
|
"tool:start": unknown;
|
|
97
102
|
"tool:end": unknown;
|
|
98
103
|
"stream:start": unknown;
|
|
@@ -28,6 +28,12 @@ export type ProviderModelConfig = {
|
|
|
28
28
|
/**
|
|
29
29
|
* Real-time provider performance tracking metrics
|
|
30
30
|
*/
|
|
31
|
+
export type ProviderPerformanceSample = {
|
|
32
|
+
responseTime: number;
|
|
33
|
+
tokensGenerated: number;
|
|
34
|
+
cost?: number;
|
|
35
|
+
success: boolean;
|
|
36
|
+
};
|
|
31
37
|
export type ProviderPerformanceMetrics = {
|
|
32
38
|
responseTime: number[];
|
|
33
39
|
successRate: number;
|
package/dist/types/generate.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ import type { ClaudeLimitSnapshot } from "./subscription.js";
|
|
|
7
7
|
import type { JsonValue } from "./common.js";
|
|
8
8
|
import type { TerminalAgentModeOption, TerminalAgentModeVersion } from "./agentMode.js";
|
|
9
9
|
import type { Content, ImageWithAltText } from "./content.js";
|
|
10
|
-
import type { ChatMessage, ConversationMemoryConfig, ToolReplayMode } from "./conversation.js";
|
|
10
|
+
import type { ChatMessage, ConversationMemoryConfig, StreamEventSequence, ToolReplayMode } from "./conversation.js";
|
|
11
11
|
import type { MemoryCallOptions } from "./memory.js";
|
|
12
12
|
import type { EvaluationData } from "./evaluation.js";
|
|
13
13
|
import type { MiddlewareFactoryOptions, OnFinishCallback, OnErrorCallback } from "./middleware.js";
|
|
@@ -1663,6 +1663,8 @@ export type TextGenerationOptions = {
|
|
|
1663
1663
|
* Text generation result (consolidated from core types)
|
|
1664
1664
|
*/
|
|
1665
1665
|
export type TextGenerationResult = {
|
|
1666
|
+
/** Ordered tool and host events persisted with the conversation turn. */
|
|
1667
|
+
events?: StreamEventSequence[];
|
|
1666
1668
|
content: string;
|
|
1667
1669
|
/** Parsed structured object when a `schema` was requested (see GenerateResult.structuredData). */
|
|
1668
1670
|
structuredData?: unknown;
|
|
@@ -106,6 +106,12 @@ export type ToolRoutingEmbeddingConfig = {
|
|
|
106
106
|
};
|
|
107
107
|
/** Constructor-level configuration for pre-call tool routing. */
|
|
108
108
|
export type ToolRoutingConfig = {
|
|
109
|
+
/**
|
|
110
|
+
* Host-owned generative routing, independent of main-turn credentials.
|
|
111
|
+
* When provided, instance decision and embedding fast-paths are bypassed;
|
|
112
|
+
* routing selection is performed by this callback.
|
|
113
|
+
*/
|
|
114
|
+
generateFn?: (options: GenerateOptions) => Promise<GenerateResult>;
|
|
109
115
|
/** Master switch. Routing runs only when true AND the server catalog is non-empty. */
|
|
110
116
|
enabled: boolean;
|
|
111
117
|
/**
|
|
@@ -384,7 +384,7 @@ export async function storeConversationTurn(conversationMemory, originalOptions,
|
|
|
384
384
|
providerDetails,
|
|
385
385
|
enableSummarization: originalOptions.enableSummarization,
|
|
386
386
|
requestId,
|
|
387
|
-
events: toolActivityEvents,
|
|
387
|
+
events: result.events?.length ? result.events : toolActivityEvents,
|
|
388
388
|
...(skillMessages && skillMessages.length > 0
|
|
389
389
|
? { skillMessages }
|
|
390
390
|
: {}),
|
package/dist/utils/pricing.js
CHANGED
|
@@ -283,6 +283,15 @@ const PRICING = {
|
|
|
283
283
|
},
|
|
284
284
|
// Google Vertex AI — Claude models on Vertex (same pricing, @ date suffix)
|
|
285
285
|
vertex: {
|
|
286
|
+
// Standard Sonnet 4 rates; the lookup also recognizes its @ date suffix.
|
|
287
|
+
// https://platform.claude.com/docs/en/about-claude/pricing
|
|
288
|
+
"claude-sonnet-4": {
|
|
289
|
+
input: 3.0 / 1_000_000,
|
|
290
|
+
output: 15.0 / 1_000_000,
|
|
291
|
+
cacheRead: 0.3 / 1_000_000,
|
|
292
|
+
cacheCreation: 3.75 / 1_000_000,
|
|
293
|
+
cacheCreation1h: 6.0 / 1_000_000,
|
|
294
|
+
},
|
|
286
295
|
// A 1-hour cache write is 2x base input, as on the direct API.
|
|
287
296
|
// Claude 5 family. Absent here until now, so a Vertex leg serving any of
|
|
288
297
|
// them resolved to no rate at all and booked the turn at $0 — on the one
|
|
@@ -527,7 +536,13 @@ const PRICING = {
|
|
|
527
536
|
},
|
|
528
537
|
// Google (Gemini) — updated March 2026
|
|
529
538
|
google: {
|
|
530
|
-
//
|
|
539
|
+
// GA text/image/video rates: https://ai.google.dev/gemini-api/docs/pricing
|
|
540
|
+
"gemini-3.1-flash-lite": {
|
|
541
|
+
input: 0.25 / 1_000_000,
|
|
542
|
+
output: 1.5 / 1_000_000,
|
|
543
|
+
cacheRead: 0.025 / 1_000_000,
|
|
544
|
+
},
|
|
545
|
+
// Gemini 3.1 preview family
|
|
531
546
|
// cacheRead = 0.25x input (cached content tokens; explicit-cache storage is
|
|
532
547
|
// billed separately by TTL/time, so no per-token cacheCreation rate here).
|
|
533
548
|
"gemini-3.1-pro-preview": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/neurolink",
|
|
3
|
-
"version": "12.49.
|
|
3
|
+
"version": "12.49.19",
|
|
4
4
|
"packageManager": "pnpm@10.15.1",
|
|
5
5
|
"description": "The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002/call) for routing, tool selection and context budgeting. MCP-native (4 transports), voice TTS/STT/realtime, RAG, agents, memory, compaction, 9 observability exporters. OpenAI · Anthropic · Gemini · Bedrock · Azure · Ollama · TypeSafe Jev and more.",
|
|
6
6
|
"author": {
|
|
@@ -75,6 +75,8 @@
|
|
|
75
75
|
"test": "pnpm exec tsx test/continuous-test-suite.ts",
|
|
76
76
|
"test:agent-delegation": "pnpm exec tsx test/continuous-test-suite-agent-delegation.ts",
|
|
77
77
|
"test:agent-tasks": "pnpm exec tsx test/continuous-test-suite-agent-tasks.ts",
|
|
78
|
+
"test:agent:session-stream": "pnpm exec tsx test/continuous-test-suite-agent-session-stream.ts",
|
|
79
|
+
"test:agent:platform-router": "pnpm exec tsx test/continuous-test-suite-agent-platform-router.ts",
|
|
78
80
|
"test:artifact-banking": "pnpm exec tsx test/continuous-test-suite-artifact-banking.ts",
|
|
79
81
|
"test:background-commands": "pnpm exec tsx test/continuous-test-suite-background-commands.ts",
|
|
80
82
|
"test:client": "pnpm exec tsx test/continuous-test-suite-client.ts",
|
|
@@ -107,6 +109,7 @@
|
|
|
107
109
|
"test:anthropic-silent-truncation": "pnpm exec tsx test/continuous-test-suite-anthropic-silent-truncation.ts",
|
|
108
110
|
"test:anthropic-thinking-disabled": "pnpm exec tsx test/continuous-test-suite-anthropic-thinking-disabled.ts",
|
|
109
111
|
"test:cache-cost-accounting": "pnpm exec tsx test/continuous-test-suite-cache-cost-accounting.ts",
|
|
112
|
+
"test:agent:cost": "pnpm exec tsx test/continuous-test-suite-agent-cost.ts",
|
|
110
113
|
"test:adjust-body-after-400": "pnpm exec tsx test/continuous-test-suite-adjust-body-after-400.ts",
|
|
111
114
|
"test:zod3-schema-native-path": "pnpm exec tsx test/continuous-test-suite-zod3-schema-native-path.ts",
|
|
112
115
|
"test:error-classification-e2e": "pnpm exec tsx test/continuous-test-suite-error-classification-e2e.ts",
|