@blinkk/root-cms 3.0.1-beta.2 → 3.0.1-beta.4
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/app.js +3 -2
- package/dist/{chunk-F4SODS5S.js → chunk-2BSW7SIH.js} +295 -4
- package/dist/chunk-BGTUWIV6.js +187 -0
- package/dist/{chunk-TRM4MQHU.js → chunk-C245C557.js} +14 -192
- package/dist/{chunk-ATPWU3CU.js → chunk-MVS2NLZM.js} +307 -19
- package/dist/cli.js +1 -2
- package/dist/client.js +1 -2
- package/dist/core.js +1 -2
- package/dist/functions.js +3 -3
- package/dist/plugin.js +32 -12
- package/dist/ui/ui.css +1 -1
- package/dist/ui/ui.js +194 -162
- package/package.json +3 -3
- package/dist/chunk-CRK7N6RR.js +0 -298
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SearchIndexService
|
|
3
|
+
} from "./chunk-C245C557.js";
|
|
4
|
+
import {
|
|
5
|
+
normalizeSlug
|
|
6
|
+
} from "./chunk-2BSW7SIH.js";
|
|
7
|
+
|
|
1
8
|
// core/ai.ts
|
|
2
9
|
import crypto from "crypto";
|
|
3
10
|
import { promises as fs } from "fs";
|
|
@@ -18,6 +25,40 @@ import { Timestamp } from "firebase-admin/firestore";
|
|
|
18
25
|
// core/ai-tools.ts
|
|
19
26
|
import { tool } from "ai";
|
|
20
27
|
import { z } from "zod";
|
|
28
|
+
|
|
29
|
+
// shared/marshal.ts
|
|
30
|
+
function isArrayObject(data) {
|
|
31
|
+
return data !== null && typeof data === "object" && !Array.isArray(data) && Array.isArray(data._array);
|
|
32
|
+
}
|
|
33
|
+
function unmarshalData(data, options) {
|
|
34
|
+
if (data === null || data === void 0) {
|
|
35
|
+
return data;
|
|
36
|
+
}
|
|
37
|
+
if (typeof data !== "object") {
|
|
38
|
+
return data;
|
|
39
|
+
}
|
|
40
|
+
if (Array.isArray(data)) {
|
|
41
|
+
return data.map((item) => unmarshalData(item, options));
|
|
42
|
+
}
|
|
43
|
+
if (options?.isTimestamp?.(data)) {
|
|
44
|
+
return options.timestampToValue ? options.timestampToValue(data) : data;
|
|
45
|
+
}
|
|
46
|
+
if (typeof data.toMillis === "function") {
|
|
47
|
+
return data.toMillis();
|
|
48
|
+
}
|
|
49
|
+
if (isArrayObject(data)) {
|
|
50
|
+
return data._array.map(
|
|
51
|
+
(key) => unmarshalData(data[key] ?? {}, options)
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
const out = {};
|
|
55
|
+
for (const [k, v] of Object.entries(data)) {
|
|
56
|
+
out[k] = unmarshalData(v, options);
|
|
57
|
+
}
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// core/ai-tools.ts
|
|
21
62
|
var READ_ONLY_CMS_TOOL_NAMES = [
|
|
22
63
|
"collections_list",
|
|
23
64
|
"docs_list",
|
|
@@ -27,8 +68,8 @@ var READ_ONLY_CMS_TOOL_NAMES = [
|
|
|
27
68
|
"doc_listVersions",
|
|
28
69
|
"schema_get"
|
|
29
70
|
];
|
|
30
|
-
function createReadOnlyCmsTools() {
|
|
31
|
-
const all = createCmsTools();
|
|
71
|
+
function createReadOnlyCmsTools(ctx) {
|
|
72
|
+
const all = createCmsTools(ctx);
|
|
32
73
|
const out = {};
|
|
33
74
|
for (const name of READ_ONLY_CMS_TOOL_NAMES) {
|
|
34
75
|
if (all[name]) {
|
|
@@ -37,11 +78,21 @@ function createReadOnlyCmsTools() {
|
|
|
37
78
|
}
|
|
38
79
|
return out;
|
|
39
80
|
}
|
|
40
|
-
function createCmsTools() {
|
|
81
|
+
function createCmsTools(ctx) {
|
|
41
82
|
return {
|
|
42
83
|
collections_list: tool({
|
|
43
84
|
description: "List all CMS collections defined in the project. Returns each collection id along with optional name/description metadata.",
|
|
44
|
-
inputSchema: z.object({})
|
|
85
|
+
inputSchema: z.object({}),
|
|
86
|
+
execute: async () => {
|
|
87
|
+
const collections = await ctx.loadAllCollections();
|
|
88
|
+
return {
|
|
89
|
+
collections: Object.entries(collections).map(([id, meta]) => ({
|
|
90
|
+
id,
|
|
91
|
+
name: meta.name,
|
|
92
|
+
description: meta.description
|
|
93
|
+
}))
|
|
94
|
+
};
|
|
95
|
+
}
|
|
45
96
|
}),
|
|
46
97
|
docs_list: tool({
|
|
47
98
|
description: "List documents inside a CMS collection. Returns up to `limit` docs (default 25, max 100). Use this to explore content before reading individual docs.",
|
|
@@ -49,14 +100,32 @@ function createCmsTools() {
|
|
|
49
100
|
collectionId: z.string().describe('Collection id, e.g. "Pages" or "BlogPosts".'),
|
|
50
101
|
mode: z.enum(["draft", "published"]).default("draft").describe("Whether to read draft or published versions."),
|
|
51
102
|
limit: z.number().int().min(1).max(100).default(25)
|
|
52
|
-
})
|
|
103
|
+
}),
|
|
104
|
+
execute: async ({ collectionId, mode = "draft", limit = 25 }) => {
|
|
105
|
+
const max = clampInt(limit, 1, 100, 25);
|
|
106
|
+
const result = await ctx.cmsClient.listDocs(collectionId, {
|
|
107
|
+
mode,
|
|
108
|
+
limit: max
|
|
109
|
+
});
|
|
110
|
+
return {
|
|
111
|
+
docs: result.docs.map((d) => ({
|
|
112
|
+
id: d.id,
|
|
113
|
+
slug: d.slug,
|
|
114
|
+
sys: unmarshalSys(d.sys)
|
|
115
|
+
}))
|
|
116
|
+
};
|
|
117
|
+
}
|
|
53
118
|
}),
|
|
54
119
|
docs_search: tool({
|
|
55
120
|
description: "Run a full-text search across all indexed CMS docs. Returns the top matching doc ids ordered by relevance.",
|
|
56
121
|
inputSchema: z.object({
|
|
57
122
|
query: z.string().min(1),
|
|
58
123
|
limit: z.number().int().min(1).max(50).default(10)
|
|
59
|
-
})
|
|
124
|
+
}),
|
|
125
|
+
execute: async ({ query, limit = 10 }) => {
|
|
126
|
+
const max = clampInt(limit, 1, 50, 10);
|
|
127
|
+
return await ctx.search(query, { limit: max });
|
|
128
|
+
}
|
|
60
129
|
}),
|
|
61
130
|
doc_get: tool({
|
|
62
131
|
description: "Read a single CMS document. Returns the doc fields plus system metadata. Use this when you need the full content of a doc.",
|
|
@@ -65,7 +134,24 @@ function createCmsTools() {
|
|
|
65
134
|
'Full doc id in the form "Collection/slug" (e.g. "Pages/home").'
|
|
66
135
|
),
|
|
67
136
|
mode: z.enum(["draft", "published"]).default("draft")
|
|
68
|
-
})
|
|
137
|
+
}),
|
|
138
|
+
execute: async ({ docId, mode = "draft" }) => {
|
|
139
|
+
const { collection, slug } = parseDocId(docId);
|
|
140
|
+
const raw = await ctx.cmsClient.getRawDoc(collection, slug, { mode });
|
|
141
|
+
if (!raw) {
|
|
142
|
+
return { found: false };
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
found: true,
|
|
146
|
+
doc: {
|
|
147
|
+
id: raw.id,
|
|
148
|
+
collection: raw.collection,
|
|
149
|
+
slug: raw.slug,
|
|
150
|
+
sys: unmarshalSys(raw.sys),
|
|
151
|
+
fields: unmarshalFields(raw.fields)
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
}
|
|
69
155
|
}),
|
|
70
156
|
doc_getVersion: tool({
|
|
71
157
|
description: 'Read a specific version of a CMS document. Use versionId "draft" or "published" for the current draft/published state, or a numeric timestamp for a historical version (from `doc_listVersions`).',
|
|
@@ -76,7 +162,33 @@ function createCmsTools() {
|
|
|
76
162
|
versionId: z.string().describe(
|
|
77
163
|
'Version identifier: "draft", "published", or a numeric timestamp.'
|
|
78
164
|
)
|
|
79
|
-
})
|
|
165
|
+
}),
|
|
166
|
+
execute: async ({ docId, versionId }) => {
|
|
167
|
+
const { collection, slug } = parseDocId(docId);
|
|
168
|
+
let path2;
|
|
169
|
+
if (versionId === "draft") {
|
|
170
|
+
path2 = `Projects/${ctx.cmsClient.projectId}/Collections/${collection}/Drafts/${slug}`;
|
|
171
|
+
} else if (versionId === "published") {
|
|
172
|
+
path2 = `Projects/${ctx.cmsClient.projectId}/Collections/${collection}/Published/${slug}`;
|
|
173
|
+
} else {
|
|
174
|
+
path2 = `Projects/${ctx.cmsClient.projectId}/Collections/${collection}/Drafts/${slug}/Versions/${versionId}`;
|
|
175
|
+
}
|
|
176
|
+
const snap = await ctx.cmsClient.db.doc(path2).get();
|
|
177
|
+
if (!snap.exists) {
|
|
178
|
+
return { found: false };
|
|
179
|
+
}
|
|
180
|
+
const raw = snap.data();
|
|
181
|
+
return {
|
|
182
|
+
found: true,
|
|
183
|
+
doc: {
|
|
184
|
+
id: raw.id,
|
|
185
|
+
collection: raw.collection,
|
|
186
|
+
slug: raw.slug,
|
|
187
|
+
sys: unmarshalSys(raw.sys),
|
|
188
|
+
fields: unmarshalFields(raw.fields)
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
}
|
|
80
192
|
}),
|
|
81
193
|
doc_set: tool({
|
|
82
194
|
description: "Replace the entire draft fields payload of a CMS document. Pass the full JSON object that should become the new draft contents \u2014 any fields omitted will be removed. The payload is validated against the collection schema and the call is rejected on validation errors. Prefer `doc_updateField` for targeted edits. Only writes the draft version; users must publish separately. Always confirm with the user before calling.",
|
|
@@ -99,10 +211,12 @@ function createCmsTools() {
|
|
|
99
211
|
})
|
|
100
212
|
}),
|
|
101
213
|
doc_updateField: tool({
|
|
102
|
-
description: 'Update a single field on a draft CMS document by JSON path. Use dotted paths (e.g. "hero.title") and array indices (e.g. "
|
|
214
|
+
description: 'Update a single field on a draft CMS document by JSON path. Paths are relative to the doc fields object; do not prefix them with "fields.". Use dotted paths (e.g. "hero.title") and zero-based array indices. For example, update the first module title with path "content.modules.0.title". To append or remove array items, first read the current doc, then set the whole array path (e.g. "content.modules") to the updated array. The value is validated against the field schema and the call is rejected if the shape is wrong (e.g. passing a string to a richtext field). Only updates the draft version; users must publish separately. Always confirm with the user before calling.',
|
|
103
215
|
inputSchema: z.object({
|
|
104
216
|
docId: z.string(),
|
|
105
|
-
path: z.string().describe(
|
|
217
|
+
path: z.string().describe(
|
|
218
|
+
'Dotted JSON path within the fields object, e.g. "content.modules.0.title".'
|
|
219
|
+
),
|
|
106
220
|
value: z.any().describe("JSON value to set at the path.")
|
|
107
221
|
})
|
|
108
222
|
}),
|
|
@@ -126,7 +240,24 @@ function createCmsTools() {
|
|
|
126
240
|
'Full doc id in the form "Collection/slug" (e.g. "Pages/home").'
|
|
127
241
|
),
|
|
128
242
|
limit: z.number().int().min(1).max(50).default(10)
|
|
129
|
-
})
|
|
243
|
+
}),
|
|
244
|
+
execute: async ({ docId, limit = 10 }) => {
|
|
245
|
+
const { collection, slug } = parseDocId(docId);
|
|
246
|
+
const max = clampInt(limit, 1, 50, 10);
|
|
247
|
+
const path2 = `Projects/${ctx.cmsClient.projectId}/Collections/${collection}/Drafts/${slug}/Versions`;
|
|
248
|
+
const snap = await ctx.cmsClient.db.collection(path2).orderBy("sys.modifiedAt", "desc").limit(max).get();
|
|
249
|
+
return {
|
|
250
|
+
versions: snap.docs.map((d) => {
|
|
251
|
+
const data = d.data();
|
|
252
|
+
return {
|
|
253
|
+
versionId: d.id,
|
|
254
|
+
sys: unmarshalSys(data.sys),
|
|
255
|
+
tags: data.tags || [],
|
|
256
|
+
publishMessage: data.publishMessage
|
|
257
|
+
};
|
|
258
|
+
})
|
|
259
|
+
};
|
|
260
|
+
}
|
|
130
261
|
}),
|
|
131
262
|
doc_translateField: tool({
|
|
132
263
|
description: "Translate a text value into one or more target locales using AI. Returns the translated strings keyed by locale. Use this to help with localization workflows.",
|
|
@@ -144,10 +275,63 @@ function createCmsTools() {
|
|
|
144
275
|
description: "Get the field schema for a CMS collection. Returns the full field definitions including types, labels, and validation rules. Use this to understand what fields a collection supports before creating or editing docs.",
|
|
145
276
|
inputSchema: z.object({
|
|
146
277
|
collectionId: z.string().describe('Collection id, e.g. "Pages" or "BlogPosts".')
|
|
147
|
-
})
|
|
278
|
+
}),
|
|
279
|
+
execute: async ({ collectionId }) => {
|
|
280
|
+
const schema = await ctx.loadCollection(collectionId);
|
|
281
|
+
if (!schema) {
|
|
282
|
+
return { found: false, collectionId };
|
|
283
|
+
}
|
|
284
|
+
return {
|
|
285
|
+
found: true,
|
|
286
|
+
collectionId,
|
|
287
|
+
fields: simplifyFields(schema.fields || [])
|
|
288
|
+
};
|
|
289
|
+
}
|
|
148
290
|
})
|
|
149
291
|
};
|
|
150
292
|
}
|
|
293
|
+
function parseDocId(docId) {
|
|
294
|
+
const idx = docId.indexOf("/");
|
|
295
|
+
if (idx <= 0 || idx === docId.length - 1) {
|
|
296
|
+
throw new Error(`invalid docId: "${docId}" (expected "Collection/slug")`);
|
|
297
|
+
}
|
|
298
|
+
return {
|
|
299
|
+
collection: docId.slice(0, idx),
|
|
300
|
+
slug: normalizeSlug(docId.slice(idx + 1))
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
function clampInt(value, min, max, fallback) {
|
|
304
|
+
const n = Number.isFinite(value) ? Math.trunc(value) : fallback;
|
|
305
|
+
return Math.min(Math.max(n, min), max);
|
|
306
|
+
}
|
|
307
|
+
function unmarshalSys(sys) {
|
|
308
|
+
return unmarshalData(sys ?? {});
|
|
309
|
+
}
|
|
310
|
+
function unmarshalFields(fields) {
|
|
311
|
+
return unmarshalData(fields ?? {});
|
|
312
|
+
}
|
|
313
|
+
function simplifyFields(fields) {
|
|
314
|
+
return fields.map((f) => {
|
|
315
|
+
const out = { id: f.id, type: f.type };
|
|
316
|
+
if (f.label) out.label = f.label;
|
|
317
|
+
if (f.description) out.description = f.description;
|
|
318
|
+
if (f.required) out.required = true;
|
|
319
|
+
if (f.translate) out.translate = true;
|
|
320
|
+
if (f.options) out.options = f.options;
|
|
321
|
+
if (f.type === "object" && f.fields) {
|
|
322
|
+
out.fields = simplifyFields(f.fields);
|
|
323
|
+
}
|
|
324
|
+
if (f.type === "array" && f.of) {
|
|
325
|
+
out.of = simplifyFields([f.of])[0];
|
|
326
|
+
}
|
|
327
|
+
if (f.type === "oneof" && f.types) {
|
|
328
|
+
out.types = f.types.map(
|
|
329
|
+
(t) => typeof t === "string" ? t : { id: t.id, fields: simplifyFields(t.fields || []) }
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
return out;
|
|
333
|
+
});
|
|
334
|
+
}
|
|
151
335
|
|
|
152
336
|
// core/ai.ts
|
|
153
337
|
var ROOT_MD_FILENAME = "ROOT.md";
|
|
@@ -272,6 +456,12 @@ function findImageModel(config, modelId) {
|
|
|
272
456
|
const defaultId = config.defaultImageModel || imageModels[0]?.id;
|
|
273
457
|
return imageModels.find((m) => m.id === defaultId) || null;
|
|
274
458
|
}
|
|
459
|
+
function normalizeExecutionMode(value) {
|
|
460
|
+
if (value === "read" || value === "suggest" || value === "approve" || value === "auto") {
|
|
461
|
+
return value;
|
|
462
|
+
}
|
|
463
|
+
return "approve";
|
|
464
|
+
}
|
|
275
465
|
function requireDefaultModel(rootConfig) {
|
|
276
466
|
const config = getAiConfig(rootConfig);
|
|
277
467
|
if (!config) {
|
|
@@ -416,10 +606,87 @@ async function generateChatTitle(model, messages) {
|
|
|
416
606
|
}
|
|
417
607
|
return fallback;
|
|
418
608
|
}
|
|
609
|
+
function buildCmsToolContext(options) {
|
|
610
|
+
let searchService = null;
|
|
611
|
+
return {
|
|
612
|
+
rootConfig: options.rootConfig,
|
|
613
|
+
cmsClient: options.cmsClient,
|
|
614
|
+
user: options.user,
|
|
615
|
+
loadCollection: options.loadCollection,
|
|
616
|
+
loadAllCollections: options.loadAllCollections,
|
|
617
|
+
search: async (query, opts) => {
|
|
618
|
+
if (!searchService) {
|
|
619
|
+
searchService = new SearchIndexService(
|
|
620
|
+
options.rootConfig,
|
|
621
|
+
options.loadCollection
|
|
622
|
+
);
|
|
623
|
+
}
|
|
624
|
+
return await searchService.search(query, opts);
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
function buildExecutionModePrompt(mode) {
|
|
629
|
+
const common = [
|
|
630
|
+
"Root AI execution workflow:",
|
|
631
|
+
"- For content-changing tasks, first gather the relevant context with read tools.",
|
|
632
|
+
"- Before the first write, briefly state a plan that names the target docs, fields, intended changes, assumptions, and validation checks.",
|
|
633
|
+
"- Never claim a draft change was applied until the matching tool output reports success.",
|
|
634
|
+
"- After write tools finish, provide a short receipt with changed docs, changed fields, validation result, and a reminder that publishing remains manual."
|
|
635
|
+
];
|
|
636
|
+
if (mode === "read") {
|
|
637
|
+
common.push(
|
|
638
|
+
"",
|
|
639
|
+
"Current execution mode: Read only.",
|
|
640
|
+
"- You only have read-only CMS tools.",
|
|
641
|
+
"- Do not propose tool writes or ask for approval to write. Answer from the context you can read."
|
|
642
|
+
);
|
|
643
|
+
} else if (mode === "suggest") {
|
|
644
|
+
common.push(
|
|
645
|
+
"",
|
|
646
|
+
"Current execution mode: Suggest changes.",
|
|
647
|
+
"- You only have read-only CMS tools.",
|
|
648
|
+
"- Do not call write tools. Provide proposed edits, field paths, and rationale for the user to review."
|
|
649
|
+
);
|
|
650
|
+
} else if (mode === "approve") {
|
|
651
|
+
common.push(
|
|
652
|
+
"",
|
|
653
|
+
"Current execution mode: Ask before writing.",
|
|
654
|
+
"- Write tools are available, but the UI will pause each draft write for user approval with a diff.",
|
|
655
|
+
"- Call write tools only after you have enough context to make a specific, reviewable change.",
|
|
656
|
+
"- If the user rejects a write, revise the plan instead of retrying the same write."
|
|
657
|
+
);
|
|
658
|
+
} else {
|
|
659
|
+
common.push(
|
|
660
|
+
"",
|
|
661
|
+
"Current execution mode: Auto-apply draft edits.",
|
|
662
|
+
"- Draft-only write tools may run without an approval pause.",
|
|
663
|
+
"- Keep edits narrowly scoped to the user request and summarize the exact draft changes afterward."
|
|
664
|
+
);
|
|
665
|
+
}
|
|
666
|
+
return common.join("\n");
|
|
667
|
+
}
|
|
419
668
|
async function runChatStream(options) {
|
|
420
|
-
const {
|
|
669
|
+
const {
|
|
670
|
+
rootConfig,
|
|
671
|
+
model,
|
|
672
|
+
config,
|
|
673
|
+
messages,
|
|
674
|
+
cmsClient,
|
|
675
|
+
user,
|
|
676
|
+
chatId,
|
|
677
|
+
executionMode = "approve",
|
|
678
|
+
loadCollection,
|
|
679
|
+
loadAllCollections
|
|
680
|
+
} = options;
|
|
421
681
|
const languageModel = resolveLanguageModel(model);
|
|
422
|
-
const
|
|
682
|
+
const toolContext = buildCmsToolContext({
|
|
683
|
+
rootConfig,
|
|
684
|
+
cmsClient,
|
|
685
|
+
user,
|
|
686
|
+
loadCollection,
|
|
687
|
+
loadAllCollections
|
|
688
|
+
});
|
|
689
|
+
const tools = model.capabilities?.tools === false ? {} : executionMode === "read" || executionMode === "suggest" ? createReadOnlyCmsTools(toolContext) : createCmsTools(toolContext);
|
|
423
690
|
const basePrompt = config.systemPrompt || [
|
|
424
691
|
"You are an assistant embedded in the Root CMS admin UI.",
|
|
425
692
|
"Help the user explore and edit content, answer questions about",
|
|
@@ -427,7 +694,10 @@ async function runChatStream(options) {
|
|
|
427
694
|
"Be concise and use markdown for rich responses."
|
|
428
695
|
].join(" ");
|
|
429
696
|
const rootMd = await readRootMd(rootConfig.rootDir);
|
|
430
|
-
const systemPrompt = buildSystemPrompt(
|
|
697
|
+
const systemPrompt = buildSystemPrompt(
|
|
698
|
+
[basePrompt, "", buildExecutionModePrompt(executionMode)].join("\n"),
|
|
699
|
+
rootMd
|
|
700
|
+
);
|
|
431
701
|
const modelMessages = await convertToModelMessages(messages, { tools });
|
|
432
702
|
const result = streamText({
|
|
433
703
|
model: languageModel,
|
|
@@ -459,9 +729,26 @@ async function runChatStream(options) {
|
|
|
459
729
|
});
|
|
460
730
|
}
|
|
461
731
|
async function runEditObjectStream(options) {
|
|
462
|
-
const {
|
|
732
|
+
const {
|
|
733
|
+
rootConfig,
|
|
734
|
+
cmsClient,
|
|
735
|
+
user,
|
|
736
|
+
model,
|
|
737
|
+
config,
|
|
738
|
+
messages,
|
|
739
|
+
editData,
|
|
740
|
+
loadCollection,
|
|
741
|
+
loadAllCollections
|
|
742
|
+
} = options;
|
|
463
743
|
const languageModel = resolveLanguageModel(model);
|
|
464
|
-
const
|
|
744
|
+
const toolContext = buildCmsToolContext({
|
|
745
|
+
rootConfig,
|
|
746
|
+
cmsClient,
|
|
747
|
+
user,
|
|
748
|
+
loadCollection,
|
|
749
|
+
loadAllCollections
|
|
750
|
+
});
|
|
751
|
+
const tools = model.capabilities?.tools === false ? {} : createReadOnlyCmsTools(toolContext);
|
|
465
752
|
const editPromptText = (await import("./edit-XX3LAGK6.js")).default;
|
|
466
753
|
const promptParts = [
|
|
467
754
|
stripLegacyEditOutputSpec(editPromptText),
|
|
@@ -716,7 +1003,7 @@ function extractJsonFromResponse(responseText) {
|
|
|
716
1003
|
// package.json
|
|
717
1004
|
var package_default = {
|
|
718
1005
|
name: "@blinkk/root-cms",
|
|
719
|
-
version: "3.0.1-beta.
|
|
1006
|
+
version: "3.0.1-beta.4",
|
|
720
1007
|
author: "s@blinkk.com",
|
|
721
1008
|
license: "MIT",
|
|
722
1009
|
engines: {
|
|
@@ -885,7 +1172,7 @@ var package_default = {
|
|
|
885
1172
|
yjs: "13.6.27"
|
|
886
1173
|
},
|
|
887
1174
|
peerDependencies: {
|
|
888
|
-
"@blinkk/root": "3.0.1-beta.
|
|
1175
|
+
"@blinkk/root": "3.0.1-beta.4",
|
|
889
1176
|
"firebase-admin": ">=11",
|
|
890
1177
|
"firebase-functions": ">=4"
|
|
891
1178
|
},
|
|
@@ -909,6 +1196,7 @@ export {
|
|
|
909
1196
|
serializeAiConfig,
|
|
910
1197
|
getAiConfig,
|
|
911
1198
|
findModel,
|
|
1199
|
+
normalizeExecutionMode,
|
|
912
1200
|
ChatStore,
|
|
913
1201
|
runChatStream,
|
|
914
1202
|
runEditObjectStream,
|
package/dist/cli.js
CHANGED
package/dist/client.js
CHANGED
package/dist/core.js
CHANGED
package/dist/functions.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
runCronJobs
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-
|
|
5
|
-
import "./chunk-
|
|
3
|
+
} from "./chunk-BGTUWIV6.js";
|
|
4
|
+
import "./chunk-C245C557.js";
|
|
5
|
+
import "./chunk-2BSW7SIH.js";
|
|
6
6
|
import "./chunk-MLKGABMK.js";
|
|
7
7
|
|
|
8
8
|
// core/functions.ts
|
package/dist/plugin.js
CHANGED
|
@@ -6,22 +6,24 @@ import {
|
|
|
6
6
|
generatePublishMessage,
|
|
7
7
|
getAiConfig,
|
|
8
8
|
getServerVersion,
|
|
9
|
+
normalizeExecutionMode,
|
|
9
10
|
runChatStream,
|
|
10
11
|
runEditObjectStream,
|
|
11
12
|
serializeAiConfig,
|
|
12
13
|
summarizeDiff,
|
|
13
14
|
translateString
|
|
14
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-MVS2NLZM.js";
|
|
15
16
|
import {
|
|
16
|
-
SearchIndexService,
|
|
17
17
|
runCronJobs
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-BGTUWIV6.js";
|
|
19
|
+
import {
|
|
20
|
+
SearchIndexService
|
|
21
|
+
} from "./chunk-C245C557.js";
|
|
19
22
|
import {
|
|
20
23
|
RootCMSClient,
|
|
21
24
|
parseDocId,
|
|
22
25
|
unmarshalData
|
|
23
|
-
} from "./chunk-
|
|
24
|
-
import "./chunk-CRK7N6RR.js";
|
|
26
|
+
} from "./chunk-2BSW7SIH.js";
|
|
25
27
|
import "./chunk-MLKGABMK.js";
|
|
26
28
|
|
|
27
29
|
// core/plugin.ts
|
|
@@ -545,24 +547,22 @@ function api(server, options) {
|
|
|
545
547
|
return;
|
|
546
548
|
}
|
|
547
549
|
const body = req.body || {};
|
|
548
|
-
const messages = body.messages || [];
|
|
549
|
-
if (!Array.isArray(messages) || messages.length === 0) {
|
|
550
|
-
res.status(400).json({ success: false, error: "MISSING_MESSAGES" });
|
|
551
|
-
return;
|
|
552
|
-
}
|
|
553
550
|
const model = findModel(aiConfig, body.modelId);
|
|
554
551
|
if (!model) {
|
|
555
552
|
res.status(400).json({ success: false, error: "UNKNOWN_MODEL" });
|
|
556
553
|
return;
|
|
557
554
|
}
|
|
555
|
+
const executionMode = normalizeExecutionMode(body.executionMode);
|
|
558
556
|
const cmsClient = new RootCMSClient(req.rootConfig);
|
|
559
557
|
const store = new ChatStore(cmsClient, req.user.email);
|
|
560
558
|
const requestedChatId = typeof body.chatId === "string" ? body.chatId.trim() : "";
|
|
561
559
|
let chatId = "";
|
|
560
|
+
let storedMessages = [];
|
|
562
561
|
if (requestedChatId) {
|
|
563
562
|
const existing = await store.getChat(requestedChatId);
|
|
564
563
|
if (existing) {
|
|
565
564
|
chatId = existing.id;
|
|
565
|
+
storedMessages = existing.messages || [];
|
|
566
566
|
} else {
|
|
567
567
|
const created = await store.createChat({
|
|
568
568
|
id: requestedChatId,
|
|
@@ -574,6 +574,15 @@ function api(server, options) {
|
|
|
574
574
|
const created = await store.createChat({ modelId: model.id });
|
|
575
575
|
chatId = created.id;
|
|
576
576
|
}
|
|
577
|
+
let messages;
|
|
578
|
+
if (body.message && typeof body.message === "object") {
|
|
579
|
+
messages = [...storedMessages, body.message];
|
|
580
|
+
} else if (Array.isArray(body.messages) && body.messages.length > 0) {
|
|
581
|
+
messages = body.messages;
|
|
582
|
+
} else {
|
|
583
|
+
res.status(400).json({ success: false, error: "MISSING_MESSAGES" });
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
577
586
|
try {
|
|
578
587
|
const streamResponse = await runChatStream({
|
|
579
588
|
rootConfig: req.rootConfig,
|
|
@@ -582,7 +591,10 @@ function api(server, options) {
|
|
|
582
591
|
model,
|
|
583
592
|
messages,
|
|
584
593
|
chatId,
|
|
585
|
-
user: req.user.email
|
|
594
|
+
user: req.user.email,
|
|
595
|
+
executionMode,
|
|
596
|
+
loadCollection: (collectionId) => getCollectionSchema(req, collectionId),
|
|
597
|
+
loadAllCollections: async () => (await options.getRenderer(req)).getCollections()
|
|
586
598
|
});
|
|
587
599
|
streamResponse.headers.set("x-root-cms-chat-id", chatId);
|
|
588
600
|
await pipeWebResponse(streamResponse, res);
|
|
@@ -625,10 +637,14 @@ function api(server, options) {
|
|
|
625
637
|
try {
|
|
626
638
|
const streamResponse = await runEditObjectStream({
|
|
627
639
|
rootConfig: req.rootConfig,
|
|
640
|
+
cmsClient: new RootCMSClient(req.rootConfig),
|
|
641
|
+
user: req.user.email,
|
|
628
642
|
config: aiConfig,
|
|
629
643
|
model,
|
|
630
644
|
messages,
|
|
631
|
-
editData: body.editData
|
|
645
|
+
editData: body.editData,
|
|
646
|
+
loadCollection: (collectionId) => getCollectionSchema(req, collectionId),
|
|
647
|
+
loadAllCollections: async () => (await options.getRenderer(req)).getCollections()
|
|
632
648
|
});
|
|
633
649
|
await pipeWebResponse(streamResponse, res);
|
|
634
650
|
} catch (err) {
|
|
@@ -1439,6 +1455,10 @@ function cmsPlugin(options) {
|
|
|
1439
1455
|
* Attaches CMS-specific middleware to the Root.js server.
|
|
1440
1456
|
*/
|
|
1441
1457
|
configureServer: async (server, serverOptions) => {
|
|
1458
|
+
server.use(
|
|
1459
|
+
["/cms/api/ai.chat", "/cms/api/ai.edit_object"],
|
|
1460
|
+
bodyParser.json({ limit: "4mb" })
|
|
1461
|
+
);
|
|
1442
1462
|
server.use(bodyParser.json());
|
|
1443
1463
|
server.use(
|
|
1444
1464
|
(err, req, res, next) => {
|