@friendlyrobot/discord-pi-agent 0.22.3 → 0.22.4

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.
@@ -5,10 +5,13 @@ const DISCORD_MESSAGE_LIMIT = 2000;
5
5
  const FENCE_OVERHEAD = 8; // \`\`\`\n + \n\`\`\`
6
6
  const MAX_CODE_FENCE_CONTENT = DISCORD_MESSAGE_LIMIT - FENCE_OVERHEAD;
7
7
  /**
8
- * Splits text by newlines into chunks that fit within maxSize.
8
+ * Splits plain text by newlines into chunks that fit within maxSize.
9
9
  * Keeps lines intact unless a single line exceeds maxSize.
10
+ *
11
+ * Unlike chunkMessage (markdown-aware, token-level), this is for
12
+ * raw text without markdown structure (e.g. command output).
10
13
  */
11
- function chunkByLines(text, maxSize) {
14
+ function splitPlainTextIntoChunks(text, maxSize) {
12
15
  const lines = text.split("\n");
13
16
  const chunks = [];
14
17
  let current = "";
@@ -126,7 +129,7 @@ export async function sendCommandReply(message, text) {
126
129
  }, "command reply skipped, channel not sendable");
127
130
  return;
128
131
  }
129
- const chunks = chunkByLines(text, MAX_CODE_FENCE_CONTENT).map((c) => `\`\`\`\n${c}\n\`\`\``);
132
+ const chunks = splitPlainTextIntoChunks(text, MAX_CODE_FENCE_CONTENT).map((c) => `\`\`\`\n${c}\n\`\`\``);
130
133
  const [firstChunk, ...remainingChunks] = chunks;
131
134
  if (!firstChunk) {
132
135
  return;
@@ -3,5 +3,9 @@
3
3
  * code blocks, tables, lists, and other block-level elements.
4
4
  * Uses marked's lexer to split on token boundaries so no element
5
5
  * gets bisected mid-structure.
6
+ *
7
+ * Code blocks larger than maxChunkSize are split into smaller
8
+ * sub-chunks, each wrapped in its own fences, so Discord can
9
+ * render each chunk as a valid code block.
6
10
  */
7
11
  export declare function chunkMessage(text: string, maxChunkSize?: number): string[];
@@ -6,6 +6,10 @@ const SAFE_MESSAGE_LIMIT = 1900;
6
6
  * code blocks, tables, lists, and other block-level elements.
7
7
  * Uses marked's lexer to split on token boundaries so no element
8
8
  * gets bisected mid-structure.
9
+ *
10
+ * Code blocks larger than maxChunkSize are split into smaller
11
+ * sub-chunks, each wrapped in its own fences, so Discord can
12
+ * render each chunk as a valid code block.
9
13
  */
10
14
  export function chunkMessage(text, maxChunkSize = SAFE_MESSAGE_LIMIT) {
11
15
  if (text.length <= maxChunkSize) {
@@ -27,12 +31,55 @@ export function chunkMessage(text, maxChunkSize = SAFE_MESSAGE_LIMIT) {
27
31
  };
28
32
  for (const token of tokens) {
29
33
  const size = token.raw.length;
34
+ // If adding this token would overflow AND we have existing
35
+ // tokens in the chunk, flush the current chunk first so the
36
+ // oversized token starts fresh.
30
37
  if (currentSize + size > maxChunkSize && currentTokens.length > 0) {
31
38
  flushChunk();
32
39
  }
40
+ // If this single code block token is larger than the Discord
41
+ // limit, break it into smaller valid code blocks. We use
42
+ // DISCORD_MESSAGE_LIMIT as the threshold (not maxChunkSize)
43
+ // so small-chunkSize tests don't accidentally split code
44
+ // blocks that are fine in practice.
45
+ if (size > DISCORD_MESSAGE_LIMIT &&
46
+ token.type === "code" &&
47
+ currentTokens.length === 0) {
48
+ const subChunks = splitOversizedCodeBlock(token, maxChunkSize);
49
+ chunks.push(...subChunks);
50
+ continue;
51
+ }
33
52
  currentTokens.push(token);
34
53
  currentSize += size;
35
54
  }
36
55
  flushChunk();
37
56
  return chunks.map((chunk) => chunk.slice(0, DISCORD_MESSAGE_LIMIT));
38
57
  }
58
+ /**
59
+ * Split an oversized fenced code block into multiple valid
60
+ * code blocks, each under maxChunkSize. Each sub-chunk gets
61
+ * its own opening and closing fences with the same language tag.
62
+ */
63
+ function splitOversizedCodeBlock(token, maxChunkSize) {
64
+ const lang = token.lang || "";
65
+ const fenceChar = token.raw.startsWith("```") ? "```" : "~~~";
66
+ const header = fenceChar + lang;
67
+ const footer = fenceChar;
68
+ // Headroom for wrapping fences plus two newlines.
69
+ const wrapOverhead = header.length + footer.length + 2;
70
+ const maxBodySize = maxChunkSize - wrapOverhead;
71
+ if (maxBodySize <= 0) {
72
+ // Fences alone eat the whole budget; just return the raw token
73
+ // capped at DISCORD_MESSAGE_LIMIT (handled by the final map).
74
+ return [token.raw];
75
+ }
76
+ const body = token.text;
77
+ const subChunks = [];
78
+ let offset = 0;
79
+ while (offset < body.length) {
80
+ const segment = body.slice(offset, offset + maxBodySize);
81
+ subChunks.push([header, segment, footer].join("\n"));
82
+ offset += maxBodySize;
83
+ }
84
+ return subChunks;
85
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@friendlyrobot/discord-pi-agent",
3
- "version": "0.22.3",
3
+ "version": "0.22.4",
4
4
  "description": "Reusable Discord gateway for persistent pi agent sessions",
5
5
  "license": "MIT",
6
6
  "type": "module",