@lobehub/lobehub 2.0.0-next.110 → 2.0.0-next.112

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 (40) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/changelog/v1.json +18 -0
  3. package/docs/development/database-schema.dbml +2 -1
  4. package/package.json +1 -1
  5. package/packages/context-engine/src/index.ts +1 -1
  6. package/packages/context-engine/src/providers/KnowledgeInjector.ts +78 -0
  7. package/packages/context-engine/src/providers/index.ts +2 -0
  8. package/packages/database/migrations/0047_add_slug_document.sql +1 -5
  9. package/packages/database/migrations/meta/0047_snapshot.json +30 -14
  10. package/packages/database/migrations/meta/_journal.json +1 -1
  11. package/packages/database/src/core/migrations.json +3 -3
  12. package/packages/database/src/models/__tests__/agent.test.ts +172 -3
  13. package/packages/database/src/models/__tests__/userMemories.test.ts +1382 -0
  14. package/packages/database/src/models/agent.ts +22 -1
  15. package/packages/database/src/models/userMemory.ts +993 -0
  16. package/packages/database/src/schemas/file.ts +5 -10
  17. package/packages/database/src/schemas/userMemories.ts +22 -5
  18. package/packages/model-bank/src/aiModels/qwen.ts +41 -3
  19. package/packages/model-runtime/src/providers/qwen/index.ts +9 -3
  20. package/packages/prompts/src/prompts/files/__snapshots__/knowledgeBase.test.ts.snap +103 -0
  21. package/packages/prompts/src/prompts/files/index.ts +3 -0
  22. package/packages/prompts/src/prompts/files/knowledgeBase.test.ts +167 -0
  23. package/packages/prompts/src/prompts/files/knowledgeBase.ts +85 -0
  24. package/packages/types/src/files/index.ts +1 -0
  25. package/packages/types/src/index.ts +1 -0
  26. package/packages/types/src/knowledgeBase/index.ts +1 -0
  27. package/packages/types/src/userMemory/index.ts +3 -0
  28. package/packages/types/src/userMemory/layers.ts +54 -0
  29. package/packages/types/src/userMemory/shared.ts +64 -0
  30. package/packages/types/src/userMemory/tools.ts +240 -0
  31. package/src/features/ChatList/Messages/index.tsx +16 -19
  32. package/src/features/ChatList/components/ContextMenu.tsx +23 -16
  33. package/src/helpers/toolEngineering/index.ts +5 -9
  34. package/src/hooks/useQueryParam.ts +24 -22
  35. package/src/server/routers/async/file.ts +2 -7
  36. package/src/server/routers/lambda/chunk.ts +6 -1
  37. package/src/services/chat/contextEngineering.ts +19 -0
  38. package/src/store/agent/slices/chat/selectors/agent.ts +4 -0
  39. package/src/store/chat/slices/builtinTool/actions/knowledgeBase.ts +5 -16
  40. package/src/tools/knowledge-base/ExecutionRuntime/index.ts +3 -3
@@ -1,6 +1,7 @@
1
1
  /* eslint-disable sort-keys-fix/sort-keys-fix */
2
2
  import { isNotNull } from 'drizzle-orm';
3
3
  import {
4
+ AnyPgColumn,
4
5
  boolean,
5
6
  index,
6
7
  integer,
@@ -41,7 +42,6 @@ export type GlobalFileItem = typeof globalFiles.$inferSelect;
41
42
  /**
42
43
  * Documents table - Stores file content or web search results
43
44
  */
44
- // @ts-ignore
45
45
  export const documents = pgTable(
46
46
  'documents',
47
47
  {
@@ -72,15 +72,12 @@ export const documents = pgTable(
72
72
  source: text('source').notNull(), // File path or web URL
73
73
 
74
74
  // Associated file (optional)
75
- // Forward reference to files table defined below
75
+ // forward reference needs AnyPgColumn to avoid circular type inference
76
76
  // eslint-disable-next-line @typescript-eslint/no-use-before-define
77
- // @ts-expect-error - files is defined later in this file, forward reference is valid at runtime
78
- // eslint-disable-next-line @typescript-eslint/no-use-before-define
79
- fileId: text('file_id').references(() => files.id, { onDelete: 'set null' }),
77
+ fileId: text('file_id').references((): AnyPgColumn => files.id, { onDelete: 'set null' }),
80
78
 
81
79
  // Parent document (for folder hierarchy structure)
82
- // @ts-ignore
83
- parentId: varchar('parent_id', { length: 255 }).references(() => documents.id, {
80
+ parentId: varchar('parent_id', { length: 255 }).references((): AnyPgColumn => documents.id, {
84
81
  onDelete: 'set null',
85
82
  }),
86
83
 
@@ -113,7 +110,6 @@ export type NewDocument = typeof documents.$inferInsert;
113
110
  export type DocumentItem = typeof documents.$inferSelect;
114
111
  export const insertDocumentSchema = createInsertSchema(documents);
115
112
 
116
- // @ts-ignore
117
113
  export const files = pgTable(
118
114
  'files',
119
115
  {
@@ -140,8 +136,7 @@ export const files = pgTable(
140
136
  source: text('source').$type<FileSource>(),
141
137
 
142
138
  // Parent Folder or Document
143
- // @ts-ignore
144
- parentId: varchar('parent_id', { length: 255 }).references(() => documents.id, {
139
+ parentId: varchar('parent_id', { length: 255 }).references((): AnyPgColumn => documents.id, {
145
140
  onDelete: 'set null',
146
141
  }),
147
142
 
@@ -17,7 +17,7 @@ export const userMemories = pgTable(
17
17
  memoryCategory: varchar255('memory_category'),
18
18
  memoryLayer: varchar255('memory_layer'),
19
19
  memoryType: varchar255('memory_type'),
20
- metadata: jsonb('metadata'),
20
+ metadata: jsonb('metadata').$type<Record<string, unknown>>(),
21
21
  tags: text('tags').array(),
22
22
 
23
23
  title: varchar255('title'),
@@ -55,7 +55,7 @@ export const userMemoriesContexts = pgTable(
55
55
  userId: text('user_id').references(() => users.id, { onDelete: 'cascade' }),
56
56
  userMemoryIds: jsonb('user_memory_ids'),
57
57
 
58
- metadata: jsonb('metadata'),
58
+ metadata: jsonb('metadata').$type<Record<string, unknown>>(),
59
59
  tags: text('tags').array(),
60
60
 
61
61
  associatedObjects: jsonb('associated_objects'),
@@ -99,7 +99,7 @@ export const userMemoriesPreferences = pgTable(
99
99
  onDelete: 'cascade',
100
100
  }),
101
101
 
102
- metadata: jsonb('metadata'),
102
+ metadata: jsonb('metadata').$type<Record<string, unknown>>(),
103
103
  tags: text('tags').array(),
104
104
 
105
105
  conclusionDirectives: text('conclusion_directives'),
@@ -132,7 +132,7 @@ export const userMemoriesIdentities = pgTable(
132
132
  onDelete: 'cascade',
133
133
  }),
134
134
 
135
- metadata: jsonb('metadata'),
135
+ metadata: jsonb('metadata').$type<Record<string, unknown>>(),
136
136
  tags: text('tags').array(),
137
137
 
138
138
  type: varchar255('type'),
@@ -165,7 +165,7 @@ export const userMemoriesExperiences = pgTable(
165
165
  onDelete: 'cascade',
166
166
  }),
167
167
 
168
- metadata: jsonb('metadata'),
168
+ metadata: jsonb('metadata').$type<Record<string, unknown>>(),
169
169
  tags: text('tags').array(),
170
170
 
171
171
  type: varchar255('type'),
@@ -200,16 +200,33 @@ export const userMemoriesExperiences = pgTable(
200
200
  );
201
201
 
202
202
  export type UserMemoryItem = typeof userMemories.$inferSelect;
203
+ export type UserMemoryItemWithoutVectors = Omit<
204
+ UserMemoryItem,
205
+ 'summaryVector1024' | 'detailsVector1024'
206
+ >;
203
207
  export type NewUserMemory = typeof userMemories.$inferInsert;
204
208
 
205
209
  export type UserMemoryPreference = typeof userMemoriesPreferences.$inferSelect;
210
+ export type UserMemoryPreferencesWithoutVectors = Omit<
211
+ UserMemoryPreference,
212
+ 'conclusionDirectivesVector'
213
+ >;
206
214
  export type NewUserMemoryPreference = typeof userMemoriesPreferences.$inferInsert;
207
215
 
208
216
  export type UserMemoryContext = typeof userMemoriesContexts.$inferSelect;
217
+ export type UserMemoryContextsWithoutVectors = Omit<
218
+ UserMemoryContext,
219
+ 'titleVector' | 'descriptionVector'
220
+ >;
209
221
  export type NewUserMemoryContext = typeof userMemoriesContexts.$inferInsert;
210
222
 
211
223
  export type UserMemoryIdentity = typeof userMemoriesIdentities.$inferSelect;
224
+ export type UserMemoryIdentitiesWithoutVectors = Omit<UserMemoryIdentity, 'descriptionVector'>;
212
225
  export type NewUserMemoryIdentity = typeof userMemoriesIdentities.$inferInsert;
213
226
 
214
227
  export type UserMemoryExperience = typeof userMemoriesExperiences.$inferSelect;
228
+ export type UserMemoryExperiencesWithoutVectors = Omit<
229
+ UserMemoryExperience,
230
+ 'situationVector' | 'actionVector' | 'keyLearningVector'
231
+ >;
215
232
  export type NewUserMemoryExperience = typeof userMemoriesExperiences.$inferInsert;
@@ -3,6 +3,31 @@ import { AIChatModelCard, AIImageModelCard } from '../types/aiModel';
3
3
  // https://help.aliyun.com/zh/model-studio/models?spm=a2c4g.11186623
4
4
 
5
5
  const qwenChatModels: AIChatModelCard[] = [
6
+ {
7
+ abilities: {
8
+ functionCall: true,
9
+ reasoning: true,
10
+ },
11
+ contextWindowTokens: 262_144,
12
+ description:
13
+ 'kimi-k2-thinking模型是月之暗面提供的具有通用 Agentic能力和推理能力的思考模型,它擅长深度推理,并可通过多步工具调用,帮助解决各类难题。',
14
+ displayName: 'Kimi K2 Thinking',
15
+ id: 'kimi-k2-thinking',
16
+ maxOutput: 16_384,
17
+ organization: 'Qwen',
18
+ pricing: {
19
+ currency: 'CNY',
20
+ units: [
21
+ { name: 'textInput', rate: 4, strategy: 'fixed', unit: 'millionTokens' },
22
+ { name: 'textOutput', rate: 16, strategy: 'fixed', unit: 'millionTokens' },
23
+ ],
24
+ },
25
+ releasedAt: '2025-11-10',
26
+ settings: {
27
+ extendParams: ['reasoningBudgetToken'],
28
+ },
29
+ type: 'chat',
30
+ },
6
31
  {
7
32
  abilities: {
8
33
  reasoning: true,
@@ -109,6 +134,7 @@ const qwenChatModels: AIChatModelCard[] = [
109
134
  {
110
135
  abilities: {
111
136
  reasoning: true,
137
+ search: true,
112
138
  },
113
139
  contextWindowTokens: 131_072,
114
140
  description:
@@ -131,6 +157,7 @@ const qwenChatModels: AIChatModelCard[] = [
131
157
  {
132
158
  abilities: {
133
159
  reasoning: true,
160
+ search: true,
134
161
  },
135
162
  contextWindowTokens: 131_072,
136
163
  description: 'DeepSeek V3.1 模型为混合推理架构模型,同时支持思考模式与非思考模式。',
@@ -151,6 +178,7 @@ const qwenChatModels: AIChatModelCard[] = [
151
178
  },
152
179
  {
153
180
  abilities: {
181
+ functionCall: true,
154
182
  search: true,
155
183
  },
156
184
  contextWindowTokens: 131_072,
@@ -1231,6 +1259,7 @@ const qwenChatModels: AIChatModelCard[] = [
1231
1259
  },
1232
1260
  {
1233
1261
  abilities: {
1262
+ reasoning: true,
1234
1263
  vision: true,
1235
1264
  },
1236
1265
  config: {
@@ -1239,7 +1268,7 @@ const qwenChatModels: AIChatModelCard[] = [
1239
1268
  contextWindowTokens: 65_536,
1240
1269
  description:
1241
1270
  'Qwen-Omni 模型能够接收文本、图片、音频、视频等多种模态的组合输入,并生成文本或语音形式的回复, 提供多种拟人音色,支持多语言和方言的语音输出,可应用于文本创作、视觉识别、语音助手等场景。',
1242
- displayName: 'Qwen Omni Turbo',
1271
+ displayName: 'Qwen3 Omni Flash',
1243
1272
  id: 'qwen3-omni-flash',
1244
1273
  maxOutput: 16_384,
1245
1274
  organization: 'Qwen',
@@ -1432,6 +1461,9 @@ const qwenChatModels: AIChatModelCard[] = [
1432
1461
  { name: 'textOutput', rate: 5, strategy: 'fixed', unit: 'millionTokens' },
1433
1462
  ],
1434
1463
  },
1464
+ settings: {
1465
+ extendParams: ['reasoningBudgetToken'],
1466
+ },
1435
1467
  type: 'chat',
1436
1468
  },
1437
1469
  {
@@ -1473,7 +1505,7 @@ const qwenChatModels: AIChatModelCard[] = [
1473
1505
  ],
1474
1506
  },
1475
1507
  settings: {
1476
- extendParams: ['enableReasoning', 'reasoningBudgetToken'],
1508
+ extendParams: ['reasoningBudgetToken'],
1477
1509
  },
1478
1510
  type: 'chat',
1479
1511
  },
@@ -1517,7 +1549,7 @@ const qwenChatModels: AIChatModelCard[] = [
1517
1549
  ],
1518
1550
  },
1519
1551
  settings: {
1520
- extendParams: ['enableReasoning', 'reasoningBudgetToken'],
1552
+ extendParams: ['reasoningBudgetToken'],
1521
1553
  },
1522
1554
  type: 'chat',
1523
1555
  },
@@ -1976,7 +2008,9 @@ const qwenChatModels: AIChatModelCard[] = [
1976
2008
  },
1977
2009
  {
1978
2010
  abilities: {
2011
+ functionCall: true,
1979
2012
  reasoning: true,
2013
+ search: true,
1980
2014
  },
1981
2015
  contextWindowTokens: 131_072,
1982
2016
  description:
@@ -1996,6 +2030,10 @@ const qwenChatModels: AIChatModelCard[] = [
1996
2030
  type: 'chat',
1997
2031
  },
1998
2032
  {
2033
+ abilities: {
2034
+ functionCall: true,
2035
+ search: true,
2036
+ },
1999
2037
  contextWindowTokens: 65_536,
2000
2038
  description:
2001
2039
  'DeepSeek-V3 为自研 MoE 模型,671B 参数,激活 37B,在 14.8T token 上进行了预训练,在长文本、代码、数学、百科、中文能力上表现优秀。',
@@ -51,9 +51,15 @@ export const LobeQwenAI = createOpenAICompatibleRuntime({
51
51
  thinking_budget:
52
52
  thinking?.budget_tokens === 0 ? 0 : thinking?.budget_tokens || undefined,
53
53
  }
54
- : ['qwen3', 'qwen-turbo', 'qwen-plus', 'deepseek-v3.1'].some((keyword) =>
55
- model.toLowerCase().includes(keyword),
56
- )
54
+ : [
55
+ 'qwen3',
56
+ 'qwen-turbo',
57
+ 'qwen-plus',
58
+ 'qwen-flash',
59
+ 'deepseek-v3.1',
60
+ 'deepseek-v3.2',
61
+ 'glm',
62
+ ].some((keyword) => model.toLowerCase().includes(keyword))
57
63
  ? {
58
64
  enable_thinking: thinking !== undefined ? thinking.type === 'enabled' : false,
59
65
  thinking_budget:
@@ -0,0 +1,103 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`promptAgentKnowledge > should format both files and knowledge bases 1`] = `
4
+ "<agent_knowledge>
5
+ <instruction>The following files and knowledge bases are available. For files, refer to their content directly. For knowledge bases, use the searchKnowledgeBase tool to find relevant information.</instruction>
6
+ <files totalCount="1">
7
+ <file id="file1" name="readme.md">
8
+ File content here
9
+ </file>
10
+ </files>
11
+ <knowledge_bases totalCount="1">
12
+ <knowledge_base id="kb1" name="Internal Docs" description="Company knowledge base" />
13
+ </knowledge_bases>
14
+ </agent_knowledge>"
15
+ `;
16
+
17
+ exports[`promptAgentKnowledge > should format only files when no knowledge bases 1`] = `
18
+ "<agent_knowledge>
19
+ <instruction>The following files are available. Refer to their content directly to answer questions. No knowledge bases are associated.</instruction>
20
+ <files totalCount="2">
21
+ <file id="file1" name="doc1.txt">
22
+ This is the content of document 1
23
+ </file>
24
+ <file id="file2" name="doc2.md">
25
+ This is the content of document 2
26
+ </file>
27
+ </files>
28
+ </agent_knowledge>"
29
+ `;
30
+
31
+ exports[`promptAgentKnowledge > should format only knowledge bases when no files 1`] = `
32
+ "<agent_knowledge>
33
+ <instruction>The following knowledge bases are available for semantic search. Use the searchKnowledgeBase tool to find relevant information.</instruction>
34
+ <knowledge_bases totalCount="2">
35
+ <knowledge_base id="kb1" name="Documentation" description="API documentation" />
36
+ <knowledge_base id="kb2" name="FAQs" />
37
+ </knowledge_bases>
38
+ </agent_knowledge>"
39
+ `;
40
+
41
+ exports[`promptAgentKnowledge > should handle file with error 1`] = `
42
+ "<agent_knowledge>
43
+ <instruction>The following files are available. Refer to their content directly to answer questions. No knowledge bases are associated.</instruction>
44
+ <files totalCount="1">
45
+ <file id="file1" name="missing.txt" error="File not found" />
46
+ </files>
47
+ </agent_knowledge>"
48
+ `;
49
+
50
+ exports[`promptAgentKnowledge > should handle file with multiline content 1`] = `
51
+ "<agent_knowledge>
52
+ <instruction>The following files are available. Refer to their content directly to answer questions. No knowledge bases are associated.</instruction>
53
+ <files totalCount="1">
54
+ <file id="file1" name="multiline.txt">
55
+ Line 1
56
+ Line 2
57
+ Line 3
58
+
59
+ Line 5 with gap
60
+ </file>
61
+ </files>
62
+ </agent_knowledge>"
63
+ `;
64
+
65
+ exports[`promptAgentKnowledge > should handle file with special characters in filename 1`] = `
66
+ "<agent_knowledge>
67
+ <instruction>The following files are available. Refer to their content directly to answer questions. No knowledge bases are associated.</instruction>
68
+ <files totalCount="1">
69
+ <file id="file1" name="file with spaces & special-chars.txt">
70
+ Special content
71
+ </file>
72
+ </files>
73
+ </agent_knowledge>"
74
+ `;
75
+
76
+ exports[`promptAgentKnowledge > should handle knowledge base without description 1`] = `
77
+ "<agent_knowledge>
78
+ <instruction>The following knowledge bases are available for semantic search. Use the searchKnowledgeBase tool to find relevant information.</instruction>
79
+ <knowledge_bases totalCount="1">
80
+ <knowledge_base id="kb1" name="Simple KB" />
81
+ </knowledge_bases>
82
+ </agent_knowledge>"
83
+ `;
84
+
85
+ exports[`promptAgentKnowledge > should handle multiple files and multiple knowledge bases 1`] = `
86
+ "<agent_knowledge>
87
+ <instruction>The following files and knowledge bases are available. For files, refer to their content directly. For knowledge bases, use the searchKnowledgeBase tool to find relevant information.</instruction>
88
+ <files totalCount="3">
89
+ <file id="file1" name="first.txt">
90
+ Content of first file
91
+ </file>
92
+ <file id="file2" name="second.md">
93
+ Content of second file
94
+ </file>
95
+ <file id="file3" name="broken.pdf" error="Parse error" />
96
+ </files>
97
+ <knowledge_bases totalCount="3">
98
+ <knowledge_base id="kb1" name="Tech Docs" description="Technical documentation" />
99
+ <knowledge_base id="kb2" name="User Guides" />
100
+ <knowledge_base id="kb3" name="FAQ Database" description="Frequently asked questions" />
101
+ </knowledge_bases>
102
+ </agent_knowledge>"
103
+ `;
@@ -4,6 +4,9 @@ import { filePrompts } from './file';
4
4
  import { imagesPrompts } from './image';
5
5
  import { videosPrompts } from './video';
6
6
 
7
+ export type { KnowledgeBaseInfo, PromptKnowledgeOptions } from './knowledgeBase';
8
+ export { promptAgentKnowledge } from './knowledgeBase';
9
+
7
10
  export const filesPrompts = ({
8
11
  imageList,
9
12
  fileList,
@@ -0,0 +1,167 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import type { FileContent } from '../knowledgeBaseQA';
4
+ import { promptAgentKnowledge } from './knowledgeBase';
5
+ import type { KnowledgeBaseInfo } from './knowledgeBase';
6
+
7
+ describe('promptAgentKnowledge', () => {
8
+ it('should return empty string when no files and no knowledge bases', () => {
9
+ const result = promptAgentKnowledge({});
10
+ expect(result).toBe('');
11
+ });
12
+
13
+ it('should format only files when no knowledge bases', () => {
14
+ const fileContents: FileContent[] = [
15
+ {
16
+ content: 'This is the content of document 1',
17
+ fileId: 'file1',
18
+ filename: 'doc1.txt',
19
+ },
20
+ {
21
+ content: 'This is the content of document 2',
22
+ fileId: 'file2',
23
+ filename: 'doc2.md',
24
+ },
25
+ ];
26
+
27
+ const result = promptAgentKnowledge({ fileContents });
28
+ expect(result).toMatchSnapshot();
29
+ });
30
+
31
+ it('should format only knowledge bases when no files', () => {
32
+ const knowledgeBases: KnowledgeBaseInfo[] = [
33
+ {
34
+ description: 'API documentation',
35
+ id: 'kb1',
36
+ name: 'Documentation',
37
+ },
38
+ {
39
+ description: null,
40
+ id: 'kb2',
41
+ name: 'FAQs',
42
+ },
43
+ ];
44
+
45
+ const result = promptAgentKnowledge({ knowledgeBases });
46
+ expect(result).toMatchSnapshot();
47
+ });
48
+
49
+ it('should format both files and knowledge bases', () => {
50
+ const fileContents: FileContent[] = [
51
+ {
52
+ content: 'File content here',
53
+ fileId: 'file1',
54
+ filename: 'readme.md',
55
+ },
56
+ ];
57
+
58
+ const knowledgeBases: KnowledgeBaseInfo[] = [
59
+ {
60
+ description: 'Company knowledge base',
61
+ id: 'kb1',
62
+ name: 'Internal Docs',
63
+ },
64
+ ];
65
+
66
+ const result = promptAgentKnowledge({ fileContents, knowledgeBases });
67
+ expect(result).toMatchSnapshot();
68
+ });
69
+
70
+ it('should handle file with error', () => {
71
+ const fileContents: FileContent[] = [
72
+ {
73
+ content: '',
74
+ error: 'File not found',
75
+ fileId: 'file1',
76
+ filename: 'missing.txt',
77
+ },
78
+ ];
79
+
80
+ const result = promptAgentKnowledge({ fileContents });
81
+ expect(result).toMatchSnapshot();
82
+ });
83
+
84
+ it('should handle multiple files and multiple knowledge bases', () => {
85
+ const fileContents: FileContent[] = [
86
+ {
87
+ content: 'Content of first file',
88
+ fileId: 'file1',
89
+ filename: 'first.txt',
90
+ },
91
+ {
92
+ content: 'Content of second file',
93
+ fileId: 'file2',
94
+ filename: 'second.md',
95
+ },
96
+ {
97
+ content: '',
98
+ error: 'Parse error',
99
+ fileId: 'file3',
100
+ filename: 'broken.pdf',
101
+ },
102
+ ];
103
+
104
+ const knowledgeBases: KnowledgeBaseInfo[] = [
105
+ {
106
+ description: 'Technical documentation',
107
+ id: 'kb1',
108
+ name: 'Tech Docs',
109
+ },
110
+ {
111
+ description: null,
112
+ id: 'kb2',
113
+ name: 'User Guides',
114
+ },
115
+ {
116
+ description: 'Frequently asked questions',
117
+ id: 'kb3',
118
+ name: 'FAQ Database',
119
+ },
120
+ ];
121
+
122
+ const result = promptAgentKnowledge({ fileContents, knowledgeBases });
123
+ expect(result).toMatchSnapshot();
124
+ });
125
+
126
+ it('should handle knowledge base without description', () => {
127
+ const knowledgeBases: KnowledgeBaseInfo[] = [
128
+ {
129
+ id: 'kb1',
130
+ name: 'Simple KB',
131
+ },
132
+ ];
133
+
134
+ const result = promptAgentKnowledge({ knowledgeBases });
135
+ expect(result).toMatchSnapshot();
136
+ });
137
+
138
+ it('should handle file with special characters in filename', () => {
139
+ const fileContents: FileContent[] = [
140
+ {
141
+ content: 'Special content',
142
+ fileId: 'file1',
143
+ filename: 'file with spaces & special-chars.txt',
144
+ },
145
+ ];
146
+
147
+ const result = promptAgentKnowledge({ fileContents });
148
+ expect(result).toMatchSnapshot();
149
+ });
150
+
151
+ it('should handle file with multiline content', () => {
152
+ const fileContents: FileContent[] = [
153
+ {
154
+ content: `Line 1
155
+ Line 2
156
+ Line 3
157
+
158
+ Line 5 with gap`,
159
+ fileId: 'file1',
160
+ filename: 'multiline.txt',
161
+ },
162
+ ];
163
+
164
+ const result = promptAgentKnowledge({ fileContents });
165
+ expect(result).toMatchSnapshot();
166
+ });
167
+ });
@@ -0,0 +1,85 @@
1
+ import type { FileContent } from '../knowledgeBaseQA';
2
+
3
+ export interface KnowledgeBaseInfo {
4
+ description?: string | null;
5
+ id: string;
6
+ name: string;
7
+ }
8
+
9
+ export interface PromptKnowledgeOptions {
10
+ /** File contents to inject */
11
+ fileContents?: FileContent[];
12
+ /** Knowledge bases to include */
13
+ knowledgeBases?: KnowledgeBaseInfo[];
14
+ }
15
+
16
+ /**
17
+ * Formats a single file content with XML tags
18
+ */
19
+ const formatFileContent = (file: FileContent): string => {
20
+ if (file.error) {
21
+ return `<file id="${file.fileId}" name="${file.filename}" error="${file.error}" />`;
22
+ }
23
+
24
+ return `<file id="${file.fileId}" name="${file.filename}">
25
+ ${file.content}
26
+ </file>`;
27
+ };
28
+
29
+ /**
30
+ * Format agent knowledge (files + knowledge bases) as unified XML prompt
31
+ */
32
+ export const promptAgentKnowledge = ({
33
+ fileContents = [],
34
+ knowledgeBases = [],
35
+ }: PromptKnowledgeOptions) => {
36
+ const hasFiles = fileContents.length > 0;
37
+ const hasKnowledgeBases = knowledgeBases.length > 0;
38
+
39
+ // If no knowledge at all, return empty
40
+ if (!hasFiles && !hasKnowledgeBases) {
41
+ return '';
42
+ }
43
+
44
+ const contentParts: string[] = [];
45
+
46
+ // Add instruction based on what's available
47
+ if (hasFiles && hasKnowledgeBases) {
48
+ contentParts.push(
49
+ '<instruction>The following files and knowledge bases are available. For files, refer to their content directly. For knowledge bases, use the searchKnowledgeBase tool to find relevant information.</instruction>',
50
+ );
51
+ } else if (hasFiles) {
52
+ contentParts.push(
53
+ '<instruction>The following files are available. Refer to their content directly to answer questions. No knowledge bases are associated.</instruction>',
54
+ );
55
+ } else {
56
+ contentParts.push(
57
+ '<instruction>The following knowledge bases are available for semantic search. Use the searchKnowledgeBase tool to find relevant information.</instruction>',
58
+ );
59
+ }
60
+
61
+ // Add files section
62
+ if (hasFiles) {
63
+ const filesXml = fileContents.map((file) => formatFileContent(file)).join('\n');
64
+ contentParts.push(`<files totalCount="${fileContents.length}">
65
+ ${filesXml}
66
+ </files>`);
67
+ }
68
+
69
+ // Add knowledge bases section
70
+ if (hasKnowledgeBases) {
71
+ const kbItems = knowledgeBases
72
+ .map(
73
+ (kb) =>
74
+ `<knowledge_base id="${kb.id}" name="${kb.name}"${kb.description ? ` description="${kb.description}"` : ''} />`,
75
+ )
76
+ .join('\n');
77
+ contentParts.push(`<knowledge_bases totalCount="${knowledgeBases.length}">
78
+ ${kbItems}
79
+ </knowledge_bases>`);
80
+ }
81
+
82
+ return `<agent_knowledge>
83
+ ${contentParts.join('\n')}
84
+ </agent_knowledge>`;
85
+ };
@@ -14,6 +14,7 @@ export enum FileSource {
14
14
  }
15
15
 
16
16
  export interface FileItem {
17
+ content?: string;
17
18
  createdAt: Date;
18
19
  enabled?: boolean;
19
20
  id: string;
@@ -36,4 +36,5 @@ export * from './openai/chat';
36
36
  export * from './openai/plugin';
37
37
  export * from './subscription';
38
38
  export * from './trace';
39
+ export * from './userMemory';
39
40
  export * from './zustand';
@@ -38,6 +38,7 @@ export enum KnowledgeType {
38
38
 
39
39
  export interface KnowledgeItem {
40
40
  avatar?: string | null;
41
+ content?: string;
41
42
  description?: string | null;
42
43
  enabled?: boolean;
43
44
  fileType?: string;
@@ -0,0 +1,3 @@
1
+ export * from './layers';
2
+ export * from './shared';
3
+ export * from './tools';