@juspay/neurolink 12.7.6 → 12.7.8
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 +370 -367
- package/dist/core/baseProvider.js +30 -1
- package/dist/mcp/externalServerManager.d.ts +41 -1
- package/dist/mcp/externalServerManager.js +172 -39
- package/dist/mcp/mcpClientFactory.d.ts +24 -2
- package/dist/mcp/mcpClientFactory.js +89 -66
- package/dist/providers/anthropic/client.js +36 -17
- package/dist/types/conversation.d.ts +7 -0
- package/dist/types/externalMcp.d.ts +12 -2
- package/dist/types/generate.d.ts +13 -4
- package/dist/utils/conversationMemory.js +9 -1
- package/dist/utils/errorHandling.js +5 -1
- package/package.json +3 -2
|
@@ -9,7 +9,6 @@ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"
|
|
|
9
9
|
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
10
10
|
import { WebSocketClientTransport } from "@modelcontextprotocol/sdk/client/websocket.js";
|
|
11
11
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
12
|
-
import { spawn } from "child_process";
|
|
13
12
|
import { mcpLogger } from "../utils/logger.js";
|
|
14
13
|
import { globalCircuitBreakerManager } from "./mcpCircuitBreaker.js";
|
|
15
14
|
import { CircuitBreakerOpenError } from "../types/index.js";
|
|
@@ -26,6 +25,48 @@ import { getActiveTraceContext } from "../telemetry/traceContext.js";
|
|
|
26
25
|
* especially when multiple MCP servers are started concurrently.
|
|
27
26
|
*/
|
|
28
27
|
const DEFAULT_CLIENT_TIMEOUT = Math.max(5000, Number(process.env.MCP_CLIENT_TIMEOUT) || 60000);
|
|
28
|
+
/**
|
|
29
|
+
* How many stderr lines to keep per stdio server. Enough for a Python
|
|
30
|
+
* traceback or an OOM message; small enough that a chatty server logging
|
|
31
|
+
* every request to stderr costs nothing.
|
|
32
|
+
*/
|
|
33
|
+
const STDERR_TAIL_LINES = 20;
|
|
34
|
+
/**
|
|
35
|
+
* Bounded buffer of the most recent stderr lines a stdio server wrote.
|
|
36
|
+
*
|
|
37
|
+
* A crashing server explains itself on stderr and nowhere else. With the
|
|
38
|
+
* stream ignored — as it was — a `Connection closed` said nothing about
|
|
39
|
+
* why, and the ExternalServerManager could only report that a process it
|
|
40
|
+
* never saw had gone. Keeping a short tail per transport lets the
|
|
41
|
+
* disconnect log, the connect error and the `disconnected` event all carry
|
|
42
|
+
* the server's last words.
|
|
43
|
+
*/
|
|
44
|
+
class StderrTail {
|
|
45
|
+
lines = [];
|
|
46
|
+
partial = "";
|
|
47
|
+
append(chunk) {
|
|
48
|
+
this.partial += chunk.toString();
|
|
49
|
+
const pieces = this.partial.split(/\r?\n/);
|
|
50
|
+
this.partial = pieces.pop() ?? "";
|
|
51
|
+
for (const piece of pieces) {
|
|
52
|
+
if (piece.trim().length === 0) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
this.lines.push(piece);
|
|
56
|
+
if (this.lines.length > STDERR_TAIL_LINES) {
|
|
57
|
+
this.lines.shift();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
snapshot() {
|
|
62
|
+
const out = [...this.lines];
|
|
63
|
+
if (this.partial.trim().length > 0) {
|
|
64
|
+
out.push(this.partial);
|
|
65
|
+
}
|
|
66
|
+
return out.slice(-STDERR_TAIL_LINES);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const stderrTails = new WeakMap();
|
|
29
70
|
/**
|
|
30
71
|
* MCPClientFactory
|
|
31
72
|
* Factory class for creating MCP clients with different transports
|
|
@@ -164,10 +205,8 @@ export class MCPClientFactory {
|
|
|
164
205
|
static async createClientInternal(config, timeout) {
|
|
165
206
|
// Create transport
|
|
166
207
|
const transportResult = await this.createTransport(config);
|
|
167
|
-
//
|
|
168
|
-
// Note: Type assertions required due to TransportResult using 'unknown' to avoid circular imports
|
|
208
|
+
// Note: Type assertion required due to TransportResult using 'unknown' to avoid circular imports
|
|
169
209
|
const transport = transportResult.transport;
|
|
170
|
-
const process = transportResult.process;
|
|
171
210
|
try {
|
|
172
211
|
// Create client
|
|
173
212
|
const client = new Client(this.NEUROLINK_IMPLEMENTATION, {
|
|
@@ -186,24 +225,37 @@ export class MCPClientFactory {
|
|
|
186
225
|
return {
|
|
187
226
|
client,
|
|
188
227
|
transport,
|
|
189
|
-
process,
|
|
190
228
|
capabilities: serverCapabilities,
|
|
191
229
|
};
|
|
192
230
|
}
|
|
193
231
|
catch (error) {
|
|
194
|
-
// Clean up on failure
|
|
232
|
+
// Clean up on failure. For stdio, transport.close() ends stdin and
|
|
233
|
+
// escalates SIGTERM → SIGKILL on the child it spawned.
|
|
195
234
|
try {
|
|
196
235
|
await transport.close();
|
|
197
236
|
}
|
|
198
237
|
catch (closeError) {
|
|
199
238
|
mcpLogger.debug(`[MCPClientFactory] Error closing transport during cleanup:`, closeError);
|
|
200
239
|
}
|
|
201
|
-
|
|
202
|
-
|
|
240
|
+
// A server that died during the handshake wrote its reason to stderr.
|
|
241
|
+
// Attach it, so "Connection closed" arrives with the traceback that
|
|
242
|
+
// explains it instead of leaving the operator to reproduce by hand.
|
|
243
|
+
const stderrTail = this.getStderrTail(transport);
|
|
244
|
+
if (error instanceof Error && stderrTail.length > 0) {
|
|
245
|
+
throw new Error(`${error.message}\nServer stderr (last ${stderrTail.length} lines):\n${stderrTail.join("\n")}`, { cause: error });
|
|
203
246
|
}
|
|
204
247
|
throw error;
|
|
205
248
|
}
|
|
206
249
|
}
|
|
250
|
+
/**
|
|
251
|
+
* The most recent stderr lines written by the stdio server behind
|
|
252
|
+
* `transport`. Empty for network transports and for servers that have
|
|
253
|
+
* written nothing. Lines are captured from before the process is even
|
|
254
|
+
* started, so an early boot failure is included.
|
|
255
|
+
*/
|
|
256
|
+
static getStderrTail(transport) {
|
|
257
|
+
return stderrTails.get(transport)?.snapshot() ?? [];
|
|
258
|
+
}
|
|
207
259
|
/**
|
|
208
260
|
* Create transport based on configuration
|
|
209
261
|
*/
|
|
@@ -222,68 +274,28 @@ export class MCPClientFactory {
|
|
|
222
274
|
}
|
|
223
275
|
}
|
|
224
276
|
/**
|
|
225
|
-
* Create stdio transport
|
|
277
|
+
* Create a stdio transport.
|
|
278
|
+
*
|
|
279
|
+
* The SDK's StdioClientTransport owns the server process: it spawns the
|
|
280
|
+
* child inside `client.connect()` and reports the child's death through
|
|
281
|
+
* `transport.onclose`, which the Client forwards as `client.onclose`. There
|
|
282
|
+
* is deliberately no `spawn()` here. An earlier "startup probe" launched
|
|
283
|
+
* the command a second time, and that duplicate — never spoken to, never
|
|
284
|
+
* closed — was the process every lifecycle hook ended up watching while
|
|
285
|
+
* the real server could die unnoticed. It also leaked as an orphan on
|
|
286
|
+
* every shutdown.
|
|
287
|
+
*
|
|
288
|
+
* stderr is piped rather than ignored so a crashing server's last lines
|
|
289
|
+
* survive to the disconnect log and the connect error. A pipe nobody reads
|
|
290
|
+
* would back-pressure the child once the buffer fills, so the tail
|
|
291
|
+
* listener is attached before `start()`; the SDK creates the stderr
|
|
292
|
+
* PassThrough in its constructor for exactly this reason.
|
|
226
293
|
*/
|
|
227
294
|
static async createStdioTransport(config) {
|
|
228
295
|
mcpLogger.debug(`[MCPClientFactory] Creating stdio transport for ${config.id}`, {
|
|
229
296
|
command: config.command,
|
|
230
297
|
args: config.args,
|
|
231
298
|
});
|
|
232
|
-
// Validate command is present
|
|
233
|
-
if (!config.command) {
|
|
234
|
-
throw new Error(`Command is required for stdio transport`);
|
|
235
|
-
}
|
|
236
|
-
// Spawn the process
|
|
237
|
-
const childProcess = spawn(config.command, config.args || [], {
|
|
238
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
239
|
-
env: Object.fromEntries(Object.entries({
|
|
240
|
-
...process.env,
|
|
241
|
-
...config.env,
|
|
242
|
-
})
|
|
243
|
-
.filter(([, value]) => value !== undefined)
|
|
244
|
-
.map(([k, v]) => [k, String(v)])),
|
|
245
|
-
cwd: config.cwd,
|
|
246
|
-
});
|
|
247
|
-
// Handle process errors
|
|
248
|
-
const processErrorPromise = new Promise((_, reject) => {
|
|
249
|
-
childProcess.on("error", (error) => {
|
|
250
|
-
reject(new Error(`Process spawn error: ${error.message}`));
|
|
251
|
-
});
|
|
252
|
-
childProcess.on("exit", (code, signal) => {
|
|
253
|
-
if (code !== 0) {
|
|
254
|
-
reject(new Error(`Process exited with code ${code}, signal ${signal}`));
|
|
255
|
-
}
|
|
256
|
-
});
|
|
257
|
-
});
|
|
258
|
-
// Wait for process to be ready or fail using AbortController for better async patterns
|
|
259
|
-
const processStartupController = new AbortController();
|
|
260
|
-
const processStartupTimeout = setTimeout(() => {
|
|
261
|
-
processStartupController.abort();
|
|
262
|
-
}, 1000);
|
|
263
|
-
try {
|
|
264
|
-
await Promise.race([
|
|
265
|
-
new Promise((resolve) => {
|
|
266
|
-
const checkReady = () => {
|
|
267
|
-
if (processStartupController.signal.aborted) {
|
|
268
|
-
resolve(); // Timeout reached, continue
|
|
269
|
-
}
|
|
270
|
-
else {
|
|
271
|
-
setTimeout(checkReady, 100);
|
|
272
|
-
}
|
|
273
|
-
};
|
|
274
|
-
checkReady();
|
|
275
|
-
}),
|
|
276
|
-
processErrorPromise,
|
|
277
|
-
]);
|
|
278
|
-
}
|
|
279
|
-
finally {
|
|
280
|
-
clearTimeout(processStartupTimeout);
|
|
281
|
-
}
|
|
282
|
-
// Check if process is still running
|
|
283
|
-
if (childProcess.killed || childProcess.exitCode !== null) {
|
|
284
|
-
throw new Error("Process failed to start or exited immediately");
|
|
285
|
-
}
|
|
286
|
-
// Create transport
|
|
287
299
|
if (!config.command) {
|
|
288
300
|
throw new Error(`Command is required for stdio transport`);
|
|
289
301
|
}
|
|
@@ -297,9 +309,20 @@ export class MCPClientFactory {
|
|
|
297
309
|
.filter(([, value]) => value !== undefined)
|
|
298
310
|
.map(([key, value]) => [key, String(value)])),
|
|
299
311
|
cwd: config.cwd,
|
|
300
|
-
stderr: "
|
|
312
|
+
stderr: "pipe",
|
|
313
|
+
});
|
|
314
|
+
const tail = new StderrTail();
|
|
315
|
+
stderrTails.set(transport, tail);
|
|
316
|
+
transport.stderr?.on("data", (chunk) => {
|
|
317
|
+
tail.append(chunk);
|
|
318
|
+
if (mcpLogger.shouldLog("debug")) {
|
|
319
|
+
const text = chunk.toString().trim();
|
|
320
|
+
if (text.length > 0) {
|
|
321
|
+
mcpLogger.debug(`[MCPClientFactory] ${config.id} stderr:`, text);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
301
324
|
});
|
|
302
|
-
return { transport
|
|
325
|
+
return { transport };
|
|
303
326
|
}
|
|
304
327
|
/**
|
|
305
328
|
* Create SSE transport
|
|
@@ -26,7 +26,7 @@ import { stringifyAnthropicToolOutput } from "./toolOutput.js";
|
|
|
26
26
|
import { createAnthropicLoopAdapter } from "./loopAdapter.js";
|
|
27
27
|
import { runAgenticLoop } from "../../core/loopEngine.js";
|
|
28
28
|
import { createAnthropicConfig, getProviderModel, validateApiKey, } from "../../utils/providerConfig.js";
|
|
29
|
-
import { composeAbortSignals, createTimeoutController, mergeAbortSignals, } from "../../utils/timeout.js";
|
|
29
|
+
import { composeAbortSignals, createTimeoutController, mergeAbortSignals, TimeoutError, } from "../../utils/timeout.js";
|
|
30
30
|
import { resolveToolChoice } from "../../utils/toolChoice.js";
|
|
31
31
|
import { emitToolEndFromStepFinish } from "../../utils/toolEndEmitter.js";
|
|
32
32
|
import { NoOutputGeneratedError } from "../../utils/generationErrors.js";
|
|
@@ -1126,28 +1126,47 @@ export class AnthropicProvider extends BaseProvider {
|
|
|
1126
1126
|
...(toolChoice ? { tool_choice: toolChoice } : {}),
|
|
1127
1127
|
...(thinking ? { thinking } : {}),
|
|
1128
1128
|
};
|
|
1129
|
-
// The
|
|
1130
|
-
//
|
|
1131
|
-
//
|
|
1132
|
-
//
|
|
1133
|
-
//
|
|
1134
|
-
//
|
|
1135
|
-
//
|
|
1136
|
-
//
|
|
1137
|
-
|
|
1138
|
-
const
|
|
1139
|
-
.
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1129
|
+
// The caller's resolved `timeout` reaches this layer only through
|
|
1130
|
+
// providerOptions.neurolink.timeoutMs (AI-SDK call options carry no
|
|
1131
|
+
// `timeout`; the old `options.timeout` read here never fired on V3).
|
|
1132
|
+
// An explicit value is a per-call contract: never floored, never
|
|
1133
|
+
// extended. Without one, the 60s anthropic default was tuned for the
|
|
1134
|
+
// old ~4096 max_tokens — now that the default ceiling is the model's
|
|
1135
|
+
// real max, raise the floor to 5 min when a large output budget is
|
|
1136
|
+
// in play. The abort signal stays the real bound.
|
|
1137
|
+
const neurolinkNs = options.providerOptions?.neurolink;
|
|
1138
|
+
const forwardedTimeoutMs = typeof neurolinkNs?.timeoutMs === "number" &&
|
|
1139
|
+
Number.isFinite(neurolinkNs.timeoutMs) &&
|
|
1140
|
+
neurolinkNs.timeoutMs > 0
|
|
1141
|
+
? neurolinkNs.timeoutMs
|
|
1142
|
+
: undefined;
|
|
1143
|
+
const generateTimeoutMs = forwardedTimeoutMs !== undefined
|
|
1144
|
+
? forwardedTimeoutMs
|
|
1145
|
+
: params.max_tokens > 8192
|
|
1146
|
+
? Math.max(getTimeoutForOptions(options), 300_000)
|
|
1147
|
+
: getTimeoutForOptions(options);
|
|
1144
1148
|
const timeoutController = createTimeoutController(generateTimeoutMs, providerName, "generate");
|
|
1149
|
+
const requestSignal = composeAbortSignals(options.abortSignal, timeoutController?.controller.signal);
|
|
1145
1150
|
let response;
|
|
1146
1151
|
try {
|
|
1147
1152
|
response = await client.messages.create(params, {
|
|
1148
|
-
signal:
|
|
1153
|
+
signal: requestSignal,
|
|
1149
1154
|
});
|
|
1150
1155
|
}
|
|
1156
|
+
catch (error) {
|
|
1157
|
+
// The Anthropic SDK collapses ANY fired signal into its generic
|
|
1158
|
+
// APIUserAbortError ("Request was aborted."), discarding the
|
|
1159
|
+
// signal's reason. When the abort came from one of NeuroLink's own
|
|
1160
|
+
// timers (this per-call timer, or the turn-level one upstream),
|
|
1161
|
+
// the TimeoutError reason is the honest identity — surface it.
|
|
1162
|
+
const reason = requestSignal?.aborted
|
|
1163
|
+
? requestSignal.reason
|
|
1164
|
+
: undefined;
|
|
1165
|
+
if (reason instanceof TimeoutError) {
|
|
1166
|
+
throw reason;
|
|
1167
|
+
}
|
|
1168
|
+
throw error;
|
|
1169
|
+
}
|
|
1151
1170
|
finally {
|
|
1152
1171
|
timeoutController?.cleanup();
|
|
1153
1172
|
}
|
|
@@ -60,6 +60,13 @@ export type ConversationMemoryConfig = {
|
|
|
60
60
|
summarizationProvider?: string;
|
|
61
61
|
/** Model to use for summarization */
|
|
62
62
|
summarizationModel?: string;
|
|
63
|
+
/**
|
|
64
|
+
* Wall-clock cap for one summarization generate call, in milliseconds
|
|
65
|
+
* (default: 60000). A summary that overruns is dropped, not fatal — the
|
|
66
|
+
* turn continues without it — so size this for the slowest summary a real
|
|
67
|
+
* conversation produces rather than losing compaction summaries silently.
|
|
68
|
+
*/
|
|
69
|
+
summarizationTimeoutMs?: number;
|
|
63
70
|
/** Memory SDK config (condensed key-value memory per user). Set enabled: true to activate. */
|
|
64
71
|
memory?: HippocampusMemory;
|
|
65
72
|
/** Redis configuration (optional) - overrides environment variables */
|
|
@@ -51,8 +51,13 @@ export type ExternalMCPServerConfig = {
|
|
|
51
51
|
export type ExternalMCPServerInstance = {
|
|
52
52
|
/** Server configuration */
|
|
53
53
|
config: ExternalMCPServerConfig;
|
|
54
|
-
/**
|
|
54
|
+
/**
|
|
55
|
+
* Child process handle. Always null for stdio servers: the SDK transport
|
|
56
|
+
* owns the process and does not expose the handle. Use `pid`.
|
|
57
|
+
*/
|
|
55
58
|
process: ChildProcess | null;
|
|
59
|
+
/** OS process id of the stdio server, once connected */
|
|
60
|
+
pid?: number;
|
|
56
61
|
/** MCP client instance */
|
|
57
62
|
client: Client | null;
|
|
58
63
|
/** Transport instance */
|
|
@@ -305,8 +310,13 @@ export type ExternalMCPManagerConfig = {
|
|
|
305
310
|
* active server management (process handles, clients, metrics, etc.)
|
|
306
311
|
*/
|
|
307
312
|
export type RuntimeMCPServerInfo = MCPServerInfo & {
|
|
308
|
-
/**
|
|
313
|
+
/**
|
|
314
|
+
* Child process handle. Always null for stdio servers: the SDK transport
|
|
315
|
+
* owns the process and does not expose the handle. Use `pid`.
|
|
316
|
+
*/
|
|
309
317
|
process: import("child_process").ChildProcess | null;
|
|
318
|
+
/** OS process id of the stdio server, once connected */
|
|
319
|
+
pid?: number;
|
|
310
320
|
/** MCP client instance for communication */
|
|
311
321
|
client: Client | null;
|
|
312
322
|
/** Transport instance (renamed from 'transport' to avoid conflict with MCPServerInfo.transport) */
|
package/dist/types/generate.d.ts
CHANGED
|
@@ -348,7 +348,14 @@ export type GenerateOptions = {
|
|
|
348
348
|
* multi-step tool loop (Vertex Gemini / Vertex Claude), this bounds EACH
|
|
349
349
|
* model call in the loop, not the whole turn — a tool-heavy turn may run
|
|
350
350
|
* far longer than this value in total. Size it for the slowest single
|
|
351
|
-
* step (default 300s), and use `abortSignal` for a
|
|
351
|
+
* step (default 300s), and use `turnTimeoutMs` (or `abortSignal`) for a
|
|
352
|
+
* total-turn deadline.
|
|
353
|
+
*
|
|
354
|
+
* On the AI-SDK loop path (direct Anthropic, litellm, OpenAI-compatible)
|
|
355
|
+
* the same split holds only when `turnTimeoutMs` is ALSO set: then this
|
|
356
|
+
* value bounds each model call and `turnTimeoutMs` bounds the turn. With
|
|
357
|
+
* `turnTimeoutMs` unset, this value bounds the WHOLE turn there (the
|
|
358
|
+
* pre-existing defensive behavior, kept for backward compatibility).
|
|
352
359
|
*
|
|
353
360
|
* When set explicitly, a step timeout is surfaced immediately instead of
|
|
354
361
|
* burning internal retries/fallbacks that would re-run the same
|
|
@@ -363,9 +370,11 @@ export type GenerateOptions = {
|
|
|
363
370
|
* imposes no product policy).
|
|
364
371
|
*
|
|
365
372
|
* Enforced by the native Vertex loops (Gemini + Claude) AND the AI-SDK
|
|
366
|
-
* loop path (litellm and other OpenAI-compatible
|
|
367
|
-
* path
|
|
368
|
-
* `
|
|
373
|
+
* loop path (direct Anthropic, litellm and other OpenAI-compatible
|
|
374
|
+
* providers). On the AI-SDK path this value also owns the whole-turn hard
|
|
375
|
+
* abort: when set, `timeout` keeps its per-model-call meaning instead of
|
|
376
|
+
* bounding the entire loop. An explicit `timeout` also engages the same
|
|
377
|
+
* wrap-up when `turnTimeoutMs` is unset. Once the wrap-up window begins (see
|
|
369
378
|
* `wrapupTimeLeadMs`), the loop forcibly sets `toolChoice: "none"` for the
|
|
370
379
|
* remaining steps — overriding any caller-supplied `toolChoice` or
|
|
371
380
|
* `prepareStep` tool selection — and appends an honest time message that a
|
|
@@ -593,7 +593,15 @@ export function getEffectiveTokenThreshold(provider, model, envOverride, session
|
|
|
593
593
|
export async function generateSummary(messages, config, logPrefix = "[ConversationMemory]", previousSummary, requestId) {
|
|
594
594
|
const summarizationPrompt = createSummarizationPrompt(messages, previousSummary);
|
|
595
595
|
const SUMMARIZER_INIT_TIMEOUT = 15_000;
|
|
596
|
-
|
|
596
|
+
// Config-driven: a compaction summary of a large conversation routinely
|
|
597
|
+
// needs more than the old hard-coded 60s, and each overrun silently loses
|
|
598
|
+
// one summary (non-fatal — the turn continues) with no knob to raise it.
|
|
599
|
+
const configuredTimeoutMs = config.summarizationTimeoutMs;
|
|
600
|
+
const SUMMARIZER_GENERATE_TIMEOUT = typeof configuredTimeoutMs === "number" &&
|
|
601
|
+
Number.isFinite(configuredTimeoutMs) &&
|
|
602
|
+
configuredTimeoutMs > 0
|
|
603
|
+
? configuredTimeoutMs
|
|
604
|
+
: 60_000;
|
|
597
605
|
try {
|
|
598
606
|
if (!cachedSummarizer) {
|
|
599
607
|
cachedSummarizer = await withTimeout((async () => {
|
|
@@ -1189,7 +1189,11 @@ export function isAbortError(error) {
|
|
|
1189
1189
|
if (error instanceof Error &&
|
|
1190
1190
|
(error.message?.includes("This operation was aborted") ||
|
|
1191
1191
|
error.message?.includes("The operation was aborted") ||
|
|
1192
|
-
error.message?.includes("The user aborted a request")
|
|
1192
|
+
error.message?.includes("The user aborted a request") ||
|
|
1193
|
+
// Anthropic SDK's APIUserAbortError — name is plain "Error", so only
|
|
1194
|
+
// the message identifies it. Missing this classified real aborts as
|
|
1195
|
+
// provider failures (ERROR log + fallback consulted on a user cancel).
|
|
1196
|
+
error.message?.includes("Request was aborted"))) {
|
|
1193
1197
|
return true;
|
|
1194
1198
|
}
|
|
1195
1199
|
return false;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/neurolink",
|
|
3
|
-
"version": "12.7.
|
|
3
|
+
"version": "12.7.8",
|
|
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": {
|
|
@@ -84,7 +84,8 @@
|
|
|
84
84
|
"test:mcp:http": "pnpm exec tsx test/continuous-test-suite-mcp-http.ts",
|
|
85
85
|
"test:mcp:sdk": "pnpm exec tsx test/continuous-test-suite-mcp-sdk.ts",
|
|
86
86
|
"test:mcp:cli": "pnpm exec tsx test/continuous-test-suite-mcp-cli.ts",
|
|
87
|
-
"test:mcp:
|
|
87
|
+
"test:mcp:stdio-lifecycle": "pnpm exec tsx test/continuous-test-suite-mcp-stdio-lifecycle.ts",
|
|
88
|
+
"test:mcp:full": "pnpm run test:mcp:infra && pnpm run test:mcp:spans && pnpm run test:mcp:stdio-lifecycle && pnpm run test:mcp:sdk && pnpm run test:mcp:cli && pnpm run test:mcp:http",
|
|
88
89
|
"test:media": "pnpm exec tsx test/continuous-test-suite-media-gen.ts",
|
|
89
90
|
"test:media-registry-collisions": "pnpm exec tsx test/continuous-test-suite-media-registry-collisions.ts",
|
|
90
91
|
"test:memory": "pnpm exec tsx test/continuous-test-suite-memory.ts",
|