@superdoc/sdk 2.23.0-next.8 → 2.23.0-next.9

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.
@@ -3,6 +3,7 @@
3
3
  var docIndex = require('../doc-index.cjs');
4
4
  var engine = require('../engine.cjs');
5
5
  var resolve = require('../resolve.cjs');
6
+ var guards = require('../../guards.cjs');
6
7
 
7
8
  const STRUCTURE_INSERT_ACTIONS = [
8
9
  'insert_toc',
@@ -22,9 +23,6 @@ const DEFAULT_TOC_CONFIG = {
22
23
  rightAlignPageNumbers: true,
23
24
  tabLeader: 'dot',
24
25
  };
25
- function isObjectRecord(value) {
26
- return value != null && typeof value === 'object' && !Array.isArray(value);
27
- }
28
26
  function parseAction(raw) {
29
27
  if (typeof raw !== 'string') {
30
28
  return undefined;
@@ -166,7 +164,7 @@ function resolvePlacement(context, action) {
166
164
  source: 'provided',
167
165
  });
168
166
  }
169
- if (!isObjectRecord(rawPlacement)) {
167
+ if (!guards.isRecord(rawPlacement)) {
170
168
  return engine.workflowStepFailure({
171
169
  status: 'failed',
172
170
  phase: 'resolve',
@@ -960,7 +958,7 @@ async function executeStep(context, _resolved, plan) {
960
958
  sectionId: 'section-break-fallback',
961
959
  });
962
960
  }
963
- const sectionId = isObjectRecord(sectionResult.section) && typeof sectionResult.section.sectionId === 'string'
961
+ const sectionId = guards.isRecord(sectionResult.section) && typeof sectionResult.section.sectionId === 'string'
964
962
  ? sectionResult.section.sectionId
965
963
  : undefined;
966
964
  const afterInfo = await context.documentHandle.info({}, context.invokeOptions);
@@ -1,6 +1,7 @@
1
1
  import { buildWorkflowDocIndex } from '../doc-index.js';
2
2
  import { runWorkflowEngine, workflowStepFailure, workflowStepSuccess, } from '../engine.js';
3
3
  import { resolveWorkflowTargetFromUnknown, } from '../resolve.js';
4
+ import { isRecord } from '../../guards.js';
4
5
  const STRUCTURE_INSERT_ACTIONS = [
5
6
  'insert_toc',
6
7
  'insert_section_break',
@@ -19,9 +20,6 @@ const DEFAULT_TOC_CONFIG = {
19
20
  rightAlignPageNumbers: true,
20
21
  tabLeader: 'dot',
21
22
  };
22
- function isObjectRecord(value) {
23
- return value != null && typeof value === 'object' && !Array.isArray(value);
24
- }
25
23
  function parseAction(raw) {
26
24
  if (typeof raw !== 'string') {
27
25
  return undefined;
@@ -163,7 +161,7 @@ function resolvePlacement(context, action) {
163
161
  source: 'provided',
164
162
  });
165
163
  }
166
- if (!isObjectRecord(rawPlacement)) {
164
+ if (!isRecord(rawPlacement)) {
167
165
  return workflowStepFailure({
168
166
  status: 'failed',
169
167
  phase: 'resolve',
@@ -957,7 +955,7 @@ async function executeStep(context, _resolved, plan) {
957
955
  sectionId: 'section-break-fallback',
958
956
  });
959
957
  }
960
- const sectionId = isObjectRecord(sectionResult.section) && typeof sectionResult.section.sectionId === 'string'
958
+ const sectionId = isRecord(sectionResult.section) && typeof sectionResult.section.sectionId === 'string'
961
959
  ? sectionResult.section.sectionId
962
960
  : undefined;
963
961
  const afterInfo = await context.documentHandle.info({}, context.invokeOptions);
@@ -2,13 +2,11 @@
2
2
 
3
3
  var engine = require('../engine.cjs');
4
4
  var resolve = require('../resolve.cjs');
5
+ var guards = require('../../guards.cjs');
5
6
 
6
7
  const TEXT_TRANSFORM_ACTIONS = ['replace_all', 'delete_all', 'rewrite_block', 'fill_placeholders'];
7
8
  const CHANGE_MODES = ['direct', 'tracked'];
8
9
  const REWRITE_BLOCK_SUPPORTED_NODE_TYPES = ['paragraph', 'heading', 'listItem'];
9
- function isObjectRecord(value) {
10
- return value != null && typeof value === 'object' && !Array.isArray(value);
11
- }
12
10
  function coerceBoolean(raw, fallback) {
13
11
  if (typeof raw === 'boolean') {
14
12
  return raw;
@@ -155,7 +153,7 @@ function parseEdits(raw) {
155
153
  }
156
154
  const edits = [];
157
155
  for (const entry of raw) {
158
- if (!isObjectRecord(entry)) {
156
+ if (!guards.isRecord(entry)) {
159
157
  return undefined;
160
158
  }
161
159
  const find = entry.find;
@@ -184,7 +182,7 @@ function parsePlaceholderValues(raw) {
184
182
  values.push(entry);
185
183
  continue;
186
184
  }
187
- if (isObjectRecord(entry) && typeof entry.value === 'string' && entry.value.length > 0) {
185
+ if (guards.isRecord(entry) && typeof entry.value === 'string' && entry.value.length > 0) {
188
186
  values.push(entry.value);
189
187
  continue;
190
188
  }
@@ -1,11 +1,9 @@
1
1
  import { runWorkflowEngine, workflowStepFailure, workflowStepSuccess, } from '../engine.js';
2
2
  import { resolveWorkflowTargetFromUnknown, } from '../resolve.js';
3
+ import { isRecord } from '../../guards.js';
3
4
  const TEXT_TRANSFORM_ACTIONS = ['replace_all', 'delete_all', 'rewrite_block', 'fill_placeholders'];
4
5
  const CHANGE_MODES = ['direct', 'tracked'];
5
6
  const REWRITE_BLOCK_SUPPORTED_NODE_TYPES = ['paragraph', 'heading', 'listItem'];
6
- function isObjectRecord(value) {
7
- return value != null && typeof value === 'object' && !Array.isArray(value);
8
- }
9
7
  function coerceBoolean(raw, fallback) {
10
8
  if (typeof raw === 'boolean') {
11
9
  return raw;
@@ -152,7 +150,7 @@ function parseEdits(raw) {
152
150
  }
153
151
  const edits = [];
154
152
  for (const entry of raw) {
155
- if (!isObjectRecord(entry)) {
153
+ if (!isRecord(entry)) {
156
154
  return undefined;
157
155
  }
158
156
  const find = entry.find;
@@ -181,7 +179,7 @@ function parsePlaceholderValues(raw) {
181
179
  values.push(entry);
182
180
  continue;
183
181
  }
184
- if (isObjectRecord(entry) && typeof entry.value === 'string' && entry.value.length > 0) {
182
+ if (isRecord(entry) && typeof entry.value === 'string' && entry.value.length > 0) {
185
183
  values.push(entry.value);
186
184
  continue;
187
185
  }
@@ -4,6 +4,7 @@ var errors = require('../runtime/errors.cjs');
4
4
  var presets = require('../presets.cjs');
5
5
  var actions = require('../agent/actions.cjs');
6
6
  var catalog = require('../agent/catalog.cjs');
7
+ var guards = require('../guards.cjs');
7
8
 
8
9
  /**
9
10
  * Customer-extensible **custom actions** for the SuperDoc LLM-tools SDK. The
@@ -71,7 +72,7 @@ function coerceInputSchema(input) {
71
72
  // `{ type: 'object', additionalProperties: true }`). The declared type makes
72
73
  // `properties` required and every consumer (and the Python mirror) assumes
73
74
  // it exists, so normalize a missing/non-object bag to {}.
74
- return isRecord(input.properties) ? input : { ...input, properties: {} };
75
+ return guards.isRecord(input.properties) ? input : { ...input, properties: {} };
75
76
  }
76
77
  // A bare properties bag — wrap it.
77
78
  return { type: 'object', properties: input, additionalProperties: true };
@@ -121,7 +122,7 @@ function defineAction(input) {
121
122
  if (!BUILTIN_ACTION_NAMES.has(step.action)) {
122
123
  throw new errors.SuperDocCliError(`defineAction "${input.name}": steps[${index}].action "${step.action}" is not a built-in core action. Steps compose built-in actions only; use the \`run\` tier for anything else.`, { code: 'INVALID_ARGUMENT', details: { name: input.name, index, action: step.action } });
123
124
  }
124
- if (step.args != null && !isRecord(step.args)) {
125
+ if (step.args != null && !guards.isRecord(step.args)) {
125
126
  throw new errors.SuperDocCliError(`defineAction "${input.name}": steps[${index}].args must be an object.`, {
126
127
  code: 'INVALID_ARGUMENT',
127
128
  details: { name: input.name, index },
@@ -240,9 +241,6 @@ function assertActionsValid(actions, presetId, standalone = false) {
240
241
  // ---------------------------------------------------------------------------
241
242
  // runCustomAction — validate, codegen, dispatch via superdoc_execute_code, map receipt
242
243
  // ---------------------------------------------------------------------------
243
- function isRecord(value) {
244
- return value != null && typeof value === 'object' && !Array.isArray(value);
245
- }
246
244
  /** Kit-level args every custom action accepts without declaring them. */
247
245
  const IMPLICIT_ACTION_ARGS = new Set(['changeMode', 'rationale']);
248
246
  function validateAgainstSchema(action, args) {
@@ -254,7 +252,7 @@ function validateAgainstSchema(action, args) {
254
252
  details: { action: action.name, missingKeys: missing },
255
253
  });
256
254
  }
257
- const properties = isRecord(action.inputSchema.properties) ? action.inputSchema.properties : {};
255
+ const properties = guards.isRecord(action.inputSchema.properties) ? action.inputSchema.properties : {};
258
256
  if (action.inputSchema.additionalProperties === false) {
259
257
  const unknown = Object.keys(args).filter((key) => !(key in properties) && !IMPLICIT_ACTION_ARGS.has(key));
260
258
  if (unknown.length > 0) {
@@ -266,7 +264,7 @@ function validateAgainstSchema(action, args) {
266
264
  }
267
265
  for (const [key, prop] of Object.entries(properties)) {
268
266
  const value = args[key];
269
- if (value !== undefined && isRecord(prop) && Array.isArray(prop.enum) && !prop.enum.includes(value)) {
267
+ if (value !== undefined && guards.isRecord(prop) && Array.isArray(prop.enum) && !prop.enum.includes(value)) {
270
268
  throw new errors.SuperDocCliError(`Invalid value for ${action.name}.${key}: ${JSON.stringify(value)} (allowed: ${prop.enum.map((entry) => JSON.stringify(entry)).join(', ')})`, { code: 'INVALID_ARGUMENT', details: { action: action.name, key, allowed: prop.enum } });
271
269
  }
272
270
  }
@@ -275,7 +273,7 @@ function validateAgainstSchema(action, args) {
275
273
  function applyInputDefaults(action, args) {
276
274
  const out = { ...args };
277
275
  for (const [key, prop] of Object.entries(action.inputSchema.properties ?? {})) {
278
- if (out[key] === undefined && isRecord(prop) && 'default' in prop)
276
+ if (out[key] === undefined && guards.isRecord(prop) && 'default' in prop)
279
277
  out[key] = prop.default;
280
278
  }
281
279
  return out;
@@ -309,7 +307,7 @@ function substituteTemplates(node, vars) {
309
307
  // would crash the transport).
310
308
  return node.map((item) => substituteTemplates(item, vars)).filter((item) => item !== undefined);
311
309
  }
312
- if (isRecord(node)) {
310
+ if (guards.isRecord(node)) {
313
311
  const out = {};
314
312
  for (const [key, value] of Object.entries(node)) {
315
313
  const substituted = substituteTemplates(value, vars);
@@ -341,7 +339,7 @@ async function runStepsAction(base, action, documentHandle, args, invokeOptions)
341
339
  let receipt;
342
340
  try {
343
341
  const dispatched = await base.dispatch(documentHandle, 'superdoc_perform_action', { action: step.action, ...stepArgs }, stepOptions);
344
- receipt = isRecord(dispatched) ? dispatched : { status: 'ok', result: dispatched };
342
+ receipt = guards.isRecord(dispatched) ? dispatched : { status: 'ok', result: dispatched };
345
343
  }
346
344
  catch (error) {
347
345
  // Validation errors THROW from dispatch; runtime failures come back as
@@ -458,13 +456,13 @@ function renormalizeAnthropicCacheMarker(tools, provider, cacheRequested) {
458
456
  if (provider !== 'anthropic' || !cacheRequested || tools.length === 0)
459
457
  return tools;
460
458
  const stripped = tools.map((tool) => {
461
- if (!isRecord(tool) || !('cache_control' in tool))
459
+ if (!guards.isRecord(tool) || !('cache_control' in tool))
462
460
  return tool;
463
461
  const { cache_control: _drop, ...rest } = tool;
464
462
  return rest;
465
463
  });
466
464
  const last = stripped[stripped.length - 1];
467
- stripped[stripped.length - 1] = isRecord(last) ? { ...last, cache_control: { type: 'ephemeral' } } : last;
465
+ stripped[stripped.length - 1] = guards.isRecord(last) ? { ...last, cache_control: { type: 'ephemeral' } } : last;
468
466
  return stripped;
469
467
  }
470
468
  function customActionsDescription(actions) {
@@ -535,16 +533,16 @@ function mergeIntoAgentAction(tools, actions) {
535
533
  // Provider dialects: openai nests under `function` (`parameters`);
536
534
  // anthropic is flat with `input_schema`; the core agent dialect for
537
535
  // vercel is flat with `inputSchema`; generic is flat with `parameters`.
538
- const fn = isRecord(t.function) ? t.function : null;
536
+ const fn = guards.isRecord(t.function) ? t.function : null;
539
537
  const schemaContainer = fn ?? t;
540
538
  const schemaKey = 'input_schema' in schemaContainer
541
539
  ? 'input_schema'
542
540
  : 'inputSchema' in schemaContainer
543
541
  ? 'inputSchema'
544
542
  : 'parameters';
545
- const schema = isRecord(schemaContainer[schemaKey]) ? schemaContainer[schemaKey] : {};
546
- const properties = isRecord(schema.properties) ? { ...schema.properties } : {};
547
- const actionProp = isRecord(properties.action) ? { ...properties.action } : {};
543
+ const schema = guards.isRecord(schemaContainer[schemaKey]) ? schemaContainer[schemaKey] : {};
544
+ const properties = guards.isRecord(schema.properties) ? { ...schema.properties } : {};
545
+ const actionProp = guards.isRecord(properties.action) ? { ...properties.action } : {};
548
546
  const enumValues = Array.isArray(actionProp.enum) ? [...actionProp.enum] : [];
549
547
  for (const action of actions) {
550
548
  if (!enumValues.includes(action.name))
@@ -748,7 +746,7 @@ function extendPreset(baseId, options) {
748
746
  // must be refused before it runs (the base can only refuse built-ins).
749
747
  const exclusionOptions = invokeOptions;
750
748
  const excluded = new Set(exclusionOptions?.excludeActions ?? []);
751
- if (toolName === 'superdoc_perform_action' && isRecord(args) && typeof args.action === 'string') {
749
+ if (toolName === 'superdoc_perform_action' && guards.isRecord(args) && typeof args.action === 'string') {
752
750
  // Advertised surface == dispatchable surface: in standalone mode custom
753
751
  // actions are advertised as their own tools, so the (unadvertised)
754
752
  // perform_action route must not execute them — it falls through to the
@@ -32,6 +32,7 @@ import { SuperDocCliError } from '../runtime/errors.js';
32
32
  import { getPreset, } from '../presets.js';
33
33
  import { ACTION_NAMES_LIST } from '../agent/actions.js';
34
34
  import { AGENT_TOOL_NAMES, buildPerformActionDefinition } from '../agent/catalog.js';
35
+ import { isRecord } from '../guards.js';
35
36
  /** Which execution tier a spec uses. */
36
37
  export function executionKindOf(spec) {
37
38
  return Array.isArray(spec.steps) ? 'steps' : 'run';
@@ -237,9 +238,6 @@ function assertActionsValid(actions, presetId, standalone = false) {
237
238
  // ---------------------------------------------------------------------------
238
239
  // runCustomAction — validate, codegen, dispatch via superdoc_execute_code, map receipt
239
240
  // ---------------------------------------------------------------------------
240
- function isRecord(value) {
241
- return value != null && typeof value === 'object' && !Array.isArray(value);
242
- }
243
241
  /** Kit-level args every custom action accepts without declaring them. */
244
242
  const IMPLICIT_ACTION_ARGS = new Set(['changeMode', 'rationale']);
245
243
  function validateAgainstSchema(action, args) {
@@ -9,6 +9,7 @@ var listTransform = require('../action-primitives/tools/list-transform.cjs');
9
9
  var structureInsert = require('../action-primitives/tools/structure-insert.cjs');
10
10
  var textTransform = require('../action-primitives/tools/text-transform.cjs');
11
11
  var docSnapshot = require('./doc-snapshot.cjs');
12
+ var guards = require('../guards.cjs');
12
13
 
13
14
  const ACTION_DEFINITIONS = actionDescriptors;
14
15
  function projectActionDefinitions(project) {
@@ -72,9 +73,6 @@ function getActionDispositions() {
72
73
  function isActionName(value) {
73
74
  return typeof value === 'string' && ACTION_NAMES.includes(value);
74
75
  }
75
- function isRecord(value) {
76
- return value != null && typeof value === 'object' && !Array.isArray(value);
77
- }
78
76
  /** The message to put in a receipt when something best-effort did not happen. */
79
77
  function reasonOf(err) {
80
78
  return err instanceof Error ? err.message : String(err);
@@ -100,7 +98,7 @@ function isNoOp(err) {
100
98
  return err?.code === 'NO_OP';
101
99
  }
102
100
  function asRecord(value) {
103
- return isRecord(value) ? value : null;
101
+ return guards.isRecord(value) ? value : null;
104
102
  }
105
103
  function asString(value, fallback) {
106
104
  return typeof value === 'string' ? value : fallback;
@@ -657,7 +655,7 @@ async function previewMutations(doc, steps, changeMode, expectedRevision) {
657
655
  });
658
656
  }
659
657
  const result = await fn({ atomic: true, changeMode: changeMode ?? 'direct', expectedRevision, steps });
660
- return isRecord(result) ? result : {};
658
+ return guards.isRecord(result) ? result : {};
661
659
  }
662
660
  async function executeCreateTable(doc, args, placement) {
663
661
  const fn = maybeMethod(doc, ['create', 'table']);
@@ -1052,7 +1050,7 @@ async function runReplaceTextUnchecked(doc, args) {
1052
1050
  const preview = await previewMutations(doc, candidates.map((candidate) => candidate.step), args.changeMode, pre.revision);
1053
1051
  if (preview.valid === true)
1054
1052
  break;
1055
- const failures = Array.isArray(preview.failures) ? preview.failures.filter(isRecord) : [];
1053
+ const failures = Array.isArray(preview.failures) ? preview.failures.filter(guards.isRecord) : [];
1056
1054
  const absent = failures.find((failure) => failure.code === 'MATCH_NOT_FOUND' && typeof failure.stepId === 'string');
1057
1055
  if (!absent) {
1058
1056
  throw new Error(typeof failures[0]?.message === 'string' ? failures[0].message : 'replacement plan could not be prepared');
@@ -2713,7 +2711,7 @@ async function listAllTrackedChanges(listFn) {
2713
2711
  const page = asRecord(await listFn({ offset, limit: PAGE, in: 'all' }));
2714
2712
  const items = Array.isArray(page?.items) ? page.items : [];
2715
2713
  for (const item of items) {
2716
- if (isRecord(item))
2714
+ if (guards.isRecord(item))
2717
2715
  out.push(item);
2718
2716
  }
2719
2717
  const total = asNumber(page?.total, out.length) ?? out.length;
@@ -4290,12 +4288,12 @@ function findTextTarget(snapshot, nodeId) {
4290
4288
  return null;
4291
4289
  }
4292
4290
  function compactOpResult(result) {
4293
- if (!isRecord(result))
4291
+ if (!guards.isRecord(result))
4294
4292
  return result;
4295
4293
  const compact = {};
4296
4294
  if (typeof result.success === 'boolean')
4297
4295
  compact.success = result.success;
4298
- if (isRecord(result.revision))
4296
+ if (guards.isRecord(result.revision))
4299
4297
  compact.revision = result.revision;
4300
4298
  if (Array.isArray(result.steps))
4301
4299
  compact.stepCount = result.steps.length;
@@ -4311,7 +4309,7 @@ function trackedChangeRefIds(result) {
4311
4309
  }));
4312
4310
  }
4313
4311
  function parseScopedReplaceArgs(args, edits, changeMode, expectedRevision) {
4314
- if (!isRecord(args.selector))
4312
+ if (!guards.isRecord(args.selector))
4315
4313
  return null;
4316
4314
  return {
4317
4315
  edits,
@@ -4341,9 +4339,9 @@ async function fieldResultRanges(doc, nodeId, blockText) {
4341
4339
  const limit = 500;
4342
4340
  for (let offset = 0; offset < 100_000; offset += limit) {
4343
4341
  const page = await fn({ limit, offset });
4344
- const items = isRecord(page) && Array.isArray(page.items) ? page.items.filter(isRecord) : [];
4342
+ const items = guards.isRecord(page) && Array.isArray(page.items) ? page.items.filter(guards.isRecord) : [];
4345
4343
  for (const item of items) {
4346
- const address = isRecord(item.address) ? item.address : null;
4344
+ const address = guards.isRecord(item.address) ? item.address : null;
4347
4345
  const inBody = typeof address?.storyId !== 'string' || address.storyId.startsWith('main:');
4348
4346
  if (address?.blockId !== nodeId || !inBody)
4349
4347
  continue;
@@ -4352,7 +4350,7 @@ async function fieldResultRanges(doc, nodeId, blockText) {
4352
4350
  }
4353
4351
  results.push(item.resolvedText);
4354
4352
  }
4355
- const total = isRecord(page) && typeof page.total === 'number' ? page.total : null;
4353
+ const total = guards.isRecord(page) && typeof page.total === 'number' ? page.total : null;
4356
4354
  if (items.length < limit || (total != null && offset + limit >= total))
4357
4355
  break;
4358
4356
  }
@@ -4493,11 +4491,11 @@ async function runScopedReplaceUnchecked(doc, args) {
4493
4491
  // that each one still covers the text matched in the snapshot, so a
4494
4492
  // projection mismatch never rewrites other text.
4495
4493
  const preview = await previewMutations(doc, steps, args.changeMode, pre.revision);
4496
- const previewSteps = Array.isArray(preview.steps) ? preview.steps.filter(isRecord) : [];
4494
+ const previewSteps = Array.isArray(preview.steps) ? preview.steps.filter(guards.isRecord) : [];
4497
4495
  const aligned = preview.valid === true &&
4498
4496
  steps.every((step, index) => {
4499
4497
  const resolutions = previewSteps.find((candidate) => candidate.stepId === step.id)?.resolutions;
4500
- const resolved = Array.isArray(resolutions) && isRecord(resolutions[0]) ? resolutions[0].text : undefined;
4498
+ const resolved = Array.isArray(resolutions) && guards.isRecord(resolutions[0]) ? resolutions[0].text : undefined;
4501
4499
  const span = targetedSpans[index];
4502
4500
  return resolved === block.text.slice(span.start, span.end);
4503
4501
  });
@@ -4530,7 +4528,7 @@ async function runScopedReplaceUnchecked(doc, args) {
4530
4528
  // atomic history step. Only do so while the document is still at the
4531
4529
  // revision this plan produced, so the undo cannot reach any other change.
4532
4530
  let rolledBack = false;
4533
- const appliedRevision = isRecord(result) && isRecord(result.revision) ? result.revision.after : undefined;
4531
+ const appliedRevision = guards.isRecord(result) && guards.isRecord(result.revision) ? result.revision.after : undefined;
4534
4532
  if (wroteExactTargets &&
4535
4533
  postBlock != null &&
4536
4534
  postBlock.text !== expected &&
@@ -4645,13 +4643,13 @@ function markRunPropsFromMarks(marks) {
4645
4643
  * paragraphIndentOf. Returns null when the paragraph mark carries nothing.
4646
4644
  */
4647
4645
  function paragraphMarkRunPropsOf(payload) {
4648
- const node = isRecord(payload) ? payload.node : null;
4649
- if (!isRecord(node))
4646
+ const node = guards.isRecord(payload) ? payload.node : null;
4647
+ if (!guards.isRecord(node))
4650
4648
  return null;
4651
- const candidates = [isRecord(node.paragraph) ? node.paragraph.props : null, node.props];
4649
+ const candidates = [guards.isRecord(node.paragraph) ? node.paragraph.props : null, node.props];
4652
4650
  for (const props of candidates) {
4653
- const mark = isRecord(props) ? props.markRunProps : null;
4654
- if (isRecord(mark) && Object.keys(mark).length > 0)
4651
+ const mark = guards.isRecord(props) ? props.markRunProps : null;
4652
+ if (guards.isRecord(mark) && Object.keys(mark).length > 0)
4655
4653
  return mark;
4656
4654
  }
4657
4655
  return null;
@@ -4896,13 +4894,13 @@ async function listItemRunPattern(doc, text, blockId) {
4896
4894
  * indent copied" rather than a thrown read.
4897
4895
  */
4898
4896
  function paragraphIndentOf(payload) {
4899
- const node = isRecord(payload) ? payload.node : null;
4900
- if (!isRecord(node))
4897
+ const node = guards.isRecord(payload) ? payload.node : null;
4898
+ if (!guards.isRecord(node))
4901
4899
  return null;
4902
- const candidates = [isRecord(node.paragraph) ? node.paragraph.props : null, node.props];
4900
+ const candidates = [guards.isRecord(node.paragraph) ? node.paragraph.props : null, node.props];
4903
4901
  for (const props of candidates) {
4904
- const indent = isRecord(props) ? props.indent : null;
4905
- if (!isRecord(indent))
4902
+ const indent = guards.isRecord(props) ? props.indent : null;
4903
+ if (!guards.isRecord(indent))
4906
4904
  continue;
4907
4905
  const twips = {};
4908
4906
  for (const key of ['left', 'right', 'firstLine', 'hanging']) {
@@ -4979,7 +4977,7 @@ function marksToInline(marks) {
4979
4977
  }
4980
4978
  /** Parse a raw agent mark bag, keeping only recognized keys with valid types. */
4981
4979
  function normalizeMarks(raw) {
4982
- if (!isRecord(raw))
4980
+ if (!guards.isRecord(raw))
4983
4981
  return undefined;
4984
4982
  const m = {};
4985
4983
  if (typeof raw.bold === 'boolean')
@@ -5009,7 +5007,7 @@ function normalizeMarks(raw) {
5009
5007
  function normalizeItemSpec(raw) {
5010
5008
  if (typeof raw === 'string')
5011
5009
  return { text: raw, level: 0 };
5012
- if (!isRecord(raw))
5010
+ if (!guards.isRecord(raw))
5013
5011
  return null;
5014
5012
  const spec = {};
5015
5013
  const lvl = asNumber(raw.level);
@@ -5023,7 +5021,7 @@ function normalizeItemSpec(raw) {
5023
5021
  if (typeof r === 'string') {
5024
5022
  runs.push({ text: r });
5025
5023
  }
5026
- else if (isRecord(r) && typeof r.text === 'string') {
5024
+ else if (guards.isRecord(r) && typeof r.text === 'string') {
5027
5025
  const rm = normalizeMarks(r.marks);
5028
5026
  runs.push(rm ? { text: r.text, marks: rm } : { text: r.text });
5029
5027
  }
@@ -5642,9 +5640,9 @@ async function appendListAtPlacement(doc, args) {
5642
5640
  const items = Array.isArray(args.items)
5643
5641
  ? args.items.filter((t) => typeof t === 'string' && t.length > 0)
5644
5642
  : [];
5645
- const placement = isRecord(args.placement) ? args.placement : null;
5643
+ const placement = guards.isRecord(args.placement) ? args.placement : null;
5646
5644
  const at = placement && typeof placement.at === 'string' ? placement.at : null;
5647
- const selector = placement && isRecord(placement.selector) ? placement.selector : null;
5645
+ const selector = placement && guards.isRecord(placement.selector) ? placement.selector : null;
5648
5646
  if (items.length === 0 || !placement || (at !== 'after' && at !== 'before') || !selector)
5649
5647
  return null;
5650
5648
  const pre = await docSnapshot.buildMutationSnapshot(doc, { includeDomains: ['blocks'] });
@@ -6042,15 +6040,15 @@ async function matchInsertedBlockFormatting(doc, preBlocks, receipt, args, avail
6042
6040
  return receipt;
6043
6041
  // The placement anchor, when the action targeted one, is the user's own
6044
6042
  // statement of "blend in with THIS" — prefer it over type-matched siblings.
6045
- const placement = isRecord(args?.placement) ? args.placement : null;
6046
- const anchorSelector = placement && isRecord(placement.selector) ? placement.selector : null;
6043
+ const placement = guards.isRecord(args?.placement) ? args.placement : null;
6044
+ const anchorSelector = placement && guards.isRecord(placement.selector) ? placement.selector : null;
6047
6045
  const anchorId = anchorSelector && typeof anchorSelector.nodeId === 'string' ? anchorSelector.nodeId : null;
6048
6046
  const anchorRow = anchorId ? (postBlocks.find((b) => b.nodeId === anchorId) ?? null) : null;
6049
6047
  // When the caller styled any entry explicitly (runs/marks), it took
6050
6048
  // ownership of intra-paragraph formatting — the automatic lead-marker
6051
6049
  // pattern pass must not fight it.
6052
6050
  const rawTexts = Array.isArray(args?.texts) ? args.texts : [];
6053
- const hasExplicitStyling = rawTexts.some((entry) => isRecord(entry) && (Array.isArray(entry.runs) || entry.marks != null));
6051
+ const hasExplicitStyling = rawTexts.some((entry) => guards.isRecord(entry) && (Array.isArray(entry.runs) || entry.marks != null));
6054
6052
  const insertedTexts = rawTexts.map((entry) => {
6055
6053
  if (typeof entry === 'string')
6056
6054
  return entry;
@@ -6075,7 +6073,7 @@ async function matchInsertedBlockFormatting(doc, preBlocks, receipt, args, avail
6075
6073
  // the previous NAME line, not like the "Name:" line directly above it.
6076
6074
  // Only tried when matching against the neighbour found nothing, so this
6077
6075
  // cannot change a case that already had an answer.
6078
- const applied = isRecord(result?.applied) ? result.applied : null;
6076
+ const applied = guards.isRecord(result?.applied) ? result.applied : null;
6079
6077
  if (createdRows.length > 1 && (!applied || Object.keys(applied).length === 0)) {
6080
6078
  const parallel = postBlocks[createdIndex - createdRows.length];
6081
6079
  if (parallel && preIds.has(parallel.nodeId) && !parallel.isEmpty) {
@@ -6291,7 +6289,7 @@ async function runAttachNumbering(doc, args) {
6291
6289
  };
6292
6290
  }
6293
6291
  async function dispatchSuperdocPerformAction(doc, args) {
6294
- if (!isRecord(args)) {
6292
+ if (!guards.isRecord(args)) {
6295
6293
  throw new errors.SuperDocCliError('superdoc_perform_action arguments must be an object', {
6296
6294
  code: 'INVALID_ARGUMENT',
6297
6295
  });
@@ -6365,7 +6363,7 @@ async function dispatchSuperdocPerformAction(doc, args) {
6365
6363
  if (expectedRevision !== undefined && (typeof expectedRevision !== 'string' || expectedRevision.length === 0)) {
6366
6364
  throw new errors.SuperDocCliError('replace_text: expectedRevision must be a non-empty string from this live document handle', { code: 'INVALID_ARGUMENT' });
6367
6365
  }
6368
- if (edits.some((edit) => edit.occurrence != null) && !isRecord(args.selector)) {
6366
+ if (edits.some((edit) => edit.occurrence != null) && !guards.isRecord(args.selector)) {
6369
6367
  throw new errors.SuperDocCliError('replace_text: explicit occurrence requires a selector resolving to one block', {
6370
6368
  code: 'INVALID_ARGUMENT',
6371
6369
  });
@@ -6995,13 +6993,13 @@ function parseExactTrackedChangeIdList(value) {
6995
6993
  }
6996
6994
  async function superdocPerformAction(doc, args) {
6997
6995
  try {
6998
- const changeMode = isRecord(args) ? parseChangeMode(args.changeMode) : undefined;
6996
+ const changeMode = guards.isRecord(args) ? parseChangeMode(args.changeMode) : undefined;
6999
6997
  return await dispatchSuperdocPerformAction(changeMode ? actionChangeMode.bindActionChangeMode(doc, changeMode) : doc, args);
7000
6998
  }
7001
6999
  catch (error) {
7002
7000
  if (!(error instanceof docSnapshot.MutationSnapshotError))
7003
7001
  throw error;
7004
- const action = isRecord(args) && isActionName(args.action) ? args.action : 'superdoc_perform_action';
7002
+ const action = guards.isRecord(args) && isActionName(args.action) ? args.action : 'superdoc_perform_action';
7005
7003
  return {
7006
7004
  status: 'failed',
7007
7005
  intent: action,
@@ -7062,7 +7060,7 @@ function parseEdits(value) {
7062
7060
  return undefined;
7063
7061
  const result = [];
7064
7062
  for (const entry of value) {
7065
- if (!isRecord(entry))
7063
+ if (!guards.isRecord(entry))
7066
7064
  return undefined;
7067
7065
  const find = asString(entry.find);
7068
7066
  const replace = asString(entry.replace) ?? '';
@@ -7082,7 +7080,7 @@ function parseEdits(value) {
7082
7080
  function parseCellCandidate(cell) {
7083
7081
  if (typeof cell === 'string')
7084
7082
  return cell;
7085
- if (isRecord(cell)) {
7083
+ if (guards.isRecord(cell)) {
7086
7084
  const spec = normalizeItemSpec(cell);
7087
7085
  if (spec)
7088
7086
  return { text: spec.text, runs: spec.runs, marks: spec.marks };
@@ -7110,7 +7108,7 @@ function parsePlaceholderFields(value) {
7110
7108
  return undefined;
7111
7109
  const result = [];
7112
7110
  for (const entry of value) {
7113
- if (!isRecord(entry) || typeof entry.value !== 'string')
7111
+ if (!guards.isRecord(entry) || typeof entry.value !== 'string')
7114
7112
  return undefined;
7115
7113
  const label = asString(entry.label);
7116
7114
  result.push(label ? { label, value: entry.value } : { value: entry.value });
@@ -7118,7 +7116,7 @@ function parsePlaceholderFields(value) {
7118
7116
  return result;
7119
7117
  }
7120
7118
  function parsePlacement(value) {
7121
- if (!isRecord(value))
7119
+ if (!guards.isRecord(value))
7122
7120
  return undefined;
7123
7121
  const at = asString(value.at);
7124
7122
  if (at === 'document_end' || at === 'document_start') {
@@ -7133,7 +7131,7 @@ function parsePlacement(value) {
7133
7131
  return undefined;
7134
7132
  }
7135
7133
  function parseSelector(value) {
7136
- if (!isRecord(value))
7134
+ if (!guards.isRecord(value))
7137
7135
  return undefined;
7138
7136
  const kind = asString(value.kind);
7139
7137
  if (kind === 'nodeId') {
@@ -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);
@@ -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' ||
@@ -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' &&
@@ -1,17 +1,15 @@
1
1
  'use strict';
2
2
 
3
3
  var documentEvidence = require('../runtime/document-evidence.cjs');
4
+ var guards = require('../guards.cjs');
4
5
 
5
6
  const LEGACY_TRACKED_CHANGE_TYPES = {
6
7
  insertion: 'insert',
7
8
  deletion: 'delete',
8
9
  formatting: 'format',
9
10
  };
10
- function isRecord(value) {
11
- return typeof value === 'object' && value !== null;
12
- }
13
11
  function normalizeTrackedChangeItem(item) {
14
- if (!isRecord(item))
12
+ if (!guards.isRecord(item))
15
13
  return item;
16
14
  const type = item.type;
17
15
  if (typeof type !== 'string' || !Object.hasOwn(LEGACY_TRACKED_CHANGE_TYPES, type))
@@ -22,7 +20,7 @@ function normalizeTrackedChangeItem(item) {
22
20
  };
23
21
  }
24
22
  function normalizeTrackedChangeList(result) {
25
- if (!isRecord(result) || !Array.isArray(result.items))
23
+ if (!guards.isRecord(result) || !Array.isArray(result.items))
26
24
  return result;
27
25
  return {
28
26
  ...result,
@@ -30,7 +28,7 @@ function normalizeTrackedChangeList(result) {
30
28
  };
31
29
  }
32
30
  function isThenable(value) {
33
- return isRecord(value) && typeof value.then === 'function';
31
+ return guards.isRecord(value) && typeof value.then === 'function';
34
32
  }
35
33
  function applyNormalization(result, normalize) {
36
34
  return isThenable(result) ? result.then(normalize) : normalize(result);
@@ -43,7 +41,7 @@ function applyNormalization(result, normalize) {
43
41
  */
44
42
  function wrapV2PresetCompat(doc) {
45
43
  const sourceTrackChanges = Reflect.get(doc, 'trackChanges');
46
- if (!isRecord(sourceTrackChanges))
44
+ if (!guards.isRecord(sourceTrackChanges))
47
45
  return doc;
48
46
  const trackChanges = new Proxy(sourceTrackChanges, {
49
47
  get(target, property, receiver) {
@@ -1,12 +1,10 @@
1
1
  import { forwardDocumentEvidence } from '../runtime/document-evidence.js';
2
+ import { isRecord } from '../guards.js';
2
3
  const LEGACY_TRACKED_CHANGE_TYPES = {
3
4
  insertion: 'insert',
4
5
  deletion: 'delete',
5
6
  formatting: 'format',
6
7
  };
7
- function isRecord(value) {
8
- return typeof value === 'object' && value !== null;
9
- }
10
8
  function normalizeTrackedChangeItem(item) {
11
9
  if (!isRecord(item))
12
10
  return item;
@@ -0,0 +1,7 @@
1
+ 'use strict';
2
+
3
+ function isRecord(value) {
4
+ return typeof value === 'object' && value != null && !Array.isArray(value);
5
+ }
6
+
7
+ exports.isRecord = isRecord;
@@ -0,0 +1 @@
1
+ export declare function isRecord(value: unknown): value is Record<string, unknown>;
package/dist/guards.js ADDED
@@ -0,0 +1,3 @@
1
+ export function isRecord(value) {
2
+ return typeof value === 'object' && value != null && !Array.isArray(value);
3
+ }
@@ -9,6 +9,7 @@ var catalog = require('../agent/catalog.cjs');
9
9
  var runtime = require('../agent/runtime.cjs');
10
10
  var actions = require('../agent/actions.cjs');
11
11
  var embeddedPrompts_generated = require('../embedded-prompts.generated.cjs');
12
+ var guards = require('../guards.cjs');
12
13
 
13
14
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
14
15
  /**
@@ -74,9 +75,6 @@ async function readPromptFile(fileName, label, candidateDirs = PROMPT_DIR_CANDID
74
75
  details: { triedPaths: tried },
75
76
  });
76
77
  }
77
- function isRecord(value) {
78
- return typeof value === 'object' && value != null && !Array.isArray(value);
79
- }
80
78
  function isObviouslyCorruptedToolArgKey(key) {
81
79
  const trimmed = key.trim();
82
80
  return trimmed.length === 0 || !/[\p{L}\p{N}]/u.test(trimmed);
@@ -85,7 +83,7 @@ function stripCorruptedToolArgKeys(value) {
85
83
  if (Array.isArray(value)) {
86
84
  return value.map((item) => stripCorruptedToolArgKeys(item));
87
85
  }
88
- if (!isRecord(value))
86
+ if (!guards.isRecord(value))
89
87
  return value;
90
88
  const clean = {};
91
89
  for (const [key, entryValue] of Object.entries(value)) {
@@ -109,7 +107,7 @@ function validateAgentToolArgs(toolName, args) {
109
107
  if (definition == null)
110
108
  return;
111
109
  const schema = definition.inputSchema;
112
- const properties = isRecord(schema.properties) ? schema.properties : {};
110
+ const properties = guards.isRecord(schema.properties) ? schema.properties : {};
113
111
  const required = Array.isArray(schema.required) ? schema.required : [];
114
112
  const knownKeys = new Set(Object.keys(properties));
115
113
  const unknownKeys = Object.keys(args).filter((key) => !knownKeys.has(key));
@@ -162,7 +160,7 @@ function compactCountMap(counts) {
162
160
  return compact;
163
161
  }
164
162
  function pickScalarFields(value, keys, limit = 6) {
165
- if (!isRecord(value))
163
+ if (!guards.isRecord(value))
166
164
  return {};
167
165
  const entries = keys
168
166
  ? keys.filter((key) => key in value).map((key) => [key, value[key]])
@@ -186,12 +184,12 @@ function compactOperationResult(result) {
186
184
  }
187
185
  if (Array.isArray(result))
188
186
  return { itemCount: result.length };
189
- if (!isRecord(result))
187
+ if (!guards.isRecord(result))
190
188
  return { kind: typeof result };
191
189
  const compact = {
192
190
  ...pickScalarFields(result, ['success', 'status', 'count', 'total', 'applied', 'created', 'deleted']),
193
191
  };
194
- if (isRecord(result.revision)) {
192
+ if (guards.isRecord(result.revision)) {
195
193
  const revision = pickScalarFields(result.revision, ['before', 'after', 'current']);
196
194
  if (Object.keys(revision).length > 0)
197
195
  compact.revision = revision;
@@ -200,7 +198,7 @@ function compactOperationResult(result) {
200
198
  compact.stepCount = result.steps.length;
201
199
  compact.steps = result.steps
202
200
  .slice(0, 4)
203
- .map((step) => (isRecord(step) ? pickScalarFields(step, ['stepId', 'op', 'effect', 'matchCount'], 4) : {}));
201
+ .map((step) => (guards.isRecord(step) ? pickScalarFields(step, ['stepId', 'op', 'effect', 'matchCount'], 4) : {}));
204
202
  }
205
203
  if (Array.isArray(result.items))
206
204
  compact.itemCount = result.items.length;
@@ -532,14 +530,14 @@ const ENFORCEMENT_SIDE_DOORS = new Set([
532
530
  ]);
533
531
  const POLICY_OPTION_KEYS = ['excludeActions', 'enforceChangeMode', 'allowDirectActions'];
534
532
  async function coreDispatch(documentHandle, toolName, args, invokeOptions) {
535
- if (!isRecord(args)) {
533
+ if (!guards.isRecord(args)) {
536
534
  throw new errors.SuperDocCliError(`Tool arguments for ${toolName} must be an object.`, {
537
535
  code: 'INVALID_ARGUMENT',
538
536
  details: { toolName },
539
537
  });
540
538
  }
541
539
  const sanitizedArgs = stripCorruptedToolArgKeys(args);
542
- if (!isRecord(sanitizedArgs)) {
540
+ if (!guards.isRecord(sanitizedArgs)) {
543
541
  throw new errors.SuperDocCliError(`Tool arguments for ${toolName} must be an object.`, {
544
542
  code: 'INVALID_ARGUMENT',
545
543
  details: { toolName },
@@ -601,7 +599,7 @@ async function coreDispatch(documentHandle, toolName, args, invokeOptions) {
601
599
  if (catalog.isAgentToolName(toolName)) {
602
600
  validateAgentToolArgs(toolName, dispatchArgs);
603
601
  const result = await dispatchAgentTool(documentHandle, toolName, dispatchArgs, forwardOptions);
604
- if (enforcing && toolName === 'superdoc_perform_action' && isRecord(result)) {
602
+ if (enforcing && toolName === 'superdoc_perform_action' && guards.isRecord(result)) {
605
603
  // Receipts under enforcement state the mode that actually ran, so a host
606
604
  // can audit "was this edit tracked?" without re-deriving the policy.
607
605
  const changeMode = dispatchArgs.changeMode === 'tracked' ? 'tracked' : 'direct';
@@ -22,6 +22,7 @@ import { AGENT_TOOL_DEFINITIONS, isAgentToolName, listAgentTools, resolveActionP
22
22
  import { agentApply, agentInspect, agentOperation, agentVerify } from '../agent/runtime.js';
23
23
  import { ACTION_ARGS, ACTION_DISPOSITIONS, isActionName, superdocPerformAction } from '../agent/actions.js';
24
24
  import { EMBEDDED_PROMPTS } from '../embedded-prompts.generated.js';
25
+ import { isRecord } from '../guards.js';
25
26
  // Prompts ship as <dist>/prompts/*.md. Resolve relative to the compiled
26
27
  // module, but the module's depth differs by build: in the SDK package this
27
28
  // file is dist/presets/core.js (so prompts are at ../prompts), while the
@@ -70,9 +71,6 @@ export async function readPromptFile(fileName, label, candidateDirs = PROMPT_DIR
70
71
  details: { triedPaths: tried },
71
72
  });
72
73
  }
73
- function isRecord(value) {
74
- return typeof value === 'object' && value != null && !Array.isArray(value);
75
- }
76
74
  function isObviouslyCorruptedToolArgKey(key) {
77
75
  const trimmed = key.trim();
78
76
  return trimmed.length === 0 || !/[\p{L}\p{N}]/u.test(trimmed);
@@ -6,6 +6,7 @@ var node_url = require('node:url');
6
6
  var errors = require('../runtime/errors.cjs');
7
7
  var intentDispatch_generated = require('../generated/intent-dispatch.generated.cjs');
8
8
  var embeddedTools_generated = require('../embedded-tools.generated.cjs');
9
+ var guards = require('../guards.cjs');
9
10
 
10
11
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
11
12
  /**
@@ -32,9 +33,6 @@ const providerFileByName = {
32
33
  generic: 'tools.generic.json',
33
34
  };
34
35
  const STRIP_EMPTY_OPTIONAL_ARGS = new Set(['parentId', 'parentCommentId', 'id', 'status']);
35
- function isRecord(value) {
36
- return typeof value === 'object' && value != null && !Array.isArray(value);
37
- }
38
36
  function isObviouslyCorruptedToolArgKey(key) {
39
37
  const trimmed = key.trim();
40
38
  return trimmed.length === 0 || !/[\p{L}\p{N}]/u.test(trimmed);
@@ -43,7 +41,7 @@ function stripCorruptedToolArgKeys(value) {
43
41
  if (Array.isArray(value)) {
44
42
  return value.map((item) => stripCorruptedToolArgKeys(item));
45
43
  }
46
- if (!isRecord(value))
44
+ if (!guards.isRecord(value))
47
45
  return value;
48
46
  const clean = {};
49
47
  for (const [key, entryValue] of Object.entries(value)) {
@@ -139,7 +137,7 @@ function resolveDocApiMethod(documentHandle, operationId) {
139
137
  const tokens = operationId.split('.').slice(1);
140
138
  let cursor = documentHandle;
141
139
  for (const token of tokens) {
142
- if (!isRecord(cursor) || !(token in cursor)) {
140
+ if (!guards.isRecord(cursor) || !(token in cursor)) {
143
141
  throw new errors.SuperDocCliError(`No SDK doc method found for operation ${operationId}.`, {
144
142
  code: 'TOOL_DISPATCH_NOT_FOUND',
145
143
  details: { operationId, token },
@@ -165,7 +163,7 @@ function resolveDocApiMethod(documentHandle, operationId) {
165
163
  */
166
164
  function validateToolArgs(toolName, args, tool) {
167
165
  const schema = tool.inputSchema;
168
- const properties = isRecord(schema.properties) ? schema.properties : {};
166
+ const properties = guards.isRecord(schema.properties) ? schema.properties : {};
169
167
  const required = Array.isArray(schema.required) ? schema.required : [];
170
168
  // 1. Reject unknown keys (additionalProperties: false in merged schema)
171
169
  const knownKeys = new Set(Object.keys(properties));
@@ -247,14 +245,14 @@ async function legacyGetMcpPrompt() {
247
245
  return (await readToolFile('system-prompt-mcp.md', 'MCP system prompt not found.')).raw;
248
246
  }
249
247
  async function legacyDispatch(documentHandle, toolName, args, invokeOptions) {
250
- if (!isRecord(args)) {
248
+ if (!guards.isRecord(args)) {
251
249
  throw new errors.SuperDocCliError(`Tool arguments for ${toolName} must be an object.`, {
252
250
  code: 'INVALID_ARGUMENT',
253
251
  details: { toolName },
254
252
  });
255
253
  }
256
254
  const sanitizedArgs = stripCorruptedToolArgKeys(args);
257
- if (!isRecord(sanitizedArgs)) {
255
+ if (!guards.isRecord(sanitizedArgs)) {
258
256
  throw new errors.SuperDocCliError(`Tool arguments for ${toolName} must be an object.`, {
259
257
  code: 'INVALID_ARGUMENT',
260
258
  details: { toolName },
@@ -19,6 +19,7 @@ import { fileURLToPath } from 'node:url';
19
19
  import { SuperDocCliError } from '../runtime/errors.js';
20
20
  import { dispatchIntentTool } from '../generated/intent-dispatch.generated.js';
21
21
  import { EMBEDDED_TOOLS } from '../embedded-tools.generated.js';
22
+ import { isRecord } from '../guards.js';
22
23
  const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
23
24
  const TOOLS_DIR_CANDIDATES = [path.resolve(MODULE_DIR, '..', '..', 'tools'), path.resolve(MODULE_DIR, 'tools')];
24
25
  const providerFileByName = {
@@ -28,9 +29,6 @@ const providerFileByName = {
28
29
  generic: 'tools.generic.json',
29
30
  };
30
31
  const STRIP_EMPTY_OPTIONAL_ARGS = new Set(['parentId', 'parentCommentId', 'id', 'status']);
31
- function isRecord(value) {
32
- return typeof value === 'object' && value != null && !Array.isArray(value);
33
- }
34
32
  function isObviouslyCorruptedToolArgKey(key) {
35
33
  const trimmed = key.trim();
36
34
  return trimmed.length === 0 || !/[\p{L}\p{N}]/u.test(trimmed);
@@ -3,6 +3,6 @@
3
3
  // AUTO-GENERATED by scripts/embed-version.mjs — DO NOT EDIT.
4
4
  // Source of truth: package.json. Regenerated on every SDK build so the
5
5
  // SDK retains its own version identity when bundled into another package.
6
- const SDK_VERSION = '2.23.0-next.8';
6
+ const SDK_VERSION = '2.23.0-next.9';
7
7
 
8
8
  exports.SDK_VERSION = SDK_VERSION;
@@ -1 +1 @@
1
- export declare const SDK_VERSION = "2.23.0-next.8";
1
+ export declare const SDK_VERSION = "2.23.0-next.9";
@@ -1,4 +1,4 @@
1
1
  // AUTO-GENERATED by scripts/embed-version.mjs — DO NOT EDIT.
2
2
  // Source of truth: package.json. Regenerated on every SDK build so the
3
3
  // SDK retains its own version identity when bundled into another package.
4
- export const SDK_VERSION = '2.23.0-next.8';
4
+ export const SDK_VERSION = '2.23.0-next.9';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@superdoc/sdk",
3
- "version": "2.23.0-next.8",
3
+ "version": "2.23.0-next.9",
4
4
  "description": "Node SDK for SuperDoc, wrapping the SuperDoc CLI to read and edit .docx files from JavaScript and TypeScript.",
5
5
  "private": false,
6
6
  "license": "AGPL-3.0",
@@ -38,11 +38,11 @@
38
38
  "typescript": "^5.9.2"
39
39
  },
40
40
  "optionalDependencies": {
41
- "@superdoc/sdk-darwin-arm64": "2.23.0-next.8",
42
- "@superdoc/sdk-darwin-x64": "2.23.0-next.8",
43
- "@superdoc/sdk-linux-x64": "2.23.0-next.8",
44
- "@superdoc/sdk-linux-arm64": "2.23.0-next.8",
45
- "@superdoc/sdk-windows-x64": "2.23.0-next.8"
41
+ "@superdoc/sdk-darwin-arm64": "2.23.0-next.9",
42
+ "@superdoc/sdk-darwin-x64": "2.23.0-next.9",
43
+ "@superdoc/sdk-linux-x64": "2.23.0-next.9",
44
+ "@superdoc/sdk-linux-arm64": "2.23.0-next.9",
45
+ "@superdoc/sdk-windows-x64": "2.23.0-next.9"
46
46
  },
47
47
  "publishConfig": {
48
48
  "access": "public"
@@ -2,7 +2,7 @@
2
2
  "formatVersion": 1,
3
3
  "contractHash": "bc924b5585efd8f81307d866cfc6ec679039be9dc2d1f2e8c0eb0c394e2be5f7",
4
4
  "documentApiSourceHash": "1a2d43b0207099cefeb15344c75639185d45e4cf46ffa487292771370935f92b",
5
- "generatorHash": "582aa8e90f60ac6426891df34d9d815413f4c0eab37300fd438d7a393673c614",
5
+ "generatorHash": "acab65c1f6e7ff2e385845f9e1e3c9a9fa0438cc4b6fbba0bb0bdbbb48a044e9",
6
6
  "inputs": {
7
7
  "apps/cli/src/host/sdk-policy.ts": "8820bc2cb4694bd665cbfacbb55c262a5df5cde4ed2a1a8d0735c09c6a5e6dd8",
8
8
  "packages/sdk/codegen/src/artifacts.mjs": "aecab7eca4694cbcf2366ddd9cabc033abddad2ff8fb0960d505580dbaf4d8b7",
@@ -15,7 +15,7 @@
15
15
  "packages/sdk/codegen/src/model/schema.mjs": "29cb1167413280907ad324d8a1fcb54dc70cce829df00c1a755f43040d01cf3b",
16
16
  "packages/sdk/codegen/src/shared.mjs": "a411e7ea4fab43b5b08b00294c746ff7063ceddbae66ece8d8c5990f131e0f7c",
17
17
  "packages/sdk/codegen/src/targets.mjs": "62c5ae3e3813e07b37e62487d973bac60ef6bea558a01f3ace2e0c6e8374e147",
18
- "packages/sdk/langs/node/package.json": "4fe97943068bf593dfc284e35857a2c491e33de0939ca7fd973a0cd51d9fa5e3",
18
+ "packages/sdk/langs/node/package.json": "5f5a2add1f0216a966bc703cb7cc347c0de4aff99393ff40bbf3959249d610e1",
19
19
  "packages/sdk/langs/node/scripts/embed-prompts.mjs": "1b1641bf89cad962a4ff11d93f301fe5a4e32fe856cd3c9586106d8aaab4ad80",
20
20
  "packages/sdk/langs/node/scripts/embed-tools.mjs": "4cd8b0605b088421ea1fdf8e4e19c70f571f4e3352309a1480491600122e7bb3",
21
21
  "packages/sdk/langs/node/scripts/embed-version.mjs": "95e2ff3a7c15cb38130e12f04c8a96c901a5e55d9960610ebefa37178fd19cde",
@@ -186,7 +186,7 @@
186
186
  },
187
187
  "packages/sdk/langs/node/src/runtime/sdk-version.generated.ts": {
188
188
  "id": "node-sdk-version",
189
- "sha256": "34ca6483ff670a0a901a765131951adf69689fae23f4cbaf653a15cb1956157e",
189
+ "sha256": "3e4cfd75c0787ca1ae4b19e72747a37554bff046b14191a1ea36c4f26618dd29",
190
190
  "consumers": [
191
191
  "node"
192
192
  ]