@aigne/gemini 0.14.16 → 1.74.0-beta

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 (58) hide show
  1. package/README.md +11 -11
  2. package/dist/gemini-chat-model.cjs +435 -0
  3. package/dist/gemini-chat-model.d.cts +123 -0
  4. package/dist/gemini-chat-model.d.cts.map +1 -0
  5. package/dist/gemini-chat-model.d.mts +123 -0
  6. package/dist/gemini-chat-model.d.mts.map +1 -0
  7. package/dist/gemini-chat-model.mjs +436 -0
  8. package/dist/gemini-chat-model.mjs.map +1 -0
  9. package/dist/gemini-image-model.cjs +169 -0
  10. package/dist/gemini-image-model.d.cts +37 -0
  11. package/dist/gemini-image-model.d.cts.map +1 -0
  12. package/dist/gemini-image-model.d.mts +37 -0
  13. package/dist/gemini-image-model.d.mts.map +1 -0
  14. package/dist/gemini-image-model.mjs +170 -0
  15. package/dist/gemini-image-model.mjs.map +1 -0
  16. package/dist/gemini-video-model.cjs +148 -0
  17. package/dist/gemini-video-model.d.cts +117 -0
  18. package/dist/gemini-video-model.d.cts.map +1 -0
  19. package/dist/gemini-video-model.d.mts +117 -0
  20. package/dist/gemini-video-model.d.mts.map +1 -0
  21. package/dist/gemini-video-model.mjs +149 -0
  22. package/dist/gemini-video-model.mjs.map +1 -0
  23. package/dist/index.cjs +7 -0
  24. package/dist/index.d.cts +4 -0
  25. package/dist/index.d.mts +4 -0
  26. package/dist/index.mjs +5 -0
  27. package/dist/utils.cjs +34 -0
  28. package/dist/utils.mjs +35 -0
  29. package/dist/utils.mjs.map +1 -0
  30. package/package.json +29 -30
  31. package/CHANGELOG.md +0 -2672
  32. package/lib/cjs/gemini-chat-model.d.ts +0 -117
  33. package/lib/cjs/gemini-chat-model.js +0 -564
  34. package/lib/cjs/gemini-image-model.d.ts +0 -34
  35. package/lib/cjs/gemini-image-model.js +0 -171
  36. package/lib/cjs/gemini-video-model.d.ts +0 -114
  37. package/lib/cjs/gemini-video-model.js +0 -164
  38. package/lib/cjs/index.d.ts +0 -3
  39. package/lib/cjs/index.js +0 -19
  40. package/lib/cjs/package.json +0 -3
  41. package/lib/cjs/utils.d.ts +0 -15
  42. package/lib/cjs/utils.js +0 -37
  43. package/lib/dts/gemini-chat-model.d.ts +0 -117
  44. package/lib/dts/gemini-image-model.d.ts +0 -34
  45. package/lib/dts/gemini-video-model.d.ts +0 -114
  46. package/lib/dts/index.d.ts +0 -3
  47. package/lib/dts/utils.d.ts +0 -15
  48. package/lib/esm/gemini-chat-model.d.ts +0 -117
  49. package/lib/esm/gemini-chat-model.js +0 -560
  50. package/lib/esm/gemini-image-model.d.ts +0 -34
  51. package/lib/esm/gemini-image-model.js +0 -167
  52. package/lib/esm/gemini-video-model.d.ts +0 -114
  53. package/lib/esm/gemini-video-model.js +0 -160
  54. package/lib/esm/index.d.ts +0 -3
  55. package/lib/esm/index.js +0 -3
  56. package/lib/esm/package.json +0 -3
  57. package/lib/esm/utils.d.ts +0 -15
  58. package/lib/esm/utils.js +0 -34
@@ -1,117 +0,0 @@
1
- import { type AgentInvokeOptions, type AgentProcessResult, ChatModel, type ChatModelInput, type ChatModelInputOptions, type ChatModelOptions, type ChatModelOutput } from "@aigne/core";
2
- import { type PromiseOrValue } from "@aigne/core/utils/type-utils.js";
3
- import { GoogleGenAI, type GoogleGenAIOptions, ThinkingLevel } from "@google/genai";
4
- export interface GeminiChatModelOptions extends ChatModelOptions {
5
- /**
6
- * API key for Gemini API
7
- *
8
- * If not provided, will look for GEMINI_API_KEY or GOOGLE_API_KEY in environment variables
9
- */
10
- apiKey?: string;
11
- /**
12
- * Optional client options for the Gemini SDK
13
- */
14
- clientOptions?: Partial<GoogleGenAIOptions>;
15
- }
16
- /**
17
- * Implementation of the ChatModel interface for Google's Gemini API
18
- *
19
- * @example
20
- * Here's how to create and use a Gemini chat model:
21
- * {@includeCode ../test/gemini-chat-model.test.ts#example-gemini-chat-model}
22
- *
23
- * @example
24
- * Here's an example with streaming response:
25
- * {@includeCode ../test/gemini-chat-model.test.ts#example-gemini-chat-model-streaming}
26
- */
27
- export declare class GeminiChatModel extends ChatModel {
28
- options?: GeminiChatModelOptions | undefined;
29
- constructor(options?: GeminiChatModelOptions | undefined);
30
- protected apiKeyEnvName: string;
31
- protected _googleClient?: GoogleGenAI;
32
- get googleClient(): GoogleGenAI;
33
- get credential(): {
34
- apiKey: string | undefined;
35
- model: string;
36
- };
37
- get modelOptions(): Partial<{
38
- [x: string]: unknown;
39
- model?: string | {
40
- $get: string;
41
- } | undefined;
42
- temperature?: number | {
43
- $get: string;
44
- } | undefined;
45
- topP?: number | {
46
- $get: string;
47
- } | undefined;
48
- frequencyPenalty?: number | {
49
- $get: string;
50
- } | undefined;
51
- presencePenalty?: number | {
52
- $get: string;
53
- } | undefined;
54
- parallelToolCalls?: boolean | {
55
- $get: string;
56
- } | undefined;
57
- modalities?: import("@aigne/core").Modality[] | {
58
- $get: string;
59
- } | undefined;
60
- preferInputFileType?: "url" | "file" | {
61
- $get: string;
62
- } | undefined;
63
- reasoningEffort?: number | "minimal" | "low" | "medium" | "high" | {
64
- $get: string;
65
- } | undefined;
66
- cacheConfig?: import("@aigne/core").CacheConfig | {
67
- $get: string;
68
- } | undefined;
69
- }> | undefined;
70
- countTokens(input: ChatModelInput): Promise<number>;
71
- private contentUnionToContent;
72
- process(input: ChatModelInput, options: AgentInvokeOptions): PromiseOrValue<AgentProcessResult<ChatModelOutput>>;
73
- protected thinkingBudgetModelMap: ({
74
- pattern: RegExp;
75
- support: boolean;
76
- type?: undefined;
77
- min?: undefined;
78
- max?: undefined;
79
- } | {
80
- pattern: RegExp;
81
- support: boolean;
82
- type: string;
83
- min?: undefined;
84
- max?: undefined;
85
- } | {
86
- pattern: RegExp;
87
- support: boolean;
88
- min: number;
89
- max: number;
90
- type?: undefined;
91
- })[];
92
- protected thinkingBudgetLevelMap: {
93
- high: number;
94
- medium: number;
95
- low: number;
96
- minimal: number;
97
- };
98
- protected thinkingLevelMap: {
99
- high: ThinkingLevel;
100
- medium: ThinkingLevel;
101
- low: ThinkingLevel;
102
- minimal: ThinkingLevel;
103
- };
104
- protected getThinkingBudget(model: string, effort: ChatModelInputOptions["reasoningEffort"]): {
105
- support: boolean;
106
- budget?: number;
107
- level?: ThinkingLevel;
108
- };
109
- private getParameters;
110
- private processInput;
111
- private buildConfig;
112
- private buildTools;
113
- private buildVideoContentParts;
114
- private buildContents;
115
- private contentToParts;
116
- private ensureMessagesHasUserMessage;
117
- }
@@ -1,564 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.GeminiChatModel = void 0;
4
- const core_1 = require("@aigne/core");
5
- const logger_js_1 = require("@aigne/core/utils/logger.js");
6
- const model_utils_js_1 = require("@aigne/core/utils/model-utils.js");
7
- const type_utils_js_1 = require("@aigne/core/utils/type-utils.js");
8
- const index_js_1 = require("@aigne/platform-helpers/nodejs/index.js");
9
- const uuid_1 = require("@aigne/uuid");
10
- const genai_1 = require("@google/genai");
11
- const yaml_1 = require("yaml");
12
- const zod_1 = require("zod");
13
- const zod_to_json_schema_1 = require("zod-to-json-schema");
14
- const GEMINI_DEFAULT_CHAT_MODEL = "gemini-2.0-flash";
15
- const OUTPUT_FUNCTION_NAME = "output";
16
- const NEED_UPLOAD_MAX_FILE_SIZE_MB = 20;
17
- /**
18
- * Implementation of the ChatModel interface for Google's Gemini API
19
- *
20
- * @example
21
- * Here's how to create and use a Gemini chat model:
22
- * {@includeCode ../test/gemini-chat-model.test.ts#example-gemini-chat-model}
23
- *
24
- * @example
25
- * Here's an example with streaming response:
26
- * {@includeCode ../test/gemini-chat-model.test.ts#example-gemini-chat-model-streaming}
27
- */
28
- class GeminiChatModel extends core_1.ChatModel {
29
- options;
30
- constructor(options) {
31
- super({
32
- ...options,
33
- model: options?.model || GEMINI_DEFAULT_CHAT_MODEL,
34
- });
35
- this.options = options;
36
- }
37
- apiKeyEnvName = "GEMINI_API_KEY";
38
- _googleClient;
39
- get googleClient() {
40
- if (this._googleClient)
41
- return this._googleClient;
42
- const { apiKey } = this.credential;
43
- if (!apiKey)
44
- throw new Error(`${this.name} requires an API key. Please provide it via \`options.apiKey\`, or set the \`${this.apiKeyEnvName}\` environment variable`);
45
- this._googleClient ??= new genai_1.GoogleGenAI({
46
- apiKey,
47
- ...this.options?.clientOptions,
48
- });
49
- return this._googleClient;
50
- }
51
- get credential() {
52
- const apiKey = this.options?.apiKey ||
53
- process.env[this.apiKeyEnvName] ||
54
- process.env.GEMINI_API_KEY ||
55
- process.env.GOOGLE_API_KEY;
56
- return {
57
- apiKey,
58
- model: this.options?.model || GEMINI_DEFAULT_CHAT_MODEL,
59
- };
60
- }
61
- get modelOptions() {
62
- return this.options?.modelOptions;
63
- }
64
- async countTokens(input) {
65
- const { model, ...request } = await this.getParameters(input);
66
- const contents = [];
67
- const { systemInstruction, tools } = request.config ?? {};
68
- if (systemInstruction)
69
- contents.push(this.contentUnionToContent(systemInstruction));
70
- if (tools?.length)
71
- contents.push({ role: "system", parts: [{ text: JSON.stringify(tools) }] });
72
- contents.push(...[request.contents].flat().map(this.contentUnionToContent));
73
- const tokens = (await this.googleClient.models.countTokens({
74
- model,
75
- contents,
76
- })).totalTokens;
77
- if (!(0, type_utils_js_1.isNil)(tokens))
78
- return tokens;
79
- return super.countTokens(input);
80
- }
81
- contentUnionToContent(content) {
82
- if (typeof content === "object" && "parts" in content) {
83
- return { role: "system", parts: content.parts };
84
- }
85
- else if (typeof content === "string") {
86
- return { role: "system", parts: [{ text: content }] };
87
- }
88
- else if (Array.isArray(content)) {
89
- return {
90
- role: "system",
91
- parts: content.map((i) => (typeof i === "string" ? { text: i } : i)),
92
- };
93
- }
94
- else {
95
- return { role: "system", parts: [content] };
96
- }
97
- }
98
- process(input, options) {
99
- return this.processInput(input, options);
100
- }
101
- // References: https://ai.google.dev/gemini-api/docs/thinking#set-budget
102
- thinkingBudgetModelMap = [
103
- // 注意:gemini-2.5-flash-image-preview 模型并不支持 thinking。see: https://github.com/CherryHQ/cherry-studio/issues/9614
104
- {
105
- pattern: /gemini-2.5-flash-image-preview/,
106
- support: false,
107
- },
108
- {
109
- pattern: /gemini-3(?!.*-image-)/,
110
- support: true,
111
- type: "level",
112
- },
113
- {
114
- pattern: /gemini-2.5-pro/,
115
- support: true,
116
- min: 128,
117
- max: 32768,
118
- },
119
- {
120
- pattern: /gemini-2.5-flash/,
121
- support: true,
122
- min: 0,
123
- max: 24576,
124
- },
125
- {
126
- pattern: /2.5-flash-lite/,
127
- support: true,
128
- min: 512,
129
- max: 24576,
130
- },
131
- {
132
- pattern: /.*/,
133
- support: false,
134
- },
135
- ];
136
- thinkingBudgetLevelMap = {
137
- high: 100000, // use 100k for high, finally capped by model max
138
- medium: 10000,
139
- low: 5000,
140
- minimal: 200,
141
- };
142
- thinkingLevelMap = {
143
- high: genai_1.ThinkingLevel.HIGH,
144
- medium: genai_1.ThinkingLevel.HIGH,
145
- low: genai_1.ThinkingLevel.LOW,
146
- minimal: genai_1.ThinkingLevel.LOW,
147
- };
148
- getThinkingBudget(model, effort) {
149
- const m = this.thinkingBudgetModelMap.find((i) => i.pattern.test(model));
150
- if (!m?.support)
151
- return { support: false };
152
- if (m.type === "level") {
153
- let level = genai_1.ThinkingLevel.THINKING_LEVEL_UNSPECIFIED;
154
- if (typeof effort === "string") {
155
- level = this.thinkingLevelMap[effort];
156
- }
157
- else if (typeof effort === "number") {
158
- level =
159
- effort >= this.thinkingBudgetLevelMap["medium"] ? genai_1.ThinkingLevel.HIGH : genai_1.ThinkingLevel.LOW;
160
- }
161
- return { support: true, level };
162
- }
163
- let budget = typeof effort === "string" ? this.thinkingBudgetLevelMap[effort] || undefined : effort;
164
- if (typeof budget === "undefined")
165
- return { support: true };
166
- if (typeof m.min === "number")
167
- budget = Math.max(m.min, budget);
168
- if (typeof m.max === "number")
169
- budget = Math.min(m.max, budget);
170
- return { support: true, budget };
171
- }
172
- async getParameters(input) {
173
- const { modelOptions = {} } = input;
174
- const model = modelOptions.model || this.credential.model;
175
- const { contents, config } = await this.buildContents(input);
176
- const thinkingBudget = this.getThinkingBudget(model, modelOptions.reasoningEffort);
177
- const parameters = {
178
- model,
179
- contents,
180
- config: {
181
- thinkingConfig: thinkingBudget.support
182
- ? {
183
- includeThoughts: true,
184
- thinkingBudget: thinkingBudget.budget,
185
- thinkingLevel: thinkingBudget.level,
186
- }
187
- : undefined,
188
- responseModalities: modelOptions.modalities,
189
- temperature: modelOptions.temperature,
190
- topP: modelOptions.topP,
191
- frequencyPenalty: modelOptions.frequencyPenalty,
192
- presencePenalty: modelOptions.presencePenalty,
193
- ...config,
194
- ...(await this.buildConfig(input)),
195
- },
196
- };
197
- return parameters;
198
- }
199
- async *processInput(input, options) {
200
- const parameters = await this.getParameters(input);
201
- const response = await this.googleClient.models.generateContentStream(parameters);
202
- let usage = {
203
- inputTokens: 0,
204
- outputTokens: 0,
205
- };
206
- let responseModel;
207
- const files = [];
208
- const toolCalls = [];
209
- let text = "";
210
- let json;
211
- for await (const chunk of response) {
212
- if (!responseModel && chunk.modelVersion) {
213
- responseModel = chunk.modelVersion;
214
- yield { delta: { json: { model: responseModel } } };
215
- }
216
- for (const { content } of chunk.candidates ?? []) {
217
- if (content?.parts) {
218
- for (const part of content.parts) {
219
- if (part.text) {
220
- if (part.thought) {
221
- yield { delta: { text: { thoughts: part.text } } };
222
- }
223
- else {
224
- text += part.text;
225
- if (input.responseFormat?.type !== "json_schema") {
226
- yield { delta: { text: { text: part.text } } };
227
- }
228
- }
229
- }
230
- if (part.inlineData?.data) {
231
- files.push({
232
- type: "file",
233
- data: part.inlineData.data,
234
- filename: part.inlineData.displayName,
235
- mimeType: part.inlineData.mimeType,
236
- });
237
- }
238
- if (part.functionCall?.name) {
239
- if (part.functionCall.name === OUTPUT_FUNCTION_NAME) {
240
- json = part.functionCall.args;
241
- }
242
- else {
243
- const toolCall = {
244
- id: part.functionCall.id || (0, uuid_1.v7)(),
245
- type: "function",
246
- function: {
247
- name: part.functionCall.name,
248
- arguments: part.functionCall.args || {},
249
- },
250
- };
251
- // Preserve thought_signature for 3.x models
252
- if (part.thoughtSignature && parameters.model.includes("gemini-3")) {
253
- toolCall.metadata = {
254
- thoughtSignature: part.thoughtSignature,
255
- };
256
- }
257
- toolCalls.push(toolCall);
258
- }
259
- }
260
- }
261
- }
262
- }
263
- if (chunk.usageMetadata) {
264
- if (chunk.usageMetadata.promptTokenCount)
265
- usage.inputTokens = chunk.usageMetadata.promptTokenCount;
266
- if (chunk.usageMetadata.candidatesTokenCount || chunk.usageMetadata.thoughtsTokenCount)
267
- usage.outputTokens =
268
- (chunk.usageMetadata.candidatesTokenCount || 0) +
269
- (chunk.usageMetadata.thoughtsTokenCount || 0);
270
- // Parse cache statistics if available
271
- if (chunk.usageMetadata.cachedContentTokenCount) {
272
- usage.cacheReadInputTokens = chunk.usageMetadata.cachedContentTokenCount;
273
- }
274
- }
275
- }
276
- if (toolCalls.length) {
277
- yield { delta: { json: { toolCalls } } };
278
- }
279
- if (input.responseFormat?.type === "json_schema") {
280
- if (json) {
281
- yield { delta: { json: { json } } };
282
- }
283
- else if (text) {
284
- yield { delta: { json: { json: (0, core_1.safeParseJSON)(text) } } };
285
- }
286
- else if (!toolCalls.length) {
287
- throw new core_1.StructuredOutputError("No JSON response from the model");
288
- }
289
- }
290
- else if (!toolCalls.length) {
291
- // NOTE: gemini-2.5-pro sometimes returns an empty response,
292
- // so we check here and retry with structured output mode (empty responses occur less frequently with tool calls)
293
- if (!text && !files.length) {
294
- logger_js_1.logger.warn("Empty response from Gemini, retrying with structured output mode");
295
- try {
296
- const outputSchema = zod_1.z.object({
297
- output: zod_1.z.string().describe("The final answer from the model"),
298
- });
299
- const response = await this.process({
300
- ...input,
301
- responseFormat: {
302
- type: "json_schema",
303
- jsonSchema: {
304
- name: "output",
305
- schema: (0, zod_to_json_schema_1.zodToJsonSchema)(outputSchema),
306
- },
307
- },
308
- }, options);
309
- const result = await (0, core_1.agentProcessResultToObject)(response);
310
- // Merge retry usage with the original usage
311
- usage = (0, model_utils_js_1.mergeUsage)(usage, result.usage);
312
- // Return the tool calls if retry has tool calls
313
- if (result.toolCalls?.length) {
314
- toolCalls.push(...result.toolCalls);
315
- yield { delta: { json: { toolCalls } } };
316
- }
317
- // Return the text from structured output of retry
318
- else {
319
- if (!result.json)
320
- throw new Error("Retrying with structured output mode got no json response");
321
- const parsed = outputSchema.safeParse(result.json);
322
- if (!parsed.success)
323
- throw new Error("Retrying with structured output mode got invalid json response");
324
- text = parsed.data.output;
325
- yield { delta: { text: { text } } };
326
- logger_js_1.logger.warn("Empty response from Gemini, retried with structured output mode successfully");
327
- }
328
- }
329
- catch (error) {
330
- logger_js_1.logger.error("Empty response from Gemini, retrying with structured output mode failed", error);
331
- throw new core_1.StructuredOutputError("No response from the model");
332
- }
333
- }
334
- }
335
- yield {
336
- delta: {
337
- json: {
338
- usage,
339
- files: files.length ? files : undefined,
340
- modelOptions: {
341
- reasoningEffort: parameters.config?.thinkingConfig?.thinkingLevel ||
342
- parameters.config?.thinkingConfig?.thinkingBudget,
343
- },
344
- },
345
- },
346
- };
347
- }
348
- async buildConfig(input) {
349
- const config = {};
350
- const { tools, toolConfig } = await this.buildTools(input);
351
- config.tools = tools;
352
- config.toolConfig = toolConfig;
353
- if (input.responseFormat?.type === "json_schema") {
354
- if (config.tools?.length) {
355
- config.tools.push({
356
- functionDeclarations: [
357
- {
358
- name: OUTPUT_FUNCTION_NAME,
359
- description: "Output the final response",
360
- parametersJsonSchema: input.responseFormat.jsonSchema.schema,
361
- },
362
- ],
363
- });
364
- config.toolConfig = {
365
- ...config.toolConfig,
366
- functionCallingConfig: { mode: genai_1.FunctionCallingConfigMode.ANY },
367
- };
368
- }
369
- else {
370
- config.responseJsonSchema = input.responseFormat.jsonSchema.schema;
371
- config.responseMimeType = "application/json";
372
- }
373
- }
374
- return config;
375
- }
376
- async buildTools(input) {
377
- const tools = [];
378
- for (const tool of input.tools ?? []) {
379
- tools.push({
380
- functionDeclarations: [
381
- {
382
- name: tool.function.name,
383
- description: tool.function.description,
384
- parametersJsonSchema: tool.function.parameters,
385
- },
386
- ],
387
- });
388
- }
389
- const functionCallingConfig = !input.toolChoice
390
- ? undefined
391
- : input.toolChoice === "auto"
392
- ? { mode: genai_1.FunctionCallingConfigMode.AUTO }
393
- : input.toolChoice === "none"
394
- ? { mode: genai_1.FunctionCallingConfigMode.NONE }
395
- : input.toolChoice === "required"
396
- ? { mode: genai_1.FunctionCallingConfigMode.ANY }
397
- : {
398
- mode: genai_1.FunctionCallingConfigMode.ANY,
399
- allowedFunctionNames: [input.toolChoice.function.name],
400
- };
401
- return { tools, toolConfig: { functionCallingConfig } };
402
- }
403
- async buildVideoContentParts(media) {
404
- const { path: filePath, mimeType: fileMimeType } = await this.transformFileType("local", media);
405
- if (filePath) {
406
- const stats = await index_js_1.nodejs.fs.stat(filePath);
407
- const fileSizeInBytes = stats.size;
408
- const fileSizeMB = fileSizeInBytes / (1024 * 1024);
409
- if (fileSizeMB > NEED_UPLOAD_MAX_FILE_SIZE_MB) {
410
- const uploadedFile = await this.googleClient.files.upload({
411
- file: filePath,
412
- config: { mimeType: fileMimeType },
413
- });
414
- let file = uploadedFile;
415
- while (file.state === "PROCESSING") {
416
- await new Promise((resolve) => setTimeout(resolve, 1000));
417
- if (file.name) {
418
- file = await this.googleClient.files.get({ name: file.name });
419
- }
420
- }
421
- if (file.state !== "ACTIVE") {
422
- throw new Error(`File ${file.name} failed to process: ${file.state}`);
423
- }
424
- if (file.uri && file.mimeType) {
425
- const result = (0, genai_1.createUserContent)([(0, genai_1.createPartFromUri)(file.uri, file.mimeType), ""]);
426
- const part = result.parts?.find((x) => x.fileData);
427
- if (part) {
428
- await index_js_1.nodejs.fs.rm(filePath);
429
- return part;
430
- }
431
- }
432
- }
433
- }
434
- }
435
- async buildContents(input) {
436
- const result = {
437
- contents: [],
438
- };
439
- const systemParts = [];
440
- result.contents = (await Promise.all(input.messages.map(async (msg) => {
441
- if (msg.role === "system") {
442
- if (typeof msg.content === "string") {
443
- systemParts.push({ text: msg.content });
444
- }
445
- else if (Array.isArray(msg.content)) {
446
- systemParts.push(...msg.content.map((item) => {
447
- if (item.type === "text")
448
- return { text: item.text };
449
- throw new Error(`Unsupported content type: ${item.type}`);
450
- }));
451
- }
452
- return;
453
- }
454
- const content = {
455
- role: msg.role === "agent" ? "model" : msg.role === "user" ? "user" : undefined,
456
- };
457
- if (msg.toolCalls) {
458
- content.parts = msg.toolCalls.map((call) => {
459
- const part = {
460
- functionCall: {
461
- id: call.id,
462
- name: call.function.name,
463
- args: call.function.arguments,
464
- },
465
- };
466
- // Restore thought_signature for 3.x models
467
- if (call.metadata?.thoughtSignature) {
468
- part.thoughtSignature = call.metadata.thoughtSignature;
469
- }
470
- return part;
471
- });
472
- }
473
- else if (msg.toolCallId) {
474
- const call = input.messages
475
- .flatMap((i) => i.toolCalls)
476
- .find((c) => c?.id === msg.toolCallId);
477
- if (!call)
478
- throw new Error(`Tool call not found: ${msg.toolCallId}`);
479
- if (!msg.content)
480
- throw new Error("Tool call must have content");
481
- // parse tool result as a record
482
- let toolResult;
483
- {
484
- let text;
485
- if (typeof msg.content === "string")
486
- text = msg.content;
487
- else if (msg.content?.length === 1) {
488
- const first = msg.content[0];
489
- if (first?.type === "text")
490
- text = first.text;
491
- }
492
- if (text) {
493
- try {
494
- const obj = (0, yaml_1.parse)(text);
495
- if ((0, type_utils_js_1.isRecord)(obj))
496
- toolResult = obj;
497
- }
498
- catch {
499
- // ignore
500
- }
501
- if (!toolResult)
502
- toolResult = { result: text };
503
- }
504
- }
505
- const functionResponse = {
506
- id: msg.toolCallId,
507
- name: call.function.name,
508
- };
509
- if (toolResult) {
510
- functionResponse.response = toolResult;
511
- }
512
- else {
513
- functionResponse.parts = await this.contentToParts(msg.content);
514
- }
515
- content.parts = [{ functionResponse }];
516
- }
517
- else if (msg.content) {
518
- content.parts = await this.contentToParts(msg.content);
519
- }
520
- return content;
521
- }))).filter(type_utils_js_1.isNonNullable);
522
- this.ensureMessagesHasUserMessage(systemParts, result.contents);
523
- if (systemParts.length) {
524
- result.config ??= {};
525
- result.config.systemInstruction = systemParts;
526
- }
527
- return result;
528
- }
529
- async contentToParts(content) {
530
- if (typeof content === "string")
531
- return [{ text: content }];
532
- return Promise.all(content.map(async (item) => {
533
- switch (item.type) {
534
- case "text":
535
- return { text: item.text };
536
- case "url":
537
- return { fileData: { fileUri: item.url, mimeType: item.mimeType } };
538
- case "file": {
539
- const part = await this.buildVideoContentParts(item);
540
- if (part)
541
- return part;
542
- return { inlineData: { data: item.data, mimeType: item.mimeType } };
543
- }
544
- case "local":
545
- throw new Error(`Unsupported local file: ${item.path}, it should be converted to base64 at ChatModel`);
546
- }
547
- }));
548
- }
549
- ensureMessagesHasUserMessage(systems, contents) {
550
- // no messages but system messages
551
- if (!contents.length && systems.length) {
552
- const system = systems.pop();
553
- if (system)
554
- contents.push({ role: "user", parts: [system] });
555
- }
556
- // first message is from model
557
- if (contents[0]?.role === "model") {
558
- const system = systems.pop();
559
- if (system)
560
- contents.unshift({ role: "user", parts: [system] });
561
- }
562
- }
563
- }
564
- exports.GeminiChatModel = GeminiChatModel;