@louloulinx/metagpt 0.1.3
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/.eslintrc.json +23 -0
- package/.prettierrc +7 -0
- package/LICENSE +21 -0
- package/README-CN.md +754 -0
- package/README.md +238 -0
- package/bun.lock +1023 -0
- package/doc/TutorialAssistant.md +114 -0
- package/doc/VercelLLMProvider.md +164 -0
- package/eslint.config.js +55 -0
- package/examples/data-interpreter-example.ts +173 -0
- package/examples/qwen-direct-example.ts +60 -0
- package/examples/qwen-example.ts +62 -0
- package/examples/tutorial-assistant-example.ts +97 -0
- package/jest.config.ts +22 -0
- package/output/tutorials/Go/350/257/255/350/250/200/347/274/226/347/250/213/346/225/231/347/250/213_2025-02-25T09-35-15-436Z.md +2208 -0
- package/output/tutorials/Rust/346/225/231/347/250/213_2025-02-25T08-27-27-632Z.md +1967 -0
- package/output/tutorials//345/246/202/344/275/225/344/275/277/347/224/250TypeScript/345/274/200/345/217/221Node.js/345/272/224/347/224/250_2025-02-25T08-14-39-605Z.md +1721 -0
- package/output/tutorials//346/225/260/345/255/227/347/273/217/346/265/216/345/255/246/346/225/231/347/250/213_2025-02-25T10-45-03-605Z.md +902 -0
- package/output/tutorials//346/232/250/345/215/227/345/244/247/345/255/246/346/225/260/345/255/227/347/273/217/346/265/216/345/255/246/345/244/215/350/257/225/350/265/204/346/226/231_2025-02-25T11-16-59-133Z.md +719 -0
- package/package.json +58 -0
- package/plan-cn.md +321 -0
- package/plan.md +154 -0
- package/src/actions/analyze-task.ts +65 -0
- package/src/actions/base-action.ts +103 -0
- package/src/actions/di/execute-nb-code.ts +247 -0
- package/src/actions/di/write-analysis-code.ts +234 -0
- package/src/actions/write-tutorial.ts +232 -0
- package/src/config/browser.ts +33 -0
- package/src/config/config.ts +345 -0
- package/src/config/embedding.ts +26 -0
- package/src/config/llm.ts +36 -0
- package/src/config/mermaid.ts +37 -0
- package/src/config/omniparse.ts +25 -0
- package/src/config/redis.ts +34 -0
- package/src/config/s3.ts +33 -0
- package/src/config/search.ts +30 -0
- package/src/config/workspace.ts +20 -0
- package/src/index.ts +40 -0
- package/src/management/team.ts +168 -0
- package/src/memory/longterm.ts +218 -0
- package/src/memory/manager.ts +160 -0
- package/src/memory/types.ts +100 -0
- package/src/memory/working.ts +154 -0
- package/src/monitoring/system.ts +413 -0
- package/src/monitoring/types.ts +230 -0
- package/src/plugin/manager.ts +79 -0
- package/src/plugin/types.ts +114 -0
- package/src/provider/vercel-llm.ts +314 -0
- package/src/rag/base-rag.ts +194 -0
- package/src/rag/document-qa.ts +102 -0
- package/src/roles/base-role.ts +155 -0
- package/src/roles/data-interpreter.ts +360 -0
- package/src/roles/engineer.ts +1 -0
- package/src/roles/tutorial-assistant.ts +217 -0
- package/src/skills/base-skill.ts +144 -0
- package/src/skills/code-review.ts +120 -0
- package/src/tools/base-tool.ts +155 -0
- package/src/tools/file-system.ts +204 -0
- package/src/tools/tool-recommend.d.ts +14 -0
- package/src/tools/tool-recommend.ts +31 -0
- package/src/types/action.ts +38 -0
- package/src/types/config.ts +129 -0
- package/src/types/document.ts +354 -0
- package/src/types/llm.ts +64 -0
- package/src/types/memory.ts +36 -0
- package/src/types/message.ts +193 -0
- package/src/types/rag.ts +86 -0
- package/src/types/role.ts +67 -0
- package/src/types/skill.ts +71 -0
- package/src/types/task.ts +32 -0
- package/src/types/team.ts +55 -0
- package/src/types/tool.ts +77 -0
- package/src/types/workflow.ts +133 -0
- package/src/utils/common.ts +73 -0
- package/src/utils/yaml.ts +67 -0
- package/src/websocket/browser-client.ts +187 -0
- package/src/websocket/client.ts +186 -0
- package/src/websocket/server.ts +169 -0
- package/src/websocket/types.ts +125 -0
- package/src/workflow/executor.ts +193 -0
- package/src/workflow/executors/action-executor.ts +72 -0
- package/src/workflow/executors/condition-executor.ts +118 -0
- package/src/workflow/executors/parallel-executor.ts +201 -0
- package/src/workflow/executors/role-executor.ts +76 -0
- package/src/workflow/executors/sequence-executor.ts +196 -0
- package/tests/actions.test.ts +105 -0
- package/tests/benchmark/performance.test.ts +147 -0
- package/tests/config/config.test.ts +115 -0
- package/tests/config.test.ts +106 -0
- package/tests/e2e/setup.ts +74 -0
- package/tests/e2e/workflow.test.ts +88 -0
- package/tests/llm.test.ts +84 -0
- package/tests/memory/memory.test.ts +164 -0
- package/tests/memory.test.ts +63 -0
- package/tests/monitoring/monitoring.test.ts +225 -0
- package/tests/plugin/plugin.test.ts +183 -0
- package/tests/provider/bailian-llm.test.ts +98 -0
- package/tests/rag.test.ts +162 -0
- package/tests/roles.test.ts +88 -0
- package/tests/skills.test.ts +166 -0
- package/tests/team.test.ts +143 -0
- package/tests/tools.test.ts +170 -0
- package/tests/types/document.test.ts +181 -0
- package/tests/types/message.test.ts +122 -0
- package/tests/utils/yaml.test.ts +110 -0
- package/tests/utils.test.ts +74 -0
- package/tests/websocket/browser-client.test.ts +1 -0
- package/tests/websocket/websocket.test.ts +42 -0
- package/tests/workflow/parallel-executor.test.ts +224 -0
- package/tests/workflow/sequence-executor.test.ts +207 -0
- package/tests/workflow.test.ts +290 -0
- package/tsconfig.json +27 -0
- package/typedoc.json +25 -0
@@ -0,0 +1,143 @@
|
|
1
|
+
import { describe, expect, test, mock, beforeAll, afterAll } from 'bun:test';
|
2
|
+
import { Team } from '../src/management/team';
|
3
|
+
import { Context } from '../src/context/context';
|
4
|
+
import { Environment } from '../src/environment/environment';
|
5
|
+
import { Role } from '../src/types/role';
|
6
|
+
import { Message } from '../src/types/message';
|
7
|
+
import { NoMoneyError } from '../src/utils/errors';
|
8
|
+
|
9
|
+
describe('Team Management', () => {
|
10
|
+
// Mock role class for testing
|
11
|
+
class TestRole implements Role {
|
12
|
+
name = 'TestRole';
|
13
|
+
profile = 'Test profile';
|
14
|
+
goal = 'Test goal';
|
15
|
+
constraints = 'Test constraints';
|
16
|
+
|
17
|
+
async observe(): Promise<number> {
|
18
|
+
return 0;
|
19
|
+
}
|
20
|
+
|
21
|
+
async think(): Promise<boolean> {
|
22
|
+
return true;
|
23
|
+
}
|
24
|
+
|
25
|
+
async act(): Promise<Message> {
|
26
|
+
return new Message({
|
27
|
+
role: this.name,
|
28
|
+
content: 'Test action',
|
29
|
+
causedBy: 'TestAction'
|
30
|
+
});
|
31
|
+
}
|
32
|
+
}
|
33
|
+
|
34
|
+
describe('Team Initialization', () => {
|
35
|
+
test('should create team with default config', () => {
|
36
|
+
const team = new Team({});
|
37
|
+
expect(team).toBeDefined();
|
38
|
+
expect(team.costManager).toBeDefined();
|
39
|
+
});
|
40
|
+
|
41
|
+
test('should create team with custom config', () => {
|
42
|
+
const context = new Context();
|
43
|
+
const env = new Environment({ context });
|
44
|
+
const roles = [new TestRole()];
|
45
|
+
|
46
|
+
const team = new Team({
|
47
|
+
environment: env,
|
48
|
+
investment: 100,
|
49
|
+
idea: 'Test idea',
|
50
|
+
context,
|
51
|
+
roles,
|
52
|
+
envDesc: 'Test environment'
|
53
|
+
});
|
54
|
+
|
55
|
+
expect(team).toBeDefined();
|
56
|
+
expect(team.costManager.maxBudget).toBe(100);
|
57
|
+
});
|
58
|
+
});
|
59
|
+
|
60
|
+
describe('Team Operations', () => {
|
61
|
+
let team: Team;
|
62
|
+
let role: TestRole;
|
63
|
+
|
64
|
+
beforeAll(() => {
|
65
|
+
team = new Team({});
|
66
|
+
role = new TestRole();
|
67
|
+
});
|
68
|
+
|
69
|
+
test('should hire roles', () => {
|
70
|
+
team.hire([role]);
|
71
|
+
// Verify through running a project
|
72
|
+
team.runProject('Test project');
|
73
|
+
expect(team['env'].roles).toContain(role);
|
74
|
+
});
|
75
|
+
|
76
|
+
test('should invest funds', () => {
|
77
|
+
team.invest(200);
|
78
|
+
expect(team.costManager.maxBudget).toBe(200);
|
79
|
+
});
|
80
|
+
|
81
|
+
test('should throw error on insufficient funds', () => {
|
82
|
+
team.costManager.totalCost = 300;
|
83
|
+
expect(() => team['checkBalance']()).toThrow(NoMoneyError);
|
84
|
+
});
|
85
|
+
});
|
86
|
+
|
87
|
+
describe('Project Execution', () => {
|
88
|
+
let team: Team;
|
89
|
+
let role: TestRole;
|
90
|
+
|
91
|
+
beforeAll(() => {
|
92
|
+
team = new Team({});
|
93
|
+
role = new TestRole();
|
94
|
+
team.hire([role]);
|
95
|
+
team.invest(1000);
|
96
|
+
});
|
97
|
+
|
98
|
+
test('should run project with idea', async () => {
|
99
|
+
const history = await team.run(1, 'Test project');
|
100
|
+
expect(history.length).toBeGreaterThan(0);
|
101
|
+
expect(history[0].content).toBe('Test project');
|
102
|
+
});
|
103
|
+
|
104
|
+
test('should stop when roles are idle', async () => {
|
105
|
+
team['env'].isIdle = true;
|
106
|
+
const history = await team.run(5, 'Test idle');
|
107
|
+
expect(history.length).toBeGreaterThan(0);
|
108
|
+
});
|
109
|
+
|
110
|
+
test('should archive after completion', async () => {
|
111
|
+
const mockArchive = mock(() => Promise.resolve());
|
112
|
+
team['env'].archive = mockArchive;
|
113
|
+
|
114
|
+
await team.run(1, 'Test archive');
|
115
|
+
expect(mockArchive).toHaveBeenCalled();
|
116
|
+
});
|
117
|
+
});
|
118
|
+
|
119
|
+
describe('Serialization', () => {
|
120
|
+
let team: Team;
|
121
|
+
const storagePath = './test-storage';
|
122
|
+
|
123
|
+
beforeAll(() => {
|
124
|
+
team = new Team({
|
125
|
+
investment: 500,
|
126
|
+
idea: 'Test serialization'
|
127
|
+
});
|
128
|
+
});
|
129
|
+
|
130
|
+
test('should serialize team state', () => {
|
131
|
+
team.serialize(storagePath);
|
132
|
+
// Verify file exists and content
|
133
|
+
const deserializedTeam = Team.deserialize(storagePath);
|
134
|
+
expect(deserializedTeam['config'].investment).toBe(500);
|
135
|
+
expect(deserializedTeam['config'].idea).toBe('Test serialization');
|
136
|
+
});
|
137
|
+
|
138
|
+
afterAll(() => {
|
139
|
+
// Clean up test storage
|
140
|
+
// TODO: Implement cleanup
|
141
|
+
});
|
142
|
+
});
|
143
|
+
});
|
@@ -0,0 +1,170 @@
|
|
1
|
+
import { describe, expect, test, mock, beforeEach, afterEach } from 'bun:test';
|
2
|
+
import { BaseTool } from '../src/tools/base-tool';
|
3
|
+
import { FileSystemTool } from '../src/tools/file-system';
|
4
|
+
import type { ToolConfig, ToolResult } from '../src/types/tool';
|
5
|
+
import { promises as fs } from 'fs';
|
6
|
+
import { join } from 'path';
|
7
|
+
|
8
|
+
// 创建测试工具类
|
9
|
+
class TestTool extends BaseTool {
|
10
|
+
constructor(config?: Partial<ToolConfig>) {
|
11
|
+
super({
|
12
|
+
name: 'test_tool',
|
13
|
+
description: 'Test tool',
|
14
|
+
version: '1.0.0',
|
15
|
+
category: 'test',
|
16
|
+
...config,
|
17
|
+
});
|
18
|
+
}
|
19
|
+
|
20
|
+
async execute(args?: Record<string, any>): Promise<ToolResult> {
|
21
|
+
const input = args?.input || 'default';
|
22
|
+
return this.createResult(true, `Processed: ${input}`);
|
23
|
+
}
|
24
|
+
}
|
25
|
+
|
26
|
+
describe('Tool System', () => {
|
27
|
+
describe('BaseTool', () => {
|
28
|
+
test('should initialize correctly', () => {
|
29
|
+
const tool = new TestTool({
|
30
|
+
name: 'custom_tool',
|
31
|
+
description: 'Custom tool',
|
32
|
+
version: '2.0.0',
|
33
|
+
category: 'custom',
|
34
|
+
});
|
35
|
+
|
36
|
+
expect(tool.name).toBe('custom_tool');
|
37
|
+
expect(tool.description).toBe('Custom tool');
|
38
|
+
expect(tool.version).toBe('2.0.0');
|
39
|
+
expect(tool.category).toBe('custom');
|
40
|
+
});
|
41
|
+
|
42
|
+
test('should handle args correctly', async () => {
|
43
|
+
const tool = new TestTool();
|
44
|
+
const result = await tool.execute({ input: 'test input' });
|
45
|
+
expect(result.success).toBe(true);
|
46
|
+
expect(result.message).toBe('Processed: test input');
|
47
|
+
});
|
48
|
+
|
49
|
+
test('should validate tool availability', async () => {
|
50
|
+
const tool = new TestTool();
|
51
|
+
const isValid = await tool.validate();
|
52
|
+
expect(isValid).toBe(true);
|
53
|
+
});
|
54
|
+
|
55
|
+
test('should provide help information', () => {
|
56
|
+
const tool = new TestTool();
|
57
|
+
const help = tool.getHelp();
|
58
|
+
expect(help).toContain('Test tool');
|
59
|
+
expect(help).toContain('1.0.0');
|
60
|
+
expect(help).toContain('test');
|
61
|
+
});
|
62
|
+
});
|
63
|
+
|
64
|
+
describe('FileSystemTool', () => {
|
65
|
+
const testDir = './test-files';
|
66
|
+
const testFile = join(testDir, 'test.txt');
|
67
|
+
const testContent = 'Hello, World!';
|
68
|
+
|
69
|
+
beforeEach(async () => {
|
70
|
+
await fs.mkdir(testDir, { recursive: true });
|
71
|
+
});
|
72
|
+
|
73
|
+
afterEach(async () => {
|
74
|
+
try {
|
75
|
+
await fs.rm(testDir, { recursive: true, force: true });
|
76
|
+
} catch (error) {
|
77
|
+
console.error('Failed to cleanup:', error);
|
78
|
+
}
|
79
|
+
});
|
80
|
+
|
81
|
+
test('should write and read file', async () => {
|
82
|
+
const tool = new FileSystemTool();
|
83
|
+
|
84
|
+
// Write file
|
85
|
+
const writeResult = await tool.execute({
|
86
|
+
operation: 'write',
|
87
|
+
path: testFile,
|
88
|
+
content: testContent,
|
89
|
+
});
|
90
|
+
expect(writeResult.success).toBe(true);
|
91
|
+
|
92
|
+
// Read file
|
93
|
+
const readResult = await tool.execute({
|
94
|
+
operation: 'read',
|
95
|
+
path: testFile,
|
96
|
+
});
|
97
|
+
expect(readResult.success).toBe(true);
|
98
|
+
expect(readResult.data?.content).toBe(testContent);
|
99
|
+
});
|
100
|
+
|
101
|
+
test('should list directory', async () => {
|
102
|
+
const tool = new FileSystemTool();
|
103
|
+
|
104
|
+
// Create test files
|
105
|
+
await fs.writeFile(join(testDir, 'file1.txt'), 'content1');
|
106
|
+
await fs.writeFile(join(testDir, 'file2.txt'), 'content2');
|
107
|
+
await fs.mkdir(join(testDir, 'subdir'), { recursive: true });
|
108
|
+
|
109
|
+
const result = await tool.execute({
|
110
|
+
operation: 'list',
|
111
|
+
path: testDir,
|
112
|
+
});
|
113
|
+
|
114
|
+
expect(result.success).toBe(true);
|
115
|
+
expect(result.data?.files).toHaveLength(3);
|
116
|
+
expect(result.data?.files.some((f: any) => f.name === 'file1.txt')).toBe(true);
|
117
|
+
expect(result.data?.files.some((f: any) => f.name === 'subdir' && f.isDirectory)).toBe(true);
|
118
|
+
});
|
119
|
+
|
120
|
+
test('should delete file', async () => {
|
121
|
+
const tool = new FileSystemTool();
|
122
|
+
|
123
|
+
// Create and then delete file
|
124
|
+
await fs.writeFile(testFile, testContent);
|
125
|
+
const result = await tool.execute({
|
126
|
+
operation: 'delete',
|
127
|
+
path: testFile,
|
128
|
+
});
|
129
|
+
|
130
|
+
expect(result.success).toBe(true);
|
131
|
+
await expect(fs.access(testFile)).rejects.toThrow();
|
132
|
+
});
|
133
|
+
|
134
|
+
test('should handle missing file', async () => {
|
135
|
+
const tool = new FileSystemTool();
|
136
|
+
|
137
|
+
const result = await tool.execute({
|
138
|
+
operation: 'read',
|
139
|
+
path: join(testDir, 'non-existent.txt'),
|
140
|
+
});
|
141
|
+
|
142
|
+
expect(result.success).toBe(false);
|
143
|
+
expect(result.message).toContain('Failed to read file');
|
144
|
+
});
|
145
|
+
|
146
|
+
test('should handle invalid operations', async () => {
|
147
|
+
const tool = new FileSystemTool();
|
148
|
+
|
149
|
+
const result = await tool.execute({
|
150
|
+
operation: 'invalid',
|
151
|
+
path: testFile,
|
152
|
+
});
|
153
|
+
|
154
|
+
expect(result.success).toBe(false);
|
155
|
+
expect(result.message).toContain('Unknown operation');
|
156
|
+
});
|
157
|
+
|
158
|
+
test.skip('should track errors', async () => {
|
159
|
+
const tool = new FileSystemTool();
|
160
|
+
|
161
|
+
await tool.execute({
|
162
|
+
operation: 'read',
|
163
|
+
path: join(testDir, 'non-existent.txt'),
|
164
|
+
});
|
165
|
+
|
166
|
+
expect(tool.getState('lastError')).toBeDefined();
|
167
|
+
expect(tool.getState('errorTimestamp')).toBeDefined();
|
168
|
+
});
|
169
|
+
});
|
170
|
+
});
|
@@ -0,0 +1,181 @@
|
|
1
|
+
import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
|
2
|
+
import path from 'path';
|
3
|
+
import fs from 'fs/promises';
|
4
|
+
import {
|
5
|
+
DocumentStatus,
|
6
|
+
DocumentImpl,
|
7
|
+
IndexableDocumentImpl,
|
8
|
+
RepoImpl,
|
9
|
+
} from '../../src/types/document';
|
10
|
+
|
11
|
+
describe('Document System', () => {
|
12
|
+
const testDir = path.join(process.cwd(), 'test-docs');
|
13
|
+
const testFile = path.join(testDir, 'test.txt');
|
14
|
+
const testContent = 'Test content';
|
15
|
+
|
16
|
+
beforeEach(async () => {
|
17
|
+
await fs.mkdir(testDir, { recursive: true });
|
18
|
+
});
|
19
|
+
|
20
|
+
afterEach(async () => {
|
21
|
+
try {
|
22
|
+
await fs.rm(testDir, { recursive: true, force: true });
|
23
|
+
} catch (error) {
|
24
|
+
// Ignore errors
|
25
|
+
}
|
26
|
+
});
|
27
|
+
|
28
|
+
describe('DocumentImpl', () => {
|
29
|
+
test('creates document with default values', () => {
|
30
|
+
const doc = new DocumentImpl();
|
31
|
+
expect(doc.name).toBe('');
|
32
|
+
expect(doc.content).toBe('');
|
33
|
+
expect(doc.author).toBe('');
|
34
|
+
expect(doc.status).toBe(DocumentStatus.DRAFT);
|
35
|
+
expect(doc.reviews).toEqual([]);
|
36
|
+
});
|
37
|
+
|
38
|
+
test('creates document from text', () => {
|
39
|
+
const doc = DocumentImpl.fromText(testContent, testFile);
|
40
|
+
expect(doc.content).toBe(testContent);
|
41
|
+
expect(doc.path).toBe(testFile);
|
42
|
+
expect(doc.name).toBe(path.basename(testFile));
|
43
|
+
});
|
44
|
+
|
45
|
+
test('creates document from path', async () => {
|
46
|
+
await fs.writeFile(testFile, testContent);
|
47
|
+
const doc = await DocumentImpl.fromPath(testFile);
|
48
|
+
expect(doc.content).toBe(testContent);
|
49
|
+
expect(doc.path).toBe(testFile);
|
50
|
+
expect(doc.name).toBe(path.basename(testFile));
|
51
|
+
});
|
52
|
+
|
53
|
+
test('saves document to path', async () => {
|
54
|
+
const doc = new DocumentImpl({
|
55
|
+
content: testContent,
|
56
|
+
path: testFile,
|
57
|
+
});
|
58
|
+
await doc.toPath();
|
59
|
+
const content = await fs.readFile(testFile, 'utf-8');
|
60
|
+
expect(content).toBe(testContent);
|
61
|
+
});
|
62
|
+
|
63
|
+
test('throws error when saving without path', async () => {
|
64
|
+
const doc = new DocumentImpl({ content: testContent });
|
65
|
+
await expect(doc.toPath()).rejects.toThrow('File path is not set');
|
66
|
+
});
|
67
|
+
});
|
68
|
+
|
69
|
+
describe('IndexableDocumentImpl', () => {
|
70
|
+
const jsonFile = path.join(testDir, 'test.json');
|
71
|
+
const csvFile = path.join(testDir, 'test.csv');
|
72
|
+
|
73
|
+
test('creates indexable document from JSON', async () => {
|
74
|
+
const jsonData = { key: 'value', content: 'test content' };
|
75
|
+
await fs.writeFile(jsonFile, JSON.stringify(jsonData));
|
76
|
+
const doc = await IndexableDocumentImpl.fromPath(jsonFile);
|
77
|
+
expect(doc.data).toEqual(jsonData);
|
78
|
+
});
|
79
|
+
|
80
|
+
test('creates indexable document from CSV', async () => {
|
81
|
+
const csvContent = 'header1,header2\nvalue1,value2';
|
82
|
+
await fs.writeFile(csvFile, csvContent);
|
83
|
+
const doc = await IndexableDocumentImpl.fromPath(csvFile);
|
84
|
+
const data = doc.data as string[][];
|
85
|
+
expect(Array.isArray(data)).toBe(true);
|
86
|
+
expect(data[0]).toEqual(['header1', 'header2']);
|
87
|
+
expect(data[1]).toEqual(['value1', 'value2']);
|
88
|
+
});
|
89
|
+
|
90
|
+
test('extracts docs and metadata from array data', () => {
|
91
|
+
const data = [
|
92
|
+
{ content: 'doc1', metadata: 'meta1' },
|
93
|
+
{ content: 'doc2', metadata: 'meta2' },
|
94
|
+
];
|
95
|
+
const doc = new IndexableDocumentImpl({
|
96
|
+
data,
|
97
|
+
contentCol: 'content',
|
98
|
+
metaCol: 'metadata',
|
99
|
+
});
|
100
|
+
const [docs, metadata] = doc.getDocsAndMetadata();
|
101
|
+
expect(docs).toEqual(['doc1', 'doc2']);
|
102
|
+
expect(metadata).toEqual([
|
103
|
+
{ metadata: 'meta1' },
|
104
|
+
{ metadata: 'meta2' },
|
105
|
+
]);
|
106
|
+
});
|
107
|
+
|
108
|
+
test('extracts docs and metadata from record data', () => {
|
109
|
+
const data = {
|
110
|
+
key1: { content: 'doc1', metadata: 'meta1' },
|
111
|
+
key2: { content: 'doc2', metadata: 'meta2' },
|
112
|
+
};
|
113
|
+
const doc = new IndexableDocumentImpl({
|
114
|
+
data,
|
115
|
+
contentCol: 'content',
|
116
|
+
metaCol: 'metadata',
|
117
|
+
});
|
118
|
+
const [docs, metadata] = doc.getDocsAndMetadata();
|
119
|
+
expect(docs).toEqual(['doc1', 'doc2']);
|
120
|
+
expect(metadata).toEqual([
|
121
|
+
{ metadata: 'meta1' },
|
122
|
+
{ metadata: 'meta2' },
|
123
|
+
]);
|
124
|
+
});
|
125
|
+
});
|
126
|
+
|
127
|
+
describe('RepoImpl', () => {
|
128
|
+
test('creates repository with default values', () => {
|
129
|
+
const repo = new RepoImpl();
|
130
|
+
expect(repo.name).toBe('');
|
131
|
+
expect(repo.docs).toEqual({});
|
132
|
+
expect(repo.codes).toEqual({});
|
133
|
+
expect(repo.assets).toEqual({});
|
134
|
+
});
|
135
|
+
|
136
|
+
test('creates repository from path', async () => {
|
137
|
+
// Create test files
|
138
|
+
const mdFile = path.join(testDir, 'test.md');
|
139
|
+
const jsFile = path.join(testDir, 'test.js');
|
140
|
+
const txtFile = path.join(testDir, 'test.txt');
|
141
|
+
|
142
|
+
await fs.writeFile(mdFile, '# Test markdown');
|
143
|
+
await fs.writeFile(jsFile, 'console.log("test");');
|
144
|
+
await fs.writeFile(txtFile, 'Test text');
|
145
|
+
|
146
|
+
const repo = await RepoImpl.fromPath(testDir);
|
147
|
+
expect(repo.name).toBe(path.basename(testDir));
|
148
|
+
expect(Object.keys(repo.docs)).toHaveLength(1); // .md file
|
149
|
+
expect(Object.keys(repo.codes)).toHaveLength(1); // .js file
|
150
|
+
expect(Object.keys(repo.assets)).toHaveLength(1); // .txt file
|
151
|
+
});
|
152
|
+
|
153
|
+
test('sets and gets documents', async () => {
|
154
|
+
const repo = new RepoImpl({ path: testDir });
|
155
|
+
await repo.set('test.md', '# Test');
|
156
|
+
const doc = repo.get('test.md');
|
157
|
+
expect(doc?.content).toBe('# Test');
|
158
|
+
});
|
159
|
+
|
160
|
+
test('gets text documents', async () => {
|
161
|
+
const repo = new RepoImpl({ path: testDir });
|
162
|
+
await repo.set('test.md', '# Test markdown');
|
163
|
+
await repo.set('test.js', 'console.log("test");');
|
164
|
+
await repo.set('test.txt', 'Test text');
|
165
|
+
|
166
|
+
const textDocs = repo.getTextDocuments();
|
167
|
+
expect(textDocs).toHaveLength(2); // .md and .js files
|
168
|
+
});
|
169
|
+
|
170
|
+
test('gets repository metadata', async () => {
|
171
|
+
const repo = new RepoImpl({ path: testDir, name: 'test-repo' });
|
172
|
+
await repo.set('test.md', '# Test');
|
173
|
+
await repo.set('test.js', 'test');
|
174
|
+
|
175
|
+
const metadata = repo.getMetadata();
|
176
|
+
expect(metadata.name).toBe('test-repo');
|
177
|
+
expect(metadata.nDocs).toBe(2);
|
178
|
+
expect(metadata.nChars).toBe(10); // '# Test' (6 chars) + 'test' (4 chars)
|
179
|
+
});
|
180
|
+
});
|
181
|
+
});
|
@@ -0,0 +1,122 @@
|
|
1
|
+
import { describe, test, expect } from 'bun:test';
|
2
|
+
import type { Message } from '../../src/types/message';
|
3
|
+
import {
|
4
|
+
MessageSchema,
|
5
|
+
UserMessage,
|
6
|
+
SystemMessage,
|
7
|
+
AIMessage,
|
8
|
+
AsyncMessageQueue,
|
9
|
+
MESSAGE_ROUTE,
|
10
|
+
} from '../../src/types/message';
|
11
|
+
|
12
|
+
describe('Message System', () => {
|
13
|
+
describe('MessageSchema', () => {
|
14
|
+
test('validates and transforms valid message data', () => {
|
15
|
+
const data = {
|
16
|
+
content: 'test message',
|
17
|
+
role: 'user',
|
18
|
+
sendTo: ['user1', 'user2'],
|
19
|
+
};
|
20
|
+
const message = MessageSchema.parse(data);
|
21
|
+
expect(message.content).toBe('test message');
|
22
|
+
expect(message.role).toBe('user');
|
23
|
+
expect(message.id).toBeDefined();
|
24
|
+
expect(message.sendTo instanceof Set).toBe(true);
|
25
|
+
expect(Array.from(message.sendTo)).toEqual(['user1', 'user2']);
|
26
|
+
});
|
27
|
+
|
28
|
+
test('provides default values for optional fields', () => {
|
29
|
+
const data = {
|
30
|
+
content: 'test message',
|
31
|
+
};
|
32
|
+
const message = MessageSchema.parse(data);
|
33
|
+
expect(message.role).toBe('user');
|
34
|
+
expect(message.causedBy).toBe(MESSAGE_ROUTE.CAUSE_BY);
|
35
|
+
expect(message.sentFrom).toBe(MESSAGE_ROUTE.FROM);
|
36
|
+
expect(message.sendTo instanceof Set).toBe(true);
|
37
|
+
expect(Array.from(message.sendTo)).toEqual([MESSAGE_ROUTE.TO_ALL]);
|
38
|
+
});
|
39
|
+
});
|
40
|
+
|
41
|
+
describe('Message Classes', () => {
|
42
|
+
test('UserMessage creates message with user role', () => {
|
43
|
+
const msg = new UserMessage('test content');
|
44
|
+
expect(msg.role).toBe('user');
|
45
|
+
expect(msg.content).toBe('test content');
|
46
|
+
expect(msg.id).toBeDefined();
|
47
|
+
});
|
48
|
+
|
49
|
+
test('SystemMessage creates message with system role', () => {
|
50
|
+
const msg = new SystemMessage('test content');
|
51
|
+
expect(msg.role).toBe('system');
|
52
|
+
expect(msg.content).toBe('test content');
|
53
|
+
expect(msg.id).toBeDefined();
|
54
|
+
});
|
55
|
+
|
56
|
+
test('AIMessage creates message with assistant role', () => {
|
57
|
+
const msg = new AIMessage('test content');
|
58
|
+
expect(msg.role).toBe('assistant');
|
59
|
+
expect(msg.content).toBe('test content');
|
60
|
+
expect(msg.id).toBeDefined();
|
61
|
+
});
|
62
|
+
});
|
63
|
+
|
64
|
+
describe('AsyncMessageQueue', () => {
|
65
|
+
test('push and pop messages', async () => {
|
66
|
+
const queue = new AsyncMessageQueue();
|
67
|
+
const msg = new UserMessage('test message');
|
68
|
+
queue.push(msg);
|
69
|
+
expect(queue.empty()).toBe(false);
|
70
|
+
|
71
|
+
const popped = await queue.pop();
|
72
|
+
expect(popped).toEqual(msg);
|
73
|
+
expect(queue.empty()).toBe(true);
|
74
|
+
});
|
75
|
+
|
76
|
+
test('pop returns null for empty queue', async () => {
|
77
|
+
const queue = new AsyncMessageQueue();
|
78
|
+
const msg = await queue.pop();
|
79
|
+
expect(msg).toBeNull();
|
80
|
+
});
|
81
|
+
|
82
|
+
test('popAll returns all messages', async () => {
|
83
|
+
const queue = new AsyncMessageQueue();
|
84
|
+
const msgs = [
|
85
|
+
new UserMessage('msg1'),
|
86
|
+
new SystemMessage('msg2'),
|
87
|
+
new AIMessage('msg3'),
|
88
|
+
];
|
89
|
+
msgs.forEach(msg => queue.push(msg));
|
90
|
+
|
91
|
+
const popped = await queue.popAll();
|
92
|
+
expect(popped).toEqual(msgs);
|
93
|
+
expect(queue.empty()).toBe(true);
|
94
|
+
});
|
95
|
+
|
96
|
+
test('dump and load queue state', async () => {
|
97
|
+
const queue = new AsyncMessageQueue();
|
98
|
+
const msgs = [
|
99
|
+
new UserMessage('msg1'),
|
100
|
+
new SystemMessage('msg2'),
|
101
|
+
new AIMessage('msg3'),
|
102
|
+
];
|
103
|
+
msgs.forEach(msg => queue.push(msg));
|
104
|
+
|
105
|
+
const dumped = await queue.dump();
|
106
|
+
const loaded = AsyncMessageQueue.load(dumped);
|
107
|
+
|
108
|
+
expect(loaded.empty()).toBe(false);
|
109
|
+
const loadedMsgs = await loaded.popAll();
|
110
|
+
expect(loadedMsgs.length).toBe(msgs.length);
|
111
|
+
loadedMsgs.forEach((msg, i) => {
|
112
|
+
expect(msg.content).toBe(msgs[i].content);
|
113
|
+
expect(msg.role).toBe(msgs[i].role);
|
114
|
+
});
|
115
|
+
});
|
116
|
+
|
117
|
+
test('handles invalid JSON when loading', () => {
|
118
|
+
const queue = AsyncMessageQueue.load('invalid json');
|
119
|
+
expect(queue.empty()).toBe(true);
|
120
|
+
});
|
121
|
+
});
|
122
|
+
});
|