@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.
- package/CHANGELOG.md +6 -0
- package/dist/adapters/video/vertexVideoHandler.js +9 -2
- package/dist/browser/neurolink.min.js +1014 -1018
- package/dist/cli/factories/commandFactory.d.ts +14 -0
- package/dist/cli/factories/commandFactory.js +50 -25
- package/dist/cli/loop/optionsSchema.d.ts +1 -1
- package/dist/cli/loop/optionsSchema.js +12 -0
- package/dist/core/baseProvider.d.ts +1 -1
- package/dist/core/modules/MessageBuilder.js +20 -0
- package/dist/factories/providerRegistry.js +5 -1
- package/dist/lib/adapters/video/vertexVideoHandler.js +9 -2
- package/dist/lib/core/baseProvider.d.ts +1 -1
- package/dist/lib/core/modules/MessageBuilder.js +20 -0
- package/dist/lib/factories/providerRegistry.js +5 -1
- package/dist/lib/memory/hippocampusInitializer.d.ts +2 -2
- package/dist/lib/memory/hippocampusInitializer.js +32 -2
- package/dist/lib/middleware/builtin/lifecycle.js +19 -48
- package/dist/lib/neurolink.js +49 -2
- package/dist/lib/providers/googleAiStudio.d.ts +11 -3
- package/dist/lib/providers/googleAiStudio.js +292 -339
- package/dist/lib/providers/googleNativeGemini3.d.ts +83 -1
- package/dist/lib/providers/googleNativeGemini3.js +208 -4
- package/dist/lib/providers/googleVertex.d.ts +116 -129
- package/dist/lib/providers/googleVertex.js +2826 -1968
- package/dist/lib/providers/openRouter.js +7 -3
- package/dist/lib/types/aliases.d.ts +14 -0
- package/dist/lib/types/common.d.ts +0 -3
- package/dist/lib/types/conversation.d.ts +10 -3
- package/dist/lib/types/generate.d.ts +14 -0
- package/dist/lib/types/index.d.ts +1 -0
- package/dist/lib/types/index.js +1 -0
- package/dist/lib/types/memory.d.ts +96 -0
- package/dist/lib/types/memory.js +23 -0
- package/dist/lib/types/providers.d.ts +140 -2
- package/dist/lib/types/stream.d.ts +6 -0
- package/dist/lib/utils/lifecycleCallbacks.d.ts +13 -0
- package/dist/lib/utils/lifecycleCallbacks.js +44 -0
- package/dist/lib/utils/messageBuilder.d.ts +10 -0
- package/dist/lib/utils/messageBuilder.js +40 -5
- package/dist/lib/utils/modelDetection.d.ts +11 -0
- package/dist/lib/utils/modelDetection.js +27 -0
- package/dist/lib/utils/providerHealth.js +7 -7
- package/dist/lib/utils/schemaConversion.d.ts +1 -1
- package/dist/lib/utils/schemaConversion.js +59 -4
- package/dist/lib/utils/tokenLimits.js +23 -32
- package/dist/memory/hippocampusInitializer.d.ts +2 -2
- package/dist/memory/hippocampusInitializer.js +32 -2
- package/dist/middleware/builtin/lifecycle.js +19 -48
- package/dist/neurolink.js +49 -2
- package/dist/providers/googleAiStudio.d.ts +11 -3
- package/dist/providers/googleAiStudio.js +291 -339
- package/dist/providers/googleNativeGemini3.d.ts +83 -1
- package/dist/providers/googleNativeGemini3.js +208 -4
- package/dist/providers/googleVertex.d.ts +116 -129
- package/dist/providers/googleVertex.js +2824 -1967
- package/dist/providers/openRouter.js +7 -3
- package/dist/types/aliases.d.ts +14 -0
- package/dist/types/common.d.ts +0 -3
- package/dist/types/conversation.d.ts +10 -3
- package/dist/types/generate.d.ts +14 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/memory.d.ts +96 -0
- package/dist/types/memory.js +22 -0
- package/dist/types/providers.d.ts +140 -2
- package/dist/types/stream.d.ts +6 -0
- package/dist/utils/lifecycleCallbacks.d.ts +13 -0
- package/dist/utils/lifecycleCallbacks.js +43 -0
- package/dist/utils/messageBuilder.d.ts +10 -0
- package/dist/utils/messageBuilder.js +40 -5
- package/dist/utils/modelDetection.d.ts +11 -0
- package/dist/utils/modelDetection.js +27 -0
- package/dist/utils/providerHealth.js +7 -7
- package/dist/utils/schemaConversion.d.ts +1 -1
- package/dist/utils/schemaConversion.js +59 -4
- package/dist/utils/tokenLimits.js +23 -32
- 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,3 +617,187 @@ 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
|
+
}
|