revise 2.2.0 → 3.0.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 (134) hide show
  1. package/CHANGELOG.md +56 -1182
  2. package/LICENSE +24 -41
  3. package/README.md +103 -26
  4. package/agent.d.ts +139 -0
  5. package/chunks/chunk-U5X47PZ6.js +135 -0
  6. package/chunks/chunk-U6YKQBKL.js +15 -0
  7. package/chunks/chunk-ZLQVPMBN.js +1650 -0
  8. package/chunks/package-5SHL5FOJ.js +14 -0
  9. package/chunks/render-QD5VWKSV.js +109 -0
  10. package/cli-output.schema.json +1984 -0
  11. package/cli.js +3609 -0
  12. package/document.d.ts +320 -0
  13. package/docx.d.ts +73 -0
  14. package/errors.d.ts +14 -0
  15. package/index.d.ts +13 -975
  16. package/index.js +31 -1
  17. package/legacy/editor.d.ts +19 -0
  18. package/legacy/editor.js +16 -0
  19. package/legacy/server.d.ts +37 -0
  20. package/legacy/server.js +25 -0
  21. package/legacy/style.css +2 -0
  22. package/package.d.ts +23 -0
  23. package/package.json +49 -55
  24. package/render.d.ts +27 -0
  25. package/FONT-LICENSE-HAN-EXT-B.txt +0 -91
  26. package/FONT-LICENSE-KATEX.txt +0 -95
  27. package/FONT-LICENSE-LIBERATION.txt +0 -102
  28. package/FONT-LICENSE-NOTO-MULTILINGUAL.txt +0 -93
  29. package/FONT-LICENSE-NOTO-SANS-SC.txt +0 -93
  30. package/FONT-LICENSE-NOTO.txt +0 -94
  31. package/FONT-LICENSE-SOURCE-CODE-PRO.txt +0 -94
  32. package/FONT-LICENSE-SOURCE-SERIF-4.txt +0 -93
  33. package/THIRD-PARTY-NOTICES.md +0 -3376
  34. package/assets/historyMaterializeWorker-B51R08XN.js +0 -612
  35. package/backend.d.ts +0 -161
  36. package/backend.js +0 -3
  37. package/chunks/AgentPageRenderer-CH8kSTJn.js +0 -1
  38. package/chunks/AgentPageRenderer-P23XVguo.js +0 -1
  39. package/chunks/AttachmentDocumentImporter-B1WOaa7d.js +0 -1
  40. package/chunks/AttachmentDocumentImporter-CT_PJp_e.js +0 -1
  41. package/chunks/BidiText-Swm5fl9J.js +0 -160
  42. package/chunks/ColorParserFull-BKH72fK8.js +0 -446
  43. package/chunks/DocumentMutator-btiDK57e.js +0 -31
  44. package/chunks/DocumentScrubber-CMPfm_HF.js +0 -18
  45. package/chunks/DocumentScrubber-Cm2kH8op.js +0 -1
  46. package/chunks/DocumentScrubber-bJoKijol.js +0 -1
  47. package/chunks/DocumentStylesheet-DreoD7sD.js +0 -7
  48. package/chunks/DocxConverter-BcVG-p5K.js +0 -13
  49. package/chunks/DocxConverter-D2bO5RE-.js +0 -1125
  50. package/chunks/DocxConverter-haCfRuWb.js +0 -13
  51. package/chunks/DocxGraft-BRKh4mHQ.js +0 -1
  52. package/chunks/DocxGraft-BxSQtl12.js +0 -7
  53. package/chunks/DocxGraft-C9MBQCnf.js +0 -1
  54. package/chunks/FileImporter-CszqRfVU.js +0 -1
  55. package/chunks/FileImporter-DObFoCCn.js +0 -1
  56. package/chunks/HorizontalRuleOrnaments-gmUZ3R7Q.js +0 -3
  57. package/chunks/HtmlConverter-DwXB4IDh.js +0 -267
  58. package/chunks/ListNumbering-Qlu6AuRZ.js +0 -23
  59. package/chunks/MarkdownConverter-CUC8GgsS.js +0 -57
  60. package/chunks/MathRenderer-DT6GyJXb.js +0 -68
  61. package/chunks/OdtConverter-7h9HHMpL.js +0 -125
  62. package/chunks/OdtConverter-Ct4qypJk.js +0 -6
  63. package/chunks/OdtConverter-DFl__sJc.js +0 -6
  64. package/chunks/OfficeImportShared-BY6FdZ8d.js +0 -1
  65. package/chunks/OfficeImportShared-BrZoDM0j.js +0 -30
  66. package/chunks/OfficeImportShared-Bvm-mOyj.js +0 -1
  67. package/chunks/ParagraphDiffAnalysis-DrmStvmz.js +0 -36
  68. package/chunks/PdfConverter-1wyvoT7X.js +0 -21
  69. package/chunks/PdfConverter-BAoEHILo.js +0 -21
  70. package/chunks/PdfConverter-CKEvFj11.js +0 -448
  71. package/chunks/PdfImportService-BhLPp5uG.js +0 -2
  72. package/chunks/PdfImportService-xbzrN__L.js +0 -2
  73. package/chunks/RtfConverter-Bxn-enp8.js +0 -15
  74. package/chunks/RtfConverter-LZPzBDSr.js +0 -15
  75. package/chunks/RtfConverter-_bacXqr1.js +0 -186
  76. package/chunks/TableBorders-CwnF3jvH.js +0 -30
  77. package/chunks/TableGrid-DbroRucl.js +0 -12
  78. package/chunks/TextFileDecoder--rsVs3NR.js +0 -9
  79. package/chunks/TxtConverter-C7FOwHpH.js +0 -14
  80. package/chunks/UnicodeUtils-CXtusU4g.js +0 -10
  81. package/chunks/XmlConverter-7oo8oeQB.js +0 -2
  82. package/chunks/XmlConverter-DGORHJcZ.js +0 -96
  83. package/chunks/XmlConverter-DeLHReK_.js +0 -2
  84. package/chunks/__vite-browser-external-l0sNRNKZ.js +0 -0
  85. package/chunks/_commonjsHelpers-DaMA6jEr.js +0 -2
  86. package/chunks/_revise-sdk-lazy-harfbuzz-CDWdjF9f.js +0 -1
  87. package/chunks/_revise-sdk-lazy-harfbuzz-X8gnYTZL.js +0 -1
  88. package/chunks/ar-ComXvtmh.js +0 -13
  89. package/chunks/backend-DecaBz-I.js +0 -490
  90. package/chunks/base64-sSPOzq-u.js +0 -198
  91. package/chunks/browser-module-CFmqfGkE.js +0 -1791
  92. package/chunks/browser-module-Dj5o3kuL.js +0 -3
  93. package/chunks/browser-module-UuM_O36W.js +0 -3
  94. package/chunks/bundle.min-DRpfgV69.js +0 -782
  95. package/chunks/capture-1btcWcPF.js +0 -9
  96. package/chunks/carrier-DQeng6yq.js +0 -6
  97. package/chunks/de-BkbpIKxx.js +0 -13
  98. package/chunks/es-CKkXujOF.js +0 -13
  99. package/chunks/fil-piIHMc8F.js +0 -13
  100. package/chunks/footnoteFlatten-DUQticYy.js +0 -6
  101. package/chunks/fr-DuA8qLje.js +0 -13
  102. package/chunks/graftExport-CYnStOdU.js +0 -82
  103. package/chunks/html2canvas.esm-CntiHiYW.js +0 -997
  104. package/chunks/index-2TBhRkX7.js +0 -387
  105. package/chunks/index-BWdRWxoH.js +0 -1807
  106. package/chunks/index-Bf14eTEW.js +0 -1096
  107. package/chunks/index-BwcX_r62.js +0 -387
  108. package/chunks/index.es-CI0QPo8w.js +0 -780
  109. package/chunks/index.es-CI7cDExj.js +0 -5
  110. package/chunks/index.es-DHpGt6Y5.js +0 -5
  111. package/chunks/ja-DgrSs555.js +0 -13
  112. package/chunks/jspdf.es.min--GshOuIm.js +0 -77
  113. package/chunks/jspdf.es.min-DU74zN8b.js +0 -1792
  114. package/chunks/jspdf.es.min-DurJZ1zW.js +0 -77
  115. package/chunks/jszip.min-CPW0h1Kx.js +0 -476
  116. package/chunks/katex-DWj8TvwO.js +0 -1462
  117. package/chunks/pdf-BLWDCiFF.js +0 -1828
  118. package/chunks/persistInlineDocumentImages-DQj2iaqg.js +0 -1
  119. package/chunks/pseudoLocale-BoYTjmiK.js +0 -1
  120. package/chunks/pseudoLocale-DG2NnZmz.js +0 -1
  121. package/chunks/purify.es-BwLardii.js +0 -107
  122. package/chunks/serverDocumentSession-CJ1EH3Tp.js +0 -15
  123. package/chunks/signatures-Bwt_OGik.js +0 -17
  124. package/chunks/suggestionAuthorship-B-IuLQlE.js +0 -1
  125. package/chunks/tree-sitter-D6zWWUMP.js +0 -4
  126. package/chunks/wasmInline.generated-CBYFkdSM.js +0 -1
  127. package/chunks/web-tree-sitter-BXGZGnNj.js +0 -2
  128. package/chunks/zh-9OF3SWWO.js +0 -13
  129. package/full.js +0 -1
  130. package/root.d.ts +0 -5
  131. package/root.js +0 -2
  132. package/serverToolContract.d.ts +0 -391
  133. package/serverToolInputs.generated.d.ts +0 -250
  134. package/style.css +0 -1
@@ -0,0 +1,1650 @@
1
+ // revise: MIT License. https://revise.io/developer/docs
2
+ import {
3
+ ReviseError
4
+ } from "./chunk-U6YKQBKL.js";
5
+
6
+ // src/embed-sdk/lib/document.ts
7
+ import { engineModules as engineModules2 } from "@reviseio/engine";
8
+ import * as Y from "yjs";
9
+ import {
10
+ createReviseYDoc as createReviseYDoc2,
11
+ createServerDocumentSession,
12
+ decodeYDoc,
13
+ encodeYDoc,
14
+ exportDocument,
15
+ fileToYDoc,
16
+ installDomShims,
17
+ parseDocument as parseDocument2,
18
+ ydocSourceFidelity as ydocSourceFidelity2,
19
+ ydocToDocument as ydocToDocument2
20
+ } from "@reviseio/engine";
21
+ import { seedDocRootWithClaim as seedDocRootWithClaim2, seedYDocFromFile } from "@reviseio/engine";
22
+
23
+ // src/embed-sdk/lib/docx.ts
24
+ import { engineModules } from "@reviseio/engine";
25
+ import {
26
+ createReviseYDoc,
27
+ documentToDocx,
28
+ parseDocument,
29
+ ydocSourceFidelity,
30
+ ydocToDocument
31
+ } from "@reviseio/engine";
32
+ import { createHash } from "node:crypto";
33
+ import { seedDocRootWithClaim } from "@reviseio/engine";
34
+ async function exportSessionDocx(ydoc, pkg, mode = "review") {
35
+ let doc = ydocToDocument(ydoc);
36
+ const grafting = Boolean(pkg && doc.docxSource);
37
+ const regenerated = grafting ? new Set((await ydocSourceFidelity(ydoc)).regeneratedBlockIds) : null;
38
+ if (mode === "final") {
39
+ const { scrubDocument } = await engineModules.scrubber();
40
+ doc = scrubDocument(doc, {
41
+ removeComments: true,
42
+ resolveSuggestions: "accept"
43
+ }).doc;
44
+ }
45
+ doc = stampParaIds(
46
+ doc,
47
+ regenerated,
48
+ grafting ? graftKeptParts(doc, await engineModules.docxGraft()) : void 0
49
+ );
50
+ if (!grafting || !pkg || !doc.docxSource) {
51
+ return {
52
+ bytes: await documentToDocx(doc),
53
+ outcome: {
54
+ method: "regenerated",
55
+ exact: false,
56
+ regeneratedBlocks: doc.children.length,
57
+ fallbackCode: doc.docxSource ? "no-original-package" : "not-from-docx"
58
+ }
59
+ };
60
+ }
61
+ const { hashBytes } = await engineModules.docxGraft();
62
+ doc = {
63
+ ...doc,
64
+ docxSource: { ...doc.docxSource, sourceHash: await hashBytes(pkg) }
65
+ };
66
+ let reported = {
67
+ method: "regenerated",
68
+ exact: false,
69
+ regeneratedBlocks: doc.children.length,
70
+ fallbackCode: "no-outcome"
71
+ };
72
+ const bytes = await documentToDocx(doc, {
73
+ originalDocx: pkg,
74
+ onGraftOutcome: (outcome) => {
75
+ reported = outcome.mode === "regenerated" ? {
76
+ method: "regenerated",
77
+ exact: false,
78
+ regeneratedBlocks: doc.children.length,
79
+ fallbackCode: outcome.fallbackCode
80
+ } : {
81
+ method: "graft",
82
+ exact: outcome.mode === "exact",
83
+ regeneratedBlocks: outcome.regeneratedBlockCount ?? 0
84
+ };
85
+ }
86
+ });
87
+ return { bytes, outcome: reported };
88
+ }
89
+ var PARA_ID_KEY = "w14:paraId";
90
+ function paraIdOf(node) {
91
+ if (!node.oxmlAttrs) return void 0;
92
+ try {
93
+ const attrs = JSON.parse(node.oxmlAttrs);
94
+ const value = attrs[PARA_ID_KEY];
95
+ return typeof value === "string" ? value.toUpperCase() : void 0;
96
+ } catch {
97
+ return void 0;
98
+ }
99
+ }
100
+ function derivedParaId(blockId, salt = 0) {
101
+ const value = createHash("sha1").update(salt ? `${blockId}:${salt}` : blockId).digest().readUInt32BE(0) & 2147483647;
102
+ return (value || 1).toString(16).toUpperCase().padStart(8, "0");
103
+ }
104
+ function childrenOf(node) {
105
+ return (Array.isArray(node.children) ? node.children : []).filter(
106
+ (child) => child !== null && typeof child === "object" && "type" in child
107
+ );
108
+ }
109
+ function contentSignature(node) {
110
+ const n = node;
111
+ if (!n || typeof n !== "object") return "";
112
+ if (n.type === "text") return JSON.stringify(n.text ?? "");
113
+ const children = Array.isArray(n.children) ? n.children : [];
114
+ return `${n.type ?? "?"}(${children.map(contentSignature).join(",")})`;
115
+ }
116
+ function walkIds(node, visit) {
117
+ visit(node);
118
+ for (const child of childrenOf(node)) walkIds(child, visit);
119
+ }
120
+ function graftKeptParts(doc, graft) {
121
+ const kept = { whole: /* @__PURE__ */ new Set(), head: /* @__PURE__ */ new Set() };
122
+ const source = doc.docxSource;
123
+ if (!source?.parts) return kept;
124
+ const version = source.signatureVersion === graft.CURRENT_GRAFT_SIGNATURE_VERSION ? graft.CURRENT_GRAFT_SIGNATURE_VERSION : 1;
125
+ for (const block of doc.children) {
126
+ const captured = block.id ? source.parts[block.id] : void 0;
127
+ if (!captured || block.type !== "table" && block.type !== "list") {
128
+ continue;
129
+ }
130
+ for (const part of block.children) {
131
+ const entry = captured.parts.find((p) => p.id === part.id);
132
+ if (!entry || !part.id) continue;
133
+ if (entry.signature === graft.partSignature(part, version)) {
134
+ kept.whole.add(part.id);
135
+ continue;
136
+ }
137
+ if (block.type === "table" && entry.cells && entry.head === graft.partHeadSignature(part, version)) {
138
+ kept.head.add(part.id);
139
+ part.children?.forEach((cell, c) => {
140
+ const cellEntry = entry.cells[c];
141
+ if (cell.id && cellEntry?.id === cell.id && cellEntry.signature === graft.partSignature(cell, version)) {
142
+ kept.whole.add(cell.id);
143
+ }
144
+ });
145
+ }
146
+ }
147
+ }
148
+ return kept;
149
+ }
150
+ function stampParaIds(doc, regenerated, kept = { whole: /* @__PURE__ */ new Set(), head: /* @__PURE__ */ new Set() }) {
151
+ const used = /* @__PURE__ */ new Set();
152
+ for (const block of doc.children) {
153
+ walkIds(block, (node) => {
154
+ const id = paraIdOf(node);
155
+ if (id) used.add(id);
156
+ });
157
+ }
158
+ const stamp = (node) => {
159
+ if (node.id && kept.whole.has(node.id)) return node;
160
+ const copy = { ...node };
161
+ if ((copy.type === "paragraph" || copy.type === "table-row") && copy.id && !kept.head.has(copy.id) && !paraIdOf(copy)) {
162
+ let salt = 0;
163
+ let candidate = derivedParaId(copy.id);
164
+ while (used.has(candidate)) candidate = derivedParaId(copy.id, ++salt);
165
+ used.add(candidate);
166
+ let attrs = {};
167
+ try {
168
+ attrs = copy.oxmlAttrs ? JSON.parse(copy.oxmlAttrs) : {};
169
+ } catch {
170
+ attrs = {};
171
+ }
172
+ copy.oxmlAttrs = JSON.stringify({ ...attrs, [PARA_ID_KEY]: candidate });
173
+ }
174
+ if (Array.isArray(node.children)) {
175
+ copy.children = node.children.map(
176
+ (child) => child && typeof child === "object" && "type" in child ? stamp(child) : child
177
+ );
178
+ }
179
+ return copy;
180
+ };
181
+ return {
182
+ ...doc,
183
+ children: doc.children.map(
184
+ (block) => !regenerated || block.id && regenerated.has(block.id) ? stamp(block) : block
185
+ )
186
+ };
187
+ }
188
+ function lcsIndexPairs(a, b) {
189
+ const n = a.length;
190
+ const m = b.length;
191
+ if (n === 0 || m === 0) return [];
192
+ if (n * m > 4e6) {
193
+ const pairs2 = [];
194
+ let i2 = 0;
195
+ while (i2 < n && i2 < m && a[i2] === b[i2]) pairs2.push([i2, i2++]);
196
+ let k = 1;
197
+ while (k <= n - i2 && k <= m - i2 && a[n - k] === b[m - k]) {
198
+ pairs2.push([n - k, m - k]);
199
+ k += 1;
200
+ }
201
+ return pairs2.sort((x, y) => x[0] - y[0]);
202
+ }
203
+ const table = new Uint32Array((n + 1) * (m + 1));
204
+ for (let i2 = n - 1; i2 >= 0; i2 -= 1) {
205
+ for (let j2 = m - 1; j2 >= 0; j2 -= 1) {
206
+ table[i2 * (m + 1) + j2] = a[i2] === b[j2] ? table[(i2 + 1) * (m + 1) + j2 + 1] + 1 : Math.max(table[(i2 + 1) * (m + 1) + j2], table[i2 * (m + 1) + j2 + 1]);
207
+ }
208
+ }
209
+ const pairs = [];
210
+ let i = 0;
211
+ let j = 0;
212
+ while (i < n && j < m) {
213
+ if (a[i] === b[j]) {
214
+ pairs.push([i, j]);
215
+ i += 1;
216
+ j += 1;
217
+ } else if (table[(i + 1) * (m + 1) + j] >= table[i * (m + 1) + j + 1]) {
218
+ i += 1;
219
+ } else {
220
+ j += 1;
221
+ }
222
+ }
223
+ return pairs;
224
+ }
225
+ function matchIds(previous, next) {
226
+ const renamed = /* @__PURE__ */ new Map();
227
+ const changed = /* @__PURE__ */ new Set();
228
+ const oldIds = /* @__PURE__ */ new Set();
229
+ for (const block of previous.children) {
230
+ walkIds(block, (node) => node.id && oldIds.add(node.id));
231
+ }
232
+ const byParaId = /* @__PURE__ */ new Map();
233
+ for (const block of previous.children) {
234
+ walkIds(block, (node) => {
235
+ const explicit = paraIdOf(node);
236
+ if (explicit && !byParaId.has(explicit)) byParaId.set(explicit, node);
237
+ });
238
+ }
239
+ for (const block of previous.children) {
240
+ walkIds(block, (node) => {
241
+ if (!node.id) return;
242
+ const derived = derivedParaId(node.id);
243
+ if (!byParaId.has(derived)) byParaId.set(derived, node);
244
+ });
245
+ }
246
+ const oldParent = /* @__PURE__ */ new Map();
247
+ const indexParents = (node) => {
248
+ for (const child of childrenOf(node)) {
249
+ oldParent.set(child, node);
250
+ indexParents(child);
251
+ }
252
+ };
253
+ previous.children.forEach((block) => indexParents(block));
254
+ const seenParaIds = /* @__PURE__ */ new Set();
255
+ const duplicated = /* @__PURE__ */ new Set();
256
+ for (const block of next.children) {
257
+ walkIds(block, (node) => {
258
+ const pid = paraIdOf(node);
259
+ if (!pid) return;
260
+ if (seenParaIds.has(pid)) duplicated.add(pid);
261
+ seenParaIds.add(pid);
262
+ });
263
+ }
264
+ const claimed = /* @__PURE__ */ new Set();
265
+ let fresh = 0;
266
+ const carrySubtree = (from, to) => {
267
+ if (from.id && to.id) {
268
+ renamed.set(to.id, from.id);
269
+ to.id = from.id;
270
+ }
271
+ claimed.add(from);
272
+ const a = childrenOf(from);
273
+ const b = childrenOf(to);
274
+ b.forEach((child, i) => {
275
+ if (a[i] && a[i].type === child.type) carrySubtree(a[i], child);
276
+ });
277
+ };
278
+ const matchLists = (olds, news) => {
279
+ const pairs = /* @__PURE__ */ new Map();
280
+ for (const node of news) {
281
+ const pid = paraIdOf(node);
282
+ if (pid && duplicated.has(pid)) continue;
283
+ const old = pid ? byParaId.get(pid) : void 0;
284
+ if (old && !claimed.has(old) && old.type === node.type) {
285
+ pairs.set(node, old);
286
+ claimed.add(old);
287
+ }
288
+ }
289
+ const oldSet = new Set(olds);
290
+ for (const node of news) {
291
+ if (pairs.has(node) || paraIdOf(node)) continue;
292
+ let found;
293
+ walkIds(node, (inner) => {
294
+ if (found || inner === node) return;
295
+ const pid = paraIdOf(inner);
296
+ let at = pid && !duplicated.has(pid) ? byParaId.get(pid) : void 0;
297
+ while (at && !oldSet.has(at)) at = oldParent.get(at);
298
+ if (at && !claimed.has(at) && at.type === node.type) found = at;
299
+ });
300
+ if (found) {
301
+ pairs.set(node, found);
302
+ claimed.add(found);
303
+ }
304
+ }
305
+ const restOld = olds.filter((node) => !claimed.has(node));
306
+ const restNew = news.filter((node) => !pairs.has(node));
307
+ const lcs = lcsIndexPairs(
308
+ restOld.map(contentSignature),
309
+ restNew.map(contentSignature)
310
+ );
311
+ for (const [i, j] of lcs) {
312
+ if (restOld[i].type === restNew[j].type) {
313
+ pairs.set(restNew[j], restOld[i]);
314
+ claimed.add(restOld[i]);
315
+ }
316
+ }
317
+ for (const node of news) {
318
+ const old = pairs.get(node);
319
+ if (!old) {
320
+ walkIds(node, (n) => {
321
+ if (n.id && n.type !== "text") fresh += 1;
322
+ if (n.id && oldIds.has(n.id)) n.id = void 0;
323
+ });
324
+ continue;
325
+ }
326
+ if (contentSignature(old) === contentSignature(node)) {
327
+ carrySubtree(old, node);
328
+ } else {
329
+ if (old.id && node.id) {
330
+ renamed.set(node.id, old.id);
331
+ node.id = old.id;
332
+ changed.add(old.id);
333
+ }
334
+ matchLists(childrenOf(old), childrenOf(node));
335
+ }
336
+ }
337
+ };
338
+ matchLists(previous.children, next.children);
339
+ for (const block of next.children) {
340
+ walkIds(block, (node) => {
341
+ if (node.type !== "text" && node.id === void 0 && "id" in node) {
342
+ node.id = freshId(oldIds);
343
+ }
344
+ });
345
+ }
346
+ const source = next.docxSource;
347
+ if (source) {
348
+ source.spans = source.spans.map((span) => ({
349
+ ...span,
350
+ blockIds: span.blockIds.map((id) => renamed.get(id) ?? id)
351
+ }));
352
+ }
353
+ const nextIds = /* @__PURE__ */ new Set();
354
+ for (const block of next.children) {
355
+ walkIds(block, (node) => node.id && nextIds.add(node.id));
356
+ }
357
+ return {
358
+ kept: renamed.size,
359
+ fresh,
360
+ changedIds: [...changed],
361
+ removedIds: [...oldIds].filter((id) => !nextIds.has(id))
362
+ };
363
+ }
364
+ function freshId(avoid) {
365
+ const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
366
+ for (; ; ) {
367
+ let id = "";
368
+ for (let i = 0; i < 6; i += 1) {
369
+ id += alphabet[Math.floor(Math.random() * alphabet.length)];
370
+ }
371
+ if (!avoid.has(id)) return id;
372
+ }
373
+ }
374
+ async function relearn(ydoc, bytes, filename) {
375
+ let next;
376
+ try {
377
+ next = await parseDocument(bytes, filename);
378
+ } catch (error) {
379
+ throw new ReviseError(
380
+ `${filename} does not open: ${error.message}. Nothing was changed.`,
381
+ "import_failed"
382
+ );
383
+ }
384
+ const previous = ydocToDocument(ydoc);
385
+ const ids = matchIds(previous, next);
386
+ const { refreshDocxSourceSignatures, storeDocxCarrier } = await engineModules.docxGraft();
387
+ refreshDocxSourceSignatures(next);
388
+ const synced = createReviseYDoc();
389
+ seedDocRootWithClaim(synced, next);
390
+ if (next.docxSource?.sourceHash) {
391
+ await storeDocxCarrier(synced, bytes, {
392
+ hash: next.docxSource.sourceHash
393
+ });
394
+ }
395
+ const styleMap = (doc) => new Map(
396
+ (doc.stylesheet?.paragraphStyles ?? []).map((style) => [
397
+ style.styleId,
398
+ JSON.stringify(style)
399
+ ])
400
+ );
401
+ const stylesBefore = styleMap(previous);
402
+ const changedStyles = [...styleMap(next)].filter(([id, json]) => stylesBefore.get(id) !== json).map(([id]) => id);
403
+ return {
404
+ ydoc: synced,
405
+ pkg: bytes,
406
+ idsKept: ids.kept,
407
+ idsNew: ids.fresh,
408
+ changedIds: ids.changedIds,
409
+ removedIds: ids.removedIds,
410
+ changedBlocks: ids.changedIds.length,
411
+ changedStyles
412
+ };
413
+ }
414
+
415
+ // src/embed-sdk/lib/document.ts
416
+ var READABLE = /\.(docx|md|markdown|html?|odt|rtf|txt)$/i;
417
+ var PLAIN_FORMATS = /\.(md|markdown|html?|txt)$/i;
418
+ var EXTENSION_FORMATS = {
419
+ ".docx": "docx",
420
+ ".pdf": "pdf",
421
+ ".md": "markdown",
422
+ ".markdown": "markdown",
423
+ ".html": "html",
424
+ ".htm": "html",
425
+ ".txt": "txt"
426
+ };
427
+ function formatForPath(file) {
428
+ const match = /\.[^./\\]+$/.exec(file);
429
+ return match ? EXTENSION_FORMATS[match[0].toLowerCase()] : void 0;
430
+ }
431
+ async function readSource(source) {
432
+ if (typeof source !== "string") return source;
433
+ const fs = await import("node:fs");
434
+ if (!fs.existsSync(source)) {
435
+ throw new ReviseError(`No such file: ${source}`, "not_found");
436
+ }
437
+ try {
438
+ return { bytes: new Uint8Array(fs.readFileSync(source)), filename: source };
439
+ } catch (error) {
440
+ throw new ReviseError(
441
+ `Could not read ${baseName(source)}: ${error.message}`,
442
+ "read_failed",
443
+ { cause: error }
444
+ );
445
+ }
446
+ }
447
+ async function importYDoc(bytes, filename) {
448
+ try {
449
+ return await fileToYDoc(bytes, filename);
450
+ } catch (error) {
451
+ throw new ReviseError(
452
+ `${baseName(filename)} does not open: ${error.message}`,
453
+ "import_failed",
454
+ { cause: error }
455
+ );
456
+ }
457
+ }
458
+ function assertReadable(filename) {
459
+ if (!READABLE.test(filename)) {
460
+ throw new ReviseError(
461
+ `Cannot read ${baseName(filename)}. Supported: .docx, .md, .html, .odt, .rtf, .txt.`,
462
+ "unsupported_format"
463
+ );
464
+ }
465
+ }
466
+ function baseName(file) {
467
+ return file.split(/[/\\]/).pop() ?? file;
468
+ }
469
+ function stem(file) {
470
+ return baseName(file).replace(/\.[^.]+$/, "");
471
+ }
472
+ async function exportYDoc(ydoc, pkg, { format, mode = "review", title = "Document" }) {
473
+ if (format === "pdf")
474
+ return { bytes: await exportPdf(ydoc, title), outcome: null };
475
+ if (format === "docx") return exportSessionDocx(ydoc, pkg, mode);
476
+ return {
477
+ bytes: await exportDocument(ydoc, { format, mode, title }),
478
+ outcome: null
479
+ };
480
+ }
481
+ async function exportPdf(ydoc, title) {
482
+ const [{ serializeDocumentFormat }, { DocumentMutator }] = await Promise.all([
483
+ engineModules2.documentFormats(),
484
+ engineModules2.mutator()
485
+ ]);
486
+ const doc = DocumentMutator.materializeAcceptedSuggestions(
487
+ ydocToDocument2(ydoc)
488
+ );
489
+ const output = await serializeDocumentFormat(doc, "pdf", { title });
490
+ return typeof output === "string" ? new TextEncoder().encode(output) : new Uint8Array(await output.arrayBuffer());
491
+ }
492
+ var SNAPSHOT_MAGIC = "RVSNAP";
493
+ var SNAPSHOT_SCHEMA = 1;
494
+ function encodeSnapshot2(header, state, pkg) {
495
+ const json = new TextEncoder().encode(
496
+ JSON.stringify({
497
+ ...header,
498
+ schema: SNAPSHOT_SCHEMA,
499
+ stateBytes: state.length,
500
+ packageBytes: pkg?.length ?? 0
501
+ })
502
+ );
503
+ const magic = new TextEncoder().encode(SNAPSHOT_MAGIC);
504
+ const out = new Uint8Array(
505
+ magic.length + 4 + json.length + state.length + (pkg?.length ?? 0)
506
+ );
507
+ out.set(magic, 0);
508
+ new DataView(out.buffer).setUint32(magic.length, json.length);
509
+ out.set(json, magic.length + 4);
510
+ out.set(state, magic.length + 4 + json.length);
511
+ if (pkg) out.set(pkg, magic.length + 4 + json.length + state.length);
512
+ return out;
513
+ }
514
+ function decodeSnapshot(bytes) {
515
+ const magic = new TextDecoder().decode(
516
+ bytes.subarray(0, SNAPSHOT_MAGIC.length)
517
+ );
518
+ if (magic !== SNAPSHOT_MAGIC) {
519
+ return { header: null, state: bytes, pkg: null };
520
+ }
521
+ try {
522
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
523
+ const jsonLength = view.getUint32(SNAPSHOT_MAGIC.length);
524
+ const start = SNAPSHOT_MAGIC.length + 4;
525
+ const header = JSON.parse(
526
+ new TextDecoder().decode(bytes.subarray(start, start + jsonLength))
527
+ );
528
+ const valid = Number.isInteger(header.schema) && header.schema >= 1 && typeof header.documentId === "string" && (header.mode === "suggesting" || header.mode === "editing") && typeof header.title === "string" && (header.author === void 0 || typeof header.author === "string") && Number.isInteger(header.stateBytes) && header.stateBytes > 0 && Number.isInteger(header.packageBytes) && header.packageBytes >= 0 && start + jsonLength + header.stateBytes + header.packageBytes === bytes.length;
529
+ if (!valid && !(header.schema > SNAPSHOT_SCHEMA)) throw new Error("header");
530
+ if (header.schema > SNAPSHOT_SCHEMA) {
531
+ throw new ReviseError(
532
+ `This snapshot uses schema ${header.schema}; this version of revise reads up to ${SNAPSHOT_SCHEMA}. Upgrade revise.`,
533
+ "snapshot_too_new"
534
+ );
535
+ }
536
+ const stateStart = start + jsonLength;
537
+ const state = bytes.subarray(stateStart, stateStart + header.stateBytes);
538
+ const pkg = header.packageBytes ? bytes.subarray(
539
+ stateStart + header.stateBytes,
540
+ stateStart + header.stateBytes + header.packageBytes
541
+ ) : null;
542
+ if (state.length !== header.stateBytes || pkg && pkg.length !== header.packageBytes) {
543
+ throw new Error("truncated");
544
+ }
545
+ return { header, state, pkg };
546
+ } catch (error) {
547
+ if (error instanceof ReviseError) throw error;
548
+ throw new ReviseError(
549
+ "This snapshot is damaged or incomplete.",
550
+ "snapshot_corrupt",
551
+ {
552
+ cause: error
553
+ }
554
+ );
555
+ }
556
+ }
557
+ var documentCounter = 0;
558
+ var ReviseDocument = class _ReviseDocument {
559
+ constructor(currentYDoc, currentSession, settings, originalPackage, ownsYDoc) {
560
+ this.currentYDoc = currentYDoc;
561
+ this.currentSession = currentSession;
562
+ this.settings = settings;
563
+ this.originalPackage = originalPackage;
564
+ this.ownsYDoc = ownsYDoc;
565
+ this.disposed = false;
566
+ this.queue = Promise.resolve();
567
+ // ── tools ────────────────────────────────────────────────────────────────
568
+ this.tools = {
569
+ /** Every editing tool's name, description, and JSON input schema. */
570
+ definitions: () => {
571
+ this.assertOpen();
572
+ return this.currentSession.tools.getDefinitions();
573
+ },
574
+ /** Run a known tool with typed input. Expected failures come back as
575
+ * `{ ok: false, error }`; nothing throws for a bad edit. */
576
+ call: (name, ...args) => this.serial(
577
+ async () => this.afterTool(
578
+ name,
579
+ await this.currentSession.tools.execute(name, ...args)
580
+ )
581
+ ),
582
+ /** Run a tool by name with untyped input (agent tool calls). Unknown
583
+ * tools and bad input come back as `{ ok: false, error }` too. */
584
+ run: (name, input = {}, options = {}) => this.serial(async () => {
585
+ const definition = this.currentSession.tools.getDefinitions().find((tool) => tool.name === name);
586
+ if (!definition) {
587
+ return {
588
+ ok: false,
589
+ error: {
590
+ code: "unknown_tool",
591
+ message: `Unknown tool "${name}".`
592
+ }
593
+ };
594
+ }
595
+ const required = definition.inputSchema.required ?? [];
596
+ const filled = required.includes("context_notes") && input.context_notes === void 0 ? { ...input, context_notes: "N/A" } : input;
597
+ return this.afterTool(
598
+ name,
599
+ await this.currentSession.tools.executeDynamic(
600
+ name,
601
+ filled,
602
+ options.direct ? { directMode: true } : void 0
603
+ )
604
+ );
605
+ })
606
+ };
607
+ }
608
+ /** Open a file (path) or bytes. Word files keep their original package,
609
+ * so untouched content exports byte for byte. */
610
+ static async open(source, options = {}) {
611
+ const { bytes, filename } = await readSource(source);
612
+ assertReadable(filename);
613
+ const ydoc = await importYDoc(bytes, filename);
614
+ return _ReviseDocument.build(ydoc, {
615
+ mode: PLAIN_FORMATS.test(filename) ? "editing" : "suggesting",
616
+ ...options,
617
+ title: stem(filename),
618
+ package: /\.docx$/i.test(filename) ? bytes : null,
619
+ owned: true
620
+ });
621
+ }
622
+ /** Start a document from the HTML the tools read and write (headings,
623
+ * `data-style-id="Title"`, lists, tables), or from Markdown. */
624
+ static async create(content = {}, options = {}) {
625
+ let ydoc;
626
+ if (content.html !== void 0) {
627
+ await installDomShims();
628
+ const { AgentHtmlConverter } = await engineModules2.agentHtml();
629
+ const body = /<body[^>]*>([\s\S]*)<\/body>/i.exec(content.html)?.[1] ?? content.html;
630
+ ydoc = createReviseYDoc2();
631
+ seedDocRootWithClaim2(ydoc, AgentHtmlConverter.fromAgentHtml(body));
632
+ } else {
633
+ const markdown = content.markdown ?? (content.title ? `# ${content.title}
634
+ ` : "\n");
635
+ ydoc = await importYDoc(new TextEncoder().encode(markdown), "new.md");
636
+ }
637
+ return _ReviseDocument.build(ydoc, {
638
+ mode: "editing",
639
+ ...options,
640
+ title: content.title ?? "Document",
641
+ package: null,
642
+ owned: true
643
+ });
644
+ }
645
+ /** Restore a document from `snapshot()` (or a bare Y.js update). Options
646
+ * override what the snapshot recorded. */
647
+ static async restore(snapshot2, options = {}) {
648
+ const { header, state, pkg } = decodeSnapshot(snapshot2);
649
+ let ydoc;
650
+ try {
651
+ ydoc = decodeYDoc(state);
652
+ } catch (error) {
653
+ throw new ReviseError("This snapshot is damaged.", "snapshot_corrupt", {
654
+ cause: error
655
+ });
656
+ }
657
+ return _ReviseDocument.build(ydoc, {
658
+ ...header ? {
659
+ documentId: header.documentId,
660
+ mode: header.mode,
661
+ title: header.title,
662
+ ...header.author ? { author: header.author } : {}
663
+ } : {},
664
+ ...options,
665
+ package: pkg,
666
+ owned: true
667
+ });
668
+ }
669
+ /** Wrap an existing Revise Y.Doc, e.g. one shared with an editor. The
670
+ * Y.Doc stays yours: `dispose()` leaves it intact, and `reload()` is not
671
+ * available (it would have to replace it). */
672
+ static async fromYDoc(ydoc, options = {}) {
673
+ let pkg = options.package;
674
+ if (pkg === void 0) {
675
+ const { loadDocxCarrier } = await engineModules2.docxGraft();
676
+ pkg = loadDocxCarrier(ydoc)?.bytes ?? null;
677
+ }
678
+ return _ReviseDocument.build(ydoc, {
679
+ ...options,
680
+ package: pkg,
681
+ owned: false
682
+ });
683
+ }
684
+ static async build(ydoc, options) {
685
+ const { package: pkg, owned, ...rest } = options;
686
+ const settings = {
687
+ ...rest,
688
+ documentId: rest.documentId ?? `revise-${Date.now()}-${++documentCounter}`,
689
+ mode: rest.mode ?? "suggesting",
690
+ title: rest.title ?? "Document"
691
+ };
692
+ return new _ReviseDocument(
693
+ ydoc,
694
+ await _ReviseDocument.startSession(ydoc, settings),
695
+ settings,
696
+ pkg ?? null,
697
+ owned
698
+ );
699
+ }
700
+ static async startSession(ydoc, settings) {
701
+ (await engineModules2.serverSession()).requireDocxRoundTrip(ydoc);
702
+ return createServerDocumentSession(ydoc, {
703
+ documentId: settings.documentId,
704
+ mode: settings.mode,
705
+ ...settings.author ? { agentName: settings.author } : {},
706
+ ...settings.images ? { images: settings.images } : {}
707
+ });
708
+ }
709
+ assertOpen() {
710
+ if (this.disposed) {
711
+ throw new ReviseError("This document was disposed.", "disposed");
712
+ }
713
+ }
714
+ /** Run `work` after every operation already queued on this document. */
715
+ serial(work) {
716
+ if (this.disposed) {
717
+ return Promise.reject(
718
+ new ReviseError("This document was disposed.", "disposed")
719
+ );
720
+ }
721
+ const run = this.queue.then(() => {
722
+ this.assertOpen();
723
+ return work();
724
+ });
725
+ this.queue = run.catch(() => void 0);
726
+ return run;
727
+ }
728
+ /** The live document model. Shared with `@reviseio/editor`. */
729
+ get ydoc() {
730
+ return this.currentYDoc;
731
+ }
732
+ /** The low-level tool session behind `tools`: an escape hatch for what
733
+ * the methods here don't cover. */
734
+ get session() {
735
+ return this.currentSession;
736
+ }
737
+ get mode() {
738
+ return this.currentSession.getMode();
739
+ }
740
+ /** Switch between tracked changes ("suggesting") and direct edits. */
741
+ setMode(mode) {
742
+ return this.serial(() => {
743
+ if (mode === "suggesting") this.currentSession.setSuggestingMode();
744
+ else this.currentSession.setEditingMode();
745
+ this.settings.mode = mode;
746
+ });
747
+ }
748
+ get author() {
749
+ return this.settings.author;
750
+ }
751
+ get documentId() {
752
+ return this.settings.documentId;
753
+ }
754
+ /** The original Word package that export grafts onto, if any. */
755
+ get package() {
756
+ return this.originalPackage;
757
+ }
758
+ /** The complete document (versioned; includes the original Word package)
759
+ * to store or hand to `@reviseio/editor`, as of every operation queued
760
+ * before this call. Restore with `restore()`. */
761
+ snapshot() {
762
+ return this.serial(
763
+ () => encodeSnapshot2(
764
+ {
765
+ documentId: this.settings.documentId,
766
+ mode: this.mode,
767
+ ...this.settings.author ? { author: this.settings.author } : {},
768
+ title: this.settings.title
769
+ },
770
+ encodeYDoc(this.currentYDoc),
771
+ this.originalPackage
772
+ )
773
+ );
774
+ }
775
+ /** An opaque value that changes whenever the document does (including
776
+ * edits that only delete, which a state vector misses). */
777
+ version() {
778
+ this.assertOpen();
779
+ return Y.encodeSnapshot(Y.snapshot(this.currentYDoc));
780
+ }
781
+ /**
782
+ * Re-read the document from new bytes (it changed elsewhere). Block ids
783
+ * carry over only where identity is provable (Word's paragraph ids, or
784
+ * identical content); `changedIds` lists kept ids whose content changed,
785
+ * so earlier reads of them are stale. Replaces the Y.Doc, so it is not
786
+ * available on a document made with `fromYDoc`.
787
+ */
788
+ reload(bytes, filename) {
789
+ return this.serial(async () => {
790
+ if (!this.ownsYDoc) {
791
+ throw new ReviseError(
792
+ "reload() would replace a Y.Doc you own (fromYDoc). Open a new document instead.",
793
+ "borrowed_ydoc"
794
+ );
795
+ }
796
+ const fresh = await relearn(this.currentYDoc, bytes, filename);
797
+ const session = await _ReviseDocument.startSession(
798
+ fresh.ydoc,
799
+ this.settings
800
+ );
801
+ this.currentSession.dispose();
802
+ this.currentYDoc.destroy();
803
+ this.currentYDoc = fresh.ydoc;
804
+ this.currentSession = session;
805
+ if (/\.docx$/i.test(filename)) this.originalPackage = bytes;
806
+ return {
807
+ idsKept: fresh.idsKept,
808
+ idsNew: fresh.idsNew,
809
+ changedIds: fresh.changedIds,
810
+ removedIds: fresh.removedIds
811
+ };
812
+ });
813
+ }
814
+ /** Start the tool session over: forget what the agent has read and
815
+ * searched (e.g. after the host compacts its transcript). */
816
+ resetToolContext() {
817
+ return this.serial(async () => {
818
+ const session = await _ReviseDocument.startSession(
819
+ this.currentYDoc,
820
+ this.settings
821
+ );
822
+ this.currentSession.dispose();
823
+ this.currentSession = session;
824
+ });
825
+ }
826
+ /** Release the tool session. The document can't be used afterwards; a
827
+ * Y.Doc it created is destroyed, one passed to fromYDoc is left alone. */
828
+ dispose() {
829
+ if (this.disposed) return;
830
+ this.disposed = true;
831
+ this.currentSession.dispose();
832
+ if (this.ownsYDoc) this.currentYDoc.destroy();
833
+ }
834
+ /** The document's title for exports: its saved title property (dc:title
835
+ * in a .docx, or set_title's), else the name it was opened under. */
836
+ title() {
837
+ const core = this.currentYDoc.getMap("metadata").get("coreProperties");
838
+ return typeof core?.title === "string" && core.title.trim() ? core.title : this.settings.title;
839
+ }
840
+ /** set_title renames the document in the editor. In a file, the title is
841
+ * the document property (docProps/core.xml in a .docx, the PDF title), so
842
+ * it is saved there too; without this, the tool reported success and the
843
+ * saved file kept its old title. */
844
+ afterTool(name, result) {
845
+ if (name !== "set_title" || !result.ok) return result;
846
+ const metadata = this.currentYDoc.getMap("metadata");
847
+ const title = metadata.get("title");
848
+ if (typeof title !== "string") return result;
849
+ const core = metadata.get("coreProperties");
850
+ this.currentYDoc.transact(() => {
851
+ metadata.set("coreProperties", {
852
+ ...core && typeof core === "object" ? core : {},
853
+ title
854
+ });
855
+ });
856
+ this.settings.title = title;
857
+ return result;
858
+ }
859
+ // ── reading ──────────────────────────────────────────────────────────────
860
+ /** Sections with their block ranges and word counts. */
861
+ outline(options = {}) {
862
+ return this.serial(async () => {
863
+ const measured = await this.currentSession.tools.call(
864
+ "measure_document",
865
+ {
866
+ include_sections: true,
867
+ section_limit: options.limit ?? 200,
868
+ section_offset: options.offset ?? 0
869
+ }
870
+ );
871
+ return measured.data;
872
+ });
873
+ }
874
+ /** Blocks, words, pending changes, and how faithfully it exports to Word. */
875
+ status() {
876
+ return this.serial(async () => {
877
+ const measured = await this.currentSession.tools.call(
878
+ "measure_document",
879
+ {}
880
+ );
881
+ return {
882
+ blocks: measured.data.totalBlocks,
883
+ words: measured.data.wordCount,
884
+ pendingSuggestions: this.currentSession.listSuggestions().length,
885
+ fidelity: this.originalPackage ? await ydocSourceFidelity2(this.currentYDoc) : null
886
+ };
887
+ });
888
+ }
889
+ /** Text-bearing blocks matching a pattern (paragraphs, headings, list
890
+ * items, table-cell paragraphs), with their ids. */
891
+ grep(pattern, options = {}) {
892
+ this.assertOpen();
893
+ const matcher = typeof pattern === "string" ? new RegExp(
894
+ pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"),
895
+ options.ignoreCase ? "gi" : "g"
896
+ ) : new RegExp(
897
+ pattern.source,
898
+ pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`
899
+ );
900
+ return textBlocks(ydocToDocument2(this.currentYDoc)).map((block) => ({
901
+ id: block.id,
902
+ blockIndex: block.topIndex,
903
+ count: (block.text.match(matcher) ?? []).length,
904
+ text: block.text
905
+ })).filter((hit) => hit.count > 0);
906
+ }
907
+ /** What the document holds, in one glance: page setup, header and footer
908
+ * text with live fields, title, headings, lists, tables, review state. */
909
+ summary() {
910
+ return this.serial(
911
+ () => documentSummary(this.currentYDoc, this.currentSession)
912
+ );
913
+ }
914
+ // ── editing ──────────────────────────────────────────────────────────────
915
+ /** Replace every exact occurrence, block by block (tracked changes in
916
+ * suggesting mode). `ids` limits it to those blocks. Replacing text with
917
+ * itself changes nothing and is not a failure. */
918
+ replaceText(find, replace, options = {}) {
919
+ return this.serial(async () => {
920
+ if (!find) {
921
+ throw new ReviseError("Nothing to find: give some text.", "usage");
922
+ }
923
+ const targets = textBlocks(ydocToDocument2(this.currentYDoc)).filter(
924
+ (block) => block.text.includes(find) && (!options.ids || options.ids.includes(block.id))
925
+ );
926
+ if (find === replace) {
927
+ return {
928
+ replaced: 0,
929
+ blocks: targets.map((t) => t.id),
930
+ changeIds: [],
931
+ failures: []
932
+ };
933
+ }
934
+ const failures = [];
935
+ const changeIds = [];
936
+ let replaced = 0;
937
+ for (const target of targets) {
938
+ await this.currentSession.tools.executeDynamic("read_specific_blocks", {
939
+ block_ids: [target.id],
940
+ context_notes: "N/A"
941
+ });
942
+ const result = await this.currentSession.tools.executeDynamic(
943
+ "replace",
944
+ {
945
+ id: target.id,
946
+ replacements: [{ find, replace, occurrence: "all" }],
947
+ context_notes: "N/A"
948
+ }
949
+ );
950
+ if ("error" in result) {
951
+ if (!/^No changes detected/i.test(result.error.message)) {
952
+ failures.push({
953
+ blockId: target.id,
954
+ code: result.error.code,
955
+ message: result.error.message
956
+ });
957
+ }
958
+ } else {
959
+ replaced += target.text.split(find).length - 1;
960
+ changeIds.push(...result.value.suggestionIds ?? []);
961
+ }
962
+ }
963
+ return {
964
+ replaced,
965
+ blocks: targets.map((t) => t.id),
966
+ changeIds,
967
+ failures
968
+ };
969
+ });
970
+ }
971
+ // ── review ───────────────────────────────────────────────────────────────
972
+ /** Pending tracked changes with where they are and what they do. */
973
+ changes(filter = {}) {
974
+ return this.serial(() => this.filteredChanges(filter));
975
+ }
976
+ async filteredChanges(filter = {}) {
977
+ return (await describeChanges(this.currentYDoc, this.currentSession)).filter((c) => filter.section ? inSection(c, filter.section) : true).filter(
978
+ (c) => filter.exceptSection ? !inSection(c, filter.exceptSection) : true
979
+ );
980
+ }
981
+ /** The tracked changes a selection would settle, without settling them. */
982
+ preview(selection) {
983
+ return this.serial(async () => {
984
+ const changes = await this.filteredChanges();
985
+ const ids = await this.selectedIds(selection, changes);
986
+ return ids === "all" ? changes : changes.filter((c) => ids.includes(c.id));
987
+ });
988
+ }
989
+ accept(selection) {
990
+ return this.serial(() => this.settle(selection, true));
991
+ }
992
+ reject(selection) {
993
+ return this.serial(() => this.settle(selection, false));
994
+ }
995
+ async selectedIds(selection, known) {
996
+ const { section, exceptSection, all } = selection;
997
+ const ids = selection.ids ?? [];
998
+ const kinds = [
999
+ ids.length > 0,
1000
+ Boolean(all),
1001
+ Boolean(section || exceptSection)
1002
+ ];
1003
+ if (kinds.filter(Boolean).length !== 1) {
1004
+ throw new ReviseError(
1005
+ "Select tracked changes one way: ids, all, or a section filter (section/exceptSection).",
1006
+ "invalid_selection"
1007
+ );
1008
+ }
1009
+ if (section || exceptSection) {
1010
+ const changes = known ?? await this.filteredChanges();
1011
+ return changes.filter((c) => section ? inSection(c, section) : true).filter((c) => exceptSection ? !inSection(c, exceptSection) : true).map((c) => c.id);
1012
+ }
1013
+ return all ? "all" : ids;
1014
+ }
1015
+ async settle(selection, accept) {
1016
+ const ids = await this.selectedIds(selection);
1017
+ const session = this.currentSession;
1018
+ if (ids === "all") {
1019
+ return accept ? session.acceptAllSuggestions() : session.rejectAllSuggestions();
1020
+ }
1021
+ if (!ids.length) return { resolved: [], missing: [], unresolved: [] };
1022
+ return accept ? session.acceptSuggestions(ids) : session.rejectSuggestions(ids);
1023
+ }
1024
+ /**
1025
+ * What the pending tracked changes do to the text (before → after), or
1026
+ * what changed since `baseline` (a snapshot).
1027
+ */
1028
+ diff(baseline) {
1029
+ return this.serial(async () => {
1030
+ let before;
1031
+ if (baseline) {
1032
+ before = decodeYDoc(decodeSnapshot(baseline).state);
1033
+ } else {
1034
+ before = decodeYDoc(encodeYDoc(this.currentYDoc));
1035
+ const copy = await createServerDocumentSession(before, {
1036
+ documentId: `${this.settings.documentId}-diff`
1037
+ });
1038
+ copy.rejectAllSuggestions();
1039
+ copy.dispose();
1040
+ }
1041
+ return textDiff(
1042
+ await markdownOf(before),
1043
+ await markdownOf(this.currentYDoc)
1044
+ );
1045
+ });
1046
+ }
1047
+ // ── output ───────────────────────────────────────────────────────────────
1048
+ /** Write the document in a format. Reports what the output actually
1049
+ * contains: its pending changes, and for .docx how much of the original
1050
+ * file was carried over byte for byte. */
1051
+ export(options) {
1052
+ return this.serial(async () => {
1053
+ options = { ...options, format: exportFormat(options.format) };
1054
+ const mode = options.format === "docx" ? options.mode ?? "review" : "final";
1055
+ const pending = this.currentSession.listSuggestions().length;
1056
+ const { bytes, outcome } = await exportYDoc(
1057
+ this.currentYDoc,
1058
+ this.originalPackage,
1059
+ { title: this.title(), ...options, mode }
1060
+ );
1061
+ const warnings = [];
1062
+ if (options.format !== "docx" && (options.mode ?? "review") === "review" && pending) {
1063
+ warnings.push(
1064
+ `A .${options.format === "markdown" ? "md" : options.format} file can't hold tracked changes: it shows the ${pending} pending change${pending === 1 ? "" : "s"} accepted.`
1065
+ );
1066
+ }
1067
+ if (outcome?.method === "regenerated") {
1068
+ if (outcome.fallbackCode === "no-original-package") {
1069
+ warnings.push(
1070
+ "The original Word file isn't available (e.g. restored from a bare Y.js state), so the whole .docx was written fresh."
1071
+ );
1072
+ } else if (outcome.fallbackCode !== "not-from-docx") {
1073
+ warnings.push(
1074
+ `Couldn't carry untouched content over from the original Word file (${outcome.fallbackCode}); the whole .docx was written fresh.`
1075
+ );
1076
+ }
1077
+ }
1078
+ return {
1079
+ bytes,
1080
+ format: options.format,
1081
+ mode,
1082
+ pendingTrackedChanges: mode === "final" ? 0 : pending,
1083
+ fidelity: outcome,
1084
+ warnings
1085
+ };
1086
+ });
1087
+ }
1088
+ /** Pages as images, laid out exactly as the editor lays them out. */
1089
+ renderPages(options = {}) {
1090
+ return this.serial(async () => {
1091
+ try {
1092
+ const { renderPages } = await import("./render-QD5VWKSV.js");
1093
+ return await renderPages(
1094
+ this.currentYDoc,
1095
+ this.settings.documentId,
1096
+ options
1097
+ );
1098
+ } catch (error) {
1099
+ if (error instanceof ReviseError) throw error;
1100
+ throw new ReviseError(
1101
+ `Rendering failed: ${error.message}`,
1102
+ "render_failed",
1103
+ { cause: error }
1104
+ );
1105
+ }
1106
+ });
1107
+ }
1108
+ };
1109
+ async function seedYDoc(ydoc, source) {
1110
+ const { bytes, filename } = await readSource(source);
1111
+ assertReadable(filename);
1112
+ return seedYDocFromFile(ydoc, bytes, filename);
1113
+ }
1114
+ async function compare(originalSource, revisedSource, options = {}) {
1115
+ const [originalFile, revisedFile] = await Promise.all([
1116
+ readSource(originalSource),
1117
+ readSource(revisedSource)
1118
+ ]);
1119
+ assertReadable(originalFile.filename);
1120
+ assertReadable(revisedFile.filename);
1121
+ const [original, revised] = await Promise.all([
1122
+ parseDocument2(originalFile.bytes, originalFile.filename),
1123
+ parseDocument2(revisedFile.bytes, revisedFile.filename)
1124
+ ]);
1125
+ const author = options.author ?? baseName(revisedFile.filename);
1126
+ const { compareDocuments } = await engineModules2.compare();
1127
+ const comparison = compareDocuments(original, revised, {
1128
+ authorName: author
1129
+ });
1130
+ if (!comparison) {
1131
+ throw new ReviseError(
1132
+ "These documents are too large to compare.",
1133
+ "too_large"
1134
+ );
1135
+ }
1136
+ const ydoc = createReviseYDoc2();
1137
+ seedDocRootWithClaim2(ydoc, comparison.doc);
1138
+ const isDocx = /\.docx$/i.test(revisedFile.filename);
1139
+ if (comparison.doc.docxSource?.sourceHash && isDocx) {
1140
+ const { storeDocxCarrier } = await engineModules2.docxGraft();
1141
+ await storeDocxCarrier(ydoc, revisedFile.bytes, {
1142
+ hash: comparison.doc.docxSource.sourceHash
1143
+ });
1144
+ }
1145
+ const document = await ReviseDocument.restore(
1146
+ encodeSnapshot2(
1147
+ {
1148
+ documentId: options.documentId ?? `revise-compare-${Date.now()}`,
1149
+ mode: options.mode ?? "suggesting",
1150
+ author,
1151
+ title: stem(revisedFile.filename)
1152
+ },
1153
+ encodeYDoc(ydoc),
1154
+ isDocx ? revisedFile.bytes : null
1155
+ ),
1156
+ options.images ? { images: options.images } : {}
1157
+ );
1158
+ const plainOf = async (y) => new TextDecoder().decode(await exportDocument(y, { format: "txt", mode: "final" })).replace(/\s+/g, " ").trim();
1159
+ const redline = isDocx ? await fileToYDoc(
1160
+ (await exportSessionDocx(document.ydoc, document.package)).bytes,
1161
+ "redline.docx"
1162
+ ) : decodeYDoc(encodeYDoc(document.ydoc));
1163
+ const rejected = decodeYDoc(encodeYDoc(redline));
1164
+ const rejecter = await createServerDocumentSession(rejected, {
1165
+ documentId: `${document.documentId}-verify`
1166
+ });
1167
+ rejecter.rejectAllSuggestions();
1168
+ rejecter.dispose();
1169
+ const [acceptedText, rejectedText, revisedText, originalText] = await Promise.all([
1170
+ plainOf(redline),
1171
+ plainOf(rejected),
1172
+ plainOf(await fileToYDoc(revisedFile.bytes, revisedFile.filename)),
1173
+ plainOf(await fileToYDoc(originalFile.bytes, originalFile.filename))
1174
+ ]);
1175
+ return {
1176
+ document,
1177
+ stats: comparison.stats,
1178
+ untrackedRegions: comparison.untrackedRegions,
1179
+ verified: {
1180
+ acceptAllGivesRevised: acceptedText === revisedText,
1181
+ rejectAllGivesOriginal: rejectedText === originalText
1182
+ }
1183
+ };
1184
+ }
1185
+ async function convert(source, format, options = {}) {
1186
+ format = exportFormat(format);
1187
+ const { bytes, filename } = await readSource(source);
1188
+ assertReadable(filename);
1189
+ const ydoc = await importYDoc(bytes, filename);
1190
+ return format === "pdf" ? exportPdf(ydoc, stem(filename)) : exportDocument(ydoc, {
1191
+ format,
1192
+ mode: options.mode ?? "review",
1193
+ title: stem(filename)
1194
+ });
1195
+ }
1196
+ var EXPORT_FORMATS = ["docx", "pdf", "markdown", "html", "txt"];
1197
+ function exportFormat(format) {
1198
+ const name = String(format).toLowerCase();
1199
+ if (name === "md") return "markdown";
1200
+ if (!EXPORT_FORMATS.includes(name)) {
1201
+ throw new ReviseError(
1202
+ `Unknown export format "${format}". Use one of: ${EXPORT_FORMATS.join(", ")}.`,
1203
+ "unsupported_format"
1204
+ );
1205
+ }
1206
+ return name;
1207
+ }
1208
+ function textBlocks(doc) {
1209
+ const out = [];
1210
+ const textOf = (node) => (node.children ?? []).map((child) => {
1211
+ const c = child;
1212
+ return c.type === "text" && !c.marks?.deletion ? c.text ?? "" : "";
1213
+ }).join("");
1214
+ doc.children.forEach((block, topIndex) => {
1215
+ const visit = (node) => {
1216
+ const n = node;
1217
+ if (!n || typeof n !== "object") return;
1218
+ if ((n.type === "paragraph" || n.type === "list-item") && n.id) {
1219
+ out.push({ id: n.id, text: textOf(n), topIndex });
1220
+ return;
1221
+ }
1222
+ n.children?.forEach(visit);
1223
+ };
1224
+ visit(block);
1225
+ });
1226
+ return out;
1227
+ }
1228
+ async function describeChanges(ydoc, session) {
1229
+ const records = session.listSuggestions();
1230
+ const doc = ydocToDocument2(ydoc);
1231
+ const measured = await session.tools.call("measure_document", {
1232
+ include_sections: true,
1233
+ section_limit: 1e3
1234
+ });
1235
+ const sections = measured.data.sections ?? [];
1236
+ const positionOf = new Map(
1237
+ doc.children.map((block, index) => [block.id, index])
1238
+ );
1239
+ const starts = sections.map((s) => ({ title: s.title, at: positionOf.get(s.startBlockId) })).filter((s) => s.at !== void 0).sort((a, b) => a.at - b.at);
1240
+ const sectionAt = (index) => {
1241
+ let title;
1242
+ for (const start of starts) {
1243
+ if (start.at > index) break;
1244
+ title = start.title;
1245
+ }
1246
+ return title;
1247
+ };
1248
+ const found = /* @__PURE__ */ new Map();
1249
+ doc.children.forEach((block, blockIndex) => {
1250
+ const visit = (node) => {
1251
+ if (!node || typeof node !== "object") return;
1252
+ const n = node;
1253
+ const id = n.marks?.suggestionId ?? n.suggestionId ?? n.structuralSuggestionId;
1254
+ if (id) {
1255
+ const entry = found.get(id) ?? {
1256
+ blockIndex,
1257
+ blockId: block.id ?? "",
1258
+ deleted: "",
1259
+ inserted: ""
1260
+ };
1261
+ if (n.type === "text") {
1262
+ if (n.marks?.deletion) entry.deleted += n.text ?? "";
1263
+ else if (n.marks?.insertion) entry.inserted += n.text ?? "";
1264
+ }
1265
+ found.set(id, entry);
1266
+ }
1267
+ n.children?.forEach(visit);
1268
+ };
1269
+ visit(block);
1270
+ });
1271
+ return records.map((record) => {
1272
+ const at = found.get(record.id);
1273
+ const blockIndex = at?.blockIndex ?? (record.blockId ? positionOf.get(record.blockId) : void 0) ?? (record.tableId ? positionOf.get(record.tableId) : void 0);
1274
+ return {
1275
+ id: record.id,
1276
+ type: record.type ?? "text",
1277
+ author: record.agentName ?? record.authorType ?? "unknown",
1278
+ ...at?.blockId || record.blockId ? { blockId: at?.blockId || record.blockId } : {},
1279
+ ...blockIndex !== void 0 ? { blockIndex, section: sectionAt(blockIndex) } : {},
1280
+ deleted: at?.deleted ?? "",
1281
+ inserted: at?.inserted ?? ""
1282
+ };
1283
+ });
1284
+ }
1285
+ function inSection(entry, query) {
1286
+ if (!entry.section) return false;
1287
+ const title = entry.section.trim().toLowerCase();
1288
+ const q = query.trim().toLowerCase().replace(/\.$/, "");
1289
+ if (/^[\d.]+$/.test(q)) {
1290
+ return new RegExp(`^${q.replace(/\./g, "\\.")}(?:[.\\s)]|$)`).test(title);
1291
+ }
1292
+ return title.includes(q);
1293
+ }
1294
+ async function markdownOf(ydoc) {
1295
+ const bytes = await exportDocument(ydoc, {
1296
+ format: "markdown",
1297
+ mode: "final"
1298
+ });
1299
+ return new TextDecoder().decode(bytes);
1300
+ }
1301
+ function lcsPairs(a, b) {
1302
+ let head = 0;
1303
+ while (head < a.length && head < b.length && a[head] === b[head]) head += 1;
1304
+ let tail = 0;
1305
+ while (tail < a.length - head && tail < b.length - head && a[a.length - 1 - tail] === b[b.length - 1 - tail]) {
1306
+ tail += 1;
1307
+ }
1308
+ const pairs = [];
1309
+ for (let k = 0; k < head; k += 1) pairs.push([k, k]);
1310
+ const midA = a.slice(head, a.length - tail);
1311
+ const midB = b.slice(head, b.length - tail);
1312
+ if (midA.length * midB.length <= 4e6) {
1313
+ for (const [i, j] of lcsTable(midA, midB)) pairs.push([i + head, j + head]);
1314
+ }
1315
+ for (let k = tail; k > 0; k -= 1) {
1316
+ pairs.push([a.length - k, b.length - k]);
1317
+ }
1318
+ return pairs;
1319
+ }
1320
+ function lcsTable(a, b) {
1321
+ const n = a.length;
1322
+ const m = b.length;
1323
+ const table = new Uint32Array((n + 1) * (m + 1));
1324
+ for (let i2 = n - 1; i2 >= 0; i2 -= 1) {
1325
+ for (let j2 = m - 1; j2 >= 0; j2 -= 1) {
1326
+ table[i2 * (m + 1) + j2] = a[i2] === b[j2] ? table[(i2 + 1) * (m + 1) + j2 + 1] + 1 : Math.max(table[(i2 + 1) * (m + 1) + j2], table[i2 * (m + 1) + j2 + 1]);
1327
+ }
1328
+ }
1329
+ const pairs = [];
1330
+ let i = 0;
1331
+ let j = 0;
1332
+ while (i < n && j < m) {
1333
+ if (a[i] === b[j]) {
1334
+ pairs.push([i, j]);
1335
+ i += 1;
1336
+ j += 1;
1337
+ } else if (table[(i + 1) * (m + 1) + j] >= table[i * (m + 1) + j + 1]) {
1338
+ i += 1;
1339
+ } else {
1340
+ j += 1;
1341
+ }
1342
+ }
1343
+ return pairs;
1344
+ }
1345
+ function wordDiff(before, after) {
1346
+ const a = before.split(/(\s+)/);
1347
+ const b = after.split(/(\s+)/);
1348
+ if (a.length * b.length > 4e6) return `[-${before}-]{+${after}+}`;
1349
+ const pairs = lcsPairs(a, b);
1350
+ let i = 0;
1351
+ let j = 0;
1352
+ let output = "";
1353
+ const flush = (ai, bj) => {
1354
+ const removed = a.slice(i, ai).join("");
1355
+ const added = b.slice(j, bj).join("");
1356
+ if (removed) output += `[-${removed}-]`;
1357
+ if (added) output += `{+${added}+}`;
1358
+ };
1359
+ for (const [ai, bj] of pairs) {
1360
+ flush(ai, bj);
1361
+ output += a[ai];
1362
+ i = ai + 1;
1363
+ j = bj + 1;
1364
+ }
1365
+ flush(a.length, b.length);
1366
+ return output;
1367
+ }
1368
+ function textDiff(before, after) {
1369
+ const split = (s) => s.split(/\n+/).filter((line) => line.trim());
1370
+ const a = split(before);
1371
+ const b = split(after);
1372
+ const pairs = lcsPairs(a, b);
1373
+ const hunks = [];
1374
+ let changed = 0;
1375
+ let i = 0;
1376
+ let j = 0;
1377
+ const flush = (ai, bj) => {
1378
+ const removed = a.slice(i, ai);
1379
+ const added = b.slice(j, bj);
1380
+ const paired = Math.min(removed.length, added.length);
1381
+ for (let k = 0; k < paired; k += 1) {
1382
+ hunks.push(`~ ${wordDiff(removed[k], added[k])}`);
1383
+ changed += 1;
1384
+ }
1385
+ for (const line of removed.slice(paired)) {
1386
+ hunks.push(`- ${line}`);
1387
+ changed += 1;
1388
+ }
1389
+ for (const line of added.slice(paired)) {
1390
+ hunks.push(`+ ${line}`);
1391
+ changed += 1;
1392
+ }
1393
+ };
1394
+ for (const [ai, bj] of pairs) {
1395
+ flush(ai, bj);
1396
+ i = ai + 1;
1397
+ j = bj + 1;
1398
+ }
1399
+ flush(a.length, b.length);
1400
+ return { text: hunks.join("\n"), changed };
1401
+ }
1402
+ async function documentSummary(ydoc, session) {
1403
+ const doc = ydocToDocument2(ydoc);
1404
+ const { resolvePageLayoutSettings, getPageDimensionsPx } = await engineModules2.pageLayout();
1405
+ const layout = resolvePageLayoutSettings(doc.pageLayout);
1406
+ const { pageWidth, pageHeight } = getPageDimensionsPx(layout);
1407
+ const inches = (px) => Number((px / 96).toFixed(2));
1408
+ const margins = [
1409
+ layout.marginTopPx,
1410
+ layout.marginRightPx,
1411
+ layout.marginBottomPx,
1412
+ layout.marginLeftPx
1413
+ ].map(inches);
1414
+ const measured = await session.tools.call("measure_document", {});
1415
+ const textWithFields = (node) => {
1416
+ const n = node;
1417
+ if (n?.type === "text") {
1418
+ const field = n.marks?.field?.toLowerCase();
1419
+ if (field) {
1420
+ return field === "numpages" || field === "pages" ? "{PAGES}" : `{${field.toUpperCase()}}`;
1421
+ }
1422
+ return n.text ?? "";
1423
+ }
1424
+ return (n?.children ?? []).map(textWithFields).join(n?.type === "table-row" ? " | " : "");
1425
+ };
1426
+ const chrome = (doc.chrome ?? []).filter((block) => block.type === "header-footer").map((block) => {
1427
+ const b = block;
1428
+ return {
1429
+ side: b.side,
1430
+ slot: b.slot ?? "default",
1431
+ text: textWithFields(block).replace(/\s+/g, " ").trim()
1432
+ };
1433
+ }).filter((c) => c.text);
1434
+ let title;
1435
+ const headings = {};
1436
+ const lists = [];
1437
+ const tables = [];
1438
+ let images = 0;
1439
+ for (const block of doc.children) {
1440
+ if (block.type === "paragraph") {
1441
+ if (block.styleId === "Title" && title === void 0) {
1442
+ title = textWithFields(block);
1443
+ }
1444
+ if (block.heading) {
1445
+ headings[`H${block.heading}`] = (headings[`H${block.heading}`] ?? 0) + 1;
1446
+ }
1447
+ } else if (block.type === "list") {
1448
+ lists.push(block.children.length);
1449
+ } else if (block.type === "table") {
1450
+ tables.push(
1451
+ `${block.children.length}\xD7${block.children[0]?.children.length ?? 0}`
1452
+ );
1453
+ } else if (block.type === "image") {
1454
+ images += 1;
1455
+ }
1456
+ }
1457
+ const pending = session.listSuggestions().length;
1458
+ const data = {
1459
+ pages: measured.data.pageCount,
1460
+ words: measured.data.wordCount,
1461
+ pageSize: layout.pageSize,
1462
+ orientation: layout.orientation,
1463
+ pageInches: [inches(pageWidth), inches(pageHeight)],
1464
+ marginsInches: margins,
1465
+ title,
1466
+ headings,
1467
+ lists,
1468
+ tables,
1469
+ images,
1470
+ headersFooters: chrome,
1471
+ pendingTrackedChanges: pending
1472
+ };
1473
+ const marginText = margins.every((m) => m === margins[0]) ? `${margins[0]} in` : `${margins.join("/")} in (top/right/bottom/left)`;
1474
+ const parts = [
1475
+ title ? `title "${title}"` : "",
1476
+ Object.keys(headings).length ? `headings ${Object.entries(headings).map(([level, n]) => `${n}\xD7${level}`).join(", ")}` : "",
1477
+ lists.length ? `${lists.length} list${lists.length > 1 ? "s" : ""} (${lists.join(", ")} items)` : "",
1478
+ tables.length ? `${tables.length} table${tables.length > 1 ? "s" : ""} (${tables.join(", ")})` : "",
1479
+ images ? `${images} image${images > 1 ? "s" : ""}` : ""
1480
+ ].filter(Boolean);
1481
+ const text = [
1482
+ `${data.pages ?? "?"} page${data.pages === 1 ? "" : "s"}, ${measured.data.wordCount} words; ${layout.pageSize} ${layout.orientation} (${inches(pageWidth)} \xD7 ${inches(pageHeight)} in), margins ${marginText}.`,
1483
+ ...parts.length ? [`Contents: ${parts.join("; ")}.`] : [],
1484
+ ...chrome.map(
1485
+ (c) => `${c.side[0].toUpperCase()}${c.side.slice(1)}${c.slot === "default" ? "" : ` (${c.slot})`}: ${c.text}`
1486
+ ),
1487
+ ...pending ? [`Pending tracked changes: ${pending}.`] : []
1488
+ ].join("\n");
1489
+ return { text, data };
1490
+ }
1491
+
1492
+ // src/embed-sdk/lib/agent.ts
1493
+ function formatToolResult(result, maxChars = 1e5) {
1494
+ if ("error" in result) {
1495
+ return {
1496
+ text: `error ${result.error.code}: ${result.error.message}`,
1497
+ isError: true
1498
+ };
1499
+ }
1500
+ const value = result.value;
1501
+ const lines = [];
1502
+ if (value.message) lines.push(value.message);
1503
+ if (value.suggestionIds?.length) {
1504
+ lines.push(`Tracked changes: ${value.suggestionIds.join(", ")}`);
1505
+ }
1506
+ if (value.data !== null && value.data !== void 0) {
1507
+ const data = JSON.stringify(value.data);
1508
+ if (data !== "{}" && data !== "[]") {
1509
+ lines.push(
1510
+ `data: ${data.length <= 4e3 ? data : compactData(value.data)}`
1511
+ );
1512
+ }
1513
+ }
1514
+ if (value.context?.html) lines.push("", value.context.html);
1515
+ let text = lines.join("\n") || "Done.";
1516
+ if (text.length > maxChars) {
1517
+ text = `${text.slice(0, maxChars)}
1518
+ \u2026 (result cut at ${maxChars} characters; read fewer blocks at a time, e.g. a later index or specific ids)`;
1519
+ }
1520
+ return { text, isError: false };
1521
+ }
1522
+ function compactData(data) {
1523
+ const summarize = (value, depth) => {
1524
+ if (Array.isArray(value)) {
1525
+ return value.length <= 5 && depth < 2 ? value.map((item) => summarize(item, depth + 1)) : `[${value.length} items]`;
1526
+ }
1527
+ if (value && typeof value === "object") {
1528
+ if (depth >= 2) return "{\u2026}";
1529
+ return Object.fromEntries(
1530
+ Object.entries(value).map(([key, inner]) => [
1531
+ key,
1532
+ summarize(inner, depth + 1)
1533
+ ])
1534
+ );
1535
+ }
1536
+ return typeof value === "string" && value.length > 200 ? `${value.slice(0, 200)}\u2026` : value;
1537
+ };
1538
+ return `${JSON.stringify(summarize(data, 0))} (summarized; the full data is larger)`;
1539
+ }
1540
+ function agentTools(doc, options) {
1541
+ const definitions = doc.tools.definitions().filter((d) => !options.include || options.include.includes(d.name)).filter((d) => !options.exclude?.includes(d.name));
1542
+ const allowed = new Set(definitions.map((d) => d.name));
1543
+ const tools = definitions.map((d) => {
1544
+ switch (options.provider) {
1545
+ case "anthropic":
1546
+ return {
1547
+ name: d.name,
1548
+ description: d.description,
1549
+ input_schema: { ...d.inputSchema, type: "object" }
1550
+ };
1551
+ case "openai":
1552
+ return {
1553
+ type: "function",
1554
+ function: {
1555
+ name: d.name,
1556
+ description: d.description,
1557
+ parameters: d.inputSchema
1558
+ }
1559
+ };
1560
+ default:
1561
+ return {
1562
+ type: "function",
1563
+ name: d.name,
1564
+ description: d.description,
1565
+ parameters: d.inputSchema,
1566
+ strict: false
1567
+ };
1568
+ }
1569
+ });
1570
+ const run = async (name, rawInput) => {
1571
+ if (!allowed.has(name)) {
1572
+ return {
1573
+ text: `error unknown_tool: There is no tool named "${name}".`,
1574
+ isError: true
1575
+ };
1576
+ }
1577
+ let input = rawInput;
1578
+ if (typeof rawInput === "string") {
1579
+ try {
1580
+ input = rawInput.trim() ? JSON.parse(rawInput) : {};
1581
+ } catch (error) {
1582
+ return {
1583
+ text: `error invalid_json: The arguments are not valid JSON (${error.message}).`,
1584
+ isError: true
1585
+ };
1586
+ }
1587
+ }
1588
+ if (!input || typeof input !== "object" || Array.isArray(input)) {
1589
+ return {
1590
+ text: "error invalid_arguments: Tool arguments must be a JSON object.",
1591
+ isError: true
1592
+ };
1593
+ }
1594
+ return formatToolResult(
1595
+ await doc.tools.run(name, input),
1596
+ options.maxResultChars
1597
+ );
1598
+ };
1599
+ const handle = async (call) => {
1600
+ switch (options.provider) {
1601
+ case "anthropic": {
1602
+ const c = call;
1603
+ const { text, isError } = await run(c.name, c.input);
1604
+ return {
1605
+ type: "tool_result",
1606
+ tool_use_id: c.id,
1607
+ content: text,
1608
+ ...isError ? { is_error: true } : {}
1609
+ };
1610
+ }
1611
+ case "openai": {
1612
+ const c = call;
1613
+ const { text } = await run(c.function.name, c.function.arguments);
1614
+ return {
1615
+ role: "tool",
1616
+ tool_call_id: c.id,
1617
+ content: text
1618
+ };
1619
+ }
1620
+ default: {
1621
+ const c = call;
1622
+ const { text } = await run(c.name, c.arguments);
1623
+ return {
1624
+ type: "function_call_output",
1625
+ call_id: c.call_id,
1626
+ output: text
1627
+ };
1628
+ }
1629
+ }
1630
+ };
1631
+ return {
1632
+ tools,
1633
+ handle,
1634
+ version: () => doc.version(),
1635
+ reset: () => doc.resetToolContext()
1636
+ };
1637
+ }
1638
+
1639
+ export {
1640
+ relearn,
1641
+ READABLE,
1642
+ formatForPath,
1643
+ exportYDoc,
1644
+ ReviseDocument,
1645
+ seedYDoc,
1646
+ compare,
1647
+ convert,
1648
+ formatToolResult,
1649
+ agentTools
1650
+ };