@tuned-tensor/cli 0.13.0 → 0.13.1-beta.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 -24
- package/dist/index.js +466 -173
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -573,18 +573,134 @@ async function rejectPreparedAction(action, persist) {
|
|
|
573
573
|
}
|
|
574
574
|
|
|
575
575
|
// src/agent-model.ts
|
|
576
|
+
import { ModelRuntime } from "@earendil-works/pi-coding-agent";
|
|
577
|
+
import { chmodSync as chmodSync2, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
578
|
+
import { dirname, join as join3 } from "path";
|
|
579
|
+
|
|
580
|
+
// src/config.ts
|
|
576
581
|
import {
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
+
chmodSync,
|
|
583
|
+
existsSync,
|
|
584
|
+
mkdirSync,
|
|
585
|
+
readFileSync,
|
|
586
|
+
writeFileSync
|
|
587
|
+
} from "fs";
|
|
588
|
+
import { homedir } from "os";
|
|
589
|
+
import { join as join2 } from "path";
|
|
590
|
+
var AGENT_THINKING_LEVELS = [
|
|
591
|
+
"off",
|
|
592
|
+
"minimal",
|
|
593
|
+
"low",
|
|
594
|
+
"medium",
|
|
595
|
+
"high",
|
|
596
|
+
"xhigh",
|
|
597
|
+
"max"
|
|
598
|
+
];
|
|
599
|
+
var CONFIG_DIR_NAME = "tuned-tensor";
|
|
600
|
+
var CONFIG_FILE_NAME = "config.json";
|
|
601
|
+
function getConfigDir() {
|
|
602
|
+
const xdg = process.env.XDG_CONFIG_HOME;
|
|
603
|
+
const base = xdg || join2(homedir(), ".config");
|
|
604
|
+
return join2(base, CONFIG_DIR_NAME);
|
|
605
|
+
}
|
|
606
|
+
function getAgentConfigDir() {
|
|
607
|
+
return join2(getConfigDir(), "agent");
|
|
608
|
+
}
|
|
609
|
+
function getConfigPath() {
|
|
610
|
+
return join2(getConfigDir(), CONFIG_FILE_NAME);
|
|
611
|
+
}
|
|
612
|
+
function readConfig() {
|
|
613
|
+
const path = getConfigPath();
|
|
614
|
+
if (!existsSync(path)) return {};
|
|
615
|
+
try {
|
|
616
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
617
|
+
} catch {
|
|
618
|
+
return {};
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
var configRevision = 0;
|
|
622
|
+
function getConfigRevision() {
|
|
623
|
+
return configRevision;
|
|
624
|
+
}
|
|
625
|
+
function writeConfig(config) {
|
|
626
|
+
const dir = getConfigDir();
|
|
627
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true, mode: 448 });
|
|
628
|
+
chmodSync(dir, 448);
|
|
629
|
+
const path = getConfigPath();
|
|
630
|
+
writeFileSync(path, JSON.stringify(config, null, 2) + "\n", {
|
|
631
|
+
mode: 384
|
|
632
|
+
});
|
|
633
|
+
chmodSync(path, 384);
|
|
634
|
+
configRevision += 1;
|
|
635
|
+
}
|
|
636
|
+
function updateConfig(partial) {
|
|
637
|
+
const current = readConfig();
|
|
638
|
+
writeConfig({ ...current, ...partial });
|
|
639
|
+
}
|
|
640
|
+
function getApiKey(opts) {
|
|
641
|
+
return opts?.apiKey || process.env.TUNED_TENSOR_API_KEY || readConfig().api_key;
|
|
642
|
+
}
|
|
643
|
+
function getAgentSelection(env = process.env) {
|
|
644
|
+
const stored = readConfig().agent;
|
|
645
|
+
const provider = env.TUNED_TENSOR_AGENT_PROVIDER || stored?.provider;
|
|
646
|
+
const model = env.TUNED_TENSOR_AGENT_MODEL || stored?.model;
|
|
647
|
+
const rawThinking = env.TUNED_TENSOR_AGENT_THINKING || stored?.thinking || "medium";
|
|
648
|
+
if (!AGENT_THINKING_LEVELS.includes(rawThinking)) {
|
|
649
|
+
throw new Error(
|
|
650
|
+
"Agent thinking must be off, minimal, low, medium, high, xhigh, or max."
|
|
651
|
+
);
|
|
652
|
+
}
|
|
653
|
+
if (!provider && !model) return void 0;
|
|
654
|
+
if (!provider || !model) {
|
|
655
|
+
throw new Error(
|
|
656
|
+
"Both an agent provider and model are required. Run `tt agent configure --provider <provider> --model <model>`."
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
return { provider, model, thinking: rawThinking };
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
// src/agent-model.ts
|
|
663
|
+
function missingProviderAuthMessage(provider) {
|
|
664
|
+
return `Provider "${provider}" is not authenticated. Open the tt shell and run /login ${provider} to save a key, then try again.`;
|
|
665
|
+
}
|
|
666
|
+
function unknownAgentProviderMessage(provider) {
|
|
667
|
+
return `Unknown provider "${provider}". Use /login <id> or /model <id>.`;
|
|
668
|
+
}
|
|
669
|
+
var MIN_REDACTED_SECRET_LENGTH = 8;
|
|
670
|
+
function isRecord(value) {
|
|
671
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
672
|
+
}
|
|
673
|
+
function pushSecret(secrets, value) {
|
|
674
|
+
if (typeof value !== "string") return;
|
|
675
|
+
const secret = value.trim();
|
|
676
|
+
if (secret.length < MIN_REDACTED_SECRET_LENGTH) return;
|
|
677
|
+
if (!secrets.includes(secret)) secrets.push(secret);
|
|
678
|
+
}
|
|
679
|
+
function readStoredProviderSecrets(authPath = getAgentAuthPath()) {
|
|
680
|
+
if (!existsSync2(authPath)) return [];
|
|
681
|
+
let parsed;
|
|
682
|
+
try {
|
|
683
|
+
parsed = JSON.parse(readFileSync2(authPath, "utf-8"));
|
|
684
|
+
} catch {
|
|
685
|
+
return [];
|
|
686
|
+
}
|
|
687
|
+
if (!isRecord(parsed)) return [];
|
|
688
|
+
const secrets = [];
|
|
689
|
+
for (const credential of Object.values(parsed)) {
|
|
690
|
+
if (!isRecord(credential)) continue;
|
|
691
|
+
if (credential.type === "api_key") {
|
|
692
|
+
pushSecret(secrets, credential.key);
|
|
693
|
+
} else if (credential.type === "oauth") {
|
|
694
|
+
pushSecret(secrets, credential.access);
|
|
695
|
+
pushSecret(secrets, credential.refresh);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
return secrets;
|
|
699
|
+
}
|
|
582
700
|
function resolveAgentModel(runtime, selection) {
|
|
583
701
|
const resolved = resolveAgentModelDefinition(runtime, selection);
|
|
584
702
|
if (!runtime.hasConfiguredAuth(selection.provider)) {
|
|
585
|
-
throw new Error(
|
|
586
|
-
`Authenticate provider "${selection.provider}" first, then retry. Provider secrets are never accepted by tt flags.`
|
|
587
|
-
);
|
|
703
|
+
throw new Error(missingProviderAuthMessage(selection.provider));
|
|
588
704
|
}
|
|
589
705
|
return resolved;
|
|
590
706
|
}
|
|
@@ -610,25 +726,46 @@ function resolveAgentModelDefinition(runtime, selection) {
|
|
|
610
726
|
}
|
|
611
727
|
return { model, thinking: selection.thinking };
|
|
612
728
|
}
|
|
729
|
+
function getAgentAuthPath() {
|
|
730
|
+
return join3(getAgentConfigDir(), "auth.json");
|
|
731
|
+
}
|
|
732
|
+
function getAgentModelsPath() {
|
|
733
|
+
return join3(getAgentConfigDir(), "models.json");
|
|
734
|
+
}
|
|
613
735
|
async function createPiModelRuntime() {
|
|
614
|
-
const agentDir =
|
|
615
|
-
|
|
736
|
+
const agentDir = getAgentConfigDir();
|
|
737
|
+
ensurePrivateDir(agentDir);
|
|
738
|
+
const modelsPath = getAgentModelsPath();
|
|
739
|
+
const authPath = getAgentAuthPath();
|
|
616
740
|
ensureOpenRouterAppHeaders(modelsPath);
|
|
617
|
-
|
|
618
|
-
authPath
|
|
741
|
+
const runtime = await ModelRuntime.create({
|
|
742
|
+
authPath,
|
|
619
743
|
modelsPath,
|
|
620
744
|
allowModelNetwork: false
|
|
621
745
|
});
|
|
746
|
+
if (existsSync2(authPath)) chmodSync2(authPath, 384);
|
|
747
|
+
return runtime;
|
|
622
748
|
}
|
|
623
749
|
var OPENROUTER_APP_HEADERS = {
|
|
624
750
|
"HTTP-Referer": "https://tunedtensor.com",
|
|
751
|
+
"X-OpenRouter-Title": "Tuned Tensor",
|
|
752
|
+
"X-OpenRouter-Categories": "cli-agent",
|
|
625
753
|
"X-Title": "Tuned Tensor"
|
|
626
754
|
};
|
|
755
|
+
function ensurePrivateDir(dir) {
|
|
756
|
+
if (!existsSync2(dir)) mkdirSync2(dir, { recursive: true, mode: 448 });
|
|
757
|
+
chmodSync2(dir, 448);
|
|
758
|
+
}
|
|
759
|
+
function writePrivateJson(path, value) {
|
|
760
|
+
ensurePrivateDir(dirname(path));
|
|
761
|
+
writeFileSync2(path, JSON.stringify(value, null, 2) + "\n", { mode: 384 });
|
|
762
|
+
chmodSync2(path, 384);
|
|
763
|
+
}
|
|
627
764
|
function ensureOpenRouterAppHeaders(modelsPath) {
|
|
628
765
|
let config = {};
|
|
629
|
-
if (
|
|
766
|
+
if (existsSync2(modelsPath)) {
|
|
630
767
|
try {
|
|
631
|
-
const parsed = JSON.parse(
|
|
768
|
+
const parsed = JSON.parse(readFileSync2(modelsPath, "utf-8"));
|
|
632
769
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
633
770
|
config = parsed;
|
|
634
771
|
}
|
|
@@ -642,10 +779,15 @@ function ensureOpenRouterAppHeaders(modelsPath) {
|
|
|
642
779
|
const providers = asRecord(config.providers) ?? (config.providers = empty());
|
|
643
780
|
const openrouter = asRecord(providers.openrouter) ?? (providers.openrouter = empty());
|
|
644
781
|
const headers = asRecord(openrouter.headers) ?? (openrouter.headers = empty());
|
|
782
|
+
const attributionNames = new Set(
|
|
783
|
+
Object.keys(OPENROUTER_APP_HEADERS).map((name) => name.toLowerCase())
|
|
784
|
+
);
|
|
785
|
+
for (const name of Object.keys(headers)) {
|
|
786
|
+
if (attributionNames.has(name.toLowerCase())) delete headers[name];
|
|
787
|
+
}
|
|
645
788
|
Object.assign(headers, OPENROUTER_APP_HEADERS);
|
|
646
789
|
if (JSON.stringify(config) === before) return;
|
|
647
|
-
|
|
648
|
-
writeFileSync(modelsPath, JSON.stringify(config, null, 2) + "\n");
|
|
790
|
+
writePrivateJson(modelsPath, config);
|
|
649
791
|
}
|
|
650
792
|
|
|
651
793
|
// src/agent-tools.ts
|
|
@@ -1335,7 +1477,7 @@ import {
|
|
|
1335
1477
|
writeFile as writeFile2
|
|
1336
1478
|
} from "fs/promises";
|
|
1337
1479
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
1338
|
-
import { join as
|
|
1480
|
+
import { join as join4 } from "path";
|
|
1339
1481
|
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
1340
1482
|
var DEFAULT_MAX_THREAD_BYTES = 1e6;
|
|
1341
1483
|
var DEFAULT_MAX_MESSAGES = 200;
|
|
@@ -1345,7 +1487,7 @@ var DEFAULT_MAX_CLAIMS = 1e3;
|
|
|
1345
1487
|
function assertThreadId(id) {
|
|
1346
1488
|
if (!UUID.test(id)) throw new Error("Invalid local agent thread ID.");
|
|
1347
1489
|
}
|
|
1348
|
-
function
|
|
1490
|
+
function isRecord2(value) {
|
|
1349
1491
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1350
1492
|
}
|
|
1351
1493
|
function scrub(value, secrets) {
|
|
@@ -1356,7 +1498,7 @@ function scrub(value, secrets) {
|
|
|
1356
1498
|
);
|
|
1357
1499
|
}
|
|
1358
1500
|
if (Array.isArray(value)) return value.map((entry) => scrub(entry, secrets));
|
|
1359
|
-
if (
|
|
1501
|
+
if (isRecord2(value)) {
|
|
1360
1502
|
return Object.fromEntries(
|
|
1361
1503
|
Object.entries(value).map(([key, entry]) => [key, scrub(entry, secrets)])
|
|
1362
1504
|
);
|
|
@@ -1366,9 +1508,10 @@ function scrub(value, secrets) {
|
|
|
1366
1508
|
var LocalAgentStore = class {
|
|
1367
1509
|
constructor(rootDir, options = {}) {
|
|
1368
1510
|
this.rootDir = rootDir;
|
|
1369
|
-
this.threadsDir =
|
|
1370
|
-
this.claimsDir =
|
|
1511
|
+
this.threadsDir = join4(rootDir, "threads");
|
|
1512
|
+
this.claimsDir = join4(rootDir, "action-claims");
|
|
1371
1513
|
this.secrets = options.secretValues?.filter(Boolean) ?? [];
|
|
1514
|
+
this.secretValueProvider = options.secretValueProvider;
|
|
1372
1515
|
this.maxThreadBytes = options.maxThreadBytes ?? DEFAULT_MAX_THREAD_BYTES;
|
|
1373
1516
|
this.maxMessages = options.maxMessages ?? DEFAULT_MAX_MESSAGES;
|
|
1374
1517
|
this.maxActions = options.maxActions ?? DEFAULT_MAX_ACTIONS;
|
|
@@ -1378,13 +1521,20 @@ var LocalAgentStore = class {
|
|
|
1378
1521
|
threadsDir;
|
|
1379
1522
|
claimsDir;
|
|
1380
1523
|
secrets;
|
|
1524
|
+
secretValueProvider;
|
|
1381
1525
|
maxThreadBytes;
|
|
1382
1526
|
maxMessages;
|
|
1383
1527
|
maxActions;
|
|
1384
1528
|
maxThreads;
|
|
1385
1529
|
maxClaims;
|
|
1386
1530
|
redact(value) {
|
|
1387
|
-
|
|
1531
|
+
let extra = [];
|
|
1532
|
+
try {
|
|
1533
|
+
extra = this.secretValueProvider?.() ?? [];
|
|
1534
|
+
} catch {
|
|
1535
|
+
extra = [];
|
|
1536
|
+
}
|
|
1537
|
+
return scrub(value, [...this.secrets, ...extra]);
|
|
1388
1538
|
}
|
|
1389
1539
|
async ensureDirectories() {
|
|
1390
1540
|
await mkdir2(this.rootDir, { recursive: true, mode: 448 });
|
|
@@ -1396,7 +1546,7 @@ var LocalAgentStore = class {
|
|
|
1396
1546
|
}
|
|
1397
1547
|
path(id) {
|
|
1398
1548
|
assertThreadId(id);
|
|
1399
|
-
return
|
|
1549
|
+
return join4(this.threadsDir, `${id}.json`);
|
|
1400
1550
|
}
|
|
1401
1551
|
serializeWithinQuota(state) {
|
|
1402
1552
|
const active = state.actions.filter(
|
|
@@ -1421,11 +1571,11 @@ var LocalAgentStore = class {
|
|
|
1421
1571
|
return serialized;
|
|
1422
1572
|
}
|
|
1423
1573
|
async pruneDirectory(directory, suffix, maximum, preserve) {
|
|
1424
|
-
const entries = await Promise.all((await readdir(directory)).filter((name) => suffix.test(name) && name !== preserve).map(async (name) => ({ name, modified: (await stat(
|
|
1574
|
+
const entries = await Promise.all((await readdir(directory)).filter((name) => suffix.test(name) && name !== preserve).map(async (name) => ({ name, modified: (await stat(join4(directory, name))).mtimeMs })));
|
|
1425
1575
|
entries.sort((a, b) => a.modified - b.modified);
|
|
1426
1576
|
const keepOther = Math.max(0, maximum - (preserve ? 1 : 0));
|
|
1427
1577
|
for (const entry of entries.slice(0, Math.max(0, entries.length - keepOther))) {
|
|
1428
|
-
await unlink(
|
|
1578
|
+
await unlink(join4(directory, entry.name));
|
|
1429
1579
|
}
|
|
1430
1580
|
}
|
|
1431
1581
|
async pruneThreads(preserve) {
|
|
@@ -1445,19 +1595,19 @@ var LocalAgentStore = class {
|
|
|
1445
1595
|
}
|
|
1446
1596
|
const candidates = await Promise.all(names.filter((name) => !protectedNames.has(name)).map(async (name) => ({
|
|
1447
1597
|
name,
|
|
1448
|
-
modified: (await stat(
|
|
1598
|
+
modified: (await stat(join4(this.threadsDir, name))).mtimeMs
|
|
1449
1599
|
})));
|
|
1450
1600
|
candidates.sort((a, b) => a.modified - b.modified);
|
|
1451
1601
|
const keep = Math.max(0, this.maxThreads - protectedNames.size);
|
|
1452
1602
|
for (const entry of candidates.slice(0, Math.max(0, candidates.length - keep))) {
|
|
1453
|
-
await unlink(
|
|
1603
|
+
await unlink(join4(this.threadsDir, entry.name));
|
|
1454
1604
|
}
|
|
1455
1605
|
}
|
|
1456
1606
|
async save(state) {
|
|
1457
1607
|
assertThreadId(state.thread.id);
|
|
1458
1608
|
await this.ensureDirectories();
|
|
1459
1609
|
const destination = this.path(state.thread.id);
|
|
1460
|
-
const temporary =
|
|
1610
|
+
const temporary = join4(
|
|
1461
1611
|
this.threadsDir,
|
|
1462
1612
|
`.${state.thread.id}.${randomUUID3()}.tmp`
|
|
1463
1613
|
);
|
|
@@ -1477,7 +1627,7 @@ var LocalAgentStore = class {
|
|
|
1477
1627
|
async claimAction(actionId) {
|
|
1478
1628
|
assertThreadId(actionId);
|
|
1479
1629
|
await this.ensureDirectories();
|
|
1480
|
-
const path =
|
|
1630
|
+
const path = join4(this.claimsDir, `${actionId}.claim`);
|
|
1481
1631
|
try {
|
|
1482
1632
|
await writeFile2(path, `${(/* @__PURE__ */ new Date()).toISOString()}
|
|
1483
1633
|
`, {
|
|
@@ -1505,7 +1655,7 @@ var LocalAgentStore = class {
|
|
|
1505
1655
|
throw new Error("Invalid local agent thread file.");
|
|
1506
1656
|
}
|
|
1507
1657
|
const parsed = JSON.parse(await readFile(path, "utf8"));
|
|
1508
|
-
if (!
|
|
1658
|
+
if (!isRecord2(parsed) || !isRecord2(parsed.thread) || parsed.thread.id !== id || !Array.isArray(parsed.messages) || !Array.isArray(parsed.actions)) {
|
|
1509
1659
|
throw new Error("Local agent thread file is malformed.");
|
|
1510
1660
|
}
|
|
1511
1661
|
return parsed;
|
|
@@ -1528,88 +1678,6 @@ var LocalAgentStore = class {
|
|
|
1528
1678
|
}
|
|
1529
1679
|
};
|
|
1530
1680
|
|
|
1531
|
-
// src/config.ts
|
|
1532
|
-
import {
|
|
1533
|
-
chmodSync,
|
|
1534
|
-
existsSync as existsSync2,
|
|
1535
|
-
mkdirSync as mkdirSync2,
|
|
1536
|
-
readFileSync as readFileSync2,
|
|
1537
|
-
writeFileSync as writeFileSync2
|
|
1538
|
-
} from "fs";
|
|
1539
|
-
import { homedir } from "os";
|
|
1540
|
-
import { join as join4 } from "path";
|
|
1541
|
-
var AGENT_THINKING_LEVELS = [
|
|
1542
|
-
"off",
|
|
1543
|
-
"minimal",
|
|
1544
|
-
"low",
|
|
1545
|
-
"medium",
|
|
1546
|
-
"high",
|
|
1547
|
-
"xhigh",
|
|
1548
|
-
"max"
|
|
1549
|
-
];
|
|
1550
|
-
var CONFIG_DIR_NAME = "tuned-tensor";
|
|
1551
|
-
var CONFIG_FILE_NAME = "config.json";
|
|
1552
|
-
function getConfigDir() {
|
|
1553
|
-
const xdg = process.env.XDG_CONFIG_HOME;
|
|
1554
|
-
const base = xdg || join4(homedir(), ".config");
|
|
1555
|
-
return join4(base, CONFIG_DIR_NAME);
|
|
1556
|
-
}
|
|
1557
|
-
function getConfigPath() {
|
|
1558
|
-
return join4(getConfigDir(), CONFIG_FILE_NAME);
|
|
1559
|
-
}
|
|
1560
|
-
function readConfig() {
|
|
1561
|
-
const path = getConfigPath();
|
|
1562
|
-
if (!existsSync2(path)) return {};
|
|
1563
|
-
try {
|
|
1564
|
-
return JSON.parse(readFileSync2(path, "utf-8"));
|
|
1565
|
-
} catch {
|
|
1566
|
-
return {};
|
|
1567
|
-
}
|
|
1568
|
-
}
|
|
1569
|
-
var configRevision = 0;
|
|
1570
|
-
function getConfigRevision() {
|
|
1571
|
-
return configRevision;
|
|
1572
|
-
}
|
|
1573
|
-
function writeConfig(config) {
|
|
1574
|
-
const dir = getConfigDir();
|
|
1575
|
-
if (!existsSync2(dir)) mkdirSync2(dir, { recursive: true, mode: 448 });
|
|
1576
|
-
chmodSync(dir, 448);
|
|
1577
|
-
const path = getConfigPath();
|
|
1578
|
-
writeFileSync2(path, JSON.stringify(config, null, 2) + "\n", {
|
|
1579
|
-
mode: 384
|
|
1580
|
-
});
|
|
1581
|
-
chmodSync(path, 384);
|
|
1582
|
-
configRevision += 1;
|
|
1583
|
-
}
|
|
1584
|
-
function updateConfig(partial) {
|
|
1585
|
-
const current = readConfig();
|
|
1586
|
-
writeConfig({ ...current, ...partial });
|
|
1587
|
-
}
|
|
1588
|
-
function getApiKey(opts) {
|
|
1589
|
-
return opts?.apiKey || process.env.TUNED_TENSOR_API_KEY || readConfig().api_key;
|
|
1590
|
-
}
|
|
1591
|
-
function getAgentSelection(env = process.env) {
|
|
1592
|
-
const stored = readConfig().agent;
|
|
1593
|
-
const provider = env.TUNED_TENSOR_AGENT_PROVIDER || stored?.provider;
|
|
1594
|
-
const model = env.TUNED_TENSOR_AGENT_MODEL || stored?.model;
|
|
1595
|
-
const rawThinking = env.TUNED_TENSOR_AGENT_THINKING || stored?.thinking || "medium";
|
|
1596
|
-
if (!AGENT_THINKING_LEVELS.includes(rawThinking)) {
|
|
1597
|
-
throw new Error(
|
|
1598
|
-
"Agent thinking must be off, minimal, low, medium, high, xhigh, or max."
|
|
1599
|
-
);
|
|
1600
|
-
}
|
|
1601
|
-
if (!provider && !model) return void 0;
|
|
1602
|
-
if (!provider || !model) {
|
|
1603
|
-
throw new Error(
|
|
1604
|
-
"Both an agent provider and model are required. Run `tt agent configure --provider <provider> --model <model>`."
|
|
1605
|
-
);
|
|
1606
|
-
}
|
|
1607
|
-
return { provider, model, thinking: rawThinking };
|
|
1608
|
-
}
|
|
1609
|
-
|
|
1610
|
-
// src/cli.ts
|
|
1611
|
-
import { join as join7 } from "path";
|
|
1612
|
-
|
|
1613
1681
|
// src/agent.ts
|
|
1614
1682
|
import chalk3 from "chalk";
|
|
1615
1683
|
import { stripVTControlCharacters as stripVTControlCharacters3 } from "util";
|
|
@@ -1619,7 +1687,7 @@ import chalk from "chalk";
|
|
|
1619
1687
|
import { stat as stat3 } from "fs/promises";
|
|
1620
1688
|
import { homedir as homedir3 } from "os";
|
|
1621
1689
|
import { isAbsolute as isAbsolute2, resolve as resolve2 } from "path";
|
|
1622
|
-
import { createInterface } from "readline";
|
|
1690
|
+
import { createInterface as createInterface2 } from "readline";
|
|
1623
1691
|
import { stripVTControlCharacters } from "util";
|
|
1624
1692
|
|
|
1625
1693
|
// src/command-catalog.ts
|
|
@@ -1659,7 +1727,8 @@ var SLASH_COMMANDS = [
|
|
|
1659
1727
|
{ path: "/help", description: "Show commands; add a word to filter." },
|
|
1660
1728
|
{ path: "/status", description: "Show lightweight workflow status." },
|
|
1661
1729
|
{ path: "/context", description: "Show the current project context." },
|
|
1662
|
-
{ path: "/model", description: "Show or change the TT agent model." },
|
|
1730
|
+
{ path: "/model", description: "Show or change the TT agent provider and model." },
|
|
1731
|
+
{ path: "/login", description: "Save a provider API key; asks which provider if omitted." },
|
|
1663
1732
|
{ path: "/cd", description: "Change the shell's working directory." },
|
|
1664
1733
|
{ path: "/clear", description: "Clear the terminal." },
|
|
1665
1734
|
{ path: "/exit", description: "Exit the TT shell." }
|
|
@@ -1901,6 +1970,24 @@ function formatShellStatus(context) {
|
|
|
1901
1970
|
}
|
|
1902
1971
|
|
|
1903
1972
|
// src/agent-control.ts
|
|
1973
|
+
import { chmodSync as chmodSync3, existsSync as existsSync3 } from "fs";
|
|
1974
|
+
var FEATURED_AGENT_PROVIDERS = [
|
|
1975
|
+
"openai",
|
|
1976
|
+
"openrouter"
|
|
1977
|
+
];
|
|
1978
|
+
var RECOMMENDED_AGENT_MODELS = [
|
|
1979
|
+
{ provider: "openai", ids: ["gpt-5.6-sol"] },
|
|
1980
|
+
{
|
|
1981
|
+
provider: "openrouter",
|
|
1982
|
+
ids: ["deepseek/deepseek-v4-flash-0731", "deepseek/deepseek-v4-flash"]
|
|
1983
|
+
}
|
|
1984
|
+
];
|
|
1985
|
+
var FEATURED_PROVIDER_INDEX = new Map(
|
|
1986
|
+
FEATURED_AGENT_PROVIDERS.map((id, index) => [id, index])
|
|
1987
|
+
);
|
|
1988
|
+
function isFeaturedProvider(id) {
|
|
1989
|
+
return FEATURED_PROVIDER_INDEX.has(id);
|
|
1990
|
+
}
|
|
1904
1991
|
function modelKey(model) {
|
|
1905
1992
|
return `${model.provider}/${model.id}`;
|
|
1906
1993
|
}
|
|
@@ -1917,9 +2004,28 @@ function modelRelevance(model, query) {
|
|
|
1917
2004
|
if (name.includes(q)) return 4;
|
|
1918
2005
|
return void 0;
|
|
1919
2006
|
}
|
|
2007
|
+
function listAgentProviders(runtime, options = {}) {
|
|
2008
|
+
return runtime.getProviders().filter((provider) => !options.featuredOnly || isFeaturedProvider(provider.id)).map((provider) => ({
|
|
2009
|
+
id: provider.id,
|
|
2010
|
+
name: provider.name ?? provider.id,
|
|
2011
|
+
authenticated: runtime.hasConfiguredAuth(provider.id)
|
|
2012
|
+
})).sort((left, right) => {
|
|
2013
|
+
const leftRank = FEATURED_PROVIDER_INDEX.get(left.id);
|
|
2014
|
+
const rightRank = FEATURED_PROVIDER_INDEX.get(right.id);
|
|
2015
|
+
if (leftRank !== void 0 || rightRank !== void 0) {
|
|
2016
|
+
return (leftRank ?? Number.MAX_SAFE_INTEGER) - (rightRank ?? Number.MAX_SAFE_INTEGER) || left.id.localeCompare(right.id);
|
|
2017
|
+
}
|
|
2018
|
+
return left.id.localeCompare(right.id);
|
|
2019
|
+
});
|
|
2020
|
+
}
|
|
2021
|
+
function findAgentProvider(runtime, id) {
|
|
2022
|
+
const needle = id.trim().toLowerCase();
|
|
2023
|
+
if (!needle) return void 0;
|
|
2024
|
+
return listAgentProviders(runtime).find((provider) => provider.id.toLowerCase() === needle);
|
|
2025
|
+
}
|
|
1920
2026
|
function listAgentModels(runtime, options = {}) {
|
|
1921
2027
|
let models = runtime.getModels(options.provider).filter(
|
|
1922
|
-
(model) => options.includeUnauthenticated || runtime.hasConfiguredAuth(model.provider)
|
|
2028
|
+
(model) => (options.includeUnauthenticated || runtime.hasConfiguredAuth(model.provider)) && (!options.featuredOnly || isFeaturedProvider(model.provider))
|
|
1923
2029
|
).map((model) => ({
|
|
1924
2030
|
provider: model.provider,
|
|
1925
2031
|
id: model.id,
|
|
@@ -1946,6 +2052,19 @@ function listAgentModels(runtime, options = {}) {
|
|
|
1946
2052
|
}
|
|
1947
2053
|
return models;
|
|
1948
2054
|
}
|
|
2055
|
+
function recommendAgentModels(runtime, options = {}) {
|
|
2056
|
+
const models = listAgentModels(runtime, {
|
|
2057
|
+
includeUnauthenticated: options.includeUnauthenticated ?? true
|
|
2058
|
+
});
|
|
2059
|
+
const picks = [];
|
|
2060
|
+
for (const recommendation of RECOMMENDED_AGENT_MODELS) {
|
|
2061
|
+
const match = recommendation.ids.map((id) => models.find(
|
|
2062
|
+
(model) => model.provider === recommendation.provider && model.id === id
|
|
2063
|
+
)).find((model) => model !== void 0);
|
|
2064
|
+
if (match) picks.push(match);
|
|
2065
|
+
}
|
|
2066
|
+
return picks;
|
|
2067
|
+
}
|
|
1949
2068
|
function describeAgentModel(runtime, env) {
|
|
1950
2069
|
const selection = getAgentSelection(env);
|
|
1951
2070
|
if (!selection) return void 0;
|
|
@@ -1961,6 +2080,23 @@ function describeAgentModel(runtime, env) {
|
|
|
1961
2080
|
supportsThinking: resolved.model.reasoning !== false
|
|
1962
2081
|
};
|
|
1963
2082
|
}
|
|
2083
|
+
async function loginAgentProvider(runtime, provider, apiKey) {
|
|
2084
|
+
const choice = findAgentProvider(runtime, provider);
|
|
2085
|
+
if (!choice) {
|
|
2086
|
+
throw new Error(unknownAgentProviderMessage(provider));
|
|
2087
|
+
}
|
|
2088
|
+
const key = apiKey.trim();
|
|
2089
|
+
if (!key) {
|
|
2090
|
+
throw new Error("API key cannot be empty.");
|
|
2091
|
+
}
|
|
2092
|
+
if (typeof runtime.setRuntimeApiKey !== "function") {
|
|
2093
|
+
throw new Error("This session cannot store provider credentials.");
|
|
2094
|
+
}
|
|
2095
|
+
await runtime.setRuntimeApiKey(choice.id, key);
|
|
2096
|
+
const authPath = getAgentAuthPath();
|
|
2097
|
+
if (existsSync3(authPath)) chmodSync3(authPath, 384);
|
|
2098
|
+
return { provider: choice.id };
|
|
2099
|
+
}
|
|
1964
2100
|
function setAgentModel(runtime, env, provider, model, options = {}) {
|
|
1965
2101
|
const current = getAgentSelection(env);
|
|
1966
2102
|
const requestedThinking = options.thinking ?? current?.thinking ?? "medium";
|
|
@@ -1997,6 +2133,65 @@ function setAgentModel(runtime, env, provider, model, options = {}) {
|
|
|
1997
2133
|
return { selection, adjustedThinking: effectiveThinking !== requestedThinking };
|
|
1998
2134
|
}
|
|
1999
2135
|
|
|
2136
|
+
// src/secret-prompt.ts
|
|
2137
|
+
import { createInterface } from "readline/promises";
|
|
2138
|
+
import { stdin, stdout } from "process";
|
|
2139
|
+
import { Writable } from "stream";
|
|
2140
|
+
async function promptVisibleInput(message, input = stdin, output = stdout) {
|
|
2141
|
+
return await promptInput(message, false, input, output);
|
|
2142
|
+
}
|
|
2143
|
+
async function promptHiddenInput(message, input = stdin, output = stdout) {
|
|
2144
|
+
return await promptInput(message, true, input, output);
|
|
2145
|
+
}
|
|
2146
|
+
async function withDetachedKeypress(input, run) {
|
|
2147
|
+
const listeners = [...input.listeners("keypress")];
|
|
2148
|
+
input.removeAllListeners("keypress");
|
|
2149
|
+
try {
|
|
2150
|
+
return await run();
|
|
2151
|
+
} finally {
|
|
2152
|
+
input.removeAllListeners("keypress");
|
|
2153
|
+
for (const listener of listeners) {
|
|
2154
|
+
input.on("keypress", listener);
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
async function promptInput(message, hidden, input, output) {
|
|
2159
|
+
if (input.isTTY !== true || output.isTTY !== true) {
|
|
2160
|
+
throw new Error("Provider login needs an interactive tt session.");
|
|
2161
|
+
}
|
|
2162
|
+
return await withDetachedKeypress(input, async () => {
|
|
2163
|
+
if (!hidden) {
|
|
2164
|
+
const rl2 = createInterface({ input, output, terminal: true });
|
|
2165
|
+
try {
|
|
2166
|
+
return await rl2.question(message);
|
|
2167
|
+
} finally {
|
|
2168
|
+
rl2.close();
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
let muted = false;
|
|
2172
|
+
const maskedOutput = new Writable({
|
|
2173
|
+
write(chunk, _encoding, callback) {
|
|
2174
|
+
if (!muted) output.write(chunk);
|
|
2175
|
+
callback();
|
|
2176
|
+
}
|
|
2177
|
+
});
|
|
2178
|
+
const rl = createInterface({
|
|
2179
|
+
input,
|
|
2180
|
+
output: maskedOutput,
|
|
2181
|
+
terminal: true
|
|
2182
|
+
});
|
|
2183
|
+
try {
|
|
2184
|
+
const pending = rl.question(message);
|
|
2185
|
+
muted = true;
|
|
2186
|
+
return await pending;
|
|
2187
|
+
} finally {
|
|
2188
|
+
muted = false;
|
|
2189
|
+
output.write("\n");
|
|
2190
|
+
rl.close();
|
|
2191
|
+
}
|
|
2192
|
+
});
|
|
2193
|
+
}
|
|
2194
|
+
|
|
2000
2195
|
// src/shell.ts
|
|
2001
2196
|
var AGENT_SLASH_COMMANDS = /* @__PURE__ */ new Set([
|
|
2002
2197
|
"new",
|
|
@@ -2155,6 +2350,7 @@ var SLASH_NAMES = /* @__PURE__ */ new Set([
|
|
|
2155
2350
|
"status",
|
|
2156
2351
|
"context",
|
|
2157
2352
|
"model",
|
|
2353
|
+
"login",
|
|
2158
2354
|
"clear",
|
|
2159
2355
|
"cd",
|
|
2160
2356
|
"exit"
|
|
@@ -2324,6 +2520,9 @@ function agentModelLabel(context) {
|
|
|
2324
2520
|
}
|
|
2325
2521
|
function renderShellBanner(snapshot) {
|
|
2326
2522
|
const heading = snapshot.version ? `${accent.bold("tt")} ${chalk.dim(`v${snapshot.version}`)}` : accent.bold("tt");
|
|
2523
|
+
const configured = Boolean(
|
|
2524
|
+
snapshot.context.agent?.provider && snapshot.context.agent?.model
|
|
2525
|
+
);
|
|
2327
2526
|
const lines = [
|
|
2328
2527
|
heading,
|
|
2329
2528
|
chalk.dim(
|
|
@@ -2331,7 +2530,9 @@ function renderShellBanner(snapshot) {
|
|
|
2331
2530
|
),
|
|
2332
2531
|
chalk.dim("ctrl+c stop/clear \xB7 ctrl+d exit \xB7 /help commands \xB7 tab complete"),
|
|
2333
2532
|
"",
|
|
2334
|
-
chalk.dim(
|
|
2533
|
+
chalk.dim(
|
|
2534
|
+
configured ? "Ask TT anything. Known commands run directly." : "Use /model to choose a provider and model. Workflow commands work now."
|
|
2535
|
+
)
|
|
2335
2536
|
];
|
|
2336
2537
|
return `${lines.join("\n")}
|
|
2337
2538
|
|
|
@@ -2440,45 +2641,74 @@ var TunedTensorShellSession = class _TunedTensorShellSession {
|
|
|
2440
2641
|
const lines = styleDetailLines([
|
|
2441
2642
|
detailLine("Agent model", label),
|
|
2442
2643
|
detailLine("Change", "/model <provider>/<model>"),
|
|
2443
|
-
detailLine("
|
|
2644
|
+
detailLine("List", "/model <provider>"),
|
|
2645
|
+
detailLine("Search", "/model <query>"),
|
|
2646
|
+
detailLine("Login", "/login <provider>")
|
|
2444
2647
|
]);
|
|
2445
|
-
const
|
|
2446
|
-
|
|
2447
|
-
|
|
2648
|
+
const catalog = { includeUnauthenticated: true };
|
|
2649
|
+
const featuredCatalog = { ...catalog, featuredOnly: true };
|
|
2650
|
+
const provider = query ? findAgentProvider(runtime, query) : void 0;
|
|
2651
|
+
if (provider) {
|
|
2652
|
+
const matches = listAgentModels(runtime, { ...catalog, provider: provider.id });
|
|
2653
|
+
lines.push("", chalk.bold(`Models from ${provider.id}`));
|
|
2654
|
+
this.appendAgentModelChoices(lines, matches, summary, 20, "/model <query> to search");
|
|
2655
|
+
if (matches.length === 0) {
|
|
2656
|
+
lines.push(chalk.dim(" no models"));
|
|
2657
|
+
}
|
|
2448
2658
|
return lines;
|
|
2449
2659
|
}
|
|
2450
2660
|
if (query) {
|
|
2451
|
-
const matches = listAgentModels(runtime, { query });
|
|
2661
|
+
const matches = listAgentModels(runtime, { ...catalog, query });
|
|
2452
2662
|
lines.push("", chalk.bold(`Models matching ${JSON.stringify(query)}`));
|
|
2663
|
+
this.appendAgentModelChoices(lines, matches, summary, 10, "refine the query");
|
|
2453
2664
|
if (matches.length === 0) {
|
|
2454
2665
|
lines.push(chalk.dim(" no matches"));
|
|
2455
|
-
} else {
|
|
2456
|
-
const shown2 = matches.slice(0, 10);
|
|
2457
|
-
for (const model of shown2) {
|
|
2458
|
-
lines.push(this.formatAgentModelChoice(model, summary));
|
|
2459
|
-
}
|
|
2460
|
-
if (matches.length > shown2.length) {
|
|
2461
|
-
lines.push(chalk.dim(` \u2026 ${matches.length - shown2.length} more \u2014 refine the query`));
|
|
2462
|
-
}
|
|
2463
2666
|
}
|
|
2464
2667
|
return lines;
|
|
2465
2668
|
}
|
|
2466
|
-
const
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2669
|
+
const providers = listAgentProviders(runtime, { featuredOnly: true });
|
|
2670
|
+
lines.push("", chalk.bold("Providers"));
|
|
2671
|
+
if (providers.length === 0) {
|
|
2672
|
+
lines.push(chalk.dim(" no providers in the catalog"));
|
|
2673
|
+
} else {
|
|
2674
|
+
for (const choice of providers) {
|
|
2675
|
+
lines.push(this.formatAgentProviderChoice(choice));
|
|
2676
|
+
}
|
|
2677
|
+
lines.push(chalk.dim(" Other providers: /login <id> or /model <id>"));
|
|
2678
|
+
if (providers.some((choice) => !choice.authenticated)) {
|
|
2679
|
+
lines.push(chalk.dim(" Use /login <provider> to save a key."));
|
|
2680
|
+
}
|
|
2681
|
+
}
|
|
2682
|
+
const suggestions = recommendAgentModels(runtime, featuredCatalog);
|
|
2683
|
+
lines.push("", chalk.bold("Suggestions"));
|
|
2684
|
+
this.appendAgentModelChoices(lines, suggestions, summary);
|
|
2685
|
+
if (suggestions.length === 0) {
|
|
2686
|
+
lines.push(chalk.dim(" no models in the catalog"));
|
|
2687
|
+
} else {
|
|
2688
|
+
lines.push(chalk.dim(" Use /model <provider> to list models, /model <query> to search"));
|
|
2689
|
+
}
|
|
2690
|
+
return lines;
|
|
2691
|
+
}
|
|
2692
|
+
appendAgentModelChoices(lines, models, summary, limit, moreHint) {
|
|
2693
|
+
const shown = limit ? models.slice(0, limit) : models;
|
|
2470
2694
|
for (const model of shown) {
|
|
2471
2695
|
lines.push(this.formatAgentModelChoice(model, summary));
|
|
2472
2696
|
}
|
|
2473
|
-
if (
|
|
2474
|
-
|
|
2697
|
+
if (limit && models.length > shown.length) {
|
|
2698
|
+
const hint = moreHint ? ` \u2014 ${moreHint}` : "";
|
|
2699
|
+
lines.push(chalk.dim(` \u2026 ${models.length - shown.length} more${hint}`));
|
|
2475
2700
|
}
|
|
2476
|
-
|
|
2701
|
+
}
|
|
2702
|
+
formatAgentProviderChoice(provider) {
|
|
2703
|
+
const displayName = provider.name !== provider.id ? ` ${provider.name}` : "";
|
|
2704
|
+
const auth = provider.authenticated ? "" : chalk.dim(" auth required");
|
|
2705
|
+
return ` ${accent(provider.id)}${displayName}${auth}`;
|
|
2477
2706
|
}
|
|
2478
2707
|
formatAgentModelChoice(model, summary) {
|
|
2479
2708
|
const active = summary?.provider === model.provider && summary?.model === model.id;
|
|
2480
2709
|
const displayName = model.name !== model.id ? ` ${model.name}` : "";
|
|
2481
|
-
|
|
2710
|
+
const auth = model.authenticated ? "" : chalk.dim(" auth required");
|
|
2711
|
+
return ` ${accent(`${model.provider}/${model.id}`)}${displayName}${active ? chalk.dim(" (active)") : ""}${auth}`;
|
|
2482
2712
|
}
|
|
2483
2713
|
async handleSlash(command) {
|
|
2484
2714
|
switch (command.name) {
|
|
@@ -2506,7 +2736,7 @@ var TunedTensorShellSession = class _TunedTensorShellSession {
|
|
|
2506
2736
|
return "continue";
|
|
2507
2737
|
}
|
|
2508
2738
|
if (command.args.length !== 1) {
|
|
2509
|
-
throw new ShellParseError("Usage: /model [<query> | <provider>/<model>]");
|
|
2739
|
+
throw new ShellParseError("Usage: /model [<query> | <provider> | <provider>/<model>]");
|
|
2510
2740
|
}
|
|
2511
2741
|
const target = command.args[0];
|
|
2512
2742
|
const slash = target.indexOf("/");
|
|
@@ -2527,6 +2757,48 @@ var TunedTensorShellSession = class _TunedTensorShellSession {
|
|
|
2527
2757
|
const note = result2.adjustedThinking ? " \u2014 thinking set to off for this model" : "";
|
|
2528
2758
|
this.io.write(
|
|
2529
2759
|
`${successMark()} Agent model: ${result2.selection.provider}/${result2.selection.model} (thinking ${result2.selection.thinking})${note}.
|
|
2760
|
+
`
|
|
2761
|
+
);
|
|
2762
|
+
return "continue";
|
|
2763
|
+
}
|
|
2764
|
+
case "login": {
|
|
2765
|
+
const runtime = await this.requireAgentModelRuntime();
|
|
2766
|
+
if (command.args.length > 1) {
|
|
2767
|
+
throw new ShellParseError("Usage: /login [<provider>]");
|
|
2768
|
+
}
|
|
2769
|
+
let providerId = command.args[0]?.trim();
|
|
2770
|
+
if (!providerId) {
|
|
2771
|
+
const providers = listAgentProviders(runtime, { featuredOnly: true });
|
|
2772
|
+
if (providers.length === 0) {
|
|
2773
|
+
throw new ShellParseError("No providers in the catalog.");
|
|
2774
|
+
}
|
|
2775
|
+
this.writeLines([
|
|
2776
|
+
"",
|
|
2777
|
+
chalk.bold("Providers"),
|
|
2778
|
+
...providers.map((choice) => this.formatAgentProviderChoice(choice)),
|
|
2779
|
+
chalk.dim(" Other providers: /login <id>")
|
|
2780
|
+
]);
|
|
2781
|
+
if (!this.io.promptLine) {
|
|
2782
|
+
throw new ShellParseError("Usage: /login <provider>");
|
|
2783
|
+
}
|
|
2784
|
+
providerId = (await this.io.promptLine("Provider: ")).trim();
|
|
2785
|
+
if (!providerId) {
|
|
2786
|
+
throw new ShellParseError("Usage: /login <provider>");
|
|
2787
|
+
}
|
|
2788
|
+
}
|
|
2789
|
+
const provider = findAgentProvider(runtime, providerId);
|
|
2790
|
+
if (!provider) {
|
|
2791
|
+
throw new ShellParseError(unknownAgentProviderMessage(providerId));
|
|
2792
|
+
}
|
|
2793
|
+
if (!this.io.promptSecret) {
|
|
2794
|
+
throw new ShellParseError("Provider login needs an interactive tt session.");
|
|
2795
|
+
}
|
|
2796
|
+
const label = provider.name !== provider.id ? provider.name : provider.id;
|
|
2797
|
+
const apiKey = await this.io.promptSecret(`${label} API key: `);
|
|
2798
|
+
const result2 = await loginAgentProvider(runtime, provider.id, apiKey);
|
|
2799
|
+
await this.refreshContext();
|
|
2800
|
+
this.io.write(
|
|
2801
|
+
`${successMark()} Saved ${result2.provider} credentials. Chat and /model can use this provider now.
|
|
2530
2802
|
`
|
|
2531
2803
|
);
|
|
2532
2804
|
return "continue";
|
|
@@ -2653,6 +2925,26 @@ async function startInteractiveShell(options) {
|
|
|
2653
2925
|
}
|
|
2654
2926
|
let readline;
|
|
2655
2927
|
const terminalInput = input;
|
|
2928
|
+
const withPrompt = async (message, prompt) => {
|
|
2929
|
+
readline?.pause();
|
|
2930
|
+
const restoreRawMode = terminalInput.isRaw === true && typeof terminalInput.setRawMode === "function";
|
|
2931
|
+
if (restoreRawMode) terminalInput.setRawMode(false);
|
|
2932
|
+
try {
|
|
2933
|
+
return await prompt(message, input, output);
|
|
2934
|
+
} finally {
|
|
2935
|
+
if (restoreRawMode) terminalInput.setRawMode(true);
|
|
2936
|
+
readline?.resume();
|
|
2937
|
+
}
|
|
2938
|
+
};
|
|
2939
|
+
const io = {
|
|
2940
|
+
...streamIO(output, error),
|
|
2941
|
+
async promptLine(message) {
|
|
2942
|
+
return await withPrompt(message, promptVisibleInput);
|
|
2943
|
+
},
|
|
2944
|
+
async promptSecret(message) {
|
|
2945
|
+
return await withPrompt(message, promptHiddenInput);
|
|
2946
|
+
}
|
|
2947
|
+
};
|
|
2656
2948
|
const foregroundRunner = (request) => withForegroundSignalHandoff(
|
|
2657
2949
|
{
|
|
2658
2950
|
input: terminalInput,
|
|
@@ -2663,7 +2955,7 @@ async function startInteractiveShell(options) {
|
|
|
2663
2955
|
);
|
|
2664
2956
|
const session = await createShellSession({
|
|
2665
2957
|
runner: foregroundRunner,
|
|
2666
|
-
io
|
|
2958
|
+
io,
|
|
2667
2959
|
agent: options.agent,
|
|
2668
2960
|
cwd: options.cwd,
|
|
2669
2961
|
env: options.env,
|
|
@@ -2671,7 +2963,7 @@ async function startInteractiveShell(options) {
|
|
|
2671
2963
|
agentModelRuntime: options.agentModelRuntime
|
|
2672
2964
|
});
|
|
2673
2965
|
const completer = createCommandCompleter(() => session.snapshot().mode);
|
|
2674
|
-
readline =
|
|
2966
|
+
readline = createInterface2({
|
|
2675
2967
|
input,
|
|
2676
2968
|
output,
|
|
2677
2969
|
terminal: true,
|
|
@@ -2844,7 +3136,7 @@ var StreamingTerminalMarkdown = class {
|
|
|
2844
3136
|
var accent3 = chalk3.hex("#8B5CF6");
|
|
2845
3137
|
var successMark2 = () => chalk3.green("\u2713");
|
|
2846
3138
|
var errorMark2 = () => chalk3.red("\u2717");
|
|
2847
|
-
function
|
|
3139
|
+
function isRecord3(value) {
|
|
2848
3140
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2849
3141
|
}
|
|
2850
3142
|
function stringValue(value) {
|
|
@@ -2885,7 +3177,7 @@ function formatThread(thread, activeId) {
|
|
|
2885
3177
|
return `${active} ${chalk3.dim(thread.id.slice(0, 8))} ${sanitizeTerminalText2(thread.title)} ${chalk3.dim(dateLabel)}`;
|
|
2886
3178
|
}
|
|
2887
3179
|
function actionFromPayload(payload) {
|
|
2888
|
-
const value =
|
|
3180
|
+
const value = isRecord3(payload.action) ? payload.action : isRecord3(payload.approval) ? payload.approval : payload;
|
|
2889
3181
|
const id = stringValue(value.id);
|
|
2890
3182
|
const title = stringValue(value.title);
|
|
2891
3183
|
if (!id || !title) return null;
|
|
@@ -3018,7 +3310,7 @@ ${chalk3.yellow.bold("Approval required")} ${chalk3.dim(action.id.slice(0, 8))}
|
|
|
3018
3310
|
if (event.type === "tool_call") {
|
|
3019
3311
|
this.flushResponse();
|
|
3020
3312
|
this.endOpenLine();
|
|
3021
|
-
const name = stringValue(payload.name) ?? stringValue(
|
|
3313
|
+
const name = stringValue(payload.name) ?? stringValue(isRecord3(payload.tool_call) ? payload.tool_call.name : null) ?? "tool";
|
|
3022
3314
|
this.options.io.write(chalk3.dim(` \u25CB ${name}
|
|
3023
3315
|
`));
|
|
3024
3316
|
return;
|
|
@@ -3276,7 +3568,7 @@ ${chalk3.yellow.bold("Approval required")} ${chalk3.dim(action.id.slice(0, 8))}
|
|
|
3276
3568
|
import { constants as osConstants } from "os";
|
|
3277
3569
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
3278
3570
|
import { spawn } from "child_process";
|
|
3279
|
-
import { existsSync as
|
|
3571
|
+
import { existsSync as existsSync4 } from "fs";
|
|
3280
3572
|
import { fileURLToPath } from "url";
|
|
3281
3573
|
import {
|
|
3282
3574
|
readFile as readFile3,
|
|
@@ -3454,7 +3746,7 @@ function shortId(id) {
|
|
|
3454
3746
|
}
|
|
3455
3747
|
|
|
3456
3748
|
// src/local-output.ts
|
|
3457
|
-
function
|
|
3749
|
+
function isRecord4(value) {
|
|
3458
3750
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
3459
3751
|
}
|
|
3460
3752
|
function text(value) {
|
|
@@ -3468,10 +3760,10 @@ function number(value) {
|
|
|
3468
3760
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
3469
3761
|
}
|
|
3470
3762
|
function record3(value) {
|
|
3471
|
-
return
|
|
3763
|
+
return isRecord4(value) ? value : void 0;
|
|
3472
3764
|
}
|
|
3473
3765
|
function rows(value) {
|
|
3474
|
-
return Array.isArray(value) ? value.filter(
|
|
3766
|
+
return Array.isArray(value) ? value.filter(isRecord4) : [];
|
|
3475
3767
|
}
|
|
3476
3768
|
function displayId(value) {
|
|
3477
3769
|
const id = text(value);
|
|
@@ -3761,19 +4053,19 @@ function renderServePlan(value) {
|
|
|
3761
4053
|
function renderKnownJson(args2, value) {
|
|
3762
4054
|
const command = args2[0];
|
|
3763
4055
|
const subcommand = args2[1];
|
|
3764
|
-
if (command === "doctor" &&
|
|
4056
|
+
if (command === "doctor" && isRecord4(value)) {
|
|
3765
4057
|
renderDoctor(value);
|
|
3766
4058
|
return true;
|
|
3767
4059
|
}
|
|
3768
|
-
if (command === "init" &&
|
|
4060
|
+
if (command === "init" && isRecord4(value)) {
|
|
3769
4061
|
renderInit(value);
|
|
3770
4062
|
return true;
|
|
3771
4063
|
}
|
|
3772
|
-
if (command === "validate" &&
|
|
4064
|
+
if (command === "validate" && isRecord4(value)) {
|
|
3773
4065
|
renderValidate(value);
|
|
3774
4066
|
return true;
|
|
3775
4067
|
}
|
|
3776
|
-
if (command === "run" &&
|
|
4068
|
+
if (command === "run" && isRecord4(value)) {
|
|
3777
4069
|
renderRun(value);
|
|
3778
4070
|
return true;
|
|
3779
4071
|
}
|
|
@@ -3786,15 +4078,15 @@ function renderKnownJson(args2, value) {
|
|
|
3786
4078
|
renderRunEvents(value);
|
|
3787
4079
|
return true;
|
|
3788
4080
|
}
|
|
3789
|
-
if (subcommand === "get" &&
|
|
4081
|
+
if (subcommand === "get" && isRecord4(value)) {
|
|
3790
4082
|
renderRunRecord(value);
|
|
3791
4083
|
return true;
|
|
3792
4084
|
}
|
|
3793
|
-
if (subcommand === "report" &&
|
|
4085
|
+
if (subcommand === "report" && isRecord4(value)) {
|
|
3794
4086
|
renderRunReport(value);
|
|
3795
4087
|
return true;
|
|
3796
4088
|
}
|
|
3797
|
-
if (subcommand === "compare" &&
|
|
4089
|
+
if (subcommand === "compare" && isRecord4(value)) {
|
|
3798
4090
|
renderRunComparison(value);
|
|
3799
4091
|
return true;
|
|
3800
4092
|
}
|
|
@@ -3804,25 +4096,25 @@ function renderKnownJson(args2, value) {
|
|
|
3804
4096
|
renderModelsList(value);
|
|
3805
4097
|
return true;
|
|
3806
4098
|
}
|
|
3807
|
-
if ((subcommand === "get" || subcommand === "verify") &&
|
|
4099
|
+
if ((subcommand === "get" || subcommand === "verify") && isRecord4(value)) {
|
|
3808
4100
|
if (subcommand === "verify") printSuccess("Local model artifact is valid");
|
|
3809
4101
|
renderModelRecord(value);
|
|
3810
4102
|
return true;
|
|
3811
4103
|
}
|
|
3812
|
-
if ((subcommand === "prefetch" || subcommand === "verify-base") &&
|
|
4104
|
+
if ((subcommand === "prefetch" || subcommand === "verify-base") && isRecord4(value)) {
|
|
3813
4105
|
renderModelPrefetch(value);
|
|
3814
4106
|
return true;
|
|
3815
4107
|
}
|
|
3816
|
-
if (["active", "activate", "rollback"].includes(subcommand ?? "") &&
|
|
4108
|
+
if (["active", "activate", "rollback"].includes(subcommand ?? "") && isRecord4(value)) {
|
|
3817
4109
|
renderActiveModel(value);
|
|
3818
4110
|
return true;
|
|
3819
4111
|
}
|
|
3820
|
-
if (subcommand === "serve" &&
|
|
4112
|
+
if (subcommand === "serve" && isRecord4(value)) {
|
|
3821
4113
|
renderServePlan(value);
|
|
3822
4114
|
return true;
|
|
3823
4115
|
}
|
|
3824
4116
|
}
|
|
3825
|
-
if (command === "serve" &&
|
|
4117
|
+
if (command === "serve" && isRecord4(value)) {
|
|
3826
4118
|
renderServePlan(value);
|
|
3827
4119
|
return true;
|
|
3828
4120
|
}
|
|
@@ -4096,7 +4388,7 @@ async function projectSpecArguments(originalArgs, cwd) {
|
|
|
4096
4388
|
}
|
|
4097
4389
|
function resolveLocalCliEntrypoint() {
|
|
4098
4390
|
const entrypoint = fileURLToPath(new URL("./local-runtime.js", import.meta.url));
|
|
4099
|
-
if (!
|
|
4391
|
+
if (!existsSync4(entrypoint)) {
|
|
4100
4392
|
throw new Error(
|
|
4101
4393
|
"The bundled local runtime is missing. Rebuild or reinstall @tuned-tensor/cli."
|
|
4102
4394
|
);
|
|
@@ -4107,10 +4399,10 @@ function localCommandStreamsStdout(args2) {
|
|
|
4107
4399
|
const serves = args2[0] === "serve" || args2[0] === "models" && args2[1] === "serve";
|
|
4108
4400
|
return serves && !args2.includes("--print-command") && !args2.includes("--help") && !args2.includes("-h");
|
|
4109
4401
|
}
|
|
4110
|
-
function parseJsonOutput(
|
|
4111
|
-
if (!
|
|
4402
|
+
function parseJsonOutput(stdout2) {
|
|
4403
|
+
if (!stdout2.trim()) return { hasJson: false, json: void 0 };
|
|
4112
4404
|
try {
|
|
4113
|
-
return { hasJson: true, json: JSON.parse(
|
|
4405
|
+
return { hasJson: true, json: JSON.parse(stdout2) };
|
|
4114
4406
|
} catch {
|
|
4115
4407
|
return { hasJson: false, json: void 0 };
|
|
4116
4408
|
}
|
|
@@ -4156,7 +4448,7 @@ async function spawnLocalCli(args2) {
|
|
|
4156
4448
|
}
|
|
4157
4449
|
);
|
|
4158
4450
|
if (pipeInput && args2.stdin && child.stdin) args2.stdin.pipe(child.stdin);
|
|
4159
|
-
let
|
|
4451
|
+
let stdout2 = "";
|
|
4160
4452
|
let stderr = "";
|
|
4161
4453
|
child.stdout?.setEncoding("utf8");
|
|
4162
4454
|
child.stderr?.setEncoding("utf8");
|
|
@@ -4166,7 +4458,7 @@ async function spawnLocalCli(args2) {
|
|
|
4166
4458
|
args2.pathReplacements
|
|
4167
4459
|
);
|
|
4168
4460
|
if (args2.streamingStdout) writeStream(output, normalized);
|
|
4169
|
-
else
|
|
4461
|
+
else stdout2 += normalized;
|
|
4170
4462
|
});
|
|
4171
4463
|
child.stderr?.on("data", (chunk) => {
|
|
4172
4464
|
const normalized = normalizeProjectedPaths(
|
|
@@ -4211,7 +4503,7 @@ async function spawnLocalCli(args2) {
|
|
|
4211
4503
|
resolveChild({
|
|
4212
4504
|
exitCode: spawnError ? 1 : code2 ?? signalExitCode(signal),
|
|
4213
4505
|
signal,
|
|
4214
|
-
stdout,
|
|
4506
|
+
stdout: stdout2,
|
|
4215
4507
|
stderr,
|
|
4216
4508
|
...spawnError ? { errorMessage: spawnError.message } : {}
|
|
4217
4509
|
});
|
|
@@ -4426,7 +4718,7 @@ function registerAgentCommands(parent, options) {
|
|
|
4426
4718
|
}
|
|
4427
4719
|
|
|
4428
4720
|
// src/commands/pipeline.ts
|
|
4429
|
-
import { existsSync as
|
|
4721
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
4430
4722
|
import { resolve as resolve4 } from "path";
|
|
4431
4723
|
var DEFAULT_PIPELINE_FILE = "tunedtensor.pipeline.json";
|
|
4432
4724
|
function parseList(value) {
|
|
@@ -4437,7 +4729,7 @@ function parseList(value) {
|
|
|
4437
4729
|
}
|
|
4438
4730
|
function loadPipeline(path) {
|
|
4439
4731
|
const resolved = resolve4(path);
|
|
4440
|
-
if (!
|
|
4732
|
+
if (!existsSync5(resolved)) throw new Error(`Pipeline file not found: ${path}. Run \`tt pipeline init\` to create one.`);
|
|
4441
4733
|
try {
|
|
4442
4734
|
return JSON.parse(readFileSync4(resolved, "utf8"));
|
|
4443
4735
|
} catch (error) {
|
|
@@ -4456,7 +4748,7 @@ function registerPipelineCommands(parent) {
|
|
|
4456
4748
|
const pipeline = parent.command("pipeline").description("Create, validate, inspect, and safely dry-run ordered pipeline recipes");
|
|
4457
4749
|
pipeline.command("init").description("Write a canonical v1 pipeline recipe").option("-f, --file <path>", "Output file", DEFAULT_PIPELINE_FILE).action((options) => {
|
|
4458
4750
|
const path = resolve4(options.file);
|
|
4459
|
-
if (
|
|
4751
|
+
if (existsSync5(path)) throw new Error(`Pipeline file already exists: ${options.file}.`);
|
|
4460
4752
|
const recipe = canonicalPipeline("local");
|
|
4461
4753
|
writeFileSync3(path, `${JSON.stringify(recipe, null, 2)}
|
|
4462
4754
|
`);
|
|
@@ -4611,7 +4903,7 @@ async function createDefaultAgentClient(runtime, env, workspaceRoot, getModelRun
|
|
|
4611
4903
|
const selection = getAgentSelection(env);
|
|
4612
4904
|
if (!selection) {
|
|
4613
4905
|
throw new Error(
|
|
4614
|
-
"The laptop-local agent is not configured.
|
|
4906
|
+
"The laptop-local agent is not configured. Use /model to choose a provider and model, then try again."
|
|
4615
4907
|
);
|
|
4616
4908
|
}
|
|
4617
4909
|
const modelRuntime = await getModelRuntime();
|
|
@@ -4620,8 +4912,9 @@ async function createDefaultAgentClient(runtime, env, workspaceRoot, getModelRun
|
|
|
4620
4912
|
const secret = getApiKey({ apiKey: env.TUNED_TENSOR_API_KEY });
|
|
4621
4913
|
const providerSecrets = Object.entries(env).filter(([name, value]) => value && /(?:API_KEY|TOKEN|SECRET)$/i.test(name)).map(([, value]) => value).filter((value) => value.length >= 8 && value !== secret);
|
|
4622
4914
|
return createLocalAgentClient({
|
|
4623
|
-
store: runtime.agentStore ?? new LocalAgentStore(
|
|
4624
|
-
secretValues: [...secret ? [secret] : [], ...providerSecrets]
|
|
4915
|
+
store: runtime.agentStore ?? new LocalAgentStore(getAgentConfigDir(), {
|
|
4916
|
+
secretValues: [...secret ? [secret] : [], ...providerSecrets],
|
|
4917
|
+
secretValueProvider: readStoredProviderSecrets
|
|
4625
4918
|
}),
|
|
4626
4919
|
workspaceRoot,
|
|
4627
4920
|
selection,
|
|
@@ -4893,7 +5186,7 @@ async function runCli(version, runtime = {}) {
|
|
|
4893
5186
|
}
|
|
4894
5187
|
|
|
4895
5188
|
// src/index.ts
|
|
4896
|
-
runCli("0.13.0").catch((err) => {
|
|
5189
|
+
runCli("0.13.1-beta.0").catch((err) => {
|
|
4897
5190
|
reportError(err);
|
|
4898
5191
|
process.exit(1);
|
|
4899
5192
|
});
|