@juspay/neurolink 12.49.19 → 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/browser/neurolink.min.js +376 -376
- package/dist/core/nativeGenerateLoop.d.ts +5 -0
- package/dist/core/nativeGenerateLoop.js +74 -3
- package/dist/providers/anthropic/client.js +1 -0
- package/dist/providers/openaiChatCompletionsBase.js +1 -0
- package/dist/types/generate.d.ts +18 -1
- package/package.json +2 -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/types/generate.d.ts
CHANGED
|
@@ -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
|
|
@@ -1799,6 +1805,17 @@ export type StepToolChoiceInput = {
|
|
|
1799
1805
|
model: string;
|
|
1800
1806
|
/** The turn's abort signal; a pending `prepareStep` is released when it fires. */
|
|
1801
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>>;
|
|
1802
1819
|
};
|
|
1803
1820
|
/**
|
|
1804
1821
|
* Inputs to the shared native generate loop (`core/nativeGenerateLoop.ts`).
|
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": {
|
|
@@ -112,6 +112,7 @@
|
|
|
112
112
|
"test:agent:cost": "pnpm exec tsx test/continuous-test-suite-agent-cost.ts",
|
|
113
113
|
"test:adjust-body-after-400": "pnpm exec tsx test/continuous-test-suite-adjust-body-after-400.ts",
|
|
114
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",
|
|
115
116
|
"test:error-classification-e2e": "pnpm exec tsx test/continuous-test-suite-error-classification-e2e.ts",
|
|
116
117
|
"test:error-classifier-contract": "pnpm exec tsx test/continuous-test-suite-error-classifier-contract.ts",
|
|
117
118
|
"test:bedrock-inference-profile": "pnpm exec tsx test/continuous-test-suite-bedrock-inference-profile.ts",
|