mirror of
https://github.com/trustgraph-ai/trustgraph.git
synced 2026-07-01 17:39:39 +02:00
refactor(ts): complete legacy host removal — drop fastify/commander/zod, delete MCP SDK server, remove ManagedRuntime facades
Finishes the remaining EFFECT_NATIVE_REWRITE_PLAN stages in one verified slice: - fastify, @fastify/websocket, commander, zod removed from all package manifests - legacy @modelcontextprotocol/sdk stdio server deleted; effect/unstable/ai McpServer is canonical - no ManagedRuntime or Effect.runPromise program facades remain in production source - gateway server/rpc-contract and client rpc/socket moved onto Effect v4 native http/rpc/socket layers Gates (force-run, no cache): check:tsgo, build, test (96 tests / 11 tasks) all green. Native-class inventory: zero blocking production classes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
a26463afc1
commit
cf12defcd8
30 changed files with 1506 additions and 456 deletions
66
ts/bun.lock
66
ts/bun.lock
|
|
@ -27,14 +27,6 @@
|
||||||
"name": "@trustgraph/base",
|
"name": "@trustgraph/base",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@effect/ai-anthropic": "4.0.0-beta.78",
|
|
||||||
"@effect/ai-openai": "4.0.0-beta.78",
|
|
||||||
"@effect/ai-openrouter": "4.0.0-beta.78",
|
|
||||||
"@effect/atom-react": "4.0.0-beta.78",
|
|
||||||
"@effect/openapi-generator": "4.0.0-beta.78",
|
|
||||||
"@effect/opentelemetry": "4.0.0-beta.78",
|
|
||||||
"@effect/platform-browser": "4.0.0-beta.78",
|
|
||||||
"@effect/platform-bun": "4.0.0-beta.78",
|
|
||||||
"effect": "4.0.0-beta.78",
|
"effect": "4.0.0-beta.78",
|
||||||
"nats": "^2.29.0",
|
"nats": "^2.29.0",
|
||||||
},
|
},
|
||||||
|
|
@ -52,16 +44,10 @@
|
||||||
"tg": "dist/index.js",
|
"tg": "dist/index.js",
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@effect/ai-anthropic": "4.0.0-beta.78",
|
|
||||||
"@effect/ai-openai": "4.0.0-beta.78",
|
|
||||||
"@effect/ai-openrouter": "4.0.0-beta.78",
|
|
||||||
"@effect/atom-react": "4.0.0-beta.78",
|
|
||||||
"@effect/openapi-generator": "4.0.0-beta.78",
|
|
||||||
"@effect/opentelemetry": "4.0.0-beta.78",
|
|
||||||
"@effect/platform-browser": "4.0.0-beta.78",
|
|
||||||
"@effect/platform-bun": "4.0.0-beta.78",
|
"@effect/platform-bun": "4.0.0-beta.78",
|
||||||
"@trustgraph/base": "workspace:*",
|
"@trustgraph/base": "workspace:*",
|
||||||
"@trustgraph/client": "workspace:*",
|
"@trustgraph/client": "workspace:*",
|
||||||
|
"effect": "4.0.0-beta.78",
|
||||||
"ws": "^8.18.0",
|
"ws": "^8.18.0",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
@ -97,21 +83,13 @@
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@effect/ai-anthropic": "4.0.0-beta.78",
|
"@effect/ai-anthropic": "4.0.0-beta.78",
|
||||||
"@effect/ai-openai": "4.0.0-beta.78",
|
|
||||||
"@effect/ai-openrouter": "4.0.0-beta.78",
|
|
||||||
"@effect/atom-react": "4.0.0-beta.78",
|
|
||||||
"@effect/openapi-generator": "4.0.0-beta.78",
|
|
||||||
"@effect/opentelemetry": "4.0.0-beta.78",
|
|
||||||
"@effect/platform-browser": "4.0.0-beta.78",
|
|
||||||
"@effect/platform-bun": "4.0.0-beta.78",
|
"@effect/platform-bun": "4.0.0-beta.78",
|
||||||
"@effect/platform-node": "4.0.0-beta.78",
|
"@effect/platform-node": "4.0.0-beta.78",
|
||||||
"@effect/platform-node-shared": "4.0.0-beta.78",
|
|
||||||
"@effect/tsgo": "0.14.0",
|
|
||||||
"@effect/vitest": "4.0.0-beta.78",
|
|
||||||
"@mistralai/mistralai": "^1.0.0",
|
"@mistralai/mistralai": "^1.0.0",
|
||||||
"@modelcontextprotocol/sdk": "^1.12.0",
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
||||||
"@qdrant/js-client-rest": "^1.13.0",
|
"@qdrant/js-client-rest": "^1.13.0",
|
||||||
"@trustgraph/base": "workspace:*",
|
"@trustgraph/base": "workspace:*",
|
||||||
|
"@trustgraph/client": "workspace:*",
|
||||||
"effect": "4.0.0-beta.78",
|
"effect": "4.0.0-beta.78",
|
||||||
"falkordb": "^5.0.0",
|
"falkordb": "^5.0.0",
|
||||||
"ollama": "^0.6.3",
|
"ollama": "^0.6.3",
|
||||||
|
|
@ -129,20 +107,11 @@
|
||||||
"name": "@trustgraph/mcp",
|
"name": "@trustgraph/mcp",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@effect/ai-anthropic": "4.0.0-beta.78",
|
|
||||||
"@effect/ai-openai": "4.0.0-beta.78",
|
|
||||||
"@effect/ai-openrouter": "4.0.0-beta.78",
|
|
||||||
"@effect/atom-react": "4.0.0-beta.78",
|
|
||||||
"@effect/openapi-generator": "4.0.0-beta.78",
|
|
||||||
"@effect/opentelemetry": "4.0.0-beta.78",
|
|
||||||
"@effect/platform-browser": "4.0.0-beta.78",
|
|
||||||
"@effect/platform-bun": "4.0.0-beta.78",
|
"@effect/platform-bun": "4.0.0-beta.78",
|
||||||
"@effect/platform-node": "4.0.0-beta.78",
|
"@effect/platform-node": "4.0.0-beta.78",
|
||||||
"@effect/platform-node-shared": "4.0.0-beta.78",
|
|
||||||
"@effect/tsgo": "0.14.0",
|
|
||||||
"@effect/vitest": "4.0.0-beta.78",
|
|
||||||
"@trustgraph/base": "workspace:*",
|
"@trustgraph/base": "workspace:*",
|
||||||
"@trustgraph/client": "workspace:*",
|
"@trustgraph/client": "workspace:*",
|
||||||
|
"effect": "4.0.0-beta.78",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@effect/vitest": "4.0.0-beta.78",
|
"@effect/vitest": "4.0.0-beta.78",
|
||||||
|
|
@ -155,21 +124,11 @@
|
||||||
"name": "@trustgraph/workbench",
|
"name": "@trustgraph/workbench",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@effect/ai-anthropic": "4.0.0-beta.78",
|
|
||||||
"@effect/ai-openai": "4.0.0-beta.78",
|
|
||||||
"@effect/ai-openrouter": "4.0.0-beta.78",
|
|
||||||
"@effect/atom-react": "4.0.0-beta.78",
|
"@effect/atom-react": "4.0.0-beta.78",
|
||||||
"@effect/openapi-generator": "4.0.0-beta.78",
|
|
||||||
"@effect/opentelemetry": "4.0.0-beta.78",
|
|
||||||
"@effect/platform-browser": "4.0.0-beta.78",
|
"@effect/platform-browser": "4.0.0-beta.78",
|
||||||
"@effect/platform-bun": "4.0.0-beta.78",
|
|
||||||
"@effect/platform-node": "4.0.0-beta.78",
|
|
||||||
"@effect/platform-node-shared": "4.0.0-beta.78",
|
|
||||||
"@effect/tsgo": "0.14.0",
|
|
||||||
"@effect/vitest": "4.0.0-beta.78",
|
|
||||||
"@tanstack/react-query": "^5.75.0",
|
|
||||||
"@trustgraph/client": "workspace:*",
|
"@trustgraph/client": "workspace:*",
|
||||||
"clsx": "^2.1.0",
|
"clsx": "^2.1.0",
|
||||||
|
"effect": "4.0.0-beta.78",
|
||||||
"lucide-react": "^0.513.0",
|
"lucide-react": "^0.513.0",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
|
|
@ -178,7 +137,6 @@
|
||||||
"react-markdown": "^10.1.0",
|
"react-markdown": "^10.1.0",
|
||||||
"react-router": "^7.6.0",
|
"react-router": "^7.6.0",
|
||||||
"tailwind-merge": "^3.3.0",
|
"tailwind-merge": "^3.3.0",
|
||||||
"zustand": "^5.0.0",
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@effect/vitest": "4.0.0-beta.78",
|
"@effect/vitest": "4.0.0-beta.78",
|
||||||
|
|
@ -234,16 +192,8 @@
|
||||||
|
|
||||||
"@effect/ai-anthropic": ["@effect/ai-anthropic@4.0.0-beta.78", "", { "peerDependencies": { "effect": "^4.0.0-beta.78" } }, "sha512-FZzwvKx2k+UIDJyv+FWMtC2CKRxJMWTII+z8EmMcT6tw1uzkkn+ouIyJ32AU+lfveKScNV1SXL4ml3VdcYGy/g=="],
|
"@effect/ai-anthropic": ["@effect/ai-anthropic@4.0.0-beta.78", "", { "peerDependencies": { "effect": "^4.0.0-beta.78" } }, "sha512-FZzwvKx2k+UIDJyv+FWMtC2CKRxJMWTII+z8EmMcT6tw1uzkkn+ouIyJ32AU+lfveKScNV1SXL4ml3VdcYGy/g=="],
|
||||||
|
|
||||||
"@effect/ai-openai": ["@effect/ai-openai@4.0.0-beta.78", "", { "peerDependencies": { "effect": "^4.0.0-beta.78" } }, "sha512-tx953rRkLqW2BeEkWK12/nRBPO0b1eS6pI+2YyWI0nQvX2JTTijrGlBv/qDVa5kxDkLm63+tA04xnxgZMlA8NA=="],
|
|
||||||
|
|
||||||
"@effect/ai-openrouter": ["@effect/ai-openrouter@4.0.0-beta.78", "", { "peerDependencies": { "effect": "^4.0.0-beta.78" } }, "sha512-9GIRU9stAnDU5EJ5ZghUWrQXaE+rECCWI/eKVfYeC7UqjZmmmJmTcEbid3tvz2NMsnvIn0ymeKsJAohWCys39w=="],
|
|
||||||
|
|
||||||
"@effect/atom-react": ["@effect/atom-react@4.0.0-beta.78", "", { "peerDependencies": { "effect": "^4.0.0-beta.78", "react": "^19.2.4", "scheduler": "*" } }, "sha512-cgxDXJaD0wlbQXbp6tiEmmY+yajwurB0ynkFG20RVucvH4LsQMB3ogiHe0mt42wGggfbVYMEDxgBpQdqDRY8yA=="],
|
"@effect/atom-react": ["@effect/atom-react@4.0.0-beta.78", "", { "peerDependencies": { "effect": "^4.0.0-beta.78", "react": "^19.2.4", "scheduler": "*" } }, "sha512-cgxDXJaD0wlbQXbp6tiEmmY+yajwurB0ynkFG20RVucvH4LsQMB3ogiHe0mt42wGggfbVYMEDxgBpQdqDRY8yA=="],
|
||||||
|
|
||||||
"@effect/openapi-generator": ["@effect/openapi-generator@4.0.0-beta.78", "", { "peerDependencies": { "@effect/platform-node": "^4.0.0-beta.78", "effect": "^4.0.0-beta.78" }, "bin": { "openapigen": "dist/bin.js" } }, "sha512-qhKRcZCNQ5b0Klrct+AC/tPQgIDBxVsD0MkQLIzqvLU3qRHaNd5yHo7kxFf/DuhCyyL++xZfbHsPdq3VdLIByg=="],
|
|
||||||
|
|
||||||
"@effect/opentelemetry": ["@effect/opentelemetry@4.0.0-beta.78", "", { "peerDependencies": { "@opentelemetry/api": "^1.9", "@opentelemetry/api-logs": ">=0.203.0 <0.300.0", "@opentelemetry/resources": "^2.0.0", "@opentelemetry/sdk-logs": ">=0.203.0 <0.300.0", "@opentelemetry/sdk-metrics": "^2.0.0", "@opentelemetry/sdk-trace-base": "^2.0.0", "@opentelemetry/sdk-trace-node": "^2.0.0", "@opentelemetry/sdk-trace-web": "^2.0.0", "@opentelemetry/semantic-conventions": "^1.33.0", "effect": "^4.0.0-beta.78" }, "optionalPeers": ["@opentelemetry/api", "@opentelemetry/api-logs", "@opentelemetry/resources", "@opentelemetry/sdk-logs", "@opentelemetry/sdk-metrics", "@opentelemetry/sdk-trace-base", "@opentelemetry/sdk-trace-node", "@opentelemetry/sdk-trace-web"] }, "sha512-OJGnlNkxfhUmZ/8aLIfQly8ic2tntcnwidAP0BdrTUKa1/sbZjq5xTrhVUjvmehFra2Thsef0k4UPTgsOrBG1A=="],
|
|
||||||
|
|
||||||
"@effect/platform-browser": ["@effect/platform-browser@4.0.0-beta.78", "", { "dependencies": { "multipasta": "^0.2.7" }, "peerDependencies": { "effect": "^4.0.0-beta.78" } }, "sha512-8r9MVuZ8xJRyVyi+C8SKSYLbMsHr7qOiUgLV6lKMECuAWyMhlbK/7Ka9SQGr0ZPqOe5ShLEvV7DevnGkG+owAQ=="],
|
"@effect/platform-browser": ["@effect/platform-browser@4.0.0-beta.78", "", { "dependencies": { "multipasta": "^0.2.7" }, "peerDependencies": { "effect": "^4.0.0-beta.78" } }, "sha512-8r9MVuZ8xJRyVyi+C8SKSYLbMsHr7qOiUgLV6lKMECuAWyMhlbK/7Ka9SQGr0ZPqOe5ShLEvV7DevnGkG+owAQ=="],
|
||||||
|
|
||||||
"@effect/platform-bun": ["@effect/platform-bun@4.0.0-beta.78", "", { "dependencies": { "@effect/platform-node-shared": "^4.0.0-beta.78" }, "peerDependencies": { "effect": "^4.0.0-beta.78" } }, "sha512-lmPCL1G7SlkCWCguX3rDPS7kKuvJ/AN4pjS7IXb/5SoauHPd67iUdc1ZbB7o6lwTChJaIfWNNPkUWygiaUeJiA=="],
|
"@effect/platform-bun": ["@effect/platform-bun@4.0.0-beta.78", "", { "dependencies": { "@effect/platform-node-shared": "^4.0.0-beta.78" }, "peerDependencies": { "effect": "^4.0.0-beta.78" } }, "sha512-lmPCL1G7SlkCWCguX3rDPS7kKuvJ/AN4pjS7IXb/5SoauHPd67iUdc1ZbB7o6lwTChJaIfWNNPkUWygiaUeJiA=="],
|
||||||
|
|
@ -382,8 +332,6 @@
|
||||||
|
|
||||||
"@opentelemetry/api": ["@opentelemetry/api@1.9.1", "", {}, "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q=="],
|
"@opentelemetry/api": ["@opentelemetry/api@1.9.1", "", {}, "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q=="],
|
||||||
|
|
||||||
"@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.41.1", "", {}, "sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA=="],
|
|
||||||
|
|
||||||
"@pdf-lib/standard-fonts": ["@pdf-lib/standard-fonts@1.0.0", "", { "dependencies": { "pako": "^1.0.6" } }, "sha512-hU30BK9IUN/su0Mn9VdlVKsWBS6GyhVfqjwl1FjZN4TxP6cCw0jP2w7V3Hf5uX7M0AZJ16vey9yE0ny7Sa59ZA=="],
|
"@pdf-lib/standard-fonts": ["@pdf-lib/standard-fonts@1.0.0", "", { "dependencies": { "pako": "^1.0.6" } }, "sha512-hU30BK9IUN/su0Mn9VdlVKsWBS6GyhVfqjwl1FjZN4TxP6cCw0jP2w7V3Hf5uX7M0AZJ16vey9yE0ny7Sa59ZA=="],
|
||||||
|
|
||||||
"@pdf-lib/upng": ["@pdf-lib/upng@1.0.1", "", { "dependencies": { "pako": "^1.0.10" } }, "sha512-dQK2FUMQtowVP00mtIksrlZhdFXQZPC+taih1q4CvPZ5vqdxR/LKBaFg0oAfzd1GlHZXXSPdQfzQnt+ViGvEIQ=="],
|
"@pdf-lib/upng": ["@pdf-lib/upng@1.0.1", "", { "dependencies": { "pako": "^1.0.10" } }, "sha512-dQK2FUMQtowVP00mtIksrlZhdFXQZPC+taih1q4CvPZ5vqdxR/LKBaFg0oAfzd1GlHZXXSPdQfzQnt+ViGvEIQ=="],
|
||||||
|
|
@ -478,10 +426,6 @@
|
||||||
|
|
||||||
"@tailwindcss/vite": ["@tailwindcss/vite@4.2.2", "", { "dependencies": { "@tailwindcss/node": "4.2.2", "@tailwindcss/oxide": "4.2.2", "tailwindcss": "4.2.2" }, "peerDependencies": { "vite": "6.4.1" } }, "sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w=="],
|
"@tailwindcss/vite": ["@tailwindcss/vite@4.2.2", "", { "dependencies": { "@tailwindcss/node": "4.2.2", "@tailwindcss/oxide": "4.2.2", "tailwindcss": "4.2.2" }, "peerDependencies": { "vite": "6.4.1" } }, "sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w=="],
|
||||||
|
|
||||||
"@tanstack/query-core": ["@tanstack/query-core@5.96.2", "", {}, "sha512-hzI6cTVh4KNRk8UtoIBS7Lv9g6BnJPXvBKsvYH1aGWvv0347jT3BnSvztOE+kD76XGvZnRC/t6qdW1CaIfwCeA=="],
|
|
||||||
|
|
||||||
"@tanstack/react-query": ["@tanstack/react-query@5.96.2", "", { "dependencies": { "@tanstack/query-core": "5.96.2" }, "peerDependencies": { "react": "19.2.4" } }, "sha512-sYyzzJT4G0g02azzJ8o55VFFV31XvFpdUpG+unxS0vSaYsJnSPKGoI6WdPwUucJL1wpgGfwfmntNX/Ub1uOViA=="],
|
|
||||||
|
|
||||||
"@trustgraph/base": ["@trustgraph/base@workspace:packages/base"],
|
"@trustgraph/base": ["@trustgraph/base@workspace:packages/base"],
|
||||||
|
|
||||||
"@trustgraph/cli": ["@trustgraph/cli@workspace:packages/cli"],
|
"@trustgraph/cli": ["@trustgraph/cli@workspace:packages/cli"],
|
||||||
|
|
@ -1242,8 +1186,6 @@
|
||||||
|
|
||||||
"zod-to-json-schema": ["zod-to-json-schema@3.25.2", "", { "peerDependencies": { "zod": "3.25.76" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="],
|
"zod-to-json-schema": ["zod-to-json-schema@3.25.2", "", { "peerDependencies": { "zod": "3.25.76" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="],
|
||||||
|
|
||||||
"zustand": ["zustand@5.0.12", "", { "optionalDependencies": { "@types/react": "19.2.14", "react": "19.2.4" } }, "sha512-i77ae3aZq4dhMlRhJVCYgMLKuSiZAaUPAct2AksxQ+gOtimhGMdXljRT21P5BNpeT4kXlLIckvkPM029OljD7g=="],
|
|
||||||
|
|
||||||
"zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
|
"zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
|
||||||
|
|
||||||
"@qdrant/js-client-rest/undici": ["undici@6.24.1", "", {}, "sha512-sC+b0tB1whOCzbtlx20fx3WgCXwkW627p4EA9uM+/tNNPkSS+eSEld6pAs9nDv7WbY1UUljBMYPtu9BCOrCWKA=="],
|
"@qdrant/js-client-rest/undici": ["undici@6.24.1", "", {}, "sha512-sC+b0tB1whOCzbtlx20fx3WgCXwkW627p4EA9uM+/tNNPkSS+eSEld6pAs9nDv7WbY1UUljBMYPtu9BCOrCWKA=="],
|
||||||
|
|
|
||||||
|
|
@ -20,14 +20,6 @@
|
||||||
"test": "bunx --bun vitest run"
|
"test": "bunx --bun vitest run"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@effect/ai-anthropic": "4.0.0-beta.78",
|
|
||||||
"@effect/ai-openai": "4.0.0-beta.78",
|
|
||||||
"@effect/ai-openrouter": "4.0.0-beta.78",
|
|
||||||
"@effect/atom-react": "4.0.0-beta.78",
|
|
||||||
"@effect/openapi-generator": "4.0.0-beta.78",
|
|
||||||
"@effect/opentelemetry": "4.0.0-beta.78",
|
|
||||||
"@effect/platform-browser": "4.0.0-beta.78",
|
|
||||||
"@effect/platform-bun": "4.0.0-beta.78",
|
|
||||||
"effect": "4.0.0-beta.78",
|
"effect": "4.0.0-beta.78",
|
||||||
"nats": "^2.29.0"
|
"nats": "^2.29.0"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { PubSubBackend } from "../backend/types.js";
|
import type { PubSubBackend } from "../backend/types.js";
|
||||||
import { makeNatsBackend } from "../backend/nats.js";
|
import { makeNatsBackend, makeNatsBackendScoped } from "../backend/nats.js";
|
||||||
import { Cause, Config as EffectConfig, Context, Effect } from "effect";
|
import { Cause, Config as EffectConfig, Context, Effect } from "effect";
|
||||||
import { processorLifecycleError, type ProcessorLifecycleError } from "../errors.js";
|
import { processorLifecycleError, type ProcessorLifecycleError } from "../errors.js";
|
||||||
import { loadProcessorRuntimeConfig } from "../runtime/config.js";
|
import { loadProcessorRuntimeConfig } from "../runtime/config.js";
|
||||||
|
|
@ -198,6 +198,27 @@ export function makeAsyncProcessor<
|
||||||
return processor;
|
return processor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const makeAsyncProcessorScoped = Effect.fn("makeAsyncProcessorScoped")(function* <
|
||||||
|
RunError = ProcessorLifecycleError,
|
||||||
|
RunRequirements = never,
|
||||||
|
>(
|
||||||
|
config: ProcessorConfig,
|
||||||
|
options: AsyncProcessorRuntimeOptions<RunError, RunRequirements> = {},
|
||||||
|
) {
|
||||||
|
if (config.pubsub !== undefined) {
|
||||||
|
return makeAsyncProcessor(config, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pubsub = yield* makeNatsBackendScoped(config.pubsubUrl ?? "nats://localhost:4222");
|
||||||
|
return makeAsyncProcessor(
|
||||||
|
{
|
||||||
|
...config,
|
||||||
|
pubsub,
|
||||||
|
},
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
export type AsyncProcessor<
|
export type AsyncProcessor<
|
||||||
RunError = ProcessorLifecycleError,
|
RunError = ProcessorLifecycleError,
|
||||||
RunRequirements = never,
|
RunRequirements = never,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
export {
|
export {
|
||||||
AsyncProcessor,
|
AsyncProcessor,
|
||||||
makeAsyncProcessor,
|
makeAsyncProcessor,
|
||||||
|
makeAsyncProcessorScoped,
|
||||||
type ConfigHandler,
|
type ConfigHandler,
|
||||||
type EffectConfigHandler,
|
type EffectConfigHandler,
|
||||||
type AsyncProcessorRuntime,
|
type AsyncProcessorRuntime,
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import {
|
||||||
type ProcessorLifecycleError,
|
type ProcessorLifecycleError,
|
||||||
type PubSubError,
|
type PubSubError,
|
||||||
} from "../errors.js";
|
} from "../errors.js";
|
||||||
import { makeNatsBackend } from "../backend/nats.js";
|
import { makeNatsBackendScoped } from "../backend/nats.js";
|
||||||
import { makePubSubService, PubSub } from "../backend/pubsub.js";
|
import { makePubSubService, PubSub } from "../backend/pubsub.js";
|
||||||
import {
|
import {
|
||||||
ConsumerFactory,
|
ConsumerFactory,
|
||||||
|
|
@ -119,18 +119,9 @@ export function makeProcessorProgram<
|
||||||
manageProcessSignals: false,
|
manageProcessSignals: false,
|
||||||
} as Config;
|
} as Config;
|
||||||
|
|
||||||
const pubsub = makePubSubService(makeNatsBackend(runtimeConfig.pubsubUrl ?? "nats://localhost:4222"));
|
const backend = yield* makeNatsBackendScoped(runtimeConfig.pubsubUrl ?? "nats://localhost:4222");
|
||||||
|
const pubsub = makePubSubService(backend);
|
||||||
const messagingConfig = yield* loadMessagingRuntimeConfig();
|
const messagingConfig = yield* loadMessagingRuntimeConfig();
|
||||||
yield* Effect.addFinalizer(() =>
|
|
||||||
pubsub.close.pipe(
|
|
||||||
Effect.catch((error) =>
|
|
||||||
Effect.logError("[PubSub] Failed to close processor backend", {
|
|
||||||
error: error.message,
|
|
||||||
operation: error.operation,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
const processorEffect = runProcessorScoped<Config, RunError, RunRequirements>(
|
const processorEffect = runProcessorScoped<Config, RunError, RunRequirements>(
|
||||||
runtimeConfig,
|
runtimeConfig,
|
||||||
options.make,
|
options.make,
|
||||||
|
|
@ -202,18 +193,9 @@ export function makeFlowProcessorProgram<
|
||||||
manageProcessSignals: false,
|
manageProcessSignals: false,
|
||||||
} as Config;
|
} as Config;
|
||||||
|
|
||||||
const pubsub = makePubSubService(makeNatsBackend(runtimeConfig.pubsubUrl ?? "nats://localhost:4222"));
|
const backend = yield* makeNatsBackendScoped(runtimeConfig.pubsubUrl ?? "nats://localhost:4222");
|
||||||
|
const pubsub = makePubSubService(backend);
|
||||||
const messagingConfig = yield* loadMessagingRuntimeConfig();
|
const messagingConfig = yield* loadMessagingRuntimeConfig();
|
||||||
yield* Effect.addFinalizer(() =>
|
|
||||||
pubsub.close.pipe(
|
|
||||||
Effect.catch((error) =>
|
|
||||||
Effect.logError("[PubSub] Failed to close processor backend", {
|
|
||||||
error: error.message,
|
|
||||||
operation: error.operation,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
const configHandlers = options.configHandlers?.(runtimeConfig);
|
const configHandlers = options.configHandlers?.(runtimeConfig);
|
||||||
const processorOptions = {
|
const processorOptions = {
|
||||||
|
|
|
||||||
|
|
@ -12,16 +12,10 @@
|
||||||
"test": "bunx --bun vitest run --passWithNoTests --exclude=dist/**"
|
"test": "bunx --bun vitest run --passWithNoTests --exclude=dist/**"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@effect/ai-anthropic": "4.0.0-beta.78",
|
|
||||||
"@effect/ai-openai": "4.0.0-beta.78",
|
|
||||||
"@effect/ai-openrouter": "4.0.0-beta.78",
|
|
||||||
"@effect/atom-react": "4.0.0-beta.78",
|
|
||||||
"@effect/openapi-generator": "4.0.0-beta.78",
|
|
||||||
"@effect/opentelemetry": "4.0.0-beta.78",
|
|
||||||
"@effect/platform-browser": "4.0.0-beta.78",
|
|
||||||
"@effect/platform-bun": "4.0.0-beta.78",
|
"@effect/platform-bun": "4.0.0-beta.78",
|
||||||
"@trustgraph/base": "workspace:*",
|
"@trustgraph/base": "workspace:*",
|
||||||
"@trustgraph/client": "workspace:*",
|
"@trustgraph/client": "workspace:*",
|
||||||
|
"effect": "4.0.0-beta.78",
|
||||||
"ws": "^8.18.0"
|
"ws": "^8.18.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
||||||
|
|
@ -7,37 +7,77 @@
|
||||||
import { Effect } from "effect";
|
import { Effect } from "effect";
|
||||||
import * as Argument from "effect/unstable/cli/Argument";
|
import * as Argument from "effect/unstable/cli/Argument";
|
||||||
import * as Command from "effect/unstable/cli/Command";
|
import * as Command from "effect/unstable/cli/Command";
|
||||||
import { cliCommandError, withSocket } from "./util.js";
|
import { cliCommandError, withGatewayClient, type CliCommandError } from "./util.js";
|
||||||
|
|
||||||
|
function asRecord(value: unknown): Record<string, unknown> {
|
||||||
|
return typeof value === "object" && value !== null && !Array.isArray(value)
|
||||||
|
? value as Record<string, unknown>
|
||||||
|
: {};
|
||||||
|
}
|
||||||
|
|
||||||
|
function stringProperty(source: unknown, key: string): string | undefined {
|
||||||
|
const value = asRecord(source)[key];
|
||||||
|
return typeof value === "string" ? value : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function booleanProperty(source: unknown, key: string): boolean | undefined {
|
||||||
|
const value = asRecord(source)[key];
|
||||||
|
return typeof value === "boolean" ? value : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function responseErrorMessage(source: unknown): string | undefined {
|
||||||
|
const error = asRecord(source).error;
|
||||||
|
if (typeof error === "string") return error;
|
||||||
|
return stringProperty(error, "message");
|
||||||
|
}
|
||||||
|
|
||||||
export const agentCommand = Command.make("agent", {
|
export const agentCommand = Command.make("agent", {
|
||||||
question: Argument.string("question").pipe(Argument.withDescription("Question to ask")),
|
question: Argument.string("question").pipe(Argument.withDescription("Question to ask")),
|
||||||
}, ({ question }) =>
|
}, ({ question }) =>
|
||||||
withSocket((socket, opts) =>
|
withGatewayClient((client, opts) =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const flow = socket.flow(opts.flow);
|
let streamError: CliCommandError | undefined;
|
||||||
|
|
||||||
yield* Effect.callback<void, ReturnType<typeof cliCommandError>>((resume) => {
|
yield* client.runDispatchStream(
|
||||||
flow.agent(
|
{
|
||||||
|
scope: "flow",
|
||||||
|
flow: opts.flow,
|
||||||
|
service: "agent",
|
||||||
|
request: {
|
||||||
question,
|
question,
|
||||||
(chunk) => {
|
user: opts.user,
|
||||||
// think — show thought process
|
collection: "default",
|
||||||
if (chunk.length > 0) process.stderr.write(chunk);
|
streaming: true,
|
||||||
},
|
},
|
||||||
(chunk) => {
|
},
|
||||||
// observe — show observations
|
(chunk) => {
|
||||||
if (chunk.length > 0) process.stderr.write(chunk);
|
const resp = asRecord(chunk.response);
|
||||||
},
|
const chunkType = stringProperty(resp, "chunk_type");
|
||||||
(chunk, complete) => {
|
const error = chunkType === "error" ? responseErrorMessage(resp) ?? "Unknown agent error" : responseErrorMessage(resp);
|
||||||
// answer — print to stdout
|
if (error !== undefined) {
|
||||||
if (chunk.length > 0) process.stdout.write(chunk);
|
streamError = cliCommandError("agent", error);
|
||||||
if (complete) {
|
return true;
|
||||||
process.stdout.write("\n");
|
}
|
||||||
resume(Effect.void);
|
|
||||||
}
|
const content = stringProperty(resp, "content") ?? "";
|
||||||
},
|
const messageComplete = booleanProperty(resp, "end_of_message") === true;
|
||||||
(err) => resume(Effect.fail(cliCommandError("agent", err))),
|
const dialogComplete = chunk.complete === true || booleanProperty(resp, "end_of_dialog") === true;
|
||||||
);
|
|
||||||
});
|
if (chunkType === "thought" || chunkType === "observation") {
|
||||||
|
if (content.length > 0) process.stderr.write(content);
|
||||||
|
} else if (chunkType === "answer" || chunkType === "final-answer") {
|
||||||
|
if (content.length > 0) process.stdout.write(content);
|
||||||
|
if (messageComplete || dialogComplete) process.stdout.write("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return dialogComplete;
|
||||||
|
},
|
||||||
|
{ timeoutMs: 120_000, retries: 2 },
|
||||||
|
);
|
||||||
|
|
||||||
|
if (streamError !== undefined) {
|
||||||
|
return yield* streamError;
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
).pipe(Command.withDescription("Ask the TrustGraph agent a question"));
|
).pipe(Command.withDescription("Ask the TrustGraph agent a question"));
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,12 @@
|
||||||
* Shared CLI utilities.
|
* Shared CLI utilities.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createTrustGraphSocket, type BaseApi } from "@trustgraph/client";
|
import {
|
||||||
|
createTrustGraphSocket,
|
||||||
|
makeTrustGraphGatewayClientScoped,
|
||||||
|
type BaseApi,
|
||||||
|
type TrustGraphGatewayClient,
|
||||||
|
} from "@trustgraph/client";
|
||||||
import { Duration, Effect } from "effect";
|
import { Duration, Effect } from "effect";
|
||||||
import * as O from "effect/Option";
|
import * as O from "effect/Option";
|
||||||
import * as S from "effect/Schema";
|
import * as S from "effect/Schema";
|
||||||
|
|
@ -109,6 +114,23 @@ export function createSocketEffect(opts: CliOpts): Effect.Effect<BaseApi, CliCom
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function gatewayUrlWithToken(opts: CliOpts): string {
|
||||||
|
if (opts.token === undefined || opts.token.length === 0) return opts.gateway;
|
||||||
|
const separator = opts.gateway.includes("?") ? "&" : "?";
|
||||||
|
return `${opts.gateway}${separator}token=${encodeURIComponent(opts.token)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const withGatewayClient = Effect.fn("withGatewayClient")(function* <A, E, R>(
|
||||||
|
use: (client: TrustGraphGatewayClient, opts: CliOpts) => Effect.Effect<A, E, R>,
|
||||||
|
) {
|
||||||
|
const opts = yield* getOpts;
|
||||||
|
return yield* Effect.scoped(
|
||||||
|
makeTrustGraphGatewayClientScoped({ url: gatewayUrlWithToken(opts) }).pipe(
|
||||||
|
Effect.flatMap((client) => use(client, opts)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
export const withSocket = Effect.fn("withSocket")(function* <A, E, R>(
|
export const withSocket = Effect.fn("withSocket")(function* <A, E, R>(
|
||||||
use: (socket: BaseApi, opts: CliOpts) => Effect.Effect<A, E, R>,
|
use: (socket: BaseApi, opts: CliOpts) => Effect.Effect<A, E, R>,
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,16 @@
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"import": "./dist/index.js"
|
||||||
|
},
|
||||||
|
"./rpc/contract": {
|
||||||
|
"types": "./dist/rpc/contract.d.ts",
|
||||||
|
"import": "./dist/rpc/contract.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "bunx --bun tsc",
|
"build": "bunx --bun tsc",
|
||||||
"dev": "tsc --watch",
|
"dev": "tsc --watch",
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ describe("FlowsApi", () => {
|
||||||
makeRequest: vi.fn(),
|
makeRequest: vi.fn(),
|
||||||
};
|
};
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
flowsApi = new FlowsApi(mockApi as any);
|
flowsApi = FlowsApi(mockApi as any);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("startFlow", () => {
|
describe("startFlow", () => {
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ describe("workbench API contracts", () => {
|
||||||
values: [{ type: "prompt", key: "welcome", value: "hello" }],
|
values: [{ type: "prompt", key: "welcome", value: "hello" }],
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await new ConfigApi(base).getValues("prompt");
|
const result = await ConfigApi(base).getValues("prompt");
|
||||||
|
|
||||||
expect(makeRequest).toHaveBeenCalledWith(
|
expect(makeRequest).toHaveBeenCalledWith(
|
||||||
"config",
|
"config",
|
||||||
|
|
@ -45,7 +45,7 @@ describe("workbench API contracts", () => {
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await new ConfigApi(base).getTokenCosts();
|
const result = await ConfigApi(base).getTokenCosts();
|
||||||
|
|
||||||
expect(result).toEqual([
|
expect(result).toEqual([
|
||||||
{ model: "gpt-test", input_price: 0.1, output_price: 0.2 },
|
{ model: "gpt-test", input_price: 0.1, output_price: 0.2 },
|
||||||
|
|
@ -55,7 +55,7 @@ describe("workbench API contracts", () => {
|
||||||
it("writes and deletes config using Python-style key/value arrays", async () => {
|
it("writes and deletes config using Python-style key/value arrays", async () => {
|
||||||
const { base, makeRequest } = makeApi();
|
const { base, makeRequest } = makeApi();
|
||||||
makeRequest.mockResolvedValue({});
|
makeRequest.mockResolvedValue({});
|
||||||
const config = new ConfigApi(base);
|
const config = ConfigApi(base);
|
||||||
|
|
||||||
await config.putConfig([{ type: "tool", key: "search", value: "{}" }]);
|
await config.putConfig([{ type: "tool", key: "search", value: "{}" }]);
|
||||||
await config.deleteConfig({ type: "tool", key: "search" });
|
await config.deleteConfig({ type: "tool", key: "search" });
|
||||||
|
|
@ -86,7 +86,7 @@ describe("workbench API contracts", () => {
|
||||||
const { base, makeRequest } = makeApi();
|
const { base, makeRequest } = makeApi();
|
||||||
const document = { id: "doc-1", title: "Document" };
|
const document = { id: "doc-1", title: "Document" };
|
||||||
const processing = { id: "proc-1", "document-id": "doc-1" };
|
const processing = { id: "proc-1", "document-id": "doc-1" };
|
||||||
const librarian = new LibrarianApi(base);
|
const librarian = LibrarianApi(base);
|
||||||
|
|
||||||
makeRequest
|
makeRequest
|
||||||
.mockResolvedValueOnce({ "document-metadatas": [document] })
|
.mockResolvedValueOnce({ "document-metadatas": [document] })
|
||||||
|
|
@ -101,7 +101,7 @@ describe("workbench API contracts", () => {
|
||||||
const document = { id: "doc-1", title: "Document" };
|
const document = { id: "doc-1", title: "Document" };
|
||||||
makeRequest.mockResolvedValue({ "document-metadata": document });
|
makeRequest.mockResolvedValue({ "document-metadata": document });
|
||||||
|
|
||||||
const result = await new LibrarianApi(base).getDocumentMetadata("doc-1");
|
const result = await LibrarianApi(base).getDocumentMetadata("doc-1");
|
||||||
|
|
||||||
expect(makeRequest).toHaveBeenCalledWith(
|
expect(makeRequest).toHaveBeenCalledWith(
|
||||||
"librarian",
|
"librarian",
|
||||||
|
|
@ -120,7 +120,7 @@ describe("workbench API contracts", () => {
|
||||||
const { base, makeRequest } = makeApi();
|
const { base, makeRequest } = makeApi();
|
||||||
makeRequest.mockResolvedValue({});
|
makeRequest.mockResolvedValue({});
|
||||||
|
|
||||||
await new LibrarianApi(base).loadDocument(
|
await LibrarianApi(base).loadDocument(
|
||||||
"SGVsbG8=",
|
"SGVsbG8=",
|
||||||
"text/plain",
|
"text/plain",
|
||||||
"Hello",
|
"Hello",
|
||||||
|
|
@ -145,7 +145,7 @@ describe("workbench API contracts", () => {
|
||||||
describe("KnowledgeApi", () => {
|
describe("KnowledgeApi", () => {
|
||||||
it("lists and loads document embedding cores", async () => {
|
it("lists and loads document embedding cores", async () => {
|
||||||
const { base, makeRequest } = makeApi();
|
const { base, makeRequest } = makeApi();
|
||||||
const knowledge = new KnowledgeApi(base);
|
const knowledge = KnowledgeApi(base);
|
||||||
|
|
||||||
makeRequest
|
makeRequest
|
||||||
.mockResolvedValueOnce({ ids: ["de-core"] })
|
.mockResolvedValueOnce({ ids: ["de-core"] })
|
||||||
|
|
@ -178,7 +178,7 @@ describe("workbench API contracts", () => {
|
||||||
const { base, makeRequest } = makeApi();
|
const { base, makeRequest } = makeApi();
|
||||||
makeRequest.mockResolvedValue({});
|
makeRequest.mockResolvedValue({});
|
||||||
|
|
||||||
await new KnowledgeApi(base).unloadKgCore("kg-core", "default");
|
await KnowledgeApi(base).unloadKgCore("kg-core", "default");
|
||||||
|
|
||||||
expect(makeRequest).toHaveBeenCalledWith(
|
expect(makeRequest).toHaveBeenCalledWith(
|
||||||
"knowledge",
|
"knowledge",
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ export * from "./models/namespaces.js";
|
||||||
|
|
||||||
// Export socket client
|
// Export socket client
|
||||||
export * from "./socket/trustgraph-socket.js";
|
export * from "./socket/trustgraph-socket.js";
|
||||||
|
export * from "./socket/effect-rpc-client.js";
|
||||||
export * from "./rpc/contract.js";
|
export * from "./rpc/contract.js";
|
||||||
|
|
||||||
// Export WebSocket adapter (isomorphic helpers and types)
|
// Export WebSocket adapter (isomorphic helpers and types)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { Schema as S } from "effect";
|
import { Schema as S } from "effect";
|
||||||
|
import { HttpApi, HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi";
|
||||||
import * as Rpc from "effect/unstable/rpc/Rpc";
|
import * as Rpc from "effect/unstable/rpc/Rpc";
|
||||||
import * as RpcGroup from "effect/unstable/rpc/RpcGroup";
|
import * as RpcGroup from "effect/unstable/rpc/RpcGroup";
|
||||||
|
|
||||||
|
|
@ -32,3 +33,14 @@ export class DispatchStream extends Rpc.make("DispatchStream", {
|
||||||
}) {}
|
}) {}
|
||||||
|
|
||||||
export const TrustGraphRpcs = RpcGroup.make(Dispatch, DispatchStream);
|
export const TrustGraphRpcs = RpcGroup.make(Dispatch, DispatchStream);
|
||||||
|
|
||||||
|
export class GatewayWorkbenchHttpApi extends HttpApi.make("trustgraph-gateway-workbench")
|
||||||
|
.add(
|
||||||
|
HttpApiGroup.make("workbench", { topLevel: true }).add(
|
||||||
|
HttpApiEndpoint.post("dispatch", "/api/v1/workbench/dispatch", {
|
||||||
|
payload: DispatchPayload,
|
||||||
|
success: S.Unknown,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
{}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Cause, Context, Effect, Fiber, Layer, ManagedRuntime, Stream, SubscriptionRef } from "effect";
|
import { Cause, Context, Effect, Exit, Fiber, Layer, Ref, Scope, Stream, SubscriptionRef } from "effect";
|
||||||
import type * as RpcGroup from "effect/unstable/rpc/RpcGroup";
|
import type * as RpcGroup from "effect/unstable/rpc/RpcGroup";
|
||||||
import * as RpcClient from "effect/unstable/rpc/RpcClient";
|
import * as RpcClient from "effect/unstable/rpc/RpcClient";
|
||||||
import type { RpcClientError } from "effect/unstable/rpc/RpcClientError";
|
import type { RpcClientError } from "effect/unstable/rpc/RpcClientError";
|
||||||
|
|
@ -35,24 +35,44 @@ export interface DispatchOptions {
|
||||||
readonly retries?: number;
|
readonly retries?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TrustGraphGatewayClient {
|
||||||
|
readonly state: Effect.Effect<RpcConnectionState>;
|
||||||
|
readonly changes: Stream.Stream<RpcConnectionState>;
|
||||||
|
readonly subscribe: (
|
||||||
|
listener: (state: RpcConnectionState) => void,
|
||||||
|
) => Effect.Effect<Effect.Effect<void>>;
|
||||||
|
readonly dispatch: (
|
||||||
|
input: DispatchInput,
|
||||||
|
options?: DispatchOptions,
|
||||||
|
) => Effect.Effect<unknown, RpcClientError | DispatchError>;
|
||||||
|
readonly dispatchStream: (
|
||||||
|
input: DispatchInput,
|
||||||
|
options?: DispatchOptions,
|
||||||
|
) => Stream.Stream<DispatchStreamChunk, RpcClientError | DispatchError>;
|
||||||
|
readonly runDispatchStream: (
|
||||||
|
input: DispatchInput,
|
||||||
|
receiver: (chunk: DispatchStreamChunk) => boolean,
|
||||||
|
options?: DispatchOptions,
|
||||||
|
) => Effect.Effect<DispatchStreamChunk | undefined, RpcClientError | DispatchError>;
|
||||||
|
readonly close: Effect.Effect<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TrustGraphGatewayClientService extends Context.Service<
|
||||||
|
TrustGraphGatewayClientService,
|
||||||
|
TrustGraphGatewayClient
|
||||||
|
>()("@trustgraph/client/socket/effect-rpc-client/TrustGraphGatewayClientService") {}
|
||||||
|
|
||||||
|
export interface TrustGraphGatewayClientOptions {
|
||||||
|
readonly url: string;
|
||||||
|
readonly onConnect?: () => void;
|
||||||
|
readonly onDisconnect?: () => void;
|
||||||
|
readonly stateRef?: SubscriptionRef.SubscriptionRef<RpcConnectionState>;
|
||||||
|
readonly closedRef?: Ref.Ref<boolean>;
|
||||||
|
}
|
||||||
|
|
||||||
const DEFAULT_REQUEST_TIMEOUT_MS = 10_000;
|
const DEFAULT_REQUEST_TIMEOUT_MS = 10_000;
|
||||||
const DEFAULT_REQUEST_ATTEMPTS = 3;
|
const DEFAULT_REQUEST_ATTEMPTS = 3;
|
||||||
|
|
||||||
type NewableFactory<Args extends readonly unknown[], A extends object> = {
|
|
||||||
new (...args: Args): A;
|
|
||||||
(...args: Args): A;
|
|
||||||
readonly prototype: A;
|
|
||||||
};
|
|
||||||
|
|
||||||
function newableFactory<Args extends readonly unknown[], A extends object>(
|
|
||||||
factory: (...args: Args) => A,
|
|
||||||
): NewableFactory<Args, A> {
|
|
||||||
function Constructor(...args: Args): A {
|
|
||||||
return factory(...args);
|
|
||||||
}
|
|
||||||
return Constructor as unknown as NewableFactory<Args, A>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface EffectRpcClient {
|
export interface EffectRpcClient {
|
||||||
readonly subscribe: (listener: (state: RpcConnectionState) => void) => () => void;
|
readonly subscribe: (listener: (state: RpcConnectionState) => void) => () => void;
|
||||||
readonly dispatch: (
|
readonly dispatch: (
|
||||||
|
|
@ -67,131 +87,207 @@ export interface EffectRpcClient {
|
||||||
readonly close: () => Promise<void>;
|
readonly close: () => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const makeClientLayer = (
|
||||||
|
options: TrustGraphGatewayClientOptions,
|
||||||
|
stateRef: SubscriptionRef.SubscriptionRef<RpcConnectionState>,
|
||||||
|
closedRef: Ref.Ref<boolean>,
|
||||||
|
): Layer.Layer<TrustGraphRpcClientService> => {
|
||||||
|
const setState = (nextState: RpcConnectionState) =>
|
||||||
|
SubscriptionRef.set(stateRef, nextState);
|
||||||
|
|
||||||
|
const socketLayer = Layer.effect(
|
||||||
|
Socket.Socket,
|
||||||
|
Socket.makeWebSocket(options.url, {
|
||||||
|
closeCodeIsError: (code) => code !== 1000,
|
||||||
|
openTimeout: "10 seconds",
|
||||||
|
}),
|
||||||
|
).pipe(Layer.provide(Socket.layerWebSocketConstructorGlobal));
|
||||||
|
|
||||||
|
const hooksLayer = Layer.succeed(
|
||||||
|
RpcClient.ConnectionHooks,
|
||||||
|
RpcClient.ConnectionHooks.of({
|
||||||
|
onConnect: Effect.gen(function* () {
|
||||||
|
yield* setState({ status: "connected" });
|
||||||
|
options.onConnect?.();
|
||||||
|
}),
|
||||||
|
onDisconnect: Effect.gen(function* () {
|
||||||
|
const closed = yield* Ref.get(closedRef);
|
||||||
|
if (!closed) {
|
||||||
|
yield* setState({
|
||||||
|
status: "connecting",
|
||||||
|
lastError: "Disconnected from gateway",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
options.onDisconnect?.();
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const protocolLayer = RpcClient.layerProtocolSocket({
|
||||||
|
retryTransientErrors: true,
|
||||||
|
}).pipe(
|
||||||
|
Layer.provide(socketLayer),
|
||||||
|
Layer.provide(RpcSerialization.layerNdjson),
|
||||||
|
Layer.provide(hooksLayer),
|
||||||
|
);
|
||||||
|
|
||||||
|
return Layer.effect(
|
||||||
|
TrustGraphRpcClientService,
|
||||||
|
RpcClient.make(TrustGraphRpcs),
|
||||||
|
).pipe(Layer.provide(protocolLayer));
|
||||||
|
};
|
||||||
|
|
||||||
|
const makeSubscribeEffect = Effect.fn("makeSubscribeEffect")(function* (
|
||||||
|
stateRef: SubscriptionRef.SubscriptionRef<RpcConnectionState>,
|
||||||
|
scope: Scope.Scope,
|
||||||
|
listener: (state: RpcConnectionState) => void,
|
||||||
|
) {
|
||||||
|
let latest = SubscriptionRef.getUnsafe(stateRef);
|
||||||
|
listener(latest);
|
||||||
|
let replaySeen = false;
|
||||||
|
const fiber = yield* Effect.forkIn(SubscriptionRef.changes(stateRef).pipe(
|
||||||
|
Stream.runForEach((nextState) =>
|
||||||
|
Effect.sync(() => {
|
||||||
|
if (!replaySeen) {
|
||||||
|
replaySeen = true;
|
||||||
|
if (nextState === latest) return;
|
||||||
|
}
|
||||||
|
latest = nextState;
|
||||||
|
listener(nextState);
|
||||||
|
})
|
||||||
|
),
|
||||||
|
), scope);
|
||||||
|
return yield* Effect.succeed(Fiber.interrupt(fiber).pipe(Effect.asVoid));
|
||||||
|
});
|
||||||
|
|
||||||
|
export const makeTrustGraphGatewayClientScoped: (
|
||||||
|
options: TrustGraphGatewayClientOptions,
|
||||||
|
) => Effect.Effect<TrustGraphGatewayClient, never, Scope.Scope> = Effect.fn("makeTrustGraphGatewayClientScoped")(function* (
|
||||||
|
options,
|
||||||
|
) {
|
||||||
|
const stateRef = options.stateRef ?? (yield* SubscriptionRef.make<RpcConnectionState>({ status: "connecting" }));
|
||||||
|
const closedRef = options.closedRef ?? (yield* Ref.make(false));
|
||||||
|
const scope = yield* Scope.Scope;
|
||||||
|
const context = yield* Layer.buildWithScope(makeClientLayer(options, stateRef, closedRef), scope).pipe(
|
||||||
|
Effect.tapCause((cause) =>
|
||||||
|
SubscriptionRef.set(stateRef, {
|
||||||
|
status: "failed",
|
||||||
|
lastError: Cause.pretty(cause),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const client = Context.get(context, TrustGraphRpcClientService);
|
||||||
|
|
||||||
|
const close = Effect.gen(function* () {
|
||||||
|
const wasClosed = yield* Ref.getAndSet(closedRef, true);
|
||||||
|
if (!wasClosed) {
|
||||||
|
yield* SubscriptionRef.set(stateRef, { status: "closed" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
yield* Effect.addFinalizer(() => close);
|
||||||
|
|
||||||
|
return {
|
||||||
|
state: SubscriptionRef.get(stateRef),
|
||||||
|
changes: SubscriptionRef.changes(stateRef),
|
||||||
|
subscribe: (listener) => makeSubscribeEffect(stateRef, scope, listener),
|
||||||
|
dispatch: (input, options = {}) =>
|
||||||
|
withDispatchRequestPolicy(client.Dispatch(DispatchPayload.make(input)), options),
|
||||||
|
dispatchStream: (input, options = {}) =>
|
||||||
|
Stream.unwrap(
|
||||||
|
withDispatchRequestPolicy(
|
||||||
|
Effect.succeed(client.DispatchStream(DispatchPayload.make(input))),
|
||||||
|
options,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
runDispatchStream: (input, receiver, options = {}) => {
|
||||||
|
let last: DispatchStreamChunk | undefined;
|
||||||
|
return withDispatchRequestPolicy(
|
||||||
|
client.DispatchStream(DispatchPayload.make(input)).pipe(
|
||||||
|
Stream.runForEachWhile((chunk) =>
|
||||||
|
Effect.suspend(() => {
|
||||||
|
last = chunk;
|
||||||
|
return Effect.succeed(!receiver(chunk));
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
Effect.andThen(() => Effect.succeed(last)),
|
||||||
|
),
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
close,
|
||||||
|
} satisfies TrustGraphGatewayClient;
|
||||||
|
});
|
||||||
|
|
||||||
|
export const makeTrustGraphGatewayClientLayer = (
|
||||||
|
options: TrustGraphGatewayClientOptions,
|
||||||
|
): Layer.Layer<TrustGraphGatewayClientService> =>
|
||||||
|
Layer.effect(
|
||||||
|
TrustGraphGatewayClientService,
|
||||||
|
makeTrustGraphGatewayClientScoped(options).pipe(
|
||||||
|
Effect.map(TrustGraphGatewayClientService.of),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
export function makeEffectRpcClient(
|
export function makeEffectRpcClient(
|
||||||
url: string,
|
url: string,
|
||||||
onConnect?: () => void,
|
onConnect?: () => void,
|
||||||
onDisconnect?: () => void,
|
onDisconnect?: () => void,
|
||||||
): EffectRpcClient {
|
): EffectRpcClient {
|
||||||
const stateRef = Effect.runSync(SubscriptionRef.make<RpcConnectionState>({ status: "connecting" }));
|
const stateRef = Effect.runSync(SubscriptionRef.make<RpcConnectionState>({ status: "connecting" }));
|
||||||
let closed = false;
|
const closedRef = Effect.runSync(Ref.make(false));
|
||||||
|
const scope = Effect.runSync(Scope.make());
|
||||||
const setState = (nextState: RpcConnectionState) =>
|
const options: TrustGraphGatewayClientOptions = {
|
||||||
SubscriptionRef.set(stateRef, nextState);
|
url,
|
||||||
|
stateRef,
|
||||||
const makeClientLayer = (): Layer.Layer<TrustGraphRpcClientService> => {
|
closedRef,
|
||||||
const socketLayer = Layer.effect(
|
...(onConnect === undefined ? {} : { onConnect }),
|
||||||
Socket.Socket,
|
...(onDisconnect === undefined ? {} : { onDisconnect }),
|
||||||
Socket.makeWebSocket(url, {
|
|
||||||
closeCodeIsError: (code) => code !== 1000,
|
|
||||||
openTimeout: "10 seconds",
|
|
||||||
}),
|
|
||||||
).pipe(Layer.provide(Socket.layerWebSocketConstructorGlobal));
|
|
||||||
|
|
||||||
const hooksLayer = Layer.succeed(
|
|
||||||
RpcClient.ConnectionHooks,
|
|
||||||
RpcClient.ConnectionHooks.of({
|
|
||||||
onConnect: Effect.gen(function* () {
|
|
||||||
yield* setState({ status: "connected" });
|
|
||||||
onConnect?.();
|
|
||||||
}),
|
|
||||||
onDisconnect: Effect.gen(function* () {
|
|
||||||
if (!closed) {
|
|
||||||
yield* setState({
|
|
||||||
status: "connecting",
|
|
||||||
lastError: "Disconnected from gateway",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
onDisconnect?.();
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
const protocolLayer = RpcClient.layerProtocolSocket({
|
|
||||||
retryTransientErrors: true,
|
|
||||||
}).pipe(
|
|
||||||
Layer.provide(socketLayer),
|
|
||||||
Layer.provide(RpcSerialization.layerNdjson),
|
|
||||||
Layer.provide(hooksLayer),
|
|
||||||
);
|
|
||||||
|
|
||||||
const clientLayer = Layer.effect(
|
|
||||||
TrustGraphRpcClientService,
|
|
||||||
RpcClient.make(TrustGraphRpcs),
|
|
||||||
).pipe(Layer.provide(protocolLayer));
|
|
||||||
|
|
||||||
return clientLayer;
|
|
||||||
};
|
};
|
||||||
|
const clientPromise = Effect.runPromise(
|
||||||
const runtime = ManagedRuntime.make(makeClientLayer());
|
makeTrustGraphGatewayClientScoped(options).pipe(Scope.provide(scope)),
|
||||||
const clientPromise = runtime.runPromise(
|
|
||||||
TrustGraphRpcClientService.pipe(
|
|
||||||
Effect.tapCause((cause) =>
|
|
||||||
setState({
|
|
||||||
status: "failed",
|
|
||||||
lastError: Cause.pretty(cause),
|
|
||||||
})
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
subscribe: (listener) => {
|
subscribe: (listener) => {
|
||||||
let latest = SubscriptionRef.getUnsafe(stateRef);
|
let unsubscribe: Effect.Effect<void> | undefined;
|
||||||
listener(latest);
|
let cancelled = false;
|
||||||
let replaySeen = false;
|
listener(SubscriptionRef.getUnsafe(stateRef));
|
||||||
const fiber = Effect.runFork(
|
void clientPromise.then((client) =>
|
||||||
SubscriptionRef.changes(stateRef).pipe(
|
Effect.runPromise(client.subscribe(listener)).then((release) => {
|
||||||
Stream.runForEach((nextState) =>
|
if (cancelled) {
|
||||||
Effect.sync(() => {
|
return Effect.runPromise(release);
|
||||||
if (!replaySeen) {
|
}
|
||||||
replaySeen = true;
|
unsubscribe = release;
|
||||||
if (nextState === latest) return;
|
})
|
||||||
}
|
|
||||||
latest = nextState;
|
|
||||||
listener(nextState);
|
|
||||||
})
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
Effect.runFork(Fiber.interrupt(fiber));
|
cancelled = true;
|
||||||
|
if (unsubscribe !== undefined) {
|
||||||
|
Effect.runFork(unsubscribe);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
dispatch: (input, options = {}) =>
|
dispatch: (input, options = {}) =>
|
||||||
clientPromise.then((client) =>
|
clientPromise.then((client) =>
|
||||||
runtime.runPromise(
|
Effect.runPromise(client.dispatch(input, options))
|
||||||
withDispatchRequestPolicy(client.Dispatch(DispatchPayload.make(input)), options),
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
dispatchStream: (input, receiver, options = {}) => {
|
dispatchStream: (input, receiver, options = {}) =>
|
||||||
let last: DispatchStreamChunk | undefined;
|
clientPromise.then((client) =>
|
||||||
return clientPromise.then((client) =>
|
Effect.runPromise(client.runDispatchStream(input, receiver, options))
|
||||||
runtime.runPromise(
|
),
|
||||||
withDispatchRequestPolicy(
|
close: () =>
|
||||||
client.DispatchStream(DispatchPayload.make(input)).pipe(
|
clientPromise.then((client) =>
|
||||||
Stream.runForEachWhile((chunk) =>
|
Effect.runPromise(
|
||||||
Effect.suspend(() => {
|
client.close.pipe(
|
||||||
last = chunk;
|
Effect.andThen(Scope.close(scope, Exit.void)),
|
||||||
return Effect.succeed(!receiver(chunk));
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
options,
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
).then(() => last);
|
),
|
||||||
},
|
|
||||||
close: () => {
|
|
||||||
if (closed) return Promise.resolve();
|
|
||||||
closed = true;
|
|
||||||
Effect.runSync(setState({ status: "closed" }));
|
|
||||||
return runtime.dispose();
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EffectRpcClient = newableFactory(makeEffectRpcClient);
|
|
||||||
|
|
||||||
export function withDispatchRequestPolicy<A, E, R>(
|
export function withDispatchRequestPolicy<A, E, R>(
|
||||||
effect: Effect.Effect<A, E, R>,
|
effect: Effect.Effect<A, E, R>,
|
||||||
options: DispatchOptions,
|
options: DispatchOptions,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// Import core types and classes for the TrustGraph API
|
// Import core types and classes for the TrustGraph API
|
||||||
import type { Term, Triple } from "../models/Triple.js";
|
import type { Term, Triple } from "../models/Triple.js";
|
||||||
import {
|
import {
|
||||||
EffectRpcClient,
|
type EffectRpcClient,
|
||||||
type DispatchInput,
|
type DispatchInput,
|
||||||
type DispatchOptions,
|
type DispatchOptions,
|
||||||
type RpcConnectionState,
|
type RpcConnectionState,
|
||||||
|
|
@ -445,21 +445,6 @@ function makeid(length: number) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
type NewableFactory<Args extends readonly unknown[], A extends object> = {
|
|
||||||
new (...args: Args): A;
|
|
||||||
(...args: Args): A;
|
|
||||||
readonly prototype: A;
|
|
||||||
};
|
|
||||||
|
|
||||||
function newableFactory<Args extends readonly unknown[], A extends object>(
|
|
||||||
factory: (...args: Args) => A,
|
|
||||||
): NewableFactory<Args, A> {
|
|
||||||
function Constructor(...args: Args): A {
|
|
||||||
return factory(...args);
|
|
||||||
}
|
|
||||||
return Constructor as unknown as NewableFactory<Args, A>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BaseApi - Core WebSocket client for TrustGraph API
|
* BaseApi - Core WebSocket client for TrustGraph API
|
||||||
* Manages connection lifecycle, message routing, and provides base request
|
* Manages connection lifecycle, message routing, and provides base request
|
||||||
|
|
@ -622,27 +607,27 @@ export function makeBaseApi(
|
||||||
|
|
||||||
// Factory methods for creating specialized API instances
|
// Factory methods for creating specialized API instances
|
||||||
librarian() {
|
librarian() {
|
||||||
return new LibrarianApi(api);
|
return makeLibrarianApi(api);
|
||||||
},
|
},
|
||||||
|
|
||||||
flows() {
|
flows() {
|
||||||
return new FlowsApi(api);
|
return makeFlowsApi(api);
|
||||||
},
|
},
|
||||||
|
|
||||||
flow(id: string) {
|
flow(id: string) {
|
||||||
return new FlowApi(api, id);
|
return makeFlowApi(api, id);
|
||||||
},
|
},
|
||||||
|
|
||||||
knowledge() {
|
knowledge() {
|
||||||
return new KnowledgeApi(api);
|
return makeKnowledgeApi(api);
|
||||||
},
|
},
|
||||||
|
|
||||||
config() {
|
config() {
|
||||||
return new ConfigApi(api);
|
return makeConfigApi(api);
|
||||||
},
|
},
|
||||||
|
|
||||||
collectionManagement() {
|
collectionManagement() {
|
||||||
return new CollectionManagementApi(api);
|
return makeCollectionManagementApi(api);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -739,7 +724,7 @@ export function makeBaseApi(
|
||||||
}
|
}
|
||||||
|
|
||||||
export type BaseApi = ReturnType<typeof makeBaseApi>;
|
export type BaseApi = ReturnType<typeof makeBaseApi>;
|
||||||
export const BaseApi = newableFactory(makeBaseApi);
|
export const BaseApi = makeBaseApi;
|
||||||
|
|
||||||
export function makeBaseApiWithRpc(
|
export function makeBaseApiWithRpc(
|
||||||
user: string,
|
user: string,
|
||||||
|
|
@ -1153,7 +1138,7 @@ export function makeLibrarianApi(api: BaseApi) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LibrarianApi = ReturnType<typeof makeLibrarianApi>;
|
export type LibrarianApi = ReturnType<typeof makeLibrarianApi>;
|
||||||
export const LibrarianApi = newableFactory(makeLibrarianApi);
|
export const LibrarianApi = makeLibrarianApi;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FlowsApi - Manages processing flows and configuration
|
* FlowsApi - Manages processing flows and configuration
|
||||||
|
|
@ -1418,7 +1403,7 @@ export function makeFlowsApi(api: BaseApi) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FlowsApi = ReturnType<typeof makeFlowsApi>;
|
export type FlowsApi = ReturnType<typeof makeFlowsApi>;
|
||||||
export const FlowsApi = newableFactory(makeFlowsApi);
|
export const FlowsApi = makeFlowsApi;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FlowApi - Interface for interacting with a specific flow instance
|
* FlowApi - Interface for interacting with a specific flow instance
|
||||||
|
|
@ -2205,7 +2190,7 @@ export function makeFlowApi(api: BaseApi, flowId: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FlowApi = ReturnType<typeof makeFlowApi>;
|
export type FlowApi = ReturnType<typeof makeFlowApi>;
|
||||||
export const FlowApi = newableFactory(makeFlowApi);
|
export const FlowApi = makeFlowApi;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ConfigApi - Dedicated configuration management interface
|
* ConfigApi - Dedicated configuration management interface
|
||||||
|
|
@ -2401,7 +2386,7 @@ export function makeConfigApi(api: BaseApi) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ConfigApi = ReturnType<typeof makeConfigApi>;
|
export type ConfigApi = ReturnType<typeof makeConfigApi>;
|
||||||
export const ConfigApi = newableFactory(makeConfigApi);
|
export const ConfigApi = makeConfigApi;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* KnowledgeApi - Manages knowledge graph cores and data
|
* KnowledgeApi - Manages knowledge graph cores and data
|
||||||
|
|
@ -2568,7 +2553,7 @@ export function makeKnowledgeApi(api: BaseApi) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export type KnowledgeApi = ReturnType<typeof makeKnowledgeApi>;
|
export type KnowledgeApi = ReturnType<typeof makeKnowledgeApi>;
|
||||||
export const KnowledgeApi = newableFactory(makeKnowledgeApi);
|
export const KnowledgeApi = makeKnowledgeApi;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CollectionManagementApi - Manages collections for organizing documents
|
* CollectionManagementApi - Manages collections for organizing documents
|
||||||
|
|
@ -2677,7 +2662,7 @@ export function makeCollectionManagementApi(api: BaseApi) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CollectionManagementApi = ReturnType<typeof makeCollectionManagementApi>;
|
export type CollectionManagementApi = ReturnType<typeof makeCollectionManagementApi>;
|
||||||
export const CollectionManagementApi = newableFactory(makeCollectionManagementApi);
|
export const CollectionManagementApi = makeCollectionManagementApi;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factory function to create a new TrustGraph WebSocket connection
|
* Factory function to create a new TrustGraph WebSocket connection
|
||||||
|
|
@ -2690,4 +2675,4 @@ export const createTrustGraphSocket = (
|
||||||
user: string,
|
user: string,
|
||||||
token?: string,
|
token?: string,
|
||||||
socketUrl?: string,
|
socketUrl?: string,
|
||||||
): BaseApi => new BaseApi(user, token, socketUrl);
|
): BaseApi => makeBaseApi(user, token, socketUrl);
|
||||||
|
|
|
||||||
|
|
@ -12,21 +12,13 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@effect/ai-anthropic": "4.0.0-beta.78",
|
"@effect/ai-anthropic": "4.0.0-beta.78",
|
||||||
"@effect/ai-openai": "4.0.0-beta.78",
|
|
||||||
"@effect/ai-openrouter": "4.0.0-beta.78",
|
|
||||||
"@effect/atom-react": "4.0.0-beta.78",
|
|
||||||
"@effect/openapi-generator": "4.0.0-beta.78",
|
|
||||||
"@effect/opentelemetry": "4.0.0-beta.78",
|
|
||||||
"@effect/platform-browser": "4.0.0-beta.78",
|
|
||||||
"@effect/platform-bun": "4.0.0-beta.78",
|
"@effect/platform-bun": "4.0.0-beta.78",
|
||||||
"@effect/platform-node": "4.0.0-beta.78",
|
"@effect/platform-node": "4.0.0-beta.78",
|
||||||
"@effect/platform-node-shared": "4.0.0-beta.78",
|
|
||||||
"@effect/tsgo": "0.14.0",
|
|
||||||
"@effect/vitest": "4.0.0-beta.78",
|
|
||||||
"@mistralai/mistralai": "^1.0.0",
|
"@mistralai/mistralai": "^1.0.0",
|
||||||
"@modelcontextprotocol/sdk": "^1.12.0",
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
||||||
"@qdrant/js-client-rest": "^1.13.0",
|
"@qdrant/js-client-rest": "^1.13.0",
|
||||||
"@trustgraph/base": "workspace:*",
|
"@trustgraph/base": "workspace:*",
|
||||||
|
"@trustgraph/client": "workspace:*",
|
||||||
"effect": "4.0.0-beta.78",
|
"effect": "4.0.0-beta.78",
|
||||||
"falkordb": "^5.0.0",
|
"falkordb": "^5.0.0",
|
||||||
"ollama": "^0.6.3",
|
"ollama": "^0.6.3",
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import { Clock, Effect, Exit, HashMap, HashSet, Option, Random, Scope, Synchroni
|
||||||
import {
|
import {
|
||||||
loadMessagingRuntimeConfig,
|
loadMessagingRuntimeConfig,
|
||||||
makeNatsBackend,
|
makeNatsBackend,
|
||||||
|
makeNatsBackendScoped,
|
||||||
makePubSubService,
|
makePubSubService,
|
||||||
makeRequestResponseFactoryService,
|
makeRequestResponseFactoryService,
|
||||||
messagingDeliveryError,
|
messagingDeliveryError,
|
||||||
|
|
@ -401,3 +402,17 @@ export function makeDispatcherManager(config: GatewayConfig): DispatcherManager
|
||||||
publishToTopic,
|
publishToTopic,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const makeDispatcherManagerScoped = Effect.fn("makeDispatcherManagerScoped")(function* (
|
||||||
|
config: GatewayConfig,
|
||||||
|
) {
|
||||||
|
if (config.pubsub !== undefined) {
|
||||||
|
return makeDispatcherManager(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pubsub = yield* makeNatsBackendScoped(config.natsUrl ?? "nats://localhost:4222");
|
||||||
|
return makeDispatcherManager({
|
||||||
|
...config,
|
||||||
|
pubsub,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1 @@
|
||||||
import { Schema as S } from "effect";
|
export * from "@trustgraph/client/rpc/contract";
|
||||||
import * as Rpc from "effect/unstable/rpc/Rpc";
|
|
||||||
import * as RpcGroup from "effect/unstable/rpc/RpcGroup";
|
|
||||||
|
|
||||||
export class DispatchPayload extends S.Class<DispatchPayload>("DispatchPayload")({
|
|
||||||
scope: S.Literals(["global", "flow"]),
|
|
||||||
service: S.String,
|
|
||||||
flow: S.optionalKey(S.String),
|
|
||||||
request: S.Record(S.String, S.Unknown),
|
|
||||||
}) {}
|
|
||||||
|
|
||||||
export class DispatchStreamChunk extends S.Class<DispatchStreamChunk>("DispatchStreamChunk")({
|
|
||||||
response: S.Unknown,
|
|
||||||
complete: S.Boolean,
|
|
||||||
}) {}
|
|
||||||
|
|
||||||
export class DispatchError extends S.TaggedErrorClass<DispatchError>()("DispatchError", {
|
|
||||||
message: S.String,
|
|
||||||
}) {}
|
|
||||||
|
|
||||||
export class Dispatch extends Rpc.make("Dispatch", {
|
|
||||||
payload: DispatchPayload,
|
|
||||||
success: S.Unknown,
|
|
||||||
error: DispatchError,
|
|
||||||
}) {}
|
|
||||||
|
|
||||||
export class DispatchStream extends Rpc.make("DispatchStream", {
|
|
||||||
payload: DispatchPayload,
|
|
||||||
success: DispatchStreamChunk,
|
|
||||||
error: DispatchError,
|
|
||||||
stream: true,
|
|
||||||
}) {}
|
|
||||||
|
|
||||||
export const TrustGraphRpcs = RpcGroup.make(Dispatch, DispatchStream);
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import { NodeHttpServer, NodeRuntime } from "@effect/platform-node";
|
||||||
import { Clock, Config, Effect, Exit, Layer, Random, Scope } from "effect";
|
import { Clock, Config, Effect, Exit, Layer, Random, Scope } from "effect";
|
||||||
import * as O from "effect/Option";
|
import * as O from "effect/Option";
|
||||||
import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http";
|
import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http";
|
||||||
|
import { HttpApiBuilder } from "effect/unstable/httpapi";
|
||||||
import * as RpcSerialization from "effect/unstable/rpc/RpcSerialization";
|
import * as RpcSerialization from "effect/unstable/rpc/RpcSerialization";
|
||||||
import {
|
import {
|
||||||
formatPrometheusMetrics,
|
formatPrometheusMetrics,
|
||||||
|
|
@ -19,7 +20,8 @@ import {
|
||||||
toTgError,
|
toTgError,
|
||||||
type PubSubBackend,
|
type PubSubBackend,
|
||||||
} from "@trustgraph/base";
|
} from "@trustgraph/base";
|
||||||
import { makeDispatcherManager, type DispatcherManager } from "./dispatch/manager.js";
|
import { GatewayWorkbenchHttpApi } from "@trustgraph/client/rpc/contract";
|
||||||
|
import { makeDispatcherManagerScoped, type DispatcherManager } from "./dispatch/manager.js";
|
||||||
import { makeGatewayRpcServer, type GatewayRpcServer } from "./rpc-server.js";
|
import { makeGatewayRpcServer, type GatewayRpcServer } from "./rpc-server.js";
|
||||||
|
|
||||||
export interface GatewayConfig {
|
export interface GatewayConfig {
|
||||||
|
|
@ -134,6 +136,22 @@ const workbenchDispatch = (
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const gatewayWorkbenchHttpApiRoutes = (
|
||||||
|
config: GatewayConfig,
|
||||||
|
dispatcher: DispatcherManager,
|
||||||
|
) => {
|
||||||
|
const handlers = HttpApiBuilder.group(
|
||||||
|
GatewayWorkbenchHttpApi,
|
||||||
|
"workbench",
|
||||||
|
(handlers) =>
|
||||||
|
handlers.handleRaw("dispatch", () => workbenchDispatch(config, dispatcher)),
|
||||||
|
);
|
||||||
|
|
||||||
|
return HttpApiBuilder.layer(GatewayWorkbenchHttpApi).pipe(
|
||||||
|
Layer.provide(handlers),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const globalDispatch = (
|
const globalDispatch = (
|
||||||
config: GatewayConfig,
|
config: GatewayConfig,
|
||||||
dispatcher: DispatcherManager,
|
dispatcher: DispatcherManager,
|
||||||
|
|
@ -240,7 +258,7 @@ export const makeGatewayRoutes = (
|
||||||
rpcScope: Scope.Scope,
|
rpcScope: Scope.Scope,
|
||||||
) =>
|
) =>
|
||||||
Layer.mergeAll(
|
Layer.mergeAll(
|
||||||
HttpRouter.add("POST", "/api/v1/workbench/dispatch", workbenchDispatch(config, dispatcher)),
|
gatewayWorkbenchHttpApiRoutes(config, dispatcher),
|
||||||
HttpRouter.add("POST", "/api/v1/:kind", globalDispatch(config, dispatcher)),
|
HttpRouter.add("POST", "/api/v1/:kind", globalDispatch(config, dispatcher)),
|
||||||
HttpRouter.add("POST", "/api/v1/flow/:flow/service/:kind", flowDispatch(config, dispatcher)),
|
HttpRouter.add("POST", "/api/v1/flow/:flow/service/:kind", flowDispatch(config, dispatcher)),
|
||||||
HttpRouter.add("POST", "/api/v1/flow/:flow/load", flowLoad(config, dispatcher)),
|
HttpRouter.add("POST", "/api/v1/flow/:flow/load", flowLoad(config, dispatcher)),
|
||||||
|
|
@ -251,7 +269,7 @@ export const makeGatewayRoutes = (
|
||||||
export function createGateway(config: GatewayConfig) {
|
export function createGateway(config: GatewayConfig) {
|
||||||
return Layer.effectDiscard(
|
return Layer.effectDiscard(
|
||||||
Effect.scoped(Effect.gen(function* () {
|
Effect.scoped(Effect.gen(function* () {
|
||||||
const dispatcher = makeDispatcherManager(config);
|
const dispatcher = yield* makeDispatcherManagerScoped(config);
|
||||||
yield* dispatcher.start.pipe(
|
yield* dispatcher.start.pipe(
|
||||||
Effect.mapError((cause) => messagingLifecycleError("gateway", "dispatcher-start", cause)),
|
Effect.mapError((cause) => messagingLifecycleError("gateway", "dispatcher-start", cause)),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
"include": ["src"],
|
"include": ["src"],
|
||||||
"exclude": ["src/**/*.test.ts", "src/**/*.spec.ts"],
|
"exclude": ["src/**/*.test.ts", "src/**/*.spec.ts"],
|
||||||
"references": [
|
"references": [
|
||||||
{ "path": "../base" }
|
{ "path": "../base" },
|
||||||
|
{ "path": "../client" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,18 +13,9 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@trustgraph/base": "workspace:*",
|
"@trustgraph/base": "workspace:*",
|
||||||
"@trustgraph/client": "workspace:*",
|
"@trustgraph/client": "workspace:*",
|
||||||
"@effect/platform-node": "4.0.0-beta.78",
|
|
||||||
"@effect/platform-node-shared": "4.0.0-beta.78",
|
|
||||||
"@effect/ai-anthropic": "4.0.0-beta.78",
|
|
||||||
"@effect/ai-openai": "4.0.0-beta.78",
|
|
||||||
"@effect/ai-openrouter": "4.0.0-beta.78",
|
|
||||||
"@effect/atom-react": "4.0.0-beta.78",
|
|
||||||
"@effect/openapi-generator": "4.0.0-beta.78",
|
|
||||||
"@effect/opentelemetry": "4.0.0-beta.78",
|
|
||||||
"@effect/platform-browser": "4.0.0-beta.78",
|
|
||||||
"@effect/platform-bun": "4.0.0-beta.78",
|
"@effect/platform-bun": "4.0.0-beta.78",
|
||||||
"@effect/tsgo": "0.14.0",
|
"@effect/platform-node": "4.0.0-beta.78",
|
||||||
"@effect/vitest": "4.0.0-beta.78"
|
"effect": "4.0.0-beta.78"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@effect/vitest": "4.0.0-beta.78",
|
"@effect/vitest": "4.0.0-beta.78",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { describe, expect, it } from "@effect/vitest";
|
import { describe, expect, it } from "@effect/vitest";
|
||||||
import type { BaseApi } from "@trustgraph/client";
|
import { DispatchStreamChunk, type BaseApi, type TrustGraphGatewayClient } from "@trustgraph/client";
|
||||||
import { Effect, Layer } from "effect";
|
import { Effect, Layer, Stream } from "effect";
|
||||||
import * as S from "effect/Schema";
|
import * as S from "effect/Schema";
|
||||||
import { McpServer } from "effect/unstable/ai";
|
import { McpServer } from "effect/unstable/ai";
|
||||||
import * as McpSchema from "effect/unstable/ai/McpSchema";
|
import * as McpSchema from "effect/unstable/ai/McpSchema";
|
||||||
|
|
@ -13,6 +13,7 @@ import {
|
||||||
TrustGraphMcpConfig,
|
TrustGraphMcpConfig,
|
||||||
TrustGraphMcpToolkit,
|
TrustGraphMcpToolkit,
|
||||||
TrustGraphMcpToolkitLive,
|
TrustGraphMcpToolkitLive,
|
||||||
|
TrustGraphGateway,
|
||||||
TrustGraphSocket,
|
TrustGraphSocket,
|
||||||
} from "../server-effect.js";
|
} from "../server-effect.js";
|
||||||
|
|
||||||
|
|
@ -124,6 +125,29 @@ const makeFakeSocket = (
|
||||||
return { socket, calls };
|
return { socket, calls };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const makeFakeGateway = (): TrustGraphGatewayClient => ({
|
||||||
|
state: Effect.succeed({ status: "connected" }),
|
||||||
|
changes: Stream.empty,
|
||||||
|
subscribe: () => Effect.succeed(Effect.void),
|
||||||
|
dispatch: () => Effect.succeed({}),
|
||||||
|
dispatchStream: () => Stream.empty,
|
||||||
|
runDispatchStream: (_input, receiver) =>
|
||||||
|
Effect.sync(() => {
|
||||||
|
const chunk = DispatchStreamChunk.make({
|
||||||
|
response: {
|
||||||
|
chunk_type: "answer",
|
||||||
|
content: "agent answer",
|
||||||
|
end_of_message: true,
|
||||||
|
end_of_dialog: true,
|
||||||
|
},
|
||||||
|
complete: true,
|
||||||
|
});
|
||||||
|
receiver(chunk);
|
||||||
|
return chunk;
|
||||||
|
}),
|
||||||
|
close: Effect.void,
|
||||||
|
});
|
||||||
|
|
||||||
const testConfig = TrustGraphMcpConfig.of({
|
const testConfig = TrustGraphMcpConfig.of({
|
||||||
gatewayUrl: "ws://localhost:8088/api/v1/rpc",
|
gatewayUrl: "ws://localhost:8088/api/v1/rpc",
|
||||||
user: "mcp-test",
|
user: "mcp-test",
|
||||||
|
|
@ -147,8 +171,10 @@ const makeNativeTestClientEffect = Effect.fn("makeNativeTestClient")(function*(
|
||||||
textCompletion: options.textCompletion,
|
textCompletion: options.textCompletion,
|
||||||
graphRag: options.graphRag,
|
graphRag: options.graphRag,
|
||||||
});
|
});
|
||||||
|
const gateway = makeFakeGateway();
|
||||||
const serverLayer = McpServer.toolkit(TrustGraphMcpToolkit).pipe(
|
const serverLayer = McpServer.toolkit(TrustGraphMcpToolkit).pipe(
|
||||||
Layer.provide(TrustGraphMcpToolkitLive),
|
Layer.provide(TrustGraphMcpToolkitLive),
|
||||||
|
Layer.provide(Layer.succeed(TrustGraphGateway, TrustGraphGateway.of(gateway))),
|
||||||
Layer.provide(Layer.succeed(TrustGraphSocket, TrustGraphSocket.of(socket))),
|
Layer.provide(Layer.succeed(TrustGraphSocket, TrustGraphSocket.of(socket))),
|
||||||
Layer.provide(Layer.succeed(TrustGraphMcpConfig, testConfig)),
|
Layer.provide(Layer.succeed(TrustGraphMcpConfig, testConfig)),
|
||||||
Layer.provide(McpServer.layerHttp({
|
Layer.provide(McpServer.layerHttp({
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,12 @@
|
||||||
import {BunHttpServer, BunRuntime} from "@effect/platform-bun";
|
import {BunHttpServer, BunRuntime} from "@effect/platform-bun";
|
||||||
import {NodeRuntime, NodeStdio} from "@effect/platform-node";
|
import {NodeRuntime, NodeStdio} from "@effect/platform-node";
|
||||||
import {createTrustGraphSocket, type BaseApi, type Term as ClientTerm} from "@trustgraph/client";
|
import {
|
||||||
|
createTrustGraphSocket,
|
||||||
|
makeTrustGraphGatewayClientScoped,
|
||||||
|
type BaseApi,
|
||||||
|
type Term as ClientTerm,
|
||||||
|
type TrustGraphGatewayClient,
|
||||||
|
} from "@trustgraph/client";
|
||||||
import {Config, Context, Effect, Layer} from "effect";
|
import {Config, Context, Effect, Layer} from "effect";
|
||||||
import * as O from "effect/Option";
|
import * as O from "effect/Option";
|
||||||
import * as Predicate from "effect/Predicate";
|
import * as Predicate from "effect/Predicate";
|
||||||
|
|
@ -1223,6 +1229,12 @@ export interface TrustGraphMcpConfigShape {
|
||||||
const readNonEmpty = (value: string | undefined): string | undefined =>
|
const readNonEmpty = (value: string | undefined): string | undefined =>
|
||||||
value !== undefined && value.length > 0 ? value : undefined
|
value !== undefined && value.length > 0 ? value : undefined
|
||||||
|
|
||||||
|
const gatewayUrlWithToken = (config: TrustGraphMcpConfigShape): string => {
|
||||||
|
if (config.token === undefined || config.token.length === 0) return config.gatewayUrl
|
||||||
|
const separator = config.gatewayUrl.includes("?") ? "&" : "?"
|
||||||
|
return `${config.gatewayUrl}${separator}token=${encodeURIComponent(config.token)}`
|
||||||
|
}
|
||||||
|
|
||||||
const parsePort = (raw: string | undefined): number => {
|
const parsePort = (raw: string | undefined): number => {
|
||||||
if (raw === undefined) {
|
if (raw === undefined) {
|
||||||
return 3000
|
return 3000
|
||||||
|
|
@ -1283,6 +1295,19 @@ export class TrustGraphSocket extends Context.Service<TrustGraphSocket, BaseApi>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class TrustGraphGateway extends Context.Service<TrustGraphGateway, TrustGraphGatewayClient>()(
|
||||||
|
"@trustgraph/mcp/server-effect/TrustGraphGateway",
|
||||||
|
) {
|
||||||
|
static readonly layer = Layer.effect(
|
||||||
|
TrustGraphGateway,
|
||||||
|
Effect.gen(function*() {
|
||||||
|
const config = yield* TrustGraphMcpConfig
|
||||||
|
const client = yield* makeTrustGraphGatewayClientScoped({url: gatewayUrlWithToken(config)})
|
||||||
|
return TrustGraphGateway.of(client)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const toErrorMessage = (cause: unknown): string => {
|
const toErrorMessage = (cause: unknown): string => {
|
||||||
if (Predicate.isError(cause) && cause.message.length > 0) {
|
if (Predicate.isError(cause) && cause.message.length > 0) {
|
||||||
return cause.message
|
return cause.message
|
||||||
|
|
@ -1296,6 +1321,25 @@ const toErrorMessage = (cause: unknown): string => {
|
||||||
return "TrustGraph MCP tool failed"
|
return "TrustGraph MCP tool failed"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const asRecord = (value: unknown): Record<string, unknown> =>
|
||||||
|
Predicate.isObject(value) && !Array.isArray(value) ? value as Record<string, unknown> : {}
|
||||||
|
|
||||||
|
const stringProperty = (source: unknown, key: string): string | undefined => {
|
||||||
|
const value = asRecord(source)[key]
|
||||||
|
return Predicate.isString(value) ? value : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const booleanProperty = (source: unknown, key: string): boolean | undefined => {
|
||||||
|
const value = asRecord(source)[key]
|
||||||
|
return typeof value === "boolean" ? value : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const responseErrorMessage = (source: unknown): string | undefined => {
|
||||||
|
const error = asRecord(source).error
|
||||||
|
if (Predicate.isString(error)) return error
|
||||||
|
return stringProperty(error, "message")
|
||||||
|
}
|
||||||
|
|
||||||
const decodeJson = S.decodeUnknownEffect(S.Json)
|
const decodeJson = S.decodeUnknownEffect(S.Json)
|
||||||
const decodeJsonArray = S.decodeUnknownEffect(S.Array(S.Json))
|
const decodeJsonArray = S.decodeUnknownEffect(S.Array(S.Json))
|
||||||
|
|
||||||
|
|
@ -1316,10 +1360,59 @@ const decodeJsonArrayOrFail = <E>(
|
||||||
const asIriTerm = (value: string | undefined): ClientTerm | undefined =>
|
const asIriTerm = (value: string | undefined): ClientTerm | undefined =>
|
||||||
value !== undefined && value.length > 0 ? {t: "i", i: value} : undefined
|
value !== undefined && value.length > 0 ? {t: "i", i: value} : undefined
|
||||||
|
|
||||||
|
const runAgentTool = Effect.fn("TrustGraphMcpToolkit.agent")(function*(
|
||||||
|
gateway: TrustGraphGatewayClient,
|
||||||
|
config: TrustGraphMcpConfigShape,
|
||||||
|
question: string,
|
||||||
|
) {
|
||||||
|
let fullAnswer = ""
|
||||||
|
let streamError: AgentError | undefined
|
||||||
|
|
||||||
|
yield* gateway.runDispatchStream(
|
||||||
|
{
|
||||||
|
scope: "flow",
|
||||||
|
flow: config.flowId,
|
||||||
|
service: "agent",
|
||||||
|
request: {
|
||||||
|
question,
|
||||||
|
user: config.user,
|
||||||
|
collection: "default",
|
||||||
|
streaming: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
(chunk) => {
|
||||||
|
const resp = asRecord(chunk.response)
|
||||||
|
const chunkType = stringProperty(resp, "chunk_type")
|
||||||
|
const error = chunkType === "error"
|
||||||
|
? responseErrorMessage(resp) ?? "Unknown agent error"
|
||||||
|
: responseErrorMessage(resp)
|
||||||
|
if (error !== undefined) {
|
||||||
|
streamError = AgentError.make({message: error})
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chunkType === "answer" || chunkType === "final-answer") {
|
||||||
|
fullAnswer += stringProperty(resp, "content") ?? ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return chunk.complete === true || booleanProperty(resp, "end_of_dialog") === true
|
||||||
|
},
|
||||||
|
{timeoutMs: 120_000, retries: 2},
|
||||||
|
).pipe(
|
||||||
|
Effect.mapError((cause) => AgentError.make({message: toErrorMessage(cause)})),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (streamError !== undefined) {
|
||||||
|
return yield* streamError
|
||||||
|
}
|
||||||
|
return AgentSuccess.make({text: fullAnswer})
|
||||||
|
})
|
||||||
|
|
||||||
export const TrustGraphMcpToolkitLive = TrustGraphMcpToolkit.toLayer(
|
export const TrustGraphMcpToolkitLive = TrustGraphMcpToolkit.toLayer(
|
||||||
Effect.gen(function*() {
|
Effect.gen(function*() {
|
||||||
const config = yield* TrustGraphMcpConfig
|
const config = yield* TrustGraphMcpConfig
|
||||||
const socket = yield* TrustGraphSocket
|
const socket = yield* TrustGraphSocket
|
||||||
|
const gateway = yield* TrustGraphGateway
|
||||||
|
|
||||||
return TrustGraphMcpToolkit.of({
|
return TrustGraphMcpToolkit.of({
|
||||||
text_completion: ({system, prompt}) =>
|
text_completion: ({system, prompt}) =>
|
||||||
|
|
@ -1354,22 +1447,7 @@ export const TrustGraphMcpToolkitLive = TrustGraphMcpToolkit.toLayer(
|
||||||
Effect.map((text) => DocumentRagSuccess.make({text})),
|
Effect.map((text) => DocumentRagSuccess.make({text})),
|
||||||
),
|
),
|
||||||
|
|
||||||
agent: ({question}) =>
|
agent: ({question}) => runAgentTool(gateway, config, question),
|
||||||
Effect.callback<AgentSuccess, AgentError>((resume) => {
|
|
||||||
let fullAnswer = ""
|
|
||||||
socket.flow(config.flowId).agent(
|
|
||||||
question,
|
|
||||||
() => {},
|
|
||||||
() => {},
|
|
||||||
(chunk, complete) => {
|
|
||||||
fullAnswer += chunk
|
|
||||||
if (complete) {
|
|
||||||
resume(Effect.succeed(AgentSuccess.make({text: fullAnswer})))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
(cause) => resume(Effect.fail(AgentError.make({message: toErrorMessage(cause)}))),
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
|
|
||||||
embeddings: ({text}) =>
|
embeddings: ({text}) =>
|
||||||
Effect.tryPromise({
|
Effect.tryPromise({
|
||||||
|
|
@ -1694,6 +1772,7 @@ const makeTrustGraphMcpHttpLayerFromConfig = (
|
||||||
version: config.version,
|
version: config.version,
|
||||||
path: config.mcpPath,
|
path: config.mcpPath,
|
||||||
})),
|
})),
|
||||||
|
Layer.provide(TrustGraphGateway.layer),
|
||||||
Layer.provide(TrustGraphSocket.layer),
|
Layer.provide(TrustGraphSocket.layer),
|
||||||
Layer.provide(Layer.succeed(TrustGraphMcpConfig, TrustGraphMcpConfig.of(config))),
|
Layer.provide(Layer.succeed(TrustGraphMcpConfig, TrustGraphMcpConfig.of(config))),
|
||||||
)
|
)
|
||||||
|
|
@ -1713,6 +1792,7 @@ const makeTrustGraphMcpStdioLayerFromConfig = (
|
||||||
version: config.version,
|
version: config.version,
|
||||||
})),
|
})),
|
||||||
Layer.provide(NodeStdio.layer),
|
Layer.provide(NodeStdio.layer),
|
||||||
|
Layer.provide(TrustGraphGateway.layer),
|
||||||
Layer.provide(TrustGraphSocket.layer),
|
Layer.provide(TrustGraphSocket.layer),
|
||||||
Layer.provide(Layer.succeed(TrustGraphMcpConfig, TrustGraphMcpConfig.of(config))),
|
Layer.provide(Layer.succeed(TrustGraphMcpConfig, TrustGraphMcpConfig.of(config))),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -10,21 +10,11 @@
|
||||||
"qa:browser": "playwright test"
|
"qa:browser": "playwright test"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@effect/ai-anthropic": "4.0.0-beta.78",
|
|
||||||
"@effect/ai-openai": "4.0.0-beta.78",
|
|
||||||
"@effect/ai-openrouter": "4.0.0-beta.78",
|
|
||||||
"@effect/atom-react": "4.0.0-beta.78",
|
"@effect/atom-react": "4.0.0-beta.78",
|
||||||
"@effect/openapi-generator": "4.0.0-beta.78",
|
|
||||||
"@effect/opentelemetry": "4.0.0-beta.78",
|
|
||||||
"@effect/platform-browser": "4.0.0-beta.78",
|
"@effect/platform-browser": "4.0.0-beta.78",
|
||||||
"@effect/platform-bun": "4.0.0-beta.78",
|
|
||||||
"@effect/platform-node": "4.0.0-beta.78",
|
|
||||||
"@effect/platform-node-shared": "4.0.0-beta.78",
|
|
||||||
"@effect/tsgo": "0.14.0",
|
|
||||||
"@effect/vitest": "4.0.0-beta.78",
|
|
||||||
"@tanstack/react-query": "^5.75.0",
|
|
||||||
"@trustgraph/client": "workspace:*",
|
"@trustgraph/client": "workspace:*",
|
||||||
"clsx": "^2.1.0",
|
"clsx": "^2.1.0",
|
||||||
|
"effect": "4.0.0-beta.78",
|
||||||
"lucide-react": "^0.513.0",
|
"lucide-react": "^0.513.0",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
|
|
@ -32,8 +22,7 @@
|
||||||
"react-force-graph-2d": "^1.29.1",
|
"react-force-graph-2d": "^1.29.1",
|
||||||
"react-markdown": "^10.1.0",
|
"react-markdown": "^10.1.0",
|
||||||
"react-router": "^7.6.0",
|
"react-router": "^7.6.0",
|
||||||
"tailwind-merge": "^3.3.0",
|
"tailwind-merge": "^3.3.0"
|
||||||
"zustand": "^5.0.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@effect/vitest": "4.0.0-beta.78",
|
"@effect/vitest": "4.0.0-beta.78",
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -20,12 +20,12 @@ export function NotificationToasts() {
|
||||||
if (notifications.length === 0) return null;
|
if (notifications.length === 0) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed bottom-4 right-4 z-50 flex flex-col gap-2" aria-live="polite">
|
<div className="pointer-events-none fixed bottom-4 right-4 z-50 flex flex-col gap-2" aria-live="polite">
|
||||||
{notifications.map((n) => (
|
{notifications.map((n) => (
|
||||||
<div
|
<div
|
||||||
key={n.id}
|
key={n.id}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex items-start gap-2 rounded-lg border px-4 py-3 text-sm shadow-lg",
|
"pointer-events-none flex items-start gap-2 rounded-lg border px-4 py-3 text-sm shadow-lg",
|
||||||
typeStyles[n.type],
|
typeStyles[n.type],
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|
@ -37,7 +37,7 @@ export function NotificationToasts() {
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => removeNotification(n.id)}
|
onClick={() => removeNotification(n.id)}
|
||||||
className="shrink-0 opacity-60 hover:opacity-100"
|
className="pointer-events-auto shrink-0 opacity-60 hover:opacity-100"
|
||||||
aria-label="Dismiss notification"
|
aria-label="Dismiss notification"
|
||||||
>
|
>
|
||||||
<X className="h-3.5 w-3.5" />
|
<X className="h-3.5 w-3.5" />
|
||||||
|
|
|
||||||
|
|
@ -266,8 +266,10 @@ export default function SettingsPage() {
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (collectionForm.id.trim().length === 0) return;
|
const collectionId = collectionForm.id.trim();
|
||||||
|
if (collectionId.length === 0) return;
|
||||||
createCollection(collectionForm);
|
createCollection(collectionForm);
|
||||||
|
setField({ key: "collection", value: collectionId });
|
||||||
setCollectionForm({ id: "", name: "", description: "", tags: "", submitting: false });
|
setCollectionForm({ id: "", name: "", description: "", tags: "", submitting: false });
|
||||||
setCreateOpen(false);
|
setCreateOpen(false);
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import {
|
||||||
type Settings,
|
type Settings,
|
||||||
type WorkbenchApiFactory,
|
type WorkbenchApiFactory,
|
||||||
} from "@/atoms/workbench";
|
} from "@/atoms/workbench";
|
||||||
|
import type { BaseApi } from "@trustgraph/client";
|
||||||
import { makeMockBaseApi, qaSettingsFromFixture, type MockWorkbenchFixture } from "@/qa/mock-api";
|
import { makeMockBaseApi, qaSettingsFromFixture, type MockWorkbenchFixture } from "@/qa/mock-api";
|
||||||
|
|
||||||
export interface WorkbenchQaWindowConfig {
|
export interface WorkbenchQaWindowConfig {
|
||||||
|
|
@ -19,6 +20,7 @@ export interface WorkbenchQaWindowConfig {
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
__TRUSTGRAPH_WORKBENCH_QA__?: WorkbenchQaWindowConfig;
|
__TRUSTGRAPH_WORKBENCH_QA__?: WorkbenchQaWindowConfig;
|
||||||
|
__TRUSTGRAPH_WORKBENCH_QA_API__?: BaseApi;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -43,6 +45,7 @@ export function getWorkbenchQaInitialValues(): Iterable<readonly [Atom.Atom<unkn
|
||||||
const apiFactory: WorkbenchApiFactory = {
|
const apiFactory: WorkbenchApiFactory = {
|
||||||
create: () => api,
|
create: () => api,
|
||||||
};
|
};
|
||||||
|
window.__TRUSTGRAPH_WORKBENCH_QA_API__ = api;
|
||||||
return [
|
return [
|
||||||
[apiFactoryAtom as Atom.Atom<unknown>, apiFactory],
|
[apiFactoryAtom as Atom.Atom<unknown>, apiFactory],
|
||||||
[settingsAtom as Atom.Atom<unknown>, qaSettings(fixture)],
|
[settingsAtom as Atom.Atom<unknown>, qaSettings(fixture)],
|
||||||
|
|
|
||||||
|
|
@ -25,19 +25,21 @@ export default defineConfig({
|
||||||
"@": path.resolve(__dirname, "./src"),
|
"@": path.resolve(__dirname, "./src"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
server: {
|
server: isWorkbenchQa
|
||||||
proxy: {
|
? {}
|
||||||
"/api/v1/rpc": {
|
: {
|
||||||
target: "ws://localhost:8088/",
|
proxy: {
|
||||||
ws: true,
|
"/api/v1/rpc": {
|
||||||
|
target: "ws://localhost:8088/",
|
||||||
|
ws: true,
|
||||||
|
},
|
||||||
|
"/api/v1": {
|
||||||
|
target: "http://localhost:8088/",
|
||||||
|
},
|
||||||
|
"/otel": {
|
||||||
|
target: "http://localhost:4328/",
|
||||||
|
rewrite: (p) => p.replace(/^\/otel/, ""),
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"/api/v1": {
|
|
||||||
target: "http://localhost:8088/",
|
|
||||||
},
|
|
||||||
"/otel": {
|
|
||||||
target: "http://localhost:4328/",
|
|
||||||
rewrite: (p) => p.replace(/^\/otel/, ""),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,8 @@ const effectClassPatterns = [
|
||||||
/\bContext\.Service\b/,
|
/\bContext\.Service\b/,
|
||||||
/\bRpc\.make\b/,
|
/\bRpc\.make\b/,
|
||||||
/\bHttpApi\.make\b/,
|
/\bHttpApi\.make\b/,
|
||||||
|
/\bAtomHttpApi\.Service\b/,
|
||||||
|
/\bAtomRpc\.Service\b/,
|
||||||
/\bEffect\.Service\b/,
|
/\bEffect\.Service\b/,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue