opc-agent 1.1.1 → 1.1.2
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 +51 -51
- package/CONTRIBUTING.md +75 -75
- package/README.md +222 -126
- package/README.zh-CN.md +129 -80
- package/dist/channels/web.js +256 -256
- package/dist/deploy/hermes.js +22 -22
- package/dist/deploy/openclaw.js +31 -31
- package/dist/templates/code-reviewer.js +5 -5
- package/dist/templates/customer-service.js +2 -2
- package/dist/templates/data-analyst.js +5 -5
- package/dist/templates/knowledge-base.js +2 -2
- package/dist/templates/sales-assistant.js +4 -4
- package/dist/templates/teacher.js +6 -6
- package/docs/.vitepress/config.ts +103 -103
- package/docs/api/cli.md +48 -48
- package/docs/api/oad-schema.md +64 -64
- package/docs/api/sdk.md +80 -80
- package/docs/guide/concepts.md +51 -51
- package/docs/guide/configuration.md +79 -79
- package/docs/guide/deployment.md +42 -42
- package/docs/guide/getting-started.md +44 -44
- package/docs/guide/templates.md +28 -28
- package/docs/guide/testing.md +84 -84
- package/docs/index.md +27 -27
- package/docs/zh/api/cli.md +54 -54
- package/docs/zh/api/oad-schema.md +87 -87
- package/docs/zh/api/sdk.md +102 -102
- package/docs/zh/guide/concepts.md +104 -104
- package/docs/zh/guide/configuration.md +135 -135
- package/docs/zh/guide/deployment.md +81 -81
- package/docs/zh/guide/getting-started.md +82 -82
- package/docs/zh/guide/templates.md +84 -84
- package/docs/zh/guide/testing.md +88 -88
- package/docs/zh/index.md +27 -27
- package/examples/customer-service-demo/README.md +90 -90
- package/examples/customer-service-demo/oad.yaml +107 -107
- package/package.json +1 -1
- package/src/analytics/index.ts +66 -66
- package/src/channels/discord.ts +192 -192
- package/src/channels/email.ts +177 -177
- package/src/channels/feishu.ts +236 -236
- package/src/channels/index.ts +15 -15
- package/src/channels/slack.ts +160 -160
- package/src/channels/telegram.ts +90 -90
- package/src/channels/voice.ts +106 -106
- package/src/channels/web.ts +596 -596
- package/src/channels/webhook.ts +199 -199
- package/src/channels/websocket.ts +87 -87
- package/src/channels/wechat.ts +149 -149
- package/src/core/a2a.ts +143 -143
- package/src/core/agent.ts +152 -152
- package/src/core/analytics-engine.ts +186 -186
- package/src/core/auth.ts +57 -57
- package/src/core/cache.ts +141 -141
- package/src/core/compose.ts +77 -77
- package/src/core/config.ts +14 -14
- package/src/core/errors.ts +148 -148
- package/src/core/hitl.ts +138 -138
- package/src/core/knowledge.ts +210 -210
- package/src/core/logger.ts +57 -57
- package/src/core/orchestrator.ts +215 -215
- package/src/core/performance.ts +187 -187
- package/src/core/rate-limiter.ts +128 -128
- package/src/core/room.ts +109 -109
- package/src/core/runtime.ts +152 -152
- package/src/core/sandbox.ts +101 -101
- package/src/core/security.ts +171 -171
- package/src/core/types.ts +68 -68
- package/src/core/versioning.ts +106 -106
- package/src/core/watch.ts +178 -178
- package/src/core/workflow.ts +235 -235
- package/src/deploy/hermes.ts +156 -156
- package/src/deploy/openclaw.ts +200 -200
- package/src/dtv/data.ts +29 -29
- package/src/dtv/trust.ts +43 -43
- package/src/dtv/value.ts +47 -47
- package/src/i18n/index.ts +216 -216
- package/src/index.ts +110 -110
- package/src/marketplace/index.ts +223 -223
- package/src/memory/deepbrain.ts +108 -108
- package/src/memory/index.ts +34 -34
- package/src/plugins/index.ts +208 -208
- package/src/providers/index.ts +183 -183
- package/src/schema/oad.ts +155 -155
- package/src/skills/base.ts +16 -16
- package/src/skills/document.ts +100 -100
- package/src/skills/http.ts +35 -35
- package/src/skills/index.ts +27 -27
- package/src/skills/scheduler.ts +80 -80
- package/src/skills/webhook-trigger.ts +59 -59
- package/src/templates/code-reviewer.ts +34 -34
- package/src/templates/customer-service.ts +80 -80
- package/src/templates/data-analyst.ts +70 -70
- package/src/templates/executive-assistant.ts +71 -71
- package/src/templates/financial-advisor.ts +60 -60
- package/src/templates/knowledge-base.ts +31 -31
- package/src/templates/legal-assistant.ts +71 -71
- package/src/templates/sales-assistant.ts +79 -79
- package/src/templates/teacher.ts +79 -79
- package/src/testing/index.ts +181 -181
- package/src/tools/calculator.ts +73 -73
- package/src/tools/datetime.ts +149 -149
- package/src/tools/json-transform.ts +187 -187
- package/src/tools/mcp.ts +76 -76
- package/src/tools/text-analysis.ts +116 -116
- package/templates/Dockerfile +15 -15
- package/templates/code-reviewer/README.md +27 -27
- package/templates/code-reviewer/oad.yaml +41 -41
- package/templates/customer-service/README.md +22 -22
- package/templates/customer-service/oad.yaml +36 -36
- package/templates/docker-compose.yml +21 -21
- package/templates/knowledge-base/README.md +28 -28
- package/templates/knowledge-base/oad.yaml +38 -38
- package/templates/sales-assistant/README.md +26 -26
- package/templates/sales-assistant/oad.yaml +43 -43
- package/tests/a2a.test.ts +66 -66
- package/tests/agent.test.ts +72 -72
- package/tests/analytics.test.ts +50 -50
- package/tests/channel.test.ts +39 -39
- package/tests/e2e.test.ts +134 -134
- package/tests/errors.test.ts +83 -83
- package/tests/hitl.test.ts +71 -71
- package/tests/i18n.test.ts +41 -41
- package/tests/mcp.test.ts +54 -54
- package/tests/oad.test.ts +68 -68
- package/tests/performance.test.ts +115 -115
- package/tests/plugin.test.ts +74 -74
- package/tests/room.test.ts +106 -106
- package/tests/runtime.test.ts +42 -42
- package/tests/sandbox.test.ts +46 -46
- package/tests/security.test.ts +60 -60
- package/tests/templates.test.ts +77 -77
- package/tests/v070.test.ts +76 -76
- package/tests/versioning.test.ts +75 -75
- package/tests/voice.test.ts +61 -61
- package/tests/webhook.test.ts +29 -29
- package/tests/workflow.test.ts +143 -143
- package/tsconfig.json +19 -19
- package/vitest.config.ts +9 -9
package/src/channels/wechat.ts
CHANGED
|
@@ -1,149 +1,149 @@
|
|
|
1
|
-
import { BaseChannel } from './index';
|
|
2
|
-
import type { Message } from '../core/types';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* WeChat Channel (Stub) — v0.8.0
|
|
6
|
-
* WeChat Official Account message handling, template messages, QR code login.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
export interface WeChatChannelConfig {
|
|
10
|
-
/** WeChat Official Account AppID */
|
|
11
|
-
appId: string;
|
|
12
|
-
/** WeChat Official Account AppSecret */
|
|
13
|
-
appSecret: string;
|
|
14
|
-
/** Verification token for message validation */
|
|
15
|
-
token: string;
|
|
16
|
-
/** AES encoding key for encrypted messages */
|
|
17
|
-
encodingAESKey?: string;
|
|
18
|
-
/** HTTP server port (default: 3002) */
|
|
19
|
-
port?: number;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface WeChatMessage {
|
|
23
|
-
toUserName: string;
|
|
24
|
-
fromUserName: string;
|
|
25
|
-
createTime: number;
|
|
26
|
-
msgType: 'text' | 'image' | 'voice' | 'video' | 'event';
|
|
27
|
-
content?: string;
|
|
28
|
-
msgId?: string;
|
|
29
|
-
event?: string;
|
|
30
|
-
eventKey?: string;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export interface TemplateMessageData {
|
|
34
|
-
toUser: string;
|
|
35
|
-
templateId: string;
|
|
36
|
-
url?: string;
|
|
37
|
-
data: Record<string, { value: string; color?: string }>;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export class WeChatChannel extends BaseChannel {
|
|
41
|
-
type = 'wechat';
|
|
42
|
-
private config: WeChatChannelConfig;
|
|
43
|
-
private accessToken: string | null = null;
|
|
44
|
-
private tokenExpiry = 0;
|
|
45
|
-
|
|
46
|
-
constructor(config: WeChatChannelConfig) {
|
|
47
|
-
super();
|
|
48
|
-
this.config = config;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
async start(): Promise<void> {
|
|
52
|
-
// TODO: Start HTTP server to receive WeChat push messages
|
|
53
|
-
// 1. Verify signature on GET requests
|
|
54
|
-
// 2. Parse XML messages on POST requests
|
|
55
|
-
// 3. Route to handler and reply with XML
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
async stop(): Promise<void> {
|
|
59
|
-
// TODO: Stop HTTP server
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/** Get or refresh access token */
|
|
63
|
-
async getAccessToken(): Promise<string> {
|
|
64
|
-
if (this.accessToken && Date.now() < this.tokenExpiry) {
|
|
65
|
-
return this.accessToken;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// TODO: Implement token refresh
|
|
69
|
-
// const url = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${this.config.appId}&secret=${this.config.appSecret}`;
|
|
70
|
-
// const res = await fetch(url);
|
|
71
|
-
// const data = await res.json();
|
|
72
|
-
// this.accessToken = data.access_token;
|
|
73
|
-
// this.tokenExpiry = Date.now() + (data.expires_in - 300) * 1000;
|
|
74
|
-
|
|
75
|
-
return this.accessToken ?? '';
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/** Handle incoming WeChat message */
|
|
79
|
-
async handleMessage(wxMsg: WeChatMessage): Promise<string> {
|
|
80
|
-
if (wxMsg.msgType === 'event') {
|
|
81
|
-
return this.handleEvent(wxMsg);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const message = this.wechatToMessage(wxMsg);
|
|
85
|
-
if (this.handler) {
|
|
86
|
-
const reply = await this.handler(message);
|
|
87
|
-
return reply.content;
|
|
88
|
-
}
|
|
89
|
-
return '';
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/** Handle WeChat events (subscribe, scan, etc.) */
|
|
93
|
-
private handleEvent(wxMsg: WeChatMessage): string {
|
|
94
|
-
switch (wxMsg.event) {
|
|
95
|
-
case 'subscribe':
|
|
96
|
-
return 'Welcome! How can I help you?';
|
|
97
|
-
case 'SCAN':
|
|
98
|
-
return `QR code scanned: ${wxMsg.eventKey}`;
|
|
99
|
-
default:
|
|
100
|
-
return '';
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/** Convert WeChat message to internal Message */
|
|
105
|
-
private wechatToMessage(wxMsg: WeChatMessage): Message {
|
|
106
|
-
return {
|
|
107
|
-
id: wxMsg.msgId ?? `wx-${wxMsg.createTime}`,
|
|
108
|
-
role: 'user',
|
|
109
|
-
content: wxMsg.content ?? '',
|
|
110
|
-
timestamp: wxMsg.createTime * 1000,
|
|
111
|
-
metadata: {
|
|
112
|
-
channel: 'wechat',
|
|
113
|
-
fromUser: wxMsg.fromUserName,
|
|
114
|
-
toUser: wxMsg.toUserName,
|
|
115
|
-
msgType: wxMsg.msgType,
|
|
116
|
-
},
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/** Send template message */
|
|
121
|
-
async sendTemplateMessage(data: TemplateMessageData): Promise<boolean> {
|
|
122
|
-
// TODO: Implement
|
|
123
|
-
// const token = await this.getAccessToken();
|
|
124
|
-
// const url = `https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=${token}`;
|
|
125
|
-
// const res = await fetch(url, {
|
|
126
|
-
// method: 'POST',
|
|
127
|
-
// body: JSON.stringify({
|
|
128
|
-
// touser: data.toUser,
|
|
129
|
-
// template_id: data.templateId,
|
|
130
|
-
// url: data.url,
|
|
131
|
-
// data: data.data,
|
|
132
|
-
// }),
|
|
133
|
-
// });
|
|
134
|
-
void data;
|
|
135
|
-
return true;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/** Generate QR code for login (stub) */
|
|
139
|
-
async generateLoginQR(): Promise<{ ticket: string; url: string; expireSeconds: number }> {
|
|
140
|
-
// TODO: Implement with WeChat QR code API
|
|
141
|
-
// const token = await this.getAccessToken();
|
|
142
|
-
// POST to https://api.weixin.qq.com/cgi-bin/qrcode/create
|
|
143
|
-
return {
|
|
144
|
-
ticket: 'stub-ticket',
|
|
145
|
-
url: 'https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=stub-ticket',
|
|
146
|
-
expireSeconds: 300,
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
}
|
|
1
|
+
import { BaseChannel } from './index';
|
|
2
|
+
import type { Message } from '../core/types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* WeChat Channel (Stub) — v0.8.0
|
|
6
|
+
* WeChat Official Account message handling, template messages, QR code login.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export interface WeChatChannelConfig {
|
|
10
|
+
/** WeChat Official Account AppID */
|
|
11
|
+
appId: string;
|
|
12
|
+
/** WeChat Official Account AppSecret */
|
|
13
|
+
appSecret: string;
|
|
14
|
+
/** Verification token for message validation */
|
|
15
|
+
token: string;
|
|
16
|
+
/** AES encoding key for encrypted messages */
|
|
17
|
+
encodingAESKey?: string;
|
|
18
|
+
/** HTTP server port (default: 3002) */
|
|
19
|
+
port?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface WeChatMessage {
|
|
23
|
+
toUserName: string;
|
|
24
|
+
fromUserName: string;
|
|
25
|
+
createTime: number;
|
|
26
|
+
msgType: 'text' | 'image' | 'voice' | 'video' | 'event';
|
|
27
|
+
content?: string;
|
|
28
|
+
msgId?: string;
|
|
29
|
+
event?: string;
|
|
30
|
+
eventKey?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface TemplateMessageData {
|
|
34
|
+
toUser: string;
|
|
35
|
+
templateId: string;
|
|
36
|
+
url?: string;
|
|
37
|
+
data: Record<string, { value: string; color?: string }>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export class WeChatChannel extends BaseChannel {
|
|
41
|
+
type = 'wechat';
|
|
42
|
+
private config: WeChatChannelConfig;
|
|
43
|
+
private accessToken: string | null = null;
|
|
44
|
+
private tokenExpiry = 0;
|
|
45
|
+
|
|
46
|
+
constructor(config: WeChatChannelConfig) {
|
|
47
|
+
super();
|
|
48
|
+
this.config = config;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async start(): Promise<void> {
|
|
52
|
+
// TODO: Start HTTP server to receive WeChat push messages
|
|
53
|
+
// 1. Verify signature on GET requests
|
|
54
|
+
// 2. Parse XML messages on POST requests
|
|
55
|
+
// 3. Route to handler and reply with XML
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async stop(): Promise<void> {
|
|
59
|
+
// TODO: Stop HTTP server
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Get or refresh access token */
|
|
63
|
+
async getAccessToken(): Promise<string> {
|
|
64
|
+
if (this.accessToken && Date.now() < this.tokenExpiry) {
|
|
65
|
+
return this.accessToken;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// TODO: Implement token refresh
|
|
69
|
+
// const url = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${this.config.appId}&secret=${this.config.appSecret}`;
|
|
70
|
+
// const res = await fetch(url);
|
|
71
|
+
// const data = await res.json();
|
|
72
|
+
// this.accessToken = data.access_token;
|
|
73
|
+
// this.tokenExpiry = Date.now() + (data.expires_in - 300) * 1000;
|
|
74
|
+
|
|
75
|
+
return this.accessToken ?? '';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Handle incoming WeChat message */
|
|
79
|
+
async handleMessage(wxMsg: WeChatMessage): Promise<string> {
|
|
80
|
+
if (wxMsg.msgType === 'event') {
|
|
81
|
+
return this.handleEvent(wxMsg);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const message = this.wechatToMessage(wxMsg);
|
|
85
|
+
if (this.handler) {
|
|
86
|
+
const reply = await this.handler(message);
|
|
87
|
+
return reply.content;
|
|
88
|
+
}
|
|
89
|
+
return '';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Handle WeChat events (subscribe, scan, etc.) */
|
|
93
|
+
private handleEvent(wxMsg: WeChatMessage): string {
|
|
94
|
+
switch (wxMsg.event) {
|
|
95
|
+
case 'subscribe':
|
|
96
|
+
return 'Welcome! How can I help you?';
|
|
97
|
+
case 'SCAN':
|
|
98
|
+
return `QR code scanned: ${wxMsg.eventKey}`;
|
|
99
|
+
default:
|
|
100
|
+
return '';
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Convert WeChat message to internal Message */
|
|
105
|
+
private wechatToMessage(wxMsg: WeChatMessage): Message {
|
|
106
|
+
return {
|
|
107
|
+
id: wxMsg.msgId ?? `wx-${wxMsg.createTime}`,
|
|
108
|
+
role: 'user',
|
|
109
|
+
content: wxMsg.content ?? '',
|
|
110
|
+
timestamp: wxMsg.createTime * 1000,
|
|
111
|
+
metadata: {
|
|
112
|
+
channel: 'wechat',
|
|
113
|
+
fromUser: wxMsg.fromUserName,
|
|
114
|
+
toUser: wxMsg.toUserName,
|
|
115
|
+
msgType: wxMsg.msgType,
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Send template message */
|
|
121
|
+
async sendTemplateMessage(data: TemplateMessageData): Promise<boolean> {
|
|
122
|
+
// TODO: Implement
|
|
123
|
+
// const token = await this.getAccessToken();
|
|
124
|
+
// const url = `https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=${token}`;
|
|
125
|
+
// const res = await fetch(url, {
|
|
126
|
+
// method: 'POST',
|
|
127
|
+
// body: JSON.stringify({
|
|
128
|
+
// touser: data.toUser,
|
|
129
|
+
// template_id: data.templateId,
|
|
130
|
+
// url: data.url,
|
|
131
|
+
// data: data.data,
|
|
132
|
+
// }),
|
|
133
|
+
// });
|
|
134
|
+
void data;
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Generate QR code for login (stub) */
|
|
139
|
+
async generateLoginQR(): Promise<{ ticket: string; url: string; expireSeconds: number }> {
|
|
140
|
+
// TODO: Implement with WeChat QR code API
|
|
141
|
+
// const token = await this.getAccessToken();
|
|
142
|
+
// POST to https://api.weixin.qq.com/cgi-bin/qrcode/create
|
|
143
|
+
return {
|
|
144
|
+
ticket: 'stub-ticket',
|
|
145
|
+
url: 'https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=stub-ticket',
|
|
146
|
+
expireSeconds: 300,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
}
|
package/src/core/a2a.ts
CHANGED
|
@@ -1,143 +1,143 @@
|
|
|
1
|
-
import { EventEmitter } from 'events';
|
|
2
|
-
import { Room } from './room';
|
|
3
|
-
import type { Message, IAgent } from './types';
|
|
4
|
-
import { Logger } from './logger';
|
|
5
|
-
|
|
6
|
-
// ── A2A Types ───────────────────────────────────────────────
|
|
7
|
-
|
|
8
|
-
export interface AgentCapability {
|
|
9
|
-
name: string;
|
|
10
|
-
description: string;
|
|
11
|
-
inputSchema?: Record<string, unknown>;
|
|
12
|
-
outputSchema?: Record<string, unknown>;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface AgentRegistration {
|
|
16
|
-
agentName: string;
|
|
17
|
-
capabilities: AgentCapability[];
|
|
18
|
-
endpoint?: string;
|
|
19
|
-
metadata?: Record<string, unknown>;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface A2ARequest {
|
|
23
|
-
id: string;
|
|
24
|
-
from: string;
|
|
25
|
-
to: string;
|
|
26
|
-
capability: string;
|
|
27
|
-
payload: string;
|
|
28
|
-
timestamp: number;
|
|
29
|
-
timeout?: number;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface A2AResponse {
|
|
33
|
-
requestId: string;
|
|
34
|
-
from: string;
|
|
35
|
-
status: 'success' | 'error' | 'timeout';
|
|
36
|
-
payload?: string;
|
|
37
|
-
error?: string;
|
|
38
|
-
timestamp: number;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// ── Agent Registry ──────────────────────────────────────────
|
|
42
|
-
|
|
43
|
-
export class AgentRegistry extends EventEmitter {
|
|
44
|
-
private registrations: Map<string, AgentRegistration> = new Map();
|
|
45
|
-
private agents: Map<string, IAgent> = new Map();
|
|
46
|
-
private room: Room;
|
|
47
|
-
private logger = new Logger('a2a');
|
|
48
|
-
|
|
49
|
-
constructor(room?: Room) {
|
|
50
|
-
super();
|
|
51
|
-
this.room = room ?? new Room('a2a-default');
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
register(agent: IAgent, capabilities: AgentCapability[]): void {
|
|
55
|
-
const reg: AgentRegistration = { agentName: agent.name, capabilities };
|
|
56
|
-
this.registrations.set(agent.name, reg);
|
|
57
|
-
this.agents.set(agent.name, agent);
|
|
58
|
-
this.room.addAgent(agent);
|
|
59
|
-
this.logger.info('Agent registered', { name: agent.name, capabilities: capabilities.map(c => c.name) });
|
|
60
|
-
this.emit('agent:registered', reg);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
unregister(name: string): void {
|
|
64
|
-
this.registrations.delete(name);
|
|
65
|
-
this.agents.delete(name);
|
|
66
|
-
this.room.removeAgent(name);
|
|
67
|
-
this.emit('agent:unregistered', name);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
discover(capability?: string): AgentRegistration[] {
|
|
71
|
-
const all = Array.from(this.registrations.values());
|
|
72
|
-
if (!capability) return all;
|
|
73
|
-
return all.filter(r => r.capabilities.some(c => c.name === capability));
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
getAgent(name: string): IAgent | undefined {
|
|
77
|
-
return this.agents.get(name);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
async request(req: A2ARequest): Promise<A2AResponse> {
|
|
81
|
-
const agent = this.agents.get(req.to);
|
|
82
|
-
if (!agent) {
|
|
83
|
-
return {
|
|
84
|
-
requestId: req.id,
|
|
85
|
-
from: req.to,
|
|
86
|
-
status: 'error',
|
|
87
|
-
error: `Agent "${req.to}" not found`,
|
|
88
|
-
timestamp: Date.now(),
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const message: Message = {
|
|
93
|
-
id: req.id,
|
|
94
|
-
role: 'user',
|
|
95
|
-
content: req.payload,
|
|
96
|
-
timestamp: req.timestamp,
|
|
97
|
-
metadata: { a2a: true, from: req.from, capability: req.capability },
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
this.emit('request', req);
|
|
101
|
-
|
|
102
|
-
try {
|
|
103
|
-
const timeoutMs = req.timeout ?? 30000;
|
|
104
|
-
const response = await Promise.race([
|
|
105
|
-
agent.handleMessage(message),
|
|
106
|
-
new Promise<never>((_, reject) =>
|
|
107
|
-
setTimeout(() => reject(new Error('A2A request timeout')), timeoutMs),
|
|
108
|
-
),
|
|
109
|
-
]);
|
|
110
|
-
|
|
111
|
-
const res: A2AResponse = {
|
|
112
|
-
requestId: req.id,
|
|
113
|
-
from: req.to,
|
|
114
|
-
status: 'success',
|
|
115
|
-
payload: response.content,
|
|
116
|
-
timestamp: Date.now(),
|
|
117
|
-
};
|
|
118
|
-
this.emit('response', res);
|
|
119
|
-
return res;
|
|
120
|
-
} catch (err) {
|
|
121
|
-
const res: A2AResponse = {
|
|
122
|
-
requestId: req.id,
|
|
123
|
-
from: req.to,
|
|
124
|
-
status: (err as Error).message.includes('timeout') ? 'timeout' : 'error',
|
|
125
|
-
error: (err as Error).message,
|
|
126
|
-
timestamp: Date.now(),
|
|
127
|
-
};
|
|
128
|
-
this.emit('response', res);
|
|
129
|
-
return res;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
async call(from: string, to: string, capability: string, payload: string): Promise<A2AResponse> {
|
|
134
|
-
return this.request({
|
|
135
|
-
id: `a2a_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
136
|
-
from,
|
|
137
|
-
to,
|
|
138
|
-
capability,
|
|
139
|
-
payload,
|
|
140
|
-
timestamp: Date.now(),
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
}
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
import { Room } from './room';
|
|
3
|
+
import type { Message, IAgent } from './types';
|
|
4
|
+
import { Logger } from './logger';
|
|
5
|
+
|
|
6
|
+
// ── A2A Types ───────────────────────────────────────────────
|
|
7
|
+
|
|
8
|
+
export interface AgentCapability {
|
|
9
|
+
name: string;
|
|
10
|
+
description: string;
|
|
11
|
+
inputSchema?: Record<string, unknown>;
|
|
12
|
+
outputSchema?: Record<string, unknown>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface AgentRegistration {
|
|
16
|
+
agentName: string;
|
|
17
|
+
capabilities: AgentCapability[];
|
|
18
|
+
endpoint?: string;
|
|
19
|
+
metadata?: Record<string, unknown>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface A2ARequest {
|
|
23
|
+
id: string;
|
|
24
|
+
from: string;
|
|
25
|
+
to: string;
|
|
26
|
+
capability: string;
|
|
27
|
+
payload: string;
|
|
28
|
+
timestamp: number;
|
|
29
|
+
timeout?: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface A2AResponse {
|
|
33
|
+
requestId: string;
|
|
34
|
+
from: string;
|
|
35
|
+
status: 'success' | 'error' | 'timeout';
|
|
36
|
+
payload?: string;
|
|
37
|
+
error?: string;
|
|
38
|
+
timestamp: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ── Agent Registry ──────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
export class AgentRegistry extends EventEmitter {
|
|
44
|
+
private registrations: Map<string, AgentRegistration> = new Map();
|
|
45
|
+
private agents: Map<string, IAgent> = new Map();
|
|
46
|
+
private room: Room;
|
|
47
|
+
private logger = new Logger('a2a');
|
|
48
|
+
|
|
49
|
+
constructor(room?: Room) {
|
|
50
|
+
super();
|
|
51
|
+
this.room = room ?? new Room('a2a-default');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
register(agent: IAgent, capabilities: AgentCapability[]): void {
|
|
55
|
+
const reg: AgentRegistration = { agentName: agent.name, capabilities };
|
|
56
|
+
this.registrations.set(agent.name, reg);
|
|
57
|
+
this.agents.set(agent.name, agent);
|
|
58
|
+
this.room.addAgent(agent);
|
|
59
|
+
this.logger.info('Agent registered', { name: agent.name, capabilities: capabilities.map(c => c.name) });
|
|
60
|
+
this.emit('agent:registered', reg);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
unregister(name: string): void {
|
|
64
|
+
this.registrations.delete(name);
|
|
65
|
+
this.agents.delete(name);
|
|
66
|
+
this.room.removeAgent(name);
|
|
67
|
+
this.emit('agent:unregistered', name);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
discover(capability?: string): AgentRegistration[] {
|
|
71
|
+
const all = Array.from(this.registrations.values());
|
|
72
|
+
if (!capability) return all;
|
|
73
|
+
return all.filter(r => r.capabilities.some(c => c.name === capability));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
getAgent(name: string): IAgent | undefined {
|
|
77
|
+
return this.agents.get(name);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async request(req: A2ARequest): Promise<A2AResponse> {
|
|
81
|
+
const agent = this.agents.get(req.to);
|
|
82
|
+
if (!agent) {
|
|
83
|
+
return {
|
|
84
|
+
requestId: req.id,
|
|
85
|
+
from: req.to,
|
|
86
|
+
status: 'error',
|
|
87
|
+
error: `Agent "${req.to}" not found`,
|
|
88
|
+
timestamp: Date.now(),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const message: Message = {
|
|
93
|
+
id: req.id,
|
|
94
|
+
role: 'user',
|
|
95
|
+
content: req.payload,
|
|
96
|
+
timestamp: req.timestamp,
|
|
97
|
+
metadata: { a2a: true, from: req.from, capability: req.capability },
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
this.emit('request', req);
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
const timeoutMs = req.timeout ?? 30000;
|
|
104
|
+
const response = await Promise.race([
|
|
105
|
+
agent.handleMessage(message),
|
|
106
|
+
new Promise<never>((_, reject) =>
|
|
107
|
+
setTimeout(() => reject(new Error('A2A request timeout')), timeoutMs),
|
|
108
|
+
),
|
|
109
|
+
]);
|
|
110
|
+
|
|
111
|
+
const res: A2AResponse = {
|
|
112
|
+
requestId: req.id,
|
|
113
|
+
from: req.to,
|
|
114
|
+
status: 'success',
|
|
115
|
+
payload: response.content,
|
|
116
|
+
timestamp: Date.now(),
|
|
117
|
+
};
|
|
118
|
+
this.emit('response', res);
|
|
119
|
+
return res;
|
|
120
|
+
} catch (err) {
|
|
121
|
+
const res: A2AResponse = {
|
|
122
|
+
requestId: req.id,
|
|
123
|
+
from: req.to,
|
|
124
|
+
status: (err as Error).message.includes('timeout') ? 'timeout' : 'error',
|
|
125
|
+
error: (err as Error).message,
|
|
126
|
+
timestamp: Date.now(),
|
|
127
|
+
};
|
|
128
|
+
this.emit('response', res);
|
|
129
|
+
return res;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async call(from: string, to: string, capability: string, payload: string): Promise<A2AResponse> {
|
|
134
|
+
return this.request({
|
|
135
|
+
id: `a2a_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
136
|
+
from,
|
|
137
|
+
to,
|
|
138
|
+
capability,
|
|
139
|
+
payload,
|
|
140
|
+
timestamp: Date.now(),
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|