@juspay/neurolink 9.81.2 → 9.82.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.
Files changed (56) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +4 -0
  3. package/dist/browser/neurolink.min.js +355 -355
  4. package/dist/cli/commands/proxy.js +5 -0
  5. package/dist/cli/factories/commandFactory.d.ts +2 -1
  6. package/dist/cli/factories/commandFactory.js +36 -16
  7. package/dist/cli/utils/audioPlayer.d.ts +37 -0
  8. package/dist/cli/utils/audioPlayer.js +138 -0
  9. package/dist/constants/contextWindows.js +10 -0
  10. package/dist/core/constants.d.ts +16 -0
  11. package/dist/core/constants.js +16 -0
  12. package/dist/core/modules/GenerationHandler.js +7 -1
  13. package/dist/core/modules/ToolsManager.d.ts +5 -0
  14. package/dist/core/modules/ToolsManager.js +62 -6
  15. package/dist/index.d.ts +6 -1
  16. package/dist/index.js +14 -6
  17. package/dist/lib/constants/contextWindows.js +10 -0
  18. package/dist/lib/core/constants.d.ts +16 -0
  19. package/dist/lib/core/constants.js +16 -0
  20. package/dist/lib/core/modules/GenerationHandler.js +7 -1
  21. package/dist/lib/core/modules/ToolsManager.d.ts +5 -0
  22. package/dist/lib/core/modules/ToolsManager.js +62 -6
  23. package/dist/lib/index.d.ts +6 -1
  24. package/dist/lib/index.js +14 -6
  25. package/dist/lib/neurolink.js +21 -3
  26. package/dist/lib/providers/googleNativeGemini3.d.ts +43 -0
  27. package/dist/lib/providers/googleNativeGemini3.js +73 -1
  28. package/dist/lib/providers/googleVertex.js +495 -80
  29. package/dist/lib/proxy/proxyDispatcher.d.ts +5 -0
  30. package/dist/lib/proxy/proxyDispatcher.js +61 -0
  31. package/dist/lib/types/generate.d.ts +5 -1
  32. package/dist/lib/utils/async/index.d.ts +1 -1
  33. package/dist/lib/utils/async/index.js +1 -1
  34. package/dist/lib/utils/async/withTimeout.d.ts +12 -0
  35. package/dist/lib/utils/async/withTimeout.js +45 -0
  36. package/dist/lib/utils/mcpErrorText.d.ts +11 -0
  37. package/dist/lib/utils/mcpErrorText.js +23 -0
  38. package/dist/lib/utils/systemMessages.d.ts +29 -0
  39. package/dist/lib/utils/systemMessages.js +45 -0
  40. package/dist/neurolink.js +21 -3
  41. package/dist/providers/googleNativeGemini3.d.ts +43 -0
  42. package/dist/providers/googleNativeGemini3.js +73 -1
  43. package/dist/providers/googleVertex.js +495 -80
  44. package/dist/proxy/proxyDispatcher.d.ts +5 -0
  45. package/dist/proxy/proxyDispatcher.js +60 -0
  46. package/dist/types/generate.d.ts +5 -1
  47. package/dist/utils/async/index.d.ts +1 -1
  48. package/dist/utils/async/index.js +1 -1
  49. package/dist/utils/async/withTimeout.d.ts +12 -0
  50. package/dist/utils/async/withTimeout.js +45 -0
  51. package/dist/utils/mcpErrorText.d.ts +11 -0
  52. package/dist/utils/mcpErrorText.js +23 -0
  53. package/dist/utils/systemMessages.d.ts +29 -0
  54. package/dist/utils/systemMessages.js +44 -0
  55. package/docs/assets/dashboards/neurolink-proxy-observability-dashboard.json +1258 -0
  56. package/package.json +3 -2
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Install the tuned global undici dispatcher. Idempotent — safe to call once at
3
+ * proxy startup. No-op when `NEUROLINK_PROXY_KEEPALIVE` is off/false/0.
4
+ */
5
+ export declare function configureProxyKeepAliveDispatcher(): void;
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Tuned global undici dispatcher for the proxy's upstream forwards.
3
+ *
4
+ * The Claude passthrough (`claudeProxyRoutes.ts`) forwards every request to
5
+ * Anthropic via the global `fetch` → undici global dispatcher. undici keep-alives
6
+ * by default, but with a ~4s idle timeout: between Claude Code turns (the user
7
+ * reads output, a tool runs, the model thinks) the idle socket closes, so the
8
+ * next request opens a brand-new TCP connection. Under sustained use that is a
9
+ * high rate of short-lived outbound flows.
10
+ *
11
+ * On hosts running a socket content-filter (CFIL) — e.g. SentinelOne or
12
+ * GlobalProtect network extensions — every new flow allocates per-flow kernel
13
+ * state, so a high flow rate amplifies any leak in that path. Reusing connections
14
+ * cuts the flow rate sharply, so we install a dispatcher with a longer keep-alive
15
+ * and a bounded, reused connection pool.
16
+ *
17
+ * Everything is overridable via env so it can be tuned (or disabled) per host:
18
+ * NEUROLINK_PROXY_KEEPALIVE=off disable; keep undici defaults
19
+ * NEUROLINK_PROXY_KEEPALIVE_MS idle keep-alive timeout (default 30000)
20
+ * NEUROLINK_PROXY_KEEPALIVE_MAX_MS keep-alive upper bound (default 600000)
21
+ * NEUROLINK_PROXY_MAX_CONNECTIONS max pooled connections per origin (default 64)
22
+ */
23
+ import { Agent, setGlobalDispatcher } from "undici";
24
+ import { logger } from "../utils/logger.js";
25
+ function readPositiveInt(name, fallback) {
26
+ const raw = process.env[name];
27
+ if (!raw) {
28
+ return fallback;
29
+ }
30
+ const parsed = Number.parseInt(raw, 10);
31
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
32
+ }
33
+ let configured = false;
34
+ /**
35
+ * Install the tuned global undici dispatcher. Idempotent — safe to call once at
36
+ * proxy startup. No-op when `NEUROLINK_PROXY_KEEPALIVE` is off/false/0.
37
+ */
38
+ export function configureProxyKeepAliveDispatcher() {
39
+ if (configured) {
40
+ return;
41
+ }
42
+ configured = true;
43
+ const toggle = (process.env.NEUROLINK_PROXY_KEEPALIVE ?? "").toLowerCase();
44
+ if (toggle === "off" || toggle === "false" || toggle === "0") {
45
+ logger.debug("[proxy] keep-alive dispatcher disabled via env");
46
+ return;
47
+ }
48
+ const keepAliveTimeout = readPositiveInt("NEUROLINK_PROXY_KEEPALIVE_MS", 30_000);
49
+ const keepAliveMaxTimeout = readPositiveInt("NEUROLINK_PROXY_KEEPALIVE_MAX_MS", 600_000);
50
+ const connections = readPositiveInt("NEUROLINK_PROXY_MAX_CONNECTIONS", 64);
51
+ setGlobalDispatcher(new Agent({
52
+ keepAliveTimeout,
53
+ keepAliveMaxTimeout,
54
+ connections,
55
+ pipelining: 1,
56
+ }));
57
+ logger.debug(`[proxy] tuned undici dispatcher installed ` +
58
+ `(keepAliveTimeout=${keepAliveTimeout}ms, ` +
59
+ `keepAliveMaxTimeout=${keepAliveMaxTimeout}ms, connections=${connections})`);
60
+ }
@@ -602,13 +602,17 @@ export type AdditionalMemoryUser = {
602
602
  *
603
603
  * - `completed` — the model finished on its own (text answer or final_result)
604
604
  * - `step-cap` — the `maxSteps` budget ran out while the model still wanted tools
605
+ * - `context-cap` — the in-loop context guard stopped the tool loop because the
606
+ * accumulated conversation approached the model's context window (and the
607
+ * terminal synthesis could not produce an answer); without the guard these
608
+ * turns died mid-loop on a provider 400 "prompt is too long"
605
609
  * - `time-limit` — the `turnTimeoutMs` wall-clock deadline passed
606
610
  * - `stalled` — no progress (no chunk, no tool start/finish) for `stallTimeoutMs`
607
611
  * - `aborted` — the caller's `abortSignal` ended the turn
608
612
  * - `provider-error` — the provider/model failed the turn (e.g. persistent
609
613
  * MALFORMED_FUNCTION_CALL after retry); usually worth a caller-side retry
610
614
  */
611
- export type GenerateStopReason = "completed" | "step-cap" | "time-limit" | "stalled" | "aborted" | "provider-error";
615
+ export type GenerateStopReason = "completed" | "step-cap" | "context-cap" | "time-limit" | "stalled" | "aborted" | "provider-error";
612
616
  /**
613
617
  * Generate function result type - Primary output format
614
618
  * Future-ready for multi-modal outputs while maintaining text focus
@@ -20,4 +20,4 @@
20
20
  */
21
21
  export { delay, sleep } from "./delay.js";
22
22
  export { calculateBackoff, createRetry, DEFAULT_RETRY_OPTIONS, RetryExhaustedError, retry, } from "./retry.js";
23
- export { TimeoutError, withTimeout, withTimeoutFn } from "./withTimeout.js";
23
+ export { TimeoutError, raceWithAbort, withTimeout, withTimeoutFn, } from "./withTimeout.js";
@@ -20,4 +20,4 @@
20
20
  */
21
21
  export { delay, sleep } from "./delay.js";
22
22
  export { calculateBackoff, createRetry, DEFAULT_RETRY_OPTIONS, RetryExhaustedError, retry, } from "./retry.js";
23
- export { TimeoutError, withTimeout, withTimeoutFn } from "./withTimeout.js";
23
+ export { TimeoutError, raceWithAbort, withTimeout, withTimeoutFn, } from "./withTimeout.js";
@@ -49,6 +49,18 @@ export declare class TimeoutError extends Error {
49
49
  * ```
50
50
  */
51
51
  export declare function withTimeout<T>(promise: Promise<T>, ms: number, message?: string): Promise<T>;
52
+ /**
53
+ * Race a promise against an AbortSignal so the caller observes an abort
54
+ * IMMEDIATELY instead of waiting for the operation to settle (or for a
55
+ * separate timeout to expire). Rejects with an abort-shaped error
56
+ * (`name === "AbortError"`) so provider loops route it to their existing
57
+ * cancellation handling.
58
+ *
59
+ * The underlying operation is NOT cancelled — it continues as a bounded
60
+ * ghost (the same tradeoff as {@link withTimeout}); its eventual settlement
61
+ * is swallowed so it can never surface as an unhandled rejection.
62
+ */
63
+ export declare function raceWithAbort<T>(promise: Promise<T>, signal: AbortSignal | undefined): Promise<T>;
52
64
  /**
53
65
  * Execute a function with timeout protection.
54
66
  *
@@ -72,6 +72,51 @@ export async function withTimeout(promise, ms, message) {
72
72
  }
73
73
  }
74
74
  }
75
+ /**
76
+ * Race a promise against an AbortSignal so the caller observes an abort
77
+ * IMMEDIATELY instead of waiting for the operation to settle (or for a
78
+ * separate timeout to expire). Rejects with an abort-shaped error
79
+ * (`name === "AbortError"`) so provider loops route it to their existing
80
+ * cancellation handling.
81
+ *
82
+ * The underlying operation is NOT cancelled — it continues as a bounded
83
+ * ghost (the same tradeoff as {@link withTimeout}); its eventual settlement
84
+ * is swallowed so it can never surface as an unhandled rejection.
85
+ */
86
+ export async function raceWithAbort(promise, signal) {
87
+ if (!signal || typeof signal.addEventListener !== "function") {
88
+ return promise;
89
+ }
90
+ const makeAbortError = () => {
91
+ const e = new Error("Operation aborted");
92
+ e.name = "AbortError";
93
+ return e;
94
+ };
95
+ if (signal.aborted) {
96
+ promise.catch(() => {
97
+ // Swallow the abandoned settlement — it must never surface as an
98
+ // unhandled rejection after the race has already been decided.
99
+ });
100
+ return Promise.reject(makeAbortError());
101
+ }
102
+ return new Promise((resolve, reject) => {
103
+ const onAbort = () => {
104
+ promise.catch(() => {
105
+ // Swallow the abandoned settlement — it must never surface as an
106
+ // unhandled rejection after the race has already been decided.
107
+ });
108
+ reject(makeAbortError());
109
+ };
110
+ signal.addEventListener("abort", onAbort, { once: true });
111
+ promise.then((value) => {
112
+ signal.removeEventListener("abort", onAbort);
113
+ resolve(value);
114
+ }, (error) => {
115
+ signal.removeEventListener("abort", onAbort);
116
+ reject(error);
117
+ });
118
+ });
119
+ }
75
120
  /**
76
121
  * Execute a function with timeout protection.
77
122
  *
@@ -24,3 +24,14 @@ export declare function extractMcpErrorText(raw: unknown): string;
24
24
  * but no readable text is present, falls back to a generic message.
25
25
  */
26
26
  export declare function extractMcpToolErrorMessage(result: unknown): string | undefined;
27
+ /**
28
+ * Detect a tool result that REPORTS failure without throwing, for the native
29
+ * loops' consecutive-failure breaker. Covers the two shapes NeuroLink itself
30
+ * produces or forwards:
31
+ * - MCP CallToolResult with `isError: true` (e.g. proxy-blocked tools) —
32
+ * delegated to {@link extractMcpToolErrorMessage}
33
+ * - our own `{ error: "..." }` payloads
34
+ * Plain strings are never classified (too false-positive-prone).
35
+ * Returns the error text, or null when the result looks like a success.
36
+ */
37
+ export declare function extractToolFailureText(result: unknown): string | null;
@@ -69,3 +69,26 @@ export function extractMcpToolErrorMessage(result) {
69
69
  ? `MCP tool returned isError: ${text}`
70
70
  : "MCP tool returned isError: true";
71
71
  }
72
+ /**
73
+ * Detect a tool result that REPORTS failure without throwing, for the native
74
+ * loops' consecutive-failure breaker. Covers the two shapes NeuroLink itself
75
+ * produces or forwards:
76
+ * - MCP CallToolResult with `isError: true` (e.g. proxy-blocked tools) —
77
+ * delegated to {@link extractMcpToolErrorMessage}
78
+ * - our own `{ error: "..." }` payloads
79
+ * Plain strings are never classified (too false-positive-prone).
80
+ * Returns the error text, or null when the result looks like a success.
81
+ */
82
+ export function extractToolFailureText(result) {
83
+ const mcpError = extractMcpToolErrorMessage(result);
84
+ if (mcpError) {
85
+ return mcpError;
86
+ }
87
+ if (result !== null &&
88
+ typeof result === "object" &&
89
+ typeof result.error === "string" &&
90
+ result.error.length > 0) {
91
+ return result.error;
92
+ }
93
+ return null;
94
+ }
@@ -0,0 +1,29 @@
1
+ import type { ModelMessage, SystemModelMessage } from "../types/index.js";
2
+ /**
3
+ * Partition a built message array into system messages and the rest, so the
4
+ * system prompt can ride `generateText`'s top-level `system` option instead of
5
+ * the `messages` array.
6
+ *
7
+ * The AI SDK deprecates system-role entries inside `messages` (warns by default
8
+ * from ai@6.0.170 via the `allowSystemInMessages` option, rejected by default
9
+ * in v7, flagged as a prompt-injection risk). The `system` option accepts full
10
+ * SystemModelMessage objects, so per-message providerOptions (e.g. the anthropic
11
+ * cacheControl breakpoint set by buildMessagesArray) survive the move. See
12
+ * issue #1024.
13
+ *
14
+ * Order is preserved within both partitions and the input is not mutated.
15
+ *
16
+ * Guard: the partition only runs when it leaves a non-empty `messages` array.
17
+ * If every message is a system message (a system-only priming call) — or there
18
+ * are no system messages at all — the original array is returned untouched with
19
+ * `system: undefined`. The AI SDK rejects an empty `messages` array, and a
20
+ * system-only call has no hoisted form (the SDK also requires a non-system
21
+ * message), so that degenerate case is deliberately left on the old
22
+ * system-in-`messages` path rather than made to throw — it keeps whatever
23
+ * behaviour it had before #1024. The normal system-plus-conversation path is
24
+ * always hoisted.
25
+ */
26
+ export declare function extractSystemMessages(messages: ModelMessage[]): {
27
+ system: SystemModelMessage[] | undefined;
28
+ messages: ModelMessage[];
29
+ };
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Partition a built message array into system messages and the rest, so the
3
+ * system prompt can ride `generateText`'s top-level `system` option instead of
4
+ * the `messages` array.
5
+ *
6
+ * The AI SDK deprecates system-role entries inside `messages` (warns by default
7
+ * from ai@6.0.170 via the `allowSystemInMessages` option, rejected by default
8
+ * in v7, flagged as a prompt-injection risk). The `system` option accepts full
9
+ * SystemModelMessage objects, so per-message providerOptions (e.g. the anthropic
10
+ * cacheControl breakpoint set by buildMessagesArray) survive the move. See
11
+ * issue #1024.
12
+ *
13
+ * Order is preserved within both partitions and the input is not mutated.
14
+ *
15
+ * Guard: the partition only runs when it leaves a non-empty `messages` array.
16
+ * If every message is a system message (a system-only priming call) — or there
17
+ * are no system messages at all — the original array is returned untouched with
18
+ * `system: undefined`. The AI SDK rejects an empty `messages` array, and a
19
+ * system-only call has no hoisted form (the SDK also requires a non-system
20
+ * message), so that degenerate case is deliberately left on the old
21
+ * system-in-`messages` path rather than made to throw — it keeps whatever
22
+ * behaviour it had before #1024. The normal system-plus-conversation path is
23
+ * always hoisted.
24
+ */
25
+ export function extractSystemMessages(messages) {
26
+ const system = [];
27
+ const rest = [];
28
+ for (const message of messages) {
29
+ if (message.role === "system") {
30
+ system.push(message);
31
+ }
32
+ else {
33
+ rest.push(message);
34
+ }
35
+ }
36
+ // Only hoist when a non-empty messages array remains. A system-only call (or
37
+ // a no-system array) passes through unchanged: hoisting would empty
38
+ // `messages`, which the SDK rejects, and a system-only call has no compliant
39
+ // hoisted form.
40
+ if (system.length === 0 || rest.length === 0) {
41
+ return { system: undefined, messages };
42
+ }
43
+ return { system, messages: rest };
44
+ }