@diegoaltoworks/chatter 0.33.0 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Optional image requests in the WhatsApp channel: detect a drawing/picture
3
+ * request (with or without an attached photo), route it through `../../images`,
4
+ * and reply with the generated picture. Inert unless `./images` is
5
+ * configured — `createWhatsAppImageHandler` returns `false` for every
6
+ * message when it isn't, so callers fall through to normal chat.
7
+ *
8
+ * This module ships no bot personality: the detector and subject-cleaning
9
+ * patterns are generic drawing verbs (no hardcoded bot names), and every
10
+ * reply string (ack/limit/moderation/error) is caller-configured with no
11
+ * default text.
12
+ *
13
+ * Downloading the attached photo needs Baileys' `downloadMediaMessage`,
14
+ * which — per this subpath's rule that only `./baileys` touches the
15
+ * optional peer dependency at runtime — is injected as `downloadPhoto`
16
+ * rather than imported here. The host wires it via `./whatsapp`'s
17
+ * `loadBaileys` (see docs/channels.md), not this module.
18
+ *
19
+ * Everything pulled in from `../../images` is type-only: each subpath is
20
+ * built as its own bundle, so a value import here (even just for an
21
+ * `instanceof` check) would inline the whole `./images` module — OpenAI SDK
22
+ * included — into `./whatsapp`, and importing from the `../../images`
23
+ * barrel rather than its leaf files pulls in every sibling module's value
24
+ * imports too. Error mapping below checks `code`/`name` structurally
25
+ * instead, which also means it works when the error crossed a bundle
26
+ * boundary (the two subpaths' `ImageGenerationError` classes are not
27
+ * `instanceof`-compatible with each other).
28
+ */
29
+ import type { WAMessage, WASocket } from "@whiskeysockets/baileys";
30
+ import type { CaptionComposer } from "../../images/captionComposer";
31
+ import type { ImageUploader } from "../../images/uploader";
32
+ export interface DrawRequestDetectorConfig {
33
+ /** Tested against the raw message text to decide whether it's an image request. Defaults to a generic set of drawing verbs — no bot-specific names. */
34
+ detectPattern?: RegExp;
35
+ /** Applied in order to derive the subject from the raw text (mention tokens, greetings/politeness, the draw verb itself). */
36
+ stripPatterns?: readonly RegExp[];
37
+ }
38
+ export declare function isDrawRequest(text: string, config?: DrawRequestDetectorConfig): boolean;
39
+ /** Cleans a draw request into a scene subject: "@1234 please draw me a cat" -> "cat". Falls back to the trimmed original when the patterns strip everything (or nothing). */
40
+ export declare function drawSubject(text: string, config?: DrawRequestDetectorConfig): string;
41
+ /** Neutral, overridable reply strings — this module ships no bot personality. Unset = silent for that outcome. */
42
+ export interface WhatsAppImageStrings {
43
+ /** Sent immediately on accepting the request, before generation starts. */
44
+ ack?: string;
45
+ /** Sent when the per-sender/day cap (`checkAndReserve`) blocks the request. */
46
+ limitReached?: string;
47
+ /** Sent when the image API blocks the request on moderation grounds. */
48
+ moderationBlocked?: string;
49
+ /** Sent for any other generation/upload/download failure. */
50
+ error?: string;
51
+ }
52
+ export interface WhatsAppImageHandlerDeps {
53
+ /** The slice of `./images`' `ImageUploader` this module needs. */
54
+ images: Pick<ImageUploader, "isConfigured" | "peekCached" | "getOrCreateImage">;
55
+ /**
56
+ * Per-sender/day reservation (e.g. `./usage`'s `DailyLimiter.checkAndReserve`).
57
+ * Omit to skip the cap entirely. Checked AFTER the cache, so a cache hit
58
+ * never burns a unit.
59
+ */
60
+ checkAndReserve?: (senderId: string) => Promise<{
61
+ allowed: boolean;
62
+ }>;
63
+ /**
64
+ * Downloads the attached photo as bytes. Omit to ignore attached photos —
65
+ * text-only requests still work. See the module docstring for why this is
66
+ * injected rather than imported.
67
+ */
68
+ downloadPhoto?: (message: WAMessage) => Promise<Uint8Array>;
69
+ /** Best-effort tailored caption for the reply. Omit to send the image with no caption. */
70
+ captionComposer?: Pick<CaptionComposer, "composeCaption">;
71
+ detector?: DrawRequestDetectorConfig;
72
+ strings?: WhatsAppImageStrings;
73
+ }
74
+ export interface WhatsAppImageRequestContext {
75
+ sock: WASocket;
76
+ message: WAMessage;
77
+ chatId: string;
78
+ senderId: string;
79
+ text: string;
80
+ hasPhoto: boolean;
81
+ }
82
+ /** `true` = the message was an image request and this module replied to it (successfully or with a mapped error) — the caller must not also route it through normal chat. `false` = not an image request, or `./images` isn't configured. */
83
+ export type WhatsAppImageHandler = (ctx: WhatsAppImageRequestContext) => Promise<boolean>;
84
+ export declare function createWhatsAppImageHandler(deps: WhatsAppImageHandlerDeps): WhatsAppImageHandler;
85
+ //# sourceMappingURL=images.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"images.d.ts","sourceRoot":"","sources":["../../../src/channels/whatsapp/images.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AACnE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAEpE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAkB3D,MAAM,WAAW,yBAAyB;IACxC,uJAAuJ;IACvJ,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,6HAA6H;IAC7H,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACnC;AAOD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,GAAE,yBAA8B,GAAG,OAAO,CAE3F;AAED,6KAA6K;AAC7K,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,GAAE,yBAA8B,GAAG,MAAM,CAUxF;AAED,kHAAkH;AAClH,MAAM,WAAW,oBAAoB;IACnC,2EAA2E;IAC3E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,+EAA+E;IAC/E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,wEAAwE;IACxE,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,wBAAwB;IACvC,kEAAkE;IAClE,MAAM,EAAE,IAAI,CAAC,aAAa,EAAE,cAAc,GAAG,YAAY,GAAG,kBAAkB,CAAC,CAAC;IAChF;;;;OAIG;IACH,eAAe,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IACtE;;;;OAIG;IACH,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,SAAS,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;IAC5D,0FAA0F;IAC1F,eAAe,CAAC,EAAE,IAAI,CAAC,eAAe,EAAE,gBAAgB,CAAC,CAAC;IAC1D,QAAQ,CAAC,EAAE,yBAAyB,CAAC;IACrC,OAAO,CAAC,EAAE,oBAAoB,CAAC;CAChC;AAED,MAAM,WAAW,2BAA2B;IAC1C,IAAI,EAAE,QAAQ,CAAC;IACf,OAAO,EAAE,SAAS,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,6OAA6O;AAC7O,MAAM,MAAM,oBAAoB,GAAG,CAAC,GAAG,EAAE,2BAA2B,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;AAsB1F,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,wBAAwB,GAAG,oBAAoB,CA4C/F"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=images.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"images.test.d.ts","sourceRoot":"","sources":["../../../src/channels/whatsapp/images.test.ts"],"names":[],"mappings":""}
@@ -50,6 +50,7 @@ import type { PromptLoader } from "../../core/prompts";
50
50
  import type { VectorStore } from "../../core/retrieval";
51
51
  import { type SessionIdentityRegistry } from "../gates";
52
52
  import type { WhatsAppMessageEvent } from "./channel";
53
+ import type { WhatsAppImageHandler } from "./images";
53
54
  export declare function isGroupJid(jid: string): boolean;
54
55
  /** "447700900123:17@s.whatsapp.net" (device-suffixed) and "447700900123@s.whatsapp.net" match. */
55
56
  export declare function jidsMatch(a: string | undefined, b: string | undefined): boolean;
@@ -124,6 +125,14 @@ export interface WhatsAppInboundConfig {
124
125
  senderPhone: string;
125
126
  text: string;
126
127
  }) => string | undefined | Promise<string | undefined>;
128
+ /**
129
+ * Optional image-request routing (see `./images`). Consulted right before
130
+ * the normal chat pipeline; a `true` result means the message was handled
131
+ * as an image request (successfully or with a mapped error string) and
132
+ * this handler returns without answering through chat. Inert unless
133
+ * `./images` is configured.
134
+ */
135
+ images?: WhatsAppImageHandler;
127
136
  now?: () => number;
128
137
  }
129
138
  /** Builds the `onMessage` handler for `createWhatsAppChannel` (see `./channel`) — see the module docstring for how to wire `ServerDependencies` into it. */
@@ -1 +1 @@
1
- {"version":3,"file":"inbound.d.ts","sourceRoot":"","sources":["../../../src/channels/whatsapp/inbound.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AACnE,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AACjC,OAAO,EAAE,KAAK,QAAQ,EAAc,MAAM,mBAAmB,CAAC;AAC9D,OAAO,EAAE,KAAK,UAAU,EAAkB,MAAM,oBAAoB,CAAC;AAErE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAKL,KAAK,uBAAuB,EAE7B,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAItD,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAE/C;AAED,kGAAkG;AAClG,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,GAAG,SAAS,EAAE,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAI/E;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAGpD;AAeD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,QAAQ,EACd,OAAO,EAAE,SAAS,EAClB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,CAAC,CA+BjB;AAID,MAAM,WAAW,gBAAgB;IAC/B,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,SAAS,GAAG,gBAAgB,CAWnE;AAED,mHAAmH;AACnH,wBAAgB,WAAW,CAAC,OAAO,EAAE,SAAS,GAAG,MAAM,CAWtD;AAID,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,WAAW,CAAC;IACnB,OAAO,EAAE,YAAY,CAAC;IACtB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;;;OAOG;IACH,QAAQ,EAAE,uBAAuB,CAAC;IAClC,4HAA4H;IAC5H,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gHAAgH;IAChH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAChD,cAAc,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IACnD,oGAAoG;IACpG,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE;QACtB,WAAW,EAAE,MAAM,CAAC;QACpB,IAAI,EAAE,MAAM,CAAC;KACd,KAAK,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACvD,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAqBD,4JAA4J;AAC5J,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,qBAAqB,GAC5B,CAAC,KAAK,EAAE,oBAAoB,KAAK,OAAO,CAAC,IAAI,CAAC,CA0GhD"}
1
+ {"version":3,"file":"inbound.d.ts","sourceRoot":"","sources":["../../../src/channels/whatsapp/inbound.ts"],"names":[],"mappings":"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"}
@@ -29,6 +29,7 @@ export type { Baileys } from "./baileys";
29
29
  export { loadBaileys } from "./baileys";
30
30
  export { acquireSessionLease, createWhatsAppChannel, type LeaseGatedConnectDeps, reconnectDelayMs, senderNameFor, shutdownWaSessions, type WaSessionHandle, type WhatsAppChannelConfig, type WhatsAppMessageEvent, } from "./channel";
31
31
  export { decrypt, encrypt } from "./crypto";
32
+ export { createWhatsAppImageHandler, type DrawRequestDetectorConfig, drawSubject, isDrawRequest, type WhatsAppImageHandler, type WhatsAppImageHandlerDeps, type WhatsAppImageRequestContext, type WhatsAppImageStrings, } from "./images";
32
33
  export { createWhatsAppInboundHandler, extractText, isGroupJid, jidsMatch, jidToPhoneNumber, messageContext, senderPhoneFor, type WaMessageContext, type WhatsAppInboundConfig, } from "./inbound";
33
34
  export { canAcquireLease, createTursoWaLeaseStore, LEASE_HEARTBEAT_MS, LEASE_STALE_MS, LEASE_WAIT_MS, type WaLeaseRow, type WaLeaseStore, } from "./lease";
34
35
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/channels/whatsapp/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,YAAY,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAC1E,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,YAAY,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EACL,mBAAmB,EACnB,qBAAqB,EACrB,KAAK,qBAAqB,EAC1B,gBAAgB,EAChB,aAAa,EACb,kBAAkB,EAClB,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,GAC1B,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,EACL,4BAA4B,EAC5B,WAAW,EACX,UAAU,EACV,SAAS,EACT,gBAAgB,EAChB,cAAc,EACd,cAAc,EACd,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,GAC3B,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,eAAe,EACf,uBAAuB,EACvB,kBAAkB,EAClB,cAAc,EACd,aAAa,EACb,KAAK,UAAU,EACf,KAAK,YAAY,GAClB,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/channels/whatsapp/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,YAAY,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAC1E,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,YAAY,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EACL,mBAAmB,EACnB,qBAAqB,EACrB,KAAK,qBAAqB,EAC1B,gBAAgB,EAChB,aAAa,EACb,kBAAkB,EAClB,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,GAC1B,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,EACL,0BAA0B,EAC1B,KAAK,yBAAyB,EAC9B,WAAW,EACX,aAAa,EACb,KAAK,oBAAoB,EACzB,KAAK,wBAAwB,EAC7B,KAAK,2BAA2B,EAChC,KAAK,oBAAoB,GAC1B,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,4BAA4B,EAC5B,WAAW,EACX,UAAU,EACV,SAAS,EACT,gBAAgB,EAChB,cAAc,EACd,cAAc,EACd,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,GAC3B,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,eAAe,EACf,uBAAuB,EACvB,kBAAkB,EAClB,cAAc,EACd,aAAa,EACb,KAAK,UAAU,EACf,KAAK,YAAY,GAClB,MAAM,SAAS,CAAC"}
@@ -37,10 +37,13 @@ __export(index_exports, {
37
37
  canAcquireLease: () => canAcquireLease,
38
38
  createTursoWaLeaseStore: () => createTursoWaLeaseStore,
39
39
  createWhatsAppChannel: () => createWhatsAppChannel,
40
+ createWhatsAppImageHandler: () => createWhatsAppImageHandler,
40
41
  createWhatsAppInboundHandler: () => createWhatsAppInboundHandler,
41
42
  decrypt: () => decrypt,
43
+ drawSubject: () => drawSubject,
42
44
  encrypt: () => encrypt,
43
45
  extractText: () => extractText,
46
+ isDrawRequest: () => isDrawRequest,
44
47
  isGroupJid: () => isGroupJid,
45
48
  jidToPhoneNumber: () => jidToPhoneNumber,
46
49
  jidsMatch: () => jidsMatch,
@@ -467,6 +470,71 @@ function createWhatsAppChannel(config) {
467
470
  };
468
471
  }
469
472
 
473
+ // src/channels/whatsapp/images.ts
474
+ var DEFAULT_DETECT_PATTERN = /\b(draw|paint|sketch|illustrate)\b|\b(make|generate|create)\b[^.!?]{0,20}\b(picture|image|drawing|photo|cartoon)\b/i;
475
+ var DEFAULT_STRIP_PATTERNS = [
476
+ /@\d+/g,
477
+ /^(hey|hi|please|ok)[,!\s]+/i,
478
+ /^(can|could|will)\s+you\s+/i,
479
+ // Longest-alternative-first ((an|a), not (a|an)): a first-match-wins
480
+ // alternation would otherwise take "a" out of "an" and leave a stray "n".
481
+ /^(please\s+)?(draw|paint|sketch|illustrate|make|generate|create)\s+(me|us)?\s*(an|a)?\s*(picture|image|drawing|sketch|photo|cartoon)?\s*(of\s+)?/i
482
+ ];
483
+ function testPattern(pattern, text) {
484
+ return new RegExp(pattern.source, pattern.flags.replace(/[gy]/g, "")).test(text);
485
+ }
486
+ function isDrawRequest(text, config = {}) {
487
+ return testPattern(config.detectPattern ?? DEFAULT_DETECT_PATTERN, text);
488
+ }
489
+ function drawSubject(text, config = {}) {
490
+ let subject = text;
491
+ for (const pattern of config.stripPatterns ?? DEFAULT_STRIP_PATTERNS) {
492
+ subject = subject.replace(pattern, " ").replace(/\s+/g, " ").trim();
493
+ }
494
+ return subject.length > 0 ? subject : text.trim();
495
+ }
496
+ function isModerationError(error) {
497
+ return typeof error === "object" && error !== null && error.name === "ImageGenerationError" && error.code === "moderation";
498
+ }
499
+ async function replyText(sock, chatId, message, text) {
500
+ if (!text) return;
501
+ await sock.sendMessage(chatId, { text }, { quoted: message }).catch(() => void 0);
502
+ }
503
+ function createWhatsAppImageHandler(deps) {
504
+ return async function handleWhatsAppImageRequest(ctx) {
505
+ if (!deps.images.isConfigured()) return false;
506
+ if (!isDrawRequest(ctx.text, deps.detector)) return false;
507
+ const subject = drawSubject(ctx.text, deps.detector);
508
+ try {
509
+ const photoBytes = ctx.hasPhoto ? await deps.downloadPhoto?.(ctx.message) : void 0;
510
+ const inputImages = photoBytes ? [{ bytes: photoBytes }] : void 0;
511
+ const request = { prompt: subject, inputImages };
512
+ if (deps.checkAndReserve) {
513
+ const cached2 = await deps.images.peekCached(request);
514
+ if (!cached2) {
515
+ const reserved = await deps.checkAndReserve(ctx.senderId);
516
+ if (!reserved.allowed) {
517
+ await replyText(ctx.sock, ctx.chatId, ctx.message, deps.strings?.limitReached);
518
+ return true;
519
+ }
520
+ }
521
+ }
522
+ await replyText(ctx.sock, ctx.chatId, ctx.message, deps.strings?.ack);
523
+ const image = await deps.images.getOrCreateImage(request);
524
+ const caption = deps.captionComposer ? await deps.captionComposer.composeCaption({ subject }) : void 0;
525
+ await ctx.sock.sendMessage(
526
+ ctx.chatId,
527
+ caption ? { image: { url: image.url }, caption } : { image: { url: image.url } },
528
+ { quoted: ctx.message }
529
+ );
530
+ } catch (error) {
531
+ const text = isModerationError(error) ? deps.strings?.moderationBlocked : deps.strings?.error;
532
+ await replyText(ctx.sock, ctx.chatId, ctx.message, text);
533
+ }
534
+ return true;
535
+ };
536
+ }
537
+
470
538
  // src/core/guardrails.ts
471
539
  function detectLeakage(text) {
472
540
  const flags = [
@@ -751,6 +819,17 @@ function createWhatsAppInboundHandler(config) {
751
819
  return;
752
820
  }
753
821
  const senderPhone = await senderPhoneFor(sock, message, chatId);
822
+ if (config.images) {
823
+ const handled = await config.images({
824
+ sock,
825
+ message,
826
+ chatId,
827
+ senderId: senderPhone,
828
+ text,
829
+ hasPhoto: Boolean(message.message?.imageMessage)
830
+ });
831
+ if (handled) return;
832
+ }
754
833
  const personaLayer = await resolvePersonaLayer(config, { senderPhone, text });
755
834
  const buckets = await resolveBuckets({
756
835
  mode: "public",
@@ -793,10 +872,13 @@ function createWhatsAppInboundHandler(config) {
793
872
  canAcquireLease,
794
873
  createTursoWaLeaseStore,
795
874
  createWhatsAppChannel,
875
+ createWhatsAppImageHandler,
796
876
  createWhatsAppInboundHandler,
797
877
  decrypt,
878
+ drawSubject,
798
879
  encrypt,
799
880
  extractText,
881
+ isDrawRequest,
800
882
  isGroupJid,
801
883
  jidToPhoneNumber,
802
884
  jidsMatch,
@@ -411,6 +411,71 @@ function createWhatsAppChannel(config) {
411
411
  };
412
412
  }
413
413
 
414
+ // src/channels/whatsapp/images.ts
415
+ var DEFAULT_DETECT_PATTERN = /\b(draw|paint|sketch|illustrate)\b|\b(make|generate|create)\b[^.!?]{0,20}\b(picture|image|drawing|photo|cartoon)\b/i;
416
+ var DEFAULT_STRIP_PATTERNS = [
417
+ /@\d+/g,
418
+ /^(hey|hi|please|ok)[,!\s]+/i,
419
+ /^(can|could|will)\s+you\s+/i,
420
+ // Longest-alternative-first ((an|a), not (a|an)): a first-match-wins
421
+ // alternation would otherwise take "a" out of "an" and leave a stray "n".
422
+ /^(please\s+)?(draw|paint|sketch|illustrate|make|generate|create)\s+(me|us)?\s*(an|a)?\s*(picture|image|drawing|sketch|photo|cartoon)?\s*(of\s+)?/i
423
+ ];
424
+ function testPattern(pattern, text) {
425
+ return new RegExp(pattern.source, pattern.flags.replace(/[gy]/g, "")).test(text);
426
+ }
427
+ function isDrawRequest(text, config = {}) {
428
+ return testPattern(config.detectPattern ?? DEFAULT_DETECT_PATTERN, text);
429
+ }
430
+ function drawSubject(text, config = {}) {
431
+ let subject = text;
432
+ for (const pattern of config.stripPatterns ?? DEFAULT_STRIP_PATTERNS) {
433
+ subject = subject.replace(pattern, " ").replace(/\s+/g, " ").trim();
434
+ }
435
+ return subject.length > 0 ? subject : text.trim();
436
+ }
437
+ function isModerationError(error) {
438
+ return typeof error === "object" && error !== null && error.name === "ImageGenerationError" && error.code === "moderation";
439
+ }
440
+ async function replyText(sock, chatId, message, text) {
441
+ if (!text) return;
442
+ await sock.sendMessage(chatId, { text }, { quoted: message }).catch(() => void 0);
443
+ }
444
+ function createWhatsAppImageHandler(deps) {
445
+ return async function handleWhatsAppImageRequest(ctx) {
446
+ if (!deps.images.isConfigured()) return false;
447
+ if (!isDrawRequest(ctx.text, deps.detector)) return false;
448
+ const subject = drawSubject(ctx.text, deps.detector);
449
+ try {
450
+ const photoBytes = ctx.hasPhoto ? await deps.downloadPhoto?.(ctx.message) : void 0;
451
+ const inputImages = photoBytes ? [{ bytes: photoBytes }] : void 0;
452
+ const request = { prompt: subject, inputImages };
453
+ if (deps.checkAndReserve) {
454
+ const cached2 = await deps.images.peekCached(request);
455
+ if (!cached2) {
456
+ const reserved = await deps.checkAndReserve(ctx.senderId);
457
+ if (!reserved.allowed) {
458
+ await replyText(ctx.sock, ctx.chatId, ctx.message, deps.strings?.limitReached);
459
+ return true;
460
+ }
461
+ }
462
+ }
463
+ await replyText(ctx.sock, ctx.chatId, ctx.message, deps.strings?.ack);
464
+ const image = await deps.images.getOrCreateImage(request);
465
+ const caption = deps.captionComposer ? await deps.captionComposer.composeCaption({ subject }) : void 0;
466
+ await ctx.sock.sendMessage(
467
+ ctx.chatId,
468
+ caption ? { image: { url: image.url }, caption } : { image: { url: image.url } },
469
+ { quoted: ctx.message }
470
+ );
471
+ } catch (error) {
472
+ const text = isModerationError(error) ? deps.strings?.moderationBlocked : deps.strings?.error;
473
+ await replyText(ctx.sock, ctx.chatId, ctx.message, text);
474
+ }
475
+ return true;
476
+ };
477
+ }
478
+
414
479
  // src/core/guardrails.ts
415
480
  function detectLeakage(text) {
416
481
  const flags = [
@@ -695,6 +760,17 @@ function createWhatsAppInboundHandler(config) {
695
760
  return;
696
761
  }
697
762
  const senderPhone = await senderPhoneFor(sock, message, chatId);
763
+ if (config.images) {
764
+ const handled = await config.images({
765
+ sock,
766
+ message,
767
+ chatId,
768
+ senderId: senderPhone,
769
+ text,
770
+ hasPhoto: Boolean(message.message?.imageMessage)
771
+ });
772
+ if (handled) return;
773
+ }
698
774
  const personaLayer = await resolvePersonaLayer(config, { senderPhone, text });
699
775
  const buckets = await resolveBuckets({
700
776
  mode: "public",
@@ -736,10 +812,13 @@ export {
736
812
  canAcquireLease,
737
813
  createTursoWaLeaseStore,
738
814
  createWhatsAppChannel,
815
+ createWhatsAppImageHandler,
739
816
  createWhatsAppInboundHandler,
740
817
  decrypt,
818
+ drawSubject,
741
819
  encrypt,
742
820
  extractText,
821
+ isDrawRequest,
743
822
  isGroupJid,
744
823
  jidToPhoneNumber,
745
824
  jidsMatch,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegoaltoworks/chatter",
3
- "version": "0.33.0",
3
+ "version": "0.34.0",
4
4
  "description": "Embeddable AI chatbot framework with RAG, authentication, and customizable widgets",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",