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