From 4a957f27963dd4540385ed2d55ef6326c997d26f Mon Sep 17 00:00:00 2001 From: Benjamin Hackl Date: Tue, 3 Nov 2026 21:18:44 +0100 Subject: [PATCH] Fix loading when installed from git: import openAIResponsesApi from pi-ai/compat pi resolves only a fixed set of its own module paths for installed extensions; pi-ai/api/* is not among them. Add a test that keeps runtime imports within that set. --- src/provider.ts | 4 +++- test/imports.test.ts | 31 +++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 test/imports.test.ts diff --git a/src/provider.ts b/src/provider.ts index 7226367..7ff187e 100644 --- a/src/provider.ts +++ b/src/provider.ts @@ -3,7 +3,9 @@ import { type Model, type Provider, } from "@earendil-works/pi-ai"; -import { openAIResponsesApi } from "@earendil-works/pi-ai/api/openai-responses.lazy"; +// pi resolves only a fixed set of its own module paths for extensions; anything +// else (e.g. pi-ai/api/*) fails in a git/npm install. See test/imports.test.ts. +import { openAIResponsesApi } from "@earendil-works/pi-ai/compat"; import { getBuiltinModels } from "@earendil-works/pi-ai/providers/all"; export const PROVIDER_ID = "unigpt"; diff --git a/test/imports.test.ts b/test/imports.test.ts new file mode 100644 index 0000000..bbea4d0 --- /dev/null +++ b/test/imports.test.ts @@ -0,0 +1,31 @@ +import assert from "node:assert/strict"; +import { readdirSync, readFileSync } from "node:fs"; +import { test } from "node:test"; + +// Installed packages get no copy of pi in node_modules (pi is a peer +// dependency). pi maps only these specifiers to its own modules when loading +// an extension; see "Available Imports" in pi's docs/extensions.md. +const HOST_PROVIDED = new Set([ + "@earendil-works/pi-coding-agent", + "@earendil-works/pi-agent-core", + "@earendil-works/pi-tui", + "@earendil-works/pi-ai", + "@earendil-works/pi-ai/compat", + "@earendil-works/pi-ai/models", + "@earendil-works/pi-ai/oauth", + "@earendil-works/pi-ai/providers/all", + "typebox", + "typebox/compile", + "typebox/value", +]); + +test("runtime source imports only modules that pi provides to extensions", () => { + const sourceDir = new URL("../src/", import.meta.url); + for (const file of readdirSync(sourceDir).filter((name) => name.endsWith(".ts"))) { + const source = readFileSync(new URL(file, sourceDir), "utf8"); + for (const [, specifier] of source.matchAll(/^\s*import\s[^;]*?from\s+"([^"]+)"/gms)) { + if (specifier.startsWith(".") || specifier.startsWith("node:")) continue; + assert.ok(HOST_PROVIDED.has(specifier), `src/${file} imports "${specifier}", which pi does not provide to installed extensions`); + } + } +});