@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.
- package/Dockerfile +27 -0
- package/README.md +91 -51
- package/config.example.yml +265 -0
- package/dist/active-hours.d.ts +23 -0
- package/dist/active-hours.d.ts.map +1 -0
- package/dist/active-hours.js +68 -0
- package/dist/active-hours.js.map +1 -0
- package/dist/agent.d.ts +84 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +383 -0
- package/dist/agent.js.map +1 -0
- package/dist/channels/markdown/ir.d.ts +79 -0
- package/dist/channels/markdown/ir.d.ts.map +1 -0
- package/dist/channels/markdown/ir.js +824 -0
- package/dist/channels/markdown/ir.js.map +1 -0
- package/dist/channels/markdown/render.d.ts +35 -0
- package/dist/channels/markdown/render.d.ts.map +1 -0
- package/dist/channels/markdown/render.js +178 -0
- package/dist/channels/markdown/render.js.map +1 -0
- package/dist/channels/telegram-network-errors.d.ts +27 -0
- package/dist/channels/telegram-network-errors.d.ts.map +1 -0
- package/dist/channels/telegram-network-errors.js +156 -0
- package/dist/channels/telegram-network-errors.js.map +1 -0
- package/dist/channels/telegram.d.ts +76 -0
- package/dist/channels/telegram.d.ts.map +1 -0
- package/dist/channels/telegram.js +814 -0
- package/dist/channels/telegram.js.map +1 -0
- package/dist/channels/types.d.ts +59 -0
- package/dist/channels/types.d.ts.map +1 -0
- package/dist/channels/types.js +9 -0
- package/dist/channels/types.js.map +1 -0
- package/dist/channels/whatsapp.d.ts +45 -0
- package/dist/channels/whatsapp.d.ts.map +1 -0
- package/dist/channels/whatsapp.js +310 -0
- package/dist/channels/whatsapp.js.map +1 -0
- package/dist/cli.d.ts +13 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +635 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands.d.ts +35 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/commands.js +113 -0
- package/dist/commands.js.map +1 -0
- package/dist/compaction/history.d.ts +17 -0
- package/dist/compaction/history.d.ts.map +1 -0
- package/dist/compaction/history.js +35 -0
- package/dist/compaction/history.js.map +1 -0
- package/dist/compaction/index.d.ts +3 -0
- package/dist/compaction/index.d.ts.map +1 -0
- package/dist/compaction/index.js +3 -0
- package/dist/compaction/index.js.map +1 -0
- package/dist/compaction/proactive.d.ts +25 -0
- package/dist/compaction/proactive.d.ts.map +1 -0
- package/dist/compaction/proactive.js +87 -0
- package/dist/compaction/proactive.js.map +1 -0
- package/dist/compaction/transcript-repair.d.ts +55 -0
- package/dist/compaction/transcript-repair.d.ts.map +1 -0
- package/dist/compaction/transcript-repair.js +215 -0
- package/dist/compaction/transcript-repair.js.map +1 -0
- package/dist/config.d.ts +128 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +317 -0
- package/dist/config.js.map +1 -0
- package/dist/crash-recovery.d.ts +23 -0
- package/dist/crash-recovery.d.ts.map +1 -0
- package/dist/crash-recovery.js +96 -0
- package/dist/crash-recovery.js.map +1 -0
- package/dist/defaults/extensions/EXTENSIONS.md +158 -0
- package/dist/defaults/extensions/documents-hedgedoc.ts +153 -0
- package/dist/history.d.ts +31 -0
- package/dist/history.d.ts.map +1 -0
- package/dist/history.js +49 -0
- package/dist/history.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +686 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +39 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +143 -0
- package/dist/logger.js.map +1 -0
- package/dist/memory/conversation-search.d.ts +15 -0
- package/dist/memory/conversation-search.d.ts.map +1 -0
- package/dist/memory/conversation-search.js +60 -0
- package/dist/memory/conversation-search.js.map +1 -0
- package/dist/memory/core-memory.d.ts +28 -0
- package/dist/memory/core-memory.d.ts.map +1 -0
- package/dist/memory/core-memory.js +102 -0
- package/dist/memory/core-memory.js.map +1 -0
- package/dist/memory/embeddings.d.ts +44 -0
- package/dist/memory/embeddings.d.ts.map +1 -0
- package/dist/memory/embeddings.js +139 -0
- package/dist/memory/embeddings.js.map +1 -0
- package/dist/memory/search.d.ts +49 -0
- package/dist/memory/search.d.ts.map +1 -0
- package/dist/memory/search.js +97 -0
- package/dist/memory/search.js.map +1 -0
- package/dist/memory/store.d.ts +32 -0
- package/dist/memory/store.d.ts.map +1 -0
- package/dist/memory/store.js +205 -0
- package/dist/memory/store.js.map +1 -0
- package/dist/message-queue.d.ts +73 -0
- package/dist/message-queue.d.ts.map +1 -0
- package/dist/message-queue.js +188 -0
- package/dist/message-queue.js.map +1 -0
- package/dist/model-infra.d.ts +64 -0
- package/dist/model-infra.d.ts.map +1 -0
- package/dist/model-infra.js +202 -0
- package/dist/model-infra.js.map +1 -0
- package/dist/projection/format.d.ts +10 -0
- package/dist/projection/format.d.ts.map +1 -0
- package/dist/projection/format.js +30 -0
- package/dist/projection/format.js.map +1 -0
- package/dist/projection/index.d.ts +11 -0
- package/dist/projection/index.d.ts.map +1 -0
- package/dist/projection/index.js +9 -0
- package/dist/projection/index.js.map +1 -0
- package/dist/projection/reflection.d.ts +119 -0
- package/dist/projection/reflection.d.ts.map +1 -0
- package/dist/projection/reflection.js +422 -0
- package/dist/projection/reflection.js.map +1 -0
- package/dist/projection/store.d.ts +144 -0
- package/dist/projection/store.d.ts.map +1 -0
- package/dist/projection/store.js +519 -0
- package/dist/projection/store.js.map +1 -0
- package/dist/projection/tools.d.ts +11 -0
- package/dist/projection/tools.d.ts.map +1 -0
- package/dist/projection/tools.js +237 -0
- package/dist/projection/tools.js.map +1 -0
- package/dist/scheduler.d.ts +36 -0
- package/dist/scheduler.d.ts.map +1 -0
- package/dist/scheduler.js +286 -0
- package/dist/scheduler.js.map +1 -0
- package/dist/system-prompt.d.ts +41 -0
- package/dist/system-prompt.d.ts.map +1 -0
- package/dist/system-prompt.js +162 -0
- package/dist/system-prompt.js.map +1 -0
- package/dist/time.d.ts +52 -0
- package/dist/time.d.ts.map +1 -0
- package/dist/time.js +138 -0
- package/dist/time.js.map +1 -0
- package/dist/tools/archival-memory-tool.d.ts +8 -0
- package/dist/tools/archival-memory-tool.d.ts.map +1 -0
- package/dist/tools/archival-memory-tool.js +68 -0
- package/dist/tools/archival-memory-tool.js.map +1 -0
- package/dist/tools/conversation-search-tool.d.ts +6 -0
- package/dist/tools/conversation-search-tool.d.ts.map +1 -0
- package/dist/tools/conversation-search-tool.js +28 -0
- package/dist/tools/conversation-search-tool.js.map +1 -0
- package/dist/tools/core-memory-tool.d.ts +7 -0
- package/dist/tools/core-memory-tool.d.ts.map +1 -0
- package/dist/tools/core-memory-tool.js +59 -0
- package/dist/tools/core-memory-tool.js.map +1 -0
- package/dist/tools/fetch-url.d.ts +15 -0
- package/dist/tools/fetch-url.d.ts.map +1 -0
- package/dist/tools/fetch-url.js +76 -0
- package/dist/tools/fetch-url.js.map +1 -0
- package/dist/tools/files.d.ts +10 -0
- package/dist/tools/files.d.ts.map +1 -0
- package/dist/tools/files.js +127 -0
- package/dist/tools/files.js.map +1 -0
- package/dist/tools/index.d.ts +17 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +118 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/result.d.ts +21 -0
- package/dist/tools/result.d.ts.map +1 -0
- package/dist/tools/result.js +36 -0
- package/dist/tools/result.js.map +1 -0
- package/dist/tools/skill-install.d.ts +17 -0
- package/dist/tools/skill-install.d.ts.map +1 -0
- package/dist/tools/skill-install.js +148 -0
- package/dist/tools/skill-install.js.map +1 -0
- package/dist/tools/web-search.d.ts +42 -0
- package/dist/tools/web-search.d.ts.map +1 -0
- package/dist/tools/web-search.js +237 -0
- package/dist/tools/web-search.js.map +1 -0
- package/dist/trust/guardrail.d.ts +60 -0
- package/dist/trust/guardrail.d.ts.map +1 -0
- package/dist/trust/guardrail.js +171 -0
- package/dist/trust/guardrail.js.map +1 -0
- package/dist/trust/index.d.ts +12 -0
- package/dist/trust/index.d.ts.map +1 -0
- package/dist/trust/index.js +12 -0
- package/dist/trust/index.js.map +1 -0
- package/dist/trust/store.d.ts +118 -0
- package/dist/trust/store.d.ts.map +1 -0
- package/dist/trust/store.js +209 -0
- package/dist/trust/store.js.map +1 -0
- package/dist/trust/wrapper.d.ts +36 -0
- package/dist/trust/wrapper.d.ts.map +1 -0
- package/dist/trust/wrapper.js +142 -0
- package/dist/trust/wrapper.js.map +1 -0
- package/dist/usage.d.ts +53 -0
- package/dist/usage.d.ts.map +1 -0
- package/dist/usage.js +124 -0
- package/dist/usage.js.map +1 -0
- package/dist/util/math.d.ts +9 -0
- package/dist/util/math.d.ts.map +1 -0
- package/dist/util/math.js +22 -0
- package/dist/util/math.js.map +1 -0
- package/dist/util/ssrf.d.ts +21 -0
- package/dist/util/ssrf.d.ts.map +1 -0
- package/dist/util/ssrf.js +77 -0
- package/dist/util/ssrf.js.map +1 -0
- package/dist/workers/index.d.ts +8 -0
- package/dist/workers/index.d.ts.map +1 -0
- package/dist/workers/index.js +7 -0
- package/dist/workers/index.js.map +1 -0
- package/dist/workers/registry.d.ts +53 -0
- package/dist/workers/registry.d.ts.map +1 -0
- package/dist/workers/registry.js +38 -0
- package/dist/workers/registry.js.map +1 -0
- package/dist/workers/scoped-tools.d.ts +21 -0
- package/dist/workers/scoped-tools.d.ts.map +1 -0
- package/dist/workers/scoped-tools.js +111 -0
- package/dist/workers/scoped-tools.js.map +1 -0
- package/dist/workers/spawn.d.ts +62 -0
- package/dist/workers/spawn.d.ts.map +1 -0
- package/dist/workers/spawn.js +314 -0
- package/dist/workers/spawn.js.map +1 -0
- package/dist/workers/tools.d.ts +26 -0
- package/dist/workers/tools.d.ts.map +1 -0
- package/dist/workers/tools.js +380 -0
- package/dist/workers/tools.js.map +1 -0
- package/docker-compose.yml +72 -0
- package/package.json +16 -1
- 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
|