@tuned-tensor/cli 0.26.2 → 0.28.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/README.md +28 -2
- package/dist/{chunk-VQWJ2XKR.js → chunk-7CO5DBCY.js} +448 -123
- package/dist/chunk-7CO5DBCY.js.map +1 -0
- package/dist/{chunk-2AIP63QO.js → chunk-FSVTMX4L.js} +13 -7
- package/dist/chunk-FSVTMX4L.js.map +1 -0
- package/dist/index.js +482 -248
- package/dist/index.js.map +1 -1
- package/dist/local-runtime.js +22 -9
- package/dist/local-runtime.js.map +1 -1
- package/dist/{prefetch-B6I7B4LR.js → prefetch-LBIOO7ZO.js} +2 -2
- package/docs/local-runtime/decision.md +2 -1
- package/docs/local-runtime/hosting.md +121 -0
- package/docs/windows.md +151 -0
- package/package.json +3 -1
- package/training/serving/uv.lock +3 -3
- package/dist/chunk-2AIP63QO.js.map +0 -1
- package/dist/chunk-VQWJ2XKR.js.map +0 -1
- /package/dist/{prefetch-B6I7B4LR.js.map → prefetch-LBIOO7ZO.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -49,6 +49,7 @@ import {
|
|
|
49
49
|
readHardwareSnapshot,
|
|
50
50
|
readJsonlTail,
|
|
51
51
|
readSpecHistory,
|
|
52
|
+
renameAtomicFile,
|
|
52
53
|
resolveProjectConfig,
|
|
53
54
|
runGpuProcess,
|
|
54
55
|
runLocalPipeline,
|
|
@@ -64,7 +65,7 @@ import {
|
|
|
64
65
|
warningsFromSnapshot,
|
|
65
66
|
withAppendLock,
|
|
66
67
|
writePrivateJsonAtomic
|
|
67
|
-
} from "./chunk-
|
|
68
|
+
} from "./chunk-7CO5DBCY.js";
|
|
68
69
|
import {
|
|
69
70
|
DECISION_MODELS,
|
|
70
71
|
DEFAULT_ARTIFACT_ROOT,
|
|
@@ -96,10 +97,10 @@ import {
|
|
|
96
97
|
specLockPath,
|
|
97
98
|
withDecisionPythonEnvironment,
|
|
98
99
|
withFoundationPythonEnvironment
|
|
99
|
-
} from "./chunk-
|
|
100
|
+
} from "./chunk-FSVTMX4L.js";
|
|
100
101
|
|
|
101
102
|
// src/cli.ts
|
|
102
|
-
import { spawn as
|
|
103
|
+
import { spawn as spawn7 } from "child_process";
|
|
103
104
|
import { constants as osConstants2 } from "os";
|
|
104
105
|
import { Command } from "commander";
|
|
105
106
|
import chalk15 from "chalk";
|
|
@@ -661,7 +662,38 @@ import { randomUUID as randomUUID3 } from "crypto";
|
|
|
661
662
|
import { closeSync, openSync } from "fs";
|
|
662
663
|
import { mkdir, open, readdir, readFile as readFile2, rm as rm3, writeFile as writeFile3 } from "fs/promises";
|
|
663
664
|
import { hostname, userInfo } from "os";
|
|
664
|
-
import { basename as basename2, dirname as dirname3, join as
|
|
665
|
+
import { basename as basename2, dirname as dirname3, join as join4 } from "path";
|
|
666
|
+
|
|
667
|
+
// src/local-runtime/windows-child-signals.ts
|
|
668
|
+
import { spawn } from "child_process";
|
|
669
|
+
import { join as join3 } from "path";
|
|
670
|
+
function windowsChildSignals(child) {
|
|
671
|
+
let timer;
|
|
672
|
+
const dispose = () => {
|
|
673
|
+
if (timer) clearTimeout(timer);
|
|
674
|
+
};
|
|
675
|
+
child.once("close", dispose);
|
|
676
|
+
return {
|
|
677
|
+
dispose,
|
|
678
|
+
forward(signal) {
|
|
679
|
+
if (process.platform !== "win32" || !child.connected) return false;
|
|
680
|
+
child.send({ ttSignal: signal }, () => {
|
|
681
|
+
});
|
|
682
|
+
timer ??= setTimeout(() => {
|
|
683
|
+
if (child.pid) {
|
|
684
|
+
spawn(
|
|
685
|
+
join3(process.env.SystemRoot ?? "C:\\Windows", "System32", "taskkill.exe"),
|
|
686
|
+
["/PID", String(child.pid), "/T", "/F"],
|
|
687
|
+
{ windowsHide: true, stdio: "ignore" }
|
|
688
|
+
).on("error", () => child.kill());
|
|
689
|
+
}
|
|
690
|
+
}, 5e3);
|
|
691
|
+
return true;
|
|
692
|
+
}
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// src/local-runtime/background-runs.ts
|
|
665
697
|
var ACTIVE = /* @__PURE__ */ new Set(["starting", "running", "cancel_requested"]);
|
|
666
698
|
var DEFAULT_MAX_PARALLEL_RUNS = 1;
|
|
667
699
|
var MAX_PARALLEL_RUNS_LIMIT = 8;
|
|
@@ -675,7 +707,7 @@ var BackgroundRunError = class extends Error {
|
|
|
675
707
|
}
|
|
676
708
|
};
|
|
677
709
|
function backgroundRunsRoot(env = process.env) {
|
|
678
|
-
return
|
|
710
|
+
return join4(getTunedTensorHome(env), "background-runs");
|
|
679
711
|
}
|
|
680
712
|
function maxParallelRuns(env = process.env) {
|
|
681
713
|
const raw = env.TT_MAX_PARALLEL_RUNS?.trim();
|
|
@@ -736,17 +768,17 @@ function plainLogText(text3) {
|
|
|
736
768
|
}
|
|
737
769
|
function createBackgroundRunStore(root = backgroundRunsRoot()) {
|
|
738
770
|
const paths = (id) => {
|
|
739
|
-
const dir =
|
|
771
|
+
const dir = join4(root, id);
|
|
740
772
|
return {
|
|
741
773
|
dir,
|
|
742
|
-
record:
|
|
743
|
-
spec:
|
|
744
|
-
events:
|
|
745
|
-
log:
|
|
746
|
-
result:
|
|
774
|
+
record: join4(dir, "record.json"),
|
|
775
|
+
spec: join4(dir, "spec.json"),
|
|
776
|
+
events: join4(dir, "events.jsonl"),
|
|
777
|
+
log: join4(dir, "output.log"),
|
|
778
|
+
result: join4(dir, "result.json")
|
|
747
779
|
};
|
|
748
780
|
};
|
|
749
|
-
const lockPath = (id) =>
|
|
781
|
+
const lockPath = (id) => join4(paths(id).dir, ".lock");
|
|
750
782
|
async function readRecord(id) {
|
|
751
783
|
return JSON.parse(await readFile2(paths(id).record, "utf8"));
|
|
752
784
|
}
|
|
@@ -845,7 +877,7 @@ function createBackgroundRunStore(root = backgroundRunsRoot()) {
|
|
|
845
877
|
async create(input, options = {}) {
|
|
846
878
|
const limit = options.maxParallel ?? DEFAULT_MAX_PARALLEL_RUNS;
|
|
847
879
|
await mkdir(root, { recursive: true, mode: 448 });
|
|
848
|
-
return await withAppendLock(
|
|
880
|
+
return await withAppendLock(join4(root, ".launch.lock"), async () => {
|
|
849
881
|
const active = (await list()).filter(isActiveBackgroundRun);
|
|
850
882
|
if (active.length >= limit) {
|
|
851
883
|
throw new BackgroundRunError(
|
|
@@ -896,7 +928,7 @@ function createBackgroundRunStore(root = backgroundRunsRoot()) {
|
|
|
896
928
|
details: { reason }
|
|
897
929
|
});
|
|
898
930
|
const pid = record6.process?.supervisor_pid;
|
|
899
|
-
if (wasRunning && pid !== void 0 && record6.process?.host === hostname()) {
|
|
931
|
+
if (process.platform !== "win32" && wasRunning && pid !== void 0 && record6.process?.host === hostname()) {
|
|
900
932
|
try {
|
|
901
933
|
process.kill(pid, "SIGTERM");
|
|
902
934
|
} catch {
|
|
@@ -1050,9 +1082,9 @@ async function launchBackgroundSupervisor(args2) {
|
|
|
1050
1082
|
stdio: "ignore"
|
|
1051
1083
|
});
|
|
1052
1084
|
if (child.pid === void 0) {
|
|
1053
|
-
await new Promise((
|
|
1085
|
+
await new Promise((resolve14, reject) => {
|
|
1054
1086
|
child.once("error", reject);
|
|
1055
|
-
child.once("spawn", () =>
|
|
1087
|
+
child.once("spawn", () => resolve14());
|
|
1056
1088
|
});
|
|
1057
1089
|
}
|
|
1058
1090
|
} catch (error) {
|
|
@@ -1069,8 +1101,8 @@ async function launchBackgroundSupervisor(args2) {
|
|
|
1069
1101
|
});
|
|
1070
1102
|
}
|
|
1071
1103
|
function workspaceSnapshotPath(run) {
|
|
1072
|
-
const specFile =
|
|
1073
|
-
return
|
|
1104
|
+
const specFile = join4(run.choices.workspace_root, run.choices.spec_path);
|
|
1105
|
+
return join4(dirname3(specFile), `.${basename2(specFile)}.tt-agent-${run.id}.json`);
|
|
1074
1106
|
}
|
|
1075
1107
|
async function superviseBackgroundRun(options) {
|
|
1076
1108
|
const { store } = options;
|
|
@@ -1095,37 +1127,43 @@ async function superviseBackgroundRun(options) {
|
|
|
1095
1127
|
return await finishEarly("failed", `Could not prepare the run: ${error.message}`);
|
|
1096
1128
|
}
|
|
1097
1129
|
let child;
|
|
1130
|
+
let windowsSignals;
|
|
1131
|
+
let cancellationPoll;
|
|
1098
1132
|
const state = { exitCode: null, exitSignal: null };
|
|
1099
1133
|
const stop = (signal) => {
|
|
1100
1134
|
state.stopSignal ??= signal;
|
|
1101
1135
|
if (!child?.pid) return;
|
|
1102
1136
|
try {
|
|
1103
1137
|
if (process.platform !== "win32") process.kill(-child.pid, "SIGTERM");
|
|
1104
|
-
else child.kill("SIGTERM");
|
|
1138
|
+
else if (!windowsSignals?.forward("SIGTERM")) child.kill("SIGTERM");
|
|
1105
1139
|
} catch {
|
|
1106
1140
|
}
|
|
1107
1141
|
};
|
|
1108
|
-
const
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1142
|
+
const onSigterm = () => stop("SIGTERM");
|
|
1143
|
+
const onSigint = () => stop("SIGINT");
|
|
1144
|
+
const onSighup = () => stop("SIGHUP");
|
|
1145
|
+
process.on("SIGTERM", onSigterm);
|
|
1146
|
+
process.on("SIGINT", onSigint);
|
|
1147
|
+
if (process.platform !== "win32") process.on("SIGHUP", onSighup);
|
|
1112
1148
|
try {
|
|
1113
1149
|
const spawnImpl = options.spawn ?? nodeSpawn;
|
|
1114
1150
|
child = spawnImpl(options.execPath ?? process.execPath, [options.entrypoint, "--json", "pipeline", "run", "--spec", snapshot], {
|
|
1115
1151
|
cwd: run.choices.workspace_root,
|
|
1116
|
-
env: options.env ?? process.env,
|
|
1152
|
+
env: process.platform === "win32" ? { ...options.env ?? process.env, TT_PARENT_IPC: "1" } : options.env ?? process.env,
|
|
1117
1153
|
detached: process.platform !== "win32",
|
|
1118
|
-
stdio: ["ignore", resultFd, logFd]
|
|
1154
|
+
stdio: ["ignore", resultFd, logFd, ...process.platform === "win32" ? ["ipc"] : []],
|
|
1155
|
+
windowsHide: true
|
|
1119
1156
|
});
|
|
1120
|
-
|
|
1157
|
+
windowsSignals = windowsChildSignals(child);
|
|
1158
|
+
const closed = new Promise((resolve14) => {
|
|
1121
1159
|
child.once("error", (error) => {
|
|
1122
1160
|
state.spawnError = error;
|
|
1123
|
-
|
|
1161
|
+
resolve14();
|
|
1124
1162
|
});
|
|
1125
1163
|
child.once("close", (code2, signal) => {
|
|
1126
1164
|
state.exitCode = code2;
|
|
1127
1165
|
state.exitSignal = signal;
|
|
1128
|
-
|
|
1166
|
+
resolve14();
|
|
1129
1167
|
});
|
|
1130
1168
|
});
|
|
1131
1169
|
if (child.pid !== void 0) {
|
|
@@ -1135,12 +1173,27 @@ async function superviseBackgroundRun(options) {
|
|
|
1135
1173
|
current.process = { ...current.process, host: hostname(), supervisor_pid: process.pid, pid: child.pid };
|
|
1136
1174
|
}, { type: "started", message: `Pipeline process ${child.pid} started.` });
|
|
1137
1175
|
if ((await store.get(run.id)).status === "cancel_requested") stop("SIGTERM");
|
|
1176
|
+
if (process.platform === "win32") {
|
|
1177
|
+
let polling = false;
|
|
1178
|
+
cancellationPoll = setInterval(() => {
|
|
1179
|
+
if (polling || state.stopSignal) return;
|
|
1180
|
+
polling = true;
|
|
1181
|
+
void store.get(run.id).then((current) => {
|
|
1182
|
+
if (current.status === "cancel_requested") stop("SIGTERM");
|
|
1183
|
+
}).catch(() => {
|
|
1184
|
+
}).finally(() => {
|
|
1185
|
+
polling = false;
|
|
1186
|
+
});
|
|
1187
|
+
}, 250);
|
|
1188
|
+
}
|
|
1138
1189
|
}
|
|
1139
1190
|
await closed;
|
|
1140
1191
|
} finally {
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1192
|
+
if (cancellationPoll) clearInterval(cancellationPoll);
|
|
1193
|
+
windowsSignals?.dispose();
|
|
1194
|
+
process.off("SIGTERM", onSigterm);
|
|
1195
|
+
process.off("SIGINT", onSigint);
|
|
1196
|
+
if (process.platform !== "win32") process.off("SIGHUP", onSighup);
|
|
1144
1197
|
closeSync(logFd);
|
|
1145
1198
|
closeSync(resultFd);
|
|
1146
1199
|
await rm3(snapshot, { force: true });
|
|
@@ -1325,7 +1378,7 @@ function createBackgroundRunExecutor(args2) {
|
|
|
1325
1378
|
// src/agent-labeling.ts
|
|
1326
1379
|
import { createHash as createHash4 } from "crypto";
|
|
1327
1380
|
import { existsSync as existsSync3, lstatSync as lstatSync2, readFileSync as readFileSync4 } from "fs";
|
|
1328
|
-
import { isAbsolute as isAbsolute2, join as
|
|
1381
|
+
import { isAbsolute as isAbsolute2, join as join6, relative as relative4 } from "path";
|
|
1329
1382
|
import { Type } from "typebox";
|
|
1330
1383
|
|
|
1331
1384
|
// src/commands/label.ts
|
|
@@ -1345,7 +1398,7 @@ import {
|
|
|
1345
1398
|
readFileSync,
|
|
1346
1399
|
writeFileSync
|
|
1347
1400
|
} from "fs";
|
|
1348
|
-
import { join as
|
|
1401
|
+
import { join as join5 } from "path";
|
|
1349
1402
|
var AGENT_THINKING_LEVELS = [
|
|
1350
1403
|
"off",
|
|
1351
1404
|
"minimal",
|
|
@@ -1371,7 +1424,7 @@ function validateAccessToken(value) {
|
|
|
1371
1424
|
}
|
|
1372
1425
|
var CONFIG_FILE_NAME = "config.json";
|
|
1373
1426
|
function getConfigPath() {
|
|
1374
|
-
return
|
|
1427
|
+
return join5(getConfigDir(), CONFIG_FILE_NAME);
|
|
1375
1428
|
}
|
|
1376
1429
|
function readConfig() {
|
|
1377
1430
|
const path = getConfigPath();
|
|
@@ -1705,7 +1758,7 @@ function formatCents(cents) {
|
|
|
1705
1758
|
return `$${(cents / 100).toFixed(2)}`;
|
|
1706
1759
|
}
|
|
1707
1760
|
function sleep(ms) {
|
|
1708
|
-
return new Promise((
|
|
1761
|
+
return new Promise((resolve14) => setTimeout(resolve14, ms));
|
|
1709
1762
|
}
|
|
1710
1763
|
function isTransientError(error) {
|
|
1711
1764
|
if (error instanceof ApiError) return error.status === 429 || error.status >= 500;
|
|
@@ -2215,10 +2268,10 @@ function registerLabelCommands(parent) {
|
|
|
2215
2268
|
console.log(`Follow progress with \`tt cloud label watch ${shortId(job.id)}\`.`);
|
|
2216
2269
|
return;
|
|
2217
2270
|
}
|
|
2218
|
-
const
|
|
2219
|
-
if (isJsonMode()) return printJson(
|
|
2271
|
+
const finished2 = await watchJob(job.id, opts);
|
|
2272
|
+
if (isJsonMode()) return printJson(finished2);
|
|
2220
2273
|
printSuccess(
|
|
2221
|
-
`Labeling ${
|
|
2274
|
+
`Labeling ${finished2.status === "awaiting_review" ? "complete" : finished2.status}: ${finished2.labeled_count}/${finished2.row_count} rows labeled.`
|
|
2222
2275
|
);
|
|
2223
2276
|
console.log(
|
|
2224
2277
|
`Review with \`tt cloud label rows ${shortId(job.id)} --status labeled\`, then \`tt cloud label download ${shortId(job.id)}\` to write local training files.`
|
|
@@ -2453,7 +2506,7 @@ async function workspaceFile(workspaceRoot, file) {
|
|
|
2453
2506
|
}
|
|
2454
2507
|
let current = root;
|
|
2455
2508
|
for (const part of parts) {
|
|
2456
|
-
current =
|
|
2509
|
+
current = join6(current, part);
|
|
2457
2510
|
let info;
|
|
2458
2511
|
try {
|
|
2459
2512
|
info = lstatSync2(current);
|
|
@@ -2629,7 +2682,7 @@ async function revalidateLabelDownload(workspaceRoot, args2) {
|
|
|
2629
2682
|
if (isAbsolute2(String(args2.out_dir)) || parts.some((part) => !part || part === "." || part === "..")) {
|
|
2630
2683
|
throw new Error("Download directory must be inside the current workspace.");
|
|
2631
2684
|
}
|
|
2632
|
-
const outDir =
|
|
2685
|
+
const outDir = join6(spec.root, ...parts);
|
|
2633
2686
|
assertLabelDownloadPaths(outDir, args2.overwrite === true);
|
|
2634
2687
|
return {
|
|
2635
2688
|
workspaceRoot,
|
|
@@ -3063,7 +3116,7 @@ async function rejectPreparedAction(action, persist) {
|
|
|
3063
3116
|
// src/agent-model.ts
|
|
3064
3117
|
import { ModelRuntime } from "@earendil-works/pi-coding-agent";
|
|
3065
3118
|
import { chmodSync as chmodSync2, existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
3066
|
-
import { dirname as dirname5, join as
|
|
3119
|
+
import { dirname as dirname5, join as join7 } from "path";
|
|
3067
3120
|
function missingProviderAuthMessage() {
|
|
3068
3121
|
return "No API key is saved for the selected model. Run /login, or /model to pick a different one.";
|
|
3069
3122
|
}
|
|
@@ -3162,10 +3215,10 @@ function resolveAgentModelDefinition(runtime, selection) {
|
|
|
3162
3215
|
return { model, thinking: selection.thinking };
|
|
3163
3216
|
}
|
|
3164
3217
|
function getAgentAuthPath() {
|
|
3165
|
-
return
|
|
3218
|
+
return join7(getAgentConfigDir(), "auth.json");
|
|
3166
3219
|
}
|
|
3167
3220
|
function getAgentModelsPath() {
|
|
3168
|
-
return
|
|
3221
|
+
return join7(getAgentConfigDir(), "models.json");
|
|
3169
3222
|
}
|
|
3170
3223
|
async function createPiModelRuntime(env = process.env) {
|
|
3171
3224
|
const agentDir = getAgentConfigDir();
|
|
@@ -3577,7 +3630,7 @@ async function fetchHuggingFaceSearch(kind, query, limit, filters, options) {
|
|
|
3577
3630
|
let lastError = new Error("Hugging Face search failed.");
|
|
3578
3631
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
3579
3632
|
if (attempt > 0) {
|
|
3580
|
-
await new Promise((
|
|
3633
|
+
await new Promise((resolve14) => setTimeout(resolve14, options.huggingFaceRetryDelayMs ?? 750));
|
|
3581
3634
|
}
|
|
3582
3635
|
try {
|
|
3583
3636
|
const payload = await fetchHuggingFaceJson(url, options);
|
|
@@ -3958,7 +4011,7 @@ function createTunedTensorTools(api, options = {}) {
|
|
|
3958
4011
|
dry_run: "tt pipeline run --dry-run --spec tunedtensor.json",
|
|
3959
4012
|
run: "tt pipeline run --spec tunedtensor.json"
|
|
3960
4013
|
},
|
|
3961
|
-
note: "Runs locally on CPU, Apple MPS or CUDA; runtime.gpu (AWS) is not used. The tuned model directory loads in Python with laya.load(<model_dir>).",
|
|
4014
|
+
note: "Runs locally on CPU, Apple MPS or CUDA; runtime.gpu (AWS) is not used. The tuned model directory loads in Python with laya.load(<model_dir>). For paid serverless CPU hosting, the user can run tt cloud endpoints create --model <model_dir> --name <name>; see docs/local-runtime/hosting.md. This uploads weights to the TT account, returns an authenticated inference URL and scoped key, and charges prepaid credits per successful request.",
|
|
3962
4015
|
...host ? { host } : {}
|
|
3963
4016
|
};
|
|
3964
4017
|
}
|
|
@@ -4142,11 +4195,21 @@ function createTunedTensorTools(api, options = {}) {
|
|
|
4142
4195
|
}, { additionalProperties: false }),
|
|
4143
4196
|
async (p) => {
|
|
4144
4197
|
const current = await inspectLocalSpec(workspaceRoot, p.spec_path);
|
|
4198
|
+
const spec = record4(current.document);
|
|
4199
|
+
const inline = current.validation.valid && spec && (spec.engine ?? "adapter") === "adapter" && !spec.dataset_prebuilt && Array.isArray(spec.examples) ? spec.examples : void 0;
|
|
4200
|
+
const split = inline ? splitSpecExamples(inline, 0) : void 0;
|
|
4145
4201
|
return {
|
|
4146
4202
|
spec_path: current.displayPath,
|
|
4147
4203
|
sha256: current.sha256,
|
|
4148
4204
|
spec: current.document ?? current.source,
|
|
4149
|
-
validation: current.validation
|
|
4205
|
+
validation: current.validation,
|
|
4206
|
+
...split ? { inline_data_split: {
|
|
4207
|
+
method: "deterministic_holdout",
|
|
4208
|
+
training_examples: split.train.length,
|
|
4209
|
+
heldout_examples: split.holdout.length,
|
|
4210
|
+
real_training_minimum_examples: 2,
|
|
4211
|
+
note: "Inline adapter data is split automatically. A small held-out set is not evidence of model quality."
|
|
4212
|
+
} } : {}
|
|
4150
4213
|
};
|
|
4151
4214
|
}
|
|
4152
4215
|
),
|
|
@@ -4485,6 +4548,8 @@ function createTunedTensorTools(api, options = {}) {
|
|
|
4485
4548
|
// src/local-agent-client.ts
|
|
4486
4549
|
var MAX_TOOL_CALLS_PER_TURN = 12;
|
|
4487
4550
|
var SYSTEM_PROMPT = `You are the local Tuned Tensor assistant running on the user's laptop.
|
|
4551
|
+
Use get_local_spec's inline_data_split counts when describing adapter training and evaluation data. An automatically held-out set is distinct from training, but a small set is not evidence of model quality.
|
|
4552
|
+
Downloads may also write TT manifests, reports and logs. Summarize the proposed effects without claiming the cache and lock are the only writes.
|
|
4488
4553
|
The workspace tunedtensor.json behavior spec is the source of truth for model behavior, training, evaluation policy, runtime placement and optional pipeline settings. For requests to change the model's behavior, inspect it with get_local_spec first, then prepare_update_local_spec with the returned SHA-256 and only the requested field changes; use add_examples to append examples. Preserve unrelated examples, instructions, engine and training settings. Explain the proposed diff and validation; never claim an edit was saved before approval. Read-only questions about the current spec use get_local_spec without proposing an edit. If no spec exists, help prepare a new one with prepare_create_local_spec; it is created as tunedtensor.json in the current workspace. For an initial draft, default to three concise representative examples unless the user requests otherwise; more examples can be added later. Malformed JSON must be repaired in an editor before prepare_update_local_spec can operate; never offer that tool as a way to reconstruct a truncated file. For direct review, point users to /spec (a readable overview), /spec <section> such as /spec examples, /spec show for the JSON, and /spec diff, /spec validate and /spec history. General educational or inspection questions do not require a spec mutation.
|
|
4489
4554
|
Local workflow commands need no TT access token. For local inspection commands such as runs, models, or doctor, tell the user to run the matching TT command in this shell (for example \`runs list\` or \`doctor\`); those commands execute outside the agent. User-owned AWS GPU training uses \`tt pipeline run\` with runtime.gpu configuration in tunedtensor.json, without a TT token. \`tt cloud\` is for TT account records, not GPU execution; hosted training start/estimate are retired. AWS credentials authorize instance lookup; separate SSH access is required. The instance must already be running, and the user manages quota, capacity, costs and shutdown. Keep the laptop running until outputs return. Model-provider choice is independent of GPU placement.
|
|
4490
4555
|
When the user requests commands to inspect a run or serve a result, provide the direct CLI handoff: \`tt runs list\` to find a run, \`tt runs get <run-id>\` for its details, \`tt runs report <run-id>\` for its evaluation report, \`tt models list\` to find the resulting model ID, and \`tt serve <model-id>\` to serve that model. Replace placeholders with actual IDs from those commands; never invent results or IDs. Until the user supplies command output, keep literal <run-id> and <model-id> placeholders; do not fabricate example IDs or prefixes. Local run IDs are UUIDs and adapter model IDs are local-<run-id>. Do not refresh hardware or propose spec edits merely to supply those commands; perform those actions only when requested.
|
|
@@ -4602,11 +4667,11 @@ function createLocalAgentClient(options) {
|
|
|
4602
4667
|
const runs = options.backgroundRuns?.store;
|
|
4603
4668
|
if (!runs) return void 0;
|
|
4604
4669
|
try {
|
|
4605
|
-
const
|
|
4606
|
-
if (
|
|
4607
|
-
await runs.markReported(
|
|
4670
|
+
const finished2 = (await runs.list()).filter((run) => run.requested_by.thread_id === threadId && !isActiveBackgroundRun(run) && !run.reported_at);
|
|
4671
|
+
if (finished2.length === 0) return void 0;
|
|
4672
|
+
await runs.markReported(finished2.map((run) => run.id));
|
|
4608
4673
|
return `[Background run outcomes, recorded by tt]
|
|
4609
|
-
${
|
|
4674
|
+
${finished2.map((run) => JSON.stringify(describeBackgroundRun(run))).join("\n")}
|
|
4610
4675
|
These runs finished since your last turn. Mention them briefly when relevant; use get_background_run for logs.`;
|
|
4611
4676
|
} catch {
|
|
4612
4677
|
return void 0;
|
|
@@ -4851,13 +4916,12 @@ import {
|
|
|
4851
4916
|
mkdir as mkdir2,
|
|
4852
4917
|
readFile as readFile3,
|
|
4853
4918
|
readdir as readdir2,
|
|
4854
|
-
rename,
|
|
4855
4919
|
stat,
|
|
4856
4920
|
unlink,
|
|
4857
4921
|
writeFile as writeFile4
|
|
4858
4922
|
} from "fs/promises";
|
|
4859
4923
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
4860
|
-
import { join as
|
|
4924
|
+
import { join as join8 } from "path";
|
|
4861
4925
|
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
4862
4926
|
var DEFAULT_MAX_THREAD_BYTES = 1e6;
|
|
4863
4927
|
var DEFAULT_MAX_MESSAGES = 200;
|
|
@@ -4888,8 +4952,8 @@ function scrub(value, secrets) {
|
|
|
4888
4952
|
var LocalAgentStore = class {
|
|
4889
4953
|
constructor(rootDir, options = {}) {
|
|
4890
4954
|
this.rootDir = rootDir;
|
|
4891
|
-
this.threadsDir =
|
|
4892
|
-
this.claimsDir =
|
|
4955
|
+
this.threadsDir = join8(rootDir, "threads");
|
|
4956
|
+
this.claimsDir = join8(rootDir, "action-claims");
|
|
4893
4957
|
this.secrets = options.secretValues?.filter(Boolean) ?? [];
|
|
4894
4958
|
this.secretValueProvider = options.secretValueProvider;
|
|
4895
4959
|
this.maxThreadBytes = options.maxThreadBytes ?? DEFAULT_MAX_THREAD_BYTES;
|
|
@@ -4927,7 +4991,7 @@ var LocalAgentStore = class {
|
|
|
4927
4991
|
}
|
|
4928
4992
|
path(id) {
|
|
4929
4993
|
assertThreadId(id);
|
|
4930
|
-
return
|
|
4994
|
+
return join8(this.threadsDir, `${id}.json`);
|
|
4931
4995
|
}
|
|
4932
4996
|
serializeWithinQuota(state) {
|
|
4933
4997
|
const active = state.actions.filter(
|
|
@@ -4966,11 +5030,11 @@ var LocalAgentStore = class {
|
|
|
4966
5030
|
return serialized;
|
|
4967
5031
|
}
|
|
4968
5032
|
async pruneDirectory(directory, suffix, maximum, preserve) {
|
|
4969
|
-
const entries = await Promise.all((await readdir2(directory)).filter((name) => suffix.test(name) && name !== preserve).map(async (name) => ({ name, modified: (await stat(
|
|
5033
|
+
const entries = await Promise.all((await readdir2(directory)).filter((name) => suffix.test(name) && name !== preserve).map(async (name) => ({ name, modified: (await stat(join8(directory, name))).mtimeMs })));
|
|
4970
5034
|
entries.sort((a, b) => a.modified - b.modified);
|
|
4971
5035
|
const keepOther = Math.max(0, maximum - (preserve ? 1 : 0));
|
|
4972
5036
|
for (const entry of entries.slice(0, Math.max(0, entries.length - keepOther))) {
|
|
4973
|
-
await unlink(
|
|
5037
|
+
await unlink(join8(directory, entry.name));
|
|
4974
5038
|
}
|
|
4975
5039
|
}
|
|
4976
5040
|
async pruneThreads(preserve) {
|
|
@@ -4990,19 +5054,19 @@ var LocalAgentStore = class {
|
|
|
4990
5054
|
}
|
|
4991
5055
|
const candidates = await Promise.all(names.filter((name) => !protectedNames.has(name)).map(async (name) => ({
|
|
4992
5056
|
name,
|
|
4993
|
-
modified: (await stat(
|
|
5057
|
+
modified: (await stat(join8(this.threadsDir, name))).mtimeMs
|
|
4994
5058
|
})));
|
|
4995
5059
|
candidates.sort((a, b) => a.modified - b.modified);
|
|
4996
5060
|
const keep = Math.max(0, this.maxThreads - protectedNames.size);
|
|
4997
5061
|
for (const entry of candidates.slice(0, Math.max(0, candidates.length - keep))) {
|
|
4998
|
-
await unlink(
|
|
5062
|
+
await unlink(join8(this.threadsDir, entry.name));
|
|
4999
5063
|
}
|
|
5000
5064
|
}
|
|
5001
5065
|
async save(state) {
|
|
5002
5066
|
assertThreadId(state.thread.id);
|
|
5003
5067
|
await this.ensureDirectories();
|
|
5004
5068
|
const destination = this.path(state.thread.id);
|
|
5005
|
-
const temporary =
|
|
5069
|
+
const temporary = join8(
|
|
5006
5070
|
this.threadsDir,
|
|
5007
5071
|
`.${state.thread.id}.${randomUUID6()}.tmp`
|
|
5008
5072
|
);
|
|
@@ -5015,14 +5079,13 @@ var LocalAgentStore = class {
|
|
|
5015
5079
|
}
|
|
5016
5080
|
await writeFile4(temporary, serialized, { mode: 384, flag: "wx" });
|
|
5017
5081
|
await chmod(temporary, 384);
|
|
5018
|
-
await
|
|
5019
|
-
await chmod(destination, 384);
|
|
5082
|
+
await renameAtomicFile(temporary, destination);
|
|
5020
5083
|
await this.pruneThreads(`${state.thread.id}.json`);
|
|
5021
5084
|
}
|
|
5022
5085
|
async claimAction(actionId) {
|
|
5023
5086
|
assertThreadId(actionId);
|
|
5024
5087
|
await this.ensureDirectories();
|
|
5025
|
-
const path =
|
|
5088
|
+
const path = join8(this.claimsDir, `${actionId}.claim`);
|
|
5026
5089
|
try {
|
|
5027
5090
|
await writeFile4(path, `${(/* @__PURE__ */ new Date()).toISOString()}
|
|
5028
5091
|
`, {
|
|
@@ -6331,7 +6394,7 @@ function createCommandCompleter(getMode = () => "local") {
|
|
|
6331
6394
|
|
|
6332
6395
|
// src/shell-context.ts
|
|
6333
6396
|
import { readFile as readFile4, readdir as readdir3, stat as stat2 } from "fs/promises";
|
|
6334
|
-
import { basename as basename5, dirname as dirname6, join as
|
|
6397
|
+
import { basename as basename5, dirname as dirname6, join as join9, resolve as resolve3 } from "path";
|
|
6335
6398
|
async function readJsonObject(path) {
|
|
6336
6399
|
try {
|
|
6337
6400
|
const parsed = JSON.parse(await readFile4(path, "utf8"));
|
|
@@ -6358,7 +6421,7 @@ function expandPath(value, baseDirectory, env) {
|
|
|
6358
6421
|
return expandUserPath(value, env, baseDirectory);
|
|
6359
6422
|
}
|
|
6360
6423
|
function configPath(env) {
|
|
6361
|
-
return
|
|
6424
|
+
return join9(getConfigDir(env), "config.json");
|
|
6362
6425
|
}
|
|
6363
6426
|
function agentSelectionFrom(env, configAgent, hasToken) {
|
|
6364
6427
|
const stored = configAgent && typeof configAgent === "object" && !Array.isArray(configAgent) ? configAgent : {};
|
|
@@ -6376,14 +6439,14 @@ async function adjacentFile(path) {
|
|
|
6376
6439
|
}
|
|
6377
6440
|
}
|
|
6378
6441
|
async function readActiveModelId(storeRoot) {
|
|
6379
|
-
const result2 = await readJsonObject(
|
|
6442
|
+
const result2 = await readJsonObject(join9(storeRoot, "active-model.json"));
|
|
6380
6443
|
if (!result2.found || result2.invalid) return void 0;
|
|
6381
6444
|
const modelId = result2.value?.model_id;
|
|
6382
6445
|
if (modelId === null) return "base";
|
|
6383
6446
|
return typeof modelId === "string" && modelId ? modelId : void 0;
|
|
6384
6447
|
}
|
|
6385
6448
|
async function readLatestRun(storeRoot) {
|
|
6386
|
-
const runsRoot =
|
|
6449
|
+
const runsRoot = join9(storeRoot, "runs");
|
|
6387
6450
|
let names;
|
|
6388
6451
|
try {
|
|
6389
6452
|
names = (await readdir3(runsRoot, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
@@ -6391,7 +6454,7 @@ async function readLatestRun(storeRoot) {
|
|
|
6391
6454
|
return void 0;
|
|
6392
6455
|
}
|
|
6393
6456
|
const states = await Promise.all(names.map(async (name) => {
|
|
6394
|
-
const result2 = await readJsonObject(
|
|
6457
|
+
const result2 = await readJsonObject(join9(runsRoot, name, "state.json"));
|
|
6395
6458
|
if (!result2.found || result2.invalid || !result2.value) return void 0;
|
|
6396
6459
|
const id = stringField(result2.value, "id") ?? name;
|
|
6397
6460
|
const state = {
|
|
@@ -6428,7 +6491,7 @@ async function discoverShellContext(options = {}) {
|
|
|
6428
6491
|
const env = options.env ?? process.env;
|
|
6429
6492
|
const cwd = resolve3(options.cwd ?? process.cwd());
|
|
6430
6493
|
const warnings = [];
|
|
6431
|
-
const specPath =
|
|
6494
|
+
const specPath = join9(cwd, "tunedtensor.json");
|
|
6432
6495
|
const specJson = await readJsonObject(specPath);
|
|
6433
6496
|
let spec;
|
|
6434
6497
|
if (specJson.found) {
|
|
@@ -6453,7 +6516,7 @@ async function discoverShellContext(options = {}) {
|
|
|
6453
6516
|
};
|
|
6454
6517
|
}
|
|
6455
6518
|
}
|
|
6456
|
-
const localConfigPath =
|
|
6519
|
+
const localConfigPath = join9(cwd, "local-runner.json");
|
|
6457
6520
|
const hasAdjacentLocalConfig = await adjacentFile(localConfigPath);
|
|
6458
6521
|
const localConfigJson = hasAdjacentLocalConfig ? await readJsonObject(localConfigPath) : { found: false, invalid: false };
|
|
6459
6522
|
if (localConfigJson.invalid) {
|
|
@@ -6787,12 +6850,12 @@ async function promptKeyChoice(message, input = stdin, output = stdout) {
|
|
|
6787
6850
|
terminal.setRawMode(true);
|
|
6788
6851
|
input.resume();
|
|
6789
6852
|
output.write(`${message} ${hint()}`);
|
|
6790
|
-
const choice = await new Promise((
|
|
6853
|
+
const choice = await new Promise((resolve14) => {
|
|
6791
6854
|
const finish = (decision) => {
|
|
6792
6855
|
input.removeListener("keypress", onKeypress);
|
|
6793
6856
|
input.removeListener("end", onEnd);
|
|
6794
6857
|
input.removeListener("close", onEnd);
|
|
6795
|
-
|
|
6858
|
+
resolve14(decision);
|
|
6796
6859
|
};
|
|
6797
6860
|
const onKeypress = (text3, key) => {
|
|
6798
6861
|
const decided = keyChoiceFor(key, text3);
|
|
@@ -6834,7 +6897,7 @@ function operatorError(operator) {
|
|
|
6834
6897
|
`Shell operator ${JSON.stringify(operator)} is not supported. Run TT commands directly; pipes, redirects, and command substitution are disabled.`
|
|
6835
6898
|
);
|
|
6836
6899
|
}
|
|
6837
|
-
function tokenizeShellInput(input) {
|
|
6900
|
+
function tokenizeShellInput(input, platform = process.platform) {
|
|
6838
6901
|
if (input.includes("\0")) {
|
|
6839
6902
|
throw new ShellParseError("Command input must not contain NUL bytes.");
|
|
6840
6903
|
}
|
|
@@ -6869,7 +6932,7 @@ function tokenizeShellInput(input) {
|
|
|
6869
6932
|
quote = "none";
|
|
6870
6933
|
continue;
|
|
6871
6934
|
}
|
|
6872
|
-
if (character === "\\") {
|
|
6935
|
+
if (character === "\\" && platform !== "win32") {
|
|
6873
6936
|
const escaped = input[index + 1];
|
|
6874
6937
|
if (escaped === void 0) {
|
|
6875
6938
|
throw new ShellParseError("Command ends with an unfinished escape.");
|
|
@@ -6901,7 +6964,7 @@ function tokenizeShellInput(input) {
|
|
|
6901
6964
|
tokenStarted = true;
|
|
6902
6965
|
continue;
|
|
6903
6966
|
}
|
|
6904
|
-
if (character === "\\") {
|
|
6967
|
+
if (character === "\\" && platform !== "win32") {
|
|
6905
6968
|
const escaped = input[index + 1];
|
|
6906
6969
|
if (escaped === void 0) {
|
|
6907
6970
|
throw new ShellParseError("Command ends with an unfinished escape.");
|
|
@@ -8446,7 +8509,7 @@ var TunedTensorAgentSession = class {
|
|
|
8446
8509
|
// src/local-runner.ts
|
|
8447
8510
|
import { constants as osConstants } from "os";
|
|
8448
8511
|
import { randomUUID as randomUUID7 } from "crypto";
|
|
8449
|
-
import { spawn } from "child_process";
|
|
8512
|
+
import { spawn as spawn2 } from "child_process";
|
|
8450
8513
|
import { existsSync as existsSync5 } from "fs";
|
|
8451
8514
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
8452
8515
|
import {
|
|
@@ -8459,7 +8522,7 @@ import {
|
|
|
8459
8522
|
basename as basename6,
|
|
8460
8523
|
dirname as dirname7,
|
|
8461
8524
|
isAbsolute as isAbsolute4,
|
|
8462
|
-
join as
|
|
8525
|
+
join as join10,
|
|
8463
8526
|
resolve as resolve5
|
|
8464
8527
|
} from "path";
|
|
8465
8528
|
|
|
@@ -9248,17 +9311,17 @@ function positionalSpecArgument(args2, start, defaultPath) {
|
|
|
9248
9311
|
function selectedSpecArgument(args2, cwd) {
|
|
9249
9312
|
const command2 = args2[0];
|
|
9250
9313
|
if (["doctor", "validate", "run"].includes(command2 ?? "")) {
|
|
9251
|
-
return positionalSpecArgument(args2, 1,
|
|
9314
|
+
return positionalSpecArgument(args2, 1, join10(cwd, DEFAULT_SPEC_NAME));
|
|
9252
9315
|
}
|
|
9253
9316
|
if (command2 === "models" && ["prefetch", "verify-base"].includes(args2[1] ?? "")) {
|
|
9254
|
-
return positionalSpecArgument(args2, 2,
|
|
9317
|
+
return positionalSpecArgument(args2, 2, join10(cwd, DEFAULT_SPEC_NAME));
|
|
9255
9318
|
}
|
|
9256
9319
|
if (command2 === "datasets") {
|
|
9257
9320
|
if (args2[1] === void 0 || args2[1].startsWith("-") || args2[1].endsWith(".json")) {
|
|
9258
9321
|
args2.splice(1, 0, "prefetch");
|
|
9259
9322
|
}
|
|
9260
9323
|
if (["prefetch", "verify"].includes(args2[1] ?? "")) {
|
|
9261
|
-
return positionalSpecArgument(args2, 2,
|
|
9324
|
+
return positionalSpecArgument(args2, 2, join10(cwd, DEFAULT_SPEC_NAME));
|
|
9262
9325
|
}
|
|
9263
9326
|
}
|
|
9264
9327
|
const serves = command2 === "serve" || command2 === "models" && args2[1] === "serve";
|
|
@@ -9346,7 +9409,7 @@ async function projectSpecArguments(originalArgs, cwd) {
|
|
|
9346
9409
|
}
|
|
9347
9410
|
};
|
|
9348
9411
|
}
|
|
9349
|
-
const temporaryPath =
|
|
9412
|
+
const temporaryPath = join10(
|
|
9350
9413
|
dirname7(sourcePath),
|
|
9351
9414
|
`.${basename6(sourcePath)}.tt-local-${process.pid}-${randomUUID7()}.json`
|
|
9352
9415
|
);
|
|
@@ -9416,18 +9479,20 @@ async function spawnLocalCli(args2) {
|
|
|
9416
9479
|
const childEnv = errorTerminal.isTTY && !args2.jsonMode && args2.env.TT_PROGRESS_TTY === void 0 ? { ...args2.env, TT_PROGRESS_TTY: "1", TT_PROGRESS_COLUMNS: String(errorTerminal.columns ?? 100) } : args2.env;
|
|
9417
9480
|
const pipeInput = Boolean(args2.stdin && args2.stdin !== process.stdin);
|
|
9418
9481
|
const pipeStreamingOutput = args2.streamingStdout && Boolean(args2.stdout && args2.stdout !== process.stdout);
|
|
9419
|
-
const child =
|
|
9482
|
+
const child = spawn2(
|
|
9420
9483
|
args2.nodeExecutable,
|
|
9421
9484
|
[args2.entrypoint, ...args2.commandArgs],
|
|
9422
9485
|
{
|
|
9423
9486
|
cwd: args2.cwd,
|
|
9424
|
-
env: childEnv,
|
|
9487
|
+
env: process.platform === "win32" ? { ...childEnv, TT_PARENT_IPC: "1" } : childEnv,
|
|
9425
9488
|
stdio: [
|
|
9426
9489
|
pipeInput ? "pipe" : "inherit",
|
|
9427
9490
|
args2.streamingStdout && !pipeStreamingOutput ? "inherit" : "pipe",
|
|
9428
|
-
"pipe"
|
|
9491
|
+
"pipe",
|
|
9492
|
+
...process.platform === "win32" ? ["ipc"] : []
|
|
9429
9493
|
],
|
|
9430
|
-
detached: process.platform !== "win32"
|
|
9494
|
+
detached: process.platform !== "win32",
|
|
9495
|
+
windowsHide: true
|
|
9431
9496
|
}
|
|
9432
9497
|
);
|
|
9433
9498
|
if (pipeInput && args2.stdin && child.stdin) args2.stdin.pipe(child.stdin);
|
|
@@ -9456,8 +9521,10 @@ async function spawnLocalCli(args2) {
|
|
|
9456
9521
|
});
|
|
9457
9522
|
return new Promise((resolveChild) => {
|
|
9458
9523
|
let spawnError;
|
|
9524
|
+
const windowsSignals = windowsChildSignals(child);
|
|
9459
9525
|
const forwardSignal = (signal) => {
|
|
9460
9526
|
const childSignal = signal === "SIGHUP" ? "SIGTERM" : signal;
|
|
9527
|
+
if (windowsSignals.forward(childSignal)) return;
|
|
9461
9528
|
if (child.pid && process.platform !== "win32") {
|
|
9462
9529
|
try {
|
|
9463
9530
|
process.kill(-child.pid, childSignal);
|
|
@@ -9470,14 +9537,16 @@ async function spawnLocalCli(args2) {
|
|
|
9470
9537
|
const onSigint = () => forwardSignal("SIGINT");
|
|
9471
9538
|
const onSigterm = () => forwardSignal("SIGTERM");
|
|
9472
9539
|
const onSighup = () => forwardSignal("SIGHUP");
|
|
9540
|
+
const signals = args2.signals ?? process;
|
|
9473
9541
|
const cleanup = () => {
|
|
9474
|
-
|
|
9475
|
-
|
|
9476
|
-
|
|
9542
|
+
windowsSignals.dispose();
|
|
9543
|
+
signals.off("SIGINT", onSigint);
|
|
9544
|
+
signals.off("SIGTERM", onSigterm);
|
|
9545
|
+
if (process.platform !== "win32") signals.off("SIGHUP", onSighup);
|
|
9477
9546
|
};
|
|
9478
|
-
|
|
9479
|
-
|
|
9480
|
-
if (process.platform !== "win32")
|
|
9547
|
+
signals.on("SIGINT", onSigint);
|
|
9548
|
+
signals.on("SIGTERM", onSigterm);
|
|
9549
|
+
if (process.platform !== "win32") signals.on("SIGHUP", onSighup);
|
|
9481
9550
|
child.once("error", (error) => {
|
|
9482
9551
|
spawnError = error;
|
|
9483
9552
|
});
|
|
@@ -9528,6 +9597,7 @@ async function runLocalCommand(args2, options = {}) {
|
|
|
9528
9597
|
stdin: options.stdin,
|
|
9529
9598
|
stdout: options.stdout,
|
|
9530
9599
|
stderr: options.stderr,
|
|
9600
|
+
signals: options.signals,
|
|
9531
9601
|
pathReplacements: projected.pathReplacements
|
|
9532
9602
|
});
|
|
9533
9603
|
const parsed = parseJsonOutput(result2.stdout);
|
|
@@ -9715,11 +9785,11 @@ function registerAgentCommands(parent, options) {
|
|
|
9715
9785
|
|
|
9716
9786
|
// src/commands/pipeline.ts
|
|
9717
9787
|
import { existsSync as existsSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
|
|
9718
|
-
import { basename as basename8, dirname as dirname11, join as
|
|
9788
|
+
import { basename as basename8, dirname as dirname11, join as join14, resolve as resolve8 } from "path";
|
|
9719
9789
|
|
|
9720
9790
|
// src/local-runtime/project-migration.ts
|
|
9721
|
-
import { lstat as lstat3, readFile as readFile6, rename
|
|
9722
|
-
import { dirname as dirname8, join as
|
|
9791
|
+
import { lstat as lstat3, readFile as readFile6, rename, writeFile as writeFile6, unlink as unlink2 } from "fs/promises";
|
|
9792
|
+
import { dirname as dirname8, join as join11 } from "path";
|
|
9723
9793
|
async function migrateProjectWorkflow(specPath) {
|
|
9724
9794
|
const info = await lstat3(specPath);
|
|
9725
9795
|
if (!info.isFile() || info.isSymbolicLink()) throw new Error("Migration requires a regular spec file.");
|
|
@@ -9730,7 +9800,7 @@ async function migrateProjectWorkflow(specPath) {
|
|
|
9730
9800
|
const files = [];
|
|
9731
9801
|
const sources = /* @__PURE__ */ new Map([[specPath, original]]);
|
|
9732
9802
|
for (const name of ["tunedtensor.pipeline.json", "local-runner.json"]) {
|
|
9733
|
-
const path =
|
|
9803
|
+
const path = join11(dirname8(specPath), name);
|
|
9734
9804
|
const entry = await lstat3(path).catch((error) => {
|
|
9735
9805
|
if (error.code === "ENOENT") return void 0;
|
|
9736
9806
|
throw error;
|
|
@@ -9779,7 +9849,7 @@ async function migrateProjectWorkflow(specPath) {
|
|
|
9779
9849
|
throw new Error(`Configuration changed during migration: ${path}; retry after reviewing it.`);
|
|
9780
9850
|
}
|
|
9781
9851
|
}
|
|
9782
|
-
await
|
|
9852
|
+
await rename(temp, specPath);
|
|
9783
9853
|
} catch (error) {
|
|
9784
9854
|
if (createdTemp) await unlink2(temp).catch(() => {
|
|
9785
9855
|
});
|
|
@@ -9797,8 +9867,8 @@ async function migrateProjectWorkflow(specPath) {
|
|
|
9797
9867
|
// src/local-runtime/foundation-runner.ts
|
|
9798
9868
|
import { createHash as createHash7, randomUUID as randomUUID8 } from "crypto";
|
|
9799
9869
|
import { createReadStream } from "fs";
|
|
9800
|
-
import { chmod as chmod2, lstat as lstat4, mkdir as mkdir3, open as open2, readdir as readdir4, readFile as readFile7, rename as
|
|
9801
|
-
import { basename as basename7, dirname as dirname9, join as
|
|
9870
|
+
import { chmod as chmod2, lstat as lstat4, mkdir as mkdir3, open as open2, readdir as readdir4, readFile as readFile7, rename as rename2, rm as rm5 } from "fs/promises";
|
|
9871
|
+
import { basename as basename7, dirname as dirname9, join as join12, resolve as resolve6 } from "path";
|
|
9802
9872
|
var INSTRUCTION_CORPUS_VERSION = 1;
|
|
9803
9873
|
function isRef(value) {
|
|
9804
9874
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value) && typeof value.from === "string";
|
|
@@ -9827,7 +9897,7 @@ async function hashPath(path) {
|
|
|
9827
9897
|
if (info.isDirectory()) {
|
|
9828
9898
|
for (const name of (await readdir4(path)).sort()) {
|
|
9829
9899
|
hash.update(name);
|
|
9830
|
-
hash.update(await hashPath(
|
|
9900
|
+
hash.update(await hashPath(join12(path, name)));
|
|
9831
9901
|
}
|
|
9832
9902
|
return hash.digest("hex");
|
|
9833
9903
|
}
|
|
@@ -9850,7 +9920,7 @@ async function makePrivateTree(path) {
|
|
|
9850
9920
|
if (info.isDirectory()) {
|
|
9851
9921
|
await chmod2(path, 448);
|
|
9852
9922
|
for (const name of await readdir4(path)) {
|
|
9853
|
-
await makePrivateTree(
|
|
9923
|
+
await makePrivateTree(join12(path, name));
|
|
9854
9924
|
}
|
|
9855
9925
|
return;
|
|
9856
9926
|
}
|
|
@@ -9879,7 +9949,7 @@ async function readRequiredJsonObject(path, description) {
|
|
|
9879
9949
|
return parsed;
|
|
9880
9950
|
}
|
|
9881
9951
|
async function writePrivateTextAtomic(path, content) {
|
|
9882
|
-
const temporary =
|
|
9952
|
+
const temporary = join12(dirname9(path), `.${basename7(path)}.${randomUUID8()}.tmp`);
|
|
9883
9953
|
const file = await open2(temporary, "wx", 384);
|
|
9884
9954
|
let closed = false;
|
|
9885
9955
|
try {
|
|
@@ -9888,7 +9958,7 @@ async function writePrivateTextAtomic(path, content) {
|
|
|
9888
9958
|
await file.sync();
|
|
9889
9959
|
await file.close();
|
|
9890
9960
|
closed = true;
|
|
9891
|
-
await
|
|
9961
|
+
await rename2(temporary, path);
|
|
9892
9962
|
} finally {
|
|
9893
9963
|
if (!closed) await file.close().catch(() => void 0);
|
|
9894
9964
|
await rm5(temporary, { force: true }).catch(() => void 0);
|
|
@@ -9995,7 +10065,7 @@ async function runFoundationPipeline(args2) {
|
|
|
9995
10065
|
assertFoundationPlanSupported(args2.spec, args2.plan);
|
|
9996
10066
|
assertFoundationSettingsMatch(args2.spec.foundation, args2.plan);
|
|
9997
10067
|
const outputDir = resolve6(
|
|
9998
|
-
args2.outputDir ?? (args2.spec.runtime?.artifactRoot ?
|
|
10068
|
+
args2.outputDir ?? (args2.spec.runtime?.artifactRoot ? join12(resolve6(dirname9(args2.specPath), args2.spec.runtime.artifactRoot), "foundation-runs", randomUUID8()) : join12(dirname9(resolve6(args2.specPath)), DEFAULT_FOUNDATION_RUNS_DIR, randomUUID8()))
|
|
9999
10069
|
);
|
|
10000
10070
|
if (args2.gpu && args2.spec.foundation.checkpoint_backup_dir && localPathsOverlap(outputDir, args2.spec.foundation.checkpoint_backup_dir)) {
|
|
10001
10071
|
throw new Error("AWS checkpoint_backup_dir must be separate from the foundation run directory; overlapping backups can overwrite returned checkpoints.");
|
|
@@ -10020,7 +10090,7 @@ async function runFoundationPipeline(args2) {
|
|
|
10020
10090
|
await mkdir3(outputDir, { recursive: true, mode: 448 });
|
|
10021
10091
|
}
|
|
10022
10092
|
await makePrivateTree(outputDir);
|
|
10023
|
-
const workflowPath =
|
|
10093
|
+
const workflowPath = join12(outputDir, "resolved-workflow.json");
|
|
10024
10094
|
const workflow = { spec: args2.spec, plan: args2.plan };
|
|
10025
10095
|
if (outputExists) {
|
|
10026
10096
|
const previous = await readFile7(workflowPath, "utf8").catch((error) => {
|
|
@@ -10053,16 +10123,16 @@ async function runFoundationPipeline(args2) {
|
|
|
10053
10123
|
throw new Error(`Missing ${kind} artifact.`);
|
|
10054
10124
|
};
|
|
10055
10125
|
for (const step of args2.plan.steps) {
|
|
10056
|
-
const stepDir =
|
|
10057
|
-
const output =
|
|
10126
|
+
const stepDir = join12(outputDir, step.id);
|
|
10127
|
+
const output = join12(stepDir, "output");
|
|
10058
10128
|
await mkdir3(stepDir, { recursive: true, mode: 448 });
|
|
10059
10129
|
await chmod2(stepDir, 448);
|
|
10060
10130
|
await mkdir3(output, { recursive: true, mode: 448 });
|
|
10061
10131
|
await chmod2(output, 448);
|
|
10062
|
-
const configPath2 =
|
|
10063
|
-
const logPath =
|
|
10064
|
-
const metricsPath =
|
|
10065
|
-
const completionPath =
|
|
10132
|
+
const configPath2 = join12(stepDir, "config.json");
|
|
10133
|
+
const logPath = join12(stepDir, "step.log");
|
|
10134
|
+
const metricsPath = join12(output, "metrics.json");
|
|
10135
|
+
const completionPath = join12(stepDir, "completion.json");
|
|
10066
10136
|
const fields = stepConfig(step);
|
|
10067
10137
|
let entrypoint;
|
|
10068
10138
|
let config;
|
|
@@ -10083,7 +10153,7 @@ async function runFoundationPipeline(args2) {
|
|
|
10083
10153
|
config = {
|
|
10084
10154
|
tokenizer_dir: tokenizerDir,
|
|
10085
10155
|
output_dir: output,
|
|
10086
|
-
work_dir:
|
|
10156
|
+
work_dir: join12(stepDir, "recovery"),
|
|
10087
10157
|
depth: fields.depth ?? hp.depth,
|
|
10088
10158
|
steps: fields.steps ?? hp.pretrain_steps,
|
|
10089
10159
|
batch_size: fields.batchSize ?? hp.batch_size,
|
|
@@ -10188,15 +10258,15 @@ async function runFoundationPipeline(args2) {
|
|
|
10188
10258
|
const artifacts = {};
|
|
10189
10259
|
const record6 = {};
|
|
10190
10260
|
if (step.uses === "tokenize") {
|
|
10191
|
-
const tokenizerPath =
|
|
10261
|
+
const tokenizerPath = join12(output, "tokenizer.json");
|
|
10192
10262
|
await requireRegularNonemptyFile(tokenizerPath, `Foundation step "${step.id}" tokenizer.json`);
|
|
10193
10263
|
await readRequiredJsonObject(tokenizerPath, `Foundation step "${step.id}" tokenizer.json`);
|
|
10194
10264
|
record6.tokenizer = output;
|
|
10195
10265
|
tokenizerDir = output;
|
|
10196
10266
|
artifacts.tokenizer = await artifactFor(output);
|
|
10197
10267
|
} else if (step.uses === "pretrain" || step.uses === "finetune" || step.uses === "rl") {
|
|
10198
|
-
const modelPath =
|
|
10199
|
-
const modelConfigPath =
|
|
10268
|
+
const modelPath = join12(output, "model.safetensors");
|
|
10269
|
+
const modelConfigPath = join12(output, "config.json");
|
|
10200
10270
|
await requireRegularNonemptyFile(modelPath, `Foundation step "${step.id}" model.safetensors`);
|
|
10201
10271
|
const modelConfig = await readRequiredJsonObject(
|
|
10202
10272
|
modelConfigPath,
|
|
@@ -10211,7 +10281,7 @@ async function runFoundationPipeline(args2) {
|
|
|
10211
10281
|
modelDir = output;
|
|
10212
10282
|
artifacts.model = await artifactFor(output);
|
|
10213
10283
|
} else if (step.uses === "evaluate") {
|
|
10214
|
-
const reportPath2 =
|
|
10284
|
+
const reportPath2 = join12(output, "report.json");
|
|
10215
10285
|
const stepReport = await readRequiredJsonObject(
|
|
10216
10286
|
reportPath2,
|
|
10217
10287
|
`Foundation step "${step.id}" report.json`
|
|
@@ -10251,7 +10321,7 @@ async function runFoundationPipeline(args2) {
|
|
|
10251
10321
|
spec: args2.specPath,
|
|
10252
10322
|
steps
|
|
10253
10323
|
};
|
|
10254
|
-
const reportPath =
|
|
10324
|
+
const reportPath = join12(outputDir, "report.json");
|
|
10255
10325
|
await writePrivateTextAtomic(reportPath, `${JSON.stringify(report, null, 2)}
|
|
10256
10326
|
`);
|
|
10257
10327
|
return {
|
|
@@ -10264,8 +10334,8 @@ async function runFoundationPipeline(args2) {
|
|
|
10264
10334
|
|
|
10265
10335
|
// src/local-runtime/decision-runner.ts
|
|
10266
10336
|
import { randomUUID as randomUUID9 } from "crypto";
|
|
10267
|
-
import { lstat as lstat5, mkdir as mkdir4, readFile as readFile8, rename as
|
|
10268
|
-
import { dirname as dirname10, join as
|
|
10337
|
+
import { lstat as lstat5, mkdir as mkdir4, readFile as readFile8, rename as rename3, rm as rm6, writeFile as writeFile7 } from "fs/promises";
|
|
10338
|
+
import { dirname as dirname10, join as join13, resolve as resolve7 } from "path";
|
|
10269
10339
|
var PROBABILITY_FLOOR = 1e-6;
|
|
10270
10340
|
function assertDecisionPlanSupported(plan) {
|
|
10271
10341
|
for (const step of plan.steps) {
|
|
@@ -10428,7 +10498,7 @@ async function writePrivateAtomic(path, content) {
|
|
|
10428
10498
|
const temporary = `${path}.${process.pid}.${randomUUID9()}.tmp`;
|
|
10429
10499
|
try {
|
|
10430
10500
|
await writeFile7(temporary, content, { encoding: "utf8", mode: 384, flag: "wx" });
|
|
10431
|
-
await
|
|
10501
|
+
await rename3(temporary, path);
|
|
10432
10502
|
} finally {
|
|
10433
10503
|
await rm6(temporary, { force: true });
|
|
10434
10504
|
}
|
|
@@ -10447,7 +10517,7 @@ async function runDecisionPipeline(args2) {
|
|
|
10447
10517
|
assertDecisionPlanSupported(args2.plan);
|
|
10448
10518
|
const model = resolveDecisionModel(args2.spec.base_model);
|
|
10449
10519
|
const specDir = dirname10(resolve7(args2.specPath));
|
|
10450
|
-
const outputDir = resolve7(args2.outputDir ?? (args2.spec.runtime?.artifactRoot ?
|
|
10520
|
+
const outputDir = resolve7(args2.outputDir ?? (args2.spec.runtime?.artifactRoot ? join13(resolve7(specDir, args2.spec.runtime.artifactRoot), "decision-runs", randomUUID9()) : join13(specDir, DEFAULT_DECISION_RUNS_DIR, randomUUID9())));
|
|
10451
10521
|
if (await lstat5(outputDir).catch(() => null)) {
|
|
10452
10522
|
throw new Error(`Decision output directory already exists: ${outputDir}`);
|
|
10453
10523
|
}
|
|
@@ -10466,13 +10536,13 @@ async function runDecisionPipeline(args2) {
|
|
|
10466
10536
|
const hp = args2.spec.hyperparameters ?? {};
|
|
10467
10537
|
const device = hp.device ?? "auto";
|
|
10468
10538
|
const evalIds = split.holdout.map((_, index) => `eval-${index}`);
|
|
10469
|
-
const dataDir =
|
|
10539
|
+
const dataDir = join13(outputDir, "data");
|
|
10470
10540
|
await mkdir4(dataDir, { recursive: true, mode: 448 });
|
|
10471
|
-
const trainPath =
|
|
10472
|
-
const evalInputsPath =
|
|
10541
|
+
const trainPath = join13(dataDir, "train.jsonl");
|
|
10542
|
+
const evalInputsPath = join13(dataDir, "eval-inputs.jsonl");
|
|
10473
10543
|
await writePrivateAtomic(trainPath, jsonl(split.train));
|
|
10474
10544
|
await writePrivateAtomic(evalInputsPath, jsonl(split.holdout.map((example, index) => ({ id: evalIds[index], input: example.input }))));
|
|
10475
|
-
await writePrivateJson2(
|
|
10545
|
+
await writePrivateJson2(join13(outputDir, "resolved-workflow.json"), {
|
|
10476
10546
|
spec: args2.spec,
|
|
10477
10547
|
plan: args2.plan,
|
|
10478
10548
|
model: { id: model.id, revision: model.revision },
|
|
@@ -10485,13 +10555,13 @@ async function runDecisionPipeline(args2) {
|
|
|
10485
10555
|
const steps = [];
|
|
10486
10556
|
let tunedModelDir;
|
|
10487
10557
|
for (const step of args2.plan.steps) {
|
|
10488
|
-
const stepDir =
|
|
10558
|
+
const stepDir = join13(outputDir, step.id);
|
|
10489
10559
|
await mkdir4(stepDir, { recursive: true, mode: 448 });
|
|
10490
|
-
const configPath2 =
|
|
10491
|
-
const logPath =
|
|
10492
|
-
const metricsPath =
|
|
10560
|
+
const configPath2 = join13(stepDir, "config.json");
|
|
10561
|
+
const logPath = join13(stepDir, "step.log");
|
|
10562
|
+
const metricsPath = join13(stepDir, "metrics.json");
|
|
10493
10563
|
if (step.uses === "train") {
|
|
10494
|
-
const modelDir =
|
|
10564
|
+
const modelDir = join13(stepDir, "model");
|
|
10495
10565
|
args2.onProgress?.(`${step.id}: fine-tuning ${model.id} on ${split.train.length} examples (log: ${logPath})`);
|
|
10496
10566
|
await writePrivateJson2(configPath2, {
|
|
10497
10567
|
model: model.id,
|
|
@@ -10511,8 +10581,8 @@ async function runDecisionPipeline(args2) {
|
|
|
10511
10581
|
await spawnStep({ entrypoint: "train.py", configPath: configPath2, logPath, stepId: step.id, onProgress: args2.onProgress });
|
|
10512
10582
|
const metrics = await readJsonObject2(metricsPath, `Decision step "${step.id}" metrics`);
|
|
10513
10583
|
if (metrics.ok !== true) throw new Error(`Decision step "${step.id}" metrics must report ok: true.`);
|
|
10514
|
-
await requireRegularNonemptyFile2(
|
|
10515
|
-
await readJsonObject2(
|
|
10584
|
+
await requireRegularNonemptyFile2(join13(modelDir, "model.safetensors"), `Decision step "${step.id}" model.safetensors`);
|
|
10585
|
+
await readJsonObject2(join13(modelDir, "rl_agent_config.json"), `Decision step "${step.id}" rl_agent_config.json`);
|
|
10516
10586
|
producedModels.set(step.id, modelDir);
|
|
10517
10587
|
tunedModelDir = modelDir;
|
|
10518
10588
|
steps.push({ id: step.id, uses: step.uses, model: modelDir, training: metrics });
|
|
@@ -10523,7 +10593,7 @@ async function runDecisionPipeline(args2) {
|
|
|
10523
10593
|
const tuned = source !== "base";
|
|
10524
10594
|
const modelDir = tuned ? producedModels.get(producer(source.from)) : void 0;
|
|
10525
10595
|
if (tuned && !modelDir) throw new Error(`Step ${step.id} needs a model from ${source.from}.`);
|
|
10526
|
-
const predictionsPath =
|
|
10596
|
+
const predictionsPath = join13(stepDir, "predictions.jsonl");
|
|
10527
10597
|
args2.onProgress?.(`${step.id}: evaluating ${tuned ? "tuned" : "base"} model on ${split.holdout.length} held-out examples`);
|
|
10528
10598
|
await writePrivateJson2(configPath2, {
|
|
10529
10599
|
model: modelDir ?? model.id,
|
|
@@ -10537,7 +10607,7 @@ async function runDecisionPipeline(args2) {
|
|
|
10537
10607
|
await spawnStep({ entrypoint: "evaluate.py", configPath: configPath2, logPath, stepId: step.id, onProgress: args2.onProgress });
|
|
10538
10608
|
const metrics = await readJsonObject2(metricsPath, `Decision step "${step.id}" metrics`);
|
|
10539
10609
|
if (metrics.ok !== true) throw new Error(`Decision step "${step.id}" metrics must report ok: true.`);
|
|
10540
|
-
const reportPath2 =
|
|
10610
|
+
const reportPath2 = join13(stepDir, "report.json");
|
|
10541
10611
|
const scored = scoreDecisionPredictions({
|
|
10542
10612
|
kind: tuned ? "candidate" : "baseline",
|
|
10543
10613
|
modelId: modelDir ? fileUri(modelDir) : model.id,
|
|
@@ -10561,10 +10631,10 @@ async function runDecisionPipeline(args2) {
|
|
|
10561
10631
|
log_loss_delta: after.metrics.log_loss - before.metrics.log_loss,
|
|
10562
10632
|
brier_delta: after.metrics.brier - before.metrics.brier
|
|
10563
10633
|
};
|
|
10564
|
-
await writePrivateJson2(
|
|
10634
|
+
await writePrivateJson2(join13(stepDir, "comparison.json"), comparison);
|
|
10565
10635
|
steps.push({ id: step.id, uses: step.uses, comparison });
|
|
10566
10636
|
}
|
|
10567
|
-
const reportPath =
|
|
10637
|
+
const reportPath = join13(outputDir, "report.json");
|
|
10568
10638
|
const report = {
|
|
10569
10639
|
status: "succeeded",
|
|
10570
10640
|
engine: "decision",
|
|
@@ -11190,7 +11260,7 @@ function registerTopupCommands(parent) {
|
|
|
11190
11260
|
import { createInterface as createInterface4 } from "readline/promises";
|
|
11191
11261
|
import { existsSync as existsSync7, readFileSync as readFileSync8 } from "fs";
|
|
11192
11262
|
import { homedir as homedir2 } from "os";
|
|
11193
|
-
import { isAbsolute as isAbsolute5, join as
|
|
11263
|
+
import { isAbsolute as isAbsolute5, join as join15, resolve as resolve9 } from "path";
|
|
11194
11264
|
import { stdin as defaultStdin, stdout as defaultStdout } from "process";
|
|
11195
11265
|
var MAX_PUBLISH_BYTES = 2 * 1024 * 1024;
|
|
11196
11266
|
function expandPath2(value, baseDirectory, homeDirectory) {
|
|
@@ -11202,8 +11272,8 @@ function resolvePublishStoreRoot(options) {
|
|
|
11202
11272
|
const cwd = options?.cwd ?? process.cwd();
|
|
11203
11273
|
const env = options?.env ?? process.env;
|
|
11204
11274
|
const homeDirectory = options?.homeDirectory ?? (env.HOME ? resolve9(env.HOME) : homedir2());
|
|
11205
|
-
const localConfigPath =
|
|
11206
|
-
const projectPath =
|
|
11275
|
+
const localConfigPath = join15(cwd, "local-runner.json");
|
|
11276
|
+
const projectPath = join15(cwd, "tunedtensor.json");
|
|
11207
11277
|
if (existsSync7(projectPath)) {
|
|
11208
11278
|
const spec = localBehaviorSpecFileSchema.parse(JSON.parse(readFileSync8(projectPath, "utf8")));
|
|
11209
11279
|
if (existsSync7(localConfigPath) && (spec.runtime || spec.evaluation)) throw new Error("Conflicting configuration sources; migrate local-runner.json first.");
|
|
@@ -12272,31 +12342,166 @@ Eval Results (${data3._evals.length}):`);
|
|
|
12272
12342
|
});
|
|
12273
12343
|
}
|
|
12274
12344
|
|
|
12345
|
+
// src/commands/endpoints.ts
|
|
12346
|
+
import { createHash as createHash8, randomUUID as randomUUID10 } from "crypto";
|
|
12347
|
+
import { createReadStream as createReadStream2, readFileSync as readFileSync11, statSync as statSync4, openSync as openSync3, readSync, closeSync as closeSync3 } from "fs";
|
|
12348
|
+
import { open as open4 } from "fs/promises";
|
|
12349
|
+
import { finished } from "stream/promises";
|
|
12350
|
+
import { resolve as resolve10, join as join16 } from "path";
|
|
12351
|
+
import { z } from "zod";
|
|
12352
|
+
async function inspectCheckpoint(modelPath) {
|
|
12353
|
+
const directory = resolve10(modelPath);
|
|
12354
|
+
const config = JSON.parse(readFileSync11(join16(directory, "rl_agent_config.json"), "utf8"));
|
|
12355
|
+
if (config.encoder !== "answerdotai/ModernBERT-large" || config.head_layers !== 2 || config.max_len !== 512 || config.head_max_len !== 192) {
|
|
12356
|
+
throw new Error("Hosting currently supports only the pinned English Laya checkpoint and TT decision fine-tunes.");
|
|
12357
|
+
}
|
|
12358
|
+
const kind = config.tuned_tensor ? "tuned" : "base";
|
|
12359
|
+
if (kind === "tuned" && (JSON.stringify(config.temperature) !== "[1,1,1]" || config.temperature_by_options)) {
|
|
12360
|
+
throw new Error("Custom calibration is not supported by hosted decision inference.");
|
|
12361
|
+
}
|
|
12362
|
+
const file = join16(directory, "model.safetensors");
|
|
12363
|
+
const size = statSync4(file).size;
|
|
12364
|
+
if (size <= 8 || size > 2e9) throw new Error("Weights must be a safetensors file smaller than 2 GB. GPU and adapter models are not supported.");
|
|
12365
|
+
const fd = openSync3(file, "r");
|
|
12366
|
+
try {
|
|
12367
|
+
const length = Buffer.alloc(8);
|
|
12368
|
+
if (readSync(fd, length, 0, 8, 0) !== 8) throw new Error("Invalid safetensors header.");
|
|
12369
|
+
const headerSize = Number(length.readBigUInt64LE());
|
|
12370
|
+
if (!Number.isSafeInteger(headerSize) || headerSize < 2 || headerSize > 1e6 || headerSize + 8 >= size) throw new Error("Invalid safetensors header.");
|
|
12371
|
+
const header = Buffer.alloc(headerSize);
|
|
12372
|
+
if (readSync(fd, header, 0, headerSize, 8) !== headerSize) throw new Error("Truncated safetensors header.");
|
|
12373
|
+
const tensors = JSON.parse(header.toString("utf8"));
|
|
12374
|
+
for (const prefix of ["encoder.", "type_emb.", "scorer.", "act_head."]) {
|
|
12375
|
+
if (!Object.keys(tensors).some((key) => key.startsWith(prefix))) throw new Error("Not a complete Laya checkpoint.");
|
|
12376
|
+
}
|
|
12377
|
+
} finally {
|
|
12378
|
+
closeSync3(fd);
|
|
12379
|
+
}
|
|
12380
|
+
const hash = createHash8("sha256");
|
|
12381
|
+
for await (const chunk of createReadStream2(file)) hash.update(chunk);
|
|
12382
|
+
return { file, weights_bytes: size, weights_sha256: hash.digest("hex"), checkpoint_kind: kind };
|
|
12383
|
+
}
|
|
12384
|
+
function readInferencePayload(file) {
|
|
12385
|
+
const path = resolve10(file);
|
|
12386
|
+
if (statSync4(path).size > 131072) throw new Error("Inference JSON must be at most 128 KiB.");
|
|
12387
|
+
const data3 = JSON.parse(readFileSync11(path, "utf8"));
|
|
12388
|
+
return z.record(z.string(), z.json()).refine((value) => Object.keys(value).length > 0).parse(data3);
|
|
12389
|
+
}
|
|
12390
|
+
async function uploadWeights(endpoint, file) {
|
|
12391
|
+
const url = new URL(endpoint.upload.url);
|
|
12392
|
+
if (url.protocol !== "https:" || url.username || url.password || !/\.s3\.[a-z0-9-]+\.amazonaws\.com$/.test(url.hostname)) {
|
|
12393
|
+
throw new Error("The server returned an invalid model upload URL.");
|
|
12394
|
+
}
|
|
12395
|
+
const handle = await open4(file, "r");
|
|
12396
|
+
const stream = handle.createReadStream();
|
|
12397
|
+
const completion = finished(stream);
|
|
12398
|
+
void completion.catch(() => {
|
|
12399
|
+
});
|
|
12400
|
+
try {
|
|
12401
|
+
const response = await fetch(url, {
|
|
12402
|
+
method: "PUT",
|
|
12403
|
+
headers: endpoint.upload.headers,
|
|
12404
|
+
body: stream,
|
|
12405
|
+
duplex: "half",
|
|
12406
|
+
redirect: "error",
|
|
12407
|
+
signal: AbortSignal.timeout(60 * 6e4)
|
|
12408
|
+
});
|
|
12409
|
+
if (!response.ok) throw new Error(`Model upload failed (${response.status}). Delete endpoint ${endpoint.id} and retry.`);
|
|
12410
|
+
} finally {
|
|
12411
|
+
stream.destroy();
|
|
12412
|
+
await completion.catch(() => {
|
|
12413
|
+
});
|
|
12414
|
+
}
|
|
12415
|
+
}
|
|
12416
|
+
function show(endpoint, opts) {
|
|
12417
|
+
const result2 = { ...endpoint, invocation_url: new URL(endpoint.invocation_path, getBaseUrl(opts)).toString() };
|
|
12418
|
+
if (isJsonMode()) return printJson(result2);
|
|
12419
|
+
printDetail([
|
|
12420
|
+
["Endpoint", result2.id],
|
|
12421
|
+
["Name", result2.name],
|
|
12422
|
+
["Status", result2.status],
|
|
12423
|
+
["Inference URL", result2.invocation_url],
|
|
12424
|
+
["Price per successful request", `${result2.price_cents} cents`],
|
|
12425
|
+
...result2.inference_key ? [["Inference key (save securely)", result2.inference_key]] : []
|
|
12426
|
+
]);
|
|
12427
|
+
}
|
|
12428
|
+
function registerEndpointCommands(parent) {
|
|
12429
|
+
const endpoints = parent.command("endpoints").description("Host small CPU decision models using prepaid TT credits");
|
|
12430
|
+
endpoints.command("create").description("Upload Laya weights and deploy a serverless endpoint").requiredOption("--model <directory>", "Local Laya checkpoint (e.g. a decision run's train/model)").requiredOption("--name <name>", "Endpoint display name").action(async (options) => {
|
|
12431
|
+
const opts = parent.optsWithGlobals();
|
|
12432
|
+
const { file, ...artifact } = await inspectCheckpoint(options.model);
|
|
12433
|
+
const { data: data3 } = await post("/endpoints", { name: options.name, format: "laya-safetensors-v1", ...artifact }, opts);
|
|
12434
|
+
if (!isJsonMode()) console.error(`Endpoint ${data3.id} created. Uploading ${(artifact.weights_bytes / 1e9).toFixed(2)} GB of weights...`);
|
|
12435
|
+
try {
|
|
12436
|
+
await uploadWeights(data3, file);
|
|
12437
|
+
const { data: deployed } = await post(`/endpoints/${data3.id}/deploy`, {}, opts);
|
|
12438
|
+
show({ ...deployed, inference_key: data3.inference_key }, opts);
|
|
12439
|
+
} catch (error) {
|
|
12440
|
+
throw new Error(`Endpoint ${data3.id}: ${error instanceof Error ? error.message : "upload/deployment failed"}. Use tt cloud endpoints get ${data3.id} or delete ${data3.id}. Rotate its key if the create response was lost.`, { cause: error });
|
|
12441
|
+
}
|
|
12442
|
+
});
|
|
12443
|
+
endpoints.command("list").action(async () => {
|
|
12444
|
+
const opts = parent.optsWithGlobals();
|
|
12445
|
+
const { data: data3 } = await get("/endpoints", void 0, opts);
|
|
12446
|
+
if (isJsonMode()) return printJson(data3);
|
|
12447
|
+
for (const row of data3) show(row, opts);
|
|
12448
|
+
});
|
|
12449
|
+
endpoints.command("get <id>").description("Check deployment or deletion status").action(async (id) => {
|
|
12450
|
+
z.uuid().parse(id);
|
|
12451
|
+
const opts = parent.optsWithGlobals();
|
|
12452
|
+
show((await get(`/endpoints/${id}`, void 0, opts)).data, opts);
|
|
12453
|
+
});
|
|
12454
|
+
endpoints.command("delete <id>").description("Stop serving and delete cloud weights and resources").action(async (id) => {
|
|
12455
|
+
z.uuid().parse(id);
|
|
12456
|
+
const opts = parent.optsWithGlobals();
|
|
12457
|
+
show((await del(`/endpoints/${id}`, opts)).data, opts);
|
|
12458
|
+
});
|
|
12459
|
+
endpoints.command("rotate-key <id>").description("Revoke the old inference key and return a new key").action(async (id) => {
|
|
12460
|
+
z.uuid().parse(id);
|
|
12461
|
+
const { data: data3 } = await post(`/endpoints/${id}/key`, {}, parent.optsWithGlobals());
|
|
12462
|
+
if (isJsonMode()) return printJson(data3);
|
|
12463
|
+
printDetail([["Inference key (save securely)", data3.inference_key]]);
|
|
12464
|
+
});
|
|
12465
|
+
endpoints.command("invoke <id>").requiredOption("--payload <file>", "Complete model request as a JSON object (up to 128 KiB)").option("--request-id <uuid>", "Reuse this ID to retry without a second charge").action(async (id, options) => {
|
|
12466
|
+
z.uuid().parse(id);
|
|
12467
|
+
const requestId = options.requestId ?? randomUUID10();
|
|
12468
|
+
z.uuid().parse(requestId);
|
|
12469
|
+
const payload = readInferencePayload(options.payload);
|
|
12470
|
+
if (!isJsonMode()) console.error(`Request ID: ${requestId} (reuse with --request-id when retrying)`);
|
|
12471
|
+
try {
|
|
12472
|
+
const { data: data3 } = await post(`/endpoints/${id}/invoke`, payload, parent.optsWithGlobals(), { "Idempotency-Key": requestId });
|
|
12473
|
+
printJson({ request_id: requestId, data: data3 });
|
|
12474
|
+
} catch (error) {
|
|
12475
|
+
throw new Error(`Request ${requestId}: ${error instanceof Error ? error.message : "inference failed"}. Retry with --request-id ${requestId}.`, { cause: error });
|
|
12476
|
+
}
|
|
12477
|
+
});
|
|
12478
|
+
}
|
|
12479
|
+
|
|
12275
12480
|
// src/commands/models.ts
|
|
12276
|
-
import { spawn as
|
|
12277
|
-
import { createHash as
|
|
12481
|
+
import { spawn as spawn4, execFileSync } from "child_process";
|
|
12482
|
+
import { createHash as createHash9 } from "crypto";
|
|
12278
12483
|
import {
|
|
12279
12484
|
existsSync as existsSync10,
|
|
12280
12485
|
mkdirSync as mkdirSync5,
|
|
12281
|
-
statSync as
|
|
12486
|
+
statSync as statSync5,
|
|
12282
12487
|
createWriteStream,
|
|
12283
12488
|
unlinkSync,
|
|
12284
|
-
readFileSync as
|
|
12489
|
+
readFileSync as readFileSync13,
|
|
12285
12490
|
writeFileSync as writeFileSync6,
|
|
12286
12491
|
readdirSync,
|
|
12287
12492
|
rmSync
|
|
12288
12493
|
} from "fs";
|
|
12289
12494
|
import { homedir as homedir3 } from "os";
|
|
12290
|
-
import { dirname as dirname13, join as
|
|
12495
|
+
import { dirname as dirname13, join as join17, resolve as resolve12, basename as basename9, normalize, isAbsolute as isAbsolute6 } from "path";
|
|
12291
12496
|
import { Readable, Transform } from "stream";
|
|
12292
12497
|
import { pipeline } from "stream/promises";
|
|
12293
12498
|
|
|
12294
12499
|
// src/commands/init.ts
|
|
12295
|
-
import { existsSync as existsSync9, readFileSync as
|
|
12296
|
-
import { resolve as
|
|
12500
|
+
import { existsSync as existsSync9, readFileSync as readFileSync12, writeFileSync as writeFileSync5 } from "fs";
|
|
12501
|
+
import { resolve as resolve11 } from "path";
|
|
12297
12502
|
var DEFAULT_SPEC_FILE2 = "tunedtensor.json";
|
|
12298
12503
|
function loadSpec(filePath) {
|
|
12299
|
-
const resolved =
|
|
12504
|
+
const resolved = resolve11(filePath || DEFAULT_SPEC_FILE2);
|
|
12300
12505
|
if (!existsSync9(resolved)) {
|
|
12301
12506
|
printError(
|
|
12302
12507
|
`Spec file not found: ${filePath || DEFAULT_SPEC_FILE2}
|
|
@@ -12304,13 +12509,13 @@ Run \`tt init\` to create one.`
|
|
|
12304
12509
|
);
|
|
12305
12510
|
process.exit(1);
|
|
12306
12511
|
}
|
|
12307
|
-
const raw = JSON.parse(
|
|
12512
|
+
const raw = JSON.parse(readFileSync12(resolved, "utf-8"));
|
|
12308
12513
|
return raw;
|
|
12309
12514
|
}
|
|
12310
12515
|
|
|
12311
12516
|
// src/serve-manager.ts
|
|
12312
|
-
import { spawn as
|
|
12313
|
-
import { randomUUID as
|
|
12517
|
+
import { spawn as spawn3 } from "child_process";
|
|
12518
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
12314
12519
|
import { appendFileSync, mkdirSync as mkdirSync4 } from "fs";
|
|
12315
12520
|
import { createServer } from "http";
|
|
12316
12521
|
import { createServer as createNetServer } from "net";
|
|
@@ -12327,10 +12532,10 @@ function parsePositiveInteger(value, fallback) {
|
|
|
12327
12532
|
return Number.isInteger(value) && value >= 0 ? value : fallback;
|
|
12328
12533
|
}
|
|
12329
12534
|
function sleep2(ms) {
|
|
12330
|
-
return new Promise((
|
|
12535
|
+
return new Promise((resolve14) => setTimeout(resolve14, ms));
|
|
12331
12536
|
}
|
|
12332
12537
|
function readRequestBody(req) {
|
|
12333
|
-
return new Promise((
|
|
12538
|
+
return new Promise((resolve14, reject) => {
|
|
12334
12539
|
if (Number(req.headers["content-length"]) > MAX_SERVE_REQUEST_BYTES) {
|
|
12335
12540
|
reject(new RequestTooLargeError());
|
|
12336
12541
|
return;
|
|
@@ -12360,7 +12565,7 @@ function readRequestBody(req) {
|
|
|
12360
12565
|
};
|
|
12361
12566
|
const onEnd = () => {
|
|
12362
12567
|
cleanup();
|
|
12363
|
-
|
|
12568
|
+
resolve14(Buffer.concat(chunks, bytes));
|
|
12364
12569
|
};
|
|
12365
12570
|
req.on("data", onData);
|
|
12366
12571
|
req.on("end", onEnd);
|
|
@@ -12437,7 +12642,7 @@ function errorSummary(response, fallback) {
|
|
|
12437
12642
|
return typeof message === "string" ? message : fallback;
|
|
12438
12643
|
}
|
|
12439
12644
|
async function allocatePort(host) {
|
|
12440
|
-
return new Promise((
|
|
12645
|
+
return new Promise((resolve14, reject) => {
|
|
12441
12646
|
const server = createNetServer();
|
|
12442
12647
|
server.on("error", reject);
|
|
12443
12648
|
server.listen(0, host, () => {
|
|
@@ -12447,14 +12652,14 @@ async function allocatePort(host) {
|
|
|
12447
12652
|
return;
|
|
12448
12653
|
}
|
|
12449
12654
|
const port = address.port;
|
|
12450
|
-
server.close(() =>
|
|
12655
|
+
server.close(() => resolve14(port));
|
|
12451
12656
|
});
|
|
12452
12657
|
});
|
|
12453
12658
|
}
|
|
12454
12659
|
var ManagedModelServer = class {
|
|
12455
12660
|
constructor(options) {
|
|
12456
12661
|
this.options = options;
|
|
12457
|
-
this.spawnModelServer = options.spawnModelServer ??
|
|
12662
|
+
this.spawnModelServer = options.spawnModelServer ?? spawn3;
|
|
12458
12663
|
this.idleTimeoutMs = parsePositiveInteger(options.idleTimeoutSeconds, 300) * 1e3;
|
|
12459
12664
|
this.restartAfterRequests = parsePositiveInteger(options.restartAfterRequests, 100);
|
|
12460
12665
|
this.logRecordOverride = options.logRecord;
|
|
@@ -12488,11 +12693,11 @@ var ManagedModelServer = class {
|
|
|
12488
12693
|
sendJson(res, 500, { error: { message: err instanceof Error ? err.message : String(err) } });
|
|
12489
12694
|
});
|
|
12490
12695
|
});
|
|
12491
|
-
await new Promise((
|
|
12696
|
+
await new Promise((resolve14, reject) => {
|
|
12492
12697
|
this.server?.once("error", reject);
|
|
12493
12698
|
this.server?.listen(this.options.publicPort, this.options.publicHost, () => {
|
|
12494
12699
|
this.server?.off("error", reject);
|
|
12495
|
-
|
|
12700
|
+
resolve14();
|
|
12496
12701
|
});
|
|
12497
12702
|
});
|
|
12498
12703
|
}
|
|
@@ -12502,15 +12707,15 @@ var ManagedModelServer = class {
|
|
|
12502
12707
|
if (!this.server) return;
|
|
12503
12708
|
const server = this.server;
|
|
12504
12709
|
this.server = null;
|
|
12505
|
-
await new Promise((
|
|
12506
|
-
server.close((err) => err ? reject(err) :
|
|
12710
|
+
await new Promise((resolve14, reject) => {
|
|
12711
|
+
server.close((err) => err ? reject(err) : resolve14());
|
|
12507
12712
|
});
|
|
12508
12713
|
}
|
|
12509
12714
|
async run() {
|
|
12510
12715
|
await this.start();
|
|
12511
|
-
await new Promise((
|
|
12716
|
+
await new Promise((resolve14, reject) => {
|
|
12512
12717
|
this.server?.once("error", reject);
|
|
12513
|
-
this.server?.once("close",
|
|
12718
|
+
this.server?.once("close", resolve14);
|
|
12514
12719
|
});
|
|
12515
12720
|
}
|
|
12516
12721
|
async handle(req, res) {
|
|
@@ -12591,7 +12796,7 @@ var ManagedModelServer = class {
|
|
|
12591
12796
|
});
|
|
12592
12797
|
}
|
|
12593
12798
|
async processGeneration(route, body, res, receivedAt) {
|
|
12594
|
-
const requestId = `ttreq-${
|
|
12799
|
+
const requestId = `ttreq-${randomUUID11()}`;
|
|
12595
12800
|
const startedAt = Date.now();
|
|
12596
12801
|
const queuedMs = startedAt - receivedAt;
|
|
12597
12802
|
this.activeRequests += 1;
|
|
@@ -12697,7 +12902,7 @@ var ManagedModelServer = class {
|
|
|
12697
12902
|
try {
|
|
12698
12903
|
child.kill("SIGTERM");
|
|
12699
12904
|
await Promise.race([
|
|
12700
|
-
new Promise((
|
|
12905
|
+
new Promise((resolve14) => child.once("exit", () => resolve14())),
|
|
12701
12906
|
sleep2(5e3).then(() => {
|
|
12702
12907
|
if (child.exitCode == null && child.signalCode == null) child.kill("SIGKILL");
|
|
12703
12908
|
})
|
|
@@ -13328,8 +13533,8 @@ function validateDownloadFilename(filename) {
|
|
|
13328
13533
|
function resolveOutputPath(output, filename) {
|
|
13329
13534
|
validateDownloadFilename(filename);
|
|
13330
13535
|
if (!output) return filename;
|
|
13331
|
-
if (existsSync10(output) &&
|
|
13332
|
-
return
|
|
13536
|
+
if (existsSync10(output) && statSync5(output).isDirectory()) {
|
|
13537
|
+
return join17(output, filename);
|
|
13333
13538
|
}
|
|
13334
13539
|
return output;
|
|
13335
13540
|
}
|
|
@@ -13436,15 +13641,15 @@ async function downloadUrlToFile(url, outputPath) {
|
|
|
13436
13641
|
return contentLength;
|
|
13437
13642
|
}
|
|
13438
13643
|
function defaultCacheDir() {
|
|
13439
|
-
const root = process.env.XDG_CACHE_HOME ||
|
|
13440
|
-
return
|
|
13644
|
+
const root = process.env.XDG_CACHE_HOME || join17(homedir3(), ".cache");
|
|
13645
|
+
return join17(root, "tuned-tensor", "models");
|
|
13441
13646
|
}
|
|
13442
13647
|
function runtimeDir(cacheDir) {
|
|
13443
|
-
return
|
|
13648
|
+
return join17(cacheDir, "_runtime");
|
|
13444
13649
|
}
|
|
13445
13650
|
function runtimePythonPath(cacheDir) {
|
|
13446
13651
|
const dir = runtimeDir(cacheDir);
|
|
13447
|
-
return process.platform === "win32" ?
|
|
13652
|
+
return process.platform === "win32" ? join17(dir, "Scripts", "python.exe") : join17(dir, "bin", "python");
|
|
13448
13653
|
}
|
|
13449
13654
|
function resolveServePython(cacheDir, explicitPython) {
|
|
13450
13655
|
if (explicitPython) return explicitPython;
|
|
@@ -13517,7 +13722,7 @@ print(json.dumps({"missing": missing}))
|
|
|
13517
13722
|
}
|
|
13518
13723
|
}
|
|
13519
13724
|
function setupRuntimeCommands(python, venvDir, deps) {
|
|
13520
|
-
const venvPython = process.platform === "win32" ?
|
|
13725
|
+
const venvPython = process.platform === "win32" ? join17(venvDir, "Scripts", "python.exe") : join17(venvDir, "bin", "python");
|
|
13521
13726
|
return [
|
|
13522
13727
|
[python, "-m", "venv", venvDir],
|
|
13523
13728
|
[venvPython, "-m", "pip", "install", "--upgrade", "pip"],
|
|
@@ -13528,7 +13733,7 @@ function safeSegment(value) {
|
|
|
13528
13733
|
return value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "model";
|
|
13529
13734
|
}
|
|
13530
13735
|
function directoryHasFiles(path) {
|
|
13531
|
-
return existsSync10(path) &&
|
|
13736
|
+
return existsSync10(path) && statSync5(path).isDirectory() && readdirSync(path).length > 0;
|
|
13532
13737
|
}
|
|
13533
13738
|
function validateArchiveEntryPath(entry) {
|
|
13534
13739
|
const normalized = normalize(entry).replace(/^[\\/]+/, "");
|
|
@@ -13555,9 +13760,9 @@ function extractArchive(archivePath, targetDir, force) {
|
|
|
13555
13760
|
return targetDir;
|
|
13556
13761
|
}
|
|
13557
13762
|
function localArchiveCacheDir(archivePath, cacheDir) {
|
|
13558
|
-
const resolved =
|
|
13559
|
-
const digest =
|
|
13560
|
-
return
|
|
13763
|
+
const resolved = resolve12(archivePath);
|
|
13764
|
+
const digest = createHash9("sha256").update(resolved).digest("hex").slice(0, 12);
|
|
13765
|
+
return join17(cacheDir, `local-${safeSegment(basename9(archivePath))}-${digest}`);
|
|
13561
13766
|
}
|
|
13562
13767
|
function buildSystemPrompt(spec) {
|
|
13563
13768
|
const parts = [];
|
|
@@ -13573,40 +13778,40 @@ ${spec.constraints.map((c) => `- ${c}`).join("\n")}`);
|
|
|
13573
13778
|
return parts.join("\n\n");
|
|
13574
13779
|
}
|
|
13575
13780
|
function findSpecPath(explicitPath, modelPath) {
|
|
13576
|
-
const candidates = explicitPath ? [explicitPath] : [
|
|
13781
|
+
const candidates = explicitPath ? [explicitPath] : [join17(process.cwd(), DEFAULT_SPEC_FILE2), join17(modelPath, DEFAULT_SPEC_FILE2)];
|
|
13577
13782
|
for (const candidate of candidates) {
|
|
13578
|
-
const resolved =
|
|
13579
|
-
if (existsSync10(resolved) &&
|
|
13783
|
+
const resolved = resolve12(candidate);
|
|
13784
|
+
if (existsSync10(resolved) && statSync5(resolved).isFile()) return resolved;
|
|
13580
13785
|
}
|
|
13581
13786
|
return null;
|
|
13582
13787
|
}
|
|
13583
13788
|
function loadSystemPromptFromSpec(specPath) {
|
|
13584
|
-
const spec = JSON.parse(
|
|
13789
|
+
const spec = JSON.parse(readFileSync13(specPath, "utf8"));
|
|
13585
13790
|
return buildSystemPrompt(spec);
|
|
13586
13791
|
}
|
|
13587
13792
|
function loadJsonSchemaForServe(schemaPath) {
|
|
13588
|
-
const resolved =
|
|
13589
|
-
if (!existsSync10(resolved) || !
|
|
13793
|
+
const resolved = resolve12(schemaPath);
|
|
13794
|
+
if (!existsSync10(resolved) || !statSync5(resolved).isFile()) {
|
|
13590
13795
|
throw new Error(`JSON schema file not found: ${schemaPath}`);
|
|
13591
13796
|
}
|
|
13592
13797
|
try {
|
|
13593
|
-
const schema = JSON.parse(
|
|
13798
|
+
const schema = JSON.parse(readFileSync13(resolved, "utf8"));
|
|
13594
13799
|
return JSON.stringify(schema);
|
|
13595
13800
|
} catch (err) {
|
|
13596
13801
|
throw new Error(`JSON schema file is not valid JSON: ${schemaPath}`);
|
|
13597
13802
|
}
|
|
13598
13803
|
}
|
|
13599
13804
|
function writeReferenceServerScript(cacheDir) {
|
|
13600
|
-
const scriptDir =
|
|
13805
|
+
const scriptDir = join17(cacheDir, "_server");
|
|
13601
13806
|
mkdirSync5(scriptDir, { recursive: true });
|
|
13602
|
-
const scriptPath =
|
|
13807
|
+
const scriptPath = join17(scriptDir, "openai_reference_server.py");
|
|
13603
13808
|
writeFileSync6(scriptPath, REFERENCE_SERVER_SCRIPT, "utf8");
|
|
13604
13809
|
return scriptPath;
|
|
13605
13810
|
}
|
|
13606
13811
|
async function resolveServeTarget(target, opts, cacheDir, forceDownload) {
|
|
13607
13812
|
if (existsSync10(target)) {
|
|
13608
|
-
const resolved =
|
|
13609
|
-
const stats =
|
|
13813
|
+
const resolved = resolve12(target);
|
|
13814
|
+
const stats = statSync5(resolved);
|
|
13610
13815
|
if (stats.isDirectory()) {
|
|
13611
13816
|
return {
|
|
13612
13817
|
modelPath: resolved,
|
|
@@ -13615,7 +13820,7 @@ async function resolveServeTarget(target, opts, cacheDir, forceDownload) {
|
|
|
13615
13820
|
};
|
|
13616
13821
|
}
|
|
13617
13822
|
if (stats.isFile() && (resolved.endsWith(".tar.gz") || resolved.endsWith(".tgz"))) {
|
|
13618
|
-
const extractDir2 =
|
|
13823
|
+
const extractDir2 = join17(localArchiveCacheDir(resolved, cacheDir), "model");
|
|
13619
13824
|
return {
|
|
13620
13825
|
modelPath: extractArchive(resolved, extractDir2, forceDownload),
|
|
13621
13826
|
modelName: basename9(resolved).replace(/\.t(ar\.)?gz$/, ""),
|
|
@@ -13629,9 +13834,9 @@ async function resolveServeTarget(target, opts, cacheDir, forceDownload) {
|
|
|
13629
13834
|
const { data: data3 } = await get(`/models/${fullId}/download`, void 0, opts);
|
|
13630
13835
|
validateDownloadFilename(fullId);
|
|
13631
13836
|
validateDownloadFilename(data3.filename);
|
|
13632
|
-
const modelCacheDir =
|
|
13633
|
-
const archivePath =
|
|
13634
|
-
const extractDir =
|
|
13837
|
+
const modelCacheDir = join17(cacheDir, fullId);
|
|
13838
|
+
const archivePath = join17(modelCacheDir, data3.filename);
|
|
13839
|
+
const extractDir = join17(modelCacheDir, "model");
|
|
13635
13840
|
if (!existsSync10(archivePath) || forceDownload) {
|
|
13636
13841
|
await downloadUrlToFile(data3.url, archivePath);
|
|
13637
13842
|
}
|
|
@@ -13736,8 +13941,8 @@ function ollamaSlug(name) {
|
|
|
13736
13941
|
}
|
|
13737
13942
|
function firstExisting(candidates) {
|
|
13738
13943
|
for (const candidate of candidates) {
|
|
13739
|
-
if (candidate && existsSync10(candidate) &&
|
|
13740
|
-
return
|
|
13944
|
+
if (candidate && existsSync10(candidate) && statSync5(candidate).isFile()) {
|
|
13945
|
+
return resolve12(candidate);
|
|
13741
13946
|
}
|
|
13742
13947
|
}
|
|
13743
13948
|
return null;
|
|
@@ -13747,14 +13952,14 @@ function llamaCppDir(explicit) {
|
|
|
13747
13952
|
}
|
|
13748
13953
|
function resolveConvertScript(opts, required) {
|
|
13749
13954
|
if (opts.convertScript) {
|
|
13750
|
-
const resolved =
|
|
13955
|
+
const resolved = resolve12(opts.convertScript);
|
|
13751
13956
|
if (required && !existsSync10(resolved)) {
|
|
13752
13957
|
throw new Error(`Conversion script not found: ${opts.convertScript}`);
|
|
13753
13958
|
}
|
|
13754
13959
|
return resolved;
|
|
13755
13960
|
}
|
|
13756
13961
|
const dir = llamaCppDir(opts.llamaCpp);
|
|
13757
|
-
const candidates = dir ? [
|
|
13962
|
+
const candidates = dir ? [join17(dir, "convert_hf_to_gguf.py"), join17(dir, "convert-hf-to-gguf.py")] : [];
|
|
13758
13963
|
const found = firstExisting(candidates);
|
|
13759
13964
|
if (found) return found;
|
|
13760
13965
|
if (required) {
|
|
@@ -13762,11 +13967,11 @@ function resolveConvertScript(opts, required) {
|
|
|
13762
13967
|
"Could not find convert_hf_to_gguf.py. Pass --llama-cpp <dir> (a llama.cpp checkout) or --convert-script <path>. Get it from https://github.com/ggml-org/llama.cpp."
|
|
13763
13968
|
);
|
|
13764
13969
|
}
|
|
13765
|
-
return dir ?
|
|
13970
|
+
return dir ? join17(dir, "convert_hf_to_gguf.py") : "convert_hf_to_gguf.py";
|
|
13766
13971
|
}
|
|
13767
13972
|
function resolveQuantizeBin(opts, required) {
|
|
13768
13973
|
if (opts.quantizeBin) {
|
|
13769
|
-
const resolved =
|
|
13974
|
+
const resolved = resolve12(opts.quantizeBin);
|
|
13770
13975
|
if (required && !existsSync10(resolved)) {
|
|
13771
13976
|
throw new Error(`llama-quantize binary not found: ${opts.quantizeBin}`);
|
|
13772
13977
|
}
|
|
@@ -13775,10 +13980,10 @@ function resolveQuantizeBin(opts, required) {
|
|
|
13775
13980
|
const dir = llamaCppDir(opts.llamaCpp);
|
|
13776
13981
|
if (dir) {
|
|
13777
13982
|
const candidates = [
|
|
13778
|
-
|
|
13779
|
-
|
|
13780
|
-
|
|
13781
|
-
|
|
13983
|
+
join17(dir, "build", "bin", "llama-quantize"),
|
|
13984
|
+
join17(dir, "build", "bin", "quantize"),
|
|
13985
|
+
join17(dir, "llama-quantize"),
|
|
13986
|
+
join17(dir, "quantize")
|
|
13782
13987
|
];
|
|
13783
13988
|
const found = firstExisting(candidates);
|
|
13784
13989
|
if (found) return found;
|
|
@@ -13926,7 +14131,7 @@ function registerModelsCommands(parent) {
|
|
|
13926
14131
|
}
|
|
13927
14132
|
const quantPlan = planQuant(String(cmdOpts.quant));
|
|
13928
14133
|
const printOnly = Boolean(cmdOpts.printCommand);
|
|
13929
|
-
const cacheDir =
|
|
14134
|
+
const cacheDir = resolve12(cmdOpts.cacheDir || defaultCacheDir());
|
|
13930
14135
|
mkdirSync5(cacheDir, { recursive: true });
|
|
13931
14136
|
const serveTarget = await resolveServeTarget(
|
|
13932
14137
|
target,
|
|
@@ -13935,9 +14140,9 @@ function registerModelsCommands(parent) {
|
|
|
13935
14140
|
Boolean(cmdOpts.forceDownload)
|
|
13936
14141
|
);
|
|
13937
14142
|
const slug = ollamaSlug(serveTarget.modelName);
|
|
13938
|
-
const outputDir =
|
|
13939
|
-
const finalPath =
|
|
13940
|
-
const intermediatePath = quantPlan.requiresQuantize ?
|
|
14143
|
+
const outputDir = resolve12(cmdOpts.output || join17(process.cwd(), `${slug}-gguf`));
|
|
14144
|
+
const finalPath = join17(outputDir, `${slug}.${quantPlan.quant}.gguf`);
|
|
14145
|
+
const intermediatePath = quantPlan.requiresQuantize ? join17(outputDir, `${slug}.f16.gguf`) : void 0;
|
|
13941
14146
|
const convertScript = resolveConvertScript(cmdOpts, !printOnly);
|
|
13942
14147
|
const convertOutfile = intermediatePath ?? finalPath;
|
|
13943
14148
|
const steps = [
|
|
@@ -13975,7 +14180,7 @@ function registerModelsCommands(parent) {
|
|
|
13975
14180
|
}
|
|
13976
14181
|
if (specPath) systemPrompt2 = loadSystemPromptFromSpec(specPath);
|
|
13977
14182
|
}
|
|
13978
|
-
modelfilePath =
|
|
14183
|
+
modelfilePath = join17(outputDir, "Modelfile");
|
|
13979
14184
|
modelfileContent = buildModelfile(basename9(finalPath), systemPrompt2);
|
|
13980
14185
|
const createModel = cmdOpts.ollamaCreate !== false;
|
|
13981
14186
|
if (createModel) {
|
|
@@ -14062,7 +14267,7 @@ function registerModelsCommands(parent) {
|
|
|
14062
14267
|
}
|
|
14063
14268
|
});
|
|
14064
14269
|
models.command("setup-runtime").description("Install an isolated Python runtime for local model serving").option("--python <path>", "Python 3.10-3.12 executable to create the runtime").option("--cache-dir <path>", "Cache directory for the managed runtime").option("-f, --force", "Recreate the runtime if it already exists").option("--print-command", "Print the setup commands without running them").action(async (cmdOpts) => {
|
|
14065
|
-
const cacheDir =
|
|
14270
|
+
const cacheDir = resolve12(cmdOpts.cacheDir || defaultCacheDir());
|
|
14066
14271
|
const venvDir = runtimeDir(cacheDir);
|
|
14067
14272
|
const venvPython = runtimePythonPath(cacheDir);
|
|
14068
14273
|
const python = pickRuntimePython(cmdOpts.python);
|
|
@@ -14108,7 +14313,7 @@ function registerModelsCommands(parent) {
|
|
|
14108
14313
|
});
|
|
14109
14314
|
models.command("serve").description("Serve a downloaded model with an OpenAI-compatible local API").argument("<target>", "Model ID/prefix, model directory, or .tar.gz artifact").option("--spec <path>", "Behavior spec JSON to apply as the default system prompt").option("--no-spec-prompt", "Do not auto-apply a behavior spec system prompt").option("--host <host>", "Host to bind", "127.0.0.1").option("--port <port>", "Port to bind", "8000").option("--python <path>", "Python executable").option("--cache-dir <path>", "Cache directory for downloaded/extracted models").option("--device <device>", "Inference device: auto, cpu, cuda, or mps", "auto").option("--force-download", "Re-download and re-extract model artifacts").option("--max-tokens <n>", "Default max completion tokens", "512").option("--temperature <n>", "Default sampling temperature", "0.7").option("--json-schema <path>", "JSON Schema file to enforce by default for chat completions").option("--json-repair-attempts <n>", "Number of JSON/schema repair retries", "1").option("--trust-remote-code", "Pass trust_remote_code=True to transformers").option("--managed", "Run a local lifecycle manager in front of the model server").option("--idle-timeout <seconds>", "Managed mode idle timeout before stopping the model", "300").option("--restart-after-requests <n>", "Managed mode restart threshold; 0 disables request-count restarts", "100").option("--gate-field <field>", "Response JSON field to log as the managed serving gate result", "should_process").option("--log-file <path>", "Write managed serving JSONL request logs to a file").option("--print-command", "Print the underlying Python command without starting it").action(async (target, cmdOpts) => {
|
|
14110
14315
|
const opts = parent.optsWithGlobals();
|
|
14111
|
-
const cacheDir =
|
|
14316
|
+
const cacheDir = resolve12(cmdOpts.cacheDir || defaultCacheDir());
|
|
14112
14317
|
mkdirSync5(cacheDir, { recursive: true });
|
|
14113
14318
|
const python = resolveServePython(cacheDir, cmdOpts.python);
|
|
14114
14319
|
if (!cmdOpts.printCommand) ensureServingRuntime(python, cacheDir);
|
|
@@ -14157,7 +14362,7 @@ function registerModelsCommands(parent) {
|
|
|
14157
14362
|
idle_timeout_seconds: idleTimeoutSeconds,
|
|
14158
14363
|
restart_after_requests: restartAfterRequests,
|
|
14159
14364
|
gate_field: String(cmdOpts.gateField),
|
|
14160
|
-
log_file: cmdOpts.logFile ?
|
|
14365
|
+
log_file: cmdOpts.logFile ? resolve12(cmdOpts.logFile) : void 0
|
|
14161
14366
|
} : void 0;
|
|
14162
14367
|
if (cmdOpts.printCommand) return printServeCommand(python, args2, env, managedConfig);
|
|
14163
14368
|
if (!isJsonMode()) {
|
|
@@ -14194,12 +14399,12 @@ function registerModelsCommands(parent) {
|
|
|
14194
14399
|
idleTimeoutSeconds,
|
|
14195
14400
|
restartAfterRequests,
|
|
14196
14401
|
gateField: String(cmdOpts.gateField),
|
|
14197
|
-
logFile: cmdOpts.logFile ?
|
|
14402
|
+
logFile: cmdOpts.logFile ? resolve12(cmdOpts.logFile) : void 0
|
|
14198
14403
|
});
|
|
14199
14404
|
return;
|
|
14200
14405
|
}
|
|
14201
14406
|
await new Promise((resolvePromise, reject) => {
|
|
14202
|
-
const child =
|
|
14407
|
+
const child = spawn4(python, args2, {
|
|
14203
14408
|
stdio: "inherit",
|
|
14204
14409
|
env: { ...process.env, ...env }
|
|
14205
14410
|
});
|
|
@@ -14220,15 +14425,15 @@ function registerModelsCommands(parent) {
|
|
|
14220
14425
|
}
|
|
14221
14426
|
|
|
14222
14427
|
// src/commands/push.ts
|
|
14223
|
-
import { readFileSync as
|
|
14224
|
-
import { resolve as
|
|
14428
|
+
import { readFileSync as readFileSync14, writeFileSync as writeFileSync7 } from "fs";
|
|
14429
|
+
import { resolve as resolve13 } from "path";
|
|
14225
14430
|
function persistRemoteId(filePath, id) {
|
|
14226
|
-
const raw = JSON.parse(
|
|
14431
|
+
const raw = JSON.parse(readFileSync14(filePath, "utf-8"));
|
|
14227
14432
|
raw.id = id;
|
|
14228
14433
|
writeFileSync7(filePath, JSON.stringify(raw, null, 2) + "\n");
|
|
14229
14434
|
}
|
|
14230
14435
|
async function pushLocalSpec(file, opts) {
|
|
14231
|
-
const filePath =
|
|
14436
|
+
const filePath = resolve13(file);
|
|
14232
14437
|
const spec = loadSpec(file);
|
|
14233
14438
|
const evalCaseErrors = validateEvalCases(spec);
|
|
14234
14439
|
if (evalCaseErrors.length > 0) {
|
|
@@ -14287,6 +14492,7 @@ function registerCloudCommands(parent) {
|
|
|
14287
14492
|
registerLabelCommands(cloud);
|
|
14288
14493
|
registerRunsCommands(cloud);
|
|
14289
14494
|
registerModelsCommands(cloud);
|
|
14495
|
+
registerEndpointCommands(cloud);
|
|
14290
14496
|
}
|
|
14291
14497
|
function weeklyRows(data3) {
|
|
14292
14498
|
if (typeof data3.weekly_remaining_percent !== "number" || !Number.isFinite(data3.weekly_remaining_percent) || typeof data3.weekly_cost_limit_usd !== "number" || !Number.isFinite(data3.weekly_cost_limit_usd) || data3.weekly_cost_limit_usd <= 0) return [];
|
|
@@ -14325,15 +14531,15 @@ Use \`tt balance\` for cloud training credits.`);
|
|
|
14325
14531
|
}
|
|
14326
14532
|
|
|
14327
14533
|
// src/commands/upgrade.ts
|
|
14328
|
-
import { spawn as
|
|
14329
|
-
import { accessSync, constants as constants2, existsSync as existsSync11, readFileSync as
|
|
14330
|
-
import { dirname as dirname15, join as
|
|
14534
|
+
import { spawn as spawn6 } from "child_process";
|
|
14535
|
+
import { accessSync, constants as constants2, existsSync as existsSync11, readFileSync as readFileSync16, realpathSync } from "fs";
|
|
14536
|
+
import { dirname as dirname15, join as join19, win32 } from "path";
|
|
14331
14537
|
import chalk14 from "chalk";
|
|
14332
14538
|
|
|
14333
14539
|
// src/update-check.ts
|
|
14334
|
-
import { spawn as
|
|
14335
|
-
import { mkdirSync as mkdirSync6, readFileSync as
|
|
14336
|
-
import { dirname as dirname14, join as
|
|
14540
|
+
import { spawn as spawn5 } from "child_process";
|
|
14541
|
+
import { mkdirSync as mkdirSync6, readFileSync as readFileSync15, writeFileSync as writeFileSync8 } from "fs";
|
|
14542
|
+
import { dirname as dirname14, join as join18 } from "path";
|
|
14337
14543
|
import chalk13 from "chalk";
|
|
14338
14544
|
var CLI_PACKAGE_NAME = "@tuned-tensor/cli";
|
|
14339
14545
|
var PACKAGE_LATEST_URL = "https://registry.npmjs.org/@tuned-tensor%2fcli/latest";
|
|
@@ -14382,14 +14588,14 @@ function compareVersions(left, right) {
|
|
|
14382
14588
|
return comparePrerelease(left.prerelease, right.prerelease);
|
|
14383
14589
|
}
|
|
14384
14590
|
function getCliUpdateCacheFile(env = process.env) {
|
|
14385
|
-
return
|
|
14591
|
+
return join18(getTunedTensorHome(env), "update-check.json");
|
|
14386
14592
|
}
|
|
14387
14593
|
function cliUpdateChecksDisabled(env = process.env) {
|
|
14388
14594
|
return [env.TT_NO_UPDATE_CHECK, env.NO_UPDATE_NOTIFIER, env.CI].some((value) => value !== void 0 && value !== "" && value !== "0" && value !== "false");
|
|
14389
14595
|
}
|
|
14390
14596
|
function readUpdateCache(file) {
|
|
14391
14597
|
try {
|
|
14392
|
-
const payload = JSON.parse(
|
|
14598
|
+
const payload = JSON.parse(readFileSync15(file, "utf8"));
|
|
14393
14599
|
if (typeof payload.checkedAt !== "number" || typeof payload.latestVersion !== "string" || !parseVersion(payload.latestVersion)) return null;
|
|
14394
14600
|
return { checkedAt: payload.checkedAt, latestVersion: payload.latestVersion };
|
|
14395
14601
|
} catch {
|
|
@@ -14441,10 +14647,10 @@ async function fetchLatestCliVersion(options = {}) {
|
|
|
14441
14647
|
return null;
|
|
14442
14648
|
}
|
|
14443
14649
|
})();
|
|
14444
|
-
const deadline = new Promise((
|
|
14650
|
+
const deadline = new Promise((resolve14) => {
|
|
14445
14651
|
timeout = setTimeout(() => {
|
|
14446
14652
|
controller.abort();
|
|
14447
|
-
|
|
14653
|
+
resolve14(null);
|
|
14448
14654
|
}, timeoutMs);
|
|
14449
14655
|
});
|
|
14450
14656
|
try {
|
|
@@ -14484,7 +14690,7 @@ function spawnCliUpdateRefresh(cacheFile) {
|
|
|
14484
14690
|
const cached = readUpdateCache(cacheFile);
|
|
14485
14691
|
if (cached) recordLatestCliVersion(cacheFile, cached.latestVersion);
|
|
14486
14692
|
try {
|
|
14487
|
-
const child =
|
|
14693
|
+
const child = spawn5(
|
|
14488
14694
|
process.execPath,
|
|
14489
14695
|
["-e", REFRESH_SCRIPT, PACKAGE_LATEST_URL, cacheFile],
|
|
14490
14696
|
{ detached: true, stdio: "ignore", windowsHide: true }
|
|
@@ -14526,10 +14732,11 @@ function resolveGlobalInstall(entrypoint, platform = process.platform) {
|
|
|
14526
14732
|
if (/[\\/]_npx[\\/]/.test(script)) return null;
|
|
14527
14733
|
const match = platform === "win32" ? script.match(/^(.*)\\node_modules\\@tuned-tensor\\cli\\dist\\index\.js$/i) : script.match(/^(.*)\/lib\/node_modules\/@tuned-tensor\/cli\/dist\/index\.js$/);
|
|
14528
14734
|
if (!match?.[1]) return null;
|
|
14529
|
-
|
|
14735
|
+
const pathApi = platform === "win32" ? win32 : { dirname: dirname15 };
|
|
14736
|
+
return { prefix: match[1], packageDir: pathApi.dirname(pathApi.dirname(script)) };
|
|
14530
14737
|
}
|
|
14531
14738
|
function prefixIsWritable(prefix, platform) {
|
|
14532
|
-
const modules = platform === "win32" ?
|
|
14739
|
+
const modules = platform === "win32" ? join19(prefix, "node_modules") : join19(prefix, "lib", "node_modules");
|
|
14533
14740
|
try {
|
|
14534
14741
|
accessSync(existsSync11(modules) ? modules : prefix, constants2.W_OK);
|
|
14535
14742
|
return true;
|
|
@@ -14539,32 +14746,50 @@ function prefixIsWritable(prefix, platform) {
|
|
|
14539
14746
|
}
|
|
14540
14747
|
function installedVersion(packageDir) {
|
|
14541
14748
|
try {
|
|
14542
|
-
const manifest = JSON.parse(
|
|
14749
|
+
const manifest = JSON.parse(readFileSync16(join19(packageDir, "package.json"), "utf8"));
|
|
14543
14750
|
return typeof manifest.version === "string" ? manifest.version : void 0;
|
|
14544
14751
|
} catch {
|
|
14545
14752
|
return void 0;
|
|
14546
14753
|
}
|
|
14547
14754
|
}
|
|
14548
|
-
function
|
|
14549
|
-
|
|
14755
|
+
function resolveWindowsNpmCli(env, execPath) {
|
|
14756
|
+
const path = Object.entries(env).find(([key]) => key.toLowerCase() === "path")?.[1] ?? "";
|
|
14757
|
+
const directories = [win32.dirname(execPath), ...path.split(";")];
|
|
14758
|
+
for (const directory of directories) {
|
|
14759
|
+
if (!directory) continue;
|
|
14760
|
+
const candidate = win32.join(directory.replace(/^"|"$/g, ""), "node_modules", "npm", "bin", "npm-cli.js");
|
|
14761
|
+
if (existsSync11(candidate)) return candidate;
|
|
14762
|
+
}
|
|
14763
|
+
throw new Error("Could not locate npm-cli.js beside Node.js or on PATH. Reinstall Node.js with npm, then retry.");
|
|
14550
14764
|
}
|
|
14551
|
-
function
|
|
14552
|
-
|
|
14765
|
+
function isWindowsInstaller(prefix) {
|
|
14766
|
+
try {
|
|
14767
|
+
return JSON.parse(readFileSync16(join19(prefix, "tt-install.json"), "utf8")).manager === "windows-installer";
|
|
14768
|
+
} catch {
|
|
14769
|
+
return false;
|
|
14770
|
+
}
|
|
14771
|
+
}
|
|
14772
|
+
function defaultNpmRunner(platform, env) {
|
|
14773
|
+
return async (args2) => await new Promise((resolve14, reject) => {
|
|
14553
14774
|
const windows = platform === "win32";
|
|
14554
|
-
const child =
|
|
14555
|
-
windows ?
|
|
14556
|
-
windows ?
|
|
14775
|
+
const child = spawn6(
|
|
14776
|
+
windows ? process.execPath : "npm",
|
|
14777
|
+
windows ? [resolveWindowsNpmCli(env, process.execPath), ...args2] : args2,
|
|
14557
14778
|
{
|
|
14558
|
-
|
|
14779
|
+
env,
|
|
14780
|
+
shell: false,
|
|
14559
14781
|
// Keep stdout clean for --json; npm progress goes to stderr.
|
|
14560
14782
|
stdio: ["ignore", isJsonMode() ? 2 : "inherit", "inherit"]
|
|
14561
14783
|
}
|
|
14562
14784
|
);
|
|
14563
14785
|
child.once("error", reject);
|
|
14564
|
-
child.once("close", (code2) =>
|
|
14786
|
+
child.once("close", (code2) => resolve14(code2 ?? 1));
|
|
14565
14787
|
});
|
|
14566
14788
|
}
|
|
14567
|
-
function formatCommand(args2) {
|
|
14789
|
+
function formatCommand(args2, platform) {
|
|
14790
|
+
if (platform === "win32") {
|
|
14791
|
+
return ["npm.cmd", ...args2].map((arg) => /^[a-zA-Z0-9_@./:\\+=-]+$/.test(arg) ? arg : "'" + arg.replaceAll("'", "''") + "'").join(" ");
|
|
14792
|
+
}
|
|
14568
14793
|
return ["npm", ...args2].map((arg) => /\s/.test(arg) ? JSON.stringify(arg) : arg).join(" ");
|
|
14569
14794
|
}
|
|
14570
14795
|
function registerUpgradeCommand(program, deps) {
|
|
@@ -14584,6 +14809,15 @@ Set TT_NO_UPDATE_CHECK=1 to silence update notices.
|
|
|
14584
14809
|
const cacheFile = getCliUpdateCacheFile(deps.env);
|
|
14585
14810
|
const current = deps.version;
|
|
14586
14811
|
const target = requested?.trim() || "latest";
|
|
14812
|
+
if (!/^[a-zA-Z0-9][a-zA-Z0-9.+-]*$/.test(target)) {
|
|
14813
|
+
throw new Error("Choose an exact version or npm dist-tag, such as 0.27.0, latest, or beta.");
|
|
14814
|
+
}
|
|
14815
|
+
const install = resolveGlobalInstall(deps.entrypoint, platform);
|
|
14816
|
+
if (!options.check && install && isWindowsInstaller(install.prefix)) {
|
|
14817
|
+
throw new Error(
|
|
14818
|
+
"This tt is managed by the Windows installer. Upgrade with:\n winget upgrade --id TunedTensor.CLI --exact --source winget\nOr download and run the newer Windows setup from https://github.com/tunedtensor/tuned-tensor-cli/releases.\nWinGet requires the release to be available in its catalog; use setup for prereleases."
|
|
14819
|
+
);
|
|
14820
|
+
}
|
|
14587
14821
|
let latest;
|
|
14588
14822
|
if (target === "latest") {
|
|
14589
14823
|
latest = await fetchLatestCliVersion({ fetchImpl: deps.fetchImpl, timeoutMs: 1e4 }) ?? void 0;
|
|
@@ -14614,7 +14848,6 @@ Set TT_NO_UPDATE_CHECK=1 to silence update notices.
|
|
|
14614
14848
|
}
|
|
14615
14849
|
return;
|
|
14616
14850
|
}
|
|
14617
|
-
const install = resolveGlobalInstall(deps.entrypoint, platform);
|
|
14618
14851
|
const packageSpec = `${CLI_PACKAGE_NAME}@${target}`;
|
|
14619
14852
|
if (!install) {
|
|
14620
14853
|
throw new Error(
|
|
@@ -14626,26 +14859,27 @@ Upgrade it the way you installed it, for example:
|
|
|
14626
14859
|
const npmArgs = ["install", "-g", "--ignore-scripts", "--prefix", install.prefix, packageSpec];
|
|
14627
14860
|
if (!prefixIsWritable(install.prefix, platform)) {
|
|
14628
14861
|
throw new Error(
|
|
14629
|
-
`tt is installed in ${install.prefix}, which this user can't write to.
|
|
14630
|
-
|
|
14862
|
+
`tt is installed in ${install.prefix}, which this user can't write to. ` + (platform === "win32" ? `Use an account with access to that directory and run:
|
|
14863
|
+
${formatCommand(npmArgs, platform)}` : `Run:
|
|
14864
|
+
sudo ${formatCommand(npmArgs, platform)}`)
|
|
14631
14865
|
);
|
|
14632
14866
|
}
|
|
14633
14867
|
if (!isJsonMode()) {
|
|
14634
14868
|
console.log(`Upgrading tt ${current} \u2192 ${chalk14.bold(latest ?? target)}`);
|
|
14635
|
-
console.log(chalk14.dim(`$ ${formatCommand(npmArgs)}`));
|
|
14869
|
+
console.log(chalk14.dim(`$ ${formatCommand(npmArgs, platform)}`));
|
|
14636
14870
|
}
|
|
14637
14871
|
let code2;
|
|
14638
14872
|
try {
|
|
14639
|
-
code2 = await (deps.runNpm ?? defaultNpmRunner(platform))(npmArgs);
|
|
14873
|
+
code2 = await (deps.runNpm ?? defaultNpmRunner(platform, deps.env))(npmArgs);
|
|
14640
14874
|
} catch (error) {
|
|
14641
14875
|
throw new Error(
|
|
14642
14876
|
`Could not run npm (${error instanceof Error ? error.message : String(error)}). Run it yourself:
|
|
14643
|
-
${formatCommand(npmArgs)}`
|
|
14877
|
+
${formatCommand(npmArgs, platform)}`
|
|
14644
14878
|
);
|
|
14645
14879
|
}
|
|
14646
14880
|
if (code2 !== 0) {
|
|
14647
14881
|
throw new Error(`npm exited with code ${code2}. Run it yourself to see details:
|
|
14648
|
-
${formatCommand(npmArgs)}`);
|
|
14882
|
+
${formatCommand(npmArgs, platform)}`);
|
|
14649
14883
|
}
|
|
14650
14884
|
const installed = installedVersion(install.packageDir) ?? latest ?? target;
|
|
14651
14885
|
if (isJsonMode()) {
|
|
@@ -14802,8 +15036,8 @@ async function runSelfCommand(args2, options = {}) {
|
|
|
14802
15036
|
if (!entrypoint) {
|
|
14803
15037
|
throw new Error("Cannot locate the tt CLI entrypoint.");
|
|
14804
15038
|
}
|
|
14805
|
-
return await new Promise((
|
|
14806
|
-
const child =
|
|
15039
|
+
return await new Promise((resolve14, reject) => {
|
|
15040
|
+
const child = spawn7(process.execPath, [entrypoint, ...options.captureJson ? ["--json"] : [], ...args2], {
|
|
14807
15041
|
cwd: options.cwd,
|
|
14808
15042
|
env: options.env ?? process.env,
|
|
14809
15043
|
stdio: options.captureJson ? ["inherit", "pipe", "inherit"] : "inherit",
|
|
@@ -14849,7 +15083,7 @@ async function runSelfCommand(args2, options = {}) {
|
|
|
14849
15083
|
child.once("close", (exitCode, signal) => {
|
|
14850
15084
|
cleanup();
|
|
14851
15085
|
const signalNumber = signal ? osConstants2.signals[signal] : void 0;
|
|
14852
|
-
|
|
15086
|
+
resolve14({
|
|
14853
15087
|
exitCode: exitCode ?? (typeof signalNumber === "number" ? 128 + signalNumber : 1),
|
|
14854
15088
|
signal,
|
|
14855
15089
|
...options.captureJson ? { stdout: stdout3 } : {}
|
|
@@ -15084,7 +15318,7 @@ ${formatCliUpdateNotice(update)}
|
|
|
15084
15318
|
}
|
|
15085
15319
|
|
|
15086
15320
|
// src/index.ts
|
|
15087
|
-
runCli("0.
|
|
15321
|
+
runCli("0.28.0").catch((err) => {
|
|
15088
15322
|
reportError(err);
|
|
15089
15323
|
process.exit(1);
|
|
15090
15324
|
});
|