agentic-documents 0.4.0 → 0.6.0
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/CHANGELOG.md +56 -0
- package/README.md +161 -11
- package/dist/{chunk-TFCRPEEM.js → chunk-34C47PDC.js} +1 -1
- package/dist/chunk-5IQX2W52.js +195 -0
- package/dist/chunk-6UYJV2EJ.js +163 -0
- package/dist/{chunk-R5XUTBC3.js → chunk-CN6UAW7B.js} +32 -9
- package/dist/chunk-DWVJIKVT.js +8 -0
- package/dist/{chunk-YBBQNCMC.js → chunk-GQ5E6T7Z.js} +18 -191
- package/dist/{chunk-FZA5LQV3.js → chunk-JBAQEIOH.js} +89 -12
- package/dist/chunk-KC5KFB5R.js +354 -0
- package/dist/{chunk-IU3YIQ4Y.js → chunk-V6TBTOHW.js} +38 -13
- package/dist/cli.js +16 -12
- package/dist/client.d.ts +23 -6
- package/dist/client.js +9 -2
- package/dist/{doc-C_8kCsl7.d.ts → doc-BXG9qG5X.d.ts} +8 -2
- package/dist/editor.css +37 -0
- package/dist/embed.d.ts +86 -0
- package/dist/embed.js +431 -0
- package/dist/formats.d.ts +52 -0
- package/dist/formats.js +24 -0
- package/dist/index.d.ts +12 -10
- package/dist/index.js +15 -9
- package/dist/live-worker.d.ts +2 -2
- package/dist/live-worker.js +7 -4
- package/dist/live.d.ts +6 -2
- package/dist/live.js +5 -3
- package/dist/{mcp-P2C75IIG.js → mcp-ZTUTC4CS.js} +5 -2
- package/dist/render-463xHb-x.d.ts +16 -0
- package/dist/{review-BQW2Ci2h.d.ts → review-Ds-i5Vnc.d.ts} +2 -16
- package/dist/{service-Do2Jwb5U.d.ts → service-Ck6oX-Kq.d.ts} +38 -5
- package/dist/ui/_headers +4 -0
- package/dist/ui/app.css +1 -0
- package/dist/ui/app.js +270 -0
- package/dist/ui/assets/inter-latin-ext-wght-normal-HA22NDSG.woff2 +0 -0
- package/dist/ui/assets/inter-latin-wght-normal-NRMW37G5.woff2 +0 -0
- package/dist/ui/assets/jetbrains-mono-latin-ext-wght-normal-TSLVDSQR.woff2 +0 -0
- package/dist/ui/assets/jetbrains-mono-latin-wght-normal-VBS632QH.woff2 +0 -0
- package/dist/ui/index.html +13 -0
- package/dist/worker.d.ts +2 -2
- package/dist/worker.js +7 -4
- package/llms.txt +1 -0
- package/package.json +43 -10
- package/dist/chunk-GYQFTF2E.js +0 -389
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AgenticDoc,
|
|
3
|
+
DocError,
|
|
4
|
+
checkAuthor
|
|
5
|
+
} from "./chunk-GQ5E6T7Z.js";
|
|
6
|
+
|
|
7
|
+
// src/formats.ts
|
|
8
|
+
import { DEFAULT_LIMITS, FormatError, readDocx, readOdt, writeDocx, writeOdt } from "agentic-formats";
|
|
9
|
+
|
|
10
|
+
// src/render.ts
|
|
11
|
+
var EXPORT_FORMATS = ["md", "html", "odt"];
|
|
12
|
+
var esc = (s) => String(s ?? "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
13
|
+
var url = (value) => /^(https?:|mailto:|[^:]*$)/i.test(String(value ?? "").trim()) ? esc(String(value).trim()) : "#";
|
|
14
|
+
var integer = (value, fallback) => Number.isFinite(Number(value)) ? Math.trunc(Number(value)) : fallback;
|
|
15
|
+
var INLINE_IMAGE = /^data:image\/(png|jpeg|gif|webp);base64,[a-z0-9+/=]+$/i;
|
|
16
|
+
var textOf = (node) => node.text ?? (node.content ?? []).map(textOf).join("");
|
|
17
|
+
var MARK_TAGS = { bold: "strong", italic: "em", strike: "s", code: "code" };
|
|
18
|
+
function image(node, remote) {
|
|
19
|
+
const src = String(node.attrs?.src ?? "").trim();
|
|
20
|
+
const alt = esc(node.attrs?.alt);
|
|
21
|
+
if (INLINE_IMAGE.test(src)) return `<img src="${src}" alt="${alt}">`;
|
|
22
|
+
if (remote && url(src) !== "#") return `<img src="${url(src)}" alt="${alt}">`;
|
|
23
|
+
return `<span class="image">[image${alt ? `: ${alt}` : ""}]</span>`;
|
|
24
|
+
}
|
|
25
|
+
function inline(node, remote) {
|
|
26
|
+
if (node.type === "hardBreak") return "<br>";
|
|
27
|
+
if (node.type === "image") return image(node, remote);
|
|
28
|
+
return (node.marks ?? []).reduce((html, mark) => {
|
|
29
|
+
if (mark.type === "link") return `<a href="${url(mark.attrs?.href)}">${html}</a>`;
|
|
30
|
+
const tag = MARK_TAGS[mark.type];
|
|
31
|
+
return tag ? `<${tag}>${html}</${tag}>` : html;
|
|
32
|
+
}, esc(node.text));
|
|
33
|
+
}
|
|
34
|
+
function block(node, remote) {
|
|
35
|
+
const kids = (node.content ?? []).map((n) => block(n, remote)).join("");
|
|
36
|
+
const text = (node.content ?? []).map((n) => inline(n, remote)).join("");
|
|
37
|
+
switch (node.type) {
|
|
38
|
+
case "paragraph":
|
|
39
|
+
return `<p>${text}</p>
|
|
40
|
+
`;
|
|
41
|
+
case "heading": {
|
|
42
|
+
const level = Math.min(6, Math.max(1, integer(node.attrs?.level, 1)));
|
|
43
|
+
return `<h${level}>${text}</h${level}>
|
|
44
|
+
`;
|
|
45
|
+
}
|
|
46
|
+
case "blockquote":
|
|
47
|
+
return `<blockquote>
|
|
48
|
+
${kids}</blockquote>
|
|
49
|
+
`;
|
|
50
|
+
case "codeBlock": {
|
|
51
|
+
const lang = node.attrs?.language ? ` class="language-${esc(node.attrs.language)}"` : "";
|
|
52
|
+
return `<pre><code${lang}>${esc(textOf(node))}</code></pre>
|
|
53
|
+
`;
|
|
54
|
+
}
|
|
55
|
+
case "horizontalRule":
|
|
56
|
+
return "<hr>\n";
|
|
57
|
+
case "bulletList":
|
|
58
|
+
case "taskList":
|
|
59
|
+
return `<ul>
|
|
60
|
+
${kids}</ul>
|
|
61
|
+
`;
|
|
62
|
+
case "orderedList":
|
|
63
|
+
return `<ol start="${integer(node.attrs?.start, 1)}">
|
|
64
|
+
${kids}</ol>
|
|
65
|
+
`;
|
|
66
|
+
case "listItem":
|
|
67
|
+
return `<li>${kids}</li>
|
|
68
|
+
`;
|
|
69
|
+
case "taskItem":
|
|
70
|
+
return `<li><input type="checkbox" disabled${node.attrs?.checked ? " checked" : ""}> ${kids}</li>
|
|
71
|
+
`;
|
|
72
|
+
case "table":
|
|
73
|
+
return `<table>
|
|
74
|
+
${kids}</table>
|
|
75
|
+
`;
|
|
76
|
+
case "tableRow":
|
|
77
|
+
return `<tr>${kids}</tr>
|
|
78
|
+
`;
|
|
79
|
+
case "tableHeader":
|
|
80
|
+
return `<th>${kids}</th>`;
|
|
81
|
+
case "tableCell":
|
|
82
|
+
return `<td>${kids}</td>`;
|
|
83
|
+
default:
|
|
84
|
+
return "";
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function toHtml(blocks, remote) {
|
|
88
|
+
const title = textOf(blocks.find((b) => b.type === "heading") ?? { type: "doc" }) || "Document";
|
|
89
|
+
const css = "body{font-family:system-ui,sans-serif;max-width:46rem;margin:2rem auto;padding:0 1rem;line-height:1.5}table{border-collapse:collapse}td,th{border:1px solid #888;padding:.25rem .5rem}pre{overflow:auto}";
|
|
90
|
+
return `<!doctype html>
|
|
91
|
+
<html lang="en">
|
|
92
|
+
<head>
|
|
93
|
+
<meta charset="utf-8">
|
|
94
|
+
<title>${esc(title)}</title>
|
|
95
|
+
<style>${css}</style>
|
|
96
|
+
</head>
|
|
97
|
+
<body>
|
|
98
|
+
${blocks.map((b) => block(b, remote)).join("")}</body>
|
|
99
|
+
</html>
|
|
100
|
+
`;
|
|
101
|
+
}
|
|
102
|
+
function exportText(doc, format, options = {}) {
|
|
103
|
+
return format === "html" ? toHtml(doc.toJSON().content ?? [], options.remoteImages === true) : doc.toMarkdown();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// src/package.ts
|
|
107
|
+
var DATA_IMAGE = /^data:(image\/(?:png|jpeg|gif|webp));base64,([a-z0-9+/=]+)$/i;
|
|
108
|
+
function toBase64(bytes) {
|
|
109
|
+
let text = "";
|
|
110
|
+
for (let i = 0; i < bytes.length; i += 32768) text += String.fromCharCode(...bytes.subarray(i, i + 32768));
|
|
111
|
+
return btoa(text);
|
|
112
|
+
}
|
|
113
|
+
var fromBase64 = (value) => Uint8Array.from(atob(value), (c) => c.charCodeAt(0));
|
|
114
|
+
|
|
115
|
+
// src/xml.ts
|
|
116
|
+
var safeHref = (value) => {
|
|
117
|
+
const href = String(value ?? "").trim();
|
|
118
|
+
return /^(https?:|mailto:|#|[^:]*$)/i.test(href) ? href : void 0;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
// src/formats.ts
|
|
122
|
+
var IMPORT_FORMATS = ["md", "odt", "docx"];
|
|
123
|
+
var FILE_FORMATS = ["md", "html", "odt", "docx"];
|
|
124
|
+
var DOCX_MIME = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
|
|
125
|
+
var CONTENT_TYPES = {
|
|
126
|
+
md: "text/markdown; charset=utf-8",
|
|
127
|
+
html: "text/html; charset=utf-8",
|
|
128
|
+
odt: "application/vnd.oasis.opendocument.text",
|
|
129
|
+
docx: DOCX_MIME
|
|
130
|
+
};
|
|
131
|
+
var PM_MARKS = { bold: "bold", italic: "italic", strike: "strike", code: "code" };
|
|
132
|
+
var MODEL_IMAGE = /^image\/(png|jpeg|gif)$/;
|
|
133
|
+
var TASK = ["\u2610 ", "\u2611 "];
|
|
134
|
+
var warner = () => {
|
|
135
|
+
const seen = /* @__PURE__ */ new Set();
|
|
136
|
+
return { warn: (line) => void seen.add(line), list: () => [...seen] };
|
|
137
|
+
};
|
|
138
|
+
function toTextDoc(blocks, title) {
|
|
139
|
+
const { warn, list } = warner();
|
|
140
|
+
const inlines = (nodes = [], extra = []) => nodes.flatMap((node) => {
|
|
141
|
+
if (node.type === "hardBreak") return [{ type: "break" }];
|
|
142
|
+
if (node.type === "image") {
|
|
143
|
+
const alt = typeof node.attrs?.alt === "string" ? node.attrs.alt : "";
|
|
144
|
+
const found = DATA_IMAGE.exec(String(node.attrs?.src ?? "").trim());
|
|
145
|
+
if (found && MODEL_IMAGE.test(found[1].toLowerCase())) {
|
|
146
|
+
try {
|
|
147
|
+
return [{ type: "image", data: fromBase64(found[2]), mime: found[1].toLowerCase(), ...alt ? { alt } : {} }];
|
|
148
|
+
} catch {
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
warn("an image that is not an embedded PNG, JPEG, or GIF is kept as its alt text");
|
|
152
|
+
return [{ type: "text", text: `[image${alt ? `: ${alt}` : ""}]` }];
|
|
153
|
+
}
|
|
154
|
+
if (!node.text) return [];
|
|
155
|
+
const marks = [...extra, ...(node.marks ?? []).flatMap((m) => PM_MARKS[m.type] ? [PM_MARKS[m.type]] : [])];
|
|
156
|
+
const href = safeHref((node.marks ?? []).find((m) => m.type === "link")?.attrs?.href);
|
|
157
|
+
return [{ type: "text", text: node.text, ...marks.length ? { marks } : {}, ...href ? { href } : {} }];
|
|
158
|
+
});
|
|
159
|
+
const listOf = (node) => {
|
|
160
|
+
const items = (node.content ?? []).map((item) => {
|
|
161
|
+
const [first, ...rest] = item.content ?? [];
|
|
162
|
+
const content = inlines(first?.content);
|
|
163
|
+
if (item.type === "taskItem") content.unshift({ type: "text", text: TASK[item.attrs?.checked ? 1 : 0] });
|
|
164
|
+
let sublist;
|
|
165
|
+
for (const child of rest) {
|
|
166
|
+
if (!sublist && /^(bulletList|orderedList|taskList)$/.test(child.type)) sublist = listOf(child);
|
|
167
|
+
else {
|
|
168
|
+
warn("a list item with more than one paragraph is kept as one paragraph with line breaks");
|
|
169
|
+
for (const b of toBlocks(child)) content.push({ type: "break" }, ...textOfBlock(b));
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return { content, ...sublist ? { sublist } : {} };
|
|
173
|
+
});
|
|
174
|
+
if (node.type === "orderedList" && Number(node.attrs?.start ?? 1) !== 1) warn("a numbered list that does not start at 1 starts at 1");
|
|
175
|
+
return { type: "list", ordered: node.type === "orderedList", items };
|
|
176
|
+
};
|
|
177
|
+
const textOfBlock = (b) => b.type === "list" ? b.items.flatMap((i, n) => [...n ? [{ type: "break" }] : [], ...i.content]) : b.type === "table" ? b.rows.flat(2).flatMap(textOfBlock) : b.content;
|
|
178
|
+
const toBlocks = (node) => {
|
|
179
|
+
switch (node.type) {
|
|
180
|
+
case "paragraph":
|
|
181
|
+
return [{ type: "paragraph", content: inlines(node.content) }];
|
|
182
|
+
case "heading": {
|
|
183
|
+
const level = Math.min(6, Math.max(1, Math.trunc(Number(node.attrs?.level)) || 1));
|
|
184
|
+
return [{ type: "heading", level, content: inlines(node.content) }];
|
|
185
|
+
}
|
|
186
|
+
case "blockquote":
|
|
187
|
+
warn("a quote is kept as plain paragraphs");
|
|
188
|
+
return (node.content ?? []).flatMap(toBlocks);
|
|
189
|
+
case "codeBlock": {
|
|
190
|
+
warn("a code block is kept as a paragraph of code text");
|
|
191
|
+
const lines = (node.content ?? []).map((t) => t.text ?? "").join("").split("\n");
|
|
192
|
+
return [{ type: "paragraph", content: lines.flatMap((line, i) => [...i ? [{ type: "break" }] : [], ...line ? [{ type: "text", text: line, marks: ["code"] }] : []]) }];
|
|
193
|
+
}
|
|
194
|
+
case "horizontalRule":
|
|
195
|
+
warn("a horizontal rule is left out");
|
|
196
|
+
return [];
|
|
197
|
+
case "bulletList":
|
|
198
|
+
case "orderedList":
|
|
199
|
+
case "taskList":
|
|
200
|
+
return [listOf(node)];
|
|
201
|
+
case "table": {
|
|
202
|
+
const rows = node.content ?? [];
|
|
203
|
+
const header = rows.length > 0 && (rows[0].content ?? []).length > 0 && (rows[0].content ?? []).every((c) => c.type === "tableHeader");
|
|
204
|
+
return [{ type: "table", ...header ? { header: true } : {}, rows: rows.map((r) => (r.content ?? []).map((c) => (c.content ?? []).flatMap(toBlocks))) }];
|
|
205
|
+
}
|
|
206
|
+
default:
|
|
207
|
+
warn(`a ${node.type} block is left out`);
|
|
208
|
+
return [];
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
const model = { ...title ? { title } : {}, blocks: blocks.flatMap(toBlocks) };
|
|
212
|
+
return { model, warnings: list() };
|
|
213
|
+
}
|
|
214
|
+
function fromTextDoc(model) {
|
|
215
|
+
const { warn, list } = warner();
|
|
216
|
+
const text = (value, marks = []) => value ? [{ type: "text", text: value, ...marks.length ? { marks } : {} }] : [];
|
|
217
|
+
const inlines = (nodes) => nodes.flatMap((node) => {
|
|
218
|
+
switch (node.type) {
|
|
219
|
+
case "break":
|
|
220
|
+
return [{ type: "hardBreak" }];
|
|
221
|
+
case "image":
|
|
222
|
+
if (!MODEL_IMAGE.test(node.mime)) {
|
|
223
|
+
warn("an image that is not a PNG, JPEG, or GIF is kept as its alt text");
|
|
224
|
+
return text(`[image${node.alt ? `: ${node.alt}` : ""}]`);
|
|
225
|
+
}
|
|
226
|
+
return [{ type: "image", attrs: { src: `data:${node.mime};base64,${toBase64(node.data)}`, alt: node.alt ?? null, title: null } }];
|
|
227
|
+
case "footnote":
|
|
228
|
+
warn("a footnote is kept as text in brackets");
|
|
229
|
+
return [...text(" ["), ...inlines(node.content), ...text("]")];
|
|
230
|
+
case "text": {
|
|
231
|
+
const marks = [];
|
|
232
|
+
for (const mark of node.marks ?? []) {
|
|
233
|
+
if (mark === "bold" || mark === "italic" || mark === "strike" || mark === "code") marks.push({ type: mark });
|
|
234
|
+
else warn(`the ${mark} mark is left out; the text stays`);
|
|
235
|
+
}
|
|
236
|
+
const href = node.href === void 0 ? void 0 : safeHref(node.href);
|
|
237
|
+
if (node.href !== void 0 && !href) warn("a link that is not a web, mail, or relative address is left out; the text stays");
|
|
238
|
+
if (href) marks.push({ type: "link", attrs: { href } });
|
|
239
|
+
if (marks.length > 1 && marks.some((m) => m.type === "code")) {
|
|
240
|
+
warn("a mark or a link on code text is left out; the text stays as code");
|
|
241
|
+
return text(node.text, [{ type: "code" }]);
|
|
242
|
+
}
|
|
243
|
+
return text(node.text, marks);
|
|
244
|
+
}
|
|
245
|
+
default:
|
|
246
|
+
return [];
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
const paragraph = (content) => ({ type: "paragraph", ...content.length ? { content: inlines(content) } : {} });
|
|
250
|
+
const toNodes = (block2) => {
|
|
251
|
+
switch (block2.type) {
|
|
252
|
+
case "heading":
|
|
253
|
+
return [{ type: "heading", attrs: { level: block2.level }, content: inlines(block2.content) }];
|
|
254
|
+
case "paragraph":
|
|
255
|
+
return [paragraph(block2.content)];
|
|
256
|
+
case "list":
|
|
257
|
+
return [
|
|
258
|
+
{
|
|
259
|
+
type: block2.ordered ? "orderedList" : "bulletList",
|
|
260
|
+
...block2.ordered ? { attrs: { start: 1, type: null } } : {},
|
|
261
|
+
content: block2.items.map((item) => ({ type: "listItem", content: [paragraph(item.content), ...item.sublist ? toNodes(item.sublist) : []] }))
|
|
262
|
+
}
|
|
263
|
+
];
|
|
264
|
+
case "table": {
|
|
265
|
+
const rows = block2.rows.filter((r) => r.length > 0);
|
|
266
|
+
if (rows.length === 0) return [];
|
|
267
|
+
return [
|
|
268
|
+
{
|
|
269
|
+
type: "table",
|
|
270
|
+
content: rows.map((row, i) => ({
|
|
271
|
+
type: "tableRow",
|
|
272
|
+
content: row.map((cell) => {
|
|
273
|
+
const content = cell.flatMap(toNodes);
|
|
274
|
+
return { type: block2.header && i === 0 ? "tableHeader" : "tableCell", attrs: { colspan: 1, rowspan: 1, colwidth: null }, content: content.length ? content : [{ type: "paragraph" }] };
|
|
275
|
+
})
|
|
276
|
+
}))
|
|
277
|
+
}
|
|
278
|
+
];
|
|
279
|
+
}
|
|
280
|
+
default:
|
|
281
|
+
return [];
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
const prune = (node) => node.content ? { ...node, content: node.content.filter((n) => n.type !== "text" || n.text).map(prune) } : node;
|
|
285
|
+
const blocks = model.blocks.flatMap(toNodes).map(prune);
|
|
286
|
+
return { blocks, ...model.title ? { title: model.title } : {}, warnings: list() };
|
|
287
|
+
}
|
|
288
|
+
var formatFailure = (error) => {
|
|
289
|
+
if (error instanceof FormatError) throw new DocError("INVALID_INPUT", `the file was refused (${error.code}): ${error.message}`);
|
|
290
|
+
throw error;
|
|
291
|
+
};
|
|
292
|
+
function importFile(input, format, author, options = {}) {
|
|
293
|
+
checkAuthor(author);
|
|
294
|
+
if (!IMPORT_FORMATS.includes(format)) throw new DocError("INVALID_INPUT", `import format must be one of ${IMPORT_FORMATS.join(", ")}`);
|
|
295
|
+
const maxBytes = options.limits?.maxInputBytes ?? DEFAULT_LIMITS.maxInputBytes;
|
|
296
|
+
if (format === "md") {
|
|
297
|
+
let markdown;
|
|
298
|
+
try {
|
|
299
|
+
markdown = typeof input === "string" ? input : new TextDecoder("utf-8", { fatal: true }).decode(input);
|
|
300
|
+
} catch {
|
|
301
|
+
throw new DocError("INVALID_INPUT", "a md file must be UTF-8 text");
|
|
302
|
+
}
|
|
303
|
+
if (new TextEncoder().encode(markdown).length > maxBytes) throw new DocError("INVALID_INPUT", `the file is larger than ${maxBytes} bytes`);
|
|
304
|
+
return { doc: AgenticDoc.fromMarkdown(markdown, author, options.doc), warnings: [] };
|
|
305
|
+
}
|
|
306
|
+
if (!(input instanceof Uint8Array)) throw new DocError("INVALID_INPUT", `a ${format} file must be bytes, not text`);
|
|
307
|
+
let read;
|
|
308
|
+
try {
|
|
309
|
+
read = format === "odt" ? readOdt(input, options.limits) : readDocx(input, options.limits);
|
|
310
|
+
} catch (error) {
|
|
311
|
+
return formatFailure(error);
|
|
312
|
+
}
|
|
313
|
+
const mapped = fromTextDoc(read.model);
|
|
314
|
+
return {
|
|
315
|
+
doc: AgenticDoc.fromBlocks(mapped.blocks, author, options.doc),
|
|
316
|
+
...mapped.title ? { title: mapped.title } : {},
|
|
317
|
+
warnings: [...read.warnings, ...mapped.warnings]
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
var ODT_MIME = CONTENT_TYPES.odt;
|
|
321
|
+
function exportOdt(blocks) {
|
|
322
|
+
try {
|
|
323
|
+
return writeOdt(toTextDoc(blocks).model).bytes;
|
|
324
|
+
} catch (error) {
|
|
325
|
+
return formatFailure(error);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
function exportFile(doc, format, options = {}) {
|
|
329
|
+
if (!FILE_FORMATS.includes(format)) throw new DocError("INVALID_INPUT", `export format must be one of ${FILE_FORMATS.join(", ")}`);
|
|
330
|
+
if (format === "md" || format === "html") return { format, contentType: CONTENT_TYPES[format], data: exportText(doc, format, options), warnings: [] };
|
|
331
|
+
const { model, warnings } = toTextDoc(doc.toJSON().content ?? [], options.title);
|
|
332
|
+
let written;
|
|
333
|
+
try {
|
|
334
|
+
written = format === "odt" ? writeOdt(model) : writeDocx(model);
|
|
335
|
+
} catch (error) {
|
|
336
|
+
return formatFailure(error);
|
|
337
|
+
}
|
|
338
|
+
return { format, contentType: CONTENT_TYPES[format], data: written.bytes, warnings: [...warnings, ...written.warnings] };
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export {
|
|
342
|
+
EXPORT_FORMATS,
|
|
343
|
+
exportText,
|
|
344
|
+
toBase64,
|
|
345
|
+
IMPORT_FORMATS,
|
|
346
|
+
FILE_FORMATS,
|
|
347
|
+
DOCX_MIME,
|
|
348
|
+
toTextDoc,
|
|
349
|
+
fromTextDoc,
|
|
350
|
+
importFile,
|
|
351
|
+
ODT_MIME,
|
|
352
|
+
exportOdt,
|
|
353
|
+
exportFile
|
|
354
|
+
};
|
|
@@ -1,15 +1,17 @@
|
|
|
1
|
+
import {
|
|
2
|
+
commands
|
|
3
|
+
} from "./chunk-6UYJV2EJ.js";
|
|
1
4
|
import {
|
|
2
5
|
DocsService,
|
|
3
6
|
MAX_LIVE_BYTES
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import {
|
|
6
|
-
commands
|
|
7
|
-
} from "./chunk-GYQFTF2E.js";
|
|
7
|
+
} from "./chunk-JBAQEIOH.js";
|
|
8
8
|
import {
|
|
9
9
|
DocError,
|
|
10
|
-
PRINCIPAL_KINDS,
|
|
11
10
|
parseAuthor
|
|
12
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-GQ5E6T7Z.js";
|
|
12
|
+
import {
|
|
13
|
+
PRINCIPAL_KINDS
|
|
14
|
+
} from "./chunk-DWVJIKVT.js";
|
|
13
15
|
|
|
14
16
|
// src/worker.ts
|
|
15
17
|
import { Hono } from "hono";
|
|
@@ -104,6 +106,22 @@ async function readCapped(request, cap) {
|
|
|
104
106
|
}
|
|
105
107
|
return whole;
|
|
106
108
|
}
|
|
109
|
+
var WS_PROTOCOL = "agentic-docs";
|
|
110
|
+
var WS_BEARER = `${WS_PROTOCOL}.bearer.`;
|
|
111
|
+
function socketBearer(c) {
|
|
112
|
+
if (c.req.header("upgrade") !== "websocket" || !c.req.path.endsWith("/ws")) return void 0;
|
|
113
|
+
for (const offered of (c.req.header("sec-websocket-protocol") ?? "").split(",")) {
|
|
114
|
+
const part = offered.trim();
|
|
115
|
+
if (!part.startsWith(WS_BEARER)) continue;
|
|
116
|
+
try {
|
|
117
|
+
const base64 = part.slice(WS_BEARER.length).replace(/-/g, "+").replace(/_/g, "/");
|
|
118
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(Uint8Array.from(atob(base64), (ch) => ch.charCodeAt(0)));
|
|
119
|
+
} catch {
|
|
120
|
+
return void 0;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return void 0;
|
|
124
|
+
}
|
|
107
125
|
var DOC_ID = /^[a-z0-9][a-z0-9_-]{0,63}$/;
|
|
108
126
|
function createApp(source, options = {}) {
|
|
109
127
|
const app2 = new Hono();
|
|
@@ -113,7 +131,7 @@ function createApp(source, options = {}) {
|
|
|
113
131
|
});
|
|
114
132
|
app2.get("/health", (c) => c.json({ ok: true, pack: "agentic-documents" }));
|
|
115
133
|
app2.use("/api/v1/*", async (c, next) => {
|
|
116
|
-
const bearer = /^Bearer (.+)$/.exec(c.req.header("authorization") ?? "")?.[1];
|
|
134
|
+
const bearer = /^Bearer (.+)$/.exec(c.req.header("authorization") ?? "")?.[1] ?? socketBearer(c);
|
|
117
135
|
const identity = bearer === void 0 ? void 0 : identityFor(c.env?.AGENTIC_DOCS_TOKENS, bearer);
|
|
118
136
|
if (!identity) return fail("UNAUTHENTICATED", "send a valid bearer token", 401);
|
|
119
137
|
c.set("author", identity.principal);
|
|
@@ -130,23 +148,28 @@ function createApp(source, options = {}) {
|
|
|
130
148
|
const { contributors } = await options.live(c.env ?? {}).meta(c.req.param("id"));
|
|
131
149
|
return c.json({ contributors: contributors.map(({ id, kind, machine }) => ({ name: id, kind, machine })) });
|
|
132
150
|
});
|
|
133
|
-
app2.get("/api/v1/docs/:id/ws", (c) => {
|
|
151
|
+
app2.get("/api/v1/docs/:id/ws", async (c) => {
|
|
134
152
|
const id = c.req.param("id");
|
|
135
153
|
if (!options.live) return fail("NOT_FOUND", "this server hosts no live rooms", 404);
|
|
136
154
|
if (c.req.header("upgrade") !== "websocket") return fail("UPGRADE_REQUIRED", "open this route as a WebSocket", 426);
|
|
137
155
|
if (!DOC_ID.test(id)) return fail("INVALID_INPUT", `invalid document id "${id}"`, 400);
|
|
138
156
|
const { kind, name } = c.get("author");
|
|
139
|
-
|
|
157
|
+
const opened = options.live(c.env ?? {}).connect(id, c.req.raw, { principal: { id: name, kind }, role: c.get("role"), name });
|
|
158
|
+
if (!(c.req.header("sec-websocket-protocol") ?? "").split(",").some((p) => p.trim() === WS_PROTOCOL)) return opened;
|
|
159
|
+
const room = await opened;
|
|
160
|
+
const socket = room.webSocket;
|
|
161
|
+
return socket ? new Response(null, { status: 101, webSocket: socket, headers: new Headers([...room.headers, ["sec-websocket-protocol", WS_PROTOCOL]]) }) : room;
|
|
140
162
|
});
|
|
141
163
|
async function run(c, command, input) {
|
|
142
164
|
try {
|
|
143
165
|
if (!command.readOnly && c.get("role") === "viewer") throw new DocError("FORBIDDEN", "this token is a viewer; it cannot change a document");
|
|
144
166
|
const result = await command.run(await (typeof source === "function" ? source(c.env ?? {}) : source), input, c.get("author"));
|
|
145
167
|
if (command.name === "export") {
|
|
146
|
-
const { data, contentType, encoding, format } = result;
|
|
168
|
+
const { data, contentType, encoding, format, warnings } = result;
|
|
147
169
|
const images = input.remoteImages === true || input.remoteImages === "true" ? "data: https:" : "data:";
|
|
148
170
|
const disposition = format === "odt" ? { "content-disposition": `attachment; filename="${input.id}.odt"` } : {};
|
|
149
|
-
const
|
|
171
|
+
const lost = warnings.length ? { "x-export-warnings": JSON.stringify(warnings).replace(/[^\x20-\x7e]/g, (ch) => `\\u${ch.charCodeAt(0).toString(16).padStart(4, "0")}`) } : {};
|
|
172
|
+
const headers = { ...disposition, ...lost, "content-type": contentType, "content-security-policy": `default-src 'none'; style-src 'unsafe-inline'; img-src ${images}; sandbox` };
|
|
150
173
|
return encoding === "base64" ? c.body(Uint8Array.from(atob(data), (ch) => ch.charCodeAt(0)), 200, headers) : c.body(data, 200, headers);
|
|
151
174
|
}
|
|
152
175
|
if (typeof result === "string") return c.body(result, 200, { "content-type": "text/markdown; charset=utf-8" });
|
|
@@ -171,12 +194,14 @@ function createApp(source, options = {}) {
|
|
|
171
194
|
app2.post("/api/v1/docs/:id/checkpoint", async (c) => {
|
|
172
195
|
if (c.get("role") === "viewer") throw new DocError("FORBIDDEN", "this token is a viewer; it cannot change a document");
|
|
173
196
|
const update = await readCapped(c.req.raw, MAX_LIVE_BYTES);
|
|
174
|
-
await (await serviceOf(c)).checkpoint(c.req.param("id"), update, c.get("author"));
|
|
197
|
+
await (await serviceOf(c)).checkpoint(c.req.param("id"), update, c.get("author"), { create: c.req.query("create") !== "false" });
|
|
175
198
|
return c.json({ ok: true });
|
|
176
199
|
});
|
|
177
200
|
const create = commands.find((c) => c.name === "new");
|
|
201
|
+
const listing = commands.find((c) => c.name === "list");
|
|
178
202
|
app2.post("/api/v1/docs", async (c) => run(c, create, await body(c)));
|
|
179
|
-
|
|
203
|
+
app2.get("/api/v1/docs", (c) => run(c, listing, {}));
|
|
204
|
+
for (const command of commands.filter((c) => c !== create && c !== listing)) {
|
|
180
205
|
const path = `/api/v1/docs/:id/${command.name}`;
|
|
181
206
|
if (command.readOnly) app2.get(path, (c) => run(c, command, { ...c.req.query(), id: c.req.param("id") }));
|
|
182
207
|
else app2.post(path, async (c) => run(c, command, { ...await body(c), id: c.req.param("id") }));
|
package/dist/cli.js
CHANGED
|
@@ -1,25 +1,28 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
VERSION
|
|
4
|
+
} from "./chunk-5Y66UCTE.js";
|
|
2
5
|
import {
|
|
3
6
|
createFileBlobs,
|
|
4
7
|
createFileStore
|
|
5
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-34C47PDC.js";
|
|
6
9
|
import {
|
|
7
10
|
LiveService,
|
|
8
11
|
whoAmI
|
|
9
|
-
} from "./chunk-
|
|
10
|
-
import {
|
|
11
|
-
DocsService
|
|
12
|
-
} from "./chunk-FZA5LQV3.js";
|
|
13
|
-
import {
|
|
14
|
-
VERSION
|
|
15
|
-
} from "./chunk-5Y66UCTE.js";
|
|
12
|
+
} from "./chunk-CN6UAW7B.js";
|
|
16
13
|
import {
|
|
17
14
|
commands
|
|
18
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-6UYJV2EJ.js";
|
|
16
|
+
import {
|
|
17
|
+
DocsService
|
|
18
|
+
} from "./chunk-JBAQEIOH.js";
|
|
19
|
+
import "./chunk-5IQX2W52.js";
|
|
20
|
+
import "./chunk-KC5KFB5R.js";
|
|
19
21
|
import {
|
|
20
22
|
DocError,
|
|
21
23
|
parseAuthor
|
|
22
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-GQ5E6T7Z.js";
|
|
25
|
+
import "./chunk-DWVJIKVT.js";
|
|
23
26
|
|
|
24
27
|
// src/cli.ts
|
|
25
28
|
import { readFileSync, writeFileSync } from "fs";
|
|
@@ -118,7 +121,7 @@ async function main(argv, env = process.env) {
|
|
|
118
121
|
author = named && scopes?.length ? { ...named, scopes } : named;
|
|
119
122
|
}
|
|
120
123
|
if (name === "mcp") {
|
|
121
|
-
const { startMcp } = await import("./mcp-
|
|
124
|
+
const { startMcp } = await import("./mcp-ZTUTC4CS.js");
|
|
122
125
|
await startMcp(service, author);
|
|
123
126
|
return 0;
|
|
124
127
|
}
|
|
@@ -138,7 +141,8 @@ async function main(argv, env = process.env) {
|
|
|
138
141
|
}
|
|
139
142
|
const result = await command.run(service, input, author);
|
|
140
143
|
if (name === "export") {
|
|
141
|
-
const { data, encoding } = result;
|
|
144
|
+
const { data, encoding, warnings } = result;
|
|
145
|
+
for (const line of warnings) console.error(`warning: ${line}`);
|
|
142
146
|
const bytes = encoding === "base64" ? Buffer.from(data, "base64") : data;
|
|
143
147
|
if (out === void 0) process.stdout.write(bytes);
|
|
144
148
|
else writeFileSync(out, bytes);
|
package/dist/client.d.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export {
|
|
3
|
-
import {
|
|
1
|
+
import { e as PrincipalKind, S as Section, E as EditEntry } from './doc-BXG9qG5X.js';
|
|
2
|
+
export { a as Anchor } from './doc-BXG9qG5X.js';
|
|
3
|
+
import { E as ExportFormat } from './render-463xHb-x.js';
|
|
4
|
+
import { a as DocErrorCode, S as Suggestion, T as Thread } from './review-Ds-i5Vnc.js';
|
|
5
|
+
import { d as DocSummary } from './service-Ck6oX-Kq.js';
|
|
4
6
|
import 'yjs';
|
|
7
|
+
import 'agentic-identity';
|
|
5
8
|
|
|
6
9
|
/** The place an edit goes: a section id, or exact text. */
|
|
7
10
|
type Where = {
|
|
@@ -69,6 +72,18 @@ declare function createClient(options: ClientOptions): {
|
|
|
69
72
|
}, call?: CallOptions) => Promise<{
|
|
70
73
|
id: string;
|
|
71
74
|
}>;
|
|
75
|
+
/** `GET /api/v1/docs`: documents with titles, creators, and last edits. */
|
|
76
|
+
list: (call?: CallOptions) => Promise<DocSummary[]>;
|
|
77
|
+
/** `POST /rename`: give a document a title. */
|
|
78
|
+
rename: (id: string, input: {
|
|
79
|
+
title: string;
|
|
80
|
+
}, call?: CallOptions) => Promise<{
|
|
81
|
+
ok: true;
|
|
82
|
+
}>;
|
|
83
|
+
/** `POST /remove`: delete a document. An agent needs the `docs:remove` scope. */
|
|
84
|
+
remove: (id: string, call?: CallOptions) => Promise<{
|
|
85
|
+
ok: true;
|
|
86
|
+
}>;
|
|
72
87
|
/** `GET /read`: the whole document, or one section, as Markdown. */
|
|
73
88
|
read(id: string, input?: {
|
|
74
89
|
section?: string;
|
|
@@ -161,11 +176,13 @@ declare function createClient(options: ClientOptions): {
|
|
|
161
176
|
}>;
|
|
162
177
|
/** `GET /state`: the stored document as a Yjs update. */
|
|
163
178
|
state(id: string, call?: CallOptions): Promise<Uint8Array>;
|
|
164
|
-
/** `POST /checkpoint`: merge a Yjs update into the stored document. */
|
|
165
|
-
checkpoint(id: string, update: Uint8Array, call?: CallOptions
|
|
179
|
+
/** `POST /checkpoint`: merge a Yjs update into the stored document. It makes the document when there is none, unless `create` is false: then it fails with `NO_DOCUMENT`. */
|
|
180
|
+
checkpoint(id: string, update: Uint8Array, call?: CallOptions & {
|
|
181
|
+
create?: boolean;
|
|
182
|
+
}): Promise<{
|
|
166
183
|
ok: true;
|
|
167
184
|
}>;
|
|
168
185
|
};
|
|
169
186
|
type DocsClient = ReturnType<typeof createClient>;
|
|
170
187
|
|
|
171
|
-
export { type CallOptions, type ClientOptions, type Contributor, DocsApiError, type DocsApiErrorCode, type DocsClient, EditEntry, ExportFormat, type Me, Section, Suggestion, Thread, type Where, createClient };
|
|
188
|
+
export { type CallOptions, type ClientOptions, type Contributor, DocSummary, DocsApiError, type DocsApiErrorCode, type DocsClient, EditEntry, ExportFormat, type Me, Section, Suggestion, Thread, type Where, createClient };
|
package/dist/client.js
CHANGED
|
@@ -72,6 +72,12 @@ function createClient(options) {
|
|
|
72
72
|
me: (call) => get("/api/v1/me", {}, (v) => isObject(v) && typeof v.kind === "string" && typeof v.name === "string" && Array.isArray(v.scopes) && (v.role === "editor" || v.role === "viewer"), "the token identity", call),
|
|
73
73
|
/** `POST /api/v1/docs`. */
|
|
74
74
|
create: (input, call) => withKey("/api/v1/docs", input, "id", call),
|
|
75
|
+
/** `GET /api/v1/docs`: documents with titles, creators, and last edits. */
|
|
76
|
+
list: (call) => list("/api/v1/docs", {}, call),
|
|
77
|
+
/** `POST /rename`: give a document a title. */
|
|
78
|
+
rename: (id, input, call) => ok(`${doc(id)}/rename`, input, call),
|
|
79
|
+
/** `POST /remove`: delete a document. An agent needs the `docs:remove` scope. */
|
|
80
|
+
remove: (id, call) => ok(`${doc(id)}/remove`, {}, call),
|
|
75
81
|
/** `GET /read`: the whole document, or one section, as Markdown. */
|
|
76
82
|
async read(id, input = {}, call) {
|
|
77
83
|
return (await send(`${doc(id)}/read`, { method: "GET", query: input }, call, "text/markdown")).text();
|
|
@@ -102,9 +108,10 @@ function createClient(options) {
|
|
|
102
108
|
async state(id, call) {
|
|
103
109
|
return new Uint8Array(await (await send(`${doc(id)}/state`, { method: "GET" }, call, "application/octet-stream")).arrayBuffer());
|
|
104
110
|
},
|
|
105
|
-
/** `POST /checkpoint`: merge a Yjs update into the stored document. */
|
|
111
|
+
/** `POST /checkpoint`: merge a Yjs update into the stored document. It makes the document when there is none, unless `create` is false: then it fails with `NO_DOCUMENT`. */
|
|
106
112
|
async checkpoint(id, update, call) {
|
|
107
|
-
|
|
113
|
+
const query = call?.create === false ? { create: false } : void 0;
|
|
114
|
+
return await json(await send(`${doc(id)}/checkpoint`, { method: "POST", body: update, query }, call), isOk, "{ ok: true }");
|
|
108
115
|
}
|
|
109
116
|
};
|
|
110
117
|
}
|
|
@@ -18,6 +18,7 @@ declare function blocksToMarkdown(nodes: PmNode[]): string;
|
|
|
18
18
|
|
|
19
19
|
declare const PRINCIPAL_KINDS: readonly ["human", "agent_of_human", "agent_of_agent", "agent", "user_token"];
|
|
20
20
|
type PrincipalKind = (typeof PRINCIPAL_KINDS)[number];
|
|
21
|
+
|
|
21
22
|
/** Who made a change. Every mutation needs one, so machine-made work stays labeled. */
|
|
22
23
|
interface Principal {
|
|
23
24
|
kind: PrincipalKind;
|
|
@@ -51,13 +52,16 @@ interface Section {
|
|
|
51
52
|
title: string;
|
|
52
53
|
}
|
|
53
54
|
interface EditEntry {
|
|
54
|
-
|
|
55
|
+
/** `live` ties the editor client of an agent to that agent, so the text the client types is marked as its work. Its counts stay 0. */
|
|
56
|
+
op: "create" | "append" | "insert" | "replace" | "delete" | "live";
|
|
55
57
|
at: string;
|
|
56
58
|
author: Principal;
|
|
57
59
|
/** Counts of characters (text targets) or blocks (section targets). Never content. */
|
|
58
60
|
inserted: number;
|
|
59
61
|
deleted: number;
|
|
60
62
|
section?: string;
|
|
63
|
+
/** The Yjs client id of the replica that made the edit. Text it wrote carries this id, so a UI can mark text by author kind. Written by the client, like the rest of the log. */
|
|
64
|
+
client?: number;
|
|
61
65
|
/** Set when an accepted suggestion caused the edit. `author` is who suggested it. */
|
|
62
66
|
suggestion?: {
|
|
63
67
|
id: string;
|
|
@@ -87,6 +91,8 @@ declare class AgenticDoc {
|
|
|
87
91
|
readonly now: () => Date;
|
|
88
92
|
constructor(ydoc?: Y.Doc, options?: DocOptions);
|
|
89
93
|
static fromMarkdown(markdown: string, author: Principal, options?: DocOptions): AgenticDoc;
|
|
94
|
+
/** Make a document from blocks in ProseMirror JSON form. Blocks that the schema does not allow fail with INVALID_INPUT. */
|
|
95
|
+
static fromBlocks(content: PmNode[], author: Principal, options?: DocOptions): AgenticDoc;
|
|
90
96
|
static fromUpdate(update: Uint8Array, options?: DocOptions): AgenticDoc;
|
|
91
97
|
encode(): Uint8Array;
|
|
92
98
|
applyUpdate(update: Uint8Array): void;
|
|
@@ -123,4 +129,4 @@ declare class AgenticDoc {
|
|
|
123
129
|
append(markdown: string, author: Principal): void;
|
|
124
130
|
}
|
|
125
131
|
|
|
126
|
-
export {
|
|
132
|
+
export { AgenticDoc as A, type DocOptions as D, type EditEntry as E, type Principal as P, type Section as S, type Target as T, type Anchor as a, type AnchorTarget as b, PRINCIPAL_KINDS as c, type PmNode as d, type PrincipalKind as e, type SectionTarget as f, type TextTarget as g, blocksToMarkdown as h, markdownToBlocks as m, parseAuthor as p };
|