@superdoc-dev/sdk 1.19.2 → 1.20.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/dist/action-primitives/doc-index.cjs +215 -0
  2. package/dist/action-primitives/doc-index.d.ts +83 -0
  3. package/dist/action-primitives/doc-index.d.ts.map +1 -0
  4. package/dist/action-primitives/doc-index.js +211 -0
  5. package/dist/action-primitives/engine.cjs +204 -0
  6. package/dist/action-primitives/engine.d.ts +71 -0
  7. package/dist/action-primitives/engine.d.ts.map +1 -0
  8. package/dist/action-primitives/engine.js +196 -0
  9. package/dist/action-primitives/receipt.cjs +39 -0
  10. package/dist/action-primitives/receipt.d.ts +49 -0
  11. package/dist/action-primitives/receipt.d.ts.map +1 -0
  12. package/dist/action-primitives/receipt.js +32 -0
  13. package/dist/action-primitives/resolve.cjs +252 -0
  14. package/dist/action-primitives/resolve.d.ts +56 -0
  15. package/dist/action-primitives/resolve.d.ts.map +1 -0
  16. package/dist/action-primitives/resolve.js +246 -0
  17. package/dist/action-primitives/session-cache.cjs +43 -0
  18. package/dist/action-primitives/session-cache.d.ts +19 -0
  19. package/dist/action-primitives/session-cache.d.ts.map +1 -0
  20. package/dist/action-primitives/session-cache.js +37 -0
  21. package/dist/action-primitives/tools/list-transform.cjs +661 -0
  22. package/dist/action-primitives/tools/list-transform.d.ts +98 -0
  23. package/dist/action-primitives/tools/list-transform.d.ts.map +1 -0
  24. package/dist/action-primitives/tools/list-transform.js +656 -0
  25. package/dist/action-primitives/tools/structure-insert.cjs +1343 -0
  26. package/dist/action-primitives/tools/structure-insert.d.ts +183 -0
  27. package/dist/action-primitives/tools/structure-insert.d.ts.map +1 -0
  28. package/dist/action-primitives/tools/structure-insert.js +1338 -0
  29. package/dist/action-primitives/tools/text-transform.cjs +669 -0
  30. package/dist/action-primitives/tools/text-transform.d.ts +64 -0
  31. package/dist/action-primitives/tools/text-transform.d.ts.map +1 -0
  32. package/dist/action-primitives/tools/text-transform.js +664 -0
  33. package/dist/action-primitives/types.d.ts +36 -0
  34. package/dist/action-primitives/types.d.ts.map +1 -0
  35. package/dist/action-primitives/types.js +15 -0
  36. package/dist/agent/actions.cjs +5381 -0
  37. package/dist/agent/actions.d.ts +404 -0
  38. package/dist/agent/actions.d.ts.map +1 -0
  39. package/dist/agent/actions.js +5373 -0
  40. package/dist/agent/catalog.cjs +483 -0
  41. package/dist/agent/catalog.d.ts +103 -0
  42. package/dist/agent/catalog.d.ts.map +1 -0
  43. package/dist/agent/catalog.js +471 -0
  44. package/dist/agent/doc-snapshot.cjs +663 -0
  45. package/dist/agent/doc-snapshot.d.ts +247 -0
  46. package/dist/agent/doc-snapshot.d.ts.map +1 -0
  47. package/dist/agent/doc-snapshot.js +657 -0
  48. package/dist/agent/index.d.ts +16 -0
  49. package/dist/agent/index.d.ts.map +1 -0
  50. package/dist/agent/index.js +15 -0
  51. package/dist/agent/ir.cjs +170 -0
  52. package/dist/agent/ir.d.ts +216 -0
  53. package/dist/agent/ir.d.ts.map +1 -0
  54. package/dist/agent/ir.js +181 -0
  55. package/dist/agent/operation-catalog.cjs +418 -0
  56. package/dist/agent/operation-catalog.d.ts +36 -0
  57. package/dist/agent/operation-catalog.d.ts.map +1 -0
  58. package/dist/agent/operation-catalog.js +446 -0
  59. package/dist/agent/runtime.cjs +501 -0
  60. package/dist/agent/runtime.d.ts +120 -0
  61. package/dist/agent/runtime.d.ts.map +1 -0
  62. package/dist/agent/runtime.js +493 -0
  63. package/dist/embedded-prompts.generated.cjs +13 -0
  64. package/dist/embedded-prompts.generated.d.ts +4 -0
  65. package/dist/embedded-prompts.generated.d.ts.map +1 -0
  66. package/dist/embedded-prompts.generated.js +9 -0
  67. package/dist/generated/client.cjs +18 -0
  68. package/dist/generated/client.d.ts +134 -0
  69. package/dist/generated/client.d.ts.map +1 -1
  70. package/dist/generated/client.js +18 -0
  71. package/dist/generated/contract.cjs +622 -3
  72. package/dist/generated/contract.d.ts.map +1 -1
  73. package/dist/generated/contract.js +622 -3
  74. package/dist/index.cjs +3 -7
  75. package/dist/index.d.ts +8 -6
  76. package/dist/index.d.ts.map +1 -1
  77. package/dist/index.js +2 -8
  78. package/dist/presets/core.cjs +454 -0
  79. package/dist/presets/core.d.ts +20 -0
  80. package/dist/presets/core.d.ts.map +1 -0
  81. package/dist/presets/core.js +447 -0
  82. package/dist/presets.cjs +55 -8
  83. package/dist/presets.d.ts +39 -8
  84. package/dist/presets.d.ts.map +1 -1
  85. package/dist/presets.js +53 -8
  86. package/dist/prompts/mcp-prompt.md +23 -0
  87. package/dist/prompts/system-prompt.md +108 -0
  88. package/dist/runtime/transport-common.cjs +8 -0
  89. package/dist/runtime/transport-common.d.ts.map +1 -1
  90. package/dist/runtime/transport-common.js +8 -0
  91. package/dist/tools.cjs +46 -11
  92. package/dist/tools.d.ts +55 -8
  93. package/dist/tools.d.ts.map +1 -1
  94. package/dist/tools.js +45 -13
  95. package/package.json +9 -8
  96. package/tools/__pycache__/__init__.cpython-312.pyc +0 -0
  97. package/tools/__pycache__/intent_dispatch_generated.cpython-312.pyc +0 -0
  98. package/tools/catalog.json +14 -0
  99. package/tools/tools-policy.json +1 -1
  100. package/tools/tools.anthropic.json +14 -0
  101. package/tools/tools.generic.json +14 -0
  102. package/tools/tools.openai.json +14 -0
  103. package/tools/tools.vercel.json +14 -0
@@ -0,0 +1,447 @@
1
+ /**
2
+ * `core` preset — actions-only LLM-facing surface.
3
+ *
4
+ * Two advertised tools: superdoc_inspect (read-only deterministic snapshots)
5
+ * and superdoc_perform_action (named, statically validated, deterministic edit
6
+ * verbs — the action registry in agent/actions.ts).
7
+ *
8
+ * superdoc_execute_code (model-authored JS against a synchronous in-host doc)
9
+ * is WIP: dispatchable for SDK callers, NOT advertised and NOT in the served
10
+ * system prompt until it ships behind a safety flag. The agent_apply /
11
+ * agent_verify / agent_operation tools are likewise dispatchable but never
12
+ * advertised.
13
+ *
14
+ * @internal
15
+ */
16
+ import { readFile } from 'node:fs/promises';
17
+ import path from 'node:path';
18
+ import { fileURLToPath } from 'node:url';
19
+ import { SuperDocCliError } from '../runtime/errors.js';
20
+ import { AGENT_TOOL_DEFINITIONS, isAgentToolName, listAgentTools, validateExclusions, PUBLIC_AGENT_TOOL_NAMES, } from '../agent/catalog.js';
21
+ import { agentApply, agentInspect, agentOperation, agentVerify } from '../agent/runtime.js';
22
+ import { superdocPerformAction } from '../agent/actions.js';
23
+ import { EMBEDDED_PROMPTS } from '../embedded-prompts.generated.js';
24
+ // Prompts ship as <dist>/prompts/*.md. Resolve relative to the compiled
25
+ // module, but the module's depth differs by build: in the SDK package this
26
+ // file is dist/presets/core.js (so prompts are at ../prompts), while the
27
+ // bundled CLI inlines it into dist/index.js (so prompts are at ./prompts).
28
+ // Try both so getSystemPrompt works through the SDK AND the CLI host (the
29
+ // path the Python SDK proxies through).
30
+ const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
31
+ const PROMPT_DIR_CANDIDATES = [path.resolve(MODULE_DIR, '..', 'prompts'), path.resolve(MODULE_DIR, 'prompts')];
32
+ let _actionsOnlyPromptCache = null;
33
+ let _mcpPromptCache = null;
34
+ /** Exported for unit tests; production callers use the default candidates. */
35
+ export async function readPromptFile(fileName, label, candidateDirs = PROMPT_DIR_CANDIDATES) {
36
+ const tried = [];
37
+ let lastIoError;
38
+ for (const dir of candidateDirs) {
39
+ const promptPath = path.join(dir, fileName);
40
+ tried.push(promptPath);
41
+ try {
42
+ return await readFile(promptPath, 'utf8');
43
+ }
44
+ catch (err) {
45
+ const ioError = err;
46
+ if (ioError?.code !== 'ENOENT') {
47
+ // Permission/IO failures are not "asset missing" — remember the real
48
+ // cause, but still try the other layout candidate.
49
+ lastIoError = ioError;
50
+ }
51
+ }
52
+ }
53
+ // Filesystem candidates exhausted. Native binaries (bun --compile) resolve
54
+ // MODULE_DIR inside bun's virtual filesystem where the .md assets don't
55
+ // exist — fall back to the compiled-in copies (identical content by
56
+ // construction; a drift test enforces it).
57
+ const embedded = EMBEDDED_PROMPTS[fileName];
58
+ if (typeof embedded === 'string') {
59
+ return embedded;
60
+ }
61
+ if (lastIoError) {
62
+ throw new SuperDocCliError(`${label} could not be read: ${lastIoError.message}`, {
63
+ code: 'TOOLS_ASSET_UNREADABLE',
64
+ details: { triedPaths: tried, cause: `${lastIoError.code ?? lastIoError.name}: ${lastIoError.message}` },
65
+ });
66
+ }
67
+ throw new SuperDocCliError(`${label} not found.`, {
68
+ code: 'TOOLS_ASSET_NOT_FOUND',
69
+ details: { triedPaths: tried },
70
+ });
71
+ }
72
+ function isRecord(value) {
73
+ return typeof value === 'object' && value != null && !Array.isArray(value);
74
+ }
75
+ function isObviouslyCorruptedToolArgKey(key) {
76
+ const trimmed = key.trim();
77
+ return trimmed.length === 0 || !/[\p{L}\p{N}]/u.test(trimmed);
78
+ }
79
+ function stripCorruptedToolArgKeys(value) {
80
+ if (Array.isArray(value)) {
81
+ return value.map((item) => stripCorruptedToolArgKeys(item));
82
+ }
83
+ if (!isRecord(value))
84
+ return value;
85
+ const clean = {};
86
+ for (const [key, entryValue] of Object.entries(value)) {
87
+ if (isObviouslyCorruptedToolArgKey(key))
88
+ continue;
89
+ clean[key] = stripCorruptedToolArgKeys(entryValue);
90
+ }
91
+ return clean;
92
+ }
93
+ function validateAgentToolArgs(toolName, args) {
94
+ const definition = AGENT_TOOL_DEFINITIONS.find((entry) => entry.name === toolName);
95
+ if (definition == null)
96
+ return;
97
+ const schema = definition.inputSchema;
98
+ const properties = isRecord(schema.properties) ? schema.properties : {};
99
+ const required = Array.isArray(schema.required) ? schema.required : [];
100
+ const knownKeys = new Set(Object.keys(properties));
101
+ const unknownKeys = Object.keys(args).filter((key) => !knownKeys.has(key));
102
+ if (unknownKeys.length > 0) {
103
+ throw new SuperDocCliError(`Unknown argument(s) for ${toolName}: ${unknownKeys.join(', ')}`, {
104
+ code: 'INVALID_ARGUMENT',
105
+ details: { toolName, unknownKeys, knownKeys: [...knownKeys] },
106
+ });
107
+ }
108
+ const missingKeys = required.filter((key) => args[key] == null);
109
+ if (missingKeys.length > 0) {
110
+ throw new SuperDocCliError(`Missing required argument(s) for ${toolName}: ${missingKeys.join(', ')}`, {
111
+ code: 'INVALID_ARGUMENT',
112
+ details: { toolName, missingKeys },
113
+ });
114
+ }
115
+ }
116
+ // ---------------------------------------------------------------------------
117
+ // Compaction helpers — keep agent receipts tight in the tool-result payload
118
+ // ---------------------------------------------------------------------------
119
+ /**
120
+ * Max entries for per-item receipt lists (executedOperations, selectedTargets).
121
+ * Receipts live in the conversation and are re-billed as prompt tokens on every
122
+ * subsequent turn; unbounded lists (one op per formatted range/paragraph) made
123
+ * single receipts cost thousands of tokens. 8 keeps enough shape to audit while
124
+ * the accompanying *Count field preserves the true total.
125
+ */
126
+ const RECEIPT_LIST_CAP = 8;
127
+ function compactCountMap(counts) {
128
+ const compact = {};
129
+ for (const [key, value] of Object.entries(counts)) {
130
+ if (typeof value === 'number' && value !== 0)
131
+ compact[key] = value;
132
+ }
133
+ if (Object.keys(compact).length === 0 && typeof counts.blocks === 'number') {
134
+ compact.blocks = counts.blocks;
135
+ }
136
+ return compact;
137
+ }
138
+ function pickScalarFields(value, keys, limit = 6) {
139
+ if (!isRecord(value))
140
+ return {};
141
+ const entries = keys
142
+ ? keys.filter((key) => key in value).map((key) => [key, value[key]])
143
+ : Object.entries(value);
144
+ const compact = {};
145
+ for (const [key, entryValue] of entries) {
146
+ if (typeof entryValue === 'string' ||
147
+ typeof entryValue === 'number' ||
148
+ typeof entryValue === 'boolean' ||
149
+ entryValue == null) {
150
+ compact[key] = entryValue ?? null;
151
+ }
152
+ if (Object.keys(compact).length >= limit)
153
+ break;
154
+ }
155
+ return compact;
156
+ }
157
+ function compactOperationResult(result) {
158
+ if (result == null || typeof result === 'string' || typeof result === 'number' || typeof result === 'boolean') {
159
+ return result;
160
+ }
161
+ if (Array.isArray(result))
162
+ return { itemCount: result.length };
163
+ if (!isRecord(result))
164
+ return { kind: typeof result };
165
+ const compact = {
166
+ ...pickScalarFields(result, ['success', 'status', 'count', 'total', 'applied', 'created', 'deleted']),
167
+ };
168
+ if (isRecord(result.revision)) {
169
+ const revision = pickScalarFields(result.revision, ['before', 'after', 'current']);
170
+ if (Object.keys(revision).length > 0)
171
+ compact.revision = revision;
172
+ }
173
+ if (Array.isArray(result.steps)) {
174
+ compact.stepCount = result.steps.length;
175
+ compact.steps = result.steps
176
+ .slice(0, 4)
177
+ .map((step) => (isRecord(step) ? pickScalarFields(step, ['stepId', 'op', 'effect', 'matchCount'], 4) : {}));
178
+ }
179
+ if (Array.isArray(result.items))
180
+ compact.itemCount = result.items.length;
181
+ if (Array.isArray(result.changes))
182
+ compact.changeCount = result.changes.length;
183
+ if (Array.isArray(result.matches))
184
+ compact.matchCount = result.matches.length;
185
+ if (Object.keys(compact).length === 0)
186
+ return { kind: 'object' };
187
+ return compact;
188
+ }
189
+ function compactAgentReceipt(receipt) {
190
+ // Compact the known heavy fields; pass every action-specific evidence field
191
+ // (editsApplied, marker, placement, revertHint, recovery, …) through
192
+ // verbatim — truth-telling receipts are only useful if the model sees them.
193
+ const { status, intent, preSnapshot, postSnapshot, selectedTargets, executedOperations, verification, saveReopen, errors, ...evidence } = receipt;
194
+ return {
195
+ status,
196
+ intent,
197
+ ...(preSnapshot
198
+ ? {
199
+ preSnapshot: {
200
+ revision: preSnapshot.revision,
201
+ ...(preSnapshot.counts ? { counts: compactCountMap(preSnapshot.counts) } : {}),
202
+ },
203
+ }
204
+ : {}),
205
+ ...(postSnapshot
206
+ ? {
207
+ postSnapshot: {
208
+ revision: postSnapshot.revision,
209
+ ...(postSnapshot.counts ? { counts: compactCountMap(postSnapshot.counts) } : {}),
210
+ },
211
+ }
212
+ : {}),
213
+ ...(selectedTargets
214
+ ? {
215
+ // Cap per-item lists: batch actions (add_comments selectors[],
216
+ // whole-body formatting) can select hundreds of targets, and the
217
+ // receipt is re-sent in conversation history on EVERY later turn.
218
+ // The count carries the evidence; the head carries the shape.
219
+ selectedTargets: selectedTargets.slice(0, RECEIPT_LIST_CAP).map((target) => ({
220
+ selector: target.selector,
221
+ matchedCount: target.matched.length,
222
+ })),
223
+ ...(selectedTargets.length > RECEIPT_LIST_CAP ? { selectedTargetCount: selectedTargets.length } : {}),
224
+ }
225
+ : {}),
226
+ ...(executedOperations
227
+ ? {
228
+ // Same cap: per-range/per-item actions (format_text on every
229
+ // occurrence, whole-body set_font_family) execute one op per item —
230
+ // an uncapped list dominates the token cost of the receipt.
231
+ executedOperations: executedOperations.slice(0, RECEIPT_LIST_CAP).map((operation) => ({
232
+ operationId: operation.operationId,
233
+ ...(operation.rationale ? { rationale: operation.rationale } : {}),
234
+ ...(operation.result !== undefined ? { result: compactOperationResult(operation.result) } : {}),
235
+ })),
236
+ ...(executedOperations.length > RECEIPT_LIST_CAP
237
+ ? { executedOperationCount: executedOperations.length }
238
+ : {}),
239
+ }
240
+ : {}),
241
+ ...(verification
242
+ ? {
243
+ verificationPassed: verification.every((entry) => entry.passed),
244
+ verification: verification.map((entry) => ({
245
+ check: pickScalarFields(entry.check, undefined, 6),
246
+ passed: entry.passed,
247
+ ...(entry.detail ? { detail: entry.detail } : {}),
248
+ })),
249
+ }
250
+ : {}),
251
+ ...(saveReopen ? { saveReopen } : {}),
252
+ ...(errors ? { errors } : {}),
253
+ ...evidence,
254
+ };
255
+ }
256
+ // ---------------------------------------------------------------------------
257
+ // superdoc_execute_code — in-host dispatch for the CLI/SDK-only `doc.executeCode` op
258
+ // ---------------------------------------------------------------------------
259
+ const EXECUTE_CODE_SPEC = {
260
+ operationId: 'doc.executeCode',
261
+ commandTokens: ['execute', 'code'],
262
+ params: [
263
+ { name: 'sessionId', kind: 'flag', flag: 'session', type: 'string' },
264
+ { name: 'code', kind: 'flag', flag: 'code', type: 'string' },
265
+ ],
266
+ };
267
+ function asRawOperationCapable(documentHandle) {
268
+ const candidate = documentHandle;
269
+ if (typeof candidate.invokeRawOperation !== 'function') {
270
+ throw new SuperDocCliError('superdoc_execute_code requires a session-bound document handle from client.open(); the provided handle cannot dispatch CLI/SDK-only operations.', {
271
+ code: 'TOOL_DISPATCH_NOT_FOUND',
272
+ details: { toolName: 'superdoc_execute_code' },
273
+ });
274
+ }
275
+ return candidate;
276
+ }
277
+ async function dispatchExecuteCode(documentHandle, args, invokeOptions) {
278
+ const code = typeof args.code === 'string' ? args.code : '';
279
+ // The generated bound client exposes doc.executeCode directly — use it.
280
+ // The invokeRawOperation path predates that binding and no handle produced
281
+ // by client.open() implements it; it remains only for callers that inject
282
+ // their own raw-capable handle.
283
+ const direct = documentHandle.executeCode;
284
+ if (typeof direct === 'function') {
285
+ return direct.call(documentHandle, { code }, invokeOptions);
286
+ }
287
+ const runner = asRawOperationCapable(documentHandle);
288
+ return runner.invokeRawOperation(EXECUTE_CODE_SPEC, { code }, invokeOptions);
289
+ }
290
+ // ---------------------------------------------------------------------------
291
+ // Dispatcher
292
+ // ---------------------------------------------------------------------------
293
+ async function dispatchAgentTool(documentHandle, toolName, args, invokeOptions) {
294
+ switch (toolName) {
295
+ case 'superdoc_inspect':
296
+ return agentInspect(documentHandle, args);
297
+ case 'agent_apply':
298
+ return compactAgentReceipt(await agentApply(documentHandle, args));
299
+ case 'agent_verify':
300
+ return compactAgentReceipt(await agentVerify(documentHandle, args));
301
+ case 'agent_operation':
302
+ return agentOperation(documentHandle, args);
303
+ case 'superdoc_perform_action':
304
+ return compactAgentReceipt(await superdocPerformAction(documentHandle, args));
305
+ case 'superdoc_execute_code':
306
+ return dispatchExecuteCode(documentHandle, args, invokeOptions);
307
+ }
308
+ throw new SuperDocCliError(`Unknown agent tool: ${toolName}`, {
309
+ code: 'TOOL_DISPATCH_NOT_FOUND',
310
+ details: { toolName },
311
+ });
312
+ }
313
+ // ---------------------------------------------------------------------------
314
+ // Provider cache markers — mirrors the legacy preset
315
+ // ---------------------------------------------------------------------------
316
+ function applyCacheMarkers(tools, provider, cacheRequested) {
317
+ if (!cacheRequested)
318
+ return { tools, cacheStrategy: 'disabled' };
319
+ if (provider === 'anthropic') {
320
+ if (tools.length === 0)
321
+ return { tools, cacheStrategy: 'explicit' };
322
+ const next = tools.slice(0, -1);
323
+ const last = {
324
+ ...tools[tools.length - 1],
325
+ cache_control: { type: 'ephemeral' },
326
+ };
327
+ next.push(last);
328
+ return { tools: next, cacheStrategy: 'explicit' };
329
+ }
330
+ if (provider === 'openai')
331
+ return { tools, cacheStrategy: 'automatic' };
332
+ return { tools, cacheStrategy: 'unsupported' };
333
+ }
334
+ // ---------------------------------------------------------------------------
335
+ // PresetDescriptor surface
336
+ // ---------------------------------------------------------------------------
337
+ async function coreGetTools(provider, options) {
338
+ const tools = listAgentTools(provider, { excludeActions: options?.excludeActions });
339
+ return applyCacheMarkers(tools, provider, options?.cache === true);
340
+ }
341
+ async function coreGetCatalog() {
342
+ // Synthetic catalog — describes the advertised LLM-facing tools.
343
+ const publicSet = new Set(PUBLIC_AGENT_TOOL_NAMES);
344
+ const tools = AGENT_TOOL_DEFINITIONS.filter((d) => publicSet.has(d.name)).map((d) => ({
345
+ toolName: d.name,
346
+ description: d.description,
347
+ inputSchema: d.inputSchema,
348
+ mutates: d.name !== 'superdoc_inspect',
349
+ operations: [],
350
+ }));
351
+ return {
352
+ contractVersion: 'core/v2',
353
+ generatedAt: null,
354
+ toolCount: tools.length,
355
+ tools,
356
+ };
357
+ }
358
+ /**
359
+ * Drop the per-action documentation lines for excluded actions. Entries render
360
+ * as single "- name: ..." lines (the drift-guard test enforces the format), so
361
+ * line-level filtering is deterministic. Prose cross-references elsewhere in
362
+ * the prompt are left alone — a mention costs a few tokens; a full per-action
363
+ * manual for an uncallable action teaches the model to call it.
364
+ */
365
+ function stripExcludedActionLines(prompt, excludedActions) {
366
+ if (excludedActions.size === 0)
367
+ return prompt;
368
+ return prompt
369
+ .split('\n')
370
+ .filter((line) => {
371
+ const match = /^- ([a-z_]+)(?: \/ ([a-z_]+))?:/.exec(line);
372
+ if (!match)
373
+ return true;
374
+ const names = [match[1], match[2]].filter((n) => Boolean(n));
375
+ // Drop the line only when EVERY action it documents is excluded (the
376
+ // paired accept/reject line survives if one side remains callable).
377
+ return !names.every((name) => excludedActions.has(name));
378
+ })
379
+ .join('\n');
380
+ }
381
+ async function coreGetSystemPrompt(options) {
382
+ const { excludedActions } = validateExclusions({ excludeActions: options?.excludeActions });
383
+ // The core preset is ACTIONS-ONLY: superdoc_execute_code is WIP and not
384
+ // advertised, and the prompt documents only the action surface (this exact
385
+ // prompt is what the eval suite validates). Code-execution guidance will be
386
+ // added when the safety-flag work ships that tool.
387
+ if (_actionsOnlyPromptCache == null) {
388
+ _actionsOnlyPromptCache = await readPromptFile('system-prompt.md', 'Core system prompt');
389
+ }
390
+ return stripExcludedActionLines(_actionsOnlyPromptCache, excludedActions);
391
+ }
392
+ async function coreGetMcpPrompt() {
393
+ if (_mcpPromptCache == null) {
394
+ _mcpPromptCache = await readPromptFile('mcp-prompt.md', 'MCP system prompt');
395
+ }
396
+ return _mcpPromptCache;
397
+ }
398
+ async function coreDispatch(documentHandle, toolName, args, invokeOptions) {
399
+ if (!isRecord(args)) {
400
+ throw new SuperDocCliError(`Tool arguments for ${toolName} must be an object.`, {
401
+ code: 'INVALID_ARGUMENT',
402
+ details: { toolName },
403
+ });
404
+ }
405
+ const sanitizedArgs = stripCorruptedToolArgKeys(args);
406
+ if (!isRecord(sanitizedArgs)) {
407
+ throw new SuperDocCliError(`Tool arguments for ${toolName} must be an object.`, {
408
+ code: 'INVALID_ARGUMENT',
409
+ details: { toolName },
410
+ });
411
+ }
412
+ // Defense-in-depth for exclusion configs: a host that narrowed the advertised
413
+ // surface (getTools excludeActions) can pass the same list here so a
414
+ // guessed/injected call to an excluded action is refused, not run.
415
+ const exclusionOptions = invokeOptions;
416
+ if (toolName === 'superdoc_perform_action' &&
417
+ typeof sanitizedArgs.action === 'string' &&
418
+ exclusionOptions?.excludeActions?.includes(sanitizedArgs.action)) {
419
+ throw new SuperDocCliError(`Action ${sanitizedArgs.action} is excluded by configuration.`, {
420
+ code: 'INVALID_ARGUMENT',
421
+ details: { toolName, action: sanitizedArgs.action, excluded: true },
422
+ });
423
+ }
424
+ let forwardOptions = invokeOptions;
425
+ if (exclusionOptions && 'excludeActions' in exclusionOptions) {
426
+ const { excludeActions: _ea, ...rest } = exclusionOptions;
427
+ forwardOptions = rest;
428
+ }
429
+ if (isAgentToolName(toolName)) {
430
+ validateAgentToolArgs(toolName, sanitizedArgs);
431
+ return dispatchAgentTool(documentHandle, toolName, sanitizedArgs, forwardOptions);
432
+ }
433
+ throw new SuperDocCliError(`Unknown tool: ${toolName}`, {
434
+ code: 'TOOL_DISPATCH_NOT_FOUND',
435
+ details: { toolName, preset: 'core' },
436
+ });
437
+ }
438
+ export const corePreset = {
439
+ id: 'core',
440
+ description: 'Actions-only LLM surface: superdoc_inspect (reads) and superdoc_perform_action (named, deterministic edit verbs). superdoc_execute_code exists but is WIP — dispatchable, not advertised.',
441
+ supportsCacheControl: true,
442
+ getTools: coreGetTools,
443
+ getCatalog: coreGetCatalog,
444
+ getSystemPrompt: coreGetSystemPrompt,
445
+ getMcpPrompt: coreGetMcpPrompt,
446
+ dispatch: coreDispatch,
447
+ };
package/dist/presets.cjs CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  var errors = require('./runtime/errors.cjs');
4
4
  var legacy = require('./presets/legacy.cjs');
5
+ var core = require('./presets/core.cjs');
5
6
 
6
7
  /**
7
8
  * Preset registry for SuperDoc LLM tools.
@@ -13,10 +14,10 @@ var legacy = require('./presets/legacy.cjs');
13
14
  *
14
15
  * const { tools, meta } = await chooseTools({ provider: 'vercel', preset: 'legacy' });
15
16
  *
16
- * v1 ships a single preset: `'legacy'` a thin wrapper around today's
17
- * codegen-emitted intent tools. When callers omit `preset`, `legacy` is used.
18
- * The default may move once a replacement preset reaches parity; bumping it is
19
- * a coordinated change in this file alone.
17
+ * Two presets ship built-in: `'legacy'` (codegen-emitted intent tools; the
18
+ * default when callers omit `preset`) and `'core'` (the actions-only LLM
19
+ * surface). The default may move once core fully replaces legacy; bumping it
20
+ * is a coordinated change in this file alone.
20
21
  *
21
22
  * Presets are NOT versioned. The preset id encodes the variant; a new shape
22
23
  * ships as a new id, not a new version of an existing one.
@@ -27,18 +28,62 @@ var legacy = require('./presets/legacy.cjs');
27
28
  // Registry
28
29
  // ---------------------------------------------------------------------------
29
30
  /**
30
- * The default preset returned when callers omit `preset`. Set to `'legacy'`
31
- * so consumers built before presets existed (today's intent-tool path) keep
32
- * working without changes.
31
+ * The default preset returned when callers omit `preset`. Stays as `'legacy'`
32
+ * for backward compatibility — consumers built before presets existed (today's
33
+ * intent-tool path) keep working without changes. To exercise the `core`
34
+ * preset, callers pass `preset: 'core'` explicitly.
33
35
  */
34
36
  const DEFAULT_PRESET = 'legacy';
35
- const PRESETS = {
37
+ const BUILTIN_PRESETS = {
36
38
  legacy: legacy.legacyPreset,
39
+ core: core.corePreset,
37
40
  };
41
+ /**
42
+ * Mutable registry, seeded with the built-ins. Customers add their own presets
43
+ * via {@link registerPreset} (e.g. one produced by `extendPreset` /
44
+ * `composePreset`) so `getPreset`/`chooseTools` can resolve them by id.
45
+ */
46
+ const PRESETS = { ...BUILTIN_PRESETS };
38
47
  /** List the IDs of all registered presets. */
39
48
  function listPresets() {
40
49
  return Object.keys(PRESETS);
41
50
  }
51
+ /**
52
+ * Register one or more presets so they resolve by id through `getPreset` and
53
+ * the `chooseTools`/`dispatchSuperDocTool` plumbing. Re-registering a custom id
54
+ * replaces the previously registered preset for that id (tests / hot-reload
55
+ * rely on this). Built-in ids (legacy / core) cannot
56
+ * be overwritten.
57
+ */
58
+ function registerPreset(...presets) {
59
+ for (const preset of presets) {
60
+ if (preset == null || typeof preset.id !== 'string' || preset.id.length === 0) {
61
+ throw new errors.SuperDocCliError('registerPreset requires a preset with a non-empty string id.', {
62
+ code: 'INVALID_ARGUMENT',
63
+ });
64
+ }
65
+ if (preset.id in BUILTIN_PRESETS) {
66
+ throw new errors.SuperDocCliError(`Cannot overwrite built-in preset "${preset.id}".`, {
67
+ code: 'INVALID_ARGUMENT',
68
+ details: { id: preset.id },
69
+ });
70
+ }
71
+ PRESETS[preset.id] = preset;
72
+ }
73
+ }
74
+ /**
75
+ * Unregister a customer-registered preset. Idempotent (no-op if absent).
76
+ * Rejects unregistering a built-in preset id.
77
+ */
78
+ function unregisterPreset(id) {
79
+ if (id in BUILTIN_PRESETS) {
80
+ throw new errors.SuperDocCliError(`Cannot unregister built-in preset "${id}".`, {
81
+ code: 'INVALID_ARGUMENT',
82
+ details: { id },
83
+ });
84
+ }
85
+ delete PRESETS[id];
86
+ }
42
87
  /**
43
88
  * Resolve a preset by ID. Throws {@link SuperDocCliError} with code
44
89
  * `PRESET_NOT_FOUND` if the ID is not registered. Omit the argument to
@@ -58,3 +103,5 @@ function getPreset(id = DEFAULT_PRESET) {
58
103
  exports.DEFAULT_PRESET = DEFAULT_PRESET;
59
104
  exports.getPreset = getPreset;
60
105
  exports.listPresets = listPresets;
106
+ exports.registerPreset = registerPreset;
107
+ exports.unregisterPreset = unregisterPreset;
package/dist/presets.d.ts CHANGED
@@ -8,10 +8,10 @@
8
8
  *
9
9
  * const { tools, meta } = await chooseTools({ provider: 'vercel', preset: 'legacy' });
10
10
  *
11
- * v1 ships a single preset: `'legacy'` a thin wrapper around today's
12
- * codegen-emitted intent tools. When callers omit `preset`, `legacy` is used.
13
- * The default may move once a replacement preset reaches parity; bumping it is
14
- * a coordinated change in this file alone.
11
+ * Two presets ship built-in: `'legacy'` (codegen-emitted intent tools; the
12
+ * default when callers omit `preset`) and `'core'` (the actions-only LLM
13
+ * surface). The default may move once core fully replaces legacy; bumping it
14
+ * is a coordinated change in this file alone.
15
15
  *
16
16
  * Presets are NOT versioned. The preset id encodes the variant; a new shape
17
17
  * ships as a new id, not a new version of an existing one.
@@ -80,6 +80,23 @@ export interface GetToolsOptions {
80
80
  * for example). When omitted or `false`, no markers are added.
81
81
  */
82
82
  cache?: boolean;
83
+ /**
84
+ * Action names to REMOVE from the advertised action surface (the `core`
85
+ * preset's `superdoc_perform_action` enum/description/args shrink together).
86
+ * Unknown names throw. Presets without an action surface (e.g. `legacy`)
87
+ * ignore this option.
88
+ */
89
+ excludeActions?: readonly string[];
90
+ }
91
+ /**
92
+ * Options for {@link PresetDescriptor.getSystemPrompt}. Mirrors the exclusion
93
+ * options on getTools so the prompt and the advertised tool surface can be
94
+ * narrowed TOGETHER — a prompt that documents an uncallable action teaches the
95
+ * model to call it.
96
+ */
97
+ export interface GetSystemPromptOptions {
98
+ /** Drop the per-action documentation lines for these actions (core preset). */
99
+ excludeActions?: readonly string[];
83
100
  }
84
101
  export interface GetToolsResult {
85
102
  tools: unknown[];
@@ -113,7 +130,7 @@ export interface PresetDescriptor {
113
130
  /** Full tool catalog with metadata (contract version, tool count, etc.). */
114
131
  getCatalog(): Promise<ToolCatalog>;
115
132
  /** System prompt for embedded LLM usage (OpenAI/Anthropic/Vercel APIs). */
116
- getSystemPrompt(): Promise<string>;
133
+ getSystemPrompt(options?: GetSystemPromptOptions): Promise<string>;
117
134
  /** System prompt for MCP server `instructions`. */
118
135
  getMcpPrompt(): Promise<string>;
119
136
  /**
@@ -125,13 +142,27 @@ export interface PresetDescriptor {
125
142
  dispatch(documentHandle: BoundDocApi, toolName: string, args: Record<string, unknown>, invokeOptions?: InvokeOptions): Promise<unknown>;
126
143
  }
127
144
  /**
128
- * The default preset returned when callers omit `preset`. Set to `'legacy'`
129
- * so consumers built before presets existed (today's intent-tool path) keep
130
- * working without changes.
145
+ * The default preset returned when callers omit `preset`. Stays as `'legacy'`
146
+ * for backward compatibility — consumers built before presets existed (today's
147
+ * intent-tool path) keep working without changes. To exercise the `core`
148
+ * preset, callers pass `preset: 'core'` explicitly.
131
149
  */
132
150
  export declare const DEFAULT_PRESET = "legacy";
133
151
  /** List the IDs of all registered presets. */
134
152
  export declare function listPresets(): readonly string[];
153
+ /**
154
+ * Register one or more presets so they resolve by id through `getPreset` and
155
+ * the `chooseTools`/`dispatchSuperDocTool` plumbing. Re-registering a custom id
156
+ * replaces the previously registered preset for that id (tests / hot-reload
157
+ * rely on this). Built-in ids (legacy / core) cannot
158
+ * be overwritten.
159
+ */
160
+ export declare function registerPreset(...presets: PresetDescriptor[]): void;
161
+ /**
162
+ * Unregister a customer-registered preset. Idempotent (no-op if absent).
163
+ * Rejects unregistering a built-in preset id.
164
+ */
165
+ export declare function unregisterPreset(id: string): void;
135
166
  /**
136
167
  * Resolve a preset by ID. Throws {@link SuperDocCliError} with code
137
168
  * `PRESET_NOT_FOUND` if the ID is not registered. Omit the argument to
@@ -1 +1 @@
1
- {"version":3,"file":"presets.d.ts","sourceRoot":"","sources":["../src/presets.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAI1D;;;;;;;GAOG;AACH,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,WAAW,GAAG,QAAQ,GAAG,SAAS,CAAC;AAEzE;;;;;;;GAOG;AACH,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,WAAW,GAAG,aAAa,GAAG,UAAU,CAAC;AAElF;;;;;GAKG;AACH,MAAM,MAAM,oBAAoB,GAAG;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC;CAC5B,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,oBAAoB,EAAE,CAAC;CACpC,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,gBAAgB,EAAE,CAAC;CAC3B,CAAC;AAEF,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,aAAa,EAAE,aAAa,CAAC;CAC9B;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,gBAAgB;IAC/B,yEAAyE;IACzE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAEpB,2DAA2D;IAC3D,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAE7B;;;;OAIG;IACH,QAAQ,CAAC,oBAAoB,EAAE,OAAO,CAAC;IAEvC,0DAA0D;IAC1D,QAAQ,CAAC,QAAQ,EAAE,YAAY,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IAErF,4EAA4E;IAC5E,UAAU,IAAI,OAAO,CAAC,WAAW,CAAC,CAAC;IAEnC,2EAA2E;IAC3E,eAAe,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAEnC,mDAAmD;IACnD,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAEhC;;;;;OAKG;IACH,QAAQ,CACN,cAAc,EAAE,WAAW,EAC3B,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,aAAa,CAAC,EAAE,aAAa,GAC5B,OAAO,CAAC,OAAO,CAAC,CAAC;CACrB;AAMD;;;;GAIG;AACH,eAAO,MAAM,cAAc,WAAW,CAAC;AAMvC,8CAA8C;AAC9C,wBAAgB,WAAW,IAAI,SAAS,MAAM,EAAE,CAE/C;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,EAAE,GAAE,MAAuB,GAAG,gBAAgB,CASvE"}
1
+ {"version":3,"file":"presets.d.ts","sourceRoot":"","sources":["../src/presets.ts"],"names":[],"mappings":"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"}