@juspay/neurolink 12.14.4 → 12.14.6
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 -370
- package/dist/cli/commands/proxy.d.ts +1 -1
- package/dist/cli/commands/proxy.js +58 -4
- package/dist/cli/commands/proxyRestart.d.ts +3 -0
- package/dist/cli/commands/proxyRestart.js +88 -0
- package/dist/cli/parser.js +3 -1
- package/dist/proxy/bodyCaptureProcessing.d.ts +1 -0
- package/dist/proxy/bodyCaptureProcessing.js +19 -4
- package/dist/proxy/bodyCaptureWorker.js +35 -13
- package/dist/proxy/otelLogSink.d.ts +22 -1
- package/dist/proxy/otelLogSink.js +247 -8
- package/dist/proxy/proxyActivity.d.ts +4 -2
- package/dist/proxy/proxyActivity.js +20 -1
- package/dist/proxy/requestLogger.d.ts +1 -0
- package/dist/proxy/requestLogger.js +53 -15
- package/dist/proxy/restartControl.d.ts +12 -0
- package/dist/proxy/restartControl.js +283 -0
- package/dist/proxy/rollingWorkerSupervisor.js +8 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/proxy.d.ts +29 -0
- package/dist/types/proxyRestart.d.ts +49 -0
- package/dist/types/proxyRestart.js +1 -0
- package/docs-site/static/search-index.json +1 -1
- package/package.json +2 -1
- package/scripts/observability/check-proxy-telemetry.mjs +20 -7
- package/scripts/observability/query-proxy-history.mjs +193 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/neurolink",
|
|
3
|
-
"version": "12.14.
|
|
3
|
+
"version": "12.14.6",
|
|
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": {
|
|
@@ -147,6 +147,7 @@
|
|
|
147
147
|
"test:proxy": "pnpm exec tsx test/continuous-test-suite-proxy.ts",
|
|
148
148
|
"test:proxy-connect-retry": "pnpm exec tsx test/continuous-test-suite-proxy-connect-retry.ts",
|
|
149
149
|
"test:proxy-telemetry": "pnpm exec tsx test/continuous-test-suite-proxy-telemetry.ts",
|
|
150
|
+
"test:proxy-restart": "pnpm exec tsx test/continuous-test-suite-proxy-restart.ts",
|
|
150
151
|
"test:codex": "pnpm exec tsx test/continuous-test-suite-codex.ts",
|
|
151
152
|
"test:bugfixes": "pnpm exec tsx test/continuous-test-suite-bugfixes.ts",
|
|
152
153
|
"test:json-e2e": "pnpm exec tsx test/continuous-test-suite-json-e2e.ts",
|
|
@@ -13,12 +13,12 @@ const DEFAULT_MAX_LAG_SECONDS = 900;
|
|
|
13
13
|
const STREAMS_TO_CHECK = [
|
|
14
14
|
{
|
|
15
15
|
type: "logs",
|
|
16
|
-
name: "neurolink_proxy",
|
|
16
|
+
name: process.env.NEUROLINK_PROXY_STREAM_HEADER || "neurolink_proxy",
|
|
17
17
|
label: "OpenObserve logs",
|
|
18
18
|
},
|
|
19
19
|
{
|
|
20
20
|
type: "traces",
|
|
21
|
-
name: "neurolink_proxy",
|
|
21
|
+
name: process.env.NEUROLINK_PROXY_STREAM_HEADER || "neurolink_proxy",
|
|
22
22
|
label: "OpenObserve traces",
|
|
23
23
|
},
|
|
24
24
|
{
|
|
@@ -74,6 +74,7 @@ async function fetchStreams(type) {
|
|
|
74
74
|
);
|
|
75
75
|
|
|
76
76
|
const response = await fetch(url, {
|
|
77
|
+
signal: globalThis.AbortSignal.timeout(15_000),
|
|
77
78
|
headers: {
|
|
78
79
|
Authorization: getAuthHeader(),
|
|
79
80
|
},
|
|
@@ -156,7 +157,8 @@ async function main() {
|
|
|
156
157
|
),
|
|
157
158
|
);
|
|
158
159
|
|
|
159
|
-
const
|
|
160
|
+
const otelOnly = process.env.NEUROLINK_PROXY_LOG_SINK === "otel";
|
|
161
|
+
const localSummary = otelOnly ? null : await readLatestLocalSummary();
|
|
160
162
|
const localSummaryMicros = localSummary?.timestamp
|
|
161
163
|
? new Date(localSummary.timestamp).getTime() * 1000
|
|
162
164
|
: null;
|
|
@@ -186,18 +188,27 @@ async function main() {
|
|
|
186
188
|
|
|
187
189
|
console.log(`${stream.label}: ${status}`);
|
|
188
190
|
console.log(` stream: ${stream.name}`);
|
|
189
|
-
console.log(
|
|
191
|
+
console.log(
|
|
192
|
+
` latest: ${latestMicros ? formatDate(latestMicros) : "missing"}`,
|
|
193
|
+
);
|
|
190
194
|
console.log(` age: ${formatAgeSeconds(ageSeconds)}`);
|
|
191
195
|
console.log(` docs: ${item?.stats?.doc_num ?? 0}`);
|
|
192
196
|
}
|
|
193
197
|
|
|
194
198
|
console.log("");
|
|
195
199
|
console.log("Local proxy summary log:");
|
|
196
|
-
if (
|
|
200
|
+
if (otelOnly) {
|
|
201
|
+
console.log(
|
|
202
|
+
" disabled (OTel-only mode); local file freshness is not a delivery check",
|
|
203
|
+
);
|
|
204
|
+
} else if (!localSummary || !localSummaryMicros) {
|
|
197
205
|
console.log(" missing");
|
|
198
206
|
hasProblem = true;
|
|
199
207
|
} else {
|
|
200
|
-
const ageSeconds = Math.max(
|
|
208
|
+
const ageSeconds = Math.max(
|
|
209
|
+
0,
|
|
210
|
+
(nowMicros - localSummaryMicros) / 1_000_000,
|
|
211
|
+
);
|
|
201
212
|
console.log(` latest: ${localSummary.timestamp}`);
|
|
202
213
|
console.log(` age: ${formatAgeSeconds(ageSeconds)}`);
|
|
203
214
|
console.log(` requestId: ${localSummary.requestId}`);
|
|
@@ -220,7 +231,9 @@ async function main() {
|
|
|
220
231
|
if (status !== "fresh") {
|
|
221
232
|
hasProblem = true;
|
|
222
233
|
}
|
|
223
|
-
console.log(
|
|
234
|
+
console.log(
|
|
235
|
+
` ${stream.name}: ${status} (${formatAgeSeconds(deltaSeconds)})`,
|
|
236
|
+
);
|
|
224
237
|
}
|
|
225
238
|
}
|
|
226
239
|
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/** Bounded, deterministic OpenObserve history queries; never accept partial data. */
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
|
|
5
|
+
const KINDS = new Set([
|
|
6
|
+
"request_final",
|
|
7
|
+
"attempt",
|
|
8
|
+
"lifecycle",
|
|
9
|
+
"supervisor",
|
|
10
|
+
"body_capture_index",
|
|
11
|
+
"stream_error",
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Query metadata in small windows; body chunks require a targeted lookup.
|
|
16
|
+
* @param {{ baseUrl: string, organization?: string, stream?: string, authorization?: string, startTime: number, endTime: number, kind?: string, maxRows?: number, fetchImpl?: typeof fetch }} options
|
|
17
|
+
*/
|
|
18
|
+
export async function queryProxyHistory({
|
|
19
|
+
baseUrl,
|
|
20
|
+
organization = "default",
|
|
21
|
+
stream = "neurolink_proxy",
|
|
22
|
+
authorization,
|
|
23
|
+
startTime,
|
|
24
|
+
endTime,
|
|
25
|
+
kind = "request_final",
|
|
26
|
+
maxRows = 10_000,
|
|
27
|
+
fetchImpl = fetch,
|
|
28
|
+
}) {
|
|
29
|
+
if (
|
|
30
|
+
!/^[a-zA-Z0-9_-]+$/.test(organization) ||
|
|
31
|
+
!/^[a-zA-Z0-9_]+$/.test(stream) ||
|
|
32
|
+
!KINDS.has(kind)
|
|
33
|
+
) {
|
|
34
|
+
throw new Error("Invalid organization, stream or metadata record kind");
|
|
35
|
+
}
|
|
36
|
+
if (
|
|
37
|
+
!Number.isSafeInteger(startTime) ||
|
|
38
|
+
!Number.isSafeInteger(endTime) ||
|
|
39
|
+
startTime >= endTime ||
|
|
40
|
+
!Number.isSafeInteger(maxRows) ||
|
|
41
|
+
maxRows < 1 ||
|
|
42
|
+
maxRows > 100_000
|
|
43
|
+
) {
|
|
44
|
+
throw new Error(
|
|
45
|
+
"Provide an increasing microsecond time range and maxRows between 1 and 100000",
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
const endpoint = new URL(`/api/${organization}/_search?type=logs`, baseUrl);
|
|
49
|
+
const loopback = ["localhost", "127.0.0.1", "[::1]"].includes(endpoint.hostname);
|
|
50
|
+
if (authorization && endpoint.protocol !== "https:" && !(
|
|
51
|
+
endpoint.protocol === "http:" && loopback
|
|
52
|
+
)) {
|
|
53
|
+
throw new Error("Credentialed OpenObserve queries require HTTPS outside loopback");
|
|
54
|
+
}
|
|
55
|
+
/** @type {Array<{start: number, endExclusive: number, offset: number, partial: boolean, tookMs?: number}>} */
|
|
56
|
+
const queries = [];
|
|
57
|
+
let rowsRead = 0;
|
|
58
|
+
/** @param {number} start @param {number} end @returns {Promise<Array<Record<string, unknown>>>} */
|
|
59
|
+
async function readWindow(start, end) {
|
|
60
|
+
const rows = [];
|
|
61
|
+
for (let offset = 0; ; offset += 200) {
|
|
62
|
+
if (queries.length >= 512) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
"History exceeds the 512-query budget; narrow the interval",
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
const response = await fetchImpl(endpoint, {
|
|
68
|
+
method: "POST",
|
|
69
|
+
redirect: authorization ? "error" : "follow",
|
|
70
|
+
signal: globalThis.AbortSignal.timeout(30_000),
|
|
71
|
+
headers: {
|
|
72
|
+
"Content-Type": "application/json",
|
|
73
|
+
...(authorization ? { Authorization: authorization } : {}),
|
|
74
|
+
},
|
|
75
|
+
body: JSON.stringify({
|
|
76
|
+
query: {
|
|
77
|
+
// The body is small metadata, not arbitrary request/response chunks.
|
|
78
|
+
// Include a secondary key: timestamp-only paging loses equal-time events.
|
|
79
|
+
sql: `SELECT _timestamp, service_instance_id, request_id, body FROM "${stream}" WHERE proxy_record_kind='${kind}' ORDER BY _timestamp ASC, service_instance_id ASC, request_id ASC, body ASC`,
|
|
80
|
+
start_time: start,
|
|
81
|
+
end_time: end - 1,
|
|
82
|
+
from: offset,
|
|
83
|
+
size: 200,
|
|
84
|
+
},
|
|
85
|
+
}),
|
|
86
|
+
});
|
|
87
|
+
if (!response.ok) {
|
|
88
|
+
throw new Error(`OpenObserve search failed: HTTP ${response.status}`);
|
|
89
|
+
}
|
|
90
|
+
const result = await response.json();
|
|
91
|
+
const partial =
|
|
92
|
+
result.is_partial === true ||
|
|
93
|
+
(Array.isArray(result.function_error)
|
|
94
|
+
? result.function_error.length > 0
|
|
95
|
+
: Boolean(result.function_error));
|
|
96
|
+
queries.push({
|
|
97
|
+
start,
|
|
98
|
+
endExclusive: end,
|
|
99
|
+
offset,
|
|
100
|
+
partial,
|
|
101
|
+
tookMs: result.took,
|
|
102
|
+
});
|
|
103
|
+
if (partial) {
|
|
104
|
+
// Discard this window's pages before splitting, so no page is duplicated.
|
|
105
|
+
if (end - start <= 10_000_000) {
|
|
106
|
+
throw new Error(
|
|
107
|
+
"OpenObserve still returned partial data in a 10-second window; no complete report can be produced",
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
rowsRead -= rows.length;
|
|
111
|
+
const middle = Math.floor((start + end) / 2);
|
|
112
|
+
return [
|
|
113
|
+
...(await readWindow(start, middle)),
|
|
114
|
+
...(await readWindow(middle, end)),
|
|
115
|
+
];
|
|
116
|
+
}
|
|
117
|
+
if (!Array.isArray(result.hits)) {
|
|
118
|
+
throw new Error("OpenObserve search omitted hits");
|
|
119
|
+
}
|
|
120
|
+
rowsRead += result.hits.length;
|
|
121
|
+
if (rowsRead > maxRows) {
|
|
122
|
+
throw new Error(
|
|
123
|
+
`History exceeds the explicit ${maxRows}-row bound; narrow the interval or increase --max-rows`,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
rows.push(...result.hits);
|
|
127
|
+
if (result.hits.length < 200) {
|
|
128
|
+
return rows;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const records = [];
|
|
133
|
+
for (let start = startTime; start < endTime; start += 600_000_000) {
|
|
134
|
+
records.push(
|
|
135
|
+
...(await readWindow(start, Math.min(start + 600_000_000, endTime))),
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
startTime,
|
|
140
|
+
endTimeExclusive: endTime,
|
|
141
|
+
kind,
|
|
142
|
+
complete: true,
|
|
143
|
+
recordCount: records.length,
|
|
144
|
+
queries,
|
|
145
|
+
records,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function main() {
|
|
150
|
+
const args = process.argv.slice(2);
|
|
151
|
+
/** @param {string} name */
|
|
152
|
+
const value = (name) => {
|
|
153
|
+
const index = args.indexOf(name);
|
|
154
|
+
return index < 0 ? undefined : args[index + 1];
|
|
155
|
+
};
|
|
156
|
+
if (args.includes("--help")) {
|
|
157
|
+
console.log(
|
|
158
|
+
"Usage: node scripts/observability/query-proxy-history.mjs --since ISO_DATE [--until ISO_DATE] [--kind request_final|attempt|lifecycle|supervisor|body_capture_index|stream_error] [--max-rows 10000]",
|
|
159
|
+
);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const since = Date.parse(value("--since") ?? "");
|
|
163
|
+
const untilValue = value("--until");
|
|
164
|
+
const until = untilValue ? Date.parse(untilValue) : Date.now();
|
|
165
|
+
const user = process.env.NEUROLINK_OPENOBSERVE_USER;
|
|
166
|
+
const password = process.env.NEUROLINK_OPENOBSERVE_PASSWORD;
|
|
167
|
+
const authorization =
|
|
168
|
+
process.env.NEUROLINK_OPENOBSERVE_BASIC_AUTH ??
|
|
169
|
+
(user && password
|
|
170
|
+
? `Basic ${Buffer.from(`${user}:${password}`).toString("base64")}`
|
|
171
|
+
: undefined);
|
|
172
|
+
const report = await queryProxyHistory({
|
|
173
|
+
baseUrl: process.env.NEUROLINK_OPENOBSERVE_URL ?? "http://127.0.0.1:5080",
|
|
174
|
+
organization: process.env.NEUROLINK_OPENOBSERVE_ORG ?? "default",
|
|
175
|
+
stream: process.env.NEUROLINK_PROXY_STREAM_HEADER ?? "neurolink_proxy",
|
|
176
|
+
authorization,
|
|
177
|
+
startTime: since * 1000,
|
|
178
|
+
endTime: until * 1000,
|
|
179
|
+
kind: value("--kind") ?? "request_final",
|
|
180
|
+
maxRows: Number(value("--max-rows") ?? 10_000),
|
|
181
|
+
});
|
|
182
|
+
console.log(JSON.stringify(report, null, 2));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (
|
|
186
|
+
process.argv[1] &&
|
|
187
|
+
import.meta.url === pathToFileURL(process.argv[1]).href
|
|
188
|
+
) {
|
|
189
|
+
main().catch((error) => {
|
|
190
|
+
console.error(error.message);
|
|
191
|
+
process.exitCode = 1;
|
|
192
|
+
});
|
|
193
|
+
}
|