From 846cd36d92dda719b7f184e714c8e49bed76386a Mon Sep 17 00:00:00 2001 From: roboomp Date: Tue, 28 Jul 2026 05:34:20 +0000 Subject: [PATCH] fix(coding-agent): bridge isContextOverflow in legacy pi-ai shim Upstream @earendil-works/pi-ai re-exported isContextOverflow from its package root, but omp's @oh-my-pi/pi-ai barrel forwards only ./error/rate-limit, so the shim's `export * from "@oh-my-pi/pi-ai"` left it off the surface. Plugins importing it (pi-blackhole) tripped Bun's static named-export check during validation, both on disk and through the omp-legacy-pi-bundled: virtual namespace. Audited the upstream root surface rather than fix one symbol at a time: of its runtime exports only isContextOverflow (now under @oh-my-pi/pi-ai/error) and parseJsonWithRepair/parseStreamingJson/ repairJson (relocated to @oh-my-pi/pi-utils) still exist in omp. Bridge exactly that set through the shim and pin it with a regression test that exercises each helper's behavior. Fixes #6859 --- packages/coding-agent/CHANGELOG.md | 4 ++ .../src/extensibility/legacy-pi-ai-shim.ts | 18 ++++++ .../legacy-pi-ai-root-exports.test.ts | 56 +++++++++++++++++++ 3 files changed, 78 insertions(+) create mode 100644 packages/coding-agent/test/extensibility/legacy-pi-ai-root-exports.test.ts diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 8379caed6..a0b8e446e 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Fixed `omp plugin install` of legacy pi extensions failing Bun's static named-export validation on `isContextOverflow` (e.g. `pi-blackhole`). The `@oh-my-pi/pi-ai` root barrel dropped upstream `@earendil-works/pi-ai`'s root re-export, so `legacy-pi-ai-shim.ts` (which backs both on-disk and `omp-legacy-pi-bundled:` virtual resolution) never surfaced it. The shim now bridges every upstream-root runtime symbol that still exists in omp — `isContextOverflow` (from `@oh-my-pi/pi-ai/error`) and `parseJsonWithRepair`/`parseStreamingJson`/`repairJson` (from `@oh-my-pi/pi-utils`) ([#6859](https://github.com/can1357/oh-my-pi/issues/6859)). + ## [17.1.7] - 2026-07-27 ### Fixed diff --git a/packages/coding-agent/src/extensibility/legacy-pi-ai-shim.ts b/packages/coding-agent/src/extensibility/legacy-pi-ai-shim.ts index f7bb39c5a..5b482eb83 100644 --- a/packages/coding-agent/src/extensibility/legacy-pi-ai-shim.ts +++ b/packages/coding-agent/src/extensibility/legacy-pi-ai-shim.ts @@ -93,3 +93,21 @@ export * from "@oh-my-pi/pi-ai"; export { calculateCost, getBundledModel, getBundledModels, getBundledProviders, modelsAreEqual, Type }; export const getModel = getBundledModel; export const getModels = getBundledModels; + +/** + * Compatibility re-exports for runtime helpers that upstream + * `@earendil-works/pi-ai` exposed from its package root but omp's + * `@oh-my-pi/pi-ai` barrel no longer forwards. Each symbol still exists in the + * host graph — only its root re-export was dropped — so bridging it here keeps + * legacy extensions importing it from the pi-ai root resolving through Bun's + * static named-export check (e.g. `omp plugin install pi-blackhole`). + * + * This is the full set derived from an audit of the upstream root surface: the + * error-classification predicate `isContextOverflow` (now under + * `@oh-my-pi/pi-ai/error`) and the JSON-repair helpers that omp relocated to + * `@oh-my-pi/pi-utils`. Upstream root symbols with no omp equivalent are + * intentionally not shimmed — the package has diverged and there is nothing to + * forward. + */ +export { isContextOverflow } from "@oh-my-pi/pi-ai/error"; +export { parseJsonWithRepair, parseStreamingJson, repairJson } from "@oh-my-pi/pi-utils"; diff --git a/packages/coding-agent/test/extensibility/legacy-pi-ai-root-exports.test.ts b/packages/coding-agent/test/extensibility/legacy-pi-ai-root-exports.test.ts new file mode 100644 index 000000000..4e41b2602 --- /dev/null +++ b/packages/coding-agent/test/extensibility/legacy-pi-ai-root-exports.test.ts @@ -0,0 +1,56 @@ +import { describe, expect, it } from "bun:test"; +import type { AssistantMessage } from "@oh-my-pi/pi-ai"; +import { + isContextOverflow, + parseJsonWithRepair, + parseStreamingJson, + repairJson, +} from "@oh-my-pi/pi-coding-agent/extensibility/legacy-pi-ai-shim"; + +// Issue #6859: pi extensions import runtime helpers from the `@earendil-works/pi-ai` +// (aliased to `@oh-my-pi/pi-ai`) package root that omp's barrel no longer forwards. +// `isContextOverflow` moved under `@oh-my-pi/pi-ai/error` and the JSON-repair +// helpers moved to `@oh-my-pi/pi-utils`, so `export * from "@oh-my-pi/pi-ai"` left +// them off the shim surface and a named import tripped Bun's static +// "No matching export" check during plugin validation (e.g. +// `omp plugin install pi-blackhole`). This pins the bridged root surface so it +// cannot silently regress the way #6583 / #6648 did one symbol at a time. +function createErrorMessage(errorMessage: string): AssistantMessage { + return { + role: "assistant", + content: [{ type: "text", text: "" }], + api: "anthropic-messages", + provider: "anthropic", + model: "claude-sonnet-4-5", + usage: { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, + totalTokens: 0, + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }, + }, + stopReason: "error", + errorMessage, + timestamp: Date.now(), + }; +} + +describe("legacy pi-ai shim root exports", () => { + it("re-exports isContextOverflow with its classification behavior", () => { + expect(typeof isContextOverflow).toBe("function"); + expect(isContextOverflow(createErrorMessage("prompt is too long: 300000 tokens > 200000 maximum"))).toBe(true); + expect(isContextOverflow(createErrorMessage("400 Bad Request: invalid API key"))).toBe(false); + }); + + it("re-exports the JSON-repair helpers that upstream exposed at the pi-ai root", () => { + // repairJson escapes a raw control char inside a string so JSON.parse stops throwing. + const broken = `{"a": "b${String.fromCharCode(1)}c"}`; + expect(() => JSON.parse(broken)).toThrow(); + expect(JSON.parse(repairJson(broken))).toEqual({ a: "b\u0001c" }); + // parseJsonWithRepair tolerates trailing commas / unquoted keys. + expect(parseJsonWithRepair<{ a: number }>("{a: 1,}")).toEqual({ a: 1 }); + // parseStreamingJson completes a truncated object at the streaming edge. + expect(parseStreamingJson<{ a: number }>('{"a": 1')).toEqual({ a: 1 }); + }); +});