@dsh-enhanced/lark-channel 0.1.3

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 (102) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +342 -0
  3. package/bin/dsh-lark-setup.js +8 -0
  4. package/bin/dsh-supervised-growth-setup.js +8 -0
  5. package/cordis.patch.yml +21 -0
  6. package/lib/adapter.d.ts +135 -0
  7. package/lib/adapter.d.ts.map +1 -0
  8. package/lib/adapter.js +965 -0
  9. package/lib/adapter.js.map +1 -0
  10. package/lib/approval.d.ts +26 -0
  11. package/lib/approval.d.ts.map +1 -0
  12. package/lib/approval.js +104 -0
  13. package/lib/approval.js.map +1 -0
  14. package/lib/config.d.ts +21 -0
  15. package/lib/config.d.ts.map +1 -0
  16. package/lib/config.js +56 -0
  17. package/lib/config.js.map +1 -0
  18. package/lib/group-thread.d.ts +11 -0
  19. package/lib/group-thread.d.ts.map +1 -0
  20. package/lib/group-thread.js +22 -0
  21. package/lib/group-thread.js.map +1 -0
  22. package/lib/index.d.ts +27 -0
  23. package/lib/index.d.ts.map +1 -0
  24. package/lib/index.js +30 -0
  25. package/lib/index.js.map +1 -0
  26. package/lib/launchd.d.ts +44 -0
  27. package/lib/launchd.d.ts.map +1 -0
  28. package/lib/launchd.js +205 -0
  29. package/lib/launchd.js.map +1 -0
  30. package/lib/model-picker.d.ts +24 -0
  31. package/lib/model-picker.d.ts.map +1 -0
  32. package/lib/model-picker.js +95 -0
  33. package/lib/model-picker.js.map +1 -0
  34. package/lib/normalize.d.ts +12 -0
  35. package/lib/normalize.d.ts.map +1 -0
  36. package/lib/normalize.js +77 -0
  37. package/lib/normalize.js.map +1 -0
  38. package/lib/permission-picker.d.ts +29 -0
  39. package/lib/permission-picker.d.ts.map +1 -0
  40. package/lib/permission-picker.js +145 -0
  41. package/lib/permission-picker.js.map +1 -0
  42. package/lib/progress.d.ts +15 -0
  43. package/lib/progress.d.ts.map +1 -0
  44. package/lib/progress.js +137 -0
  45. package/lib/progress.js.map +1 -0
  46. package/lib/resident.d.ts +13 -0
  47. package/lib/resident.d.ts.map +1 -0
  48. package/lib/resident.js +41 -0
  49. package/lib/resident.js.map +1 -0
  50. package/lib/sdk.d.ts +131 -0
  51. package/lib/sdk.d.ts.map +1 -0
  52. package/lib/sdk.js +945 -0
  53. package/lib/sdk.js.map +1 -0
  54. package/lib/service.d.ts +30 -0
  55. package/lib/service.d.ts.map +1 -0
  56. package/lib/service.js +157 -0
  57. package/lib/service.js.map +1 -0
  58. package/lib/setup-materialization.d.ts +15 -0
  59. package/lib/setup-materialization.d.ts.map +1 -0
  60. package/lib/setup-materialization.js +221 -0
  61. package/lib/setup-materialization.js.map +1 -0
  62. package/lib/setup-profile.d.ts +23 -0
  63. package/lib/setup-profile.d.ts.map +1 -0
  64. package/lib/setup-profile.js +1126 -0
  65. package/lib/setup-profile.js.map +1 -0
  66. package/lib/setup.d.ts +206 -0
  67. package/lib/setup.d.ts.map +1 -0
  68. package/lib/setup.js +2143 -0
  69. package/lib/setup.js.map +1 -0
  70. package/lib/supervised-growth-profile.d.ts +74 -0
  71. package/lib/supervised-growth-profile.d.ts.map +1 -0
  72. package/lib/supervised-growth-profile.js +485 -0
  73. package/lib/supervised-growth-profile.js.map +1 -0
  74. package/lib/supervised-growth-setup.d.ts +85 -0
  75. package/lib/supervised-growth-setup.d.ts.map +1 -0
  76. package/lib/supervised-growth-setup.js +371 -0
  77. package/lib/supervised-growth-setup.js.map +1 -0
  78. package/lib/systemd.d.ts +39 -0
  79. package/lib/systemd.d.ts.map +1 -0
  80. package/lib/systemd.js +142 -0
  81. package/lib/systemd.js.map +1 -0
  82. package/lib/tool-approval.d.ts +22 -0
  83. package/lib/tool-approval.d.ts.map +1 -0
  84. package/lib/tool-approval.js +107 -0
  85. package/lib/tool-approval.js.map +1 -0
  86. package/lib/types.d.ts +158 -0
  87. package/lib/types.d.ts.map +1 -0
  88. package/lib/types.js +11 -0
  89. package/lib/types.js.map +1 -0
  90. package/lib/version.d.ts +2 -0
  91. package/lib/version.d.ts.map +1 -0
  92. package/lib/version.js +2 -0
  93. package/lib/version.js.map +1 -0
  94. package/lib/windows-task.d.ts +39 -0
  95. package/lib/windows-task.d.ts.map +1 -0
  96. package/lib/windows-task.js +117 -0
  97. package/lib/windows-task.js.map +1 -0
  98. package/lib/ws-card-callback.d.ts +7 -0
  99. package/lib/ws-card-callback.d.ts.map +1 -0
  100. package/lib/ws-card-callback.js +51 -0
  101. package/lib/ws-card-callback.js.map +1 -0
  102. package/package.json +96 -0
package/lib/sdk.js ADDED
@@ -0,0 +1,945 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { Readable } from 'node:stream';
3
+ import { AppType, Client, defaultHttpInstance, Domain, EventDispatcher, LoggerLevel, WSClient, normalize, normalizeCardAction, } from '@larksuiteoapi/node-sdk';
4
+ import { LarkTransportError, } from './types.js';
5
+ import { installLarkCardCallbackBridge } from './ws-card-callback.js';
6
+ const LARK_PROGRESS_API = '/open-apis/im/v1/message_cot';
7
+ const LARK_MESSAGE_RESOURCE_API = '/open-apis/im/v1/messages';
8
+ const LARK_ERROR_BODY_MAX_BYTES = 16 * 1_024;
9
+ const larkResourceIdentifier = /^[A-Za-z0-9][A-Za-z0-9._@-]{0,255}$/u;
10
+ class LarkImageResponseValidationError extends Error {
11
+ }
12
+ function imageResponseValidationError(message) {
13
+ return new LarkImageResponseValidationError(message);
14
+ }
15
+ function assertResourceIdentifier(value, field) {
16
+ if (!larkResourceIdentifier.test(value)) {
17
+ throw new Error(`lark-channel: invalid ${field} identifier`);
18
+ }
19
+ return value;
20
+ }
21
+ export function createLarkImageResourceRequest(messageId, fileKey, options) {
22
+ const message = assertResourceIdentifier(messageId, 'message');
23
+ const resource = assertResourceIdentifier(fileKey, 'image resource');
24
+ if (!Number.isSafeInteger(options.timeoutMs) || options.timeoutMs < 1
25
+ || !Number.isSafeInteger(options.maxBytes) || options.maxBytes < 1) {
26
+ throw new Error('lark-channel: invalid image resource request limits');
27
+ }
28
+ return {
29
+ method: 'GET',
30
+ url: `${LARK_MESSAGE_RESOURCE_API}/${encodeURIComponent(message)}/resources/${encodeURIComponent(resource)}`,
31
+ params: { type: 'image' },
32
+ responseType: 'stream',
33
+ timeout: options.timeoutMs,
34
+ signal: options.signal,
35
+ maxRedirects: 0,
36
+ maxContentLength: options.maxBytes,
37
+ maxBodyLength: options.maxBytes,
38
+ $return_headers: true,
39
+ };
40
+ }
41
+ function responseHeader(headers, name) {
42
+ if ('get' in headers && typeof headers.get === 'function')
43
+ return headers.get(name);
44
+ const record = headers;
45
+ const expected = name.toLowerCase();
46
+ for (const [header, value] of Object.entries(record)) {
47
+ if (header.toLowerCase() === expected)
48
+ return value;
49
+ }
50
+ return undefined;
51
+ }
52
+ function declaredContentLength(headers) {
53
+ const value = responseHeader(headers, 'content-length');
54
+ if (value === undefined)
55
+ return undefined;
56
+ const text = Array.isArray(value) ? value[0] : value;
57
+ if ((typeof text !== 'string' && typeof text !== 'number') || !/^\d+$/u.test(String(text))) {
58
+ throw imageResponseValidationError('lark-channel: invalid image resource content length');
59
+ }
60
+ const length = Number(text);
61
+ if (!Number.isSafeInteger(length)) {
62
+ throw imageResponseValidationError('lark-channel: invalid image resource content length');
63
+ }
64
+ return length;
65
+ }
66
+ function imageMediaType(data) {
67
+ if (data.length >= 8
68
+ && data[0] === 0x89 && data[1] === 0x50 && data[2] === 0x4e && data[3] === 0x47
69
+ && data[4] === 0x0d && data[5] === 0x0a && data[6] === 0x1a && data[7] === 0x0a)
70
+ return 'image/png';
71
+ if (data.length >= 3 && data[0] === 0xff && data[1] === 0xd8 && data[2] === 0xff)
72
+ return 'image/jpeg';
73
+ if (data.length >= 6) {
74
+ const signature = Buffer.from(data.subarray(0, 6)).toString('ascii');
75
+ if (signature === 'GIF87a' || signature === 'GIF89a')
76
+ return 'image/gif';
77
+ }
78
+ if (data.length >= 12
79
+ && Buffer.from(data.subarray(0, 4)).toString('ascii') === 'RIFF'
80
+ && Buffer.from(data.subarray(8, 12)).toString('ascii') === 'WEBP')
81
+ return 'image/webp';
82
+ return undefined;
83
+ }
84
+ function declaredImageMediaType(headers) {
85
+ const value = responseHeader(headers, 'content-type');
86
+ if (value === undefined)
87
+ return undefined;
88
+ if (typeof value !== 'string') {
89
+ throw imageResponseValidationError('lark-channel: invalid image resource content type');
90
+ }
91
+ const mediaType = value.split(';', 1)[0].trim().toLowerCase();
92
+ if (mediaType === 'application/octet-stream')
93
+ return undefined;
94
+ if (mediaType === 'image/gif' || mediaType === 'image/jpeg'
95
+ || mediaType === 'image/png' || mediaType === 'image/webp')
96
+ return mediaType;
97
+ throw imageResponseValidationError('lark-channel: unsupported image resource content type');
98
+ }
99
+ function abortReason(signal) {
100
+ return signal.reason instanceof Error ? signal.reason : new Error('lark-channel: image resource request aborted');
101
+ }
102
+ function boundedHttpOptions(options, hardTimeoutMs, lifecycleSignal) {
103
+ const source = options;
104
+ const requestedTimeout = source?.timeout;
105
+ const timeout = typeof requestedTimeout === 'number' && Number.isFinite(requestedTimeout) && requestedTimeout > 0
106
+ ? Math.min(requestedTimeout, hardTimeoutMs)
107
+ : hardTimeoutMs;
108
+ const hardTimeoutSignal = AbortSignal.timeout(hardTimeoutMs);
109
+ const signal = source?.signal === undefined
110
+ ? AbortSignal.any([hardTimeoutSignal, lifecycleSignal])
111
+ : AbortSignal.any([source.signal, hardTimeoutSignal, lifecycleSignal]);
112
+ return { ...source, timeout, signal };
113
+ }
114
+ function asSdkHttpResult(request) {
115
+ // The SDK's HttpInstance contract describes its response-interceptor output
116
+ // as R. Axios 1.19 leaves that conditional generic unresolved for arbitrary
117
+ // R even though defaultHttpInstance installs exactly that interceptor.
118
+ return request;
119
+ }
120
+ /**
121
+ * The SDK's token manager calls `httpInstance.post()` before the resource
122
+ * request exists, so request-local Axios limits cannot cover a cache miss.
123
+ * Keep the dedicated image client's entire HTTP stack bounded instead.
124
+ */
125
+ function boundedImageHttpInstance(hardTimeoutMs, lifecycleSignal) {
126
+ if (!Number.isSafeInteger(hardTimeoutMs) || hardTimeoutMs < 1) {
127
+ throw new Error('lark-channel: invalid image HTTP timeout');
128
+ }
129
+ return {
130
+ request(options) {
131
+ return asSdkHttpResult(defaultHttpInstance.request(boundedHttpOptions(options, hardTimeoutMs, lifecycleSignal)));
132
+ },
133
+ get(url, options) {
134
+ return asSdkHttpResult(defaultHttpInstance.get(url, boundedHttpOptions(options, hardTimeoutMs, lifecycleSignal)));
135
+ },
136
+ delete(url, options) {
137
+ return asSdkHttpResult(defaultHttpInstance.delete(url, boundedHttpOptions(options, hardTimeoutMs, lifecycleSignal)));
138
+ },
139
+ head(url, options) {
140
+ return asSdkHttpResult(defaultHttpInstance.head(url, boundedHttpOptions(options, hardTimeoutMs, lifecycleSignal)));
141
+ },
142
+ options(url, options) {
143
+ return asSdkHttpResult(defaultHttpInstance.options(url, boundedHttpOptions(options, hardTimeoutMs, lifecycleSignal)));
144
+ },
145
+ post(url, data, options) {
146
+ return asSdkHttpResult(defaultHttpInstance.post(url, data, boundedHttpOptions(options, hardTimeoutMs, lifecycleSignal)));
147
+ },
148
+ put(url, data, options) {
149
+ return asSdkHttpResult(defaultHttpInstance.put(url, data, boundedHttpOptions(options, hardTimeoutMs, lifecycleSignal)));
150
+ },
151
+ patch(url, data, options) {
152
+ return asSdkHttpResult(defaultHttpInstance.patch(url, data, boundedHttpOptions(options, hardTimeoutMs, lifecycleSignal)));
153
+ },
154
+ };
155
+ }
156
+ function awaitImageResourceRequest(signal, request) {
157
+ return new Promise((resolve, reject) => {
158
+ let settled = false;
159
+ const finish = (complete) => {
160
+ if (settled)
161
+ return false;
162
+ settled = true;
163
+ signal.removeEventListener('abort', abort);
164
+ complete();
165
+ return true;
166
+ };
167
+ const abort = () => { finish(() => reject(abortReason(signal))); };
168
+ signal.addEventListener('abort', abort, { once: true });
169
+ if (signal.aborted) {
170
+ abort();
171
+ return;
172
+ }
173
+ let pending;
174
+ try {
175
+ pending = request();
176
+ }
177
+ catch (error) {
178
+ finish(() => reject(error));
179
+ return;
180
+ }
181
+ void pending.then(response => {
182
+ if (!finish(() => resolve(response)) && response.data instanceof Readable)
183
+ response.data.destroy();
184
+ }, error => {
185
+ // The outer abort is authoritative. This rejection handler intentionally
186
+ // consumes a later Axios/token failure so credential-bearing request
187
+ // metadata can never surface as an unhandled rejection.
188
+ finish(() => reject(error));
189
+ });
190
+ });
191
+ }
192
+ export async function readLarkImageResourceResponse(response, options) {
193
+ if (!Number.isSafeInteger(options.maxBytes) || options.maxBytes < 1) {
194
+ response.data.destroy();
195
+ throw imageResponseValidationError('lark-channel: invalid image resource byte limit');
196
+ }
197
+ if (options.signal.aborted) {
198
+ response.data.destroy();
199
+ throw abortReason(options.signal);
200
+ }
201
+ let declaredLength;
202
+ let declaredType;
203
+ try {
204
+ declaredLength = declaredContentLength(response.headers);
205
+ declaredType = declaredImageMediaType(response.headers);
206
+ }
207
+ catch (error) {
208
+ response.data.destroy();
209
+ throw error;
210
+ }
211
+ if (declaredLength !== undefined && declaredLength > options.maxBytes) {
212
+ response.data.destroy();
213
+ throw imageResponseValidationError('lark-channel: image resource exceeds its byte limit');
214
+ }
215
+ const chunks = [];
216
+ let bytes = 0;
217
+ const abort = () => response.data.destroy(abortReason(options.signal));
218
+ options.signal.addEventListener('abort', abort, { once: true });
219
+ try {
220
+ for await (const value of response.data) {
221
+ if (options.signal.aborted)
222
+ throw abortReason(options.signal);
223
+ if (!(value instanceof Uint8Array)) {
224
+ throw imageResponseValidationError('lark-channel: invalid image resource stream chunk');
225
+ }
226
+ bytes += value.byteLength;
227
+ if (bytes > options.maxBytes) {
228
+ response.data.destroy();
229
+ throw imageResponseValidationError('lark-channel: image resource exceeds its byte limit');
230
+ }
231
+ chunks.push(Buffer.from(value.buffer, value.byteOffset, value.byteLength));
232
+ }
233
+ }
234
+ catch (error) {
235
+ if (options.signal.aborted)
236
+ throw abortReason(options.signal);
237
+ throw error;
238
+ }
239
+ finally {
240
+ options.signal.removeEventListener('abort', abort);
241
+ }
242
+ if (options.signal.aborted)
243
+ throw abortReason(options.signal);
244
+ if (declaredLength !== undefined && declaredLength !== bytes) {
245
+ throw new Error('lark-channel: image resource length does not match its response header');
246
+ }
247
+ const data = new Uint8Array(Buffer.concat(chunks, bytes));
248
+ const detectedType = imageMediaType(data);
249
+ if (detectedType === undefined) {
250
+ throw imageResponseValidationError('lark-channel: image resource has an unsupported byte signature');
251
+ }
252
+ if (declaredType !== undefined && declaredType !== detectedType) {
253
+ throw imageResponseValidationError('lark-channel: image resource content type does not match its bytes');
254
+ }
255
+ if (options.signal.aborted)
256
+ throw abortReason(options.signal);
257
+ return { data, mediaType: detectedType };
258
+ }
259
+ /** Provider-safe UTF-8 budget for the complete serialized approval card. */
260
+ export const LARK_APPROVAL_CARD_MAX_BYTES = 28 * 1_024;
261
+ function providerFailureCode(code) {
262
+ if (code === 99991400 || code === 230020)
263
+ return 'rate_limited';
264
+ if (code === 230027 || code === 234009 || code === 99991663
265
+ || code === 99991672 || code === 99991676 || code === 99991679)
266
+ return 'permission_denied';
267
+ if (code === 200530)
268
+ return 'format_error';
269
+ return undefined;
270
+ }
271
+ const silentLogger = {
272
+ error() { },
273
+ warn() { },
274
+ info() { },
275
+ debug() { },
276
+ trace() { },
277
+ };
278
+ const MAX_RETRY_AFTER_MS = 24 * 60 * 60 * 1_000;
279
+ function retryAfter(headers) {
280
+ const value = headers === undefined ? undefined : responseHeader(headers, 'retry-after');
281
+ const delaySeconds = typeof value === 'number' && Number.isSafeInteger(value)
282
+ ? value
283
+ : typeof value === 'string' && /^\d+$/.test(value.trim())
284
+ ? Number(value.trim())
285
+ : Number.NaN;
286
+ const delayMs = delaySeconds * 1_000;
287
+ return Number.isSafeInteger(delayMs) && delayMs >= 0 && delayMs <= MAX_RETRY_AFTER_MS
288
+ ? delayMs
289
+ : undefined;
290
+ }
291
+ export function classifyLarkSdkFailure(error) {
292
+ const shape = error !== null && typeof error === 'object' ? error : {};
293
+ const status = typeof shape.response?.status === 'number' ? shape.response.status : undefined;
294
+ if (status === 429) {
295
+ const delay = retryAfter(shape.response?.headers);
296
+ return { code: 'rate_limited', ...(delay === undefined ? {} : { retryAfterMs: delay }) };
297
+ }
298
+ const responseData = shape.response?.data;
299
+ const responseCode = responseData !== null && typeof responseData === 'object' && !Array.isArray(responseData)
300
+ ? responseData.code
301
+ : undefined;
302
+ const providerCode = providerFailureCode(responseCode) ?? providerFailureCode(shape.code);
303
+ if (providerCode !== undefined) {
304
+ const delay = providerCode === 'rate_limited' ? retryAfter(shape.response?.headers) : undefined;
305
+ return { code: providerCode, ...(delay === undefined ? {} : { retryAfterMs: delay }) };
306
+ }
307
+ if (status === 401 || status === 403)
308
+ return { code: 'permission_denied' };
309
+ if (status !== undefined && status >= 400 && status < 500)
310
+ return { code: 'format_error' };
311
+ if (shape.code === 'ETIMEDOUT' || shape.code === 'ECONNABORTED')
312
+ return { code: 'send_timeout' };
313
+ return { code: 'unknown' };
314
+ }
315
+ async function streamedProviderFailureCode(stream, signal) {
316
+ if (signal.aborted) {
317
+ stream.destroy();
318
+ return undefined;
319
+ }
320
+ const chunks = [];
321
+ let bytes = 0;
322
+ let exceeded = false;
323
+ const abort = () => stream.destroy();
324
+ signal.addEventListener('abort', abort, { once: true });
325
+ try {
326
+ for await (const value of stream) {
327
+ if (!(value instanceof Uint8Array)) {
328
+ stream.destroy();
329
+ return undefined;
330
+ }
331
+ bytes += value.byteLength;
332
+ if (bytes > LARK_ERROR_BODY_MAX_BYTES) {
333
+ exceeded = true;
334
+ stream.destroy();
335
+ break;
336
+ }
337
+ chunks.push(Buffer.from(value.buffer, value.byteOffset, value.byteLength));
338
+ }
339
+ }
340
+ catch {
341
+ return undefined;
342
+ }
343
+ finally {
344
+ signal.removeEventListener('abort', abort);
345
+ }
346
+ if (exceeded || signal.aborted)
347
+ return undefined;
348
+ try {
349
+ const value = JSON.parse(Buffer.concat(chunks, bytes).toString('utf8'));
350
+ return value !== null && typeof value === 'object' && !Array.isArray(value)
351
+ ? value.code
352
+ : undefined;
353
+ }
354
+ catch {
355
+ return undefined;
356
+ }
357
+ }
358
+ export async function classifyLarkImageSdkFailure(error, signal) {
359
+ const shape = error !== null && typeof error === 'object' ? error : {};
360
+ const stream = shape.response?.data instanceof Readable ? shape.response.data : undefined;
361
+ if (stream === undefined)
362
+ return classifyLarkSdkFailure(error);
363
+ const status = typeof shape.response?.status === 'number' ? shape.response.status : undefined;
364
+ if (status !== 400) {
365
+ stream.destroy();
366
+ return classifyLarkSdkFailure(error);
367
+ }
368
+ const code = await streamedProviderFailureCode(stream, signal);
369
+ const classified = providerFailureCode(code);
370
+ if (classified === undefined)
371
+ return classifyLarkSdkFailure(error);
372
+ const delay = classified === 'rate_limited' ? retryAfter(shape.response?.headers) : undefined;
373
+ return { code: classified, ...(delay === undefined ? {} : { retryAfterMs: delay }) };
374
+ }
375
+ export function larkRequestUuid(idempotencyKey) {
376
+ return createHash('sha256').update(idempotencyKey).digest('hex').slice(0, 32);
377
+ }
378
+ export function createLarkProgressRequest(chatId, options) {
379
+ return {
380
+ method: 'POST',
381
+ url: `${LARK_PROGRESS_API}?receive_id_type=chat_id`,
382
+ data: {
383
+ receive_id: chatId,
384
+ origin_message_id: options.replyTo,
385
+ cot_hidden: options.hidden,
386
+ enable_badge: false,
387
+ update_feed_rank: false,
388
+ },
389
+ };
390
+ }
391
+ export function writeLarkProgressRequest(handle, events) {
392
+ return {
393
+ method: 'PUT',
394
+ url: LARK_PROGRESS_API,
395
+ data: {
396
+ cot_id: handle.cotId,
397
+ message_id: handle.messageId,
398
+ events: events.map(event => ({
399
+ event_type: event.eventType,
400
+ content: event.content,
401
+ timestamp: event.timestamp,
402
+ })),
403
+ },
404
+ };
405
+ }
406
+ export const LARK_MODEL_PICKER_CONTROLS = Object.freeze({
407
+ provider: 'model_provider',
408
+ model: 'model_route',
409
+ effort: 'model_effort',
410
+ confirm: 'model_confirm',
411
+ });
412
+ export const LARK_PERMISSION_PICKER_CONTROLS = Object.freeze({
413
+ ask: 'permission_ask',
414
+ auto: 'permission_auto',
415
+ full: 'permission_full',
416
+ });
417
+ export function renderLarkMessage(input) {
418
+ if ('text' in input)
419
+ return { msgType: 'text', content: JSON.stringify({ text: input.text }) };
420
+ if ('permissionPicker' in input) {
421
+ const picker = input.permissionPicker;
422
+ const level = (key, label, description) => {
423
+ const current = picker.current === key;
424
+ const value = picker.callbackValues[key];
425
+ const full = key === 'full';
426
+ return {
427
+ tag: 'interactive_container',
428
+ behaviors: [],
429
+ width: 'fill',
430
+ corner_radius: '8px',
431
+ has_border: true,
432
+ border_color: full ? 'orange-200' : current ? 'blue-200' : 'grey-200',
433
+ background_style: full ? 'orange-50' : current ? 'blue-50' : 'grey-50',
434
+ padding: '12px 12px 12px 12px',
435
+ direction: 'vertical',
436
+ vertical_spacing: '6px',
437
+ margin: '0px 0px 8px 0px',
438
+ elements: [
439
+ { tag: 'div', text: { tag: 'plain_text', content: `${current ? '✓ ' : ''}${label}` } },
440
+ { tag: 'div', text: { tag: 'plain_text', content: description } },
441
+ {
442
+ tag: 'button',
443
+ name: LARK_PERMISSION_PICKER_CONTROLS[key],
444
+ type: full ? 'danger' : current ? 'primary' : 'default',
445
+ width: 'fill',
446
+ text: { tag: 'plain_text', content: current ? '✓ 当前档位' : '选择此档位' },
447
+ value,
448
+ behaviors: [{ type: 'callback', value }],
449
+ ...(full ? { confirm: {
450
+ title: { tag: 'plain_text', content: '确认开启完全访问权限?' },
451
+ text: { tag: 'plain_text', content: '开启后可访问互联网和电脑上的任何文件,并关闭逐次审批。' },
452
+ } } : {}),
453
+ },
454
+ ],
455
+ };
456
+ };
457
+ return {
458
+ msgType: 'interactive',
459
+ content: JSON.stringify({
460
+ schema: '2.0',
461
+ config: { enable_forward_interaction: false },
462
+ header: {
463
+ template: 'blue',
464
+ title: { tag: 'plain_text', content: picker.title },
465
+ subtitle: { tag: 'plain_text', content: picker.body },
466
+ },
467
+ body: { padding: '12px 12px 12px 12px', elements: [
468
+ level('ask', '请求批准(ask)', '编辑外部文件和使用互联网时始终询问'),
469
+ level('auto', '帮我批准(auto)', '仅对检测到的风险操作请求批准'),
470
+ level('full', '完全访问权限(full)', '不受限制地访问互联网和电脑上的任何文件,并关闭逐次审批'),
471
+ ] },
472
+ }),
473
+ };
474
+ }
475
+ if ('modelPicker' in input) {
476
+ const picker = input.modelPicker;
477
+ const callback = (action) => picker.callbackValues[action];
478
+ const select = (name, placeholder, options, initial, action) => {
479
+ const value = callback(action);
480
+ // Lark resolves `initial_option` against the option's displayed text, not its
481
+ // callback value, so a route-shaped or synthetic initial value never matches and
482
+ // the client silently falls back to the first option. Address the selection by its
483
+ // 1-based `initial_index`, and only add the textual form when that label identifies
484
+ // exactly one option, because `initial_option` overrides `initial_index`. An initial
485
+ // value absent from `options` preselects nothing.
486
+ const selected = initial === undefined
487
+ ? -1
488
+ : options.findIndex(option => option.value === initial);
489
+ const label = selected < 0 ? undefined : options[selected].label;
490
+ const unambiguous = label !== undefined
491
+ && options.filter(option => option.label === label).length === 1;
492
+ return {
493
+ tag: 'select_static',
494
+ name,
495
+ width: 'fill',
496
+ placeholder: { tag: 'plain_text', content: placeholder },
497
+ options: options.map(option => ({
498
+ text: { tag: 'plain_text', content: option.label },
499
+ value: option.value,
500
+ })),
501
+ value,
502
+ behaviors: [{ type: 'callback', value }],
503
+ ...(selected < 0 ? {} : { initial_index: selected + 1 }),
504
+ ...(unambiguous ? { initial_option: label } : {}),
505
+ };
506
+ };
507
+ const confirmValue = callback('confirm');
508
+ // Layout follows the card style guide: header carries "what this is" (title + subtitle + icon),
509
+ // each of the three selects sits in its own bordered surface so the groups read as blocks
510
+ // instead of a flat run of markdown labels, and the single primary button is the only focus.
511
+ const field = (label, hint, control, last = false) => ({
512
+ tag: 'interactive_container',
513
+ behaviors: [],
514
+ width: 'fill',
515
+ corner_radius: '8px',
516
+ has_border: true,
517
+ border_color: 'blue-100',
518
+ background_style: 'blue-50',
519
+ padding: '12px 12px 12px 12px',
520
+ direction: 'vertical',
521
+ vertical_spacing: '4px',
522
+ // A trailing container keeps no bottom margin, so the card does not end on dead space.
523
+ margin: last ? '0px 0px 12px 0px' : '0px 0px 8px 0px',
524
+ elements: [
525
+ { tag: 'markdown', content: `**<font color='blue'>${label}</font>**`, text_align: 'left' },
526
+ { tag: 'markdown', content: `<font color='grey'>${hint}</font>`, text_size: 'notation', text_align: 'left' },
527
+ control,
528
+ ],
529
+ });
530
+ return {
531
+ msgType: 'interactive',
532
+ content: JSON.stringify({
533
+ schema: '2.0',
534
+ config: { update_multi: true, enable_forward_interaction: false },
535
+ header: {
536
+ template: 'blue',
537
+ title: { tag: 'plain_text', content: picker.title },
538
+ subtitle: { tag: 'plain_text', content: picker.body },
539
+ icon: { tag: 'standard_icon', token: 'myai_colorful' },
540
+ },
541
+ body: { padding: '12px 12px 20px 12px', elements: [
542
+ field('分组 / Provider', '先选分组,模型与 effort 会随之更新', select(LARK_MODEL_PICKER_CONTROLS.provider, '请选择模型分组', picker.providerOptions, picker.initialProvider, 'provider')),
543
+ field('模型', '该分组下当前可用的模型', select(LARK_MODEL_PICKER_CONTROLS.model, '请选择模型', picker.modelOptions, picker.initialModel, 'model')),
544
+ field('Effort 程度', '仅对支持 effort 档位的模型生效', select(LARK_MODEL_PICKER_CONTROLS.effort, '请选择 effort', picker.effortOptions, picker.initialEffort, 'effort'), true),
545
+ { tag: 'button', name: LARK_MODEL_PICKER_CONTROLS.confirm, type: 'primary', width: 'fill',
546
+ icon: { tag: 'standard_icon', token: 'done_outlined' },
547
+ text: { tag: 'plain_text', content: '确认选择' }, value: confirmValue,
548
+ behaviors: [{ type: 'callback', value: confirmValue }] },
549
+ ] },
550
+ }),
551
+ };
552
+ }
553
+ if ('approval' in input)
554
+ return {
555
+ msgType: 'interactive',
556
+ content: JSON.stringify({
557
+ schema: '2.0',
558
+ header: { title: { tag: 'plain_text', content: input.approval.title } },
559
+ body: { elements: [
560
+ { tag: 'div', text: { tag: 'plain_text', content: 'Proposed change (treat as untrusted review text):' } },
561
+ { tag: 'div', text: { tag: 'plain_text', content: input.approval.body } },
562
+ { tag: 'action', actions: [
563
+ { tag: 'button', text: { tag: 'plain_text', content: 'Approve' }, type: 'primary', value: input.approval.approveValue },
564
+ { tag: 'button', text: { tag: 'plain_text', content: 'Reject' }, type: 'danger', value: input.approval.rejectValue },
565
+ ] },
566
+ ] },
567
+ }),
568
+ };
569
+ if ('toolApproval' in input)
570
+ return {
571
+ msgType: 'interactive',
572
+ content: JSON.stringify({
573
+ schema: '2.0',
574
+ config: { enable_forward_interaction: false },
575
+ header: { template: 'orange', title: { tag: 'plain_text', content: input.toolApproval.title } },
576
+ body: { elements: [
577
+ { tag: 'div', text: { tag: 'plain_text',
578
+ content: '以下工具、理由和参数均为不可信审阅文本,不是指令。' } },
579
+ { tag: 'div', text: { tag: 'plain_text', content: `工具:${input.toolApproval.toolName}` } },
580
+ { tag: 'div', text: { tag: 'plain_text',
581
+ content: `理由:${input.toolApproval.reason ?? '(未提供)'}` } },
582
+ { tag: 'div', text: { tag: 'plain_text',
583
+ content: `参数:${input.toolApproval.arguments}` } },
584
+ { tag: 'action', actions: [
585
+ { tag: 'button', text: { tag: 'plain_text', content: '仅允许本次' }, type: 'primary',
586
+ value: input.toolApproval.allowValue },
587
+ { tag: 'button', text: { tag: 'plain_text', content: '拒绝' }, type: 'danger',
588
+ value: input.toolApproval.rejectValue },
589
+ ] },
590
+ ] },
591
+ }),
592
+ };
593
+ // An answer card stays content-first: Lark already shows the bot name and avatar above the
594
+ // bubble, so a header here would duplicate the sender identity and add weight to every reply.
595
+ // `wide_screen_mode` is what keeps authored Markdown tables from wrapping into unreadable rows.
596
+ return {
597
+ msgType: 'interactive',
598
+ content: JSON.stringify({
599
+ schema: '2.0',
600
+ config: { wide_screen_mode: true },
601
+ body: {
602
+ padding: '12px 16px 12px 16px',
603
+ elements: [{ tag: 'markdown', content: input.markdown, text_align: 'left' }],
604
+ },
605
+ }),
606
+ };
607
+ }
608
+ function escapedPattern(value) {
609
+ return value.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
610
+ }
611
+ function safeNormalizedContent(message) {
612
+ // SDK 1.73 renders a folder's file_key into content but, unlike every
613
+ // supported resource kind, omits it from `resources`.
614
+ if (message.rawContentType === 'folder')
615
+ return '[Folder attachment]';
616
+ let content = message.content;
617
+ if (message.resources.length === 1 && message.resources[0]?.type === 'image'
618
+ && content.trim() === `![image](${message.resources[0].fileKey})`)
619
+ return '';
620
+ for (const resource of message.resources) {
621
+ const key = resource.fileKey;
622
+ if (resource.type === 'image') {
623
+ content = content.replaceAll(`![image](${key})`, '[Image attachment]');
624
+ }
625
+ else {
626
+ const tag = resource.type;
627
+ content = content.replace(new RegExp(`<${tag}\\b[^>]*\\bkey="${escapedPattern(key)}"[^>]*/>`, 'gu'), `[${tag[0].toUpperCase()}${tag.slice(1)} attachment]`);
628
+ }
629
+ // Defense in depth for future SDK renderings: a provider capability key
630
+ // is never user-facing text and must not cross into the model prompt.
631
+ content = content.replaceAll(key, '[redacted-resource]');
632
+ }
633
+ return content.trim();
634
+ }
635
+ export function toSafeLarkMessage(message) {
636
+ return {
637
+ messageId: message.messageId,
638
+ chatId: message.chatId,
639
+ chatType: message.chatType,
640
+ senderId: message.senderId,
641
+ content: safeNormalizedContent(message),
642
+ rawContentType: message.rawContentType,
643
+ resources: message.resources,
644
+ mentionAll: message.mentionAll,
645
+ mentionedBot: message.mentionedBot,
646
+ createTime: message.createTime,
647
+ ...(message.rootId === undefined ? {} : { rootId: message.rootId }),
648
+ ...(message.threadId === undefined ? {} : { threadId: message.threadId }),
649
+ ...(message.replyToMessageId === undefined ? {} : { replyToMessageId: message.replyToMessageId }),
650
+ };
651
+ }
652
+ function providerError(result) {
653
+ const classified = providerFailureCode(result.code);
654
+ if (classified === 'permission_denied') {
655
+ return new LarkTransportError('permission_denied', 'Lark rejected the application credential or permission');
656
+ }
657
+ if (classified === 'rate_limited' || /rate|frequency|too many/iu.test(result.msg ?? '')) {
658
+ return new LarkTransportError('rate_limited', 'Lark rate limited the message');
659
+ }
660
+ if (classified === 'format_error') {
661
+ return new LarkTransportError('format_error', 'Lark rejected the message card format');
662
+ }
663
+ return new LarkTransportError('unknown', `Lark rejected the message with code ${String(result.code ?? 'missing')}`);
664
+ }
665
+ export class OfficialLarkTransport {
666
+ client;
667
+ imageClient;
668
+ dispatcher;
669
+ ws;
670
+ handshakeTimeoutMs;
671
+ imageDownloadTimeoutMs;
672
+ lifecycleController = new AbortController();
673
+ handlers;
674
+ identity;
675
+ pendingHandshake;
676
+ constructor(options) {
677
+ const domain = options.domain === 'lark' ? Domain.Lark : Domain.Feishu;
678
+ const shared = { appId: options.appId, appSecret: options.appSecret, domain,
679
+ logger: silentLogger, loggerLevel: LoggerLevel.error, source: 'dsh-enhanced-lark-channel' };
680
+ this.handshakeTimeoutMs = options.handshakeTimeoutMs;
681
+ this.imageDownloadTimeoutMs = options.imageDownloadTimeoutMs;
682
+ this.client = new Client({ ...shared, appType: AppType.SelfBuild });
683
+ this.imageClient = new Client({
684
+ ...shared,
685
+ appType: AppType.SelfBuild,
686
+ httpInstance: boundedImageHttpInstance(options.imageDownloadTimeoutMs, this.lifecycleController.signal),
687
+ });
688
+ this.dispatcher = new EventDispatcher({ logger: silentLogger, loggerLevel: LoggerLevel.error });
689
+ this.dispatcher.register({
690
+ 'im.message.receive_v1': async (raw) => {
691
+ if (this.handlers === undefined || this.identity === undefined)
692
+ return;
693
+ const message = await normalize(raw, {
694
+ botIdentity: this.identity,
695
+ stripBotMentions: true,
696
+ });
697
+ await this.handlers.message(toSafeLarkMessage(message));
698
+ },
699
+ 'card.action.trigger': async (raw) => {
700
+ if (this.handlers === undefined)
701
+ return;
702
+ const action = normalizeCardAction(raw);
703
+ if (action === null)
704
+ return;
705
+ return await this.handlers.cardAction({
706
+ messageId: action.messageId,
707
+ chatId: action.chatId,
708
+ operatorId: action.operator.openId,
709
+ value: action.action.value,
710
+ tag: action.action.tag,
711
+ ...(action.action.name === undefined ? {} : { name: action.action.name }),
712
+ ...(action.action.option === undefined ? {} : { option: action.action.option }),
713
+ });
714
+ },
715
+ });
716
+ this.ws = new WSClient({
717
+ ...shared,
718
+ autoReconnect: true,
719
+ handshakeTimeoutMs: options.handshakeTimeoutMs,
720
+ onReady: () => this.pendingHandshake?.resolve(),
721
+ onError: error => {
722
+ const normalized = new LarkTransportError('not_connected', 'Lark WebSocket connection failed');
723
+ if (this.pendingHandshake !== undefined)
724
+ this.pendingHandshake.reject(normalized);
725
+ else
726
+ this.handlers?.error(normalized);
727
+ void error;
728
+ },
729
+ onReconnecting: () => this.handlers?.reconnecting(),
730
+ onReconnected: () => this.handlers?.reconnected(),
731
+ });
732
+ installLarkCardCallbackBridge(this.ws);
733
+ }
734
+ subscribe(handlers) {
735
+ if (this.handlers !== undefined)
736
+ throw new Error('lark-channel: transport is already subscribed');
737
+ this.handlers = handlers;
738
+ return () => { if (this.handlers === handlers)
739
+ this.handlers = undefined; };
740
+ }
741
+ async connect() {
742
+ this.identity = await this.fetchBotIdentity();
743
+ await new Promise((resolve, reject) => {
744
+ let settled = false;
745
+ const timer = setTimeout(() => {
746
+ if (settled)
747
+ return;
748
+ settled = true;
749
+ this.pendingHandshake = undefined;
750
+ this.ws.close({ force: true });
751
+ reject(new LarkTransportError('not_connected', 'Lark WebSocket handshake timed out'));
752
+ }, this.handshakeTimeoutMs + 1_000);
753
+ timer.unref?.();
754
+ this.pendingHandshake = {
755
+ resolve: () => {
756
+ if (settled)
757
+ return;
758
+ settled = true;
759
+ clearTimeout(timer);
760
+ this.pendingHandshake = undefined;
761
+ resolve();
762
+ },
763
+ reject: error => {
764
+ if (settled)
765
+ return;
766
+ settled = true;
767
+ clearTimeout(timer);
768
+ this.pendingHandshake = undefined;
769
+ reject(error);
770
+ },
771
+ };
772
+ void this.ws.start({ eventDispatcher: this.dispatcher }).catch(error => {
773
+ void error;
774
+ this.pendingHandshake?.reject(new LarkTransportError('not_connected', 'Lark WebSocket startup failed'));
775
+ });
776
+ });
777
+ }
778
+ async disconnect() {
779
+ this.lifecycleController.abort(new Error('lark-channel: transport stopped'));
780
+ this.pendingHandshake?.reject(new LarkTransportError('not_connected', 'Lark transport stopped'));
781
+ this.ws.close({ force: true });
782
+ }
783
+ async downloadMessageImage(messageId, fileKey, options) {
784
+ const timeoutSignal = AbortSignal.timeout(this.imageDownloadTimeoutMs);
785
+ const signal = AbortSignal.any([options.signal, timeoutSignal, this.lifecycleController.signal]);
786
+ let response;
787
+ try {
788
+ response = await awaitImageResourceRequest(signal, async () => await this.imageClient.request(createLarkImageResourceRequest(messageId, fileKey, {
789
+ maxBytes: options.maxBytes, timeoutMs: this.imageDownloadTimeoutMs, signal,
790
+ })));
791
+ }
792
+ catch (error) {
793
+ if (options.signal.aborted) {
794
+ throw new LarkTransportError('not_connected', 'Lark image resource request was cancelled');
795
+ }
796
+ if (this.lifecycleController.signal.aborted) {
797
+ throw new LarkTransportError('not_connected', 'Lark image resource request was cancelled');
798
+ }
799
+ if (timeoutSignal.aborted) {
800
+ throw new LarkTransportError('send_timeout', 'Lark image resource request timed out');
801
+ }
802
+ const classified = await classifyLarkImageSdkFailure(error, signal);
803
+ if (options.signal.aborted || this.lifecycleController.signal.aborted) {
804
+ throw new LarkTransportError('not_connected', 'Lark image resource request was cancelled');
805
+ }
806
+ if (timeoutSignal.aborted) {
807
+ throw new LarkTransportError('send_timeout', 'Lark image resource request timed out');
808
+ }
809
+ throw new LarkTransportError(classified.code, 'Lark image resource request failed', classified.retryAfterMs);
810
+ }
811
+ try {
812
+ const image = await readLarkImageResourceResponse(response, { maxBytes: options.maxBytes, signal });
813
+ if (options.signal.aborted || this.lifecycleController.signal.aborted) {
814
+ throw new LarkTransportError('not_connected', 'Lark image resource request was cancelled');
815
+ }
816
+ if (timeoutSignal.aborted) {
817
+ throw new LarkTransportError('send_timeout', 'Lark image resource request timed out');
818
+ }
819
+ return image;
820
+ }
821
+ catch (error) {
822
+ if (options.signal.aborted) {
823
+ throw new LarkTransportError('not_connected', 'Lark image resource request was cancelled');
824
+ }
825
+ if (this.lifecycleController.signal.aborted) {
826
+ throw new LarkTransportError('not_connected', 'Lark image resource request was cancelled');
827
+ }
828
+ if (timeoutSignal.aborted) {
829
+ throw new LarkTransportError('send_timeout', 'Lark image resource request timed out');
830
+ }
831
+ const classified = error instanceof LarkImageResponseValidationError
832
+ ? { code: 'format_error' }
833
+ : classifyLarkSdkFailure(error);
834
+ const message = classified.code === 'format_error'
835
+ ? 'Lark image resource response failed validation'
836
+ : 'Lark image resource response could not be read';
837
+ throw new LarkTransportError(classified.code, message, classified.retryAfterMs);
838
+ }
839
+ }
840
+ async addReaction(messageId, emojiType) {
841
+ try {
842
+ const response = await this.client.im.v1.messageReaction.create({
843
+ path: { message_id: messageId },
844
+ data: { reaction_type: { emoji_type: emojiType } },
845
+ });
846
+ if (response.code !== undefined && response.code !== 0) {
847
+ throw providerError({ code: response.code, msg: response.msg });
848
+ }
849
+ const reactionId = response.data?.reaction_id;
850
+ if (reactionId === undefined || reactionId === '') {
851
+ throw new LarkTransportError('unknown', 'Lark accepted reaction omitted reaction identity');
852
+ }
853
+ return reactionId;
854
+ }
855
+ catch (error) {
856
+ if (error instanceof LarkTransportError)
857
+ throw error;
858
+ const classified = classifyLarkSdkFailure(error);
859
+ throw new LarkTransportError(classified.code, 'Lark reaction request failed', classified.retryAfterMs);
860
+ }
861
+ }
862
+ async createProgress(chatId, options) {
863
+ try {
864
+ const response = await this.client.request(createLarkProgressRequest(chatId, options));
865
+ if (response.code !== undefined && response.code !== 0) {
866
+ throw providerError({ code: response.code, msg: response.msg });
867
+ }
868
+ const cotId = response.data?.cot_id;
869
+ const messageId = response.data?.message_id;
870
+ if (cotId === undefined || messageId === undefined) {
871
+ throw new LarkTransportError('unknown', 'Lark accepted progress request omitted progress identity');
872
+ }
873
+ return { cotId, messageId };
874
+ }
875
+ catch (error) {
876
+ if (error instanceof LarkTransportError)
877
+ throw error;
878
+ const classified = classifyLarkSdkFailure(error);
879
+ throw new LarkTransportError(classified.code, 'Lark progress request failed', classified.retryAfterMs);
880
+ }
881
+ }
882
+ async writeProgress(handle, events) {
883
+ try {
884
+ const response = await this.client.request(writeLarkProgressRequest(handle, events));
885
+ if (response.code !== undefined && response.code !== 0) {
886
+ throw providerError({ code: response.code, msg: response.msg });
887
+ }
888
+ }
889
+ catch (error) {
890
+ if (error instanceof LarkTransportError)
891
+ throw error;
892
+ const classified = classifyLarkSdkFailure(error);
893
+ throw new LarkTransportError(classified.code, 'Lark progress update failed', classified.retryAfterMs);
894
+ }
895
+ }
896
+ async send(chatId, input, options = {}) {
897
+ const rendered = renderLarkMessage(input);
898
+ const uuid = larkRequestUuid(options.requestKey ?? `${chatId}:${rendered.content}`);
899
+ try {
900
+ const response = options.replyTo === undefined
901
+ ? await this.client.im.v1.message.create({
902
+ params: { receive_id_type: 'chat_id' },
903
+ data: { receive_id: chatId, msg_type: rendered.msgType, content: rendered.content, uuid },
904
+ })
905
+ : await this.client.im.v1.message.reply({
906
+ path: { message_id: options.replyTo },
907
+ data: { msg_type: rendered.msgType, content: rendered.content, uuid,
908
+ ...(options.replyInThread === undefined ? {} : { reply_in_thread: options.replyInThread }) },
909
+ });
910
+ if (response.code !== 0)
911
+ throw providerError({ code: response.code, msg: response.msg });
912
+ const messageId = response.data?.message_id;
913
+ if (messageId === undefined)
914
+ throw new LarkTransportError('unknown', 'Lark accepted response omitted message identity');
915
+ return { messageId };
916
+ }
917
+ catch (error) {
918
+ if (error instanceof LarkTransportError)
919
+ throw error;
920
+ const classified = classifyLarkSdkFailure(error);
921
+ throw new LarkTransportError(classified.code, 'Lark SDK request failed', classified.retryAfterMs);
922
+ }
923
+ }
924
+ async fetchBotIdentity() {
925
+ try {
926
+ const response = await this.client.request({
927
+ url: '/open-apis/bot/v3/info', method: 'GET',
928
+ });
929
+ if (response.bot?.open_id === undefined) {
930
+ throw new LarkTransportError('permission_denied', 'Lark bot identity is unavailable');
931
+ }
932
+ return { openId: response.bot.open_id, name: response.bot.app_name ?? 'bot' };
933
+ }
934
+ catch (error) {
935
+ if (error instanceof LarkTransportError)
936
+ throw error;
937
+ const classified = classifyLarkSdkFailure(error);
938
+ throw new LarkTransportError(classified.code, 'Lark bot identity request failed', classified.retryAfterMs);
939
+ }
940
+ }
941
+ }
942
+ export function createOfficialLarkTransport(options) {
943
+ return new OfficialLarkTransport(options);
944
+ }
945
+ //# sourceMappingURL=sdk.js.map