@juspay/neurolink 9.93.1 → 9.93.2
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 +6 -0
- package/dist/browser/neurolink.min.js +245 -245
- package/dist/cli/commands/proxy.js +15 -18
- package/dist/cli/utils/serverUtils.js +4 -1
- package/dist/lib/proxy/globalInstaller.js +1 -1
- package/dist/lib/proxy/openaiFormat.js +1 -0
- package/dist/lib/proxy/sseInterceptor.js +1 -1
- package/dist/lib/proxy/workerLog.d.ts +6 -0
- package/dist/lib/proxy/workerLog.js +42 -0
- package/dist/lib/server/routes/openaiProxyRoutes.d.ts +9 -0
- package/dist/lib/server/routes/openaiProxyRoutes.js +16 -1
- package/dist/proxy/globalInstaller.js +1 -1
- package/dist/proxy/openaiFormat.js +1 -0
- package/dist/proxy/sseInterceptor.js +1 -1
- package/dist/proxy/workerLog.d.ts +6 -0
- package/dist/proxy/workerLog.js +41 -0
- package/dist/server/routes/openaiProxyRoutes.d.ts +9 -0
- package/dist/server/routes/openaiProxyRoutes.js +16 -1
- package/package.json +1 -1
|
@@ -16,6 +16,7 @@ import chalk from "chalk";
|
|
|
16
16
|
import ora from "ora";
|
|
17
17
|
import { buildProxyHealthResponse, createProxyReadinessState, markProxyDrainingForUpdate, markProxyReady, resumeProxyConnections, waitForProxyReadiness, } from "../../lib/proxy/proxyHealth.js";
|
|
18
18
|
import { logger } from "../../lib/utils/logger.js";
|
|
19
|
+
import { sanitizeForLog } from "../../lib/utils/logSanitize.js";
|
|
19
20
|
import { withTimeout } from "../../lib/utils/async/withTimeout.js";
|
|
20
21
|
import { formatUptime, isProcessRunning, StateFileManager, } from "../utils/serverUtils.js";
|
|
21
22
|
import { configureProxyKeepAliveDispatcher } from "../../lib/proxy/proxyDispatcher.js";
|
|
@@ -25,6 +26,7 @@ import { beginProxyRequest, getProxyActivitySnapshot, trackProxyResponse, } from
|
|
|
25
26
|
import { flushProxyLifecycleEvents, getProxyLifecycleLoggerSnapshot, hashProxyLifecycleSessionId, logProxyLifecycleEvent, } from "../../lib/proxy/proxyLifecycle.js";
|
|
26
27
|
import { describeInstallFailure, getGlobalInstallArgs, resolveGlobalInstaller, validateInstalledVersion, } from "../../lib/proxy/globalInstaller.js";
|
|
27
28
|
import { startUpdaterWorkerSupervisor } from "../../lib/proxy/updaterSupervisor.js";
|
|
29
|
+
import { openProxyWorkerLog } from "../../lib/proxy/workerLog.js";
|
|
28
30
|
import { waitForProxyUpdateWindow } from "../../lib/proxy/updateCoordinator.js";
|
|
29
31
|
import { abandonPendingUpdate, clearUpdateDeferral, isVersionSuppressed, loadUpdateState, recordCheck, recordSuccessfulUpdate, recordUpdateDeferred, recordUpdateFailure, recordUpdateInstalled, suppressVersion, } from "../../lib/proxy/updateState.js";
|
|
30
32
|
import { loadProxyEnvFile, resolveProxyEnvFile, } from "../../lib/proxy/proxyEnv.js";
|
|
@@ -538,18 +540,14 @@ function spawnFailOpenGuard(host, port, parentPid) {
|
|
|
538
540
|
String(parentPid),
|
|
539
541
|
"--quiet",
|
|
540
542
|
];
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
mkdirSync(guardLogDir, { recursive: true });
|
|
546
|
-
}
|
|
547
|
-
const guardLogPath = join(guardLogDir, "proxy-guard.log");
|
|
548
|
-
const logFd = openSync(guardLogPath, "a");
|
|
543
|
+
const workerLog = openProxyWorkerLog("proxy-guard.log");
|
|
544
|
+
if (workerLog.error) {
|
|
545
|
+
logger.debug(`[proxy] guard logging disabled: ${workerLog.error}`);
|
|
546
|
+
}
|
|
549
547
|
try {
|
|
550
548
|
const child = spawn(process.execPath, args, {
|
|
551
549
|
detached: true,
|
|
552
|
-
stdio: ["ignore",
|
|
550
|
+
stdio: ["ignore", workerLog.stdio, workerLog.stdio],
|
|
553
551
|
});
|
|
554
552
|
child.unref();
|
|
555
553
|
return child.pid;
|
|
@@ -559,7 +557,7 @@ function spawnFailOpenGuard(host, port, parentPid) {
|
|
|
559
557
|
return undefined;
|
|
560
558
|
}
|
|
561
559
|
finally {
|
|
562
|
-
|
|
560
|
+
workerLog.close();
|
|
563
561
|
}
|
|
564
562
|
}
|
|
565
563
|
function spawnProxyUpdater(host, port, parentPid) {
|
|
@@ -585,16 +583,14 @@ function spawnProxyUpdater(host, port, parentPid) {
|
|
|
585
583
|
"--updater-only",
|
|
586
584
|
"--quiet",
|
|
587
585
|
];
|
|
588
|
-
const
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
mkdirSync(logsDir, { recursive: true });
|
|
586
|
+
const workerLog = openProxyWorkerLog("proxy-updater.log");
|
|
587
|
+
if (workerLog.error) {
|
|
588
|
+
logger.always(`[proxy] updater logging disabled: ${workerLog.error}`);
|
|
592
589
|
}
|
|
593
|
-
const logFd = openSync(join(logsDir, "proxy-updater.log"), "a");
|
|
594
590
|
try {
|
|
595
591
|
const child = spawn(process.execPath, args, {
|
|
596
592
|
detached: true,
|
|
597
|
-
stdio: ["ignore",
|
|
593
|
+
stdio: ["ignore", workerLog.stdio, workerLog.stdio],
|
|
598
594
|
env: {
|
|
599
595
|
...process.env,
|
|
600
596
|
NEUROLINK_PROXY_UPDATE_CONTROL_TOKEN: PROXY_UPDATE_CONTROL_TOKEN,
|
|
@@ -614,7 +610,7 @@ function spawnProxyUpdater(host, port, parentPid) {
|
|
|
614
610
|
return undefined;
|
|
615
611
|
}
|
|
616
612
|
finally {
|
|
617
|
-
|
|
613
|
+
workerLog.close();
|
|
618
614
|
}
|
|
619
615
|
}
|
|
620
616
|
async function runProxyTelemetryManager(command) {
|
|
@@ -1065,7 +1061,8 @@ export async function createProxyStartApp(params) {
|
|
|
1065
1061
|
metadata.stream = stream === "stream";
|
|
1066
1062
|
metadata.toolCount = toolCount;
|
|
1067
1063
|
}
|
|
1068
|
-
|
|
1064
|
+
const logModel = sanitizeForLog(String(model));
|
|
1065
|
+
logger.always(`[proxy] ${c.req.method} ${c.req.path} → model=${logModel} ${stream} tools=${toolCount}`);
|
|
1069
1066
|
const ctx = {
|
|
1070
1067
|
requestId: metadata?.requestId ?? crypto.randomUUID(),
|
|
1071
1068
|
method: c.req.method,
|
|
@@ -112,7 +112,10 @@ export class StateFileManager {
|
|
|
112
112
|
if (!fs.existsSync(dir)) {
|
|
113
113
|
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
114
114
|
}
|
|
115
|
-
fs.writeFileSync(this.filePath, JSON.stringify(state, null, 2)
|
|
115
|
+
fs.writeFileSync(this.filePath, JSON.stringify(state, null, 2), {
|
|
116
|
+
mode: 0o600,
|
|
117
|
+
});
|
|
118
|
+
fs.chmodSync(this.filePath, 0o600);
|
|
116
119
|
}
|
|
117
120
|
/**
|
|
118
121
|
* Load state from the state file
|
|
@@ -337,7 +337,7 @@ function processEvent(acc, event) {
|
|
|
337
337
|
const message = nestedError?.message ?? payload.message;
|
|
338
338
|
acc.streamErrorMessage =
|
|
339
339
|
typeof message === "string" && message.trim()
|
|
340
|
-
? message.trim()
|
|
340
|
+
? truncateString(message.trim(), MAX_EVENT_DATA_BYTES)
|
|
341
341
|
: truncateString(event.data, MAX_EVENT_DATA_BYTES);
|
|
342
342
|
}
|
|
343
343
|
switch (event.event) {
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { chmodSync, closeSync, existsSync, mkdirSync, openSync, statSync, } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { basename, join } from "node:path";
|
|
4
|
+
/** Open a restrictive worker log without making proxy startup depend on it. */
|
|
5
|
+
export function openProxyWorkerLog(filename, logDir = join(homedir(), ".neurolink", "logs")) {
|
|
6
|
+
let fd;
|
|
7
|
+
try {
|
|
8
|
+
if (basename(filename) !== filename) {
|
|
9
|
+
throw new Error("worker log filename must not contain a path");
|
|
10
|
+
}
|
|
11
|
+
if (!existsSync(logDir)) {
|
|
12
|
+
mkdirSync(logDir, { recursive: true, mode: 0o700 });
|
|
13
|
+
}
|
|
14
|
+
else if (!statSync(logDir).isDirectory()) {
|
|
15
|
+
throw new Error("worker log path exists but is not a directory");
|
|
16
|
+
}
|
|
17
|
+
chmodSync(logDir, 0o700);
|
|
18
|
+
const path = join(logDir, filename);
|
|
19
|
+
fd = openSync(path, "a", 0o600);
|
|
20
|
+
chmodSync(path, 0o600);
|
|
21
|
+
return {
|
|
22
|
+
stdio: fd,
|
|
23
|
+
close: () => closeSync(fd),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
if (fd !== undefined) {
|
|
28
|
+
try {
|
|
29
|
+
closeSync(fd);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// The log is optional and startup must remain fail-open.
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
stdio: "ignore",
|
|
37
|
+
close: () => undefined,
|
|
38
|
+
error: error instanceof Error ? error.message : String(error),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=workerLog.js.map
|
|
@@ -12,6 +12,11 @@
|
|
|
12
12
|
* provider/model pairs (e.g. "gpt-4o" -> vertex/gemini-2.5-pro).
|
|
13
13
|
*/
|
|
14
14
|
import type { ModelRouterInterface, ProxyRuntimeConfigProvider, RouteGroup } from "../../types/index.js";
|
|
15
|
+
declare function resolveStreamCancellationLifecycle(terminalStreamError: string | undefined): {
|
|
16
|
+
status: number;
|
|
17
|
+
errorType: string;
|
|
18
|
+
errorMessage: string;
|
|
19
|
+
};
|
|
15
20
|
/**
|
|
16
21
|
* Create OpenAI-compatible proxy routes.
|
|
17
22
|
*
|
|
@@ -27,3 +32,7 @@ import type { ModelRouterInterface, ProxyRuntimeConfigProvider, RouteGroup } fro
|
|
|
27
32
|
* @returns RouteGroup with OpenAI-compatible endpoints.
|
|
28
33
|
*/
|
|
29
34
|
export declare function createOpenAIProxyRoutes(modelRouter?: ModelRouterInterface, basePath?: string, loopbackPort?: number, runtimeConfigProvider?: ProxyRuntimeConfigProvider): RouteGroup;
|
|
35
|
+
export declare const __testHooks: {
|
|
36
|
+
resolveStreamCancellationLifecycle: typeof resolveStreamCancellationLifecycle;
|
|
37
|
+
};
|
|
38
|
+
export {};
|
|
@@ -27,6 +27,19 @@ const LOOPBACK_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes — long enough for slow
|
|
|
27
27
|
// `createOpenAIProxyRoutes`'s third argument when the actual listener port is
|
|
28
28
|
// known (e.g. when started from the CLI handler).
|
|
29
29
|
const DEFAULT_LOOPBACK_PORT = 55669;
|
|
30
|
+
function resolveStreamCancellationLifecycle(terminalStreamError) {
|
|
31
|
+
return terminalStreamError
|
|
32
|
+
? {
|
|
33
|
+
status: 502,
|
|
34
|
+
errorType: "loopback_stream_error",
|
|
35
|
+
errorMessage: terminalStreamError,
|
|
36
|
+
}
|
|
37
|
+
: {
|
|
38
|
+
status: 499,
|
|
39
|
+
errorType: "client_cancelled",
|
|
40
|
+
errorMessage: "Client cancelled the OpenAI-compatible stream",
|
|
41
|
+
};
|
|
42
|
+
}
|
|
30
43
|
/**
|
|
31
44
|
* Build an OpenAI-shaped error as a typed Response with the intended status.
|
|
32
45
|
*
|
|
@@ -188,7 +201,8 @@ async function handleOpenAIToAnthropicBridge(args) {
|
|
|
188
201
|
await reader.cancel(reason);
|
|
189
202
|
}
|
|
190
203
|
finally {
|
|
191
|
-
|
|
204
|
+
const cancellation = resolveStreamCancellationLifecycle(terminalStreamError);
|
|
205
|
+
await finishLifecycle(cancellation.status, cancellation.errorType, cancellation.errorMessage);
|
|
192
206
|
}
|
|
193
207
|
},
|
|
194
208
|
});
|
|
@@ -381,4 +395,5 @@ export function createOpenAIProxyRoutes(modelRouter, basePath = "", loopbackPort
|
|
|
381
395
|
],
|
|
382
396
|
};
|
|
383
397
|
}
|
|
398
|
+
export const __testHooks = { resolveStreamCancellationLifecycle };
|
|
384
399
|
//# sourceMappingURL=openaiProxyRoutes.js.map
|
|
@@ -337,7 +337,7 @@ function processEvent(acc, event) {
|
|
|
337
337
|
const message = nestedError?.message ?? payload.message;
|
|
338
338
|
acc.streamErrorMessage =
|
|
339
339
|
typeof message === "string" && message.trim()
|
|
340
|
-
? message.trim()
|
|
340
|
+
? truncateString(message.trim(), MAX_EVENT_DATA_BYTES)
|
|
341
341
|
: truncateString(event.data, MAX_EVENT_DATA_BYTES);
|
|
342
342
|
}
|
|
343
343
|
switch (event.event) {
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { chmodSync, closeSync, existsSync, mkdirSync, openSync, statSync, } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { basename, join } from "node:path";
|
|
4
|
+
/** Open a restrictive worker log without making proxy startup depend on it. */
|
|
5
|
+
export function openProxyWorkerLog(filename, logDir = join(homedir(), ".neurolink", "logs")) {
|
|
6
|
+
let fd;
|
|
7
|
+
try {
|
|
8
|
+
if (basename(filename) !== filename) {
|
|
9
|
+
throw new Error("worker log filename must not contain a path");
|
|
10
|
+
}
|
|
11
|
+
if (!existsSync(logDir)) {
|
|
12
|
+
mkdirSync(logDir, { recursive: true, mode: 0o700 });
|
|
13
|
+
}
|
|
14
|
+
else if (!statSync(logDir).isDirectory()) {
|
|
15
|
+
throw new Error("worker log path exists but is not a directory");
|
|
16
|
+
}
|
|
17
|
+
chmodSync(logDir, 0o700);
|
|
18
|
+
const path = join(logDir, filename);
|
|
19
|
+
fd = openSync(path, "a", 0o600);
|
|
20
|
+
chmodSync(path, 0o600);
|
|
21
|
+
return {
|
|
22
|
+
stdio: fd,
|
|
23
|
+
close: () => closeSync(fd),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
if (fd !== undefined) {
|
|
28
|
+
try {
|
|
29
|
+
closeSync(fd);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// The log is optional and startup must remain fail-open.
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
stdio: "ignore",
|
|
37
|
+
close: () => undefined,
|
|
38
|
+
error: error instanceof Error ? error.message : String(error),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -12,6 +12,11 @@
|
|
|
12
12
|
* provider/model pairs (e.g. "gpt-4o" -> vertex/gemini-2.5-pro).
|
|
13
13
|
*/
|
|
14
14
|
import type { ModelRouterInterface, ProxyRuntimeConfigProvider, RouteGroup } from "../../types/index.js";
|
|
15
|
+
declare function resolveStreamCancellationLifecycle(terminalStreamError: string | undefined): {
|
|
16
|
+
status: number;
|
|
17
|
+
errorType: string;
|
|
18
|
+
errorMessage: string;
|
|
19
|
+
};
|
|
15
20
|
/**
|
|
16
21
|
* Create OpenAI-compatible proxy routes.
|
|
17
22
|
*
|
|
@@ -27,3 +32,7 @@ import type { ModelRouterInterface, ProxyRuntimeConfigProvider, RouteGroup } fro
|
|
|
27
32
|
* @returns RouteGroup with OpenAI-compatible endpoints.
|
|
28
33
|
*/
|
|
29
34
|
export declare function createOpenAIProxyRoutes(modelRouter?: ModelRouterInterface, basePath?: string, loopbackPort?: number, runtimeConfigProvider?: ProxyRuntimeConfigProvider): RouteGroup;
|
|
35
|
+
export declare const __testHooks: {
|
|
36
|
+
resolveStreamCancellationLifecycle: typeof resolveStreamCancellationLifecycle;
|
|
37
|
+
};
|
|
38
|
+
export {};
|
|
@@ -27,6 +27,19 @@ const LOOPBACK_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes — long enough for slow
|
|
|
27
27
|
// `createOpenAIProxyRoutes`'s third argument when the actual listener port is
|
|
28
28
|
// known (e.g. when started from the CLI handler).
|
|
29
29
|
const DEFAULT_LOOPBACK_PORT = 55669;
|
|
30
|
+
function resolveStreamCancellationLifecycle(terminalStreamError) {
|
|
31
|
+
return terminalStreamError
|
|
32
|
+
? {
|
|
33
|
+
status: 502,
|
|
34
|
+
errorType: "loopback_stream_error",
|
|
35
|
+
errorMessage: terminalStreamError,
|
|
36
|
+
}
|
|
37
|
+
: {
|
|
38
|
+
status: 499,
|
|
39
|
+
errorType: "client_cancelled",
|
|
40
|
+
errorMessage: "Client cancelled the OpenAI-compatible stream",
|
|
41
|
+
};
|
|
42
|
+
}
|
|
30
43
|
/**
|
|
31
44
|
* Build an OpenAI-shaped error as a typed Response with the intended status.
|
|
32
45
|
*
|
|
@@ -188,7 +201,8 @@ async function handleOpenAIToAnthropicBridge(args) {
|
|
|
188
201
|
await reader.cancel(reason);
|
|
189
202
|
}
|
|
190
203
|
finally {
|
|
191
|
-
|
|
204
|
+
const cancellation = resolveStreamCancellationLifecycle(terminalStreamError);
|
|
205
|
+
await finishLifecycle(cancellation.status, cancellation.errorType, cancellation.errorMessage);
|
|
192
206
|
}
|
|
193
207
|
},
|
|
194
208
|
});
|
|
@@ -381,3 +395,4 @@ export function createOpenAIProxyRoutes(modelRouter, basePath = "", loopbackPort
|
|
|
381
395
|
],
|
|
382
396
|
};
|
|
383
397
|
}
|
|
398
|
+
export const __testHooks = { resolveStreamCancellationLifecycle };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/neurolink",
|
|
3
|
-
"version": "9.93.
|
|
3
|
+
"version": "9.93.2",
|
|
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": {
|