@superdoc/sdk 2.9.1-next.2 → 2.10.0-next.10
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/README.md +45 -0
- package/dist/action-primitives/doc-index.cjs +6 -4
- package/dist/action-primitives/doc-index.js +6 -4
- package/dist/action-primitives/engine.cjs +6 -2
- package/dist/action-primitives/engine.js +6 -2
- package/dist/action-primitives/receipt.d.ts +4 -0
- package/dist/action-primitives/tools/structure-insert.d.ts +1 -1
- package/dist/agent/actions.cjs +384 -373
- package/dist/agent/actions.d.ts +5 -0
- package/dist/agent/actions.js +385 -374
- package/dist/agent/catalog.cjs +15 -0
- package/dist/agent/catalog.js +15 -0
- package/dist/agent/doc-snapshot.cjs +205 -86
- package/dist/agent/doc-snapshot.d.ts +11 -0
- package/dist/agent/doc-snapshot.js +203 -86
- package/dist/agent/execution-context.cjs +385 -0
- package/dist/agent/execution-context.d.ts +97 -0
- package/dist/agent/execution-context.js +376 -0
- package/dist/agent/runtime.cjs +123 -117
- package/dist/agent/runtime.d.ts +3 -0
- package/dist/agent/runtime.js +124 -118
- package/dist/agent/v2-preset-compat.cjs +5 -1
- package/dist/agent/v2-preset-compat.js +4 -1
- package/dist/embedded-tools.generated.cjs +5 -5
- package/dist/embedded-tools.generated.js +5 -5
- package/dist/generated/client.cjs +2 -0
- package/dist/generated/client.d.ts +85 -0
- package/dist/generated/client.js +2 -0
- package/dist/generated/contract.cjs +1796 -1297
- package/dist/generated/contract.js +1797 -1297
- package/dist/index.cjs +23 -0
- package/dist/index.d.ts +7 -2
- package/dist/index.js +23 -0
- package/dist/presets/core.cjs +1 -1
- package/dist/presets/core.js +1 -1
- package/dist/runtime/document-evidence.cjs +40 -0
- package/dist/runtime/document-evidence.d.ts +13 -0
- package/dist/runtime/document-evidence.js +30 -0
- package/dist/runtime/document-rpc.cjs +27 -0
- package/dist/runtime/document-rpc.d.ts +2 -0
- package/dist/runtime/document-rpc.js +25 -0
- package/dist/runtime/host.cjs +65 -4
- package/dist/runtime/host.d.ts +3 -0
- package/dist/runtime/host.js +66 -5
- package/dist/runtime/process.cjs +38 -0
- package/dist/runtime/process.d.ts +16 -0
- package/dist/runtime/process.js +38 -0
- package/dist/runtime/sdk-version.generated.cjs +1 -1
- package/dist/runtime/sdk-version.generated.d.ts +1 -1
- package/dist/runtime/sdk-version.generated.js +1 -1
- package/package.json +10 -8
- package/tools/catalog.json +89 -0
- package/tools/tools-policy.json +1 -1
- package/tools/tools.anthropic.json +89 -0
- package/tools/tools.generic.json +89 -0
- package/tools/tools.openai.json +89 -0
- package/tools/tools.vercel.json +89 -0
|
@@ -97,6 +97,48 @@ function includesDomain(requested, domain) {
|
|
|
97
97
|
function truncateBlockText(value, limit) {
|
|
98
98
|
return limit == null ? value : value.slice(0, limit);
|
|
99
99
|
}
|
|
100
|
+
function populateTableCellsFromExtract(tables, extractRaw, tableOrdinalBase = 0) {
|
|
101
|
+
const extractRec = asRecord(extractRaw);
|
|
102
|
+
const extractBlocks = Array.isArray(extractRec?.blocks) ? extractRec.blocks : [];
|
|
103
|
+
const cellsByTableNodeId = new Map();
|
|
104
|
+
for (const block of extractBlocks) {
|
|
105
|
+
const rec = asRecord(block);
|
|
106
|
+
const tableContext = asRecord(rec?.tableContext);
|
|
107
|
+
if (!rec || !tableContext)
|
|
108
|
+
continue;
|
|
109
|
+
const tableOrdinal = asNumber(tableContext.tableOrdinal, -1);
|
|
110
|
+
const rowIndex = asNumber(tableContext.rowIndex, -1);
|
|
111
|
+
const columnIndex = asNumber(tableContext.columnIndex, -1);
|
|
112
|
+
if (tableOrdinal < 0 || rowIndex < 0 || columnIndex < 0)
|
|
113
|
+
continue;
|
|
114
|
+
const table = tables[tableOrdinal - tableOrdinalBase];
|
|
115
|
+
if (!table)
|
|
116
|
+
continue;
|
|
117
|
+
const key = `${rowIndex}:${columnIndex}`;
|
|
118
|
+
const text = asString(rec.text);
|
|
119
|
+
const nodeId = asString(rec.nodeId) || undefined;
|
|
120
|
+
const cellMap = cellsByTableNodeId.get(table.nodeId) ??
|
|
121
|
+
new Map();
|
|
122
|
+
const existing = cellMap.get(key);
|
|
123
|
+
cellMap.set(key, {
|
|
124
|
+
rowIndex,
|
|
125
|
+
columnIndex,
|
|
126
|
+
text: existing == null || text.length === 0
|
|
127
|
+
? (existing?.text ?? text)
|
|
128
|
+
: existing.text.length === 0
|
|
129
|
+
? text
|
|
130
|
+
: `${existing.text}\n${text}`,
|
|
131
|
+
nodeId: existing?.nodeId ?? nodeId,
|
|
132
|
+
});
|
|
133
|
+
cellsByTableNodeId.set(table.nodeId, cellMap);
|
|
134
|
+
}
|
|
135
|
+
for (const table of tables) {
|
|
136
|
+
const cellMap = cellsByTableNodeId.get(table.nodeId);
|
|
137
|
+
if (!cellMap)
|
|
138
|
+
continue;
|
|
139
|
+
table.cells = [...cellMap.values()].sort((left, right) => left.rowIndex - right.rowIndex || left.columnIndex - right.columnIndex);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
100
142
|
/**
|
|
101
143
|
* Build a deterministic snapshot of a document. The snapshot uses only
|
|
102
144
|
* read-mode operations from the generated contract — it never mutates state,
|
|
@@ -115,57 +157,33 @@ export async function buildDocumentSnapshot(doc, options = {}) {
|
|
|
115
157
|
// first match so one call both LOCATES and READS the target section.
|
|
116
158
|
let finds;
|
|
117
159
|
if (typeof options.findText === 'string' && options.findText.trim().length > 0) {
|
|
118
|
-
const scanFn = maybeMethod(doc, ['blocks', '
|
|
160
|
+
const scanFn = maybeMethod(doc, ['blocks', 'findText']);
|
|
119
161
|
if (scanFn) {
|
|
120
|
-
// Paginated scan with a hard cap: bounded bridge payloads per page, and
|
|
121
|
-
// an explicit truncated flag instead of a silently-partial `total`.
|
|
122
|
-
const SCAN_PAGE = 2000;
|
|
123
|
-
const SCAN_CAP = 20000;
|
|
124
|
-
const needle = options.findText.toLowerCase();
|
|
125
162
|
const findLimit = options.findLimit != null && options.findLimit > 0 ? options.findLimit : 8;
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
const scanBlocks = Array.isArray(scanRec?.blocks) ? scanRec.blocks : [];
|
|
133
|
-
scanBlocks.forEach((b, i) => {
|
|
134
|
-
const rec = asRecord(b) ?? {};
|
|
135
|
-
const text = asString(rec.text);
|
|
136
|
-
if (!text || !text.toLowerCase().includes(needle))
|
|
137
|
-
return;
|
|
138
|
-
matches.push({
|
|
139
|
-
ordinal: asNumber(rec.ordinal, pageOffset + i) + 1,
|
|
140
|
-
nodeId: asString(rec.nodeId),
|
|
141
|
-
nodeType: asString(rec.nodeType, 'paragraph'),
|
|
142
|
-
preview: text.slice(0, 100),
|
|
143
|
-
index: asNumber(rec.ordinal, pageOffset + i),
|
|
144
|
-
});
|
|
145
|
-
});
|
|
146
|
-
scanned += scanBlocks.length;
|
|
147
|
-
const totalBlocksReported = explicitCount(scanRec ?? {}, 'total');
|
|
148
|
-
if (scanBlocks.length < SCAN_PAGE)
|
|
149
|
-
break; // last page
|
|
150
|
-
if (totalBlocksReported != null && scanned >= totalBlocksReported)
|
|
151
|
-
break;
|
|
152
|
-
if (pageOffset + SCAN_PAGE >= SCAN_CAP) {
|
|
153
|
-
truncated = true;
|
|
154
|
-
break;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
163
|
+
const scanRaw = await safeCall(() => scanFn({ text: options.findText, limit: Math.min(20000, Math.trunc(findLimit)) }), null, recordError('blocks.findText'));
|
|
164
|
+
const scan = asRecord(scanRaw);
|
|
165
|
+
const scanError = asRecord(scan?.scanError);
|
|
166
|
+
if (typeof scanError?.message === 'string')
|
|
167
|
+
recordError('blocks.findText')(scanError.message);
|
|
168
|
+
const matches = Array.isArray(scan?.matches) ? scan.matches : [];
|
|
157
169
|
finds = {
|
|
158
170
|
query: options.findText,
|
|
159
|
-
total:
|
|
160
|
-
...(truncated ? { truncated: true } : {}),
|
|
161
|
-
scannedBlocks:
|
|
162
|
-
matches: matches.
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
171
|
+
total: asNumber(scan?.total),
|
|
172
|
+
...(scan?.truncated === true ? { truncated: true } : {}),
|
|
173
|
+
scannedBlocks: asNumber(scan?.scannedBlocks),
|
|
174
|
+
matches: matches.map((match) => {
|
|
175
|
+
const rec = asRecord(match) ?? {};
|
|
176
|
+
const nodeId = asString(rec.nodeId);
|
|
177
|
+
return {
|
|
178
|
+
ordinal: asNumber(rec.ordinal) + 1,
|
|
179
|
+
...(nodeId ? { nodeId } : {}),
|
|
180
|
+
nodeType: asString(rec.nodeType, 'paragraph'),
|
|
181
|
+
preview: asString(rec.preview),
|
|
182
|
+
};
|
|
183
|
+
}),
|
|
166
184
|
};
|
|
167
|
-
if (
|
|
168
|
-
blockOffset = Math.max(0,
|
|
185
|
+
if (typeof scan?.firstMatchOrdinal === 'number' && options.blockOffset == null) {
|
|
186
|
+
blockOffset = Math.max(0, scan.firstMatchOrdinal - 2);
|
|
169
187
|
blockLimit = options.blockLimit != null && options.blockLimit > 0 ? options.blockLimit : 12;
|
|
170
188
|
}
|
|
171
189
|
}
|
|
@@ -402,46 +420,7 @@ export async function buildDocumentSnapshot(doc, options = {}) {
|
|
|
402
420
|
const extractFn = maybeMethod(doc, ['extract']);
|
|
403
421
|
if (tables.length > 0 && extractFn) {
|
|
404
422
|
const extractRaw = await safeCall(() => extractFn({}), null, recordError('extract'));
|
|
405
|
-
|
|
406
|
-
const extractBlocks = Array.isArray(extractRec?.blocks) ? extractRec.blocks : [];
|
|
407
|
-
const cellsByTableNodeId = new Map();
|
|
408
|
-
for (const block of extractBlocks) {
|
|
409
|
-
const rec = asRecord(block);
|
|
410
|
-
const tableContext = asRecord(rec?.tableContext);
|
|
411
|
-
if (!rec || !tableContext)
|
|
412
|
-
continue;
|
|
413
|
-
const tableOrdinal = asNumber(tableContext.tableOrdinal, -1);
|
|
414
|
-
const rowIndex = asNumber(tableContext.rowIndex, -1);
|
|
415
|
-
const columnIndex = asNumber(tableContext.columnIndex, -1);
|
|
416
|
-
if (tableOrdinal < 0 || rowIndex < 0 || columnIndex < 0)
|
|
417
|
-
continue;
|
|
418
|
-
const table = tables[tableOrdinal - precedingTableCount];
|
|
419
|
-
if (!table)
|
|
420
|
-
continue;
|
|
421
|
-
const key = `${rowIndex}:${columnIndex}`;
|
|
422
|
-
const text = asString(rec.text);
|
|
423
|
-
const nodeId = asString(rec.nodeId) || undefined;
|
|
424
|
-
const cellMap = cellsByTableNodeId.get(table.nodeId) ??
|
|
425
|
-
new Map();
|
|
426
|
-
const existing = cellMap.get(key);
|
|
427
|
-
cellMap.set(key, {
|
|
428
|
-
rowIndex,
|
|
429
|
-
columnIndex,
|
|
430
|
-
text: existing == null || text.length === 0
|
|
431
|
-
? (existing?.text ?? text)
|
|
432
|
-
: existing.text.length === 0
|
|
433
|
-
? text
|
|
434
|
-
: `${existing.text}\n${text}`,
|
|
435
|
-
nodeId: existing?.nodeId ?? nodeId,
|
|
436
|
-
});
|
|
437
|
-
cellsByTableNodeId.set(table.nodeId, cellMap);
|
|
438
|
-
}
|
|
439
|
-
for (const table of tables) {
|
|
440
|
-
const cellMap = cellsByTableNodeId.get(table.nodeId);
|
|
441
|
-
if (!cellMap)
|
|
442
|
-
continue;
|
|
443
|
-
table.cells = [...cellMap.values()].sort((left, right) => left.rowIndex - right.rowIndex || left.columnIndex - right.columnIndex);
|
|
444
|
-
}
|
|
423
|
+
populateTableCellsFromExtract(tables, extractRaw, precedingTableCount);
|
|
445
424
|
}
|
|
446
425
|
// Optionally enrich table cells with per-run formatting (opt-in; one
|
|
447
426
|
// query.match per cell, bounded) so a reader can match a cell's pattern.
|
|
@@ -746,6 +725,144 @@ export async function buildDocumentSnapshot(doc, options = {}) {
|
|
|
746
725
|
...(finds ? { finds } : {}),
|
|
747
726
|
};
|
|
748
727
|
}
|
|
728
|
+
export class MutationSnapshotError extends Error {
|
|
729
|
+
code;
|
|
730
|
+
constructor(code, message) {
|
|
731
|
+
super(message);
|
|
732
|
+
this.code = code;
|
|
733
|
+
this.name = 'MutationSnapshotError';
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
const MUTATION_BLOCK_PAGE_SIZE = 1000;
|
|
737
|
+
const MUTATION_BLOCK_PAGE_CONCURRENCY = 4;
|
|
738
|
+
const MUTATION_NON_TABLE_DOMAINS = [
|
|
739
|
+
'blocks',
|
|
740
|
+
'lists',
|
|
741
|
+
'comments',
|
|
742
|
+
'trackedChanges',
|
|
743
|
+
'sections',
|
|
744
|
+
'headerFooters',
|
|
745
|
+
'styles',
|
|
746
|
+
'contentControls',
|
|
747
|
+
'fields',
|
|
748
|
+
'hyperlinks',
|
|
749
|
+
'bookmarks',
|
|
750
|
+
'permissionRanges',
|
|
751
|
+
'images',
|
|
752
|
+
];
|
|
753
|
+
/**
|
|
754
|
+
* Build the complete block index used to authorize mutations. Inspection
|
|
755
|
+
* snapshots stay windowed; mutation selectors must not treat that presentation
|
|
756
|
+
* window as the end of the document. Reads remain bounded and the revision is
|
|
757
|
+
* checked before the combined snapshot is returned.
|
|
758
|
+
*/
|
|
759
|
+
export async function buildMutationSnapshot(doc, options = {}) {
|
|
760
|
+
const requestedDomains = options.includeDomains == null || options.includeDomains.length === 0 ? null : new Set(options.includeDomains);
|
|
761
|
+
const includeTables = requestedDomains == null || requestedDomains.has('tables');
|
|
762
|
+
const firstDomains = requestedDomains == null
|
|
763
|
+
? MUTATION_NON_TABLE_DOMAINS
|
|
764
|
+
: [...requestedDomains].filter((domain) => domain !== 'tables').concat('blocks');
|
|
765
|
+
const first = await buildDocumentSnapshot(doc, {
|
|
766
|
+
includeDomains: firstDomains,
|
|
767
|
+
blockOffset: 0,
|
|
768
|
+
blockLimit: MUTATION_BLOCK_PAGE_SIZE,
|
|
769
|
+
});
|
|
770
|
+
assertMutationBlockRead(first, 0);
|
|
771
|
+
const total = first.counts.blocks;
|
|
772
|
+
if (first.blocks.length > total) {
|
|
773
|
+
throw new MutationSnapshotError('SNAPSHOT_INCOMPLETE', `cannot build complete mutation snapshot: document reports ${total} blocks but returned ${first.blocks.length}`);
|
|
774
|
+
}
|
|
775
|
+
if (first.blocks.length === 0 && total > 0) {
|
|
776
|
+
throw new MutationSnapshotError('SNAPSHOT_INCOMPLETE', `cannot build complete mutation snapshot: document reports ${total} blocks but returned none`);
|
|
777
|
+
}
|
|
778
|
+
const blocks = [...first.blocks];
|
|
779
|
+
const diagnostics = [...first.diagnostics];
|
|
780
|
+
const pageStride = first.blocks.length || MUTATION_BLOCK_PAGE_SIZE;
|
|
781
|
+
const offsets = first.blocks.length === total
|
|
782
|
+
? []
|
|
783
|
+
: Array.from({ length: Math.ceil((total - pageStride) / pageStride) }, (_, index) => pageStride * (index + 1));
|
|
784
|
+
for (let index = 0; index < offsets.length; index += MUTATION_BLOCK_PAGE_CONCURRENCY) {
|
|
785
|
+
const batchOffsets = offsets.slice(index, index + MUTATION_BLOCK_PAGE_CONCURRENCY);
|
|
786
|
+
const pages = await Promise.all(batchOffsets.map((blockOffset) => buildDocumentSnapshot(doc, {
|
|
787
|
+
includeDomains: ['blocks'],
|
|
788
|
+
blockOffset,
|
|
789
|
+
blockLimit: pageStride,
|
|
790
|
+
})));
|
|
791
|
+
for (let pageIndex = 0; pageIndex < pages.length; pageIndex += 1) {
|
|
792
|
+
const page = pages[pageIndex];
|
|
793
|
+
const blockOffset = batchOffsets[pageIndex];
|
|
794
|
+
assertMutationBlockRead(page, blockOffset);
|
|
795
|
+
assertMutationSnapshotRevision(first.revision, page.revision);
|
|
796
|
+
if (page.blocks.length === 0) {
|
|
797
|
+
throw new MutationSnapshotError('SNAPSHOT_INCOMPLETE', `cannot build complete mutation snapshot: block pagination stopped at ${blockOffset} of ${total} blocks`);
|
|
798
|
+
}
|
|
799
|
+
blocks.push(...page.blocks);
|
|
800
|
+
diagnostics.push(...page.diagnostics);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
if (blocks.length !== total) {
|
|
804
|
+
throw new MutationSnapshotError('SNAPSHOT_INCOMPLETE', `cannot build complete mutation snapshot: expected ${total} blocks but materialized ${blocks.length}`);
|
|
805
|
+
}
|
|
806
|
+
const tables = includeTables ? await buildMutationTables(doc, blocks, diagnostics) : [];
|
|
807
|
+
const finalIdentity = await buildDocumentSnapshot(doc, { countsOnly: true });
|
|
808
|
+
assertMutationSnapshotRevision(first.revision, finalIdentity.revision);
|
|
809
|
+
return {
|
|
810
|
+
...first,
|
|
811
|
+
blocks,
|
|
812
|
+
tables,
|
|
813
|
+
diagnostics,
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
async function buildMutationTables(doc, blocks, diagnostics) {
|
|
817
|
+
const tableBlocks = blocks.filter((block) => block.nodeType === 'table');
|
|
818
|
+
const tables = tableBlocks.map((block, index) => ({
|
|
819
|
+
nodeId: block.nodeId,
|
|
820
|
+
ordinal: index + 1,
|
|
821
|
+
rows: 0,
|
|
822
|
+
columns: 0,
|
|
823
|
+
cells: [],
|
|
824
|
+
}));
|
|
825
|
+
const getTable = maybeMethod(doc, ['tables', 'get']);
|
|
826
|
+
if (getTable) {
|
|
827
|
+
for (let index = 0; index < tables.length; index += MUTATION_BLOCK_PAGE_CONCURRENCY) {
|
|
828
|
+
const batch = tables.slice(index, index + MUTATION_BLOCK_PAGE_CONCURRENCY);
|
|
829
|
+
await Promise.all(batch.map(async (table) => {
|
|
830
|
+
try {
|
|
831
|
+
const raw = asRecord(await getTable({ nodeId: table.nodeId }));
|
|
832
|
+
table.rows = asNumber(raw?.rows);
|
|
833
|
+
table.columns = asNumber(raw?.columns);
|
|
834
|
+
}
|
|
835
|
+
catch (error) {
|
|
836
|
+
diagnostics.push({
|
|
837
|
+
section: `tables.get:${table.nodeId}`,
|
|
838
|
+
message: error instanceof Error ? error.message : String(error),
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
}));
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
const extract = maybeMethod(doc, ['extract']);
|
|
845
|
+
if (tables.length > 0 && extract) {
|
|
846
|
+
try {
|
|
847
|
+
populateTableCellsFromExtract(tables, await extract({}));
|
|
848
|
+
}
|
|
849
|
+
catch (error) {
|
|
850
|
+
diagnostics.push({ section: 'extract', message: error instanceof Error ? error.message : String(error) });
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
return tables;
|
|
854
|
+
}
|
|
855
|
+
function assertMutationBlockRead(snapshot, blockOffset) {
|
|
856
|
+
const failure = snapshot.diagnostics.find((diagnostic) => diagnostic.section === 'blocks.list');
|
|
857
|
+
if (!failure)
|
|
858
|
+
return;
|
|
859
|
+
throw new MutationSnapshotError('SNAPSHOT_INCOMPLETE', `cannot build complete mutation snapshot: block page at offset ${blockOffset} failed: ${failure.message}`);
|
|
860
|
+
}
|
|
861
|
+
function assertMutationSnapshotRevision(expected, actual) {
|
|
862
|
+
if (expected === 'unknown' || actual === 'unknown' || expected === actual)
|
|
863
|
+
return;
|
|
864
|
+
throw new MutationSnapshotError('REVISION_CONFLICT', `document changed while resolving mutation selectors (${expected} -> ${actual}); retry the action`);
|
|
865
|
+
}
|
|
749
866
|
/**
|
|
750
867
|
* Structured ambiguity error returned when multiple candidates match a
|
|
751
868
|
* selector and the plan required uniqueness.
|