@juspay/neurolink 9.63.0 → 9.64.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 (79) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/adapters/video/vertexVideoHandler.js +9 -2
  3. package/dist/browser/neurolink.min.js +1015 -1019
  4. package/dist/cli/factories/commandFactory.d.ts +14 -0
  5. package/dist/cli/factories/commandFactory.js +50 -25
  6. package/dist/cli/loop/optionsSchema.d.ts +1 -1
  7. package/dist/cli/loop/optionsSchema.js +12 -0
  8. package/dist/core/baseProvider.d.ts +1 -1
  9. package/dist/core/modules/MessageBuilder.js +20 -0
  10. package/dist/core/redisConversationMemoryManager.js +0 -3
  11. package/dist/factories/providerRegistry.js +5 -1
  12. package/dist/lib/adapters/video/vertexVideoHandler.js +9 -2
  13. package/dist/lib/core/baseProvider.d.ts +1 -1
  14. package/dist/lib/core/modules/MessageBuilder.js +20 -0
  15. package/dist/lib/core/redisConversationMemoryManager.js +0 -3
  16. package/dist/lib/factories/providerRegistry.js +5 -1
  17. package/dist/lib/memory/hippocampusInitializer.d.ts +2 -2
  18. package/dist/lib/memory/hippocampusInitializer.js +32 -2
  19. package/dist/lib/middleware/builtin/lifecycle.js +19 -48
  20. package/dist/lib/neurolink.js +49 -2
  21. package/dist/lib/providers/googleAiStudio.d.ts +11 -3
  22. package/dist/lib/providers/googleAiStudio.js +292 -339
  23. package/dist/lib/providers/googleNativeGemini3.d.ts +83 -1
  24. package/dist/lib/providers/googleNativeGemini3.js +208 -4
  25. package/dist/lib/providers/googleVertex.d.ts +116 -129
  26. package/dist/lib/providers/googleVertex.js +2826 -1968
  27. package/dist/lib/providers/openRouter.js +7 -3
  28. package/dist/lib/types/aliases.d.ts +14 -0
  29. package/dist/lib/types/common.d.ts +0 -3
  30. package/dist/lib/types/conversation.d.ts +10 -3
  31. package/dist/lib/types/generate.d.ts +14 -0
  32. package/dist/lib/types/index.d.ts +1 -0
  33. package/dist/lib/types/index.js +1 -0
  34. package/dist/lib/types/memory.d.ts +96 -0
  35. package/dist/lib/types/memory.js +23 -0
  36. package/dist/lib/types/providers.d.ts +140 -2
  37. package/dist/lib/types/stream.d.ts +6 -0
  38. package/dist/lib/utils/lifecycleCallbacks.d.ts +13 -0
  39. package/dist/lib/utils/lifecycleCallbacks.js +44 -0
  40. package/dist/lib/utils/messageBuilder.d.ts +10 -0
  41. package/dist/lib/utils/messageBuilder.js +40 -5
  42. package/dist/lib/utils/modelDetection.d.ts +11 -0
  43. package/dist/lib/utils/modelDetection.js +27 -0
  44. package/dist/lib/utils/providerHealth.js +7 -7
  45. package/dist/lib/utils/schemaConversion.d.ts +1 -1
  46. package/dist/lib/utils/schemaConversion.js +59 -4
  47. package/dist/lib/utils/tokenLimits.js +23 -32
  48. package/dist/memory/hippocampusInitializer.d.ts +2 -2
  49. package/dist/memory/hippocampusInitializer.js +32 -2
  50. package/dist/middleware/builtin/lifecycle.js +19 -48
  51. package/dist/neurolink.js +49 -2
  52. package/dist/providers/googleAiStudio.d.ts +11 -3
  53. package/dist/providers/googleAiStudio.js +291 -339
  54. package/dist/providers/googleNativeGemini3.d.ts +83 -1
  55. package/dist/providers/googleNativeGemini3.js +208 -4
  56. package/dist/providers/googleVertex.d.ts +116 -129
  57. package/dist/providers/googleVertex.js +2824 -1967
  58. package/dist/providers/openRouter.js +7 -3
  59. package/dist/types/aliases.d.ts +14 -0
  60. package/dist/types/common.d.ts +0 -3
  61. package/dist/types/conversation.d.ts +10 -3
  62. package/dist/types/generate.d.ts +14 -0
  63. package/dist/types/index.d.ts +1 -0
  64. package/dist/types/index.js +1 -0
  65. package/dist/types/memory.d.ts +96 -0
  66. package/dist/types/memory.js +22 -0
  67. package/dist/types/providers.d.ts +140 -2
  68. package/dist/types/stream.d.ts +6 -0
  69. package/dist/utils/lifecycleCallbacks.d.ts +13 -0
  70. package/dist/utils/lifecycleCallbacks.js +43 -0
  71. package/dist/utils/messageBuilder.d.ts +10 -0
  72. package/dist/utils/messageBuilder.js +40 -5
  73. package/dist/utils/modelDetection.d.ts +11 -0
  74. package/dist/utils/modelDetection.js +27 -0
  75. package/dist/utils/providerHealth.js +7 -7
  76. package/dist/utils/schemaConversion.d.ts +1 -1
  77. package/dist/utils/schemaConversion.js +59 -4
  78. package/dist/utils/tokenLimits.js +23 -32
  79. package/package.json +11 -4
@@ -3,6 +3,20 @@ import type { CommandModule } from "yargs";
3
3
  * CLI Command Factory for generate commands
4
4
  */
5
5
  export declare class CLICommandFactory {
6
+ /**
7
+ * Normalize loop session variables before merging them into provider options.
8
+ *
9
+ * The CLI loop schema models some fields (e.g. `stopSequences`,
10
+ * `enabledToolNames`) as a single comma-separated string for ergonomic
11
+ * input, but providers expect `string[]`. Without conversion,
12
+ * `set stopSequences a,b` would be sent as one stop token "a,b" instead
13
+ * of two ("a", "b"); `set enabledToolNames read,write` would be cast to
14
+ * a `string[]` containing the single literal "read,write" and silently
15
+ * filter out every tool. This helper splits and trims those fields so
16
+ * the spread into `enhancedOptions` produces the correct shape across
17
+ * generate / batch / stream paths.
18
+ */
19
+ private static normalizeLoopSessionVariables;
6
20
  private static readonly commonOptions;
7
21
  private static buildOptions;
8
22
  private static processCliImages;
@@ -28,6 +28,35 @@ import { SageMakerCommandFactory } from "./sagemakerCommandFactory.js";
28
28
  * CLI Command Factory for generate commands
29
29
  */
30
30
  export class CLICommandFactory {
31
+ /**
32
+ * Normalize loop session variables before merging them into provider options.
33
+ *
34
+ * The CLI loop schema models some fields (e.g. `stopSequences`,
35
+ * `enabledToolNames`) as a single comma-separated string for ergonomic
36
+ * input, but providers expect `string[]`. Without conversion,
37
+ * `set stopSequences a,b` would be sent as one stop token "a,b" instead
38
+ * of two ("a", "b"); `set enabledToolNames read,write` would be cast to
39
+ * a `string[]` containing the single literal "read,write" and silently
40
+ * filter out every tool. This helper splits and trims those fields so
41
+ * the spread into `enhancedOptions` produces the correct shape across
42
+ * generate / batch / stream paths.
43
+ */
44
+ static normalizeLoopSessionVariables(vars) {
45
+ const normalized = { ...vars };
46
+ if (typeof normalized.stopSequences === "string") {
47
+ normalized.stopSequences = normalized.stopSequences
48
+ .split(",")
49
+ .map((s) => s.trim())
50
+ .filter(Boolean);
51
+ }
52
+ if (typeof normalized.enabledToolNames === "string") {
53
+ normalized.enabledToolNames = normalized.enabledToolNames
54
+ .split(",")
55
+ .map((s) => s.trim())
56
+ .filter(Boolean);
57
+ }
58
+ return normalized;
59
+ }
31
60
  // Common options available on all commands
32
61
  static commonOptions = {
33
62
  // Core generation options
@@ -621,6 +650,13 @@ export class CLICommandFactory {
621
650
  model: argv.model,
622
651
  temperature: argv.temperature,
623
652
  maxTokens: argv.maxTokens,
653
+ // Sampling controls — surfaced here so all three command paths
654
+ // (generate / stream / batch) get them consistently typed instead
655
+ // of relying on an ad-hoc cast at each sdk call site.
656
+ topP: argv.topP,
657
+ topK: argv.topK,
658
+ stopSequences: argv.stopSequences,
659
+ enabledToolNames: argv.enabledToolNames,
624
660
  systemPrompt: argv.system,
625
661
  timeout: argv.timeout,
626
662
  disableTools: argv.disableTools,
@@ -1930,18 +1966,8 @@ export class CLICommandFactory {
1930
1966
  }
1931
1967
  // Initialize SDK and session
1932
1968
  const sdk = globalSession.getOrCreateNeuroLink();
1933
- const sessionVariables = globalSession.getSessionVariables();
1934
- const enhancedOptions = {
1935
- ...options,
1936
- ...sessionVariables,
1937
- // enabledToolNames must be string[] for the SDK — normalize from CSV string
1938
- ...(typeof sessionVariables.enabledToolNames === "string" && {
1939
- enabledToolNames: sessionVariables.enabledToolNames
1940
- .split(",")
1941
- .map((t) => t.trim())
1942
- .filter(Boolean),
1943
- }),
1944
- };
1969
+ const sessionVariables = CLICommandFactory.normalizeLoopSessionVariables(globalSession.getSessionVariables());
1970
+ const enhancedOptions = { ...options, ...sessionVariables };
1945
1971
  const sessionId = globalSession.getCurrentSessionId();
1946
1972
  const context = sessionId
1947
1973
  ? { ...options.context, sessionId }
@@ -1993,6 +2019,9 @@ export class CLICommandFactory {
1993
2019
  model: enhancedOptions.model,
1994
2020
  temperature: enhancedOptions.temperature,
1995
2021
  maxTokens: enhancedOptions.maxTokens,
2022
+ topP: enhancedOptions.topP,
2023
+ topK: enhancedOptions.topK,
2024
+ stopSequences: enhancedOptions.stopSequences,
1996
2025
  systemPrompt: enhancedOptions.systemPrompt,
1997
2026
  timeout: enhancedOptions.timeout
1998
2027
  ? enhancedOptions.timeout * 1000
@@ -2166,7 +2195,7 @@ export class CLICommandFactory {
2166
2195
  */
2167
2196
  static async executeRealStream(argv, options, inputText, contextMetadata) {
2168
2197
  const sdk = globalSession.getOrCreateNeuroLink();
2169
- const sessionVariables = globalSession.getSessionVariables();
2198
+ const sessionVariables = CLICommandFactory.normalizeLoopSessionVariables(globalSession.getSessionVariables());
2170
2199
  const enhancedOptions = { ...options, ...sessionVariables };
2171
2200
  const sessionId = globalSession.getCurrentSessionId();
2172
2201
  const context = sessionId
@@ -2201,6 +2230,9 @@ export class CLICommandFactory {
2201
2230
  model: enhancedOptions.model,
2202
2231
  temperature: enhancedOptions.temperature,
2203
2232
  maxTokens: enhancedOptions.maxTokens,
2233
+ topP: enhancedOptions.topP,
2234
+ topK: enhancedOptions.topK,
2235
+ stopSequences: enhancedOptions.stopSequences,
2204
2236
  systemPrompt: enhancedOptions.systemPrompt,
2205
2237
  timeout: enhancedOptions.timeout
2206
2238
  ? enhancedOptions.timeout * 1000
@@ -2639,18 +2671,8 @@ export class CLICommandFactory {
2639
2671
  }
2640
2672
  const results = [];
2641
2673
  const sdk = globalSession.getOrCreateNeuroLink();
2642
- const sessionVariables = globalSession.getSessionVariables();
2643
- const enhancedOptions = {
2644
- ...options,
2645
- ...sessionVariables,
2646
- // enabledToolNames must be string[] for the SDK — normalize from CSV string
2647
- ...(typeof sessionVariables.enabledToolNames === "string" && {
2648
- enabledToolNames: sessionVariables.enabledToolNames
2649
- .split(",")
2650
- .map((t) => t.trim())
2651
- .filter(Boolean),
2652
- }),
2653
- };
2674
+ const sessionVariables = CLICommandFactory.normalizeLoopSessionVariables(globalSession.getSessionVariables());
2675
+ const enhancedOptions = { ...options, ...sessionVariables };
2654
2676
  const sessionId = globalSession.getCurrentSessionId();
2655
2677
  for (let i = 0; i < prompts.length; i++) {
2656
2678
  if (spinner) {
@@ -2692,6 +2714,9 @@ export class CLICommandFactory {
2692
2714
  model: enhancedOptions.model,
2693
2715
  temperature: enhancedOptions.temperature,
2694
2716
  maxTokens: enhancedOptions.maxTokens,
2717
+ topP: enhancedOptions.topP,
2718
+ topK: enhancedOptions.topK,
2719
+ stopSequences: enhancedOptions.stopSequences,
2695
2720
  systemPrompt: enhancedOptions.systemPrompt,
2696
2721
  timeout: enhancedOptions.timeout
2697
2722
  ? enhancedOptions.timeout * 1000
@@ -4,4 +4,4 @@ import type { OptionSchema, TextGenerationOptions } from "../../lib/types/index.
4
4
  * This object provides metadata for validation and help text in the CLI loop.
5
5
  * It is derived from the main TextGenerationOptions interface to ensure consistency.
6
6
  */
7
- export declare const textGenerationOptionsSchema: Record<keyof Omit<TextGenerationOptions, "prompt" | "input" | "schema" | "tools" | "context" | "conversationHistory" | "conversationMessages" | "conversationMemoryConfig" | "originalPrompt" | "middleware" | "expectedOutcome" | "evaluationCriteria" | "region" | "csvOptions" | "tts" | "stt" | "thinkingConfig" | "requestId" | "fileRegistry" | "abortSignal" | "toolFilter" | "excludeTools" | "toolChoice" | "prepareStep" | "credentials">, OptionSchema>;
7
+ export declare const textGenerationOptionsSchema: Record<keyof Omit<TextGenerationOptions, "prompt" | "input" | "schema" | "tools" | "context" | "conversationHistory" | "conversationMessages" | "conversationMemoryConfig" | "originalPrompt" | "middleware" | "expectedOutcome" | "evaluationCriteria" | "region" | "csvOptions" | "tts" | "stt" | "thinkingConfig" | "requestId" | "fileRegistry" | "abortSignal" | "toolFilter" | "excludeTools" | "toolChoice" | "prepareStep" | "credentials" | "onFinish" | "onError">, OptionSchema>;
@@ -24,6 +24,18 @@ export const textGenerationOptionsSchema = {
24
24
  type: "number",
25
25
  description: "The maximum number of tokens to generate.",
26
26
  },
27
+ topP: {
28
+ type: "number",
29
+ description: "Top-p (nucleus) sampling parameter. Controls diversity of generated tokens (0.0-1.0).",
30
+ },
31
+ topK: {
32
+ type: "number",
33
+ description: "Top-k sampling parameter. Limits the number of tokens considered (Google/Gemini models only).",
34
+ },
35
+ stopSequences: {
36
+ type: "string",
37
+ description: "Stop sequences that will halt generation when encountered (comma-separated).",
38
+ },
27
39
  output: {
28
40
  type: "string",
29
41
  description: "AI response format - specify just the format value (e.g., 'json', 'structured'). Note: This is automatically transformed to { format: value } for the API.",
@@ -352,7 +352,7 @@ export declare abstract class BaseProvider implements AIProvider {
352
352
  * // result.video contains the generated video
353
353
  * ```
354
354
  */
355
- private handleVideoGeneration;
355
+ protected handleVideoGeneration(options: TextGenerationOptions, startTime: number): Promise<EnhancedGenerateResult>;
356
356
  /**
357
357
  * Create analytics - delegated to TelemetryHandler
358
358
  */
@@ -106,6 +106,16 @@ export class MessageBuilder {
106
106
  fileRegistry: options.fileRegistry,
107
107
  };
108
108
  messages = await buildMultimodalMessagesArray(multimodalOptions, this.providerName, this.modelName);
109
+ // Propagate any systemPrompt augmentation (e.g. inline-file
110
+ // handling guidance from processUnifiedFilesArray) back to the
111
+ // caller's options. Providers like GoogleVertex's native
112
+ // @google/genai stream path read `options.systemPrompt` directly
113
+ // — without this propagation the augmentation lives only on the
114
+ // local `multimodalOptions` clone and never reaches the model.
115
+ if (multimodalOptions.systemPrompt &&
116
+ multimodalOptions.systemPrompt !== options.systemPrompt) {
117
+ options.systemPrompt = multimodalOptions.systemPrompt;
118
+ }
109
119
  }
110
120
  else {
111
121
  if (process.env.NEUROLINK_DEBUG === "true") {
@@ -214,6 +224,16 @@ export class MessageBuilder {
214
224
  fileRegistry: options.fileRegistry,
215
225
  };
216
226
  messages = await buildMultimodalMessagesArray(multimodalOptions, this.providerName, this.modelName);
227
+ // Propagate any systemPrompt augmentation (e.g. inline-file
228
+ // handling guidance from processUnifiedFilesArray) back to the
229
+ // caller's options. Providers like GoogleVertex's native
230
+ // @google/genai stream path read `options.systemPrompt` directly
231
+ // — without this propagation the augmentation lives only on the
232
+ // local `multimodalOptions` clone and never reaches the model.
233
+ if (multimodalOptions.systemPrompt &&
234
+ multimodalOptions.systemPrompt !== options.systemPrompt) {
235
+ options.systemPrompt = multimodalOptions.systemPrompt;
236
+ }
217
237
  }
218
238
  else {
219
239
  if (process.env.NEUROLINK_DEBUG === "true") {
@@ -958,9 +958,6 @@ User message: "${userMessage}"`;
958
958
  title = title.replace(/^(Title:|Here's a title:|The title is:)\s*/i, "");
959
959
  title = title.replace(/['"]/g, ""); // Remove quotes
960
960
  title = title.replace(/\.$/, ""); // Remove trailing period
961
- if (title.length > 60) {
962
- title = title.substring(0, 57) + "...";
963
- }
964
961
  if (title.length < 3) {
965
962
  title = "New Conversation";
966
963
  }
@@ -135,7 +135,11 @@ export class ProviderRegistry {
135
135
  const openrouterCreds = credentials;
136
136
  const { OpenRouterProvider } = await import("../providers/openRouter.js");
137
137
  return new OpenRouterProvider(modelName, sdk, undefined, openrouterCreds);
138
- }, process.env.OPENROUTER_MODEL || "anthropic/claude-3-5-sonnet", ["openrouter", "or"]);
138
+ },
139
+ // Default updated from claude-3-5-sonnet (sunset by OpenRouter)
140
+ // to claude-sonnet-4.5. Must match getDefaultOpenRouterModel()
141
+ // in src/lib/providers/openRouter.ts.
142
+ process.env.OPENROUTER_MODEL || "anthropic/claude-sonnet-4.5", ["openrouter", "or"]);
139
143
  // Register Amazon SageMaker provider
140
144
  ProviderFactory.registerProvider(AIProviderName.SAGEMAKER, async (modelName, _providerName, _sdk, region, credentials) => {
141
145
  const sagemakerCreds = credentials;
@@ -89,8 +89,15 @@ export function isVertexVideoConfigured() {
89
89
  * @throws VideoError if project cannot be determined
90
90
  */
91
91
  async function getVertexConfig() {
92
- const location = process.env.GOOGLE_VERTEX_LOCATION ||
93
- process.env.GOOGLE_CLOUD_LOCATION ||
92
+ // Veo 3.1 is only published in us-central1 (and the synthetic
93
+ // `global` endpoint). When the operator's environment defaults to
94
+ // a region where Veo 3.1 isn't available (e.g. us-east5 set for
95
+ // Gemini chat traffic), the regional endpoint returns a 404
96
+ // "Publisher Model … was not found". Allow `GOOGLE_VEO_LOCATION` as
97
+ // a Veo-specific override and fall through to us-central1 instead
98
+ // of inheriting the default Vertex region.
99
+ const location = process.env.GOOGLE_VEO_LOCATION ||
100
+ process.env.GOOGLE_VERTEX_VIDEO_LOCATION ||
94
101
  DEFAULT_LOCATION;
95
102
  // Try environment variables first
96
103
  let project = process.env.GOOGLE_VERTEX_PROJECT ||
@@ -352,7 +352,7 @@ export declare abstract class BaseProvider implements AIProvider {
352
352
  * // result.video contains the generated video
353
353
  * ```
354
354
  */
355
- private handleVideoGeneration;
355
+ protected handleVideoGeneration(options: TextGenerationOptions, startTime: number): Promise<EnhancedGenerateResult>;
356
356
  /**
357
357
  * Create analytics - delegated to TelemetryHandler
358
358
  */
@@ -106,6 +106,16 @@ export class MessageBuilder {
106
106
  fileRegistry: options.fileRegistry,
107
107
  };
108
108
  messages = await buildMultimodalMessagesArray(multimodalOptions, this.providerName, this.modelName);
109
+ // Propagate any systemPrompt augmentation (e.g. inline-file
110
+ // handling guidance from processUnifiedFilesArray) back to the
111
+ // caller's options. Providers like GoogleVertex's native
112
+ // @google/genai stream path read `options.systemPrompt` directly
113
+ // — without this propagation the augmentation lives only on the
114
+ // local `multimodalOptions` clone and never reaches the model.
115
+ if (multimodalOptions.systemPrompt &&
116
+ multimodalOptions.systemPrompt !== options.systemPrompt) {
117
+ options.systemPrompt = multimodalOptions.systemPrompt;
118
+ }
109
119
  }
110
120
  else {
111
121
  if (process.env.NEUROLINK_DEBUG === "true") {
@@ -214,6 +224,16 @@ export class MessageBuilder {
214
224
  fileRegistry: options.fileRegistry,
215
225
  };
216
226
  messages = await buildMultimodalMessagesArray(multimodalOptions, this.providerName, this.modelName);
227
+ // Propagate any systemPrompt augmentation (e.g. inline-file
228
+ // handling guidance from processUnifiedFilesArray) back to the
229
+ // caller's options. Providers like GoogleVertex's native
230
+ // @google/genai stream path read `options.systemPrompt` directly
231
+ // — without this propagation the augmentation lives only on the
232
+ // local `multimodalOptions` clone and never reaches the model.
233
+ if (multimodalOptions.systemPrompt &&
234
+ multimodalOptions.systemPrompt !== options.systemPrompt) {
235
+ options.systemPrompt = multimodalOptions.systemPrompt;
236
+ }
217
237
  }
218
238
  else {
219
239
  if (process.env.NEUROLINK_DEBUG === "true") {
@@ -958,9 +958,6 @@ User message: "${userMessage}"`;
958
958
  title = title.replace(/^(Title:|Here's a title:|The title is:)\s*/i, "");
959
959
  title = title.replace(/['"]/g, ""); // Remove quotes
960
960
  title = title.replace(/\.$/, ""); // Remove trailing period
961
- if (title.length > 60) {
962
- title = title.substring(0, 57) + "...";
963
- }
964
961
  if (title.length < 3) {
965
962
  title = "New Conversation";
966
963
  }
@@ -135,7 +135,11 @@ export class ProviderRegistry {
135
135
  const openrouterCreds = credentials;
136
136
  const { OpenRouterProvider } = await import("../providers/openRouter.js");
137
137
  return new OpenRouterProvider(modelName, sdk, undefined, openrouterCreds);
138
- }, process.env.OPENROUTER_MODEL || "anthropic/claude-3-5-sonnet", ["openrouter", "or"]);
138
+ },
139
+ // Default updated from claude-3-5-sonnet (sunset by OpenRouter)
140
+ // to claude-sonnet-4.5. Must match getDefaultOpenRouterModel()
141
+ // in src/lib/providers/openRouter.ts.
142
+ process.env.OPENROUTER_MODEL || "anthropic/claude-sonnet-4.5", ["openrouter", "or"]);
139
143
  // Register Amazon SageMaker provider
140
144
  ProviderFactory.registerProvider(AIProviderName.SAGEMAKER, async (modelName, _providerName, _sdk, region, credentials) => {
141
145
  const sagemakerCreds = credentials;
@@ -1,2 +1,2 @@
1
- import { Hippocampus, type HippocampusConfig } from "@juspay/hippocampus";
2
- export declare function initializeHippocampus(config: HippocampusConfig): Hippocampus | null;
1
+ import type { HippocampusConfig, HippocampusLike } from "../types/index.js";
2
+ export declare function initializeHippocampus(config: HippocampusConfig): HippocampusLike | null;
@@ -1,8 +1,38 @@
1
- import { Hippocampus } from "@juspay/hippocampus";
1
+ import { createRequire } from "node:module";
2
2
  import { logger } from "../utils/logger.js";
3
+ // Lazy require so importing NeuroLink core does not fail when the optional
4
+ // peer @juspay/hippocampus is not installed. The package was previously a
5
+ // hard runtime dependency, but Hippocampus declares a peer on
6
+ // @juspay/neurolink which made pnpm pull a registry NeuroLink that
7
+ // transitively required @ai-sdk/google + @ai-sdk/google-vertex into the
8
+ // production graph. Making memory optional breaks that cycle while keeping
9
+ // the same runtime behavior whenever the package is installed.
10
+ const lazyRequire = createRequire(import.meta.url);
11
+ let cachedModule;
12
+ function loadHippocampusModule() {
13
+ if (cachedModule !== undefined) {
14
+ return cachedModule;
15
+ }
16
+ try {
17
+ cachedModule = lazyRequire("@juspay/hippocampus");
18
+ return cachedModule;
19
+ }
20
+ catch (error) {
21
+ cachedModule = null;
22
+ logger.debug("[memoryInitializer] @juspay/hippocampus is not installed; memory features disabled.", {
23
+ error: error instanceof Error ? error.message : String(error),
24
+ });
25
+ return null;
26
+ }
27
+ }
3
28
  export function initializeHippocampus(config) {
29
+ const mod = loadHippocampusModule();
30
+ if (!mod) {
31
+ logger.warn("[memoryInitializer] Memory configuration provided but @juspay/hippocampus is not installed. Run `pnpm add @juspay/hippocampus` (or your package manager equivalent) to enable memory.");
32
+ return null;
33
+ }
4
34
  try {
5
- const instance = new Hippocampus(config);
35
+ const instance = new mod.Hippocampus(config);
6
36
  logger.info("[memoryInitializer] Memory initialized successfully", {
7
37
  storageType: config.storage?.type || "sqlite",
8
38
  maxWords: config.maxWords || 50,
@@ -9,6 +9,7 @@
9
9
  */
10
10
  import { logger } from "../../utils/logger.js";
11
11
  import { isRecoverableError } from "../../utils/errorHandling.js";
12
+ import { fireOnErrorOnce } from "../../utils/lifecycleCallbacks.js";
12
13
  export function createLifecycleMiddleware(config = {}) {
13
14
  const metadata = {
14
15
  id: "lifecycle",
@@ -50,22 +51,12 @@ export function createLifecycleMiddleware(config = {}) {
50
51
  return result;
51
52
  }
52
53
  catch (error) {
53
- if (config.onError) {
54
- const err = error instanceof Error ? error : new Error(String(error));
55
- try {
56
- const callbackResult = config.onError({
57
- error: err,
58
- duration: Date.now() - startTime,
59
- recoverable: isRecoverableError(err),
60
- });
61
- Promise.resolve(callbackResult).catch((e) => {
62
- logger.warn("[LifecycleMiddleware] onError callback error:", e);
63
- });
64
- }
65
- catch (e) {
66
- logger.warn("[LifecycleMiddleware] onError callback error:", e);
67
- }
68
- }
54
+ const err = error instanceof Error ? error : new Error(String(error));
55
+ fireOnErrorOnce(config.onError, error, {
56
+ error: err,
57
+ duration: Date.now() - startTime,
58
+ recoverable: isRecoverableError(err),
59
+ });
69
60
  throw error;
70
61
  }
71
62
  },
@@ -102,22 +93,12 @@ export function createLifecycleMiddleware(config = {}) {
102
93
  controller.enqueue(chunk);
103
94
  }
104
95
  catch (error) {
105
- if (config.onError) {
106
- const err = error instanceof Error ? error : new Error(String(error));
107
- try {
108
- const callbackResult = config.onError({
109
- error: err,
110
- duration: Date.now() - startTime,
111
- recoverable: isRecoverableError(err),
112
- });
113
- Promise.resolve(callbackResult).catch((e) => {
114
- logger.warn("[LifecycleMiddleware] onError callback error:", e);
115
- });
116
- }
117
- catch (e) {
118
- logger.warn("[LifecycleMiddleware] onError callback error:", e);
119
- }
120
- }
96
+ const err = error instanceof Error ? error : new Error(String(error));
97
+ fireOnErrorOnce(config.onError, error, {
98
+ error: err,
99
+ duration: Date.now() - startTime,
100
+ recoverable: isRecoverableError(err),
101
+ });
121
102
  throw error;
122
103
  }
123
104
  },
@@ -144,22 +125,12 @@ export function createLifecycleMiddleware(config = {}) {
144
125
  };
145
126
  }
146
127
  catch (error) {
147
- if (config.onError) {
148
- const err = error instanceof Error ? error : new Error(String(error));
149
- try {
150
- const callbackResult = config.onError({
151
- error: err,
152
- duration: Date.now() - startTime,
153
- recoverable: isRecoverableError(err),
154
- });
155
- Promise.resolve(callbackResult).catch((e) => {
156
- logger.warn("[LifecycleMiddleware] onError callback error:", e);
157
- });
158
- }
159
- catch (e) {
160
- logger.warn("[LifecycleMiddleware] onError callback error:", e);
161
- }
162
- }
128
+ const err = error instanceof Error ? error : new Error(String(error));
129
+ fireOnErrorOnce(config.onError, error, {
130
+ error: err,
131
+ duration: Date.now() - startTime,
132
+ recoverable: isRecoverableError(err),
133
+ });
163
134
  throw error;
164
135
  }
165
136
  },
@@ -64,6 +64,7 @@ import { getConversationMessages, storeConversationTurn, } from "./utils/convers
64
64
  import { CircuitBreaker, ERROR_CODES, ErrorFactory, isAbortError, isRetriableError, logStructuredError, NeuroLinkError, withRetry, withTimeout, } from "./utils/errorHandling.js";
65
65
  // Factory processing imports
66
66
  import { createCleanStreamOptions, enhanceTextGenerationOptions, processFactoryOptions, processStreamingFactoryOptions, validateFactoryConfig, } from "./utils/factoryProcessing.js";
67
+ import { fireOnErrorOnce } from "./utils/lifecycleCallbacks.js";
67
68
  import { logger, mcpLogger } from "./utils/logger.js";
68
69
  import { extractMcpErrorText } from "./utils/mcpErrorText.js";
69
70
  import { createCustomToolServerInfo, detectCategory, } from "./utils/mcpDefaults.js";
@@ -2753,7 +2754,29 @@ Current user's request: ${currentInput}`;
2753
2754
  * @since 1.0.0
2754
2755
  */
2755
2756
  async generate(optionsOrPrompt) {
2756
- return this.runWithFallbackOrchestration(optionsOrPrompt, "generate", (opts) => tracers.sdk.startActiveSpan("neurolink.generate", { kind: SpanKind.INTERNAL }, (generateSpan) => this.executeGenerateWithMetricsContext(opts, generateSpan)));
2757
+ const startTime = Date.now();
2758
+ try {
2759
+ return await this.runWithFallbackOrchestration(optionsOrPrompt, "generate", (opts) => tracers.sdk.startActiveSpan("neurolink.generate", { kind: SpanKind.INTERNAL }, (generateSpan) => this.executeGenerateWithMetricsContext(opts, generateSpan)));
2760
+ }
2761
+ catch (error) {
2762
+ // Fire `onError` lifecycle callback for ANY thrown error — including
2763
+ // ones raised before the provider is even instantiated (invalid
2764
+ // provider name, missing credentials, etc.). The downstream
2765
+ // LifecycleMiddleware only fires `onError` once it has wrapped the
2766
+ // AI SDK doGenerate, which is too late for early-resolution failures.
2767
+ // `fireOnErrorOnce` dedupes against the middleware path so the
2768
+ // consumer callback fires at most once per logical failure.
2769
+ const onError = typeof optionsOrPrompt === "object" && optionsOrPrompt !== null
2770
+ ? optionsOrPrompt.onError
2771
+ : undefined;
2772
+ const err = error instanceof Error ? error : new Error(String(error));
2773
+ fireOnErrorOnce(onError, error, {
2774
+ error: err,
2775
+ duration: Date.now() - startTime,
2776
+ recoverable: false,
2777
+ });
2778
+ throw error;
2779
+ }
2757
2780
  }
2758
2781
  /**
2759
2782
  * Curator P2-3: wraps a generate/stream call with the fallback
@@ -3167,6 +3190,12 @@ Current user's request: ${currentInput}`;
3167
3190
  middleware: options.middleware,
3168
3191
  conversationMessages: options.conversationMessages,
3169
3192
  credentials: options.credentials,
3193
+ // Lifecycle callbacks must reach the provider so non-AI-SDK paths
3194
+ // (Vertex's native @google/genai, native Bedrock, Ollama, etc.) can
3195
+ // invoke them directly. Pipeline A also still receives them via the
3196
+ // wrapped middleware config set by applyGenerateLifecycleMiddleware.
3197
+ onFinish: options.onFinish,
3198
+ onError: options.onError,
3170
3199
  };
3171
3200
  const extraContext = options;
3172
3201
  if (extraContext.sessionId || extraContext.userId) {
@@ -5032,7 +5061,25 @@ Current user's request: ${currentInput}`;
5032
5061
  : [],
5033
5062
  optionKeys: Object.keys(options),
5034
5063
  });
5035
- return this.streamWithIterationFallback(options);
5064
+ const startTime = Date.now();
5065
+ try {
5066
+ return await this.streamWithIterationFallback(options);
5067
+ }
5068
+ catch (error) {
5069
+ // Fire `onError` for early-resolution failures (invalid provider,
5070
+ // missing credentials, etc.) that surface before the per-chunk
5071
+ // wrapper installed by GoogleVertex / LifecycleMiddleware can run.
5072
+ // `fireOnErrorOnce` dedupes against the middleware path so the
5073
+ // consumer callback fires at most once per logical failure.
5074
+ const onError = options.onError;
5075
+ const err = error instanceof Error ? error : new Error(String(error));
5076
+ fireOnErrorOnce(onError, error, {
5077
+ error: err,
5078
+ duration: Date.now() - startTime,
5079
+ recoverable: false,
5080
+ });
5081
+ throw error;
5082
+ }
5036
5083
  }
5037
5084
  /**
5038
5085
  * Curator P2-3 / Reviewer Finding #2: stream-fallback that also covers
@@ -41,7 +41,8 @@ export declare class GoogleAIStudioProvider extends BaseProvider {
41
41
  getProviderName(): AIProviderName;
42
42
  getDefaultModel(): string;
43
43
  /**
44
- * 🔧 PHASE 2: Return AI SDK model instance for tool calling
44
+ * AI SDK model instance no longer used.
45
+ * All models are routed through native @google/genai SDK directly.
45
46
  */
46
47
  getAISDKModel(): LanguageModel;
47
48
  protected formatProviderError(error: unknown): Error;
@@ -62,8 +63,8 @@ export declare class GoogleAIStudioProvider extends BaseProvider {
62
63
  private estimateTokenCount;
63
64
  protected executeStream(options: StreamOptions, analysisSchema?: ZodUnknownSchema | Schema<unknown>): Promise<StreamResult>;
64
65
  /**
65
- * Execute stream using native @google/genai SDK for Gemini 3 models
66
- * This bypasses @ai-sdk/google to properly handle thought_signature
66
+ * Execute stream using native @google/genai SDK
67
+ * Uses @google/genai directly for all Gemini models (2.0, 2.5, 3.x)
67
68
  */
68
69
  private executeNativeGemini3Stream;
69
70
  /**
@@ -75,6 +76,13 @@ export declare class GoogleAIStudioProvider extends BaseProvider {
75
76
  * Override generate to route Gemini 3 models with tools to native SDK
76
77
  */
77
78
  generate(optionsOrPrompt: TextGenerationOptions | string): Promise<EnhancedGenerateResult | null>;
79
+ /**
80
+ * Emit `generation:end` so the Pipeline B observability listener creates
81
+ * a `model.generation` span for native Google AI Studio generate calls.
82
+ * Without this hand-off the native path silently disappears from
83
+ * Pipeline B exporters (Langfuse, custom OTEL collectors).
84
+ */
85
+ private emitPipelineBGenerationEvent;
78
86
  private executeAudioStreamViaGeminiLive;
79
87
  protected getDefaultEmbeddingModel(): string;
80
88
  /**