pi-mono/packages/durable/test/examples/09-registry-reload.ts
Mario Zechner 6536f80129 feat(ai,agent,coding-agent): validate tool arguments with pi-schema, drop TypeBox
pi-schema: opt-in coercion, compile(schema, { coerce: true }) with coerce() and
parse(), the superset of TypeBox's Value.Convert rules and pi-ai's old coercion;
unresolvedRefs option; url and json-pointer-uri-fragment formats.
pi-ai: Tool takes any JSON Schema; validation through pi-schema; Type, Static
and TSchema are no longer exported (breaking). pi-agent-core and pi-durable:
tools on pi-schema, no TypeBox dependency. Coding agent: extensions keep Type,
Static, TSchema and StringEnum through an extension view of pi-ai; built-in
tools stay on TypeBox.
2026-11-01 10:08:42 +00:00

49 lines
2.1 KiB
TypeScript

// Reload extension code through the registry.
// Run from packages/durable:
// node --conditions=source --experimental-strip-types test/examples/09-registry-reload.ts
import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
import { createModels } from "@earendil-works/pi-ai/models";
import { obj, str } from "@earendil-works/pi-schema";
import { createRegistry, defineExtension, defineTool, Harness, MemoryStorage, wrapTool } from "../../src/index.ts";
const context = BACKGROUND_CONTEXT;
function exampleTool(name: string, description: string) {
return defineTool({
name,
description,
parameters: obj({ path: str() }),
execute: async (args) => ({ output: [{ type: "text", text: `${name} ${args.path}` }] }),
});
}
const read = exampleTool("read", "Read a file");
const registry = createRegistry();
registry.install(defineExtension({ name: "files", tools: [read, exampleTool("grep", "Search files")] }));
const harness = await Harness.open(new MemoryStorage(), { models: createModels(), registry }, context);
const root = await harness.root(context);
const tools = async () => (await root.agent(context)).tools.map((tool) => `${tool.name}: ${tool.description}`);
// Reloading is installing a new object with the same name: it replaces the old
// one in place, in one publication, so no conversation ever sees it missing.
// A task phase that already started keeps the snapshot it took; the next phase
// uses the new code.
registry.install(defineExtension({ name: "files", tools: [read, exampleTool("grep", "Search files, faster")] }));
console.log("after reload:", await tools());
// Another extension may wrap a tool by name. The wrap applies where the
// wrapping extension is selected, and survives reloads of the wrapped tool.
const Audit = defineExtension({
name: "audit",
wraps: [wrapTool(read, (tool) => ({ ...tool, description: `${tool.description} (audited)` }))],
});
registry.install(Audit);
console.log("with audit:", await tools());
// Uninstall removes an extension by name; conversations that select it stop
// getting its code.
registry.uninstall(Audit);
console.log("after uninstall:", await tools());
await harness.close(context);