@caupulican/pi-adaptative 0.81.19 → 0.81.21
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 +20 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +17 -0
- 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/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 +57 -4
- package/dist/core/models/local-runtime.d.ts.map +1 -1
- package/dist/core/models/local-runtime.js +307 -30
- 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/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";
|
|
@@ -54,7 +54,19 @@ const TRANSFORMERS_RUNTIME_DIR_NAME = "hf-transformers";
|
|
|
54
54
|
const TRANSFORMERS_VENV_DIR_NAME = "venv";
|
|
55
55
|
const TRANSFORMERS_SERVER_RELATIVE_PATH = join("runtimes", "hf-transformers-openai-server.py");
|
|
56
56
|
const TRANSFORMERS_PINNED_PACKAGES = ["transformers==5.13.0", "huggingface-hub==1.22.0", "safetensors==0.8.0"];
|
|
57
|
+
const TRANSFORMERS_REQUIRED_MODULES = ["torch", "transformers", "huggingface_hub", "safetensors"];
|
|
57
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
|
+
}
|
|
58
70
|
async function runCommandDefault(command, args, options = {}) {
|
|
59
71
|
try {
|
|
60
72
|
const proc = spawnProcess(command, args, {
|
|
@@ -112,6 +124,8 @@ export class OllamaRuntime {
|
|
|
112
124
|
_fetch;
|
|
113
125
|
_spawn;
|
|
114
126
|
_exists;
|
|
127
|
+
_linkFile;
|
|
128
|
+
_copyFile;
|
|
115
129
|
_envPath;
|
|
116
130
|
_homeDir;
|
|
117
131
|
_sleep;
|
|
@@ -121,12 +135,15 @@ export class OllamaRuntime {
|
|
|
121
135
|
_hasCommand;
|
|
122
136
|
_extractArchiveOverride;
|
|
123
137
|
_child;
|
|
138
|
+
_childModelsDir;
|
|
124
139
|
constructor(args) {
|
|
125
140
|
this._agentDir = args.agentDir;
|
|
126
141
|
this._baseUrl = (args.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
127
142
|
this._fetch = args.deps?.fetchFn ?? fetch;
|
|
128
143
|
this._spawn = args.deps?.spawnFn ?? ((command, argv, options) => spawn(command, argv, options));
|
|
129
144
|
this._exists = args.deps?.existsFn ?? existsSync;
|
|
145
|
+
this._linkFile = args.deps?.linkFile ?? linkSync;
|
|
146
|
+
this._copyFile = args.deps?.copyFile ?? copyFileSync;
|
|
130
147
|
this._envPath = args.deps?.envPath ?? process.env.PATH ?? "";
|
|
131
148
|
this._homeDir = args.deps?.homeDir ?? homedir();
|
|
132
149
|
this._sleep = args.deps?.sleepFn ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
@@ -154,6 +171,35 @@ export class OllamaRuntime {
|
|
|
154
171
|
ownedModelsDir() {
|
|
155
172
|
return join(this._agentDir, "models", "ollama");
|
|
156
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
|
+
}
|
|
157
203
|
_findBinary() {
|
|
158
204
|
const piOwned = join(this._agentDir, "runtimes", "ollama", "bin", "ollama");
|
|
159
205
|
if (this._exists(piOwned))
|
|
@@ -171,27 +217,115 @@ export class OllamaRuntime {
|
|
|
171
217
|
return undefined;
|
|
172
218
|
}
|
|
173
219
|
async _serverUp() {
|
|
220
|
+
return (await this._serverModels()).up;
|
|
221
|
+
}
|
|
222
|
+
async _serverModels() {
|
|
174
223
|
try {
|
|
175
224
|
const response = await this._fetch(`${this._baseUrl}/api/tags`, {
|
|
176
225
|
signal: AbortSignal.timeout(HEALTH_TIMEOUT_MS),
|
|
177
226
|
});
|
|
178
|
-
|
|
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 };
|
|
179
231
|
}
|
|
180
232
|
catch {
|
|
181
|
-
return false;
|
|
233
|
+
return { up: false, modelCount: 0 };
|
|
234
|
+
}
|
|
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 };
|
|
182
253
|
}
|
|
254
|
+
return { kind: "external", path: "external/unknown", modelCount: serverModelCount };
|
|
183
255
|
}
|
|
184
256
|
async detect() {
|
|
185
257
|
const binary = this._findBinary();
|
|
186
|
-
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;
|
|
187
264
|
return {
|
|
188
265
|
binaryPath: binary?.path,
|
|
189
266
|
binarySource: binary?.source,
|
|
190
|
-
serverUp,
|
|
267
|
+
serverUp: serverModels.up,
|
|
191
268
|
serverUrl: this._baseUrl,
|
|
192
|
-
managedByPi: this._child !== undefined &&
|
|
269
|
+
managedByPi: this._child !== undefined && serverModels.up,
|
|
193
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,
|
|
194
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
|
+
}
|
|
195
329
|
}
|
|
196
330
|
/**
|
|
197
331
|
* Manual-steps fallback text — shown when the managed install below wasn't offered (headless),
|
|
@@ -332,9 +466,9 @@ export class OllamaRuntime {
|
|
|
332
466
|
* differs between them (owned storage vs reusing the user's own). */
|
|
333
467
|
async _spawnAndPoll(binary, extraEnv) {
|
|
334
468
|
const host = this._baseUrl.replace(/^https?:\/\//, "");
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
});
|
|
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 });
|
|
338
472
|
this._child.unref?.();
|
|
339
473
|
for (let attempt = 0; attempt < START_POLL_ATTEMPTS; attempt++) {
|
|
340
474
|
if (await this._serverUp())
|
|
@@ -398,6 +532,7 @@ export class OllamaRuntime {
|
|
|
398
532
|
// already gone
|
|
399
533
|
}
|
|
400
534
|
this._child = undefined;
|
|
535
|
+
this._childModelsDir = undefined;
|
|
401
536
|
return { stopped: true };
|
|
402
537
|
}
|
|
403
538
|
async list() {
|
|
@@ -409,6 +544,43 @@ export class OllamaRuntime {
|
|
|
409
544
|
.filter((model) => typeof model.name === "string")
|
|
410
545
|
.map((model) => ({ name: model.name, sizeBytes: model.size ?? 0 }));
|
|
411
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
|
+
}
|
|
412
584
|
async show(ref) {
|
|
413
585
|
try {
|
|
414
586
|
const response = await this._fetch(`${this._baseUrl}/api/show`, {
|
|
@@ -609,9 +781,10 @@ export class TransformersRuntime {
|
|
|
609
781
|
}
|
|
610
782
|
}
|
|
611
783
|
async detect() {
|
|
784
|
+
const serverUp = await this.serverUp();
|
|
612
785
|
return {
|
|
613
|
-
runtimeInstalled: this._exists(this.pythonPath),
|
|
614
|
-
serverUp
|
|
786
|
+
runtimeInstalled: this._exists(this.pythonPath) && (serverUp || (await this.runtimeDependenciesInstalled())),
|
|
787
|
+
serverUp,
|
|
615
788
|
baseUrl: this._baseUrl,
|
|
616
789
|
modelId: this._modelId,
|
|
617
790
|
venvDir: this.venvDir,
|
|
@@ -620,15 +793,11 @@ export class TransformersRuntime {
|
|
|
620
793
|
};
|
|
621
794
|
}
|
|
622
795
|
pythonCandidates() {
|
|
796
|
+
const envPython = process.env.PYTHON?.trim();
|
|
797
|
+
const envCandidates = envPython ? [{ command: envPython, args: [] }] : [];
|
|
623
798
|
return this._platform() === "win32"
|
|
624
|
-
? [
|
|
625
|
-
|
|
626
|
-
{ command: "python", args: [] },
|
|
627
|
-
]
|
|
628
|
-
: [
|
|
629
|
-
{ command: "python3", args: [] },
|
|
630
|
-
{ command: "python", args: [] },
|
|
631
|
-
];
|
|
799
|
+
? [...envCandidates, { command: "py", args: ["-3"] }, { command: "python", args: [] }]
|
|
800
|
+
: [...envCandidates, { command: "python", args: [] }, { command: "python3", args: [] }];
|
|
632
801
|
}
|
|
633
802
|
async findPython() {
|
|
634
803
|
for (const candidate of this.pythonCandidates()) {
|
|
@@ -663,21 +832,127 @@ export class TransformersRuntime {
|
|
|
663
832
|
: undefined,
|
|
664
833
|
});
|
|
665
834
|
}
|
|
835
|
+
async runtimeDependenciesInstalled() {
|
|
836
|
+
if (!this._exists(this.pythonPath))
|
|
837
|
+
return false;
|
|
838
|
+
const modules = JSON.stringify(TRANSFORMERS_REQUIRED_MODULES);
|
|
839
|
+
const result = await this.runPython([
|
|
840
|
+
"-c",
|
|
841
|
+
`import importlib.util, sys; missing = [name for name in ${modules} if importlib.util.find_spec(name) is None]; sys.exit(1 if missing else 0)`,
|
|
842
|
+
]);
|
|
843
|
+
return result.ok;
|
|
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
|
+
}
|
|
666
940
|
async installManaged(onProgress) {
|
|
667
941
|
mkdirSync(this.runtimeDir, { recursive: true });
|
|
668
942
|
mkdirSync(this.cacheDir, { recursive: true });
|
|
669
|
-
if (
|
|
670
|
-
const
|
|
671
|
-
if (!
|
|
672
|
-
return
|
|
673
|
-
onProgress?.(`creating isolated Python venv at ${this.venvDir}`);
|
|
674
|
-
const venv = await this._runCommand(python.command, [...python.args, "-m", "venv", this.venvDir]);
|
|
675
|
-
if (!venv.ok)
|
|
676
|
-
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;
|
|
677
947
|
}
|
|
678
948
|
if (!this._exists(this.pythonPath)) {
|
|
679
|
-
|
|
949
|
+
const created = await this.createVenv(onProgress);
|
|
950
|
+
if (!created.ok)
|
|
951
|
+
return created;
|
|
680
952
|
}
|
|
953
|
+
const pipReady = await this.ensurePipAvailable(onProgress);
|
|
954
|
+
if (!pipReady.ok)
|
|
955
|
+
return pipReady;
|
|
681
956
|
onProgress?.("upgrading pip inside pi-managed venv");
|
|
682
957
|
const pip = await this.runPython(["-m", "pip", "install", "--upgrade", "pip"], onProgress);
|
|
683
958
|
if (!pip.ok)
|
|
@@ -705,6 +980,8 @@ export class TransformersRuntime {
|
|
|
705
980
|
async downloadModel(onProgress) {
|
|
706
981
|
if (!this._exists(this.pythonPath))
|
|
707
982
|
return { ok: false, error: "runtime-missing" };
|
|
983
|
+
if (!(await this.runtimeDependenciesInstalled()))
|
|
984
|
+
return { ok: false, error: "runtime-dependencies-missing" };
|
|
708
985
|
if (!this._exists(this._serverScriptPath))
|
|
709
986
|
return { ok: false, error: `server-script-missing: ${this._serverScriptPath}` };
|
|
710
987
|
mkdirSync(this.cacheDir, { recursive: true });
|
|
@@ -755,7 +1032,7 @@ export class TransformersRuntime {
|
|
|
755
1032
|
"Hugging Face Transformers runtime is not installed for this model.",
|
|
756
1033
|
`Pi can install it into its own venv at ${this.venvDir} and cache weights under ${this.cacheDir}.`,
|
|
757
1034
|
"Manual equivalent:",
|
|
758
|
-
`
|
|
1035
|
+
` python -m venv ${this.venvDir}`,
|
|
759
1036
|
` ${this.pythonPath} -m pip install --upgrade pip`,
|
|
760
1037
|
` ${this.pythonPath} -m pip install ${TRANSFORMERS_PINNED_PACKAGES.join(" ")}`,
|
|
761
1038
|
` ${this.pythonPath} ${this.torchInstallArgs().join(" ")}`,
|