@juspay/neurolink 9.41.0 → 9.42.1

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 (212) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +7 -1
  3. package/dist/auth/anthropicOAuth.d.ts +18 -3
  4. package/dist/auth/anthropicOAuth.js +149 -4
  5. package/dist/auth/providers/firebase.js +5 -1
  6. package/dist/auth/providers/jwt.js +5 -1
  7. package/dist/auth/providers/workos.js +5 -1
  8. package/dist/auth/sessionManager.d.ts +1 -1
  9. package/dist/auth/sessionManager.js +58 -27
  10. package/dist/browser/neurolink.min.js +354 -334
  11. package/dist/cli/commands/mcp.d.ts +6 -0
  12. package/dist/cli/commands/mcp.js +188 -181
  13. package/dist/cli/commands/proxy.d.ts +2 -1
  14. package/dist/cli/commands/proxy.js +713 -431
  15. package/dist/cli/commands/task.js +3 -0
  16. package/dist/cli/factories/commandFactory.d.ts +2 -0
  17. package/dist/cli/factories/commandFactory.js +38 -0
  18. package/dist/cli/parser.js +4 -3
  19. package/dist/client/aiSdkAdapter.js +3 -0
  20. package/dist/client/streamingClient.js +30 -10
  21. package/dist/core/baseProvider.d.ts +6 -1
  22. package/dist/core/baseProvider.js +208 -230
  23. package/dist/core/factory.d.ts +3 -0
  24. package/dist/core/factory.js +138 -188
  25. package/dist/core/modules/GenerationHandler.js +3 -2
  26. package/dist/core/redisConversationMemoryManager.js +7 -3
  27. package/dist/evaluation/BatchEvaluator.js +4 -1
  28. package/dist/evaluation/hooks/observabilityHooks.js +5 -3
  29. package/dist/evaluation/pipeline/evaluationPipeline.d.ts +3 -2
  30. package/dist/evaluation/pipeline/evaluationPipeline.js +24 -9
  31. package/dist/evaluation/pipeline/strategies/batchStrategy.js +6 -3
  32. package/dist/evaluation/pipeline/strategies/samplingStrategy.js +18 -10
  33. package/dist/evaluation/scorers/scorerRegistry.d.ts +3 -0
  34. package/dist/evaluation/scorers/scorerRegistry.js +353 -282
  35. package/dist/lib/auth/anthropicOAuth.d.ts +18 -3
  36. package/dist/lib/auth/anthropicOAuth.js +149 -4
  37. package/dist/lib/auth/providers/firebase.js +5 -1
  38. package/dist/lib/auth/providers/jwt.js +5 -1
  39. package/dist/lib/auth/providers/workos.js +5 -1
  40. package/dist/lib/auth/sessionManager.d.ts +1 -1
  41. package/dist/lib/auth/sessionManager.js +58 -27
  42. package/dist/lib/client/aiSdkAdapter.js +3 -0
  43. package/dist/lib/client/streamingClient.js +30 -10
  44. package/dist/lib/core/baseProvider.d.ts +6 -1
  45. package/dist/lib/core/baseProvider.js +208 -230
  46. package/dist/lib/core/factory.d.ts +3 -0
  47. package/dist/lib/core/factory.js +138 -188
  48. package/dist/lib/core/modules/GenerationHandler.js +3 -2
  49. package/dist/lib/core/redisConversationMemoryManager.js +7 -3
  50. package/dist/lib/evaluation/BatchEvaluator.js +4 -1
  51. package/dist/lib/evaluation/hooks/observabilityHooks.js +5 -3
  52. package/dist/lib/evaluation/pipeline/evaluationPipeline.d.ts +3 -2
  53. package/dist/lib/evaluation/pipeline/evaluationPipeline.js +24 -9
  54. package/dist/lib/evaluation/pipeline/strategies/batchStrategy.js +6 -3
  55. package/dist/lib/evaluation/pipeline/strategies/samplingStrategy.js +18 -10
  56. package/dist/lib/evaluation/scorers/scorerRegistry.d.ts +3 -0
  57. package/dist/lib/evaluation/scorers/scorerRegistry.js +353 -282
  58. package/dist/lib/mcp/toolRegistry.d.ts +2 -0
  59. package/dist/lib/mcp/toolRegistry.js +32 -31
  60. package/dist/lib/neurolink.d.ts +41 -2
  61. package/dist/lib/neurolink.js +1616 -1681
  62. package/dist/lib/observability/otelBridge.d.ts +2 -2
  63. package/dist/lib/observability/otelBridge.js +12 -3
  64. package/dist/lib/providers/amazonBedrock.js +2 -4
  65. package/dist/lib/providers/anthropic.d.ts +9 -5
  66. package/dist/lib/providers/anthropic.js +19 -14
  67. package/dist/lib/providers/anthropicBaseProvider.d.ts +3 -3
  68. package/dist/lib/providers/anthropicBaseProvider.js +5 -4
  69. package/dist/lib/providers/azureOpenai.d.ts +1 -1
  70. package/dist/lib/providers/azureOpenai.js +5 -4
  71. package/dist/lib/providers/googleAiStudio.js +30 -6
  72. package/dist/lib/providers/googleVertex.d.ts +10 -0
  73. package/dist/lib/providers/googleVertex.js +437 -423
  74. package/dist/lib/providers/huggingFace.d.ts +3 -3
  75. package/dist/lib/providers/huggingFace.js +6 -8
  76. package/dist/lib/providers/litellm.d.ts +1 -0
  77. package/dist/lib/providers/litellm.js +76 -55
  78. package/dist/lib/providers/mistral.js +2 -1
  79. package/dist/lib/providers/ollama.js +93 -23
  80. package/dist/lib/providers/openAI.d.ts +2 -0
  81. package/dist/lib/providers/openAI.js +141 -141
  82. package/dist/lib/providers/openRouter.js +2 -1
  83. package/dist/lib/providers/openaiCompatible.d.ts +4 -4
  84. package/dist/lib/providers/openaiCompatible.js +4 -4
  85. package/dist/lib/proxy/claudeFormat.d.ts +3 -2
  86. package/dist/lib/proxy/claudeFormat.js +27 -14
  87. package/dist/lib/proxy/cloaking/plugins/sessionIdentity.d.ts +2 -6
  88. package/dist/lib/proxy/cloaking/plugins/sessionIdentity.js +9 -33
  89. package/dist/lib/proxy/modelRouter.js +3 -0
  90. package/dist/lib/proxy/oauthFetch.d.ts +1 -1
  91. package/dist/lib/proxy/oauthFetch.js +289 -316
  92. package/dist/lib/proxy/proxyConfig.js +46 -24
  93. package/dist/lib/proxy/proxyEnv.d.ts +19 -0
  94. package/dist/lib/proxy/proxyEnv.js +73 -0
  95. package/dist/lib/proxy/proxyFetch.js +291 -217
  96. package/dist/lib/proxy/proxyTracer.d.ts +133 -0
  97. package/dist/lib/proxy/proxyTracer.js +645 -0
  98. package/dist/lib/proxy/rawStreamCapture.d.ts +10 -0
  99. package/dist/lib/proxy/rawStreamCapture.js +83 -0
  100. package/dist/lib/proxy/requestLogger.d.ts +32 -5
  101. package/dist/lib/proxy/requestLogger.js +503 -47
  102. package/dist/lib/proxy/sseInterceptor.d.ts +97 -0
  103. package/dist/lib/proxy/sseInterceptor.js +427 -0
  104. package/dist/lib/proxy/usageStats.d.ts +4 -3
  105. package/dist/lib/proxy/usageStats.js +25 -12
  106. package/dist/lib/rag/chunkers/MarkdownChunker.js +13 -5
  107. package/dist/lib/rag/chunking/markdownChunker.js +15 -6
  108. package/dist/lib/server/routes/claudeProxyRoutes.d.ts +17 -3
  109. package/dist/lib/server/routes/claudeProxyRoutes.js +3032 -1349
  110. package/dist/lib/services/server/ai/observability/instrumentation.d.ts +7 -1
  111. package/dist/lib/services/server/ai/observability/instrumentation.js +337 -161
  112. package/dist/lib/tasks/backends/bullmqBackend.d.ts +1 -0
  113. package/dist/lib/tasks/backends/bullmqBackend.js +35 -22
  114. package/dist/lib/tasks/store/redisTaskStore.d.ts +1 -0
  115. package/dist/lib/tasks/store/redisTaskStore.js +54 -39
  116. package/dist/lib/tasks/taskManager.d.ts +5 -0
  117. package/dist/lib/tasks/taskManager.js +158 -30
  118. package/dist/lib/telemetry/index.d.ts +2 -1
  119. package/dist/lib/telemetry/index.js +2 -1
  120. package/dist/lib/telemetry/telemetryService.d.ts +3 -0
  121. package/dist/lib/telemetry/telemetryService.js +69 -5
  122. package/dist/lib/types/cli.d.ts +10 -0
  123. package/dist/lib/types/proxyTypes.d.ts +160 -5
  124. package/dist/lib/types/streamTypes.d.ts +25 -3
  125. package/dist/lib/utils/messageBuilder.js +3 -2
  126. package/dist/lib/utils/providerHealth.d.ts +19 -0
  127. package/dist/lib/utils/providerHealth.js +279 -33
  128. package/dist/lib/utils/providerUtils.js +17 -22
  129. package/dist/lib/utils/toolChoice.d.ts +4 -0
  130. package/dist/lib/utils/toolChoice.js +7 -0
  131. package/dist/mcp/toolRegistry.d.ts +2 -0
  132. package/dist/mcp/toolRegistry.js +32 -31
  133. package/dist/neurolink.d.ts +41 -2
  134. package/dist/neurolink.js +1616 -1681
  135. package/dist/observability/otelBridge.d.ts +2 -2
  136. package/dist/observability/otelBridge.js +12 -3
  137. package/dist/providers/amazonBedrock.js +2 -4
  138. package/dist/providers/anthropic.d.ts +9 -5
  139. package/dist/providers/anthropic.js +19 -14
  140. package/dist/providers/anthropicBaseProvider.d.ts +3 -3
  141. package/dist/providers/anthropicBaseProvider.js +5 -4
  142. package/dist/providers/azureOpenai.d.ts +1 -1
  143. package/dist/providers/azureOpenai.js +5 -4
  144. package/dist/providers/googleAiStudio.js +30 -6
  145. package/dist/providers/googleVertex.d.ts +10 -0
  146. package/dist/providers/googleVertex.js +437 -423
  147. package/dist/providers/huggingFace.d.ts +3 -3
  148. package/dist/providers/huggingFace.js +6 -7
  149. package/dist/providers/litellm.d.ts +1 -0
  150. package/dist/providers/litellm.js +76 -55
  151. package/dist/providers/mistral.js +2 -1
  152. package/dist/providers/ollama.js +93 -23
  153. package/dist/providers/openAI.d.ts +2 -0
  154. package/dist/providers/openAI.js +141 -141
  155. package/dist/providers/openRouter.js +2 -1
  156. package/dist/providers/openaiCompatible.d.ts +4 -4
  157. package/dist/providers/openaiCompatible.js +4 -3
  158. package/dist/proxy/claudeFormat.d.ts +3 -2
  159. package/dist/proxy/claudeFormat.js +27 -14
  160. package/dist/proxy/cloaking/plugins/sessionIdentity.d.ts +2 -6
  161. package/dist/proxy/cloaking/plugins/sessionIdentity.js +9 -33
  162. package/dist/proxy/modelRouter.js +3 -0
  163. package/dist/proxy/oauthFetch.d.ts +1 -1
  164. package/dist/proxy/oauthFetch.js +289 -316
  165. package/dist/proxy/proxyConfig.js +46 -24
  166. package/dist/proxy/proxyEnv.d.ts +19 -0
  167. package/dist/proxy/proxyEnv.js +72 -0
  168. package/dist/proxy/proxyFetch.js +291 -217
  169. package/dist/proxy/proxyTracer.d.ts +133 -0
  170. package/dist/proxy/proxyTracer.js +644 -0
  171. package/dist/proxy/rawStreamCapture.d.ts +10 -0
  172. package/dist/proxy/rawStreamCapture.js +82 -0
  173. package/dist/proxy/requestLogger.d.ts +32 -5
  174. package/dist/proxy/requestLogger.js +503 -47
  175. package/dist/proxy/sseInterceptor.d.ts +97 -0
  176. package/dist/proxy/sseInterceptor.js +426 -0
  177. package/dist/proxy/usageStats.d.ts +4 -3
  178. package/dist/proxy/usageStats.js +25 -12
  179. package/dist/rag/chunkers/MarkdownChunker.js +13 -5
  180. package/dist/rag/chunking/markdownChunker.js +15 -6
  181. package/dist/server/routes/claudeProxyRoutes.d.ts +17 -3
  182. package/dist/server/routes/claudeProxyRoutes.js +3032 -1349
  183. package/dist/services/server/ai/observability/instrumentation.d.ts +7 -1
  184. package/dist/services/server/ai/observability/instrumentation.js +337 -161
  185. package/dist/tasks/backends/bullmqBackend.d.ts +1 -0
  186. package/dist/tasks/backends/bullmqBackend.js +35 -22
  187. package/dist/tasks/store/redisTaskStore.d.ts +1 -0
  188. package/dist/tasks/store/redisTaskStore.js +54 -39
  189. package/dist/tasks/taskManager.d.ts +5 -0
  190. package/dist/tasks/taskManager.js +158 -30
  191. package/dist/telemetry/index.d.ts +2 -1
  192. package/dist/telemetry/index.js +2 -1
  193. package/dist/telemetry/telemetryService.d.ts +3 -0
  194. package/dist/telemetry/telemetryService.js +69 -5
  195. package/dist/types/cli.d.ts +10 -0
  196. package/dist/types/proxyTypes.d.ts +160 -5
  197. package/dist/types/streamTypes.d.ts +25 -3
  198. package/dist/utils/messageBuilder.js +3 -2
  199. package/dist/utils/providerHealth.d.ts +19 -0
  200. package/dist/utils/providerHealth.js +279 -33
  201. package/dist/utils/providerUtils.js +18 -22
  202. package/dist/utils/toolChoice.d.ts +4 -0
  203. package/dist/utils/toolChoice.js +6 -0
  204. package/docs/assets/dashboards/neurolink-proxy-observability-dashboard.json +6609 -0
  205. package/docs/changelog.md +252 -0
  206. package/package.json +19 -2
  207. package/scripts/observability/check-proxy-telemetry.mjs +235 -0
  208. package/scripts/observability/docker-compose.proxy-observability.yaml +55 -0
  209. package/scripts/observability/import-openobserve-dashboard.mjs +240 -0
  210. package/scripts/observability/manage-local-openobserve.sh +215 -0
  211. package/scripts/observability/otel-collector.proxy-observability.yaml +78 -0
  212. package/scripts/observability/proxy-observability.env.example +23 -0
@@ -0,0 +1,240 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "node:fs/promises";
4
+ import path from "node:path";
5
+ import process from "node:process";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
+ const defaultDashboardPath = path.resolve(
10
+ __dirname,
11
+ "../../docs/assets/dashboards/neurolink-proxy-observability-dashboard.json",
12
+ );
13
+
14
+ function usage() {
15
+ console.error(`Usage:
16
+ node scripts/observability/import-openobserve-dashboard.mjs [options]
17
+
18
+ Options:
19
+ --dashboard <path> Dashboard JSON to import
20
+ --openobserve-url <url> OpenObserve base URL
21
+ --org <org> OpenObserve org identifier
22
+ --user <email> OpenObserve login email
23
+ --password <password> OpenObserve login password
24
+ --replace-by-title Delete older dashboards with the same title after import
25
+ --dry-run Validate config and list matching dashboards without importing
26
+ --help Show this message
27
+ `);
28
+ }
29
+
30
+ function parseArgs(argv) {
31
+ const options = {
32
+ dashboard: defaultDashboardPath,
33
+ openobserveUrl: process.env.NEUROLINK_OPENOBSERVE_URL ?? "http://localhost:5080",
34
+ org: process.env.NEUROLINK_OPENOBSERVE_ORG ?? "default",
35
+ user: process.env.NEUROLINK_OPENOBSERVE_USER ?? "root@example.com",
36
+ password:
37
+ process.env.NEUROLINK_OPENOBSERVE_PASSWORD ?? "Complexpass#123",
38
+ replaceByTitle: false,
39
+ dryRun: false,
40
+ };
41
+
42
+ for (let index = 0; index < argv.length; index += 1) {
43
+ const arg = argv[index];
44
+
45
+ switch (arg) {
46
+ case "--dashboard":
47
+ options.dashboard = argv[index + 1];
48
+ index += 1;
49
+ break;
50
+ case "--openobserve-url":
51
+ options.openobserveUrl = argv[index + 1];
52
+ index += 1;
53
+ break;
54
+ case "--org":
55
+ options.org = argv[index + 1];
56
+ index += 1;
57
+ break;
58
+ case "--user":
59
+ options.user = argv[index + 1];
60
+ index += 1;
61
+ break;
62
+ case "--password":
63
+ options.password = argv[index + 1];
64
+ index += 1;
65
+ break;
66
+ case "--replace-by-title":
67
+ options.replaceByTitle = true;
68
+ break;
69
+ case "--dry-run":
70
+ options.dryRun = true;
71
+ break;
72
+ case "--help":
73
+ usage();
74
+ process.exit(0);
75
+ break; // satisfy noFallthroughSwitchClause
76
+ default:
77
+ throw new Error(`Unknown argument: ${arg}`);
78
+ }
79
+ }
80
+
81
+ return options;
82
+ }
83
+
84
+ async function readDashboardTemplate(filePath) {
85
+ const raw = JSON.parse(await fs.readFile(filePath, "utf8"));
86
+ const template = structuredClone(raw.v5 ?? raw);
87
+
88
+ delete template.dashboardId;
89
+ delete template.owner;
90
+ delete template.created;
91
+
92
+ if (!template.title) {
93
+ throw new Error(`Dashboard template ${filePath} is missing v5.title`);
94
+ }
95
+
96
+ return template;
97
+ }
98
+
99
+ function normalizeDashboardList(payload) {
100
+ if (Array.isArray(payload)) {
101
+ return payload;
102
+ }
103
+
104
+ if (Array.isArray(payload?.dashboards)) {
105
+ return payload.dashboards;
106
+ }
107
+
108
+ if (Array.isArray(payload?.data)) {
109
+ return payload.data;
110
+ }
111
+
112
+ return [];
113
+ }
114
+
115
+ function dashboardIdOf(item) {
116
+ return item?.dashboardId ?? item?.v5?.dashboardId ?? null;
117
+ }
118
+
119
+ function dashboardTitleOf(item) {
120
+ return item?.title ?? item?.v5?.title ?? null;
121
+ }
122
+
123
+ async function requestJson(url, { user, password, ...init }) {
124
+ const response = await fetch(url, {
125
+ ...init,
126
+ headers: {
127
+ Authorization: `Basic ${Buffer.from(`${user}:${password}`).toString("base64")}`,
128
+ "Content-Type": "application/json",
129
+ ...(init.headers ?? {}),
130
+ },
131
+ });
132
+
133
+ const text = await response.text();
134
+ let payload = null;
135
+
136
+ if (text) {
137
+ try {
138
+ payload = JSON.parse(text);
139
+ } catch {
140
+ payload = text;
141
+ }
142
+ }
143
+
144
+ if (!response.ok) {
145
+ const detail =
146
+ typeof payload === "string" ? payload : JSON.stringify(payload, null, 2);
147
+ throw new Error(`${response.status} ${response.statusText}: ${detail}`);
148
+ }
149
+
150
+ return payload;
151
+ }
152
+
153
+ async function main() {
154
+ const options = parseArgs(process.argv.slice(2));
155
+ const dashboard = await readDashboardTemplate(
156
+ path.resolve(process.cwd(), options.dashboard),
157
+ );
158
+ const baseUrl = options.openobserveUrl.replace(/\/+$/, "");
159
+ const dashboardsUrl = `${baseUrl}/api/${options.org}/dashboards`;
160
+
161
+ const existingPayload = await requestJson(dashboardsUrl, {
162
+ method: "GET",
163
+ user: options.user,
164
+ password: options.password,
165
+ });
166
+ const existingDashboards = normalizeDashboardList(existingPayload).filter(
167
+ (item) => dashboardTitleOf(item) === dashboard.title,
168
+ );
169
+
170
+ if (options.dryRun) {
171
+ console.log(
172
+ JSON.stringify(
173
+ {
174
+ dashboardPath: path.resolve(process.cwd(), options.dashboard),
175
+ title: dashboard.title,
176
+ matchingDashboardIds: existingDashboards
177
+ .map((item) => dashboardIdOf(item))
178
+ .filter(Boolean),
179
+ openobserveUrl: baseUrl,
180
+ org: options.org,
181
+ },
182
+ null,
183
+ 2,
184
+ ),
185
+ );
186
+ return;
187
+ }
188
+
189
+ const createdPayload = await requestJson(dashboardsUrl, {
190
+ method: "POST",
191
+ body: JSON.stringify(dashboard),
192
+ user: options.user,
193
+ password: options.password,
194
+ });
195
+
196
+ const createdDashboardId =
197
+ createdPayload?.dashboardId ??
198
+ createdPayload?.v5?.dashboardId ??
199
+ createdPayload?.data?.dashboardId;
200
+
201
+ if (!createdDashboardId) {
202
+ throw new Error("OpenObserve did not return a dashboardId for the new dashboard");
203
+ }
204
+
205
+ const deletedDashboardIds = [];
206
+
207
+ if (options.replaceByTitle) {
208
+ for (const entry of existingDashboards) {
209
+ const duplicateId = dashboardIdOf(entry);
210
+ if (!duplicateId || duplicateId === createdDashboardId) {
211
+ continue;
212
+ }
213
+
214
+ await requestJson(`${dashboardsUrl}/${duplicateId}`, {
215
+ method: "DELETE",
216
+ user: options.user,
217
+ password: options.password,
218
+ });
219
+ deletedDashboardIds.push(duplicateId);
220
+ }
221
+ }
222
+
223
+ console.log(
224
+ JSON.stringify(
225
+ {
226
+ title: dashboard.title,
227
+ dashboardId: createdDashboardId,
228
+ deletedDashboardIds,
229
+ openobserveUrl: `${baseUrl}/web/dashboards?org_identifier=${options.org}`,
230
+ },
231
+ null,
232
+ 2,
233
+ ),
234
+ );
235
+ }
236
+
237
+ main().catch((error) => {
238
+ console.error(error instanceof Error ? error.message : String(error));
239
+ process.exit(1);
240
+ });
@@ -0,0 +1,215 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -euo pipefail
4
+
5
+ SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
6
+ COMPOSE_FILE="${SCRIPT_DIR}/docker-compose.proxy-observability.yaml"
7
+ ENV_FILE="${SCRIPT_DIR}/proxy-observability.env"
8
+ IMPORT_SCRIPT="${SCRIPT_DIR}/import-openobserve-dashboard.mjs"
9
+ DOCTOR_SCRIPT="${SCRIPT_DIR}/check-proxy-telemetry.mjs"
10
+
11
+ load_env() {
12
+ if [[ -f "${ENV_FILE}" ]]; then
13
+ while IFS= read -r line || [[ -n "${line}" ]]; do
14
+ [[ "${line}" =~ ^[[:space:]]*$ ]] && continue
15
+ [[ "${line}" =~ ^[[:space:]]*# ]] && continue
16
+
17
+ line="${line#"${line%%[![:space:]]*}"}"
18
+ if [[ "${line}" =~ ^[[:space:]]*export[[:space:]]+ ]]; then
19
+ line="${line#export }"
20
+ fi
21
+
22
+ if [[ "${line}" != *=* ]]; then
23
+ continue
24
+ fi
25
+
26
+ local key="${line%%=*}"
27
+ local value="${line#*=}"
28
+
29
+ key="${key#"${key%%[![:space:]]*}"}"
30
+ key="${key%"${key##*[![:space:]]}"}"
31
+ value="${value#"${value%%[![:space:]]*}"}"
32
+ value="${value%"${value##*[![:space:]]}"}"
33
+
34
+ if [[ "${value}" =~ ^\".*\"$ || "${value}" =~ ^\'.*\'$ ]]; then
35
+ value="${value:1:${#value}-2}"
36
+ fi
37
+
38
+ if [[ -n "${key}" ]]; then
39
+ if [[ ! "${key}" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]]; then
40
+ echo "Skipping invalid env var name in ${ENV_FILE}: ${key}" >&2
41
+ continue
42
+ fi
43
+ printf -v "${key}" '%s' "${value}"
44
+ export "${key}"
45
+ fi
46
+ done < "${ENV_FILE}"
47
+ fi
48
+ }
49
+
50
+ select_compose() {
51
+ if command -v docker >/dev/null 2>&1 && docker compose version >/dev/null 2>&1; then
52
+ COMPOSE_CMD=(docker compose)
53
+ return
54
+ fi
55
+
56
+ if command -v docker-compose >/dev/null 2>&1; then
57
+ COMPOSE_CMD=(docker-compose)
58
+ return
59
+ fi
60
+
61
+ if command -v podman-compose >/dev/null 2>&1; then
62
+ COMPOSE_CMD=(podman-compose)
63
+ return
64
+ fi
65
+
66
+ echo "No supported compose command found. Install docker compose, docker-compose, or podman-compose." >&2
67
+ exit 1
68
+ }
69
+
70
+ compose() {
71
+ "${COMPOSE_CMD[@]}" -p "${NEUROLINK_OBSERVABILITY_PROJECT_NAME}" -f "${COMPOSE_FILE}" "$@"
72
+ }
73
+
74
+ wait_for_http() {
75
+ local name="$1"
76
+ local url="$2"
77
+ local attempts="${3:-60}"
78
+
79
+ for ((attempt = 1; attempt <= attempts; attempt += 1)); do
80
+ if curl -fsS "${url}" >/dev/null 2>&1; then
81
+ return 0
82
+ fi
83
+ sleep 1
84
+ done
85
+
86
+ echo "${name} did not become healthy at ${url}" >&2
87
+ return 1
88
+ }
89
+
90
+ print_status() {
91
+ local openobserve_status="DOWN"
92
+ local collector_status="DOWN"
93
+
94
+ if curl -fsS "${NEUROLINK_OPENOBSERVE_URL}/healthz" >/dev/null 2>&1; then
95
+ openobserve_status="UP"
96
+ fi
97
+
98
+ if curl -fsS "http://localhost:${NEUROLINK_OTEL_HEALTH_PORT}/" >/dev/null 2>&1; then
99
+ collector_status="UP"
100
+ fi
101
+
102
+ cat <<EOF
103
+ OpenObserve: ${openobserve_status}
104
+ UI: ${NEUROLINK_OPENOBSERVE_URL}
105
+ Login: ${NEUROLINK_OPENOBSERVE_USER}
106
+
107
+ OTEL Collector: ${collector_status}
108
+ OTLP gRPC: http://localhost:${NEUROLINK_OTLP_GRPC_PORT}
109
+ OTLP HTTP: http://localhost:${NEUROLINK_OTLP_HTTP_PORT}
110
+ Health: http://localhost:${NEUROLINK_OTEL_HEALTH_PORT}
111
+ EOF
112
+ }
113
+
114
+ print_usage() {
115
+ cat <<EOF
116
+ Usage: bash scripts/observability/manage-local-openobserve.sh <command>
117
+
118
+ Commands:
119
+ setup Start OpenObserve + OTEL collector and import the dashboard
120
+ up Start the local stack
121
+ start Alias for up
122
+ down Stop the local stack
123
+ stop Alias for down
124
+ logs Follow stack logs
125
+ status Show stack health and URLs
126
+ doctor Validate proxy telemetry freshness across logs, traces, and metrics
127
+ import-dashboard Import the NeuroLink proxy dashboard and dedupe by title
128
+ EOF
129
+ }
130
+
131
+ load_env
132
+
133
+ : "${NEUROLINK_OPENOBSERVE_USER:=root@example.com}"
134
+ : "${NEUROLINK_OPENOBSERVE_PASSWORD:=Complexpass#123}"
135
+ : "${NEUROLINK_OPENOBSERVE_ORG:=default}"
136
+ : "${NEUROLINK_OPENOBSERVE_HTTP_PORT:=5080}"
137
+ : "${NEUROLINK_OPENOBSERVE_GRPC_PORT:=5081}"
138
+ : "${NEUROLINK_OTLP_GRPC_PORT:=14317}"
139
+ : "${NEUROLINK_OTLP_HTTP_PORT:=14318}"
140
+ : "${NEUROLINK_OTEL_HEALTH_PORT:=14333}"
141
+ : "${NEUROLINK_OTEL_METRICS_PORT:=18888}"
142
+ : "${NEUROLINK_OTEL_PPROF_PORT:=11777}"
143
+ : "${NEUROLINK_OBSERVABILITY_PROJECT_NAME:=neurolink-proxy-observability}"
144
+ : "${NEUROLINK_PROXY_STREAM_HEADER:=neurolink-proxy}"
145
+ : "${NEUROLINK_OPENOBSERVE_URL:=http://localhost:${NEUROLINK_OPENOBSERVE_HTTP_PORT}}"
146
+ : "${NEUROLINK_OPENOBSERVE_OTLP_ENDPOINT:=http://openobserve:5080/api/${NEUROLINK_OPENOBSERVE_ORG}}"
147
+
148
+ if [[ -z "${NEUROLINK_OPENOBSERVE_BASIC_AUTH:-}" ]]; then
149
+ encoded_auth="$(printf '%s' "${NEUROLINK_OPENOBSERVE_USER}:${NEUROLINK_OPENOBSERVE_PASSWORD}" | base64 | tr -d '\n')"
150
+ export NEUROLINK_OPENOBSERVE_BASIC_AUTH="Basic ${encoded_auth}"
151
+ fi
152
+
153
+ export NEUROLINK_OPENOBSERVE_USER
154
+ export NEUROLINK_OPENOBSERVE_PASSWORD
155
+ export NEUROLINK_OPENOBSERVE_ORG
156
+ export NEUROLINK_OPENOBSERVE_HTTP_PORT
157
+ export NEUROLINK_OPENOBSERVE_GRPC_PORT
158
+ export NEUROLINK_OTLP_GRPC_PORT
159
+ export NEUROLINK_OTLP_HTTP_PORT
160
+ export NEUROLINK_OTEL_HEALTH_PORT
161
+ export NEUROLINK_OTEL_METRICS_PORT
162
+ export NEUROLINK_OTEL_PPROF_PORT
163
+ export NEUROLINK_OBSERVABILITY_PROJECT_NAME
164
+ export NEUROLINK_PROXY_STREAM_HEADER
165
+ export NEUROLINK_OPENOBSERVE_URL
166
+ export NEUROLINK_OPENOBSERVE_OTLP_ENDPOINT
167
+
168
+ select_compose
169
+
170
+ command="${1:-setup}"
171
+
172
+ case "${command}" in
173
+ setup)
174
+ compose up -d
175
+ wait_for_http "OpenObserve" "${NEUROLINK_OPENOBSERVE_URL}/healthz"
176
+ wait_for_http "OTEL collector" "http://localhost:${NEUROLINK_OTEL_HEALTH_PORT}/"
177
+ node "${IMPORT_SCRIPT}" --replace-by-title
178
+ cat <<EOF
179
+ Local proxy observability is ready.
180
+
181
+ OpenObserve UI: ${NEUROLINK_OPENOBSERVE_URL}
182
+ OpenObserve login user: ${NEUROLINK_OPENOBSERVE_USER}
183
+ OpenObserve password source: ${ENV_FILE} (NEUROLINK_OPENOBSERVE_PASSWORD)
184
+ OTLP HTTP endpoint: http://localhost:${NEUROLINK_OTLP_HTTP_PORT}
185
+
186
+ Set this before starting the proxy:
187
+ export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:${NEUROLINK_OTLP_HTTP_PORT}
188
+ EOF
189
+ ;;
190
+ up|start)
191
+ compose up -d
192
+ ;;
193
+ down|stop)
194
+ compose down
195
+ ;;
196
+ logs)
197
+ compose logs -f
198
+ ;;
199
+ status)
200
+ print_status
201
+ ;;
202
+ doctor)
203
+ node "${DOCTOR_SCRIPT}"
204
+ ;;
205
+ import-dashboard)
206
+ node "${IMPORT_SCRIPT}" --replace-by-title
207
+ ;;
208
+ help|-h|--help)
209
+ print_usage
210
+ ;;
211
+ *)
212
+ print_usage >&2
213
+ exit 1
214
+ ;;
215
+ esac
@@ -0,0 +1,78 @@
1
+ # OpenTelemetry Collector configuration dedicated to the NeuroLink proxy.
2
+ #
3
+ # Active OpenObserve streams created by this setup:
4
+ # - Logs: neurolink_proxy
5
+ # - Traces: neurolink_proxy
6
+ # - Metrics: proxy_* (metric name becomes the stream)
7
+
8
+ receivers:
9
+ otlp:
10
+ protocols:
11
+ grpc:
12
+ endpoint: 0.0.0.0:4317
13
+ http:
14
+ endpoint: 0.0.0.0:4318
15
+
16
+ processors:
17
+ memory_limiter:
18
+ check_interval: 1s
19
+ limit_mib: 512
20
+ spike_limit_mib: 128
21
+
22
+ batch:
23
+ timeout: 1s
24
+ send_batch_size: 1024
25
+ send_batch_max_size: 2048
26
+
27
+ filter/healthcheck:
28
+ error_mode: ignore
29
+ traces:
30
+ span:
31
+ - 'IsMatch(name, ".*health.*") or IsMatch(name, ".*healthz.*") or IsMatch(name, ".*ready.*")'
32
+
33
+ exporters:
34
+ otlphttp/openobserve:
35
+ endpoint: ${env:NEUROLINK_OPENOBSERVE_OTLP_ENDPOINT}
36
+ headers:
37
+ Authorization: ${env:NEUROLINK_OPENOBSERVE_BASIC_AUTH}
38
+ stream-name: ${env:NEUROLINK_PROXY_STREAM_HEADER}
39
+ tls:
40
+ insecure: true
41
+
42
+ extensions:
43
+ health_check:
44
+ endpoint: 0.0.0.0:13133
45
+
46
+ pprof:
47
+ endpoint: 0.0.0.0:1777
48
+
49
+ service:
50
+ extensions: [health_check, pprof]
51
+
52
+ pipelines:
53
+ traces:
54
+ receivers: [otlp]
55
+ processors: [memory_limiter, filter/healthcheck, batch]
56
+ exporters: [otlphttp/openobserve]
57
+
58
+ metrics:
59
+ receivers: [otlp]
60
+ processors: [memory_limiter, batch]
61
+ exporters: [otlphttp/openobserve]
62
+
63
+ logs:
64
+ receivers: [otlp]
65
+ processors: [memory_limiter, batch]
66
+ exporters: [otlphttp/openobserve]
67
+
68
+ telemetry:
69
+ logs:
70
+ level: info
71
+ metrics:
72
+ level: detailed
73
+ readers:
74
+ - pull:
75
+ exporter:
76
+ prometheus:
77
+ host: "0.0.0.0"
78
+ port: 8888
@@ -0,0 +1,23 @@
1
+ # Copy this file to scripts/observability/proxy-observability.env only if the
2
+ # defaults collide with your machine or you want different credentials.
3
+
4
+ NEUROLINK_OPENOBSERVE_USER=root@example.com
5
+ NEUROLINK_OPENOBSERVE_PASSWORD=Complexpass#123
6
+ NEUROLINK_OPENOBSERVE_ORG=default
7
+
8
+ # Host ports exposed by the local stack.
9
+ NEUROLINK_OPENOBSERVE_HTTP_PORT=5080
10
+ NEUROLINK_OPENOBSERVE_GRPC_PORT=5081
11
+ NEUROLINK_OTLP_GRPC_PORT=14317
12
+ NEUROLINK_OTLP_HTTP_PORT=14318
13
+ NEUROLINK_OTEL_HEALTH_PORT=14333
14
+ NEUROLINK_OTEL_METRICS_PORT=18888
15
+ NEUROLINK_OTEL_PPROF_PORT=11777
16
+
17
+ # Optional names if you want multiple local stacks in parallel.
18
+ # NEUROLINK_OBSERVABILITY_PROJECT_NAME=neurolink-proxy-observability
19
+ # NEUROLINK_OPENOBSERVE_VOLUME_NAME=neurolink-openobserve-data
20
+
21
+ # The collector exports proxy telemetry into this OpenObserve stream header.
22
+ # OpenObserve normalizes the logs and traces stream name to "neurolink_proxy".
23
+ # NEUROLINK_PROXY_STREAM_HEADER=neurolink-proxy