mesh-tools — the tool runtime and AMQP broker binding
The per-node process that makes a module's tools serve on the mesh: - broker-amqp.ts: a concrete AMQP implementation of the sdk's Broker contract (request/reply over a reply queue + correlation id, publish/ subscribe over a topic exchange). Kept here, not in the sdk, so a broker-client change never rebuilds a module (ADR 0044). - runtime.ts: bind the broker, import the assigned modules' tool entrypoints (each registers as it loads), serveTools. The thin wrapper. - main.ts: the entrypoint, configured by MESH_BROKER_URL + MESH_TOOL_MODULES. - Dockerfile: the container a node runs it as. Verified over a REAL broker: the test spins LavinMQ (the mesh's broker), the runtime serves a registered tool, a separate connection invokes it by name over AMQP and gets the result, and an unknown tool is refused over the wire. So 'modules can serve' is now running-on-the-mesh, not just proven in a mock. Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { registerModuleTools, resetTools } from "@novox/mesh-sdk/tools";
|
||||
import { connectAmqp } from "../dist/broker-amqp.js";
|
||||
import { runTools } from "../dist/runtime.js";
|
||||
|
||||
// Requires a real broker at $MESH_BROKER_URL. The test harness spins LavinMQ (the mesh's broker)
|
||||
// and points this at it; skipped if it is not set, never failed for the environment.
|
||||
const url = process.env.MESH_BROKER_URL;
|
||||
|
||||
test("a module's tool serves and is invoked over a real AMQP broker", { skip: !url }, async () => {
|
||||
resetTools();
|
||||
|
||||
// A module registers a real tool, exactly as umami does.
|
||||
registerModuleTools("demo", () => [
|
||||
{
|
||||
name: "greet",
|
||||
description: "return a greeting",
|
||||
input: { who: { type: "string" } },
|
||||
run: async (args) => ({ hello: String(args.who), from: "the tool runtime" }),
|
||||
},
|
||||
]);
|
||||
|
||||
// The runtime binds the broker and serves the module (no module entrypoints to import here — the
|
||||
// tool is registered in-process — but this is the exact runtime that serves on a node).
|
||||
const serverBroker = await connectAmqp(url!);
|
||||
const stop = await runTools({ broker: serverBroker, moduleEntrypoints: [] });
|
||||
|
||||
// A separate connection — a caller, like mesh-control's command API — invokes over the broker.
|
||||
const caller = await connectAmqp(url!);
|
||||
const result = await caller.request<{ tool: string; args: Record<string, unknown> }, { hello: string }>(
|
||||
"tools.invoke",
|
||||
{ tool: "greet", args: { who: "mesh" } },
|
||||
);
|
||||
assert.equal(result.hello, "mesh");
|
||||
|
||||
// An unknown tool is refused over the wire, not silently dropped.
|
||||
await assert.rejects(caller.request("tools.invoke", { tool: "nope", args: {} }), /no such tool/);
|
||||
|
||||
stop();
|
||||
await caller.close();
|
||||
await serverBroker.close();
|
||||
});
|
||||
Reference in New Issue
Block a user