@kubuild/ai 0.3.1 → 0.4.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/{chunk-27AY2KBI.js → chunk-3DRAVYU3.js} +173 -46
- package/dist/chunk-3DRAVYU3.js.map +1 -0
- package/dist/chunk-ILLNRABB.js +24 -0
- package/dist/chunk-ILLNRABB.js.map +1 -0
- package/dist/{chunk-JY4NHD3Q.js → chunk-T72YWBJS.js} +62 -1
- package/dist/chunk-T72YWBJS.js.map +1 -0
- package/dist/{chunk-UH24LHRY.js → chunk-YETTTWHI.js} +226 -1
- package/dist/chunk-YETTTWHI.js.map +1 -0
- package/dist/client/ai-client.d.ts +20 -1
- package/dist/client/ai-client.d.ts.map +1 -1
- package/dist/client/index.cjs +61 -0
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.js +1 -1
- package/dist/core/document-outline.d.ts +60 -0
- package/dist/core/document-outline.d.ts.map +1 -0
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/messages.d.ts +18 -0
- package/dist/core/messages.d.ts.map +1 -0
- package/dist/core/prompt-compiler.d.ts +29 -0
- package/dist/core/prompt-compiler.d.ts.map +1 -1
- package/dist/index.cjs +261 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +31 -3
- package/dist/react/index.cjs +173 -0
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.ts +1 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +112 -1
- package/dist/react/index.js.map +1 -1
- package/dist/react/use-ai-agent.d.ts +47 -0
- package/dist/react/use-ai-agent.d.ts.map +1 -0
- package/dist/server/adapters/anthropic.d.ts.map +1 -1
- package/dist/server/adapters/custom.d.ts.map +1 -1
- package/dist/server/adapters/gemini.d.ts.map +1 -1
- package/dist/server/adapters/index.cjs +180 -45
- package/dist/server/adapters/index.cjs.map +1 -1
- package/dist/server/adapters/index.js +2 -1
- package/dist/server/adapters/openai.d.ts +25 -0
- package/dist/server/adapters/openai.d.ts.map +1 -1
- package/dist/server/agent.d.ts +59 -0
- package/dist/server/agent.d.ts.map +1 -0
- package/dist/server/engine.d.ts.map +1 -1
- package/dist/server/handler.d.ts +10 -1
- package/dist/server/handler.d.ts.map +1 -1
- package/dist/server/index.cjs +1734 -260
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1229 -94
- package/dist/server/index.js.map +1 -1
- package/dist/server/tools/helpers.d.ts +66 -0
- package/dist/server/tools/helpers.d.ts.map +1 -0
- package/dist/server/tools/index.d.ts +25 -0
- package/dist/server/tools/index.d.ts.map +1 -0
- package/dist/server/tools/read-tools.d.ts +3 -0
- package/dist/server/tools/read-tools.d.ts.map +1 -0
- package/dist/server/tools/types.d.ts +51 -0
- package/dist/server/tools/types.d.ts.map +1 -0
- package/dist/server/tools/write-tools.d.ts +3 -0
- package/dist/server/tools/write-tools.d.ts.map +1 -0
- package/dist/types.d.ts +193 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -5
- package/dist/chunk-27AY2KBI.js.map +0 -1
- package/dist/chunk-JY4NHD3Q.js.map +0 -1
- package/dist/chunk-UH24LHRY.js.map +0 -1
package/dist/server/index.js
CHANGED
|
@@ -1,18 +1,27 @@
|
|
|
1
1
|
import {
|
|
2
|
+
buildAgentSystemPrompt,
|
|
2
3
|
buildJsonSchemaForMode,
|
|
4
|
+
buildSelectionContext,
|
|
3
5
|
buildSystemPrompt,
|
|
4
6
|
compileComponentCatalog,
|
|
5
7
|
extractJsonFromResponse,
|
|
8
|
+
getNodeLabel,
|
|
6
9
|
normalizeAndValidatePageDocument,
|
|
7
10
|
normalizeAndValidateRefactoredNode,
|
|
8
|
-
normalizeAndValidateSectionNode
|
|
9
|
-
|
|
11
|
+
normalizeAndValidateSectionNode,
|
|
12
|
+
normalizeNodeTree,
|
|
13
|
+
pruneNodeForPrompt,
|
|
14
|
+
summarizeNodeTree
|
|
15
|
+
} from "../chunk-YETTTWHI.js";
|
|
10
16
|
import {
|
|
11
17
|
AnthropicAdapter,
|
|
12
18
|
CustomHttpAdapter,
|
|
13
19
|
GeminiAdapter,
|
|
14
20
|
OpenAiAdapter
|
|
15
|
-
} from "../chunk-
|
|
21
|
+
} from "../chunk-3DRAVYU3.js";
|
|
22
|
+
import {
|
|
23
|
+
getMessageText
|
|
24
|
+
} from "../chunk-ILLNRABB.js";
|
|
16
25
|
|
|
17
26
|
// src/server/engine.ts
|
|
18
27
|
import { DocumentMetadataSchema } from "@kubuild/schema";
|
|
@@ -423,7 +432,7 @@ ${sectionSummary || "(Canvas is currently empty)"}
|
|
|
423
432
|
Currently Selected Component Node ID: "${request.selectedNodeId}"`;
|
|
424
433
|
}
|
|
425
434
|
const lastUserMsg = [...request.messages].reverse().find((m) => m.role === "user");
|
|
426
|
-
const userPrompt = lastUserMsg
|
|
435
|
+
const userPrompt = lastUserMsg ? getMessageText(lastUserMsg) || "Hello" : "Hello";
|
|
427
436
|
return { systemPrompt, userPrompt };
|
|
428
437
|
}
|
|
429
438
|
/**
|
|
@@ -564,8 +573,1067 @@ Currently Selected Component Node ID: "${request.selectedNodeId}"`;
|
|
|
564
573
|
}
|
|
565
574
|
};
|
|
566
575
|
|
|
576
|
+
// src/server/tools/helpers.ts
|
|
577
|
+
import { NodeSchema } from "@kubuild/schema";
|
|
578
|
+
import {
|
|
579
|
+
collectNodeIdSet,
|
|
580
|
+
findNodeById,
|
|
581
|
+
validateDocumentSecurity
|
|
582
|
+
} from "@kubuild/core";
|
|
583
|
+
function fail(summary, message, extra) {
|
|
584
|
+
return {
|
|
585
|
+
ok: false,
|
|
586
|
+
summary,
|
|
587
|
+
content: { ok: false, error: message, ...extra ?? {} }
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
function succeed(summary, content, rest) {
|
|
591
|
+
return { ok: true, summary, content: { ok: true, ...content }, ...rest ?? {} };
|
|
592
|
+
}
|
|
593
|
+
function readString(input, key) {
|
|
594
|
+
const value = input[key];
|
|
595
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
596
|
+
}
|
|
597
|
+
function readPlainObject(input, key) {
|
|
598
|
+
const value = input[key];
|
|
599
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
600
|
+
return value;
|
|
601
|
+
}
|
|
602
|
+
function readOptionalIndex(input, key) {
|
|
603
|
+
const value = input[key];
|
|
604
|
+
if (typeof value === "number" && Number.isInteger(value) && value >= 0) return value;
|
|
605
|
+
return void 0;
|
|
606
|
+
}
|
|
607
|
+
function similarity(a, b) {
|
|
608
|
+
if (a === b) return Infinity;
|
|
609
|
+
let shared = 0;
|
|
610
|
+
const max = Math.min(a.length, b.length);
|
|
611
|
+
while (shared < max && a[shared] === b[shared]) shared++;
|
|
612
|
+
return shared + (a.includes(b) || b.includes(a) ? 3 : 0);
|
|
613
|
+
}
|
|
614
|
+
function suggestNodeIds(document, badId, limit = 5) {
|
|
615
|
+
return Array.from(collectNodeIdSet(document.document)).map((id) => ({ id, score: similarity(id.toLowerCase(), badId.toLowerCase()) })).filter((entry) => entry.score > 0).sort((a, b) => b.score - a.score).slice(0, limit).map((entry) => entry.id);
|
|
616
|
+
}
|
|
617
|
+
function resolveNode(document, nodeId, toolName) {
|
|
618
|
+
const node = findNodeById(document.document, nodeId);
|
|
619
|
+
if (!node) {
|
|
620
|
+
return {
|
|
621
|
+
error: fail(
|
|
622
|
+
`${toolName}: node "${nodeId}" tidak ditemukan`,
|
|
623
|
+
`No node with id "${nodeId}" exists in this page. Node ids must come from the outline or from a tool result \u2014 never invented.`,
|
|
624
|
+
{ suggestedNodeIds: suggestNodeIds(document, nodeId) }
|
|
625
|
+
)
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
return { node };
|
|
629
|
+
}
|
|
630
|
+
function findCatalogEntry(catalog, type) {
|
|
631
|
+
return catalog.find((entry) => entry.type === type);
|
|
632
|
+
}
|
|
633
|
+
function checkNesting(catalog, parent, childType) {
|
|
634
|
+
if (catalog.length === 0) return null;
|
|
635
|
+
const parentSpec = findCatalogEntry(catalog, parent.type);
|
|
636
|
+
if (parentSpec && !parentSpec.acceptsChildren) {
|
|
637
|
+
return `Component "${parent.type}" (#${parent.id}) cannot contain children. Insert into a container/section node instead.`;
|
|
638
|
+
}
|
|
639
|
+
if (parentSpec?.allowedChildren && parentSpec.allowedChildren.length > 0) {
|
|
640
|
+
if (!parentSpec.allowedChildren.includes(childType)) {
|
|
641
|
+
return `Component "${parent.type}" only accepts children of type [${parentSpec.allowedChildren.join(", ")}] \u2014 "${childType}" is not allowed.`;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
const childSpec = findCatalogEntry(catalog, childType);
|
|
645
|
+
if (childSpec?.disallowedParents?.includes(parent.type)) {
|
|
646
|
+
return `Component "${childType}" cannot be placed inside "${parent.type}".`;
|
|
647
|
+
}
|
|
648
|
+
return null;
|
|
649
|
+
}
|
|
650
|
+
function checkComponentType(catalog, type) {
|
|
651
|
+
if (catalog.length === 0) return null;
|
|
652
|
+
if (findCatalogEntry(catalog, type)) return null;
|
|
653
|
+
const known = catalog.map((entry) => entry.type);
|
|
654
|
+
return `Unknown component type "${type}". Use list_component_types to see valid types. Known types: ${known.slice(0, 40).join(", ")}${known.length > 40 ? ", \u2026" : ""}`;
|
|
655
|
+
}
|
|
656
|
+
function checkDocumentSecurity(document, limits) {
|
|
657
|
+
const result = validateDocumentSecurity(document, limits);
|
|
658
|
+
if (result.safe) return null;
|
|
659
|
+
return result.errors.map((error) => `${error.code}: ${error.message}`).join("; ");
|
|
660
|
+
}
|
|
661
|
+
function normalizeIncomingNode(raw, document, options = {}) {
|
|
662
|
+
let normalized;
|
|
663
|
+
try {
|
|
664
|
+
normalized = normalizeNodeTree(raw);
|
|
665
|
+
} catch (err) {
|
|
666
|
+
return { error: err instanceof Error ? err.message : String(err) };
|
|
667
|
+
}
|
|
668
|
+
if (!options.preserveIds) {
|
|
669
|
+
const taken = collectNodeIdSet(document.document);
|
|
670
|
+
const reassign = (node) => {
|
|
671
|
+
if (!node.id || taken.has(node.id)) {
|
|
672
|
+
let counter = 1;
|
|
673
|
+
let candidate = `${node.type}-${counter}`;
|
|
674
|
+
while (taken.has(candidate)) {
|
|
675
|
+
counter++;
|
|
676
|
+
candidate = `${node.type}-${counter}`;
|
|
677
|
+
}
|
|
678
|
+
node.id = candidate;
|
|
679
|
+
}
|
|
680
|
+
taken.add(node.id);
|
|
681
|
+
for (const child of node.children ?? []) reassign(child);
|
|
682
|
+
};
|
|
683
|
+
reassign(normalized);
|
|
684
|
+
}
|
|
685
|
+
const parsed = NodeSchema.safeParse(normalized);
|
|
686
|
+
if (!parsed.success) {
|
|
687
|
+
return {
|
|
688
|
+
error: parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ")
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
return { node: parsed.data };
|
|
692
|
+
}
|
|
693
|
+
function describeNode(node) {
|
|
694
|
+
const label = getNodeLabel(node, 32);
|
|
695
|
+
return label ? `${node.type} "${label}" (#${node.id})` : `${node.type} (#${node.id})`;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// src/server/tools/read-tools.ts
|
|
699
|
+
var getPageOutline = {
|
|
700
|
+
kind: "read",
|
|
701
|
+
definition: {
|
|
702
|
+
name: "get_page_outline",
|
|
703
|
+
description: "Return the page structure as an indented outline of node ids, types, labels and child counts. Use this to re-orient after edits. Does not include props or styles \u2014 call read_node for those.",
|
|
704
|
+
inputSchema: {
|
|
705
|
+
type: "object",
|
|
706
|
+
properties: {
|
|
707
|
+
maxDepth: {
|
|
708
|
+
type: "integer",
|
|
709
|
+
description: "How deep to expand the tree before collapsing. Default 3."
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
},
|
|
714
|
+
execute(input, context) {
|
|
715
|
+
const maxDepth = typeof input.maxDepth === "number" && input.maxDepth > 0 ? Math.floor(input.maxDepth) : 3;
|
|
716
|
+
return succeed("Membaca struktur halaman", {
|
|
717
|
+
outline: summarizeNodeTree(context.document.document, { maxDepth })
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
};
|
|
721
|
+
var readNode = {
|
|
722
|
+
kind: "read",
|
|
723
|
+
definition: {
|
|
724
|
+
name: "read_node",
|
|
725
|
+
description: "Return one node with its full props and styles, plus its parent, position and siblings. Always read a node before editing it, so you change only the fields that need changing.",
|
|
726
|
+
inputSchema: {
|
|
727
|
+
type: "object",
|
|
728
|
+
properties: {
|
|
729
|
+
nodeId: { type: "string", description: "Id of the node to read (from the outline)." },
|
|
730
|
+
includeChildren: {
|
|
731
|
+
type: "boolean",
|
|
732
|
+
description: "Include the node subtree. Default false \u2014 keeps the result small."
|
|
733
|
+
}
|
|
734
|
+
},
|
|
735
|
+
required: ["nodeId"]
|
|
736
|
+
}
|
|
737
|
+
},
|
|
738
|
+
execute(input, context) {
|
|
739
|
+
const nodeId = readString(input, "nodeId");
|
|
740
|
+
if (!nodeId) return fail("read_node: nodeId kosong", 'Argument "nodeId" is required.');
|
|
741
|
+
const resolved = resolveNode(context.document, nodeId, "read_node");
|
|
742
|
+
if ("error" in resolved) return resolved.error;
|
|
743
|
+
const selection = buildSelectionContext(context.document.document, nodeId);
|
|
744
|
+
const includeChildren = input.includeChildren === true;
|
|
745
|
+
const node = includeChildren ? pruneNodeForPrompt(resolved.node, 3) : { ...resolved.node, children: void 0 };
|
|
746
|
+
return succeed(`Membaca node ${nodeId}`, {
|
|
747
|
+
node,
|
|
748
|
+
childCount: resolved.node.children?.length ?? 0,
|
|
749
|
+
parentId: selection?.parentId ?? null,
|
|
750
|
+
index: selection?.index ?? 0,
|
|
751
|
+
ancestors: selection?.ancestors.map((a) => ({ id: a.id, type: a.type })) ?? [],
|
|
752
|
+
siblings: selection?.siblings.map((sibling, i) => ({
|
|
753
|
+
index: i,
|
|
754
|
+
id: sibling.id,
|
|
755
|
+
type: sibling.type,
|
|
756
|
+
label: getNodeLabel(sibling)
|
|
757
|
+
})) ?? []
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
};
|
|
761
|
+
var findNodes = {
|
|
762
|
+
kind: "read",
|
|
763
|
+
definition: {
|
|
764
|
+
name: "find_nodes",
|
|
765
|
+
description: 'Search the page for nodes by component type and/or visible text. Use this instead of guessing ids when the user refers to something by what it says ("the Get Started button").',
|
|
766
|
+
inputSchema: {
|
|
767
|
+
type: "object",
|
|
768
|
+
properties: {
|
|
769
|
+
type: { type: "string", description: 'Component type to match exactly, e.g. "button".' },
|
|
770
|
+
textContains: {
|
|
771
|
+
type: "string",
|
|
772
|
+
description: "Case-insensitive substring to match against the node text/label props."
|
|
773
|
+
},
|
|
774
|
+
parentId: { type: "string", description: "Limit the search to this node subtree." },
|
|
775
|
+
limit: { type: "integer", description: "Max results. Default 20." }
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
},
|
|
779
|
+
execute(input, context) {
|
|
780
|
+
const type = readString(input, "type");
|
|
781
|
+
const textContains = readString(input, "textContains");
|
|
782
|
+
const parentId = readString(input, "parentId");
|
|
783
|
+
const limit = typeof input.limit === "number" && input.limit > 0 ? Math.floor(input.limit) : 20;
|
|
784
|
+
if (!type && !textContains) {
|
|
785
|
+
return fail(
|
|
786
|
+
"find_nodes: filter kosong",
|
|
787
|
+
'Provide at least one of "type" or "textContains" \u2014 an unfiltered search would just return the whole outline.'
|
|
788
|
+
);
|
|
789
|
+
}
|
|
790
|
+
let root = context.document.document;
|
|
791
|
+
if (parentId) {
|
|
792
|
+
const resolved = resolveNode(context.document, parentId, "find_nodes");
|
|
793
|
+
if ("error" in resolved) return resolved.error;
|
|
794
|
+
root = resolved.node;
|
|
795
|
+
}
|
|
796
|
+
const needle = textContains?.toLowerCase();
|
|
797
|
+
const matches = [];
|
|
798
|
+
const walk = (node) => {
|
|
799
|
+
if (matches.length >= limit) return;
|
|
800
|
+
const label = getNodeLabel(node);
|
|
801
|
+
const typeMatches = !type || node.type === type;
|
|
802
|
+
const textMatches = !needle || label.toLowerCase().includes(needle);
|
|
803
|
+
if (typeMatches && textMatches) {
|
|
804
|
+
matches.push({ id: node.id, type: node.type, label });
|
|
805
|
+
}
|
|
806
|
+
for (const child of node.children ?? []) walk(child);
|
|
807
|
+
};
|
|
808
|
+
walk(root);
|
|
809
|
+
return succeed(
|
|
810
|
+
`Mencari node (${matches.length} hasil)`,
|
|
811
|
+
matches.length > 0 ? { matches, count: matches.length } : {
|
|
812
|
+
matches: [],
|
|
813
|
+
count: 0,
|
|
814
|
+
hint: "Nothing matched. Try a broader filter, or call get_page_outline to see what exists."
|
|
815
|
+
}
|
|
816
|
+
);
|
|
817
|
+
}
|
|
818
|
+
};
|
|
819
|
+
var listComponentTypes = {
|
|
820
|
+
kind: "read",
|
|
821
|
+
definition: {
|
|
822
|
+
name: "list_component_types",
|
|
823
|
+
description: "List component types available in this editor, with their props and nesting rules. Call this before inserting a component type you have not used yet.",
|
|
824
|
+
inputSchema: {
|
|
825
|
+
type: "object",
|
|
826
|
+
properties: {
|
|
827
|
+
category: { type: "string", description: 'Filter to one category, e.g. "layout".' }
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
},
|
|
831
|
+
execute(input, context) {
|
|
832
|
+
const category = readString(input, "category");
|
|
833
|
+
const entries = category ? context.catalog.filter((spec) => spec.category === category) : context.catalog;
|
|
834
|
+
return succeed(`Melihat katalog komponen (${entries.length})`, {
|
|
835
|
+
components: entries.map((spec) => ({
|
|
836
|
+
type: spec.type,
|
|
837
|
+
label: spec.label,
|
|
838
|
+
category: spec.category,
|
|
839
|
+
acceptsChildren: spec.acceptsChildren,
|
|
840
|
+
allowedChildren: spec.allowedChildren,
|
|
841
|
+
props: spec.props?.map((prop) => ({
|
|
842
|
+
name: prop.name,
|
|
843
|
+
type: prop.type,
|
|
844
|
+
options: prop.options
|
|
845
|
+
}))
|
|
846
|
+
}))
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
};
|
|
850
|
+
var READ_TOOLS = [getPageOutline, readNode, findNodes, listComponentTypes];
|
|
851
|
+
|
|
852
|
+
// src/server/tools/write-tools.ts
|
|
853
|
+
import {
|
|
854
|
+
duplicateNode,
|
|
855
|
+
findNodeById as findNodeById2,
|
|
856
|
+
insertNode,
|
|
857
|
+
moveNode,
|
|
858
|
+
removeNode,
|
|
859
|
+
replaceNode,
|
|
860
|
+
updateProps,
|
|
861
|
+
updateStyle
|
|
862
|
+
} from "@kubuild/core";
|
|
863
|
+
var BREAKPOINTS = ["base", "desktop", "tablet", "mobile"];
|
|
864
|
+
function applyCommand(toolName, run) {
|
|
865
|
+
try {
|
|
866
|
+
return run();
|
|
867
|
+
} catch (err) {
|
|
868
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
869
|
+
return { error: fail(`${toolName} gagal`, message) };
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
function guardSecurity(toolName, document, context) {
|
|
873
|
+
const violation = checkDocumentSecurity(document, context.securityLimits);
|
|
874
|
+
if (!violation) return null;
|
|
875
|
+
return fail(
|
|
876
|
+
`${toolName} ditolak (security)`,
|
|
877
|
+
`The resulting document failed the security check and was rejected: ${violation}`
|
|
878
|
+
);
|
|
879
|
+
}
|
|
880
|
+
var updateNodeProps = {
|
|
881
|
+
kind: "write",
|
|
882
|
+
definition: {
|
|
883
|
+
name: "update_node_props",
|
|
884
|
+
description: "Change the props of one existing node (text, label, href, src, level, \u2026). This is the right tool for any content/copy change \u2014 it touches nothing else on the page.",
|
|
885
|
+
inputSchema: {
|
|
886
|
+
type: "object",
|
|
887
|
+
properties: {
|
|
888
|
+
nodeId: { type: "string", description: "Id of the node to edit." },
|
|
889
|
+
props: {
|
|
890
|
+
type: "object",
|
|
891
|
+
description: "Props to write. Include only the props that should change."
|
|
892
|
+
},
|
|
893
|
+
merge: {
|
|
894
|
+
type: "boolean",
|
|
895
|
+
description: "true (default) merges with existing props. Only pass false to deliberately clear the other props."
|
|
896
|
+
}
|
|
897
|
+
},
|
|
898
|
+
required: ["nodeId", "props"]
|
|
899
|
+
}
|
|
900
|
+
},
|
|
901
|
+
execute(input, context) {
|
|
902
|
+
const nodeId = readString(input, "nodeId");
|
|
903
|
+
if (!nodeId) return fail("update_node_props: nodeId kosong", 'Argument "nodeId" is required.');
|
|
904
|
+
const props = readPlainObject(input, "props");
|
|
905
|
+
if (!props) {
|
|
906
|
+
return fail(
|
|
907
|
+
"update_node_props: props tidak valid",
|
|
908
|
+
'Argument "props" must be a JSON object of prop names to values.'
|
|
909
|
+
);
|
|
910
|
+
}
|
|
911
|
+
const resolved = resolveNode(context.document, nodeId, "update_node_props");
|
|
912
|
+
if ("error" in resolved) return resolved.error;
|
|
913
|
+
const merge = input.merge !== false;
|
|
914
|
+
const applied = applyCommand(
|
|
915
|
+
"update_node_props",
|
|
916
|
+
() => updateProps(context.document, { nodeId, props, merge })
|
|
917
|
+
);
|
|
918
|
+
if ("error" in applied) return applied.error;
|
|
919
|
+
const violation = guardSecurity("update_node_props", applied.document, context);
|
|
920
|
+
if (violation) return violation;
|
|
921
|
+
const op = { kind: "update-props", nodeId, props, merge };
|
|
922
|
+
const updated = findNodeById2(applied.document.document, nodeId);
|
|
923
|
+
return succeed(
|
|
924
|
+
`Ubah props ${describeNode(resolved.node)}`,
|
|
925
|
+
{ nodeId, props: updated?.props ?? props, changedKeys: Object.keys(props) },
|
|
926
|
+
{ op, document: applied.document }
|
|
927
|
+
);
|
|
928
|
+
}
|
|
929
|
+
};
|
|
930
|
+
var updateNodeStyles = {
|
|
931
|
+
kind: "write",
|
|
932
|
+
definition: {
|
|
933
|
+
name: "update_node_styles",
|
|
934
|
+
description: 'Change the styles of one existing node. Use `breakpoint` for normal styling and `state` for pseudo-classes like ":hover". This is the right tool for any color/spacing/size/typography change.',
|
|
935
|
+
inputSchema: {
|
|
936
|
+
type: "object",
|
|
937
|
+
properties: {
|
|
938
|
+
nodeId: { type: "string", description: "Id of the node to restyle." },
|
|
939
|
+
styles: {
|
|
940
|
+
type: "object",
|
|
941
|
+
description: 'CSS properties in camelCase with primitive values, e.g. { "backgroundColor": "#16a34a", "paddingTop": "24px" }. Never nest objects here.'
|
|
942
|
+
},
|
|
943
|
+
breakpoint: {
|
|
944
|
+
type: "string",
|
|
945
|
+
enum: ["base", "desktop", "tablet", "mobile"],
|
|
946
|
+
description: 'Breakpoint layer to write. Default "base".'
|
|
947
|
+
},
|
|
948
|
+
state: {
|
|
949
|
+
type: "string",
|
|
950
|
+
description: 'Pseudo-state layer such as ":hover", ":focus", ":active". Mutually exclusive with breakpoint.'
|
|
951
|
+
},
|
|
952
|
+
merge: {
|
|
953
|
+
type: "boolean",
|
|
954
|
+
description: "true (default) merges with existing styles in that layer."
|
|
955
|
+
}
|
|
956
|
+
},
|
|
957
|
+
required: ["nodeId", "styles"]
|
|
958
|
+
}
|
|
959
|
+
},
|
|
960
|
+
execute(input, context) {
|
|
961
|
+
const nodeId = readString(input, "nodeId");
|
|
962
|
+
if (!nodeId) return fail("update_node_styles: nodeId kosong", 'Argument "nodeId" is required.');
|
|
963
|
+
const styles = readPlainObject(input, "styles");
|
|
964
|
+
if (!styles) {
|
|
965
|
+
return fail(
|
|
966
|
+
"update_node_styles: styles tidak valid",
|
|
967
|
+
'Argument "styles" must be a JSON object of CSS properties with primitive values.'
|
|
968
|
+
);
|
|
969
|
+
}
|
|
970
|
+
const nested = Object.entries(styles).find(
|
|
971
|
+
([, value]) => value !== null && typeof value === "object"
|
|
972
|
+
);
|
|
973
|
+
if (nested) {
|
|
974
|
+
return fail(
|
|
975
|
+
"update_node_styles: nilai bersarang",
|
|
976
|
+
`Style property "${nested[0]}" has an object value. Style values must be primitives \u2014 use the "state" argument for pseudo-classes instead of nesting them.`
|
|
977
|
+
);
|
|
978
|
+
}
|
|
979
|
+
const rawState = readString(input, "state");
|
|
980
|
+
const rawBreakpoint = readString(input, "breakpoint");
|
|
981
|
+
if (rawState && rawBreakpoint) {
|
|
982
|
+
return fail(
|
|
983
|
+
"update_node_styles: argumen bentrok",
|
|
984
|
+
'Pass either "breakpoint" or "state", not both \u2014 a pseudo-state layer is not per-breakpoint.'
|
|
985
|
+
);
|
|
986
|
+
}
|
|
987
|
+
if (rawBreakpoint && !BREAKPOINTS.includes(rawBreakpoint)) {
|
|
988
|
+
return fail(
|
|
989
|
+
"update_node_styles: breakpoint tidak dikenal",
|
|
990
|
+
`Unknown breakpoint "${rawBreakpoint}". Valid values: ${BREAKPOINTS.join(", ")}.`
|
|
991
|
+
);
|
|
992
|
+
}
|
|
993
|
+
const resolved = resolveNode(context.document, nodeId, "update_node_styles");
|
|
994
|
+
if ("error" in resolved) return resolved.error;
|
|
995
|
+
const state = rawState ? rawState.startsWith(":") ? rawState : `:${rawState}` : void 0;
|
|
996
|
+
const breakpoint = state ? void 0 : rawBreakpoint ?? "base";
|
|
997
|
+
const merge = input.merge !== false;
|
|
998
|
+
const applied = applyCommand(
|
|
999
|
+
"update_node_styles",
|
|
1000
|
+
() => updateStyle(context.document, { nodeId, styles, breakpoint, state, merge })
|
|
1001
|
+
);
|
|
1002
|
+
if ("error" in applied) return applied.error;
|
|
1003
|
+
const violation = guardSecurity("update_node_styles", applied.document, context);
|
|
1004
|
+
if (violation) return violation;
|
|
1005
|
+
const op = { kind: "update-styles", nodeId, styles, breakpoint, state, merge };
|
|
1006
|
+
const layer = state ? `state ${state}` : `breakpoint ${breakpoint}`;
|
|
1007
|
+
return succeed(
|
|
1008
|
+
`Ubah style ${describeNode(resolved.node)} (${layer})`,
|
|
1009
|
+
{ nodeId, layer, appliedStyles: styles },
|
|
1010
|
+
{ op, document: applied.document }
|
|
1011
|
+
);
|
|
1012
|
+
}
|
|
1013
|
+
};
|
|
1014
|
+
var insertComponent = {
|
|
1015
|
+
kind: "write",
|
|
1016
|
+
definition: {
|
|
1017
|
+
name: "insert_component",
|
|
1018
|
+
description: "Insert one new component into an existing parent node. Use this for adding a single element (a button, a paragraph, an image). For a whole new page section, use insert_section instead.",
|
|
1019
|
+
inputSchema: {
|
|
1020
|
+
type: "object",
|
|
1021
|
+
properties: {
|
|
1022
|
+
parentId: { type: "string", description: "Id of the node that will contain the new component." },
|
|
1023
|
+
type: { type: "string", description: "Component type from the catalog." },
|
|
1024
|
+
index: {
|
|
1025
|
+
type: "integer",
|
|
1026
|
+
description: "Position among the parent children. Omit to append at the end."
|
|
1027
|
+
},
|
|
1028
|
+
props: { type: "object", description: "Initial props for the new component." },
|
|
1029
|
+
styles: {
|
|
1030
|
+
type: "object",
|
|
1031
|
+
description: 'Initial styles, e.g. { "base": { "marginTop": "16px" } }.'
|
|
1032
|
+
},
|
|
1033
|
+
children: {
|
|
1034
|
+
type: "array",
|
|
1035
|
+
description: "Optional nested child nodes, each { type, props?, styles?, children? }.",
|
|
1036
|
+
items: { type: "object" }
|
|
1037
|
+
}
|
|
1038
|
+
},
|
|
1039
|
+
required: ["parentId", "type"]
|
|
1040
|
+
}
|
|
1041
|
+
},
|
|
1042
|
+
execute(input, context) {
|
|
1043
|
+
const parentId = readString(input, "parentId");
|
|
1044
|
+
const type = readString(input, "type");
|
|
1045
|
+
if (!parentId || !type) {
|
|
1046
|
+
return fail(
|
|
1047
|
+
"insert_component: argumen kurang",
|
|
1048
|
+
'Arguments "parentId" and "type" are both required.'
|
|
1049
|
+
);
|
|
1050
|
+
}
|
|
1051
|
+
const typeError = checkComponentType(context.catalog, type);
|
|
1052
|
+
if (typeError) return fail("insert_component: tipe tidak dikenal", typeError);
|
|
1053
|
+
const resolvedParent = resolveNode(context.document, parentId, "insert_component");
|
|
1054
|
+
if ("error" in resolvedParent) return resolvedParent.error;
|
|
1055
|
+
const nestingError = checkNesting(context.catalog, resolvedParent.node, type);
|
|
1056
|
+
if (nestingError) return fail("insert_component: nesting tidak valid", nestingError);
|
|
1057
|
+
const normalized = normalizeIncomingNode(
|
|
1058
|
+
{
|
|
1059
|
+
type,
|
|
1060
|
+
props: readPlainObject(input, "props") ?? void 0,
|
|
1061
|
+
styles: readPlainObject(input, "styles") ?? void 0,
|
|
1062
|
+
children: Array.isArray(input.children) ? input.children : void 0
|
|
1063
|
+
},
|
|
1064
|
+
context.document
|
|
1065
|
+
);
|
|
1066
|
+
if ("error" in normalized) {
|
|
1067
|
+
return fail("insert_component: node tidak valid", normalized.error);
|
|
1068
|
+
}
|
|
1069
|
+
const index = readOptionalIndex(input, "index");
|
|
1070
|
+
const applied = applyCommand(
|
|
1071
|
+
"insert_component",
|
|
1072
|
+
() => insertNode(context.document, { parentId, node: normalized.node, index })
|
|
1073
|
+
);
|
|
1074
|
+
if ("error" in applied) return applied.error;
|
|
1075
|
+
const violation = guardSecurity("insert_component", applied.document, context);
|
|
1076
|
+
if (violation) return violation;
|
|
1077
|
+
const op = { kind: "insert-node", parentId, index, node: normalized.node };
|
|
1078
|
+
return succeed(
|
|
1079
|
+
`Tambah ${describeNode(normalized.node)} ke #${parentId}`,
|
|
1080
|
+
{ nodeId: normalized.node.id, parentId, index: index ?? null },
|
|
1081
|
+
{ op, document: applied.document }
|
|
1082
|
+
);
|
|
1083
|
+
}
|
|
1084
|
+
};
|
|
1085
|
+
var insertSection = {
|
|
1086
|
+
kind: "write",
|
|
1087
|
+
definition: {
|
|
1088
|
+
name: "insert_section",
|
|
1089
|
+
description: "Generate and insert a complete new page section (hero, features, pricing, FAQ, CTA, \u2026) from a description. Use this when the user asks to ADD a section \u2014 do not hand-build sections with insert_component.",
|
|
1090
|
+
inputSchema: {
|
|
1091
|
+
type: "object",
|
|
1092
|
+
properties: {
|
|
1093
|
+
prompt: {
|
|
1094
|
+
type: "string",
|
|
1095
|
+
description: "Detailed description of the section: purpose, content, layout and tone. Write it in the language the page uses."
|
|
1096
|
+
},
|
|
1097
|
+
index: {
|
|
1098
|
+
type: "integer",
|
|
1099
|
+
description: "Position among the page sections. Omit to append at the end."
|
|
1100
|
+
}
|
|
1101
|
+
},
|
|
1102
|
+
required: ["prompt"]
|
|
1103
|
+
}
|
|
1104
|
+
},
|
|
1105
|
+
async execute(input, context) {
|
|
1106
|
+
const prompt = readString(input, "prompt");
|
|
1107
|
+
if (!prompt) return fail("insert_section: prompt kosong", 'Argument "prompt" is required.');
|
|
1108
|
+
if (!context.generateSection) {
|
|
1109
|
+
return fail(
|
|
1110
|
+
"insert_section: tidak tersedia",
|
|
1111
|
+
"Section generation is not available in this deployment. Build the section with insert_component instead."
|
|
1112
|
+
);
|
|
1113
|
+
}
|
|
1114
|
+
const rootId = context.document.document.id;
|
|
1115
|
+
const existingSections = (context.document.document.children ?? []).map((child) => `${child.type}: ${getNodeLabel(child) || child.id}`).join(", ");
|
|
1116
|
+
let generated;
|
|
1117
|
+
try {
|
|
1118
|
+
generated = await context.generateSection({
|
|
1119
|
+
prompt,
|
|
1120
|
+
parentContext: `Page "${context.document.metadata?.title ?? "Untitled"}". Existing sections: ${existingSections || "(none)"}`
|
|
1121
|
+
});
|
|
1122
|
+
} catch (err) {
|
|
1123
|
+
return fail(
|
|
1124
|
+
"insert_section gagal",
|
|
1125
|
+
`Section generation failed: ${err instanceof Error ? err.message : String(err)}`
|
|
1126
|
+
);
|
|
1127
|
+
}
|
|
1128
|
+
const normalized = normalizeIncomingNode(generated, context.document);
|
|
1129
|
+
if ("error" in normalized) {
|
|
1130
|
+
return fail("insert_section: section tidak valid", normalized.error);
|
|
1131
|
+
}
|
|
1132
|
+
const index = readOptionalIndex(input, "index");
|
|
1133
|
+
const applied = applyCommand(
|
|
1134
|
+
"insert_section",
|
|
1135
|
+
() => insertNode(context.document, { parentId: rootId, node: normalized.node, index })
|
|
1136
|
+
);
|
|
1137
|
+
if ("error" in applied) return applied.error;
|
|
1138
|
+
const violation = guardSecurity("insert_section", applied.document, context);
|
|
1139
|
+
if (violation) return violation;
|
|
1140
|
+
const op = { kind: "insert-node", parentId: rootId, index, node: normalized.node };
|
|
1141
|
+
return succeed(
|
|
1142
|
+
`Tambah section baru (#${normalized.node.id})`,
|
|
1143
|
+
{
|
|
1144
|
+
nodeId: normalized.node.id,
|
|
1145
|
+
index: index ?? null,
|
|
1146
|
+
childCount: normalized.node.children?.length ?? 0
|
|
1147
|
+
},
|
|
1148
|
+
{ op, document: applied.document }
|
|
1149
|
+
);
|
|
1150
|
+
}
|
|
1151
|
+
};
|
|
1152
|
+
var moveNodeTool = {
|
|
1153
|
+
kind: "write",
|
|
1154
|
+
definition: {
|
|
1155
|
+
name: "move_node",
|
|
1156
|
+
description: "Move an existing node to a different parent and/or position.",
|
|
1157
|
+
inputSchema: {
|
|
1158
|
+
type: "object",
|
|
1159
|
+
properties: {
|
|
1160
|
+
nodeId: { type: "string" },
|
|
1161
|
+
targetParentId: { type: "string", description: "Id of the new parent node." },
|
|
1162
|
+
index: { type: "integer", description: "Position within the new parent. Omit to append." }
|
|
1163
|
+
},
|
|
1164
|
+
required: ["nodeId", "targetParentId"]
|
|
1165
|
+
}
|
|
1166
|
+
},
|
|
1167
|
+
execute(input, context) {
|
|
1168
|
+
const nodeId = readString(input, "nodeId");
|
|
1169
|
+
const targetParentId = readString(input, "targetParentId");
|
|
1170
|
+
if (!nodeId || !targetParentId) {
|
|
1171
|
+
return fail(
|
|
1172
|
+
"move_node: argumen kurang",
|
|
1173
|
+
'Arguments "nodeId" and "targetParentId" are both required.'
|
|
1174
|
+
);
|
|
1175
|
+
}
|
|
1176
|
+
const resolved = resolveNode(context.document, nodeId, "move_node");
|
|
1177
|
+
if ("error" in resolved) return resolved.error;
|
|
1178
|
+
const resolvedParent = resolveNode(context.document, targetParentId, "move_node");
|
|
1179
|
+
if ("error" in resolvedParent) return resolvedParent.error;
|
|
1180
|
+
const nestingError = checkNesting(context.catalog, resolvedParent.node, resolved.node.type);
|
|
1181
|
+
if (nestingError) return fail("move_node: nesting tidak valid", nestingError);
|
|
1182
|
+
const index = readOptionalIndex(input, "index");
|
|
1183
|
+
const applied = applyCommand(
|
|
1184
|
+
"move_node",
|
|
1185
|
+
() => moveNode(context.document, { nodeId, targetParentId, index })
|
|
1186
|
+
);
|
|
1187
|
+
if ("error" in applied) return applied.error;
|
|
1188
|
+
const violation = guardSecurity("move_node", applied.document, context);
|
|
1189
|
+
if (violation) return violation;
|
|
1190
|
+
const op = { kind: "move-node", nodeId, targetParentId, index };
|
|
1191
|
+
return succeed(
|
|
1192
|
+
`Pindah ${describeNode(resolved.node)} ke #${targetParentId}`,
|
|
1193
|
+
{ nodeId, targetParentId, index: index ?? null },
|
|
1194
|
+
{ op, document: applied.document }
|
|
1195
|
+
);
|
|
1196
|
+
}
|
|
1197
|
+
};
|
|
1198
|
+
var duplicateNodeTool = {
|
|
1199
|
+
kind: "write",
|
|
1200
|
+
definition: {
|
|
1201
|
+
name: "duplicate_node",
|
|
1202
|
+
description: "Duplicate an existing node and its subtree, with fresh ids. Useful for repeating an existing card/feature rather than authoring a new one from scratch.",
|
|
1203
|
+
inputSchema: {
|
|
1204
|
+
type: "object",
|
|
1205
|
+
properties: {
|
|
1206
|
+
nodeId: { type: "string" },
|
|
1207
|
+
targetParentId: {
|
|
1208
|
+
type: "string",
|
|
1209
|
+
description: "Where to put the copy. Defaults to the original node parent."
|
|
1210
|
+
},
|
|
1211
|
+
index: { type: "integer", description: "Position of the copy. Defaults to right after the original." }
|
|
1212
|
+
},
|
|
1213
|
+
required: ["nodeId"]
|
|
1214
|
+
}
|
|
1215
|
+
},
|
|
1216
|
+
execute(input, context) {
|
|
1217
|
+
const nodeId = readString(input, "nodeId");
|
|
1218
|
+
if (!nodeId) return fail("duplicate_node: nodeId kosong", 'Argument "nodeId" is required.');
|
|
1219
|
+
const resolved = resolveNode(context.document, nodeId, "duplicate_node");
|
|
1220
|
+
if ("error" in resolved) return resolved.error;
|
|
1221
|
+
const targetParentId = readString(input, "targetParentId") ?? void 0;
|
|
1222
|
+
if (targetParentId) {
|
|
1223
|
+
const resolvedParent = resolveNode(context.document, targetParentId, "duplicate_node");
|
|
1224
|
+
if ("error" in resolvedParent) return resolvedParent.error;
|
|
1225
|
+
}
|
|
1226
|
+
const index = readOptionalIndex(input, "index");
|
|
1227
|
+
const applied = applyCommand(
|
|
1228
|
+
"duplicate_node",
|
|
1229
|
+
() => duplicateNode(context.document, { nodeId, targetParentId, index })
|
|
1230
|
+
);
|
|
1231
|
+
if ("error" in applied) return applied.error;
|
|
1232
|
+
const violation = guardSecurity("duplicate_node", applied.document, context);
|
|
1233
|
+
if (violation) return violation;
|
|
1234
|
+
const op = { kind: "duplicate-node", nodeId, targetParentId, index };
|
|
1235
|
+
return succeed(
|
|
1236
|
+
`Duplikat ${describeNode(resolved.node)}`,
|
|
1237
|
+
{ nodeId, targetParentId: targetParentId ?? null, index: index ?? null },
|
|
1238
|
+
{ op, document: applied.document }
|
|
1239
|
+
);
|
|
1240
|
+
}
|
|
1241
|
+
};
|
|
1242
|
+
var deleteNode = {
|
|
1243
|
+
kind: "write",
|
|
1244
|
+
destructive: true,
|
|
1245
|
+
definition: {
|
|
1246
|
+
name: "delete_node",
|
|
1247
|
+
description: "Permanently remove a node and everything inside it. Only call this when the user unambiguously asked to remove that specific thing. If the request is vague, ask instead.",
|
|
1248
|
+
inputSchema: {
|
|
1249
|
+
type: "object",
|
|
1250
|
+
properties: {
|
|
1251
|
+
nodeId: { type: "string" },
|
|
1252
|
+
reason: {
|
|
1253
|
+
type: "string",
|
|
1254
|
+
description: "Short justification quoting what the user asked for. Shown to the user before they apply."
|
|
1255
|
+
}
|
|
1256
|
+
},
|
|
1257
|
+
required: ["nodeId", "reason"]
|
|
1258
|
+
}
|
|
1259
|
+
},
|
|
1260
|
+
execute(input, context) {
|
|
1261
|
+
const nodeId = readString(input, "nodeId");
|
|
1262
|
+
if (!nodeId) return fail("delete_node: nodeId kosong", 'Argument "nodeId" is required.');
|
|
1263
|
+
const reason = readString(input, "reason");
|
|
1264
|
+
if (!reason) {
|
|
1265
|
+
return fail(
|
|
1266
|
+
"delete_node: alasan kosong",
|
|
1267
|
+
'Argument "reason" is required for destructive actions \u2014 state what the user asked for.'
|
|
1268
|
+
);
|
|
1269
|
+
}
|
|
1270
|
+
const resolved = resolveNode(context.document, nodeId, "delete_node");
|
|
1271
|
+
if ("error" in resolved) return resolved.error;
|
|
1272
|
+
const applied = applyCommand("delete_node", () => removeNode(context.document, { nodeId }));
|
|
1273
|
+
if ("error" in applied) return applied.error;
|
|
1274
|
+
const op = { kind: "delete-node", nodeId };
|
|
1275
|
+
return succeed(
|
|
1276
|
+
`Hapus ${describeNode(resolved.node)} \u2014 ${reason}`,
|
|
1277
|
+
{ nodeId, removedType: resolved.node.type },
|
|
1278
|
+
{ op, document: applied.document }
|
|
1279
|
+
);
|
|
1280
|
+
}
|
|
1281
|
+
};
|
|
1282
|
+
var replaceNodeTool = {
|
|
1283
|
+
kind: "write",
|
|
1284
|
+
destructive: true,
|
|
1285
|
+
definition: {
|
|
1286
|
+
name: "replace_node",
|
|
1287
|
+
description: "Replace a node subtree wholesale. LAST RESORT \u2014 only when the children genuinely must be restructured. Never use it to change props or styles; use update_node_props / update_node_styles for that.",
|
|
1288
|
+
inputSchema: {
|
|
1289
|
+
type: "object",
|
|
1290
|
+
properties: {
|
|
1291
|
+
nodeId: { type: "string", description: "Id of the node being replaced. The replacement keeps this id." },
|
|
1292
|
+
node: {
|
|
1293
|
+
type: "object",
|
|
1294
|
+
description: "Replacement node { type, props?, styles?, children? }. Its id is forced to nodeId."
|
|
1295
|
+
},
|
|
1296
|
+
reason: {
|
|
1297
|
+
type: "string",
|
|
1298
|
+
description: "Why a structural replacement is required instead of a props/styles edit."
|
|
1299
|
+
}
|
|
1300
|
+
},
|
|
1301
|
+
required: ["nodeId", "node", "reason"]
|
|
1302
|
+
}
|
|
1303
|
+
},
|
|
1304
|
+
execute(input, context) {
|
|
1305
|
+
const nodeId = readString(input, "nodeId");
|
|
1306
|
+
const raw = readPlainObject(input, "node");
|
|
1307
|
+
const reason = readString(input, "reason");
|
|
1308
|
+
if (!nodeId || !raw || !reason) {
|
|
1309
|
+
return fail(
|
|
1310
|
+
"replace_node: argumen kurang",
|
|
1311
|
+
'Arguments "nodeId", "node" and "reason" are all required.'
|
|
1312
|
+
);
|
|
1313
|
+
}
|
|
1314
|
+
const resolved = resolveNode(context.document, nodeId, "replace_node");
|
|
1315
|
+
if ("error" in resolved) return resolved.error;
|
|
1316
|
+
const normalized = normalizeIncomingNode({ ...raw, id: void 0 }, context.document);
|
|
1317
|
+
if ("error" in normalized) {
|
|
1318
|
+
return fail("replace_node: node tidak valid", normalized.error);
|
|
1319
|
+
}
|
|
1320
|
+
const replacement = { ...normalized.node, id: nodeId, type: resolved.node.type };
|
|
1321
|
+
const applied = applyCommand(
|
|
1322
|
+
"replace_node",
|
|
1323
|
+
() => replaceNode(context.document, { nodeId, node: replacement })
|
|
1324
|
+
);
|
|
1325
|
+
if ("error" in applied) return applied.error;
|
|
1326
|
+
const violation = guardSecurity("replace_node", applied.document, context);
|
|
1327
|
+
if (violation) return violation;
|
|
1328
|
+
const op = { kind: "replace-node", nodeId, node: replacement };
|
|
1329
|
+
return succeed(
|
|
1330
|
+
`Ganti struktur ${describeNode(resolved.node)} \u2014 ${reason}`,
|
|
1331
|
+
{ nodeId, childCount: replacement.children?.length ?? 0 },
|
|
1332
|
+
{ op, document: applied.document }
|
|
1333
|
+
);
|
|
1334
|
+
}
|
|
1335
|
+
};
|
|
1336
|
+
var WRITE_TOOLS = [
|
|
1337
|
+
updateNodeProps,
|
|
1338
|
+
updateNodeStyles,
|
|
1339
|
+
insertComponent,
|
|
1340
|
+
insertSection,
|
|
1341
|
+
moveNodeTool,
|
|
1342
|
+
duplicateNodeTool,
|
|
1343
|
+
deleteNode,
|
|
1344
|
+
replaceNodeTool
|
|
1345
|
+
];
|
|
1346
|
+
|
|
1347
|
+
// src/server/tools/index.ts
|
|
1348
|
+
function createDocumentTools(options = {}) {
|
|
1349
|
+
const { allowWrites = true, exclude = [] } = options;
|
|
1350
|
+
const tools = allowWrites ? [...READ_TOOLS, ...WRITE_TOOLS] : [...READ_TOOLS];
|
|
1351
|
+
return tools.filter((tool) => !exclude.includes(tool.definition.name));
|
|
1352
|
+
}
|
|
1353
|
+
function toToolDefinitions(tools) {
|
|
1354
|
+
return tools.map((tool) => tool.definition);
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
// src/server/agent.ts
|
|
1358
|
+
var DEFAULT_MAX_STEPS = 8;
|
|
1359
|
+
var MAX_TOOL_CALLS_PER_STEP = 8;
|
|
1360
|
+
function toolResultMessage(blocks) {
|
|
1361
|
+
return { role: "user", content: blocks, timestamp: Date.now() };
|
|
1362
|
+
}
|
|
1363
|
+
var KubuildAiAgent = class {
|
|
1364
|
+
options;
|
|
1365
|
+
catalog;
|
|
1366
|
+
tools;
|
|
1367
|
+
constructor(options) {
|
|
1368
|
+
if (!options.adapter) {
|
|
1369
|
+
throw new Error("KubuildAiAgent requires an adapter instance");
|
|
1370
|
+
}
|
|
1371
|
+
if (!options.adapter.supportsTools) {
|
|
1372
|
+
throw new Error(
|
|
1373
|
+
`Adapter "${options.adapter.name}" does not support tool calling, which agent mode requires. Use an adapter with supportsTools === true (e.g. OpenAiAdapter).`
|
|
1374
|
+
);
|
|
1375
|
+
}
|
|
1376
|
+
this.options = options;
|
|
1377
|
+
this.catalog = compileComponentCatalog(options.registry);
|
|
1378
|
+
this.tools = options.tools ?? createDocumentTools();
|
|
1379
|
+
}
|
|
1380
|
+
get adapterName() {
|
|
1381
|
+
return this.options.adapter.name;
|
|
1382
|
+
}
|
|
1383
|
+
refreshCatalog() {
|
|
1384
|
+
this.catalog = compileComponentCatalog(this.options.registry);
|
|
1385
|
+
}
|
|
1386
|
+
log(level, message, meta) {
|
|
1387
|
+
if (this.options.logger) {
|
|
1388
|
+
this.options.logger(level, message, meta);
|
|
1389
|
+
} else if (this.options.debug) {
|
|
1390
|
+
console.log(`[KUBUILD-AGENT ${level.toUpperCase()}] ${message}`, meta !== void 0 ? meta : "");
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
findTool(name) {
|
|
1394
|
+
return this.tools.find((tool) => tool.definition.name === name);
|
|
1395
|
+
}
|
|
1396
|
+
/**
|
|
1397
|
+
* Runs one tool call against the current snapshot.
|
|
1398
|
+
*
|
|
1399
|
+
* An unknown tool name is answered with a normal (failed) tool result rather than an
|
|
1400
|
+
* exception, for the same reason bad arguments are: it gives the model a chance to pick a
|
|
1401
|
+
* real tool on the next step instead of killing the run.
|
|
1402
|
+
*/
|
|
1403
|
+
async executeToolCall(call, snapshot, signal) {
|
|
1404
|
+
const tool = this.findTool(call.name);
|
|
1405
|
+
if (!tool) {
|
|
1406
|
+
const available = this.tools.map((t) => t.definition.name).join(", ");
|
|
1407
|
+
return {
|
|
1408
|
+
ok: false,
|
|
1409
|
+
summary: `Tool "${call.name}" tidak dikenal`,
|
|
1410
|
+
snapshot,
|
|
1411
|
+
block: {
|
|
1412
|
+
type: "tool_result",
|
|
1413
|
+
toolUseId: call.id,
|
|
1414
|
+
isError: true,
|
|
1415
|
+
content: JSON.stringify({
|
|
1416
|
+
ok: false,
|
|
1417
|
+
error: `Unknown tool "${call.name}". Available tools: ${available}.`
|
|
1418
|
+
})
|
|
1419
|
+
}
|
|
1420
|
+
};
|
|
1421
|
+
}
|
|
1422
|
+
let result;
|
|
1423
|
+
try {
|
|
1424
|
+
result = await tool.execute(call.input ?? {}, {
|
|
1425
|
+
document: snapshot,
|
|
1426
|
+
catalog: this.catalog,
|
|
1427
|
+
securityLimits: this.options.securityLimits,
|
|
1428
|
+
signal,
|
|
1429
|
+
generateSection: this.options.engine ? async ({ prompt, parentContext }) => {
|
|
1430
|
+
const response = await this.options.engine.generateSection(
|
|
1431
|
+
{ prompt, parentContext },
|
|
1432
|
+
{ signal }
|
|
1433
|
+
);
|
|
1434
|
+
if (!response.success || !response.data) {
|
|
1435
|
+
throw new Error(response.error?.message ?? "Section generation failed");
|
|
1436
|
+
}
|
|
1437
|
+
return response.data;
|
|
1438
|
+
} : void 0
|
|
1439
|
+
});
|
|
1440
|
+
} catch (err) {
|
|
1441
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1442
|
+
this.log("error", `Tool "${call.name}" threw`, message);
|
|
1443
|
+
return {
|
|
1444
|
+
ok: false,
|
|
1445
|
+
summary: `Tool "${call.name}" error`,
|
|
1446
|
+
snapshot,
|
|
1447
|
+
block: {
|
|
1448
|
+
type: "tool_result",
|
|
1449
|
+
toolUseId: call.id,
|
|
1450
|
+
isError: true,
|
|
1451
|
+
content: JSON.stringify({ ok: false, error: message })
|
|
1452
|
+
}
|
|
1453
|
+
};
|
|
1454
|
+
}
|
|
1455
|
+
const op = result.ok && result.op ? {
|
|
1456
|
+
id: call.id,
|
|
1457
|
+
op: result.op,
|
|
1458
|
+
summary: result.summary,
|
|
1459
|
+
destructive: tool.destructive === true
|
|
1460
|
+
} : void 0;
|
|
1461
|
+
return {
|
|
1462
|
+
ok: result.ok,
|
|
1463
|
+
summary: result.summary,
|
|
1464
|
+
snapshot: result.document ?? snapshot,
|
|
1465
|
+
op,
|
|
1466
|
+
block: {
|
|
1467
|
+
type: "tool_result",
|
|
1468
|
+
toolUseId: call.id,
|
|
1469
|
+
isError: !result.ok,
|
|
1470
|
+
content: JSON.stringify(result.content)
|
|
1471
|
+
}
|
|
1472
|
+
};
|
|
1473
|
+
}
|
|
1474
|
+
/**
|
|
1475
|
+
* Executes one agent turn, streaming progress as `AiStreamEvent`s and ending with a
|
|
1476
|
+
* terminal `agent-complete` (or `error`) event.
|
|
1477
|
+
*
|
|
1478
|
+
* The loop stops as soon as the model returns a turn with no tool calls — that final
|
|
1479
|
+
* message is the summary shown to the user. It also stops at `maxSteps` (cost guard) or
|
|
1480
|
+
* on abort, and reports which of those happened via `stoppedBy` so the UI can say
|
|
1481
|
+
* "stopped after N steps" rather than pretending the work is finished.
|
|
1482
|
+
*/
|
|
1483
|
+
async *run(request, context) {
|
|
1484
|
+
const maxSteps = request.maxSteps ?? this.options.maxAgentSteps ?? DEFAULT_MAX_STEPS;
|
|
1485
|
+
const signal = context?.signal;
|
|
1486
|
+
if (!request.messages || request.messages.length === 0) {
|
|
1487
|
+
yield {
|
|
1488
|
+
type: "error",
|
|
1489
|
+
error: { code: "INVALID_AGENT_REQUEST", message: '"messages" must not be empty' }
|
|
1490
|
+
};
|
|
1491
|
+
return;
|
|
1492
|
+
}
|
|
1493
|
+
if (!request.document?.document) {
|
|
1494
|
+
yield {
|
|
1495
|
+
type: "error",
|
|
1496
|
+
error: { code: "INVALID_AGENT_REQUEST", message: '"document" is required for agent mode' }
|
|
1497
|
+
};
|
|
1498
|
+
return;
|
|
1499
|
+
}
|
|
1500
|
+
const systemPrompt = buildAgentSystemPrompt({
|
|
1501
|
+
catalog: this.catalog,
|
|
1502
|
+
document: request.document,
|
|
1503
|
+
selectedNodeId: request.selectedNodeId,
|
|
1504
|
+
prefix: this.options.systemPromptPrefix,
|
|
1505
|
+
stylePreference: request.stylePreference,
|
|
1506
|
+
additionalContext: request.systemPrompt
|
|
1507
|
+
});
|
|
1508
|
+
const toolDefinitions = toToolDefinitions(this.tools);
|
|
1509
|
+
const messages = [...request.messages];
|
|
1510
|
+
const ops = [];
|
|
1511
|
+
let snapshot = request.document;
|
|
1512
|
+
let promptTokens = 0;
|
|
1513
|
+
let completionTokens = 0;
|
|
1514
|
+
let summary = "";
|
|
1515
|
+
let stoppedBy = "complete";
|
|
1516
|
+
let step = 0;
|
|
1517
|
+
this.log("info", `[AGENT] run started (maxSteps=${maxSteps}, tools=${toolDefinitions.length})`);
|
|
1518
|
+
try {
|
|
1519
|
+
while (step < maxSteps) {
|
|
1520
|
+
if (signal?.aborted) {
|
|
1521
|
+
stoppedBy = "aborted";
|
|
1522
|
+
break;
|
|
1523
|
+
}
|
|
1524
|
+
step++;
|
|
1525
|
+
yield { type: "agent-step", step, maxSteps };
|
|
1526
|
+
const result = await this.options.adapter.generate({
|
|
1527
|
+
systemPrompt,
|
|
1528
|
+
userPrompt: getMessageText(request.messages[request.messages.length - 1]),
|
|
1529
|
+
messages,
|
|
1530
|
+
tools: toolDefinitions,
|
|
1531
|
+
toolChoice: "auto",
|
|
1532
|
+
signal
|
|
1533
|
+
});
|
|
1534
|
+
promptTokens += result.usage?.promptTokens ?? 0;
|
|
1535
|
+
completionTokens += result.usage?.completionTokens ?? 0;
|
|
1536
|
+
const toolCalls = result.toolCalls ?? [];
|
|
1537
|
+
if (toolCalls.length === 0) {
|
|
1538
|
+
summary = result.text.trim();
|
|
1539
|
+
stoppedBy = "complete";
|
|
1540
|
+
this.log("info", `[AGENT] finished after ${step} step(s) with ${ops.length} op(s)`);
|
|
1541
|
+
break;
|
|
1542
|
+
}
|
|
1543
|
+
const assistantBlocks = [];
|
|
1544
|
+
if (result.text.trim()) {
|
|
1545
|
+
assistantBlocks.push({ type: "text", text: result.text });
|
|
1546
|
+
}
|
|
1547
|
+
assistantBlocks.push(...toolCalls);
|
|
1548
|
+
messages.push({ role: "assistant", content: assistantBlocks, timestamp: Date.now() });
|
|
1549
|
+
const capped = toolCalls.slice(0, MAX_TOOL_CALLS_PER_STEP);
|
|
1550
|
+
const resultBlocks = [];
|
|
1551
|
+
for (const call of capped) {
|
|
1552
|
+
if (signal?.aborted) {
|
|
1553
|
+
stoppedBy = "aborted";
|
|
1554
|
+
break;
|
|
1555
|
+
}
|
|
1556
|
+
yield { type: "tool-call", id: call.id, name: call.name, input: call.input ?? {} };
|
|
1557
|
+
const executed = await this.executeToolCall(call, snapshot, signal);
|
|
1558
|
+
snapshot = executed.snapshot;
|
|
1559
|
+
resultBlocks.push(executed.block);
|
|
1560
|
+
if (executed.op) ops.push(executed.op);
|
|
1561
|
+
yield {
|
|
1562
|
+
type: "tool-result",
|
|
1563
|
+
id: call.id,
|
|
1564
|
+
name: call.name,
|
|
1565
|
+
ok: executed.ok,
|
|
1566
|
+
summary: executed.summary
|
|
1567
|
+
};
|
|
1568
|
+
}
|
|
1569
|
+
for (const call of toolCalls.slice(capped.length)) {
|
|
1570
|
+
resultBlocks.push({
|
|
1571
|
+
type: "tool_result",
|
|
1572
|
+
toolUseId: call.id,
|
|
1573
|
+
isError: true,
|
|
1574
|
+
content: JSON.stringify({
|
|
1575
|
+
ok: false,
|
|
1576
|
+
error: `Too many tool calls in one step (limit ${MAX_TOOL_CALLS_PER_STEP}). This call was not executed \u2014 request it again in the next step.`
|
|
1577
|
+
})
|
|
1578
|
+
});
|
|
1579
|
+
}
|
|
1580
|
+
messages.push(toolResultMessage(resultBlocks));
|
|
1581
|
+
if (stoppedBy === "aborted") break;
|
|
1582
|
+
}
|
|
1583
|
+
if (stoppedBy === "complete" && step >= maxSteps && !summary) {
|
|
1584
|
+
stoppedBy = "max-steps";
|
|
1585
|
+
summary = `Berhenti setelah ${maxSteps} langkah. ${ops.length} perubahan sudah disiapkan \u2014 periksa hasilnya lalu minta lanjutan bila perlu.`;
|
|
1586
|
+
this.log("warn", `[AGENT] hit maxSteps (${maxSteps}) with ${ops.length} op(s)`);
|
|
1587
|
+
}
|
|
1588
|
+
if (stoppedBy === "aborted" && !summary) {
|
|
1589
|
+
summary = `Dihentikan. ${ops.length} perubahan sempat disiapkan.`;
|
|
1590
|
+
}
|
|
1591
|
+
yield {
|
|
1592
|
+
type: "agent-complete",
|
|
1593
|
+
result: {
|
|
1594
|
+
ops,
|
|
1595
|
+
summary,
|
|
1596
|
+
stepsUsed: step,
|
|
1597
|
+
stoppedBy,
|
|
1598
|
+
usage: { promptTokens, completionTokens }
|
|
1599
|
+
}
|
|
1600
|
+
};
|
|
1601
|
+
} catch (err) {
|
|
1602
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1603
|
+
this.log("error", `[AGENT] run failed: ${message}`);
|
|
1604
|
+
yield {
|
|
1605
|
+
type: "agent-complete",
|
|
1606
|
+
result: {
|
|
1607
|
+
ops,
|
|
1608
|
+
summary: summary || `Terjadi error: ${message}`,
|
|
1609
|
+
stepsUsed: step,
|
|
1610
|
+
stoppedBy: "error",
|
|
1611
|
+
usage: { promptTokens, completionTokens }
|
|
1612
|
+
}
|
|
1613
|
+
};
|
|
1614
|
+
yield { type: "error", error: { code: "AGENT_ERROR", message } };
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
/** Non-streaming convenience wrapper — drains `run()` and returns its terminal result. */
|
|
1618
|
+
async execute(request, context) {
|
|
1619
|
+
let final = null;
|
|
1620
|
+
let error = null;
|
|
1621
|
+
for await (const event of this.run(request, context)) {
|
|
1622
|
+
if (event.type === "agent-complete") final = event.result;
|
|
1623
|
+
if (event.type === "error") error = event.error;
|
|
1624
|
+
}
|
|
1625
|
+
if (final) return final;
|
|
1626
|
+
return {
|
|
1627
|
+
ops: [],
|
|
1628
|
+
summary: error?.message ?? "Agent produced no result",
|
|
1629
|
+
stepsUsed: 0,
|
|
1630
|
+
stoppedBy: "error"
|
|
1631
|
+
};
|
|
1632
|
+
}
|
|
1633
|
+
};
|
|
1634
|
+
|
|
567
1635
|
// src/server/handler.ts
|
|
568
|
-
async function processAiRequest(engine, body, signal) {
|
|
1636
|
+
async function processAiRequest(engine, body, signal, agent) {
|
|
569
1637
|
if (!body || typeof body !== "object") {
|
|
570
1638
|
return {
|
|
571
1639
|
status: 400,
|
|
@@ -687,17 +1755,113 @@ async function processAiRequest(engine, body, signal) {
|
|
|
687
1755
|
response: result
|
|
688
1756
|
};
|
|
689
1757
|
}
|
|
1758
|
+
if (mode === "agent") {
|
|
1759
|
+
const validationError = validateAgentPayload(payload, agent);
|
|
1760
|
+
if (validationError) {
|
|
1761
|
+
return { status: validationError.status, response: validationError.response };
|
|
1762
|
+
}
|
|
1763
|
+
const result = await agent.execute(
|
|
1764
|
+
{
|
|
1765
|
+
messages: payload.messages,
|
|
1766
|
+
document: payload.document,
|
|
1767
|
+
selectedNodeId: payload.selectedNodeId,
|
|
1768
|
+
stylePreference: payload.stylePreference,
|
|
1769
|
+
maxSteps: payload.maxSteps
|
|
1770
|
+
},
|
|
1771
|
+
{ signal }
|
|
1772
|
+
);
|
|
1773
|
+
return {
|
|
1774
|
+
status: result.stoppedBy === "error" ? 500 : 200,
|
|
1775
|
+
response: { success: result.stoppedBy !== "error", data: result }
|
|
1776
|
+
};
|
|
1777
|
+
}
|
|
690
1778
|
return {
|
|
691
1779
|
status: 400,
|
|
692
1780
|
response: {
|
|
693
1781
|
success: false,
|
|
694
1782
|
error: {
|
|
695
1783
|
code: "UNKNOWN_MODE",
|
|
696
|
-
message: `Unsupported mode: ${String(mode)}. Supported modes: 'full-page', 'section', 'refactor', 'chat'`
|
|
1784
|
+
message: `Unsupported mode: ${String(mode)}. Supported modes: 'full-page', 'section', 'refactor', 'chat', 'agent'`
|
|
697
1785
|
}
|
|
698
1786
|
}
|
|
699
1787
|
};
|
|
700
1788
|
}
|
|
1789
|
+
function validateAgentPayload(payload, agent) {
|
|
1790
|
+
if (!agent) {
|
|
1791
|
+
return {
|
|
1792
|
+
status: 501,
|
|
1793
|
+
response: {
|
|
1794
|
+
success: false,
|
|
1795
|
+
error: {
|
|
1796
|
+
code: "AGENT_NOT_CONFIGURED",
|
|
1797
|
+
message: "Agent mode is not enabled on this endpoint. Pass a KubuildAiAgent to createAiHandler to enable it."
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
};
|
|
1801
|
+
}
|
|
1802
|
+
if (!payload.messages || !Array.isArray(payload.messages) || payload.messages.length === 0) {
|
|
1803
|
+
return {
|
|
1804
|
+
status: 400,
|
|
1805
|
+
response: {
|
|
1806
|
+
success: false,
|
|
1807
|
+
error: {
|
|
1808
|
+
code: "INVALID_AGENT_PARAMS",
|
|
1809
|
+
message: '"messages" array is required and must not be empty for agent mode'
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
};
|
|
1813
|
+
}
|
|
1814
|
+
if (!payload.document || !payload.document.document) {
|
|
1815
|
+
return {
|
|
1816
|
+
status: 400,
|
|
1817
|
+
response: {
|
|
1818
|
+
success: false,
|
|
1819
|
+
error: {
|
|
1820
|
+
code: "INVALID_AGENT_PARAMS",
|
|
1821
|
+
message: '"document" (the current PageDocument) is required for agent mode'
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
};
|
|
1825
|
+
}
|
|
1826
|
+
return null;
|
|
1827
|
+
}
|
|
1828
|
+
function sseResponse(events, errorCode) {
|
|
1829
|
+
const encoder = new TextEncoder();
|
|
1830
|
+
const readable = new ReadableStream({
|
|
1831
|
+
async start(controller) {
|
|
1832
|
+
try {
|
|
1833
|
+
for await (const event of events) {
|
|
1834
|
+
controller.enqueue(
|
|
1835
|
+
encoder.encode(`event: ${event.type}
|
|
1836
|
+
data: ${JSON.stringify(event)}
|
|
1837
|
+
|
|
1838
|
+
`)
|
|
1839
|
+
);
|
|
1840
|
+
}
|
|
1841
|
+
} catch (err) {
|
|
1842
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1843
|
+
controller.enqueue(
|
|
1844
|
+
encoder.encode(
|
|
1845
|
+
`event: error
|
|
1846
|
+
data: ${JSON.stringify({ type: "error", error: { code: errorCode, message } })}
|
|
1847
|
+
|
|
1848
|
+
`
|
|
1849
|
+
)
|
|
1850
|
+
);
|
|
1851
|
+
} finally {
|
|
1852
|
+
controller.close();
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
});
|
|
1856
|
+
return new Response(readable, {
|
|
1857
|
+
status: 200,
|
|
1858
|
+
headers: {
|
|
1859
|
+
"Content-Type": "text/event-stream",
|
|
1860
|
+
"Cache-Control": "no-cache, no-transform",
|
|
1861
|
+
Connection: "keep-alive"
|
|
1862
|
+
}
|
|
1863
|
+
});
|
|
1864
|
+
}
|
|
701
1865
|
function createAiHandler(engine, options) {
|
|
702
1866
|
return async (request) => {
|
|
703
1867
|
try {
|
|
@@ -728,6 +1892,28 @@ function createAiHandler(engine, options) {
|
|
|
728
1892
|
const body = await request.json().catch(() => null);
|
|
729
1893
|
if (body && typeof body === "object" && body.stream === true) {
|
|
730
1894
|
const mode = body.mode || "full-page";
|
|
1895
|
+
if (mode === "agent") {
|
|
1896
|
+
const validationError = validateAgentPayload(body, options?.agent);
|
|
1897
|
+
if (validationError) {
|
|
1898
|
+
return new Response(JSON.stringify(validationError.response), {
|
|
1899
|
+
status: validationError.status,
|
|
1900
|
+
headers: { "Content-Type": "application/json" }
|
|
1901
|
+
});
|
|
1902
|
+
}
|
|
1903
|
+
return sseResponse(
|
|
1904
|
+
options.agent.run(
|
|
1905
|
+
{
|
|
1906
|
+
messages: body.messages,
|
|
1907
|
+
document: body.document,
|
|
1908
|
+
selectedNodeId: body.selectedNodeId,
|
|
1909
|
+
stylePreference: body.stylePreference,
|
|
1910
|
+
maxSteps: body.maxSteps
|
|
1911
|
+
},
|
|
1912
|
+
{ signal: request.signal }
|
|
1913
|
+
),
|
|
1914
|
+
"AGENT_STREAM_ERROR"
|
|
1915
|
+
);
|
|
1916
|
+
}
|
|
731
1917
|
if (mode === "chat") {
|
|
732
1918
|
if (!body.messages || !Array.isArray(body.messages) || body.messages.length === 0) {
|
|
733
1919
|
return new Response(
|
|
@@ -744,48 +1930,17 @@ function createAiHandler(engine, options) {
|
|
|
744
1930
|
}
|
|
745
1931
|
);
|
|
746
1932
|
}
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
)) {
|
|
759
|
-
controller.enqueue(
|
|
760
|
-
encoder2.encode(`event: ${event.type}
|
|
761
|
-
data: ${JSON.stringify(event)}
|
|
762
|
-
|
|
763
|
-
`)
|
|
764
|
-
);
|
|
765
|
-
}
|
|
766
|
-
} catch (err) {
|
|
767
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
768
|
-
controller.enqueue(
|
|
769
|
-
encoder2.encode(
|
|
770
|
-
`event: error
|
|
771
|
-
data: ${JSON.stringify({ type: "error", error: { code: "CHAT_STREAM_ERROR", message } })}
|
|
772
|
-
|
|
773
|
-
`
|
|
774
|
-
)
|
|
775
|
-
);
|
|
776
|
-
} finally {
|
|
777
|
-
controller.close();
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
});
|
|
781
|
-
return new Response(readable2, {
|
|
782
|
-
status: 200,
|
|
783
|
-
headers: {
|
|
784
|
-
"Content-Type": "text/event-stream",
|
|
785
|
-
"Cache-Control": "no-cache, no-transform",
|
|
786
|
-
Connection: "keep-alive"
|
|
787
|
-
}
|
|
788
|
-
});
|
|
1933
|
+
return sseResponse(
|
|
1934
|
+
engine.chatStream(
|
|
1935
|
+
{
|
|
1936
|
+
messages: body.messages,
|
|
1937
|
+
currentDocument: body.currentDocument,
|
|
1938
|
+
selectedNodeId: body.selectedNodeId
|
|
1939
|
+
},
|
|
1940
|
+
{ signal: request.signal }
|
|
1941
|
+
),
|
|
1942
|
+
"CHAT_STREAM_ERROR"
|
|
1943
|
+
);
|
|
789
1944
|
}
|
|
790
1945
|
if (!body.prompt || typeof body.prompt !== "string") {
|
|
791
1946
|
return new Response(
|
|
@@ -802,55 +1957,25 @@ data: ${JSON.stringify({ type: "error", error: { code: "CHAT_STREAM_ERROR", mess
|
|
|
802
1957
|
}
|
|
803
1958
|
);
|
|
804
1959
|
}
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
)) {
|
|
819
|
-
controller.enqueue(
|
|
820
|
-
encoder.encode(`event: ${event.type}
|
|
821
|
-
data: ${JSON.stringify(event)}
|
|
822
|
-
|
|
823
|
-
`)
|
|
824
|
-
);
|
|
825
|
-
}
|
|
826
|
-
} catch (err) {
|
|
827
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
828
|
-
controller.enqueue(
|
|
829
|
-
encoder.encode(
|
|
830
|
-
`event: error
|
|
831
|
-
data: ${JSON.stringify({ type: "error", error: { code: "STREAM_ERROR", message } })}
|
|
832
|
-
|
|
833
|
-
`
|
|
834
|
-
)
|
|
835
|
-
);
|
|
836
|
-
} finally {
|
|
837
|
-
controller.close();
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
});
|
|
841
|
-
return new Response(readable, {
|
|
842
|
-
status: 200,
|
|
843
|
-
headers: {
|
|
844
|
-
"Content-Type": "text/event-stream",
|
|
845
|
-
"Cache-Control": "no-cache, no-transform",
|
|
846
|
-
Connection: "keep-alive"
|
|
847
|
-
}
|
|
848
|
-
});
|
|
1960
|
+
return sseResponse(
|
|
1961
|
+
engine.streamPage(
|
|
1962
|
+
{
|
|
1963
|
+
prompt: body.prompt,
|
|
1964
|
+
stylePreference: body.stylePreference,
|
|
1965
|
+
tone: body.tone,
|
|
1966
|
+
locale: body.locale,
|
|
1967
|
+
metadata: body.metadata
|
|
1968
|
+
},
|
|
1969
|
+
{ signal: request.signal }
|
|
1970
|
+
),
|
|
1971
|
+
"STREAM_ERROR"
|
|
1972
|
+
);
|
|
849
1973
|
}
|
|
850
1974
|
const { status, response } = await processAiRequest(
|
|
851
1975
|
engine,
|
|
852
1976
|
body,
|
|
853
|
-
request.signal
|
|
1977
|
+
request.signal,
|
|
1978
|
+
options?.agent
|
|
854
1979
|
);
|
|
855
1980
|
return new Response(JSON.stringify(response), {
|
|
856
1981
|
status,
|
|
@@ -882,9 +2007,19 @@ export {
|
|
|
882
2007
|
AnthropicAdapter,
|
|
883
2008
|
CustomHttpAdapter,
|
|
884
2009
|
GeminiAdapter,
|
|
2010
|
+
KubuildAiAgent,
|
|
885
2011
|
KubuildAiEngine,
|
|
886
2012
|
OpenAiAdapter,
|
|
2013
|
+
READ_TOOLS,
|
|
2014
|
+
WRITE_TOOLS,
|
|
2015
|
+
checkComponentType,
|
|
2016
|
+
checkDocumentSecurity,
|
|
2017
|
+
checkNesting,
|
|
887
2018
|
createAiHandler,
|
|
888
|
-
|
|
2019
|
+
createDocumentTools,
|
|
2020
|
+
normalizeIncomingNode,
|
|
2021
|
+
processAiRequest,
|
|
2022
|
+
suggestNodeIds,
|
|
2023
|
+
toToolDefinitions
|
|
889
2024
|
};
|
|
890
2025
|
//# sourceMappingURL=index.js.map
|