@lobehub/chat 1.94.4 → 1.94.5

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,39 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ### [Version 1.94.5](https://github.com/lobehub/lobe-chat/compare/v1.94.4...v1.94.5)
6
+
7
+ <sup>Released on **2025-06-12**</sup>
8
+
9
+ #### 🐛 Bug Fixes
10
+
11
+ - **chat**: Improve response animation merging logic.
12
+
13
+ #### 💄 Styles
14
+
15
+ - **misc**: Support `web_search_preview` & fix some bug form OpenAI Response API.
16
+
17
+ <br/>
18
+
19
+ <details>
20
+ <summary><kbd>Improvements and Fixes</kbd></summary>
21
+
22
+ #### What's fixed
23
+
24
+ - **chat**: Improve response animation merging logic, closes [#8160](https://github.com/lobehub/lobe-chat/issues/8160) ([9d81cdc](https://github.com/lobehub/lobe-chat/commit/9d81cdc))
25
+
26
+ #### Styles
27
+
28
+ - **misc**: Support `web_search_preview` & fix some bug form OpenAI Response API, closes [#8131](https://github.com/lobehub/lobe-chat/issues/8131) ([b2983f0](https://github.com/lobehub/lobe-chat/commit/b2983f0))
29
+
30
+ </details>
31
+
32
+ <div align="right">
33
+
34
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
35
+
36
+ </div>
37
+
5
38
  ### [Version 1.94.4](https://github.com/lobehub/lobe-chat/compare/v1.94.3...v1.94.4)
6
39
 
7
40
  <sup>Released on **2025-06-11**</sup>
package/README.md CHANGED
@@ -335,7 +335,7 @@ In addition, these plugins are not limited to news aggregation, but can also ext
335
335
  | [Bing_websearch](https://lobechat.com/discover/plugin/Bingsearch-identifier)<br/><sup>By **FineHow** on **2024-12-22**</sup> | Search for information from the internet base BingApi<br/>`bingsearch` |
336
336
  | [Google CSE](https://lobechat.com/discover/plugin/google-cse)<br/><sup>By **vsnthdev** on **2024-12-02**</sup> | Searches Google through their official CSE API.<br/>`web` `search` |
337
337
 
338
- > 📊 Total plugins: [<kbd>**43**</kbd>](https://lobechat.com/discover/plugins)
338
+ > 📊 Total plugins: [<kbd>**42**</kbd>](https://lobechat.com/discover/plugins)
339
339
 
340
340
  <!-- PLUGIN LIST -->
341
341
 
package/README.zh-CN.md CHANGED
@@ -328,7 +328,7 @@ LobeChat 的插件生态系统是其核心功能的重要扩展,它极大地
328
328
  | [必应网页搜索](https://lobechat.com/discover/plugin/Bingsearch-identifier)<br/><sup>By **FineHow** on **2024-12-22**</sup> | 通过 BingApi 搜索互联网上的信息<br/>`bingsearch` |
329
329
  | [谷歌自定义搜索引擎](https://lobechat.com/discover/plugin/google-cse)<br/><sup>By **vsnthdev** on **2024-12-02**</sup> | 通过他们的官方自定义搜索引擎 API 搜索谷歌。<br/>`网络` `搜索` |
330
330
 
331
- > 📊 Total plugins: [<kbd>**43**</kbd>](https://lobechat.com/discover/plugins)
331
+ > 📊 Total plugins: [<kbd>**42**</kbd>](https://lobechat.com/discover/plugins)
332
332
 
333
333
  <!-- PLUGIN LIST -->
334
334
 
package/changelog/v1.json CHANGED
@@ -1,4 +1,13 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "improvements": [
5
+ "Support web_search_preview & fix some bug form OpenAI Response API."
6
+ ]
7
+ },
8
+ "date": "2025-06-12",
9
+ "version": "1.94.5"
10
+ },
2
11
  {
3
12
  "children": {
4
13
  "improvements": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "1.94.4",
3
+ "version": "1.94.5",
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",
@@ -54,7 +54,7 @@
54
54
  "dev:desktop": "next dev --turbopack -p 3015",
55
55
  "docs:i18n": "lobe-i18n md && npm run lint:md && npm run lint:mdx && prettier -c --write locales/**/*",
56
56
  "docs:seo": "lobe-seo && npm run lint:mdx",
57
- "i18n": "npm run workflow:i18n && lobe-i18n",
57
+ "i18n": "npm run workflow:i18n && lobe-i18n && prettier -c --write \"locales/**\"",
58
58
  "lint": "npm run lint:ts && npm run lint:style && npm run type-check && npm run lint:circular",
59
59
  "lint:circular": "dpdm src/**/*.ts --no-warning --no-tree --exit-code circular:1 --no-progress -T true --skip-dynamic-imports circular",
60
60
  "lint:md": "remark . --silent --output",
@@ -282,6 +282,7 @@
282
282
  "@next/bundle-analyzer": "^15.3.3",
283
283
  "@next/eslint-plugin-next": "^15.3.3",
284
284
  "@peculiar/webcrypto": "^1.5.0",
285
+ "@prettier/sync": "^0.6.1",
285
286
  "@semantic-release/exec": "^6.0.3",
286
287
  "@testing-library/jest-dom": "^6.6.3",
287
288
  "@testing-library/react": "^16.3.0",
@@ -2,7 +2,7 @@ import { consola } from 'consola';
2
2
  import { colors } from 'consola/utils';
3
3
 
4
4
  import { entryLocaleJsonFilepath, i18nConfig, srcDefaultLocales } from './const';
5
- import { tagWhite, writeJSON } from './utils';
5
+ import { tagWhite, writeJSONWithPrettier } from './utils';
6
6
 
7
7
  export const genDefaultLocale = () => {
8
8
  consola.info(`Default locale is ${i18nConfig.entryLocale}...`);
@@ -13,7 +13,7 @@ export const genDefaultLocale = () => {
13
13
 
14
14
  for (const [ns, value] of data) {
15
15
  const filepath = entryLocaleJsonFilepath(`${ns}.json`);
16
- writeJSON(filepath, value);
16
+ writeJSONWithPrettier(filepath, value);
17
17
  consola.success(tagWhite(ns), colors.gray(filepath));
18
18
  }
19
19
  };
@@ -10,10 +10,10 @@ import {
10
10
  outputLocaleJsonFilepath,
11
11
  srcDefaultLocales,
12
12
  } from './const';
13
- import { readJSON, tagWhite, writeJSON } from './utils';
13
+ import { readJSON, tagWhite, writeJSONWithPrettier } from './utils';
14
14
 
15
15
  export const genDiff = () => {
16
- consola.start(`Diff between Dev/Prod local...`);
16
+ consola.start(`Remove diff analysis...`);
17
17
 
18
18
  const resources = require(srcDefaultLocales);
19
19
  const data = Object.entries(resources.default);
@@ -21,27 +21,26 @@ export const genDiff = () => {
21
21
  for (const [ns, devJSON] of data) {
22
22
  const filepath = entryLocaleJsonFilepath(`${ns}.json`);
23
23
  if (!existsSync(filepath)) continue;
24
- const prodJSON = readJSON(filepath);
24
+ const previousProdJSON = readJSON(filepath);
25
25
 
26
- const diffResult = diff(prodJSON, devJSON as any);
27
- const remove = diffResult.filter((item) => item.op === 'remove');
28
- if (remove.length === 0) {
26
+ const diffResult = diff(previousProdJSON, devJSON as any);
27
+ if (diffResult.length === 0) {
29
28
  consola.success(tagWhite(ns), colors.gray(filepath));
30
29
  continue;
31
30
  }
32
31
 
33
32
  const clearLocals = [];
34
33
 
35
- for (const locale of [i18nConfig.entryLocale, ...i18nConfig.outputLocales]) {
34
+ for (const locale of i18nConfig.outputLocales) {
36
35
  const localeFilepath = outputLocaleJsonFilepath(locale, `${ns}.json`);
37
36
  if (!existsSync(localeFilepath)) continue;
38
37
  const localeJSON = readJSON(localeFilepath);
39
38
 
40
- for (const item of remove) {
39
+ for (const item of diffResult) {
41
40
  unset(localeJSON, item.path);
42
41
  }
43
42
 
44
- writeJSON(localeFilepath, localeJSON);
43
+ writeJSONWithPrettier(localeFilepath, localeJSON);
45
44
  clearLocals.push(locale);
46
45
  }
47
46
  consola.info('clear', clearLocals);
@@ -2,9 +2,13 @@ import { consola } from 'consola';
2
2
  import { colors } from 'consola/utils';
3
3
  import { readFileSync, writeFileSync } from 'node:fs';
4
4
  import { resolve } from 'node:path';
5
-
5
+ import prettier from "@prettier/sync";
6
6
  import i18nConfig from '../../.i18nrc';
7
7
 
8
+ let prettierOptions = prettier.resolveConfig(
9
+ resolve(__dirname, '../../.prettierrc.js')
10
+ );
11
+
8
12
  export const readJSON = (filePath: string) => {
9
13
  const data = readFileSync(filePath, 'utf8');
10
14
  return JSON.parse(data);
@@ -15,6 +19,15 @@ export const writeJSON = (filePath: string, data: any) => {
15
19
  writeFileSync(filePath, jsonStr, 'utf8');
16
20
  };
17
21
 
22
+ export const writeJSONWithPrettier = (filePath: string, data: any) => {
23
+ const jsonStr = JSON.stringify(data, null, 2);
24
+ const formatted = prettier.format(jsonStr, {
25
+ ...prettierOptions,
26
+ parser: 'json',
27
+ });
28
+ writeFileSync(filePath, formatted, 'utf8');
29
+ };
30
+
18
31
  export const genResourcesContent = (locales: string[]) => {
19
32
  let index = '';
20
33
  let indexObj = '';
@@ -59,6 +59,7 @@ export const openaiChatModels: AIChatModelCard[] = [
59
59
  {
60
60
  abilities: {
61
61
  functionCall: true,
62
+ search: true,
62
63
  vision: true,
63
64
  },
64
65
  contextWindowTokens: 1_047_576,
@@ -73,11 +74,15 @@ export const openaiChatModels: AIChatModelCard[] = [
73
74
  output: 8,
74
75
  },
75
76
  releasedAt: '2025-04-14',
77
+ settings: {
78
+ searchImpl: 'params',
79
+ },
76
80
  type: 'chat',
77
81
  },
78
82
  {
79
83
  abilities: {
80
84
  functionCall: true,
85
+ search: true,
81
86
  vision: true,
82
87
  },
83
88
  contextWindowTokens: 1_047_576,
@@ -93,6 +98,9 @@ export const openaiChatModels: AIChatModelCard[] = [
93
98
  output: 1.6,
94
99
  },
95
100
  releasedAt: '2025-04-14',
101
+ settings: {
102
+ searchImpl: 'params',
103
+ },
96
104
  type: 'chat',
97
105
  },
98
106
  {
@@ -135,6 +143,28 @@ export const openaiChatModels: AIChatModelCard[] = [
135
143
  },
136
144
  type: 'chat',
137
145
  },
146
+ {
147
+ abilities: {
148
+ functionCall: true,
149
+ reasoning: true,
150
+ vision: true,
151
+ },
152
+ contextWindowTokens: 200_000,
153
+ description:
154
+ 'o1 系列模型经过强化学习训练,能够在回答前进行思考,并执行复杂的推理任务。o1-pro 模型使用了更多计算资源,以进行更深入的思考,从而持续提供更优质的回答。',
155
+ displayName: 'o1-pro',
156
+ id: 'o1-pro',
157
+ maxOutput: 100_000,
158
+ pricing: {
159
+ input: 150,
160
+ output: 600,
161
+ },
162
+ releasedAt: '2025-03-19',
163
+ settings: {
164
+ extendParams: ['reasoningEffort'],
165
+ },
166
+ type: 'chat',
167
+ },
138
168
  {
139
169
  abilities: {
140
170
  reasoning: true,
@@ -158,6 +188,7 @@ export const openaiChatModels: AIChatModelCard[] = [
158
188
  },
159
189
  {
160
190
  abilities: {
191
+ functionCall: true,
161
192
  reasoning: true,
162
193
  vision: true,
163
194
  },
@@ -220,6 +251,7 @@ export const openaiChatModels: AIChatModelCard[] = [
220
251
  {
221
252
  abilities: {
222
253
  functionCall: true,
254
+ search: true,
223
255
  vision: true,
224
256
  },
225
257
  contextWindowTokens: 128_000,
@@ -234,6 +266,9 @@ export const openaiChatModels: AIChatModelCard[] = [
234
266
  output: 0.6,
235
267
  },
236
268
  releasedAt: '2024-07-18',
269
+ settings: {
270
+ searchImpl: 'params',
271
+ },
237
272
  type: 'chat',
238
273
  },
239
274
  {
@@ -259,6 +294,29 @@ export const openaiChatModels: AIChatModelCard[] = [
259
294
  {
260
295
  abilities: {
261
296
  functionCall: true,
297
+ //search: true,
298
+ },
299
+ contextWindowTokens: 128_000,
300
+ description: 'GPT-4o mini Audio 模型,支持音频输入输出',
301
+ displayName: 'GPT-4o mini Audio',
302
+ id: 'gpt-4o-mini-audio-preview',
303
+ maxOutput: 16_384,
304
+ pricing: {
305
+ input: 0.15,
306
+ output: 0.6,
307
+ },
308
+ releasedAt: '2024-12-17',
309
+ /*
310
+ settings: {
311
+ searchImpl: 'params',
312
+ },
313
+ */
314
+ type: 'chat',
315
+ },
316
+ {
317
+ abilities: {
318
+ functionCall: true,
319
+ search: true,
262
320
  vision: true,
263
321
  },
264
322
  contextWindowTokens: 128_000,
@@ -272,6 +330,9 @@ export const openaiChatModels: AIChatModelCard[] = [
272
330
  output: 10,
273
331
  },
274
332
  releasedAt: '2024-05-13',
333
+ settings: {
334
+ searchImpl: 'params',
335
+ },
275
336
  type: 'chat',
276
337
  },
277
338
  {
@@ -297,6 +358,7 @@ export const openaiChatModels: AIChatModelCard[] = [
297
358
  {
298
359
  abilities: {
299
360
  functionCall: true,
361
+ search: true,
300
362
  vision: true,
301
363
  },
302
364
  contextWindowTokens: 128_000,
@@ -310,11 +372,15 @@ export const openaiChatModels: AIChatModelCard[] = [
310
372
  output: 10,
311
373
  },
312
374
  releasedAt: '2024-11-20',
375
+ settings: {
376
+ searchImpl: 'params',
377
+ },
313
378
  type: 'chat',
314
379
  },
315
380
  {
316
381
  abilities: {
317
382
  functionCall: true,
383
+ search: true,
318
384
  vision: true,
319
385
  },
320
386
  contextWindowTokens: 128_000,
@@ -327,9 +393,16 @@ export const openaiChatModels: AIChatModelCard[] = [
327
393
  output: 15,
328
394
  },
329
395
  releasedAt: '2024-05-13',
396
+ settings: {
397
+ searchImpl: 'params',
398
+ },
330
399
  type: 'chat',
331
400
  },
332
401
  {
402
+ abilities: {
403
+ functionCall: true,
404
+ //search: true,
405
+ },
333
406
  contextWindowTokens: 128_000,
334
407
  description: 'GPT-4o Audio 模型,支持音频输入输出',
335
408
  displayName: 'GPT-4o Audio',
@@ -340,6 +413,11 @@ export const openaiChatModels: AIChatModelCard[] = [
340
413
  output: 10,
341
414
  },
342
415
  releasedAt: '2024-10-01',
416
+ /*
417
+ settings: {
418
+ searchImpl: 'params',
419
+ },
420
+ */
343
421
  type: 'chat',
344
422
  },
345
423
  {
@@ -545,6 +623,48 @@ export const openaiChatModels: AIChatModelCard[] = [
545
623
  },
546
624
  type: 'chat',
547
625
  },
626
+ {
627
+ abilities: {
628
+ functionCall: true,
629
+ reasoning: true,
630
+ vision: true,
631
+ },
632
+ contextWindowTokens: 200_000,
633
+ description: 'codex-mini-latest 是 o4-mini 的微调版本,专门用于 Codex CLI。对于直接通过 API 使用,我们推荐从 gpt-4.1 开始。',
634
+ displayName: 'Codex mini',
635
+ id: 'codex-mini-latest',
636
+ maxOutput: 100_000,
637
+ pricing: {
638
+ input: 1.5,
639
+ output: 6,
640
+ },
641
+ releasedAt: '2025-06-01',
642
+ settings: {
643
+ extendParams: ['reasoningEffort'],
644
+ },
645
+ type: 'chat',
646
+ },
647
+ {
648
+ abilities: {
649
+ functionCall: true,
650
+ reasoning: true,
651
+ vision: true,
652
+ },
653
+ contextWindowTokens: 8192,
654
+ description: 'computer-use-preview 模型是专为“计算机使用工具”设计的专用模型,经过训练以理解并执行计算机相关任务。',
655
+ displayName: 'Computer Use Preview',
656
+ id: 'computer-use-preview',
657
+ maxOutput: 1024,
658
+ pricing: {
659
+ input: 3,
660
+ output: 12,
661
+ },
662
+ releasedAt: '2025-03-11',
663
+ settings: {
664
+ extendParams: ['reasoningEffort'],
665
+ },
666
+ type: 'chat',
667
+ },
548
668
  ];
549
669
 
550
670
  export const openaiEmbeddingModels: AIEmbeddingModelCard[] = [
@@ -6,7 +6,32 @@ export const systemToUserModels = new Set([
6
6
  ]);
7
7
 
8
8
  // TODO: 临时写法,后续要重构成 model card 展示配置
9
- export const disableStreamModels = new Set(['o1', 'o1-2024-12-17']);
9
+ export const disableStreamModels = new Set([
10
+ 'o1',
11
+ 'o1-2024-12-17',
12
+ 'o1-pro',
13
+ 'o1-pro-2025-03-19',
14
+ /*
15
+ 官网显示不支持,但是实际试下来支持 Streaming,暂时注释掉
16
+ 'o3-pro',
17
+ 'o3-pro-2025-06-10',
18
+ */
19
+ 'computer-use-preview',
20
+ 'computer-use-preview-2025-03-11',
21
+ ]);
22
+
23
+ /**
24
+ * models use Responses API only
25
+ */
26
+ export const responsesAPIModels = new Set([
27
+ 'o1-pro',
28
+ 'o1-pro-2025-03-19',
29
+ 'o3-pro',
30
+ 'o3-pro-2025-06-10',
31
+ 'codex-mini-latest',
32
+ 'computer-use-preview',
33
+ 'computer-use-preview-2025-03-11',
34
+ ]);
10
35
 
11
36
  /**
12
37
  * models support context caching
@@ -2,21 +2,24 @@ import { ChatStreamPayload, ModelProvider } from '../types';
2
2
  import { processMultiProviderModelList } from '../utils/modelParse';
3
3
  import { createOpenAICompatibleRuntime } from '../utils/openaiCompatibleFactory';
4
4
  import { pruneReasoningPayload } from '../utils/openaiHelpers';
5
+ import { responsesAPIModels } from '@/const/models';
5
6
 
6
7
  export interface OpenAIModelCard {
7
8
  id: string;
8
9
  }
9
10
 
10
- const prunePrefixes = ['o1', 'o3', 'o4'];
11
+ const prunePrefixes = ['o1', 'o3', 'o4', 'codex', 'computer-use'];
12
+
13
+ const oaiSearchContextSize = process.env.OPENAI_SEARCH_CONTEXT_SIZE; // low, medium, high
11
14
 
12
15
  export const LobeOpenAI = createOpenAICompatibleRuntime({
13
16
  baseURL: 'https://api.openai.com/v1',
14
17
  chatCompletion: {
15
18
  handlePayload: (payload) => {
16
- const { model } = payload;
19
+ const { enabledSearch, model, ...rest } = payload;
17
20
 
18
- if (model === 'o1-pro') {
19
- return { ...payload, apiMode: 'responses' } as ChatStreamPayload;
21
+ if (responsesAPIModels.has(model) || enabledSearch) {
22
+ return { ...rest, apiMode: 'responses', enabledSearch, model } as ChatStreamPayload;
20
23
  }
21
24
 
22
25
  if (prunePrefixes.some((prefix) => model.startsWith(prefix))) {
@@ -24,11 +27,10 @@ export const LobeOpenAI = createOpenAICompatibleRuntime({
24
27
  }
25
28
 
26
29
  if (model.includes('-search-')) {
27
- const oaiSearchContextSize = process.env.OPENAI_SEARCH_CONTEXT_SIZE; // low, medium, high
28
-
29
30
  return {
30
- ...payload,
31
+ ...rest,
31
32
  frequency_penalty: undefined,
33
+ model,
32
34
  presence_penalty: undefined,
33
35
  stream: payload.stream ?? true,
34
36
  temperature: undefined,
@@ -41,7 +43,7 @@ export const LobeOpenAI = createOpenAICompatibleRuntime({
41
43
  } as any;
42
44
  }
43
45
 
44
- return { ...payload, stream: payload.stream ?? true };
46
+ return { ...rest, model, stream: payload.stream ?? true };
45
47
  },
46
48
  },
47
49
  debug: {
@@ -57,17 +59,37 @@ export const LobeOpenAI = createOpenAICompatibleRuntime({
57
59
  },
58
60
  provider: ModelProvider.OpenAI,
59
61
  responses: {
60
- handlePayload: (payload: ChatStreamPayload) => {
61
- const { model } = payload;
62
+ handlePayload: (payload) => {
63
+ const { enabledSearch, model, tools, ...rest } = payload;
64
+
65
+ const openaiTools = enabledSearch
66
+ ? [
67
+ ...(tools || []),
68
+ {
69
+ type: 'web_search_preview',
70
+ ...(oaiSearchContextSize && {
71
+ search_context_size: oaiSearchContextSize,
72
+ }),
73
+ },
74
+ ]
75
+ : tools;
76
+
62
77
  if (prunePrefixes.some((prefix) => model.startsWith(prefix))) {
63
78
  if (!payload.reasoning) {
64
79
  payload.reasoning = { summary: 'auto' };
65
80
  } else {
66
81
  payload.reasoning.summary = 'auto';
67
82
  }
83
+
84
+ // computer-use series must set truncation as auto
85
+ if (model.startsWith('computer-use')) {
86
+ payload.truncation = 'auto';
87
+ }
88
+
89
+ return pruneReasoningPayload(payload) as any;
68
90
  }
69
91
 
70
- return { ...payload, stream: payload.stream ?? true };
92
+ return { ...rest, model, stream: payload.stream ?? true, tools: openaiTools } as any;
71
93
  },
72
94
  },
73
95
  });
@@ -107,6 +107,7 @@ export interface ChatStreamPayload {
107
107
  effort?: string;
108
108
  summary?: string;
109
109
  };
110
+ reasoning_effort?: 'low' | 'medium' | 'high';
110
111
  responseMode?: 'stream' | 'json';
111
112
  /**
112
113
  * @title 是否开启流式请求
@@ -132,6 +133,7 @@ export interface ChatStreamPayload {
132
133
  * @default 1
133
134
  */
134
135
  top_p?: number;
136
+ truncation?: 'auto' | 'disabled';
135
137
  }
136
138
 
137
139
  export interface ChatMethodOptions {
@@ -209,14 +209,9 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
209
209
  }
210
210
 
211
211
  async chat(
212
- { responseMode, apiMode, ...payload }: ChatStreamPayload,
212
+ { responseMode, ...payload }: ChatStreamPayload,
213
213
  options?: ChatMethodOptions,
214
214
  ) {
215
- // new openai Response API
216
- if (apiMode === 'responses') {
217
- return this.handleResponseAPIMode(payload, options);
218
- }
219
-
220
215
  try {
221
216
  const inputStartAt = Date.now();
222
217
  const postPayload = chatCompletion?.handlePayload
@@ -226,6 +221,11 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
226
221
  stream: payload.stream ?? true,
227
222
  } as OpenAI.ChatCompletionCreateParamsStreaming);
228
223
 
224
+ // new openai Response API
225
+ if ((postPayload as any).apiMode === 'responses') {
226
+ return this.handleResponseAPIMode(payload, options);
227
+ }
228
+
229
229
  const messages = await convertOpenAIMessages(postPayload.messages);
230
230
 
231
231
  let response: Stream<OpenAI.Chat.Completions.ChatCompletionChunk>;
@@ -478,11 +478,12 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
478
478
  ): Promise<Response> {
479
479
  const inputStartAt = Date.now();
480
480
 
481
- const { messages, ...res } = responses?.handlePayload
481
+ const { messages, reasoning_effort, tools, ...res } = responses?.handlePayload
482
482
  ? (responses?.handlePayload(payload, this._options) as ChatStreamPayload)
483
483
  : payload;
484
484
 
485
485
  // remove penalty params
486
+ delete res.apiMode;
486
487
  delete res.frequency_penalty;
487
488
  delete res.presence_penalty;
488
489
 
@@ -490,9 +491,10 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
490
491
 
491
492
  const postPayload = {
492
493
  ...res,
494
+ ...(reasoning_effort ? { reasoning: { effort: reasoning_effort } } : {}),
493
495
  input,
494
496
  store: false,
495
- tools: payload.tools?.map((tool) => this.convertChatCompletionToolToResponseTool(tool)),
497
+ tools: tools?.map((tool) => this.convertChatCompletionToolToResponseTool(tool)),
496
498
  } as OpenAI.Responses.ResponseCreateParamsStreaming;
497
499
 
498
500
  if (debug?.responses?.()) {
@@ -86,11 +86,11 @@ exports[`OpenAIResponsesStream > Reasoning > summary 1`] = `
86
86
  "data: " analyzing"
87
87
 
88
88
  ",
89
- "id: resp_684313b89200819087f27686e0c822260b502bf083132d0d
89
+ "id: rs_684313b9774481908ee856625f82fb8c0b502bf083132d0d
90
90
  ",
91
- "event: data
91
+ "event: text
92
92
  ",
93
- "data: {"type":"response.output_item.done","output_index":0,"item":{"id":"rs_684313b9774481908ee856625f82fb8c0b502bf083132d0d","type":"reasoning","summary":[{"type":"summary_text","text":"**Answering a numeric comparison**\\n\\nThe user is asking in Chinese which number is larger: 9.1 or 9.92. This is straightforward since 9.92 is clearly larger, as it's greater than 9.1. We can respond with \\"9.92大于9.1\\" without needing to search for more information. It's simple comparison, but I could also add a little explanation, noting that 9.92 is indeed 0.82 more than 9.1. However, keeping it simple with \\"9.92 > 9.1\\" is perfectly fine!"}]}}
93
+ "data: null
94
94
 
95
95
  ",
96
96
  "id: resp_684313b89200819087f27686e0c822260b502bf083132d0d
@@ -128,11 +128,11 @@ exports[`OpenAIResponsesStream > Reasoning > summary 1`] = `
128
128
  "data: {"type":"response.content_part.done","item_id":"msg_684313bee2c88190b0f4b09621ad7dc60b502bf083132d0d","output_index":1,"content_index":0,"part":{"type":"output_text","annotations":[],"text":"9.92 比 9.1 大。"}}
129
129
 
130
130
  ",
131
- "id: resp_684313b89200819087f27686e0c822260b502bf083132d0d
131
+ "id: msg_684313bee2c88190b0f4b09621ad7dc60b502bf083132d0d
132
132
  ",
133
- "event: data
133
+ "event: text
134
134
  ",
135
- "data: {"type":"response.output_item.done","output_index":1,"item":{"id":"msg_684313bee2c88190b0f4b09621ad7dc60b502bf083132d0d","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"text":"9.92 比 9. 大。"}],"role":"assistant"}}
135
+ "data: null
136
136
 
137
137
  ",
138
138
  "id: resp_684313b89200819087f27686e0c822260b502bf083132d0d
@@ -1,7 +1,7 @@
1
1
  import OpenAI from 'openai';
2
2
  import type { Stream } from 'openai/streaming';
3
3
 
4
- import { ChatMessageError } from '@/types/message';
4
+ import { ChatMessageError, CitationItem } from '@/types/message';
5
5
 
6
6
  import { AgentRuntimeErrorType } from '../../../error';
7
7
  import { convertResponseUsage } from '../../usageConverter';
@@ -20,7 +20,17 @@ import {
20
20
  import { OpenAIStreamOptions } from './openai';
21
21
 
22
22
  const transformOpenAIStream = (
23
- chunk: OpenAI.Responses.ResponseStreamEvent,
23
+ chunk: OpenAI.Responses.ResponseStreamEvent | {
24
+ annotation: {
25
+ end_index: number;
26
+ start_index: number;
27
+ title: string;
28
+ type: 'url_citation';
29
+ url: string;
30
+ };
31
+ item_id: string;
32
+ type: 'response.output_text.annotation.added';
33
+ },
24
34
  streamContext: StreamContext,
25
35
  ): StreamProtocolChunk | StreamProtocolChunk[] => {
26
36
  // handle the first chunk error
@@ -42,6 +52,7 @@ const transformOpenAIStream = (
42
52
  switch (chunk.type) {
43
53
  case 'response.created': {
44
54
  streamContext.id = chunk.response.id;
55
+ streamContext.returnedCitationArray = [];
45
56
 
46
57
  return { data: chunk.response.status, id: streamContext.id, type: 'data' };
47
58
  }
@@ -106,6 +117,31 @@ const transformOpenAIStream = (
106
117
  return { data: chunk.delta, id: chunk.item_id, type: 'reasoning' };
107
118
  }
108
119
 
120
+ case 'response.output_text.annotation.added': {
121
+ const citations = chunk.annotation;
122
+
123
+ if (streamContext.returnedCitationArray) {
124
+ streamContext.returnedCitationArray.push({
125
+ title: citations.title,
126
+ url: citations.url,
127
+ } as CitationItem);
128
+ }
129
+
130
+ return { data: null, id: chunk.item_id, type: 'text' };
131
+ }
132
+
133
+ case 'response.output_item.done': {
134
+ if (streamContext.returnedCitationArray?.length) {
135
+ return {
136
+ data: { citations: streamContext.returnedCitationArray },
137
+ id: chunk.item.id,
138
+ type: 'grounding',
139
+ }
140
+ }
141
+
142
+ return { data: null, id: chunk.item.id, type: 'text' };
143
+ }
144
+
109
145
  case 'response.completed': {
110
146
  if (chunk.response.usage) {
111
147
  return {
@@ -388,6 +388,13 @@ class ChatService {
388
388
  const userPreferTransitionMode =
389
389
  userGeneralSettingsSelectors.transitionMode(getUserStoreState());
390
390
 
391
+ // The order of the array is very important.
392
+ const mergedResponseAnimation = [
393
+ providerConfig?.settings?.responseAnimation || {},
394
+ userPreferTransitionMode,
395
+ responseAnimation,
396
+ ].reduce((acc, cur) => merge(acc, standardizeAnimationStyle(cur)), {});
397
+
391
398
  return fetchSSE(API_ENDPOINTS.chat(sdkType), {
392
399
  body: JSON.stringify(payload),
393
400
  fetcher: fetcher,
@@ -397,10 +404,7 @@ class ChatService {
397
404
  onErrorHandle: options?.onErrorHandle,
398
405
  onFinish: options?.onFinish,
399
406
  onMessageHandle: options?.onMessageHandle,
400
- responseAnimation: [userPreferTransitionMode, responseAnimation].reduce(
401
- (acc, cur) => merge(acc, standardizeAnimationStyle(cur)),
402
- providerConfig?.settings?.responseAnimation ?? {},
403
- ),
407
+ responseAnimation: mergedResponseAnimation,
404
408
  signal,
405
409
  });
406
410
  };