@lobehub/lobehub 2.0.0-next.110 → 2.0.0-next.111
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/changelog/v1.json +9 -0
- package/docs/development/database-schema.dbml +2 -1
- package/package.json +1 -1
- package/packages/context-engine/src/index.ts +1 -1
- package/packages/context-engine/src/providers/KnowledgeInjector.ts +78 -0
- package/packages/context-engine/src/providers/index.ts +2 -0
- package/packages/database/migrations/0047_add_slug_document.sql +1 -5
- package/packages/database/migrations/meta/0047_snapshot.json +30 -14
- package/packages/database/migrations/meta/_journal.json +1 -1
- package/packages/database/src/core/migrations.json +3 -3
- package/packages/database/src/models/__tests__/agent.test.ts +172 -3
- package/packages/database/src/models/__tests__/userMemories.test.ts +1382 -0
- package/packages/database/src/models/agent.ts +17 -0
- package/packages/database/src/models/userMemory.ts +993 -0
- package/packages/database/src/schemas/userMemories.ts +22 -5
- package/packages/prompts/src/prompts/files/__snapshots__/knowledgeBase.test.ts.snap +103 -0
- package/packages/prompts/src/prompts/files/index.ts +3 -0
- package/packages/prompts/src/prompts/files/knowledgeBase.test.ts +167 -0
- package/packages/prompts/src/prompts/files/knowledgeBase.ts +85 -0
- package/packages/types/src/files/index.ts +1 -0
- package/packages/types/src/index.ts +1 -0
- package/packages/types/src/knowledgeBase/index.ts +1 -0
- package/packages/types/src/userMemory/index.ts +3 -0
- package/packages/types/src/userMemory/layers.ts +54 -0
- package/packages/types/src/userMemory/shared.ts +64 -0
- package/packages/types/src/userMemory/tools.ts +240 -0
- package/src/features/ChatList/Messages/index.tsx +16 -19
- package/src/features/ChatList/components/ContextMenu.tsx +23 -16
- package/src/helpers/toolEngineering/index.ts +5 -9
- package/src/hooks/useQueryParam.ts +24 -22
- package/src/server/routers/async/file.ts +2 -7
- package/src/services/chat/contextEngineering.ts +19 -0
- package/src/store/agent/slices/chat/selectors/agent.ts +4 -0
- package/src/store/chat/slices/builtinTool/actions/knowledgeBase.ts +5 -15
- package/src/tools/knowledge-base/ExecutionRuntime/index.ts +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,32 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
+
## [Version 2.0.0-next.111](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.110...v2.0.0-next.111)
|
|
6
|
+
|
|
7
|
+
<sup>Released on **2025-11-24**</sup>
|
|
8
|
+
|
|
9
|
+
#### 🐛 Bug Fixes
|
|
10
|
+
|
|
11
|
+
- **misc**: Fix db migration snapshot not align with db schema, Separate agent file injection from knowledge base RAG search.
|
|
12
|
+
|
|
13
|
+
<br/>
|
|
14
|
+
|
|
15
|
+
<details>
|
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
17
|
+
|
|
18
|
+
#### What's fixed
|
|
19
|
+
|
|
20
|
+
- **misc**: Fix db migration snapshot not align with db schema, closes [#10399](https://github.com/lobehub/lobe-chat/issues/10399) ([760105a](https://github.com/lobehub/lobe-chat/commit/760105a))
|
|
21
|
+
- **misc**: Separate agent file injection from knowledge base RAG search, closes [#10398](https://github.com/lobehub/lobe-chat/issues/10398) ([e1c813a](https://github.com/lobehub/lobe-chat/commit/e1c813a))
|
|
22
|
+
|
|
23
|
+
</details>
|
|
24
|
+
|
|
25
|
+
<div align="right">
|
|
26
|
+
|
|
27
|
+
[](#readme-top)
|
|
28
|
+
|
|
29
|
+
</div>
|
|
30
|
+
|
|
5
31
|
## [Version 2.0.0-next.110](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.109...v2.0.0-next.110)
|
|
6
32
|
|
|
7
33
|
<sup>Released on **2025-11-24**</sup>
|
package/changelog/v1.json
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"children": {
|
|
4
|
+
"fixes": [
|
|
5
|
+
"Fix db migration snapshot not align with db schema, Separate agent file injection from knowledge base RAG search."
|
|
6
|
+
]
|
|
7
|
+
},
|
|
8
|
+
"date": "2025-11-24",
|
|
9
|
+
"version": "2.0.0-next.111"
|
|
10
|
+
},
|
|
2
11
|
{
|
|
3
12
|
"children": {
|
|
4
13
|
"improvements": [
|
|
@@ -187,6 +187,7 @@ table documents {
|
|
|
187
187
|
user_id text [not null]
|
|
188
188
|
client_id text
|
|
189
189
|
editor_data jsonb
|
|
190
|
+
slug varchar(255)
|
|
190
191
|
accessed_at "timestamp with time zone" [not null, default: `now()`]
|
|
191
192
|
created_at "timestamp with time zone" [not null, default: `now()`]
|
|
192
193
|
updated_at "timestamp with time zone" [not null, default: `now()`]
|
|
@@ -197,6 +198,7 @@ table documents {
|
|
|
197
198
|
file_id [name: 'documents_file_id_idx']
|
|
198
199
|
parent_id [name: 'documents_parent_id_idx']
|
|
199
200
|
(client_id, user_id) [name: 'documents_client_id_user_id_unique', unique]
|
|
201
|
+
(slug, user_id) [name: 'documents_slug_user_id_unique', unique]
|
|
200
202
|
}
|
|
201
203
|
}
|
|
202
204
|
|
|
@@ -214,7 +216,6 @@ table files {
|
|
|
214
216
|
metadata jsonb
|
|
215
217
|
chunk_task_id uuid
|
|
216
218
|
embedding_task_id uuid
|
|
217
|
-
slug text [unique]
|
|
218
219
|
accessed_at "timestamp with time zone" [not null, default: `now()`]
|
|
219
220
|
created_at "timestamp with time zone" [not null, default: `now()`]
|
|
220
221
|
updated_at "timestamp with time zone" [not null, default: `now()`]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobehub/lobehub",
|
|
3
|
-
"version": "2.0.0-next.
|
|
3
|
+
"version": "2.0.0-next.111",
|
|
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",
|
|
@@ -10,7 +10,7 @@ export type { ContextEngineConfig } from './pipeline';
|
|
|
10
10
|
export { ContextEngine } from './pipeline';
|
|
11
11
|
|
|
12
12
|
// Context Providers
|
|
13
|
-
export
|
|
13
|
+
export * from './providers';
|
|
14
14
|
|
|
15
15
|
// Processors
|
|
16
16
|
export {
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { promptAgentKnowledge } from '@lobechat/prompts';
|
|
2
|
+
import type { FileContent, KnowledgeBaseInfo } from '@lobechat/prompts';
|
|
3
|
+
import debug from 'debug';
|
|
4
|
+
|
|
5
|
+
import { BaseProvider } from '../base/BaseProvider';
|
|
6
|
+
import type { PipelineContext, ProcessorOptions } from '../types';
|
|
7
|
+
|
|
8
|
+
const log = debug('context-engine:provider:KnowledgeInjector');
|
|
9
|
+
|
|
10
|
+
export interface KnowledgeInjectorConfig {
|
|
11
|
+
/** File contents to inject */
|
|
12
|
+
fileContents?: FileContent[];
|
|
13
|
+
/** Knowledge bases to inject */
|
|
14
|
+
knowledgeBases?: KnowledgeBaseInfo[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Knowledge Injector
|
|
19
|
+
* Responsible for injecting agent's knowledge (files and knowledge bases) into context
|
|
20
|
+
*/
|
|
21
|
+
export class KnowledgeInjector extends BaseProvider {
|
|
22
|
+
readonly name = 'KnowledgeInjector';
|
|
23
|
+
|
|
24
|
+
constructor(
|
|
25
|
+
private config: KnowledgeInjectorConfig,
|
|
26
|
+
options: ProcessorOptions = {},
|
|
27
|
+
) {
|
|
28
|
+
super(options);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
protected async doProcess(context: PipelineContext): Promise<PipelineContext> {
|
|
32
|
+
const clonedContext = this.cloneContext(context);
|
|
33
|
+
|
|
34
|
+
const fileContents = this.config.fileContents || [];
|
|
35
|
+
const knowledgeBases = this.config.knowledgeBases || [];
|
|
36
|
+
|
|
37
|
+
// Generate unified knowledge prompt
|
|
38
|
+
const formattedContent = promptAgentKnowledge({ fileContents, knowledgeBases });
|
|
39
|
+
|
|
40
|
+
// Skip injection if no knowledge at all
|
|
41
|
+
if (!formattedContent) {
|
|
42
|
+
log('No knowledge to inject');
|
|
43
|
+
return this.markAsExecuted(clonedContext);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Find the first user message index
|
|
47
|
+
const firstUserIndex = clonedContext.messages.findIndex((msg) => msg.role === 'user');
|
|
48
|
+
|
|
49
|
+
if (firstUserIndex === -1) {
|
|
50
|
+
log('No user messages found, skipping injection');
|
|
51
|
+
return this.markAsExecuted(clonedContext);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Insert a new user message with knowledge before the first user message
|
|
55
|
+
// Mark it as application-level system injection
|
|
56
|
+
const knowledgeMessage = {
|
|
57
|
+
content: formattedContent,
|
|
58
|
+
createdAt: Date.now(),
|
|
59
|
+
id: `knowledge-${Date.now()}`,
|
|
60
|
+
meta: { injectType: 'knowledge', systemInjection: true },
|
|
61
|
+
role: 'user' as const,
|
|
62
|
+
updatedAt: Date.now(),
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
clonedContext.messages.splice(firstUserIndex, 0, knowledgeMessage);
|
|
66
|
+
|
|
67
|
+
// Update metadata
|
|
68
|
+
clonedContext.metadata.knowledgeInjected = true;
|
|
69
|
+
clonedContext.metadata.filesCount = fileContents.length;
|
|
70
|
+
clonedContext.metadata.knowledgeBasesCount = knowledgeBases.length;
|
|
71
|
+
|
|
72
|
+
log(
|
|
73
|
+
`Agent knowledge injected as new user message: ${fileContents.length} file(s), ${knowledgeBases.length} knowledge base(s)`,
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
return this.markAsExecuted(clonedContext);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
// Context Provider exports
|
|
2
2
|
export { HistorySummaryProvider } from './HistorySummary';
|
|
3
|
+
export { KnowledgeInjector } from './KnowledgeInjector';
|
|
3
4
|
export { SystemRoleInjector } from './SystemRoleInjector';
|
|
4
5
|
export { ToolSystemRoleProvider } from './ToolSystemRole';
|
|
5
6
|
|
|
6
7
|
// Re-export types
|
|
7
8
|
export type { HistorySummaryConfig } from './HistorySummary';
|
|
9
|
+
export type { KnowledgeInjectorConfig } from './KnowledgeInjector';
|
|
8
10
|
export type { SystemRoleInjectorConfig } from './SystemRoleInjector';
|
|
9
11
|
export type { ToolSystemRoleConfig } from './ToolSystemRole';
|
|
@@ -1,6 +1,2 @@
|
|
|
1
1
|
ALTER TABLE "documents" ADD COLUMN IF NOT EXISTS "slug" varchar(255);--> statement-breakpoint
|
|
2
|
-
|
|
3
|
-
CREATE UNIQUE INDEX IF NOT EXISTS "documents_slug_user_id_unique" ON "documents" ("slug","user_id") WHERE "slug" IS NOT NULL;
|
|
4
|
-
EXCEPTION
|
|
5
|
-
WHEN duplicate_object THEN null;
|
|
6
|
-
END $$;
|
|
2
|
+
CREATE UNIQUE INDEX IF NOT EXISTS "documents_slug_user_id_unique" ON "documents" USING btree ("slug","user_id") WHERE "documents"."slug" is not null;
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
},
|
|
7
7
|
"dialect": "postgresql",
|
|
8
8
|
"enums": {},
|
|
9
|
-
"id": "
|
|
9
|
+
"id": "20a4c30c-04f0-4993-b493-0ae6a3144f9e",
|
|
10
10
|
"policies": {},
|
|
11
11
|
"prevId": "abe0cb47-a970-4ec0-a29f-1afa9bd2fe02",
|
|
12
12
|
"roles": {},
|
|
@@ -1259,6 +1259,12 @@
|
|
|
1259
1259
|
"primaryKey": false,
|
|
1260
1260
|
"notNull": false
|
|
1261
1261
|
},
|
|
1262
|
+
"slug": {
|
|
1263
|
+
"name": "slug",
|
|
1264
|
+
"type": "varchar(255)",
|
|
1265
|
+
"primaryKey": false,
|
|
1266
|
+
"notNull": false
|
|
1267
|
+
},
|
|
1262
1268
|
"accessed_at": {
|
|
1263
1269
|
"name": "accessed_at",
|
|
1264
1270
|
"type": "timestamp with time zone",
|
|
@@ -1362,6 +1368,28 @@
|
|
|
1362
1368
|
"concurrently": false,
|
|
1363
1369
|
"method": "btree",
|
|
1364
1370
|
"with": {}
|
|
1371
|
+
},
|
|
1372
|
+
"documents_slug_user_id_unique": {
|
|
1373
|
+
"name": "documents_slug_user_id_unique",
|
|
1374
|
+
"columns": [
|
|
1375
|
+
{
|
|
1376
|
+
"expression": "slug",
|
|
1377
|
+
"isExpression": false,
|
|
1378
|
+
"asc": true,
|
|
1379
|
+
"nulls": "last"
|
|
1380
|
+
},
|
|
1381
|
+
{
|
|
1382
|
+
"expression": "user_id",
|
|
1383
|
+
"isExpression": false,
|
|
1384
|
+
"asc": true,
|
|
1385
|
+
"nulls": "last"
|
|
1386
|
+
}
|
|
1387
|
+
],
|
|
1388
|
+
"isUnique": true,
|
|
1389
|
+
"where": "\"documents\".\"slug\" is not null",
|
|
1390
|
+
"concurrently": false,
|
|
1391
|
+
"method": "btree",
|
|
1392
|
+
"with": {}
|
|
1365
1393
|
}
|
|
1366
1394
|
},
|
|
1367
1395
|
"foreignKeys": {
|
|
@@ -1481,12 +1509,6 @@
|
|
|
1481
1509
|
"primaryKey": false,
|
|
1482
1510
|
"notNull": false
|
|
1483
1511
|
},
|
|
1484
|
-
"slug": {
|
|
1485
|
-
"name": "slug",
|
|
1486
|
-
"type": "text",
|
|
1487
|
-
"primaryKey": false,
|
|
1488
|
-
"notNull": false
|
|
1489
|
-
},
|
|
1490
1512
|
"accessed_at": {
|
|
1491
1513
|
"name": "accessed_at",
|
|
1492
1514
|
"type": "timestamp with time zone",
|
|
@@ -1610,13 +1632,7 @@
|
|
|
1610
1632
|
}
|
|
1611
1633
|
},
|
|
1612
1634
|
"compositePrimaryKeys": {},
|
|
1613
|
-
"uniqueConstraints": {
|
|
1614
|
-
"files_slug_unique": {
|
|
1615
|
-
"name": "files_slug_unique",
|
|
1616
|
-
"nullsNotDistinct": false,
|
|
1617
|
-
"columns": ["slug"]
|
|
1618
|
-
}
|
|
1619
|
-
},
|
|
1635
|
+
"uniqueConstraints": {},
|
|
1620
1636
|
"policies": {},
|
|
1621
1637
|
"checkConstraints": {},
|
|
1622
1638
|
"isRLSEnabled": false
|
|
@@ -792,10 +792,10 @@
|
|
|
792
792
|
{
|
|
793
793
|
"sql": [
|
|
794
794
|
"ALTER TABLE \"documents\" ADD COLUMN IF NOT EXISTS \"slug\" varchar(255);",
|
|
795
|
-
"\
|
|
795
|
+
"\nCREATE UNIQUE INDEX IF NOT EXISTS \"documents_slug_user_id_unique\" ON \"documents\" USING btree (\"slug\",\"user_id\") WHERE \"documents\".\"slug\" is not null;\n"
|
|
796
796
|
],
|
|
797
797
|
"bps": true,
|
|
798
|
-
"folderMillis":
|
|
799
|
-
"hash": "
|
|
798
|
+
"folderMillis": 1763987922211,
|
|
799
|
+
"hash": "f823b521f4d25e5dc5ab238b372727d2d2d7f0aed27b5eabc8a9608ce4e50568"
|
|
800
800
|
}
|
|
801
801
|
]
|
|
@@ -2,17 +2,18 @@
|
|
|
2
2
|
import { eq } from 'drizzle-orm';
|
|
3
3
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
4
4
|
|
|
5
|
-
import { LobeChatDatabase } from '../../type';
|
|
6
5
|
import {
|
|
7
6
|
agents,
|
|
8
7
|
agentsFiles,
|
|
9
8
|
agentsKnowledgeBases,
|
|
10
9
|
agentsToSessions,
|
|
10
|
+
documents,
|
|
11
11
|
files,
|
|
12
12
|
knowledgeBases,
|
|
13
13
|
sessions,
|
|
14
14
|
users,
|
|
15
15
|
} from '../../schemas';
|
|
16
|
+
import { LobeChatDatabase } from '../../type';
|
|
16
17
|
import { AgentModel } from '../agent';
|
|
17
18
|
import { getTestDB } from './_util';
|
|
18
19
|
|
|
@@ -69,6 +70,76 @@ describe('AgentModel', () => {
|
|
|
69
70
|
expect(result.knowledgeBases).toHaveLength(1);
|
|
70
71
|
expect(result.files).toHaveLength(1);
|
|
71
72
|
});
|
|
73
|
+
|
|
74
|
+
it('should fetch and include document content for enabled files', async () => {
|
|
75
|
+
const agentId = 'test-agent-with-docs';
|
|
76
|
+
await serverDB.insert(agents).values({ id: agentId, userId });
|
|
77
|
+
await serverDB.insert(agentsFiles).values({ agentId, fileId: '1', userId, enabled: true });
|
|
78
|
+
await serverDB.insert(documents).values({
|
|
79
|
+
id: 'doc1',
|
|
80
|
+
fileId: '1',
|
|
81
|
+
userId,
|
|
82
|
+
content: 'This is document content',
|
|
83
|
+
fileType: 'application/pdf',
|
|
84
|
+
totalCharCount: 100,
|
|
85
|
+
totalLineCount: 10,
|
|
86
|
+
sourceType: 'file',
|
|
87
|
+
source: 'document.pdf',
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const result = await agentModel.getAgentConfigById(agentId);
|
|
91
|
+
|
|
92
|
+
expect(result).toBeDefined();
|
|
93
|
+
expect(result.files).toHaveLength(1);
|
|
94
|
+
expect(result.files[0].content).toBe('This is document content');
|
|
95
|
+
expect(result.files[0].enabled).toBe(true);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should not include content for disabled files', async () => {
|
|
99
|
+
const agentId = 'test-agent-disabled-file';
|
|
100
|
+
await serverDB.insert(agents).values({ id: agentId, userId });
|
|
101
|
+
await serverDB.insert(agentsFiles).values({ agentId, fileId: '1', userId, enabled: false });
|
|
102
|
+
await serverDB.insert(documents).values({
|
|
103
|
+
id: 'doc2',
|
|
104
|
+
fileId: '1',
|
|
105
|
+
userId,
|
|
106
|
+
content: 'This should not be included',
|
|
107
|
+
fileType: 'application/pdf',
|
|
108
|
+
totalCharCount: 100,
|
|
109
|
+
totalLineCount: 10,
|
|
110
|
+
sourceType: 'file',
|
|
111
|
+
source: 'document.pdf',
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const result = await agentModel.getAgentConfigById(agentId);
|
|
115
|
+
|
|
116
|
+
expect(result).toBeDefined();
|
|
117
|
+
expect(result.files).toHaveLength(1);
|
|
118
|
+
expect(result.files[0].content).toBeUndefined();
|
|
119
|
+
expect(result.files[0].enabled).toBe(false);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('should handle files without documents', async () => {
|
|
123
|
+
const agentId = 'test-agent-no-docs';
|
|
124
|
+
await serverDB.insert(agents).values({ id: agentId, userId });
|
|
125
|
+
await serverDB.insert(agentsFiles).values({ agentId, fileId: '2', userId, enabled: true });
|
|
126
|
+
|
|
127
|
+
const result = await agentModel.getAgentConfigById(agentId);
|
|
128
|
+
|
|
129
|
+
expect(result).toBeDefined();
|
|
130
|
+
expect(result.files).toHaveLength(1);
|
|
131
|
+
expect(result.files[0].content).toBeUndefined();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('should handle agent with no files', async () => {
|
|
135
|
+
const agentId = 'test-agent-no-files';
|
|
136
|
+
await serverDB.insert(agents).values({ id: agentId, userId });
|
|
137
|
+
|
|
138
|
+
const result = await agentModel.getAgentConfigById(agentId);
|
|
139
|
+
|
|
140
|
+
expect(result).toBeDefined();
|
|
141
|
+
expect(result.files).toHaveLength(0);
|
|
142
|
+
});
|
|
72
143
|
});
|
|
73
144
|
|
|
74
145
|
describe('findBySessionId', () => {
|
|
@@ -84,10 +155,16 @@ describe('AgentModel', () => {
|
|
|
84
155
|
expect(result).toBeDefined();
|
|
85
156
|
expect(result?.id).toBe(agentId);
|
|
86
157
|
});
|
|
158
|
+
|
|
159
|
+
it('should return undefined when session is not found', async () => {
|
|
160
|
+
const result = await agentModel.findBySessionId('non-existent-session');
|
|
161
|
+
|
|
162
|
+
expect(result).toBeUndefined();
|
|
163
|
+
});
|
|
87
164
|
});
|
|
88
165
|
|
|
89
166
|
describe('createAgentKnowledgeBase', () => {
|
|
90
|
-
it('should create a new agent knowledge base association', async () => {
|
|
167
|
+
it('should create a new agent knowledge base association with enabled=true by default', async () => {
|
|
91
168
|
const agent = await serverDB
|
|
92
169
|
.insert(agents)
|
|
93
170
|
.values({ userId })
|
|
@@ -107,6 +184,27 @@ describe('AgentModel', () => {
|
|
|
107
184
|
enabled: true,
|
|
108
185
|
});
|
|
109
186
|
});
|
|
187
|
+
|
|
188
|
+
it('should create a new agent knowledge base association with enabled=false', async () => {
|
|
189
|
+
const agent = await serverDB
|
|
190
|
+
.insert(agents)
|
|
191
|
+
.values({ userId })
|
|
192
|
+
.returning()
|
|
193
|
+
.then((res) => res[0]);
|
|
194
|
+
|
|
195
|
+
await agentModel.createAgentKnowledgeBase(agent.id, knowledgeBase.id, false);
|
|
196
|
+
|
|
197
|
+
const result = await serverDB.query.agentsKnowledgeBases.findFirst({
|
|
198
|
+
where: eq(agentsKnowledgeBases.agentId, agent.id),
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
expect(result).toMatchObject({
|
|
202
|
+
agentId: agent.id,
|
|
203
|
+
knowledgeBaseId: knowledgeBase.id,
|
|
204
|
+
userId,
|
|
205
|
+
enabled: false,
|
|
206
|
+
});
|
|
207
|
+
});
|
|
110
208
|
});
|
|
111
209
|
|
|
112
210
|
describe('deleteAgentKnowledgeBase', () => {
|
|
@@ -153,7 +251,7 @@ describe('AgentModel', () => {
|
|
|
153
251
|
});
|
|
154
252
|
|
|
155
253
|
describe('createAgentFiles', () => {
|
|
156
|
-
it('should create new agent file associations', async () => {
|
|
254
|
+
it('should create new agent file associations with enabled=true by default', async () => {
|
|
157
255
|
const agent = await serverDB
|
|
158
256
|
.insert(agents)
|
|
159
257
|
.values({ userId })
|
|
@@ -174,6 +272,77 @@ describe('AgentModel', () => {
|
|
|
174
272
|
]),
|
|
175
273
|
);
|
|
176
274
|
});
|
|
275
|
+
|
|
276
|
+
it('should create new agent file associations with enabled=false', async () => {
|
|
277
|
+
const agent = await serverDB
|
|
278
|
+
.insert(agents)
|
|
279
|
+
.values({ userId })
|
|
280
|
+
.returning()
|
|
281
|
+
.then((res) => res[0]);
|
|
282
|
+
|
|
283
|
+
await agentModel.createAgentFiles(agent.id, ['1'], false);
|
|
284
|
+
|
|
285
|
+
const results = await serverDB.query.agentsFiles.findMany({
|
|
286
|
+
where: eq(agentsFiles.agentId, agent.id),
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
expect(results).toHaveLength(1);
|
|
290
|
+
expect(results[0]).toMatchObject({
|
|
291
|
+
agentId: agent.id,
|
|
292
|
+
fileId: '1',
|
|
293
|
+
userId,
|
|
294
|
+
enabled: false,
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it('should skip files that already exist', async () => {
|
|
299
|
+
const agent = await serverDB
|
|
300
|
+
.insert(agents)
|
|
301
|
+
.values({ userId })
|
|
302
|
+
.returning()
|
|
303
|
+
.then((res) => res[0]);
|
|
304
|
+
|
|
305
|
+
// First insert
|
|
306
|
+
await serverDB.insert(agentsFiles).values({ agentId: agent.id, fileId: '1', userId });
|
|
307
|
+
|
|
308
|
+
// Try to insert the same file again
|
|
309
|
+
await agentModel.createAgentFiles(agent.id, ['1', '2']);
|
|
310
|
+
|
|
311
|
+
const results = await serverDB.query.agentsFiles.findMany({
|
|
312
|
+
where: eq(agentsFiles.agentId, agent.id),
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
// Should only have 2 files (1 existing + 1 new), not 3
|
|
316
|
+
expect(results).toHaveLength(2);
|
|
317
|
+
expect(results.map((r) => r.fileId).sort()).toEqual(['1', '2']);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it('should return early when all files already exist', async () => {
|
|
321
|
+
const agent = await serverDB
|
|
322
|
+
.insert(agents)
|
|
323
|
+
.values({ userId })
|
|
324
|
+
.returning()
|
|
325
|
+
.then((res) => res[0]);
|
|
326
|
+
|
|
327
|
+
// First insert
|
|
328
|
+
await serverDB.insert(agentsFiles).values([
|
|
329
|
+
{ agentId: agent.id, fileId: '1', userId },
|
|
330
|
+
{ agentId: agent.id, fileId: '2', userId },
|
|
331
|
+
]);
|
|
332
|
+
|
|
333
|
+
// Try to insert the same files again
|
|
334
|
+
const result = await agentModel.createAgentFiles(agent.id, ['1', '2']);
|
|
335
|
+
|
|
336
|
+
// Should return undefined (early return)
|
|
337
|
+
expect(result).toBeUndefined();
|
|
338
|
+
|
|
339
|
+
const results = await serverDB.query.agentsFiles.findMany({
|
|
340
|
+
where: eq(agentsFiles.agentId, agent.id),
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
// Should still only have 2 files
|
|
344
|
+
expect(results).toHaveLength(2);
|
|
345
|
+
});
|
|
177
346
|
});
|
|
178
347
|
|
|
179
348
|
describe('deleteAgentFile', () => {
|