@juspay/neurolink 9.41.0 → 9.42.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.
Files changed (189) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +7 -1
  3. package/dist/auth/anthropicOAuth.d.ts +18 -3
  4. package/dist/auth/anthropicOAuth.js +137 -4
  5. package/dist/auth/providers/firebase.js +5 -1
  6. package/dist/auth/providers/jwt.js +5 -1
  7. package/dist/auth/providers/workos.js +5 -1
  8. package/dist/auth/sessionManager.d.ts +1 -1
  9. package/dist/auth/sessionManager.js +58 -27
  10. package/dist/browser/neurolink.min.js +337 -318
  11. package/dist/cli/commands/mcp.js +3 -0
  12. package/dist/cli/commands/proxy.d.ts +2 -1
  13. package/dist/cli/commands/proxy.js +279 -16
  14. package/dist/cli/commands/task.js +3 -0
  15. package/dist/cli/factories/commandFactory.d.ts +2 -0
  16. package/dist/cli/factories/commandFactory.js +38 -0
  17. package/dist/cli/parser.js +4 -3
  18. package/dist/client/aiSdkAdapter.js +3 -0
  19. package/dist/client/streamingClient.js +30 -10
  20. package/dist/core/modules/GenerationHandler.js +3 -2
  21. package/dist/core/redisConversationMemoryManager.js +7 -3
  22. package/dist/evaluation/BatchEvaluator.js +4 -1
  23. package/dist/evaluation/hooks/observabilityHooks.js +5 -3
  24. package/dist/evaluation/pipeline/evaluationPipeline.d.ts +3 -2
  25. package/dist/evaluation/pipeline/evaluationPipeline.js +20 -8
  26. package/dist/evaluation/pipeline/strategies/batchStrategy.js +6 -3
  27. package/dist/evaluation/pipeline/strategies/samplingStrategy.js +18 -10
  28. package/dist/lib/auth/anthropicOAuth.d.ts +18 -3
  29. package/dist/lib/auth/anthropicOAuth.js +137 -4
  30. package/dist/lib/auth/providers/firebase.js +5 -1
  31. package/dist/lib/auth/providers/jwt.js +5 -1
  32. package/dist/lib/auth/providers/workos.js +5 -1
  33. package/dist/lib/auth/sessionManager.d.ts +1 -1
  34. package/dist/lib/auth/sessionManager.js +58 -27
  35. package/dist/lib/client/aiSdkAdapter.js +3 -0
  36. package/dist/lib/client/streamingClient.js +30 -10
  37. package/dist/lib/core/modules/GenerationHandler.js +3 -2
  38. package/dist/lib/core/redisConversationMemoryManager.js +7 -3
  39. package/dist/lib/evaluation/BatchEvaluator.js +4 -1
  40. package/dist/lib/evaluation/hooks/observabilityHooks.js +5 -3
  41. package/dist/lib/evaluation/pipeline/evaluationPipeline.d.ts +3 -2
  42. package/dist/lib/evaluation/pipeline/evaluationPipeline.js +20 -8
  43. package/dist/lib/evaluation/pipeline/strategies/batchStrategy.js +6 -3
  44. package/dist/lib/evaluation/pipeline/strategies/samplingStrategy.js +18 -10
  45. package/dist/lib/neurolink.d.ts +3 -2
  46. package/dist/lib/neurolink.js +260 -494
  47. package/dist/lib/observability/otelBridge.d.ts +2 -2
  48. package/dist/lib/observability/otelBridge.js +12 -3
  49. package/dist/lib/providers/amazonBedrock.js +2 -4
  50. package/dist/lib/providers/anthropic.d.ts +9 -5
  51. package/dist/lib/providers/anthropic.js +19 -14
  52. package/dist/lib/providers/anthropicBaseProvider.d.ts +3 -3
  53. package/dist/lib/providers/anthropicBaseProvider.js +5 -4
  54. package/dist/lib/providers/azureOpenai.d.ts +1 -1
  55. package/dist/lib/providers/azureOpenai.js +5 -4
  56. package/dist/lib/providers/googleAiStudio.js +30 -1
  57. package/dist/lib/providers/googleVertex.js +28 -6
  58. package/dist/lib/providers/huggingFace.d.ts +3 -3
  59. package/dist/lib/providers/huggingFace.js +6 -8
  60. package/dist/lib/providers/litellm.js +41 -29
  61. package/dist/lib/providers/mistral.js +2 -1
  62. package/dist/lib/providers/ollama.js +80 -23
  63. package/dist/lib/providers/openAI.js +3 -2
  64. package/dist/lib/providers/openRouter.js +2 -1
  65. package/dist/lib/providers/openaiCompatible.d.ts +4 -4
  66. package/dist/lib/providers/openaiCompatible.js +4 -4
  67. package/dist/lib/proxy/claudeFormat.d.ts +3 -2
  68. package/dist/lib/proxy/claudeFormat.js +25 -20
  69. package/dist/lib/proxy/cloaking/plugins/sessionIdentity.d.ts +2 -6
  70. package/dist/lib/proxy/cloaking/plugins/sessionIdentity.js +9 -33
  71. package/dist/lib/proxy/modelRouter.js +3 -0
  72. package/dist/lib/proxy/oauthFetch.d.ts +1 -1
  73. package/dist/lib/proxy/oauthFetch.js +65 -72
  74. package/dist/lib/proxy/proxyConfig.js +44 -24
  75. package/dist/lib/proxy/proxyEnv.d.ts +19 -0
  76. package/dist/lib/proxy/proxyEnv.js +73 -0
  77. package/dist/lib/proxy/proxyFetch.js +50 -4
  78. package/dist/lib/proxy/proxyTracer.d.ts +133 -0
  79. package/dist/lib/proxy/proxyTracer.js +645 -0
  80. package/dist/lib/proxy/rawStreamCapture.d.ts +10 -0
  81. package/dist/lib/proxy/rawStreamCapture.js +83 -0
  82. package/dist/lib/proxy/requestLogger.d.ts +32 -5
  83. package/dist/lib/proxy/requestLogger.js +406 -37
  84. package/dist/lib/proxy/sseInterceptor.d.ts +97 -0
  85. package/dist/lib/proxy/sseInterceptor.js +402 -0
  86. package/dist/lib/proxy/usageStats.d.ts +4 -3
  87. package/dist/lib/proxy/usageStats.js +25 -12
  88. package/dist/lib/rag/chunkers/MarkdownChunker.js +13 -5
  89. package/dist/lib/rag/chunking/markdownChunker.js +15 -6
  90. package/dist/lib/server/routes/claudeProxyRoutes.d.ts +7 -2
  91. package/dist/lib/server/routes/claudeProxyRoutes.js +1737 -508
  92. package/dist/lib/services/server/ai/observability/instrumentation.d.ts +7 -1
  93. package/dist/lib/services/server/ai/observability/instrumentation.js +240 -40
  94. package/dist/lib/tasks/backends/bullmqBackend.d.ts +1 -0
  95. package/dist/lib/tasks/backends/bullmqBackend.js +14 -7
  96. package/dist/lib/tasks/store/redisTaskStore.d.ts +1 -0
  97. package/dist/lib/tasks/store/redisTaskStore.js +34 -26
  98. package/dist/lib/tasks/taskManager.d.ts +3 -0
  99. package/dist/lib/tasks/taskManager.js +63 -30
  100. package/dist/lib/telemetry/index.d.ts +2 -1
  101. package/dist/lib/telemetry/index.js +2 -1
  102. package/dist/lib/telemetry/telemetryService.d.ts +3 -0
  103. package/dist/lib/telemetry/telemetryService.js +65 -5
  104. package/dist/lib/types/cli.d.ts +10 -0
  105. package/dist/lib/types/proxyTypes.d.ts +37 -5
  106. package/dist/lib/types/streamTypes.d.ts +25 -3
  107. package/dist/lib/utils/messageBuilder.js +3 -2
  108. package/dist/lib/utils/providerHealth.d.ts +18 -0
  109. package/dist/lib/utils/providerHealth.js +240 -9
  110. package/dist/lib/utils/providerUtils.js +14 -8
  111. package/dist/lib/utils/toolChoice.d.ts +4 -0
  112. package/dist/lib/utils/toolChoice.js +7 -0
  113. package/dist/neurolink.d.ts +3 -2
  114. package/dist/neurolink.js +260 -494
  115. package/dist/observability/otelBridge.d.ts +2 -2
  116. package/dist/observability/otelBridge.js +12 -3
  117. package/dist/providers/amazonBedrock.js +2 -4
  118. package/dist/providers/anthropic.d.ts +9 -5
  119. package/dist/providers/anthropic.js +19 -14
  120. package/dist/providers/anthropicBaseProvider.d.ts +3 -3
  121. package/dist/providers/anthropicBaseProvider.js +5 -4
  122. package/dist/providers/azureOpenai.d.ts +1 -1
  123. package/dist/providers/azureOpenai.js +5 -4
  124. package/dist/providers/googleAiStudio.js +30 -1
  125. package/dist/providers/googleVertex.js +28 -6
  126. package/dist/providers/huggingFace.d.ts +3 -3
  127. package/dist/providers/huggingFace.js +6 -7
  128. package/dist/providers/litellm.js +41 -29
  129. package/dist/providers/mistral.js +2 -1
  130. package/dist/providers/ollama.js +80 -23
  131. package/dist/providers/openAI.js +3 -2
  132. package/dist/providers/openRouter.js +2 -1
  133. package/dist/providers/openaiCompatible.d.ts +4 -4
  134. package/dist/providers/openaiCompatible.js +4 -3
  135. package/dist/proxy/claudeFormat.d.ts +3 -2
  136. package/dist/proxy/claudeFormat.js +25 -20
  137. package/dist/proxy/cloaking/plugins/sessionIdentity.d.ts +2 -6
  138. package/dist/proxy/cloaking/plugins/sessionIdentity.js +9 -33
  139. package/dist/proxy/modelRouter.js +3 -0
  140. package/dist/proxy/oauthFetch.d.ts +1 -1
  141. package/dist/proxy/oauthFetch.js +65 -72
  142. package/dist/proxy/proxyConfig.js +44 -24
  143. package/dist/proxy/proxyEnv.d.ts +19 -0
  144. package/dist/proxy/proxyEnv.js +72 -0
  145. package/dist/proxy/proxyFetch.js +50 -4
  146. package/dist/proxy/proxyTracer.d.ts +133 -0
  147. package/dist/proxy/proxyTracer.js +644 -0
  148. package/dist/proxy/rawStreamCapture.d.ts +10 -0
  149. package/dist/proxy/rawStreamCapture.js +82 -0
  150. package/dist/proxy/requestLogger.d.ts +32 -5
  151. package/dist/proxy/requestLogger.js +406 -37
  152. package/dist/proxy/sseInterceptor.d.ts +97 -0
  153. package/dist/proxy/sseInterceptor.js +401 -0
  154. package/dist/proxy/usageStats.d.ts +4 -3
  155. package/dist/proxy/usageStats.js +25 -12
  156. package/dist/rag/chunkers/MarkdownChunker.js +13 -5
  157. package/dist/rag/chunking/markdownChunker.js +15 -6
  158. package/dist/server/routes/claudeProxyRoutes.d.ts +7 -2
  159. package/dist/server/routes/claudeProxyRoutes.js +1737 -508
  160. package/dist/services/server/ai/observability/instrumentation.d.ts +7 -1
  161. package/dist/services/server/ai/observability/instrumentation.js +240 -40
  162. package/dist/tasks/backends/bullmqBackend.d.ts +1 -0
  163. package/dist/tasks/backends/bullmqBackend.js +14 -7
  164. package/dist/tasks/store/redisTaskStore.d.ts +1 -0
  165. package/dist/tasks/store/redisTaskStore.js +34 -26
  166. package/dist/tasks/taskManager.d.ts +3 -0
  167. package/dist/tasks/taskManager.js +63 -30
  168. package/dist/telemetry/index.d.ts +2 -1
  169. package/dist/telemetry/index.js +2 -1
  170. package/dist/telemetry/telemetryService.d.ts +3 -0
  171. package/dist/telemetry/telemetryService.js +65 -5
  172. package/dist/types/cli.d.ts +10 -0
  173. package/dist/types/proxyTypes.d.ts +37 -5
  174. package/dist/types/streamTypes.d.ts +25 -3
  175. package/dist/utils/messageBuilder.js +3 -2
  176. package/dist/utils/providerHealth.d.ts +18 -0
  177. package/dist/utils/providerHealth.js +240 -9
  178. package/dist/utils/providerUtils.js +14 -8
  179. package/dist/utils/toolChoice.d.ts +4 -0
  180. package/dist/utils/toolChoice.js +6 -0
  181. package/docs/assets/dashboards/neurolink-proxy-observability-dashboard.json +6609 -0
  182. package/docs/changelog.md +252 -0
  183. package/package.json +17 -1
  184. package/scripts/observability/check-proxy-telemetry.mjs +235 -0
  185. package/scripts/observability/docker-compose.proxy-observability.yaml +55 -0
  186. package/scripts/observability/import-openobserve-dashboard.mjs +240 -0
  187. package/scripts/observability/manage-local-openobserve.sh +184 -0
  188. package/scripts/observability/otel-collector.proxy-observability.yaml +78 -0
  189. package/scripts/observability/proxy-observability.env.example +23 -0
@@ -251,10 +251,12 @@ export class SSEClient {
251
251
  * Register event handler
252
252
  */
253
253
  on(event, callback) {
254
- if (!this.eventHandlers.has(event)) {
255
- this.eventHandlers.set(event, new Set());
254
+ let handlers = this.eventHandlers.get(event);
255
+ if (!handlers) {
256
+ handlers = new Set();
257
+ this.eventHandlers.set(event, handlers);
256
258
  }
257
- this.eventHandlers.get(event).add(callback);
259
+ handlers.add(callback);
258
260
  }
259
261
  /**
260
262
  * Remove event handler
@@ -315,7 +317,11 @@ export class SSEClient {
315
317
  });
316
318
  while (!done && !error) {
317
319
  if (events.length > 0) {
318
- yield events.shift();
320
+ const nextEvent = events.shift();
321
+ if (!nextEvent) {
322
+ continue;
323
+ }
324
+ yield nextEvent;
319
325
  }
320
326
  else {
321
327
  await new Promise((resolve) => {
@@ -325,7 +331,11 @@ export class SSEClient {
325
331
  }
326
332
  // Yield remaining events
327
333
  while (events.length > 0) {
328
- yield events.shift();
334
+ const nextEvent = events.shift();
335
+ if (!nextEvent) {
336
+ continue;
337
+ }
338
+ yield nextEvent;
329
339
  }
330
340
  if (error) {
331
341
  throw error;
@@ -545,10 +555,12 @@ export class WebSocketStreamingClient {
545
555
  * Register event handler
546
556
  */
547
557
  on(event, callback) {
548
- if (!this.eventHandlers.has(event)) {
549
- this.eventHandlers.set(event, new Set());
558
+ let handlers = this.eventHandlers.get(event);
559
+ if (!handlers) {
560
+ handlers = new Set();
561
+ this.eventHandlers.set(event, handlers);
550
562
  }
551
- this.eventHandlers.get(event).add(callback);
563
+ handlers.add(callback);
552
564
  }
553
565
  /**
554
566
  * Remove event handler
@@ -591,7 +603,11 @@ export class WebSocketStreamingClient {
591
603
  try {
592
604
  while (!disconnected) {
593
605
  if (messageQueue.length > 0) {
594
- yield messageQueue.shift();
606
+ const nextMessage = messageQueue.shift();
607
+ if (nextMessage === undefined) {
608
+ continue;
609
+ }
610
+ yield nextMessage;
595
611
  }
596
612
  else {
597
613
  await new Promise((resolve) => {
@@ -601,7 +617,11 @@ export class WebSocketStreamingClient {
601
617
  }
602
618
  // Yield remaining messages
603
619
  while (messageQueue.length > 0) {
604
- yield messageQueue.shift();
620
+ const nextMessage = messageQueue.shift();
621
+ if (nextMessage === undefined) {
622
+ continue;
623
+ }
624
+ yield nextMessage;
605
625
  }
606
626
  }
607
627
  finally {
@@ -95,6 +95,7 @@ export class GenerationHandler {
95
95
  };
96
96
  }
97
97
  }
98
+ const prepareStep = options.prepareStep;
98
99
  return await generateText({
99
100
  model,
100
101
  messages,
@@ -103,8 +104,8 @@ export class GenerationHandler {
103
104
  stopWhen: stepCountIs(options.maxSteps ?? DEFAULT_MAX_STEPS),
104
105
  ...(shouldUseTools &&
105
106
  options.toolChoice && { toolChoice: options.toolChoice }),
106
- ...(options.prepareStep && {
107
- experimental_prepareStep: ((stepOptions) => options.prepareStep({
107
+ ...(prepareStep && {
108
+ experimental_prepareStep: ((stepOptions) => prepareStep({
108
109
  ...stepOptions,
109
110
  maxSteps: options.maxSteps ?? DEFAULT_MAX_STEPS,
110
111
  })),
@@ -16,6 +16,7 @@ import { runWithCurrentLangfuseContext } from "../services/server/ai/observabili
16
16
  import { logger } from "../utils/logger.js";
17
17
  import { deserializeConversation, getNormalizedConfig, getPooledRedisClient, getSessionKey, getUserSessionsKey, releasePooledRedisClient, scanKeys, serializeConversation, } from "../utils/redis.js";
18
18
  const redisTracer = tracers.redis;
19
+ const REDIS_TIMEOUT_MS = 5000;
19
20
  /**
20
21
  * Redis-based implementation of the ConversationMemoryManager
21
22
  * Uses the same interface but stores data in Redis
@@ -130,13 +131,14 @@ export class RedisConversationMemoryManager {
130
131
  if (!this.redisClient) {
131
132
  return undefined;
132
133
  }
134
+ const redisClient = this.redisClient;
133
135
  return redisTracer.startActiveSpan("neurolink.memory.getSession", { kind: SpanKind.CLIENT, attributes: { "session.id": sessionId } }, async (span) => {
134
136
  if (userId) {
135
137
  span.setAttribute("user.id", userId);
136
138
  }
137
139
  try {
138
140
  const redisKey = getSessionKey(this.redisConfig, sessionId, userId);
139
- const conversationData = await this.redisClient.get(redisKey);
141
+ const conversationData = await withTimeout(redisClient.get(redisKey), REDIS_TIMEOUT_MS);
140
142
  const conversation = deserializeConversation(conversationData || null);
141
143
  if (!conversation) {
142
144
  span.setAttribute("session.found", false);
@@ -630,6 +632,7 @@ export class RedisConversationMemoryManager {
630
632
  logger.warn("[RedisConversationMemoryManager] Redis client not available in buildContextMessages");
631
633
  return [];
632
634
  }
635
+ const redisClient = this.redisClient;
633
636
  // NLK-GAP-012: Add span for buildContext CRUD operation
634
637
  return redisTracer.startActiveSpan("neurolink.memory.buildContext", {
635
638
  kind: SpanKind.CLIENT,
@@ -645,7 +648,7 @@ export class RedisConversationMemoryManager {
645
648
  method: "buildContextMessages",
646
649
  });
647
650
  const redisKey = getSessionKey(this.redisConfig, sessionId, userId);
648
- const conversationData = await this.redisClient.get(redisKey);
651
+ const conversationData = await withTimeout(redisClient.get(redisKey), REDIS_TIMEOUT_MS);
649
652
  const conversation = deserializeConversation(conversationData || null);
650
653
  if (!conversation) {
651
654
  span.setAttribute("session.found", false);
@@ -1080,6 +1083,7 @@ User message: "${userMessage}"`;
1080
1083
  if (!this.redisClient) {
1081
1084
  return false;
1082
1085
  }
1086
+ const redisClient = this.redisClient;
1083
1087
  // NLK-GAP-012: Add span for clearSession CRUD operation
1084
1088
  return redisTracer.startActiveSpan("neurolink.memory.clear", {
1085
1089
  kind: SpanKind.CLIENT,
@@ -1090,7 +1094,7 @@ User message: "${userMessage}"`;
1090
1094
  }, async (span) => {
1091
1095
  try {
1092
1096
  const redisKey = getSessionKey(this.redisConfig, sessionId, userId);
1093
- const result = await this.redisClient.del(redisKey);
1097
+ const result = await withTimeout(redisClient.del(redisKey), REDIS_TIMEOUT_MS);
1094
1098
  if (Number(result) > 0) {
1095
1099
  // Remove session from user's session set
1096
1100
  if (userId) {
@@ -6,6 +6,9 @@ import { Evaluator } from "./index.js";
6
6
  import { createBatchEvaluationError, isRetryableEvaluationError, } from "./errors/EvaluationError.js";
7
7
  import { logger } from "../utils/logger.js";
8
8
  import { NeuroLinkFeatureError } from "../core/infrastructure/index.js";
9
+ function hasEvaluationData(result) {
10
+ return result.success && result.data !== undefined;
11
+ }
9
12
  /**
10
13
  * BatchEvaluator - Performs evaluation on multiple items in parallel.
11
14
  * Supports configurable concurrency, retry logic, and progress tracking.
@@ -201,7 +204,7 @@ export class BatchEvaluator {
201
204
  }
202
205
  }
203
206
  // Calculate summary statistics
204
- const successfulResults = results.filter((r) => r.success && r.data);
207
+ const successfulResults = results.filter(hasEvaluationData);
205
208
  const scores = successfulResults.map((r) => r.data.overall);
206
209
  const passingScores = successfulResults.filter((r) => r.data.overall >=
207
210
  (autoEvalConfig.threshold || this.config.threshold || 7));
@@ -41,10 +41,12 @@ export class ObservabilityHooks {
41
41
  * Register an event handler
42
42
  */
43
43
  on(event, handler) {
44
- if (!this._handlers.has(event)) {
45
- this._handlers.set(event, new Set());
44
+ let handlers = this._handlers.get(event);
45
+ if (!handlers) {
46
+ handlers = new Set();
47
+ this._handlers.set(event, handlers);
46
48
  }
47
- this._handlers.get(event).add(handler);
49
+ handlers.add(handler);
48
50
  // Return unsubscribe function
49
51
  return () => {
50
52
  this._handlers.get(event)?.delete(handler);
@@ -12,9 +12,9 @@ export type PipelineExecutionOptions = {
12
12
  correlationId?: string;
13
13
  /** Custom timeout override */
14
14
  timeout?: number;
15
- /** Skip specific scorers */
15
+ /** Skip specific scorers. Mutually exclusive with onlyScorers. */
16
16
  skipScorers?: string[];
17
- /** Only run specific scorers */
17
+ /** Only run specific scorers. Mutually exclusive with skipScorers. */
18
18
  onlyScorers?: string[];
19
19
  /** Additional metadata to attach */
20
20
  metadata?: JsonObject;
@@ -59,6 +59,7 @@ export declare class EvaluationPipeline {
59
59
  * Execute the pipeline on input
60
60
  */
61
61
  execute(input: ScorerInput, options?: PipelineExecutionOptions): Promise<PipelineResult>;
62
+ private _validateExecutionOptions;
62
63
  /**
63
64
  * Get scorers to run based on options
64
65
  */
@@ -91,6 +91,7 @@ export class EvaluationPipeline {
91
91
  if (!this._initialized) {
92
92
  await this.initialize();
93
93
  }
94
+ this._validateExecutionOptions(options);
94
95
  const startTime = Date.now();
95
96
  const correlationId = options?.correlationId ?? `pipeline-${Date.now()}`;
96
97
  logger.debug(`Executing pipeline: ${this._config.name ?? "unnamed"}`, {
@@ -173,16 +174,25 @@ export class EvaluationPipeline {
173
174
  skippedScorers,
174
175
  };
175
176
  }
177
+ _validateExecutionOptions(options) {
178
+ const hasOnlyScorers = !!options?.onlyScorers && options.onlyScorers.length > 0;
179
+ const hasSkipScorers = !!options?.skipScorers && options.skipScorers.length > 0;
180
+ if (hasOnlyScorers && hasSkipScorers) {
181
+ throw new Error("Cannot specify both 'onlyScorers' and 'skipScorers' options");
182
+ }
183
+ }
176
184
  /**
177
185
  * Get scorers to run based on options
178
186
  */
179
187
  _getScorersToRun(options) {
180
188
  const allScorers = Array.from(this._scorers.entries());
181
- if (options?.onlyScorers && options.onlyScorers.length > 0) {
182
- return allScorers.filter(([id]) => options.onlyScorers.includes(id));
189
+ const onlyScorers = options?.onlyScorers;
190
+ const skipScorers = options?.skipScorers;
191
+ if (onlyScorers && onlyScorers.length > 0) {
192
+ return allScorers.filter(([id]) => onlyScorers.includes(id));
183
193
  }
184
- if (options?.skipScorers && options.skipScorers.length > 0) {
185
- return allScorers.filter(([id]) => !options.skipScorers.includes(id));
194
+ if (skipScorers && skipScorers.length > 0) {
195
+ return allScorers.filter(([id]) => !skipScorers.includes(id));
186
196
  }
187
197
  return allScorers;
188
198
  }
@@ -191,11 +201,13 @@ export class EvaluationPipeline {
191
201
  */
192
202
  _getSkippedScorers(options) {
193
203
  const allIds = Array.from(this._scorers.keys());
194
- if (options?.onlyScorers && options.onlyScorers.length > 0) {
195
- return allIds.filter((id) => !options.onlyScorers.includes(id));
204
+ const onlyScorers = options?.onlyScorers;
205
+ const skipScorers = options?.skipScorers;
206
+ if (onlyScorers && onlyScorers.length > 0) {
207
+ return allIds.filter((id) => !onlyScorers.includes(id));
196
208
  }
197
- if (options?.skipScorers && options.skipScorers.length > 0) {
198
- return options.skipScorers.filter((id) => allIds.includes(id));
209
+ if (skipScorers && skipScorers.length > 0) {
210
+ return skipScorers.filter((id) => allIds.includes(id));
199
211
  }
200
212
  return [];
201
213
  }
@@ -2,6 +2,9 @@
2
2
  * @file Batch Strategy
3
3
  * Batch processing for evaluation pipelines
4
4
  */
5
+ function hasPipelineResult(result) {
6
+ return !result.error && result.result !== undefined;
7
+ }
5
8
  /**
6
9
  * Default batch configuration
7
10
  */
@@ -66,7 +69,7 @@ export class BatchStrategy {
66
69
  }
67
70
  // Calculate summary
68
71
  const totalDuration = Date.now() - startTime;
69
- const successfulResults = results.filter((r) => !r.error && r.result);
72
+ const successfulResults = results.filter(hasPipelineResult);
70
73
  const scores = successfulResults.map((r) => r.result.overallScore);
71
74
  const passed = successfulResults.filter((r) => r.result.passed);
72
75
  return {
@@ -192,7 +195,7 @@ export async function* streamBatchEvaluation(pipeline, inputs, config) {
192
195
  durations.push(result.duration);
193
196
  // If continueOnError is false and this result has an error, abort and return summary
194
197
  if (result.error && batchConfig.continueOnError === false) {
195
- const successfulResults = results.filter((r) => !r.error && r.result);
198
+ const successfulResults = results.filter(hasPipelineResult);
196
199
  const earlyScores = successfulResults.map((r) => r.result.overallScore);
197
200
  const earlyPassed = successfulResults.filter((r) => r.result.passed);
198
201
  return {
@@ -219,7 +222,7 @@ export async function* streamBatchEvaluation(pipeline, inputs, config) {
219
222
  }
220
223
  }
221
224
  // Return summary
222
- const successfulResults = results.filter((r) => !r.error && r.result);
225
+ const successfulResults = results.filter(hasPipelineResult);
223
226
  const scores = successfulResults.map((r) => r.result.overallScore);
224
227
  const passed = successfulResults.filter((r) => r.result.passed);
225
228
  return {
@@ -17,6 +17,12 @@ export const DEFAULT_SAMPLING_CONFIG = {
17
17
  maxRate: 1.0,
18
18
  },
19
19
  };
20
+ const DEFAULT_ADAPTIVE_CONFIG = {
21
+ enabled: false,
22
+ qualityThreshold: 0.7,
23
+ minRate: 0.1,
24
+ maxRate: 1.0,
25
+ };
20
26
  /**
21
27
  * Sampling strategy for evaluation
22
28
  */
@@ -27,9 +33,10 @@ export class SamplingStrategy {
27
33
  _maxRecentScores = 100;
28
34
  constructor(config = {}) {
29
35
  const clamp01 = (v) => Math.max(0, Math.min(1, v));
36
+ const defaultAdaptive = DEFAULT_SAMPLING_CONFIG.adaptive ?? DEFAULT_ADAPTIVE_CONFIG;
30
37
  const rawRate = config.rate ?? DEFAULT_SAMPLING_CONFIG.rate;
31
- const rawMinRate = config.adaptive?.minRate ?? DEFAULT_SAMPLING_CONFIG.adaptive.minRate;
32
- const rawMaxRate = config.adaptive?.maxRate ?? DEFAULT_SAMPLING_CONFIG.adaptive.maxRate;
38
+ const rawMinRate = config.adaptive?.minRate ?? defaultAdaptive.minRate;
39
+ const rawMaxRate = config.adaptive?.maxRate ?? defaultAdaptive.maxRate;
33
40
  const minRate = clamp01(Math.min(rawMinRate, rawMaxRate));
34
41
  const maxRate = clamp01(Math.max(rawMinRate, rawMaxRate));
35
42
  this._config = {
@@ -41,9 +48,8 @@ export class SamplingStrategy {
41
48
  ...(config.alwaysEvaluate ?? {}),
42
49
  },
43
50
  adaptive: {
44
- enabled: config.adaptive?.enabled ?? DEFAULT_SAMPLING_CONFIG.adaptive.enabled,
45
- qualityThreshold: clamp01(config.adaptive?.qualityThreshold ??
46
- DEFAULT_SAMPLING_CONFIG.adaptive.qualityThreshold),
51
+ enabled: config.adaptive?.enabled ?? defaultAdaptive.enabled,
52
+ qualityThreshold: clamp01(config.adaptive?.qualityThreshold ?? defaultAdaptive.qualityThreshold),
47
53
  minRate,
48
54
  maxRate,
49
55
  },
@@ -156,9 +162,12 @@ export class SamplingStrategy {
156
162
  */
157
163
  configure(config) {
158
164
  const clamp01 = (v) => Math.max(0, Math.min(1, v));
165
+ const currentAdaptive = this._config.adaptive ??
166
+ DEFAULT_SAMPLING_CONFIG.adaptive ??
167
+ DEFAULT_ADAPTIVE_CONFIG;
159
168
  const rawRate = config.rate ?? this._config.rate;
160
- const rawMinRate = config.adaptive?.minRate ?? this._config.adaptive.minRate;
161
- const rawMaxRate = config.adaptive?.maxRate ?? this._config.adaptive.maxRate;
169
+ const rawMinRate = config.adaptive?.minRate ?? currentAdaptive.minRate;
170
+ const rawMaxRate = config.adaptive?.maxRate ?? currentAdaptive.maxRate;
162
171
  const minRate = clamp01(Math.min(rawMinRate, rawMaxRate));
163
172
  const maxRate = clamp01(Math.max(rawMinRate, rawMaxRate));
164
173
  this._config = {
@@ -170,9 +179,8 @@ export class SamplingStrategy {
170
179
  ...(config.alwaysEvaluate ?? {}),
171
180
  },
172
181
  adaptive: {
173
- enabled: config.adaptive?.enabled ?? this._config.adaptive.enabled,
174
- qualityThreshold: clamp01(config.adaptive?.qualityThreshold ??
175
- this._config.adaptive.qualityThreshold),
182
+ enabled: config.adaptive?.enabled ?? currentAdaptive.enabled,
183
+ qualityThreshold: clamp01(config.adaptive?.qualityThreshold ?? currentAdaptive.qualityThreshold),
176
184
  minRate,
177
185
  maxRate,
178
186
  },
@@ -43,13 +43,28 @@ export declare const DEFAULT_SCOPES: readonly string[];
43
43
  /**
44
44
  * User-Agent string to spoof Claude CLI
45
45
  */
46
- export declare const CLAUDE_CLI_USER_AGENT = "claude-cli/2.1.2 (external, cli)";
46
+ export declare const CLAUDE_CODE_VERSION = "2.1.87.6d6";
47
+ export declare const CLAUDE_CODE_ENTRYPOINT = "sdk-cli";
48
+ export declare const CLAUDE_CLI_USER_AGENT = "claude-cli/2.1.87 (external, sdk-cli)";
49
+ export type ClaudeCodeIdentity = {
50
+ deviceId: string;
51
+ accountUuid: string;
52
+ sessionId: string;
53
+ metadataUserId: string;
54
+ };
55
+ export declare function parseClaudeCodeUserId(userId: unknown): ClaudeCodeIdentity | null;
56
+ export declare function getOrCreateClaudeCodeIdentity(seed: string, options?: {
57
+ existingUserId?: unknown;
58
+ preferredSessionId?: string;
59
+ }): ClaudeCodeIdentity;
60
+ export declare function purgeExpiredClaudeCodeIdentities(now?: number): number;
61
+ export declare function buildStableClaudeCodeBillingHeader(originalText?: string): string;
47
62
  /**
48
63
  * Required beta headers for OAuth API requests.
49
64
  * The "oauth-2025-04-20" header is CRITICAL for OAuth authentication.
50
- * The "interleaved-thinking-2025-05-14" enables extended thinking.
51
65
  */
52
- export declare const OAUTH_BETA_HEADERS = "oauth-2025-04-20,interleaved-thinking-2025-05-14";
66
+ export declare const OAUTH_BETA_HEADERS = "oauth-2025-04-20";
67
+ export declare const CLAUDE_CODE_OAUTH_BETAS: readonly ["oauth-2025-04-20", "claude-code-20250219", "context-management-2025-06-27", "prompt-caching-scope-2026-01-05", "advanced-tool-use-2025-11-20", "effort-2025-11-24"];
53
68
  /**
54
69
  * Tool name prefix required for OAuth API requests
55
70
  */
@@ -14,7 +14,7 @@
14
14
  *
15
15
  * @module auth/anthropicOAuth
16
16
  */
17
- import { createHash, randomBytes } from "crypto";
17
+ import { createHash, createHmac, randomBytes, randomUUID } from "crypto";
18
18
  import { createServer, IncomingMessage, ServerResponse } from "http";
19
19
  import { OAuthError, OAuthConfigurationError, OAuthTokenExchangeError, OAuthTokenRefreshError, OAuthTokenRevocationError, OAuthCallbackServerError, } from "../types/errors.js";
20
20
  import { logger } from "../utils/logger.js";
@@ -73,13 +73,146 @@ export const DEFAULT_SCOPES = [
73
73
  /**
74
74
  * User-Agent string to spoof Claude CLI
75
75
  */
76
- export const CLAUDE_CLI_USER_AGENT = "claude-cli/2.1.2 (external, cli)";
76
+ export const CLAUDE_CODE_VERSION = "2.1.87.6d6";
77
+ export const CLAUDE_CODE_ENTRYPOINT = "sdk-cli";
78
+ export const CLAUDE_CLI_USER_AGENT = "claude-cli/2.1.87 (external, sdk-cli)";
79
+ const CLAUDE_CODE_IDENTITY_TTL_MS = 3_600_000;
80
+ const CLAUDE_CODE_IDENTITY_NAMESPACE = "neurolink-claude-code-identity-v1";
81
+ const claudeCodeIdentityCache = new Map();
82
+ function stableIdentityDigest(input) {
83
+ // These identifiers are deterministic pseudonyms for Claude Code metadata,
84
+ // not password hashes or authentication secrets.
85
+ return createHmac("sha256", CLAUDE_CODE_IDENTITY_NAMESPACE)
86
+ .update(input)
87
+ .digest("hex");
88
+ }
89
+ function hexToUuid(hex) {
90
+ const trimmed = hex.replace(/-/g, "").slice(0, 32).padEnd(32, "0");
91
+ return `${trimmed.slice(0, 8)}-${trimmed.slice(8, 12)}-${trimmed.slice(12, 16)}-${trimmed.slice(16, 20)}-${trimmed.slice(20, 32)}`;
92
+ }
93
+ function isUuid(value) {
94
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
95
+ }
96
+ function buildMetadataUserId(identity) {
97
+ return JSON.stringify({
98
+ device_id: identity.deviceId,
99
+ account_uuid: identity.accountUuid,
100
+ session_id: identity.sessionId,
101
+ });
102
+ }
103
+ export function parseClaudeCodeUserId(userId) {
104
+ if (typeof userId !== "string") {
105
+ return null;
106
+ }
107
+ try {
108
+ const parsed = JSON.parse(userId);
109
+ if (typeof parsed.device_id !== "string" ||
110
+ !/^[0-9a-f]{64}$/i.test(parsed.device_id) ||
111
+ typeof parsed.account_uuid !== "string" ||
112
+ !isUuid(parsed.account_uuid) ||
113
+ typeof parsed.session_id !== "string" ||
114
+ !isUuid(parsed.session_id)) {
115
+ return null;
116
+ }
117
+ return {
118
+ deviceId: parsed.device_id,
119
+ accountUuid: parsed.account_uuid,
120
+ sessionId: parsed.session_id,
121
+ metadataUserId: buildMetadataUserId({
122
+ deviceId: parsed.device_id,
123
+ accountUuid: parsed.account_uuid,
124
+ sessionId: parsed.session_id,
125
+ }),
126
+ };
127
+ }
128
+ catch {
129
+ return null;
130
+ }
131
+ }
132
+ export function getOrCreateClaudeCodeIdentity(seed, options) {
133
+ const parsedExisting = parseClaudeCodeUserId(options?.existingUserId);
134
+ if (parsedExisting) {
135
+ if (options?.preferredSessionId && isUuid(options.preferredSessionId)) {
136
+ return {
137
+ deviceId: parsedExisting.deviceId,
138
+ accountUuid: parsedExisting.accountUuid,
139
+ sessionId: options.preferredSessionId,
140
+ metadataUserId: buildMetadataUserId({
141
+ deviceId: parsedExisting.deviceId,
142
+ accountUuid: parsedExisting.accountUuid,
143
+ sessionId: options.preferredSessionId,
144
+ }),
145
+ };
146
+ }
147
+ return parsedExisting;
148
+ }
149
+ const now = Date.now();
150
+ const cacheKey = seed || "default";
151
+ const cached = claudeCodeIdentityCache.get(cacheKey);
152
+ if (cached && cached.expiresAt > now) {
153
+ if (options?.preferredSessionId && isUuid(options.preferredSessionId)) {
154
+ return {
155
+ deviceId: cached.deviceId,
156
+ accountUuid: cached.accountUuid,
157
+ sessionId: options.preferredSessionId,
158
+ metadataUserId: buildMetadataUserId({
159
+ deviceId: cached.deviceId,
160
+ accountUuid: cached.accountUuid,
161
+ sessionId: options.preferredSessionId,
162
+ }),
163
+ };
164
+ }
165
+ return cached;
166
+ }
167
+ const deviceId = stableIdentityDigest(`${cacheKey}:device`);
168
+ const accountUuid = hexToUuid(stableIdentityDigest(`${cacheKey}:account`));
169
+ const sessionId = options?.preferredSessionId && isUuid(options.preferredSessionId)
170
+ ? options.preferredSessionId
171
+ : randomUUID();
172
+ const identity = {
173
+ deviceId,
174
+ accountUuid,
175
+ sessionId,
176
+ metadataUserId: buildMetadataUserId({
177
+ deviceId,
178
+ accountUuid,
179
+ sessionId,
180
+ }),
181
+ expiresAt: now + CLAUDE_CODE_IDENTITY_TTL_MS,
182
+ };
183
+ claudeCodeIdentityCache.set(cacheKey, identity);
184
+ return identity;
185
+ }
186
+ export function purgeExpiredClaudeCodeIdentities(now = Date.now()) {
187
+ let removed = 0;
188
+ for (const [cacheKey, identity] of claudeCodeIdentityCache.entries()) {
189
+ if (identity.expiresAt <= now) {
190
+ claudeCodeIdentityCache.delete(cacheKey);
191
+ removed += 1;
192
+ }
193
+ }
194
+ return removed;
195
+ }
196
+ export function buildStableClaudeCodeBillingHeader(originalText) {
197
+ const version = originalText?.match(/cc_version=([^;]+)/)?.[1]?.trim() ||
198
+ CLAUDE_CODE_VERSION;
199
+ const entrypoint = originalText?.match(/cc_entrypoint=([^;]+)/)?.[1]?.trim() ||
200
+ CLAUDE_CODE_ENTRYPOINT;
201
+ return `x-anthropic-billing-header: cc_version=${version}; cc_entrypoint=${entrypoint}; cch=00000;`;
202
+ }
77
203
  /**
78
204
  * Required beta headers for OAuth API requests.
79
205
  * The "oauth-2025-04-20" header is CRITICAL for OAuth authentication.
80
- * The "interleaved-thinking-2025-05-14" enables extended thinking.
81
206
  */
82
- export const OAUTH_BETA_HEADERS = "oauth-2025-04-20,interleaved-thinking-2025-05-14";
207
+ export const OAUTH_BETA_HEADERS = "oauth-2025-04-20";
208
+ export const CLAUDE_CODE_OAUTH_BETAS = [
209
+ "oauth-2025-04-20",
210
+ "claude-code-20250219",
211
+ "context-management-2025-06-27",
212
+ "prompt-caching-scope-2026-01-05",
213
+ "advanced-tool-use-2025-11-20",
214
+ "effort-2025-11-24",
215
+ ];
83
216
  /**
84
217
  * Tool name prefix required for OAuth API requests
85
218
  */
@@ -60,8 +60,12 @@ export class FirebaseAuthProvider extends BaseAuthProvider {
60
60
  await this.initialize();
61
61
  }
62
62
  try {
63
+ const jwks = this.jwks;
64
+ if (!jwks) {
65
+ throw AuthError.create("PROVIDER_INIT_FAILED", "Firebase JWKS was not initialized", { details: { provider: "firebase" } });
66
+ }
63
67
  // Verify the token using Google's public keys
64
- const { payload } = await jose.jwtVerify(token, this.jwks, {
68
+ const { payload } = await jose.jwtVerify(token, jwks, {
65
69
  issuer: `https://securetoken.google.com/${this.projectId}`,
66
70
  audience: this.projectId,
67
71
  });
@@ -92,6 +92,10 @@ export class JWTProvider extends BaseAuthProvider {
92
92
  await this.initialize();
93
93
  }
94
94
  try {
95
+ const keyObject = this.keyObject;
96
+ if (!keyObject) {
97
+ throw AuthError.create("PROVIDER_INIT_FAILED", "JWT verification key was not initialized", { details: { provider: "jwt" } });
98
+ }
95
99
  const verifyOptions = {};
96
100
  if (this.algorithms.length > 0) {
97
101
  verifyOptions.algorithms = this
@@ -103,7 +107,7 @@ export class JWTProvider extends BaseAuthProvider {
103
107
  if (this.audience) {
104
108
  verifyOptions.audience = this.audience;
105
109
  }
106
- const { payload } = await jose.jwtVerify(token, this.keyObject, verifyOptions);
110
+ const { payload } = await jose.jwtVerify(token, keyObject, verifyOptions);
107
111
  // Reject tokens without a non-empty sub claim
108
112
  if (!payload.sub) {
109
113
  return {
@@ -65,8 +65,12 @@ export class WorkOSProvider extends BaseAuthProvider {
65
65
  await this.initialize();
66
66
  }
67
67
  try {
68
+ const jwks = this.jwks;
69
+ if (!jwks) {
70
+ throw AuthError.create("PROVIDER_INIT_FAILED", "WorkOS JWKS was not initialized", { details: { provider: "workos" } });
71
+ }
68
72
  // Verify the JWT
69
- const { payload } = await jose.jwtVerify(token, this.jwks, {
73
+ const { payload } = await jose.jwtVerify(token, jwks, {
70
74
  audience: this.clientId,
71
75
  });
72
76
  // Enforce organizationId if configured
@@ -46,7 +46,7 @@ export declare class MemorySessionStorage implements SessionManagerStorage {
46
46
  * Redis session storage
47
47
  *
48
48
  * Distributed session storage using Redis. Suitable for multi-instance
49
- * deployments. Requires ioredis or similar Redis client.
49
+ * deployments. Requires the "redis" (node-redis) package.
50
50
  *
51
51
  * Note: Redis client must be provided or configured via environment.
52
52
  */