@lobehub/lobehub 2.0.0-next.262 → 2.0.0-next.263

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 (36) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/changelog/v1.json +9 -0
  3. package/locales/zh-CN/chat.json +1 -0
  4. package/locales/zh-CN/modelProvider.json +20 -0
  5. package/package.json +1 -1
  6. package/packages/database/src/models/aiModel.ts +2 -0
  7. package/packages/database/src/repositories/aiInfra/index.test.ts +41 -1
  8. package/packages/database/src/repositories/aiInfra/index.ts +3 -1
  9. package/packages/model-runtime/src/providers/openrouter/index.test.ts +9 -55
  10. package/packages/model-runtime/src/providers/openrouter/index.ts +47 -27
  11. package/packages/model-runtime/src/providers/openrouter/type.ts +16 -28
  12. package/packages/model-runtime/src/providers/vercelaigateway/index.test.ts +6 -6
  13. package/packages/model-runtime/src/providers/vercelaigateway/index.ts +54 -11
  14. package/packages/model-runtime/src/utils/modelParse.test.ts +185 -3
  15. package/packages/model-runtime/src/utils/modelParse.ts +108 -1
  16. package/packages/types/src/llm.ts +3 -1
  17. package/src/app/[variants]/(main)/settings/provider/features/ModelList/CreateNewModelModal/ExtendParamsSelect.tsx +398 -0
  18. package/src/app/[variants]/(main)/settings/provider/features/ModelList/CreateNewModelModal/Form.tsx +11 -2
  19. package/src/app/[variants]/(main)/settings/provider/features/ModelList/CreateNewModelModal/__tests__/ExtendParamsSelect.test.tsx +59 -0
  20. package/src/features/ChatInput/ActionBar/Model/ControlsForm.tsx +1 -1
  21. package/src/features/ChatInput/ActionBar/Model/GPT51ReasoningEffortSlider.tsx +9 -54
  22. package/src/features/ChatInput/ActionBar/Model/GPT52ProReasoningEffortSlider.tsx +9 -53
  23. package/src/features/ChatInput/ActionBar/Model/GPT52ReasoningEffortSlider.tsx +9 -55
  24. package/src/features/ChatInput/ActionBar/Model/GPT5ReasoningEffortSlider.tsx +9 -54
  25. package/src/features/ChatInput/ActionBar/Model/ImageAspectRatioSelect.tsx +50 -16
  26. package/src/features/ChatInput/ActionBar/Model/ImageResolutionSlider.tsx +7 -53
  27. package/src/features/ChatInput/ActionBar/Model/LevelSlider.tsx +92 -0
  28. package/src/features/ChatInput/ActionBar/Model/ReasoningEffortSlider.tsx +9 -53
  29. package/src/features/ChatInput/ActionBar/Model/TextVerbositySlider.tsx +9 -53
  30. package/src/features/ChatInput/ActionBar/Model/ThinkingLevel2Slider.tsx +9 -52
  31. package/src/features/ChatInput/ActionBar/Model/ThinkingLevelSlider.tsx +9 -54
  32. package/src/features/ChatInput/ActionBar/Model/ThinkingSlider.tsx +20 -56
  33. package/src/features/ChatInput/ActionBar/Model/__tests__/createLevelSlider.test.tsx +126 -0
  34. package/src/features/ChatInput/ActionBar/Model/createLevelSlider.tsx +105 -0
  35. package/src/locales/default/chat.ts +1 -0
  36. package/src/locales/default/modelProvider.ts +38 -0
package/CHANGELOG.md CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 2.0.0-next.263](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.262...v2.0.0-next.263)
6
+
7
+ <sup>Released on **2026-01-11**</sup>
8
+
9
+ #### 💄 Styles
10
+
11
+ - **misc**: ModelParse support to get model settings params & Customize extendParams UI.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### Styles
19
+
20
+ - **misc**: ModelParse support to get model settings params & Customize extendParams UI, closes [#11185](https://github.com/lobehub/lobe-chat/issues/11185) ([94e985a](https://github.com/lobehub/lobe-chat/commit/94e985a))
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 2.0.0-next.262](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.261...v2.0.0-next.262)
6
31
 
7
32
  <sup>Released on **2026-01-11**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,13 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "improvements": [
5
+ "ModelParse support to get model settings params & Customize extendParams UI."
6
+ ]
7
+ },
8
+ "date": "2026-01-11",
9
+ "version": "2.0.0-next.263"
10
+ },
2
11
  {
3
12
  "children": {
4
13
  "improvements": [
@@ -63,6 +63,7 @@
63
63
  "extendParams.reasoningEffort.title": "推理强度",
64
64
  "extendParams.textVerbosity.title": "输出详细程度",
65
65
  "extendParams.thinking.title": "深度思考开关",
66
+ "extendParams.thinkingBudget.title": "思考预算",
66
67
  "extendParams.thinkingLevel.title": "思考水平",
67
68
  "extendParams.title": "模型扩展功能",
68
69
  "extendParams.urlContext.desc": "开启后会自动解析网页链接,提取网页内容作为上下文",
@@ -194,6 +194,26 @@
194
194
  "providerModels.item.modelConfig.deployName.title": "模型部署名称",
195
195
  "providerModels.item.modelConfig.displayName.placeholder": "请输入模型的展示名称,例如 ChatGPT、GPT-4 等",
196
196
  "providerModels.item.modelConfig.displayName.title": "模型展示名称",
197
+ "providerModels.item.modelConfig.extendParams.extra": "根据模型支持的能力选择扩展参数,将鼠标悬停在选项上可预览配置。错误配置可能导致请求失败。",
198
+ "providerModels.item.modelConfig.extendParams.options.disableContextCaching.hint": "适用于 Claude 系列,可降低成本并加快响应速度。",
199
+ "providerModels.item.modelConfig.extendParams.options.enableReasoning.hint": "适用于 Claude、DeepSeek 等推理模型,开启深度推理能力。",
200
+ "providerModels.item.modelConfig.extendParams.options.gpt5ReasoningEffort.hint": "适用于 GPT-5 系列,控制推理力度。",
201
+ "providerModels.item.modelConfig.extendParams.options.gpt5_1ReasoningEffort.hint": "适用于 GPT-5.1 系列,控制推理力度。",
202
+ "providerModels.item.modelConfig.extendParams.options.gpt5_2ProReasoningEffort.hint": "适用于 GPT-5.2 Pro 系列,控制推理力度。",
203
+ "providerModels.item.modelConfig.extendParams.options.gpt5_2ReasoningEffort.hint": "适用于 GPT-5.2 系列,控制推理力度。",
204
+ "providerModels.item.modelConfig.extendParams.options.imageAspectRatio.hint": "适用于 Gemini 生图模型,控制生成图像的纵横比。",
205
+ "providerModels.item.modelConfig.extendParams.options.imageResolution.hint": "适用于 Gemini 3 生图模型,控制生成图像的分辨率。",
206
+ "providerModels.item.modelConfig.extendParams.options.reasoningBudgetToken.hint": "适用于 Claude、Qwen3 等模型,控制推理 token 预算。",
207
+ "providerModels.item.modelConfig.extendParams.options.reasoningEffort.hint": "适用于 OpenAI 等支持推理的模型,控制推理强度。",
208
+ "providerModels.item.modelConfig.extendParams.options.textVerbosity.hint": "适用于 GPT-5 系列,控制输出详尽程度。",
209
+ "providerModels.item.modelConfig.extendParams.options.thinking.hint": "适用于部分豆包系列,允许模型自行决定是否思考。",
210
+ "providerModels.item.modelConfig.extendParams.options.thinkingBudget.hint": "适用于 Gemini 系列,控制思考预算。",
211
+ "providerModels.item.modelConfig.extendParams.options.thinkingLevel.hint": "适用于 Gemini 3 Flash Preview 模型,控制思考深度。",
212
+ "providerModels.item.modelConfig.extendParams.options.thinkingLevel2.hint": "适用于 Gemini 3 Pro Preview 模型,控制思考深度。",
213
+ "providerModels.item.modelConfig.extendParams.options.urlContext.hint": "适用于 Gemini 系列,支持 URL 上下文输入。",
214
+ "providerModels.item.modelConfig.extendParams.placeholder": "选择需要启用的扩展参数",
215
+ "providerModels.item.modelConfig.extendParams.previewFallback": "暂不支持预览",
216
+ "providerModels.item.modelConfig.extendParams.title": "扩展功能参数",
197
217
  "providerModels.item.modelConfig.files.extra": "当前文件上传实现仅为一种 Hack 方案,仅限自行尝试。完整文件上传能力请等待后续实现",
198
218
  "providerModels.item.modelConfig.files.title": "支持文件上传",
199
219
  "providerModels.item.modelConfig.functionCall.extra": "此配置将仅开启模型使用技能的能力,进而可以为模型添加技能。但是否支持真正使用技能完全取决于模型本身,请自行测试的可用性",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/lobehub",
3
- "version": "2.0.0-next.262",
3
+ "version": "2.0.0-next.263",
4
4
  "description": "LobeHub - an open-source,comprehensive AI Agent 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",
@@ -78,6 +78,7 @@ export class AiModelModel {
78
78
  parameters: aiModels.parameters,
79
79
  pricing: aiModels.pricing,
80
80
  releasedAt: aiModels.releasedAt,
81
+ settings: aiModels.settings,
81
82
  source: aiModels.source,
82
83
  type: aiModels.type,
83
84
  })
@@ -105,6 +106,7 @@ export class AiModelModel {
105
106
  parameters: aiModels.parameters,
106
107
  providerId: aiModels.providerId,
107
108
  releasedAt: aiModels.releasedAt,
109
+ settings: aiModels.settings,
108
110
  sort: aiModels.sort,
109
111
  source: aiModels.source,
110
112
  type: aiModels.type,
@@ -4,7 +4,7 @@ import type {
4
4
  AiProviderRuntimeConfig,
5
5
  EnabledProvider,
6
6
  } from '@lobechat/types';
7
- import { AiProviderModelListItem, EnabledAiModel } from 'model-bank';
7
+ import { AiProviderModelListItem, EnabledAiModel, ExtendParamsType } from 'model-bank';
8
8
  import { DEFAULT_MODEL_PROVIDER_LIST } from 'model-bank/modelProviders';
9
9
  import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
10
10
 
@@ -801,6 +801,46 @@ describe('AiInfraRepos', () => {
801
801
  expect(merged?.settings).toEqual({ searchImpl: 'tool', searchProvider: 'google' });
802
802
  });
803
803
 
804
+ it('should merge builtin settings with user-provided extend params', async () => {
805
+ const mockProviders = [
806
+ { enabled: true, id: 'openai', name: 'OpenAI', source: 'builtin' as const },
807
+ ];
808
+
809
+ const userModel: EnabledAiModel = {
810
+ abilities: {},
811
+ id: 'gpt-4',
812
+ providerId: 'openai',
813
+ enabled: true,
814
+ type: 'chat',
815
+ settings: { extendParams: ['reasoningEffort'] as ExtendParamsType[] },
816
+ };
817
+
818
+ const builtinModel = {
819
+ id: 'gpt-4',
820
+ enabled: true,
821
+ type: 'chat' as const,
822
+ settings: {
823
+ extendParams: ['thinking'] as ExtendParamsType[],
824
+ searchImpl: 'params',
825
+ searchProvider: 'builtin-provider',
826
+ },
827
+ };
828
+
829
+ vi.spyOn(repo, 'getAiProviderList').mockResolvedValue(mockProviders);
830
+ vi.spyOn(repo.aiModelModel, 'getAllModels').mockResolvedValue([userModel]);
831
+ vi.spyOn(repo as any, 'fetchBuiltinModels').mockResolvedValue([builtinModel]);
832
+
833
+ const result = await repo.getEnabledModels();
834
+
835
+ const merged = result.find((m) => m.id === 'gpt-4');
836
+ expect(merged).toBeDefined();
837
+ expect(merged?.settings).toEqual({
838
+ extendParams: ['reasoningEffort'],
839
+ searchImpl: 'params',
840
+ searchProvider: 'builtin-provider',
841
+ });
842
+ });
843
+
804
844
  it('should have no settings when both user and builtin have no settings', async () => {
805
845
  const mockProviders = [
806
846
  { enabled: true, id: 'openai', name: 'OpenAI', source: 'builtin' as const },
@@ -226,7 +226,9 @@ export class AiInfraRepos {
226
226
  enabled: typeof user.enabled === 'boolean' ? user.enabled : item.enabled,
227
227
  id: item.id,
228
228
  providerId: provider.id,
229
- settings: user.settings || item.settings,
229
+ settings: isEmpty(user.settings)
230
+ ? item.settings
231
+ : merge(item.settings || {}, user.settings || {}),
230
232
  sort: user.sort || undefined,
231
233
  type: user.type || item.type,
232
234
  };
@@ -162,14 +162,14 @@ describe('LobeOpenRouterAI - custom features', () => {
162
162
  );
163
163
  });
164
164
 
165
- it('should add empty reasoning object when thinking is not enabled', async () => {
165
+ it('should not add reasoning object when thinking is not enabled', async () => {
166
166
  await instance.chat({
167
167
  messages: [{ content: 'Hello', role: 'user' }],
168
168
  model: 'openai/gpt-4',
169
169
  });
170
170
 
171
171
  expect(instance['client'].chat.completions.create).toHaveBeenCalledWith(
172
- expect.objectContaining({ reasoning: {} }),
172
+ expect.not.objectContaining({ reasoning: expect.anything() }),
173
173
  expect.anything(),
174
174
  );
175
175
  });
@@ -204,22 +204,6 @@ describe('LobeOpenRouterAI - custom features', () => {
204
204
  );
205
205
  });
206
206
 
207
- it('should cap reasoning tokens to max_tokens - 1 when budget exceeds max_tokens', async () => {
208
- await instance.chat({
209
- messages: [{ content: 'Think about this', role: 'user' }],
210
- model: 'openai/gpt-4',
211
- max_tokens: 1000,
212
- thinking: { type: 'enabled', budget_tokens: 2000 },
213
- });
214
-
215
- expect(instance['client'].chat.completions.create).toHaveBeenCalledWith(
216
- expect.objectContaining({
217
- reasoning: { max_tokens: 999 }, // min(2000, 1000 - 1) = 999
218
- }),
219
- expect.anything(),
220
- );
221
- });
222
-
223
207
  it('should use model maxOutput when no max_tokens provided', async () => {
224
208
  // Mock OpenRouterModels to have a specific maxOutput
225
209
  const { openrouter } = await import('model-bank');
@@ -241,21 +225,6 @@ describe('LobeOpenRouterAI - custom features', () => {
241
225
  }
242
226
  });
243
227
 
244
- it('should use default 32000 when no max_tokens or model maxOutput available', async () => {
245
- await instance.chat({
246
- messages: [{ content: 'Think about this', role: 'user' }],
247
- model: 'unknown/model-without-config',
248
- thinking: { type: 'enabled', budget_tokens: 50000 },
249
- });
250
-
251
- expect(instance['client'].chat.completions.create).toHaveBeenCalledWith(
252
- expect.objectContaining({
253
- reasoning: { max_tokens: 31999 }, // min(50000, 32000 - 1) = 31999
254
- }),
255
- expect.anything(),
256
- );
257
- });
258
-
259
228
  it('should combine enabledSearch and thinking features', async () => {
260
229
  await instance.chat({
261
230
  messages: [{ content: 'Search and think', role: 'user' }],
@@ -302,12 +271,12 @@ describe('LobeOpenRouterAI - custom features', () => {
302
271
  });
303
272
 
304
273
  expect(instance['client'].chat.completions.create).toHaveBeenCalledWith(
305
- expect.objectContaining({ reasoning: {} }),
274
+ expect.objectContaining({ reasoning: { enabled: false } }),
306
275
  expect.anything(),
307
276
  );
308
277
  });
309
278
 
310
- it('should handle undefined thinking', async () => {
279
+ it('should not add reasoning when thinking is undefined', async () => {
311
280
  await instance.chat({
312
281
  messages: [{ content: 'Hello', role: 'user' }],
313
282
  model: 'openai/gpt-4',
@@ -315,23 +284,7 @@ describe('LobeOpenRouterAI - custom features', () => {
315
284
  });
316
285
 
317
286
  expect(instance['client'].chat.completions.create).toHaveBeenCalledWith(
318
- expect.objectContaining({ reasoning: {} }),
319
- expect.anything(),
320
- );
321
- });
322
-
323
- it('should cap reasoning tokens to 1 when max_tokens is 2', async () => {
324
- await instance.chat({
325
- messages: [{ content: 'Think about this', role: 'user' }],
326
- model: 'openai/gpt-4',
327
- max_tokens: 2,
328
- thinking: { type: 'enabled', budget_tokens: 2000 },
329
- });
330
-
331
- expect(instance['client'].chat.completions.create).toHaveBeenCalledWith(
332
- expect.objectContaining({
333
- reasoning: { max_tokens: 1 }, // min(2000, 2 - 1) = 1
334
- }),
287
+ expect.not.objectContaining({ reasoning: expect.anything() }),
335
288
  expect.anything(),
336
289
  );
337
290
  });
@@ -351,7 +304,7 @@ describe('LobeOpenRouterAI - custom features', () => {
351
304
  );
352
305
  });
353
306
 
354
- it('should handle 0 budget_tokens (falsy, falls back to 1024)', async () => {
307
+ it('should handle 0 budget_tokens (passes directly)', async () => {
355
308
  await instance.chat({
356
309
  messages: [{ content: 'Think about this', role: 'user' }],
357
310
  model: 'openai/gpt-4',
@@ -360,7 +313,7 @@ describe('LobeOpenRouterAI - custom features', () => {
360
313
 
361
314
  expect(instance['client'].chat.completions.create).toHaveBeenCalledWith(
362
315
  expect.objectContaining({
363
- reasoning: { max_tokens: 1024 }, // 0 is falsy, falls back to 1024
316
+ reasoning: { max_tokens: 0 }, // 0 is passed directly
364
317
  }),
365
318
  expect.anything(),
366
319
  );
@@ -1092,7 +1045,8 @@ describe('LobeOpenRouterAI - custom features', () => {
1092
1045
  const models = await params.models();
1093
1046
 
1094
1047
  const nullMaxOutputModel = models.find((m) => m.id === 'null-maxoutput/model');
1095
- expect(nullMaxOutputModel?.maxOutput).toBeUndefined();
1048
+ // When top_provider.max_completion_tokens is null, falls back to model.context_length
1049
+ expect(nullMaxOutputModel?.maxOutput).toBe(8192);
1096
1050
  });
1097
1051
 
1098
1052
  it('should format releasedAt from created timestamp', async () => {
@@ -1,4 +1,4 @@
1
- import { ModelProvider, openrouter as OpenRouterModels } from 'model-bank';
1
+ import { ModelProvider } from 'model-bank';
2
2
 
3
3
  import {
4
4
  OpenAICompatibleFactoryOptions,
@@ -16,34 +16,27 @@ export const params = {
16
16
  baseURL: 'https://openrouter.ai/api/v1',
17
17
  chatCompletion: {
18
18
  handlePayload: (payload) => {
19
- const { thinking, model, max_tokens } = payload;
20
-
21
- let reasoning: OpenRouterReasoning = {};
22
-
23
- if (thinking?.type === 'enabled') {
24
- const modelConfig = OpenRouterModels.find((m) => m.id === model);
25
- const defaultMaxOutput = modelConfig?.maxOutput;
26
-
27
- // 配置优先级:用户设置 > 模型配置 > 硬编码默认值
28
- const getMaxTokens = () => {
29
- if (max_tokens) return max_tokens;
30
- if (defaultMaxOutput) return defaultMaxOutput;
31
- return undefined;
32
- };
33
-
34
- const maxTokens = getMaxTokens() || 32_000; // Claude Opus 4 has minimum maxOutput
35
-
36
- reasoning = {
37
- max_tokens: thinking?.budget_tokens
38
- ? Math.min(thinking.budget_tokens, maxTokens - 1)
39
- : 1024,
40
- };
19
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
20
+ const { reasoning_effort, thinking, reasoning: _reasoning, ...rest } = payload;
21
+
22
+ let reasoning: OpenRouterReasoning | undefined;
23
+
24
+ if (thinking?.type || thinking?.budget_tokens !== undefined || reasoning_effort) {
25
+ if (thinking?.type === 'disabled') {
26
+ reasoning = { enabled: false };
27
+ } else if (thinking?.budget_tokens !== undefined) {
28
+ reasoning = {
29
+ max_tokens: thinking?.budget_tokens,
30
+ };
31
+ } else if (reasoning_effort) {
32
+ reasoning = { effort: reasoning_effort };
33
+ }
41
34
  }
42
35
 
43
36
  return {
44
- ...payload,
37
+ ...rest,
45
38
  model: payload.enabledSearch ? `${payload.model}:online` : payload.model,
46
- reasoning,
39
+ ...(reasoning && { reasoning }),
47
40
  stream: payload.stream ?? true,
48
41
  } as any;
49
42
  },
@@ -104,6 +97,8 @@ export const params = {
104
97
  displayName += ' (free)';
105
98
  }
106
99
 
100
+ const hasReasoning = supported_parameters.includes('reasoning');
101
+
107
102
  return {
108
103
  contextWindowTokens: top_provider.context_length || model.context_length,
109
104
  description: model.description,
@@ -113,16 +108,41 @@ export const params = {
113
108
  maxOutput:
114
109
  typeof top_provider.max_completion_tokens === 'number'
115
110
  ? top_provider.max_completion_tokens
116
- : undefined,
111
+ : typeof model.context_length === 'number'
112
+ ? model.context_length
113
+ : undefined,
117
114
  pricing: {
118
115
  cachedInput: cachedInputPrice,
119
116
  input: inputPrice,
120
117
  output: outputPrice,
121
118
  writeCacheInput: writeCacheInputPrice,
122
119
  },
123
- reasoning: supported_parameters.includes('reasoning'),
120
+ reasoning: hasReasoning,
124
121
  releasedAt: new Date(model.created * 1000).toISOString().split('T')[0],
125
122
  vision: inputModalities.includes('image'),
123
+ // Merge all applicable extendParams for settings
124
+ ...(() => {
125
+ const extendParams: string[] = [];
126
+ if (model.description && model.description.includes('`reasoning` `enabled`')) {
127
+ extendParams.push('enableReasoning');
128
+ }
129
+ if (hasReasoning && model.id.includes('gpt-5')) {
130
+ extendParams.push('gpt5ReasoningEffort');
131
+ }
132
+ if (hasReasoning && model.id.includes('openai') && !model.id.includes('gpt-5')) {
133
+ extendParams.push('reasoningEffort');
134
+ }
135
+ if (hasReasoning && model.id.includes('claude')) {
136
+ extendParams.push('enableReasoning', 'reasoningBudgetToken');
137
+ }
138
+ if (model.id.includes('claude') && writeCacheInputPrice && writeCacheInputPrice !== 0) {
139
+ extendParams.push('disableContextCaching');
140
+ }
141
+ if (hasReasoning && model.id.includes('gemini-2.5')) {
142
+ extendParams.push('reasoningBudgetToken');
143
+ }
144
+ return extendParams.length > 0 ? { settings: { extendParams } } : {};
145
+ })(),
126
146
  };
127
147
  });
128
148
 
@@ -3,57 +3,45 @@ interface ModelPricing {
3
3
  image?: string;
4
4
  input_cache_read?: string;
5
5
  input_cache_write?: string;
6
+ internal_reasoning?: string;
6
7
  prompt: string;
7
8
  request?: string;
8
9
  web_search?: string;
9
- internal_reasoning?: string;
10
10
  }
11
11
 
12
12
  interface TopProvider {
13
13
  context_length: number;
14
- max_completion_tokens: number | null;
15
14
  is_moderated: boolean;
15
+ max_completion_tokens: number | null;
16
16
  }
17
17
 
18
18
  interface Architecture {
19
- modality: string;
20
19
  input_modalities: string[];
20
+ instruct_type: string | null;
21
+ modality: string;
21
22
  output_modalities: string[];
22
23
  tokenizer: string;
23
- instruct_type: string | null;
24
24
  }
25
25
 
26
26
  export interface OpenRouterModelCard {
27
- id: string;
27
+ architecture: Architecture;
28
28
  canonical_slug: string;
29
- hugging_face_id?: string;
30
- name: string;
29
+ context_length: number;
31
30
  created: number;
31
+ default_parameters?: any | null;
32
32
  description?: string;
33
- context_length: number;
34
- architecture: Architecture;
35
- pricing: ModelPricing;
36
- top_provider: TopProvider;
33
+ hugging_face_id?: string;
34
+ id: string;
35
+ name: string;
37
36
  per_request_limits?: any | null;
37
+ pricing: ModelPricing;
38
38
  supported_parameters: string[];
39
- default_parameters?: any | null;
40
- }
41
-
42
- interface OpenRouterOpenAIReasoning {
43
- effort: 'high' | 'medium' | 'low';
44
- exclude?: boolean;
45
- }
46
-
47
- interface OpenRouterAnthropicReasoning {
48
- exclude?: boolean;
49
- max_tokens: number;
39
+ top_provider: TopProvider;
50
40
  }
51
41
 
52
- interface OpenRouterCommonReasoning {
42
+ export interface OpenRouterReasoning {
43
+ effort?: 'high' | 'medium' | 'low' | 'minimal';
44
+ enabled?: boolean;
53
45
  exclude?: boolean;
46
+ max_tokens?: number;
54
47
  }
55
-
56
- export type OpenRouterReasoning =
57
- | OpenRouterOpenAIReasoning
58
- | OpenRouterAnthropicReasoning
59
- | OpenRouterCommonReasoning;
@@ -71,7 +71,7 @@ describe('LobeVercelAIGatewayAI - custom features', () => {
71
71
  it('should add reasoning_effort to providerOptions.openai', async () => {
72
72
  await instance.chat({
73
73
  messages: [{ content: 'Hello', role: 'user' }],
74
- model: 'o1-preview',
74
+ model: 'openai/o1-preview',
75
75
  reasoning_effort: 'high',
76
76
  });
77
77
 
@@ -83,7 +83,7 @@ describe('LobeVercelAIGatewayAI - custom features', () => {
83
83
  it('should handle both reasoning_effort and verbosity', async () => {
84
84
  await instance.chat({
85
85
  messages: [{ content: 'Hello', role: 'user' }],
86
- model: 'o1-preview',
86
+ model: 'openai/o1-preview',
87
87
  reasoning_effort: 'medium',
88
88
  verbosity: 'low',
89
89
  });
@@ -96,7 +96,7 @@ describe('LobeVercelAIGatewayAI - custom features', () => {
96
96
  it('should handle verbosity without reasoning_effort', async () => {
97
97
  await instance.chat({
98
98
  messages: [{ content: 'Hello', role: 'user' }],
99
- model: 'gpt-4o',
99
+ model: 'openai/gpt-4o',
100
100
  verbosity: 'high',
101
101
  });
102
102
 
@@ -119,13 +119,13 @@ describe('LobeVercelAIGatewayAI - custom features', () => {
119
119
  await instance.chat({
120
120
  max_tokens: 1000,
121
121
  messages: [{ content: 'Hello', role: 'user' }],
122
- model: 'o1-preview',
122
+ model: 'openai/o1-preview',
123
123
  reasoning_effort: 'high',
124
124
  temperature: 0.7,
125
125
  });
126
126
 
127
127
  const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
128
- expect(calledPayload.model).toBe('o1-preview');
128
+ expect(calledPayload.model).toBe('openai/o1-preview');
129
129
  expect(calledPayload.temperature).toBe(0.7);
130
130
  expect(calledPayload.max_tokens).toBe(1000);
131
131
  expect(calledPayload.reasoning_effort).toBeUndefined();
@@ -138,7 +138,7 @@ describe('LobeVercelAIGatewayAI - custom features', () => {
138
138
  vi.clearAllMocks();
139
139
  await instance.chat({
140
140
  messages: [{ content: 'Hello', role: 'user' }],
141
- model: 'o1-preview',
141
+ model: 'openai/o1-preview',
142
142
  reasoning_effort: effort,
143
143
  } as any);
144
144
 
@@ -23,6 +23,11 @@ export interface VercelAIGatewayModelCard {
23
23
  type?: string;
24
24
  }
25
25
 
26
+ export interface VercelAIGatewayReasoning {
27
+ enabled?: boolean;
28
+ max_tokens?: number;
29
+ }
30
+
26
31
  export const formatPrice = (price?: string | number) => {
27
32
  if (price === undefined || price === null) return undefined;
28
33
  const n = typeof price === 'number' ? price : Number(price);
@@ -35,23 +40,41 @@ export const params = {
35
40
  baseURL: 'https://ai-gateway.vercel.sh/v1',
36
41
  chatCompletion: {
37
42
  handlePayload: (payload) => {
38
- const { model, reasoning_effort, verbosity, ...rest } = payload;
43
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
44
+ const { reasoning_effort, thinking, reasoning: _reasoning, verbosity, ...rest } = payload;
39
45
 
40
- const providerOptions: any = {};
41
- if (reasoning_effort || verbosity) {
42
- providerOptions.openai = {};
43
- if (reasoning_effort) {
44
- providerOptions.openai.reasoningEffort = reasoning_effort;
45
- providerOptions.openai.reasoningSummary = 'auto';
46
- }
47
- if (verbosity) {
48
- providerOptions.openai.textVerbosity = verbosity;
46
+ let reasoning: VercelAIGatewayReasoning | undefined;
47
+
48
+ if (thinking?.type || thinking?.budget_tokens !== undefined || reasoning_effort) {
49
+ if (thinking?.type === 'disabled') {
50
+ reasoning = { enabled: false };
51
+ } else if (thinking?.budget_tokens !== undefined) {
52
+ reasoning = {
53
+ enabled: true,
54
+ max_tokens: thinking?.budget_tokens,
55
+ };
56
+ } else if (reasoning_effort) {
57
+ reasoning = { enabled: true };
49
58
  }
50
59
  }
51
60
 
61
+ const providerOptions: any = {};
62
+ if ((verbosity || reasoning) && payload.model.includes('openai')) {
63
+ providerOptions.openai = {
64
+ ...(reasoning_effort && {
65
+ reasoningEffort: reasoning_effort,
66
+ reasoningSummary: 'auto',
67
+ }),
68
+ ...(verbosity && {
69
+ textVerbosity: verbosity,
70
+ }),
71
+ };
72
+ }
73
+
52
74
  return {
53
75
  ...rest,
54
- model,
76
+ model: payload.model,
77
+ ...(reasoning && { reasoning }),
55
78
  providerOptions,
56
79
  } as any;
57
80
  },
@@ -99,6 +122,26 @@ export const params = {
99
122
  reasoning: tags.includes('reasoning') || false,
100
123
  type: m.type === 'embedding' ? 'embedding' : 'chat',
101
124
  vision: tags.includes('vision') || false,
125
+ // Merge all applicable extendParams for settings
126
+ ...(() => {
127
+ const extendParams: string[] = [];
128
+ if (tags.includes('reasoning') && m.id.includes('gpt-5')) {
129
+ extendParams.push('gpt5ReasoningEffort', 'textVerbosity');
130
+ }
131
+ if (tags.includes('reasoning') && m.id.includes('openai') && !m.id.includes('gpt-5')) {
132
+ extendParams.push('reasoningEffort', 'textVerbosity');
133
+ }
134
+ if (tags.includes('reasoning') && m.id.includes('claude')) {
135
+ extendParams.push('enableReasoning', 'reasoningBudgetToken');
136
+ }
137
+ if (m.id.includes('claude') && writeCacheInputPrice && writeCacheInputPrice !== 0) {
138
+ extendParams.push('disableContextCaching');
139
+ }
140
+ if (tags.includes('reasoning') && m.id.includes('gemini-2.5')) {
141
+ extendParams.push('reasoningBudgetToken');
142
+ }
143
+ return extendParams.length > 0 ? { settings: { extendParams } } : {};
144
+ })(),
102
145
  } as any;
103
146
  });
104
147