@juspay/neurolink 9.38.0 → 9.40.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.
@@ -426,8 +426,37 @@ export type GenerateOptions = {
426
426
  read?: boolean;
427
427
  /** Whether to write (add/condense) the conversation into memory after completion. Defaults to true. */
428
428
  write?: boolean;
429
+ /**
430
+ * Additional users whose memory should be retrieved/stored alongside the primary user.
431
+ * Each entry can override the condensation prompt and maxWords for that user.
432
+ * Primary user is still determined by context.userId.
433
+ */
434
+ additionalUsers?: AdditionalMemoryUser[];
429
435
  };
430
436
  };
437
+ /**
438
+ * Represents an additional user whose memory should be included in a generate/stream call.
439
+ * Allows per-user prompt overrides for different memory condensation strategies
440
+ * (e.g. personal preferences vs org-level policies).
441
+ */
442
+ export type AdditionalMemoryUser = {
443
+ /** The user/owner ID to retrieve or store memory for. */
444
+ userId: string;
445
+ /**
446
+ * Human-readable label used in the formatted memory context.
447
+ * E.g. "Organization Policy", "Team Context", "User Preferences".
448
+ * If not provided, defaults to userId.
449
+ */
450
+ label?: string;
451
+ /** Whether to read this user's memory and include in context. Defaults to true. */
452
+ read?: boolean;
453
+ /** Whether to write conversation into this user's memory. Defaults to true. */
454
+ write?: boolean;
455
+ /** Custom condensation prompt for this user. Overrides the default Hippocampus prompt. */
456
+ prompt?: string;
457
+ /** Max words for this user's condensed memory. Overrides the default maxWords. */
458
+ maxWords?: number;
459
+ };
431
460
  /**
432
461
  * Generate function result type - Primary output format
433
462
  * Future-ready for multi-modal outputs while maintaining text focus
@@ -22,7 +22,7 @@ export type { DomainConfig, DomainConfigOptions, DomainEvaluationCriteria, Domai
22
22
  export * from "./evaluation.js";
23
23
  export * from "./evaluationProviders.js";
24
24
  export * from "./fileTypes.js";
25
- export type { EnhancedGenerateResult, EnhancedProvider, FactoryEnhancedProvider, GenerateOptions, GenerateResult as GenerateApiResult, // Renamed to avoid conflict with cli.js GenerateResult
25
+ export type { AdditionalMemoryUser, EnhancedGenerateResult, EnhancedProvider, FactoryEnhancedProvider, GenerateOptions, GenerateResult as GenerateApiResult, // Renamed to avoid conflict with cli.js GenerateResult
26
26
  TextGenerationOptions, TextGenerationResult, UnifiedGenerationOptions, } from "./generateTypes.js";
27
27
  export * from "./hitlTypes.js";
28
28
  export * from "./middlewareTypes.js";
@@ -10,6 +10,7 @@ import type { Content, ImageWithAltText } from "./content.js";
10
10
  import type { ChatMessage } from "./conversation.js";
11
11
  import type { AIModelProviderConfig } from "./providers.js";
12
12
  import type { TTSChunk, TTSOptions } from "./ttsTypes.js";
13
+ import type { AdditionalMemoryUser } from "./generateTypes.js";
13
14
  import type { StandardRecord, ValidationSchema } from "./typeAliases.js";
14
15
  /**
15
16
  * Progress tracking and metadata for streaming operations
@@ -435,6 +436,12 @@ export type StreamOptions = {
435
436
  read?: boolean;
436
437
  /** Whether to write (add/condense) the conversation into memory after completion. Defaults to true. */
437
438
  write?: boolean;
439
+ /**
440
+ * Additional users whose memory should be retrieved/stored alongside the primary user.
441
+ * Each entry can override the condensation prompt and maxWords for that user.
442
+ * Primary user is still determined by context.userId.
443
+ */
444
+ additionalUsers?: AdditionalMemoryUser[];
438
445
  };
439
446
  };
440
447
  /**
@@ -20,8 +20,10 @@ export declare enum AnthropicModel {
20
20
  CLAUDE_3_5_SONNET = "claude-3-5-sonnet-20241022",
21
21
  CLAUDE_3_5_SONNET_V2 = "claude-3-5-sonnet-v2-20241022",
22
22
  CLAUDE_SONNET_4 = "claude-sonnet-4-20250514",
23
+ CLAUDE_SONNET_4_6 = "claude-sonnet-4-6",
23
24
  CLAUDE_3_OPUS = "claude-3-opus-20240229",
24
- CLAUDE_OPUS_4 = "claude-opus-4-20250514"
25
+ CLAUDE_OPUS_4 = "claude-opus-4-20250514",
26
+ CLAUDE_OPUS_4_6 = "claude-opus-4-6"
25
27
  }
26
28
  /**
27
29
  * Model access mapping by subscription tier
@@ -27,10 +27,14 @@ export var AnthropicModel;
27
27
  AnthropicModel["CLAUDE_3_5_SONNET_V2"] = "claude-3-5-sonnet-v2-20241022";
28
28
  // Claude Sonnet 4 (Latest Sonnet)
29
29
  AnthropicModel["CLAUDE_SONNET_4"] = "claude-sonnet-4-20250514";
30
+ // Claude Sonnet 4.6
31
+ AnthropicModel["CLAUDE_SONNET_4_6"] = "claude-sonnet-4-6";
30
32
  // Claude 3 Opus (Legacy flagship)
31
33
  AnthropicModel["CLAUDE_3_OPUS"] = "claude-3-opus-20240229";
32
34
  // Claude Opus 4 (Latest flagship)
33
35
  AnthropicModel["CLAUDE_OPUS_4"] = "claude-opus-4-20250514";
36
+ // Claude Opus 4.6
37
+ AnthropicModel["CLAUDE_OPUS_4_6"] = "claude-opus-4-6";
34
38
  })(AnthropicModel || (AnthropicModel = {}));
35
39
  // ============================================================================
36
40
  // MODEL TIER ACCESS DEFINITIONS
@@ -56,6 +60,7 @@ export const MODEL_TIER_ACCESS = {
56
60
  AnthropicModel.CLAUDE_3_5_SONNET,
57
61
  AnthropicModel.CLAUDE_3_5_SONNET_V2,
58
62
  AnthropicModel.CLAUDE_SONNET_4,
63
+ AnthropicModel.CLAUDE_SONNET_4_6,
59
64
  ],
60
65
  // Max tier: All models including Opus
61
66
  max: ["*"], // All models
@@ -167,6 +172,32 @@ export const MODEL_METADATA = {
167
172
  family: "opus",
168
173
  description: "Latest flagship model with advanced reasoning",
169
174
  },
175
+ // Claude Sonnet 4.6
176
+ [AnthropicModel.CLAUDE_SONNET_4_6]: {
177
+ displayName: "Claude Sonnet 4.6",
178
+ contextWindow: 1000000,
179
+ maxOutputTokens: 64000,
180
+ supportsVision: true,
181
+ supportsExtendedThinking: true,
182
+ supportsToolUse: true,
183
+ supportsStreaming: true,
184
+ deprecated: false,
185
+ family: "sonnet",
186
+ description: "Claude 4.6 Sonnet with 1M context window",
187
+ },
188
+ // Claude Opus 4.6
189
+ [AnthropicModel.CLAUDE_OPUS_4_6]: {
190
+ displayName: "Claude Opus 4.6",
191
+ contextWindow: 1000000,
192
+ maxOutputTokens: 64000,
193
+ supportsVision: true,
194
+ supportsExtendedThinking: true,
195
+ supportsToolUse: true,
196
+ supportsStreaming: true,
197
+ deprecated: false,
198
+ family: "opus",
199
+ description: "Claude 4.6 Opus flagship with 1M context window",
200
+ },
170
201
  };
171
202
  // ============================================================================
172
203
  // DEFAULT MODELS BY TIER
@@ -417,11 +448,16 @@ export function getLatestModelsByFamily() {
417
448
  const familyPriority = {
418
449
  haiku: [AnthropicModel.CLAUDE_3_5_HAIKU, AnthropicModel.CLAUDE_3_HAIKU],
419
450
  sonnet: [
451
+ AnthropicModel.CLAUDE_SONNET_4_6,
420
452
  AnthropicModel.CLAUDE_SONNET_4,
421
453
  AnthropicModel.CLAUDE_3_5_SONNET_V2,
422
454
  AnthropicModel.CLAUDE_3_5_SONNET,
423
455
  ],
424
- opus: [AnthropicModel.CLAUDE_OPUS_4, AnthropicModel.CLAUDE_3_OPUS],
456
+ opus: [
457
+ AnthropicModel.CLAUDE_OPUS_4_6,
458
+ AnthropicModel.CLAUDE_OPUS_4,
459
+ AnthropicModel.CLAUDE_3_OPUS,
460
+ ],
425
461
  };
426
462
  for (const family of Object.keys(familyPriority)) {
427
463
  for (const model of familyPriority[family]) {
@@ -181,6 +181,10 @@ export declare class NeuroLink {
181
181
  private registerMemoryRetrievalTools;
182
182
  /** Format memory context for prompt inclusion */
183
183
  private formatMemoryContext;
184
+ /**
185
+ * Format memory context from multiple users into a labeled block.
186
+ */
187
+ private formatMultiUserMemoryContext;
184
188
  /**
185
189
  * Determine whether memory should be read (retrieved) for this call.
186
190
  * Respects both the global memory SDK config and per-call overrides.
@@ -192,13 +196,14 @@ export declare class NeuroLink {
192
196
  */
193
197
  private shouldWriteMemory;
194
198
  /**
195
- * Retrieve condensed memory for a user.
199
+ * Retrieve condensed memory for a user (and optionally additional users).
196
200
  * Returns the input text enhanced with memory context, or unchanged if no memory.
197
201
  */
198
202
  private retrieveMemory;
199
203
  /**
200
204
  * Store a conversation turn in memory (non-blocking).
201
205
  * Calls add(userId, content) which internally condenses old + new via LLM.
206
+ * Supports additional users with per-user prompt and maxWords overrides.
202
207
  */
203
208
  private storeMemoryInBackground;
204
209
  /**
package/dist/neurolink.js CHANGED
@@ -823,6 +823,20 @@ export class NeuroLink {
823
823
 
824
824
  ${memoryContext}
825
825
 
826
+ Current user's request: ${currentInput}`;
827
+ }
828
+ /**
829
+ * Format memory context from multiple users into a labeled block.
830
+ */
831
+ formatMultiUserMemoryContext(memories, currentInput) {
832
+ const memoryBlocks = [];
833
+ for (const [label, memory] of memories) {
834
+ memoryBlocks.push(`[${label}]\n${memory}`);
835
+ }
836
+ return `Context from previous conversations:
837
+
838
+ ${memoryBlocks.join("\n\n")}
839
+
826
840
  Current user's request: ${currentInput}`;
827
841
  }
828
842
  /**
@@ -863,32 +877,71 @@ Current user's request: ${currentInput}`;
863
877
  return true;
864
878
  }
865
879
  /**
866
- * Retrieve condensed memory for a user.
880
+ * Retrieve condensed memory for a user (and optionally additional users).
867
881
  * Returns the input text enhanced with memory context, or unchanged if no memory.
868
882
  */
869
- async retrieveMemory(inputText, userId) {
883
+ async retrieveMemory(inputText, userId, additionalUsers) {
870
884
  const client = this.ensureMemoryReady();
871
885
  if (!client) {
872
886
  return inputText;
873
887
  }
874
- const memory = await client.get(userId);
875
- if (!memory) {
888
+ // Collect all user IDs to read (primary + additional users with read !== false)
889
+ const readableAdditional = (additionalUsers || []).filter((u) => u.read !== false);
890
+ if (readableAdditional.length === 0) {
891
+ // Single user — use original fast path
892
+ const memory = await client.get(userId);
893
+ if (!memory) {
894
+ return inputText;
895
+ }
896
+ return this.formatMemoryContext(memory, inputText);
897
+ }
898
+ // Multi-user: fetch all memories in parallel
899
+ // Build entries with labels for formatting
900
+ const entries = [
901
+ { id: userId, label: "User" },
902
+ ...readableAdditional.map((u) => ({
903
+ id: u.userId,
904
+ label: u.label || u.userId,
905
+ })),
906
+ ];
907
+ const results = await Promise.all(entries.map(async (entry) => {
908
+ const memory = await client.get(entry.id);
909
+ return { ...entry, memory };
910
+ }));
911
+ const memories = new Map();
912
+ for (const { label, memory } of results) {
913
+ if (memory) {
914
+ memories.set(label, memory);
915
+ }
916
+ }
917
+ if (memories.size === 0) {
876
918
  return inputText;
877
919
  }
878
- return this.formatMemoryContext(memory, inputText);
920
+ return this.formatMultiUserMemoryContext(memories, inputText);
879
921
  }
880
922
  /**
881
923
  * Store a conversation turn in memory (non-blocking).
882
924
  * Calls add(userId, content) which internally condenses old + new via LLM.
925
+ * Supports additional users with per-user prompt and maxWords overrides.
883
926
  */
884
- storeMemoryInBackground(originalPrompt, responseContent, userId) {
927
+ storeMemoryInBackground(originalPrompt, responseContent, userId, additionalUsers) {
885
928
  setImmediate(async () => {
886
929
  try {
887
930
  const client = this.ensureMemoryReady();
888
- if (client) {
889
- const content = `User: ${originalPrompt}\nAssistant: ${responseContent}`;
890
- await client.add(userId, content);
931
+ if (!client) {
932
+ return;
891
933
  }
934
+ const content = `User: ${originalPrompt}\nAssistant: ${responseContent}`;
935
+ // Collect all users to write: primary + additional users with write !== false
936
+ const writeOps = [client.add(userId, content)];
937
+ const writableAdditional = (additionalUsers || []).filter((u) => u.write !== false);
938
+ for (const user of writableAdditional) {
939
+ const addOptions = user.prompt || user.maxWords
940
+ ? { prompt: user.prompt, maxWords: user.maxWords }
941
+ : undefined;
942
+ writeOps.push(client.add(user.userId, content, addOptions));
943
+ }
944
+ await Promise.all(writeOps);
892
945
  }
893
946
  catch (error) {
894
947
  logger.warn("Memory storage failed:", error);
@@ -2428,6 +2481,17 @@ Current user's request: ${currentInput}`;
2428
2481
  });
2429
2482
  }
2430
2483
  }
2484
+ // Memory retrieval for generate path
2485
+ if (this.shouldReadMemory(options.memory, options.context?.userId) &&
2486
+ options.context?.userId) {
2487
+ try {
2488
+ options.input.text = await this.retrieveMemory(options.input.text, options.context.userId, options.memory?.additionalUsers);
2489
+ logger.debug("Memory retrieval successful (generate)");
2490
+ }
2491
+ catch (error) {
2492
+ logger.warn("Memory retrieval failed (generate):", error);
2493
+ }
2494
+ }
2431
2495
  // 🔧 CRITICAL FIX: Convert to TextGenerationOptions while preserving the input object for multimodal support
2432
2496
  const baseOptions = {
2433
2497
  prompt: options.input.text,
@@ -2613,7 +2677,7 @@ Current user's request: ${currentInput}`;
2613
2677
  // Memory storage
2614
2678
  if (this.shouldWriteMemory(options.memory, options.context?.userId, generateResult.content) &&
2615
2679
  options.context?.userId) {
2616
- this.storeMemoryInBackground(originalPrompt ?? "", generateResult.content.trim(), options.context.userId);
2680
+ this.storeMemoryInBackground(originalPrompt ?? "", generateResult.content.trim(), options.context.userId, options.memory?.additionalUsers);
2617
2681
  }
2618
2682
  }
2619
2683
  /**
@@ -4411,7 +4475,7 @@ Current user's request: ${currentInput}`;
4411
4475
  if (this.shouldReadMemory(options.memory, options.context?.userId) &&
4412
4476
  options.context?.userId) {
4413
4477
  try {
4414
- options.input.text = await this.retrieveMemory(options.input.text, options.context.userId);
4478
+ options.input.text = await this.retrieveMemory(options.input.text, options.context.userId, options.memory?.additionalUsers);
4415
4479
  logger.debug("Memory retrieval successful");
4416
4480
  }
4417
4481
  catch (error) {
@@ -4724,7 +4788,7 @@ Current user's request: ${currentInput}`;
4724
4788
  }
4725
4789
  }
4726
4790
  if (this.shouldWriteMemory(enhancedOptions.memory, enhancedOptions.context?.userId, accumulatedContent)) {
4727
- this.storeMemoryInBackground(originalPrompt ?? "", accumulatedContent.trim(), enhancedOptions.context?.userId);
4791
+ this.storeMemoryInBackground(originalPrompt ?? "", accumulatedContent.trim(), enhancedOptions.context?.userId, enhancedOptions.memory?.additionalUsers);
4728
4792
  }
4729
4793
  }
4730
4794
  /**
@@ -140,7 +140,24 @@ const detectSubscriptionTier = (oauthToken) => {
140
140
  * OAuth takes precedence over API key if both are available.
141
141
  */
142
142
  const detectAuthMethod = (oauthToken) => {
143
- // OAuth takes precedence if available
143
+ // Explicit env var takes highest precedence allows forcing api_key mode
144
+ // even when OAuth credentials exist (e.g., when using a proxy that handles auth)
145
+ const explicit = process.env.ANTHROPIC_AUTH_METHOD?.toLowerCase();
146
+ if (explicit === "api_key" || explicit === "apikey") {
147
+ logger.debug("[detectAuthMethod] Forced to api_key by ANTHROPIC_AUTH_METHOD env var");
148
+ return "api_key";
149
+ }
150
+ if (explicit === "oauth") {
151
+ if (oauthToken) {
152
+ logger.debug("[detectAuthMethod] Forced to oauth by ANTHROPIC_AUTH_METHOD env var");
153
+ return "oauth";
154
+ }
155
+ logger.warn("[detectAuthMethod] ANTHROPIC_AUTH_METHOD=oauth but no OAuth token found; falling through to auto-detection");
156
+ }
157
+ else if (explicit) {
158
+ logger.warn("[detectAuthMethod] Unrecognized ANTHROPIC_AUTH_METHOD value; falling through to auto-detection", { value: explicit });
159
+ }
160
+ // Auto-detect: OAuth takes precedence if available
144
161
  const method = oauthToken ? "oauth" : "api_key";
145
162
  logger.debug("[detectAuthMethod] Auth method resolved", {
146
163
  method,
@@ -200,11 +217,22 @@ export class AnthropicProvider extends BaseProvider {
200
217
  constructor(modelName, sdk, config) {
201
218
  // Pre-compute effective model with tier validation before calling super
202
219
  const oauthToken = config?.oauthToken ?? getOAuthToken();
203
- const subscriptionTier = config?.subscriptionTier ?? detectSubscriptionTier(oauthToken);
220
+ // Resolve auth method FIRST so that tier detection uses the chosen method.
221
+ // If ANTHROPIC_AUTH_METHOD=api_key wins over an existing OAuth token, the
222
+ // tier must reflect api_key mode (full model access) rather than the OAuth
223
+ // token's subscription level.
224
+ const authMethod = config?.authMethod ?? detectAuthMethod(oauthToken);
225
+ const subscriptionTier = config?.subscriptionTier ??
226
+ (authMethod === "oauth" ? detectSubscriptionTier(oauthToken) : "api");
204
227
  const targetModel = modelName || getDefaultAnthropicModel();
205
- // Determine effective model based on tier access
228
+ // Determine effective model based on tier access.
229
+ // Skip tier validation when a proxy is in use (ANTHROPIC_BASE_URL is set)
230
+ // — the proxy handles model access and auth, so the SDK should pass
231
+ // the requested model through without downgrading.
206
232
  let effectiveModel = targetModel;
207
- if (subscriptionTier !== "api" &&
233
+ const usingProxy = !!process.env.ANTHROPIC_BASE_URL;
234
+ if (!usingProxy &&
235
+ subscriptionTier !== "api" &&
208
236
  !isModelAvailableForTier(targetModel, subscriptionTier)) {
209
237
  effectiveModel = getRecommendedModelForTier(subscriptionTier);
210
238
  logger.warn("Model not available for subscription tier, using recommended model", {
@@ -219,8 +247,8 @@ export class AnthropicProvider extends BaseProvider {
219
247
  // Store computed values
220
248
  this.oauthToken = oauthToken;
221
249
  this.subscriptionTier = subscriptionTier;
222
- // Determine auth method - config takes precedence, then auto-detect
223
- this.authMethod = config?.authMethod ?? detectAuthMethod(this.oauthToken);
250
+ // Use the auth method already resolved above (before tier computation)
251
+ this.authMethod = authMethod;
224
252
  // Build headers based on auth method and subscription tier
225
253
  const headers = this.getAuthHeaders();
226
254
  // Create Anthropic instance based on auth method
@@ -348,6 +376,10 @@ export class AnthropicProvider extends BaseProvider {
348
376
  * ```
349
377
  */
350
378
  validateModelAccess(model) {
379
+ // Proxy mode: bypass tier validation entirely — the proxy handles model access
380
+ if (process.env.ANTHROPIC_BASE_URL) {
381
+ return true;
382
+ }
351
383
  // API tier has access to all models
352
384
  if (this.subscriptionTier === "api") {
353
385
  return true;