opc-agent 2.0.0 → 2.0.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/README.md +545 -365
- package/dist/channels/email.d.ts +32 -26
- package/dist/channels/email.js +239 -62
- package/dist/channels/feishu.d.ts +21 -6
- package/dist/channels/feishu.js +225 -126
- package/dist/channels/websocket.d.ts +46 -3
- package/dist/channels/websocket.js +306 -37
- package/dist/channels/wechat.d.ts +33 -13
- package/dist/channels/wechat.js +229 -42
- package/dist/cli.js +712 -11
- package/dist/core/a2a.d.ts +17 -0
- package/dist/core/a2a.js +43 -1
- package/dist/core/agent.d.ts +16 -0
- package/dist/core/agent.js +108 -0
- package/dist/core/runtime.d.ts +6 -0
- package/dist/core/runtime.js +161 -2
- package/dist/core/sandbox.d.ts +26 -0
- package/dist/core/sandbox.js +117 -0
- package/dist/core/workflow-graph.d.ts +93 -0
- package/dist/core/workflow-graph.js +247 -0
- package/dist/doctor.d.ts +15 -0
- package/dist/doctor.js +183 -0
- package/dist/eval/index.d.ts +65 -0
- package/dist/eval/index.js +191 -0
- package/dist/index.d.ts +32 -6
- package/dist/index.js +63 -4
- package/dist/plugins/content-filter.d.ts +7 -0
- package/dist/plugins/content-filter.js +25 -0
- package/dist/plugins/index.d.ts +42 -0
- package/dist/plugins/index.js +108 -2
- package/dist/plugins/logger.d.ts +6 -0
- package/dist/plugins/logger.js +20 -0
- package/dist/plugins/rate-limiter.d.ts +7 -0
- package/dist/plugins/rate-limiter.js +35 -0
- package/dist/protocols/a2a/client.d.ts +25 -0
- package/dist/protocols/a2a/client.js +115 -0
- package/dist/protocols/a2a/index.d.ts +6 -0
- package/dist/protocols/a2a/index.js +12 -0
- package/dist/protocols/a2a/server.d.ts +41 -0
- package/dist/protocols/a2a/server.js +295 -0
- package/dist/protocols/a2a/types.d.ts +91 -0
- package/dist/protocols/a2a/types.js +15 -0
- package/dist/protocols/a2a/utils.d.ts +6 -0
- package/dist/protocols/a2a/utils.js +47 -0
- package/dist/protocols/agui/client.d.ts +10 -0
- package/dist/protocols/agui/client.js +75 -0
- package/dist/protocols/agui/index.d.ts +4 -0
- package/dist/protocols/agui/index.js +25 -0
- package/dist/protocols/agui/server.d.ts +37 -0
- package/dist/protocols/agui/server.js +191 -0
- package/dist/protocols/agui/types.d.ts +107 -0
- package/dist/protocols/agui/types.js +17 -0
- package/dist/protocols/index.d.ts +2 -0
- package/dist/protocols/index.js +19 -0
- package/dist/protocols/mcp/agent-tools.d.ts +11 -0
- package/dist/protocols/mcp/agent-tools.js +129 -0
- package/dist/protocols/mcp/index.d.ts +5 -0
- package/dist/protocols/mcp/index.js +11 -0
- package/dist/protocols/mcp/server.d.ts +31 -0
- package/dist/protocols/mcp/server.js +248 -0
- package/dist/protocols/mcp/types.d.ts +92 -0
- package/dist/protocols/mcp/types.js +17 -0
- package/dist/publish/index.d.ts +45 -0
- package/dist/publish/index.js +350 -0
- package/dist/schema/oad.d.ts +682 -65
- package/dist/schema/oad.js +36 -3
- package/dist/security/approval.d.ts +36 -0
- package/dist/security/approval.js +113 -0
- package/dist/security/index.d.ts +4 -0
- package/dist/security/index.js +8 -0
- package/dist/security/keys.d.ts +16 -0
- package/dist/security/keys.js +117 -0
- package/dist/studio/server.d.ts +63 -0
- package/dist/studio/server.js +625 -0
- package/dist/studio-ui/index.html +662 -0
- package/dist/telemetry/index.d.ts +93 -0
- package/dist/telemetry/index.js +285 -0
- package/package.json +5 -3
- package/scripts/install.ps1 +31 -0
- package/scripts/install.sh +40 -0
- package/src/channels/email.ts +351 -177
- package/src/channels/feishu.ts +349 -236
- package/src/channels/websocket.ts +399 -87
- package/src/channels/wechat.ts +329 -149
- package/src/cli.ts +783 -12
- package/src/core/a2a.ts +60 -0
- package/src/core/agent.ts +125 -0
- package/src/core/runtime.ts +127 -0
- package/src/core/sandbox.ts +143 -0
- package/src/core/workflow-graph.ts +365 -0
- package/src/doctor.ts +156 -0
- package/src/eval/index.ts +211 -0
- package/src/eval/suites/basic.json +16 -0
- package/src/eval/suites/memory.json +12 -0
- package/src/eval/suites/safety.json +14 -0
- package/src/index.ts +58 -6
- package/src/plugins/content-filter.ts +23 -0
- package/src/plugins/index.ts +133 -2
- package/src/plugins/logger.ts +18 -0
- package/src/plugins/rate-limiter.ts +38 -0
- package/src/protocols/a2a/client.ts +132 -0
- package/src/protocols/a2a/index.ts +8 -0
- package/src/protocols/a2a/server.ts +333 -0
- package/src/protocols/a2a/types.ts +88 -0
- package/src/protocols/a2a/utils.ts +50 -0
- package/src/protocols/agui/client.ts +83 -0
- package/src/protocols/agui/index.ts +4 -0
- package/src/protocols/agui/server.ts +218 -0
- package/src/protocols/agui/types.ts +153 -0
- package/src/protocols/index.ts +2 -0
- package/src/protocols/mcp/agent-tools.ts +134 -0
- package/src/protocols/mcp/index.ts +8 -0
- package/src/protocols/mcp/server.ts +262 -0
- package/src/protocols/mcp/types.ts +69 -0
- package/src/publish/index.ts +376 -0
- package/src/schema/oad.ts +39 -2
- package/src/security/approval.ts +131 -0
- package/src/security/index.ts +3 -0
- package/src/security/keys.ts +87 -0
- package/src/studio/server.ts +629 -0
- package/src/studio-ui/index.html +662 -0
- package/src/telemetry/index.ts +324 -0
- package/src/types/agent-workstation.d.ts +2 -0
- package/tests/a2a-protocol.test.ts +285 -0
- package/tests/agui-protocol.test.ts +246 -0
- package/tests/channels/discord.test.ts +79 -0
- package/tests/channels/email.test.ts +148 -0
- package/tests/channels/feishu.test.ts +123 -0
- package/tests/channels/telegram.test.ts +129 -0
- package/tests/channels/websocket.test.ts +53 -0
- package/tests/channels/wechat.test.ts +170 -0
- package/tests/chat-cli.test.ts +160 -0
- package/tests/daemon.test.ts +135 -0
- package/tests/deepbrain-wire.test.ts +234 -0
- package/tests/doctor.test.ts +38 -0
- package/tests/eval.test.ts +173 -0
- package/tests/init-role.test.ts +124 -0
- package/tests/mcp-client.test.ts +92 -0
- package/tests/mcp-server.test.ts +178 -0
- package/tests/plugin-a2a-enhanced.test.ts +230 -0
- package/tests/publish.test.ts +231 -0
- package/tests/scheduler.test.ts +200 -0
- package/tests/security-enhanced.test.ts +233 -0
- package/tests/skill-learner.test.ts +161 -0
- package/tests/studio.test.ts +229 -0
- package/tests/subagent.test.ts +63 -0
- package/tests/telemetry.test.ts +186 -0
- package/tests/tools/builtin-extended.test.ts +138 -0
- package/tests/workflow-graph.test.ts +279 -0
- package/tutorial/customer-service-agent/README.md +612 -0
- package/tutorial/customer-service-agent/SOUL.md +26 -0
- package/tutorial/customer-service-agent/agent.yaml +63 -0
- package/tutorial/customer-service-agent/package.json +19 -0
- package/tutorial/customer-service-agent/src/index.ts +69 -0
- package/tutorial/customer-service-agent/src/skills/faq.ts +27 -0
- package/tutorial/customer-service-agent/src/skills/ticket.ts +22 -0
- package/tutorial/customer-service-agent/tsconfig.json +14 -0
package/dist/channels/feishu.js
CHANGED
|
@@ -35,6 +35,8 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.FeishuChannel = void 0;
|
|
37
37
|
const index_1 = require("./index");
|
|
38
|
+
const http = __importStar(require("http"));
|
|
39
|
+
const https = __importStar(require("https"));
|
|
38
40
|
class FeishuChannel extends index_1.BaseChannel {
|
|
39
41
|
type = 'feishu';
|
|
40
42
|
config;
|
|
@@ -48,7 +50,7 @@ class FeishuChannel extends index_1.BaseChannel {
|
|
|
48
50
|
appSecret: config.appSecret ?? process.env.FEISHU_APP_SECRET ?? '',
|
|
49
51
|
verificationToken: config.verificationToken ?? process.env.FEISHU_VERIFICATION_TOKEN ?? '',
|
|
50
52
|
encryptKey: config.encryptKey ?? process.env.FEISHU_ENCRYPT_KEY,
|
|
51
|
-
port: config.port ??
|
|
53
|
+
port: config.port ?? 8081,
|
|
52
54
|
apiBase: config.apiBase ?? 'https://open.feishu.cn',
|
|
53
55
|
};
|
|
54
56
|
}
|
|
@@ -57,163 +59,260 @@ class FeishuChannel extends index_1.BaseChannel {
|
|
|
57
59
|
console.warn('[FeishuChannel] Missing appId/appSecret. Set FEISHU_APP_ID and FEISHU_APP_SECRET.');
|
|
58
60
|
return;
|
|
59
61
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
this.server = http.createServer(async (req, res) => {
|
|
63
|
+
if (req.method === 'GET' && req.url === '/health') {
|
|
64
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
65
|
+
res.end(JSON.stringify({ status: 'ok', channel: 'feishu' }));
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (req.method !== 'POST') {
|
|
69
|
+
res.writeHead(404);
|
|
70
|
+
res.end();
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
65
73
|
try {
|
|
66
|
-
const body = req
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
res.json({ challenge: body.challenge });
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
// Deduplicate events
|
|
73
|
-
const eventId = body.header?.event_id;
|
|
74
|
-
if (eventId && this.processedEvents.has(eventId)) {
|
|
75
|
-
res.json({ ok: true });
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
if (eventId) {
|
|
79
|
-
this.processedEvents.add(eventId);
|
|
80
|
-
// Prune old events (keep last 1000)
|
|
81
|
-
if (this.processedEvents.size > 1000) {
|
|
82
|
-
const arr = [...this.processedEvents];
|
|
83
|
-
this.processedEvents = new Set(arr.slice(-500));
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
// Verify token
|
|
87
|
-
if (this.config.verificationToken && body.header?.token !== this.config.verificationToken) {
|
|
88
|
-
res.status(403).json({ error: 'Invalid verification token' });
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
// Handle im.message.receive_v1
|
|
92
|
-
const event = body.event;
|
|
93
|
-
if (body.header?.event_type === 'im.message.receive_v1' && this.handler) {
|
|
94
|
-
const msgBody = event?.message;
|
|
95
|
-
if (!msgBody) {
|
|
96
|
-
res.json({ ok: true });
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
// Only handle text messages for now
|
|
100
|
-
const msgType = msgBody.message_type;
|
|
101
|
-
let content = '';
|
|
102
|
-
if (msgType === 'text') {
|
|
103
|
-
try {
|
|
104
|
-
const parsed = JSON.parse(msgBody.content);
|
|
105
|
-
content = parsed.text ?? '';
|
|
106
|
-
}
|
|
107
|
-
catch {
|
|
108
|
-
content = msgBody.content ?? '';
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
else {
|
|
112
|
-
// Acknowledge non-text silently
|
|
113
|
-
res.json({ ok: true });
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
// Strip @bot mentions
|
|
117
|
-
content = content.replace(/@_user_\d+/g, '').trim();
|
|
118
|
-
if (!content) {
|
|
119
|
-
res.json({ ok: true });
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
const chatId = msgBody.chat_id;
|
|
123
|
-
const senderId = event.sender?.sender_id?.open_id ?? 'unknown';
|
|
124
|
-
const msg = {
|
|
125
|
-
id: `feishu_${msgBody.message_id}`,
|
|
126
|
-
role: 'user',
|
|
127
|
-
content,
|
|
128
|
-
timestamp: parseInt(msgBody.create_time, 10) || Date.now(),
|
|
129
|
-
metadata: {
|
|
130
|
-
sessionId: `feishu_${chatId}`,
|
|
131
|
-
chatId,
|
|
132
|
-
userId: senderId,
|
|
133
|
-
platform: 'feishu',
|
|
134
|
-
messageId: msgBody.message_id,
|
|
135
|
-
chatType: msgBody.chat_type, // 'p2p' or 'group'
|
|
136
|
-
},
|
|
137
|
-
};
|
|
138
|
-
const response = await this.handler(msg);
|
|
139
|
-
await this.sendTextMessage(chatId, response.content);
|
|
140
|
-
}
|
|
141
|
-
res.json({ ok: true });
|
|
74
|
+
const body = await this.readBody(req);
|
|
75
|
+
const parsed = JSON.parse(body);
|
|
76
|
+
await this.handleEvent(parsed, res);
|
|
142
77
|
}
|
|
143
78
|
catch (err) {
|
|
144
|
-
console.error('[FeishuChannel] Error
|
|
145
|
-
res.
|
|
79
|
+
console.error('[FeishuChannel] Error:', err);
|
|
80
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
81
|
+
res.end(JSON.stringify({ error: 'Internal error' }));
|
|
146
82
|
}
|
|
147
83
|
});
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
84
|
+
return new Promise((resolve) => {
|
|
85
|
+
this.server.listen(this.config.port, () => {
|
|
86
|
+
console.log(`[FeishuChannel] Listening on port ${this.config.port}`);
|
|
87
|
+
resolve();
|
|
88
|
+
});
|
|
153
89
|
});
|
|
154
90
|
}
|
|
155
91
|
async stop() {
|
|
156
|
-
|
|
157
|
-
this.server
|
|
92
|
+
return new Promise((resolve, reject) => {
|
|
93
|
+
if (!this.server)
|
|
94
|
+
return resolve();
|
|
95
|
+
this.server.close((err) => (err ? reject(err) : resolve()));
|
|
158
96
|
this.server = null;
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
/** Handle Feishu event */
|
|
100
|
+
async handleEvent(body, res) {
|
|
101
|
+
// URL verification challenge
|
|
102
|
+
if (body.type === 'url_verification') {
|
|
103
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
104
|
+
res.end(JSON.stringify({ challenge: body.challenge }));
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
// Deduplicate events
|
|
108
|
+
const eventId = body.header?.event_id;
|
|
109
|
+
if (eventId && this.processedEvents.has(eventId)) {
|
|
110
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
111
|
+
res.end(JSON.stringify({ ok: true }));
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (eventId) {
|
|
115
|
+
this.processedEvents.add(eventId);
|
|
116
|
+
if (this.processedEvents.size > 1000) {
|
|
117
|
+
const arr = [...this.processedEvents];
|
|
118
|
+
this.processedEvents = new Set(arr.slice(-500));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// Verify token
|
|
122
|
+
if (this.config.verificationToken && body.header?.token !== this.config.verificationToken) {
|
|
123
|
+
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
124
|
+
res.end(JSON.stringify({ error: 'Invalid verification token' }));
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
// Handle im.message.receive_v1
|
|
128
|
+
const event = body.event;
|
|
129
|
+
if (body.header?.event_type === 'im.message.receive_v1' && this.handler) {
|
|
130
|
+
const msgBody = event?.message;
|
|
131
|
+
if (!msgBody) {
|
|
132
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
133
|
+
res.end(JSON.stringify({ ok: true }));
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const msgType = msgBody.message_type;
|
|
137
|
+
let content = '';
|
|
138
|
+
if (msgType === 'text') {
|
|
139
|
+
try {
|
|
140
|
+
const parsed = JSON.parse(msgBody.content);
|
|
141
|
+
content = parsed.text ?? '';
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
content = msgBody.content ?? '';
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
149
|
+
res.end(JSON.stringify({ ok: true }));
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
// Strip @bot mentions
|
|
153
|
+
content = content.replace(/@_user_\d+/g, '').trim();
|
|
154
|
+
if (!content) {
|
|
155
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
156
|
+
res.end(JSON.stringify({ ok: true }));
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const chatId = msgBody.chat_id;
|
|
160
|
+
const senderId = event.sender?.sender_id?.open_id ?? 'unknown';
|
|
161
|
+
const msg = {
|
|
162
|
+
id: `feishu_${msgBody.message_id}`,
|
|
163
|
+
role: 'user',
|
|
164
|
+
content,
|
|
165
|
+
timestamp: parseInt(msgBody.create_time, 10) || Date.now(),
|
|
166
|
+
metadata: {
|
|
167
|
+
sessionId: `feishu_${chatId}`,
|
|
168
|
+
chatId,
|
|
169
|
+
userId: senderId,
|
|
170
|
+
platform: 'feishu',
|
|
171
|
+
messageId: msgBody.message_id,
|
|
172
|
+
chatType: msgBody.chat_type,
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
// Don't block the response
|
|
176
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
177
|
+
res.end(JSON.stringify({ ok: true }));
|
|
178
|
+
try {
|
|
179
|
+
const response = await this.handler(msg);
|
|
180
|
+
await this.sendTextMessage(chatId, response.content);
|
|
181
|
+
}
|
|
182
|
+
catch (err) {
|
|
183
|
+
console.error('[FeishuChannel] Handler error:', err);
|
|
184
|
+
}
|
|
185
|
+
return;
|
|
159
186
|
}
|
|
187
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
188
|
+
res.end(JSON.stringify({ ok: true }));
|
|
189
|
+
}
|
|
190
|
+
/** Parse Feishu event body (exported for testing) */
|
|
191
|
+
static parseEventBody(body) {
|
|
192
|
+
if (body.type === 'url_verification') {
|
|
193
|
+
return { type: 'url_verification', challenge: body.challenge };
|
|
194
|
+
}
|
|
195
|
+
const eventType = body.header?.event_type;
|
|
196
|
+
const event = body.event;
|
|
197
|
+
if (eventType === 'im.message.receive_v1' && event?.message) {
|
|
198
|
+
const msgBody = event.message;
|
|
199
|
+
let content = '';
|
|
200
|
+
if (msgBody.message_type === 'text') {
|
|
201
|
+
try {
|
|
202
|
+
const parsed = JSON.parse(msgBody.content);
|
|
203
|
+
content = parsed.text ?? '';
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
content = msgBody.content ?? '';
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return {
|
|
210
|
+
type: 'message',
|
|
211
|
+
eventType,
|
|
212
|
+
content: content.replace(/@_user_\d+/g, '').trim(),
|
|
213
|
+
chatId: msgBody.chat_id,
|
|
214
|
+
senderId: event.sender?.sender_id?.open_id,
|
|
215
|
+
messageId: msgBody.message_id,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
return { type: 'unknown', eventType };
|
|
160
219
|
}
|
|
161
220
|
/** Get tenant access token (cached) */
|
|
162
221
|
async getAccessToken() {
|
|
163
222
|
if (this.tokenCache && Date.now() < this.tokenCache.expiresAt) {
|
|
164
223
|
return this.tokenCache.token;
|
|
165
224
|
}
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
body: JSON.stringify({
|
|
170
|
-
app_id: this.config.appId,
|
|
171
|
-
app_secret: this.config.appSecret,
|
|
172
|
-
}),
|
|
225
|
+
const body = JSON.stringify({
|
|
226
|
+
app_id: this.config.appId,
|
|
227
|
+
app_secret: this.config.appSecret,
|
|
173
228
|
});
|
|
174
|
-
const
|
|
229
|
+
const result = await this.httpsPost(`${this.config.apiBase}/open-apis/auth/v3/tenant_access_token/internal`, body);
|
|
230
|
+
const data = JSON.parse(result);
|
|
175
231
|
if (data.code !== 0) {
|
|
176
|
-
throw new Error(`[FeishuChannel] Failed to get access token: ${
|
|
232
|
+
throw new Error(`[FeishuChannel] Failed to get access token: ${result}`);
|
|
177
233
|
}
|
|
178
234
|
this.tokenCache = {
|
|
179
235
|
token: data.tenant_access_token,
|
|
180
|
-
expiresAt: Date.now() + (data.expire - 60) * 1000,
|
|
236
|
+
expiresAt: Date.now() + (data.expire - 60) * 1000,
|
|
181
237
|
};
|
|
182
238
|
return this.tokenCache.token;
|
|
183
239
|
}
|
|
184
240
|
/** Send a text message to a chat */
|
|
185
241
|
async sendTextMessage(chatId, text) {
|
|
186
242
|
const token = await this.getAccessToken();
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
'Authorization': `Bearer ${token}`,
|
|
192
|
-
},
|
|
193
|
-
body: JSON.stringify({
|
|
194
|
-
receive_id: chatId,
|
|
195
|
-
msg_type: 'text',
|
|
196
|
-
content: JSON.stringify({ text }),
|
|
197
|
-
}),
|
|
243
|
+
const body = JSON.stringify({
|
|
244
|
+
receive_id: chatId,
|
|
245
|
+
msg_type: 'text',
|
|
246
|
+
content: JSON.stringify({ text }),
|
|
198
247
|
});
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
}
|
|
248
|
+
const url = `${this.config.apiBase}/open-apis/im/v1/messages?receive_id_type=chat_id`;
|
|
249
|
+
await this.httpsPostWithAuth(url, body, token);
|
|
202
250
|
}
|
|
203
251
|
/** Send an interactive card message */
|
|
204
252
|
async sendCardMessage(chatId, card) {
|
|
205
253
|
const token = await this.getAccessToken();
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
254
|
+
const body = JSON.stringify({
|
|
255
|
+
receive_id: chatId,
|
|
256
|
+
msg_type: 'interactive',
|
|
257
|
+
content: JSON.stringify(card),
|
|
258
|
+
});
|
|
259
|
+
const url = `${this.config.apiBase}/open-apis/im/v1/messages?receive_id_type=chat_id`;
|
|
260
|
+
await this.httpsPostWithAuth(url, body, token);
|
|
261
|
+
}
|
|
262
|
+
/** Read request body */
|
|
263
|
+
readBody(req) {
|
|
264
|
+
return new Promise((resolve, reject) => {
|
|
265
|
+
const chunks = [];
|
|
266
|
+
req.on('data', (chunk) => chunks.push(chunk));
|
|
267
|
+
req.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));
|
|
268
|
+
req.on('error', reject);
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
/** HTTPS POST */
|
|
272
|
+
httpsPost(url, body) {
|
|
273
|
+
return new Promise((resolve, reject) => {
|
|
274
|
+
const parsed = new URL(url);
|
|
275
|
+
const req = https.request({
|
|
276
|
+
hostname: parsed.hostname,
|
|
277
|
+
path: parsed.pathname + parsed.search,
|
|
278
|
+
method: 'POST',
|
|
279
|
+
headers: {
|
|
280
|
+
'Content-Type': 'application/json',
|
|
281
|
+
'Content-Length': Buffer.byteLength(body),
|
|
282
|
+
},
|
|
283
|
+
}, (res) => {
|
|
284
|
+
const chunks = [];
|
|
285
|
+
res.on('data', (chunk) => chunks.push(chunk));
|
|
286
|
+
res.on('end', () => resolve(Buffer.concat(chunks).toString()));
|
|
287
|
+
res.on('error', reject);
|
|
288
|
+
});
|
|
289
|
+
req.on('error', reject);
|
|
290
|
+
req.write(body);
|
|
291
|
+
req.end();
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
/** HTTPS POST with Bearer auth */
|
|
295
|
+
httpsPostWithAuth(url, body, token) {
|
|
296
|
+
return new Promise((resolve, reject) => {
|
|
297
|
+
const parsed = new URL(url);
|
|
298
|
+
const req = https.request({
|
|
299
|
+
hostname: parsed.hostname,
|
|
300
|
+
path: parsed.pathname + parsed.search,
|
|
301
|
+
method: 'POST',
|
|
302
|
+
headers: {
|
|
303
|
+
'Content-Type': 'application/json',
|
|
304
|
+
'Content-Length': Buffer.byteLength(body),
|
|
305
|
+
'Authorization': `Bearer ${token}`,
|
|
306
|
+
},
|
|
307
|
+
}, (res) => {
|
|
308
|
+
const chunks = [];
|
|
309
|
+
res.on('data', (chunk) => chunks.push(chunk));
|
|
310
|
+
res.on('end', () => resolve(Buffer.concat(chunks).toString()));
|
|
311
|
+
res.on('error', reject);
|
|
312
|
+
});
|
|
313
|
+
req.on('error', reject);
|
|
314
|
+
req.write(body);
|
|
315
|
+
req.end();
|
|
217
316
|
});
|
|
218
317
|
}
|
|
219
318
|
}
|
|
@@ -1,15 +1,58 @@
|
|
|
1
1
|
import { BaseChannel } from './index';
|
|
2
2
|
/**
|
|
3
|
-
* WebSocket
|
|
3
|
+
* WebSocket Channel — v1.1.0
|
|
4
|
+
*
|
|
5
|
+
* Enhanced with:
|
|
6
|
+
* - Room support (multiple clients in a room)
|
|
7
|
+
* - Heartbeat/ping-pong to detect disconnected clients
|
|
8
|
+
* - Reconnection handling (session persistence)
|
|
9
|
+
* - Binary message support
|
|
10
|
+
* - Connection authentication (optional token in query string)
|
|
4
11
|
*/
|
|
12
|
+
export interface WebSocketChannelConfig {
|
|
13
|
+
port?: number;
|
|
14
|
+
/** Heartbeat interval in ms (default: 30000) */
|
|
15
|
+
heartbeatInterval?: number;
|
|
16
|
+
/** Valid auth tokens (if empty, no auth required) */
|
|
17
|
+
authTokens?: string[];
|
|
18
|
+
/** Max clients per room (default: 100) */
|
|
19
|
+
maxClientsPerRoom?: number;
|
|
20
|
+
}
|
|
5
21
|
export declare class WebSocketChannel extends BaseChannel {
|
|
6
22
|
readonly type = "websocket";
|
|
7
23
|
private wss;
|
|
8
|
-
private
|
|
24
|
+
private config;
|
|
9
25
|
private clients;
|
|
10
|
-
|
|
26
|
+
private rooms;
|
|
27
|
+
private heartbeatTimer;
|
|
28
|
+
constructor(configOrPort?: number | WebSocketChannelConfig);
|
|
11
29
|
start(): Promise<void>;
|
|
12
30
|
stop(): Promise<void>;
|
|
31
|
+
/** Handle text (JSON) messages */
|
|
32
|
+
private handleTextMessage;
|
|
33
|
+
/** Handle binary messages */
|
|
34
|
+
private handleBinaryMessage;
|
|
35
|
+
/** Join a room */
|
|
36
|
+
joinRoom(sessionId: string, roomId: string): boolean;
|
|
37
|
+
/** Leave a room */
|
|
38
|
+
leaveRoom(sessionId: string, roomId: string): void;
|
|
39
|
+
/** Remove client completely */
|
|
40
|
+
private removeClient;
|
|
41
|
+
/** Get room member session IDs */
|
|
42
|
+
getRoomMembers(roomId: string): string[];
|
|
43
|
+
/** Get all rooms */
|
|
44
|
+
getRooms(): string[];
|
|
45
|
+
/** Broadcast to all clients */
|
|
13
46
|
broadcast(content: string): void;
|
|
47
|
+
/** Broadcast to all clients in a room */
|
|
48
|
+
broadcastToRoom(roomId: string, data: any, excludeSessionId?: string): void;
|
|
49
|
+
/** Send to specific session */
|
|
50
|
+
sendToSession(sessionId: string, data: any): boolean;
|
|
51
|
+
/** Get connection stats */
|
|
52
|
+
getStats(): {
|
|
53
|
+
clients: number;
|
|
54
|
+
rooms: number;
|
|
55
|
+
roomDetails: Record<string, number>;
|
|
56
|
+
};
|
|
14
57
|
}
|
|
15
58
|
//# sourceMappingURL=websocket.d.ts.map
|