@cicctencent/agent-midway 0.1.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/README.md +280 -0
- package/dist/adapters/express.d.ts +8 -0
- package/dist/adapters/express.js +91 -0
- package/dist/adapters/index.d.ts +5 -0
- package/dist/adapters/index.js +21 -0
- package/dist/adapters/koa.d.ts +3 -0
- package/dist/adapters/koa.js +75 -0
- package/dist/adapters/midway.d.ts +5 -0
- package/dist/adapters/midway.js +11 -0
- package/dist/adapters/next.d.ts +12 -0
- package/dist/adapters/next.js +89 -0
- package/dist/adapters/shared.d.ts +4 -0
- package/dist/adapters/shared.js +31 -0
- package/dist/channel/dingtalk.d.ts +18 -0
- package/dist/channel/dingtalk.js +68 -0
- package/dist/channel/feishu.d.ts +20 -0
- package/dist/channel/feishu.js +96 -0
- package/dist/channel/index.d.ts +46 -0
- package/dist/channel/index.js +311 -0
- package/dist/channel/types.d.ts +77 -0
- package/dist/channel/types.js +7 -0
- package/dist/channel/wecom.d.ts +22 -0
- package/dist/channel/wecom.js +106 -0
- package/dist/component.d.ts +49 -0
- package/dist/component.js +129 -0
- package/dist/connector/calendar-adapter.d.ts +19 -0
- package/dist/connector/calendar-adapter.js +236 -0
- package/dist/connector/db-adapter.d.ts +28 -0
- package/dist/connector/db-adapter.js +193 -0
- package/dist/connector/email-adapter.d.ts +23 -0
- package/dist/connector/email-adapter.js +192 -0
- package/dist/connector/fs-adapter.d.ts +15 -0
- package/dist/connector/fs-adapter.js +199 -0
- package/dist/connector/http-adapter.d.ts +29 -0
- package/dist/connector/http-adapter.js +181 -0
- package/dist/connector/index.d.ts +24 -0
- package/dist/connector/index.js +454 -0
- package/dist/connector/mcp-adapter.d.ts +27 -0
- package/dist/connector/mcp-adapter.js +156 -0
- package/dist/connector/mq-adapter.d.ts +25 -0
- package/dist/connector/mq-adapter.js +181 -0
- package/dist/connector/types.d.ts +205 -0
- package/dist/connector/types.js +9 -0
- package/dist/controller/a2a.controller.d.ts +41 -0
- package/dist/controller/a2a.controller.js +150 -0
- package/dist/controller/agent-profile.controller.d.ts +97 -0
- package/dist/controller/agent-profile.controller.js +200 -0
- package/dist/controller/agent.controller.d.ts +199 -0
- package/dist/controller/agent.controller.js +414 -0
- package/dist/controller/application.controller.d.ts +113 -0
- package/dist/controller/application.controller.js +217 -0
- package/dist/controller/automation.controller.d.ts +113 -0
- package/dist/controller/automation.controller.js +246 -0
- package/dist/controller/channel.controller.d.ts +73 -0
- package/dist/controller/channel.controller.js +183 -0
- package/dist/controller/chat.controller.d.ts +188 -0
- package/dist/controller/chat.controller.js +375 -0
- package/dist/controller/connector.controller.d.ts +134 -0
- package/dist/controller/connector.controller.js +257 -0
- package/dist/controller/knowledge-base.controller.d.ts +157 -0
- package/dist/controller/knowledge-base.controller.js +278 -0
- package/dist/controller/mcp-server.controller.d.ts +115 -0
- package/dist/controller/mcp-server.controller.js +236 -0
- package/dist/controller/model-config.controller.d.ts +139 -0
- package/dist/controller/model-config.controller.js +274 -0
- package/dist/controller/observability.controller.d.ts +124 -0
- package/dist/controller/observability.controller.js +142 -0
- package/dist/controller/security.controller.d.ts +91 -0
- package/dist/controller/security.controller.js +172 -0
- package/dist/controller/settings.controller.d.ts +83 -0
- package/dist/controller/settings.controller.js +280 -0
- package/dist/core/ai-workstation.d.ts +17 -0
- package/dist/core/ai-workstation.js +129 -0
- package/dist/core/index.d.ts +4 -0
- package/dist/core/index.js +20 -0
- package/dist/core/service-container.d.ts +12 -0
- package/dist/core/service-container.js +54 -0
- package/dist/core/sse.d.ts +6 -0
- package/dist/core/sse.js +56 -0
- package/dist/core/types.d.ts +72 -0
- package/dist/core/types.js +2 -0
- package/dist/dto/agent.dto.d.ts +21 -0
- package/dist/dto/agent.dto.js +79 -0
- package/dist/dto/ai-config.dto.d.ts +67 -0
- package/dist/dto/ai-config.dto.js +249 -0
- package/dist/dto/chat.dto.d.ts +40 -0
- package/dist/dto/chat.dto.js +122 -0
- package/dist/index.d.ts +101 -0
- package/dist/index.js +195 -0
- package/dist/memory/db-store.d.ts +33 -0
- package/dist/memory/db-store.js +143 -0
- package/dist/memory/index.d.ts +187 -0
- package/dist/memory/index.js +443 -0
- package/dist/model/ai-agent-profile.entity.d.ts +32 -0
- package/dist/model/ai-agent-profile.entity.js +289 -0
- package/dist/model/ai-application.entity.d.ts +20 -0
- package/dist/model/ai-application.entity.js +166 -0
- package/dist/model/ai-chat-memory.entity.d.ts +16 -0
- package/dist/model/ai-chat-memory.entity.js +123 -0
- package/dist/model/ai-chat-message.entity.d.ts +16 -0
- package/dist/model/ai-chat-message.entity.js +122 -0
- package/dist/model/ai-chat-skill.entity.d.ts +19 -0
- package/dist/model/ai-chat-skill.entity.js +155 -0
- package/dist/model/ai-chat-thread.entity.d.ts +15 -0
- package/dist/model/ai-chat-thread.entity.js +113 -0
- package/dist/model/ai-chat-workspace.entity.d.ts +17 -0
- package/dist/model/ai-chat-workspace.entity.js +136 -0
- package/dist/model/ai-kb-document.entity.d.ts +16 -0
- package/dist/model/ai-kb-document.entity.js +122 -0
- package/dist/model/ai-knowledge-base.entity.d.ts +22 -0
- package/dist/model/ai-knowledge-base.entity.js +185 -0
- package/dist/model/ai-mcp-server.entity.d.ts +23 -0
- package/dist/model/ai-mcp-server.entity.js +198 -0
- package/dist/model/ai-model-config.entity.d.ts +24 -0
- package/dist/model/ai-model-config.entity.js +200 -0
- package/dist/service/a2a.service.d.ts +142 -0
- package/dist/service/a2a.service.js +537 -0
- package/dist/service/agent-profile.service.d.ts +34 -0
- package/dist/service/agent-profile.service.js +110 -0
- package/dist/service/agent-server.service.d.ts +91 -0
- package/dist/service/agent-server.service.js +634 -0
- package/dist/service/agent-task-queue.service.d.ts +98 -0
- package/dist/service/agent-task-queue.service.js +283 -0
- package/dist/service/ai-chat.service.d.ts +103 -0
- package/dist/service/ai-chat.service.js +431 -0
- package/dist/service/ai-skill.service.d.ts +116 -0
- package/dist/service/ai-skill.service.js +457 -0
- package/dist/service/application.service.d.ts +42 -0
- package/dist/service/application.service.js +139 -0
- package/dist/service/automation.service.d.ts +37 -0
- package/dist/service/automation.service.js +196 -0
- package/dist/service/connector.service.d.ts +136 -0
- package/dist/service/connector.service.js +524 -0
- package/dist/service/knowledge-base.service.d.ts +138 -0
- package/dist/service/knowledge-base.service.js +528 -0
- package/dist/service/mcp-server.service.d.ts +39 -0
- package/dist/service/mcp-server.service.js +143 -0
- package/dist/service/model-config.service.d.ts +57 -0
- package/dist/service/model-config.service.js +168 -0
- package/dist/service/observability.service.d.ts +145 -0
- package/dist/service/observability.service.js +281 -0
- package/dist/service/openai.service.d.ts +88 -0
- package/dist/service/openai.service.js +406 -0
- package/dist/service/prompt-builder.service.d.ts +50 -0
- package/dist/service/prompt-builder.service.js +246 -0
- package/dist/tools/code-exec.tool.d.ts +37 -0
- package/dist/tools/code-exec.tool.js +162 -0
- package/dist/tools/datetime.tool.d.ts +21 -0
- package/dist/tools/datetime.tool.js +379 -0
- package/dist/tools/http-request.tool.d.ts +43 -0
- package/dist/tools/http-request.tool.js +455 -0
- package/dist/tools/registry.d.ts +71 -0
- package/dist/tools/registry.js +77 -0
- package/dist/tools/text-process.tool.d.ts +7 -0
- package/dist/tools/text-process.tool.js +366 -0
- package/dist/tools/web-search.tool.d.ts +28 -0
- package/dist/tools/web-search.tool.js +304 -0
- package/dist/types.d.ts +70 -0
- package/dist/types.js +7 -0
- package/package.json +69 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DingTalkAdapter = void 0;
|
|
4
|
+
class DingTalkAdapter {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.type = 'dingtalk';
|
|
7
|
+
this.config = {
|
|
8
|
+
type: 'dingtalk',
|
|
9
|
+
name: '钉钉',
|
|
10
|
+
enabled: false,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
async init(config) {
|
|
14
|
+
this.config = { ...config };
|
|
15
|
+
}
|
|
16
|
+
/** 获取钉钉 access_token */
|
|
17
|
+
async getAccessToken(config) {
|
|
18
|
+
const response = await fetch(`https://oapi.dingtalk.com/gettoken?appkey=${config.appId}&appsecret=${config.appSecret}`);
|
|
19
|
+
const data = (await response.json());
|
|
20
|
+
if (data.errcode !== 0) {
|
|
21
|
+
throw new Error(`Failed to get dingtalk token: ${data.errmsg || 'unknown error'}`);
|
|
22
|
+
}
|
|
23
|
+
return data.access_token;
|
|
24
|
+
}
|
|
25
|
+
async sendMessage(config, receiveId, message) {
|
|
26
|
+
const token = await this.getAccessToken(config);
|
|
27
|
+
const response = await fetch('https://oapi.dingtalk.com/v1.0/robot/oToMessages/batchSend', {
|
|
28
|
+
method: 'POST',
|
|
29
|
+
headers: {
|
|
30
|
+
'Content-Type': 'application/json',
|
|
31
|
+
'x-acs-dingtalk-access-token': token,
|
|
32
|
+
},
|
|
33
|
+
body: JSON.stringify({
|
|
34
|
+
robotCode: config.appId,
|
|
35
|
+
userIds: [receiveId],
|
|
36
|
+
msgKey: 'sampleText',
|
|
37
|
+
msgParam: JSON.stringify({ content: message }),
|
|
38
|
+
}),
|
|
39
|
+
});
|
|
40
|
+
const data = (await response.json());
|
|
41
|
+
return !data.processingInstanceId === undefined ? true : response.ok;
|
|
42
|
+
}
|
|
43
|
+
async receiveMessage(body, _headers) {
|
|
44
|
+
// 钉钉机器人消息回调格式
|
|
45
|
+
const text = body?.text?.content;
|
|
46
|
+
if (!text)
|
|
47
|
+
return null;
|
|
48
|
+
return {
|
|
49
|
+
sessionId: body?.conversationId || body?.conversationId || '',
|
|
50
|
+
message: text.trim(),
|
|
51
|
+
senderId: body?.senderStaffId || body?.senderId || '',
|
|
52
|
+
senderName: body?.senderNick || body?.senderNick || '',
|
|
53
|
+
raw: body,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
getConfig() {
|
|
57
|
+
return { ...this.config };
|
|
58
|
+
}
|
|
59
|
+
verifySignature(body, headers, config) {
|
|
60
|
+
// 钉钉签名验证
|
|
61
|
+
const signature = headers?.['sign'];
|
|
62
|
+
if (!signature || !config.token)
|
|
63
|
+
return true;
|
|
64
|
+
// TODO: 实现完整的签名验证逻辑
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
exports.DingTalkAdapter = DingTalkAdapter;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 飞书渠道适配器
|
|
3
|
+
*
|
|
4
|
+
* 发送消息:调用 https://open.feishu.cn/open-apis/im/v1/messages
|
|
5
|
+
* 接收消息:解析事件回调,支持 URL 验证挑战
|
|
6
|
+
*/
|
|
7
|
+
import type { ChannelAdapter, ChannelConfig, ChannelType, ReceivedMessage } from './types';
|
|
8
|
+
export declare class FeishuAdapter implements ChannelAdapter {
|
|
9
|
+
readonly type: ChannelType;
|
|
10
|
+
private config;
|
|
11
|
+
init(config: ChannelConfig): Promise<void>;
|
|
12
|
+
/** 获取 tenant_access_token */
|
|
13
|
+
private getAccessToken;
|
|
14
|
+
sendMessage(config: ChannelConfig, receiveId: string, message: string): Promise<boolean>;
|
|
15
|
+
receiveMessage(body: any, _headers?: any): Promise<ReceivedMessage | null>;
|
|
16
|
+
getConfig(): ChannelConfig;
|
|
17
|
+
verifySignature(body: any, headers: any, _config: ChannelConfig): boolean;
|
|
18
|
+
/** URL 验证挑战 */
|
|
19
|
+
verifyChallenge(body: any, _config: ChannelConfig): any | null;
|
|
20
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FeishuAdapter = void 0;
|
|
4
|
+
class FeishuAdapter {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.type = 'feishu';
|
|
7
|
+
this.config = {
|
|
8
|
+
type: 'feishu',
|
|
9
|
+
name: '飞书',
|
|
10
|
+
enabled: false,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
async init(config) {
|
|
14
|
+
this.config = { ...config };
|
|
15
|
+
}
|
|
16
|
+
/** 获取 tenant_access_token */
|
|
17
|
+
async getAccessToken(config) {
|
|
18
|
+
const response = await fetch('https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal', {
|
|
19
|
+
method: 'POST',
|
|
20
|
+
headers: { 'Content-Type': 'application/json' },
|
|
21
|
+
body: JSON.stringify({
|
|
22
|
+
app_id: config.appId,
|
|
23
|
+
app_secret: config.appSecret,
|
|
24
|
+
}),
|
|
25
|
+
});
|
|
26
|
+
const data = (await response.json());
|
|
27
|
+
if (!data.tenant_access_token) {
|
|
28
|
+
throw new Error(`Failed to get feishu token: ${data.msg || 'unknown error'}`);
|
|
29
|
+
}
|
|
30
|
+
return data.tenant_access_token;
|
|
31
|
+
}
|
|
32
|
+
async sendMessage(config, receiveId, message) {
|
|
33
|
+
const token = await this.getAccessToken(config);
|
|
34
|
+
const receiveIdType = config.receiveIdType || 'open_id';
|
|
35
|
+
const response = await fetch(`https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=${receiveIdType}`, {
|
|
36
|
+
method: 'POST',
|
|
37
|
+
headers: {
|
|
38
|
+
'Content-Type': 'application/json',
|
|
39
|
+
Authorization: `Bearer ${token}`,
|
|
40
|
+
},
|
|
41
|
+
body: JSON.stringify({
|
|
42
|
+
receive_id: receiveId,
|
|
43
|
+
msg_type: 'text',
|
|
44
|
+
content: JSON.stringify({ text: message }),
|
|
45
|
+
}),
|
|
46
|
+
});
|
|
47
|
+
const data = (await response.json());
|
|
48
|
+
return data.code === 0;
|
|
49
|
+
}
|
|
50
|
+
async receiveMessage(body, _headers) {
|
|
51
|
+
// 飞书事件回调格式
|
|
52
|
+
const event = body?.event || body;
|
|
53
|
+
if (!event)
|
|
54
|
+
return null;
|
|
55
|
+
const message = event?.message;
|
|
56
|
+
const sender = event?.sender;
|
|
57
|
+
if (!message)
|
|
58
|
+
return null;
|
|
59
|
+
// 解析消息内容
|
|
60
|
+
let text = '';
|
|
61
|
+
try {
|
|
62
|
+
const content = JSON.parse(message.content || '{}');
|
|
63
|
+
text = content.text || content.content || '';
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
text = message.content || '';
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
sessionId: message.chat_id || message.thread_id || '',
|
|
70
|
+
message: text,
|
|
71
|
+
senderId: sender?.sender_id?.open_id || '',
|
|
72
|
+
senderName: sender?.sender_id?.name || '',
|
|
73
|
+
raw: body,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
getConfig() {
|
|
77
|
+
return { ...this.config };
|
|
78
|
+
}
|
|
79
|
+
verifySignature(body, headers, _config) {
|
|
80
|
+
// 飞书签名验证(简化版)
|
|
81
|
+
// 实际生产中应使用 EncryptKey 验证 X-Lark-Signature
|
|
82
|
+
const signature = headers?.['x-lark-signature'];
|
|
83
|
+
if (!signature)
|
|
84
|
+
return true; // 未配置签名时放行
|
|
85
|
+
// TODO: 实现完整的签名验证逻辑
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
/** URL 验证挑战 */
|
|
89
|
+
verifyChallenge(body, _config) {
|
|
90
|
+
if (body?.type === 'url_verification' && body?.challenge) {
|
|
91
|
+
return { challenge: body.challenge };
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
exports.FeishuAdapter = FeishuAdapter;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { ChannelAdapter, ChannelConfig, ChannelStatus, ChannelType, ReceivedMessage } from './types';
|
|
2
|
+
import { FeishuAdapter } from './feishu';
|
|
3
|
+
import { DingTalkAdapter } from './dingtalk';
|
|
4
|
+
import { WeComAdapter } from './wecom';
|
|
5
|
+
export declare class ChannelManager {
|
|
6
|
+
private static instance;
|
|
7
|
+
/** 适配器注册表 */
|
|
8
|
+
private adapters;
|
|
9
|
+
/** 渠道配置 */
|
|
10
|
+
private configs;
|
|
11
|
+
/** 渠道状态 */
|
|
12
|
+
private statuses;
|
|
13
|
+
/** 是否已初始化 */
|
|
14
|
+
private initialized;
|
|
15
|
+
private constructor();
|
|
16
|
+
static getInstance(): ChannelManager;
|
|
17
|
+
/** 初始化:注册适配器并加载配置 */
|
|
18
|
+
init(): Promise<void>;
|
|
19
|
+
/** 注册适配器 */
|
|
20
|
+
registerAdapter(adapter: ChannelAdapter): void;
|
|
21
|
+
/** 加载配置文件 */
|
|
22
|
+
private loadConfigs;
|
|
23
|
+
/** 保存配置文件 */
|
|
24
|
+
private saveConfigs;
|
|
25
|
+
/** 获取所有渠道状态 */
|
|
26
|
+
getStatuses(): ChannelStatus[];
|
|
27
|
+
/** 获取渠道配置(脱敏) */
|
|
28
|
+
getConfig(type: ChannelType): ChannelConfig | null;
|
|
29
|
+
/** 更新渠道配置 */
|
|
30
|
+
updateConfig(type: ChannelType, updates: Partial<ChannelConfig>): Promise<ChannelConfig>;
|
|
31
|
+
/** 路由 webhook 请求 */
|
|
32
|
+
routeWebhook(type: ChannelType, body: any, headers?: any): Promise<{
|
|
33
|
+
response: any;
|
|
34
|
+
message: ReceivedMessage | null;
|
|
35
|
+
}>;
|
|
36
|
+
/** 发送消息 */
|
|
37
|
+
sendMessage(type: ChannelType, receiveId: string, message: string): Promise<boolean>;
|
|
38
|
+
/** 脱敏配置(隐藏敏感字段) */
|
|
39
|
+
private maskConfig;
|
|
40
|
+
}
|
|
41
|
+
/** 导出单例 */
|
|
42
|
+
export declare const channelManager: ChannelManager;
|
|
43
|
+
/** 导出适配器类 */
|
|
44
|
+
export { FeishuAdapter, DingTalkAdapter, WeComAdapter };
|
|
45
|
+
/** 导出类型 */
|
|
46
|
+
export type { ChannelAdapter, ChannelConfig, ChannelStatus, ChannelType, ReceivedMessage, } from './types';
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.WeComAdapter = exports.DingTalkAdapter = exports.FeishuAdapter = exports.channelManager = exports.ChannelManager = void 0;
|
|
37
|
+
/**
|
|
38
|
+
* ChannelManager — IM 渠道管理器
|
|
39
|
+
*
|
|
40
|
+
* 单例类,负责:
|
|
41
|
+
* - 注册和管理渠道适配器(飞书、钉钉、企业微信)
|
|
42
|
+
* - 读写渠道配置(data/channels.json)
|
|
43
|
+
* - 路由 webhook 请求到对应适配器
|
|
44
|
+
* - 发送消息
|
|
45
|
+
* - 查询渠道状态
|
|
46
|
+
*/
|
|
47
|
+
const fs = __importStar(require("fs/promises"));
|
|
48
|
+
const path = __importStar(require("path"));
|
|
49
|
+
const feishu_1 = require("./feishu");
|
|
50
|
+
Object.defineProperty(exports, "FeishuAdapter", { enumerable: true, get: function () { return feishu_1.FeishuAdapter; } });
|
|
51
|
+
const dingtalk_1 = require("./dingtalk");
|
|
52
|
+
Object.defineProperty(exports, "DingTalkAdapter", { enumerable: true, get: function () { return dingtalk_1.DingTalkAdapter; } });
|
|
53
|
+
const wecom_1 = require("./wecom");
|
|
54
|
+
Object.defineProperty(exports, "WeComAdapter", { enumerable: true, get: function () { return wecom_1.WeComAdapter; } });
|
|
55
|
+
/** 配置文件路径 */
|
|
56
|
+
const CONFIG_FILE = path.join(process.cwd(), 'data', 'channels.json');
|
|
57
|
+
/** 默认渠道配置 */
|
|
58
|
+
const DEFAULT_CONFIGS = {
|
|
59
|
+
feishu: {
|
|
60
|
+
type: 'feishu',
|
|
61
|
+
name: '飞书',
|
|
62
|
+
enabled: false,
|
|
63
|
+
webhookUrl: '',
|
|
64
|
+
token: '',
|
|
65
|
+
appId: '',
|
|
66
|
+
appSecret: '',
|
|
67
|
+
receiveIdType: 'open_id',
|
|
68
|
+
},
|
|
69
|
+
dingtalk: {
|
|
70
|
+
type: 'dingtalk',
|
|
71
|
+
name: '钉钉',
|
|
72
|
+
enabled: false,
|
|
73
|
+
webhookUrl: '',
|
|
74
|
+
token: '',
|
|
75
|
+
appId: '',
|
|
76
|
+
appSecret: '',
|
|
77
|
+
},
|
|
78
|
+
wecom: {
|
|
79
|
+
type: 'wecom',
|
|
80
|
+
name: '企业微信',
|
|
81
|
+
enabled: false,
|
|
82
|
+
webhookUrl: '',
|
|
83
|
+
token: '',
|
|
84
|
+
appId: '',
|
|
85
|
+
appSecret: '',
|
|
86
|
+
wecomAgentId: '',
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
class ChannelManager {
|
|
90
|
+
constructor() {
|
|
91
|
+
/** 适配器注册表 */
|
|
92
|
+
this.adapters = new Map();
|
|
93
|
+
/** 渠道配置 */
|
|
94
|
+
this.configs = {};
|
|
95
|
+
/** 渠道状态 */
|
|
96
|
+
this.statuses = new Map();
|
|
97
|
+
/** 是否已初始化 */
|
|
98
|
+
this.initialized = false;
|
|
99
|
+
}
|
|
100
|
+
static getInstance() {
|
|
101
|
+
if (!ChannelManager.instance) {
|
|
102
|
+
ChannelManager.instance = new ChannelManager();
|
|
103
|
+
}
|
|
104
|
+
return ChannelManager.instance;
|
|
105
|
+
}
|
|
106
|
+
/** 初始化:注册适配器并加载配置 */
|
|
107
|
+
async init() {
|
|
108
|
+
if (this.initialized)
|
|
109
|
+
return;
|
|
110
|
+
// 注册适配器
|
|
111
|
+
this.registerAdapter(new feishu_1.FeishuAdapter());
|
|
112
|
+
this.registerAdapter(new dingtalk_1.DingTalkAdapter());
|
|
113
|
+
this.registerAdapter(new wecom_1.WeComAdapter());
|
|
114
|
+
// 加载配置
|
|
115
|
+
await this.loadConfigs();
|
|
116
|
+
// 初始化已启用的适配器
|
|
117
|
+
for (const [type, adapter] of this.adapters) {
|
|
118
|
+
const config = this.configs[type];
|
|
119
|
+
if (config && config.enabled) {
|
|
120
|
+
try {
|
|
121
|
+
await adapter.init(config);
|
|
122
|
+
this.statuses.set(type, {
|
|
123
|
+
type,
|
|
124
|
+
name: config.name,
|
|
125
|
+
enabled: true,
|
|
126
|
+
initialized: true,
|
|
127
|
+
lastActiveAt: Date.now(),
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
this.statuses.set(type, {
|
|
132
|
+
type,
|
|
133
|
+
name: config.name,
|
|
134
|
+
enabled: true,
|
|
135
|
+
initialized: false,
|
|
136
|
+
error: error.message,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
this.statuses.set(type, {
|
|
142
|
+
type,
|
|
143
|
+
name: config?.name || type,
|
|
144
|
+
enabled: false,
|
|
145
|
+
initialized: false,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
this.initialized = true;
|
|
150
|
+
}
|
|
151
|
+
/** 注册适配器 */
|
|
152
|
+
registerAdapter(adapter) {
|
|
153
|
+
this.adapters.set(adapter.type, adapter);
|
|
154
|
+
}
|
|
155
|
+
/** 加载配置文件 */
|
|
156
|
+
async loadConfigs() {
|
|
157
|
+
try {
|
|
158
|
+
const content = await fs.readFile(CONFIG_FILE, 'utf-8');
|
|
159
|
+
this.configs = JSON.parse(content);
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
// 文件不存在时使用默认配置
|
|
163
|
+
this.configs = { ...DEFAULT_CONFIGS };
|
|
164
|
+
await this.saveConfigs();
|
|
165
|
+
}
|
|
166
|
+
// 确保所有渠道类型都有配置
|
|
167
|
+
for (const type of this.adapters.keys()) {
|
|
168
|
+
if (!this.configs[type]) {
|
|
169
|
+
this.configs[type] = { ...DEFAULT_CONFIGS[type] };
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/** 保存配置文件 */
|
|
174
|
+
async saveConfigs() {
|
|
175
|
+
const dir = path.dirname(CONFIG_FILE);
|
|
176
|
+
await fs.mkdir(dir, { recursive: true });
|
|
177
|
+
await fs.writeFile(CONFIG_FILE, JSON.stringify(this.configs, null, 2), 'utf-8');
|
|
178
|
+
}
|
|
179
|
+
/** 获取所有渠道状态 */
|
|
180
|
+
getStatuses() {
|
|
181
|
+
const result = [];
|
|
182
|
+
for (const [, status] of this.statuses) {
|
|
183
|
+
result.push(status);
|
|
184
|
+
}
|
|
185
|
+
return result;
|
|
186
|
+
}
|
|
187
|
+
/** 获取渠道配置(脱敏) */
|
|
188
|
+
getConfig(type) {
|
|
189
|
+
const config = this.configs[type];
|
|
190
|
+
if (!config)
|
|
191
|
+
return null;
|
|
192
|
+
return this.maskConfig(config);
|
|
193
|
+
}
|
|
194
|
+
/** 更新渠道配置 */
|
|
195
|
+
async updateConfig(type, updates) {
|
|
196
|
+
const current = this.configs[type] || DEFAULT_CONFIGS[type];
|
|
197
|
+
const newConfig = {
|
|
198
|
+
...current,
|
|
199
|
+
...updates,
|
|
200
|
+
type, // 确保 type 不被覆盖
|
|
201
|
+
};
|
|
202
|
+
this.configs[type] = newConfig;
|
|
203
|
+
await this.saveConfigs();
|
|
204
|
+
// 如果启用,重新初始化适配器
|
|
205
|
+
if (newConfig.enabled) {
|
|
206
|
+
const adapter = this.adapters.get(type);
|
|
207
|
+
if (adapter) {
|
|
208
|
+
try {
|
|
209
|
+
await adapter.init(newConfig);
|
|
210
|
+
this.statuses.set(type, {
|
|
211
|
+
type,
|
|
212
|
+
name: newConfig.name,
|
|
213
|
+
enabled: true,
|
|
214
|
+
initialized: true,
|
|
215
|
+
lastActiveAt: Date.now(),
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
this.statuses.set(type, {
|
|
220
|
+
type,
|
|
221
|
+
name: newConfig.name,
|
|
222
|
+
enabled: true,
|
|
223
|
+
initialized: false,
|
|
224
|
+
error: error.message,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
this.statuses.set(type, {
|
|
231
|
+
type,
|
|
232
|
+
name: newConfig.name,
|
|
233
|
+
enabled: false,
|
|
234
|
+
initialized: false,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
return this.maskConfig(newConfig);
|
|
238
|
+
}
|
|
239
|
+
/** 路由 webhook 请求 */
|
|
240
|
+
async routeWebhook(type, body, headers) {
|
|
241
|
+
const adapter = this.adapters.get(type);
|
|
242
|
+
const config = this.configs[type];
|
|
243
|
+
if (!adapter || !config) {
|
|
244
|
+
return {
|
|
245
|
+
response: { error: 'Channel not found' },
|
|
246
|
+
message: null,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
// 验证签名
|
|
250
|
+
if (!adapter.verifySignature(body, headers, config)) {
|
|
251
|
+
return {
|
|
252
|
+
response: { error: 'Signature verification failed' },
|
|
253
|
+
message: null,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
// 检查验证挑战(飞书 URL 验证等)
|
|
257
|
+
if (adapter.verifyChallenge) {
|
|
258
|
+
const challenge = adapter.verifyChallenge(body, config);
|
|
259
|
+
if (challenge !== null) {
|
|
260
|
+
return { response: challenge, message: null };
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
// 解析消息
|
|
264
|
+
const message = await adapter.receiveMessage(body, headers);
|
|
265
|
+
// 更新状态
|
|
266
|
+
const status = this.statuses.get(type);
|
|
267
|
+
if (status) {
|
|
268
|
+
status.lastActiveAt = Date.now();
|
|
269
|
+
}
|
|
270
|
+
// 返回成功响应(IM 平台要求快速响应)
|
|
271
|
+
return {
|
|
272
|
+
response: { success: true },
|
|
273
|
+
message,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
/** 发送消息 */
|
|
277
|
+
async sendMessage(type, receiveId, message) {
|
|
278
|
+
const adapter = this.adapters.get(type);
|
|
279
|
+
const config = this.configs[type];
|
|
280
|
+
if (!adapter || !config || !config.enabled) {
|
|
281
|
+
throw new Error(`Channel ${type} is not available or not enabled`);
|
|
282
|
+
}
|
|
283
|
+
const success = await adapter.sendMessage(config, receiveId, message);
|
|
284
|
+
// 更新状态
|
|
285
|
+
const status = this.statuses.get(type);
|
|
286
|
+
if (status) {
|
|
287
|
+
status.lastActiveAt = Date.now();
|
|
288
|
+
if (!success) {
|
|
289
|
+
status.error = 'Failed to send message';
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
status.error = undefined;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return success;
|
|
296
|
+
}
|
|
297
|
+
/** 脱敏配置(隐藏敏感字段) */
|
|
298
|
+
maskConfig(config) {
|
|
299
|
+
const masked = { ...config };
|
|
300
|
+
if (masked.appSecret) {
|
|
301
|
+
masked.appSecret = '***';
|
|
302
|
+
}
|
|
303
|
+
if (masked.token) {
|
|
304
|
+
masked.token = '***';
|
|
305
|
+
}
|
|
306
|
+
return masked;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
exports.ChannelManager = ChannelManager;
|
|
310
|
+
/** 导出单例 */
|
|
311
|
+
exports.channelManager = ChannelManager.getInstance();
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IM 渠道类型定义
|
|
3
|
+
*
|
|
4
|
+
* 定义渠道适配器接口和通用类型,支持飞书、钉钉、企业微信等 IM 平台。
|
|
5
|
+
*/
|
|
6
|
+
/** 渠道类型 */
|
|
7
|
+
export type ChannelType = 'feishu' | 'dingtalk' | 'wecom';
|
|
8
|
+
/** 渠道配置 */
|
|
9
|
+
export interface ChannelConfig {
|
|
10
|
+
/** 渠道类型 */
|
|
11
|
+
type: ChannelType;
|
|
12
|
+
/** 渠道名称 */
|
|
13
|
+
name: string;
|
|
14
|
+
/** 是否启用 */
|
|
15
|
+
enabled: boolean;
|
|
16
|
+
/** Webhook URL(接收消息的端点) */
|
|
17
|
+
webhookUrl?: string;
|
|
18
|
+
/** 验证 Token */
|
|
19
|
+
token?: string;
|
|
20
|
+
/** 应用 ID */
|
|
21
|
+
appId?: string;
|
|
22
|
+
/** 应用密钥 */
|
|
23
|
+
appSecret?: string;
|
|
24
|
+
/** 接收者 ID 类型 */
|
|
25
|
+
receiveIdType?: string;
|
|
26
|
+
/** 飞书 Agent ID */
|
|
27
|
+
agentId?: string;
|
|
28
|
+
/** 企业微信 Agent ID */
|
|
29
|
+
wecomAgentId?: string;
|
|
30
|
+
/** 额外配置 */
|
|
31
|
+
extra?: Record<string, any>;
|
|
32
|
+
}
|
|
33
|
+
/** 解析后的消息 */
|
|
34
|
+
export interface ReceivedMessage {
|
|
35
|
+
/** 会话 ID(用于关联上下文) */
|
|
36
|
+
sessionId: string;
|
|
37
|
+
/** 消息文本内容 */
|
|
38
|
+
message: string;
|
|
39
|
+
/** 发送者 ID */
|
|
40
|
+
senderId: string;
|
|
41
|
+
/** 发送者名称 */
|
|
42
|
+
senderName: string;
|
|
43
|
+
/** 原始消息体 */
|
|
44
|
+
raw: any;
|
|
45
|
+
}
|
|
46
|
+
/** 渠道状态 */
|
|
47
|
+
export interface ChannelStatus {
|
|
48
|
+
/** 渠道类型 */
|
|
49
|
+
type: ChannelType;
|
|
50
|
+
/** 渠道名称 */
|
|
51
|
+
name: string;
|
|
52
|
+
/** 是否启用 */
|
|
53
|
+
enabled: boolean;
|
|
54
|
+
/** 是否已初始化 */
|
|
55
|
+
initialized: boolean;
|
|
56
|
+
/** 最后活跃时间 */
|
|
57
|
+
lastActiveAt?: number;
|
|
58
|
+
/** 错误信息 */
|
|
59
|
+
error?: string;
|
|
60
|
+
}
|
|
61
|
+
/** 渠道适配器接口 */
|
|
62
|
+
export interface ChannelAdapter {
|
|
63
|
+
/** 渠道类型 */
|
|
64
|
+
readonly type: ChannelType;
|
|
65
|
+
/** 初始化适配器 */
|
|
66
|
+
init(config: ChannelConfig): Promise<void>;
|
|
67
|
+
/** 发送消息 */
|
|
68
|
+
sendMessage(config: ChannelConfig, receiveId: string, message: string): Promise<boolean>;
|
|
69
|
+
/** 接收并解析消息 */
|
|
70
|
+
receiveMessage(body: any, headers?: any): Promise<ReceivedMessage | null>;
|
|
71
|
+
/** 获取配置(脱敏后) */
|
|
72
|
+
getConfig(): ChannelConfig;
|
|
73
|
+
/** 验证签名 */
|
|
74
|
+
verifySignature(body: any, headers: any, config: ChannelConfig): boolean;
|
|
75
|
+
/** Webhook 验证挑战(飞书 URL 验证等) */
|
|
76
|
+
verifyChallenge?(body: any, config: ChannelConfig): any | null;
|
|
77
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 企业微信渠道适配器
|
|
3
|
+
*
|
|
4
|
+
* 发送消息:调用 https://qyapi.weixin.qq.com/cgi-bin/message/send
|
|
5
|
+
* 接收消息:支持 JSON 和 XML 格式
|
|
6
|
+
*/
|
|
7
|
+
import type { ChannelAdapter, ChannelConfig, ChannelType, ReceivedMessage } from './types';
|
|
8
|
+
export declare class WeComAdapter implements ChannelAdapter {
|
|
9
|
+
readonly type: ChannelType;
|
|
10
|
+
private config;
|
|
11
|
+
init(config: ChannelConfig): Promise<void>;
|
|
12
|
+
/** 获取企业微信 access_token */
|
|
13
|
+
private getAccessToken;
|
|
14
|
+
sendMessage(config: ChannelConfig, receiveId: string, message: string): Promise<boolean>;
|
|
15
|
+
receiveMessage(body: any, headers?: any): Promise<ReceivedMessage | null>;
|
|
16
|
+
getConfig(): ChannelConfig;
|
|
17
|
+
verifySignature(body: any, headers: any, config: ChannelConfig): boolean;
|
|
18
|
+
/** URL 验证挑战 */
|
|
19
|
+
verifyChallenge(body: any, _config: ChannelConfig): any | null;
|
|
20
|
+
/** 简单 XML 解析(企业微信回调) */
|
|
21
|
+
private parseXml;
|
|
22
|
+
}
|