@0xmaxma/claude-gateway 1.8.9 → 1.8.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 (121) hide show
  1. package/README.md +46 -6
  2. package/config.template.json +2 -1
  3. package/dist/agent/runner.d.ts +86 -1
  4. package/dist/agent/runner.d.ts.map +1 -1
  5. package/dist/agent/runner.js +396 -9
  6. package/dist/agent/runner.js.map +1 -1
  7. package/dist/api/router.d.ts.map +1 -1
  8. package/dist/api/router.js +1028 -6
  9. package/dist/api/router.js.map +1 -1
  10. package/dist/api/share-router.d.ts.map +1 -1
  11. package/dist/api/share-router.js +24 -0
  12. package/dist/api/share-router.js.map +1 -1
  13. package/dist/api/webhooks-router.d.ts +2 -0
  14. package/dist/api/webhooks-router.d.ts.map +1 -1
  15. package/dist/api/webhooks-router.js +2 -0
  16. package/dist/api/webhooks-router.js.map +1 -1
  17. package/dist/api/whatsapp-access.d.ts +123 -0
  18. package/dist/api/whatsapp-access.d.ts.map +1 -0
  19. package/dist/api/whatsapp-access.js +135 -0
  20. package/dist/api/whatsapp-access.js.map +1 -0
  21. package/dist/api/whatsapp-cloud-access.d.ts +48 -0
  22. package/dist/api/whatsapp-cloud-access.d.ts.map +1 -0
  23. package/dist/api/whatsapp-cloud-access.js +58 -0
  24. package/dist/api/whatsapp-cloud-access.js.map +1 -0
  25. package/dist/api/whatsapp-cloud-client.d.ts +143 -0
  26. package/dist/api/whatsapp-cloud-client.d.ts.map +1 -0
  27. package/dist/api/whatsapp-cloud-client.js +298 -0
  28. package/dist/api/whatsapp-cloud-client.js.map +1 -0
  29. package/dist/api/whatsapp-cloud-webhook-router.d.ts +132 -0
  30. package/dist/api/whatsapp-cloud-webhook-router.d.ts.map +1 -0
  31. package/dist/api/whatsapp-cloud-webhook-router.js +494 -0
  32. package/dist/api/whatsapp-cloud-webhook-router.js.map +1 -0
  33. package/dist/cli/args.d.ts +34 -0
  34. package/dist/cli/args.d.ts.map +1 -1
  35. package/dist/cli/args.js +55 -0
  36. package/dist/cli/args.js.map +1 -1
  37. package/dist/cli/commands/app.d.ts +28 -0
  38. package/dist/cli/commands/app.d.ts.map +1 -0
  39. package/dist/cli/commands/app.js +411 -0
  40. package/dist/cli/commands/app.js.map +1 -0
  41. package/dist/cli/commands/doctor.d.ts.map +1 -1
  42. package/dist/cli/commands/doctor.js +77 -16
  43. package/dist/cli/commands/doctor.js.map +1 -1
  44. package/dist/cli/commands/service.d.ts.map +1 -1
  45. package/dist/cli/commands/service.js +373 -56
  46. package/dist/cli/commands/service.js.map +1 -1
  47. package/dist/cli/commands/update.d.ts.map +1 -1
  48. package/dist/cli/commands/update.js +1 -17
  49. package/dist/cli/commands/update.js.map +1 -1
  50. package/dist/cli/http-client.d.ts +8 -0
  51. package/dist/cli/http-client.d.ts.map +1 -1
  52. package/dist/cli/http-client.js +5 -2
  53. package/dist/cli/http-client.js.map +1 -1
  54. package/dist/cli/index.d.ts.map +1 -1
  55. package/dist/cli/index.js +16 -4
  56. package/dist/cli/index.js.map +1 -1
  57. package/dist/cli/prompt.d.ts +17 -2
  58. package/dist/cli/prompt.d.ts.map +1 -1
  59. package/dist/cli/prompt.js +33 -2
  60. package/dist/cli/prompt.js.map +1 -1
  61. package/dist/config/loader.d.ts +8 -0
  62. package/dist/config/loader.d.ts.map +1 -1
  63. package/dist/config/loader.js +22 -4
  64. package/dist/config/loader.js.map +1 -1
  65. package/dist/config/watcher.d.ts.map +1 -1
  66. package/dist/config/watcher.js +12 -0
  67. package/dist/config/watcher.js.map +1 -1
  68. package/dist/config/whatsapp-accounts.d.ts +77 -0
  69. package/dist/config/whatsapp-accounts.d.ts.map +1 -0
  70. package/dist/config/whatsapp-accounts.js +218 -0
  71. package/dist/config/whatsapp-accounts.js.map +1 -0
  72. package/dist/history/db.d.ts.map +1 -1
  73. package/dist/history/db.js +43 -11
  74. package/dist/history/db.js.map +1 -1
  75. package/dist/history/types.d.ts +18 -1
  76. package/dist/history/types.d.ts.map +1 -1
  77. package/dist/history/types.js +1 -1
  78. package/dist/history/types.js.map +1 -1
  79. package/dist/index.js +26 -0
  80. package/dist/index.js.map +1 -1
  81. package/dist/session/process.d.ts.map +1 -1
  82. package/dist/session/process.js +37 -0
  83. package/dist/session/process.js.map +1 -1
  84. package/dist/session/store.d.ts +3 -2
  85. package/dist/session/store.d.ts.map +1 -1
  86. package/dist/session/store.js +1 -1
  87. package/dist/session/store.js.map +1 -1
  88. package/dist/share/session-video-catalog.d.ts +19 -0
  89. package/dist/share/session-video-catalog.d.ts.map +1 -0
  90. package/dist/share/session-video-catalog.js +119 -0
  91. package/dist/share/session-video-catalog.js.map +1 -0
  92. package/dist/shared/image-optimize.d.ts +32 -0
  93. package/dist/shared/image-optimize.d.ts.map +1 -0
  94. package/dist/shared/image-optimize.js +197 -0
  95. package/dist/shared/image-optimize.js.map +1 -0
  96. package/dist/shared/image-sniff.d.ts +9 -0
  97. package/dist/shared/image-sniff.d.ts.map +1 -1
  98. package/dist/shared/image-sniff.js +13 -0
  99. package/dist/shared/image-sniff.js.map +1 -1
  100. package/dist/shared/text-chunk.d.ts +37 -0
  101. package/dist/shared/text-chunk.d.ts.map +1 -0
  102. package/dist/shared/text-chunk.js +105 -0
  103. package/dist/shared/text-chunk.js.map +1 -0
  104. package/dist/shared/whatsapp-ack.d.ts +13 -0
  105. package/dist/shared/whatsapp-ack.d.ts.map +1 -0
  106. package/dist/shared/whatsapp-ack.js +16 -0
  107. package/dist/shared/whatsapp-ack.js.map +1 -0
  108. package/dist/types.d.ts +183 -0
  109. package/dist/types.d.ts.map +1 -1
  110. package/dist/whatsapp/manager.d.ts +173 -0
  111. package/dist/whatsapp/manager.d.ts.map +1 -0
  112. package/dist/whatsapp/manager.js +838 -0
  113. package/dist/whatsapp/manager.js.map +1 -0
  114. package/mcp/server.ts +4 -0
  115. package/mcp/tools/image/module.ts +6 -1
  116. package/mcp/tools/telegram/receiver-server.ts +15 -8
  117. package/mcp/tools/telegram/reply-attachment.ts +81 -0
  118. package/mcp/tools/video/module.ts +6 -1
  119. package/mcp/tools/whatsapp/module.ts +162 -0
  120. package/mcp/tools/whatsapp-cloud/module.ts +536 -0
  121. package/package.json +7 -2
@@ -0,0 +1,143 @@
1
+ import { WHATSAPP_ACK_EMOJI } from '../shared/whatsapp-ack';
2
+ /**
3
+ * Cloud API hard limit for a text message body. Longer replies are split
4
+ * across several messages rather than being rejected by Meta (error 131009)
5
+ * or silently truncated.
6
+ */
7
+ export declare const WHATSAPP_CLOUD_MAX_TEXT_CHARS = 4096;
8
+ /**
9
+ * Meta's hard cap on reply buttons in an `interactive: {type: 'button'}`
10
+ * message. Not a soft/UX limit — the Graph API rejects a 4th button outright,
11
+ * so `sendInteractiveButtons` refuses locally with a readable message rather
12
+ * than letting the agent see an opaque Meta error code.
13
+ */
14
+ export declare const WHATSAPP_CLOUD_MAX_BUTTONS = 3;
15
+ /** A single reply button: `id` is what comes back on tap, `title` is what the user sees. */
16
+ export interface WhatsAppCloudButton {
17
+ id: string;
18
+ title: string;
19
+ }
20
+ /** One row of a list message. `description` is the optional grey sub-line. */
21
+ export interface WhatsAppCloudListRow {
22
+ id: string;
23
+ title: string;
24
+ description?: string;
25
+ }
26
+ /** A list message's section — a titled group of rows. */
27
+ export interface WhatsAppCloudListSection {
28
+ title: string;
29
+ rows: WhatsAppCloudListRow[];
30
+ }
31
+ export { WHATSAPP_ACK_EMOJI };
32
+ export interface WhatsAppCloudClientOptions {
33
+ accessToken: string;
34
+ phoneNumberId: string;
35
+ logDir: string;
36
+ /** Test-only override for the Graph API base URL. Production uses the real default. */
37
+ apiBase?: string;
38
+ }
39
+ export interface WhatsAppCloudApiResponse {
40
+ error?: {
41
+ message?: string;
42
+ type?: string;
43
+ code?: number;
44
+ [key: string]: unknown;
45
+ };
46
+ [key: string]: unknown;
47
+ }
48
+ export declare class WhatsAppCloudClient {
49
+ private readonly accessToken;
50
+ private readonly phoneNumberId;
51
+ private readonly apiBase;
52
+ private readonly logger;
53
+ constructor(opts: WhatsAppCloudClientOptions);
54
+ private call;
55
+ /**
56
+ * Verify the token + phone number id — used both as the connect flow's
57
+ * "Save"-time check (router.ts) and reused here for any other caller that
58
+ * wants a live credential check without duplicating the request shape.
59
+ */
60
+ verifyCredentials(): Promise<{
61
+ ok: boolean;
62
+ error?: string;
63
+ }>;
64
+ /**
65
+ * Send a plain text message, splitting anything over the Cloud API's
66
+ * 4096-char body limit into several messages (one Graph API call per chunk,
67
+ * sent in order).
68
+ *
69
+ * `quotedMessageId` makes the FIRST chunk a quote-reply to that inbound
70
+ * message (Meta's outbound `context: {message_id}`); follow-on chunks are
71
+ * plain so the conversation doesn't show the same quote block repeated.
72
+ *
73
+ * Returns the first errored response if any chunk failed (so existing
74
+ * `if (sent.error)` callers still see the failure), otherwise the last
75
+ * successful one.
76
+ */
77
+ sendText(to: string, body: string, quotedMessageId?: string): Promise<WhatsAppCloudApiResponse>;
78
+ /**
79
+ * Send up to 3 tappable reply buttons under a body of text (Phase 3).
80
+ *
81
+ * A tap comes back through the webhook as `type: 'interactive'` with a
82
+ * `button_reply` — the inbound normalizer turns that into plain text
83
+ * (the button's title), so the agent reads a tap exactly as if the user had
84
+ * typed the label. See normalizeWhatsAppCloudMessage's interactive branch.
85
+ *
86
+ * THROWS (rather than returning an `{error}` response) when more than
87
+ * WHATSAPP_CLOUD_MAX_BUTTONS are passed or the list is empty: both are
88
+ * caller bugs that Meta would reject anyway, and a thrown, readable message
89
+ * reaches the agent through the MCP tool's catch as actionable text.
90
+ */
91
+ sendInteractiveButtons(to: string, bodyText: string, buttons: WhatsAppCloudButton[]): Promise<WhatsAppCloudApiResponse>;
92
+ /**
93
+ * Send a list message — a single button that opens a picker of grouped rows
94
+ * (Phase 3). The way past the 3-button ceiling above.
95
+ *
96
+ * A pick comes back as `type: 'interactive'` with a `list_reply`, handled by
97
+ * the same normalizer branch as button taps.
98
+ */
99
+ sendInteractiveList(to: string, bodyText: string, buttonLabel: string, sections: WhatsAppCloudListSection[]): Promise<WhatsAppCloudApiResponse>;
100
+ /**
101
+ * Send a pre-approved message template (Phase 3) — the ONLY way to open a
102
+ * conversation outside WhatsApp's 24h customer-service window, which is why
103
+ * the caller must be gated on `whatsapp_cloud.templatesEnabled` (see
104
+ * mcp/tools/whatsapp-cloud/module.ts; this method itself does not gate).
105
+ *
106
+ * `components` is Meta's variable-substitution array. It is passed through
107
+ * VERBATIM and deliberately NOT validated or typed beyond `unknown[]`: its
108
+ * required shape depends on the individual template's definition in Meta's
109
+ * Business Manager, which this gateway has no visibility into.
110
+ */
111
+ sendTemplate(to: string, templateName: string, languageCode: string, components?: unknown[]): Promise<WhatsAppCloudApiResponse>;
112
+ /**
113
+ * Mark an inbound message as read (the blue double-tick on the sender's
114
+ * side). Best-effort: logged, never thrown — a read receipt is UX polish,
115
+ * not correctness-critical, exactly like Slack's ack reaction.
116
+ */
117
+ markAsRead(messageId: string): Promise<void>;
118
+ /**
119
+ * Ack-reaction (ported from `slack-client.ts`'s addReaction): added to the
120
+ * inbound message on receipt, cleared once the agent's reply lands. An
121
+ * EMPTY `emoji` is how the Cloud API removes an existing reaction — see
122
+ * removeReaction below. Best-effort, same as markAsRead.
123
+ */
124
+ sendReaction(to: string, messageId: string, emoji?: string): Promise<void>;
125
+ /** Clear a reaction — the same endpoint with an empty-string emoji. */
126
+ removeReaction(to: string, messageId: string): Promise<void>;
127
+ /**
128
+ * Upload a local file to the Cloud API's media store, returning its
129
+ * `media_id` — a prerequisite step before `sendImage`/`sendDocument`
130
+ * (the Cloud API sends media by id, not by raw bytes or URL, for
131
+ * gateway-originated files).
132
+ */
133
+ uploadMedia(filePath: string, mimeType: string): Promise<{
134
+ mediaId: string;
135
+ } | {
136
+ error: string;
137
+ }>;
138
+ /** Send an already-uploaded image by media id, with an optional caption. */
139
+ sendImage(to: string, mediaId: string, caption?: string): Promise<WhatsAppCloudApiResponse>;
140
+ /** Send an already-uploaded document by media id, with a filename and optional caption. */
141
+ sendDocument(to: string, mediaId: string, filename: string, caption?: string): Promise<WhatsAppCloudApiResponse>;
142
+ }
143
+ //# sourceMappingURL=whatsapp-cloud-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"whatsapp-cloud-client.d.ts","sourceRoot":"","sources":["../../src/api/whatsapp-cloud-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,298 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.WhatsAppCloudClient = exports.WHATSAPP_ACK_EMOJI = exports.WHATSAPP_CLOUD_MAX_BUTTONS = exports.WHATSAPP_CLOUD_MAX_TEXT_CHARS = void 0;
37
+ /**
38
+ * Thin WhatsApp Business Cloud API (Meta Graph API) wrapper — the outbound
39
+ * half of the WhatsApp Cloud channel.
40
+ *
41
+ * Modeled on `slack-client.ts`'s shape (Bearer auth, thin fetch wrappers, a
42
+ * `{accessToken, phoneNumberId, logDir, apiBase?}` constructor) but the
43
+ * request body is plain JSON, not form-urlencoded — Slack's form-encoding
44
+ * was a Slack-specific finding (see slack-client.ts's `call()` doc comment),
45
+ * not a universal REST-API rule. The Graph API accepts and expects JSON.
46
+ *
47
+ * Phase 2 of the WhatsApp feature-parity plan added the ack/read-receipt half
48
+ * that v1 deliberately skipped (`markAsRead`, `sendReaction`,
49
+ * `removeReaction`) — ported from `slack-client.ts`'s addReaction/
50
+ * removeReaction shape, same best-effort posture (log, never throw).
51
+ *
52
+ * Phase 3 adds the two Cloud-ONLY message kinds that have no Baileys analogue
53
+ * at all (`sendInteractiveButtons`/`sendInteractiveList`) and the 24h-window
54
+ * escape hatch (`sendTemplate`). These are net-new here — no other channel in
55
+ * this gateway sends interactive blocks, so there was no in-repo pattern to
56
+ * port; the payload shapes come straight from Meta's Cloud API reference.
57
+ */
58
+ const fs = __importStar(require("fs"));
59
+ const logger_1 = require("../logger");
60
+ const text_chunk_1 = require("../shared/text-chunk");
61
+ const whatsapp_ack_1 = require("../shared/whatsapp-ack");
62
+ Object.defineProperty(exports, "WHATSAPP_ACK_EMOJI", { enumerable: true, get: function () { return whatsapp_ack_1.WHATSAPP_ACK_EMOJI; } });
63
+ const WHATSAPP_CLOUD_API_BASE = 'https://graph.facebook.com/v20.0';
64
+ /**
65
+ * Cloud API hard limit for a text message body. Longer replies are split
66
+ * across several messages rather than being rejected by Meta (error 131009)
67
+ * or silently truncated.
68
+ */
69
+ exports.WHATSAPP_CLOUD_MAX_TEXT_CHARS = 4096;
70
+ /**
71
+ * Meta's hard cap on reply buttons in an `interactive: {type: 'button'}`
72
+ * message. Not a soft/UX limit — the Graph API rejects a 4th button outright,
73
+ * so `sendInteractiveButtons` refuses locally with a readable message rather
74
+ * than letting the agent see an opaque Meta error code.
75
+ */
76
+ exports.WHATSAPP_CLOUD_MAX_BUTTONS = 3;
77
+ class WhatsAppCloudClient {
78
+ constructor(opts) {
79
+ this.accessToken = opts.accessToken;
80
+ this.phoneNumberId = opts.phoneNumberId;
81
+ this.apiBase = opts.apiBase ?? WHATSAPP_CLOUD_API_BASE;
82
+ this.logger = (0, logger_1.createLogger)('whatsapp-cloud-client', opts.logDir);
83
+ }
84
+ async call(method, path, body) {
85
+ const res = await fetch(`${this.apiBase}${path}`, {
86
+ method,
87
+ headers: {
88
+ Authorization: `Bearer ${this.accessToken}`,
89
+ ...(body ? { 'Content-Type': 'application/json' } : {}),
90
+ },
91
+ ...(body ? { body: JSON.stringify(body) } : {}),
92
+ });
93
+ const json = (await res.json());
94
+ if (json.error) {
95
+ this.logger.warn(`WhatsApp Cloud API ${method} ${path} failed`, { error: json.error });
96
+ }
97
+ return json;
98
+ }
99
+ /**
100
+ * Verify the token + phone number id — used both as the connect flow's
101
+ * "Save"-time check (router.ts) and reused here for any other caller that
102
+ * wants a live credential check without duplicating the request shape.
103
+ */
104
+ async verifyCredentials() {
105
+ const json = await this.call('GET', `/${this.phoneNumberId}`);
106
+ if (json.error) {
107
+ return { ok: false, error: json.error.message ?? 'unknown error' };
108
+ }
109
+ return { ok: true };
110
+ }
111
+ /**
112
+ * Send a plain text message, splitting anything over the Cloud API's
113
+ * 4096-char body limit into several messages (one Graph API call per chunk,
114
+ * sent in order).
115
+ *
116
+ * `quotedMessageId` makes the FIRST chunk a quote-reply to that inbound
117
+ * message (Meta's outbound `context: {message_id}`); follow-on chunks are
118
+ * plain so the conversation doesn't show the same quote block repeated.
119
+ *
120
+ * Returns the first errored response if any chunk failed (so existing
121
+ * `if (sent.error)` callers still see the failure), otherwise the last
122
+ * successful one.
123
+ */
124
+ async sendText(to, body, quotedMessageId) {
125
+ const chunks = (0, text_chunk_1.chunkText)(body, exports.WHATSAPP_CLOUD_MAX_TEXT_CHARS);
126
+ // chunkText returns [] for an empty body — keep the historical single-call
127
+ // behaviour so a caller that deliberately sends "" still hits the API.
128
+ const parts = chunks.length > 0 ? chunks : [body];
129
+ let last = {};
130
+ for (let i = 0; i < parts.length; i++) {
131
+ last = await this.call('POST', `/${this.phoneNumberId}/messages`, {
132
+ messaging_product: 'whatsapp',
133
+ to,
134
+ type: 'text',
135
+ text: { body: parts[i] },
136
+ ...(i === 0 && quotedMessageId ? { context: { message_id: quotedMessageId } } : {}),
137
+ });
138
+ if (last.error)
139
+ return last;
140
+ }
141
+ return last;
142
+ }
143
+ /**
144
+ * Send up to 3 tappable reply buttons under a body of text (Phase 3).
145
+ *
146
+ * A tap comes back through the webhook as `type: 'interactive'` with a
147
+ * `button_reply` — the inbound normalizer turns that into plain text
148
+ * (the button's title), so the agent reads a tap exactly as if the user had
149
+ * typed the label. See normalizeWhatsAppCloudMessage's interactive branch.
150
+ *
151
+ * THROWS (rather than returning an `{error}` response) when more than
152
+ * WHATSAPP_CLOUD_MAX_BUTTONS are passed or the list is empty: both are
153
+ * caller bugs that Meta would reject anyway, and a thrown, readable message
154
+ * reaches the agent through the MCP tool's catch as actionable text.
155
+ */
156
+ async sendInteractiveButtons(to, bodyText, buttons) {
157
+ if (buttons.length === 0) {
158
+ throw new Error('sendInteractiveButtons: at least 1 button is required');
159
+ }
160
+ if (buttons.length > exports.WHATSAPP_CLOUD_MAX_BUTTONS) {
161
+ throw new Error(`sendInteractiveButtons: WhatsApp allows at most ${exports.WHATSAPP_CLOUD_MAX_BUTTONS} buttons, got ${buttons.length}. ` +
162
+ 'Use a list message (sendInteractiveList) for more options.');
163
+ }
164
+ return this.call('POST', `/${this.phoneNumberId}/messages`, {
165
+ messaging_product: 'whatsapp',
166
+ to,
167
+ type: 'interactive',
168
+ interactive: {
169
+ type: 'button',
170
+ body: { text: bodyText },
171
+ action: {
172
+ buttons: buttons.map((b) => ({ type: 'reply', reply: { id: b.id, title: b.title } })),
173
+ },
174
+ },
175
+ });
176
+ }
177
+ /**
178
+ * Send a list message — a single button that opens a picker of grouped rows
179
+ * (Phase 3). The way past the 3-button ceiling above.
180
+ *
181
+ * A pick comes back as `type: 'interactive'` with a `list_reply`, handled by
182
+ * the same normalizer branch as button taps.
183
+ */
184
+ async sendInteractiveList(to, bodyText, buttonLabel, sections) {
185
+ return this.call('POST', `/${this.phoneNumberId}/messages`, {
186
+ messaging_product: 'whatsapp',
187
+ to,
188
+ type: 'interactive',
189
+ interactive: {
190
+ type: 'list',
191
+ body: { text: bodyText },
192
+ action: { button: buttonLabel, sections },
193
+ },
194
+ });
195
+ }
196
+ /**
197
+ * Send a pre-approved message template (Phase 3) — the ONLY way to open a
198
+ * conversation outside WhatsApp's 24h customer-service window, which is why
199
+ * the caller must be gated on `whatsapp_cloud.templatesEnabled` (see
200
+ * mcp/tools/whatsapp-cloud/module.ts; this method itself does not gate).
201
+ *
202
+ * `components` is Meta's variable-substitution array. It is passed through
203
+ * VERBATIM and deliberately NOT validated or typed beyond `unknown[]`: its
204
+ * required shape depends on the individual template's definition in Meta's
205
+ * Business Manager, which this gateway has no visibility into.
206
+ */
207
+ async sendTemplate(to, templateName, languageCode, components) {
208
+ return this.call('POST', `/${this.phoneNumberId}/messages`, {
209
+ messaging_product: 'whatsapp',
210
+ to,
211
+ type: 'template',
212
+ template: {
213
+ name: templateName,
214
+ language: { code: languageCode },
215
+ ...(components ? { components } : {}),
216
+ },
217
+ });
218
+ }
219
+ /**
220
+ * Mark an inbound message as read (the blue double-tick on the sender's
221
+ * side). Best-effort: logged, never thrown — a read receipt is UX polish,
222
+ * not correctness-critical, exactly like Slack's ack reaction.
223
+ */
224
+ async markAsRead(messageId) {
225
+ const json = await this.call('POST', `/${this.phoneNumberId}/messages`, {
226
+ messaging_product: 'whatsapp',
227
+ status: 'read',
228
+ message_id: messageId,
229
+ });
230
+ if (json.error) {
231
+ this.logger.debug('markAsRead failed', { error: json.error });
232
+ }
233
+ }
234
+ /**
235
+ * Ack-reaction (ported from `slack-client.ts`'s addReaction): added to the
236
+ * inbound message on receipt, cleared once the agent's reply lands. An
237
+ * EMPTY `emoji` is how the Cloud API removes an existing reaction — see
238
+ * removeReaction below. Best-effort, same as markAsRead.
239
+ */
240
+ async sendReaction(to, messageId, emoji = whatsapp_ack_1.WHATSAPP_ACK_EMOJI) {
241
+ const json = await this.call('POST', `/${this.phoneNumberId}/messages`, {
242
+ messaging_product: 'whatsapp',
243
+ to,
244
+ type: 'reaction',
245
+ reaction: { message_id: messageId, emoji },
246
+ });
247
+ if (json.error) {
248
+ this.logger.debug('sendReaction failed', { error: json.error });
249
+ }
250
+ }
251
+ /** Clear a reaction — the same endpoint with an empty-string emoji. */
252
+ async removeReaction(to, messageId) {
253
+ return this.sendReaction(to, messageId, '');
254
+ }
255
+ /**
256
+ * Upload a local file to the Cloud API's media store, returning its
257
+ * `media_id` — a prerequisite step before `sendImage`/`sendDocument`
258
+ * (the Cloud API sends media by id, not by raw bytes or URL, for
259
+ * gateway-originated files).
260
+ */
261
+ async uploadMedia(filePath, mimeType) {
262
+ const form = new FormData();
263
+ form.append('messaging_product', 'whatsapp');
264
+ form.append('file', new Blob([new Uint8Array(fs.readFileSync(filePath))], { type: mimeType }), filePath.split('/').pop());
265
+ const res = await fetch(`${this.apiBase}/${this.phoneNumberId}/media`, {
266
+ method: 'POST',
267
+ headers: { Authorization: `Bearer ${this.accessToken}` },
268
+ body: form,
269
+ });
270
+ const json = (await res.json());
271
+ if (json.error || typeof json.id !== 'string') {
272
+ const error = json.error?.message ?? 'upload failed: no media id returned';
273
+ this.logger.warn('WhatsApp Cloud media upload failed', { error });
274
+ return { error };
275
+ }
276
+ return { mediaId: json.id };
277
+ }
278
+ /** Send an already-uploaded image by media id, with an optional caption. */
279
+ async sendImage(to, mediaId, caption) {
280
+ return this.call('POST', `/${this.phoneNumberId}/messages`, {
281
+ messaging_product: 'whatsapp',
282
+ to,
283
+ type: 'image',
284
+ image: { id: mediaId, ...(caption ? { caption } : {}) },
285
+ });
286
+ }
287
+ /** Send an already-uploaded document by media id, with a filename and optional caption. */
288
+ async sendDocument(to, mediaId, filename, caption) {
289
+ return this.call('POST', `/${this.phoneNumberId}/messages`, {
290
+ messaging_product: 'whatsapp',
291
+ to,
292
+ type: 'document',
293
+ document: { id: mediaId, filename, ...(caption ? { caption } : {}) },
294
+ });
295
+ }
296
+ }
297
+ exports.WhatsAppCloudClient = WhatsAppCloudClient;
298
+ //# sourceMappingURL=whatsapp-cloud-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"whatsapp-cloud-client.js","sourceRoot":"","sources":["../../src/api/whatsapp-cloud-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,132 @@
1
+ import type { AgentRunner } from '../agent/runner';
2
+ import { type ResolvedWhatsAppCloudSource } from './whatsapp-cloud-access';
3
+ import type { WebhookAppHandler } from './webhooks-router';
4
+ export type NormalizedWhatsAppCloudMessage = {
5
+ content: string;
6
+ meta: Record<string, string>;
7
+ };
8
+ interface WhatsAppCloudContact {
9
+ profile?: {
10
+ name?: string;
11
+ };
12
+ wa_id?: string;
13
+ }
14
+ interface WhatsAppCloudMediaObject {
15
+ id?: string;
16
+ mime_type?: string;
17
+ caption?: string;
18
+ filename?: string;
19
+ }
20
+ /**
21
+ * Meta's structured contact payload. NOT a vCard — unlike Baileys (which
22
+ * hands over the raw vCard string the sender's phone produced), the Cloud API
23
+ * decomposes it into JSON, so a vCard has to be synthesized from these fields
24
+ * if the agent is to see one (see `synthesizeVCard`).
25
+ */
26
+ interface WhatsAppCloudContactCard {
27
+ name?: {
28
+ formatted_name?: string;
29
+ };
30
+ phones?: Array<{
31
+ phone?: string;
32
+ }>;
33
+ }
34
+ export interface WhatsAppCloudMessage {
35
+ from?: string;
36
+ id?: string;
37
+ timestamp?: string;
38
+ type?: string;
39
+ text?: {
40
+ body?: string;
41
+ };
42
+ image?: WhatsAppCloudMediaObject;
43
+ document?: WhatsAppCloudMediaObject;
44
+ /**
45
+ * Present when the user replied to a specific earlier message; `id` is that
46
+ * message's id. Meta does NOT inline the quoted message's text or sender
47
+ * here — see normalizeWhatsAppCloudMessage's reply-context branch.
48
+ */
49
+ context?: {
50
+ id?: string;
51
+ };
52
+ location?: {
53
+ latitude?: number;
54
+ longitude?: number;
55
+ name?: string;
56
+ address?: string;
57
+ };
58
+ contacts?: WhatsAppCloudContactCard[];
59
+ /** Stickers are `image/webp` media, downloaded through the same path as images. */
60
+ sticker?: WhatsAppCloudMediaObject;
61
+ /**
62
+ * A tap on a reply button / a pick from a list message that WE sent (Phase 3
63
+ * — see WhatsAppCloudClient.sendInteractiveButtons/sendInteractiveList).
64
+ * Exactly one of `button_reply`/`list_reply` is present, matching the
65
+ * interactive type that was sent.
66
+ */
67
+ interactive?: {
68
+ type?: string;
69
+ button_reply?: {
70
+ id: string;
71
+ title: string;
72
+ };
73
+ list_reply?: {
74
+ id: string;
75
+ title: string;
76
+ description?: string;
77
+ };
78
+ };
79
+ [key: string]: unknown;
80
+ }
81
+ export interface WhatsAppCloudValue {
82
+ messaging_product?: string;
83
+ metadata?: {
84
+ phone_number_id?: string;
85
+ };
86
+ contacts?: WhatsAppCloudContact[];
87
+ messages?: WhatsAppCloudMessage[];
88
+ statuses?: unknown[];
89
+ }
90
+ /**
91
+ * Extract the inbound message list from a webhook `value` object — returns
92
+ * [] for a status-only batch (our own outbound delivery receipts) or any
93
+ * payload shape without a `messages` array at all, so callers never try to
94
+ * normalize a status update as an inbound message.
95
+ */
96
+ export declare function extractInboundMessages(value: WhatsAppCloudValue | undefined): WhatsAppCloudMessage[];
97
+ /**
98
+ * Build a minimal vCard from Meta's structured contact payload.
99
+ *
100
+ * The Cloud API does NOT hand over a vCard string (Baileys does — see
101
+ * manager.ts's contactMessage branch, which uses `contactMessage.vcard`
102
+ * verbatim), it hands over decomposed JSON. Synthesizing a 3.0 vCard here
103
+ * means BOTH WhatsApp channels put the same kind of value in the same
104
+ * `meta.vcard` key, so the agent never needs channel-specific handling.
105
+ * Returns undefined when there is neither a name nor a phone to put in it.
106
+ */
107
+ export declare function synthesizeVCard(contact: WhatsAppCloudContactCard | undefined): string | undefined;
108
+ /**
109
+ * Normalize an inbound Cloud API message into the gateway's {content, meta}
110
+ * intake shape. Returns null when `from` is missing/empty (no reply target).
111
+ * `content` is the text body for `type: 'text'`, the caption (possibly
112
+ * empty) for `type: 'image'`/`'document'`, or a short human summary for
113
+ * `type: 'location'`, or the selected option's title for `type: 'interactive'`
114
+ * (a button tap / list pick) — remaining types (audio, video, ...) are still
115
+ * out of scope and forward with empty content rather than being dropped, same
116
+ * posture as Slack's "no text → empty content, not null".
117
+ *
118
+ * Stays synchronous and pure: an attached image/document/sticker's bytes are
119
+ * fetched by the async handler AFTER this returns, which sets
120
+ * `meta.image_path`/`meta.document_path`/`meta.sticker_path` on the object
121
+ * built here.
122
+ */
123
+ export declare function normalizeWhatsAppCloudMessage(msg: WhatsAppCloudMessage, resolved?: ResolvedWhatsAppCloudSource): NormalizedWhatsAppCloudMessage | null;
124
+ /** Verify `X-Hub-Signature-256` (HMAC-SHA256 of the raw body — no timestamp component). */
125
+ export declare function verifyMetaSignature(rawBody: Buffer, appSecret: string, sigHeader: string | undefined): boolean;
126
+ export interface WhatsAppCloudWebhookOptions {
127
+ /** Test-only Graph API base override (see WhatsAppCloudClient / media metadata fetch). */
128
+ apiBase?: string;
129
+ }
130
+ export declare function createWhatsAppCloudWebhookHandler(agents: Map<string, AgentRunner>, logDir: string, opts?: WhatsAppCloudWebhookOptions): WebhookAppHandler;
131
+ export {};
132
+ //# sourceMappingURL=whatsapp-cloud-webhook-router.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"whatsapp-cloud-webhook-router.d.ts","sourceRoot":"","sources":["../../src/api/whatsapp-cloud-webhook-router.ts"],"names":[],"mappings":"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"}