@bryti/agent 0.0.1 → 0.1.1

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.
Files changed (228) hide show
  1. package/Dockerfile +27 -0
  2. package/README.md +91 -51
  3. package/config.example.yml +265 -0
  4. package/dist/active-hours.d.ts +23 -0
  5. package/dist/active-hours.d.ts.map +1 -0
  6. package/dist/active-hours.js +68 -0
  7. package/dist/active-hours.js.map +1 -0
  8. package/dist/agent.d.ts +84 -0
  9. package/dist/agent.d.ts.map +1 -0
  10. package/dist/agent.js +383 -0
  11. package/dist/agent.js.map +1 -0
  12. package/dist/channels/markdown/ir.d.ts +79 -0
  13. package/dist/channels/markdown/ir.d.ts.map +1 -0
  14. package/dist/channels/markdown/ir.js +824 -0
  15. package/dist/channels/markdown/ir.js.map +1 -0
  16. package/dist/channels/markdown/render.d.ts +35 -0
  17. package/dist/channels/markdown/render.d.ts.map +1 -0
  18. package/dist/channels/markdown/render.js +178 -0
  19. package/dist/channels/markdown/render.js.map +1 -0
  20. package/dist/channels/telegram-network-errors.d.ts +27 -0
  21. package/dist/channels/telegram-network-errors.d.ts.map +1 -0
  22. package/dist/channels/telegram-network-errors.js +156 -0
  23. package/dist/channels/telegram-network-errors.js.map +1 -0
  24. package/dist/channels/telegram.d.ts +76 -0
  25. package/dist/channels/telegram.d.ts.map +1 -0
  26. package/dist/channels/telegram.js +814 -0
  27. package/dist/channels/telegram.js.map +1 -0
  28. package/dist/channels/types.d.ts +59 -0
  29. package/dist/channels/types.d.ts.map +1 -0
  30. package/dist/channels/types.js +9 -0
  31. package/dist/channels/types.js.map +1 -0
  32. package/dist/channels/whatsapp.d.ts +45 -0
  33. package/dist/channels/whatsapp.d.ts.map +1 -0
  34. package/dist/channels/whatsapp.js +310 -0
  35. package/dist/channels/whatsapp.js.map +1 -0
  36. package/dist/cli.d.ts +13 -0
  37. package/dist/cli.d.ts.map +1 -0
  38. package/dist/cli.js +635 -0
  39. package/dist/cli.js.map +1 -0
  40. package/dist/commands.d.ts +35 -0
  41. package/dist/commands.d.ts.map +1 -0
  42. package/dist/commands.js +113 -0
  43. package/dist/commands.js.map +1 -0
  44. package/dist/compaction/history.d.ts +17 -0
  45. package/dist/compaction/history.d.ts.map +1 -0
  46. package/dist/compaction/history.js +35 -0
  47. package/dist/compaction/history.js.map +1 -0
  48. package/dist/compaction/index.d.ts +3 -0
  49. package/dist/compaction/index.d.ts.map +1 -0
  50. package/dist/compaction/index.js +3 -0
  51. package/dist/compaction/index.js.map +1 -0
  52. package/dist/compaction/proactive.d.ts +25 -0
  53. package/dist/compaction/proactive.d.ts.map +1 -0
  54. package/dist/compaction/proactive.js +87 -0
  55. package/dist/compaction/proactive.js.map +1 -0
  56. package/dist/compaction/transcript-repair.d.ts +55 -0
  57. package/dist/compaction/transcript-repair.d.ts.map +1 -0
  58. package/dist/compaction/transcript-repair.js +215 -0
  59. package/dist/compaction/transcript-repair.js.map +1 -0
  60. package/dist/config.d.ts +128 -0
  61. package/dist/config.d.ts.map +1 -0
  62. package/dist/config.js +317 -0
  63. package/dist/config.js.map +1 -0
  64. package/dist/crash-recovery.d.ts +23 -0
  65. package/dist/crash-recovery.d.ts.map +1 -0
  66. package/dist/crash-recovery.js +96 -0
  67. package/dist/crash-recovery.js.map +1 -0
  68. package/dist/defaults/extensions/EXTENSIONS.md +158 -0
  69. package/dist/defaults/extensions/documents-hedgedoc.ts +153 -0
  70. package/dist/history.d.ts +31 -0
  71. package/dist/history.d.ts.map +1 -0
  72. package/dist/history.js +49 -0
  73. package/dist/history.js.map +1 -0
  74. package/dist/index.d.ts +19 -0
  75. package/dist/index.d.ts.map +1 -0
  76. package/dist/index.js +686 -0
  77. package/dist/index.js.map +1 -0
  78. package/dist/logger.d.ts +39 -0
  79. package/dist/logger.d.ts.map +1 -0
  80. package/dist/logger.js +143 -0
  81. package/dist/logger.js.map +1 -0
  82. package/dist/memory/conversation-search.d.ts +15 -0
  83. package/dist/memory/conversation-search.d.ts.map +1 -0
  84. package/dist/memory/conversation-search.js +60 -0
  85. package/dist/memory/conversation-search.js.map +1 -0
  86. package/dist/memory/core-memory.d.ts +28 -0
  87. package/dist/memory/core-memory.d.ts.map +1 -0
  88. package/dist/memory/core-memory.js +102 -0
  89. package/dist/memory/core-memory.js.map +1 -0
  90. package/dist/memory/embeddings.d.ts +44 -0
  91. package/dist/memory/embeddings.d.ts.map +1 -0
  92. package/dist/memory/embeddings.js +139 -0
  93. package/dist/memory/embeddings.js.map +1 -0
  94. package/dist/memory/search.d.ts +49 -0
  95. package/dist/memory/search.d.ts.map +1 -0
  96. package/dist/memory/search.js +97 -0
  97. package/dist/memory/search.js.map +1 -0
  98. package/dist/memory/store.d.ts +32 -0
  99. package/dist/memory/store.d.ts.map +1 -0
  100. package/dist/memory/store.js +205 -0
  101. package/dist/memory/store.js.map +1 -0
  102. package/dist/message-queue.d.ts +73 -0
  103. package/dist/message-queue.d.ts.map +1 -0
  104. package/dist/message-queue.js +188 -0
  105. package/dist/message-queue.js.map +1 -0
  106. package/dist/model-infra.d.ts +64 -0
  107. package/dist/model-infra.d.ts.map +1 -0
  108. package/dist/model-infra.js +202 -0
  109. package/dist/model-infra.js.map +1 -0
  110. package/dist/projection/format.d.ts +10 -0
  111. package/dist/projection/format.d.ts.map +1 -0
  112. package/dist/projection/format.js +30 -0
  113. package/dist/projection/format.js.map +1 -0
  114. package/dist/projection/index.d.ts +11 -0
  115. package/dist/projection/index.d.ts.map +1 -0
  116. package/dist/projection/index.js +9 -0
  117. package/dist/projection/index.js.map +1 -0
  118. package/dist/projection/reflection.d.ts +119 -0
  119. package/dist/projection/reflection.d.ts.map +1 -0
  120. package/dist/projection/reflection.js +422 -0
  121. package/dist/projection/reflection.js.map +1 -0
  122. package/dist/projection/store.d.ts +144 -0
  123. package/dist/projection/store.d.ts.map +1 -0
  124. package/dist/projection/store.js +519 -0
  125. package/dist/projection/store.js.map +1 -0
  126. package/dist/projection/tools.d.ts +11 -0
  127. package/dist/projection/tools.d.ts.map +1 -0
  128. package/dist/projection/tools.js +237 -0
  129. package/dist/projection/tools.js.map +1 -0
  130. package/dist/scheduler.d.ts +36 -0
  131. package/dist/scheduler.d.ts.map +1 -0
  132. package/dist/scheduler.js +286 -0
  133. package/dist/scheduler.js.map +1 -0
  134. package/dist/system-prompt.d.ts +41 -0
  135. package/dist/system-prompt.d.ts.map +1 -0
  136. package/dist/system-prompt.js +162 -0
  137. package/dist/system-prompt.js.map +1 -0
  138. package/dist/time.d.ts +52 -0
  139. package/dist/time.d.ts.map +1 -0
  140. package/dist/time.js +138 -0
  141. package/dist/time.js.map +1 -0
  142. package/dist/tools/archival-memory-tool.d.ts +8 -0
  143. package/dist/tools/archival-memory-tool.d.ts.map +1 -0
  144. package/dist/tools/archival-memory-tool.js +68 -0
  145. package/dist/tools/archival-memory-tool.js.map +1 -0
  146. package/dist/tools/conversation-search-tool.d.ts +6 -0
  147. package/dist/tools/conversation-search-tool.d.ts.map +1 -0
  148. package/dist/tools/conversation-search-tool.js +28 -0
  149. package/dist/tools/conversation-search-tool.js.map +1 -0
  150. package/dist/tools/core-memory-tool.d.ts +7 -0
  151. package/dist/tools/core-memory-tool.d.ts.map +1 -0
  152. package/dist/tools/core-memory-tool.js +59 -0
  153. package/dist/tools/core-memory-tool.js.map +1 -0
  154. package/dist/tools/fetch-url.d.ts +15 -0
  155. package/dist/tools/fetch-url.d.ts.map +1 -0
  156. package/dist/tools/fetch-url.js +76 -0
  157. package/dist/tools/fetch-url.js.map +1 -0
  158. package/dist/tools/files.d.ts +10 -0
  159. package/dist/tools/files.d.ts.map +1 -0
  160. package/dist/tools/files.js +127 -0
  161. package/dist/tools/files.js.map +1 -0
  162. package/dist/tools/index.d.ts +17 -0
  163. package/dist/tools/index.d.ts.map +1 -0
  164. package/dist/tools/index.js +118 -0
  165. package/dist/tools/index.js.map +1 -0
  166. package/dist/tools/result.d.ts +21 -0
  167. package/dist/tools/result.d.ts.map +1 -0
  168. package/dist/tools/result.js +36 -0
  169. package/dist/tools/result.js.map +1 -0
  170. package/dist/tools/skill-install.d.ts +17 -0
  171. package/dist/tools/skill-install.d.ts.map +1 -0
  172. package/dist/tools/skill-install.js +148 -0
  173. package/dist/tools/skill-install.js.map +1 -0
  174. package/dist/tools/web-search.d.ts +42 -0
  175. package/dist/tools/web-search.d.ts.map +1 -0
  176. package/dist/tools/web-search.js +237 -0
  177. package/dist/tools/web-search.js.map +1 -0
  178. package/dist/trust/guardrail.d.ts +60 -0
  179. package/dist/trust/guardrail.d.ts.map +1 -0
  180. package/dist/trust/guardrail.js +171 -0
  181. package/dist/trust/guardrail.js.map +1 -0
  182. package/dist/trust/index.d.ts +12 -0
  183. package/dist/trust/index.d.ts.map +1 -0
  184. package/dist/trust/index.js +12 -0
  185. package/dist/trust/index.js.map +1 -0
  186. package/dist/trust/store.d.ts +118 -0
  187. package/dist/trust/store.d.ts.map +1 -0
  188. package/dist/trust/store.js +209 -0
  189. package/dist/trust/store.js.map +1 -0
  190. package/dist/trust/wrapper.d.ts +36 -0
  191. package/dist/trust/wrapper.d.ts.map +1 -0
  192. package/dist/trust/wrapper.js +142 -0
  193. package/dist/trust/wrapper.js.map +1 -0
  194. package/dist/usage.d.ts +53 -0
  195. package/dist/usage.d.ts.map +1 -0
  196. package/dist/usage.js +124 -0
  197. package/dist/usage.js.map +1 -0
  198. package/dist/util/math.d.ts +9 -0
  199. package/dist/util/math.d.ts.map +1 -0
  200. package/dist/util/math.js +22 -0
  201. package/dist/util/math.js.map +1 -0
  202. package/dist/util/ssrf.d.ts +21 -0
  203. package/dist/util/ssrf.d.ts.map +1 -0
  204. package/dist/util/ssrf.js +77 -0
  205. package/dist/util/ssrf.js.map +1 -0
  206. package/dist/workers/index.d.ts +8 -0
  207. package/dist/workers/index.d.ts.map +1 -0
  208. package/dist/workers/index.js +7 -0
  209. package/dist/workers/index.js.map +1 -0
  210. package/dist/workers/registry.d.ts +53 -0
  211. package/dist/workers/registry.d.ts.map +1 -0
  212. package/dist/workers/registry.js +38 -0
  213. package/dist/workers/registry.js.map +1 -0
  214. package/dist/workers/scoped-tools.d.ts +21 -0
  215. package/dist/workers/scoped-tools.d.ts.map +1 -0
  216. package/dist/workers/scoped-tools.js +111 -0
  217. package/dist/workers/scoped-tools.js.map +1 -0
  218. package/dist/workers/spawn.d.ts +62 -0
  219. package/dist/workers/spawn.d.ts.map +1 -0
  220. package/dist/workers/spawn.js +314 -0
  221. package/dist/workers/spawn.js.map +1 -0
  222. package/dist/workers/tools.d.ts +26 -0
  223. package/dist/workers/tools.d.ts.map +1 -0
  224. package/dist/workers/tools.js +380 -0
  225. package/dist/workers/tools.js.map +1 -0
  226. package/docker-compose.yml +72 -0
  227. package/package.json +16 -1
  228. package/run.sh +27 -0
@@ -0,0 +1,824 @@
1
+ /**
2
+ * Intermediate Representation (IR) for markdown rendering.
3
+ *
4
+ * Why a custom IR instead of regex?
5
+ *
6
+ * Regex-based rendering has two fundamental problems:
7
+ * 1. It can't chunk safely. Splitting a string at a character limit with regex
8
+ * will routinely cut inside a bold span, an inline-code span, or a link
9
+ * label, producing malformed output.
10
+ * 2. It can't handle overlapping or nested spans. LLM output frequently
11
+ * contains quirks like unclosed markers, nested bold-italic, or partial
12
+ * code fences that confuse any single-pass regex strategy.
13
+ *
14
+ * The IR solves this by separating concerns:
15
+ * - Parsing (markdown-it) produces a token stream with well-defined open/close
16
+ * pairs, which is walked once to build a flat text string plus span metadata.
17
+ * - Rendering (telegram.ts, etc.) reads the IR and emits platform-specific
18
+ * markup, with full knowledge of every active style at every character.
19
+ * - Chunking (chunkMarkdownIR) operates on the IR, so it can guarantee that
20
+ * chunk boundaries never land inside a style span.
21
+ */
22
+ import MarkdownIt from "markdown-it";
23
+ /**
24
+ * Split text into chunks no longer than limit, preferring paragraph/newline
25
+ * boundaries. Used by chunkMarkdownIR.
26
+ */
27
+ function chunkText(text, limit) {
28
+ if (!text || limit <= 0 || text.length <= limit) {
29
+ return text ? [text] : [];
30
+ }
31
+ const chunks = [];
32
+ let remaining = text;
33
+ while (remaining.length > limit) {
34
+ const window = remaining.slice(0, limit);
35
+ let splitAt = window.lastIndexOf("\n\n");
36
+ if (splitAt < limit * 0.3)
37
+ splitAt = -1;
38
+ if (splitAt === -1) {
39
+ const nl = window.lastIndexOf("\n");
40
+ if (nl > limit * 0.3)
41
+ splitAt = nl;
42
+ }
43
+ if (splitAt === -1) {
44
+ const ws = window.lastIndexOf(" ");
45
+ if (ws > limit * 0.3)
46
+ splitAt = ws;
47
+ }
48
+ if (splitAt === -1)
49
+ splitAt = limit;
50
+ chunks.push(remaining.slice(0, splitAt).trimEnd());
51
+ remaining = remaining.slice(splitAt).trimStart();
52
+ }
53
+ if (remaining)
54
+ chunks.push(remaining);
55
+ return chunks;
56
+ }
57
+ function createMarkdownIt(options) {
58
+ const md = new MarkdownIt({
59
+ html: false,
60
+ linkify: options.linkify ?? true,
61
+ breaks: false,
62
+ typographer: false,
63
+ });
64
+ md.enable("strikethrough");
65
+ if (options.tableMode && options.tableMode !== "off") {
66
+ md.enable("table");
67
+ }
68
+ else {
69
+ md.disable("table");
70
+ }
71
+ if (options.autolink === false) {
72
+ md.disable("autolink");
73
+ }
74
+ return md;
75
+ }
76
+ function getAttr(token, name) {
77
+ if (token.attrGet) {
78
+ return token.attrGet(name);
79
+ }
80
+ if (token.attrs) {
81
+ for (const [key, value] of token.attrs) {
82
+ if (key === name) {
83
+ return value;
84
+ }
85
+ }
86
+ }
87
+ return null;
88
+ }
89
+ function createTextToken(base, content) {
90
+ return { ...base, type: "text", content, children: undefined };
91
+ }
92
+ function applySpoilerTokens(tokens) {
93
+ for (const token of tokens) {
94
+ if (token.children && token.children.length > 0) {
95
+ token.children = injectSpoilersIntoInline(token.children);
96
+ }
97
+ }
98
+ }
99
+ function injectSpoilersIntoInline(tokens) {
100
+ const result = [];
101
+ const state = { spoilerOpen: false };
102
+ for (const token of tokens) {
103
+ if (token.type !== "text") {
104
+ result.push(token);
105
+ continue;
106
+ }
107
+ const content = token.content ?? "";
108
+ if (!content.includes("||")) {
109
+ result.push(token);
110
+ continue;
111
+ }
112
+ let index = 0;
113
+ while (index < content.length) {
114
+ const next = content.indexOf("||", index);
115
+ if (next === -1) {
116
+ if (index < content.length) {
117
+ result.push(createTextToken(token, content.slice(index)));
118
+ }
119
+ break;
120
+ }
121
+ if (next > index) {
122
+ result.push(createTextToken(token, content.slice(index, next)));
123
+ }
124
+ state.spoilerOpen = !state.spoilerOpen;
125
+ result.push({
126
+ type: state.spoilerOpen ? "spoiler_open" : "spoiler_close",
127
+ });
128
+ index = next + 2;
129
+ }
130
+ }
131
+ return result;
132
+ }
133
+ function initRenderTarget() {
134
+ return {
135
+ text: "",
136
+ styles: [],
137
+ openStyles: [],
138
+ links: [],
139
+ linkStack: [],
140
+ };
141
+ }
142
+ function resolveRenderTarget(state) {
143
+ return state.table?.currentCell ?? state;
144
+ }
145
+ function appendText(state, value) {
146
+ if (!value) {
147
+ return;
148
+ }
149
+ const target = resolveRenderTarget(state);
150
+ target.text += value;
151
+ }
152
+ function openStyle(state, style) {
153
+ const target = resolveRenderTarget(state);
154
+ target.openStyles.push({ style, start: target.text.length });
155
+ }
156
+ function closeStyle(state, style) {
157
+ const target = resolveRenderTarget(state);
158
+ for (let i = target.openStyles.length - 1; i >= 0; i -= 1) {
159
+ if (target.openStyles[i]?.style === style) {
160
+ const start = target.openStyles[i].start;
161
+ target.openStyles.splice(i, 1);
162
+ const end = target.text.length;
163
+ if (end > start) {
164
+ target.styles.push({ start, end, style });
165
+ }
166
+ return;
167
+ }
168
+ }
169
+ }
170
+ function appendParagraphSeparator(state) {
171
+ if (state.env.listStack.length > 0) {
172
+ return;
173
+ }
174
+ if (state.table) {
175
+ return;
176
+ } // Don't add paragraph separators inside tables
177
+ state.text += "\n\n";
178
+ }
179
+ function appendListPrefix(state) {
180
+ const stack = state.env.listStack;
181
+ const top = stack[stack.length - 1];
182
+ if (!top) {
183
+ return;
184
+ }
185
+ top.index += 1;
186
+ const indent = " ".repeat(Math.max(0, stack.length - 1));
187
+ const prefix = top.type === "ordered" ? `${top.index}. ` : "• ";
188
+ state.text += `${indent}${prefix}`;
189
+ }
190
+ function renderInlineCode(state, content) {
191
+ if (!content) {
192
+ return;
193
+ }
194
+ const target = resolveRenderTarget(state);
195
+ const start = target.text.length;
196
+ target.text += content;
197
+ target.styles.push({ start, end: start + content.length, style: "code" });
198
+ }
199
+ /**
200
+ * Code blocks differ from inline code in two important ways:
201
+ *
202
+ * 1. Style isolation: a fenced code block resets the active styling context.
203
+ * Unlike inline code (which just adds a `code` span on top of whatever is
204
+ * open), code blocks record their span directly on the target without going
205
+ * through the openStyle/closeStyle stack, so surrounding bold/italic cannot
206
+ * "leak" into the block.
207
+ * 2. No splitting: code blocks must never be split across message chunks. The
208
+ * chunking logic in chunkMarkdownIR respects span boundaries, so a
209
+ * `code_block` span always lands in a single chunk intact.
210
+ */
211
+ function renderCodeBlock(state, content) {
212
+ let code = content ?? "";
213
+ if (!code.endsWith("\n")) {
214
+ code = `${code}\n`;
215
+ }
216
+ const target = resolveRenderTarget(state);
217
+ const start = target.text.length;
218
+ target.text += code;
219
+ target.styles.push({ start, end: start + code.length, style: "code_block" });
220
+ if (state.env.listStack.length === 0) {
221
+ target.text += "\n";
222
+ }
223
+ }
224
+ function handleLinkClose(state) {
225
+ const target = resolveRenderTarget(state);
226
+ const link = target.linkStack.pop();
227
+ if (!link?.href) {
228
+ return;
229
+ }
230
+ const href = link.href.trim();
231
+ if (!href) {
232
+ return;
233
+ }
234
+ const start = link.labelStart;
235
+ const end = target.text.length;
236
+ if (end <= start) {
237
+ target.links.push({ start, end, href });
238
+ return;
239
+ }
240
+ target.links.push({ start, end, href });
241
+ }
242
+ function initTableState() {
243
+ return {
244
+ headers: [],
245
+ rows: [],
246
+ currentRow: [],
247
+ currentCell: null,
248
+ inHeader: false,
249
+ };
250
+ }
251
+ function finishTableCell(cell) {
252
+ closeRemainingStyles(cell);
253
+ return {
254
+ text: cell.text,
255
+ styles: cell.styles,
256
+ links: cell.links,
257
+ };
258
+ }
259
+ function trimCell(cell) {
260
+ const text = cell.text;
261
+ let start = 0;
262
+ let end = text.length;
263
+ while (start < end && /\s/.test(text[start] ?? "")) {
264
+ start += 1;
265
+ }
266
+ while (end > start && /\s/.test(text[end - 1] ?? "")) {
267
+ end -= 1;
268
+ }
269
+ if (start === 0 && end === text.length) {
270
+ return cell;
271
+ }
272
+ const trimmedText = text.slice(start, end);
273
+ const trimmedLength = trimmedText.length;
274
+ const trimmedStyles = [];
275
+ for (const span of cell.styles) {
276
+ const sliceStart = Math.max(0, span.start - start);
277
+ const sliceEnd = Math.min(trimmedLength, span.end - start);
278
+ if (sliceEnd > sliceStart) {
279
+ trimmedStyles.push({ start: sliceStart, end: sliceEnd, style: span.style });
280
+ }
281
+ }
282
+ const trimmedLinks = [];
283
+ for (const span of cell.links) {
284
+ const sliceStart = Math.max(0, span.start - start);
285
+ const sliceEnd = Math.min(trimmedLength, span.end - start);
286
+ if (sliceEnd > sliceStart) {
287
+ trimmedLinks.push({ start: sliceStart, end: sliceEnd, href: span.href });
288
+ }
289
+ }
290
+ return { text: trimmedText, styles: trimmedStyles, links: trimmedLinks };
291
+ }
292
+ function appendCell(state, cell) {
293
+ if (!cell.text) {
294
+ return;
295
+ }
296
+ const start = state.text.length;
297
+ state.text += cell.text;
298
+ for (const span of cell.styles) {
299
+ state.styles.push({
300
+ start: start + span.start,
301
+ end: start + span.end,
302
+ style: span.style,
303
+ });
304
+ }
305
+ for (const link of cell.links) {
306
+ state.links.push({
307
+ start: start + link.start,
308
+ end: start + link.end,
309
+ href: link.href,
310
+ });
311
+ }
312
+ }
313
+ /**
314
+ * Telegram has no native table support, so tables are converted to bullet
315
+ * lists. The strategy depends on column count:
316
+ *
317
+ * - Multi-column: each row becomes a section. The first column value is
318
+ * bolded as a section label, and the remaining columns are rendered as
319
+ * "• Header: value" lines beneath it.
320
+ * - Single-column (or no headers): each cell is its own bullet entry.
321
+ *
322
+ * Style and link spans from individual cells are preserved and re-offset into
323
+ * the flat output coordinate space via appendCell().
324
+ */
325
+ function renderTableAsBullets(state) {
326
+ if (!state.table) {
327
+ return;
328
+ }
329
+ const headers = state.table.headers.map(trimCell);
330
+ const rows = state.table.rows.map((row) => row.map(trimCell));
331
+ // If no headers or rows, skip
332
+ if (headers.length === 0 && rows.length === 0) {
333
+ return;
334
+ }
335
+ // Determine if first column should be used as row labels
336
+ // (common pattern: first column is category/feature name)
337
+ const useFirstColAsLabel = headers.length > 1 && rows.length > 0;
338
+ if (useFirstColAsLabel) {
339
+ // Format: each row becomes a section with header as row[0], then key:value pairs
340
+ for (const row of rows) {
341
+ if (row.length === 0) {
342
+ continue;
343
+ }
344
+ const rowLabel = row[0];
345
+ if (rowLabel?.text) {
346
+ const labelStart = state.text.length;
347
+ appendCell(state, rowLabel);
348
+ const labelEnd = state.text.length;
349
+ if (labelEnd > labelStart) {
350
+ state.styles.push({ start: labelStart, end: labelEnd, style: "bold" });
351
+ }
352
+ state.text += "\n";
353
+ }
354
+ // Add each column as a bullet point
355
+ for (let i = 1; i < row.length; i++) {
356
+ const header = headers[i];
357
+ const value = row[i];
358
+ if (!value?.text) {
359
+ continue;
360
+ }
361
+ state.text += "• ";
362
+ if (header?.text) {
363
+ appendCell(state, header);
364
+ state.text += ": ";
365
+ }
366
+ else {
367
+ state.text += `Column ${i}: `;
368
+ }
369
+ appendCell(state, value);
370
+ state.text += "\n";
371
+ }
372
+ state.text += "\n";
373
+ }
374
+ }
375
+ else {
376
+ // Simple table: just list headers and values
377
+ for (const row of rows) {
378
+ for (let i = 0; i < row.length; i++) {
379
+ const header = headers[i];
380
+ const value = row[i];
381
+ if (!value?.text) {
382
+ continue;
383
+ }
384
+ state.text += "• ";
385
+ if (header?.text) {
386
+ appendCell(state, header);
387
+ state.text += ": ";
388
+ }
389
+ appendCell(state, value);
390
+ state.text += "\n";
391
+ }
392
+ state.text += "\n";
393
+ }
394
+ }
395
+ }
396
+ function renderTableAsCode(state) {
397
+ if (!state.table) {
398
+ return;
399
+ }
400
+ const headers = state.table.headers.map(trimCell);
401
+ const rows = state.table.rows.map((row) => row.map(trimCell));
402
+ const columnCount = Math.max(headers.length, ...rows.map((row) => row.length));
403
+ if (columnCount === 0) {
404
+ return;
405
+ }
406
+ const widths = Array.from({ length: columnCount }, () => 0);
407
+ const updateWidths = (cells) => {
408
+ for (let i = 0; i < columnCount; i += 1) {
409
+ const cell = cells[i];
410
+ const width = cell?.text.length ?? 0;
411
+ if (widths[i] < width) {
412
+ widths[i] = width;
413
+ }
414
+ }
415
+ };
416
+ updateWidths(headers);
417
+ for (const row of rows) {
418
+ updateWidths(row);
419
+ }
420
+ const codeStart = state.text.length;
421
+ const appendRow = (cells) => {
422
+ state.text += "|";
423
+ for (let i = 0; i < columnCount; i += 1) {
424
+ state.text += " ";
425
+ const cell = cells[i];
426
+ if (cell) {
427
+ appendCell(state, cell);
428
+ }
429
+ const pad = widths[i] - (cell?.text.length ?? 0);
430
+ if (pad > 0) {
431
+ state.text += " ".repeat(pad);
432
+ }
433
+ state.text += " |";
434
+ }
435
+ state.text += "\n";
436
+ };
437
+ const appendDivider = () => {
438
+ state.text += "|";
439
+ for (let i = 0; i < columnCount; i += 1) {
440
+ const dashCount = Math.max(3, widths[i]);
441
+ state.text += ` ${"-".repeat(dashCount)} |`;
442
+ }
443
+ state.text += "\n";
444
+ };
445
+ appendRow(headers);
446
+ appendDivider();
447
+ for (const row of rows) {
448
+ appendRow(row);
449
+ }
450
+ const codeEnd = state.text.length;
451
+ if (codeEnd > codeStart) {
452
+ state.styles.push({ start: codeStart, end: codeEnd, style: "code_block" });
453
+ }
454
+ if (state.env.listStack.length === 0) {
455
+ state.text += "\n";
456
+ }
457
+ }
458
+ function renderTokens(tokens, state) {
459
+ for (const token of tokens) {
460
+ switch (token.type) {
461
+ case "inline":
462
+ if (token.children) {
463
+ renderTokens(token.children, state);
464
+ }
465
+ break;
466
+ case "text":
467
+ appendText(state, token.content ?? "");
468
+ break;
469
+ case "em_open":
470
+ openStyle(state, "italic");
471
+ break;
472
+ case "em_close":
473
+ closeStyle(state, "italic");
474
+ break;
475
+ case "strong_open":
476
+ openStyle(state, "bold");
477
+ break;
478
+ case "strong_close":
479
+ closeStyle(state, "bold");
480
+ break;
481
+ case "s_open":
482
+ openStyle(state, "strikethrough");
483
+ break;
484
+ case "s_close":
485
+ closeStyle(state, "strikethrough");
486
+ break;
487
+ case "code_inline":
488
+ renderInlineCode(state, token.content ?? "");
489
+ break;
490
+ case "spoiler_open":
491
+ if (state.enableSpoilers) {
492
+ openStyle(state, "spoiler");
493
+ }
494
+ break;
495
+ case "spoiler_close":
496
+ if (state.enableSpoilers) {
497
+ closeStyle(state, "spoiler");
498
+ }
499
+ break;
500
+ case "link_open": {
501
+ const href = getAttr(token, "href") ?? "";
502
+ const target = resolveRenderTarget(state);
503
+ target.linkStack.push({ href, labelStart: target.text.length });
504
+ break;
505
+ }
506
+ case "link_close":
507
+ handleLinkClose(state);
508
+ break;
509
+ case "image":
510
+ appendText(state, token.content ?? "");
511
+ break;
512
+ case "softbreak":
513
+ case "hardbreak":
514
+ appendText(state, "\n");
515
+ break;
516
+ case "paragraph_close":
517
+ appendParagraphSeparator(state);
518
+ break;
519
+ case "heading_open":
520
+ if (state.headingStyle === "bold") {
521
+ openStyle(state, "bold");
522
+ }
523
+ break;
524
+ case "heading_close":
525
+ if (state.headingStyle === "bold") {
526
+ closeStyle(state, "bold");
527
+ }
528
+ appendParagraphSeparator(state);
529
+ break;
530
+ case "blockquote_open":
531
+ if (state.blockquotePrefix) {
532
+ state.text += state.blockquotePrefix;
533
+ }
534
+ break;
535
+ case "blockquote_close":
536
+ state.text += "\n";
537
+ break;
538
+ case "bullet_list_open":
539
+ state.env.listStack.push({ type: "bullet", index: 0 });
540
+ break;
541
+ case "bullet_list_close":
542
+ state.env.listStack.pop();
543
+ break;
544
+ case "ordered_list_open": {
545
+ const start = Number(getAttr(token, "start") ?? "1");
546
+ state.env.listStack.push({ type: "ordered", index: start - 1 });
547
+ break;
548
+ }
549
+ case "ordered_list_close":
550
+ state.env.listStack.pop();
551
+ break;
552
+ case "list_item_open":
553
+ appendListPrefix(state);
554
+ break;
555
+ case "list_item_close":
556
+ state.text += "\n";
557
+ break;
558
+ case "code_block":
559
+ case "fence":
560
+ renderCodeBlock(state, token.content ?? "");
561
+ break;
562
+ case "html_block":
563
+ case "html_inline":
564
+ appendText(state, token.content ?? "");
565
+ break;
566
+ // Table handling
567
+ case "table_open":
568
+ if (state.tableMode !== "off") {
569
+ state.table = initTableState();
570
+ state.hasTables = true;
571
+ }
572
+ break;
573
+ case "table_close":
574
+ if (state.table) {
575
+ if (state.tableMode === "bullets") {
576
+ renderTableAsBullets(state);
577
+ }
578
+ else if (state.tableMode === "code") {
579
+ renderTableAsCode(state);
580
+ }
581
+ }
582
+ state.table = null;
583
+ break;
584
+ case "thead_open":
585
+ if (state.table) {
586
+ state.table.inHeader = true;
587
+ }
588
+ break;
589
+ case "thead_close":
590
+ if (state.table) {
591
+ state.table.inHeader = false;
592
+ }
593
+ break;
594
+ case "tbody_open":
595
+ case "tbody_close":
596
+ break;
597
+ case "tr_open":
598
+ if (state.table) {
599
+ state.table.currentRow = [];
600
+ }
601
+ break;
602
+ case "tr_close":
603
+ if (state.table) {
604
+ if (state.table.inHeader) {
605
+ state.table.headers = state.table.currentRow;
606
+ }
607
+ else {
608
+ state.table.rows.push(state.table.currentRow);
609
+ }
610
+ state.table.currentRow = [];
611
+ }
612
+ break;
613
+ case "th_open":
614
+ case "td_open":
615
+ if (state.table) {
616
+ state.table.currentCell = initRenderTarget();
617
+ }
618
+ break;
619
+ case "th_close":
620
+ case "td_close":
621
+ if (state.table?.currentCell) {
622
+ state.table.currentRow.push(finishTableCell(state.table.currentCell));
623
+ state.table.currentCell = null;
624
+ }
625
+ break;
626
+ case "hr":
627
+ state.text += "\n";
628
+ break;
629
+ default:
630
+ if (token.children) {
631
+ renderTokens(token.children, state);
632
+ }
633
+ break;
634
+ }
635
+ }
636
+ }
637
+ function closeRemainingStyles(target) {
638
+ for (let i = target.openStyles.length - 1; i >= 0; i -= 1) {
639
+ const open = target.openStyles[i];
640
+ const end = target.text.length;
641
+ if (end > open.start) {
642
+ target.styles.push({
643
+ start: open.start,
644
+ end,
645
+ style: open.style,
646
+ });
647
+ }
648
+ }
649
+ target.openStyles = [];
650
+ }
651
+ function clampStyleSpans(spans, maxLength) {
652
+ const clamped = [];
653
+ for (const span of spans) {
654
+ const start = Math.max(0, Math.min(span.start, maxLength));
655
+ const end = Math.max(start, Math.min(span.end, maxLength));
656
+ if (end > start) {
657
+ clamped.push({ start, end, style: span.style });
658
+ }
659
+ }
660
+ return clamped;
661
+ }
662
+ function clampLinkSpans(spans, maxLength) {
663
+ const clamped = [];
664
+ for (const span of spans) {
665
+ const start = Math.max(0, Math.min(span.start, maxLength));
666
+ const end = Math.max(start, Math.min(span.end, maxLength));
667
+ if (end > start) {
668
+ clamped.push({ start, end, href: span.href });
669
+ }
670
+ }
671
+ return clamped;
672
+ }
673
+ function mergeStyleSpans(spans) {
674
+ const sorted = [...spans].toSorted((a, b) => {
675
+ if (a.start !== b.start) {
676
+ return a.start - b.start;
677
+ }
678
+ if (a.end !== b.end) {
679
+ return a.end - b.end;
680
+ }
681
+ return a.style.localeCompare(b.style);
682
+ });
683
+ const merged = [];
684
+ for (const span of sorted) {
685
+ const prev = merged[merged.length - 1];
686
+ if (prev && prev.style === span.style && span.start <= prev.end) {
687
+ prev.end = Math.max(prev.end, span.end);
688
+ continue;
689
+ }
690
+ merged.push({ ...span });
691
+ }
692
+ return merged;
693
+ }
694
+ function sliceStyleSpans(spans, start, end) {
695
+ if (spans.length === 0) {
696
+ return [];
697
+ }
698
+ const sliced = [];
699
+ for (const span of spans) {
700
+ const sliceStart = Math.max(span.start, start);
701
+ const sliceEnd = Math.min(span.end, end);
702
+ if (sliceEnd > sliceStart) {
703
+ sliced.push({
704
+ start: sliceStart - start,
705
+ end: sliceEnd - start,
706
+ style: span.style,
707
+ });
708
+ }
709
+ }
710
+ return mergeStyleSpans(sliced);
711
+ }
712
+ function sliceLinkSpans(spans, start, end) {
713
+ if (spans.length === 0) {
714
+ return [];
715
+ }
716
+ const sliced = [];
717
+ for (const span of spans) {
718
+ const sliceStart = Math.max(span.start, start);
719
+ const sliceEnd = Math.min(span.end, end);
720
+ if (sliceEnd > sliceStart) {
721
+ sliced.push({
722
+ start: sliceStart - start,
723
+ end: sliceEnd - start,
724
+ href: span.href,
725
+ });
726
+ }
727
+ }
728
+ return sliced;
729
+ }
730
+ export function markdownToIR(markdown, options = {}) {
731
+ return markdownToIRWithMeta(markdown, options).ir;
732
+ }
733
+ export function markdownToIRWithMeta(markdown, options = {}) {
734
+ const env = { listStack: [] };
735
+ const md = createMarkdownIt(options);
736
+ const tokens = md.parse(markdown ?? "", env);
737
+ if (options.enableSpoilers) {
738
+ applySpoilerTokens(tokens);
739
+ }
740
+ const tableMode = options.tableMode ?? "off";
741
+ const state = {
742
+ text: "",
743
+ styles: [],
744
+ openStyles: [],
745
+ links: [],
746
+ linkStack: [],
747
+ env,
748
+ headingStyle: options.headingStyle ?? "none",
749
+ blockquotePrefix: options.blockquotePrefix ?? "",
750
+ enableSpoilers: options.enableSpoilers ?? false,
751
+ tableMode,
752
+ table: null,
753
+ hasTables: false,
754
+ };
755
+ renderTokens(tokens, state);
756
+ closeRemainingStyles(state);
757
+ const trimmedText = state.text.trimEnd();
758
+ const trimmedLength = trimmedText.length;
759
+ let codeBlockEnd = 0;
760
+ for (const span of state.styles) {
761
+ if (span.style !== "code_block") {
762
+ continue;
763
+ }
764
+ if (span.end > codeBlockEnd) {
765
+ codeBlockEnd = span.end;
766
+ }
767
+ }
768
+ const finalLength = Math.max(trimmedLength, codeBlockEnd);
769
+ const finalText = finalLength === state.text.length ? state.text : state.text.slice(0, finalLength);
770
+ return {
771
+ ir: {
772
+ text: finalText,
773
+ styles: mergeStyleSpans(clampStyleSpans(state.styles, finalLength)),
774
+ links: clampLinkSpans(state.links, finalLength),
775
+ },
776
+ hasTables: state.hasTables,
777
+ };
778
+ }
779
+ /**
780
+ * Split an IR into chunks whose `text` length does not exceed `limit`.
781
+ *
782
+ * Chunking strategy (applied in priority order):
783
+ * 1. Paragraph boundary (`\n\n`): preferred split point; keeps semantic units
784
+ * together and produces the most natural message breaks.
785
+ * 2. Line break (`\n`): used when no paragraph boundary falls in the latter
786
+ * 70% of the window.
787
+ * 3. Word boundary (space): last resort for very long lines with no newlines.
788
+ * 4. Hard cut at `limit`: only when no whitespace is available at all.
789
+ *
790
+ * Key invariant: a chunk boundary is never placed inside a style span. The
791
+ * sliceStyleSpans / sliceLinkSpans helpers remap span coordinates to each
792
+ * chunk's local origin, so every output IR is self-consistent.
793
+ */
794
+ export function chunkMarkdownIR(ir, limit) {
795
+ if (!ir.text) {
796
+ return [];
797
+ }
798
+ if (limit <= 0 || ir.text.length <= limit) {
799
+ return [ir];
800
+ }
801
+ const chunks = chunkText(ir.text, limit);
802
+ const results = [];
803
+ let cursor = 0;
804
+ chunks.forEach((chunk, index) => {
805
+ if (!chunk) {
806
+ return;
807
+ }
808
+ if (index > 0) {
809
+ while (cursor < ir.text.length && /\s/.test(ir.text[cursor] ?? "")) {
810
+ cursor += 1;
811
+ }
812
+ }
813
+ const start = cursor;
814
+ const end = Math.min(ir.text.length, start + chunk.length);
815
+ results.push({
816
+ text: chunk,
817
+ styles: sliceStyleSpans(ir.styles, start, end),
818
+ links: sliceLinkSpans(ir.links, start, end),
819
+ });
820
+ cursor = end;
821
+ });
822
+ return results;
823
+ }
824
+ //# sourceMappingURL=ir.js.map