@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,187 @@
|
|
1
|
+
import type { Message } from '../types/message';
|
2
|
+
import {
|
3
|
+
WebSocketMessageSchema,
|
4
|
+
WebSocketMessageType,
|
5
|
+
} from './types';
|
6
|
+
import type {
|
7
|
+
WebSocketClient,
|
8
|
+
WebSocketClientOptions,
|
9
|
+
WebSocketMessage,
|
10
|
+
WebSocketStreamOptions,
|
11
|
+
} from './types';
|
12
|
+
|
13
|
+
/**
|
14
|
+
* Browser-compatible WebSocket client implementation
|
15
|
+
*
|
16
|
+
* Features:
|
17
|
+
* - Uses native browser WebSocket API
|
18
|
+
* - Automatic reconnection with configurable attempts and intervals
|
19
|
+
* - Message validation using Zod schema
|
20
|
+
* - Support for streaming large messages in chunks
|
21
|
+
* - Progress tracking for streaming operations
|
22
|
+
* - Error handling and event callbacks
|
23
|
+
*/
|
24
|
+
export class BrowserWebSocketClient implements WebSocketClient {
|
25
|
+
private ws: WebSocket | null = null;
|
26
|
+
private reconnectAttempts = 0;
|
27
|
+
private reconnectTimeout: number | null = null;
|
28
|
+
|
29
|
+
/**
|
30
|
+
* Create a new browser WebSocket client instance
|
31
|
+
* @param options - Configuration options for the client
|
32
|
+
*/
|
33
|
+
constructor(private options: WebSocketClientOptions) {
|
34
|
+
this.options = {
|
35
|
+
reconnect: true,
|
36
|
+
reconnectInterval: 1000,
|
37
|
+
maxReconnectAttempts: 5,
|
38
|
+
...options,
|
39
|
+
};
|
40
|
+
}
|
41
|
+
|
42
|
+
/**
|
43
|
+
* Connect to WebSocket server with automatic reconnection support
|
44
|
+
* @throws Error if connection fails and max reconnection attempts are reached
|
45
|
+
*/
|
46
|
+
public async connect(): Promise<void> {
|
47
|
+
return new Promise((resolve, reject) => {
|
48
|
+
try {
|
49
|
+
this.ws = new WebSocket(this.options.url, this.options.protocols);
|
50
|
+
|
51
|
+
// Handle successful connection
|
52
|
+
this.ws.onopen = () => {
|
53
|
+
this.reconnectAttempts = 0;
|
54
|
+
this.options.onConnect?.();
|
55
|
+
resolve();
|
56
|
+
};
|
57
|
+
|
58
|
+
// Handle incoming messages with validation
|
59
|
+
this.ws.onmessage = (event: MessageEvent) => {
|
60
|
+
try {
|
61
|
+
const message = WebSocketMessageSchema.parse(JSON.parse(event.data));
|
62
|
+
this.options.onMessage?.(message);
|
63
|
+
} catch (error: unknown) {
|
64
|
+
const message = error instanceof Error ? error.message : String(error);
|
65
|
+
this.options.onError?.(new Error(`Invalid message format: ${message}`));
|
66
|
+
}
|
67
|
+
};
|
68
|
+
|
69
|
+
// Handle connection errors
|
70
|
+
this.ws.onerror = (event: Event) => {
|
71
|
+
const error = new Error('WebSocket connection error');
|
72
|
+
this.options.onError?.(error);
|
73
|
+
reject(error);
|
74
|
+
};
|
75
|
+
|
76
|
+
// Handle disconnection and reconnection
|
77
|
+
this.ws.onclose = () => {
|
78
|
+
this.options.onDisconnect?.();
|
79
|
+
if (this.options.reconnect && this.reconnectAttempts < this.options.maxReconnectAttempts!) {
|
80
|
+
this.reconnectTimeout = window.setTimeout(() => {
|
81
|
+
this.reconnectAttempts++;
|
82
|
+
this.connect().catch(error => {
|
83
|
+
if (error instanceof Error) {
|
84
|
+
this.options.onError?.(error);
|
85
|
+
} else {
|
86
|
+
this.options.onError?.(new Error(String(error)));
|
87
|
+
}
|
88
|
+
});
|
89
|
+
}, this.options.reconnectInterval);
|
90
|
+
}
|
91
|
+
};
|
92
|
+
} catch (error: unknown) {
|
93
|
+
if (error instanceof Error) {
|
94
|
+
reject(error);
|
95
|
+
} else {
|
96
|
+
reject(new Error(String(error)));
|
97
|
+
}
|
98
|
+
}
|
99
|
+
});
|
100
|
+
}
|
101
|
+
|
102
|
+
/**
|
103
|
+
* Disconnect from WebSocket server and cleanup resources
|
104
|
+
*/
|
105
|
+
public async disconnect(): Promise<void> {
|
106
|
+
if (this.reconnectTimeout !== null) {
|
107
|
+
window.clearTimeout(this.reconnectTimeout);
|
108
|
+
this.reconnectTimeout = null;
|
109
|
+
}
|
110
|
+
|
111
|
+
if (this.ws) {
|
112
|
+
this.ws.close();
|
113
|
+
this.ws = null;
|
114
|
+
}
|
115
|
+
}
|
116
|
+
|
117
|
+
/**
|
118
|
+
* Send a message to the WebSocket server
|
119
|
+
* @param message - Message to send (either Message or WebSocketMessage format)
|
120
|
+
* @throws Error if client is not connected
|
121
|
+
*/
|
122
|
+
public async send(message: Message | WebSocketMessage): Promise<void> {
|
123
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
124
|
+
throw new Error('WebSocket is not connected');
|
125
|
+
}
|
126
|
+
|
127
|
+
const wsMessage = 'type' in message ? message : {
|
128
|
+
type: WebSocketMessageType.MESSAGE,
|
129
|
+
payload: message,
|
130
|
+
timestamp: Date.now(),
|
131
|
+
id: crypto.randomUUID(),
|
132
|
+
};
|
133
|
+
|
134
|
+
this.ws.send(JSON.stringify(wsMessage));
|
135
|
+
}
|
136
|
+
|
137
|
+
/**
|
138
|
+
* Stream a large message in chunks with progress tracking
|
139
|
+
* @param message - Message to stream
|
140
|
+
* @param options - Streaming options (chunk size, delay, progress callback)
|
141
|
+
* @yields Each chunk of the message
|
142
|
+
*/
|
143
|
+
public async *stream(
|
144
|
+
message: Message,
|
145
|
+
options: WebSocketStreamOptions = {}
|
146
|
+
): AsyncIterableIterator<string> {
|
147
|
+
const {
|
148
|
+
chunkSize = 1024,
|
149
|
+
delay = 0,
|
150
|
+
onProgress,
|
151
|
+
} = options;
|
152
|
+
|
153
|
+
const content = message.content;
|
154
|
+
const totalChunks = Math.ceil(content.length / chunkSize);
|
155
|
+
|
156
|
+
for (let i = 0; i < totalChunks; i++) {
|
157
|
+
const chunk = content.slice(i * chunkSize, (i + 1) * chunkSize);
|
158
|
+
const progress = Math.round(((i + 1) / totalChunks) * 100);
|
159
|
+
|
160
|
+
await this.send({
|
161
|
+
type: i === totalChunks - 1 ? WebSocketMessageType.STREAM_END : WebSocketMessageType.STREAM,
|
162
|
+
payload: {
|
163
|
+
...message,
|
164
|
+
content: chunk,
|
165
|
+
progress,
|
166
|
+
},
|
167
|
+
timestamp: Date.now(),
|
168
|
+
id: crypto.randomUUID(),
|
169
|
+
});
|
170
|
+
|
171
|
+
onProgress?.(progress);
|
172
|
+
yield chunk;
|
173
|
+
|
174
|
+
if (delay > 0) {
|
175
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
176
|
+
}
|
177
|
+
}
|
178
|
+
}
|
179
|
+
|
180
|
+
/**
|
181
|
+
* Check if client is currently connected to the server
|
182
|
+
* @returns true if connected, false otherwise
|
183
|
+
*/
|
184
|
+
public isConnected(): boolean {
|
185
|
+
return this.ws?.readyState === WebSocket.OPEN;
|
186
|
+
}
|
187
|
+
}
|
@@ -0,0 +1,186 @@
|
|
1
|
+
import WebSocket from 'ws';
|
2
|
+
import type { Message } from '../types/message';
|
3
|
+
import {
|
4
|
+
WebSocketMessageSchema,
|
5
|
+
WebSocketMessageType,
|
6
|
+
} from './types';
|
7
|
+
import type {
|
8
|
+
WebSocketClient,
|
9
|
+
WebSocketClientOptions,
|
10
|
+
WebSocketMessage,
|
11
|
+
WebSocketStreamOptions,
|
12
|
+
} from './types';
|
13
|
+
|
14
|
+
/**
|
15
|
+
* WebSocket client implementation with reconnection and streaming support
|
16
|
+
*
|
17
|
+
* Features:
|
18
|
+
* - Automatic reconnection with configurable attempts and intervals
|
19
|
+
* - Message validation using Zod schema
|
20
|
+
* - Support for streaming large messages in chunks
|
21
|
+
* - Progress tracking for streaming operations
|
22
|
+
* - Error handling and event callbacks
|
23
|
+
*/
|
24
|
+
export class WebSocketClientImpl implements WebSocketClient {
|
25
|
+
private ws: WebSocket | null = null;
|
26
|
+
private reconnectAttempts = 0;
|
27
|
+
private reconnectTimeout: ReturnType<typeof setTimeout> | null = null;
|
28
|
+
|
29
|
+
/**
|
30
|
+
* Create a new WebSocket client instance
|
31
|
+
* @param options - Configuration options for the client
|
32
|
+
*/
|
33
|
+
constructor(private options: WebSocketClientOptions) {
|
34
|
+
this.options = {
|
35
|
+
reconnect: true,
|
36
|
+
reconnectInterval: 1000,
|
37
|
+
maxReconnectAttempts: 5,
|
38
|
+
...options,
|
39
|
+
};
|
40
|
+
}
|
41
|
+
|
42
|
+
/**
|
43
|
+
* Connect to WebSocket server with automatic reconnection support
|
44
|
+
* @throws Error if connection fails and max reconnection attempts are reached
|
45
|
+
*/
|
46
|
+
public async connect(): Promise<void> {
|
47
|
+
return new Promise((resolve, reject) => {
|
48
|
+
try {
|
49
|
+
this.ws = new WebSocket(this.options.url, this.options.protocols);
|
50
|
+
|
51
|
+
// Handle successful connection
|
52
|
+
this.ws.on('open', () => {
|
53
|
+
this.reconnectAttempts = 0;
|
54
|
+
this.options.onConnect?.();
|
55
|
+
resolve();
|
56
|
+
});
|
57
|
+
|
58
|
+
// Handle incoming messages with validation
|
59
|
+
this.ws.on('message', (data: string) => {
|
60
|
+
try {
|
61
|
+
const message = WebSocketMessageSchema.parse(JSON.parse(data));
|
62
|
+
this.options.onMessage?.(message);
|
63
|
+
} catch (error: unknown) {
|
64
|
+
const message = error instanceof Error ? error.message : String(error);
|
65
|
+
this.options.onError?.(new Error(`Invalid message format: ${message}`));
|
66
|
+
}
|
67
|
+
});
|
68
|
+
|
69
|
+
// Handle connection errors
|
70
|
+
this.ws.on('error', (error: Error) => {
|
71
|
+
this.options.onError?.(error);
|
72
|
+
reject(error);
|
73
|
+
});
|
74
|
+
|
75
|
+
// Handle disconnection and reconnection
|
76
|
+
this.ws.on('close', () => {
|
77
|
+
this.options.onDisconnect?.();
|
78
|
+
if (this.options.reconnect && this.reconnectAttempts < this.options.maxReconnectAttempts!) {
|
79
|
+
this.reconnectTimeout = setTimeout(() => {
|
80
|
+
this.reconnectAttempts++;
|
81
|
+
this.connect().catch(error => {
|
82
|
+
if (error instanceof Error) {
|
83
|
+
this.options.onError?.(error);
|
84
|
+
} else {
|
85
|
+
this.options.onError?.(new Error(String(error)));
|
86
|
+
}
|
87
|
+
});
|
88
|
+
}, this.options.reconnectInterval);
|
89
|
+
}
|
90
|
+
});
|
91
|
+
} catch (error: unknown) {
|
92
|
+
if (error instanceof Error) {
|
93
|
+
reject(error);
|
94
|
+
} else {
|
95
|
+
reject(new Error(String(error)));
|
96
|
+
}
|
97
|
+
}
|
98
|
+
});
|
99
|
+
}
|
100
|
+
|
101
|
+
/**
|
102
|
+
* Disconnect from WebSocket server and cleanup resources
|
103
|
+
*/
|
104
|
+
public async disconnect(): Promise<void> {
|
105
|
+
if (this.reconnectTimeout) {
|
106
|
+
clearTimeout(this.reconnectTimeout);
|
107
|
+
this.reconnectTimeout = null;
|
108
|
+
}
|
109
|
+
|
110
|
+
if (this.ws) {
|
111
|
+
this.ws.close();
|
112
|
+
this.ws = null;
|
113
|
+
}
|
114
|
+
}
|
115
|
+
|
116
|
+
/**
|
117
|
+
* Send a message to the WebSocket server
|
118
|
+
* @param message - Message to send (either Message or WebSocketMessage format)
|
119
|
+
* @throws Error if client is not connected
|
120
|
+
*/
|
121
|
+
public async send(message: Message | WebSocketMessage): Promise<void> {
|
122
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
123
|
+
throw new Error('WebSocket is not connected');
|
124
|
+
}
|
125
|
+
|
126
|
+
const wsMessage = 'type' in message ? message : {
|
127
|
+
type: WebSocketMessageType.MESSAGE,
|
128
|
+
payload: message,
|
129
|
+
timestamp: Date.now(),
|
130
|
+
id: crypto.randomUUID(),
|
131
|
+
};
|
132
|
+
|
133
|
+
this.ws.send(JSON.stringify(wsMessage));
|
134
|
+
}
|
135
|
+
|
136
|
+
/**
|
137
|
+
* Stream a large message in chunks with progress tracking
|
138
|
+
* @param message - Message to stream
|
139
|
+
* @param options - Streaming options (chunk size, delay, progress callback)
|
140
|
+
* @yields Each chunk of the message
|
141
|
+
*/
|
142
|
+
public async *stream(
|
143
|
+
message: Message,
|
144
|
+
options: WebSocketStreamOptions = {}
|
145
|
+
): AsyncIterableIterator<string> {
|
146
|
+
const {
|
147
|
+
chunkSize = 1024,
|
148
|
+
delay = 0,
|
149
|
+
onProgress,
|
150
|
+
} = options;
|
151
|
+
|
152
|
+
const content = message.content;
|
153
|
+
const totalChunks = Math.ceil(content.length / chunkSize);
|
154
|
+
|
155
|
+
for (let i = 0; i < totalChunks; i++) {
|
156
|
+
const chunk = content.slice(i * chunkSize, (i + 1) * chunkSize);
|
157
|
+
const progress = Math.round(((i + 1) / totalChunks) * 100);
|
158
|
+
|
159
|
+
await this.send({
|
160
|
+
type: i === totalChunks - 1 ? WebSocketMessageType.STREAM_END : WebSocketMessageType.STREAM,
|
161
|
+
payload: {
|
162
|
+
...message,
|
163
|
+
content: chunk,
|
164
|
+
progress,
|
165
|
+
},
|
166
|
+
timestamp: Date.now(),
|
167
|
+
id: crypto.randomUUID(),
|
168
|
+
});
|
169
|
+
|
170
|
+
onProgress?.(progress);
|
171
|
+
yield chunk;
|
172
|
+
|
173
|
+
if (delay > 0) {
|
174
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
175
|
+
}
|
176
|
+
}
|
177
|
+
}
|
178
|
+
|
179
|
+
/**
|
180
|
+
* Check if client is currently connected to the server
|
181
|
+
* @returns true if connected, false otherwise
|
182
|
+
*/
|
183
|
+
public isConnected(): boolean {
|
184
|
+
return this.ws?.readyState === WebSocket.OPEN;
|
185
|
+
}
|
186
|
+
}
|
@@ -0,0 +1,169 @@
|
|
1
|
+
import { WebSocketServer as WSServer } from 'ws';
|
2
|
+
import type { Message } from '../types/message';
|
3
|
+
import {
|
4
|
+
WebSocketMessageSchema,
|
5
|
+
WebSocketMessageType,
|
6
|
+
} from './types';
|
7
|
+
import type {
|
8
|
+
WebSocketMessage,
|
9
|
+
WebSocketServer,
|
10
|
+
WebSocketServerOptions,
|
11
|
+
} from './types';
|
12
|
+
|
13
|
+
/**
|
14
|
+
* WebSocket server implementation with client management and broadcasting support
|
15
|
+
*
|
16
|
+
* Features:
|
17
|
+
* - Client connection management
|
18
|
+
* - Message validation using Zod schema
|
19
|
+
* - Broadcasting to all connected clients
|
20
|
+
* - Automatic cleanup of disconnected clients
|
21
|
+
* - Error handling and event callbacks
|
22
|
+
*/
|
23
|
+
export class WebSocketServerImpl implements WebSocketServer {
|
24
|
+
private wss: WSServer | null = null;
|
25
|
+
private clients: Set<WebSocket> = new Set();
|
26
|
+
|
27
|
+
/**
|
28
|
+
* Create a new WebSocket server instance
|
29
|
+
* @param options - Configuration options for the server
|
30
|
+
*/
|
31
|
+
constructor(private options: WebSocketServerOptions) {}
|
32
|
+
|
33
|
+
/**
|
34
|
+
* Start the WebSocket server and begin accepting connections
|
35
|
+
* @throws Error if server fails to start
|
36
|
+
*/
|
37
|
+
public async start(): Promise<void> {
|
38
|
+
return new Promise((resolve, reject) => {
|
39
|
+
try {
|
40
|
+
this.wss = new WSServer({
|
41
|
+
port: this.options.port,
|
42
|
+
host: this.options.host,
|
43
|
+
path: this.options.path,
|
44
|
+
});
|
45
|
+
|
46
|
+
// Handle new client connections
|
47
|
+
this.wss.on('connection', (client: WebSocket) => {
|
48
|
+
this.clients.add(client);
|
49
|
+
this.options.onConnection?.(client);
|
50
|
+
|
51
|
+
// Handle incoming messages with validation
|
52
|
+
client.on('message', async (data: string) => {
|
53
|
+
try {
|
54
|
+
const message = WebSocketMessageSchema.parse(JSON.parse(data));
|
55
|
+
this.options.onMessage?.(message, client);
|
56
|
+
} catch (error: unknown) {
|
57
|
+
const message = error instanceof Error ? error.message : String(error);
|
58
|
+
this.options.onError?.(new Error(`Invalid message format: ${message}`));
|
59
|
+
}
|
60
|
+
});
|
61
|
+
|
62
|
+
// Remove client on disconnection
|
63
|
+
client.on('close', () => {
|
64
|
+
this.clients.delete(client);
|
65
|
+
});
|
66
|
+
|
67
|
+
// Handle client errors and cleanup
|
68
|
+
client.on('error', (error: Error) => {
|
69
|
+
this.options.onError?.(error);
|
70
|
+
this.clients.delete(client);
|
71
|
+
});
|
72
|
+
});
|
73
|
+
|
74
|
+
// Handle server errors
|
75
|
+
this.wss.on('error', (error: Error) => {
|
76
|
+
this.options.onError?.(error);
|
77
|
+
reject(error);
|
78
|
+
});
|
79
|
+
|
80
|
+
// Server is ready to accept connections
|
81
|
+
this.wss.on('listening', () => {
|
82
|
+
resolve();
|
83
|
+
});
|
84
|
+
} catch (error: unknown) {
|
85
|
+
if (error instanceof Error) {
|
86
|
+
reject(error);
|
87
|
+
} else {
|
88
|
+
reject(new Error(String(error)));
|
89
|
+
}
|
90
|
+
}
|
91
|
+
});
|
92
|
+
}
|
93
|
+
|
94
|
+
/**
|
95
|
+
* Stop the WebSocket server and close all client connections
|
96
|
+
* @throws Error if server fails to stop cleanly
|
97
|
+
*/
|
98
|
+
public async stop(): Promise<void> {
|
99
|
+
return new Promise((resolve, reject) => {
|
100
|
+
if (!this.wss) {
|
101
|
+
resolve();
|
102
|
+
return;
|
103
|
+
}
|
104
|
+
|
105
|
+
this.wss.close((error) => {
|
106
|
+
if (error) {
|
107
|
+
reject(error);
|
108
|
+
} else {
|
109
|
+
this.wss = null;
|
110
|
+
this.clients.clear();
|
111
|
+
resolve();
|
112
|
+
}
|
113
|
+
});
|
114
|
+
});
|
115
|
+
}
|
116
|
+
|
117
|
+
/**
|
118
|
+
* Broadcast a message to all connected clients
|
119
|
+
* @param message - Message to broadcast (either Message or WebSocketMessage format)
|
120
|
+
* Automatically removes disconnected clients during broadcast
|
121
|
+
*/
|
122
|
+
public async broadcast(message: Message | WebSocketMessage): Promise<void> {
|
123
|
+
const wsMessage = 'type' in message ? message : {
|
124
|
+
type: WebSocketMessageType.MESSAGE,
|
125
|
+
payload: message,
|
126
|
+
timestamp: Date.now(),
|
127
|
+
id: crypto.randomUUID(),
|
128
|
+
};
|
129
|
+
|
130
|
+
const data = JSON.stringify(wsMessage);
|
131
|
+
const deadClients = new Set<WebSocket>();
|
132
|
+
|
133
|
+
// Send message to all connected clients
|
134
|
+
for (const client of this.clients) {
|
135
|
+
try {
|
136
|
+
if (client.readyState === WebSocket.OPEN) {
|
137
|
+
client.send(data);
|
138
|
+
} else {
|
139
|
+
deadClients.add(client);
|
140
|
+
}
|
141
|
+
} catch (error: unknown) {
|
142
|
+
const message = error instanceof Error ? error.message : String(error);
|
143
|
+
this.options.onError?.(new Error(`Failed to send message to client: ${message}`));
|
144
|
+
deadClients.add(client);
|
145
|
+
}
|
146
|
+
}
|
147
|
+
|
148
|
+
// Clean up dead clients
|
149
|
+
for (const client of deadClients) {
|
150
|
+
this.clients.delete(client);
|
151
|
+
}
|
152
|
+
}
|
153
|
+
|
154
|
+
/**
|
155
|
+
* Get array of all currently connected clients
|
156
|
+
* @returns Array of WebSocket client instances
|
157
|
+
*/
|
158
|
+
public getClients(): WebSocket[] {
|
159
|
+
return Array.from(this.clients);
|
160
|
+
}
|
161
|
+
|
162
|
+
/**
|
163
|
+
* Check if server is currently running
|
164
|
+
* @returns true if server is running, false otherwise
|
165
|
+
*/
|
166
|
+
public isRunning(): boolean {
|
167
|
+
return this.wss !== null;
|
168
|
+
}
|
169
|
+
}
|
@@ -0,0 +1,125 @@
|
|
1
|
+
import { z } from 'zod';
|
2
|
+
import type { Message } from '../types/message';
|
3
|
+
|
4
|
+
/**
|
5
|
+
* WebSocket message types for different communication scenarios
|
6
|
+
*/
|
7
|
+
export enum WebSocketMessageType {
|
8
|
+
/** Connection established */
|
9
|
+
CONNECT = 'connect',
|
10
|
+
/** Connection terminated */
|
11
|
+
DISCONNECT = 'disconnect',
|
12
|
+
/** Regular message */
|
13
|
+
MESSAGE = 'message',
|
14
|
+
/** Error occurred */
|
15
|
+
ERROR = 'error',
|
16
|
+
/** Streaming chunk */
|
17
|
+
STREAM = 'stream',
|
18
|
+
/** End of stream */
|
19
|
+
STREAM_END = 'stream_end',
|
20
|
+
}
|
21
|
+
|
22
|
+
/**
|
23
|
+
* Schema for WebSocket messages with validation
|
24
|
+
*/
|
25
|
+
export const WebSocketMessageSchema = z.object({
|
26
|
+
/** Message type from WebSocketMessageType enum */
|
27
|
+
type: z.nativeEnum(WebSocketMessageType),
|
28
|
+
/** Message payload - can be any valid JSON data */
|
29
|
+
payload: z.any(),
|
30
|
+
/** Timestamp of message creation */
|
31
|
+
timestamp: z.number().default(() => Date.now()),
|
32
|
+
/** Unique message identifier */
|
33
|
+
id: z.string().uuid().default(() => crypto.randomUUID()),
|
34
|
+
});
|
35
|
+
|
36
|
+
/**
|
37
|
+
* WebSocket message type
|
38
|
+
*/
|
39
|
+
export type WebSocketMessage = z.infer<typeof WebSocketMessageSchema>;
|
40
|
+
|
41
|
+
/**
|
42
|
+
* Configuration options for WebSocket client
|
43
|
+
*/
|
44
|
+
export interface WebSocketClientOptions {
|
45
|
+
/** WebSocket server URL */
|
46
|
+
url: string;
|
47
|
+
/** WebSocket protocols */
|
48
|
+
protocols?: string | string[];
|
49
|
+
/** Whether to attempt reconnection on disconnect */
|
50
|
+
reconnect?: boolean;
|
51
|
+
/** Interval between reconnection attempts in ms */
|
52
|
+
reconnectInterval?: number;
|
53
|
+
/** Maximum number of reconnection attempts */
|
54
|
+
maxReconnectAttempts?: number;
|
55
|
+
/** Callback for received messages */
|
56
|
+
onMessage?: (message: WebSocketMessage) => void;
|
57
|
+
/** Callback for errors */
|
58
|
+
onError?: (error: Error) => void;
|
59
|
+
/** Callback for successful connection */
|
60
|
+
onConnect?: () => void;
|
61
|
+
/** Callback for disconnection */
|
62
|
+
onDisconnect?: () => void;
|
63
|
+
}
|
64
|
+
|
65
|
+
/**
|
66
|
+
* Configuration options for WebSocket server
|
67
|
+
*/
|
68
|
+
export interface WebSocketServerOptions {
|
69
|
+
/** Port to listen on */
|
70
|
+
port: number;
|
71
|
+
/** Host to bind to */
|
72
|
+
host?: string;
|
73
|
+
/** URL path for WebSocket endpoint */
|
74
|
+
path?: string;
|
75
|
+
/** Callback for new client connections */
|
76
|
+
onConnection?: (client: WebSocket) => void;
|
77
|
+
/** Callback for received messages */
|
78
|
+
onMessage?: (message: WebSocketMessage, client: WebSocket) => void;
|
79
|
+
/** Callback for errors */
|
80
|
+
onError?: (error: Error) => void;
|
81
|
+
}
|
82
|
+
|
83
|
+
/**
|
84
|
+
* Options for streaming messages over WebSocket
|
85
|
+
*/
|
86
|
+
export interface WebSocketStreamOptions {
|
87
|
+
/** Size of each chunk in bytes */
|
88
|
+
chunkSize?: number;
|
89
|
+
/** Delay between chunks in ms */
|
90
|
+
delay?: number;
|
91
|
+
/** Callback for streaming progress */
|
92
|
+
onProgress?: (progress: number) => void;
|
93
|
+
}
|
94
|
+
|
95
|
+
/**
|
96
|
+
* Interface for WebSocket client implementation
|
97
|
+
*/
|
98
|
+
export interface WebSocketClient {
|
99
|
+
/** Connect to WebSocket server */
|
100
|
+
connect(): Promise<void>;
|
101
|
+
/** Disconnect from WebSocket server */
|
102
|
+
disconnect(): Promise<void>;
|
103
|
+
/** Send a message */
|
104
|
+
send(message: Message | WebSocketMessage): Promise<void>;
|
105
|
+
/** Stream a message in chunks */
|
106
|
+
stream(message: Message, options?: WebSocketStreamOptions): AsyncIterableIterator<string>;
|
107
|
+
/** Check connection status */
|
108
|
+
isConnected(): boolean;
|
109
|
+
}
|
110
|
+
|
111
|
+
/**
|
112
|
+
* Interface for WebSocket server implementation
|
113
|
+
*/
|
114
|
+
export interface WebSocketServer {
|
115
|
+
/** Start the WebSocket server */
|
116
|
+
start(): Promise<void>;
|
117
|
+
/** Stop the WebSocket server */
|
118
|
+
stop(): Promise<void>;
|
119
|
+
/** Broadcast a message to all clients */
|
120
|
+
broadcast(message: Message | WebSocketMessage): Promise<void>;
|
121
|
+
/** Get list of connected clients */
|
122
|
+
getClients(): WebSocket[];
|
123
|
+
/** Check server status */
|
124
|
+
isRunning(): boolean;
|
125
|
+
}
|