@juspay/neurolink 9.10.0 → 9.11.0
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 +12 -0
- package/dist/adapters/video/videoAnalyzer.d.ts +3 -3
- package/dist/adapters/video/videoAnalyzer.js +39 -25
- package/dist/agent/directTools.d.ts +3 -3
- package/dist/cli/commands/config.d.ts +9 -9
- package/dist/cli/loop/optionsSchema.d.ts +1 -1
- package/dist/constants/contextWindows.d.ts +6 -3
- package/dist/constants/contextWindows.js +30 -3
- package/dist/constants/index.d.ts +3 -3
- package/dist/constants/retry.d.ts +4 -4
- package/dist/constants/retry.js +1 -1
- package/dist/context/contextCompactor.d.ts +1 -1
- package/dist/context/contextCompactor.js +59 -1
- package/dist/context/summarizationEngine.d.ts +2 -2
- package/dist/context/summarizationEngine.js +44 -18
- package/dist/context/toolOutputLimits.d.ts +22 -13
- package/dist/context/toolOutputLimits.js +58 -64
- package/dist/core/baseProvider.d.ts +11 -2
- package/dist/core/baseProvider.js +30 -1
- package/dist/core/conversationMemoryManager.d.ts +13 -1
- package/dist/core/conversationMemoryManager.js +36 -5
- package/dist/core/modules/GenerationHandler.d.ts +6 -0
- package/dist/core/modules/GenerationHandler.js +192 -7
- package/dist/core/modules/MessageBuilder.js +42 -4
- package/dist/core/modules/TelemetryHandler.js +4 -1
- package/dist/core/redisConversationMemoryManager.d.ts +19 -3
- package/dist/core/redisConversationMemoryManager.js +253 -58
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/lib/adapters/video/videoAnalyzer.d.ts +3 -3
- package/dist/lib/adapters/video/videoAnalyzer.js +39 -25
- package/dist/lib/agent/directTools.d.ts +7 -7
- package/dist/lib/constants/contextWindows.d.ts +6 -3
- package/dist/lib/constants/contextWindows.js +30 -3
- package/dist/lib/constants/index.d.ts +3 -3
- package/dist/lib/constants/retry.d.ts +4 -4
- package/dist/lib/constants/retry.js +1 -1
- package/dist/lib/context/contextCompactor.d.ts +1 -1
- package/dist/lib/context/contextCompactor.js +59 -1
- package/dist/lib/context/summarizationEngine.d.ts +2 -2
- package/dist/lib/context/summarizationEngine.js +44 -18
- package/dist/lib/context/toolOutputLimits.d.ts +22 -13
- package/dist/lib/context/toolOutputLimits.js +58 -64
- package/dist/lib/core/baseProvider.d.ts +11 -2
- package/dist/lib/core/baseProvider.js +30 -1
- package/dist/lib/core/conversationMemoryManager.d.ts +13 -1
- package/dist/lib/core/conversationMemoryManager.js +36 -5
- package/dist/lib/core/modules/GenerationHandler.d.ts +6 -0
- package/dist/lib/core/modules/GenerationHandler.js +192 -7
- package/dist/lib/core/modules/MessageBuilder.js +42 -4
- package/dist/lib/core/modules/TelemetryHandler.js +4 -1
- package/dist/lib/core/redisConversationMemoryManager.d.ts +19 -3
- package/dist/lib/core/redisConversationMemoryManager.js +253 -58
- package/dist/lib/files/fileTools.d.ts +3 -3
- package/dist/lib/index.d.ts +2 -0
- package/dist/lib/index.js +3 -0
- package/dist/lib/mcp/externalServerManager.js +36 -1
- package/dist/lib/memory/memoryRetrievalTools.d.ts +166 -0
- package/dist/lib/memory/memoryRetrievalTools.js +145 -0
- package/dist/lib/neurolink.d.ts +35 -1
- package/dist/lib/neurolink.js +471 -16
- package/dist/lib/providers/amazonBedrock.d.ts +1 -1
- package/dist/lib/providers/amazonBedrock.js +78 -45
- package/dist/lib/providers/amazonSagemaker.d.ts +1 -1
- package/dist/lib/providers/amazonSagemaker.js +1 -1
- package/dist/lib/providers/anthropic.d.ts +1 -1
- package/dist/lib/providers/anthropic.js +7 -7
- package/dist/lib/providers/anthropicBaseProvider.d.ts +1 -1
- package/dist/lib/providers/anthropicBaseProvider.js +7 -6
- package/dist/lib/providers/azureOpenai.d.ts +1 -1
- package/dist/lib/providers/azureOpenai.js +1 -1
- package/dist/lib/providers/googleAiStudio.d.ts +1 -1
- package/dist/lib/providers/googleAiStudio.js +5 -5
- package/dist/lib/providers/googleVertex.d.ts +1 -1
- package/dist/lib/providers/googleVertex.js +74 -17
- package/dist/lib/providers/huggingFace.d.ts +1 -1
- package/dist/lib/providers/huggingFace.js +1 -1
- package/dist/lib/providers/litellm.d.ts +1 -1
- package/dist/lib/providers/litellm.js +18 -16
- package/dist/lib/providers/mistral.d.ts +1 -1
- package/dist/lib/providers/mistral.js +1 -1
- package/dist/lib/providers/ollama.d.ts +1 -1
- package/dist/lib/providers/ollama.js +8 -7
- package/dist/lib/providers/openAI.d.ts +1 -1
- package/dist/lib/providers/openAI.js +6 -6
- package/dist/lib/providers/openRouter.d.ts +1 -1
- package/dist/lib/providers/openRouter.js +6 -2
- package/dist/lib/providers/openaiCompatible.d.ts +1 -1
- package/dist/lib/providers/openaiCompatible.js +1 -1
- package/dist/lib/proxy/proxyFetch.js +291 -65
- package/dist/lib/server/utils/validation.d.ts +4 -4
- package/dist/lib/services/server/ai/observability/instrumentation.js +12 -3
- package/dist/lib/telemetry/telemetryService.d.ts +2 -1
- package/dist/lib/telemetry/telemetryService.js +8 -1
- package/dist/lib/types/contextTypes.d.ts +26 -2
- package/dist/lib/types/conversation.d.ts +72 -40
- package/dist/lib/types/conversationMemoryInterface.d.ts +5 -1
- package/dist/lib/types/generateTypes.d.ts +26 -0
- package/dist/lib/types/modelTypes.d.ts +2 -2
- package/dist/lib/types/multimodal.d.ts +2 -0
- package/dist/lib/types/observability.d.ts +10 -0
- package/dist/lib/types/sdkTypes.d.ts +1 -1
- package/dist/lib/utils/conversationMemory.d.ts +4 -3
- package/dist/lib/utils/conversationMemory.js +44 -6
- package/dist/lib/utils/errorHandling.d.ts +5 -0
- package/dist/lib/utils/errorHandling.js +7 -2
- package/dist/lib/utils/logger.d.ts +8 -0
- package/dist/lib/utils/logger.js +56 -1
- package/dist/lib/utils/messageBuilder.js +74 -4
- package/dist/lib/utils/redis.js +6 -1
- package/dist/lib/utils/tokenEstimation.d.ts +2 -2
- package/dist/lib/utils/tokenEstimation.js +16 -1
- package/dist/lib/utils/videoAnalysisProcessor.d.ts +2 -1
- package/dist/lib/utils/videoAnalysisProcessor.js +7 -2
- package/dist/lib/workflow/config.d.ts +110 -110
- package/dist/mcp/externalServerManager.js +36 -1
- package/dist/memory/memoryRetrievalTools.d.ts +166 -0
- package/dist/memory/memoryRetrievalTools.js +144 -0
- package/dist/neurolink.d.ts +35 -1
- package/dist/neurolink.js +471 -16
- package/dist/providers/amazonBedrock.d.ts +1 -1
- package/dist/providers/amazonBedrock.js +78 -45
- package/dist/providers/amazonSagemaker.d.ts +1 -1
- package/dist/providers/amazonSagemaker.js +1 -1
- package/dist/providers/anthropic.d.ts +1 -1
- package/dist/providers/anthropic.js +7 -7
- package/dist/providers/anthropicBaseProvider.d.ts +1 -1
- package/dist/providers/anthropicBaseProvider.js +7 -6
- package/dist/providers/azureOpenai.d.ts +1 -1
- package/dist/providers/azureOpenai.js +1 -1
- package/dist/providers/googleAiStudio.d.ts +1 -1
- package/dist/providers/googleAiStudio.js +5 -5
- package/dist/providers/googleVertex.d.ts +1 -1
- package/dist/providers/googleVertex.js +74 -17
- package/dist/providers/huggingFace.d.ts +1 -1
- package/dist/providers/huggingFace.js +1 -1
- package/dist/providers/litellm.d.ts +1 -1
- package/dist/providers/litellm.js +18 -16
- package/dist/providers/mistral.d.ts +1 -1
- package/dist/providers/mistral.js +1 -1
- package/dist/providers/ollama.d.ts +1 -1
- package/dist/providers/ollama.js +8 -7
- package/dist/providers/openAI.d.ts +1 -1
- package/dist/providers/openAI.js +6 -6
- package/dist/providers/openRouter.d.ts +1 -1
- package/dist/providers/openRouter.js +6 -2
- package/dist/providers/openaiCompatible.d.ts +1 -1
- package/dist/providers/openaiCompatible.js +1 -1
- package/dist/proxy/proxyFetch.js +291 -65
- package/dist/services/server/ai/observability/instrumentation.js +12 -3
- package/dist/telemetry/telemetryService.d.ts +2 -1
- package/dist/telemetry/telemetryService.js +8 -1
- package/dist/types/contextTypes.d.ts +26 -2
- package/dist/types/conversation.d.ts +72 -40
- package/dist/types/conversationMemoryInterface.d.ts +5 -1
- package/dist/types/generateTypes.d.ts +26 -0
- package/dist/types/modelTypes.d.ts +10 -10
- package/dist/types/multimodal.d.ts +2 -0
- package/dist/types/observability.d.ts +10 -0
- package/dist/types/sdkTypes.d.ts +1 -1
- package/dist/utils/conversationMemory.d.ts +4 -3
- package/dist/utils/conversationMemory.js +44 -6
- package/dist/utils/errorHandling.d.ts +5 -0
- package/dist/utils/errorHandling.js +7 -2
- package/dist/utils/logger.d.ts +8 -0
- package/dist/utils/logger.js +56 -1
- package/dist/utils/messageBuilder.js +74 -4
- package/dist/utils/redis.js +6 -1
- package/dist/utils/tokenEstimation.d.ts +2 -2
- package/dist/utils/tokenEstimation.js +16 -1
- package/dist/utils/videoAnalysisProcessor.d.ts +2 -1
- package/dist/utils/videoAnalysisProcessor.js +7 -2
- package/dist/workflow/config.d.ts +12 -12
- package/package.json +1 -1
package/dist/proxy/proxyFetch.js
CHANGED
|
@@ -6,36 +6,118 @@
|
|
|
6
6
|
import { logger } from "../utils/logger.js";
|
|
7
7
|
import { shouldBypassProxy } from "./utils/noProxyUtils.js";
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
9
|
+
* Retry-aware fetch wrapper for transient network errors (ECONNRESET, ETIMEDOUT, socket hang up).
|
|
10
|
+
* Protects all LLM API calls and token refreshes that go through createProxyFetch().
|
|
11
11
|
*/
|
|
12
|
-
function
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
async function fetchWithRetry(url, init, maxRetries = 3, baseDelay = 500) {
|
|
13
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
14
|
+
try {
|
|
15
|
+
return await fetch(url, init);
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
const err = error;
|
|
19
|
+
const isRetryable = err?.code === "ECONNRESET" ||
|
|
20
|
+
err?.code === "ETIMEDOUT" ||
|
|
21
|
+
err?.message?.includes("socket hang up") ||
|
|
22
|
+
err?.message?.includes("network socket disconnected");
|
|
23
|
+
if (!isRetryable || attempt === maxRetries) {
|
|
24
|
+
throw error;
|
|
25
|
+
}
|
|
26
|
+
const delay = baseDelay * Math.pow(2, attempt);
|
|
27
|
+
logger.debug(`[fetchWithRetry] Transient error (${err?.code || err?.message}), retrying in ${delay}ms (attempt ${attempt + 1}/${maxRetries})`);
|
|
28
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
29
|
+
}
|
|
15
30
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
31
|
+
throw new Error("fetchWithRetry exhausted"); // unreachable
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Parse request body to readable format for debug logging
|
|
35
|
+
*/
|
|
36
|
+
function parseBody(body) {
|
|
37
|
+
if (!body) {
|
|
38
|
+
return { parsed: null, size: 0, type: "empty" };
|
|
39
|
+
}
|
|
40
|
+
if (typeof body === "string") {
|
|
41
|
+
try {
|
|
42
|
+
return { parsed: JSON.parse(body), size: body.length, type: "json" };
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return { parsed: body, size: body.length, type: "text" };
|
|
21
46
|
}
|
|
22
|
-
// Return original URL if no credentials found
|
|
23
|
-
return proxyUrl;
|
|
24
47
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
48
|
+
if (body instanceof ArrayBuffer) {
|
|
49
|
+
return {
|
|
50
|
+
parsed: "[ArrayBuffer]",
|
|
51
|
+
size: body.byteLength,
|
|
52
|
+
type: "arraybuffer",
|
|
53
|
+
};
|
|
28
54
|
}
|
|
55
|
+
if (body instanceof Uint8Array) {
|
|
56
|
+
return { parsed: "[Uint8Array]", size: body.length, type: "uint8array" };
|
|
57
|
+
}
|
|
58
|
+
return { parsed: "[Stream]", size: -1, type: "stream" };
|
|
29
59
|
}
|
|
30
60
|
/**
|
|
31
|
-
*
|
|
61
|
+
* Sensitive header names whose values should be redacted in logs
|
|
62
|
+
*/
|
|
63
|
+
const SENSITIVE_HEADERS = new Set([
|
|
64
|
+
"authorization",
|
|
65
|
+
"x-api-key",
|
|
66
|
+
"api-key",
|
|
67
|
+
"x-goog-api-key",
|
|
68
|
+
"proxy-authorization",
|
|
69
|
+
"cookie",
|
|
70
|
+
"set-cookie",
|
|
71
|
+
]);
|
|
72
|
+
/**
|
|
73
|
+
* Extract all headers as plain object with sensitive values redacted
|
|
32
74
|
*/
|
|
33
|
-
function
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
75
|
+
function getAllHeaders(headers) {
|
|
76
|
+
if (!headers) {
|
|
77
|
+
return {};
|
|
78
|
+
}
|
|
79
|
+
const entries = headers instanceof Headers
|
|
80
|
+
? [...headers.entries()]
|
|
81
|
+
: Array.isArray(headers)
|
|
82
|
+
? headers
|
|
83
|
+
: Object.entries(headers);
|
|
84
|
+
return Object.fromEntries(entries.map(([key, value]) => SENSITIVE_HEADERS.has(key.toLowerCase())
|
|
85
|
+
? [key, `${value.substring(0, 4)}***`]
|
|
86
|
+
: [key, value]));
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Clone response and read body + headers for debug logging
|
|
90
|
+
*/
|
|
91
|
+
async function readResponseBody(response) {
|
|
92
|
+
const headers = {};
|
|
93
|
+
response.headers.forEach((value, key) => {
|
|
94
|
+
headers[key] = SENSITIVE_HEADERS.has(key.toLowerCase())
|
|
95
|
+
? `${value.substring(0, 4)}***`
|
|
96
|
+
: value;
|
|
97
|
+
});
|
|
98
|
+
try {
|
|
99
|
+
const cloned = response.clone();
|
|
100
|
+
const text = await cloned.text();
|
|
101
|
+
try {
|
|
102
|
+
return {
|
|
103
|
+
parsed: JSON.parse(text),
|
|
104
|
+
size: text.length,
|
|
105
|
+
type: "json",
|
|
106
|
+
headers,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return { parsed: text, size: text.length, type: "text", headers };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return {
|
|
115
|
+
parsed: "[unable to read body]",
|
|
116
|
+
size: -1,
|
|
117
|
+
type: "error",
|
|
118
|
+
headers,
|
|
119
|
+
};
|
|
37
120
|
}
|
|
38
|
-
return masked;
|
|
39
121
|
}
|
|
40
122
|
// ==================== LIGHTWEIGHT PROXY IMPLEMENTATIONS ====================
|
|
41
123
|
// ParsedProxyConfig interface moved to ../types/utilities.js
|
|
@@ -61,11 +143,22 @@ function parseProxyUrl(proxyUrl) {
|
|
|
61
143
|
return config;
|
|
62
144
|
}
|
|
63
145
|
catch (error) {
|
|
146
|
+
// Sanitize proxy URL to avoid leaking credentials in logs/errors
|
|
147
|
+
let safeUrl;
|
|
148
|
+
try {
|
|
149
|
+
const u = new URL(proxyUrl);
|
|
150
|
+
u.username = "";
|
|
151
|
+
u.password = "";
|
|
152
|
+
safeUrl = u.toString();
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
safeUrl = "[invalid-url]";
|
|
156
|
+
}
|
|
64
157
|
logger.error("[Proxy] Failed to parse proxy URL", {
|
|
65
|
-
proxyUrl:
|
|
158
|
+
proxyUrl: safeUrl,
|
|
66
159
|
error,
|
|
67
160
|
});
|
|
68
|
-
throw new Error(`Invalid proxy URL: ${
|
|
161
|
+
throw new Error(`Invalid proxy URL: ${safeUrl}`);
|
|
69
162
|
}
|
|
70
163
|
}
|
|
71
164
|
/**
|
|
@@ -160,55 +253,117 @@ export function createProxyFetch() {
|
|
|
160
253
|
const allProxy = process.env.ALL_PROXY || process.env.all_proxy;
|
|
161
254
|
const socksProxy = process.env.SOCKS_PROXY || process.env.socks_proxy;
|
|
162
255
|
const noProxy = process.env.NO_PROXY || process.env.no_proxy;
|
|
163
|
-
// ENHANCED LOGGING: Capture ALL
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
allProxy: maskProxyCredentials(allProxy || "NOT_SET"),
|
|
169
|
-
socksProxy: maskProxyCredentials(socksProxy || "NOT_SET"),
|
|
170
|
-
noProxy: noProxy || "NOT_SET", // NO_PROXY doesn't contain credentials
|
|
171
|
-
// Legacy variables for compatibility (credentials masked)
|
|
172
|
-
originalNodejsHttpProxy: maskProxyCredentials(process.env.nodejs_http_proxy || "NOT_SET"),
|
|
173
|
-
originalNodejsHttpsProxy: maskProxyCredentials(process.env.nodejs_https_proxy || "NOT_SET"),
|
|
174
|
-
// All potential proxy-related environment variables (credentials masked)
|
|
175
|
-
allProxyRelatedEnvVars: maskProxyEnvVars(Object.keys(process.env)
|
|
256
|
+
// ENHANCED LOGGING: Capture ALL proxy-related environment variables — credentials redacted
|
|
257
|
+
// Reuse module-level maskProxyUrl, defaulting to "NOT_SET" for undefined values
|
|
258
|
+
const sanitizeProxyUrl = (url) => maskProxyUrl(url) ?? "NOT_SET";
|
|
259
|
+
if (logger.shouldLog("debug")) {
|
|
260
|
+
const allProxyRelatedEnvVars = Object.keys(process.env)
|
|
176
261
|
.filter((key) => key.toLowerCase().includes("proxy"))
|
|
177
262
|
.reduce((acc, key) => {
|
|
178
|
-
|
|
263
|
+
const val = process.env[key] || "NOT_SET";
|
|
264
|
+
acc[key] =
|
|
265
|
+
key.toLowerCase() === "no_proxy" ? val : sanitizeProxyUrl(val);
|
|
179
266
|
return acc;
|
|
180
|
-
}, {})
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
267
|
+
}, {});
|
|
268
|
+
logger.debug("[Proxy Fetch] ENHANCED_PROXY_ENV_DETECTION", {
|
|
269
|
+
httpProxy: sanitizeProxyUrl(httpProxy),
|
|
270
|
+
httpsProxy: sanitizeProxyUrl(httpsProxy),
|
|
271
|
+
allProxy: sanitizeProxyUrl(allProxy),
|
|
272
|
+
socksProxy: sanitizeProxyUrl(socksProxy),
|
|
273
|
+
noProxy: noProxy || "NOT_SET",
|
|
274
|
+
allProxyRelatedEnvVars,
|
|
275
|
+
message: "Enhanced proxy environment detection — credentials redacted",
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
// If no proxy configured, return instrumented standard fetch
|
|
184
279
|
if (!httpsProxy && !httpProxy && !allProxy && !socksProxy) {
|
|
185
280
|
logger.debug("[Proxy Fetch] No proxy environment variables found - using standard fetch");
|
|
186
|
-
return
|
|
281
|
+
return async (input, init) => {
|
|
282
|
+
const reqId = `req-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
|
283
|
+
const startTs = Date.now();
|
|
284
|
+
const url = typeof input === "string"
|
|
285
|
+
? input
|
|
286
|
+
: input instanceof URL
|
|
287
|
+
? input.href
|
|
288
|
+
: input.url;
|
|
289
|
+
if (logger.shouldLog("debug")) {
|
|
290
|
+
const { parsed: requestBody, size: bodySize, type: bodyType, } = parseBody(init?.body);
|
|
291
|
+
logger.debug("[Observability] HTTP request to LLM provider", {
|
|
292
|
+
requestId: reqId,
|
|
293
|
+
url,
|
|
294
|
+
method: init?.method || "POST",
|
|
295
|
+
headers: getAllHeaders(init?.headers),
|
|
296
|
+
body: requestBody,
|
|
297
|
+
bodySize,
|
|
298
|
+
bodyType,
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
try {
|
|
302
|
+
const response = await fetchWithRetry(input, init);
|
|
303
|
+
if (logger.shouldLog("debug")) {
|
|
304
|
+
const { parsed: responseBody, size: responseSize, type: responseType, headers: responseHeaders, } = await readResponseBody(response);
|
|
305
|
+
logger.debug("[Observability] HTTP response from LLM provider", {
|
|
306
|
+
requestId: reqId,
|
|
307
|
+
url,
|
|
308
|
+
status: response.status,
|
|
309
|
+
statusText: response.statusText,
|
|
310
|
+
durationMs: Date.now() - startTs,
|
|
311
|
+
headers: responseHeaders,
|
|
312
|
+
body: responseBody,
|
|
313
|
+
bodySize: responseSize,
|
|
314
|
+
bodyType: responseType,
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
return response;
|
|
318
|
+
}
|
|
319
|
+
catch (error) {
|
|
320
|
+
logger.debug("[Observability] HTTP request failed", {
|
|
321
|
+
requestId: reqId,
|
|
322
|
+
url,
|
|
323
|
+
error: error instanceof Error ? error.message : String(error),
|
|
324
|
+
durationMs: Date.now() - startTs,
|
|
325
|
+
});
|
|
326
|
+
throw error;
|
|
327
|
+
}
|
|
328
|
+
};
|
|
187
329
|
}
|
|
188
330
|
logger.debug(`[Proxy Fetch] Configuring enhanced proxy with multiple protocol support`);
|
|
189
|
-
logger.debug(`[Proxy Fetch] HTTP_PROXY: ${
|
|
190
|
-
logger.debug(`[Proxy Fetch] HTTPS_PROXY: ${
|
|
191
|
-
logger.debug(`[Proxy Fetch] ALL_PROXY: ${
|
|
192
|
-
logger.debug(`[Proxy Fetch] SOCKS_PROXY: ${
|
|
331
|
+
logger.debug(`[Proxy Fetch] HTTP_PROXY: ${sanitizeProxyUrl(httpProxy)}`);
|
|
332
|
+
logger.debug(`[Proxy Fetch] HTTPS_PROXY: ${sanitizeProxyUrl(httpsProxy)}`);
|
|
333
|
+
logger.debug(`[Proxy Fetch] ALL_PROXY: ${sanitizeProxyUrl(allProxy)}`);
|
|
334
|
+
logger.debug(`[Proxy Fetch] SOCKS_PROXY: ${sanitizeProxyUrl(socksProxy)}`);
|
|
193
335
|
logger.debug(`[Proxy Fetch] NO_PROXY: ${noProxy || "not set"}`);
|
|
194
336
|
// Return enhanced proxy-aware fetch function
|
|
195
337
|
return async (input, init) => {
|
|
196
338
|
const requestId = `req-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
|
339
|
+
const requestStartTime = Date.now();
|
|
197
340
|
// Determine target URL
|
|
198
341
|
const targetUrl = typeof input === "string"
|
|
199
342
|
? input
|
|
200
343
|
: input instanceof URL
|
|
201
344
|
? input.href
|
|
202
345
|
: input.url;
|
|
203
|
-
|
|
346
|
+
// Request logging with sensitive header redaction — gated behind debug check
|
|
347
|
+
if (logger.shouldLog("debug")) {
|
|
348
|
+
const { parsed: requestBody, size: bodySize, type: bodyType, } = parseBody(init?.body);
|
|
349
|
+
logger.debug("[Observability] HTTP request to LLM provider", {
|
|
350
|
+
requestId,
|
|
351
|
+
url: targetUrl,
|
|
352
|
+
method: init?.method || "POST",
|
|
353
|
+
headers: getAllHeaders(init?.headers),
|
|
354
|
+
body: requestBody,
|
|
355
|
+
bodySize,
|
|
356
|
+
bodyType,
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
logger.debug(`[Proxy Fetch] ENHANCED REQUEST START`, {
|
|
204
360
|
requestId,
|
|
205
361
|
targetUrl,
|
|
206
362
|
timestamp: new Date().toISOString(),
|
|
207
|
-
httpProxy: httpProxy
|
|
208
|
-
httpsProxy: httpsProxy
|
|
209
|
-
allProxy: allProxy
|
|
210
|
-
socksProxy: socksProxy
|
|
211
|
-
initHeaders: init?.headers || "NO_HEADERS",
|
|
363
|
+
httpProxy: sanitizeProxyUrl(httpProxy),
|
|
364
|
+
httpsProxy: sanitizeProxyUrl(httpsProxy),
|
|
365
|
+
allProxy: sanitizeProxyUrl(allProxy),
|
|
366
|
+
socksProxy: sanitizeProxyUrl(socksProxy),
|
|
212
367
|
initMethod: init?.method || "GET",
|
|
213
368
|
});
|
|
214
369
|
try {
|
|
@@ -216,18 +371,19 @@ export function createProxyFetch() {
|
|
|
216
371
|
const proxyUrl = selectProxyUrl(targetUrl);
|
|
217
372
|
if (proxyUrl) {
|
|
218
373
|
const url = new URL(targetUrl);
|
|
374
|
+
const sanitizedProxy = sanitizeProxyUrl(proxyUrl);
|
|
219
375
|
logger.debug(`[Proxy Fetch] 🔗 ENHANCED URL ANALYSIS`, {
|
|
220
376
|
requestId,
|
|
221
377
|
targetUrl,
|
|
222
378
|
urlHostname: url.hostname,
|
|
223
379
|
urlProtocol: url.protocol,
|
|
224
380
|
urlPort: url.port,
|
|
225
|
-
selectedProxyUrl:
|
|
381
|
+
selectedProxyUrl: sanitizedProxy,
|
|
226
382
|
timestamp: new Date().toISOString(),
|
|
227
383
|
});
|
|
228
384
|
logger.debug(`[Proxy Fetch] 🎯 ENHANCED PROXY AGENT CREATION`, {
|
|
229
385
|
requestId,
|
|
230
|
-
proxyUrl:
|
|
386
|
+
proxyUrl: sanitizedProxy,
|
|
231
387
|
targetHostname: url.hostname,
|
|
232
388
|
targetProtocol: url.protocol,
|
|
233
389
|
aboutToCreateProxyAgent: true,
|
|
@@ -236,8 +392,9 @@ export function createProxyFetch() {
|
|
|
236
392
|
// Create/reuse proxy agent (HTTP/HTTPS/SOCKS)
|
|
237
393
|
const agentCache = globalThis.__NL_PROXY_AGENT_CACHE__ ??
|
|
238
394
|
(globalThis.__NL_PROXY_AGENT_CACHE__ = new Map());
|
|
239
|
-
const
|
|
240
|
-
agentCache.
|
|
395
|
+
const cacheKey = maskProxyUrl(proxyUrl) ?? proxyUrl; // credentials stripped for key
|
|
396
|
+
const dispatcher = agentCache.get(cacheKey) || (await createProxyAgent(proxyUrl));
|
|
397
|
+
agentCache.set(cacheKey, dispatcher);
|
|
241
398
|
logger.debug(`[Proxy Fetch] ✅ ENHANCED PROXY AGENT CREATED`, {
|
|
242
399
|
requestId,
|
|
243
400
|
hasDispatcher: !!dispatcher,
|
|
@@ -266,20 +423,40 @@ export function createProxyFetch() {
|
|
|
266
423
|
...fetchInit,
|
|
267
424
|
dispatcher: dispatcher,
|
|
268
425
|
});
|
|
269
|
-
logger.debug
|
|
426
|
+
if (logger.shouldLog("debug")) {
|
|
427
|
+
const { parsed: responseBody, size: responseSize, type: responseType, headers: responseHeaders, } = await readResponseBody(response);
|
|
428
|
+
logger.debug("[Observability] HTTP response from LLM provider", {
|
|
429
|
+
requestId,
|
|
430
|
+
url: targetUrl,
|
|
431
|
+
status: response?.status,
|
|
432
|
+
statusText: response?.statusText,
|
|
433
|
+
durationMs: Date.now() - requestStartTime,
|
|
434
|
+
headers: responseHeaders,
|
|
435
|
+
body: responseBody,
|
|
436
|
+
bodySize: responseSize,
|
|
437
|
+
bodyType: responseType,
|
|
438
|
+
proxied: true,
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
logger.debug(`[Proxy Fetch] ENHANCED PROXY SUCCESS`, {
|
|
270
442
|
requestId,
|
|
271
443
|
responseStatus: response?.status,
|
|
272
444
|
responseOk: response?.ok,
|
|
273
445
|
proxyUsed: true,
|
|
274
446
|
timestamp: new Date().toISOString(),
|
|
275
447
|
});
|
|
276
|
-
logger.debug(`[Proxy Fetch] ✅ Request proxied successfully via enhanced proxy`);
|
|
277
448
|
return response;
|
|
278
449
|
}
|
|
279
450
|
}
|
|
280
451
|
catch (error) {
|
|
281
452
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
282
|
-
logger.debug(
|
|
453
|
+
logger.debug("[Observability] HTTP request failed", {
|
|
454
|
+
requestId,
|
|
455
|
+
url: targetUrl,
|
|
456
|
+
error: errorMessage,
|
|
457
|
+
durationMs: Date.now() - requestStartTime,
|
|
458
|
+
});
|
|
459
|
+
logger.debug(`[Proxy Fetch] ENHANCED ERROR ANALYSIS`, {
|
|
283
460
|
requestId,
|
|
284
461
|
error: errorMessage,
|
|
285
462
|
errorType: error instanceof Error ? error.constructor.name : typeof error,
|
|
@@ -289,14 +466,63 @@ export function createProxyFetch() {
|
|
|
289
466
|
logger.warn(`[Proxy Fetch] Enhanced proxy failed (${errorMessage}), falling back to direct connection`);
|
|
290
467
|
}
|
|
291
468
|
// Fallback to standard fetch
|
|
292
|
-
logger.debug(`[Proxy Fetch]
|
|
469
|
+
logger.debug(`[Proxy Fetch] ENHANCED FALLBACK TO STANDARD FETCH`, {
|
|
293
470
|
requestId,
|
|
294
471
|
fallbackReason: "No proxy configured or proxy failed",
|
|
295
472
|
timestamp: new Date().toISOString(),
|
|
296
473
|
});
|
|
297
|
-
|
|
474
|
+
try {
|
|
475
|
+
const response = await fetchWithRetry(input, init);
|
|
476
|
+
if (logger.shouldLog("debug")) {
|
|
477
|
+
const { parsed: responseBody, size: responseSize, type: responseType, headers: responseHeaders, } = await readResponseBody(response);
|
|
478
|
+
logger.debug("[Observability] HTTP response from LLM provider", {
|
|
479
|
+
requestId,
|
|
480
|
+
url: targetUrl,
|
|
481
|
+
status: response.status,
|
|
482
|
+
statusText: response.statusText,
|
|
483
|
+
durationMs: Date.now() - requestStartTime,
|
|
484
|
+
headers: responseHeaders,
|
|
485
|
+
body: responseBody,
|
|
486
|
+
bodySize: responseSize,
|
|
487
|
+
bodyType: responseType,
|
|
488
|
+
proxied: false,
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
return response;
|
|
492
|
+
}
|
|
493
|
+
catch (fallbackError) {
|
|
494
|
+
const fallbackMessage = fallbackError instanceof Error
|
|
495
|
+
? fallbackError.message
|
|
496
|
+
: String(fallbackError);
|
|
497
|
+
logger.debug("[Observability] HTTP request failed", {
|
|
498
|
+
requestId,
|
|
499
|
+
url: targetUrl,
|
|
500
|
+
error: fallbackMessage,
|
|
501
|
+
durationMs: Date.now() - requestStartTime,
|
|
502
|
+
});
|
|
503
|
+
throw fallbackError;
|
|
504
|
+
}
|
|
298
505
|
};
|
|
299
506
|
}
|
|
507
|
+
/**
|
|
508
|
+
* Mask credentials in a proxy URL for safe logging/reporting.
|
|
509
|
+
*/
|
|
510
|
+
function maskProxyUrl(url) {
|
|
511
|
+
if (!url) {
|
|
512
|
+
return null;
|
|
513
|
+
}
|
|
514
|
+
try {
|
|
515
|
+
const u = new URL(url);
|
|
516
|
+
if (u.username || u.password) {
|
|
517
|
+
u.username = "***";
|
|
518
|
+
u.password = "***";
|
|
519
|
+
}
|
|
520
|
+
return u.toString();
|
|
521
|
+
}
|
|
522
|
+
catch {
|
|
523
|
+
return "[invalid-url]";
|
|
524
|
+
}
|
|
525
|
+
}
|
|
300
526
|
/**
|
|
301
527
|
* Get enhanced proxy status information
|
|
302
528
|
*/
|
|
@@ -308,10 +534,10 @@ export function getProxyStatus() {
|
|
|
308
534
|
const noProxy = process.env.NO_PROXY || process.env.no_proxy;
|
|
309
535
|
return {
|
|
310
536
|
enabled: !!(httpsProxy || httpProxy || allProxy || socksProxy),
|
|
311
|
-
httpProxy: httpProxy
|
|
312
|
-
httpsProxy: httpsProxy
|
|
313
|
-
allProxy: allProxy
|
|
314
|
-
socksProxy: socksProxy
|
|
537
|
+
httpProxy: maskProxyUrl(httpProxy),
|
|
538
|
+
httpsProxy: maskProxyUrl(httpsProxy),
|
|
539
|
+
allProxy: maskProxyUrl(allProxy),
|
|
540
|
+
socksProxy: maskProxyUrl(socksProxy),
|
|
315
541
|
noProxy: noProxy || null,
|
|
316
542
|
method: "enhanced-proxy-agent",
|
|
317
543
|
capabilities: [
|
|
@@ -394,11 +394,20 @@ export function initializeOpenTelemetry(config) {
|
|
|
394
394
|
const provider = globalProvider;
|
|
395
395
|
// Add ContextEnricher for trace name enrichment
|
|
396
396
|
provider.addSpanProcessor(new ContextEnricher());
|
|
397
|
-
//
|
|
398
|
-
|
|
397
|
+
// Only add LangfuseSpanProcessor if the host has not already registered one.
|
|
398
|
+
// When skipLangfuseSpanProcessor is true, the host (e.g. Curator) already
|
|
399
|
+
// registers its own LangfuseSpanProcessor via a DeferredSpanProcessor, so
|
|
400
|
+
// adding another one here would cause duplicate trace exports to Langfuse.
|
|
401
|
+
const skipLangfuse = config.skipLangfuseSpanProcessor === true;
|
|
402
|
+
if (!skipLangfuse) {
|
|
403
|
+
provider.addSpanProcessor(langfuseProcessor);
|
|
404
|
+
}
|
|
399
405
|
logger.info(`${LOG_PREFIX} Auto-registered processors with global TracerProvider`, {
|
|
400
|
-
processors:
|
|
406
|
+
processors: skipLangfuse
|
|
407
|
+
? ["ContextEnricher"]
|
|
408
|
+
: ["ContextEnricher", "LangfuseSpanProcessor"],
|
|
401
409
|
reason: "External provider mode with auto-registration",
|
|
410
|
+
skippedLangfuseSpanProcessor: skipLangfuse,
|
|
402
411
|
});
|
|
403
412
|
}
|
|
404
413
|
else {
|
|
@@ -17,6 +17,7 @@ export declare class TelemetryService {
|
|
|
17
17
|
private aiRequestDuration?;
|
|
18
18
|
private aiTokensUsed?;
|
|
19
19
|
private aiProviderErrors?;
|
|
20
|
+
private aiCostUsd?;
|
|
20
21
|
private mcpToolCalls?;
|
|
21
22
|
private connectionCounter?;
|
|
22
23
|
private responseTimeHistogram?;
|
|
@@ -32,7 +33,7 @@ export declare class TelemetryService {
|
|
|
32
33
|
private initializeMetrics;
|
|
33
34
|
initialize(): Promise<void>;
|
|
34
35
|
traceAIRequest<T>(provider: string, operation: () => Promise<T>, operationType?: string): Promise<T>;
|
|
35
|
-
recordAIRequest(provider: string, model: string, tokens: number, duration: number): void;
|
|
36
|
+
recordAIRequest(provider: string, model: string, tokens: number, duration: number, cost?: number): void;
|
|
36
37
|
recordAIError(provider: string, error: Error): void;
|
|
37
38
|
recordMCPToolCall(toolName: string, duration: number, success: boolean): void;
|
|
38
39
|
recordConnection(type: "websocket" | "sse" | "http"): void;
|
|
@@ -16,6 +16,7 @@ export class TelemetryService {
|
|
|
16
16
|
aiRequestDuration;
|
|
17
17
|
aiTokensUsed;
|
|
18
18
|
aiProviderErrors;
|
|
19
|
+
aiCostUsd;
|
|
19
20
|
mcpToolCalls;
|
|
20
21
|
connectionCounter;
|
|
21
22
|
responseTimeHistogram;
|
|
@@ -79,6 +80,9 @@ export class TelemetryService {
|
|
|
79
80
|
this.aiTokensUsed = this.meter.createCounter("ai_tokens_used_total", {
|
|
80
81
|
description: "Total number of AI tokens used",
|
|
81
82
|
});
|
|
83
|
+
this.aiCostUsd = this.meter.createCounter("ai_cost_usd_total", {
|
|
84
|
+
description: "Total accumulated AI cost in USD",
|
|
85
|
+
});
|
|
82
86
|
this.aiProviderErrors = this.meter.createCounter("ai_provider_errors_total", {
|
|
83
87
|
description: "Total number of AI provider errors",
|
|
84
88
|
});
|
|
@@ -136,7 +140,7 @@ export class TelemetryService {
|
|
|
136
140
|
}
|
|
137
141
|
}
|
|
138
142
|
// Metrics Recording (NO-OP when disabled)
|
|
139
|
-
recordAIRequest(provider, model, tokens, duration) {
|
|
143
|
+
recordAIRequest(provider, model, tokens, duration, cost) {
|
|
140
144
|
// Track runtime metrics
|
|
141
145
|
this.requestCount++;
|
|
142
146
|
this.totalResponseTime += duration;
|
|
@@ -148,6 +152,9 @@ export class TelemetryService {
|
|
|
148
152
|
this.aiRequestCounter.add(1, labels);
|
|
149
153
|
this.aiRequestDuration?.record(duration, labels);
|
|
150
154
|
this.aiTokensUsed?.add(tokens, labels);
|
|
155
|
+
if (cost !== undefined && Number.isFinite(cost) && cost > 0) {
|
|
156
|
+
this.aiCostUsd?.add(cost, labels);
|
|
157
|
+
}
|
|
151
158
|
}
|
|
152
159
|
recordAIError(provider, error) {
|
|
153
160
|
// Track runtime metrics
|
|
@@ -343,7 +343,9 @@ export type BudgetFileInput = {
|
|
|
343
343
|
/** Optional file type hint for type-aware token estimation */
|
|
344
344
|
fileType?: string;
|
|
345
345
|
};
|
|
346
|
-
/**
|
|
346
|
+
/**
|
|
347
|
+
* @deprecated Use ToolOutputPreviewOptions instead.
|
|
348
|
+
*/
|
|
347
349
|
export type TruncateOptions = {
|
|
348
350
|
maxBytes?: number;
|
|
349
351
|
maxLines?: number;
|
|
@@ -351,13 +353,35 @@ export type TruncateOptions = {
|
|
|
351
353
|
saveToDisk?: boolean;
|
|
352
354
|
saveDir?: string;
|
|
353
355
|
};
|
|
354
|
-
/**
|
|
356
|
+
/**
|
|
357
|
+
* @deprecated Use ToolOutputPreviewResult instead.
|
|
358
|
+
*/
|
|
355
359
|
export type TruncateResult = {
|
|
356
360
|
content: string;
|
|
357
361
|
truncated: boolean;
|
|
358
362
|
savedPath?: string;
|
|
359
363
|
originalSize: number;
|
|
360
364
|
};
|
|
365
|
+
/** Options for tool output preview generation. */
|
|
366
|
+
export type ToolOutputPreviewOptions = {
|
|
367
|
+
/** Maximum bytes for the preview (default: 50KB) */
|
|
368
|
+
maxBytes?: number;
|
|
369
|
+
/** Maximum lines for the preview (default: 2000) */
|
|
370
|
+
maxLines?: number;
|
|
371
|
+
/** Fraction of preview budget allocated to the head (default: 0.25) */
|
|
372
|
+
headRatio?: number;
|
|
373
|
+
/** Fraction of preview budget allocated to the tail (default: 0.75) */
|
|
374
|
+
tailRatio?: number;
|
|
375
|
+
};
|
|
376
|
+
/** Result of tool output preview generation. */
|
|
377
|
+
export type ToolOutputPreviewResult = {
|
|
378
|
+
/** The preview string (or full output if under limits) */
|
|
379
|
+
preview: string;
|
|
380
|
+
/** Whether truncation was applied */
|
|
381
|
+
truncated: boolean;
|
|
382
|
+
/** Original byte size of the full output */
|
|
383
|
+
originalSize: number;
|
|
384
|
+
};
|
|
361
385
|
/** Result of tool pair repair. */
|
|
362
386
|
export type RepairResult = {
|
|
363
387
|
repaired: boolean;
|