@zhin.js/adapter-lark 1.0.97 → 1.1.2

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 (80) hide show
  1. package/CHANGELOG.md +729 -3
  2. package/PERMITS.md +22 -0
  3. package/README.md +69 -83
  4. package/adapters/lark/index.js +36 -0
  5. package/adapters/lark/index.ts +41 -0
  6. package/agents/lark/agent.json +20 -0
  7. package/agents/lark/boundaries.md +3 -0
  8. package/agents/lark/conventions.md +3 -0
  9. package/agents/lark/skills/lark/SKILL.md +22 -0
  10. package/agents/lark/skills/lark/tools/add_members/index.js +19 -0
  11. package/agents/lark/skills/lark/tools/add_members/index.ts +19 -0
  12. package/agents/lark/skills/lark/tools/create_chat/index.js +18 -0
  13. package/agents/lark/skills/lark/tools/create_chat/index.ts +18 -0
  14. package/agents/lark/skills/lark/tools/dissolve_chat/index.js +16 -0
  15. package/agents/lark/skills/lark/tools/dissolve_chat/index.ts +16 -0
  16. package/agents/lark/skills/lark/tools/get_user/index.js +15 -0
  17. package/agents/lark/skills/lark/tools/get_user/index.ts +16 -0
  18. package/agents/lark/skills/lark/tools/remove_managers/index.js +19 -0
  19. package/agents/lark/skills/lark/tools/remove_managers/index.ts +19 -0
  20. package/agents/lark/skills/lark/tools/set_managers/index.js +19 -0
  21. package/agents/lark/skills/lark/tools/set_managers/index.ts +19 -0
  22. package/agents/lark/skills/lark/tools/update_chat/index.js +20 -0
  23. package/agents/lark/skills/lark/tools/update_chat/index.ts +20 -0
  24. package/agents/lark/skills/lark/tools/upload_file/index.js +17 -0
  25. package/agents/lark/skills/lark/tools/upload_file/index.ts +23 -0
  26. package/agents/lark/system.md +3 -0
  27. package/commands/lark/endpoint/add/[id]/index.js +3 -0
  28. package/commands/lark/endpoint/add/[id]/index.ts +3 -0
  29. package/commands/lark/endpoint/definition.js +20 -0
  30. package/commands/lark/endpoint/definition.ts +20 -0
  31. package/commands/lark/endpoint/list/index.js +3 -0
  32. package/commands/lark/endpoint/list/index.ts +3 -0
  33. package/commands/lark/endpoint/remove/[id]/index.js +3 -0
  34. package/commands/lark/endpoint/remove/[id]/index.ts +3 -0
  35. package/lib/client.d.ts +12 -0
  36. package/lib/client.js +2 -0
  37. package/lib/endpoint.d.ts +100 -0
  38. package/lib/endpoint.js +497 -0
  39. package/lib/index.d.ts +6 -15
  40. package/lib/index.js +6 -209
  41. package/lib/lark-runtime-state.d.ts +1 -0
  42. package/lib/lark-runtime-state.js +6 -0
  43. package/lib/media-upload.d.ts +15 -0
  44. package/lib/media-upload.js +58 -0
  45. package/lib/platform-permit.d.ts +14 -0
  46. package/lib/platform-permit.js +35 -0
  47. package/lib/protocol.d.ts +119 -0
  48. package/lib/protocol.js +272 -0
  49. package/lib/webhook.d.ts +14 -0
  50. package/lib/webhook.js +73 -0
  51. package/package.json +76 -21
  52. package/plugin.js +19 -0
  53. package/schema.json +110 -0
  54. package/src/client.ts +16 -0
  55. package/src/endpoint.ts +590 -0
  56. package/src/index.ts +51 -227
  57. package/src/lark-runtime-state.ts +7 -0
  58. package/src/media-upload.ts +69 -0
  59. package/src/platform-permit.ts +50 -0
  60. package/src/protocol.ts +396 -0
  61. package/src/webhook.ts +110 -0
  62. package/lib/adapter.d.ts +0 -17
  63. package/lib/adapter.d.ts.map +0 -1
  64. package/lib/adapter.js +0 -45
  65. package/lib/adapter.js.map +0 -1
  66. package/lib/bot.d.ts +0 -82
  67. package/lib/bot.d.ts.map +0 -1
  68. package/lib/bot.js +0 -638
  69. package/lib/bot.js.map +0 -1
  70. package/lib/index.d.ts.map +0 -1
  71. package/lib/index.js.map +0 -1
  72. package/lib/types.d.ts +0 -63
  73. package/lib/types.d.ts.map +0 -1
  74. package/lib/types.js +0 -2
  75. package/lib/types.js.map +0 -1
  76. package/plugin.yml +0 -3
  77. package/skills/lark/SKILL.md +0 -89
  78. package/src/adapter.ts +0 -55
  79. package/src/bot.ts +0 -712
  80. package/src/types.ts +0 -58
@@ -0,0 +1,272 @@
1
+ /**
2
+ * Lark/Feishu protocol helpers — no legacy Adapter/Endpoint / segment-mapper.
3
+ * Canonicalization is owned by gateway/core before endpoint.send.
4
+ */
5
+ import { createHash, timingSafeEqual } from 'node:crypto';
6
+ import { isMediaRef } from '@zhin.js/im-contract';
7
+ import { formatCompact, getLogger } from '@zhin.js/logger';
8
+ const logger = getLogger('lark');
9
+ export function resolveLarkConfig(config) {
10
+ const id = requiredEndpointField(config.id, 'id');
11
+ const appId = requiredEndpointField(config.appId, 'appId');
12
+ const appSecret = requiredEndpointField(config.appSecret, 'appSecret');
13
+ const isFeishu = config.isFeishu ?? true;
14
+ const webhookPath = normalizeWebhookPath(config.webhookPath ?? '/lark/webhook');
15
+ const defaultBase = isFeishu
16
+ ? 'https://open.feishu.cn/open-apis'
17
+ : 'https://open.larksuite.com/open-apis';
18
+ const apiBaseUrl = (config.apiBaseUrl ?? defaultBase).replace(/\/$/, '');
19
+ const encryptKey = config.encryptKey;
20
+ const verificationToken = config.verificationToken;
21
+ return {
22
+ context: 'lark',
23
+ id,
24
+ appId,
25
+ appSecret,
26
+ ...(encryptKey ? { encryptKey } : {}),
27
+ ...(verificationToken ? { verificationToken } : {}),
28
+ webhookPath,
29
+ apiBaseUrl,
30
+ isFeishu,
31
+ };
32
+ }
33
+ function requiredEndpointField(value, field) {
34
+ if (typeof value !== 'string' || !value.trim()) {
35
+ throw new TypeError(`Lark endpoint requires a non-empty ${field}`);
36
+ }
37
+ return value.trim();
38
+ }
39
+ export function normalizeWebhookPath(path) {
40
+ const trimmed = path.trim() || '/lark/webhook';
41
+ return trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
42
+ }
43
+ export function resolveChatType(chatId, chatType) {
44
+ // 优先使用事件里的 chat_type:p2p 会话的 chat_id 同样以 oc_ 开头,不能靠前缀判断
45
+ if (chatType === 'group')
46
+ return 'group';
47
+ if (chatType === 'p2p')
48
+ return 'private';
49
+ return chatId?.startsWith('oc_') ? 'group' : 'private';
50
+ }
51
+ /**
52
+ * 入站归一化 → ConversationRef:`chat_type` 为 'group' 的会话映射为 group,
53
+ * p2p 单聊映射为 private;id 一律为平台 chat_id。Lark 无 guild/频道容器,
54
+ * 不产生 `parent`。
55
+ */
56
+ export function larkInboundConversation(endpointKey, msg) {
57
+ return {
58
+ endpoint: { id: endpointKey, adapter: endpointKey.split('\0')[0] ?? endpointKey },
59
+ kind: resolveChatType(msg.chat_id, msg.chat_type),
60
+ id: msg.chat_id || 'unknown',
61
+ };
62
+ }
63
+ export function resolveSender(msg) {
64
+ return msg.sender?.sender_id?.open_id
65
+ || msg.sender?.sender_id?.user_id
66
+ || 'unknown';
67
+ }
68
+ export function generateMessageId(msg) {
69
+ return msg.message_id || `${msg.create_time ?? Date.now()}`;
70
+ }
71
+ /** Build inbound text for OutboundMessageService.receive. */
72
+ export function formatInboundContent(msg) {
73
+ if (!msg.content || !msg.message_type)
74
+ return '';
75
+ try {
76
+ const parsed = JSON.parse(msg.content);
77
+ switch (msg.message_type) {
78
+ case 'text':
79
+ return typeof parsed.text === 'string' ? parsed.text : '';
80
+ case 'image':
81
+ return '[image]';
82
+ case 'file': {
83
+ const name = typeof parsed.file_name === 'string' ? parsed.file_name : '';
84
+ return name ? `[file: ${name}]` : '[file]';
85
+ }
86
+ case 'audio':
87
+ return '[audio]';
88
+ case 'video':
89
+ return '[video]';
90
+ case 'sticker':
91
+ return '[sticker]';
92
+ case 'post':
93
+ case 'interactive':
94
+ return `[${msg.message_type}]`;
95
+ case 'rich_text':
96
+ return '[rich_text]';
97
+ default:
98
+ return `[${msg.message_type}]`;
99
+ }
100
+ }
101
+ catch {
102
+ return '[消息解析失败]';
103
+ }
104
+ }
105
+ /** 验签时间戳时效窗口(防重放)。 */
106
+ export const LARK_SIGNATURE_MAX_AGE_MS = 5 * 60_000;
107
+ export function verifySignature(encryptKey, timestamp, nonce, body, signature, nowMs = Date.now()) {
108
+ try {
109
+ // 时效窗口 ±5min,超出直接拒绝(timestamp 为秒)
110
+ const ts = Number(timestamp);
111
+ if (!Number.isFinite(ts) || Math.abs(nowMs - ts * 1000) > LARK_SIGNATURE_MAX_AGE_MS) {
112
+ return false;
113
+ }
114
+ const stringToSign = `${timestamp}${nonce}${encryptKey}${body}`;
115
+ const calculated = createHash('sha256').update(stringToSign).digest('hex');
116
+ const a = Buffer.from(calculated);
117
+ const b = Buffer.from(signature);
118
+ if (a.length !== b.length)
119
+ return false;
120
+ return timingSafeEqual(a, b);
121
+ }
122
+ catch {
123
+ return false;
124
+ }
125
+ }
126
+ /** timing-safe 比较 webhook verification token(长度不同快速失败)。 */
127
+ export function verifyToken(token, expected) {
128
+ try {
129
+ const a = Buffer.from(token);
130
+ const b = Buffer.from(expected);
131
+ if (a.length !== b.length)
132
+ return false;
133
+ return timingSafeEqual(a, b);
134
+ }
135
+ catch {
136
+ return false;
137
+ }
138
+ }
139
+ /**
140
+ * Wire-encode an already-rendered outbound payload into Lark im/v1 message body.
141
+ * Segment canonicalization is intentionally not done here.
142
+ */
143
+ export function formatOutboundBody(payload) {
144
+ if (typeof payload === 'string') {
145
+ return {
146
+ msg_type: 'text',
147
+ content: JSON.stringify({ text: payload }),
148
+ };
149
+ }
150
+ const items = Array.isArray(payload)
151
+ ? payload
152
+ : payload && typeof payload === 'object' && 'type' in payload
153
+ ? [payload]
154
+ : [];
155
+ const hasMarkdown = items.some((item) => typeof item !== 'string' && item.type === 'markdown');
156
+ if (items.length === 0) {
157
+ const text = payload == null
158
+ ? ''
159
+ : typeof payload === 'object'
160
+ ? JSON.stringify(payload)
161
+ : String(payload);
162
+ return {
163
+ msg_type: 'text',
164
+ content: JSON.stringify({ text }),
165
+ };
166
+ }
167
+ const textParts = [];
168
+ let media = null;
169
+ for (const item of items) {
170
+ if (typeof item === 'string') {
171
+ textParts.push(item);
172
+ continue;
173
+ }
174
+ const data = item.data ?? {};
175
+ switch (item.type) {
176
+ case 'text':
177
+ textParts.push(String(data.content ?? data.text ?? ''));
178
+ break;
179
+ case 'markdown':
180
+ textParts.push(String(data.content ?? data.text ?? ''));
181
+ break;
182
+ case 'at':
183
+ textParts.push(`<at user_id="${String(data.id ?? '')}">${String(data.name || data.id || '')}</at>`);
184
+ break;
185
+ case 'image':
186
+ if (!media) {
187
+ // image_key 唯一来源:canonical MediaRef kind=file(endpoint 上传物化产物或调用方既有平台引用)
188
+ const ref = data.media;
189
+ if (isMediaRef(ref) && ref.kind === 'file') {
190
+ media = {
191
+ msg_type: 'image',
192
+ content: JSON.stringify({ image_key: ref.value }),
193
+ };
194
+ }
195
+ else {
196
+ logger.warn(formatCompact({
197
+ op: 'lark_outbound_media_dropped',
198
+ type: 'image',
199
+ reason: isMediaRef(ref) ? 'unmaterialized_media' : 'missing_media_ref',
200
+ }));
201
+ }
202
+ }
203
+ break;
204
+ case 'file':
205
+ if (!media) {
206
+ const ref = data.media;
207
+ if (isMediaRef(ref) && ref.kind === 'file') {
208
+ media = {
209
+ msg_type: 'file',
210
+ content: JSON.stringify({ file_key: ref.value }),
211
+ };
212
+ }
213
+ else {
214
+ logger.warn(formatCompact({
215
+ op: 'lark_outbound_media_dropped',
216
+ type: 'file',
217
+ reason: isMediaRef(ref) ? 'unmaterialized_media' : 'missing_media_ref',
218
+ }));
219
+ }
220
+ }
221
+ break;
222
+ case 'card':
223
+ if (!media) {
224
+ media = {
225
+ msg_type: 'interactive',
226
+ content: JSON.stringify(data),
227
+ };
228
+ }
229
+ break;
230
+ default:
231
+ textParts.push(`[${item.type}]`);
232
+ }
233
+ }
234
+ if (media)
235
+ return media;
236
+ if (hasMarkdown) {
237
+ return {
238
+ msg_type: 'interactive',
239
+ content: JSON.stringify({
240
+ elements: [{
241
+ tag: 'div',
242
+ text: { tag: 'lark_md', content: textParts.join('') },
243
+ }],
244
+ }),
245
+ };
246
+ }
247
+ return {
248
+ msg_type: 'text',
249
+ content: JSON.stringify({ text: textParts.join('') }),
250
+ };
251
+ }
252
+ export function headerValue(headers, name) {
253
+ const value = headers[name] ?? headers[name.toLowerCase()];
254
+ if (Array.isArray(value))
255
+ return value[0] ?? '';
256
+ return value ?? '';
257
+ }
258
+ export async function readTextBody(request, options = {}) {
259
+ const limit = options.limit ?? 1_048_576;
260
+ const chunks = [];
261
+ let size = 0;
262
+ for await (const chunk of request) {
263
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
264
+ size += buffer.length;
265
+ if (size > limit) {
266
+ request.destroy();
267
+ throw new Error(`Request body exceeds ${limit} bytes`);
268
+ }
269
+ chunks.push(buffer);
270
+ }
271
+ return Buffer.concat(chunks).toString('utf8');
272
+ }
@@ -0,0 +1,14 @@
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 { type LarkMessage, type ResolvedLarkConfig } from './protocol.js';
7
+ export interface LarkWebhookHandler {
8
+ readonly config: ResolvedLarkConfig;
9
+ readonly isOpen: boolean;
10
+ admitPlatform(name: string, event: unknown): void;
11
+ admit(msg: LarkMessage): void;
12
+ }
13
+ export declare function registerLarkWebhookRoutes(http: HttpHost, handler: LarkWebhookHandler): HttpRouteRegistration[];
14
+ export declare function handleLarkWebhookRequest(request: IncomingMessage, response: ServerResponse, handler: LarkWebhookHandler): Promise<void>;
package/lib/webhook.js ADDED
@@ -0,0 +1,73 @@
1
+ import { formatCompact, getLogger } from '@zhin.js/logger';
2
+ import { headerValue, readTextBody, verifySignature, verifyToken, } from './protocol.js';
3
+ const logger = getLogger('lark');
4
+ export function registerLarkWebhookRoutes(http, handler) {
5
+ const path = handler.config.webhookPath;
6
+ return [
7
+ http.route('POST', path, async (request, response) => {
8
+ await handleLarkWebhookRequest(request, response, handler);
9
+ }, { summary: 'Lark/Feishu event webhook', tags: ['lark'] }),
10
+ ];
11
+ }
12
+ export async function handleLarkWebhookRequest(request, response, handler) {
13
+ try {
14
+ const rawBody = await readTextBody(request);
15
+ if (handler.config.verificationToken) {
16
+ const token = headerValue(request.headers, 'x-lark-request-token');
17
+ if (!verifyToken(token, handler.config.verificationToken)) {
18
+ logger.warn(formatCompact({ op: 'webhook', ok: false, error: 'invalid verification token' }));
19
+ response.writeHead(403, { 'Content-Type': 'text/plain' });
20
+ response.end('Forbidden');
21
+ return;
22
+ }
23
+ }
24
+ if (handler.config.encryptKey) {
25
+ const timestamp = headerValue(request.headers, 'x-lark-request-timestamp');
26
+ const nonce = headerValue(request.headers, 'x-lark-request-nonce');
27
+ const signature = headerValue(request.headers, 'x-lark-signature');
28
+ if (!timestamp
29
+ || !nonce
30
+ || !signature
31
+ || !verifySignature(handler.config.encryptKey, timestamp, nonce, rawBody, signature)) {
32
+ logger.warn(formatCompact({ op: 'webhook', ok: false, error: 'invalid signature' }));
33
+ response.writeHead(403, { 'Content-Type': 'text/plain' });
34
+ response.end('Forbidden');
35
+ return;
36
+ }
37
+ }
38
+ let event;
39
+ try {
40
+ event = JSON.parse(rawBody);
41
+ }
42
+ catch {
43
+ response.writeHead(200, { 'Content-Type': 'application/json' });
44
+ response.end(JSON.stringify({ code: 0, msg: 'success' }));
45
+ return;
46
+ }
47
+ if (handler.isOpen) {
48
+ const eventName = event.type === 'event_callback'
49
+ ? String(event.event?.type ?? event.type)
50
+ : String(event.type ?? 'event');
51
+ handler.admitPlatform(eventName, event);
52
+ }
53
+ if (event.type === 'url_verification') {
54
+ response.writeHead(200, { 'Content-Type': 'application/json' });
55
+ response.end(JSON.stringify({ challenge: event.challenge }));
56
+ return;
57
+ }
58
+ if (event.type === 'event_callback' && event.event?.message && handler.isOpen) {
59
+ const message = {
60
+ ...event.event.message,
61
+ sender: event.event.message.sender ?? event.event.sender,
62
+ };
63
+ handler.admit(message);
64
+ }
65
+ response.writeHead(200, { 'Content-Type': 'application/json' });
66
+ response.end(JSON.stringify({ code: 0, msg: 'success' }));
67
+ }
68
+ catch (error) {
69
+ logger.error('Webhook error:', error);
70
+ response.writeHead(500, { 'Content-Type': 'application/json' });
71
+ response.end(JSON.stringify({ code: -1, msg: 'Internal Server Error' }));
72
+ }
73
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@zhin.js/adapter-lark",
3
- "version": "1.0.97",
4
- "description": "Zhin.js adapter for Lark/Feishu (飞书)",
3
+ "version": "1.1.2",
4
+ "description": "Zhin.js Lark/Feishu (飞书) adapter for Plugin Runtime (HTTP webhook)",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
7
7
  "types": "./lib/index.d.ts",
@@ -34,41 +34,96 @@
34
34
  "directory": "plugins/adapters/lark"
35
35
  },
36
36
  "dependencies": {
37
- "axios": "^1.16.1"
38
- },
39
- "devDependencies": {
40
- "@types/koa": "^3.0.3",
41
- "@types/node": "^25.9.1",
42
- "typescript": "^6.0.3",
43
- "zhin.js": "1.0.93"
37
+ "@zhin.js/adapter": "1.1.14",
38
+ "@zhin.js/agent-feature": "1.1.1",
39
+ "@zhin.js/core": "1.1.37",
40
+ "@zhin.js/feature-kit": "1.1.1",
41
+ "@zhin.js/host-http": "1.1.1",
42
+ "@zhin.js/im-contract": "1.1.1",
43
+ "@zhin.js/logger": "1.1.1",
44
+ "@zhin.js/skill": "1.1.1",
45
+ "@zhin.js/tool": "1.1.1"
44
46
  },
45
47
  "peerDependencies": {
46
- "zhin.js": "1.0.93",
47
- "@zhin.js/host-router": "0.0.3"
48
+ "zod": "^4.0.0",
49
+ "@zhin.js/adapter": "^1.1.14",
50
+ "@zhin.js/command": "^1.1.1",
51
+ "@zhin.js/core": "^1.1.37",
52
+ "@zhin.js/host-http": "^1.1.1",
53
+ "@zhin.js/permission": "^1.1.1",
54
+ "zhin.js": "^1.1.2"
48
55
  },
49
56
  "peerDependenciesMeta": {
50
- "@zhin.js/host-router": {
51
- "optional": false
57
+ "@zhin.js/command": {
58
+ "optional": true
59
+ },
60
+ "zhin.js": {
61
+ "optional": true
62
+ },
63
+ "zod": {
64
+ "optional": true
52
65
  }
53
66
  },
67
+ "devDependencies": {
68
+ "@types/node": "^26.1.2",
69
+ "typescript": "^6.0.3",
70
+ "vitest": "^4.1.10",
71
+ "zod": "^4.4.3",
72
+ "zhin.js": "1.1.2"
73
+ },
54
74
  "files": [
75
+ "adapters",
76
+ "commands",
77
+ "plugin.js",
78
+ "schema.json",
55
79
  "src",
56
80
  "lib",
57
- "client",
58
- "dist",
59
- "skills",
60
- "plugin.yml",
81
+ "tools",
82
+ "agents",
61
83
  "README.md",
62
- "node",
63
- "CHANGELOG.md"
84
+ "CHANGELOG.md",
85
+ "PERMITS.md"
64
86
  ],
65
87
  "publishConfig": {
66
88
  "access": "public",
67
89
  "registry": "https://registry.npmjs.org"
68
90
  },
91
+ "engines": {
92
+ "node": "^20.19.0 || >=22.12.0"
93
+ },
94
+ "zhin": {
95
+ "protocol": 1,
96
+ "type": "plugin",
97
+ "entry": "./plugin.js",
98
+ "engine": "^1.0.0",
99
+ "runtime": "trusted",
100
+ "features": [
101
+ {
102
+ "package": "@zhin.js/adapter",
103
+ "api": "^1.0.0"
104
+ },
105
+ {
106
+ "package": "@zhin.js/command",
107
+ "api": "^1.0.0"
108
+ },
109
+ {
110
+ "package": "@zhin.js/tool",
111
+ "api": "^1.0.0"
112
+ },
113
+ {
114
+ "package": "@zhin.js/skill",
115
+ "api": "^1.0.0"
116
+ },
117
+ {
118
+ "package": "@zhin.js/agent-feature",
119
+ "api": "^1.0.0"
120
+ }
121
+ ],
122
+ "plugins": []
123
+ },
69
124
  "scripts": {
70
- "build": "pnpm build:node",
125
+ "build": "tsc",
71
126
  "clean": "rimraf lib",
72
- "build:node": "tsc"
127
+ "test": "NODE_OPTIONS=--experimental-strip-types vitest run --root ../../.. plugins/adapters/lark/tests"
73
128
  }
74
129
  }
package/plugin.js ADDED
@@ -0,0 +1,19 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { createEndpointRuntimeState } from 'zhin.js/adapter';
3
+ import { definePlugin } from 'zhin.js';
4
+ import { permissionHostToken } from '@zhin.js/permission';
5
+ import { checkLarkPlatformPermit } from "./lib/platform-permit.js";
6
+ import { larkRuntimeStateToken } from "./lib/lark-runtime-state.js";
7
+ export default definePlugin({
8
+ name: 'lark',
9
+ metadata: {
10
+ displayName: 'Lark/Feishu (飞书) Adapter',
11
+ },
12
+ setup(context) {
13
+ context.resources.provide(larkRuntimeStateToken, createEndpointRuntimeState());
14
+ if (context.resources.has(permissionHostToken)) {
15
+ const host = context.resources.use(permissionHostToken);
16
+ return host.registerPlatform('lark', checkLarkPlatformPermit);
17
+ }
18
+ },
19
+ });
package/schema.json ADDED
@@ -0,0 +1,110 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "type": "object",
4
+ "additionalProperties": false,
5
+ "properties": {
6
+ "webhookPath": {
7
+ "type": "string",
8
+ "default": "/lark/webhook"
9
+ },
10
+ "apiBaseUrl": {
11
+ "type": "string"
12
+ },
13
+ "isFeishu": {
14
+ "type": "boolean",
15
+ "default": true
16
+ },
17
+ "master": {
18
+ "type": [
19
+ "string",
20
+ "number"
21
+ ],
22
+ "description": "框架 master(Lark/Feishu open_id or user_id;AI/工具权限、endpoint 管理)。endpoints[i].master 可逐项覆盖"
23
+ },
24
+ "trusted": {
25
+ "type": "array",
26
+ "items": {
27
+ "type": [
28
+ "string",
29
+ "number"
30
+ ],
31
+ "description": "Trusted Lark/Feishu open_id or user_id"
32
+ },
33
+ "description": "框架 trusted 用户列表(弱于 master)。endpoints[i].trusted 可逐项追加"
34
+ },
35
+ "endpoints": {
36
+ "type": "array",
37
+ "minItems": 1,
38
+ "description": "多账号:一个插件实例挂多个 endpoint。每项与顶层字段同构(id 必填,其余覆盖顶层)",
39
+ "items": {
40
+ "type": "object",
41
+ "additionalProperties": false,
42
+ "properties": {
43
+ "master": {
44
+ "type": [
45
+ "string",
46
+ "number"
47
+ ],
48
+ "description": "本 endpoint 的框架 master(Lark/Feishu open_id or user_id);覆盖顶层 master"
49
+ },
50
+ "trusted": {
51
+ "type": "array",
52
+ "items": {
53
+ "type": [
54
+ "string",
55
+ "number"
56
+ ],
57
+ "description": "Trusted Lark/Feishu open_id or user_id"
58
+ },
59
+ "description": "本 endpoint 的 trusted 列表"
60
+ },
61
+ "appId": {
62
+ "type": "string",
63
+ "description": "Lark app ID"
64
+ },
65
+ "appSecret": {
66
+ "type": "string",
67
+ "description": "Lark app secret"
68
+ },
69
+ "encryptKey": {
70
+ "type": "string",
71
+ "description": "Lark encrypt key"
72
+ },
73
+ "verificationToken": {
74
+ "type": "string",
75
+ "description": "Lark verification token"
76
+ },
77
+ "webhookPath": {
78
+ "type": "string"
79
+ },
80
+ "apiBaseUrl": {
81
+ "type": "string"
82
+ },
83
+ "isFeishu": {
84
+ "type": "boolean"
85
+ },
86
+ "commandPrefix": {
87
+ "type": "string"
88
+ },
89
+ "id": {
90
+ "type": "string",
91
+ "description": "Lark bot name"
92
+ }
93
+ },
94
+ "required": [
95
+ "id",
96
+ "appId",
97
+ "appSecret"
98
+ ]
99
+ }
100
+ },
101
+ "commandPrefix": {
102
+ "type": "string",
103
+ "default": "",
104
+ "description": "命令前缀(默认 '' 无前缀,任意文本按命令匹配;如 '/' 要求 / 开头)。endpoints[i] 可逐项覆盖"
105
+ }
106
+ },
107
+ "required": [
108
+ "endpoints"
109
+ ]
110
+ }
package/src/client.ts ADDED
@@ -0,0 +1,16 @@
1
+ import { defineEndpointClient } from 'zhin.js/adapter';
2
+ import type { LarkClient } from './endpoint.js';
3
+ import type { LarkEventBody } from './protocol.js';
4
+
5
+ export type LarkClientEventMap = Record<string, LarkEventBody>;
6
+
7
+ declare module '@zhin.js/feature-kit' {
8
+ interface AdapterClientRegistry {
9
+ readonly lark: {
10
+ readonly client: LarkClient;
11
+ readonly events: LarkClientEventMap;
12
+ };
13
+ }
14
+ }
15
+
16
+ export const larkClient = defineEndpointClient<LarkClient, LarkClientEventMap>('lark');