@lobehub/chat 1.136.12 → 1.136.13
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/.github/workflows/claude-translator.yml +13 -1
- package/CHANGELOG.md +34 -0
- package/changelog/v1.json +12 -0
- package/locales/ar/modelProvider.json +12 -0
- package/locales/ar/models.json +39 -24
- package/locales/bg-BG/modelProvider.json +12 -0
- package/locales/bg-BG/models.json +39 -24
- package/locales/de-DE/modelProvider.json +12 -0
- package/locales/de-DE/models.json +39 -24
- package/locales/en-US/modelProvider.json +12 -0
- package/locales/en-US/models.json +39 -24
- package/locales/es-ES/modelProvider.json +12 -0
- package/locales/es-ES/models.json +39 -24
- package/locales/fa-IR/modelProvider.json +12 -0
- package/locales/fa-IR/models.json +39 -24
- package/locales/fr-FR/modelProvider.json +12 -0
- package/locales/fr-FR/models.json +39 -24
- package/locales/it-IT/modelProvider.json +12 -0
- package/locales/it-IT/models.json +39 -24
- package/locales/ja-JP/modelProvider.json +12 -0
- package/locales/ja-JP/models.json +39 -24
- package/locales/ko-KR/modelProvider.json +12 -0
- package/locales/ko-KR/models.json +39 -24
- package/locales/nl-NL/modelProvider.json +12 -0
- package/locales/nl-NL/models.json +39 -24
- package/locales/pl-PL/modelProvider.json +12 -0
- package/locales/pl-PL/models.json +39 -24
- package/locales/pt-BR/modelProvider.json +12 -0
- package/locales/pt-BR/models.json +39 -24
- package/locales/ru-RU/modelProvider.json +12 -0
- package/locales/ru-RU/models.json +39 -24
- package/locales/tr-TR/modelProvider.json +12 -0
- package/locales/tr-TR/models.json +39 -24
- package/locales/vi-VN/modelProvider.json +12 -0
- package/locales/vi-VN/models.json +39 -24
- package/locales/zh-CN/modelProvider.json +12 -0
- package/locales/zh-CN/models.json +39 -24
- package/locales/zh-TW/modelProvider.json +12 -0
- package/locales/zh-TW/models.json +39 -24
- package/package.json +3 -3
- package/packages/const/src/settings/index.ts +1 -0
- package/packages/database/package.json +7 -5
- package/packages/electron-client-ipc/src/events/index.ts +2 -2
- package/packages/electron-client-ipc/src/events/{localFile.ts → localSystem.ts} +25 -6
- package/packages/electron-client-ipc/src/types/index.ts +1 -1
- package/packages/electron-client-ipc/src/types/{localFile.ts → localSystem.ts} +89 -4
- package/packages/file-loaders/package.json +1 -2
- package/packages/file-loaders/src/loadFile.ts +4 -1
- package/packages/file-loaders/src/loaders/doc/__snapshots__/index.test.ts.snap +46 -0
- package/packages/file-loaders/src/loaders/doc/index.test.ts +38 -0
- package/packages/file-loaders/src/loaders/doc/index.ts +57 -0
- package/packages/file-loaders/src/loaders/docx/index.ts +36 -45
- package/packages/file-loaders/src/loaders/index.ts +2 -0
- package/packages/file-loaders/src/types/word-extractor.d.ts +9 -0
- package/packages/file-loaders/src/types.ts +1 -1
- package/packages/model-runtime/src/core/openaiCompatibleFactory/index.test.ts +267 -38
- package/packages/model-runtime/src/core/openaiCompatibleFactory/index.ts +45 -0
- package/packages/model-runtime/src/providerTestUtils.ts +0 -5
- package/packages/model-runtime/src/providers/anthropic/generateObject.test.ts +57 -44
- package/packages/model-runtime/src/providers/anthropic/generateObject.ts +28 -20
- package/packages/model-runtime/src/providers/deepseek/index.ts +5 -0
- package/packages/model-runtime/src/providers/openai/index.test.ts +0 -5
- package/packages/model-runtime/src/providers/openrouter/index.test.ts +3 -3
- package/packages/model-runtime/src/providers/openrouter/index.ts +32 -20
- package/packages/model-runtime/src/providers/openrouter/type.ts +25 -24
- package/packages/model-runtime/src/providers/zhipu/index.test.ts +0 -1
- package/packages/model-runtime/src/types/structureOutput.ts +13 -1
- package/packages/model-runtime/src/utils/handleOpenAIError.test.ts +0 -5
- package/packages/model-runtime/src/utils/handleOpenAIError.ts +2 -2
- package/packages/types/src/aiChat.ts +13 -1
- package/packages/types/src/index.ts +1 -0
- package/src/features/ChatInput/InputEditor/index.tsx +39 -26
- package/src/features/Conversation/Messages/Assistant/Tool/Render/LoadingPlaceholder/index.tsx +1 -1
- package/src/server/routers/lambda/agent.ts +2 -3
- package/src/server/routers/lambda/aiChat.ts +33 -1
- package/src/server/routers/lambda/chunk.ts +2 -2
- package/src/services/electron/file.ts +1 -2
- package/src/services/electron/localFileService.ts +40 -0
- package/src/tools/local-system/Placeholder/ListFiles.tsx +23 -0
- package/src/tools/local-system/Placeholder/ReadLocalFile.tsx +9 -0
- package/src/tools/local-system/Placeholder/SearchFiles.tsx +55 -0
- package/src/tools/local-system/Placeholder/index.tsx +25 -0
- package/src/tools/placeholders.ts +3 -0
|
@@ -12,7 +12,7 @@ describe('Anthropic generateObject', () => {
|
|
|
12
12
|
content: [
|
|
13
13
|
{
|
|
14
14
|
type: 'tool_use',
|
|
15
|
-
name: '
|
|
15
|
+
name: 'person_extractor',
|
|
16
16
|
input: { name: 'John', age: 30 },
|
|
17
17
|
},
|
|
18
18
|
],
|
|
@@ -23,8 +23,13 @@ describe('Anthropic generateObject', () => {
|
|
|
23
23
|
const payload = {
|
|
24
24
|
messages: [{ content: 'Generate a person object', role: 'user' as const }],
|
|
25
25
|
schema: {
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
name: 'person_extractor',
|
|
27
|
+
description: 'Extract person information',
|
|
28
|
+
schema: {
|
|
29
|
+
type: 'object' as const,
|
|
30
|
+
properties: { name: { type: 'string' }, age: { type: 'number' } },
|
|
31
|
+
required: ['name', 'age'],
|
|
32
|
+
},
|
|
28
33
|
},
|
|
29
34
|
model: 'claude-3-5-sonnet-20241022',
|
|
30
35
|
};
|
|
@@ -35,30 +40,24 @@ describe('Anthropic generateObject', () => {
|
|
|
35
40
|
expect.objectContaining({
|
|
36
41
|
model: 'claude-3-5-sonnet-20241022',
|
|
37
42
|
max_tokens: 8192,
|
|
38
|
-
messages: [
|
|
39
|
-
{ content: 'Generate a person object', role: 'user' },
|
|
40
|
-
{
|
|
41
|
-
content:
|
|
42
|
-
'Please use the structured_output tool to provide your response in the required format.',
|
|
43
|
-
role: 'user',
|
|
44
|
-
},
|
|
45
|
-
],
|
|
43
|
+
messages: [{ content: 'Generate a person object', role: 'user' }],
|
|
46
44
|
tools: [
|
|
47
45
|
{
|
|
48
|
-
name: '
|
|
49
|
-
description: '
|
|
46
|
+
name: 'person_extractor',
|
|
47
|
+
description: 'Extract person information',
|
|
50
48
|
input_schema: {
|
|
51
49
|
type: 'object',
|
|
52
50
|
properties: {
|
|
53
51
|
name: { type: 'string' },
|
|
54
52
|
age: { type: 'number' },
|
|
55
53
|
},
|
|
54
|
+
required: ['name', 'age'],
|
|
56
55
|
},
|
|
57
56
|
},
|
|
58
57
|
],
|
|
59
58
|
tool_choice: {
|
|
60
59
|
type: 'tool',
|
|
61
|
-
name: '
|
|
60
|
+
name: 'person_extractor',
|
|
62
61
|
},
|
|
63
62
|
}),
|
|
64
63
|
expect.objectContaining({}),
|
|
@@ -74,7 +73,7 @@ describe('Anthropic generateObject', () => {
|
|
|
74
73
|
content: [
|
|
75
74
|
{
|
|
76
75
|
type: 'tool_use',
|
|
77
|
-
name: '
|
|
76
|
+
name: 'status_extractor',
|
|
78
77
|
input: { status: 'success' },
|
|
79
78
|
},
|
|
80
79
|
],
|
|
@@ -87,7 +86,10 @@ describe('Anthropic generateObject', () => {
|
|
|
87
86
|
{ content: 'You are a helpful assistant', role: 'system' as const },
|
|
88
87
|
{ content: 'Generate status', role: 'user' as const },
|
|
89
88
|
],
|
|
90
|
-
schema: {
|
|
89
|
+
schema: {
|
|
90
|
+
name: 'status_extractor',
|
|
91
|
+
schema: { type: 'object' as const, properties: { status: { type: 'string' } } },
|
|
92
|
+
},
|
|
91
93
|
model: 'claude-3-5-sonnet-20241022',
|
|
92
94
|
};
|
|
93
95
|
|
|
@@ -111,7 +113,7 @@ describe('Anthropic generateObject', () => {
|
|
|
111
113
|
content: [
|
|
112
114
|
{
|
|
113
115
|
type: 'tool_use',
|
|
114
|
-
name: '
|
|
116
|
+
name: 'data_extractor',
|
|
115
117
|
input: { data: 'test' },
|
|
116
118
|
},
|
|
117
119
|
],
|
|
@@ -121,7 +123,10 @@ describe('Anthropic generateObject', () => {
|
|
|
121
123
|
|
|
122
124
|
const payload = {
|
|
123
125
|
messages: [{ content: 'Generate data', role: 'user' as const }],
|
|
124
|
-
schema: {
|
|
126
|
+
schema: {
|
|
127
|
+
name: 'data_extractor',
|
|
128
|
+
schema: { type: 'object' as const, properties: { data: { type: 'string' } } },
|
|
129
|
+
},
|
|
125
130
|
model: 'claude-3-5-sonnet-20241022',
|
|
126
131
|
};
|
|
127
132
|
|
|
@@ -155,20 +160,18 @@ describe('Anthropic generateObject', () => {
|
|
|
155
160
|
},
|
|
156
161
|
};
|
|
157
162
|
|
|
158
|
-
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
159
|
-
|
|
160
163
|
const payload = {
|
|
161
164
|
messages: [{ content: 'Generate data', role: 'user' as const }],
|
|
162
|
-
schema: {
|
|
165
|
+
schema: {
|
|
166
|
+
name: 'test_tool',
|
|
167
|
+
schema: { type: 'object' },
|
|
168
|
+
},
|
|
163
169
|
model: 'claude-3-5-sonnet-20241022',
|
|
164
170
|
};
|
|
165
171
|
|
|
166
|
-
const result = await createAnthropicGenerateObject(mockClient as any, payload);
|
|
172
|
+
const result = await createAnthropicGenerateObject(mockClient as any, payload as any);
|
|
167
173
|
|
|
168
|
-
expect(consoleSpy).toHaveBeenCalledWith('No structured output tool use found in response');
|
|
169
174
|
expect(result).toBeUndefined();
|
|
170
|
-
|
|
171
|
-
consoleSpy.mockRestore();
|
|
172
175
|
});
|
|
173
176
|
|
|
174
177
|
it('should handle complex nested schemas', async () => {
|
|
@@ -178,7 +181,7 @@ describe('Anthropic generateObject', () => {
|
|
|
178
181
|
content: [
|
|
179
182
|
{
|
|
180
183
|
type: 'tool_use',
|
|
181
|
-
name: '
|
|
184
|
+
name: 'user_extractor',
|
|
182
185
|
input: {
|
|
183
186
|
user: {
|
|
184
187
|
name: 'Alice',
|
|
@@ -200,22 +203,26 @@ describe('Anthropic generateObject', () => {
|
|
|
200
203
|
const payload = {
|
|
201
204
|
messages: [{ content: 'Generate complex user data', role: 'user' as const }],
|
|
202
205
|
schema: {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
206
|
+
name: 'user_extractor',
|
|
207
|
+
description: 'Extract complex user information',
|
|
208
|
+
schema: {
|
|
209
|
+
type: 'object' as const,
|
|
210
|
+
properties: {
|
|
211
|
+
user: {
|
|
212
|
+
type: 'object',
|
|
213
|
+
properties: {
|
|
214
|
+
name: { type: 'string' },
|
|
215
|
+
profile: {
|
|
216
|
+
type: 'object',
|
|
217
|
+
properties: {
|
|
218
|
+
age: { type: 'number' },
|
|
219
|
+
preferences: { type: 'array', items: { type: 'string' } },
|
|
220
|
+
},
|
|
214
221
|
},
|
|
215
222
|
},
|
|
216
223
|
},
|
|
224
|
+
metadata: { type: 'object' },
|
|
217
225
|
},
|
|
218
|
-
metadata: { type: 'object' },
|
|
219
226
|
},
|
|
220
227
|
},
|
|
221
228
|
model: 'claude-3-5-sonnet-20241022',
|
|
@@ -248,13 +255,16 @@ describe('Anthropic generateObject', () => {
|
|
|
248
255
|
|
|
249
256
|
const payload = {
|
|
250
257
|
messages: [{ content: 'Generate data', role: 'user' as const }],
|
|
251
|
-
schema: {
|
|
258
|
+
schema: {
|
|
259
|
+
name: 'test_tool',
|
|
260
|
+
schema: { type: 'object' },
|
|
261
|
+
},
|
|
252
262
|
model: 'claude-3-5-sonnet-20241022',
|
|
253
263
|
};
|
|
254
264
|
|
|
255
|
-
await expect(
|
|
256
|
-
|
|
257
|
-
);
|
|
265
|
+
await expect(
|
|
266
|
+
createAnthropicGenerateObject(mockClient as any, payload as any),
|
|
267
|
+
).rejects.toThrow('API Error: Model not found');
|
|
258
268
|
});
|
|
259
269
|
|
|
260
270
|
it('should handle abort signals correctly', async () => {
|
|
@@ -269,7 +279,10 @@ describe('Anthropic generateObject', () => {
|
|
|
269
279
|
|
|
270
280
|
const payload = {
|
|
271
281
|
messages: [{ content: 'Generate data', role: 'user' as const }],
|
|
272
|
-
schema: {
|
|
282
|
+
schema: {
|
|
283
|
+
name: 'test_tool',
|
|
284
|
+
schema: { type: 'object' },
|
|
285
|
+
},
|
|
273
286
|
model: 'claude-3-5-sonnet-20241022',
|
|
274
287
|
};
|
|
275
288
|
|
|
@@ -278,7 +291,7 @@ describe('Anthropic generateObject', () => {
|
|
|
278
291
|
};
|
|
279
292
|
|
|
280
293
|
await expect(
|
|
281
|
-
createAnthropicGenerateObject(mockClient as any, payload, options),
|
|
294
|
+
createAnthropicGenerateObject(mockClient as any, payload as any, options),
|
|
282
295
|
).rejects.toThrow();
|
|
283
296
|
});
|
|
284
297
|
});
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import Anthropic from '@anthropic-ai/sdk';
|
|
1
|
+
import type Anthropic from '@anthropic-ai/sdk';
|
|
2
|
+
import debug from 'debug';
|
|
2
3
|
|
|
3
4
|
import { buildAnthropicMessages } from '../../core/contextBuilders/anthropic';
|
|
4
5
|
import { GenerateObjectOptions, GenerateObjectPayload } from '../../types';
|
|
5
6
|
|
|
7
|
+
const log = debug('lobe-model-runtime:anthropic:generate-object');
|
|
8
|
+
|
|
6
9
|
/**
|
|
7
10
|
* Generate structured output using Anthropic Claude API with Function Calling
|
|
8
11
|
*/
|
|
@@ -13,26 +16,25 @@ export const createAnthropicGenerateObject = async (
|
|
|
13
16
|
) => {
|
|
14
17
|
const { schema, messages, model } = payload;
|
|
15
18
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
log('generateObject called with model: %s', model);
|
|
20
|
+
log('schema: %O', schema);
|
|
21
|
+
log('messages count: %d', messages.length);
|
|
22
|
+
|
|
23
|
+
// Convert OpenAI-style schema to Anthropic tool format
|
|
24
|
+
const tool: Anthropic.ToolUnion = {
|
|
25
|
+
description:
|
|
26
|
+
schema.description || 'Generate structured output according to the provided schema',
|
|
27
|
+
input_schema: schema.schema as any,
|
|
28
|
+
name: schema.name || 'structured_output',
|
|
21
29
|
};
|
|
30
|
+
|
|
31
|
+
log('converted tool: %O', tool);
|
|
22
32
|
// Convert messages to Anthropic format
|
|
23
33
|
const system_message = messages.find((m) => m.role === 'system');
|
|
24
34
|
const user_messages = messages.filter((m) => m.role !== 'system');
|
|
25
35
|
const anthropicMessages = await buildAnthropicMessages(user_messages);
|
|
26
36
|
|
|
27
|
-
|
|
28
|
-
const enhancedMessages = [
|
|
29
|
-
...anthropicMessages,
|
|
30
|
-
{
|
|
31
|
-
content:
|
|
32
|
-
'Please use the structured_output tool to provide your response in the required format.',
|
|
33
|
-
role: 'user' as const,
|
|
34
|
-
},
|
|
35
|
-
];
|
|
37
|
+
log('converted %d messages to Anthropic format', anthropicMessages.length);
|
|
36
38
|
|
|
37
39
|
const systemPrompts = system_message?.content
|
|
38
40
|
? [
|
|
@@ -44,13 +46,15 @@ export const createAnthropicGenerateObject = async (
|
|
|
44
46
|
: undefined;
|
|
45
47
|
|
|
46
48
|
try {
|
|
49
|
+
log('calling Anthropic API with max_tokens: %d', 8192);
|
|
50
|
+
|
|
47
51
|
const response = await client.messages.create(
|
|
48
52
|
{
|
|
49
53
|
max_tokens: 8192,
|
|
50
|
-
messages:
|
|
54
|
+
messages: anthropicMessages,
|
|
51
55
|
model,
|
|
52
56
|
system: systemPrompts,
|
|
53
|
-
tool_choice: { name:
|
|
57
|
+
tool_choice: { name: tool.name, type: 'tool' },
|
|
54
58
|
tools: [tool],
|
|
55
59
|
},
|
|
56
60
|
{
|
|
@@ -58,19 +62,23 @@ export const createAnthropicGenerateObject = async (
|
|
|
58
62
|
},
|
|
59
63
|
);
|
|
60
64
|
|
|
65
|
+
log('received response with %d content blocks', response.content.length);
|
|
66
|
+
log('response: %O', response);
|
|
67
|
+
|
|
61
68
|
// Extract the tool use result
|
|
62
69
|
const toolUseBlock = response.content.find(
|
|
63
|
-
(block) => block.type === 'tool_use' && block.name ===
|
|
70
|
+
(block) => block.type === 'tool_use' && block.name === tool.name,
|
|
64
71
|
);
|
|
65
72
|
|
|
66
73
|
if (!toolUseBlock || toolUseBlock.type !== 'tool_use') {
|
|
67
|
-
|
|
74
|
+
log('no tool use found in response (expected tool: %s)', tool.name);
|
|
68
75
|
return undefined;
|
|
69
76
|
}
|
|
70
77
|
|
|
78
|
+
log('extracted tool input: %O', toolUseBlock.input);
|
|
71
79
|
return toolUseBlock.input;
|
|
72
80
|
} catch (error) {
|
|
73
|
-
|
|
81
|
+
log('generateObject error: %O', error);
|
|
74
82
|
throw error;
|
|
75
83
|
}
|
|
76
84
|
};
|
|
@@ -12,6 +12,11 @@ export const LobeDeepSeekAI = createOpenAICompatibleRuntime({
|
|
|
12
12
|
debug: {
|
|
13
13
|
chatCompletion: () => process.env.DEBUG_DEEPSEEK_CHAT_COMPLETION === '1',
|
|
14
14
|
},
|
|
15
|
+
// Deepseek don't support json format well
|
|
16
|
+
// use Tools calling to simulate
|
|
17
|
+
generateObject: {
|
|
18
|
+
useToolsCalling: true,
|
|
19
|
+
},
|
|
15
20
|
models: async ({ client }) => {
|
|
16
21
|
const modelsPage = (await client.models.list()) as any;
|
|
17
22
|
const modelList: DeepSeekModelCard[] = modelsPage.data;
|
|
@@ -101,7 +101,6 @@ describe('LobeOpenAI', () => {
|
|
|
101
101
|
it('should return ProviderBizError with the cause when OpenAI.APIError is thrown with cause', async () => {
|
|
102
102
|
// Arrange
|
|
103
103
|
const errorInfo = {
|
|
104
|
-
stack: 'abc',
|
|
105
104
|
cause: {
|
|
106
105
|
message: 'api is undefined',
|
|
107
106
|
},
|
|
@@ -122,7 +121,6 @@ describe('LobeOpenAI', () => {
|
|
|
122
121
|
endpoint: 'https://api.openai.com/v1',
|
|
123
122
|
error: {
|
|
124
123
|
cause: { message: 'api is undefined' },
|
|
125
|
-
stack: 'abc',
|
|
126
124
|
},
|
|
127
125
|
errorType: 'ProviderBizError',
|
|
128
126
|
provider: 'openai',
|
|
@@ -133,7 +131,6 @@ describe('LobeOpenAI', () => {
|
|
|
133
131
|
it('should return ProviderBizError with an cause response with desensitize Url', async () => {
|
|
134
132
|
// Arrange
|
|
135
133
|
const errorInfo = {
|
|
136
|
-
stack: 'abc',
|
|
137
134
|
cause: { message: 'api is undefined' },
|
|
138
135
|
};
|
|
139
136
|
const apiError = new OpenAI.APIError(400, errorInfo, 'module error', {});
|
|
@@ -158,7 +155,6 @@ describe('LobeOpenAI', () => {
|
|
|
158
155
|
endpoint: 'https://api.***.com/v1',
|
|
159
156
|
error: {
|
|
160
157
|
cause: { message: 'api is undefined' },
|
|
161
|
-
stack: 'abc',
|
|
162
158
|
},
|
|
163
159
|
errorType: 'ProviderBizError',
|
|
164
160
|
provider: 'openai',
|
|
@@ -188,7 +184,6 @@ describe('LobeOpenAI', () => {
|
|
|
188
184
|
name: genericError.name,
|
|
189
185
|
cause: genericError.cause,
|
|
190
186
|
message: genericError.message,
|
|
191
|
-
stack: genericError.stack,
|
|
192
187
|
},
|
|
193
188
|
});
|
|
194
189
|
}
|
|
@@ -21,7 +21,7 @@ testProvider({
|
|
|
21
21
|
});
|
|
22
22
|
|
|
23
23
|
// Mock the console.error to avoid polluting test output
|
|
24
|
-
vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
24
|
+
vi.spyOn(console, 'error').mockImplementation(() => { });
|
|
25
25
|
|
|
26
26
|
let instance: LobeOpenAICompatibleRuntime;
|
|
27
27
|
|
|
@@ -154,8 +154,8 @@ describe('LobeOpenRouterAI', () => {
|
|
|
154
154
|
|
|
155
155
|
const list = await instance.models();
|
|
156
156
|
|
|
157
|
-
// 验证在当前实现中,当
|
|
158
|
-
expect(fetch).toHaveBeenCalledWith('https://openrouter.ai/api/
|
|
157
|
+
// 验证在当前实现中,当 model fetch 返回非 ok 时,会返回空列表
|
|
158
|
+
expect(fetch).toHaveBeenCalledWith('https://openrouter.ai/api/v1/models');
|
|
159
159
|
expect(list.length).toBe(0);
|
|
160
160
|
expect(list).toEqual([]);
|
|
161
161
|
});
|
|
@@ -58,7 +58,7 @@ export const LobeOpenRouterAI = createOpenAICompatibleRuntime({
|
|
|
58
58
|
let modelList: OpenRouterModelCard[] = [];
|
|
59
59
|
|
|
60
60
|
try {
|
|
61
|
-
const response = await fetch('https://openrouter.ai/api/
|
|
61
|
+
const response = await fetch('https://openrouter.ai/api/v1/models');
|
|
62
62
|
if (response.ok) {
|
|
63
63
|
const data = await response.json();
|
|
64
64
|
modelList = data['data'];
|
|
@@ -70,19 +70,31 @@ export const LobeOpenRouterAI = createOpenAICompatibleRuntime({
|
|
|
70
70
|
|
|
71
71
|
// 处理前端获取的模型信息,转换为标准格式
|
|
72
72
|
const formattedModels = modelList.map((model) => {
|
|
73
|
-
const {
|
|
74
|
-
const endpointModel = endpoint?.model;
|
|
73
|
+
const { top_provider, architecture, pricing, supported_parameters } = model;
|
|
75
74
|
|
|
76
|
-
const inputModalities =
|
|
75
|
+
const inputModalities = architecture.input_modalities || [];
|
|
77
76
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
77
|
+
// 处理 name,默认去除冒号及其前面的内容
|
|
78
|
+
let displayName = model.name;
|
|
79
|
+
const colonIndex = displayName.indexOf(':');
|
|
80
|
+
if (colonIndex !== -1) {
|
|
81
|
+
const prefix = displayName.substring(0, colonIndex).trim();
|
|
82
|
+
const suffix = displayName.substring(colonIndex + 1).trim();
|
|
81
83
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
84
|
+
const isDeepSeekPrefix = prefix.toLowerCase() === 'deepseek';
|
|
85
|
+
const suffixHasDeepSeek = suffix.toLowerCase().includes('deepseek');
|
|
86
|
+
|
|
87
|
+
if (isDeepSeekPrefix && !suffixHasDeepSeek) {
|
|
88
|
+
displayName = model.name;
|
|
89
|
+
} else {
|
|
90
|
+
displayName = suffix;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const inputPrice = formatPrice(pricing.prompt);
|
|
95
|
+
const outputPrice = formatPrice(pricing.completion);
|
|
96
|
+
const cachedInputPrice = formatPrice(pricing.input_cache_read);
|
|
97
|
+
const writeCacheInputPrice = formatPrice(pricing.input_cache_write);
|
|
86
98
|
|
|
87
99
|
const isFree = (inputPrice === 0 || outputPrice === 0) && !displayName.endsWith('(free)');
|
|
88
100
|
if (isFree) {
|
|
@@ -90,14 +102,14 @@ export const LobeOpenRouterAI = createOpenAICompatibleRuntime({
|
|
|
90
102
|
}
|
|
91
103
|
|
|
92
104
|
return {
|
|
93
|
-
contextWindowTokens:
|
|
94
|
-
description:
|
|
105
|
+
contextWindowTokens: top_provider.context_length || model.context_length,
|
|
106
|
+
description: model.description,
|
|
95
107
|
displayName,
|
|
96
|
-
functionCall:
|
|
97
|
-
id:
|
|
108
|
+
functionCall: supported_parameters.includes('tools'),
|
|
109
|
+
id: model.id,
|
|
98
110
|
maxOutput:
|
|
99
|
-
typeof
|
|
100
|
-
?
|
|
111
|
+
typeof top_provider.max_completion_tokens === 'number'
|
|
112
|
+
? top_provider.max_completion_tokens
|
|
101
113
|
: undefined,
|
|
102
114
|
pricing: {
|
|
103
115
|
input: inputPrice,
|
|
@@ -105,9 +117,9 @@ export const LobeOpenRouterAI = createOpenAICompatibleRuntime({
|
|
|
105
117
|
writeCacheInput: writeCacheInputPrice,
|
|
106
118
|
output: outputPrice,
|
|
107
119
|
},
|
|
108
|
-
reasoning:
|
|
109
|
-
releasedAt: new Date(model.
|
|
110
|
-
vision:
|
|
120
|
+
reasoning: supported_parameters.includes('reasoning'),
|
|
121
|
+
releasedAt: new Date(model.created * 1000).toISOString().split('T')[0],
|
|
122
|
+
vision: inputModalities.includes('image'),
|
|
111
123
|
};
|
|
112
124
|
});
|
|
113
125
|
|
|
@@ -5,37 +5,38 @@ interface ModelPricing {
|
|
|
5
5
|
input_cache_write?: string;
|
|
6
6
|
prompt: string;
|
|
7
7
|
request?: string;
|
|
8
|
+
web_search?: string;
|
|
9
|
+
internal_reasoning?: string;
|
|
8
10
|
}
|
|
9
11
|
|
|
10
|
-
|
|
12
|
+
interface TopProvider {
|
|
11
13
|
context_length: number;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
endpoint: OpenRouterModelEndpoint;
|
|
15
|
-
input_modalities?: string[];
|
|
16
|
-
name?: string;
|
|
17
|
-
output_modalities?: string[];
|
|
18
|
-
per_request_limits?: any | null;
|
|
19
|
-
short_name?: string;
|
|
20
|
-
slug: string;
|
|
14
|
+
max_completion_tokens: number | null;
|
|
15
|
+
is_moderated: boolean;
|
|
21
16
|
}
|
|
22
17
|
|
|
23
|
-
interface
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
18
|
+
interface Architecture {
|
|
19
|
+
modality: string;
|
|
20
|
+
input_modalities: string[];
|
|
21
|
+
output_modalities: string[];
|
|
22
|
+
tokenizer: string;
|
|
23
|
+
instruct_type: string | null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface OpenRouterModelCard {
|
|
27
|
+
id: string;
|
|
28
|
+
canonical_slug: string;
|
|
29
|
+
hugging_face_id?: string;
|
|
30
|
+
name: string;
|
|
31
|
+
created: number;
|
|
32
|
+
description?: string;
|
|
33
|
+
context_length: number;
|
|
34
|
+
architecture: Architecture;
|
|
34
35
|
pricing: ModelPricing;
|
|
36
|
+
top_provider: TopProvider;
|
|
37
|
+
per_request_limits?: any | null;
|
|
35
38
|
supported_parameters: string[];
|
|
36
|
-
|
|
37
|
-
supports_tool_parameters?: boolean;
|
|
38
|
-
variant?: 'free' | 'standard' | 'unknown';
|
|
39
|
+
default_parameters?: any | null;
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
interface OpenRouterOpenAIReasoning {
|
|
@@ -4,11 +4,23 @@ interface GenerateObjectMessage {
|
|
|
4
4
|
role: 'user' | 'system' | 'assistant';
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
+
interface GenerateObjectSchema {
|
|
8
|
+
description?: string;
|
|
9
|
+
name: string;
|
|
10
|
+
schema: {
|
|
11
|
+
additionalProperties?: boolean;
|
|
12
|
+
properties: Record<string, any>;
|
|
13
|
+
required?: string[];
|
|
14
|
+
type: 'object';
|
|
15
|
+
};
|
|
16
|
+
strict?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
7
19
|
export interface GenerateObjectPayload {
|
|
8
20
|
messages: GenerateObjectMessage[];
|
|
9
21
|
model: string;
|
|
10
22
|
responseApi?: boolean;
|
|
11
|
-
schema:
|
|
23
|
+
schema: GenerateObjectSchema;
|
|
12
24
|
}
|
|
13
25
|
|
|
14
26
|
export interface GenerateObjectOptions {
|
|
@@ -48,7 +48,6 @@ describe('handleOpenAIError', () => {
|
|
|
48
48
|
|
|
49
49
|
expect(result.errorResult).toEqual({
|
|
50
50
|
headers: { headers, status: 401 },
|
|
51
|
-
stack: apiError.stack,
|
|
52
51
|
status: 472,
|
|
53
52
|
});
|
|
54
53
|
expect(result.RuntimeError).toBeUndefined();
|
|
@@ -84,7 +83,6 @@ describe('handleOpenAIError', () => {
|
|
|
84
83
|
cause: { details: 'Error details' },
|
|
85
84
|
message: 'Generic error',
|
|
86
85
|
name: 'Error',
|
|
87
|
-
stack: error.stack,
|
|
88
86
|
},
|
|
89
87
|
});
|
|
90
88
|
});
|
|
@@ -100,7 +98,6 @@ describe('handleOpenAIError', () => {
|
|
|
100
98
|
cause: undefined,
|
|
101
99
|
message: 'Simple error',
|
|
102
100
|
name: 'Error',
|
|
103
|
-
stack: error.stack,
|
|
104
101
|
},
|
|
105
102
|
});
|
|
106
103
|
});
|
|
@@ -122,7 +119,6 @@ describe('handleOpenAIError', () => {
|
|
|
122
119
|
cause: undefined,
|
|
123
120
|
message: 'Custom error message',
|
|
124
121
|
name: 'CustomError',
|
|
125
|
-
stack: error.stack,
|
|
126
122
|
},
|
|
127
123
|
});
|
|
128
124
|
});
|
|
@@ -141,7 +137,6 @@ describe('handleOpenAIError', () => {
|
|
|
141
137
|
cause: undefined,
|
|
142
138
|
message: 'Object error',
|
|
143
139
|
name: undefined,
|
|
144
|
-
stack: undefined,
|
|
145
140
|
},
|
|
146
141
|
});
|
|
147
142
|
});
|
|
@@ -20,7 +20,7 @@ export const handleOpenAIError = (
|
|
|
20
20
|
}
|
|
21
21
|
// if there is no other request error, the error object is a Response like object
|
|
22
22
|
else {
|
|
23
|
-
errorResult = { headers: error.headers,
|
|
23
|
+
errorResult = { headers: error.headers, status: error.status };
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
return {
|
|
@@ -29,7 +29,7 @@ export const handleOpenAIError = (
|
|
|
29
29
|
} else {
|
|
30
30
|
const err = error as Error;
|
|
31
31
|
|
|
32
|
-
errorResult = { cause: err.cause, message: err.message, name: err.name
|
|
32
|
+
errorResult = { cause: err.cause, message: err.message, name: err.name };
|
|
33
33
|
|
|
34
34
|
return {
|
|
35
35
|
RuntimeError: AgentRuntimeErrorType.AgentRuntimeError,
|
|
@@ -54,12 +54,24 @@ export interface SendMessageServerResponse {
|
|
|
54
54
|
userMessageId: string;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
export const StructureSchema = z.object({
|
|
58
|
+
description: z.string().optional(),
|
|
59
|
+
name: z.string(),
|
|
60
|
+
schema: z.object({
|
|
61
|
+
additionalProperties: z.boolean().optional(),
|
|
62
|
+
properties: z.record(z.string(), z.any()),
|
|
63
|
+
required: z.array(z.string()).optional(),
|
|
64
|
+
type: z.literal('object'),
|
|
65
|
+
}),
|
|
66
|
+
strict: z.boolean().optional(),
|
|
67
|
+
});
|
|
68
|
+
|
|
57
69
|
export const StructureOutputSchema = z.object({
|
|
58
70
|
keyVaultsPayload: z.string(),
|
|
59
71
|
messages: z.array(z.any()),
|
|
60
72
|
model: z.string(),
|
|
61
73
|
provider: z.string(),
|
|
62
|
-
schema:
|
|
74
|
+
schema: StructureSchema,
|
|
63
75
|
});
|
|
64
76
|
|
|
65
77
|
export interface StructureOutputParams {
|