Spaces:
Running
Running
dylanebert
commited on
Commit
·
ec75a88
1
Parent(s):
eb1a39a
langgraph.js migration
Browse files- CLAUDE.md +1 -1
- bun.lock +60 -1
- layers/structure.md +2 -4
- package.json +4 -1
- src/lib/components/chat/ReasoningBlock.svelte +2 -5
- src/lib/server/agent-config.ts +0 -19
- src/lib/server/agent-runner.test.ts +0 -100
- src/lib/server/agent-runner.ts +0 -176
- src/lib/server/api.ts +38 -16
- src/lib/server/context.md +22 -18
- src/lib/server/hint.test.ts +0 -31
- src/lib/server/langgraph-agent.ts +333 -0
- src/lib/server/prompts.ts +0 -46
- src/lib/server/reasoning-extractor.test.ts +0 -114
- src/lib/server/reasoning-extractor.ts +0 -131
- src/lib/server/tools.ts +62 -0
- src/lib/stores/agent.ts +47 -1
- src/lib/stores/loading.ts +0 -3
- src/lib/tools/context.md +0 -37
- src/lib/tools/executor.ts +0 -36
- src/lib/tools/index.ts +0 -17
- src/lib/tools/parser.ts +0 -41
- src/lib/tools/read-console-output.ts +0 -47
- src/lib/tools/read-game-code.ts +0 -18
- src/lib/tools/registry.ts +0 -51
- src/lib/tools/tools-integration.test.ts +0 -110
- src/lib/tools/write-game-code.ts +0 -38
- vite.config.ts +1 -4
CLAUDE.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1 |
# AI Context - Working Agreement
|
2 |
|
3 |
<project-description>
|
4 |
-
AI-assisted game development environment using the VibeGame engine. Iterative development with Svelte UI, Monaco editor, and
|
5 |
</project-description>
|
6 |
|
7 |
**Required**: Read [layers/structure.md](layers/structure.md) before proceeding with any task
|
|
|
1 |
# AI Context - Working Agreement
|
2 |
|
3 |
<project-description>
|
4 |
+
AI-assisted game development environment using the VibeGame engine. Iterative development with Svelte UI, Monaco editor, and LangGraph.js agent for AI-driven game modifications.
|
5 |
</project-description>
|
6 |
|
7 |
**Required**: Read [layers/structure.md](layers/structure.md) before proceeding with any task
|
bun.lock
CHANGED
@@ -6,6 +6,8 @@
|
|
6 |
"dependencies": {
|
7 |
"@huggingface/hub": "^2.6.3",
|
8 |
"@huggingface/inference": "^4.8.0",
|
|
|
|
|
9 |
"@types/marked": "^6.0.0",
|
10 |
"@types/node": "^24.3.3",
|
11 |
"gsap": "^3.13.0",
|
@@ -13,6 +15,7 @@
|
|
13 |
"monaco-editor": "^0.50.0",
|
14 |
"svelte-splitpanes": "^8.0.5",
|
15 |
"vibegame": "^0.1.1",
|
|
|
16 |
},
|
17 |
"devDependencies": {
|
18 |
"@eslint/js": "^9.33.0",
|
@@ -37,6 +40,8 @@
|
|
37 |
"packages": {
|
38 |
"@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="],
|
39 |
|
|
|
|
|
40 |
"@dimforge/rapier3d-compat": ["@dimforge/rapier3d-compat@0.18.2", "", {}, "sha512-DTXrOsn3ra9ZonB+VyqJc16xnRXWsHVa5FK230Z+R1PJ7q8oSRmWQ+AU6e+IJYBHxkM0a5QVDqd729DyeEhHPA=="],
|
41 |
|
42 |
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.21.5", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ=="],
|
@@ -127,6 +132,14 @@
|
|
127 |
|
128 |
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
129 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
130 |
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
|
131 |
|
132 |
"@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
|
@@ -199,6 +212,10 @@
|
|
199 |
|
200 |
"@types/react": ["@types/react@19.1.13", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ=="],
|
201 |
|
|
|
|
|
|
|
|
|
202 |
"@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
|
203 |
|
204 |
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.43.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.43.0", "@typescript-eslint/type-utils": "8.43.0", "@typescript-eslint/utils": "8.43.0", "@typescript-eslint/visitor-keys": "8.43.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.43.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-8tg+gt7ENL7KewsKMKDHXR1vm8tt9eMxjJBYINf6swonlWgkYn5NwyIgXpbbDxTNU5DgpDFfj95prcTq2clIQQ=="],
|
@@ -229,7 +246,7 @@
|
|
229 |
|
230 |
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
231 |
|
232 |
-
"ansi-styles": ["ansi-styles@
|
233 |
|
234 |
"anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
|
235 |
|
@@ -257,6 +274,8 @@
|
|
257 |
|
258 |
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
259 |
|
|
|
|
|
260 |
"binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="],
|
261 |
|
262 |
"bitecs": ["bitecs@0.3.40", "", {}, "sha512-wAylY4pNfX8IeIH5phtwt1lUNtHKrkoSNrArI7Ris2Y4nEQWFIVvXdgAuqprEg9bq8Wolmlj0gVfeG6MFmtI2Q=="],
|
@@ -277,6 +296,8 @@
|
|
277 |
|
278 |
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
|
279 |
|
|
|
|
|
280 |
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
281 |
|
282 |
"chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
|
@@ -291,6 +312,8 @@
|
|
291 |
|
292 |
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
|
293 |
|
|
|
|
|
294 |
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
|
295 |
|
296 |
"css-tree": ["css-tree@2.3.1", "", { "dependencies": { "mdn-data": "2.0.30", "source-map-js": "^1.0.1" } }, "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw=="],
|
@@ -305,6 +328,8 @@
|
|
305 |
|
306 |
"debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
|
307 |
|
|
|
|
|
308 |
"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
|
309 |
|
310 |
"deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
|
@@ -373,6 +398,8 @@
|
|
373 |
|
374 |
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
|
375 |
|
|
|
|
|
376 |
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
|
377 |
|
378 |
"fast-diff": ["fast-diff@1.3.0", "", {}, "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw=="],
|
@@ -515,6 +542,8 @@
|
|
515 |
|
516 |
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
517 |
|
|
|
|
|
518 |
"js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="],
|
519 |
|
520 |
"json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="],
|
@@ -529,6 +558,8 @@
|
|
529 |
|
530 |
"kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
|
531 |
|
|
|
|
|
532 |
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
|
533 |
|
534 |
"locate-character": ["locate-character@3.0.0", "", {}, "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="],
|
@@ -563,6 +594,8 @@
|
|
563 |
|
564 |
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
565 |
|
|
|
|
|
566 |
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
|
567 |
|
568 |
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
|
@@ -587,10 +620,18 @@
|
|
587 |
|
588 |
"own-keys": ["own-keys@1.0.1", "", { "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg=="],
|
589 |
|
|
|
|
|
590 |
"p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
|
591 |
|
592 |
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
|
593 |
|
|
|
|
|
|
|
|
|
|
|
|
|
594 |
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
|
595 |
|
596 |
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
|
@@ -631,6 +672,8 @@
|
|
631 |
|
632 |
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
|
633 |
|
|
|
|
|
634 |
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
|
635 |
|
636 |
"rimraf": ["rimraf@2.7.1", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "./bin.js" } }, "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w=="],
|
@@ -669,6 +712,8 @@
|
|
669 |
|
670 |
"side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
|
671 |
|
|
|
|
|
672 |
"sorcery": ["sorcery@0.11.1", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.14", "buffer-crc32": "^1.0.0", "minimist": "^1.2.0", "sander": "^0.5.0" }, "bin": { "sorcery": "bin/sorcery" } }, "sha512-o7npfeJE6wi6J9l0/5LKshFzZ2rMatRiCDwYeDQaOzqdzRJwALhX7mk/A/ecg6wjMu7wdZbmXfD2S/vpOg0bdQ=="],
|
673 |
|
674 |
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
@@ -733,6 +778,8 @@
|
|
733 |
|
734 |
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
|
735 |
|
|
|
|
|
736 |
"vibegame": ["vibegame@0.1.1", "", { "dependencies": { "@dimforge/rapier3d-compat": "^0.18.2", "gsap": "^3.13.0", "zod": "^4.1.5" }, "peerDependencies": { "bitecs": ">=0.3.40", "three": ">=0.170.0" } }, "sha512-HjWMlO4qUus9ChEBsD+abS9UGle/qiDZWukIqkve811N2oliKjWE7WYCDhqaDhWS18FaaYaY1a/Vc8yF0HaS1Q=="],
|
737 |
|
738 |
"vite": ["vite@5.4.20", "", { "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", "rollup": "^4.20.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || >=20.0.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" }, "optionalPeers": ["@types/node", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser"], "bin": { "vite": "bin/vite.js" } }, "sha512-j3lYzGC3P+B5Yfy/pfKNgVEg4+UtcIJcVRt2cDjIOmhLourAqPqf8P7acgxeiSgUB7E3p2P8/3gNIgDLpwzs4g=="],
|
@@ -759,14 +806,24 @@
|
|
759 |
|
760 |
"zod": ["zod@4.1.8", "", {}, "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ=="],
|
761 |
|
|
|
|
|
762 |
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
763 |
|
764 |
"@eslint/eslintrc/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
|
765 |
|
|
|
|
|
|
|
|
|
|
|
|
|
766 |
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
767 |
|
768 |
"@typescript-eslint/typescript-estree/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
|
769 |
|
|
|
|
|
770 |
"chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
771 |
|
772 |
"eslint/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
|
@@ -779,6 +836,8 @@
|
|
779 |
|
780 |
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
781 |
|
|
|
|
|
782 |
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
|
783 |
}
|
784 |
}
|
|
|
6 |
"dependencies": {
|
7 |
"@huggingface/hub": "^2.6.3",
|
8 |
"@huggingface/inference": "^4.8.0",
|
9 |
+
"@langchain/core": "^0.3.75",
|
10 |
+
"@langchain/langgraph": "^0.4.9",
|
11 |
"@types/marked": "^6.0.0",
|
12 |
"@types/node": "^24.3.3",
|
13 |
"gsap": "^3.13.0",
|
|
|
15 |
"monaco-editor": "^0.50.0",
|
16 |
"svelte-splitpanes": "^8.0.5",
|
17 |
"vibegame": "^0.1.1",
|
18 |
+
"zod": "^4.1.8",
|
19 |
},
|
20 |
"devDependencies": {
|
21 |
"@eslint/js": "^9.33.0",
|
|
|
40 |
"packages": {
|
41 |
"@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="],
|
42 |
|
43 |
+
"@cfworker/json-schema": ["@cfworker/json-schema@4.1.1", "", {}, "sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og=="],
|
44 |
+
|
45 |
"@dimforge/rapier3d-compat": ["@dimforge/rapier3d-compat@0.18.2", "", {}, "sha512-DTXrOsn3ra9ZonB+VyqJc16xnRXWsHVa5FK230Z+R1PJ7q8oSRmWQ+AU6e+IJYBHxkM0a5QVDqd729DyeEhHPA=="],
|
46 |
|
47 |
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.21.5", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ=="],
|
|
|
132 |
|
133 |
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
134 |
|
135 |
+
"@langchain/core": ["@langchain/core@0.3.75", "", { "dependencies": { "@cfworker/json-schema": "^4.0.2", "ansi-styles": "^5.0.0", "camelcase": "6", "decamelize": "1.2.0", "js-tiktoken": "^1.0.12", "langsmith": "^0.3.67", "mustache": "^4.2.0", "p-queue": "^6.6.2", "p-retry": "4", "uuid": "^10.0.0", "zod": "^3.25.32", "zod-to-json-schema": "^3.22.3" } }, "sha512-kTyBS0DTeD0JYa9YH5lg6UdDbHmvplk3t9PCjP5jDQZCK5kPe2aDFToqdiCaLzZg8RzzM+clXLVyJtPTE8bZ2Q=="],
|
136 |
+
|
137 |
+
"@langchain/langgraph": ["@langchain/langgraph@0.4.9", "", { "dependencies": { "@langchain/langgraph-checkpoint": "^0.1.1", "@langchain/langgraph-sdk": "~0.1.0", "uuid": "^10.0.0", "zod": "^3.25.32" }, "peerDependencies": { "@langchain/core": ">=0.3.58 < 0.4.0", "zod-to-json-schema": "^3.x" }, "optionalPeers": ["zod-to-json-schema"] }, "sha512-+rcdTGi4Ium4X/VtIX3Zw4RhxEkYWpwUyz806V6rffjHOAMamg6/WZDxpJbrP33RV/wJG1GH12Z29oX3Pqq3Aw=="],
|
138 |
+
|
139 |
+
"@langchain/langgraph-checkpoint": ["@langchain/langgraph-checkpoint@0.1.1", "", { "dependencies": { "uuid": "^10.0.0" }, "peerDependencies": { "@langchain/core": ">=0.2.31 <0.4.0 || ^1.0.0-alpha" } }, "sha512-h2bP0RUikQZu0Um1ZUPErQLXyhzroJqKRbRcxYRTAh49oNlsfeq4A3K4YEDRbGGuyPZI/Jiqwhks1wZwY73AZw=="],
|
140 |
+
|
141 |
+
"@langchain/langgraph-sdk": ["@langchain/langgraph-sdk@0.1.3", "", { "dependencies": { "@types/json-schema": "^7.0.15", "p-queue": "^6.6.2", "p-retry": "4", "uuid": "^9.0.0" }, "peerDependencies": { "@langchain/core": ">=0.2.31 <0.4.0", "react": "^18 || ^19", "react-dom": "^18 || ^19" }, "optionalPeers": ["@langchain/core", "react", "react-dom"] }, "sha512-muZJ+D9A7gL0/QSvsjL6LMwgvN58euO2+MwSXq5Nb40ZgnjVWWtUYmjdVLno4IpgAXWXOQi34i2XtLbTwbyXvg=="],
|
142 |
+
|
143 |
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
|
144 |
|
145 |
"@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
|
|
|
212 |
|
213 |
"@types/react": ["@types/react@19.1.13", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ=="],
|
214 |
|
215 |
+
"@types/retry": ["@types/retry@0.12.0", "", {}, "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA=="],
|
216 |
+
|
217 |
+
"@types/uuid": ["@types/uuid@10.0.0", "", {}, "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ=="],
|
218 |
+
|
219 |
"@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
|
220 |
|
221 |
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.43.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.43.0", "@typescript-eslint/type-utils": "8.43.0", "@typescript-eslint/utils": "8.43.0", "@typescript-eslint/visitor-keys": "8.43.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.43.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-8tg+gt7ENL7KewsKMKDHXR1vm8tt9eMxjJBYINf6swonlWgkYn5NwyIgXpbbDxTNU5DgpDFfj95prcTq2clIQQ=="],
|
|
|
246 |
|
247 |
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
248 |
|
249 |
+
"ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="],
|
250 |
|
251 |
"anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
|
252 |
|
|
|
274 |
|
275 |
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
276 |
|
277 |
+
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
|
278 |
+
|
279 |
"binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="],
|
280 |
|
281 |
"bitecs": ["bitecs@0.3.40", "", {}, "sha512-wAylY4pNfX8IeIH5phtwt1lUNtHKrkoSNrArI7Ris2Y4nEQWFIVvXdgAuqprEg9bq8Wolmlj0gVfeG6MFmtI2Q=="],
|
|
|
296 |
|
297 |
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
|
298 |
|
299 |
+
"camelcase": ["camelcase@6.3.0", "", {}, "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA=="],
|
300 |
+
|
301 |
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
302 |
|
303 |
"chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
|
|
|
312 |
|
313 |
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
|
314 |
|
315 |
+
"console-table-printer": ["console-table-printer@2.14.6", "", { "dependencies": { "simple-wcswidth": "^1.0.1" } }, "sha512-MCBl5HNVaFuuHW6FGbL/4fB7N/ormCy+tQ+sxTrF6QtSbSNETvPuOVbkJBhzDgYhvjWGrTma4eYJa37ZuoQsPw=="],
|
316 |
+
|
317 |
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
|
318 |
|
319 |
"css-tree": ["css-tree@2.3.1", "", { "dependencies": { "mdn-data": "2.0.30", "source-map-js": "^1.0.1" } }, "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw=="],
|
|
|
328 |
|
329 |
"debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
|
330 |
|
331 |
+
"decamelize": ["decamelize@1.2.0", "", {}, "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA=="],
|
332 |
+
|
333 |
"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
|
334 |
|
335 |
"deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
|
|
|
398 |
|
399 |
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
|
400 |
|
401 |
+
"eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="],
|
402 |
+
|
403 |
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
|
404 |
|
405 |
"fast-diff": ["fast-diff@1.3.0", "", {}, "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw=="],
|
|
|
542 |
|
543 |
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
544 |
|
545 |
+
"js-tiktoken": ["js-tiktoken@1.0.21", "", { "dependencies": { "base64-js": "^1.5.1" } }, "sha512-biOj/6M5qdgx5TKjDnFT1ymSpM5tbd3ylwDtrQvFQSu0Z7bBYko2dF+W/aUkXUPuk6IVpRxk/3Q2sHOzGlS36g=="],
|
546 |
+
|
547 |
"js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="],
|
548 |
|
549 |
"json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="],
|
|
|
558 |
|
559 |
"kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
|
560 |
|
561 |
+
"langsmith": ["langsmith@0.3.68", "", { "dependencies": { "@types/uuid": "^10.0.0", "chalk": "^4.1.2", "console-table-printer": "^2.12.1", "p-queue": "^6.6.2", "p-retry": "4", "semver": "^7.6.3", "uuid": "^10.0.0" }, "peerDependencies": { "@opentelemetry/api": "*", "@opentelemetry/exporter-trace-otlp-proto": "*", "@opentelemetry/sdk-trace-base": "*", "openai": "*" }, "optionalPeers": ["@opentelemetry/api", "@opentelemetry/exporter-trace-otlp-proto", "@opentelemetry/sdk-trace-base", "openai"] }, "sha512-Yx4fnyTjrPKtqH2ax9nb6Ua6XAMYkafKkOLMcTzbJ/w+Yu3V6JjE+vabl/Q600oC53bo3hg6ourI4/KdZVXr4A=="],
|
562 |
+
|
563 |
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
|
564 |
|
565 |
"locate-character": ["locate-character@3.0.0", "", {}, "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="],
|
|
|
594 |
|
595 |
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
596 |
|
597 |
+
"mustache": ["mustache@4.2.0", "", { "bin": { "mustache": "bin/mustache" } }, "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ=="],
|
598 |
+
|
599 |
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
|
600 |
|
601 |
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
|
|
|
620 |
|
621 |
"own-keys": ["own-keys@1.0.1", "", { "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg=="],
|
622 |
|
623 |
+
"p-finally": ["p-finally@1.0.0", "", {}, "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow=="],
|
624 |
+
|
625 |
"p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
|
626 |
|
627 |
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
|
628 |
|
629 |
+
"p-queue": ["p-queue@6.6.2", "", { "dependencies": { "eventemitter3": "^4.0.4", "p-timeout": "^3.2.0" } }, "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ=="],
|
630 |
+
|
631 |
+
"p-retry": ["p-retry@4.6.2", "", { "dependencies": { "@types/retry": "0.12.0", "retry": "^0.13.1" } }, "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ=="],
|
632 |
+
|
633 |
+
"p-timeout": ["p-timeout@3.2.0", "", { "dependencies": { "p-finally": "^1.0.0" } }, "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg=="],
|
634 |
+
|
635 |
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
|
636 |
|
637 |
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
|
|
|
672 |
|
673 |
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
|
674 |
|
675 |
+
"retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="],
|
676 |
+
|
677 |
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
|
678 |
|
679 |
"rimraf": ["rimraf@2.7.1", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "./bin.js" } }, "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w=="],
|
|
|
712 |
|
713 |
"side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
|
714 |
|
715 |
+
"simple-wcswidth": ["simple-wcswidth@1.1.2", "", {}, "sha512-j7piyCjAeTDSjzTSQ7DokZtMNwNlEAyxqSZeCS+CXH7fJ4jx3FuJ/mTW3mE+6JLs4VJBbcll0Kjn+KXI5t21Iw=="],
|
716 |
+
|
717 |
"sorcery": ["sorcery@0.11.1", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.14", "buffer-crc32": "^1.0.0", "minimist": "^1.2.0", "sander": "^0.5.0" }, "bin": { "sorcery": "bin/sorcery" } }, "sha512-o7npfeJE6wi6J9l0/5LKshFzZ2rMatRiCDwYeDQaOzqdzRJwALhX7mk/A/ecg6wjMu7wdZbmXfD2S/vpOg0bdQ=="],
|
718 |
|
719 |
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
|
|
778 |
|
779 |
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
|
780 |
|
781 |
+
"uuid": ["uuid@10.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ=="],
|
782 |
+
|
783 |
"vibegame": ["vibegame@0.1.1", "", { "dependencies": { "@dimforge/rapier3d-compat": "^0.18.2", "gsap": "^3.13.0", "zod": "^4.1.5" }, "peerDependencies": { "bitecs": ">=0.3.40", "three": ">=0.170.0" } }, "sha512-HjWMlO4qUus9ChEBsD+abS9UGle/qiDZWukIqkve811N2oliKjWE7WYCDhqaDhWS18FaaYaY1a/Vc8yF0HaS1Q=="],
|
784 |
|
785 |
"vite": ["vite@5.4.20", "", { "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", "rollup": "^4.20.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || >=20.0.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" }, "optionalPeers": ["@types/node", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser"], "bin": { "vite": "bin/vite.js" } }, "sha512-j3lYzGC3P+B5Yfy/pfKNgVEg4+UtcIJcVRt2cDjIOmhLourAqPqf8P7acgxeiSgUB7E3p2P8/3gNIgDLpwzs4g=="],
|
|
|
806 |
|
807 |
"zod": ["zod@4.1.8", "", {}, "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ=="],
|
808 |
|
809 |
+
"zod-to-json-schema": ["zod-to-json-schema@3.24.6", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg=="],
|
810 |
+
|
811 |
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
812 |
|
813 |
"@eslint/eslintrc/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
|
814 |
|
815 |
+
"@langchain/core/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
816 |
+
|
817 |
+
"@langchain/langgraph/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
818 |
+
|
819 |
+
"@langchain/langgraph-sdk/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],
|
820 |
+
|
821 |
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
822 |
|
823 |
"@typescript-eslint/typescript-estree/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
|
824 |
|
825 |
+
"chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
826 |
+
|
827 |
"chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
828 |
|
829 |
"eslint/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
|
|
|
836 |
|
837 |
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
838 |
|
839 |
+
"langsmith/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
|
840 |
+
|
841 |
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
|
842 |
}
|
843 |
}
|
layers/structure.md
CHANGED
@@ -9,7 +9,7 @@ AI-assisted iterative game development environment with real-time feedback
|
|
9 |
- UI Framework: Svelte
|
10 |
- Game Engine: VibeGame (3D physics, ECS)
|
11 |
- Code Editor: Monaco Editor
|
12 |
-
- AI Agent: Hugging Face Inference
|
13 |
- WebSocket: ws (for real-time communication)
|
14 |
- Build: Vite
|
15 |
- Animation: GSAP (bundled with VibeGame)
|
@@ -64,9 +64,8 @@ vibegame/
|
|
64 |
Three-layer architecture: UI (Svelte) → Agent (Hugging Face) → Game (VibeGame)
|
65 |
|
66 |
- **UI Layer**: Svelte components for chat, code editing, console display
|
67 |
-
- **Agent Layer**:
|
68 |
- **Game Layer**: VibeGame ECS with declarative XML scene definition
|
69 |
-
- **Feedback Loop**: Console output → Agent parsing → Self-correction (planned)
|
70 |
|
71 |
## Entry points
|
72 |
|
@@ -99,7 +98,6 @@ TypeScript/Web standards with Svelte and ECS conventions
|
|
99 |
- State management → `src/lib/stores/[store].ts`
|
100 |
- Business logic → `src/lib/services/[service].ts`
|
101 |
- Server/Agent logic → `src/lib/server/[module].ts`
|
102 |
-
- MCP tools → `src/lib/tools/[tool].ts`
|
103 |
- Configuration → `src/lib/config/[config].ts`
|
104 |
- Game entities → XML in Monaco editor
|
105 |
- Custom ECS components → `src/lib/game/components/[Component].ts`
|
|
|
9 |
- UI Framework: Svelte
|
10 |
- Game Engine: VibeGame (3D physics, ECS)
|
11 |
- Code Editor: Monaco Editor
|
12 |
+
- AI Agent: LangGraph.js with Hugging Face Inference
|
13 |
- WebSocket: ws (for real-time communication)
|
14 |
- Build: Vite
|
15 |
- Animation: GSAP (bundled with VibeGame)
|
|
|
64 |
Three-layer architecture: UI (Svelte) → Agent (Hugging Face) → Game (VibeGame)
|
65 |
|
66 |
- **UI Layer**: Svelte components for chat, code editing, console display
|
67 |
+
- **Agent Layer**: LangGraph.js agent via WebSocket with Read/Write tools
|
68 |
- **Game Layer**: VibeGame ECS with declarative XML scene definition
|
|
|
69 |
|
70 |
## Entry points
|
71 |
|
|
|
98 |
- State management → `src/lib/stores/[store].ts`
|
99 |
- Business logic → `src/lib/services/[service].ts`
|
100 |
- Server/Agent logic → `src/lib/server/[module].ts`
|
|
|
101 |
- Configuration → `src/lib/config/[config].ts`
|
102 |
- Game entities → XML in Monaco editor
|
103 |
- Custom ECS components → `src/lib/game/components/[Component].ts`
|
package.json
CHANGED
@@ -38,12 +38,15 @@
|
|
38 |
"dependencies": {
|
39 |
"@huggingface/hub": "^2.6.3",
|
40 |
"@huggingface/inference": "^4.8.0",
|
|
|
|
|
41 |
"@types/marked": "^6.0.0",
|
42 |
"@types/node": "^24.3.3",
|
43 |
"gsap": "^3.13.0",
|
44 |
"marked": "^16.2.1",
|
45 |
"monaco-editor": "^0.50.0",
|
46 |
"svelte-splitpanes": "^8.0.5",
|
47 |
-
"vibegame": "^0.1.1"
|
|
|
48 |
}
|
49 |
}
|
|
|
38 |
"dependencies": {
|
39 |
"@huggingface/hub": "^2.6.3",
|
40 |
"@huggingface/inference": "^4.8.0",
|
41 |
+
"@langchain/core": "^0.3.75",
|
42 |
+
"@langchain/langgraph": "^0.4.9",
|
43 |
"@types/marked": "^6.0.0",
|
44 |
"@types/node": "^24.3.3",
|
45 |
"gsap": "^3.13.0",
|
46 |
"marked": "^16.2.1",
|
47 |
"monaco-editor": "^0.50.0",
|
48 |
"svelte-splitpanes": "^8.0.5",
|
49 |
+
"vibegame": "^0.1.1",
|
50 |
+
"zod": "^4.1.8"
|
51 |
}
|
52 |
}
|
src/lib/components/chat/ReasoningBlock.svelte
CHANGED
@@ -14,14 +14,12 @@
|
|
14 |
if (!iconElement || !contentElement) return;
|
15 |
|
16 |
if (isExpanded) {
|
17 |
-
// Expand - quick but smooth
|
18 |
gsap.to(iconElement, {
|
19 |
rotation: 180,
|
20 |
duration: 0.15,
|
21 |
ease: "power2.out"
|
22 |
});
|
23 |
|
24 |
-
// Show content
|
25 |
gsap.set(contentElement, {
|
26 |
display: 'block'
|
27 |
});
|
@@ -32,14 +30,13 @@
|
|
32 |
y: -10
|
33 |
}, {
|
34 |
opacity: 1,
|
35 |
-
maxHeight: 500,
|
36 |
y: 0,
|
37 |
duration: 0.2,
|
38 |
ease: "power2.out"
|
39 |
});
|
40 |
|
41 |
} else {
|
42 |
-
// Collapse - nearly instant
|
43 |
gsap.to(iconElement, {
|
44 |
rotation: 0,
|
45 |
duration: 0.1,
|
@@ -234,4 +231,4 @@
|
|
234 |
.reasoning-text::-webkit-scrollbar-thumb:hover {
|
235 |
background: rgba(120, 120, 130, 0.5);
|
236 |
}
|
237 |
-
</style>
|
|
|
14 |
if (!iconElement || !contentElement) return;
|
15 |
|
16 |
if (isExpanded) {
|
|
|
17 |
gsap.to(iconElement, {
|
18 |
rotation: 180,
|
19 |
duration: 0.15,
|
20 |
ease: "power2.out"
|
21 |
});
|
22 |
|
|
|
23 |
gsap.set(contentElement, {
|
24 |
display: 'block'
|
25 |
});
|
|
|
30 |
y: -10
|
31 |
}, {
|
32 |
opacity: 1,
|
33 |
+
maxHeight: 500,
|
34 |
y: 0,
|
35 |
duration: 0.2,
|
36 |
ease: "power2.out"
|
37 |
});
|
38 |
|
39 |
} else {
|
|
|
40 |
gsap.to(iconElement, {
|
41 |
rotation: 0,
|
42 |
duration: 0.1,
|
|
|
231 |
.reasoning-text::-webkit-scrollbar-thumb:hover {
|
232 |
background: rgba(120, 120, 130, 0.5);
|
233 |
}
|
234 |
+
</style>
|
src/lib/server/agent-config.ts
DELETED
@@ -1,19 +0,0 @@
|
|
1 |
-
export interface AgentConfig {
|
2 |
-
model?: string;
|
3 |
-
maxIterations?: number;
|
4 |
-
temperature?: number;
|
5 |
-
maxTokens?: number;
|
6 |
-
}
|
7 |
-
|
8 |
-
export const DEFAULT_CONFIG: Required<AgentConfig> = {
|
9 |
-
model: "Qwen/Qwen3-Next-80B-A3B-Instruct",
|
10 |
-
maxIterations: 10,
|
11 |
-
temperature: 0.5,
|
12 |
-
maxTokens: 1200,
|
13 |
-
};
|
14 |
-
|
15 |
-
export const mergeConfig = (
|
16 |
-
config: AgentConfig = {},
|
17 |
-
): Required<AgentConfig> => {
|
18 |
-
return { ...DEFAULT_CONFIG, ...config };
|
19 |
-
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/lib/server/agent-runner.test.ts
DELETED
@@ -1,100 +0,0 @@
|
|
1 |
-
import { describe, expect, test, beforeAll } from "bun:test";
|
2 |
-
import { AgentRunner } from "./agent-runner";
|
3 |
-
import "../tools"; // Import to register tools
|
4 |
-
|
5 |
-
describe("AgentRunner Tool Execution", () => {
|
6 |
-
beforeAll(() => {
|
7 |
-
// Create runner instance for test context
|
8 |
-
new AgentRunner({
|
9 |
-
model: "test-model",
|
10 |
-
temperature: 0.7,
|
11 |
-
maxTokens: 1000,
|
12 |
-
});
|
13 |
-
});
|
14 |
-
|
15 |
-
describe("Tool Detection", () => {
|
16 |
-
test("should detect tool calls in response", async () => {
|
17 |
-
const { parseToolCalls } = await import("../tools");
|
18 |
-
|
19 |
-
// Test various agent responses
|
20 |
-
const responses = [
|
21 |
-
{
|
22 |
-
text: "I'll read the game code for you.\n\n[TOOL: read_game_code]\n\nAnalyzing...",
|
23 |
-
expected: ["read_game_code"],
|
24 |
-
},
|
25 |
-
{
|
26 |
-
text: "Let me check that. [TOOL: read_game_code] Now I can see the code.",
|
27 |
-
expected: ["read_game_code"],
|
28 |
-
},
|
29 |
-
{
|
30 |
-
text: "No tools needed for this response.",
|
31 |
-
expected: [],
|
32 |
-
},
|
33 |
-
];
|
34 |
-
|
35 |
-
for (const { text, expected } of responses) {
|
36 |
-
const calls = parseToolCalls(text);
|
37 |
-
expect(calls.map((c) => c.tool)).toEqual(expected);
|
38 |
-
}
|
39 |
-
});
|
40 |
-
|
41 |
-
test("should format tool results correctly", async () => {
|
42 |
-
const { formatToolResult } = await import("../tools");
|
43 |
-
|
44 |
-
const successResult = {
|
45 |
-
success: true,
|
46 |
-
data: { content: "<world>test</world>", language: "html" },
|
47 |
-
};
|
48 |
-
|
49 |
-
const formatted = formatToolResult("read_game_code", successResult);
|
50 |
-
expect(formatted).toContain("[TOOL_RESULT: read_game_code]");
|
51 |
-
expect(formatted).toContain("<world>test</world>");
|
52 |
-
expect(formatted).toContain("[/TOOL_RESULT]");
|
53 |
-
});
|
54 |
-
});
|
55 |
-
|
56 |
-
describe("System Prompt", () => {
|
57 |
-
test("should include tool instructions", async () => {
|
58 |
-
const { toolRegistry } = await import("../tools");
|
59 |
-
const descriptions = toolRegistry.getToolDescriptions();
|
60 |
-
|
61 |
-
// System prompt should mention the tool
|
62 |
-
expect(descriptions).toContain("read_game_code");
|
63 |
-
expect(descriptions).toContain(
|
64 |
-
"Get the current game code from the editor",
|
65 |
-
);
|
66 |
-
});
|
67 |
-
});
|
68 |
-
});
|
69 |
-
|
70 |
-
// Integration test that would require mocking HuggingFace API
|
71 |
-
describe("Full Tool Flow (Mock)", () => {
|
72 |
-
test("should execute tool when detected in response", async () => {
|
73 |
-
// This test demonstrates the expected flow
|
74 |
-
// In production, this would make actual API calls
|
75 |
-
|
76 |
-
const mockResponse = "I'll check the game code.\n\n[TOOL: read_game_code]";
|
77 |
-
const { parseToolCalls, toolRegistry } = await import("../tools");
|
78 |
-
|
79 |
-
// 1. Parse tool calls from response
|
80 |
-
const calls = parseToolCalls(mockResponse);
|
81 |
-
expect(calls).toHaveLength(1);
|
82 |
-
expect(calls[0].tool).toBe("read_game_code");
|
83 |
-
|
84 |
-
// 2. Execute the tool
|
85 |
-
const result = await toolRegistry.execute(
|
86 |
-
calls[0].tool,
|
87 |
-
calls[0].parameters,
|
88 |
-
);
|
89 |
-
expect(result.success).toBe(true);
|
90 |
-
expect(result.data).toBeDefined();
|
91 |
-
|
92 |
-
// 3. Format result for context
|
93 |
-
const { formatToolResult } = await import("../tools");
|
94 |
-
const formatted = formatToolResult(calls[0].tool, result);
|
95 |
-
expect(formatted).toContain("[TOOL_RESULT:");
|
96 |
-
|
97 |
-
// 4. In the real flow, this formatted result would be added to message history
|
98 |
-
// and a follow-up API call would be made to process it
|
99 |
-
});
|
100 |
-
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/lib/server/agent-runner.ts
DELETED
@@ -1,176 +0,0 @@
|
|
1 |
-
import { HfInference } from "@huggingface/inference";
|
2 |
-
import { toolRegistry, toolExecutor } from "../tools";
|
3 |
-
import { documentationService } from "./documentation";
|
4 |
-
import {
|
5 |
-
buildSystemPrompt,
|
6 |
-
buildToolFollowUpPrompt,
|
7 |
-
formatDocumentation,
|
8 |
-
} from "./prompts";
|
9 |
-
import { mergeConfig, type AgentConfig } from "./agent-config";
|
10 |
-
import { UniversalReasoningExtractor } from "./reasoning-extractor";
|
11 |
-
|
12 |
-
export interface AgentMessage {
|
13 |
-
id: string;
|
14 |
-
role: "user" | "assistant" | "system";
|
15 |
-
content: string;
|
16 |
-
timestamp: number;
|
17 |
-
reasoning?: string;
|
18 |
-
}
|
19 |
-
|
20 |
-
export class AgentRunner {
|
21 |
-
private client: HfInference | null = null;
|
22 |
-
private config: Required<AgentConfig>;
|
23 |
-
private messageHistory: AgentMessage[] = [];
|
24 |
-
private reasoningExtractor = new UniversalReasoningExtractor();
|
25 |
-
|
26 |
-
constructor(config: AgentConfig = {}) {
|
27 |
-
this.config = mergeConfig(config);
|
28 |
-
}
|
29 |
-
|
30 |
-
async initialize(hfToken?: string) {
|
31 |
-
if (!hfToken) {
|
32 |
-
throw new Error(
|
33 |
-
"Hugging Face authentication required. Please sign in to continue.",
|
34 |
-
);
|
35 |
-
}
|
36 |
-
this.client = new HfInference(hfToken);
|
37 |
-
}
|
38 |
-
|
39 |
-
async processMessage(
|
40 |
-
message: string,
|
41 |
-
onStream?: (chunk: string, reasoning?: string) => void,
|
42 |
-
): Promise<string> {
|
43 |
-
if (!this.client) {
|
44 |
-
throw new Error("AgentRunner not initialized. Call initialize() first.");
|
45 |
-
}
|
46 |
-
|
47 |
-
this.addUserMessage(message);
|
48 |
-
|
49 |
-
const documentation = await this.loadDocumentation();
|
50 |
-
const systemPrompt = this.buildPrompt(documentation);
|
51 |
-
|
52 |
-
this.reasoningExtractor.reset();
|
53 |
-
const response = await this.streamCompletion(systemPrompt, onStream);
|
54 |
-
|
55 |
-
const extracted = this.reasoningExtractor.extract(response);
|
56 |
-
this.addAssistantMessage(extracted.content, extracted.reasoning);
|
57 |
-
|
58 |
-
const toolResults = await this.executeToolsIfNeeded(extracted.content);
|
59 |
-
|
60 |
-
if (toolResults.length > 0) {
|
61 |
-
this.addToolResults(toolResults);
|
62 |
-
return await this.handleToolFollowUp(documentation, onStream);
|
63 |
-
}
|
64 |
-
|
65 |
-
return extracted.content;
|
66 |
-
}
|
67 |
-
|
68 |
-
clearHistory() {
|
69 |
-
this.messageHistory = [];
|
70 |
-
}
|
71 |
-
|
72 |
-
getHistory(): AgentMessage[] {
|
73 |
-
return [...this.messageHistory];
|
74 |
-
}
|
75 |
-
|
76 |
-
private async loadDocumentation(): Promise<string> {
|
77 |
-
const fullDocs = await documentationService.load();
|
78 |
-
return formatDocumentation(fullDocs || "");
|
79 |
-
}
|
80 |
-
|
81 |
-
private buildPrompt(documentation: string): string {
|
82 |
-
const tools = toolRegistry.getToolDescriptions();
|
83 |
-
return buildSystemPrompt(tools, documentation);
|
84 |
-
}
|
85 |
-
|
86 |
-
private async streamCompletion(
|
87 |
-
systemPrompt: string,
|
88 |
-
onStream?: (chunk: string, reasoning?: string) => void,
|
89 |
-
): Promise<string> {
|
90 |
-
const messages = this.buildMessages(systemPrompt);
|
91 |
-
|
92 |
-
const stream = await this.client!.chatCompletionStream({
|
93 |
-
model: this.config.model,
|
94 |
-
messages: messages as Array<{ role: string; content: string }>,
|
95 |
-
temperature: this.config.temperature,
|
96 |
-
max_tokens: this.config.maxTokens,
|
97 |
-
});
|
98 |
-
|
99 |
-
let fullResponse = "";
|
100 |
-
for await (const chunk of stream) {
|
101 |
-
const delta = chunk.choices?.[0]?.delta?.content;
|
102 |
-
if (delta) {
|
103 |
-
fullResponse += delta;
|
104 |
-
|
105 |
-
const result = this.reasoningExtractor.stream(delta);
|
106 |
-
if (result.partial) {
|
107 |
-
onStream?.(result.partial, result.reasoning);
|
108 |
-
} else if (result.reasoning) {
|
109 |
-
onStream?.("", result.reasoning);
|
110 |
-
}
|
111 |
-
}
|
112 |
-
}
|
113 |
-
|
114 |
-
return fullResponse;
|
115 |
-
}
|
116 |
-
|
117 |
-
private buildMessages(systemPrompt: string) {
|
118 |
-
return [
|
119 |
-
{ role: "system", content: systemPrompt },
|
120 |
-
...this.messageHistory.map((msg) => ({
|
121 |
-
role: msg.role,
|
122 |
-
content: msg.content,
|
123 |
-
})),
|
124 |
-
];
|
125 |
-
}
|
126 |
-
|
127 |
-
private async executeToolsIfNeeded(response: string) {
|
128 |
-
return toolExecutor.executeFromResponse(response);
|
129 |
-
}
|
130 |
-
|
131 |
-
private addToolResults(results: { formatted: string }[]) {
|
132 |
-
for (const result of results) {
|
133 |
-
this.messageHistory.push({
|
134 |
-
id: this.generateId(),
|
135 |
-
role: "system",
|
136 |
-
content: result.formatted,
|
137 |
-
timestamp: Date.now(),
|
138 |
-
});
|
139 |
-
}
|
140 |
-
}
|
141 |
-
|
142 |
-
private async handleToolFollowUp(
|
143 |
-
documentation: string,
|
144 |
-
onStream?: (chunk: string, reasoning?: string) => void,
|
145 |
-
): Promise<string> {
|
146 |
-
const followUpPrompt = buildToolFollowUpPrompt(documentation);
|
147 |
-
this.reasoningExtractor.reset();
|
148 |
-
const response = await this.streamCompletion(followUpPrompt, onStream);
|
149 |
-
const extracted = this.reasoningExtractor.extract(response);
|
150 |
-
this.addAssistantMessage(extracted.content, extracted.reasoning);
|
151 |
-
return extracted.content;
|
152 |
-
}
|
153 |
-
|
154 |
-
private addUserMessage(content: string) {
|
155 |
-
this.messageHistory.push({
|
156 |
-
id: this.generateId(),
|
157 |
-
role: "user",
|
158 |
-
content,
|
159 |
-
timestamp: Date.now(),
|
160 |
-
});
|
161 |
-
}
|
162 |
-
|
163 |
-
private addAssistantMessage(content: string, reasoning?: string) {
|
164 |
-
this.messageHistory.push({
|
165 |
-
id: this.generateId(),
|
166 |
-
role: "assistant",
|
167 |
-
content,
|
168 |
-
timestamp: Date.now(),
|
169 |
-
reasoning,
|
170 |
-
});
|
171 |
-
}
|
172 |
-
|
173 |
-
private generateId(): string {
|
174 |
-
return `msg-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
175 |
-
}
|
176 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/lib/server/api.ts
CHANGED
@@ -1,26 +1,40 @@
|
|
1 |
import type { IncomingMessage } from "http";
|
2 |
import type { WebSocket } from "ws";
|
3 |
-
import
|
|
|
|
|
4 |
|
5 |
export interface WebSocketMessage {
|
6 |
-
type:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
7 |
payload: {
|
8 |
content?: string;
|
9 |
role?: string;
|
10 |
chunk?: string;
|
11 |
-
reasoning?: string;
|
12 |
error?: string;
|
13 |
processing?: boolean;
|
14 |
connected?: boolean;
|
15 |
message?: string;
|
16 |
token?: string;
|
|
|
|
|
|
|
17 |
};
|
18 |
timestamp: number;
|
19 |
}
|
20 |
|
21 |
class WebSocketManager {
|
22 |
-
private connections: Map<
|
23 |
-
|
|
|
|
|
24 |
|
25 |
handleConnection(ws: WebSocket, _request: IncomingMessage) {
|
26 |
this.connections.set(ws, {});
|
@@ -60,10 +74,9 @@ class WebSocketManager {
|
|
60 |
connectionData.token = message.payload.token;
|
61 |
|
62 |
try {
|
63 |
-
|
64 |
-
|
65 |
-
connectionData.
|
66 |
-
await connectionData.agent.initialize(message.payload.token);
|
67 |
|
68 |
this.sendMessage(ws, {
|
69 |
type: "status",
|
@@ -82,6 +95,12 @@ class WebSocketManager {
|
|
82 |
}
|
83 |
break;
|
84 |
|
|
|
|
|
|
|
|
|
|
|
|
|
85 |
case "chat":
|
86 |
try {
|
87 |
if (!connectionData?.agent) {
|
@@ -101,17 +120,25 @@ class WebSocketManager {
|
|
101 |
timestamp: Date.now(),
|
102 |
});
|
103 |
|
|
|
|
|
|
|
|
|
|
|
104 |
const response = await connectionData.agent.processMessage(
|
105 |
userMessage,
|
106 |
-
(
|
|
|
107 |
this.sendMessage(ws, {
|
108 |
type: "stream",
|
109 |
-
payload: { chunk
|
110 |
timestamp: Date.now(),
|
111 |
});
|
112 |
},
|
113 |
);
|
114 |
|
|
|
|
|
115 |
this.sendMessage(ws, {
|
116 |
type: "chat",
|
117 |
payload: {
|
@@ -162,8 +189,3 @@ class WebSocketManager {
|
|
162 |
}
|
163 |
|
164 |
export const wsManager = new WebSocketManager();
|
165 |
-
|
166 |
-
export async function initializeAgent() {
|
167 |
-
// Initialize tools when server starts
|
168 |
-
await import("../tools");
|
169 |
-
}
|
|
|
1 |
import type { IncomingMessage } from "http";
|
2 |
import type { WebSocket } from "ws";
|
3 |
+
import { LangGraphAgent } from "./langgraph-agent";
|
4 |
+
import { HumanMessage, AIMessage, BaseMessage } from "@langchain/core/messages";
|
5 |
+
import { updateEditorContent } from "./tools";
|
6 |
|
7 |
export interface WebSocketMessage {
|
8 |
+
type:
|
9 |
+
| "chat"
|
10 |
+
| "error"
|
11 |
+
| "status"
|
12 |
+
| "stream"
|
13 |
+
| "auth"
|
14 |
+
| "editor_update"
|
15 |
+
| "editor_sync"
|
16 |
+
| "tool_execution";
|
17 |
payload: {
|
18 |
content?: string;
|
19 |
role?: string;
|
20 |
chunk?: string;
|
|
|
21 |
error?: string;
|
22 |
processing?: boolean;
|
23 |
connected?: boolean;
|
24 |
message?: string;
|
25 |
token?: string;
|
26 |
+
toolName?: string;
|
27 |
+
toolArgs?: Record<string, unknown>;
|
28 |
+
toolResult?: string;
|
29 |
};
|
30 |
timestamp: number;
|
31 |
}
|
32 |
|
33 |
class WebSocketManager {
|
34 |
+
private connections: Map<
|
35 |
+
WebSocket,
|
36 |
+
{ token?: string; agent?: LangGraphAgent; messages?: BaseMessage[] }
|
37 |
+
> = new Map();
|
38 |
|
39 |
handleConnection(ws: WebSocket, _request: IncomingMessage) {
|
40 |
this.connections.set(ws, {});
|
|
|
74 |
connectionData.token = message.payload.token;
|
75 |
|
76 |
try {
|
77 |
+
connectionData.agent = new LangGraphAgent();
|
78 |
+
await connectionData.agent.initialize(message.payload.token, ws);
|
79 |
+
connectionData.messages = [];
|
|
|
80 |
|
81 |
this.sendMessage(ws, {
|
82 |
type: "status",
|
|
|
95 |
}
|
96 |
break;
|
97 |
|
98 |
+
case "editor_sync":
|
99 |
+
if (message.payload.content) {
|
100 |
+
updateEditorContent(message.payload.content);
|
101 |
+
}
|
102 |
+
break;
|
103 |
+
|
104 |
case "chat":
|
105 |
try {
|
106 |
if (!connectionData?.agent) {
|
|
|
120 |
timestamp: Date.now(),
|
121 |
});
|
122 |
|
123 |
+
if (!connectionData.messages) {
|
124 |
+
connectionData.messages = [];
|
125 |
+
}
|
126 |
+
connectionData.messages.push(new HumanMessage(userMessage));
|
127 |
+
|
128 |
const response = await connectionData.agent.processMessage(
|
129 |
userMessage,
|
130 |
+
connectionData.messages.slice(0, -1),
|
131 |
+
(chunk: string) => {
|
132 |
this.sendMessage(ws, {
|
133 |
type: "stream",
|
134 |
+
payload: { chunk },
|
135 |
timestamp: Date.now(),
|
136 |
});
|
137 |
},
|
138 |
);
|
139 |
|
140 |
+
connectionData.messages.push(new AIMessage(response));
|
141 |
+
|
142 |
this.sendMessage(ws, {
|
143 |
type: "chat",
|
144 |
payload: {
|
|
|
189 |
}
|
190 |
|
191 |
export const wsManager = new WebSocketManager();
|
|
|
|
|
|
|
|
|
|
src/lib/server/context.md
CHANGED
@@ -1,25 +1,29 @@
|
|
1 |
# Server Context
|
2 |
|
3 |
-
WebSocket server
|
4 |
|
5 |
-
## Components
|
6 |
|
7 |
-
-
|
8 |
-
-
|
9 |
-
-
|
10 |
-
-
|
11 |
-
-
|
12 |
-
- `reasoning-extractor.ts` - Filters thinking tags from model output
|
13 |
|
14 |
## Architecture
|
15 |
|
16 |
-
|
17 |
-
|
18 |
-
-
|
19 |
-
-
|
20 |
-
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
|
|
|
|
|
|
|
|
|
|
|
1 |
# Server Context
|
2 |
|
3 |
+
WebSocket server with React Agent pattern for AI-assisted game development.
|
4 |
|
5 |
+
## Key Components
|
6 |
|
7 |
+
- **api.ts** - WebSocket routing with editor sync and tool feedback
|
8 |
+
- **langgraph-agent.ts** - React Agent with conditional loops for tool execution
|
9 |
+
- **tools.ts** - Editor read/write with WebSocket-based state sync
|
10 |
+
- **documentation.ts** - VibeGame documentation loader
|
11 |
+
- **prompts.ts** - Documentation formatting utilities
|
|
|
12 |
|
13 |
## Architecture
|
14 |
|
15 |
+
React Agent pattern with conditional execution flow:
|
16 |
+
- Agent node processes messages and executes tools
|
17 |
+
- Conditional edges loop back after tool execution
|
18 |
+
- Tool results feed back into agent for final response
|
19 |
+
- Real-time tool execution feedback via WebSocket
|
20 |
+
|
21 |
+
## Message Types
|
22 |
+
|
23 |
+
- `auth` - HF token authentication
|
24 |
+
- `chat` - User messages
|
25 |
+
- `editor_sync` - Sync editor content with server
|
26 |
+
- `editor_update` - Server updates client editor
|
27 |
+
- `tool_execution` - Tool execution notifications
|
28 |
+
- `stream` - Response streaming
|
29 |
+
- `status` - Connection and processing status
|
src/lib/server/hint.test.ts
DELETED
@@ -1,31 +0,0 @@
|
|
1 |
-
import { describe, expect, test } from "bun:test";
|
2 |
-
|
3 |
-
describe("Message Enhancement for Tool Usage", () => {
|
4 |
-
test("should detect requests to read game code", () => {
|
5 |
-
const testCases = [
|
6 |
-
{ input: "read the game code", shouldTrigger: true },
|
7 |
-
{ input: "show me the game", shouldTrigger: true },
|
8 |
-
{ input: "what's in the game?", shouldTrigger: true },
|
9 |
-
{ input: "check the code", shouldTrigger: true },
|
10 |
-
{ input: "view the editor", shouldTrigger: true },
|
11 |
-
{ input: "see the scene", shouldTrigger: true },
|
12 |
-
{ input: "display the game code", shouldTrigger: true },
|
13 |
-
{ input: "look at the code", shouldTrigger: true },
|
14 |
-
{ input: "get the game", shouldTrigger: true },
|
15 |
-
{ input: "game code please", shouldTrigger: true },
|
16 |
-
{ input: "hello there", shouldTrigger: false },
|
17 |
-
{ input: "add a box", shouldTrigger: false },
|
18 |
-
{ input: "change the color", shouldTrigger: false },
|
19 |
-
];
|
20 |
-
|
21 |
-
const pattern =
|
22 |
-
/\b(read|show|view|check|see|what's in|what is in|get|display|look at)\b.*\b(game|code|editor|scene)\b/i;
|
23 |
-
const reversePattern =
|
24 |
-
/\b(game|code|editor|scene)\b.*\b(read|show|view|check|see|please)\b/i;
|
25 |
-
|
26 |
-
for (const { input, shouldTrigger } of testCases) {
|
27 |
-
const matches = pattern.test(input) || reversePattern.test(input);
|
28 |
-
expect(matches).toBe(shouldTrigger);
|
29 |
-
}
|
30 |
-
});
|
31 |
-
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/lib/server/langgraph-agent.ts
ADDED
@@ -0,0 +1,333 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { InferenceClient } from "@huggingface/inference";
|
2 |
+
import { StateGraph, Annotation, START, END } from "@langchain/langgraph";
|
3 |
+
import {
|
4 |
+
HumanMessage,
|
5 |
+
AIMessage,
|
6 |
+
BaseMessage,
|
7 |
+
ToolMessage,
|
8 |
+
} from "@langchain/core/messages";
|
9 |
+
import {
|
10 |
+
readEditorTool,
|
11 |
+
writeEditorTool,
|
12 |
+
setWebSocketConnection,
|
13 |
+
} from "./tools";
|
14 |
+
import { documentationService } from "./documentation";
|
15 |
+
import type { WebSocket } from "ws";
|
16 |
+
|
17 |
+
const AgentState = Annotation.Root({
|
18 |
+
messages: Annotation<BaseMessage[]>({
|
19 |
+
reducer: (x, y) => x.concat(y),
|
20 |
+
}),
|
21 |
+
hasToolCalls: Annotation<boolean>({
|
22 |
+
reducer: (_, y) => y,
|
23 |
+
default: () => false,
|
24 |
+
}),
|
25 |
+
});
|
26 |
+
|
27 |
+
export class LangGraphAgent {
|
28 |
+
private client: InferenceClient | null = null;
|
29 |
+
private graph!: ReturnType<typeof StateGraph.prototype.compile>;
|
30 |
+
private model: string = "Qwen/Qwen2.5-Coder-32B-Instruct";
|
31 |
+
private documentation: string = "";
|
32 |
+
private ws: WebSocket | null = null;
|
33 |
+
|
34 |
+
constructor() {
|
35 |
+
this.setupGraph();
|
36 |
+
}
|
37 |
+
|
38 |
+
async initialize(hfToken: string, ws?: WebSocket) {
|
39 |
+
if (!hfToken) {
|
40 |
+
throw new Error("Hugging Face authentication required");
|
41 |
+
}
|
42 |
+
this.client = new InferenceClient(hfToken);
|
43 |
+
|
44 |
+
if (ws) {
|
45 |
+
this.ws = ws;
|
46 |
+
setWebSocketConnection({
|
47 |
+
send: (message: { type: string; payload: Record<string, unknown> }) => {
|
48 |
+
if (this.ws && this.ws.readyState === this.ws.OPEN) {
|
49 |
+
this.ws.send(JSON.stringify(message));
|
50 |
+
}
|
51 |
+
},
|
52 |
+
});
|
53 |
+
}
|
54 |
+
|
55 |
+
const docs = await documentationService.load();
|
56 |
+
this.documentation = docs || "";
|
57 |
+
}
|
58 |
+
|
59 |
+
private setupGraph() {
|
60 |
+
const graph = new StateGraph(AgentState);
|
61 |
+
|
62 |
+
graph.addNode("agent", async (state) => {
|
63 |
+
const systemPrompt = this.buildSystemPrompt();
|
64 |
+
const messages = this.formatMessages(state.messages, systemPrompt);
|
65 |
+
|
66 |
+
const response = await this.callModel(messages);
|
67 |
+
const toolCalls = this.parseToolCalls(response);
|
68 |
+
|
69 |
+
if (toolCalls.length > 0) {
|
70 |
+
const toolResults = await this.executeTools(toolCalls);
|
71 |
+
const toolMessage = new AIMessage({
|
72 |
+
content: response,
|
73 |
+
additional_kwargs: { has_tool_calls: true },
|
74 |
+
});
|
75 |
+
|
76 |
+
return {
|
77 |
+
messages: [toolMessage, ...toolResults],
|
78 |
+
hasToolCalls: true,
|
79 |
+
};
|
80 |
+
}
|
81 |
+
|
82 |
+
return {
|
83 |
+
messages: [new AIMessage(response)],
|
84 |
+
hasToolCalls: false,
|
85 |
+
};
|
86 |
+
});
|
87 |
+
|
88 |
+
// @ts-expect-error - LangGraph type mismatch with START constant
|
89 |
+
graph.addEdge(START, "agent");
|
90 |
+
|
91 |
+
// @ts-expect-error - LangGraph type mismatch with conditional edges
|
92 |
+
graph.addConditionalEdges("agent", (state) => this.shouldContinue(state), {
|
93 |
+
continue: "agent",
|
94 |
+
end: END,
|
95 |
+
});
|
96 |
+
|
97 |
+
this.graph = graph.compile();
|
98 |
+
}
|
99 |
+
|
100 |
+
private shouldContinue(state: typeof AgentState.State): string {
|
101 |
+
const lastMessage = state.messages[state.messages.length - 1];
|
102 |
+
|
103 |
+
if (lastMessage instanceof ToolMessage) {
|
104 |
+
return "continue";
|
105 |
+
}
|
106 |
+
|
107 |
+
if (
|
108 |
+
lastMessage instanceof AIMessage &&
|
109 |
+
lastMessage.additional_kwargs?.has_tool_calls
|
110 |
+
) {
|
111 |
+
return "continue";
|
112 |
+
}
|
113 |
+
|
114 |
+
return "end";
|
115 |
+
}
|
116 |
+
|
117 |
+
private buildSystemPrompt(): string {
|
118 |
+
return `You are an expert VibeGame developer assistant. VibeGame is a declarative 3D game engine using XML-like syntax.
|
119 |
+
|
120 |
+
VIBEGAME DOCUMENTATION:
|
121 |
+
${this.documentation}
|
122 |
+
|
123 |
+
AVAILABLE TOOLS:
|
124 |
+
- read_editor: Read the current code in the editor
|
125 |
+
- write_editor: Write new code to the editor
|
126 |
+
|
127 |
+
TOOL USAGE FORMAT:
|
128 |
+
To use a tool, format your response with the tool call in this exact format:
|
129 |
+
[TOOL: tool_name]
|
130 |
+
or with parameters:
|
131 |
+
[TOOL: tool_name {"param": "value"}]
|
132 |
+
|
133 |
+
Example:
|
134 |
+
To read the editor: [TOOL: read_editor]
|
135 |
+
To write code: [TOOL: write_editor {"content": "<world>...</world>"}]
|
136 |
+
|
137 |
+
IMPORTANT WORKFLOW:
|
138 |
+
1. When asked to modify code, FIRST use read_editor to see the current code
|
139 |
+
2. After receiving tool results, CONTINUE with your analysis and next steps
|
140 |
+
3. Use write_editor to make the requested changes
|
141 |
+
4. After writing, provide a clear explanation of what was changed
|
142 |
+
|
143 |
+
When you receive tool results, use them to inform your next action. Do not stop after using a tool - continue until the task is complete.
|
144 |
+
|
145 |
+
Be concise, accurate, and focus on practical solutions.`;
|
146 |
+
}
|
147 |
+
|
148 |
+
private formatMessages(
|
149 |
+
messages: BaseMessage[],
|
150 |
+
systemPrompt: string,
|
151 |
+
): Array<{ role: string; content: string }> {
|
152 |
+
const formatted = [
|
153 |
+
{ role: "system", content: systemPrompt },
|
154 |
+
...messages.map((msg) => {
|
155 |
+
let role = "assistant";
|
156 |
+
if (msg instanceof HumanMessage) {
|
157 |
+
role = "user";
|
158 |
+
} else if (msg instanceof ToolMessage) {
|
159 |
+
const content = `Tool result for ${msg.name}: ${msg.content}`;
|
160 |
+
return { role: "assistant", content };
|
161 |
+
}
|
162 |
+
|
163 |
+
const content =
|
164 |
+
typeof msg.content === "string"
|
165 |
+
? msg.content
|
166 |
+
: JSON.stringify(msg.content);
|
167 |
+
return { role, content };
|
168 |
+
}),
|
169 |
+
];
|
170 |
+
return formatted;
|
171 |
+
}
|
172 |
+
|
173 |
+
private async callModel(
|
174 |
+
messages: Array<{ role: string; content: string }>,
|
175 |
+
): Promise<string> {
|
176 |
+
if (!this.client) {
|
177 |
+
throw new Error("Agent not initialized");
|
178 |
+
}
|
179 |
+
|
180 |
+
const response = await this.client.chatCompletion({
|
181 |
+
model: this.model,
|
182 |
+
messages,
|
183 |
+
temperature: 0.3,
|
184 |
+
max_tokens: 2048,
|
185 |
+
});
|
186 |
+
|
187 |
+
return response.choices[0].message.content || "";
|
188 |
+
}
|
189 |
+
|
190 |
+
private parseToolCalls(
|
191 |
+
response: string,
|
192 |
+
): Array<{ name: string; args: Record<string, unknown> }> {
|
193 |
+
const toolCalls = [];
|
194 |
+
const toolRegex = /\[TOOL:\s*(\w+)(?:\s+({[^}]+}))?\]/g;
|
195 |
+
let match;
|
196 |
+
|
197 |
+
while ((match = toolRegex.exec(response)) !== null) {
|
198 |
+
const toolName = match[1];
|
199 |
+
const params = match[2] ? JSON.parse(match[2]) : {};
|
200 |
+
|
201 |
+
toolCalls.push({
|
202 |
+
name: toolName,
|
203 |
+
args: params,
|
204 |
+
});
|
205 |
+
}
|
206 |
+
|
207 |
+
return toolCalls;
|
208 |
+
}
|
209 |
+
|
210 |
+
private async executeTools(
|
211 |
+
toolCalls: Array<{ name: string; args: Record<string, unknown> }>,
|
212 |
+
): Promise<BaseMessage[]> {
|
213 |
+
const results = [];
|
214 |
+
|
215 |
+
for (const call of toolCalls) {
|
216 |
+
try {
|
217 |
+
if (this.ws && this.ws.readyState === this.ws.OPEN) {
|
218 |
+
this.ws.send(
|
219 |
+
JSON.stringify({
|
220 |
+
type: "tool_execution",
|
221 |
+
payload: {
|
222 |
+
toolName: call.name,
|
223 |
+
toolArgs: call.args,
|
224 |
+
message: `Executing ${call.name}...`,
|
225 |
+
},
|
226 |
+
timestamp: Date.now(),
|
227 |
+
}),
|
228 |
+
);
|
229 |
+
}
|
230 |
+
|
231 |
+
let result;
|
232 |
+
if (call.name === "read_editor") {
|
233 |
+
result = await readEditorTool.func("");
|
234 |
+
} else if (call.name === "write_editor") {
|
235 |
+
result = await writeEditorTool.func(call.args as { content: string });
|
236 |
+
} else {
|
237 |
+
result = `Unknown tool: ${call.name}`;
|
238 |
+
}
|
239 |
+
|
240 |
+
if (this.ws && this.ws.readyState === this.ws.OPEN) {
|
241 |
+
this.ws.send(
|
242 |
+
JSON.stringify({
|
243 |
+
type: "tool_execution",
|
244 |
+
payload: {
|
245 |
+
toolName: call.name,
|
246 |
+
toolResult: result,
|
247 |
+
message: `${call.name} completed`,
|
248 |
+
},
|
249 |
+
timestamp: Date.now(),
|
250 |
+
}),
|
251 |
+
);
|
252 |
+
}
|
253 |
+
|
254 |
+
results.push(
|
255 |
+
new ToolMessage({
|
256 |
+
content: result,
|
257 |
+
tool_call_id: `${call.name}_${Date.now()}`,
|
258 |
+
name: call.name,
|
259 |
+
}),
|
260 |
+
);
|
261 |
+
} catch (error) {
|
262 |
+
results.push(
|
263 |
+
new ToolMessage({
|
264 |
+
content: `Error executing ${call.name}: ${error}`,
|
265 |
+
tool_call_id: `${call.name}_${Date.now()}`,
|
266 |
+
name: call.name,
|
267 |
+
}),
|
268 |
+
);
|
269 |
+
}
|
270 |
+
}
|
271 |
+
|
272 |
+
return results;
|
273 |
+
}
|
274 |
+
|
275 |
+
async processMessage(
|
276 |
+
message: string,
|
277 |
+
messageHistory: BaseMessage[] = [],
|
278 |
+
onStream?: (chunk: string) => void,
|
279 |
+
): Promise<string> {
|
280 |
+
if (!this.client) {
|
281 |
+
throw new Error("Agent not initialized");
|
282 |
+
}
|
283 |
+
|
284 |
+
const messages = [...messageHistory, new HumanMessage(message)];
|
285 |
+
|
286 |
+
const stream = await this.graph.stream({
|
287 |
+
messages,
|
288 |
+
hasToolCalls: false,
|
289 |
+
});
|
290 |
+
|
291 |
+
let fullResponse = "";
|
292 |
+
let isStreaming = false;
|
293 |
+
let lastAIResponse = "";
|
294 |
+
let hasExecutedTools = false;
|
295 |
+
|
296 |
+
for await (const chunk of stream) {
|
297 |
+
if (chunk.agent?.messages) {
|
298 |
+
for (const msg of chunk.agent.messages) {
|
299 |
+
if (msg instanceof AIMessage) {
|
300 |
+
const content = msg.content as string;
|
301 |
+
|
302 |
+
if (msg.additional_kwargs?.has_tool_calls) {
|
303 |
+
hasExecutedTools = true;
|
304 |
+
lastAIResponse = content;
|
305 |
+
} else {
|
306 |
+
if (!isStreaming) {
|
307 |
+
isStreaming = true;
|
308 |
+
for (const char of content) {
|
309 |
+
fullResponse += char;
|
310 |
+
onStream?.(char);
|
311 |
+
await new Promise((resolve) => setTimeout(resolve, 5));
|
312 |
+
}
|
313 |
+
} else {
|
314 |
+
fullResponse = content;
|
315 |
+
}
|
316 |
+
lastAIResponse = content;
|
317 |
+
}
|
318 |
+
} else if (msg instanceof ToolMessage) {
|
319 |
+
if (onStream && !hasExecutedTools) {
|
320 |
+
const toolNotification = `\n🔧 ${msg.name}\n`;
|
321 |
+
for (const char of toolNotification) {
|
322 |
+
onStream(char);
|
323 |
+
await new Promise((resolve) => setTimeout(resolve, 5));
|
324 |
+
}
|
325 |
+
}
|
326 |
+
}
|
327 |
+
}
|
328 |
+
}
|
329 |
+
}
|
330 |
+
|
331 |
+
return fullResponse || lastAIResponse;
|
332 |
+
}
|
333 |
+
}
|
src/lib/server/prompts.ts
CHANGED
@@ -1,50 +1,4 @@
|
|
1 |
-
export const SYSTEM_PROMPTS = {
|
2 |
-
main: (
|
3 |
-
tools: string,
|
4 |
-
documentation: string,
|
5 |
-
) => `You are an expert VibeGame developer assistant. VibeGame is a declarative 3D game engine using XML-like syntax, similar to Roblox but with "vibe coding" - focusing on quick, creative game development.
|
6 |
-
|
7 |
-
ENGINE CONTEXT:
|
8 |
-
VibeGame is a custom engine not in your training data. It uses declarative XML syntax for scene definition with Entity-Component-System (ECS) architecture.
|
9 |
-
|
10 |
-
${documentation}
|
11 |
-
|
12 |
-
AVAILABLE TOOLS:
|
13 |
-
${tools}
|
14 |
-
|
15 |
-
TOOL USAGE:
|
16 |
-
- Execute tools with: [TOOL: tool_name] or [TOOL: tool_name {"param": "value"}]
|
17 |
-
- read_game_code: Get current editor content
|
18 |
-
- write_game_code: Update the editor with new code
|
19 |
-
- read_console_output: Check console for errors, logs, and game output
|
20 |
-
|
21 |
-
WORKFLOW:
|
22 |
-
1. Read the current code to understand the game
|
23 |
-
2. Make changes using write_game_code
|
24 |
-
3. Check console output for errors or success
|
25 |
-
4. Iterate based on feedback
|
26 |
-
|
27 |
-
Be helpful, accurate, and adapt to what the user needs. Focus on working code and practical solutions.`,
|
28 |
-
|
29 |
-
toolFollowUp: (
|
30 |
-
documentation: string,
|
31 |
-
) => `Based on the tool execution result, provide relevant analysis of what you found.
|
32 |
-
|
33 |
-
${documentation}`,
|
34 |
-
};
|
35 |
-
|
36 |
export const formatDocumentation = (fullDocs: string): string => {
|
37 |
return `VIBEGAME REFERENCE:
|
38 |
${fullDocs}`;
|
39 |
};
|
40 |
-
|
41 |
-
export const buildSystemPrompt = (
|
42 |
-
tools: string,
|
43 |
-
documentation: string,
|
44 |
-
): string => {
|
45 |
-
return SYSTEM_PROMPTS.main(tools, documentation);
|
46 |
-
};
|
47 |
-
|
48 |
-
export const buildToolFollowUpPrompt = (documentation: string): string => {
|
49 |
-
return SYSTEM_PROMPTS.toolFollowUp(documentation);
|
50 |
-
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
export const formatDocumentation = (fullDocs: string): string => {
|
2 |
return `VIBEGAME REFERENCE:
|
3 |
${fullDocs}`;
|
4 |
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/lib/server/reasoning-extractor.test.ts
DELETED
@@ -1,114 +0,0 @@
|
|
1 |
-
import { describe, test, expect } from "bun:test";
|
2 |
-
import { UniversalReasoningExtractor } from "./reasoning-extractor";
|
3 |
-
|
4 |
-
describe("UniversalReasoningExtractor", () => {
|
5 |
-
test("extracts Qwen thinking tags", () => {
|
6 |
-
const extractor = new UniversalReasoningExtractor();
|
7 |
-
const input = `<thinking>
|
8 |
-
Let me analyze this step by step:
|
9 |
-
1. First, I need to understand the problem
|
10 |
-
2. Then I'll formulate a solution
|
11 |
-
3. Finally, I'll provide the answer
|
12 |
-
</thinking>
|
13 |
-
|
14 |
-
The answer to your question is 42.`;
|
15 |
-
|
16 |
-
const result = extractor.extract(input);
|
17 |
-
expect(result.content).toBe("The answer to your question is 42.");
|
18 |
-
expect(result.reasoning).toContain("Let me analyze this step by step");
|
19 |
-
});
|
20 |
-
|
21 |
-
test("extracts DeepSeek R1 think tags", () => {
|
22 |
-
const extractor = new UniversalReasoningExtractor();
|
23 |
-
const input = `<think>
|
24 |
-
Hmm, this is an interesting problem.
|
25 |
-
I should consider multiple approaches.
|
26 |
-
</think>
|
27 |
-
|
28 |
-
Here's the solution to your problem.`;
|
29 |
-
|
30 |
-
const result = extractor.extract(input);
|
31 |
-
expect(result.content).toBe("Here's the solution to your problem.");
|
32 |
-
expect(result.reasoning).toContain("interesting problem");
|
33 |
-
});
|
34 |
-
|
35 |
-
test("handles interleaved reasoning tags", () => {
|
36 |
-
const extractor = new UniversalReasoningExtractor();
|
37 |
-
const input = `<thinking>
|
38 |
-
First reasoning block
|
39 |
-
</thinking>
|
40 |
-
Some content here.
|
41 |
-
<reasoning>
|
42 |
-
Second reasoning block
|
43 |
-
</reasoning>
|
44 |
-
Final answer.`;
|
45 |
-
|
46 |
-
const result = extractor.extract(input);
|
47 |
-
expect(result.content).toBe("Some content here.\n\nFinal answer.");
|
48 |
-
expect(result.reasoning).toContain("First reasoning block");
|
49 |
-
expect(result.reasoning).toContain("Second reasoning block");
|
50 |
-
});
|
51 |
-
|
52 |
-
test("streams content correctly", () => {
|
53 |
-
const extractor = new UniversalReasoningExtractor();
|
54 |
-
|
55 |
-
const chunks = [
|
56 |
-
"Hello, ",
|
57 |
-
"<think",
|
58 |
-
"ing>",
|
59 |
-
"This is my ",
|
60 |
-
"reasoning process",
|
61 |
-
"</thinking>",
|
62 |
-
" Here is the answer.",
|
63 |
-
];
|
64 |
-
|
65 |
-
let finalContent = "";
|
66 |
-
let finalReasoning = "";
|
67 |
-
|
68 |
-
for (const chunk of chunks) {
|
69 |
-
const result = extractor.stream(chunk);
|
70 |
-
if (result.partial) {
|
71 |
-
finalContent += result.partial;
|
72 |
-
}
|
73 |
-
if (result.reasoning) {
|
74 |
-
finalReasoning = result.reasoning;
|
75 |
-
}
|
76 |
-
}
|
77 |
-
|
78 |
-
expect(finalContent).toBe("Hello, Here is the answer.");
|
79 |
-
expect(finalReasoning).toBe("This is my reasoning process");
|
80 |
-
});
|
81 |
-
|
82 |
-
test("handles multiple reasoning blocks", () => {
|
83 |
-
const extractor = new UniversalReasoningExtractor();
|
84 |
-
const input = `<thinking>First thought</thinking>
|
85 |
-
Some content here.
|
86 |
-
<reasoning>Second thought</reasoning>
|
87 |
-
More content.`;
|
88 |
-
|
89 |
-
const result = extractor.extract(input);
|
90 |
-
expect(result.content).toBe("Some content here.\n\nMore content.");
|
91 |
-
expect(result.reasoning).toContain("First thought");
|
92 |
-
expect(result.reasoning).toContain("Second thought");
|
93 |
-
});
|
94 |
-
|
95 |
-
test("returns original text when no reasoning tags", () => {
|
96 |
-
const extractor = new UniversalReasoningExtractor();
|
97 |
-
const input = "This is just regular text without any reasoning tags.";
|
98 |
-
|
99 |
-
const result = extractor.extract(input);
|
100 |
-
expect(result.content).toBe(input);
|
101 |
-
expect(result.reasoning).toBe("");
|
102 |
-
});
|
103 |
-
|
104 |
-
test("reset clears internal state", () => {
|
105 |
-
const extractor = new UniversalReasoningExtractor();
|
106 |
-
|
107 |
-
extractor.stream("<thinking>Test");
|
108 |
-
extractor.reset();
|
109 |
-
|
110 |
-
const result = extractor.stream("Normal text");
|
111 |
-
expect(result.partial).toBe("Normal text");
|
112 |
-
expect(result.reasoning).toBeUndefined();
|
113 |
-
});
|
114 |
-
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/lib/server/reasoning-extractor.ts
DELETED
@@ -1,131 +0,0 @@
|
|
1 |
-
export interface ExtractedContent {
|
2 |
-
reasoning: string;
|
3 |
-
content: string;
|
4 |
-
}
|
5 |
-
|
6 |
-
export interface StreamResult {
|
7 |
-
partial: string;
|
8 |
-
reasoning?: string;
|
9 |
-
complete: boolean;
|
10 |
-
}
|
11 |
-
|
12 |
-
export interface ReasoningExtractor {
|
13 |
-
patterns: RegExp[];
|
14 |
-
extract(text: string): ExtractedContent;
|
15 |
-
stream(chunk: string): StreamResult;
|
16 |
-
reset(): void;
|
17 |
-
}
|
18 |
-
|
19 |
-
export class UniversalReasoningExtractor implements ReasoningExtractor {
|
20 |
-
patterns = [
|
21 |
-
/<thinking>(.*?)<\/thinking>/s,
|
22 |
-
/<think>(.*?)<\/think>/s,
|
23 |
-
/<reasoning>(.*?)<\/reasoning>/s,
|
24 |
-
];
|
25 |
-
|
26 |
-
private buffer = "";
|
27 |
-
private inReasoning = false;
|
28 |
-
private reasoningBuffer = "";
|
29 |
-
private currentTag: string | null = null;
|
30 |
-
|
31 |
-
extract(text: string): ExtractedContent {
|
32 |
-
let reasoning = "";
|
33 |
-
let cleanContent = text;
|
34 |
-
|
35 |
-
for (const pattern of this.patterns) {
|
36 |
-
const matches = Array.from(
|
37 |
-
text.matchAll(new RegExp(pattern.source, "gs")),
|
38 |
-
);
|
39 |
-
|
40 |
-
for (const match of matches) {
|
41 |
-
if (match[1]) {
|
42 |
-
reasoning += match[1].trim() + "\n";
|
43 |
-
cleanContent = cleanContent.replace(match[0], "");
|
44 |
-
}
|
45 |
-
}
|
46 |
-
}
|
47 |
-
|
48 |
-
return {
|
49 |
-
reasoning: reasoning.trim(),
|
50 |
-
content: cleanContent.trim(),
|
51 |
-
};
|
52 |
-
}
|
53 |
-
|
54 |
-
stream(chunk: string): StreamResult {
|
55 |
-
this.buffer += chunk;
|
56 |
-
|
57 |
-
const result: StreamResult = {
|
58 |
-
partial: "",
|
59 |
-
complete: false,
|
60 |
-
};
|
61 |
-
|
62 |
-
// Check for opening tags
|
63 |
-
if (!this.inReasoning) {
|
64 |
-
const openingMatch = this.buffer.match(/<(thinking|think|reasoning)>/);
|
65 |
-
if (openingMatch) {
|
66 |
-
const beforeTag = this.buffer.substring(0, openingMatch.index);
|
67 |
-
result.partial = beforeTag;
|
68 |
-
this.buffer = this.buffer.substring(
|
69 |
-
openingMatch.index! + openingMatch[0].length,
|
70 |
-
);
|
71 |
-
this.inReasoning = true;
|
72 |
-
this.currentTag = openingMatch[1];
|
73 |
-
return result;
|
74 |
-
}
|
75 |
-
}
|
76 |
-
|
77 |
-
// Check for closing tags
|
78 |
-
if (this.inReasoning && this.currentTag) {
|
79 |
-
const closingTag = `</${this.currentTag}>`;
|
80 |
-
const closingIndex = this.buffer.indexOf(closingTag);
|
81 |
-
|
82 |
-
if (closingIndex !== -1) {
|
83 |
-
this.reasoningBuffer += this.buffer.substring(0, closingIndex);
|
84 |
-
result.reasoning = this.reasoningBuffer.trim();
|
85 |
-
this.reasoningBuffer = "";
|
86 |
-
this.buffer = this.buffer.substring(closingIndex + closingTag.length);
|
87 |
-
this.inReasoning = false;
|
88 |
-
this.currentTag = null;
|
89 |
-
|
90 |
-
// Process any remaining content
|
91 |
-
const nextResult = this.stream("");
|
92 |
-
result.partial = nextResult.partial;
|
93 |
-
if (nextResult.reasoning) {
|
94 |
-
result.reasoning =
|
95 |
-
(result.reasoning || "") + "\n" + nextResult.reasoning;
|
96 |
-
}
|
97 |
-
return result;
|
98 |
-
} else {
|
99 |
-
// Still in reasoning, buffer it
|
100 |
-
this.reasoningBuffer += this.buffer;
|
101 |
-
this.buffer = "";
|
102 |
-
return result;
|
103 |
-
}
|
104 |
-
}
|
105 |
-
|
106 |
-
// No reasoning tags, output as content
|
107 |
-
if (!this.inReasoning && this.buffer.length > 0) {
|
108 |
-
// Check if we might be in the middle of a tag
|
109 |
-
const lastAngle = this.buffer.lastIndexOf("<");
|
110 |
-
if (lastAngle !== -1 && !this.buffer.substring(lastAngle).includes(">")) {
|
111 |
-
// Might be incomplete tag, output everything before it
|
112 |
-
result.partial = this.buffer.substring(0, lastAngle);
|
113 |
-
this.buffer = this.buffer.substring(lastAngle);
|
114 |
-
} else {
|
115 |
-
// Safe to output everything
|
116 |
-
result.partial = this.buffer;
|
117 |
-
this.buffer = "";
|
118 |
-
}
|
119 |
-
}
|
120 |
-
|
121 |
-
result.complete = !this.inReasoning && this.buffer.length === 0;
|
122 |
-
return result;
|
123 |
-
}
|
124 |
-
|
125 |
-
reset(): void {
|
126 |
-
this.buffer = "";
|
127 |
-
this.inReasoning = false;
|
128 |
-
this.reasoningBuffer = "";
|
129 |
-
this.currentTag = null;
|
130 |
-
}
|
131 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/lib/server/tools.ts
ADDED
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { DynamicStructuredTool, DynamicTool } from "@langchain/core/tools";
|
2 |
+
import { z } from "zod";
|
3 |
+
|
4 |
+
let currentEditorContent = `<canvas id="game-canvas"></canvas>
|
5 |
+
|
6 |
+
<world canvas="#game-canvas" sky="#87ceeb">
|
7 |
+
<!-- Ground -->
|
8 |
+
<static-part pos="0 -0.5 0" shape="box" size="20 1 20" color="#90ee90"></static-part>
|
9 |
+
|
10 |
+
<!-- Ball -->
|
11 |
+
<dynamic-part pos="-2 4 -3" shape="sphere" size="1" color="#ff4500"></dynamic-part>
|
12 |
+
</world>
|
13 |
+
|
14 |
+
<script type="module">
|
15 |
+
import * as GAME from 'vibegame';
|
16 |
+
|
17 |
+
GAME.run();
|
18 |
+
</script>`;
|
19 |
+
|
20 |
+
interface WebSocketConnection {
|
21 |
+
send: (message: { type: string; payload: Record<string, unknown> }) => void;
|
22 |
+
}
|
23 |
+
|
24 |
+
let wsConnection: WebSocketConnection | null = null;
|
25 |
+
|
26 |
+
export function setWebSocketConnection(ws: WebSocketConnection) {
|
27 |
+
wsConnection = ws;
|
28 |
+
}
|
29 |
+
|
30 |
+
export function updateEditorContent(content: string) {
|
31 |
+
currentEditorContent = content;
|
32 |
+
}
|
33 |
+
|
34 |
+
export const readEditorTool = new DynamicTool({
|
35 |
+
name: "read_editor",
|
36 |
+
description: "Read the current code in the editor",
|
37 |
+
func: async () => {
|
38 |
+
return `Current editor content (html):\n${currentEditorContent}`;
|
39 |
+
},
|
40 |
+
});
|
41 |
+
|
42 |
+
export const writeEditorTool = new DynamicStructuredTool({
|
43 |
+
name: "write_editor",
|
44 |
+
description: "Write new code to the editor",
|
45 |
+
schema: z.object({
|
46 |
+
content: z.string().describe("The code content to write to the editor"),
|
47 |
+
}),
|
48 |
+
func: async (input: { content: string }) => {
|
49 |
+
currentEditorContent = input.content;
|
50 |
+
|
51 |
+
if (wsConnection) {
|
52 |
+
wsConnection.send({
|
53 |
+
type: "editor_update",
|
54 |
+
payload: { content: input.content },
|
55 |
+
});
|
56 |
+
}
|
57 |
+
|
58 |
+
return "Code updated successfully in the editor";
|
59 |
+
},
|
60 |
+
});
|
61 |
+
|
62 |
+
export const tools = [readEditorTool, writeEditorTool];
|
src/lib/stores/agent.ts
CHANGED
@@ -1,5 +1,6 @@
|
|
1 |
-
import { writable, derived } from "svelte/store";
|
2 |
import { authStore } from "../services/auth";
|
|
|
3 |
|
4 |
export interface ChatMessage {
|
5 |
id: string;
|
@@ -53,6 +54,21 @@ function createAgentStore() {
|
|
53 |
timestamp: Date.now(),
|
54 |
}),
|
55 |
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
56 |
} else {
|
57 |
update((state) => ({
|
58 |
...state,
|
@@ -101,6 +117,10 @@ function createAgentStore() {
|
|
101 |
role?: string;
|
102 |
content?: string;
|
103 |
error?: string;
|
|
|
|
|
|
|
|
|
104 |
};
|
105 |
}) {
|
106 |
switch (message.type) {
|
@@ -199,6 +219,32 @@ function createAgentStore() {
|
|
199 |
processing: false,
|
200 |
}));
|
201 |
break;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
202 |
}
|
203 |
}
|
204 |
|
|
|
1 |
+
import { writable, derived, get } from "svelte/store";
|
2 |
import { authStore } from "../services/auth";
|
3 |
+
import { editorStore } from "./editor";
|
4 |
|
5 |
export interface ChatMessage {
|
6 |
id: string;
|
|
|
54 |
timestamp: Date.now(),
|
55 |
}),
|
56 |
);
|
57 |
+
|
58 |
+
const editorState = get(editorStore);
|
59 |
+
if (editorState && editorState.content) {
|
60 |
+
setTimeout(() => {
|
61 |
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
62 |
+
ws.send(
|
63 |
+
JSON.stringify({
|
64 |
+
type: "editor_sync",
|
65 |
+
payload: { content: editorState.content },
|
66 |
+
timestamp: Date.now(),
|
67 |
+
}),
|
68 |
+
);
|
69 |
+
}
|
70 |
+
}, 500);
|
71 |
+
}
|
72 |
} else {
|
73 |
update((state) => ({
|
74 |
...state,
|
|
|
117 |
role?: string;
|
118 |
content?: string;
|
119 |
error?: string;
|
120 |
+
toolName?: string;
|
121 |
+
toolArgs?: Record<string, unknown>;
|
122 |
+
toolResult?: string;
|
123 |
+
message?: string;
|
124 |
};
|
125 |
}) {
|
126 |
switch (message.type) {
|
|
|
219 |
processing: false,
|
220 |
}));
|
221 |
break;
|
222 |
+
|
223 |
+
case "editor_update":
|
224 |
+
if (message.payload.content) {
|
225 |
+
editorStore.setContent(message.payload.content);
|
226 |
+
}
|
227 |
+
break;
|
228 |
+
|
229 |
+
case "tool_execution":
|
230 |
+
update((state) => {
|
231 |
+
const toolMessage: ChatMessage = {
|
232 |
+
id: `tool_${Date.now()}`,
|
233 |
+
role: "system",
|
234 |
+
content: `🔧 ${message.payload.toolName}: ${message.payload.message || "Executing..."}`,
|
235 |
+
timestamp: Date.now(),
|
236 |
+
};
|
237 |
+
|
238 |
+
if (message.payload.toolResult) {
|
239 |
+
toolMessage.content = `🔧 ${message.payload.toolName} completed`;
|
240 |
+
}
|
241 |
+
|
242 |
+
return {
|
243 |
+
...state,
|
244 |
+
messages: [...state.messages, toolMessage],
|
245 |
+
};
|
246 |
+
});
|
247 |
+
break;
|
248 |
}
|
249 |
}
|
250 |
|
src/lib/stores/loading.ts
CHANGED
@@ -24,7 +24,6 @@ function createLoadingStore() {
|
|
24 |
startLoading: () => {
|
25 |
set({ isLoading: true, progress: 0 });
|
26 |
|
27 |
-
// Simulate initial progress
|
28 |
let currentProgress = 0;
|
29 |
progressInterval = window.setInterval(() => {
|
30 |
currentProgress += Math.random() * 15;
|
@@ -44,10 +43,8 @@ function createLoadingStore() {
|
|
44 |
progressInterval = null;
|
45 |
}
|
46 |
|
47 |
-
// Complete the progress animation
|
48 |
update((state) => ({ ...state, progress: 100 }));
|
49 |
|
50 |
-
// Hide loading screen after progress completes
|
51 |
setTimeout(() => {
|
52 |
set({ isLoading: false, progress: 100 });
|
53 |
}, 300);
|
|
|
24 |
startLoading: () => {
|
25 |
set({ isLoading: true, progress: 0 });
|
26 |
|
|
|
27 |
let currentProgress = 0;
|
28 |
progressInterval = window.setInterval(() => {
|
29 |
currentProgress += Math.random() * 15;
|
|
|
43 |
progressInterval = null;
|
44 |
}
|
45 |
|
|
|
46 |
update((state) => ({ ...state, progress: 100 }));
|
47 |
|
|
|
48 |
setTimeout(() => {
|
49 |
set({ isLoading: false, progress: 100 });
|
50 |
}, 300);
|
src/lib/tools/context.md
DELETED
@@ -1,37 +0,0 @@
|
|
1 |
-
# Tools Context
|
2 |
-
|
3 |
-
AI agent tools for game manipulation and feedback.
|
4 |
-
|
5 |
-
## Structure
|
6 |
-
|
7 |
-
- `registry.ts` - Tool registration
|
8 |
-
- `parser.ts` - Tool call parsing
|
9 |
-
- `executor.ts` - Execution flow
|
10 |
-
- `read-game-code.ts` - Editor content reader
|
11 |
-
- `write-game-code.ts` - Editor content writer
|
12 |
-
- `read-console-output.ts` - Console message reader
|
13 |
-
- `index.ts` - Auto-registration
|
14 |
-
|
15 |
-
## Architecture
|
16 |
-
|
17 |
-
Clean separation:
|
18 |
-
|
19 |
-
- Registry handles registration only
|
20 |
-
- Parser extracts tool calls from responses
|
21 |
-
- Executor manages execution flow
|
22 |
-
- Individual tools implement business logic
|
23 |
-
|
24 |
-
## Pattern
|
25 |
-
|
26 |
-
Agent includes: `[TOOL: tool_name {"param": "value"}]`
|
27 |
-
Results formatted: `[TOOL_RESULT: name]` or `[TOOL_ERROR: name]`
|
28 |
-
|
29 |
-
## Available Tools
|
30 |
-
|
31 |
-
1. **read_game_code** - Returns editor content
|
32 |
-
2. **write_game_code** - Updates editor with new code
|
33 |
-
3. **read_console_output** - Reads console messages with filtering
|
34 |
-
|
35 |
-
## Workflow
|
36 |
-
|
37 |
-
Read code → Write changes → Check console → Iterate on feedback
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/lib/tools/executor.ts
DELETED
@@ -1,36 +0,0 @@
|
|
1 |
-
import { toolRegistry } from "./registry";
|
2 |
-
import { parseToolCalls, formatToolResult, type ToolCall } from "./parser";
|
3 |
-
|
4 |
-
export interface ToolExecutionResult {
|
5 |
-
toolName: string;
|
6 |
-
formatted: string;
|
7 |
-
raw: {
|
8 |
-
success: boolean;
|
9 |
-
data?: unknown;
|
10 |
-
error?: string;
|
11 |
-
};
|
12 |
-
}
|
13 |
-
|
14 |
-
export class ToolExecutor {
|
15 |
-
async executeFromResponse(response: string): Promise<ToolExecutionResult[]> {
|
16 |
-
const toolCalls = parseToolCalls(response);
|
17 |
-
|
18 |
-
if (toolCalls.length === 0) {
|
19 |
-
return [];
|
20 |
-
}
|
21 |
-
|
22 |
-
return Promise.all(toolCalls.map((call) => this.executeSingle(call)));
|
23 |
-
}
|
24 |
-
|
25 |
-
private async executeSingle(call: ToolCall): Promise<ToolExecutionResult> {
|
26 |
-
const result = await toolRegistry.execute(call.tool, call.parameters);
|
27 |
-
|
28 |
-
return {
|
29 |
-
toolName: call.tool,
|
30 |
-
formatted: formatToolResult(call.tool, result),
|
31 |
-
raw: result,
|
32 |
-
};
|
33 |
-
}
|
34 |
-
}
|
35 |
-
|
36 |
-
export const toolExecutor = new ToolExecutor();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/lib/tools/index.ts
DELETED
@@ -1,17 +0,0 @@
|
|
1 |
-
export { toolRegistry } from "./registry";
|
2 |
-
export type { Tool, ToolResult } from "./registry";
|
3 |
-
|
4 |
-
export { parseToolCalls, formatToolResult } from "./parser";
|
5 |
-
export type { ToolCall } from "./parser";
|
6 |
-
|
7 |
-
export { toolExecutor } from "./executor";
|
8 |
-
export type { ToolExecutionResult } from "./executor";
|
9 |
-
|
10 |
-
import { toolRegistry } from "./registry";
|
11 |
-
import { readGameCodeTool } from "./read-game-code";
|
12 |
-
import { writeGameCodeTool } from "./write-game-code";
|
13 |
-
import { readConsoleOutputTool } from "./read-console-output";
|
14 |
-
|
15 |
-
toolRegistry.register(readGameCodeTool);
|
16 |
-
toolRegistry.register(writeGameCodeTool);
|
17 |
-
toolRegistry.register(readConsoleOutputTool);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/lib/tools/parser.ts
DELETED
@@ -1,41 +0,0 @@
|
|
1 |
-
export interface ToolCall {
|
2 |
-
tool: string;
|
3 |
-
parameters?: unknown;
|
4 |
-
}
|
5 |
-
|
6 |
-
const TOOL_PATTERN = /\[TOOL:\s*(\w+)(?:\s+({[^}]*}))?\]/g;
|
7 |
-
|
8 |
-
export const parseToolCalls = (response: string): ToolCall[] => {
|
9 |
-
const toolCalls: ToolCall[] = [];
|
10 |
-
let match;
|
11 |
-
|
12 |
-
while ((match = TOOL_PATTERN.exec(response)) !== null) {
|
13 |
-
const toolName = match[1];
|
14 |
-
const paramsStr = match[2];
|
15 |
-
|
16 |
-
toolCalls.push({
|
17 |
-
tool: toolName,
|
18 |
-
parameters: paramsStr ? tryParseJson(paramsStr) : undefined,
|
19 |
-
});
|
20 |
-
}
|
21 |
-
|
22 |
-
return toolCalls;
|
23 |
-
};
|
24 |
-
|
25 |
-
const tryParseJson = (str: string): unknown => {
|
26 |
-
try {
|
27 |
-
return JSON.parse(str);
|
28 |
-
} catch {
|
29 |
-
return undefined;
|
30 |
-
}
|
31 |
-
};
|
32 |
-
|
33 |
-
export const formatToolResult = (
|
34 |
-
toolName: string,
|
35 |
-
result: { success: boolean; data?: unknown; error?: string },
|
36 |
-
): string => {
|
37 |
-
if (result.success) {
|
38 |
-
return `[TOOL_RESULT: ${toolName}]\n${JSON.stringify(result.data, null, 2)}\n[/TOOL_RESULT]`;
|
39 |
-
}
|
40 |
-
return `[TOOL_ERROR: ${toolName}]\n${result.error}\n[/TOOL_ERROR]`;
|
41 |
-
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/lib/tools/read-console-output.ts
DELETED
@@ -1,47 +0,0 @@
|
|
1 |
-
import { get } from "svelte/store";
|
2 |
-
import { consoleStore } from "../stores/console";
|
3 |
-
import type { Tool } from "./registry";
|
4 |
-
|
5 |
-
interface ReadConsoleParams {
|
6 |
-
limit?: number;
|
7 |
-
type?: "log" | "warn" | "error" | "info";
|
8 |
-
}
|
9 |
-
|
10 |
-
export const readConsoleOutputTool: Tool = {
|
11 |
-
name: "read_console_output",
|
12 |
-
description: "Read messages from the game console",
|
13 |
-
execute: async (params: unknown) => {
|
14 |
-
const { limit = 10, type } = (params || {}) as ReadConsoleParams;
|
15 |
-
|
16 |
-
try {
|
17 |
-
const state = get(consoleStore);
|
18 |
-
let messages = state.messages;
|
19 |
-
|
20 |
-
if (type) {
|
21 |
-
messages = messages.filter((msg) => msg.type === type);
|
22 |
-
}
|
23 |
-
|
24 |
-
if (limit > 0) {
|
25 |
-
messages = messages.slice(-limit);
|
26 |
-
}
|
27 |
-
|
28 |
-
return {
|
29 |
-
success: true,
|
30 |
-
data: {
|
31 |
-
messages: messages.map((msg) => ({
|
32 |
-
type: msg.type,
|
33 |
-
message: msg.message,
|
34 |
-
timestamp: msg.timestamp,
|
35 |
-
})),
|
36 |
-
total: messages.length,
|
37 |
-
},
|
38 |
-
};
|
39 |
-
} catch (error) {
|
40 |
-
return {
|
41 |
-
success: false,
|
42 |
-
error:
|
43 |
-
error instanceof Error ? error.message : "Failed to read console",
|
44 |
-
};
|
45 |
-
}
|
46 |
-
},
|
47 |
-
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/lib/tools/read-game-code.ts
DELETED
@@ -1,18 +0,0 @@
|
|
1 |
-
import { get } from "svelte/store";
|
2 |
-
import { editorStore } from "../stores/editor";
|
3 |
-
import type { Tool } from "./registry";
|
4 |
-
|
5 |
-
export const readGameCodeTool: Tool = {
|
6 |
-
name: "read_game_code",
|
7 |
-
description: "Get the current game code from the editor",
|
8 |
-
execute: async () => {
|
9 |
-
const state = get(editorStore);
|
10 |
-
return {
|
11 |
-
success: true,
|
12 |
-
data: {
|
13 |
-
content: state.content,
|
14 |
-
language: state.language,
|
15 |
-
},
|
16 |
-
};
|
17 |
-
},
|
18 |
-
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/lib/tools/registry.ts
DELETED
@@ -1,51 +0,0 @@
|
|
1 |
-
export interface Tool {
|
2 |
-
name: string;
|
3 |
-
description: string;
|
4 |
-
execute: (params: unknown) => Promise<ToolResult>;
|
5 |
-
}
|
6 |
-
|
7 |
-
export interface ToolResult {
|
8 |
-
success: boolean;
|
9 |
-
data?: unknown;
|
10 |
-
error?: string;
|
11 |
-
}
|
12 |
-
|
13 |
-
class ToolRegistry {
|
14 |
-
private tools: Map<string, Tool> = new Map();
|
15 |
-
|
16 |
-
register(tool: Tool): void {
|
17 |
-
this.tools.set(tool.name, tool);
|
18 |
-
}
|
19 |
-
|
20 |
-
async execute(toolName: string, params?: unknown): Promise<ToolResult> {
|
21 |
-
const tool = this.tools.get(toolName);
|
22 |
-
|
23 |
-
if (!tool) {
|
24 |
-
return {
|
25 |
-
success: false,
|
26 |
-
error: `Tool '${toolName}' not found. Available: ${this.getToolNames().join(", ")}`,
|
27 |
-
};
|
28 |
-
}
|
29 |
-
|
30 |
-
try {
|
31 |
-
return await tool.execute(params);
|
32 |
-
} catch (error) {
|
33 |
-
return {
|
34 |
-
success: false,
|
35 |
-
error: error instanceof Error ? error.message : "Unknown error",
|
36 |
-
};
|
37 |
-
}
|
38 |
-
}
|
39 |
-
|
40 |
-
getToolNames(): string[] {
|
41 |
-
return Array.from(this.tools.keys());
|
42 |
-
}
|
43 |
-
|
44 |
-
getToolDescriptions(): string {
|
45 |
-
return Array.from(this.tools.values())
|
46 |
-
.map((tool) => `- ${tool.name}: ${tool.description}`)
|
47 |
-
.join("\n");
|
48 |
-
}
|
49 |
-
}
|
50 |
-
|
51 |
-
export const toolRegistry = new ToolRegistry();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/lib/tools/tools-integration.test.ts
DELETED
@@ -1,110 +0,0 @@
|
|
1 |
-
import { describe, it, expect, beforeEach } from "bun:test";
|
2 |
-
import { get } from "svelte/store";
|
3 |
-
import { toolRegistry } from "./registry";
|
4 |
-
import { editorStore } from "../stores/editor";
|
5 |
-
import { consoleStore } from "../stores/console";
|
6 |
-
import "./index"; // Import to register tools
|
7 |
-
|
8 |
-
describe("Tool Integration", () => {
|
9 |
-
beforeEach(() => {
|
10 |
-
// Reset stores
|
11 |
-
editorStore.setContent("");
|
12 |
-
consoleStore.reset();
|
13 |
-
});
|
14 |
-
|
15 |
-
describe("write_game_code", () => {
|
16 |
-
it("should update editor content", async () => {
|
17 |
-
const testCode = `<world canvas="#game-canvas">
|
18 |
-
<static-part pos="0 0 0" shape="box" size="10 1 10" color="#90ee90"></static-part>
|
19 |
-
</world>`;
|
20 |
-
|
21 |
-
const result = await toolRegistry.execute("write_game_code", {
|
22 |
-
content: testCode,
|
23 |
-
});
|
24 |
-
|
25 |
-
expect(result.success).toBe(true);
|
26 |
-
expect(get(editorStore).content).toBe(testCode);
|
27 |
-
});
|
28 |
-
|
29 |
-
it("should handle invalid input", async () => {
|
30 |
-
const result = await toolRegistry.execute("write_game_code", {
|
31 |
-
content: 123, // Invalid type
|
32 |
-
});
|
33 |
-
|
34 |
-
expect(result.success).toBe(false);
|
35 |
-
expect(result.error).toContain("Content must be a string");
|
36 |
-
});
|
37 |
-
});
|
38 |
-
|
39 |
-
describe("read_game_code", () => {
|
40 |
-
it("should read current editor content", async () => {
|
41 |
-
const testCode = "<world>Test</world>";
|
42 |
-
editorStore.setContent(testCode);
|
43 |
-
|
44 |
-
const result = await toolRegistry.execute("read_game_code");
|
45 |
-
|
46 |
-
expect(result.success).toBe(true);
|
47 |
-
expect(result.data).toEqual({
|
48 |
-
content: testCode,
|
49 |
-
language: "html",
|
50 |
-
});
|
51 |
-
});
|
52 |
-
});
|
53 |
-
|
54 |
-
describe("read_console_output", () => {
|
55 |
-
it("should read console messages", async () => {
|
56 |
-
consoleStore.addMessage("log", "Game started");
|
57 |
-
consoleStore.addMessage("error", "Entity not found");
|
58 |
-
consoleStore.addMessage("warn", "Low performance");
|
59 |
-
|
60 |
-
const result = await toolRegistry.execute("read_console_output", {
|
61 |
-
limit: 2,
|
62 |
-
});
|
63 |
-
|
64 |
-
expect(result.success).toBe(true);
|
65 |
-
const data = result.data as { messages: Array<{ message: string }> };
|
66 |
-
expect(data.messages).toHaveLength(2);
|
67 |
-
expect(data.messages[1].message).toBe("Low performance");
|
68 |
-
});
|
69 |
-
|
70 |
-
it("should filter by type", async () => {
|
71 |
-
consoleStore.addMessage("log", "Info 1");
|
72 |
-
consoleStore.addMessage("error", "Error 1");
|
73 |
-
consoleStore.addMessage("log", "Info 2");
|
74 |
-
|
75 |
-
const result = await toolRegistry.execute("read_console_output", {
|
76 |
-
type: "error",
|
77 |
-
});
|
78 |
-
|
79 |
-
expect(result.success).toBe(true);
|
80 |
-
const data = result.data as { messages: Array<{ message: string }> };
|
81 |
-
expect(data.messages).toHaveLength(1);
|
82 |
-
expect(data.messages[0].message).toBe("Error 1");
|
83 |
-
});
|
84 |
-
});
|
85 |
-
|
86 |
-
describe("Tool workflow", () => {
|
87 |
-
it("should support a complete edit cycle", async () => {
|
88 |
-
// 1. Write some code
|
89 |
-
const code = `<world canvas="#game-canvas">
|
90 |
-
<dynamic-part pos="0 5 0" shape="sphere" size="1" color="#ff0000"></dynamic-part>
|
91 |
-
</world>`;
|
92 |
-
|
93 |
-
await toolRegistry.execute("write_game_code", { content: code });
|
94 |
-
|
95 |
-
// 2. Read it back
|
96 |
-
const readResult = await toolRegistry.execute("read_game_code");
|
97 |
-
expect((readResult.data as { content: string }).content).toBe(code);
|
98 |
-
|
99 |
-
// 3. Simulate console output
|
100 |
-
consoleStore.addMessage("log", "Entity created: dynamic-part");
|
101 |
-
consoleStore.addMessage("info", "Physics initialized");
|
102 |
-
|
103 |
-
// 4. Read console
|
104 |
-
const consoleResult = await toolRegistry.execute("read_console_output");
|
105 |
-
expect(consoleResult.success).toBe(true);
|
106 |
-
const messages = (consoleResult.data as { messages: unknown[] }).messages;
|
107 |
-
expect(messages.length).toBeGreaterThan(0);
|
108 |
-
});
|
109 |
-
});
|
110 |
-
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/lib/tools/write-game-code.ts
DELETED
@@ -1,38 +0,0 @@
|
|
1 |
-
import { editorStore } from "../stores/editor";
|
2 |
-
import type { Tool } from "./registry";
|
3 |
-
|
4 |
-
interface WriteGameCodeParams {
|
5 |
-
content: string;
|
6 |
-
}
|
7 |
-
|
8 |
-
export const writeGameCodeTool: Tool = {
|
9 |
-
name: "write_game_code",
|
10 |
-
description: "Update the game code in the editor",
|
11 |
-
execute: async (params: unknown) => {
|
12 |
-
const { content } = params as WriteGameCodeParams;
|
13 |
-
|
14 |
-
if (typeof content !== "string") {
|
15 |
-
return {
|
16 |
-
success: false,
|
17 |
-
error: "Content must be a string",
|
18 |
-
};
|
19 |
-
}
|
20 |
-
|
21 |
-
try {
|
22 |
-
editorStore.setContent(content);
|
23 |
-
|
24 |
-
return {
|
25 |
-
success: true,
|
26 |
-
data: {
|
27 |
-
message: "Code updated successfully",
|
28 |
-
length: content.length,
|
29 |
-
},
|
30 |
-
};
|
31 |
-
} catch (error) {
|
32 |
-
return {
|
33 |
-
success: false,
|
34 |
-
error: error instanceof Error ? error.message : "Failed to update code",
|
35 |
-
};
|
36 |
-
}
|
37 |
-
},
|
38 |
-
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
vite.config.ts
CHANGED
@@ -2,7 +2,7 @@ import { defineConfig } from "vite";
|
|
2 |
import { svelte } from "@sveltejs/vite-plugin-svelte";
|
3 |
import { vibegame, consoleForwarding } from "vibegame/vite";
|
4 |
import { WebSocketServer } from "ws";
|
5 |
-
import { wsManager
|
6 |
|
7 |
export default defineConfig({
|
8 |
plugins: [
|
@@ -14,8 +14,6 @@ export default defineConfig({
|
|
14 |
configureServer(server) {
|
15 |
const wss = new WebSocketServer({ noServer: true });
|
16 |
|
17 |
-
initializeAgent().catch(console.error);
|
18 |
-
|
19 |
server.httpServer?.on("upgrade", (request, socket, head) => {
|
20 |
if (request.url === "/ws") {
|
21 |
wss.handleUpgrade(request, socket, head, (ws) => {
|
@@ -24,7 +22,6 @@ export default defineConfig({
|
|
24 |
}
|
25 |
});
|
26 |
|
27 |
-
// Serve OAuth callback page
|
28 |
server.middlewares.use((req, res, next) => {
|
29 |
if (req.url === "/auth/callback") {
|
30 |
res.statusCode = 200;
|
|
|
2 |
import { svelte } from "@sveltejs/vite-plugin-svelte";
|
3 |
import { vibegame, consoleForwarding } from "vibegame/vite";
|
4 |
import { WebSocketServer } from "ws";
|
5 |
+
import { wsManager } from "./src/lib/server/api";
|
6 |
|
7 |
export default defineConfig({
|
8 |
plugins: [
|
|
|
14 |
configureServer(server) {
|
15 |
const wss = new WebSocketServer({ noServer: true });
|
16 |
|
|
|
|
|
17 |
server.httpServer?.on("upgrade", (request, socket, head) => {
|
18 |
if (request.url === "/ws") {
|
19 |
wss.handleUpgrade(request, socket, head, (ws) => {
|
|
|
22 |
}
|
23 |
});
|
24 |
|
|
|
25 |
server.middlewares.use((req, res, next) => {
|
26 |
if (req.url === "/auth/callback") {
|
27 |
res.statusCode = 200;
|