@zhin.js/adapter-lark 4.0.2 → 5.0.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 (65) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/README.md +60 -88
  3. package/adapters/lark.ts +26 -0
  4. package/agent/tools/add_members.ts +2 -2
  5. package/agent/tools/create_chat.ts +2 -2
  6. package/agent/tools/dissolve_chat.ts +2 -2
  7. package/agent/tools/get_user.ts +2 -2
  8. package/agent/tools/remove_managers.ts +2 -2
  9. package/agent/tools/set_managers.ts +2 -2
  10. package/agent/tools/update_chat.ts +2 -2
  11. package/agent/tools/upload_file.ts +2 -2
  12. package/lib/endpoint.d.ts +53 -0
  13. package/lib/endpoint.js +333 -0
  14. package/lib/index.d.ts +5 -0
  15. package/lib/index.js +5 -0
  16. package/lib/lark-agent-deps.d.ts +27 -0
  17. package/lib/lark-agent-deps.js +30 -0
  18. package/lib/platform-permit.d.ts +15 -0
  19. package/lib/{src/platform-permit.js → platform-permit.js} +1 -2
  20. package/lib/protocol.d.ts +111 -0
  21. package/lib/protocol.js +204 -0
  22. package/lib/webhook.d.ts +13 -0
  23. package/lib/webhook.js +67 -0
  24. package/package.json +43 -24
  25. package/plugin.ts +12 -0
  26. package/schema.json +61 -0
  27. package/src/endpoint.ts +354 -705
  28. package/src/index.ts +45 -59
  29. package/src/lark-agent-deps.ts +53 -9
  30. package/src/platform-permit.ts +1 -1
  31. package/src/protocol.ts +332 -0
  32. package/src/webhook.ts +101 -0
  33. package/lib/agent/tools/add_members.js +0 -21
  34. package/lib/agent/tools/add_members.js.map +0 -1
  35. package/lib/agent/tools/create_chat.js +0 -20
  36. package/lib/agent/tools/create_chat.js.map +0 -1
  37. package/lib/agent/tools/dissolve_chat.js +0 -18
  38. package/lib/agent/tools/dissolve_chat.js.map +0 -1
  39. package/lib/agent/tools/get_user.js +0 -17
  40. package/lib/agent/tools/get_user.js.map +0 -1
  41. package/lib/agent/tools/remove_managers.js +0 -21
  42. package/lib/agent/tools/remove_managers.js.map +0 -1
  43. package/lib/agent/tools/set_managers.js +0 -21
  44. package/lib/agent/tools/set_managers.js.map +0 -1
  45. package/lib/agent/tools/update_chat.js +0 -22
  46. package/lib/agent/tools/update_chat.js.map +0 -1
  47. package/lib/agent/tools/upload_file.js +0 -19
  48. package/lib/agent/tools/upload_file.js.map +0 -1
  49. package/lib/src/adapter.js +0 -48
  50. package/lib/src/adapter.js.map +0 -1
  51. package/lib/src/endpoint.js +0 -677
  52. package/lib/src/endpoint.js.map +0 -1
  53. package/lib/src/index.js +0 -43
  54. package/lib/src/index.js.map +0 -1
  55. package/lib/src/lark-agent-deps.js +0 -10
  56. package/lib/src/lark-agent-deps.js.map +0 -1
  57. package/lib/src/platform-permit.js.map +0 -1
  58. package/lib/src/segment-mapper.js +0 -2
  59. package/lib/src/segment-mapper.js.map +0 -1
  60. package/lib/src/types.js +0 -2
  61. package/lib/src/types.js.map +0 -1
  62. package/plugin.yml +0 -3
  63. package/src/adapter.ts +0 -57
  64. package/src/segment-mapper.ts +0 -1
  65. package/src/types.ts +0 -58
package/src/index.ts CHANGED
@@ -1,63 +1,49 @@
1
- /**
2
- * 飞书/Lark 适配器入口:类型扩展、导出、注册
3
- */
4
- import { usePlugin, type Plugin, type Context, type ISceneManagement, createSceneManagementTools, type ToolFeature } from "zhin.js";
5
- import { LarkAdapter } from "./adapter.js";
6
- import {
7
- larkGroupPermitResolver,
8
- registerLarkPlatformPermitChecker,
9
- } from "./platform-permit.js";
10
- import { setLarkAgentDeps } from "./lark-agent-deps.js";
11
-
12
- declare module "zhin.js" {
13
- namespace Plugin {
14
- interface Contexts {
15
- router: import("@zhin.js/host-router").Router;
16
- }
17
- }
18
- interface Adapters {
19
- lark: LarkAdapter;
20
- }
21
- }
1
+ export {
2
+ formatInboundContent,
3
+ formatOutboundBody,
4
+ generateMessageId,
5
+ headerValue,
6
+ normalizeWebhookPath,
7
+ readTextBody,
8
+ resolveChatType,
9
+ resolveLarkConfig,
10
+ resolveSender,
11
+ resolveTarget,
12
+ verifySignature,
13
+ type AccessToken,
14
+ type LarkAdapterConfig,
15
+ type LarkApiResponse,
16
+ type LarkEventBody,
17
+ type LarkMessage,
18
+ type LarkSendBody,
19
+ type LarkWireSegment,
20
+ type ResolvedLarkConfig,
21
+ } from './protocol.js';
22
22
 
23
- export * from "./types.js";
24
- export { LarkEndpoint } from "./endpoint.js";
25
- export { LarkAdapter } from "./adapter.js";
23
+ export {
24
+ getLarkAgentDeps,
25
+ registerLarkAgentEndpoint,
26
+ setLarkAgentDeps,
27
+ type LarkAgentDeps,
28
+ type LarkAgentEndpoint,
29
+ } from './lark-agent-deps.js';
26
30
 
27
- const plugin = usePlugin();
28
- const { provide, useContext } = plugin;
31
+ export {
32
+ checkLarkPlatformPermit,
33
+ larkGroupPermitResolver,
34
+ normalizeLarkSenderForPermit,
35
+ platformPermit,
36
+ registerLarkPlatformPermitChecker,
37
+ } from './platform-permit.js';
29
38
 
30
- (useContext as (key: string, fn: (router: any) => void) => void)("router", (router) => {
31
- provide({
32
- name: "lark",
33
- description: "Lark/Feishu Endpoint Adapter",
34
- mounted: async (p: Plugin) => {
35
- const adapter = new LarkAdapter(p, router);
36
- await adapter.start();
37
- return adapter;
38
- },
39
- dispose: async (adapter: LarkAdapter) => {
40
- await adapter.stop();
41
- },
42
- } as Context<"lark">);
43
- });
39
+ export {
40
+ LarkEndpoint,
41
+ type LarkEndpointOptions,
42
+ type LarkFetch,
43
+ } from './endpoint.js';
44
44
 
45
- useContext('tool', 'lark', (toolService: ToolFeature, lark: LarkAdapter) => {
46
- const disposers: (() => void)[] = [];
47
- disposers.push(registerLarkPlatformPermitChecker());
48
- setLarkAgentDeps({
49
- getEndpoint: (endpointId) => {
50
- const endpoint = lark.endpoints.get(endpointId);
51
- if (!endpoint) throw new Error(`Endpoint ${endpointId} 不存在`);
52
- return endpoint;
53
- },
54
- getAdapter: () => lark,
55
- });
56
- const sceneTools = createSceneManagementTools(
57
- lark as unknown as ISceneManagement,
58
- 'lark',
59
- { permitResolver: larkGroupPermitResolver, registerChecker: false },
60
- );
61
- disposers.push(...sceneTools.map(t => toolService.addTool(t, plugin.name)));
62
- return () => disposers.forEach(d => d());
63
- });
45
+ export {
46
+ registerLarkWebhookRoutes,
47
+ handleLarkWebhookRequest,
48
+ type LarkWebhookHandler,
49
+ } from './webhook.js';
@@ -1,18 +1,62 @@
1
- import type { LarkAdapter } from './adapter.js';
2
- import type { LarkEndpoint } from './endpoint.js';
1
+ /**
2
+ * Agent tool deps for lark (user / chat / managers / upload).
3
+ * Endpoints register themselves on start; tools look up by endpoint id.
4
+ */
5
+
6
+ export interface LarkAgentEndpoint {
7
+ getUserInfo(
8
+ userId: string,
9
+ userIdType?: 'open_id' | 'user_id' | 'union_id',
10
+ ): Promise<unknown>;
11
+ getChatInfo(chatId: string): Promise<unknown>;
12
+ uploadFile(
13
+ filePath: string,
14
+ fileType?: 'image' | 'file' | 'video' | 'audio',
15
+ ): Promise<string | null>;
16
+ createChat(name: string, userIds: string[], ownerId?: string): Promise<string | null>;
17
+ updateChatInfo(
18
+ chatId: string,
19
+ options: { name?: string; description?: string },
20
+ ): Promise<boolean>;
21
+ addChatMembers(chatId: string, userIds: string[]): Promise<boolean>;
22
+ removeChatMembers(chatId: string, userIds: string[]): Promise<boolean>;
23
+ getChatMembers(chatId: string): Promise<unknown[]>;
24
+ dissolveChat(chatId: string): Promise<boolean>;
25
+ setChatManagers(chatId: string, userIds: string[]): Promise<boolean>;
26
+ removeChatManagers(chatId: string, userIds: string[]): Promise<boolean>;
27
+ }
3
28
 
4
29
  export interface LarkAgentDeps {
5
- getEndpoint: (endpointId: string) => LarkEndpoint;
6
- getAdapter: () => LarkAdapter;
30
+ getEndpoint: (endpointId: string) => LarkAgentEndpoint;
7
31
  }
8
32
 
9
- let _deps: LarkAgentDeps | null = null;
33
+ const endpoints = new Map<string, LarkAgentEndpoint>();
34
+ let override: LarkAgentDeps | null = null;
35
+
36
+ export function registerLarkAgentEndpoint(
37
+ endpointId: string,
38
+ endpoint: LarkAgentEndpoint,
39
+ ): () => void {
40
+ endpoints.set(endpointId, endpoint);
41
+ return () => {
42
+ if (endpoints.get(endpointId) === endpoint) {
43
+ endpoints.delete(endpointId);
44
+ }
45
+ };
46
+ }
10
47
 
11
- export function setLarkAgentDeps(deps: LarkAgentDeps): void {
12
- _deps = deps;
48
+ /** Optional override used by tests / transitional callers. Pass `null` to clear. */
49
+ export function setLarkAgentDeps(deps: LarkAgentDeps | null): void {
50
+ override = deps;
13
51
  }
14
52
 
15
53
  export function getLarkAgentDeps(): LarkAgentDeps {
16
- if (!_deps) throw new Error('lark agent deps not initialized');
17
- return _deps;
54
+ if (override) return override;
55
+ return {
56
+ getEndpoint(endpointId) {
57
+ const endpoint = endpoints.get(endpointId);
58
+ if (!endpoint) throw new Error(`Endpoint ${endpointId} 不存在`);
59
+ return endpoint;
60
+ },
61
+ };
18
62
  }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * 飞书 Lark platform permit
3
3
  */
4
- import { registerPlatformPermitChecker, type Message } from 'zhin.js';
4
+ import { registerPlatformPermitChecker, type Message } from '@zhin.js/core';
5
5
 
6
6
  const ADAPTER = 'lark';
7
7
 
@@ -0,0 +1,332 @@
1
+ /**
2
+ * Lark/Feishu protocol helpers — no legacy Adapter/Endpoint / segment-mapper.
3
+ * Canonicalization is owned by gateway/core before endpoint.send.
4
+ */
5
+
6
+ import { createHash, timingSafeEqual } from 'node:crypto';
7
+ import type { IncomingMessage } from 'node:http';
8
+
9
+ /** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
10
+ export interface LarkAdapterConfig {
11
+ readonly name?: string;
12
+ readonly appId?: string;
13
+ readonly appSecret?: string;
14
+ readonly encryptKey?: string;
15
+ readonly verificationToken?: string;
16
+ readonly webhookPath?: string;
17
+ readonly apiBaseUrl?: string;
18
+ readonly isFeishu?: boolean;
19
+ /** Transitional: legacy root `endpoints[]` with `context: lark`. */
20
+ readonly endpoints?: ReadonlyArray<Partial<ResolvedLarkConfig> & {
21
+ readonly context?: string;
22
+ }>;
23
+ }
24
+
25
+ export interface ResolvedLarkConfig {
26
+ readonly context: 'lark';
27
+ readonly name: string;
28
+ readonly appId: string;
29
+ readonly appSecret: string;
30
+ readonly encryptKey?: string;
31
+ readonly verificationToken?: string;
32
+ readonly webhookPath: string;
33
+ readonly apiBaseUrl: string;
34
+ readonly isFeishu: boolean;
35
+ }
36
+
37
+ export interface LarkMessage {
38
+ readonly message_id?: string;
39
+ readonly root_id?: string;
40
+ readonly parent_id?: string;
41
+ readonly create_time?: string;
42
+ readonly update_time?: string;
43
+ readonly chat_id?: string;
44
+ readonly sender?: {
45
+ readonly sender_id?: {
46
+ readonly user_id?: string;
47
+ readonly open_id?: string;
48
+ readonly union_id?: string;
49
+ };
50
+ readonly sender_type?: string;
51
+ readonly tenant_key?: string;
52
+ };
53
+ readonly message_type?: string;
54
+ readonly content?: string;
55
+ readonly mentions?: ReadonlyArray<{
56
+ readonly key?: string;
57
+ readonly id?: {
58
+ readonly user_id?: string;
59
+ readonly open_id?: string;
60
+ readonly union_id?: string;
61
+ };
62
+ readonly name?: string;
63
+ readonly tenant_key?: string;
64
+ }>;
65
+ }
66
+
67
+ export interface LarkEventBody {
68
+ readonly uuid?: string;
69
+ readonly token?: string;
70
+ readonly ts?: string;
71
+ readonly type?: string;
72
+ readonly challenge?: string;
73
+ readonly event?: {
74
+ readonly sender?: LarkMessage['sender'];
75
+ readonly message?: LarkMessage;
76
+ readonly [key: string]: unknown;
77
+ };
78
+ }
79
+
80
+ export interface AccessToken {
81
+ token: string;
82
+ expires_in: number;
83
+ timestamp: number;
84
+ }
85
+
86
+ export interface LarkApiResponse {
87
+ readonly code: number;
88
+ readonly msg?: string;
89
+ readonly tenant_access_token?: string;
90
+ readonly expire?: number;
91
+ readonly data?: Record<string, unknown>;
92
+ readonly [key: string]: unknown;
93
+ }
94
+
95
+ export interface LarkWireSegment {
96
+ readonly type: string;
97
+ readonly data?: Record<string, unknown>;
98
+ }
99
+
100
+ export interface LarkSendBody {
101
+ readonly msg_type: string;
102
+ readonly content: string;
103
+ }
104
+
105
+ export function resolveLarkConfig(config: LarkAdapterConfig = {}): ResolvedLarkConfig {
106
+ const entry = config.endpoints?.find((item) => item.context === 'lark');
107
+ const appId = config.appId ?? entry?.appId ?? process.env.LARK_APP_ID;
108
+ const appSecret = config.appSecret ?? entry?.appSecret ?? process.env.LARK_APP_SECRET;
109
+ if (!appId || !appSecret) {
110
+ throw new TypeError(
111
+ 'Lark adapter requires appId + appSecret (plugins.<key> or endpoints with context: lark)',
112
+ );
113
+ }
114
+ const isFeishu = config.isFeishu ?? entry?.isFeishu ?? true;
115
+ const name = (typeof config.name === 'string' && config.name)
116
+ || (typeof entry?.name === 'string' && entry.name)
117
+ || process.env.LARK_BOT_NAME
118
+ || 'lark-bot';
119
+ const webhookPath = normalizeWebhookPath(
120
+ config.webhookPath ?? entry?.webhookPath ?? '/lark/webhook',
121
+ );
122
+ const defaultBase = isFeishu
123
+ ? 'https://open.feishu.cn/open-apis'
124
+ : 'https://open.larksuite.com/open-apis';
125
+ const apiBaseUrl = (
126
+ config.apiBaseUrl ?? entry?.apiBaseUrl ?? defaultBase
127
+ ).replace(/\/$/, '');
128
+ const encryptKey = config.encryptKey ?? entry?.encryptKey;
129
+ const verificationToken = config.verificationToken ?? entry?.verificationToken;
130
+ return {
131
+ context: 'lark',
132
+ name,
133
+ appId,
134
+ appSecret,
135
+ ...(encryptKey ? { encryptKey } : {}),
136
+ ...(verificationToken ? { verificationToken } : {}),
137
+ webhookPath,
138
+ apiBaseUrl,
139
+ isFeishu,
140
+ };
141
+ }
142
+
143
+ export function normalizeWebhookPath(path: string): string {
144
+ const trimmed = path.trim() || '/lark/webhook';
145
+ return trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
146
+ }
147
+
148
+ export function resolveChatType(chatId?: string): 'group' | 'private' {
149
+ return chatId?.startsWith('oc_') ? 'group' : 'private';
150
+ }
151
+
152
+ export function resolveTarget(msg: LarkMessage): string {
153
+ return msg.chat_id || 'unknown';
154
+ }
155
+
156
+ export function resolveSender(msg: LarkMessage): string {
157
+ return msg.sender?.sender_id?.open_id
158
+ || msg.sender?.sender_id?.user_id
159
+ || 'unknown';
160
+ }
161
+
162
+ export function generateMessageId(msg: LarkMessage): string {
163
+ return msg.message_id || `${msg.create_time ?? Date.now()}`;
164
+ }
165
+
166
+ /** Build inbound text for MessageGateway.receive. */
167
+ export function formatInboundContent(msg: LarkMessage): string {
168
+ if (!msg.content || !msg.message_type) return '';
169
+ try {
170
+ const parsed = JSON.parse(msg.content) as Record<string, unknown>;
171
+ switch (msg.message_type) {
172
+ case 'text':
173
+ return typeof parsed.text === 'string' ? parsed.text : '';
174
+ case 'image':
175
+ return '[image]';
176
+ case 'file': {
177
+ const name = typeof parsed.file_name === 'string' ? parsed.file_name : '';
178
+ return name ? `[file: ${name}]` : '[file]';
179
+ }
180
+ case 'audio':
181
+ return '[audio]';
182
+ case 'video':
183
+ return '[video]';
184
+ case 'sticker':
185
+ return '[sticker]';
186
+ case 'post':
187
+ case 'interactive':
188
+ return `[${msg.message_type}]`;
189
+ case 'rich_text':
190
+ return '[rich_text]';
191
+ default:
192
+ return `[${msg.message_type}]`;
193
+ }
194
+ } catch {
195
+ return '[消息解析失败]';
196
+ }
197
+ }
198
+
199
+ export function verifySignature(
200
+ encryptKey: string,
201
+ timestamp: string,
202
+ nonce: string,
203
+ body: string,
204
+ signature: string,
205
+ ): boolean {
206
+ try {
207
+ const stringToSign = `${timestamp}${nonce}${encryptKey}${body}`;
208
+ const calculated = createHash('sha256').update(stringToSign).digest('hex');
209
+ const a = Buffer.from(calculated);
210
+ const b = Buffer.from(signature);
211
+ if (a.length !== b.length) return false;
212
+ return timingSafeEqual(a, b);
213
+ } catch {
214
+ return false;
215
+ }
216
+ }
217
+
218
+ /**
219
+ * Wire-encode an already-rendered outbound payload into Lark im/v1 message body.
220
+ * Segment canonicalization is intentionally not done here.
221
+ */
222
+ export function formatOutboundBody(payload: unknown): LarkSendBody {
223
+ if (typeof payload === 'string') {
224
+ return {
225
+ msg_type: 'text',
226
+ content: JSON.stringify({ text: payload }),
227
+ };
228
+ }
229
+
230
+ const items: Array<string | LarkWireSegment> = Array.isArray(payload)
231
+ ? payload as Array<string | LarkWireSegment>
232
+ : payload && typeof payload === 'object' && 'type' in (payload as object)
233
+ ? [payload as LarkWireSegment]
234
+ : [];
235
+
236
+ if (items.length === 0) {
237
+ const text = payload == null
238
+ ? ''
239
+ : typeof payload === 'object'
240
+ ? JSON.stringify(payload)
241
+ : String(payload);
242
+ return {
243
+ msg_type: 'text',
244
+ content: JSON.stringify({ text }),
245
+ };
246
+ }
247
+
248
+ const textParts: string[] = [];
249
+ let media: LarkSendBody | null = null;
250
+
251
+ for (const item of items) {
252
+ if (typeof item === 'string') {
253
+ textParts.push(item);
254
+ continue;
255
+ }
256
+ const data = item.data ?? {};
257
+ switch (item.type) {
258
+ case 'text':
259
+ textParts.push(String(data.content ?? data.text ?? ''));
260
+ break;
261
+ case 'at':
262
+ textParts.push(
263
+ `<at user_id="${String(data.id ?? '')}">${String(data.name || data.id || '')}</at>`,
264
+ );
265
+ break;
266
+ case 'image':
267
+ if (!media) {
268
+ media = {
269
+ msg_type: 'image',
270
+ content: JSON.stringify({
271
+ image_key: data.file_key ?? data.key ?? data.url,
272
+ }),
273
+ };
274
+ }
275
+ break;
276
+ case 'file':
277
+ if (!media) {
278
+ media = {
279
+ msg_type: 'file',
280
+ content: JSON.stringify({
281
+ file_key: data.file_key ?? data.key,
282
+ }),
283
+ };
284
+ }
285
+ break;
286
+ case 'card':
287
+ if (!media) {
288
+ media = {
289
+ msg_type: 'interactive',
290
+ content: JSON.stringify(data),
291
+ };
292
+ }
293
+ break;
294
+ default:
295
+ textParts.push(`[${item.type}]`);
296
+ }
297
+ }
298
+
299
+ if (media) return media;
300
+ return {
301
+ msg_type: 'text',
302
+ content: JSON.stringify({ text: textParts.join('') }),
303
+ };
304
+ }
305
+
306
+ export function headerValue(
307
+ headers: IncomingMessage['headers'],
308
+ name: string,
309
+ ): string {
310
+ const value = headers[name] ?? headers[name.toLowerCase()];
311
+ if (Array.isArray(value)) return value[0] ?? '';
312
+ return value ?? '';
313
+ }
314
+
315
+ export async function readTextBody(
316
+ request: IncomingMessage,
317
+ options: { readonly limit?: number } = {},
318
+ ): Promise<string> {
319
+ const limit = options.limit ?? 1_048_576;
320
+ const chunks: Buffer[] = [];
321
+ let size = 0;
322
+ for await (const chunk of request) {
323
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
324
+ size += buffer.length;
325
+ if (size > limit) {
326
+ request.destroy();
327
+ throw new Error(`Request body exceeds ${limit} bytes`);
328
+ }
329
+ chunks.push(buffer);
330
+ }
331
+ return Buffer.concat(chunks).toString('utf8');
332
+ }
package/src/webhook.ts ADDED
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Lark/Feishu webhook HTTP: verification → signature → challenge/event → admit.
3
+ */
4
+ import type { IncomingMessage, ServerResponse } from 'node:http';
5
+ import type { HttpHost, HttpRouteRegistration } from '@zhin.js/host-http';
6
+ import { formatCompact, getLogger } from '@zhin.js/logger';
7
+ import {
8
+ headerValue,
9
+ readTextBody,
10
+ verifySignature,
11
+ type LarkEventBody,
12
+ type LarkMessage,
13
+ type ResolvedLarkConfig,
14
+ } from './protocol.js';
15
+
16
+ const logger = getLogger('lark');
17
+
18
+ export interface LarkWebhookHandler {
19
+ readonly config: ResolvedLarkConfig;
20
+ readonly isOpen: boolean;
21
+ admit(msg: LarkMessage): void;
22
+ }
23
+
24
+ export function registerLarkWebhookRoutes(
25
+ http: HttpHost,
26
+ handler: LarkWebhookHandler,
27
+ ): HttpRouteRegistration[] {
28
+ const path = handler.config.webhookPath;
29
+ return [
30
+ http.route('POST', path, async (request, response) => {
31
+ await handleLarkWebhookRequest(request, response, handler);
32
+ }, { summary: 'Lark/Feishu event webhook', tags: ['lark'] }),
33
+ ];
34
+ }
35
+
36
+ export async function handleLarkWebhookRequest(
37
+ request: IncomingMessage,
38
+ response: ServerResponse,
39
+ handler: LarkWebhookHandler,
40
+ ): Promise<void> {
41
+ try {
42
+ const rawBody = await readTextBody(request);
43
+
44
+ if (handler.config.verificationToken) {
45
+ const token = headerValue(request.headers, 'x-lark-request-token');
46
+ if (token !== handler.config.verificationToken) {
47
+ logger.warn(formatCompact({ op: 'webhook', ok: false, error: 'invalid verification token' }));
48
+ response.writeHead(403, { 'Content-Type': 'text/plain' });
49
+ response.end('Forbidden');
50
+ return;
51
+ }
52
+ }
53
+
54
+ if (handler.config.encryptKey) {
55
+ const timestamp = headerValue(request.headers, 'x-lark-request-timestamp');
56
+ const nonce = headerValue(request.headers, 'x-lark-request-nonce');
57
+ const signature = headerValue(request.headers, 'x-lark-signature');
58
+ if (
59
+ !timestamp
60
+ || !nonce
61
+ || !signature
62
+ || !verifySignature(handler.config.encryptKey, timestamp, nonce, rawBody, signature)
63
+ ) {
64
+ logger.warn(formatCompact({ op: 'webhook', ok: false, error: 'invalid signature' }));
65
+ response.writeHead(403, { 'Content-Type': 'text/plain' });
66
+ response.end('Forbidden');
67
+ return;
68
+ }
69
+ }
70
+
71
+ let event: LarkEventBody;
72
+ try {
73
+ event = JSON.parse(rawBody) as LarkEventBody;
74
+ } catch {
75
+ response.writeHead(200, { 'Content-Type': 'application/json' });
76
+ response.end(JSON.stringify({ code: 0, msg: 'success' }));
77
+ return;
78
+ }
79
+
80
+ if (event.type === 'url_verification') {
81
+ response.writeHead(200, { 'Content-Type': 'application/json' });
82
+ response.end(JSON.stringify({ challenge: event.challenge }));
83
+ return;
84
+ }
85
+
86
+ if (event.type === 'event_callback' && event.event?.message && handler.isOpen) {
87
+ const message: LarkMessage = {
88
+ ...event.event.message,
89
+ sender: event.event.message.sender ?? event.event.sender,
90
+ };
91
+ handler.admit(message);
92
+ }
93
+
94
+ response.writeHead(200, { 'Content-Type': 'application/json' });
95
+ response.end(JSON.stringify({ code: 0, msg: 'success' }));
96
+ } catch (error) {
97
+ logger.error('Webhook error:', error);
98
+ response.writeHead(500, { 'Content-Type': 'application/json' });
99
+ response.end(JSON.stringify({ code: -1, msg: 'Internal Server Error' }));
100
+ }
101
+ }
@@ -1,21 +0,0 @@
1
- import { defineTool } from '@zhin.js/agent/tools';
2
- import { z } from 'zod';
3
- import { getLarkAgentDeps } from '../../src/lark-agent-deps.js';
4
- import { platformPermit } from '../../src/platform-permit.js';
5
- export default defineTool({
6
- description: '添加飞书群成员',
7
- inputSchema: z.object({
8
- endpoint_id: z.string().describe('Endpoint 名称'),
9
- chat_id: z.string().describe('群聊 ID'),
10
- user_ids: z.string().describe('用户 open_id 列表,逗号分隔'),
11
- }),
12
- platforms: ['lark'],
13
- tags: ['lark'],
14
- permissions: [platformPermit('chat_admin')],
15
- async execute({ endpoint_id, chat_id, user_ids }) {
16
- const endpoint = getLarkAgentDeps().getEndpoint(endpoint_id);
17
- const success = await endpoint.addChatMembers(chat_id, user_ids.split(','));
18
- return { success, message: success ? '成员添加成功' : '添加失败' };
19
- },
20
- });
21
- //# sourceMappingURL=add_members.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"add_members.js","sourceRoot":"","sources":["../../../agent/tools/add_members.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAE9D,eAAe,UAAU,CAA6D;IACpF,WAAW,EAAE,SAAS;IACtB,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC;QACpB,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,aAAa,CAAC;QAC/C,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC;QACrC,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,oBAAoB,CAAC;KACpD,CAAC;IACF,SAAS,EAAE,CAAC,MAAM,CAAC;IACnB,IAAI,EAAE,CAAC,MAAM,CAAC;IACd,WAAW,EAAE,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC;IAC3C,KAAK,CAAC,OAAO,CAAC,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAgE;QAC5G,MAAM,QAAQ,GAAG,gBAAgB,EAAE,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC;QAC7D,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,cAAc,CAAC,OAAO,EAAE,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;QAC5E,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IAC3D,CAAC;CACF,CAAC,CAAC"}
@@ -1,20 +0,0 @@
1
- import { defineTool } from '@zhin.js/agent/tools';
2
- import { z } from 'zod';
3
- import { getLarkAgentDeps } from '../../src/lark-agent-deps.js';
4
- export default defineTool({
5
- description: '创建飞书群聊',
6
- inputSchema: z.object({
7
- endpoint_id: z.string().describe('Endpoint 名称'),
8
- name: z.string().describe('群名'),
9
- members: z.string().describe('成员 open_id 列表,逗号分隔'),
10
- owner: z.string().optional().describe('群主 open_id(可选)'),
11
- }),
12
- platforms: ['lark'],
13
- tags: ['lark'],
14
- async execute({ endpoint_id, name, members, owner }) {
15
- const endpoint = getLarkAgentDeps().getEndpoint(endpoint_id);
16
- const chatId = await endpoint.createChat(name, members.split(','), owner);
17
- return { success: !!chatId, chat_id: chatId, message: chatId ? `群聊创建成功: ${chatId}` : '创建失败' };
18
- },
19
- });
20
- //# sourceMappingURL=create_chat.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"create_chat.js","sourceRoot":"","sources":["../../../agent/tools/create_chat.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAChE,eAAe,UAAU,CAAyE;IAChG,WAAW,EAAE,QAAQ;IACrB,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC;QACpB,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,aAAa,CAAC;QAC/C,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC;QAC/B,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,oBAAoB,CAAC;QAClD,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,gBAAgB,CAAC;KACxD,CAAC;IACF,SAAS,EAAE,CAAC,MAAM,CAAC;IACnB,IAAI,EAAE,CAAC,MAAM,CAAC;IACd,KAAK,CAAC,OAAO,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAA4E;QAC3H,MAAM,QAAQ,GAAG,gBAAgB,EAAE,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC;QAC7D,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,UAAU,CAAC,IAAI,EAAE,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC;QAC1E,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,WAAW,MAAM,EAAE,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IAChG,CAAC;CACF,CAAC,CAAC"}