@dotcms/ai 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +137 -0
- package/index.cjs.default.js +1 -0
- package/index.cjs.js +7 -0
- package/index.cjs.mjs +2 -0
- package/index.d.ts +1 -0
- package/index.esm.js +1 -0
- package/package.json +72 -0
- package/runtime.cjs.default.js +1 -0
- package/runtime.cjs.js +0 -0
- package/runtime.cjs.mjs +2 -0
- package/runtime.d.ts +1 -0
- package/runtime.esm.js +0 -0
- package/spec.cjs.js +16090 -0
- package/spec.esm.js +16088 -0
- package/src/adapter/context-cache.d.ts +35 -0
- package/src/adapter/context.d.ts +42 -0
- package/src/adapter/http-client.d.ts +37 -0
- package/src/adapter/index.d.ts +12 -0
- package/src/adapter/request-core.d.ts +77 -0
- package/src/runtime.d.ts +70 -0
- package/src/sandbox/bun-worker.d.ts +10 -0
- package/src/sandbox/define-adapter.d.ts +88 -0
- package/src/sandbox/errors.d.ts +91 -0
- package/src/sandbox/executor.d.ts +27 -0
- package/src/sandbox/factory.d.ts +13 -0
- package/src/sandbox/index.d.ts +44 -0
- package/src/sandbox/interface.d.ts +8 -0
- package/src/sandbox/node-worker.d.ts +10 -0
- package/src/sandbox/types.d.ts +111 -0
- package/src/sandbox/worker-harness.d.ts +15 -0
- package/src/spec/index.d.ts +7 -0
- package/src/spec/spec.d.ts +1 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type DotCMSContext } from './context';
|
|
2
|
+
import type { Adapter } from '../sandbox/types';
|
|
3
|
+
interface CacheOptions {
|
|
4
|
+
ttlMs?: number;
|
|
5
|
+
onError?: (label: string, error: unknown) => void;
|
|
6
|
+
now?: () => number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Cache for dotCMS instance context, keyed on `(sessionId, url)`.
|
|
10
|
+
* - TTL-based expiry (default 5 minutes)
|
|
11
|
+
* - In-flight dedup so concurrent loads for the same key run once
|
|
12
|
+
* - In-memory only; no persistence
|
|
13
|
+
*/
|
|
14
|
+
export declare class ContextCache {
|
|
15
|
+
private cache;
|
|
16
|
+
private inFlight;
|
|
17
|
+
private ttlMs;
|
|
18
|
+
private onError?;
|
|
19
|
+
private now;
|
|
20
|
+
constructor(options?: CacheOptions);
|
|
21
|
+
/**
|
|
22
|
+
* Get (or load) the instance context for `(sessionId, url)`. `url` MUST be the dotCMS
|
|
23
|
+
* base URL the `apiAdapter` is bound to — it is folded into the cache key so two tenants
|
|
24
|
+
* sharing a `sessionId` never collide.
|
|
25
|
+
*/
|
|
26
|
+
get(sessionId: string, url: string, apiAdapter: Adapter): Promise<DotCMSContext>;
|
|
27
|
+
invalidate(sessionId: string, url: string): void;
|
|
28
|
+
clear(): void;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Shared cache instance. Prefer letting the runtime own its own `ContextCache` (the front
|
|
32
|
+
* door does); this module-level singleton remains for low-level callers that opt into it.
|
|
33
|
+
*/
|
|
34
|
+
export declare function getSharedContextCache(options?: CacheOptions): ContextCache;
|
|
35
|
+
export {};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Adapter } from '../sandbox/types';
|
|
2
|
+
export interface ContentTypeSummary {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
variable: string;
|
|
6
|
+
baseType: string;
|
|
7
|
+
host?: string;
|
|
8
|
+
folder?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface SiteSummary {
|
|
11
|
+
identifier: string;
|
|
12
|
+
hostname: string;
|
|
13
|
+
isDefault: boolean;
|
|
14
|
+
archived: boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface LanguageSummary {
|
|
17
|
+
id: number;
|
|
18
|
+
languageCode: string;
|
|
19
|
+
countryCode: string;
|
|
20
|
+
language: string;
|
|
21
|
+
country: string;
|
|
22
|
+
isoCode: string;
|
|
23
|
+
}
|
|
24
|
+
export interface CurrentUserSummary {
|
|
25
|
+
userId: string;
|
|
26
|
+
email: string;
|
|
27
|
+
givenName?: string;
|
|
28
|
+
surname?: string;
|
|
29
|
+
admin: boolean;
|
|
30
|
+
roles?: string[];
|
|
31
|
+
}
|
|
32
|
+
export interface DotCMSContext {
|
|
33
|
+
contentTypes: ContentTypeSummary[];
|
|
34
|
+
sites: SiteSummary[];
|
|
35
|
+
languages: LanguageSummary[];
|
|
36
|
+
currentUser: CurrentUserSummary | null;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Load a minimal snapshot of dotCMS instance context for sandbox injection.
|
|
40
|
+
* Each loader is independent — a failure in one does not poison the others.
|
|
41
|
+
*/
|
|
42
|
+
export declare function loadDotCMSContext(apiAdapter: Adapter, onError?: (label: string, error: unknown) => void): Promise<DotCMSContext>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type RequestCallEvent, type RequestOptions, type RequestPolicy } from './request-core';
|
|
2
|
+
import type { Adapter } from '../sandbox/types';
|
|
3
|
+
export { type BinaryResponseEnvelope, type RequestOptions, type RequestPolicy, type RequestCallEvent, isBinaryResponseEnvelope } from './request-core';
|
|
4
|
+
export interface ApiAdapterConfig {
|
|
5
|
+
dotcmsUrl: string;
|
|
6
|
+
authToken: string;
|
|
7
|
+
/** Optional policy/allow-list consulted before each request reaches the wire. */
|
|
8
|
+
policy?: RequestPolicy;
|
|
9
|
+
/** Aborts in-flight requests when the surrounding execution (e.g. the sandbox) is torn down. */
|
|
10
|
+
signal?: AbortSignal;
|
|
11
|
+
/** Observability hook fired around each call (token/sensitive bodies never included). */
|
|
12
|
+
onCall?: (event: RequestCallEvent) => void;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Create the "api" adapter for making authenticated HTTP calls to dotCMS.
|
|
16
|
+
*
|
|
17
|
+
* This is the dotCMS door out of the sandbox. The adapter's single `request` method is a
|
|
18
|
+
* thin wrapper over the shared {@link requestCore} — so a request driven by the sandboxed
|
|
19
|
+
* `api.request` and one driven by the runtime's direct `request()` are byte-for-byte the
|
|
20
|
+
* same code path (one auth path, one allow-list, one error model). Auth tokens are injected
|
|
21
|
+
* by the host — never exposed to the sandbox.
|
|
22
|
+
*/
|
|
23
|
+
export declare function createApiAdapter(config: ApiAdapterConfig): Adapter;
|
|
24
|
+
/**
|
|
25
|
+
* The dotCMS adapter — the building block exported from `@dotcms/ai/adapter`.
|
|
26
|
+
*
|
|
27
|
+
* Returns both the underlying {@link Adapter} (for the sandbox) and a direct `request`
|
|
28
|
+
* function (the no-worker path). A power-user can wrap this value — e.g. with an allow-list —
|
|
29
|
+
* before handing it to the runtime, because it is a plain object you can compose.
|
|
30
|
+
*/
|
|
31
|
+
export interface DotCMSAdapter {
|
|
32
|
+
/** The adapter object the sandbox executor consumes (`api.request` inside sandbox code). */
|
|
33
|
+
adapter: Adapter;
|
|
34
|
+
/** The direct, no-worker request path — the same shared core the sandbox bridges to. */
|
|
35
|
+
request(options: RequestOptions): Promise<unknown>;
|
|
36
|
+
}
|
|
37
|
+
export declare function dotcmsAdapter(config: ApiAdapterConfig): DotCMSAdapter;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@dotcms/ai/adapter` — the dotCMS-specific building blocks: the dotCMS door out of the
|
|
3
|
+
* sandbox (`dotcmsAdapter` / `createApiAdapter`), the shared request core, instance-context
|
|
4
|
+
* loading, and the context cache. dotCMS-wired by design (the generic engine is `/sandbox`).
|
|
5
|
+
*/
|
|
6
|
+
export { createApiAdapter, dotcmsAdapter, isBinaryResponseEnvelope } from './http-client';
|
|
7
|
+
export type { ApiAdapterConfig, DotCMSAdapter, BinaryResponseEnvelope, RequestOptions, RequestPolicy, RequestCallEvent } from './http-client';
|
|
8
|
+
export { requestCore } from './request-core';
|
|
9
|
+
export type { RequestCoreContext } from './request-core';
|
|
10
|
+
export { loadDotCMSContext } from './context';
|
|
11
|
+
export type { DotCMSContext, ContentTypeSummary, SiteSummary, LanguageSummary, CurrentUserSummary } from './context';
|
|
12
|
+
export { ContextCache, getSharedContextCache } from './context-cache';
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The single shared request core.
|
|
3
|
+
*
|
|
4
|
+
* "One adapter, one auth path, one allow-list" is only true if BOTH verbs of the runtime
|
|
5
|
+
* route through one function that owns *all* of: path/method validation, the policy check,
|
|
6
|
+
* auth injection, `fetch`, response decoding (incl. the binary envelope), the error model,
|
|
7
|
+
* and abort/timeout. `runtime.request()` calls `requestCore` directly; the sandbox's
|
|
8
|
+
* `api.request` is a thin worker→host bridge to this same function. Neither verb may add
|
|
9
|
+
* behavior the other lacks — that is the contract, not an aspiration.
|
|
10
|
+
*/
|
|
11
|
+
interface FileFieldDescriptor {
|
|
12
|
+
name: string;
|
|
13
|
+
type: string;
|
|
14
|
+
data?: string;
|
|
15
|
+
url?: string;
|
|
16
|
+
}
|
|
17
|
+
type FormDataFieldValue = string | FileFieldDescriptor;
|
|
18
|
+
export interface RequestOptions {
|
|
19
|
+
method?: string;
|
|
20
|
+
path: string;
|
|
21
|
+
query?: Record<string, string | number | boolean>;
|
|
22
|
+
body?: unknown;
|
|
23
|
+
formData?: Record<string, FormDataFieldValue>;
|
|
24
|
+
headers?: Record<string, string>;
|
|
25
|
+
responseType?: 'auto' | 'base64';
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* A policy hook consulted before a request reaches the wire. Return `false` (or throw) to
|
|
29
|
+
* reject. The single place a caller-owned allow-list plugs in — both verbs honor it because
|
|
30
|
+
* both flow through `requestCore`.
|
|
31
|
+
*/
|
|
32
|
+
export type RequestPolicy = (req: {
|
|
33
|
+
method: string;
|
|
34
|
+
path: string;
|
|
35
|
+
}) => boolean | void;
|
|
36
|
+
/** Host-side context the request core needs. Auth lives here, never in the caller's code. */
|
|
37
|
+
export interface RequestCoreContext {
|
|
38
|
+
baseUrl: string;
|
|
39
|
+
authToken: string;
|
|
40
|
+
/** Optional policy/allow-list consulted before every call. */
|
|
41
|
+
policy?: RequestPolicy;
|
|
42
|
+
/** Aborts the in-flight fetch when the surrounding execution (e.g. sandbox) is torn down. */
|
|
43
|
+
signal?: AbortSignal;
|
|
44
|
+
/** Observability hook — fired around each call with redacted metadata (never the token). */
|
|
45
|
+
onCall?: (event: RequestCallEvent) => void;
|
|
46
|
+
}
|
|
47
|
+
export interface RequestCallEvent {
|
|
48
|
+
method: string;
|
|
49
|
+
path: string;
|
|
50
|
+
status?: number;
|
|
51
|
+
durationMs: number;
|
|
52
|
+
ok: boolean;
|
|
53
|
+
errorCode?: string;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Tagged envelope returned for non-textual response bodies. The raw bytes are
|
|
57
|
+
* base64-encoded so they survive the `JSON.stringify` serialization boundary in
|
|
58
|
+
* the consuming sandbox intact — `response.text()` would corrupt any non-UTF-8 byte
|
|
59
|
+
* into the U+FFFD replacement char. Consumers detect `__dotcmsBinary` and decode.
|
|
60
|
+
*/
|
|
61
|
+
export interface BinaryResponseEnvelope {
|
|
62
|
+
__dotcmsBinary: true;
|
|
63
|
+
contentType: string;
|
|
64
|
+
base64: string;
|
|
65
|
+
byteLength: number;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Type guard for the binary response envelope. Consumers can use this to detect
|
|
69
|
+
* a binary body and `Buffer.from(envelope.base64, 'base64')` to recover the bytes.
|
|
70
|
+
*/
|
|
71
|
+
export declare function isBinaryResponseEnvelope(value: unknown): value is BinaryResponseEnvelope;
|
|
72
|
+
/**
|
|
73
|
+
* Execute one authenticated request against dotCMS. This is the function both verbs share.
|
|
74
|
+
* Auth is injected here, on the host side; the executing code never sees the token.
|
|
75
|
+
*/
|
|
76
|
+
export declare function requestCore(options: RequestOptions, ctx: RequestCoreContext): Promise<unknown>;
|
|
77
|
+
export {};
|
package/src/runtime.d.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { type RequestCallEvent, type RequestOptions, type RequestPolicy } from './adapter/request-core';
|
|
2
|
+
import type { DotCMSContext } from './adapter/context';
|
|
3
|
+
import type { SandboxResult } from './sandbox/types';
|
|
4
|
+
/**
|
|
5
|
+
* Policy controlling which requests are permitted. Either a list of allowed path prefixes
|
|
6
|
+
* (a request is allowed if its path starts with any entry) or a predicate consulted per call.
|
|
7
|
+
* Both verbs honor it, because both flow through the one shared request core.
|
|
8
|
+
*/
|
|
9
|
+
export type RuntimeAllow = string[] | RequestPolicy;
|
|
10
|
+
export interface DotCMSRuntimeConfig {
|
|
11
|
+
/** dotCMS instance URL. */
|
|
12
|
+
url: string;
|
|
13
|
+
/** Server-side token. NEVER enters the sandbox — injected host-side on every call. */
|
|
14
|
+
token: string;
|
|
15
|
+
/** Optional allow-list / policy applied to every request. */
|
|
16
|
+
allow?: RuntimeAllow;
|
|
17
|
+
/** Context-cache + isolation key. Defaults to `__default__`. */
|
|
18
|
+
sessionId?: string;
|
|
19
|
+
/** Inject the OpenAPI `spec` global into `run(code)` (the search use case). */
|
|
20
|
+
includeSpec?: boolean;
|
|
21
|
+
/** Sandbox wall-clock timeout (ms) for `run(code)`. Defaults to 15000. */
|
|
22
|
+
timeout?: number;
|
|
23
|
+
/** Observability hook fired around each request (token/sensitive bodies never logged). */
|
|
24
|
+
onCall?: (event: RequestCallEvent) => void;
|
|
25
|
+
/** Error handler for instance-context load failures (per loader). */
|
|
26
|
+
onContextError?: (label: string, error: unknown) => void;
|
|
27
|
+
/**
|
|
28
|
+
* Whether to leak stack traces (which can contain host paths) to executed/model code.
|
|
29
|
+
* Defaults to false — stacks are withheld from `run(code)` results.
|
|
30
|
+
*/
|
|
31
|
+
includeStacks?: boolean;
|
|
32
|
+
}
|
|
33
|
+
export interface DotCMSRuntime {
|
|
34
|
+
/**
|
|
35
|
+
* DIRECT — you write the call. No worker. Use this whenever you author the call yourself.
|
|
36
|
+
* Routes through the same shared request core as `run`. Pass `opts.signal` to make the
|
|
37
|
+
* call abortable (e.g. to wrap it in your own timeout) — the direct path has no surrounding
|
|
38
|
+
* timeout of its own.
|
|
39
|
+
*/
|
|
40
|
+
request(options: RequestOptions, opts?: {
|
|
41
|
+
signal?: AbortSignal;
|
|
42
|
+
}): Promise<unknown>;
|
|
43
|
+
/**
|
|
44
|
+
* SANDBOXED — runs `code` you did NOT write (a model did) in a confined worker whose
|
|
45
|
+
* `api.request` forwards to the same request core. Returns the structured sandbox result.
|
|
46
|
+
*/
|
|
47
|
+
run<T = unknown>(code: string): Promise<SandboxResult<T>>;
|
|
48
|
+
/** Load (or read cached) instance context for this runtime's session+url. */
|
|
49
|
+
loadContext(): Promise<DotCMSContext>;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The front door. One runtime, two verbs.
|
|
53
|
+
*
|
|
54
|
+
* `request` is the default — use it when you write the call. `run` is only for code you did
|
|
55
|
+
* not write (a model did). `run(code)` is implemented *as* "spin a worker whose `api.request`
|
|
56
|
+
* forwards to this runtime's request core", so the two verbs cannot drift: one is built on
|
|
57
|
+
* the other, sharing one adapter, one auth path, one allow-list, one error model.
|
|
58
|
+
*
|
|
59
|
+
* Note: this is an execution runtime, not an agent. There is no LLM, no inference, no
|
|
60
|
+
* prompting inside it. The agent is what the *user* builds on top.
|
|
61
|
+
*/
|
|
62
|
+
export declare function createRuntime(config: DotCMSRuntimeConfig): DotCMSRuntime;
|
|
63
|
+
export { defineAdapter, describeAdapterForLLM } from './sandbox/define-adapter';
|
|
64
|
+
export type { AdapterContext, AdapterDef, AdapterMethodDef, DefinedAdapter } from './sandbox/define-adapter';
|
|
65
|
+
export { DotCMSError, ValidationError, PolicyError, HttpError, TimeoutError, AbortError, SandboxError, RuntimeError, isDotCMSError, serializeError } from './sandbox/errors';
|
|
66
|
+
export type { DotCMSErrorCode, SerializedDotCMSError } from './sandbox/errors';
|
|
67
|
+
export type { SandboxResult, SandboxResultError } from './sandbox/types';
|
|
68
|
+
export { isBinaryResponseEnvelope } from './adapter/request-core';
|
|
69
|
+
export type { BinaryResponseEnvelope, RequestOptions, RequestCallEvent } from './adapter/request-core';
|
|
70
|
+
export type { DotCMSContext, ContentTypeSummary, SiteSummary, LanguageSummary, CurrentUserSummary } from './adapter/context';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ISandbox } from './interface';
|
|
2
|
+
import type { ExecutionContext, SandboxConfig, SandboxResult } from './types';
|
|
3
|
+
export declare class BunWorkerSandbox implements ISandbox {
|
|
4
|
+
private config;
|
|
5
|
+
constructor(config?: SandboxConfig);
|
|
6
|
+
getConfig(): SandboxConfig;
|
|
7
|
+
configure(config: Partial<SandboxConfig>): void;
|
|
8
|
+
execute<T = unknown>(code: string, context: ExecutionContext): Promise<SandboxResult<T>>;
|
|
9
|
+
dispose(): void;
|
|
10
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { Adapter } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* The host capabilities the runtime injects into every adapter handler. `request` is the
|
|
5
|
+
* shared request core with auth already bound — a handler reaches dotCMS through it, never
|
|
6
|
+
* through a free-floating global. `signal` aborts in-flight work when the sandbox times out.
|
|
7
|
+
*/
|
|
8
|
+
export interface AdapterContext {
|
|
9
|
+
request: (opts: RequestLike) => Promise<unknown>;
|
|
10
|
+
signal?: AbortSignal;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Minimal request shape an adapter handler passes to `ctx.request`. Kept structural (not a
|
|
14
|
+
* hard import of the dotCMS RequestOptions) so `/sandbox` stays free of dotCMS specifics.
|
|
15
|
+
*/
|
|
16
|
+
export interface RequestLike {
|
|
17
|
+
method?: string;
|
|
18
|
+
path: string;
|
|
19
|
+
query?: Record<string, string | number | boolean>;
|
|
20
|
+
body?: unknown;
|
|
21
|
+
formData?: Record<string, unknown>;
|
|
22
|
+
headers?: Record<string, string>;
|
|
23
|
+
responseType?: 'auto' | 'base64';
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* A single method definition on an adapter. `input` is mandatory — it is the *trust*
|
|
27
|
+
* boundary: arguments arrive from model-authored code inside the sandbox and must be
|
|
28
|
+
* validated before the handler runs. `output` is the *tool-contract* boundary: required for
|
|
29
|
+
* any model-facing adapter (it becomes the result schema the LLM plans against), optional
|
|
30
|
+
* only for internal host-to-host plumbing the model never sees.
|
|
31
|
+
*/
|
|
32
|
+
export interface AdapterMethodDef<TInput extends z.ZodTypeAny = z.ZodTypeAny, TOutput extends z.ZodTypeAny = z.ZodTypeAny> {
|
|
33
|
+
description: string;
|
|
34
|
+
input: TInput;
|
|
35
|
+
output?: TOutput;
|
|
36
|
+
handler: (input: z.infer<TInput>, ctx: AdapterContext) => Promise<unknown> | unknown;
|
|
37
|
+
}
|
|
38
|
+
export interface AdapterDef<TMethods extends Record<string, AdapterMethodDef<any, any>> = Record<string, AdapterMethodDef<any, any>>> {
|
|
39
|
+
name: string;
|
|
40
|
+
description?: string;
|
|
41
|
+
version?: string;
|
|
42
|
+
methods: TMethods;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* A defined adapter. It IS a plain `Adapter` (so the executor consumes it unchanged) plus:
|
|
46
|
+
* - `__schemas`: the per-method Zod input/output, used to auto-generate tool definitions;
|
|
47
|
+
* - `withContext(ctx)`: binds the injected host capabilities, returning a runnable adapter.
|
|
48
|
+
*
|
|
49
|
+
* The schemas being declared is what lets the runtime *describe the adapter to an LLM
|
|
50
|
+
* automatically* — that declaration IS the tool definition (the bridge to MCP/tool-calling).
|
|
51
|
+
*/
|
|
52
|
+
export interface DefinedAdapter extends Adapter {
|
|
53
|
+
readonly __defined: true;
|
|
54
|
+
readonly __schemas: Record<string, {
|
|
55
|
+
description: string;
|
|
56
|
+
input: z.ZodTypeAny;
|
|
57
|
+
output?: z.ZodTypeAny;
|
|
58
|
+
}>;
|
|
59
|
+
/** Every method declares an `output` schema → safe to expose to a model. */
|
|
60
|
+
readonly modelExposable: boolean;
|
|
61
|
+
/** Bind host capabilities (the injected `ctx`) and return a runnable adapter. */
|
|
62
|
+
withContext(ctx: AdapterContext): Adapter;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Build a typed, schema-validated adapter. Handlers get `(input, ctx)` — `input` already
|
|
66
|
+
* validated, `ctx.request` already auth-bound by the runtime.
|
|
67
|
+
*
|
|
68
|
+
* The returned value is a plain `Adapter` (the `methods` Map is populated with thunks that
|
|
69
|
+
* throw until `withContext` is called), so it can be wrapped (e.g. by an allow-list) before
|
|
70
|
+
* it reaches the runtime, exactly like the built-in `dotcmsAdapter`.
|
|
71
|
+
*/
|
|
72
|
+
export declare function defineAdapter<TMethods extends Record<string, AdapterMethodDef<any, any>>>(def: AdapterDef<TMethods>): DefinedAdapter;
|
|
73
|
+
/** Type guard: distinguishes a `defineAdapter` result from a hand-built `Adapter`. */
|
|
74
|
+
export declare function isDefinedAdapter(adapter: Adapter): adapter is DefinedAdapter;
|
|
75
|
+
/**
|
|
76
|
+
* Render a defined adapter's methods as JSON-Schema-ish tool definitions an LLM can consume.
|
|
77
|
+
* This is the bridge to MCP / tool-calling: the declared Zod schemas become the tool's
|
|
78
|
+
* input/output contract, replacing hand-written description prose.
|
|
79
|
+
*
|
|
80
|
+
* Only methods that declare an `output` schema are emitted — a method without one is treated
|
|
81
|
+
* as internal plumbing and withheld from the model.
|
|
82
|
+
*/
|
|
83
|
+
export declare function describeAdapterForLLM(adapter: DefinedAdapter): Array<{
|
|
84
|
+
name: string;
|
|
85
|
+
description: string;
|
|
86
|
+
inputSchema: unknown;
|
|
87
|
+
outputSchema?: unknown;
|
|
88
|
+
}>;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The single typed error hierarchy for `@dotcms/ai`.
|
|
3
|
+
*
|
|
4
|
+
* Both verbs of the runtime — `request()` (direct) and `run()` (sandboxed) — surface
|
|
5
|
+
* the *same* error types, because both route through one shared request core. The
|
|
6
|
+
* model-facing string an MCP tool builds is *formatting on top of* these errors, not a
|
|
7
|
+
* separate error model.
|
|
8
|
+
*
|
|
9
|
+
* Every error carries a stable, machine-readable `code` and a `toJSON()` so it survives
|
|
10
|
+
* the worker→host serialization boundary (a thrown error becomes `{ name, message, stack }`
|
|
11
|
+
* by default; these types preserve `code` and the structured detail too).
|
|
12
|
+
*/
|
|
13
|
+
export type DotCMSErrorCode = 'VALIDATION' | 'POLICY' | 'HTTP' | 'TIMEOUT' | 'ABORT' | 'SANDBOX' | 'RUNTIME';
|
|
14
|
+
/** Serializable shape every DotCMSError flattens to (and that crosses the worker boundary). */
|
|
15
|
+
export interface SerializedDotCMSError {
|
|
16
|
+
name: string;
|
|
17
|
+
code: DotCMSErrorCode;
|
|
18
|
+
message: string;
|
|
19
|
+
/**
|
|
20
|
+
* Present only when `includeStack` is enabled on the runtime. Host stack traces can
|
|
21
|
+
* contain absolute host paths, so they are withheld from model-authored code by default.
|
|
22
|
+
*/
|
|
23
|
+
stack?: string;
|
|
24
|
+
/** Type-specific structured detail (HTTP status + body, validation issues, etc.). */
|
|
25
|
+
detail?: Record<string, unknown>;
|
|
26
|
+
}
|
|
27
|
+
export declare abstract class DotCMSError extends Error {
|
|
28
|
+
abstract readonly code: DotCMSErrorCode;
|
|
29
|
+
constructor(message: string, options?: {
|
|
30
|
+
cause?: unknown;
|
|
31
|
+
});
|
|
32
|
+
/** Type-specific structured detail; overridden by subclasses that carry data. */
|
|
33
|
+
detail(): Record<string, unknown> | undefined;
|
|
34
|
+
toJSON(): SerializedDotCMSError;
|
|
35
|
+
}
|
|
36
|
+
/** Adapter input failed validation, or request options were malformed. */
|
|
37
|
+
export declare class ValidationError extends DotCMSError {
|
|
38
|
+
readonly code: "VALIDATION";
|
|
39
|
+
/** Field-level issues, when the source was a Zod (or similar) schema. */
|
|
40
|
+
readonly issues?: unknown[];
|
|
41
|
+
constructor(message: string, issues?: unknown[]);
|
|
42
|
+
detail(): Record<string, unknown> | undefined;
|
|
43
|
+
}
|
|
44
|
+
/** The allow-list / policy rejected the call before it reached the network. */
|
|
45
|
+
export declare class PolicyError extends DotCMSError {
|
|
46
|
+
readonly code: "POLICY";
|
|
47
|
+
readonly method: string;
|
|
48
|
+
readonly path: string;
|
|
49
|
+
constructor(message: string, method: string, path: string);
|
|
50
|
+
detail(): Record<string, unknown>;
|
|
51
|
+
}
|
|
52
|
+
/** dotCMS responded with a non-2xx status. Carries the status and the (text) body. */
|
|
53
|
+
export declare class HttpError extends DotCMSError {
|
|
54
|
+
readonly code: "HTTP";
|
|
55
|
+
readonly status: number;
|
|
56
|
+
readonly statusText: string;
|
|
57
|
+
readonly body: string;
|
|
58
|
+
constructor(status: number, statusText: string, body: string);
|
|
59
|
+
detail(): Record<string, unknown>;
|
|
60
|
+
}
|
|
61
|
+
/** A timeout elapsed (sandbox wall-clock, per-adapter-call, or context load). */
|
|
62
|
+
export declare class TimeoutError extends DotCMSError {
|
|
63
|
+
readonly code: "TIMEOUT";
|
|
64
|
+
readonly timeoutMs?: number;
|
|
65
|
+
constructor(message: string, timeoutMs?: number);
|
|
66
|
+
detail(): Record<string, unknown> | undefined;
|
|
67
|
+
}
|
|
68
|
+
/** The call was aborted in-flight (typically because the sandbox timed out). */
|
|
69
|
+
export declare class AbortError extends DotCMSError {
|
|
70
|
+
readonly code: "ABORT";
|
|
71
|
+
}
|
|
72
|
+
/** Model-authored code threw while executing inside the sandbox. */
|
|
73
|
+
export declare class SandboxError extends DotCMSError {
|
|
74
|
+
readonly code: "SANDBOX";
|
|
75
|
+
/** The original error's name (e.g. "TypeError") as seen inside the sandbox. */
|
|
76
|
+
readonly originalName?: string;
|
|
77
|
+
constructor(message: string, originalName?: string, stack?: string);
|
|
78
|
+
detail(): Record<string, unknown> | undefined;
|
|
79
|
+
}
|
|
80
|
+
/** Catch-all for runtime-internal failures that don't fit a more specific type. */
|
|
81
|
+
export declare class RuntimeError extends DotCMSError {
|
|
82
|
+
readonly code: "RUNTIME";
|
|
83
|
+
}
|
|
84
|
+
/** Type guard for any error in the hierarchy. */
|
|
85
|
+
export declare function isDotCMSError(value: unknown): value is DotCMSError;
|
|
86
|
+
/**
|
|
87
|
+
* Normalize any thrown value into a serializable error shape. Used at the worker→host
|
|
88
|
+
* boundary and when formatting a result, so callers always get a consistent structure
|
|
89
|
+
* regardless of what was thrown.
|
|
90
|
+
*/
|
|
91
|
+
export declare function serializeError(value: unknown): SerializedDotCMSError;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ISandbox } from './interface';
|
|
2
|
+
import type { Adapter, SandboxConfig, SandboxResult } from './types';
|
|
3
|
+
export interface ExecutorOptions {
|
|
4
|
+
config?: {
|
|
5
|
+
adapters?: Adapter[];
|
|
6
|
+
sandbox?: SandboxConfig;
|
|
7
|
+
};
|
|
8
|
+
sandboxFactory?: (config?: SandboxConfig) => ISandbox;
|
|
9
|
+
}
|
|
10
|
+
export declare class Executor {
|
|
11
|
+
private adapters;
|
|
12
|
+
private sandboxConfig;
|
|
13
|
+
private sandboxFactory;
|
|
14
|
+
constructor(options?: ExecutorOptions);
|
|
15
|
+
registerAdapter(adapter: Adapter): void;
|
|
16
|
+
unregisterAdapter(name: string): boolean;
|
|
17
|
+
getAdapter(name: string): Adapter | undefined;
|
|
18
|
+
getAdapterNames(): string[];
|
|
19
|
+
execute<T = unknown>(code: string, options?: {
|
|
20
|
+
sandbox?: Partial<SandboxConfig>;
|
|
21
|
+
adapters?: string[];
|
|
22
|
+
variables?: Record<string, unknown>;
|
|
23
|
+
}): Promise<SandboxResult<T>>;
|
|
24
|
+
configureSandbox(config: Partial<SandboxConfig>): void;
|
|
25
|
+
private buildExecutionContext;
|
|
26
|
+
}
|
|
27
|
+
export declare function createExecutor(options?: ExecutorOptions): Executor;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ISandbox } from './interface';
|
|
2
|
+
import type { SandboxConfig } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Low-level worker-sandbox factory. Auto-detects the runtime: native Web Workers on Bun,
|
|
5
|
+
* `worker_threads` on Node. Returns the raw {@link ISandbox} whose `execute(code, context)`
|
|
6
|
+
* the {@link Executor} drives. Most callers want the higher-level `createSandbox` barrel
|
|
7
|
+
* export (which returns a `run(code)` surface) instead of this.
|
|
8
|
+
*
|
|
9
|
+
* Backends are statically imported (not `require`d) so this works in both the ESM and CJS
|
|
10
|
+
* builds — `require` is undefined in an ESM module. Importing both is cheap: each file only
|
|
11
|
+
* declares a class, and `node:worker_threads` (used by the Node backend) is available on Bun too.
|
|
12
|
+
*/
|
|
13
|
+
export declare function createWorkerSandbox(config?: SandboxConfig): ISandbox;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@dotcms/ai/sandbox` — the generic execution engine. ZERO dotCMS code lives here (a
|
|
3
|
+
* lint-enforced module boundary); this subpath is fully generic. The dotCMS wiring lives in
|
|
4
|
+
* `@dotcms/ai/adapter`, and the front door that combines them in `@dotcms/ai`.
|
|
5
|
+
*/
|
|
6
|
+
import { type RequestLike } from './define-adapter';
|
|
7
|
+
import type { Adapter, SandboxResourceLimits, SandboxResult } from './types';
|
|
8
|
+
export { Executor, createExecutor } from './executor';
|
|
9
|
+
export type { ExecutorOptions } from './executor';
|
|
10
|
+
export { createWorkerSandbox } from './factory';
|
|
11
|
+
export type { ISandbox, SandboxFactory } from './interface';
|
|
12
|
+
export { defineAdapter, isDefinedAdapter, describeAdapterForLLM } from './define-adapter';
|
|
13
|
+
export type { AdapterContext, AdapterDef, AdapterMethodDef, DefinedAdapter, RequestLike } from './define-adapter';
|
|
14
|
+
export { DotCMSError, ValidationError, PolicyError, HttpError, TimeoutError, AbortError, SandboxError, RuntimeError, isDotCMSError, serializeError } from './errors';
|
|
15
|
+
export type { DotCMSErrorCode, SerializedDotCMSError } from './errors';
|
|
16
|
+
export type { Adapter, AdapterMethod, AdapterMethodParameter, SandboxConfig, SandboxResourceLimits, SandboxResult, SandboxResultError, ExecutionContext } from './types';
|
|
17
|
+
export interface CreateSandboxConfig {
|
|
18
|
+
/** Adapters granted to the sandboxed code — the only doors out. */
|
|
19
|
+
adapters: Adapter[];
|
|
20
|
+
/** Wall-clock timeout (ms). */
|
|
21
|
+
timeout?: number;
|
|
22
|
+
/** Extra globals injected into the sandbox. */
|
|
23
|
+
globals?: Record<string, unknown>;
|
|
24
|
+
/** Per-execution memory/stack caps. */
|
|
25
|
+
resourceLimits?: SandboxResourceLimits;
|
|
26
|
+
/**
|
|
27
|
+
* Host `request` capability injected into `defineAdapter` handlers as `ctx.request`. When
|
|
28
|
+
* provided, defined adapters are bound to it (and the per-run abort signal) before running.
|
|
29
|
+
* For the generic engine this is optional; the dotCMS front door always provides one.
|
|
30
|
+
*/
|
|
31
|
+
request?: (opts: RequestLike) => Promise<unknown>;
|
|
32
|
+
}
|
|
33
|
+
export interface Sandbox {
|
|
34
|
+
/** Run code in the confined worker. Returns the structured result. */
|
|
35
|
+
run<T = unknown>(code: string): Promise<SandboxResult<T>>;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Create a sandbox over a set of adapters — the generic entry point shown in §6.2.
|
|
39
|
+
*
|
|
40
|
+
* `defineAdapter` results are bound to the injected host `request` (and a per-run abort
|
|
41
|
+
* signal) via `withContext`; plain hand-built adapters are passed through untouched. Each
|
|
42
|
+
* `run()` gets its own `AbortController`, so a timeout aborts in-flight host work.
|
|
43
|
+
*/
|
|
44
|
+
export declare function createSandbox(config: CreateSandboxConfig): Sandbox;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ExecutionContext, SandboxConfig, SandboxResult } from './types';
|
|
2
|
+
export interface ISandbox {
|
|
3
|
+
execute<T = unknown>(code: string, context: ExecutionContext): Promise<SandboxResult<T>>;
|
|
4
|
+
getConfig(): SandboxConfig;
|
|
5
|
+
configure(config: Partial<SandboxConfig>): void;
|
|
6
|
+
dispose(): void;
|
|
7
|
+
}
|
|
8
|
+
export type SandboxFactory = (config?: SandboxConfig) => ISandbox;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ISandbox } from './interface';
|
|
2
|
+
import type { ExecutionContext, SandboxConfig, SandboxResult } from './types';
|
|
3
|
+
export declare class NodeWorkerSandbox implements ISandbox {
|
|
4
|
+
private config;
|
|
5
|
+
constructor(config?: SandboxConfig);
|
|
6
|
+
getConfig(): SandboxConfig;
|
|
7
|
+
configure(config: Partial<SandboxConfig>): void;
|
|
8
|
+
execute<T = unknown>(code: string, context: ExecutionContext): Promise<SandboxResult<T>>;
|
|
9
|
+
dispose(): void;
|
|
10
|
+
}
|