mirror of
https://github.com/trustgraph-ai/trustgraph.git
synced 2026-07-01 09:29:38 +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
|
|
@ -13,18 +13,9 @@
|
|||
"dependencies": {
|
||||
"@trustgraph/base": "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/tsgo": "0.14.0",
|
||||
"@effect/vitest": "4.0.0-beta.78"
|
||||
"@effect/platform-node": "4.0.0-beta.78",
|
||||
"effect": "4.0.0-beta.78"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@effect/vitest": "4.0.0-beta.78",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { describe, expect, it } from "@effect/vitest";
|
||||
import type { BaseApi } from "@trustgraph/client";
|
||||
import { Effect, Layer } from "effect";
|
||||
import { DispatchStreamChunk, type BaseApi, type TrustGraphGatewayClient } from "@trustgraph/client";
|
||||
import { Effect, Layer, Stream } from "effect";
|
||||
import * as S from "effect/Schema";
|
||||
import { McpServer } from "effect/unstable/ai";
|
||||
import * as McpSchema from "effect/unstable/ai/McpSchema";
|
||||
|
|
@ -13,6 +13,7 @@ import {
|
|||
TrustGraphMcpConfig,
|
||||
TrustGraphMcpToolkit,
|
||||
TrustGraphMcpToolkitLive,
|
||||
TrustGraphGateway,
|
||||
TrustGraphSocket,
|
||||
} from "../server-effect.js";
|
||||
|
||||
|
|
@ -124,6 +125,29 @@ const makeFakeSocket = (
|
|||
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({
|
||||
gatewayUrl: "ws://localhost:8088/api/v1/rpc",
|
||||
user: "mcp-test",
|
||||
|
|
@ -147,8 +171,10 @@ const makeNativeTestClientEffect = Effect.fn("makeNativeTestClient")(function*(
|
|||
textCompletion: options.textCompletion,
|
||||
graphRag: options.graphRag,
|
||||
});
|
||||
const gateway = makeFakeGateway();
|
||||
const serverLayer = McpServer.toolkit(TrustGraphMcpToolkit).pipe(
|
||||
Layer.provide(TrustGraphMcpToolkitLive),
|
||||
Layer.provide(Layer.succeed(TrustGraphGateway, TrustGraphGateway.of(gateway))),
|
||||
Layer.provide(Layer.succeed(TrustGraphSocket, TrustGraphSocket.of(socket))),
|
||||
Layer.provide(Layer.succeed(TrustGraphMcpConfig, testConfig)),
|
||||
Layer.provide(McpServer.layerHttp({
|
||||
|
|
|
|||
|
|
@ -1,6 +1,12 @@
|
|||
import {BunHttpServer, BunRuntime} from "@effect/platform-bun";
|
||||
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 * as O from "effect/Option";
|
||||
import * as Predicate from "effect/Predicate";
|
||||
|
|
@ -1223,6 +1229,12 @@ export interface TrustGraphMcpConfigShape {
|
|||
const readNonEmpty = (value: string | undefined): string | 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 => {
|
||||
if (raw === undefined) {
|
||||
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 => {
|
||||
if (Predicate.isError(cause) && cause.message.length > 0) {
|
||||
return cause.message
|
||||
|
|
@ -1296,6 +1321,25 @@ const toErrorMessage = (cause: unknown): string => {
|
|||
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 decodeJsonArray = S.decodeUnknownEffect(S.Array(S.Json))
|
||||
|
||||
|
|
@ -1316,10 +1360,59 @@ const decodeJsonArrayOrFail = <E>(
|
|||
const asIriTerm = (value: string | undefined): ClientTerm | 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(
|
||||
Effect.gen(function*() {
|
||||
const config = yield* TrustGraphMcpConfig
|
||||
const socket = yield* TrustGraphSocket
|
||||
const gateway = yield* TrustGraphGateway
|
||||
|
||||
return TrustGraphMcpToolkit.of({
|
||||
text_completion: ({system, prompt}) =>
|
||||
|
|
@ -1354,22 +1447,7 @@ export const TrustGraphMcpToolkitLive = TrustGraphMcpToolkit.toLayer(
|
|||
Effect.map((text) => DocumentRagSuccess.make({text})),
|
||||
),
|
||||
|
||||
agent: ({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)}))),
|
||||
)
|
||||
}),
|
||||
agent: ({question}) => runAgentTool(gateway, config, question),
|
||||
|
||||
embeddings: ({text}) =>
|
||||
Effect.tryPromise({
|
||||
|
|
@ -1694,6 +1772,7 @@ const makeTrustGraphMcpHttpLayerFromConfig = (
|
|||
version: config.version,
|
||||
path: config.mcpPath,
|
||||
})),
|
||||
Layer.provide(TrustGraphGateway.layer),
|
||||
Layer.provide(TrustGraphSocket.layer),
|
||||
Layer.provide(Layer.succeed(TrustGraphMcpConfig, TrustGraphMcpConfig.of(config))),
|
||||
)
|
||||
|
|
@ -1713,6 +1792,7 @@ const makeTrustGraphMcpStdioLayerFromConfig = (
|
|||
version: config.version,
|
||||
})),
|
||||
Layer.provide(NodeStdio.layer),
|
||||
Layer.provide(TrustGraphGateway.layer),
|
||||
Layer.provide(TrustGraphSocket.layer),
|
||||
Layer.provide(Layer.succeed(TrustGraphMcpConfig, TrustGraphMcpConfig.of(config))),
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue