@gguf/claw
Version:
WhatsApp gateway CLI (Baileys web) with Pi RPC agent
128 lines (117 loc) • 4.25 kB
text/typescript
export type DmPolicy = "pairing" | "allowlist" | "open" | "disabled";
export type GroupPolicy = "open" | "disabled" | "allowlist";
export type BlueBubblesGroupConfig = {
/** If true, only respond in this group when mentioned. */
requireMention?: boolean;
/** Optional tool policy overrides for this group. */
tools?: { allow?: string[]; deny?: string[] };
};
export type BlueBubblesAccountConfig = {
/** Optional display name for this account (used in CLI/UI lists). */
name?: string;
/** Optional provider capability tags used for agent/runtime guidance. */
capabilities?: string[];
/** Allow channel-initiated config writes (default: true). */
configWrites?: boolean;
/** If false, do not start this BlueBubbles account. Default: true. */
enabled?: boolean;
/** Base URL for the BlueBubbles API. */
serverUrl?: string;
/** Password for BlueBubbles API authentication. */
password?: string;
/** Webhook path for the gateway HTTP server. */
webhookPath?: string;
/** Direct message access policy (default: pairing). */
dmPolicy?: DmPolicy;
allowFrom?: Array<string | number>;
/** Optional allowlist for group senders. */
groupAllowFrom?: Array<string | number>;
/** Group message handling policy. */
groupPolicy?: GroupPolicy;
/** Max group messages to keep as history context (0 disables). */
historyLimit?: number;
/** Max DM turns to keep as history context. */
dmHistoryLimit?: number;
/** Per-DM config overrides keyed by user ID. */
dms?: Record<string, unknown>;
/** Outbound text chunk size (chars). Default: 4000. */
textChunkLimit?: number;
/** Chunking mode: "newline" (default) splits on every newline; "length" splits by size. */
chunkMode?: "length" | "newline";
blockStreaming?: boolean;
/** Merge streamed block replies before sending. */
blockStreamingCoalesce?: Record<string, unknown>;
/** Max outbound media size in MB. */
mediaMaxMb?: number;
/** Send read receipts for incoming messages (default: true). */
sendReadReceipts?: boolean;
/** Per-group configuration keyed by chat GUID or identifier. */
groups?: Record<string, BlueBubblesGroupConfig>;
};
export type BlueBubblesActionConfig = {
reactions?: boolean;
edit?: boolean;
unsend?: boolean;
reply?: boolean;
sendWithEffect?: boolean;
renameGroup?: boolean;
addParticipant?: boolean;
removeParticipant?: boolean;
leaveGroup?: boolean;
sendAttachment?: boolean;
};
export type BlueBubblesConfig = {
/** Optional per-account BlueBubbles configuration (multi-account). */
accounts?: Record<string, BlueBubblesAccountConfig>;
/** Per-action tool gating (default: true for all). */
actions?: BlueBubblesActionConfig;
} & BlueBubblesAccountConfig;
export type BlueBubblesSendTarget =
| { kind: "chat_id"; chatId: number }
| { kind: "chat_guid"; chatGuid: string }
| { kind: "chat_identifier"; chatIdentifier: string }
| { kind: "handle"; address: string; service?: "imessage" | "sms" | "auto" };
export type BlueBubblesAttachment = {
guid?: string;
uti?: string;
mimeType?: string;
transferName?: string;
totalBytes?: number;
height?: number;
width?: number;
originalROWID?: number;
};
const DEFAULT_TIMEOUT_MS = 10_000;
export function normalizeBlueBubblesServerUrl(raw: string): string {
const trimmed = raw.trim();
if (!trimmed) {
throw new Error("BlueBubbles serverUrl is required");
}
const withScheme = /^https?:\/\//i.test(trimmed) ? trimmed : `http://${trimmed}`;
return withScheme.replace(/\/+$/, "");
}
export function buildBlueBubblesApiUrl(params: {
baseUrl: string;
path: string;
password?: string;
}): string {
const normalized = normalizeBlueBubblesServerUrl(params.baseUrl);
const url = new URL(params.path, `${normalized}/`);
if (params.password) {
url.searchParams.set("password", params.password);
}
return url.toString();
}
export async function blueBubblesFetchWithTimeout(
url: string,
init: RequestInit,
timeoutMs = DEFAULT_TIMEOUT_MS,
) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeoutMs);
try {
return await fetch(url, { ...init, signal: controller.signal });
} finally {
clearTimeout(timer);
}
}