@gtrabanco/pi-nan-provider 0.6.10 → 0.6.11
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/AGENTS.md +7 -4
- package/package.json +1 -1
- package/scripts/models.generated.ts +2 -2
- package/src/pi-ai-loader.ts +81 -17
package/AGENTS.md
CHANGED
|
@@ -93,10 +93,13 @@ Every PR that changes code MUST bump `package.json` version in the same PR; CI p
|
|
|
93
93
|
`~/.pi/agent/npm/node_modules`; its `estimateMessageTokens` lacks the `system`
|
|
94
94
|
branch and crashes pi 0.87's string-content `system` transcript with
|
|
95
95
|
`block.name.length`. `src/pi-ai-loader.ts` therefore binds the streaming
|
|
96
|
-
factory to the same package instance
|
|
97
|
-
|
|
98
|
-
`
|
|
99
|
-
(
|
|
96
|
+
factory to the same package instance the host loaded: use the root export
|
|
97
|
+
when present, else resolve the bare root **from the host process entrypoint**
|
|
98
|
+
(`process.argv[1]`) via
|
|
99
|
+
`import.meta.resolve("@earendil-works/pi-ai", hostAnchor)` — an
|
|
100
|
+
extension-relative resolve returns the extension tree's stale copy (the
|
|
101
|
+
v0.6.10 regression that left #8 open) — then derive a FILE URL for
|
|
102
|
+
`api/openai-completions.lazy.js` (then `compat.js`). No bare pi-ai subpath
|
|
100
103
|
specifier is imported anywhere in `src/` (static or dynamic); failure is loud
|
|
101
104
|
(`PiAiStreamingApiResolutionError`). Guarded by `test/extension-load.test.ts`
|
|
102
105
|
and `test/issue-8-pi-ai-instance.test.ts`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gtrabanco/pi-nan-provider",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.11",
|
|
4
4
|
"description": "NaN Builders (api.nan.builders) model provider for pi - OpenAI-compatible registration with a models.dev-generated fallback, tier-aware live catalog, and MCP bridges (official web search + optional community media server)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// This file is auto-generated by scripts/generate-models.ts
|
|
2
2
|
// Do not edit manually — run `bun run generate-models` to update.
|
|
3
3
|
//
|
|
4
|
-
// Source: https://models.dev/api.json (provider "nan"), fetched 2026-09-
|
|
4
|
+
// Source: https://models.dev/api.json (provider "nan"), fetched 2026-09-22T08:55:04.775Z
|
|
5
5
|
// Provenance: every contextWindow/maxTokens/input/cost value traces to
|
|
6
6
|
// models.dev or to the per-entry notes below. Nothing is invented; entries
|
|
7
7
|
// models.dev documents incompletely are omitted and flagged instead.
|
|
@@ -382,7 +382,7 @@ export const NAN_GENERATED_MODELS: readonly GeneratedModelEntry[] = [
|
|
|
382
382
|
export const GENERATED_CATALOG_META = {
|
|
383
383
|
source: "https://models.dev/api.json",
|
|
384
384
|
modelsDevProvider: "nan",
|
|
385
|
-
fetchedAt: "2026-09-
|
|
385
|
+
fetchedAt: "2026-09-22T08:55:04.775Z",
|
|
386
386
|
modelCount: 6,
|
|
387
387
|
models: ["deepseek-v4-flash","gemma4","glm5.3-flash","mimo-v2.5","qwen3.6","qwen3.8-flash"],
|
|
388
388
|
notes: [
|
package/src/pi-ai-loader.ts
CHANGED
|
@@ -18,13 +18,15 @@
|
|
|
18
18
|
* `undefined is not an object (evaluating 'block.name.length')` — before the
|
|
19
19
|
* request is ever sent, so it reads like a NaN/gateway failure.
|
|
20
20
|
*
|
|
21
|
-
* `
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
21
|
+
* The anchor is the **host process entrypoint** (`process.argv[1]`), passed to
|
|
22
|
+
* `import.meta.resolve(specifier, parent)`. An extension-relative resolve is
|
|
23
|
+
* NOT enough: it returns whatever the extension's own tree holds, which under
|
|
24
|
+
* pi-web is exactly the stale 0.85.1 copy (that was the v0.6.10 regression:
|
|
25
|
+
* the "host-resolved root" was still the extension's copy). From the
|
|
26
|
+
* host-resolved root derive a **file URL** for the sibling
|
|
27
|
+
* `api/openai-completions.lazy.js` (then `compat.js`) and dynamic-import the
|
|
28
|
+
* URL. A file URL bypasses package resolution entirely, so the loaded module is
|
|
29
|
+
* guaranteed to be the host's instance.
|
|
28
30
|
*
|
|
29
31
|
* Under the bundled CLI / Node-mode aliases / compiled binary, the bare root
|
|
30
32
|
* is the compat entrypoint and already exposes the factory, so the first
|
|
@@ -42,6 +44,7 @@
|
|
|
42
44
|
import * as piAi from "@earendil-works/pi-ai";
|
|
43
45
|
import type { ProviderStreams } from "@earendil-works/pi-ai";
|
|
44
46
|
import { createRequire } from "node:module";
|
|
47
|
+
import { pathToFileURL } from "node:url";
|
|
45
48
|
|
|
46
49
|
/** The only pi-ai specifier this package may import. */
|
|
47
50
|
export const PI_AI_PACKAGE_SPECIFIER = "@earendil-works/pi-ai";
|
|
@@ -106,22 +109,83 @@ export function openAICompletionsApiFrom(namespace: unknown): OpenAICompletionsA
|
|
|
106
109
|
* `import.meta.resolve` is absent from bun-types' `ImportMeta`, so read it
|
|
107
110
|
* through an explicit shape. Bun/Node expose it at runtime; when it is missing
|
|
108
111
|
* or throws, `createRequire` resolves the same bare root from this module.
|
|
112
|
+
*
|
|
113
|
+
* Referenced directly (not via a variable or type cast) so pi's jiti loader
|
|
114
|
+
* can rewrite it. PR #11 (jiti compatibility) and PR #12 (host-anchored
|
|
115
|
+
* resolution) merge: the direct reference fixes jiti, the host anchor fixes
|
|
116
|
+
* pi-web.
|
|
109
117
|
*/
|
|
110
|
-
|
|
118
|
+
function readImportMetaResolve(): ((specifier: string, parent?: string) => string) | undefined {
|
|
119
|
+
if (typeof import.meta.resolve !== "function") return undefined;
|
|
120
|
+
return (specifier, parent) =>
|
|
121
|
+
parent === undefined ? import.meta.resolve(specifier) : import.meta.resolve(specifier, parent);
|
|
122
|
+
}
|
|
111
123
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
124
|
+
/**
|
|
125
|
+
* File URL of the host process entrypoint (pi-web's `sessiond.js`, the pi CLI,
|
|
126
|
+
* a test runner). Resolving FROM it pins the result to the host's module graph
|
|
127
|
+
* instead of the extension's own tree.
|
|
128
|
+
*/
|
|
129
|
+
export function hostAnchorUrl(entry: string | undefined): string | undefined {
|
|
130
|
+
if (typeof entry !== "string" || entry.length === 0) return undefined;
|
|
131
|
+
try {
|
|
132
|
+
return pathToFileURL(entry).href;
|
|
133
|
+
} catch {
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Injection seam for {@link resolvePiAiSpecifier}. */
|
|
139
|
+
export interface PiAiSpecifierResolution {
|
|
140
|
+
/** Anchor URL to resolve FROM; omitted means extension-relative resolution. */
|
|
141
|
+
anchorUrl?: string;
|
|
142
|
+
/** `import.meta.resolve`, when the runtime exposes it. */
|
|
143
|
+
resolve?: (specifier: string, parent?: string) => string;
|
|
144
|
+
/** Last-resort CJS resolver; only used when neither `resolve` call succeeds. */
|
|
145
|
+
fallback?: (specifier: string) => string;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Resolve a pi-ai specifier the way the host runtime does.
|
|
150
|
+
*
|
|
151
|
+
* The `anchorUrl` is the whole point. Under pi-web the extension's own tree
|
|
152
|
+
* holds a stale hoisted `@earendil-works/pi-ai` (0.85.1), so an
|
|
153
|
+
* extension-relative resolve returns a package the host never loaded, and the
|
|
154
|
+
* derived "host-resolved root" is still that stale copy. Passing the host
|
|
155
|
+
* entrypoint as the resolver's parent returns the instance the host itself
|
|
156
|
+
* uses (the v0.6.10 fix anchored on `import.meta.resolve` alone and therefore
|
|
157
|
+
* still selected the stale copy).
|
|
158
|
+
*/
|
|
159
|
+
export function resolvePiAiSpecifier(
|
|
160
|
+
specifier: string,
|
|
161
|
+
options: PiAiSpecifierResolution = {},
|
|
162
|
+
): string {
|
|
163
|
+
const resolve = options.resolve ?? readImportMetaResolve();
|
|
164
|
+
const fallback =
|
|
165
|
+
options.fallback ?? ((value: string) => createRequire(import.meta.url).resolve(value));
|
|
166
|
+
if (resolve !== undefined) {
|
|
167
|
+
if (options.anchorUrl !== undefined) {
|
|
117
168
|
try {
|
|
118
|
-
const
|
|
119
|
-
if (typeof
|
|
169
|
+
const anchored = resolve(specifier, options.anchorUrl);
|
|
170
|
+
if (typeof anchored === "string" && anchored.length > 0) return anchored;
|
|
120
171
|
} catch {
|
|
121
|
-
//
|
|
172
|
+
// Runtimes without parent support fall through to the bare call.
|
|
122
173
|
}
|
|
123
174
|
}
|
|
124
|
-
|
|
175
|
+
try {
|
|
176
|
+
const resolved = resolve(specifier);
|
|
177
|
+
if (typeof resolved === "string" && resolved.length > 0) return resolved;
|
|
178
|
+
} catch {
|
|
179
|
+
// Fall through to createRequire — same bare root, same instance.
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return fallback(specifier);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const defaultPiAiLoaderHost: PiAiLoaderHost = {
|
|
186
|
+
namespace: piAi as unknown as ModuleNamespace,
|
|
187
|
+
resolveSpecifier(specifier: string): string {
|
|
188
|
+
return resolvePiAiSpecifier(specifier, { anchorUrl: hostAnchorUrl(process.argv[1]) });
|
|
125
189
|
},
|
|
126
190
|
importModule: (url: string) => import(url) as Promise<ModuleNamespace>,
|
|
127
191
|
};
|