eve-lark 0.1.0 → 0.2.1
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.
- package/README.md +36 -20
- package/dist/index.d.ts +25 -0
- package/dist/index.js +125 -6
- package/dist/index.js.map +1 -1
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -36,14 +36,9 @@ These are intentionally **not** shipped — file an issue if you need them:
|
|
|
36
36
|
|
|
37
37
|
## Quick start
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
Two steps. One file, one command.
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
pnpm add eve-lark
|
|
43
|
-
# or: npm install eve-lark / yarn add eve-lark
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
Create the channel in your eve agent:
|
|
41
|
+
**1. Declare the channel:**
|
|
47
42
|
|
|
48
43
|
```ts
|
|
49
44
|
// agent/channels/lark.ts
|
|
@@ -58,14 +53,24 @@ export default createLarkChannel({
|
|
|
58
53
|
});
|
|
59
54
|
```
|
|
60
55
|
|
|
61
|
-
|
|
56
|
+
**2. Run `eve dev`:**
|
|
62
57
|
|
|
58
|
+
```bash
|
|
59
|
+
pnpm add eve-lark eve
|
|
60
|
+
eve dev
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
That's it. The channel starts a Feishu `WSClient` as a side effect of construction — Feishu only sees the outbound WebSocket, so **no public webhook URL is needed** for local dev. Every event is re-signed and re-encrypted, then POSTed to the channel's own webhook on `localhost` where the standard handler runs (with full `send()` access).
|
|
64
|
+
|
|
65
|
+
In the [Feishu developer console](https://open.feishu.cn/app):
|
|
63
66
|
1. Create a **Custom App**. Note the `App ID` and `App Secret`.
|
|
64
|
-
2. Under **Event Subscriptions**,
|
|
67
|
+
2. Under **Event Subscriptions**, select **「使用长连接接收事件」** mode (not HTTP callback).
|
|
65
68
|
3. Generate a **Verification Token** and an **Encrypt Key** — copy both into your env.
|
|
66
|
-
4. Subscribe to
|
|
69
|
+
4. Subscribe to `im.message.receive_v1`.
|
|
67
70
|
5. Add the bot to a chat or DM it directly.
|
|
68
71
|
|
|
72
|
+
For production, switch to HTTP-callback mode in the Feishu console and pass `mode: "webhook"` to `createLarkChannel`. See [Production](#production-deploy).
|
|
73
|
+
|
|
69
74
|
## Configuration reference
|
|
70
75
|
|
|
71
76
|
All fields can be supplied as options or read from the matching env var (options win).
|
|
@@ -78,6 +83,8 @@ All fields can be supplied as options or read from the matching env var (options
|
|
|
78
83
|
| `encryptKey` | `string` | no | — | `LARK_ENCRYPT_KEY` |
|
|
79
84
|
| `baseUrl` | `string` | no | `https://open.feishu.cn` | `LARK_BASE_URL` |
|
|
80
85
|
| `botOpenId` | `string` | no | — | `LARK_BOT_OPEN_ID` |
|
|
86
|
+
| `mode` | `"long-connection" \| "webhook"` | no | `"long-connection"` | — |
|
|
87
|
+
| `port` | `number` | no | `$PORT` or `2000` | `PORT` |
|
|
81
88
|
| `webhookPath` | `string` | no | `/lark/webhook` | — |
|
|
82
89
|
| `replyMode` | `"streaming" \| "static"` | no | `"streaming"` | — |
|
|
83
90
|
| `streamPatchIntervalMs` | `number` | no | `1000` | — |
|
|
@@ -88,6 +95,7 @@ All fields can be supplied as options or read from the matching env var (options
|
|
|
88
95
|
| `maxRetries` | `number` | no | `2` | — |
|
|
89
96
|
| `tokenRefreshBufferMs` | `number` | no | `300_000` (5 min) | — |
|
|
90
97
|
| `signatureSkewMs` | `number` | no | `300_000` (5 min) | — |
|
|
98
|
+
| `ackReaction` | `string \| readonly string[] \| false` | no | `"TYPING"` | — |
|
|
91
99
|
| `fetch` | `typeof fetch` | no | `globalThis.fetch` | — |
|
|
92
100
|
|
|
93
101
|
## Feishu vs Lark (international)
|
|
@@ -185,20 +193,28 @@ pnpm build # tsup build → dist/
|
|
|
185
193
|
|
|
186
194
|
## Smoke testing against a real Feishu app
|
|
187
195
|
|
|
188
|
-
See [`examples/README.md`](./examples/README.md) for a
|
|
196
|
+
See [`examples/README.md`](./examples/README.md) for a complete walkthrough. The TL;DR matches the [Quick start](#quick-start) above: install deps, fill `.env`, run `eve dev`. Send `ping` to the bot; expect `pong` as a streaming card.
|
|
197
|
+
|
|
198
|
+
## Production deploy
|
|
199
|
+
|
|
200
|
+
For production, switch to HTTP-callback mode:
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
// agent/channels/lark.ts
|
|
204
|
+
export default createLarkChannel({
|
|
205
|
+
// ... credentials ...
|
|
206
|
+
mode: "webhook", // disables the WSClient side effect
|
|
207
|
+
});
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
In the Feishu console, switch **Event Subscription** from 「长连接」back to **HTTP callback**, and set the URL to your deployed agent's `/lark/webhook`. Then:
|
|
189
211
|
|
|
190
212
|
```bash
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
$EDITOR examples/agent/.env # fill in credentials
|
|
194
|
-
cd examples/agent && pnpm install
|
|
195
|
-
# Terminal A:
|
|
196
|
-
cd examples/agent && pnpm dev # eve dev server
|
|
197
|
-
# Terminal B (from repo root):
|
|
198
|
-
pnpm tsx examples/ws-forwarder.ts # Feishu WS → localhost HTTP
|
|
213
|
+
eve build
|
|
214
|
+
eve deploy # or: eve start on a server with a public URL
|
|
199
215
|
```
|
|
200
216
|
|
|
201
|
-
|
|
217
|
+
Everything else (signing, AES, dedup, streaming) works unchanged.
|
|
202
218
|
|
|
203
219
|
Test layout:
|
|
204
220
|
|
package/dist/index.d.ts
CHANGED
|
@@ -9,6 +9,18 @@ import { Channel } from 'eve/channels';
|
|
|
9
9
|
*/
|
|
10
10
|
type LarkInboundMessage = LarkInboundResult;
|
|
11
11
|
type LarkReplyMode = "streaming" | "static";
|
|
12
|
+
/**
|
|
13
|
+
* How the channel receives events from Feishu.
|
|
14
|
+
*
|
|
15
|
+
* - `"long-connection"` (default): the channel starts a Feishu WSClient as a
|
|
16
|
+
* side effect of construction. Events arrive via the official
|
|
17
|
+
* `@larksuiteoapi/node-sdk` long-connection transport; no public webhook
|
|
18
|
+
* URL is needed. Requires `@larksuiteoapi/node-sdk` to be installed.
|
|
19
|
+
* - `"webhook"`: pure HTTP. The channel mounts a POST webhook only; Feishu
|
|
20
|
+
* must be configured for HTTP callback mode with a public URL pointing at
|
|
21
|
+
* your agent. Use this for production deploy (`eve deploy`).
|
|
22
|
+
*/
|
|
23
|
+
type LarkTransportMode = "long-connection" | "webhook";
|
|
12
24
|
/**
|
|
13
25
|
* Branded string for a continuation token in the wire format `${chatId}:${rootMessageId ?? "_"}`.
|
|
14
26
|
* Use {@link larkContinuationToken} to mint one.
|
|
@@ -41,6 +53,17 @@ interface LarkChannelOptions {
|
|
|
41
53
|
* or `false` to disable. Default: "TYPING".
|
|
42
54
|
*/
|
|
43
55
|
ackReaction?: string | readonly string[] | false | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* Transport mode. Default: `"long-connection"` (WSClient side effect, no
|
|
58
|
+
* public URL needed). Set to `"webhook"` for production with a public URL.
|
|
59
|
+
*/
|
|
60
|
+
mode?: LarkTransportMode | undefined;
|
|
61
|
+
/**
|
|
62
|
+
* Port the host eve server listens on. Used only in `"long-connection"`
|
|
63
|
+
* mode to compute the localhost webhook URL we POST forwarded events to.
|
|
64
|
+
* Defaults to `$PORT` or `2000` (matches `eve dev`).
|
|
65
|
+
*/
|
|
66
|
+
port?: number | undefined;
|
|
44
67
|
}
|
|
45
68
|
interface ResolvedLarkOptions {
|
|
46
69
|
appId: string;
|
|
@@ -61,6 +84,8 @@ interface ResolvedLarkOptions {
|
|
|
61
84
|
signatureSkewMs: number;
|
|
62
85
|
fetch: typeof fetch;
|
|
63
86
|
ackReaction: string | readonly string[] | false;
|
|
87
|
+
mode: LarkTransportMode;
|
|
88
|
+
port: number;
|
|
64
89
|
}
|
|
65
90
|
type LarkSenderType = "user" | "app";
|
|
66
91
|
type LarkChatType = "p2p" | "group";
|
package/dist/index.js
CHANGED
|
@@ -719,7 +719,8 @@ var DEFAULTS = {
|
|
|
719
719
|
maxRetries: 2,
|
|
720
720
|
tokenRefreshBufferMs: 5 * 60 * 1e3,
|
|
721
721
|
signatureSkewMs: 5 * 60 * 1e3,
|
|
722
|
-
ackReaction: "TYPING"
|
|
722
|
+
ackReaction: "TYPING",
|
|
723
|
+
mode: "long-connection"
|
|
723
724
|
};
|
|
724
725
|
var ENV_KEYS = {
|
|
725
726
|
appId: "LARK_APP_ID",
|
|
@@ -782,11 +783,127 @@ function resolveOptions(options, env = defaultEnv()) {
|
|
|
782
783
|
tokenRefreshBufferMs: options.tokenRefreshBufferMs ?? DEFAULTS.tokenRefreshBufferMs,
|
|
783
784
|
signatureSkewMs: options.signatureSkewMs ?? DEFAULTS.signatureSkewMs,
|
|
784
785
|
fetch: options.fetch ?? globalThis.fetch,
|
|
785
|
-
ackReaction: options.ackReaction ?? DEFAULTS.ackReaction
|
|
786
|
+
ackReaction: options.ackReaction ?? DEFAULTS.ackReaction,
|
|
787
|
+
mode: options.mode ?? DEFAULTS.mode,
|
|
788
|
+
port: options.port ?? (process.env.PORT ? Number(process.env.PORT) : 2e3)
|
|
786
789
|
};
|
|
787
790
|
}
|
|
788
791
|
__name(resolveOptions, "resolveOptions");
|
|
789
792
|
|
|
793
|
+
// src/long-connection.ts
|
|
794
|
+
import {
|
|
795
|
+
createCipheriv,
|
|
796
|
+
createHash as createHash2,
|
|
797
|
+
randomBytes
|
|
798
|
+
} from "crypto";
|
|
799
|
+
function aesEncrypt(plaintext, key) {
|
|
800
|
+
const keyBuf = createHash2("sha256").update(key).digest();
|
|
801
|
+
const iv = randomBytes(16);
|
|
802
|
+
const cipher = createCipheriv("aes-256-cbc", keyBuf, iv);
|
|
803
|
+
return Buffer.concat([iv, cipher.update(plaintext), cipher.final()]);
|
|
804
|
+
}
|
|
805
|
+
__name(aesEncrypt, "aesEncrypt");
|
|
806
|
+
function signBody(timestamp, nonce, body, key) {
|
|
807
|
+
return createHash2("sha256").update(timestamp + nonce + key).update(body).digest("hex");
|
|
808
|
+
}
|
|
809
|
+
__name(signBody, "signBody");
|
|
810
|
+
async function postEventToWebhook(event, opts) {
|
|
811
|
+
const fetchImpl = opts.fetch ?? globalThis.fetch;
|
|
812
|
+
const plain = Buffer.from(JSON.stringify(event), "utf8");
|
|
813
|
+
let body;
|
|
814
|
+
const headers = { "content-type": "application/json" };
|
|
815
|
+
if (opts.encryptKey) {
|
|
816
|
+
const encrypted = aesEncrypt(plain, opts.encryptKey).toString("base64");
|
|
817
|
+
body = Buffer.from(JSON.stringify({ encrypt: encrypted }), "utf8");
|
|
818
|
+
const ts = Math.floor(Date.now() / 1e3).toString();
|
|
819
|
+
const nonce = randomBytes(8).toString("hex");
|
|
820
|
+
const sig = signBody(ts, nonce, body, opts.encryptKey);
|
|
821
|
+
headers["x-lark-request-timestamp"] = ts;
|
|
822
|
+
headers["x-lark-request-nonce"] = nonce;
|
|
823
|
+
headers["x-lark-signature"] = `sha256=${sig}`;
|
|
824
|
+
} else {
|
|
825
|
+
body = plain;
|
|
826
|
+
}
|
|
827
|
+
const res = await fetchImpl(opts.eveWebhookUrl, {
|
|
828
|
+
method: "POST",
|
|
829
|
+
headers,
|
|
830
|
+
body: new Uint8Array(body.buffer, body.byteOffset, body.byteLength)
|
|
831
|
+
});
|
|
832
|
+
if (!res.ok) {
|
|
833
|
+
const text = await res.text().catch(() => "");
|
|
834
|
+
throw new Error(
|
|
835
|
+
`eve-lark: forward to ${opts.eveWebhookUrl} failed (HTTP ${res.status})${text ? `: ${text.slice(0, 200)}` : ""}`
|
|
836
|
+
);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
__name(postEventToWebhook, "postEventToWebhook");
|
|
840
|
+
function rebuildEnvelopeFromSdkEvent(eventType, data, ctx) {
|
|
841
|
+
const maybeHeader = data?.header;
|
|
842
|
+
const header = maybeHeader && typeof maybeHeader === "object" ? maybeHeader : {
|
|
843
|
+
event_id: `lc_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
844
|
+
event_type: eventType,
|
|
845
|
+
create_time: String(Math.floor(Date.now() / 1e3)),
|
|
846
|
+
token: ctx.verificationToken,
|
|
847
|
+
app_id: ctx.appId
|
|
848
|
+
};
|
|
849
|
+
const maybeEvent = data?.event;
|
|
850
|
+
return {
|
|
851
|
+
schema: "2.0",
|
|
852
|
+
header,
|
|
853
|
+
event: maybeEvent ?? data
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
__name(rebuildEnvelopeFromSdkEvent, "rebuildEnvelopeFromSdkEvent");
|
|
857
|
+
async function startLongConnection(args) {
|
|
858
|
+
const log = args.log ?? ((m) => console.log(`[eve-lark] ${m}`));
|
|
859
|
+
const logError = args.logError ?? ((m, e) => console.error(`[eve-lark] ${m}`, e ?? ""));
|
|
860
|
+
const sdk = args.sdk ?? await loadLarkSdk();
|
|
861
|
+
const dispatcher = new sdk.EventDispatcher({
|
|
862
|
+
verificationToken: args.resolved.verificationToken,
|
|
863
|
+
encryptKey: args.resolved.encryptKey
|
|
864
|
+
});
|
|
865
|
+
dispatcher.register({
|
|
866
|
+
"im.message.receive_v1": /* @__PURE__ */ __name(async (data) => {
|
|
867
|
+
try {
|
|
868
|
+
const envelope = rebuildEnvelopeFromSdkEvent("im.message.receive_v1", data, {
|
|
869
|
+
appId: args.resolved.appId,
|
|
870
|
+
verificationToken: args.resolved.verificationToken
|
|
871
|
+
});
|
|
872
|
+
await postEventToWebhook(envelope, {
|
|
873
|
+
eveWebhookUrl: args.eveWebhookUrl,
|
|
874
|
+
encryptKey: args.resolved.encryptKey
|
|
875
|
+
});
|
|
876
|
+
log(`\u2190 feishu \u2192 eve ok`);
|
|
877
|
+
} catch (e) {
|
|
878
|
+
logError(`forward failed`, e);
|
|
879
|
+
}
|
|
880
|
+
}, "im.message.receive_v1")
|
|
881
|
+
});
|
|
882
|
+
const domain = args.resolved.baseUrl.includes("larksuite.com") ? sdk.Domain.Lark : sdk.Domain.Feishu;
|
|
883
|
+
const wsClient = new sdk.WSClient({
|
|
884
|
+
appId: args.resolved.appId,
|
|
885
|
+
appSecret: args.resolved.appSecret,
|
|
886
|
+
domain,
|
|
887
|
+
onReady: /* @__PURE__ */ __name(() => log(`WS connected to Feishu (${args.resolved.baseUrl})`), "onReady"),
|
|
888
|
+
onError: /* @__PURE__ */ __name((err) => logError(`WS error`, err), "onError"),
|
|
889
|
+
onReconnecting: /* @__PURE__ */ __name(() => log(`WS reconnecting\u2026`), "onReconnecting"),
|
|
890
|
+
onReconnected: /* @__PURE__ */ __name(() => log(`WS reconnected`), "onReconnected"),
|
|
891
|
+
autoReconnect: true
|
|
892
|
+
});
|
|
893
|
+
await wsClient.start({ eventDispatcher: dispatcher });
|
|
894
|
+
}
|
|
895
|
+
__name(startLongConnection, "startLongConnection");
|
|
896
|
+
async function loadLarkSdk() {
|
|
897
|
+
try {
|
|
898
|
+
return await import("@larksuiteoapi/node-sdk");
|
|
899
|
+
} catch {
|
|
900
|
+
throw new Error(
|
|
901
|
+
'eve-lark: mode:"long-connection" requires @larksuiteoapi/node-sdk. Install it: pnpm add @larksuiteoapi/node-sdk (or npm/yarn equivalent).'
|
|
902
|
+
);
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
__name(loadLarkSdk, "loadLarkSdk");
|
|
906
|
+
|
|
790
907
|
// src/channel.ts
|
|
791
908
|
function larkContinuationToken(chatId, rootMessageId) {
|
|
792
909
|
return `${chatId}:${rootMessageId ?? "_"}`;
|
|
@@ -838,6 +955,12 @@ function createLarkChannel(optionsInput) {
|
|
|
838
955
|
const options = resolveOptions(optionsInput);
|
|
839
956
|
const client = new LarkClient(options);
|
|
840
957
|
const dedup = new DedupMap(options.dedupTtlMs, options.dedupMaxEntries);
|
|
958
|
+
if (options.mode === "long-connection") {
|
|
959
|
+
const eveWebhookUrl = `http://127.0.0.1:${options.port}${options.webhookPath}`;
|
|
960
|
+
void startLongConnection({ resolved: options, eveWebhookUrl }).catch((e) => {
|
|
961
|
+
console.error("[eve-lark] long-connection startup failed:", e);
|
|
962
|
+
});
|
|
963
|
+
}
|
|
841
964
|
const controllers = /* @__PURE__ */ new Map();
|
|
842
965
|
const sessionMeta = /* @__PURE__ */ new Map();
|
|
843
966
|
function getController(sessionId, meta) {
|
|
@@ -855,10 +978,6 @@ function createLarkChannel(optionsInput) {
|
|
|
855
978
|
return ctrl;
|
|
856
979
|
}
|
|
857
980
|
__name(getController, "getController");
|
|
858
|
-
function lookupMeta(sessionId) {
|
|
859
|
-
return sessionMeta.get(sessionId);
|
|
860
|
-
}
|
|
861
|
-
__name(lookupMeta, "lookupMeta");
|
|
862
981
|
function dropController(sessionId) {
|
|
863
982
|
controllers.delete(sessionId);
|
|
864
983
|
sessionMeta.delete(sessionId);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/channel.ts","../src/errors.ts","../src/lark-client.ts","../src/dedup.ts","../src/crypto.ts","../src/parse.ts","../src/card.ts","../src/streaming-controller.ts","../src/options.ts"],"sourcesContent":["import {\n defineChannel,\n POST,\n type Channel,\n type RouteHandlerArgs,\n} from \"eve/channels\";\n\nimport { LarkClient } from \"./lark-client.js\";\nimport { DedupMap } from \"./dedup.js\";\nimport { decryptPayload, verifySignature } from \"./crypto.js\";\nimport { parseInbound } from \"./parse.js\";\nimport { StreamingCardController } from \"./streaming-controller.js\";\nimport { buildTextCard } from \"./card.js\";\nimport { resolveOptions } from \"./options.js\";\nimport type {\n LarkChannelOptions,\n LarkContinuationToken,\n LarkEncryptedBody,\n LarkEventBody,\n LarkInboundFile,\n ResolvedLarkOptions,\n} from \"./types.js\";\n\n/**\n * Continuation token format: `${chatId}:${rootMessageId ?? \"_\"}`.\n * The framework prepends the channel file stem before handing the token to\n * the runtime; consumers should call this helper rather than concatenate.\n */\nexport function larkContinuationToken(\n chatId: string,\n rootMessageId: string | null,\n): LarkContinuationToken {\n return `${chatId}:${rootMessageId ?? \"_\"}` as LarkContinuationToken;\n}\n\ninterface LarkSessionMeta {\n chatId: string;\n rootId?: string | undefined;\n parentId?: string | undefined;\n}\n\n/**\n * Extract the chat metadata we stashed on `auth.initiator.attributes` when\n * starting the session. We can't keep this in a closure-scoped Map because eve\n * may run channel event handlers across a process/worker boundary from the\n * webhook handler — closure state doesn't survive. The auth attributes are\n * persisted with the session and surface cleanly through `ctx.session.auth`.\n */\nfunction metaFromCtx(ctx: { session?: { auth?: { initiator?: { attributes?: unknown } | null } | null } | null }): LarkSessionMeta | null {\n const attrs = (ctx.session?.auth?.initiator?.attributes ?? {}) as {\n chatId?: unknown;\n rootMessageId?: unknown;\n parentId?: unknown;\n };\n if (typeof attrs.chatId !== \"string\" || !attrs.chatId) return null;\n return {\n chatId: attrs.chatId,\n rootId: typeof attrs.rootMessageId === \"string\" ? attrs.rootMessageId : undefined,\n parentId: typeof attrs.parentId === \"string\" ? attrs.parentId : undefined,\n };\n}\n\nfunction ackOk(): Response {\n return Response.json({ code: 0 });\n}\n\n/**\n * Resolve the configured `ackReaction` to a single emoji type for this event,\n * or `false` if reactions are disabled. Picks randomly when given an array.\n */\nfunction pickAckEmoji(reaction: string | readonly string[] | false): string | false {\n if (typeof reaction === \"string\") return reaction;\n if (Array.isArray(reaction)) {\n if (reaction.length === 0) return false;\n const idx = Math.floor(Math.random() * reaction.length);\n return reaction[idx] ?? false;\n }\n return false;\n}\n\nfunction resourceUrl(\n options: ResolvedLarkOptions,\n file: LarkInboundFile,\n messageId: string,\n): string {\n const type = file.kind === \"image\" ? \"image\" : \"file\";\n return `${options.baseUrl}/open-apis/im/v1/messages/${encodeURIComponent(messageId)}/resources/${encodeURIComponent(file.fileKey)}?type=${type}`;\n}\n\n/**\n * Build the eve UserContent payload from a parsed inbound event. Text comes\n * first; each inbound image/file becomes a `file` part carrying a URL pointing\n * at the Lark resource endpoint. The channel's `fetchFile` hook will stage\n * those URLs to bytes when the model runs.\n */\nfunction buildUserContent(\n text: string,\n files: LarkInboundFile[],\n options: ResolvedLarkOptions,\n messageId: string,\n): unknown[] {\n const parts: unknown[] = [];\n if (text.length > 0) parts.push({ type: \"text\", text });\n for (const f of files) {\n parts.push({\n type: \"file\",\n data: new URL(resourceUrl(options, f, messageId)),\n mediaType: f.mediaType,\n });\n }\n return parts;\n}\n\n/**\n * Create a Lark/Feishu channel for the eve agent framework.\n *\n * The channel mounts a single POST webhook that verifies the request,\n * decrypts the body when an encrypt key is configured, deduplicates events\n * by id, parses the inbound message, and starts or resumes an eve session.\n *\n * Streaming happens via eve's native channel events: `message.appended`\n * drives live card patches, `message.completed` finalizes the card, and\n * `turn.failed` aborts it. In `replyMode: \"static\"` the controller is\n * skipped and `message.completed` delivers a single card.\n */\nexport function createLarkChannel(\n optionsInput: LarkChannelOptions,\n): Channel<undefined, Record<string, unknown>, Record<string, unknown>> {\n const options = resolveOptions(optionsInput);\n const client = new LarkClient(options);\n const dedup = new DedupMap(options.dedupTtlMs, options.dedupMaxEntries);\n\n // Channel-scoped (closure) state — shared across sessions on the same\n // process. Each session has its own controller + chat metadata, keyed by\n // session.id.\n const controllers = new Map<string, StreamingCardController>();\n const sessionMeta = new Map<string, LarkSessionMeta>();\n\n function getController(sessionId: string, meta: LarkSessionMeta): StreamingCardController {\n let ctrl = controllers.get(sessionId);\n if (!ctrl) {\n ctrl = new StreamingCardController(client, {\n chatId: meta.chatId,\n rootId: meta.rootId,\n parentId: meta.parentId,\n patchIntervalMs: options.streamPatchIntervalMs,\n createThresholdMs: options.streamCreateThresholdMs,\n });\n controllers.set(sessionId, ctrl);\n }\n return ctrl;\n }\n\n function lookupMeta(sessionId: string): LarkSessionMeta | undefined {\n return sessionMeta.get(sessionId);\n }\n\n function dropController(sessionId: string): void {\n controllers.delete(sessionId);\n sessionMeta.delete(sessionId);\n }\n\n const webhookHandler = async (\n req: Request,\n helpers: RouteHandlerArgs[\"send\"] extends never ? never : RouteHandlerArgs,\n ): Promise<Response> => {\n const rawBody = Buffer.from(await req.arrayBuffer());\n\n // 1) Skew check (only enforced when a real timestamp header is present)\n const tsHeader = req.headers.get(\"x-lark-request-timestamp\") ?? \"\";\n const ts = Number(tsHeader);\n if (\n tsHeader &&\n Number.isFinite(ts) &&\n ts > 0 &&\n Math.abs(Date.now() / 1000 - ts) > options.signatureSkewMs / 1000\n ) {\n return new Response(\"request timestamp out of skew window\", { status: 408 });\n }\n\n // 2) Signature verify + AES decrypt when encryptKey configured\n let workingBody: Buffer = rawBody;\n if (options.encryptKey) {\n const nonce = req.headers.get(\"x-lark-request-nonce\") ?? \"\";\n const sigHeader = req.headers.get(\"x-lark-signature\");\n if (!sigHeader) return new Response(\"missing signature\", { status: 401 });\n const ok = verifySignature({\n timestamp: tsHeader,\n nonce,\n encryptKey: options.encryptKey,\n rawBody,\n signatureHeader: sigHeader,\n });\n if (!ok) return new Response(\"bad signature\", { status: 401 });\n\n try {\n const envelope = JSON.parse(rawBody.toString(\"utf8\")) as LarkEncryptedBody;\n if (envelope.encrypt) {\n workingBody = decryptPayload(envelope.encrypt, options.encryptKey) as Buffer;\n }\n } catch {\n return new Response(\"decrypt failed\", { status: 400 });\n }\n }\n\n // 3) Parse body\n let body: LarkEventBody;\n try {\n body = JSON.parse(workingBody.toString(\"utf8\")) as LarkEventBody;\n } catch {\n return new Response(\"invalid json\", { status: 400 });\n }\n\n // 4) url_verification short-circuit\n if (body.type === \"url_verification\") {\n return Response.json({ challenge: body.challenge ?? \"\" });\n }\n\n // 5) Schema check\n if (body.schema !== \"2.0\") {\n return ackOk();\n }\n\n // 6) Verification-token check\n if (body.header?.token !== options.verificationToken) {\n return new Response(\"verification token mismatch\", { status: 401 });\n }\n\n // 7) Dedup\n const dedupKey = body.header?.event_id ?? body.event?.message?.message_id;\n if (dedupKey) {\n if (dedup.has(dedupKey)) return ackOk();\n dedup.set(dedupKey);\n }\n\n // 8) Event filter — only handle text messages in v1\n if (body.header?.event_type !== \"im.message.receive_v1\") {\n return ackOk();\n }\n if (!body.event) return ackOk();\n\n // 9) Parse\n const parsed = parseInbound(body.event, options.botOpenId);\n\n // 10) Self-message suppression\n if (parsed.senderType === \"app\") {\n return ackOk();\n }\n\n // 11) Skip unsupported message types\n if (parsed.text === \"\" && parsed.files.length === 0) {\n return ackOk();\n }\n\n // 12) Build session inputs\n const userContent = buildUserContent(parsed.text, parsed.files, options, parsed.messageId);\n const continuationToken = larkContinuationToken(parsed.chatId, parsed.parentId ?? parsed.rootId);\n const auth = {\n authenticator: \"lark\",\n principalType: \"user\",\n principalId: parsed.senderOpenId,\n attributes: {\n chatId: parsed.chatId,\n rootMessageId: parsed.rootId,\n messageId: parsed.messageId,\n chatType: parsed.chatType,\n },\n };\n\n // 13) Start/resume session. Cast userContent because eve's UserContent\n // comes from the `ai` package and we intentionally don't depend on it;\n // our shape is structurally compatible (string | Array<TextPart|FilePart>).\n const session = await helpers.send(userContent as never, {\n auth: auth as never,\n continuationToken,\n });\n\n // 14) Remember chat metadata keyed by session.id so event handlers below\n // can look up where to deliver replies.\n sessionMeta.set(session.id, {\n chatId: parsed.chatId,\n rootId: parsed.rootId ?? undefined,\n parentId: parsed.parentId ?? undefined,\n });\n console.log(\"[eve-lark] session registered\", session.id, \"chat:\", parsed.chatId);\n\n // 15) Ack reaction — fire-and-forget in the background so the webhook\n // returns immediately. Best-effort: a failed reaction is logged and\n // swallowed (the user will still see the streaming card eventually).\n const emoji = pickAckEmoji(options.ackReaction);\n if (emoji) {\n helpers.waitUntil(\n client\n .addReaction({ messageId: parsed.messageId, emojiType: emoji })\n .catch((e) => {\n console.warn(\"[eve-lark] ack reaction failed\", e);\n }),\n );\n }\n\n return ackOk();\n };\n\n return defineChannel({\n routes: [POST(options.webhookPath, webhookHandler as never)],\n\n fetchFile: async (url: string) => {\n if (!url.startsWith(options.baseUrl)) return null;\n const m = url.match(/\\/messages\\/([^/]+)\\/resources\\/([^?]+)\\?type=(image|file)/);\n if (!m || !m[1] || !m[2] || !m[3]) return null;\n return client.downloadResource({\n messageId: m[1],\n fileKey: m[2],\n type: m[3] as \"image\" | \"file\",\n });\n },\n\n events: {\n // Streaming delta — patch the card.\n \"message.appended\"(data, _channel, ctx) {\n console.log(\"[eve-lark] message.appended\", ctx.session.id);\n if (options.replyMode !== \"streaming\") return;\n const sessionId = ctx.session.id;\n const meta = metaFromCtx(ctx);\n if (!meta) {\n console.log(\"[eve-lark] message.appended: no meta for\", sessionId);\n return;\n }\n const d = data as { messageDelta?: string; messageSoFar?: string };\n const ctrl = getController(sessionId, meta);\n if (typeof d.messageDelta === \"string\") {\n ctrl.appendDelta(d.messageDelta);\n }\n },\n\n // Terminal — finalize the card OR deliver a fresh one in static mode.\n async \"message.completed\"(data, _channel, ctx) {\n console.log(\"[eve-lark] message.completed\", ctx.session.id);\n const sessionId = ctx.session.id;\n const meta = metaFromCtx(ctx);\n if (!meta) {\n console.log(\"[eve-lark] message.completed: no meta for\", sessionId);\n return;\n }\n const d = data as { message?: string | null };\n const text = typeof d.message === \"string\" ? d.message : \"\";\n\n if (options.replyMode === \"streaming\") {\n const ctrl = getController(sessionId, meta);\n try {\n await ctrl.finalize(text);\n console.log(\"[eve-lark] finalize done for\", sessionId);\n } catch (e) {\n console.log(\"[eve-lark] finalize failed for\", sessionId, e);\n }\n dropController(sessionId);\n return;\n }\n\n // Static mode: single shot delivery.\n try {\n await client.sendCard({\n chatId: meta.chatId,\n card: buildTextCard(text),\n rootId: meta.rootId,\n parentId: meta.parentId,\n });\n console.log(\"[eve-lark] static sendCard done for\", sessionId);\n } catch (e) {\n console.log(\"[eve-lark] static sendCard failed:\", e);\n await client.sendText({\n chatId: meta.chatId,\n content: text,\n rootId: meta.rootId,\n parentId: meta.parentId,\n });\n }\n dropController(sessionId);\n },\n\n async \"turn.failed\"(data, _channel, ctx) {\n console.log(\"[eve-lark] turn.failed\", ctx?.session?.id);\n const sessionId = ctx?.session?.id;\n if (!sessionId) return;\n const meta = metaFromCtx(ctx);\n if (!meta) return;\n const d = data as { error?: { message?: string } | string };\n const errMsg = typeof d === \"object\" && d !== null && \"error\" in d\n ? typeof d.error === \"string\"\n ? d.error\n : d.error?.message ?? \"turn failed\"\n : \"turn failed\";\n\n if (options.replyMode === \"streaming\") {\n const ctrl = controllers.get(sessionId);\n if (ctrl) {\n try {\n await ctrl.abort(errMsg);\n } catch {\n // best-effort\n }\n }\n }\n dropController(sessionId);\n },\n\n async \"session.failed\"(data, _channel) {\n // No ctx for this event — we can't look up by session.id. Use the\n // continuationToken from channel ops to find the session by meta\n // match would be unreliable; instead log and move on.\n const d = data as { error?: { message?: string } };\n const errMsg = d?.error?.message ?? \"session failed\";\n // Best-effort: drop any controllers we know about. In practice this\n // event is rare and a missed cleanup just GCs on next process restart.\n for (const [, ctrl] of controllers) {\n try {\n await ctrl.abort(errMsg);\n } catch {\n // best-effort\n }\n }\n controllers.clear();\n sessionMeta.clear();\n },\n },\n });\n}\n","/**\n * Typed error hierarchy for eve-lark.\n *\n * All errors extend a common base so consumers can `instanceof LarkChannelError`\n * to catch anything thrown by the channel.\n */\n\nexport class LarkChannelError extends Error {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = new.target.name;\n }\n}\n\nexport class LarkConfigError extends LarkChannelError {}\n\nexport class LarkSignatureError extends LarkChannelError {}\n\nexport class LarkDecryptError extends LarkChannelError {}\n\nexport interface LarkApiErrorBody {\n code?: number | undefined;\n msg?: string | undefined;\n}\n\nexport class LarkApiError extends LarkChannelError {\n readonly code: number | undefined;\n readonly body: LarkApiErrorBody | undefined;\n readonly status: number | undefined;\n\n constructor(\n message: string,\n opts?: {\n code?: number | undefined;\n body?: LarkApiErrorBody | undefined;\n status?: number | undefined;\n cause?: unknown;\n },\n ) {\n super(message, { cause: opts?.cause });\n this.code = opts?.code;\n this.body = opts?.body;\n this.status = opts?.status;\n }\n}\n","import { LarkApiError, type LarkApiErrorBody } from \"./errors.js\";\nimport type { LarkCard, ResolvedLarkOptions } from \"./types.js\";\n\ninterface TokenState {\n value: string;\n expiresAt: number;\n}\n\nconst TOKEN_INVALID_CODES = new Set<number>([99991663, 99991664, 99991661]);\n\ninterface RequestResult {\n status: number;\n body: unknown;\n retryAfter: number | null;\n}\n\nexport class LarkClient {\n private readonly options: ResolvedLarkOptions;\n private token: TokenState | null = null;\n private refreshPromise: Promise<string> | null = null;\n\n constructor(options: ResolvedLarkOptions) {\n this.options = options;\n }\n\n async getTenantAccessToken(): Promise<string> {\n if (\n this.token &&\n Date.now() + this.options.tokenRefreshBufferMs < this.token.expiresAt\n ) {\n return this.token.value;\n }\n if (this.refreshPromise) return this.refreshPromise;\n this.refreshPromise = this.#refresh();\n try {\n return await this.refreshPromise;\n } finally {\n this.refreshPromise = null;\n }\n }\n\n async #refresh(): Promise<string> {\n const body = {\n app_id: this.options.appId,\n app_secret: this.options.appSecret,\n };\n const res = await this.options.fetch(\n `${this.options.baseUrl}/open-apis/auth/v3/tenant_access_token/internal`,\n {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(this.options.requestTimeoutMs),\n },\n );\n if (!res.ok) {\n throw new LarkApiError(\n `eve-lark: token refresh failed (HTTP ${res.status})`,\n { status: res.status },\n );\n }\n const json = (await res.json()) as { code?: number; tenant_access_token?: string; expire?: number; msg?: string };\n if (json.code !== 0 || !json.tenant_access_token) {\n throw new LarkApiError(\n `eve-lark: token refresh returned code=${json.code ?? \"?\"} msg=${json.msg ?? \"?\"}`,\n { body: json, code: json.code },\n );\n }\n const expireSec = typeof json.expire === \"number\" ? json.expire : 7200;\n this.token = {\n value: json.tenant_access_token,\n expiresAt: Date.now() + expireSec * 1000,\n };\n return this.token.value;\n }\n\n async sendText(args: {\n chatId: string;\n content: string;\n rootId?: string;\n parentId?: string;\n }): Promise<{ messageId: string }> {\n const content = JSON.stringify({ text: args.content });\n return this.#sendMessage({\n receive_id: args.chatId,\n msg_type: \"text\",\n content,\n root_id: args.rootId,\n parent_id: args.parentId,\n });\n }\n\n async sendCard(args: {\n chatId: string;\n card: LarkCard;\n rootId?: string;\n parentId?: string;\n }): Promise<{ messageId: string }> {\n const content = JSON.stringify(args.card);\n return this.#sendMessage({\n receive_id: args.chatId,\n msg_type: \"interactive\",\n content,\n root_id: args.rootId,\n parent_id: args.parentId,\n });\n }\n\n async #sendMessage(body: Record<string, unknown>): Promise<{ messageId: string }> {\n const payload = Object.fromEntries(\n Object.entries(body).filter(([, v]) => v !== undefined),\n );\n const json = await this.#request(\"POST\", \"/open-apis/im/v1/messages?receive_id_type=chat_id\", payload);\n const messageId = (json as { data?: { message_id?: string } }).data?.message_id;\n if (!messageId) {\n throw new LarkApiError(\"eve-lark: send missing message_id in response\", {\n body: json as LarkApiErrorBody,\n });\n }\n return { messageId };\n }\n\n async patchCard(args: { messageId: string; card: LarkCard }): Promise<void> {\n await this.#request(\n \"PATCH\",\n `/open-apis/im/v1/messages/${encodeURIComponent(args.messageId)}`,\n { content: JSON.stringify(args.card) },\n );\n }\n\n async downloadResource(args: {\n messageId: string;\n fileKey: string;\n type: \"image\" | \"file\";\n }): Promise<Buffer> {\n const path = `/open-apis/im/v1/messages/${encodeURIComponent(args.messageId)}/resources/${encodeURIComponent(args.fileKey)}?type=${args.type}`;\n const token = await this.getTenantAccessToken();\n const res = await this.options.fetch(`${this.options.baseUrl}${path}`, {\n method: \"GET\",\n headers: { authorization: `Bearer ${token}` },\n signal: AbortSignal.timeout(this.options.requestTimeoutMs),\n });\n if (!res.ok) {\n throw new LarkApiError(\n `eve-lark: downloadResource HTTP ${res.status}`,\n { status: res.status },\n );\n }\n return Buffer.from(await res.arrayBuffer());\n }\n\n async addReaction(args: {\n messageId: string;\n emojiType: string;\n }): Promise<{ reactionId: string }> {\n const path = `/open-apis/im/v1/messages/${encodeURIComponent(args.messageId)}/reactions`;\n const json = (await this.#request(\"POST\", path, {\n reaction_type: { emoji_type: args.emojiType },\n })) as { data?: { reaction_id?: string } };\n const reactionId = json.data?.reaction_id;\n if (!reactionId) {\n throw new LarkApiError(\"eve-lark: addReaction missing reaction_id\", {\n body: json as LarkApiErrorBody,\n });\n }\n return { reactionId };\n }\n\n async removeReaction(args: { messageId: string; reactionId: string }): Promise<void> {\n const path = `/open-apis/im/v1/messages/${encodeURIComponent(args.messageId)}/reactions/${encodeURIComponent(args.reactionId)}`;\n await this.#request(\"DELETE\", path, undefined);\n }\n\n /**\n * Central request wrapper with auth, retry, and Feishu error decoding.\n *\n * Retries on 429 (honoring Retry-After), 5xx (exponential backoff + jitter),\n * and token-invalid responses (one refresh + one retry). Other 4xx throws\n * LarkApiError with the Feishu code/msg from the body.\n */\n async #request(method: string, path: string, body: unknown): Promise<unknown> {\n const url = `${this.options.baseUrl}${path}`;\n let token = await this.getTenantAccessToken();\n let tokenRefreshed = false;\n\n for (let attempt = 0; attempt <= this.options.maxRetries; attempt++) {\n const res = await this.options.fetch(url, {\n method,\n headers: {\n authorization: `Bearer ${token}`,\n \"content-type\": \"application/json\",\n },\n body: body === undefined ? undefined : JSON.stringify(body),\n signal: AbortSignal.timeout(this.options.requestTimeoutMs),\n });\n\n const result = await this.#consumeResponse(res);\n const status = result.status;\n\n if (status >= 200 && status < 300) {\n const jsonBody = result.body as { code?: number; msg?: string };\n if (jsonBody && typeof jsonBody.code === \"number\" && jsonBody.code !== 0) {\n if (TOKEN_INVALID_CODES.has(jsonBody.code) && !tokenRefreshed) {\n this.token = null;\n token = await this.getTenantAccessToken();\n tokenRefreshed = true;\n attempt -= 1;\n continue;\n }\n throw new LarkApiError(\n `eve-lark: ${method} ${path} failed code=${jsonBody.code} msg=${jsonBody.msg ?? \"?\"}`,\n { code: jsonBody.code, body: jsonBody as LarkApiErrorBody, status },\n );\n }\n return result.body;\n }\n\n if (status === 401 && !tokenRefreshed) {\n this.token = null;\n token = await this.getTenantAccessToken();\n tokenRefreshed = true;\n attempt -= 1;\n continue;\n }\n\n const retryable =\n status === 429 || (status >= 500 && status < 600);\n if (retryable && attempt < this.options.maxRetries) {\n const delayMs = this.#computeBackoff(status, result.retryAfter, attempt);\n await sleep(delayMs);\n continue;\n }\n\n const bodyObj = result.body as LarkApiErrorBody | undefined;\n const code = bodyObj?.code;\n const msg = bodyObj?.msg;\n const detail = msg ? ` code=${code ?? \"?\"} msg=${msg}` : \"\";\n throw new LarkApiError(\n `eve-lark: ${method} ${path} failed HTTP ${status}${detail}`,\n { status, body: bodyObj, code },\n );\n }\n throw new LarkApiError(`eve-lark: ${method} ${path} exhausted retries`);\n }\n\n async #consumeResponse(res: Response): Promise<RequestResult> {\n const retryAfterRaw = res.headers.get(\"retry-after\");\n const retryAfter = retryAfterRaw ? parseRetryAfter(retryAfterRaw) : null;\n const text = await res.text();\n if (!text) {\n return { status: res.status, body: undefined, retryAfter };\n }\n try {\n return { status: res.status, body: JSON.parse(text), retryAfter };\n } catch {\n return { status: res.status, body: { raw: text }, retryAfter };\n }\n }\n\n #computeBackoff(status: number, retryAfter: number | null, attempt: number): number {\n if (status === 429 && retryAfter !== null) {\n return Math.min(retryAfter * 1000, 10_000);\n }\n const base = 300 * Math.pow(2, attempt);\n const jitter = base * 0.2 * (Math.random() * 2 - 1);\n return Math.max(0, Math.round(base + jitter));\n }\n}\n\nfunction parseRetryAfter(raw: string): number | null {\n const sec = Number(raw);\n if (Number.isFinite(sec) && sec >= 0) return sec;\n const date = Date.parse(raw);\n if (Number.isFinite(date)) return Math.max(0, (date - Date.now()) / 1000);\n return null;\n}\n\nfunction sleep(ms: number): Promise<void> {\n if (ms <= 0) return Promise.resolve();\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","/**\n * In-process deduplication for Feishu webhook events.\n *\n * Feishu retries delivery on non-2xx responses and during brief outage windows,\n * so consumers must idempotently ack events they've already seen. We key by\n * `header.event_id` (or `message.message_id` as a fallback) and remember each\n * key for the TTL window.\n *\n * Backed by an insertion-ordered Map so FIFO eviction is O(1) at the front.\n * Lazy sweep on every insert prevents unbounded growth of expired entries;\n * no `setInterval` so this is safe in serverless.\n */\nexport class DedupMap {\n private readonly entries = new Map<string, number>();\n private readonly ttlMs: number;\n private readonly maxEntries: number;\n private insertsSinceSweep = 0;\n\n constructor(ttlMs: number, maxEntries: number) {\n this.ttlMs = ttlMs;\n this.maxEntries = maxEntries;\n }\n\n has(key: string): boolean {\n const at = this.entries.get(key);\n if (at === undefined) return false;\n if (Date.now() - at > this.ttlMs) {\n this.entries.delete(key);\n return false;\n }\n return true;\n }\n\n set(key: string): void {\n this.maybeSweep();\n // Refresh timestamp by re-inserting at the tail of insertion order.\n this.entries.delete(key);\n this.entries.set(key, Date.now());\n\n while (this.entries.size > this.maxEntries) {\n const oldestKey = this.entries.keys().next().value;\n if (oldestKey === undefined) break;\n this.entries.delete(oldestKey);\n }\n }\n\n /**\n * Walk the insertion-ordered map from the front and drop expired entries.\n * Stops at the first non-expired entry since events arrive roughly in time\n * order. Called on every set, so cost is amortized.\n */\n private maybeSweep(): void {\n this.insertsSinceSweep += 1;\n if (this.insertsSinceSweep < 64 && this.entries.size < this.maxEntries) {\n return;\n }\n this.insertsSinceSweep = 0;\n const now = Date.now();\n for (const [key, at] of this.entries) {\n if (now - at <= this.ttlMs) break;\n this.entries.delete(key);\n }\n }\n}\n","import { createDecipheriv, createHash, timingSafeEqual } from \"node:crypto\";\nimport { LarkDecryptError } from \"./errors.js\";\n\n/**\n * Verify an `X-Lark-Signature` header against the raw webhook body.\n *\n * Feishu computes: `sha256(timestamp + nonce + encrypt_key + body)` and ships\n * the hex digest (optionally prefixed with `sha256=`) in `X-Lark-Signature`.\n * We concatenate the string parts first, then the raw bytes of the body, to\n * avoid a UTF-8 round-trip on the request body.\n *\n * Constant-time compare. Returns false on length mismatch instead of throwing.\n */\nexport function verifySignature(opts: {\n timestamp: string;\n nonce: string;\n encryptKey: string;\n rawBody: Buffer;\n signatureHeader: string;\n}): boolean {\n const expected = opts.signatureHeader.replace(/^sha256=/, \"\");\n const computed = createHash(\"sha256\")\n .update(opts.timestamp + opts.nonce + opts.encryptKey)\n .update(opts.rawBody)\n .digest(\"hex\");\n const a = Buffer.from(computed, \"hex\");\n const b = Buffer.from(expected, \"hex\");\n return a.length === b.length && timingSafeEqual(a, b);\n}\n\n/**\n * Decrypt the `encrypt` field from a Feishu webhook body.\n *\n * Layout:\n * key = SHA256(encrypt_key) // 32 bytes → AES-256\n * buf = base64decode(encrypt_field)\n * iv = buf[0:16]\n * ct = buf[16:] // AES-256-CBC ciphertext\n * plaintext = AES_256_CBC_decrypt(key, iv, ct) // PKCS#7 unpadded\n *\n * Returns the raw plaintext bytes. The caller is expected to JSON.parse them.\n */\nexport function decryptPayload(encryptB64: string, encryptKey: string): Buffer {\n const key = createHash(\"sha256\").update(encryptKey).digest();\n const buf = Buffer.from(encryptB64, \"base64\");\n\n if (buf.length < 32) {\n throw new LarkDecryptError(\n `eve-lark: ciphertext too short (${buf.length} bytes; need >= 32 for IV + one block)`,\n );\n }\n if ((buf.length - 16) % 16 !== 0) {\n throw new LarkDecryptError(\n `eve-lark: ciphertext length ${buf.length} is not 16 + N*16`,\n );\n }\n\n const iv = buf.subarray(0, 16);\n const ct = buf.subarray(16);\n const dec = createDecipheriv(\"aes-256-cbc\", key, iv);\n\n try {\n return Buffer.concat([dec.update(ct), dec.final()]);\n } catch (e) {\n throw new LarkDecryptError(\"eve-lark: AES decrypt failed (bad padding or wrong key)\", {\n cause: e,\n });\n }\n}\n","import type {\n LarkInboundEvent,\n LarkInboundFile,\n LarkInboundResult,\n LarkMention,\n LarkRawMention,\n} from \"./types.js\";\n\nconst MIME_BY_EXT: Record<string, string> = {\n pdf: \"application/pdf\",\n zip: \"application/zip\",\n gz: \"application/gzip\",\n tar: \"application/x-tar\",\n doc: \"application/msword\",\n docx: \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\",\n xls: \"application/vnd.ms-excel\",\n xlsx: \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\",\n ppt: \"application/vnd.ms-powerpoint\",\n pptx: \"application/vnd.openxmlformats-officedocument.presentationml.presentation\",\n csv: \"text/csv\",\n txt: \"text/plain\",\n md: \"text/markdown\",\n json: \"application/json\",\n xml: \"application/xml\",\n html: \"text/html\",\n htm: \"text/html\",\n png: \"image/png\",\n jpg: \"image/jpeg\",\n jpeg: \"image/jpeg\",\n gif: \"image/gif\",\n webp: \"image/webp\",\n mp3: \"audio/mpeg\",\n wav: \"audio/wav\",\n mp4: \"video/mp4\",\n mov: \"video/quicktime\",\n};\n\nfunction mimeFromExt(filename: string | undefined): string {\n if (!filename) return \"application/octet-stream\";\n const dot = filename.lastIndexOf(\".\");\n if (dot < 0) return \"application/octet-stream\";\n return MIME_BY_EXT[filename.slice(dot + 1).toLowerCase()] ?? \"application/octet-stream\";\n}\n\nfunction mentionFromRaw(m: LarkRawMention, botOpenId: string | undefined): LarkMention {\n const isOpenIdOfBot =\n !!botOpenId && !!m.id.open_id && m.id.open_id === botOpenId;\n const isAll = !!m.id.open_id && m.id.open_id === \"all\";\n return {\n key: m.key,\n id: {\n openId: m.id.open_id,\n userId: m.id.user_id,\n unionId: m.id.union_id,\n },\n name: m.name,\n idType: m.id_type ?? \"open_id\",\n isOpenIdOfBot,\n isAll,\n };\n}\n\nfunction stripBotMentions(text: string, mentions: LarkMention[]): string {\n // Feishu ships mentions as opaque placeholders (e.g. \"@_user_1\") in the text\n // body alongside a structured mentions array. Rewrite them to something the\n // model can read:\n // - the bot itself: dropped (the model already knows it's being addressed)\n // - @all: replaced with a literal \"@all\" token\n // - other users: replaced with \"@<display name>\"\n let out = text;\n for (const m of mentions) {\n if (!m.key) continue;\n if (m.isOpenIdOfBot) {\n out = out.split(m.key).join(\"\");\n } else if (m.isAll) {\n out = out.split(m.key).join(\"@all\");\n } else {\n out = out.split(m.key).join(`@${m.name}`);\n }\n }\n return out.replace(/\\s+/g, \" \").trim();\n}\n\ninterface ParsedContent {\n text: string;\n files: LarkInboundFile[];\n}\n\nfunction parseContent(messageType: string, rawContent: string): ParsedContent {\n if (!rawContent) return { text: \"\", files: [] };\n let content: Record<string, unknown>;\n try {\n content = JSON.parse(rawContent) as Record<string, unknown>;\n } catch {\n return { text: \"\", files: [] };\n }\n\n switch (messageType) {\n case \"text\": {\n const text = typeof content.text === \"string\" ? content.text : \"\";\n return { text, files: [] };\n }\n case \"image\": {\n const imageKey = typeof content.image_key === \"string\" ? content.image_key : \"\";\n if (!imageKey) return { text: \"\", files: [] };\n return {\n text: \"\",\n files: [{ fileKey: imageKey, mediaType: \"image/png\", kind: \"image\" }],\n };\n }\n case \"file\": {\n const fileKey = typeof content.file_key === \"string\" ? content.file_key : \"\";\n if (!fileKey) return { text: \"\", files: [] };\n const fileName = typeof content.file_name === \"string\" ? content.file_name : undefined;\n return {\n text: \"\",\n files: [{ fileKey, mediaType: mimeFromExt(fileName), kind: \"file\" }],\n };\n }\n case \"post\": {\n const locale = (content.zh_cn ?? content.en_us ?? content.ja_jp ?? null) as\n | { content?: unknown[][] }\n | null;\n if (!locale?.content) return { text: \"\", files: [] };\n const text = locale.content\n .flatMap((line) =>\n (line ?? [])\n .filter((node): node is { tag: string; text?: unknown } => {\n if (typeof node !== \"object\" || node === null) return false;\n const tag = (node as { tag?: unknown }).tag;\n const text = (node as { text?: unknown }).text;\n return tag === \"text\" && typeof text === \"string\";\n })\n .map((node) => node.text as string),\n )\n .join(\" \");\n return { text, files: [] };\n }\n default:\n // audio, media, sticker, share_chat, share_user, interactive — not in v1 scope.\n return { text: \"\", files: [] };\n }\n}\n\nexport function parseInbound(\n event: LarkInboundEvent,\n botOpenId?: string,\n): LarkInboundResult {\n const messageType = event.message.message_type;\n const parsed = parseContent(messageType, event.message.content);\n const rawMentions = event.message.mentions ?? [];\n const mentions = rawMentions.map((m) => mentionFromRaw(m, botOpenId));\n\n const senderOpenId =\n event.sender.sender_id.open_id ??\n event.sender.sender_id.user_id ??\n event.sender.sender_id.union_id ??\n \"\";\n\n const text =\n messageType === \"text\"\n ? stripBotMentions(parsed.text, mentions)\n : parsed.text;\n\n const chatType = event.chat_type === \"group\" ? \"group\" : \"p2p\";\n const senderType = event.sender.sender_type === \"app\" ? \"app\" : \"user\";\n\n return {\n text,\n files: parsed.files,\n chatId: event.message.chat_id,\n rootId: event.message.root_id ?? null,\n parentId: event.message.parent_id ?? null,\n messageId: event.message.message_id,\n senderOpenId,\n senderType,\n chatType,\n mentions,\n };\n}\n","import type { LarkCard } from \"./types.js\";\n\nconst BASE_CONFIG = {\n wide_screen_mode: true,\n update_multi: true,\n} as const;\n\n/**\n * Build a simple single-shot card with the given markdown text.\n */\nexport function buildTextCard(text: string): LarkCard {\n return {\n config: { ...BASE_CONFIG },\n elements: [{ tag: \"markdown\", content: text }],\n };\n}\n\n/**\n * Build a streaming card with an optional status prefix and an answer buffer.\n *\n * Format:\n * <optional status prefix in muted tone>\n * <buffer>\n */\nexport function buildStreamingCard(opts: { buffer: string; status?: string | undefined }): LarkCard {\n const lines: string[] = [];\n if (opts.status) {\n lines.push(`<font color='grey'>${opts.status}</font>`);\n }\n lines.push(opts.buffer.length > 0 ? opts.buffer : \"_…_\");\n return {\n config: { ...BASE_CONFIG },\n elements: [{ tag: \"markdown\", content: lines.join(\"\\n\\n\") }],\n };\n}\n\n/**\n * Build an error card displayed when a turn fails.\n */\nexport function buildErrorCard(message: string): LarkCard {\n return {\n config: { ...BASE_CONFIG },\n elements: [\n { tag: \"markdown\", content: `<font color='red'>⚠ ${message}</font>` },\n ],\n };\n}\n","import { buildErrorCard, buildStreamingCard, buildTextCard } from \"./card.js\";\nimport type { LarkCard } from \"./types.js\";\n\ntype State = \"idle\" | \"creating\" | \"streaming\" | \"completed\" | \"aborted\";\n\ninterface ControllerDeps {\n chatId: string;\n rootId?: string | undefined;\n parentId?: string | undefined;\n patchIntervalMs: number;\n createThresholdMs: number;\n}\n\ninterface LarkClientLike {\n sendCard(args: {\n chatId: string;\n card: LarkCard;\n rootId?: string;\n parentId?: string;\n }): Promise<{ messageId: string }>;\n patchCard(args: { messageId: string; card: LarkCard }): Promise<void>;\n sendText(args: {\n chatId: string;\n content: string;\n rootId?: string;\n parentId?: string;\n }): Promise<{ messageId: string }>;\n}\n\n/**\n * Streaming interactive-card state machine.\n *\n * idle ──first delta──> creating ──sendCard ok──> streaming ──finalize──> completed\n * │\n * └──sendCard fail──> aborted (flag; static fallback on message.completed)\n *\n * The card is created lazily after `createThresholdMs` of the first delta, so\n * short turns can short-circuit straight to `finalize` (which sends the card\n * with the full answer in one shot). Once streaming, patches are throttled to\n * `patchIntervalMs`.\n *\n * If `sendCard` fails on creation, the controller flips to `fallbackToText`\n * and `finalize`/`ensureFinalized` deliver via `sendText` instead.\n */\nexport class StreamingCardController {\n private readonly deps: ControllerDeps;\n private readonly client: LarkClientLike;\n\n private state: State = \"idle\";\n private buffer = \"\";\n private status: string | undefined;\n private messageId: string | undefined;\n private fallbackToText = false;\n\n private createTimer: ReturnType<typeof setTimeout> | null = null;\n private patchInFlight: Promise<void> | null = null;\n private patchScheduled = false;\n private lastPatchAt = 0;\n\n constructor(client: LarkClientLike, deps: ControllerDeps) {\n this.client = client;\n this.deps = deps;\n }\n\n appendDelta(text: string): void {\n if (this.state === \"completed\" || this.state === \"aborted\") return;\n this.buffer += text;\n if (this.state === \"idle\") {\n this.scheduleCreate();\n } else if (this.state === \"streaming\") {\n this.schedulePatch();\n }\n }\n\n setStatus(status: string): void {\n if (this.state === \"completed\" || this.state === \"aborted\") return;\n this.status = status;\n if (this.state === \"streaming\") {\n this.schedulePatch();\n }\n }\n\n async finalize(fullText: string): Promise<void> {\n if (this.state === \"completed\" || this.state === \"aborted\") return;\n this.cancelCreateTimer();\n this.buffer = fullText;\n\n if (this.fallbackToText) {\n await this.client.sendText({\n chatId: this.deps.chatId,\n content: fullText,\n rootId: this.deps.rootId,\n parentId: this.deps.parentId,\n });\n this.state = \"completed\";\n return;\n }\n\n if (this.messageId === undefined) {\n // Never managed to create a card. Send one with the full text in a\n // single shot so the user still gets a card reply.\n try {\n const res = await this.client.sendCard({\n chatId: this.deps.chatId,\n card: buildTextCard(fullText),\n rootId: this.deps.rootId,\n parentId: this.deps.parentId,\n });\n this.messageId = res.messageId;\n this.state = \"completed\";\n } catch {\n // Last-resort fallback: plain text.\n this.fallbackToText = true;\n await this.client.sendText({\n chatId: this.deps.chatId,\n content: fullText,\n rootId: this.deps.rootId,\n parentId: this.deps.parentId,\n });\n this.state = \"completed\";\n }\n return;\n }\n\n // Card already exists; flush the final state.\n if (this.patchInFlight) {\n try {\n await this.patchInFlight;\n } catch {\n // swallow; we'll attempt the final patch below\n }\n }\n await this.client.patchCard({\n messageId: this.messageId,\n card: buildStreamingCard({ buffer: fullText, status: undefined }),\n });\n this.state = \"completed\";\n }\n\n async abort(error: string): Promise<void> {\n if (this.state === \"completed\" || this.state === \"aborted\") return;\n this.cancelCreateTimer();\n if (this.messageId === undefined) {\n // No card to patch; mark fallback and let finalize/ensureFinalized\n // deliver a plain-text error if asked.\n this.fallbackToText = true;\n this.state = \"aborted\";\n return;\n }\n try {\n await this.client.patchCard({\n messageId: this.messageId,\n card: buildErrorCard(error),\n });\n } finally {\n this.state = \"aborted\";\n }\n }\n\n async ensureFinalized(): Promise<void> {\n if (this.state !== \"completed\" && this.state !== \"aborted\") {\n await this.finalize(this.buffer);\n }\n }\n\n isStreaming(): boolean {\n return this.state === \"streaming\" || this.state === \"creating\";\n }\n\n isCompleted(): boolean {\n return this.state === \"completed\" || this.state === \"aborted\";\n }\n\n private scheduleCreate(): void {\n if (this.createTimer) return;\n this.state = \"creating\";\n this.createTimer = setTimeout(() => {\n this.createTimer = null;\n void this.doCreate();\n }, this.deps.createThresholdMs);\n }\n\n private cancelCreateTimer(): void {\n if (this.createTimer) {\n clearTimeout(this.createTimer);\n this.createTimer = null;\n }\n }\n\n private async doCreate(): Promise<void> {\n if (this.state !== \"creating\") return;\n try {\n const res = await this.client.sendCard({\n chatId: this.deps.chatId,\n card: buildStreamingCard({ buffer: this.buffer, status: this.status }),\n rootId: this.deps.rootId,\n parentId: this.deps.parentId,\n });\n this.messageId = res.messageId;\n this.state = \"streaming\";\n this.lastPatchAt = Date.now();\n } catch {\n this.fallbackToText = true;\n this.state = \"streaming\"; // keep accepting deltas; finalize will deliver as text\n }\n }\n\n private schedulePatch(): void {\n if (this.patchScheduled) return;\n this.patchScheduled = true;\n const elapsed = Date.now() - this.lastPatchAt;\n const wait = Math.max(0, this.deps.patchIntervalMs - elapsed);\n setTimeout(() => {\n this.patchScheduled = false;\n void this.maybeFlushPatch();\n }, wait);\n }\n\n private async maybeFlushPatch(): Promise<void> {\n if (this.state !== \"streaming\") return;\n if (this.patchInFlight) return;\n if (this.messageId === undefined) return;\n const card = buildStreamingCard({ buffer: this.buffer, status: this.status });\n this.patchInFlight = this.client\n .patchCard({ messageId: this.messageId, card })\n .catch(() => {\n // best-effort patch; surface failure silently since the next delta will retry\n })\n .finally(() => {\n this.patchInFlight = null;\n this.lastPatchAt = Date.now();\n });\n await this.patchInFlight;\n }\n}\n","import { LarkConfigError } from \"./errors.js\";\nimport type {\n LarkChannelOptions,\n LarkReplyMode,\n ResolvedLarkOptions,\n} from \"./types.js\";\n\nconst DEFAULTS = {\n baseUrl: \"https://open.feishu.cn\",\n webhookPath: \"/lark/webhook\",\n replyMode: \"streaming\" as LarkReplyMode,\n streamPatchIntervalMs: 1000,\n streamCreateThresholdMs: 400,\n dedupTtlMs: 30 * 60 * 1000,\n dedupMaxEntries: 5000,\n requestTimeoutMs: 15000,\n maxRetries: 2,\n tokenRefreshBufferMs: 5 * 60 * 1000,\n signatureSkewMs: 5 * 60 * 1000,\n ackReaction: \"TYPING\" as string | false,\n};\n\nconst ENV_KEYS = {\n appId: \"LARK_APP_ID\",\n appSecret: \"LARK_APP_SECRET\",\n verificationToken: \"LARK_VERIFICATION_TOKEN\",\n encryptKey: \"LARK_ENCRYPT_KEY\",\n baseUrl: \"LARK_BASE_URL\",\n botOpenId: \"LARK_BOT_OPEN_ID\",\n} as const;\n\nexport type ResolveEnv = Record<string, string | undefined>;\n\nfunction defaultEnv(): ResolveEnv {\n if (typeof process !== \"undefined\" && process.env) {\n return process.env as ResolveEnv;\n }\n return {};\n}\n\nfunction pick(input: string | undefined, envValue: string | undefined): string | undefined {\n return input ?? envValue;\n}\n\nexport function resolveOptions(\n options: LarkChannelOptions,\n env: ResolveEnv = defaultEnv(),\n): ResolvedLarkOptions {\n const appId = pick(options.appId, env[ENV_KEYS.appId]);\n const appSecret = pick(options.appSecret, env[ENV_KEYS.appSecret]);\n const verificationToken = pick(\n options.verificationToken,\n env[ENV_KEYS.verificationToken],\n );\n\n if (!appId) {\n throw new LarkConfigError(\n `eve-lark: appId is required (option \\`appId\\` or env \\`${ENV_KEYS.appId}\\`)`,\n );\n }\n if (!appSecret) {\n throw new LarkConfigError(\n `eve-lark: appSecret is required (option \\`appSecret\\` or env \\`${ENV_KEYS.appSecret}\\`)`,\n );\n }\n if (!verificationToken) {\n throw new LarkConfigError(\n `eve-lark: verificationToken is required (option \\`verificationToken\\` or env \\`${ENV_KEYS.verificationToken}\\`)`,\n );\n }\n\n const rawBaseUrl = pick(options.baseUrl, env[ENV_KEYS.baseUrl]) ?? DEFAULTS.baseUrl;\n const baseUrl = rawBaseUrl.replace(/\\/+$/, \"\");\n\n return {\n appId,\n appSecret,\n verificationToken,\n encryptKey: pick(options.encryptKey, env[ENV_KEYS.encryptKey]),\n baseUrl,\n botOpenId: pick(options.botOpenId, env[ENV_KEYS.botOpenId]),\n webhookPath: options.webhookPath ?? DEFAULTS.webhookPath,\n replyMode: options.replyMode ?? DEFAULTS.replyMode,\n streamPatchIntervalMs: options.streamPatchIntervalMs ?? DEFAULTS.streamPatchIntervalMs,\n streamCreateThresholdMs: options.streamCreateThresholdMs ?? DEFAULTS.streamCreateThresholdMs,\n dedupTtlMs: options.dedupTtlMs ?? DEFAULTS.dedupTtlMs,\n dedupMaxEntries: options.dedupMaxEntries ?? DEFAULTS.dedupMaxEntries,\n requestTimeoutMs: options.requestTimeoutMs ?? DEFAULTS.requestTimeoutMs,\n maxRetries: options.maxRetries ?? DEFAULTS.maxRetries,\n tokenRefreshBufferMs: options.tokenRefreshBufferMs ?? DEFAULTS.tokenRefreshBufferMs,\n signatureSkewMs: options.signatureSkewMs ?? DEFAULTS.signatureSkewMs,\n fetch: options.fetch ?? globalThis.fetch,\n ackReaction: options.ackReaction ?? DEFAULTS.ackReaction,\n };\n}\n"],"mappings":"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","names":["text"]}
|
|
1
|
+
{"version":3,"sources":["../src/channel.ts","../src/errors.ts","../src/lark-client.ts","../src/dedup.ts","../src/crypto.ts","../src/parse.ts","../src/card.ts","../src/streaming-controller.ts","../src/options.ts","../src/long-connection.ts"],"sourcesContent":["import {\n defineChannel,\n POST,\n type Channel,\n type RouteHandlerArgs,\n} from \"eve/channels\";\n\nimport { LarkClient } from \"./lark-client.js\";\nimport { DedupMap } from \"./dedup.js\";\nimport { decryptPayload, verifySignature } from \"./crypto.js\";\nimport { parseInbound } from \"./parse.js\";\nimport { StreamingCardController } from \"./streaming-controller.js\";\nimport { buildTextCard } from \"./card.js\";\nimport { resolveOptions } from \"./options.js\";\nimport { startLongConnection } from \"./long-connection.js\";\nimport type {\n LarkChannelOptions,\n LarkContinuationToken,\n LarkEncryptedBody,\n LarkEventBody,\n LarkInboundFile,\n ResolvedLarkOptions,\n} from \"./types.js\";\n\n/**\n * Continuation token format: `${chatId}:${rootMessageId ?? \"_\"}`.\n * The framework prepends the channel file stem before handing the token to\n * the runtime; consumers should call this helper rather than concatenate.\n */\nexport function larkContinuationToken(\n chatId: string,\n rootMessageId: string | null,\n): LarkContinuationToken {\n return `${chatId}:${rootMessageId ?? \"_\"}` as LarkContinuationToken;\n}\n\ninterface LarkSessionMeta {\n chatId: string;\n rootId?: string | undefined;\n parentId?: string | undefined;\n}\n\n/**\n * Extract the chat metadata we stashed on `auth.initiator.attributes` when\n * starting the session. We can't keep this in a closure-scoped Map because eve\n * may run channel event handlers across a process/worker boundary from the\n * webhook handler — closure state doesn't survive. The auth attributes are\n * persisted with the session and surface cleanly through `ctx.session.auth`.\n */\nfunction metaFromCtx(ctx: { session?: { auth?: { initiator?: { attributes?: unknown } | null } | null } | null }): LarkSessionMeta | null {\n const attrs = (ctx.session?.auth?.initiator?.attributes ?? {}) as {\n chatId?: unknown;\n rootMessageId?: unknown;\n parentId?: unknown;\n };\n if (typeof attrs.chatId !== \"string\" || !attrs.chatId) return null;\n return {\n chatId: attrs.chatId,\n rootId: typeof attrs.rootMessageId === \"string\" ? attrs.rootMessageId : undefined,\n parentId: typeof attrs.parentId === \"string\" ? attrs.parentId : undefined,\n };\n}\n\nfunction ackOk(): Response {\n return Response.json({ code: 0 });\n}\n\n/**\n * Resolve the configured `ackReaction` to a single emoji type for this event,\n * or `false` if reactions are disabled. Picks randomly when given an array.\n */\nfunction pickAckEmoji(reaction: string | readonly string[] | false): string | false {\n if (typeof reaction === \"string\") return reaction;\n if (Array.isArray(reaction)) {\n if (reaction.length === 0) return false;\n const idx = Math.floor(Math.random() * reaction.length);\n return reaction[idx] ?? false;\n }\n return false;\n}\n\nfunction resourceUrl(\n options: ResolvedLarkOptions,\n file: LarkInboundFile,\n messageId: string,\n): string {\n const type = file.kind === \"image\" ? \"image\" : \"file\";\n return `${options.baseUrl}/open-apis/im/v1/messages/${encodeURIComponent(messageId)}/resources/${encodeURIComponent(file.fileKey)}?type=${type}`;\n}\n\n/**\n * Build the eve UserContent payload from a parsed inbound event. Text comes\n * first; each inbound image/file becomes a `file` part carrying a URL pointing\n * at the Lark resource endpoint. The channel's `fetchFile` hook will stage\n * those URLs to bytes when the model runs.\n */\nfunction buildUserContent(\n text: string,\n files: LarkInboundFile[],\n options: ResolvedLarkOptions,\n messageId: string,\n): unknown[] {\n const parts: unknown[] = [];\n if (text.length > 0) parts.push({ type: \"text\", text });\n for (const f of files) {\n parts.push({\n type: \"file\",\n data: new URL(resourceUrl(options, f, messageId)),\n mediaType: f.mediaType,\n });\n }\n return parts;\n}\n\n/**\n * Create a Lark/Feishu channel for the eve agent framework.\n *\n * The channel mounts a single POST webhook that verifies the request,\n * decrypts the body when an encrypt key is configured, deduplicates events\n * by id, parses the inbound message, and starts or resumes an eve session.\n *\n * Streaming happens via eve's native channel events: `message.appended`\n * drives live card patches, `message.completed` finalizes the card, and\n * `turn.failed` aborts it. In `replyMode: \"static\"` the controller is\n * skipped and `message.completed` delivers a single card.\n */\nexport function createLarkChannel(\n optionsInput: LarkChannelOptions,\n): Channel<undefined, Record<string, unknown>, Record<string, unknown>> {\n const options = resolveOptions(optionsInput);\n const client = new LarkClient(options);\n const dedup = new DedupMap(options.dedupTtlMs, options.dedupMaxEntries);\n\n // Long-connection side effect: when mode is \"long-connection\" (the\n // default), start a Feishu WSClient in the background. Each inbound event\n // is re-signed and POSTed to this channel's webhook on localhost, where\n // the standard handler runs with full access to send() etc.\n //\n // Fire-and-forget: the channel factory returns synchronously, eve dev\n // continues to boot, and the WSClient connects in the background. Errors\n // during startup are logged but don't crash the agent.\n if (options.mode === \"long-connection\") {\n const eveWebhookUrl = `http://127.0.0.1:${options.port}${options.webhookPath}`;\n void startLongConnection({ resolved: options, eveWebhookUrl }).catch((e) => {\n console.error(\"[eve-lark] long-connection startup failed:\", e);\n });\n }\n\n // Channel-scoped (closure) state — shared across sessions on the same\n // process. Each session has its own controller + chat metadata, keyed by\n // session.id.\n const controllers = new Map<string, StreamingCardController>();\n const sessionMeta = new Map<string, LarkSessionMeta>();\n\n function getController(sessionId: string, meta: LarkSessionMeta): StreamingCardController {\n let ctrl = controllers.get(sessionId);\n if (!ctrl) {\n ctrl = new StreamingCardController(client, {\n chatId: meta.chatId,\n rootId: meta.rootId,\n parentId: meta.parentId,\n patchIntervalMs: options.streamPatchIntervalMs,\n createThresholdMs: options.streamCreateThresholdMs,\n });\n controllers.set(sessionId, ctrl);\n }\n return ctrl;\n }\n\n function dropController(sessionId: string): void {\n controllers.delete(sessionId);\n sessionMeta.delete(sessionId);\n }\n\n const webhookHandler = async (\n req: Request,\n helpers: RouteHandlerArgs[\"send\"] extends never ? never : RouteHandlerArgs,\n ): Promise<Response> => {\n const rawBody = Buffer.from(await req.arrayBuffer());\n\n // 1) Skew check (only enforced when a real timestamp header is present)\n const tsHeader = req.headers.get(\"x-lark-request-timestamp\") ?? \"\";\n const ts = Number(tsHeader);\n if (\n tsHeader &&\n Number.isFinite(ts) &&\n ts > 0 &&\n Math.abs(Date.now() / 1000 - ts) > options.signatureSkewMs / 1000\n ) {\n return new Response(\"request timestamp out of skew window\", { status: 408 });\n }\n\n // 2) Signature verify + AES decrypt when encryptKey configured\n let workingBody: Buffer = rawBody;\n if (options.encryptKey) {\n const nonce = req.headers.get(\"x-lark-request-nonce\") ?? \"\";\n const sigHeader = req.headers.get(\"x-lark-signature\");\n if (!sigHeader) return new Response(\"missing signature\", { status: 401 });\n const ok = verifySignature({\n timestamp: tsHeader,\n nonce,\n encryptKey: options.encryptKey,\n rawBody,\n signatureHeader: sigHeader,\n });\n if (!ok) return new Response(\"bad signature\", { status: 401 });\n\n try {\n const envelope = JSON.parse(rawBody.toString(\"utf8\")) as LarkEncryptedBody;\n if (envelope.encrypt) {\n workingBody = decryptPayload(envelope.encrypt, options.encryptKey) as Buffer;\n }\n } catch {\n return new Response(\"decrypt failed\", { status: 400 });\n }\n }\n\n // 3) Parse body\n let body: LarkEventBody;\n try {\n body = JSON.parse(workingBody.toString(\"utf8\")) as LarkEventBody;\n } catch {\n return new Response(\"invalid json\", { status: 400 });\n }\n\n // 4) url_verification short-circuit\n if (body.type === \"url_verification\") {\n return Response.json({ challenge: body.challenge ?? \"\" });\n }\n\n // 5) Schema check\n if (body.schema !== \"2.0\") {\n return ackOk();\n }\n\n // 6) Verification-token check\n if (body.header?.token !== options.verificationToken) {\n return new Response(\"verification token mismatch\", { status: 401 });\n }\n\n // 7) Dedup\n const dedupKey = body.header?.event_id ?? body.event?.message?.message_id;\n if (dedupKey) {\n if (dedup.has(dedupKey)) return ackOk();\n dedup.set(dedupKey);\n }\n\n // 8) Event filter — only handle text messages in v1\n if (body.header?.event_type !== \"im.message.receive_v1\") {\n return ackOk();\n }\n if (!body.event) return ackOk();\n\n // 9) Parse\n const parsed = parseInbound(body.event, options.botOpenId);\n\n // 10) Self-message suppression\n if (parsed.senderType === \"app\") {\n return ackOk();\n }\n\n // 11) Skip unsupported message types\n if (parsed.text === \"\" && parsed.files.length === 0) {\n return ackOk();\n }\n\n // 12) Build session inputs\n const userContent = buildUserContent(parsed.text, parsed.files, options, parsed.messageId);\n const continuationToken = larkContinuationToken(parsed.chatId, parsed.parentId ?? parsed.rootId);\n const auth = {\n authenticator: \"lark\",\n principalType: \"user\",\n principalId: parsed.senderOpenId,\n attributes: {\n chatId: parsed.chatId,\n rootMessageId: parsed.rootId,\n messageId: parsed.messageId,\n chatType: parsed.chatType,\n },\n };\n\n // 13) Start/resume session. Cast userContent because eve's UserContent\n // comes from the `ai` package and we intentionally don't depend on it;\n // our shape is structurally compatible (string | Array<TextPart|FilePart>).\n const session = await helpers.send(userContent as never, {\n auth: auth as never,\n continuationToken,\n });\n\n // 14) Remember chat metadata keyed by session.id so event handlers below\n // can look up where to deliver replies.\n sessionMeta.set(session.id, {\n chatId: parsed.chatId,\n rootId: parsed.rootId ?? undefined,\n parentId: parsed.parentId ?? undefined,\n });\n console.log(\"[eve-lark] session registered\", session.id, \"chat:\", parsed.chatId);\n\n // 15) Ack reaction — fire-and-forget in the background so the webhook\n // returns immediately. Best-effort: a failed reaction is logged and\n // swallowed (the user will still see the streaming card eventually).\n const emoji = pickAckEmoji(options.ackReaction);\n if (emoji) {\n helpers.waitUntil(\n client\n .addReaction({ messageId: parsed.messageId, emojiType: emoji })\n .catch((e) => {\n console.warn(\"[eve-lark] ack reaction failed\", e);\n }),\n );\n }\n\n return ackOk();\n };\n\n return defineChannel({\n routes: [POST(options.webhookPath, webhookHandler as never)],\n\n fetchFile: async (url: string) => {\n if (!url.startsWith(options.baseUrl)) return null;\n const m = url.match(/\\/messages\\/([^/]+)\\/resources\\/([^?]+)\\?type=(image|file)/);\n if (!m || !m[1] || !m[2] || !m[3]) return null;\n return client.downloadResource({\n messageId: m[1],\n fileKey: m[2],\n type: m[3] as \"image\" | \"file\",\n });\n },\n\n events: {\n // Streaming delta — patch the card.\n \"message.appended\"(data, _channel, ctx) {\n console.log(\"[eve-lark] message.appended\", ctx.session.id);\n if (options.replyMode !== \"streaming\") return;\n const sessionId = ctx.session.id;\n const meta = metaFromCtx(ctx);\n if (!meta) {\n console.log(\"[eve-lark] message.appended: no meta for\", sessionId);\n return;\n }\n const d = data as { messageDelta?: string; messageSoFar?: string };\n const ctrl = getController(sessionId, meta);\n if (typeof d.messageDelta === \"string\") {\n ctrl.appendDelta(d.messageDelta);\n }\n },\n\n // Terminal — finalize the card OR deliver a fresh one in static mode.\n async \"message.completed\"(data, _channel, ctx) {\n console.log(\"[eve-lark] message.completed\", ctx.session.id);\n const sessionId = ctx.session.id;\n const meta = metaFromCtx(ctx);\n if (!meta) {\n console.log(\"[eve-lark] message.completed: no meta for\", sessionId);\n return;\n }\n const d = data as { message?: string | null };\n const text = typeof d.message === \"string\" ? d.message : \"\";\n\n if (options.replyMode === \"streaming\") {\n const ctrl = getController(sessionId, meta);\n try {\n await ctrl.finalize(text);\n console.log(\"[eve-lark] finalize done for\", sessionId);\n } catch (e) {\n console.log(\"[eve-lark] finalize failed for\", sessionId, e);\n }\n dropController(sessionId);\n return;\n }\n\n // Static mode: single shot delivery.\n try {\n await client.sendCard({\n chatId: meta.chatId,\n card: buildTextCard(text),\n rootId: meta.rootId,\n parentId: meta.parentId,\n });\n console.log(\"[eve-lark] static sendCard done for\", sessionId);\n } catch (e) {\n console.log(\"[eve-lark] static sendCard failed:\", e);\n await client.sendText({\n chatId: meta.chatId,\n content: text,\n rootId: meta.rootId,\n parentId: meta.parentId,\n });\n }\n dropController(sessionId);\n },\n\n async \"turn.failed\"(data, _channel, ctx) {\n console.log(\"[eve-lark] turn.failed\", ctx?.session?.id);\n const sessionId = ctx?.session?.id;\n if (!sessionId) return;\n const meta = metaFromCtx(ctx);\n if (!meta) return;\n const d = data as { error?: { message?: string } | string };\n const errMsg = typeof d === \"object\" && d !== null && \"error\" in d\n ? typeof d.error === \"string\"\n ? d.error\n : d.error?.message ?? \"turn failed\"\n : \"turn failed\";\n\n if (options.replyMode === \"streaming\") {\n const ctrl = controllers.get(sessionId);\n if (ctrl) {\n try {\n await ctrl.abort(errMsg);\n } catch {\n // best-effort\n }\n }\n }\n dropController(sessionId);\n },\n\n async \"session.failed\"(data, _channel) {\n // No ctx for this event — we can't look up by session.id. Use the\n // continuationToken from channel ops to find the session by meta\n // match would be unreliable; instead log and move on.\n const d = data as { error?: { message?: string } };\n const errMsg = d?.error?.message ?? \"session failed\";\n // Best-effort: drop any controllers we know about. In practice this\n // event is rare and a missed cleanup just GCs on next process restart.\n for (const [, ctrl] of controllers) {\n try {\n await ctrl.abort(errMsg);\n } catch {\n // best-effort\n }\n }\n controllers.clear();\n sessionMeta.clear();\n },\n },\n });\n}\n","/**\n * Typed error hierarchy for eve-lark.\n *\n * All errors extend a common base so consumers can `instanceof LarkChannelError`\n * to catch anything thrown by the channel.\n */\n\nexport class LarkChannelError extends Error {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = new.target.name;\n }\n}\n\nexport class LarkConfigError extends LarkChannelError {}\n\nexport class LarkSignatureError extends LarkChannelError {}\n\nexport class LarkDecryptError extends LarkChannelError {}\n\nexport interface LarkApiErrorBody {\n code?: number | undefined;\n msg?: string | undefined;\n}\n\nexport class LarkApiError extends LarkChannelError {\n readonly code: number | undefined;\n readonly body: LarkApiErrorBody | undefined;\n readonly status: number | undefined;\n\n constructor(\n message: string,\n opts?: {\n code?: number | undefined;\n body?: LarkApiErrorBody | undefined;\n status?: number | undefined;\n cause?: unknown;\n },\n ) {\n super(message, { cause: opts?.cause });\n this.code = opts?.code;\n this.body = opts?.body;\n this.status = opts?.status;\n }\n}\n","import { LarkApiError, type LarkApiErrorBody } from \"./errors.js\";\nimport type { LarkCard, ResolvedLarkOptions } from \"./types.js\";\n\ninterface TokenState {\n value: string;\n expiresAt: number;\n}\n\nconst TOKEN_INVALID_CODES = new Set<number>([99991663, 99991664, 99991661]);\n\ninterface RequestResult {\n status: number;\n body: unknown;\n retryAfter: number | null;\n}\n\nexport class LarkClient {\n private readonly options: ResolvedLarkOptions;\n private token: TokenState | null = null;\n private refreshPromise: Promise<string> | null = null;\n\n constructor(options: ResolvedLarkOptions) {\n this.options = options;\n }\n\n async getTenantAccessToken(): Promise<string> {\n if (\n this.token &&\n Date.now() + this.options.tokenRefreshBufferMs < this.token.expiresAt\n ) {\n return this.token.value;\n }\n if (this.refreshPromise) return this.refreshPromise;\n this.refreshPromise = this.#refresh();\n try {\n return await this.refreshPromise;\n } finally {\n this.refreshPromise = null;\n }\n }\n\n async #refresh(): Promise<string> {\n const body = {\n app_id: this.options.appId,\n app_secret: this.options.appSecret,\n };\n const res = await this.options.fetch(\n `${this.options.baseUrl}/open-apis/auth/v3/tenant_access_token/internal`,\n {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(this.options.requestTimeoutMs),\n },\n );\n if (!res.ok) {\n throw new LarkApiError(\n `eve-lark: token refresh failed (HTTP ${res.status})`,\n { status: res.status },\n );\n }\n const json = (await res.json()) as { code?: number; tenant_access_token?: string; expire?: number; msg?: string };\n if (json.code !== 0 || !json.tenant_access_token) {\n throw new LarkApiError(\n `eve-lark: token refresh returned code=${json.code ?? \"?\"} msg=${json.msg ?? \"?\"}`,\n { body: json, code: json.code },\n );\n }\n const expireSec = typeof json.expire === \"number\" ? json.expire : 7200;\n this.token = {\n value: json.tenant_access_token,\n expiresAt: Date.now() + expireSec * 1000,\n };\n return this.token.value;\n }\n\n async sendText(args: {\n chatId: string;\n content: string;\n rootId?: string;\n parentId?: string;\n }): Promise<{ messageId: string }> {\n const content = JSON.stringify({ text: args.content });\n return this.#sendMessage({\n receive_id: args.chatId,\n msg_type: \"text\",\n content,\n root_id: args.rootId,\n parent_id: args.parentId,\n });\n }\n\n async sendCard(args: {\n chatId: string;\n card: LarkCard;\n rootId?: string;\n parentId?: string;\n }): Promise<{ messageId: string }> {\n const content = JSON.stringify(args.card);\n return this.#sendMessage({\n receive_id: args.chatId,\n msg_type: \"interactive\",\n content,\n root_id: args.rootId,\n parent_id: args.parentId,\n });\n }\n\n async #sendMessage(body: Record<string, unknown>): Promise<{ messageId: string }> {\n const payload = Object.fromEntries(\n Object.entries(body).filter(([, v]) => v !== undefined),\n );\n const json = await this.#request(\"POST\", \"/open-apis/im/v1/messages?receive_id_type=chat_id\", payload);\n const messageId = (json as { data?: { message_id?: string } }).data?.message_id;\n if (!messageId) {\n throw new LarkApiError(\"eve-lark: send missing message_id in response\", {\n body: json as LarkApiErrorBody,\n });\n }\n return { messageId };\n }\n\n async patchCard(args: { messageId: string; card: LarkCard }): Promise<void> {\n await this.#request(\n \"PATCH\",\n `/open-apis/im/v1/messages/${encodeURIComponent(args.messageId)}`,\n { content: JSON.stringify(args.card) },\n );\n }\n\n async downloadResource(args: {\n messageId: string;\n fileKey: string;\n type: \"image\" | \"file\";\n }): Promise<Buffer> {\n const path = `/open-apis/im/v1/messages/${encodeURIComponent(args.messageId)}/resources/${encodeURIComponent(args.fileKey)}?type=${args.type}`;\n const token = await this.getTenantAccessToken();\n const res = await this.options.fetch(`${this.options.baseUrl}${path}`, {\n method: \"GET\",\n headers: { authorization: `Bearer ${token}` },\n signal: AbortSignal.timeout(this.options.requestTimeoutMs),\n });\n if (!res.ok) {\n throw new LarkApiError(\n `eve-lark: downloadResource HTTP ${res.status}`,\n { status: res.status },\n );\n }\n return Buffer.from(await res.arrayBuffer());\n }\n\n async addReaction(args: {\n messageId: string;\n emojiType: string;\n }): Promise<{ reactionId: string }> {\n const path = `/open-apis/im/v1/messages/${encodeURIComponent(args.messageId)}/reactions`;\n const json = (await this.#request(\"POST\", path, {\n reaction_type: { emoji_type: args.emojiType },\n })) as { data?: { reaction_id?: string } };\n const reactionId = json.data?.reaction_id;\n if (!reactionId) {\n throw new LarkApiError(\"eve-lark: addReaction missing reaction_id\", {\n body: json as LarkApiErrorBody,\n });\n }\n return { reactionId };\n }\n\n async removeReaction(args: { messageId: string; reactionId: string }): Promise<void> {\n const path = `/open-apis/im/v1/messages/${encodeURIComponent(args.messageId)}/reactions/${encodeURIComponent(args.reactionId)}`;\n await this.#request(\"DELETE\", path, undefined);\n }\n\n /**\n * Central request wrapper with auth, retry, and Feishu error decoding.\n *\n * Retries on 429 (honoring Retry-After), 5xx (exponential backoff + jitter),\n * and token-invalid responses (one refresh + one retry). Other 4xx throws\n * LarkApiError with the Feishu code/msg from the body.\n */\n async #request(method: string, path: string, body: unknown): Promise<unknown> {\n const url = `${this.options.baseUrl}${path}`;\n let token = await this.getTenantAccessToken();\n let tokenRefreshed = false;\n\n for (let attempt = 0; attempt <= this.options.maxRetries; attempt++) {\n const res = await this.options.fetch(url, {\n method,\n headers: {\n authorization: `Bearer ${token}`,\n \"content-type\": \"application/json\",\n },\n body: body === undefined ? undefined : JSON.stringify(body),\n signal: AbortSignal.timeout(this.options.requestTimeoutMs),\n });\n\n const result = await this.#consumeResponse(res);\n const status = result.status;\n\n if (status >= 200 && status < 300) {\n const jsonBody = result.body as { code?: number; msg?: string };\n if (jsonBody && typeof jsonBody.code === \"number\" && jsonBody.code !== 0) {\n if (TOKEN_INVALID_CODES.has(jsonBody.code) && !tokenRefreshed) {\n this.token = null;\n token = await this.getTenantAccessToken();\n tokenRefreshed = true;\n attempt -= 1;\n continue;\n }\n throw new LarkApiError(\n `eve-lark: ${method} ${path} failed code=${jsonBody.code} msg=${jsonBody.msg ?? \"?\"}`,\n { code: jsonBody.code, body: jsonBody as LarkApiErrorBody, status },\n );\n }\n return result.body;\n }\n\n if (status === 401 && !tokenRefreshed) {\n this.token = null;\n token = await this.getTenantAccessToken();\n tokenRefreshed = true;\n attempt -= 1;\n continue;\n }\n\n const retryable =\n status === 429 || (status >= 500 && status < 600);\n if (retryable && attempt < this.options.maxRetries) {\n const delayMs = this.#computeBackoff(status, result.retryAfter, attempt);\n await sleep(delayMs);\n continue;\n }\n\n const bodyObj = result.body as LarkApiErrorBody | undefined;\n const code = bodyObj?.code;\n const msg = bodyObj?.msg;\n const detail = msg ? ` code=${code ?? \"?\"} msg=${msg}` : \"\";\n throw new LarkApiError(\n `eve-lark: ${method} ${path} failed HTTP ${status}${detail}`,\n { status, body: bodyObj, code },\n );\n }\n throw new LarkApiError(`eve-lark: ${method} ${path} exhausted retries`);\n }\n\n async #consumeResponse(res: Response): Promise<RequestResult> {\n const retryAfterRaw = res.headers.get(\"retry-after\");\n const retryAfter = retryAfterRaw ? parseRetryAfter(retryAfterRaw) : null;\n const text = await res.text();\n if (!text) {\n return { status: res.status, body: undefined, retryAfter };\n }\n try {\n return { status: res.status, body: JSON.parse(text), retryAfter };\n } catch {\n return { status: res.status, body: { raw: text }, retryAfter };\n }\n }\n\n #computeBackoff(status: number, retryAfter: number | null, attempt: number): number {\n if (status === 429 && retryAfter !== null) {\n return Math.min(retryAfter * 1000, 10_000);\n }\n const base = 300 * Math.pow(2, attempt);\n const jitter = base * 0.2 * (Math.random() * 2 - 1);\n return Math.max(0, Math.round(base + jitter));\n }\n}\n\nfunction parseRetryAfter(raw: string): number | null {\n const sec = Number(raw);\n if (Number.isFinite(sec) && sec >= 0) return sec;\n const date = Date.parse(raw);\n if (Number.isFinite(date)) return Math.max(0, (date - Date.now()) / 1000);\n return null;\n}\n\nfunction sleep(ms: number): Promise<void> {\n if (ms <= 0) return Promise.resolve();\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","/**\n * In-process deduplication for Feishu webhook events.\n *\n * Feishu retries delivery on non-2xx responses and during brief outage windows,\n * so consumers must idempotently ack events they've already seen. We key by\n * `header.event_id` (or `message.message_id` as a fallback) and remember each\n * key for the TTL window.\n *\n * Backed by an insertion-ordered Map so FIFO eviction is O(1) at the front.\n * Lazy sweep on every insert prevents unbounded growth of expired entries;\n * no `setInterval` so this is safe in serverless.\n */\nexport class DedupMap {\n private readonly entries = new Map<string, number>();\n private readonly ttlMs: number;\n private readonly maxEntries: number;\n private insertsSinceSweep = 0;\n\n constructor(ttlMs: number, maxEntries: number) {\n this.ttlMs = ttlMs;\n this.maxEntries = maxEntries;\n }\n\n has(key: string): boolean {\n const at = this.entries.get(key);\n if (at === undefined) return false;\n if (Date.now() - at > this.ttlMs) {\n this.entries.delete(key);\n return false;\n }\n return true;\n }\n\n set(key: string): void {\n this.maybeSweep();\n // Refresh timestamp by re-inserting at the tail of insertion order.\n this.entries.delete(key);\n this.entries.set(key, Date.now());\n\n while (this.entries.size > this.maxEntries) {\n const oldestKey = this.entries.keys().next().value;\n if (oldestKey === undefined) break;\n this.entries.delete(oldestKey);\n }\n }\n\n /**\n * Walk the insertion-ordered map from the front and drop expired entries.\n * Stops at the first non-expired entry since events arrive roughly in time\n * order. Called on every set, so cost is amortized.\n */\n private maybeSweep(): void {\n this.insertsSinceSweep += 1;\n if (this.insertsSinceSweep < 64 && this.entries.size < this.maxEntries) {\n return;\n }\n this.insertsSinceSweep = 0;\n const now = Date.now();\n for (const [key, at] of this.entries) {\n if (now - at <= this.ttlMs) break;\n this.entries.delete(key);\n }\n }\n}\n","import { createDecipheriv, createHash, timingSafeEqual } from \"node:crypto\";\nimport { LarkDecryptError } from \"./errors.js\";\n\n/**\n * Verify an `X-Lark-Signature` header against the raw webhook body.\n *\n * Feishu computes: `sha256(timestamp + nonce + encrypt_key + body)` and ships\n * the hex digest (optionally prefixed with `sha256=`) in `X-Lark-Signature`.\n * We concatenate the string parts first, then the raw bytes of the body, to\n * avoid a UTF-8 round-trip on the request body.\n *\n * Constant-time compare. Returns false on length mismatch instead of throwing.\n */\nexport function verifySignature(opts: {\n timestamp: string;\n nonce: string;\n encryptKey: string;\n rawBody: Buffer;\n signatureHeader: string;\n}): boolean {\n const expected = opts.signatureHeader.replace(/^sha256=/, \"\");\n const computed = createHash(\"sha256\")\n .update(opts.timestamp + opts.nonce + opts.encryptKey)\n .update(opts.rawBody)\n .digest(\"hex\");\n const a = Buffer.from(computed, \"hex\");\n const b = Buffer.from(expected, \"hex\");\n return a.length === b.length && timingSafeEqual(a, b);\n}\n\n/**\n * Decrypt the `encrypt` field from a Feishu webhook body.\n *\n * Layout:\n * key = SHA256(encrypt_key) // 32 bytes → AES-256\n * buf = base64decode(encrypt_field)\n * iv = buf[0:16]\n * ct = buf[16:] // AES-256-CBC ciphertext\n * plaintext = AES_256_CBC_decrypt(key, iv, ct) // PKCS#7 unpadded\n *\n * Returns the raw plaintext bytes. The caller is expected to JSON.parse them.\n */\nexport function decryptPayload(encryptB64: string, encryptKey: string): Buffer {\n const key = createHash(\"sha256\").update(encryptKey).digest();\n const buf = Buffer.from(encryptB64, \"base64\");\n\n if (buf.length < 32) {\n throw new LarkDecryptError(\n `eve-lark: ciphertext too short (${buf.length} bytes; need >= 32 for IV + one block)`,\n );\n }\n if ((buf.length - 16) % 16 !== 0) {\n throw new LarkDecryptError(\n `eve-lark: ciphertext length ${buf.length} is not 16 + N*16`,\n );\n }\n\n const iv = buf.subarray(0, 16);\n const ct = buf.subarray(16);\n const dec = createDecipheriv(\"aes-256-cbc\", key, iv);\n\n try {\n return Buffer.concat([dec.update(ct), dec.final()]);\n } catch (e) {\n throw new LarkDecryptError(\"eve-lark: AES decrypt failed (bad padding or wrong key)\", {\n cause: e,\n });\n }\n}\n","import type {\n LarkInboundEvent,\n LarkInboundFile,\n LarkInboundResult,\n LarkMention,\n LarkRawMention,\n} from \"./types.js\";\n\nconst MIME_BY_EXT: Record<string, string> = {\n pdf: \"application/pdf\",\n zip: \"application/zip\",\n gz: \"application/gzip\",\n tar: \"application/x-tar\",\n doc: \"application/msword\",\n docx: \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\",\n xls: \"application/vnd.ms-excel\",\n xlsx: \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\",\n ppt: \"application/vnd.ms-powerpoint\",\n pptx: \"application/vnd.openxmlformats-officedocument.presentationml.presentation\",\n csv: \"text/csv\",\n txt: \"text/plain\",\n md: \"text/markdown\",\n json: \"application/json\",\n xml: \"application/xml\",\n html: \"text/html\",\n htm: \"text/html\",\n png: \"image/png\",\n jpg: \"image/jpeg\",\n jpeg: \"image/jpeg\",\n gif: \"image/gif\",\n webp: \"image/webp\",\n mp3: \"audio/mpeg\",\n wav: \"audio/wav\",\n mp4: \"video/mp4\",\n mov: \"video/quicktime\",\n};\n\nfunction mimeFromExt(filename: string | undefined): string {\n if (!filename) return \"application/octet-stream\";\n const dot = filename.lastIndexOf(\".\");\n if (dot < 0) return \"application/octet-stream\";\n return MIME_BY_EXT[filename.slice(dot + 1).toLowerCase()] ?? \"application/octet-stream\";\n}\n\nfunction mentionFromRaw(m: LarkRawMention, botOpenId: string | undefined): LarkMention {\n const isOpenIdOfBot =\n !!botOpenId && !!m.id.open_id && m.id.open_id === botOpenId;\n const isAll = !!m.id.open_id && m.id.open_id === \"all\";\n return {\n key: m.key,\n id: {\n openId: m.id.open_id,\n userId: m.id.user_id,\n unionId: m.id.union_id,\n },\n name: m.name,\n idType: m.id_type ?? \"open_id\",\n isOpenIdOfBot,\n isAll,\n };\n}\n\nfunction stripBotMentions(text: string, mentions: LarkMention[]): string {\n // Feishu ships mentions as opaque placeholders (e.g. \"@_user_1\") in the text\n // body alongside a structured mentions array. Rewrite them to something the\n // model can read:\n // - the bot itself: dropped (the model already knows it's being addressed)\n // - @all: replaced with a literal \"@all\" token\n // - other users: replaced with \"@<display name>\"\n let out = text;\n for (const m of mentions) {\n if (!m.key) continue;\n if (m.isOpenIdOfBot) {\n out = out.split(m.key).join(\"\");\n } else if (m.isAll) {\n out = out.split(m.key).join(\"@all\");\n } else {\n out = out.split(m.key).join(`@${m.name}`);\n }\n }\n return out.replace(/\\s+/g, \" \").trim();\n}\n\ninterface ParsedContent {\n text: string;\n files: LarkInboundFile[];\n}\n\nfunction parseContent(messageType: string, rawContent: string): ParsedContent {\n if (!rawContent) return { text: \"\", files: [] };\n let content: Record<string, unknown>;\n try {\n content = JSON.parse(rawContent) as Record<string, unknown>;\n } catch {\n return { text: \"\", files: [] };\n }\n\n switch (messageType) {\n case \"text\": {\n const text = typeof content.text === \"string\" ? content.text : \"\";\n return { text, files: [] };\n }\n case \"image\": {\n const imageKey = typeof content.image_key === \"string\" ? content.image_key : \"\";\n if (!imageKey) return { text: \"\", files: [] };\n return {\n text: \"\",\n files: [{ fileKey: imageKey, mediaType: \"image/png\", kind: \"image\" }],\n };\n }\n case \"file\": {\n const fileKey = typeof content.file_key === \"string\" ? content.file_key : \"\";\n if (!fileKey) return { text: \"\", files: [] };\n const fileName = typeof content.file_name === \"string\" ? content.file_name : undefined;\n return {\n text: \"\",\n files: [{ fileKey, mediaType: mimeFromExt(fileName), kind: \"file\" }],\n };\n }\n case \"post\": {\n const locale = (content.zh_cn ?? content.en_us ?? content.ja_jp ?? null) as\n | { content?: unknown[][] }\n | null;\n if (!locale?.content) return { text: \"\", files: [] };\n const text = locale.content\n .flatMap((line) =>\n (line ?? [])\n .filter((node): node is { tag: string; text?: unknown } => {\n if (typeof node !== \"object\" || node === null) return false;\n const tag = (node as { tag?: unknown }).tag;\n const text = (node as { text?: unknown }).text;\n return tag === \"text\" && typeof text === \"string\";\n })\n .map((node) => node.text as string),\n )\n .join(\" \");\n return { text, files: [] };\n }\n default:\n // audio, media, sticker, share_chat, share_user, interactive — not in v1 scope.\n return { text: \"\", files: [] };\n }\n}\n\nexport function parseInbound(\n event: LarkInboundEvent,\n botOpenId?: string,\n): LarkInboundResult {\n const messageType = event.message.message_type;\n const parsed = parseContent(messageType, event.message.content);\n const rawMentions = event.message.mentions ?? [];\n const mentions = rawMentions.map((m) => mentionFromRaw(m, botOpenId));\n\n const senderOpenId =\n event.sender.sender_id.open_id ??\n event.sender.sender_id.user_id ??\n event.sender.sender_id.union_id ??\n \"\";\n\n const text =\n messageType === \"text\"\n ? stripBotMentions(parsed.text, mentions)\n : parsed.text;\n\n const chatType = event.chat_type === \"group\" ? \"group\" : \"p2p\";\n const senderType = event.sender.sender_type === \"app\" ? \"app\" : \"user\";\n\n return {\n text,\n files: parsed.files,\n chatId: event.message.chat_id,\n rootId: event.message.root_id ?? null,\n parentId: event.message.parent_id ?? null,\n messageId: event.message.message_id,\n senderOpenId,\n senderType,\n chatType,\n mentions,\n };\n}\n","import type { LarkCard } from \"./types.js\";\n\nconst BASE_CONFIG = {\n wide_screen_mode: true,\n update_multi: true,\n} as const;\n\n/**\n * Build a simple single-shot card with the given markdown text.\n */\nexport function buildTextCard(text: string): LarkCard {\n return {\n config: { ...BASE_CONFIG },\n elements: [{ tag: \"markdown\", content: text }],\n };\n}\n\n/**\n * Build a streaming card with an optional status prefix and an answer buffer.\n *\n * Format:\n * <optional status prefix in muted tone>\n * <buffer>\n */\nexport function buildStreamingCard(opts: { buffer: string; status?: string | undefined }): LarkCard {\n const lines: string[] = [];\n if (opts.status) {\n lines.push(`<font color='grey'>${opts.status}</font>`);\n }\n lines.push(opts.buffer.length > 0 ? opts.buffer : \"_…_\");\n return {\n config: { ...BASE_CONFIG },\n elements: [{ tag: \"markdown\", content: lines.join(\"\\n\\n\") }],\n };\n}\n\n/**\n * Build an error card displayed when a turn fails.\n */\nexport function buildErrorCard(message: string): LarkCard {\n return {\n config: { ...BASE_CONFIG },\n elements: [\n { tag: \"markdown\", content: `<font color='red'>⚠ ${message}</font>` },\n ],\n };\n}\n","import { buildErrorCard, buildStreamingCard, buildTextCard } from \"./card.js\";\nimport type { LarkCard } from \"./types.js\";\n\ntype State = \"idle\" | \"creating\" | \"streaming\" | \"completed\" | \"aborted\";\n\ninterface ControllerDeps {\n chatId: string;\n rootId?: string | undefined;\n parentId?: string | undefined;\n patchIntervalMs: number;\n createThresholdMs: number;\n}\n\ninterface LarkClientLike {\n sendCard(args: {\n chatId: string;\n card: LarkCard;\n rootId?: string;\n parentId?: string;\n }): Promise<{ messageId: string }>;\n patchCard(args: { messageId: string; card: LarkCard }): Promise<void>;\n sendText(args: {\n chatId: string;\n content: string;\n rootId?: string;\n parentId?: string;\n }): Promise<{ messageId: string }>;\n}\n\n/**\n * Streaming interactive-card state machine.\n *\n * idle ──first delta──> creating ──sendCard ok──> streaming ──finalize──> completed\n * │\n * └──sendCard fail──> aborted (flag; static fallback on message.completed)\n *\n * The card is created lazily after `createThresholdMs` of the first delta, so\n * short turns can short-circuit straight to `finalize` (which sends the card\n * with the full answer in one shot). Once streaming, patches are throttled to\n * `patchIntervalMs`.\n *\n * If `sendCard` fails on creation, the controller flips to `fallbackToText`\n * and `finalize`/`ensureFinalized` deliver via `sendText` instead.\n */\nexport class StreamingCardController {\n private readonly deps: ControllerDeps;\n private readonly client: LarkClientLike;\n\n private state: State = \"idle\";\n private buffer = \"\";\n private status: string | undefined;\n private messageId: string | undefined;\n private fallbackToText = false;\n\n private createTimer: ReturnType<typeof setTimeout> | null = null;\n private patchInFlight: Promise<void> | null = null;\n private patchScheduled = false;\n private lastPatchAt = 0;\n\n constructor(client: LarkClientLike, deps: ControllerDeps) {\n this.client = client;\n this.deps = deps;\n }\n\n appendDelta(text: string): void {\n if (this.state === \"completed\" || this.state === \"aborted\") return;\n this.buffer += text;\n if (this.state === \"idle\") {\n this.scheduleCreate();\n } else if (this.state === \"streaming\") {\n this.schedulePatch();\n }\n }\n\n setStatus(status: string): void {\n if (this.state === \"completed\" || this.state === \"aborted\") return;\n this.status = status;\n if (this.state === \"streaming\") {\n this.schedulePatch();\n }\n }\n\n async finalize(fullText: string): Promise<void> {\n if (this.state === \"completed\" || this.state === \"aborted\") return;\n this.cancelCreateTimer();\n this.buffer = fullText;\n\n if (this.fallbackToText) {\n await this.client.sendText({\n chatId: this.deps.chatId,\n content: fullText,\n rootId: this.deps.rootId,\n parentId: this.deps.parentId,\n });\n this.state = \"completed\";\n return;\n }\n\n if (this.messageId === undefined) {\n // Never managed to create a card. Send one with the full text in a\n // single shot so the user still gets a card reply.\n try {\n const res = await this.client.sendCard({\n chatId: this.deps.chatId,\n card: buildTextCard(fullText),\n rootId: this.deps.rootId,\n parentId: this.deps.parentId,\n });\n this.messageId = res.messageId;\n this.state = \"completed\";\n } catch {\n // Last-resort fallback: plain text.\n this.fallbackToText = true;\n await this.client.sendText({\n chatId: this.deps.chatId,\n content: fullText,\n rootId: this.deps.rootId,\n parentId: this.deps.parentId,\n });\n this.state = \"completed\";\n }\n return;\n }\n\n // Card already exists; flush the final state.\n if (this.patchInFlight) {\n try {\n await this.patchInFlight;\n } catch {\n // swallow; we'll attempt the final patch below\n }\n }\n await this.client.patchCard({\n messageId: this.messageId,\n card: buildStreamingCard({ buffer: fullText, status: undefined }),\n });\n this.state = \"completed\";\n }\n\n async abort(error: string): Promise<void> {\n if (this.state === \"completed\" || this.state === \"aborted\") return;\n this.cancelCreateTimer();\n if (this.messageId === undefined) {\n // No card to patch; mark fallback and let finalize/ensureFinalized\n // deliver a plain-text error if asked.\n this.fallbackToText = true;\n this.state = \"aborted\";\n return;\n }\n try {\n await this.client.patchCard({\n messageId: this.messageId,\n card: buildErrorCard(error),\n });\n } finally {\n this.state = \"aborted\";\n }\n }\n\n async ensureFinalized(): Promise<void> {\n if (this.state !== \"completed\" && this.state !== \"aborted\") {\n await this.finalize(this.buffer);\n }\n }\n\n isStreaming(): boolean {\n return this.state === \"streaming\" || this.state === \"creating\";\n }\n\n isCompleted(): boolean {\n return this.state === \"completed\" || this.state === \"aborted\";\n }\n\n private scheduleCreate(): void {\n if (this.createTimer) return;\n this.state = \"creating\";\n this.createTimer = setTimeout(() => {\n this.createTimer = null;\n void this.doCreate();\n }, this.deps.createThresholdMs);\n }\n\n private cancelCreateTimer(): void {\n if (this.createTimer) {\n clearTimeout(this.createTimer);\n this.createTimer = null;\n }\n }\n\n private async doCreate(): Promise<void> {\n if (this.state !== \"creating\") return;\n try {\n const res = await this.client.sendCard({\n chatId: this.deps.chatId,\n card: buildStreamingCard({ buffer: this.buffer, status: this.status }),\n rootId: this.deps.rootId,\n parentId: this.deps.parentId,\n });\n this.messageId = res.messageId;\n this.state = \"streaming\";\n this.lastPatchAt = Date.now();\n } catch {\n this.fallbackToText = true;\n this.state = \"streaming\"; // keep accepting deltas; finalize will deliver as text\n }\n }\n\n private schedulePatch(): void {\n if (this.patchScheduled) return;\n this.patchScheduled = true;\n const elapsed = Date.now() - this.lastPatchAt;\n const wait = Math.max(0, this.deps.patchIntervalMs - elapsed);\n setTimeout(() => {\n this.patchScheduled = false;\n void this.maybeFlushPatch();\n }, wait);\n }\n\n private async maybeFlushPatch(): Promise<void> {\n if (this.state !== \"streaming\") return;\n if (this.patchInFlight) return;\n if (this.messageId === undefined) return;\n const card = buildStreamingCard({ buffer: this.buffer, status: this.status });\n this.patchInFlight = this.client\n .patchCard({ messageId: this.messageId, card })\n .catch(() => {\n // best-effort patch; surface failure silently since the next delta will retry\n })\n .finally(() => {\n this.patchInFlight = null;\n this.lastPatchAt = Date.now();\n });\n await this.patchInFlight;\n }\n}\n","import { LarkConfigError } from \"./errors.js\";\nimport type {\n LarkChannelOptions,\n LarkReplyMode,\n LarkTransportMode,\n ResolvedLarkOptions,\n} from \"./types.js\";\n\nconst DEFAULTS = {\n baseUrl: \"https://open.feishu.cn\",\n webhookPath: \"/lark/webhook\",\n replyMode: \"streaming\" as LarkReplyMode,\n streamPatchIntervalMs: 1000,\n streamCreateThresholdMs: 400,\n dedupTtlMs: 30 * 60 * 1000,\n dedupMaxEntries: 5000,\n requestTimeoutMs: 15000,\n maxRetries: 2,\n tokenRefreshBufferMs: 5 * 60 * 1000,\n signatureSkewMs: 5 * 60 * 1000,\n ackReaction: \"TYPING\" as string | false,\n mode: \"long-connection\" as LarkTransportMode,\n};\n\nconst ENV_KEYS = {\n appId: \"LARK_APP_ID\",\n appSecret: \"LARK_APP_SECRET\",\n verificationToken: \"LARK_VERIFICATION_TOKEN\",\n encryptKey: \"LARK_ENCRYPT_KEY\",\n baseUrl: \"LARK_BASE_URL\",\n botOpenId: \"LARK_BOT_OPEN_ID\",\n} as const;\n\nexport type ResolveEnv = Record<string, string | undefined>;\n\nfunction defaultEnv(): ResolveEnv {\n if (typeof process !== \"undefined\" && process.env) {\n return process.env as ResolveEnv;\n }\n return {};\n}\n\nfunction pick(input: string | undefined, envValue: string | undefined): string | undefined {\n return input ?? envValue;\n}\n\nexport function resolveOptions(\n options: LarkChannelOptions,\n env: ResolveEnv = defaultEnv(),\n): ResolvedLarkOptions {\n const appId = pick(options.appId, env[ENV_KEYS.appId]);\n const appSecret = pick(options.appSecret, env[ENV_KEYS.appSecret]);\n const verificationToken = pick(\n options.verificationToken,\n env[ENV_KEYS.verificationToken],\n );\n\n if (!appId) {\n throw new LarkConfigError(\n `eve-lark: appId is required (option \\`appId\\` or env \\`${ENV_KEYS.appId}\\`)`,\n );\n }\n if (!appSecret) {\n throw new LarkConfigError(\n `eve-lark: appSecret is required (option \\`appSecret\\` or env \\`${ENV_KEYS.appSecret}\\`)`,\n );\n }\n if (!verificationToken) {\n throw new LarkConfigError(\n `eve-lark: verificationToken is required (option \\`verificationToken\\` or env \\`${ENV_KEYS.verificationToken}\\`)`,\n );\n }\n\n const rawBaseUrl = pick(options.baseUrl, env[ENV_KEYS.baseUrl]) ?? DEFAULTS.baseUrl;\n const baseUrl = rawBaseUrl.replace(/\\/+$/, \"\");\n\n return {\n appId,\n appSecret,\n verificationToken,\n encryptKey: pick(options.encryptKey, env[ENV_KEYS.encryptKey]),\n baseUrl,\n botOpenId: pick(options.botOpenId, env[ENV_KEYS.botOpenId]),\n webhookPath: options.webhookPath ?? DEFAULTS.webhookPath,\n replyMode: options.replyMode ?? DEFAULTS.replyMode,\n streamPatchIntervalMs: options.streamPatchIntervalMs ?? DEFAULTS.streamPatchIntervalMs,\n streamCreateThresholdMs: options.streamCreateThresholdMs ?? DEFAULTS.streamCreateThresholdMs,\n dedupTtlMs: options.dedupTtlMs ?? DEFAULTS.dedupTtlMs,\n dedupMaxEntries: options.dedupMaxEntries ?? DEFAULTS.dedupMaxEntries,\n requestTimeoutMs: options.requestTimeoutMs ?? DEFAULTS.requestTimeoutMs,\n maxRetries: options.maxRetries ?? DEFAULTS.maxRetries,\n tokenRefreshBufferMs: options.tokenRefreshBufferMs ?? DEFAULTS.tokenRefreshBufferMs,\n signatureSkewMs: options.signatureSkewMs ?? DEFAULTS.signatureSkewMs,\n fetch: options.fetch ?? globalThis.fetch,\n ackReaction: options.ackReaction ?? DEFAULTS.ackReaction,\n mode: options.mode ?? DEFAULTS.mode,\n port:\n options.port ??\n (process.env.PORT ? Number(process.env.PORT) : 2000),\n };\n}\n","/**\n * Long-connection transport: when `mode: \"long-connection\"` is set on\n * {@link createLarkChannel}, the channel starts a Feishu\n * `@larksuiteoapi/node-sdk` WSClient as a side effect of construction. Each\n * inbound event is re-encrypted + re-signed and POSTed to the channel's own\n * webhook on localhost, where the standard webhook handler runs (with full\n * access to `send()` etc.). This lets users run the bot against a real Feishu\n * app from `eve dev` alone — no public webhook URL, no second process.\n *\n * The SDK is dynamically imported so consumers who only use `mode: \"webhook\"`\n * (production) don't pay the ~5 MB install.\n */\n\nimport {\n createCipheriv,\n createHash,\n randomBytes,\n} from \"node:crypto\";\nimport type { ResolvedLarkOptions } from \"./types.js\";\n\n/** A Feishu v2 envelope we forward to the channel webhook. */\nexport type LarkEvent = {\n schema?: string;\n type?: string;\n challenge?: string;\n token?: string;\n header?: Record<string, unknown>;\n event?: unknown;\n [k: string]: unknown;\n};\n\nexport interface PostEventOptions {\n eveWebhookUrl: string;\n /** When set, the body is AES-encrypted and the request is signed. */\n encryptKey?: string | undefined;\n /** Override for tests. Defaults to globalThis.fetch. */\n fetch?: typeof fetch | undefined;\n}\n\nfunction aesEncrypt(plaintext: Buffer, key: string): Buffer {\n const keyBuf = createHash(\"sha256\").update(key).digest();\n const iv = randomBytes(16);\n const cipher = createCipheriv(\"aes-256-cbc\", keyBuf, iv);\n return Buffer.concat([iv, cipher.update(plaintext), cipher.final()]);\n}\n\nfunction signBody(timestamp: string, nonce: string, body: Buffer, key: string): string {\n return createHash(\"sha256\")\n .update(timestamp + nonce + key)\n .update(body)\n .digest(\"hex\");\n}\n\n/**\n * Forward one Feishu event to the local eve webhook. Re-encrypts and signs\n * when an `encryptKey` is set, so the channel handler exercises its own\n * signature + AES pipeline on every event.\n */\nexport async function postEventToWebhook(\n event: LarkEvent,\n opts: PostEventOptions,\n): Promise<void> {\n const fetchImpl = opts.fetch ?? globalThis.fetch;\n const plain = Buffer.from(JSON.stringify(event), \"utf8\");\n let body: Buffer;\n const headers: Record<string, string> = { \"content-type\": \"application/json\" };\n\n if (opts.encryptKey) {\n const encrypted = aesEncrypt(plain, opts.encryptKey).toString(\"base64\");\n body = Buffer.from(JSON.stringify({ encrypt: encrypted }), \"utf8\");\n const ts = Math.floor(Date.now() / 1000).toString();\n const nonce = randomBytes(8).toString(\"hex\");\n const sig = signBody(ts, nonce, body, opts.encryptKey);\n headers[\"x-lark-request-timestamp\"] = ts;\n headers[\"x-lark-request-nonce\"] = nonce;\n headers[\"x-lark-signature\"] = `sha256=${sig}`;\n } else {\n body = plain;\n }\n\n const res = await fetchImpl(opts.eveWebhookUrl, {\n method: \"POST\",\n headers,\n body: new Uint8Array(body.buffer, body.byteOffset, body.byteLength) as never,\n });\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n throw new Error(\n `eve-lark: forward to ${opts.eveWebhookUrl} failed (HTTP ${res.status})${text ? `: ${text.slice(0, 200)}` : \"\"}`,\n );\n }\n}\n\n/**\n * The Feishu SDK's EventDispatcher passes handlers a payload that may or may\n * not include the outer envelope. Rebuild a v2-shaped envelope so the channel\n * webhook can parse it the same way it parses a raw Feishu POST.\n */\nexport function rebuildEnvelopeFromSdkEvent(\n eventType: string,\n data: unknown,\n ctx: { appId: string; verificationToken: string },\n): LarkEvent {\n const maybeHeader = (data as { header?: Record<string, unknown> })?.header;\n const header =\n maybeHeader && typeof maybeHeader === \"object\"\n ? maybeHeader\n : {\n event_id: `lc_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,\n event_type: eventType,\n create_time: String(Math.floor(Date.now() / 1000)),\n token: ctx.verificationToken,\n app_id: ctx.appId,\n };\n const maybeEvent = (data as { event?: unknown })?.event;\n return {\n schema: \"2.0\",\n header,\n event: maybeEvent ?? data,\n };\n}\n\nexport interface StartLongConnectionArgs {\n resolved: ResolvedLarkOptions;\n eveWebhookUrl: string;\n /** Override logger. */\n log?: ((msg: string) => void) | undefined;\n logError?: ((msg: string, err?: unknown) => void) | undefined;\n /** Test seam: inject a custom SDK module. */\n sdk?: unknown;\n}\n\n/**\n * Start the Feishu WSClient side effect. Connects via the official SDK,\n * registers a handler that re-signs each event and POSTs it to the local eve\n * webhook. Resolves once the connection is established; the WSClient then\n * runs in the background for the lifetime of the process.\n *\n * @throws if @larksuiteoapi/node-sdk is not installed.\n */\nexport async function startLongConnection(args: StartLongConnectionArgs): Promise<void> {\n const log = args.log ?? ((m: string) => console.log(`[eve-lark] ${m}`));\n const logError = args.logError ?? ((m: string, e?: unknown) => console.error(`[eve-lark] ${m}`, e ?? \"\"));\n\n const sdk = (args.sdk ?? (await loadLarkSdk())) as LarkSdk;\n\n const dispatcher = new sdk.EventDispatcher({\n verificationToken: args.resolved.verificationToken,\n encryptKey: args.resolved.encryptKey,\n });\n\n dispatcher.register({\n \"im.message.receive_v1\": async (data: unknown) => {\n try {\n const envelope = rebuildEnvelopeFromSdkEvent(\"im.message.receive_v1\", data, {\n appId: args.resolved.appId,\n verificationToken: args.resolved.verificationToken,\n });\n await postEventToWebhook(envelope, {\n eveWebhookUrl: args.eveWebhookUrl,\n encryptKey: args.resolved.encryptKey,\n });\n log(`← feishu → eve ok`);\n } catch (e) {\n logError(`forward failed`, e);\n }\n },\n });\n\n const domain = args.resolved.baseUrl.includes(\"larksuite.com\")\n ? sdk.Domain.Lark\n : sdk.Domain.Feishu;\n\n const wsClient = new sdk.WSClient({\n appId: args.resolved.appId,\n appSecret: args.resolved.appSecret,\n domain,\n onReady: () => log(`WS connected to Feishu (${args.resolved.baseUrl})`),\n onError: (err: Error) => logError(`WS error`, err),\n onReconnecting: () => log(`WS reconnecting…`),\n onReconnected: () => log(`WS reconnected`),\n autoReconnect: true,\n });\n\n await wsClient.start({ eventDispatcher: dispatcher });\n}\n\n/* eslint-disable @typescript-eslint/consistent-type-imports */\n// The `import()` type query is the canonical way to express \"the runtime\n// namespace of this dynamic import\" without making the SDK a hard dep.\ntype LarkSdk = typeof import(\"@larksuiteoapi/node-sdk\");\n/* eslint-enable @typescript-eslint/consistent-type-imports */\n\nasync function loadLarkSdk(): Promise<LarkSdk> {\n try {\n return await import(\"@larksuiteoapi/node-sdk\");\n } catch {\n throw new Error(\n \"eve-lark: mode:\\\"long-connection\\\" requires @larksuiteoapi/node-sdk. Install it: pnpm add @larksuiteoapi/node-sdk (or npm/yarn equivalent).\",\n );\n }\n}\n"],"mappings":"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","names":["text","createHash","createHash"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eve-lark",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Lark/Feishu channel for the eve agent framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -45,6 +45,9 @@
|
|
|
45
45
|
"lint": "eslint . --max-warnings=0",
|
|
46
46
|
"prepublishOnly": "npm run typecheck && npm run test && npm run build"
|
|
47
47
|
},
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"@larksuiteoapi/node-sdk": "1.67.0"
|
|
50
|
+
},
|
|
48
51
|
"peerDependencies": {
|
|
49
52
|
"eve": "*"
|
|
50
53
|
},
|
|
@@ -54,7 +57,6 @@
|
|
|
54
57
|
}
|
|
55
58
|
},
|
|
56
59
|
"devDependencies": {
|
|
57
|
-
"@larksuiteoapi/node-sdk": "1.67.0",
|
|
58
60
|
"@types/node": "^22.10.0",
|
|
59
61
|
"dotenv": "^17.4.2",
|
|
60
62
|
"eslint": "^9.15.0",
|