@webmaster-droid/server 0.1.0-alpha.0 → 0.2.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/SOUL.md +36 -0
- package/dist/agent/index.d.ts +5 -0
- package/dist/agent/index.js +2 -2
- package/dist/api-aws/index.js +4 -3
- package/dist/api-supabase/index.d.ts +3 -0
- package/dist/api-supabase/index.js +12 -0
- package/dist/{chunk-5CVLHGGO.js → chunk-6M55DMUE.js} +44 -151
- package/dist/{chunk-2LAI3MY2.js → chunk-EYY23AAK.js} +1 -0
- package/dist/chunk-JIGCFERP.js +509 -0
- package/dist/chunk-OWXROQ4O.js +416 -0
- package/dist/{chunk-X6TU47KZ.js → chunk-PS4GESOZ.js} +850 -793
- package/dist/chunk-SIXK4BMG.js +138 -0
- package/dist/core/index.d.ts +2 -1
- package/dist/core/index.js +3 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +14 -3
- package/dist/storage-supabase/index.d.ts +50 -0
- package/dist/storage-supabase/index.js +6 -0
- package/package.json +14 -4
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getBearerToken,
|
|
3
|
+
normalizeEditablePath,
|
|
4
|
+
verifyAdminToken
|
|
5
|
+
} from "./chunk-SIXK4BMG.js";
|
|
6
|
+
import {
|
|
7
|
+
runAgentTurn
|
|
8
|
+
} from "./chunk-PS4GESOZ.js";
|
|
9
|
+
import {
|
|
10
|
+
CmsService
|
|
11
|
+
} from "./chunk-EYY23AAK.js";
|
|
12
|
+
import {
|
|
13
|
+
SupabaseCmsStorage
|
|
14
|
+
} from "./chunk-OWXROQ4O.js";
|
|
15
|
+
|
|
16
|
+
// src/api-supabase/http.ts
|
|
17
|
+
var CORS_HEADERS = {
|
|
18
|
+
"access-control-allow-origin": "*",
|
|
19
|
+
"access-control-allow-headers": "content-type,authorization,accept,cache-control",
|
|
20
|
+
"access-control-allow-methods": "GET,POST,OPTIONS"
|
|
21
|
+
};
|
|
22
|
+
var SSE_BASE_HEADERS = {
|
|
23
|
+
...CORS_HEADERS,
|
|
24
|
+
"content-type": "text/event-stream",
|
|
25
|
+
"cache-control": "no-cache, no-transform",
|
|
26
|
+
connection: "keep-alive"
|
|
27
|
+
};
|
|
28
|
+
function jsonResponse(statusCode, body, headers) {
|
|
29
|
+
return new Response(JSON.stringify(body), {
|
|
30
|
+
status: statusCode,
|
|
31
|
+
headers: {
|
|
32
|
+
...CORS_HEADERS,
|
|
33
|
+
"content-type": "application/json",
|
|
34
|
+
...headers
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
function sseHeaders(headers) {
|
|
39
|
+
return {
|
|
40
|
+
...SSE_BASE_HEADERS,
|
|
41
|
+
...headers
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function sseEvent(event, data) {
|
|
45
|
+
const payload = typeof data === "string" ? data : JSON.stringify(data);
|
|
46
|
+
return `event: ${event}
|
|
47
|
+
data: ${payload}
|
|
48
|
+
|
|
49
|
+
`;
|
|
50
|
+
}
|
|
51
|
+
async function parseJsonBody(request) {
|
|
52
|
+
const raw = await request.text();
|
|
53
|
+
if (!raw) {
|
|
54
|
+
throw new Error("Request body is required.");
|
|
55
|
+
}
|
|
56
|
+
return JSON.parse(raw);
|
|
57
|
+
}
|
|
58
|
+
function normalizePath(path) {
|
|
59
|
+
return path.replace(/\/+$/, "") || "/";
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// src/api-supabase/service-factory.ts
|
|
63
|
+
import {
|
|
64
|
+
createDefaultCmsDocument
|
|
65
|
+
} from "@webmaster-droid/contracts";
|
|
66
|
+
var DEFAULT_SUPABASE_BUCKET = "webmaster-droid-cms";
|
|
67
|
+
var DEFAULT_STORAGE_PREFIX = "cms";
|
|
68
|
+
var servicePromise = null;
|
|
69
|
+
function requireEnv(name) {
|
|
70
|
+
const value = process.env[name];
|
|
71
|
+
if (!value) {
|
|
72
|
+
throw new Error(`Missing required environment variable: ${name}`);
|
|
73
|
+
}
|
|
74
|
+
return value;
|
|
75
|
+
}
|
|
76
|
+
function parseBooleanEnv(name, defaultValue) {
|
|
77
|
+
const raw = process.env[name];
|
|
78
|
+
if (!raw) {
|
|
79
|
+
return defaultValue;
|
|
80
|
+
}
|
|
81
|
+
return raw.toLowerCase() === "true";
|
|
82
|
+
}
|
|
83
|
+
function parseOptionalEnv(name) {
|
|
84
|
+
const raw = process.env[name];
|
|
85
|
+
if (!raw) {
|
|
86
|
+
return void 0;
|
|
87
|
+
}
|
|
88
|
+
const trimmed = raw.trim();
|
|
89
|
+
return trimmed || void 0;
|
|
90
|
+
}
|
|
91
|
+
function buildModelConfig() {
|
|
92
|
+
return {
|
|
93
|
+
openaiEnabled: parseBooleanEnv("MODEL_OPENAI_ENABLED", true),
|
|
94
|
+
geminiEnabled: parseBooleanEnv("MODEL_GEMINI_ENABLED", true),
|
|
95
|
+
defaultModelId: process.env.DEFAULT_MODEL_ID ?? "openai:gpt-5.2"
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function normalizeAllowedPath(path) {
|
|
99
|
+
const trimmed = path.trim();
|
|
100
|
+
if (!trimmed.startsWith("/")) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
if (trimmed === "/") {
|
|
104
|
+
return "/";
|
|
105
|
+
}
|
|
106
|
+
const normalized = trimmed.replace(/\/+$/, "");
|
|
107
|
+
if (!normalized) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
return `${normalized}/`;
|
|
111
|
+
}
|
|
112
|
+
function parseAllowedInternalPathsEnv() {
|
|
113
|
+
const raw = process.env.CMS_ALLOWED_INTERNAL_PATHS;
|
|
114
|
+
if (!raw) {
|
|
115
|
+
return ["/"];
|
|
116
|
+
}
|
|
117
|
+
const normalized = raw.split(",").map((item) => normalizeAllowedPath(item)).filter((value) => Boolean(value));
|
|
118
|
+
return normalized.length > 0 ? normalized : ["/"];
|
|
119
|
+
}
|
|
120
|
+
function resolveStorageBucket() {
|
|
121
|
+
return parseOptionalEnv("CMS_SUPABASE_BUCKET") ?? parseOptionalEnv("SUPABASE_STORAGE_BUCKET") ?? DEFAULT_SUPABASE_BUCKET;
|
|
122
|
+
}
|
|
123
|
+
function resolveStoragePrefix() {
|
|
124
|
+
return parseOptionalEnv("CMS_STORAGE_PREFIX") ?? DEFAULT_STORAGE_PREFIX;
|
|
125
|
+
}
|
|
126
|
+
function deriveSupabasePublicBaseUrl(bucket) {
|
|
127
|
+
const explicit = parseOptionalEnv("CMS_PUBLIC_BASE_URL");
|
|
128
|
+
if (explicit) {
|
|
129
|
+
return explicit;
|
|
130
|
+
}
|
|
131
|
+
const supabaseUrl = parseOptionalEnv("SUPABASE_URL");
|
|
132
|
+
if (!supabaseUrl) {
|
|
133
|
+
return void 0;
|
|
134
|
+
}
|
|
135
|
+
const normalized = supabaseUrl.replace(/\/+$/, "");
|
|
136
|
+
return `${normalized}/storage/v1/object/public/${bucket}`;
|
|
137
|
+
}
|
|
138
|
+
async function getCmsService() {
|
|
139
|
+
if (!servicePromise) {
|
|
140
|
+
servicePromise = (async () => {
|
|
141
|
+
const bucket = resolveStorageBucket();
|
|
142
|
+
const storage = new SupabaseCmsStorage({
|
|
143
|
+
supabaseUrl: requireEnv("SUPABASE_URL"),
|
|
144
|
+
serviceRoleKey: requireEnv("SUPABASE_SERVICE_ROLE_KEY"),
|
|
145
|
+
bucket,
|
|
146
|
+
prefix: resolveStoragePrefix()
|
|
147
|
+
});
|
|
148
|
+
const service = new CmsService(storage, {
|
|
149
|
+
modelConfig: buildModelConfig(),
|
|
150
|
+
allowedInternalPaths: parseAllowedInternalPathsEnv(),
|
|
151
|
+
publicAssetBaseUrl: deriveSupabasePublicBaseUrl(bucket),
|
|
152
|
+
publicAssetPrefix: parseOptionalEnv("CMS_GENERATED_ASSET_PREFIX")
|
|
153
|
+
});
|
|
154
|
+
await service.ensureInitialized(createDefaultCmsDocument());
|
|
155
|
+
return service;
|
|
156
|
+
})();
|
|
157
|
+
}
|
|
158
|
+
return servicePromise;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// src/api-supabase/index.ts
|
|
162
|
+
var SELECTION_KIND_SET = /* @__PURE__ */ new Set([
|
|
163
|
+
"text",
|
|
164
|
+
"image",
|
|
165
|
+
"link",
|
|
166
|
+
"section"
|
|
167
|
+
]);
|
|
168
|
+
var DEFAULT_MODEL_OPTIONS = [
|
|
169
|
+
{
|
|
170
|
+
id: "openai:gpt-5.2",
|
|
171
|
+
label: "OpenAI GPT-5.2",
|
|
172
|
+
provider: "openai"
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
id: "gemini:gemini-3-flash-preview",
|
|
176
|
+
label: "Google Gemini 3 Flash (preview)",
|
|
177
|
+
provider: "gemini"
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
id: "gemini:gemini-3.1-pro-preview",
|
|
181
|
+
label: "Google Gemini 3.1 Pro (preview)",
|
|
182
|
+
provider: "gemini"
|
|
183
|
+
}
|
|
184
|
+
];
|
|
185
|
+
function buildAvailableModels(config) {
|
|
186
|
+
const enabledProviders = /* @__PURE__ */ new Set();
|
|
187
|
+
if (config.openaiEnabled) {
|
|
188
|
+
enabledProviders.add("openai");
|
|
189
|
+
}
|
|
190
|
+
if (config.geminiEnabled) {
|
|
191
|
+
enabledProviders.add("gemini");
|
|
192
|
+
}
|
|
193
|
+
const options = /* @__PURE__ */ new Map();
|
|
194
|
+
for (const candidate of DEFAULT_MODEL_OPTIONS) {
|
|
195
|
+
if (!enabledProviders.has(candidate.provider)) {
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
options.set(candidate.id, {
|
|
199
|
+
id: candidate.id,
|
|
200
|
+
label: candidate.label
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
return Array.from(options.values());
|
|
204
|
+
}
|
|
205
|
+
function buildModelCapabilities(input) {
|
|
206
|
+
const hasReadableModel = input.availableModels.length > 0;
|
|
207
|
+
const hasImagePipeline = input.config.geminiEnabled && input.hasPublicAssetBaseUrl;
|
|
208
|
+
return {
|
|
209
|
+
contentEdit: hasReadableModel,
|
|
210
|
+
themeTokenEdit: hasReadableModel,
|
|
211
|
+
imageGenerate: hasImagePipeline,
|
|
212
|
+
imageEdit: hasImagePipeline,
|
|
213
|
+
visionAssist: hasReadableModel
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
function resolveDefaultModelId(config, availableModels) {
|
|
217
|
+
const requestedDefault = config.defaultModelId.trim();
|
|
218
|
+
if (availableModels.some((model) => model.id === requestedDefault)) {
|
|
219
|
+
return requestedDefault;
|
|
220
|
+
}
|
|
221
|
+
return availableModels[0]?.id ?? requestedDefault;
|
|
222
|
+
}
|
|
223
|
+
function toHeaderRecord(headers) {
|
|
224
|
+
const out = {};
|
|
225
|
+
headers.forEach((value, key) => {
|
|
226
|
+
out[key] = value;
|
|
227
|
+
});
|
|
228
|
+
return out;
|
|
229
|
+
}
|
|
230
|
+
async function requireAdminFromHeaders(headers) {
|
|
231
|
+
const token = getBearerToken(toHeaderRecord(headers));
|
|
232
|
+
if (!token) {
|
|
233
|
+
throw new Error("Missing bearer token.");
|
|
234
|
+
}
|
|
235
|
+
return verifyAdminToken(token);
|
|
236
|
+
}
|
|
237
|
+
function getStageFromQuery(url) {
|
|
238
|
+
const stage = url.searchParams.get("stage");
|
|
239
|
+
if (stage === "draft") {
|
|
240
|
+
return "draft";
|
|
241
|
+
}
|
|
242
|
+
return "live";
|
|
243
|
+
}
|
|
244
|
+
function normalizeText(value, maxLength) {
|
|
245
|
+
if (typeof value !== "string") {
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
const compact = value.replace(/\s+/g, " ").trim();
|
|
249
|
+
if (!compact) {
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
if (compact.length <= maxLength) {
|
|
253
|
+
return compact;
|
|
254
|
+
}
|
|
255
|
+
return `${compact.slice(0, maxLength - 3)}...`;
|
|
256
|
+
}
|
|
257
|
+
function normalizeSelectionKind(value) {
|
|
258
|
+
if (typeof value !== "string") {
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
if (SELECTION_KIND_SET.has(value)) {
|
|
262
|
+
return value;
|
|
263
|
+
}
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
function normalizePagePath(value) {
|
|
267
|
+
if (typeof value !== "string") {
|
|
268
|
+
return null;
|
|
269
|
+
}
|
|
270
|
+
const [withoutQuery] = value.split(/[?#]/, 1);
|
|
271
|
+
const trimmed = withoutQuery?.trim() ?? "";
|
|
272
|
+
if (!trimmed || !trimmed.startsWith("/")) {
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
return trimmed;
|
|
276
|
+
}
|
|
277
|
+
function normalizeRelatedPaths(value) {
|
|
278
|
+
if (!Array.isArray(value)) {
|
|
279
|
+
return [];
|
|
280
|
+
}
|
|
281
|
+
const out = /* @__PURE__ */ new Set();
|
|
282
|
+
for (const item of value) {
|
|
283
|
+
const normalized = normalizeEditablePath(item);
|
|
284
|
+
if (normalized) {
|
|
285
|
+
out.add(normalized);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return Array.from(out);
|
|
289
|
+
}
|
|
290
|
+
function normalizeSelectedElementContext(value) {
|
|
291
|
+
if (!value || typeof value !== "object") {
|
|
292
|
+
return void 0;
|
|
293
|
+
}
|
|
294
|
+
const record = value;
|
|
295
|
+
const path = normalizeEditablePath(record.path);
|
|
296
|
+
const label = normalizeText(record.label, 120);
|
|
297
|
+
const kind = normalizeSelectionKind(record.kind);
|
|
298
|
+
const pagePath = normalizePagePath(record.pagePath);
|
|
299
|
+
if (!path || !label || !kind || !pagePath) {
|
|
300
|
+
return void 0;
|
|
301
|
+
}
|
|
302
|
+
const selected = {
|
|
303
|
+
path,
|
|
304
|
+
label,
|
|
305
|
+
kind,
|
|
306
|
+
pagePath
|
|
307
|
+
};
|
|
308
|
+
const relatedPaths = normalizeRelatedPaths(record.relatedPaths);
|
|
309
|
+
if (relatedPaths.length > 0) {
|
|
310
|
+
selected.relatedPaths = relatedPaths;
|
|
311
|
+
}
|
|
312
|
+
const preview = normalizeText(record.preview, 140);
|
|
313
|
+
if (preview) {
|
|
314
|
+
selected.preview = preview;
|
|
315
|
+
}
|
|
316
|
+
return selected;
|
|
317
|
+
}
|
|
318
|
+
function errorMessage(error) {
|
|
319
|
+
return error instanceof Error ? error.message : "Unknown error";
|
|
320
|
+
}
|
|
321
|
+
function createChatStreamResponse(input) {
|
|
322
|
+
const encoder = new TextEncoder();
|
|
323
|
+
const stream = new ReadableStream({
|
|
324
|
+
start(controller) {
|
|
325
|
+
let heartbeat = null;
|
|
326
|
+
let closed = false;
|
|
327
|
+
const close = () => {
|
|
328
|
+
if (closed) {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
closed = true;
|
|
332
|
+
if (heartbeat) {
|
|
333
|
+
clearInterval(heartbeat);
|
|
334
|
+
}
|
|
335
|
+
controller.close();
|
|
336
|
+
};
|
|
337
|
+
const write = (eventName, data) => {
|
|
338
|
+
if (closed) {
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
controller.enqueue(encoder.encode(sseEvent(eventName, data)));
|
|
342
|
+
};
|
|
343
|
+
const abortHandler = () => {
|
|
344
|
+
close();
|
|
345
|
+
};
|
|
346
|
+
input.requestSignal.addEventListener("abort", abortHandler, { once: true });
|
|
347
|
+
void (async () => {
|
|
348
|
+
try {
|
|
349
|
+
const selectedElement = normalizeSelectedElementContext(input.body.selectedElement);
|
|
350
|
+
write("ready", { ok: true });
|
|
351
|
+
heartbeat = setInterval(() => {
|
|
352
|
+
write("ping", { ts: (/* @__PURE__ */ new Date()).toISOString() });
|
|
353
|
+
}, 15e3);
|
|
354
|
+
const result = await runAgentTurn(input.service, {
|
|
355
|
+
prompt: input.body.message,
|
|
356
|
+
modelId: input.body.modelId,
|
|
357
|
+
includeThinking: input.body.includeThinking,
|
|
358
|
+
actor: input.actor,
|
|
359
|
+
currentPath: input.body.currentPath,
|
|
360
|
+
selectedElement,
|
|
361
|
+
history: input.body.history,
|
|
362
|
+
onThinkingEvent: input.body.includeThinking ? (note) => {
|
|
363
|
+
write("thinking", { note });
|
|
364
|
+
} : void 0,
|
|
365
|
+
onToolEvent: (toolEvent) => {
|
|
366
|
+
write("tool", toolEvent);
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
write("message", { text: result.text });
|
|
370
|
+
if (result.mutationsApplied) {
|
|
371
|
+
write("draft-updated", {
|
|
372
|
+
contentVersion: result.updatedDraft.meta.contentVersion,
|
|
373
|
+
updatedAt: result.updatedDraft.meta.updatedAt,
|
|
374
|
+
summary: result.mutationSummary ?? {
|
|
375
|
+
contentOperations: 0,
|
|
376
|
+
themeTokenChanges: 0,
|
|
377
|
+
imageOperations: 0
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
write("done", { ok: true });
|
|
382
|
+
} catch (error) {
|
|
383
|
+
write("error", {
|
|
384
|
+
ok: false,
|
|
385
|
+
error: errorMessage(error)
|
|
386
|
+
});
|
|
387
|
+
write("done", { ok: false });
|
|
388
|
+
} finally {
|
|
389
|
+
input.requestSignal.removeEventListener("abort", abortHandler);
|
|
390
|
+
close();
|
|
391
|
+
}
|
|
392
|
+
})();
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
return new Response(stream, {
|
|
396
|
+
status: 200,
|
|
397
|
+
headers: sseHeaders()
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
async function handler(request) {
|
|
401
|
+
try {
|
|
402
|
+
const method = request.method.toUpperCase();
|
|
403
|
+
if (method === "OPTIONS") {
|
|
404
|
+
return jsonResponse(200, { ok: true });
|
|
405
|
+
}
|
|
406
|
+
const url = new URL(request.url);
|
|
407
|
+
const path = normalizePath(url.pathname);
|
|
408
|
+
const service = await getCmsService();
|
|
409
|
+
if (method === "GET" && path.endsWith("/content")) {
|
|
410
|
+
const stage = getStageFromQuery(url);
|
|
411
|
+
if (stage === "draft") {
|
|
412
|
+
await requireAdminFromHeaders(request.headers);
|
|
413
|
+
}
|
|
414
|
+
const content = await service.getContent(stage);
|
|
415
|
+
return jsonResponse(200, { stage, content });
|
|
416
|
+
}
|
|
417
|
+
if (method === "GET" && path.endsWith("/models")) {
|
|
418
|
+
const config = service.getModelConfig();
|
|
419
|
+
const availableModels = buildAvailableModels(config);
|
|
420
|
+
const defaultModelId = resolveDefaultModelId(config, availableModels);
|
|
421
|
+
const capabilities = buildModelCapabilities({
|
|
422
|
+
config,
|
|
423
|
+
availableModels,
|
|
424
|
+
hasPublicAssetBaseUrl: Boolean(service.getPublicAssetBaseUrl())
|
|
425
|
+
});
|
|
426
|
+
return jsonResponse(200, {
|
|
427
|
+
providers: {
|
|
428
|
+
openai: config.openaiEnabled,
|
|
429
|
+
gemini: config.geminiEnabled
|
|
430
|
+
},
|
|
431
|
+
capabilities,
|
|
432
|
+
defaultModelId,
|
|
433
|
+
showModelPicker: availableModels.length > 1,
|
|
434
|
+
availableModels
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
if (method === "GET" && path.endsWith("/session")) {
|
|
438
|
+
const identity = await requireAdminFromHeaders(request.headers);
|
|
439
|
+
return jsonResponse(200, {
|
|
440
|
+
authenticated: true,
|
|
441
|
+
identity
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
if (method === "GET" && path.endsWith("/history")) {
|
|
445
|
+
await requireAdminFromHeaders(request.headers);
|
|
446
|
+
const history = await service.listHistory();
|
|
447
|
+
return jsonResponse(200, history);
|
|
448
|
+
}
|
|
449
|
+
if (method === "POST" && path.endsWith("/publish")) {
|
|
450
|
+
const identity = await requireAdminFromHeaders(request.headers);
|
|
451
|
+
const body = await parseJsonBody(request);
|
|
452
|
+
const result = await service.publishDraft(body, identity.email ?? identity.sub);
|
|
453
|
+
return jsonResponse(200, {
|
|
454
|
+
ok: true,
|
|
455
|
+
version: result
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
if (method === "POST" && path.endsWith("/rollback")) {
|
|
459
|
+
const identity = await requireAdminFromHeaders(request.headers);
|
|
460
|
+
const body = await parseJsonBody(request);
|
|
461
|
+
const document = await service.rollbackDraft(body, identity.email ?? identity.sub);
|
|
462
|
+
return jsonResponse(200, {
|
|
463
|
+
ok: true,
|
|
464
|
+
draftVersion: document.meta.contentVersion
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
if (method === "POST" && path.endsWith("/checkpoints/delete")) {
|
|
468
|
+
await requireAdminFromHeaders(request.headers);
|
|
469
|
+
const body = await parseJsonBody(request);
|
|
470
|
+
const checkpointId = typeof body.checkpointId === "string" ? body.checkpointId.trim() : "";
|
|
471
|
+
if (!checkpointId) {
|
|
472
|
+
throw new Error("checkpointId is required.");
|
|
473
|
+
}
|
|
474
|
+
await service.deleteCheckpoint(checkpointId);
|
|
475
|
+
return jsonResponse(200, {
|
|
476
|
+
ok: true,
|
|
477
|
+
checkpointId
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
if (method === "POST" && path.endsWith("/chat/stream")) {
|
|
481
|
+
const identity = await requireAdminFromHeaders(request.headers);
|
|
482
|
+
const body = await parseJsonBody(request);
|
|
483
|
+
return createChatStreamResponse({
|
|
484
|
+
service,
|
|
485
|
+
requestSignal: request.signal,
|
|
486
|
+
body,
|
|
487
|
+
actor: identity.email ?? identity.sub
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
return jsonResponse(404, {
|
|
491
|
+
error: "Not found",
|
|
492
|
+
path,
|
|
493
|
+
method
|
|
494
|
+
});
|
|
495
|
+
} catch (error) {
|
|
496
|
+
const message = errorMessage(error);
|
|
497
|
+
const statusCode = message.startsWith("Checkpoint not found:") ? 404 : 400;
|
|
498
|
+
return jsonResponse(statusCode, {
|
|
499
|
+
ok: false,
|
|
500
|
+
error: message
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
var api_supabase_default = handler;
|
|
505
|
+
|
|
506
|
+
export {
|
|
507
|
+
handler,
|
|
508
|
+
api_supabase_default
|
|
509
|
+
};
|