@starlink-awaken/agentmesh 1.0.2 → 1.2.0
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 +60 -41
- package/README.zh-CN.md +137 -167
- package/config/gateway.yaml +78 -0
- package/dist/src/adapters/base.d.ts +22 -0
- package/dist/src/adapters/base.js +10 -0
- package/dist/src/adapters/claude-code.d.ts +22 -0
- package/dist/src/adapters/claude-code.js +112 -0
- package/dist/src/adapters/openclaw.d.ts +22 -0
- package/dist/src/adapters/openclaw.js +110 -0
- package/dist/src/adapters/process.d.ts +28 -0
- package/dist/src/adapters/process.js +121 -0
- package/dist/src/cli/connect.d.ts +26 -0
- package/dist/src/cli/connect.js +544 -0
- package/dist/src/cli/setup.d.ts +2 -0
- package/dist/src/cli/setup.js +97 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +410 -0
- package/dist/src/core/agent-registry.d.ts +48 -0
- package/dist/src/core/agent-registry.js +295 -0
- package/dist/src/core/config.d.ts +59 -0
- package/dist/src/core/config.js +101 -0
- package/dist/src/core/context-manager.d.ts +52 -0
- package/dist/src/core/context-manager.js +165 -0
- package/dist/src/core/event-bus.d.ts +35 -0
- package/dist/src/core/event-bus.js +62 -0
- package/dist/src/core/logger.d.ts +14 -0
- package/dist/src/core/logger.js +57 -0
- package/dist/src/core/metrics.d.ts +87 -0
- package/dist/src/core/metrics.js +167 -0
- package/dist/src/core/router.d.ts +46 -0
- package/dist/src/core/router.js +90 -0
- package/dist/src/core/task-manager.d.ts +41 -0
- package/dist/src/core/task-manager.js +197 -0
- package/dist/src/core/vector-store.d.ts +37 -0
- package/dist/src/core/vector-store.js +175 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +105 -0
- package/dist/src/model-gateway/circuit-breaker.d.ts +21 -0
- package/dist/src/model-gateway/circuit-breaker.js +86 -0
- package/dist/src/model-gateway/health.d.ts +12 -0
- package/dist/src/model-gateway/health.js +80 -0
- package/dist/src/model-gateway/providers.d.ts +4 -0
- package/dist/src/model-gateway/providers.js +113 -0
- package/dist/src/model-gateway/quota.d.ts +4 -0
- package/dist/src/model-gateway/quota.js +107 -0
- package/dist/src/model-gateway/rate-limit.d.ts +12 -0
- package/dist/src/model-gateway/rate-limit.js +51 -0
- package/dist/src/model-gateway/retry.d.ts +14 -0
- package/dist/src/model-gateway/retry.js +48 -0
- package/dist/src/model-gateway/router.d.ts +4 -0
- package/dist/src/model-gateway/router.js +79 -0
- package/dist/src/model-gateway/routes.d.ts +2 -0
- package/dist/src/model-gateway/routes.js +172 -0
- package/dist/src/model-gateway/types.d.ts +47 -0
- package/dist/src/model-gateway/types.js +1 -0
- package/dist/src/routes/api.d.ts +2 -0
- package/dist/src/routes/api.js +128 -0
- package/dist/src/routes/websocket.d.ts +2 -0
- package/dist/src/routes/websocket.js +64 -0
- package/dist/src/types/index.d.ts +71 -0
- package/dist/src/types/index.js +1 -0
- package/dist/tests/core/context-manager.test.d.ts +1 -0
- package/dist/tests/core/context-manager.test.js +35 -0
- package/dist/tests/core/router.test.d.ts +1 -0
- package/dist/tests/core/router.test.js +79 -0
- package/dist/tests/model-gateway/circuit-breaker.test.d.ts +1 -0
- package/dist/tests/model-gateway/circuit-breaker.test.js +84 -0
- package/dist/tests/model-gateway/providers.test.d.ts +1 -0
- package/dist/tests/model-gateway/providers.test.js +80 -0
- package/dist/tests/model-gateway/quota.test.d.ts +1 -0
- package/dist/tests/model-gateway/quota.test.js +60 -0
- package/dist/tests/model-gateway/rate-limit.test.d.ts +1 -0
- package/dist/tests/model-gateway/rate-limit.test.js +42 -0
- package/dist/tests/model-gateway/retry.test.d.ts +1 -0
- package/dist/tests/model-gateway/retry.test.js +47 -0
- package/dist/tests/model-gateway/router.test.d.ts +1 -0
- package/dist/tests/model-gateway/router.test.js +108 -0
- package/dist/tests/model-gateway/routes.test.d.ts +1 -0
- package/dist/tests/model-gateway/routes.test.js +83 -0
- package/docs/api.md +187 -460
- package/docs/architecture.md +138 -0
- package/docs/configuration.md +188 -0
- package/package.json +3 -1
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
2
|
+
import { eventBus } from './event-bus.js';
|
|
3
|
+
import { router } from './router.js';
|
|
4
|
+
import { contextManager } from './context-manager.js';
|
|
5
|
+
import { agentRegistry } from './agent-registry.js';
|
|
6
|
+
export class TaskManager {
|
|
7
|
+
tasks = new Map();
|
|
8
|
+
/**
|
|
9
|
+
* 创建新任务
|
|
10
|
+
*/
|
|
11
|
+
async createTask(request) {
|
|
12
|
+
const taskId = uuidv4();
|
|
13
|
+
const task = {
|
|
14
|
+
id: taskId,
|
|
15
|
+
status: 'pending',
|
|
16
|
+
request,
|
|
17
|
+
assignedAgents: [],
|
|
18
|
+
createdAt: Date.now(),
|
|
19
|
+
updatedAt: Date.now()
|
|
20
|
+
};
|
|
21
|
+
this.tasks.set(taskId, task);
|
|
22
|
+
// 发布任务提交事件
|
|
23
|
+
eventBus.publishTaskEvent('task.submitted', {
|
|
24
|
+
...request,
|
|
25
|
+
id: taskId
|
|
26
|
+
});
|
|
27
|
+
return task;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 分配任务到 Agent
|
|
31
|
+
*/
|
|
32
|
+
assignTask(taskId, agentIds) {
|
|
33
|
+
const task = this.tasks.get(taskId);
|
|
34
|
+
if (!task) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
task.assignedAgents = agentIds;
|
|
38
|
+
task.status = 'assigned';
|
|
39
|
+
task.updatedAt = Date.now();
|
|
40
|
+
// 发布任务分配事件
|
|
41
|
+
eventBus.publishTaskEvent('task.assigned', {
|
|
42
|
+
...task.request,
|
|
43
|
+
id: taskId
|
|
44
|
+
});
|
|
45
|
+
return task;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* 开始执行任务
|
|
49
|
+
*/
|
|
50
|
+
startTask(taskId) {
|
|
51
|
+
const task = this.tasks.get(taskId);
|
|
52
|
+
if (!task) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
task.status = 'running';
|
|
56
|
+
task.updatedAt = Date.now();
|
|
57
|
+
eventBus.publishTaskEvent('task.started', {
|
|
58
|
+
...task.request,
|
|
59
|
+
id: taskId
|
|
60
|
+
});
|
|
61
|
+
return task;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* 完成任务
|
|
65
|
+
*/
|
|
66
|
+
completeTask(taskId, result) {
|
|
67
|
+
const task = this.tasks.get(taskId);
|
|
68
|
+
if (!task) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
task.status = 'completed';
|
|
72
|
+
task.result = result;
|
|
73
|
+
task.updatedAt = Date.now();
|
|
74
|
+
eventBus.publishTaskEvent('task.completed', {
|
|
75
|
+
...task.request,
|
|
76
|
+
id: taskId,
|
|
77
|
+
result
|
|
78
|
+
});
|
|
79
|
+
return task;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* 任务失败
|
|
83
|
+
*/
|
|
84
|
+
failTask(taskId, error) {
|
|
85
|
+
const task = this.tasks.get(taskId);
|
|
86
|
+
if (!task) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
task.status = 'failed';
|
|
90
|
+
task.error = error;
|
|
91
|
+
task.updatedAt = Date.now();
|
|
92
|
+
eventBus.publishTaskEvent('task.failed', {
|
|
93
|
+
...task.request,
|
|
94
|
+
id: taskId,
|
|
95
|
+
error
|
|
96
|
+
});
|
|
97
|
+
return task;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* 获取任务
|
|
101
|
+
*/
|
|
102
|
+
getTask(taskId) {
|
|
103
|
+
return this.tasks.get(taskId);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* 获取所有任务
|
|
107
|
+
*/
|
|
108
|
+
getAllTasks() {
|
|
109
|
+
return Array.from(this.tasks.values());
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* 处理任务
|
|
113
|
+
*/
|
|
114
|
+
async processTask(message) {
|
|
115
|
+
// 1. 创建任务
|
|
116
|
+
const task = await this.createTask(message);
|
|
117
|
+
// 2. 如果有共享空间,添加消息到上下文
|
|
118
|
+
if (message.payload?.context?.shared_space_id) {
|
|
119
|
+
await contextManager.addMessage(message.payload.context.shared_space_id, message);
|
|
120
|
+
}
|
|
121
|
+
// 3. 路由任务到 Agent
|
|
122
|
+
const { agentIds, strategy } = router.route(message);
|
|
123
|
+
if (agentIds.length === 0) {
|
|
124
|
+
this.failTask(task.id, {
|
|
125
|
+
code: 'NO_AGENT_AVAILABLE',
|
|
126
|
+
message: 'No available agents to handle this task'
|
|
127
|
+
});
|
|
128
|
+
throw new Error('No available agents');
|
|
129
|
+
}
|
|
130
|
+
// 4. 分配任务
|
|
131
|
+
this.assignTask(task.id, agentIds);
|
|
132
|
+
this.startTask(task.id);
|
|
133
|
+
// 5. 执行任务
|
|
134
|
+
await this.executeTask(task, agentIds, strategy);
|
|
135
|
+
return task;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* 执行任务
|
|
139
|
+
*/
|
|
140
|
+
async executeTask(task, agentIds, strategy) {
|
|
141
|
+
const results = {};
|
|
142
|
+
if (strategy === 'direct' && agentIds[0]) {
|
|
143
|
+
// 单 Agent 执行
|
|
144
|
+
const agentId = agentIds[0];
|
|
145
|
+
const adapter = agentRegistry.get(agentId);
|
|
146
|
+
if (!adapter) {
|
|
147
|
+
this.failTask(task.id, {
|
|
148
|
+
code: 'AGENT_NOT_FOUND',
|
|
149
|
+
message: `Agent ${agentId} not found`
|
|
150
|
+
});
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
try {
|
|
154
|
+
console.log(`[TaskManager] Executing task ${task.id} with agent ${agentId}`);
|
|
155
|
+
const response = await adapter.invoke(task.request);
|
|
156
|
+
results[agentId] = response.result;
|
|
157
|
+
// 如果有共享空间,添加响应到上下文
|
|
158
|
+
if (task.request.payload?.context?.shared_space_id) {
|
|
159
|
+
await contextManager.addMessage(task.request.payload.context.shared_space_id, response);
|
|
160
|
+
}
|
|
161
|
+
this.completeTask(task.id, response.result);
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
165
|
+
this.failTask(task.id, {
|
|
166
|
+
code: 'EXECUTION_ERROR',
|
|
167
|
+
message: errorMessage
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
// 广播模式:多个 Agent 同时执行
|
|
173
|
+
const promises = agentIds.map(async (agentId) => {
|
|
174
|
+
const adapter = agentRegistry.get(agentId);
|
|
175
|
+
if (!adapter) {
|
|
176
|
+
results[agentId] = { error: `Agent ${agentId} not found` };
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
try {
|
|
180
|
+
const response = await adapter.invoke(task.request);
|
|
181
|
+
results[agentId] = response.result;
|
|
182
|
+
// 添加响应到上下文
|
|
183
|
+
if (task.request.payload?.context?.shared_space_id) {
|
|
184
|
+
await contextManager.addMessage(task.request.payload.context.shared_space_id, response);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
189
|
+
results[agentId] = { error: errorMessage };
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
await Promise.all(promises);
|
|
193
|
+
this.completeTask(task.id, results);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
export const taskManager = new TaskManager();
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { AgentMessage } from '../types/index.js';
|
|
2
|
+
export declare class VectorStore {
|
|
3
|
+
private client;
|
|
4
|
+
private collection;
|
|
5
|
+
private isInitialized;
|
|
6
|
+
private baseDir;
|
|
7
|
+
constructor(baseDir?: string);
|
|
8
|
+
/**
|
|
9
|
+
* 初始化向量数据库
|
|
10
|
+
*/
|
|
11
|
+
initialize(): Promise<void>;
|
|
12
|
+
/**
|
|
13
|
+
* 添加消息到向量存储
|
|
14
|
+
*/
|
|
15
|
+
addMessage(spaceId: string, message: AgentMessage): Promise<void>;
|
|
16
|
+
/**
|
|
17
|
+
* 搜索相似上下文
|
|
18
|
+
*/
|
|
19
|
+
searchSimilar(spaceId: string, query: string, limit?: number): Promise<AgentMessage[]>;
|
|
20
|
+
/**
|
|
21
|
+
* 获取空间的向量数量
|
|
22
|
+
*/
|
|
23
|
+
getCount(spaceId: string): Promise<number>;
|
|
24
|
+
/**
|
|
25
|
+
* 删除空间的向量
|
|
26
|
+
*/
|
|
27
|
+
deleteSpace(spaceId: string): Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* 将消息转换为可向量化的文本
|
|
30
|
+
*/
|
|
31
|
+
private messageToText;
|
|
32
|
+
/**
|
|
33
|
+
* 检查是否可用
|
|
34
|
+
*/
|
|
35
|
+
isAvailable(): boolean;
|
|
36
|
+
}
|
|
37
|
+
export declare const vectorStore: VectorStore;
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { ChromaClient } from 'chromadb';
|
|
2
|
+
export class VectorStore {
|
|
3
|
+
client = null;
|
|
4
|
+
collection = null;
|
|
5
|
+
isInitialized = false;
|
|
6
|
+
baseDir;
|
|
7
|
+
constructor(baseDir = './data/vector-db') {
|
|
8
|
+
this.baseDir = baseDir;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* 初始化向量数据库
|
|
12
|
+
*/
|
|
13
|
+
async initialize() {
|
|
14
|
+
if (this.isInitialized)
|
|
15
|
+
return;
|
|
16
|
+
try {
|
|
17
|
+
this.client = new ChromaClient({
|
|
18
|
+
path: 'http://localhost:8000'
|
|
19
|
+
});
|
|
20
|
+
// 尝试获取或创建 collection
|
|
21
|
+
try {
|
|
22
|
+
this.collection = await this.client.getOrCreateCollection({
|
|
23
|
+
name: 'agent-context'
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// Collection 可能不存在,创建新的
|
|
28
|
+
this.collection = await this.client.createCollection({
|
|
29
|
+
name: 'agent-context'
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
this.isInitialized = true;
|
|
33
|
+
console.log('[VectorStore] Initialized successfully');
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
console.warn('[VectorStore] Failed to initialize (ChromaDB not running?):', error);
|
|
37
|
+
this.isInitialized = false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* 添加消息到向量存储
|
|
42
|
+
*/
|
|
43
|
+
async addMessage(spaceId, message) {
|
|
44
|
+
if (!this.isInitialized || !this.collection) {
|
|
45
|
+
console.warn('[VectorStore] Not initialized, skipping add');
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const entry = {
|
|
49
|
+
id: `${spaceId}_${message.id}`,
|
|
50
|
+
message
|
|
51
|
+
};
|
|
52
|
+
try {
|
|
53
|
+
// 简单文本向量化(使用消息内容)
|
|
54
|
+
const text = this.messageToText(message);
|
|
55
|
+
await this.collection.add({
|
|
56
|
+
ids: [entry.id],
|
|
57
|
+
documents: [text],
|
|
58
|
+
metadatas: [{
|
|
59
|
+
space_id: spaceId,
|
|
60
|
+
message_id: message.id,
|
|
61
|
+
timestamp: message.timestamp,
|
|
62
|
+
source: message.source,
|
|
63
|
+
type: message.type
|
|
64
|
+
}]
|
|
65
|
+
});
|
|
66
|
+
console.log('[VectorStore] Added message:', entry.id);
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
console.error('[VectorStore] Failed to add message:', error);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* 搜索相似上下文
|
|
74
|
+
*/
|
|
75
|
+
async searchSimilar(spaceId, query, limit = 5) {
|
|
76
|
+
if (!this.isInitialized || !this.collection) {
|
|
77
|
+
console.warn('[VectorStore] Not initialized, returning empty');
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
const results = await this.collection.query({
|
|
82
|
+
queryTexts: [query],
|
|
83
|
+
nResults: limit,
|
|
84
|
+
where: { space_id: spaceId }
|
|
85
|
+
});
|
|
86
|
+
const messages = [];
|
|
87
|
+
if (results.documents && results.documents[0]) {
|
|
88
|
+
for (let i = 0; i < results.documents[0].length; i++) {
|
|
89
|
+
const metadata = results.metadatas?.[0]?.[i];
|
|
90
|
+
if (metadata?.message_id) {
|
|
91
|
+
// 这里返回元数据,实际使用时可以从文件/内存中获取完整消息
|
|
92
|
+
messages.push({
|
|
93
|
+
id: metadata.message_id,
|
|
94
|
+
type: 'event',
|
|
95
|
+
source: metadata.source,
|
|
96
|
+
target: 'search',
|
|
97
|
+
correlation_id: '',
|
|
98
|
+
timestamp: metadata.timestamp,
|
|
99
|
+
payload: {
|
|
100
|
+
task: results.documents[0][i] || ''
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return messages;
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
console.error('[VectorStore] Search failed:', error);
|
|
110
|
+
return [];
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* 获取空间的向量数量
|
|
115
|
+
*/
|
|
116
|
+
async getCount(spaceId) {
|
|
117
|
+
if (!this.isInitialized || !this.collection) {
|
|
118
|
+
return 0;
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
const results = await this.collection.get({
|
|
122
|
+
where: { space_id: spaceId }
|
|
123
|
+
});
|
|
124
|
+
return results.ids?.length || 0;
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return 0;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* 删除空间的向量
|
|
132
|
+
*/
|
|
133
|
+
async deleteSpace(spaceId) {
|
|
134
|
+
if (!this.isInitialized || !this.collection) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
// 获取该 space 的所有 ID
|
|
139
|
+
const results = await this.collection.get({
|
|
140
|
+
where: { space_id: spaceId }
|
|
141
|
+
});
|
|
142
|
+
if (results.ids && results.ids.length > 0) {
|
|
143
|
+
await this.collection.delete({
|
|
144
|
+
ids: results.ids
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
console.error('[VectorStore] Delete space failed:', error);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* 将消息转换为可向量化的文本
|
|
154
|
+
*/
|
|
155
|
+
messageToText(message) {
|
|
156
|
+
const parts = [];
|
|
157
|
+
if (message.payload?.task) {
|
|
158
|
+
parts.push(`Task: ${message.payload.task}`);
|
|
159
|
+
}
|
|
160
|
+
if (message.result) {
|
|
161
|
+
parts.push(`Result: ${JSON.stringify(message.result)}`);
|
|
162
|
+
}
|
|
163
|
+
if (message.error) {
|
|
164
|
+
parts.push(`Error: ${message.error.message}`);
|
|
165
|
+
}
|
|
166
|
+
return parts.join('\n') || JSON.stringify(message);
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* 检查是否可用
|
|
170
|
+
*/
|
|
171
|
+
isAvailable() {
|
|
172
|
+
return this.isInitialized;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
export const vectorStore = new VectorStore();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import Fastify from 'fastify';
|
|
2
|
+
import cors from '@fastify/cors';
|
|
3
|
+
import { apiRoutes } from './routes/api.js';
|
|
4
|
+
import { websocketRoutes } from './routes/websocket.js';
|
|
5
|
+
import { modelGatewayRoutes } from './model-gateway/routes.js';
|
|
6
|
+
import { initModelRouter } from './model-gateway/router.js';
|
|
7
|
+
import { eventBus } from './core/event-bus.js';
|
|
8
|
+
import { router } from './core/router.js';
|
|
9
|
+
import { agentRegistry } from './core/agent-registry.js';
|
|
10
|
+
import { vectorStore } from './core/vector-store.js';
|
|
11
|
+
import { loadConfig, getRoutingRules, getDefaultAgent } from './core/config.js';
|
|
12
|
+
import { circuitBreakerRegistry } from './model-gateway/circuit-breaker.js';
|
|
13
|
+
import { configureRetry } from './model-gateway/retry.js';
|
|
14
|
+
import { initRateLimiter } from './model-gateway/rate-limit.js';
|
|
15
|
+
async function main() {
|
|
16
|
+
const config = loadConfig();
|
|
17
|
+
// 初始化 Fastify
|
|
18
|
+
const fastify = Fastify({
|
|
19
|
+
logger: {
|
|
20
|
+
level: config.logLevel
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
// 注册 CORS
|
|
24
|
+
await fastify.register(cors, {
|
|
25
|
+
origin: true
|
|
26
|
+
});
|
|
27
|
+
// 注册路由
|
|
28
|
+
await fastify.register(apiRoutes);
|
|
29
|
+
await fastify.register(websocketRoutes);
|
|
30
|
+
await fastify.register(modelGatewayRoutes);
|
|
31
|
+
// 初始化模型网关
|
|
32
|
+
const modelsConfig = config.models;
|
|
33
|
+
if (modelsConfig) {
|
|
34
|
+
// 配置熔断器
|
|
35
|
+
const cbDefaults = modelsConfig.defaults?.circuit_breaker;
|
|
36
|
+
if (cbDefaults) {
|
|
37
|
+
for (const [name] of Object.entries(modelsConfig.providers || {})) {
|
|
38
|
+
circuitBreakerRegistry.configure(name, cbDefaults);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// 配置重试
|
|
42
|
+
const retryDefaults = modelsConfig.defaults?.retry;
|
|
43
|
+
if (retryDefaults) {
|
|
44
|
+
configureRetry(retryDefaults);
|
|
45
|
+
}
|
|
46
|
+
// 初始化限流器
|
|
47
|
+
initRateLimiter();
|
|
48
|
+
initModelRouter(modelsConfig);
|
|
49
|
+
console.log(`[ModelGW] Initialized: ${Object.keys(modelsConfig.providers || {}).length} providers, fallback: ${modelsConfig.fallback_chain?.join(' → ')}`);
|
|
50
|
+
// 配额预热(异步,不阻塞启动)
|
|
51
|
+
import('./model-gateway/quota.js').then(m => m.probeQuota()).catch(() => { });
|
|
52
|
+
console.log('[ModelGW] Quota pre-warming started (background)');
|
|
53
|
+
}
|
|
54
|
+
// 初始化组件
|
|
55
|
+
const rules = getRoutingRules();
|
|
56
|
+
const defaultAgent = getDefaultAgent();
|
|
57
|
+
router.configure(rules, defaultAgent);
|
|
58
|
+
// 初始化 Agent Registry
|
|
59
|
+
agentRegistry.initialize();
|
|
60
|
+
// 注册所有 Agent 到 Router
|
|
61
|
+
agentRegistry.getAgents().forEach(agent => {
|
|
62
|
+
router.registerAgent(agent);
|
|
63
|
+
});
|
|
64
|
+
// 初始化向量存储(异步)
|
|
65
|
+
vectorStore.initialize().catch(err => {
|
|
66
|
+
console.warn('[VectorStore] Init failed:', err);
|
|
67
|
+
});
|
|
68
|
+
// 启动服务器
|
|
69
|
+
try {
|
|
70
|
+
await fastify.listen({
|
|
71
|
+
port: config.port,
|
|
72
|
+
host: config.host
|
|
73
|
+
});
|
|
74
|
+
console.log(`
|
|
75
|
+
╔═══════════════════════════════════════════════════╗
|
|
76
|
+
║ Agent Mesh Gateway ║
|
|
77
|
+
╠═══════════════════════════════════════════════════╣
|
|
78
|
+
║ HTTP: http://${config.host}:${config.port} ║
|
|
79
|
+
║ WebSocket: ws://${config.host}:${config.port}/ws ║
|
|
80
|
+
║ Health: http://${config.host}:${config.port}/health ║
|
|
81
|
+
║ Tasks: http://${config.host}:${config.port}/tasks ║
|
|
82
|
+
║ Spaces: http://${config.host}:${config.port}/spaces ║
|
|
83
|
+
║ Agents: http://${config.host}:${config.port}/agents ║
|
|
84
|
+
╠═══════════════════════════════════════════════════╣
|
|
85
|
+
║ Model GW: http://${config.host}:${config.port}/v1/chat/completions ║
|
|
86
|
+
║ Models: http://${config.host}:${config.port}/v1/models ║
|
|
87
|
+
║ Quota: http://${config.host}:${config.port}/model-gateway/quota ║
|
|
88
|
+
╚═══════════════════════════════════════════════════╝
|
|
89
|
+
`);
|
|
90
|
+
// 订阅事件日志
|
|
91
|
+
eventBus.getEventTypes().forEach(eventType => {
|
|
92
|
+
eventBus.subscribe(eventType, (event) => {
|
|
93
|
+
console.log(`[Event] ${event.type}:`, {
|
|
94
|
+
id: event.data.id,
|
|
95
|
+
correlation_id: event.data.correlation_id
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
fastify.log.error(error);
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
main();
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type CircuitState = 'CLOSED' | 'OPEN' | 'HALF_OPEN';
|
|
2
|
+
export interface CircuitBreakerConfig {
|
|
3
|
+
failureThreshold: number;
|
|
4
|
+
resetTimeoutMs: number;
|
|
5
|
+
halfOpenMaxRequests: number;
|
|
6
|
+
}
|
|
7
|
+
export declare class CircuitBreakerRegistry {
|
|
8
|
+
private circuits;
|
|
9
|
+
private configs;
|
|
10
|
+
configure(provider: string, config?: Partial<CircuitBreakerConfig>): void;
|
|
11
|
+
getState(provider: string): CircuitState;
|
|
12
|
+
isOpen(provider: string): boolean;
|
|
13
|
+
canRequest(provider: string): boolean;
|
|
14
|
+
recordSuccess(provider: string): void;
|
|
15
|
+
recordFailure(provider: string): void;
|
|
16
|
+
getStatus(): Record<string, {
|
|
17
|
+
state: CircuitState;
|
|
18
|
+
failures: number;
|
|
19
|
+
}>;
|
|
20
|
+
}
|
|
21
|
+
export declare const circuitBreakerRegistry: CircuitBreakerRegistry;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
const DEFAULTS = {
|
|
2
|
+
failureThreshold: 3,
|
|
3
|
+
resetTimeoutMs: 30_000,
|
|
4
|
+
halfOpenMaxRequests: 1,
|
|
5
|
+
};
|
|
6
|
+
export class CircuitBreakerRegistry {
|
|
7
|
+
circuits = new Map();
|
|
8
|
+
configs = new Map();
|
|
9
|
+
configure(provider, config = {}) {
|
|
10
|
+
this.configs.set(provider, { ...DEFAULTS, ...config });
|
|
11
|
+
}
|
|
12
|
+
getState(provider) {
|
|
13
|
+
const entry = this.circuits.get(provider);
|
|
14
|
+
if (!entry)
|
|
15
|
+
return 'CLOSED';
|
|
16
|
+
// OPEN → HALF_OPEN after timeout
|
|
17
|
+
if (entry.state === 'OPEN') {
|
|
18
|
+
const cfg = this.configs.get(provider) || DEFAULTS;
|
|
19
|
+
if (Date.now() - entry.lastFailureTime >= cfg.resetTimeoutMs) {
|
|
20
|
+
entry.state = 'HALF_OPEN';
|
|
21
|
+
entry.halfOpenCount = 0;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return entry.state;
|
|
25
|
+
}
|
|
26
|
+
isOpen(provider) {
|
|
27
|
+
return this.getState(provider) === 'OPEN';
|
|
28
|
+
}
|
|
29
|
+
canRequest(provider) {
|
|
30
|
+
const state = this.getState(provider);
|
|
31
|
+
if (state === 'CLOSED')
|
|
32
|
+
return true;
|
|
33
|
+
if (state === 'OPEN')
|
|
34
|
+
return false;
|
|
35
|
+
// HALF_OPEN: allow limited probe requests
|
|
36
|
+
const cfg = this.configs.get(provider) || DEFAULTS;
|
|
37
|
+
const entry = this.circuits.get(provider);
|
|
38
|
+
return (entry?.halfOpenCount ?? 0) < cfg.halfOpenMaxRequests;
|
|
39
|
+
}
|
|
40
|
+
recordSuccess(provider) {
|
|
41
|
+
const entry = this.circuits.get(provider);
|
|
42
|
+
if (!entry)
|
|
43
|
+
return;
|
|
44
|
+
if (entry.state === 'HALF_OPEN') {
|
|
45
|
+
// Half-open success → reset to closed
|
|
46
|
+
this.circuits.delete(provider);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
// Reset failure count on success in closed state
|
|
50
|
+
entry.failures = 0;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
recordFailure(provider) {
|
|
54
|
+
let entry = this.circuits.get(provider);
|
|
55
|
+
if (!entry) {
|
|
56
|
+
entry = {
|
|
57
|
+
failures: 0,
|
|
58
|
+
lastFailureTime: 0,
|
|
59
|
+
state: 'CLOSED',
|
|
60
|
+
halfOpenCount: 0,
|
|
61
|
+
};
|
|
62
|
+
this.circuits.set(provider, entry);
|
|
63
|
+
}
|
|
64
|
+
const cfg = this.configs.get(provider) || DEFAULTS;
|
|
65
|
+
if (entry.state === 'HALF_OPEN') {
|
|
66
|
+
// Half-open failure → back to open
|
|
67
|
+
entry.state = 'OPEN';
|
|
68
|
+
entry.lastFailureTime = Date.now();
|
|
69
|
+
entry.halfOpenCount = 0;
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
entry.failures++;
|
|
73
|
+
entry.lastFailureTime = Date.now();
|
|
74
|
+
if (entry.failures >= cfg.failureThreshold) {
|
|
75
|
+
entry.state = 'OPEN';
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
getStatus() {
|
|
79
|
+
const status = {};
|
|
80
|
+
for (const [name, entry] of this.circuits) {
|
|
81
|
+
status[name] = { state: this.getState(name), failures: entry.failures };
|
|
82
|
+
}
|
|
83
|
+
return status;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
export const circuitBreakerRegistry = new CircuitBreakerRegistry();
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ModelGatewayConfig, ResolvedProvider } from './types.js';
|
|
2
|
+
interface ProviderHealth {
|
|
3
|
+
provider: string;
|
|
4
|
+
status: 'healthy' | 'unhealthy' | 'unknown';
|
|
5
|
+
latency_ms: number;
|
|
6
|
+
circuit: string;
|
|
7
|
+
error?: string;
|
|
8
|
+
checked_at: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function checkProviderHealth(provider: ResolvedProvider): Promise<ProviderHealth>;
|
|
11
|
+
export declare function checkAllProviders(config: ModelGatewayConfig): Promise<ProviderHealth[]>;
|
|
12
|
+
export {};
|