@juspay/neurolink 9.63.1 → 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 (77) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/adapters/video/vertexVideoHandler.js +9 -2
  3. package/dist/browser/neurolink.min.js +1014 -1018
  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/factories/providerRegistry.js +5 -1
  11. package/dist/lib/adapters/video/vertexVideoHandler.js +9 -2
  12. package/dist/lib/core/baseProvider.d.ts +1 -1
  13. package/dist/lib/core/modules/MessageBuilder.js +20 -0
  14. package/dist/lib/factories/providerRegistry.js +5 -1
  15. package/dist/lib/memory/hippocampusInitializer.d.ts +2 -2
  16. package/dist/lib/memory/hippocampusInitializer.js +32 -2
  17. package/dist/lib/middleware/builtin/lifecycle.js +19 -48
  18. package/dist/lib/neurolink.js +49 -2
  19. package/dist/lib/providers/googleAiStudio.d.ts +11 -3
  20. package/dist/lib/providers/googleAiStudio.js +292 -339
  21. package/dist/lib/providers/googleNativeGemini3.d.ts +83 -1
  22. package/dist/lib/providers/googleNativeGemini3.js +208 -4
  23. package/dist/lib/providers/googleVertex.d.ts +116 -129
  24. package/dist/lib/providers/googleVertex.js +2826 -1968
  25. package/dist/lib/providers/openRouter.js +7 -3
  26. package/dist/lib/types/aliases.d.ts +14 -0
  27. package/dist/lib/types/common.d.ts +0 -3
  28. package/dist/lib/types/conversation.d.ts +10 -3
  29. package/dist/lib/types/generate.d.ts +14 -0
  30. package/dist/lib/types/index.d.ts +1 -0
  31. package/dist/lib/types/index.js +1 -0
  32. package/dist/lib/types/memory.d.ts +96 -0
  33. package/dist/lib/types/memory.js +23 -0
  34. package/dist/lib/types/providers.d.ts +140 -2
  35. package/dist/lib/types/stream.d.ts +6 -0
  36. package/dist/lib/utils/lifecycleCallbacks.d.ts +13 -0
  37. package/dist/lib/utils/lifecycleCallbacks.js +44 -0
  38. package/dist/lib/utils/messageBuilder.d.ts +10 -0
  39. package/dist/lib/utils/messageBuilder.js +40 -5
  40. package/dist/lib/utils/modelDetection.d.ts +11 -0
  41. package/dist/lib/utils/modelDetection.js +27 -0
  42. package/dist/lib/utils/providerHealth.js +7 -7
  43. package/dist/lib/utils/schemaConversion.d.ts +1 -1
  44. package/dist/lib/utils/schemaConversion.js +59 -4
  45. package/dist/lib/utils/tokenLimits.js +23 -32
  46. package/dist/memory/hippocampusInitializer.d.ts +2 -2
  47. package/dist/memory/hippocampusInitializer.js +32 -2
  48. package/dist/middleware/builtin/lifecycle.js +19 -48
  49. package/dist/neurolink.js +49 -2
  50. package/dist/providers/googleAiStudio.d.ts +11 -3
  51. package/dist/providers/googleAiStudio.js +291 -339
  52. package/dist/providers/googleNativeGemini3.d.ts +83 -1
  53. package/dist/providers/googleNativeGemini3.js +208 -4
  54. package/dist/providers/googleVertex.d.ts +116 -129
  55. package/dist/providers/googleVertex.js +2824 -1967
  56. package/dist/providers/openRouter.js +7 -3
  57. package/dist/types/aliases.d.ts +14 -0
  58. package/dist/types/common.d.ts +0 -3
  59. package/dist/types/conversation.d.ts +10 -3
  60. package/dist/types/generate.d.ts +14 -0
  61. package/dist/types/index.d.ts +1 -0
  62. package/dist/types/index.js +1 -0
  63. package/dist/types/memory.d.ts +96 -0
  64. package/dist/types/memory.js +22 -0
  65. package/dist/types/providers.d.ts +140 -2
  66. package/dist/types/stream.d.ts +6 -0
  67. package/dist/utils/lifecycleCallbacks.d.ts +13 -0
  68. package/dist/utils/lifecycleCallbacks.js +43 -0
  69. package/dist/utils/messageBuilder.d.ts +10 -0
  70. package/dist/utils/messageBuilder.js +40 -5
  71. package/dist/utils/modelDetection.d.ts +11 -0
  72. package/dist/utils/modelDetection.js +27 -0
  73. package/dist/utils/providerHealth.js +7 -7
  74. package/dist/utils/schemaConversion.d.ts +1 -1
  75. package/dist/utils/schemaConversion.js +59 -4
  76. package/dist/utils/tokenLimits.js +23 -32
  77. package/package.json +11 -4
@@ -9,7 +9,7 @@
9
9
  * providers so they can share a single implementation.
10
10
  */
11
11
  import { type Tool } from "ai";
12
- import type { ThinkingConfig, CollectedChunkResult, NativeFunctionCall, NativeFunctionResponse, NativeToolDeclarationsResult, NativeToolsConfig, TextChannel } from "../types/index.js";
12
+ import type { ThinkingConfig, CollectedChunkResult, NativeFunctionCall, NativeFunctionResponse, NativeToolDeclarationsResult, NativeToolsConfig, TextChannel, VertexNativePart, GeminiMultimodalInput } from "../types/index.js";
13
13
  /**
14
14
  * Sanitize a JSON Schema for Gemini's proto-based API.
15
15
  *
@@ -48,12 +48,30 @@ export declare function normalizeToolsForJsonSchemaProvider(tools: Record<string
48
48
  export declare function buildNativeToolDeclarations(tools: Record<string, Tool>): NativeToolDeclarationsResult;
49
49
  /**
50
50
  * Build the native @google/genai config object shared by stream and generate.
51
+ *
52
+ * Caller is responsible for the tools-vs-JSON conflict resolution: Gemini's
53
+ * function calling cannot be combined with `responseMimeType:
54
+ * "application/json"`, and `responseSchema` requires that mime type. So
55
+ * when tools are active, callers must NOT pass `wantsJsonOutput`/
56
+ * `responseSchema` here; when JSON/schema output is requested, callers
57
+ * must omit `toolsConfig`. The AI Studio path enforces this by forcing
58
+ * `disableTools: true` whenever JSON/schema output is requested.
51
59
  */
52
60
  export declare function buildNativeConfig(options: {
53
61
  temperature?: number;
54
62
  maxTokens?: number;
55
63
  systemPrompt?: string;
56
64
  thinkingConfig?: ThinkingConfig;
65
+ /**
66
+ * When true (and `toolsConfig` is undefined), set
67
+ * `responseMimeType: "application/json"` to enforce native JSON output.
68
+ */
69
+ wantsJsonOutput?: boolean;
70
+ /**
71
+ * Pre-converted JSON Schema for native `responseSchema`. Implies
72
+ * `wantsJsonOutput`. Ignored if `toolsConfig` is present.
73
+ */
74
+ responseSchema?: Record<string, unknown>;
57
75
  }, toolsConfig?: NativeToolsConfig): Record<string, unknown>;
58
76
  /**
59
77
  * Compute a safe, clamped maxSteps value.
@@ -142,3 +160,67 @@ export declare function pushModelResponseToHistory(currentContents: Array<{
142
160
  role: string;
143
161
  parts: unknown[];
144
162
  }>, rawResponseParts: unknown[], stepFunctionCalls: NativeFunctionCall[]): void;
163
+ /**
164
+ * Convert a Zod schema (or AI SDK `jsonSchema()` wrapper) into the shape
165
+ * `@google/genai` accepts as `responseSchema`. Mirrors the inline pipeline
166
+ * the Vertex Gemini paths already use:
167
+ *
168
+ * convertZodToJsonSchema → inlineJsonSchema → strip `$schema` → ensure
169
+ * every nested schema has a `type` (Vertex/Gemini reject schemas missing
170
+ * that field, even on nested objects).
171
+ *
172
+ * Lives here so the AI Studio and Vertex paths can share the same
173
+ * sanitization without duplicating the schema-conversion churn.
174
+ */
175
+ export declare function buildGeminiResponseSchema(schema: unknown): Record<string, unknown>;
176
+ /**
177
+ * Map NeuroLink ChatMessage[] history into the @google/genai content format
178
+ * and push the entries onto a contents array.
179
+ *
180
+ * Used by the native Vertex Gemini and Google AI Studio paths to honor
181
+ * `options.conversationMessages` so multi-turn conversations (memory, loop
182
+ * REPL, agent flows) actually carry prior turns into the request.
183
+ *
184
+ * Behavior notes:
185
+ * - Only `user` and `assistant` roles are forwarded; system messages are
186
+ * expected to be wired via `systemInstruction`, and tool-call /
187
+ * tool-result roles only appear inside intra-call tool loops which build
188
+ * their own model/function entries.
189
+ * - String content is wrapped as a single `{ text }` part. Empty strings
190
+ * are skipped to avoid sending empty parts that some Gemini regions
191
+ * reject.
192
+ * - The current user input should be appended AFTER calling this helper
193
+ * so the prior turns appear first in chronological order.
194
+ */
195
+ export declare function prependConversationMessages(contents: Array<{
196
+ role: string;
197
+ parts: unknown[];
198
+ }>, conversationMessages?: Array<{
199
+ role: string;
200
+ content: string;
201
+ }>): void;
202
+ /**
203
+ * Build the `parts` array for the current user turn of a Gemini native
204
+ * `generateContent` request, including inline image + PDF blobs.
205
+ *
206
+ * Both providers that hit the native `@google/genai` SDK — `GoogleVertex`
207
+ * and `GoogleAIStudio` — need this. The previous AI Studio code only
208
+ * pushed a single `{ text }` part, which silently dropped `input.images`
209
+ * and `input.pdfFiles` on the floor: the model received text only and
210
+ * legitimately reported "no image attached". Extracting this from the
211
+ * Vertex copy keeps both providers on one definition.
212
+ *
213
+ * Accepted shapes per element (mirroring the runtime behaviour the Vertex
214
+ * code already supported):
215
+ * - `Buffer` → used as-is
216
+ * - local file path → read via `readFileSync`, MIME guessed from extension
217
+ * - `data:<mime>;base64,...` URL → mime parsed, data base64-decoded
218
+ * - `http(s)://...` URL → fetched, mime from `content-type`
219
+ * - any other string → assumed to be a base64-encoded payload
220
+ *
221
+ * Image MIME guessing is conservative — only known extensions override the
222
+ * default `image/jpeg`. Fetch failures are logged and the offending entry
223
+ * is skipped rather than aborting the entire request, matching prior
224
+ * Vertex behaviour.
225
+ */
226
+ export declare function buildUserPartsWithMultimodal(input: GeminiMultimodalInput | undefined, textOverride?: string, logPrefix?: string): Promise<VertexNativePart[]>;
@@ -9,10 +9,12 @@
9
9
  * providers so they can share a single implementation.
10
10
  */
11
11
  import { randomUUID } from "node:crypto";
12
+ import { existsSync, readFileSync } from "node:fs";
13
+ import { extname } from "node:path";
12
14
  import { jsonSchema as aiJsonSchema, tool as createAISDKTool, } from "ai";
13
15
  import { DEFAULT_MAX_STEPS, DEFAULT_TOOL_MAX_RETRIES, } from "../core/constants.js";
14
16
  import { logger } from "../utils/logger.js";
15
- import { convertZodToJsonSchema, inlineJsonSchema, isZodSchema, normalizeJsonSchemaObject, } from "../utils/schemaConversion.js";
17
+ import { convertZodToJsonSchema, ensureNestedSchemaTypes, inlineJsonSchema, isZodSchema, normalizeJsonSchemaObject, } from "../utils/schemaConversion.js";
16
18
  import { createNativeThinkingConfig } from "../utils/thinkingConfig.js";
17
19
  // ── Functions ──
18
20
  /**
@@ -125,7 +127,7 @@ export function sanitizeToolsForGemini(tools) {
125
127
  typeof params === "object" &&
126
128
  "_def" in params &&
127
129
  typeof params.parse === "function") {
128
- const rawJsonSchema = convertZodToJsonSchema(params);
130
+ const rawJsonSchema = convertZodToJsonSchema(params, "openApi3");
129
131
  const inlined = inlineJsonSchema(rawJsonSchema);
130
132
  // Gemini sanitization strips Zod-only features not supported by the Gemini API:
131
133
  // union types (anyOf/oneOf) are collapsed to string, default values and
@@ -171,7 +173,7 @@ export function normalizeToolsForJsonSchemaProvider(tools) {
171
173
  const toolParams = legacyTool.parameters || tool.inputSchema;
172
174
  let rawSchema;
173
175
  if (isZodSchema(toolParams)) {
174
- rawSchema = convertZodToJsonSchema(toolParams);
176
+ rawSchema = convertZodToJsonSchema(toolParams, "openApi3");
175
177
  }
176
178
  else if (toolParams && typeof toolParams === "object") {
177
179
  rawSchema = toolParams;
@@ -222,7 +224,7 @@ export function buildNativeToolDeclarations(tools) {
222
224
  let rawSchema;
223
225
  const toolParams = legacyTool.parameters || tool.inputSchema;
224
226
  if (isZodSchema(toolParams)) {
225
- rawSchema = convertZodToJsonSchema(toolParams);
227
+ rawSchema = convertZodToJsonSchema(toolParams, "openApi3");
226
228
  }
227
229
  else if (typeof toolParams === "object") {
228
230
  rawSchema = toolParams;
@@ -255,6 +257,14 @@ export function buildNativeToolDeclarations(tools) {
255
257
  }
256
258
  /**
257
259
  * Build the native @google/genai config object shared by stream and generate.
260
+ *
261
+ * Caller is responsible for the tools-vs-JSON conflict resolution: Gemini's
262
+ * function calling cannot be combined with `responseMimeType:
263
+ * "application/json"`, and `responseSchema` requires that mime type. So
264
+ * when tools are active, callers must NOT pass `wantsJsonOutput`/
265
+ * `responseSchema` here; when JSON/schema output is requested, callers
266
+ * must omit `toolsConfig`. The AI Studio path enforces this by forcing
267
+ * `disableTools: true` whenever JSON/schema output is requested.
258
268
  */
259
269
  export function buildNativeConfig(options, toolsConfig) {
260
270
  const config = {
@@ -272,6 +282,16 @@ export function buildNativeConfig(options, toolsConfig) {
272
282
  if (nativeThinkingConfig) {
273
283
  config.thinkingConfig = nativeThinkingConfig;
274
284
  }
285
+ // Native JSON / schema enforcement. Only set when tools are NOT being sent
286
+ // (Gemini rejects the combination). responseSchema implies JSON mime type.
287
+ if (!toolsConfig) {
288
+ if (options.responseSchema || options.wantsJsonOutput) {
289
+ config.responseMimeType = "application/json";
290
+ }
291
+ if (options.responseSchema) {
292
+ config.responseSchema = options.responseSchema;
293
+ }
294
+ }
275
295
  return config;
276
296
  }
277
297
  /**
@@ -597,4 +617,188 @@ export function pushModelResponseToHistory(currentContents, rawResponseParts, st
597
617
  : stepFunctionCalls.map((fc) => ({ functionCall: fc })),
598
618
  });
599
619
  }
620
+ /**
621
+ * Convert a Zod schema (or AI SDK `jsonSchema()` wrapper) into the shape
622
+ * `@google/genai` accepts as `responseSchema`. Mirrors the inline pipeline
623
+ * the Vertex Gemini paths already use:
624
+ *
625
+ * convertZodToJsonSchema → inlineJsonSchema → strip `$schema` → ensure
626
+ * every nested schema has a `type` (Vertex/Gemini reject schemas missing
627
+ * that field, even on nested objects).
628
+ *
629
+ * Lives here so the AI Studio and Vertex paths can share the same
630
+ * sanitization without duplicating the schema-conversion churn.
631
+ */
632
+ export function buildGeminiResponseSchema(schema) {
633
+ const raw = convertZodToJsonSchema(schema, "openApi3");
634
+ const inlined = inlineJsonSchema(raw);
635
+ if (inlined.$schema) {
636
+ delete inlined.$schema;
637
+ }
638
+ return ensureNestedSchemaTypes(inlined);
639
+ }
640
+ /**
641
+ * Map NeuroLink ChatMessage[] history into the @google/genai content format
642
+ * and push the entries onto a contents array.
643
+ *
644
+ * Used by the native Vertex Gemini and Google AI Studio paths to honor
645
+ * `options.conversationMessages` so multi-turn conversations (memory, loop
646
+ * REPL, agent flows) actually carry prior turns into the request.
647
+ *
648
+ * Behavior notes:
649
+ * - Only `user` and `assistant` roles are forwarded; system messages are
650
+ * expected to be wired via `systemInstruction`, and tool-call /
651
+ * tool-result roles only appear inside intra-call tool loops which build
652
+ * their own model/function entries.
653
+ * - String content is wrapped as a single `{ text }` part. Empty strings
654
+ * are skipped to avoid sending empty parts that some Gemini regions
655
+ * reject.
656
+ * - The current user input should be appended AFTER calling this helper
657
+ * so the prior turns appear first in chronological order.
658
+ */
659
+ export function prependConversationMessages(contents, conversationMessages) {
660
+ if (!conversationMessages || conversationMessages.length === 0) {
661
+ return;
662
+ }
663
+ for (const msg of conversationMessages) {
664
+ if (msg.role !== "user" && msg.role !== "assistant") {
665
+ continue;
666
+ }
667
+ const text = typeof msg.content === "string" ? msg.content : "";
668
+ if (text.length === 0) {
669
+ continue;
670
+ }
671
+ contents.push({
672
+ role: msg.role === "assistant" ? "model" : "user",
673
+ parts: [{ text }],
674
+ });
675
+ }
676
+ }
677
+ /**
678
+ * Build the `parts` array for the current user turn of a Gemini native
679
+ * `generateContent` request, including inline image + PDF blobs.
680
+ *
681
+ * Both providers that hit the native `@google/genai` SDK — `GoogleVertex`
682
+ * and `GoogleAIStudio` — need this. The previous AI Studio code only
683
+ * pushed a single `{ text }` part, which silently dropped `input.images`
684
+ * and `input.pdfFiles` on the floor: the model received text only and
685
+ * legitimately reported "no image attached". Extracting this from the
686
+ * Vertex copy keeps both providers on one definition.
687
+ *
688
+ * Accepted shapes per element (mirroring the runtime behaviour the Vertex
689
+ * code already supported):
690
+ * - `Buffer` → used as-is
691
+ * - local file path → read via `readFileSync`, MIME guessed from extension
692
+ * - `data:<mime>;base64,...` URL → mime parsed, data base64-decoded
693
+ * - `http(s)://...` URL → fetched, mime from `content-type`
694
+ * - any other string → assumed to be a base64-encoded payload
695
+ *
696
+ * Image MIME guessing is conservative — only known extensions override the
697
+ * default `image/jpeg`. Fetch failures are logged and the offending entry
698
+ * is skipped rather than aborting the entire request, matching prior
699
+ * Vertex behaviour.
700
+ */
701
+ export async function buildUserPartsWithMultimodal(input, textOverride, logPrefix = "[GeminiNative]") {
702
+ const text = typeof textOverride === "string" ? textOverride : (input?.text ?? "");
703
+ const parts = [{ text }];
704
+ if (input?.pdfFiles && input.pdfFiles.length > 0) {
705
+ logger.debug(`${logPrefix} Processing ${input.pdfFiles.length} PDF(s)`);
706
+ for (const pdfFile of input.pdfFiles) {
707
+ let pdfBuffer;
708
+ if (typeof pdfFile === "string") {
709
+ if (existsSync(pdfFile)) {
710
+ pdfBuffer = readFileSync(pdfFile);
711
+ }
712
+ else {
713
+ // Treat as already-base64-encoded payload
714
+ pdfBuffer = Buffer.from(pdfFile, "base64");
715
+ }
716
+ }
717
+ else {
718
+ pdfBuffer = pdfFile;
719
+ }
720
+ parts.push({
721
+ inlineData: {
722
+ mimeType: "application/pdf",
723
+ data: pdfBuffer.toString("base64"),
724
+ },
725
+ });
726
+ }
727
+ }
728
+ if (input?.images && input.images.length > 0) {
729
+ logger.debug(`${logPrefix} Processing ${input.images.length} image(s)`);
730
+ for (const rawImage of input.images) {
731
+ // `images` may carry plain Buffer/string values or `{ data, altText? }`
732
+ // objects. Normalise to the inner payload before format detection.
733
+ const image = rawImage && typeof rawImage === "object" && !Buffer.isBuffer(rawImage)
734
+ ? rawImage.data
735
+ : rawImage;
736
+ let imageBuffer;
737
+ let mimeType = "image/jpeg";
738
+ if (typeof image === "string") {
739
+ if (existsSync(image)) {
740
+ imageBuffer = readFileSync(image);
741
+ const ext = extname(image).toLowerCase();
742
+ if (ext === ".png") {
743
+ mimeType = "image/png";
744
+ }
745
+ else if (ext === ".gif") {
746
+ mimeType = "image/gif";
747
+ }
748
+ else if (ext === ".webp") {
749
+ mimeType = "image/webp";
750
+ }
751
+ }
752
+ else if (image.startsWith("data:")) {
753
+ const matches = image.match(/^data:([^;]+);base64,(.+)$/);
754
+ if (matches) {
755
+ mimeType = matches[1];
756
+ imageBuffer = Buffer.from(matches[2], "base64");
757
+ }
758
+ else {
759
+ continue;
760
+ }
761
+ }
762
+ else if (image.startsWith("http://") ||
763
+ image.startsWith("https://")) {
764
+ try {
765
+ const response = await fetch(image);
766
+ if (!response.ok) {
767
+ logger.warn(`${logPrefix} Image fetch failed: ${response.status} ${response.statusText}, skipping`, { url: image });
768
+ continue;
769
+ }
770
+ const arrayBuffer = await response.arrayBuffer();
771
+ imageBuffer = Buffer.from(arrayBuffer);
772
+ const headerMime = response.headers.get("content-type");
773
+ if (headerMime && headerMime.startsWith("image/")) {
774
+ mimeType = headerMime.split(";")[0];
775
+ }
776
+ }
777
+ catch (fetchError) {
778
+ logger.warn(`${logPrefix} Image URL fetch threw, skipping: ${fetchError instanceof Error
779
+ ? fetchError.message
780
+ : String(fetchError)}`, { url: image });
781
+ continue;
782
+ }
783
+ }
784
+ else {
785
+ imageBuffer = Buffer.from(image, "base64");
786
+ }
787
+ }
788
+ else {
789
+ imageBuffer = image;
790
+ }
791
+ if (!imageBuffer) {
792
+ continue;
793
+ }
794
+ parts.push({
795
+ inlineData: {
796
+ mimeType,
797
+ data: imageBuffer.toString("base64"),
798
+ },
799
+ });
800
+ }
801
+ }
802
+ return parts;
803
+ }
600
804
  //# sourceMappingURL=googleNativeGemini3.js.map