@juicesharp/rpiv-advisor 1.20.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/advisor/config.ts +2 -2
- package/advisor/execute.ts +3 -2
- package/advisor/pi-compat.test.ts +64 -0
- package/advisor/pi-compat.ts +64 -0
- package/package.json +4 -4
package/advisor/config.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import type { ThinkingLevel } from "@earendil-works/pi-ai";
|
|
8
8
|
import type { GuidanceFields } from "@juicesharp/rpiv-config";
|
|
9
|
-
import { configPath,
|
|
9
|
+
import { configPath, loadJsonConfigWithLegacyFallback, saveJsonConfig } from "@juicesharp/rpiv-config";
|
|
10
10
|
import { EFFORT_ORDINAL } from "./messages.js";
|
|
11
11
|
|
|
12
12
|
const ADVISOR_CONFIG_PATH = configPath("rpiv-advisor", "advisor.json");
|
|
@@ -21,7 +21,7 @@ interface AdvisorConfig {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
export function loadAdvisorConfig(): AdvisorConfig {
|
|
24
|
-
return
|
|
24
|
+
return loadJsonConfigWithLegacyFallback<AdvisorConfig>("rpiv-advisor", "advisor.json");
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export function validateDisabledForModels(value: unknown): DisabledForModelsEntry[] {
|
package/advisor/execute.ts
CHANGED
|
@@ -6,8 +6,7 @@
|
|
|
6
6
|
* buildAdvisorResult so the envelope is built in exactly one place.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import type { StopReason, Usage } from "@earendil-works/pi-ai";
|
|
10
|
-
import { completeSimple, type Message, type ThinkingLevel } from "@earendil-works/pi-ai";
|
|
9
|
+
import type { Message, StopReason, ThinkingLevel, Usage } from "@earendil-works/pi-ai";
|
|
11
10
|
import {
|
|
12
11
|
type AgentToolResult,
|
|
13
12
|
type AgentToolUpdateCallback,
|
|
@@ -32,6 +31,7 @@ import {
|
|
|
32
31
|
errNoApiKeyDetail,
|
|
33
32
|
msgConsulting,
|
|
34
33
|
} from "./messages.js";
|
|
34
|
+
import { loadCompleteSimple } from "./pi-compat.js";
|
|
35
35
|
import { ADVISOR_SYSTEM_PROMPT } from "./prompt.js";
|
|
36
36
|
import { getAdvisorEffort, getAdvisorModel } from "./state.js";
|
|
37
37
|
|
|
@@ -117,6 +117,7 @@ export async function executeAdvisor(
|
|
|
117
117
|
});
|
|
118
118
|
|
|
119
119
|
try {
|
|
120
|
+
const completeSimple = await loadCompleteSimple();
|
|
120
121
|
const response = await completeSimple(
|
|
121
122
|
advisor,
|
|
122
123
|
// `tools: []` reaffirms the "never calls tools" contract even when
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pi-compat tests — the host-version-tolerant `completeSimple` loader.
|
|
3
|
+
*
|
|
4
|
+
* The consumer test files mock `@earendil-works/pi-ai/compat` to SUCCEED, so
|
|
5
|
+
* the version-tolerance arms the shim exists for are exercised here instead:
|
|
6
|
+
* /compat resolves (host >= 0.80.1), /compat unresolvable → root fallback
|
|
7
|
+
* (host <= 0.79.x), a REAL /compat failure rethrows instead of masking, and a
|
|
8
|
+
* host with neither export fails with a clear error.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
12
|
+
|
|
13
|
+
describe("loadCompleteSimple", () => {
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
vi.doUnmock("@earendil-works/pi-ai/compat");
|
|
16
|
+
vi.doUnmock("@earendil-works/pi-ai");
|
|
17
|
+
vi.resetModules();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
/** Import the shim AFTER the per-test doMocks so its dynamic imports resolve
|
|
21
|
+
* against them (vi.resetModules first drops any previously-cached copies). */
|
|
22
|
+
async function load(): Promise<unknown> {
|
|
23
|
+
const mod = await import("./pi-compat.js");
|
|
24
|
+
return mod.loadCompleteSimple();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
it("resolves completeSimple from /compat when the host exposes it (pi >= 0.80.1)", async () => {
|
|
28
|
+
vi.resetModules();
|
|
29
|
+
const compatFn = vi.fn();
|
|
30
|
+
vi.doMock("@earendil-works/pi-ai/compat", () => ({ completeSimple: compatFn }));
|
|
31
|
+
await expect(load()).resolves.toBe(compatFn);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("falls back to the package root when /compat is not exported (host <= 0.79.x)", async () => {
|
|
35
|
+
vi.resetModules();
|
|
36
|
+
vi.doMock("@earendil-works/pi-ai/compat", () => {
|
|
37
|
+
// The code an installed-but-old pi-ai actually produces: the package
|
|
38
|
+
// resolves, but "./compat" is missing from its exports map.
|
|
39
|
+
throw Object.assign(new Error("Package subpath './compat' is not defined"), {
|
|
40
|
+
code: "ERR_PACKAGE_PATH_NOT_EXPORTED",
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
const rootFn = vi.fn();
|
|
44
|
+
vi.doMock("@earendil-works/pi-ai", () => ({ completeSimple: rootFn }));
|
|
45
|
+
await expect(load()).resolves.toBe(rootFn);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("rethrows a non-resolution /compat failure instead of masking it with the root fallback", async () => {
|
|
49
|
+
vi.resetModules();
|
|
50
|
+
vi.doMock("@earendil-works/pi-ai/compat", () => {
|
|
51
|
+
throw new Error("compat entrypoint exploded at module init");
|
|
52
|
+
});
|
|
53
|
+
// A WORKING root export proves the rejection comes from the rethrow: the
|
|
54
|
+
// old catch-all `.catch` fallback would have resolved here.
|
|
55
|
+
vi.doMock("@earendil-works/pi-ai", () => ({ completeSimple: vi.fn() }));
|
|
56
|
+
await expect(load()).rejects.toThrow();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("fails with a clear error when neither entrypoint exposes completeSimple", async () => {
|
|
60
|
+
vi.resetModules();
|
|
61
|
+
vi.doMock("@earendil-works/pi-ai/compat", () => ({}));
|
|
62
|
+
await expect(load()).rejects.toThrow(/completeSimple/);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host-version-tolerant loader for pi-ai's `completeSimple`.
|
|
3
|
+
*
|
|
4
|
+
* Pi >= 0.80.1 moved the global dispatch API (`completeSimple` et al.) to the
|
|
5
|
+
* "@earendil-works/pi-ai/compat" entrypoint; hosts <= 0.79.x export it from
|
|
6
|
+
* the package root and have no /compat entrypoint at all. pi-ai resolves at
|
|
7
|
+
* runtime against the HOST's copy (peerDependency "*"), so neither path can
|
|
8
|
+
* be a static import — try /compat first, fall back to the root entrypoint.
|
|
9
|
+
*
|
|
10
|
+
* The fallback is reserved for RESOLUTION failures (the /compat subpath does
|
|
11
|
+
* not exist on this host); any other /compat error — the entrypoint exists but
|
|
12
|
+
* throws at module init — rethrows so the real failure surfaces at the call
|
|
13
|
+
* site instead of being masked by a root import that may lack the export.
|
|
14
|
+
*
|
|
15
|
+
* /compat is documented as temporary (deleted with pi's ModelManager
|
|
16
|
+
* migration); when that lands, this module is the single place to migrate.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
type CompleteSimpleFn = typeof import("@earendil-works/pi-ai/compat").completeSimple;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Error codes meaning "the /compat entrypoint is not resolvable on this host":
|
|
23
|
+
* - `ERR_PACKAGE_PATH_NOT_EXPORTED` — Node's ESM resolver when the installed
|
|
24
|
+
* pi-ai (<= 0.79.x) resolves but has no "./compat" in its `exports` map —
|
|
25
|
+
* the code an old host actually produces.
|
|
26
|
+
* - `ERR_MODULE_NOT_FOUND` / `MODULE_NOT_FOUND` — Node's / jiti's resolver
|
|
27
|
+
* for an unresolvable module (jiti is what Pi loads `.ts` extensions with).
|
|
28
|
+
* Mirrors rpiv-core's `isModuleNotFound` (plus the subpath-export code) —
|
|
29
|
+
* duplicated because siblings never import each other at runtime.
|
|
30
|
+
*/
|
|
31
|
+
const MODULE_NOT_FOUND_CODES = new Set(["ERR_PACKAGE_PATH_NOT_EXPORTED", "ERR_MODULE_NOT_FOUND", "MODULE_NOT_FOUND"]);
|
|
32
|
+
|
|
33
|
+
/** True for a module-resolution failure. Walks the `cause` chain — ESM loaders
|
|
34
|
+
* and tooling (vitest's mock layer, some bundlers) nest the real code under
|
|
35
|
+
* `.cause`. Bounded against pathological self-referential chains. */
|
|
36
|
+
function isModuleNotFound(err: unknown): boolean {
|
|
37
|
+
for (
|
|
38
|
+
let cur: unknown = err, depth = 0;
|
|
39
|
+
cur != null && depth < 16;
|
|
40
|
+
cur = (cur as { cause?: unknown }).cause, depth++
|
|
41
|
+
) {
|
|
42
|
+
if (typeof cur === "object" && MODULE_NOT_FOUND_CODES.has((cur as { code?: unknown }).code as string)) {
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function loadCompleteSimple(): Promise<CompleteSimpleFn> {
|
|
50
|
+
let mod: { completeSimple?: CompleteSimpleFn };
|
|
51
|
+
try {
|
|
52
|
+
mod = (await import("@earendil-works/pi-ai/compat")) as { completeSimple?: CompleteSimpleFn };
|
|
53
|
+
} catch (err) {
|
|
54
|
+
if (!isModuleNotFound(err)) throw err; // a real /compat failure must surface, not mask as a fallback
|
|
55
|
+
mod = (await import("@earendil-works/pi-ai")) as { completeSimple?: CompleteSimpleFn };
|
|
56
|
+
}
|
|
57
|
+
const completeSimple = mod.completeSimple;
|
|
58
|
+
if (typeof completeSimple !== "function") {
|
|
59
|
+
throw new Error(
|
|
60
|
+
"pi-ai does not expose completeSimple on /compat or the package root — unsupported host pi-ai version",
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
return completeSimple;
|
|
64
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juicesharp/rpiv-advisor",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Pi extension. A second opinion the model can request from a stronger reviewer model before it acts.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -41,12 +41,12 @@
|
|
|
41
41
|
]
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@juicesharp/rpiv-config": "^
|
|
44
|
+
"@juicesharp/rpiv-config": "^2.0.0",
|
|
45
|
+
"typebox": "^1.1.24"
|
|
45
46
|
},
|
|
46
47
|
"peerDependencies": {
|
|
47
48
|
"@earendil-works/pi-ai": "*",
|
|
48
49
|
"@earendil-works/pi-coding-agent": "*",
|
|
49
|
-
"@earendil-works/pi-tui": "*"
|
|
50
|
-
"typebox": "*"
|
|
50
|
+
"@earendil-works/pi-tui": "*"
|
|
51
51
|
}
|
|
52
52
|
}
|