@krischoichoi/channel-lark 0.6.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 (62) hide show
  1. package/README.md +114 -0
  2. package/lib/adapter.d.ts +128 -0
  3. package/lib/adapter.d.ts.map +1 -0
  4. package/lib/adapter.js +347 -0
  5. package/lib/adapter.js.map +1 -0
  6. package/lib/auth/device-authorization.d.ts +12 -0
  7. package/lib/auth/device-authorization.d.ts.map +1 -0
  8. package/lib/auth/device-authorization.js +169 -0
  9. package/lib/auth/device-authorization.js.map +1 -0
  10. package/lib/card.d.ts +124 -0
  11. package/lib/card.d.ts.map +1 -0
  12. package/lib/card.js +347 -0
  13. package/lib/card.js.map +1 -0
  14. package/lib/config.d.ts +103 -0
  15. package/lib/config.d.ts.map +1 -0
  16. package/lib/config.js +60 -0
  17. package/lib/config.js.map +1 -0
  18. package/lib/definition.d.ts +74 -0
  19. package/lib/definition.d.ts.map +1 -0
  20. package/lib/definition.js +190 -0
  21. package/lib/definition.js.map +1 -0
  22. package/lib/inbound.d.ts +41 -0
  23. package/lib/inbound.d.ts.map +1 -0
  24. package/lib/inbound.js +102 -0
  25. package/lib/inbound.js.map +1 -0
  26. package/lib/index.d.ts +58 -0
  27. package/lib/index.d.ts.map +1 -0
  28. package/lib/index.js +104 -0
  29. package/lib/index.js.map +1 -0
  30. package/lib/lark-sdk-upstream.d.ts +128 -0
  31. package/lib/lark-sdk-upstream.d.ts.map +1 -0
  32. package/lib/lark-sdk-upstream.js +339 -0
  33. package/lib/lark-sdk-upstream.js.map +1 -0
  34. package/lib/manifest.d.ts +23 -0
  35. package/lib/manifest.d.ts.map +1 -0
  36. package/lib/manifest.js +36 -0
  37. package/lib/manifest.js.map +1 -0
  38. package/lib/mapper.d.ts +47 -0
  39. package/lib/mapper.d.ts.map +1 -0
  40. package/lib/mapper.js +223 -0
  41. package/lib/mapper.js.map +1 -0
  42. package/lib/media-hydrator.d.ts +55 -0
  43. package/lib/media-hydrator.d.ts.map +1 -0
  44. package/lib/media-hydrator.js +172 -0
  45. package/lib/media-hydrator.js.map +1 -0
  46. package/lib/openapi-outbound.d.ts +216 -0
  47. package/lib/openapi-outbound.d.ts.map +1 -0
  48. package/lib/openapi-outbound.js +384 -0
  49. package/lib/openapi-outbound.js.map +1 -0
  50. package/lib/outbound.d.ts +21 -0
  51. package/lib/outbound.d.ts.map +1 -0
  52. package/lib/outbound.js +93 -0
  53. package/lib/outbound.js.map +1 -0
  54. package/lib/upstream/media-port.d.ts +154 -0
  55. package/lib/upstream/media-port.d.ts.map +1 -0
  56. package/lib/upstream/media-port.js +168 -0
  57. package/lib/upstream/media-port.js.map +1 -0
  58. package/lib/upstream.d.ts +119 -0
  59. package/lib/upstream.d.ts.map +1 -0
  60. package/lib/upstream.js +2 -0
  61. package/lib/upstream.js.map +1 -0
  62. package/package.json +46 -0
@@ -0,0 +1,93 @@
1
+ import { ChannelSendError } from '@krischoichoi/channel-core';
2
+ import { toTextPayload } from './mapper.js';
3
+ export class OutboundSender {
4
+ upstream;
5
+ logger;
6
+ interactiveCards = new Map();
7
+ constructor(upstream, logger) {
8
+ this.upstream = upstream;
9
+ this.logger = logger;
10
+ }
11
+ async send(target, message) {
12
+ try {
13
+ if (message.actions?.length) {
14
+ const payload = toTextPayload(target, message);
15
+ const response = await this.upstream.sendInteractive(payload.to, payload.content, message.actions);
16
+ const messageId = messageIdOf(response);
17
+ if (messageId)
18
+ this.interactiveCards.set(messageId, { text: payload.content });
19
+ return { delivered: true, messageId, raw: response };
20
+ }
21
+ const file = firstFileWithData(message.parts);
22
+ if (file && !message.text) {
23
+ const response = await this.upstream.sendFile(target.conversationId, toFileRef(file));
24
+ return { delivered: true, raw: response };
25
+ }
26
+ const image = firstImage(message.parts);
27
+ if (image && !message.text) {
28
+ const response = await this.upstream.sendMedia(target.conversationId, image);
29
+ return { delivered: true, raw: response };
30
+ }
31
+ const payload = toTextPayload(target, message);
32
+ const response = await this.upstream.sendText(payload.to, payload.content);
33
+ return { delivered: true, raw: response };
34
+ }
35
+ catch (error) {
36
+ this.logger.error('[channel-lark] send failed to ' + target.conversationId, error instanceof Error ? error.message : error);
37
+ throw new ChannelSendError('lark send failed: ' + (error instanceof Error ? error.message : String(error)));
38
+ }
39
+ }
40
+ async edit(target, messageId, message) {
41
+ try {
42
+ if (message.actions === undefined && message.text === undefined)
43
+ return { delivered: true };
44
+ const known = this.interactiveCards.get(messageId);
45
+ if (!known) {
46
+ throw new ChannelSendError('lark cannot edit an interactive card that was not sent by this adapter');
47
+ }
48
+ const text = message.text ?? known.text;
49
+ const actions = message.actions ?? [];
50
+ const response = await this.upstream.updateInteractive(messageId, text, actions);
51
+ this.interactiveCards.set(messageId, { text });
52
+ return { delivered: true, messageId, raw: response };
53
+ }
54
+ catch (error) {
55
+ this.logger.error('[channel-lark] interactive card edit failed for ' + target.conversationId, error instanceof Error ? error.message : error);
56
+ throw new ChannelSendError('lark interactive card edit failed: ' + (error instanceof Error ? error.message : String(error)));
57
+ }
58
+ }
59
+ }
60
+ function messageIdOf(response) {
61
+ if (!response || typeof response !== 'object')
62
+ return undefined;
63
+ const data = response.data;
64
+ if (!data || typeof data !== 'object')
65
+ return undefined;
66
+ const messageId = data.message_id;
67
+ return typeof messageId === 'string' && messageId.length > 0 ? messageId : undefined;
68
+ }
69
+ /**
70
+ * The first file part that already carries trusted bytes (localData). A file
71
+ * part without bytes is not in a state the official outbound can send yet
72
+ * (no re-download here), so it falls through to the text path.
73
+ */
74
+ function firstFileWithData(parts) {
75
+ if (!parts)
76
+ return undefined;
77
+ return parts.find((part) => part.type === 'file' && part.localData !== undefined);
78
+ }
79
+ /** Shape a hydrated FilePart into the outbound LarkFileRef. */
80
+ function toFileRef(file) {
81
+ return {
82
+ type: 'file',
83
+ localData: file.localData,
84
+ name: file.name,
85
+ mimeType: file.mimeType,
86
+ };
87
+ }
88
+ function firstImage(parts) {
89
+ if (!parts)
90
+ return undefined;
91
+ return parts.find((part) => part.type === 'image');
92
+ }
93
+ //# sourceMappingURL=outbound.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outbound.js","sourceRoot":"","sources":["../src/outbound.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAG5C,MAAM,OAAO,cAAc;IAIN;IACA;IAJF,gBAAgB,GAAG,IAAI,GAAG,EAA4B,CAAC;IAExE,YACmB,QAAsB,EACtB,MAAqB;QADrB,aAAQ,GAAR,QAAQ,CAAc;QACtB,WAAM,GAAN,MAAM,CAAe;IACrC,CAAC;IAEJ,KAAK,CAAC,IAAI,CAAC,MAAqB,EAAE,OAAwB;QACxD,IAAI,CAAC;YACH,IAAI,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC;gBAC5B,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;gBAC/C,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,eAAe,CAAC,OAAO,CAAC,EAAE,EAAE,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;gBACnG,MAAM,SAAS,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;gBACxC,IAAI,SAAS;oBAAE,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;gBAC/E,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC;YACvD,CAAC;YACD,MAAM,IAAI,GAAG,iBAAiB,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YAC9C,IAAI,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;gBAC1B,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,cAAc,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;gBACtF,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC;YAC5C,CAAC;YACD,MAAM,KAAK,GAAG,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YACxC,IAAI,KAAK,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;gBAC3B,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,MAAM,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC;gBAC7E,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC;YAC5C,CAAC;YACD,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;YAC/C,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;YAC3E,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC;QAC5C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,KAAK,CACf,gCAAgC,GAAG,MAAM,CAAC,cAAc,EACxD,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAC/C,CAAC;YACF,MAAM,IAAI,gBAAgB,CACxB,oBAAoB,GAAG,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAChF,CAAC;QACJ,CAAC;IACH,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,MAAqB,EAAE,SAAiB,EAAE,OAAwB;QAC3E,IAAI,CAAC;YACH,IAAI,OAAO,CAAC,OAAO,KAAK,SAAS,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS;gBAAE,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;YAC5F,MAAM,KAAK,GAAG,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;YACnD,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,MAAM,IAAI,gBAAgB,CAAC,wEAAwE,CAAC,CAAC;YACvG,CAAC;YACD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC;YACxC,MAAM,OAAO,GAAwB,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC;YAC3D,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,iBAAiB,CAAC,SAAS,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;YACjF,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;YAC/C,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC;QACvD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,KAAK,CACf,kDAAkD,GAAG,MAAM,CAAC,cAAc,EAC1E,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAC/C,CAAC;YACF,MAAM,IAAI,gBAAgB,CACxB,qCAAqC,GAAG,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CACjG,CAAC;QACJ,CAAC;IACH,CAAC;CACF;AAED,SAAS,WAAW,CAAC,QAAiB;IACpC,IAAI,CAAC,QAAQ,IAAI,OAAO,QAAQ,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IAChE,MAAM,IAAI,GAAI,QAA+B,CAAC,IAAI,CAAC;IACnD,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IACxD,MAAM,SAAS,GAAI,IAAiC,CAAC,UAAU,CAAC;IAChE,OAAO,OAAO,SAAS,KAAK,QAAQ,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;AACvF,CAAC;AAED;;;;GAIG;AACH,SAAS,iBAAiB,CACxB,KAAgC;IAEhC,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAC7B,OAAO,KAAK,CAAC,IAAI,CACf,CAAC,IAAI,EAAgD,EAAE,CACrD,IAAI,CAAC,IAAI,KAAK,MAAM,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS,CACvD,CAAC;AACJ,CAAC;AAED,+DAA+D;AAC/D,SAAS,SAAS,CAAC,IAA0C;IAC3D,OAAO;QACL,IAAI,EAAE,MAAM;QACZ,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,QAAQ,EAAE,IAAI,CAAC,QAAQ;KACxB,CAAC;AACJ,CAAC;AAED,SAAS,UAAU,CAAC,KAAgC;IAClD,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAC7B,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAmD,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC;AACtG,CAAC"}
@@ -0,0 +1,154 @@
1
+ /** Official per-resource limit, also enforced locally while reading. */
2
+ export declare const LARK_MESSAGE_RESOURCE_MAX_BYTES: number;
3
+ /** The four binary resource kinds the official message.resource API resolves. */
4
+ export type LarkResourceType = 'image' | 'file' | 'audio' | 'video';
5
+ /** Minimal im.v1.messageResource.get payload/result shapes consumed here. */
6
+ export interface LarkMessageResourceGetPayload {
7
+ params: {
8
+ type: LarkResourceType | string;
9
+ };
10
+ path: {
11
+ message_id: string;
12
+ file_key: string;
13
+ };
14
+ }
15
+ /** The binary-stream result the official SDK returns for resource downloads. */
16
+ export interface LarkMessageResourceResult {
17
+ /** Persist the downloaded resource to a local file path. */
18
+ writeFile?(filePath: string): Promise<unknown>;
19
+ /** The downloaded resource as a Node readable stream. */
20
+ getReadableStream(): import('node:stream').Readable;
21
+ /** Response headers (may carry content-type / content-disposition). */
22
+ headers?: Record<string, unknown>;
23
+ }
24
+ /** Minimal im.v1.image.get payload shape (image download). */
25
+ export interface LarkImageGetPayload {
26
+ path: {
27
+ image_key: string;
28
+ };
29
+ }
30
+ /** Minimal im.v1.image.create payload/result shapes. */
31
+ export interface LarkImageCreatePayload {
32
+ data: {
33
+ image_type: 'message' | 'avatar';
34
+ image: Uint8Array;
35
+ };
36
+ }
37
+ export interface LarkImageCreateResult {
38
+ image_key?: string;
39
+ }
40
+ /** Minimal im.v1.file.create payload/result shapes. */
41
+ export interface LarkFileCreatePayload {
42
+ data: {
43
+ file_type: 'opus' | 'mp4' | 'pdf' | 'doc' | 'xls' | 'ppt' | 'stream';
44
+ file_name: string;
45
+ duration?: number;
46
+ file: Uint8Array;
47
+ };
48
+ }
49
+ export interface LarkFileCreateResult {
50
+ file_key?: string;
51
+ }
52
+ /**
53
+ * Structural subset of the official SDK Client required by the media port.
54
+ * The real Client (1.73.1) satisfies this shape; tests inject a fake.
55
+ * Mirrors the LarkOpenApiClient pattern from openapi-outbound.ts.
56
+ */
57
+ export interface LarkMediaClient {
58
+ im: {
59
+ v1: {
60
+ messageResource: {
61
+ get(payload: LarkMessageResourceGetPayload): Promise<LarkMessageResourceResult>;
62
+ };
63
+ image: {
64
+ create(payload: LarkImageCreatePayload): Promise<LarkImageCreateResult | null>;
65
+ };
66
+ file: {
67
+ create(payload: LarkFileCreatePayload): Promise<LarkFileCreateResult | null>;
68
+ };
69
+ };
70
+ };
71
+ }
72
+ /**
73
+ * Media port contract (plan section 26). The adapter depends on this
74
+ * interface so it is fully injectable in offline tests.
75
+ */
76
+ export interface LarkMediaPort {
77
+ /**
78
+ * Download a resource (image/file/audio/video) embedded in a message.
79
+ * resourceKey is the platform image_key/file_key (a resourceRef), resolved
80
+ * via the official messageResource.get against the owning messageId. The
81
+ * per-kind `type` mirrors the resource kind the official API documents for
82
+ * audio/video downloads (plan section 23-A4).
83
+ */
84
+ downloadMessageResource(input: {
85
+ messageId: string;
86
+ resourceKey: string;
87
+ type: LarkResourceType;
88
+ signal?: AbortSignal;
89
+ /** Hard cap enforced while consuming the SDK stream. */
90
+ maxBytes?: number;
91
+ }): Promise<{
92
+ data: Uint8Array;
93
+ mimeType?: string;
94
+ name?: string;
95
+ }>;
96
+ /** Upload an image; returns the platform image_key. */
97
+ uploadImage(input: {
98
+ data: Uint8Array;
99
+ signal?: AbortSignal;
100
+ }): Promise<{
101
+ imageKey: string;
102
+ }>;
103
+ /** Upload a generic file; returns the platform file_key. */
104
+ uploadFile(input: {
105
+ data: Uint8Array;
106
+ name: string;
107
+ fileType: 'opus' | 'mp4' | 'pdf' | 'doc' | 'xls' | 'ppt' | 'stream';
108
+ durationMs?: number;
109
+ signal?: AbortSignal;
110
+ }): Promise<{
111
+ fileKey: string;
112
+ }>;
113
+ }
114
+ export interface LarkMediaPortOptions {
115
+ /** Official OpenAPI client (real Client or injected fake). */
116
+ client: LarkMediaClient;
117
+ }
118
+ /**
119
+ * Official-SDK implementation of the media port. downloadMessageResource is
120
+ * the M2A image-ingress surface; uploadImage/uploadFile are declared now and
121
+ * implemented via the official image/file clients so the later outbound-media
122
+ * milestone (M7A) can reuse them.
123
+ */
124
+ export declare class LarkOpenApiMediaPort implements LarkMediaPort {
125
+ private readonly options;
126
+ constructor(options: LarkMediaPortOptions);
127
+ downloadMessageResource(input: {
128
+ messageId: string;
129
+ resourceKey: string;
130
+ type: LarkResourceType;
131
+ signal?: AbortSignal;
132
+ maxBytes?: number;
133
+ }): Promise<{
134
+ data: Uint8Array;
135
+ mimeType?: string;
136
+ name?: string;
137
+ }>;
138
+ uploadImage(input: {
139
+ data: Uint8Array;
140
+ signal?: AbortSignal;
141
+ }): Promise<{
142
+ imageKey: string;
143
+ }>;
144
+ uploadFile(input: {
145
+ data: Uint8Array;
146
+ name: string;
147
+ fileType: 'opus' | 'mp4' | 'pdf' | 'doc' | 'xls' | 'ppt' | 'stream';
148
+ durationMs?: number;
149
+ signal?: AbortSignal;
150
+ }): Promise<{
151
+ fileKey: string;
152
+ }>;
153
+ }
154
+ //# sourceMappingURL=media-port.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media-port.d.ts","sourceRoot":"","sources":["../../src/upstream/media-port.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,168 @@
1
+ /**
2
+ * Lark media port - Media ingress/egress over the official
3
+ * @larksuiteoapi/node-sdk OpenAPI Client (Milestone M2A, plan section 26/27).
4
+ *
5
+ * The port is the ONLY module in channel-lark that resolves platform media.
6
+ * downloadMessageResource is the M2A Native Image Ingress path; it goes
7
+ * strictly through client.im.v1.messageResource.get (official SDK) - never
8
+ * hand-written /open-apis/im/v1/... URLs, no tenant-token logic. The SDK
9
+ * returns a binary stream ({ getReadableStream }) which this port normalizes
10
+ * to a Uint8Array, best-effort copying the content-type header into mimeType.
11
+ *
12
+ * Since plan section 23-A4 the port resolves ALL four binary resource kinds:
13
+ * the official `message.resource` API documents its scope as 音频、视频、图片和文件
14
+ * ("resource files in the specified message, including audio, video, image
15
+ * and file", see the SDK-embedded doc statement for im.v1.messageResource.get
16
+ * in @larksuiteoapi/node-sdk@1.73.1), and the SDK `type` query param is typed
17
+ * `string` - NOT restricted to image/file. The mapper therefore routes
18
+ * audio/video file_keys through the same `type: 'audio'` / `type: 'video'`
19
+ * download seam as image/file. Platform-level limits stay official facts:
20
+ * <= 100 MB per resource, no stickers, no sub-messages of merged-forward /
21
+ * card messages (error 234043) - those surface as port failures that the
22
+ * hydrator maps to a stable ingressFailure.
23
+ *
24
+ * uploadImage -> client.im.v1.image.create and uploadFile ->
25
+ * client.im.v1.file.create are declared now (needed by the outbound-media
26
+ * milestone M7A later) and implemented because they are straightforward.
27
+ *
28
+ * To stay offline-testable this module consumes only a minimal structural
29
+ * subset of the real SDK Client (mirrors the LarkOpenApiClient split in
30
+ * openapi-outbound.ts); the real Client satisfies the shape and tests inject
31
+ * a fake. Credentials are never referenced here - the Client is built
32
+ * elsewhere from config.
33
+ *
34
+ * The SDK messageResource.get signature bound here (1.73.1 types):
35
+ * get(payload: { params: { type: string }; path: { message_id: string;
36
+ * file_key: string } }, options?):
37
+ * Promise<{ writeFile(fp): Promise<unknown>;
38
+ * getReadableStream(): Readable; headers: any }>
39
+ */
40
+ import { BodyTooLargeError, ChannelError, RemoteMediaAbortedError, concatenate, } from '@krischoichoi/channel-core';
41
+ /** Official per-resource limit, also enforced locally while reading. */
42
+ export const LARK_MESSAGE_RESOURCE_MAX_BYTES = 100 * 1024 * 1024;
43
+ /**
44
+ * Official-SDK implementation of the media port. downloadMessageResource is
45
+ * the M2A image-ingress surface; uploadImage/uploadFile are declared now and
46
+ * implemented via the official image/file clients so the later outbound-media
47
+ * milestone (M7A) can reuse them.
48
+ */
49
+ export class LarkOpenApiMediaPort {
50
+ options;
51
+ constructor(options) {
52
+ this.options = options;
53
+ }
54
+ async downloadMessageResource(input) {
55
+ if (input.signal?.aborted)
56
+ throw new RemoteMediaAbortedError();
57
+ const result = await this.options.client.im.v1.messageResource.get({
58
+ params: { type: input.type },
59
+ path: { message_id: input.messageId, file_key: input.resourceKey },
60
+ });
61
+ const data = await streamToUint8Array(result, input.maxBytes ?? LARK_MESSAGE_RESOURCE_MAX_BYTES, input.signal);
62
+ return { data, mimeType: contentTypeOf(result), name: fileNameOf(result) };
63
+ }
64
+ async uploadImage(input) {
65
+ void input.signal;
66
+ const result = await this.options.client.im.v1.image.create({
67
+ data: { image_type: 'message', image: input.data },
68
+ });
69
+ const imageKey = result?.image_key;
70
+ if (!imageKey) {
71
+ throw new ChannelError('CHANNEL_ERROR', 'lark image upload returned no image_key');
72
+ }
73
+ return { imageKey };
74
+ }
75
+ async uploadFile(input) {
76
+ void input.signal;
77
+ const data = {
78
+ file_type: input.fileType,
79
+ file_name: input.name,
80
+ duration: input.durationMs,
81
+ file: input.data,
82
+ };
83
+ if (input.durationMs === undefined)
84
+ delete data.duration;
85
+ const result = await this.options.client.im.v1.file.create({ data });
86
+ const fileKey = result?.file_key;
87
+ if (!fileKey) {
88
+ throw new ChannelError('CHANNEL_ERROR', 'lark file upload returned no file_key');
89
+ }
90
+ return { fileKey };
91
+ }
92
+ }
93
+ /** Consume the official Node stream under a hard cumulative byte cap. */
94
+ async function streamToUint8Array(result, maxBytes, signal) {
95
+ if (maxBytes <= 0) {
96
+ throw new BodyTooLargeError(maxBytes, `maxBytes must be positive, got ${maxBytes}`);
97
+ }
98
+ if (signal?.aborted)
99
+ throw new RemoteMediaAbortedError();
100
+ const stream = result.getReadableStream();
101
+ const chunks = [];
102
+ let received = 0;
103
+ const onAbort = () => stream.destroy(new RemoteMediaAbortedError());
104
+ signal?.addEventListener('abort', onAbort, { once: true });
105
+ try {
106
+ for await (const rawChunk of stream) {
107
+ if (signal?.aborted)
108
+ throw new RemoteMediaAbortedError();
109
+ const chunk = normalizeStreamChunk(rawChunk);
110
+ if (chunk === undefined) {
111
+ stream.destroy();
112
+ throw new ChannelError('CHANNEL_ERROR', 'lark media stream returned a non-binary chunk');
113
+ }
114
+ received += chunk.byteLength;
115
+ if (received > maxBytes) {
116
+ stream.destroy();
117
+ throw new BodyTooLargeError(maxBytes);
118
+ }
119
+ chunks.push(chunk);
120
+ }
121
+ }
122
+ catch (error) {
123
+ if (signal?.aborted)
124
+ throw new RemoteMediaAbortedError(undefined, { cause: error });
125
+ throw error;
126
+ }
127
+ finally {
128
+ signal?.removeEventListener('abort', onAbort);
129
+ }
130
+ return concatenate(chunks, received);
131
+ }
132
+ function normalizeStreamChunk(chunk) {
133
+ if (Buffer.isBuffer(chunk))
134
+ return chunk;
135
+ if (typeof chunk === 'string')
136
+ return Buffer.from(chunk);
137
+ if (chunk instanceof Uint8Array)
138
+ return chunk;
139
+ if (chunk instanceof ArrayBuffer)
140
+ return new Uint8Array(chunk);
141
+ if (ArrayBuffer.isView(chunk)) {
142
+ return new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength);
143
+ }
144
+ return undefined;
145
+ }
146
+ /** Best-effort content-type from the SDK response headers. */
147
+ function contentTypeOf(result) {
148
+ const headers = result.headers;
149
+ if (!headers)
150
+ return undefined;
151
+ const raw = headers['content-type'] ?? headers['Content-Type'];
152
+ if (typeof raw === 'string')
153
+ return raw.split(';')[0]?.trim() || undefined;
154
+ return undefined;
155
+ }
156
+ /** Best-effort filename from the SDK response headers (content-disposition). */
157
+ function fileNameOf(result) {
158
+ const headers = result.headers;
159
+ if (!headers)
160
+ return undefined;
161
+ const disposition = headers['content-disposition'] ?? headers['Content-Disposition'];
162
+ if (typeof disposition !== 'string')
163
+ return undefined;
164
+ const match = disposition.match(/filename=([^;]+)/i);
165
+ const value = match?.[1]?.trim().replace(/^"|"$/g, '');
166
+ return value || undefined;
167
+ }
168
+ //# sourceMappingURL=media-port.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media-port.js","sourceRoot":"","sources":["../../src/upstream/media-port.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Lark upstream contract — the platform-neutral surface implemented by the
3
+ * official `@larksuiteoapi/node-sdk` driver stack.
4
+ *
5
+ * The upstream is the OFFICIAL SDK only:
6
+ * - inbound — `LarkSdkUpstream` (WSClient + EventDispatcher,
7
+ * `im.message.receive_v1` / `card.action.trigger`);
8
+ * - outbound — `LarkOpenApiOutbound` (im.v1.* messages/media + CardKit 2.0
9
+ * card entities and native streaming).
10
+ *
11
+ * Every operation in this contract is expressed in official-SDK semantics —
12
+ * there is no self-hosted gateway and no legacy `/message/*` / `/card/*`
13
+ * endpoint. Credentials never appear in this module (clients are built
14
+ * elsewhere from resolved config/credentials).
15
+ */
16
+ import type { OutboundActionRow } from '@krischoichoi/channel-core';
17
+ /** Minimal media reference for the basic outbound image send. */
18
+ export interface LarkMediaRef {
19
+ type: 'image';
20
+ url?: string;
21
+ dataUri?: string;
22
+ name?: string;
23
+ /** Human-readable description; used as the send name when the part has none. */
24
+ alt?: string;
25
+ }
26
+ /**
27
+ * Outbound generic-file reference. localData carries the trusted bytes
28
+ * (preferred; the adapter never re-downloads), url/dataUri are fallbacks. The
29
+ * file_type sent to the SDK is derived from name by the outbound driver.
30
+ */
31
+ export interface LarkFileRef {
32
+ type: 'file';
33
+ /** Trusted bytes already held by the adapter (preferred over url/dataUri). */
34
+ localData?: Uint8Array;
35
+ url?: string;
36
+ dataUri?: string;
37
+ /** Human-readable filename; drives the SDK file_name + derived file_type. */
38
+ name?: string;
39
+ mimeType?: string;
40
+ }
41
+ /**
42
+ * Outbound surface of the official OpenAPI client: plain message/media sends,
43
+ * Card JSON 2.0 interactive cards, and CardKit 2.0 card-entity + native
44
+ * streaming operations. Implemented by `LarkOpenApiOutbound` and delegated
45
+ * through `LarkSdkUpstream`; tests inject a fake OpenAPI client.
46
+ */
47
+ export interface LarkOutbound {
48
+ /** Send a plain text message (buffered fallback). */
49
+ sendText(to: string, text: string): Promise<unknown>;
50
+ /** Send a basic media message (e.g. a plain image). */
51
+ sendMedia(to: string, media: LarkMediaRef): Promise<unknown>;
52
+ /** Send a generic file message. */
53
+ sendFile(to: string, file: LarkFileRef): Promise<unknown>;
54
+ /**
55
+ * Send an official Feishu interactive-card button set (Card JSON 2.0
56
+ * buttons with `behaviors[].value`).
57
+ */
58
+ sendInteractive(to: string, text: string, actions: OutboundActionRow[]): Promise<unknown>;
59
+ /**
60
+ * Rewrite an already-sent interactive card, for example to remove completed
61
+ * actions. Card edit boundary:
62
+ * - PLAIN SENT MESSAGE CARDS (`im.v1.message.create` with the full Card JSON
63
+ * 2.0 body inline) are rewritten via `im.v1.message.patch`.
64
+ * - CARDKIT CARD ENTITIES (`cardkit.v1.card.create` → card reference) are
65
+ * updated via the CardKit surface (`updateCardElementContent` /
66
+ * `finishStreamingCard`, or the `cardkit.v1.card.update`/`batchUpdate`
67
+ * client methods) — never by this `message.patch` path.
68
+ */
69
+ updateInteractive(cardId: string, text: string, actions: OutboundActionRow[]): Promise<unknown>;
70
+ /**
71
+ * Create a CardKit 2.0 card entity from a serialized Card JSON 2.0 spec.
72
+ * Returns the entity `card_id` used by later element/settings updates.
73
+ */
74
+ createCardEntity(cardJson: string): Promise<{
75
+ cardId: string;
76
+ }>;
77
+ /**
78
+ * Send a card reference (`{ type: "card", data: { card_id } }`) for an
79
+ * existing CardKit entity. Returns the sent message id. A card entity may
80
+ * only be sent once.
81
+ */
82
+ sendCardEntity(conversationId: string, cardId: string): Promise<{
83
+ messageId: string;
84
+ }>;
85
+ /**
86
+ * Native streaming update of one card element's content (the official
87
+ * "typewriter" interface). `sequence` must increase monotonically per card;
88
+ * `uuid` is the stable request id for idempotency.
89
+ */
90
+ updateCardElementContent(cardId: string, elementId: string, content: string, sequence: number, uuid: string): Promise<unknown>;
91
+ /** Disable streaming mode and write the final summary (preview text). */
92
+ finishStreamingCard(cardId: string, sequence: number, summary: string): Promise<unknown>;
93
+ /** Resolve the official chat mode for a card action before ACL admission. */
94
+ getChatType?(conversationId: string): Promise<'p2p' | 'group' | undefined>;
95
+ startTyping?(messageId: string): Promise<void>;
96
+ stopTyping?(messageId: string): Promise<void>;
97
+ }
98
+ /**
99
+ * A CardKit streaming card that has been created and whose card reference has
100
+ * been sent. The reply handle owns this state across rollovers.
101
+ */
102
+ export interface LarkStreamingCardRef {
103
+ /** CardKit entity id. */
104
+ cardId: string;
105
+ /** Message id of the interactive card reference already sent. */
106
+ messageId: string;
107
+ /** Stable id of the markdown element inside the card (matches the entity JSON). */
108
+ elementId: string;
109
+ }
110
+ /** Full upstream driver: official SDK inbound receive + outbound delegate. */
111
+ export interface LarkUpstream extends LarkOutbound {
112
+ /**
113
+ * Keep the inbound WS long-connection open and forward each raw inbound
114
+ * payload to `onMessage` until `signal` aborts. Raw payloads are
115
+ * unstructured — the mapper owns shape validation.
116
+ */
117
+ receive(signal: AbortSignal, onMessage: (raw: unknown) => void): Promise<void>;
118
+ }
119
+ //# sourceMappingURL=upstream.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upstream.d.ts","sourceRoot":"","sources":["../src/upstream.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=upstream.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upstream.js","sourceRoot":"","sources":["../src/upstream.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@krischoichoi/channel-lark",
3
+ "version": "0.6.0",
4
+ "repository": {
5
+ "type": "git",
6
+ "url": "git+https://github.com/krischoichoi/dsh-channels.git"
7
+ },
8
+ "publishConfig": {
9
+ "access": "public"
10
+ },
11
+ "description": "Lark / Feishu channel adapter for DeepSeek Harness (threads, editable card streaming, edit strategy)",
12
+ "type": "module",
13
+ "main": "./lib/index.js",
14
+ "types": "./lib/index.d.ts",
15
+ "exports": {
16
+ ".": {
17
+ "types": "./lib/index.d.ts",
18
+ "default": "./lib/index.js"
19
+ },
20
+ "./package.json": "./package.json"
21
+ },
22
+ "files": [
23
+ "lib"
24
+ ],
25
+ "scripts": {
26
+ "build": "tsc -p tsconfig.json",
27
+ "typecheck": "tsc -p tsconfig.json --noEmit",
28
+ "test": "vitest run --passWithNoTests",
29
+ "clean": "rimraf lib"
30
+ },
31
+ "dependencies": {
32
+ "@deepseek-ai/cordis": "^4.0.4",
33
+ "@deepseek-ai/dsh-credentials": "0.2.0-rc.2",
34
+ "@deepseek-ai/dsh-settings": "0.2.0-rc.2",
35
+ "@deepseek-ai/schemastery": "~3.18.4",
36
+ "@larksuiteoapi/node-sdk": "1.73.1",
37
+ "@krischoichoi/channel-control": "workspace:*",
38
+ "@krischoichoi/channel-core": "workspace:*",
39
+ "zod": "^4.4.3"
40
+ },
41
+ "devDependencies": {
42
+ "@krischoichoi/channel-testkit": "workspace:*",
43
+ "rimraf": "^6.0.0"
44
+ },
45
+ "license": "MIT"
46
+ }