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 <dantedan@users.noreply.github.com>
This commit is contained in:
Petr Houška 2026-10-20 21:29:51 +02:00
parent 885a564b3b
commit 22dc6c26f3
5 changed files with 26 additions and 0 deletions

View file

@ -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

View file

@ -92,6 +92,7 @@ function getAliases(): Record<string, string> {
// 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<string, string> {
"@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<string, string> {
"@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,

View file

@ -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<string, unknown> = {
// 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<string, unknown> = {
"@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,

View file

@ -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"]);
});
});

View file

@ -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\/(.+)$/,