mirror of
https://github.com/trustgraph-ai/trustgraph.git
synced 2026-07-02 02:58:10 +02:00
Enforce strict Effect tsgo migrations
This commit is contained in:
parent
64fb23e7d0
commit
f6878d4dd7
49 changed files with 5547 additions and 3250 deletions
|
|
@ -75,7 +75,7 @@ const mcpToolError = (
|
|||
cause: unknown,
|
||||
tool?: string,
|
||||
): McpToolError =>
|
||||
new McpToolError({
|
||||
McpToolError.make({
|
||||
operation,
|
||||
message: errorMessage(cause),
|
||||
...(tool === undefined ? {} : { tool }),
|
||||
|
|
@ -166,12 +166,9 @@ const invokeConfiguredTool = Effect.fn("McpToolRuntime.invokeTool")(function* (
|
|||
|
||||
const result = yield* Effect.acquireUseRelease(
|
||||
Effect.tryPromise({
|
||||
try: async () => {
|
||||
await client.connect(transport as unknown as Parameters<Client["connect"]>[0]);
|
||||
return client;
|
||||
},
|
||||
try: () => client.connect(transport as unknown as Parameters<Client["connect"]>[0]),
|
||||
catch: (cause) => mcpToolError("connect", cause, name),
|
||||
}),
|
||||
}).pipe(Effect.as(client)),
|
||||
(connectedClient) =>
|
||||
Effect.tryPromise({
|
||||
try: () =>
|
||||
|
|
@ -318,6 +315,6 @@ export const program = makeFlowProcessorProgram<ProcessorConfig, never, McpToolR
|
|||
layer: () => McpToolRuntimeLive,
|
||||
});
|
||||
|
||||
export async function run(): Promise<void> {
|
||||
await Effect.runPromise(program);
|
||||
export function run(): Promise<void> {
|
||||
return Effect.runPromise(program);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -132,79 +132,80 @@ const toPromiseRequestor = <TReq, TRes>(
|
|||
const buildConfiguredTool = (
|
||||
toolId: string,
|
||||
data: ToolConfigEntry,
|
||||
): AgentTool | null => {
|
||||
const implType = data.type ?? "";
|
||||
const name = data.name ?? "";
|
||||
const description = data.description ?? "";
|
||||
const config = { ...data } as Record<string, unknown>;
|
||||
): Effect.Effect<AgentTool | null> =>
|
||||
Effect.gen(function* () {
|
||||
const implType = data.type ?? "";
|
||||
const name = data.name ?? "";
|
||||
const description = data.description ?? "";
|
||||
const config = { ...data } as Record<string, unknown>;
|
||||
|
||||
if (name.length === 0) {
|
||||
console.warn(`[AgentService] Skipping tool with no name: ${toolId}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (implType) {
|
||||
case "knowledge-query":
|
||||
return {
|
||||
name,
|
||||
description:
|
||||
description.length > 0
|
||||
? description
|
||||
: "Query the knowledge graph for information about entities and their relationships.",
|
||||
args: [{ name: "question", type: "string", description: "The question to ask" }],
|
||||
config,
|
||||
execute: async () => "",
|
||||
};
|
||||
|
||||
case "document-query":
|
||||
return {
|
||||
name,
|
||||
description:
|
||||
description.length > 0
|
||||
? description
|
||||
: "Search documents for relevant information.",
|
||||
args: [{ name: "question", type: "string", description: "The question to search for" }],
|
||||
config,
|
||||
execute: async () => "",
|
||||
};
|
||||
|
||||
case "triples-query":
|
||||
return {
|
||||
name,
|
||||
description:
|
||||
description.length > 0
|
||||
? description
|
||||
: "Query for specific triples in the knowledge graph.",
|
||||
args: [
|
||||
{ name: "subject", type: "string", description: "Subject entity (optional)" },
|
||||
{ name: "predicate", type: "string", description: "Predicate/relationship (optional)" },
|
||||
{ name: "object", type: "string", description: "Object entity (optional)" },
|
||||
],
|
||||
config,
|
||||
execute: async () => "",
|
||||
};
|
||||
|
||||
case "mcp-tool": {
|
||||
const args: ToolArg[] = (data.arguments ?? []).map((arg) => ({
|
||||
name: arg.name ?? "",
|
||||
type: arg.type ?? "string",
|
||||
description: arg.description ?? "",
|
||||
}));
|
||||
|
||||
return {
|
||||
name,
|
||||
description,
|
||||
args,
|
||||
config,
|
||||
execute: async () => "",
|
||||
};
|
||||
if (name.length === 0) {
|
||||
yield* Effect.logWarning(`[AgentService] Skipping tool with no name: ${toolId}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
default:
|
||||
console.warn(`[AgentService] Unknown tool type "${implType}" for ${name}`);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
switch (implType) {
|
||||
case "knowledge-query":
|
||||
return {
|
||||
name,
|
||||
description:
|
||||
description.length > 0
|
||||
? description
|
||||
: "Query the knowledge graph for information about entities and their relationships.",
|
||||
args: [{ name: "question", type: "string", description: "The question to ask" }],
|
||||
config,
|
||||
execute: () => Promise.resolve(""),
|
||||
};
|
||||
|
||||
case "document-query":
|
||||
return {
|
||||
name,
|
||||
description:
|
||||
description.length > 0
|
||||
? description
|
||||
: "Search documents for relevant information.",
|
||||
args: [{ name: "question", type: "string", description: "The question to search for" }],
|
||||
config,
|
||||
execute: () => Promise.resolve(""),
|
||||
};
|
||||
|
||||
case "triples-query":
|
||||
return {
|
||||
name,
|
||||
description:
|
||||
description.length > 0
|
||||
? description
|
||||
: "Query for specific triples in the knowledge graph.",
|
||||
args: [
|
||||
{ name: "subject", type: "string", description: "Subject entity (optional)" },
|
||||
{ name: "predicate", type: "string", description: "Predicate/relationship (optional)" },
|
||||
{ name: "object", type: "string", description: "Object entity (optional)" },
|
||||
],
|
||||
config,
|
||||
execute: () => Promise.resolve(""),
|
||||
};
|
||||
|
||||
case "mcp-tool": {
|
||||
const args: ToolArg[] = (data.arguments ?? []).map((arg) => ({
|
||||
name: arg.name ?? "",
|
||||
type: arg.type ?? "string",
|
||||
description: arg.description ?? "",
|
||||
}));
|
||||
|
||||
return {
|
||||
name,
|
||||
description,
|
||||
args,
|
||||
config,
|
||||
execute: () => Promise.resolve(""),
|
||||
};
|
||||
}
|
||||
|
||||
default:
|
||||
yield* Effect.logWarning(`[AgentService] Unknown tool type "${implType}" for ${name}`);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
const loadConfiguredTools = Effect.fn("AgentRuntime.loadConfiguredTools")(function* (
|
||||
config: Record<string, unknown>,
|
||||
|
|
@ -231,7 +232,7 @@ const loadConfiguredTools = Effect.fn("AgentRuntime.loadConfiguredTools")(functi
|
|||
continue;
|
||||
}
|
||||
|
||||
const tool = buildConfiguredTool(toolId, decoded.value);
|
||||
const tool = yield* buildConfiguredTool(toolId, decoded.value);
|
||||
if (tool === null) continue;
|
||||
|
||||
tools.push(tool);
|
||||
|
|
@ -348,7 +349,7 @@ const executeTool = (
|
|||
): Effect.Effect<string> =>
|
||||
Effect.tryPromise({
|
||||
try: () => tool.execute(input),
|
||||
catch: (cause) => new AgentToolExecutionError({ message: errorMessage(cause) }),
|
||||
catch: (cause) => AgentToolExecutionError.make({ message: errorMessage(cause) }),
|
||||
}).pipe(
|
||||
Effect.catch((error: AgentToolExecutionError) =>
|
||||
Effect.succeed(`Error executing tool: ${error.message}`),
|
||||
|
|
@ -473,12 +474,12 @@ const onAgentRequest = Effect.fn("AgentService.onRequest")(function* (
|
|||
}).pipe(
|
||||
Effect.catch((error: unknown) =>
|
||||
Effect.logError(`[AgentService] Error processing request ${requestId}`, {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: errorMessage(error),
|
||||
}).pipe(
|
||||
Effect.flatMap(() =>
|
||||
responseProducer.send(requestId, {
|
||||
chunk_type: "error",
|
||||
content: `Agent error: ${error instanceof Error ? error.message : String(error)}`,
|
||||
content: `Agent error: ${errorMessage(error)}`,
|
||||
end_of_message: true,
|
||||
end_of_dialog: true,
|
||||
}),
|
||||
|
|
@ -538,7 +539,7 @@ export function makeAgentService(config: ProcessorConfig): AgentService {
|
|||
Effect.provideService(AgentRuntime, runtime),
|
||||
)),
|
||||
);
|
||||
console.log("[AgentService] Service initialized");
|
||||
Effect.runSync(Effect.log("[AgentService] Service initialized"));
|
||||
return service;
|
||||
}
|
||||
|
||||
|
|
@ -629,6 +630,6 @@ export const program = makeFlowProcessorProgram<ProcessorConfig, never, AgentRun
|
|||
layer: () => AgentRuntimeLive,
|
||||
});
|
||||
|
||||
export async function run(): Promise<void> {
|
||||
await Effect.runPromise(program);
|
||||
export function run(): Promise<void> {
|
||||
return Effect.runPromise(program);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,9 +18,14 @@ import type {
|
|||
Term,
|
||||
Triple,
|
||||
} from "@trustgraph/base";
|
||||
import { Effect } from "effect";
|
||||
import * as O from "effect/Option";
|
||||
import * as S from "effect/Schema";
|
||||
|
||||
import type { AgentTool, ToolArg } from "./types.js";
|
||||
|
||||
const decodeJsonUnknown = S.decodeUnknownOption(S.UnknownFromJsonString);
|
||||
|
||||
/**
|
||||
* Format a Term to a human-readable string.
|
||||
*/
|
||||
|
|
@ -41,17 +46,15 @@ function termToString(term: Term): string {
|
|||
* Parse tool input -- accepts either raw JSON or a plain string question.
|
||||
*/
|
||||
function parseQuestion(input: string): string {
|
||||
try {
|
||||
const parsed = JSON.parse(input) as Record<string, unknown>;
|
||||
if (typeof parsed === "object" && parsed !== null && "question" in parsed) {
|
||||
return String(parsed.question);
|
||||
}
|
||||
// If it's a string JSON value, use it directly
|
||||
if (typeof parsed === "string") {
|
||||
return parsed;
|
||||
}
|
||||
} catch {
|
||||
// Not valid JSON -- treat as plain text
|
||||
const decoded = decodeJsonUnknown(input);
|
||||
if (O.isNone(decoded)) return input;
|
||||
|
||||
const parsed = decoded.value;
|
||||
if (typeof parsed === "object" && parsed !== null && "question" in parsed) {
|
||||
return String(parsed.question);
|
||||
}
|
||||
if (typeof parsed === "string") {
|
||||
return parsed;
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
|
@ -83,15 +86,15 @@ export function createKnowledgeQueryTool(
|
|||
description: "The question to ask the knowledge graph",
|
||||
},
|
||||
],
|
||||
async execute(input: string): Promise<string> {
|
||||
execute: (input: string): Promise<string> => Effect.runPromise(Effect.gen(function* () {
|
||||
const question = parseQuestion(input);
|
||||
console.log(`[KnowledgeQuery] Executing: "${question.slice(0, 60)}..." collection=${collection}`);
|
||||
yield* Effect.log(`[KnowledgeQuery] Executing: "${question.slice(0, 60)}..." collection=${collection}`);
|
||||
const request: GraphRagRequest = {
|
||||
query: question,
|
||||
...(collection !== undefined ? { collection } : {}),
|
||||
};
|
||||
const res = await client.request(request);
|
||||
console.log(`[KnowledgeQuery] Response (${res.response?.length ?? 0} chars): ${res.error !== undefined ? `ERROR: ${res.error.message}` : `${res.response?.slice(0, 300)}...`}`);
|
||||
const res = yield* Effect.tryPromise(() => client.request(request));
|
||||
yield* Effect.log(`[KnowledgeQuery] Response (${res.response?.length ?? 0} chars): ${res.error !== undefined ? `ERROR: ${res.error.message}` : `${res.response?.slice(0, 300)}...`}`);
|
||||
|
||||
// Extract explain data if embedded in the response
|
||||
const rawRes = res as Record<string, unknown>;
|
||||
|
|
@ -100,15 +103,15 @@ export function createKnowledgeQueryTool(
|
|||
rawRes.explain_triples !== undefined &&
|
||||
onExplain !== undefined
|
||||
) {
|
||||
onExplain({
|
||||
yield* Effect.sync(() => onExplain({
|
||||
explainId: (rawRes.explain_id as string) ?? "",
|
||||
triples: rawRes.explain_triples as Triple[],
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
if (res.error !== undefined) return `Error: ${res.error.message}`;
|
||||
return res.response;
|
||||
},
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -130,16 +133,16 @@ export function createDocumentQueryTool(
|
|||
description: "The question to search documents for",
|
||||
},
|
||||
],
|
||||
async execute(input: string): Promise<string> {
|
||||
execute: (input: string): Promise<string> => Effect.runPromise(Effect.gen(function* () {
|
||||
const question = parseQuestion(input);
|
||||
const request: DocumentRagRequest = {
|
||||
query: question,
|
||||
...(collection !== undefined ? { collection } : {}),
|
||||
};
|
||||
const res = await client.request(request);
|
||||
const res = yield* Effect.tryPromise(() => client.request(request));
|
||||
if (res.error !== undefined) return `Error: ${res.error.message}`;
|
||||
return res.response;
|
||||
},
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -152,39 +155,42 @@ function parseTriplesInput(input: string): {
|
|||
o?: Term;
|
||||
limit?: number;
|
||||
} {
|
||||
try {
|
||||
const parsed = JSON.parse(input) as Record<string, unknown>;
|
||||
|
||||
const toTerm = (val: unknown): Term | undefined => {
|
||||
if (typeof val === "string") {
|
||||
return { type: "LITERAL", value: val };
|
||||
}
|
||||
if (typeof val === "object" && val !== null && "type" in val) {
|
||||
return val as Term;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const result: {
|
||||
s?: Term;
|
||||
p?: Term;
|
||||
o?: Term;
|
||||
limit?: number;
|
||||
} = {};
|
||||
const s = toTerm(parsed.subject ?? parsed.s);
|
||||
const p = toTerm(parsed.predicate ?? parsed.p);
|
||||
const o = toTerm(parsed.object ?? parsed.o);
|
||||
if (s !== undefined) result.s = s;
|
||||
if (p !== undefined) result.p = p;
|
||||
if (o !== undefined) result.o = o;
|
||||
if (typeof parsed.limit === "number") result.limit = parsed.limit;
|
||||
return result;
|
||||
} catch {
|
||||
// If not valid JSON, treat as a subject search
|
||||
const decoded = decodeJsonUnknown(input);
|
||||
if (
|
||||
O.isNone(decoded) ||
|
||||
typeof decoded.value !== "object" ||
|
||||
decoded.value === null
|
||||
) {
|
||||
return {
|
||||
s: { type: "LITERAL", value: input },
|
||||
};
|
||||
}
|
||||
|
||||
const parsed = decoded.value as Record<string, unknown>;
|
||||
const toTerm = (val: unknown): Term | undefined => {
|
||||
if (typeof val === "string") {
|
||||
return { type: "LITERAL", value: val };
|
||||
}
|
||||
if (typeof val === "object" && val !== null && "type" in val) {
|
||||
return val as Term;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const result: {
|
||||
s?: Term;
|
||||
p?: Term;
|
||||
o?: Term;
|
||||
limit?: number;
|
||||
} = {};
|
||||
const s = toTerm(parsed.subject ?? parsed.s);
|
||||
const p = toTerm(parsed.predicate ?? parsed.p);
|
||||
const o = toTerm(parsed.object ?? parsed.o);
|
||||
if (s !== undefined) result.s = s;
|
||||
if (p !== undefined) result.p = p;
|
||||
if (o !== undefined) result.o = o;
|
||||
if (typeof parsed.limit === "number") result.limit = parsed.limit;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -216,7 +222,7 @@ export function createTriplesQueryTool(
|
|||
description: "The object entity to search for (optional)",
|
||||
},
|
||||
],
|
||||
async execute(input: string): Promise<string> {
|
||||
execute: (input: string): Promise<string> => Effect.runPromise(Effect.gen(function* () {
|
||||
const { s, p, o, limit } = parseTriplesInput(input);
|
||||
const request: TriplesQueryRequest = {
|
||||
limit: limit ?? 20,
|
||||
|
|
@ -225,7 +231,7 @@ export function createTriplesQueryTool(
|
|||
...(o !== undefined ? { o } : {}),
|
||||
...(collection !== undefined ? { collection } : {}),
|
||||
};
|
||||
const res = await client.request(request);
|
||||
const res = yield* Effect.tryPromise(() => client.request(request));
|
||||
|
||||
if (res.error !== undefined) return `Error: ${res.error.message}`;
|
||||
|
||||
|
|
@ -238,7 +244,7 @@ export function createTriplesQueryTool(
|
|||
`(${termToString(t.s)}) -[${termToString(t.p)}]-> (${termToString(t.o)})`,
|
||||
);
|
||||
return lines.join("\n");
|
||||
},
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -258,12 +264,12 @@ export function createMcpTool(
|
|||
name: toolName,
|
||||
description,
|
||||
args,
|
||||
async execute(input: string): Promise<string> {
|
||||
const res = await client.request({ name: toolName, parameters: input });
|
||||
execute: (input: string): Promise<string> => Effect.runPromise(Effect.gen(function* () {
|
||||
const res = yield* Effect.tryPromise(() => client.request({ name: toolName, parameters: input }));
|
||||
if (res.error !== undefined) return `Error: ${res.error.message}`;
|
||||
if (res.text !== undefined) return res.text;
|
||||
if (res.object !== undefined) return res.object;
|
||||
return "No content";
|
||||
},
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue