@juspay/neurolink 9.42.0 → 9.43.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 +8 -0
- package/dist/auth/anthropicOAuth.js +12 -0
- package/dist/browser/neurolink.min.js +335 -334
- package/dist/cli/commands/mcp.d.ts +6 -0
- package/dist/cli/commands/mcp.js +200 -184
- package/dist/cli/commands/proxy.js +560 -518
- package/dist/core/baseProvider.d.ts +6 -1
- package/dist/core/baseProvider.js +219 -232
- package/dist/core/factory.d.ts +3 -0
- package/dist/core/factory.js +140 -190
- package/dist/core/modules/ToolsManager.d.ts +1 -0
- package/dist/core/modules/ToolsManager.js +40 -42
- package/dist/core/toolEvents.d.ts +3 -0
- package/dist/core/toolEvents.js +7 -0
- package/dist/evaluation/pipeline/evaluationPipeline.js +5 -2
- package/dist/evaluation/scorers/scorerRegistry.d.ts +3 -0
- package/dist/evaluation/scorers/scorerRegistry.js +356 -284
- package/dist/lib/auth/anthropicOAuth.js +12 -0
- package/dist/lib/core/baseProvider.d.ts +6 -1
- package/dist/lib/core/baseProvider.js +219 -232
- package/dist/lib/core/factory.d.ts +3 -0
- package/dist/lib/core/factory.js +140 -190
- package/dist/lib/core/modules/ToolsManager.d.ts +1 -0
- package/dist/lib/core/modules/ToolsManager.js +40 -42
- package/dist/lib/core/toolEvents.d.ts +3 -0
- package/dist/lib/core/toolEvents.js +8 -0
- package/dist/lib/evaluation/pipeline/evaluationPipeline.js +5 -2
- package/dist/lib/evaluation/scorers/scorerRegistry.d.ts +3 -0
- package/dist/lib/evaluation/scorers/scorerRegistry.js +356 -284
- package/dist/lib/mcp/toolRegistry.d.ts +2 -0
- package/dist/lib/mcp/toolRegistry.js +32 -31
- package/dist/lib/neurolink.d.ts +38 -0
- package/dist/lib/neurolink.js +1890 -1707
- package/dist/lib/providers/googleAiStudio.js +0 -5
- package/dist/lib/providers/googleNativeGemini3.d.ts +4 -0
- package/dist/lib/providers/googleNativeGemini3.js +39 -1
- package/dist/lib/providers/googleVertex.d.ts +10 -0
- package/dist/lib/providers/googleVertex.js +445 -445
- package/dist/lib/providers/litellm.d.ts +1 -0
- package/dist/lib/providers/litellm.js +73 -64
- package/dist/lib/providers/ollama.js +17 -4
- package/dist/lib/providers/openAI.d.ts +2 -0
- package/dist/lib/providers/openAI.js +139 -140
- package/dist/lib/proxy/claudeFormat.js +14 -5
- package/dist/lib/proxy/oauthFetch.js +298 -318
- package/dist/lib/proxy/proxyConfig.js +3 -1
- package/dist/lib/proxy/proxyFetch.js +250 -222
- package/dist/lib/proxy/proxyHealth.d.ts +17 -0
- package/dist/lib/proxy/proxyHealth.js +55 -0
- package/dist/lib/proxy/requestLogger.js +140 -48
- package/dist/lib/proxy/routingPolicy.d.ts +33 -0
- package/dist/lib/proxy/routingPolicy.js +255 -0
- package/dist/lib/proxy/snapshotPersistence.d.ts +2 -0
- package/dist/lib/proxy/snapshotPersistence.js +41 -0
- package/dist/lib/proxy/sseInterceptor.js +36 -11
- package/dist/lib/server/routes/claudeProxyRoutes.d.ts +2 -1
- package/dist/lib/server/routes/claudeProxyRoutes.js +2916 -2377
- package/dist/lib/services/server/ai/observability/instrumentation.js +194 -218
- package/dist/lib/tasks/backends/bullmqBackend.js +24 -18
- package/dist/lib/tasks/store/redisTaskStore.js +42 -17
- package/dist/lib/tasks/taskManager.d.ts +2 -0
- package/dist/lib/tasks/taskManager.js +100 -5
- package/dist/lib/telemetry/telemetryService.js +9 -5
- package/dist/lib/types/cli.d.ts +4 -0
- package/dist/lib/types/proxyTypes.d.ts +211 -1
- package/dist/lib/types/tools.d.ts +18 -0
- package/dist/lib/utils/providerHealth.d.ts +1 -0
- package/dist/lib/utils/providerHealth.js +46 -31
- package/dist/lib/utils/providerUtils.js +11 -22
- package/dist/lib/utils/schemaConversion.d.ts +1 -0
- package/dist/lib/utils/schemaConversion.js +3 -0
- package/dist/mcp/toolRegistry.d.ts +2 -0
- package/dist/mcp/toolRegistry.js +32 -31
- package/dist/neurolink.d.ts +38 -0
- package/dist/neurolink.js +1890 -1707
- package/dist/providers/googleAiStudio.js +0 -5
- package/dist/providers/googleNativeGemini3.d.ts +4 -0
- package/dist/providers/googleNativeGemini3.js +39 -1
- package/dist/providers/googleVertex.d.ts +10 -0
- package/dist/providers/googleVertex.js +445 -445
- package/dist/providers/litellm.d.ts +1 -0
- package/dist/providers/litellm.js +73 -64
- package/dist/providers/ollama.js +17 -4
- package/dist/providers/openAI.d.ts +2 -0
- package/dist/providers/openAI.js +139 -140
- package/dist/proxy/claudeFormat.js +14 -5
- package/dist/proxy/oauthFetch.js +298 -318
- package/dist/proxy/proxyConfig.js +3 -1
- package/dist/proxy/proxyFetch.js +250 -222
- package/dist/proxy/proxyHealth.d.ts +17 -0
- package/dist/proxy/proxyHealth.js +54 -0
- package/dist/proxy/requestLogger.js +140 -48
- package/dist/proxy/routingPolicy.d.ts +33 -0
- package/dist/proxy/routingPolicy.js +254 -0
- package/dist/proxy/snapshotPersistence.d.ts +2 -0
- package/dist/proxy/snapshotPersistence.js +40 -0
- package/dist/proxy/sseInterceptor.js +36 -11
- package/dist/server/routes/claudeProxyRoutes.d.ts +2 -1
- package/dist/server/routes/claudeProxyRoutes.js +2916 -2377
- package/dist/services/server/ai/observability/instrumentation.js +194 -218
- package/dist/tasks/backends/bullmqBackend.js +24 -18
- package/dist/tasks/store/redisTaskStore.js +42 -17
- package/dist/tasks/taskManager.d.ts +2 -0
- package/dist/tasks/taskManager.js +100 -5
- package/dist/telemetry/telemetryService.js +9 -5
- package/dist/types/cli.d.ts +4 -0
- package/dist/types/proxyTypes.d.ts +211 -1
- package/dist/types/tools.d.ts +18 -0
- package/dist/utils/providerHealth.d.ts +1 -0
- package/dist/utils/providerHealth.js +46 -31
- package/dist/utils/providerUtils.js +12 -22
- package/dist/utils/schemaConversion.d.ts +1 -0
- package/dist/utils/schemaConversion.js +3 -0
- package/package.json +3 -2
- package/scripts/observability/check-proxy-telemetry.mjs +1 -1
- package/scripts/observability/manage-local-openobserve.sh +36 -5
package/dist/proxy/proxyFetch.js
CHANGED
|
@@ -7,6 +7,7 @@ import { logger } from "../utils/logger.js";
|
|
|
7
7
|
import { SpanStatusCode, propagation, context } from "@opentelemetry/api";
|
|
8
8
|
import { tracers } from "../telemetry/tracers.js";
|
|
9
9
|
import { shouldBypassProxy } from "./utils/noProxyUtils.js";
|
|
10
|
+
import { createHash } from "node:crypto";
|
|
10
11
|
async function getLangfuseContext() {
|
|
11
12
|
try {
|
|
12
13
|
// Dynamic import to avoid hard dependency — getLangfuseContext is only
|
|
@@ -24,7 +25,17 @@ async function getLangfuseContext() {
|
|
|
24
25
|
* - The NeuroLink proxy to link proxy spans as children of the calling SDK's trace
|
|
25
26
|
* - Conversation-level session/user attribution on proxy spans
|
|
26
27
|
*/
|
|
27
|
-
|
|
28
|
+
function mergeTraceHeaders(input, init) {
|
|
29
|
+
const existingHeaders = new Headers(input instanceof Request ? input.headers : undefined);
|
|
30
|
+
if (init?.headers) {
|
|
31
|
+
const initHeaders = new Headers(init.headers);
|
|
32
|
+
for (const [key, value] of initHeaders.entries()) {
|
|
33
|
+
existingHeaders.set(key, value);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return existingHeaders;
|
|
37
|
+
}
|
|
38
|
+
async function injectTraceContext(input, init) {
|
|
28
39
|
const carrier = {};
|
|
29
40
|
propagation.inject(context.active(), carrier);
|
|
30
41
|
// Also inject NeuroLink session context from Langfuse AsyncLocalStorage
|
|
@@ -41,7 +52,7 @@ async function injectTraceContext(init) {
|
|
|
41
52
|
if (Object.keys(carrier).length === 0) {
|
|
42
53
|
return init ?? {};
|
|
43
54
|
}
|
|
44
|
-
const existingHeaders =
|
|
55
|
+
const existingHeaders = mergeTraceHeaders(input, init);
|
|
45
56
|
for (const [key, value] of Object.entries(carrier)) {
|
|
46
57
|
if (!existingHeaders.has(key)) {
|
|
47
58
|
existingHeaders.set(key, value);
|
|
@@ -321,6 +332,234 @@ async function createProxyAgent(proxyUrl) {
|
|
|
321
332
|
throw new Error(`Unsupported proxy protocol: ${parsed.protocol}`);
|
|
322
333
|
}
|
|
323
334
|
}
|
|
335
|
+
function sanitizeProxyUrl(url) {
|
|
336
|
+
return maskProxyUrl(url) ?? "NOT_SET";
|
|
337
|
+
}
|
|
338
|
+
function getTargetUrl(input) {
|
|
339
|
+
return typeof input === "string"
|
|
340
|
+
? input
|
|
341
|
+
: input instanceof URL
|
|
342
|
+
? input.href
|
|
343
|
+
: input.url;
|
|
344
|
+
}
|
|
345
|
+
function createDirectFetchHandler() {
|
|
346
|
+
return async (input, init) => {
|
|
347
|
+
const enrichedInit = await injectTraceContext(input, init);
|
|
348
|
+
const reqId = `req-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
|
349
|
+
const startTs = Date.now();
|
|
350
|
+
const url = getTargetUrl(input);
|
|
351
|
+
if (logger.shouldLog("debug")) {
|
|
352
|
+
const { size: bodySize, type: bodyType } = parseBody(enrichedInit?.body);
|
|
353
|
+
logger.debug("[Observability] HTTP request to LLM provider", {
|
|
354
|
+
requestId: reqId,
|
|
355
|
+
url,
|
|
356
|
+
method: enrichedInit?.method || "POST",
|
|
357
|
+
bodySize,
|
|
358
|
+
bodyType,
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
try {
|
|
362
|
+
const response = await fetchWithRetry(input, enrichedInit);
|
|
363
|
+
if (logger.shouldLog("debug")) {
|
|
364
|
+
const { parsed: responseBody, size: responseSize, type: responseType, headers: responseHeaders, } = await readResponseBody(response);
|
|
365
|
+
logger.debug("[Observability] HTTP response from LLM provider", {
|
|
366
|
+
requestId: reqId,
|
|
367
|
+
url,
|
|
368
|
+
status: response.status,
|
|
369
|
+
statusText: response.statusText,
|
|
370
|
+
durationMs: Date.now() - startTs,
|
|
371
|
+
contentLength: responseSize,
|
|
372
|
+
hasContent: !!responseBody,
|
|
373
|
+
bodyType: responseType,
|
|
374
|
+
responseHeaders,
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
return response;
|
|
378
|
+
}
|
|
379
|
+
catch (error) {
|
|
380
|
+
logger.debug("[Observability] HTTP request failed", {
|
|
381
|
+
requestId: reqId,
|
|
382
|
+
url,
|
|
383
|
+
error: error instanceof Error ? error.message : String(error),
|
|
384
|
+
durationMs: Date.now() - startTs,
|
|
385
|
+
});
|
|
386
|
+
throw error;
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
async function executeProxiedFetch(input, init, proxyEnv) {
|
|
391
|
+
const { httpsProxy, httpProxy, allProxy, socksProxy, noProxy } = proxyEnv;
|
|
392
|
+
init = await injectTraceContext(input, init);
|
|
393
|
+
const requestId = `req-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
|
394
|
+
const requestStartTime = Date.now();
|
|
395
|
+
const targetUrl = getTargetUrl(input);
|
|
396
|
+
if (logger.shouldLog("debug")) {
|
|
397
|
+
const { size: bodySize, type: bodyType } = parseBody(init?.body);
|
|
398
|
+
logger.debug("[Observability] HTTP request to LLM provider", {
|
|
399
|
+
requestId,
|
|
400
|
+
url: targetUrl,
|
|
401
|
+
method: init?.method || "POST",
|
|
402
|
+
bodySize,
|
|
403
|
+
bodyType,
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
logger.debug(`[Proxy Fetch] ENHANCED REQUEST START`, {
|
|
407
|
+
requestId,
|
|
408
|
+
targetUrl,
|
|
409
|
+
timestamp: new Date().toISOString(),
|
|
410
|
+
httpProxy: sanitizeProxyUrl(httpProxy),
|
|
411
|
+
httpsProxy: sanitizeProxyUrl(httpsProxy),
|
|
412
|
+
allProxy: sanitizeProxyUrl(allProxy),
|
|
413
|
+
socksProxy: sanitizeProxyUrl(socksProxy),
|
|
414
|
+
noProxy: noProxy || "NOT_SET",
|
|
415
|
+
initMethod: init?.method || "GET",
|
|
416
|
+
});
|
|
417
|
+
// Clone the request before any proxy attempt so that if the proxy path
|
|
418
|
+
// consumes the body stream and then fails, the fallback still has an intact
|
|
419
|
+
// body to send.
|
|
420
|
+
const requestClone = input instanceof Request ? input.clone() : null;
|
|
421
|
+
try {
|
|
422
|
+
const proxyUrl = selectProxyUrl(targetUrl);
|
|
423
|
+
if (proxyUrl) {
|
|
424
|
+
const url = new URL(targetUrl);
|
|
425
|
+
logger.debug(`[Proxy Fetch] 🔗 ENHANCED URL ANALYSIS`, {
|
|
426
|
+
requestId,
|
|
427
|
+
targetUrl,
|
|
428
|
+
urlHostname: url.hostname,
|
|
429
|
+
urlProtocol: url.protocol,
|
|
430
|
+
urlPort: url.port,
|
|
431
|
+
selectedProxyUrl: sanitizeProxyUrl(proxyUrl),
|
|
432
|
+
timestamp: new Date().toISOString(),
|
|
433
|
+
});
|
|
434
|
+
logger.debug(`[Proxy Fetch] 🎯 ENHANCED PROXY AGENT CREATION`, {
|
|
435
|
+
requestId,
|
|
436
|
+
proxyUrl: sanitizeProxyUrl(proxyUrl),
|
|
437
|
+
targetHostname: url.hostname,
|
|
438
|
+
targetProtocol: url.protocol,
|
|
439
|
+
aboutToCreateProxyAgent: true,
|
|
440
|
+
timestamp: new Date().toISOString(),
|
|
441
|
+
});
|
|
442
|
+
const globalWithCache = globalThis;
|
|
443
|
+
if (!globalWithCache.__NL_PROXY_AGENT_CACHE__) {
|
|
444
|
+
globalWithCache.__NL_PROXY_AGENT_CACHE__ = new Map();
|
|
445
|
+
}
|
|
446
|
+
const agentCache = globalWithCache.__NL_PROXY_AGENT_CACHE__;
|
|
447
|
+
const cacheKey = createHash("sha256")
|
|
448
|
+
.update(maskProxyUrl(proxyUrl) ?? proxyUrl)
|
|
449
|
+
.digest("hex");
|
|
450
|
+
const dispatcher = agentCache.get(cacheKey) || (await createProxyAgent(proxyUrl));
|
|
451
|
+
agentCache.set(cacheKey, dispatcher);
|
|
452
|
+
logger.debug(`[Proxy Fetch] ✅ ENHANCED PROXY AGENT CREATED`, {
|
|
453
|
+
requestId,
|
|
454
|
+
hasDispatcher: !!dispatcher,
|
|
455
|
+
dispatcherType: typeof dispatcher,
|
|
456
|
+
dispatcherConstructor: dispatcher?.constructor?.name || "unknown",
|
|
457
|
+
timestamp: new Date().toISOString(),
|
|
458
|
+
});
|
|
459
|
+
let fetchInput;
|
|
460
|
+
let fetchInit = { ...init };
|
|
461
|
+
if (input instanceof Request) {
|
|
462
|
+
fetchInput = input.url;
|
|
463
|
+
fetchInit = {
|
|
464
|
+
method: input.method,
|
|
465
|
+
headers: input.headers,
|
|
466
|
+
body: input.body,
|
|
467
|
+
...init,
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
else {
|
|
471
|
+
fetchInput = input;
|
|
472
|
+
}
|
|
473
|
+
const undici = await import("undici");
|
|
474
|
+
const response = await undici.fetch(fetchInput, {
|
|
475
|
+
...fetchInit,
|
|
476
|
+
dispatcher,
|
|
477
|
+
});
|
|
478
|
+
if (logger.shouldLog("debug")) {
|
|
479
|
+
const { parsed: responseBody, size: responseSize, type: responseType, headers: responseHeaders, } = await readResponseBody(response);
|
|
480
|
+
logger.debug("[Observability] HTTP response from LLM provider", {
|
|
481
|
+
requestId,
|
|
482
|
+
url: targetUrl,
|
|
483
|
+
status: response?.status,
|
|
484
|
+
statusText: response?.statusText,
|
|
485
|
+
durationMs: Date.now() - requestStartTime,
|
|
486
|
+
contentLength: responseSize,
|
|
487
|
+
hasContent: !!responseBody,
|
|
488
|
+
bodyType: responseType,
|
|
489
|
+
proxied: true,
|
|
490
|
+
responseHeaders,
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
logger.debug(`[Proxy Fetch] ENHANCED PROXY SUCCESS`, {
|
|
494
|
+
requestId,
|
|
495
|
+
responseStatus: response?.status,
|
|
496
|
+
responseOk: response?.ok,
|
|
497
|
+
proxyUsed: true,
|
|
498
|
+
timestamp: new Date().toISOString(),
|
|
499
|
+
});
|
|
500
|
+
return response;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
catch (error) {
|
|
504
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
505
|
+
logger.debug("[Observability] HTTP request failed", {
|
|
506
|
+
requestId,
|
|
507
|
+
url: targetUrl,
|
|
508
|
+
error: errorMessage,
|
|
509
|
+
durationMs: Date.now() - requestStartTime,
|
|
510
|
+
});
|
|
511
|
+
logger.debug(`[Proxy Fetch] ENHANCED ERROR ANALYSIS`, {
|
|
512
|
+
requestId,
|
|
513
|
+
error: errorMessage,
|
|
514
|
+
errorType: error instanceof Error ? error.constructor.name : typeof error,
|
|
515
|
+
willFallback: true,
|
|
516
|
+
timestamp: new Date().toISOString(),
|
|
517
|
+
});
|
|
518
|
+
logger.warn(`[Proxy Fetch] Enhanced proxy failed (${errorMessage}), falling back to direct connection`);
|
|
519
|
+
}
|
|
520
|
+
logger.debug(`[Proxy Fetch] ENHANCED FALLBACK TO STANDARD FETCH`, {
|
|
521
|
+
requestId,
|
|
522
|
+
fallbackReason: "No proxy configured or proxy failed",
|
|
523
|
+
timestamp: new Date().toISOString(),
|
|
524
|
+
});
|
|
525
|
+
// Use the cloned request for the fallback so that the body stream is not
|
|
526
|
+
// already consumed from the proxy attempt above.
|
|
527
|
+
const fallbackInput = (input instanceof Request ? (requestClone ?? input) : input);
|
|
528
|
+
try {
|
|
529
|
+
const response = await fetchWithRetry(fallbackInput, init);
|
|
530
|
+
if (logger.shouldLog("debug")) {
|
|
531
|
+
const { parsed: responseBody, size: responseSize, type: responseType, headers: responseHeaders, } = await readResponseBody(response);
|
|
532
|
+
logger.debug("[Observability] HTTP response from LLM provider", {
|
|
533
|
+
requestId,
|
|
534
|
+
url: targetUrl,
|
|
535
|
+
status: response.status,
|
|
536
|
+
statusText: response.statusText,
|
|
537
|
+
durationMs: Date.now() - requestStartTime,
|
|
538
|
+
contentLength: responseSize,
|
|
539
|
+
hasContent: !!responseBody,
|
|
540
|
+
bodyType: responseType,
|
|
541
|
+
proxied: false,
|
|
542
|
+
responseHeaders,
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
return response;
|
|
546
|
+
}
|
|
547
|
+
catch (fallbackError) {
|
|
548
|
+
const fallbackMessage = fallbackError instanceof Error
|
|
549
|
+
? fallbackError.message
|
|
550
|
+
: String(fallbackError);
|
|
551
|
+
logger.debug("[Observability] HTTP request failed", {
|
|
552
|
+
requestId,
|
|
553
|
+
url: targetUrl,
|
|
554
|
+
error: fallbackMessage,
|
|
555
|
+
durationMs: Date.now() - requestStartTime,
|
|
556
|
+
});
|
|
557
|
+
throw fallbackError;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
function createProxiedFetchHandler(proxyEnv) {
|
|
561
|
+
return async (input, init) => executeProxiedFetch(input, init, proxyEnv);
|
|
562
|
+
}
|
|
324
563
|
// ==================== ENHANCED PROXY FETCH FUNCTION ====================
|
|
325
564
|
/**
|
|
326
565
|
* Create a proxy-aware fetch function with enhanced capabilities
|
|
@@ -333,9 +572,14 @@ export function createProxyFetch() {
|
|
|
333
572
|
const allProxy = process.env.ALL_PROXY || process.env.all_proxy;
|
|
334
573
|
const socksProxy = process.env.SOCKS_PROXY || process.env.socks_proxy;
|
|
335
574
|
const noProxy = process.env.NO_PROXY || process.env.no_proxy;
|
|
575
|
+
const proxyEnv = {
|
|
576
|
+
httpsProxy,
|
|
577
|
+
httpProxy,
|
|
578
|
+
allProxy,
|
|
579
|
+
socksProxy,
|
|
580
|
+
noProxy,
|
|
581
|
+
};
|
|
336
582
|
// ENHANCED LOGGING: Capture ALL proxy-related environment variables — credentials redacted
|
|
337
|
-
// Reuse module-level maskProxyUrl, defaulting to "NOT_SET" for undefined values
|
|
338
|
-
const sanitizeProxyUrl = (url) => maskProxyUrl(url) ?? "NOT_SET";
|
|
339
583
|
if (logger.shouldLog("debug")) {
|
|
340
584
|
const allProxyRelatedEnvVars = Object.keys(process.env)
|
|
341
585
|
.filter((key) => key.toLowerCase().includes("proxy"))
|
|
@@ -358,54 +602,7 @@ export function createProxyFetch() {
|
|
|
358
602
|
// If no proxy configured, return instrumented standard fetch
|
|
359
603
|
if (!httpsProxy && !httpProxy && !allProxy && !socksProxy) {
|
|
360
604
|
logger.debug("[Proxy Fetch] No proxy environment variables found - using standard fetch");
|
|
361
|
-
return
|
|
362
|
-
// Inject OTel traceparent so the proxy can link to this trace
|
|
363
|
-
const enrichedInit = await injectTraceContext(init);
|
|
364
|
-
const reqId = `req-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
|
365
|
-
const startTs = Date.now();
|
|
366
|
-
const url = typeof input === "string"
|
|
367
|
-
? input
|
|
368
|
-
: input instanceof URL
|
|
369
|
-
? input.href
|
|
370
|
-
: input.url;
|
|
371
|
-
if (logger.shouldLog("debug")) {
|
|
372
|
-
const { size: bodySize, type: bodyType } = parseBody(enrichedInit?.body);
|
|
373
|
-
logger.debug("[Observability] HTTP request to LLM provider", {
|
|
374
|
-
requestId: reqId,
|
|
375
|
-
url,
|
|
376
|
-
method: enrichedInit?.method || "POST",
|
|
377
|
-
bodySize,
|
|
378
|
-
bodyType,
|
|
379
|
-
});
|
|
380
|
-
}
|
|
381
|
-
try {
|
|
382
|
-
const response = await fetchWithRetry(input, enrichedInit);
|
|
383
|
-
if (logger.shouldLog("debug")) {
|
|
384
|
-
const { parsed: responseBody, size: responseSize, type: responseType, headers: responseHeaders, } = await readResponseBody(response);
|
|
385
|
-
logger.debug("[Observability] HTTP response from LLM provider", {
|
|
386
|
-
requestId: reqId,
|
|
387
|
-
url,
|
|
388
|
-
status: response.status,
|
|
389
|
-
statusText: response.statusText,
|
|
390
|
-
durationMs: Date.now() - startTs,
|
|
391
|
-
contentLength: responseSize,
|
|
392
|
-
hasContent: !!responseBody,
|
|
393
|
-
bodyType: responseType,
|
|
394
|
-
responseHeaders,
|
|
395
|
-
});
|
|
396
|
-
}
|
|
397
|
-
return response;
|
|
398
|
-
}
|
|
399
|
-
catch (error) {
|
|
400
|
-
logger.debug("[Observability] HTTP request failed", {
|
|
401
|
-
requestId: reqId,
|
|
402
|
-
url,
|
|
403
|
-
error: error instanceof Error ? error.message : String(error),
|
|
404
|
-
durationMs: Date.now() - startTs,
|
|
405
|
-
});
|
|
406
|
-
throw error;
|
|
407
|
-
}
|
|
408
|
-
};
|
|
605
|
+
return createDirectFetchHandler();
|
|
409
606
|
}
|
|
410
607
|
logger.debug(`[Proxy Fetch] Configuring enhanced proxy with multiple protocol support`);
|
|
411
608
|
logger.debug(`[Proxy Fetch] HTTP_PROXY: ${sanitizeProxyUrl(httpProxy)}`);
|
|
@@ -413,176 +610,7 @@ export function createProxyFetch() {
|
|
|
413
610
|
logger.debug(`[Proxy Fetch] ALL_PROXY: ${sanitizeProxyUrl(allProxy)}`);
|
|
414
611
|
logger.debug(`[Proxy Fetch] SOCKS_PROXY: ${sanitizeProxyUrl(socksProxy)}`);
|
|
415
612
|
logger.debug(`[Proxy Fetch] NO_PROXY: ${noProxy || "not set"}`);
|
|
416
|
-
|
|
417
|
-
return async (input, init) => {
|
|
418
|
-
// Inject OTel traceparent so the proxy can link to this trace
|
|
419
|
-
init = await injectTraceContext(init);
|
|
420
|
-
const requestId = `req-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
|
421
|
-
const requestStartTime = Date.now();
|
|
422
|
-
// Determine target URL
|
|
423
|
-
const targetUrl = typeof input === "string"
|
|
424
|
-
? input
|
|
425
|
-
: input instanceof URL
|
|
426
|
-
? input.href
|
|
427
|
-
: input.url;
|
|
428
|
-
// Request logging with sensitive header redaction — gated behind debug check
|
|
429
|
-
if (logger.shouldLog("debug")) {
|
|
430
|
-
const { size: bodySize, type: bodyType } = parseBody(init?.body);
|
|
431
|
-
logger.debug("[Observability] HTTP request to LLM provider", {
|
|
432
|
-
requestId,
|
|
433
|
-
url: targetUrl,
|
|
434
|
-
method: init?.method || "POST",
|
|
435
|
-
bodySize,
|
|
436
|
-
bodyType,
|
|
437
|
-
});
|
|
438
|
-
}
|
|
439
|
-
logger.debug(`[Proxy Fetch] ENHANCED REQUEST START`, {
|
|
440
|
-
requestId,
|
|
441
|
-
targetUrl,
|
|
442
|
-
timestamp: new Date().toISOString(),
|
|
443
|
-
httpProxy: sanitizeProxyUrl(httpProxy),
|
|
444
|
-
httpsProxy: sanitizeProxyUrl(httpsProxy),
|
|
445
|
-
allProxy: sanitizeProxyUrl(allProxy),
|
|
446
|
-
socksProxy: sanitizeProxyUrl(socksProxy),
|
|
447
|
-
initMethod: init?.method || "GET",
|
|
448
|
-
});
|
|
449
|
-
try {
|
|
450
|
-
// Enhanced proxy selection with NO_PROXY bypass and multiple protocols
|
|
451
|
-
const proxyUrl = selectProxyUrl(targetUrl);
|
|
452
|
-
if (proxyUrl) {
|
|
453
|
-
const url = new URL(targetUrl);
|
|
454
|
-
const sanitizedProxy = sanitizeProxyUrl(proxyUrl);
|
|
455
|
-
logger.debug(`[Proxy Fetch] 🔗 ENHANCED URL ANALYSIS`, {
|
|
456
|
-
requestId,
|
|
457
|
-
targetUrl,
|
|
458
|
-
urlHostname: url.hostname,
|
|
459
|
-
urlProtocol: url.protocol,
|
|
460
|
-
urlPort: url.port,
|
|
461
|
-
selectedProxyUrl: sanitizedProxy,
|
|
462
|
-
timestamp: new Date().toISOString(),
|
|
463
|
-
});
|
|
464
|
-
logger.debug(`[Proxy Fetch] 🎯 ENHANCED PROXY AGENT CREATION`, {
|
|
465
|
-
requestId,
|
|
466
|
-
proxyUrl: sanitizedProxy,
|
|
467
|
-
targetHostname: url.hostname,
|
|
468
|
-
targetProtocol: url.protocol,
|
|
469
|
-
aboutToCreateProxyAgent: true,
|
|
470
|
-
timestamp: new Date().toISOString(),
|
|
471
|
-
});
|
|
472
|
-
// Create/reuse proxy agent (HTTP/HTTPS/SOCKS)
|
|
473
|
-
const agentCache = globalThis.__NL_PROXY_AGENT_CACHE__ ??
|
|
474
|
-
(globalThis.__NL_PROXY_AGENT_CACHE__ = new Map());
|
|
475
|
-
const cacheKey = maskProxyUrl(proxyUrl) ?? proxyUrl; // mask credentials in cache key
|
|
476
|
-
const dispatcher = agentCache.get(cacheKey) || (await createProxyAgent(proxyUrl));
|
|
477
|
-
agentCache.set(cacheKey, dispatcher);
|
|
478
|
-
logger.debug(`[Proxy Fetch] ✅ ENHANCED PROXY AGENT CREATED`, {
|
|
479
|
-
requestId,
|
|
480
|
-
hasDispatcher: !!dispatcher,
|
|
481
|
-
dispatcherType: typeof dispatcher,
|
|
482
|
-
dispatcherConstructor: dispatcher?.constructor?.name || "unknown",
|
|
483
|
-
timestamp: new Date().toISOString(),
|
|
484
|
-
});
|
|
485
|
-
// Handle Request objects by extracting URL and merging properties
|
|
486
|
-
let fetchInput;
|
|
487
|
-
let fetchInit = { ...init };
|
|
488
|
-
if (input instanceof Request) {
|
|
489
|
-
fetchInput = input.url;
|
|
490
|
-
fetchInit = {
|
|
491
|
-
method: input.method,
|
|
492
|
-
headers: input.headers,
|
|
493
|
-
body: input.body,
|
|
494
|
-
...init, // Allow init to override Request properties
|
|
495
|
-
};
|
|
496
|
-
}
|
|
497
|
-
else {
|
|
498
|
-
fetchInput = input;
|
|
499
|
-
}
|
|
500
|
-
// Use undici fetch with enhanced dispatcher (supports HTTP/HTTPS/SOCKS)
|
|
501
|
-
const undici = await import("undici");
|
|
502
|
-
const response = await undici.fetch(fetchInput, {
|
|
503
|
-
...fetchInit,
|
|
504
|
-
dispatcher: dispatcher,
|
|
505
|
-
});
|
|
506
|
-
if (logger.shouldLog("debug")) {
|
|
507
|
-
const { parsed: responseBody, size: responseSize, type: responseType, headers: responseHeaders, } = await readResponseBody(response);
|
|
508
|
-
logger.debug("[Observability] HTTP response from LLM provider", {
|
|
509
|
-
requestId,
|
|
510
|
-
url: targetUrl,
|
|
511
|
-
status: response?.status,
|
|
512
|
-
statusText: response?.statusText,
|
|
513
|
-
durationMs: Date.now() - requestStartTime,
|
|
514
|
-
contentLength: responseSize,
|
|
515
|
-
hasContent: !!responseBody,
|
|
516
|
-
bodyType: responseType,
|
|
517
|
-
proxied: true,
|
|
518
|
-
responseHeaders,
|
|
519
|
-
});
|
|
520
|
-
}
|
|
521
|
-
logger.debug(`[Proxy Fetch] ENHANCED PROXY SUCCESS`, {
|
|
522
|
-
requestId,
|
|
523
|
-
responseStatus: response?.status,
|
|
524
|
-
responseOk: response?.ok,
|
|
525
|
-
proxyUsed: true,
|
|
526
|
-
timestamp: new Date().toISOString(),
|
|
527
|
-
});
|
|
528
|
-
return response;
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
catch (error) {
|
|
532
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
533
|
-
logger.debug("[Observability] HTTP request failed", {
|
|
534
|
-
requestId,
|
|
535
|
-
url: targetUrl,
|
|
536
|
-
error: errorMessage,
|
|
537
|
-
durationMs: Date.now() - requestStartTime,
|
|
538
|
-
});
|
|
539
|
-
logger.debug(`[Proxy Fetch] ENHANCED ERROR ANALYSIS`, {
|
|
540
|
-
requestId,
|
|
541
|
-
error: errorMessage,
|
|
542
|
-
errorType: error instanceof Error ? error.constructor.name : typeof error,
|
|
543
|
-
willFallback: true,
|
|
544
|
-
timestamp: new Date().toISOString(),
|
|
545
|
-
});
|
|
546
|
-
logger.warn(`[Proxy Fetch] Enhanced proxy failed (${errorMessage}), falling back to direct connection`);
|
|
547
|
-
}
|
|
548
|
-
// Fallback to standard fetch
|
|
549
|
-
logger.debug(`[Proxy Fetch] ENHANCED FALLBACK TO STANDARD FETCH`, {
|
|
550
|
-
requestId,
|
|
551
|
-
fallbackReason: "No proxy configured or proxy failed",
|
|
552
|
-
timestamp: new Date().toISOString(),
|
|
553
|
-
});
|
|
554
|
-
try {
|
|
555
|
-
const response = await fetchWithRetry(input, init);
|
|
556
|
-
if (logger.shouldLog("debug")) {
|
|
557
|
-
const { parsed: responseBody, size: responseSize, type: responseType, headers: responseHeaders, } = await readResponseBody(response);
|
|
558
|
-
logger.debug("[Observability] HTTP response from LLM provider", {
|
|
559
|
-
requestId,
|
|
560
|
-
url: targetUrl,
|
|
561
|
-
status: response.status,
|
|
562
|
-
statusText: response.statusText,
|
|
563
|
-
durationMs: Date.now() - requestStartTime,
|
|
564
|
-
contentLength: responseSize,
|
|
565
|
-
hasContent: !!responseBody,
|
|
566
|
-
bodyType: responseType,
|
|
567
|
-
proxied: false,
|
|
568
|
-
responseHeaders,
|
|
569
|
-
});
|
|
570
|
-
}
|
|
571
|
-
return response;
|
|
572
|
-
}
|
|
573
|
-
catch (fallbackError) {
|
|
574
|
-
const fallbackMessage = fallbackError instanceof Error
|
|
575
|
-
? fallbackError.message
|
|
576
|
-
: String(fallbackError);
|
|
577
|
-
logger.debug("[Observability] HTTP request failed", {
|
|
578
|
-
requestId,
|
|
579
|
-
url: targetUrl,
|
|
580
|
-
error: fallbackMessage,
|
|
581
|
-
durationMs: Date.now() - requestStartTime,
|
|
582
|
-
});
|
|
583
|
-
throw fallbackError;
|
|
584
|
-
}
|
|
585
|
-
};
|
|
613
|
+
return createProxiedFetchHandler(proxyEnv);
|
|
586
614
|
}
|
|
587
615
|
/**
|
|
588
616
|
* Mask credentials in a proxy URL for safe logging/reporting.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ProxyHealthResponse, ProxyReadinessState } from "../types/index.js";
|
|
2
|
+
export type { ProxyHealthResponse, ProxyReadinessState };
|
|
3
|
+
export declare function createProxyReadinessState(startTimeMs?: number): ProxyReadinessState;
|
|
4
|
+
export declare function markProxyReady(state: ProxyReadinessState, readyAtMs?: number): void;
|
|
5
|
+
export declare function buildProxyHealthResponse(state: ProxyReadinessState, options: {
|
|
6
|
+
strategy: string;
|
|
7
|
+
passthrough: boolean;
|
|
8
|
+
version: string;
|
|
9
|
+
now?: number;
|
|
10
|
+
}): ProxyHealthResponse;
|
|
11
|
+
export declare function waitForProxyReadiness(args: {
|
|
12
|
+
host: string;
|
|
13
|
+
port: number;
|
|
14
|
+
timeoutMs?: number;
|
|
15
|
+
intervalMs?: number;
|
|
16
|
+
fetchImpl?: typeof fetch;
|
|
17
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export function createProxyReadinessState(startTimeMs = Date.now()) {
|
|
2
|
+
return {
|
|
3
|
+
startTimeMs,
|
|
4
|
+
acceptingConnections: false,
|
|
5
|
+
ready: false,
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
export function markProxyReady(state, readyAtMs = Date.now()) {
|
|
9
|
+
state.acceptingConnections = true;
|
|
10
|
+
state.ready = true;
|
|
11
|
+
state.readyAtMs = readyAtMs;
|
|
12
|
+
}
|
|
13
|
+
export function buildProxyHealthResponse(state, options) {
|
|
14
|
+
const now = options.now ?? Date.now();
|
|
15
|
+
return {
|
|
16
|
+
status: state.ready ? "ok" : "starting",
|
|
17
|
+
ready: state.ready,
|
|
18
|
+
acceptingConnections: state.acceptingConnections,
|
|
19
|
+
strategy: options.strategy,
|
|
20
|
+
passthrough: options.passthrough,
|
|
21
|
+
version: options.version,
|
|
22
|
+
startedAt: new Date(state.startTimeMs).toISOString(),
|
|
23
|
+
readyAt: state.readyAtMs ? new Date(state.readyAtMs).toISOString() : null,
|
|
24
|
+
uptime: Math.max(0, (now - state.startTimeMs) / 1000),
|
|
25
|
+
healthPath: "/health",
|
|
26
|
+
statusPath: "/status",
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function sleep(ms) {
|
|
30
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
31
|
+
}
|
|
32
|
+
export async function waitForProxyReadiness(args) {
|
|
33
|
+
const timeoutMs = args.timeoutMs ?? 5_000;
|
|
34
|
+
const intervalMs = args.intervalMs ?? 100;
|
|
35
|
+
const fetchImpl = args.fetchImpl ?? fetch;
|
|
36
|
+
const deadline = Date.now() + timeoutMs;
|
|
37
|
+
let lastError;
|
|
38
|
+
while (Date.now() < deadline) {
|
|
39
|
+
try {
|
|
40
|
+
const response = await fetchImpl(`http://${args.host}:${args.port}/health`, {
|
|
41
|
+
signal: AbortSignal.timeout(Math.min(intervalMs * 4, 1_000)),
|
|
42
|
+
});
|
|
43
|
+
if (response.ok) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
lastError = `health endpoint returned ${response.status}`;
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
lastError = error instanceof Error ? error.message : String(error);
|
|
50
|
+
}
|
|
51
|
+
await sleep(intervalMs);
|
|
52
|
+
}
|
|
53
|
+
throw new Error(`Proxy failed readiness check on http://${args.host}:${args.port}/health within ${timeoutMs}ms${lastError ? ` (${lastError})` : ""}`);
|
|
54
|
+
}
|