@tuned-tensor/cli 0.20.0 → 0.21.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 +40 -7
- package/dist/{chunk-4Q54NHKR.js → chunk-PVLX7LJI.js} +381 -148
- package/dist/chunk-PVLX7LJI.js.map +1 -0
- package/dist/index.js +1679 -325
- package/dist/index.js.map +1 -1
- package/dist/local-runtime.js +41 -6
- package/dist/local-runtime.js.map +1 -1
- package/docs/local-runtime/architecture.md +6 -1
- package/docs/local-runtime/decision.md +142 -0
- package/docs/spec-architecture.html +2 -2
- package/docs/spec-workflow.md +11 -3
- package/examples/single-spec/decision/tunedtensor.json +630 -0
- package/package.json +5 -2
- package/training/adapter/src/evaluate.py +14 -7
- package/training/adapter/src/model_contract.py +38 -5
- package/training/adapter/src/prefetch.py +6 -1
- package/training/adapter/src/serve.py +37 -11
- package/training/adapter/src/train.py +8 -0
- package/training/decision/pyproject.toml +25 -0
- package/training/decision/src/common.py +127 -0
- package/training/decision/src/evaluate.py +70 -0
- package/training/decision/src/train.py +181 -0
- package/training/decision/uv.lock +1175 -0
- package/dist/chunk-4Q54NHKR.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,22 +1,31 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
DECISION_MODELS,
|
|
3
4
|
DEFAULT_ARTIFACT_ROOT,
|
|
5
|
+
DEFAULT_DECISION_RUNS_DIR,
|
|
4
6
|
DEFAULT_FOUNDATION_RUNS_DIR,
|
|
5
7
|
DEFAULT_PROJECT_STORE_ROOT,
|
|
6
8
|
TRAINING_MODELS,
|
|
7
9
|
assertFoundationSettingsMatch,
|
|
8
10
|
assessHardware,
|
|
11
|
+
buildDecisionPythonCommand,
|
|
9
12
|
buildFoundationPythonCommand,
|
|
10
13
|
buildSystemMessage,
|
|
11
14
|
canonicalFoundationPipeline,
|
|
12
15
|
canonicalJson,
|
|
13
16
|
canonicalPipeline,
|
|
17
|
+
compareEvalReports,
|
|
14
18
|
configForSpec,
|
|
15
19
|
createExecutionPlan,
|
|
16
20
|
createLocalStore,
|
|
21
|
+
decisionLabels,
|
|
17
22
|
defaultLocalHome,
|
|
18
23
|
defaultStoreRoot,
|
|
24
|
+
deriveSampleSeed,
|
|
25
|
+
evalReportSchema,
|
|
26
|
+
evaluateCapabilities,
|
|
19
27
|
expandUserPath,
|
|
28
|
+
fileUri,
|
|
20
29
|
formatAgentHostBlock,
|
|
21
30
|
formatHostStatusLine,
|
|
22
31
|
getAgentConfigDir,
|
|
@@ -32,24 +41,27 @@ import {
|
|
|
32
41
|
pipelineForRunInput,
|
|
33
42
|
pipelineFromFoundationHyperparameters,
|
|
34
43
|
readHardwareSnapshot,
|
|
44
|
+
resolveDecisionModel,
|
|
35
45
|
resolveProjectConfig,
|
|
36
46
|
runGpuProcess,
|
|
37
47
|
runLocalPipeline,
|
|
38
48
|
runLoggedProcess,
|
|
39
49
|
runReportSchema,
|
|
40
50
|
specHash,
|
|
51
|
+
splitSpecExamples,
|
|
41
52
|
validateBehaviorSpec,
|
|
42
53
|
validateEvalCases,
|
|
43
54
|
validatePipeline,
|
|
44
55
|
warningsFromSnapshot,
|
|
56
|
+
withDecisionPythonEnvironment,
|
|
45
57
|
withFoundationPythonEnvironment
|
|
46
|
-
} from "./chunk-
|
|
58
|
+
} from "./chunk-PVLX7LJI.js";
|
|
47
59
|
|
|
48
60
|
// src/cli.ts
|
|
49
61
|
import { spawn as spawn4 } from "child_process";
|
|
50
62
|
import { constants as osConstants2 } from "os";
|
|
51
63
|
import { Command } from "commander";
|
|
52
|
-
import
|
|
64
|
+
import chalk13 from "chalk";
|
|
53
65
|
|
|
54
66
|
// src/local-agent-client.ts
|
|
55
67
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
@@ -75,15 +87,18 @@ var FolderName = Type.String({
|
|
|
75
87
|
description: "One new folder name directly beneath the current workspace"
|
|
76
88
|
});
|
|
77
89
|
var LocalBaseModel = Type.String({
|
|
78
|
-
|
|
79
|
-
|
|
90
|
+
minLength: 3,
|
|
91
|
+
maxLength: 193,
|
|
92
|
+
pattern: "^[A-Za-z0-9][A-Za-z0-9._-]*/[A-Za-z0-9][A-Za-z0-9._-]*$",
|
|
93
|
+
description: `Hugging Face repo id. Certified adapter models: ${TRAINING_MODELS.map((model) => model.id).join(", ")}. Decision models: ${DECISION_MODELS.map((model) => model.id).join(", ")}. Other adapter repos require hyperparameters.base_model_revision (40-character commit SHA).`
|
|
80
94
|
});
|
|
81
95
|
var NonEmptyText = Type.String({ minLength: 1, maxLength: 1e3 });
|
|
82
96
|
var LocalProjectSpecSchema = Type.Object({
|
|
83
97
|
name: Type.String({ minLength: 1, maxLength: 255 }),
|
|
84
98
|
description: Type.Optional(Type.String({ maxLength: 5e3 })),
|
|
85
99
|
base_model: Type.Optional(LocalBaseModel),
|
|
86
|
-
engine: Type.Optional(Type.Union([Type.Literal("adapter"), Type.Literal("foundation")])),
|
|
100
|
+
engine: Type.Optional(Type.Union([Type.Literal("adapter"), Type.Literal("foundation"), Type.Literal("decision")])),
|
|
101
|
+
decision: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
|
|
87
102
|
hyperparameters: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
|
|
88
103
|
foundation: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
|
|
89
104
|
dataset_prebuilt: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
|
|
@@ -335,6 +350,7 @@ var EDIT_KEYS = /* @__PURE__ */ new Set([
|
|
|
335
350
|
"base_model",
|
|
336
351
|
"hyperparameters",
|
|
337
352
|
"foundation",
|
|
353
|
+
"decision",
|
|
338
354
|
"dataset_prebuilt",
|
|
339
355
|
"runtime",
|
|
340
356
|
"evaluation",
|
|
@@ -774,7 +790,7 @@ async function prepareLocalPipelineAction(args2) {
|
|
|
774
790
|
specPath: spec.displayPath,
|
|
775
791
|
specSha256: createHash2("sha256").update(spec.source).digest("hex"),
|
|
776
792
|
dryRun,
|
|
777
|
-
engine: input.kind === "foundation-spec" ? "foundation" : "adapter",
|
|
793
|
+
engine: input.kind === "foundation-spec" ? "foundation" : input.kind === "decision-spec" ? "decision" : "adapter",
|
|
778
794
|
resolvedSpec: input.spec,
|
|
779
795
|
resolvedConfig,
|
|
780
796
|
...config ? {
|
|
@@ -1534,9 +1550,16 @@ function canonicalizeHostedBaseModel(model) {
|
|
|
1534
1550
|
}
|
|
1535
1551
|
return canonical;
|
|
1536
1552
|
}
|
|
1553
|
+
var HUGGING_FACE_REPO_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,95}\/[A-Za-z0-9][A-Za-z0-9._-]{0,95}$/;
|
|
1554
|
+
function canonicalizeLocalBaseModel(model) {
|
|
1555
|
+
if (typeof model === "string" && HUGGING_FACE_REPO_ID.test(model.trim())) {
|
|
1556
|
+
return BASE_MODEL_ALIASES.get(model.trim().toLowerCase()) ?? model.trim();
|
|
1557
|
+
}
|
|
1558
|
+
return canonicalizeBaseModel(model);
|
|
1559
|
+
}
|
|
1537
1560
|
function canonicalizeSpecBaseModel(body) {
|
|
1538
1561
|
if ("base_model" in body && body.base_model !== void 0) {
|
|
1539
|
-
return { ...body, base_model:
|
|
1562
|
+
return { ...body, base_model: canonicalizeLocalBaseModel(body.base_model) };
|
|
1540
1563
|
}
|
|
1541
1564
|
return body;
|
|
1542
1565
|
}
|
|
@@ -1958,8 +1981,8 @@ function createTunedTensorTools(api, options = {}) {
|
|
|
1958
1981
|
});
|
|
1959
1982
|
}
|
|
1960
1983
|
),
|
|
1961
|
-
define("describe_pipeline", "Describe pipeline", "Describe the built-in adapter or
|
|
1962
|
-
engine: Type2.Optional(Type2.Union([Type2.Literal("adapter"), Type2.Literal("foundation")])),
|
|
1984
|
+
define("describe_pipeline", "Describe pipeline", "Describe the built-in adapter, foundation or decision workflow and exact TT commands. A decision spec fine-tunes a small typed decision model (e.g. convaiinnovations/laya) that returns a calibrated probability per label instead of generated text; use it for classification, routing, triage and yes/no or score gates. This never executes anything.", Type2.Object({
|
|
1985
|
+
engine: Type2.Optional(Type2.Union([Type2.Literal("adapter"), Type2.Literal("foundation"), Type2.Literal("decision")])),
|
|
1963
1986
|
target: Type2.Optional(Type2.Union([Type2.Literal("local"), Type2.Literal("cloud")]))
|
|
1964
1987
|
}, { additionalProperties: false }), async (p) => {
|
|
1965
1988
|
const engine = p.engine ?? "adapter";
|
|
@@ -1980,6 +2003,24 @@ function createTunedTensorTools(api, options = {}) {
|
|
|
1980
2003
|
},
|
|
1981
2004
|
note: "Configure runtime.gpu in tunedtensor.json with provider aws, instanceId, SSH user and optional AWS profile/region/identityFile. The instance must already be running and reachable with a verified SSH host key. AWS profile lookup and SSH access are separate; no TT token is required. GPU processes use it automatically; orchestration, scoring and artifacts stay local. Hardware tools inspect only the laptop; doctor checks the configured instance. The user manages AWS quota, costs and shutdown. Keep the laptop running until outputs return. Hosted training is retired."
|
|
1982
2005
|
} : void 0;
|
|
2006
|
+
if (engine === "decision") {
|
|
2007
|
+
return {
|
|
2008
|
+
version: 1,
|
|
2009
|
+
engine,
|
|
2010
|
+
scope: { execution: "local", devices: ["cpu", "mps", "cuda"] },
|
|
2011
|
+
canonical: canonicalPipeline("local"),
|
|
2012
|
+
models: DECISION_MODELS.map((model) => ({ id: model.id, revision: model.revision, parameters: model.parameters, license: model.license })),
|
|
2013
|
+
spec: 'engine "decision", base_model, decision {type: choice|noul|score, criteria}; example outputs are labels (choice keys, true/false, or score level indices). system_prompt, guidelines and constraints become the question instructions.',
|
|
2014
|
+
commands: {
|
|
2015
|
+
init: "tt init --engine decision",
|
|
2016
|
+
validate: "tt validate",
|
|
2017
|
+
dry_run: "tt pipeline run --dry-run --spec tunedtensor.json",
|
|
2018
|
+
run: "tt pipeline run --spec tunedtensor.json"
|
|
2019
|
+
},
|
|
2020
|
+
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>).",
|
|
2021
|
+
...host ? { host } : {}
|
|
2022
|
+
};
|
|
2023
|
+
}
|
|
1983
2024
|
if (engine === "foundation") {
|
|
1984
2025
|
const foundation = {
|
|
1985
2026
|
version: 1,
|
|
@@ -2097,7 +2138,7 @@ function createTunedTensorTools(api, options = {}) {
|
|
|
2097
2138
|
define(
|
|
2098
2139
|
"prepare_update_local_spec",
|
|
2099
2140
|
"Prepare behavior spec edit",
|
|
2100
|
-
"Prepare a reviewed edit of the existing local spec using the SHA-256 from get_local_spec. Use add_examples to append examples without resending existing ones. Other arrays and pipeline replace whole fields; hyperparameters and foundation settings merge by key. Runtime and evaluation objects merge recursively; null removes optional settings. Preserve unrelated fields. No write until /approve.",
|
|
2141
|
+
"Prepare a reviewed edit of the existing local spec using the SHA-256 from get_local_spec. Use add_examples to append examples without resending existing ones. Other arrays, the decision question and pipeline replace whole fields; hyperparameters and foundation settings merge by key. Runtime and evaluation objects merge recursively; null removes optional settings. Preserve unrelated fields. No write until /approve.",
|
|
2101
2142
|
Type2.Object({
|
|
2102
2143
|
spec_path: Type2.Optional(Type2.String({ maxLength: 1e3 })),
|
|
2103
2144
|
expected_sha256: Type2.String({ pattern: "^[a-f0-9]{64}$" }),
|
|
@@ -2118,6 +2159,7 @@ function createTunedTensorTools(api, options = {}) {
|
|
|
2118
2159
|
}, { additionalProperties: false }), { minItems: 1 })),
|
|
2119
2160
|
hyperparameters: Type2.Optional(Type2.Record(Type2.String(), Type2.Unknown())),
|
|
2120
2161
|
foundation: Type2.Optional(Type2.Record(Type2.String(), Type2.Unknown())),
|
|
2162
|
+
decision: Type2.Optional(Type2.Record(Type2.String(), Type2.Unknown())),
|
|
2121
2163
|
runtime: Type2.Optional(Type2.Union([Type2.Record(Type2.String(), Type2.Unknown()), Type2.Null()])),
|
|
2122
2164
|
evaluation: Type2.Optional(Type2.Union([Type2.Record(Type2.String(), Type2.Unknown()), Type2.Null()])),
|
|
2123
2165
|
pipeline: Type2.Optional(Type2.Union([Type2.Record(Type2.String(), Type2.Unknown()), Type2.Null()])),
|
|
@@ -2246,11 +2288,13 @@ function createTunedTensorTools(api, options = {}) {
|
|
|
2246
2288
|
// src/local-agent-client.ts
|
|
2247
2289
|
var MAX_TOOL_CALLS_PER_TURN = 12;
|
|
2248
2290
|
var SYSTEM_PROMPT = `You are the local Tuned Tensor assistant running on the user's laptop.
|
|
2249
|
-
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 project. 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.
|
|
2291
|
+
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 project. 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.
|
|
2250
2292
|
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.
|
|
2251
2293
|
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.
|
|
2252
|
-
For accurate adapter and
|
|
2294
|
+
For accurate adapter, foundation and decision workflow stages and commands, call \`describe_pipeline\` with the matching engine instead of relying on prior knowledge.
|
|
2253
2295
|
When the user asks to train, fine-tune, dry-run, or execute a workflow, call \`prepare_pipeline_run\`. Omit its pipeline argument to use the saved recipe or derive its default from the workspace spec. To change workflow stages, propose an edit to the spec pipeline section first; runtime and evaluation settings also belong in the core spec. This prepares a sealed local pipeline dry-run; approved pipeline actions are dry-runs only and may be stopped with Ctrl-C. Never claim training started. Real training requires an explicit direct \`tt pipeline run --spec tunedtensor.json\` command in the shell; preview uses the same command with \`--dry-run\`. The spec path is a --spec option, not a positional argument. No pipeline file or pipeline init is required for this default spec-driven workflow.
|
|
2296
|
+
Adapter base models: TT certifies ${TRAINING_MODELS.map((model) => model.id).join(", ")} (reviewed revision, architecture and memory profile). For adapter training, when the user asks for a model that is not certified, do not silently substitute a certified one. Confirm the repo exists with \`search_hugging_face\`, then explain the trade-off: an uncertified model trains as a text causal LM with generic LoRA defaults, safetensors weights and no remote code, but TT has not verified its architecture, chat template or hardware fit. If the user accepts, propose setting base_model to the exact repo id and hyperparameters.base_model_revision to a 40-character commit SHA the user copies from its Hugging Face commits page; never invent a SHA. Suggest the closest certified model as the lower-risk option, and point out that models needing remote code, vision inputs or gated access the user lacks will fail.
|
|
2297
|
+
Decision models use engine "decision" and their own certified registry. Call \`describe_pipeline\` with engine "decision" for supported models and typed question settings; do not apply adapter revision requirements to decision specs.
|
|
2254
2298
|
When the user asks to discover public models or datasets, call \`search_hugging_face\`; it searches Hugging Face metadata for foundation and fine-tuning workflows. The query is sent to huggingface.co, so never include secrets or private data.
|
|
2255
2299
|
For educational questions about how or why training works, call \`inspect_training_source\` before answering. Ground the explanation in the returned code and distinguish observed behavior from inferred rationale; do not invent author intent.
|
|
2256
2300
|
When the user wants to examine this host, GPU, VRAM, CUDA, or decide what this machine can train, fine-tune, or infer, call \`examine_hardware\` before recommending a base model, engine, or pipeline. For local execution, recommend only workloads marked ready (mention tight as a caution). This tool inspects only the laptop: do not use a missing local GPU to reject AWS training or infer remote capacity. For AWS, direct the user to \`tt doctor tunedtensor.json\` to check the configured instance. Never invent generic 7B/70B sizing. Never start or cancel training from the model tool loop.
|
|
@@ -2778,16 +2822,79 @@ function del(path, opts) {
|
|
|
2778
2822
|
}
|
|
2779
2823
|
|
|
2780
2824
|
// src/agent.ts
|
|
2781
|
-
import
|
|
2825
|
+
import chalk8 from "chalk";
|
|
2826
|
+
|
|
2827
|
+
// src/mascot.ts
|
|
2828
|
+
import chalk from "chalk";
|
|
2829
|
+
var MASCOT_NAME = "tt";
|
|
2830
|
+
var MASCOT_WIDTH = 11;
|
|
2831
|
+
var PALETTE = {
|
|
2832
|
+
a: "#B39DFB",
|
|
2833
|
+
// diagonal, top-left
|
|
2834
|
+
b: "#9B72F8",
|
|
2835
|
+
// diagonal, center
|
|
2836
|
+
c: "#7C3AED",
|
|
2837
|
+
// diagonal, bottom-right
|
|
2838
|
+
d: "#2B323D"
|
|
2839
|
+
// slate tiles
|
|
2840
|
+
};
|
|
2841
|
+
var MARK = [
|
|
2842
|
+
"aa.dd...",
|
|
2843
|
+
"aa.dd...",
|
|
2844
|
+
"........",
|
|
2845
|
+
"dd.bb.dd",
|
|
2846
|
+
"dd.bb.dd",
|
|
2847
|
+
"........",
|
|
2848
|
+
"...dd.cc",
|
|
2849
|
+
"...dd.cc"
|
|
2850
|
+
];
|
|
2851
|
+
function pixelRow(upper, lower) {
|
|
2852
|
+
let line = "";
|
|
2853
|
+
for (let column = 0; column < upper.length; column += 1) {
|
|
2854
|
+
const up = upper[column];
|
|
2855
|
+
const down = lower[column];
|
|
2856
|
+
if (up === "." && down === ".") line += " ";
|
|
2857
|
+
else if (up === ".") line += chalk.hex(PALETTE[down])("\u2584");
|
|
2858
|
+
else if (down === ".") line += chalk.hex(PALETTE[up])("\u2580");
|
|
2859
|
+
else if (up === down) line += chalk.hex(PALETTE[up])("\u2588");
|
|
2860
|
+
else line += chalk.hex(PALETTE[up]).bgHex(PALETTE[down])("\u2580");
|
|
2861
|
+
}
|
|
2862
|
+
return line;
|
|
2863
|
+
}
|
|
2864
|
+
function renderMascot() {
|
|
2865
|
+
if (chalk.level === 0) {
|
|
2866
|
+
return ["", " \u25A0 \u25A1", " \u25A1 \u25A0 \u25A1", " \u25A1 \u25A0"].map((row) => row.padEnd(MASCOT_WIDTH));
|
|
2867
|
+
}
|
|
2868
|
+
const rows2 = [];
|
|
2869
|
+
for (let row = 0; row < MARK.length; row += 2) {
|
|
2870
|
+
rows2.push(` ${pixelRow(MARK[row], MARK[row + 1])} `);
|
|
2871
|
+
}
|
|
2872
|
+
return rows2;
|
|
2873
|
+
}
|
|
2874
|
+
function mascotMark() {
|
|
2875
|
+
if (chalk.level === 0) return "\u259A";
|
|
2876
|
+
return `${chalk.hex(PALETTE.a)("\u2580")}${chalk.hex(PALETTE.c)("\u2584")}`;
|
|
2877
|
+
}
|
|
2878
|
+
function mascotSays(message) {
|
|
2879
|
+
return `${mascotMark()} ${chalk.hex(PALETTE.b).bold(MASCOT_NAME)} ${chalk.dim("\u203A")} ${message}`;
|
|
2880
|
+
}
|
|
2881
|
+
var FAREWELLS = [
|
|
2882
|
+
"See you next epoch.",
|
|
2883
|
+
"Bye! Your checkpoints are safe with me.",
|
|
2884
|
+
"Run tt to pick up where you left off."
|
|
2885
|
+
];
|
|
2886
|
+
function mascotFarewell(seed = Date.now()) {
|
|
2887
|
+
return mascotSays(FAREWELLS[Math.abs(seed) % FAREWELLS.length]);
|
|
2888
|
+
}
|
|
2782
2889
|
|
|
2783
2890
|
// src/shell.ts
|
|
2784
|
-
import
|
|
2891
|
+
import chalk7 from "chalk";
|
|
2785
2892
|
|
|
2786
2893
|
// src/terminal-markdown.ts
|
|
2787
|
-
import
|
|
2894
|
+
import chalk2 from "chalk";
|
|
2788
2895
|
import { stripVTControlCharacters } from "util";
|
|
2789
|
-
var accent =
|
|
2790
|
-
var code =
|
|
2896
|
+
var accent = chalk2.hex("#8B5CF6");
|
|
2897
|
+
var code = chalk2.hex("#A78BFA");
|
|
2791
2898
|
function sanitizeTerminalText(text2) {
|
|
2792
2899
|
return stripVTControlCharacters(text2).replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f]/g, "");
|
|
2793
2900
|
}
|
|
@@ -2806,7 +2913,7 @@ function renderInlineMarkdown(text2) {
|
|
|
2806
2913
|
if (text2[index] === "[") {
|
|
2807
2914
|
const link = text2.slice(index).match(/^\[([^\]]+)]\(([^)]+)\)/);
|
|
2808
2915
|
if (link) {
|
|
2809
|
-
rendered += `${
|
|
2916
|
+
rendered += `${chalk2.underline(renderInlineMarkdown(link[1]))}${chalk2.dim(` (${link[2]})`)}`;
|
|
2810
2917
|
index += link[0].length;
|
|
2811
2918
|
continue;
|
|
2812
2919
|
}
|
|
@@ -2815,7 +2922,7 @@ function renderInlineMarkdown(text2) {
|
|
|
2815
2922
|
if (strong) {
|
|
2816
2923
|
const close = closingDelimiter(text2, strong, index);
|
|
2817
2924
|
if (close > index + strong.length) {
|
|
2818
|
-
rendered +=
|
|
2925
|
+
rendered += chalk2.bold(
|
|
2819
2926
|
renderInlineMarkdown(text2.slice(index + strong.length, close))
|
|
2820
2927
|
);
|
|
2821
2928
|
index = close + strong.length;
|
|
@@ -2834,7 +2941,7 @@ function renderInlineMarkdown(text2) {
|
|
|
2834
2941
|
if (emphasis) {
|
|
2835
2942
|
const close = closingDelimiter(text2, emphasis, index);
|
|
2836
2943
|
if (close > index + 1) {
|
|
2837
|
-
rendered +=
|
|
2944
|
+
rendered += chalk2.italic(
|
|
2838
2945
|
renderInlineMarkdown(text2.slice(index + 1, close))
|
|
2839
2946
|
);
|
|
2840
2947
|
index = close + 1;
|
|
@@ -2849,7 +2956,7 @@ function renderInlineMarkdown(text2) {
|
|
|
2849
2956
|
function renderMarkdownLine(line, inCodeBlock) {
|
|
2850
2957
|
if (inCodeBlock) return code(` ${line}`);
|
|
2851
2958
|
const heading = line.match(/^\s{0,3}#{1,6}\s+(.+)$/);
|
|
2852
|
-
if (heading) return
|
|
2959
|
+
if (heading) return chalk2.bold(renderInlineMarkdown(heading[1]));
|
|
2853
2960
|
const bullet = line.match(/^(\s*)[-+*]\s+(.+)$/);
|
|
2854
2961
|
if (bullet) {
|
|
2855
2962
|
return `${bullet[1]}${accent("\u2022")} ${renderInlineMarkdown(bullet[2])}`;
|
|
@@ -2859,9 +2966,9 @@ function renderMarkdownLine(line, inCodeBlock) {
|
|
|
2859
2966
|
return `${numbered[1]}${accent(`${numbered[2]}.`)} ${renderInlineMarkdown(numbered[3])}`;
|
|
2860
2967
|
}
|
|
2861
2968
|
const quote = line.match(/^\s*>\s?(.*)$/);
|
|
2862
|
-
if (quote) return `${
|
|
2969
|
+
if (quote) return `${chalk2.dim("\u2502")} ${chalk2.italic(renderInlineMarkdown(quote[1]))}`;
|
|
2863
2970
|
if (/^\s{0,3}([-*_])(?:\s*\1){2,}\s*$/.test(line)) {
|
|
2864
|
-
return
|
|
2971
|
+
return chalk2.dim("\u2500".repeat(32));
|
|
2865
2972
|
}
|
|
2866
2973
|
return renderInlineMarkdown(line);
|
|
2867
2974
|
}
|
|
@@ -2937,9 +3044,9 @@ function wrapTerminalLine(line, width) {
|
|
|
2937
3044
|
}
|
|
2938
3045
|
|
|
2939
3046
|
// src/output.ts
|
|
2940
|
-
import
|
|
3047
|
+
import chalk3 from "chalk";
|
|
2941
3048
|
import Table from "cli-table3";
|
|
2942
|
-
var labelStyle =
|
|
3049
|
+
var labelStyle = chalk3.bold.hex("#A78BFA");
|
|
2943
3050
|
var jsonMode = false;
|
|
2944
3051
|
function setJsonMode(enabled) {
|
|
2945
3052
|
jsonMode = enabled;
|
|
@@ -2962,7 +3069,7 @@ function printTable(headers, rows2, meta) {
|
|
|
2962
3069
|
if (meta) {
|
|
2963
3070
|
const totalPages = Math.ceil(meta.total / meta.per_page);
|
|
2964
3071
|
console.log(
|
|
2965
|
-
|
|
3072
|
+
chalk3.dim(
|
|
2966
3073
|
`
|
|
2967
3074
|
Page ${meta.page}/${totalPages} (${meta.total} total)`
|
|
2968
3075
|
)
|
|
@@ -2973,17 +3080,17 @@ function printDetail(fields) {
|
|
|
2973
3080
|
const maxLabel = Math.max(...fields.map(([l]) => l.length));
|
|
2974
3081
|
for (const [label, value] of fields) {
|
|
2975
3082
|
const paddedLabel = label.padEnd(maxLabel);
|
|
2976
|
-
console.log(`${labelStyle(paddedLabel)} ${value ??
|
|
3083
|
+
console.log(`${labelStyle(paddedLabel)} ${value ?? chalk3.dim("\u2014")}`);
|
|
2977
3084
|
}
|
|
2978
3085
|
}
|
|
2979
3086
|
function printSuccess(message) {
|
|
2980
|
-
console.log(
|
|
3087
|
+
console.log(chalk3.green("\u2713") + " " + message);
|
|
2981
3088
|
}
|
|
2982
3089
|
function printWarning(message) {
|
|
2983
|
-
console.log(
|
|
3090
|
+
console.log(chalk3.yellow("!") + " " + message);
|
|
2984
3091
|
}
|
|
2985
3092
|
function printError(message) {
|
|
2986
|
-
console.error(
|
|
3093
|
+
console.error(chalk3.red("\u2717") + " " + message);
|
|
2987
3094
|
}
|
|
2988
3095
|
function reportError(err) {
|
|
2989
3096
|
const isApi = err instanceof ApiError;
|
|
@@ -3008,19 +3115,19 @@ function formatDate(iso) {
|
|
|
3008
3115
|
}
|
|
3009
3116
|
function formatStatus(status2) {
|
|
3010
3117
|
const map = {
|
|
3011
|
-
completed:
|
|
3012
|
-
running:
|
|
3013
|
-
training:
|
|
3014
|
-
evaluating:
|
|
3015
|
-
preparing:
|
|
3016
|
-
pending:
|
|
3017
|
-
uploading:
|
|
3018
|
-
failed:
|
|
3019
|
-
cancelled:
|
|
3020
|
-
validated:
|
|
3021
|
-
invalid:
|
|
3118
|
+
completed: chalk3.green,
|
|
3119
|
+
running: chalk3.hex("#A78BFA"),
|
|
3120
|
+
training: chalk3.hex("#A78BFA"),
|
|
3121
|
+
evaluating: chalk3.hex("#A78BFA"),
|
|
3122
|
+
preparing: chalk3.yellow,
|
|
3123
|
+
pending: chalk3.yellow,
|
|
3124
|
+
uploading: chalk3.yellow,
|
|
3125
|
+
failed: chalk3.red,
|
|
3126
|
+
cancelled: chalk3.dim,
|
|
3127
|
+
validated: chalk3.green,
|
|
3128
|
+
invalid: chalk3.red
|
|
3022
3129
|
};
|
|
3023
|
-
const colorFn = map[status2] ||
|
|
3130
|
+
const colorFn = map[status2] || chalk3.white;
|
|
3024
3131
|
return colorFn(status2);
|
|
3025
3132
|
}
|
|
3026
3133
|
function truncate(str, max) {
|
|
@@ -3031,6 +3138,400 @@ function shortId(id) {
|
|
|
3031
3138
|
return id.slice(0, 8);
|
|
3032
3139
|
}
|
|
3033
3140
|
|
|
3141
|
+
// src/spec-view.ts
|
|
3142
|
+
import chalk4 from "chalk";
|
|
3143
|
+
var accent2 = chalk4.hex("#8B5CF6");
|
|
3144
|
+
var soft = chalk4.hex("#A78BFA");
|
|
3145
|
+
var keyColor = chalk4.hex("#C4B5FD");
|
|
3146
|
+
var stringColor = chalk4.hex("#86EFAC");
|
|
3147
|
+
var numberColor = chalk4.hex("#FCD34D");
|
|
3148
|
+
var literalColor = chalk4.hex("#F9A8D4");
|
|
3149
|
+
var SPEC_SECTIONS = [
|
|
3150
|
+
"identity",
|
|
3151
|
+
"behavior",
|
|
3152
|
+
"examples",
|
|
3153
|
+
"training",
|
|
3154
|
+
"evaluation",
|
|
3155
|
+
"runtime",
|
|
3156
|
+
"pipeline"
|
|
3157
|
+
];
|
|
3158
|
+
var SECTION_ALIASES = {
|
|
3159
|
+
id: "identity",
|
|
3160
|
+
about: "identity",
|
|
3161
|
+
prompt: "behavior",
|
|
3162
|
+
system: "behavior",
|
|
3163
|
+
guidelines: "behavior",
|
|
3164
|
+
constraints: "behavior",
|
|
3165
|
+
example: "examples",
|
|
3166
|
+
data: "examples",
|
|
3167
|
+
hyperparameters: "training",
|
|
3168
|
+
hparams: "training",
|
|
3169
|
+
foundation: "training",
|
|
3170
|
+
train: "training",
|
|
3171
|
+
eval: "evaluation",
|
|
3172
|
+
gpu: "runtime"
|
|
3173
|
+
};
|
|
3174
|
+
function resolveSpecSection(name) {
|
|
3175
|
+
const normalized = name.trim().toLowerCase();
|
|
3176
|
+
if (SPEC_SECTIONS.includes(normalized)) return normalized;
|
|
3177
|
+
return SECTION_ALIASES[normalized];
|
|
3178
|
+
}
|
|
3179
|
+
var SECTION_BLURBS = {
|
|
3180
|
+
identity: "what this model is",
|
|
3181
|
+
behavior: "what the model should do",
|
|
3182
|
+
examples: "teaches by demonstration",
|
|
3183
|
+
training: "how it is trained",
|
|
3184
|
+
evaluation: "how results are scored",
|
|
3185
|
+
runtime: "where it runs",
|
|
3186
|
+
pipeline: "custom step recipe"
|
|
3187
|
+
};
|
|
3188
|
+
var SECTION_KEYS = {
|
|
3189
|
+
identity: ["name", "description", "engine", "base_model", "id", "dataset_prebuilt"],
|
|
3190
|
+
behavior: ["system_prompt", "guidelines", "constraints", "decision"],
|
|
3191
|
+
examples: ["examples"],
|
|
3192
|
+
training: ["hyperparameters", "foundation"],
|
|
3193
|
+
evaluation: ["evaluation"],
|
|
3194
|
+
runtime: ["runtime"],
|
|
3195
|
+
pipeline: ["pipeline"]
|
|
3196
|
+
};
|
|
3197
|
+
var KNOWN_KEYS = new Set(Object.values(SECTION_KEYS).flat());
|
|
3198
|
+
function isObject2(value) {
|
|
3199
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
3200
|
+
}
|
|
3201
|
+
function clean(value) {
|
|
3202
|
+
return sanitizeTerminalText(value);
|
|
3203
|
+
}
|
|
3204
|
+
function truncateText(value, width) {
|
|
3205
|
+
const flat = clean(value).replace(/\s+/g, " ").trim();
|
|
3206
|
+
if (terminalWidth(flat) <= width) return flat;
|
|
3207
|
+
let out = "";
|
|
3208
|
+
for (const char of flat) {
|
|
3209
|
+
if (terminalWidth(out + char) > width - 1) break;
|
|
3210
|
+
out += char;
|
|
3211
|
+
}
|
|
3212
|
+
return `${out}\u2026`;
|
|
3213
|
+
}
|
|
3214
|
+
function hintLines(items, width) {
|
|
3215
|
+
return joinFitting(items, width).map((line) => chalk4.dim(line));
|
|
3216
|
+
}
|
|
3217
|
+
function joinFitting(items, width) {
|
|
3218
|
+
const lines = [];
|
|
3219
|
+
let current = "";
|
|
3220
|
+
for (const item of items) {
|
|
3221
|
+
const next = current ? `${current} \xB7 ${item}` : item;
|
|
3222
|
+
if (current && terminalWidth(next) > width) {
|
|
3223
|
+
lines.push(current);
|
|
3224
|
+
current = item;
|
|
3225
|
+
} else {
|
|
3226
|
+
current = next;
|
|
3227
|
+
}
|
|
3228
|
+
}
|
|
3229
|
+
if (current) lines.push(current);
|
|
3230
|
+
return lines;
|
|
3231
|
+
}
|
|
3232
|
+
function formatScalar(value) {
|
|
3233
|
+
if (typeof value === "string") return stringColor(clean(value));
|
|
3234
|
+
if (typeof value === "number") return numberColor(String(value));
|
|
3235
|
+
if (typeof value === "boolean" || value === null) return literalColor(String(value));
|
|
3236
|
+
return highlightJson(JSON.stringify(value));
|
|
3237
|
+
}
|
|
3238
|
+
function flattenSettings(value, prefix = "") {
|
|
3239
|
+
if (!isObject2(value)) return [[prefix || "value", value]];
|
|
3240
|
+
const rows2 = [];
|
|
3241
|
+
for (const [key, child] of Object.entries(value)) {
|
|
3242
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
3243
|
+
if (isObject2(child) && Object.keys(child).length > 0) rows2.push(...flattenSettings(child, path));
|
|
3244
|
+
else rows2.push([path, child]);
|
|
3245
|
+
}
|
|
3246
|
+
return rows2;
|
|
3247
|
+
}
|
|
3248
|
+
function highlightJson(source) {
|
|
3249
|
+
const token = /("(?:[^"\\]|\\.)*")(\s*:)?|(-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)|\b(true|false|null)\b|([{}[\],])/g;
|
|
3250
|
+
return clean(source).replace(token, (match, string, colon, number2, literal, punctuation) => {
|
|
3251
|
+
if (string !== void 0) {
|
|
3252
|
+
return colon !== void 0 ? `${keyColor(string)}${chalk4.dim(colon)}` : stringColor(string);
|
|
3253
|
+
}
|
|
3254
|
+
if (number2 !== void 0) return numberColor(number2);
|
|
3255
|
+
if (literal !== void 0) return literalColor(literal);
|
|
3256
|
+
if (punctuation !== void 0) return chalk4.dim(punctuation);
|
|
3257
|
+
return match;
|
|
3258
|
+
});
|
|
3259
|
+
}
|
|
3260
|
+
function highlightSpecDiff(diff) {
|
|
3261
|
+
return diff.split("\n").map((line) => {
|
|
3262
|
+
if (line.startsWith("@@")) return accent2.bold(line);
|
|
3263
|
+
if (line.startsWith("+")) return chalk4.green(line);
|
|
3264
|
+
if (line.startsWith("-")) return chalk4.red(line);
|
|
3265
|
+
return line;
|
|
3266
|
+
}).join("\n");
|
|
3267
|
+
}
|
|
3268
|
+
var ViewBuilder = class {
|
|
3269
|
+
constructor(width) {
|
|
3270
|
+
this.width = width;
|
|
3271
|
+
}
|
|
3272
|
+
lines = [];
|
|
3273
|
+
push(line = "") {
|
|
3274
|
+
this.lines.push(line);
|
|
3275
|
+
}
|
|
3276
|
+
/** Wrap text under a fixed indent and optional gutter marker. */
|
|
3277
|
+
paragraph(text2, indent, style = (value) => value, maxLines) {
|
|
3278
|
+
const body = this.width - terminalWidth(indent);
|
|
3279
|
+
const wrapped = clean(text2).split(/\r?\n/).flatMap((line) => line.trim() ? wrapTerminalLine(line, Math.max(20, body)) : [""]);
|
|
3280
|
+
const shown = maxLines && wrapped.length > maxLines ? wrapped.slice(0, maxLines) : wrapped;
|
|
3281
|
+
for (const line of shown) this.push(`${indent}${style(line)}`);
|
|
3282
|
+
if (shown.length < wrapped.length) {
|
|
3283
|
+
this.push(`${indent}${chalk4.dim(`\u2026 ${wrapped.length - shown.length} more lines`)}`);
|
|
3284
|
+
}
|
|
3285
|
+
}
|
|
3286
|
+
heading(section, detail) {
|
|
3287
|
+
const title = section.toUpperCase();
|
|
3288
|
+
const room = this.width - title.length - 3;
|
|
3289
|
+
const extra = detail && room >= 8 ? ` ${chalk4.dim(truncateText(detail, room))}` : "";
|
|
3290
|
+
this.push("");
|
|
3291
|
+
this.push(`${accent2("\u258D")}${accent2.bold(title)}${extra}`);
|
|
3292
|
+
}
|
|
3293
|
+
settingsGrid(entries, indent = " ") {
|
|
3294
|
+
if (entries.length === 0) return;
|
|
3295
|
+
const rows2 = entries.map(([key, value]) => [clean(key), value]);
|
|
3296
|
+
const compact = rows2.every(([, value]) => value === null || ["number", "boolean"].includes(typeof value) || typeof value === "string" && value.length <= 16);
|
|
3297
|
+
if (compact && rows2.length > 4) {
|
|
3298
|
+
const keyWidth2 = Math.max(...rows2.map(([key]) => key.length));
|
|
3299
|
+
const valueWidth = Math.max(...rows2.map(([, value]) => String(value).length));
|
|
3300
|
+
const cell = keyWidth2 + 2 + valueWidth;
|
|
3301
|
+
const columns = Math.min(3, Math.floor((this.width - indent.length + 4) / (cell + 4)));
|
|
3302
|
+
if (columns >= 2) {
|
|
3303
|
+
const height = Math.ceil(rows2.length / columns);
|
|
3304
|
+
for (let row = 0; row < height; row += 1) {
|
|
3305
|
+
const cells = [];
|
|
3306
|
+
for (let column = 0; column < columns; column += 1) {
|
|
3307
|
+
const entry = rows2[column * height + row];
|
|
3308
|
+
if (!entry) continue;
|
|
3309
|
+
const [key, value] = entry;
|
|
3310
|
+
const text2 = String(value);
|
|
3311
|
+
cells.push(`${keyColor(key.padEnd(keyWidth2))} ${formatScalar(typeof value === "string" ? clean(value) : value)}${" ".repeat(valueWidth - text2.length)}`);
|
|
3312
|
+
}
|
|
3313
|
+
this.push(`${indent}${cells.join(" ").trimEnd()}`);
|
|
3314
|
+
}
|
|
3315
|
+
return;
|
|
3316
|
+
}
|
|
3317
|
+
}
|
|
3318
|
+
const keyWidth = Math.min(28, Math.max(...rows2.map(([key]) => key.length)));
|
|
3319
|
+
for (const [key, value] of rows2) {
|
|
3320
|
+
const label = key.length > keyWidth ? truncateText(key, keyWidth) : key.padEnd(keyWidth);
|
|
3321
|
+
const rendered = typeof value === "string" ? stringColor(truncateText(value, Math.max(20, this.width - keyWidth - indent.length - 2))) : formatScalar(value);
|
|
3322
|
+
this.push(`${indent}${keyColor(label)} ${rendered}`);
|
|
3323
|
+
}
|
|
3324
|
+
}
|
|
3325
|
+
};
|
|
3326
|
+
function stringList(value) {
|
|
3327
|
+
return Array.isArray(value) ? value.map((item) => typeof item === "string" ? item : JSON.stringify(item)) : [];
|
|
3328
|
+
}
|
|
3329
|
+
function exampleText(value) {
|
|
3330
|
+
if (typeof value === "string") return value;
|
|
3331
|
+
if (value === void 0) return "";
|
|
3332
|
+
return JSON.stringify(value);
|
|
3333
|
+
}
|
|
3334
|
+
function renderIdentity(view, spec, full) {
|
|
3335
|
+
if (!full) return;
|
|
3336
|
+
view.heading("identity", SECTION_BLURBS.identity);
|
|
3337
|
+
view.settingsGrid(SECTION_KEYS.identity.filter((key) => spec[key] !== void 0).map((key) => [key, spec[key]]));
|
|
3338
|
+
if (spec.engine === void 0) view.push(` ${chalk4.dim("engine defaults to adapter (LoRA on base_model)")}`);
|
|
3339
|
+
}
|
|
3340
|
+
function renderBehavior(view, spec, full) {
|
|
3341
|
+
const guidelines = stringList(spec.guidelines);
|
|
3342
|
+
const constraints = stringList(spec.constraints);
|
|
3343
|
+
view.heading("behavior", SECTION_BLURBS.behavior);
|
|
3344
|
+
view.push(` ${chalk4.bold("System prompt")}`);
|
|
3345
|
+
if (typeof spec.system_prompt === "string" && spec.system_prompt.trim()) {
|
|
3346
|
+
view.paragraph(spec.system_prompt, ` ${soft("\u2502")} `, chalk4.italic, full ? void 0 : 6);
|
|
3347
|
+
} else {
|
|
3348
|
+
view.push(` ${soft("\u2502")} ${chalk4.dim("(none)")}`);
|
|
3349
|
+
}
|
|
3350
|
+
const renderList = (label, items, mark) => {
|
|
3351
|
+
view.push(` ${chalk4.bold(label)} ${chalk4.dim(String(items.length))}`);
|
|
3352
|
+
if (items.length === 0) {
|
|
3353
|
+
view.push(` ${chalk4.dim("(none)")}`);
|
|
3354
|
+
return;
|
|
3355
|
+
}
|
|
3356
|
+
const limit = full ? items.length : 5;
|
|
3357
|
+
for (const item of items.slice(0, limit)) {
|
|
3358
|
+
const before = view.lines.length;
|
|
3359
|
+
view.paragraph(item, " ", void 0, full ? void 0 : 2);
|
|
3360
|
+
view.lines[before] = ` ${mark} ${view.lines[before].slice(6)}`;
|
|
3361
|
+
}
|
|
3362
|
+
if (items.length > limit) view.push(` ${chalk4.dim(`\u2026 ${items.length - limit} more \xB7 /spec behavior`)}`);
|
|
3363
|
+
};
|
|
3364
|
+
if (isObject2(spec.decision)) {
|
|
3365
|
+
view.push(` ${chalk4.bold("Decision question")}`);
|
|
3366
|
+
view.settingsGrid(flattenSettings(spec.decision));
|
|
3367
|
+
}
|
|
3368
|
+
renderList("Guidelines", guidelines, chalk4.green("\u2713"));
|
|
3369
|
+
renderList("Constraints", constraints, chalk4.red("\u2717"));
|
|
3370
|
+
}
|
|
3371
|
+
function renderExamples(view, spec, full) {
|
|
3372
|
+
const examples = Array.isArray(spec.examples) ? spec.examples : [];
|
|
3373
|
+
const count = examples.length;
|
|
3374
|
+
const size = count === 0 ? "none yet" : `${count} example${count === 1 ? "" : "s"}`;
|
|
3375
|
+
view.heading("examples", `${size} \xB7 ${SECTION_BLURBS.examples}`);
|
|
3376
|
+
if (count === 0) {
|
|
3377
|
+
view.push(` ${chalk4.dim("Ask TT to draft examples, or add input/output pairs to tunedtensor.json.")}`);
|
|
3378
|
+
return;
|
|
3379
|
+
}
|
|
3380
|
+
const limit = full ? count : 3;
|
|
3381
|
+
const number2 = String(Math.min(count, limit)).length;
|
|
3382
|
+
const textWidth = Math.max(20, view.width - number2 - 10);
|
|
3383
|
+
examples.slice(0, limit).forEach((example, index) => {
|
|
3384
|
+
const record4 = isObject2(example) ? example : { input: example };
|
|
3385
|
+
const label = chalk4.dim(String(index + 1).padStart(number2));
|
|
3386
|
+
const pad = " ".repeat(number2);
|
|
3387
|
+
if (full) {
|
|
3388
|
+
const before = view.lines.length;
|
|
3389
|
+
view.paragraph(exampleText(record4.input), ` ${pad} ${chalk4.cyan("in ")} `);
|
|
3390
|
+
view.lines[before] = view.lines[before].replace(` ${pad}`, ` ${label}`);
|
|
3391
|
+
view.paragraph(exampleText(record4.output), ` ${pad} ${chalk4.green("out")} `, stringColor);
|
|
3392
|
+
view.push("");
|
|
3393
|
+
} else {
|
|
3394
|
+
view.push(` ${label} ${chalk4.cyan("in ")} ${truncateText(exampleText(record4.input), textWidth)}`);
|
|
3395
|
+
view.push(` ${pad} ${chalk4.green("out")} ${stringColor(truncateText(exampleText(record4.output), textWidth))}`);
|
|
3396
|
+
}
|
|
3397
|
+
});
|
|
3398
|
+
if (full && view.lines.at(-1) === "") view.lines.pop();
|
|
3399
|
+
if (count > limit) view.push(` ${chalk4.dim(`\u2026 ${count - limit} more \xB7 /spec examples shows all`)}`);
|
|
3400
|
+
}
|
|
3401
|
+
function renderTraining(view, spec) {
|
|
3402
|
+
const foundation = spec.engine === "foundation";
|
|
3403
|
+
const settings = foundation ? spec.foundation : spec.hyperparameters;
|
|
3404
|
+
view.heading("training", foundation ? "foundation \xB7 tokenizer, pretrain, SFT" : spec.engine === "decision" ? "decision \xB7 typed classification" : `adapter \xB7 LoRA on ${typeof spec.base_model === "string" ? clean(spec.base_model) : "base_model"}`);
|
|
3405
|
+
const rows2 = isObject2(settings) ? flattenSettings(settings) : [];
|
|
3406
|
+
if (rows2.length === 0) {
|
|
3407
|
+
view.push(` ${chalk4.dim("Defaults \u2014 no overrides set.")}`);
|
|
3408
|
+
return;
|
|
3409
|
+
}
|
|
3410
|
+
view.settingsGrid(rows2);
|
|
3411
|
+
const other = foundation ? spec.hyperparameters : spec.foundation;
|
|
3412
|
+
if (isObject2(other)) view.push(` ${chalk4.yellow("!")} ${chalk4.dim(`${foundation ? "hyperparameters" : "foundation"} is set but unused by this engine`)}`);
|
|
3413
|
+
}
|
|
3414
|
+
function renderSettingsSection(view, spec, section, full) {
|
|
3415
|
+
const value = spec[section];
|
|
3416
|
+
if (value === void 0) {
|
|
3417
|
+
if (full) {
|
|
3418
|
+
view.heading(section, SECTION_BLURBS[section]);
|
|
3419
|
+
view.push(` ${chalk4.dim(section === "runtime" ? "Not set \u2014 runs on this machine with default paths." : section === "pipeline" ? "Not set \u2014 TT derives the default pipeline from this spec." : "Not set \u2014 default scoring.")}`);
|
|
3420
|
+
}
|
|
3421
|
+
return;
|
|
3422
|
+
}
|
|
3423
|
+
if (section === "pipeline" && !full) {
|
|
3424
|
+
const steps = isObject2(value) && Array.isArray(value.steps) ? value.steps.length : void 0;
|
|
3425
|
+
view.heading(section, SECTION_BLURBS[section]);
|
|
3426
|
+
view.push(` ${steps === void 0 ? "custom recipe" : `${steps} custom step${steps === 1 ? "" : "s"}`} ${chalk4.dim("\xB7 /spec pipeline")}`);
|
|
3427
|
+
return;
|
|
3428
|
+
}
|
|
3429
|
+
view.heading(section, SECTION_BLURBS[section]);
|
|
3430
|
+
if (section === "pipeline") {
|
|
3431
|
+
for (const line of JSON.stringify(value, null, 2).split("\n")) view.push(` ${highlightJson(line)}`);
|
|
3432
|
+
return;
|
|
3433
|
+
}
|
|
3434
|
+
view.settingsGrid(flattenSettings(value));
|
|
3435
|
+
}
|
|
3436
|
+
function renderOther(view, spec) {
|
|
3437
|
+
const rows2 = Object.entries(spec).filter(([key]) => !KNOWN_KEYS.has(key));
|
|
3438
|
+
if (rows2.length === 0) return;
|
|
3439
|
+
view.push("");
|
|
3440
|
+
view.push(`${accent2("\u258D")}${accent2.bold("OTHER")}`);
|
|
3441
|
+
view.settingsGrid(rows2);
|
|
3442
|
+
}
|
|
3443
|
+
function renderHeader(view, input, spec) {
|
|
3444
|
+
const name = typeof spec.name === "string" && spec.name.trim() ? clean(spec.name) : "Unnamed spec";
|
|
3445
|
+
const status2 = input.validation.valid ? chalk4.green("\u2713 valid") : chalk4.red(`\u2717 ${input.validation.errors.length} issue${input.validation.errors.length === 1 ? "" : "s"}`);
|
|
3446
|
+
const gap = Math.max(2, view.width - terminalWidth(name) - terminalWidth(status2));
|
|
3447
|
+
view.push(`${chalk4.bold(name)}${" ".repeat(gap)}${status2}`);
|
|
3448
|
+
if (typeof spec.description === "string" && spec.description.trim()) {
|
|
3449
|
+
view.paragraph(spec.description, "", chalk4.dim, 3);
|
|
3450
|
+
}
|
|
3451
|
+
const examples = Array.isArray(spec.examples) ? spec.examples.length : 0;
|
|
3452
|
+
const facts = [
|
|
3453
|
+
spec.engine === "foundation" ? "foundation" : spec.engine === "decision" ? "decision" : "adapter",
|
|
3454
|
+
typeof spec.base_model === "string" ? truncateText(spec.base_model, view.width) : void 0,
|
|
3455
|
+
`${examples} example${examples === 1 ? "" : "s"}`,
|
|
3456
|
+
isObject2(spec.runtime) && isObject2(spec.runtime.gpu) ? "remote GPU" : void 0
|
|
3457
|
+
].filter((fact) => Boolean(fact));
|
|
3458
|
+
view.lines.push(...joinFitting(facts, view.width));
|
|
3459
|
+
view.lines.push(...hintLines([truncateText(input.displayPath, view.width), `sha256 ${input.sha256.slice(0, 12)}`], view.width));
|
|
3460
|
+
}
|
|
3461
|
+
function renderDiagnostics(view, input) {
|
|
3462
|
+
const { errors, warnings, valid } = input.validation;
|
|
3463
|
+
if (valid && warnings.length === 0) return;
|
|
3464
|
+
view.push("");
|
|
3465
|
+
const marked = (text2, mark, style) => {
|
|
3466
|
+
const before = view.lines.length;
|
|
3467
|
+
view.paragraph(text2, " ", style);
|
|
3468
|
+
view.lines[before] = `${mark} ${view.lines[before].slice(2)}`;
|
|
3469
|
+
};
|
|
3470
|
+
for (const error of errors) marked(error, chalk4.red("\u2717"), chalk4.red);
|
|
3471
|
+
for (const warning of warnings) marked(warning, chalk4.yellow("!"), chalk4.yellow);
|
|
3472
|
+
}
|
|
3473
|
+
function renderInvalid(view, input) {
|
|
3474
|
+
view.push(`${chalk4.red("\u2717")} ${chalk4.bold(clean(input.displayPath))} ${chalk4.dim("could not be parsed")}`);
|
|
3475
|
+
for (const error of input.validation.errors) view.paragraph(error, " ", chalk4.red);
|
|
3476
|
+
view.push("");
|
|
3477
|
+
for (const line of clean(input.source).split("\n")) view.push(` ${chalk4.dim(line)}`);
|
|
3478
|
+
return `${view.lines.join("\n")}
|
|
3479
|
+
`;
|
|
3480
|
+
}
|
|
3481
|
+
function renderSpecView(input, options = {}) {
|
|
3482
|
+
const width = Math.max(40, Math.min(options.columns ?? 88, 88));
|
|
3483
|
+
const view = new ViewBuilder(width);
|
|
3484
|
+
if (!isObject2(input.document)) return renderInvalid(view, input);
|
|
3485
|
+
const spec = input.document;
|
|
3486
|
+
const section = options.section;
|
|
3487
|
+
if (!section) {
|
|
3488
|
+
renderHeader(view, input, spec);
|
|
3489
|
+
renderBehavior(view, spec, false);
|
|
3490
|
+
renderExamples(view, spec, false);
|
|
3491
|
+
renderTraining(view, spec);
|
|
3492
|
+
renderSettingsSection(view, spec, "evaluation", false);
|
|
3493
|
+
renderSettingsSection(view, spec, "runtime", false);
|
|
3494
|
+
renderSettingsSection(view, spec, "pipeline", false);
|
|
3495
|
+
renderOther(view, spec);
|
|
3496
|
+
renderDiagnostics(view, input);
|
|
3497
|
+
view.push("");
|
|
3498
|
+
view.lines.push(...hintLines(["/spec <section> for detail", "/spec show for JSON", "/spec diff", "/spec history"], width));
|
|
3499
|
+
view.lines.push(...wrapTerminalLine(`sections: ${SPEC_SECTIONS.join(", ")}`, width).map((line) => chalk4.dim(line)));
|
|
3500
|
+
return `${view.lines.join("\n")}
|
|
3501
|
+
`;
|
|
3502
|
+
}
|
|
3503
|
+
switch (section) {
|
|
3504
|
+
case "identity":
|
|
3505
|
+
renderIdentity(view, spec, true);
|
|
3506
|
+
break;
|
|
3507
|
+
case "behavior":
|
|
3508
|
+
renderBehavior(view, spec, true);
|
|
3509
|
+
break;
|
|
3510
|
+
case "examples":
|
|
3511
|
+
renderExamples(view, spec, true);
|
|
3512
|
+
break;
|
|
3513
|
+
case "training":
|
|
3514
|
+
renderTraining(view, spec);
|
|
3515
|
+
break;
|
|
3516
|
+
default:
|
|
3517
|
+
renderSettingsSection(view, spec, section, true);
|
|
3518
|
+
}
|
|
3519
|
+
view.lines.shift();
|
|
3520
|
+
return `${view.lines.join("\n")}
|
|
3521
|
+
`;
|
|
3522
|
+
}
|
|
3523
|
+
function renderSpecSource(input) {
|
|
3524
|
+
const status2 = input.validation.valid ? chalk4.green("\u2713 valid") : chalk4.red("\u2717 invalid");
|
|
3525
|
+
const lines = [
|
|
3526
|
+
`${chalk4.bold(clean(input.displayPath))} ${chalk4.dim(`\xB7 sha256 ${input.sha256.slice(0, 12)}`)} ${status2}`,
|
|
3527
|
+
highlightJson(input.source.replace(/\s+$/, "")),
|
|
3528
|
+
...input.validation.errors.map((error) => chalk4.red(`\u2717 ${clean(error)}`)),
|
|
3529
|
+
...input.validation.warnings.map((warning) => chalk4.yellow(`! ${clean(warning)}`))
|
|
3530
|
+
];
|
|
3531
|
+
return `${lines.join("\n")}
|
|
3532
|
+
`;
|
|
3533
|
+
}
|
|
3534
|
+
|
|
3034
3535
|
// src/commands/spec.ts
|
|
3035
3536
|
async function reviewSpec(workspaceRoot, operation = "show", specPath = "tunedtensor.json") {
|
|
3036
3537
|
if (!["show", "validate", "diff", "history"].includes(operation)) {
|
|
@@ -3083,8 +3584,14 @@ ${label}
|
|
|
3083
3584
|
${diff}` };
|
|
3084
3585
|
}
|
|
3085
3586
|
function registerSpecCommands(program, cwd) {
|
|
3086
|
-
program.command("spec").description("Review the local behavior spec, validation, changes and history").argument("[operation]",
|
|
3087
|
-
const
|
|
3587
|
+
program.command("spec").description("Review the local behavior spec, validation, changes and history").argument("[operation]", `show, view, validate, diff, history, or a section (${SPEC_SECTIONS.join(", ")})`, "show").argument("[path]", "Workspace-relative tunedtensor.json", "tunedtensor.json").action(async (operation, path) => {
|
|
3588
|
+
const section = resolveSpecSection(operation);
|
|
3589
|
+
if ((section || operation === "view") && !isJsonMode()) {
|
|
3590
|
+
const spec = await inspectLocalSpec(cwd, path);
|
|
3591
|
+
process.stdout.write(renderSpecView(spec, { section, columns: process.stdout.columns }));
|
|
3592
|
+
return;
|
|
3593
|
+
}
|
|
3594
|
+
const result2 = await reviewSpec(cwd, section || operation === "view" ? "show" : operation, path);
|
|
3088
3595
|
if (isJsonMode()) {
|
|
3089
3596
|
const { text: _text, ...data3 } = result2;
|
|
3090
3597
|
printJson(data3);
|
|
@@ -3097,10 +3604,440 @@ function registerSpecCommands(program, cwd) {
|
|
|
3097
3604
|
});
|
|
3098
3605
|
}
|
|
3099
3606
|
|
|
3607
|
+
// src/shell-view.ts
|
|
3608
|
+
import chalk5 from "chalk";
|
|
3609
|
+
var accent3 = chalk5.hex("#8B5CF6");
|
|
3610
|
+
var GIB = 1024 ** 3;
|
|
3611
|
+
var BAR_WIDTH = 10;
|
|
3612
|
+
var MAX_PANEL_WIDTH = 88;
|
|
3613
|
+
function panelWidth(columns) {
|
|
3614
|
+
return Math.max(40, Math.min(columns ?? 80, MAX_PANEL_WIDTH));
|
|
3615
|
+
}
|
|
3616
|
+
function formatGiB(bytes) {
|
|
3617
|
+
const amount = bytes / GIB;
|
|
3618
|
+
return amount >= 100 ? amount.toFixed(0) : amount.toFixed(1);
|
|
3619
|
+
}
|
|
3620
|
+
function loadColor(percent2) {
|
|
3621
|
+
if (percent2 >= 85) return chalk5.red;
|
|
3622
|
+
if (percent2 >= 60) return chalk5.yellow;
|
|
3623
|
+
return chalk5.green;
|
|
3624
|
+
}
|
|
3625
|
+
function usageBar(percent2, width = BAR_WIDTH) {
|
|
3626
|
+
if (percent2 === void 0) return chalk5.dim("\xB7".repeat(width));
|
|
3627
|
+
const clamped = Math.max(0, Math.min(100, percent2));
|
|
3628
|
+
const filled = Math.round(clamped / 100 * width);
|
|
3629
|
+
return `${loadColor(clamped)("\u2588".repeat(filled))}${chalk5.dim("\u2591".repeat(width - filled))}`;
|
|
3630
|
+
}
|
|
3631
|
+
function percentLabel(percent2) {
|
|
3632
|
+
if (percent2 === void 0) return chalk5.dim(" n/a");
|
|
3633
|
+
return loadColor(percent2)(`${String(Math.round(percent2)).padStart(3)}%`).padStart(4);
|
|
3634
|
+
}
|
|
3635
|
+
function sectionRule(title, width, detail) {
|
|
3636
|
+
const label = `${accent3.bold(title)}${detail ? ` ${chalk5.dim(detail)}` : ""}`;
|
|
3637
|
+
const rule = Math.max(3, width - terminalWidth(label) - 1);
|
|
3638
|
+
return `${label} ${chalk5.dim("\u2500".repeat(rule))}`;
|
|
3639
|
+
}
|
|
3640
|
+
function inventoryFromUsage(usage) {
|
|
3641
|
+
const nvidia = usage.gpus.filter((gpu) => gpu.vendor === "nvidia");
|
|
3642
|
+
return {
|
|
3643
|
+
collected_at: usage.sampled_at,
|
|
3644
|
+
quick: true,
|
|
3645
|
+
node: { version: process.versions.node, major: Number(process.versions.node.split(".")[0]), ok: true },
|
|
3646
|
+
os: {
|
|
3647
|
+
platform: process.platform,
|
|
3648
|
+
arch: process.arch,
|
|
3649
|
+
type: process.platform,
|
|
3650
|
+
cpu_count: usage.cpu.cores,
|
|
3651
|
+
total_memory_bytes: usage.memory.total_bytes,
|
|
3652
|
+
free_memory_bytes: usage.memory.total_bytes - usage.memory.used_bytes
|
|
3653
|
+
},
|
|
3654
|
+
gpus: nvidia.map((gpu) => ({
|
|
3655
|
+
index: gpu.index,
|
|
3656
|
+
name: gpu.name,
|
|
3657
|
+
memory_total_bytes: gpu.memory_total_bytes,
|
|
3658
|
+
memory_free_bytes: gpu.memory_total_bytes !== void 0 && gpu.memory_used_bytes !== void 0 ? gpu.memory_total_bytes - gpu.memory_used_bytes : void 0,
|
|
3659
|
+
unified_memory: gpu.unified_memory
|
|
3660
|
+
})),
|
|
3661
|
+
nvidia_smi: { ok: nvidia.length > 0, message: usage.gpu_note ?? "" },
|
|
3662
|
+
disks: []
|
|
3663
|
+
};
|
|
3664
|
+
}
|
|
3665
|
+
function shortModel(id, width = 24) {
|
|
3666
|
+
return truncateText(id.split("/").at(-1) ?? id, width);
|
|
3667
|
+
}
|
|
3668
|
+
var STATUS_WORD = {
|
|
3669
|
+
ready: "ready",
|
|
3670
|
+
tight: "tight",
|
|
3671
|
+
not_possible: "not possible"
|
|
3672
|
+
};
|
|
3673
|
+
function describeFineTuneFit(usage, context) {
|
|
3674
|
+
const spec = context.spec;
|
|
3675
|
+
if (spec?.remoteGpu) {
|
|
3676
|
+
return {
|
|
3677
|
+
status: "remote",
|
|
3678
|
+
text: `training runs on AWS ${spec.remoteGpu} (runtime.gpu)`,
|
|
3679
|
+
source: "tt doctor checks it"
|
|
3680
|
+
};
|
|
3681
|
+
}
|
|
3682
|
+
const snapshot = context.host && !context.host.stale ? context.host.capabilities : void 0;
|
|
3683
|
+
let report = snapshot;
|
|
3684
|
+
const source = snapshot ? "from tt hardware" : "quick check \xB7 tt hardware for a full probe";
|
|
3685
|
+
if (!report && usage) report = evaluateCapabilities(inventoryFromUsage(usage));
|
|
3686
|
+
if (!report) return { status: "unknown", text: "not checked yet", source: "run tt hardware" };
|
|
3687
|
+
if (spec?.engine === "decision") {
|
|
3688
|
+
return {
|
|
3689
|
+
status: "unknown",
|
|
3690
|
+
text: "decision training runs locally on CPU, MPS or CUDA",
|
|
3691
|
+
source: "device selected by hyperparameters.device"
|
|
3692
|
+
};
|
|
3693
|
+
}
|
|
3694
|
+
const noCuda = !report.cuda_available;
|
|
3695
|
+
if (spec?.engine === "foundation") {
|
|
3696
|
+
const verdict = report.foundation.train;
|
|
3697
|
+
return {
|
|
3698
|
+
status: verdict.status,
|
|
3699
|
+
text: verdict.status === "not_possible" ? `foundation training ${STATUS_WORD[verdict.status]} \u2014 ${verdict.reason}` : `foundation training ${STATUS_WORD[verdict.status]} \xB7 suggested max depth ${report.foundation.suggested_max_depth}`,
|
|
3700
|
+
source: noCuda ? "set runtime.gpu to train on your AWS GPU" : source
|
|
3701
|
+
};
|
|
3702
|
+
}
|
|
3703
|
+
if (spec?.baseModel) {
|
|
3704
|
+
const adapter = report.adapters.find((item) => item.id.toLowerCase() === spec.baseModel.toLowerCase());
|
|
3705
|
+
if (!adapter) {
|
|
3706
|
+
return {
|
|
3707
|
+
status: "unknown",
|
|
3708
|
+
text: `no certified memory profile for ${shortModel(spec.baseModel, 32)}`,
|
|
3709
|
+
source: "tt hardware lists supported base models"
|
|
3710
|
+
};
|
|
3711
|
+
}
|
|
3712
|
+
const verdict = adapter.finetune;
|
|
3713
|
+
return {
|
|
3714
|
+
status: verdict.status,
|
|
3715
|
+
text: `LoRA ${shortModel(adapter.id)} ${STATUS_WORD[verdict.status]} \u2014 ${verdict.reason}`,
|
|
3716
|
+
source: noCuda ? "set runtime.gpu to train on your AWS GPU" : source
|
|
3717
|
+
};
|
|
3718
|
+
}
|
|
3719
|
+
const ready = report.adapters.filter((item) => item.finetune.status !== "not_possible");
|
|
3720
|
+
if (ready.length === 0) {
|
|
3721
|
+
return {
|
|
3722
|
+
status: "not_possible",
|
|
3723
|
+
text: noCuda ? "no local CUDA GPU for LoRA training" : "no certified base model fits this GPU",
|
|
3724
|
+
source: noCuda ? "runtime.gpu can use your AWS GPU" : source
|
|
3725
|
+
};
|
|
3726
|
+
}
|
|
3727
|
+
const best = ready.some((item) => item.finetune.status === "ready") ? "ready" : "tight";
|
|
3728
|
+
return {
|
|
3729
|
+
status: best,
|
|
3730
|
+
text: `LoRA ${best} for ${ready.map((item) => shortModel(item.id, 20)).join(", ")}`,
|
|
3731
|
+
source
|
|
3732
|
+
};
|
|
3733
|
+
}
|
|
3734
|
+
function fitMark(status2) {
|
|
3735
|
+
switch (status2) {
|
|
3736
|
+
case "ready":
|
|
3737
|
+
return chalk5.green("\u2713");
|
|
3738
|
+
case "tight":
|
|
3739
|
+
return chalk5.yellow("!");
|
|
3740
|
+
case "not_possible":
|
|
3741
|
+
return chalk5.red("\u2717");
|
|
3742
|
+
case "remote":
|
|
3743
|
+
return accent3("\u2197");
|
|
3744
|
+
default:
|
|
3745
|
+
return chalk5.dim("?");
|
|
3746
|
+
}
|
|
3747
|
+
}
|
|
3748
|
+
function gpuDetail(gpu) {
|
|
3749
|
+
const parts = [];
|
|
3750
|
+
if (gpu.vendor === "apple") {
|
|
3751
|
+
parts.push(`${formatGiB(gpu.memory_total_bytes ?? 0)} GiB unified \xB7 Metal`);
|
|
3752
|
+
} else if (gpu.memory_total_bytes !== void 0) {
|
|
3753
|
+
const used = gpu.memory_used_bytes;
|
|
3754
|
+
parts.push(used === void 0 ? `${formatGiB(gpu.memory_total_bytes)} GiB VRAM` : `VRAM ${formatGiB(used)}/${formatGiB(gpu.memory_total_bytes)} GiB`);
|
|
3755
|
+
}
|
|
3756
|
+
if (gpu.temperature_c !== void 0) parts.push(`${Math.round(gpu.temperature_c)}\xB0C`);
|
|
3757
|
+
return parts.join(" ");
|
|
3758
|
+
}
|
|
3759
|
+
function renderMeterRows(rows2, width) {
|
|
3760
|
+
const detailWidth = Math.max(0, ...rows2.map((row) => terminalWidth(row.detail)));
|
|
3761
|
+
const fixed = 6 + 1 + 1 + BAR_WIDTH + 1 + 4 + 2;
|
|
3762
|
+
const nameWidth = Math.max(6, Math.min(28, width - fixed - detailWidth));
|
|
3763
|
+
const detailRoom = Math.max(8, width - fixed - nameWidth);
|
|
3764
|
+
return rows2.map((row) => {
|
|
3765
|
+
const nameCell = truncateText(row.name, nameWidth).padEnd(nameWidth);
|
|
3766
|
+
return `${chalk5.bold(row.label.padEnd(6))} ${nameCell} ${usageBar(row.percent)} ${percentLabel(row.percent)} ${chalk5.dim(truncateText(row.detail, detailRoom))}`;
|
|
3767
|
+
});
|
|
3768
|
+
}
|
|
3769
|
+
function renderMachinePanel(usage, context, options = {}) {
|
|
3770
|
+
const width = panelWidth(options.columns);
|
|
3771
|
+
const lines = [sectionRule("MACHINE", width)];
|
|
3772
|
+
if (usage.gpus.length === 0) {
|
|
3773
|
+
const reason = usage.gpu_note ? ` (${usage.gpu_note})` : "";
|
|
3774
|
+
lines.push(`${chalk5.bold("GPU".padEnd(6))} ${chalk5.dim(truncateText(`no NVIDIA GPU detected${reason}`, width - 7))}`);
|
|
3775
|
+
}
|
|
3776
|
+
const meters = usage.gpus.map((gpu) => ({
|
|
3777
|
+
label: usage.gpus.length > 1 ? `GPU ${gpu.index}` : "GPU",
|
|
3778
|
+
// "NVIDIA GeForce RTX 4090" reads better as "GeForce RTX 4090" in a narrow column.
|
|
3779
|
+
name: sanitizeTerminalText(gpu.name).replace(/^NVIDIA\s+(?=\S)/, ""),
|
|
3780
|
+
percent: gpu.utilization_percent,
|
|
3781
|
+
detail: gpuDetail(gpu)
|
|
3782
|
+
}));
|
|
3783
|
+
meters.push({
|
|
3784
|
+
label: "CPU",
|
|
3785
|
+
name: `${usage.cpu.cores}\xD7 ${sanitizeTerminalText(usage.cpu.model)}`,
|
|
3786
|
+
percent: usage.cpu.utilization_percent,
|
|
3787
|
+
detail: `load ${usage.cpu.load_average[0].toFixed(2)}`
|
|
3788
|
+
});
|
|
3789
|
+
meters.push({
|
|
3790
|
+
label: "RAM",
|
|
3791
|
+
name: "system memory",
|
|
3792
|
+
percent: usage.memory.total_bytes > 0 ? usage.memory.used_bytes / usage.memory.total_bytes * 100 : void 0,
|
|
3793
|
+
detail: `${formatGiB(usage.memory.used_bytes)}/${formatGiB(usage.memory.total_bytes)} GiB`
|
|
3794
|
+
});
|
|
3795
|
+
if (usage.disk && usage.disk.total_bytes > 0) {
|
|
3796
|
+
meters.push({
|
|
3797
|
+
label: "Disk",
|
|
3798
|
+
name: "model cache",
|
|
3799
|
+
percent: (usage.disk.total_bytes - usage.disk.free_bytes) / usage.disk.total_bytes * 100,
|
|
3800
|
+
detail: `${formatGiB(usage.disk.free_bytes)} GiB free`
|
|
3801
|
+
});
|
|
3802
|
+
}
|
|
3803
|
+
lines.push(...renderMeterRows(meters, width));
|
|
3804
|
+
const fit = describeFineTuneFit(usage, context);
|
|
3805
|
+
lines.push(`${chalk5.bold("Tune".padEnd(6))} ${fitMark(fit.status)} ${truncateText(fit.text, width - 9)}`);
|
|
3806
|
+
if (fit.source) lines.push(`${" ".repeat(9)}${chalk5.dim(truncateText(fit.source, width - 9))}`);
|
|
3807
|
+
if (options.detailed) {
|
|
3808
|
+
const report = context.host && !context.host.stale ? context.host.capabilities : evaluateCapabilities(inventoryFromUsage(usage));
|
|
3809
|
+
if (report) {
|
|
3810
|
+
lines.push("", sectionRule("CERTIFIED BASE MODELS", width, "LoRA fine-tune"));
|
|
3811
|
+
const idWidth = Math.min(44, Math.floor(width * 0.55));
|
|
3812
|
+
const reasonWidth = Math.max(10, width - idWidth - 3);
|
|
3813
|
+
const row = (status2, label, reason) => `${fitMark(status2)} ${truncateText(label, idWidth).padEnd(idWidth)} ${chalk5.dim(truncateText(reason, reasonWidth))}`;
|
|
3814
|
+
for (const adapter of report.adapters) {
|
|
3815
|
+
lines.push(row(adapter.finetune.status, adapter.id, adapter.finetune.reason));
|
|
3816
|
+
}
|
|
3817
|
+
const foundation = report.foundation.train;
|
|
3818
|
+
lines.push(row(
|
|
3819
|
+
foundation.status,
|
|
3820
|
+
"foundation engine (from scratch)",
|
|
3821
|
+
foundation.status === "not_possible" ? foundation.reason : `max depth ${report.foundation.suggested_max_depth}`
|
|
3822
|
+
));
|
|
3823
|
+
}
|
|
3824
|
+
lines.push("");
|
|
3825
|
+
const note = context.host ? `Full probe ${context.host.stale ? "is stale" : "cached"} from ${context.host.collectedAt.slice(0, 16).replace("T", " ")} \xB7 tt hardware refreshes it` : "No full probe yet \xB7 tt hardware checks CUDA, torch and disk space";
|
|
3826
|
+
lines.push(...wrapTerminalLine(note, width).map((line) => chalk5.dim(line)));
|
|
3827
|
+
}
|
|
3828
|
+
return lines;
|
|
3829
|
+
}
|
|
3830
|
+
function agentLabel(context) {
|
|
3831
|
+
const agent = context.agent;
|
|
3832
|
+
if (!agent?.provider || !agent.model) return void 0;
|
|
3833
|
+
return `${agent.provider}/${agent.model}`;
|
|
3834
|
+
}
|
|
3835
|
+
function bannerTip(input) {
|
|
3836
|
+
const { context, usage } = input;
|
|
3837
|
+
const agent = agentLabel(context);
|
|
3838
|
+
if (!agent) {
|
|
3839
|
+
return "Use /login tunedtensor for managed inference, or /model for your own provider. Workflow commands work now.";
|
|
3840
|
+
}
|
|
3841
|
+
if (!context.spec) {
|
|
3842
|
+
return "No tunedtensor.json here yet. Describe the model you want and I'll draft a spec, or run init.";
|
|
3843
|
+
}
|
|
3844
|
+
if (context.spec.parseError) {
|
|
3845
|
+
return "tunedtensor.json doesn't parse. /spec show points at the problem.";
|
|
3846
|
+
}
|
|
3847
|
+
const busy = usage?.gpus.find((gpu) => (gpu.utilization_percent ?? 0) >= 85);
|
|
3848
|
+
if (busy) {
|
|
3849
|
+
return `${sanitizeTerminalText(busy.name)} is ${busy.utilization_percent}% busy; training now may be slow.`;
|
|
3850
|
+
}
|
|
3851
|
+
const examples = context.spec.exampleCount ?? 0;
|
|
3852
|
+
if (examples < 20) {
|
|
3853
|
+
return `${examples} example${examples === 1 ? "" : "s"} is a small set. Ask me to draft more, or /spec examples to review them.`;
|
|
3854
|
+
}
|
|
3855
|
+
return "Ask TT anything. Known commands run directly.";
|
|
3856
|
+
}
|
|
3857
|
+
function specLine(context) {
|
|
3858
|
+
const spec = context.spec;
|
|
3859
|
+
if (!spec) return `${chalk5.dim("spec")} ${chalk5.yellow("none here")} ${chalk5.dim("\xB7 init creates one")}`;
|
|
3860
|
+
if (spec.parseError) return `${chalk5.dim("spec")} ${chalk5.red("tunedtensor.json is invalid JSON")}`;
|
|
3861
|
+
const name = sanitizeTerminalText(spec.name ?? "unnamed");
|
|
3862
|
+
const facts = [
|
|
3863
|
+
spec.engine === "foundation" ? "foundation" : spec.baseModel ? sanitizeTerminalText(spec.baseModel) : void 0,
|
|
3864
|
+
spec.exampleCount === void 0 ? void 0 : `${spec.exampleCount} examples`
|
|
3865
|
+
].filter(Boolean).join(" \xB7 ");
|
|
3866
|
+
return `${chalk5.dim("spec")} ${chalk5.bold(name)}${facts ? chalk5.dim(` \xB7 ${facts}`) : ""}`;
|
|
3867
|
+
}
|
|
3868
|
+
function renderBanner(input) {
|
|
3869
|
+
const width = panelWidth(input.columns);
|
|
3870
|
+
const { context } = input;
|
|
3871
|
+
const tip = bannerTip(input);
|
|
3872
|
+
const heading = [
|
|
3873
|
+
`${accent3.bold("tt")}${input.version ? ` ${chalk5.dim(`v${input.version}`)}` : ""} ${chalk5.dim("Tuned Tensor")}`,
|
|
3874
|
+
specLine(context),
|
|
3875
|
+
chalk5.dim(`agent ${agentLabel(context) ?? "not configured"} \xB7 workflow model ${input.activeModel}`)
|
|
3876
|
+
];
|
|
3877
|
+
const mascot = renderMascot();
|
|
3878
|
+
const textWidth = width - MASCOT_WIDTH - 1;
|
|
3879
|
+
const offset = mascot.length - heading.length;
|
|
3880
|
+
const lines = mascot.map((row, index) => {
|
|
3881
|
+
const text2 = heading[index - offset] ?? "";
|
|
3882
|
+
return `${row} ${terminalWidth(text2) > textWidth ? truncateText(text2, textWidth) : text2}`;
|
|
3883
|
+
});
|
|
3884
|
+
if (input.usage) {
|
|
3885
|
+
lines.push("", ...renderMachinePanel(input.usage, context, { columns: input.columns }));
|
|
3886
|
+
}
|
|
3887
|
+
const prefix = mascotSays("");
|
|
3888
|
+
const hang = terminalWidth(prefix);
|
|
3889
|
+
const wrapped = wrapTerminalLine(tip, Math.max(20, width - hang));
|
|
3890
|
+
lines.push("", `${prefix}${wrapped[0]}`, ...wrapped.slice(1).map((line) => `${" ".repeat(hang)}${line}`));
|
|
3891
|
+
lines.push(...hintLines([
|
|
3892
|
+
"/spec review",
|
|
3893
|
+
"/system machine",
|
|
3894
|
+
"/help commands",
|
|
3895
|
+
"tab complete",
|
|
3896
|
+
"ctrl+c stop/clear",
|
|
3897
|
+
"ctrl+d exit"
|
|
3898
|
+
], width));
|
|
3899
|
+
return `${lines.join("\n")}
|
|
3900
|
+
|
|
3901
|
+
`;
|
|
3902
|
+
}
|
|
3903
|
+
|
|
3904
|
+
// src/local-runtime/live-usage.ts
|
|
3905
|
+
import { execFile } from "child_process";
|
|
3906
|
+
import { statfs } from "fs/promises";
|
|
3907
|
+
import { cpus, freemem, homedir, loadavg, totalmem } from "os";
|
|
3908
|
+
import { dirname as dirname3, join as join7, resolve as resolve2 } from "path";
|
|
3909
|
+
function cpuTimes() {
|
|
3910
|
+
let idle = 0;
|
|
3911
|
+
let total = 0;
|
|
3912
|
+
for (const cpu of cpus()) {
|
|
3913
|
+
const times = cpu.times;
|
|
3914
|
+
idle += times.idle;
|
|
3915
|
+
total += times.user + times.nice + times.sys + times.irq + times.idle;
|
|
3916
|
+
}
|
|
3917
|
+
return { idle, total };
|
|
3918
|
+
}
|
|
3919
|
+
function cpuUtilization(before, after) {
|
|
3920
|
+
const total = after.total - before.total;
|
|
3921
|
+
if (!(total > 0)) return void 0;
|
|
3922
|
+
const busy = total - (after.idle - before.idle);
|
|
3923
|
+
return Math.min(100, Math.max(0, Math.round(busy / total * 100)));
|
|
3924
|
+
}
|
|
3925
|
+
function optionalNumber(value) {
|
|
3926
|
+
if (value === void 0) return void 0;
|
|
3927
|
+
const trimmed = value.trim();
|
|
3928
|
+
if (!trimmed || /^\[?n\/a\]?$/i.test(trimmed) || /not supported/i.test(trimmed)) return void 0;
|
|
3929
|
+
const parsed = Number(trimmed);
|
|
3930
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
3931
|
+
}
|
|
3932
|
+
var MIB = 1024 * 1024;
|
|
3933
|
+
var NVIDIA_USAGE_QUERY = [
|
|
3934
|
+
"--query-gpu=index,name,utilization.gpu,memory.used,memory.total,temperature.gpu",
|
|
3935
|
+
"--format=csv,noheader,nounits"
|
|
3936
|
+
];
|
|
3937
|
+
function parseNvidiaUsage(stdout3) {
|
|
3938
|
+
const gpus = [];
|
|
3939
|
+
for (const line of stdout3.split(/\r?\n/).map((item) => item.trim()).filter(Boolean)) {
|
|
3940
|
+
const parts = line.split(",").map((part) => part.trim());
|
|
3941
|
+
if (parts.length < 2 || !/^\d+$/.test(parts[0] ?? "")) continue;
|
|
3942
|
+
const name = parts[1] || "NVIDIA GPU";
|
|
3943
|
+
const used = optionalNumber(parts[3]);
|
|
3944
|
+
const total = optionalNumber(parts[4]);
|
|
3945
|
+
gpus.push({
|
|
3946
|
+
index: Number(parts[0]),
|
|
3947
|
+
name,
|
|
3948
|
+
utilization_percent: optionalNumber(parts[2]),
|
|
3949
|
+
memory_used_bytes: used === void 0 ? void 0 : Math.round(used * MIB),
|
|
3950
|
+
memory_total_bytes: total === void 0 || total <= 0 ? void 0 : Math.round(total * MIB),
|
|
3951
|
+
temperature_c: optionalNumber(parts[5]),
|
|
3952
|
+
unified_memory: /spark|gb10/i.test(name),
|
|
3953
|
+
vendor: "nvidia"
|
|
3954
|
+
});
|
|
3955
|
+
}
|
|
3956
|
+
return gpus;
|
|
3957
|
+
}
|
|
3958
|
+
function queryNvidia(env, timeoutMs) {
|
|
3959
|
+
return new Promise((resolve13) => {
|
|
3960
|
+
execFile("nvidia-smi", NVIDIA_USAGE_QUERY, { env, timeout: timeoutMs, windowsHide: true }, (error, stdout3) => {
|
|
3961
|
+
if (error) {
|
|
3962
|
+
const code2 = error.code;
|
|
3963
|
+
resolve13({
|
|
3964
|
+
gpus: [],
|
|
3965
|
+
note: code2 === "ENOENT" ? "nvidia-smi not found" : error.killed ? "nvidia-smi timed out" : "nvidia-smi failed"
|
|
3966
|
+
});
|
|
3967
|
+
return;
|
|
3968
|
+
}
|
|
3969
|
+
const gpus = parseNvidiaUsage(String(stdout3));
|
|
3970
|
+
resolve13(gpus.length > 0 ? { gpus } : { gpus, note: "nvidia-smi listed no GPUs" });
|
|
3971
|
+
});
|
|
3972
|
+
});
|
|
3973
|
+
}
|
|
3974
|
+
function tidyCpuModel(model) {
|
|
3975
|
+
return model.replace(/\((?:R|TM|tm|r)\)/g, "").replace(/\s+(?:CPU|Processor)\b/gi, "").replace(/\s*@\s*[\d.]+\s*[GM]Hz/i, "").replace(/\s+\d+-Core$/i, "").replace(/\s+/g, " ").trim() || "CPU";
|
|
3976
|
+
}
|
|
3977
|
+
async function modelCacheDisk(env) {
|
|
3978
|
+
const path = resolve2(env.HF_HOME?.trim() || join7(homedir(), ".cache", "huggingface"));
|
|
3979
|
+
for (let probe = path, depth = 0; depth < 8; depth += 1) {
|
|
3980
|
+
try {
|
|
3981
|
+
const fs = await statfs(probe);
|
|
3982
|
+
return {
|
|
3983
|
+
path,
|
|
3984
|
+
free_bytes: Number(fs.bavail) * Number(fs.bsize),
|
|
3985
|
+
total_bytes: Number(fs.blocks) * Number(fs.bsize)
|
|
3986
|
+
};
|
|
3987
|
+
} catch {
|
|
3988
|
+
const parent = dirname3(probe);
|
|
3989
|
+
if (parent === probe) break;
|
|
3990
|
+
probe = parent;
|
|
3991
|
+
}
|
|
3992
|
+
}
|
|
3993
|
+
return void 0;
|
|
3994
|
+
}
|
|
3995
|
+
function appleGpu(cpuModel, platform, arch) {
|
|
3996
|
+
if (platform !== "darwin" || arch !== "arm64") return void 0;
|
|
3997
|
+
const chip = /apple/i.test(cpuModel) ? cpuModel.trim() : "Apple Silicon";
|
|
3998
|
+
return {
|
|
3999
|
+
index: 0,
|
|
4000
|
+
name: `${chip} GPU`,
|
|
4001
|
+
memory_total_bytes: totalmem(),
|
|
4002
|
+
unified_memory: true,
|
|
4003
|
+
vendor: "apple"
|
|
4004
|
+
};
|
|
4005
|
+
}
|
|
4006
|
+
async function sampleLiveUsage(options = {}) {
|
|
4007
|
+
const env = options.env ?? process.env;
|
|
4008
|
+
const platform = options.platform ?? process.platform;
|
|
4009
|
+
const arch = options.arch ?? process.arch;
|
|
4010
|
+
const before = cpuTimes();
|
|
4011
|
+
const [nvidia, disk] = await Promise.all([
|
|
4012
|
+
queryNvidia(env, options.gpuTimeoutMs ?? 2e3),
|
|
4013
|
+
modelCacheDisk(env).catch(() => void 0),
|
|
4014
|
+
new Promise((resolve13) => setTimeout(resolve13, options.cpuSampleMs ?? 200))
|
|
4015
|
+
]);
|
|
4016
|
+
const after = cpuTimes();
|
|
4017
|
+
const list = cpus();
|
|
4018
|
+
const model = tidyCpuModel(list[0]?.model ?? "");
|
|
4019
|
+
const total = totalmem();
|
|
4020
|
+
const apple = nvidia.gpus.length === 0 ? appleGpu(model, platform, arch) : void 0;
|
|
4021
|
+
const [one, five, fifteen] = loadavg();
|
|
4022
|
+
return {
|
|
4023
|
+
sampled_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4024
|
+
cpu: {
|
|
4025
|
+
model,
|
|
4026
|
+
cores: list.length,
|
|
4027
|
+
utilization_percent: cpuUtilization(before, after),
|
|
4028
|
+
load_average: [one ?? 0, five ?? 0, fifteen ?? 0]
|
|
4029
|
+
},
|
|
4030
|
+
memory: { used_bytes: Math.max(0, total - freemem()), total_bytes: total },
|
|
4031
|
+
disk,
|
|
4032
|
+
gpus: apple ? [apple] : nvidia.gpus,
|
|
4033
|
+
gpu_note: apple ? void 0 : nvidia.note
|
|
4034
|
+
};
|
|
4035
|
+
}
|
|
4036
|
+
|
|
3100
4037
|
// src/shell.ts
|
|
3101
4038
|
import { stat as stat3 } from "fs/promises";
|
|
3102
|
-
import { homedir } from "os";
|
|
3103
|
-
import { isAbsolute as isAbsolute3, resolve as
|
|
4039
|
+
import { homedir as homedir2 } from "os";
|
|
4040
|
+
import { isAbsolute as isAbsolute3, resolve as resolve4 } from "path";
|
|
3104
4041
|
import { createInterface as createInterface2 } from "readline";
|
|
3105
4042
|
import { stripVTControlCharacters as stripVTControlCharacters2 } from "util";
|
|
3106
4043
|
|
|
@@ -3108,6 +4045,7 @@ import { stripVTControlCharacters as stripVTControlCharacters2 } from "util";
|
|
|
3108
4045
|
var LOCAL = ["local"];
|
|
3109
4046
|
var COMMAND_CATALOG = [
|
|
3110
4047
|
{ path: "spec", description: "Review the current behavior spec.", group: "Workflow", modes: LOCAL },
|
|
4048
|
+
{ path: "spec view", description: "Readable, highlighted behavior spec; add a section name for detail.", group: "Workflow", modes: LOCAL },
|
|
3111
4049
|
...["show", "diff", "validate", "history"].map((operation) => ({ path: `spec ${operation}`, description: `Review behavior spec ${operation}.`, group: "Workflow", modes: LOCAL })),
|
|
3112
4050
|
{ path: "init", description: "Create a local behaviour-spec project.", group: "Workflow", modes: LOCAL },
|
|
3113
4051
|
{ path: "validate", description: "Validate a local fine-tuning project.", group: "Workflow", modes: LOCAL },
|
|
@@ -3184,7 +4122,8 @@ var COMMAND_CATALOG = [
|
|
|
3184
4122
|
{ path: "agent status", description: "Show the laptop-local TT agent selection.", group: "Inspect", modes: LOCAL }
|
|
3185
4123
|
];
|
|
3186
4124
|
var SLASH_COMMANDS = [
|
|
3187
|
-
{ path: "/spec", description: "
|
|
4125
|
+
{ path: "/spec", description: "Readable behavior spec; add a section, show, diff, validate or history." },
|
|
4126
|
+
{ path: "/system", description: "Live GPU, CPU and memory use, and what this machine can fine-tune." },
|
|
3188
4127
|
{ path: "/help", description: "Show commands; add a word to filter." },
|
|
3189
4128
|
{ path: "/status", description: "Show lightweight workflow status." },
|
|
3190
4129
|
{ path: "/context", description: "Show the current project context." },
|
|
@@ -3246,7 +4185,7 @@ function createCommandCompleter(getMode = () => "local") {
|
|
|
3246
4185
|
|
|
3247
4186
|
// src/shell-context.ts
|
|
3248
4187
|
import { readFile as readFile3, readdir as readdir3, stat as stat2 } from "fs/promises";
|
|
3249
|
-
import { basename as basename2, dirname as
|
|
4188
|
+
import { basename as basename2, dirname as dirname4, join as join8, resolve as resolve3 } from "path";
|
|
3250
4189
|
async function readJsonObject(path) {
|
|
3251
4190
|
try {
|
|
3252
4191
|
const parsed = JSON.parse(await readFile3(path, "utf8"));
|
|
@@ -3273,7 +4212,7 @@ function expandPath(value, baseDirectory, env) {
|
|
|
3273
4212
|
return expandUserPath(value, env, baseDirectory);
|
|
3274
4213
|
}
|
|
3275
4214
|
function configPath(env) {
|
|
3276
|
-
return
|
|
4215
|
+
return join8(getConfigDir(env), "config.json");
|
|
3277
4216
|
}
|
|
3278
4217
|
function agentSelectionFrom(env, configAgent, hasToken) {
|
|
3279
4218
|
const stored = configAgent && typeof configAgent === "object" && !Array.isArray(configAgent) ? configAgent : {};
|
|
@@ -3291,14 +4230,14 @@ async function adjacentFile(path) {
|
|
|
3291
4230
|
}
|
|
3292
4231
|
}
|
|
3293
4232
|
async function readActiveModelId(storeRoot) {
|
|
3294
|
-
const result2 = await readJsonObject(
|
|
4233
|
+
const result2 = await readJsonObject(join8(storeRoot, "active-model.json"));
|
|
3295
4234
|
if (!result2.found || result2.invalid) return void 0;
|
|
3296
4235
|
const modelId = result2.value?.model_id;
|
|
3297
4236
|
if (modelId === null) return "base";
|
|
3298
4237
|
return typeof modelId === "string" && modelId ? modelId : void 0;
|
|
3299
4238
|
}
|
|
3300
4239
|
async function readLatestRun(storeRoot) {
|
|
3301
|
-
const runsRoot =
|
|
4240
|
+
const runsRoot = join8(storeRoot, "runs");
|
|
3302
4241
|
let names;
|
|
3303
4242
|
try {
|
|
3304
4243
|
names = (await readdir3(runsRoot, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
@@ -3306,7 +4245,7 @@ async function readLatestRun(storeRoot) {
|
|
|
3306
4245
|
return void 0;
|
|
3307
4246
|
}
|
|
3308
4247
|
const states = await Promise.all(names.map(async (name) => {
|
|
3309
|
-
const result2 = await readJsonObject(
|
|
4248
|
+
const result2 = await readJsonObject(join8(runsRoot, name, "state.json"));
|
|
3310
4249
|
if (!result2.found || result2.invalid || !result2.value) return void 0;
|
|
3311
4250
|
const id = stringField(result2.value, "id") ?? name;
|
|
3312
4251
|
const state = {
|
|
@@ -3334,14 +4273,15 @@ function hostFromSnapshot(snapshot) {
|
|
|
3334
4273
|
summary: snapshot.summary,
|
|
3335
4274
|
line: formatHostStatusLine(snapshot),
|
|
3336
4275
|
cuda: snapshot.capabilities.cuda_available,
|
|
3337
|
-
gpuName: snapshot.capabilities.gpu?.name
|
|
4276
|
+
gpuName: snapshot.capabilities.gpu?.name,
|
|
4277
|
+
capabilities: snapshot.capabilities
|
|
3338
4278
|
};
|
|
3339
4279
|
}
|
|
3340
4280
|
async function discoverShellContext(options = {}) {
|
|
3341
4281
|
const env = options.env ?? process.env;
|
|
3342
|
-
const cwd =
|
|
4282
|
+
const cwd = resolve3(options.cwd ?? process.cwd());
|
|
3343
4283
|
const warnings = [];
|
|
3344
|
-
const specPath =
|
|
4284
|
+
const specPath = join8(cwd, "tunedtensor.json");
|
|
3345
4285
|
const specJson = await readJsonObject(specPath);
|
|
3346
4286
|
let spec;
|
|
3347
4287
|
if (specJson.found) {
|
|
@@ -3350,31 +4290,35 @@ async function discoverShellContext(options = {}) {
|
|
|
3350
4290
|
warnings.push("tunedtensor.json could not be parsed.");
|
|
3351
4291
|
} else {
|
|
3352
4292
|
const examples = specJson.value.examples;
|
|
4293
|
+
const runtime2 = specJson.value.runtime;
|
|
4294
|
+
const gpu = runtime2 && typeof runtime2 === "object" && !Array.isArray(runtime2) ? runtime2.gpu : void 0;
|
|
3353
4295
|
spec = {
|
|
3354
4296
|
path: specPath,
|
|
3355
4297
|
id: stringField(specJson.value, "id"),
|
|
3356
4298
|
name: stringField(specJson.value, "name"),
|
|
3357
4299
|
baseModel: stringField(specJson.value, "base_model"),
|
|
4300
|
+
engine: stringField(specJson.value, "engine"),
|
|
3358
4301
|
exampleCount: Array.isArray(examples) ? examples.length : void 0,
|
|
4302
|
+
remoteGpu: gpu && typeof gpu === "object" && !Array.isArray(gpu) ? stringField(gpu, "instanceId") : void 0,
|
|
3359
4303
|
parseError: false
|
|
3360
4304
|
};
|
|
3361
4305
|
}
|
|
3362
4306
|
}
|
|
3363
|
-
const localConfigPath =
|
|
4307
|
+
const localConfigPath = join8(cwd, "local-runner.json");
|
|
3364
4308
|
const hasAdjacentLocalConfig = await adjacentFile(localConfigPath);
|
|
3365
4309
|
const localConfigJson = hasAdjacentLocalConfig ? await readJsonObject(localConfigPath) : { found: false, invalid: false };
|
|
3366
4310
|
if (localConfigJson.invalid) {
|
|
3367
4311
|
warnings.push("local-runner.json could not be parsed.");
|
|
3368
4312
|
}
|
|
3369
|
-
const localConfigDirectory = hasAdjacentLocalConfig ?
|
|
4313
|
+
const localConfigDirectory = hasAdjacentLocalConfig ? dirname4(localConfigPath) : cwd;
|
|
3370
4314
|
const runtime = specJson.value?.runtime;
|
|
3371
4315
|
const projectRuntime = runtime && typeof runtime === "object" && !Array.isArray(runtime) ? runtime : void 0;
|
|
3372
4316
|
if (hasAdjacentLocalConfig && (projectRuntime || specJson.value?.evaluation)) warnings.push("Conflicting configuration sources; run tt pipeline migrate after reconciling the spec and local-runner.json.");
|
|
3373
4317
|
const config = projectRuntime ?? localConfigJson.value;
|
|
3374
4318
|
const configuredArtifactRoot = stringField(config, "artifactRoot");
|
|
3375
4319
|
const configuredStoreRoot = stringField(config, "storeRoot");
|
|
3376
|
-
const artifactRoot = configuredArtifactRoot ? expandPath(configuredArtifactRoot, localConfigDirectory, env) :
|
|
3377
|
-
const storeRoot = configuredStoreRoot ? expandPath(configuredStoreRoot, localConfigDirectory, env) : specJson.value && !hasAdjacentLocalConfig && !env.TT_LOCAL_HOME?.trim() ?
|
|
4320
|
+
const artifactRoot = configuredArtifactRoot ? expandPath(configuredArtifactRoot, localConfigDirectory, env) : resolve3(cwd, DEFAULT_ARTIFACT_ROOT);
|
|
4321
|
+
const storeRoot = configuredStoreRoot ? expandPath(configuredStoreRoot, localConfigDirectory, env) : specJson.value && !hasAdjacentLocalConfig && !env.TT_LOCAL_HOME?.trim() ? resolve3(cwd, DEFAULT_PROJECT_STORE_ROOT) : defaultStoreRoot(env);
|
|
3378
4322
|
const storedConfigPath = configPath(env);
|
|
3379
4323
|
const storedConfigJson = await readJsonObject(storedConfigPath);
|
|
3380
4324
|
if (storedConfigJson.invalid) {
|
|
@@ -3408,7 +4352,7 @@ async function discoverShellContext(options = {}) {
|
|
|
3408
4352
|
function valueOrDash(value) {
|
|
3409
4353
|
return value === void 0 || value === "" ? "\u2014" : String(value);
|
|
3410
4354
|
}
|
|
3411
|
-
function
|
|
4355
|
+
function agentLabel2(context) {
|
|
3412
4356
|
const agent = context.agent;
|
|
3413
4357
|
if (!agent?.provider && !agent?.model) return "not configured";
|
|
3414
4358
|
const id = [agent.provider, agent.model].filter(Boolean).join("/");
|
|
@@ -3423,7 +4367,7 @@ function formatShellContext(context) {
|
|
|
3423
4367
|
`Spec ID ${valueOrDash(context.spec?.id)}`,
|
|
3424
4368
|
`Base model ${valueOrDash(context.spec?.baseModel)}`,
|
|
3425
4369
|
`Examples ${valueOrDash(context.spec?.exampleCount)}`,
|
|
3426
|
-
`Agent model ${
|
|
4370
|
+
`Agent model ${agentLabel2(context)}`,
|
|
3427
4371
|
`Local config ${context.local.configPath ?? "not found"}`,
|
|
3428
4372
|
`Artifact root ${context.local.artifactRoot}`,
|
|
3429
4373
|
`Store root ${context.local.storeRoot}`,
|
|
@@ -3435,7 +4379,7 @@ function formatShellContext(context) {
|
|
|
3435
4379
|
function formatShellStatus(context) {
|
|
3436
4380
|
return [
|
|
3437
4381
|
`Spec ${context.spec?.name ?? (context.spec ? "unnamed" : "not found")}`,
|
|
3438
|
-
`Agent model ${
|
|
4382
|
+
`Agent model ${agentLabel2(context)}`,
|
|
3439
4383
|
`Local config ${context.local.configPath ?? "not found"}`,
|
|
3440
4384
|
`Active model ${context.local.activeModelId ?? "base"}`,
|
|
3441
4385
|
`Latest run ${context.local.latestRun ? `${context.local.latestRun.id} (${context.local.latestRun.status ?? "unknown"})` : "none"}`,
|
|
@@ -3612,7 +4556,7 @@ function setAgentModel(runtime, env, provider, model, options = {}) {
|
|
|
3612
4556
|
}
|
|
3613
4557
|
|
|
3614
4558
|
// src/secret-prompt.ts
|
|
3615
|
-
import
|
|
4559
|
+
import chalk6 from "chalk";
|
|
3616
4560
|
import { emitKeypressEvents } from "readline";
|
|
3617
4561
|
import { createInterface } from "readline/promises";
|
|
3618
4562
|
import { stdin, stdout } from "process";
|
|
@@ -3690,16 +4634,16 @@ async function promptKeyChoice(message, input = stdin, output = stdout) {
|
|
|
3690
4634
|
emitKeypressEvents(input);
|
|
3691
4635
|
return await withDetachedKeypress(input, async () => {
|
|
3692
4636
|
const wasRaw = terminal.isRaw === true;
|
|
3693
|
-
terminal.setRawMode(true);
|
|
3694
|
-
input.resume();
|
|
3695
|
-
output.write(`${message} ${hint()}`);
|
|
3696
4637
|
try {
|
|
3697
|
-
|
|
4638
|
+
terminal.setRawMode(true);
|
|
4639
|
+
input.resume();
|
|
4640
|
+
output.write(`${message} ${hint()}`);
|
|
4641
|
+
const choice = await new Promise((resolve13) => {
|
|
3698
4642
|
const finish = (decision) => {
|
|
3699
4643
|
input.removeListener("keypress", onKeypress);
|
|
3700
4644
|
input.removeListener("end", onEnd);
|
|
3701
4645
|
input.removeListener("close", onEnd);
|
|
3702
|
-
|
|
4646
|
+
resolve13(decision);
|
|
3703
4647
|
};
|
|
3704
4648
|
const onKeypress = (text2, key) => {
|
|
3705
4649
|
const decided = keyChoiceFor(key, text2);
|
|
@@ -3719,7 +4663,7 @@ async function promptKeyChoice(message, input = stdin, output = stdout) {
|
|
|
3719
4663
|
});
|
|
3720
4664
|
}
|
|
3721
4665
|
function hint() {
|
|
3722
|
-
return
|
|
4666
|
+
return chalk6.dim("y yes \xB7 n no \xB7 esc decide later ");
|
|
3723
4667
|
}
|
|
3724
4668
|
|
|
3725
4669
|
// src/shell.ts
|
|
@@ -3880,6 +4824,7 @@ var SLASH_NAMES = /* @__PURE__ */ new Set([
|
|
|
3880
4824
|
"help",
|
|
3881
4825
|
"status",
|
|
3882
4826
|
"context",
|
|
4827
|
+
"system",
|
|
3883
4828
|
"model",
|
|
3884
4829
|
"login",
|
|
3885
4830
|
"clear",
|
|
@@ -3949,9 +4894,9 @@ function parseSlashCommand(input) {
|
|
|
3949
4894
|
function errorMessage(error) {
|
|
3950
4895
|
return error instanceof Error ? error.message : String(error);
|
|
3951
4896
|
}
|
|
3952
|
-
var successMark = () =>
|
|
3953
|
-
var errorMark = () =>
|
|
3954
|
-
var
|
|
4897
|
+
var successMark = () => chalk7.green("\u2713");
|
|
4898
|
+
var errorMark = () => chalk7.red("\u2717");
|
|
4899
|
+
var accent4 = chalk7.hex("#8B5CF6");
|
|
3955
4900
|
var ANSI_BOLD = "\x1B[1m";
|
|
3956
4901
|
var ANSI_BOLD_OFF = "\x1B[22m";
|
|
3957
4902
|
var ANSI_CLEAR_LINE = "\x1B[2K";
|
|
@@ -3960,15 +4905,15 @@ var ANSI_CLEAR_DOWN = "\x1B[J";
|
|
|
3960
4905
|
var ANSI_CURSOR_UP = "\x1B[1A";
|
|
3961
4906
|
var ANSI_RESET = "\x1B[0m";
|
|
3962
4907
|
function inputColorCodes() {
|
|
3963
|
-
if (
|
|
3964
|
-
if (
|
|
4908
|
+
if (chalk7.level === 0) return null;
|
|
4909
|
+
if (chalk7.level === 1) {
|
|
3965
4910
|
return {
|
|
3966
4911
|
background: "\x1B[100m",
|
|
3967
4912
|
accent: "\x1B[95m",
|
|
3968
4913
|
text: "\x1B[97m"
|
|
3969
4914
|
};
|
|
3970
4915
|
}
|
|
3971
|
-
if (
|
|
4916
|
+
if (chalk7.level === 2) {
|
|
3972
4917
|
return {
|
|
3973
4918
|
background: "\x1B[48;5;238m",
|
|
3974
4919
|
accent: "\x1B[38;5;183m",
|
|
@@ -3981,13 +4926,14 @@ function inputColorCodes() {
|
|
|
3981
4926
|
text: "\x1B[38;2;245;243;255m"
|
|
3982
4927
|
};
|
|
3983
4928
|
}
|
|
4929
|
+
var SPEC_USAGE = `Usage: /spec [${SPEC_SECTIONS.join("|")}|show|diff|validate|history] [path/to/tunedtensor.json]`;
|
|
3984
4930
|
var DETAIL_LABEL_WIDTH = 15;
|
|
3985
4931
|
function styleDetailLines(lines) {
|
|
3986
4932
|
return lines.map((line) => {
|
|
3987
4933
|
if (line.length <= DETAIL_LABEL_WIDTH) return line;
|
|
3988
4934
|
const label = line.slice(0, DETAIL_LABEL_WIDTH);
|
|
3989
4935
|
if (!label.trim()) return line;
|
|
3990
|
-
const color = label.startsWith("Warning") ?
|
|
4936
|
+
const color = label.startsWith("Warning") ? chalk7.yellow : accent4.bold;
|
|
3991
4937
|
return `${color(label)}${line.slice(DETAIL_LABEL_WIDTH)}`;
|
|
3992
4938
|
});
|
|
3993
4939
|
}
|
|
@@ -4000,42 +4946,47 @@ function assertNoArgs(command, args2) {
|
|
|
4000
4946
|
}
|
|
4001
4947
|
}
|
|
4002
4948
|
function expandDirectory(value, cwd, env) {
|
|
4003
|
-
const home = env.HOME ?
|
|
4949
|
+
const home = env.HOME ? resolve4(env.HOME) : homedir2();
|
|
4004
4950
|
if (value === "~") return home;
|
|
4005
|
-
if (value.startsWith("~/")) return
|
|
4006
|
-
return isAbsolute3(value) ?
|
|
4951
|
+
if (value.startsWith("~/")) return resolve4(home, value.slice(2));
|
|
4952
|
+
return isAbsolute3(value) ? resolve4(value) : resolve4(cwd, value);
|
|
4007
4953
|
}
|
|
4008
4954
|
function helpText(mode, query, palette = false) {
|
|
4009
4955
|
const groups = groupedCatalog(mode, query);
|
|
4010
4956
|
const lines = [];
|
|
4011
|
-
lines.push(
|
|
4957
|
+
lines.push(accent4.bold(palette ? "Commands \u2014 type a command, or prefix with : to make the intent explicit" : `TT commands${query ? ` matching ${JSON.stringify(query)}` : ""}`));
|
|
4012
4958
|
if (groups.size === 0) {
|
|
4013
|
-
lines.push(
|
|
4959
|
+
lines.push(chalk7.dim(" No matching commands."));
|
|
4014
4960
|
} else {
|
|
4015
4961
|
for (const [group, commands] of groups) {
|
|
4016
4962
|
lines.push("");
|
|
4017
|
-
lines.push(
|
|
4963
|
+
lines.push(chalk7.bold(group));
|
|
4018
4964
|
for (const command of commands) {
|
|
4019
|
-
lines.push(` ${
|
|
4965
|
+
lines.push(` ${accent4(command.path.padEnd(22))} ${command.description}`);
|
|
4020
4966
|
}
|
|
4021
4967
|
}
|
|
4022
4968
|
}
|
|
4023
4969
|
if (!query) {
|
|
4024
4970
|
lines.push("");
|
|
4025
|
-
lines.push(
|
|
4026
|
-
lines.push(` ${
|
|
4027
|
-
lines.push(` ${
|
|
4028
|
-
lines.push(` ${
|
|
4029
|
-
lines.push(` ${
|
|
4030
|
-
lines.push(` ${
|
|
4031
|
-
lines.push(` ${
|
|
4971
|
+
lines.push(chalk7.bold("Conversation"));
|
|
4972
|
+
lines.push(` ${accent4("plain text".padEnd(22))} Ask the Tuned Tensor agent anything.`);
|
|
4973
|
+
lines.push(` ${accent4("/new".padEnd(22))} Start a new agent conversation.`);
|
|
4974
|
+
lines.push(` ${accent4("/threads".padEnd(22))} List recent conversations.`);
|
|
4975
|
+
lines.push(` ${accent4("/resume <id>".padEnd(22))} Resume a conversation.`);
|
|
4976
|
+
lines.push(` ${accent4("/approve [id]".padEnd(22))} Approve an action left pending.`);
|
|
4977
|
+
lines.push(` ${accent4("/reject [id]".padEnd(22))} Reject an action left pending.`);
|
|
4032
4978
|
lines.push("");
|
|
4033
|
-
lines.push(
|
|
4979
|
+
lines.push(chalk7.bold("Session"));
|
|
4034
4980
|
for (const command of SLASH_COMMANDS) {
|
|
4035
|
-
lines.push(` ${
|
|
4981
|
+
lines.push(` ${accent4(command.path.padEnd(22))} ${command.description}`);
|
|
4036
4982
|
}
|
|
4037
|
-
lines.push(` ${
|
|
4038
|
-
lines.push(
|
|
4983
|
+
lines.push(` ${accent4("?".padEnd(22))} Alias for /help.`);
|
|
4984
|
+
lines.push("");
|
|
4985
|
+
lines.push(chalk7.bold("Behavior spec"));
|
|
4986
|
+
lines.push(` ${accent4("/spec".padEnd(22))} Readable overview of tunedtensor.json.`);
|
|
4987
|
+
lines.push(` ${accent4("/spec <section>".padEnd(22))} One section in full: ${SPEC_SECTIONS.join(", ")}.`);
|
|
4988
|
+
lines.push(` ${accent4("/spec show".padEnd(22))} The raw JSON, highlighted.`);
|
|
4989
|
+
lines.push(chalk7.dim(
|
|
4039
4990
|
"\nKnown TT commands execute directly; other text goes to the agent. Prefix a command with : to make the intent explicit."
|
|
4040
4991
|
));
|
|
4041
4992
|
}
|
|
@@ -4045,33 +4996,17 @@ function helpText(mode, query, palette = false) {
|
|
|
4045
4996
|
function activeModelLabel(snapshot) {
|
|
4046
4997
|
return snapshot.context.local.activeModelId ?? "base";
|
|
4047
4998
|
}
|
|
4048
|
-
function
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
snapshot.context.agent?.provider && snapshot.context.agent?.model
|
|
4057
|
-
);
|
|
4058
|
-
const lines = [
|
|
4059
|
-
heading,
|
|
4060
|
-
chalk4.dim(
|
|
4061
|
-
`agent ${agentModelLabel(snapshot.context)} \xB7 workflow model ${activeModelLabel(snapshot)}`
|
|
4062
|
-
),
|
|
4063
|
-
chalk4.dim("/spec review \xB7 ctrl+c stop/clear \xB7 ctrl+d exit \xB7 /help commands \xB7 tab complete"),
|
|
4064
|
-
"",
|
|
4065
|
-
chalk4.dim(
|
|
4066
|
-
configured ? "Ask TT anything. Known commands run directly." : "Use /login tunedtensor for managed inference, or /model for your own provider. Workflow commands work now."
|
|
4067
|
-
)
|
|
4068
|
-
];
|
|
4069
|
-
return `${lines.join("\n")}
|
|
4070
|
-
|
|
4071
|
-
`;
|
|
4999
|
+
function renderShellBanner(snapshot, columns) {
|
|
5000
|
+
return renderBanner({
|
|
5001
|
+
version: snapshot.version,
|
|
5002
|
+
context: snapshot.context,
|
|
5003
|
+
activeModel: activeModelLabel(snapshot),
|
|
5004
|
+
usage: snapshot.usage,
|
|
5005
|
+
columns
|
|
5006
|
+
});
|
|
4072
5007
|
}
|
|
4073
5008
|
function renderShellPrompt() {
|
|
4074
|
-
return `${
|
|
5009
|
+
return `${accent4.bold("\u203A")} `;
|
|
4075
5010
|
}
|
|
4076
5011
|
function renderSubmittedShellInput(input, columns) {
|
|
4077
5012
|
const colors = inputColorCodes();
|
|
@@ -4104,7 +5039,7 @@ function resetShellPromptStyle() {
|
|
|
4104
5039
|
return "";
|
|
4105
5040
|
}
|
|
4106
5041
|
var TunedTensorShellSession = class _TunedTensorShellSession {
|
|
4107
|
-
constructor(runner, io, agent, env, contextProvider, version, agentModelRuntime, initialContext) {
|
|
5042
|
+
constructor(runner, io, agent, env, contextProvider, version, agentModelRuntime, systemProbe, columns, initialContext) {
|
|
4108
5043
|
this.runner = runner;
|
|
4109
5044
|
this.io = io;
|
|
4110
5045
|
this.agent = agent;
|
|
@@ -4112,6 +5047,8 @@ var TunedTensorShellSession = class _TunedTensorShellSession {
|
|
|
4112
5047
|
this.contextProvider = contextProvider;
|
|
4113
5048
|
this.version = version;
|
|
4114
5049
|
this.agentModelRuntime = agentModelRuntime;
|
|
5050
|
+
this.systemProbe = systemProbe;
|
|
5051
|
+
this.columns = columns;
|
|
4115
5052
|
this.cwd = initialContext.cwd;
|
|
4116
5053
|
this.context = initialContext;
|
|
4117
5054
|
}
|
|
@@ -4119,12 +5056,13 @@ var TunedTensorShellSession = class _TunedTensorShellSession {
|
|
|
4119
5056
|
modeSource = "default-local";
|
|
4120
5057
|
cwd;
|
|
4121
5058
|
context;
|
|
5059
|
+
usage;
|
|
4122
5060
|
static async create(options) {
|
|
4123
5061
|
const env = options.env ?? process.env;
|
|
4124
|
-
const cwd =
|
|
5062
|
+
const cwd = resolve4(options.cwd ?? process.cwd());
|
|
4125
5063
|
const contextProvider = options.contextProvider ?? ((input) => discoverShellContext(input));
|
|
4126
5064
|
const initialContext = await contextProvider({ cwd, env });
|
|
4127
|
-
|
|
5065
|
+
const session = new _TunedTensorShellSession(
|
|
4128
5066
|
options.runner,
|
|
4129
5067
|
options.io,
|
|
4130
5068
|
options.agent,
|
|
@@ -4132,8 +5070,22 @@ var TunedTensorShellSession = class _TunedTensorShellSession {
|
|
|
4132
5070
|
contextProvider,
|
|
4133
5071
|
options.version,
|
|
4134
5072
|
options.agentModelRuntime,
|
|
5073
|
+
options.systemProbe,
|
|
5074
|
+
options.columns ?? (() => void 0),
|
|
4135
5075
|
initialContext
|
|
4136
5076
|
);
|
|
5077
|
+
await session.sampleSystem();
|
|
5078
|
+
return session;
|
|
5079
|
+
}
|
|
5080
|
+
/** Refresh live machine usage; a failed probe only hides the panel. */
|
|
5081
|
+
async sampleSystem() {
|
|
5082
|
+
if (!this.systemProbe) return void 0;
|
|
5083
|
+
try {
|
|
5084
|
+
this.usage = await this.systemProbe();
|
|
5085
|
+
} catch {
|
|
5086
|
+
this.usage = void 0;
|
|
5087
|
+
}
|
|
5088
|
+
return this.usage;
|
|
4137
5089
|
}
|
|
4138
5090
|
interruptAgent() {
|
|
4139
5091
|
return this.agent?.interrupt() ?? false;
|
|
@@ -4144,14 +5096,25 @@ var TunedTensorShellSession = class _TunedTensorShellSession {
|
|
|
4144
5096
|
modeSource: this.modeSource,
|
|
4145
5097
|
cwd: this.cwd,
|
|
4146
5098
|
context: this.context,
|
|
4147
|
-
version: this.version
|
|
5099
|
+
version: this.version,
|
|
5100
|
+
usage: this.usage
|
|
4148
5101
|
};
|
|
4149
5102
|
}
|
|
4150
5103
|
prompt() {
|
|
4151
5104
|
return renderShellPrompt();
|
|
4152
5105
|
}
|
|
4153
5106
|
banner() {
|
|
4154
|
-
return renderShellBanner(this.snapshot());
|
|
5107
|
+
return renderShellBanner(this.snapshot(), this.columns());
|
|
5108
|
+
}
|
|
5109
|
+
async inspectSpecForView(path) {
|
|
5110
|
+
try {
|
|
5111
|
+
return await inspectLocalSpec(this.cwd, path);
|
|
5112
|
+
} catch (error) {
|
|
5113
|
+
if (error.code === "ENOENT") {
|
|
5114
|
+
throw new ShellParseError(`No ${path} in ${this.cwd}. Run init, or ask TT to draft a behavior spec.`);
|
|
5115
|
+
}
|
|
5116
|
+
throw error;
|
|
5117
|
+
}
|
|
4155
5118
|
}
|
|
4156
5119
|
async refreshContext() {
|
|
4157
5120
|
this.context = await this.contextProvider({ cwd: this.cwd, env: this.env });
|
|
@@ -4187,42 +5150,42 @@ var TunedTensorShellSession = class _TunedTensorShellSession {
|
|
|
4187
5150
|
const provider = query ? findAgentProvider(runtime, query) : void 0;
|
|
4188
5151
|
if (provider) {
|
|
4189
5152
|
const matches = listAgentModels(runtime, { ...catalog, provider: provider.id });
|
|
4190
|
-
lines.push("",
|
|
5153
|
+
lines.push("", chalk7.bold(`Models from ${provider.id}`));
|
|
4191
5154
|
this.appendAgentModelChoices(lines, matches, summary, 20, "/model <query> to search");
|
|
4192
5155
|
if (matches.length === 0) {
|
|
4193
|
-
lines.push(
|
|
5156
|
+
lines.push(chalk7.dim(" no models"));
|
|
4194
5157
|
}
|
|
4195
5158
|
return lines;
|
|
4196
5159
|
}
|
|
4197
5160
|
if (query) {
|
|
4198
5161
|
const matches = listAgentModels(runtime, { ...catalog, query });
|
|
4199
|
-
lines.push("",
|
|
5162
|
+
lines.push("", chalk7.bold(`Models matching ${JSON.stringify(query)}`));
|
|
4200
5163
|
this.appendAgentModelChoices(lines, matches, summary, 10, "refine the query");
|
|
4201
5164
|
if (matches.length === 0) {
|
|
4202
|
-
lines.push(
|
|
5165
|
+
lines.push(chalk7.dim(" no matches"));
|
|
4203
5166
|
}
|
|
4204
5167
|
return lines;
|
|
4205
5168
|
}
|
|
4206
5169
|
const providers = listAgentProviders(runtime, { featuredOnly: true });
|
|
4207
|
-
lines.push("",
|
|
5170
|
+
lines.push("", chalk7.bold("Providers"));
|
|
4208
5171
|
if (providers.length === 0) {
|
|
4209
|
-
lines.push(
|
|
5172
|
+
lines.push(chalk7.dim(" no providers in the catalog"));
|
|
4210
5173
|
} else {
|
|
4211
5174
|
for (const choice of providers) {
|
|
4212
5175
|
lines.push(this.formatAgentProviderChoice(choice));
|
|
4213
5176
|
}
|
|
4214
|
-
lines.push(
|
|
5177
|
+
lines.push(chalk7.dim(" Other providers: /login <id> or /model <id>"));
|
|
4215
5178
|
if (providers.some((choice) => !choice.authenticated)) {
|
|
4216
|
-
lines.push(
|
|
5179
|
+
lines.push(chalk7.dim(" Use /login tunedtensor for your TT token, or /login <provider> for a BYO key."));
|
|
4217
5180
|
}
|
|
4218
5181
|
}
|
|
4219
5182
|
const suggestions = recommendAgentModels(runtime, featuredCatalog);
|
|
4220
|
-
lines.push("",
|
|
5183
|
+
lines.push("", chalk7.bold("Suggestions"));
|
|
4221
5184
|
this.appendAgentModelChoices(lines, suggestions, summary);
|
|
4222
5185
|
if (suggestions.length === 0) {
|
|
4223
|
-
lines.push(
|
|
5186
|
+
lines.push(chalk7.dim(" no models in the catalog"));
|
|
4224
5187
|
} else {
|
|
4225
|
-
lines.push(
|
|
5188
|
+
lines.push(chalk7.dim(" Use /model <provider> to list models, /model <query> to search"));
|
|
4226
5189
|
}
|
|
4227
5190
|
return lines;
|
|
4228
5191
|
}
|
|
@@ -4233,25 +5196,35 @@ var TunedTensorShellSession = class _TunedTensorShellSession {
|
|
|
4233
5196
|
}
|
|
4234
5197
|
if (limit && models.length > shown.length) {
|
|
4235
5198
|
const hint2 = moreHint ? ` \u2014 ${moreHint}` : "";
|
|
4236
|
-
lines.push(
|
|
5199
|
+
lines.push(chalk7.dim(` \u2026 ${models.length - shown.length} more${hint2}`));
|
|
4237
5200
|
}
|
|
4238
5201
|
}
|
|
4239
5202
|
formatAgentProviderChoice(provider) {
|
|
4240
5203
|
const displayName = provider.name !== provider.id ? ` ${provider.name}` : "";
|
|
4241
|
-
const auth = provider.authenticated ? "" :
|
|
4242
|
-
return ` ${
|
|
5204
|
+
const auth = provider.authenticated ? "" : chalk7.dim(" auth required");
|
|
5205
|
+
return ` ${accent4(provider.id)}${displayName}${auth}`;
|
|
4243
5206
|
}
|
|
4244
5207
|
formatAgentModelChoice(model, summary) {
|
|
4245
5208
|
const active = summary?.provider === model.provider && summary?.model === model.id;
|
|
4246
5209
|
const displayName = model.name !== model.id ? ` ${model.name}` : "";
|
|
4247
|
-
const auth = model.authenticated ? "" :
|
|
4248
|
-
return ` ${
|
|
5210
|
+
const auth = model.authenticated ? "" : chalk7.dim(" auth required");
|
|
5211
|
+
return ` ${accent4(`${model.provider}/${model.id}`)}${displayName}${active ? chalk7.dim(" (active)") : ""}${auth}`;
|
|
4249
5212
|
}
|
|
4250
5213
|
async handleSlash(command) {
|
|
4251
5214
|
switch (command.name) {
|
|
4252
5215
|
case "spec": {
|
|
4253
|
-
if (command.args.length > 2) throw new ShellParseError(
|
|
4254
|
-
|
|
5216
|
+
if (command.args.length > 2) throw new ShellParseError(SPEC_USAGE);
|
|
5217
|
+
let [operation = "view", path = "tunedtensor.json"] = command.args;
|
|
5218
|
+
if (command.args.length === 1 && /(^|\/)tunedtensor\.json$/.test(operation)) {
|
|
5219
|
+
[operation, path] = ["view", operation];
|
|
5220
|
+
}
|
|
5221
|
+
const section = resolveSpecSection(operation);
|
|
5222
|
+
if (section || operation === "view" || operation === "show") {
|
|
5223
|
+
const spec = await this.inspectSpecForView(path);
|
|
5224
|
+
this.io.write(operation === "show" ? renderSpecSource(spec) : renderSpecView(spec, { section, columns: this.columns() }));
|
|
5225
|
+
return "continue";
|
|
5226
|
+
}
|
|
5227
|
+
if (!["diff", "validate", "history"].includes(operation)) throw new ShellParseError(SPEC_USAGE);
|
|
4255
5228
|
if (operation === "diff") {
|
|
4256
5229
|
const current = await inspectLocalSpec(this.cwd, path);
|
|
4257
5230
|
const pending = this.agent?.snapshot?.().pendingActions.filter((action) => {
|
|
@@ -4262,18 +5235,27 @@ var TunedTensorShellSession = class _TunedTensorShellSession {
|
|
|
4262
5235
|
for (const action of pending) {
|
|
4263
5236
|
const preview = action.preview;
|
|
4264
5237
|
const input = action.arguments;
|
|
4265
|
-
const
|
|
5238
|
+
const text3 = `Pending edit ${action.id.slice(0, 8)}${input.expected_sha256 !== current.sha256 ? " (stale; prepare again)" : ""}
|
|
4266
5239
|
${preview.diff}
|
|
4267
5240
|
`;
|
|
4268
|
-
this.io.write(sanitizeTerminalText(
|
|
5241
|
+
this.io.write(highlightSpecDiff(sanitizeTerminalText(text3)));
|
|
4269
5242
|
}
|
|
4270
5243
|
return "continue";
|
|
4271
5244
|
}
|
|
4272
5245
|
}
|
|
4273
|
-
|
|
5246
|
+
const text2 = sanitizeTerminalText((await reviewSpec(this.cwd, operation, path)).text);
|
|
5247
|
+
this.io.write(`${operation === "diff" ? highlightSpecDiff(text2) : text2}
|
|
4274
5248
|
`);
|
|
4275
5249
|
return "continue";
|
|
4276
5250
|
}
|
|
5251
|
+
case "system": {
|
|
5252
|
+
assertNoArgs("system", command.args);
|
|
5253
|
+
const usage = await this.sampleSystem();
|
|
5254
|
+
if (!usage) throw new ShellParseError("Live machine usage is unavailable in this session.");
|
|
5255
|
+
await this.refreshContext();
|
|
5256
|
+
this.writeLines(renderMachinePanel(usage, this.context, { columns: this.columns(), detailed: true }));
|
|
5257
|
+
return "continue";
|
|
5258
|
+
}
|
|
4277
5259
|
case "palette":
|
|
4278
5260
|
this.io.write(helpText(this.mode, void 0, true));
|
|
4279
5261
|
return "continue";
|
|
@@ -4336,9 +5318,9 @@ ${preview.diff}
|
|
|
4336
5318
|
}
|
|
4337
5319
|
this.writeLines([
|
|
4338
5320
|
"",
|
|
4339
|
-
|
|
5321
|
+
chalk7.bold("Providers"),
|
|
4340
5322
|
...providers.map((choice) => this.formatAgentProviderChoice(choice)),
|
|
4341
|
-
|
|
5323
|
+
chalk7.dim(" Other providers: /login <id>")
|
|
4342
5324
|
]);
|
|
4343
5325
|
if (!this.io.promptLine) {
|
|
4344
5326
|
throw new ShellParseError("Usage: /login <provider>");
|
|
@@ -4526,7 +5508,9 @@ async function startInteractiveShell(options) {
|
|
|
4526
5508
|
cwd: options.cwd,
|
|
4527
5509
|
env: options.env,
|
|
4528
5510
|
version: options.version,
|
|
4529
|
-
agentModelRuntime: options.agentModelRuntime
|
|
5511
|
+
agentModelRuntime: options.agentModelRuntime,
|
|
5512
|
+
systemProbe: options.systemProbe ?? (() => sampleLiveUsage({ env: { ...process.env, ...options.env } })),
|
|
5513
|
+
columns: () => output.columns
|
|
4530
5514
|
});
|
|
4531
5515
|
const completer = createCommandCompleter(() => session.snapshot().mode);
|
|
4532
5516
|
readline = createInterface2({
|
|
@@ -4577,6 +5561,9 @@ async function startInteractiveShell(options) {
|
|
|
4577
5561
|
} finally {
|
|
4578
5562
|
output.write(resetShellPromptStyle());
|
|
4579
5563
|
readline.close();
|
|
5564
|
+
output.write(`
|
|
5565
|
+
${mascotFarewell()}
|
|
5566
|
+
`);
|
|
4580
5567
|
}
|
|
4581
5568
|
}
|
|
4582
5569
|
function ciEnabled(value) {
|
|
@@ -4589,9 +5576,9 @@ function shouldStartInteractiveShell(options) {
|
|
|
4589
5576
|
}
|
|
4590
5577
|
|
|
4591
5578
|
// src/agent.ts
|
|
4592
|
-
var
|
|
4593
|
-
var successMark2 = () =>
|
|
4594
|
-
var errorMark2 = () =>
|
|
5579
|
+
var accent5 = chalk8.hex("#8B5CF6");
|
|
5580
|
+
var successMark2 = () => chalk8.green("\u2713");
|
|
5581
|
+
var errorMark2 = () => chalk8.red("\u2717");
|
|
4595
5582
|
var ANSI_CLEAR_LINE2 = "\r\x1B[2K";
|
|
4596
5583
|
var GUTTER = " ";
|
|
4597
5584
|
function isRecord3(value) {
|
|
@@ -4602,25 +5589,25 @@ function stringValue(value) {
|
|
|
4602
5589
|
}
|
|
4603
5590
|
function helpText2() {
|
|
4604
5591
|
return [
|
|
4605
|
-
|
|
5592
|
+
accent5.bold("Agent commands"),
|
|
4606
5593
|
"",
|
|
4607
|
-
` ${
|
|
4608
|
-
` ${
|
|
4609
|
-
` ${
|
|
4610
|
-
` ${
|
|
4611
|
-
` ${
|
|
4612
|
-
` ${
|
|
4613
|
-
` ${
|
|
4614
|
-
` ${
|
|
5594
|
+
` ${accent5("/new".padEnd(22))} Start a new conversation.`,
|
|
5595
|
+
` ${accent5("/threads".padEnd(22))} List recent conversations.`,
|
|
5596
|
+
` ${accent5("/resume <id>".padEnd(22))} Resume a conversation by ID or prefix.`,
|
|
5597
|
+
` ${accent5("/approve [id]".padEnd(22))} Approve an action left pending.`,
|
|
5598
|
+
` ${accent5("/reject [id]".padEnd(22))} Reject an action left pending.`,
|
|
5599
|
+
` ${accent5("/status".padEnd(22))} Show the active conversation.`,
|
|
5600
|
+
` ${accent5("/clear".padEnd(22))} Clear the terminal.`,
|
|
5601
|
+
` ${accent5("/exit".padEnd(22))} Exit the agent.`,
|
|
4615
5602
|
"",
|
|
4616
|
-
|
|
5603
|
+
chalk8.dim(
|
|
4617
5604
|
"Everything else is sent to the laptop-local Tuned Tensor assistant; known TT commands still run directly in the shell. Proposed changes ask for approval right after the answer."
|
|
4618
5605
|
),
|
|
4619
5606
|
""
|
|
4620
5607
|
].join("\n");
|
|
4621
5608
|
}
|
|
4622
5609
|
function formatThread(thread, activeId) {
|
|
4623
|
-
const active = thread.id === activeId ?
|
|
5610
|
+
const active = thread.id === activeId ? accent5("\u25CF") : chalk8.dim("\u25CB");
|
|
4624
5611
|
const time = thread.last_message_at ?? thread.updated_at ?? thread.created_at;
|
|
4625
5612
|
const date = new Date(time);
|
|
4626
5613
|
const dateLabel = Number.isNaN(date.getTime()) ? "" : date.toLocaleString([], {
|
|
@@ -4629,7 +5616,7 @@ function formatThread(thread, activeId) {
|
|
|
4629
5616
|
hour: "2-digit",
|
|
4630
5617
|
minute: "2-digit"
|
|
4631
5618
|
});
|
|
4632
|
-
return `${active} ${
|
|
5619
|
+
return `${active} ${chalk8.dim(thread.id.slice(0, 8))} ${sanitizeTerminalText(thread.title)} ${chalk8.dim(dateLabel)}`;
|
|
4633
5620
|
}
|
|
4634
5621
|
function actionFromPayload(payload) {
|
|
4635
5622
|
const value = isRecord3(payload.action) ? payload.action : isRecord3(payload.approval) ? payload.approval : payload;
|
|
@@ -4660,14 +5647,14 @@ function formatJson(value) {
|
|
|
4660
5647
|
}
|
|
4661
5648
|
function riskLabel(risk) {
|
|
4662
5649
|
const safe = sanitizeTerminalText(risk);
|
|
4663
|
-
const color = safe === "low" ?
|
|
5650
|
+
const color = safe === "low" ? chalk8.green : safe === "high" ? chalk8.red : chalk8.yellow;
|
|
4664
5651
|
return color(`${safe} risk`);
|
|
4665
5652
|
}
|
|
4666
5653
|
function diffStyle(line) {
|
|
4667
|
-
if (line.startsWith("+++") || line.startsWith("---")) return
|
|
4668
|
-
if (line.startsWith("+")) return
|
|
4669
|
-
if (line.startsWith("-")) return
|
|
4670
|
-
if (line.startsWith("@@")) return
|
|
5654
|
+
if (line.startsWith("+++") || line.startsWith("---")) return chalk8.dim;
|
|
5655
|
+
if (line.startsWith("+")) return chalk8.green;
|
|
5656
|
+
if (line.startsWith("-")) return chalk8.red;
|
|
5657
|
+
if (line.startsWith("@@")) return chalk8.cyan;
|
|
4671
5658
|
return (text2) => text2;
|
|
4672
5659
|
}
|
|
4673
5660
|
function approvalQuestion(action) {
|
|
@@ -4805,7 +5792,7 @@ var TunedTensorAgentSession = class {
|
|
|
4805
5792
|
wrapped.forEach((part, partIndex) => {
|
|
4806
5793
|
if (partIndex > 0) this.io.write("\n");
|
|
4807
5794
|
if (!this.lineOpen || partIndex > 0) {
|
|
4808
|
-
const marker = this.blockStart ? `${
|
|
5795
|
+
const marker = this.blockStart ? `${accent5("\u25CF")} ` : GUTTER;
|
|
4809
5796
|
if (this.blockStart && this.wroteInTurn) this.io.write("\n");
|
|
4810
5797
|
this.blockStart = false;
|
|
4811
5798
|
this.wroteInTurn = true;
|
|
@@ -4822,11 +5809,11 @@ var TunedTensorAgentSession = class {
|
|
|
4822
5809
|
renderAction(action) {
|
|
4823
5810
|
this.flushResponse();
|
|
4824
5811
|
this.endOpenLine();
|
|
4825
|
-
const bar =
|
|
5812
|
+
const bar = chalk8.yellow("\u2502");
|
|
4826
5813
|
const out = [
|
|
4827
5814
|
"",
|
|
4828
|
-
`${GUTTER}${
|
|
4829
|
-
`${GUTTER}${bar} ${
|
|
5815
|
+
`${GUTTER}${chalk8.yellow.bold("\u25C6 Approval needed")} ${chalk8.dim(action.id.slice(0, 8))}`,
|
|
5816
|
+
`${GUTTER}${bar} ${chalk8.bold(sanitizeTerminalText(action.title))} ${chalk8.dim("\xB7")} ${riskLabel(action.risk)}`
|
|
4830
5817
|
];
|
|
4831
5818
|
const width = this.wrapWidth();
|
|
4832
5819
|
const push = (text2, style = (line) => line) => {
|
|
@@ -4835,7 +5822,7 @@ var TunedTensorAgentSession = class {
|
|
|
4835
5822
|
for (const part of parts) out.push(`${GUTTER}${bar} ${style(part)}`);
|
|
4836
5823
|
}
|
|
4837
5824
|
};
|
|
4838
|
-
if (action.summary) push(action.summary,
|
|
5825
|
+
if (action.summary) push(action.summary, chalk8.dim);
|
|
4839
5826
|
if (action.operation === "update_local_spec") {
|
|
4840
5827
|
const preview = action.preview;
|
|
4841
5828
|
out.push(`${GUTTER}${bar}`);
|
|
@@ -4846,7 +5833,7 @@ var TunedTensorAgentSession = class {
|
|
|
4846
5833
|
out.push(`${GUTTER}${bar} ${style(index === 0 ? part : ` ${part}`)}`);
|
|
4847
5834
|
});
|
|
4848
5835
|
}
|
|
4849
|
-
for (const warning of preview?.validation?.warnings ?? []) push(`! ${warning}`,
|
|
5836
|
+
for (const warning of preview?.validation?.warnings ?? []) push(`! ${warning}`, chalk8.yellow);
|
|
4850
5837
|
} else {
|
|
4851
5838
|
const request2 = [action.method, action.path].filter(Boolean).join(" ");
|
|
4852
5839
|
const technical = {
|
|
@@ -4856,14 +5843,14 @@ var TunedTensorAgentSession = class {
|
|
|
4856
5843
|
preview: action.preview ?? null
|
|
4857
5844
|
};
|
|
4858
5845
|
out.push(`${GUTTER}${bar}`);
|
|
4859
|
-
push(formatJson(technical),
|
|
5846
|
+
push(formatJson(technical), chalk8.dim);
|
|
4860
5847
|
}
|
|
4861
5848
|
this.io.write(`${out.join("\n")}
|
|
4862
5849
|
`);
|
|
4863
5850
|
}
|
|
4864
5851
|
writePendingHint() {
|
|
4865
5852
|
this.io.write(
|
|
4866
|
-
|
|
5853
|
+
chalk8.dim(`${GUTTER}Nothing changes until you decide: /approve applies it, /reject discards it.
|
|
4867
5854
|
`)
|
|
4868
5855
|
);
|
|
4869
5856
|
}
|
|
@@ -4880,14 +5867,14 @@ var TunedTensorAgentSession = class {
|
|
|
4880
5867
|
const delta = stringValue(payload.delta) ?? stringValue(payload.text) ?? stringValue(payload.content);
|
|
4881
5868
|
if (!delta) return;
|
|
4882
5869
|
if (this.reasoningActive) return;
|
|
4883
|
-
this.writeStatus(
|
|
5870
|
+
this.writeStatus(`${mascotMark()} ${chalk8.dim("Thinking\u2026")}`, { transient: true });
|
|
4884
5871
|
this.reasoningActive = true;
|
|
4885
5872
|
return;
|
|
4886
5873
|
}
|
|
4887
5874
|
if (event.type === "tool_call") {
|
|
4888
5875
|
this.reasoningActive = false;
|
|
4889
5876
|
const label = toolLabel(payload);
|
|
4890
|
-
this.writeStatus(
|
|
5877
|
+
this.writeStatus(chalk8.dim(`\u25CB ${label}\u2026`), { transient: true });
|
|
4891
5878
|
const id = stringValue(payload.toolUseId);
|
|
4892
5879
|
if (id) this.pendingToolLabels.set(id, label);
|
|
4893
5880
|
else this.pendingUnkeyedToolLabel = label;
|
|
@@ -4900,7 +5887,7 @@ var TunedTensorAgentSession = class {
|
|
|
4900
5887
|
if (id) this.pendingToolLabels.delete(id);
|
|
4901
5888
|
else this.pendingUnkeyedToolLabel = null;
|
|
4902
5889
|
this.writeStatus(
|
|
4903
|
-
`${failed ? errorMark2() : successMark2()} ${
|
|
5890
|
+
`${failed ? errorMark2() : successMark2()} ${chalk8.dim(failed ? `${label} failed` : label)}`
|
|
4904
5891
|
);
|
|
4905
5892
|
return;
|
|
4906
5893
|
}
|
|
@@ -4912,7 +5899,7 @@ var TunedTensorAgentSession = class {
|
|
|
4912
5899
|
return;
|
|
4913
5900
|
}
|
|
4914
5901
|
if (event.type === "action_started") {
|
|
4915
|
-
this.writeStatus(
|
|
5902
|
+
this.writeStatus(chalk8.dim("\u25CB Running approved action\u2026"));
|
|
4916
5903
|
return;
|
|
4917
5904
|
}
|
|
4918
5905
|
if (event.type === "action_result") {
|
|
@@ -4959,7 +5946,7 @@ var TunedTensorAgentSession = class {
|
|
|
4959
5946
|
}
|
|
4960
5947
|
}
|
|
4961
5948
|
if (controller.signal.aborted || result2.status === "cancelled") {
|
|
4962
|
-
this.io.write(
|
|
5949
|
+
this.io.write(chalk8.dim(`${GUTTER}Response stopped.
|
|
4963
5950
|
`));
|
|
4964
5951
|
return null;
|
|
4965
5952
|
}
|
|
@@ -4968,7 +5955,7 @@ var TunedTensorAgentSession = class {
|
|
|
4968
5955
|
this.flushResponse();
|
|
4969
5956
|
this.endOpenLine();
|
|
4970
5957
|
if (controller.signal.aborted) {
|
|
4971
|
-
this.io.write(
|
|
5958
|
+
this.io.write(chalk8.dim(`${GUTTER}Response stopped.
|
|
4972
5959
|
`));
|
|
4973
5960
|
return null;
|
|
4974
5961
|
}
|
|
@@ -5041,13 +6028,13 @@ var TunedTensorAgentSession = class {
|
|
|
5041
6028
|
}
|
|
5042
6029
|
for (const action of actions) {
|
|
5043
6030
|
if (!this.pendingActions.has(action.id)) continue;
|
|
5044
|
-
const label = actions.length > 1 ? `${approvalQuestion(action)} ${
|
|
5045
|
-
const decision = await this.prompter(`${GUTTER}${
|
|
6031
|
+
const label = actions.length > 1 ? `${approvalQuestion(action)} ${chalk8.dim(`(${action.title})`)}` : approvalQuestion(action);
|
|
6032
|
+
const decision = await this.prompter(`${GUTTER}${chalk8.yellow("?")} ${chalk8.bold(label)}`);
|
|
5046
6033
|
try {
|
|
5047
6034
|
if (decision === "approve") await this.approve(action.id, context);
|
|
5048
6035
|
else if (decision === "reject") await this.reject(action.id);
|
|
5049
6036
|
else {
|
|
5050
|
-
this.io.write(
|
|
6037
|
+
this.io.write(chalk8.dim(`${GUTTER}Left pending. Use /approve or /reject when you're ready.
|
|
5051
6038
|
`));
|
|
5052
6039
|
}
|
|
5053
6040
|
} catch (error) {
|
|
@@ -5120,7 +6107,7 @@ var TunedTensorAgentSession = class {
|
|
|
5120
6107
|
if (args2.length > 0) throw new Error("/threads does not accept arguments.");
|
|
5121
6108
|
const threads = await this.options.client.listThreads();
|
|
5122
6109
|
if (threads.length === 0) {
|
|
5123
|
-
this.options.io.write(
|
|
6110
|
+
this.options.io.write(chalk8.dim("No conversations yet.\n"));
|
|
5124
6111
|
return "continue";
|
|
5125
6112
|
}
|
|
5126
6113
|
this.options.io.write(
|
|
@@ -5158,10 +6145,10 @@ var TunedTensorAgentSession = class {
|
|
|
5158
6145
|
if (args2.length > 0) throw new Error("/status does not accept arguments.");
|
|
5159
6146
|
this.options.io.write(
|
|
5160
6147
|
this.thread ? `${formatThread(this.thread, this.thread.id)}
|
|
5161
|
-
` :
|
|
6148
|
+
` : chalk8.dim("New conversation; send a message to create it.\n")
|
|
5162
6149
|
);
|
|
5163
6150
|
this.options.io.write(
|
|
5164
|
-
|
|
6151
|
+
chalk8.dim(`${this.pendingActions.size} pending approval(s).
|
|
5165
6152
|
`)
|
|
5166
6153
|
);
|
|
5167
6154
|
return "continue";
|
|
@@ -5205,10 +6192,10 @@ import {
|
|
|
5205
6192
|
} from "fs/promises";
|
|
5206
6193
|
import {
|
|
5207
6194
|
basename as basename3,
|
|
5208
|
-
dirname as
|
|
6195
|
+
dirname as dirname5,
|
|
5209
6196
|
isAbsolute as isAbsolute4,
|
|
5210
|
-
join as
|
|
5211
|
-
resolve as
|
|
6197
|
+
join as join9,
|
|
6198
|
+
resolve as resolve5
|
|
5212
6199
|
} from "path";
|
|
5213
6200
|
|
|
5214
6201
|
// src/project-spec.ts
|
|
@@ -5227,6 +6214,7 @@ var LOCAL_ONLY_SPEC_KEYS = [
|
|
|
5227
6214
|
"dataset_prebuilt",
|
|
5228
6215
|
"engine",
|
|
5229
6216
|
"foundation",
|
|
6217
|
+
"decision",
|
|
5230
6218
|
"evaluation",
|
|
5231
6219
|
"runtime",
|
|
5232
6220
|
"pipeline"
|
|
@@ -5268,7 +6256,7 @@ function projectCloudSpec(raw) {
|
|
|
5268
6256
|
return projectSpec(raw, CLOUD_SPEC_KEY_SET, canonicalizeHostedSpecBaseModel);
|
|
5269
6257
|
}
|
|
5270
6258
|
function projectLocalSpec(raw) {
|
|
5271
|
-
return projectSpec(raw, LOCAL_SPEC_KEY_SET, canonicalizeSpecBaseModel);
|
|
6259
|
+
return projectSpec(raw, LOCAL_SPEC_KEY_SET, raw.engine === "decision" ? (body) => body : canonicalizeSpecBaseModel);
|
|
5272
6260
|
}
|
|
5273
6261
|
function hasLocalOnlySpecFields(raw) {
|
|
5274
6262
|
return Object.keys(raw).some((key) => LOCAL_ONLY_SPEC_KEY_SET.has(key));
|
|
@@ -5383,6 +6371,7 @@ function renderInit(value) {
|
|
|
5383
6371
|
["Path", text(value.path)],
|
|
5384
6372
|
["Name", text(value.name)],
|
|
5385
6373
|
["ID", text(value.id)],
|
|
6374
|
+
["Engine", text(value.engine)],
|
|
5386
6375
|
["Base model", text(value.base_model)],
|
|
5387
6376
|
["Config", text(value.config_path)]
|
|
5388
6377
|
]);
|
|
@@ -5391,6 +6380,7 @@ function renderValidate(value) {
|
|
|
5391
6380
|
printSuccess("Local behavior spec is valid");
|
|
5392
6381
|
printDetail([
|
|
5393
6382
|
["Input", text(value.input_path)],
|
|
6383
|
+
["Engine", text(value.engine)],
|
|
5394
6384
|
["Config", text(value.config_path)],
|
|
5395
6385
|
["Spec", text(value.behavior_spec_id)],
|
|
5396
6386
|
["Base model", text(value.base_model)],
|
|
@@ -5867,10 +6857,10 @@ function positionalSpecArgument(args2, start, defaultPath) {
|
|
|
5867
6857
|
function selectedSpecArgument(args2, cwd) {
|
|
5868
6858
|
const command = args2[0];
|
|
5869
6859
|
if (["doctor", "validate", "run"].includes(command ?? "")) {
|
|
5870
|
-
return positionalSpecArgument(args2, 1,
|
|
6860
|
+
return positionalSpecArgument(args2, 1, join9(cwd, DEFAULT_SPEC_NAME));
|
|
5871
6861
|
}
|
|
5872
6862
|
if (command === "models" && ["prefetch", "verify-base"].includes(args2[1] ?? "")) {
|
|
5873
|
-
return positionalSpecArgument(args2, 2,
|
|
6863
|
+
return positionalSpecArgument(args2, 2, join9(cwd, DEFAULT_SPEC_NAME));
|
|
5874
6864
|
}
|
|
5875
6865
|
const serves = command === "serve" || command === "models" && args2[1] === "serve";
|
|
5876
6866
|
if (!serves) return null;
|
|
@@ -5883,7 +6873,7 @@ async function pathIsFile(path) {
|
|
|
5883
6873
|
}
|
|
5884
6874
|
function resolveFromCwd(path, cwd) {
|
|
5885
6875
|
if (isAbsolute4(path)) return path;
|
|
5886
|
-
return
|
|
6876
|
+
return resolve5(cwd, path);
|
|
5887
6877
|
}
|
|
5888
6878
|
async function projectSpecArguments(originalArgs, cwd) {
|
|
5889
6879
|
const args2 = [...originalArgs];
|
|
@@ -5946,8 +6936,8 @@ async function projectSpecArguments(originalArgs, cwd) {
|
|
|
5946
6936
|
}
|
|
5947
6937
|
};
|
|
5948
6938
|
}
|
|
5949
|
-
const temporaryPath =
|
|
5950
|
-
|
|
6939
|
+
const temporaryPath = join9(
|
|
6940
|
+
dirname5(sourcePath),
|
|
5951
6941
|
`.${basename3(sourcePath)}.tt-local-${process.pid}-${randomUUID7()}.json`
|
|
5952
6942
|
);
|
|
5953
6943
|
await writeFile4(
|
|
@@ -6109,7 +7099,7 @@ function setupFailure(originalArgs, error) {
|
|
|
6109
7099
|
};
|
|
6110
7100
|
}
|
|
6111
7101
|
async function runLocalCommand(args2, options = {}) {
|
|
6112
|
-
const cwd =
|
|
7102
|
+
const cwd = resolve5(options.cwd ?? process.cwd());
|
|
6113
7103
|
let projected;
|
|
6114
7104
|
try {
|
|
6115
7105
|
const entrypoint = options.entrypoint ?? resolveLocalCliEntrypoint();
|
|
@@ -6155,7 +7145,7 @@ async function executeLocalCommand(args2, options = {}) {
|
|
|
6155
7145
|
}
|
|
6156
7146
|
|
|
6157
7147
|
// src/commands/local.ts
|
|
6158
|
-
import
|
|
7148
|
+
import chalk9 from "chalk";
|
|
6159
7149
|
|
|
6160
7150
|
// src/passthrough.ts
|
|
6161
7151
|
function extractPassthroughOptions(args2, root) {
|
|
@@ -6210,7 +7200,7 @@ function registerLocalCommands(program, runtime) {
|
|
|
6210
7200
|
const runPassthrough = async (forwarded) => {
|
|
6211
7201
|
const root = program.opts();
|
|
6212
7202
|
const passthrough = extractPassthroughOptions(forwarded, root);
|
|
6213
|
-
if (!passthrough.color)
|
|
7203
|
+
if (!passthrough.color) chalk9.level = 0;
|
|
6214
7204
|
const result2 = await runtime.invokeLocal(
|
|
6215
7205
|
passthrough.args.length > 0 ? passthrough.args : ["--help"],
|
|
6216
7206
|
{
|
|
@@ -6309,11 +7299,11 @@ function registerAgentCommands(parent, options) {
|
|
|
6309
7299
|
|
|
6310
7300
|
// src/commands/pipeline.ts
|
|
6311
7301
|
import { existsSync as existsSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
6312
|
-
import { dirname as
|
|
7302
|
+
import { dirname as dirname9, join as join13, resolve as resolve8 } from "path";
|
|
6313
7303
|
|
|
6314
7304
|
// src/local-runtime/project-migration.ts
|
|
6315
7305
|
import { lstat as lstat5, readFile as readFile5, rename as rename3, writeFile as writeFile5, unlink as unlink3 } from "fs/promises";
|
|
6316
|
-
import { dirname as
|
|
7306
|
+
import { dirname as dirname6, join as join10 } from "path";
|
|
6317
7307
|
async function migrateProjectWorkflow(specPath) {
|
|
6318
7308
|
const info = await lstat5(specPath);
|
|
6319
7309
|
if (!info.isFile() || info.isSymbolicLink()) throw new Error("Migration requires a regular spec file.");
|
|
@@ -6324,7 +7314,7 @@ async function migrateProjectWorkflow(specPath) {
|
|
|
6324
7314
|
const files = [];
|
|
6325
7315
|
const sources = /* @__PURE__ */ new Map([[specPath, original]]);
|
|
6326
7316
|
for (const name of ["tunedtensor.pipeline.json", "local-runner.json"]) {
|
|
6327
|
-
const path =
|
|
7317
|
+
const path = join10(dirname6(specPath), name);
|
|
6328
7318
|
const entry = await lstat5(path).catch((error) => {
|
|
6329
7319
|
if (error.code === "ENOENT") return void 0;
|
|
6330
7320
|
throw error;
|
|
@@ -6392,7 +7382,7 @@ async function migrateProjectWorkflow(specPath) {
|
|
|
6392
7382
|
import { createHash as createHash5, randomUUID as randomUUID8 } from "crypto";
|
|
6393
7383
|
import { createReadStream } from "fs";
|
|
6394
7384
|
import { chmod as chmod2, lstat as lstat6, mkdir as mkdir4, open as open3, readdir as readdir4, readFile as readFile6, rename as rename4, rm as rm3 } from "fs/promises";
|
|
6395
|
-
import { basename as basename4, dirname as
|
|
7385
|
+
import { basename as basename4, dirname as dirname7, join as join11, resolve as resolve6 } from "path";
|
|
6396
7386
|
var INSTRUCTION_CORPUS_VERSION = 1;
|
|
6397
7387
|
function isRef(value) {
|
|
6398
7388
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value) && typeof value.from === "string";
|
|
@@ -6421,7 +7411,7 @@ async function hashPath(path) {
|
|
|
6421
7411
|
if (info.isDirectory()) {
|
|
6422
7412
|
for (const name of (await readdir4(path)).sort()) {
|
|
6423
7413
|
hash.update(name);
|
|
6424
|
-
hash.update(await hashPath(
|
|
7414
|
+
hash.update(await hashPath(join11(path, name)));
|
|
6425
7415
|
}
|
|
6426
7416
|
return hash.digest("hex");
|
|
6427
7417
|
}
|
|
@@ -6444,7 +7434,7 @@ async function makePrivateTree(path) {
|
|
|
6444
7434
|
if (info.isDirectory()) {
|
|
6445
7435
|
await chmod2(path, 448);
|
|
6446
7436
|
for (const name of await readdir4(path)) {
|
|
6447
|
-
await makePrivateTree(
|
|
7437
|
+
await makePrivateTree(join11(path, name));
|
|
6448
7438
|
}
|
|
6449
7439
|
return;
|
|
6450
7440
|
}
|
|
@@ -6473,7 +7463,7 @@ async function readRequiredJsonObject(path, description) {
|
|
|
6473
7463
|
return parsed;
|
|
6474
7464
|
}
|
|
6475
7465
|
async function writePrivateTextAtomic(path, content) {
|
|
6476
|
-
const temporary =
|
|
7466
|
+
const temporary = join11(dirname7(path), `.${basename4(path)}.${randomUUID8()}.tmp`);
|
|
6477
7467
|
const file = await open3(temporary, "wx", 384);
|
|
6478
7468
|
let closed = false;
|
|
6479
7469
|
try {
|
|
@@ -6588,8 +7578,8 @@ ${result2.stderr}` : ""}`
|
|
|
6588
7578
|
async function runFoundationPipeline(args2) {
|
|
6589
7579
|
assertFoundationPlanSupported(args2.spec, args2.plan);
|
|
6590
7580
|
assertFoundationSettingsMatch(args2.spec.foundation, args2.plan);
|
|
6591
|
-
const outputDir =
|
|
6592
|
-
args2.outputDir ?? (args2.spec.runtime?.artifactRoot ?
|
|
7581
|
+
const outputDir = resolve6(
|
|
7582
|
+
args2.outputDir ?? (args2.spec.runtime?.artifactRoot ? join11(resolve6(dirname7(args2.specPath), args2.spec.runtime.artifactRoot), "foundation-runs", randomUUID8()) : join11(dirname7(resolve6(args2.specPath)), DEFAULT_FOUNDATION_RUNS_DIR, randomUUID8()))
|
|
6593
7583
|
);
|
|
6594
7584
|
if (args2.gpu && args2.spec.foundation.checkpoint_backup_dir && localPathsOverlap(outputDir, args2.spec.foundation.checkpoint_backup_dir)) {
|
|
6595
7585
|
throw new Error("AWS checkpoint_backup_dir must be separate from the foundation run directory; overlapping backups can overwrite returned checkpoints.");
|
|
@@ -6614,7 +7604,7 @@ async function runFoundationPipeline(args2) {
|
|
|
6614
7604
|
await mkdir4(outputDir, { recursive: true, mode: 448 });
|
|
6615
7605
|
}
|
|
6616
7606
|
await makePrivateTree(outputDir);
|
|
6617
|
-
const workflowPath =
|
|
7607
|
+
const workflowPath = join11(outputDir, "resolved-workflow.json");
|
|
6618
7608
|
const workflow = { spec: args2.spec, plan: args2.plan };
|
|
6619
7609
|
if (outputExists) {
|
|
6620
7610
|
const previous = await readFile6(workflowPath, "utf8").catch((error) => {
|
|
@@ -6647,16 +7637,16 @@ async function runFoundationPipeline(args2) {
|
|
|
6647
7637
|
throw new Error(`Missing ${kind} artifact.`);
|
|
6648
7638
|
};
|
|
6649
7639
|
for (const step of args2.plan.steps) {
|
|
6650
|
-
const stepDir =
|
|
6651
|
-
const output =
|
|
7640
|
+
const stepDir = join11(outputDir, step.id);
|
|
7641
|
+
const output = join11(stepDir, "output");
|
|
6652
7642
|
await mkdir4(stepDir, { recursive: true, mode: 448 });
|
|
6653
7643
|
await chmod2(stepDir, 448);
|
|
6654
7644
|
await mkdir4(output, { recursive: true, mode: 448 });
|
|
6655
7645
|
await chmod2(output, 448);
|
|
6656
|
-
const configPath2 =
|
|
6657
|
-
const logPath =
|
|
6658
|
-
const metricsPath =
|
|
6659
|
-
const completionPath =
|
|
7646
|
+
const configPath2 = join11(stepDir, "config.json");
|
|
7647
|
+
const logPath = join11(stepDir, "step.log");
|
|
7648
|
+
const metricsPath = join11(output, "metrics.json");
|
|
7649
|
+
const completionPath = join11(stepDir, "completion.json");
|
|
6660
7650
|
const fields = stepConfig(step);
|
|
6661
7651
|
let entrypoint;
|
|
6662
7652
|
let config;
|
|
@@ -6677,7 +7667,7 @@ async function runFoundationPipeline(args2) {
|
|
|
6677
7667
|
config = {
|
|
6678
7668
|
tokenizer_dir: tokenizerDir,
|
|
6679
7669
|
output_dir: output,
|
|
6680
|
-
work_dir:
|
|
7670
|
+
work_dir: join11(stepDir, "recovery"),
|
|
6681
7671
|
depth: fields.depth ?? hp.depth,
|
|
6682
7672
|
steps: fields.steps ?? hp.pretrain_steps,
|
|
6683
7673
|
batch_size: fields.batchSize ?? hp.batch_size,
|
|
@@ -6782,15 +7772,15 @@ async function runFoundationPipeline(args2) {
|
|
|
6782
7772
|
const artifacts = {};
|
|
6783
7773
|
const record4 = {};
|
|
6784
7774
|
if (step.uses === "tokenize") {
|
|
6785
|
-
const tokenizerPath =
|
|
7775
|
+
const tokenizerPath = join11(output, "tokenizer.json");
|
|
6786
7776
|
await requireRegularNonemptyFile(tokenizerPath, `Foundation step "${step.id}" tokenizer.json`);
|
|
6787
7777
|
await readRequiredJsonObject(tokenizerPath, `Foundation step "${step.id}" tokenizer.json`);
|
|
6788
7778
|
record4.tokenizer = output;
|
|
6789
7779
|
tokenizerDir = output;
|
|
6790
7780
|
artifacts.tokenizer = await artifactFor(output);
|
|
6791
7781
|
} else if (step.uses === "pretrain" || step.uses === "finetune" || step.uses === "rl") {
|
|
6792
|
-
const modelPath =
|
|
6793
|
-
const modelConfigPath =
|
|
7782
|
+
const modelPath = join11(output, "model.safetensors");
|
|
7783
|
+
const modelConfigPath = join11(output, "config.json");
|
|
6794
7784
|
await requireRegularNonemptyFile(modelPath, `Foundation step "${step.id}" model.safetensors`);
|
|
6795
7785
|
const modelConfig = await readRequiredJsonObject(
|
|
6796
7786
|
modelConfigPath,
|
|
@@ -6805,7 +7795,7 @@ async function runFoundationPipeline(args2) {
|
|
|
6805
7795
|
modelDir = output;
|
|
6806
7796
|
artifacts.model = await artifactFor(output);
|
|
6807
7797
|
} else if (step.uses === "evaluate") {
|
|
6808
|
-
const reportPath2 =
|
|
7798
|
+
const reportPath2 = join11(output, "report.json");
|
|
6809
7799
|
const stepReport = await readRequiredJsonObject(
|
|
6810
7800
|
reportPath2,
|
|
6811
7801
|
`Foundation step "${step.id}" report.json`
|
|
@@ -6845,7 +7835,7 @@ async function runFoundationPipeline(args2) {
|
|
|
6845
7835
|
spec: args2.specPath,
|
|
6846
7836
|
steps
|
|
6847
7837
|
};
|
|
6848
|
-
const reportPath =
|
|
7838
|
+
const reportPath = join11(outputDir, "report.json");
|
|
6849
7839
|
await writePrivateTextAtomic(reportPath, `${JSON.stringify(report, null, 2)}
|
|
6850
7840
|
`);
|
|
6851
7841
|
return {
|
|
@@ -6856,6 +7846,334 @@ async function runFoundationPipeline(args2) {
|
|
|
6856
7846
|
};
|
|
6857
7847
|
}
|
|
6858
7848
|
|
|
7849
|
+
// src/local-runtime/decision-runner.ts
|
|
7850
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
7851
|
+
import { lstat as lstat7, mkdir as mkdir5, readFile as readFile7, rename as rename5, rm as rm4, writeFile as writeFile6 } from "fs/promises";
|
|
7852
|
+
import { dirname as dirname8, join as join12, resolve as resolve7 } from "path";
|
|
7853
|
+
var PROBABILITY_FLOOR = 1e-6;
|
|
7854
|
+
function assertDecisionPlanSupported(plan) {
|
|
7855
|
+
for (const step of plan.steps) {
|
|
7856
|
+
if (step.uses === "evaluate" && step.with.evaluator !== "behavior") {
|
|
7857
|
+
throw new Error(`Decision runner evaluates with the behavior evaluator; step ${step.id} uses ${step.with.evaluator}.`);
|
|
7858
|
+
}
|
|
7859
|
+
if (!["train", "evaluate", "compare"].includes(step.uses)) {
|
|
7860
|
+
throw new Error(`Decision runner does not implement ${step.uses} (step ${step.id}).`);
|
|
7861
|
+
}
|
|
7862
|
+
}
|
|
7863
|
+
}
|
|
7864
|
+
function decisionQuestion(spec) {
|
|
7865
|
+
return { ...spec.decision, instructions: buildSystemMessage(spec) };
|
|
7866
|
+
}
|
|
7867
|
+
function canonicalLabel(labels, output) {
|
|
7868
|
+
const normalized = output.trim().toLowerCase();
|
|
7869
|
+
const label = labels.find((candidate) => candidate.trim().toLowerCase() === normalized);
|
|
7870
|
+
if (!label) throw new Error(`Example output ${JSON.stringify(output)} is not a decision label (${labels.join(", ")}).`);
|
|
7871
|
+
return label;
|
|
7872
|
+
}
|
|
7873
|
+
async function readPredictions(path, ids, labels) {
|
|
7874
|
+
const rows2 = (await readFile7(path, "utf8")).split("\n").filter((line) => line.trim()).map((line) => JSON.parse(line));
|
|
7875
|
+
if (rows2.length !== ids.length || new Set(rows2.map((row) => row.id)).size !== rows2.length) {
|
|
7876
|
+
throw new Error(`Decision predictions must contain exactly one row per evaluation ID: ${path}`);
|
|
7877
|
+
}
|
|
7878
|
+
for (const row of rows2) validatePrediction(row, labels);
|
|
7879
|
+
const byId = new Map(rows2.map((row) => [row.id, row]));
|
|
7880
|
+
return ids.map((id) => {
|
|
7881
|
+
const row = byId.get(id);
|
|
7882
|
+
if (!row) throw new Error(`Decision predictions are missing row ${id}: ${path}`);
|
|
7883
|
+
if (!labels.includes(row.prediction)) throw new Error(`Decision prediction for ${id} is not a label: ${row.prediction}`);
|
|
7884
|
+
return row;
|
|
7885
|
+
});
|
|
7886
|
+
}
|
|
7887
|
+
function validatePrediction(row, labels) {
|
|
7888
|
+
const values = row?.probabilities;
|
|
7889
|
+
if (!values || Object.keys(values).length !== labels.length || labels.some((label) => !Object.hasOwn(values, label) || !Number.isFinite(values[label]) || values[label] < 0 || values[label] > 1)) {
|
|
7890
|
+
throw new Error("Decision probabilities must contain a finite probability for every label.");
|
|
7891
|
+
}
|
|
7892
|
+
const sum = labels.reduce((total, label) => total + values[label], 0);
|
|
7893
|
+
const maximum = Math.max(...labels.map((label) => values[label]));
|
|
7894
|
+
const sumTolerance = labels.length * 5e-5 + 1e-8;
|
|
7895
|
+
const tolerance = 2e-5;
|
|
7896
|
+
if (Math.abs(sum - 1) > sumTolerance || !labels.includes(row.prediction) || Math.abs(values[row.prediction] - maximum) > tolerance || !Number.isFinite(row.confidence) || Math.abs(row.confidence - maximum) > tolerance || !Number.isFinite(row.latency_ms) || row.latency_ms < 0) {
|
|
7897
|
+
throw new Error("Decision prediction must have normalized probabilities, an argmax label, matching confidence and nonnegative latency.");
|
|
7898
|
+
}
|
|
7899
|
+
}
|
|
7900
|
+
function scoreDecisionPredictions(args2) {
|
|
7901
|
+
if (args2.predictions.length !== args2.examples.length) {
|
|
7902
|
+
throw new Error("Decision prediction count must match the evaluation examples.");
|
|
7903
|
+
}
|
|
7904
|
+
for (const prediction of args2.predictions) validatePrediction(prediction, args2.labels);
|
|
7905
|
+
const perLabel = Object.fromEntries(
|
|
7906
|
+
args2.labels.map((label) => [label, { support: 0, correct: 0, recall: 0 }])
|
|
7907
|
+
);
|
|
7908
|
+
let logLoss = 0;
|
|
7909
|
+
let brier = 0;
|
|
7910
|
+
let confidence = 0;
|
|
7911
|
+
const results = args2.examples.map((example, index) => {
|
|
7912
|
+
const prediction = args2.predictions[index];
|
|
7913
|
+
const passed = prediction.prediction === example.output;
|
|
7914
|
+
const expectedProbability = prediction.probabilities[example.output] ?? 0;
|
|
7915
|
+
logLoss -= Math.log(Math.max(expectedProbability, PROBABILITY_FLOOR));
|
|
7916
|
+
brier += args2.labels.reduce((sum, label) => {
|
|
7917
|
+
const target = label === example.output ? 1 : 0;
|
|
7918
|
+
return sum + ((prediction.probabilities[label] ?? 0) - target) ** 2;
|
|
7919
|
+
}, 0);
|
|
7920
|
+
confidence += prediction.confidence;
|
|
7921
|
+
const entry = perLabel[example.output];
|
|
7922
|
+
entry.support += 1;
|
|
7923
|
+
if (passed) entry.correct += 1;
|
|
7924
|
+
return {
|
|
7925
|
+
prompt: example.input,
|
|
7926
|
+
expected: example.output,
|
|
7927
|
+
actual: prediction.prediction,
|
|
7928
|
+
passed,
|
|
7929
|
+
score: passed ? 1 : 0,
|
|
7930
|
+
reasoning: `p(expected)=${expectedProbability.toFixed(4)}; confidence=${prediction.confidence.toFixed(4)}`,
|
|
7931
|
+
latency_ms: Math.max(0, Math.round(prediction.latency_ms)),
|
|
7932
|
+
scored_by: "exact_match"
|
|
7933
|
+
};
|
|
7934
|
+
});
|
|
7935
|
+
for (const entry of Object.values(perLabel)) entry.recall = entry.support ? entry.correct / entry.support : 0;
|
|
7936
|
+
const total = results.length;
|
|
7937
|
+
const mean = (value) => total ? value / total : 0;
|
|
7938
|
+
const accuracy = mean(results.filter((result2) => result2.passed).length);
|
|
7939
|
+
const report = evalReportSchema.parse({
|
|
7940
|
+
kind: args2.kind,
|
|
7941
|
+
model_id: args2.modelId,
|
|
7942
|
+
total,
|
|
7943
|
+
eval_examples_total: total,
|
|
7944
|
+
eval_examples_used: total,
|
|
7945
|
+
eval_truncated: false,
|
|
7946
|
+
eval_split: "spec_holdout",
|
|
7947
|
+
eval_sample_seed: args2.sampleSeed,
|
|
7948
|
+
avg_score: accuracy,
|
|
7949
|
+
pass_rate: accuracy,
|
|
7950
|
+
exact_match_rate: accuracy,
|
|
7951
|
+
avg_latency_ms: Math.round(mean(results.reduce((sum, result2) => sum + result2.latency_ms, 0))),
|
|
7952
|
+
results,
|
|
7953
|
+
artifact_uri: fileUri(args2.outputPath),
|
|
7954
|
+
scoring_method: "exact_match",
|
|
7955
|
+
scoring_mode: "exact_match"
|
|
7956
|
+
});
|
|
7957
|
+
return {
|
|
7958
|
+
report,
|
|
7959
|
+
metrics: {
|
|
7960
|
+
accuracy,
|
|
7961
|
+
log_loss: mean(logLoss),
|
|
7962
|
+
brier: mean(brier),
|
|
7963
|
+
mean_confidence: mean(confidence),
|
|
7964
|
+
per_label: perLabel
|
|
7965
|
+
}
|
|
7966
|
+
};
|
|
7967
|
+
}
|
|
7968
|
+
async function defaultDecisionSpawn(args2) {
|
|
7969
|
+
const launched = buildDecisionPythonCommand(args2.entrypoint, ["--config", args2.configPath]);
|
|
7970
|
+
const result2 = await runLoggedProcess({
|
|
7971
|
+
command: launched.command,
|
|
7972
|
+
commandArgs: launched.commandArgs,
|
|
7973
|
+
env: withDecisionPythonEnvironment(process.env),
|
|
7974
|
+
logPath: args2.logPath,
|
|
7975
|
+
stage: args2.stepId,
|
|
7976
|
+
appendLog: true,
|
|
7977
|
+
onLine: (line, stream) => {
|
|
7978
|
+
if (stream !== "stdout" || !args2.onProgress) return;
|
|
7979
|
+
try {
|
|
7980
|
+
const event = JSON.parse(line);
|
|
7981
|
+
if (event.event === "step") {
|
|
7982
|
+
args2.onProgress(`${args2.stepId}: step ${event.step}/${event.total_steps} (epoch ${event.epoch}) loss ${event.loss}`);
|
|
7983
|
+
}
|
|
7984
|
+
} catch {
|
|
7985
|
+
}
|
|
7986
|
+
}
|
|
7987
|
+
});
|
|
7988
|
+
if (result2.exitCode !== 0) {
|
|
7989
|
+
throw new Error(
|
|
7990
|
+
`Decision step "${args2.stepId}" failed (exit ${result2.exitCode}). Log: ${args2.logPath}${result2.stderr ? `
|
|
7991
|
+
${result2.stderr}` : ""}`
|
|
7992
|
+
);
|
|
7993
|
+
}
|
|
7994
|
+
}
|
|
7995
|
+
async function requireRegularNonemptyFile2(path, description) {
|
|
7996
|
+
const info = await lstat7(path).catch(() => null);
|
|
7997
|
+
if (!info || info.isSymbolicLink() || !info.isFile() || info.size === 0) {
|
|
7998
|
+
throw new Error(`${description} must be a non-empty regular file: ${path}`);
|
|
7999
|
+
}
|
|
8000
|
+
}
|
|
8001
|
+
async function readJsonObject2(path, description) {
|
|
8002
|
+
const parsed = JSON.parse(await readFile7(path, "utf8").catch((error) => {
|
|
8003
|
+
throw new Error(`${description} is missing or unreadable: ${path}`, { cause: error });
|
|
8004
|
+
}));
|
|
8005
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
8006
|
+
throw new Error(`${description} must be a JSON object: ${path}`);
|
|
8007
|
+
}
|
|
8008
|
+
return parsed;
|
|
8009
|
+
}
|
|
8010
|
+
async function writePrivateAtomic(path, content) {
|
|
8011
|
+
await mkdir5(dirname8(path), { recursive: true, mode: 448 });
|
|
8012
|
+
const temporary = `${path}.${process.pid}.${randomUUID9()}.tmp`;
|
|
8013
|
+
try {
|
|
8014
|
+
await writeFile6(temporary, content, { encoding: "utf8", mode: 384, flag: "wx" });
|
|
8015
|
+
await rename5(temporary, path);
|
|
8016
|
+
} finally {
|
|
8017
|
+
await rm4(temporary, { force: true });
|
|
8018
|
+
}
|
|
8019
|
+
}
|
|
8020
|
+
async function writePrivateJson2(path, value) {
|
|
8021
|
+
await writePrivateAtomic(path, `${JSON.stringify(value, null, 2)}
|
|
8022
|
+
`);
|
|
8023
|
+
}
|
|
8024
|
+
function producer(reference) {
|
|
8025
|
+
return reference.slice(0, reference.lastIndexOf("."));
|
|
8026
|
+
}
|
|
8027
|
+
function jsonl(rows2) {
|
|
8028
|
+
return rows2.map((row) => JSON.stringify(row)).join("\n") + "\n";
|
|
8029
|
+
}
|
|
8030
|
+
async function runDecisionPipeline(args2) {
|
|
8031
|
+
assertDecisionPlanSupported(args2.plan);
|
|
8032
|
+
const model = resolveDecisionModel(args2.spec.base_model);
|
|
8033
|
+
const specDir = dirname8(resolve7(args2.specPath));
|
|
8034
|
+
const outputDir = resolve7(args2.outputDir ?? (args2.spec.runtime?.artifactRoot ? join12(resolve7(specDir, args2.spec.runtime.artifactRoot), "decision-runs", randomUUID9()) : join12(specDir, DEFAULT_DECISION_RUNS_DIR, randomUUID9())));
|
|
8035
|
+
if (await lstat7(outputDir).catch(() => null)) {
|
|
8036
|
+
throw new Error(`Decision output directory already exists: ${outputDir}`);
|
|
8037
|
+
}
|
|
8038
|
+
await mkdir5(outputDir, { recursive: true, mode: 448 });
|
|
8039
|
+
const labels = decisionLabels(args2.spec.decision);
|
|
8040
|
+
const question = decisionQuestion(args2.spec);
|
|
8041
|
+
const examples = args2.spec.examples.map((example) => ({
|
|
8042
|
+
input: example.input,
|
|
8043
|
+
output: canonicalLabel(labels, example.output)
|
|
8044
|
+
}));
|
|
8045
|
+
const seed = deriveSampleSeed(args2.spec.id ?? args2.spec.name);
|
|
8046
|
+
const split = splitSpecExamples(examples, seed);
|
|
8047
|
+
if (!split.train.length || !split.holdout.length) {
|
|
8048
|
+
throw new Error("Decision runs need at least one training and one held-out example.");
|
|
8049
|
+
}
|
|
8050
|
+
const hp = args2.spec.hyperparameters ?? {};
|
|
8051
|
+
const device = hp.device ?? "auto";
|
|
8052
|
+
const evalIds = split.holdout.map((_, index) => `eval-${index}`);
|
|
8053
|
+
const dataDir = join12(outputDir, "data");
|
|
8054
|
+
await mkdir5(dataDir, { recursive: true, mode: 448 });
|
|
8055
|
+
const trainPath = join12(dataDir, "train.jsonl");
|
|
8056
|
+
const evalInputsPath = join12(dataDir, "eval-inputs.jsonl");
|
|
8057
|
+
await writePrivateAtomic(trainPath, jsonl(split.train));
|
|
8058
|
+
await writePrivateAtomic(evalInputsPath, jsonl(split.holdout.map((example, index) => ({ id: evalIds[index], input: example.input }))));
|
|
8059
|
+
await writePrivateJson2(join12(outputDir, "resolved-workflow.json"), {
|
|
8060
|
+
spec: args2.spec,
|
|
8061
|
+
plan: args2.plan,
|
|
8062
|
+
model: { id: model.id, revision: model.revision },
|
|
8063
|
+
question,
|
|
8064
|
+
split: { seed, train: split.train.length, eval: split.holdout.length }
|
|
8065
|
+
});
|
|
8066
|
+
const spawnStep = args2.spawnStep ?? defaultDecisionSpawn;
|
|
8067
|
+
const producedModels = /* @__PURE__ */ new Map();
|
|
8068
|
+
const reports = /* @__PURE__ */ new Map();
|
|
8069
|
+
const steps = [];
|
|
8070
|
+
let tunedModelDir;
|
|
8071
|
+
for (const step of args2.plan.steps) {
|
|
8072
|
+
const stepDir = join12(outputDir, step.id);
|
|
8073
|
+
await mkdir5(stepDir, { recursive: true, mode: 448 });
|
|
8074
|
+
const configPath2 = join12(stepDir, "config.json");
|
|
8075
|
+
const logPath = join12(stepDir, "step.log");
|
|
8076
|
+
const metricsPath = join12(stepDir, "metrics.json");
|
|
8077
|
+
if (step.uses === "train") {
|
|
8078
|
+
const modelDir = join12(stepDir, "model");
|
|
8079
|
+
args2.onProgress?.(`${step.id}: fine-tuning ${model.id} on ${split.train.length} examples (log: ${logPath})`);
|
|
8080
|
+
await writePrivateJson2(configPath2, {
|
|
8081
|
+
model: model.id,
|
|
8082
|
+
revision: model.revision,
|
|
8083
|
+
question,
|
|
8084
|
+
train_path: trainPath,
|
|
8085
|
+
output_dir: modelDir,
|
|
8086
|
+
metrics_path: metricsPath,
|
|
8087
|
+
epochs: hp.n_epochs ?? model.defaultEpochs,
|
|
8088
|
+
learning_rate: hp.learning_rate ?? model.defaultLearningRate,
|
|
8089
|
+
batch_size: hp.batch_size ?? model.defaultBatchSize,
|
|
8090
|
+
freeze_encoder: hp.freeze_encoder ?? false,
|
|
8091
|
+
shuffle_options: hp.shuffle_options ?? true,
|
|
8092
|
+
seed: hp.seed ?? 0,
|
|
8093
|
+
device
|
|
8094
|
+
});
|
|
8095
|
+
await spawnStep({ entrypoint: "train.py", configPath: configPath2, logPath, stepId: step.id, onProgress: args2.onProgress });
|
|
8096
|
+
const metrics = await readJsonObject2(metricsPath, `Decision step "${step.id}" metrics`);
|
|
8097
|
+
if (metrics.ok !== true) throw new Error(`Decision step "${step.id}" metrics must report ok: true.`);
|
|
8098
|
+
await requireRegularNonemptyFile2(join12(modelDir, "model.safetensors"), `Decision step "${step.id}" model.safetensors`);
|
|
8099
|
+
await readJsonObject2(join12(modelDir, "rl_agent_config.json"), `Decision step "${step.id}" rl_agent_config.json`);
|
|
8100
|
+
producedModels.set(step.id, modelDir);
|
|
8101
|
+
tunedModelDir = modelDir;
|
|
8102
|
+
steps.push({ id: step.id, uses: step.uses, model: modelDir, training: metrics });
|
|
8103
|
+
continue;
|
|
8104
|
+
}
|
|
8105
|
+
if (step.uses === "evaluate") {
|
|
8106
|
+
const source = step.with.model;
|
|
8107
|
+
const tuned = source !== "base";
|
|
8108
|
+
const modelDir = tuned ? producedModels.get(producer(source.from)) : void 0;
|
|
8109
|
+
if (tuned && !modelDir) throw new Error(`Step ${step.id} needs a model from ${source.from}.`);
|
|
8110
|
+
const predictionsPath = join12(stepDir, "predictions.jsonl");
|
|
8111
|
+
args2.onProgress?.(`${step.id}: evaluating ${tuned ? "tuned" : "base"} model on ${split.holdout.length} held-out examples`);
|
|
8112
|
+
await writePrivateJson2(configPath2, {
|
|
8113
|
+
model: modelDir ?? model.id,
|
|
8114
|
+
...tuned ? {} : { revision: model.revision },
|
|
8115
|
+
question,
|
|
8116
|
+
inputs_path: evalInputsPath,
|
|
8117
|
+
output_path: predictionsPath,
|
|
8118
|
+
metrics_path: metricsPath,
|
|
8119
|
+
device
|
|
8120
|
+
});
|
|
8121
|
+
await spawnStep({ entrypoint: "evaluate.py", configPath: configPath2, logPath, stepId: step.id, onProgress: args2.onProgress });
|
|
8122
|
+
const metrics = await readJsonObject2(metricsPath, `Decision step "${step.id}" metrics`);
|
|
8123
|
+
if (metrics.ok !== true) throw new Error(`Decision step "${step.id}" metrics must report ok: true.`);
|
|
8124
|
+
const reportPath2 = join12(stepDir, "report.json");
|
|
8125
|
+
const scored = scoreDecisionPredictions({
|
|
8126
|
+
kind: tuned ? "candidate" : "baseline",
|
|
8127
|
+
modelId: modelDir ? fileUri(modelDir) : model.id,
|
|
8128
|
+
labels,
|
|
8129
|
+
examples: split.holdout,
|
|
8130
|
+
predictions: await readPredictions(predictionsPath, evalIds, labels),
|
|
8131
|
+
outputPath: reportPath2,
|
|
8132
|
+
sampleSeed: seed
|
|
8133
|
+
});
|
|
8134
|
+
await writePrivateJson2(reportPath2, { ...scored.report, decision_metrics: scored.metrics });
|
|
8135
|
+
reports.set(step.id, scored);
|
|
8136
|
+
steps.push({ id: step.id, uses: step.uses, model: modelDir ?? model.id, report: scored.report, decision_metrics: scored.metrics });
|
|
8137
|
+
continue;
|
|
8138
|
+
}
|
|
8139
|
+
if (step.uses !== "compare") throw new Error(`Decision runner does not implement ${step.uses} (step ${step.id}).`);
|
|
8140
|
+
const before = reports.get(producer(step.with.before.from));
|
|
8141
|
+
const after = reports.get(producer(step.with.after.from));
|
|
8142
|
+
if (!before || !after) throw new Error(`Step ${step.id} needs both evaluation reports.`);
|
|
8143
|
+
const comparison = {
|
|
8144
|
+
...compareEvalReports(before.report, after.report),
|
|
8145
|
+
log_loss_delta: after.metrics.log_loss - before.metrics.log_loss,
|
|
8146
|
+
brier_delta: after.metrics.brier - before.metrics.brier
|
|
8147
|
+
};
|
|
8148
|
+
await writePrivateJson2(join12(stepDir, "comparison.json"), comparison);
|
|
8149
|
+
steps.push({ id: step.id, uses: step.uses, comparison });
|
|
8150
|
+
}
|
|
8151
|
+
const reportPath = join12(outputDir, "report.json");
|
|
8152
|
+
const report = {
|
|
8153
|
+
status: "succeeded",
|
|
8154
|
+
engine: "decision",
|
|
8155
|
+
...args2.plan.name ? { name: args2.plan.name } : {},
|
|
8156
|
+
spec: resolve7(args2.specPath),
|
|
8157
|
+
base_model: model.id,
|
|
8158
|
+
base_model_revision: model.revision,
|
|
8159
|
+
question: { type: question.type, labels },
|
|
8160
|
+
split: { seed, train: split.train.length, eval: split.holdout.length, eval_split: "spec_holdout" },
|
|
8161
|
+
...tunedModelDir ? { model_dir: tunedModelDir } : {},
|
|
8162
|
+
steps,
|
|
8163
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
8164
|
+
};
|
|
8165
|
+
await writePrivateJson2(reportPath, report);
|
|
8166
|
+
return {
|
|
8167
|
+
status: "succeeded",
|
|
8168
|
+
engine: "decision",
|
|
8169
|
+
...args2.plan.name ? { name: args2.plan.name } : {},
|
|
8170
|
+
run_dir: outputDir,
|
|
8171
|
+
report_path: reportPath,
|
|
8172
|
+
...tunedModelDir ? { model_dir: tunedModelDir } : {},
|
|
8173
|
+
steps
|
|
8174
|
+
};
|
|
8175
|
+
}
|
|
8176
|
+
|
|
6859
8177
|
// src/commands/pipeline.ts
|
|
6860
8178
|
var DEFAULT_SPEC_FILE = "tunedtensor.json";
|
|
6861
8179
|
function parseList(value) {
|
|
@@ -6865,7 +8183,7 @@ function parseList(value) {
|
|
|
6865
8183
|
return ids;
|
|
6866
8184
|
}
|
|
6867
8185
|
function loadPipelineFile(path) {
|
|
6868
|
-
const resolved =
|
|
8186
|
+
const resolved = resolve8(path);
|
|
6869
8187
|
if (!existsSync4(resolved)) throw new Error(`Pipeline file not found: ${path}. Run \`tt pipeline init\` to create one.`);
|
|
6870
8188
|
try {
|
|
6871
8189
|
return JSON.parse(readFileSync5(resolved, "utf8"));
|
|
@@ -6877,7 +8195,7 @@ function pipelineFromSpec(input) {
|
|
|
6877
8195
|
return pipelineFromFoundationHyperparameters(input.spec.name, input.spec.foundation);
|
|
6878
8196
|
}
|
|
6879
8197
|
function resolvePipelineDocument(options, specRequired) {
|
|
6880
|
-
const specPath =
|
|
8198
|
+
const specPath = resolve8(options.spec);
|
|
6881
8199
|
const requested = options.file ? loadPipelineFile(options.file) : void 0;
|
|
6882
8200
|
if (!existsSync4(specPath)) {
|
|
6883
8201
|
if (specRequired) throw new Error(`Behavior spec not found: ${options.spec}.`);
|
|
@@ -6890,10 +8208,14 @@ function resolvePipelineDocument(options, specRequired) {
|
|
|
6890
8208
|
const spec = {
|
|
6891
8209
|
path: specPath,
|
|
6892
8210
|
sha256: specHash(source),
|
|
6893
|
-
engine: input
|
|
8211
|
+
engine: specEngine(input)
|
|
6894
8212
|
};
|
|
6895
8213
|
return { document, input, spec };
|
|
6896
8214
|
}
|
|
8215
|
+
function specEngine(input) {
|
|
8216
|
+
if (input.kind === "foundation-spec") return "foundation";
|
|
8217
|
+
return input.kind === "decision-spec" ? "decision" : "adapter";
|
|
8218
|
+
}
|
|
6897
8219
|
function outputPlan(plan, hostWarnings = []) {
|
|
6898
8220
|
if (isJsonMode()) {
|
|
6899
8221
|
return printJson(hostWarnings.length ? { ...plan, host_warnings: hostWarnings } : plan);
|
|
@@ -6910,15 +8232,34 @@ function outputPlan(plan, hostWarnings = []) {
|
|
|
6910
8232
|
async function hostWarningsForPipeline(document, input) {
|
|
6911
8233
|
const snapshot = await readHardwareSnapshot();
|
|
6912
8234
|
if (!snapshot) return [];
|
|
8235
|
+
if (input?.kind === "decision-spec") return warningsFromSnapshot(snapshot.capabilities, { engine: "decision" });
|
|
6913
8236
|
const engine = isFoundationPipeline(parsePipeline(document)) ? "foundation" : "adapter";
|
|
6914
8237
|
const baseModel = input && input.kind !== "foundation-spec" ? input.request.spec_snapshot.base_model : void 0;
|
|
6915
8238
|
return warningsFromSnapshot(snapshot.capabilities, { engine, baseModel });
|
|
6916
8239
|
}
|
|
8240
|
+
function percent(value) {
|
|
8241
|
+
return `${(value * 100).toFixed(1)}%`;
|
|
8242
|
+
}
|
|
8243
|
+
function printDecisionSummary(result2) {
|
|
8244
|
+
for (const step of result2.steps) {
|
|
8245
|
+
if (step.decision_metrics) {
|
|
8246
|
+
const metrics = step.decision_metrics;
|
|
8247
|
+
console.log(`${step.id.padEnd(12)} accuracy ${percent(metrics.accuracy)} log-loss ${metrics.log_loss.toFixed(3)} brier ${metrics.brier.toFixed(3)} (${step.report?.total ?? 0} held-out)`);
|
|
8248
|
+
}
|
|
8249
|
+
if (step.comparison) {
|
|
8250
|
+
const comparison = step.comparison;
|
|
8251
|
+
const sign = (value) => value >= 0 ? "+" : "";
|
|
8252
|
+
console.log(`${step.id.padEnd(12)} accuracy ${sign(comparison.pass_rate_delta)}${percent(comparison.pass_rate_delta)} log-loss ${sign(comparison.log_loss_delta)}${comparison.log_loss_delta.toFixed(3)} improved ${comparison.improvements}, regressed ${comparison.regressions}`);
|
|
8253
|
+
}
|
|
8254
|
+
}
|
|
8255
|
+
if (result2.model_dir) console.log(`Tuned decision model: ${result2.model_dir}`);
|
|
8256
|
+
printSuccess(`Decision pipeline completed. Report: ${result2.report_path}`);
|
|
8257
|
+
}
|
|
6917
8258
|
function registerPipelineCommands(parent) {
|
|
6918
8259
|
const pipeline2 = parent.command("pipeline").description("Create, validate, inspect, and safely dry-run ordered pipeline recipes");
|
|
6919
8260
|
pipeline2.command("init").description("Write a canonical v1 pipeline recipe").option("-f, --file <path>", "Legacy standalone recipe output (default: embed in tunedtensor.json)").option("--engine <engine>", "adapter (default) or foundation").option("--spec <path>", "Foundation spec whose hyperparameters stamp the DAG").action(async (options) => {
|
|
6920
8261
|
if (!options.file) {
|
|
6921
|
-
const path2 =
|
|
8262
|
+
const path2 = resolve8(options.spec ?? DEFAULT_SPEC_FILE);
|
|
6922
8263
|
const raw = JSON.parse(readFileSync5(path2, "utf8"));
|
|
6923
8264
|
const input = parseLocalRunInput(raw, path2);
|
|
6924
8265
|
if (input.kind === "request") throw new Error("Pipeline init requires a behavior spec.");
|
|
@@ -6937,12 +8278,12 @@ function registerPipelineCommands(parent) {
|
|
|
6937
8278
|
if (engine !== "foundation" && options.spec) {
|
|
6938
8279
|
throw new Error("--spec is only valid with --engine foundation.");
|
|
6939
8280
|
}
|
|
6940
|
-
const path =
|
|
8281
|
+
const path = resolve8(options.file);
|
|
6941
8282
|
if (existsSync4(path)) throw new Error(`Pipeline file already exists: ${options.file}.`);
|
|
6942
8283
|
let recipe;
|
|
6943
8284
|
if (engine === "foundation") {
|
|
6944
8285
|
if (options.spec) {
|
|
6945
|
-
const input = await loadLocalRunInput(
|
|
8286
|
+
const input = await loadLocalRunInput(resolve8(options.spec));
|
|
6946
8287
|
if (input.kind !== "foundation-spec") {
|
|
6947
8288
|
throw new Error("--spec must be a foundation tunedtensor.json.");
|
|
6948
8289
|
}
|
|
@@ -6959,7 +8300,7 @@ function registerPipelineCommands(parent) {
|
|
|
6959
8300
|
printSuccess(`Created ${options.file}`);
|
|
6960
8301
|
});
|
|
6961
8302
|
pipeline2.command("migrate").description("Move legacy configuration into tunedtensor.json, retaining .bak copies").option("--spec <path>", "Local behavior spec", DEFAULT_SPEC_FILE).action(async (options) => {
|
|
6962
|
-
const result2 = await migrateProjectWorkflow(
|
|
8303
|
+
const result2 = await migrateProjectWorkflow(resolve8(options.spec));
|
|
6963
8304
|
if (isJsonMode()) return printJson(result2);
|
|
6964
8305
|
printSuccess(result2.migrated ? `Migrated ${result2.path}; originals retained as .bak files.` : "No legacy configuration found.");
|
|
6965
8306
|
});
|
|
@@ -6983,7 +8324,7 @@ function registerPipelineCommands(parent) {
|
|
|
6983
8324
|
const plan = createExecutionPlan(document, { only: parseList(options.only), skip: parseList(options.skip) });
|
|
6984
8325
|
outputPlan({ ...plan, ...spec ? { spec } : {} }, await hostWarningsForPipeline(document, input));
|
|
6985
8326
|
});
|
|
6986
|
-
pipeline2.command("run").description("Run an ordered local pipeline, or safely preview any pipeline").option("--dry-run", "Resolve and display only; never execute, transfer, or reserve credits").option("-f, --file <path>", "Explicit pipeline recipe (default: derive from behavior spec)").option("--spec <path>", "Local behavior spec", DEFAULT_SPEC_FILE).option("--config <path>", "Local runtime config").option("--output <path>", "Foundation run directory (must not already exist)").option("--resume <path>", "Resume a foundation run directory").option("--only <ids>", "Comma-separated step IDs to include").option("--skip <ids>", "Comma-separated step IDs to omit").action(async (options, command) => {
|
|
8327
|
+
pipeline2.command("run").description("Run an ordered local pipeline, or safely preview any pipeline").option("--dry-run", "Resolve and display only; never execute, transfer, or reserve credits").option("-f, --file <path>", "Explicit pipeline recipe (default: derive from behavior spec)").option("--spec <path>", "Local behavior spec", DEFAULT_SPEC_FILE).option("--config <path>", "Local runtime config").option("--output <path>", "Foundation or decision run directory (must not already exist)").option("--resume <path>", "Resume a foundation run directory").option("--only <ids>", "Comma-separated step IDs to include").option("--skip <ids>", "Comma-separated step IDs to omit").action(async (options, command) => {
|
|
6987
8328
|
if (options.output && options.resume) {
|
|
6988
8329
|
throw new Error("--output and --resume are mutually exclusive.");
|
|
6989
8330
|
}
|
|
@@ -7016,8 +8357,8 @@ function registerPipelineCommands(parent) {
|
|
|
7016
8357
|
const result3 = await runFoundationPipeline({
|
|
7017
8358
|
spec: input.spec,
|
|
7018
8359
|
plan,
|
|
7019
|
-
specPath:
|
|
7020
|
-
...options.output || options.resume ? { outputDir:
|
|
8360
|
+
specPath: resolve8(options.spec),
|
|
8361
|
+
...options.output || options.resume ? { outputDir: resolve8(options.resume ?? options.output) } : {},
|
|
7021
8362
|
resume: Boolean(options.resume),
|
|
7022
8363
|
gpu: config.gpu
|
|
7023
8364
|
});
|
|
@@ -7025,8 +8366,21 @@ function registerPipelineCommands(parent) {
|
|
|
7025
8366
|
printSuccess(`Foundation pipeline completed. Report: ${result3.report_path}`);
|
|
7026
8367
|
return;
|
|
7027
8368
|
}
|
|
8369
|
+
if (input.kind === "decision-spec") {
|
|
8370
|
+
if (options.resume) throw new Error("--resume is only valid for foundation pipelines.");
|
|
8371
|
+
const result3 = await runDecisionPipeline({
|
|
8372
|
+
spec: input.spec,
|
|
8373
|
+
plan,
|
|
8374
|
+
specPath: resolve8(options.spec),
|
|
8375
|
+
...options.output ? { outputDir: resolve8(options.output) } : {},
|
|
8376
|
+
onProgress: isJsonMode() ? void 0 : (message) => console.error(message)
|
|
8377
|
+
});
|
|
8378
|
+
if (isJsonMode()) return printJson({ ...result3, spec });
|
|
8379
|
+
printDecisionSummary(result3);
|
|
8380
|
+
return;
|
|
8381
|
+
}
|
|
7028
8382
|
if (options.output || options.resume) {
|
|
7029
|
-
throw new Error("--output and --resume
|
|
8383
|
+
throw new Error("--output is only valid for foundation and decision pipelines; --resume only for foundation.");
|
|
7030
8384
|
}
|
|
7031
8385
|
const localPipeline = {
|
|
7032
8386
|
version: 1,
|
|
@@ -7043,7 +8397,7 @@ function registerPipelineCommands(parent) {
|
|
|
7043
8397
|
import { createInterface as createInterface3 } from "readline/promises";
|
|
7044
8398
|
import { stdin as stdin2, stdout as stdout2 } from "process";
|
|
7045
8399
|
import { Writable as Writable2 } from "stream";
|
|
7046
|
-
import
|
|
8400
|
+
import chalk10 from "chalk";
|
|
7047
8401
|
async function promptForApiKey(input = stdin2, output = stdout2) {
|
|
7048
8402
|
if (input.isTTY !== true || output.isTTY !== true) {
|
|
7049
8403
|
throw new Error(
|
|
@@ -7085,9 +8439,9 @@ function registerAuthCommands(parent) {
|
|
|
7085
8439
|
const settingsUrl = `${getBaseUrl(parent.optsWithGlobals()).replace(/\/$/, "")}/dashboard/settings`;
|
|
7086
8440
|
console.log();
|
|
7087
8441
|
console.log(
|
|
7088
|
-
` To get your TT access token, go to ${
|
|
8442
|
+
` To get your TT access token, go to ${chalk10.bold("Settings \u2192 API Keys")} in the Tuned Tensor dashboard:`
|
|
7089
8443
|
);
|
|
7090
|
-
console.log(` ${
|
|
8444
|
+
console.log(` ${chalk10.hex("#A78BFA").underline(settingsUrl)}`);
|
|
7091
8445
|
console.log();
|
|
7092
8446
|
apiKey = await promptForApiKey();
|
|
7093
8447
|
}
|
|
@@ -7143,7 +8497,7 @@ function registerAuthCommands(parent) {
|
|
|
7143
8497
|
}
|
|
7144
8498
|
|
|
7145
8499
|
// src/commands/balance.ts
|
|
7146
|
-
import
|
|
8500
|
+
import chalk11 from "chalk";
|
|
7147
8501
|
var KIND_LABELS = {
|
|
7148
8502
|
signup_bonus: "Signup bonus",
|
|
7149
8503
|
topup: "Top-up",
|
|
@@ -7158,8 +8512,8 @@ function formatCents(cents) {
|
|
|
7158
8512
|
return `${sign}$${(abs / 100).toFixed(2)}`;
|
|
7159
8513
|
}
|
|
7160
8514
|
function formatSigned(cents) {
|
|
7161
|
-
if (cents > 0) return
|
|
7162
|
-
if (cents < 0) return
|
|
8515
|
+
if (cents > 0) return chalk11.green(`+${formatCents(cents)}`);
|
|
8516
|
+
if (cents < 0) return chalk11.red(formatCents(cents));
|
|
7163
8517
|
return formatCents(cents);
|
|
7164
8518
|
}
|
|
7165
8519
|
function registerBalanceCommands(parent) {
|
|
@@ -7174,7 +8528,7 @@ function registerBalanceCommands(parent) {
|
|
|
7174
8528
|
return printJson({ balance, transactions });
|
|
7175
8529
|
}
|
|
7176
8530
|
const lowBalance = balance.balance_cents < 100;
|
|
7177
|
-
const balanceLine = lowBalance ?
|
|
8531
|
+
const balanceLine = lowBalance ? chalk11.red(formatCents(balance.balance_cents)) + chalk11.dim(" (low)") : chalk11.bold.green(formatCents(balance.balance_cents));
|
|
7178
8532
|
printDetail([
|
|
7179
8533
|
["Credits", balanceLine],
|
|
7180
8534
|
[
|
|
@@ -7189,7 +8543,7 @@ function registerBalanceCommands(parent) {
|
|
|
7189
8543
|
]);
|
|
7190
8544
|
if (transactions && transactions.length > 0) {
|
|
7191
8545
|
console.log(`
|
|
7192
|
-
${
|
|
8546
|
+
${chalk11.bold("Recent transactions")}`);
|
|
7193
8547
|
printTable(
|
|
7194
8548
|
["Date", "Type", "Amount", "Balance", "Reference"],
|
|
7195
8549
|
transactions.map((row) => [
|
|
@@ -7197,19 +8551,19 @@ ${chalk8.bold("Recent transactions")}`);
|
|
|
7197
8551
|
KIND_LABELS[row.kind] ?? row.kind,
|
|
7198
8552
|
formatSigned(row.amount_cents),
|
|
7199
8553
|
formatCents(row.balance_after_cents),
|
|
7200
|
-
row.reference_id ? shortId(row.reference_id) :
|
|
8554
|
+
row.reference_id ? shortId(row.reference_id) : chalk11.dim("\u2014")
|
|
7201
8555
|
])
|
|
7202
8556
|
);
|
|
7203
8557
|
} else {
|
|
7204
8558
|
console.log(
|
|
7205
8559
|
`
|
|
7206
|
-
${
|
|
8560
|
+
${chalk11.dim("No transactions yet \u2014 run `tt topup` to add credits.")}`
|
|
7207
8561
|
);
|
|
7208
8562
|
}
|
|
7209
8563
|
if (lowBalance) {
|
|
7210
8564
|
console.log(
|
|
7211
8565
|
`
|
|
7212
|
-
${formatStatus("preparing")} ${
|
|
8566
|
+
${formatStatus("preparing")} ${chalk11.yellow(
|
|
7213
8567
|
"Run `tt topup` to add more credits before starting a run."
|
|
7214
8568
|
)}`
|
|
7215
8569
|
);
|
|
@@ -7218,7 +8572,7 @@ ${formatStatus("preparing")} ${chalk8.yellow(
|
|
|
7218
8572
|
}
|
|
7219
8573
|
|
|
7220
8574
|
// src/commands/topup.ts
|
|
7221
|
-
import
|
|
8575
|
+
import chalk12 from "chalk";
|
|
7222
8576
|
import open4 from "open";
|
|
7223
8577
|
var PRESETS_USD = [10, 25, 50, 100];
|
|
7224
8578
|
var MIN_USD = 5;
|
|
@@ -7257,9 +8611,9 @@ function registerTopupCommands(parent) {
|
|
|
7257
8611
|
"amount is required in --json mode (use --amount <usd>)"
|
|
7258
8612
|
);
|
|
7259
8613
|
}
|
|
7260
|
-
console.log(
|
|
8614
|
+
console.log(chalk12.bold("Add credits via Stripe Checkout"));
|
|
7261
8615
|
console.log(
|
|
7262
|
-
|
|
8616
|
+
chalk12.dim(
|
|
7263
8617
|
`Quick picks: ${PRESETS_USD.map((n) => `$${n}`).join(", ")}`
|
|
7264
8618
|
)
|
|
7265
8619
|
);
|
|
@@ -7286,8 +8640,8 @@ function registerTopupCommands(parent) {
|
|
|
7286
8640
|
}
|
|
7287
8641
|
const url = data3.checkout_url;
|
|
7288
8642
|
console.log(
|
|
7289
|
-
`${
|
|
7290
|
-
` +
|
|
8643
|
+
`${chalk12.bold("Checkout URL:")} ${chalk12.hex("#A78BFA")(url)}
|
|
8644
|
+
` + chalk12.dim("Complete the payment to credit your account.")
|
|
7291
8645
|
);
|
|
7292
8646
|
if (options.open !== false) {
|
|
7293
8647
|
try {
|
|
@@ -7295,7 +8649,7 @@ function registerTopupCommands(parent) {
|
|
|
7295
8649
|
printSuccess("Opened Stripe Checkout in your browser.");
|
|
7296
8650
|
} catch {
|
|
7297
8651
|
console.log(
|
|
7298
|
-
|
|
8652
|
+
chalk12.yellow(
|
|
7299
8653
|
"Could not open browser automatically \u2014 copy the URL above."
|
|
7300
8654
|
)
|
|
7301
8655
|
);
|
|
@@ -7307,21 +8661,21 @@ function registerTopupCommands(parent) {
|
|
|
7307
8661
|
// src/commands/publish.ts
|
|
7308
8662
|
import { createInterface as createInterface4 } from "readline/promises";
|
|
7309
8663
|
import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
|
|
7310
|
-
import { homedir as
|
|
7311
|
-
import { isAbsolute as isAbsolute5, join as
|
|
8664
|
+
import { homedir as homedir3 } from "os";
|
|
8665
|
+
import { isAbsolute as isAbsolute5, join as join14, resolve as resolve9 } from "path";
|
|
7312
8666
|
import { stdin as defaultStdin, stdout as defaultStdout } from "process";
|
|
7313
8667
|
var MAX_PUBLISH_BYTES = 2 * 1024 * 1024;
|
|
7314
8668
|
function expandPath2(value, baseDirectory, homeDirectory) {
|
|
7315
8669
|
if (value === "~") return homeDirectory;
|
|
7316
|
-
if (value.startsWith("~/")) return
|
|
7317
|
-
return isAbsolute5(value) ?
|
|
8670
|
+
if (value.startsWith("~/")) return resolve9(homeDirectory, value.slice(2));
|
|
8671
|
+
return isAbsolute5(value) ? resolve9(value) : resolve9(baseDirectory, value);
|
|
7318
8672
|
}
|
|
7319
8673
|
function resolvePublishStoreRoot(options) {
|
|
7320
8674
|
const cwd = options?.cwd ?? process.cwd();
|
|
7321
8675
|
const env = options?.env ?? process.env;
|
|
7322
|
-
const homeDirectory = options?.homeDirectory ?? (env.HOME ?
|
|
7323
|
-
const localConfigPath =
|
|
7324
|
-
const projectPath =
|
|
8676
|
+
const homeDirectory = options?.homeDirectory ?? (env.HOME ? resolve9(env.HOME) : homedir3());
|
|
8677
|
+
const localConfigPath = join14(cwd, "local-runner.json");
|
|
8678
|
+
const projectPath = join14(cwd, "tunedtensor.json");
|
|
7325
8679
|
if (existsSync5(projectPath)) {
|
|
7326
8680
|
const spec = localBehaviorSpecFileSchema.parse(JSON.parse(readFileSync6(projectPath, "utf8")));
|
|
7327
8681
|
if (existsSync5(localConfigPath) && (spec.runtime || spec.evaluation)) throw new Error("Conflicting configuration sources; migrate local-runner.json first.");
|
|
@@ -8034,7 +9388,7 @@ function formatCents2(cents) {
|
|
|
8034
9388
|
return `$${(cents / 100).toFixed(2)}`;
|
|
8035
9389
|
}
|
|
8036
9390
|
function sleep(ms) {
|
|
8037
|
-
return new Promise((
|
|
9391
|
+
return new Promise((resolve13) => setTimeout(resolve13, ms));
|
|
8038
9392
|
}
|
|
8039
9393
|
function parseCsvHeader(line) {
|
|
8040
9394
|
const fields = [];
|
|
@@ -8876,17 +10230,17 @@ import {
|
|
|
8876
10230
|
readdirSync,
|
|
8877
10231
|
rmSync
|
|
8878
10232
|
} from "fs";
|
|
8879
|
-
import { homedir as
|
|
8880
|
-
import { dirname as
|
|
10233
|
+
import { homedir as homedir4 } from "os";
|
|
10234
|
+
import { dirname as dirname11, join as join15, resolve as resolve11, basename as basename5, normalize, isAbsolute as isAbsolute6 } from "path";
|
|
8881
10235
|
import { Readable, Transform } from "stream";
|
|
8882
10236
|
import { pipeline } from "stream/promises";
|
|
8883
10237
|
|
|
8884
10238
|
// src/commands/init.ts
|
|
8885
10239
|
import { existsSync as existsSync8, readFileSync as readFileSync10, writeFileSync as writeFileSync4 } from "fs";
|
|
8886
|
-
import { resolve as
|
|
10240
|
+
import { resolve as resolve10 } from "path";
|
|
8887
10241
|
var DEFAULT_SPEC_FILE2 = "tunedtensor.json";
|
|
8888
10242
|
function loadSpec(filePath) {
|
|
8889
|
-
const resolved =
|
|
10243
|
+
const resolved = resolve10(filePath || DEFAULT_SPEC_FILE2);
|
|
8890
10244
|
if (!existsSync8(resolved)) {
|
|
8891
10245
|
printError(
|
|
8892
10246
|
`Spec file not found: ${filePath || DEFAULT_SPEC_FILE2}
|
|
@@ -8900,23 +10254,23 @@ Run \`tt init\` to create one.`
|
|
|
8900
10254
|
|
|
8901
10255
|
// src/serve-manager.ts
|
|
8902
10256
|
import { spawn as spawn2 } from "child_process";
|
|
8903
|
-
import { randomUUID as
|
|
10257
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
8904
10258
|
import { appendFileSync, mkdirSync as mkdirSync3 } from "fs";
|
|
8905
10259
|
import { createServer } from "http";
|
|
8906
10260
|
import { createServer as createNetServer } from "net";
|
|
8907
|
-
import { dirname as
|
|
10261
|
+
import { dirname as dirname10 } from "path";
|
|
8908
10262
|
var COMPLETION_ROUTES = /* @__PURE__ */ new Set(["/v1/chat/completions", "/chat/completions"]);
|
|
8909
10263
|
function parsePositiveInteger(value, fallback) {
|
|
8910
10264
|
return Number.isInteger(value) && value >= 0 ? value : fallback;
|
|
8911
10265
|
}
|
|
8912
10266
|
function sleep2(ms) {
|
|
8913
|
-
return new Promise((
|
|
10267
|
+
return new Promise((resolve13) => setTimeout(resolve13, ms));
|
|
8914
10268
|
}
|
|
8915
10269
|
function readRequestBody(req) {
|
|
8916
|
-
return new Promise((
|
|
10270
|
+
return new Promise((resolve13, reject) => {
|
|
8917
10271
|
const chunks = [];
|
|
8918
10272
|
req.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
|
|
8919
|
-
req.on("end", () =>
|
|
10273
|
+
req.on("end", () => resolve13(Buffer.concat(chunks)));
|
|
8920
10274
|
req.on("error", reject);
|
|
8921
10275
|
});
|
|
8922
10276
|
}
|
|
@@ -8984,7 +10338,7 @@ function errorSummary(response, fallback) {
|
|
|
8984
10338
|
return typeof message === "string" ? message : fallback;
|
|
8985
10339
|
}
|
|
8986
10340
|
async function allocatePort(host) {
|
|
8987
|
-
return new Promise((
|
|
10341
|
+
return new Promise((resolve13, reject) => {
|
|
8988
10342
|
const server = createNetServer();
|
|
8989
10343
|
server.on("error", reject);
|
|
8990
10344
|
server.listen(0, host, () => {
|
|
@@ -8994,7 +10348,7 @@ async function allocatePort(host) {
|
|
|
8994
10348
|
return;
|
|
8995
10349
|
}
|
|
8996
10350
|
const port = address.port;
|
|
8997
|
-
server.close(() =>
|
|
10351
|
+
server.close(() => resolve13(port));
|
|
8998
10352
|
});
|
|
8999
10353
|
});
|
|
9000
10354
|
}
|
|
@@ -9034,11 +10388,11 @@ var ManagedModelServer = class {
|
|
|
9034
10388
|
sendJson(res, 500, { error: { message: err instanceof Error ? err.message : String(err) } });
|
|
9035
10389
|
});
|
|
9036
10390
|
});
|
|
9037
|
-
await new Promise((
|
|
10391
|
+
await new Promise((resolve13, reject) => {
|
|
9038
10392
|
this.server?.once("error", reject);
|
|
9039
10393
|
this.server?.listen(this.options.publicPort, this.options.publicHost, () => {
|
|
9040
10394
|
this.server?.off("error", reject);
|
|
9041
|
-
|
|
10395
|
+
resolve13();
|
|
9042
10396
|
});
|
|
9043
10397
|
});
|
|
9044
10398
|
}
|
|
@@ -9048,15 +10402,15 @@ var ManagedModelServer = class {
|
|
|
9048
10402
|
if (!this.server) return;
|
|
9049
10403
|
const server = this.server;
|
|
9050
10404
|
this.server = null;
|
|
9051
|
-
await new Promise((
|
|
9052
|
-
server.close((err) => err ? reject(err) :
|
|
10405
|
+
await new Promise((resolve13, reject) => {
|
|
10406
|
+
server.close((err) => err ? reject(err) : resolve13());
|
|
9053
10407
|
});
|
|
9054
10408
|
}
|
|
9055
10409
|
async run() {
|
|
9056
10410
|
await this.start();
|
|
9057
|
-
await new Promise((
|
|
10411
|
+
await new Promise((resolve13, reject) => {
|
|
9058
10412
|
this.server?.once("error", reject);
|
|
9059
|
-
this.server?.once("close",
|
|
10413
|
+
this.server?.once("close", resolve13);
|
|
9060
10414
|
});
|
|
9061
10415
|
}
|
|
9062
10416
|
async handle(req, res) {
|
|
@@ -9124,7 +10478,7 @@ var ManagedModelServer = class {
|
|
|
9124
10478
|
});
|
|
9125
10479
|
}
|
|
9126
10480
|
async processGeneration(route, body, res, receivedAt) {
|
|
9127
|
-
const requestId = `ttreq-${
|
|
10481
|
+
const requestId = `ttreq-${randomUUID10()}`;
|
|
9128
10482
|
const startedAt = Date.now();
|
|
9129
10483
|
const queuedMs = startedAt - receivedAt;
|
|
9130
10484
|
this.activeRequests += 1;
|
|
@@ -9230,7 +10584,7 @@ var ManagedModelServer = class {
|
|
|
9230
10584
|
try {
|
|
9231
10585
|
child.kill("SIGTERM");
|
|
9232
10586
|
await Promise.race([
|
|
9233
|
-
new Promise((
|
|
10587
|
+
new Promise((resolve13) => child.once("exit", () => resolve13())),
|
|
9234
10588
|
sleep2(5e3).then(() => {
|
|
9235
10589
|
if (child.exitCode == null && child.signalCode == null) child.kill("SIGKILL");
|
|
9236
10590
|
})
|
|
@@ -9308,7 +10662,7 @@ var ManagedModelServer = class {
|
|
|
9308
10662
|
const line = `${JSON.stringify(record4)}
|
|
9309
10663
|
`;
|
|
9310
10664
|
if (this.options.logFile) {
|
|
9311
|
-
mkdirSync3(
|
|
10665
|
+
mkdirSync3(dirname10(this.options.logFile), { recursive: true });
|
|
9312
10666
|
appendFileSync(this.options.logFile, line, "utf8");
|
|
9313
10667
|
return;
|
|
9314
10668
|
}
|
|
@@ -9856,7 +11210,7 @@ if __name__ == "__main__":
|
|
|
9856
11210
|
function resolveOutputPath(output, filename) {
|
|
9857
11211
|
if (!output) return filename;
|
|
9858
11212
|
if (existsSync9(output) && statSync4(output).isDirectory()) {
|
|
9859
|
-
return
|
|
11213
|
+
return join15(output, filename);
|
|
9860
11214
|
}
|
|
9861
11215
|
return output;
|
|
9862
11216
|
}
|
|
@@ -9932,7 +11286,7 @@ async function downloadUrlToFile(url, outputPath) {
|
|
|
9932
11286
|
if (!res.body) {
|
|
9933
11287
|
throw new Error("Download failed: response body was empty");
|
|
9934
11288
|
}
|
|
9935
|
-
mkdirSync4(
|
|
11289
|
+
mkdirSync4(dirname11(outputPath), { recursive: true });
|
|
9936
11290
|
const file = createWriteStream(outputPath);
|
|
9937
11291
|
const contentLength = parseContentLength(res.headers.get("content-length"));
|
|
9938
11292
|
const progress = createDownloadProgress(
|
|
@@ -9963,15 +11317,15 @@ async function downloadUrlToFile(url, outputPath) {
|
|
|
9963
11317
|
return contentLength;
|
|
9964
11318
|
}
|
|
9965
11319
|
function defaultCacheDir() {
|
|
9966
|
-
const root = process.env.XDG_CACHE_HOME ||
|
|
9967
|
-
return
|
|
11320
|
+
const root = process.env.XDG_CACHE_HOME || join15(homedir4(), ".cache");
|
|
11321
|
+
return join15(root, "tuned-tensor", "models");
|
|
9968
11322
|
}
|
|
9969
11323
|
function runtimeDir(cacheDir) {
|
|
9970
|
-
return
|
|
11324
|
+
return join15(cacheDir, "_runtime");
|
|
9971
11325
|
}
|
|
9972
11326
|
function runtimePythonPath(cacheDir) {
|
|
9973
11327
|
const dir = runtimeDir(cacheDir);
|
|
9974
|
-
return process.platform === "win32" ?
|
|
11328
|
+
return process.platform === "win32" ? join15(dir, "Scripts", "python.exe") : join15(dir, "bin", "python");
|
|
9975
11329
|
}
|
|
9976
11330
|
function resolveServePython(cacheDir, explicitPython) {
|
|
9977
11331
|
if (explicitPython) return explicitPython;
|
|
@@ -10044,7 +11398,7 @@ print(json.dumps({"missing": missing}))
|
|
|
10044
11398
|
}
|
|
10045
11399
|
}
|
|
10046
11400
|
function setupRuntimeCommands(python, venvDir, deps) {
|
|
10047
|
-
const venvPython = process.platform === "win32" ?
|
|
11401
|
+
const venvPython = process.platform === "win32" ? join15(venvDir, "Scripts", "python.exe") : join15(venvDir, "bin", "python");
|
|
10048
11402
|
return [
|
|
10049
11403
|
[python, "-m", "venv", venvDir],
|
|
10050
11404
|
[venvPython, "-m", "pip", "install", "--upgrade", "pip"],
|
|
@@ -10082,9 +11436,9 @@ function extractArchive(archivePath, targetDir, force) {
|
|
|
10082
11436
|
return targetDir;
|
|
10083
11437
|
}
|
|
10084
11438
|
function localArchiveCacheDir(archivePath, cacheDir) {
|
|
10085
|
-
const resolved =
|
|
11439
|
+
const resolved = resolve11(archivePath);
|
|
10086
11440
|
const digest = createHash6("sha256").update(resolved).digest("hex").slice(0, 12);
|
|
10087
|
-
return
|
|
11441
|
+
return join15(cacheDir, `local-${safeSegment(basename5(archivePath))}-${digest}`);
|
|
10088
11442
|
}
|
|
10089
11443
|
function buildSystemPrompt(spec) {
|
|
10090
11444
|
const parts = [];
|
|
@@ -10100,9 +11454,9 @@ ${spec.constraints.map((c) => `- ${c}`).join("\n")}`);
|
|
|
10100
11454
|
return parts.join("\n\n");
|
|
10101
11455
|
}
|
|
10102
11456
|
function findSpecPath(explicitPath, modelPath) {
|
|
10103
|
-
const candidates = explicitPath ? [explicitPath] : [
|
|
11457
|
+
const candidates = explicitPath ? [explicitPath] : [join15(process.cwd(), DEFAULT_SPEC_FILE2), join15(modelPath, DEFAULT_SPEC_FILE2)];
|
|
10104
11458
|
for (const candidate of candidates) {
|
|
10105
|
-
const resolved =
|
|
11459
|
+
const resolved = resolve11(candidate);
|
|
10106
11460
|
if (existsSync9(resolved) && statSync4(resolved).isFile()) return resolved;
|
|
10107
11461
|
}
|
|
10108
11462
|
return null;
|
|
@@ -10112,7 +11466,7 @@ function loadSystemPromptFromSpec(specPath) {
|
|
|
10112
11466
|
return buildSystemPrompt(spec);
|
|
10113
11467
|
}
|
|
10114
11468
|
function loadJsonSchemaForServe(schemaPath) {
|
|
10115
|
-
const resolved =
|
|
11469
|
+
const resolved = resolve11(schemaPath);
|
|
10116
11470
|
if (!existsSync9(resolved) || !statSync4(resolved).isFile()) {
|
|
10117
11471
|
throw new Error(`JSON schema file not found: ${schemaPath}`);
|
|
10118
11472
|
}
|
|
@@ -10124,15 +11478,15 @@ function loadJsonSchemaForServe(schemaPath) {
|
|
|
10124
11478
|
}
|
|
10125
11479
|
}
|
|
10126
11480
|
function writeReferenceServerScript(cacheDir) {
|
|
10127
|
-
const scriptDir =
|
|
11481
|
+
const scriptDir = join15(cacheDir, "_server");
|
|
10128
11482
|
mkdirSync4(scriptDir, { recursive: true });
|
|
10129
|
-
const scriptPath =
|
|
11483
|
+
const scriptPath = join15(scriptDir, "openai_reference_server.py");
|
|
10130
11484
|
writeFileSync5(scriptPath, REFERENCE_SERVER_SCRIPT, "utf8");
|
|
10131
11485
|
return scriptPath;
|
|
10132
11486
|
}
|
|
10133
11487
|
async function resolveServeTarget(target, opts, cacheDir, forceDownload) {
|
|
10134
11488
|
if (existsSync9(target)) {
|
|
10135
|
-
const resolved =
|
|
11489
|
+
const resolved = resolve11(target);
|
|
10136
11490
|
const stats = statSync4(resolved);
|
|
10137
11491
|
if (stats.isDirectory()) {
|
|
10138
11492
|
return {
|
|
@@ -10142,7 +11496,7 @@ async function resolveServeTarget(target, opts, cacheDir, forceDownload) {
|
|
|
10142
11496
|
};
|
|
10143
11497
|
}
|
|
10144
11498
|
if (stats.isFile() && (resolved.endsWith(".tar.gz") || resolved.endsWith(".tgz"))) {
|
|
10145
|
-
const extractDir2 =
|
|
11499
|
+
const extractDir2 = join15(localArchiveCacheDir(resolved, cacheDir), "model");
|
|
10146
11500
|
return {
|
|
10147
11501
|
modelPath: extractArchive(resolved, extractDir2, forceDownload),
|
|
10148
11502
|
modelName: basename5(resolved).replace(/\.t(ar\.)?gz$/, ""),
|
|
@@ -10154,9 +11508,9 @@ async function resolveServeTarget(target, opts, cacheDir, forceDownload) {
|
|
|
10154
11508
|
const fullId = await resolveModelId(target, opts);
|
|
10155
11509
|
const { data: model } = await get(`/models/${fullId}`, void 0, opts);
|
|
10156
11510
|
const { data: data3 } = await get(`/models/${fullId}/download`, void 0, opts);
|
|
10157
|
-
const modelCacheDir =
|
|
10158
|
-
const archivePath =
|
|
10159
|
-
const extractDir =
|
|
11511
|
+
const modelCacheDir = join15(cacheDir, fullId);
|
|
11512
|
+
const archivePath = join15(modelCacheDir, data3.filename);
|
|
11513
|
+
const extractDir = join15(modelCacheDir, "model");
|
|
10160
11514
|
if (!existsSync9(archivePath) || forceDownload) {
|
|
10161
11515
|
await downloadUrlToFile(data3.url, archivePath);
|
|
10162
11516
|
}
|
|
@@ -10262,7 +11616,7 @@ function ollamaSlug(name) {
|
|
|
10262
11616
|
function firstExisting(candidates) {
|
|
10263
11617
|
for (const candidate of candidates) {
|
|
10264
11618
|
if (candidate && existsSync9(candidate) && statSync4(candidate).isFile()) {
|
|
10265
|
-
return
|
|
11619
|
+
return resolve11(candidate);
|
|
10266
11620
|
}
|
|
10267
11621
|
}
|
|
10268
11622
|
return null;
|
|
@@ -10272,14 +11626,14 @@ function llamaCppDir(explicit) {
|
|
|
10272
11626
|
}
|
|
10273
11627
|
function resolveConvertScript(opts, required) {
|
|
10274
11628
|
if (opts.convertScript) {
|
|
10275
|
-
const resolved =
|
|
11629
|
+
const resolved = resolve11(opts.convertScript);
|
|
10276
11630
|
if (required && !existsSync9(resolved)) {
|
|
10277
11631
|
throw new Error(`Conversion script not found: ${opts.convertScript}`);
|
|
10278
11632
|
}
|
|
10279
11633
|
return resolved;
|
|
10280
11634
|
}
|
|
10281
11635
|
const dir = llamaCppDir(opts.llamaCpp);
|
|
10282
|
-
const candidates = dir ? [
|
|
11636
|
+
const candidates = dir ? [join15(dir, "convert_hf_to_gguf.py"), join15(dir, "convert-hf-to-gguf.py")] : [];
|
|
10283
11637
|
const found = firstExisting(candidates);
|
|
10284
11638
|
if (found) return found;
|
|
10285
11639
|
if (required) {
|
|
@@ -10287,11 +11641,11 @@ function resolveConvertScript(opts, required) {
|
|
|
10287
11641
|
"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."
|
|
10288
11642
|
);
|
|
10289
11643
|
}
|
|
10290
|
-
return dir ?
|
|
11644
|
+
return dir ? join15(dir, "convert_hf_to_gguf.py") : "convert_hf_to_gguf.py";
|
|
10291
11645
|
}
|
|
10292
11646
|
function resolveQuantizeBin(opts, required) {
|
|
10293
11647
|
if (opts.quantizeBin) {
|
|
10294
|
-
const resolved =
|
|
11648
|
+
const resolved = resolve11(opts.quantizeBin);
|
|
10295
11649
|
if (required && !existsSync9(resolved)) {
|
|
10296
11650
|
throw new Error(`llama-quantize binary not found: ${opts.quantizeBin}`);
|
|
10297
11651
|
}
|
|
@@ -10300,10 +11654,10 @@ function resolveQuantizeBin(opts, required) {
|
|
|
10300
11654
|
const dir = llamaCppDir(opts.llamaCpp);
|
|
10301
11655
|
if (dir) {
|
|
10302
11656
|
const candidates = [
|
|
10303
|
-
|
|
10304
|
-
|
|
10305
|
-
|
|
10306
|
-
|
|
11657
|
+
join15(dir, "build", "bin", "llama-quantize"),
|
|
11658
|
+
join15(dir, "build", "bin", "quantize"),
|
|
11659
|
+
join15(dir, "llama-quantize"),
|
|
11660
|
+
join15(dir, "quantize")
|
|
10307
11661
|
];
|
|
10308
11662
|
const found = firstExisting(candidates);
|
|
10309
11663
|
if (found) return found;
|
|
@@ -10451,7 +11805,7 @@ function registerModelsCommands(parent) {
|
|
|
10451
11805
|
}
|
|
10452
11806
|
const quantPlan = planQuant(String(cmdOpts.quant));
|
|
10453
11807
|
const printOnly = Boolean(cmdOpts.printCommand);
|
|
10454
|
-
const cacheDir =
|
|
11808
|
+
const cacheDir = resolve11(cmdOpts.cacheDir || defaultCacheDir());
|
|
10455
11809
|
mkdirSync4(cacheDir, { recursive: true });
|
|
10456
11810
|
const serveTarget = await resolveServeTarget(
|
|
10457
11811
|
target,
|
|
@@ -10460,9 +11814,9 @@ function registerModelsCommands(parent) {
|
|
|
10460
11814
|
Boolean(cmdOpts.forceDownload)
|
|
10461
11815
|
);
|
|
10462
11816
|
const slug = ollamaSlug(serveTarget.modelName);
|
|
10463
|
-
const outputDir =
|
|
10464
|
-
const finalPath =
|
|
10465
|
-
const intermediatePath = quantPlan.requiresQuantize ?
|
|
11817
|
+
const outputDir = resolve11(cmdOpts.output || join15(process.cwd(), `${slug}-gguf`));
|
|
11818
|
+
const finalPath = join15(outputDir, `${slug}.${quantPlan.quant}.gguf`);
|
|
11819
|
+
const intermediatePath = quantPlan.requiresQuantize ? join15(outputDir, `${slug}.f16.gguf`) : void 0;
|
|
10466
11820
|
const convertScript = resolveConvertScript(cmdOpts, !printOnly);
|
|
10467
11821
|
const convertOutfile = intermediatePath ?? finalPath;
|
|
10468
11822
|
const steps = [
|
|
@@ -10500,7 +11854,7 @@ function registerModelsCommands(parent) {
|
|
|
10500
11854
|
}
|
|
10501
11855
|
if (specPath) systemPrompt2 = loadSystemPromptFromSpec(specPath);
|
|
10502
11856
|
}
|
|
10503
|
-
modelfilePath =
|
|
11857
|
+
modelfilePath = join15(outputDir, "Modelfile");
|
|
10504
11858
|
modelfileContent = buildModelfile(basename5(finalPath), systemPrompt2);
|
|
10505
11859
|
const createModel = cmdOpts.ollamaCreate !== false;
|
|
10506
11860
|
if (createModel) {
|
|
@@ -10587,7 +11941,7 @@ function registerModelsCommands(parent) {
|
|
|
10587
11941
|
}
|
|
10588
11942
|
});
|
|
10589
11943
|
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) => {
|
|
10590
|
-
const cacheDir =
|
|
11944
|
+
const cacheDir = resolve11(cmdOpts.cacheDir || defaultCacheDir());
|
|
10591
11945
|
const venvDir = runtimeDir(cacheDir);
|
|
10592
11946
|
const venvPython = runtimePythonPath(cacheDir);
|
|
10593
11947
|
const python = pickRuntimePython(cmdOpts.python);
|
|
@@ -10633,7 +11987,7 @@ function registerModelsCommands(parent) {
|
|
|
10633
11987
|
});
|
|
10634
11988
|
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) => {
|
|
10635
11989
|
const opts = parent.optsWithGlobals();
|
|
10636
|
-
const cacheDir =
|
|
11990
|
+
const cacheDir = resolve11(cmdOpts.cacheDir || defaultCacheDir());
|
|
10637
11991
|
mkdirSync4(cacheDir, { recursive: true });
|
|
10638
11992
|
const python = resolveServePython(cacheDir, cmdOpts.python);
|
|
10639
11993
|
if (!cmdOpts.printCommand) ensureServingRuntime(python, cacheDir);
|
|
@@ -10682,7 +12036,7 @@ function registerModelsCommands(parent) {
|
|
|
10682
12036
|
idle_timeout_seconds: idleTimeoutSeconds,
|
|
10683
12037
|
restart_after_requests: restartAfterRequests,
|
|
10684
12038
|
gate_field: String(cmdOpts.gateField),
|
|
10685
|
-
log_file: cmdOpts.logFile ?
|
|
12039
|
+
log_file: cmdOpts.logFile ? resolve11(cmdOpts.logFile) : void 0
|
|
10686
12040
|
} : void 0;
|
|
10687
12041
|
if (cmdOpts.printCommand) return printServeCommand(python, args2, env, managedConfig);
|
|
10688
12042
|
if (!isJsonMode()) {
|
|
@@ -10719,7 +12073,7 @@ function registerModelsCommands(parent) {
|
|
|
10719
12073
|
idleTimeoutSeconds,
|
|
10720
12074
|
restartAfterRequests,
|
|
10721
12075
|
gateField: String(cmdOpts.gateField),
|
|
10722
|
-
logFile: cmdOpts.logFile ?
|
|
12076
|
+
logFile: cmdOpts.logFile ? resolve11(cmdOpts.logFile) : void 0
|
|
10723
12077
|
});
|
|
10724
12078
|
return;
|
|
10725
12079
|
}
|
|
@@ -10746,7 +12100,7 @@ function registerModelsCommands(parent) {
|
|
|
10746
12100
|
|
|
10747
12101
|
// src/commands/push.ts
|
|
10748
12102
|
import { readFileSync as readFileSync12, writeFileSync as writeFileSync6 } from "fs";
|
|
10749
|
-
import { resolve as
|
|
12103
|
+
import { resolve as resolve12 } from "path";
|
|
10750
12104
|
function persistRemoteId(filePath, id) {
|
|
10751
12105
|
const raw = JSON.parse(readFileSync12(filePath, "utf-8"));
|
|
10752
12106
|
raw.id = id;
|
|
@@ -10755,7 +12109,7 @@ function persistRemoteId(filePath, id) {
|
|
|
10755
12109
|
function registerPushCommand(parent) {
|
|
10756
12110
|
parent.command("push").description("Push local spec to the Tuned Tensor API").option("-f, --file <path>", "Spec file path", DEFAULT_SPEC_FILE2).action(async (cmdOpts) => {
|
|
10757
12111
|
const opts = parent.optsWithGlobals();
|
|
10758
|
-
const filePath =
|
|
12112
|
+
const filePath = resolve12(cmdOpts.file);
|
|
10759
12113
|
const spec = loadSpec(cmdOpts.file);
|
|
10760
12114
|
const evalCaseErrors = validateEvalCases(spec);
|
|
10761
12115
|
if (evalCaseErrors.length > 0) {
|
|
@@ -10901,10 +12255,10 @@ async function checkForCliUpdate(currentVersion, options = {}) {
|
|
|
10901
12255
|
return null;
|
|
10902
12256
|
}
|
|
10903
12257
|
})();
|
|
10904
|
-
const deadline = new Promise((
|
|
12258
|
+
const deadline = new Promise((resolve13) => {
|
|
10905
12259
|
timeout = setTimeout(() => {
|
|
10906
12260
|
controller.abort();
|
|
10907
|
-
|
|
12261
|
+
resolve13(null);
|
|
10908
12262
|
}, timeoutMs);
|
|
10909
12263
|
});
|
|
10910
12264
|
try {
|
|
@@ -11029,7 +12383,7 @@ async function runSelfCommand(args2, options = {}) {
|
|
|
11029
12383
|
if (!entrypoint) {
|
|
11030
12384
|
throw new Error("Cannot locate the tt CLI entrypoint.");
|
|
11031
12385
|
}
|
|
11032
|
-
return await new Promise((
|
|
12386
|
+
return await new Promise((resolve13, reject) => {
|
|
11033
12387
|
const child = spawn4(process.execPath, [entrypoint, ...args2], {
|
|
11034
12388
|
cwd: options.cwd,
|
|
11035
12389
|
env: options.env ?? process.env,
|
|
@@ -11069,7 +12423,7 @@ async function runSelfCommand(args2, options = {}) {
|
|
|
11069
12423
|
child.once("close", (exitCode, signal) => {
|
|
11070
12424
|
cleanup();
|
|
11071
12425
|
const signalNumber = signal ? osConstants2.signals[signal] : void 0;
|
|
11072
|
-
|
|
12426
|
+
resolve13({
|
|
11073
12427
|
exitCode: exitCode ?? (typeof signalNumber === "number" ? 128 + signalNumber : 1),
|
|
11074
12428
|
signal
|
|
11075
12429
|
});
|
|
@@ -11132,7 +12486,7 @@ function createProgram(version, runtime = {}) {
|
|
|
11132
12486
|
if (root.json) setJsonMode(true);
|
|
11133
12487
|
if (root.color === false) {
|
|
11134
12488
|
process.env.FORCE_COLOR = "0";
|
|
11135
|
-
|
|
12489
|
+
chalk13.level = 0;
|
|
11136
12490
|
}
|
|
11137
12491
|
});
|
|
11138
12492
|
registerLocalCommands(program, {
|
|
@@ -11170,12 +12524,12 @@ function createProgram(version, runtime = {}) {
|
|
|
11170
12524
|
`);
|
|
11171
12525
|
return;
|
|
11172
12526
|
}
|
|
11173
|
-
output.write(`${
|
|
12527
|
+
output.write(`${chalk13.bold.hex("#8B5CF6")("Tuned Tensor status")}
|
|
11174
12528
|
`);
|
|
11175
12529
|
output.write(`${formatShellStatus(context).join("\n")}
|
|
11176
12530
|
|
|
11177
12531
|
`);
|
|
11178
|
-
output.write(`${
|
|
12532
|
+
output.write(`${chalk13.bold("Context")}
|
|
11179
12533
|
`);
|
|
11180
12534
|
output.write(`${formatShellContext(context).join("\n")}
|
|
11181
12535
|
`);
|
|
@@ -11276,7 +12630,7 @@ async function runCli(version, runtime = {}) {
|
|
|
11276
12630
|
}
|
|
11277
12631
|
|
|
11278
12632
|
// src/index.ts
|
|
11279
|
-
runCli("0.
|
|
12633
|
+
runCli("0.21.0").catch((err) => {
|
|
11280
12634
|
reportError(err);
|
|
11281
12635
|
process.exit(1);
|
|
11282
12636
|
});
|