@juspay/neurolink 10.12.4 → 10.12.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/browser/neurolink.min.js +420 -420
  3. package/dist/cli/commands/proxy.js +13 -17
  4. package/dist/cli/commands/proxyAnalyze.js +8 -3
  5. package/dist/lib/processors/archive/ArchiveProcessor.js +13 -67
  6. package/dist/lib/processors/archive/zipEntryReader.d.ts +51 -0
  7. package/dist/lib/processors/archive/zipEntryReader.js +81 -0
  8. package/dist/lib/processors/document/OpenDocumentProcessor.js +13 -1
  9. package/dist/lib/processors/document/PptxProcessor.js +58 -12
  10. package/dist/lib/proxy/logCleanupScheduler.d.ts +12 -0
  11. package/dist/lib/proxy/logCleanupScheduler.js +74 -0
  12. package/dist/lib/proxy/logCleanupWorkerEntry.d.ts +1 -0
  13. package/dist/lib/proxy/logCleanupWorkerEntry.js +15 -0
  14. package/dist/lib/proxy/proxyAnalysis.js +6 -0
  15. package/dist/lib/proxy/requestLogger.d.ts +6 -0
  16. package/dist/lib/proxy/requestLogger.js +57 -47
  17. package/dist/lib/proxy/rollingWorkerSupervisor.d.ts +2 -0
  18. package/dist/lib/proxy/rollingWorkerSupervisor.js +40 -2
  19. package/dist/lib/server/routes/claudeProxyRoutes.d.ts +2 -0
  20. package/dist/lib/server/routes/claudeProxyRoutes.js +33 -2
  21. package/dist/lib/types/processor.d.ts +15 -0
  22. package/dist/lib/types/proxy.d.ts +32 -1
  23. package/dist/processors/archive/ArchiveProcessor.js +13 -67
  24. package/dist/processors/archive/zipEntryReader.d.ts +51 -0
  25. package/dist/processors/archive/zipEntryReader.js +80 -0
  26. package/dist/processors/document/OpenDocumentProcessor.js +13 -1
  27. package/dist/processors/document/PptxProcessor.js +58 -12
  28. package/dist/proxy/logCleanupScheduler.d.ts +12 -0
  29. package/dist/proxy/logCleanupScheduler.js +73 -0
  30. package/dist/proxy/logCleanupWorkerEntry.d.ts +1 -0
  31. package/dist/proxy/logCleanupWorkerEntry.js +14 -0
  32. package/dist/proxy/proxyAnalysis.js +6 -0
  33. package/dist/proxy/requestLogger.d.ts +6 -0
  34. package/dist/proxy/requestLogger.js +57 -47
  35. package/dist/proxy/rollingWorkerSupervisor.d.ts +2 -0
  36. package/dist/proxy/rollingWorkerSupervisor.js +40 -2
  37. package/dist/server/routes/claudeProxyRoutes.d.ts +2 -0
  38. package/dist/server/routes/claudeProxyRoutes.js +33 -2
  39. package/dist/types/processor.d.ts +15 -0
  40. package/dist/types/proxy.d.ts +32 -1
  41. package/package.json +5 -3
@@ -63,6 +63,7 @@ let lastKnownAccountCount = 0;
63
63
  const MAX_AUTH_RETRIES = 5;
64
64
  const MAX_TRANSIENT_SAME_ACCOUNT_RETRIES = 2;
65
65
  const TRANSIENT_SAME_ACCOUNT_RETRY_DELAYS_MS = [250, 1_000];
66
+ const OVERLOAD_ACCOUNT_ROTATION_DELAYS_MS = [250, 500, 1_000, 2_000];
66
67
  const MAX_FALLBACK_NETWORK_RETRIES = 1;
67
68
  const FALLBACK_STREAM_IDLE_TIMEOUT_MS = 2 * 60 * 1000;
68
69
  /** Maximum upstream 429 attempts per account before rotating — for a TRANSIENT
@@ -2883,7 +2884,13 @@ async function handleAnthropicStreamingSuccessResponse(args) {
2883
2884
  });
2884
2885
  return {
2885
2886
  retryNextAccount: true,
2886
- failure: { message: preflight.message, rateLimit: isRateLimit },
2887
+ failure: {
2888
+ message: preflight.message,
2889
+ rateLimit: isRateLimit,
2890
+ ...(preflight.errorType === "overloaded_error"
2891
+ ? { retryDelayMs: getOverloadRotationDelayMs(attemptNumber) }
2892
+ : {}),
2893
+ },
2887
2894
  };
2888
2895
  }
2889
2896
  logAttempt(response.status, undefined, undefined, {
@@ -3484,6 +3491,9 @@ async function handleAnthropicAuthRetry(args) {
3484
3491
  const failure = successResult.failure;
3485
3492
  return {
3486
3493
  continueLoop: true,
3494
+ ...(failure?.retryDelayMs
3495
+ ? { retryDelayMs: failure.retryDelayMs }
3496
+ : {}),
3487
3497
  lastError: failure?.message ?? currentLastError,
3488
3498
  authFailureMessage: currentAuthFailureMessage,
3489
3499
  sawRateLimit: currentSawRateLimit || Boolean(failure?.rateLimit),
@@ -3941,6 +3951,9 @@ async function handleAnthropicNonOkResponse(args) {
3941
3951
  return {
3942
3952
  continueLoop: true,
3943
3953
  retrySameAccount: !upstreamOverload,
3954
+ ...(upstreamOverload
3955
+ ? { retryDelayMs: getOverloadRotationDelayMs(attemptNumber) }
3956
+ : {}),
3944
3957
  lastError: currentLastError,
3945
3958
  authFailureMessage: currentAuthFailureMessage,
3946
3959
  sawTransientFailure: currentSawTransientFailure,
@@ -4553,7 +4566,8 @@ async function handleAnthropicRoutedClaudeRequest(args) {
4553
4566
  effectiveAccounts.every((account) => !isAccountAdmissionAvailable(account.key, accountAdmissionCapacity))) {
4554
4567
  queuedAccountAdmission = await acquireFirstAvailableAccountAdmission(effectiveAccounts.map((account) => account.key), accountAdmissionCapacity, ctx.abortSignal);
4555
4568
  }
4556
- accountLoop: for (const account of effectiveAccounts) {
4569
+ accountLoop: for (const [accountIndex, account,] of effectiveAccounts.entries()) {
4570
+ const hasNextAccount = accountIndex < effectiveAccounts.length - 1;
4557
4571
  const accountState = getOrCreateRuntimeState(account.key);
4558
4572
  let transientSameAccountRetries = 0;
4559
4573
  let rateLimitSameAccountRetries = 0;
@@ -4763,6 +4777,10 @@ async function handleAnthropicRoutedClaudeRequest(args) {
4763
4777
  return authRetryResult.response;
4764
4778
  }
4765
4779
  if (authRetryResult.continueLoop) {
4780
+ if (hasNextAccount && authRetryResult.retryDelayMs) {
4781
+ logger.always(`[proxy] pacing cross-account SSE overload rotation for ${authRetryResult.retryDelayMs}ms after auth refresh`);
4782
+ await sleep(authRetryResult.retryDelayMs);
4783
+ }
4766
4784
  continue accountLoop;
4767
4785
  }
4768
4786
  }
@@ -4804,6 +4822,10 @@ async function handleAnthropicRoutedClaudeRequest(args) {
4804
4822
  if (nonOkResult.retrySameAccount) {
4805
4823
  logger.always(`[proxy] exhausted transient same-account retries for account=${account.label}; rotating`);
4806
4824
  }
4825
+ if (hasNextAccount && nonOkResult.retryDelayMs) {
4826
+ logger.always(`[proxy] pacing cross-account overload rotation for ${nonOkResult.retryDelayMs}ms`);
4827
+ await sleep(nonOkResult.retryDelayMs);
4828
+ }
4807
4829
  continue accountLoop;
4808
4830
  }
4809
4831
  break accountLoop;
@@ -4845,6 +4867,10 @@ async function handleAnthropicRoutedClaudeRequest(args) {
4845
4867
  loopState.lastError = successResult.failure.message;
4846
4868
  loopState.sawRateLimit ||= successResult.failure.rateLimit;
4847
4869
  loopState.sawTransientFailure ||= !successResult.failure.rateLimit;
4870
+ if (hasNextAccount && successResult.failure.retryDelayMs) {
4871
+ logger.always(`[proxy] pacing cross-account SSE overload rotation for ${successResult.failure.retryDelayMs}ms`);
4872
+ await sleep(successResult.failure.retryDelayMs);
4873
+ }
4848
4874
  }
4849
4875
  continue accountLoop;
4850
4876
  }
@@ -5315,6 +5341,10 @@ export function getTransientSameAccountRetryDelayMs(retryNumber) {
5315
5341
  const index = Math.min(Math.max(retryNumber - 1, 0), TRANSIENT_SAME_ACCOUNT_RETRY_DELAYS_MS.length - 1);
5316
5342
  return TRANSIENT_SAME_ACCOUNT_RETRY_DELAYS_MS[index] ?? 0;
5317
5343
  }
5344
+ export function getOverloadRotationDelayMs(attemptNumber) {
5345
+ const index = Math.min(Math.max(attemptNumber - 1, 0), OVERLOAD_ACCOUNT_ROTATION_DELAYS_MS.length - 1);
5346
+ return jitteredDelay(OVERLOAD_ACCOUNT_ROTATION_DELAYS_MS[index] ?? 250);
5347
+ }
5318
5348
  async function sleep(ms) {
5319
5349
  await new Promise((resolve) => setTimeout(resolve, ms));
5320
5350
  }
@@ -5586,6 +5616,7 @@ export const __testHooks = {
5586
5616
  describeTransportError,
5587
5617
  redactProviderErrorMessage,
5588
5618
  isUpstreamOverload,
5619
+ getOverloadRotationDelayMs,
5589
5620
  shouldAttemptClaudeFallback,
5590
5621
  executeClaudeFallbackWithRetry,
5591
5622
  buildClaudeAnthropicFailureResponse,
@@ -774,6 +774,21 @@ export type ArchiveDecompressionResult = {
774
774
  * perfectly well-formed, and telling the user it is damaged would send them to
775
775
  * re-create a file that was never broken.
776
776
  */
777
+ /**
778
+ * The slice of an adm-zip entry the bounded reader depends on.
779
+ *
780
+ * Structural rather than adm-zip's own `IZipEntry` so the reader states what it
781
+ * actually needs — the compressed bytes and the header fields it refuses to
782
+ * trust — instead of importing a library type it would then have to satisfy in
783
+ * full when building a test double.
784
+ */
785
+ export type BoundedZipEntry = {
786
+ getCompressedData: () => Buffer;
787
+ header: {
788
+ method: number;
789
+ crc: number;
790
+ };
791
+ };
777
792
  export type ArchiveEntryReadResult = {
778
793
  readonly status: "ok";
779
794
  readonly buffer: Buffer;
@@ -646,6 +646,7 @@ export type AnthropicSuccessResult = {
646
646
  failure?: {
647
647
  message: string;
648
648
  rateLimit: boolean;
649
+ retryDelayMs?: number;
649
650
  };
650
651
  } | {
651
652
  response: Response | unknown;
@@ -702,6 +703,8 @@ export type AnthropicAuthRetryResult = {
702
703
  response?: Response | unknown;
703
704
  holdsAccountAdmission?: boolean;
704
705
  continueLoop: boolean;
706
+ /** Failure-path pacing before rotating after provider-wide overload. */
707
+ retryDelayMs?: number;
705
708
  lastError: unknown;
706
709
  authFailureMessage: string | null;
707
710
  sawRateLimit: boolean;
@@ -713,6 +716,8 @@ export type AnthropicNonOkResult = {
713
716
  response?: Response | unknown;
714
717
  continueLoop: boolean;
715
718
  retrySameAccount?: boolean;
719
+ /** Failure-path pacing before rotating after provider-wide overload. */
720
+ retryDelayMs?: number;
716
721
  lastError: unknown;
717
722
  authFailureMessage: string | null;
718
723
  sawTransientFailure: boolean;
@@ -1457,6 +1462,9 @@ export type ProxyAnalysisReport = {
1457
1462
  attemptLatency: boolean;
1458
1463
  cacheUsage: boolean;
1459
1464
  routingDecisions: boolean;
1465
+ /** True only when every stream needed for cross-stream request/attempt
1466
+ * reconciliation begins at or before the requested analysis window. */
1467
+ comparableRequestAttempts: boolean;
1460
1468
  };
1461
1469
  dataQuality: {
1462
1470
  linesRead: number;
@@ -1468,6 +1476,8 @@ export type ProxyAnalysisReport = {
1468
1476
  observedFrom: string | null;
1469
1477
  observedTo: string | null;
1470
1478
  startsAtOrBeforeRequestedWindow: boolean;
1479
+ /** Whether this stream can support claims covering the full window. */
1480
+ completeWindow: boolean;
1471
1481
  }>;
1472
1482
  bodyArtifacts: {
1473
1483
  capturesIndexed: number;
@@ -2073,6 +2083,14 @@ export type RollingWorkerFailureDetails = {
2073
2083
  workerExitSignal?: string | null;
2074
2084
  supervisorAction?: "none" | "sigkill_after_transfer_failure";
2075
2085
  };
2086
+ export type RollingWorkerSupervisorEvent = {
2087
+ at: string;
2088
+ type: "activated" | "failure" | "failed_transfer" | "rejected_socket";
2089
+ generation: number | null;
2090
+ version: string | null;
2091
+ phase?: "startup" | "activation" | "runtime" | "transfer";
2092
+ reason?: string;
2093
+ };
2076
2094
  export type RollingWorkerSupervisorSnapshot = {
2077
2095
  generation: number;
2078
2096
  active: {
@@ -2093,6 +2111,8 @@ export type RollingWorkerSupervisorSnapshot = {
2093
2111
  queuedSockets: number;
2094
2112
  rejectedSockets: number;
2095
2113
  failedTransfers: number;
2114
+ /** Bounded generation-scoped evidence for attributing lifetime counters. */
2115
+ recentEvents: RollingWorkerSupervisorEvent[];
2096
2116
  lastFailure: ({
2097
2117
  at: string;
2098
2118
  generation: number;
@@ -2305,7 +2325,18 @@ export type ProxyNeurolinkRuntime = {
2305
2325
  neurolink: {
2306
2326
  getToolRegistry(): MCPToolRegistry;
2307
2327
  };
2308
- cleanupLogs: (daysToKeep?: number, maxFiles?: number) => void;
2328
+ logsDir: string;
2329
+ };
2330
+ /** Data passed to the isolated proxy log-retention worker. */
2331
+ export type ProxyLogCleanupWorkerData = {
2332
+ logsDir: string;
2333
+ maxAgeDays: number;
2334
+ maxSizeMb: number;
2335
+ };
2336
+ /** Lifecycle handle for non-blocking proxy log retention. */
2337
+ export type ProxyLogCleanupScheduler = {
2338
+ trigger: () => boolean;
2339
+ stop: () => Promise<void>;
2309
2340
  };
2310
2341
  /** Hono app + readiness state created by the proxy start command. */
2311
2342
  export type ProxyStartApp = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/neurolink",
3
- "version": "10.12.4",
3
+ "version": "10.12.6",
4
4
  "packageManager": "pnpm@10.15.1",
5
5
  "description": "TypeScript AI SDK with 24+ LLM providers behind one consistent API. MCP-native (connect any MCP server), voice TTS/STT/realtime, RAG, agents, memory, context compaction. OpenAI · Anthropic · Gemini · Bedrock · Azure · Ollama · DeepSeek · NVIDIA NIM and more.",
6
6
  "author": {
@@ -163,7 +163,7 @@
163
163
  "test:system-messages": "npx tsx test/continuous-test-suite-system-messages.ts",
164
164
  "test:test-stubs": "npx tsx test/continuous-test-suite-test-stubs.ts",
165
165
  "test:tool-routing-semantic": "npx tsx test/continuous-test-suite-tool-routing-semantic.ts",
166
- "test:unit": "pnpm run test:envguard && pnpm run test:bugfixes && pnpm run test:file-detector-extension && pnpm run test:file-detector-magic-bytes && pnpm run test:mcp:infra && pnpm run test:mcp:bash && pnpm run test:mcp:limits && pnpm run test:mcp:spans && pnpm run test:autoresearch:redis && pnpm run test:tool-routing && pnpm run test:tool-routing-cli && pnpm run test:tool-dedup && pnpm run test:model-pool && pnpm run test:litellm-context && pnpm run test:dedup-execute-map && pnpm run test:step-budget-guard && pnpm run test:agent-plumbing && pnpm run test:tool-execution-recorder && pnpm run test:proxy-terminal-errors && pnpm run test:proxy-usage-refresh && pnpm run test:system-messages && pnpm run test:tool-routing-semantic && pnpm run test:anthropic-tools-policy && pnpm run test:anthropic-structured && pnpm run test:sagemaker-tools && pnpm run test:anthropic-multimodal && pnpm run test:excel-interop && pnpm run test:model-capabilities && pnpm run test:agent-runtime:vitest && pnpm run test:agent-delegation && pnpm run test:retry-after:vitest && pnpm run test:sampling-params && pnpm run test:structured-recovery && pnpm run test:prompt-redaction && pnpm run test:mcp-result-cache && pnpm run test:test-stubs && pnpm run test:model-not-found-retryable && pnpm run test:websearch-grounding && pnpm run test:archive:security",
166
+ "test:unit": "pnpm run test:envguard && pnpm run test:bugfixes && pnpm run test:file-detector-extension && pnpm run test:file-detector-magic-bytes && pnpm run test:mcp:infra && pnpm run test:mcp:bash && pnpm run test:mcp:limits && pnpm run test:mcp:spans && pnpm run test:autoresearch:redis && pnpm run test:tool-routing && pnpm run test:tool-routing-cli && pnpm run test:tool-dedup && pnpm run test:model-pool && pnpm run test:litellm-context && pnpm run test:dedup-execute-map && pnpm run test:step-budget-guard && pnpm run test:agent-plumbing && pnpm run test:tool-execution-recorder && pnpm run test:proxy-terminal-errors && pnpm run test:proxy-usage-refresh && pnpm run test:system-messages && pnpm run test:tool-routing-semantic && pnpm run test:anthropic-tools-policy && pnpm run test:anthropic-structured && pnpm run test:sagemaker-tools && pnpm run test:anthropic-multimodal && pnpm run test:excel-interop && pnpm run test:model-capabilities && pnpm run test:agent-runtime:vitest && pnpm run test:agent-delegation && pnpm run test:retry-after:vitest && pnpm run test:sampling-params && pnpm run test:structured-recovery && pnpm run test:prompt-redaction && pnpm run test:mcp-result-cache && pnpm run test:test-stubs && pnpm run test:model-not-found-retryable && pnpm run test:websearch-grounding && pnpm run test:archive:security && pnpm run test:office:security && pnpm run test:proxy:vitest",
167
167
  "// CI tier — live providers, runs only when API keys are present (test:credentials and test:dynamic make real provider calls when keys are set, so they live here, not in test:unit)": "",
168
168
  "test:live": "pnpm run test:providers && pnpm run test:mcp:http && pnpm run test:mcp:sdk && pnpm run test:mcp:cli && pnpm run test:observability && pnpm run test:context && pnpm run test:memory && pnpm run test:tool-reliability && pnpm run test:evaluation && pnpm run test:autoresearch && pnpm run test:credentials && pnpm run test:dynamic",
169
169
  "// CI tier — product output (image/video/TTS/PPT) — costs $$ per run": "",
@@ -234,6 +234,7 @@
234
234
  "test:proxy-terminal-errors": "npx tsx test/continuous-test-suite-proxy-terminal-errors.ts",
235
235
  "test:proxy-limit-headers": "npx tsx test/continuous-test-suite-proxy-limit-headers.ts",
236
236
  "test:proxy-usage-refresh": "npx tsx test/continuous-test-suite-proxy-usage-refresh.ts",
237
+ "test:proxy:vitest": "pnpm exec vitest run test/proxyAnalysis.test.ts test/proxyConfigHotReload.test.ts test/proxyObservabilityFoundation.test.ts test/proxyReliabilityHardening.test.ts test/proxyReplay.test.ts test/proxyRollingWorkerHandoff.test.ts test/proxyTestIsolation.test.ts test/proxyUpdaterFallback.test.ts test/proxyUsageStatsPersistence.test.ts",
237
238
  "test:anthropic-limit-capture": "npx tsx test/continuous-test-suite-anthropic-limit-capture.ts",
238
239
  "test:audio": "npx tsx test/continuous-test-suite-audio.ts",
239
240
  "test:file-formats": "npx tsx test/continuous-test-suite-file-formats.ts",
@@ -246,7 +247,8 @@
246
247
  "test:structured-recovery": "npx tsx test/continuous-test-suite-structured-recovery.ts",
247
248
  "test:prompt-redaction": "npx tsx test/continuous-test-suite-prompt-redaction.ts",
248
249
  "test:mcp-result-cache": "npx tsx test/continuous-test-suite-mcp-result-cache.ts",
249
- "test:archive:security": "npx tsx test/continuous-test-suite-archive-security.ts"
250
+ "test:archive:security": "npx tsx test/continuous-test-suite-archive-security.ts",
251
+ "test:office:security": "npx tsx test/continuous-test-suite-office-security.ts"
250
252
  },
251
253
  "files": [
252
254
  "dist",