@superdoc-dev/sdk 1.14.0 → 1.15.0-next.2
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/index.cjs +4 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/presets/legacy.cjs +297 -0
- package/dist/presets/legacy.d.ts +18 -0
- package/dist/presets/legacy.d.ts.map +1 -0
- package/dist/presets/legacy.js +291 -0
- package/dist/presets.cjs +60 -0
- package/dist/presets.d.ts +141 -0
- package/dist/presets.d.ts.map +1 -0
- package/dist/presets.js +53 -0
- package/dist/tools.cjs +61 -291
- package/dist/tools.d.ts +51 -43
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +59 -290
- package/package.json +6 -6
- package/tools/__pycache__/__init__.cpython-312.pyc +0 -0
- package/tools/__pycache__/intent_dispatch_generated.cpython-312.pyc +0 -0
package/dist/presets.cjs
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var errors = require('./runtime/errors.cjs');
|
|
4
|
+
var legacy = require('./presets/legacy.cjs');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Preset registry for SuperDoc LLM tools.
|
|
8
|
+
*
|
|
9
|
+
* A preset is a self-contained collection of LLM tools — provider catalogs
|
|
10
|
+
* (openai / anthropic / vercel / generic), a system prompt, and a dispatcher.
|
|
11
|
+
* Multiple presets can coexist in the SDK; consumers select one at runtime via
|
|
12
|
+
* `chooseTools({ preset })`.
|
|
13
|
+
*
|
|
14
|
+
* const { tools, meta } = await chooseTools({ provider: 'vercel', preset: 'legacy' });
|
|
15
|
+
*
|
|
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.
|
|
20
|
+
*
|
|
21
|
+
* Presets are NOT versioned. The preset id encodes the variant; a new shape
|
|
22
|
+
* ships as a new id, not a new version of an existing one.
|
|
23
|
+
*
|
|
24
|
+
* @internal
|
|
25
|
+
*/
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// Registry
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
/**
|
|
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.
|
|
33
|
+
*/
|
|
34
|
+
const DEFAULT_PRESET = 'legacy';
|
|
35
|
+
const PRESETS = {
|
|
36
|
+
legacy: legacy.legacyPreset,
|
|
37
|
+
};
|
|
38
|
+
/** List the IDs of all registered presets. */
|
|
39
|
+
function listPresets() {
|
|
40
|
+
return Object.keys(PRESETS);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Resolve a preset by ID. Throws {@link SuperDocCliError} with code
|
|
44
|
+
* `PRESET_NOT_FOUND` if the ID is not registered. Omit the argument to
|
|
45
|
+
* get the default preset.
|
|
46
|
+
*/
|
|
47
|
+
function getPreset(id = DEFAULT_PRESET) {
|
|
48
|
+
const preset = PRESETS[id];
|
|
49
|
+
if (preset == null) {
|
|
50
|
+
throw new errors.SuperDocCliError(`Unknown LLM-tools preset: "${id}"`, {
|
|
51
|
+
code: 'PRESET_NOT_FOUND',
|
|
52
|
+
details: { id, availablePresets: Object.keys(PRESETS) },
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return preset;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
exports.DEFAULT_PRESET = DEFAULT_PRESET;
|
|
59
|
+
exports.getPreset = getPreset;
|
|
60
|
+
exports.listPresets = listPresets;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preset registry for SuperDoc LLM tools.
|
|
3
|
+
*
|
|
4
|
+
* A preset is a self-contained collection of LLM tools — provider catalogs
|
|
5
|
+
* (openai / anthropic / vercel / generic), a system prompt, and a dispatcher.
|
|
6
|
+
* Multiple presets can coexist in the SDK; consumers select one at runtime via
|
|
7
|
+
* `chooseTools({ preset })`.
|
|
8
|
+
*
|
|
9
|
+
* const { tools, meta } = await chooseTools({ provider: 'vercel', preset: 'legacy' });
|
|
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.
|
|
15
|
+
*
|
|
16
|
+
* Presets are NOT versioned. The preset id encodes the variant; a new shape
|
|
17
|
+
* ships as a new id, not a new version of an existing one.
|
|
18
|
+
*
|
|
19
|
+
* @internal
|
|
20
|
+
*/
|
|
21
|
+
import type { BoundDocApi } from './generated/client.js';
|
|
22
|
+
import type { InvokeOptions } from './runtime/process.js';
|
|
23
|
+
/**
|
|
24
|
+
* Wire format the tools are emitted in.
|
|
25
|
+
*
|
|
26
|
+
* - `openai` — OpenAI Chat Completions / Responses
|
|
27
|
+
* - `anthropic` — Anthropic Messages API
|
|
28
|
+
* - `vercel` — Vercel AI SDK (provider-agnostic adapter)
|
|
29
|
+
* - `generic` — vendor-neutral JSON Schema shape
|
|
30
|
+
*/
|
|
31
|
+
export type ToolProvider = 'openai' | 'anthropic' | 'vercel' | 'generic';
|
|
32
|
+
/**
|
|
33
|
+
* Prompt-cache strategy returned by `chooseTools.meta.cacheStrategy`.
|
|
34
|
+
*
|
|
35
|
+
* - `explicit` — preset emitted provider-specific cache markers (Anthropic `cache_control`)
|
|
36
|
+
* - `automatic` — provider caches automatically (OpenAI ≥ 1024 prompt tokens)
|
|
37
|
+
* - `unsupported` — pass-through; caching depends on the underlying model (vercel/generic)
|
|
38
|
+
* - `disabled` — caller passed `cache: false` or omitted the flag
|
|
39
|
+
*/
|
|
40
|
+
export type CacheStrategy = 'explicit' | 'automatic' | 'unsupported' | 'disabled';
|
|
41
|
+
/**
|
|
42
|
+
* One operation row in a {@link ToolCatalogEntry}. Each catalog entry can
|
|
43
|
+
* dispatch to one or more operations (e.g. multi-action intent tools), so
|
|
44
|
+
* the catalog records the operation id and the action discriminator that
|
|
45
|
+
* routes to it.
|
|
46
|
+
*/
|
|
47
|
+
export type ToolCatalogOperation = {
|
|
48
|
+
operationId: string;
|
|
49
|
+
intentAction: string;
|
|
50
|
+
required?: string[];
|
|
51
|
+
requiredOneOf?: string[][];
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* One entry in the {@link ToolCatalog}. Matches the shape of the catalog
|
|
55
|
+
* emitted by the legacy preset's codegen — kept stable as the public
|
|
56
|
+
* catalog row shape so TypeScript consumers can introspect `tools[i]`
|
|
57
|
+
* without losing property typing.
|
|
58
|
+
*/
|
|
59
|
+
export type ToolCatalogEntry = {
|
|
60
|
+
toolName: string;
|
|
61
|
+
description: string;
|
|
62
|
+
inputSchema: Record<string, unknown>;
|
|
63
|
+
mutates: boolean;
|
|
64
|
+
operations: ToolCatalogOperation[];
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Full tool catalog shape. The legacy preset returns the existing codegen
|
|
68
|
+
* catalog with `contractVersion`, `generatedAt`, `toolCount`, `tools`.
|
|
69
|
+
*/
|
|
70
|
+
export type ToolCatalog = {
|
|
71
|
+
contractVersion: string;
|
|
72
|
+
generatedAt: string | null;
|
|
73
|
+
toolCount: number;
|
|
74
|
+
tools: ToolCatalogEntry[];
|
|
75
|
+
};
|
|
76
|
+
export interface GetToolsOptions {
|
|
77
|
+
/**
|
|
78
|
+
* When `true`, the preset applies provider-specific prompt-cache markers
|
|
79
|
+
* (Anthropic `cache_control: { type: "ephemeral" }` on the last tool,
|
|
80
|
+
* for example). When omitted or `false`, no markers are added.
|
|
81
|
+
*/
|
|
82
|
+
cache?: boolean;
|
|
83
|
+
}
|
|
84
|
+
export interface GetToolsResult {
|
|
85
|
+
tools: unknown[];
|
|
86
|
+
cacheStrategy: CacheStrategy;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Self-contained preset of LLM tools.
|
|
90
|
+
*
|
|
91
|
+
* Each preset owns:
|
|
92
|
+
* - its tool catalogs per provider format
|
|
93
|
+
* - its system prompt (and MCP-flavored variant)
|
|
94
|
+
* - its dispatcher (how a named tool call routes against a doc handle)
|
|
95
|
+
*
|
|
96
|
+
* Presets are stateless; the same descriptor handles every call.
|
|
97
|
+
*
|
|
98
|
+
* @internal
|
|
99
|
+
*/
|
|
100
|
+
export interface PresetDescriptor {
|
|
101
|
+
/** Stable identifier — used as the preset's only "version" reference. */
|
|
102
|
+
readonly id: string;
|
|
103
|
+
/** Human-readable description shown by `listPresets()`. */
|
|
104
|
+
readonly description: string;
|
|
105
|
+
/**
|
|
106
|
+
* Whether this preset's provider adapters emit Anthropic prompt-cache
|
|
107
|
+
* markers when called with `cache: true`. Informational; per-provider
|
|
108
|
+
* behavior is reported via `GetToolsResult.cacheStrategy`.
|
|
109
|
+
*/
|
|
110
|
+
readonly supportsCacheControl: boolean;
|
|
111
|
+
/** Tool definitions for the requested provider format. */
|
|
112
|
+
getTools(provider: ToolProvider, options?: GetToolsOptions): Promise<GetToolsResult>;
|
|
113
|
+
/** Full tool catalog with metadata (contract version, tool count, etc.). */
|
|
114
|
+
getCatalog(): Promise<ToolCatalog>;
|
|
115
|
+
/** System prompt for embedded LLM usage (OpenAI/Anthropic/Vercel APIs). */
|
|
116
|
+
getSystemPrompt(): Promise<string>;
|
|
117
|
+
/** System prompt for MCP server `instructions`. */
|
|
118
|
+
getMcpPrompt(): Promise<string>;
|
|
119
|
+
/**
|
|
120
|
+
* Dispatch a tool call against a bound document handle.
|
|
121
|
+
*
|
|
122
|
+
* The handle injects session targeting; `args` must NOT carry `doc` or
|
|
123
|
+
* `sessionId`. Returns whatever the underlying operation produces.
|
|
124
|
+
*/
|
|
125
|
+
dispatch(documentHandle: BoundDocApi, toolName: string, args: Record<string, unknown>, invokeOptions?: InvokeOptions): Promise<unknown>;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
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.
|
|
131
|
+
*/
|
|
132
|
+
export declare const DEFAULT_PRESET = "legacy";
|
|
133
|
+
/** List the IDs of all registered presets. */
|
|
134
|
+
export declare function listPresets(): readonly string[];
|
|
135
|
+
/**
|
|
136
|
+
* Resolve a preset by ID. Throws {@link SuperDocCliError} with code
|
|
137
|
+
* `PRESET_NOT_FOUND` if the ID is not registered. Omit the argument to
|
|
138
|
+
* get the default preset.
|
|
139
|
+
*/
|
|
140
|
+
export declare function getPreset(id?: string): PresetDescriptor;
|
|
141
|
+
//# sourceMappingURL=presets.d.ts.map
|
|
@@ -0,0 +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"}
|
package/dist/presets.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preset registry for SuperDoc LLM tools.
|
|
3
|
+
*
|
|
4
|
+
* A preset is a self-contained collection of LLM tools — provider catalogs
|
|
5
|
+
* (openai / anthropic / vercel / generic), a system prompt, and a dispatcher.
|
|
6
|
+
* Multiple presets can coexist in the SDK; consumers select one at runtime via
|
|
7
|
+
* `chooseTools({ preset })`.
|
|
8
|
+
*
|
|
9
|
+
* const { tools, meta } = await chooseTools({ provider: 'vercel', preset: 'legacy' });
|
|
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.
|
|
15
|
+
*
|
|
16
|
+
* Presets are NOT versioned. The preset id encodes the variant; a new shape
|
|
17
|
+
* ships as a new id, not a new version of an existing one.
|
|
18
|
+
*
|
|
19
|
+
* @internal
|
|
20
|
+
*/
|
|
21
|
+
import { SuperDocCliError } from './runtime/errors.js';
|
|
22
|
+
import { legacyPreset } from './presets/legacy.js';
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Registry
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
/**
|
|
27
|
+
* The default preset returned when callers omit `preset`. Set to `'legacy'`
|
|
28
|
+
* so consumers built before presets existed (today's intent-tool path) keep
|
|
29
|
+
* working without changes.
|
|
30
|
+
*/
|
|
31
|
+
export const DEFAULT_PRESET = 'legacy';
|
|
32
|
+
const PRESETS = {
|
|
33
|
+
legacy: legacyPreset,
|
|
34
|
+
};
|
|
35
|
+
/** List the IDs of all registered presets. */
|
|
36
|
+
export function listPresets() {
|
|
37
|
+
return Object.keys(PRESETS);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Resolve a preset by ID. Throws {@link SuperDocCliError} with code
|
|
41
|
+
* `PRESET_NOT_FOUND` if the ID is not registered. Omit the argument to
|
|
42
|
+
* get the default preset.
|
|
43
|
+
*/
|
|
44
|
+
export function getPreset(id = DEFAULT_PRESET) {
|
|
45
|
+
const preset = PRESETS[id];
|
|
46
|
+
if (preset == null) {
|
|
47
|
+
throw new SuperDocCliError(`Unknown LLM-tools preset: "${id}"`, {
|
|
48
|
+
code: 'PRESET_NOT_FOUND',
|
|
49
|
+
details: { id, availablePresets: Object.keys(PRESETS) },
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return preset;
|
|
53
|
+
}
|
package/dist/tools.cjs
CHANGED
|
@@ -1,334 +1,101 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
var path = require('node:path');
|
|
5
|
-
var node_url = require('node:url');
|
|
6
|
-
var errors = require('./runtime/errors.cjs');
|
|
7
|
-
var intentDispatch_generated = require('./generated/intent-dispatch.generated.cjs');
|
|
3
|
+
var presets = require('./presets.cjs');
|
|
8
4
|
|
|
9
|
-
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
10
|
-
// Resolve tools directory relative to package root (works from both src/ and dist/)
|
|
11
|
-
const toolsDir = path.resolve(path.dirname(node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('tools.cjs', document.baseURI).href)))), '..', 'tools');
|
|
12
|
-
const providerFileByName = {
|
|
13
|
-
openai: 'tools.openai.json',
|
|
14
|
-
anthropic: 'tools.anthropic.json',
|
|
15
|
-
vercel: 'tools.vercel.json',
|
|
16
|
-
generic: 'tools.generic.json',
|
|
17
|
-
};
|
|
18
|
-
const STRIP_EMPTY_OPTIONAL_ARGS = new Set(['parentId', 'parentCommentId', 'id', 'status']);
|
|
19
|
-
function isRecord(value) {
|
|
20
|
-
return typeof value === 'object' && value != null && !Array.isArray(value);
|
|
21
|
-
}
|
|
22
|
-
function isObviouslyCorruptedToolArgKey(key) {
|
|
23
|
-
const trimmed = key.trim();
|
|
24
|
-
return trimmed.length === 0 || !/[\p{L}\p{N}]/u.test(trimmed);
|
|
25
|
-
}
|
|
26
|
-
function stripCorruptedToolArgKeys(value) {
|
|
27
|
-
if (Array.isArray(value)) {
|
|
28
|
-
return value.map((item) => stripCorruptedToolArgKeys(item));
|
|
29
|
-
}
|
|
30
|
-
if (!isRecord(value))
|
|
31
|
-
return value;
|
|
32
|
-
const clean = {};
|
|
33
|
-
for (const [key, entryValue] of Object.entries(value)) {
|
|
34
|
-
if (isObviouslyCorruptedToolArgKey(key))
|
|
35
|
-
continue;
|
|
36
|
-
clean[key] = stripCorruptedToolArgKeys(entryValue);
|
|
37
|
-
}
|
|
38
|
-
return clean;
|
|
39
|
-
}
|
|
40
|
-
async function readJson(fileName) {
|
|
41
|
-
const filePath = path.join(toolsDir, fileName);
|
|
42
|
-
let raw = '';
|
|
43
|
-
try {
|
|
44
|
-
raw = await promises.readFile(filePath, 'utf8');
|
|
45
|
-
}
|
|
46
|
-
catch (error) {
|
|
47
|
-
throw new errors.SuperDocCliError('Unable to load packaged tool artifact.', {
|
|
48
|
-
code: 'TOOLS_ASSET_NOT_FOUND',
|
|
49
|
-
details: {
|
|
50
|
-
filePath,
|
|
51
|
-
message: error instanceof Error ? error.message : String(error),
|
|
52
|
-
},
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
try {
|
|
56
|
-
return JSON.parse(raw);
|
|
57
|
-
}
|
|
58
|
-
catch (error) {
|
|
59
|
-
throw new errors.SuperDocCliError('Packaged tool artifact is invalid JSON.', {
|
|
60
|
-
code: 'TOOLS_ASSET_INVALID',
|
|
61
|
-
details: {
|
|
62
|
-
filePath,
|
|
63
|
-
message: error instanceof Error ? error.message : String(error),
|
|
64
|
-
},
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
async function loadProviderBundle(provider) {
|
|
69
|
-
return readJson(providerFileByName[provider]);
|
|
70
|
-
}
|
|
71
|
-
async function loadCatalog() {
|
|
72
|
-
return readJson('catalog.json');
|
|
73
|
-
}
|
|
74
|
-
async function getToolCatalog() {
|
|
75
|
-
return getCachedCatalog();
|
|
76
|
-
}
|
|
77
|
-
async function listTools(provider) {
|
|
78
|
-
const bundle = await loadProviderBundle(provider);
|
|
79
|
-
const tools = bundle.tools;
|
|
80
|
-
if (!Array.isArray(tools)) {
|
|
81
|
-
throw new errors.SuperDocCliError('Tool provider bundle is missing tools array.', {
|
|
82
|
-
code: 'TOOLS_ASSET_INVALID',
|
|
83
|
-
details: { provider },
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
return tools;
|
|
87
|
-
}
|
|
88
5
|
/**
|
|
89
|
-
*
|
|
6
|
+
* Public LLM-tools API. Thin layer over the preset registry — every call here
|
|
7
|
+
* resolves a preset (defaulting to `legacy` for backwards compat) and delegates
|
|
8
|
+
* to it.
|
|
90
9
|
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
10
|
+
* Presets are the unit of swapping. To add a new tool surface (e.g. handwritten
|
|
11
|
+
* "core" tools, prompt-caching variant, lazy-load experiment), register a new
|
|
12
|
+
* descriptor in `presets.ts` — no changes here required.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Select tools for a specific provider from a preset.
|
|
94
16
|
*
|
|
95
17
|
* @example
|
|
96
18
|
* ```ts
|
|
19
|
+
* // Default — legacy preset, no cache markers.
|
|
20
|
+
* const { tools, meta } = await chooseTools({ provider: 'vercel' });
|
|
21
|
+
*
|
|
97
22
|
* // Anthropic — last tool gets cache_control automatically.
|
|
98
23
|
* const { tools, meta } = await chooseTools({ provider: 'anthropic', cache: true });
|
|
99
24
|
*
|
|
100
|
-
* //
|
|
101
|
-
* const { tools } = await chooseTools({ provider: 'openai',
|
|
25
|
+
* // Pick a specific preset by ID.
|
|
26
|
+
* const { tools, meta } = await chooseTools({ provider: 'openai', preset: 'legacy' });
|
|
102
27
|
* ```
|
|
103
28
|
*/
|
|
104
29
|
async function chooseTools(input) {
|
|
105
|
-
const
|
|
106
|
-
const
|
|
107
|
-
const
|
|
108
|
-
|
|
30
|
+
const presetId = input.preset ?? presets.DEFAULT_PRESET;
|
|
31
|
+
const preset = presets.getPreset(presetId);
|
|
32
|
+
const { tools, cacheStrategy } = await preset.getTools(input.provider, {
|
|
33
|
+
cache: input.cache === true,
|
|
34
|
+
});
|
|
109
35
|
return {
|
|
110
36
|
tools,
|
|
111
37
|
meta: {
|
|
112
38
|
provider: input.provider,
|
|
39
|
+
preset: presetId,
|
|
113
40
|
toolCount: tools.length,
|
|
114
41
|
cacheStrategy,
|
|
115
42
|
},
|
|
116
43
|
};
|
|
117
44
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
if (!cacheRequested) {
|
|
125
|
-
return { tools, cacheStrategy: 'disabled' };
|
|
126
|
-
}
|
|
127
|
-
if (provider === 'anthropic') {
|
|
128
|
-
if (tools.length === 0)
|
|
129
|
-
return { tools, cacheStrategy: 'explicit' };
|
|
130
|
-
// Anthropic: marking the LAST tool with cache_control caches the entire
|
|
131
|
-
// tools block (and everything before it in the request — system prompt
|
|
132
|
-
// first if it also has cache_control). Shallow-spread the last entry so we
|
|
133
|
-
// don't mutate the cached bundle in place.
|
|
134
|
-
const next = tools.slice(0, -1);
|
|
135
|
-
const last = {
|
|
136
|
-
...tools[tools.length - 1],
|
|
137
|
-
cache_control: { type: 'ephemeral' },
|
|
138
|
-
};
|
|
139
|
-
next.push(last);
|
|
140
|
-
return { tools: next, cacheStrategy: 'explicit' };
|
|
141
|
-
}
|
|
142
|
-
if (provider === 'openai') {
|
|
143
|
-
// OpenAI caches prompts ≥ 1024 tokens automatically. No marker needed,
|
|
144
|
-
// but we still report cacheStrategy:'automatic' so callers can branch on
|
|
145
|
-
// it (e.g. for measurement).
|
|
146
|
-
return { tools, cacheStrategy: 'automatic' };
|
|
147
|
-
}
|
|
148
|
-
// vercel / generic — depends on underlying model.
|
|
149
|
-
return { tools, cacheStrategy: 'unsupported' };
|
|
150
|
-
}
|
|
151
|
-
function resolveDocApiMethod(documentHandle, operationId) {
|
|
152
|
-
const tokens = operationId.split('.').slice(1);
|
|
153
|
-
let cursor = documentHandle;
|
|
154
|
-
for (const token of tokens) {
|
|
155
|
-
if (!isRecord(cursor) || !(token in cursor)) {
|
|
156
|
-
throw new errors.SuperDocCliError(`No SDK doc method found for operation ${operationId}.`, {
|
|
157
|
-
code: 'TOOL_DISPATCH_NOT_FOUND',
|
|
158
|
-
details: { operationId, token },
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
cursor = cursor[token];
|
|
162
|
-
}
|
|
163
|
-
if (typeof cursor !== 'function') {
|
|
164
|
-
throw new errors.SuperDocCliError(`Resolved member for ${operationId} is not callable.`, {
|
|
165
|
-
code: 'TOOL_DISPATCH_NOT_FOUND',
|
|
166
|
-
details: { operationId },
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
return cursor;
|
|
170
|
-
}
|
|
171
|
-
// Cached catalog instance — loaded once per process.
|
|
172
|
-
let _catalogCache = null;
|
|
173
|
-
async function getCachedCatalog() {
|
|
174
|
-
if (_catalogCache == null) {
|
|
175
|
-
_catalogCache = await loadCatalog();
|
|
176
|
-
}
|
|
177
|
-
return _catalogCache;
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// Catalog + listings (preset-scoped; default to legacy)
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
/** Return the full tool catalog for a preset (default: legacy). */
|
|
49
|
+
async function getToolCatalog(preset) {
|
|
50
|
+
return presets.getPreset(preset ?? presets.DEFAULT_PRESET).getCatalog();
|
|
178
51
|
}
|
|
179
52
|
/**
|
|
180
|
-
*
|
|
53
|
+
* Return the raw tool array for a provider from a preset (default: legacy).
|
|
181
54
|
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
* 2. All universally-required keys present (merged schema `required`)
|
|
185
|
-
* 3. All action-specific required keys present (per-operation `required`)
|
|
55
|
+
* No cache markers are applied. Use {@link chooseTools} when you need cache
|
|
56
|
+
* markers and metadata.
|
|
186
57
|
*/
|
|
187
|
-
function
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
const required = Array.isArray(schema.required) ? schema.required : [];
|
|
191
|
-
// 1. Reject unknown keys
|
|
192
|
-
const knownKeys = new Set(Object.keys(properties));
|
|
193
|
-
const unknownKeys = Object.keys(args).filter((k) => !knownKeys.has(k));
|
|
194
|
-
if (unknownKeys.length > 0) {
|
|
195
|
-
throw new errors.SuperDocCliError(`Unknown argument(s) for ${toolName}: ${unknownKeys.join(', ')}`, {
|
|
196
|
-
code: 'INVALID_ARGUMENT',
|
|
197
|
-
details: { toolName, unknownKeys, knownKeys: [...knownKeys] },
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
// 2. Reject missing universally-required keys
|
|
201
|
-
const missingKeys = required.filter((k) => args[k] == null);
|
|
202
|
-
if (missingKeys.length > 0) {
|
|
203
|
-
throw new errors.SuperDocCliError(`Missing required argument(s) for ${toolName}: ${missingKeys.join(', ')}`, {
|
|
204
|
-
code: 'INVALID_ARGUMENT',
|
|
205
|
-
details: { toolName, missingKeys },
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
// 3. Reject missing per-operation required keys.
|
|
209
|
-
// For multi-action tools, resolve the operation by action; for single-op
|
|
210
|
-
// tools, use the sole operation entry.
|
|
211
|
-
const action = args.action;
|
|
212
|
-
let op;
|
|
213
|
-
if (typeof action === 'string' && tool.operations.length > 1) {
|
|
214
|
-
op = tool.operations.find((o) => o.intentAction === action);
|
|
215
|
-
}
|
|
216
|
-
else if (tool.operations.length === 1) {
|
|
217
|
-
op = tool.operations[0];
|
|
218
|
-
}
|
|
219
|
-
if (op) {
|
|
220
|
-
validateOperationRequired(toolName, action, args, op);
|
|
221
|
-
}
|
|
58
|
+
async function listTools(provider, preset) {
|
|
59
|
+
const { tools } = await presets.getPreset(preset ?? presets.DEFAULT_PRESET).getTools(provider, { cache: false });
|
|
60
|
+
return tools;
|
|
222
61
|
}
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
// Dispatch
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
223
65
|
/**
|
|
224
|
-
*
|
|
66
|
+
* Dispatch a tool call against a bound document handle using the default
|
|
67
|
+
* preset (`legacy`).
|
|
225
68
|
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
* - `requiredOneOf: string[][]` — at least one branch must be fully satisfied
|
|
229
|
-
* (mirrors JSON Schema `oneOf` with per-branch `required` arrays)
|
|
230
|
-
*/
|
|
231
|
-
function validateOperationRequired(toolName, action, args, op) {
|
|
232
|
-
const actionLabel = typeof action === 'string' ? ` action "${action}"` : '';
|
|
233
|
-
if (op.requiredOneOf && op.requiredOneOf.length > 0) {
|
|
234
|
-
const satisfied = op.requiredOneOf.some((branch) => branch.every((k) => args[k] != null));
|
|
235
|
-
if (!satisfied) {
|
|
236
|
-
const options = op.requiredOneOf.map((b) => b.join(' + ')).join(' | ');
|
|
237
|
-
throw new errors.SuperDocCliError(`Missing required argument(s) for ${toolName}${actionLabel}: must provide one of: ${options}`, {
|
|
238
|
-
code: 'INVALID_ARGUMENT',
|
|
239
|
-
details: { toolName, action, requiredOneOf: op.requiredOneOf },
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
else if (op.required && op.required.length > 0) {
|
|
244
|
-
const missingActionKeys = op.required.filter((k) => args[k] == null);
|
|
245
|
-
if (missingActionKeys.length > 0) {
|
|
246
|
-
throw new errors.SuperDocCliError(`Missing required argument(s) for ${toolName}${actionLabel}: ${missingActionKeys.join(', ')}`, {
|
|
247
|
-
code: 'INVALID_ARGUMENT',
|
|
248
|
-
details: { toolName, action, missingKeys: missingActionKeys },
|
|
249
|
-
});
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
/**
|
|
254
|
-
* Dispatch a tool call against a bound document handle.
|
|
69
|
+
* The document handle injects session targeting automatically; tool arguments
|
|
70
|
+
* should not contain `doc` or `sessionId`.
|
|
255
71
|
*
|
|
256
|
-
*
|
|
257
|
-
*
|
|
72
|
+
* For preset-aware dispatch — e.g. when comparing two presets — call
|
|
73
|
+
* `getPreset('id').dispatch(...)` directly.
|
|
258
74
|
*/
|
|
259
75
|
async function dispatchSuperDocTool(documentHandle, toolName, args = {}, invokeOptions) {
|
|
260
|
-
|
|
261
|
-
throw new errors.SuperDocCliError(`Tool arguments for ${toolName} must be an object.`, {
|
|
262
|
-
code: 'INVALID_ARGUMENT',
|
|
263
|
-
details: { toolName },
|
|
264
|
-
});
|
|
265
|
-
}
|
|
266
|
-
const sanitizedArgs = stripCorruptedToolArgKeys(args);
|
|
267
|
-
if (!isRecord(sanitizedArgs)) {
|
|
268
|
-
throw new errors.SuperDocCliError(`Tool arguments for ${toolName} must be an object.`, {
|
|
269
|
-
code: 'INVALID_ARGUMENT',
|
|
270
|
-
details: { toolName },
|
|
271
|
-
});
|
|
272
|
-
}
|
|
273
|
-
// Validate against the tool schema before dispatch.
|
|
274
|
-
const catalog = await getCachedCatalog();
|
|
275
|
-
const tool = catalog.tools.find((t) => t.toolName === toolName);
|
|
276
|
-
if (tool == null) {
|
|
277
|
-
throw new errors.SuperDocCliError(`Unknown tool: ${toolName}`, {
|
|
278
|
-
code: 'TOOL_DISPATCH_NOT_FOUND',
|
|
279
|
-
details: { toolName },
|
|
280
|
-
});
|
|
281
|
-
}
|
|
282
|
-
validateToolArgs(toolName, sanitizedArgs, tool);
|
|
283
|
-
// Strip empty strings for known optional ID/enum params that LLMs fill with ""
|
|
284
|
-
// instead of omitting. Only target params where "" is never a valid value.
|
|
285
|
-
const cleanArgs = {};
|
|
286
|
-
for (const [key, value] of Object.entries(sanitizedArgs)) {
|
|
287
|
-
if (value === '' && STRIP_EMPTY_OPTIONAL_ARGS.has(key))
|
|
288
|
-
continue;
|
|
289
|
-
cleanArgs[key] = value;
|
|
290
|
-
}
|
|
291
|
-
return intentDispatch_generated.dispatchIntentTool(toolName, cleanArgs, (operationId, input) => {
|
|
292
|
-
const method = resolveDocApiMethod(documentHandle, operationId);
|
|
293
|
-
return method(input, invokeOptions);
|
|
294
|
-
});
|
|
76
|
+
return presets.getPreset(presets.DEFAULT_PRESET).dispatch(documentHandle, toolName, args, invokeOptions);
|
|
295
77
|
}
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
// System prompts (preset-scoped; default to legacy)
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
296
81
|
/**
|
|
297
|
-
* Read the
|
|
82
|
+
* Read the packaged SDK system prompt (default preset: legacy).
|
|
298
83
|
*
|
|
299
|
-
*
|
|
300
|
-
* suitable for embedded LLM usage (OpenAI, Anthropic, Vercel APIs).
|
|
301
|
-
*
|
|
84
|
+
* Includes a persona preamble ("You are a document editing assistant…")
|
|
85
|
+
* suitable for embedded LLM usage (OpenAI, Anthropic, Vercel APIs). For MCP
|
|
86
|
+
* server instructions, use {@link getMcpPrompt} instead.
|
|
302
87
|
*/
|
|
303
|
-
async function getSystemPrompt() {
|
|
304
|
-
|
|
305
|
-
try {
|
|
306
|
-
return await promises.readFile(promptPath, 'utf8');
|
|
307
|
-
}
|
|
308
|
-
catch {
|
|
309
|
-
throw new errors.SuperDocCliError('System prompt not found.', {
|
|
310
|
-
code: 'TOOLS_ASSET_NOT_FOUND',
|
|
311
|
-
details: { filePath: promptPath },
|
|
312
|
-
});
|
|
313
|
-
}
|
|
88
|
+
async function getSystemPrompt(preset) {
|
|
89
|
+
return presets.getPreset(preset ?? presets.DEFAULT_PRESET).getSystemPrompt();
|
|
314
90
|
}
|
|
315
91
|
/**
|
|
316
|
-
* Read the
|
|
92
|
+
* Read the packaged MCP system prompt for intent tools (default preset: legacy).
|
|
317
93
|
*
|
|
318
|
-
*
|
|
319
|
-
*
|
|
94
|
+
* Omits the persona preamble and includes session lifecycle instructions
|
|
95
|
+
* (open/save/close) suitable for MCP server `instructions`.
|
|
320
96
|
*/
|
|
321
|
-
async function getMcpPrompt() {
|
|
322
|
-
|
|
323
|
-
try {
|
|
324
|
-
return await promises.readFile(promptPath, 'utf8');
|
|
325
|
-
}
|
|
326
|
-
catch {
|
|
327
|
-
throw new errors.SuperDocCliError('MCP system prompt not found.', {
|
|
328
|
-
code: 'TOOLS_ASSET_NOT_FOUND',
|
|
329
|
-
details: { filePath: promptPath },
|
|
330
|
-
});
|
|
331
|
-
}
|
|
97
|
+
async function getMcpPrompt(preset) {
|
|
98
|
+
return presets.getPreset(preset ?? presets.DEFAULT_PRESET).getMcpPrompt();
|
|
332
99
|
}
|
|
333
100
|
/**
|
|
334
101
|
* Get the system prompt formatted for a specific LLM provider, with optional
|
|
@@ -355,7 +122,7 @@ async function getMcpPrompt() {
|
|
|
355
122
|
* ```
|
|
356
123
|
*/
|
|
357
124
|
async function getSystemPromptForProvider(input) {
|
|
358
|
-
const text = await getSystemPrompt();
|
|
125
|
+
const text = await getSystemPrompt(input.preset);
|
|
359
126
|
const cacheRequested = input.cache === true;
|
|
360
127
|
if (input.provider === 'anthropic') {
|
|
361
128
|
const block = { type: 'text', text };
|
|
@@ -375,6 +142,9 @@ async function getSystemPromptForProvider(input) {
|
|
|
375
142
|
return { provider: input.provider, content: text, cacheStrategy };
|
|
376
143
|
}
|
|
377
144
|
|
|
145
|
+
exports.DEFAULT_PRESET = presets.DEFAULT_PRESET;
|
|
146
|
+
exports.getPreset = presets.getPreset;
|
|
147
|
+
exports.listPresets = presets.listPresets;
|
|
378
148
|
exports.chooseTools = chooseTools;
|
|
379
149
|
exports.dispatchSuperDocTool = dispatchSuperDocTool;
|
|
380
150
|
exports.getMcpPrompt = getMcpPrompt;
|