@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
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Model detection utilities for capability checking
3
3
  */
4
+ import { IMAGE_GENERATION_MODELS } from "../core/constants.js";
4
5
  /**
5
6
  * Check if model name is valid for detection functions
6
7
  */
@@ -78,4 +79,30 @@ export function getModelFamily(modelName) {
78
79
  }
79
80
  return "unknown";
80
81
  }
82
+ // Compiled once at module load — hasRestrictedOutputLimit() is called per
83
+ // request and previously rebuilt this array on every call.
84
+ const IMAGE_MODEL_PATTERNS = IMAGE_GENERATION_MODELS.map((m) => new RegExp(`^${m.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`, "i"));
85
+ /**
86
+ * Check if a model has restricted output token limit (32768 max)
87
+ * This applies to:
88
+ * - All Gemini 3 models (gemini-3-flash, gemini-3-pro, etc.)
89
+ * - Image generation models (gemini-2.5-flash-image, gemini-3-pro-image-preview)
90
+ */
91
+ export function hasRestrictedOutputLimit(modelName) {
92
+ if (!isValidModelName(modelName)) {
93
+ return false;
94
+ }
95
+ // Check for Gemini 3 models (anchored regex for consistency)
96
+ if (/^gemini-3/i.test(modelName)) {
97
+ return true;
98
+ }
99
+ if (IMAGE_MODEL_PATTERNS.some((pattern) => pattern.test(modelName))) {
100
+ return true;
101
+ }
102
+ return false;
103
+ }
104
+ /**
105
+ * Get the max output tokens for a model (32768 for restricted models)
106
+ */
107
+ export const RESTRICTED_OUTPUT_TOKEN_LIMIT = 32768;
81
108
  //# sourceMappingURL=modelDetection.js.map
@@ -349,7 +349,7 @@ export class ProviderHealthChecker {
349
349
  ` • claude-sonnet-4@20250514, claude-opus-4@20250514\n` +
350
350
  ` • claude-3-5-sonnet-20241022, claude-3-5-haiku-20241022\n` +
351
351
  ` • claude-3-sonnet-20240229, claude-3-haiku-20240307, claude-3-opus-20240229\n` +
352
- ` Implementation: Uses @ai-sdk/google-vertex with dual provider setup\n` +
352
+ ` Implementation: Uses native SDKs (@google/genai for Gemini, @anthropic-ai/vertex-sdk for Claude)\n` +
353
353
  ` Authentication: Requires Google Cloud project with Vertex AI API enabled\n` +
354
354
  ` Note: Anthropic models require Anthropic integration in your Google Cloud project`);
355
355
  }
@@ -969,13 +969,13 @@ export class ProviderHealthChecker {
969
969
  logger.debug("Starting comprehensive Vertex Anthropic support verification");
970
970
  try {
971
971
  // 1. Check SDK module availability
972
- logger.debug("Checking @ai-sdk/google-vertex/anthropic module availability");
973
- const anthropicModule = await import("@ai-sdk/google-vertex/anthropic");
972
+ logger.debug("Checking @anthropic-ai/vertex-sdk module availability");
973
+ const anthropicModule = await import("@anthropic-ai/vertex-sdk");
974
974
  result.hasCreateVertexAnthropic =
975
- typeof anthropicModule.createVertexAnthropic === "function";
976
- result.hasCorrectTypes = true; // Types are bundled with the function
975
+ typeof anthropicModule.AnthropicVertex === "function";
976
+ result.hasCorrectTypes = true; // Types are bundled with the class
977
977
  if (!result.hasCreateVertexAnthropic) {
978
- result.troubleshooting.push("📦 Update @ai-sdk/google-vertex to latest version with Anthropic support", "🔄 Run: npm install @ai-sdk/google-vertex@latest", "📖 See: https://sdk.vercel.ai/providers/ai-sdk-providers/google-vertex#anthropic-models");
978
+ result.troubleshooting.push("📦 Install @anthropic-ai/vertex-sdk for Claude on Vertex AI support", "🔄 Run: npm install @anthropic-ai/vertex-sdk", "📖 See: https://docs.anthropic.com/en/api/claude-on-vertex-ai");
979
979
  return result;
980
980
  }
981
981
  logger.debug("SDK module verified successfully");
@@ -1065,7 +1065,7 @@ export class ProviderHealthChecker {
1065
1065
  error: error instanceof Error ? error.message : String(error),
1066
1066
  stack: error instanceof Error ? error.stack : undefined,
1067
1067
  });
1068
- result.recommendations.push("❌ Comprehensive Anthropic support check failed", `🐛 Error: ${error instanceof Error ? error.message : String(error)}`, "", "🔧 Troubleshooting steps:", "1. Update @ai-sdk/google-vertex to latest version", "2. Verify Google Cloud authentication setup", "3. Check project ID and region configuration", "4. Enable Vertex AI API in Google Cloud Console", "5. Enable Anthropic integration in Vertex AI Model Garden");
1068
+ result.recommendations.push("❌ Comprehensive Anthropic support check failed", `🐛 Error: ${error instanceof Error ? error.message : String(error)}`, "", "🔧 Troubleshooting steps:", "1. Verify @google-cloud/vertexai and @anthropic-ai/vertex-sdk are properly installed", "2. Verify Google Cloud authentication setup", "3. Check project ID and region configuration", "4. Enable Vertex AI API in Google Cloud Console", "5. Enable Anthropic integration in Vertex AI Model Garden");
1069
1069
  }
1070
1070
  return result;
1071
1071
  }
@@ -27,7 +27,7 @@ export declare function ensureNestedSchemaTypes(schema: Record<string, unknown>)
27
27
  * 2. AI SDK `jsonSchema()` wrappers (have `.jsonSchema` property) -- extracted directly
28
28
  * 3. Plain JSON Schema objects (have `type`/`properties` but no `_def`) -- returned as-is
29
29
  */
30
- export declare function convertZodToJsonSchema(zodSchema: ZodUnknownSchema): object;
30
+ export declare function convertZodToJsonSchema(zodSchema: ZodUnknownSchema, target?: "jsonSchema7" | "openApi3"): object;
31
31
  export declare function normalizeJsonSchemaObject(schema: Record<string, unknown> | undefined | null): Record<string, unknown>;
32
32
  /**
33
33
  * Check if a value is a Zod schema
@@ -1,7 +1,25 @@
1
1
  import { zodToJsonSchema } from "zod-to-json-schema";
2
2
  import { jsonSchemaToZod } from "json-schema-to-zod";
3
+ import * as zodModule from "zod";
3
4
  import { z } from "zod";
4
5
  import { logger } from "./logger.js";
6
+ // Zod 4 ships a built-in `z.toJSONSchema(...)`. Zod 3 does not — it returned
7
+ // nothing of the sort and we relied entirely on `zod-to-json-schema`. The
8
+ // `zod-to-json-schema` package only understands Zod 3's internal `_def`
9
+ // shape, so feeding it a Zod 4 schema yields an empty `{}` (it silently
10
+ // produces `definitions: { ToolParameters: {} }`). When this happens
11
+ // downstream callers send an empty `responseSchema` to the model and get
12
+ // back arbitrary JSON, which is exactly how the Vertex Structured-Output
13
+ // regressions surfaced. Detect the Zod 4 helper at module load and prefer
14
+ // it for actual Zod schemas.
15
+ // Zod 4 spells the OpenAPI target as "openapi-3.0" (with a dot) while the
16
+ // third-party zod-to-json-schema package uses "openApi3". Internally we use
17
+ // the latter for backwards compatibility with existing call sites; this map
18
+ // translates to the dialect Zod 4 actually accepts. The Zod4Native* types
19
+ // live in src/lib/types/aliases.ts per project rule 2.
20
+ const zodToJsonSchemaV4 = typeof zodModule.toJSONSchema === "function"
21
+ ? zodModule.toJSONSchema
22
+ : undefined;
5
23
  /**
6
24
  * Resolve a deep JSON pointer path within a schema.
7
25
  * Handles paths like "#/definitions/ToolParameters/properties/foo/properties/bar"
@@ -96,7 +114,12 @@ export function inlineJsonSchema(schema, definitions, visited = new Set(), rootS
96
114
  visited.delete(refPath);
97
115
  return inlined;
98
116
  }
99
- logger.debug(`[SCHEMA-INLINE] Could not resolve $ref: ${refPath}`);
117
+ // Unresolved $ref: warn and preserve the original node verbatim. Falling
118
+ // through to the copy loop below would strip the $ref key and silently
119
+ // turn a ref-only node into an empty {}, which broadens validation
120
+ // instead of failing closed.
121
+ logger.warn(`[SCHEMA-INLINE] Could not resolve $ref: ${refPath}`);
122
+ return { ...schema };
100
123
  }
101
124
  // Create result without $ref and definitions
102
125
  const result = {};
@@ -279,7 +302,12 @@ export function ensureNestedSchemaTypes(schema) {
279
302
  * 2. AI SDK `jsonSchema()` wrappers (have `.jsonSchema` property) -- extracted directly
280
303
  * 3. Plain JSON Schema objects (have `type`/`properties` but no `_def`) -- returned as-is
281
304
  */
282
- export function convertZodToJsonSchema(zodSchema) {
305
+ export function convertZodToJsonSchema(zodSchema,
306
+ // Default to JSON Schema draft-07 so non-Vertex consumers (Bedrock, MCP
307
+ // tool registration, etc.) keep their pre-migration dialect. Vertex/Gemini
308
+ // callers opt into "openApi3" explicitly to get `nullable: true` instead
309
+ // of `anyOf: [..., {type: "null"}]`.
310
+ target = "jsonSchema7") {
283
311
  const schema = zodSchema;
284
312
  if (!schema || typeof schema !== "object") {
285
313
  return { type: "object", properties: {} };
@@ -295,14 +323,41 @@ export function convertZodToJsonSchema(zodSchema) {
295
323
  if (!isZodSchema(schema)) {
296
324
  return ensureNestedSchemaTypes(ensureTypeField(schema));
297
325
  }
298
- // Actual Zod schema — convert via zod-to-json-schema
326
+ // Actual Zod schema — prefer Zod 4's native `z.toJSONSchema` when
327
+ // available (the runtime version of `zod` here is Zod 4), then fall
328
+ // back to `zod-to-json-schema` for Zod 3 schemas that external callers
329
+ // might still pass in.
330
+ //
331
+ // Translate our `target` to Zod 4's native dialect identifier so the
332
+ // openApi3 path emits the OpenAPI 3 schema shape Vertex/Gemini expect
333
+ // (and not the default draft-07 anyOf/null union).
334
+ if (zodToJsonSchemaV4) {
335
+ const nativeTarget = target === "openApi3" ? "openapi-3.0" : "draft-07";
336
+ try {
337
+ const native = zodToJsonSchemaV4(zodSchema, {
338
+ target: nativeTarget,
339
+ });
340
+ // Drop the $schema metadata Vertex/Gemini doesn't need, then walk to
341
+ // backfill any missing nested types (Zod 4's output is already flat
342
+ // — no $defs/$ref by default — but the helper is cheap and matches
343
+ // the Zod 3 path's contract).
344
+ const flat = { ...native };
345
+ delete flat.$schema;
346
+ const inlined = inlineJsonSchema(flat);
347
+ return ensureNestedSchemaTypes(ensureTypeField(inlined));
348
+ }
349
+ catch (error) {
350
+ logger.warn("Native z.toJSONSchema failed; falling back to zod-to-json-schema", { error: error instanceof Error ? error.message : String(error) });
351
+ }
352
+ }
353
+ // Zod 3 fallback path
299
354
  try {
300
355
  // Zod 4→3 boundary: zodToJsonSchema types reference Zod 3's ZodSchema via zod/v3.
301
356
  // Runtime compatible — cast through unknown at this third-party boundary only.
302
357
  const zodV3Schema = zodSchema;
303
358
  const jsonSchema = zodToJsonSchema(zodV3Schema, {
304
359
  name: "ToolParameters",
305
- target: "openApi3", // Use OpenAPI 3.0 for nullable: true instead of anyOf with null (required for Vertex AI)
360
+ target,
306
361
  errorMessages: true,
307
362
  });
308
363
  // zodToJsonSchema with 'name' produces { $ref: "#/definitions/ToolParameters", definitions: {...} }
@@ -2,30 +2,14 @@
2
2
  * Provider-specific token limit utilities
3
3
  * Provides safe maxTokens values based on provider and model capabilities
4
4
  */
5
- import { PROVIDER_MAX_TOKENS, IMAGE_GENERATION_MODELS, } from "../core/constants.js";
5
+ import { PROVIDER_MAX_TOKENS } from "../core/constants.js";
6
6
  import { logger } from "./logger.js";
7
- // Gemini 3 models and Gemini 2.5 image models have a hard limit of 32768 output tokens
8
- const GEMINI_RESTRICTED_MAX_OUTPUT_TOKENS = 32768;
9
- /**
10
- * Check if a model has the restricted 32768 output token limit
11
- * This applies to:
12
- * - All Gemini 3 models (gemini-3-flash, gemini-3-pro, etc.)
13
- * - All Gemini 2.5 image generation models (gemini-2.5-flash-image)
14
- */
15
- function hasRestrictedOutputLimit(model) {
16
- if (!model) {
17
- return false;
18
- }
19
- // Check for Gemini 3 models
20
- if (model.includes("gemini-3")) {
21
- return true;
22
- }
23
- // Check for image generation models (includes gemini-2.5-flash-image)
24
- if (IMAGE_GENERATION_MODELS.some((m) => model.includes(m))) {
25
- return true;
26
- }
27
- return false;
28
- }
7
+ import { hasRestrictedOutputLimit, RESTRICTED_OUTPUT_TOKEN_LIMIT, } from "./modelDetection.js";
8
+ // Restricted-model detection (Gemini 3.x + image-gen models capped at 32768
9
+ // output tokens) lives in modelDetection.ts. Importing the canonical helper
10
+ // keeps both call sites on one definition; the previous local copy used
11
+ // case-sensitive substring matches and could miss identifiers like
12
+ // "Gemini-3-Pro" which the canonical anchored, case-insensitive regex matches.
29
13
  /**
30
14
  * Get the safe maximum tokens for a provider and model
31
15
  */
@@ -33,19 +17,19 @@ export function getSafeMaxTokens(provider, model, requestedMaxTokens) {
33
17
  // CRITICAL: Gemini 3 models AND image generation models have a hard limit of 32768 output tokens
34
18
  // This check must happen FIRST, before any other logic, because these models
35
19
  // will reject requests with maxOutputTokens > 32768
36
- const isRestrictedModel = hasRestrictedOutputLimit(model);
20
+ const isRestrictedModel = model ? hasRestrictedOutputLimit(model) : false;
37
21
  if (isRestrictedModel) {
38
22
  // Explicit undefined/null check so a caller-supplied 0 is preserved
39
- // (truthy checks would treat 0 as "unset" and silently fall back to the cap).
23
+ // (truthy guards would treat 0 as "unset" and silently fall back to the cap).
40
24
  if (requestedMaxTokens !== undefined &&
41
25
  requestedMaxTokens !== null &&
42
- requestedMaxTokens > GEMINI_RESTRICTED_MAX_OUTPUT_TOKENS) {
43
- logger.warn(`Requested maxTokens ${requestedMaxTokens} exceeds ${model} limit of ${GEMINI_RESTRICTED_MAX_OUTPUT_TOKENS}. Using ${GEMINI_RESTRICTED_MAX_OUTPUT_TOKENS} instead.`);
44
- return GEMINI_RESTRICTED_MAX_OUTPUT_TOKENS;
26
+ requestedMaxTokens > RESTRICTED_OUTPUT_TOKEN_LIMIT) {
27
+ logger.warn(`Requested maxTokens ${requestedMaxTokens} exceeds ${model} limit of ${RESTRICTED_OUTPUT_TOKEN_LIMIT}. Using ${RESTRICTED_OUTPUT_TOKEN_LIMIT} instead.`);
28
+ return RESTRICTED_OUTPUT_TOKEN_LIMIT;
45
29
  }
46
30
  // If no maxTokens specified, use the restricted limit as default
47
31
  if (requestedMaxTokens === undefined || requestedMaxTokens === null) {
48
- return GEMINI_RESTRICTED_MAX_OUTPUT_TOKENS;
32
+ return RESTRICTED_OUTPUT_TOKEN_LIMIT;
49
33
  }
50
34
  // Otherwise, use the requested value (it's within limits, including 0)
51
35
  return requestedMaxTokens;
@@ -54,7 +38,12 @@ export function getSafeMaxTokens(provider, model, requestedMaxTokens) {
54
38
  const providerLimits = PROVIDER_MAX_TOKENS[provider];
55
39
  if (!providerLimits) {
56
40
  logger.warn(`Unknown provider ${provider}, no token limits enforced`);
57
- return requestedMaxTokens || undefined; // No default limit for unknown providers
41
+ // Explicit undefined/null check so a caller-supplied 0 is preserved
42
+ // (truthy guard would silently drop 0 here as it does in the restricted branch).
43
+ if (requestedMaxTokens === undefined || requestedMaxTokens === null) {
44
+ return undefined;
45
+ }
46
+ return requestedMaxTokens;
58
47
  }
59
48
  // Get model-specific limit or provider default
60
49
  let maxLimit;
@@ -73,8 +62,10 @@ export function getSafeMaxTokens(provider, model, requestedMaxTokens) {
73
62
  else {
74
63
  maxLimit = PROVIDER_MAX_TOKENS.default;
75
64
  }
76
- // If no specific maxTokens requested, return the provider limit
77
- if (!requestedMaxTokens) {
65
+ // If no specific maxTokens requested, return the provider limit.
66
+ // Use explicit undefined/null so a caller-supplied 0 is preserved
67
+ // (matches the restricted-model branch above).
68
+ if (requestedMaxTokens === undefined || requestedMaxTokens === null) {
78
69
  return maxLimit;
79
70
  }
80
71
  // If requested maxTokens exceeds the limit, use the limit and warn
@@ -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
  },
package/dist/neurolink.js CHANGED
@@ -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
  /**