From 22dc6c26f3c530e11b8cc40443ecdce2b3c84116 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Hou=C5=A1ka?= Date: Tue, 20 Oct 2026 21:29:51 +0200 Subject: [PATCH] fix(coding-agent): expose @earendil-works/pi-ai/models in the extension loader Extensions importing the public /models subpath (e.g. clampThinkingLevel) failed with "Cannot find module" because the loader aliased/virtualized only the pi-ai root, /compat, /oauth, and /providers/all entrypoints. Add /models to the jiti aliases and the embedded virtual module map, plus a regression test importing it without a local pi-ai install. Closes #11156 Co-authored-by: Dante-dan --- packages/coding-agent/CHANGELOG.md | 1 + .../coding-agent/src/core/extensions/loader.ts | 3 +++ .../src/core/extensions/virtual-modules.ts | 3 +++ .../11156-pi-ai-models-virtual-module.test.ts | 17 +++++++++++++++++ vitest.base.ts | 2 ++ 5 files changed, 26 insertions(+) create mode 100644 packages/coding-agent/test/suite/regressions/11156-pi-ai-models-virtual-module.test.ts diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index cbf3ab469..b15bdbb56 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -20,6 +20,7 @@ - Fixed concurrent extension `select`/`confirm`/`input`/`editor` dialogs and inline `custom()` components in interactive mode: a second dialog replaced the first and the first promise never settled; closing the visible dialog now shows the previous one again ([#11166](https://github.com/earendil-works/pi/issues/11166), [#9909](https://github.com/earendil-works/pi/issues/9909)) - Fixed pasting in the shell hanging after copying text in pi and suspending it with Ctrl+Z on Linux: the background `wl-copy`/`xclip`/`xsel` process that keeps serving the clipboard was suspended along with pi ([#11179](https://github.com/earendil-works/pi/issues/11179)) - Fixed codemode scripts failing immediately when both codemode workers are busy, for example with parallel codemode calls or in-process subagents. Scripts now wait up to 60 seconds for a free worker, and `PI_CODEMODE_MAX_WORKERS` raises the limit of two workers ([#11184](https://github.com/earendil-works/pi/issues/11184)) +- Fixed extensions failing to load when importing `@earendil-works/pi-ai/models` (e.g. for `clampThinkingLevel`) by exposing the subpath through the extension loader aliases and virtual modules ([#11156](https://github.com/earendil-works/pi/issues/11156)) ## [1.3.0] - 2026-10-15 diff --git a/packages/coding-agent/src/core/extensions/loader.ts b/packages/coding-agent/src/core/extensions/loader.ts index bee134d35..cbc1100fc 100644 --- a/packages/coding-agent/src/core/extensions/loader.ts +++ b/packages/coding-agent/src/core/extensions/loader.ts @@ -92,6 +92,7 @@ function getAliases(): Record { // superset of the core entrypoint): existing extensions using the old // global API keep working at runtime until compat is removed. const piAiCompatEntry = resolveWorkspaceOrImport("ai/dist/compat.js", "@earendil-works/pi-ai/compat"); + const piAiModelsEntry = resolveWorkspaceOrImport("ai/dist/models.js", "@earendil-works/pi-ai/models"); const piAiOauthEntry = resolveWorkspaceOrImport("ai/dist/oauth.js", "@earendil-works/pi-ai/oauth"); const piAiProvidersEntry = resolveWorkspaceOrImport( "ai/dist/providers/all.js", @@ -104,6 +105,7 @@ function getAliases(): Record { "@earendil-works/pi-tui": piTuiEntry, "@earendil-works/pi-ai/providers/all": piAiProvidersEntry, "@earendil-works/pi-ai/compat": piAiCompatEntry, + "@earendil-works/pi-ai/models": piAiModelsEntry, "@earendil-works/pi-ai/oauth": piAiOauthEntry, "@earendil-works/pi-ai": piAiCompatEntry, "@mariozechner/pi-coding-agent": piCodingAgentEntry, @@ -111,6 +113,7 @@ function getAliases(): Record { "@mariozechner/pi-tui": piTuiEntry, "@mariozechner/pi-ai/providers/all": piAiProvidersEntry, "@mariozechner/pi-ai/compat": piAiCompatEntry, + "@mariozechner/pi-ai/models": piAiModelsEntry, "@mariozechner/pi-ai/oauth": piAiOauthEntry, "@mariozechner/pi-ai": piAiCompatEntry, typebox: typeboxEntry, diff --git a/packages/coding-agent/src/core/extensions/virtual-modules.ts b/packages/coding-agent/src/core/extensions/virtual-modules.ts index 66a71f695..4b8449338 100644 --- a/packages/coding-agent/src/core/extensions/virtual-modules.ts +++ b/packages/coding-agent/src/core/extensions/virtual-modules.ts @@ -1,5 +1,6 @@ import * as bundledPiAgentCore from "@earendil-works/pi-agent-core"; import * as bundledPiAiCompat from "@earendil-works/pi-ai/compat"; +import * as bundledPiAiModels from "@earendil-works/pi-ai/models"; import * as bundledPiAiOauth from "@earendil-works/pi-ai/oauth"; import * as bundledPiAiProviders from "@earendil-works/pi-ai/providers/all"; import * as bundledPiTui from "@earendil-works/pi-tui"; @@ -25,6 +26,7 @@ export const VIRTUAL_MODULES: Record = { // global API keep working at runtime until compat is removed. "@earendil-works/pi-ai": bundledPiAiCompat, "@earendil-works/pi-ai/compat": bundledPiAiCompat, + "@earendil-works/pi-ai/models": bundledPiAiModels, "@earendil-works/pi-ai/oauth": bundledPiAiOauth, "@earendil-works/pi-ai/providers/all": bundledPiAiProviders, "@earendil-works/pi-coding-agent": bundledPiCodingAgent, @@ -32,6 +34,7 @@ export const VIRTUAL_MODULES: Record = { "@mariozechner/pi-tui": bundledPiTui, "@mariozechner/pi-ai": bundledPiAiCompat, "@mariozechner/pi-ai/compat": bundledPiAiCompat, + "@mariozechner/pi-ai/models": bundledPiAiModels, "@mariozechner/pi-ai/oauth": bundledPiAiOauth, "@mariozechner/pi-ai/providers/all": bundledPiAiProviders, "@mariozechner/pi-coding-agent": bundledPiCodingAgent, diff --git a/packages/coding-agent/test/suite/regressions/11156-pi-ai-models-virtual-module.test.ts b/packages/coding-agent/test/suite/regressions/11156-pi-ai-models-virtual-module.test.ts new file mode 100644 index 000000000..205a51290 --- /dev/null +++ b/packages/coding-agent/test/suite/regressions/11156-pi-ai-models-virtual-module.test.ts @@ -0,0 +1,17 @@ +import { describe, expect, it } from "vitest"; +import { VIRTUAL_MODULES } from "../../../src/core/extensions/virtual-modules.ts"; + +// Regression test for #11156: extensions importing @earendil-works/pi-ai/models +// (e.g. for clampThinkingLevel) failed with "Cannot find module" in embedded +// runtimes because the subpath was missing from the virtual module map. +describe("pi-ai/models virtual module", () => { + it("exposes the pi-ai /models entrypoint to extensions", () => { + const models = VIRTUAL_MODULES["@earendil-works/pi-ai/models"] as { clampThinkingLevel?: unknown } | undefined; + expect(models).toBeDefined(); + expect(typeof models?.clampThinkingLevel).toBe("function"); + }); + + it("exposes the same module under the legacy @mariozechner scope", () => { + expect(VIRTUAL_MODULES["@mariozechner/pi-ai/models"]).toBe(VIRTUAL_MODULES["@earendil-works/pi-ai/models"]); + }); +}); diff --git a/vitest.base.ts b/vitest.base.ts index d450260c2..dacac8bfe 100644 --- a/vitest.base.ts +++ b/vitest.base.ts @@ -13,6 +13,7 @@ export const workspaceSourcePaths = { mcpTesting: fileURLToPath(new URL("./packages/mcp/src/testing/index.ts", import.meta.url)), aiIndex: fileURLToPath(new URL("./packages/ai/src/index.ts", import.meta.url)), aiCompat: fileURLToPath(new URL("./packages/ai/src/compat.ts", import.meta.url)), + aiModels: fileURLToPath(new URL("./packages/ai/src/models.ts", import.meta.url)), aiOAuth: fileURLToPath(new URL("./packages/ai/src/oauth.ts", import.meta.url)), aiApi: fileURLToPath(new URL("./packages/ai/src/api", import.meta.url)), aiProviders: fileURLToPath(new URL("./packages/ai/src/providers", import.meta.url)), @@ -39,6 +40,7 @@ export default defineConfig({ { find: /^@earendil-works\/pi-mcp\/testing$/, replacement: workspaceSourcePaths.mcpTesting }, { find: /^@earendil-works\/pi-ai$/, replacement: workspaceSourcePaths.aiIndex }, { find: /^@earendil-works\/pi-ai\/compat$/, replacement: workspaceSourcePaths.aiCompat }, + { find: /^@earendil-works\/pi-ai\/models$/, replacement: workspaceSourcePaths.aiModels }, { find: /^@earendil-works\/pi-ai\/oauth$/, replacement: workspaceSourcePaths.aiOAuth }, { find: /^@earendil-works\/pi-ai\/utils\/(.+)$/,