@chen0825/aiapp-ability 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +39 -0
  2. package/ali-aiapp-ability-0.1.0.tgz +0 -0
  3. package/coverage/clover.xml +226 -0
  4. package/coverage/coverage-final.json +12 -0
  5. package/coverage/lcov-report/abilities/dingtalk/get-access-token.ts.html +244 -0
  6. package/coverage/lcov-report/abilities/dingtalk/get-user-info.ts.html +253 -0
  7. package/coverage/lcov-report/abilities/dingtalk/index.html +146 -0
  8. package/coverage/lcov-report/abilities/dingtalk/send-message.ts.html +259 -0
  9. package/coverage/lcov-report/abilities/index.html +116 -0
  10. package/coverage/lcov-report/abilities/registry.ts.html +484 -0
  11. package/coverage/lcov-report/base.css +224 -0
  12. package/coverage/lcov-report/block-navigation.js +87 -0
  13. package/coverage/lcov-report/favicon.png +0 -0
  14. package/coverage/lcov-report/index.html +176 -0
  15. package/coverage/lcov-report/prettify.css +1 -0
  16. package/coverage/lcov-report/prettify.js +2 -0
  17. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  18. package/coverage/lcov-report/sorter.js +210 -0
  19. package/coverage/lcov-report/src/abilities/dingtalk/get-access-token.ts.html +244 -0
  20. package/coverage/lcov-report/src/abilities/dingtalk/get-user-info.ts.html +253 -0
  21. package/coverage/lcov-report/src/abilities/dingtalk/index.html +161 -0
  22. package/coverage/lcov-report/src/abilities/dingtalk/index.ts.html +106 -0
  23. package/coverage/lcov-report/src/abilities/dingtalk/send-message.ts.html +259 -0
  24. package/coverage/lcov-report/src/abilities/index.html +131 -0
  25. package/coverage/lcov-report/src/abilities/index.ts.html +103 -0
  26. package/coverage/lcov-report/src/abilities/registry.ts.html +484 -0
  27. package/coverage/lcov-report/src/core/environment.ts.html +241 -0
  28. package/coverage/lcov-report/src/core/http-client.ts.html +430 -0
  29. package/coverage/lcov-report/src/core/index.html +146 -0
  30. package/coverage/lcov-report/src/core/index.ts.html +103 -0
  31. package/coverage/lcov-report/src/index.html +116 -0
  32. package/coverage/lcov-report/src/index.ts.html +268 -0
  33. package/coverage/lcov-report/src/utils/index.html +116 -0
  34. package/coverage/lcov-report/src/utils/index.ts.html +385 -0
  35. package/coverage/lcov-report/utils/index.html +116 -0
  36. package/coverage/lcov-report/utils/index.ts.html +385 -0
  37. package/coverage/lcov.info +397 -0
  38. package/package.json +40 -0
  39. package/src/abilities/dingtalk/index.ts +311 -0
  40. package/src/abilities/index.ts +6 -0
  41. package/src/abilities/registry.ts +134 -0
  42. package/src/core/environment.ts +79 -0
  43. package/src/core/http-client.ts +88 -0
  44. package/src/core/index.ts +6 -0
  45. package/src/index.ts +17 -0
  46. package/src/types/dingtalk.ts +91 -0
  47. package/src/types/index.ts +25 -0
  48. package/src/utils/index.ts +0 -0
  49. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,311 @@
1
+ /**
2
+ * 钉钉能力模块
3
+ */
4
+
5
+ import { HttpClient } from '../../core/http-client';
6
+ import { getDingTalkEnvConfig } from '../../core/environment';
7
+ import {
8
+ BatchSendRobotMsgRequest,
9
+ BatchSendResponse,
10
+ RobotMsgKey,
11
+ GetAccessTokenResponse,
12
+ } from '../../types/dingtalk';
13
+ import { AbilityProvider } from '../../types';
14
+
15
+ /** 钉钉API批量发送限制:每次最多20人 */
16
+ const BATCH_SEND_LIMIT = 20;
17
+
18
+ /**
19
+ * 简化的 Markdown 通知参数
20
+ */
21
+ export interface MarkdownNotificationParams {
22
+ /** 接收者用户 ID 列表 */
23
+ userIds: string[];
24
+ /** 消息标题 */
25
+ title: string;
26
+ /** Markdown 格式的消息内容 */
27
+ text: string;
28
+ }
29
+
30
+ /**
31
+ * 通用批量发送参数
32
+ */
33
+ export interface BatchNotificationParams<T extends RobotMsgKey = RobotMsgKey> {
34
+ /** 接收者用户 ID 列表 */
35
+ userIds: string[];
36
+ /** 消息类型 */
37
+ msgKey: T;
38
+ /** 消息参数 */
39
+ msgParam: Record<string, unknown>;
40
+ }
41
+
42
+ /**
43
+ * 钉钉能力提供者
44
+ *
45
+ * 必须配置以下环境变量:
46
+ * - DINGTALK_APP_KEY: 钉钉应用 AppKey
47
+ * - DINGTALK_APP_SECRET: 钉钉应用 AppSecret
48
+ * - DINGTALK_ROBOT_CODE: 钉钉机器人编码
49
+ * - DINGTALK_API_URL: 钉钉 API 基础 URL
50
+ */
51
+ export class DingTalkProvider implements AbilityProvider {
52
+ public readonly name = 'dingtalk';
53
+ public readonly version = '1.0.0';
54
+
55
+ private dingtalkApiClient: HttpClient;
56
+
57
+ constructor() {
58
+ const envConfig = getDingTalkEnvConfig();
59
+ this.dingtalkApiClient = new HttpClient(envConfig.apiUrl);
60
+ }
61
+
62
+ /**
63
+ * 获取 appKey
64
+ */
65
+ private getAppKey(): string | undefined {
66
+ return getDingTalkEnvConfig().appKey;
67
+ }
68
+
69
+ /**
70
+ * 获取 appSecret
71
+ */
72
+ private getAppSecret(): string | undefined {
73
+ return getDingTalkEnvConfig().appSecret;
74
+ }
75
+
76
+ /**
77
+ * 获取机器人编码
78
+ */
79
+ private getRobotCode(): string | undefined {
80
+ return getDingTalkEnvConfig().robotCode;
81
+ }
82
+
83
+ /**
84
+ * 获取 API 基础 URL
85
+ */
86
+ private getApiUrl(): string {
87
+ return getDingTalkEnvConfig().apiUrl;
88
+ }
89
+
90
+ /**
91
+ * 获取企业内部应用的 accessToken(每次调用都重新获取)
92
+ */
93
+ async getAccessToken(): Promise<{ success: boolean; accessToken?: string; message?: string }> {
94
+ const appKey = this.getAppKey();
95
+ const appSecret = this.getAppSecret();
96
+
97
+ if (!appKey || !appSecret) {
98
+ return {
99
+ success: false,
100
+ message: '未配置环境变量 DINGTALK_APP_KEY 或 DINGTALK_APP_SECRET',
101
+ };
102
+ }
103
+
104
+ try {
105
+ const response = await fetch(`${this.getApiUrl()}/v1.0/oauth2/accessToken`, {
106
+ method: 'POST',
107
+ headers: {
108
+ 'Content-Type': 'application/json',
109
+ },
110
+ body: JSON.stringify({ appKey, appSecret }),
111
+ });
112
+
113
+ if (!response.ok) {
114
+ const errorText = await response.text();
115
+ console.error('获取 accessToken 失败:', errorText);
116
+ return {
117
+ success: false,
118
+ message: `获取 accessToken 失败: ${response.status}`,
119
+ };
120
+ }
121
+
122
+ const data: GetAccessTokenResponse = await response.json();
123
+
124
+ if (!data.accessToken) {
125
+ return {
126
+ success: false,
127
+ message: '获取 accessToken 失败: 响应中无 accessToken',
128
+ };
129
+ }
130
+
131
+ return {
132
+ success: true,
133
+ accessToken: data.accessToken,
134
+ };
135
+ } catch (error) {
136
+ console.error('获取 accessToken 异常:', error);
137
+ return {
138
+ success: false,
139
+ message: error instanceof Error ? error.message : '获取 accessToken 异常',
140
+ };
141
+ }
142
+ }
143
+
144
+ /**
145
+ * 批量发送机器人消息
146
+ */
147
+ async batchSendRobotMessage<T extends RobotMsgKey>(
148
+ params: BatchSendRobotMsgRequest<T>,
149
+ ): Promise<BatchSendResponse> {
150
+ const tokenResult = await this.getAccessToken();
151
+ if (!tokenResult.success || !tokenResult.accessToken) {
152
+ return {
153
+ success: false,
154
+ message: tokenResult.message || '获取 accessToken 失败',
155
+ };
156
+ }
157
+
158
+ if (!params.userIds || params.userIds.length === 0) {
159
+ return {
160
+ success: false,
161
+ message: '接收者列表不能为空',
162
+ };
163
+ }
164
+
165
+ if (params.userIds.length > BATCH_SEND_LIMIT) {
166
+ return this.batchSendInChunks(params);
167
+ }
168
+
169
+ try {
170
+ const result = await this.dingtalkApiClient.post<any>(
171
+ '/v1.0/robot/oToMessages/batchSend',
172
+ params,
173
+ {
174
+ headers: {
175
+ 'x-acs-dingtalk-access-token': tokenResult.accessToken,
176
+ },
177
+ },
178
+ );
179
+
180
+ if (!result.success) {
181
+ return {
182
+ success: false,
183
+ message: result.message || '批量发送机器人消息失败',
184
+ data: result.data,
185
+ };
186
+ }
187
+
188
+ return {
189
+ success: true,
190
+ message: '批量发送成功',
191
+ data: result.data,
192
+ successCount: params.userIds.length,
193
+ failCount: 0,
194
+ };
195
+ } catch (error) {
196
+ console.error('批量发送机器人消息异常:', error);
197
+ return {
198
+ success: false,
199
+ message: error instanceof Error ? error.message : '批量发送机器人消息异常',
200
+ failedUserIds: params.userIds,
201
+ };
202
+ }
203
+ }
204
+
205
+ /**
206
+ * 分批发送机器人消息(超过20人时自动分批)
207
+ */
208
+ private async batchSendInChunks<T extends RobotMsgKey>(
209
+ params: BatchSendRobotMsgRequest<T>,
210
+ ): Promise<BatchSendResponse> {
211
+ const { userIds, ...restParams } = params;
212
+ const chunks = this.chunkArray(userIds, BATCH_SEND_LIMIT);
213
+ const failedUserIds: string[] = [];
214
+ let successCount = 0;
215
+ let failCount = 0;
216
+
217
+ for (const chunk of chunks) {
218
+ const chunkParams: BatchSendRobotMsgRequest<T> = {
219
+ ...restParams,
220
+ userIds: chunk,
221
+ } as BatchSendRobotMsgRequest<T>;
222
+
223
+ const result = await this.batchSendRobotMessage(chunkParams);
224
+
225
+ if (result.success) {
226
+ successCount += chunk.length;
227
+ } else {
228
+ failCount += chunk.length;
229
+ failedUserIds.push(...chunk);
230
+ }
231
+ }
232
+
233
+ const allSuccess = failCount === 0;
234
+ return {
235
+ success: allSuccess,
236
+ message: allSuccess
237
+ ? `批量发送成功,共 ${successCount} 人`
238
+ : `部分发送失败,成功 ${successCount} 人,失败 ${failCount} 人`,
239
+ successCount,
240
+ failCount,
241
+ failedUserIds: failedUserIds.length > 0 ? failedUserIds : undefined,
242
+ };
243
+ }
244
+
245
+ /**
246
+ * 发送 Markdown 消息
247
+ */
248
+ async sendMarkdownMessage(params: MarkdownNotificationParams): Promise<BatchSendResponse> {
249
+ const robotCode = this.getRobotCode();
250
+ if (!robotCode) {
251
+ return {
252
+ success: false,
253
+ message: '未配置环境变量 DINGTALK_ROBOT_CODE',
254
+ };
255
+ }
256
+
257
+ return this.batchSendRobotMessage({
258
+ robotCode,
259
+ userIds: params.userIds,
260
+ msgKey: 'sampleMarkdown',
261
+ msgParam: JSON.stringify({
262
+ title: params.title,
263
+ text: params.text,
264
+ }),
265
+ });
266
+ }
267
+
268
+ /**
269
+ * 通用批量发送接口
270
+ */
271
+ async sendBatchNotification<T extends RobotMsgKey>(
272
+ params: BatchNotificationParams<T>,
273
+ ): Promise<BatchSendResponse> {
274
+ const robotCode = this.getRobotCode();
275
+ if (!robotCode) {
276
+ return {
277
+ success: false,
278
+ message: '未配置环境变量 DINGTALK_ROBOT_CODE',
279
+ };
280
+ }
281
+
282
+ return this.batchSendRobotMessage({
283
+ robotCode,
284
+ userIds: params.userIds,
285
+ msgKey: params.msgKey,
286
+ msgParam: JSON.stringify(params.msgParam),
287
+ });
288
+ }
289
+
290
+ /**
291
+ * 初始化(重新加载环境变量配置)
292
+ */
293
+ async initialize(): Promise<void> {
294
+ const envConfig = getDingTalkEnvConfig();
295
+ this.dingtalkApiClient = new HttpClient(envConfig.apiUrl);
296
+ }
297
+
298
+ /**
299
+ * 将数组分割成指定大小的块
300
+ */
301
+ private chunkArray<T>(array: T[], size: number): T[][] {
302
+ const chunks: T[][] = [];
303
+ for (let i = 0; i < array.length; i += size) {
304
+ chunks.push(array.slice(i, i + size));
305
+ }
306
+ return chunks;
307
+ }
308
+ }
309
+
310
+ /** 默认钉钉提供者实例 */
311
+ export const dingtalkProvider = new DingTalkProvider();
@@ -0,0 +1,6 @@
1
+ /**
2
+ * 能力模块统一导出
3
+ */
4
+
5
+ export * from './dingtalk';
6
+ export * from './registry';
@@ -0,0 +1,134 @@
1
+ /**
2
+ * 能力注册表模块
3
+ */
4
+
5
+ import { AbilityProvider } from '../types';
6
+
7
+ /**
8
+ * 能力注册表配置
9
+ */
10
+ export interface AbilityRegistryConfig {
11
+ /** 是否启用日志 */
12
+ enableLogging?: boolean;
13
+ /** 默认超时时间 */
14
+ defaultTimeout?: number;
15
+ }
16
+
17
+ /**
18
+ * 能力注册表
19
+ */
20
+ export class AbilityRegistry {
21
+ private providers: Map<string, AbilityProvider> = new Map();
22
+ private config: AbilityRegistryConfig;
23
+
24
+ constructor(config: AbilityRegistryConfig = {}) {
25
+ this.config = {
26
+ enableLogging: false,
27
+ defaultTimeout: 30000,
28
+ ...config,
29
+ };
30
+ }
31
+
32
+ /**
33
+ * 注册能力提供者
34
+ * @param provider 能力提供者
35
+ */
36
+ register(provider: AbilityProvider): void {
37
+ if (this.providers.has(provider.name)) {
38
+ throw new Error(`Provider "${provider.name}" is already registered`);
39
+ }
40
+ this.providers.set(provider.name, provider);
41
+ this.log(`Registered provider: ${provider.name}`);
42
+ }
43
+
44
+ /**
45
+ * 批量注册能力提供者
46
+ * @param providers 能力提供者数组
47
+ */
48
+ registerAll(providers: AbilityProvider[]): void {
49
+ for (const provider of providers) {
50
+ this.register(provider);
51
+ }
52
+ }
53
+
54
+ /**
55
+ * 获取能力提供者
56
+ * @param name 提供者名称
57
+ */
58
+ get<T extends AbilityProvider = AbilityProvider>(name: string): T | undefined {
59
+ return this.providers.get(name) as T | undefined;
60
+ }
61
+
62
+ /**
63
+ * 检查能力提供者是否已注册
64
+ * @param name 提供者名称
65
+ */
66
+ has(name: string): boolean {
67
+ return this.providers.has(name);
68
+ }
69
+
70
+ /**
71
+ * 获取所有已注册的提供者名称
72
+ */
73
+ getNames(): string[] {
74
+ return Array.from(this.providers.keys());
75
+ }
76
+
77
+ /**
78
+ * 获取所有已注册的提供者
79
+ */
80
+ getAll(): AbilityProvider[] {
81
+ return Array.from(this.providers.values());
82
+ }
83
+
84
+ /**
85
+ * 注销能力提供者
86
+ * @param name 提供者名称
87
+ */
88
+ unregister(name: string): boolean {
89
+ const result = this.providers.delete(name);
90
+ if (result) {
91
+ this.log(`Unregistered provider: ${name}`);
92
+ }
93
+ return result;
94
+ }
95
+
96
+ /**
97
+ * 清空所有能力提供者
98
+ */
99
+ clear(): void {
100
+ this.providers.clear();
101
+ this.log('Cleared all providers');
102
+ }
103
+
104
+ /**
105
+ * 初始化所有已注册的提供者
106
+ */
107
+ async initializeAll(): Promise<void> {
108
+ for (const provider of this.providers.values()) {
109
+ if (provider.initialize) {
110
+ await provider.initialize();
111
+ }
112
+ }
113
+ }
114
+
115
+ /**
116
+ * 销毁所有已注册的提供者
117
+ */
118
+ async destroyAll(): Promise<void> {
119
+ for (const provider of this.providers.values()) {
120
+ if (provider.destroy) {
121
+ await provider.destroy();
122
+ }
123
+ }
124
+ }
125
+
126
+ private log(message: string): void {
127
+ if (this.config.enableLogging) {
128
+ console.log(`[AbilityRegistry] ${message}`);
129
+ }
130
+ }
131
+ }
132
+
133
+ /** 默认能力注册表实例 */
134
+ export const defaultRegistry = new AbilityRegistry();
@@ -0,0 +1,79 @@
1
+ /**
2
+ * 环境配置模块
3
+ */
4
+ export interface EnvironmentConfig {
5
+ /** API 基础 URL */
6
+ apiUrl: string;
7
+ /** 当前环境 */
8
+ env: 'development' | 'production';
9
+ /** 应用 ID */
10
+ appId?: string;
11
+ /** 用户 ID */
12
+ userId?: string;
13
+ /** 租户 ID */
14
+ tenantId?: string;
15
+ }
16
+
17
+ /**
18
+ * 钉钉环境配置
19
+ */
20
+ export interface DingTalkEnvConfig {
21
+ /** 钉钉应用 AppKey */
22
+ appKey?: string;
23
+ /** 钉钉应用 AppSecret */
24
+ appSecret?: string;
25
+ /** 钉钉机器人编码 */
26
+ robotCode?: string;
27
+ /** 钉钉 API 基础 URL */
28
+ apiUrl?: string;
29
+ }
30
+
31
+ /**
32
+ * 默认环境配置
33
+ */
34
+ const defaultConfig: EnvironmentConfig = {
35
+ apiUrl: process.env.API_URL || '',
36
+ env: (process.env.NODE_ENV as 'development' | 'production') || 'development',
37
+ appId: process.env.APP_ID,
38
+ userId: process.env.USER_ID,
39
+ tenantId: process.env.TENANT_ID,
40
+ };
41
+
42
+ /** 当前环境配置 */
43
+ let currentConfig: EnvironmentConfig = { ...defaultConfig };
44
+
45
+ /**
46
+ * 获取环境配置
47
+ * @returns 环境配置对象
48
+ */
49
+ export function getEnvironmentConfig(): EnvironmentConfig {
50
+ return { ...currentConfig };
51
+ }
52
+
53
+ /**
54
+ * 设置环境配置
55
+ * @param config 部分配置
56
+ */
57
+ export function setEnvironmentConfig(config: Partial<EnvironmentConfig>): void {
58
+ currentConfig = { ...currentConfig, ...config };
59
+ }
60
+
61
+ /**
62
+ * 重置环境配置为默认值
63
+ */
64
+ export function resetEnvironmentConfig(): void {
65
+ currentConfig = { ...defaultConfig };
66
+ }
67
+
68
+ /**
69
+ * 获取钉钉环境配置(从环境变量)
70
+ * @returns 钉钉环境配置对象
71
+ */
72
+ export function getDingTalkEnvConfig(): DingTalkEnvConfig {
73
+ return {
74
+ appKey: process.env.DINGTALK_APP_KEY,
75
+ appSecret: process.env.DINGTALK_APP_SECRET,
76
+ robotCode: process.env.DINGTALK_ROBOT_CODE,
77
+ apiUrl: process.env.DINGTALK_API_URL,
78
+ };
79
+ }
@@ -0,0 +1,88 @@
1
+ import { BaseResponse } from "../types";
2
+
3
+ /**
4
+ * HTTP 请求客户端
5
+ */
6
+ export class HttpClient {
7
+ private baseUrl: string;
8
+
9
+ constructor(baseUrl: string) {
10
+ this.baseUrl = baseUrl;
11
+ }
12
+
13
+ /**
14
+ * 发送 POST 请求
15
+ */
16
+ async post<T = any>(
17
+ endpoint: string,
18
+ data: any,
19
+ options?: RequestInit,
20
+ ): Promise<BaseResponse<T>> {
21
+ try {
22
+ const response = await fetch(`${this.baseUrl}${endpoint}`, {
23
+ method: "POST",
24
+ headers: {
25
+ "Content-Type": "application/json",
26
+ ...options?.headers,
27
+ },
28
+ body: JSON.stringify(data),
29
+ ...options,
30
+ });
31
+
32
+ if (!response.ok) {
33
+ return {
34
+ success: false,
35
+ message: "请求异常",
36
+ data: undefined,
37
+ };
38
+ }
39
+
40
+ const result = await response.json();
41
+ return result;
42
+ } catch (error) {
43
+ console.error("HTTP请求失败:", error);
44
+ return {
45
+ success: false,
46
+ message: error instanceof Error ? error.message : "请求失败",
47
+ data: undefined,
48
+ };
49
+ }
50
+ }
51
+
52
+ /**
53
+ * 发送 GET 请求
54
+ */
55
+ async get<T = any>(
56
+ endpoint: string,
57
+ options?: RequestInit,
58
+ ): Promise<BaseResponse<T>> {
59
+ try {
60
+ const response = await fetch(`${this.baseUrl}${endpoint}`, {
61
+ method: "GET",
62
+ headers: {
63
+ "Content-Type": "application/json",
64
+ ...options?.headers,
65
+ },
66
+ ...options,
67
+ });
68
+
69
+ if (!response.ok) {
70
+ return {
71
+ success: false,
72
+ message: "请求异常",
73
+ data: undefined,
74
+ };
75
+ }
76
+
77
+ const result = await response.json();
78
+ return result;
79
+ } catch (error) {
80
+ console.error("HTTP请求失败:", error);
81
+ return {
82
+ success: false,
83
+ message: error instanceof Error ? error.message : "请求失败",
84
+ data: undefined,
85
+ };
86
+ }
87
+ }
88
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * 核心模块统一导出
3
+ */
4
+
5
+ export * from './http-client';
6
+ export * from './environment';
package/src/index.ts ADDED
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @ali/aiapp-ability
3
+ * AI应用能力包
4
+ */
5
+
6
+ // 导出类型
7
+ export * from './types';
8
+
9
+ // 导出核心模块
10
+ export * from './core';
11
+
12
+ // 导出能力模块
13
+ export * from './abilities';
14
+
15
+ export default function main() {
16
+ return 'Write your own legend...';
17
+ }