opc-agent 1.4.0 → 2.0.1
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 +25 -0
- package/README.md +91 -32
- 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/telegram.d.ts +30 -9
- package/dist/channels/telegram.js +125 -33
- 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 +1127 -19
- package/dist/core/a2a.d.ts +17 -0
- package/dist/core/a2a.js +43 -1
- package/dist/core/agent.d.ts +39 -0
- package/dist/core/agent.js +228 -3
- package/dist/core/runtime.d.ts +7 -0
- package/dist/core/runtime.js +205 -2
- package/dist/core/sandbox.d.ts +26 -0
- package/dist/core/sandbox.js +117 -0
- package/dist/core/scheduler.d.ts +52 -0
- package/dist/core/scheduler.js +168 -0
- package/dist/core/subagent.d.ts +28 -0
- package/dist/core/subagent.js +65 -0
- package/dist/core/workflow-graph.d.ts +93 -0
- package/dist/core/workflow-graph.js +247 -0
- package/dist/daemon.d.ts +3 -0
- package/dist/daemon.js +134 -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 +37 -6
- package/dist/index.js +75 -3
- 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/providers/index.d.ts +5 -1
- package/dist/providers/index.js +16 -9
- package/dist/publish/index.d.ts +45 -0
- package/dist/publish/index.js +350 -0
- package/dist/schema/oad.d.ts +859 -67
- package/dist/schema/oad.js +47 -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/skills/auto-learn.d.ts +28 -0
- package/dist/skills/auto-learn.js +257 -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/dist/tools/builtin/datetime.d.ts +3 -0
- package/dist/tools/builtin/datetime.js +44 -0
- package/dist/tools/builtin/file.d.ts +3 -0
- package/dist/tools/builtin/file.js +151 -0
- package/dist/tools/builtin/index.d.ts +15 -0
- package/dist/tools/builtin/index.js +30 -0
- package/dist/tools/builtin/shell.d.ts +3 -0
- package/dist/tools/builtin/shell.js +43 -0
- package/dist/tools/builtin/web.d.ts +3 -0
- package/dist/tools/builtin/web.js +37 -0
- package/dist/tools/mcp-client.d.ts +24 -0
- package/dist/tools/mcp-client.js +119 -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/telegram.ts +212 -90
- package/src/channels/websocket.ts +399 -87
- package/src/channels/wechat.ts +329 -149
- package/src/cli.ts +1201 -20
- package/src/core/a2a.ts +60 -0
- package/src/core/agent.ts +420 -152
- package/src/core/runtime.ts +174 -0
- package/src/core/sandbox.ts +143 -0
- package/src/core/scheduler.ts +187 -0
- package/src/core/subagent.ts +98 -0
- package/src/core/workflow-graph.ts +365 -0
- package/src/daemon.ts +96 -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 +65 -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/providers/index.ts +354 -339
- package/src/publish/index.ts +376 -0
- package/src/schema/oad.ts +204 -154
- package/src/security/approval.ts +131 -0
- package/src/security/index.ts +3 -0
- package/src/security/keys.ts +87 -0
- package/src/skills/auto-learn.ts +262 -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/tools/builtin/datetime.ts +41 -0
- package/src/tools/builtin/file.ts +107 -0
- package/src/tools/builtin/index.ts +28 -0
- package/src/tools/builtin/shell.ts +43 -0
- package/src/tools/builtin/web.ts +35 -0
- package/src/tools/mcp-client.ts +131 -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/auto-learn.test.ts +105 -0
- package/tests/builtin-tools.test.ts +83 -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/cli.test.ts +46 -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 +193 -0
- package/tests/telegram-discord.test.ts +60 -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
|
@@ -8,11 +8,8 @@ import { BaseChannel } from './index';
|
|
|
8
8
|
* - URL verification challenge
|
|
9
9
|
* - Message card (interactive) responses
|
|
10
10
|
* - Group chat & P2P messaging
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* FEISHU_APP_ID, FEISHU_APP_SECRET — app credentials
|
|
14
|
-
* FEISHU_VERIFICATION_TOKEN — event subscription verification
|
|
15
|
-
* FEISHU_ENCRYPT_KEY — (optional) event encryption key
|
|
11
|
+
* - Event deduplication
|
|
12
|
+
* - No external dependencies (uses Node.js built-in http/https)
|
|
16
13
|
*/
|
|
17
14
|
export interface FeishuChannelConfig {
|
|
18
15
|
/** Feishu App ID */
|
|
@@ -23,7 +20,7 @@ export interface FeishuChannelConfig {
|
|
|
23
20
|
verificationToken?: string;
|
|
24
21
|
/** Encrypt key (optional, for encrypted events) */
|
|
25
22
|
encryptKey?: string;
|
|
26
|
-
/** Webhook server port (default:
|
|
23
|
+
/** Webhook server port (default: 8081) */
|
|
27
24
|
port?: number;
|
|
28
25
|
/** API base URL (use 'https://open.larksuite.com' for Lark international) */
|
|
29
26
|
apiBase?: string;
|
|
@@ -37,11 +34,29 @@ export declare class FeishuChannel extends BaseChannel {
|
|
|
37
34
|
constructor(config?: FeishuChannelConfig);
|
|
38
35
|
start(): Promise<void>;
|
|
39
36
|
stop(): Promise<void>;
|
|
37
|
+
/** Handle Feishu event */
|
|
38
|
+
private handleEvent;
|
|
39
|
+
/** Parse Feishu event body (exported for testing) */
|
|
40
|
+
static parseEventBody(body: any): {
|
|
41
|
+
type: string;
|
|
42
|
+
challenge?: string;
|
|
43
|
+
eventType?: string;
|
|
44
|
+
content?: string;
|
|
45
|
+
chatId?: string;
|
|
46
|
+
senderId?: string;
|
|
47
|
+
messageId?: string;
|
|
48
|
+
};
|
|
40
49
|
/** Get tenant access token (cached) */
|
|
41
50
|
private getAccessToken;
|
|
42
51
|
/** Send a text message to a chat */
|
|
43
52
|
sendTextMessage(chatId: string, text: string): Promise<void>;
|
|
44
53
|
/** Send an interactive card message */
|
|
45
54
|
sendCardMessage(chatId: string, card: Record<string, unknown>): Promise<void>;
|
|
55
|
+
/** Read request body */
|
|
56
|
+
private readBody;
|
|
57
|
+
/** HTTPS POST */
|
|
58
|
+
private httpsPost;
|
|
59
|
+
/** HTTPS POST with Bearer auth */
|
|
60
|
+
private httpsPostWithAuth;
|
|
46
61
|
}
|
|
47
62
|
//# sourceMappingURL=feishu.d.ts.map
|
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,21 +1,42 @@
|
|
|
1
1
|
import { BaseChannel } from './index';
|
|
2
2
|
/**
|
|
3
|
-
* Telegram channel —
|
|
4
|
-
*
|
|
3
|
+
* Telegram channel — supports both long-polling and webhook modes.
|
|
4
|
+
*
|
|
5
|
+
* Config:
|
|
6
|
+
* token: bot token (or TELEGRAM_BOT_TOKEN env var)
|
|
7
|
+
* mode: 'polling' | 'webhook' (default: 'polling')
|
|
8
|
+
* webhookUrl: required for webhook mode
|
|
9
|
+
* port: webhook server port (default: 3001)
|
|
10
|
+
*
|
|
11
|
+
* Polling mode requires no public URL — ideal for dev/local.
|
|
12
|
+
* Webhook mode is more efficient for production.
|
|
5
13
|
*/
|
|
14
|
+
export interface TelegramChannelConfig {
|
|
15
|
+
token?: string;
|
|
16
|
+
mode?: 'polling' | 'webhook';
|
|
17
|
+
webhookUrl?: string;
|
|
18
|
+
port?: number;
|
|
19
|
+
}
|
|
6
20
|
export declare class TelegramChannel extends BaseChannel {
|
|
7
21
|
readonly type = "telegram";
|
|
8
22
|
private token;
|
|
23
|
+
private mode;
|
|
9
24
|
private webhookUrl?;
|
|
10
|
-
private server;
|
|
11
25
|
private port;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
});
|
|
26
|
+
private offset;
|
|
27
|
+
private polling;
|
|
28
|
+
private server;
|
|
29
|
+
constructor(config?: TelegramChannelConfig);
|
|
17
30
|
start(): Promise<void>;
|
|
18
31
|
stop(): Promise<void>;
|
|
19
|
-
private
|
|
32
|
+
private startPolling;
|
|
33
|
+
private poll;
|
|
34
|
+
private getUpdates;
|
|
35
|
+
private startWebhook;
|
|
36
|
+
private stopWebhook;
|
|
37
|
+
private processUpdate;
|
|
38
|
+
sendMessage(chatId: number | string, text: string): Promise<void>;
|
|
39
|
+
private apiCall;
|
|
40
|
+
private splitText;
|
|
20
41
|
}
|
|
21
42
|
//# sourceMappingURL=telegram.d.ts.map
|