@yanhaidao/wecom 2.3.150 → 2.3.180

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 (43) hide show
  1. package/README.md +238 -385
  2. package/SKILLS_CAL.md +895 -0
  3. package/SKILLS_DOC.md +2136 -0
  4. package/changelog/v2.3.16.md +11 -0
  5. package/changelog/v2.3.18.md +22 -0
  6. package/index.ts +39 -3
  7. package/package.json +2 -3
  8. package/src/agent/handler.event-filter.test.ts +11 -0
  9. package/src/agent/handler.ts +732 -643
  10. package/src/app/account-runtime.ts +46 -20
  11. package/src/app/index.ts +19 -1
  12. package/src/capability/calendar/SKILLS_CHECKLIST.md +251 -0
  13. package/src/capability/calendar/client.ts +815 -0
  14. package/src/capability/calendar/index.ts +3 -0
  15. package/src/capability/calendar/schema.ts +417 -0
  16. package/src/capability/calendar/tool.ts +417 -0
  17. package/src/capability/calendar/types.ts +309 -0
  18. package/src/capability/doc/client.ts +567 -62
  19. package/src/capability/doc/schema.ts +419 -318
  20. package/src/capability/doc/tool.ts +1510 -1178
  21. package/src/capability/doc/types.ts +130 -14
  22. package/src/capability/mcp/index.ts +10 -0
  23. package/src/capability/mcp/schema.ts +107 -0
  24. package/src/capability/mcp/tool.ts +170 -0
  25. package/src/capability/mcp/transport.ts +394 -0
  26. package/src/channel.ts +70 -28
  27. package/src/config/schema.ts +71 -102
  28. package/src/outbound.test.ts +91 -14
  29. package/src/outbound.ts +143 -30
  30. package/src/runtime/reply-orchestrator.test.ts +35 -2
  31. package/src/runtime/reply-orchestrator.ts +14 -2
  32. package/src/runtime/session-manager.ts +20 -6
  33. package/src/runtime/source-registry.ts +165 -0
  34. package/src/target.ts +7 -4
  35. package/src/transport/bot-ws/inbound.test.ts +46 -0
  36. package/src/transport/bot-ws/inbound.ts +23 -5
  37. package/src/transport/bot-ws/media.ts +269 -0
  38. package/src/transport/bot-ws/reply.test.ts +85 -17
  39. package/src/transport/bot-ws/reply.ts +109 -21
  40. package/src/transport/bot-ws/sdk-adapter.test.ts +64 -1
  41. package/src/transport/bot-ws/sdk-adapter.ts +88 -12
  42. package/.claude/settings.local.json +0 -11
  43. package/docs/update-content-fix.md +0 -135
@@ -0,0 +1,815 @@
1
+ // ============================================================================
2
+ // Calendar Client - Complete Implementation
3
+ // 严格遵循企业微信官方 API 文档:https://developer.work.weixin.qq.com/document/path/93329
4
+ // ============================================================================
5
+ import type { ResolvedAgentAccount } from "../../types/index.js";
6
+ import { getAccessToken } from "../../transport/agent-api/core.js";
7
+ import { wecomFetch } from "../../http.js";
8
+ import { resolveWecomEgressProxyUrlFromNetwork } from "../../config/index.js";
9
+ import { LIMITS } from "../../types/constants.js";
10
+ import type {
11
+ CreateCalendarRequest, CreateCalendarResponse, UpdateCalendarRequest, UpdateCalendarResponse,
12
+ GetCalendarRequest, GetCalendarResponse, DeleteCalendarResponse,
13
+ CreateScheduleRequest, CreateScheduleResponse, UpdateScheduleRequest, UpdateScheduleResponse,
14
+ AddScheduleAttendeesRequest, AddScheduleAttendeesResponse,
15
+ DeleteScheduleAttendeesRequest, DeleteScheduleAttendeesResponse,
16
+ GetScheduleByCalendarRequest, GetScheduleByCalendarResponse,
17
+ GetScheduleRequest, GetScheduleResponse, DeleteScheduleRequest, DeleteScheduleResponse,
18
+ GetSystemCalendarIdRequest, GetSystemCalendarIdResponse,
19
+ CreateSystemScheduleRequest, RespondScheduleRequest, RespondScheduleResponse,
20
+ SyncScheduleRequest, SyncScheduleResponse, CalendarInfo, ScheduleInfo,
21
+ ScheduleReminders,
22
+ } from "./types.js";
23
+
24
+ // ============================================================================
25
+ // Constants - 官方 API 限定值
26
+ // ============================================================================
27
+
28
+ const CALENDAR_LIMITS = {
29
+ ADMINS_MAX: 3,
30
+ SHARES_MAX: 2000,
31
+ PUBLIC_USERS_MAX: 1000,
32
+ PUBLIC_PARTIES_MAX: 100,
33
+ SUMMARY_MIN_LENGTH: 1,
34
+ SUMMARY_MAX_LENGTH: 128,
35
+ DESCRIPTION_MAX_LENGTH: 512,
36
+ COLOR_PATTERN: /^#?[0-9A-Fa-f]{6}$/,
37
+ SCHEDULE_ATTENDEES_MAX: 1000,
38
+ SCHEDULE_DESCRIPTION_MAX_LENGTH: 1000,
39
+ SCHEDULE_LOCATION_MAX_LENGTH: 128,
40
+ SCHEDULE_SUMMARY_MAX_LENGTH: 128,
41
+ REMINDER_BEFORE_EVENT_VALUES: [0, 300, 900, 3600, 86400],
42
+ REMINDER_TIME_DIFFS_VALUES: [0, -300, -900, -3600, -86400, 32400, -172800, -604800],
43
+ REPEAT_TYPE_VALUES: [0, 1, 2, 5, 7],
44
+ TIMEZONE_MIN: -12,
45
+ TIMEZONE_MAX: 12,
46
+ CAL_ID_LIST_MAX: 1000,
47
+ SCHEDULE_ID_LIST_MAX: 1000,
48
+ GET_SCHEDULE_LIMIT_MIN: 1,
49
+ GET_SCHEDULE_LIMIT_MAX: 1000,
50
+ OP_MODE_VALUES: [0, 1, 2],
51
+ PERMISSION_VALUES: [1, 3],
52
+ RESPONSE_STATUS_VALUES: [1, 2, 4],
53
+ } as const;
54
+
55
+ // ============================================================================
56
+ // Validation Functions
57
+ // ============================================================================
58
+
59
+ function validateString(value: unknown, fieldName: string, opts?: { min?: number; max?: number; pattern?: RegExp; allowEmpty?: boolean }): string {
60
+ const str = String(value ?? "");
61
+ if (!opts?.allowEmpty && !str.trim()) {
62
+ throw new Error(`${fieldName} 不能为空`);
63
+ }
64
+ if (opts?.min !== undefined && str.length < opts.min) {
65
+ throw new Error(`${fieldName} 长度不能少于 ${opts.min} 字符`);
66
+ }
67
+ if (opts?.max !== undefined && str.length > opts.max) {
68
+ throw new Error(`${fieldName} 长度不能超过 ${opts.max} 字符`);
69
+ }
70
+ if (opts?.pattern && !opts.pattern.test(str)) {
71
+ throw new Error(`${fieldName} 格式不正确`);
72
+ }
73
+ return str;
74
+ }
75
+
76
+ function validateNumber(value: unknown, fieldName: string, opts?: { min?: number; max?: number; required?: boolean }): number {
77
+ if (value === undefined || value === null) {
78
+ if (opts?.required) throw new Error(`${fieldName} 是必填项`);
79
+ return 0;
80
+ }
81
+ const num = Number(value);
82
+ if (isNaN(num)) throw new Error(`${fieldName} 必须是数字`);
83
+ if (opts?.min !== undefined && num < opts.min) throw new Error(`${fieldName} 不能小于 ${opts.min}`);
84
+ if (opts?.max !== undefined && num > opts.max) throw new Error(`${fieldName} 不能大于 ${opts.max}`);
85
+ return num;
86
+ }
87
+
88
+ function validateArray<T>(value: unknown, fieldName: string, opts?: { min?: number; max?: number; required?: boolean }): any[] {
89
+ if (!Array.isArray(value)) {
90
+ if (opts?.required) throw new Error(`${fieldName} 必须是数组`);
91
+ return [];
92
+ }
93
+ if (opts?.min !== undefined && value.length < opts.min) {
94
+ throw new Error(`${fieldName} 至少需要 ${opts.min} 项`);
95
+ }
96
+ if (opts?.max !== undefined && value.length > opts.max) {
97
+ throw new Error(`${fieldName} 不能超过 ${opts.max} 项`);
98
+ }
99
+ return value;
100
+ }
101
+
102
+ function validateCalendarAdmins(admins?: string[]): string[] | undefined {
103
+ if (!admins) return undefined;
104
+ validateArray(admins, "admins", { max: CALENDAR_LIMITS.ADMINS_MAX });
105
+ return admins.map((id, i) => validateString(id, `admins[${i}]`, { allowEmpty: false }));
106
+ }
107
+
108
+ function validateCalendarShares(shares?: Array<{ userid: string; permission?: 1 | 3 }>): Array<{ userid: string; permission?: 1 | 3 }> | undefined {
109
+ if (!shares) return undefined;
110
+ validateArray(shares, "shares", { max: CALENDAR_LIMITS.SHARES_MAX });
111
+ return shares.map((s, i) => {
112
+ const userid = validateString(s.userid, `shares[${i}].userid`, { allowEmpty: false });
113
+ if (s.permission !== undefined && !CALENDAR_LIMITS.PERMISSION_VALUES.includes(s.permission)) {
114
+ throw new Error(`shares[${i}].permission 必须是 1 或 3`);
115
+ }
116
+ return { userid, permission: s.permission };
117
+ });
118
+ }
119
+
120
+ function validatePublicRange(publicRange?: { userids?: string[]; partyids?: number[] }): { userids?: string[]; partyids?: number[] } | undefined {
121
+ if (!publicRange) return undefined;
122
+ const result: { userids?: string[]; partyids?: number[] } = {};
123
+ if (publicRange.userids) {
124
+ validateArray(publicRange.userids, "public_range.userids", { max: CALENDAR_LIMITS.PUBLIC_USERS_MAX });
125
+ result.userids = publicRange.userids.map((id, i) => validateString(id, `public_range.userids[${i}]`, { allowEmpty: false }));
126
+ }
127
+ if (publicRange.partyids) {
128
+ validateArray(publicRange.partyids, "public_range.partyids", { max: CALENDAR_LIMITS.PUBLIC_PARTIES_MAX });
129
+ result.partyids = publicRange.partyids.map((id, i) => validateNumber(id, `public_range.partyids[${i}]`, { min: 1, required: true }));
130
+ }
131
+ return result;
132
+ }
133
+
134
+ function validateScheduleAttendees(attendees?: Array<{ userid: string }>): Array<{ userid: string }> | undefined {
135
+ if (!attendees) return undefined;
136
+ validateArray(attendees, "attendees", { max: CALENDAR_LIMITS.SCHEDULE_ATTENDEES_MAX });
137
+ return attendees.map((a, i) => ({
138
+ userid: validateString(a.userid, `attendees[${i}].userid`, { allowEmpty: false, max: 64 })
139
+ }));
140
+ }
141
+
142
+ function validateReminders(reminders?: any): any | undefined {
143
+ if (!reminders) return undefined;
144
+ const result: any = {};
145
+
146
+ if (reminders.is_remind !== undefined) {
147
+ if (![0, 1].includes(reminders.is_remind)) throw new Error("reminders.is_remind 必须是 0 或 1");
148
+ result.is_remind = reminders.is_remind;
149
+ }
150
+
151
+ if (reminders.is_repeat !== undefined) {
152
+ if (![0, 1].includes(reminders.is_repeat)) throw new Error("reminders.is_repeat 必须是 0 或 1");
153
+ result.is_repeat = reminders.is_repeat;
154
+ }
155
+
156
+ if (reminders.remind_before_event_secs !== undefined) {
157
+ const val = reminders.remind_before_event_secs;
158
+ if (!CALENDAR_LIMITS.REMINDER_BEFORE_EVENT_VALUES.includes(val)) {
159
+ throw new Error(`reminders.remind_before_event_secs 必须是 ${CALENDAR_LIMITS.REMINDER_BEFORE_EVENT_VALUES.join(",")}`);
160
+ }
161
+ result.remind_before_event_secs = val;
162
+ }
163
+
164
+ if (reminders.remind_time_diffs !== undefined) {
165
+ validateArray(reminders.remind_time_diffs, "reminders.remind_time_diffs");
166
+ result.remind_time_diffs = reminders.remind_time_diffs.map((v: number, i: number) => {
167
+ if (!CALENDAR_LIMITS.REMINDER_TIME_DIFFS_VALUES.includes(v as any)) {
168
+ throw new Error(`reminders.remind_time_diffs[${i}] 必须是 ${CALENDAR_LIMITS.REMINDER_TIME_DIFFS_VALUES.join(",")}`);
169
+ }
170
+ return v as any;
171
+ });
172
+ }
173
+
174
+ if (reminders.repeat_type !== undefined) {
175
+ if (!CALENDAR_LIMITS.REPEAT_TYPE_VALUES.includes(reminders.repeat_type)) {
176
+ throw new Error(`reminders.repeat_type 必须是 ${CALENDAR_LIMITS.REPEAT_TYPE_VALUES.join(",")}`);
177
+ }
178
+ result.repeat_type = reminders.repeat_type;
179
+ }
180
+
181
+ if (reminders.repeat_until !== undefined) {
182
+ result.repeat_until = validateNumber(reminders.repeat_until, "reminders.repeat_until", { min: 0 });
183
+ }
184
+
185
+ if (reminders.is_custom_repeat !== undefined) {
186
+ if (![0, 1].includes(reminders.is_custom_repeat)) {
187
+ throw new Error("reminders.is_custom_repeat 必须是 0 或 1");
188
+ }
189
+ result.is_custom_repeat = reminders.is_custom_repeat;
190
+ }
191
+
192
+ if (reminders.repeat_interval !== undefined) {
193
+ result.repeat_interval = validateNumber(reminders.repeat_interval, "reminders.repeat_interval", { min: 1 });
194
+ }
195
+
196
+ if (reminders.repeat_day_of_week !== undefined) {
197
+ validateArray(reminders.repeat_day_of_week, "reminders.repeat_day_of_week");
198
+ result.repeat_day_of_week = reminders.repeat_day_of_week.map((v: number, i: number) => {
199
+ if (v < 1 || v > 7) throw new Error(`reminders.repeat_day_of_week[${i}] 必须是 1-7`);
200
+ return v;
201
+ });
202
+ }
203
+
204
+ if (reminders.repeat_day_of_month !== undefined) {
205
+ validateArray(reminders.repeat_day_of_month, "reminders.repeat_day_of_month");
206
+ result.repeat_day_of_month = reminders.repeat_day_of_month.map((v: number, i: number) => {
207
+ if (v < 1 || v > 31) throw new Error(`reminders.repeat_day_of_month[${i}] 必须是 1-31`);
208
+ return v;
209
+ });
210
+ }
211
+
212
+ if (reminders.timezone !== undefined) {
213
+ result.timezone = validateNumber(reminders.timezone, "reminders.timezone", {
214
+ min: CALENDAR_LIMITS.TIMEZONE_MIN,
215
+ max: CALENDAR_LIMITS.TIMEZONE_MAX
216
+ });
217
+ }
218
+
219
+ if (reminders.exclude_time_list !== undefined) {
220
+ validateArray(reminders.exclude_time_list, "reminders.exclude_time_list");
221
+ result.exclude_time_list = reminders.exclude_time_list.map((item: any, i: number) => ({
222
+ start_time: validateNumber(item.start_time, `reminders.exclude_time_list[${i}].start_time`, { min: 0, required: true })
223
+ }));
224
+ }
225
+
226
+ return result;
227
+ }
228
+
229
+ function validateOpMode(opMode?: number): 0 | 1 | 2 | undefined {
230
+ if (opMode === undefined) return undefined;
231
+ if (!CALENDAR_LIMITS.OP_MODE_VALUES.includes(opMode as any)) {
232
+ throw new Error(`op_mode 必须是 ${CALENDAR_LIMITS.OP_MODE_VALUES.join(",")}`);
233
+ }
234
+ return opMode as 0 | 1 | 2;
235
+ }
236
+
237
+ // ============================================================================
238
+ // Helper Functions
239
+ // ============================================================================
240
+
241
+ async function parseResponse<T>(res: Response, label: string): Promise<T> {
242
+ let json: any;
243
+ try {
244
+ json = await res.json();
245
+ } catch {
246
+ throw new Error(`${label}: 无效的 JSON 响应`);
247
+ }
248
+
249
+ if (!json || typeof json !== "object") {
250
+ throw new Error(`${label}: 空响应`);
251
+ }
252
+
253
+ if (Array.isArray(json)) {
254
+ const failed = json.find((i: any) => Number(i?.errcode ?? 0) !== 0);
255
+ if (failed) throw new Error(`${label}: ${failed?.errmsg || "failed"} (${failed?.errcode})`);
256
+ return json as T;
257
+ }
258
+
259
+ const errCode = Number(json.errcode ?? 0);
260
+ if (errCode !== 0) {
261
+ throw new Error(`${label}: ${json.errmsg || "failed"} (${errCode})`);
262
+ }
263
+
264
+ return json as T;
265
+ }
266
+
267
+ function normalizeColor(color: string): string {
268
+ const trimmed = color.trim();
269
+ return trimmed.startsWith("#") ? trimmed : `#${trimmed}`;
270
+ }
271
+
272
+ // ============================================================================
273
+ // WecomCalendarClient Class
274
+ // ============================================================================
275
+
276
+ export class WecomCalendarClient {
277
+ private async post<T>(path: string, label: string, agent: ResolvedAgentAccount, body: any): Promise<T> {
278
+ if (!agent?.corpId || !agent?.corpSecret) {
279
+ throw new Error(`${label}: 账号配置不完整,需要 corpId 和 corpSecret`);
280
+ }
281
+
282
+ const token = await getAccessToken(agent);
283
+ const url = `https://qyapi.weixin.qq.com${path}?access_token=${encodeURIComponent(token)}`;
284
+ const proxyUrl = resolveWecomEgressProxyUrlFromNetwork(agent.network);
285
+
286
+ let lastError: Error | undefined;
287
+ for (let attempt = 1; attempt <= 3; attempt++) {
288
+ try {
289
+ const res = await wecomFetch(
290
+ url,
291
+ {
292
+ method: "POST",
293
+ headers: { "content-type": "application/json" },
294
+ body: JSON.stringify(body || {}),
295
+ },
296
+ { proxyUrl, timeoutMs: LIMITS.REQUEST_TIMEOUT_MS }
297
+ );
298
+ return await parseResponse<T>(res, label);
299
+ } catch (e) {
300
+ lastError = e instanceof Error ? e : new Error(String(e));
301
+ if (attempt < 3) {
302
+ await new Promise(r => setTimeout(r, 1000 * attempt));
303
+ }
304
+ }
305
+ }
306
+ throw lastError || new Error(`${label}: 请求失败`);
307
+ }
308
+
309
+ // ========================================================================
310
+ // Calendar APIs
311
+ // ========================================================================
312
+
313
+ /**
314
+ * 创建日历
315
+ * POST /cgi-bin/oa/calendar/add
316
+ */
317
+ async createCalendar(p: { agent: ResolvedAgentAccount; request: CreateCalendarRequest }): Promise<{ raw: CreateCalendarResponse; calId: string }> {
318
+ const calendar = p.request.calendar;
319
+
320
+ // 验证必填字段
321
+ const summary = validateString(calendar.summary, "calendar.summary", {
322
+ min: CALENDAR_LIMITS.SUMMARY_MIN_LENGTH,
323
+ max: CALENDAR_LIMITS.SUMMARY_MAX_LENGTH
324
+ });
325
+ const color = validateString(calendar.color, "calendar.color", { pattern: CALENDAR_LIMITS.COLOR_PATTERN });
326
+
327
+ // 验证可选字段
328
+ const description = calendar.description !== undefined
329
+ ? validateString(calendar.description, "calendar.description", { max: CALENDAR_LIMITS.DESCRIPTION_MAX_LENGTH, allowEmpty: true })
330
+ : undefined;
331
+
332
+ const admins = validateCalendarAdmins(calendar.admins);
333
+ const shares = validateCalendarShares(calendar.shares);
334
+ const publicRange = validatePublicRange(calendar.public_range);
335
+
336
+ if (calendar.set_as_default !== undefined && ![0, 1].includes(calendar.set_as_default)) {
337
+ throw new Error("calendar.set_as_default 必须是 0 或 1");
338
+ }
339
+ if (calendar.is_public !== undefined && ![0, 1].includes(calendar.is_public)) {
340
+ throw new Error("calendar.is_public 必须是 0 或 1");
341
+ }
342
+ if (calendar.is_corp_calendar !== undefined && ![0, 1].includes(calendar.is_corp_calendar)) {
343
+ throw new Error("calendar.is_corp_calendar 必须是 0 或 1");
344
+ }
345
+
346
+ // 全员日历必须是公共日历且必须指定 public_range
347
+ if (calendar.is_corp_calendar === 1) {
348
+ if (calendar.is_public !== 1) {
349
+ throw new Error("全员日历必须设置 is_public=1");
350
+ }
351
+ if (!publicRange || (!publicRange.userids && !publicRange.partyids)) {
352
+ throw new Error("全员日历必须指定 public_range");
353
+ }
354
+ }
355
+
356
+ const request: CreateCalendarRequest = {
357
+ calendar: {
358
+ summary,
359
+ color: normalizeColor(color),
360
+ description,
361
+ admins,
362
+ set_as_default: calendar.set_as_default,
363
+ shares,
364
+ is_public: calendar.is_public,
365
+ public_range: publicRange,
366
+ is_corp_calendar: calendar.is_corp_calendar,
367
+ },
368
+ };
369
+
370
+ if (p.request.agentid !== undefined) {
371
+ request.agentid = p.request.agentid;
372
+ }
373
+
374
+ const json = await this.post<CreateCalendarResponse>("/cgi-bin/oa/calendar/add", "create_calendar", p.agent, request);
375
+ return { raw: json, calId: json.cal_id };
376
+ }
377
+
378
+ /**
379
+ * 更新日历
380
+ * POST /cgi-bin/oa/calendar/update
381
+ * 注意:更新操作是覆盖式,不是增量式
382
+ */
383
+ async updateCalendar(p: { agent: ResolvedAgentAccount; request: UpdateCalendarRequest }): Promise<{ raw: UpdateCalendarResponse; calId: string }> {
384
+ const calendar = p.request.calendar;
385
+
386
+ const calId = validateString(calendar.cal_id, "calendar.cal_id", { allowEmpty: false });
387
+ const summary = validateString(calendar.summary, "calendar.summary", {
388
+ min: CALENDAR_LIMITS.SUMMARY_MIN_LENGTH,
389
+ max: CALENDAR_LIMITS.SUMMARY_MAX_LENGTH
390
+ });
391
+ const color = validateString(calendar.color, "calendar.color", { pattern: CALENDAR_LIMITS.COLOR_PATTERN });
392
+ const description = calendar.description !== undefined
393
+ ? validateString(calendar.description, "calendar.description", { max: CALENDAR_LIMITS.DESCRIPTION_MAX_LENGTH, allowEmpty: true })
394
+ : undefined;
395
+
396
+ const admins = validateCalendarAdmins(calendar.admins);
397
+ const shares = validateCalendarShares(calendar.shares);
398
+ const publicRange = validatePublicRange(calendar.public_range);
399
+
400
+ if (p.request.skip_public_range !== undefined && ![0, 1].includes(p.request.skip_public_range)) {
401
+ throw new Error("skip_public_range 必须是 0 或 1");
402
+ }
403
+
404
+ const request: UpdateCalendarRequest = {
405
+ calendar: {
406
+ cal_id: calId,
407
+ summary,
408
+ color: normalizeColor(color),
409
+ description,
410
+ admins,
411
+ shares,
412
+ public_range: publicRange,
413
+ },
414
+ skip_public_range: p.request.skip_public_range,
415
+ };
416
+
417
+ const json = await this.post<UpdateCalendarResponse>("/cgi-bin/oa/calendar/update", "update_calendar", p.agent, request);
418
+ return { raw: json, calId: calId };
419
+ }
420
+
421
+ /**
422
+ * 获取日历详情
423
+ * POST /cgi-bin/oa/calendar/get
424
+ */
425
+ async getCalendar(p: { agent: ResolvedAgentAccount; request: GetCalendarRequest }): Promise<{ raw: GetCalendarResponse; calendarList: CalendarInfo[] }> {
426
+ const calIdList = validateArray(p.request.cal_id_list, "cal_id_list", {
427
+ min: 1,
428
+ max: CALENDAR_LIMITS.CAL_ID_LIST_MAX,
429
+ required: true
430
+ }).map((id, i) => validateString(id, `cal_id_list[${i}]`, { allowEmpty: false }));
431
+
432
+ const request: GetCalendarRequest = { cal_id_list: calIdList };
433
+ const json = await this.post<GetCalendarResponse>("/cgi-bin/oa/calendar/get", "get_calendar", p.agent, request);
434
+ return { raw: json, calendarList: json.calendar_list || [] };
435
+ }
436
+
437
+ /**
438
+ * 删除日历
439
+ * POST /cgi-bin/oa/calendar/del
440
+ */
441
+ async deleteCalendar(p: { agent: ResolvedAgentAccount; calId: string }): Promise<{ raw: DeleteCalendarResponse; calId: string }> {
442
+ const calId = validateString(p.calId, "calId", { allowEmpty: false });
443
+ const json = await this.post<DeleteCalendarResponse>("/cgi-bin/oa/calendar/del", "delete_calendar", p.agent, { cal_id: calId });
444
+ return { raw: json, calId: calId };
445
+ }
446
+
447
+ // ========================================================================
448
+ // Schedule APIs
449
+ // ========================================================================
450
+
451
+ /**
452
+ * 创建日程
453
+ * POST /cgi-bin/oa/schedule/add
454
+ */
455
+ async createSchedule(p: { agent: ResolvedAgentAccount; request: CreateScheduleRequest }): Promise<{ raw: CreateScheduleResponse; scheduleId: string }> {
456
+ const schedule = p.request.schedule;
457
+
458
+ const startTime = validateNumber(schedule.start_time, "schedule.start_time", { min: 0, required: true });
459
+ const endTime = validateNumber(schedule.end_time, "schedule.end_time", { min: 0, required: true });
460
+
461
+ if (endTime <= startTime) {
462
+ throw new Error("schedule.end_time 必须大于 schedule.start_time");
463
+ }
464
+
465
+ if (schedule.is_whole_day !== undefined && ![0, 1].includes(schedule.is_whole_day)) {
466
+ throw new Error("schedule.is_whole_day 必须是 0 或 1");
467
+ }
468
+
469
+ const summary = schedule.summary !== undefined
470
+ ? validateString(schedule.summary, "schedule.summary", { max: CALENDAR_LIMITS.SCHEDULE_SUMMARY_MAX_LENGTH, allowEmpty: true })
471
+ : undefined;
472
+
473
+ const description = schedule.description !== undefined
474
+ ? validateString(schedule.description, "schedule.description", { max: CALENDAR_LIMITS.SCHEDULE_DESCRIPTION_MAX_LENGTH, allowEmpty: true })
475
+ : undefined;
476
+
477
+ const location = schedule.location !== undefined
478
+ ? validateString(schedule.location, "schedule.location", { max: CALENDAR_LIMITS.SCHEDULE_LOCATION_MAX_LENGTH, allowEmpty: true })
479
+ : undefined;
480
+
481
+ const admins = validateCalendarAdmins(schedule.admins);
482
+ const attendees = validateScheduleAttendees(schedule.attendees);
483
+ const reminders = validateReminders(schedule.reminders);
484
+
485
+ const calId = schedule.cal_id !== undefined
486
+ ? validateString(schedule.cal_id, "schedule.cal_id", { max: 64, allowEmpty: true })
487
+ : undefined;
488
+
489
+ const request: CreateScheduleRequest = {
490
+ schedule: {
491
+ start_time: startTime,
492
+ end_time: endTime,
493
+ is_whole_day: schedule.is_whole_day,
494
+ summary,
495
+ description,
496
+ location,
497
+ admins,
498
+ attendees,
499
+ reminders,
500
+ cal_id: calId,
501
+ },
502
+ };
503
+
504
+ if (p.request.agentid !== undefined) {
505
+ request.agentid = p.request.agentid;
506
+ }
507
+
508
+ const json = await this.post<CreateScheduleResponse>("/cgi-bin/oa/schedule/add", "create_schedule", p.agent, request);
509
+ return { raw: json, scheduleId: json.schedule_id };
510
+ }
511
+
512
+ /**
513
+ * 更新日程
514
+ * POST /cgi-bin/oa/schedule/update
515
+ * 注意:更新操作是覆盖式,不是增量式
516
+ */
517
+ async updateSchedule(p: { agent: ResolvedAgentAccount; request: UpdateScheduleRequest }): Promise<{ raw: UpdateScheduleResponse; scheduleId: string }> {
518
+ const schedule = p.request.schedule;
519
+
520
+ const scheduleId = validateString(schedule.schedule_id, "schedule.schedule_id", { allowEmpty: false });
521
+ const startTime = validateNumber(schedule.start_time, "schedule.start_time", { min: 0, required: true });
522
+ const endTime = validateNumber(schedule.end_time, "schedule.end_time", { min: 0, required: true });
523
+
524
+ if (endTime <= startTime) {
525
+ throw new Error("schedule.end_time 必须大于 schedule.start_time");
526
+ }
527
+
528
+ if (schedule.is_whole_day !== undefined && ![0, 1].includes(schedule.is_whole_day)) {
529
+ throw new Error("schedule.is_whole_day 必须是 0 或 1");
530
+ }
531
+
532
+ const summary = schedule.summary !== undefined
533
+ ? validateString(schedule.summary, "schedule.summary", { max: CALENDAR_LIMITS.SCHEDULE_SUMMARY_MAX_LENGTH, allowEmpty: true })
534
+ : undefined;
535
+
536
+ const description = schedule.description !== undefined
537
+ ? validateString(schedule.description, "schedule.description", { max: CALENDAR_LIMITS.SCHEDULE_DESCRIPTION_MAX_LENGTH, allowEmpty: true })
538
+ : undefined;
539
+
540
+ const location = schedule.location !== undefined
541
+ ? validateString(schedule.location, "schedule.location", { max: CALENDAR_LIMITS.SCHEDULE_LOCATION_MAX_LENGTH, allowEmpty: true })
542
+ : undefined;
543
+
544
+ const admins = validateCalendarAdmins(schedule.admins);
545
+ const attendees = validateScheduleAttendees(schedule.attendees);
546
+ const reminders = validateReminders(schedule.reminders);
547
+
548
+ if (p.request.skip_attendees !== undefined && ![0, 1].includes(p.request.skip_attendees)) {
549
+ throw new Error("skip_attendees 必须是 0 或 1");
550
+ }
551
+
552
+ const opMode = validateOpMode(p.request.op_mode);
553
+ const opStartTime = p.request.op_start_time !== undefined
554
+ ? validateNumber(p.request.op_start_time, "op_start_time", { min: 0 })
555
+ : undefined;
556
+
557
+ const request: UpdateScheduleRequest = {
558
+ schedule: {
559
+ schedule_id: scheduleId,
560
+ start_time: startTime,
561
+ end_time: endTime,
562
+ is_whole_day: schedule.is_whole_day,
563
+ summary,
564
+ description,
565
+ location,
566
+ admins,
567
+ attendees,
568
+ reminders,
569
+ },
570
+ skip_attendees: p.request.skip_attendees as any,
571
+ op_mode: opMode as any,
572
+ op_start_time: opStartTime,
573
+ };
574
+
575
+ const json = await this.post<UpdateScheduleResponse>("/cgi-bin/oa/schedule/update", "update_schedule", p.agent, request);
576
+ return { raw: json, scheduleId: json.schedule_id || scheduleId };
577
+ }
578
+
579
+ /**
580
+ * 新增日程参与者
581
+ * POST /cgi-bin/oa/schedule/add_attendees
582
+ * 注意:该接口是增量式
583
+ */
584
+ async addScheduleAttendees(p: { agent: ResolvedAgentAccount; request: AddScheduleAttendeesRequest }): Promise<{ raw: AddScheduleAttendeesResponse; scheduleId: string }> {
585
+ const scheduleId = validateString(p.request.schedule_id, "schedule_id", { allowEmpty: false });
586
+ const attendees = validateScheduleAttendees(p.request.attendees);
587
+
588
+ if (!attendees || attendees.length === 0) {
589
+ throw new Error("attendees 不能为空");
590
+ }
591
+
592
+ const request: AddScheduleAttendeesRequest = {
593
+ schedule_id: scheduleId,
594
+ attendees,
595
+ };
596
+
597
+ const json = await this.post<AddScheduleAttendeesResponse>("/cgi-bin/oa/schedule/add_attendees", "add_attendees", p.agent, request);
598
+ return { raw: json, scheduleId: scheduleId };
599
+ }
600
+
601
+ /**
602
+ * 删除日程参与者
603
+ * POST /cgi-bin/oa/schedule/del_attendees
604
+ * 注意:该接口是增量式
605
+ */
606
+ async deleteScheduleAttendees(p: { agent: ResolvedAgentAccount; request: DeleteScheduleAttendeesRequest }): Promise<{ raw: DeleteScheduleAttendeesResponse; scheduleId: string }> {
607
+ const scheduleId = validateString(p.request.schedule_id, "schedule_id", { allowEmpty: false });
608
+ const attendees = validateScheduleAttendees(p.request.attendees);
609
+
610
+ if (!attendees || attendees.length === 0) {
611
+ throw new Error("attendees 不能为空");
612
+ }
613
+
614
+ const request: DeleteScheduleAttendeesRequest = {
615
+ schedule_id: scheduleId,
616
+ attendees,
617
+ };
618
+
619
+ const json = await this.post<DeleteScheduleAttendeesResponse>("/cgi-bin/oa/schedule/del_attendees", "del_attendees", p.agent, request);
620
+ return { raw: json, scheduleId: scheduleId };
621
+ }
622
+
623
+ /**
624
+ * 获取日历下的日程列表
625
+ * POST /cgi-bin/oa/schedule/get_by_calendar
626
+ */
627
+ async getScheduleByCalendar(p: { agent: ResolvedAgentAccount; request: GetScheduleByCalendarRequest }): Promise<{ raw: GetScheduleByCalendarResponse; scheduleList: ScheduleInfo[] }> {
628
+ const calId = validateString(p.request.cal_id, "cal_id", { allowEmpty: false });
629
+
630
+ if (p.request.offset !== undefined && p.request.offset < 0) {
631
+ throw new Error("offset 不能小于 0");
632
+ }
633
+
634
+ let limit = p.request.limit;
635
+ if (limit !== undefined) {
636
+ if (limit < CALENDAR_LIMITS.GET_SCHEDULE_LIMIT_MIN || limit > CALENDAR_LIMITS.GET_SCHEDULE_LIMIT_MAX) {
637
+ throw new Error(`limit 必须在 ${CALENDAR_LIMITS.GET_SCHEDULE_LIMIT_MIN}-${CALENDAR_LIMITS.GET_SCHEDULE_LIMIT_MAX} 之间`);
638
+ }
639
+ }
640
+
641
+ const request: GetScheduleByCalendarRequest = {
642
+ cal_id: calId,
643
+ offset: p.request.offset,
644
+ limit: limit,
645
+ };
646
+
647
+ const json = await this.post<GetScheduleByCalendarResponse>("/cgi-bin/oa/schedule/get_by_calendar", "get_by_calendar", p.agent, request);
648
+ return { raw: json, scheduleList: json.schedule_list || [] };
649
+ }
650
+
651
+ /**
652
+ * 获取日程详情
653
+ * POST /cgi-bin/oa/schedule/get
654
+ */
655
+ async getSchedule(p: { agent: ResolvedAgentAccount; request: GetScheduleRequest }): Promise<{ raw: GetScheduleResponse; scheduleList: ScheduleInfo[]; meetingCode?: string; meetingLink?: string }> {
656
+ const scheduleIdList = validateArray(p.request.schedule_id_list, "schedule_id_list", {
657
+ min: 1,
658
+ max: CALENDAR_LIMITS.SCHEDULE_ID_LIST_MAX,
659
+ required: true
660
+ }).map((id, i) => validateString(id, `schedule_id_list[${i}]`, { allowEmpty: false }));
661
+
662
+ const request: GetScheduleRequest = { schedule_id_list: scheduleIdList };
663
+ const json = await this.post<GetScheduleResponse>("/cgi-bin/oa/schedule/get", "get_schedule", p.agent, request);
664
+ return {
665
+ raw: json,
666
+ scheduleList: json.schedule_list || [],
667
+ meetingCode: json.meeting_code,
668
+ meetingLink: json.meeting_link,
669
+ };
670
+ }
671
+
672
+ /**
673
+ * 取消日程
674
+ * POST /cgi-bin/oa/schedule/del
675
+ */
676
+ async deleteSchedule(p: { agent: ResolvedAgentAccount; request: DeleteScheduleRequest }): Promise<{ raw: DeleteScheduleResponse; scheduleId: string }> {
677
+ const scheduleId = validateString(p.request.schedule_id, "schedule_id", { allowEmpty: false });
678
+ const opMode = validateOpMode(p.request.op_mode);
679
+ const opStartTime = p.request.op_start_time !== undefined
680
+ ? validateNumber(p.request.op_start_time, "op_start_time", { min: 0 })
681
+ : undefined;
682
+
683
+ const request: DeleteScheduleRequest = {
684
+ schedule_id: scheduleId,
685
+ op_mode: opMode as any,
686
+ op_start_time: opStartTime,
687
+ };
688
+
689
+ const json = await this.post<DeleteScheduleResponse>("/cgi-bin/oa/schedule/del", "delete_schedule", p.agent, request);
690
+ return { raw: json, scheduleId: scheduleId };
691
+ }
692
+
693
+ // ========================================================================
694
+ // System Calendar APIs
695
+ // ========================================================================
696
+
697
+ /**
698
+ * 获取默认日历本 ID
699
+ * POST /cgi-bin/oa/calendar/get_system_calid
700
+ */
701
+ async getSystemCalendarId(p: { agent: ResolvedAgentAccount; userid: string }): Promise<{ raw: GetSystemCalendarIdResponse; calId: string }> {
702
+ const userid = validateString(p.userid, "userid", { allowEmpty: false });
703
+ const json = await this.post<GetSystemCalendarIdResponse>("/cgi-bin/oa/calendar/get_system_calid", "get_system_calid", p.agent, { userid });
704
+ return { raw: json, calId: json.cal_id };
705
+ }
706
+
707
+ /**
708
+ * 在默认日历本中创建日程
709
+ * POST /cgi-bin/oa/schedule/add_schedule_in_system_cal
710
+ */
711
+ async createSystemSchedule(p: { agent: ResolvedAgentAccount; request: CreateSystemScheduleRequest }): Promise<{ raw: CreateScheduleResponse; scheduleId: string }> {
712
+ const schedule = p.request.schedule;
713
+
714
+ const organizer = validateString(schedule.organizer, "schedule.organizer", { allowEmpty: false });
715
+ const startTime = validateNumber(schedule.start_time, "schedule.start_time", { min: 0, required: true });
716
+ const endTime = validateNumber(schedule.end_time, "schedule.end_time", { min: 0, required: true });
717
+
718
+ if (endTime <= startTime) {
719
+ throw new Error("schedule.end_time 必须大于 schedule.start_time");
720
+ }
721
+
722
+ if (schedule.is_whole_day !== undefined && ![0, 1].includes(schedule.is_whole_day)) {
723
+ throw new Error("schedule.is_whole_day 必须是 0 或 1");
724
+ }
725
+
726
+ const summary = schedule.summary !== undefined
727
+ ? validateString(schedule.summary, "schedule.summary", { max: CALENDAR_LIMITS.SCHEDULE_SUMMARY_MAX_LENGTH, allowEmpty: true })
728
+ : undefined;
729
+
730
+ const description = schedule.description !== undefined
731
+ ? validateString(schedule.description, "schedule.description", { max: CALENDAR_LIMITS.SCHEDULE_DESCRIPTION_MAX_LENGTH, allowEmpty: true })
732
+ : undefined;
733
+
734
+ const location = schedule.location !== undefined
735
+ ? validateString(schedule.location, "schedule.location", { max: CALENDAR_LIMITS.SCHEDULE_LOCATION_MAX_LENGTH, allowEmpty: true })
736
+ : undefined;
737
+
738
+ const attendees = validateScheduleAttendees(schedule.attendees);
739
+ const reminders = validateReminders(schedule.reminders);
740
+
741
+ const request: CreateSystemScheduleRequest = {
742
+ schedule: {
743
+ organizer,
744
+ start_time: startTime,
745
+ end_time: endTime,
746
+ is_whole_day: schedule.is_whole_day,
747
+ summary,
748
+ description,
749
+ location,
750
+ attendees,
751
+ reminders,
752
+ },
753
+ };
754
+
755
+ const json = await this.post<CreateScheduleResponse>("/cgi-bin/oa/schedule/add_schedule_in_system_cal", "create_system_schedule", p.agent, request);
756
+ return { raw: json, scheduleId: json.schedule_id };
757
+ }
758
+
759
+ /**
760
+ * 日程回执
761
+ * POST /cgi-bin/oa/schedule/respond
762
+ */
763
+ async respondSchedule(p: { agent: ResolvedAgentAccount; request: RespondScheduleRequest }): Promise<{ raw: RespondScheduleResponse; scheduleId: string }> {
764
+ const scheduleId = validateString(p.request.schedule_id, "schedule_id", { allowEmpty: false });
765
+ const opMode = validateOpMode(p.request.op_mode);
766
+ const opStartTime = p.request.op_start_time !== undefined
767
+ ? validateNumber(p.request.op_start_time, "op_start_time", { min: 0 })
768
+ : undefined;
769
+
770
+ const attendee = validateString(p.request.attendee, "attendee", { allowEmpty: false });
771
+
772
+ if (!CALENDAR_LIMITS.RESPONSE_STATUS_VALUES.includes(p.request.response_status)) {
773
+ throw new Error(`response_status 必须是 ${CALENDAR_LIMITS.RESPONSE_STATUS_VALUES.join(",")}`);
774
+ }
775
+
776
+ const request: RespondScheduleRequest = {
777
+ schedule_id: scheduleId,
778
+ op_mode: opMode as any,
779
+ op_start_time: opStartTime,
780
+ attendee,
781
+ response_status: p.request.response_status as any,
782
+ };
783
+
784
+ const json = await this.post<RespondScheduleResponse>("/cgi-bin/oa/schedule/respond", "respond_schedule", p.agent, request);
785
+ return { raw: json, scheduleId: scheduleId };
786
+ }
787
+
788
+ /**
789
+ * 同步日程
790
+ * POST /cgi-bin/oa/schedule/sync
791
+ */
792
+ async syncSchedule(p: { agent: ResolvedAgentAccount; request: SyncScheduleRequest }): Promise<{ raw: SyncScheduleResponse; nextCursor: string; scheduleList: ScheduleInfo[] }> {
793
+ const calId = validateString(p.request.cal_id, "cal_id", { allowEmpty: false });
794
+
795
+ let limit = p.request.limit;
796
+ if (limit !== undefined) {
797
+ if (limit < CALENDAR_LIMITS.GET_SCHEDULE_LIMIT_MIN || limit > CALENDAR_LIMITS.GET_SCHEDULE_LIMIT_MAX) {
798
+ throw new Error(`limit 必须在 ${CALENDAR_LIMITS.GET_SCHEDULE_LIMIT_MIN}-${CALENDAR_LIMITS.GET_SCHEDULE_LIMIT_MAX} 之间`);
799
+ }
800
+ }
801
+
802
+ const request: SyncScheduleRequest = {
803
+ cal_id: calId,
804
+ cursor: p.request.cursor,
805
+ limit: limit,
806
+ };
807
+
808
+ const json = await this.post<SyncScheduleResponse>("/cgi-bin/oa/schedule/sync", "sync_schedule", p.agent, request);
809
+ return {
810
+ raw: json,
811
+ nextCursor: json.next_cursor,
812
+ scheduleList: json.schedule_list || [],
813
+ };
814
+ }
815
+ }