@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/neurolink",
3
- "version": "12.14.4",
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 localSummary = await readLatestLocalSummary();
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(` latest: ${latestMicros ? formatDate(latestMicros) : "missing"}`);
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 (!localSummary || !localSummaryMicros) {
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(0, (nowMicros - localSummaryMicros) / 1_000_000);
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(` ${stream.name}: ${status} (${formatAgeSeconds(deltaSeconds)})`);
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
+ }