fix(pi-ext): stop importing @earendil-works/pi-ai at runtime
Some checks failed
CI/CD / typecheck (pull_request) Successful in 1m19s
CI/CD / test (pull_request) Successful in 1m23s
CI/CD / lint (pull_request) Successful in 3m10s
CI/CD / smoke (pull_request) Failing after 10m42s
CI/CD / build (pull_request) Failing after 13m32s
CI/CD / publish (pull_request) Has been skipped
Some checks failed
CI/CD / typecheck (pull_request) Successful in 1m19s
CI/CD / test (pull_request) Successful in 1m23s
CI/CD / lint (pull_request) Successful in 3m10s
CI/CD / smoke (pull_request) Failing after 10m42s
CI/CD / build (pull_request) Failing after 13m32s
CI/CD / publish (pull_request) Has been skipped
The extension failed to load outright on older pi installs: Failed to load extension ".../mcpctl-pi.ts": Cannot find module '@earendil-works/pi-ai' pi doesn't resolve an extension's bare specifiers the ordinary way — it hands jiti a hard-coded alias table built from its own dependencies, and that table differs between pi distributions. `@earendil-works/pi-coding- agent` (0.84.1) aliases both the `@earendil-works/*` and legacy `@mariozechner/*` names; `@mariozechner/pi-coding-agent` (0.73.1) aliases only the old ones. Neither resolves the other's namespace, so a single import outside the intersection takes the whole extension down: every tool, the /mcpctl command, and the status line, all gone. The only thing we used from pi-ai was `StringEnum`, a six-line wrapper over `Type.Unsafe`. Inlined as a local `stringEnum` with byte-identical output, so `typebox` — aliased by every published pi — is now the sole bare runtime import. The call site also passes `description` through, which the pi-ai version was silently dropping. Guarded in tests/config/pi-extension-embed.test.ts: any runtime import in the embedded sources that isn't `node:`, relative, or typebox now fails. Verified against both installs with the same active project: 0.73.1 reproduced the error verbatim before the change and loads cleanly after, and 0.84.1 keeps registering the gate tool exactly as before. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014tsRTqhEC7YYYYaP3cBqo8
This commit is contained in:
@@ -20,9 +20,16 @@
|
||||
* or via settings: "extensions": ["/abs/path/to/mcpctl-pi.ts"]
|
||||
*
|
||||
* Only imports pi-bundled packages — no @mcpctl/*, no ~/.claude.
|
||||
*
|
||||
* RUNTIME IMPORTS ARE LOAD-BEARING: pi resolves an extension's bare specifiers
|
||||
* through a fixed alias table in its own loader, and that table differs between
|
||||
* pi distributions — `@earendil-works/*` exists only in the newer packages,
|
||||
* while `@mariozechner/*` installs alias only the old names. `typebox` is the
|
||||
* one specifier every published pi aliases, so it is the ONLY runtime import
|
||||
* allowed here. Anything else must be `import type` (erased before jiti runs)
|
||||
* or inlined — see `stringEnum` below.
|
||||
*/
|
||||
import { Type, type TSchema } from "typebox";
|
||||
import { StringEnum } from "@earendil-works/pi-ai";
|
||||
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
||||
import {
|
||||
McpHttpSession,
|
||||
@@ -110,6 +117,23 @@ async function listProjects(mcplocalUrl: string, token?: string): Promise<string
|
||||
}
|
||||
|
||||
// ── JSON Schema → TypeBox ────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* `{ type: "string", enum: [...] }` rather than a union of literals: Google's
|
||||
* API (and other providers that reject anyOf/const) only accept the flat form.
|
||||
*
|
||||
* Inlined from pi-ai's `StringEnum` on purpose — importing it dragged in
|
||||
* `@earendil-works/pi-ai`, which older pi installs cannot resolve, and the
|
||||
* whole extension then failed to load. See the import note at the top.
|
||||
*/
|
||||
function stringEnum(values: string[], description?: string): TSchema {
|
||||
return Type.Unsafe<string>({
|
||||
type: "string",
|
||||
enum: values,
|
||||
...(description ? { description } : {}),
|
||||
});
|
||||
}
|
||||
|
||||
function convertSchema(inputSchema: unknown): TSchema {
|
||||
if (!inputSchema || typeof inputSchema !== "object") {
|
||||
return Type.Object({});
|
||||
@@ -147,7 +171,7 @@ function convertProp(raw: unknown): TSchema {
|
||||
const enumVals = Array.isArray(s.enum) && s.enum.length > 0 ? s.enum : undefined;
|
||||
|
||||
if (enumVals && enumVals.every((v) => typeof v === "string")) {
|
||||
return StringEnum(enumVals as string[]);
|
||||
return stringEnum(enumVals as string[], desc);
|
||||
}
|
||||
if (enumVals && enumVals.every((v) => typeof v === "number")) {
|
||||
const literals = enumVals.map((v) => Type.Literal(v));
|
||||
|
||||
Reference in New Issue
Block a user