@elytracms/studio 0.0.43 → 0.0.44
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/components/app-views/studio-config.d.ts +12 -0
- package/dist/components/app-views/studio-config.js.map +1 -1
- package/dist/components/assets/assets-collection-view-source.d.ts +13 -1
- package/dist/components/assets/assets-collection-view-source.js +42 -51
- package/dist/components/assets/assets-collection-view-source.js.map +1 -1
- package/dist/components/cms/cms-store.js +9 -11
- package/dist/components/cms/cms-store.js.map +1 -1
- package/dist/components/cms/detail-editor.js +110 -338
- package/dist/components/cms/detail-editor.js.map +1 -1
- package/dist/components/cms/document-staging.d.ts +8 -3
- package/dist/components/cms/document-staging.js +81 -21
- package/dist/components/cms/document-staging.js.map +1 -1
- package/dist/components/cms/use-compare-selection.d.ts +27 -0
- package/dist/components/cms/use-compare-selection.js +29 -0
- package/dist/components/cms/use-compare-selection.js.map +1 -0
- package/dist/components/cms/use-discard-controller.d.ts +44 -0
- package/dist/components/cms/use-discard-controller.js +51 -0
- package/dist/components/cms/use-discard-controller.js.map +1 -0
- package/dist/components/cms/use-embed-inspector-host.d.ts +12 -0
- package/dist/components/cms/use-embed-inspector-host.js +17 -0
- package/dist/components/cms/use-embed-inspector-host.js.map +1 -0
- package/dist/components/cms/use-field-focus.d.ts +38 -0
- package/dist/components/cms/use-field-focus.js +70 -0
- package/dist/components/cms/use-field-focus.js.map +1 -0
- package/dist/components/cms/use-live-url.d.ts +41 -0
- package/dist/components/cms/use-live-url.js +55 -0
- package/dist/components/cms/use-live-url.js.map +1 -0
- package/dist/components/cms/use-publish-controller.d.ts +49 -0
- package/dist/components/cms/use-publish-controller.js +102 -0
- package/dist/components/cms/use-publish-controller.js.map +1 -0
- package/dist/components/cms/use-published-perspective.d.ts +67 -0
- package/dist/components/cms/use-published-perspective.js +67 -0
- package/dist/components/cms/use-published-perspective.js.map +1 -0
- package/dist/components/cms/use-restore-version.d.ts +57 -0
- package/dist/components/cms/use-restore-version.js +66 -0
- package/dist/components/cms/use-restore-version.js.map +1 -0
- package/dist/components/cms/use-save-mode.d.ts +35 -0
- package/dist/components/cms/use-save-mode.js +54 -0
- package/dist/components/cms/use-save-mode.js.map +1 -0
- package/dist/components/cms/version-compare.js +13 -16
- package/dist/components/cms/version-compare.js.map +1 -1
- package/dist/components/cms/versions-pane.js +21 -19
- package/dist/components/cms/versions-pane.js.map +1 -1
- package/dist/components/components/transfer-dialog.d.ts +11 -6
- package/dist/components/components/transfer-dialog.js +98 -25
- package/dist/components/components/transfer-dialog.js.map +1 -1
- package/dist/components/components/transfer.d.ts +74 -8
- package/dist/components/components/transfer.js +87 -2
- package/dist/components/components/transfer.js.map +1 -1
- package/dist/components/rail-store-status.d.ts +7 -5
- package/dist/components/rail-store-status.js +12 -9
- package/dist/components/rail-store-status.js.map +1 -1
- package/dist/components/rail-view-source.d.ts +111 -0
- package/dist/components/rail-view-source.js +139 -0
- package/dist/components/rail-view-source.js.map +1 -0
- package/dist/components/redirects/redirects-collection-view-source.d.ts +13 -1
- package/dist/components/redirects/redirects-collection-view-source.js +42 -70
- package/dist/components/redirects/redirects-collection-view-source.js.map +1 -1
- package/dist/components/section-state.d.ts +12 -3
- package/dist/components/section-state.js +8 -4
- package/dist/components/section-state.js.map +1 -1
- package/dist/components/users/users-collection-view-source.d.ts +13 -1
- package/dist/components/users/users-collection-view-source.js +60 -79
- package/dist/components/users/users-collection-view-source.js.map +1 -1
- package/dist/lib/ai/anthropic-provider.d.ts +29 -0
- package/dist/lib/ai/anthropic-provider.js +59 -0
- package/dist/lib/ai/anthropic-provider.js.map +1 -0
- package/dist/lib/ai/apply-content.d.ts +9 -0
- package/dist/lib/ai/apply-content.js +170 -0
- package/dist/lib/ai/apply-content.js.map +1 -0
- package/dist/lib/ai/config.d.ts +58 -0
- package/dist/lib/ai/config.js +134 -0
- package/dist/lib/ai/config.js.map +1 -0
- package/dist/lib/ai/fake-provider.d.ts +34 -0
- package/dist/lib/ai/fake-provider.js +31 -0
- package/dist/lib/ai/fake-provider.js.map +1 -0
- package/dist/lib/ai/index.d.ts +22 -0
- package/dist/lib/ai/index.js +23 -0
- package/dist/lib/ai/index.js.map +1 -0
- package/dist/lib/ai/knowledge.d.ts +39 -0
- package/dist/lib/ai/knowledge.js +60 -0
- package/dist/lib/ai/knowledge.js.map +1 -0
- package/dist/lib/ai/provenance.d.ts +20 -0
- package/dist/lib/ai/provenance.js +15 -0
- package/dist/lib/ai/provenance.js.map +1 -0
- package/dist/lib/ai/route.d.ts +24 -0
- package/dist/lib/ai/route.js +90 -0
- package/dist/lib/ai/route.js.map +1 -0
- package/dist/lib/ai/shape.d.ts +35 -0
- package/dist/lib/ai/shape.js +113 -0
- package/dist/lib/ai/shape.js.map +1 -0
- package/dist/lib/ai/types.d.ts +227 -0
- package/dist/lib/ai/types.js +2 -0
- package/dist/lib/ai/types.js.map +1 -0
- package/dist/lib/i18n/messages.en.js +4 -2
- package/dist/lib/i18n/messages.en.js.map +1 -1
- package/dist/lib/workspace/index.d.ts +9 -0
- package/dist/lib/workspace/index.js +11 -0
- package/dist/lib/workspace/index.js.map +1 -1
- package/package.json +8 -2
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { CollectionDef, JsonSchema } from '@elytracms/core/cms-core';
|
|
2
|
+
import type { RelationCandidates } from './types';
|
|
3
|
+
/** A relation field's resolved candidate set — how the engine maps ids → reference envelopes. */
|
|
4
|
+
export interface TargetRelationField {
|
|
5
|
+
fieldName: string;
|
|
6
|
+
/** The relation's target collection id. */
|
|
7
|
+
target: string;
|
|
8
|
+
multiple: boolean;
|
|
9
|
+
/** The allowed candidate ids (the enum the model chose from). */
|
|
10
|
+
candidateIds: string[];
|
|
11
|
+
}
|
|
12
|
+
/** The derived target shape + the metadata the engine needs to post-process the output. */
|
|
13
|
+
export interface TargetShape {
|
|
14
|
+
/** The JSON Schema the structured output must satisfy. */
|
|
15
|
+
schema: JsonSchema;
|
|
16
|
+
/** Field names removed by `ai.exclude` (untouchable). */
|
|
17
|
+
excluded: string[];
|
|
18
|
+
/** Field names whose values route under `localized[locale]`. */
|
|
19
|
+
localizedFields: string[];
|
|
20
|
+
/** Relation fields with their candidate enums. */
|
|
21
|
+
relationFields: TargetRelationField[];
|
|
22
|
+
}
|
|
23
|
+
export interface BuildTargetShapeInput {
|
|
24
|
+
collection: CollectionDef;
|
|
25
|
+
/** Relation candidate ids by field — a relation field with none is omitted from the shape. */
|
|
26
|
+
candidates?: RelationCandidates;
|
|
27
|
+
/** UI locale for resolving field labels into descriptions. */
|
|
28
|
+
locale?: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Build the leash-aware target shape from a collection + candidates. Deterministic;
|
|
32
|
+
* never throws. Reuses `collectionJsonSchema` as the base, then rewrites relations /
|
|
33
|
+
* rich text and strips excluded fields.
|
|
34
|
+
*/
|
|
35
|
+
export declare function buildTargetShape(input: BuildTargetShapeInput): TargetShape;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Target-shape derivation for `applyContentWithShape` (EC-331).
|
|
3
|
+
*
|
|
4
|
+
* Reuses the existing collection→JSON-Schema derivation (`collectionJsonSchema`,
|
|
5
|
+
* cms-core) rather than writing a new one, then applies the three leashes the AI
|
|
6
|
+
* engine adds on top:
|
|
7
|
+
*
|
|
8
|
+
* - `ai.exclude` fields are removed entirely (the model never sees them → cannot
|
|
9
|
+
* write them).
|
|
10
|
+
* - relation fields become an ENUM over the provided candidate ids (or are omitted
|
|
11
|
+
* when no candidates exist) — so a hallucinated relation id is not representable
|
|
12
|
+
* as valid structured output.
|
|
13
|
+
* - rich-text fields are emitted in the `{ version, doc }` ProseMirror storage shape
|
|
14
|
+
* (the `allow` leash on embeds is enforced later by `validateRichTextEmbeds`).
|
|
15
|
+
*
|
|
16
|
+
* Localized fields stay flat in the shape (the model authors one locale at a time);
|
|
17
|
+
* the engine routes their values under `localized[locale]`.
|
|
18
|
+
*/
|
|
19
|
+
import { collectionJsonSchema, resolveLabel } from '@elytracms/core/cms-core';
|
|
20
|
+
/** The rich-text `{ version, doc }` storage envelope, as a JSON Schema. */
|
|
21
|
+
function richTextEnvelopeSchema() {
|
|
22
|
+
return {
|
|
23
|
+
type: 'object',
|
|
24
|
+
description: 'Rich text in the ProseMirror storage shape: { version, doc: { type: "doc", content: [...] } }.',
|
|
25
|
+
properties: {
|
|
26
|
+
version: { type: 'number' },
|
|
27
|
+
doc: {
|
|
28
|
+
type: 'object',
|
|
29
|
+
properties: {
|
|
30
|
+
type: { type: 'string', const: 'doc' },
|
|
31
|
+
content: { type: 'array', items: { type: 'object' } },
|
|
32
|
+
},
|
|
33
|
+
required: ['type', 'content'],
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
required: ['version', 'doc'],
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/** A field's model-facing description: resolved label + optional `ai.hint`. */
|
|
40
|
+
function describe(field, locale) {
|
|
41
|
+
const label = resolveLabel(field.form?.label, locale) ?? field.name;
|
|
42
|
+
const hint = field.ai?.hint;
|
|
43
|
+
const parts = [label, hint].filter((p) => typeof p === 'string' && p.length > 0);
|
|
44
|
+
return parts.length > 0 ? parts.join(' — ') : undefined;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Build the leash-aware target shape from a collection + candidates. Deterministic;
|
|
48
|
+
* never throws. Reuses `collectionJsonSchema` as the base, then rewrites relations /
|
|
49
|
+
* rich text and strips excluded fields.
|
|
50
|
+
*/
|
|
51
|
+
export function buildTargetShape(input) {
|
|
52
|
+
const base = collectionJsonSchema(input.collection);
|
|
53
|
+
const properties = {
|
|
54
|
+
...(base.properties ?? {}),
|
|
55
|
+
};
|
|
56
|
+
const excluded = [];
|
|
57
|
+
const localizedFields = [];
|
|
58
|
+
const relationFields = [];
|
|
59
|
+
for (const field of input.collection.fields) {
|
|
60
|
+
if (field.localized)
|
|
61
|
+
localizedFields.push(field.name);
|
|
62
|
+
if (field.ai?.exclude) {
|
|
63
|
+
excluded.push(field.name);
|
|
64
|
+
delete properties[field.name];
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (field.type === 'relation') {
|
|
68
|
+
const candidates = input.candidates?.[field.name] ?? [];
|
|
69
|
+
if (candidates.length === 0) {
|
|
70
|
+
// No candidate ids ⇒ omit the field: the model cannot emit a relation value
|
|
71
|
+
// at all, so a hallucinated id is structurally impossible.
|
|
72
|
+
delete properties[field.name];
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
const ids = candidates.map((c) => c.id);
|
|
76
|
+
const label = candidates
|
|
77
|
+
.map((c) => (c.label ? `${c.id} (${c.label})` : c.id))
|
|
78
|
+
.join(', ');
|
|
79
|
+
const idSchema = {
|
|
80
|
+
type: 'string',
|
|
81
|
+
enum: ids,
|
|
82
|
+
description: `Choose one of: ${label}`,
|
|
83
|
+
};
|
|
84
|
+
properties[field.name] = field.multiple ? { type: 'array', items: idSchema } : idSchema;
|
|
85
|
+
relationFields.push({
|
|
86
|
+
fieldName: field.name,
|
|
87
|
+
target: field.target,
|
|
88
|
+
multiple: field.multiple,
|
|
89
|
+
candidateIds: ids,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
else if (field.type === 'richText') {
|
|
93
|
+
properties[field.name] = richTextEnvelopeSchema();
|
|
94
|
+
}
|
|
95
|
+
const prop = properties[field.name];
|
|
96
|
+
if (prop) {
|
|
97
|
+
const desc = describe(field, input.locale);
|
|
98
|
+
// Preserve an existing description (e.g. relation enum / rich-text note).
|
|
99
|
+
if (desc && typeof prop.description !== 'string')
|
|
100
|
+
prop.description = desc;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const requiredIn = Array.isArray(base.required) ? base.required : [];
|
|
104
|
+
const required = requiredIn.filter((n) => !excluded.includes(n) && properties[n] !== undefined);
|
|
105
|
+
const schema = {
|
|
106
|
+
type: 'object',
|
|
107
|
+
properties,
|
|
108
|
+
additionalProperties: false,
|
|
109
|
+
...(required.length > 0 ? { required } : {}),
|
|
110
|
+
};
|
|
111
|
+
return { schema, excluded, localizedFields, relationFields };
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=shape.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shape.js","sourceRoot":"","sources":["../../../src/lib/ai/shape.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Target-shape derivation for `applyContentWithShape` (EC-331).\n *\n * Reuses the existing collection→JSON-Schema derivation (`collectionJsonSchema`,\n * cms-core) rather than writing a new one, then applies the three leashes the AI\n * engine adds on top:\n *\n * - `ai.exclude` fields are removed entirely (the model never sees them → cannot\n * write them).\n * - relation fields become an ENUM over the provided candidate ids (or are omitted\n * when no candidates exist) — so a hallucinated relation id is not representable\n * as valid structured output.\n * - rich-text fields are emitted in the `{ version, doc }` ProseMirror storage shape\n * (the `allow` leash on embeds is enforced later by `validateRichTextEmbeds`).\n *\n * Localized fields stay flat in the shape (the model authors one locale at a time);\n * the engine routes their values under `localized[locale]`.\n */\nimport { collectionJsonSchema, resolveLabel } from '@elytracms/core/cms-core'\nimport type { CollectionDef, FieldDef, JsonSchema } from '@elytracms/core/cms-core'\nimport type { RelationCandidates } from './types'\n\n/** A relation field's resolved candidate set — how the engine maps ids → reference envelopes. */\nexport interface TargetRelationField {\n fieldName: string\n /** The relation's target collection id. */\n target: string\n multiple: boolean\n /** The allowed candidate ids (the enum the model chose from). */\n candidateIds: string[]\n}\n\n/** The derived target shape + the metadata the engine needs to post-process the output. */\nexport interface TargetShape {\n /** The JSON Schema the structured output must satisfy. */\n schema: JsonSchema\n /** Field names removed by `ai.exclude` (untouchable). */\n excluded: string[]\n /** Field names whose values route under `localized[locale]`. */\n localizedFields: string[]\n /** Relation fields with their candidate enums. */\n relationFields: TargetRelationField[]\n}\n\nexport interface BuildTargetShapeInput {\n collection: CollectionDef\n /** Relation candidate ids by field — a relation field with none is omitted from the shape. */\n candidates?: RelationCandidates\n /** UI locale for resolving field labels into descriptions. */\n locale?: string\n}\n\n/** The rich-text `{ version, doc }` storage envelope, as a JSON Schema. */\nfunction richTextEnvelopeSchema(): JsonSchema {\n return {\n type: 'object',\n description:\n 'Rich text in the ProseMirror storage shape: { version, doc: { type: \"doc\", content: [...] } }.',\n properties: {\n version: { type: 'number' },\n doc: {\n type: 'object',\n properties: {\n type: { type: 'string', const: 'doc' },\n content: { type: 'array', items: { type: 'object' } },\n },\n required: ['type', 'content'],\n },\n },\n required: ['version', 'doc'],\n }\n}\n\n/** A field's model-facing description: resolved label + optional `ai.hint`. */\nfunction describe(field: FieldDef, locale?: string): string | undefined {\n const label = resolveLabel(field.form?.label, locale) ?? field.name\n const hint = field.ai?.hint\n const parts = [label, hint].filter((p): p is string => typeof p === 'string' && p.length > 0)\n return parts.length > 0 ? parts.join(' — ') : undefined\n}\n\n/**\n * Build the leash-aware target shape from a collection + candidates. Deterministic;\n * never throws. Reuses `collectionJsonSchema` as the base, then rewrites relations /\n * rich text and strips excluded fields.\n */\nexport function buildTargetShape(input: BuildTargetShapeInput): TargetShape {\n const base = collectionJsonSchema(input.collection)\n const properties: Record<string, JsonSchema> = {\n ...((base.properties as Record<string, JsonSchema>) ?? {}),\n }\n const excluded: string[] = []\n const localizedFields: string[] = []\n const relationFields: TargetRelationField[] = []\n\n for (const field of input.collection.fields) {\n if (field.localized) localizedFields.push(field.name)\n\n if (field.ai?.exclude) {\n excluded.push(field.name)\n delete properties[field.name]\n continue\n }\n\n if (field.type === 'relation') {\n const candidates = input.candidates?.[field.name] ?? []\n if (candidates.length === 0) {\n // No candidate ids ⇒ omit the field: the model cannot emit a relation value\n // at all, so a hallucinated id is structurally impossible.\n delete properties[field.name]\n continue\n }\n const ids = candidates.map((c) => c.id)\n const label = candidates\n .map((c) => (c.label ? `${c.id} (${c.label})` : c.id))\n .join(', ')\n const idSchema: JsonSchema = {\n type: 'string',\n enum: ids,\n description: `Choose one of: ${label}`,\n }\n properties[field.name] = field.multiple ? { type: 'array', items: idSchema } : idSchema\n relationFields.push({\n fieldName: field.name,\n target: field.target,\n multiple: field.multiple,\n candidateIds: ids,\n })\n } else if (field.type === 'richText') {\n properties[field.name] = richTextEnvelopeSchema()\n }\n\n const prop = properties[field.name]\n if (prop) {\n const desc = describe(field, input.locale)\n // Preserve an existing description (e.g. relation enum / rich-text note).\n if (desc && typeof prop.description !== 'string') prop.description = desc\n }\n }\n\n const requiredIn = Array.isArray(base.required) ? (base.required as string[]) : []\n const required = requiredIn.filter((n) => !excluded.includes(n) && properties[n] !== undefined)\n\n const schema: JsonSchema = {\n type: 'object',\n properties,\n additionalProperties: false,\n ...(required.length > 0 ? { required } : {}),\n }\n return { schema, excluded, localizedFields, relationFields }\n}\n"]}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@elytracms/studio/lib/ai` — headless AI platform seam types (EC-331, phase 1).
|
|
3
|
+
*
|
|
4
|
+
* Pure types only: this module imports nothing at runtime (every import is
|
|
5
|
+
* `import type`), so referencing `StudioAiConfig` from the studio-config type does
|
|
6
|
+
* NOT pull the `ai` / `@ai-sdk/*` runtime onto the critical path. The runtime lives
|
|
7
|
+
* in the sibling modules (`config`, `anthropic-provider`, `fake-provider`,
|
|
8
|
+
* `knowledge`, `shape`, `apply-content`, `route`, `provenance`), reached only when
|
|
9
|
+
* the `ai` block is configured and the AI subsystem actually boots. The Anthropic
|
|
10
|
+
* provider additionally loads `ai`/`@ai-sdk/anthropic` via dynamic `import()`, so
|
|
11
|
+
* even importing this barrel never eagerly loads the SDK.
|
|
12
|
+
*/
|
|
13
|
+
import type { CollectionDef, CollectionRegistry, CmsValidationIssue, FieldValues, JsonSchema, ValidateDocumentOptions } from '@elytracms/core/cms-core';
|
|
14
|
+
import type { ComponentManifest } from '@elytracms/core/component-registry';
|
|
15
|
+
/**
|
|
16
|
+
* The capabilities the engine requires of a provider. Both must be `true` — the
|
|
17
|
+
* capability gate (`resolveAiConfig`) fails loud otherwise (tool-calling +
|
|
18
|
+
* structured output are the non-negotiable floor for schema-guaranteed authoring).
|
|
19
|
+
*/
|
|
20
|
+
export interface AiProviderCapabilities {
|
|
21
|
+
/** The provider can call tools (function/tool calling). */
|
|
22
|
+
toolCalling: boolean;
|
|
23
|
+
/** The provider can return schema-constrained structured output. */
|
|
24
|
+
structuredOutput: boolean;
|
|
25
|
+
}
|
|
26
|
+
/** A request for one schema-constrained structured object — the engine's core primitive. */
|
|
27
|
+
export interface GenerateStructuredRequest {
|
|
28
|
+
/** System prompt: rules, injection stance, closed-world constraints. */
|
|
29
|
+
system?: string;
|
|
30
|
+
/** The task prompt plus assembled context (knowledge, existing values, prior issues). */
|
|
31
|
+
prompt: string;
|
|
32
|
+
/** The target shape as a JSON Schema (draft 2020-12 subset) the output must satisfy. */
|
|
33
|
+
schema: JsonSchema;
|
|
34
|
+
/** A short name for the schema (some providers surface it as the tool/output name). */
|
|
35
|
+
schemaName?: string;
|
|
36
|
+
/** A one-line description of what the schema represents. */
|
|
37
|
+
schemaDescription?: string;
|
|
38
|
+
}
|
|
39
|
+
/** Token usage a provider may report (best-effort; recorded for soft spend counters). */
|
|
40
|
+
export interface AiUsage {
|
|
41
|
+
inputTokens?: number;
|
|
42
|
+
outputTokens?: number;
|
|
43
|
+
}
|
|
44
|
+
export interface GenerateStructuredResult {
|
|
45
|
+
/** The structured object the provider produced (best-effort match to the request schema). */
|
|
46
|
+
object: unknown;
|
|
47
|
+
usage?: AiUsage;
|
|
48
|
+
}
|
|
49
|
+
/** One chat message the streaming route relays to the provider. */
|
|
50
|
+
export interface AiChatMessage {
|
|
51
|
+
role: 'system' | 'user' | 'assistant';
|
|
52
|
+
content: string;
|
|
53
|
+
}
|
|
54
|
+
/** A streaming chat/completion request the server-route factory forwards to the provider. */
|
|
55
|
+
export interface StreamChatRequest {
|
|
56
|
+
system?: string;
|
|
57
|
+
messages: AiChatMessage[];
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* A provider adapter over a tool-calling, structured-output model. Anthropic in
|
|
61
|
+
* production, {@link FakeProvider} (deterministic, keyless) in tests/playground —
|
|
62
|
+
* both implement this identical surface so the whole loop runs without a key.
|
|
63
|
+
*/
|
|
64
|
+
export interface AiProvider {
|
|
65
|
+
/** Stable provider id (e.g. `anthropic`, `fake`); recorded in provenance. */
|
|
66
|
+
readonly id: string;
|
|
67
|
+
/** The model id the provider targets, when fixed; recorded in provenance. */
|
|
68
|
+
readonly model?: string;
|
|
69
|
+
/** The provider's capabilities — validated by the capability gate at config load. */
|
|
70
|
+
capabilities(): AiProviderCapabilities;
|
|
71
|
+
/** Produce a single schema-constrained structured object. */
|
|
72
|
+
generateStructured(request: GenerateStructuredRequest): Promise<GenerateStructuredResult>;
|
|
73
|
+
/**
|
|
74
|
+
* Stream a chat/completion response as a Web `Response` — the server-route body.
|
|
75
|
+
* Optional: headless callers may only use {@link generateStructured}.
|
|
76
|
+
*/
|
|
77
|
+
streamChat?(request: StreamChatRequest): Promise<Response>;
|
|
78
|
+
}
|
|
79
|
+
/** Soft, advisory limits for the route counter and the engine repair loop. */
|
|
80
|
+
export interface AiLimits {
|
|
81
|
+
/** Soft per-project ceiling on route requests per minute (advisory; route-enforced). */
|
|
82
|
+
maxRequestsPerMinute?: number;
|
|
83
|
+
/** Cap on repair attempts after the first generation (engine default when omitted). */
|
|
84
|
+
maxRepairAttempts?: number;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The `ai` block of `defineStudio({ ai })` (EC-331). Studio-only live code — like
|
|
88
|
+
* `fieldRenderers` / `i18n`, it never reaches the delivery bundle. The block's
|
|
89
|
+
* ABSENCE means the feature is fully absent (no AI UI, no AI deps on the critical
|
|
90
|
+
* path); `enabled: false` is a temporary kill-switch that keeps the config.
|
|
91
|
+
*/
|
|
92
|
+
export interface StudioAiConfig {
|
|
93
|
+
/**
|
|
94
|
+
* Temporary kill-switch. Presence of the whole `ai` block enables the feature;
|
|
95
|
+
* `enabled: false` disables it without removing the config. Omitted ⇒ enabled.
|
|
96
|
+
*/
|
|
97
|
+
enabled?: boolean;
|
|
98
|
+
/** The provider adapter (live code): Anthropic in production, FakeProvider in tests/playground. */
|
|
99
|
+
provider: AiProvider;
|
|
100
|
+
/** Project knowledge sources assembled into the model's context. */
|
|
101
|
+
knowledge?: KnowledgeSource[];
|
|
102
|
+
/** Soft rate / repair limits. */
|
|
103
|
+
limits?: AiLimits;
|
|
104
|
+
}
|
|
105
|
+
/** An inline knowledge literal (markdown prose versioned with the repo). */
|
|
106
|
+
export interface KnowledgeItem {
|
|
107
|
+
title?: string;
|
|
108
|
+
content: string;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* A `fromCollection('<id>')` source: studio-editable knowledge whose *content* is
|
|
112
|
+
* prose held in the DB (data), while the mechanism stays code. Resolved by a
|
|
113
|
+
* caller-supplied collection reader (starter-kit / studio wires it to operations).
|
|
114
|
+
*/
|
|
115
|
+
export interface KnowledgeCollectionSource {
|
|
116
|
+
readonly kind: 'collection';
|
|
117
|
+
/** Collection id whose documents supply editable knowledge prose. */
|
|
118
|
+
readonly collectionId: string;
|
|
119
|
+
/** Field carrying the prose; when omitted the reader picks the collection's title/first text field. */
|
|
120
|
+
readonly field?: string;
|
|
121
|
+
}
|
|
122
|
+
/** An async (or sync) function producing knowledge items at resolve time. */
|
|
123
|
+
export type KnowledgeFn = () => KnowledgeItem | KnowledgeItem[] | Promise<KnowledgeItem | KnowledgeItem[]>;
|
|
124
|
+
/** A knowledge source: inline literal, async function, or `fromCollection(...)` sugar. */
|
|
125
|
+
export type KnowledgeSource = KnowledgeItem | KnowledgeFn | KnowledgeCollectionSource;
|
|
126
|
+
/** The assembled knowledge context — a flat, deterministic list of resolved items. */
|
|
127
|
+
export interface ResolvedKnowledge {
|
|
128
|
+
items: KnowledgeItem[];
|
|
129
|
+
}
|
|
130
|
+
/** Reads knowledge prose out of a `fromCollection` source (wired to the operations layer). */
|
|
131
|
+
export type KnowledgeCollectionReader = (source: KnowledgeCollectionSource) => KnowledgeItem[] | Promise<KnowledgeItem[]>;
|
|
132
|
+
/**
|
|
133
|
+
* A relation candidate id (from a `searchDocuments`/tool result). Relation fields may
|
|
134
|
+
* ONLY use ids present here — the target shape emits them as an enum, so a
|
|
135
|
+
* hallucinated id is not even representable as valid structured output.
|
|
136
|
+
*/
|
|
137
|
+
export interface RelationCandidate {
|
|
138
|
+
id: string;
|
|
139
|
+
/** Human label (title) for the model to choose against. */
|
|
140
|
+
label?: string;
|
|
141
|
+
}
|
|
142
|
+
/** Relation candidate ids keyed by field name. A field absent here is omitted from the shape. */
|
|
143
|
+
export type RelationCandidates = Record<string, RelationCandidate[]>;
|
|
144
|
+
/** The typed draft-values patch the engine emits — applied by the draft-write path. */
|
|
145
|
+
export interface ContentPatch {
|
|
146
|
+
collection: string;
|
|
147
|
+
id: string;
|
|
148
|
+
/** Non-localized field values. */
|
|
149
|
+
values: FieldValues;
|
|
150
|
+
/** Per-locale field values (localized fields, under the target locale). */
|
|
151
|
+
localized?: Record<string, FieldValues>;
|
|
152
|
+
}
|
|
153
|
+
/** Origin marker the draft-write path records so an AI-authored draft is attributable. */
|
|
154
|
+
export interface AiProvenance {
|
|
155
|
+
origin: 'assistant';
|
|
156
|
+
/** Provider id (e.g. `anthropic`, `fake`). */
|
|
157
|
+
provider: string;
|
|
158
|
+
/** Model id, when the provider fixes one. */
|
|
159
|
+
model?: string;
|
|
160
|
+
/** ISO timestamp when the content was generated. */
|
|
161
|
+
at: string;
|
|
162
|
+
}
|
|
163
|
+
export interface ApplyContentInput {
|
|
164
|
+
provider: AiProvider;
|
|
165
|
+
registry: CollectionRegistry;
|
|
166
|
+
collection: CollectionDef;
|
|
167
|
+
/** Document id the patch targets (an existing doc for updates, or a fresh id for creation). */
|
|
168
|
+
documentId: string;
|
|
169
|
+
/** The author's instruction / prompt. */
|
|
170
|
+
instruction: string;
|
|
171
|
+
/** Assembled knowledge context (from `resolveKnowledge`). */
|
|
172
|
+
knowledge?: ResolvedKnowledge;
|
|
173
|
+
/** Component manifests — context for `allow`-leashed richText/blocks fields. */
|
|
174
|
+
components?: readonly ComponentManifest[];
|
|
175
|
+
/** Relation candidate ids by field — the ONLY ids a relation field may use. */
|
|
176
|
+
candidates?: RelationCandidates;
|
|
177
|
+
/** Existing draft values (updates): the model output merges over these before validation. */
|
|
178
|
+
existing?: FieldValues;
|
|
179
|
+
/** Target locale for localized fields (their values land under `localized[locale]`). */
|
|
180
|
+
locale?: string;
|
|
181
|
+
/** Injected rich-text embed leash (studio-wired); enforced when present. */
|
|
182
|
+
validateRichTextEmbeds?: ValidateDocumentOptions['validateRichTextEmbeds'];
|
|
183
|
+
/** Injected blocks composition leash (studio-wired); enforced when present. */
|
|
184
|
+
validateComposition?: ValidateDocumentOptions['validateComposition'];
|
|
185
|
+
/** Document store for relation/asset existence checks (optional). */
|
|
186
|
+
store?: ValidateDocumentOptions['store'];
|
|
187
|
+
/** Max repair attempts after the first generation (default {@link DEFAULT_MAX_REPAIR_ATTEMPTS}). */
|
|
188
|
+
maxRepairAttempts?: number;
|
|
189
|
+
}
|
|
190
|
+
/** The engine's outcome: a valid, leash-compliant patch, or the blocking issues. */
|
|
191
|
+
export type ApplyContentResult = {
|
|
192
|
+
ok: true;
|
|
193
|
+
patch: ContentPatch;
|
|
194
|
+
provenance: AiProvenance;
|
|
195
|
+
/** Total generation attempts made (1 = happy path, >1 = repaired). */
|
|
196
|
+
attempts: number;
|
|
197
|
+
/** Non-error issues surfaced by the accepted document (e.g. warnings). */
|
|
198
|
+
issues: CmsValidationIssue[];
|
|
199
|
+
} | {
|
|
200
|
+
ok: false;
|
|
201
|
+
/** The error-severity issues from the final failed attempt. */
|
|
202
|
+
issues: CmsValidationIssue[];
|
|
203
|
+
attempts: number;
|
|
204
|
+
provenance: AiProvenance;
|
|
205
|
+
};
|
|
206
|
+
/** A validated studio session, from the host-wired `getSession`. Opaque beyond `userId`. */
|
|
207
|
+
export interface AiSession {
|
|
208
|
+
/** Authenticated principal id. */
|
|
209
|
+
userId: string;
|
|
210
|
+
[key: string]: unknown;
|
|
211
|
+
}
|
|
212
|
+
export interface AiRouteHandlerOptions {
|
|
213
|
+
/** The provider (holds the host-env key). The route is a thin proxy over it. */
|
|
214
|
+
provider: AiProvider;
|
|
215
|
+
/**
|
|
216
|
+
* Server-side session check (reject anonymous callers). Returns a session for an
|
|
217
|
+
* authenticated caller, `null` otherwise. Wired by the host to its own auth — the
|
|
218
|
+
* route consumes it, never re-implements it.
|
|
219
|
+
*/
|
|
220
|
+
getSession: (request: Request) => AiSession | null | Promise<AiSession | null>;
|
|
221
|
+
/** Soft per-project rate limit (advisory spend guard). */
|
|
222
|
+
limits?: AiLimits;
|
|
223
|
+
/** Derive the per-project rate-bucket key from a request (default: one global bucket). */
|
|
224
|
+
projectKey?: (request: Request) => string;
|
|
225
|
+
}
|
|
226
|
+
/** The mounted route handler: a framework-agnostic Web `Request → Response`. */
|
|
227
|
+
export type AiRouteHandler = (request: Request) => Promise<Response>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/lib/ai/types.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * `@elytracms/studio/lib/ai` — headless AI platform seam types (EC-331, phase 1).\n *\n * Pure types only: this module imports nothing at runtime (every import is\n * `import type`), so referencing `StudioAiConfig` from the studio-config type does\n * NOT pull the `ai` / `@ai-sdk/*` runtime onto the critical path. The runtime lives\n * in the sibling modules (`config`, `anthropic-provider`, `fake-provider`,\n * `knowledge`, `shape`, `apply-content`, `route`, `provenance`), reached only when\n * the `ai` block is configured and the AI subsystem actually boots. The Anthropic\n * provider additionally loads `ai`/`@ai-sdk/anthropic` via dynamic `import()`, so\n * even importing this barrel never eagerly loads the SDK.\n */\nimport type {\n CollectionDef,\n CollectionRegistry,\n CmsValidationIssue,\n FieldValues,\n JsonSchema,\n ValidateDocumentOptions,\n} from '@elytracms/core/cms-core'\nimport type { ComponentManifest } from '@elytracms/core/component-registry'\n\n// --- Provider seam ----------------------------------------------------------------\n\n/**\n * The capabilities the engine requires of a provider. Both must be `true` — the\n * capability gate (`resolveAiConfig`) fails loud otherwise (tool-calling +\n * structured output are the non-negotiable floor for schema-guaranteed authoring).\n */\nexport interface AiProviderCapabilities {\n /** The provider can call tools (function/tool calling). */\n toolCalling: boolean\n /** The provider can return schema-constrained structured output. */\n structuredOutput: boolean\n}\n\n/** A request for one schema-constrained structured object — the engine's core primitive. */\nexport interface GenerateStructuredRequest {\n /** System prompt: rules, injection stance, closed-world constraints. */\n system?: string\n /** The task prompt plus assembled context (knowledge, existing values, prior issues). */\n prompt: string\n /** The target shape as a JSON Schema (draft 2020-12 subset) the output must satisfy. */\n schema: JsonSchema\n /** A short name for the schema (some providers surface it as the tool/output name). */\n schemaName?: string\n /** A one-line description of what the schema represents. */\n schemaDescription?: string\n}\n\n/** Token usage a provider may report (best-effort; recorded for soft spend counters). */\nexport interface AiUsage {\n inputTokens?: number\n outputTokens?: number\n}\n\nexport interface GenerateStructuredResult {\n /** The structured object the provider produced (best-effort match to the request schema). */\n object: unknown\n usage?: AiUsage\n}\n\n/** One chat message the streaming route relays to the provider. */\nexport interface AiChatMessage {\n role: 'system' | 'user' | 'assistant'\n content: string\n}\n\n/** A streaming chat/completion request the server-route factory forwards to the provider. */\nexport interface StreamChatRequest {\n system?: string\n messages: AiChatMessage[]\n}\n\n/**\n * A provider adapter over a tool-calling, structured-output model. Anthropic in\n * production, {@link FakeProvider} (deterministic, keyless) in tests/playground —\n * both implement this identical surface so the whole loop runs without a key.\n */\nexport interface AiProvider {\n /** Stable provider id (e.g. `anthropic`, `fake`); recorded in provenance. */\n readonly id: string\n /** The model id the provider targets, when fixed; recorded in provenance. */\n readonly model?: string\n /** The provider's capabilities — validated by the capability gate at config load. */\n capabilities(): AiProviderCapabilities\n /** Produce a single schema-constrained structured object. */\n generateStructured(request: GenerateStructuredRequest): Promise<GenerateStructuredResult>\n /**\n * Stream a chat/completion response as a Web `Response` — the server-route body.\n * Optional: headless callers may only use {@link generateStructured}.\n */\n streamChat?(request: StreamChatRequest): Promise<Response>\n}\n\n// --- Config seam ------------------------------------------------------------------\n\n/** Soft, advisory limits for the route counter and the engine repair loop. */\nexport interface AiLimits {\n /** Soft per-project ceiling on route requests per minute (advisory; route-enforced). */\n maxRequestsPerMinute?: number\n /** Cap on repair attempts after the first generation (engine default when omitted). */\n maxRepairAttempts?: number\n}\n\n/**\n * The `ai` block of `defineStudio({ ai })` (EC-331). Studio-only live code — like\n * `fieldRenderers` / `i18n`, it never reaches the delivery bundle. The block's\n * ABSENCE means the feature is fully absent (no AI UI, no AI deps on the critical\n * path); `enabled: false` is a temporary kill-switch that keeps the config.\n */\nexport interface StudioAiConfig {\n /**\n * Temporary kill-switch. Presence of the whole `ai` block enables the feature;\n * `enabled: false` disables it without removing the config. Omitted ⇒ enabled.\n */\n enabled?: boolean\n /** The provider adapter (live code): Anthropic in production, FakeProvider in tests/playground. */\n provider: AiProvider\n /** Project knowledge sources assembled into the model's context. */\n knowledge?: KnowledgeSource[]\n /** Soft rate / repair limits. */\n limits?: AiLimits\n}\n\n// --- Knowledge seam ---------------------------------------------------------------\n\n/** An inline knowledge literal (markdown prose versioned with the repo). */\nexport interface KnowledgeItem {\n title?: string\n content: string\n}\n\n/**\n * A `fromCollection('<id>')` source: studio-editable knowledge whose *content* is\n * prose held in the DB (data), while the mechanism stays code. Resolved by a\n * caller-supplied collection reader (starter-kit / studio wires it to operations).\n */\nexport interface KnowledgeCollectionSource {\n readonly kind: 'collection'\n /** Collection id whose documents supply editable knowledge prose. */\n readonly collectionId: string\n /** Field carrying the prose; when omitted the reader picks the collection's title/first text field. */\n readonly field?: string\n}\n\n/** An async (or sync) function producing knowledge items at resolve time. */\nexport type KnowledgeFn = () =>\n | KnowledgeItem\n | KnowledgeItem[]\n | Promise<KnowledgeItem | KnowledgeItem[]>\n\n/** A knowledge source: inline literal, async function, or `fromCollection(...)` sugar. */\nexport type KnowledgeSource = KnowledgeItem | KnowledgeFn | KnowledgeCollectionSource\n\n/** The assembled knowledge context — a flat, deterministic list of resolved items. */\nexport interface ResolvedKnowledge {\n items: KnowledgeItem[]\n}\n\n/** Reads knowledge prose out of a `fromCollection` source (wired to the operations layer). */\nexport type KnowledgeCollectionReader = (\n source: KnowledgeCollectionSource,\n) => KnowledgeItem[] | Promise<KnowledgeItem[]>\n\n// --- Engine seam ------------------------------------------------------------------\n\n/**\n * A relation candidate id (from a `searchDocuments`/tool result). Relation fields may\n * ONLY use ids present here — the target shape emits them as an enum, so a\n * hallucinated id is not even representable as valid structured output.\n */\nexport interface RelationCandidate {\n id: string\n /** Human label (title) for the model to choose against. */\n label?: string\n}\n\n/** Relation candidate ids keyed by field name. A field absent here is omitted from the shape. */\nexport type RelationCandidates = Record<string, RelationCandidate[]>\n\n/** The typed draft-values patch the engine emits — applied by the draft-write path. */\nexport interface ContentPatch {\n collection: string\n id: string\n /** Non-localized field values. */\n values: FieldValues\n /** Per-locale field values (localized fields, under the target locale). */\n localized?: Record<string, FieldValues>\n}\n\n/** Origin marker the draft-write path records so an AI-authored draft is attributable. */\nexport interface AiProvenance {\n origin: 'assistant'\n /** Provider id (e.g. `anthropic`, `fake`). */\n provider: string\n /** Model id, when the provider fixes one. */\n model?: string\n /** ISO timestamp when the content was generated. */\n at: string\n}\n\nexport interface ApplyContentInput {\n provider: AiProvider\n registry: CollectionRegistry\n collection: CollectionDef\n /** Document id the patch targets (an existing doc for updates, or a fresh id for creation). */\n documentId: string\n /** The author's instruction / prompt. */\n instruction: string\n /** Assembled knowledge context (from `resolveKnowledge`). */\n knowledge?: ResolvedKnowledge\n /** Component manifests — context for `allow`-leashed richText/blocks fields. */\n components?: readonly ComponentManifest[]\n /** Relation candidate ids by field — the ONLY ids a relation field may use. */\n candidates?: RelationCandidates\n /** Existing draft values (updates): the model output merges over these before validation. */\n existing?: FieldValues\n /** Target locale for localized fields (their values land under `localized[locale]`). */\n locale?: string\n /** Injected rich-text embed leash (studio-wired); enforced when present. */\n validateRichTextEmbeds?: ValidateDocumentOptions['validateRichTextEmbeds']\n /** Injected blocks composition leash (studio-wired); enforced when present. */\n validateComposition?: ValidateDocumentOptions['validateComposition']\n /** Document store for relation/asset existence checks (optional). */\n store?: ValidateDocumentOptions['store']\n /** Max repair attempts after the first generation (default {@link DEFAULT_MAX_REPAIR_ATTEMPTS}). */\n maxRepairAttempts?: number\n}\n\n/** The engine's outcome: a valid, leash-compliant patch, or the blocking issues. */\nexport type ApplyContentResult =\n | {\n ok: true\n patch: ContentPatch\n provenance: AiProvenance\n /** Total generation attempts made (1 = happy path, >1 = repaired). */\n attempts: number\n /** Non-error issues surfaced by the accepted document (e.g. warnings). */\n issues: CmsValidationIssue[]\n }\n | {\n ok: false\n /** The error-severity issues from the final failed attempt. */\n issues: CmsValidationIssue[]\n attempts: number\n provenance: AiProvenance\n }\n\n// --- Route seam -------------------------------------------------------------------\n\n/** A validated studio session, from the host-wired `getSession`. Opaque beyond `userId`. */\nexport interface AiSession {\n /** Authenticated principal id. */\n userId: string\n [key: string]: unknown\n}\n\nexport interface AiRouteHandlerOptions {\n /** The provider (holds the host-env key). The route is a thin proxy over it. */\n provider: AiProvider\n /**\n * Server-side session check (reject anonymous callers). Returns a session for an\n * authenticated caller, `null` otherwise. Wired by the host to its own auth — the\n * route consumes it, never re-implements it.\n */\n getSession: (request: Request) => AiSession | null | Promise<AiSession | null>\n /** Soft per-project rate limit (advisory spend guard). */\n limits?: AiLimits\n /** Derive the per-project rate-bucket key from a request (default: one global bucket). */\n projectKey?: (request: Request) => string\n}\n\n/** The mounted route handler: a framework-agnostic Web `Request → Response`. */\nexport type AiRouteHandler = (request: Request) => Promise<Response>\n"]}
|
|
@@ -578,9 +578,11 @@ export const EN_MESSAGES = {
|
|
|
578
578
|
'components.preview.propNeedsDocument': msg('Editable in a document — no workspace context in the component preview.', 'Hint under an asset/relation/object prop control in the read-only component preview.'),
|
|
579
579
|
'components.transfer.action': msg('Use in document…'),
|
|
580
580
|
'components.transfer.title': msg('Use “{name}” in a document'),
|
|
581
|
-
'components.transfer.description': msg('Create a new draft
|
|
581
|
+
'components.transfer.description': msg('Create a new draft, or add to an existing document’s blocks field, then open it.'),
|
|
582
582
|
'components.transfer.intoField': msg('into'),
|
|
583
|
-
'components.transfer.
|
|
583
|
+
'components.transfer.newDocument': msg('New {collection} document'),
|
|
584
|
+
'components.transfer.orExisting': msg('or add to an existing document'),
|
|
585
|
+
'components.transfer.noTargets': msg('No document field can receive this component.'),
|
|
584
586
|
'components.fieldInspector.props': msg('Props'),
|
|
585
587
|
'components.fieldInspector.noProps': msg('This component declares no editable props.'),
|
|
586
588
|
'components.fieldInspector.slots': msg('Slots'),
|