@langchain/google 0.1.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 (93) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE +21 -0
  3. package/README.md +97 -0
  4. package/dist/_virtual/rolldown_runtime.cjs +25 -0
  5. package/dist/chat_models/api-types.d.cts +1506 -0
  6. package/dist/chat_models/api-types.d.ts +1506 -0
  7. package/dist/chat_models/base.cjs +445 -0
  8. package/dist/chat_models/base.cjs.map +1 -0
  9. package/dist/chat_models/base.d.cts +319 -0
  10. package/dist/chat_models/base.d.ts +319 -0
  11. package/dist/chat_models/base.js +442 -0
  12. package/dist/chat_models/base.js.map +1 -0
  13. package/dist/chat_models/index.cjs +23 -0
  14. package/dist/chat_models/index.cjs.map +1 -0
  15. package/dist/chat_models/index.d.cts +39 -0
  16. package/dist/chat_models/index.d.ts +39 -0
  17. package/dist/chat_models/index.js +22 -0
  18. package/dist/chat_models/index.js.map +1 -0
  19. package/dist/chat_models/node.cjs +35 -0
  20. package/dist/chat_models/node.cjs.map +1 -0
  21. package/dist/chat_models/node.d.cts +13 -0
  22. package/dist/chat_models/node.d.ts +13 -0
  23. package/dist/chat_models/node.js +34 -0
  24. package/dist/chat_models/node.js.map +1 -0
  25. package/dist/chat_models/profiles.cjs +373 -0
  26. package/dist/chat_models/profiles.cjs.map +1 -0
  27. package/dist/chat_models/profiles.js +372 -0
  28. package/dist/chat_models/profiles.js.map +1 -0
  29. package/dist/chat_models/types.d.cts +250 -0
  30. package/dist/chat_models/types.d.ts +250 -0
  31. package/dist/clients/index.cjs +219 -0
  32. package/dist/clients/index.cjs.map +1 -0
  33. package/dist/clients/index.d.cts +49 -0
  34. package/dist/clients/index.d.ts +49 -0
  35. package/dist/clients/index.js +217 -0
  36. package/dist/clients/index.js.map +1 -0
  37. package/dist/clients/node.cjs +327 -0
  38. package/dist/clients/node.cjs.map +1 -0
  39. package/dist/clients/node.d.cts +59 -0
  40. package/dist/clients/node.d.ts +59 -0
  41. package/dist/clients/node.js +325 -0
  42. package/dist/clients/node.js.map +1 -0
  43. package/dist/const.cjs +13 -0
  44. package/dist/const.cjs.map +1 -0
  45. package/dist/const.js +9 -0
  46. package/dist/const.js.map +1 -0
  47. package/dist/converters/messages.cjs +697 -0
  48. package/dist/converters/messages.cjs.map +1 -0
  49. package/dist/converters/messages.js +691 -0
  50. package/dist/converters/messages.js.map +1 -0
  51. package/dist/converters/params.cjs +205 -0
  52. package/dist/converters/params.cjs.map +1 -0
  53. package/dist/converters/params.js +202 -0
  54. package/dist/converters/params.js.map +1 -0
  55. package/dist/converters/tools.cjs +356 -0
  56. package/dist/converters/tools.cjs.map +1 -0
  57. package/dist/converters/tools.js +353 -0
  58. package/dist/converters/tools.js.map +1 -0
  59. package/dist/index.cjs +7 -0
  60. package/dist/index.d.cts +3 -0
  61. package/dist/index.d.ts +3 -0
  62. package/dist/index.js +4 -0
  63. package/dist/node.cjs +7 -0
  64. package/dist/node.d.cts +3 -0
  65. package/dist/node.d.ts +3 -0
  66. package/dist/node.js +4 -0
  67. package/dist/utils/errors.cjs +543 -0
  68. package/dist/utils/errors.cjs.map +1 -0
  69. package/dist/utils/errors.js +535 -0
  70. package/dist/utils/errors.js.map +1 -0
  71. package/dist/utils/gcp-auth.cjs +188 -0
  72. package/dist/utils/gcp-auth.cjs.map +1 -0
  73. package/dist/utils/gcp-auth.d.cts +43 -0
  74. package/dist/utils/gcp-auth.d.ts +43 -0
  75. package/dist/utils/gcp-auth.js +186 -0
  76. package/dist/utils/gcp-auth.js.map +1 -0
  77. package/dist/utils/handler.cjs +67 -0
  78. package/dist/utils/handler.cjs.map +1 -0
  79. package/dist/utils/handler.d.cts +34 -0
  80. package/dist/utils/handler.d.ts +34 -0
  81. package/dist/utils/handler.js +64 -0
  82. package/dist/utils/handler.js.map +1 -0
  83. package/dist/utils/misc.cjs +10 -0
  84. package/dist/utils/misc.cjs.map +1 -0
  85. package/dist/utils/misc.d.cts +14 -0
  86. package/dist/utils/misc.d.ts +14 -0
  87. package/dist/utils/misc.js +9 -0
  88. package/dist/utils/misc.js.map +1 -0
  89. package/dist/utils/stream.cjs +26 -0
  90. package/dist/utils/stream.cjs.map +1 -0
  91. package/dist/utils/stream.js +25 -0
  92. package/dist/utils/stream.js.map +1 -0
  93. package/package.json +96 -0
@@ -0,0 +1,691 @@
1
+ import { iife } from "../utils/misc.js";
2
+ import { InvalidInputError, ToolCallNotFoundError } from "../utils/errors.js";
3
+ import { AIMessage, ChatMessage, HumanMessage, SystemMessage, ToolMessage, convertToProviderContentBlock, isDataContentBlock, parseBase64DataUrl } from "@langchain/core/messages";
4
+
5
+ //#region src/converters/messages.ts
6
+ /**
7
+ * Standard content block converter for Google Gemini API.
8
+ * Converts deprecated Data content blocks to Gemini Part format.
9
+ *
10
+ * @deprecated This converter is for backward compatibility with older message formats.
11
+ * Use ContentBlock.Multimodal.Standard instead.
12
+ */
13
+ const geminiContentBlockConverter = {
14
+ providerName: "ChatGoogle",
15
+ fromStandardTextBlock(block) {
16
+ return { text: block.text };
17
+ },
18
+ fromStandardImageBlock(block) {
19
+ if (isDataContentBlock(block)) {
20
+ if (block.source_type === "base64") {
21
+ if (!block.mime_type) throw new InvalidInputError("mime_type is required for base64 image blocks");
22
+ const dataStr = typeof block.data === "string" ? block.data : typeof block.data === "object" && block.data !== null ? btoa(Array.from(block.data).map((byte) => String.fromCharCode(byte)).join("")) : String(block.data);
23
+ return { inlineData: {
24
+ mimeType: block.mime_type,
25
+ data: dataStr
26
+ } };
27
+ }
28
+ if (block.source_type === "url") {
29
+ const parsed = parseBase64DataUrl({ dataUrl: block.url });
30
+ if (parsed) {
31
+ const dataStr = typeof parsed.data === "string" ? parsed.data : btoa(Array.from(parsed.data).map((byte) => String.fromCharCode(byte)).join(""));
32
+ return { inlineData: {
33
+ mimeType: parsed.mime_type || block.mime_type || "image/jpeg",
34
+ data: dataStr
35
+ } };
36
+ }
37
+ return { fileData: {
38
+ mimeType: block.mime_type || "image/jpeg",
39
+ fileUri: block.url
40
+ } };
41
+ }
42
+ if (block.source_type === "id") return { fileData: {
43
+ mimeType: block.mime_type || "image/jpeg",
44
+ fileUri: block.id
45
+ } };
46
+ }
47
+ throw new InvalidInputError(`Image content blocks with source_type "${block.source_type}" are not supported for ChatGoogle. Supported source types are: "base64", "url", "id".`);
48
+ },
49
+ fromStandardAudioBlock(block) {
50
+ if (isDataContentBlock(block)) {
51
+ if (block.source_type === "base64") {
52
+ if (!block.mime_type) throw new InvalidInputError("mime_type is required for base64 audio blocks");
53
+ const dataStr = typeof block.data === "string" ? block.data : typeof block.data === "object" && block.data !== null ? btoa(Array.from(block.data).map((byte) => String.fromCharCode(byte)).join("")) : String(block.data);
54
+ return { inlineData: {
55
+ mimeType: block.mime_type,
56
+ data: dataStr
57
+ } };
58
+ }
59
+ if (block.source_type === "url") {
60
+ const parsed = parseBase64DataUrl({ dataUrl: block.url });
61
+ if (parsed) {
62
+ const dataStr = typeof parsed.data === "string" ? parsed.data : btoa(Array.from(parsed.data).map((byte) => String.fromCharCode(byte)).join(""));
63
+ return { inlineData: {
64
+ mimeType: parsed.mime_type || block.mime_type || "audio/mpeg",
65
+ data: dataStr
66
+ } };
67
+ }
68
+ return { fileData: {
69
+ mimeType: block.mime_type || "audio/mpeg",
70
+ fileUri: block.url
71
+ } };
72
+ }
73
+ if (block.source_type === "id") return { fileData: {
74
+ mimeType: block.mime_type || "audio/mpeg",
75
+ fileUri: block.id
76
+ } };
77
+ }
78
+ throw new InvalidInputError(`Audio content blocks with source_type "${block.source_type}" are not supported for ChatGoogle. Supported source types are: "base64", "url", "id".`);
79
+ },
80
+ fromStandardFileBlock(block) {
81
+ if (isDataContentBlock(block)) {
82
+ if (block.source_type === "base64") {
83
+ if (!block.mime_type) throw new InvalidInputError("mime_type is required for base64 file blocks");
84
+ const dataStr = typeof block.data === "string" ? block.data : typeof block.data === "object" && block.data !== null ? btoa(Array.from(block.data).map((byte) => String.fromCharCode(byte)).join("")) : String(block.data);
85
+ return { inlineData: {
86
+ mimeType: block.mime_type,
87
+ data: dataStr
88
+ } };
89
+ }
90
+ if (block.source_type === "url") {
91
+ const parsed = parseBase64DataUrl({ dataUrl: block.url });
92
+ if (parsed) {
93
+ const dataStr = typeof parsed.data === "string" ? parsed.data : btoa(Array.from(parsed.data).map((byte) => String.fromCharCode(byte)).join(""));
94
+ return { inlineData: {
95
+ mimeType: parsed.mime_type || block.mime_type || "application/octet-stream",
96
+ data: dataStr
97
+ } };
98
+ }
99
+ return { fileData: {
100
+ mimeType: block.mime_type || "application/octet-stream",
101
+ fileUri: block.url
102
+ } };
103
+ }
104
+ if (block.source_type === "id") return { fileData: {
105
+ mimeType: block.mime_type || "application/octet-stream",
106
+ fileUri: block.id
107
+ } };
108
+ }
109
+ if ("source_type" in block && block.source_type === "text" && "text" in block) return { text: block.text };
110
+ throw new InvalidInputError(`File content blocks with source_type "${block.source_type}" are not supported for ChatGoogle. Supported source types are: "base64", "url", "id", "text".`);
111
+ }
112
+ };
113
+ function convertStandardDataContentBlockToGeminiPart(block) {
114
+ function uint8arrayToString(data) {
115
+ return btoa(Array.from(data).map((byte) => String.fromCharCode(byte)).join(""));
116
+ }
117
+ function extractMimeType(str) {
118
+ if (str.startsWith("data:")) return {
119
+ mimeType: str.split(":")[1].split(";")[0],
120
+ data: str.split(",")[1]
121
+ };
122
+ return {
123
+ mimeType: null,
124
+ data: null
125
+ };
126
+ }
127
+ if ("mimeType" in block && "data" in block) {
128
+ const mimeType = block.mimeType;
129
+ const data = typeof block.data === "string" ? block.data : uint8arrayToString(block.data);
130
+ return { inlineData: {
131
+ mimeType,
132
+ data
133
+ } };
134
+ } else if ("mimeType" in block && "url" in block) {
135
+ const mimeType = block.mimeType;
136
+ const fileUri = block.url;
137
+ return { fileData: {
138
+ mimeType,
139
+ fileUri
140
+ } };
141
+ } else if ("url" in block && block.url?.startsWith("data:")) {
142
+ const { mimeType, data } = extractMimeType(block.url);
143
+ if (mimeType && data) return { inlineData: {
144
+ mimeType,
145
+ data
146
+ } };
147
+ }
148
+ return null;
149
+ }
150
+ function convertStandardVideoContentBlockToGeminiPart(block) {
151
+ const ret = convertStandardDataContentBlockToGeminiPart(block);
152
+ if (ret && block.metadata && "videoMetadata" in block.metadata) ret.videoMetadata = block.metadata.videoMetadata;
153
+ return ret;
154
+ }
155
+ /**
156
+ * Converts a single LangChain standard content block (v1 format)
157
+ * into a Gemini.Part.
158
+ *
159
+ * This is intended to be called from `convertStandardContentMessageToGeminiContent`
160
+ */
161
+ function convertStandardContentBlockToGeminiPart(block) {
162
+ switch (block.type) {
163
+ case "text": return { text: block.text };
164
+ case "image":
165
+ case "audio": return convertStandardDataContentBlockToGeminiPart(block);
166
+ case "video": return convertStandardVideoContentBlockToGeminiPart(block);
167
+ default: return null;
168
+ }
169
+ }
170
+ /**
171
+ * Converts a single LangChain message with standard content blocks (v1 format) to Gemini Content.
172
+ * This handles messages that have `response_metadata.output_version === "v1"`.
173
+ *
174
+ * This is intended to be called from `convertMessagesToGeminiContent`
175
+ */
176
+ function convertStandardContentMessageToGeminiContent(message) {
177
+ if (SystemMessage.isInstance(message)) return null;
178
+ let role;
179
+ if (HumanMessage.isInstance(message)) role = "user";
180
+ else if (AIMessage.isInstance(message)) role = "model";
181
+ else if (ToolMessage.isInstance(message)) role = "function";
182
+ else if (ChatMessage.isInstance(message)) {
183
+ const msgRole = message.role.toLowerCase();
184
+ if (msgRole === "user" || msgRole === "human") role = "user";
185
+ else if (msgRole === "assistant" || msgRole === "ai" || msgRole === "model") role = "model";
186
+ else if (msgRole === "function" || msgRole === "tool") role = "function";
187
+ else role = "user";
188
+ } else role = "user";
189
+ const parts = [];
190
+ message.contentBlocks.forEach((block) => {
191
+ const contentBlock = message.additional_kwargs.originalTextContentBlock || block;
192
+ const part = convertStandardContentBlockToGeminiPart(contentBlock);
193
+ if (part) parts.push(part);
194
+ });
195
+ if (ToolMessage.isInstance(message) && message.tool_call_id) {
196
+ const responseContent = typeof message.content === "string" ? message.content : JSON.stringify(message.content);
197
+ parts.push({ functionResponse: {
198
+ name: message.name || "unknown",
199
+ response: { result: responseContent }
200
+ } });
201
+ }
202
+ if (parts.length === 0) return null;
203
+ return {
204
+ role,
205
+ parts
206
+ };
207
+ }
208
+ /**
209
+ * Converts a single LangChain message with legacy content blocks (v0 format) to Gemini Content.
210
+ * This handles messages that have `response_metadata.output_version === "v0"`.
211
+ *
212
+ * This is intended to be called from `convertMessagesToGeminiContent`
213
+ */
214
+ function convertLegacyContentMessageToGeminiContent(message, messages) {
215
+ if (SystemMessage.isInstance(message)) return null;
216
+ /**
217
+ * @deprecated - This is for use by `convertLegacyContentMessageToGeminiContent` only
218
+ */
219
+ function isMessageContentText(content) {
220
+ return typeof content === "object" && content !== null && "text" in content;
221
+ }
222
+ /**
223
+ * @deprecated - This is for use by `convertLegacyContentMessageToGeminiContent` only
224
+ */
225
+ function isMessageContentImageUrl(content) {
226
+ return typeof content === "object" && content !== null && "image_url" in content;
227
+ }
228
+ /**
229
+ * @deprecated - This is for use by `convertLegacyContentMessageToGeminiContent` only
230
+ */
231
+ function isMessageContentMedia(content) {
232
+ return typeof content === "object" && content !== null && "type" in content && content.type === "media";
233
+ }
234
+ /**
235
+ * Infers the MIME type from a URL based on its file extension.
236
+ * This is used as a fallback when the MIME type is not provided.
237
+ *
238
+ * @param url - The URL to infer the MIME type from
239
+ * @returns The inferred MIME type or a generic type if it cannot be determined
240
+ */
241
+ function inferMimeTypeFromUrl(url) {
242
+ const mimeTypeMap = {
243
+ jpg: "image/jpeg",
244
+ jpeg: "image/jpeg",
245
+ png: "image/png",
246
+ gif: "image/gif",
247
+ webp: "image/webp",
248
+ bmp: "image/bmp",
249
+ svg: "image/svg+xml",
250
+ ico: "image/x-icon",
251
+ tiff: "image/tiff",
252
+ tif: "image/tiff"
253
+ };
254
+ try {
255
+ const pathname = new URL(url).pathname;
256
+ const extension = pathname.split(".").pop()?.toLowerCase().split(/[?#]/)[0];
257
+ return extension ? mimeTypeMap[extension] : "application/octet-stream";
258
+ } catch {
259
+ const match = url.match(/\.([a-zA-Z0-9]+)(?:[?#]|$)/);
260
+ if (match) {
261
+ const extension = match[1].toLowerCase();
262
+ return mimeTypeMap[extension];
263
+ }
264
+ return "application/octet-stream";
265
+ }
266
+ }
267
+ function messageContentImageUrlData(content) {
268
+ const url = typeof content.image_url === "string" ? content.image_url : content.image_url.url;
269
+ if (!url) throw new InvalidInputError("Missing image URL in image_url content block.");
270
+ const dataUrl = parseBase64DataUrl({ dataUrl: url });
271
+ if (dataUrl?.data && dataUrl?.mime_type) return { inlineData: {
272
+ data: dataUrl.data,
273
+ mimeType: dataUrl.mime_type
274
+ } };
275
+ else {
276
+ const mimeType = inferMimeTypeFromUrl(url) || "image/png";
277
+ return { fileData: {
278
+ mimeType,
279
+ fileUri: url
280
+ } };
281
+ }
282
+ }
283
+ function messageContentImageUrl(content) {
284
+ const ret = messageContentImageUrlData(content);
285
+ supplementVideoMetadata(content, ret);
286
+ return ret;
287
+ }
288
+ function messageContentMediaData(content) {
289
+ if ("mimeType" in content && "data" in content) return { inlineData: {
290
+ mimeType: content.mimeType,
291
+ data: content.data
292
+ } };
293
+ else if ("mimeType" in content && "fileUri" in content) return { fileData: {
294
+ mimeType: content.mimeType,
295
+ fileUri: content.fileUri
296
+ } };
297
+ throw new InvalidInputError(`Invalid media content: ${JSON.stringify(content, null, 1)}. Expected either { mimeType, data } for inline data or { mimeType, fileUri } for file references.`);
298
+ }
299
+ function supplementVideoMetadata(content, ret) {
300
+ if ("videoMetadata" in content && typeof ret === "object") ret.videoMetadata = content.videoMetadata;
301
+ return ret;
302
+ }
303
+ function messageContentMedia(content) {
304
+ const ret = messageContentMediaData(content);
305
+ supplementVideoMetadata(content, ret);
306
+ return ret;
307
+ }
308
+ const role = iife(() => {
309
+ if (HumanMessage.isInstance(message)) return "user";
310
+ else if (AIMessage.isInstance(message)) return "model";
311
+ else if (ToolMessage.isInstance(message)) return "function";
312
+ else if (ChatMessage.isInstance(message)) {
313
+ const msgRole = message.role.toLowerCase();
314
+ if (msgRole === "user" || msgRole === "human") return "user";
315
+ else if (msgRole === "assistant" || msgRole === "ai" || msgRole === "model") return "model";
316
+ else if (msgRole === "function" || msgRole === "tool") return "function";
317
+ else return "user";
318
+ } else return "user";
319
+ });
320
+ let parts = [];
321
+ if (typeof message.content === "string") {
322
+ if (message.content.trim()) parts.push({ text: message.content });
323
+ } else if (Array.isArray(message.content)) {
324
+ for (const item of message.content) if (typeof item === "string") parts.push({ text: item });
325
+ else if (typeof item === "object" && item !== null) if (isMessageContentText(item)) parts.push({ text: item.text });
326
+ else if (isDataContentBlock(item)) parts.push(convertToProviderContentBlock(item, geminiContentBlockConverter));
327
+ else if (item?.type === "functionCall") {
328
+ const { type, functionCall,...etc } = item;
329
+ parts.push({
330
+ ...etc,
331
+ functionCall
332
+ });
333
+ } else if (isMessageContentImageUrl(item)) parts.push(messageContentImageUrl(item));
334
+ else if (isMessageContentMedia(item)) parts.push(messageContentMedia(item));
335
+ else parts.push(item);
336
+ }
337
+ if (ToolMessage.isInstance(message) && message.tool_call_id) {
338
+ const responseContent = typeof message.content === "string" ? message.content : JSON.stringify(message.content);
339
+ const toolCall = messages.filter(AIMessage.isInstance).find((msg) => msg.tool_calls?.find((tc) => tc.id === message.tool_call_id));
340
+ if (!toolCall) throw new ToolCallNotFoundError(message.tool_call_id);
341
+ parts.push({ functionResponse: {
342
+ name: toolCall?.name || "unknown",
343
+ response: { result: responseContent }
344
+ } });
345
+ }
346
+ if (role === "function") parts = parts.filter((part) => "functionResponse" in part);
347
+ if (parts.length > 0) return {
348
+ role,
349
+ parts
350
+ };
351
+ return null;
352
+ }
353
+ /**
354
+ * Converts an array of LangChain messages to Gemini API content format.
355
+ *
356
+ * This converter transforms LangChain's message types into the content structure
357
+ * expected by Google's Gemini API.
358
+ *
359
+ * @remarks
360
+ * The converter processes messages in two distinct ways based on their format:
361
+ *
362
+ * **V1 Standard Content Format**:
363
+ * Messages with `output_version: "v1"` in their response metadata use the
364
+ * standardized content block format. These are processed through
365
+ * `convertStandardContentMessageToGeminiContent`, which extracts text blocks
366
+ * from the `contentBlocks` array and maps message roles appropriately.
367
+ *
368
+ * **Google Format**:
369
+ * Messages without the v1 marker are processed using generic logic that handles:
370
+ * - String content: Simple text strings are converted to text parts
371
+ * - Array content: Arrays of strings or content blocks are processed individually
372
+ * - Tool messages: Converted to function response parts with tool call metadata
373
+ *
374
+ * @example
375
+ * ```typescript
376
+ * // Simple conversation with text messages
377
+ * const messages = [
378
+ * new HumanMessage("What is the weather?"),
379
+ * new AIMessage("I'll check that for you.")
380
+ * ];
381
+ * const contents = convertMessagesToGeminiContents(messages);
382
+ * // Returns: [
383
+ * // { role: "user", parts: [{ text: "What is the weather?" }] },
384
+ * // { role: "model", parts: [{ text: "I'll check that for you." }] }
385
+ * // ]
386
+ * ```
387
+ */
388
+ const convertMessagesToGeminiContents = (messages) => {
389
+ const contents = [];
390
+ for (const message of messages) {
391
+ const content = iife(() => {
392
+ const outputVersion = "output_version" in message.response_metadata ? message.response_metadata?.output_version : "v0";
393
+ switch (outputVersion) {
394
+ case "v1": return convertStandardContentMessageToGeminiContent(message);
395
+ case "v0":
396
+ default: return convertLegacyContentMessageToGeminiContent(message, messages);
397
+ }
398
+ });
399
+ if (content) contents.push(content);
400
+ }
401
+ return contents;
402
+ };
403
+ /**
404
+ * Converts LangChain system messages to Gemini API system instruction format.
405
+ *
406
+ * This converter extracts system messages from a message array and transforms them
407
+ * into the format expected by Google's Gemini API for system instructions. System
408
+ * instructions provide context and guidelines that influence the model's behavior
409
+ * throughout the conversation.
410
+ *
411
+ * @remarks
412
+ * The converter handles two distinct message formats:
413
+ *
414
+ * 1. **V1 Standard Content Format**: Messages with `output_version: "v1"` in their
415
+ * response metadata use the standardized content block format. Only text blocks
416
+ * are extracted from the `contentBlocks` array.
417
+ *
418
+ * 2. **Legacy Google Format**: Messages without the v1 marker use the legacy format
419
+ * where content can be:
420
+ * - A simple string (trimmed and converted to a text part)
421
+ * - An array of strings or objects with `type: "text"` and a `text` property
422
+ *
423
+ * The converter filters out non-SystemMessage instances and only processes text
424
+ * content, ignoring other content types like images or tool calls that may be
425
+ * present in the message array.
426
+ *
427
+ * @example
428
+ * ```typescript
429
+ * // Simple string system message
430
+ * const messages = [
431
+ * new SystemMessage("You are a helpful assistant.")
432
+ * ];
433
+ * const instruction = convertMessagesToGeminiSystemInstruction(messages);
434
+ * // Returns: { parts: [{ text: "You are a helpful assistant." }] }
435
+ * ```
436
+ *
437
+ * @param messages - Array of LangChain messages to extract system instructions from
438
+ * @returns A Gemini system instruction object with text parts, or undefined if no
439
+ * system messages with text content are found
440
+ */
441
+ const convertMessagesToGeminiSystemInstruction = (messages) => {
442
+ const systemParts = [];
443
+ for (const message of messages) if (SystemMessage.isInstance(message)) {
444
+ if (message.text.trim()) systemParts.push({ text: message.text });
445
+ }
446
+ if (systemParts.length === 0) return void 0;
447
+ return { parts: systemParts };
448
+ };
449
+ /**
450
+ * Converts an array of Gemini API parts into an array of LangChain tool calls.
451
+ *
452
+ * This function iterates through a given array of `Gemini.Part` objects,
453
+ * identifies parts that represent a function call, and transforms them into
454
+ * LangChain's standardized `ToolCall` format. Each resulting tool call is
455
+ * assigned a unique, sequential ID.
456
+ *
457
+ * @param parts - An array of `Gemini.Part` objects, which may or may not
458
+ * contain function calls.
459
+ * @returns An array of `ContentBlock.Tools.ToolCall` objects. If no function
460
+ * call parts are found, an empty array is returned.
461
+ *
462
+ * @remarks
463
+ * The `id` for each tool call is generated sequentially in the format `call_0`,
464
+ * `call_1`, and so on, based on its order of appearance in the input array.
465
+ *
466
+ * @example
467
+ * ```typescript
468
+ * const geminiParts: Gemini.Part[] = [
469
+ * { text: "Thinking about what to do..." },
470
+ * {
471
+ * functionCall: {
472
+ * name: "search_web",
473
+ * args: { query: "latest AI news" }
474
+ * }
475
+ * },
476
+ * {
477
+ * functionCall: {
478
+ * name: "calculator",
479
+ * args: { expression: "2 + 2" }
480
+ * }
481
+ * }
482
+ * ];
483
+ *
484
+ * const toolCalls = convertGeminiPartsToToolCalls(geminiParts);
485
+ * // toolCalls will be:
486
+ * // [
487
+ * // { type: "tool_call", id: "call_0", name: "search_web", args: { query: "latest AI news" } },
488
+ * // { type: "tool_call", id: "call_1", name: "calculator", args: { expression: "2 + 2" } }
489
+ * // ]
490
+ * ```
491
+ */
492
+ const convertGeminiPartsToToolCalls = (parts) => {
493
+ const toolCalls = [];
494
+ for (let i = 0; i < parts.length; i++) {
495
+ const part = parts[i];
496
+ if ("functionCall" in part && part.functionCall) {
497
+ const functionCallPart = part;
498
+ toolCalls.push({
499
+ type: "tool_call",
500
+ id: `call_${toolCalls.length}`,
501
+ name: functionCallPart.functionCall.name,
502
+ args: functionCallPart.functionCall.args ?? {},
503
+ thoughtSignature: functionCallPart.thoughtSignature
504
+ });
505
+ }
506
+ }
507
+ return toolCalls;
508
+ };
509
+ const convertGeminiPartToContentBlock = (part) => {
510
+ const block = iife(() => {
511
+ if ("text" in part && typeof part.text === "string") return {
512
+ type: "text",
513
+ text: part.text
514
+ };
515
+ else if ("inlineData" in part && part.inlineData) return {
516
+ type: "inlineData",
517
+ inlineData: part.inlineData
518
+ };
519
+ else if ("fileData" in part && part.fileData) return {
520
+ type: "fileData",
521
+ fileData: part.fileData
522
+ };
523
+ else if ("functionCall" in part && part.functionCall) return {
524
+ type: "functionCall",
525
+ functionCall: part.functionCall
526
+ };
527
+ else if ("functionResponse" in part && part.functionResponse) return {
528
+ type: "functionResponse",
529
+ functionResponse: part.functionResponse
530
+ };
531
+ else if ("executableCode" in part && part.executableCode) return {
532
+ type: "executableCode",
533
+ executableCode: part.executableCode
534
+ };
535
+ else if ("codeExecutionResult" in part && part.codeExecutionResult) return {
536
+ type: "codeExecutionResult",
537
+ codeExecutionResult: part.codeExecutionResult
538
+ };
539
+ return part;
540
+ });
541
+ const ret = {
542
+ thought: part.thought,
543
+ thoughtSignature: part.thoughtSignature,
544
+ partMetadata: part.partMetadata,
545
+ ...block
546
+ };
547
+ for (const attribute in ret) if (ret[attribute] === void 0) delete ret[attribute];
548
+ return ret;
549
+ };
550
+ /**
551
+ * Converts a Gemini API candidate response to a LangChain AIMessage.
552
+ *
553
+ * This converter transforms the response from Google's Gemini API into a standardized
554
+ * AIMessage format that can be used throughout the LangChain ecosystem. It handles
555
+ * various types of content including text, inline data, file data, function calls,
556
+ * and code execution results.
557
+ *
558
+ * @remarks
559
+ * The converter performs the following transformations:
560
+ * - Extracts tool calls from function call parts and assigns sequential IDs
561
+ * - Converts single text responses to string format for simplicity
562
+ * - Converts multi-part responses to ContentBlock array format
563
+ * - Preserves metadata including finish reason, safety ratings, and citations
564
+ * - Handles various Gemini part types (text, inlineData, fileData, functionCall, etc.)
565
+ *
566
+ * @example
567
+ * ```typescript
568
+ * const candidate: Gemini.Candidate = {
569
+ * content: {
570
+ * parts: [
571
+ * { text: "Hello, how can I help you?" }
572
+ * ]
573
+ * },
574
+ * finishReason: "STOP",
575
+ * safetyRatings: [...]
576
+ * };
577
+ *
578
+ * const aiMessage = convertGeminiCandidateToAIMessage(candidate);
579
+ * // Returns: AIMessage with content "Hello, how can I help you?"
580
+ * ```
581
+ *
582
+ * @param candidate - The Gemini API candidate response containing content parts and metadata
583
+ * @returns An AIMessage instance with converted content, tool calls, and metadata
584
+ */
585
+ const convertGeminiCandidateToAIMessage = (candidate) => {
586
+ function groundingSupportByPart(groundingSupports) {
587
+ const ret = [];
588
+ if (!groundingSupports || groundingSupports.length === 0) return [[]];
589
+ groundingSupports?.forEach((groundingSupport) => {
590
+ const segment = groundingSupport?.segment;
591
+ const partIndex = segment?.partIndex ?? 0;
592
+ if (ret[partIndex]) ret[partIndex].push(groundingSupport);
593
+ else ret[partIndex] = [groundingSupport];
594
+ });
595
+ return ret;
596
+ }
597
+ const parts = candidate.content?.parts || [];
598
+ const toolCalls = convertGeminiPartsToToolCalls(parts);
599
+ let content;
600
+ let originalTextContentBlock = void 0;
601
+ const groundingMetadata = candidate?.groundingMetadata;
602
+ const citationMetadata = candidate?.citationMetadata;
603
+ const groundingParts = groundingSupportByPart(groundingMetadata?.groundingSupports);
604
+ const hasContentPayload = (p) => !!p.text || !!p.thought || !!p.inlineData || !!p.fileData || !!p.functionCall || !!p.functionResponse || !!p.executableCode || !!p.codeExecutionResult;
605
+ const contentParts = parts.filter(hasContentPayload);
606
+ if (contentParts.length === 0) {
607
+ content = "";
608
+ if (parts.length > 0) originalTextContentBlock = convertGeminiPartToContentBlock(parts[0]);
609
+ } else if (contentParts.length === 1 && "text" in contentParts[0] && !("thought" in contentParts[0])) {
610
+ content = contentParts[0].text ?? "";
611
+ const mergedPart = { ...contentParts[0] };
612
+ for (const p of parts) if (p !== contentParts[0] && p.thoughtSignature) mergedPart.thoughtSignature = p.thoughtSignature;
613
+ originalTextContentBlock = convertGeminiPartToContentBlock(mergedPart);
614
+ } else content = parts.map((p) => convertGeminiPartToContentBlock(p));
615
+ const additional_kwargs = {
616
+ finishReason: candidate.finishReason,
617
+ finishMessage: candidate.finishMessage,
618
+ safetyRatings: candidate.safetyRatings,
619
+ tokenCount: candidate.tokenCount,
620
+ citationMetadata: candidate.citationMetadata,
621
+ groundingMetadata: candidate.groundingMetadata,
622
+ groundingAttributions: candidate.groundingAttributions,
623
+ urlRetrievalMetadata: candidate.urlRetrievalMetadata,
624
+ urlContextMetadata: candidate.urlContextMetadata,
625
+ avgLogprobs: candidate.avgLogprobs,
626
+ logprobsResult: candidate.logprobsResult,
627
+ originalTextContentBlock
628
+ };
629
+ const response_metadata = {
630
+ model_provider: "google",
631
+ finish_reason: candidate.finishReason,
632
+ safety_ratings: candidate.safetyRatings,
633
+ citation_metadata: candidate.citationMetadata,
634
+ grounding_metadata: candidate.groundingMetadata,
635
+ grounding_attributions: candidate.groundingAttributions,
636
+ url_retrieval_metadata: candidate.urlRetrievalMetadata,
637
+ url_context_metadata: candidate.urlContextMetadata,
638
+ avg_logprobs: candidate.avgLogprobs,
639
+ logprobs_result: candidate.logprobsResult,
640
+ citationMetadata,
641
+ groundingMetadata,
642
+ groundingSupport: groundingParts[0]
643
+ };
644
+ for (const attribute in additional_kwargs) if (additional_kwargs[attribute] === void 0) delete additional_kwargs[attribute];
645
+ for (const attribute in response_metadata) if (response_metadata[attribute] === void 0) delete response_metadata[attribute];
646
+ return new AIMessage({
647
+ content,
648
+ tool_calls: toolCalls.length > 0 ? toolCalls : void 0,
649
+ additional_kwargs,
650
+ response_metadata
651
+ });
652
+ };
653
+ const convertAIMessageToText = (message) => {
654
+ if (typeof message.content === "string") return message.content;
655
+ else if (Array.isArray(message.content)) return message.content.filter((c) => typeof c === "string" || c.type === "text").map((c) => typeof c === "string" ? c : c.text || "").join("");
656
+ else return "";
657
+ };
658
+ const convertGeminiGenerateContentResponseToUsageMetadata = (data) => {
659
+ function addModalityCounts(modalityTokenCounts, details) {
660
+ modalityTokenCounts?.forEach((modalityTokenCount) => {
661
+ const { modality, tokenCount } = modalityTokenCount;
662
+ if (typeof modality === "undefined" || typeof tokenCount === "undefined") return;
663
+ const modalityLc = modality.toLowerCase();
664
+ const currentCount = details[modalityLc] ?? 0;
665
+ details[modalityLc] = currentCount + tokenCount;
666
+ });
667
+ }
668
+ const usageMetadata = data.usageMetadata;
669
+ const inputTokenCount = usageMetadata?.promptTokenCount ?? 0;
670
+ const candidatesTokenCount = usageMetadata?.candidatesTokenCount ?? 0;
671
+ const thoughtsTokenCount = usageMetadata?.thoughtsTokenCount ?? 0;
672
+ const outputTokenCount = candidatesTokenCount + thoughtsTokenCount;
673
+ const totalTokens = usageMetadata?.totalTokenCount ?? inputTokenCount + outputTokenCount;
674
+ const input_token_details = {};
675
+ addModalityCounts(usageMetadata?.promptTokensDetails, input_token_details);
676
+ input_token_details.cache_read = usageMetadata?.cachedContentTokenCount ?? 0;
677
+ const output_token_details = {};
678
+ addModalityCounts(usageMetadata?.candidatesTokensDetails, output_token_details);
679
+ output_token_details.reasoning = usageMetadata?.thoughtsTokenCount ?? 0;
680
+ return {
681
+ input_tokens: inputTokenCount,
682
+ output_tokens: outputTokenCount,
683
+ total_tokens: totalTokens,
684
+ input_token_details,
685
+ output_token_details
686
+ };
687
+ };
688
+
689
+ //#endregion
690
+ export { convertAIMessageToText, convertGeminiCandidateToAIMessage, convertGeminiGenerateContentResponseToUsageMetadata, convertGeminiPartsToToolCalls, convertMessagesToGeminiContents, convertMessagesToGeminiSystemInstruction };
691
+ //# sourceMappingURL=messages.js.map