@caupulican/pi-adaptative 0.81.20 → 0.81.22
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/CHANGELOG.md +22 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +21 -4
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/doctor.d.ts.map +1 -1
- package/dist/core/doctor.js +4 -1
- package/dist/core/doctor.js.map +1 -1
- package/dist/core/local-runtime-controller.d.ts +2 -0
- package/dist/core/local-runtime-controller.d.ts.map +1 -1
- package/dist/core/local-runtime-controller.js +92 -6
- package/dist/core/local-runtime-controller.js.map +1 -1
- package/dist/core/model-router/intent-classifier.d.ts.map +1 -1
- package/dist/core/model-router/intent-classifier.js +1 -1
- package/dist/core/model-router/intent-classifier.js.map +1 -1
- package/dist/core/model-router/route-judge.d.ts.map +1 -1
- package/dist/core/model-router/route-judge.js +10 -6
- package/dist/core/model-router/route-judge.js.map +1 -1
- package/dist/core/model-router/tool-escalation.d.ts.map +1 -1
- package/dist/core/model-router/tool-escalation.js +2 -1
- package/dist/core/model-router/tool-escalation.js.map +1 -1
- package/dist/core/model-router-controller.d.ts.map +1 -1
- package/dist/core/model-router-controller.js +27 -2
- package/dist/core/model-router-controller.js.map +1 -1
- package/dist/core/models/context-sizing.d.ts.map +1 -1
- package/dist/core/models/context-sizing.js +18 -26
- package/dist/core/models/context-sizing.js.map +1 -1
- package/dist/core/models/local-runtime.d.ts +56 -4
- package/dist/core/models/local-runtime.d.ts.map +1 -1
- package/dist/core/models/local-runtime.js +286 -19
- package/dist/core/models/local-runtime.js.map +1 -1
- package/dist/core/models/perf-profile.d.ts +3 -0
- package/dist/core/models/perf-profile.d.ts.map +1 -1
- package/dist/core/models/perf-profile.js +42 -13
- package/dist/core/models/perf-profile.js.map +1 -1
- package/dist/core/models/runtime-arbiter.d.ts +45 -0
- package/dist/core/models/runtime-arbiter.d.ts.map +1 -1
- package/dist/core/models/runtime-arbiter.js +115 -0
- package/dist/core/models/runtime-arbiter.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +3 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +25 -1
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/local-model-commands.d.ts.map +1 -1
- package/dist/modes/interactive/local-model-commands.js +24 -6
- package/dist/modes/interactive/local-model-commands.js.map +1 -1
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +12 -12
- package/package.json +4 -4
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
-
import { createWriteStream, existsSync, mkdirSync, rmSync } from "node:fs";
|
|
2
|
+
import { copyFileSync, createWriteStream, existsSync, linkSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, } from "node:fs";
|
|
3
3
|
import { homedir, arch as osArch, platform as osPlatform } from "node:os";
|
|
4
|
-
import { delimiter, join } from "node:path";
|
|
4
|
+
import { delimiter, dirname, join, relative } from "node:path";
|
|
5
5
|
import { pipeline } from "node:stream/promises";
|
|
6
6
|
import * as nodeZlib from "node:zlib";
|
|
7
7
|
import { getBundledResourcesDir } from "../../config.js";
|
|
@@ -56,6 +56,17 @@ const TRANSFORMERS_SERVER_RELATIVE_PATH = join("runtimes", "hf-transformers-open
|
|
|
56
56
|
const TRANSFORMERS_PINNED_PACKAGES = ["transformers==5.13.0", "huggingface-hub==1.22.0", "safetensors==0.8.0"];
|
|
57
57
|
const TRANSFORMERS_REQUIRED_MODULES = ["torch", "transformers", "huggingface_hub", "safetensors"];
|
|
58
58
|
const TORCH_PINNED_VERSION = "2.12.1";
|
|
59
|
+
function isCrossDeviceLinkError(error) {
|
|
60
|
+
return typeof error === "object" && error !== null && error.code === "EXDEV";
|
|
61
|
+
}
|
|
62
|
+
function fileSizeBytes(path) {
|
|
63
|
+
try {
|
|
64
|
+
return statSync(path).size;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return 0;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
59
70
|
async function runCommandDefault(command, args, options = {}) {
|
|
60
71
|
try {
|
|
61
72
|
const proc = spawnProcess(command, args, {
|
|
@@ -113,6 +124,8 @@ export class OllamaRuntime {
|
|
|
113
124
|
_fetch;
|
|
114
125
|
_spawn;
|
|
115
126
|
_exists;
|
|
127
|
+
_linkFile;
|
|
128
|
+
_copyFile;
|
|
116
129
|
_envPath;
|
|
117
130
|
_homeDir;
|
|
118
131
|
_sleep;
|
|
@@ -122,12 +135,15 @@ export class OllamaRuntime {
|
|
|
122
135
|
_hasCommand;
|
|
123
136
|
_extractArchiveOverride;
|
|
124
137
|
_child;
|
|
138
|
+
_childModelsDir;
|
|
125
139
|
constructor(args) {
|
|
126
140
|
this._agentDir = args.agentDir;
|
|
127
141
|
this._baseUrl = (args.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
128
142
|
this._fetch = args.deps?.fetchFn ?? fetch;
|
|
129
143
|
this._spawn = args.deps?.spawnFn ?? ((command, argv, options) => spawn(command, argv, options));
|
|
130
144
|
this._exists = args.deps?.existsFn ?? existsSync;
|
|
145
|
+
this._linkFile = args.deps?.linkFile ?? linkSync;
|
|
146
|
+
this._copyFile = args.deps?.copyFile ?? copyFileSync;
|
|
131
147
|
this._envPath = args.deps?.envPath ?? process.env.PATH ?? "";
|
|
132
148
|
this._homeDir = args.deps?.homeDir ?? homedir();
|
|
133
149
|
this._sleep = args.deps?.sleepFn ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
@@ -155,6 +171,35 @@ export class OllamaRuntime {
|
|
|
155
171
|
ownedModelsDir() {
|
|
156
172
|
return join(this._agentDir, "models", "ollama");
|
|
157
173
|
}
|
|
174
|
+
userModelsDir() {
|
|
175
|
+
return join(this._homeDir, ".ollama", "models");
|
|
176
|
+
}
|
|
177
|
+
_storeSummary(kind, path) {
|
|
178
|
+
return { kind, path, modelCount: this._countStoreModels(path) };
|
|
179
|
+
}
|
|
180
|
+
_countStoreModels(storeDir) {
|
|
181
|
+
return this._walkFiles(join(storeDir, "manifests")).length;
|
|
182
|
+
}
|
|
183
|
+
_walkFiles(root) {
|
|
184
|
+
if (!this._exists(root))
|
|
185
|
+
return [];
|
|
186
|
+
let entries;
|
|
187
|
+
try {
|
|
188
|
+
entries = readdirSync(root, { withFileTypes: true });
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
return [];
|
|
192
|
+
}
|
|
193
|
+
const files = [];
|
|
194
|
+
for (const entry of entries) {
|
|
195
|
+
const path = join(root, entry.name);
|
|
196
|
+
if (entry.isDirectory())
|
|
197
|
+
files.push(...this._walkFiles(path));
|
|
198
|
+
else if (entry.isFile())
|
|
199
|
+
files.push(path);
|
|
200
|
+
}
|
|
201
|
+
return files;
|
|
202
|
+
}
|
|
158
203
|
_findBinary() {
|
|
159
204
|
const piOwned = join(this._agentDir, "runtimes", "ollama", "bin", "ollama");
|
|
160
205
|
if (this._exists(piOwned))
|
|
@@ -172,27 +217,115 @@ export class OllamaRuntime {
|
|
|
172
217
|
return undefined;
|
|
173
218
|
}
|
|
174
219
|
async _serverUp() {
|
|
220
|
+
return (await this._serverModels()).up;
|
|
221
|
+
}
|
|
222
|
+
async _serverModels() {
|
|
175
223
|
try {
|
|
176
224
|
const response = await this._fetch(`${this._baseUrl}/api/tags`, {
|
|
177
225
|
signal: AbortSignal.timeout(HEALTH_TIMEOUT_MS),
|
|
178
226
|
});
|
|
179
|
-
|
|
227
|
+
if (!response.ok)
|
|
228
|
+
return { up: false, modelCount: 0 };
|
|
229
|
+
const data = (await response.json().catch(() => ({})));
|
|
230
|
+
return { up: true, modelCount: Array.isArray(data.models) ? data.models.length : 0 };
|
|
180
231
|
}
|
|
181
232
|
catch {
|
|
182
|
-
return false;
|
|
233
|
+
return { up: false, modelCount: 0 };
|
|
183
234
|
}
|
|
184
235
|
}
|
|
236
|
+
_activeStoreSummary(serverModelCount, ownedStore, userStore) {
|
|
237
|
+
if (this._childModelsDir) {
|
|
238
|
+
return {
|
|
239
|
+
kind: this._childModelsDir === this.ownedModelsDir() ? "pi-owned" : "user",
|
|
240
|
+
path: this._childModelsDir,
|
|
241
|
+
modelCount: serverModelCount,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
if (serverModelCount > 0 &&
|
|
245
|
+
serverModelCount === ownedStore.modelCount &&
|
|
246
|
+
serverModelCount !== userStore.modelCount) {
|
|
247
|
+
return { ...ownedStore, modelCount: serverModelCount };
|
|
248
|
+
}
|
|
249
|
+
if (serverModelCount > 0 &&
|
|
250
|
+
serverModelCount === userStore.modelCount &&
|
|
251
|
+
serverModelCount !== ownedStore.modelCount) {
|
|
252
|
+
return { ...userStore, modelCount: serverModelCount };
|
|
253
|
+
}
|
|
254
|
+
return { kind: "external", path: "external/unknown", modelCount: serverModelCount };
|
|
255
|
+
}
|
|
185
256
|
async detect() {
|
|
186
257
|
const binary = this._findBinary();
|
|
187
|
-
const
|
|
258
|
+
const serverModels = await this._serverModels();
|
|
259
|
+
const ownedStore = this._storeSummary("pi-owned", this.ownedModelsDir());
|
|
260
|
+
const userStore = this._storeSummary("user", this.userModelsDir());
|
|
261
|
+
const activeStore = serverModels.up
|
|
262
|
+
? this._activeStoreSummary(serverModels.modelCount, ownedStore, userStore)
|
|
263
|
+
: undefined;
|
|
188
264
|
return {
|
|
189
265
|
binaryPath: binary?.path,
|
|
190
266
|
binarySource: binary?.source,
|
|
191
|
-
serverUp,
|
|
267
|
+
serverUp: serverModels.up,
|
|
192
268
|
serverUrl: this._baseUrl,
|
|
193
|
-
managedByPi: this._child !== undefined &&
|
|
269
|
+
managedByPi: this._child !== undefined && serverModels.up,
|
|
194
270
|
ownedModelsDir: this.ownedModelsDir(),
|
|
271
|
+
userModelsDir: this.userModelsDir(),
|
|
272
|
+
ownedStore,
|
|
273
|
+
userStore,
|
|
274
|
+
activeStore,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
importUserModels() {
|
|
278
|
+
const sourceDir = this.userModelsDir();
|
|
279
|
+
const targetDir = this.ownedModelsDir();
|
|
280
|
+
const result = {
|
|
281
|
+
sourceDir,
|
|
282
|
+
targetDir,
|
|
283
|
+
manifestsImported: 0,
|
|
284
|
+
manifestsSkipped: 0,
|
|
285
|
+
blobsHardlinked: 0,
|
|
286
|
+
blobsCopied: 0,
|
|
287
|
+
blobsSkipped: 0,
|
|
288
|
+
bytesImported: 0,
|
|
195
289
|
};
|
|
290
|
+
if (sourceDir === targetDir || !this._exists(sourceDir))
|
|
291
|
+
return result;
|
|
292
|
+
mkdirSync(targetDir, { recursive: true });
|
|
293
|
+
this._importManifestFiles(join(sourceDir, "manifests"), join(targetDir, "manifests"), result);
|
|
294
|
+
this._importBlobFiles(join(sourceDir, "blobs"), join(targetDir, "blobs"), result);
|
|
295
|
+
return result;
|
|
296
|
+
}
|
|
297
|
+
_importManifestFiles(sourceRoot, targetRoot, result) {
|
|
298
|
+
for (const source of this._walkFiles(sourceRoot)) {
|
|
299
|
+
const target = join(targetRoot, relative(sourceRoot, source));
|
|
300
|
+
if (this._exists(target)) {
|
|
301
|
+
result.manifestsSkipped++;
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
305
|
+
this._copyFile(source, target);
|
|
306
|
+
result.manifestsImported++;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
_importBlobFiles(sourceRoot, targetRoot, result) {
|
|
310
|
+
for (const source of this._walkFiles(sourceRoot)) {
|
|
311
|
+
const target = join(targetRoot, relative(sourceRoot, source));
|
|
312
|
+
if (this._exists(target)) {
|
|
313
|
+
result.blobsSkipped++;
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
317
|
+
try {
|
|
318
|
+
this._linkFile(source, target);
|
|
319
|
+
result.blobsHardlinked++;
|
|
320
|
+
}
|
|
321
|
+
catch (error) {
|
|
322
|
+
if (!isCrossDeviceLinkError(error))
|
|
323
|
+
throw error;
|
|
324
|
+
this._copyFile(source, target);
|
|
325
|
+
result.blobsCopied++;
|
|
326
|
+
}
|
|
327
|
+
result.bytesImported += fileSizeBytes(target);
|
|
328
|
+
}
|
|
196
329
|
}
|
|
197
330
|
/**
|
|
198
331
|
* Manual-steps fallback text — shown when the managed install below wasn't offered (headless),
|
|
@@ -333,9 +466,9 @@ export class OllamaRuntime {
|
|
|
333
466
|
* differs between them (owned storage vs reusing the user's own). */
|
|
334
467
|
async _spawnAndPoll(binary, extraEnv) {
|
|
335
468
|
const host = this._baseUrl.replace(/^https?:\/\//, "");
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
});
|
|
469
|
+
const env = { ...process.env, OLLAMA_HOST: host, ...extraEnv };
|
|
470
|
+
this._childModelsDir = env.OLLAMA_MODELS ?? this.userModelsDir();
|
|
471
|
+
this._child = this._spawn(binary.path, ["serve"], { env });
|
|
339
472
|
this._child.unref?.();
|
|
340
473
|
for (let attempt = 0; attempt < START_POLL_ATTEMPTS; attempt++) {
|
|
341
474
|
if (await this._serverUp())
|
|
@@ -399,6 +532,7 @@ export class OllamaRuntime {
|
|
|
399
532
|
// already gone
|
|
400
533
|
}
|
|
401
534
|
this._child = undefined;
|
|
535
|
+
this._childModelsDir = undefined;
|
|
402
536
|
return { stopped: true };
|
|
403
537
|
}
|
|
404
538
|
async list() {
|
|
@@ -410,6 +544,43 @@ export class OllamaRuntime {
|
|
|
410
544
|
.filter((model) => typeof model.name === "string")
|
|
411
545
|
.map((model) => ({ name: model.name, sizeBytes: model.size ?? 0 }));
|
|
412
546
|
}
|
|
547
|
+
async listResidentModels() {
|
|
548
|
+
const response = await this._fetch(`${this._baseUrl}/api/ps`, { signal: AbortSignal.timeout(10_000) });
|
|
549
|
+
if (!response.ok)
|
|
550
|
+
throw new Error(`ollama ps failed: HTTP ${response.status}`);
|
|
551
|
+
const data = (await response.json());
|
|
552
|
+
return (data.models ?? [])
|
|
553
|
+
.map((model) => ({ name: model.name ?? model.model, sizeBytes: model.size_vram ?? model.size ?? 0 }))
|
|
554
|
+
.filter((model) => typeof model.name === "string" && model.name.length > 0);
|
|
555
|
+
}
|
|
556
|
+
async ensureResident(model) {
|
|
557
|
+
return this._postKeepAlive(model, "30m");
|
|
558
|
+
}
|
|
559
|
+
async releaseResident(model) {
|
|
560
|
+
return this._postKeepAlive(model, 0);
|
|
561
|
+
}
|
|
562
|
+
async _postKeepAlive(model, keepAlive) {
|
|
563
|
+
try {
|
|
564
|
+
const response = await this._fetch(`${this._baseUrl}/api/generate`, {
|
|
565
|
+
method: "POST",
|
|
566
|
+
headers: { "content-type": "application/json" },
|
|
567
|
+
body: JSON.stringify({
|
|
568
|
+
model,
|
|
569
|
+
prompt: "",
|
|
570
|
+
stream: false,
|
|
571
|
+
keep_alive: keepAlive,
|
|
572
|
+
options: { num_predict: 0 },
|
|
573
|
+
}),
|
|
574
|
+
signal: AbortSignal.timeout(60_000),
|
|
575
|
+
});
|
|
576
|
+
if (!response.ok)
|
|
577
|
+
return { ok: false, error: `HTTP ${response.status}: ${await response.text().catch(() => "")}` };
|
|
578
|
+
return { ok: true };
|
|
579
|
+
}
|
|
580
|
+
catch (error) {
|
|
581
|
+
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
582
|
+
}
|
|
583
|
+
}
|
|
413
584
|
async show(ref) {
|
|
414
585
|
try {
|
|
415
586
|
const response = await this._fetch(`${this._baseUrl}/api/show`, {
|
|
@@ -671,21 +842,117 @@ export class TransformersRuntime {
|
|
|
671
842
|
]);
|
|
672
843
|
return result.ok;
|
|
673
844
|
}
|
|
845
|
+
readPyvenvHome() {
|
|
846
|
+
const pyvenvCfg = join(this.venvDir, "pyvenv.cfg");
|
|
847
|
+
if (!this._exists(pyvenvCfg))
|
|
848
|
+
return undefined;
|
|
849
|
+
try {
|
|
850
|
+
const text = readFileSync(pyvenvCfg, "utf8");
|
|
851
|
+
const homeLine = text
|
|
852
|
+
.split(/\r?\n/)
|
|
853
|
+
.map((line) => line.trim())
|
|
854
|
+
.find((line) => line.toLowerCase().startsWith("home ="));
|
|
855
|
+
return homeLine?.slice(homeLine.indexOf("=") + 1).trim() || undefined;
|
|
856
|
+
}
|
|
857
|
+
catch {
|
|
858
|
+
return undefined;
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
async venvInterpreterCoherent() {
|
|
862
|
+
const pyvenvHome = this.readPyvenvHome();
|
|
863
|
+
if (!pyvenvHome)
|
|
864
|
+
return false;
|
|
865
|
+
const base = await this.runPython(["-c", "import sys; print(sys.base_prefix or sys.prefix)"]);
|
|
866
|
+
if (!base.ok)
|
|
867
|
+
return false;
|
|
868
|
+
const basePrefix = base.stdout.trim();
|
|
869
|
+
if (!basePrefix)
|
|
870
|
+
return true;
|
|
871
|
+
return pyvenvHome === basePrefix || pyvenvHome.startsWith(basePrefix) || basePrefix.startsWith(pyvenvHome);
|
|
872
|
+
}
|
|
873
|
+
async venvHealthy() {
|
|
874
|
+
const pip = await this.runPython(["-m", "pip", "--version"]);
|
|
875
|
+
return pip.ok && (await this.venvInterpreterCoherent());
|
|
876
|
+
}
|
|
877
|
+
linuxVenvPackageCommand() {
|
|
878
|
+
try {
|
|
879
|
+
const osRelease = readFileSync("/etc/os-release", "utf8").toLowerCase();
|
|
880
|
+
if (osRelease.includes("alpine"))
|
|
881
|
+
return "sudo apk add python3 py3-pip";
|
|
882
|
+
if (osRelease.includes("fedora") || osRelease.includes("rhel") || osRelease.includes("centos")) {
|
|
883
|
+
return "sudo dnf install python3";
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
catch {
|
|
887
|
+
// fall through to the most common Debian/Ubuntu package split.
|
|
888
|
+
}
|
|
889
|
+
return "sudo apt install python3-venv";
|
|
890
|
+
}
|
|
891
|
+
venvInstallHint() {
|
|
892
|
+
if (this._platform() === "darwin")
|
|
893
|
+
return "install Python from python.org or run: brew install python";
|
|
894
|
+
if (this._platform() === "win32")
|
|
895
|
+
return "install Python 3 from python.org, then run: py -3 -m venv <path>";
|
|
896
|
+
if (this._platform() === "linux")
|
|
897
|
+
return this.linuxVenvPackageCommand();
|
|
898
|
+
return "install Python with venv/ensurepip support for this platform";
|
|
899
|
+
}
|
|
900
|
+
venvFailure(error) {
|
|
901
|
+
return {
|
|
902
|
+
ok: false,
|
|
903
|
+
error: `venv-fail: ${sanitizeCommandOutput(error)}. Install Python venv support: ${this.venvInstallHint()}`,
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
async createVenv(onProgress) {
|
|
907
|
+
const python = await this.findPython();
|
|
908
|
+
if (!python)
|
|
909
|
+
return { ok: false, error: "python-missing" };
|
|
910
|
+
onProgress?.(`creating isolated Python venv at ${this.venvDir}`);
|
|
911
|
+
const venv = await this._runCommand(python.command, [...python.args, "-m", "venv", this.venvDir]);
|
|
912
|
+
if (!venv.ok)
|
|
913
|
+
return this.venvFailure(venv.error ?? venv.stderr);
|
|
914
|
+
if (!this._exists(this.pythonPath))
|
|
915
|
+
return this.venvFailure(`${this.pythonPath} was not created`);
|
|
916
|
+
return { ok: true };
|
|
917
|
+
}
|
|
918
|
+
async repairExistingVenv(onProgress) {
|
|
919
|
+
if (await this.venvHealthy())
|
|
920
|
+
return { ok: true };
|
|
921
|
+
onProgress?.("repairing pi-managed Transformers venv with ensurepip");
|
|
922
|
+
const ensurepip = await this.runPython(["-m", "ensurepip", "--upgrade"], onProgress);
|
|
923
|
+
if (ensurepip.ok && (await this.venvHealthy()))
|
|
924
|
+
return { ok: true };
|
|
925
|
+
onProgress?.("recreating pi-managed Transformers venv");
|
|
926
|
+
rmSync(this.venvDir, { recursive: true, force: true });
|
|
927
|
+
return this.createVenv(onProgress);
|
|
928
|
+
}
|
|
929
|
+
async ensurePipAvailable(onProgress) {
|
|
930
|
+
const pip = await this.runPython(["-m", "pip", "--version"]);
|
|
931
|
+
if (pip.ok)
|
|
932
|
+
return { ok: true };
|
|
933
|
+
onProgress?.("bootstrapping pip inside pi-managed venv");
|
|
934
|
+
const ensurepip = await this.runPython(["-m", "ensurepip", "--upgrade"], onProgress);
|
|
935
|
+
if (!ensurepip.ok)
|
|
936
|
+
return this.venvFailure(ensurepip.error ?? ensurepip.stderr);
|
|
937
|
+
const verified = await this.runPython(["-m", "pip", "--version"]);
|
|
938
|
+
return verified.ok ? { ok: true } : this.venvFailure(verified.error ?? verified.stderr);
|
|
939
|
+
}
|
|
674
940
|
async installManaged(onProgress) {
|
|
675
941
|
mkdirSync(this.runtimeDir, { recursive: true });
|
|
676
942
|
mkdirSync(this.cacheDir, { recursive: true });
|
|
677
|
-
if (
|
|
678
|
-
const
|
|
679
|
-
if (!
|
|
680
|
-
return
|
|
681
|
-
onProgress?.(`creating isolated Python venv at ${this.venvDir}`);
|
|
682
|
-
const venv = await this._runCommand(python.command, [...python.args, "-m", "venv", this.venvDir]);
|
|
683
|
-
if (!venv.ok)
|
|
684
|
-
return { ok: false, error: `venv-fail: ${sanitizeCommandOutput(venv.error ?? venv.stderr)}` };
|
|
943
|
+
if (this._exists(this.pythonPath)) {
|
|
944
|
+
const repaired = await this.repairExistingVenv(onProgress);
|
|
945
|
+
if (!repaired.ok)
|
|
946
|
+
return repaired;
|
|
685
947
|
}
|
|
686
948
|
if (!this._exists(this.pythonPath)) {
|
|
687
|
-
|
|
949
|
+
const created = await this.createVenv(onProgress);
|
|
950
|
+
if (!created.ok)
|
|
951
|
+
return created;
|
|
688
952
|
}
|
|
953
|
+
const pipReady = await this.ensurePipAvailable(onProgress);
|
|
954
|
+
if (!pipReady.ok)
|
|
955
|
+
return pipReady;
|
|
689
956
|
onProgress?.("upgrading pip inside pi-managed venv");
|
|
690
957
|
const pip = await this.runPython(["-m", "pip", "install", "--upgrade", "pip"], onProgress);
|
|
691
958
|
if (!pip.ok)
|