@lobehub/lobehub 2.0.0-next.4 → 2.0.0-next.6

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 (35) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/changelog/v1.json +14 -0
  3. package/docs/development/database-schema.dbml +11 -1
  4. package/docs/self-hosting/advanced/online-search.mdx +30 -25
  5. package/docs/self-hosting/advanced/online-search.zh-CN.mdx +25 -23
  6. package/package.json +1 -1
  7. package/packages/database/migrations/0041_improve_index.sql +10 -0
  8. package/packages/database/migrations/meta/0041_snapshot.json +7784 -0
  9. package/packages/database/migrations/meta/_journal.json +7 -0
  10. package/packages/database/src/core/migrations.json +17 -0
  11. package/packages/database/src/models/session.ts +60 -19
  12. package/packages/database/src/schemas/agent.ts +10 -11
  13. package/packages/database/src/schemas/message.ts +5 -1
  14. package/packages/database/src/schemas/relations.ts +6 -4
  15. package/packages/database/src/schemas/session.ts +2 -0
  16. package/packages/database/src/schemas/topic.ts +6 -1
  17. package/packages/model-bank/src/aiModels/anthropic.ts +0 -63
  18. package/packages/model-bank/src/aiModels/higress.ts +0 -55
  19. package/packages/model-bank/src/aiModels/infiniai.ts +21 -0
  20. package/packages/model-bank/src/aiModels/ollamacloud.ts +13 -0
  21. package/packages/model-bank/src/aiModels/siliconcloud.ts +19 -0
  22. package/packages/model-runtime/src/core/streams/openai/__snapshots__/responsesStream.test.ts.snap +0 -38
  23. package/packages/model-runtime/src/providers/minimax/index.ts +5 -5
  24. package/packages/model-runtime/src/providers/search1api/index.test.ts +2 -2
  25. package/packages/web-crawler/src/crawImpl/firecrawl.ts +39 -12
  26. package/scripts/migrateServerDB/index.ts +2 -1
  27. package/src/config/modelProviders/anthropic.ts +0 -23
  28. package/src/config/modelProviders/higress.ts +0 -23
  29. package/src/config/modelProviders/minimax.ts +1 -1
  30. package/src/config/modelProviders/qiniu.ts +1 -1
  31. package/src/server/modules/AssistantStore/index.ts +1 -1
  32. package/src/server/routers/lambda/session.ts +8 -5
  33. package/src/server/services/search/impls/firecrawl/index.ts +51 -11
  34. package/src/server/services/search/impls/firecrawl/type.ts +60 -9
  35. package/src/services/user/client.test.ts +4 -1
@@ -287,6 +287,13 @@
287
287
  "when": 1761563458595,
288
288
  "tag": "0040_improve_user_memory_field",
289
289
  "breakpoints": true
290
+ },
291
+ {
292
+ "idx": 41,
293
+ "version": "7",
294
+ "when": 1761878697451,
295
+ "tag": "0041_improve_index",
296
+ "breakpoints": true
290
297
  }
291
298
  ],
292
299
  "version": "6"
@@ -729,5 +729,22 @@
729
729
  "bps": true,
730
730
  "folderMillis": 1761563458595,
731
731
  "hash": "c483b3ea859f78f687737229bd40f6fef5486aaf05fa20a8ea6961c2a8f0cc64"
732
+ },
733
+ {
734
+ "sql": [
735
+ "CREATE INDEX IF NOT EXISTS \"agents_files_agent_id_idx\" ON \"agents_files\" USING btree (\"agent_id\");",
736
+ "\nCREATE INDEX IF NOT EXISTS \"message_groups_topic_id_idx\" ON \"message_groups\" USING btree (\"topic_id\");",
737
+ "\nCREATE INDEX IF NOT EXISTS \"messages_agent_id_idx\" ON \"messages\" USING btree (\"agent_id\");",
738
+ "\nCREATE INDEX IF NOT EXISTS \"agents_to_sessions_session_id_idx\" ON \"agents_to_sessions\" USING btree (\"session_id\");",
739
+ "\nCREATE INDEX IF NOT EXISTS \"agents_to_sessions_agent_id_idx\" ON \"agents_to_sessions\" USING btree (\"agent_id\");",
740
+ "\nCREATE INDEX IF NOT EXISTS \"sessions_user_id_updated_at_idx\" ON \"sessions\" USING btree (\"user_id\",\"updated_at\");",
741
+ "\nCREATE INDEX IF NOT EXISTS \"sessions_group_id_idx\" ON \"sessions\" USING btree (\"group_id\");",
742
+ "\nCREATE INDEX IF NOT EXISTS \"threads_topic_id_idx\" ON \"threads\" USING btree (\"topic_id\");",
743
+ "\nCREATE INDEX IF NOT EXISTS \"topics_session_id_idx\" ON \"topics\" USING btree (\"session_id\");",
744
+ "\nCREATE INDEX IF NOT EXISTS \"topics_group_id_idx\" ON \"topics\" USING btree (\"group_id\");\n"
745
+ ],
746
+ "bps": true,
747
+ "folderMillis": 1761878697451,
748
+ "hash": "2f740719356c4ea28a4f71b6089595871f6e185e056b43288a7d16f7d0aae196"
732
749
  }
733
750
  ]
@@ -53,13 +53,44 @@ export class SessionModel {
53
53
  query = async ({ current = 0, pageSize = 9999 } = {}) => {
54
54
  const offset = current * pageSize;
55
55
 
56
- return this.db.query.sessions.findMany({
57
- limit: pageSize,
58
- offset,
59
- orderBy: [desc(sessions.updatedAt)],
60
- where: and(eq(sessions.userId, this.userId), not(eq(sessions.slug, INBOX_SESSION_ID))),
61
- with: { agentsToSessions: { columns: {}, with: { agent: true } }, group: true },
62
- });
56
+ // Use leftJoin instead of nested with for better performance
57
+ const result = await this.db
58
+ .select({
59
+ // Agent fields (from agentsToSessions join)
60
+ agent: agents,
61
+ // Group fields
62
+ group: sessionGroups,
63
+ // Session fields
64
+ session: sessions,
65
+ })
66
+ .from(sessions)
67
+ .leftJoin(agentsToSessions, eq(sessions.id, agentsToSessions.sessionId))
68
+ .leftJoin(agents, eq(agentsToSessions.agentId, agents.id))
69
+ .leftJoin(sessionGroups, eq(sessions.groupId, sessionGroups.id))
70
+ .where(and(eq(sessions.userId, this.userId), not(eq(sessions.slug, INBOX_SESSION_ID))))
71
+ .orderBy(desc(sessions.updatedAt))
72
+ .limit(pageSize)
73
+ .offset(offset);
74
+
75
+ // Group results by session (since leftJoin can create multiple rows per session)
76
+ // Use Map to preserve order
77
+ const groupedResults = new Map<string, any>();
78
+
79
+ for (const row of result) {
80
+ const sessionId = row.session.id;
81
+ if (!groupedResults.has(sessionId)) {
82
+ groupedResults.set(sessionId, {
83
+ ...row.session,
84
+ agentsToSessions: [],
85
+ group: row.group,
86
+ });
87
+ }
88
+ if (row.agent) {
89
+ groupedResults.get(sessionId)!.agentsToSessions.push({ agent: row.agent });
90
+ }
91
+ }
92
+
93
+ return Array.from(groupedResults.values());
63
94
  };
64
95
 
65
96
  queryWithGroups = async (): Promise<ChatSessionList> => {
@@ -71,9 +102,11 @@ export class SessionModel {
71
102
  where: eq(sessions.userId, this.userId),
72
103
  });
73
104
 
105
+ const mappedSessions = result.map((item) => this.mapSessionItem(item as any));
106
+
74
107
  return {
75
108
  sessionGroups: groups as unknown as ChatSessionList['sessionGroups'],
76
- sessions: result.map((item) => this.mapSessionItem(item as any)),
109
+ sessions: mappedSessions,
77
110
  };
78
111
  };
79
112
 
@@ -332,7 +365,7 @@ export class SessionModel {
332
365
  .delete(agentsToSessions)
333
366
  .where(and(eq(agentsToSessions.sessionId, id), eq(agentsToSessions.userId, this.userId)));
334
367
 
335
- // Delete the session
368
+ // Delete the session (this will cascade delete messages, topics, etc.)
336
369
  const result = await trx
337
370
  .delete(sessions)
338
371
  .where(and(eq(sessions.id, id), eq(sessions.userId, this.userId)));
@@ -397,17 +430,25 @@ export class SessionModel {
397
430
  };
398
431
 
399
432
  clearOrphanAgent = async (agentIds: string[], trx: any) => {
400
- // Delete orphaned agents (those not linked to any other sessions)
401
- for (const agentId of agentIds) {
402
- const remaining = await trx
403
- .select()
404
- .from(agentsToSessions)
405
- .where(eq(agentsToSessions.agentId, agentId))
406
- .limit(1);
433
+ if (agentIds.length === 0) return;
407
434
 
408
- if (remaining.length === 0) {
409
- await trx.delete(agents).where(and(eq(agents.id, agentId), eq(agents.userId, this.userId)));
410
- }
435
+ // Batch query to find which agents still have sessions
436
+ const remainingLinks = (await trx
437
+ .select({ agentId: agentsToSessions.agentId })
438
+ .from(agentsToSessions)
439
+ .where(inArray(agentsToSessions.agentId, agentIds))) as { agentId: string }[];
440
+
441
+ const linkedAgentIds = new Set(remainingLinks.map((link) => link.agentId));
442
+
443
+ // Find orphaned agents (those not in the linked set)
444
+ const orphanedAgentIds = agentIds.filter((id) => !linkedAgentIds.has(id));
445
+
446
+ // Batch delete orphaned agents (this will cascade to agentsFiles, agentsKnowledgeBases, etc.)
447
+ // and SET NULL on messages.agentId
448
+ if (orphanedAgentIds.length > 0) {
449
+ await trx
450
+ .delete(agents)
451
+ .where(and(inArray(agents.id, orphanedAgentIds), eq(agents.userId, this.userId)));
411
452
  }
412
453
  };
413
454
 
@@ -62,11 +62,11 @@ export const agents = pgTable(
62
62
 
63
63
  ...timestamps,
64
64
  },
65
- (t) => ({
66
- clientIdUnique: uniqueIndex('client_id_user_id_unique').on(t.clientId, t.userId),
67
- titleIndex: index('agents_title_idx').on(t.title),
68
- descriptionIndex: index('agents_description_idx').on(t.description),
69
- }),
65
+ (t) => [
66
+ uniqueIndex('client_id_user_id_unique').on(t.clientId, t.userId),
67
+ index('agents_title_idx').on(t.title),
68
+ index('agents_description_idx').on(t.description),
69
+ ],
70
70
  );
71
71
 
72
72
  export const insertAgentSchema = createInsertSchema(agents);
@@ -90,9 +90,7 @@ export const agentsKnowledgeBases = pgTable(
90
90
 
91
91
  ...timestamps,
92
92
  },
93
- (t) => ({
94
- pk: primaryKey({ columns: [t.agentId, t.knowledgeBaseId] }),
95
- }),
93
+ (t) => [primaryKey({ columns: [t.agentId, t.knowledgeBaseId] })],
96
94
  );
97
95
 
98
96
  export const agentsFiles = pgTable(
@@ -111,7 +109,8 @@ export const agentsFiles = pgTable(
111
109
 
112
110
  ...timestamps,
113
111
  },
114
- (t) => ({
115
- pk: primaryKey({ columns: [t.fileId, t.agentId, t.userId] }),
116
- }),
112
+ (t) => [
113
+ primaryKey({ columns: [t.fileId, t.agentId, t.userId] }),
114
+ index('agents_files_agent_id_idx').on(t.agentId),
115
+ ],
117
116
  );
@@ -62,7 +62,10 @@ export const messageGroups = pgTable(
62
62
 
63
63
  ...timestamps,
64
64
  },
65
- (t) => [uniqueIndex('message_groups_client_id_user_id_unique').on(t.clientId, t.userId)],
65
+ (t) => [
66
+ uniqueIndex('message_groups_client_id_user_id_unique').on(t.clientId, t.userId),
67
+ index('message_groups_topic_id_idx').on(t.topicId),
68
+ ],
66
69
  );
67
70
 
68
71
  export const insertMessageGroupSchema = createInsertSchema(messageGroups);
@@ -130,6 +133,7 @@ export const messages = pgTable(
130
133
  index('messages_user_id_idx').on(table.userId),
131
134
  index('messages_session_id_idx').on(table.sessionId),
132
135
  index('messages_thread_id_idx').on(table.threadId),
136
+ index('messages_agent_id_idx').on(table.agentId),
133
137
  ],
134
138
  );
135
139
 
@@ -1,6 +1,6 @@
1
1
  /* eslint-disable sort-keys-fix/sort-keys-fix */
2
2
  import { relations } from 'drizzle-orm';
3
- import { pgTable, primaryKey, text, uuid, varchar } from 'drizzle-orm/pg-core';
3
+ import { index, pgTable, primaryKey, text, uuid, varchar } from 'drizzle-orm/pg-core';
4
4
 
5
5
  import { createdAt } from './_helpers';
6
6
  import { agents, agentsFiles, agentsKnowledgeBases } from './agent';
@@ -28,9 +28,11 @@ export const agentsToSessions = pgTable(
28
28
  .references(() => users.id, { onDelete: 'cascade' })
29
29
  .notNull(),
30
30
  },
31
- (t) => ({
32
- pk: primaryKey({ columns: [t.agentId, t.sessionId] }),
33
- }),
31
+ (t) => [
32
+ primaryKey({ columns: [t.agentId, t.sessionId] }),
33
+ index('agents_to_sessions_session_id_idx').on(t.sessionId),
34
+ index('agents_to_sessions_agent_id_idx').on(t.agentId),
35
+ ],
34
36
  );
35
37
 
36
38
  export const filesToSessions = pgTable(
@@ -70,6 +70,8 @@ export const sessions = pgTable(
70
70
 
71
71
  index('sessions_user_id_idx').on(t.userId),
72
72
  index('sessions_id_user_id_idx').on(t.id, t.userId),
73
+ index('sessions_user_id_updated_at_idx').on(t.userId, t.updatedAt),
74
+ index('sessions_group_id_idx').on(t.groupId),
73
75
  ],
74
76
  );
75
77
 
@@ -33,6 +33,8 @@ export const topics = pgTable(
33
33
  uniqueIndex('topics_client_id_user_id_unique').on(t.clientId, t.userId),
34
34
  index('topics_user_id_idx').on(t.userId),
35
35
  index('topics_id_user_id_idx').on(t.id, t.userId),
36
+ index('topics_session_id_idx').on(t.sessionId),
37
+ index('topics_group_id_idx').on(t.groupId),
36
38
  ],
37
39
  );
38
40
 
@@ -65,7 +67,10 @@ export const threads = pgTable(
65
67
  lastActiveAt: timestamptz('last_active_at').defaultNow(),
66
68
  ...timestamps,
67
69
  },
68
- (t) => [uniqueIndex('threads_client_id_user_id_unique').on(t.clientId, t.userId)],
70
+ (t) => [
71
+ uniqueIndex('threads_client_id_user_id_unique').on(t.clientId, t.userId),
72
+ index('threads_topic_id_idx').on(t.topicId),
73
+ ],
69
74
  );
70
75
 
71
76
  export type NewThread = typeof threads.$inferInsert;
@@ -197,69 +197,6 @@ const anthropicChatModels: AIChatModelCard[] = [
197
197
  },
198
198
  type: 'chat',
199
199
  },
200
- {
201
- abilities: {
202
- functionCall: true,
203
- search: true,
204
- vision: true,
205
- },
206
- contextWindowTokens: 200_000,
207
- description:
208
- 'Claude 3.5 Sonnet 提供了超越 Opus 的能力和比 Sonnet 更快的速度,同时保持与 Sonnet 相同的价格。Sonnet 特别擅长编程、数据科学、视觉处理、代理任务。',
209
- displayName: 'Claude 3.5 Sonnet (New)',
210
- id: 'claude-3-5-sonnet-20241022',
211
- maxOutput: 8192,
212
- pricing: {
213
- units: [
214
- { name: 'textInput_cacheRead', rate: 0.3, strategy: 'fixed', unit: 'millionTokens' },
215
- { name: 'textInput', rate: 3, strategy: 'fixed', unit: 'millionTokens' },
216
- { name: 'textOutput', rate: 15, strategy: 'fixed', unit: 'millionTokens' },
217
- {
218
- lookup: { prices: { '1h': 6, '5m': 3.75 }, pricingParams: ['ttl'] },
219
- name: 'textInput_cacheWrite',
220
- strategy: 'lookup',
221
- unit: 'millionTokens',
222
- },
223
- ],
224
- },
225
- releasedAt: '2024-10-22',
226
- settings: {
227
- extendParams: ['disableContextCaching'],
228
- searchImpl: 'params',
229
- },
230
- type: 'chat',
231
- },
232
- {
233
- abilities: {
234
- functionCall: true,
235
- vision: true,
236
- },
237
- contextWindowTokens: 200_000,
238
- description:
239
- 'Claude 3.5 Sonnet 提供了超越 Opus 的能力和比 Sonnet 更快的速度,同时保持与 Sonnet 相同的价格。Sonnet 特别擅长编程、数据科学、视觉处理、代理任务。',
240
- displayName: 'Claude 3.5 Sonnet (Old)',
241
- id: 'claude-3-5-sonnet-20240620',
242
- maxOutput: 8192,
243
- pricing: {
244
- units: [
245
- { name: 'textInput_cacheRead', rate: 0.3, strategy: 'fixed', unit: 'millionTokens' },
246
- { name: 'textInput', rate: 3, strategy: 'fixed', unit: 'millionTokens' },
247
- { name: 'textOutput', rate: 15, strategy: 'fixed', unit: 'millionTokens' },
248
- {
249
- lookup: { prices: { '1h': 6, '5m': 3.75 }, pricingParams: ['ttl'] },
250
- name: 'textInput_cacheWrite',
251
- strategy: 'lookup',
252
- unit: 'millionTokens',
253
- },
254
- ],
255
- },
256
- releasedAt: '2024-06-20',
257
- settings: {
258
- extendParams: ['disableContextCaching'],
259
- searchImpl: 'params',
260
- },
261
- type: 'chat',
262
- },
263
200
  {
264
201
  abilities: {
265
202
  functionCall: true,
@@ -2393,61 +2393,6 @@ const higressChatModels: AIChatModelCard[] = [
2393
2393
  releasedAt: '2024-11-05',
2394
2394
  type: 'chat',
2395
2395
  },
2396
- {
2397
- abilities: {
2398
- functionCall: true,
2399
- vision: true,
2400
- },
2401
- contextWindowTokens: 200_000,
2402
- description:
2403
- 'Claude 3.5 Sonnet 提供了超越 Opus 的能力和比 Sonnet 更快的速度,同时保持与 Sonnet 相同的价格。Sonnet 特别擅长编程、数据科学、视觉处理、代理任务。',
2404
- displayName: 'Claude 3.5 Sonnet',
2405
- enabled: true,
2406
- id: 'claude-3-5-sonnet-20241022',
2407
- maxOutput: 8192,
2408
- pricing: {
2409
- units: [
2410
- { name: 'textInput_cacheRead', rate: 0.3, strategy: 'fixed', unit: 'millionTokens' },
2411
- { name: 'textInput', rate: 3, strategy: 'fixed', unit: 'millionTokens' },
2412
- { name: 'textOutput', rate: 15, strategy: 'fixed', unit: 'millionTokens' },
2413
- {
2414
- lookup: { prices: { '5m': 3.75 }, pricingParams: ['ttl'] },
2415
- name: 'textInput_cacheWrite',
2416
- strategy: 'lookup',
2417
- unit: 'millionTokens',
2418
- },
2419
- ],
2420
- },
2421
- releasedAt: '2024-10-22',
2422
- type: 'chat',
2423
- },
2424
- {
2425
- abilities: {
2426
- functionCall: true,
2427
- vision: true,
2428
- },
2429
- contextWindowTokens: 200_000,
2430
- description:
2431
- 'Claude 3.5 Sonnet 提供了超越 Opus 的能力和比 Sonnet 更快的速度,同时保持与 Sonnet 相同的价格。Sonnet 特别擅长编程、数据科学、视觉处理、代理任务。',
2432
- displayName: 'Claude 3.5 Sonnet 0620',
2433
- id: 'claude-3-5-sonnet-20240620',
2434
- maxOutput: 8192,
2435
- pricing: {
2436
- units: [
2437
- { name: 'textInput_cacheRead', rate: 0.3, strategy: 'fixed', unit: 'millionTokens' },
2438
- { name: 'textInput', rate: 3, strategy: 'fixed', unit: 'millionTokens' },
2439
- { name: 'textOutput', rate: 15, strategy: 'fixed', unit: 'millionTokens' },
2440
- {
2441
- lookup: { prices: { '5m': 3.75 }, pricingParams: ['ttl'] },
2442
- name: 'textInput_cacheWrite',
2443
- strategy: 'lookup',
2444
- unit: 'millionTokens',
2445
- },
2446
- ],
2447
- },
2448
- releasedAt: '2024-06-20',
2449
- type: 'chat',
2450
- },
2451
2396
  {
2452
2397
  abilities: {
2453
2398
  functionCall: true,
@@ -3,6 +3,27 @@ import { AIChatModelCard } from '../types/aiModel';
3
3
  // https://cloud.infini-ai.com/genstudio/model
4
4
 
5
5
  const infiniaiChatModels: AIChatModelCard[] = [
6
+ {
7
+ abilities: {
8
+ functionCall: true,
9
+ reasoning: true,
10
+ },
11
+ contextWindowTokens: 200_000,
12
+ description:
13
+ 'MiniMax-M2 是一款专为编码与智能体工作流优化的专家混合(MoE)语言模型,具有约 230B 总参数与约 10B 活跃参数。它在保持强通用智能的同时,针对多文件编辑、代码-运行-修复闭环、测试校验修复等开发者场景进行深度增强,在终端、IDE 与 CI 等真实环境中表现稳定、高效。',
14
+ displayName: 'MiniMax M2',
15
+ enabled: true,
16
+ id: 'minimax-m2',
17
+ maxOutput: 200_000,
18
+ pricing: {
19
+ currency: 'CNY',
20
+ units: [
21
+ { name: 'textInput', rate: 2.1, strategy: 'fixed', unit: 'millionTokens' },
22
+ { name: 'textOutput', rate: 8.4, strategy: 'fixed', unit: 'millionTokens' },
23
+ ],
24
+ },
25
+ type: 'chat',
26
+ },
6
27
  {
7
28
  abilities: {
8
29
  functionCall: true,
@@ -1,6 +1,19 @@
1
1
  import { AIChatModelCard } from '../types/aiModel';
2
2
 
3
3
  const ollamaCloudModels: AIChatModelCard[] = [
4
+ {
5
+ abilities: {
6
+ functionCall: true,
7
+ reasoning: true,
8
+ },
9
+ contextWindowTokens: 200_000,
10
+ description:
11
+ 'MiniMax M2 是专为编码和代理工作流程构建的高效大型语言模型。',
12
+ displayName: 'MiniMax M2',
13
+ enabled: true,
14
+ id: 'minimax-m2',
15
+ type: 'chat',
16
+ },
4
17
  {
5
18
  abilities: {
6
19
  functionCall: true,
@@ -145,6 +145,25 @@ const siliconcloudChatModels: AIChatModelCard[] = [
145
145
  releasedAt: '2025-09-01',
146
146
  type: 'chat',
147
147
  },
148
+ {
149
+ abilities: {
150
+ functionCall: true,
151
+ },
152
+ contextWindowTokens: 131_072,
153
+ description:
154
+ 'KAT-Dev(32B)是一款专为软件工程任务设计的开源 32B 参数模型。在 SWE-Bench Verified 基准测试中,它取得了 62.4% 的解决率,在所有不同规模的开源模型中排名第五。该模型通过多个阶段进行优化,包括中间训练、监督微调(SFT)与强化学习(RL),旨在为代码补全、缺陷修复、代码评审等复杂编程任务提供强大支持。',
155
+ displayName: 'KAT-Dev 32B',
156
+ id: 'Kwaipilot/KAT-Dev',
157
+ pricing: {
158
+ currency: 'CNY',
159
+ units: [
160
+ { name: 'textInput', rate: 1, strategy: 'fixed', unit: 'millionTokens' },
161
+ { name: 'textOutput', rate: 4, strategy: 'fixed', unit: 'millionTokens' },
162
+ ],
163
+ },
164
+ releasedAt: '2025-09-27',
165
+ type: 'chat',
166
+ },
148
167
  {
149
168
  abilities: {
150
169
  functionCall: true,
@@ -145,25 +145,6 @@ exports[`OpenAIResponsesStream > Reasoning > summary 1`] = `
145
145
  ]
146
146
  `;
147
147
 
148
- exports[`OpenAIResponsesStream > should handle chunk errors in catch block 1`] = `
149
- [
150
- "id: resp_error_catch
151
- ",
152
- "event: data
153
- ",
154
- "data: "in_progress"
155
-
156
- ",
157
- "id: undefined
158
- ",
159
- "event: reasoning
160
- ",
161
- "data: undefined
162
-
163
- ",
164
- ]
165
- `;
166
-
167
148
  exports[`OpenAIResponsesStream > should handle chunks with undefined values gracefully 1`] = `
168
149
  [
169
150
  "id: resp_undefined_vals
@@ -546,25 +527,6 @@ exports[`OpenAIResponsesStream > should handle response.reasoning_summary_text.d
546
527
  ]
547
528
  `;
548
529
 
549
- exports[`OpenAIResponsesStream > should handle stream chunk transformation error with null access 1`] = `
550
- [
551
- "id: resp_error_test
552
- ",
553
- "event: data
554
- ",
555
- "data: "in_progress"
556
-
557
- ",
558
- "id: null
559
- ",
560
- "event: text
561
- ",
562
- "data: "test"
563
-
564
- ",
565
- ]
566
- `;
567
-
568
530
  exports[`OpenAIResponsesStream > should handle unknown chunk type as data 1`] = `
569
531
  [
570
532
  "id: resp_unknown
@@ -17,11 +17,11 @@ export const LobeMinimaxAI = createOpenAICompatibleRuntime({
17
17
 
18
18
  const minimaxTools = enabledSearch
19
19
  ? [
20
- ...(tools || []),
21
- {
22
- type: 'web_search',
23
- },
24
- ]
20
+ ...(tools || []),
21
+ {
22
+ type: 'web_search',
23
+ },
24
+ ]
25
25
  : tools;
26
26
 
27
27
  // Resolve parameters with constraints
@@ -830,9 +830,9 @@ describe('LobeSearch1API - custom features', () => {
830
830
  it('should handle mix of known and unknown models', async () => {
831
831
  mockClient.models.list.mockResolvedValue({
832
832
  data: [
833
- { id: 'gpt-4o-mini' },
833
+ { id: 'gpt-4o' },
834
834
  { id: 'unknown-model-1' },
835
- { id: 'claude-3-5-sonnet-20241022' },
835
+ { id: 'gpt-4o-mini' },
836
836
  { id: 'unknown-model-2' },
837
837
  ],
838
838
  });
@@ -3,36 +3,55 @@ import { NetworkConnectionError, PageNotFoundError, TimeoutError } from '../util
3
3
  import { DEFAULT_TIMEOUT, withTimeout } from '../utils/withTimeout';
4
4
 
5
5
  interface FirecrawlMetadata {
6
- description: string;
7
- keywords: string;
8
- language: string;
6
+ description?: string;
7
+ error?: string;
8
+ keywords?: string;
9
+ language?: string;
9
10
  ogDescription?: string;
10
11
  ogImage?: string;
11
12
  ogLocaleAlternate?: string[];
12
13
  ogSiteName?: string;
13
14
  ogTitle?: string;
14
15
  ogUrl?: string;
15
- robots: string;
16
- statusCode: number;
16
+ robots?: string;
17
17
  sourceURL: string;
18
- title: string;
18
+ statusCode: number;
19
+ title?: string;
19
20
  }
20
21
 
21
22
  interface FirecrawlResults {
23
+ actions?: {
24
+ javascriptReturns?: Array<{ type: string; value: any }>;
25
+ pdfs?: string[];
26
+ scrapes?: Array<{ html: string; url: string }>;
27
+ screenshots?: string[];
28
+ };
29
+ changeTracking?: {
30
+ changeStatus?: string;
31
+ diff?: string;
32
+ json?: Record<string, any>;
33
+ previousScrapeAt?: string;
34
+ visibility?: string;
35
+ };
22
36
  html?: string;
37
+ links?: string[];
23
38
  markdown?: string;
24
39
  metadata: FirecrawlMetadata;
40
+ rawHtml?: string;
41
+ screenshot?: string;
42
+ summary?: string;
43
+ warning?: string;
25
44
  }
26
45
 
27
46
  interface FirecrawlResponse {
28
- success: boolean;
29
47
  data: FirecrawlResults;
48
+ success: boolean;
30
49
  }
31
50
 
32
51
  export const firecrawl: CrawlImpl = async (url) => {
33
52
  // Get API key from environment variable
34
53
  const apiKey = process.env.FIRECRAWL_API_KEY;
35
- const baseUrl = process.env.FIRECRAWL_URL || 'https://api.firecrawl.dev/v1';
54
+ const baseUrl = process.env.FIRECRAWL_URL || 'https://api.firecrawl.dev/v2';
36
55
 
37
56
  let res: Response;
38
57
 
@@ -40,7 +59,7 @@ export const firecrawl: CrawlImpl = async (url) => {
40
59
  res = await withTimeout(
41
60
  fetch(`${baseUrl}/scrape`, {
42
61
  body: JSON.stringify({
43
- formats: ["markdown"], // ["markdown", "html"]
62
+ formats: ['markdown'], // ["markdown", "html"]
44
63
  url,
45
64
  }),
46
65
  headers: {
@@ -75,6 +94,14 @@ export const firecrawl: CrawlImpl = async (url) => {
75
94
  try {
76
95
  const data = (await res.json()) as FirecrawlResponse;
77
96
 
97
+ if (data.data.warning) {
98
+ console.warn('[Firecrawl] Warning:', data.data.warning);
99
+ }
100
+
101
+ if (data.data.metadata.error) {
102
+ console.error('[Firecrawl] Metadata error:', data.data.metadata.error);
103
+ }
104
+
78
105
  // Check if content is empty or too short
79
106
  if (!data.data.markdown || data.data.markdown.length < 100) {
80
107
  return;
@@ -83,14 +110,14 @@ export const firecrawl: CrawlImpl = async (url) => {
83
110
  return {
84
111
  content: data.data.markdown,
85
112
  contentType: 'text',
86
- description: data.data.metadata.description,
113
+ description: data.data.metadata.description || '',
87
114
  length: data.data.markdown.length,
88
115
  siteName: new URL(url).hostname,
89
- title: data.data.metadata.title,
116
+ title: data.data.metadata.title || '',
90
117
  url: url,
91
118
  } satisfies CrawlSuccessResult;
92
119
  } catch (error) {
93
- console.error(error);
120
+ console.error('[Firecrawl] Parse error:', error);
94
121
  }
95
122
 
96
123
  return;
@@ -17,13 +17,14 @@ const isDesktop = process.env.NEXT_PUBLIC_IS_DESKTOP_APP === '1';
17
17
  const runMigrations = async () => {
18
18
  const { serverDB } = await import('../../packages/database/src/server');
19
19
 
20
+ const time = Date.now();
20
21
  if (process.env.DATABASE_DRIVER === 'node') {
21
22
  await nodeMigrate(serverDB, { migrationsFolder });
22
23
  } else {
23
24
  await neonMigrate(serverDB, { migrationsFolder });
24
25
  }
25
26
 
26
- console.log('✅ database migration pass.');
27
+ console.log('✅ database migration pass. use: %s ms', Date.now() - time);
27
28
  // eslint-disable-next-line unicorn/no-process-exit
28
29
  process.exit(0);
29
30
  };