@dotcms/ai 26.10.20-1 → 26.10.21-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.
- package/README.md +203 -29
- package/adapter.cjs.default.js +1 -0
- package/adapter.cjs.js +14 -0
- package/adapter.cjs.mjs +2 -0
- package/adapter.cjs2.js +0 -0
- package/adapter.d.ts +1 -0
- package/adapter.esm.js +2 -0
- package/adapter.esm2.js +0 -0
- package/errors.cjs.js +192 -0
- package/errors.esm.js +180 -0
- package/format-result.cjs.js +833 -0
- package/format-result.esm.js +825 -0
- package/package.json +25 -20
- package/runtime.cjs.js +0 -0
- package/runtime.esm.js +0 -0
- package/sandbox.cjs.default.js +1 -0
- package/sandbox.cjs.js +69 -0
- package/sandbox.cjs.mjs +2 -0
- package/sandbox.d.ts +1 -0
- package/sandbox.esm.js +50 -0
- package/spec.cjs.default.js +1 -0
- package/spec.cjs.js +3 -23912
- package/spec.cjs.mjs +2 -0
- package/spec.cjs2.js +23916 -0
- package/spec.d.ts +1 -0
- package/spec.esm.js +1 -23914
- package/spec.esm2.js +23914 -0
- package/src/adapter/context.d.ts +11 -0
- package/src/adapter/request-core.d.ts +30 -0
- package/src/adapter.d.ts +1 -0
- package/src/runtime.d.ts +5 -4
- package/src/sandbox/errors.d.ts +23 -1
- package/src/sandbox/index.d.ts +1 -1
- package/src/sandbox.d.ts +1 -0
- package/src/spec.d.ts +1 -0
- package/src/tools/definitions/download-assets.d.ts +27 -0
- package/src/tools/definitions/execute.d.ts +15 -0
- package/src/tools/definitions/page-create.d.ts +23 -0
- package/src/tools/definitions/page-place-content.d.ts +32 -0
- package/src/tools/definitions/page-verify.d.ts +20 -0
- package/src/tools/definitions/search.d.ts +13 -0
- package/src/tools/definitions/upload-assets.d.ts +23 -0
- package/src/tools/index.d.ts +40 -0
- package/src/tools/operations/download-assets.d.ts +55 -0
- package/src/tools/operations/page-create.d.ts +81 -0
- package/src/tools/operations/page-place-content.d.ts +102 -0
- package/src/tools/operations/page-verify.d.ts +164 -0
- package/src/tools/operations/shared/asset-common.d.ts +28 -0
- package/src/tools/operations/shared/glob.d.ts +22 -0
- package/src/tools/operations/shared/local-root.d.ts +11 -0
- package/src/tools/operations/shared/page-common.d.ts +45 -0
- package/src/tools/operations/shared/page-path.d.ts +49 -0
- package/src/tools/operations/shared/resolve.d.ts +43 -0
- package/src/tools/operations/upload-assets.d.ts +64 -0
- package/src/tools/toolkit/connection.d.ts +56 -0
- package/src/tools/toolkit/create-tool.d.ts +66 -0
- package/src/tools/toolkit/endpoints.d.ts +52 -0
- package/src/tools/toolkit/lenient-boolean.d.ts +14 -0
- package/src/tools/toolkit/results.d.ts +32 -0
- package/src/tools/toolkit/tool-runtime.d.ts +108 -0
- package/src/tools/toolkit/types.d.ts +120 -0
- package/src/tools.d.ts +1 -0
- package/tools.cjs.default.js +1 -0
- package/tools.cjs.js +3850 -0
- package/tools.cjs.mjs +2 -0
- package/tools.d.ts +1 -0
- package/tools.esm.js +3817 -0
- package/index.cjs.default.js +0 -1
- package/index.cjs.js +0 -7
- package/index.cjs.mjs +0 -2
- package/index.d.ts +0 -1
- package/index.esm.js +0 -1
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { LayoutRow } from './shared/page-common';
|
|
2
|
+
import { type DotCMSRuntime } from '../../runtime';
|
|
3
|
+
import { type Endpoint } from '../toolkit/endpoints';
|
|
4
|
+
/**
|
|
5
|
+
* Every endpoint `verifyPage` calls — all reads. The `page_verify` tool enforces exactly this
|
|
6
|
+
* list, and `page-verify.spec.ts` checks every request against it. Add a request, add it here.
|
|
7
|
+
*/
|
|
8
|
+
export declare const PAGE_VERIFY_ENDPOINTS: readonly Endpoint[];
|
|
9
|
+
/** Render modes the verify tool supports. LIVE = published; WORKING = latest saved (pre-publish). */
|
|
10
|
+
export type VerifyMode = 'LIVE' | 'WORKING';
|
|
11
|
+
/** The default render mode when the caller does not name one. */
|
|
12
|
+
export declare const DEFAULT_MODE: VerifyMode;
|
|
13
|
+
/** The default language id when the caller does not name one. */
|
|
14
|
+
export declare const DEFAULT_LANGUAGE_ID = 1;
|
|
15
|
+
/**
|
|
16
|
+
* Per-slot verdict:
|
|
17
|
+
* - ok — the slot produced rendered HTML.
|
|
18
|
+
* - empty-vtl-error — content IS placed in the slot, but it rendered empty → the container/widget
|
|
19
|
+
* VTL failed (a #dotParse error swallowed to nothing). Fix lives in Layer 1:
|
|
20
|
+
* re-run that VTL through POST /api/vtl/dynamic to get the parse/eval error.
|
|
21
|
+
* - empty-no-content — the slot resolved but has no contentlets placed. A placement gap, not a code
|
|
22
|
+
* bug — use page_place_content to add content.
|
|
23
|
+
* - cache-stale — the slot rendered HTML, but the full page.rendered came back empty → a
|
|
24
|
+
* page-level cache problem. Set cachettl "0" and re-publish.
|
|
25
|
+
* - not-assembled — the slot rendered HTML in isolation, but distinctive evidence from that
|
|
26
|
+
* HTML is absent from the assembled page → the template/theme omitted it.
|
|
27
|
+
*/
|
|
28
|
+
export type SlotVerdict = 'ok' | 'empty-vtl-error' | 'empty-no-content' | 'cache-stale' | 'not-assembled';
|
|
29
|
+
export interface VerifySlotResult {
|
|
30
|
+
/** Container key as the layout addresses it (path for file containers, id for db containers). */
|
|
31
|
+
container: string;
|
|
32
|
+
/** Slot instance uuid. */
|
|
33
|
+
uuid: string;
|
|
34
|
+
/** Whether this slot produced non-empty rendered HTML. */
|
|
35
|
+
rendered: boolean;
|
|
36
|
+
/** Byte length of this slot's rendered HTML (0 when empty). */
|
|
37
|
+
bytes: number;
|
|
38
|
+
/** How many contentlets are placed in the slot (from the render response's contentlets map). */
|
|
39
|
+
contentCount: number;
|
|
40
|
+
/** Classification of the slot's render outcome. */
|
|
41
|
+
verdict: SlotVerdict;
|
|
42
|
+
}
|
|
43
|
+
/** The $URLMapContent resolution outcome — only present for URL-mapped (detail) pages. */
|
|
44
|
+
export interface UrlMapResult {
|
|
45
|
+
/** True when the path resolved to a concrete detail contentlet (vs. the no-match branch). */
|
|
46
|
+
resolved: boolean;
|
|
47
|
+
/** The resolved contentlet identifier, when resolved. */
|
|
48
|
+
contentletId?: string;
|
|
49
|
+
}
|
|
50
|
+
export interface VerifyPageOptions {
|
|
51
|
+
dotcms: DotCMSRuntime;
|
|
52
|
+
/** Page URL path (e.g. "/about-us" or a URL-mapped detail slug like "/blog/my-post"). */
|
|
53
|
+
path: string;
|
|
54
|
+
/**
|
|
55
|
+
* Site to render against — hostname or identifier (UUID). Optional: when omitted the instance's
|
|
56
|
+
* current/default site is used (no host_id sent). Only NON-default sites need this.
|
|
57
|
+
*/
|
|
58
|
+
site?: string;
|
|
59
|
+
/** Language id. Default 1. */
|
|
60
|
+
languageId?: number;
|
|
61
|
+
/** Render mode. Default "LIVE" (published). "WORKING" checks the latest saved, pre-publish. */
|
|
62
|
+
mode?: VerifyMode;
|
|
63
|
+
/** Include a bounded prefix of the assembled page HTML in the manifest. Default false. */
|
|
64
|
+
includeHtml?: boolean;
|
|
65
|
+
}
|
|
66
|
+
export interface VerifyHtmlResult {
|
|
67
|
+
/** Bounded prefix of the assembled page HTML. */
|
|
68
|
+
content: string;
|
|
69
|
+
/** Total JavaScript character count before truncation. */
|
|
70
|
+
totalChars: number;
|
|
71
|
+
/** Total UTF-8 byte count before truncation. */
|
|
72
|
+
totalBytes: number;
|
|
73
|
+
/** True when content is only a prefix of the assembled HTML. */
|
|
74
|
+
truncated: boolean;
|
|
75
|
+
/** Maximum number of characters returned in content. */
|
|
76
|
+
limit: number;
|
|
77
|
+
}
|
|
78
|
+
export interface VerifyPageManifest {
|
|
79
|
+
/** The path that was verified. */
|
|
80
|
+
path: string;
|
|
81
|
+
/** The page's resolved url. */
|
|
82
|
+
url: string;
|
|
83
|
+
/** The site the page rendered against (hostname when resolved, else "(default)"). */
|
|
84
|
+
site: string;
|
|
85
|
+
/** The render mode used. */
|
|
86
|
+
mode: VerifyMode;
|
|
87
|
+
/** Language id used. */
|
|
88
|
+
languageId: number;
|
|
89
|
+
/** HTTP status of the render call (200 even when the body is empty — see pageRendered). */
|
|
90
|
+
httpStatus: number;
|
|
91
|
+
/** True when page.rendered came back non-empty. A 200 with pageRendered=false is a swallowed error. */
|
|
92
|
+
pageRendered: boolean;
|
|
93
|
+
/** Byte length of the full page.rendered HTML. */
|
|
94
|
+
pageBytes: number;
|
|
95
|
+
/** Per-slot verdicts, in layout order. */
|
|
96
|
+
slots: VerifySlotResult[];
|
|
97
|
+
/** URL-map resolution for detail pages; null for a regular page. */
|
|
98
|
+
urlMap: UrlMapResult | null;
|
|
99
|
+
/** Actionable notices derived from the verdicts. */
|
|
100
|
+
warnings: string[];
|
|
101
|
+
/** One-line summary plus the next action to take. */
|
|
102
|
+
diagnosis: string;
|
|
103
|
+
/** Bounded assembled HTML, present only when includeHtml=true. */
|
|
104
|
+
html?: VerifyHtmlResult;
|
|
105
|
+
}
|
|
106
|
+
export declare const MAX_INCLUDED_HTML_CHARS = 20000;
|
|
107
|
+
/**
|
|
108
|
+
* Verify that a dotCMS page actually renders — the layer that catches a blank slot, a swallowed
|
|
109
|
+
* #dotParse error, a cache-stale page, or an unpublished edit, none of which a VTL-only check
|
|
110
|
+
* (/api/vtl/dynamic) can see because that runs in a request context, not a render context.
|
|
111
|
+
*
|
|
112
|
+
* Wraps GET /api/v1/page/render/{uri} and absorbs its sharp edges:
|
|
113
|
+
* - Host: the caller passes a hostname (or nothing); the tool resolves it to a host_id. host_id is
|
|
114
|
+
* required only for a NON-default site — omit `site` and the default site is used.
|
|
115
|
+
* - 200 != rendered: #dotParse swallows a VTL error into an empty HTTP 200, so a 200 with an empty
|
|
116
|
+
* body is a failure, surfaced as pageRendered=false, not success.
|
|
117
|
+
* - Two rendered layers that can disagree: each slot's rendered HTML (containers[].rendered[uuid])
|
|
118
|
+
* vs. the assembled page.rendered. The disagreement IS the diagnosis (cache-stale) as opposed to
|
|
119
|
+
* an empty slot (VTL error or no content).
|
|
120
|
+
*
|
|
121
|
+
* Returns a structured verdict — per-slot classification plus a one-line diagnosis with the next
|
|
122
|
+
* action — instead of two JSON blobs the caller has to compare by hand.
|
|
123
|
+
*/
|
|
124
|
+
export declare function verifyPage(options: VerifyPageOptions): Promise<VerifyPageManifest>;
|
|
125
|
+
interface RenderResponse {
|
|
126
|
+
entity?: {
|
|
127
|
+
page?: {
|
|
128
|
+
rendered?: string;
|
|
129
|
+
pageURI?: string;
|
|
130
|
+
pageUrl?: string;
|
|
131
|
+
url?: string;
|
|
132
|
+
};
|
|
133
|
+
containers?: Record<string, RenderedContainer>;
|
|
134
|
+
layout?: {
|
|
135
|
+
body?: {
|
|
136
|
+
rows?: LayoutRow[];
|
|
137
|
+
};
|
|
138
|
+
};
|
|
139
|
+
urlContentMap?: {
|
|
140
|
+
identifier?: string;
|
|
141
|
+
inode?: string;
|
|
142
|
+
} | null;
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
interface RenderedContainer {
|
|
146
|
+
/** Per-slot rendered HTML, keyed by uuid (ContainerRendered.getRendered()). */
|
|
147
|
+
rendered?: Record<string, string>;
|
|
148
|
+
/** Per-slot placed contentlets, keyed by uuid (ContainerRaw.getContentletsMap()). */
|
|
149
|
+
contentlets?: Record<string, Array<{
|
|
150
|
+
identifier?: string;
|
|
151
|
+
}>>;
|
|
152
|
+
}
|
|
153
|
+
/** Assemble the verdict manifest from a render response. Pure — no I/O, so it is unit-testable. */
|
|
154
|
+
export declare function buildManifest(input: {
|
|
155
|
+
path: string;
|
|
156
|
+
uri: string;
|
|
157
|
+
siteLabel: string;
|
|
158
|
+
mode: VerifyMode;
|
|
159
|
+
languageId: number;
|
|
160
|
+
status: number;
|
|
161
|
+
body: RenderResponse;
|
|
162
|
+
includeHtml?: boolean;
|
|
163
|
+
}): VerifyPageManifest;
|
|
164
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What `uploadAssets` and `downloadAssets` share: the entries their manifests are made of, the
|
|
3
|
+
* dotCMS path form both accept, and how a manifest is finished.
|
|
4
|
+
*/
|
|
5
|
+
export interface AssetManifestFile {
|
|
6
|
+
path: string;
|
|
7
|
+
bytes: number;
|
|
8
|
+
identifier?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface AssetManifestFailure {
|
|
11
|
+
path: string;
|
|
12
|
+
error: string;
|
|
13
|
+
}
|
|
14
|
+
export interface AssetManifestSkipped {
|
|
15
|
+
path: string;
|
|
16
|
+
reason: string;
|
|
17
|
+
}
|
|
18
|
+
export declare function normalizeDotCMSPath(input: string): {
|
|
19
|
+
siteQualified?: string;
|
|
20
|
+
path: string;
|
|
21
|
+
};
|
|
22
|
+
export declare function sumBytes(files: AssetManifestFile[]): number;
|
|
23
|
+
export declare function sortManifest<T extends {
|
|
24
|
+
files: AssetManifestFile[];
|
|
25
|
+
failures: AssetManifestFailure[];
|
|
26
|
+
skipped?: AssetManifestSkipped[];
|
|
27
|
+
notLive?: AssetManifestFile[];
|
|
28
|
+
}>(manifest: T): T;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `include` glob filter the asset operations share: `upload_assets` matches it against
|
|
3
|
+
* local paths relative to `src`, `download_assets` against dotCMS paths relative to the folder.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Split an `include` string into its comma-separated patterns — but NOT on commas inside a brace
|
|
7
|
+
* group, so `*.{png,webp,jpg}` stays one pattern while `*.vtl,*.scss` is two. Exported for tests.
|
|
8
|
+
*/
|
|
9
|
+
export declare function splitIncludePatterns(include?: string): string[];
|
|
10
|
+
/**
|
|
11
|
+
* Build a matcher over relative POSIX paths from a comma-separated `include` string. A file matches
|
|
12
|
+
* if it matches ANY pattern. No `include` → matches everything. Exported for tests.
|
|
13
|
+
*
|
|
14
|
+
* Supports the glob features callers reasonably assume from a standard glob:
|
|
15
|
+
* - `*` matches any run of chars WITHIN a path segment (does not cross `/`)
|
|
16
|
+
* - `**` matches across segments, including zero (so a leading globstar also matches a top-level file)
|
|
17
|
+
* - `?` matches a single non-`/` char
|
|
18
|
+
* - `{png,webp,jpg}` brace expansion (alternation)
|
|
19
|
+
* A pattern with no `/` matches the file's basename anywhere in the tree; a pattern with a `/` is
|
|
20
|
+
* anchored at the root of `src`.
|
|
21
|
+
*/
|
|
22
|
+
export declare function includeMatcher(include?: string): (rel: string) => boolean;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Refuse `path` when it resolves outside `root`. No `root` means no boundary — for trusted,
|
|
3
|
+
* direct callers of the operations; the tools always set one.
|
|
4
|
+
*/
|
|
5
|
+
export declare function assertInsideRoot(root: string | undefined, label: 'src' | 'dest', path: string): Promise<void>;
|
|
6
|
+
/**
|
|
7
|
+
* Refuse to write `file` when it would land outside `root`: its directory resolves elsewhere,
|
|
8
|
+
* or the file itself is a symlink — `writeFile` follows one, so a link planted inside the
|
|
9
|
+
* destination would otherwise redirect the write anywhere.
|
|
10
|
+
*/
|
|
11
|
+
export declare function assertWritableInsideRoot(root: string | undefined, file: string): Promise<void>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { DotCMSColumnContainer } from '@dotcms/types';
|
|
2
|
+
import type { DotCMSRuntime } from '../../../runtime';
|
|
3
|
+
/**
|
|
4
|
+
* Response shapes and probes shared by more than one page tool.
|
|
5
|
+
*
|
|
6
|
+
* Small on purpose: these are the pieces that were previously written out twice and had
|
|
7
|
+
* already started to drift, not a general dumping ground for page types.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* One row of a page layout, as `/api/v1/page/json` and `/api/v1/page/render` return it.
|
|
11
|
+
*
|
|
12
|
+
* Hoisted here because `page-verify` and `page-place-content` declared byte-identical
|
|
13
|
+
* copies, which meant two tools parsing the SAME response through two shapes nobody kept in
|
|
14
|
+
* step.
|
|
15
|
+
*
|
|
16
|
+
* The innermost element is DERIVED from `DotCMSColumnContainer` — the SDK's own page-asset
|
|
17
|
+
* contract — so a change to the container shape upstream fails here at compile time instead
|
|
18
|
+
* of drifting silently. `import type` erases at build, so this costs nothing at runtime.
|
|
19
|
+
*
|
|
20
|
+
* The surrounding optionality stays hand-written, and that is deliberate: the canonical
|
|
21
|
+
* `DotPageAssetLayoutRow` declares `columns` (and every field below it) as REQUIRED, which
|
|
22
|
+
* would be a lie about a response this code has not validated. Everything here is optional
|
|
23
|
+
* because the payload is unproven at compile time. `historyUUIDs` is picked off for the same
|
|
24
|
+
* reason — these tools never read it, and requiring it would assert a field that may be
|
|
25
|
+
* absent. `@dotcms/dotcms-models` also ships a `DotLayoutRow`, but it is the editor-side
|
|
26
|
+
* model (fully required, and its barrel pulls Angular into the transitive graph), so it is
|
|
27
|
+
* the wrong contract for a tool parsing a REST response.
|
|
28
|
+
*/
|
|
29
|
+
export interface LayoutRow {
|
|
30
|
+
columns?: Array<{
|
|
31
|
+
containers?: Array<Pick<DotCMSColumnContainer, 'identifier' | 'uuid'>>;
|
|
32
|
+
}>;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Whether a contentlet is published.
|
|
36
|
+
*
|
|
37
|
+
* THROWS on a failed read, and that is the contract callers depend on rather than an
|
|
38
|
+
* oversight. The two previous copies disagreed here: one swallowed every error into `false`,
|
|
39
|
+
* which conflates "this is not live" with "we could not find out" — and for the transfer
|
|
40
|
+
* manifest those are opposite conclusions, since a read failure reported as not-live sends
|
|
41
|
+
* the caller off to re-publish assets that were already fine. `upload-assets` needs the
|
|
42
|
+
* distinction, so the shared primitive is the honest one and the caller that wants a
|
|
43
|
+
* best-effort answer catches for itself.
|
|
44
|
+
*/
|
|
45
|
+
export declare function isContentLive(dotcms: DotCMSRuntime, identifier: string): Promise<boolean>;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalization for the page paths callers hand to the page tools.
|
|
3
|
+
*
|
|
4
|
+
* Shared because all three tools build a request URL by interpolating a caller-supplied path
|
|
5
|
+
* into an endpoint template — `/api/v1/page/render${uri}`, `/api/v1/page/json${uri}` — and
|
|
6
|
+
* only `page_create` was normalizing first. The two that were not had a live correctness bug
|
|
7
|
+
* and a latent security one:
|
|
8
|
+
*
|
|
9
|
+
* - Live today: `page_verify({ path: '/a/../b' })` renders `/b` while the manifest reports
|
|
10
|
+
* `/a/../b`, and a `#` silently truncates the path. The tool's whole job is to report on
|
|
11
|
+
* the page it actually checked, so reporting a different path than it read is a
|
|
12
|
+
* correctness failure, not a cosmetic one.
|
|
13
|
+
* - Latent: `requestCore` policy-checks the RAW request string and normalizes afterwards,
|
|
14
|
+
* so a path like `/../../../../api/v1/users/current` would pass an `/api/v1/page/` prefix
|
|
15
|
+
* allowlist and then resolve somewhere else entirely. No `allow` policy is configured
|
|
16
|
+
* today, which is the only reason this is not already exploitable — and adding one is the
|
|
17
|
+
* natural next hardening step for this tool surface.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* The canonical form of a page path: leading slash, `.`/`..` collapsed, query and fragment
|
|
21
|
+
* dropped, percent-encoding decoded, encoded separators refused.
|
|
22
|
+
*
|
|
23
|
+
* Returned so the CALLER can report what it actually requested. A tool that normalizes for
|
|
24
|
+
* the request but echoes the raw input in its manifest is telling the model about a page it
|
|
25
|
+
* did not look at.
|
|
26
|
+
*/
|
|
27
|
+
export declare function normalizePagePath(path: string, label?: string): string;
|
|
28
|
+
/**
|
|
29
|
+
* Split a page-relative URL into the parent folder and the leaf url stored on the page.
|
|
30
|
+
*
|
|
31
|
+
* "/books/index" → { folder: "/books", url: "index", fullPath: "/books/index" }
|
|
32
|
+
* "/books" → { folder: "/books", url: "index", fullPath: "/books/index" }
|
|
33
|
+
* "/about-us/" → { folder: "/about-us", url: "index", fullPath: "/about-us/index" }
|
|
34
|
+
* "/" → { folder: "/", url: "index", fullPath: "/index" }
|
|
35
|
+
*
|
|
36
|
+
* Shares its normalization with {@link normalizePagePath} but NOT its output: the folder-vs-leaf
|
|
37
|
+
* decision is page-create's alone. The URL API preserves a trailing slash but does not know that
|
|
38
|
+
* "/about-us/" means a folder index while "/about-us" means a leaf url, and dotCMS pages always
|
|
39
|
+
* have a leaf url (commonly "index"), so a path with no explicit leaf gets one — matching how the
|
|
40
|
+
* admin UI and the rest of the platform address a folder's default page.
|
|
41
|
+
*
|
|
42
|
+
* Segments are returned DECODED here, because they become folder and page names rather than
|
|
43
|
+
* parts of a URL.
|
|
44
|
+
*/
|
|
45
|
+
export declare function splitUrlPath(urlPath: string): {
|
|
46
|
+
folder: string;
|
|
47
|
+
url: string;
|
|
48
|
+
fullPath: string;
|
|
49
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type DotCMSRuntime } from '../../../runtime';
|
|
2
|
+
import { type Endpoint } from '../../toolkit/endpoints';
|
|
3
|
+
/**
|
|
4
|
+
* Every endpoint this module calls. Each operation that resolves a site or language includes
|
|
5
|
+
* these in its own list. `resolve.spec.ts` checks every request against them.
|
|
6
|
+
*/
|
|
7
|
+
export declare const RESOLVE_ENDPOINTS: readonly Endpoint[];
|
|
8
|
+
/** A resolved site: the identifier the API needs, plus the hostname for manifests. */
|
|
9
|
+
export interface ResolvedSite {
|
|
10
|
+
identifier: string;
|
|
11
|
+
hostname: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Resolve a site (hostname OR identifier) to its identifier + hostname.
|
|
15
|
+
*
|
|
16
|
+
* Order is deliberate — cheapest and least fallible first:
|
|
17
|
+
* 1. the session cache, when it actually has sites;
|
|
18
|
+
* 2. a direct `GET /api/v1/site/{id}` when the caller gave something UUID-shaped;
|
|
19
|
+
* 3. a filtered `GET /api/v1/site` lookup by hostname.
|
|
20
|
+
*
|
|
21
|
+
* Step 2 matters most for the reported failure: a caller who already knows the identifier
|
|
22
|
+
* should never be blocked by a cache, because the identifier is precisely what the
|
|
23
|
+
* downstream call needs and no lookup is required to use it.
|
|
24
|
+
*/
|
|
25
|
+
export declare function resolveSite(dotcms: DotCMSRuntime, site: string): Promise<ResolvedSite>;
|
|
26
|
+
/**
|
|
27
|
+
* The instance's default site — what a path with no `//host` prefix means.
|
|
28
|
+
*
|
|
29
|
+
* The session cache first; then `GET /api/v1/site/defaultSite`, so a failed context load does
|
|
30
|
+
* not leave the caller unscoped. A failure of that read propagates as the instance error it is:
|
|
31
|
+
* falling back to "no site" would silently widen whatever the caller was about to scope.
|
|
32
|
+
*/
|
|
33
|
+
export declare function resolveDefaultSite(dotcms: DotCMSRuntime): Promise<ResolvedSite>;
|
|
34
|
+
/**
|
|
35
|
+
* Resolve a language id against the instance.
|
|
36
|
+
*
|
|
37
|
+
* dotCMS silently falls back to its default language for an unknown id rather than
|
|
38
|
+
* rejecting it, so an unrecognised id does not fail — it quietly writes to a DIFFERENT
|
|
39
|
+
* language than the caller named while the manifest echoes the id they asked for. That is
|
|
40
|
+
* worth catching, but ONLY when the language list actually loaded: with an empty list the
|
|
41
|
+
* caller's explicit id is better evidence than our missing cache, so it is passed through.
|
|
42
|
+
*/
|
|
43
|
+
export declare function resolveLanguageId(dotcms: DotCMSRuntime, languageId?: number): Promise<number>;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { type AssetManifestFailure, type AssetManifestFile, type AssetManifestSkipped } from './shared/asset-common';
|
|
2
|
+
import { type DotCMSRuntime } from '../../runtime';
|
|
3
|
+
import { type Endpoint } from '../toolkit/endpoints';
|
|
4
|
+
/**
|
|
5
|
+
* Every endpoint `uploadAssets` calls — the `upload_assets` tool enforces exactly this list,
|
|
6
|
+
* and `upload-assets.spec.ts` checks every request against it. Add a request, add it here.
|
|
7
|
+
*/
|
|
8
|
+
export declare const UPLOAD_ASSETS_ENDPOINTS: readonly Endpoint[];
|
|
9
|
+
export interface UploadAssetsOptions {
|
|
10
|
+
dotcms: DotCMSRuntime;
|
|
11
|
+
/** Absolute local directory the files are read from. */
|
|
12
|
+
src: string;
|
|
13
|
+
/** Host-qualified destination folder, e.g. `//demo.dotcms.com/application/themes/travel`. */
|
|
14
|
+
dest: string;
|
|
15
|
+
/** Comma-separated glob filter, matched against each path relative to `src`. */
|
|
16
|
+
include?: string;
|
|
17
|
+
/** Publish (`/api/v2/assets/publish`) rather than only save. */
|
|
18
|
+
publish: boolean;
|
|
19
|
+
/** After publishing, confirm each asset reports live. */
|
|
20
|
+
verify: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Directory `src` must be inside — after symlinks resolve. Omit only when YOU chose `src`;
|
|
23
|
+
* the `upload_assets` tool always sets it.
|
|
24
|
+
*/
|
|
25
|
+
root?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Largest file to send, in bytes. A larger one is recorded as a failure before it is read,
|
|
28
|
+
* and the rest still upload. Each file is held in memory once while it is sent, so this
|
|
29
|
+
* bounds the upload's memory. Unbounded when omitted; the `upload_assets` tool always sets it.
|
|
30
|
+
*/
|
|
31
|
+
maxFileBytes?: number;
|
|
32
|
+
/**
|
|
33
|
+
* Most files one upload sends (after `include`). More is refused before anything is sent,
|
|
34
|
+
* and the directory walk stops as soon as it is exceeded. Unbounded when omitted; the
|
|
35
|
+
* `upload_assets` tool always sets it.
|
|
36
|
+
*/
|
|
37
|
+
maxFiles?: number;
|
|
38
|
+
/**
|
|
39
|
+
* Most entries — files and directories — the walk of `src` visits, whether or not
|
|
40
|
+
* `include` selects them. More is refused before anything is sent. `maxFiles` alone does
|
|
41
|
+
* not bound the walk: a vast `src` with a narrow `include` matches few files and is walked
|
|
42
|
+
* to the end. Unbounded when omitted; the `upload_assets` tool always sets it.
|
|
43
|
+
*/
|
|
44
|
+
maxScannedEntries?: number;
|
|
45
|
+
}
|
|
46
|
+
/** What `uploadAssets` returns — never the file bytes, only what landed and what did not. */
|
|
47
|
+
export interface UploadAssetsManifest {
|
|
48
|
+
src: string;
|
|
49
|
+
dest: string;
|
|
50
|
+
count: number;
|
|
51
|
+
bytes: number;
|
|
52
|
+
files: AssetManifestFile[];
|
|
53
|
+
failures: AssetManifestFailure[];
|
|
54
|
+
skipped: AssetManifestSkipped[];
|
|
55
|
+
/** Published assets that did not report live when verified. */
|
|
56
|
+
notLive: AssetManifestFile[];
|
|
57
|
+
warnings: string[];
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Upload every file under a local directory as dotCMS file assets, preserving relative
|
|
61
|
+
* paths. Files are saved or published one at a time; a failed file is recorded in the
|
|
62
|
+
* manifest rather than aborting the batch.
|
|
63
|
+
*/
|
|
64
|
+
export declare function uploadAssets(options: UploadAssetsOptions): Promise<UploadAssetsManifest>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { RequestCallEvent } from '../../runtime';
|
|
2
|
+
/**
|
|
3
|
+
* A value given as-is, or a resolver read on every tool call — for tokens that rotate, come
|
|
4
|
+
* from a secrets manager, or are read lazily from a host's own configuration. A resolver may
|
|
5
|
+
* return `undefined` (e.g. an unset environment variable); the call then fails with a readable
|
|
6
|
+
* `CONFIGURATION` failure instead of the host crashing.
|
|
7
|
+
*/
|
|
8
|
+
export type Resolvable<T> = T | (() => T | undefined | Promise<T | undefined>);
|
|
9
|
+
/** Who is calling which dotCMS instance — everything a tool needs that is not about the tool. */
|
|
10
|
+
export interface DotCMSConnectionConfig {
|
|
11
|
+
/** dotCMS instance URL. */
|
|
12
|
+
url: Resolvable<string>;
|
|
13
|
+
/** dotCMS API token. Injected host-side — it never reaches the model or the sandbox. */
|
|
14
|
+
token: Resolvable<string>;
|
|
15
|
+
/** Observability hook fired around each request (token and sensitive bodies are never passed). */
|
|
16
|
+
onCall?: (event: RequestCallEvent) => void;
|
|
17
|
+
/** Called when loading instance context (sites, content types, …) fails. */
|
|
18
|
+
onContextError?: (label: string, error: unknown) => void;
|
|
19
|
+
/**
|
|
20
|
+
* Called when a `url` / `token` resolver throws — the host's view of the failure. The model
|
|
21
|
+
* is only told the connection could not be resolved: a resolver's own error (a secrets
|
|
22
|
+
* manager's message) can carry paths, key names or identifiers, so its detail comes here.
|
|
23
|
+
*/
|
|
24
|
+
onResolveError?: (field: 'url' | 'token', error: unknown) => void;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* A dotCMS connection: created once, handed to every tool. Holds no state and makes no request
|
|
28
|
+
* — so a host serving many users creates one per session, each with that user's token.
|
|
29
|
+
*/
|
|
30
|
+
export interface DotCMSConnection {
|
|
31
|
+
readonly __dotcmsConnection: true;
|
|
32
|
+
readonly config: Readonly<DotCMSConnectionConfig>;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Describe the dotCMS instance and identity the tools act as.
|
|
36
|
+
*
|
|
37
|
+
* ```ts
|
|
38
|
+
* const dotcms = dotcmsConnection({ url, token: () => secrets.get('dotcms') });
|
|
39
|
+
* const tools = [searchTool(dotcms), pageVerifyTool(dotcms)];
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
* Nothing is read, validated or fetched here — the values are resolved on each tool call.
|
|
43
|
+
*/
|
|
44
|
+
export declare function dotcmsConnection(config: DotCMSConnectionConfig): DotCMSConnection;
|
|
45
|
+
/** Whether `value` came from {@link dotcmsConnection}. */
|
|
46
|
+
export declare function isDotCMSConnection(value: unknown): value is DotCMSConnection;
|
|
47
|
+
/**
|
|
48
|
+
* The URL and token for one call. Throws `ConfigurationError` when the tool was given no
|
|
49
|
+
* connection at all (a JavaScript caller, or a cast) or a resolver failed; an EMPTY value is
|
|
50
|
+
* left to `createToolRuntime`, which reports it the same way.
|
|
51
|
+
*/
|
|
52
|
+
export declare function resolveConnection(connection: unknown): Promise<{
|
|
53
|
+
url: string;
|
|
54
|
+
token: string;
|
|
55
|
+
config: DotCMSConnectionConfig;
|
|
56
|
+
}>;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { type DotCMSConnection } from './connection';
|
|
3
|
+
import { type Endpoint } from './endpoints';
|
|
4
|
+
import { type DotCMSRuntime } from '../../runtime';
|
|
5
|
+
import type { DotCMSTool, DotCMSToolAnnotations, ExecuteToolOptions, RequestToolOptions } from './types';
|
|
6
|
+
/** The union of every factory's options — what `createTool` may find on any of them. */
|
|
7
|
+
type AnyToolOptions = Partial<ExecuteToolOptions> & Partial<RequestToolOptions>;
|
|
8
|
+
/** What a tool's handler receives for one call. */
|
|
9
|
+
export interface ToolContext<TOptions extends object> {
|
|
10
|
+
/** The options the tool's factory was called with. */
|
|
11
|
+
options: TOptions;
|
|
12
|
+
/**
|
|
13
|
+
* Build this call's runtime from the resolved connection. Throws `ConfigurationError` when
|
|
14
|
+
* the URL or token is empty, which `execute` turns into a `CONFIGURATION` failure.
|
|
15
|
+
*/
|
|
16
|
+
runtime(runtimeOptions?: {
|
|
17
|
+
timeout?: number;
|
|
18
|
+
includeSpec?: boolean;
|
|
19
|
+
}): DotCMSRuntime;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The static half of a tool — everything except the connection and options a consumer passes
|
|
23
|
+
* its factory. `handler` receives input that already passed `inputSchema`, and anything it
|
|
24
|
+
* throws becomes a `ToolFailure`.
|
|
25
|
+
*/
|
|
26
|
+
export interface ToolDefinition<TInput extends z.ZodObject, TResult, TOptions extends object> {
|
|
27
|
+
name: string;
|
|
28
|
+
title: string;
|
|
29
|
+
description: string;
|
|
30
|
+
inputSchema: TInput;
|
|
31
|
+
annotations: DotCMSToolAnnotations;
|
|
32
|
+
/**
|
|
33
|
+
* Every endpoint the tool calls — enforced, not documented: any other request fails with
|
|
34
|
+
* a `POLICY` failure before it reaches the wire. `'model-chosen'` is for `execute` alone,
|
|
35
|
+
* where the model picks the endpoints and the consumer's `allow` bounds them instead.
|
|
36
|
+
*/
|
|
37
|
+
endpoints: readonly Endpoint[] | 'model-chosen';
|
|
38
|
+
/** Appended to the rejection message when a request falls outside `endpoints`. */
|
|
39
|
+
endpointHint?: string;
|
|
40
|
+
/**
|
|
41
|
+
* The tool's text form, for a tool whose result IS text (`search`, `execute`): shown to the
|
|
42
|
+
* model as text rather than as a JSON string escaped inside an object. Omit it and results
|
|
43
|
+
* are shown as structured JSON. Declared here, typed against `TResult`, so the choice is the
|
|
44
|
+
* tool's — never inferred from what a result happens to look like.
|
|
45
|
+
*/
|
|
46
|
+
toText?: (result: TResult) => string;
|
|
47
|
+
handler(input: z.output<TInput>, ctx: ToolContext<TOptions>): Promise<TResult>;
|
|
48
|
+
}
|
|
49
|
+
/** Identity helper that infers the generics, so each handler sees its own input type. */
|
|
50
|
+
export declare function defineTool<TInput extends z.ZodObject, TResult, TOptions extends object = object>(definition: ToolDefinition<TInput, TResult, TOptions>): ToolDefinition<TInput, TResult, TOptions>;
|
|
51
|
+
/**
|
|
52
|
+
* Turn a definition, a connection and a consumer's options into the tool a framework
|
|
53
|
+
* registers.
|
|
54
|
+
*
|
|
55
|
+
* Cheap and side-effect free: nothing is resolved or fetched here. The connection is resolved
|
|
56
|
+
* inside `execute`, on every call — so a host started without credentials still boots and
|
|
57
|
+
* lists its tools, and each call reports the `CONFIGURATION` problem instead of the host
|
|
58
|
+
* crashing at startup.
|
|
59
|
+
*
|
|
60
|
+
* The one thing checked here is the consumer's own static `allow` list (`executeTool`), which
|
|
61
|
+
* becomes the tool's policy now: an entry that is not a path throws a `ValidationError`, so a
|
|
62
|
+
* malformed security boundary fails at startup instead of opening the API (see
|
|
63
|
+
* `toRequestPolicy`).
|
|
64
|
+
*/
|
|
65
|
+
export declare function createTool<TInput extends z.ZodObject, TResult, TOptions extends AnyToolOptions>(definition: ToolDefinition<TInput, TResult, TOptions>, connection: DotCMSConnection, options: TOptions): DotCMSTool<TInput, TResult>;
|
|
66
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { type RequestPolicy } from '../../adapter/request-core';
|
|
2
|
+
import type { RuntimeAllow } from '../../runtime';
|
|
3
|
+
/**
|
|
4
|
+
* One endpoint a tool may call: an HTTP method and a path pattern, e.g.
|
|
5
|
+
* `'POST /api/v1/page/{pageId}/content'`.
|
|
6
|
+
*
|
|
7
|
+
* In the pattern, `{name}` matches exactly one path segment and a trailing `/**` matches
|
|
8
|
+
* anything below that prefix. Patterns are matched against the RESOLVED path the request core
|
|
9
|
+
* hands a policy (dot-segments and encoded dots already collapsed), so `/**` cannot be used
|
|
10
|
+
* to climb out of its prefix.
|
|
11
|
+
*/
|
|
12
|
+
export type Endpoint = `${'GET' | 'POST' | 'PUT' | 'DELETE'} /${string}`;
|
|
13
|
+
/**
|
|
14
|
+
* What loading instance context reads. The runtime does it on a tool's behalf, so every tool
|
|
15
|
+
* that touches context includes these — they are the one endpoint list the toolkit owns.
|
|
16
|
+
* Every other list lives next to the operation that makes those requests.
|
|
17
|
+
*/
|
|
18
|
+
export declare const CONTEXT_ENDPOINTS: readonly Endpoint[];
|
|
19
|
+
/** Whether `req` is one of `endpoints`. */
|
|
20
|
+
export declare function matchesEndpoint(endpoints: readonly Endpoint[], req: {
|
|
21
|
+
method: string;
|
|
22
|
+
path: string;
|
|
23
|
+
}): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* The calls `endpoints` would refuse, as `METHOD path` strings — empty when every call is
|
|
26
|
+
* covered. Paths are resolved the way the request core resolves them before its policy check.
|
|
27
|
+
*
|
|
28
|
+
* What keeps an allow-list honest: the operation specs run every call their fakes see through
|
|
29
|
+
* this, so a request added to an operation without adding it to the tool's endpoints fails
|
|
30
|
+
* the build instead of being refused in production.
|
|
31
|
+
*/
|
|
32
|
+
export declare function unlistedCalls(endpoints: readonly Endpoint[], calls: ReadonlyArray<{
|
|
33
|
+
method?: string;
|
|
34
|
+
path: string;
|
|
35
|
+
}>): string[];
|
|
36
|
+
/**
|
|
37
|
+
* The policy for a fixed-purpose tool: exactly the endpoints it owns, and nothing else.
|
|
38
|
+
*
|
|
39
|
+
* The consumer never configures this — the tool knows what it calls. It is least privilege by
|
|
40
|
+
* construction: a bug in `page_verify`, or a page path shaped to reach somewhere else, cannot
|
|
41
|
+
* get past the render and site-lookup endpoints the tool actually needs.
|
|
42
|
+
*/
|
|
43
|
+
export declare function toolPolicy(toolName: string, endpoints: readonly Endpoint[], hint?: string): RequestPolicy;
|
|
44
|
+
/**
|
|
45
|
+
* The policy for `execute`, where the MODEL chooses the endpoints: the consumer's `allow`
|
|
46
|
+
* bounds its code, plus the context reads the runtime makes on the tool's behalf — so a narrow
|
|
47
|
+
* `allow` never silently empties the `sites` / `contentTypes` globals the code relies on.
|
|
48
|
+
* Those reads expose nothing the code does not already hold as globals.
|
|
49
|
+
*
|
|
50
|
+
* No `allow` means no policy: the model's code may call anything the token permits.
|
|
51
|
+
*/
|
|
52
|
+
export declare function modelChosenPolicy(allow: RuntimeAllow | undefined): RequestPolicy | undefined;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* A boolean that also accepts the string forms tool-calling clients (MCP and others) often
|
|
4
|
+
* send ("true"/"false"/"1"/"0"). Plain `z.coerce.boolean()` is wrong here: it uses JS
|
|
5
|
+
* truthiness, so the string "false" becomes `true`. This maps the string forms to their
|
|
6
|
+
* intended value and leaves real booleans untouched.
|
|
7
|
+
*
|
|
8
|
+
* The default is declared twice, on purpose. The inner one covers a blank string, which the
|
|
9
|
+
* preprocess turns into `undefined` AFTER the outer wrapper has already let it through. The
|
|
10
|
+
* outer one is what marks the field optional in the generated JSON Schema: a preprocess hides
|
|
11
|
+
* the inner default from `z.toJSONSchema`, so without it the schema a model reads lists the
|
|
12
|
+
* field as REQUIRED even though omitting it is the normal case.
|
|
13
|
+
*/
|
|
14
|
+
export declare const lenientBoolean: (defaultValue: boolean) => z.ZodDefault<z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodDefault<z.ZodBoolean>>>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type ToolFailure } from './tool-runtime';
|
|
2
|
+
/**
|
|
3
|
+
* What the two code tools (`search`, `execute`) resolve to: the capped text of the model's
|
|
4
|
+
* code's result. An object rather than a bare string because some frameworks (Google ADK)
|
|
5
|
+
* require tool results to be objects.
|
|
6
|
+
*/
|
|
7
|
+
export interface CodeToolResult {
|
|
8
|
+
result: string;
|
|
9
|
+
}
|
|
10
|
+
/** A JSON value: the shape the Vercel AI SDK requires of a `json` model output. */
|
|
11
|
+
export type JSONValue = null | string | number | boolean | {
|
|
12
|
+
[key: string]: JSONValue | undefined;
|
|
13
|
+
} | JSONValue[];
|
|
14
|
+
/** A tool's model-facing output, in the shape the Vercel AI SDK's `toModelOutput` returns. */
|
|
15
|
+
export type ToolModelOutput = {
|
|
16
|
+
type: 'text';
|
|
17
|
+
value: string;
|
|
18
|
+
} | {
|
|
19
|
+
type: 'json';
|
|
20
|
+
value: JSONValue;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* How a tool shows one of its results to the model — the one place that is decided.
|
|
24
|
+
*
|
|
25
|
+
* `toText` is the TOOL's declaration that it has a text form, typed against its own result:
|
|
26
|
+
* nothing is inferred from the value's shape, so a result can never be mistaken for text
|
|
27
|
+
* because it happens to look like one. A tool without it is shown as structured JSON, and so
|
|
28
|
+
* is a failure from any tool.
|
|
29
|
+
*/
|
|
30
|
+
export declare function renderToolOutput<TResult>(toText: ((result: TResult) => string) | undefined, output: TResult | ToolFailure): ToolModelOutput;
|
|
31
|
+
/** A model output as the text a text-only transport (an MCP `content` block) carries. */
|
|
32
|
+
export declare function modelOutputText(output: ToolModelOutput): string;
|