@superdoc-dev/sdk 1.19.1 → 1.20.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/dist/action-primitives/doc-index.cjs +215 -0
- package/dist/action-primitives/doc-index.d.ts +83 -0
- package/dist/action-primitives/doc-index.d.ts.map +1 -0
- package/dist/action-primitives/doc-index.js +211 -0
- package/dist/action-primitives/engine.cjs +204 -0
- package/dist/action-primitives/engine.d.ts +71 -0
- package/dist/action-primitives/engine.d.ts.map +1 -0
- package/dist/action-primitives/engine.js +196 -0
- package/dist/action-primitives/receipt.cjs +39 -0
- package/dist/action-primitives/receipt.d.ts +49 -0
- package/dist/action-primitives/receipt.d.ts.map +1 -0
- package/dist/action-primitives/receipt.js +32 -0
- package/dist/action-primitives/resolve.cjs +252 -0
- package/dist/action-primitives/resolve.d.ts +56 -0
- package/dist/action-primitives/resolve.d.ts.map +1 -0
- package/dist/action-primitives/resolve.js +246 -0
- package/dist/action-primitives/session-cache.cjs +43 -0
- package/dist/action-primitives/session-cache.d.ts +19 -0
- package/dist/action-primitives/session-cache.d.ts.map +1 -0
- package/dist/action-primitives/session-cache.js +37 -0
- package/dist/action-primitives/tools/list-transform.cjs +661 -0
- package/dist/action-primitives/tools/list-transform.d.ts +98 -0
- package/dist/action-primitives/tools/list-transform.d.ts.map +1 -0
- package/dist/action-primitives/tools/list-transform.js +656 -0
- package/dist/action-primitives/tools/structure-insert.cjs +1343 -0
- package/dist/action-primitives/tools/structure-insert.d.ts +183 -0
- package/dist/action-primitives/tools/structure-insert.d.ts.map +1 -0
- package/dist/action-primitives/tools/structure-insert.js +1338 -0
- package/dist/action-primitives/tools/text-transform.cjs +669 -0
- package/dist/action-primitives/tools/text-transform.d.ts +64 -0
- package/dist/action-primitives/tools/text-transform.d.ts.map +1 -0
- package/dist/action-primitives/tools/text-transform.js +664 -0
- package/dist/action-primitives/types.d.ts +36 -0
- package/dist/action-primitives/types.d.ts.map +1 -0
- package/dist/action-primitives/types.js +15 -0
- package/dist/agent/actions.cjs +5381 -0
- package/dist/agent/actions.d.ts +404 -0
- package/dist/agent/actions.d.ts.map +1 -0
- package/dist/agent/actions.js +5373 -0
- package/dist/agent/catalog.cjs +483 -0
- package/dist/agent/catalog.d.ts +103 -0
- package/dist/agent/catalog.d.ts.map +1 -0
- package/dist/agent/catalog.js +471 -0
- package/dist/agent/doc-snapshot.cjs +663 -0
- package/dist/agent/doc-snapshot.d.ts +247 -0
- package/dist/agent/doc-snapshot.d.ts.map +1 -0
- package/dist/agent/doc-snapshot.js +657 -0
- package/dist/agent/index.d.ts +16 -0
- package/dist/agent/index.d.ts.map +1 -0
- package/dist/agent/index.js +15 -0
- package/dist/agent/ir.cjs +170 -0
- package/dist/agent/ir.d.ts +216 -0
- package/dist/agent/ir.d.ts.map +1 -0
- package/dist/agent/ir.js +181 -0
- package/dist/agent/operation-catalog.cjs +418 -0
- package/dist/agent/operation-catalog.d.ts +36 -0
- package/dist/agent/operation-catalog.d.ts.map +1 -0
- package/dist/agent/operation-catalog.js +446 -0
- package/dist/agent/runtime.cjs +501 -0
- package/dist/agent/runtime.d.ts +120 -0
- package/dist/agent/runtime.d.ts.map +1 -0
- package/dist/agent/runtime.js +493 -0
- package/dist/embedded-prompts.generated.cjs +13 -0
- package/dist/embedded-prompts.generated.d.ts +4 -0
- package/dist/embedded-prompts.generated.d.ts.map +1 -0
- package/dist/embedded-prompts.generated.js +9 -0
- package/dist/generated/client.cjs +18 -20
- package/dist/generated/client.d.ts +723 -3223
- package/dist/generated/client.d.ts.map +1 -1
- package/dist/generated/client.js +18 -20
- package/dist/generated/contract.cjs +12898 -18467
- package/dist/generated/contract.d.ts.map +1 -1
- package/dist/generated/contract.js +12898 -18467
- package/dist/generated/intent-dispatch.generated.cjs +0 -1
- package/dist/generated/intent-dispatch.generated.d.ts.map +1 -1
- package/dist/generated/intent-dispatch.generated.js +0 -1
- package/dist/index.cjs +3 -7
- package/dist/index.d.ts +8 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -8
- package/dist/presets/core.cjs +454 -0
- package/dist/presets/core.d.ts +20 -0
- package/dist/presets/core.d.ts.map +1 -0
- package/dist/presets/core.js +447 -0
- package/dist/presets.cjs +55 -8
- package/dist/presets.d.ts +39 -8
- package/dist/presets.d.ts.map +1 -1
- package/dist/presets.js +53 -8
- package/dist/prompts/mcp-prompt.md +23 -0
- package/dist/prompts/system-prompt.md +108 -0
- package/dist/runtime/transport-common.cjs +8 -0
- package/dist/runtime/transport-common.d.ts.map +1 -1
- package/dist/runtime/transport-common.js +8 -0
- package/dist/tools.cjs +46 -11
- package/dist/tools.d.ts +55 -8
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +45 -13
- package/package.json +9 -8
- package/tools/__pycache__/__init__.cpython-312.pyc +0 -0
- package/tools/__pycache__/intent_dispatch_generated.cpython-312.pyc +0 -0
- package/tools/catalog.json +22 -178
- package/tools/intent_dispatch_generated.py +0 -2
- package/tools/tools-policy.json +1 -1
- package/tools/tools.anthropic.json +22 -158
- package/tools/tools.generic.json +23 -160
- package/tools/tools.openai.json +22 -158
- package/tools/tools.vercel.json +22 -158
|
@@ -0,0 +1,663 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function asString(v, fallback = '') {
|
|
4
|
+
return typeof v === 'string' ? v : fallback;
|
|
5
|
+
}
|
|
6
|
+
function asNumber(v, fallback = 0) {
|
|
7
|
+
return typeof v === 'number' && Number.isFinite(v) ? v : fallback;
|
|
8
|
+
}
|
|
9
|
+
function asRecord(v) {
|
|
10
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v) ? v : null;
|
|
11
|
+
}
|
|
12
|
+
async function safeCall(thunk, fallback, onError) {
|
|
13
|
+
try {
|
|
14
|
+
return await thunk();
|
|
15
|
+
}
|
|
16
|
+
catch (err) {
|
|
17
|
+
onError(err);
|
|
18
|
+
return fallback;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function maybeMethod(api, path) {
|
|
22
|
+
let cursor = api;
|
|
23
|
+
for (const token of path) {
|
|
24
|
+
// Function-shaped intermediates are legal: RPC/proxy document handles
|
|
25
|
+
// (e.g. the browser doc-bridge) expose namespaces as callables.
|
|
26
|
+
if (!cursor || (typeof cursor !== 'object' && typeof cursor !== 'function'))
|
|
27
|
+
return null;
|
|
28
|
+
cursor = cursor[token];
|
|
29
|
+
}
|
|
30
|
+
return typeof cursor === 'function' ? cursor : null;
|
|
31
|
+
}
|
|
32
|
+
function explicitCount(counts, ...keys) {
|
|
33
|
+
for (const key of keys) {
|
|
34
|
+
const value = counts[key];
|
|
35
|
+
if (typeof value === 'number' && Number.isFinite(value))
|
|
36
|
+
return value;
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
function includesDomain(requested, domain) {
|
|
41
|
+
return requested == null || requested.has(domain);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Build a deterministic snapshot of a document. The snapshot uses only
|
|
45
|
+
* read-mode operations from the generated contract — it never mutates state,
|
|
46
|
+
* and any individual section that fails is captured as a diagnostic so the
|
|
47
|
+
* rest of the snapshot remains usable.
|
|
48
|
+
*/
|
|
49
|
+
async function buildDocumentSnapshot(doc, options = {}) {
|
|
50
|
+
const diagnostics = [];
|
|
51
|
+
const recordError = (section) => (err) => {
|
|
52
|
+
diagnostics.push({ section, message: err instanceof Error ? err.message : String(err) });
|
|
53
|
+
};
|
|
54
|
+
const blockOffset = Math.max(0, options.blockOffset ?? 0);
|
|
55
|
+
const blockLimit = options.blockLimit ?? options.blockTextLimit ?? 1000;
|
|
56
|
+
const listLimit = options.listLimit ?? 1000;
|
|
57
|
+
const tableLimit = options.tableLimit ?? 500;
|
|
58
|
+
const commentLimit = options.commentLimit ?? 500;
|
|
59
|
+
const trackedChangeLimit = options.trackedChangeLimit ?? 500;
|
|
60
|
+
const requestedDomains = Array.isArray(options.includeDomains) && options.includeDomains.length > 0 ? new Set(options.includeDomains) : null;
|
|
61
|
+
const blockNodeTypes = new Set(Array.isArray(options.blockNodeTypes)
|
|
62
|
+
? options.blockNodeTypes.filter((value) => typeof value === 'string' && value.length > 0)
|
|
63
|
+
: []);
|
|
64
|
+
const infoFn = maybeMethod(doc, ['info']);
|
|
65
|
+
const info = infoFn ? await safeCall(() => infoFn({}), null, recordError('info')) : null;
|
|
66
|
+
const infoRec = asRecord(info);
|
|
67
|
+
const counts = asRecord(infoRec?.counts) ?? {};
|
|
68
|
+
const revision = asString(infoRec?.revision, 'unknown');
|
|
69
|
+
const countsFromInfo = {
|
|
70
|
+
blocks: explicitCount(counts, 'blocks') ?? 0,
|
|
71
|
+
paragraphs: explicitCount(counts, 'paragraphs') ?? 0,
|
|
72
|
+
headings: explicitCount(counts, 'headings') ?? 0,
|
|
73
|
+
tables: explicitCount(counts, 'tables') ?? 0,
|
|
74
|
+
lists: explicitCount(counts, 'lists') ?? 0,
|
|
75
|
+
images: explicitCount(counts, 'images') ?? 0,
|
|
76
|
+
comments: explicitCount(counts, 'comments') ?? 0,
|
|
77
|
+
trackedChanges: explicitCount(counts, 'trackedChanges') ?? 0,
|
|
78
|
+
sections: explicitCount(counts, 'sections') ?? 0,
|
|
79
|
+
fields: explicitCount(counts, 'fields') ?? 0,
|
|
80
|
+
hyperlinks: explicitCount(counts, 'hyperlinks') ?? 0,
|
|
81
|
+
bookmarks: explicitCount(counts, 'bookmarks') ?? 0,
|
|
82
|
+
contentControls: explicitCount(counts, 'contentControls', 'sdtFields') ?? 0,
|
|
83
|
+
permissionRanges: explicitCount(counts, 'permissionRanges') ?? 0,
|
|
84
|
+
styles: explicitCount(counts, 'styles') ?? 0,
|
|
85
|
+
headers: explicitCount(counts, 'headers') ?? 0,
|
|
86
|
+
footers: explicitCount(counts, 'footers') ?? 0,
|
|
87
|
+
};
|
|
88
|
+
if (options.countsOnly === true) {
|
|
89
|
+
return {
|
|
90
|
+
revision,
|
|
91
|
+
counts: countsFromInfo,
|
|
92
|
+
blocks: [],
|
|
93
|
+
lists: [],
|
|
94
|
+
tables: [],
|
|
95
|
+
comments: [],
|
|
96
|
+
trackedChanges: [],
|
|
97
|
+
sections: [],
|
|
98
|
+
headerFooters: [],
|
|
99
|
+
styles: [],
|
|
100
|
+
contentControls: [],
|
|
101
|
+
fields: [],
|
|
102
|
+
hyperlinks: [],
|
|
103
|
+
bookmarks: [],
|
|
104
|
+
permissionRanges: [],
|
|
105
|
+
images: [],
|
|
106
|
+
diagnostics,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
// Blocks
|
|
110
|
+
const includeBlocksInSnapshot = includesDomain(requestedDomains, 'blocks');
|
|
111
|
+
const needBlocksRead = includeBlocksInSnapshot || includesDomain(requestedDomains, 'tables');
|
|
112
|
+
const blocksFn = maybeMethod(doc, ['blocks', 'list']);
|
|
113
|
+
const blocksRaw = needBlocksRead && blocksFn
|
|
114
|
+
? await safeCall(() => blocksFn({ offset: blockOffset, limit: blockLimit, includeText: includeBlocksInSnapshot }), null, recordError('blocks.list'))
|
|
115
|
+
: null;
|
|
116
|
+
const blocksRec = asRecord(blocksRaw);
|
|
117
|
+
const totalBlocks = explicitCount(blocksRec ?? {}, 'total') ?? countsFromInfo.blocks;
|
|
118
|
+
const rawBlocks = Array.isArray(blocksRec?.blocks) ? blocksRec.blocks : [];
|
|
119
|
+
const normalizedBlocks = rawBlocks.map((b, index) => {
|
|
120
|
+
const rec = asRecord(b) ?? {};
|
|
121
|
+
return {
|
|
122
|
+
// doc-api block.ordinal is 0-based; the model-facing convention is
|
|
123
|
+
// 1-based (matches paragraphOrdinal/tableOrdinal and ordinal selectors).
|
|
124
|
+
ordinal: asNumber(rec.ordinal, blockOffset + index) + 1,
|
|
125
|
+
nodeId: asString(rec.nodeId),
|
|
126
|
+
nodeType: asString(rec.nodeType, 'paragraph'),
|
|
127
|
+
text: asString(rec.text),
|
|
128
|
+
textPreview: typeof rec.textPreview === 'string' ? rec.textPreview : null,
|
|
129
|
+
styleId: typeof rec.styleId === 'string' ? rec.styleId : null,
|
|
130
|
+
headingLevel: typeof rec.headingLevel === 'number' ? rec.headingLevel : undefined,
|
|
131
|
+
...(asRecord(rec.numbering)
|
|
132
|
+
? {
|
|
133
|
+
numbering: {
|
|
134
|
+
marker: asString(asRecord(rec.numbering).marker) || null,
|
|
135
|
+
path: Array.isArray(asRecord(rec.numbering).path) ? asRecord(rec.numbering).path : null,
|
|
136
|
+
kind: asString(asRecord(rec.numbering).kind) || null,
|
|
137
|
+
},
|
|
138
|
+
}
|
|
139
|
+
: {}),
|
|
140
|
+
};
|
|
141
|
+
});
|
|
142
|
+
let blocks = includeBlocksInSnapshot && blockNodeTypes.size > 0
|
|
143
|
+
? normalizedBlocks.filter((block) => blockNodeTypes.has(block.nodeType))
|
|
144
|
+
: includeBlocksInSnapshot
|
|
145
|
+
? normalizedBlocks
|
|
146
|
+
: [];
|
|
147
|
+
if (includeBlocksInSnapshot && options.omitEmptyBlocks === true) {
|
|
148
|
+
// Drop empty paragraphs/list-items only; structural blocks (tables) keep
|
|
149
|
+
// their slot even with empty text.
|
|
150
|
+
blocks = blocks.filter((block) => block.text.trim().length > 0 || (block.nodeType !== 'paragraph' && block.nodeType !== 'listItem'));
|
|
151
|
+
}
|
|
152
|
+
if (includeBlocksInSnapshot && options.dropTextPreview === true) {
|
|
153
|
+
blocks = blocks.map((block) => ({ ...block, textPreview: null }));
|
|
154
|
+
}
|
|
155
|
+
// Lists
|
|
156
|
+
const listsFn = maybeMethod(doc, ['lists', 'list']);
|
|
157
|
+
const listsRaw = includesDomain(requestedDomains, 'lists') && listsFn
|
|
158
|
+
? await safeCall(() => listsFn({ offset: 0, limit: listLimit }), null, recordError('lists.list'))
|
|
159
|
+
: null;
|
|
160
|
+
const listsRec = asRecord(listsRaw);
|
|
161
|
+
const listItemsArr = Array.isArray(listsRec?.items) ? listsRec.items : [];
|
|
162
|
+
const listsById = new Map();
|
|
163
|
+
for (const item of listItemsArr) {
|
|
164
|
+
const rec = asRecord(item);
|
|
165
|
+
if (!rec)
|
|
166
|
+
continue;
|
|
167
|
+
const listId = asString(rec.listId, 'list:unknown');
|
|
168
|
+
if (!listsById.has(listId)) {
|
|
169
|
+
listsById.set(listId, {
|
|
170
|
+
listId,
|
|
171
|
+
kind: asString(rec.kind, 'ordered'),
|
|
172
|
+
items: [],
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
const addr = asRecord(rec.address);
|
|
176
|
+
listsById.get(listId).items.push({
|
|
177
|
+
nodeId: asString(addr?.nodeId),
|
|
178
|
+
ordinal: asNumber(rec.ordinal),
|
|
179
|
+
level: asNumber(rec.level),
|
|
180
|
+
text: asString(rec.text),
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
const lists = [...listsById.values()];
|
|
184
|
+
// Tables: derive table block list from `blocks`, then query shape per table.
|
|
185
|
+
const tableBlocks = normalizedBlocks.filter((b) => b.nodeType === 'table');
|
|
186
|
+
const tablesFn = maybeMethod(doc, ['tables', 'get']);
|
|
187
|
+
const tables = [];
|
|
188
|
+
if (includesDomain(requestedDomains, 'tables')) {
|
|
189
|
+
for (let i = 0; i < Math.min(tableBlocks.length, tableLimit); i += 1) {
|
|
190
|
+
const block = tableBlocks[i];
|
|
191
|
+
if (!tablesFn) {
|
|
192
|
+
tables.push({ nodeId: block.nodeId, ordinal: i + 1, rows: 0, columns: 0, cells: [] });
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
const tableRaw = await safeCall(() => tablesFn({ nodeId: block.nodeId }), null, recordError(`tables.get:${block.nodeId}`));
|
|
196
|
+
const rec = asRecord(tableRaw);
|
|
197
|
+
tables.push({
|
|
198
|
+
nodeId: block.nodeId,
|
|
199
|
+
ordinal: i + 1,
|
|
200
|
+
rows: asNumber(rec?.rows),
|
|
201
|
+
columns: asNumber(rec?.columns),
|
|
202
|
+
cells: [],
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
const extractFn = maybeMethod(doc, ['extract']);
|
|
207
|
+
if (tables.length > 0 && extractFn) {
|
|
208
|
+
const extractRaw = await safeCall(() => extractFn({}), null, recordError('extract'));
|
|
209
|
+
const extractRec = asRecord(extractRaw);
|
|
210
|
+
const extractBlocks = Array.isArray(extractRec?.blocks) ? extractRec.blocks : [];
|
|
211
|
+
const cellsByTableNodeId = new Map();
|
|
212
|
+
for (const block of extractBlocks) {
|
|
213
|
+
const rec = asRecord(block);
|
|
214
|
+
const tableContext = asRecord(rec?.tableContext);
|
|
215
|
+
if (!rec || !tableContext)
|
|
216
|
+
continue;
|
|
217
|
+
const tableOrdinal = asNumber(tableContext.tableOrdinal, -1);
|
|
218
|
+
const rowIndex = asNumber(tableContext.rowIndex, -1);
|
|
219
|
+
const columnIndex = asNumber(tableContext.columnIndex, -1);
|
|
220
|
+
if (tableOrdinal < 0 || rowIndex < 0 || columnIndex < 0)
|
|
221
|
+
continue;
|
|
222
|
+
const table = tables[tableOrdinal];
|
|
223
|
+
if (!table)
|
|
224
|
+
continue;
|
|
225
|
+
const key = `${rowIndex}:${columnIndex}`;
|
|
226
|
+
const text = asString(rec.text);
|
|
227
|
+
const nodeId = asString(rec.nodeId) || undefined;
|
|
228
|
+
const cellMap = cellsByTableNodeId.get(table.nodeId) ??
|
|
229
|
+
new Map();
|
|
230
|
+
const existing = cellMap.get(key);
|
|
231
|
+
cellMap.set(key, {
|
|
232
|
+
rowIndex,
|
|
233
|
+
columnIndex,
|
|
234
|
+
text: existing == null || text.length === 0
|
|
235
|
+
? (existing?.text ?? text)
|
|
236
|
+
: existing.text.length === 0
|
|
237
|
+
? text
|
|
238
|
+
: `${existing.text}\n${text}`,
|
|
239
|
+
nodeId: existing?.nodeId ?? nodeId,
|
|
240
|
+
});
|
|
241
|
+
cellsByTableNodeId.set(table.nodeId, cellMap);
|
|
242
|
+
}
|
|
243
|
+
for (const table of tables) {
|
|
244
|
+
const cellMap = cellsByTableNodeId.get(table.nodeId);
|
|
245
|
+
if (!cellMap)
|
|
246
|
+
continue;
|
|
247
|
+
table.cells = [...cellMap.values()].sort((left, right) => left.rowIndex - right.rowIndex || left.columnIndex - right.columnIndex);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
// Comments
|
|
251
|
+
const commentsFn = maybeMethod(doc, ['comments', 'list']);
|
|
252
|
+
const commentsRaw = includesDomain(requestedDomains, 'comments') && commentsFn
|
|
253
|
+
? await safeCall(() => commentsFn({ includeResolved: true, offset: 0, limit: commentLimit }), null, recordError('comments.list'))
|
|
254
|
+
: null;
|
|
255
|
+
const commentsRec = asRecord(commentsRaw);
|
|
256
|
+
const commentItems = Array.isArray(commentsRec?.items) ? commentsRec.items : [];
|
|
257
|
+
const comments = commentItems.map((c) => {
|
|
258
|
+
const rec = asRecord(c) ?? {};
|
|
259
|
+
const target = asRecord(rec.target);
|
|
260
|
+
const segments = Array.isArray(target?.segments) ? target.segments : [];
|
|
261
|
+
return {
|
|
262
|
+
id: asString(rec.id),
|
|
263
|
+
text: asString(rec.text),
|
|
264
|
+
status: asString(rec.status, 'open'),
|
|
265
|
+
anchoredText: typeof rec.anchoredText === 'string' ? rec.anchoredText : undefined,
|
|
266
|
+
segments: segments.flatMap((seg) => {
|
|
267
|
+
const segRec = asRecord(seg);
|
|
268
|
+
const range = asRecord(segRec?.range);
|
|
269
|
+
if (!segRec)
|
|
270
|
+
return [];
|
|
271
|
+
return [
|
|
272
|
+
{
|
|
273
|
+
blockId: asString(segRec.blockId),
|
|
274
|
+
start: asNumber(range?.start),
|
|
275
|
+
end: asNumber(range?.end),
|
|
276
|
+
},
|
|
277
|
+
];
|
|
278
|
+
}),
|
|
279
|
+
};
|
|
280
|
+
});
|
|
281
|
+
// Tracked changes
|
|
282
|
+
const trackedFn = maybeMethod(doc, ['trackChanges', 'list']);
|
|
283
|
+
const trackedRaw = includesDomain(requestedDomains, 'trackedChanges') && trackedFn
|
|
284
|
+
? await safeCall(() => trackedFn({ offset: 0, limit: trackedChangeLimit }), null, recordError('trackChanges.list'))
|
|
285
|
+
: null;
|
|
286
|
+
const trackedRec = asRecord(trackedRaw);
|
|
287
|
+
const trackedItems = Array.isArray(trackedRec?.items) ? trackedRec.items : [];
|
|
288
|
+
const trackedChanges = trackedItems.map((t) => {
|
|
289
|
+
const rec = asRecord(t) ?? {};
|
|
290
|
+
const addr = asRecord(rec.address);
|
|
291
|
+
const story = asRecord(addr?.story);
|
|
292
|
+
return {
|
|
293
|
+
id: asString(rec.id),
|
|
294
|
+
type: asString(rec.type, 'insert'),
|
|
295
|
+
excerpt: typeof rec.excerpt === 'string' ? rec.excerpt : undefined,
|
|
296
|
+
author: typeof rec.author === 'string' ? rec.author : undefined,
|
|
297
|
+
date: typeof rec.date === 'string' ? rec.date : undefined,
|
|
298
|
+
story: typeof story?.storyType === 'string' ? story.storyType : undefined,
|
|
299
|
+
};
|
|
300
|
+
});
|
|
301
|
+
// Sections (best effort; not all SDK builds expose a sections.list)
|
|
302
|
+
const sectionsFn = maybeMethod(doc, ['sections', 'list']);
|
|
303
|
+
let sections = [];
|
|
304
|
+
if (includesDomain(requestedDomains, 'sections') && sectionsFn) {
|
|
305
|
+
const sectionsRaw = await safeCall(() => sectionsFn({}), null, recordError('sections.list'));
|
|
306
|
+
const rec = asRecord(sectionsRaw);
|
|
307
|
+
const items = Array.isArray(rec?.items) ? rec.items : [];
|
|
308
|
+
sections = items.map((s, index) => {
|
|
309
|
+
const srec = asRecord(s) ?? {};
|
|
310
|
+
const pageSize = asRecord(srec.pageSize);
|
|
311
|
+
return {
|
|
312
|
+
ordinal: asNumber(srec.ordinal, index + 1),
|
|
313
|
+
sectionId: typeof srec.sectionId === 'string' ? srec.sectionId : undefined,
|
|
314
|
+
startNodeId: typeof srec.startNodeId === 'string' ? srec.startNodeId : undefined,
|
|
315
|
+
endNodeId: typeof srec.endNodeId === 'string' ? srec.endNodeId : undefined,
|
|
316
|
+
pageSize: pageSize ? { width: asNumber(pageSize.width), height: asNumber(pageSize.height) } : undefined,
|
|
317
|
+
};
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
// Header / footer (best effort)
|
|
321
|
+
const headerFooterFn = maybeMethod(doc, ['headerFooters', 'list']);
|
|
322
|
+
let headerFooters = [];
|
|
323
|
+
if (includesDomain(requestedDomains, 'headerFooters') && headerFooterFn) {
|
|
324
|
+
const raw = await safeCall(() => headerFooterFn({}), null, recordError('headerFooters.list'));
|
|
325
|
+
const rec = asRecord(raw);
|
|
326
|
+
const items = Array.isArray(rec?.items) ? rec.items : [];
|
|
327
|
+
headerFooters = items.flatMap((hf) => {
|
|
328
|
+
const r = asRecord(hf);
|
|
329
|
+
if (!r)
|
|
330
|
+
return [];
|
|
331
|
+
const kind = asString(r.kind, 'header');
|
|
332
|
+
return [
|
|
333
|
+
{
|
|
334
|
+
kind: kind === 'footer' ? 'footer' : 'header',
|
|
335
|
+
sectionId: typeof r.sectionId === 'string' ? r.sectionId : undefined,
|
|
336
|
+
sectionIndex: typeof r.sectionIndex === 'number' ? r.sectionIndex : undefined,
|
|
337
|
+
variant: typeof r.variant === 'string' ? r.variant : undefined,
|
|
338
|
+
blocks: [],
|
|
339
|
+
},
|
|
340
|
+
];
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
// Styles
|
|
344
|
+
const stylesFn = maybeMethod(doc, ['styles', 'list']);
|
|
345
|
+
let styles = [];
|
|
346
|
+
if (includesDomain(requestedDomains, 'styles') && stylesFn) {
|
|
347
|
+
const raw = await safeCall(() => stylesFn({}), null, recordError('styles.list'));
|
|
348
|
+
const rec = asRecord(raw);
|
|
349
|
+
const items = Array.isArray(rec?.items) ? rec.items : [];
|
|
350
|
+
styles = items.flatMap((it) => {
|
|
351
|
+
const r = asRecord(it);
|
|
352
|
+
if (!r)
|
|
353
|
+
return [];
|
|
354
|
+
return [
|
|
355
|
+
{
|
|
356
|
+
styleId: asString(r.styleId),
|
|
357
|
+
name: asString(r.name, asString(r.styleId)),
|
|
358
|
+
type: typeof r.type === 'string' ? r.type : undefined,
|
|
359
|
+
basedOn: typeof r.basedOn === 'string' ? r.basedOn : undefined,
|
|
360
|
+
},
|
|
361
|
+
];
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
// Content controls
|
|
365
|
+
const contentControlsFn = maybeMethod(doc, ['contentControls', 'list']);
|
|
366
|
+
let contentControls = [];
|
|
367
|
+
if (includesDomain(requestedDomains, 'contentControls') && contentControlsFn) {
|
|
368
|
+
const raw = await safeCall(() => contentControlsFn({}), null, recordError('contentControls.list'));
|
|
369
|
+
const rec = asRecord(raw);
|
|
370
|
+
const items = Array.isArray(rec?.items) ? rec.items : [];
|
|
371
|
+
contentControls = items.flatMap((it) => {
|
|
372
|
+
const r = asRecord(it);
|
|
373
|
+
if (!r)
|
|
374
|
+
return [];
|
|
375
|
+
return [
|
|
376
|
+
{
|
|
377
|
+
id: asString(r.id),
|
|
378
|
+
type: typeof r.type === 'string' ? r.type : undefined,
|
|
379
|
+
alias: typeof r.alias === 'string' ? r.alias : undefined,
|
|
380
|
+
tag: typeof r.tag === 'string' ? r.tag : undefined,
|
|
381
|
+
text: typeof r.text === 'string' ? r.text : undefined,
|
|
382
|
+
},
|
|
383
|
+
];
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
// Fields
|
|
387
|
+
const fieldsFn = maybeMethod(doc, ['fields', 'list']);
|
|
388
|
+
let fields = [];
|
|
389
|
+
if (includesDomain(requestedDomains, 'fields') && fieldsFn) {
|
|
390
|
+
const raw = await safeCall(() => fieldsFn({}), null, recordError('fields.list'));
|
|
391
|
+
const rec = asRecord(raw);
|
|
392
|
+
const items = Array.isArray(rec?.items) ? rec.items : [];
|
|
393
|
+
fields = items.flatMap((it) => {
|
|
394
|
+
const r = asRecord(it);
|
|
395
|
+
if (!r)
|
|
396
|
+
return [];
|
|
397
|
+
return [
|
|
398
|
+
{
|
|
399
|
+
id: asString(r.id),
|
|
400
|
+
type: typeof r.type === 'string' ? r.type : undefined,
|
|
401
|
+
result: typeof r.result === 'string' ? r.result : undefined,
|
|
402
|
+
},
|
|
403
|
+
];
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
// Hyperlinks
|
|
407
|
+
const hyperlinksFn = maybeMethod(doc, ['hyperlinks', 'list']);
|
|
408
|
+
let hyperlinks = [];
|
|
409
|
+
if (includesDomain(requestedDomains, 'hyperlinks') && hyperlinksFn) {
|
|
410
|
+
const raw = await safeCall(() => hyperlinksFn({}), null, recordError('hyperlinks.list'));
|
|
411
|
+
const rec = asRecord(raw);
|
|
412
|
+
const items = Array.isArray(rec?.items) ? rec.items : [];
|
|
413
|
+
hyperlinks = items.flatMap((it) => {
|
|
414
|
+
const r = asRecord(it);
|
|
415
|
+
if (!r)
|
|
416
|
+
return [];
|
|
417
|
+
return [
|
|
418
|
+
{
|
|
419
|
+
id: asString(r.id),
|
|
420
|
+
url: asString(r.url),
|
|
421
|
+
display: typeof r.display === 'string' ? r.display : undefined,
|
|
422
|
+
nodeId: typeof r.nodeId === 'string' ? r.nodeId : undefined,
|
|
423
|
+
},
|
|
424
|
+
];
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
// Bookmarks
|
|
428
|
+
const bookmarksFn = maybeMethod(doc, ['bookmarks', 'list']);
|
|
429
|
+
let bookmarks = [];
|
|
430
|
+
if (includesDomain(requestedDomains, 'bookmarks') && bookmarksFn) {
|
|
431
|
+
const raw = await safeCall(() => bookmarksFn({}), null, recordError('bookmarks.list'));
|
|
432
|
+
const rec = asRecord(raw);
|
|
433
|
+
const items = Array.isArray(rec?.items) ? rec.items : [];
|
|
434
|
+
bookmarks = items.flatMap((it) => {
|
|
435
|
+
const r = asRecord(it);
|
|
436
|
+
if (!r)
|
|
437
|
+
return [];
|
|
438
|
+
return [
|
|
439
|
+
{
|
|
440
|
+
id: asString(r.id),
|
|
441
|
+
name: asString(r.name, asString(r.id)),
|
|
442
|
+
startNodeId: typeof r.startNodeId === 'string' ? r.startNodeId : undefined,
|
|
443
|
+
endNodeId: typeof r.endNodeId === 'string' ? r.endNodeId : undefined,
|
|
444
|
+
},
|
|
445
|
+
];
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
// Permission ranges
|
|
449
|
+
const prFn = maybeMethod(doc, ['permissionRanges', 'list']);
|
|
450
|
+
let permissionRanges = [];
|
|
451
|
+
if (includesDomain(requestedDomains, 'permissionRanges') && prFn) {
|
|
452
|
+
const raw = await safeCall(() => prFn({}), null, recordError('permissionRanges.list'));
|
|
453
|
+
const rec = asRecord(raw);
|
|
454
|
+
const items = Array.isArray(rec?.items) ? rec.items : [];
|
|
455
|
+
permissionRanges = items.flatMap((it) => {
|
|
456
|
+
const r = asRecord(it);
|
|
457
|
+
if (!r)
|
|
458
|
+
return [];
|
|
459
|
+
return [
|
|
460
|
+
{
|
|
461
|
+
id: asString(r.id),
|
|
462
|
+
startNodeId: typeof r.startNodeId === 'string' ? r.startNodeId : undefined,
|
|
463
|
+
endNodeId: typeof r.endNodeId === 'string' ? r.endNodeId : undefined,
|
|
464
|
+
editorOrEditorGroup: typeof r.editorOrEditorGroup === 'string' ? r.editorOrEditorGroup : undefined,
|
|
465
|
+
},
|
|
466
|
+
];
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
// Images
|
|
470
|
+
const imagesFn = maybeMethod(doc, ['images', 'list']);
|
|
471
|
+
let images = [];
|
|
472
|
+
if (includesDomain(requestedDomains, 'images') && imagesFn) {
|
|
473
|
+
const raw = await safeCall(() => imagesFn({}), null, recordError('images.list'));
|
|
474
|
+
const rec = asRecord(raw);
|
|
475
|
+
const items = Array.isArray(rec?.items) ? rec.items : [];
|
|
476
|
+
images = items.flatMap((it) => {
|
|
477
|
+
const r = asRecord(it);
|
|
478
|
+
if (!r)
|
|
479
|
+
return [];
|
|
480
|
+
return [
|
|
481
|
+
{
|
|
482
|
+
imageId: asString(r.imageId, asString(r.id)),
|
|
483
|
+
nodeId: typeof r.nodeId === 'string' ? r.nodeId : undefined,
|
|
484
|
+
alt: typeof r.alt === 'string' ? r.alt : undefined,
|
|
485
|
+
caption: typeof r.caption === 'string' ? r.caption : undefined,
|
|
486
|
+
},
|
|
487
|
+
];
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
return {
|
|
491
|
+
revision,
|
|
492
|
+
counts: {
|
|
493
|
+
blocks: countsFromInfo.blocks || totalBlocks || normalizedBlocks.length,
|
|
494
|
+
paragraphs: countsFromInfo.paragraphs || normalizedBlocks.filter((b) => b.nodeType === 'paragraph').length,
|
|
495
|
+
headings: countsFromInfo.headings || normalizedBlocks.filter((b) => b.nodeType === 'heading').length,
|
|
496
|
+
tables: countsFromInfo.tables || tableBlocks.length,
|
|
497
|
+
lists: countsFromInfo.lists || lists.length,
|
|
498
|
+
images: countsFromInfo.images || images.length,
|
|
499
|
+
comments: countsFromInfo.comments || comments.length,
|
|
500
|
+
trackedChanges: countsFromInfo.trackedChanges || trackedChanges.length,
|
|
501
|
+
sections: countsFromInfo.sections || sections.length,
|
|
502
|
+
fields: countsFromInfo.fields || fields.length,
|
|
503
|
+
hyperlinks: countsFromInfo.hyperlinks || hyperlinks.length,
|
|
504
|
+
bookmarks: countsFromInfo.bookmarks || bookmarks.length,
|
|
505
|
+
contentControls: countsFromInfo.contentControls || contentControls.length,
|
|
506
|
+
permissionRanges: countsFromInfo.permissionRanges || permissionRanges.length,
|
|
507
|
+
styles: countsFromInfo.styles || styles.length,
|
|
508
|
+
headers: countsFromInfo.headers || headerFooters.filter((h) => h.kind === 'header').length,
|
|
509
|
+
footers: countsFromInfo.footers || headerFooters.filter((h) => h.kind === 'footer').length,
|
|
510
|
+
},
|
|
511
|
+
blocks,
|
|
512
|
+
lists,
|
|
513
|
+
tables,
|
|
514
|
+
comments,
|
|
515
|
+
trackedChanges,
|
|
516
|
+
sections,
|
|
517
|
+
headerFooters,
|
|
518
|
+
styles,
|
|
519
|
+
contentControls,
|
|
520
|
+
fields,
|
|
521
|
+
hyperlinks,
|
|
522
|
+
bookmarks,
|
|
523
|
+
permissionRanges,
|
|
524
|
+
images,
|
|
525
|
+
diagnostics,
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Structured ambiguity error returned when multiple candidates match a
|
|
530
|
+
* selector and the plan required uniqueness.
|
|
531
|
+
*/
|
|
532
|
+
class AmbiguousSelectorError extends Error {
|
|
533
|
+
code = 'AMBIGUOUS_SELECTOR';
|
|
534
|
+
candidates;
|
|
535
|
+
constructor(message, candidates) {
|
|
536
|
+
super(message);
|
|
537
|
+
this.name = 'AmbiguousSelectorError';
|
|
538
|
+
this.candidates = candidates;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
542
|
+
* Resolve a deterministic selector against the snapshot. Returns the matching
|
|
543
|
+
* block ids in document order. Selectors that target entities return
|
|
544
|
+
* entity-domain matches by id.
|
|
545
|
+
*/
|
|
546
|
+
function resolveSnapshotSelector(snapshot, selector) {
|
|
547
|
+
const nonEmptyParagraphs = snapshot.blocks.filter((block) => block.nodeType === 'paragraph' && block.text.trim().length > 0);
|
|
548
|
+
const firstHeading = snapshot.blocks.find((block) => block.nodeType === 'heading');
|
|
549
|
+
const bodyParagraphs = firstHeading == null
|
|
550
|
+
? nonEmptyParagraphs
|
|
551
|
+
: snapshot.blocks.filter((block) => block.nodeType === 'paragraph' && block.text.trim().length > 0 && block.ordinal > firstHeading.ordinal);
|
|
552
|
+
switch (selector.kind) {
|
|
553
|
+
case 'ref':
|
|
554
|
+
return snapshot.blocks.filter((b) => b.nodeId === selector.ref).map((b) => b.nodeId);
|
|
555
|
+
case 'nodeId':
|
|
556
|
+
return snapshot.blocks.filter((b) => b.nodeId === selector.nodeId).map((b) => b.nodeId);
|
|
557
|
+
case 'tableCell': {
|
|
558
|
+
const table = snapshot.tables[selector.tableOrdinal - 1];
|
|
559
|
+
if (!table)
|
|
560
|
+
return [];
|
|
561
|
+
const cell = table.cells.find((entry) => entry.rowIndex === selector.rowIndex && entry.columnIndex === selector.columnIndex);
|
|
562
|
+
return cell?.nodeId ? [cell.nodeId] : [];
|
|
563
|
+
}
|
|
564
|
+
case 'textSearch': {
|
|
565
|
+
const matchMode = selector.match ?? 'all';
|
|
566
|
+
const occurrence = selector.occurrence ?? 1;
|
|
567
|
+
const caseSensitive = selector.caseSensitive === true;
|
|
568
|
+
const allowedNodeTypes = new Set(selector.nodeTypes ?? ['paragraph', 'heading', 'listItem']);
|
|
569
|
+
const terms = selector.terms.filter((term) => term.trim().length > 0);
|
|
570
|
+
if (terms.length === 0)
|
|
571
|
+
return [];
|
|
572
|
+
const normalizedTerms = caseSensitive ? terms : terms.map((term) => term.toLocaleLowerCase());
|
|
573
|
+
const matches = snapshot.blocks.filter((block) => {
|
|
574
|
+
if (!allowedNodeTypes.has(block.nodeType))
|
|
575
|
+
return false;
|
|
576
|
+
if (block.text.trim().length === 0)
|
|
577
|
+
return false;
|
|
578
|
+
const haystack = caseSensitive ? block.text : block.text.toLocaleLowerCase();
|
|
579
|
+
const hits = normalizedTerms.filter((term) => haystack.includes(term)).length;
|
|
580
|
+
return matchMode === 'any' ? hits > 0 : hits === normalizedTerms.length;
|
|
581
|
+
});
|
|
582
|
+
const target = matches[occurrence - 1];
|
|
583
|
+
return target ? [target.nodeId] : [];
|
|
584
|
+
}
|
|
585
|
+
case 'ordinal': {
|
|
586
|
+
switch (selector.ordinalKind) {
|
|
587
|
+
case 'blockOrdinal': {
|
|
588
|
+
const target = snapshot.blocks[selector.value - 1];
|
|
589
|
+
return target ? [target.nodeId] : [];
|
|
590
|
+
}
|
|
591
|
+
case 'paragraphOrdinal': {
|
|
592
|
+
const target = nonEmptyParagraphs[selector.value - 1];
|
|
593
|
+
return target ? [target.nodeId] : [];
|
|
594
|
+
}
|
|
595
|
+
case 'bodyParagraphOrdinal': {
|
|
596
|
+
const target = (bodyParagraphs.length > 0 ? bodyParagraphs : nonEmptyParagraphs)[selector.value - 1];
|
|
597
|
+
return target ? [target.nodeId] : [];
|
|
598
|
+
}
|
|
599
|
+
case 'headingOrdinal': {
|
|
600
|
+
const headings = snapshot.blocks.filter((b) => b.nodeType === 'heading');
|
|
601
|
+
const target = headings[selector.value - 1];
|
|
602
|
+
return target ? [target.nodeId] : [];
|
|
603
|
+
}
|
|
604
|
+
case 'tableOrdinal': {
|
|
605
|
+
const target = snapshot.tables[selector.value - 1];
|
|
606
|
+
return target ? [target.nodeId] : [];
|
|
607
|
+
}
|
|
608
|
+
case 'listOrdinal': {
|
|
609
|
+
const target = snapshot.lists[selector.value - 1];
|
|
610
|
+
return target ? target.items.map((item) => item.nodeId) : [];
|
|
611
|
+
}
|
|
612
|
+
case 'sectionOrdinal': {
|
|
613
|
+
const target = snapshot.sections[selector.value - 1];
|
|
614
|
+
return target?.startNodeId ? [target.startNodeId] : [];
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
return [];
|
|
618
|
+
}
|
|
619
|
+
case 'entity':
|
|
620
|
+
switch (selector.entityType) {
|
|
621
|
+
case 'comment':
|
|
622
|
+
return snapshot.comments.filter((c) => c.id === selector.entityId).map((c) => c.id);
|
|
623
|
+
case 'trackedChange':
|
|
624
|
+
return snapshot.trackedChanges.filter((c) => c.id === selector.entityId).map((c) => c.id);
|
|
625
|
+
case 'bookmark':
|
|
626
|
+
return snapshot.bookmarks.filter((b) => b.id === selector.entityId).map((b) => b.id);
|
|
627
|
+
case 'image':
|
|
628
|
+
return snapshot.images.filter((i) => i.imageId === selector.entityId).map((i) => i.imageId);
|
|
629
|
+
case 'hyperlink':
|
|
630
|
+
return snapshot.hyperlinks.filter((h) => h.id === selector.entityId).map((h) => h.id);
|
|
631
|
+
case 'field':
|
|
632
|
+
return snapshot.fields.filter((f) => f.id === selector.entityId).map((f) => f.id);
|
|
633
|
+
}
|
|
634
|
+
return [];
|
|
635
|
+
case 'placement':
|
|
636
|
+
if (selector.at === 'document_start') {
|
|
637
|
+
const first = snapshot.blocks[0];
|
|
638
|
+
return first ? [first.nodeId] : [];
|
|
639
|
+
}
|
|
640
|
+
const last = snapshot.blocks[snapshot.blocks.length - 1];
|
|
641
|
+
return last ? [last.nodeId] : [];
|
|
642
|
+
case 'relative': {
|
|
643
|
+
const targetIds = resolveSnapshotSelector(snapshot, selector.target);
|
|
644
|
+
const offset = selector.position === 'before' ? -1 : 1;
|
|
645
|
+
const matches = [];
|
|
646
|
+
for (const targetId of targetIds) {
|
|
647
|
+
const index = snapshot.blocks.findIndex((block) => block.nodeId === targetId);
|
|
648
|
+
if (index === -1)
|
|
649
|
+
continue;
|
|
650
|
+
const sibling = snapshot.blocks[index + offset];
|
|
651
|
+
if (sibling != null)
|
|
652
|
+
matches.push(sibling.nodeId);
|
|
653
|
+
}
|
|
654
|
+
return [...new Set(matches)];
|
|
655
|
+
}
|
|
656
|
+
case 'document':
|
|
657
|
+
return snapshot.blocks.map((b) => b.nodeId);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
exports.AmbiguousSelectorError = AmbiguousSelectorError;
|
|
662
|
+
exports.buildDocumentSnapshot = buildDocumentSnapshot;
|
|
663
|
+
exports.resolveSnapshotSelector = resolveSnapshotSelector;
|