@diegoaltoworks/chatter 0.32.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.
- package/dist/channels/whatsapp/images.d.ts +85 -0
- package/dist/channels/whatsapp/images.d.ts.map +1 -0
- package/dist/channels/whatsapp/images.test.d.ts +2 -0
- package/dist/channels/whatsapp/images.test.d.ts.map +1 -0
- package/dist/channels/whatsapp/inbound.d.ts +9 -0
- package/dist/channels/whatsapp/inbound.d.ts.map +1 -1
- package/dist/channels/whatsapp/index.d.ts +1 -0
- package/dist/channels/whatsapp/index.d.ts.map +1 -1
- package/dist/channels/whatsapp/index.js +82 -0
- package/dist/channels/whatsapp/index.mjs +79 -0
- package/dist/images/cache.d.ts +32 -0
- package/dist/images/cache.d.ts.map +1 -0
- package/dist/images/cache.test.d.ts +2 -0
- package/dist/images/cache.test.d.ts.map +1 -0
- package/dist/images/cacheStore.d.ts +20 -0
- package/dist/images/cacheStore.d.ts.map +1 -0
- package/dist/images/cacheStore.test.d.ts +8 -0
- package/dist/images/cacheStore.test.d.ts.map +1 -0
- package/dist/images/captionComposer.d.ts +33 -0
- package/dist/images/captionComposer.d.ts.map +1 -0
- package/dist/images/captionComposer.test.d.ts +2 -0
- package/dist/images/captionComposer.test.d.ts.map +1 -0
- package/dist/images/cloudinaryClient.d.ts +12 -0
- package/dist/images/cloudinaryClient.d.ts.map +1 -0
- package/dist/images/cloudinaryClient.test.d.ts +2 -0
- package/dist/images/cloudinaryClient.test.d.ts.map +1 -0
- package/dist/images/cloudinaryConfig.d.ts +29 -0
- package/dist/images/cloudinaryConfig.d.ts.map +1 -0
- package/dist/images/cloudinaryConfig.test.d.ts +2 -0
- package/dist/images/cloudinaryConfig.test.d.ts.map +1 -0
- package/dist/images/cloudinarySignature.d.ts +9 -0
- package/dist/images/cloudinarySignature.d.ts.map +1 -0
- package/dist/images/cloudinarySignature.test.d.ts +2 -0
- package/dist/images/cloudinarySignature.test.d.ts.map +1 -0
- package/dist/images/config.d.ts +28 -0
- package/dist/images/config.d.ts.map +1 -0
- package/dist/images/config.test.d.ts +2 -0
- package/dist/images/config.test.d.ts.map +1 -0
- package/dist/images/generator.d.ts +19 -0
- package/dist/images/generator.d.ts.map +1 -0
- package/dist/images/generator.test.d.ts +2 -0
- package/dist/images/generator.test.d.ts.map +1 -0
- package/dist/images/index.d.ts +34 -0
- package/dist/images/index.d.ts.map +1 -0
- package/dist/images/index.js +412 -0
- package/dist/images/index.mjs +357 -0
- package/dist/images/openaiClient.d.ts +14 -0
- package/dist/images/openaiClient.d.ts.map +1 -0
- package/dist/images/openaiClient.test.d.ts +2 -0
- package/dist/images/openaiClient.test.d.ts.map +1 -0
- package/dist/images/types.d.ts +96 -0
- package/dist/images/types.d.ts.map +1 -0
- package/dist/images/uploader.d.ts +39 -0
- package/dist/images/uploader.d.ts.map +1 -0
- package/dist/images/uploader.test.d.ts +2 -0
- package/dist/images/uploader.test.d.ts.map +1 -0
- package/package.json +8 -2
|
@@ -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 @@
|
|
|
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;
|
|
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,
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image cache - deterministic keying and freshness, no I/O.
|
|
3
|
+
*
|
|
4
|
+
* The store itself is injected ({@link ImageCacheStore}): a host backs it with
|
|
5
|
+
* whatever it wants (see `./cacheStore` for the shipped Turso binding), so
|
|
6
|
+
* this file stays free of any DB client.
|
|
7
|
+
*/
|
|
8
|
+
import type { ImageInput } from "./types";
|
|
9
|
+
/** A cached upload: where it lives, and when it was created. */
|
|
10
|
+
export interface ImageCacheEntry {
|
|
11
|
+
url: string;
|
|
12
|
+
/** Epoch milliseconds. */
|
|
13
|
+
createdAt: number;
|
|
14
|
+
}
|
|
15
|
+
/** Structural cache dependency - a host implements this against its own storage. */
|
|
16
|
+
export interface ImageCacheStore {
|
|
17
|
+
get(key: string): Promise<ImageCacheEntry | null>;
|
|
18
|
+
set(key: string, entry: ImageCacheEntry): Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
/** Identical requests are served from cache for this long before regenerating. */
|
|
21
|
+
export declare const CACHE_TTL_MS: number;
|
|
22
|
+
/**
|
|
23
|
+
* Deterministic cache key (and Cloudinary `public_id`) for a request: the
|
|
24
|
+
* prompt plus any base/input images. The same prompt against a different
|
|
25
|
+
* photo must be a different picture.
|
|
26
|
+
*/
|
|
27
|
+
export declare function publicIdForRequest(prompt: string, images?: {
|
|
28
|
+
baseImage?: ImageInput;
|
|
29
|
+
inputImages?: ImageInput[];
|
|
30
|
+
}): string;
|
|
31
|
+
export declare function isCacheEntryFresh(entry: ImageCacheEntry, now: number): boolean;
|
|
32
|
+
//# sourceMappingURL=cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/images/cache.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE1C,gEAAgE;AAChE,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,0BAA0B;IAC1B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,oFAAoF;AACpF,MAAM,WAAW,eAAe;IAC9B,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC;IAClD,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACzD;AAED,kFAAkF;AAClF,eAAO,MAAM,YAAY,QAAsB,CAAC;AAEhD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,EACd,MAAM,CAAC,EAAE;IAAE,SAAS,CAAC,EAAE,UAAU,CAAC;IAAC,WAAW,CAAC,EAAE,UAAU,EAAE,CAAA;CAAE,GAC9D,MAAM,CAOR;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,eAAe,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAE9E"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.test.d.ts","sourceRoot":"","sources":["../../src/images/cache.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turso/libsql-backed {@link ImageCacheStore}.
|
|
3
|
+
*
|
|
4
|
+
* Schema and upsert only, mirroring `../usage/tursoStore`: idempotent table
|
|
5
|
+
* creation memoized per (client, table), and a table name restricted to a
|
|
6
|
+
* plain identifier since it is interpolated into SQL.
|
|
7
|
+
*
|
|
8
|
+
* `@libsql/client` is imported for its type alone, so this module adds no
|
|
9
|
+
* runtime dependency to anyone importing `./cache` for the pure key/freshness
|
|
10
|
+
* helpers.
|
|
11
|
+
*/
|
|
12
|
+
import type { Client } from "@libsql/client";
|
|
13
|
+
import type { ImageCacheStore } from "./cache";
|
|
14
|
+
/**
|
|
15
|
+
* Creates a cache store backed by `tableName` on `client`. Table creation is
|
|
16
|
+
* idempotent and happens once per (client, table); give each cached resource
|
|
17
|
+
* its own table.
|
|
18
|
+
*/
|
|
19
|
+
export declare function createTursoImageCacheStore(client: Client, tableName: string): ImageCacheStore;
|
|
20
|
+
//# sourceMappingURL=cacheStore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cacheStore.d.ts","sourceRoot":"","sources":["../../src/images/cacheStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,KAAK,EAAmB,eAAe,EAAE,MAAM,SAAS,CAAC;AAmChE;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,eAAe,CA0B7F"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cacheStore.test.d.ts","sourceRoot":"","sources":["../../src/images/cacheStore.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optional caption helper for a generated image - two tiers, no content of
|
|
3
|
+
* its own.
|
|
4
|
+
*
|
|
5
|
+
* 1. `randomCaption()` picks from a caller-supplied template pool. Always
|
|
6
|
+
* works, costs nothing.
|
|
7
|
+
* 2. `composeCaption()` runs a caller-supplied compose step (typically an LLM
|
|
8
|
+
* call) racing a timeout; any failure or timeout falls back to tier 1. It
|
|
9
|
+
* never throws.
|
|
10
|
+
*/
|
|
11
|
+
export interface CaptionRequest {
|
|
12
|
+
/** What the picture is of, used to fill `{subject}` in pool templates. */
|
|
13
|
+
subject: string;
|
|
14
|
+
/** Arbitrary extra context a caller's compose function may use. */
|
|
15
|
+
[key: string]: unknown;
|
|
16
|
+
}
|
|
17
|
+
export type CaptionComposeFn = (request: CaptionRequest) => Promise<string>;
|
|
18
|
+
export interface CaptionComposerOptions {
|
|
19
|
+
/** Templates with an optional `{subject}` placeholder. Ships no content - always caller-supplied. */
|
|
20
|
+
pool: readonly string[];
|
|
21
|
+
/** Best-effort tailored caption step, e.g. wrapping an LLM call. Omit to always use the pool. */
|
|
22
|
+
compose?: CaptionComposeFn;
|
|
23
|
+
/** Milliseconds before `compose` is abandoned in favour of the pool. */
|
|
24
|
+
timeoutMs?: number;
|
|
25
|
+
/** Injectable randomness for tests. Default `Math.random`. */
|
|
26
|
+
random?: () => number;
|
|
27
|
+
}
|
|
28
|
+
export interface CaptionComposer {
|
|
29
|
+
randomCaption(subject?: string): string;
|
|
30
|
+
composeCaption(request: CaptionRequest): Promise<string>;
|
|
31
|
+
}
|
|
32
|
+
export declare function createCaptionComposer(options: CaptionComposerOptions): CaptionComposer;
|
|
33
|
+
//# sourceMappingURL=captionComposer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"captionComposer.d.ts","sourceRoot":"","sources":["../../src/images/captionComposer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,WAAW,cAAc;IAC7B,0EAA0E;IAC1E,OAAO,EAAE,MAAM,CAAC;IAChB,mEAAmE;IACnE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,EAAE,cAAc,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAE5E,MAAM,WAAW,sBAAsB;IACrC,qGAAqG;IACrG,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,iGAAiG;IACjG,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAC3B,wEAAwE;IACxE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8DAA8D;IAC9D,MAAM,CAAC,EAAE,MAAM,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,eAAe;IAC9B,aAAa,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxC,cAAc,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAC1D;AAKD,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,sBAAsB,GAAG,eAAe,CAsCtF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"captionComposer.test.d.ts","sourceRoot":"","sources":["../../src/images/captionComposer.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The real Cloudinary upload client, over plain HTTP.
|
|
3
|
+
*
|
|
4
|
+
* This is the only file in the module that talks to Cloudinary. `fetchImpl`
|
|
5
|
+
* is injected (defaulting to the global `fetch`), so tests substitute a mock
|
|
6
|
+
* and never make a real upload. No Cloudinary SDK dependency is needed -
|
|
7
|
+
* signed uploads are a handful of form fields over `fetch`.
|
|
8
|
+
*/
|
|
9
|
+
import { type CloudinaryModuleConfig } from "./cloudinaryConfig";
|
|
10
|
+
import type { CloudinaryUploadClient } from "./types";
|
|
11
|
+
export declare function createCloudinaryUploadClient(config: CloudinaryModuleConfig, fetchImpl?: typeof fetch): CloudinaryUploadClient;
|
|
12
|
+
//# sourceMappingURL=cloudinaryClient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloudinaryClient.d.ts","sourceRoot":"","sources":["../../src/images/cloudinaryClient.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,KAAK,sBAAsB,EAA0B,MAAM,oBAAoB,CAAC;AAEzF,OAAO,KAAK,EAAE,sBAAsB,EAA2B,MAAM,SAAS,CAAC;AAO/E,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,sBAAsB,EAC9B,SAAS,GAAE,OAAO,KAAa,GAC9B,sBAAsB,CA6CxB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloudinaryClient.test.d.ts","sourceRoot":"","sources":["../../src/images/cloudinaryClient.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloudinary upload configuration - pure resolution, no I/O.
|
|
3
|
+
*
|
|
4
|
+
* Same shape as `./config`: the module never reads process.env itself, a host
|
|
5
|
+
* passes an env-shaped record in, and the feature is off when unconfigured.
|
|
6
|
+
*/
|
|
7
|
+
export interface CloudinaryModuleConfig {
|
|
8
|
+
cloudName?: string;
|
|
9
|
+
apiKey?: string;
|
|
10
|
+
apiSecret?: string;
|
|
11
|
+
/** Cloudinary folder generated images are uploaded into. */
|
|
12
|
+
folder: string;
|
|
13
|
+
}
|
|
14
|
+
export declare const DEFAULT_CLOUDINARY_FOLDER = "generated";
|
|
15
|
+
/** The env vars this module reads, all optional. */
|
|
16
|
+
export interface CloudinaryEnv {
|
|
17
|
+
CLOUDINARY_CLOUD_NAME?: string;
|
|
18
|
+
CLOUDINARY_API_KEY?: string;
|
|
19
|
+
CLOUDINARY_API_SECRET?: string;
|
|
20
|
+
CLOUDINARY_FOLDER?: string;
|
|
21
|
+
}
|
|
22
|
+
export declare function resolveCloudinaryConfig(env?: CloudinaryEnv, overrides?: Partial<CloudinaryModuleConfig>): CloudinaryModuleConfig;
|
|
23
|
+
/** Feature detection: any credential missing, no upload. Narrows the credential fields to `string`. */
|
|
24
|
+
export declare function isCloudinaryConfigured(config: CloudinaryModuleConfig): config is CloudinaryModuleConfig & {
|
|
25
|
+
cloudName: string;
|
|
26
|
+
apiKey: string;
|
|
27
|
+
apiSecret: string;
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=cloudinaryConfig.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloudinaryConfig.d.ts","sourceRoot":"","sources":["../../src/images/cloudinaryConfig.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,WAAW,sBAAsB;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4DAA4D;IAC5D,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,eAAO,MAAM,yBAAyB,cAAc,CAAC;AAErD,oDAAoD;AACpD,MAAM,WAAW,aAAa;IAC5B,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAOD,wBAAgB,uBAAuB,CACrC,GAAG,GAAE,aAAkB,EACvB,SAAS,GAAE,OAAO,CAAC,sBAAsB,CAAM,GAC9C,sBAAsB,CASxB;AAED,uGAAuG;AACvG,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,sBAAsB,GAC7B,MAAM,IAAI,sBAAsB,GAAG;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAE7F"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloudinaryConfig.test.d.ts","sourceRoot":"","sources":["../../src/images/cloudinaryConfig.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloudinary's signed-upload scheme - pure, no I/O.
|
|
3
|
+
*
|
|
4
|
+
* Sign every parameter except the file itself: sort keys, join as `key=value`
|
|
5
|
+
* pairs with `&`, append the API secret with no separator, then SHA-1 the
|
|
6
|
+
* result. https://cloudinary.com/documentation/signatures
|
|
7
|
+
*/
|
|
8
|
+
export declare function signCloudinaryParams(params: Record<string, string>, apiSecret: string): string;
|
|
9
|
+
//# sourceMappingURL=cloudinarySignature.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloudinarySignature.d.ts","sourceRoot":"","sources":["../../src/images/cloudinarySignature.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAM9F"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloudinarySignature.test.d.ts","sourceRoot":"","sources":["../../src/images/cloudinarySignature.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image module configuration - pure resolution, no I/O.
|
|
3
|
+
*
|
|
4
|
+
* The module never reads process.env itself; a host passes an env-shaped
|
|
5
|
+
* record in. That keeps the module liftable and lets tests describe
|
|
6
|
+
* configured and unconfigured worlds without touching the real environment.
|
|
7
|
+
*/
|
|
8
|
+
export interface ImageModuleConfig {
|
|
9
|
+
/** Absent or empty means the feature is off; the server still boots. */
|
|
10
|
+
apiKey?: string;
|
|
11
|
+
model: string;
|
|
12
|
+
size: string;
|
|
13
|
+
quality: string;
|
|
14
|
+
}
|
|
15
|
+
export declare const DEFAULT_IMAGE_MODEL = "gpt-image-1";
|
|
16
|
+
export declare const DEFAULT_IMAGE_SIZE = "1024x1024";
|
|
17
|
+
export declare const DEFAULT_IMAGE_QUALITY = "medium";
|
|
18
|
+
/** The env vars this module reads, all optional. */
|
|
19
|
+
export interface ImageEnv {
|
|
20
|
+
OPENAI_API_KEY?: string;
|
|
21
|
+
IMAGE_MODEL?: string;
|
|
22
|
+
IMAGE_SIZE?: string;
|
|
23
|
+
IMAGE_QUALITY?: string;
|
|
24
|
+
}
|
|
25
|
+
export declare function resolveImageConfig(env?: ImageEnv, overrides?: Partial<ImageModuleConfig>): ImageModuleConfig;
|
|
26
|
+
/** Feature detection: no key, no image generation. */
|
|
27
|
+
export declare function isImageConfigured(config: ImageModuleConfig): boolean;
|
|
28
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/images/config.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,iBAAiB;IAChC,wEAAwE;IACxE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,eAAO,MAAM,mBAAmB,gBAAgB,CAAC;AACjD,eAAO,MAAM,kBAAkB,cAAc,CAAC;AAC9C,eAAO,MAAM,qBAAqB,WAAW,CAAC;AAE9C,oDAAoD;AACpD,MAAM,WAAW,QAAQ;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAOD,wBAAgB,kBAAkB,CAChC,GAAG,GAAE,QAAa,EAClB,SAAS,GAAE,OAAO,CAAC,iBAAiB,CAAM,GACzC,iBAAiB,CAOnB;AAED,sDAAsD;AACtD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAEpE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.test.d.ts","sourceRoot":"","sources":["../../src/images/config.test.ts"],"names":[],"mappings":""}
|