feat: add chunking mode option for BlueBubbles (#1645)

* feat: add chunking mode for outbound messages

- Introduced `chunkMode` option in various account configurations to allow splitting messages by "length" or "newline".
- Updated message processing to handle chunking based on the selected mode.
- Added tests for new chunking functionality, ensuring correct behavior for both modes.

* feat: enhance chunking mode documentation and configuration

- Added `chunkMode` option to the BlueBubbles account configuration, allowing users to choose between "length" and "newline" for message chunking.
- Updated documentation to clarify the behavior of the `chunkMode` setting.
- Adjusted account merging logic to incorporate the new `chunkMode` configuration.

* refactor: simplify chunk mode handling for BlueBubbles

- Removed `chunkMode` configuration from various account schemas and types, centralizing chunk mode logic to BlueBubbles only.
- Updated `processMessage` to default to "newline" for BlueBubbles chunking.
- Adjusted tests to reflect changes in chunk mode handling for BlueBubbles, ensuring proper functionality.

* fix: update default chunk mode to 'length' for BlueBubbles

- Changed the default value of `chunkMode` from 'newline' to 'length' in the BlueBubbles configuration and related processing functions.
- Updated documentation to reflect the new default behavior for chunking messages.
- Adjusted tests to ensure the correct default value is returned for BlueBubbles chunk mode.
This commit is contained in:
Tyler Yust
2026-01-24 16:47:10 -08:00
committed by GitHub
parent 6375ee836f
commit 92e794dc18
13 changed files with 247 additions and 8 deletions

View File

@@ -47,7 +47,8 @@ function mergeBlueBubblesAccountConfig(
};
const { accounts: _ignored, ...rest } = base;
const account = resolveAccountConfig(cfg, accountId) ?? {};
return { ...rest, ...account };
const chunkMode = account.chunkMode ?? rest.chunkMode ?? "length";
return { ...rest, ...account, chunkMode };
}
export function resolveBlueBubblesAccount(params: {

View File

@@ -38,6 +38,7 @@ const bluebubblesAccountSchema = z.object({
historyLimit: z.number().int().min(0).optional(),
dmHistoryLimit: z.number().int().min(0).optional(),
textChunkLimit: z.number().int().positive().optional(),
chunkMode: z.enum(["length", "newline"]).optional(),
mediaMaxMb: z.number().int().positive().optional(),
sendReadReceipts: z.boolean().optional(),
blockStreaming: z.boolean().optional(),

View File

@@ -1851,16 +1851,21 @@ async function processMessage(
account.config.textChunkLimit && account.config.textChunkLimit > 0
? account.config.textChunkLimit
: DEFAULT_TEXT_LIMIT;
const chunkMode = account.config.chunkMode ?? "length";
const tableMode = core.channel.text.resolveMarkdownTableMode({
cfg: config,
channel: "bluebubbles",
accountId: account.accountId,
});
const text = core.channel.text.convertMarkdownTables(payload.text ?? "", tableMode);
const chunks = core.channel.text.chunkMarkdownText(text, textLimit);
const chunks =
chunkMode === "newline"
? core.channel.text.chunkTextWithMode(text, textLimit, chunkMode)
: core.channel.text.chunkMarkdownText(text, textLimit);
if (!chunks.length && text) chunks.push(text);
if (!chunks.length) return;
for (const chunk of chunks) {
for (let i = 0; i < chunks.length; i++) {
const chunk = chunks[i];
const result = await sendMessageBlueBubbles(outboundTarget, chunk, {
cfg: config,
accountId: account.accountId,
@@ -1869,6 +1874,17 @@ async function processMessage(
maybeEnqueueOutboundMessageId(result.messageId, chunk);
sentMessage = true;
statusSink?.({ lastOutboundAt: Date.now() });
// In newline mode, restart typing after each chunk if more chunks remain
// Small delay allows the Apple API to finish clearing the typing state from message send
if (chunkMode === "newline" && i < chunks.length - 1 && chatGuidForActions) {
await new Promise((r) => setTimeout(r, 150));
sendBlueBubblesTyping(chatGuidForActions, true, {
cfg: config,
accountId: account.accountId,
}).catch(() => {
// Ignore typing errors
});
}
}
},
onReplyStart: async () => {

View File

@@ -38,6 +38,8 @@ export type BlueBubblesAccountConfig = {
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>;