@juspay/neurolink 9.42.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 (84) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/dist/auth/anthropicOAuth.js +12 -0
  3. package/dist/browser/neurolink.min.js +337 -336
  4. package/dist/cli/commands/mcp.d.ts +6 -0
  5. package/dist/cli/commands/mcp.js +188 -184
  6. package/dist/cli/commands/proxy.js +537 -518
  7. package/dist/core/baseProvider.d.ts +6 -1
  8. package/dist/core/baseProvider.js +208 -230
  9. package/dist/core/factory.d.ts +3 -0
  10. package/dist/core/factory.js +138 -188
  11. package/dist/evaluation/pipeline/evaluationPipeline.js +5 -2
  12. package/dist/evaluation/scorers/scorerRegistry.d.ts +3 -0
  13. package/dist/evaluation/scorers/scorerRegistry.js +353 -282
  14. package/dist/lib/auth/anthropicOAuth.js +12 -0
  15. package/dist/lib/core/baseProvider.d.ts +6 -1
  16. package/dist/lib/core/baseProvider.js +208 -230
  17. package/dist/lib/core/factory.d.ts +3 -0
  18. package/dist/lib/core/factory.js +138 -188
  19. package/dist/lib/evaluation/pipeline/evaluationPipeline.js +5 -2
  20. package/dist/lib/evaluation/scorers/scorerRegistry.d.ts +3 -0
  21. package/dist/lib/evaluation/scorers/scorerRegistry.js +353 -282
  22. package/dist/lib/mcp/toolRegistry.d.ts +2 -0
  23. package/dist/lib/mcp/toolRegistry.js +32 -31
  24. package/dist/lib/neurolink.d.ts +38 -0
  25. package/dist/lib/neurolink.js +1858 -1689
  26. package/dist/lib/providers/googleAiStudio.js +0 -5
  27. package/dist/lib/providers/googleVertex.d.ts +10 -0
  28. package/dist/lib/providers/googleVertex.js +436 -444
  29. package/dist/lib/providers/litellm.d.ts +1 -0
  30. package/dist/lib/providers/litellm.js +73 -64
  31. package/dist/lib/providers/ollama.js +17 -4
  32. package/dist/lib/providers/openAI.d.ts +2 -0
  33. package/dist/lib/providers/openAI.js +139 -140
  34. package/dist/lib/proxy/claudeFormat.js +12 -4
  35. package/dist/lib/proxy/oauthFetch.js +298 -318
  36. package/dist/lib/proxy/proxyConfig.js +3 -1
  37. package/dist/lib/proxy/proxyFetch.js +250 -222
  38. package/dist/lib/proxy/requestLogger.js +132 -45
  39. package/dist/lib/proxy/sseInterceptor.js +36 -11
  40. package/dist/lib/server/routes/claudeProxyRoutes.d.ts +10 -1
  41. package/dist/lib/server/routes/claudeProxyRoutes.js +2726 -2272
  42. package/dist/lib/services/server/ai/observability/instrumentation.js +194 -218
  43. package/dist/lib/tasks/backends/bullmqBackend.js +24 -18
  44. package/dist/lib/tasks/store/redisTaskStore.js +23 -16
  45. package/dist/lib/tasks/taskManager.d.ts +2 -0
  46. package/dist/lib/tasks/taskManager.js +100 -5
  47. package/dist/lib/telemetry/telemetryService.js +9 -5
  48. package/dist/lib/types/proxyTypes.d.ts +124 -1
  49. package/dist/lib/utils/providerHealth.d.ts +1 -0
  50. package/dist/lib/utils/providerHealth.js +46 -31
  51. package/dist/lib/utils/providerUtils.js +11 -22
  52. package/dist/mcp/toolRegistry.d.ts +2 -0
  53. package/dist/mcp/toolRegistry.js +32 -31
  54. package/dist/neurolink.d.ts +38 -0
  55. package/dist/neurolink.js +1858 -1689
  56. package/dist/providers/googleAiStudio.js +0 -5
  57. package/dist/providers/googleVertex.d.ts +10 -0
  58. package/dist/providers/googleVertex.js +436 -444
  59. package/dist/providers/litellm.d.ts +1 -0
  60. package/dist/providers/litellm.js +73 -64
  61. package/dist/providers/ollama.js +17 -4
  62. package/dist/providers/openAI.d.ts +2 -0
  63. package/dist/providers/openAI.js +139 -140
  64. package/dist/proxy/claudeFormat.js +12 -4
  65. package/dist/proxy/oauthFetch.js +298 -318
  66. package/dist/proxy/proxyConfig.js +3 -1
  67. package/dist/proxy/proxyFetch.js +250 -222
  68. package/dist/proxy/requestLogger.js +132 -45
  69. package/dist/proxy/sseInterceptor.js +36 -11
  70. package/dist/server/routes/claudeProxyRoutes.d.ts +10 -1
  71. package/dist/server/routes/claudeProxyRoutes.js +2726 -2272
  72. package/dist/services/server/ai/observability/instrumentation.js +194 -218
  73. package/dist/tasks/backends/bullmqBackend.js +24 -18
  74. package/dist/tasks/store/redisTaskStore.js +23 -16
  75. package/dist/tasks/taskManager.d.ts +2 -0
  76. package/dist/tasks/taskManager.js +100 -5
  77. package/dist/telemetry/telemetryService.js +9 -5
  78. package/dist/types/proxyTypes.d.ts +124 -1
  79. package/dist/utils/providerHealth.d.ts +1 -0
  80. package/dist/utils/providerHealth.js +46 -31
  81. package/dist/utils/providerUtils.js +12 -22
  82. package/package.json +3 -2
  83. package/scripts/observability/check-proxy-telemetry.mjs +1 -1
  84. package/scripts/observability/manage-local-openobserve.sh +36 -5
@@ -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
- async function injectTraceContext(init) {
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 = new Headers(init?.headers);
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 async (input, init) => {
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
- // Return enhanced proxy-aware fetch function
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.