@superdoc/sdk 2.23.0-next.8 → 2.23.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/tools/structure-insert.cjs +3 -5
- package/dist/action-primitives/tools/structure-insert.js +3 -5
- package/dist/action-primitives/tools/text-transform.cjs +3 -5
- package/dist/action-primitives/tools/text-transform.js +3 -5
- package/dist/actions/define.cjs +15 -17
- package/dist/actions/define.js +1 -3
- package/dist/agent/actions.cjs +438 -52
- package/dist/agent/actions.d.ts +13 -0
- package/dist/agent/actions.js +394 -11
- package/dist/agent/runtime.cjs +7 -9
- package/dist/agent/runtime.js +1 -3
- package/dist/agent/v2-preset-compat.cjs +5 -7
- package/dist/agent/v2-preset-compat.js +1 -3
- package/dist/embedded-prompts.generated.cjs +1 -1
- package/dist/embedded-prompts.generated.js +1 -1
- package/dist/embedded-tools.generated.cjs +5 -5
- package/dist/embedded-tools.generated.js +5 -5
- package/dist/generated/client.d.ts +193 -177
- package/dist/generated/contract.cjs +1 -1
- package/dist/generated/contract.js +1 -1
- package/dist/guards.cjs +7 -0
- package/dist/guards.d.ts +1 -0
- package/dist/guards.js +3 -0
- package/dist/presets/core.cjs +10 -12
- package/dist/presets/core.js +1 -3
- package/dist/presets/legacy.cjs +6 -8
- package/dist/presets/legacy.js +1 -3
- package/dist/prompts/system-prompt.md +6 -6
- 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 +6 -6
- package/tools/catalog.json +8 -0
- package/tools/generation-receipt.json +18 -18
- package/tools/tools-policy.json +1 -1
- package/tools/tools.anthropic.json +8 -0
- package/tools/tools.generic.json +8 -0
- package/tools/tools.openai.json +8 -0
- package/tools/tools.vercel.json +8 -0
package/dist/agent/actions.d.ts
CHANGED
|
@@ -491,6 +491,19 @@ export type AddHyperlinkArgs = {
|
|
|
491
491
|
url: string;
|
|
492
492
|
tooltip?: string;
|
|
493
493
|
};
|
|
494
|
+
export declare function typedMarkerPrefixOf(text: string): {
|
|
495
|
+
prefix: string;
|
|
496
|
+
marker: string;
|
|
497
|
+
} | null;
|
|
498
|
+
export type MarkerShape = {
|
|
499
|
+
wrapper: 'paren' | 'period' | 'close';
|
|
500
|
+
kind: 'digit' | 'lower-roman' | 'lower-alpha' | 'upper-roman' | 'upper-alpha';
|
|
501
|
+
};
|
|
502
|
+
export declare function markerShapeOf(text: string): MarkerShape | null;
|
|
503
|
+
export declare function typedCaptionSpanOf(text: string, delimiter?: string): {
|
|
504
|
+
start: number;
|
|
505
|
+
end: number;
|
|
506
|
+
} | null;
|
|
494
507
|
export declare function superdocPerformAction(doc: BoundDocApi, args: unknown): Promise<AgentReceipt>;
|
|
495
508
|
export declare const ACTION_NAMES_LIST: readonly ActionName[];
|
|
496
509
|
export {};
|
package/dist/agent/actions.js
CHANGED
|
@@ -7,6 +7,7 @@ import { runSuperdocListTransformWorkflow } from '../action-primitives/tools/lis
|
|
|
7
7
|
import { runSuperdocStructureInsertWorkflow } from '../action-primitives/tools/structure-insert.js';
|
|
8
8
|
import { runSuperdocTextTransformWorkflow } from '../action-primitives/tools/text-transform.js';
|
|
9
9
|
import { buildDocumentSnapshot, buildMutationSnapshot, matchRunsForBlock, MutationSnapshotError, resolveSnapshotSelector, } from './doc-snapshot.js';
|
|
10
|
+
import { isRecord } from '../guards.js';
|
|
10
11
|
export const ACTION_DEFINITIONS = actionDescriptors;
|
|
11
12
|
function projectActionDefinitions(project) {
|
|
12
13
|
return Object.fromEntries(Object.entries(ACTION_DEFINITIONS).map(([name, definition]) => [name, project(definition)]));
|
|
@@ -69,9 +70,6 @@ export function getActionDispositions() {
|
|
|
69
70
|
export function isActionName(value) {
|
|
70
71
|
return typeof value === 'string' && ACTION_NAMES.includes(value);
|
|
71
72
|
}
|
|
72
|
-
function isRecord(value) {
|
|
73
|
-
return value != null && typeof value === 'object' && !Array.isArray(value);
|
|
74
|
-
}
|
|
75
73
|
/** The message to put in a receipt when something best-effort did not happen. */
|
|
76
74
|
function reasonOf(err) {
|
|
77
75
|
return err instanceof Error ? err.message : String(err);
|
|
@@ -877,6 +875,7 @@ async function styleTableCells(doc, cells, changeMode) {
|
|
|
877
875
|
await applyItemStyling(doc, nodeIds, specs, changeMode);
|
|
878
876
|
return styled.length;
|
|
879
877
|
}
|
|
878
|
+
const TYPED_SIBLING_WINDOW = 20;
|
|
880
879
|
async function runInsertParagraphs(doc, args, formatArgs) {
|
|
881
880
|
const context = new ExecutionContext(doc, { evidence: args.evidence, selectors: placementSelectors(args.placement) });
|
|
882
881
|
try {
|
|
@@ -889,7 +888,7 @@ async function runInsertParagraphs(doc, args, formatArgs) {
|
|
|
889
888
|
kind: 'placement',
|
|
890
889
|
at: placement.kind === 'documentStart' ? 'document_start' : 'document_end',
|
|
891
890
|
});
|
|
892
|
-
|
|
891
|
+
let preRows = pre.complete ? await listBlockRows(doc) : anchor ? [anchor] : [];
|
|
893
892
|
let currentPlacement = placement;
|
|
894
893
|
const nodeIds = [];
|
|
895
894
|
const specs = [];
|
|
@@ -929,6 +928,16 @@ async function runInsertParagraphs(doc, args, formatArgs) {
|
|
|
929
928
|
if (!pre.complete) {
|
|
930
929
|
const created = await post.page({ nodeIds, limit: Math.max(nodeIds.length, 1) });
|
|
931
930
|
localRows = [...(anchor ? [anchor] : []), ...created].sort((a, b) => a.ordinal - b.ordinal);
|
|
931
|
+
const placed = anchor && texts.some((text) => typedMarkerPrefixOf(text)) ? await post.block(anchor.nodeId) : null;
|
|
932
|
+
if (placed) {
|
|
933
|
+
const span = TYPED_SIBLING_WINDOW + nodeIds.length;
|
|
934
|
+
const window = await post.page({ offset: Math.max(0, placed.ordinal - 1 - span), limit: 2 * span + 1 });
|
|
935
|
+
const createdIds = new Set(nodeIds);
|
|
936
|
+
if (nodeIds.every((id) => window.some((row) => row.nodeId === id))) {
|
|
937
|
+
preRows = window.filter((row) => !createdIds.has(row.nodeId));
|
|
938
|
+
localRows = window;
|
|
939
|
+
}
|
|
940
|
+
}
|
|
932
941
|
}
|
|
933
942
|
const count = context.executedOperations.length;
|
|
934
943
|
formatted = await matchInsertedBlockFormatting(context.document, preRows, context.receipt('insert_paragraphs', []), formatArgs, localRows);
|
|
@@ -1278,6 +1287,172 @@ async function runDeleteText(doc, args) {
|
|
|
1278
1287
|
return failedReceipt('delete_text', err, pre);
|
|
1279
1288
|
}
|
|
1280
1289
|
}
|
|
1290
|
+
const CONJUNCTION_TAIL = /[;,]\s*(and|or)\s*$/i;
|
|
1291
|
+
const ITEM_ENDING = /(?:[;,]\s*(?:and|or)|[.;,:])\s*$/i;
|
|
1292
|
+
const ITEM_CLOSERS = /["'”’)\]]+$/;
|
|
1293
|
+
function enumerationSlotOf(block) {
|
|
1294
|
+
const numbering = blockNumbering(block);
|
|
1295
|
+
const path = numbering?.path;
|
|
1296
|
+
if (Array.isArray(path) && path.length > 0) {
|
|
1297
|
+
return { key: `scheme:${path.slice(0, -1).join('.')}:${path.length}`, depth: path.length, typed: null };
|
|
1298
|
+
}
|
|
1299
|
+
const markerShape = numbering?.marker ? markerShapeOf(numbering.marker) : null;
|
|
1300
|
+
if (markerShape)
|
|
1301
|
+
return { key: `scheme-marker:${markerShape.wrapper}:${markerShape.kind}`, depth: null, typed: null };
|
|
1302
|
+
const text = block.text || block.textPreview || '';
|
|
1303
|
+
const typedMarker = typedMarkerPrefixOf(text)?.marker;
|
|
1304
|
+
const typedShape = typedMarker ? markerShapeOf(text) : null;
|
|
1305
|
+
if (!typedMarker || !typedShape)
|
|
1306
|
+
return null;
|
|
1307
|
+
const parent = typedShape.kind === 'digit' ? typedDigitParentOf(typedMarker) : '';
|
|
1308
|
+
return { key: `typed:${typedShape.wrapper}:${typedShape.kind}:${parent}`, depth: null, typed: typedShape };
|
|
1309
|
+
}
|
|
1310
|
+
function typedDigitParentOf(marker) {
|
|
1311
|
+
const segments = marker.replace(/^\(/, '').replace(/[.)]$/, '').split('.');
|
|
1312
|
+
return `${segments.length}:${segments.slice(0, -1).join('.')}`;
|
|
1313
|
+
}
|
|
1314
|
+
const ROMAN_DIGITS = { i: 1, v: 5, x: 10 };
|
|
1315
|
+
function markerValueOf(token, shape) {
|
|
1316
|
+
const core = token.replace(/^\(/, '').replace(/[.)]$/, '').toLowerCase();
|
|
1317
|
+
if (shape.kind === 'digit')
|
|
1318
|
+
return Number(core.split('.').pop());
|
|
1319
|
+
if (shape.kind === 'lower-roman' || shape.kind === 'upper-roman') {
|
|
1320
|
+
let total = 0;
|
|
1321
|
+
for (let i = 0; i < core.length; i += 1) {
|
|
1322
|
+
const value = ROMAN_DIGITS[core[i]] ?? 0;
|
|
1323
|
+
total += value < (ROMAN_DIGITS[core[i + 1] ?? ''] ?? 0) ? -value : value;
|
|
1324
|
+
}
|
|
1325
|
+
return total;
|
|
1326
|
+
}
|
|
1327
|
+
const letter = core.charCodeAt(0) - 96;
|
|
1328
|
+
return core.length === 1 ? letter : 26 * (core.length - 1) + letter;
|
|
1329
|
+
}
|
|
1330
|
+
function enumerationRunOf(blocks, index, slot, textOf) {
|
|
1331
|
+
const typed = slot.typed;
|
|
1332
|
+
const valueOf = (block) => {
|
|
1333
|
+
if (!typed) {
|
|
1334
|
+
const path = blockNumbering(block)?.path;
|
|
1335
|
+
return Array.isArray(path) && path.length > 0 ? Number(path[path.length - 1]) : null;
|
|
1336
|
+
}
|
|
1337
|
+
const token = typed ? typedMarkerPrefixOf(textOf(block))?.marker : null;
|
|
1338
|
+
return token && typed ? markerValueOf(token, typed) : null;
|
|
1339
|
+
};
|
|
1340
|
+
const walk = (step) => {
|
|
1341
|
+
const found = [];
|
|
1342
|
+
let edge = valueOf(blocks[index]);
|
|
1343
|
+
let crossed = false;
|
|
1344
|
+
for (let i = index + step; i >= 0 && i < blocks.length; i += step) {
|
|
1345
|
+
const block = blocks[i];
|
|
1346
|
+
if (textOf(block).length === 0)
|
|
1347
|
+
continue;
|
|
1348
|
+
const other = enumerationSlotOf(block);
|
|
1349
|
+
const continuesAsLetter = typed != null &&
|
|
1350
|
+
other?.typed != null &&
|
|
1351
|
+
markerShapeFits(typed, other.typed) &&
|
|
1352
|
+
edge != null &&
|
|
1353
|
+
valueOf(block) === edge + step;
|
|
1354
|
+
if (other?.key === slot.key || continuesAsLetter) {
|
|
1355
|
+
const value = valueOf(block);
|
|
1356
|
+
if (value != null && edge != null) {
|
|
1357
|
+
const advance = (value - edge) * step;
|
|
1358
|
+
if (advance <= 0 || ((crossed || !typed) && advance !== 1))
|
|
1359
|
+
break;
|
|
1360
|
+
}
|
|
1361
|
+
found.push(block);
|
|
1362
|
+
edge = value;
|
|
1363
|
+
crossed = false;
|
|
1364
|
+
continue;
|
|
1365
|
+
}
|
|
1366
|
+
if (typed && other?.typed) {
|
|
1367
|
+
if (step === -1 && edge != null) {
|
|
1368
|
+
const prefix = typedMarkerPrefixOf(textOf(block)).prefix;
|
|
1369
|
+
const inline = new RegExp(String.raw `(?:^|\s)(${TYPED_MARKER_TOKEN}) `).exec(textOf(block).slice(prefix.length));
|
|
1370
|
+
const inlineShape = inline ? markerShapeOf(inline[1]) : null;
|
|
1371
|
+
if (inline && sameMarkerShape(inlineShape, typed) && markerValueOf(inline[1], typed) === edge - 1) {
|
|
1372
|
+
found.push(block);
|
|
1373
|
+
break;
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
crossed = true;
|
|
1377
|
+
continue;
|
|
1378
|
+
}
|
|
1379
|
+
if (slot.depth != null && other?.depth != null && other.depth > slot.depth)
|
|
1380
|
+
continue;
|
|
1381
|
+
break;
|
|
1382
|
+
}
|
|
1383
|
+
return found;
|
|
1384
|
+
};
|
|
1385
|
+
return [...walk(-1).reverse(), blocks[index], ...walk(1)];
|
|
1386
|
+
}
|
|
1387
|
+
function listPunctuationAfterDelete(blocks, deletedIds) {
|
|
1388
|
+
const textOf = (block) => (block.text || (block.textPreview ?? '').replace(/…\s*$/, '')).trimEnd();
|
|
1389
|
+
const endingOf = (block) => {
|
|
1390
|
+
const text = textOf(block);
|
|
1391
|
+
const closers = ITEM_CLOSERS.exec(text)?.[0] ?? '';
|
|
1392
|
+
const body = text.slice(0, text.length - closers.length).trimEnd();
|
|
1393
|
+
const ending = ITEM_ENDING.exec(body);
|
|
1394
|
+
return {
|
|
1395
|
+
core: ending?.[0].trim() ?? '',
|
|
1396
|
+
real: ending ? `${ending[0].trim()}${closers}` : closers,
|
|
1397
|
+
closers,
|
|
1398
|
+
conjunction: CONJUNCTION_TAIL.exec(body)?.[0].trim() ?? null,
|
|
1399
|
+
};
|
|
1400
|
+
};
|
|
1401
|
+
const quote = (block) => {
|
|
1402
|
+
const text = textOf(block);
|
|
1403
|
+
return JSON.stringify(text.length > 40 ? `...${text.slice(-40)}` : text);
|
|
1404
|
+
};
|
|
1405
|
+
const change = (block, to) => {
|
|
1406
|
+
const ending = endingOf(block);
|
|
1407
|
+
return `change the ending of ${quote(block)} (nodeId ${block.nodeId}) from "${ending.real}" to "${to}${ending.closers}"`;
|
|
1408
|
+
};
|
|
1409
|
+
const entries = [];
|
|
1410
|
+
const covered = new Set();
|
|
1411
|
+
blocks.forEach((block, index) => {
|
|
1412
|
+
if (!deletedIds.has(block.nodeId) || covered.has(block.nodeId))
|
|
1413
|
+
return;
|
|
1414
|
+
const slot = enumerationSlotOf(block);
|
|
1415
|
+
if (!slot)
|
|
1416
|
+
return;
|
|
1417
|
+
const run = enumerationRunOf(blocks, index, slot, textOf);
|
|
1418
|
+
for (const item of run)
|
|
1419
|
+
covered.add(item.nodeId);
|
|
1420
|
+
const survivors = run.filter((item) => !deletedIds.has(item.nodeId));
|
|
1421
|
+
if (survivors.length === 0)
|
|
1422
|
+
return;
|
|
1423
|
+
const last = survivors[survivors.length - 1];
|
|
1424
|
+
const penultimate = survivors.length >= 2 ? survivors[survivors.length - 2] : undefined;
|
|
1425
|
+
const originalPenultimate = run[run.length - 2];
|
|
1426
|
+
if (run.some((item) => item !== originalPenultimate && endingOf(item).conjunction))
|
|
1427
|
+
return;
|
|
1428
|
+
const runEnd = run[run.length - 1];
|
|
1429
|
+
if (!deletedIds.has(runEnd.nodeId))
|
|
1430
|
+
return;
|
|
1431
|
+
const tail = originalPenultimate ? endingOf(originalPenultimate).conjunction : null;
|
|
1432
|
+
const separator = tail ? tail.charAt(0) : ';';
|
|
1433
|
+
const closing = endingOf(runEnd).core || '.';
|
|
1434
|
+
const lastEnding = endingOf(last);
|
|
1435
|
+
if (lastEnding.core === ':')
|
|
1436
|
+
return;
|
|
1437
|
+
const needsClose = lastEnding.core !== closing && (lastEnding.conjunction != null || lastEnding.core === separator);
|
|
1438
|
+
const needsTail = penultimate != null && tail != null && endingOf(penultimate).core !== ':' && !endingOf(penultimate).conjunction;
|
|
1439
|
+
if (!needsClose && !(needsTail && lastEnding.core !== closing))
|
|
1440
|
+
return;
|
|
1441
|
+
const edits = [
|
|
1442
|
+
`end ${quote(last)} (nodeId ${last.nodeId}) with "${closing}${lastEnding.closers}" instead of "${lastEnding.real}"`,
|
|
1443
|
+
];
|
|
1444
|
+
if (needsTail)
|
|
1445
|
+
edits.push(change(penultimate, tail));
|
|
1446
|
+
entries.push({
|
|
1447
|
+
deleted: runEnd.nodeId,
|
|
1448
|
+
lastItem: last.nodeId,
|
|
1449
|
+
lastItemEnds: lastEnding.real,
|
|
1450
|
+
...(penultimate ? { penultimate: penultimate.nodeId, penultimateEnds: endingOf(penultimate).real } : {}),
|
|
1451
|
+
suggestion: `The run now ends at ${quote(last)}: ${edits.join(', and ')}, using replace_text.`,
|
|
1452
|
+
});
|
|
1453
|
+
});
|
|
1454
|
+
return entries;
|
|
1455
|
+
}
|
|
1281
1456
|
/** Block node types `doc.blocks.delete` removes as a whole paragraph-shaped block. */
|
|
1282
1457
|
const DELETABLE_BLOCK_NODE_TYPES = new Set(['paragraph', 'heading', 'listItem']);
|
|
1283
1458
|
/**
|
|
@@ -1447,6 +1622,11 @@ async function runDeleteBlocks(doc, args) {
|
|
|
1447
1622
|
// an error, so the `errors.length` term still catches a short request.
|
|
1448
1623
|
const allApplied = deleted.length === targets.length && errors.length === 0;
|
|
1449
1624
|
const verified = verification.every((v) => v.passed);
|
|
1625
|
+
let listPunctuation = [];
|
|
1626
|
+
try {
|
|
1627
|
+
listPunctuation = listPunctuationAfterDelete(pre.blocks, new Set(deleted.map((entry) => entry.nodeId)));
|
|
1628
|
+
}
|
|
1629
|
+
catch { }
|
|
1450
1630
|
return {
|
|
1451
1631
|
status: !verified || deleted.length === 0 ? 'failed' : allApplied ? 'ok' : 'partial',
|
|
1452
1632
|
intent: `delete_blocks: ${deleted.length} block(s)`,
|
|
@@ -1456,6 +1636,7 @@ async function runDeleteBlocks(doc, args) {
|
|
|
1456
1636
|
executedOperations,
|
|
1457
1637
|
verification,
|
|
1458
1638
|
deletedBlocks: deleted,
|
|
1639
|
+
...(listPunctuation.length > 0 ? { listPunctuation } : {}),
|
|
1459
1640
|
...(errors.length ? { errors } : {}),
|
|
1460
1641
|
...(errors.length
|
|
1461
1642
|
? { nextStep: 'Re-inspect the document and retry the selectors that did not resolve to one block.' }
|
|
@@ -4400,8 +4581,9 @@ async function runScopedReplaceUnchecked(doc, args) {
|
|
|
4400
4581
|
occurrences: spans.length,
|
|
4401
4582
|
...(edit.occurrence != null ? { occurrence: edit.occurrence } : {}),
|
|
4402
4583
|
});
|
|
4403
|
-
for (const span of spans)
|
|
4404
|
-
allSpans.push({ ...span, replace: edit.replace, find: edit.find });
|
|
4584
|
+
for (const span of spans) {
|
|
4585
|
+
allSpans.push({ ...span, replace: edit.replace, find: edit.find, editIndex: perEdit.length - 1 });
|
|
4586
|
+
}
|
|
4405
4587
|
}
|
|
4406
4588
|
const appliedEdits = perEdit.filter((e) => e.occurrences > 0);
|
|
4407
4589
|
const skippedEdits = perEdit.filter((e) => e.occurrences === 0);
|
|
@@ -4438,6 +4620,23 @@ async function runScopedReplaceUnchecked(doc, args) {
|
|
|
4438
4620
|
planned.push(span);
|
|
4439
4621
|
lastEnd = span.end;
|
|
4440
4622
|
}
|
|
4623
|
+
planned.forEach((span, at) => {
|
|
4624
|
+
if (span.replace.trim().length > 0)
|
|
4625
|
+
return;
|
|
4626
|
+
const text = block.text;
|
|
4627
|
+
if (text.charAt(span.start - 1) !== ' ' || text.charAt(span.end) !== ' ')
|
|
4628
|
+
return;
|
|
4629
|
+
if (text.charAt(span.start - 2) === ' ' || text.charAt(span.end + 1) === ' ')
|
|
4630
|
+
return;
|
|
4631
|
+
if (/[.!?]["'\u201d\u2019)\]]*$/.test(text.slice(0, span.start - 1)))
|
|
4632
|
+
return;
|
|
4633
|
+
if ((planned[at + 1]?.start ?? Infinity) <= span.end || (planned[at - 1]?.end ?? -1) >= span.start - 1)
|
|
4634
|
+
return;
|
|
4635
|
+
if (span.replace.length > 0)
|
|
4636
|
+
span.start -= 1;
|
|
4637
|
+
span.end += 1;
|
|
4638
|
+
perEdit[span.editIndex].collapsedSpace = true;
|
|
4639
|
+
});
|
|
4441
4640
|
// The block text once every planned span is replaced: the whole-block
|
|
4442
4641
|
// rewrite writes it, and exact-target writes must produce it.
|
|
4443
4642
|
const expected = planned.reduceRight((text, span) => text.slice(0, span.start) + span.replace + text.slice(span.end), block.text);
|
|
@@ -4724,6 +4923,8 @@ function leadInShapeOf(blockText, labelText, quote) {
|
|
|
4724
4923
|
}
|
|
4725
4924
|
/** The span of `text` that carries the label look, or null when it has none. */
|
|
4726
4925
|
function leadInSpanOf(text, shape) {
|
|
4926
|
+
if (shape.kind === 'typed')
|
|
4927
|
+
return typedCaptionSpanOf(text, shape.delimiter);
|
|
4727
4928
|
if (shape.kind === 'word') {
|
|
4728
4929
|
// Only the SAME token inherits the look — "WHEREAS the Parties…" does,
|
|
4729
4930
|
// "The Parties further agree…" does not (no guessing a label there).
|
|
@@ -4758,6 +4959,81 @@ function leadInSpanOf(text, shape) {
|
|
|
4758
4959
|
const end = leadInEndOf(text, shape.delimiter);
|
|
4759
4960
|
return end == null ? null : { start: 0, end };
|
|
4760
4961
|
}
|
|
4962
|
+
const TYPED_MARKER_CORE = String.raw `(?:\d{1,3}(?:\.\d{1,3})*|[ivx]{1,6}|[IVX]{1,6}|[a-z]{1,2}|[A-Z])`;
|
|
4963
|
+
const TYPED_MARKER_TOKEN = String.raw `(?:\(${TYPED_MARKER_CORE}\)|${TYPED_MARKER_CORE}[.)])`;
|
|
4964
|
+
const TYPED_MARKER_PREFIX = new RegExp(String.raw `^[ \t]*(${TYPED_MARKER_TOKEN})\t[\t ]*`);
|
|
4965
|
+
const LEADING_MARKER_TOKEN = new RegExp(String.raw `^\s*(${TYPED_MARKER_TOKEN})(?=\s|$)`);
|
|
4966
|
+
export function typedMarkerPrefixOf(text) {
|
|
4967
|
+
const match = TYPED_MARKER_PREFIX.exec(text);
|
|
4968
|
+
return match ? { prefix: match[0], marker: match[1] } : null;
|
|
4969
|
+
}
|
|
4970
|
+
export function markerShapeOf(text) {
|
|
4971
|
+
const match = LEADING_MARKER_TOKEN.exec(text);
|
|
4972
|
+
if (!match)
|
|
4973
|
+
return null;
|
|
4974
|
+
const token = match[1];
|
|
4975
|
+
const wrapper = token.startsWith('(') ? 'paren' : token.endsWith('.') ? 'period' : 'close';
|
|
4976
|
+
const core = token.replace(/^\(/, '').replace(/[.)]$/, '');
|
|
4977
|
+
let kind;
|
|
4978
|
+
if (/^\d/.test(core))
|
|
4979
|
+
kind = 'digit';
|
|
4980
|
+
else if (/^[ivx]+$/.test(core))
|
|
4981
|
+
kind = 'lower-roman';
|
|
4982
|
+
else if (/^[IVX]+$/.test(core))
|
|
4983
|
+
kind = 'upper-roman';
|
|
4984
|
+
else if (/^[a-z]+$/.test(core))
|
|
4985
|
+
kind = 'lower-alpha';
|
|
4986
|
+
else if (/^[A-Z]+$/.test(core))
|
|
4987
|
+
kind = 'upper-alpha';
|
|
4988
|
+
else
|
|
4989
|
+
return null;
|
|
4990
|
+
return { wrapper, kind };
|
|
4991
|
+
}
|
|
4992
|
+
function sameMarkerShape(a, b) {
|
|
4993
|
+
return a != null && b != null && a.wrapper === b.wrapper && a.kind === b.kind;
|
|
4994
|
+
}
|
|
4995
|
+
function markerShapeFits(reference, created) {
|
|
4996
|
+
if (!reference || reference.wrapper !== created.wrapper)
|
|
4997
|
+
return false;
|
|
4998
|
+
if (reference.kind === created.kind)
|
|
4999
|
+
return true;
|
|
5000
|
+
return ((created.kind === 'lower-roman' && reference.kind === 'lower-alpha') ||
|
|
5001
|
+
(created.kind === 'upper-roman' && reference.kind === 'upper-alpha'));
|
|
5002
|
+
}
|
|
5003
|
+
function continuesAlphabet(referenceText, createdText, referenceBefore) {
|
|
5004
|
+
const letterOf = (text) => /^[a-zA-Z]$/.exec(LEADING_MARKER_TOKEN.exec(text)?.[1]?.replace(/[().]/g, '') ?? '')?.[0];
|
|
5005
|
+
const reference = letterOf(referenceText);
|
|
5006
|
+
const created = letterOf(createdText);
|
|
5007
|
+
return (reference != null &&
|
|
5008
|
+
created != null &&
|
|
5009
|
+
created.charCodeAt(0) - reference.charCodeAt(0) === (referenceBefore ? 1 : -1));
|
|
5010
|
+
}
|
|
5011
|
+
const TYPED_CAPTION_MAX_CHARS = 60;
|
|
5012
|
+
function isTitleLike(caption) {
|
|
5013
|
+
const words = caption.match(/\p{L}+/gu) ?? [];
|
|
5014
|
+
const capitalised = words.filter((word) => /^\p{Lu}/u.test(word)).length;
|
|
5015
|
+
return (words.length > 0 &&
|
|
5016
|
+
words.every((word) => word.length < 4 || /^\p{Lu}/u.test(word)) &&
|
|
5017
|
+
capitalised * 2 >= words.length);
|
|
5018
|
+
}
|
|
5019
|
+
export function typedCaptionSpanOf(text, delimiter = '.') {
|
|
5020
|
+
const typed = typedMarkerPrefixOf(text);
|
|
5021
|
+
const mark = delimiter.trim();
|
|
5022
|
+
if (!typed || !mark)
|
|
5023
|
+
return null;
|
|
5024
|
+
const start = typed.prefix.length;
|
|
5025
|
+
for (let at = text.indexOf(mark, start); at >= 0; at = text.indexOf(mark, at + 1)) {
|
|
5026
|
+
const end = at + mark.length;
|
|
5027
|
+
if (end - start > TYPED_CAPTION_MAX_CHARS)
|
|
5028
|
+
return null;
|
|
5029
|
+
if (at === start || !/\s/.test(text.charAt(end)))
|
|
5030
|
+
continue;
|
|
5031
|
+
if (!isTitleLike(text.slice(start, at)))
|
|
5032
|
+
return null;
|
|
5033
|
+
return text.slice(end).trim().length > 0 ? { start, end } : null;
|
|
5034
|
+
}
|
|
5035
|
+
return null;
|
|
5036
|
+
}
|
|
4761
5037
|
/**
|
|
4762
5038
|
* Cut `text` at `end` without splitting a surrogate pair: half an emoji is not
|
|
4763
5039
|
* document text, and `query.match` finds nothing when handed a lone surrogate —
|
|
@@ -4834,7 +5110,10 @@ async function listItemRunPattern(doc, text, blockId) {
|
|
|
4834
5110
|
// Window: through the label's delimiter plus enough of the sentence to sample
|
|
4835
5111
|
// it, or a plain head when the item carries no label at all.
|
|
4836
5112
|
const delimiterAt = leadInEndOf(text);
|
|
4837
|
-
|
|
5113
|
+
let windowEnd = delimiterAt != null ? delimiterAt + LEAD_IN_BODY_SAMPLE_CHARS : LEAD_IN_BODY_SAMPLE_CHARS;
|
|
5114
|
+
const typedCaption = typedCaptionSpanOf(text);
|
|
5115
|
+
if (typedCaption)
|
|
5116
|
+
windowEnd = Math.max(windowEnd, typedCaption.end + LEAD_IN_BODY_SAMPLE_CHARS);
|
|
4838
5117
|
const pattern = sliceWholeChars(text, windowEnd);
|
|
4839
5118
|
const result = asRecord(await queryFn({ select: { type: 'text', pattern } }));
|
|
4840
5119
|
const items = Array.isArray(result?.items) ? result.items : [];
|
|
@@ -4848,6 +5127,9 @@ async function listItemRunPattern(doc, text, blockId) {
|
|
|
4848
5127
|
const runs = (Array.isArray(rec?.runs) ? rec.runs : []).filter((r) => runTextOf(r).length > 0);
|
|
4849
5128
|
if (runs.length === 0)
|
|
4850
5129
|
continue;
|
|
5130
|
+
const typedLead = typedPrefixLabelOf(runs, runTextOf);
|
|
5131
|
+
if (typedLead)
|
|
5132
|
+
return typedLead;
|
|
4851
5133
|
const blockText = asString(rec?.text, '') ?? '';
|
|
4852
5134
|
// A quoted defined term keeps its opening quote in a run of its own —
|
|
4853
5135
|
// `"` + **Deliverables** + `" means …` — so the label is the SECOND run
|
|
@@ -4883,6 +5165,59 @@ async function listItemRunPattern(doc, text, blockId) {
|
|
|
4883
5165
|
}
|
|
4884
5166
|
return null;
|
|
4885
5167
|
}
|
|
5168
|
+
function typedPrefixLabelOf(runs, runTextOf) {
|
|
5169
|
+
const typed = typedMarkerPrefixOf(runs.map(runTextOf).join(''));
|
|
5170
|
+
if (!typed)
|
|
5171
|
+
return null;
|
|
5172
|
+
const markerEnd = typed.prefix.indexOf(typed.marker) + typed.marker.length;
|
|
5173
|
+
let covered = 0;
|
|
5174
|
+
let labelIndex = 0;
|
|
5175
|
+
while (labelIndex < runs.length && covered + runTextOf(runs[labelIndex]).length <= typed.prefix.length) {
|
|
5176
|
+
covered += runTextOf(runs[labelIndex]).length;
|
|
5177
|
+
labelIndex += 1;
|
|
5178
|
+
}
|
|
5179
|
+
if (covered < markerEnd || labelIndex + 1 >= runs.length)
|
|
5180
|
+
return null;
|
|
5181
|
+
const underlined = (run) => asRecord(asRecord(asRecord(run)?.styles)?.effective)?.underline === true;
|
|
5182
|
+
const emphasisOf = (run) => {
|
|
5183
|
+
const style = matchRunStyle(run);
|
|
5184
|
+
return `${style.bold}/${style.italic}/${underlined(run)}`;
|
|
5185
|
+
};
|
|
5186
|
+
const label = runs[labelIndex];
|
|
5187
|
+
if (emphasisOf(label) === 'false/false/false')
|
|
5188
|
+
return null;
|
|
5189
|
+
const isBlank = (run) => runTextOf(run).trim().length === 0;
|
|
5190
|
+
const continuesLabel = (index) => {
|
|
5191
|
+
if (emphasisOf(runs[index]) === emphasisOf(label))
|
|
5192
|
+
return true;
|
|
5193
|
+
let next = index;
|
|
5194
|
+
while (next < runs.length && isBlank(runs[next]))
|
|
5195
|
+
next += 1;
|
|
5196
|
+
return next > index && next < runs.length && emphasisOf(runs[next]) === emphasisOf(label);
|
|
5197
|
+
};
|
|
5198
|
+
let bodyIndex = labelIndex + 1;
|
|
5199
|
+
while (bodyIndex < runs.length && continuesLabel(bodyIndex))
|
|
5200
|
+
bodyIndex += 1;
|
|
5201
|
+
while (bodyIndex < runs.length - 1 && isBlank(runs[bodyIndex]))
|
|
5202
|
+
bodyIndex += 1;
|
|
5203
|
+
if (bodyIndex >= runs.length)
|
|
5204
|
+
return null;
|
|
5205
|
+
const labelText = runs.slice(labelIndex, bodyIndex).map(runTextOf).join('').trim();
|
|
5206
|
+
if (labelText.length === 0 || labelText.length > LEAD_IN_MAX_CHARS)
|
|
5207
|
+
return null;
|
|
5208
|
+
const delimiter = ['.', ...LEAD_IN_DELIMITERS.map((d) => d.trim())].find((d) => labelText.endsWith(d) && labelText.length > d.length);
|
|
5209
|
+
if (!delimiter)
|
|
5210
|
+
return null;
|
|
5211
|
+
const body = runs[bodyIndex];
|
|
5212
|
+
const leadStyle = matchRunStyle(label);
|
|
5213
|
+
const bodyStyle = matchRunStyle(body);
|
|
5214
|
+
const leadUnderline = underlined(label);
|
|
5215
|
+
const bodyUnderline = underlined(body);
|
|
5216
|
+
const look = inlineFromMatchStyle(leadStyle, bodyStyle);
|
|
5217
|
+
if (leadUnderline || bodyUnderline)
|
|
5218
|
+
look.underline = leadUnderline;
|
|
5219
|
+
return { body: inlineFromMatchStyle(bodyStyle), lead: { look, shape: { kind: 'typed', delimiter } } };
|
|
5220
|
+
}
|
|
4886
5221
|
/**
|
|
4887
5222
|
* Pull a paragraph's direct indentation out of a `getNodeById` payload and
|
|
4888
5223
|
* convert it to the twips that `format.paragraph.setIndentation` expects.
|
|
@@ -5761,6 +6096,7 @@ async function matchOneBlock(doc, postBlocks, preIds, created, createdIndex, anc
|
|
|
5761
6096
|
}
|
|
5762
6097
|
if (!reference)
|
|
5763
6098
|
return null;
|
|
6099
|
+
reference = typedShapeReference(postBlocks, preIds, created, createdIndex, createdText, reference, compatibleHeading);
|
|
5764
6100
|
const applied = {};
|
|
5765
6101
|
// Numbering: when the reference participates in a numbering scheme and
|
|
5766
6102
|
// the new block does not, attach it to the same sequence — this is what
|
|
@@ -5786,7 +6122,9 @@ async function matchOneBlock(doc, postBlocks, preIds, created, createdIndex, anc
|
|
|
5786
6122
|
}
|
|
5787
6123
|
}
|
|
5788
6124
|
if (reference.styleId && reference.styleId !== created.styleId) {
|
|
5789
|
-
const
|
|
6125
|
+
const keepRunMarks = !allowLeadPattern;
|
|
6126
|
+
const setStyleFn = (keepRunMarks ? maybeMethod(doc, ['styles', 'paragraph', 'setStyleRef']) : null) ??
|
|
6127
|
+
maybeMethod(doc, ['styles', 'paragraph', 'setStyle']);
|
|
5790
6128
|
if (setStyleFn) {
|
|
5791
6129
|
const target = { kind: 'block', nodeType: created.nodeType, nodeId: created.nodeId };
|
|
5792
6130
|
let canCopyStyle = true;
|
|
@@ -5889,6 +6227,42 @@ async function matchOneBlock(doc, postBlocks, preIds, created, createdIndex, anc
|
|
|
5889
6227
|
return { matchedSibling: null, skipped: reasonOf(err) };
|
|
5890
6228
|
}
|
|
5891
6229
|
}
|
|
6230
|
+
function typedShapeReference(postBlocks, preIds, created, createdIndex, createdText, reference, compatible) {
|
|
6231
|
+
const typed = createdText ? typedMarkerPrefixOf(createdText) : null;
|
|
6232
|
+
if (!typed || createdText.slice(typed.prefix.length).trim().length === 0)
|
|
6233
|
+
return reference;
|
|
6234
|
+
const shape = markerShapeOf(createdText);
|
|
6235
|
+
const referenceShape = markerShapeOf(reference.textPreview ?? '');
|
|
6236
|
+
if (!shape || sameMarkerShape(referenceShape, shape))
|
|
6237
|
+
return reference;
|
|
6238
|
+
const usable = (row) => !!row && preIds.has(row.nodeId) && row.nodeType === created.nodeType && !row.isEmpty && compatible(row);
|
|
6239
|
+
const before = postBlocks.slice(0, createdIndex).reverse().find(usable);
|
|
6240
|
+
const after = postBlocks.slice(createdIndex + 1).find(usable);
|
|
6241
|
+
const continued = [reference, before, after].find((row) => !!row &&
|
|
6242
|
+
markerShapeFits(markerShapeOf(row.textPreview ?? ''), shape) &&
|
|
6243
|
+
continuesAlphabet(row.textPreview ?? '', createdText, postBlocks.findIndex((b) => b.nodeId === row.nodeId) < createdIndex));
|
|
6244
|
+
if (continued)
|
|
6245
|
+
return continued;
|
|
6246
|
+
const candidate = (row) => {
|
|
6247
|
+
if (!usable(row))
|
|
6248
|
+
return null;
|
|
6249
|
+
const preview = row.textPreview ?? '';
|
|
6250
|
+
return typedMarkerPrefixOf(preview) ? markerShapeOf(preview) : null;
|
|
6251
|
+
};
|
|
6252
|
+
const nearest = (accepts) => {
|
|
6253
|
+
for (let distance = 1; distance < postBlocks.length; distance += 1) {
|
|
6254
|
+
for (const index of [createdIndex - distance, createdIndex + distance]) {
|
|
6255
|
+
const row = postBlocks[index];
|
|
6256
|
+
if (index >= 0 && accepts(candidate(row)))
|
|
6257
|
+
return row;
|
|
6258
|
+
}
|
|
6259
|
+
}
|
|
6260
|
+
return null;
|
|
6261
|
+
};
|
|
6262
|
+
return (nearest((rowShape) => sameMarkerShape(rowShape, shape)) ??
|
|
6263
|
+
nearest((rowShape) => markerShapeFits(rowShape, shape)) ??
|
|
6264
|
+
reference);
|
|
6265
|
+
}
|
|
5892
6266
|
/** A lead-in marker token like "(m)", "1.", "a)", "(iv)" at the start of clause text. */
|
|
5893
6267
|
const LEAD_MARKER_PATTERN = /^\(?(?:[a-zA-Z]{1,3}|\d{1,3})[).:\]]/;
|
|
5894
6268
|
/**
|
|
@@ -6019,7 +6393,8 @@ async function matchBlockRunPattern(doc, reference, created, createdText) {
|
|
|
6019
6393
|
const span = leadInSpanOf(fullText, pattern.lead.shape);
|
|
6020
6394
|
// A uniform reference has no body look and no delimiter of its own: its look
|
|
6021
6395
|
// belongs to the whole created block, not to a label span inside it.
|
|
6022
|
-
const range = span ??
|
|
6396
|
+
const range = span ??
|
|
6397
|
+
(Object.keys(pattern.body).length === 0 && pattern.lead.shape.kind !== 'typed' ? { start: 0, end } : null);
|
|
6023
6398
|
if (range && (await applyInlineQuiet(formatFn, { blockId: created.nodeId, ...range, inline: pattern.lead.look }))) {
|
|
6024
6399
|
applied.lead = { ...pattern.lead.look, range: [range.start, range.end] };
|
|
6025
6400
|
}
|
|
@@ -6046,7 +6421,7 @@ async function matchInsertedBlockFormatting(doc, preBlocks, receipt, args, avail
|
|
|
6046
6421
|
// When the caller styled any entry explicitly (runs/marks), it took
|
|
6047
6422
|
// ownership of intra-paragraph formatting — the automatic lead-marker
|
|
6048
6423
|
// pattern pass must not fight it.
|
|
6049
|
-
const rawTexts = Array.isArray(args?.texts) ? args.texts : [];
|
|
6424
|
+
const rawTexts = Array.isArray(args?.texts) ? args.texts : args?.text != null ? [args.text] : [];
|
|
6050
6425
|
const hasExplicitStyling = rawTexts.some((entry) => isRecord(entry) && (Array.isArray(entry.runs) || entry.marks != null));
|
|
6051
6426
|
const insertedTexts = rawTexts.map((entry) => {
|
|
6052
6427
|
if (typeof entry === 'string')
|
|
@@ -6247,7 +6622,15 @@ async function runAttachNumbering(doc, args) {
|
|
|
6247
6622
|
errors: [
|
|
6248
6623
|
{
|
|
6249
6624
|
code: 'TARGET_NOT_FOUND',
|
|
6250
|
-
message:
|
|
6625
|
+
message: available.length > 0
|
|
6626
|
+
? `no block renders marker ${JSON.stringify(likeMarker)}. Available: ${available.join(' ')}`
|
|
6627
|
+
: rows.some((r) => typedMarkerPrefixOf(r.textPreview ?? '') != null)
|
|
6628
|
+
? `no block renders marker ${JSON.stringify(likeMarker)}: this document has no numbering scheme; its clause numbers are typed text. ` +
|
|
6629
|
+
`Copy the sibling's typed prefix (tab, next number, tab) into the paragraph text with insert_paragraphs or replace_text, ` +
|
|
6630
|
+
`then renumber the following typed markers with replace_text.`
|
|
6631
|
+
: `no block renders marker ${JSON.stringify(likeMarker)}: no block in this document carries numbering. ` +
|
|
6632
|
+
`To add a paragraph that matches its neighbours use insert_paragraphs next to them; ` +
|
|
6633
|
+
`to start a numbered list use append_list or convert_list.`,
|
|
6251
6634
|
recovery: { kind: 'reinspect' },
|
|
6252
6635
|
},
|
|
6253
6636
|
],
|
package/dist/agent/runtime.cjs
CHANGED
|
@@ -7,6 +7,7 @@ var errors = require('../runtime/errors.cjs');
|
|
|
7
7
|
var ir = require('./ir.cjs');
|
|
8
8
|
var docSnapshot = require('./doc-snapshot.cjs');
|
|
9
9
|
var operationCatalog = require('./operation-catalog.cjs');
|
|
10
|
+
var guards = require('../guards.cjs');
|
|
10
11
|
|
|
11
12
|
const RESERVED_ARG_KEYS = new Set(['sessionId', 'doc']);
|
|
12
13
|
function ensureKnownOperation(operationId) {
|
|
@@ -30,11 +31,8 @@ function ensureClean(args) {
|
|
|
30
31
|
}
|
|
31
32
|
return args;
|
|
32
33
|
}
|
|
33
|
-
function isRecord(value) {
|
|
34
|
-
return value != null && typeof value === 'object' && !Array.isArray(value);
|
|
35
|
-
}
|
|
36
34
|
function isRefToken(value) {
|
|
37
|
-
return (isRecord(value) &&
|
|
35
|
+
return (guards.isRecord(value) &&
|
|
38
36
|
typeof value.ref === 'string' &&
|
|
39
37
|
(value.path == null || typeof value.path === 'string') &&
|
|
40
38
|
Object.keys(value).every((key) => key === 'ref' || key === 'path'));
|
|
@@ -51,7 +49,7 @@ function readBindingPath(value, path) {
|
|
|
51
49
|
cursor = cursor[index];
|
|
52
50
|
continue;
|
|
53
51
|
}
|
|
54
|
-
if (!isRecord(cursor) || !(segment in cursor))
|
|
52
|
+
if (!guards.isRecord(cursor) || !(segment in cursor))
|
|
55
53
|
return undefined;
|
|
56
54
|
cursor = cursor[segment];
|
|
57
55
|
}
|
|
@@ -80,7 +78,7 @@ function resolveBindingTokens(value, bindings) {
|
|
|
80
78
|
if (Array.isArray(value)) {
|
|
81
79
|
return value.map((entry) => resolveBindingTokens(entry, bindings));
|
|
82
80
|
}
|
|
83
|
-
if (!isRecord(value))
|
|
81
|
+
if (!guards.isRecord(value))
|
|
84
82
|
return value;
|
|
85
83
|
return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, resolveBindingTokens(entry, bindings)]));
|
|
86
84
|
}
|
|
@@ -90,7 +88,7 @@ function extractBoundNodeIds(value) {
|
|
|
90
88
|
if (Array.isArray(value)) {
|
|
91
89
|
return [...new Set(value.flatMap((entry) => extractBoundNodeIds(entry)))];
|
|
92
90
|
}
|
|
93
|
-
if (!isRecord(value))
|
|
91
|
+
if (!guards.isRecord(value))
|
|
94
92
|
return [];
|
|
95
93
|
const direct = typeof value.nodeId === 'string' && value.nodeId.length > 0
|
|
96
94
|
? [value.nodeId]
|
|
@@ -364,7 +362,7 @@ function revisionOnlyParagraphStep(plan) {
|
|
|
364
362
|
return undefined;
|
|
365
363
|
const at = apply.args.at;
|
|
366
364
|
if (at !== undefined) {
|
|
367
|
-
if (!isRecord(at))
|
|
365
|
+
if (!guards.isRecord(at))
|
|
368
366
|
return undefined;
|
|
369
367
|
if (at.kind === 'documentStart' || at.kind === 'documentEnd') {
|
|
370
368
|
if (Object.keys(at).some((key) => key !== 'kind'))
|
|
@@ -372,7 +370,7 @@ function revisionOnlyParagraphStep(plan) {
|
|
|
372
370
|
}
|
|
373
371
|
else if (at.kind === 'before' || at.kind === 'after') {
|
|
374
372
|
if (Object.keys(at).some((key) => key !== 'kind' && key !== 'target') ||
|
|
375
|
-
!isRecord(at.target) ||
|
|
373
|
+
!guards.isRecord(at.target) ||
|
|
376
374
|
at.target.kind !== 'block' ||
|
|
377
375
|
typeof at.target.nodeId !== 'string' ||
|
|
378
376
|
typeof at.target.nodeType !== 'string' ||
|
package/dist/agent/runtime.js
CHANGED
|
@@ -5,6 +5,7 @@ import { SuperDocCliError } from '../runtime/errors.js';
|
|
|
5
5
|
import { validatePlan } from './ir.js';
|
|
6
6
|
import { buildDocumentSnapshot, buildMutationSnapshot, resolveSnapshotSelector, AmbiguousSelectorError, } from './doc-snapshot.js';
|
|
7
7
|
import { getOperationCatalogEntry } from './operation-catalog.js';
|
|
8
|
+
import { isRecord } from '../guards.js';
|
|
8
9
|
const RESERVED_ARG_KEYS = new Set(['sessionId', 'doc']);
|
|
9
10
|
function ensureKnownOperation(operationId) {
|
|
10
11
|
const entry = getOperationCatalogEntry(operationId);
|
|
@@ -27,9 +28,6 @@ function ensureClean(args) {
|
|
|
27
28
|
}
|
|
28
29
|
return args;
|
|
29
30
|
}
|
|
30
|
-
function isRecord(value) {
|
|
31
|
-
return value != null && typeof value === 'object' && !Array.isArray(value);
|
|
32
|
-
}
|
|
33
31
|
function isRefToken(value) {
|
|
34
32
|
return (isRecord(value) &&
|
|
35
33
|
typeof value.ref === 'string' &&
|