pi-codebuddy-sdk 0.4.0 → 0.4.2
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/README.md +6 -0
- package/package.json +1 -1
- package/src/index.ts +16 -12
- package/src/models-cache.ts +48 -0
package/README.md
CHANGED
|
@@ -136,6 +136,12 @@ export CODEBUDDY_SDK_DEBUG=1
|
|
|
136
136
|
|
|
137
137
|
Default log: `~/.pi/agent/codebuddy-sdk.log`. See [CONTRIBUTING.md](CONTRIBUTING.md) for maintainer details.
|
|
138
138
|
|
|
139
|
+
**After `pi -r`, the previously selected codebuddy model is gone from `/model`?**
|
|
140
|
+
The last-known-good model list is cached to `~/.pi/agent/codebuddy-sdk-models.json` on every
|
|
141
|
+
successful discovery, so a new pi process can re-register it synchronously at module load —
|
|
142
|
+
even when SDK discovery is still in flight (new process, gate busy, or stale auth). If the
|
|
143
|
+
model is still missing, share the debug log (`CODEBUDDY_SDK_DEBUG=1`) and the cache file.
|
|
144
|
+
|
|
139
145
|
## Development
|
|
140
146
|
|
|
141
147
|
Maintainers: [CONTRIBUTING.md](CONTRIBUTING.md).
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { homedir } from "os";
|
|
|
10
10
|
import { dirname, join } from "path";
|
|
11
11
|
import { PROVIDER_ID, messageContentToText, convertPiMessages } from "./convert.js";
|
|
12
12
|
import { buildModels, codebuddyModelId, FALLBACK_MODELS, rawModelsFromSdk, resolveModel as _resolveModel, type PiModel } from "./models.js";
|
|
13
|
+
import { readModelsCache, writeModelsCache } from "./models-cache.js";
|
|
13
14
|
import { MCP_SERVER_NAME, MCP_TOOL_PREFIX, buildCodebuddySystemPrompt } from "./skills.js";
|
|
14
15
|
import { verifyWrittenSession as _verifyWrittenSession } from "./session-verify.js";
|
|
15
16
|
import { extractAllToolResults as _extractAllToolResults, type McpResult } from "./extract-tool-results.js";
|
|
@@ -124,10 +125,6 @@ const SDK_TO_PI_TOOL_NAME: Record<string, string> = {
|
|
|
124
125
|
let MODELS: PiModel[] = buildModels(FALLBACK_MODELS);
|
|
125
126
|
let providerSettings: NonNullable<Config["provider"]> = {};
|
|
126
127
|
|
|
127
|
-
// Last-known-good model list, kept in a shared global so a module reload
|
|
128
|
-
// during session resume can recover it when SDK discovery fails.
|
|
129
|
-
const MODELS_CACHE_KEY = Symbol.for("codebuddy-sdk:modelsCache");
|
|
130
|
-
|
|
131
128
|
type ContentBlockParam =
|
|
132
129
|
| { type: "text"; text: string }
|
|
133
130
|
| { type: "image"; source: { type: "base64"; media_type: string; data: string } };
|
|
@@ -1595,7 +1592,10 @@ async function discoverModels(pi: ExtensionAPI): Promise<void> {
|
|
|
1595
1592
|
// Persist the successful discovery globally so a later session
|
|
1596
1593
|
// resume (module reload) can recover the real model list even if
|
|
1597
1594
|
// discovery fails that time (e.g. SDK subprocess gate is busy).
|
|
1598
|
-
(
|
|
1595
|
+
// Written to disk so a NEW pi process (`pi -r` after exit) can also
|
|
1596
|
+
// recover it synchronously at module load — the in-process global
|
|
1597
|
+
// alone dies with the old process.
|
|
1598
|
+
writeModelsCache(MODELS);
|
|
1599
1599
|
const g = globalThis as Record<symbol, any>;
|
|
1600
1600
|
const streamFn = g[ACTIVE_STREAM_SIMPLE_KEY] ?? streamCodebuddySdk;
|
|
1601
1601
|
pi.registerProvider(PROVIDER_ID, {
|
|
@@ -1614,7 +1614,7 @@ async function discoverModels(pi: ExtensionAPI): Promise<void> {
|
|
|
1614
1614
|
// model list instead of collapsing to the single fallback model —
|
|
1615
1615
|
// otherwise the previously selected codebuddy model disappears
|
|
1616
1616
|
// from /model after `pi -r` and the user must re-pick it.
|
|
1617
|
-
const cached = (
|
|
1617
|
+
const cached = readModelsCache();
|
|
1618
1618
|
if (cached && cached.length) {
|
|
1619
1619
|
MODELS = cached;
|
|
1620
1620
|
debug(`discoverModels: failed, recovered ${MODELS.length} cached models`, err);
|
|
@@ -1642,23 +1642,27 @@ async function ensureModelsDiscovered(): Promise<void> {
|
|
|
1642
1642
|
await discoverInFlight;
|
|
1643
1643
|
}
|
|
1644
1644
|
|
|
1645
|
-
export default function (pi: ExtensionAPI) {
|
|
1645
|
+
export default async function (pi: ExtensionAPI) {
|
|
1646
1646
|
piApi = pi;
|
|
1647
1647
|
const config = loadConfig(process.cwd());
|
|
1648
1648
|
debug("loadConfig:", JSON.stringify(config));
|
|
1649
1649
|
providerSettings = config.provider ?? {};
|
|
1650
1650
|
// Apply config overrides to the fallback models immediately, and recover
|
|
1651
|
-
// a previously cached model list across module reloads (session resume)
|
|
1652
|
-
|
|
1651
|
+
// a previously cached model list across module reloads (session resume)
|
|
1652
|
+
// or across processes (`pi -r` after exit).
|
|
1653
|
+
const cached = readModelsCache();
|
|
1653
1654
|
if (cached && cached.length) {
|
|
1654
1655
|
MODELS = cached;
|
|
1655
1656
|
debug(`default: recovered ${MODELS.length} cached models from prior discovery`);
|
|
1656
1657
|
} else {
|
|
1657
1658
|
MODELS = buildModels(FALLBACK_MODELS, providerSettings, providerSettings.modelOverrides);
|
|
1658
1659
|
}
|
|
1659
|
-
const registeredModels = MODELS;
|
|
1660
1660
|
|
|
1661
|
-
|
|
1661
|
+
// Discover real models BEFORE registering the provider, so the first
|
|
1662
|
+
// (synchronous-flush) registration carries live models instead of the
|
|
1663
|
+
// FALLBACK_MODELS snapshot. pi awaits this async factory before startup
|
|
1664
|
+
// continues, so the model list shown at startup is never the fallback.
|
|
1665
|
+
await ensureModelsDiscovered();
|
|
1662
1666
|
|
|
1663
1667
|
const clearSession = (event: string) => {
|
|
1664
1668
|
debug(`${event}: clearing session ${sharedSession?.sessionId?.slice(0, 8) ?? "none"}`);
|
|
@@ -1746,7 +1750,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1746
1750
|
baseUrl: PROVIDER_ID,
|
|
1747
1751
|
apiKey: "not-used",
|
|
1748
1752
|
api: "codebuddy-sdk",
|
|
1749
|
-
models:
|
|
1753
|
+
models: MODELS as any,
|
|
1750
1754
|
// Cast: pi-ai AssistantMessageEventStream diamond dep between pi-coding-agent and pi-agent-core
|
|
1751
1755
|
streamSimple: streamCodebuddySdk as any,
|
|
1752
1756
|
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Persisted last-known-good model list.
|
|
2
|
+
//
|
|
3
|
+
// `pi -r` after exiting pi spawns a NEW process: the Symbol.for in-process
|
|
4
|
+
// cache from v0.4.0 dies with the old process, so a session-resume model
|
|
5
|
+
// resolution that runs while SDK discovery is still in flight only sees the
|
|
6
|
+
// fallback list and the previously selected codebuddy model drops out of
|
|
7
|
+
// /model (user must re-pick it). Persisting the list to disk lets the new
|
|
8
|
+
// process register the full model list synchronously at module load.
|
|
9
|
+
|
|
10
|
+
import { homedir } from "os";
|
|
11
|
+
import { dirname, join } from "path";
|
|
12
|
+
import { mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
|
|
13
|
+
import type { PiModel } from "./models.js";
|
|
14
|
+
|
|
15
|
+
export const MODELS_CACHE_KEY = Symbol.for("codebuddy-sdk:modelsCache");
|
|
16
|
+
|
|
17
|
+
const defaultCacheFile = () => join(homedir(), ".pi", "agent", "codebuddy-sdk-models.json");
|
|
18
|
+
|
|
19
|
+
// Injectable for tests.
|
|
20
|
+
const cacheFile = () => process.env.CODEBUDDY_SDK_MODELS_CACHE_PATH || defaultCacheFile();
|
|
21
|
+
|
|
22
|
+
export function readModelsCache(): PiModel[] | undefined {
|
|
23
|
+
// In-process shared cache first (module reloads within a process).
|
|
24
|
+
const inProcess = (globalThis as Record<symbol, unknown>)[MODELS_CACHE_KEY] as PiModel[] | undefined;
|
|
25
|
+
if (inProcess?.length) return inProcess;
|
|
26
|
+
// Disk cache second (new process — `pi -r` after exiting pi).
|
|
27
|
+
try {
|
|
28
|
+
const parsed: unknown = JSON.parse(readFileSync(cacheFile(), "utf8"));
|
|
29
|
+
if (Array.isArray(parsed) && parsed.length) return parsed as PiModel[];
|
|
30
|
+
} catch {
|
|
31
|
+
// No cache yet, or corrupt file — caller falls back.
|
|
32
|
+
}
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function writeModelsCache(models: PiModel[]): void {
|
|
37
|
+
if (!models.length) return;
|
|
38
|
+
(globalThis as Record<symbol, unknown>)[MODELS_CACHE_KEY] = models;
|
|
39
|
+
try {
|
|
40
|
+
const path = cacheFile();
|
|
41
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
42
|
+
const tmp = `${path}.tmp`;
|
|
43
|
+
writeFileSync(tmp, JSON.stringify(models));
|
|
44
|
+
renameSync(tmp, path);
|
|
45
|
+
} catch {
|
|
46
|
+
// Cache is best-effort; discovery retries next load.
|
|
47
|
+
}
|
|
48
|
+
}
|