Something went wrong. Try again.
fork of prime agent with changes i want but are not upstreamable probqbly (and some fixes)
Something went wrong. Try again.
6.8 kB · 215 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216import * as fs from "node:fs";import * as os from "node:os";import * as path from "node:path";import { fileURLToPath } from "node:url";import { afterEach, beforeEach, describe, expect, it } from "vitest";import { discoverAndLoadExtensions, loadExtensions } from "../src/core/extensions/loader.js";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const extensionCode = ` export default function(pi) { pi.registerCommand("test", { handler: async () => {} }); }`;
const extensionCodeWithTool = (toolName: string) => ` import { Type } from "typebox"; export default function(pi) { pi.registerTool({ name: "${toolName}", label: "${toolName}", description: "Test tool", parameters: Type.Object({}), execute: async () => ({ content: [{ type: "text", text: "ok" }] }), }); }`;
describe("extensions discovery", () => { let tempDir: string; let extensionsDir: string;
beforeEach(() => { tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-ext-test-")); extensionsDir = path.join(tempDir, "extensions"); fs.mkdirSync(extensionsDir); });
afterEach(() => { fs.rmSync(tempDir, { recursive: true, force: true }); });
const write = (file: string, code = extensionCode) => { const target = path.join(extensionsDir, file); fs.mkdirSync(path.dirname(target), { recursive: true }); fs.writeFileSync(target, code); }; const manifest = (dir: string, extensions: string[]) => write(path.join(dir, "package.json"), JSON.stringify({ name: "my-package", pi: { extensions } }));
it.each([ { name: "direct .ts files", setup: () => { write("foo.ts"); write("bar.ts"); }, expected: ["bar.ts", "foo.ts"], }, { name: "direct .js files", setup: () => write("foo.js"), expected: ["foo.js"] }, { name: "subdirectory index.ts", setup: () => write("my-extension/index.ts"), expected: ["my-extension/index.ts"], }, { name: "subdirectory index.js", setup: () => write("my-extension/index.js"), expected: ["my-extension/index.js"], }, { name: "index.ts wins over index.js", setup: () => { write("my-extension/index.ts"); write("my-extension/index.js"); }, expected: ["my-extension/index.ts"], }, { name: "package.json pi field", setup: () => { write("my-package/src/main.ts"); manifest("my-package", ["./src/main.ts"]); }, expected: ["my-package/src/main.ts"], }, { name: "package.json declaring several extensions", setup: () => { write("my-package/ext1.ts"); write("my-package/ext2.ts"); manifest("my-package", ["./ext1.ts", "./ext2.ts"]); }, expected: ["my-package/ext1.ts", "my-package/ext2.ts"], }, { name: "package.json pi field wins over index.ts", setup: () => { write("my-package/index.ts"); write("my-package/custom.ts"); manifest("my-package", ["./custom.ts"]); }, expected: ["my-package/custom.ts"], }, { name: "package.json without pi field falls back to index.ts", setup: () => { write("my-package/index.ts"); write("my-package/package.json", JSON.stringify({ name: "my-package", version: "1.0.0" })); }, expected: ["my-package/index.ts"], }, { name: "package.json paths that do not exist are skipped", setup: () => { write("my-package/exists.ts"); manifest("my-package", ["./exists.ts", "./missing.ts"]); }, expected: ["my-package/exists.ts"], }, { name: "subdirectory without index or manifest", setup: () => { write("not-an-extension/helper.ts"); write("not-an-extension/utils.ts"); }, expected: [], }, { name: "no recursion beyond one level", setup: () => write("container/nested/index.ts"), expected: [] }, { name: "mixed direct files and subdirectories", setup: () => { write("direct.ts"); write("with-index/index.ts"); write("with-manifest/entry.ts"); manifest("with-manifest", ["./entry.ts"]); }, expected: ["direct.ts", "with-index/index.ts", "with-manifest/entry.ts"], }, ])("discovers $name", async ({ setup, expected }) => { setup();
const result = await discoverAndLoadExtensions([], tempDir, tempDir);
expect(result.errors).toHaveLength(0); expect(result.extensions.map((extension) => path.relative(extensionsDir, extension.path)).sort()).toEqual( expected.map((relative) => path.join(...relative.split("/"))).sort(), ); });
it.each([ { name: "code that fails to load", file: "invalid.ts", code: "this is not valid typescript export", error: undefined, }, { name: "a factory that throws", file: "throws.ts", code: 'export default function(pi) { throw new Error("Initialization failed!"); }', error: "Initialization failed!", }, { name: "a missing default export", file: "no-default.ts", code: "export function notDefault(pi) {}", error: "does not export a valid factory function", }, ])("reports $name as a load error", async ({ file, code, error }) => { write(file, code);
const result = await discoverAndLoadExtensions([], tempDir, tempDir);
expect(result.extensions).toHaveLength(0); expect(result.errors).toHaveLength(1); expect(result.errors[0].path).toContain(file); if (error) expect(result.errors[0].error).toContain(error); });
it("loads explicitly configured paths outside the extensions directory", async () => { const customPath = path.join(tempDir, "custom-location", "my-ext.ts"); fs.mkdirSync(path.dirname(customPath), { recursive: true }); fs.writeFileSync(customPath, extensionCode);
const result = await discoverAndLoadExtensions([customPath], tempDir, tempDir);
expect(result.errors).toHaveLength(0); expect(result.extensions.map((extension) => extension.path)).toEqual([customPath]); });
it("resolves dependencies from an extension's own node_modules", async () => { const extPath = path.resolve(__dirname, "../examples/extensions/with-deps");
const result = await discoverAndLoadExtensions([extPath], tempDir, tempDir);
expect(result.errors).toHaveLength(0); expect(result.extensions).toHaveLength(1); expect(result.extensions[0].tools.has("parse_duration")).toBe(true); });
it.each([ { name: "loads only the explicit path", explicit: true }, { name: "loads nothing without paths", explicit: false }, ])("loadExtensions $name", async ({ explicit }) => { write("discovered.ts", extensionCodeWithTool("discovered")); const explicitPath = path.join(tempDir, "explicit.ts"); fs.writeFileSync(explicitPath, extensionCodeWithTool("explicit"));
const result = await loadExtensions(explicit ? [explicitPath] : [], tempDir);
expect(result.errors).toHaveLength(0); expect(result.extensions.map((extension) => extension.path)).toEqual(explicit ? [explicitPath] : []); });});