@juspay/neurolink 12.49.18 → 12.49.20
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 +59 -4
- package/dist/browser/neurolink.min.js +399 -399
- package/dist/core/nativeGenerateLoop.d.ts +5 -0
- package/dist/core/nativeGenerateLoop.js +74 -3
- package/dist/neurolink.d.ts +5 -0
- package/dist/neurolink.js +115 -10
- package/dist/providers/anthropic/client.js +1 -0
- package/dist/providers/openaiChatCompletionsBase.js +1 -0
- package/dist/types/agentNetwork.d.ts +21 -1
- package/dist/types/common.d.ts +5 -0
- package/dist/types/generate.d.ts +21 -2
- package/dist/types/toolRouting.d.ts +6 -0
- package/dist/utils/conversationMemory.js +1 -1
- package/package.json +4 -1
|
@@ -28,6 +28,11 @@ import type { NativeGenerateLoopArgs, NativeGenerateLoopResult, StepResult, Step
|
|
|
28
28
|
* how a caller forces a tool on step 3 only. A hook that returns no
|
|
29
29
|
* `toolChoice` defers to the rule. Nothing else on the hook's result is read.
|
|
30
30
|
*
|
|
31
|
+
* A forced single-tool choice, from the hook or from the turn, that names a
|
|
32
|
+
* tool outside `declaredTools` is warned about and not sent. A hook's choice
|
|
33
|
+
* is then treated as "no override" and the rule decides; the turn's own
|
|
34
|
+
* choice becomes `"auto"`. Without `declaredTools` nothing is checked.
|
|
35
|
+
*
|
|
31
36
|
* A hook that THROWS is logged and treated as "no override": the steps
|
|
32
37
|
* already billed are kept and the rule decides. An abort of the turn while
|
|
33
38
|
* the hook is pending is not a hook failure and is re-thrown. The two are
|
|
@@ -32,6 +32,60 @@ const isForcedToolChoice = (choice) => {
|
|
|
32
32
|
}
|
|
33
33
|
return false;
|
|
34
34
|
};
|
|
35
|
+
/**
|
|
36
|
+
* The tool a choice forces by name, when it forces one. Both wire mappers
|
|
37
|
+
* honour exactly `{ type: "tool", toolName }` with a non-empty name, so
|
|
38
|
+
* that is the only shape that names a function on the wire.
|
|
39
|
+
*/
|
|
40
|
+
const forcedToolName = (choice) => {
|
|
41
|
+
if (typeof choice !== "object" || choice === null) {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
const { type, toolName } = choice;
|
|
45
|
+
return type === "tool" && typeof toolName === "string" && toolName !== ""
|
|
46
|
+
? toolName
|
|
47
|
+
: undefined;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* The name a choice forces when `declaredTools` is known and lacks it, else
|
|
51
|
+
* undefined. Own keys only: the live record may have a null prototype or be
|
|
52
|
+
* keyed by a name such as "constructor".
|
|
53
|
+
*/
|
|
54
|
+
const undeclaredForcedToolName = (choice, declaredTools) => {
|
|
55
|
+
if (!declaredTools) {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
const name = forcedToolName(choice);
|
|
59
|
+
return name !== undefined && !Object.hasOwn(declaredTools, name)
|
|
60
|
+
? name
|
|
61
|
+
: undefined;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* A forced choice for a tool the request does not declare is dropped for the
|
|
65
|
+
* step rather than sent, so a stale hook or a platform-gated tool degrades
|
|
66
|
+
* the turn to the model's own choice instead of failing it with a 400.
|
|
67
|
+
*/
|
|
68
|
+
const warnUndeclaredForcedTool = (toolName, step, source) => {
|
|
69
|
+
logger.warn(source === "prepareStep"
|
|
70
|
+
? `prepareStep forced tool "${toolName}" at step ${step}, but the request does not declare it; ignoring the override for this step`
|
|
71
|
+
: `toolChoice forces tool "${toolName}" at step ${step}, but the request does not declare it; using "auto" for this step`, { toolName, step, source });
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* The names this request actually carries, in the record shape
|
|
75
|
+
* `declaredTools` takes. The loop sends `args.tools`, built once by the
|
|
76
|
+
* caller; `args.toolsRecord` can hold more (discovery hydrates into it, and
|
|
77
|
+
* this loop never re-declares), so only `tools` answers "is it offered".
|
|
78
|
+
* Null prototype: a tool may be named `__proto__` or `constructor`.
|
|
79
|
+
*/
|
|
80
|
+
const declaredToolRecord = (tools) => {
|
|
81
|
+
const declared = Object.create(null);
|
|
82
|
+
for (const tool of tools ?? []) {
|
|
83
|
+
if (typeof tool.name === "string") {
|
|
84
|
+
declared[tool.name] = true;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return declared;
|
|
88
|
+
};
|
|
35
89
|
/**
|
|
36
90
|
* `toolChoiceSteps` is a count of leading steps, so anything but a
|
|
37
91
|
* non-negative integer is meaningless. Fall back to the default with a WARN
|
|
@@ -127,6 +181,11 @@ const abortReason = (signal) => {
|
|
|
127
181
|
* how a caller forces a tool on step 3 only. A hook that returns no
|
|
128
182
|
* `toolChoice` defers to the rule. Nothing else on the hook's result is read.
|
|
129
183
|
*
|
|
184
|
+
* A forced single-tool choice, from the hook or from the turn, that names a
|
|
185
|
+
* tool outside `declaredTools` is warned about and not sent. A hook's choice
|
|
186
|
+
* is then treated as "no override" and the rule decides; the turn's own
|
|
187
|
+
* choice becomes `"auto"`. Without `declaredTools` nothing is checked.
|
|
188
|
+
*
|
|
130
189
|
* A hook that THROWS is logged and treated as "no override": the steps
|
|
131
190
|
* already billed are kept and the rule decides. An abort of the turn while
|
|
132
191
|
* the hook is pending is not a hook failure and is re-thrown. The two are
|
|
@@ -156,16 +215,27 @@ export async function resolveStepToolChoice(input) {
|
|
|
156
215
|
}
|
|
157
216
|
warnIgnoredPrepareStepFields(prepared);
|
|
158
217
|
if (prepared?.toolChoice !== undefined) {
|
|
159
|
-
|
|
218
|
+
const undeclaredPrepared = undeclaredForcedToolName(prepared.toolChoice, input.declaredTools);
|
|
219
|
+
if (undeclaredPrepared === undefined) {
|
|
220
|
+
return prepared.toolChoice;
|
|
221
|
+
}
|
|
222
|
+
warnUndeclaredForcedTool(undeclaredPrepared, input.step, "prepareStep");
|
|
160
223
|
}
|
|
161
224
|
}
|
|
162
225
|
if (input.base === undefined) {
|
|
163
226
|
return undefined;
|
|
164
227
|
}
|
|
165
|
-
if (!isForcedToolChoice(input.base)
|
|
166
|
-
|
|
228
|
+
if (!isForcedToolChoice(input.base)) {
|
|
229
|
+
return input.base;
|
|
230
|
+
}
|
|
231
|
+
if (input.step >= normalizeToolChoiceSteps(input.toolChoiceSteps)) {
|
|
232
|
+
return "auto";
|
|
233
|
+
}
|
|
234
|
+
const undeclaredBase = undeclaredForcedToolName(input.base, input.declaredTools);
|
|
235
|
+
if (undeclaredBase === undefined) {
|
|
167
236
|
return input.base;
|
|
168
237
|
}
|
|
238
|
+
warnUndeclaredForcedTool(undeclaredBase, input.step, "base");
|
|
169
239
|
return "auto";
|
|
170
240
|
}
|
|
171
241
|
/**
|
|
@@ -356,6 +426,7 @@ export async function runNativeGenerateLoop(args, toolExecutionSummaries) {
|
|
|
356
426
|
steps: stepRecords,
|
|
357
427
|
maxSteps: args.maxSteps,
|
|
358
428
|
model: args.modelId,
|
|
429
|
+
declaredTools: declaredToolRecord(args.tools),
|
|
359
430
|
...(args.abortSignal ? { abortSignal: args.abortSignal } : {}),
|
|
360
431
|
})
|
|
361
432
|
: undefined;
|
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
|
*/
|
|
@@ -86,6 +86,20 @@ export type AgentExecutionOptions = {
|
|
|
86
86
|
enableAnalytics?: boolean;
|
|
87
87
|
/** Cumulative budget for the NeuroLink instance, not a per-run cap. */
|
|
88
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;
|
|
89
103
|
/** Additional context for the agent */
|
|
90
104
|
context?: Record<string, unknown>;
|
|
91
105
|
/** Override max steps for this execution */
|
|
@@ -493,6 +507,12 @@ export type AgentStreamChunk = {
|
|
|
493
507
|
usage?: TokenUsage;
|
|
494
508
|
/** Duration in ms (for complete chunks) */
|
|
495
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;
|
|
496
516
|
/** Error message (for error chunks) */
|
|
497
517
|
error?: string;
|
|
498
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;
|
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";
|
|
@@ -584,6 +584,10 @@ export type GenerateOptions = {
|
|
|
584
584
|
* compel a tool call on every step, and the loop would only end when
|
|
585
585
|
* `maxSteps` ran out. `"auto"` and `"none"` are applied unchanged on every
|
|
586
586
|
* step. Vertex, Google AI Studio and Bedrock do not honour `toolChoice`.
|
|
587
|
+
*
|
|
588
|
+
* A named tool that the request does not declare is not sent, because the
|
|
589
|
+
* provider would reject the request: a warning is logged and the step uses
|
|
590
|
+
* `"auto"`.
|
|
587
591
|
*/
|
|
588
592
|
toolChoice?: ToolChoice<Record<string, Tool>>;
|
|
589
593
|
/**
|
|
@@ -606,7 +610,9 @@ export type GenerateOptions = {
|
|
|
606
610
|
* Optional callback that runs before each step in a multi-step generation.
|
|
607
611
|
*
|
|
608
612
|
* Honoured field of the result: `toolChoice`, applied to that step only
|
|
609
|
-
* and taking precedence over `toolChoice` / `toolChoiceSteps`.
|
|
613
|
+
* and taking precedence over `toolChoice` / `toolChoiceSteps`. A named tool
|
|
614
|
+
* the request does not declare is ignored with a warning, as if the hook had
|
|
615
|
+
* returned no `toolChoice`.
|
|
610
616
|
*
|
|
611
617
|
* Not honoured — accepted for source compatibility with the former Vercel
|
|
612
618
|
* AI SDK `experimental_prepareStep` shape, but ignored by every native
|
|
@@ -1663,6 +1669,8 @@ export type TextGenerationOptions = {
|
|
|
1663
1669
|
* Text generation result (consolidated from core types)
|
|
1664
1670
|
*/
|
|
1665
1671
|
export type TextGenerationResult = {
|
|
1672
|
+
/** Ordered tool and host events persisted with the conversation turn. */
|
|
1673
|
+
events?: StreamEventSequence[];
|
|
1666
1674
|
content: string;
|
|
1667
1675
|
/** Parsed structured object when a `schema` was requested (see GenerateResult.structuredData). */
|
|
1668
1676
|
structuredData?: unknown;
|
|
@@ -1797,6 +1805,17 @@ export type StepToolChoiceInput = {
|
|
|
1797
1805
|
model: string;
|
|
1798
1806
|
/** The turn's abort signal; a pending `prepareStep` is released when it fires. */
|
|
1799
1807
|
abortSignal?: AbortSignal;
|
|
1808
|
+
/**
|
|
1809
|
+
* The tools this request declares, keyed by SDK-side tool name. When given,
|
|
1810
|
+
* a forced single-tool choice naming a tool outside it is not sent: a
|
|
1811
|
+
* provider answers a request that forces a function its own tools list
|
|
1812
|
+
* lacks with a 400. A loop that re-declares hydrated tools in the same
|
|
1813
|
+
* step (the streaming loops) passes the live record, because discovery
|
|
1814
|
+
* hydrates tools into it between steps; a loop that sends a fixed tools
|
|
1815
|
+
* array (the shared generate loop) passes the names of that array.
|
|
1816
|
+
* Absent means the choice is not checked.
|
|
1817
|
+
*/
|
|
1818
|
+
declaredTools?: Readonly<Record<string, unknown>>;
|
|
1800
1819
|
};
|
|
1801
1820
|
/**
|
|
1802
1821
|
* Inputs to the shared native generate loop (`core/nativeGenerateLoop.ts`).
|
|
@@ -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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/neurolink",
|
|
3
|
-
"version": "12.49.
|
|
3
|
+
"version": "12.49.20",
|
|
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",
|
|
@@ -110,6 +112,7 @@
|
|
|
110
112
|
"test:agent:cost": "pnpm exec tsx test/continuous-test-suite-agent-cost.ts",
|
|
111
113
|
"test:adjust-body-after-400": "pnpm exec tsx test/continuous-test-suite-adjust-body-after-400.ts",
|
|
112
114
|
"test:zod3-schema-native-path": "pnpm exec tsx test/continuous-test-suite-zod3-schema-native-path.ts",
|
|
115
|
+
"test:forced-tool-declared": "pnpm exec tsx test/continuous-test-suite-forced-tool-declared.ts",
|
|
113
116
|
"test:error-classification-e2e": "pnpm exec tsx test/continuous-test-suite-error-classification-e2e.ts",
|
|
114
117
|
"test:error-classifier-contract": "pnpm exec tsx test/continuous-test-suite-error-classifier-contract.ts",
|
|
115
118
|
"test:bedrock-inference-profile": "pnpm exec tsx test/continuous-test-suite-bedrock-inference-profile.ts",
|