@aigne/gemini 0.14.16-beta.11 → 0.14.16-beta.12

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 CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.14.16-beta.12](https://github.com/AIGNE-io/aigne-framework/compare/gemini-v0.14.16-beta.11...gemini-v0.14.16-beta.12) (2026-01-06)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **core:** preserve Agent Skill in session compact and support complex tool result content ([#876](https://github.com/AIGNE-io/aigne-framework/issues/876)) ([edb86ae](https://github.com/AIGNE-io/aigne-framework/commit/edb86ae2b9cfe56a8f08b276f843606e310566cf))
9
+
10
+
11
+ ### Dependencies
12
+
13
+ * The following workspace dependencies were updated
14
+ * dependencies
15
+ * @aigne/core bumped to 1.72.0-beta.11
16
+ * devDependencies
17
+ * @aigne/test-utils bumped to 0.5.69-beta.11
18
+
3
19
  ## [0.14.16-beta.11](https://github.com/AIGNE-io/aigne-framework/compare/gemini-v0.14.16-beta.10...gemini-v0.14.16-beta.11) (2026-01-06)
4
20
 
5
21
 
@@ -109,5 +109,6 @@ export declare class GeminiChatModel extends ChatModel {
109
109
  private buildTools;
110
110
  private buildVideoContentParts;
111
111
  private buildContents;
112
+ private contentToParts;
112
113
  private ensureMessagesHasUserMessage;
113
114
  }
@@ -438,55 +438,46 @@ class GeminiChatModel extends core_1.ChatModel {
438
438
  .find((c) => c?.id === msg.toolCallId);
439
439
  if (!call)
440
440
  throw new Error(`Tool call not found: ${msg.toolCallId}`);
441
- const output = (0, yaml_1.parse)(msg.content);
442
- const isError = "error" in output && Boolean(input.error);
443
- const response = {
444
- tool: call.function.name,
441
+ if (!msg.content)
442
+ throw new Error("Tool call must have content");
443
+ // parse tool result as a record
444
+ let toolResult;
445
+ {
446
+ let text;
447
+ if (typeof msg.content === "string")
448
+ text = msg.content;
449
+ else if (msg.content?.length === 1) {
450
+ const first = msg.content[0];
451
+ if (first?.type === "text")
452
+ text = first.text;
453
+ }
454
+ if (text) {
455
+ try {
456
+ const obj = (0, yaml_1.parse)(text);
457
+ if ((0, type_utils_js_1.isRecord)(obj))
458
+ toolResult = obj;
459
+ }
460
+ catch {
461
+ // ignore
462
+ }
463
+ if (!toolResult)
464
+ toolResult = { result: text };
465
+ }
466
+ }
467
+ const functionResponse = {
468
+ id: msg.toolCallId,
469
+ name: call.function.name,
445
470
  };
446
- // NOTE: base on the documentation of gemini api, the content should include `output` field for successful result or `error` field for failed result,
447
- // and base on the actual test, add a tool field presenting the tool name can improve the LLM understanding that which tool is called.
448
- if (isError) {
449
- Object.assign(response, { status: "error" }, output);
471
+ if (toolResult) {
472
+ functionResponse.response = toolResult;
450
473
  }
451
474
  else {
452
- Object.assign(response, { status: "success" });
453
- if ("output" in output) {
454
- Object.assign(response, output);
455
- }
456
- else {
457
- Object.assign(response, { output });
458
- }
475
+ functionResponse.parts = await this.contentToParts(msg.content, options);
459
476
  }
460
- content.parts = [
461
- {
462
- functionResponse: {
463
- id: msg.toolCallId,
464
- name: call.function.name,
465
- response,
466
- },
467
- },
468
- ];
469
- }
470
- else if (typeof msg.content === "string") {
471
- content.parts = [{ text: msg.content }];
477
+ content.parts = [{ functionResponse }];
472
478
  }
473
- else if (Array.isArray(msg.content)) {
474
- content.parts = await Promise.all(msg.content.map(async (item) => {
475
- switch (item.type) {
476
- case "text":
477
- return { text: item.text };
478
- case "url":
479
- return { fileData: { fileUri: item.url, mimeType: item.mimeType } };
480
- case "file": {
481
- const part = await this.buildVideoContentParts(item, options);
482
- if (part)
483
- return part;
484
- return { inlineData: { data: item.data, mimeType: item.mimeType } };
485
- }
486
- case "local":
487
- throw new Error(`Unsupported local file: ${item.path}, it should be converted to base64 at ChatModel`);
488
- }
489
- }));
479
+ else if (msg.content) {
480
+ content.parts = await this.contentToParts(msg.content, options);
490
481
  }
491
482
  return content;
492
483
  }))).filter(type_utils_js_1.isNonNullable);
@@ -497,6 +488,26 @@ class GeminiChatModel extends core_1.ChatModel {
497
488
  }
498
489
  return result;
499
490
  }
491
+ async contentToParts(content, options) {
492
+ if (typeof content === "string")
493
+ return [{ text: content }];
494
+ return Promise.all(content.map(async (item) => {
495
+ switch (item.type) {
496
+ case "text":
497
+ return { text: item.text };
498
+ case "url":
499
+ return { fileData: { fileUri: item.url, mimeType: item.mimeType } };
500
+ case "file": {
501
+ const part = await this.buildVideoContentParts(item, options);
502
+ if (part)
503
+ return part;
504
+ return { inlineData: { data: item.data, mimeType: item.mimeType } };
505
+ }
506
+ case "local":
507
+ throw new Error(`Unsupported local file: ${item.path}, it should be converted to base64 at ChatModel`);
508
+ }
509
+ }));
510
+ }
500
511
  ensureMessagesHasUserMessage(systems, contents) {
501
512
  // no messages but system messages
502
513
  if (!contents.length && systems.length) {
@@ -109,5 +109,6 @@ export declare class GeminiChatModel extends ChatModel {
109
109
  private buildTools;
110
110
  private buildVideoContentParts;
111
111
  private buildContents;
112
+ private contentToParts;
112
113
  private ensureMessagesHasUserMessage;
113
114
  }
@@ -109,5 +109,6 @@ export declare class GeminiChatModel extends ChatModel {
109
109
  private buildTools;
110
110
  private buildVideoContentParts;
111
111
  private buildContents;
112
+ private contentToParts;
112
113
  private ensureMessagesHasUserMessage;
113
114
  }
@@ -1,7 +1,7 @@
1
1
  import { agentProcessResultToObject, ChatModel, StructuredOutputError, safeParseJSON, } from "@aigne/core";
2
2
  import { logger } from "@aigne/core/utils/logger.js";
3
3
  import { mergeUsage } from "@aigne/core/utils/model-utils.js";
4
- import { isNonNullable } from "@aigne/core/utils/type-utils.js";
4
+ import { isNonNullable, isRecord } from "@aigne/core/utils/type-utils.js";
5
5
  import { nodejs } from "@aigne/platform-helpers/nodejs/index.js";
6
6
  import { v7 } from "@aigne/uuid";
7
7
  import { createPartFromUri, createUserContent, FunctionCallingConfigMode, GoogleGenAI, ThinkingLevel, } from "@google/genai";
@@ -435,55 +435,46 @@ export class GeminiChatModel extends ChatModel {
435
435
  .find((c) => c?.id === msg.toolCallId);
436
436
  if (!call)
437
437
  throw new Error(`Tool call not found: ${msg.toolCallId}`);
438
- const output = parse(msg.content);
439
- const isError = "error" in output && Boolean(input.error);
440
- const response = {
441
- tool: call.function.name,
438
+ if (!msg.content)
439
+ throw new Error("Tool call must have content");
440
+ // parse tool result as a record
441
+ let toolResult;
442
+ {
443
+ let text;
444
+ if (typeof msg.content === "string")
445
+ text = msg.content;
446
+ else if (msg.content?.length === 1) {
447
+ const first = msg.content[0];
448
+ if (first?.type === "text")
449
+ text = first.text;
450
+ }
451
+ if (text) {
452
+ try {
453
+ const obj = parse(text);
454
+ if (isRecord(obj))
455
+ toolResult = obj;
456
+ }
457
+ catch {
458
+ // ignore
459
+ }
460
+ if (!toolResult)
461
+ toolResult = { result: text };
462
+ }
463
+ }
464
+ const functionResponse = {
465
+ id: msg.toolCallId,
466
+ name: call.function.name,
442
467
  };
443
- // NOTE: base on the documentation of gemini api, the content should include `output` field for successful result or `error` field for failed result,
444
- // and base on the actual test, add a tool field presenting the tool name can improve the LLM understanding that which tool is called.
445
- if (isError) {
446
- Object.assign(response, { status: "error" }, output);
468
+ if (toolResult) {
469
+ functionResponse.response = toolResult;
447
470
  }
448
471
  else {
449
- Object.assign(response, { status: "success" });
450
- if ("output" in output) {
451
- Object.assign(response, output);
452
- }
453
- else {
454
- Object.assign(response, { output });
455
- }
472
+ functionResponse.parts = await this.contentToParts(msg.content, options);
456
473
  }
457
- content.parts = [
458
- {
459
- functionResponse: {
460
- id: msg.toolCallId,
461
- name: call.function.name,
462
- response,
463
- },
464
- },
465
- ];
466
- }
467
- else if (typeof msg.content === "string") {
468
- content.parts = [{ text: msg.content }];
474
+ content.parts = [{ functionResponse }];
469
475
  }
470
- else if (Array.isArray(msg.content)) {
471
- content.parts = await Promise.all(msg.content.map(async (item) => {
472
- switch (item.type) {
473
- case "text":
474
- return { text: item.text };
475
- case "url":
476
- return { fileData: { fileUri: item.url, mimeType: item.mimeType } };
477
- case "file": {
478
- const part = await this.buildVideoContentParts(item, options);
479
- if (part)
480
- return part;
481
- return { inlineData: { data: item.data, mimeType: item.mimeType } };
482
- }
483
- case "local":
484
- throw new Error(`Unsupported local file: ${item.path}, it should be converted to base64 at ChatModel`);
485
- }
486
- }));
476
+ else if (msg.content) {
477
+ content.parts = await this.contentToParts(msg.content, options);
487
478
  }
488
479
  return content;
489
480
  }))).filter(isNonNullable);
@@ -494,6 +485,26 @@ export class GeminiChatModel extends ChatModel {
494
485
  }
495
486
  return result;
496
487
  }
488
+ async contentToParts(content, options) {
489
+ if (typeof content === "string")
490
+ return [{ text: content }];
491
+ return Promise.all(content.map(async (item) => {
492
+ switch (item.type) {
493
+ case "text":
494
+ return { text: item.text };
495
+ case "url":
496
+ return { fileData: { fileUri: item.url, mimeType: item.mimeType } };
497
+ case "file": {
498
+ const part = await this.buildVideoContentParts(item, options);
499
+ if (part)
500
+ return part;
501
+ return { inlineData: { data: item.data, mimeType: item.mimeType } };
502
+ }
503
+ case "local":
504
+ throw new Error(`Unsupported local file: ${item.path}, it should be converted to base64 at ChatModel`);
505
+ }
506
+ }));
507
+ }
497
508
  ensureMessagesHasUserMessage(systems, contents) {
498
509
  // no messages but system messages
499
510
  if (!contents.length && systems.length) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aigne/gemini",
3
- "version": "0.14.16-beta.11",
3
+ "version": "0.14.16-beta.12",
4
4
  "description": "AIGNE Gemini SDK for integrating with Google's Gemini AI models",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -40,7 +40,7 @@
40
40
  "yaml": "^2.8.1",
41
41
  "zod": "^3.25.67",
42
42
  "zod-to-json-schema": "^3.24.6",
43
- "@aigne/core": "^1.72.0-beta.10",
43
+ "@aigne/core": "^1.72.0-beta.11",
44
44
  "@aigne/platform-helpers": "^0.6.7-beta"
45
45
  },
46
46
  "devDependencies": {
@@ -49,7 +49,7 @@
49
49
  "npm-run-all": "^4.1.5",
50
50
  "rimraf": "^6.0.1",
51
51
  "typescript": "^5.9.2",
52
- "@aigne/test-utils": "^0.5.69-beta.10"
52
+ "@aigne/test-utils": "^0.5.69-beta.11"
53
53
  },
54
54
  "scripts": {
55
55
  "lint": "tsc --noEmit",