@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
package/README.md CHANGED
@@ -2,28 +2,28 @@
2
2
 
3
3
  <p align="center">
4
4
  <picture>
5
- <source srcset="https://raw.githubusercontent.com/AIGNE-io/aigne-framework/main/logo-dark.svg" media="(prefers-color-scheme: dark)">
6
- <source srcset="https://raw.githubusercontent.com/AIGNE-io/aigne-framework/main/logo.svg" media="(prefers-color-scheme: light)">
7
- <img src="https://raw.githubusercontent.com/AIGNE-io/aigne-framework/main/logo.svg" alt="AIGNE Logo" width="400" />
5
+ <source srcset="https://raw.githubusercontent.com/ArcBlock/aigne-framework/main/logo-dark.svg" media="(prefers-color-scheme: dark)">
6
+ <source srcset="https://raw.githubusercontent.com/ArcBlock/aigne-framework/main/logo.svg" media="(prefers-color-scheme: light)">
7
+ <img src="https://raw.githubusercontent.com/ArcBlock/aigne-framework/main/logo.svg" alt="AIGNE Logo" width="400" />
8
8
  </picture>
9
9
  </p>
10
10
 
11
- [![GitHub star chart](https://img.shields.io/github/stars/AIGNE-io/aigne-framework?style=flat-square)](https://star-history.com/#AIGNE-io/aigne-framework)
12
- [![Open Issues](https://img.shields.io/github/issues-raw/AIGNE-io/aigne-framework?style=flat-square)](https://github.com/AIGNE-io/aigne-framework/issues)
13
- [![codecov](https://codecov.io/gh/AIGNE-io/aigne-framework/graph/badge.svg?token=DO07834RQL)](https://codecov.io/gh/AIGNE-io/aigne-framework)
11
+ [![GitHub star chart](https://img.shields.io/github/stars/ArcBlock/aigne-framework?style=flat-square)](https://star-history.com/#ArcBlock/aigne-framework)
12
+ [![Open Issues](https://img.shields.io/github/issues-raw/ArcBlock/aigne-framework?style=flat-square)](https://github.com/ArcBlock/aigne-framework/issues)
13
+ [![codecov](https://codecov.io/gh/ArcBlock/aigne-framework/graph/badge.svg?token=DO07834RQL)](https://codecov.io/gh/ArcBlock/aigne-framework)
14
14
  [![NPM Version](https://img.shields.io/npm/v/@aigne/gemini)](https://www.npmjs.com/package/@aigne/gemini)
15
- [![Elastic-2.0 licensed](https://img.shields.io/npm/l/@aigne/gemini)](https://github.com/AIGNE-io/aigne-framework/blob/main/LICENSE.md)
15
+ [![Elastic-2.0 licensed](https://img.shields.io/npm/l/@aigne/gemini)](https://github.com/ArcBlock/aigne-framework/blob/main/LICENSE.md)
16
16
 
17
- AIGNE Gemini SDK for integrating with Google's Gemini AI models within the [AIGNE Framework](https://github.com/AIGNE-io/aigne-framework).
17
+ AIGNE Gemini SDK for integrating with Google's Gemini AI models within the [AIGNE Framework](https://github.com/ArcBlock/aigne-framework).
18
18
 
19
19
  ## Introduction
20
20
 
21
21
  `@aigne/gemini` provides a seamless integration between the AIGNE Framework and Google's Gemini language models and API. This package enables developers to easily leverage Gemini's advanced AI capabilities in their AIGNE applications, providing a consistent interface across the framework while taking advantage of Google's state-of-the-art multimodal models.
22
22
 
23
23
  <picture>
24
- <source srcset="https://raw.githubusercontent.com/AIGNE-io/aigne-framework/main/assets/aigne-gemini-dark.png" media="(prefers-color-scheme: dark)">
25
- <source srcset="https://raw.githubusercontent.com/AIGNE-io/aigne-framework/main/assets/aigne-gemini.png" media="(prefers-color-scheme: light)">
26
- <img src="https://raw.githubusercontent.com/AIGNE-io/aigne-framework/main/assets/aigne-gemini.png" alt="AIGNE Arch" />
24
+ <source srcset="https://raw.githubusercontent.com/ArcBlock/aigne-framework/main/assets/aigne-gemini-dark.png" media="(prefers-color-scheme: dark)">
25
+ <source srcset="https://raw.githubusercontent.com/ArcBlock/aigne-framework/main/assets/aigne-gemini.png" media="(prefers-color-scheme: light)">
26
+ <img src="https://raw.githubusercontent.com/ArcBlock/aigne-framework/main/assets/aigne-gemini.png" alt="AIGNE Arch" />
27
27
  </picture>
28
28
 
29
29
  ## Features
@@ -0,0 +1,435 @@
1
+ let _aigne_core = require("@aigne/core");
2
+ let _aigne_core_utils_logger = require("@aigne/core/utils/logger");
3
+ let _aigne_core_utils_model_utils = require("@aigne/core/utils/model-utils");
4
+ let _aigne_core_utils_type_utils = require("@aigne/core/utils/type-utils");
5
+ let _aigne_utils_nodejs = require("@aigne/utils/nodejs");
6
+ let _aigne_uuid = require("@aigne/uuid");
7
+ let _google_genai = require("@google/genai");
8
+ let yaml = require("yaml");
9
+ let zod = require("zod");
10
+ let zod_to_json_schema = require("zod-to-json-schema");
11
+
12
+ //#region src/gemini-chat-model.ts
13
+ const GEMINI_DEFAULT_CHAT_MODEL = "gemini-2.0-flash";
14
+ const OUTPUT_FUNCTION_NAME = "output";
15
+ const NEED_UPLOAD_MAX_FILE_SIZE_MB = 20;
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
+ var GeminiChatModel = class extends _aigne_core.ChatModel {
28
+ constructor(options) {
29
+ super({
30
+ ...options,
31
+ model: options?.model || GEMINI_DEFAULT_CHAT_MODEL
32
+ });
33
+ this.options = options;
34
+ }
35
+ apiKeyEnvName = "GEMINI_API_KEY";
36
+ _googleClient;
37
+ get googleClient() {
38
+ if (this._googleClient) return this._googleClient;
39
+ const { apiKey } = this.credential;
40
+ if (!apiKey) throw new Error(`${this.name} requires an API key. Please provide it via \`options.apiKey\`, or set the \`${this.apiKeyEnvName}\` environment variable`);
41
+ this._googleClient ??= new _google_genai.GoogleGenAI({
42
+ apiKey,
43
+ ...this.options?.clientOptions
44
+ });
45
+ return this._googleClient;
46
+ }
47
+ get credential() {
48
+ return {
49
+ apiKey: this.options?.apiKey || process.env[this.apiKeyEnvName] || process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY,
50
+ model: this.options?.model || GEMINI_DEFAULT_CHAT_MODEL
51
+ };
52
+ }
53
+ get modelOptions() {
54
+ return this.options?.modelOptions;
55
+ }
56
+ async countTokens(input) {
57
+ const { model, ...request } = await this.getParameters(input);
58
+ const contents = [];
59
+ const { systemInstruction, tools } = request.config ?? {};
60
+ if (systemInstruction) contents.push(this.contentUnionToContent(systemInstruction));
61
+ if (tools?.length) contents.push({
62
+ role: "system",
63
+ parts: [{ text: JSON.stringify(tools) }]
64
+ });
65
+ contents.push(...[request.contents].flat().map(this.contentUnionToContent));
66
+ const tokens = (await this.googleClient.models.countTokens({
67
+ model,
68
+ contents
69
+ })).totalTokens;
70
+ if (!(0, _aigne_core_utils_type_utils.isNil)(tokens)) return tokens;
71
+ return super.countTokens(input);
72
+ }
73
+ contentUnionToContent(content) {
74
+ if (typeof content === "object" && "parts" in content) return {
75
+ role: "system",
76
+ parts: content.parts
77
+ };
78
+ else if (typeof content === "string") return {
79
+ role: "system",
80
+ parts: [{ text: content }]
81
+ };
82
+ else if (Array.isArray(content)) return {
83
+ role: "system",
84
+ parts: content.map((i) => typeof i === "string" ? { text: i } : i)
85
+ };
86
+ else return {
87
+ role: "system",
88
+ parts: [content]
89
+ };
90
+ }
91
+ process(input, options) {
92
+ return this.processInput(input, options);
93
+ }
94
+ thinkingBudgetModelMap = [
95
+ {
96
+ pattern: /gemini-2.5-flash-image-preview/,
97
+ support: false
98
+ },
99
+ {
100
+ pattern: /gemini-3(?!.*-image-)/,
101
+ support: true,
102
+ type: "level"
103
+ },
104
+ {
105
+ pattern: /gemini-2.5-pro/,
106
+ support: true,
107
+ min: 128,
108
+ max: 32768
109
+ },
110
+ {
111
+ pattern: /gemini-2.5-flash/,
112
+ support: true,
113
+ min: 0,
114
+ max: 24576
115
+ },
116
+ {
117
+ pattern: /2.5-flash-lite/,
118
+ support: true,
119
+ min: 512,
120
+ max: 24576
121
+ },
122
+ {
123
+ pattern: /.*/,
124
+ support: false
125
+ }
126
+ ];
127
+ thinkingBudgetLevelMap = {
128
+ high: 1e5,
129
+ medium: 1e4,
130
+ low: 5e3,
131
+ minimal: 200
132
+ };
133
+ thinkingLevelMap = {
134
+ high: _google_genai.ThinkingLevel.HIGH,
135
+ medium: _google_genai.ThinkingLevel.HIGH,
136
+ low: _google_genai.ThinkingLevel.LOW,
137
+ minimal: _google_genai.ThinkingLevel.LOW
138
+ };
139
+ getThinkingBudget(model, effort) {
140
+ const m = this.thinkingBudgetModelMap.find((i) => i.pattern.test(model));
141
+ if (!m?.support) return { support: false };
142
+ if (m.type === "level") {
143
+ let level = _google_genai.ThinkingLevel.THINKING_LEVEL_UNSPECIFIED;
144
+ if (typeof effort === "string") level = this.thinkingLevelMap[effort];
145
+ else if (typeof effort === "number") level = effort >= this.thinkingBudgetLevelMap["medium"] ? _google_genai.ThinkingLevel.HIGH : _google_genai.ThinkingLevel.LOW;
146
+ return {
147
+ support: true,
148
+ level
149
+ };
150
+ }
151
+ let budget = typeof effort === "string" ? this.thinkingBudgetLevelMap[effort] || void 0 : effort;
152
+ if (typeof budget === "undefined") return { support: true };
153
+ if (typeof m.min === "number") budget = Math.max(m.min, budget);
154
+ if (typeof m.max === "number") budget = Math.min(m.max, budget);
155
+ return {
156
+ support: true,
157
+ budget
158
+ };
159
+ }
160
+ async getParameters(input) {
161
+ const { modelOptions = {} } = input;
162
+ const model = modelOptions.model || this.credential.model;
163
+ const { contents, config } = await this.buildContents(input);
164
+ const thinkingBudget = this.getThinkingBudget(model, modelOptions.reasoningEffort);
165
+ return {
166
+ model,
167
+ contents,
168
+ config: {
169
+ thinkingConfig: thinkingBudget.support ? {
170
+ includeThoughts: true,
171
+ thinkingBudget: thinkingBudget.budget,
172
+ thinkingLevel: thinkingBudget.level
173
+ } : void 0,
174
+ responseModalities: modelOptions.modalities,
175
+ temperature: modelOptions.temperature,
176
+ topP: modelOptions.topP,
177
+ frequencyPenalty: modelOptions.frequencyPenalty,
178
+ presencePenalty: modelOptions.presencePenalty,
179
+ ...config,
180
+ ...await this.buildConfig(input)
181
+ }
182
+ };
183
+ }
184
+ async *processInput(input, options) {
185
+ const parameters = await this.getParameters(input);
186
+ const response = await this.googleClient.models.generateContentStream(parameters);
187
+ let usage = {
188
+ inputTokens: 0,
189
+ outputTokens: 0
190
+ };
191
+ let responseModel;
192
+ const files = [];
193
+ const toolCalls = [];
194
+ let text = "";
195
+ let json;
196
+ for await (const chunk of response) {
197
+ if (!responseModel && chunk.modelVersion) {
198
+ responseModel = chunk.modelVersion;
199
+ yield { delta: { json: { model: responseModel } } };
200
+ }
201
+ for (const { content } of chunk.candidates ?? []) if (content?.parts) for (const part of content.parts) {
202
+ if (part.text) if (part.thought) yield { delta: { text: { thoughts: part.text } } };
203
+ else {
204
+ text += part.text;
205
+ if (input.responseFormat?.type !== "json_schema") yield { delta: { text: { text: part.text } } };
206
+ }
207
+ if (part.inlineData?.data) files.push({
208
+ type: "file",
209
+ data: part.inlineData.data,
210
+ filename: part.inlineData.displayName,
211
+ mimeType: part.inlineData.mimeType
212
+ });
213
+ if (part.functionCall?.name) if (part.functionCall.name === OUTPUT_FUNCTION_NAME) json = part.functionCall.args;
214
+ else {
215
+ const toolCall = {
216
+ id: part.functionCall.id || (0, _aigne_uuid.v7)(),
217
+ type: "function",
218
+ function: {
219
+ name: part.functionCall.name,
220
+ arguments: part.functionCall.args || {}
221
+ }
222
+ };
223
+ if (part.thoughtSignature && parameters.model.includes("gemini-3")) toolCall.metadata = { thoughtSignature: part.thoughtSignature };
224
+ toolCalls.push(toolCall);
225
+ }
226
+ }
227
+ if (chunk.usageMetadata) {
228
+ if (chunk.usageMetadata.promptTokenCount) usage.inputTokens = chunk.usageMetadata.promptTokenCount;
229
+ if (chunk.usageMetadata.candidatesTokenCount || chunk.usageMetadata.thoughtsTokenCount) usage.outputTokens = (chunk.usageMetadata.candidatesTokenCount || 0) + (chunk.usageMetadata.thoughtsTokenCount || 0);
230
+ if (chunk.usageMetadata.cachedContentTokenCount) usage.cacheReadInputTokens = chunk.usageMetadata.cachedContentTokenCount;
231
+ }
232
+ }
233
+ if (toolCalls.length) yield { delta: { json: { toolCalls } } };
234
+ if (input.responseFormat?.type === "json_schema") {
235
+ if (json) yield { delta: { json: { json } } };
236
+ else if (text) yield { delta: { json: { json: (0, _aigne_core.safeParseJSON)(text) } } };
237
+ else if (!toolCalls.length) throw new _aigne_core.StructuredOutputError("No JSON response from the model");
238
+ } else if (!toolCalls.length) {
239
+ if (!text && !files.length) {
240
+ _aigne_core_utils_logger.logger.warn("Empty response from Gemini, retrying with structured output mode");
241
+ try {
242
+ const outputSchema = zod.z.object({ output: zod.z.string().describe("The final answer from the model") });
243
+ const result = await (0, _aigne_core.agentProcessResultToObject)(await this.process({
244
+ ...input,
245
+ responseFormat: {
246
+ type: "json_schema",
247
+ jsonSchema: {
248
+ name: "output",
249
+ schema: (0, zod_to_json_schema.zodToJsonSchema)(outputSchema)
250
+ }
251
+ }
252
+ }, options));
253
+ usage = (0, _aigne_core_utils_model_utils.mergeUsage)(usage, result.usage);
254
+ if (result.toolCalls?.length) {
255
+ toolCalls.push(...result.toolCalls);
256
+ yield { delta: { json: { toolCalls } } };
257
+ } else {
258
+ if (!result.json) throw new Error("Retrying with structured output mode got no json response");
259
+ const parsed = outputSchema.safeParse(result.json);
260
+ if (!parsed.success) throw new Error("Retrying with structured output mode got invalid json response");
261
+ text = parsed.data.output;
262
+ yield { delta: { text: { text } } };
263
+ _aigne_core_utils_logger.logger.warn("Empty response from Gemini, retried with structured output mode successfully");
264
+ }
265
+ } catch (error) {
266
+ _aigne_core_utils_logger.logger.error("Empty response from Gemini, retrying with structured output mode failed", error);
267
+ throw new _aigne_core.StructuredOutputError("No response from the model");
268
+ }
269
+ }
270
+ }
271
+ yield { delta: { json: {
272
+ usage,
273
+ files: files.length ? files : void 0,
274
+ modelOptions: { reasoningEffort: parameters.config?.thinkingConfig?.thinkingLevel || parameters.config?.thinkingConfig?.thinkingBudget }
275
+ } } };
276
+ }
277
+ async buildConfig(input) {
278
+ const config = {};
279
+ const { tools, toolConfig } = await this.buildTools(input);
280
+ config.tools = tools;
281
+ config.toolConfig = toolConfig;
282
+ if (input.responseFormat?.type === "json_schema") if (config.tools?.length) {
283
+ config.tools.push({ functionDeclarations: [{
284
+ name: OUTPUT_FUNCTION_NAME,
285
+ description: "Output the final response",
286
+ parametersJsonSchema: input.responseFormat.jsonSchema.schema
287
+ }] });
288
+ config.toolConfig = {
289
+ ...config.toolConfig,
290
+ functionCallingConfig: { mode: _google_genai.FunctionCallingConfigMode.ANY }
291
+ };
292
+ } else {
293
+ config.responseJsonSchema = input.responseFormat.jsonSchema.schema;
294
+ config.responseMimeType = "application/json";
295
+ }
296
+ return config;
297
+ }
298
+ async buildTools(input) {
299
+ const tools = [];
300
+ for (const tool of input.tools ?? []) tools.push({ functionDeclarations: [{
301
+ name: tool.function.name,
302
+ description: tool.function.description,
303
+ parametersJsonSchema: tool.function.parameters
304
+ }] });
305
+ return {
306
+ tools,
307
+ toolConfig: { functionCallingConfig: !input.toolChoice ? void 0 : input.toolChoice === "auto" ? { mode: _google_genai.FunctionCallingConfigMode.AUTO } : input.toolChoice === "none" ? { mode: _google_genai.FunctionCallingConfigMode.NONE } : input.toolChoice === "required" ? { mode: _google_genai.FunctionCallingConfigMode.ANY } : {
308
+ mode: _google_genai.FunctionCallingConfigMode.ANY,
309
+ allowedFunctionNames: [input.toolChoice.function.name]
310
+ } }
311
+ };
312
+ }
313
+ async buildVideoContentParts(media) {
314
+ const { path: filePath, mimeType: fileMimeType } = await this.transformFileType("local", media);
315
+ if (filePath) {
316
+ if ((await _aigne_utils_nodejs.nodejs.fs.stat(filePath)).size / (1024 * 1024) > NEED_UPLOAD_MAX_FILE_SIZE_MB) {
317
+ let file = await this.googleClient.files.upload({
318
+ file: filePath,
319
+ config: { mimeType: fileMimeType }
320
+ });
321
+ while (file.state === "PROCESSING") {
322
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
323
+ if (file.name) file = await this.googleClient.files.get({ name: file.name });
324
+ }
325
+ if (file.state !== "ACTIVE") throw new Error(`File ${file.name} failed to process: ${file.state}`);
326
+ if (file.uri && file.mimeType) {
327
+ const part = (0, _google_genai.createUserContent)([(0, _google_genai.createPartFromUri)(file.uri, file.mimeType), ""]).parts?.find((x) => x.fileData);
328
+ if (part) {
329
+ await _aigne_utils_nodejs.nodejs.fs.rm(filePath);
330
+ return part;
331
+ }
332
+ }
333
+ }
334
+ }
335
+ }
336
+ async buildContents(input) {
337
+ const result = { contents: [] };
338
+ const systemParts = [];
339
+ result.contents = (await Promise.all(input.messages.map(async (msg) => {
340
+ if (msg.role === "system") {
341
+ if (typeof msg.content === "string") systemParts.push({ text: msg.content });
342
+ else if (Array.isArray(msg.content)) systemParts.push(...msg.content.map((item) => {
343
+ if (item.type === "text") return { text: item.text };
344
+ throw new Error(`Unsupported content type: ${item.type}`);
345
+ }));
346
+ return;
347
+ }
348
+ const content = { role: msg.role === "agent" ? "model" : msg.role === "user" ? "user" : void 0 };
349
+ if (msg.toolCalls) content.parts = msg.toolCalls.map((call) => {
350
+ const part = { functionCall: {
351
+ id: call.id,
352
+ name: call.function.name,
353
+ args: call.function.arguments
354
+ } };
355
+ if (call.metadata?.thoughtSignature) part.thoughtSignature = call.metadata.thoughtSignature;
356
+ return part;
357
+ });
358
+ else if (msg.toolCallId) {
359
+ const call = input.messages.flatMap((i) => i.toolCalls).find((c) => c?.id === msg.toolCallId);
360
+ if (!call) throw new Error(`Tool call not found: ${msg.toolCallId}`);
361
+ if (!msg.content) throw new Error("Tool call must have content");
362
+ let toolResult;
363
+ {
364
+ let text;
365
+ if (typeof msg.content === "string") text = msg.content;
366
+ else if (msg.content?.length === 1) {
367
+ const first = msg.content[0];
368
+ if (first?.type === "text") text = first.text;
369
+ }
370
+ if (text) {
371
+ try {
372
+ const obj = (0, yaml.parse)(text);
373
+ if ((0, _aigne_core_utils_type_utils.isRecord)(obj)) toolResult = obj;
374
+ } catch {}
375
+ if (!toolResult) toolResult = { result: text };
376
+ }
377
+ }
378
+ const functionResponse = {
379
+ id: msg.toolCallId,
380
+ name: call.function.name
381
+ };
382
+ if (toolResult) functionResponse.response = toolResult;
383
+ else functionResponse.parts = await this.contentToParts(msg.content);
384
+ content.parts = [{ functionResponse }];
385
+ } else if (msg.content) content.parts = await this.contentToParts(msg.content);
386
+ return content;
387
+ }))).filter(_aigne_core_utils_type_utils.isNonNullable);
388
+ this.ensureMessagesHasUserMessage(systemParts, result.contents);
389
+ if (systemParts.length) {
390
+ result.config ??= {};
391
+ result.config.systemInstruction = systemParts;
392
+ }
393
+ return result;
394
+ }
395
+ async contentToParts(content) {
396
+ if (typeof content === "string") return [{ text: content }];
397
+ return Promise.all(content.map(async (item) => {
398
+ switch (item.type) {
399
+ case "text": return { text: item.text };
400
+ case "url": return { fileData: {
401
+ fileUri: item.url,
402
+ mimeType: item.mimeType
403
+ } };
404
+ case "file": {
405
+ const part = await this.buildVideoContentParts(item);
406
+ if (part) return part;
407
+ return { inlineData: {
408
+ data: item.data,
409
+ mimeType: item.mimeType
410
+ } };
411
+ }
412
+ case "local": throw new Error(`Unsupported local file: ${item.path}, it should be converted to base64 at ChatModel`);
413
+ }
414
+ }));
415
+ }
416
+ ensureMessagesHasUserMessage(systems, contents) {
417
+ if (!contents.length && systems.length) {
418
+ const system = systems.pop();
419
+ if (system) contents.push({
420
+ role: "user",
421
+ parts: [system]
422
+ });
423
+ }
424
+ if (contents[0]?.role === "model") {
425
+ const system = systems.pop();
426
+ if (system) contents.unshift({
427
+ role: "user",
428
+ parts: [system]
429
+ });
430
+ }
431
+ }
432
+ };
433
+
434
+ //#endregion
435
+ exports.GeminiChatModel = GeminiChatModel;
@@ -0,0 +1,123 @@
1
+ import * as _aigne_core0 from "@aigne/core";
2
+ import { AgentInvokeOptions, AgentProcessResult, ChatModel, ChatModelInput, ChatModelInputOptions, ChatModelOptions, ChatModelOutput } from "@aigne/core";
3
+ import { PromiseOrValue } from "@aigne/core/utils/type-utils";
4
+ import { GoogleGenAI, GoogleGenAIOptions, ThinkingLevel } from "@google/genai";
5
+
6
+ //#region src/gemini-chat-model.d.ts
7
+ interface GeminiChatModelOptions extends ChatModelOptions {
8
+ /**
9
+ * API key for Gemini API
10
+ *
11
+ * If not provided, will look for GEMINI_API_KEY or GOOGLE_API_KEY in environment variables
12
+ */
13
+ apiKey?: string;
14
+ /**
15
+ * Optional client options for the Gemini SDK
16
+ */
17
+ clientOptions?: Partial<GoogleGenAIOptions>;
18
+ }
19
+ /**
20
+ * Implementation of the ChatModel interface for Google's Gemini API
21
+ *
22
+ * @example
23
+ * Here's how to create and use a Gemini chat model:
24
+ * {@includeCode ../test/gemini-chat-model.test.ts#example-gemini-chat-model}
25
+ *
26
+ * @example
27
+ * Here's an example with streaming response:
28
+ * {@includeCode ../test/gemini-chat-model.test.ts#example-gemini-chat-model-streaming}
29
+ */
30
+ declare class GeminiChatModel extends ChatModel {
31
+ options?: GeminiChatModelOptions | undefined;
32
+ constructor(options?: GeminiChatModelOptions | undefined);
33
+ protected apiKeyEnvName: string;
34
+ protected _googleClient?: GoogleGenAI;
35
+ get googleClient(): GoogleGenAI;
36
+ get credential(): {
37
+ apiKey: string | undefined;
38
+ model: string;
39
+ };
40
+ get modelOptions(): Partial<{
41
+ [x: string]: unknown;
42
+ model?: string | {
43
+ $get: string;
44
+ } | undefined;
45
+ temperature?: number | {
46
+ $get: string;
47
+ } | undefined;
48
+ topP?: number | {
49
+ $get: string;
50
+ } | undefined;
51
+ frequencyPenalty?: number | {
52
+ $get: string;
53
+ } | undefined;
54
+ presencePenalty?: number | {
55
+ $get: string;
56
+ } | undefined;
57
+ parallelToolCalls?: boolean | {
58
+ $get: string;
59
+ } | undefined;
60
+ modalities?: {
61
+ $get: string;
62
+ } | _aigne_core0.Modality[] | undefined;
63
+ preferInputFileType?: "url" | {
64
+ $get: string;
65
+ } | "file" | undefined;
66
+ reasoningEffort?: number | {
67
+ $get: string;
68
+ } | "minimal" | "low" | "medium" | "high" | undefined;
69
+ cacheConfig?: {
70
+ $get: string;
71
+ } | _aigne_core0.CacheConfig | undefined;
72
+ }> | undefined;
73
+ countTokens(input: ChatModelInput): Promise<number>;
74
+ private contentUnionToContent;
75
+ process(input: ChatModelInput, options: AgentInvokeOptions): PromiseOrValue<AgentProcessResult<ChatModelOutput>>;
76
+ protected thinkingBudgetModelMap: ({
77
+ pattern: RegExp;
78
+ support: boolean;
79
+ type?: undefined;
80
+ min?: undefined;
81
+ max?: undefined;
82
+ } | {
83
+ pattern: RegExp;
84
+ support: boolean;
85
+ type: string;
86
+ min?: undefined;
87
+ max?: undefined;
88
+ } | {
89
+ pattern: RegExp;
90
+ support: boolean;
91
+ min: number;
92
+ max: number;
93
+ type?: undefined;
94
+ })[];
95
+ protected thinkingBudgetLevelMap: {
96
+ high: number;
97
+ medium: number;
98
+ low: number;
99
+ minimal: number;
100
+ };
101
+ protected thinkingLevelMap: {
102
+ high: ThinkingLevel;
103
+ medium: ThinkingLevel;
104
+ low: ThinkingLevel;
105
+ minimal: ThinkingLevel;
106
+ };
107
+ protected getThinkingBudget(model: string, effort: ChatModelInputOptions["reasoningEffort"]): {
108
+ support: boolean;
109
+ budget?: number;
110
+ level?: ThinkingLevel;
111
+ };
112
+ private getParameters;
113
+ private processInput;
114
+ private buildConfig;
115
+ private buildTools;
116
+ private buildVideoContentParts;
117
+ private buildContents;
118
+ private contentToParts;
119
+ private ensureMessagesHasUserMessage;
120
+ }
121
+ //#endregion
122
+ export { GeminiChatModel, GeminiChatModelOptions };
123
+ //# sourceMappingURL=gemini-chat-model.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gemini-chat-model.d.cts","names":[],"sources":["../src/gemini-chat-model.ts"],"mappings":";;;;;;UA+CiB,sBAAA,SAA+B,gBAAA;EAAA;;AAyBhD;;;EAzBgD,MAAA;EAAA;;AAyBhD;EAzBgD,aAAA,GAW9B,OAAA,CAAQ,kBAAA;AAAA;AAAA;;;;AAc1B;;;;;;;AAd0B,cAcb,eAAA,SAAwB,SAAA;EAAA,OAAA,GACG,sBAAA;EAAA,YAAA,OAAA,GAAA,sBAAA;EAAA,UAAA,aAAA;EAAA,UAAA,aAAA,GASZ,WAAA;EAAA,IAAA,aAAA,GAEV,WAAA;EAAA,IAAA,WAAA;IAAA,MAAA;IAAA,KAAA;EAAA;EAAA,IAAA,aAAA,GA+BA,OAAA;IAAA,CAAA,CAAA;IAAA,KAAA;MAAA,IAAA;IAAA;IAAA,WAAA;MAAA,IAAA;IAAA;IAAA,IAAA;MAAA,IAAA;IAAA;IAAA,gBAAA;MAAA,IAAA;IAAA;IAAA,eAAA;MAAA,IAAA;IAAA;IAAA,iBAAA;MAAA,IAAA;IAAA;IAAA,UAAA;MAAA,IAAA;IAAA,IA/BA,YAAA,CAAA,QAAA;IAAA,mBAAA;MAAA,IAAA;IAAA;IAAA,eAAA;MAAA,IAAA;IAAA;IAAA,WAAA;MAAA,IAAA;IAAA;;qBAmCkB,cAAA,GAAiB,OAAA;EAAA,QAAA,qBAAA;EAAA,QAAA,KAAA,EAwC1C,cAAA,EAAA,OAAA,EACE,kBAAA,GACR,cAAA,CAAe,kBAAA,CAAmB,eAAA;EAAA,UAAA,sBAAA;IAAA,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qDAwD3B,qBAAA;IAAA,OAAA;IAAA,MAAA;IAAA,KAAA,GACsC,aAAA;EAAA;EAAA,QAAA,aAAA;EAAA,QAAA,YAAA;EAAA,QAAA,WAAA;EAAA,QAAA,UAAA;EAAA,QAAA,sBAAA;EAAA,QAAA,aAAA;EAAA,QAAA,cAAA;EAAA,QAAA,4BAAA;AAAA"}