@kubuild/ai 0.3.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.
Files changed (68) hide show
  1. package/LICENSE +21 -0
  2. package/dist/chunk-27AY2KBI.js +500 -0
  3. package/dist/chunk-27AY2KBI.js.map +1 -0
  4. package/dist/chunk-JY4NHD3Q.js +227 -0
  5. package/dist/chunk-JY4NHD3Q.js.map +1 -0
  6. package/dist/chunk-UH24LHRY.js +443 -0
  7. package/dist/chunk-UH24LHRY.js.map +1 -0
  8. package/dist/client/ai-client.d.ts +59 -0
  9. package/dist/client/ai-client.d.ts.map +1 -0
  10. package/dist/client/index.cjs +254 -0
  11. package/dist/client/index.cjs.map +1 -0
  12. package/dist/client/index.d.ts +2 -0
  13. package/dist/client/index.d.ts.map +1 -0
  14. package/dist/client/index.js +9 -0
  15. package/dist/client/index.js.map +1 -0
  16. package/dist/core/index.d.ts +3 -0
  17. package/dist/core/index.d.ts.map +1 -0
  18. package/dist/core/normalizer.d.ts +9 -0
  19. package/dist/core/normalizer.d.ts.map +1 -0
  20. package/dist/core/prompt-compiler.d.ts +10 -0
  21. package/dist/core/prompt-compiler.d.ts.map +1 -0
  22. package/dist/index.cjs +468 -0
  23. package/dist/index.cjs.map +1 -0
  24. package/dist/index.d.ts +3 -0
  25. package/dist/index.d.ts.map +1 -0
  26. package/dist/index.js +23 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/react/index.cjs +584 -0
  29. package/dist/react/index.cjs.map +1 -0
  30. package/dist/react/index.d.ts +3 -0
  31. package/dist/react/index.d.ts.map +1 -0
  32. package/dist/react/index.js +336 -0
  33. package/dist/react/index.js.map +1 -0
  34. package/dist/react/use-ai-chat.d.ts +63 -0
  35. package/dist/react/use-ai-chat.d.ts.map +1 -0
  36. package/dist/react/use-ai-generator.d.ts +19 -0
  37. package/dist/react/use-ai-generator.d.ts.map +1 -0
  38. package/dist/server/adapters/anthropic.d.ts +30 -0
  39. package/dist/server/adapters/anthropic.d.ts.map +1 -0
  40. package/dist/server/adapters/base.d.ts +2 -0
  41. package/dist/server/adapters/base.d.ts.map +1 -0
  42. package/dist/server/adapters/custom.d.ts +18 -0
  43. package/dist/server/adapters/custom.d.ts.map +1 -0
  44. package/dist/server/adapters/gemini.d.ts +22 -0
  45. package/dist/server/adapters/gemini.d.ts.map +1 -0
  46. package/dist/server/adapters/index.cjs +529 -0
  47. package/dist/server/adapters/index.cjs.map +1 -0
  48. package/dist/server/adapters/index.d.ts +6 -0
  49. package/dist/server/adapters/index.d.ts.map +1 -0
  50. package/dist/server/adapters/index.js +13 -0
  51. package/dist/server/adapters/index.js.map +1 -0
  52. package/dist/server/adapters/openai.d.ts +26 -0
  53. package/dist/server/adapters/openai.d.ts.map +1 -0
  54. package/dist/server/adapters/stream-utils.d.ts +12 -0
  55. package/dist/server/adapters/stream-utils.d.ts.map +1 -0
  56. package/dist/server/engine.d.ts +59 -0
  57. package/dist/server/engine.d.ts.map +1 -0
  58. package/dist/server/handler.d.ts +66 -0
  59. package/dist/server/handler.d.ts.map +1 -0
  60. package/dist/server/index.cjs +1824 -0
  61. package/dist/server/index.cjs.map +1 -0
  62. package/dist/server/index.d.ts +4 -0
  63. package/dist/server/index.d.ts.map +1 -0
  64. package/dist/server/index.js +890 -0
  65. package/dist/server/index.js.map +1 -0
  66. package/dist/types.d.ts +184 -0
  67. package/dist/types.d.ts.map +1 -0
  68. package/package.json +94 -0
@@ -0,0 +1,1824 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/server/index.ts
21
+ var server_exports = {};
22
+ __export(server_exports, {
23
+ AnthropicAdapter: () => AnthropicAdapter,
24
+ CustomHttpAdapter: () => CustomHttpAdapter,
25
+ GeminiAdapter: () => GeminiAdapter,
26
+ KubuildAiEngine: () => KubuildAiEngine,
27
+ OpenAiAdapter: () => OpenAiAdapter,
28
+ createAiHandler: () => createAiHandler,
29
+ processAiRequest: () => processAiRequest
30
+ });
31
+ module.exports = __toCommonJS(server_exports);
32
+
33
+ // src/server/engine.ts
34
+ var import_schema2 = require("@kubuild/schema");
35
+
36
+ // src/core/prompt-compiler.ts
37
+ function compileComponentCatalog(registry) {
38
+ if (!registry) return [];
39
+ const definitions = registry.list();
40
+ return definitions.map((def) => {
41
+ const props = [];
42
+ if (def.propFields && Array.isArray(def.propFields)) {
43
+ for (const field of def.propFields) {
44
+ props.push({
45
+ name: field.name,
46
+ type: field.type || "string",
47
+ defaultValue: field.defaultValue,
48
+ options: field.options?.map((o) => o.value),
49
+ description: field.description
50
+ });
51
+ }
52
+ }
53
+ return {
54
+ type: def.type,
55
+ label: def.label || def.type,
56
+ category: def.category || "custom",
57
+ description: def.description,
58
+ acceptsChildren: def.acceptsChildren ?? false,
59
+ allowedChildren: def.allowedChildren,
60
+ disallowedParents: def.disallowedParents,
61
+ defaultProps: def.defaultProps,
62
+ props: props.length > 0 ? props : void 0
63
+ };
64
+ });
65
+ }
66
+ function buildSystemPrompt(options) {
67
+ const { catalog, mode, prefix, stylePreference } = options;
68
+ const catalogSummary = catalog.map((c) => {
69
+ let desc = `- **${c.type}** (${c.category}): ${c.label}`;
70
+ if (c.description) desc += ` - ${c.description}`;
71
+ desc += ` | acceptsChildren: ${c.acceptsChildren}`;
72
+ if (c.allowedChildren && c.allowedChildren.length > 0) {
73
+ desc += ` | allowedChildren: [${c.allowedChildren.join(", ")}]`;
74
+ }
75
+ if (c.props && c.props.length > 0) {
76
+ const propList = c.props.map((p) => {
77
+ let pStr = `${p.name} (${p.type})`;
78
+ if (p.options) pStr += `[${p.options.map(String).join("|")}]`;
79
+ return pStr;
80
+ }).join(", ");
81
+ desc += ` | props: { ${propList} }`;
82
+ }
83
+ return desc;
84
+ }).join("\n");
85
+ let modeInstructions = "";
86
+ if (mode === "full-page") {
87
+ modeInstructions = `
88
+ You must generate a complete, valid KUBUILD PageDocument JSON with this exact structure:
89
+ {
90
+ "schema": "stora.page",
91
+ "version": "1.0.0",
92
+ "metadata": {
93
+ "title": "<Page Title>",
94
+ "description": "<Page Description>",
95
+ "tags": ["<tag1>", "<tag2>"],
96
+ "category": "<category>"
97
+ },
98
+ "document": {
99
+ "id": "root-page",
100
+ "type": "page",
101
+ "styles": {
102
+ "base": {
103
+ "backgroundColor": "#ffffff",
104
+ "fontFamily": "Inter, system-ui, sans-serif",
105
+ "color": "#111827",
106
+ "minHeight": "100vh"
107
+ }
108
+ },
109
+ "children": [
110
+ // Sections here...
111
+ ]
112
+ }
113
+ }
114
+ `;
115
+ } else if (mode === "section") {
116
+ modeInstructions = `
117
+ You must generate a single valid KUBUILD section Node JSON with this structure:
118
+ {
119
+ "id": "section_<unique_suffix>",
120
+ "type": "section",
121
+ "styles": {
122
+ "base": {
123
+ "paddingTop": "64px",
124
+ "paddingBottom": "64px",
125
+ "paddingLeft": "24px",
126
+ "paddingRight": "24px"
127
+ }
128
+ },
129
+ "children": [
130
+ // Container and child nodes...
131
+ ]
132
+ }
133
+ `;
134
+ } else if (mode === "refactor") {
135
+ modeInstructions = `
136
+ You must refactor the provided KUBUILD Node based on the user's instructions.
137
+ Return ONLY the updated Node JSON while maintaining structural integrity.
138
+ `;
139
+ }
140
+ const styleGuide = stylePreference ? `
141
+ User Style Preference: "${stylePreference}". Follow this visual aesthetic closely.` : "\nStyle with modern, clean, high-conversion design patterns (accessible contrast, harmonious spacing, beautiful typography, subtle shadows/borders).";
142
+ return `${prefix ? prefix + "\n\n" : ""}You are an expert web designer and KUBUILD architecture generator.
143
+ KUBUILD is a component-driven visual website builder. Your job is to output pure, valid JSON (and NOTHING ELSE) matching the KUBUILD schema and the registered component definitions.
144
+
145
+ ### RULES & CONSTRAINTS:
146
+ 1. Output MUST be valid JSON only. No markdown fences, no explanatory text, no HTML tags outside html-embed.
147
+ 2. Only use component types listed in the Component Catalog below. Do NOT invent new component types.
148
+ 3. Obey \`acceptsChildren\` and \`allowedChildren\`:
149
+ - Layout hierarchy: \`page\` -> \`section\` -> \`container\` or \`columns\` -> content components (\`heading\`, \`paragraph\`, \`button\`, \`image\`, \`form\`, etc.).
150
+ - Leaf components (e.g. \`button\`, \`input\`, \`heading\`, \`text\`, \`badge\`) must have \`acceptsChildren: false\` and empty or omitted \`children\`.
151
+ 4. Every node MUST have a unique string \`id\` (e.g. "hero-section", "hero-title", "cta-btn").
152
+ 5. Responsive & Interactive State Styling:
153
+ - Use the \`styles\` object with breakpoint keys: \`base\` (all viewports/desktop), and optionally \`tablet\` or \`mobile\`.
154
+ - In \`base\`, \`tablet\`, and \`mobile\`, values MUST be CSS primitives (strings or numbers, e.g. "backgroundColor": "#3b82f6"). NEVER put nested objects or ":hover" inside "base"!
155
+ - For hover, active, or interactive pseudo-classes, use \`styles.states\` with pseudo-class keys (e.g. \`":hover"\`), like:
156
+ "styles": {
157
+ "base": { "backgroundColor": "#3b82f6", "color": "#ffffff" },
158
+ "states": {
159
+ ":hover": { "backgroundColor": "#2563eb" }
160
+ }
161
+ }
162
+ 6. NEVER include dangerous scripts, \`javascript:\` URIs, or malicious payload attributes.
163
+ ${styleGuide}
164
+
165
+ ### OUTPUT SPECIFICATION:
166
+ ${modeInstructions}
167
+
168
+ ### REGISTERED COMPONENT CATALOG:
169
+ ${catalogSummary || "(Standard core components: page, section, container, columns, heading, paragraph, text, button, input, textarea, select, image, video, icon, badge, code-block)"}
170
+ `;
171
+ }
172
+ function buildJsonSchemaForMode(mode) {
173
+ const nodeSchema = {
174
+ type: "object",
175
+ required: ["id", "type"],
176
+ properties: {
177
+ id: { type: "string" },
178
+ type: { type: "string" },
179
+ props: { type: "object" },
180
+ styles: {
181
+ type: "object",
182
+ properties: {
183
+ base: { type: "object" },
184
+ tablet: { type: "object" },
185
+ mobile: { type: "object" },
186
+ states: { type: "object" }
187
+ }
188
+ },
189
+ children: {
190
+ type: "array",
191
+ items: { $ref: "#" }
192
+ }
193
+ }
194
+ };
195
+ if (mode === "full-page") {
196
+ return {
197
+ type: "object",
198
+ required: ["schema", "version", "document"],
199
+ properties: {
200
+ schema: { type: "string", const: "stora.page" },
201
+ version: { type: "string", const: "1.0.0" },
202
+ metadata: {
203
+ type: "object",
204
+ properties: {
205
+ title: { type: "string" },
206
+ description: { type: "string" },
207
+ tags: { type: "array", items: { type: "string" } },
208
+ category: { type: "string" }
209
+ }
210
+ },
211
+ document: nodeSchema
212
+ }
213
+ };
214
+ }
215
+ return nodeSchema;
216
+ }
217
+
218
+ // src/core/normalizer.ts
219
+ var import_schema = require("@kubuild/schema");
220
+ var import_core = require("@kubuild/core");
221
+ function extractJsonFromResponse(raw) {
222
+ let cleaned = raw.trim();
223
+ if (cleaned.startsWith("```")) {
224
+ const firstNewline = cleaned.indexOf("\n");
225
+ if (firstNewline !== -1) {
226
+ cleaned = cleaned.slice(firstNewline + 1);
227
+ }
228
+ if (cleaned.endsWith("```")) {
229
+ cleaned = cleaned.slice(0, cleaned.length - 3).trim();
230
+ }
231
+ }
232
+ const firstBrace = cleaned.indexOf("{");
233
+ const lastBrace = cleaned.lastIndexOf("}");
234
+ if (firstBrace !== -1 && lastBrace !== -1 && lastBrace > firstBrace) {
235
+ cleaned = cleaned.slice(firstBrace, lastBrace + 1);
236
+ }
237
+ try {
238
+ return JSON.parse(cleaned);
239
+ } catch (err) {
240
+ const msg = err instanceof Error ? err.message : String(err);
241
+ throw new Error(`Failed to parse AI response as JSON: ${msg}. Response was: ${raw.slice(0, 200)}...`);
242
+ }
243
+ }
244
+ function normalizeStyles(rawStyles) {
245
+ if (!rawStyles || typeof rawStyles !== "object" || Array.isArray(rawStyles)) {
246
+ return void 0;
247
+ }
248
+ const raw = rawStyles;
249
+ const result = {};
250
+ const states = {};
251
+ if (raw.states && typeof raw.states === "object" && !Array.isArray(raw.states)) {
252
+ for (const [stateKey, stateVal] of Object.entries(raw.states)) {
253
+ if (stateVal && typeof stateVal === "object" && !Array.isArray(stateVal)) {
254
+ states[stateKey] = { ...stateVal };
255
+ }
256
+ }
257
+ }
258
+ const knownBreakpoints = ["base", "desktop", "tablet", "mobile"];
259
+ const hasBreakpoint = Object.keys(raw).some((k) => knownBreakpoints.includes(k));
260
+ if (!hasBreakpoint) {
261
+ const baseStyles = {};
262
+ for (const [key, value] of Object.entries(raw)) {
263
+ if (key === "states") continue;
264
+ if (key.startsWith(":") || key === "hover" || key === "focus" || key === "active") {
265
+ const stateKey = key.startsWith(":") ? key : `:${key}`;
266
+ if (value && typeof value === "object" && !Array.isArray(value)) {
267
+ states[stateKey] = { ...states[stateKey] || {}, ...value };
268
+ }
269
+ } else if (typeof value !== "object" || value === null) {
270
+ baseStyles[key] = value;
271
+ }
272
+ }
273
+ if (Object.keys(baseStyles).length > 0) {
274
+ result.base = baseStyles;
275
+ }
276
+ } else {
277
+ for (const bp of knownBreakpoints) {
278
+ const bpVal = raw[bp];
279
+ if (bpVal && typeof bpVal === "object" && !Array.isArray(bpVal)) {
280
+ const cleanBp = {};
281
+ for (const [key, value] of Object.entries(bpVal)) {
282
+ if (key.startsWith(":") || key === "hover" || key === "focus" || key === "active") {
283
+ const stateKey = key.startsWith(":") ? key : `:${key}`;
284
+ if (value && typeof value === "object" && !Array.isArray(value)) {
285
+ states[stateKey] = { ...states[stateKey] || {}, ...value };
286
+ }
287
+ } else if (typeof value !== "object" || value === null) {
288
+ cleanBp[key] = value;
289
+ }
290
+ }
291
+ if (Object.keys(cleanBp).length > 0) {
292
+ result[bp] = cleanBp;
293
+ }
294
+ }
295
+ }
296
+ for (const [key, value] of Object.entries(raw)) {
297
+ if (knownBreakpoints.includes(key) || key === "states") continue;
298
+ if (key.startsWith(":") || key === "hover" || key === "focus" || key === "active") {
299
+ const stateKey = key.startsWith(":") ? key : `:${key}`;
300
+ if (value && typeof value === "object" && !Array.isArray(value)) {
301
+ states[stateKey] = { ...states[stateKey] || {}, ...value };
302
+ }
303
+ }
304
+ }
305
+ }
306
+ const cleanStates = {};
307
+ for (const [stateKey, stateDef] of Object.entries(states)) {
308
+ const cleanDef = {};
309
+ for (const [propKey, propVal] of Object.entries(stateDef)) {
310
+ if (typeof propVal !== "object" || propVal === null) {
311
+ cleanDef[propKey] = propVal;
312
+ }
313
+ }
314
+ if (Object.keys(cleanDef).length > 0) {
315
+ cleanStates[stateKey] = cleanDef;
316
+ }
317
+ }
318
+ if (Object.keys(cleanStates).length > 0) {
319
+ result.states = cleanStates;
320
+ }
321
+ return Object.keys(result).length > 0 ? result : void 0;
322
+ }
323
+ function normalizeNodeTree(node, usedIds = /* @__PURE__ */ new Set(), depth = 0) {
324
+ if (!node || typeof node !== "object") {
325
+ throw new Error(`Invalid node at depth ${depth}: must be an object`);
326
+ }
327
+ const raw = node;
328
+ const type = typeof raw.type === "string" && raw.type.trim() ? raw.type.trim() : "container";
329
+ let id = typeof raw.id === "string" && raw.id.trim() ? raw.id.trim() : "";
330
+ if (!id || usedIds.has(id)) {
331
+ let counter = 1;
332
+ let candidate = `${type}-${counter}`;
333
+ while (usedIds.has(candidate)) {
334
+ counter++;
335
+ candidate = `${type}-${counter}`;
336
+ }
337
+ id = candidate;
338
+ }
339
+ usedIds.add(id);
340
+ const props = raw.props && typeof raw.props === "object" && !Array.isArray(raw.props) ? { ...raw.props } : {};
341
+ if (type === "heading" && typeof props.level === "string") {
342
+ const parsedLevel = parseInt(props.level, 10);
343
+ if (!isNaN(parsedLevel) && parsedLevel >= 1 && parsedLevel <= 6) {
344
+ props.level = parsedLevel;
345
+ }
346
+ }
347
+ const styles = normalizeStyles(raw.styles);
348
+ let children = void 0;
349
+ if (Array.isArray(raw.children)) {
350
+ children = raw.children.filter((c) => c && typeof c === "object").map((c) => normalizeNodeTree(c, usedIds, depth + 1));
351
+ }
352
+ return {
353
+ id,
354
+ type,
355
+ props: Object.keys(props).length > 0 ? props : void 0,
356
+ styles,
357
+ children
358
+ };
359
+ }
360
+ function normalizeAndValidatePageDocument(rawJson, securityLimits) {
361
+ if (!rawJson || typeof rawJson !== "object") {
362
+ throw new Error("AI output must be a JSON object");
363
+ }
364
+ const obj = rawJson;
365
+ let docObject;
366
+ if (obj.type === "page" && !obj.document) {
367
+ docObject = {
368
+ schema: import_schema.SCHEMA_NAME,
369
+ version: import_schema.CURRENT_SCHEMA_VERSION,
370
+ metadata: {
371
+ title: typeof obj.props === "object" && obj.props !== null && obj.props.title || "AI Generated Page",
372
+ description: "Generated by KUBUILD AI"
373
+ },
374
+ document: obj
375
+ };
376
+ } else {
377
+ docObject = {
378
+ schema: import_schema.SCHEMA_NAME,
379
+ version: typeof obj.version === "string" ? obj.version : import_schema.CURRENT_SCHEMA_VERSION,
380
+ metadata: typeof obj.metadata === "object" && obj.metadata !== null ? obj.metadata : { title: "AI Generated Page", description: "Generated by KUBUILD AI" },
381
+ document: obj.document || obj
382
+ };
383
+ }
384
+ const usedIds = /* @__PURE__ */ new Set();
385
+ const normalizedRoot = normalizeNodeTree(docObject.document, usedIds);
386
+ if (normalizedRoot.type !== "page") {
387
+ normalizedRoot.type = "page";
388
+ }
389
+ const parsedMetadata = import_schema.DocumentMetadataSchema.parse({
390
+ title: "AI Generated Page",
391
+ description: "Generated by KUBUILD AI",
392
+ author: "KUBUILD AI",
393
+ tags: ["ai-generated"],
394
+ category: "general",
395
+ version: "1.0.0",
396
+ ...typeof docObject.metadata === "object" && docObject.metadata !== null ? docObject.metadata : {}
397
+ });
398
+ const finalDocument = {
399
+ schema: import_schema.SCHEMA_NAME,
400
+ version: import_schema.CURRENT_SCHEMA_VERSION,
401
+ metadata: parsedMetadata,
402
+ document: normalizedRoot
403
+ };
404
+ const parsed = import_schema.PageDocumentSchema.safeParse(finalDocument);
405
+ if (!parsed.success) {
406
+ const issues = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
407
+ throw new Error(`Document schema validation failed: ${issues}`);
408
+ }
409
+ const validationResult = (0, import_core.validateDocument)(finalDocument, { securityLimits });
410
+ if (!validationResult.valid) {
411
+ const errorMsgs = validationResult.errors.map((e) => `${e.code}: ${e.message} (${e.path})`).join("; ");
412
+ throw new Error(`Document validation failed: ${errorMsgs}`);
413
+ }
414
+ return parsed.data;
415
+ }
416
+ function normalizeAndValidateSectionNode(rawJson, securityLimits) {
417
+ if (!rawJson || typeof rawJson !== "object") {
418
+ throw new Error("AI output must be a JSON object");
419
+ }
420
+ const usedIds = /* @__PURE__ */ new Set();
421
+ const normalized = normalizeNodeTree(rawJson, usedIds);
422
+ if (normalized.type !== "section") {
423
+ normalized.type = "section";
424
+ }
425
+ const parsed = import_schema.NodeSchema.safeParse(normalized);
426
+ if (!parsed.success) {
427
+ const issues = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
428
+ throw new Error(`Section node schema validation failed: ${issues}`);
429
+ }
430
+ const secCheck = (0, import_core.validateDocumentSecurity)({ document: normalized }, securityLimits);
431
+ if (!secCheck.safe) {
432
+ const issues = secCheck.errors.map((e) => `${e.code}: ${e.message}`).join("; ");
433
+ throw new Error(`Section security check failed: ${issues}`);
434
+ }
435
+ return parsed.data;
436
+ }
437
+ function normalizeAndValidateRefactoredNode(rawJson, originalNode, securityLimits) {
438
+ if (!rawJson || typeof rawJson !== "object") {
439
+ throw new Error("AI output must be a JSON object");
440
+ }
441
+ const usedIds = /* @__PURE__ */ new Set();
442
+ const normalized = normalizeNodeTree(rawJson, usedIds);
443
+ normalized.id = originalNode.id;
444
+ normalized.type = originalNode.type;
445
+ const parsed = import_schema.NodeSchema.safeParse(normalized);
446
+ if (!parsed.success) {
447
+ const issues = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
448
+ throw new Error(`Refactored node schema validation failed: ${issues}`);
449
+ }
450
+ const secCheck = (0, import_core.validateDocumentSecurity)({ document: normalized }, securityLimits);
451
+ if (!secCheck.safe) {
452
+ const issues = secCheck.errors.map((e) => `${e.code}: ${e.message}`).join("; ");
453
+ throw new Error(`Refactored node security check failed: ${issues}`);
454
+ }
455
+ return parsed.data;
456
+ }
457
+
458
+ // src/server/engine.ts
459
+ var KubuildAiEngine = class {
460
+ options;
461
+ catalog;
462
+ constructor(options) {
463
+ if (!options.adapter) {
464
+ throw new Error("KubuildAiEngine requires an adapter instance");
465
+ }
466
+ this.options = options;
467
+ this.catalog = compileComponentCatalog(options.registry);
468
+ }
469
+ get adapterName() {
470
+ return this.options.adapter.name;
471
+ }
472
+ refreshCatalog() {
473
+ this.catalog = compileComponentCatalog(this.options.registry);
474
+ }
475
+ log(level, message, meta) {
476
+ if (this.options.logger) {
477
+ this.options.logger(level, message, meta);
478
+ } else if (this.options.debug) {
479
+ const ts = (/* @__PURE__ */ new Date()).toISOString();
480
+ console.log(`[KUBUILD-AI ${level.toUpperCase()}] [${ts}] ${message}`, meta !== void 0 ? meta : "");
481
+ }
482
+ }
483
+ async generatePage(request, context) {
484
+ let rawText = "";
485
+ try {
486
+ this.log("info", `Generating full page for prompt: "${request.prompt}"`);
487
+ const systemPrompt = buildSystemPrompt({
488
+ catalog: this.catalog,
489
+ mode: "full-page",
490
+ prefix: this.options.systemPromptPrefix,
491
+ stylePreference: request.stylePreference
492
+ });
493
+ let userPrompt = `User Request: ${request.prompt}`;
494
+ if (request.tone) userPrompt += `
495
+ Tone: ${request.tone}`;
496
+ if (request.locale) userPrompt += `
497
+ Language/Locale: ${request.locale}`;
498
+ const jsonSchema = buildJsonSchemaForMode("full-page");
499
+ const result = await this.options.adapter.generate({
500
+ systemPrompt,
501
+ userPrompt,
502
+ jsonSchema,
503
+ signal: context?.signal
504
+ });
505
+ this.log("debug", "Raw model response (generatePage)", result.text);
506
+ rawText = result.text;
507
+ const rawJson = extractJsonFromResponse(rawText);
508
+ const document = normalizeAndValidatePageDocument(
509
+ rawJson,
510
+ this.options.securityLimits
511
+ );
512
+ if (request.metadata) {
513
+ document.metadata = import_schema2.DocumentMetadataSchema.parse({
514
+ ...document.metadata,
515
+ ...request.metadata
516
+ });
517
+ }
518
+ this.log(
519
+ "info",
520
+ `Full page successfully generated: "${document.metadata?.title || "Untitled"}" (${document.document.children?.length || 0} sections)`
521
+ );
522
+ return {
523
+ success: true,
524
+ data: document,
525
+ usage: result.usage,
526
+ rawModelResponse: rawText
527
+ };
528
+ } catch (err) {
529
+ const message = err instanceof Error ? err.message : String(err);
530
+ this.log("error", `Generation failed: ${message}`);
531
+ return {
532
+ success: false,
533
+ error: {
534
+ code: "GENERATION_ERROR",
535
+ message
536
+ },
537
+ rawModelResponse: rawText || void 0
538
+ };
539
+ }
540
+ }
541
+ async generateSection(request, context) {
542
+ let rawText = "";
543
+ try {
544
+ const systemPrompt = buildSystemPrompt({
545
+ catalog: this.catalog,
546
+ mode: "section",
547
+ prefix: this.options.systemPromptPrefix,
548
+ stylePreference: request.stylePreference
549
+ });
550
+ let userPrompt = `Generate a single section node for: ${request.prompt}`;
551
+ if (request.targetSectionType) {
552
+ userPrompt += `
553
+ Target Section Purpose: ${request.targetSectionType}`;
554
+ }
555
+ if (request.parentContext) {
556
+ userPrompt += `
557
+ Surrounding Page Context: ${request.parentContext}`;
558
+ }
559
+ const jsonSchema = buildJsonSchemaForMode("section");
560
+ const result = await this.options.adapter.generate({
561
+ systemPrompt,
562
+ userPrompt,
563
+ jsonSchema,
564
+ signal: context?.signal
565
+ });
566
+ this.log("debug", "Raw model response (generateSection)", result.text);
567
+ rawText = result.text;
568
+ const rawJson = extractJsonFromResponse(rawText);
569
+ const sectionNode = normalizeAndValidateSectionNode(
570
+ rawJson,
571
+ this.options.securityLimits
572
+ );
573
+ return {
574
+ success: true,
575
+ data: sectionNode,
576
+ usage: result.usage,
577
+ rawModelResponse: rawText
578
+ };
579
+ } catch (err) {
580
+ const message = err instanceof Error ? err.message : String(err);
581
+ return {
582
+ success: false,
583
+ error: {
584
+ code: "SECTION_GENERATION_ERROR",
585
+ message
586
+ },
587
+ rawModelResponse: rawText || void 0
588
+ };
589
+ }
590
+ }
591
+ async refactorNode(request, context) {
592
+ let rawText = "";
593
+ try {
594
+ const systemPrompt = buildSystemPrompt({
595
+ catalog: this.catalog,
596
+ mode: "refactor",
597
+ prefix: this.options.systemPromptPrefix,
598
+ stylePreference: request.stylePreference
599
+ });
600
+ const userPrompt = `Instruction: ${request.instruction}
601
+
602
+ Current Node:
603
+ ${JSON.stringify(request.node, null, 2)}`;
604
+ const jsonSchema = buildJsonSchemaForMode("refactor");
605
+ const result = await this.options.adapter.generate({
606
+ systemPrompt,
607
+ userPrompt,
608
+ jsonSchema,
609
+ signal: context?.signal
610
+ });
611
+ rawText = result.text;
612
+ const rawJson = extractJsonFromResponse(rawText);
613
+ const updatedNode = normalizeAndValidateRefactoredNode(
614
+ rawJson,
615
+ request.node,
616
+ this.options.securityLimits
617
+ );
618
+ return {
619
+ success: true,
620
+ data: updatedNode,
621
+ usage: result.usage,
622
+ rawModelResponse: rawText
623
+ };
624
+ } catch (err) {
625
+ const message = err instanceof Error ? err.message : String(err);
626
+ return {
627
+ success: false,
628
+ error: {
629
+ code: "REFACTOR_ERROR",
630
+ message
631
+ },
632
+ rawModelResponse: rawText || void 0
633
+ };
634
+ }
635
+ }
636
+ /**
637
+ * Progressive Section Streaming Generator.
638
+ * Emits structured SSE events: status -> metadata -> section (one by one) -> complete.
639
+ */
640
+ async *streamPage(request, context) {
641
+ try {
642
+ this.log("info", `[SSE] Starting streamPage for prompt: "${request.prompt}"`);
643
+ yield {
644
+ type: "status",
645
+ message: "Analyzing requirements and planning page sections..."
646
+ };
647
+ const planSystemPrompt = `
648
+ You are a web architect for the KUBUILD page builder.
649
+ Given the user's prompt, plan the website structure. Output pure JSON (no markdown fences, no explanatory text):
650
+ {
651
+ "title": "<Page Title>",
652
+ "description": "<Page Description>",
653
+ "pageStyles": {
654
+ "base": {
655
+ "backgroundColor": "#ffffff",
656
+ "fontFamily": "Inter, system-ui, sans-serif",
657
+ "color": "#111827",
658
+ "minHeight": "100vh"
659
+ }
660
+ },
661
+ "sections": [
662
+ {
663
+ "type": "<hero|features|pricing|testimonials|cta|contact|footer>",
664
+ "title": "<Brief title>",
665
+ "prompt": "<Detailed prompt for generating this specific section with components, layout, and content>"
666
+ }
667
+ ]
668
+ }
669
+ Plan 3 cohesive, essential sections (e.g., hero, features, and cta) that fulfill the request.
670
+ `;
671
+ let planUserPrompt = `User Request: ${request.prompt}`;
672
+ if (request.stylePreference) {
673
+ planUserPrompt += `
674
+ Style Preference: ${request.stylePreference}`;
675
+ }
676
+ if (request.tone) planUserPrompt += `
677
+ Tone: ${request.tone}`;
678
+ if (request.locale) planUserPrompt += `
679
+ Locale: ${request.locale}`;
680
+ this.log("info", "[SSE] Generating website layout plan...");
681
+ const planResult = await this.options.adapter.generate({
682
+ systemPrompt: planSystemPrompt,
683
+ userPrompt: planUserPrompt,
684
+ signal: context?.signal
685
+ });
686
+ this.log("debug", "[SSE] Raw plan response from model", planResult.text);
687
+ let plan;
688
+ try {
689
+ plan = extractJsonFromResponse(planResult.text);
690
+ this.log("debug", "[SSE] Parsed plan successfully", plan);
691
+ } catch (parseErr) {
692
+ this.log("warn", "[SSE] Failed to parse plan JSON, using fallback plan", parseErr);
693
+ plan = {
694
+ title: "AI Generated Page",
695
+ description: "Generated by KUBUILD AI",
696
+ sections: [
697
+ { type: "hero", title: "Hero Banner", prompt: `Hero section for: ${request.prompt}` },
698
+ { type: "features", title: "Key Features", prompt: `Features grid for: ${request.prompt}` },
699
+ { type: "cta", title: "Call To Action", prompt: `Call to action section for: ${request.prompt}` }
700
+ ]
701
+ };
702
+ }
703
+ const sectionsToGenerate = Array.isArray(plan.sections) && plan.sections.length > 0 ? plan.sections : [
704
+ { type: "hero", title: "Hero Section", prompt: `Hero banner for: ${request.prompt}` },
705
+ { type: "features", title: "Features", prompt: `Features grid for: ${request.prompt}` },
706
+ { type: "cta", title: "Call To Action", prompt: `CTA section for: ${request.prompt}` }
707
+ ];
708
+ const metadata = import_schema2.DocumentMetadataSchema.parse({
709
+ title: plan.title || "AI Generated Page",
710
+ description: plan.description || "Generated by KUBUILD AI",
711
+ author: "KUBUILD AI",
712
+ tags: ["ai-generated", "progressive-stream"],
713
+ category: "landing",
714
+ version: "1.0.0",
715
+ ...request.metadata || {}
716
+ });
717
+ const rootPageNode = {
718
+ id: "root-page",
719
+ type: "page",
720
+ styles: plan.pageStyles || {
721
+ base: {
722
+ backgroundColor: "#ffffff",
723
+ fontFamily: "Inter, system-ui, sans-serif",
724
+ color: "#111827",
725
+ minHeight: "100vh"
726
+ }
727
+ },
728
+ children: []
729
+ };
730
+ this.log("info", `[SSE] Emitting metadata: "${metadata.title}"`);
731
+ yield {
732
+ type: "metadata",
733
+ metadata,
734
+ rootPageNode
735
+ };
736
+ this.log(
737
+ "info",
738
+ `[SSE] Plan ready with ${sectionsToGenerate.length} sections: ${sectionsToGenerate.map((s) => s.title).join(", ")}`
739
+ );
740
+ yield {
741
+ type: "status",
742
+ message: `Plan ready with ${sectionsToGenerate.length} sections: ${sectionsToGenerate.map((s) => s.title).join(", ")}`
743
+ };
744
+ const completedSections = [];
745
+ const total = sectionsToGenerate.length;
746
+ for (let i = 0; i < total; i++) {
747
+ if (context?.signal?.aborted) {
748
+ this.log("warn", "[SSE] Streaming aborted by client signal");
749
+ throw new Error("Streaming aborted by client");
750
+ }
751
+ const plannedSec = sectionsToGenerate[i];
752
+ this.log(
753
+ "info",
754
+ `[SSE] Generating section ${i + 1}/${total} [${plannedSec.type}]: "${plannedSec.title}"`
755
+ );
756
+ yield {
757
+ type: "status",
758
+ message: `Generating section ${i + 1}/${total} (${plannedSec.title})...`
759
+ };
760
+ const secRes = await this.generateSection(
761
+ {
762
+ prompt: plannedSec.prompt,
763
+ stylePreference: request.stylePreference,
764
+ targetSectionType: plannedSec.type,
765
+ parentContext: `Website: ${plan.title}. Previous sections: ${sectionsToGenerate.slice(0, i).map((s) => s.title).join(", ")}`
766
+ },
767
+ { signal: context?.signal }
768
+ );
769
+ if (secRes.success && secRes.data) {
770
+ this.log(
771
+ "info",
772
+ `[SSE] Section ${i + 1}/${total} generated successfully: [${secRes.data.id || secRes.data.type}]`
773
+ );
774
+ completedSections.push(secRes.data);
775
+ yield {
776
+ type: "section",
777
+ index: i,
778
+ total,
779
+ section: secRes.data
780
+ };
781
+ } else {
782
+ this.log("error", `[SSE] Section ${i + 1}/${total} failed`, secRes.error?.message);
783
+ }
784
+ }
785
+ if (completedSections.length === 0) {
786
+ this.log("error", "[SSE] All sections failed to generate.");
787
+ yield {
788
+ type: "error",
789
+ error: {
790
+ code: "STREAM_GENERATION_FAILED",
791
+ message: "All sections failed to generate. Please check server logs or retry."
792
+ }
793
+ };
794
+ return;
795
+ }
796
+ const finalDocument = {
797
+ schema: "stora.page",
798
+ version: "1.0.0",
799
+ metadata,
800
+ document: {
801
+ ...rootPageNode,
802
+ children: completedSections
803
+ }
804
+ };
805
+ this.log(
806
+ "info",
807
+ `[SSE] All ${completedSections.length} sections generated. Emitting complete event.`
808
+ );
809
+ yield {
810
+ type: "complete",
811
+ document: finalDocument
812
+ };
813
+ } catch (err) {
814
+ const message = err instanceof Error ? err.message : String(err);
815
+ this.log("error", `[SSE] Streaming failed: ${message}`);
816
+ yield {
817
+ type: "error",
818
+ error: {
819
+ code: "STREAM_ERROR",
820
+ message
821
+ }
822
+ };
823
+ }
824
+ }
825
+ /**
826
+ * Builds the system/user prompt pair shared by `chat()` and `chatStream()` — the exact
827
+ * same document/selection grounding is used regardless of whether the response is
828
+ * streamed token-by-token or returned as a single request/response (STORA-515).
829
+ */
830
+ buildChatPrompt(request) {
831
+ let systemPrompt = `You are the KUBUILD AI Assistant \u2014 an expert web designer, developer, and page architecture consultant.
832
+ Your role is to assist users with creating, designing, refining, and understanding web pages built with the KUBUILD builder.
833
+ Answer questions directly, concisely, and helpfully using friendly professional tone and markdown formatting.
834
+
835
+ ### Available KUBUILD Components:
836
+ ${this.catalog.map((c) => `- **${c.type}** (${c.category}): ${c.label}${c.description ? ` \u2014 ${c.description}` : ""}`).join("\n")}
837
+ `;
838
+ if (this.options.systemPromptPrefix) {
839
+ systemPrompt = `${this.options.systemPromptPrefix}
840
+
841
+ ${systemPrompt}`;
842
+ }
843
+ if (request.systemPrompt) {
844
+ systemPrompt += `
845
+ Additional Context:
846
+ ${request.systemPrompt}`;
847
+ }
848
+ if (request.currentDocument) {
849
+ const doc = request.currentDocument;
850
+ const sections = doc.document?.children || [];
851
+ const sectionSummary = sections.map((s, idx) => `Section ${idx + 1}: id="${s.id}", type="${s.type}", title="${s.props?.title || "untitled"}", childrenCount=${s.children?.length || 0}`).join("\n");
852
+ systemPrompt += `
853
+ ### Current Page in Builder Canvas:
854
+ - Title: ${doc.metadata?.title || "Untitled Page"}
855
+ - Description: ${doc.metadata?.description || "N/A"}
856
+ - Total Sections: ${sections.length}
857
+ - Sections Summary:
858
+ ${sectionSummary || "(Canvas is currently empty)"}
859
+ `;
860
+ }
861
+ if (request.selectedNodeId) {
862
+ systemPrompt += `
863
+ Currently Selected Component Node ID: "${request.selectedNodeId}"`;
864
+ }
865
+ const lastUserMsg = [...request.messages].reverse().find((m) => m.role === "user");
866
+ const userPrompt = lastUserMsg?.content || "Hello";
867
+ return { systemPrompt, userPrompt };
868
+ }
869
+ /**
870
+ * Conversational Assistant / Q&A.
871
+ * Can discuss web design, explain KUBUILD components, review current page,
872
+ * suggest improvements, or recommend copywriting.
873
+ *
874
+ * Single request/response — unchanged since before STORA-515. See `chatStream()` for
875
+ * the token-level streaming variant.
876
+ */
877
+ async chat(request, context) {
878
+ let rawText = "";
879
+ try {
880
+ if (!request.messages || !Array.isArray(request.messages) || request.messages.length === 0) {
881
+ return {
882
+ success: false,
883
+ error: {
884
+ code: "INVALID_CHAT_REQUEST",
885
+ message: '"messages" array is required and must not be empty'
886
+ }
887
+ };
888
+ }
889
+ this.log("info", `Processing chat turn with ${request.messages.length} messages`);
890
+ const { systemPrompt, userPrompt } = this.buildChatPrompt(request);
891
+ const result = await this.options.adapter.generate({
892
+ systemPrompt,
893
+ userPrompt,
894
+ messages: request.messages,
895
+ signal: context?.signal
896
+ });
897
+ rawText = result.text.trim();
898
+ const assistantMessage = {
899
+ role: "assistant",
900
+ content: rawText,
901
+ timestamp: Date.now()
902
+ };
903
+ return {
904
+ success: true,
905
+ data: {
906
+ message: assistantMessage
907
+ },
908
+ usage: result.usage,
909
+ rawModelResponse: rawText
910
+ };
911
+ } catch (err) {
912
+ const message = err instanceof Error ? err.message : String(err);
913
+ this.log("error", `Chat failed: ${message}`);
914
+ return {
915
+ success: false,
916
+ error: {
917
+ code: "CHAT_ERROR",
918
+ message
919
+ },
920
+ rawModelResponse: rawText || void 0
921
+ };
922
+ }
923
+ }
924
+ /**
925
+ * Token-level streaming variant of `chat()` (STORA-515). Emits `chat-chunk` events as
926
+ * partial text arrives, then a terminal `chat-complete` event with the fully assembled
927
+ * assistant message — the same `AsyncIterable<AiStreamEvent>` mechanism `streamPage`
928
+ * already uses, so it can be piped over SSE by `createAiHandler`/consumed by
929
+ * `KubuildAiClient` with no separate transport.
930
+ *
931
+ * When the configured adapter does not implement `generateStream` (e.g. the generic
932
+ * custom/HTTP adapter), this falls back to a single non-streaming `generate()` call and
933
+ * emits the entire response as one `chat-chunk`, so callers never see an error just
934
+ * because the underlying provider can't stream — only a less granular update cadence.
935
+ */
936
+ async *chatStream(request, context) {
937
+ try {
938
+ if (!request.messages || !Array.isArray(request.messages) || request.messages.length === 0) {
939
+ yield {
940
+ type: "error",
941
+ error: {
942
+ code: "INVALID_CHAT_REQUEST",
943
+ message: '"messages" array is required and must not be empty'
944
+ }
945
+ };
946
+ return;
947
+ }
948
+ this.log(
949
+ "info",
950
+ `[SSE] Starting chatStream with ${request.messages.length} messages (adapter: ${this.options.adapter.name})`
951
+ );
952
+ const { systemPrompt, userPrompt } = this.buildChatPrompt(request);
953
+ let accumulated = "";
954
+ let usage;
955
+ if (typeof this.options.adapter.generateStream === "function") {
956
+ const generator = this.options.adapter.generateStream({
957
+ systemPrompt,
958
+ userPrompt,
959
+ messages: request.messages,
960
+ signal: context?.signal
961
+ });
962
+ while (true) {
963
+ const { value, done } = await generator.next();
964
+ if (done) {
965
+ usage = value?.usage;
966
+ break;
967
+ }
968
+ accumulated += value;
969
+ yield { type: "chat-chunk", delta: value, content: accumulated };
970
+ }
971
+ } else {
972
+ this.log(
973
+ "debug",
974
+ `Adapter "${this.options.adapter.name}" has no generateStream; falling back to non-streaming chat`
975
+ );
976
+ const result = await this.options.adapter.generate({
977
+ systemPrompt,
978
+ userPrompt,
979
+ messages: request.messages,
980
+ signal: context?.signal
981
+ });
982
+ accumulated = result.text;
983
+ usage = result.usage;
984
+ yield { type: "chat-chunk", delta: accumulated, content: accumulated };
985
+ }
986
+ const assistantMessage = {
987
+ role: "assistant",
988
+ content: accumulated.trim(),
989
+ timestamp: Date.now()
990
+ };
991
+ this.log("info", "[SSE] Chat stream completed", usage);
992
+ yield { type: "chat-complete", message: assistantMessage };
993
+ } catch (err) {
994
+ const message = err instanceof Error ? err.message : String(err);
995
+ this.log("error", `[SSE] Chat streaming failed: ${message}`);
996
+ yield {
997
+ type: "error",
998
+ error: {
999
+ code: "CHAT_STREAM_ERROR",
1000
+ message
1001
+ }
1002
+ };
1003
+ }
1004
+ }
1005
+ };
1006
+
1007
+ // src/server/handler.ts
1008
+ async function processAiRequest(engine, body, signal) {
1009
+ if (!body || typeof body !== "object") {
1010
+ return {
1011
+ status: 400,
1012
+ response: {
1013
+ success: false,
1014
+ error: {
1015
+ code: "BAD_REQUEST",
1016
+ message: "Request body must be a valid JSON object"
1017
+ }
1018
+ }
1019
+ };
1020
+ }
1021
+ const payload = body;
1022
+ const mode = payload.mode || "full-page";
1023
+ if (mode === "full-page") {
1024
+ if (!payload.prompt || typeof payload.prompt !== "string") {
1025
+ return {
1026
+ status: 400,
1027
+ response: {
1028
+ success: false,
1029
+ error: {
1030
+ code: "INVALID_PROMPT",
1031
+ message: '"prompt" is required for full-page generation'
1032
+ }
1033
+ }
1034
+ };
1035
+ }
1036
+ const result = await engine.generatePage(
1037
+ {
1038
+ prompt: payload.prompt,
1039
+ stylePreference: payload.stylePreference,
1040
+ tone: payload.tone,
1041
+ locale: payload.locale,
1042
+ metadata: payload.metadata
1043
+ },
1044
+ { signal }
1045
+ );
1046
+ return {
1047
+ status: result.success ? 200 : 500,
1048
+ response: result
1049
+ };
1050
+ }
1051
+ if (mode === "section") {
1052
+ if (!payload.prompt || typeof payload.prompt !== "string") {
1053
+ return {
1054
+ status: 400,
1055
+ response: {
1056
+ success: false,
1057
+ error: {
1058
+ code: "INVALID_PROMPT",
1059
+ message: '"prompt" is required for section generation'
1060
+ }
1061
+ }
1062
+ };
1063
+ }
1064
+ const result = await engine.generateSection(
1065
+ {
1066
+ prompt: payload.prompt,
1067
+ stylePreference: payload.stylePreference,
1068
+ targetSectionType: payload.targetSectionType,
1069
+ parentContext: payload.parentContext
1070
+ },
1071
+ { signal }
1072
+ );
1073
+ return {
1074
+ status: result.success ? 200 : 500,
1075
+ response: result
1076
+ };
1077
+ }
1078
+ if (mode === "refactor") {
1079
+ if (!payload.node || !payload.instruction) {
1080
+ return {
1081
+ status: 400,
1082
+ response: {
1083
+ success: false,
1084
+ error: {
1085
+ code: "INVALID_REFACTOR_PARAMS",
1086
+ message: '"node" and "instruction" are required for refactoring'
1087
+ }
1088
+ }
1089
+ };
1090
+ }
1091
+ const result = await engine.refactorNode(
1092
+ {
1093
+ node: payload.node,
1094
+ instruction: payload.instruction,
1095
+ stylePreference: payload.stylePreference
1096
+ },
1097
+ { signal }
1098
+ );
1099
+ return {
1100
+ status: result.success ? 200 : 500,
1101
+ response: result
1102
+ };
1103
+ }
1104
+ if (mode === "chat") {
1105
+ if (!payload.messages || !Array.isArray(payload.messages) || payload.messages.length === 0) {
1106
+ return {
1107
+ status: 400,
1108
+ response: {
1109
+ success: false,
1110
+ error: {
1111
+ code: "INVALID_CHAT_PARAMS",
1112
+ message: '"messages" array is required and must not be empty for chat mode'
1113
+ }
1114
+ }
1115
+ };
1116
+ }
1117
+ const result = await engine.chat(
1118
+ {
1119
+ messages: payload.messages,
1120
+ currentDocument: payload.currentDocument,
1121
+ selectedNodeId: payload.selectedNodeId
1122
+ },
1123
+ { signal }
1124
+ );
1125
+ return {
1126
+ status: result.success ? 200 : 500,
1127
+ response: result
1128
+ };
1129
+ }
1130
+ return {
1131
+ status: 400,
1132
+ response: {
1133
+ success: false,
1134
+ error: {
1135
+ code: "UNKNOWN_MODE",
1136
+ message: `Unsupported mode: ${String(mode)}. Supported modes: 'full-page', 'section', 'refactor', 'chat'`
1137
+ }
1138
+ }
1139
+ };
1140
+ }
1141
+ function createAiHandler(engine, options) {
1142
+ return async (request) => {
1143
+ try {
1144
+ if (options?.beforeRequest) {
1145
+ const hookResult = await options.beforeRequest(request);
1146
+ if (hookResult instanceof Response) {
1147
+ return hookResult;
1148
+ }
1149
+ }
1150
+ if (request.method !== "POST") {
1151
+ return new Response(
1152
+ JSON.stringify({
1153
+ success: false,
1154
+ error: {
1155
+ code: "METHOD_NOT_ALLOWED",
1156
+ message: "Only POST requests are accepted"
1157
+ }
1158
+ }),
1159
+ {
1160
+ status: 405,
1161
+ headers: {
1162
+ "Content-Type": "application/json",
1163
+ Allow: "POST"
1164
+ }
1165
+ }
1166
+ );
1167
+ }
1168
+ const body = await request.json().catch(() => null);
1169
+ if (body && typeof body === "object" && body.stream === true) {
1170
+ const mode = body.mode || "full-page";
1171
+ if (mode === "chat") {
1172
+ if (!body.messages || !Array.isArray(body.messages) || body.messages.length === 0) {
1173
+ return new Response(
1174
+ JSON.stringify({
1175
+ success: false,
1176
+ error: {
1177
+ code: "INVALID_CHAT_PARAMS",
1178
+ message: '"messages" array is required and must not be empty for chat mode'
1179
+ }
1180
+ }),
1181
+ {
1182
+ status: 400,
1183
+ headers: { "Content-Type": "application/json" }
1184
+ }
1185
+ );
1186
+ }
1187
+ const encoder2 = new TextEncoder();
1188
+ const readable2 = new ReadableStream({
1189
+ async start(controller) {
1190
+ try {
1191
+ for await (const event of engine.chatStream(
1192
+ {
1193
+ messages: body.messages,
1194
+ currentDocument: body.currentDocument,
1195
+ selectedNodeId: body.selectedNodeId
1196
+ },
1197
+ { signal: request.signal }
1198
+ )) {
1199
+ controller.enqueue(
1200
+ encoder2.encode(`event: ${event.type}
1201
+ data: ${JSON.stringify(event)}
1202
+
1203
+ `)
1204
+ );
1205
+ }
1206
+ } catch (err) {
1207
+ const message = err instanceof Error ? err.message : String(err);
1208
+ controller.enqueue(
1209
+ encoder2.encode(
1210
+ `event: error
1211
+ data: ${JSON.stringify({ type: "error", error: { code: "CHAT_STREAM_ERROR", message } })}
1212
+
1213
+ `
1214
+ )
1215
+ );
1216
+ } finally {
1217
+ controller.close();
1218
+ }
1219
+ }
1220
+ });
1221
+ return new Response(readable2, {
1222
+ status: 200,
1223
+ headers: {
1224
+ "Content-Type": "text/event-stream",
1225
+ "Cache-Control": "no-cache, no-transform",
1226
+ Connection: "keep-alive"
1227
+ }
1228
+ });
1229
+ }
1230
+ if (!body.prompt || typeof body.prompt !== "string") {
1231
+ return new Response(
1232
+ JSON.stringify({
1233
+ success: false,
1234
+ error: {
1235
+ code: "INVALID_PROMPT",
1236
+ message: '"prompt" is required for streaming generation'
1237
+ }
1238
+ }),
1239
+ {
1240
+ status: 400,
1241
+ headers: { "Content-Type": "application/json" }
1242
+ }
1243
+ );
1244
+ }
1245
+ const encoder = new TextEncoder();
1246
+ const readable = new ReadableStream({
1247
+ async start(controller) {
1248
+ try {
1249
+ for await (const event of engine.streamPage(
1250
+ {
1251
+ prompt: body.prompt,
1252
+ stylePreference: body.stylePreference,
1253
+ tone: body.tone,
1254
+ locale: body.locale,
1255
+ metadata: body.metadata
1256
+ },
1257
+ { signal: request.signal }
1258
+ )) {
1259
+ controller.enqueue(
1260
+ encoder.encode(`event: ${event.type}
1261
+ data: ${JSON.stringify(event)}
1262
+
1263
+ `)
1264
+ );
1265
+ }
1266
+ } catch (err) {
1267
+ const message = err instanceof Error ? err.message : String(err);
1268
+ controller.enqueue(
1269
+ encoder.encode(
1270
+ `event: error
1271
+ data: ${JSON.stringify({ type: "error", error: { code: "STREAM_ERROR", message } })}
1272
+
1273
+ `
1274
+ )
1275
+ );
1276
+ } finally {
1277
+ controller.close();
1278
+ }
1279
+ }
1280
+ });
1281
+ return new Response(readable, {
1282
+ status: 200,
1283
+ headers: {
1284
+ "Content-Type": "text/event-stream",
1285
+ "Cache-Control": "no-cache, no-transform",
1286
+ Connection: "keep-alive"
1287
+ }
1288
+ });
1289
+ }
1290
+ const { status, response } = await processAiRequest(
1291
+ engine,
1292
+ body,
1293
+ request.signal
1294
+ );
1295
+ return new Response(JSON.stringify(response), {
1296
+ status,
1297
+ headers: {
1298
+ "Content-Type": "application/json"
1299
+ }
1300
+ });
1301
+ } catch (err) {
1302
+ const message = err instanceof Error ? err.message : String(err);
1303
+ return new Response(
1304
+ JSON.stringify({
1305
+ success: false,
1306
+ error: {
1307
+ code: "INTERNAL_SERVER_ERROR",
1308
+ message
1309
+ }
1310
+ }),
1311
+ {
1312
+ status: 500,
1313
+ headers: {
1314
+ "Content-Type": "application/json"
1315
+ }
1316
+ }
1317
+ );
1318
+ }
1319
+ };
1320
+ }
1321
+
1322
+ // src/server/adapters/stream-utils.ts
1323
+ async function* readSseDataLines(body, signal) {
1324
+ const reader = body.getReader();
1325
+ const decoder = new TextDecoder();
1326
+ let buffer = "";
1327
+ try {
1328
+ while (true) {
1329
+ if (signal?.aborted) {
1330
+ throw new Error("Streaming aborted by client");
1331
+ }
1332
+ const { done, value } = await reader.read();
1333
+ if (done) break;
1334
+ buffer += decoder.decode(value, { stream: true });
1335
+ const frames = buffer.split("\n\n");
1336
+ buffer = frames.pop() || "";
1337
+ for (const frame of frames) {
1338
+ for (const line of frame.split("\n")) {
1339
+ const trimmed = line.trim();
1340
+ if (trimmed.startsWith("data:")) {
1341
+ yield trimmed.slice(5).trim();
1342
+ }
1343
+ }
1344
+ }
1345
+ }
1346
+ } finally {
1347
+ reader.releaseLock();
1348
+ }
1349
+ }
1350
+
1351
+ // src/server/adapters/gemini.ts
1352
+ var GeminiAdapter = class {
1353
+ name = "gemini";
1354
+ apiKey;
1355
+ model;
1356
+ temperature;
1357
+ baseUrl;
1358
+ constructor(options) {
1359
+ if (!options.apiKey) {
1360
+ throw new Error("GeminiAdapter requires an apiKey");
1361
+ }
1362
+ this.apiKey = options.apiKey;
1363
+ this.model = options.model || "gemini-2.5-flash";
1364
+ this.temperature = options.temperature ?? 0.4;
1365
+ this.baseUrl = options.baseUrl || "https://generativelanguage.googleapis.com/v1beta";
1366
+ }
1367
+ buildContents(params) {
1368
+ const { userPrompt, messages: chatMessages } = params;
1369
+ return chatMessages && chatMessages.length > 0 ? chatMessages.map((m) => ({
1370
+ role: m.role === "assistant" ? "model" : "user",
1371
+ parts: [{ text: m.content }]
1372
+ })) : [
1373
+ {
1374
+ role: "user",
1375
+ parts: [{ text: userPrompt }]
1376
+ }
1377
+ ];
1378
+ }
1379
+ async generate(params) {
1380
+ const { systemPrompt, messages: chatMessages, signal } = params;
1381
+ const url = `${this.baseUrl}/models/${encodeURIComponent(this.model)}:generateContent?key=${encodeURIComponent(this.apiKey)}`;
1382
+ const contents = this.buildContents(params);
1383
+ const generationConfig = {
1384
+ temperature: this.temperature
1385
+ };
1386
+ if (params.jsonSchema || !chatMessages && (systemPrompt.includes("JSON") || systemPrompt.includes("json"))) {
1387
+ generationConfig.responseMimeType = "application/json";
1388
+ }
1389
+ const body = {
1390
+ system_instruction: {
1391
+ parts: [{ text: systemPrompt }]
1392
+ },
1393
+ contents,
1394
+ generationConfig
1395
+ };
1396
+ const res = await fetch(url, {
1397
+ method: "POST",
1398
+ headers: {
1399
+ "Content-Type": "application/json"
1400
+ },
1401
+ body: JSON.stringify(body),
1402
+ signal
1403
+ });
1404
+ if (!res.ok) {
1405
+ const errText = await res.text().catch(() => "");
1406
+ throw new Error(
1407
+ `Gemini API error [${res.status} ${res.statusText}]: ${errText || "Unknown error"}`
1408
+ );
1409
+ }
1410
+ const data = await res.json();
1411
+ const candidateText = data.candidates?.[0]?.content?.parts?.[0]?.text;
1412
+ if (!candidateText) {
1413
+ throw new Error("Gemini returned an empty or invalid candidate response");
1414
+ }
1415
+ return {
1416
+ text: candidateText,
1417
+ usage: data.usageMetadata ? {
1418
+ promptTokens: data.usageMetadata.promptTokenCount,
1419
+ completionTokens: data.usageMetadata.candidatesTokenCount
1420
+ } : void 0
1421
+ };
1422
+ }
1423
+ /**
1424
+ * Token-level streaming via Gemini's `streamGenerateContent` SSE endpoint (STORA-515).
1425
+ */
1426
+ async *generateStream(params) {
1427
+ const { systemPrompt, signal } = params;
1428
+ const url = `${this.baseUrl}/models/${encodeURIComponent(this.model)}:streamGenerateContent?alt=sse&key=${encodeURIComponent(this.apiKey)}`;
1429
+ const contents = this.buildContents(params);
1430
+ const generationConfig = {
1431
+ temperature: this.temperature
1432
+ };
1433
+ const body = {
1434
+ system_instruction: {
1435
+ parts: [{ text: systemPrompt }]
1436
+ },
1437
+ contents,
1438
+ generationConfig
1439
+ };
1440
+ const res = await fetch(url, {
1441
+ method: "POST",
1442
+ headers: {
1443
+ "Content-Type": "application/json"
1444
+ },
1445
+ body: JSON.stringify(body),
1446
+ signal
1447
+ });
1448
+ if (!res.ok) {
1449
+ const errText = await res.text().catch(() => "");
1450
+ throw new Error(
1451
+ `Gemini API error [${res.status} ${res.statusText}]: ${errText || "Unknown error"}`
1452
+ );
1453
+ }
1454
+ if (!res.body) {
1455
+ throw new Error("Gemini streaming response has no readable body");
1456
+ }
1457
+ let text = "";
1458
+ let usage;
1459
+ for await (const raw of readSseDataLines(res.body, signal)) {
1460
+ let parsed;
1461
+ try {
1462
+ parsed = JSON.parse(raw);
1463
+ } catch {
1464
+ continue;
1465
+ }
1466
+ const delta = parsed.candidates?.[0]?.content?.parts?.[0]?.text;
1467
+ if (typeof delta === "string" && delta.length > 0) {
1468
+ text += delta;
1469
+ yield delta;
1470
+ }
1471
+ if (parsed.usageMetadata) {
1472
+ usage = {
1473
+ promptTokens: parsed.usageMetadata.promptTokenCount,
1474
+ completionTokens: parsed.usageMetadata.candidatesTokenCount
1475
+ };
1476
+ }
1477
+ }
1478
+ return { text, usage };
1479
+ }
1480
+ };
1481
+
1482
+ // src/server/adapters/openai.ts
1483
+ var OpenAiAdapter = class {
1484
+ name = "openai";
1485
+ apiKey;
1486
+ model;
1487
+ temperature;
1488
+ baseUrl;
1489
+ customHeaders;
1490
+ constructor(options) {
1491
+ if (!options.apiKey) {
1492
+ throw new Error("OpenAiAdapter requires an apiKey");
1493
+ }
1494
+ this.apiKey = options.apiKey;
1495
+ this.model = options.model || "gpt-4o";
1496
+ this.temperature = options.temperature ?? 0.4;
1497
+ this.baseUrl = (options.baseUrl || "https://api.openai.com/v1").replace(/\/+$/, "");
1498
+ this.customHeaders = options.headers;
1499
+ }
1500
+ buildMessages(params) {
1501
+ const { systemPrompt, userPrompt, messages: chatMessages } = params;
1502
+ return chatMessages && chatMessages.length > 0 ? [
1503
+ { role: "system", content: systemPrompt },
1504
+ ...chatMessages.map((m) => ({ role: m.role, content: m.content }))
1505
+ ] : [
1506
+ { role: "system", content: systemPrompt },
1507
+ { role: "user", content: userPrompt }
1508
+ ];
1509
+ }
1510
+ async generate(params) {
1511
+ const { systemPrompt, signal } = params;
1512
+ const url = `${this.baseUrl}/chat/completions`;
1513
+ const messages = this.buildMessages(params);
1514
+ const chatMessages = params.messages;
1515
+ const body = {
1516
+ model: this.model,
1517
+ temperature: this.temperature,
1518
+ messages
1519
+ };
1520
+ if (params.jsonSchema || !chatMessages && (systemPrompt.includes("JSON") || systemPrompt.includes("json"))) {
1521
+ body.response_format = { type: "json_object" };
1522
+ }
1523
+ const res = await fetch(url, {
1524
+ method: "POST",
1525
+ headers: {
1526
+ "Content-Type": "application/json",
1527
+ Authorization: `Bearer ${this.apiKey}`,
1528
+ ...this.customHeaders
1529
+ },
1530
+ body: JSON.stringify(body),
1531
+ signal
1532
+ });
1533
+ if (!res.ok) {
1534
+ const errText = await res.text().catch(() => "");
1535
+ throw new Error(
1536
+ `OpenAI API error [${res.status} ${res.statusText}]: ${errText || "Unknown error"}`
1537
+ );
1538
+ }
1539
+ const data = await res.json();
1540
+ const content = data.choices?.[0]?.message?.content;
1541
+ if (!content) {
1542
+ throw new Error("OpenAI returned an empty message content");
1543
+ }
1544
+ return {
1545
+ text: content,
1546
+ usage: data.usage ? {
1547
+ promptTokens: data.usage.prompt_tokens,
1548
+ completionTokens: data.usage.completion_tokens
1549
+ } : void 0
1550
+ };
1551
+ }
1552
+ /**
1553
+ * Token-level streaming via OpenAI's `stream: true` chat completions mode (STORA-515).
1554
+ * Yields each `delta.content` fragment as it arrives and returns the fully assembled
1555
+ * text + usage (when the final chunk includes it) once the stream ends.
1556
+ */
1557
+ async *generateStream(params) {
1558
+ const { signal } = params;
1559
+ const url = `${this.baseUrl}/chat/completions`;
1560
+ const messages = this.buildMessages(params);
1561
+ const body = {
1562
+ model: this.model,
1563
+ temperature: this.temperature,
1564
+ messages,
1565
+ stream: true
1566
+ };
1567
+ const res = await fetch(url, {
1568
+ method: "POST",
1569
+ headers: {
1570
+ "Content-Type": "application/json",
1571
+ Authorization: `Bearer ${this.apiKey}`,
1572
+ ...this.customHeaders
1573
+ },
1574
+ body: JSON.stringify(body),
1575
+ signal
1576
+ });
1577
+ if (!res.ok) {
1578
+ const errText = await res.text().catch(() => "");
1579
+ throw new Error(
1580
+ `OpenAI API error [${res.status} ${res.statusText}]: ${errText || "Unknown error"}`
1581
+ );
1582
+ }
1583
+ if (!res.body) {
1584
+ throw new Error("OpenAI streaming response has no readable body");
1585
+ }
1586
+ let text = "";
1587
+ let usage;
1588
+ for await (const raw of readSseDataLines(res.body, signal)) {
1589
+ if (raw === "[DONE]") break;
1590
+ let parsed;
1591
+ try {
1592
+ parsed = JSON.parse(raw);
1593
+ } catch {
1594
+ continue;
1595
+ }
1596
+ const delta = parsed.choices?.[0]?.delta?.content;
1597
+ if (typeof delta === "string" && delta.length > 0) {
1598
+ text += delta;
1599
+ yield delta;
1600
+ }
1601
+ if (parsed.usage) {
1602
+ usage = {
1603
+ promptTokens: parsed.usage.prompt_tokens,
1604
+ completionTokens: parsed.usage.completion_tokens
1605
+ };
1606
+ }
1607
+ }
1608
+ return { text, usage };
1609
+ }
1610
+ };
1611
+
1612
+ // src/server/adapters/anthropic.ts
1613
+ var AnthropicAdapter = class {
1614
+ name = "anthropic";
1615
+ apiKey;
1616
+ model;
1617
+ maxTokens;
1618
+ temperature;
1619
+ baseUrl;
1620
+ anthropicVersion;
1621
+ customHeaders;
1622
+ constructor(options) {
1623
+ if (!options.apiKey) {
1624
+ throw new Error("AnthropicAdapter requires an apiKey");
1625
+ }
1626
+ this.apiKey = options.apiKey;
1627
+ this.model = options.model || "claude-3-5-sonnet-20241022";
1628
+ this.maxTokens = options.maxTokens ?? 4096;
1629
+ this.temperature = options.temperature ?? 0.4;
1630
+ this.baseUrl = (options.baseUrl || "https://api.anthropic.com/v1").replace(/\/+$/, "");
1631
+ this.anthropicVersion = options.anthropicVersion || "2023-06-01";
1632
+ this.customHeaders = options.headers;
1633
+ }
1634
+ buildMessages(params) {
1635
+ const { userPrompt, messages: chatMessages } = params;
1636
+ return chatMessages && chatMessages.length > 0 ? chatMessages.filter((m) => m.role === "user" || m.role === "assistant").map((m) => ({ role: m.role, content: m.content })) : [
1637
+ {
1638
+ role: "user",
1639
+ content: userPrompt
1640
+ }
1641
+ ];
1642
+ }
1643
+ async generate(params) {
1644
+ const { systemPrompt, signal } = params;
1645
+ const url = `${this.baseUrl}/messages`;
1646
+ const messages = this.buildMessages(params);
1647
+ const body = {
1648
+ model: this.model,
1649
+ max_tokens: this.maxTokens,
1650
+ temperature: this.temperature,
1651
+ system: systemPrompt,
1652
+ messages
1653
+ };
1654
+ const res = await fetch(url, {
1655
+ method: "POST",
1656
+ headers: {
1657
+ "Content-Type": "application/json",
1658
+ "x-api-key": this.apiKey,
1659
+ "anthropic-version": this.anthropicVersion,
1660
+ ...this.customHeaders
1661
+ },
1662
+ body: JSON.stringify(body),
1663
+ signal
1664
+ });
1665
+ if (!res.ok) {
1666
+ const errText = await res.text().catch(() => "");
1667
+ throw new Error(
1668
+ `Anthropic API error [${res.status} ${res.statusText}]: ${errText || "Unknown error"}`
1669
+ );
1670
+ }
1671
+ const data = await res.json();
1672
+ const textPart = data.content?.find((c) => c.type === "text")?.text;
1673
+ if (!textPart) {
1674
+ throw new Error("Anthropic returned an empty text content");
1675
+ }
1676
+ return {
1677
+ text: textPart,
1678
+ usage: data.usage ? {
1679
+ promptTokens: data.usage.input_tokens,
1680
+ completionTokens: data.usage.output_tokens
1681
+ } : void 0
1682
+ };
1683
+ }
1684
+ /**
1685
+ * Token-level streaming via Anthropic's `stream: true` Messages API (STORA-515).
1686
+ * Parses the `content_block_delta` (`text_delta`) events for incremental text and the
1687
+ * `message_start`/`message_delta` events for usage accounting.
1688
+ */
1689
+ async *generateStream(params) {
1690
+ const { systemPrompt, signal } = params;
1691
+ const url = `${this.baseUrl}/messages`;
1692
+ const messages = this.buildMessages(params);
1693
+ const body = {
1694
+ model: this.model,
1695
+ max_tokens: this.maxTokens,
1696
+ temperature: this.temperature,
1697
+ system: systemPrompt,
1698
+ messages,
1699
+ stream: true
1700
+ };
1701
+ const res = await fetch(url, {
1702
+ method: "POST",
1703
+ headers: {
1704
+ "Content-Type": "application/json",
1705
+ "x-api-key": this.apiKey,
1706
+ "anthropic-version": this.anthropicVersion,
1707
+ ...this.customHeaders
1708
+ },
1709
+ body: JSON.stringify(body),
1710
+ signal
1711
+ });
1712
+ if (!res.ok) {
1713
+ const errText = await res.text().catch(() => "");
1714
+ throw new Error(
1715
+ `Anthropic API error [${res.status} ${res.statusText}]: ${errText || "Unknown error"}`
1716
+ );
1717
+ }
1718
+ if (!res.body) {
1719
+ throw new Error("Anthropic streaming response has no readable body");
1720
+ }
1721
+ let text = "";
1722
+ let promptTokens;
1723
+ let completionTokens;
1724
+ for await (const raw of readSseDataLines(res.body, signal)) {
1725
+ if (!raw) continue;
1726
+ let evt;
1727
+ try {
1728
+ evt = JSON.parse(raw);
1729
+ } catch {
1730
+ continue;
1731
+ }
1732
+ if (evt.type === "content_block_delta" && evt.delta?.type === "text_delta") {
1733
+ const delta = evt.delta.text;
1734
+ if (typeof delta === "string" && delta.length > 0) {
1735
+ text += delta;
1736
+ yield delta;
1737
+ }
1738
+ } else if (evt.type === "message_start" && evt.message?.usage?.input_tokens !== void 0) {
1739
+ promptTokens = evt.message.usage.input_tokens;
1740
+ } else if (evt.type === "message_delta" && evt.usage?.output_tokens !== void 0) {
1741
+ completionTokens = evt.usage.output_tokens;
1742
+ }
1743
+ }
1744
+ return {
1745
+ text,
1746
+ usage: promptTokens !== void 0 || completionTokens !== void 0 ? { promptTokens, completionTokens } : void 0
1747
+ };
1748
+ }
1749
+ };
1750
+
1751
+ // src/server/adapters/custom.ts
1752
+ var CustomHttpAdapter = class {
1753
+ name;
1754
+ url;
1755
+ headers;
1756
+ formatRequestBody;
1757
+ parseResponse;
1758
+ constructor(options) {
1759
+ if (!options.url) {
1760
+ throw new Error("CustomHttpAdapter requires a url");
1761
+ }
1762
+ this.url = options.url;
1763
+ this.name = options.name || "custom-http";
1764
+ this.headers = options.headers;
1765
+ this.formatRequestBody = options.formatRequestBody || ((params) => ({
1766
+ messages: params.messages && params.messages.length > 0 ? [
1767
+ { role: "system", content: params.systemPrompt },
1768
+ ...params.messages.map((m) => ({ role: m.role, content: m.content }))
1769
+ ] : [
1770
+ { role: "system", content: params.systemPrompt },
1771
+ { role: "user", content: params.userPrompt }
1772
+ ]
1773
+ }));
1774
+ this.parseResponse = options.parseResponse || ((json) => {
1775
+ if (!json || typeof json !== "object") {
1776
+ throw new Error("Response is not a valid JSON object");
1777
+ }
1778
+ const obj = json;
1779
+ if (typeof obj.response === "string") return obj.response;
1780
+ if (typeof obj.text === "string") return obj.text;
1781
+ const choices = obj.choices;
1782
+ if (Array.isArray(choices) && choices[0]?.message?.content) {
1783
+ return choices[0].message.content;
1784
+ }
1785
+ const message = obj.message;
1786
+ if (message && typeof message.content === "string") {
1787
+ return message.content;
1788
+ }
1789
+ throw new Error("Unable to extract generated text from custom HTTP response");
1790
+ });
1791
+ }
1792
+ async generate(params) {
1793
+ const body = this.formatRequestBody(params);
1794
+ const res = await fetch(this.url, {
1795
+ method: "POST",
1796
+ headers: {
1797
+ "Content-Type": "application/json",
1798
+ ...this.headers
1799
+ },
1800
+ body: JSON.stringify(body),
1801
+ signal: params.signal
1802
+ });
1803
+ if (!res.ok) {
1804
+ const errText = await res.text().catch(() => "");
1805
+ throw new Error(
1806
+ `Custom HTTP adapter error [${res.status} ${res.statusText}]: ${errText || "Unknown error"}`
1807
+ );
1808
+ }
1809
+ const json = await res.json();
1810
+ const text = this.parseResponse(json);
1811
+ return { text };
1812
+ }
1813
+ };
1814
+ // Annotate the CommonJS export names for ESM import in node:
1815
+ 0 && (module.exports = {
1816
+ AnthropicAdapter,
1817
+ CustomHttpAdapter,
1818
+ GeminiAdapter,
1819
+ KubuildAiEngine,
1820
+ OpenAiAdapter,
1821
+ createAiHandler,
1822
+ processAiRequest
1823
+ });
1824
+ //# sourceMappingURL=index.cjs.map