@juspay/neurolink 12.12.16 → 12.14.0
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 +3 -3
- package/dist/browser/neurolink.min.js +381 -381
- package/dist/cli/commands/proxy.js +11 -9
- package/dist/cli/proxy-clients/grok.d.ts +45 -0
- package/dist/cli/proxy-clients/grok.js +342 -0
- package/dist/cli/proxy-clients/registry.d.ts +2 -2
- package/dist/cli/proxy-clients/registry.js +4 -2
- package/dist/constants/proxyModels.d.ts +7 -1
- package/dist/constants/proxyModels.js +7 -1
- package/dist/core/baseProvider.d.ts +10 -0
- package/dist/core/baseProvider.js +21 -0
- package/dist/core/constants.d.ts +14 -1
- package/dist/core/constants.js +18 -1
- package/dist/core/loopEngine.js +150 -7
- package/dist/core/toolExecutionGuards.js +6 -1
- package/dist/providers/amazonBedrock/client.js +10 -0
- package/dist/providers/anthropic/client.d.ts +8 -0
- package/dist/providers/anthropic/client.js +132 -3
- package/dist/providers/anthropic/loopAdapter.js +352 -244
- package/dist/providers/googleAiStudio/client.js +12 -0
- package/dist/providers/googleVertex/client.js +19 -5
- package/dist/proxy/clientAttribution.js +3 -0
- package/dist/proxy/proxyConfig.d.ts +4 -0
- package/dist/proxy/proxyConfig.js +13 -1
- package/dist/types/generate.d.ts +22 -9
- package/dist/types/loopEngine.d.ts +97 -2
- package/dist/types/proxyClient.d.ts +34 -1
- package/dist/types/stream.d.ts +141 -5
- package/dist/utils/parameterValidation.d.ts +30 -0
- package/dist/utils/parameterValidation.js +101 -0
- package/dist/utils/timeout.js +25 -2
- package/package.json +2 -1
|
@@ -978,3 +978,104 @@ export function createValidationSummary(result) {
|
|
|
978
978
|
export function hasOnlyWarnings(result) {
|
|
979
979
|
return result.errors.length === 0 && result.warnings.length > 0;
|
|
980
980
|
}
|
|
981
|
+
// ============================================================================
|
|
982
|
+
// EXECUTION CONTROL
|
|
983
|
+
// ============================================================================
|
|
984
|
+
/**
|
|
985
|
+
* Default bound on a `beforeStep` callback (ms).
|
|
986
|
+
*
|
|
987
|
+
* A boundary callback sits between two model calls, in the one place no other
|
|
988
|
+
* timer in the turn is watching: the request deadline has been disposed and
|
|
989
|
+
* the next one is not armed yet. An unbounded callback therefore stalls the
|
|
990
|
+
* turn silently and indefinitely.
|
|
991
|
+
*/
|
|
992
|
+
export const DEFAULT_BEFORE_STEP_TIMEOUT_MS = 30_000;
|
|
993
|
+
function isFinitePositive(value) {
|
|
994
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0;
|
|
995
|
+
}
|
|
996
|
+
/**
|
|
997
|
+
* Validate an opt-in `executionControl` object and its provider support.
|
|
998
|
+
*
|
|
999
|
+
* Rejecting rather than ignoring is the whole point. A caller that asked for
|
|
1000
|
+
* no lifetime ceiling and got one anyway does not find out at call time — it
|
|
1001
|
+
* finds out much later, when a long turn dies at a limit its owner believed it
|
|
1002
|
+
* had removed, reported as an ordinary cancel. So an unsupported provider is an
|
|
1003
|
+
* error here, and so is every shape that could be read two ways.
|
|
1004
|
+
*
|
|
1005
|
+
* Throws ValidationError; returns void when the control is absent or valid.
|
|
1006
|
+
*
|
|
1007
|
+
* @param request the sibling options on the SAME call that this control has to
|
|
1008
|
+
* be read together with. `turnTimeoutMs`, because a combination in which one
|
|
1009
|
+
* of the two ceilings would be silently discarded is rejected instead; and
|
|
1010
|
+
* `toolTimeoutMs`, because removing the turn's ceiling makes the per-tool
|
|
1011
|
+
* deadline the last thing watching a tool that never returns.
|
|
1012
|
+
*/
|
|
1013
|
+
export function validateExecutionControl(control, providerName, supported, request = {}) {
|
|
1014
|
+
const { turnTimeoutMs, toolTimeoutMs } = request;
|
|
1015
|
+
if (control === undefined || control === null) {
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
if (!isNonNullObject(control)) {
|
|
1019
|
+
throw new ValidationError("executionControl must be an object", "executionControl", "INVALID_TYPE");
|
|
1020
|
+
}
|
|
1021
|
+
if (!supported) {
|
|
1022
|
+
throw new ValidationError(`executionControl is not supported by provider "${providerName}" — it is implemented only on the native Anthropic stream path. Remove it, or route this turn to anthropic.`, "executionControl", "UNSUPPORTED_PROVIDER", [
|
|
1023
|
+
"Use provider: 'anthropic' for turns that need executionControl",
|
|
1024
|
+
"Use turnTimeoutMs / maxSteps for other providers",
|
|
1025
|
+
]);
|
|
1026
|
+
}
|
|
1027
|
+
const opts = control;
|
|
1028
|
+
if (!isFinitePositive(opts.requestTimeoutMs)) {
|
|
1029
|
+
throw new ValidationError(`executionControl.requestTimeoutMs is required and must be a finite positive number of milliseconds (received ${String(opts.requestTimeoutMs)}). It is the deadline that bounds a stalled upstream even when the turn has no lifetime ceiling, so there is no valid turn without it.`, "executionControl.requestTimeoutMs", "INVALID_VALUE");
|
|
1030
|
+
}
|
|
1031
|
+
// `null` is a value here, not an omission: it is the only way to say "no
|
|
1032
|
+
// lifetime timer at all", which no number can express — a very large one is
|
|
1033
|
+
// still a ceiling that fires mid-turn.
|
|
1034
|
+
if (opts.lifetimeTimeoutMs !== undefined &&
|
|
1035
|
+
opts.lifetimeTimeoutMs !== null &&
|
|
1036
|
+
!isFinitePositive(opts.lifetimeTimeoutMs)) {
|
|
1037
|
+
throw new ValidationError(`executionControl.lifetimeTimeoutMs must be null (no lifetime ceiling), a finite positive number of milliseconds, or absent (inherit the legacy timeout handling). Received ${String(opts.lifetimeTimeoutMs)}.`, "executionControl.lifetimeTimeoutMs", "INVALID_VALUE");
|
|
1038
|
+
}
|
|
1039
|
+
// Two whole-turn ceilings, one turn. An explicit `lifetimeTimeoutMs` — a
|
|
1040
|
+
// number or `null` — takes over the turn's lifetime timer completely, so a
|
|
1041
|
+
// `turnTimeoutMs` supplied alongside it was read by nothing at all. Dropping
|
|
1042
|
+
// it silently is the same defect this contract exists to remove, one layer
|
|
1043
|
+
// up: the caller's stated ceiling is discarded and it finds out when the
|
|
1044
|
+
// turn ends somewhere it did not expect.
|
|
1045
|
+
//
|
|
1046
|
+
// Scoped deliberately to the case where the drop happens. `lifetimeTimeoutMs`
|
|
1047
|
+
// ABSENT means "no opinion about the turn's lifetime", and that documented
|
|
1048
|
+
// case inherits `turnTimeoutMs` and honours it — there is nothing to reject.
|
|
1049
|
+
if (opts.lifetimeTimeoutMs !== undefined &&
|
|
1050
|
+
typeof turnTimeoutMs === "number" &&
|
|
1051
|
+
Number.isFinite(turnTimeoutMs) &&
|
|
1052
|
+
turnTimeoutMs > 0) {
|
|
1053
|
+
throw new ValidationError(`turnTimeoutMs (${turnTimeoutMs}) cannot be combined with executionControl.lifetimeTimeoutMs (${String(opts.lifetimeTimeoutMs)}): both set the turn's wall-clock ceiling, and executionControl wins, so the turnTimeoutMs would be ignored. Set exactly one of them.`, "executionControl.lifetimeTimeoutMs", "INVALID_VALUE", [
|
|
1054
|
+
"Drop turnTimeoutMs and express the ceiling as executionControl.lifetimeTimeoutMs",
|
|
1055
|
+
"Or drop executionControl.lifetimeTimeoutMs to inherit turnTimeoutMs unchanged",
|
|
1056
|
+
]);
|
|
1057
|
+
}
|
|
1058
|
+
// No ceiling, and no floor either. `lifetimeTimeoutMs: null` deliberately
|
|
1059
|
+
// arms no turn-level timer, which leaves the per-tool deadline as the only
|
|
1060
|
+
// thing that will ever end a tool that neither returns nor honours its
|
|
1061
|
+
// signal — the step cap does not advance while a tool is in flight, and the
|
|
1062
|
+
// request deadline was disposed when the step settled. `toolTimeoutMs: null`
|
|
1063
|
+
// removes that too, and the turn then has nothing watching it anywhere.
|
|
1064
|
+
//
|
|
1065
|
+
// Refused rather than resolved, because there is no safe way to pick which
|
|
1066
|
+
// of the two the caller meant to keep, and picking one silently is how a
|
|
1067
|
+
// turn ends up bounded by a limit its owner did not choose.
|
|
1068
|
+
if (opts.lifetimeTimeoutMs === null && toolTimeoutMs === null) {
|
|
1069
|
+
throw new ValidationError("executionControl.lifetimeTimeoutMs: null removes the turn's wall-clock ceiling, which leaves toolTimeoutMs as the only bound on a tool that never returns — and toolTimeoutMs: null removes that one too, so the turn would have no bound anywhere. Keep one of them.", "executionControl.lifetimeTimeoutMs", "INVALID_VALUE", [
|
|
1070
|
+
"Give toolTimeoutMs a finite bound, or omit it to take the 300000ms default",
|
|
1071
|
+
"Or give executionControl.lifetimeTimeoutMs a finite ceiling instead of null",
|
|
1072
|
+
]);
|
|
1073
|
+
}
|
|
1074
|
+
if (opts.beforeStep !== undefined && typeof opts.beforeStep !== "function") {
|
|
1075
|
+
throw new ValidationError("executionControl.beforeStep must be a function", "executionControl.beforeStep", "INVALID_TYPE");
|
|
1076
|
+
}
|
|
1077
|
+
if (opts.beforeStepTimeoutMs !== undefined &&
|
|
1078
|
+
!isFinitePositive(opts.beforeStepTimeoutMs)) {
|
|
1079
|
+
throw new ValidationError(`executionControl.beforeStepTimeoutMs must be a finite positive number of milliseconds when supplied. Received ${String(opts.beforeStepTimeoutMs)}.`, "executionControl.beforeStepTimeoutMs", "INVALID_VALUE");
|
|
1080
|
+
}
|
|
1081
|
+
}
|
package/dist/utils/timeout.js
CHANGED
|
@@ -259,12 +259,35 @@ export async function withTimeout(promise, timeout, provider, operation) {
|
|
|
259
259
|
if (!timeoutMs) {
|
|
260
260
|
return promise;
|
|
261
261
|
}
|
|
262
|
+
// The handle is captured, unref'd and cleared — the same shape
|
|
263
|
+
// `createTimeoutPromise` above already uses. `Promise.race` settles on the
|
|
264
|
+
// first outcome but cancels nothing, so an uncaptured timer stayed pending
|
|
265
|
+
// for its full duration after the wrapped promise had already resolved: one
|
|
266
|
+
// live timer per call, each holding the event loop open until it fired. The
|
|
267
|
+
// `finally` clears it the moment the race is decided, which is what makes
|
|
268
|
+
// this safe to wrap around something invoked once per tool call.
|
|
269
|
+
let timeoutHandle;
|
|
262
270
|
const timeoutPromise = new Promise((_, reject) => {
|
|
263
|
-
setTimeout(() => {
|
|
271
|
+
const timer = setTimeout(() => {
|
|
264
272
|
reject(new TimeoutError(`${provider} ${operation} operation timed out after ${timeoutMs}ms`, timeoutMs, provider, operation));
|
|
265
273
|
}, timeoutMs);
|
|
274
|
+
timeoutHandle = timer;
|
|
275
|
+
// Unref the timer so it doesn't keep the process alive (Node.js only)
|
|
276
|
+
if (typeof timer === "object" &&
|
|
277
|
+
timer &&
|
|
278
|
+
"unref" in timer &&
|
|
279
|
+
typeof timer.unref === "function") {
|
|
280
|
+
timer.unref();
|
|
281
|
+
}
|
|
266
282
|
});
|
|
267
|
-
|
|
283
|
+
try {
|
|
284
|
+
return await Promise.race([promise, timeoutPromise]);
|
|
285
|
+
}
|
|
286
|
+
finally {
|
|
287
|
+
if (timeoutHandle !== undefined) {
|
|
288
|
+
clearTimeout(timeoutHandle);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
268
291
|
}
|
|
269
292
|
/**
|
|
270
293
|
* Wrap a streaming async generator with timeout
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/neurolink",
|
|
3
|
-
"version": "12.
|
|
3
|
+
"version": "12.14.0",
|
|
4
4
|
"packageManager": "pnpm@10.15.1",
|
|
5
5
|
"description": "TypeScript AI SDK with 24+ LLM providers behind one consistent API. MCP-native (connect any MCP server), voice TTS/STT/realtime, RAG, agents, memory, context compaction. OpenAI · Anthropic · Gemini · Bedrock · Azure · Ollama · DeepSeek · NVIDIA NIM and more.",
|
|
6
6
|
"author": {
|
|
@@ -236,6 +236,7 @@
|
|
|
236
236
|
"test:bedrock-loop-characterization": "tsx test/continuous-test-suite-bedrock-loop-characterization.ts",
|
|
237
237
|
"test:sagemaker-streaming": "tsx test/continuous-test-suite-sagemaker-streaming.ts",
|
|
238
238
|
"test:anthropic-loop-characterization": "tsx test/continuous-test-suite-anthropic-loop-characterization.ts",
|
|
239
|
+
"test:anthropic-execution-control": "tsx test/continuous-test-suite-anthropic-execution-control.ts",
|
|
239
240
|
"test:aistudio-loop-characterization": "tsx test/continuous-test-suite-aistudio-loop-characterization.ts",
|
|
240
241
|
"test:docs-mcp": "pnpm exec tsx test/continuous-test-suite-docs-mcp.ts",
|
|
241
242
|
"test:vertex-claude-characterization": "tsx test/continuous-test-suite-vertex-claude-characterization.ts"
|