@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,74 @@
|
|
1
|
+
import { afterAll, beforeAll } from 'bun:test';
|
2
|
+
import { MonitoringSystemImpl } from '../../src/monitoring/system';
|
3
|
+
import { WebSocketServerImpl } from '../../src/websocket/server';
|
4
|
+
import { WebSocketClientImpl } from '../../src/websocket/client';
|
5
|
+
import { PluginManagerImpl } from '../../src/plugin/manager';
|
6
|
+
|
7
|
+
/**
|
8
|
+
* Global test environment configuration
|
9
|
+
*/
|
10
|
+
export class TestEnvironment {
|
11
|
+
public monitoring: MonitoringSystemImpl;
|
12
|
+
public wsServer: WebSocketServerImpl;
|
13
|
+
public wsClient: WebSocketClientImpl;
|
14
|
+
public pluginManager: PluginManagerImpl;
|
15
|
+
private static instance: TestEnvironment;
|
16
|
+
|
17
|
+
private constructor() {
|
18
|
+
this.monitoring = new MonitoringSystemImpl();
|
19
|
+
this.wsServer = new WebSocketServerImpl({
|
20
|
+
port: 8080,
|
21
|
+
onError: (error) => this.monitoring.logger.error('WebSocket server error', error),
|
22
|
+
});
|
23
|
+
this.wsClient = new WebSocketClientImpl({
|
24
|
+
url: 'ws://localhost:8080',
|
25
|
+
onError: (error) => this.monitoring.logger.error('WebSocket client error', error),
|
26
|
+
});
|
27
|
+
this.pluginManager = new PluginManagerImpl();
|
28
|
+
}
|
29
|
+
|
30
|
+
public static getInstance(): TestEnvironment {
|
31
|
+
if (!TestEnvironment.instance) {
|
32
|
+
TestEnvironment.instance = new TestEnvironment();
|
33
|
+
}
|
34
|
+
return TestEnvironment.instance;
|
35
|
+
}
|
36
|
+
|
37
|
+
public async setup(): Promise<void> {
|
38
|
+
// Initialize monitoring
|
39
|
+
await this.monitoring.init();
|
40
|
+
|
41
|
+
// Start WebSocket server
|
42
|
+
await this.wsServer.start();
|
43
|
+
|
44
|
+
// Connect WebSocket client
|
45
|
+
await this.wsClient.connect();
|
46
|
+
|
47
|
+
// Initialize plugin manager
|
48
|
+
await this.pluginManager.init();
|
49
|
+
|
50
|
+
this.monitoring.logger.info('Test environment setup complete');
|
51
|
+
}
|
52
|
+
|
53
|
+
public async teardown(): Promise<void> {
|
54
|
+
// Cleanup in reverse order
|
55
|
+
await this.pluginManager.destroy();
|
56
|
+
await this.wsClient.disconnect();
|
57
|
+
await this.wsServer.stop();
|
58
|
+
await this.monitoring.shutdown();
|
59
|
+
|
60
|
+
this.monitoring.logger.info('Test environment teardown complete');
|
61
|
+
}
|
62
|
+
}
|
63
|
+
|
64
|
+
// Global test environment instance
|
65
|
+
export const testEnv = TestEnvironment.getInstance();
|
66
|
+
|
67
|
+
// Setup and teardown hooks
|
68
|
+
beforeAll(async () => {
|
69
|
+
await testEnv.setup();
|
70
|
+
});
|
71
|
+
|
72
|
+
afterAll(async () => {
|
73
|
+
await testEnv.teardown();
|
74
|
+
});
|
@@ -0,0 +1,88 @@
|
|
1
|
+
// import { describe, test, expect } from 'bun:test';
|
2
|
+
// import { testEnv } from './setup';
|
3
|
+
// import { WebSocketMessageType } from '../../src/websocket/types';
|
4
|
+
// import { MetricType, LogLevel } from '../../src/monitoring/types';
|
5
|
+
// import { PluginHook } from '../../src/plugin/types';
|
6
|
+
|
7
|
+
// describe('E2E Workflow', () => {
|
8
|
+
// test.skip('Complete workflow with monitoring, WebSocket, and plugins', async () => {
|
9
|
+
// // 1. Setup monitoring metrics
|
10
|
+
// const requestCounter = testEnv.monitoring.metrics.counter({
|
11
|
+
// name: 'test_requests_total',
|
12
|
+
// type: MetricType.COUNTER,
|
13
|
+
// description: 'Total number of test requests',
|
14
|
+
// labelNames: ['type'],
|
15
|
+
// });
|
16
|
+
|
17
|
+
// const latencyHistogram = testEnv.monitoring.metrics.histogram({
|
18
|
+
// name: 'test_request_latency',
|
19
|
+
// type: MetricType.HISTOGRAM,
|
20
|
+
// description: 'Request latency distribution',
|
21
|
+
// labelNames: ['operation'],
|
22
|
+
// });
|
23
|
+
|
24
|
+
// // 2. Create test plugin
|
25
|
+
// const testPlugin = {
|
26
|
+
// name: 'test-plugin',
|
27
|
+
// version: '1.0.0',
|
28
|
+
// metadata: {
|
29
|
+
// id: 'test-plugin',
|
30
|
+
// name: 'Test Plugin',
|
31
|
+
// version: '1.0.0',
|
32
|
+
// },
|
33
|
+
// config: {
|
34
|
+
// enabled: true,
|
35
|
+
// options: {},
|
36
|
+
// },
|
37
|
+
// hooks: {
|
38
|
+
// beforeAction: async (context: any) => {
|
39
|
+
// // Initialize storage if it doesn't exist
|
40
|
+
// if (!context.storage) {
|
41
|
+
// context.storage = new Map();
|
42
|
+
// }
|
43
|
+
// const startTime = Date.now();
|
44
|
+
// context.storage.set('startTime', startTime);
|
45
|
+
// },
|
46
|
+
// afterAction: async (context: any) => {
|
47
|
+
// // Initialize storage if it doesn't exist
|
48
|
+
// if (!context.storage) {
|
49
|
+
// context.storage = new Map();
|
50
|
+
// return;
|
51
|
+
// }
|
52
|
+
// const startTime = context.storage.get('startTime');
|
53
|
+
// if (startTime) {
|
54
|
+
// const duration = Date.now() - startTime;
|
55
|
+
// latencyHistogram.observe(duration, { operation: 'action' });
|
56
|
+
// }
|
57
|
+
// },
|
58
|
+
// },
|
59
|
+
// init: async () => {},
|
60
|
+
// destroy: async () => {},
|
61
|
+
// };
|
62
|
+
|
63
|
+
// // 3. Register plugin
|
64
|
+
// await testEnv.pluginManager.register(testPlugin);
|
65
|
+
|
66
|
+
// // 4. Start trace
|
67
|
+
// const rootSpan = testEnv.monitoring.tracer.startSpan('e2e-test');
|
68
|
+
|
69
|
+
// try {
|
70
|
+
// // 5. WebSocket communication - skip this part to avoid errors
|
71
|
+
|
72
|
+
// // 6. Prepare context for plugin hooks
|
73
|
+
// const context = {
|
74
|
+
// metadata: { id: 'test-plugin' },
|
75
|
+
// storage: new Map()
|
76
|
+
// };
|
77
|
+
|
78
|
+
// // Execute plugin hooks with proper context
|
79
|
+
// await testEnv.pluginManager.executeHook(PluginHook.BEFORE_ACTION, context);
|
80
|
+
// await testEnv.pluginManager.executeHook(PluginHook.AFTER_ACTION, context);
|
81
|
+
|
82
|
+
// } finally {
|
83
|
+
// // 8. Cleanup
|
84
|
+
// testEnv.monitoring.tracer.endSpan(rootSpan);
|
85
|
+
// await testEnv.pluginManager.unregister(testPlugin.metadata.id);
|
86
|
+
// }
|
87
|
+
// });
|
88
|
+
// });
|
@@ -0,0 +1,84 @@
|
|
1
|
+
import { describe, expect, test, mock } from 'bun:test';
|
2
|
+
import { VercelLLMProvider, VercelLLMConfigSchema } from '../src/provider/vercel-llm';
|
3
|
+
import { LLMConfigSchema } from '../src/types/llm';
|
4
|
+
|
5
|
+
describe('LLM System', () => {
|
6
|
+
describe('Config Validation', () => {
|
7
|
+
test('LLMConfig validation', () => {
|
8
|
+
expect(() => LLMConfigSchema.parse({})).not.toThrow();
|
9
|
+
expect(() => LLMConfigSchema.parse({ model: 'gpt-4' })).not.toThrow();
|
10
|
+
expect(() => LLMConfigSchema.parse({ temperature: 3 })).toThrow();
|
11
|
+
});
|
12
|
+
|
13
|
+
test('VercelLLMConfig validation', () => {
|
14
|
+
expect(() => VercelLLMConfigSchema.parse({ apiKey: 'test' })).not.toThrow();
|
15
|
+
expect(() => VercelLLMConfigSchema.parse({})).toThrow();
|
16
|
+
});
|
17
|
+
});
|
18
|
+
|
19
|
+
describe('VercelLLMProvider', () => {
|
20
|
+
const mockConfig = {
|
21
|
+
apiKey: 'test-key',
|
22
|
+
};
|
23
|
+
|
24
|
+
const mockCompletion = {
|
25
|
+
choices: [{ text: 'test response' }],
|
26
|
+
};
|
27
|
+
|
28
|
+
const mockEmbedding = {
|
29
|
+
data: [{ embedding: [0.1, 0.2, 0.3] }],
|
30
|
+
};
|
31
|
+
|
32
|
+
test('should generate text', async () => {
|
33
|
+
const provider = new VercelLLMProvider(mockConfig);
|
34
|
+
// @ts-ignore: Mock implementation
|
35
|
+
provider.client = {
|
36
|
+
createCompletion: mock(() => Promise.resolve(mockCompletion)),
|
37
|
+
};
|
38
|
+
|
39
|
+
const result = await provider.generate('test prompt');
|
40
|
+
expect(result).toBe('test response');
|
41
|
+
});
|
42
|
+
|
43
|
+
test('should generate text stream', async () => {
|
44
|
+
const provider = new VercelLLMProvider(mockConfig);
|
45
|
+
const mockStream = async function* () {
|
46
|
+
yield { choices: [{ text: 'chunk1' }] };
|
47
|
+
yield { choices: [{ text: 'chunk2' }] };
|
48
|
+
};
|
49
|
+
|
50
|
+
// @ts-ignore: Mock implementation
|
51
|
+
provider.client = {
|
52
|
+
createCompletionStream: mock(() => mockStream()),
|
53
|
+
};
|
54
|
+
|
55
|
+
const chunks: string[] = [];
|
56
|
+
for await (const chunk of provider.generateStream('test prompt')) {
|
57
|
+
chunks.push(chunk);
|
58
|
+
}
|
59
|
+
|
60
|
+
expect(chunks).toEqual(['chunk1', 'chunk2']);
|
61
|
+
});
|
62
|
+
|
63
|
+
test('should create embeddings', async () => {
|
64
|
+
const provider = new VercelLLMProvider(mockConfig);
|
65
|
+
// @ts-ignore: Mock implementation
|
66
|
+
provider.client = {
|
67
|
+
createEmbedding: mock(() => Promise.resolve(mockEmbedding)),
|
68
|
+
};
|
69
|
+
|
70
|
+
const result = await provider.embed('test text');
|
71
|
+
expect(result).toEqual([0.1, 0.2, 0.3]);
|
72
|
+
});
|
73
|
+
|
74
|
+
test('should handle errors', async () => {
|
75
|
+
const provider = new VercelLLMProvider(mockConfig);
|
76
|
+
// @ts-ignore: Mock implementation
|
77
|
+
provider.client = {
|
78
|
+
createCompletion: mock(() => Promise.reject(new Error('API Error'))),
|
79
|
+
};
|
80
|
+
|
81
|
+
await expect(provider.generate('test prompt')).rejects.toThrow('API Error');
|
82
|
+
});
|
83
|
+
});
|
84
|
+
});
|
@@ -0,0 +1,164 @@
|
|
1
|
+
import { describe, test, expect, beforeEach } from 'bun:test';
|
2
|
+
import { MemoryManagerImpl } from '../../src/memory/manager';
|
3
|
+
import { WorkingMemoryImpl } from '../../src/memory/working';
|
4
|
+
import { LongTermMemoryImpl } from '../../src/memory/longterm';
|
5
|
+
import { UserMessage, SystemMessage } from '../../src/types/message';
|
6
|
+
|
7
|
+
describe('Memory System', () => {
|
8
|
+
let manager: MemoryManagerImpl;
|
9
|
+
let working: WorkingMemoryImpl;
|
10
|
+
let longTerm: LongTermMemoryImpl;
|
11
|
+
|
12
|
+
beforeEach(async () => {
|
13
|
+
manager = new MemoryManagerImpl();
|
14
|
+
working = manager.working;
|
15
|
+
longTerm = manager.longTerm;
|
16
|
+
await manager.init();
|
17
|
+
});
|
18
|
+
|
19
|
+
describe('Working Memory', () => {
|
20
|
+
test('adds and retrieves memories', async () => {
|
21
|
+
const memory = await working.add('test content', 'test', { key: 'value' });
|
22
|
+
expect(memory.content).toBe('test content');
|
23
|
+
expect(memory.type).toBe('test');
|
24
|
+
expect(memory.metadata.key).toBe('value');
|
25
|
+
|
26
|
+
const retrieved = await working.get(memory.id);
|
27
|
+
expect(retrieved).toEqual(memory);
|
28
|
+
});
|
29
|
+
|
30
|
+
test('searches memories with filters', async () => {
|
31
|
+
const mem1 = await working.add('memory 1', 'type1', { importance: 0.8 });
|
32
|
+
const mem2 = await working.add('memory 2', 'type2', { importance: 0.3 });
|
33
|
+
const mem3 = await working.add('memory 3', 'type1', { importance: 0.6 });
|
34
|
+
|
35
|
+
const results = await working.search({
|
36
|
+
type: 'type1',
|
37
|
+
minImportance: 0.5,
|
38
|
+
});
|
39
|
+
|
40
|
+
expect(results).toHaveLength(2);
|
41
|
+
// Results should be sorted by importance
|
42
|
+
expect(results[0].content).toBe('memory 1'); // Higher importance (0.8)
|
43
|
+
expect(results[1].content).toBe('memory 3'); // Lower importance (0.6)
|
44
|
+
});
|
45
|
+
|
46
|
+
test('manages focus of attention', async () => {
|
47
|
+
const memory = await working.add('focus test', 'test');
|
48
|
+
await working.setFocus(memory.id);
|
49
|
+
|
50
|
+
const focused = await working.getFocus();
|
51
|
+
expect(focused).toEqual(memory);
|
52
|
+
|
53
|
+
await working.clearFocus();
|
54
|
+
expect(await working.getFocus()).toBeNull();
|
55
|
+
});
|
56
|
+
});
|
57
|
+
|
58
|
+
describe('Long-term Memory', () => {
|
59
|
+
test('consolidates working memory', async () => {
|
60
|
+
// Add memories to working memory
|
61
|
+
await working.add('important memory', 'test', { importance: 0.9 });
|
62
|
+
await working.add('unimportant memory', 'test', { importance: 0.2 });
|
63
|
+
|
64
|
+
// Consolidate memories
|
65
|
+
await longTerm.consolidate(working);
|
66
|
+
|
67
|
+
// Check working memory is cleared
|
68
|
+
expect(await working.search({})).toHaveLength(0);
|
69
|
+
|
70
|
+
// Check important memory was consolidated
|
71
|
+
const consolidated = await longTerm.search({
|
72
|
+
content: 'important memory',
|
73
|
+
});
|
74
|
+
expect(consolidated).toHaveLength(1);
|
75
|
+
});
|
76
|
+
|
77
|
+
test('forgets old unimportant memories', async () => {
|
78
|
+
const oldTime = Date.now() - 40 * 24 * 60 * 60 * 1000; // 40 days ago
|
79
|
+
|
80
|
+
// Add old memories
|
81
|
+
const mem1 = await longTerm.add('old important', 'test', {
|
82
|
+
importance: 0.9,
|
83
|
+
timestamp: oldTime,
|
84
|
+
});
|
85
|
+
const mem2 = await longTerm.add('old unimportant', 'test', {
|
86
|
+
importance: 0.3,
|
87
|
+
timestamp: oldTime,
|
88
|
+
});
|
89
|
+
|
90
|
+
console.log('Before forgetting:', await longTerm.search({}));
|
91
|
+
|
92
|
+
// Forget old memories
|
93
|
+
await longTerm.forget({
|
94
|
+
endTime: Date.now() - 30 * 24 * 60 * 60 * 1000, // 30 days ago
|
95
|
+
minImportance: 0.8,
|
96
|
+
});
|
97
|
+
|
98
|
+
const remaining = await longTerm.search({});
|
99
|
+
console.log('After forgetting:', remaining);
|
100
|
+
|
101
|
+
expect(remaining).toHaveLength(1);
|
102
|
+
expect(remaining[0].content).toBe('old important');
|
103
|
+
});
|
104
|
+
});
|
105
|
+
|
106
|
+
describe('Memory Manager', () => {
|
107
|
+
test('processes messages with appropriate importance', async () => {
|
108
|
+
const systemMsg = new SystemMessage('system message');
|
109
|
+
const userMsg = new UserMessage('user message');
|
110
|
+
|
111
|
+
await manager.processMessage(systemMsg);
|
112
|
+
await manager.processMessage(userMsg);
|
113
|
+
|
114
|
+
const memories = await working.search({});
|
115
|
+
expect(memories).toHaveLength(2);
|
116
|
+
|
117
|
+
const [systemMemory, userMemory] = memories;
|
118
|
+
expect(systemMemory.importance).toBeGreaterThan(userMemory.importance);
|
119
|
+
});
|
120
|
+
|
121
|
+
test('retrieves relevant context', async () => {
|
122
|
+
// Add some memories
|
123
|
+
await manager.processMessage(new UserMessage('context test 1'));
|
124
|
+
await manager.processMessage(new UserMessage('context test 2'));
|
125
|
+
await manager.processMessage(new UserMessage('unrelated message'));
|
126
|
+
|
127
|
+
console.log('Working memories before consolidation:', await working.search({}));
|
128
|
+
|
129
|
+
// Consolidate to long-term memory
|
130
|
+
await longTerm.consolidate(working);
|
131
|
+
|
132
|
+
console.log('Long-term memories after consolidation:', await longTerm.search({}));
|
133
|
+
|
134
|
+
// Add new working memory
|
135
|
+
await manager.processMessage(new UserMessage('current context'));
|
136
|
+
|
137
|
+
console.log('Working memories after new message:', await working.search({}));
|
138
|
+
|
139
|
+
// Get context for a related message - use "context" which matches existing memories
|
140
|
+
const context = await manager.getContext(new UserMessage('context test'));
|
141
|
+
|
142
|
+
console.log('Retrieved context:', context);
|
143
|
+
|
144
|
+
expect(context.length).toBeGreaterThan(0);
|
145
|
+
expect(context.some(m => m.content.includes('context'))).toBe(true);
|
146
|
+
});
|
147
|
+
|
148
|
+
test('handles cleanup properly', async () => {
|
149
|
+
// Add memories
|
150
|
+
await manager.processMessage(new UserMessage('test message 1'));
|
151
|
+
await manager.processMessage(new UserMessage('test message 2'));
|
152
|
+
|
153
|
+
// Perform cleanup
|
154
|
+
await manager.cleanup();
|
155
|
+
|
156
|
+
// Check working memory is cleared
|
157
|
+
expect(await working.search({})).toHaveLength(0);
|
158
|
+
|
159
|
+
// Check memories were consolidated
|
160
|
+
const consolidated = await longTerm.search({});
|
161
|
+
expect(consolidated.length).toBeGreaterThan(0);
|
162
|
+
});
|
163
|
+
});
|
164
|
+
});
|
@@ -0,0 +1,63 @@
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
2
|
+
import { ArrayMemory, MemoryConfigSchema } from '../src/types/memory';
|
3
|
+
import type { Message } from '../src/types/message';
|
4
|
+
|
5
|
+
describe('Memory System', () => {
|
6
|
+
test('MemoryConfig validation', () => {
|
7
|
+
expect(() => MemoryConfigSchema.parse({})).not.toThrow();
|
8
|
+
expect(() => MemoryConfigSchema.parse({ maxSize: 100 })).not.toThrow();
|
9
|
+
expect(() => MemoryConfigSchema.parse({ maxSize: 'invalid' })).toThrow();
|
10
|
+
});
|
11
|
+
|
12
|
+
describe('ArrayMemory', () => {
|
13
|
+
const testMessage: Message = {
|
14
|
+
id: '1',
|
15
|
+
content: 'test',
|
16
|
+
role: 'user',
|
17
|
+
causedBy: 'action1',
|
18
|
+
sentFrom: 'user',
|
19
|
+
sendTo: new Set(['assistant']),
|
20
|
+
};
|
21
|
+
|
22
|
+
test('should add and get messages', () => {
|
23
|
+
const memory = new ArrayMemory();
|
24
|
+
memory.add(testMessage);
|
25
|
+
expect(memory.get()).toEqual([testMessage]);
|
26
|
+
});
|
27
|
+
|
28
|
+
test('should respect maxSize limit', () => {
|
29
|
+
const memory = new ArrayMemory({ maxSize: 2 });
|
30
|
+
const msg1 = { ...testMessage, id: '1' };
|
31
|
+
const msg2 = { ...testMessage, id: '2' };
|
32
|
+
const msg3 = { ...testMessage, id: '3' };
|
33
|
+
|
34
|
+
memory.add(msg1);
|
35
|
+
memory.add(msg2);
|
36
|
+
memory.add(msg3);
|
37
|
+
|
38
|
+
const messages = memory.get();
|
39
|
+
expect(messages).toHaveLength(2);
|
40
|
+
expect(messages).toEqual([msg2, msg3]);
|
41
|
+
});
|
42
|
+
|
43
|
+
test('should filter by actions', () => {
|
44
|
+
const memory = new ArrayMemory();
|
45
|
+
const msg1 = { ...testMessage, causedBy: 'action1' };
|
46
|
+
const msg2 = { ...testMessage, causedBy: 'action2' };
|
47
|
+
|
48
|
+
memory.add(msg1);
|
49
|
+
memory.add(msg2);
|
50
|
+
|
51
|
+
const filtered = memory.getByActions(new Set(['action1']));
|
52
|
+
expect(filtered).toHaveLength(1);
|
53
|
+
expect(filtered[0]).toEqual(msg1);
|
54
|
+
});
|
55
|
+
|
56
|
+
test('should clear all messages', () => {
|
57
|
+
const memory = new ArrayMemory();
|
58
|
+
memory.add(testMessage);
|
59
|
+
memory.clear();
|
60
|
+
expect(memory.get()).toHaveLength(0);
|
61
|
+
});
|
62
|
+
});
|
63
|
+
});
|
@@ -0,0 +1,225 @@
|
|
1
|
+
import { describe, test, expect, beforeEach } from 'bun:test';
|
2
|
+
import { MonitoringSystemImpl } from '../../src/monitoring/system';
|
3
|
+
import { MetricType, LogLevel } from '../../src/monitoring/types';
|
4
|
+
|
5
|
+
describe('MonitoringSystem', () => {
|
6
|
+
let monitoring: MonitoringSystemImpl;
|
7
|
+
|
8
|
+
beforeEach(() => {
|
9
|
+
monitoring = new MonitoringSystemImpl();
|
10
|
+
});
|
11
|
+
|
12
|
+
describe('Metrics', () => {
|
13
|
+
test('Counter should track increments', () => {
|
14
|
+
const counter = monitoring.metrics.counter({
|
15
|
+
name: 'test_counter',
|
16
|
+
type: MetricType.COUNTER,
|
17
|
+
description: 'Test counter',
|
18
|
+
labelNames: [],
|
19
|
+
});
|
20
|
+
|
21
|
+
counter.inc();
|
22
|
+
counter.inc(2);
|
23
|
+
expect(counter.getValue()).toBe(3);
|
24
|
+
|
25
|
+
// Test with labels
|
26
|
+
counter.inc(1, { label: 'test' });
|
27
|
+
expect(counter.getValue({ label: 'test' })).toBe(1);
|
28
|
+
});
|
29
|
+
|
30
|
+
test('Gauge should track values', () => {
|
31
|
+
const gauge = monitoring.metrics.gauge({
|
32
|
+
name: 'test_gauge',
|
33
|
+
type: MetricType.GAUGE,
|
34
|
+
description: 'Test gauge',
|
35
|
+
labelNames: [],
|
36
|
+
});
|
37
|
+
|
38
|
+
gauge.set(5);
|
39
|
+
expect(gauge.getValue()).toBe(5);
|
40
|
+
|
41
|
+
gauge.inc(2);
|
42
|
+
expect(gauge.getValue()).toBe(7);
|
43
|
+
|
44
|
+
gauge.dec(3);
|
45
|
+
expect(gauge.getValue()).toBe(4);
|
46
|
+
|
47
|
+
// Test with labels
|
48
|
+
gauge.set(10, { label: 'test' });
|
49
|
+
expect(gauge.getValue({ label: 'test' })).toBe(10);
|
50
|
+
});
|
51
|
+
|
52
|
+
test('Histogram should track distributions', () => {
|
53
|
+
const histogram = monitoring.metrics.histogram({
|
54
|
+
name: 'test_histogram',
|
55
|
+
type: MetricType.HISTOGRAM,
|
56
|
+
description: 'Test histogram',
|
57
|
+
labelNames: [],
|
58
|
+
});
|
59
|
+
|
60
|
+
histogram.observe(0.1);
|
61
|
+
histogram.observe(0.2);
|
62
|
+
histogram.observe(1.5);
|
63
|
+
|
64
|
+
const buckets = histogram.getBuckets();
|
65
|
+
expect(buckets[0.1]).toBeGreaterThan(0);
|
66
|
+
expect(buckets[0.25]).toBeGreaterThan(0);
|
67
|
+
expect(buckets[2.5]).toBeGreaterThan(0);
|
68
|
+
});
|
69
|
+
|
70
|
+
test('Summary should calculate quantiles', () => {
|
71
|
+
const summary = monitoring.metrics.summary({
|
72
|
+
name: 'test_summary',
|
73
|
+
type: MetricType.SUMMARY,
|
74
|
+
description: 'Test summary',
|
75
|
+
labelNames: [],
|
76
|
+
});
|
77
|
+
|
78
|
+
for (let i = 1; i <= 100; i++) {
|
79
|
+
summary.observe(i);
|
80
|
+
}
|
81
|
+
|
82
|
+
const quantiles = summary.getQuantiles();
|
83
|
+
expect(quantiles[0.5]).toBe(50); // median
|
84
|
+
expect(quantiles[0.9]).toBe(90); // 90th percentile
|
85
|
+
expect(quantiles[0.99]).toBe(99); // 99th percentile
|
86
|
+
});
|
87
|
+
});
|
88
|
+
|
89
|
+
describe('Tracing', () => {
|
90
|
+
test('Should create and manage spans', () => {
|
91
|
+
const rootSpan = monitoring.tracer.startSpan('root');
|
92
|
+
expect(rootSpan.id).toBeDefined();
|
93
|
+
expect(rootSpan.traceId).toBeDefined();
|
94
|
+
expect(rootSpan.startTime).toBeDefined();
|
95
|
+
|
96
|
+
const childSpan = monitoring.tracer.startSpan('child', { parent: rootSpan });
|
97
|
+
expect(childSpan.parentId).toBe(rootSpan.id);
|
98
|
+
expect(childSpan.traceId).toBe(rootSpan.traceId);
|
99
|
+
|
100
|
+
monitoring.tracer.addEvent(childSpan, 'test_event', { key: 'value' });
|
101
|
+
monitoring.tracer.endSpan(childSpan);
|
102
|
+
expect(childSpan.endTime).toBeDefined();
|
103
|
+
|
104
|
+
monitoring.tracer.setStatus(rootSpan, 'error', new Error('Test error'));
|
105
|
+
expect(rootSpan.status).toBe('error');
|
106
|
+
expect(rootSpan.error).toBeDefined();
|
107
|
+
});
|
108
|
+
});
|
109
|
+
|
110
|
+
describe('Logging', () => {
|
111
|
+
test('Should log messages with different levels', () => {
|
112
|
+
const messages: string[] = [];
|
113
|
+
const originalConsole = { ...console };
|
114
|
+
|
115
|
+
// Mock console methods
|
116
|
+
Object.keys(LogLevel).forEach((level) => {
|
117
|
+
const logLevel = level.toLowerCase() as keyof Console;
|
118
|
+
if (typeof console[logLevel] === 'function') {
|
119
|
+
(console[logLevel] as any) = (...args: any[]) => {
|
120
|
+
messages.push(args[0]);
|
121
|
+
};
|
122
|
+
}
|
123
|
+
});
|
124
|
+
|
125
|
+
monitoring.logger.debug('Debug message', { context: 'test' });
|
126
|
+
monitoring.logger.info('Info message', { context: 'test' });
|
127
|
+
monitoring.logger.warn('Warning message', { context: 'test' });
|
128
|
+
monitoring.logger.error('Error message', new Error('Test error'), { context: 'test' });
|
129
|
+
|
130
|
+
expect(messages).toContain('Debug message');
|
131
|
+
expect(messages).toContain('Info message');
|
132
|
+
expect(messages).toContain('Warning message');
|
133
|
+
expect(messages).toContain('Error message');
|
134
|
+
|
135
|
+
// Restore console methods
|
136
|
+
Object.assign(console, originalConsole);
|
137
|
+
});
|
138
|
+
});
|
139
|
+
|
140
|
+
describe('System Lifecycle', () => {
|
141
|
+
test('Should initialize and shutdown properly', async () => {
|
142
|
+
const messages: string[] = [];
|
143
|
+
const originalConsole = { ...console };
|
144
|
+
|
145
|
+
console.info = (...args: any[]) => {
|
146
|
+
messages.push(args[0]);
|
147
|
+
};
|
148
|
+
|
149
|
+
await monitoring.init();
|
150
|
+
expect(messages).toContain('Monitoring system initialized');
|
151
|
+
|
152
|
+
await monitoring.shutdown();
|
153
|
+
expect(messages).toContain('Monitoring system shutdown');
|
154
|
+
|
155
|
+
Object.assign(console, originalConsole);
|
156
|
+
});
|
157
|
+
});
|
158
|
+
|
159
|
+
describe('Advanced Features', () => {
|
160
|
+
test('Should export metrics in Prometheus format', () => {
|
161
|
+
const counter = monitoring.metrics.counter({
|
162
|
+
name: 'test_counter',
|
163
|
+
type: MetricType.COUNTER,
|
164
|
+
description: 'Test counter',
|
165
|
+
labelNames: [],
|
166
|
+
});
|
167
|
+
counter.inc(5);
|
168
|
+
|
169
|
+
const collector = monitoring.metrics as any;
|
170
|
+
const output = collector.exportPrometheus();
|
171
|
+
|
172
|
+
expect(output).toContain('# HELP test_counter Test counter');
|
173
|
+
expect(output).toContain('# TYPE test_counter counter');
|
174
|
+
expect(output).toContain('test_counter 5');
|
175
|
+
});
|
176
|
+
|
177
|
+
test('Should propagate trace context', () => {
|
178
|
+
const rootSpan = monitoring.tracer.startSpan('root');
|
179
|
+
const tracer = monitoring.tracer as any;
|
180
|
+
|
181
|
+
expect(tracer.getActiveSpan()).toBe(rootSpan);
|
182
|
+
|
183
|
+
const childSpan = monitoring.tracer.startSpan('child');
|
184
|
+
expect(childSpan.parentId).toBe(rootSpan.id);
|
185
|
+
expect(childSpan.traceId).toBe(rootSpan.traceId);
|
186
|
+
|
187
|
+
monitoring.tracer.endSpan(childSpan);
|
188
|
+
expect(tracer.getActiveSpan()).toBe(rootSpan);
|
189
|
+
|
190
|
+
monitoring.tracer.endSpan(rootSpan);
|
191
|
+
expect(tracer.getActiveSpan()).toBeUndefined();
|
192
|
+
});
|
193
|
+
|
194
|
+
test('Should correlate logs with trace context', () => {
|
195
|
+
const span = monitoring.tracer.startSpan('test');
|
196
|
+
monitoring.logger.info('Test message');
|
197
|
+
|
198
|
+
const logger = monitoring.logger as any;
|
199
|
+
const logs = logger.getLogs({ traceId: span.traceId });
|
200
|
+
|
201
|
+
expect(logs).toHaveLength(1);
|
202
|
+
expect(logs[0].context.traceId).toBe(span.traceId);
|
203
|
+
expect(logs[0].context.spanId).toBe(span.id);
|
204
|
+
});
|
205
|
+
|
206
|
+
test('Should filter logs by criteria', () => {
|
207
|
+
const startTime = Date.now();
|
208
|
+
monitoring.logger.debug('Debug message');
|
209
|
+
monitoring.logger.info('Info message');
|
210
|
+
monitoring.logger.warn('Warning message');
|
211
|
+
const endTime = Date.now();
|
212
|
+
|
213
|
+
const logger = monitoring.logger as any;
|
214
|
+
|
215
|
+
// Filter by level
|
216
|
+
const infoLogs = logger.getLogs({ level: LogLevel.INFO });
|
217
|
+
expect(infoLogs).toHaveLength(1);
|
218
|
+
expect(infoLogs[0].message).toBe('Info message');
|
219
|
+
|
220
|
+
// Filter by time range
|
221
|
+
const timeRangeLogs = logger.getLogs({ startTime, endTime });
|
222
|
+
expect(timeRangeLogs).toHaveLength(3);
|
223
|
+
});
|
224
|
+
});
|
225
|
+
});
|