@openclaw/crabline 0.1.9 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. package/README.md +47 -22
  2. package/assets/crabline-banner.svg +20 -0
  3. package/dist/src/bin/crabline.js.map +1 -1
  4. package/dist/src/cli/program.d.ts +25 -1
  5. package/dist/src/cli/program.js +579 -82
  6. package/dist/src/cli/program.js.map +1 -1
  7. package/dist/src/config/load.js +59 -6
  8. package/dist/src/config/load.js.map +1 -1
  9. package/dist/src/config/schema.d.ts +116 -92
  10. package/dist/src/config/schema.js +179 -73
  11. package/dist/src/config/schema.js.map +1 -1
  12. package/dist/src/core/errors.js +9 -2
  13. package/dist/src/core/errors.js.map +1 -1
  14. package/dist/src/core/exit-codes.js.map +1 -1
  15. package/dist/src/core/matcher.js +7 -6
  16. package/dist/src/core/matcher.js.map +1 -1
  17. package/dist/src/core/message-template.js.map +1 -1
  18. package/dist/src/core/nonces.d.ts +1 -0
  19. package/dist/src/core/nonces.js +4 -2
  20. package/dist/src/core/nonces.js.map +1 -1
  21. package/dist/src/core/reporters.d.ts +1 -0
  22. package/dist/src/core/reporters.js +42 -4
  23. package/dist/src/core/reporters.js.map +1 -1
  24. package/dist/src/core/run.d.ts +3 -0
  25. package/dist/src/core/run.js +415 -107
  26. package/dist/src/core/run.js.map +1 -1
  27. package/dist/src/core/safe-regex.d.ts +3 -0
  28. package/dist/src/core/safe-regex.js +21 -0
  29. package/dist/src/core/safe-regex.js.map +1 -0
  30. package/dist/src/index.d.ts +2 -2
  31. package/dist/src/index.js +1 -1
  32. package/dist/src/index.js.map +1 -1
  33. package/dist/src/openclaw/artifact-generation.d.ts +43 -0
  34. package/dist/src/openclaw/artifact-generation.js +363 -0
  35. package/dist/src/openclaw/artifact-generation.js.map +1 -0
  36. package/dist/src/openclaw/bridges/matrix.js +97 -12
  37. package/dist/src/openclaw/bridges/matrix.js.map +1 -1
  38. package/dist/src/openclaw/bridges/mattermost.js +32 -10
  39. package/dist/src/openclaw/bridges/mattermost.js.map +1 -1
  40. package/dist/src/openclaw/bridges/signal.js +55 -18
  41. package/dist/src/openclaw/bridges/signal.js.map +1 -1
  42. package/dist/src/openclaw/bridges/slack.js +85 -7
  43. package/dist/src/openclaw/bridges/slack.js.map +1 -1
  44. package/dist/src/openclaw/bridges/telegram.js +124 -30
  45. package/dist/src/openclaw/bridges/telegram.js.map +1 -1
  46. package/dist/src/openclaw/bridges/whatsapp.js +78 -17
  47. package/dist/src/openclaw/bridges/whatsapp.js.map +1 -1
  48. package/dist/src/openclaw/bridges/zalo.js +14 -5
  49. package/dist/src/openclaw/bridges/zalo.js.map +1 -1
  50. package/dist/src/openclaw/outbound-contract.d.ts +5 -0
  51. package/dist/src/openclaw/outbound-contract.js +40 -0
  52. package/dist/src/openclaw/outbound-contract.js.map +1 -0
  53. package/dist/src/openclaw/private-file.d.ts +28 -0
  54. package/dist/src/openclaw/private-file.js +465 -0
  55. package/dist/src/openclaw/private-file.js.map +1 -0
  56. package/dist/src/openclaw/shared.d.ts +21 -2
  57. package/dist/src/openclaw/shared.js +115 -18
  58. package/dist/src/openclaw/shared.js.map +1 -1
  59. package/dist/src/openclaw/smoke-lock.d.ts +63 -0
  60. package/dist/src/openclaw/smoke-lock.js +1004 -0
  61. package/dist/src/openclaw/smoke-lock.js.map +1 -0
  62. package/dist/src/openclaw.d.ts +16 -8
  63. package/dist/src/openclaw.js +334 -52
  64. package/dist/src/openclaw.js.map +1 -1
  65. package/dist/src/providers/builtin/discord.d.ts +10 -1
  66. package/dist/src/providers/builtin/discord.js +144 -18
  67. package/dist/src/providers/builtin/discord.js.map +1 -1
  68. package/dist/src/providers/builtin/external-webhook-auth.d.ts +11 -0
  69. package/dist/src/providers/builtin/external-webhook-auth.js +10 -0
  70. package/dist/src/providers/builtin/external-webhook-auth.js.map +1 -0
  71. package/dist/src/providers/builtin/feishu.d.ts +19 -2
  72. package/dist/src/providers/builtin/feishu.js +307 -25
  73. package/dist/src/providers/builtin/feishu.js.map +1 -1
  74. package/dist/src/providers/builtin/googlechat.d.ts +20 -1
  75. package/dist/src/providers/builtin/googlechat.js +178 -19
  76. package/dist/src/providers/builtin/googlechat.js.map +1 -1
  77. package/dist/src/providers/builtin/imessage.d.ts +4 -0
  78. package/dist/src/providers/builtin/imessage.js +26 -11
  79. package/dist/src/providers/builtin/imessage.js.map +1 -1
  80. package/dist/src/providers/builtin/loopback.js +92 -30
  81. package/dist/src/providers/builtin/loopback.js.map +1 -1
  82. package/dist/src/providers/builtin/matrix.d.ts +11 -1
  83. package/dist/src/providers/builtin/matrix.js +53 -29
  84. package/dist/src/providers/builtin/matrix.js.map +1 -1
  85. package/dist/src/providers/builtin/mattermost.d.ts +10 -0
  86. package/dist/src/providers/builtin/mattermost.js +38 -15
  87. package/dist/src/providers/builtin/mattermost.js.map +1 -1
  88. package/dist/src/providers/builtin/msteams.d.ts +17 -1
  89. package/dist/src/providers/builtin/msteams.js +113 -14
  90. package/dist/src/providers/builtin/msteams.js.map +1 -1
  91. package/dist/src/providers/builtin/native-local-mock.d.ts +2 -9
  92. package/dist/src/providers/builtin/native-local-mock.js +12 -35
  93. package/dist/src/providers/builtin/native-local-mock.js.map +1 -1
  94. package/dist/src/providers/builtin/script.d.ts +11 -15
  95. package/dist/src/providers/builtin/script.js +566 -104
  96. package/dist/src/providers/builtin/script.js.map +1 -1
  97. package/dist/src/providers/builtin/slack.d.ts +6 -0
  98. package/dist/src/providers/builtin/slack.js +99 -40
  99. package/dist/src/providers/builtin/slack.js.map +1 -1
  100. package/dist/src/providers/builtin/telegram.d.ts +6 -0
  101. package/dist/src/providers/builtin/telegram.js +64 -43
  102. package/dist/src/providers/builtin/telegram.js.map +1 -1
  103. package/dist/src/providers/builtin/whatsapp.d.ts +28 -8
  104. package/dist/src/providers/builtin/whatsapp.js +567 -30
  105. package/dist/src/providers/builtin/whatsapp.js.map +1 -1
  106. package/dist/src/providers/builtin/zalo.js +30 -19
  107. package/dist/src/providers/builtin/zalo.js.map +1 -1
  108. package/dist/src/providers/catalog.d.ts +3 -3
  109. package/dist/src/providers/catalog.js +3 -3
  110. package/dist/src/providers/catalog.js.map +1 -1
  111. package/dist/src/providers/local-mock.d.ts +11 -2
  112. package/dist/src/providers/local-mock.js +322 -96
  113. package/dist/src/providers/local-mock.js.map +1 -1
  114. package/dist/src/providers/native-ids.d.ts +1 -0
  115. package/dist/src/providers/native-ids.js +6 -1
  116. package/dist/src/providers/native-ids.js.map +1 -1
  117. package/dist/src/providers/recorder.d.ts +30 -2
  118. package/dist/src/providers/recorder.js +549 -70
  119. package/dist/src/providers/recorder.js.map +1 -1
  120. package/dist/src/providers/registry.d.ts +25 -1
  121. package/dist/src/providers/registry.js +280 -40
  122. package/dist/src/providers/registry.js.map +1 -1
  123. package/dist/src/providers/signed-jwt.d.ts +29 -0
  124. package/dist/src/providers/signed-jwt.js +202 -0
  125. package/dist/src/providers/signed-jwt.js.map +1 -0
  126. package/dist/src/providers/slack-ids.js.map +1 -1
  127. package/dist/src/providers/target-normalizers.d.ts +26 -0
  128. package/dist/src/providers/target-normalizers.js +276 -0
  129. package/dist/src/providers/target-normalizers.js.map +1 -0
  130. package/dist/src/providers/types.d.ts +5 -0
  131. package/dist/src/providers/types.js.map +1 -1
  132. package/dist/src/providers/webhook-server.d.ts +2 -0
  133. package/dist/src/providers/webhook-server.js +72 -25
  134. package/dist/src/providers/webhook-server.js.map +1 -1
  135. package/dist/src/servers/http.d.ts +23 -3
  136. package/dist/src/servers/http.js +213 -20
  137. package/dist/src/servers/http.js.map +1 -1
  138. package/dist/src/servers/index.js +20 -21
  139. package/dist/src/servers/index.js.map +1 -1
  140. package/dist/src/servers/matrix.js +465 -51
  141. package/dist/src/servers/matrix.js.map +1 -1
  142. package/dist/src/servers/mattermost.d.ts +5 -0
  143. package/dist/src/servers/mattermost.js +306 -58
  144. package/dist/src/servers/mattermost.js.map +1 -1
  145. package/dist/src/servers/pending-events.d.ts +2 -0
  146. package/dist/src/servers/pending-events.js +11 -0
  147. package/dist/src/servers/pending-events.js.map +1 -0
  148. package/dist/src/servers/recorder.d.ts +5 -0
  149. package/dist/src/servers/recorder.js +262 -3
  150. package/dist/src/servers/recorder.js.map +1 -1
  151. package/dist/src/servers/signal.d.ts +2 -0
  152. package/dist/src/servers/signal.js +444 -56
  153. package/dist/src/servers/signal.js.map +1 -1
  154. package/dist/src/servers/slack.d.ts +5 -0
  155. package/dist/src/servers/slack.js +515 -49
  156. package/dist/src/servers/slack.js.map +1 -1
  157. package/dist/src/servers/telegram.d.ts +111 -0
  158. package/dist/src/servers/telegram.js +830 -158
  159. package/dist/src/servers/telegram.js.map +1 -1
  160. package/dist/src/servers/webhook-target.d.ts +40 -0
  161. package/dist/src/servers/webhook-target.js +349 -0
  162. package/dist/src/servers/webhook-target.js.map +1 -0
  163. package/dist/src/servers/websocket.d.ts +2 -0
  164. package/dist/src/servers/websocket.js +29 -0
  165. package/dist/src/servers/websocket.js.map +1 -0
  166. package/dist/src/servers/whatsapp-baileys-websocket.d.ts +65 -1
  167. package/dist/src/servers/whatsapp-baileys-websocket.js +723 -99
  168. package/dist/src/servers/whatsapp-baileys-websocket.js.map +1 -1
  169. package/dist/src/servers/whatsapp-jid.d.ts +5 -0
  170. package/dist/src/servers/whatsapp-jid.js +32 -0
  171. package/dist/src/servers/whatsapp-jid.js.map +1 -0
  172. package/dist/src/servers/whatsapp-wire/binary-node.d.ts +5 -0
  173. package/dist/src/servers/whatsapp-wire/binary-node.js +87 -20
  174. package/dist/src/servers/whatsapp-wire/binary-node.js.map +1 -1
  175. package/dist/src/servers/whatsapp-wire/crypto.d.ts +6 -1
  176. package/dist/src/servers/whatsapp-wire/crypto.js +99 -45
  177. package/dist/src/servers/whatsapp-wire/crypto.js.map +1 -1
  178. package/dist/src/servers/whatsapp-wire/handshake.js +39 -4
  179. package/dist/src/servers/whatsapp-wire/handshake.js.map +1 -1
  180. package/dist/src/servers/whatsapp-wire/signal.js.map +1 -1
  181. package/dist/src/servers/whatsapp.d.ts +12 -7
  182. package/dist/src/servers/whatsapp.js +269 -107
  183. package/dist/src/servers/whatsapp.js.map +1 -1
  184. package/dist/src/servers/zalo.d.ts +14 -0
  185. package/dist/src/servers/zalo.js +521 -88
  186. package/dist/src/servers/zalo.js.map +1 -1
  187. package/docs/channel-setup.md +197 -19
  188. package/fixtures/examples/crabline.example.yaml +10 -0
  189. package/package.json +20 -15
@@ -1,49 +1,172 @@
1
+ import { createDecipheriv, createHash, timingSafeEqual } from "node:crypto";
1
2
  import path from "node:path";
2
3
  import { CrablineError } from "../../core/errors.js";
3
4
  import { LocalMockProviderAdapter } from "../local-mock.js";
4
- import { authorFromBotFlag, createNativeTargetCodec, genericMockPayloadWithNativeThread, isRecord, optionalRecord, optionalString, requireNativeInboundId, } from "./native-local-mock.js";
5
- const FEISHU_CHAT_ID_RULE = {
6
- example: "oc_abc123",
7
- name: "Feishu chat_id",
8
- pattern: /^oc_[A-Za-z0-9_-]+$/u,
9
- };
10
- const FEISHU_MESSAGE_ID_RULE = {
11
- example: "om_abc123",
12
- name: "Feishu message_id",
13
- pattern: /^om_[A-Za-z0-9_-]+$/u,
14
- };
15
- export function resolveFeishuAdapterConfig(config, _env = process.env) {
5
+ import { FEISHU_CHAT_ID_RULE, FEISHU_MESSAGE_ID_RULE, getBuiltinTargetCodec, } from "../target-normalizers.js";
6
+ import { authorFromBotFlag, createSecretVerifier, genericMockPayloadWithNativeThread, isRecord, optionalRecord, optionalString, requireNativeInboundId, } from "./native-local-mock.js";
7
+ import { requireExternalWebhookAuthentication } from "./external-webhook-auth.js";
8
+ const FEISHU_MAX_CALLBACK_AGE_MS = 5 * 60_000;
9
+ const FEISHU_REPLAY_CACHE_LIMIT = 2_048;
10
+ export function resolveFeishuAdapterConfig(config, env = process.env) {
16
11
  return {
17
12
  appId: config.feishu?.appId ?? "local-mock-feishu-app",
13
+ encryptKey: config.feishu?.encryptKey ?? env.FEISHU_ENCRYPT_KEY,
18
14
  userName: config.feishu?.userName,
15
+ verificationToken: config.feishu?.verificationToken ?? env.FEISHU_VERIFICATION_TOKEN,
16
+ };
17
+ }
18
+ export function handleFeishuWebhookPayload(payload) {
19
+ if (isRecord(payload) &&
20
+ payload.type === "url_verification" &&
21
+ typeof payload.challenge === "string") {
22
+ return Response.json({ challenge: payload.challenge });
23
+ }
24
+ if (isRecord(payload) && isRecord(payload.event)) {
25
+ const message = optionalRecord(payload.event, "message");
26
+ if (!message) {
27
+ return new Response(null, { status: 200 });
28
+ }
29
+ const messageType = optionalString(message, "message_type");
30
+ const text = parseFeishuText(optionalString(message, "content"));
31
+ if (messageType !== "text" || !text) {
32
+ return new Response(null, { status: 200 });
33
+ }
34
+ }
35
+ return undefined;
36
+ }
37
+ export function createFeishuWebhookAuthenticator(config, env = process.env, runtime = {}) {
38
+ return createFeishuWebhookAuthenticatorWithReplay(config, env, runtime);
39
+ }
40
+ function createFeishuWebhookAuthenticatorWithReplay(config, env, runtime, replayState) {
41
+ const resolved = resolveFeishuAdapterConfig(config, env);
42
+ const validateCallback = resolved.verificationToken
43
+ ? createSecretVerifier(resolved.verificationToken)
44
+ : undefined;
45
+ if (!(resolved.encryptKey || validateCallback)) {
46
+ return undefined;
47
+ }
48
+ return async (request, rawBody) => {
49
+ let payload;
50
+ try {
51
+ payload = JSON.parse(rawBody);
52
+ }
53
+ catch {
54
+ return undefined;
55
+ }
56
+ if (!isRecord(payload)) {
57
+ return unauthorizedFeishuWebhook();
58
+ }
59
+ const encryptedEnvelope = payload;
60
+ const encrypted = typeof payload.encrypt === "string";
61
+ let signedRequest = false;
62
+ if (encrypted) {
63
+ if (!resolved.encryptKey) {
64
+ return unauthorizedFeishuWebhook();
65
+ }
66
+ try {
67
+ payload = decryptFeishuWebhookPayload(payload, resolved.encryptKey);
68
+ }
69
+ catch {
70
+ return unauthorizedFeishuWebhook();
71
+ }
72
+ if (!verifyFeishuSignature(request, rawBody, resolved.encryptKey) &&
73
+ !isFeishuUrlVerification(payload)) {
74
+ return unauthorizedFeishuWebhook();
75
+ }
76
+ signedRequest = !isFeishuUrlVerification(payload);
77
+ }
78
+ else if (resolved.encryptKey) {
79
+ return unauthorizedFeishuWebhook();
80
+ }
81
+ if (validateCallback && !validateCallback(readFeishuVerificationToken(payload))) {
82
+ return unauthorizedFeishuWebhook();
83
+ }
84
+ const now = runtime.now?.() ?? Date.now();
85
+ const callbackTimestamp = readFeishuCallbackTimestamp(request, payload, signedRequest);
86
+ if (callbackTimestamp === null) {
87
+ return unauthorizedFeishuWebhook();
88
+ }
89
+ if (callbackTimestamp !== undefined &&
90
+ Math.abs(now - callbackTimestamp) > FEISHU_MAX_CALLBACK_AGE_MS) {
91
+ return unauthorizedFeishuWebhook();
92
+ }
93
+ const callbackKeys = readFeishuCallbackKeys(payload, encryptedEnvelope);
94
+ if (!replayState) {
95
+ return undefined;
96
+ }
97
+ pruneFeishuReplayCache(replayState.accepted, now);
98
+ while (callbackKeys.length > 0) {
99
+ if (callbackKeys.some((key) => replayState.accepted.has(key))) {
100
+ return new Response(null, { status: 200 });
101
+ }
102
+ const reservation = callbackKeys
103
+ .map((key) => replayState.inFlight.get(key))
104
+ .find((candidate) => candidate !== undefined);
105
+ if (!reservation) {
106
+ reserveFeishuCallback(replayState, callbackKeys);
107
+ break;
108
+ }
109
+ if (await reservation.promise) {
110
+ return new Response(null, { status: 200 });
111
+ }
112
+ }
113
+ return undefined;
19
114
  };
20
115
  }
21
116
  export class FeishuProviderAdapter extends LocalMockProviderAdapter {
22
- constructor(id, config, _userName, _runtime) {
117
+ constructor(id, config, _userName, runtime) {
118
+ const authRuntime = runtime ?? {};
119
+ const env = authRuntime.env ?? process.env;
120
+ const resolved = resolveFeishuAdapterConfig(config, env);
121
+ requireExternalWebhookAuthentication({
122
+ authenticated: Boolean(resolved.encryptKey),
123
+ provider: "Feishu",
124
+ requirement: "feishu.encryptKey or FEISHU_ENCRYPT_KEY for X-Lark-Signature verification",
125
+ webhook: config.feishu?.webhook,
126
+ });
127
+ const replayState = {
128
+ accepted: new Map(),
129
+ inFlight: new Map(),
130
+ };
131
+ const authenticateWebhookRequest = createFeishuWebhookAuthenticatorWithReplay(config, env, authRuntime, replayState);
132
+ const decodePayload = (payload) => resolved.encryptKey ? decryptFeishuWebhookPayload(payload, resolved.encryptKey) : payload;
23
133
  super({
24
- codec: createNativeTargetCodec({
25
- channel: FEISHU_CHAT_ID_RULE,
26
- channelLabel: "Feishu chat_id",
27
- thread: FEISHU_MESSAGE_ID_RULE,
28
- threadLabel: "Feishu message_id",
29
- }),
134
+ codec: getBuiltinTargetCodec("feishu"),
30
135
  config,
31
136
  id,
32
137
  options: {
138
+ ...(authenticateWebhookRequest ? { authenticateWebhookRequest } : {}),
139
+ createWebhookSuccessResponse(payload, responseId) {
140
+ return new Response(JSON.stringify({ id: responseId, ok: true }), {
141
+ headers: { "content-type": "application/json" },
142
+ status: 200,
143
+ });
144
+ },
33
145
  defaultWebhook: { host: "127.0.0.1", path: "/feishu/webhook", port: 8795 },
34
146
  endpointLabel: "webhook endpoint",
35
- normalizeWebhookPayload: normalizeFeishuWebhookPayload,
147
+ handleWebhookPayload: (payload) => handleFeishuWebhookPayload(decodePayload(payload)),
148
+ normalizeWebhookPayload: (payload) => normalizeFeishuWebhookPayload(decodePayload(payload)),
36
149
  platform: "feishu",
37
150
  publicUrl: config.feishu?.webhook.publicUrl,
38
151
  recorderPath: config.feishu?.recorder.path
39
152
  ? path.resolve(config.feishu.recorder.path)
40
153
  : undefined,
154
+ settleWebhookRequest({ accepted, payload, rawBody }) {
155
+ const encryptedEnvelope = readFeishuSettledEnvelope(payload, rawBody);
156
+ const decodedPayload = decodePayload(encryptedEnvelope);
157
+ if (accepted) {
158
+ acceptFeishuCallback(replayState, decodedPayload, encryptedEnvelope, authRuntime.now?.() ?? Date.now());
159
+ }
160
+ else {
161
+ rejectFeishuCallback(replayState, decodedPayload, encryptedEnvelope);
162
+ }
163
+ },
41
164
  webhook: config.feishu?.webhook,
42
165
  },
43
166
  });
44
167
  }
45
168
  }
46
- function normalizeFeishuWebhookPayload(payload) {
169
+ export function normalizeFeishuWebhookPayload(payload) {
47
170
  if (!isRecord(payload)) {
48
171
  throw new CrablineError("Feishu webhook payload must be an object", { kind: "inbound" });
49
172
  }
@@ -57,23 +180,32 @@ function normalizeFeishuWebhookPayload(payload) {
57
180
  });
58
181
  }
59
182
  const chatId = optionalString(message, "chat_id");
183
+ const messageType = optionalString(message, "message_type");
60
184
  const messageId = optionalString(message, "message_id");
185
+ const rootId = optionalString(message, "root_id");
61
186
  const rawContent = optionalString(message, "content");
62
187
  const text = parseFeishuText(rawContent);
188
+ if (messageType !== "text") {
189
+ throw new CrablineError("Feishu event payload requires message.message_type=text", {
190
+ kind: "inbound",
191
+ });
192
+ }
63
193
  if (!chatId || !text) {
64
194
  throw new CrablineError("Feishu event payload requires message.chat_id and message.content", {
65
195
  kind: "inbound",
66
196
  });
67
197
  }
68
198
  return {
69
- author: authorFromBotFlag(false),
199
+ author: authorFromBotFlag(event
200
+ ? ["app", "bot"].includes(optionalString(optionalRecord(event, "sender") ?? {}, "sender_type") ?? "")
201
+ : false),
70
202
  ...(messageId
71
203
  ? { id: requireNativeInboundId(messageId, FEISHU_MESSAGE_ID_RULE, "Feishu message_id") }
72
204
  : {}),
73
205
  raw: payload,
74
206
  text,
75
- threadId: messageId
76
- ? requireNativeInboundId(messageId, FEISHU_MESSAGE_ID_RULE, "Feishu message_id")
207
+ threadId: rootId
208
+ ? requireNativeInboundId(rootId, FEISHU_MESSAGE_ID_RULE, "Feishu root_id")
77
209
  : requireNativeInboundId(chatId, FEISHU_CHAT_ID_RULE, "Feishu chat_id"),
78
210
  };
79
211
  }
@@ -86,10 +218,160 @@ function parseFeishuText(content) {
86
218
  if (isRecord(parsed) && typeof parsed.text === "string") {
87
219
  return parsed.text;
88
220
  }
221
+ return undefined;
89
222
  }
90
223
  catch {
91
224
  return content;
92
225
  }
93
- return content;
226
+ }
227
+ function readFeishuSettledEnvelope(payload, rawBody) {
228
+ if (isRecord(payload)) {
229
+ return payload;
230
+ }
231
+ try {
232
+ return JSON.parse(rawBody);
233
+ }
234
+ catch {
235
+ return payload;
236
+ }
237
+ }
238
+ export function decryptFeishuWebhookPayload(payload, encryptKey) {
239
+ if (!isRecord(payload) || typeof payload.encrypt !== "string") {
240
+ return payload;
241
+ }
242
+ const encrypted = Buffer.from(payload.encrypt, "base64");
243
+ if (encrypted.length <= 16 || (encrypted.length - 16) % 16 !== 0) {
244
+ throw new Error("Feishu encrypted payload is truncated.");
245
+ }
246
+ const key = createHash("sha256").update(encryptKey).digest();
247
+ const decipher = createDecipheriv("aes-256-cbc", key, encrypted.subarray(0, 16));
248
+ const decrypted = Buffer.concat([
249
+ decipher.update(encrypted.subarray(16)),
250
+ decipher.final(),
251
+ ]).toString("utf8");
252
+ return JSON.parse(decrypted);
253
+ }
254
+ function readFeishuVerificationToken(payload) {
255
+ if (!isRecord(payload)) {
256
+ return null;
257
+ }
258
+ const header = optionalRecord(payload, "header");
259
+ return (optionalString(payload, "token") ?? (header ? optionalString(header, "token") : null) ?? null);
260
+ }
261
+ function isFeishuUrlVerification(payload) {
262
+ return (isRecord(payload) &&
263
+ payload.type === "url_verification" &&
264
+ typeof payload.challenge === "string" &&
265
+ payload.challenge.length > 0);
266
+ }
267
+ function verifyFeishuSignature(request, rawBody, encryptKey) {
268
+ const timestamp = request.headers.get("x-lark-request-timestamp");
269
+ const nonce = request.headers.get("x-lark-request-nonce");
270
+ const signature = request.headers.get("x-lark-signature");
271
+ if (!timestamp || !nonce || !signature || !/^[0-9a-f]{64}$/iu.test(signature)) {
272
+ return false;
273
+ }
274
+ const expected = createHash("sha256")
275
+ .update(timestamp + nonce + encryptKey + rawBody)
276
+ .digest();
277
+ return timingSafeEqual(expected, Buffer.from(signature, "hex"));
278
+ }
279
+ function readFeishuCallbackTimestamp(request, payload, signedRequest) {
280
+ const requestTimestamp = signedRequest
281
+ ? request.headers.get("x-lark-request-timestamp")
282
+ : undefined;
283
+ const header = isRecord(payload) ? optionalRecord(payload, "header") : undefined;
284
+ const headerTimestamp = header ? optionalString(header, "create_time") : undefined;
285
+ const value = requestTimestamp ?? headerTimestamp;
286
+ if (!value) {
287
+ return undefined;
288
+ }
289
+ if (!/^\d+$/u.test(value)) {
290
+ return null;
291
+ }
292
+ const timestamp = Number(value);
293
+ if (!Number.isSafeInteger(timestamp)) {
294
+ return null;
295
+ }
296
+ if (timestamp >= 100_000_000_000_000) {
297
+ return Math.floor(timestamp / 1_000);
298
+ }
299
+ if (timestamp >= 100_000_000_000) {
300
+ return timestamp;
301
+ }
302
+ return timestamp * 1_000;
303
+ }
304
+ function readFeishuCallbackKeys(payload, encryptedEnvelope) {
305
+ if (!isRecord(payload) || isFeishuUrlVerification(payload)) {
306
+ return [];
307
+ }
308
+ const event = optionalRecord(payload, "event");
309
+ const message = event ? optionalRecord(event, "message") : undefined;
310
+ const header = optionalRecord(payload, "header");
311
+ const messageId = message ? optionalString(message, "message_id") : undefined;
312
+ const eventId = header ? optionalString(header, "event_id") : undefined;
313
+ const encrypted = isRecord(encryptedEnvelope)
314
+ ? optionalString(encryptedEnvelope, "encrypt")
315
+ : undefined;
316
+ return [
317
+ ...(messageId ? [`message:${messageId}`] : []),
318
+ ...(eventId ? [`event:${eventId}`] : []),
319
+ ...(encrypted ? [`encrypted:${createHash("sha256").update(encrypted).digest("hex")}`] : []),
320
+ ];
321
+ }
322
+ function reserveFeishuCallback(replayState, callbackKeys) {
323
+ let resolveReservation;
324
+ const promise = new Promise((resolve) => {
325
+ resolveReservation = resolve;
326
+ });
327
+ const reservation = {
328
+ keys: new Set(callbackKeys),
329
+ promise,
330
+ resolve: resolveReservation,
331
+ };
332
+ for (const key of callbackKeys) {
333
+ replayState.inFlight.set(key, reservation);
334
+ }
335
+ }
336
+ function acceptFeishuCallback(replayState, payload, encryptedEnvelope, now) {
337
+ const callbackKeys = readFeishuCallbackKeys(payload, encryptedEnvelope);
338
+ pruneFeishuReplayCache(replayState.accepted, now);
339
+ for (const key of callbackKeys) {
340
+ replayState.accepted.set(key, now);
341
+ }
342
+ const reservations = new Set(callbackKeys
343
+ .map((key) => replayState.inFlight.get(key))
344
+ .filter((reservation) => reservation !== undefined));
345
+ for (const reservation of reservations) {
346
+ releaseFeishuReservation(replayState, reservation, true);
347
+ }
348
+ }
349
+ function rejectFeishuCallback(replayState, payload, encryptedEnvelope) {
350
+ const callbackKeys = readFeishuCallbackKeys(payload, encryptedEnvelope);
351
+ const reservations = new Set(callbackKeys
352
+ .map((key) => replayState.inFlight.get(key))
353
+ .filter((reservation) => reservation !== undefined));
354
+ for (const reservation of reservations) {
355
+ releaseFeishuReservation(replayState, reservation, false);
356
+ }
357
+ }
358
+ function releaseFeishuReservation(replayState, reservation, accepted) {
359
+ for (const key of reservation.keys) {
360
+ if (replayState.inFlight.get(key) === reservation) {
361
+ replayState.inFlight.delete(key);
362
+ }
363
+ }
364
+ reservation.resolve(accepted);
365
+ }
366
+ function pruneFeishuReplayCache(recentCallbacks, now) {
367
+ for (const [key, acceptedAt] of recentCallbacks) {
368
+ if (now - acceptedAt > FEISHU_MAX_CALLBACK_AGE_MS ||
369
+ recentCallbacks.size > FEISHU_REPLAY_CACHE_LIMIT) {
370
+ recentCallbacks.delete(key);
371
+ }
372
+ }
373
+ }
374
+ function unauthorizedFeishuWebhook() {
375
+ return new Response("unauthorized", { status: 401 });
94
376
  }
95
377
  //# sourceMappingURL=feishu.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"feishu.js","sourceRoot":"","sources":["../../../../src/providers/builtin/feishu.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"feishu.js","sourceRoot":"","sources":["../../../../src/providers/builtin/feishu.ts"],"names":[],"mappings":"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","sourcesContent":["import { createDecipheriv, createHash, timingSafeEqual } from \"node:crypto\";\nimport path from \"node:path\";\nimport { CrablineError } from \"../../core/errors.js\";\nimport type { ProviderConfig } from \"../../config/schema.js\";\nimport { LocalMockProviderAdapter } from \"../local-mock.js\";\nimport type { ProviderAdapter } from \"../types.js\";\nimport {\n FEISHU_CHAT_ID_RULE,\n FEISHU_MESSAGE_ID_RULE,\n getBuiltinTargetCodec,\n} from \"../target-normalizers.js\";\nimport {\n authorFromBotFlag,\n createSecretVerifier,\n genericMockPayloadWithNativeThread,\n isRecord,\n optionalRecord,\n optionalString,\n requireNativeInboundId,\n} from \"./native-local-mock.js\";\nimport { requireExternalWebhookAuthentication } from \"./external-webhook-auth.js\";\n\ntype FeishuEnvironment = Partial<\n Pick<NodeJS.ProcessEnv, \"FEISHU_ENCRYPT_KEY\" | \"FEISHU_VERIFICATION_TOKEN\">\n>;\n\ntype FeishuAuthRuntime = {\n now?: (() => number) | undefined;\n};\n\ntype FeishuReplayReservation = {\n keys: Set<string>;\n promise: Promise<boolean>;\n resolve: (accepted: boolean) => void;\n};\n\ntype FeishuReplayState = {\n accepted: Map<string, number>;\n inFlight: Map<string, FeishuReplayReservation>;\n};\n\nconst FEISHU_MAX_CALLBACK_AGE_MS = 5 * 60_000;\nconst FEISHU_REPLAY_CACHE_LIMIT = 2_048;\n\nexport function resolveFeishuAdapterConfig(\n config: ProviderConfig,\n env: FeishuEnvironment = process.env,\n) {\n return {\n appId: config.feishu?.appId ?? \"local-mock-feishu-app\",\n encryptKey: config.feishu?.encryptKey ?? env.FEISHU_ENCRYPT_KEY,\n userName: config.feishu?.userName,\n verificationToken: config.feishu?.verificationToken ?? env.FEISHU_VERIFICATION_TOKEN,\n };\n}\n\nexport function handleFeishuWebhookPayload(payload: unknown): Response | undefined {\n if (\n isRecord(payload) &&\n payload.type === \"url_verification\" &&\n typeof payload.challenge === \"string\"\n ) {\n return Response.json({ challenge: payload.challenge });\n }\n if (isRecord(payload) && isRecord(payload.event)) {\n const message = optionalRecord(payload.event, \"message\");\n if (!message) {\n return new Response(null, { status: 200 });\n }\n const messageType = optionalString(message, \"message_type\");\n const text = parseFeishuText(optionalString(message, \"content\"));\n if (messageType !== \"text\" || !text) {\n return new Response(null, { status: 200 });\n }\n }\n return undefined;\n}\n\nexport function createFeishuWebhookAuthenticator(\n config: ProviderConfig,\n env: FeishuEnvironment = process.env,\n runtime: FeishuAuthRuntime = {},\n) {\n return createFeishuWebhookAuthenticatorWithReplay(config, env, runtime);\n}\n\nfunction createFeishuWebhookAuthenticatorWithReplay(\n config: ProviderConfig,\n env: FeishuEnvironment,\n runtime: FeishuAuthRuntime,\n replayState?: FeishuReplayState,\n) {\n const resolved = resolveFeishuAdapterConfig(config, env);\n const validateCallback = resolved.verificationToken\n ? createSecretVerifier(resolved.verificationToken)\n : undefined;\n if (!(resolved.encryptKey || validateCallback)) {\n return undefined;\n }\n return async (request: Request, rawBody: string): Promise<Response | undefined> => {\n let payload: unknown;\n try {\n payload = JSON.parse(rawBody) as unknown;\n } catch {\n return undefined;\n }\n if (!isRecord(payload)) {\n return unauthorizedFeishuWebhook();\n }\n const encryptedEnvelope = payload;\n\n const encrypted = typeof payload.encrypt === \"string\";\n let signedRequest = false;\n if (encrypted) {\n if (!resolved.encryptKey) {\n return unauthorizedFeishuWebhook();\n }\n try {\n payload = decryptFeishuWebhookPayload(payload, resolved.encryptKey);\n } catch {\n return unauthorizedFeishuWebhook();\n }\n if (\n !verifyFeishuSignature(request, rawBody, resolved.encryptKey) &&\n !isFeishuUrlVerification(payload)\n ) {\n return unauthorizedFeishuWebhook();\n }\n signedRequest = !isFeishuUrlVerification(payload);\n } else if (resolved.encryptKey) {\n return unauthorizedFeishuWebhook();\n }\n\n if (validateCallback && !validateCallback(readFeishuVerificationToken(payload))) {\n return unauthorizedFeishuWebhook();\n }\n const now = runtime.now?.() ?? Date.now();\n const callbackTimestamp = readFeishuCallbackTimestamp(request, payload, signedRequest);\n if (callbackTimestamp === null) {\n return unauthorizedFeishuWebhook();\n }\n if (\n callbackTimestamp !== undefined &&\n Math.abs(now - callbackTimestamp) > FEISHU_MAX_CALLBACK_AGE_MS\n ) {\n return unauthorizedFeishuWebhook();\n }\n const callbackKeys = readFeishuCallbackKeys(payload, encryptedEnvelope);\n if (!replayState) {\n return undefined;\n }\n pruneFeishuReplayCache(replayState.accepted, now);\n while (callbackKeys.length > 0) {\n if (callbackKeys.some((key) => replayState.accepted.has(key))) {\n return new Response(null, { status: 200 });\n }\n const reservation = callbackKeys\n .map((key) => replayState.inFlight.get(key))\n .find((candidate) => candidate !== undefined);\n if (!reservation) {\n reserveFeishuCallback(replayState, callbackKeys);\n break;\n }\n if (await reservation.promise) {\n return new Response(null, { status: 200 });\n }\n }\n return undefined;\n };\n}\n\nexport class FeishuProviderAdapter extends LocalMockProviderAdapter implements ProviderAdapter {\n constructor(id: string, config: ProviderConfig, _userName: string, runtime?: unknown) {\n const authRuntime =\n (runtime as (FeishuAuthRuntime & { env?: FeishuEnvironment }) | undefined) ?? {};\n const env = authRuntime.env ?? process.env;\n const resolved = resolveFeishuAdapterConfig(config, env);\n requireExternalWebhookAuthentication({\n authenticated: Boolean(resolved.encryptKey),\n provider: \"Feishu\",\n requirement: \"feishu.encryptKey or FEISHU_ENCRYPT_KEY for X-Lark-Signature verification\",\n webhook: config.feishu?.webhook,\n });\n const replayState: FeishuReplayState = {\n accepted: new Map(),\n inFlight: new Map(),\n };\n const authenticateWebhookRequest = createFeishuWebhookAuthenticatorWithReplay(\n config,\n env,\n authRuntime,\n replayState,\n );\n const decodePayload = (payload: unknown) =>\n resolved.encryptKey ? decryptFeishuWebhookPayload(payload, resolved.encryptKey) : payload;\n super({\n codec: getBuiltinTargetCodec(\"feishu\"),\n config,\n id,\n options: {\n ...(authenticateWebhookRequest ? { authenticateWebhookRequest } : {}),\n createWebhookSuccessResponse(payload, responseId) {\n return new Response(JSON.stringify({ id: responseId, ok: true }), {\n headers: { \"content-type\": \"application/json\" },\n status: 200,\n });\n },\n defaultWebhook: { host: \"127.0.0.1\", path: \"/feishu/webhook\", port: 8795 },\n endpointLabel: \"webhook endpoint\",\n handleWebhookPayload: (payload) => handleFeishuWebhookPayload(decodePayload(payload)),\n normalizeWebhookPayload: (payload) => normalizeFeishuWebhookPayload(decodePayload(payload)),\n platform: \"feishu\",\n publicUrl: config.feishu?.webhook.publicUrl,\n recorderPath: config.feishu?.recorder.path\n ? path.resolve(config.feishu.recorder.path)\n : undefined,\n settleWebhookRequest({ accepted, payload, rawBody }) {\n const encryptedEnvelope = readFeishuSettledEnvelope(payload, rawBody);\n const decodedPayload = decodePayload(encryptedEnvelope);\n if (accepted) {\n acceptFeishuCallback(\n replayState,\n decodedPayload,\n encryptedEnvelope,\n authRuntime.now?.() ?? Date.now(),\n );\n } else {\n rejectFeishuCallback(replayState, decodedPayload, encryptedEnvelope);\n }\n },\n webhook: config.feishu?.webhook,\n },\n });\n }\n}\n\nexport function normalizeFeishuWebhookPayload(payload: unknown) {\n if (!isRecord(payload)) {\n throw new CrablineError(\"Feishu webhook payload must be an object\", { kind: \"inbound\" });\n }\n\n const event = optionalRecord(payload, \"event\");\n const message = event ? optionalRecord(event, \"message\") : undefined;\n if (!message) {\n return genericMockPayloadWithNativeThread({\n channelRule: FEISHU_CHAT_ID_RULE,\n payload,\n threadRule: FEISHU_MESSAGE_ID_RULE,\n });\n }\n\n const chatId = optionalString(message, \"chat_id\");\n const messageType = optionalString(message, \"message_type\");\n const messageId = optionalString(message, \"message_id\");\n const rootId = optionalString(message, \"root_id\");\n const rawContent = optionalString(message, \"content\");\n const text = parseFeishuText(rawContent);\n if (messageType !== \"text\") {\n throw new CrablineError(\"Feishu event payload requires message.message_type=text\", {\n kind: \"inbound\",\n });\n }\n if (!chatId || !text) {\n throw new CrablineError(\"Feishu event payload requires message.chat_id and message.content\", {\n kind: \"inbound\",\n });\n }\n\n return {\n author: authorFromBotFlag(\n event\n ? [\"app\", \"bot\"].includes(\n optionalString(optionalRecord(event, \"sender\") ?? {}, \"sender_type\") ?? \"\",\n )\n : false,\n ),\n ...(messageId\n ? { id: requireNativeInboundId(messageId, FEISHU_MESSAGE_ID_RULE, \"Feishu message_id\") }\n : {}),\n raw: payload,\n text,\n threadId: rootId\n ? requireNativeInboundId(rootId, FEISHU_MESSAGE_ID_RULE, \"Feishu root_id\")\n : requireNativeInboundId(chatId, FEISHU_CHAT_ID_RULE, \"Feishu chat_id\"),\n };\n}\n\nfunction parseFeishuText(content: string | undefined): string | undefined {\n if (!content) {\n return undefined;\n }\n try {\n const parsed: unknown = JSON.parse(content);\n if (isRecord(parsed) && typeof parsed.text === \"string\") {\n return parsed.text;\n }\n return undefined;\n } catch {\n return content;\n }\n}\n\nfunction readFeishuSettledEnvelope(payload: unknown, rawBody: string): unknown {\n if (isRecord(payload)) {\n return payload;\n }\n try {\n return JSON.parse(rawBody) as unknown;\n } catch {\n return payload;\n }\n}\n\nexport function decryptFeishuWebhookPayload(payload: unknown, encryptKey: string): unknown {\n if (!isRecord(payload) || typeof payload.encrypt !== \"string\") {\n return payload;\n }\n const encrypted = Buffer.from(payload.encrypt, \"base64\");\n if (encrypted.length <= 16 || (encrypted.length - 16) % 16 !== 0) {\n throw new Error(\"Feishu encrypted payload is truncated.\");\n }\n const key = createHash(\"sha256\").update(encryptKey).digest();\n const decipher = createDecipheriv(\"aes-256-cbc\", key, encrypted.subarray(0, 16));\n const decrypted = Buffer.concat([\n decipher.update(encrypted.subarray(16)),\n decipher.final(),\n ]).toString(\"utf8\");\n return JSON.parse(decrypted) as unknown;\n}\n\nfunction readFeishuVerificationToken(payload: unknown): string | null {\n if (!isRecord(payload)) {\n return null;\n }\n const header = optionalRecord(payload, \"header\");\n return (\n optionalString(payload, \"token\") ?? (header ? optionalString(header, \"token\") : null) ?? null\n );\n}\n\nfunction isFeishuUrlVerification(payload: unknown): boolean {\n return (\n isRecord(payload) &&\n payload.type === \"url_verification\" &&\n typeof payload.challenge === \"string\" &&\n payload.challenge.length > 0\n );\n}\n\nfunction verifyFeishuSignature(request: Request, rawBody: string, encryptKey: string): boolean {\n const timestamp = request.headers.get(\"x-lark-request-timestamp\");\n const nonce = request.headers.get(\"x-lark-request-nonce\");\n const signature = request.headers.get(\"x-lark-signature\");\n if (!timestamp || !nonce || !signature || !/^[0-9a-f]{64}$/iu.test(signature)) {\n return false;\n }\n const expected = createHash(\"sha256\")\n .update(timestamp + nonce + encryptKey + rawBody)\n .digest();\n return timingSafeEqual(expected, Buffer.from(signature, \"hex\"));\n}\n\nfunction readFeishuCallbackTimestamp(\n request: Request,\n payload: unknown,\n signedRequest: boolean,\n): number | null | undefined {\n const requestTimestamp = signedRequest\n ? request.headers.get(\"x-lark-request-timestamp\")\n : undefined;\n const header = isRecord(payload) ? optionalRecord(payload, \"header\") : undefined;\n const headerTimestamp = header ? optionalString(header, \"create_time\") : undefined;\n const value = requestTimestamp ?? headerTimestamp;\n if (!value) {\n return undefined;\n }\n if (!/^\\d+$/u.test(value)) {\n return null;\n }\n const timestamp = Number(value);\n if (!Number.isSafeInteger(timestamp)) {\n return null;\n }\n if (timestamp >= 100_000_000_000_000) {\n return Math.floor(timestamp / 1_000);\n }\n if (timestamp >= 100_000_000_000) {\n return timestamp;\n }\n return timestamp * 1_000;\n}\n\nfunction readFeishuCallbackKeys(payload: unknown, encryptedEnvelope: unknown): string[] {\n if (!isRecord(payload) || isFeishuUrlVerification(payload)) {\n return [];\n }\n const event = optionalRecord(payload, \"event\");\n const message = event ? optionalRecord(event, \"message\") : undefined;\n const header = optionalRecord(payload, \"header\");\n const messageId = message ? optionalString(message, \"message_id\") : undefined;\n const eventId = header ? optionalString(header, \"event_id\") : undefined;\n const encrypted = isRecord(encryptedEnvelope)\n ? optionalString(encryptedEnvelope, \"encrypt\")\n : undefined;\n return [\n ...(messageId ? [`message:${messageId}`] : []),\n ...(eventId ? [`event:${eventId}`] : []),\n ...(encrypted ? [`encrypted:${createHash(\"sha256\").update(encrypted).digest(\"hex\")}`] : []),\n ];\n}\n\nfunction reserveFeishuCallback(replayState: FeishuReplayState, callbackKeys: string[]): void {\n let resolveReservation!: (accepted: boolean) => void;\n const promise = new Promise<boolean>((resolve) => {\n resolveReservation = resolve;\n });\n const reservation: FeishuReplayReservation = {\n keys: new Set(callbackKeys),\n promise,\n resolve: resolveReservation,\n };\n for (const key of callbackKeys) {\n replayState.inFlight.set(key, reservation);\n }\n}\n\nfunction acceptFeishuCallback(\n replayState: FeishuReplayState,\n payload: unknown,\n encryptedEnvelope: unknown,\n now: number,\n): void {\n const callbackKeys = readFeishuCallbackKeys(payload, encryptedEnvelope);\n pruneFeishuReplayCache(replayState.accepted, now);\n for (const key of callbackKeys) {\n replayState.accepted.set(key, now);\n }\n const reservations = new Set(\n callbackKeys\n .map((key) => replayState.inFlight.get(key))\n .filter((reservation) => reservation !== undefined),\n );\n for (const reservation of reservations) {\n releaseFeishuReservation(replayState, reservation, true);\n }\n}\n\nfunction rejectFeishuCallback(\n replayState: FeishuReplayState,\n payload: unknown,\n encryptedEnvelope: unknown,\n): void {\n const callbackKeys = readFeishuCallbackKeys(payload, encryptedEnvelope);\n const reservations = new Set(\n callbackKeys\n .map((key) => replayState.inFlight.get(key))\n .filter((reservation) => reservation !== undefined),\n );\n for (const reservation of reservations) {\n releaseFeishuReservation(replayState, reservation, false);\n }\n}\n\nfunction releaseFeishuReservation(\n replayState: FeishuReplayState,\n reservation: FeishuReplayReservation,\n accepted: boolean,\n): void {\n for (const key of reservation.keys) {\n if (replayState.inFlight.get(key) === reservation) {\n replayState.inFlight.delete(key);\n }\n }\n reservation.resolve(accepted);\n}\n\nfunction pruneFeishuReplayCache(recentCallbacks: Map<string, number>, now: number): void {\n for (const [key, acceptedAt] of recentCallbacks) {\n if (\n now - acceptedAt > FEISHU_MAX_CALLBACK_AGE_MS ||\n recentCallbacks.size > FEISHU_REPLAY_CACHE_LIMIT\n ) {\n recentCallbacks.delete(key);\n }\n }\n}\n\nfunction unauthorizedFeishuWebhook(): Response {\n return new Response(\"unauthorized\", { status: 401 });\n}\n"]}
@@ -4,8 +4,27 @@ import type { ProviderAdapter } from "../types.js";
4
4
  export declare function resolveGoogleChatAdapterConfig(config: ProviderConfig, _env?: NodeJS.ProcessEnv): {
5
5
  endpointUrl: string | undefined;
6
6
  projectNumber: string;
7
+ pubsubServiceAccountEmail: string | undefined;
7
8
  userName: string | undefined;
8
9
  };
10
+ type GoogleChatAuthRuntime = {
11
+ fetch?: typeof fetch | undefined;
12
+ keyFetchTimeoutMs?: number | undefined;
13
+ now?: (() => number) | undefined;
14
+ unknownKeyCooldownMs?: number | undefined;
15
+ };
16
+ export declare function createGoogleChatWebhookAuthenticator(config: ProviderConfig, runtime?: GoogleChatAuthRuntime): ((request: Request, rawBody: string) => Promise<Response | undefined>) | undefined;
17
+ export declare function matchesGoogleChatThread(candidateThreadId: string, expectedThreadId: string | undefined, target?: {
18
+ channelId?: string | undefined;
19
+ }): boolean;
9
20
  export declare class GoogleChatProviderAdapter extends LocalMockProviderAdapter implements ProviderAdapter {
10
- constructor(id: string, config: ProviderConfig, _userName: string, _runtime?: unknown);
21
+ constructor(id: string, config: ProviderConfig, _userName: string, runtime?: unknown);
11
22
  }
23
+ export declare function normalizeGoogleChatWebhookPayload(payload: unknown): {
24
+ author?: "assistant" | "system" | "user" | undefined;
25
+ authorIsBot?: boolean;
26
+ id?: string | undefined;
27
+ raw: {};
28
+ text?: string | undefined;
29
+ };
30
+ export {};