@lobehub/chat 1.96.13 → 1.96.14

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
@@ -2,6 +2,31 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ### [Version 1.96.14](https://github.com/lobehub/lobe-chat/compare/v1.96.13...v1.96.14)
6
+
7
+ <sup>Released on **2025-07-01**</sup>
8
+
9
+ #### ♻ Code Refactoring
10
+
11
+ - **misc**: Migrate to `@google/genai` SDK for Google Gemini API and Vertex AI.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### Code refactoring
19
+
20
+ - **misc**: Migrate to `@google/genai` SDK for Google Gemini API and Vertex AI, closes [#7884](https://github.com/lobehub/lobe-chat/issues/7884) ([fef3e5f](https://github.com/lobehub/lobe-chat/commit/fef3e5f))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
5
30
  ### [Version 1.96.13](https://github.com/lobehub/lobe-chat/compare/v1.96.12...v1.96.13)
6
31
 
7
32
  <sup>Released on **2025-07-01**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,13 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "improvements": [
5
+ "Migrate to @google/genai SDK for Google Gemini API and Vertex AI."
6
+ ]
7
+ },
8
+ "date": "2025-07-01",
9
+ "version": "1.96.14"
10
+ },
2
11
  {
3
12
  "children": {
4
13
  "improvements": [
@@ -58,19 +58,19 @@ SEARCH_PROVIDERS="searxng"
58
58
 
59
59
  Supported search engines include:
60
60
 
61
- | Value | Description | Environment Variable |
62
- | ------------ | ---------------------------------------------------------------------------------------- | ------------------------------------------- |
63
- | `anspire` | Search service provided by [Anspire](https://anspire.ai/). | `ANSPIRE_API_KEY` |
64
- | `bocha` | Search service from [Bocha](https://open.bochaai.com/). | `BOCHA_API_KEY` |
65
- | `brave` | [Brave](https://search.brave.com/help/api), a privacy-friendly search source. | `BRAVE_API_KEY` |
66
- | `exa` | [Exa](https://exa.ai/), a search API designed for AI. | `EXA_API_KEY` |
67
- | `firecrawl` | Search capabilities via [Firecrawl](https://firecrawl.dev/). | `FIRECRAWL_API_KEY` |
68
- | `google` | Uses [Google Programmable Search Engine](https://programmablesearchengine.google.com/). | `GOOGLE_PSE_API_KEY` `GOOGLE_PSE_ENGINE_ID` |
69
- | `jina` | Semantic search provided by [Jina AI](https://jina.ai/). | `JINA_READER_API_KEY` |
70
- | `kagi` | Premium search API by [Kagi](https://kagi.com/), requires a subscription key. | `KAGI_API_KEY` |
71
- | `search1api` | Aggregated search capabilities from [Search1API](https://www.search1api.com). | `SEARCH1API_CRAWL_API_KEY` |
72
- | `searxng` | Use a self-hosted or public [SearXNG](https://searx.space/) instance. | `SEARXNG_URL` |
73
- | `tavily` | [Tavily](https://www.tavily.com/), offers fast web summaries and answers. | `TAVILY_API_KEY` |
61
+ | Value | Description | Environment Variable |
62
+ | ------------ | --------------------------------------------------------------------------------------- | ------------------------------------------- |
63
+ | `anspire` | Search service provided by [Anspire](https://anspire.ai/). | `ANSPIRE_API_KEY` |
64
+ | `bocha` | Search service from [Bocha](https://open.bochaai.com/). | `BOCHA_API_KEY` |
65
+ | `brave` | [Brave](https://search.brave.com/help/api), a privacy-friendly search source. | `BRAVE_API_KEY` |
66
+ | `exa` | [Exa](https://exa.ai/), a search API designed for AI. | `EXA_API_KEY` |
67
+ | `firecrawl` | Search capabilities via [Firecrawl](https://firecrawl.dev/). | `FIRECRAWL_API_KEY` |
68
+ | `google` | Uses [Google Programmable Search Engine](https://programmablesearchengine.google.com/). | `GOOGLE_PSE_API_KEY` `GOOGLE_PSE_ENGINE_ID` |
69
+ | `jina` | Semantic search provided by [Jina AI](https://jina.ai/). | `JINA_READER_API_KEY` |
70
+ | `kagi` | Premium search API by [Kagi](https://kagi.com/), requires a subscription key. | `KAGI_API_KEY` |
71
+ | `search1api` | Aggregated search capabilities from [Search1API](https://www.search1api.com). | `SEARCH1API_CRAWL_API_KEY` |
72
+ | `searxng` | Use a self-hosted or public [SearXNG](https://searx.space/) instance. | `SEARXNG_URL` |
73
+ | `tavily` | [Tavily](https://www.tavily.com/), offers fast web summaries and answers. | `TAVILY_API_KEY` |
74
74
 
75
75
  > ⚠️ Some search providers require you to apply for an API Key and configure it in your `.env` file.
76
76
 
@@ -98,8 +98,8 @@ BROWSERLESS_BLOCK_ADS=1
98
98
 
99
99
  > 📌 Supported values:
100
100
  >
101
- > * `1`: Enable ad blocking (recommended);
102
- > * `0`: Disable ad blocking (default).
101
+ > - `1`: Enable ad blocking (recommended);
102
+ > - `0`: Disable ad blocking (default).
103
103
 
104
104
  > ✅ It is recommended to use with `BROWSERLESS_STEALTH_MODE=1` to enhance stealth and scraping success rate.
105
105
 
@@ -115,8 +115,8 @@ BROWSERLESS_STEALTH_MODE=1
115
115
 
116
116
  > 📌 Supported values:
117
117
  >
118
- > * `1`: Enable stealth mode (recommended);
119
- > * `0`: Disable stealth mode (default).
118
+ > - `1`: Enable stealth mode (recommended);
119
+ > - `0`: Disable stealth mode (default).
120
120
 
121
121
  > ⚠️ Some websites use advanced anti-scraping techniques. Enabling stealth mode can significantly improve scraping success rate.
122
122
 
@@ -156,8 +156,8 @@ TAVILY_SEARCH_DEPTH=basic
156
156
 
157
157
  Supported values:
158
158
 
159
- * `basic`: Fast search, returns brief results;
160
- * `advanced`: Deep search, returns more context and web page details.
159
+ - `basic`: Fast search, returns brief results;
160
+ - `advanced`: Deep search, returns more context and web page details.
161
161
 
162
162
  ---
163
163
 
@@ -171,8 +171,8 @@ TAVILY_EXTRACT_DEPTH=basic
171
171
 
172
172
  Supported values:
173
173
 
174
- * `basic`: Extracts basic info like title and content summary;
175
- * `advanced`: Extracts structured data, lists, charts, and more from web pages.
174
+ - `basic`: Extracts basic info like title and content summary;
175
+ - `advanced`: Extracts structured data, lists, charts, and more from web pages.
176
176
 
177
177
  ---
178
178
 
@@ -16,7 +16,7 @@ LobeChat 支持为 AI 配置**联网搜索功能**,使其能够实时获取互
16
16
  联网搜索可以让 AI 获取时效性内容,如最新新闻、技术动态或产品信息。你可以使用开源的 SearXNG 自行部署,也可以选择集成主流搜索引擎服务,如 Search1API、Google、Brave 等,根据你的使用场景自由组合。
17
17
  </Callout>
18
18
 
19
- 通过设置搜索服务环境变量 `SEARCH_PROVIDERS` 和对应的 API Key,LobeChat 将在多个搜索源中查询并返回结果。你还可以搭配配置爬虫服务环境变量 `CRAWLER_IMPLS`(如 `browserless`、`firecrawl`、`tavily` 等)以提取网页内容,实现搜索+阅读的增强能力。
19
+ 通过设置搜索服务环境变量 `SEARCH_PROVIDERS` 和对应的 API Key,LobeChat 将在多个搜索源中查询并返回结果。你还可以搭配配置爬虫服务环境变量 `CRAWLER_IMPLS`(如 `browserless`、`firecrawl`、`tavily` 等)以提取网页内容,实现搜索 + 阅读的增强能力。
20
20
 
21
21
  # 核心环境变量
22
22
 
@@ -30,15 +30,15 @@ CRAWLER_IMPLS="native,search1api"
30
30
 
31
31
  支持的爬虫类型如下:
32
32
 
33
- | 值 | 说明 | 环境变量 |
34
- | ------------- | -------------------------------------------------------------------------------------- | -------------------------- |
35
- | `browserless` | 基于 [Browserless](https://www.browserless.io/) 的无头浏览器爬虫,适合渲染复杂页面。 | `BROWSERLESS_TOKEN` |
36
- | `exa` | 使用 [Exa](https://exa.ai/) 提供的爬虫能力,需申请 API。 | `EXA_API_KEY` |
37
- | `firecrawl` | [Firecrawl](https://firecrawl.dev/) 无头浏览器 API,适合现代网站抓取。 | `FIRECRAWL_API_KEY` |
38
- | `jina` | 使用 [Jina AI](https://jina.ai/) 的爬虫服务,支持快速提取摘要信息。 | `JINA_READER_API_KEY` |
39
- | `native` | 内置通用爬虫,适用于标准网页结构。 | |
33
+ | 值 | 说明 | 环境变量 |
34
+ | ------------- | ---------------------------------------------------------------- | -------------------------- |
35
+ | `browserless` | 基于 [Browserless](https://www.browserless.io/) 的无头浏览器爬虫,适合渲染复杂页面。 | `BROWSERLESS_TOKEN` |
36
+ | `exa` | 使用 [Exa](https://exa.ai/) 提供的爬虫能力,需申请 API。 | `EXA_API_KEY` |
37
+ | `firecrawl` | [Firecrawl](https://firecrawl.dev/) 无头浏览器 API,适合现代网站抓取。 | `FIRECRAWL_API_KEY` |
38
+ | `jina` | 使用 [Jina AI](https://jina.ai/) 的爬虫服务,支持快速提取摘要信息。 | `JINA_READER_API_KEY` |
39
+ | `native` | 内置通用爬虫,适用于标准网页结构。 | |
40
40
  | `search1api` | 利用 [Search1API](https://www.search1api.com) 提供的页面抓取能力,适合结构化内容提取。 | `SEARCH1API_CRAWL_API_KEY` |
41
- | `tavily` | 使用 [Tavily](https://www.tavily.com/) 的网页抓取与摘要 API。 | `TAVILY_API_KEY` |
41
+ | `tavily` | 使用 [Tavily](https://www.tavily.com/) 的网页抓取与摘要 API。 | `TAVILY_API_KEY` |
42
42
 
43
43
  > 💡 设置多个爬虫可提升成功率,系统将根据优先级尝试不同爬虫。
44
44
 
@@ -54,19 +54,19 @@ SEARCH_PROVIDERS="searxng"
54
54
 
55
55
  支持的搜索引擎如下:
56
56
 
57
- | 值 | 说明 | 环境变量 |
58
- | ------------ | ---------------------------------------------------------------------------------------- | ------------------------------------------- |
59
- | `anspire` | 基于 [Anspire(安思派)](https://anspire.ai/) 提供的搜索服务。 | `ANSPIRE_API_KEY` |
60
- | `bocha` | 基于 [Bocha(博查)](https://open.bochaai.com/) 提供的搜索服务。 | `BOCHA_API_KEY` |
61
- | `brave` | [Brave](https://search.brave.com/help/api),隐私友好的搜索源。 | `BRAVE_API_KEY` |
62
- | `exa` | [Exa](https://exa.ai/),面向 AI 的搜索 API。 | `EXA_API_KEY` |
63
- | `firecrawl` | 支持 [Firecrawl](https://firecrawl.dev/) 提供的搜索服务。 | `FIRECRAWL_API_KEY` |
57
+ | 值 | 说明 | 环境变量 |
58
+ | ------------ | ------------------------------------------------------------------------------------- | ------------------------------------------- |
59
+ | `anspire` | 基于 [Anspire(安思派)](https://anspire.ai/) 提供的搜索服务。 | `ANSPIRE_API_KEY` |
60
+ | `bocha` | 基于 [Bocha(博查)](https://open.bochaai.com/) 提供的搜索服务。 | `BOCHA_API_KEY` |
61
+ | `brave` | [Brave](https://search.brave.com/help/api),隐私友好的搜索源。 | `BRAVE_API_KEY` |
62
+ | `exa` | [Exa](https://exa.ai/),面向 AI 的搜索 API。 | `EXA_API_KEY` |
63
+ | `firecrawl` | 支持 [Firecrawl](https://firecrawl.dev/) 提供的搜索服务。 | `FIRECRAWL_API_KEY` |
64
64
  | `google` | 使用 [Google Programmable Search Engine](https://programmablesearchengine.google.com/)。 | `GOOGLE_PSE_API_KEY` `GOOGLE_PSE_ENGINE_ID` |
65
- | `jina` | 使用 [Jina AI](https://jina.ai/) 提供的语义搜索服务。 | `JINA_READER_API_KEY` |
66
- | `kagi` | [Kagi](https://kagi.com/) 提供的高级搜索 API,需订阅 Key。 | `KAGI_API_KEY` |
67
- | `search1api` | 使用 [Search1API](https://www.search1api.com) 聚合搜索能力。 | `SEARCH1API_CRAWL_API_KEY` |
68
- | `searxng` | 使用自托管或公共 [SearXNG](https://searx.space/) 实例。 | `SEARXNG_URL` |
69
- | `tavily` | [Tavily](https://www.tavily.com/),快速网页摘要与答案返回。 | `TAVILY_API_KEY` |
65
+ | `jina` | 使用 [Jina AI](https://jina.ai/) 提供的语义搜索服务。 | `JINA_READER_API_KEY` |
66
+ | `kagi` | [Kagi](https://kagi.com/) 提供的高级搜索 API,需订阅 Key。 | `KAGI_API_KEY` |
67
+ | `search1api` | 使用 [Search1API](https://www.search1api.com) 聚合搜索能力。 | `SEARCH1API_CRAWL_API_KEY` |
68
+ | `searxng` | 使用自托管或公共 [SearXNG](https://searx.space/) 实例。 | `SEARXNG_URL` |
69
+ | `tavily` | [Tavily](https://www.tavily.com/),快速网页摘要与答案返回。 | `TAVILY_API_KEY` |
70
70
 
71
71
  > ⚠️ 某些搜索提供商需要单独申请 API Key,并在 `.env` 中配置相关凭证。
72
72
 
@@ -94,8 +94,8 @@ BROWSERLESS_BLOCK_ADS=1
94
94
 
95
95
  > 📌 支持的值:
96
96
  >
97
- > * `1`:启用广告拦截(推荐);
98
- > * `0`:禁用广告拦截(默认)。
97
+ > - `1`:启用广告拦截(推荐);
98
+ > - `0`:禁用广告拦截(默认)。
99
99
 
100
100
  > ✅ 建议与 `BROWSERLESS_STEALTH_MODE=1` 一起使用,提高爬虫的隐蔽性和成功率。
101
101
 
@@ -111,8 +111,8 @@ BROWSERLESS_STEALTH_MODE=1
111
111
 
112
112
  > 📌 支持的值:
113
113
  >
114
- > * `1`:启用隐身模式(推荐);
115
- > * `0`:禁用隐身模式(默认)。
114
+ > - `1`:启用隐身模式(推荐);
115
+ > - `0`:禁用隐身模式(默认)。
116
116
 
117
117
  > ⚠️ 某些网站存在高级反爬机制,启用隐身模式可以显著提升抓取成功率。
118
118
 
@@ -152,8 +152,8 @@ TAVILY_SEARCH_DEPTH=basic
152
152
 
153
153
  支持的值:
154
154
 
155
- * `basic`: 快速搜索,返回简要结果;
156
- * `advanced`: 深度搜索,返回更多上下文和网页信息。
155
+ - `basic`: 快速搜索,返回简要结果;
156
+ - `advanced`: 深度搜索,返回更多上下文和网页信息。
157
157
 
158
158
  ---
159
159
 
@@ -167,8 +167,8 @@ TAVILY_EXTRACT_DEPTH=basic
167
167
 
168
168
  支持的值:
169
169
 
170
- * `basic`: 提取标题、正文摘要等基础信息;
171
- * `advanced`: 提取网页的结构化信息、列表、图表等更多内容。
170
+ - `basic`: 提取标题、正文摘要等基础信息;
171
+ - `advanced`: 提取网页的结构化信息、列表、图表等更多内容。
172
172
 
173
173
  ---
174
174
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "1.96.13",
3
+ "version": "1.96.14",
4
4
  "description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -135,8 +135,7 @@
135
135
  "@codesandbox/sandpack-react": "^2.20.0",
136
136
  "@cyntler/react-doc-viewer": "^1.17.0",
137
137
  "@electric-sql/pglite": "0.2.17",
138
- "@google-cloud/vertexai": "^1.10.0",
139
- "@google/generative-ai": "^0.24.1",
138
+ "@google/genai": "^1.6.0",
140
139
  "@huggingface/inference": "^2.8.1",
141
140
  "@icons-pack/react-simple-icons": "9.6.0",
142
141
  "@khmyznikov/pwa-install": "0.3.9",
@@ -1,9 +1,10 @@
1
1
  // @vitest-environment edge-runtime
2
- import { FunctionDeclarationsTool } from '@google/generative-ai';
2
+ import { GenerateContentResponse, Tool } from '@google/genai';
3
3
  import OpenAI from 'openai';
4
4
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
5
5
 
6
6
  import { OpenAIChatMessage } from '@/libs/model-runtime';
7
+ import { ChatStreamPayload } from '@/types/openai/chat';
7
8
  import * as imageToBase64Module from '@/utils/imageToBase64';
8
9
 
9
10
  import * as debugStreamModule from '../utils/debugStream';
@@ -22,9 +23,8 @@ beforeEach(() => {
22
23
  instance = new LobeGoogleAI({ apiKey: 'test' });
23
24
 
24
25
  // 使用 vi.spyOn 来模拟 chat.completions.create 方法
25
- vi.spyOn(instance['client'], 'getGenerativeModel').mockReturnValue({
26
- generateContentStream: vi.fn().mockResolvedValue(new ReadableStream()),
27
- } as any);
26
+ const mockStreamData = (async function* (): AsyncGenerator<GenerateContentResponse> {})();
27
+ vi.spyOn(instance['client'].models, 'generateContentStream').mockResolvedValue(mockStreamData);
28
28
  });
29
29
 
30
30
  afterEach(() => {
@@ -60,9 +60,9 @@ describe('LobeGoogleAI', () => {
60
60
  controller.close();
61
61
  },
62
62
  });
63
- vi.spyOn(instance['client'], 'getGenerativeModel').mockReturnValue({
64
- generateContentStream: vi.fn().mockResolvedValueOnce(mockStream),
65
- } as any);
63
+ vi.spyOn(instance['client'].models, 'generateContentStream').mockResolvedValue(
64
+ mockStream as any,
65
+ );
66
66
 
67
67
  const result = await instance.chat({
68
68
  messages: [{ content: 'Hello', role: 'user' }],
@@ -208,9 +208,9 @@ describe('LobeGoogleAI', () => {
208
208
  },
209
209
  });
210
210
 
211
- vi.spyOn(instance['client'], 'getGenerativeModel').mockReturnValue({
212
- generateContentStream: vi.fn().mockResolvedValueOnce(mockStream),
213
- } as any);
211
+ vi.spyOn(instance['client'].models, 'generateContentStream').mockResolvedValue(
212
+ mockStream as any,
213
+ );
214
214
  });
215
215
 
216
216
  it('should call debugStream in DEBUG mode', async () => {
@@ -224,9 +224,9 @@ describe('LobeGoogleAI', () => {
224
224
  controller.close();
225
225
  },
226
226
  });
227
- vi.spyOn(instance['client'], 'getGenerativeModel').mockReturnValue({
228
- generateContentStream: vi.fn().mockResolvedValueOnce(mockStream),
229
- } as any);
227
+ vi.spyOn(instance['client'].models, 'generateContentStream').mockResolvedValue(
228
+ mockStream as any,
229
+ );
230
230
  const debugStreamSpy = vi
231
231
  .spyOn(debugStreamModule, 'debugStream')
232
232
  .mockImplementation(() => Promise.resolve());
@@ -250,9 +250,7 @@ describe('LobeGoogleAI', () => {
250
250
 
251
251
  const apiError = new Error(message);
252
252
 
253
- vi.spyOn(instance['client'], 'getGenerativeModel').mockReturnValue({
254
- generateContentStream: vi.fn().mockRejectedValue(apiError),
255
- } as any);
253
+ vi.spyOn(instance['client'].models, 'generateContentStream').mockRejectedValue(apiError);
256
254
 
257
255
  try {
258
256
  await instance.chat({
@@ -271,9 +269,7 @@ describe('LobeGoogleAI', () => {
271
269
 
272
270
  const apiError = new Error(message);
273
271
 
274
- vi.spyOn(instance['client'], 'getGenerativeModel').mockReturnValue({
275
- generateContentStream: vi.fn().mockRejectedValue(apiError),
276
- } as any);
272
+ vi.spyOn(instance['client'].models, 'generateContentStream').mockRejectedValue(apiError);
277
273
 
278
274
  try {
279
275
  await instance.chat({
@@ -292,9 +288,7 @@ describe('LobeGoogleAI', () => {
292
288
 
293
289
  const apiError = new Error(message);
294
290
 
295
- vi.spyOn(instance['client'], 'getGenerativeModel').mockReturnValue({
296
- generateContentStream: vi.fn().mockRejectedValue(apiError),
297
- } as any);
291
+ vi.spyOn(instance['client'].models, 'generateContentStream').mockRejectedValue(apiError);
298
292
 
299
293
  try {
300
294
  await instance.chat({
@@ -326,9 +320,7 @@ describe('LobeGoogleAI', () => {
326
320
 
327
321
  const apiError = new Error(message);
328
322
 
329
- vi.spyOn(instance['client'], 'getGenerativeModel').mockReturnValue({
330
- generateContentStream: vi.fn().mockRejectedValue(apiError),
331
- } as any);
323
+ vi.spyOn(instance['client'].models, 'generateContentStream').mockRejectedValue(apiError);
332
324
 
333
325
  try {
334
326
  await instance.chat({
@@ -354,9 +346,7 @@ describe('LobeGoogleAI', () => {
354
346
  const apiError = new Error('Error message');
355
347
 
356
348
  // 使用 vi.spyOn 来模拟 chat.completions.create 方法
357
- vi.spyOn(instance['client'], 'getGenerativeModel').mockReturnValue({
358
- generateContentStream: vi.fn().mockRejectedValue(apiError),
359
- } as any);
349
+ vi.spyOn(instance['client'].models, 'generateContentStream').mockRejectedValue(apiError);
360
350
 
361
351
  // Act
362
352
  try {
@@ -392,9 +382,7 @@ describe('LobeGoogleAI', () => {
392
382
  };
393
383
  const apiError = new OpenAI.APIError(400, errorInfo, 'module error', {});
394
384
 
395
- vi.spyOn(instance['client'], 'getGenerativeModel').mockReturnValue({
396
- generateContentStream: vi.fn().mockRejectedValue(apiError),
397
- } as any);
385
+ vi.spyOn(instance['client'].models, 'generateContentStream').mockRejectedValue(apiError);
398
386
 
399
387
  // Act
400
388
  try {
@@ -418,9 +406,9 @@ describe('LobeGoogleAI', () => {
418
406
  // Arrange
419
407
  const genericError = new Error('Generic Error');
420
408
 
421
- vi.spyOn(instance['client'], 'getGenerativeModel').mockReturnValue({
422
- generateContentStream: vi.fn().mockRejectedValue(genericError),
423
- } as any);
409
+ vi.spyOn(instance['client'].models, 'generateContentStream').mockRejectedValue(
410
+ genericError,
411
+ );
424
412
 
425
413
  // Act
426
414
  try {
@@ -642,11 +630,11 @@ describe('LobeGoogleAI', () => {
642
630
  const googleTools = instance['buildGoogleTools'](tools);
643
631
 
644
632
  expect(googleTools).toHaveLength(1);
645
- expect((googleTools![0] as FunctionDeclarationsTool).functionDeclarations![0]).toEqual({
633
+ expect((googleTools![0] as Tool).functionDeclarations![0]).toEqual({
646
634
  name: 'testTool',
647
635
  description: 'A test tool',
648
636
  parameters: {
649
- type: 'object',
637
+ type: 'OBJECT',
650
638
  properties: {
651
639
  param1: { type: 'string' },
652
640
  param2: { type: 'number' },
@@ -655,6 +643,75 @@ describe('LobeGoogleAI', () => {
655
643
  },
656
644
  });
657
645
  });
646
+
647
+ it('should also add tools when tool_calls exists', () => {
648
+ const tools: OpenAI.ChatCompletionTool[] = [
649
+ {
650
+ function: {
651
+ name: 'testTool',
652
+ description: 'A test tool',
653
+ parameters: {
654
+ type: 'object',
655
+ properties: {
656
+ param1: { type: 'string' },
657
+ param2: { type: 'number' },
658
+ },
659
+ required: ['param1'],
660
+ },
661
+ },
662
+ type: 'function',
663
+ },
664
+ ];
665
+
666
+ const payload: ChatStreamPayload = {
667
+ messages: [
668
+ {
669
+ role: 'user',
670
+ content: '',
671
+ tool_calls: [
672
+ { function: { name: 'some_func', arguments: '' }, id: 'func_1', type: 'function' },
673
+ ],
674
+ },
675
+ ],
676
+ model: 'gemini-2.5-flash-preview-04-17',
677
+ temperature: 1,
678
+ };
679
+
680
+ const googleTools = instance['buildGoogleTools'](tools, payload);
681
+
682
+ expect(googleTools).toHaveLength(1);
683
+ expect((googleTools![0] as Tool).functionDeclarations![0]).toEqual({
684
+ name: 'testTool',
685
+ description: 'A test tool',
686
+ parameters: {
687
+ type: 'OBJECT',
688
+ properties: {
689
+ param1: { type: 'string' },
690
+ param2: { type: 'number' },
691
+ },
692
+ required: ['param1'],
693
+ },
694
+ });
695
+ });
696
+
697
+ it('should handle googleSearch', () => {
698
+ const payload: ChatStreamPayload = {
699
+ messages: [
700
+ {
701
+ role: 'user',
702
+ content: '',
703
+ },
704
+ ],
705
+ model: 'gemini-2.5-flash-preview-04-17',
706
+ temperature: 1,
707
+ enabledSearch: true,
708
+ };
709
+
710
+ const googleTools = instance['buildGoogleTools'](undefined, payload);
711
+
712
+ expect(googleTools).toHaveLength(1);
713
+ expect(googleTools![0] as Tool).toEqual({ googleSearch: {} });
714
+ });
658
715
  });
659
716
 
660
717
  describe('convertOAIMessagesToGoogleMessage', () => {