@webmaster-droid/server 0.1.0-alpha.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/dist/agent/index.d.ts +37 -0
- package/dist/agent/index.js +9 -0
- package/dist/api-aws/index.d.ts +6 -0
- package/dist/api-aws/index.js +11 -0
- package/dist/chunk-2LAI3MY2.js +620 -0
- package/dist/chunk-5CVLHGGO.js +672 -0
- package/dist/chunk-MLID7STX.js +412 -0
- package/dist/chunk-X6TU47KZ.js +1496 -0
- package/dist/core/index.d.ts +10 -0
- package/dist/core/index.js +16 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +32 -0
- package/dist/service-BYwdlvCI.d.ts +47 -0
- package/dist/storage-s3/index.d.ts +49 -0
- package/dist/storage-s3/index.js +6 -0
- package/dist/types-OKJgq7Oo.d.ts +101 -0
- package/package.json +63 -0
|
@@ -0,0 +1,672 @@
|
|
|
1
|
+
import {
|
|
2
|
+
runAgentTurn
|
|
3
|
+
} from "./chunk-X6TU47KZ.js";
|
|
4
|
+
import {
|
|
5
|
+
CmsService
|
|
6
|
+
} from "./chunk-2LAI3MY2.js";
|
|
7
|
+
import {
|
|
8
|
+
S3CmsStorage
|
|
9
|
+
} from "./chunk-MLID7STX.js";
|
|
10
|
+
|
|
11
|
+
// src/api-aws/auth.ts
|
|
12
|
+
import { createRemoteJWKSet, decodeProtectedHeader, jwtVerify } from "jose";
|
|
13
|
+
var jwksCache = null;
|
|
14
|
+
function getJwks() {
|
|
15
|
+
const jwksUrl = process.env.SUPABASE_JWKS_URL;
|
|
16
|
+
if (!jwksUrl) {
|
|
17
|
+
throw new Error("SUPABASE_JWKS_URL is not configured");
|
|
18
|
+
}
|
|
19
|
+
if (!jwksCache) {
|
|
20
|
+
jwksCache = createRemoteJWKSet(new URL(jwksUrl));
|
|
21
|
+
}
|
|
22
|
+
return jwksCache;
|
|
23
|
+
}
|
|
24
|
+
function buildSupabaseUserEndpoint() {
|
|
25
|
+
const explicitBaseUrl = process.env.SUPABASE_URL?.trim();
|
|
26
|
+
if (explicitBaseUrl) {
|
|
27
|
+
return `${explicitBaseUrl.replace(/\/$/, "")}/auth/v1/user`;
|
|
28
|
+
}
|
|
29
|
+
const jwksUrl = process.env.SUPABASE_JWKS_URL?.trim();
|
|
30
|
+
if (!jwksUrl) {
|
|
31
|
+
throw new Error("SUPABASE_JWKS_URL is not configured");
|
|
32
|
+
}
|
|
33
|
+
const parsed = new URL(jwksUrl);
|
|
34
|
+
return `${parsed.origin}/auth/v1/user`;
|
|
35
|
+
}
|
|
36
|
+
function getSupabaseAnonKey() {
|
|
37
|
+
const key = process.env.SUPABASE_ANON_KEY?.trim() ?? process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY?.trim();
|
|
38
|
+
if (!key) {
|
|
39
|
+
throw new Error("SUPABASE_ANON_KEY is required for HS256 token verification fallback.");
|
|
40
|
+
}
|
|
41
|
+
return key;
|
|
42
|
+
}
|
|
43
|
+
async function verifyHs256ViaSupabase(token) {
|
|
44
|
+
const response = await fetch(buildSupabaseUserEndpoint(), {
|
|
45
|
+
method: "GET",
|
|
46
|
+
headers: {
|
|
47
|
+
authorization: `Bearer ${token}`,
|
|
48
|
+
apikey: getSupabaseAnonKey()
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
if (!response.ok) {
|
|
52
|
+
const detail = await response.text();
|
|
53
|
+
throw new Error(`Supabase token verification failed: ${response.status} ${detail}`);
|
|
54
|
+
}
|
|
55
|
+
const user = await response.json();
|
|
56
|
+
const sub = typeof user.id === "string" ? user.id : "";
|
|
57
|
+
if (!sub) {
|
|
58
|
+
throw new Error("Supabase token verification returned no user id.");
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
sub,
|
|
62
|
+
email: typeof user.email === "string" ? user.email : void 0,
|
|
63
|
+
role: typeof user.role === "string" ? user.role : void 0
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function getBearerToken(headers) {
|
|
67
|
+
const value = headers.authorization ?? headers.Authorization;
|
|
68
|
+
if (!value) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
const [prefix, token] = value.split(" ");
|
|
72
|
+
if (prefix?.toLowerCase() !== "bearer" || !token) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
return token;
|
|
76
|
+
}
|
|
77
|
+
async function verifyAdminToken(token) {
|
|
78
|
+
const header = decodeProtectedHeader(token);
|
|
79
|
+
const algorithm = typeof header.alg === "string" ? header.alg : "";
|
|
80
|
+
if (algorithm === "HS256") {
|
|
81
|
+
const secret = process.env.SUPABASE_JWT_SECRET?.trim();
|
|
82
|
+
if (secret) {
|
|
83
|
+
const result2 = await jwtVerify(token, new TextEncoder().encode(secret), {
|
|
84
|
+
algorithms: ["HS256"]
|
|
85
|
+
});
|
|
86
|
+
const payload2 = result2.payload;
|
|
87
|
+
const identity3 = {
|
|
88
|
+
sub: String(payload2.sub ?? ""),
|
|
89
|
+
email: typeof payload2.email === "string" ? payload2.email : void 0,
|
|
90
|
+
role: typeof payload2.role === "string" ? payload2.role : typeof payload2.user_role === "string" ? payload2.user_role : void 0
|
|
91
|
+
};
|
|
92
|
+
if (!identity3.sub) {
|
|
93
|
+
throw new Error("Invalid token: subject is missing.");
|
|
94
|
+
}
|
|
95
|
+
const enforcedAdminEmail3 = process.env.ADMIN_EMAIL;
|
|
96
|
+
if (enforcedAdminEmail3 && identity3.email?.toLowerCase() !== enforcedAdminEmail3.toLowerCase()) {
|
|
97
|
+
throw new Error("Authenticated user is not allowed for admin access.");
|
|
98
|
+
}
|
|
99
|
+
return identity3;
|
|
100
|
+
}
|
|
101
|
+
const identity2 = await verifyHs256ViaSupabase(token);
|
|
102
|
+
const enforcedAdminEmail2 = process.env.ADMIN_EMAIL;
|
|
103
|
+
if (enforcedAdminEmail2 && identity2.email?.toLowerCase() !== enforcedAdminEmail2.toLowerCase()) {
|
|
104
|
+
throw new Error("Authenticated user is not allowed for admin access.");
|
|
105
|
+
}
|
|
106
|
+
return identity2;
|
|
107
|
+
}
|
|
108
|
+
const result = await jwtVerify(token, getJwks(), {
|
|
109
|
+
algorithms: ["RS256", "ES256"]
|
|
110
|
+
});
|
|
111
|
+
const payload = result.payload;
|
|
112
|
+
const identity = {
|
|
113
|
+
sub: String(payload.sub ?? ""),
|
|
114
|
+
email: typeof payload.email === "string" ? payload.email : void 0,
|
|
115
|
+
role: typeof payload.role === "string" ? payload.role : typeof payload.user_role === "string" ? payload.user_role : void 0
|
|
116
|
+
};
|
|
117
|
+
if (!identity.sub) {
|
|
118
|
+
throw new Error("Invalid token: subject is missing.");
|
|
119
|
+
}
|
|
120
|
+
const enforcedAdminEmail = process.env.ADMIN_EMAIL;
|
|
121
|
+
if (enforcedAdminEmail && identity.email?.toLowerCase() !== enforcedAdminEmail.toLowerCase()) {
|
|
122
|
+
throw new Error("Authenticated user is not allowed for admin access.");
|
|
123
|
+
}
|
|
124
|
+
return identity;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// src/api-aws/http.ts
|
|
128
|
+
function jsonResponse(statusCode, body) {
|
|
129
|
+
return {
|
|
130
|
+
statusCode,
|
|
131
|
+
headers: {
|
|
132
|
+
"content-type": "application/json",
|
|
133
|
+
"access-control-allow-origin": "*",
|
|
134
|
+
"access-control-allow-headers": "content-type,authorization,accept,cache-control",
|
|
135
|
+
"access-control-allow-methods": "GET,POST,OPTIONS"
|
|
136
|
+
},
|
|
137
|
+
body: JSON.stringify(body)
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
function sseResponse(statusCode, events) {
|
|
141
|
+
const body = events.map((item) => {
|
|
142
|
+
const payload = typeof item.data === "string" ? item.data : JSON.stringify(item.data);
|
|
143
|
+
return `event: ${item.event}
|
|
144
|
+
data: ${payload}
|
|
145
|
+
|
|
146
|
+
`;
|
|
147
|
+
}).join("");
|
|
148
|
+
return {
|
|
149
|
+
statusCode,
|
|
150
|
+
headers: {
|
|
151
|
+
"content-type": "text/event-stream",
|
|
152
|
+
"cache-control": "no-cache, no-transform",
|
|
153
|
+
connection: "keep-alive",
|
|
154
|
+
"access-control-allow-origin": "*",
|
|
155
|
+
"access-control-allow-headers": "content-type,authorization,accept,cache-control",
|
|
156
|
+
"access-control-allow-methods": "GET,POST,OPTIONS"
|
|
157
|
+
},
|
|
158
|
+
body
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function parseJsonBody(raw) {
|
|
162
|
+
if (!raw) {
|
|
163
|
+
throw new Error("Request body is required.");
|
|
164
|
+
}
|
|
165
|
+
return JSON.parse(raw);
|
|
166
|
+
}
|
|
167
|
+
function normalizePath(path) {
|
|
168
|
+
return path.replace(/\/+$/, "") || "/";
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// src/api-aws/service-factory.ts
|
|
172
|
+
import { createStarterCmsDocument } from "@webmaster-droid/contracts/starter";
|
|
173
|
+
var servicePromise = null;
|
|
174
|
+
function requireEnv(name) {
|
|
175
|
+
const value = process.env[name];
|
|
176
|
+
if (!value) {
|
|
177
|
+
throw new Error(`Missing required environment variable: ${name}`);
|
|
178
|
+
}
|
|
179
|
+
return value;
|
|
180
|
+
}
|
|
181
|
+
function parseBooleanEnv(name, defaultValue) {
|
|
182
|
+
const raw = process.env[name];
|
|
183
|
+
if (!raw) {
|
|
184
|
+
return defaultValue;
|
|
185
|
+
}
|
|
186
|
+
return raw.toLowerCase() === "true";
|
|
187
|
+
}
|
|
188
|
+
function parseOptionalEnv(name) {
|
|
189
|
+
const raw = process.env[name];
|
|
190
|
+
if (!raw) {
|
|
191
|
+
return void 0;
|
|
192
|
+
}
|
|
193
|
+
const trimmed = raw.trim();
|
|
194
|
+
return trimmed || void 0;
|
|
195
|
+
}
|
|
196
|
+
function buildModelConfig() {
|
|
197
|
+
return {
|
|
198
|
+
openaiEnabled: parseBooleanEnv("MODEL_OPENAI_ENABLED", true),
|
|
199
|
+
geminiEnabled: parseBooleanEnv("MODEL_GEMINI_ENABLED", true),
|
|
200
|
+
defaultModelId: process.env.DEFAULT_MODEL_ID ?? "openai:gpt-5.2"
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
function normalizeAllowedPath(path) {
|
|
204
|
+
const trimmed = path.trim();
|
|
205
|
+
if (!trimmed.startsWith("/")) {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
if (trimmed === "/") {
|
|
209
|
+
return "/";
|
|
210
|
+
}
|
|
211
|
+
const normalized = trimmed.replace(/\/+$/, "");
|
|
212
|
+
if (!normalized) {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
return `${normalized}/`;
|
|
216
|
+
}
|
|
217
|
+
function starterAllowedInternalPaths() {
|
|
218
|
+
const seed = createStarterCmsDocument();
|
|
219
|
+
const out = /* @__PURE__ */ new Set(["/"]);
|
|
220
|
+
for (const entry of Object.values(seed.seo)) {
|
|
221
|
+
const normalized = normalizeAllowedPath(entry.path);
|
|
222
|
+
if (normalized) {
|
|
223
|
+
out.add(normalized);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return Array.from(out);
|
|
227
|
+
}
|
|
228
|
+
function parseAllowedInternalPathsEnv(fallbackPaths) {
|
|
229
|
+
const raw = process.env.CMS_ALLOWED_INTERNAL_PATHS;
|
|
230
|
+
if (!raw) {
|
|
231
|
+
return fallbackPaths;
|
|
232
|
+
}
|
|
233
|
+
const normalized = raw.split(",").map((item) => normalizeAllowedPath(item)).filter((value) => Boolean(value));
|
|
234
|
+
return normalized.length > 0 ? normalized : fallbackPaths;
|
|
235
|
+
}
|
|
236
|
+
async function getCmsService() {
|
|
237
|
+
if (!servicePromise) {
|
|
238
|
+
servicePromise = (async () => {
|
|
239
|
+
const storage = new S3CmsStorage({
|
|
240
|
+
bucket: requireEnv("CMS_S3_BUCKET"),
|
|
241
|
+
region: requireEnv("CMS_S3_REGION"),
|
|
242
|
+
prefix: "cms"
|
|
243
|
+
});
|
|
244
|
+
const service = new CmsService(storage, {
|
|
245
|
+
modelConfig: buildModelConfig(),
|
|
246
|
+
allowedInternalPaths: parseAllowedInternalPathsEnv(starterAllowedInternalPaths()),
|
|
247
|
+
publicAssetBaseUrl: parseOptionalEnv("CMS_PUBLIC_BASE_URL"),
|
|
248
|
+
publicAssetPrefix: parseOptionalEnv("CMS_GENERATED_ASSET_PREFIX")
|
|
249
|
+
});
|
|
250
|
+
await service.ensureInitialized(createStarterCmsDocument());
|
|
251
|
+
return service;
|
|
252
|
+
})();
|
|
253
|
+
}
|
|
254
|
+
return servicePromise;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// src/api-aws/index.ts
|
|
258
|
+
var SSE_HEADERS = {
|
|
259
|
+
"content-type": "text/event-stream",
|
|
260
|
+
"cache-control": "no-cache, no-transform",
|
|
261
|
+
connection: "keep-alive",
|
|
262
|
+
"access-control-allow-origin": "*",
|
|
263
|
+
"access-control-allow-headers": "content-type,authorization,accept,cache-control",
|
|
264
|
+
"access-control-allow-methods": "GET,POST,OPTIONS"
|
|
265
|
+
};
|
|
266
|
+
var EDITABLE_ROOTS = ["pages.", "layout.", "seo.", "themeTokens."];
|
|
267
|
+
var SELECTION_KIND_SET = /* @__PURE__ */ new Set([
|
|
268
|
+
"text",
|
|
269
|
+
"image",
|
|
270
|
+
"link",
|
|
271
|
+
"section"
|
|
272
|
+
]);
|
|
273
|
+
var DEFAULT_MODEL_OPTIONS = [
|
|
274
|
+
{
|
|
275
|
+
id: "openai:gpt-5.2",
|
|
276
|
+
label: "OpenAI GPT-5.2",
|
|
277
|
+
provider: "openai"
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
id: "gemini:gemini-3-flash-preview",
|
|
281
|
+
label: "Google Gemini 3 Flash (preview)",
|
|
282
|
+
provider: "gemini"
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
id: "gemini:gemini-3.1-pro-preview",
|
|
286
|
+
label: "Google Gemini 3.1 Pro (preview)",
|
|
287
|
+
provider: "gemini"
|
|
288
|
+
}
|
|
289
|
+
];
|
|
290
|
+
function buildAvailableModels(config) {
|
|
291
|
+
const enabledProviders = /* @__PURE__ */ new Set();
|
|
292
|
+
if (config.openaiEnabled) {
|
|
293
|
+
enabledProviders.add("openai");
|
|
294
|
+
}
|
|
295
|
+
if (config.geminiEnabled) {
|
|
296
|
+
enabledProviders.add("gemini");
|
|
297
|
+
}
|
|
298
|
+
const options = /* @__PURE__ */ new Map();
|
|
299
|
+
for (const candidate of DEFAULT_MODEL_OPTIONS) {
|
|
300
|
+
if (!enabledProviders.has(candidate.provider)) {
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
options.set(candidate.id, {
|
|
304
|
+
id: candidate.id,
|
|
305
|
+
label: candidate.label
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
return Array.from(options.values());
|
|
309
|
+
}
|
|
310
|
+
function resolveDefaultModelId(config, availableModels) {
|
|
311
|
+
const requestedDefault = config.defaultModelId.trim();
|
|
312
|
+
if (availableModels.some((model) => model.id === requestedDefault)) {
|
|
313
|
+
return requestedDefault;
|
|
314
|
+
}
|
|
315
|
+
return availableModels[0]?.id ?? requestedDefault;
|
|
316
|
+
}
|
|
317
|
+
function toHeaderRecord(headers) {
|
|
318
|
+
if (!headers || typeof headers !== "object") {
|
|
319
|
+
return {};
|
|
320
|
+
}
|
|
321
|
+
return headers;
|
|
322
|
+
}
|
|
323
|
+
function eventMethod(event) {
|
|
324
|
+
if (!event || typeof event !== "object") {
|
|
325
|
+
return "GET";
|
|
326
|
+
}
|
|
327
|
+
const e = event;
|
|
328
|
+
return e.httpMethod ?? e.requestContext?.http?.method ?? "GET";
|
|
329
|
+
}
|
|
330
|
+
function eventPath(event) {
|
|
331
|
+
if (!event || typeof event !== "object") {
|
|
332
|
+
return "/";
|
|
333
|
+
}
|
|
334
|
+
const e = event;
|
|
335
|
+
return normalizePath(e.path ?? e.rawPath ?? "/");
|
|
336
|
+
}
|
|
337
|
+
function parseEventBody(event) {
|
|
338
|
+
if (!event || typeof event !== "object") {
|
|
339
|
+
throw new Error("Request body is required.");
|
|
340
|
+
}
|
|
341
|
+
const e = event;
|
|
342
|
+
if (!e.body) {
|
|
343
|
+
throw new Error("Request body is required.");
|
|
344
|
+
}
|
|
345
|
+
const raw = e.isBase64Encoded ? Buffer.from(e.body, "base64").toString("utf8") : e.body;
|
|
346
|
+
return JSON.parse(raw);
|
|
347
|
+
}
|
|
348
|
+
function sseEvent(event, data) {
|
|
349
|
+
const payload = typeof data === "string" ? data : JSON.stringify(data);
|
|
350
|
+
return `event: ${event}
|
|
351
|
+
data: ${payload}
|
|
352
|
+
|
|
353
|
+
`;
|
|
354
|
+
}
|
|
355
|
+
function errorMessage(error) {
|
|
356
|
+
return error instanceof Error ? error.message : "Unknown error";
|
|
357
|
+
}
|
|
358
|
+
async function requireAdminFromHeaders(headers) {
|
|
359
|
+
const token = getBearerToken(headers);
|
|
360
|
+
if (!token) {
|
|
361
|
+
throw new Error("Missing bearer token.");
|
|
362
|
+
}
|
|
363
|
+
return verifyAdminToken(token);
|
|
364
|
+
}
|
|
365
|
+
async function requireAdmin(event) {
|
|
366
|
+
return requireAdminFromHeaders(toHeaderRecord(event.headers));
|
|
367
|
+
}
|
|
368
|
+
function getStageFromQuery(query) {
|
|
369
|
+
const stage = query?.stage;
|
|
370
|
+
if (stage === "draft") {
|
|
371
|
+
return "draft";
|
|
372
|
+
}
|
|
373
|
+
return "live";
|
|
374
|
+
}
|
|
375
|
+
function normalizeEditablePath(value) {
|
|
376
|
+
if (typeof value !== "string") {
|
|
377
|
+
return null;
|
|
378
|
+
}
|
|
379
|
+
const trimmed = value.trim();
|
|
380
|
+
if (!trimmed || trimmed.length > 320) {
|
|
381
|
+
return null;
|
|
382
|
+
}
|
|
383
|
+
if (!EDITABLE_ROOTS.some((prefix) => trimmed.startsWith(prefix))) {
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
return trimmed;
|
|
387
|
+
}
|
|
388
|
+
function normalizeText(value, maxLength) {
|
|
389
|
+
if (typeof value !== "string") {
|
|
390
|
+
return null;
|
|
391
|
+
}
|
|
392
|
+
const compact = value.replace(/\s+/g, " ").trim();
|
|
393
|
+
if (!compact) {
|
|
394
|
+
return null;
|
|
395
|
+
}
|
|
396
|
+
if (compact.length <= maxLength) {
|
|
397
|
+
return compact;
|
|
398
|
+
}
|
|
399
|
+
return `${compact.slice(0, maxLength - 3)}...`;
|
|
400
|
+
}
|
|
401
|
+
function normalizeSelectionKind(value) {
|
|
402
|
+
if (typeof value !== "string") {
|
|
403
|
+
return null;
|
|
404
|
+
}
|
|
405
|
+
if (SELECTION_KIND_SET.has(value)) {
|
|
406
|
+
return value;
|
|
407
|
+
}
|
|
408
|
+
return null;
|
|
409
|
+
}
|
|
410
|
+
function normalizePagePath(value) {
|
|
411
|
+
if (typeof value !== "string") {
|
|
412
|
+
return null;
|
|
413
|
+
}
|
|
414
|
+
const [withoutQuery] = value.split(/[?#]/, 1);
|
|
415
|
+
const trimmed = withoutQuery?.trim() ?? "";
|
|
416
|
+
if (!trimmed || !trimmed.startsWith("/")) {
|
|
417
|
+
return null;
|
|
418
|
+
}
|
|
419
|
+
return trimmed;
|
|
420
|
+
}
|
|
421
|
+
function normalizeRelatedPaths(value) {
|
|
422
|
+
if (!Array.isArray(value)) {
|
|
423
|
+
return [];
|
|
424
|
+
}
|
|
425
|
+
const out = /* @__PURE__ */ new Set();
|
|
426
|
+
for (const item of value) {
|
|
427
|
+
const normalized = normalizeEditablePath(item);
|
|
428
|
+
if (normalized) {
|
|
429
|
+
out.add(normalized);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
return Array.from(out);
|
|
433
|
+
}
|
|
434
|
+
function normalizeSelectedElementContext(value) {
|
|
435
|
+
if (!value || typeof value !== "object") {
|
|
436
|
+
return void 0;
|
|
437
|
+
}
|
|
438
|
+
const record = value;
|
|
439
|
+
const path = normalizeEditablePath(record.path);
|
|
440
|
+
const label = normalizeText(record.label, 120);
|
|
441
|
+
const kind = normalizeSelectionKind(record.kind);
|
|
442
|
+
const pagePath = normalizePagePath(record.pagePath);
|
|
443
|
+
if (!path || !label || !kind || !pagePath) {
|
|
444
|
+
return void 0;
|
|
445
|
+
}
|
|
446
|
+
const selected = {
|
|
447
|
+
path,
|
|
448
|
+
label,
|
|
449
|
+
kind,
|
|
450
|
+
pagePath
|
|
451
|
+
};
|
|
452
|
+
const relatedPaths = normalizeRelatedPaths(record.relatedPaths);
|
|
453
|
+
if (relatedPaths.length > 0) {
|
|
454
|
+
selected.relatedPaths = relatedPaths;
|
|
455
|
+
}
|
|
456
|
+
const preview = normalizeText(record.preview, 140);
|
|
457
|
+
if (preview) {
|
|
458
|
+
selected.preview = preview;
|
|
459
|
+
}
|
|
460
|
+
return selected;
|
|
461
|
+
}
|
|
462
|
+
async function handler(event) {
|
|
463
|
+
try {
|
|
464
|
+
if (event.httpMethod === "OPTIONS") {
|
|
465
|
+
return jsonResponse(200, { ok: true });
|
|
466
|
+
}
|
|
467
|
+
const service = await getCmsService();
|
|
468
|
+
const path = normalizePath(event.path);
|
|
469
|
+
if (event.httpMethod === "GET" && path.endsWith("/content")) {
|
|
470
|
+
const stage = getStageFromQuery(event.queryStringParameters);
|
|
471
|
+
if (stage === "draft") {
|
|
472
|
+
await requireAdmin(event);
|
|
473
|
+
}
|
|
474
|
+
const content = await service.getContent(stage);
|
|
475
|
+
return jsonResponse(200, { stage, content });
|
|
476
|
+
}
|
|
477
|
+
if (event.httpMethod === "GET" && path.endsWith("/models")) {
|
|
478
|
+
const config = service.getModelConfig();
|
|
479
|
+
const availableModels = buildAvailableModels(config);
|
|
480
|
+
const defaultModelId = resolveDefaultModelId(config, availableModels);
|
|
481
|
+
return jsonResponse(200, {
|
|
482
|
+
providers: {
|
|
483
|
+
openai: config.openaiEnabled,
|
|
484
|
+
gemini: config.geminiEnabled
|
|
485
|
+
},
|
|
486
|
+
defaultModelId,
|
|
487
|
+
showModelPicker: availableModels.length > 1,
|
|
488
|
+
availableModels
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
if (event.httpMethod === "GET" && path.endsWith("/session")) {
|
|
492
|
+
const identity = await requireAdmin(event);
|
|
493
|
+
return jsonResponse(200, {
|
|
494
|
+
authenticated: true,
|
|
495
|
+
identity
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
if (event.httpMethod === "GET" && path.endsWith("/history")) {
|
|
499
|
+
await requireAdmin(event);
|
|
500
|
+
const history = await service.listHistory();
|
|
501
|
+
return jsonResponse(200, history);
|
|
502
|
+
}
|
|
503
|
+
if (event.httpMethod === "POST" && path.endsWith("/publish")) {
|
|
504
|
+
const identity = await requireAdmin(event);
|
|
505
|
+
const body = parseJsonBody(event.body);
|
|
506
|
+
const result = await service.publishDraft(body, identity.email ?? identity.sub);
|
|
507
|
+
return jsonResponse(200, {
|
|
508
|
+
ok: true,
|
|
509
|
+
version: result
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
if (event.httpMethod === "POST" && path.endsWith("/rollback")) {
|
|
513
|
+
const identity = await requireAdmin(event);
|
|
514
|
+
const body = parseJsonBody(event.body);
|
|
515
|
+
const document = await service.rollbackDraft(body, identity.email ?? identity.sub);
|
|
516
|
+
return jsonResponse(200, {
|
|
517
|
+
ok: true,
|
|
518
|
+
draftVersion: document.meta.contentVersion
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
if (event.httpMethod === "POST" && path.endsWith("/checkpoints/delete")) {
|
|
522
|
+
await requireAdmin(event);
|
|
523
|
+
const body = parseJsonBody(event.body);
|
|
524
|
+
const checkpointId = typeof body.checkpointId === "string" ? body.checkpointId.trim() : "";
|
|
525
|
+
if (!checkpointId) {
|
|
526
|
+
throw new Error("checkpointId is required.");
|
|
527
|
+
}
|
|
528
|
+
await service.deleteCheckpoint(checkpointId);
|
|
529
|
+
return jsonResponse(200, {
|
|
530
|
+
ok: true,
|
|
531
|
+
checkpointId
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
if (event.httpMethod === "POST" && path.endsWith("/chat/stream")) {
|
|
535
|
+
const identity = await requireAdmin(event);
|
|
536
|
+
const body = parseJsonBody(event.body);
|
|
537
|
+
const selectedElement = normalizeSelectedElementContext(body.selectedElement);
|
|
538
|
+
const result = await runAgentTurn(service, {
|
|
539
|
+
prompt: body.message,
|
|
540
|
+
modelId: body.modelId,
|
|
541
|
+
includeThinking: body.includeThinking,
|
|
542
|
+
actor: identity.email ?? identity.sub,
|
|
543
|
+
currentPath: body.currentPath,
|
|
544
|
+
selectedElement,
|
|
545
|
+
history: body.history
|
|
546
|
+
});
|
|
547
|
+
const events = [];
|
|
548
|
+
if (body.includeThinking) {
|
|
549
|
+
for (const note of result.thinking) {
|
|
550
|
+
events.push({
|
|
551
|
+
event: "thinking",
|
|
552
|
+
data: { note }
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
for (const toolEvent of result.toolEvents) {
|
|
557
|
+
events.push({
|
|
558
|
+
event: "tool",
|
|
559
|
+
data: toolEvent
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
events.push({
|
|
563
|
+
event: "message",
|
|
564
|
+
data: {
|
|
565
|
+
text: result.text
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
if (result.mutationsApplied) {
|
|
569
|
+
events.push({
|
|
570
|
+
event: "draft-updated",
|
|
571
|
+
data: {
|
|
572
|
+
contentVersion: result.updatedDraft.meta.contentVersion,
|
|
573
|
+
updatedAt: result.updatedDraft.meta.updatedAt
|
|
574
|
+
}
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
events.push({
|
|
578
|
+
event: "done",
|
|
579
|
+
data: { ok: true }
|
|
580
|
+
});
|
|
581
|
+
return sseResponse(200, events);
|
|
582
|
+
}
|
|
583
|
+
return jsonResponse(404, {
|
|
584
|
+
error: "Not found",
|
|
585
|
+
path,
|
|
586
|
+
method: event.httpMethod
|
|
587
|
+
});
|
|
588
|
+
} catch (error) {
|
|
589
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
590
|
+
const statusCode = message.startsWith("Checkpoint not found:") ? 404 : 400;
|
|
591
|
+
return jsonResponse(statusCode, {
|
|
592
|
+
ok: false,
|
|
593
|
+
error: message
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
var streamHandler = awslambda.streamifyResponse(
|
|
598
|
+
async (event, responseStream) => {
|
|
599
|
+
const stream = awslambda.HttpResponseStream.from(responseStream, {
|
|
600
|
+
statusCode: 200,
|
|
601
|
+
headers: SSE_HEADERS
|
|
602
|
+
});
|
|
603
|
+
const write = (eventName, data) => {
|
|
604
|
+
stream.write(sseEvent(eventName, data));
|
|
605
|
+
};
|
|
606
|
+
let heartbeat = null;
|
|
607
|
+
try {
|
|
608
|
+
const method = eventMethod(event);
|
|
609
|
+
const path = eventPath(event);
|
|
610
|
+
if (method === "OPTIONS") {
|
|
611
|
+
write("done", { ok: true });
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
if (method !== "POST" || !path.endsWith("/chat/stream")) {
|
|
615
|
+
write("error", {
|
|
616
|
+
ok: false,
|
|
617
|
+
error: `Not found: ${method} ${path}`
|
|
618
|
+
});
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
const identity = await requireAdminFromHeaders(
|
|
622
|
+
toHeaderRecord(event.headers)
|
|
623
|
+
);
|
|
624
|
+
const body = parseEventBody(event);
|
|
625
|
+
const service = await getCmsService();
|
|
626
|
+
const selectedElement = normalizeSelectedElementContext(body.selectedElement);
|
|
627
|
+
write("ready", { ok: true });
|
|
628
|
+
heartbeat = setInterval(() => {
|
|
629
|
+
write("ping", { ts: (/* @__PURE__ */ new Date()).toISOString() });
|
|
630
|
+
}, 15e3);
|
|
631
|
+
const result = await runAgentTurn(service, {
|
|
632
|
+
prompt: body.message,
|
|
633
|
+
modelId: body.modelId,
|
|
634
|
+
includeThinking: body.includeThinking,
|
|
635
|
+
actor: identity.email ?? identity.sub,
|
|
636
|
+
currentPath: body.currentPath,
|
|
637
|
+
selectedElement,
|
|
638
|
+
history: body.history,
|
|
639
|
+
onThinkingEvent: body.includeThinking ? (note) => {
|
|
640
|
+
write("thinking", { note });
|
|
641
|
+
} : void 0,
|
|
642
|
+
onToolEvent: (toolEvent) => {
|
|
643
|
+
write("tool", toolEvent);
|
|
644
|
+
}
|
|
645
|
+
});
|
|
646
|
+
write("message", { text: result.text });
|
|
647
|
+
if (result.mutationsApplied) {
|
|
648
|
+
write("draft-updated", {
|
|
649
|
+
contentVersion: result.updatedDraft.meta.contentVersion,
|
|
650
|
+
updatedAt: result.updatedDraft.meta.updatedAt
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
write("done", { ok: true });
|
|
654
|
+
} catch (error) {
|
|
655
|
+
write("error", {
|
|
656
|
+
ok: false,
|
|
657
|
+
error: errorMessage(error)
|
|
658
|
+
});
|
|
659
|
+
write("done", { ok: false });
|
|
660
|
+
} finally {
|
|
661
|
+
if (heartbeat) {
|
|
662
|
+
clearInterval(heartbeat);
|
|
663
|
+
}
|
|
664
|
+
stream.end();
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
);
|
|
668
|
+
|
|
669
|
+
export {
|
|
670
|
+
handler,
|
|
671
|
+
streamHandler
|
|
672
|
+
};
|