@llmist/cli 16.0.4 → 16.1.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/dist/cli.js +1514 -1117
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +88 -44
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -110,7 +110,7 @@ import { Command, InvalidArgumentError as InvalidArgumentError2 } from "commande
|
|
|
110
110
|
// package.json
|
|
111
111
|
var package_default = {
|
|
112
112
|
name: "@llmist/cli",
|
|
113
|
-
version: "16.0
|
|
113
|
+
version: "16.1.0",
|
|
114
114
|
description: "CLI for llmist - run LLM agents from the command line",
|
|
115
115
|
type: "module",
|
|
116
116
|
main: "dist/cli.js",
|
|
@@ -167,7 +167,7 @@ var package_default = {
|
|
|
167
167
|
node: ">=22.0.0"
|
|
168
168
|
},
|
|
169
169
|
dependencies: {
|
|
170
|
-
llmist: "^16.0
|
|
170
|
+
llmist: "^16.1.0",
|
|
171
171
|
"@unblessed/node": "^1.0.0-alpha.23",
|
|
172
172
|
"diff-match-patch": "^1.0.5",
|
|
173
173
|
chalk: "^5.6.2",
|
|
@@ -182,7 +182,7 @@ var package_default = {
|
|
|
182
182
|
zod: "^4.1.12"
|
|
183
183
|
},
|
|
184
184
|
devDependencies: {
|
|
185
|
-
"@llmist/testing": "^16.0
|
|
185
|
+
"@llmist/testing": "^16.1.0",
|
|
186
186
|
"@types/diff": "^8.0.0",
|
|
187
187
|
"@types/diff-match-patch": "^1.0.36",
|
|
188
188
|
"@types/js-yaml": "^4.0.9",
|
|
@@ -453,7 +453,6 @@ var finish = createGadget2({
|
|
|
453
453
|
throw new TaskCompletionSignal("Task completed");
|
|
454
454
|
}
|
|
455
455
|
});
|
|
456
|
-
var builtinGadgets = [askUser, tellUser, finish, createTextToSpeech()];
|
|
457
456
|
function getBuiltinGadgets(speechConfig) {
|
|
458
457
|
return [askUser, tellUser, finish, createTextToSpeech(speechConfig)];
|
|
459
458
|
}
|
|
@@ -507,8 +506,6 @@ var AGENT_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
|
507
506
|
// Add to inherited gadgets
|
|
508
507
|
"gadget-remove",
|
|
509
508
|
// Remove from inherited gadgets
|
|
510
|
-
"gadget",
|
|
511
|
-
// DEPRECATED: alias for gadgets
|
|
512
509
|
"builtins",
|
|
513
510
|
"builtin-interaction",
|
|
514
511
|
"gadget-start-prefix",
|
|
@@ -559,18 +556,18 @@ var VALID_REASONING_EFFORTS = /* @__PURE__ */ new Set(["none", "low", "medium",
|
|
|
559
556
|
|
|
560
557
|
// src/paths.ts
|
|
561
558
|
import { homedir } from "os";
|
|
562
|
-
function expandTildePath(
|
|
563
|
-
if (!
|
|
564
|
-
return
|
|
559
|
+
function expandTildePath(path7) {
|
|
560
|
+
if (!path7.startsWith("~")) {
|
|
561
|
+
return path7;
|
|
565
562
|
}
|
|
566
|
-
return
|
|
563
|
+
return path7.replace(/^~/, homedir());
|
|
567
564
|
}
|
|
568
565
|
|
|
569
566
|
// src/config-validators.ts
|
|
570
567
|
var ConfigError = class extends Error {
|
|
571
|
-
constructor(message,
|
|
572
|
-
super(
|
|
573
|
-
this.path =
|
|
568
|
+
constructor(message, path7) {
|
|
569
|
+
super(path7 ? `${path7}: ${message}` : message);
|
|
570
|
+
this.path = path7;
|
|
574
571
|
this.name = "ConfigError";
|
|
575
572
|
}
|
|
576
573
|
};
|
|
@@ -935,9 +932,6 @@ function validateAgentFields(rawObj, section, result) {
|
|
|
935
932
|
section
|
|
936
933
|
);
|
|
937
934
|
}
|
|
938
|
-
if ("gadget" in rawObj) {
|
|
939
|
-
result.gadget = validateStringArray(rawObj.gadget, "gadget", section);
|
|
940
|
-
}
|
|
941
935
|
if ("builtins" in rawObj) {
|
|
942
936
|
result.builtins = validateBoolean(rawObj.builtins, "builtins", section);
|
|
943
937
|
}
|
|
@@ -1189,13 +1183,13 @@ function createTemplateEngine(prompts, configPath) {
|
|
|
1189
1183
|
functionHeader: "const includeFile = (path) => it.__includeFile(path);"
|
|
1190
1184
|
});
|
|
1191
1185
|
const includeStack = [];
|
|
1192
|
-
eta.__includeFileImpl = (
|
|
1193
|
-
if (includeStack.includes(
|
|
1194
|
-
throw new Error(`Circular include detected: ${[...includeStack,
|
|
1186
|
+
eta.__includeFileImpl = (path7) => {
|
|
1187
|
+
if (includeStack.includes(path7)) {
|
|
1188
|
+
throw new Error(`Circular include detected: ${[...includeStack, path7].join(" -> ")}`);
|
|
1195
1189
|
}
|
|
1196
|
-
includeStack.push(
|
|
1190
|
+
includeStack.push(path7);
|
|
1197
1191
|
try {
|
|
1198
|
-
const content = loadFileContents(
|
|
1192
|
+
const content = loadFileContents(path7, configDir);
|
|
1199
1193
|
if (hasTemplateSyntax(content)) {
|
|
1200
1194
|
const context = {
|
|
1201
1195
|
env: process.env,
|
|
@@ -1278,15 +1272,9 @@ function hasTemplateSyntax(str) {
|
|
|
1278
1272
|
// src/config-resolution.ts
|
|
1279
1273
|
function resolveGadgets(section, inheritedGadgets, sectionName, configPath) {
|
|
1280
1274
|
const hasGadgets = "gadgets" in section;
|
|
1281
|
-
const hasGadgetLegacy = "gadget" in section;
|
|
1282
1275
|
const hasGadgetAdd = "gadget-add" in section;
|
|
1283
1276
|
const hasGadgetRemove = "gadget-remove" in section;
|
|
1284
|
-
if (
|
|
1285
|
-
console.warn(
|
|
1286
|
-
`[config] Warning: [${sectionName}].gadget is deprecated, use 'gadgets' (plural) instead`
|
|
1287
|
-
);
|
|
1288
|
-
}
|
|
1289
|
-
if ((hasGadgets || hasGadgetLegacy) && (hasGadgetAdd || hasGadgetRemove)) {
|
|
1277
|
+
if (hasGadgets && (hasGadgetAdd || hasGadgetRemove)) {
|
|
1290
1278
|
throw new ConfigError(
|
|
1291
1279
|
`[${sectionName}] Cannot use 'gadgets' with 'gadget-add'/'gadget-remove'. Use either full replacement (gadgets) OR modification (gadget-add/gadget-remove).`,
|
|
1292
1280
|
configPath
|
|
@@ -1295,9 +1283,6 @@ function resolveGadgets(section, inheritedGadgets, sectionName, configPath) {
|
|
|
1295
1283
|
if (hasGadgets) {
|
|
1296
1284
|
return section.gadgets;
|
|
1297
1285
|
}
|
|
1298
|
-
if (hasGadgetLegacy) {
|
|
1299
|
-
return section.gadget;
|
|
1300
|
-
}
|
|
1301
1286
|
let result = [...inheritedGadgets];
|
|
1302
1287
|
if (hasGadgetRemove) {
|
|
1303
1288
|
const toRemove = new Set(section["gadget-remove"]);
|
|
@@ -1336,7 +1321,6 @@ function resolveInheritance(config, configPath) {
|
|
|
1336
1321
|
const {
|
|
1337
1322
|
inherits: _inherits,
|
|
1338
1323
|
gadgets: _gadgets,
|
|
1339
|
-
gadget: _gadget,
|
|
1340
1324
|
"gadget-add": _gadgetAdd,
|
|
1341
1325
|
"gadget-remove": _gadgetRemove,
|
|
1342
1326
|
...ownValues
|
|
@@ -1346,7 +1330,6 @@ function resolveInheritance(config, configPath) {
|
|
|
1346
1330
|
if (resolvedGadgets.length > 0) {
|
|
1347
1331
|
merged.gadgets = resolvedGadgets;
|
|
1348
1332
|
}
|
|
1349
|
-
delete merged.gadget;
|
|
1350
1333
|
delete merged["gadget-add"];
|
|
1351
1334
|
delete merged["gadget-remove"];
|
|
1352
1335
|
resolving.delete(name);
|
|
@@ -1600,18 +1583,93 @@ async function readFileBuffer(filePath, options = {}) {
|
|
|
1600
1583
|
}
|
|
1601
1584
|
|
|
1602
1585
|
// src/gadgets.ts
|
|
1603
|
-
import
|
|
1586
|
+
import fs7 from "fs";
|
|
1604
1587
|
import { createRequire } from "module";
|
|
1605
|
-
import
|
|
1588
|
+
import path6 from "path";
|
|
1606
1589
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
1607
1590
|
import { createJiti } from "jiti";
|
|
1608
1591
|
import { AbstractGadget } from "llmist";
|
|
1609
1592
|
|
|
1610
|
-
// src/builtins/filesystem/
|
|
1611
|
-
import
|
|
1593
|
+
// src/builtins/filesystem/delete-file.ts
|
|
1594
|
+
import fs2 from "fs";
|
|
1612
1595
|
import { createGadget as createGadget3 } from "llmist";
|
|
1613
1596
|
import { z as z3 } from "zod";
|
|
1614
1597
|
|
|
1598
|
+
// src/builtins/filesystem/utils.ts
|
|
1599
|
+
import fs from "fs";
|
|
1600
|
+
import path from "path";
|
|
1601
|
+
var PathSandboxException = class extends Error {
|
|
1602
|
+
constructor(inputPath, reason) {
|
|
1603
|
+
super(`Path access denied: ${inputPath}. ${reason}`);
|
|
1604
|
+
this.name = "PathSandboxException";
|
|
1605
|
+
}
|
|
1606
|
+
};
|
|
1607
|
+
function validatePathIsWithinCwd(inputPath) {
|
|
1608
|
+
const cwd = process.cwd();
|
|
1609
|
+
const resolvedPath = path.resolve(cwd, inputPath);
|
|
1610
|
+
let finalPath;
|
|
1611
|
+
try {
|
|
1612
|
+
finalPath = fs.realpathSync(resolvedPath);
|
|
1613
|
+
} catch (error) {
|
|
1614
|
+
const nodeError = error;
|
|
1615
|
+
if (nodeError.code === "ENOENT") {
|
|
1616
|
+
finalPath = resolvedPath;
|
|
1617
|
+
} else {
|
|
1618
|
+
throw error;
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
const cwdWithSep = cwd + path.sep;
|
|
1622
|
+
if (!finalPath.startsWith(cwdWithSep) && finalPath !== cwd) {
|
|
1623
|
+
throw new PathSandboxException(inputPath, "Path is outside the current working directory");
|
|
1624
|
+
}
|
|
1625
|
+
return finalPath;
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
// src/builtins/filesystem/delete-file.ts
|
|
1629
|
+
var deleteFile = createGadget3({
|
|
1630
|
+
name: "DeleteFile",
|
|
1631
|
+
description: "Delete a file or directory from the local filesystem. The path must be within the current working directory or its subdirectories.",
|
|
1632
|
+
maxConcurrent: 1,
|
|
1633
|
+
// Sequential execution to prevent race conditions
|
|
1634
|
+
schema: z3.object({
|
|
1635
|
+
filePath: z3.string().describe("Path to the file or directory to delete (relative or absolute)"),
|
|
1636
|
+
recursive: z3.boolean().optional().default(false).describe("If true, perform a recursive deletion (required for directories)")
|
|
1637
|
+
}),
|
|
1638
|
+
examples: [
|
|
1639
|
+
{
|
|
1640
|
+
params: { filePath: "temp.txt", recursive: false },
|
|
1641
|
+
output: "path=temp.txt\n\nDeleted file successfully",
|
|
1642
|
+
comment: "Delete a single file"
|
|
1643
|
+
},
|
|
1644
|
+
{
|
|
1645
|
+
params: { filePath: "tmp-dir", recursive: true },
|
|
1646
|
+
output: "path=tmp-dir\n\nDeleted directory successfully",
|
|
1647
|
+
comment: "Delete a directory and its contents"
|
|
1648
|
+
}
|
|
1649
|
+
],
|
|
1650
|
+
execute: ({ filePath, recursive }) => {
|
|
1651
|
+
const validatedPath = validatePathIsWithinCwd(filePath);
|
|
1652
|
+
if (!fs2.existsSync(validatedPath)) {
|
|
1653
|
+
return `Error: Path does not exist: ${filePath}`;
|
|
1654
|
+
}
|
|
1655
|
+
const stats = fs2.statSync(validatedPath);
|
|
1656
|
+
const isDirectory = stats.isDirectory();
|
|
1657
|
+
if (isDirectory && !recursive) {
|
|
1658
|
+
return `Error: ${filePath} is a directory. Set recursive=true to delete it.`;
|
|
1659
|
+
}
|
|
1660
|
+
fs2.rmSync(validatedPath, { recursive, force: true });
|
|
1661
|
+
const type = isDirectory ? "directory" : "file";
|
|
1662
|
+
return `path=${filePath}
|
|
1663
|
+
|
|
1664
|
+
Deleted ${type} successfully`;
|
|
1665
|
+
}
|
|
1666
|
+
});
|
|
1667
|
+
|
|
1668
|
+
// src/builtins/filesystem/edit-file.ts
|
|
1669
|
+
import { readFileSync as readFileSync3, writeFileSync } from "fs";
|
|
1670
|
+
import { createGadget as createGadget4 } from "llmist";
|
|
1671
|
+
import { z as z4 } from "zod";
|
|
1672
|
+
|
|
1615
1673
|
// src/builtins/filesystem/editfile/matcher.ts
|
|
1616
1674
|
import DiffMatchPatch from "diff-match-patch";
|
|
1617
1675
|
var dmp = new DiffMatchPatch();
|
|
@@ -2029,36 +2087,6 @@ function formatMultipleMatches(content, matches, maxMatches = 5) {
|
|
|
2029
2087
|
return output.join("\n");
|
|
2030
2088
|
}
|
|
2031
2089
|
|
|
2032
|
-
// src/builtins/filesystem/utils.ts
|
|
2033
|
-
import fs from "fs";
|
|
2034
|
-
import path from "path";
|
|
2035
|
-
var PathSandboxException = class extends Error {
|
|
2036
|
-
constructor(inputPath, reason) {
|
|
2037
|
-
super(`Path access denied: ${inputPath}. ${reason}`);
|
|
2038
|
-
this.name = "PathSandboxException";
|
|
2039
|
-
}
|
|
2040
|
-
};
|
|
2041
|
-
function validatePathIsWithinCwd(inputPath) {
|
|
2042
|
-
const cwd = process.cwd();
|
|
2043
|
-
const resolvedPath = path.resolve(cwd, inputPath);
|
|
2044
|
-
let finalPath;
|
|
2045
|
-
try {
|
|
2046
|
-
finalPath = fs.realpathSync(resolvedPath);
|
|
2047
|
-
} catch (error) {
|
|
2048
|
-
const nodeError = error;
|
|
2049
|
-
if (nodeError.code === "ENOENT") {
|
|
2050
|
-
finalPath = resolvedPath;
|
|
2051
|
-
} else {
|
|
2052
|
-
throw error;
|
|
2053
|
-
}
|
|
2054
|
-
}
|
|
2055
|
-
const cwdWithSep = cwd + path.sep;
|
|
2056
|
-
if (!finalPath.startsWith(cwdWithSep) && finalPath !== cwd) {
|
|
2057
|
-
throw new PathSandboxException(inputPath, "Path is outside the current working directory");
|
|
2058
|
-
}
|
|
2059
|
-
return finalPath;
|
|
2060
|
-
}
|
|
2061
|
-
|
|
2062
2090
|
// src/builtins/filesystem/edit-file.ts
|
|
2063
2091
|
function formatFailure(filePath, search, failure, fileContent) {
|
|
2064
2092
|
const lines = [
|
|
@@ -2090,7 +2118,7 @@ function formatFailure(filePath, search, failure, fileContent) {
|
|
|
2090
2118
|
lines.push("", "CURRENT FILE CONTENT:", "```", fileContent, "```");
|
|
2091
2119
|
return lines.join("\n");
|
|
2092
2120
|
}
|
|
2093
|
-
var editFile =
|
|
2121
|
+
var editFile = createGadget4({
|
|
2094
2122
|
name: "EditFile",
|
|
2095
2123
|
description: `Edit a file by searching for content and replacing it.
|
|
2096
2124
|
|
|
@@ -2109,12 +2137,12 @@ Options:
|
|
|
2109
2137
|
- expectedCount: Validate exact number of matches before applying`,
|
|
2110
2138
|
maxConcurrent: 1,
|
|
2111
2139
|
// Sequential execution to prevent race conditions
|
|
2112
|
-
schema:
|
|
2113
|
-
filePath:
|
|
2114
|
-
search:
|
|
2115
|
-
replace:
|
|
2116
|
-
replaceAll:
|
|
2117
|
-
expectedCount:
|
|
2140
|
+
schema: z4.object({
|
|
2141
|
+
filePath: z4.string().describe("Path to the file to edit (relative or absolute)"),
|
|
2142
|
+
search: z4.string().describe("The content to search for in the file"),
|
|
2143
|
+
replace: z4.string().describe("The content to replace it with (empty string to delete)"),
|
|
2144
|
+
replaceAll: z4.boolean().optional().default(false).describe("Replace all occurrences instead of just the first match"),
|
|
2145
|
+
expectedCount: z4.number().int().positive().optional().describe("Expected number of matches. Edit fails if actual count differs")
|
|
2118
2146
|
}),
|
|
2119
2147
|
examples: [
|
|
2120
2148
|
{
|
|
@@ -2266,19 +2294,19 @@ function executeReplaceAll(content, matches, replace) {
|
|
|
2266
2294
|
}
|
|
2267
2295
|
|
|
2268
2296
|
// src/builtins/filesystem/list-directory.ts
|
|
2269
|
-
import
|
|
2297
|
+
import fs3 from "fs";
|
|
2270
2298
|
import path2 from "path";
|
|
2271
|
-
import { createGadget as
|
|
2272
|
-
import { z as
|
|
2299
|
+
import { createGadget as createGadget5 } from "llmist";
|
|
2300
|
+
import { z as z5 } from "zod";
|
|
2273
2301
|
function listFiles(dirPath, basePath = dirPath, maxDepth = 1, currentDepth = 1) {
|
|
2274
2302
|
const entries = [];
|
|
2275
2303
|
try {
|
|
2276
|
-
const items =
|
|
2304
|
+
const items = fs3.readdirSync(dirPath);
|
|
2277
2305
|
for (const item of items) {
|
|
2278
2306
|
const fullPath = path2.join(dirPath, item);
|
|
2279
2307
|
const relativePath = path2.relative(basePath, fullPath);
|
|
2280
2308
|
try {
|
|
2281
|
-
const stats =
|
|
2309
|
+
const stats = fs3.lstatSync(fullPath);
|
|
2282
2310
|
let type;
|
|
2283
2311
|
let size;
|
|
2284
2312
|
if (stats.isSymbolicLink()) {
|
|
@@ -2353,12 +2381,12 @@ function formatEntriesAsString(entries) {
|
|
|
2353
2381
|
);
|
|
2354
2382
|
return [header, ...rows].join("\n");
|
|
2355
2383
|
}
|
|
2356
|
-
var listDirectory =
|
|
2384
|
+
var listDirectory = createGadget5({
|
|
2357
2385
|
name: "ListDirectory",
|
|
2358
2386
|
description: "List files and directories in a directory with full details (names, types, sizes, modification dates). Use maxDepth to explore subdirectories recursively. The directory path must be within the current working directory or its subdirectories.",
|
|
2359
|
-
schema:
|
|
2360
|
-
directoryPath:
|
|
2361
|
-
maxDepth:
|
|
2387
|
+
schema: z5.object({
|
|
2388
|
+
directoryPath: z5.string().default(".").describe("Path to the directory to list"),
|
|
2389
|
+
maxDepth: z5.number().int().min(1).max(10).default(3).describe(
|
|
2362
2390
|
"Maximum depth to recurse (1 = immediate children only, 2 = include grandchildren, etc.)"
|
|
2363
2391
|
)
|
|
2364
2392
|
}),
|
|
@@ -2376,7 +2404,7 @@ var listDirectory = createGadget4({
|
|
|
2376
2404
|
],
|
|
2377
2405
|
execute: ({ directoryPath, maxDepth }) => {
|
|
2378
2406
|
const validatedPath = validatePathIsWithinCwd(directoryPath);
|
|
2379
|
-
const stats =
|
|
2407
|
+
const stats = fs3.statSync(validatedPath);
|
|
2380
2408
|
if (!stats.isDirectory()) {
|
|
2381
2409
|
throw new Error(`Path is not a directory: ${directoryPath}`);
|
|
2382
2410
|
}
|
|
@@ -2389,14 +2417,14 @@ ${formattedList}`;
|
|
|
2389
2417
|
});
|
|
2390
2418
|
|
|
2391
2419
|
// src/builtins/filesystem/read-file.ts
|
|
2392
|
-
import
|
|
2393
|
-
import { createGadget as
|
|
2394
|
-
import { z as
|
|
2395
|
-
var readFile2 =
|
|
2420
|
+
import fs4 from "fs";
|
|
2421
|
+
import { createGadget as createGadget6 } from "llmist";
|
|
2422
|
+
import { z as z6 } from "zod";
|
|
2423
|
+
var readFile2 = createGadget6({
|
|
2396
2424
|
name: "ReadFile",
|
|
2397
2425
|
description: "Read the entire content of a file and return it as text. The file path must be within the current working directory or its subdirectories.",
|
|
2398
|
-
schema:
|
|
2399
|
-
filePath:
|
|
2426
|
+
schema: z6.object({
|
|
2427
|
+
filePath: z6.string().describe("Path to the file to read (relative or absolute)")
|
|
2400
2428
|
}),
|
|
2401
2429
|
examples: [
|
|
2402
2430
|
{
|
|
@@ -2412,7 +2440,7 @@ var readFile2 = createGadget5({
|
|
|
2412
2440
|
],
|
|
2413
2441
|
execute: ({ filePath }) => {
|
|
2414
2442
|
const validatedPath = validatePathIsWithinCwd(filePath);
|
|
2415
|
-
const content =
|
|
2443
|
+
const content = fs4.readFileSync(validatedPath, "utf-8");
|
|
2416
2444
|
return `path=${filePath}
|
|
2417
2445
|
|
|
2418
2446
|
${content}`;
|
|
@@ -2420,18 +2448,18 @@ ${content}`;
|
|
|
2420
2448
|
});
|
|
2421
2449
|
|
|
2422
2450
|
// src/builtins/filesystem/write-file.ts
|
|
2423
|
-
import
|
|
2451
|
+
import fs5 from "fs";
|
|
2424
2452
|
import path3 from "path";
|
|
2425
|
-
import { createGadget as
|
|
2426
|
-
import { z as
|
|
2427
|
-
var writeFile =
|
|
2453
|
+
import { createGadget as createGadget7 } from "llmist";
|
|
2454
|
+
import { z as z7 } from "zod";
|
|
2455
|
+
var writeFile = createGadget7({
|
|
2428
2456
|
name: "WriteFile",
|
|
2429
2457
|
description: "Write content to a file. Creates parent directories if needed. Overwrites existing files. The file path must be within the current working directory or its subdirectories.",
|
|
2430
2458
|
maxConcurrent: 1,
|
|
2431
2459
|
// Sequential execution to prevent race conditions
|
|
2432
|
-
schema:
|
|
2433
|
-
filePath:
|
|
2434
|
-
content:
|
|
2460
|
+
schema: z7.object({
|
|
2461
|
+
filePath: z7.string().describe("Path to the file to write (relative or absolute)"),
|
|
2462
|
+
content: z7.string().describe("Content to write to the file")
|
|
2435
2463
|
}),
|
|
2436
2464
|
examples: [
|
|
2437
2465
|
{
|
|
@@ -2461,12 +2489,12 @@ console.log(\`Server running on http://localhost:\${port}\`);`
|
|
|
2461
2489
|
const validatedPath = validatePathIsWithinCwd(filePath);
|
|
2462
2490
|
const parentDir = path3.dirname(validatedPath);
|
|
2463
2491
|
let createdDir = false;
|
|
2464
|
-
if (!
|
|
2492
|
+
if (!fs5.existsSync(parentDir)) {
|
|
2465
2493
|
validatePathIsWithinCwd(parentDir);
|
|
2466
|
-
|
|
2494
|
+
fs5.mkdirSync(parentDir, { recursive: true });
|
|
2467
2495
|
createdDir = true;
|
|
2468
2496
|
}
|
|
2469
|
-
|
|
2497
|
+
fs5.writeFileSync(validatedPath, content, "utf-8");
|
|
2470
2498
|
const bytesWritten = Buffer.byteLength(content, "utf-8");
|
|
2471
2499
|
const dirNote = createdDir ? ` (created directory: ${path3.dirname(filePath)})` : "";
|
|
2472
2500
|
return `path=${filePath}
|
|
@@ -2475,9 +2503,141 @@ Wrote ${bytesWritten} bytes${dirNote}`;
|
|
|
2475
2503
|
}
|
|
2476
2504
|
});
|
|
2477
2505
|
|
|
2506
|
+
// src/builtins/read-image.ts
|
|
2507
|
+
import { readFile as readFile3, stat as stat2 } from "fs/promises";
|
|
2508
|
+
import path4 from "path";
|
|
2509
|
+
import { createGadget as createGadget8, getErrorMessage as getErrorMessage2, resultWithImage } from "llmist";
|
|
2510
|
+
import { z as z8 } from "zod";
|
|
2511
|
+
var MAX_IMAGE_SIZE = 50 * 1024 * 1024;
|
|
2512
|
+
var URL_FETCH_TIMEOUT_MS = 3e4;
|
|
2513
|
+
var SUPPORTED_FORMATS = "JPEG, PNG, GIF, WebP";
|
|
2514
|
+
var USER_AGENT = "llmist-cli";
|
|
2515
|
+
var MIME_TO_EXT = {
|
|
2516
|
+
"image/jpeg": ".jpg",
|
|
2517
|
+
"image/png": ".png",
|
|
2518
|
+
"image/gif": ".gif",
|
|
2519
|
+
"image/webp": ".webp"
|
|
2520
|
+
};
|
|
2521
|
+
function isUrl(source) {
|
|
2522
|
+
return source.startsWith("http://") || source.startsWith("https://");
|
|
2523
|
+
}
|
|
2524
|
+
function fileNameFromUrl(source) {
|
|
2525
|
+
try {
|
|
2526
|
+
const urlPath = new URL(source).pathname;
|
|
2527
|
+
const base = path4.basename(urlPath);
|
|
2528
|
+
if (base && base.includes(".")) {
|
|
2529
|
+
return base;
|
|
2530
|
+
}
|
|
2531
|
+
} catch {
|
|
2532
|
+
}
|
|
2533
|
+
return void 0;
|
|
2534
|
+
}
|
|
2535
|
+
async function readImageFromFile(source) {
|
|
2536
|
+
const resolvedPath = path4.resolve(source);
|
|
2537
|
+
let stats;
|
|
2538
|
+
try {
|
|
2539
|
+
stats = await stat2(resolvedPath);
|
|
2540
|
+
} catch (error) {
|
|
2541
|
+
return `error: Cannot read image file "${source}": ${getErrorMessage2(error)}`;
|
|
2542
|
+
}
|
|
2543
|
+
if (stats.size > MAX_IMAGE_SIZE) {
|
|
2544
|
+
return `error: Image file "${source}" is too large (${formatFileSize(stats.size)}). Maximum: ${formatFileSize(MAX_IMAGE_SIZE)}.`;
|
|
2545
|
+
}
|
|
2546
|
+
let buffer;
|
|
2547
|
+
try {
|
|
2548
|
+
buffer = await readFile3(resolvedPath);
|
|
2549
|
+
} catch (error) {
|
|
2550
|
+
return `error: Cannot read image file "${source}": ${getErrorMessage2(error)}`;
|
|
2551
|
+
}
|
|
2552
|
+
const fileName = path4.basename(resolvedPath);
|
|
2553
|
+
try {
|
|
2554
|
+
return resultWithImage(`source=${source}
|
|
2555
|
+
size=${formatFileSize(buffer.length)}`, buffer, {
|
|
2556
|
+
description: `Image: ${fileName}`,
|
|
2557
|
+
fileName
|
|
2558
|
+
});
|
|
2559
|
+
} catch (error) {
|
|
2560
|
+
if (getErrorMessage2(error).includes("MIME type")) {
|
|
2561
|
+
return `error: File "${source}" is not a supported image format. Supported formats: ${SUPPORTED_FORMATS}.`;
|
|
2562
|
+
}
|
|
2563
|
+
throw error;
|
|
2564
|
+
}
|
|
2565
|
+
}
|
|
2566
|
+
async function fetchImageFromUrl(source) {
|
|
2567
|
+
const response = await fetch(source, {
|
|
2568
|
+
signal: AbortSignal.timeout(URL_FETCH_TIMEOUT_MS),
|
|
2569
|
+
headers: { "User-Agent": USER_AGENT }
|
|
2570
|
+
});
|
|
2571
|
+
if (!response.ok) {
|
|
2572
|
+
return `error: Failed to fetch image: HTTP ${response.status} ${response.statusText}`;
|
|
2573
|
+
}
|
|
2574
|
+
const contentLength = response.headers.get("content-length");
|
|
2575
|
+
if (contentLength && Number.parseInt(contentLength, 10) > MAX_IMAGE_SIZE) {
|
|
2576
|
+
return `error: Image at URL is too large (${formatFileSize(Number.parseInt(contentLength, 10))}). Maximum: ${formatFileSize(MAX_IMAGE_SIZE)}.`;
|
|
2577
|
+
}
|
|
2578
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
2579
|
+
const buffer = Buffer.from(arrayBuffer);
|
|
2580
|
+
if (buffer.length > MAX_IMAGE_SIZE) {
|
|
2581
|
+
return `error: Downloaded image is too large (${formatFileSize(buffer.length)}). Maximum: ${formatFileSize(MAX_IMAGE_SIZE)}.`;
|
|
2582
|
+
}
|
|
2583
|
+
try {
|
|
2584
|
+
const result = resultWithImage(
|
|
2585
|
+
`source=${source}
|
|
2586
|
+
size=${formatFileSize(buffer.length)}`,
|
|
2587
|
+
buffer,
|
|
2588
|
+
{ description: `Image from ${source}` }
|
|
2589
|
+
);
|
|
2590
|
+
const detectedMime = result.media?.[0]?.mimeType;
|
|
2591
|
+
const fileName = fileNameFromUrl(source) ?? `image${detectedMime && MIME_TO_EXT[detectedMime] || ".bin"}`;
|
|
2592
|
+
if (result.media?.[0]) {
|
|
2593
|
+
result.media[0].fileName = fileName;
|
|
2594
|
+
}
|
|
2595
|
+
return result;
|
|
2596
|
+
} catch (error) {
|
|
2597
|
+
if (getErrorMessage2(error).includes("MIME type")) {
|
|
2598
|
+
return `error: Content at "${source}" is not a supported image format. Supported formats: ${SUPPORTED_FORMATS}.`;
|
|
2599
|
+
}
|
|
2600
|
+
throw error;
|
|
2601
|
+
}
|
|
2602
|
+
}
|
|
2603
|
+
var readImage = createGadget8({
|
|
2604
|
+
name: "ReadImage",
|
|
2605
|
+
description: "Read an image from a local file path or HTTP/HTTPS URL and return it for visual analysis. Supports JPEG, PNG, GIF, and WebP formats.",
|
|
2606
|
+
schema: z8.object({
|
|
2607
|
+
source: z8.string().min(1).describe("Path to a local image file or an HTTP/HTTPS URL")
|
|
2608
|
+
}),
|
|
2609
|
+
examples: [
|
|
2610
|
+
{
|
|
2611
|
+
params: { source: "./screenshot.png" },
|
|
2612
|
+
output: "source=./screenshot.png\nsize=1.2 MB",
|
|
2613
|
+
comment: "Read a local PNG image"
|
|
2614
|
+
},
|
|
2615
|
+
{
|
|
2616
|
+
params: { source: "https://example.com/photo.jpg" },
|
|
2617
|
+
output: "source=https://example.com/photo.jpg\nsize=245.3 KB",
|
|
2618
|
+
comment: "Fetch an image from a URL"
|
|
2619
|
+
},
|
|
2620
|
+
{
|
|
2621
|
+
params: { source: "/home/user/photos/cat.webp" },
|
|
2622
|
+
output: "source=/home/user/photos/cat.webp\nsize=89.4 KB",
|
|
2623
|
+
comment: "Read an image with absolute path (no CWD restriction)"
|
|
2624
|
+
}
|
|
2625
|
+
],
|
|
2626
|
+
execute: async ({ source }) => {
|
|
2627
|
+
try {
|
|
2628
|
+
if (isUrl(source)) {
|
|
2629
|
+
return await fetchImageFromUrl(source);
|
|
2630
|
+
}
|
|
2631
|
+
return await readImageFromFile(source);
|
|
2632
|
+
} catch (error) {
|
|
2633
|
+
return `error: ${getErrorMessage2(error)}`;
|
|
2634
|
+
}
|
|
2635
|
+
}
|
|
2636
|
+
});
|
|
2637
|
+
|
|
2478
2638
|
// src/builtins/run-command.ts
|
|
2479
|
-
import { createGadget as
|
|
2480
|
-
import { z as
|
|
2639
|
+
import { createGadget as createGadget9 } from "llmist";
|
|
2640
|
+
import { z as z9 } from "zod";
|
|
2481
2641
|
|
|
2482
2642
|
// src/spawn.ts
|
|
2483
2643
|
import { spawn as nodeSpawn } from "child_process";
|
|
@@ -2538,13 +2698,13 @@ function spawn2(argv, options = {}) {
|
|
|
2538
2698
|
}
|
|
2539
2699
|
|
|
2540
2700
|
// src/builtins/run-command.ts
|
|
2541
|
-
var runCommand =
|
|
2701
|
+
var runCommand = createGadget9({
|
|
2542
2702
|
name: "RunCommand",
|
|
2543
2703
|
description: "Execute a command with arguments and return its output. Uses argv array to bypass shell - arguments are passed directly without interpretation. Returns stdout/stderr combined with exit status.",
|
|
2544
|
-
schema:
|
|
2545
|
-
argv:
|
|
2546
|
-
cwd:
|
|
2547
|
-
timeout:
|
|
2704
|
+
schema: z9.object({
|
|
2705
|
+
argv: z9.array(z9.string()).describe("Command and arguments as array (e.g., ['git', 'commit', '-m', 'message'])"),
|
|
2706
|
+
cwd: z9.string().optional().describe("Working directory for the command (default: current directory)"),
|
|
2707
|
+
timeout: z9.number().default(3e4).describe("Timeout in milliseconds (default: 30000)")
|
|
2548
2708
|
}),
|
|
2549
2709
|
examples: [
|
|
2550
2710
|
{
|
|
@@ -2649,8 +2809,10 @@ error: ${message}`;
|
|
|
2649
2809
|
var builtinGadgetRegistry = {
|
|
2650
2810
|
ListDirectory: listDirectory,
|
|
2651
2811
|
ReadFile: readFile2,
|
|
2812
|
+
ReadImage: readImage,
|
|
2652
2813
|
WriteFile: writeFile,
|
|
2653
2814
|
EditFile: editFile,
|
|
2815
|
+
DeleteFile: deleteFile,
|
|
2654
2816
|
RunCommand: runCommand,
|
|
2655
2817
|
TextToSpeech: textToSpeech
|
|
2656
2818
|
};
|
|
@@ -2660,12 +2822,15 @@ function getBuiltinGadget(name) {
|
|
|
2660
2822
|
function isBuiltinGadgetName(name) {
|
|
2661
2823
|
return name in builtinGadgetRegistry;
|
|
2662
2824
|
}
|
|
2825
|
+
function getBuiltinGadgetNames() {
|
|
2826
|
+
return Object.keys(builtinGadgetRegistry);
|
|
2827
|
+
}
|
|
2663
2828
|
|
|
2664
2829
|
// src/external-gadgets.ts
|
|
2665
2830
|
import { execSync } from "child_process";
|
|
2666
|
-
import
|
|
2831
|
+
import fs6 from "fs";
|
|
2667
2832
|
import os from "os";
|
|
2668
|
-
import
|
|
2833
|
+
import path5 from "path";
|
|
2669
2834
|
import { pathToFileURL } from "url";
|
|
2670
2835
|
function isCommandAvailable(cmd) {
|
|
2671
2836
|
try {
|
|
@@ -2676,7 +2841,7 @@ function isCommandAvailable(cmd) {
|
|
|
2676
2841
|
return false;
|
|
2677
2842
|
}
|
|
2678
2843
|
}
|
|
2679
|
-
var CACHE_DIR =
|
|
2844
|
+
var CACHE_DIR = path5.join(os.homedir(), ".llmist", "gadget-cache");
|
|
2680
2845
|
function isExternalPackageSpecifier(specifier) {
|
|
2681
2846
|
if (/^(?:@[a-z0-9][\w.-]*\/)?[a-z0-9][\w.-]*(?:@[\w.-]+)?(?::[a-z]+)?(?:\/[A-Z]\w*)?$/i.test(
|
|
2682
2847
|
specifier
|
|
@@ -2766,21 +2931,21 @@ function parseGadgetSpecifier(specifier) {
|
|
|
2766
2931
|
function getCacheDir(spec) {
|
|
2767
2932
|
const versionSuffix = spec.version ? `@${spec.version}` : "@latest";
|
|
2768
2933
|
if (spec.type === "npm") {
|
|
2769
|
-
return
|
|
2934
|
+
return path5.join(CACHE_DIR, "npm", `${spec.package}${versionSuffix}`);
|
|
2770
2935
|
}
|
|
2771
2936
|
const sanitizedUrl = spec.package.replace(/[/:]/g, "-").replace(/^-+|-+$/g, "");
|
|
2772
|
-
return
|
|
2937
|
+
return path5.join(CACHE_DIR, "git", `${sanitizedUrl}${versionSuffix}`);
|
|
2773
2938
|
}
|
|
2774
2939
|
function isCached(cacheDir) {
|
|
2775
|
-
const packageJsonPath =
|
|
2776
|
-
if (!
|
|
2940
|
+
const packageJsonPath = path5.join(cacheDir, "package.json");
|
|
2941
|
+
if (!fs6.existsSync(packageJsonPath)) {
|
|
2777
2942
|
return false;
|
|
2778
2943
|
}
|
|
2779
2944
|
try {
|
|
2780
|
-
const packageJson = JSON.parse(
|
|
2945
|
+
const packageJson = JSON.parse(fs6.readFileSync(packageJsonPath, "utf-8"));
|
|
2781
2946
|
const entryPoint = packageJson.llmist?.gadgets || "./dist/index.js";
|
|
2782
|
-
const entryPointPath =
|
|
2783
|
-
if (!
|
|
2947
|
+
const entryPointPath = path5.join(cacheDir, entryPoint);
|
|
2948
|
+
if (!fs6.existsSync(entryPointPath)) {
|
|
2784
2949
|
return false;
|
|
2785
2950
|
}
|
|
2786
2951
|
} catch {
|
|
@@ -2789,13 +2954,13 @@ function isCached(cacheDir) {
|
|
|
2789
2954
|
return true;
|
|
2790
2955
|
}
|
|
2791
2956
|
async function installNpmPackage(spec, cacheDir) {
|
|
2792
|
-
|
|
2957
|
+
fs6.mkdirSync(cacheDir, { recursive: true });
|
|
2793
2958
|
const packageJson = {
|
|
2794
2959
|
name: "llmist-gadget-cache",
|
|
2795
2960
|
private: true,
|
|
2796
2961
|
type: "module"
|
|
2797
2962
|
};
|
|
2798
|
-
|
|
2963
|
+
fs6.writeFileSync(path5.join(cacheDir, "package.json"), JSON.stringify(packageJson, null, 2));
|
|
2799
2964
|
const packageSpec = spec.version ? `${spec.package}@${spec.version}` : spec.package;
|
|
2800
2965
|
const quietEnv = { ...process.env, DISABLE_OPENCOLLECTIVE: "1", ADBLOCK: "1" };
|
|
2801
2966
|
if (!isCommandAvailable("npm")) {
|
|
@@ -2813,18 +2978,18 @@ Please install Node.js: https://nodejs.org/`
|
|
|
2813
2978
|
}
|
|
2814
2979
|
}
|
|
2815
2980
|
async function installGitPackage(spec, cacheDir) {
|
|
2816
|
-
|
|
2817
|
-
if (
|
|
2981
|
+
fs6.mkdirSync(path5.dirname(cacheDir), { recursive: true });
|
|
2982
|
+
if (fs6.existsSync(cacheDir)) {
|
|
2818
2983
|
try {
|
|
2819
2984
|
execSync("git fetch", { cwd: cacheDir, stdio: "pipe" });
|
|
2820
2985
|
if (spec.version) {
|
|
2821
2986
|
execSync(`git checkout ${spec.version}`, { cwd: cacheDir, stdio: "pipe" });
|
|
2822
2987
|
}
|
|
2823
2988
|
} catch (error) {
|
|
2824
|
-
|
|
2989
|
+
fs6.rmSync(cacheDir, { recursive: true, force: true });
|
|
2825
2990
|
}
|
|
2826
2991
|
}
|
|
2827
|
-
if (!
|
|
2992
|
+
if (!fs6.existsSync(cacheDir)) {
|
|
2828
2993
|
try {
|
|
2829
2994
|
const cloneCmd = spec.version ? `git clone --branch ${spec.version} "${spec.package}" "${cacheDir}"` : `git clone "${spec.package}" "${cacheDir}"`;
|
|
2830
2995
|
execSync(cloneCmd, { stdio: "pipe" });
|
|
@@ -2832,7 +2997,7 @@ async function installGitPackage(spec, cacheDir) {
|
|
|
2832
2997
|
const message = error instanceof Error ? error.message : String(error);
|
|
2833
2998
|
throw new Error(`Failed to clone git repository '${spec.package}': ${message}`);
|
|
2834
2999
|
}
|
|
2835
|
-
if (
|
|
3000
|
+
if (fs6.existsSync(path5.join(cacheDir, "package.json"))) {
|
|
2836
3001
|
const hasNpm = isCommandAvailable("npm");
|
|
2837
3002
|
if (!hasNpm) {
|
|
2838
3003
|
throw new Error(
|
|
@@ -2851,14 +3016,14 @@ Please install Node.js: https://nodejs.org/`
|
|
|
2851
3016
|
`Failed to install dependencies for '${spec.package}' using npm: ${message}`
|
|
2852
3017
|
);
|
|
2853
3018
|
}
|
|
2854
|
-
const packageJson = JSON.parse(
|
|
3019
|
+
const packageJson = JSON.parse(fs6.readFileSync(path5.join(cacheDir, "package.json"), "utf-8"));
|
|
2855
3020
|
if (packageJson.scripts?.build) {
|
|
2856
3021
|
try {
|
|
2857
3022
|
execSync(`${runCmd} build`, { cwd: cacheDir, stdio: "inherit", env: quietEnv });
|
|
2858
3023
|
} catch (error) {
|
|
2859
3024
|
const entryPoint = packageJson.llmist?.gadgets || "./dist/index.js";
|
|
2860
|
-
const entryPointPath =
|
|
2861
|
-
if (!
|
|
3025
|
+
const entryPointPath = path5.join(cacheDir, entryPoint);
|
|
3026
|
+
if (!fs6.existsSync(entryPointPath)) {
|
|
2862
3027
|
const message = error instanceof Error ? error.message : String(error);
|
|
2863
3028
|
throw new Error(`Failed to build package '${spec.package}': ${message}`);
|
|
2864
3029
|
}
|
|
@@ -2868,20 +3033,20 @@ Please install Node.js: https://nodejs.org/`
|
|
|
2868
3033
|
}
|
|
2869
3034
|
}
|
|
2870
3035
|
function readManifest(packageDir) {
|
|
2871
|
-
const packageJsonPath =
|
|
2872
|
-
if (!
|
|
3036
|
+
const packageJsonPath = path5.join(packageDir, "package.json");
|
|
3037
|
+
if (!fs6.existsSync(packageJsonPath)) {
|
|
2873
3038
|
return null;
|
|
2874
3039
|
}
|
|
2875
3040
|
try {
|
|
2876
|
-
const packageJson = JSON.parse(
|
|
3041
|
+
const packageJson = JSON.parse(fs6.readFileSync(packageJsonPath, "utf-8"));
|
|
2877
3042
|
return packageJson.llmist || null;
|
|
2878
3043
|
} catch {
|
|
2879
3044
|
return null;
|
|
2880
3045
|
}
|
|
2881
3046
|
}
|
|
2882
3047
|
function getPackagePath(cacheDir, packageName) {
|
|
2883
|
-
const nodeModulesPath =
|
|
2884
|
-
if (
|
|
3048
|
+
const nodeModulesPath = path5.join(cacheDir, "node_modules", packageName);
|
|
3049
|
+
if (fs6.existsSync(nodeModulesPath)) {
|
|
2885
3050
|
return nodeModulesPath;
|
|
2886
3051
|
}
|
|
2887
3052
|
return cacheDir;
|
|
@@ -2927,8 +3092,8 @@ async function loadExternalGadgets(specifier, forceInstall = false) {
|
|
|
2927
3092
|
} else {
|
|
2928
3093
|
entryPoint = manifest?.gadgets || "./dist/index.js";
|
|
2929
3094
|
}
|
|
2930
|
-
const resolvedEntryPoint =
|
|
2931
|
-
if (!
|
|
3095
|
+
const resolvedEntryPoint = path5.resolve(packagePath, entryPoint);
|
|
3096
|
+
if (!fs6.existsSync(resolvedEntryPoint)) {
|
|
2932
3097
|
throw new Error(
|
|
2933
3098
|
`Entry point not found: ${resolvedEntryPoint}. Make sure the package is built (run 'npm run build' in the package directory).`
|
|
2934
3099
|
);
|
|
@@ -3071,7 +3236,7 @@ function parseLocalSpecifier(specifier) {
|
|
|
3071
3236
|
return { path: specifier };
|
|
3072
3237
|
}
|
|
3073
3238
|
function isFileLikeSpecifier(specifier) {
|
|
3074
|
-
return PATH_PREFIXES.some((prefix) => specifier.startsWith(prefix)) || specifier.includes(
|
|
3239
|
+
return PATH_PREFIXES.some((prefix) => specifier.startsWith(prefix)) || specifier.includes(path6.sep);
|
|
3075
3240
|
}
|
|
3076
3241
|
function tryResolveBuiltin(specifier) {
|
|
3077
3242
|
if (specifier.startsWith(BUILTIN_PREFIX)) {
|
|
@@ -3079,7 +3244,7 @@ function tryResolveBuiltin(specifier) {
|
|
|
3079
3244
|
const gadget = getBuiltinGadget(name);
|
|
3080
3245
|
if (!gadget) {
|
|
3081
3246
|
throw new Error(
|
|
3082
|
-
`Unknown builtin gadget: ${name}. Available builtins:
|
|
3247
|
+
`Unknown builtin gadget: ${name}. Available builtins: ${getBuiltinGadgetNames().join(", ")}`
|
|
3083
3248
|
);
|
|
3084
3249
|
}
|
|
3085
3250
|
return gadget;
|
|
@@ -3094,8 +3259,8 @@ function resolveGadgetSpecifier(specifier, cwd) {
|
|
|
3094
3259
|
return specifier;
|
|
3095
3260
|
}
|
|
3096
3261
|
const expanded = expandTildePath(specifier);
|
|
3097
|
-
const resolvedPath =
|
|
3098
|
-
if (!
|
|
3262
|
+
const resolvedPath = path6.resolve(cwd, expanded);
|
|
3263
|
+
if (!fs7.existsSync(resolvedPath)) {
|
|
3099
3264
|
throw new Error(`Gadget module not found at ${resolvedPath}`);
|
|
3100
3265
|
}
|
|
3101
3266
|
return pathToFileURL2(resolvedPath).href;
|
|
@@ -3262,7 +3427,7 @@ async function resolvePrompt(promptArg, env) {
|
|
|
3262
3427
|
}
|
|
3263
3428
|
|
|
3264
3429
|
// src/ui/formatters.ts
|
|
3265
|
-
import
|
|
3430
|
+
import chalk4 from "chalk";
|
|
3266
3431
|
import { format } from "llmist";
|
|
3267
3432
|
|
|
3268
3433
|
// src/ui/call-number.ts
|
|
@@ -3376,6 +3541,62 @@ function stripProviderPrefix(model) {
|
|
|
3376
3541
|
return model.includes(":") ? model.split(":")[1] : model;
|
|
3377
3542
|
}
|
|
3378
3543
|
|
|
3544
|
+
// src/ui/metric-parts.ts
|
|
3545
|
+
import chalk3 from "chalk";
|
|
3546
|
+
function tokenPart(direction, tokens, opts) {
|
|
3547
|
+
const prefix = opts?.estimated ? "~" : "";
|
|
3548
|
+
const formatted = `${prefix}${formatTokens(tokens)}`;
|
|
3549
|
+
switch (direction) {
|
|
3550
|
+
case "input":
|
|
3551
|
+
return chalk3.dim("\u2191") + chalk3.yellow(` ${formatted}`);
|
|
3552
|
+
case "cached":
|
|
3553
|
+
return chalk3.dim("\u27F3") + chalk3.blue(` ${formatted}`);
|
|
3554
|
+
case "output":
|
|
3555
|
+
return chalk3.dim("\u2193") + chalk3.green(` ${formatted}`);
|
|
3556
|
+
case "reasoning":
|
|
3557
|
+
return chalk3.dim("\u{1F4AD}") + chalk3.magenta(` ${formatted}`);
|
|
3558
|
+
case "cacheCreation":
|
|
3559
|
+
return chalk3.dim("\u270E") + chalk3.magenta(` ${formatted}`);
|
|
3560
|
+
}
|
|
3561
|
+
}
|
|
3562
|
+
function costPart(cost) {
|
|
3563
|
+
return chalk3.cyan(`$${formatCost(cost)}`);
|
|
3564
|
+
}
|
|
3565
|
+
function timePart(seconds) {
|
|
3566
|
+
return chalk3.dim(`${seconds.toFixed(1)}s`);
|
|
3567
|
+
}
|
|
3568
|
+
function finishReasonPart(reason) {
|
|
3569
|
+
const upper = reason.toUpperCase();
|
|
3570
|
+
if (upper === "STOP" || upper === "END_TURN") {
|
|
3571
|
+
return chalk3.green(upper);
|
|
3572
|
+
}
|
|
3573
|
+
return chalk3.yellow(upper);
|
|
3574
|
+
}
|
|
3575
|
+
function buildTokenMetrics(opts) {
|
|
3576
|
+
const parts = [];
|
|
3577
|
+
if (opts.input && opts.input > 0) {
|
|
3578
|
+
parts.push(tokenPart("input", opts.input, { estimated: opts.estimated?.input }));
|
|
3579
|
+
}
|
|
3580
|
+
if (opts.cached && opts.cached > 0) {
|
|
3581
|
+
parts.push(tokenPart("cached", opts.cached));
|
|
3582
|
+
}
|
|
3583
|
+
if (opts.output && opts.output > 0) {
|
|
3584
|
+
parts.push(tokenPart("output", opts.output, { estimated: opts.estimated?.output }));
|
|
3585
|
+
}
|
|
3586
|
+
if (opts.reasoning && opts.reasoning > 0) {
|
|
3587
|
+
parts.push(tokenPart("reasoning", opts.reasoning));
|
|
3588
|
+
}
|
|
3589
|
+
if (opts.cacheCreation && opts.cacheCreation > 0) {
|
|
3590
|
+
parts.push(tokenPart("cacheCreation", opts.cacheCreation));
|
|
3591
|
+
}
|
|
3592
|
+
return parts;
|
|
3593
|
+
}
|
|
3594
|
+
function joinParts(parts) {
|
|
3595
|
+
const filtered = parts.filter((p) => p.length > 0);
|
|
3596
|
+
if (filtered.length === 0) return "";
|
|
3597
|
+
return filtered.join(chalk3.dim(" | "));
|
|
3598
|
+
}
|
|
3599
|
+
|
|
3379
3600
|
// src/ui/formatters.ts
|
|
3380
3601
|
function formatLLMCallLine(info) {
|
|
3381
3602
|
const parts = [];
|
|
@@ -3384,85 +3605,81 @@ function formatLLMCallLine(info) {
|
|
|
3384
3605
|
info.parentCallNumber,
|
|
3385
3606
|
info.gadgetInvocationId
|
|
3386
3607
|
);
|
|
3387
|
-
parts.push(`${
|
|
3608
|
+
parts.push(`${chalk4.cyan(callNumber)} ${chalk4.magenta(info.model)}`);
|
|
3388
3609
|
if (info.contextPercent !== void 0 && info.contextPercent !== null) {
|
|
3389
3610
|
const formatted = `${Math.round(info.contextPercent)}%`;
|
|
3390
3611
|
if (info.contextPercent >= 80) {
|
|
3391
|
-
parts.push(
|
|
3612
|
+
parts.push(chalk4.red(formatted));
|
|
3392
3613
|
} else if (info.contextPercent >= 50) {
|
|
3393
|
-
parts.push(
|
|
3614
|
+
parts.push(chalk4.yellow(formatted));
|
|
3394
3615
|
} else {
|
|
3395
|
-
parts.push(
|
|
3616
|
+
parts.push(chalk4.green(formatted));
|
|
3396
3617
|
}
|
|
3397
3618
|
}
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3619
|
+
parts.push(
|
|
3620
|
+
...buildTokenMetrics({
|
|
3621
|
+
input: info.inputTokens,
|
|
3622
|
+
cached: info.cachedInputTokens,
|
|
3623
|
+
estimated: info.estimated
|
|
3624
|
+
})
|
|
3625
|
+
);
|
|
3405
3626
|
if (info.outputTokens !== void 0 && info.outputTokens > 0 || info.isStreaming) {
|
|
3406
3627
|
const prefix = info.estimated?.output ? "~" : "";
|
|
3407
|
-
parts.push(
|
|
3628
|
+
parts.push(chalk4.dim("\u2193") + chalk4.green(` ${prefix}${formatTokens(info.outputTokens ?? 0)}`));
|
|
3408
3629
|
}
|
|
3409
|
-
parts.push(
|
|
3630
|
+
parts.push(timePart(info.elapsedSeconds));
|
|
3410
3631
|
if (info.cost !== void 0 && info.cost > 0) {
|
|
3411
|
-
parts.push(
|
|
3632
|
+
parts.push(costPart(info.cost));
|
|
3412
3633
|
}
|
|
3413
3634
|
if (!info.isStreaming && info.finishReason !== void 0) {
|
|
3414
3635
|
const reason = info.finishReason || "stop";
|
|
3415
|
-
|
|
3416
|
-
parts.push(chalk3.green(reason.toUpperCase()));
|
|
3417
|
-
} else {
|
|
3418
|
-
parts.push(chalk3.yellow(reason.toUpperCase()));
|
|
3419
|
-
}
|
|
3636
|
+
parts.push(finishReasonPart(reason));
|
|
3420
3637
|
}
|
|
3421
|
-
const line = parts
|
|
3638
|
+
const line = joinParts(parts);
|
|
3422
3639
|
if (info.isStreaming && info.spinner) {
|
|
3423
|
-
return `${
|
|
3640
|
+
return `${chalk4.cyan(info.spinner)} ${line}`;
|
|
3424
3641
|
}
|
|
3425
3642
|
if (!info.isStreaming) {
|
|
3426
|
-
return `${
|
|
3643
|
+
return `${chalk4.green("\u2713")} ${line}`;
|
|
3427
3644
|
}
|
|
3428
3645
|
return line;
|
|
3429
3646
|
}
|
|
3430
3647
|
function renderSummary(metadata) {
|
|
3431
3648
|
const parts = [];
|
|
3432
3649
|
if (metadata.iterations !== void 0) {
|
|
3433
|
-
const iterPart =
|
|
3650
|
+
const iterPart = chalk4.cyan(`#${metadata.iterations}`);
|
|
3434
3651
|
if (metadata.model) {
|
|
3435
|
-
parts.push(`${iterPart} ${
|
|
3652
|
+
parts.push(`${iterPart} ${chalk4.magenta(metadata.model)}`);
|
|
3436
3653
|
} else {
|
|
3437
3654
|
parts.push(iterPart);
|
|
3438
3655
|
}
|
|
3439
3656
|
} else if (metadata.model) {
|
|
3440
|
-
parts.push(
|
|
3657
|
+
parts.push(chalk4.magenta(metadata.model));
|
|
3441
3658
|
}
|
|
3442
3659
|
if (metadata.usage) {
|
|
3443
3660
|
const { inputTokens, outputTokens, cachedInputTokens, cacheCreationInputTokens } = metadata.usage;
|
|
3444
|
-
parts.push(
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3661
|
+
parts.push(
|
|
3662
|
+
...buildTokenMetrics({
|
|
3663
|
+
input: inputTokens,
|
|
3664
|
+
cached: cachedInputTokens,
|
|
3665
|
+
cacheCreation: cacheCreationInputTokens,
|
|
3666
|
+
output: outputTokens
|
|
3667
|
+
})
|
|
3668
|
+
);
|
|
3452
3669
|
}
|
|
3453
3670
|
if (metadata.elapsedSeconds !== void 0 && metadata.elapsedSeconds > 0) {
|
|
3454
|
-
parts.push(
|
|
3671
|
+
parts.push(timePart(metadata.elapsedSeconds));
|
|
3455
3672
|
}
|
|
3456
3673
|
if (metadata.cost !== void 0 && metadata.cost > 0) {
|
|
3457
|
-
parts.push(
|
|
3674
|
+
parts.push(costPart(metadata.cost));
|
|
3458
3675
|
}
|
|
3459
3676
|
if (metadata.finishReason) {
|
|
3460
|
-
parts.push(
|
|
3677
|
+
parts.push(chalk4.dim(metadata.finishReason));
|
|
3461
3678
|
}
|
|
3462
3679
|
if (parts.length === 0) {
|
|
3463
3680
|
return null;
|
|
3464
3681
|
}
|
|
3465
|
-
return parts
|
|
3682
|
+
return joinParts(parts);
|
|
3466
3683
|
}
|
|
3467
3684
|
function getRawValue(value) {
|
|
3468
3685
|
if (typeof value === "string") {
|
|
@@ -3516,48 +3733,48 @@ function formatParametersInline(params, maxWidth) {
|
|
|
3516
3733
|
}
|
|
3517
3734
|
return entries.map(([key, _], i) => {
|
|
3518
3735
|
const formatted = truncateValue(rawValues[i], limits[i]);
|
|
3519
|
-
return `${
|
|
3520
|
-
}).join(
|
|
3736
|
+
return `${chalk4.dim(key)}${chalk4.dim("=")}${chalk4.cyan(formatted)}`;
|
|
3737
|
+
}).join(chalk4.dim(", "));
|
|
3521
3738
|
}
|
|
3522
3739
|
function formatGadgetLine(info, maxWidth) {
|
|
3523
3740
|
const terminalWidth = maxWidth ?? process.stdout.columns ?? 80;
|
|
3524
|
-
const gadgetLabel =
|
|
3741
|
+
const gadgetLabel = chalk4.magenta.bold(info.name);
|
|
3525
3742
|
const timeStr = `${info.elapsedSeconds.toFixed(1)}s`;
|
|
3526
|
-
const timeLabel =
|
|
3743
|
+
const timeLabel = chalk4.dim(timeStr);
|
|
3527
3744
|
const fixedLength = 3 + info.name.length + 2 + 1 + timeStr.length;
|
|
3528
3745
|
const availableForParams = Math.max(40, terminalWidth - fixedLength - 3);
|
|
3529
3746
|
const paramsStr = formatParametersInline(info.parameters, availableForParams);
|
|
3530
|
-
const paramsLabel = paramsStr ? `${
|
|
3747
|
+
const paramsLabel = paramsStr ? `${chalk4.dim("(")}${paramsStr}${chalk4.dim(")")}` : "";
|
|
3531
3748
|
if (info.error) {
|
|
3532
3749
|
const errorMsg = info.error.length > 50 ? `${info.error.slice(0, 50)}\u2026` : info.error;
|
|
3533
|
-
return `${
|
|
3750
|
+
return `${chalk4.red("\u2717")} ${gadgetLabel}${paramsLabel} ${chalk4.red("error:")} ${errorMsg} ${timeLabel}`;
|
|
3534
3751
|
}
|
|
3535
3752
|
if (!info.isComplete) {
|
|
3536
3753
|
const parts = [];
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3754
|
+
parts.push(
|
|
3755
|
+
...buildTokenMetrics({
|
|
3756
|
+
input: info.subagentInputTokens,
|
|
3757
|
+
output: info.subagentOutputTokens
|
|
3758
|
+
})
|
|
3759
|
+
);
|
|
3543
3760
|
if (info.subagentCost && info.subagentCost > 0) {
|
|
3544
|
-
parts.push(
|
|
3761
|
+
parts.push(costPart(info.subagentCost));
|
|
3545
3762
|
}
|
|
3546
|
-
parts.push(
|
|
3547
|
-
const metricsStr = parts.length > 0 ? ` ${parts
|
|
3548
|
-
return `${
|
|
3763
|
+
parts.push(timePart(info.elapsedSeconds));
|
|
3764
|
+
const metricsStr = parts.length > 0 ? ` ${joinParts(parts)}` : "";
|
|
3765
|
+
return `${chalk4.blue("\u23F5")} ${gadgetLabel}${metricsStr}`;
|
|
3549
3766
|
}
|
|
3550
3767
|
let outputLabel;
|
|
3551
3768
|
if (info.tokenCount !== void 0 && info.tokenCount > 0) {
|
|
3552
|
-
outputLabel =
|
|
3769
|
+
outputLabel = chalk4.dim("\u2193") + chalk4.green(` ${formatTokens(info.tokenCount)} `);
|
|
3553
3770
|
} else if (info.outputBytes !== void 0 && info.outputBytes > 0) {
|
|
3554
|
-
outputLabel = `${
|
|
3771
|
+
outputLabel = `${chalk4.green(format.bytes(info.outputBytes))} `;
|
|
3555
3772
|
} else {
|
|
3556
3773
|
outputLabel = "";
|
|
3557
3774
|
}
|
|
3558
|
-
const resultIcon = info.breaksLoop ?
|
|
3559
|
-
const nameRef =
|
|
3560
|
-
const line1 = `${
|
|
3775
|
+
const resultIcon = info.breaksLoop ? chalk4.yellow("\u23F9") : chalk4.green("\u2713");
|
|
3776
|
+
const nameRef = chalk4.magenta(info.name);
|
|
3777
|
+
const line1 = `${chalk4.dim("\u2192")} ${gadgetLabel}${paramsLabel}`;
|
|
3561
3778
|
const line2Prefix = ` ${resultIcon} ${nameRef} ${outputLabel}`;
|
|
3562
3779
|
const line2 = `${line2Prefix}${timeLabel}`;
|
|
3563
3780
|
return `${line1}
|
|
@@ -3979,7 +4196,7 @@ var NestedOperationTracker = class {
|
|
|
3979
4196
|
};
|
|
3980
4197
|
|
|
3981
4198
|
// src/progress/progress-renderer.ts
|
|
3982
|
-
import
|
|
4199
|
+
import chalk5 from "chalk";
|
|
3983
4200
|
import { FALLBACK_CHARS_PER_TOKEN } from "llmist";
|
|
3984
4201
|
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
3985
4202
|
var SPINNER_DELAY_MS = 500;
|
|
@@ -4163,7 +4380,7 @@ var ProgressRenderer = class {
|
|
|
4163
4380
|
const elapsedSeconds2 = (Date.now() - op.startTime) / 1e3;
|
|
4164
4381
|
const nestedMetrics = op.id ? this.nestedOperationTracker.getAggregatedSubagentMetrics(op.id) : { inputTokens: 0, outputTokens: 0, cachedInputTokens: 0, cost: 0, callCount: 0 };
|
|
4165
4382
|
const termWidth2 = process.stdout.columns ?? 80;
|
|
4166
|
-
const parentPrefix = `${
|
|
4383
|
+
const parentPrefix = `${chalk5.dim(`${gadget.name}:`)} `;
|
|
4167
4384
|
const line2 = formatGadgetLine(
|
|
4168
4385
|
{
|
|
4169
4386
|
name: op.name ?? "",
|
|
@@ -4186,7 +4403,7 @@ var ProgressRenderer = class {
|
|
|
4186
4403
|
}
|
|
4187
4404
|
for (const stream of activeNestedStreams) {
|
|
4188
4405
|
const indent = " ".repeat(stream.depth + 2);
|
|
4189
|
-
const parentPrefix = `${
|
|
4406
|
+
const parentPrefix = `${chalk5.dim(`${stream.parentGadgetName}:`)} `;
|
|
4190
4407
|
const elapsedSeconds = (Date.now() - stream.startTime) / 1e3;
|
|
4191
4408
|
const line = formatLLMCallLine({
|
|
4192
4409
|
iteration: stream.iteration,
|
|
@@ -4244,21 +4461,19 @@ var ProgressRenderer = class {
|
|
|
4244
4461
|
const elapsed = ((Date.now() - this.callStatsTracker.totalStartTime) / 1e3).toFixed(1);
|
|
4245
4462
|
const parts = [];
|
|
4246
4463
|
if (this.callStatsTracker.model) {
|
|
4247
|
-
parts.push(
|
|
4464
|
+
parts.push(chalk5.cyan(this.callStatsTracker.model));
|
|
4248
4465
|
}
|
|
4249
4466
|
if (this.callStatsTracker.totalTokens > 0) {
|
|
4250
|
-
parts.push(
|
|
4467
|
+
parts.push(chalk5.dim("total:") + chalk5.magenta(` ${this.callStatsTracker.totalTokens}`));
|
|
4251
4468
|
}
|
|
4252
4469
|
if (this.callStatsTracker.iterations > 0) {
|
|
4253
|
-
parts.push(
|
|
4470
|
+
parts.push(chalk5.dim("iter:") + chalk5.blue(` ${this.callStatsTracker.iterations}`));
|
|
4254
4471
|
}
|
|
4255
4472
|
if (this.callStatsTracker.totalCost > 0) {
|
|
4256
|
-
parts.push(
|
|
4257
|
-
chalk4.dim("cost:") + chalk4.cyan(` $${formatCost(this.callStatsTracker.totalCost)}`)
|
|
4258
|
-
);
|
|
4473
|
+
parts.push(`${chalk5.dim("cost:")} ${costPart(this.callStatsTracker.totalCost)}`);
|
|
4259
4474
|
}
|
|
4260
|
-
parts.push(
|
|
4261
|
-
return `${parts.join(
|
|
4475
|
+
parts.push(chalk5.dim(`${elapsed}s`));
|
|
4476
|
+
return `${parts.join(chalk5.dim(" | "))} ${chalk5.cyan(spinner)}`;
|
|
4262
4477
|
}
|
|
4263
4478
|
/**
|
|
4264
4479
|
* Returns a formatted stats string for cancellation messages.
|
|
@@ -4269,15 +4484,21 @@ var ProgressRenderer = class {
|
|
|
4269
4484
|
const elapsed = ((Date.now() - this.callStatsTracker.callStartTime) / 1e3).toFixed(1);
|
|
4270
4485
|
const outTokens = this.callStatsTracker.callOutputTokensEstimated ? Math.round(this.callStatsTracker.callOutputChars / FALLBACK_CHARS_PER_TOKEN) : this.callStatsTracker.callOutputTokens;
|
|
4271
4486
|
if (this.callStatsTracker.callInputTokens > 0) {
|
|
4272
|
-
|
|
4273
|
-
|
|
4487
|
+
parts.push(
|
|
4488
|
+
tokenPart("input", this.callStatsTracker.callInputTokens, {
|
|
4489
|
+
estimated: this.callStatsTracker.callInputTokensEstimated
|
|
4490
|
+
})
|
|
4491
|
+
);
|
|
4274
4492
|
}
|
|
4275
4493
|
if (outTokens > 0) {
|
|
4276
|
-
|
|
4277
|
-
|
|
4494
|
+
parts.push(
|
|
4495
|
+
tokenPart("output", outTokens, {
|
|
4496
|
+
estimated: this.callStatsTracker.callOutputTokensEstimated
|
|
4497
|
+
})
|
|
4498
|
+
);
|
|
4278
4499
|
}
|
|
4279
4500
|
parts.push(`${elapsed}s`);
|
|
4280
|
-
return parts
|
|
4501
|
+
return joinParts(parts);
|
|
4281
4502
|
}
|
|
4282
4503
|
/**
|
|
4283
4504
|
* Returns a formatted prompt string with stats (like bash PS1).
|
|
@@ -4291,30 +4512,30 @@ var ProgressRenderer = class {
|
|
|
4291
4512
|
const outTokens = this.callStatsTracker.callOutputTokensEstimated ? Math.round(this.callStatsTracker.callOutputChars / FALLBACK_CHARS_PER_TOKEN) : this.callStatsTracker.callOutputTokens;
|
|
4292
4513
|
const outEstimated = this.callStatsTracker.callOutputTokensEstimated;
|
|
4293
4514
|
if (this.callStatsTracker.callInputTokens > 0) {
|
|
4294
|
-
const prefix = this.callStatsTracker.callInputTokensEstimated ? "~" : "";
|
|
4295
4515
|
parts.push(
|
|
4296
|
-
|
|
4516
|
+
tokenPart("input", this.callStatsTracker.callInputTokens, {
|
|
4517
|
+
estimated: this.callStatsTracker.callInputTokensEstimated
|
|
4518
|
+
})
|
|
4297
4519
|
);
|
|
4298
4520
|
}
|
|
4299
4521
|
if (outTokens > 0) {
|
|
4300
|
-
|
|
4301
|
-
parts.push(chalk4.dim("\u2193") + chalk4.green(` ${prefix}${formatTokens(outTokens)}`));
|
|
4522
|
+
parts.push(tokenPart("output", outTokens, { estimated: outEstimated }));
|
|
4302
4523
|
}
|
|
4303
|
-
parts.push(
|
|
4524
|
+
parts.push(chalk5.dim(`${elapsed}s`));
|
|
4304
4525
|
} else {
|
|
4305
4526
|
const elapsed = Math.round((Date.now() - this.callStatsTracker.totalStartTime) / 1e3);
|
|
4306
4527
|
if (this.callStatsTracker.totalTokens > 0) {
|
|
4307
|
-
parts.push(
|
|
4528
|
+
parts.push(chalk5.magenta(formatTokens(this.callStatsTracker.totalTokens)));
|
|
4308
4529
|
}
|
|
4309
4530
|
if (this.callStatsTracker.iterations > 0) {
|
|
4310
|
-
parts.push(
|
|
4531
|
+
parts.push(chalk5.blue(`i${this.callStatsTracker.iterations}`));
|
|
4311
4532
|
}
|
|
4312
4533
|
if (this.callStatsTracker.totalCost > 0) {
|
|
4313
|
-
parts.push(
|
|
4534
|
+
parts.push(costPart(this.callStatsTracker.totalCost));
|
|
4314
4535
|
}
|
|
4315
|
-
parts.push(
|
|
4536
|
+
parts.push(chalk5.dim(`${elapsed}s`));
|
|
4316
4537
|
}
|
|
4317
|
-
return `${parts.join(
|
|
4538
|
+
return `${parts.join(chalk5.dim(" | "))} ${chalk5.green(">")} `;
|
|
4318
4539
|
}
|
|
4319
4540
|
};
|
|
4320
4541
|
|
|
@@ -4556,7 +4777,7 @@ function addAgentOptions(cmd, defaults) {
|
|
|
4556
4777
|
...previous,
|
|
4557
4778
|
value
|
|
4558
4779
|
];
|
|
4559
|
-
const defaultGadgets = defaults?.gadgets ??
|
|
4780
|
+
const defaultGadgets = defaults?.gadgets ?? [];
|
|
4560
4781
|
return cmd.option(OPTION_FLAGS.model, OPTION_DESCRIPTIONS.model, defaults?.model ?? DEFAULT_MODEL).option(OPTION_FLAGS.systemPrompt, OPTION_DESCRIPTIONS.systemPrompt, defaults?.system).option(OPTION_FLAGS.systemPromptFile, OPTION_DESCRIPTIONS.systemPromptFile).option(
|
|
4561
4782
|
OPTION_FLAGS.temperature,
|
|
4562
4783
|
OPTION_DESCRIPTIONS.temperature,
|
|
@@ -4658,8 +4879,7 @@ function configToAgentOptions(config) {
|
|
|
4658
4879
|
if (config.temperature !== void 0) result.temperature = config.temperature;
|
|
4659
4880
|
if (config["max-iterations"] !== void 0) result.maxIterations = config["max-iterations"];
|
|
4660
4881
|
if (config.budget !== void 0) result.budget = config.budget;
|
|
4661
|
-
|
|
4662
|
-
if (gadgets !== void 0) result.gadget = gadgets;
|
|
4882
|
+
if (config.gadgets !== void 0) result.gadget = config.gadgets;
|
|
4663
4883
|
if (config.builtins !== void 0) result.builtins = config.builtins;
|
|
4664
4884
|
if (config["builtin-interaction"] !== void 0)
|
|
4665
4885
|
result.builtinInteraction = config["builtin-interaction"];
|
|
@@ -4910,11 +5130,34 @@ function extractSubagentConfig(globalConfig, subagentName) {
|
|
|
4910
5130
|
return {};
|
|
4911
5131
|
}
|
|
4912
5132
|
|
|
5133
|
+
// src/tui/raw-viewer-data.ts
|
|
5134
|
+
function isRawViewerNode(node) {
|
|
5135
|
+
return node.type === "llm_call" || node.type === "gadget";
|
|
5136
|
+
}
|
|
5137
|
+
function createRawViewerData(node, mode) {
|
|
5138
|
+
if (node.type === "llm_call") {
|
|
5139
|
+
return {
|
|
5140
|
+
mode,
|
|
5141
|
+
request: node.rawRequest,
|
|
5142
|
+
response: node.rawResponse,
|
|
5143
|
+
iteration: node.iteration,
|
|
5144
|
+
model: node.model
|
|
5145
|
+
};
|
|
5146
|
+
}
|
|
5147
|
+
return {
|
|
5148
|
+
mode,
|
|
5149
|
+
gadgetName: node.name,
|
|
5150
|
+
parameters: node.parameters,
|
|
5151
|
+
result: node.result,
|
|
5152
|
+
error: node.error
|
|
5153
|
+
};
|
|
5154
|
+
}
|
|
5155
|
+
|
|
4913
5156
|
// src/tui/block-renderer.ts
|
|
4914
5157
|
import { Box } from "@unblessed/node";
|
|
4915
5158
|
|
|
4916
5159
|
// src/ui/block-formatters.ts
|
|
4917
|
-
import
|
|
5160
|
+
import chalk6 from "chalk";
|
|
4918
5161
|
var BOX = {
|
|
4919
5162
|
topLeft: "\u250C",
|
|
4920
5163
|
topRight: "\u2510",
|
|
@@ -4930,44 +5173,35 @@ var COMPLETE_INDICATOR = "\u2713";
|
|
|
4930
5173
|
var ERROR_INDICATOR = "\u2717";
|
|
4931
5174
|
function formatLLMCallCollapsed(node, selected) {
|
|
4932
5175
|
const indicator = node.isComplete ? COMPLETE_INDICATOR : PROGRESS_INDICATOR;
|
|
4933
|
-
const indicatorColor = node.isComplete ?
|
|
5176
|
+
const indicatorColor = node.isComplete ? chalk6.green : chalk6.blue;
|
|
4934
5177
|
const parts = [];
|
|
4935
|
-
const callNumber =
|
|
4936
|
-
const model =
|
|
5178
|
+
const callNumber = chalk6.cyan(`#${node.iteration}`);
|
|
5179
|
+
const model = chalk6.magenta(node.model);
|
|
4937
5180
|
parts.push(`${callNumber} ${model}`);
|
|
4938
5181
|
if (node.details) {
|
|
4939
5182
|
const d = node.details;
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
}
|
|
4949
|
-
if (d.reasoningTokens && d.reasoningTokens > 0) {
|
|
4950
|
-
parts.push(chalk5.dim("\u{1F4AD}") + chalk5.magenta(` ${formatTokens(d.reasoningTokens)}`));
|
|
4951
|
-
}
|
|
5183
|
+
parts.push(
|
|
5184
|
+
...buildTokenMetrics({
|
|
5185
|
+
input: d.inputTokens,
|
|
5186
|
+
cached: d.cachedInputTokens,
|
|
5187
|
+
output: d.outputTokens,
|
|
5188
|
+
reasoning: d.reasoningTokens
|
|
5189
|
+
})
|
|
5190
|
+
);
|
|
4952
5191
|
if (d.elapsedSeconds !== void 0) {
|
|
4953
|
-
parts.push(
|
|
5192
|
+
parts.push(timePart(d.elapsedSeconds));
|
|
4954
5193
|
}
|
|
4955
5194
|
if (d.cost !== void 0 && d.cost > 0) {
|
|
4956
|
-
parts.push(
|
|
5195
|
+
parts.push(costPart(d.cost));
|
|
4957
5196
|
}
|
|
4958
5197
|
if (node.isComplete && d.finishReason) {
|
|
4959
|
-
|
|
4960
|
-
if (reason === "STOP" || reason === "END_TURN") {
|
|
4961
|
-
parts.push(chalk5.green(reason));
|
|
4962
|
-
} else {
|
|
4963
|
-
parts.push(chalk5.yellow(reason));
|
|
4964
|
-
}
|
|
5198
|
+
parts.push(finishReasonPart(d.finishReason));
|
|
4965
5199
|
}
|
|
4966
5200
|
}
|
|
4967
|
-
const line = parts
|
|
5201
|
+
const line = joinParts(parts);
|
|
4968
5202
|
const prefix = indicatorColor(indicator);
|
|
4969
5203
|
if (selected) {
|
|
4970
|
-
return
|
|
5204
|
+
return chalk6.bgBlue.white(`${prefix} ${line}`);
|
|
4971
5205
|
}
|
|
4972
5206
|
return `${prefix} ${line}`;
|
|
4973
5207
|
}
|
|
@@ -4976,58 +5210,58 @@ function formatLLMCallExpanded(node) {
|
|
|
4976
5210
|
const indent = " ";
|
|
4977
5211
|
const d = node.details;
|
|
4978
5212
|
if (!d) {
|
|
4979
|
-
lines.push(`${indent}${
|
|
5213
|
+
lines.push(`${indent}${chalk6.dim("No details available")}`);
|
|
4980
5214
|
return lines;
|
|
4981
5215
|
}
|
|
4982
5216
|
const width = Math.min(60, (process.stdout.columns || 80) - 4);
|
|
4983
5217
|
const headerLine = `${BOX.topLeft}${BOX.horizontal} Details ${BOX.horizontal.repeat(width - 11)}`;
|
|
4984
|
-
lines.push(`${indent}${
|
|
4985
|
-
lines.push(`${indent}${
|
|
5218
|
+
lines.push(`${indent}${chalk6.dim(headerLine)}`);
|
|
5219
|
+
lines.push(`${indent}${chalk6.dim(BOX.vertical)} Model: ${chalk6.magenta(node.model)}`);
|
|
4986
5220
|
if (d.inputTokens !== void 0) {
|
|
4987
|
-
let inputLine = `${indent}${
|
|
5221
|
+
let inputLine = `${indent}${chalk6.dim(BOX.vertical)} Input: ${chalk6.yellow(formatTokens(d.inputTokens))} tokens`;
|
|
4988
5222
|
if (d.cachedInputTokens && d.cachedInputTokens > 0) {
|
|
4989
5223
|
const cachePercent = (d.cachedInputTokens / d.inputTokens * 100).toFixed(1);
|
|
4990
|
-
inputLine +=
|
|
5224
|
+
inputLine += chalk6.blue(` (${formatTokens(d.cachedInputTokens)} cached, ${cachePercent}%)`);
|
|
4991
5225
|
}
|
|
4992
5226
|
lines.push(inputLine);
|
|
4993
5227
|
}
|
|
4994
5228
|
if (d.outputTokens !== void 0) {
|
|
4995
5229
|
lines.push(
|
|
4996
|
-
`${indent}${
|
|
5230
|
+
`${indent}${chalk6.dim(BOX.vertical)} Output: ${chalk6.green(formatTokens(d.outputTokens))} tokens`
|
|
4997
5231
|
);
|
|
4998
5232
|
}
|
|
4999
5233
|
if (d.reasoningTokens !== void 0 && d.reasoningTokens > 0) {
|
|
5000
5234
|
lines.push(
|
|
5001
|
-
`${indent}${
|
|
5235
|
+
`${indent}${chalk6.dim(BOX.vertical)} Reason: ${chalk6.magenta(formatTokens(d.reasoningTokens))} tokens`
|
|
5002
5236
|
);
|
|
5003
5237
|
}
|
|
5004
5238
|
if (d.contextPercent !== void 0) {
|
|
5005
|
-
let contextColor =
|
|
5006
|
-
if (d.contextPercent >= 80) contextColor =
|
|
5007
|
-
else if (d.contextPercent >= 50) contextColor =
|
|
5239
|
+
let contextColor = chalk6.green;
|
|
5240
|
+
if (d.contextPercent >= 80) contextColor = chalk6.red;
|
|
5241
|
+
else if (d.contextPercent >= 50) contextColor = chalk6.yellow;
|
|
5008
5242
|
lines.push(
|
|
5009
|
-
`${indent}${
|
|
5243
|
+
`${indent}${chalk6.dim(BOX.vertical)} Context: ${contextColor(`${Math.round(d.contextPercent)}%`)}`
|
|
5010
5244
|
);
|
|
5011
5245
|
}
|
|
5012
5246
|
if (d.elapsedSeconds !== void 0) {
|
|
5013
|
-
let timeLine = `${indent}${
|
|
5247
|
+
let timeLine = `${indent}${chalk6.dim(BOX.vertical)} Time: ${chalk6.dim(`${d.elapsedSeconds.toFixed(1)}s`)}`;
|
|
5014
5248
|
if (d.outputTokens && d.elapsedSeconds > 0) {
|
|
5015
5249
|
const tokensPerSec = Math.round(d.outputTokens / d.elapsedSeconds);
|
|
5016
|
-
timeLine +=
|
|
5250
|
+
timeLine += chalk6.dim(` (${tokensPerSec} tok/s)`);
|
|
5017
5251
|
}
|
|
5018
5252
|
lines.push(timeLine);
|
|
5019
5253
|
}
|
|
5020
5254
|
if (d.cost !== void 0 && d.cost > 0) {
|
|
5021
5255
|
lines.push(
|
|
5022
|
-
`${indent}${
|
|
5256
|
+
`${indent}${chalk6.dim(BOX.vertical)} Cost: ${chalk6.cyan(`$${formatCost(d.cost)}`)}`
|
|
5023
5257
|
);
|
|
5024
5258
|
}
|
|
5025
5259
|
if (d.finishReason) {
|
|
5026
5260
|
const reason = d.finishReason.toUpperCase();
|
|
5027
|
-
const reasonColor = reason === "STOP" || reason === "END_TURN" ?
|
|
5028
|
-
lines.push(`${indent}${
|
|
5261
|
+
const reasonColor = reason === "STOP" || reason === "END_TURN" ? chalk6.green : chalk6.yellow;
|
|
5262
|
+
lines.push(`${indent}${chalk6.dim(BOX.vertical)} Finish: ${reasonColor(reason)}`);
|
|
5029
5263
|
}
|
|
5030
|
-
lines.push(`${indent}${
|
|
5264
|
+
lines.push(`${indent}${chalk6.dim(BOX.bottomLeft + BOX.horizontal.repeat(width - 1))}`);
|
|
5031
5265
|
return lines;
|
|
5032
5266
|
}
|
|
5033
5267
|
function formatGadgetCollapsed(node, selected) {
|
|
@@ -5035,15 +5269,15 @@ function formatGadgetCollapsed(node, selected) {
|
|
|
5035
5269
|
let indicatorColor;
|
|
5036
5270
|
if (node.error) {
|
|
5037
5271
|
indicator = ERROR_INDICATOR;
|
|
5038
|
-
indicatorColor =
|
|
5272
|
+
indicatorColor = chalk6.red;
|
|
5039
5273
|
} else if (node.isComplete) {
|
|
5040
5274
|
indicator = COMPLETE_INDICATOR;
|
|
5041
|
-
indicatorColor =
|
|
5275
|
+
indicatorColor = chalk6.green;
|
|
5042
5276
|
} else {
|
|
5043
5277
|
indicator = PROGRESS_INDICATOR;
|
|
5044
|
-
indicatorColor =
|
|
5278
|
+
indicatorColor = chalk6.blue;
|
|
5045
5279
|
}
|
|
5046
|
-
const gadgetLabel =
|
|
5280
|
+
const gadgetLabel = chalk6.magenta.bold(node.name);
|
|
5047
5281
|
let paramsStr = "";
|
|
5048
5282
|
if (node.parameters && Object.keys(node.parameters).length > 0) {
|
|
5049
5283
|
if (node.name === "TellUser") {
|
|
@@ -5065,15 +5299,15 @@ function formatGadgetCollapsed(node, selected) {
|
|
|
5065
5299
|
const formatted = entries.map(([key, value]) => {
|
|
5066
5300
|
const strValue = typeof value === "string" ? value : JSON.stringify(value);
|
|
5067
5301
|
const truncated = strValue.length > maxParamLen ? `${strValue.slice(0, maxParamLen - 3)}...` : strValue;
|
|
5068
|
-
return `${
|
|
5302
|
+
return `${chalk6.dim(key)}=${chalk6.cyan(truncated)}`;
|
|
5069
5303
|
});
|
|
5070
|
-
paramsStr = `${
|
|
5304
|
+
paramsStr = `${chalk6.dim("(")}${formatted.join(chalk6.dim(", "))}${chalk6.dim(")")}`;
|
|
5071
5305
|
}
|
|
5072
5306
|
}
|
|
5073
5307
|
let errorStr = "";
|
|
5074
5308
|
if (node.error) {
|
|
5075
5309
|
const truncated = node.error.length > 40 ? `${node.error.slice(0, 37)}...` : node.error;
|
|
5076
|
-
errorStr = ` ${
|
|
5310
|
+
errorStr = ` ${chalk6.red("error:")} ${truncated}`;
|
|
5077
5311
|
}
|
|
5078
5312
|
const metrics = [];
|
|
5079
5313
|
if (node.executionTimeMs !== void 0) {
|
|
@@ -5081,24 +5315,23 @@ function formatGadgetCollapsed(node, selected) {
|
|
|
5081
5315
|
}
|
|
5082
5316
|
if (node.subagentStats && node.subagentStats.llmCallCount > 0) {
|
|
5083
5317
|
const { inputTokens, cachedTokens, outputTokens } = node.subagentStats;
|
|
5084
|
-
const
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
}
|
|
5089
|
-
|
|
5090
|
-
metrics.push(tokenParts.join(" "));
|
|
5318
|
+
const tokenMetrics = buildTokenMetrics({
|
|
5319
|
+
input: inputTokens,
|
|
5320
|
+
cached: cachedTokens,
|
|
5321
|
+
output: outputTokens
|
|
5322
|
+
});
|
|
5323
|
+
metrics.push(tokenMetrics.join(" "));
|
|
5091
5324
|
} else if (node.resultTokens && node.resultTokens > 0) {
|
|
5092
|
-
metrics.push(
|
|
5325
|
+
metrics.push(chalk6.dim("\u2193") + chalk6.green(` ${formatTokens(node.resultTokens)}`));
|
|
5093
5326
|
}
|
|
5094
5327
|
if (node.cost && node.cost > 0) {
|
|
5095
|
-
metrics.push(
|
|
5328
|
+
metrics.push(costPart(node.cost));
|
|
5096
5329
|
}
|
|
5097
|
-
const metricsStr = metrics.length > 0 ? ` ${
|
|
5330
|
+
const metricsStr = metrics.length > 0 ? ` ${chalk6.dim(metrics.join(" | "))}` : "";
|
|
5098
5331
|
const line = `${indicatorColor(indicator)} ${gadgetLabel}${paramsStr}${errorStr}${metricsStr}`;
|
|
5099
5332
|
let result;
|
|
5100
5333
|
if (selected) {
|
|
5101
|
-
result =
|
|
5334
|
+
result = chalk6.bgBlue.white(line);
|
|
5102
5335
|
} else {
|
|
5103
5336
|
result = line;
|
|
5104
5337
|
}
|
|
@@ -5120,7 +5353,7 @@ function formatGadgetExpanded(node) {
|
|
|
5120
5353
|
const paramsToShow = node.parameters ? Object.entries(node.parameters).filter(([key]) => !(isTellUser && key === "message")) : [];
|
|
5121
5354
|
if (paramsToShow.length > 0) {
|
|
5122
5355
|
const headerLine = `${BOX.topLeft}${BOX.horizontal} Parameters ${BOX.horizontal.repeat(width - 14)}`;
|
|
5123
|
-
lines.push(`${indent}${
|
|
5356
|
+
lines.push(`${indent}${chalk6.dim(headerLine)}`);
|
|
5124
5357
|
for (const [key, value] of paramsToShow) {
|
|
5125
5358
|
const strValue = typeof value === "string" ? value : JSON.stringify(value, null, 2);
|
|
5126
5359
|
const valueLines = strValue.split("\n");
|
|
@@ -5128,30 +5361,30 @@ function formatGadgetExpanded(node) {
|
|
|
5128
5361
|
if (valueLines.length === 1) {
|
|
5129
5362
|
const truncated = strValue.length > maxValueLen ? `${strValue.slice(0, maxValueLen - 3)}...` : strValue;
|
|
5130
5363
|
lines.push(
|
|
5131
|
-
`${indent}${
|
|
5364
|
+
`${indent}${chalk6.dim(BOX.vertical)} ${chalk6.dim(key)}: ${chalk6.cyan(truncated)}`
|
|
5132
5365
|
);
|
|
5133
5366
|
} else {
|
|
5134
|
-
lines.push(`${indent}${
|
|
5367
|
+
lines.push(`${indent}${chalk6.dim(BOX.vertical)} ${chalk6.dim(key)}:`);
|
|
5135
5368
|
for (const line of valueLines.slice(0, 5)) {
|
|
5136
|
-
lines.push(`${indent}${
|
|
5369
|
+
lines.push(`${indent}${chalk6.dim(BOX.vertical)} ${chalk6.cyan(line)}`);
|
|
5137
5370
|
}
|
|
5138
5371
|
if (valueLines.length > 5) {
|
|
5139
5372
|
lines.push(
|
|
5140
|
-
`${indent}${
|
|
5373
|
+
`${indent}${chalk6.dim(BOX.vertical)} ${chalk6.dim(`... (${valueLines.length - 5} more lines)`)}`
|
|
5141
5374
|
);
|
|
5142
5375
|
}
|
|
5143
5376
|
}
|
|
5144
5377
|
}
|
|
5145
|
-
lines.push(`${indent}${
|
|
5378
|
+
lines.push(`${indent}${chalk6.dim(BOX.bottomLeft + BOX.horizontal.repeat(width - 1))}`);
|
|
5146
5379
|
}
|
|
5147
5380
|
if (node.name === "TellUser" && node.parameters?.message) {
|
|
5148
5381
|
const message = String(node.parameters.message);
|
|
5149
5382
|
const messageType = node.parameters.type || "info";
|
|
5150
5383
|
const typeIndicators = {
|
|
5151
|
-
info: { emoji: "\u2139\uFE0F", color:
|
|
5152
|
-
success: { emoji: "\u2705", color:
|
|
5153
|
-
warning: { emoji: "\u26A0\uFE0F", color:
|
|
5154
|
-
error: { emoji: "\u274C", color:
|
|
5384
|
+
info: { emoji: "\u2139\uFE0F", color: chalk6.blue },
|
|
5385
|
+
success: { emoji: "\u2705", color: chalk6.green },
|
|
5386
|
+
warning: { emoji: "\u26A0\uFE0F", color: chalk6.yellow },
|
|
5387
|
+
error: { emoji: "\u274C", color: chalk6.red }
|
|
5155
5388
|
};
|
|
5156
5389
|
const typeInfo = typeIndicators[messageType] || typeIndicators.info;
|
|
5157
5390
|
const headerLine = `${BOX.topLeft}${BOX.horizontal} ${typeInfo.emoji} Message ${BOX.horizontal.repeat(width - 13)}`;
|
|
@@ -5159,86 +5392,84 @@ function formatGadgetExpanded(node) {
|
|
|
5159
5392
|
const rendered = renderMarkdown(message);
|
|
5160
5393
|
const renderedLines = rendered.split("\n");
|
|
5161
5394
|
for (const line of renderedLines) {
|
|
5162
|
-
lines.push(`${indent}${
|
|
5395
|
+
lines.push(`${indent}${chalk6.dim(BOX.vertical)} ${line}`);
|
|
5163
5396
|
}
|
|
5164
5397
|
lines.push(`${indent}${typeInfo.color(BOX.bottomLeft + BOX.horizontal.repeat(width - 1))}`);
|
|
5165
5398
|
} else if (node.result || node.error) {
|
|
5166
5399
|
const headerText = node.error ? " Error " : " Result ";
|
|
5167
5400
|
const headerLine = `${BOX.topLeft}${BOX.horizontal}${headerText}${BOX.horizontal.repeat(width - headerText.length - 2)}`;
|
|
5168
|
-
lines.push(`${indent}${
|
|
5401
|
+
lines.push(`${indent}${chalk6.dim(headerLine)}`);
|
|
5169
5402
|
const content = node.error || node.result || "";
|
|
5170
5403
|
const contentLines = content.split("\n");
|
|
5171
5404
|
const maxLines = 10;
|
|
5172
5405
|
const displayLines = contentLines.slice(0, maxLines);
|
|
5173
5406
|
for (const line of displayLines) {
|
|
5174
5407
|
const truncated = line.length > width - 4 ? `${line.slice(0, width - 7)}...` : line;
|
|
5175
|
-
const color = node.error ?
|
|
5176
|
-
lines.push(`${indent}${
|
|
5408
|
+
const color = node.error ? chalk6.red : chalk6.white;
|
|
5409
|
+
lines.push(`${indent}${chalk6.dim(BOX.vertical)} ${color(truncated)}`);
|
|
5177
5410
|
}
|
|
5178
5411
|
if (contentLines.length > maxLines) {
|
|
5179
5412
|
lines.push(
|
|
5180
|
-
`${indent}${
|
|
5413
|
+
`${indent}${chalk6.dim(BOX.vertical)} ${chalk6.dim(`... (${contentLines.length - maxLines} more lines)`)}`
|
|
5181
5414
|
);
|
|
5182
5415
|
}
|
|
5183
5416
|
if (node.executionTimeMs !== void 0) {
|
|
5184
5417
|
lines.push(
|
|
5185
|
-
`${indent}${
|
|
5418
|
+
`${indent}${chalk6.dim(BOX.vertical)} Time: ${chalk6.dim(formatExecutionTime(node.executionTimeMs))}`
|
|
5186
5419
|
);
|
|
5187
5420
|
}
|
|
5188
|
-
lines.push(`${indent}${
|
|
5421
|
+
lines.push(`${indent}${chalk6.dim(BOX.bottomLeft + BOX.horizontal.repeat(width - 1))}`);
|
|
5189
5422
|
}
|
|
5190
5423
|
if (node.mediaOutputs && node.mediaOutputs.length > 0) {
|
|
5191
5424
|
const headerLine = `${BOX.topLeft}${BOX.horizontal} Media ${BOX.horizontal.repeat(width - 9)}`;
|
|
5192
|
-
lines.push(`${indent}${
|
|
5425
|
+
lines.push(`${indent}${chalk6.dim(headerLine)}`);
|
|
5193
5426
|
for (const media of node.mediaOutputs) {
|
|
5194
5427
|
const kindEmoji = media.kind === "audio" ? "\u{1F50A}" : media.kind === "image" ? "\u{1F5BC}\uFE0F" : media.kind === "video" ? "\u{1F3AC}" : "\u{1F4C4}";
|
|
5195
5428
|
const maxPathLen = width - 8;
|
|
5196
5429
|
const displayPath = media.path.length > maxPathLen ? `...${media.path.slice(-(maxPathLen - 3))}` : media.path;
|
|
5197
|
-
lines.push(`${indent}${
|
|
5430
|
+
lines.push(`${indent}${chalk6.dim(BOX.vertical)} ${kindEmoji} ${chalk6.cyan(displayPath)}`);
|
|
5198
5431
|
}
|
|
5199
|
-
lines.push(`${indent}${
|
|
5432
|
+
lines.push(`${indent}${chalk6.dim(BOX.bottomLeft + BOX.horizontal.repeat(width - 1))}`);
|
|
5200
5433
|
}
|
|
5201
5434
|
if (node.children.length > 0) {
|
|
5202
5435
|
const headerLine = `${BOX.topLeft}${BOX.horizontal} Subagent Activity ${BOX.horizontal.repeat(width - 21)}`;
|
|
5203
|
-
lines.push(`${indent}${
|
|
5436
|
+
lines.push(`${indent}${chalk6.dim(headerLine)}`);
|
|
5204
5437
|
lines.push(
|
|
5205
|
-
`${indent}${
|
|
5438
|
+
`${indent}${chalk6.dim(BOX.vertical)} ${chalk6.dim(`${node.children.length} nested calls (expand children to see details)`)}`
|
|
5206
5439
|
);
|
|
5207
|
-
lines.push(`${indent}${
|
|
5440
|
+
lines.push(`${indent}${chalk6.dim(BOX.bottomLeft + BOX.horizontal.repeat(width - 1))}`);
|
|
5208
5441
|
}
|
|
5209
5442
|
if (node.executionTimeMs !== void 0 || node.cost || node.resultTokens || node.subagentStats) {
|
|
5210
5443
|
const metricsHeaderLine = `${BOX.topLeft}${BOX.horizontal} Metrics ${BOX.horizontal.repeat(width - 11)}`;
|
|
5211
|
-
lines.push(`${indent}${
|
|
5444
|
+
lines.push(`${indent}${chalk6.dim(metricsHeaderLine)}`);
|
|
5212
5445
|
if (node.executionTimeMs !== void 0) {
|
|
5213
5446
|
lines.push(
|
|
5214
|
-
`${indent}${
|
|
5447
|
+
`${indent}${chalk6.dim(BOX.vertical)} Duration: ${chalk6.dim(formatExecutionTime(node.executionTimeMs))}`
|
|
5215
5448
|
);
|
|
5216
5449
|
}
|
|
5217
5450
|
if (node.resultTokens && node.resultTokens > 0) {
|
|
5218
5451
|
lines.push(
|
|
5219
|
-
`${indent}${
|
|
5452
|
+
`${indent}${chalk6.dim(BOX.vertical)} Output: ${chalk6.green(`~${formatTokens(node.resultTokens)}`)} tokens`
|
|
5220
5453
|
);
|
|
5221
5454
|
}
|
|
5222
5455
|
if (node.cost && node.cost > 0) {
|
|
5223
5456
|
lines.push(
|
|
5224
|
-
`${indent}${
|
|
5457
|
+
`${indent}${chalk6.dim(BOX.vertical)} Cost: ${chalk6.cyan(`$${formatCost(node.cost)}`)}`
|
|
5225
5458
|
);
|
|
5226
5459
|
}
|
|
5227
5460
|
if (node.subagentStats && node.subagentStats.llmCallCount > 0) {
|
|
5228
5461
|
const s = node.subagentStats;
|
|
5229
|
-
const
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
}
|
|
5234
|
-
|
|
5235
|
-
const tokenStr = tokenParts.join(" ");
|
|
5236
|
-
lines.push(`${indent}${chalk5.dim(BOX.vertical)} LLM calls: ${s.llmCallCount} (${tokenStr})`);
|
|
5462
|
+
const tokenStr = buildTokenMetrics({
|
|
5463
|
+
input: s.inputTokens,
|
|
5464
|
+
cached: s.cachedTokens,
|
|
5465
|
+
output: s.outputTokens
|
|
5466
|
+
}).join(" ");
|
|
5467
|
+
lines.push(`${indent}${chalk6.dim(BOX.vertical)} LLM calls: ${s.llmCallCount} (${tokenStr})`);
|
|
5237
5468
|
}
|
|
5238
|
-
lines.push(`${indent}${
|
|
5469
|
+
lines.push(`${indent}${chalk6.dim(BOX.bottomLeft + BOX.horizontal.repeat(width - 1))}`);
|
|
5239
5470
|
}
|
|
5240
5471
|
if (lines.length === 0) {
|
|
5241
|
-
lines.push(`${indent}${
|
|
5472
|
+
lines.push(`${indent}${chalk6.dim("No details available")}`);
|
|
5242
5473
|
}
|
|
5243
5474
|
return lines;
|
|
5244
5475
|
}
|
|
@@ -5253,26 +5484,183 @@ function getContinuationIndent(depth) {
|
|
|
5253
5484
|
return `${" ".repeat(depth)} `;
|
|
5254
5485
|
}
|
|
5255
5486
|
|
|
5256
|
-
// src/tui/
|
|
5257
|
-
|
|
5258
|
-
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
|
|
5266
|
-
|
|
5267
|
-
|
|
5268
|
-
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5272
|
-
|
|
5273
|
-
|
|
5274
|
-
|
|
5275
|
-
|
|
5487
|
+
// src/tui/block-content-formatter.ts
|
|
5488
|
+
function formatBlockContent(node, selected, expanded) {
|
|
5489
|
+
const indent = getIndent(node.depth);
|
|
5490
|
+
switch (node.type) {
|
|
5491
|
+
case "llm_call": {
|
|
5492
|
+
const collapsed = formatLLMCallCollapsed(node, selected);
|
|
5493
|
+
if (!expanded) {
|
|
5494
|
+
return indent + collapsed;
|
|
5495
|
+
}
|
|
5496
|
+
const expandedLines = formatLLMCallExpanded(node);
|
|
5497
|
+
const contIndent = getContinuationIndent(node.depth);
|
|
5498
|
+
return [indent + collapsed, ...expandedLines.map((line) => contIndent + line)].join("\n");
|
|
5499
|
+
}
|
|
5500
|
+
case "gadget": {
|
|
5501
|
+
const collapsed = formatGadgetCollapsed(node, selected);
|
|
5502
|
+
if (!expanded) {
|
|
5503
|
+
return indent + collapsed;
|
|
5504
|
+
}
|
|
5505
|
+
const expandedLines = formatGadgetExpanded(node);
|
|
5506
|
+
const contIndent = getContinuationIndent(node.depth);
|
|
5507
|
+
return [indent + collapsed, ...expandedLines.map((line) => contIndent + line)].join("\n");
|
|
5508
|
+
}
|
|
5509
|
+
case "text": {
|
|
5510
|
+
if (node.id.startsWith("user_")) {
|
|
5511
|
+
return formatUserMessage(node.content);
|
|
5512
|
+
}
|
|
5513
|
+
const fullContent = renderMarkdown(node.content);
|
|
5514
|
+
if (expanded) {
|
|
5515
|
+
return `
|
|
5516
|
+
${fullContent}
|
|
5517
|
+
`;
|
|
5518
|
+
}
|
|
5519
|
+
return abbreviateToLines(fullContent, 2, selected);
|
|
5520
|
+
}
|
|
5521
|
+
case "thinking": {
|
|
5522
|
+
return formatThinkingContent(node, indent, expanded);
|
|
5523
|
+
}
|
|
5524
|
+
case "system_message": {
|
|
5525
|
+
const icon = getSystemMessageIcon(node.category);
|
|
5526
|
+
const color = getSystemMessageColor(node.category);
|
|
5527
|
+
const RESET3 = "\x1B[0m";
|
|
5528
|
+
return `${indent}${color}${icon} ${node.message}${RESET3}`;
|
|
5529
|
+
}
|
|
5530
|
+
}
|
|
5531
|
+
}
|
|
5532
|
+
function formatThinkingContent(node, indent, expanded) {
|
|
5533
|
+
const DIM2 = "\x1B[2m";
|
|
5534
|
+
const RED_DIM = "\x1B[2;31m";
|
|
5535
|
+
const RESET3 = "\x1B[0m";
|
|
5536
|
+
const contIndent = getContinuationIndent(node.depth);
|
|
5537
|
+
if (node.thinkingType === "redacted") {
|
|
5538
|
+
return `${indent}${RED_DIM}\u{1F512} [Redacted thinking block]${RESET3}`;
|
|
5539
|
+
}
|
|
5540
|
+
if (!expanded) {
|
|
5541
|
+
const firstLine = node.content.split("\n")[0]?.slice(0, 60) ?? "";
|
|
5542
|
+
const suffix = node.isComplete ? "" : "...";
|
|
5543
|
+
return `${indent}${DIM2}\u{1F4AD} Thinking${suffix} ${firstLine}${RESET3}`;
|
|
5544
|
+
}
|
|
5545
|
+
const tokenInfo = node.isComplete ? ` (${Math.ceil(node.content.length / 4)} tokens est.)` : "";
|
|
5546
|
+
const header = `${indent}${DIM2}\u25BC \u{1F4AD} Thinking${tokenInfo}${RESET3}`;
|
|
5547
|
+
const contentLines = node.content.split("\n").map((line) => `${contIndent}${DIM2}${line}${RESET3}`);
|
|
5548
|
+
return [header, ...contentLines].join("\n");
|
|
5549
|
+
}
|
|
5550
|
+
function getSystemMessageIcon(category) {
|
|
5551
|
+
switch (category) {
|
|
5552
|
+
case "throttle":
|
|
5553
|
+
return "\u23F8";
|
|
5554
|
+
case "retry":
|
|
5555
|
+
return "\u{1F504}";
|
|
5556
|
+
case "info":
|
|
5557
|
+
return "\u2139\uFE0F";
|
|
5558
|
+
case "warning":
|
|
5559
|
+
return "\u26A0\uFE0F";
|
|
5560
|
+
case "error":
|
|
5561
|
+
return "\u274C";
|
|
5562
|
+
}
|
|
5563
|
+
}
|
|
5564
|
+
function getSystemMessageColor(category) {
|
|
5565
|
+
const YELLOW2 = "\x1B[33m";
|
|
5566
|
+
const BLUE = "\x1B[34m";
|
|
5567
|
+
const GRAY2 = "\x1B[90m";
|
|
5568
|
+
const RED2 = "\x1B[31m";
|
|
5569
|
+
switch (category) {
|
|
5570
|
+
case "throttle":
|
|
5571
|
+
return YELLOW2;
|
|
5572
|
+
case "retry":
|
|
5573
|
+
return BLUE;
|
|
5574
|
+
case "info":
|
|
5575
|
+
return GRAY2;
|
|
5576
|
+
case "warning":
|
|
5577
|
+
return YELLOW2;
|
|
5578
|
+
case "error":
|
|
5579
|
+
return RED2;
|
|
5580
|
+
}
|
|
5581
|
+
}
|
|
5582
|
+
function abbreviateToLines(text3, maxLines, selected) {
|
|
5583
|
+
const lines = text3.split("\n");
|
|
5584
|
+
let startIndex = 0;
|
|
5585
|
+
while (startIndex < lines.length && lines[startIndex].trim() === "") {
|
|
5586
|
+
startIndex++;
|
|
5587
|
+
}
|
|
5588
|
+
const contentLines = lines.slice(startIndex);
|
|
5589
|
+
if (contentLines.length <= maxLines) {
|
|
5590
|
+
return `
|
|
5591
|
+
${contentLines.join("\n")}`;
|
|
5592
|
+
}
|
|
5593
|
+
const truncatedLines = contentLines.slice(0, maxLines);
|
|
5594
|
+
const indicator = selected ? "\u25B6 ..." : " ...";
|
|
5595
|
+
return `
|
|
5596
|
+
${truncatedLines.join("\n")}
|
|
5597
|
+
${indicator}`;
|
|
5598
|
+
}
|
|
5599
|
+
function isNodeVisibleInFilterMode(node, contentFilterMode) {
|
|
5600
|
+
if (contentFilterMode === "full") {
|
|
5601
|
+
return true;
|
|
5602
|
+
}
|
|
5603
|
+
switch (node.type) {
|
|
5604
|
+
case "text":
|
|
5605
|
+
return true;
|
|
5606
|
+
case "gadget":
|
|
5607
|
+
return shouldRenderAsText(node, contentFilterMode);
|
|
5608
|
+
default:
|
|
5609
|
+
return false;
|
|
5610
|
+
}
|
|
5611
|
+
}
|
|
5612
|
+
function shouldRenderAsText(node, contentFilterMode) {
|
|
5613
|
+
if (contentFilterMode !== "focused") return false;
|
|
5614
|
+
if (node.type !== "gadget") return false;
|
|
5615
|
+
const name = node.name;
|
|
5616
|
+
return name === "TellUser" || name === "AskUser" || name === "Finish";
|
|
5617
|
+
}
|
|
5618
|
+
function formatGadgetAsText(node) {
|
|
5619
|
+
if (node.name === "TellUser") {
|
|
5620
|
+
const message = node.parameters?.message;
|
|
5621
|
+
if (typeof message === "string") {
|
|
5622
|
+
return `
|
|
5623
|
+
${renderMarkdown(message)}
|
|
5624
|
+
`;
|
|
5625
|
+
}
|
|
5626
|
+
} else if (node.name === "AskUser") {
|
|
5627
|
+
const question = node.parameters?.question;
|
|
5628
|
+
if (typeof question === "string") {
|
|
5629
|
+
return `
|
|
5630
|
+
? ${question}
|
|
5631
|
+
`;
|
|
5632
|
+
}
|
|
5633
|
+
} else if (node.name === "Finish") {
|
|
5634
|
+
const message = node.parameters?.message;
|
|
5635
|
+
if (typeof message === "string" && message.trim()) {
|
|
5636
|
+
return `
|
|
5637
|
+
\x1B[32m\u2713\x1B[0m ${renderMarkdown(message)}
|
|
5638
|
+
`;
|
|
5639
|
+
}
|
|
5640
|
+
}
|
|
5641
|
+
return "";
|
|
5642
|
+
}
|
|
5643
|
+
|
|
5644
|
+
// src/tui/node-store.ts
|
|
5645
|
+
var NodeStore = class {
|
|
5646
|
+
/** All nodes in the tree (flat for easy lookup) */
|
|
5647
|
+
nodes = /* @__PURE__ */ new Map();
|
|
5648
|
+
/** Root node IDs (top-level LLM calls and text) */
|
|
5649
|
+
rootIds = [];
|
|
5650
|
+
/** Counter for generating unique node IDs */
|
|
5651
|
+
nodeIdCounter = 0;
|
|
5652
|
+
/** Current LLM call node (for adding gadget children) */
|
|
5653
|
+
currentLLMCallId = null;
|
|
5654
|
+
/** Current thinking block (accumulates chunks during streaming) */
|
|
5655
|
+
currentThinkingId = null;
|
|
5656
|
+
/** Current session ID (increments each new REPL turn) */
|
|
5657
|
+
currentSessionId = 0;
|
|
5658
|
+
/** Previous session ID (for deferred cleanup) */
|
|
5659
|
+
previousSessionId = null;
|
|
5660
|
+
/** Track main agent LLM calls by iteration for idempotency */
|
|
5661
|
+
llmCallByIteration = /* @__PURE__ */ new Map();
|
|
5662
|
+
/** Track gadgets by invocationId for idempotency */
|
|
5663
|
+
gadgetByInvocationId = /* @__PURE__ */ new Map();
|
|
5276
5664
|
/** Track nested LLM calls by parentId_iteration for idempotency */
|
|
5277
5665
|
nestedLLMCallByKey = /* @__PURE__ */ new Map();
|
|
5278
5666
|
/** Callbacks for change notifications */
|
|
@@ -5675,6 +6063,28 @@ var NodeStore = class {
|
|
|
5675
6063
|
}
|
|
5676
6064
|
};
|
|
5677
6065
|
|
|
6066
|
+
// src/tui/tree-layout.ts
|
|
6067
|
+
function traverseNodeTree(nodeId, getNode, visit, top) {
|
|
6068
|
+
const node = getNode(nodeId);
|
|
6069
|
+
if (!node) {
|
|
6070
|
+
return top;
|
|
6071
|
+
}
|
|
6072
|
+
let nextTop = visit(nodeId, node, top);
|
|
6073
|
+
if ("children" in node) {
|
|
6074
|
+
for (const childId of node.children) {
|
|
6075
|
+
nextTop = traverseNodeTree(childId, getNode, visit, nextTop);
|
|
6076
|
+
}
|
|
6077
|
+
}
|
|
6078
|
+
return nextTop;
|
|
6079
|
+
}
|
|
6080
|
+
function traverseRootTrees(rootIds, getNode, visit) {
|
|
6081
|
+
let top = 0;
|
|
6082
|
+
for (const rootId of rootIds) {
|
|
6083
|
+
top = traverseNodeTree(rootId, getNode, visit, top);
|
|
6084
|
+
}
|
|
6085
|
+
return top;
|
|
6086
|
+
}
|
|
6087
|
+
|
|
5678
6088
|
// src/tui/scroll-manager.ts
|
|
5679
6089
|
var ScrollManager = class _ScrollManager {
|
|
5680
6090
|
container;
|
|
@@ -5803,25 +6213,14 @@ var ScrollManager = class _ScrollManager {
|
|
|
5803
6213
|
* Calculate total height of all rendered blocks.
|
|
5804
6214
|
*/
|
|
5805
6215
|
getTotalContentHeight() {
|
|
5806
|
-
|
|
5807
|
-
|
|
5808
|
-
|
|
5809
|
-
|
|
5810
|
-
|
|
5811
|
-
|
|
5812
|
-
sumNodeTreeHeight(nodeId, currentHeight) {
|
|
5813
|
-
const node = this.accessors.getNode(nodeId);
|
|
5814
|
-
if (!node) return currentHeight;
|
|
5815
|
-
const block = this.accessors.getBlock(nodeId);
|
|
5816
|
-
if (block) {
|
|
5817
|
-
currentHeight += getBlockHeight(block);
|
|
5818
|
-
}
|
|
5819
|
-
if ("children" in node) {
|
|
5820
|
-
for (const childId of node.children) {
|
|
5821
|
-
currentHeight = this.sumNodeTreeHeight(childId, currentHeight);
|
|
6216
|
+
return traverseRootTrees(
|
|
6217
|
+
this.accessors.getRootIds(),
|
|
6218
|
+
(id) => this.accessors.getNode(id),
|
|
6219
|
+
(nodeId, _node, currentHeight) => {
|
|
6220
|
+
const block = this.accessors.getBlock(nodeId);
|
|
6221
|
+
return currentHeight + (block ? getBlockHeight(block) : 0);
|
|
5822
6222
|
}
|
|
5823
|
-
|
|
5824
|
-
return currentHeight;
|
|
6223
|
+
);
|
|
5825
6224
|
}
|
|
5826
6225
|
/**
|
|
5827
6226
|
* Calculate vertical offset to push content to bottom when content < viewport.
|
|
@@ -5838,17 +6237,18 @@ var ScrollManager = class _ScrollManager {
|
|
|
5838
6237
|
* Apply vertical offset to a node tree (for bottom alignment).
|
|
5839
6238
|
*/
|
|
5840
6239
|
applyOffsetToNodeTree(nodeId, offset) {
|
|
5841
|
-
|
|
5842
|
-
|
|
5843
|
-
|
|
5844
|
-
|
|
5845
|
-
|
|
5846
|
-
|
|
5847
|
-
|
|
5848
|
-
|
|
5849
|
-
|
|
5850
|
-
}
|
|
5851
|
-
|
|
6240
|
+
traverseNodeTree(
|
|
6241
|
+
nodeId,
|
|
6242
|
+
(id) => this.accessors.getNode(id),
|
|
6243
|
+
(currentNodeId, _node, top) => {
|
|
6244
|
+
const block = this.accessors.getBlock(currentNodeId);
|
|
6245
|
+
if (block) {
|
|
6246
|
+
block.box.top = block.box.top + offset;
|
|
6247
|
+
}
|
|
6248
|
+
return top;
|
|
6249
|
+
},
|
|
6250
|
+
0
|
|
6251
|
+
);
|
|
5852
6252
|
}
|
|
5853
6253
|
};
|
|
5854
6254
|
function getBlockHeight(block) {
|
|
@@ -5856,6 +6256,167 @@ function getBlockHeight(block) {
|
|
|
5856
6256
|
return content.split("\n").length;
|
|
5857
6257
|
}
|
|
5858
6258
|
|
|
6259
|
+
// src/tui/tree-bridge.ts
|
|
6260
|
+
var TreeBridge = class {
|
|
6261
|
+
/** Unsubscribe function for tree events */
|
|
6262
|
+
treeUnsubscribe = null;
|
|
6263
|
+
/** Map tree node IDs to block node IDs */
|
|
6264
|
+
treeNodeToBlockId = /* @__PURE__ */ new Map();
|
|
6265
|
+
callbacks;
|
|
6266
|
+
constructor(callbacks) {
|
|
6267
|
+
this.callbacks = callbacks;
|
|
6268
|
+
}
|
|
6269
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
6270
|
+
// Public API
|
|
6271
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
6272
|
+
/**
|
|
6273
|
+
* Check if tree subscription is active.
|
|
6274
|
+
*/
|
|
6275
|
+
isSubscribed() {
|
|
6276
|
+
return this.treeUnsubscribe !== null;
|
|
6277
|
+
}
|
|
6278
|
+
/**
|
|
6279
|
+
* Subscribe to an ExecutionTree for automatic block updates.
|
|
6280
|
+
*
|
|
6281
|
+
* When subscribed, the TreeBridge will automatically create and update
|
|
6282
|
+
* blocks based on tree events, calling the provided callbacks.
|
|
6283
|
+
*
|
|
6284
|
+
* @param tree - The ExecutionTree to subscribe to
|
|
6285
|
+
* @returns Unsubscribe function to stop listening
|
|
6286
|
+
*
|
|
6287
|
+
* @example
|
|
6288
|
+
* ```typescript
|
|
6289
|
+
* const agent = builder.ask("Hello");
|
|
6290
|
+
* const unsubscribe = treeBridge.subscribeToTree(agent.getTree());
|
|
6291
|
+
*
|
|
6292
|
+
* for await (const event of agent.run()) {
|
|
6293
|
+
* // Blocks are automatically updated via tree subscription
|
|
6294
|
+
* }
|
|
6295
|
+
*
|
|
6296
|
+
* unsubscribe();
|
|
6297
|
+
* ```
|
|
6298
|
+
*/
|
|
6299
|
+
subscribeToTree(tree) {
|
|
6300
|
+
if (this.treeUnsubscribe) {
|
|
6301
|
+
this.treeUnsubscribe();
|
|
6302
|
+
}
|
|
6303
|
+
this.treeNodeToBlockId.clear();
|
|
6304
|
+
this.callbacks.onClearIdempotencyMaps();
|
|
6305
|
+
this.treeUnsubscribe = tree.onAll((event) => {
|
|
6306
|
+
this.handleTreeEvent(event, tree);
|
|
6307
|
+
});
|
|
6308
|
+
return () => {
|
|
6309
|
+
if (this.treeUnsubscribe) {
|
|
6310
|
+
this.treeUnsubscribe();
|
|
6311
|
+
this.treeUnsubscribe = null;
|
|
6312
|
+
}
|
|
6313
|
+
};
|
|
6314
|
+
}
|
|
6315
|
+
/**
|
|
6316
|
+
* Get block ID for a tree node ID.
|
|
6317
|
+
* Useful for external code that needs to correlate tree nodes with blocks.
|
|
6318
|
+
*/
|
|
6319
|
+
getBlockIdForTreeNode(treeNodeId) {
|
|
6320
|
+
return this.treeNodeToBlockId.get(treeNodeId);
|
|
6321
|
+
}
|
|
6322
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
6323
|
+
// Event Handling
|
|
6324
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
6325
|
+
/**
|
|
6326
|
+
* Handle an ExecutionTree event.
|
|
6327
|
+
*/
|
|
6328
|
+
handleTreeEvent(event, tree) {
|
|
6329
|
+
switch (event.type) {
|
|
6330
|
+
case "llm_call_start": {
|
|
6331
|
+
this.callbacks.onResetThinking();
|
|
6332
|
+
let parentBlockId;
|
|
6333
|
+
if (event.parentId) {
|
|
6334
|
+
parentBlockId = this.treeNodeToBlockId.get(event.parentId);
|
|
6335
|
+
}
|
|
6336
|
+
const blockId = this.callbacks.onAddLLMCall(
|
|
6337
|
+
event.iteration + 1,
|
|
6338
|
+
event.model,
|
|
6339
|
+
parentBlockId,
|
|
6340
|
+
event.depth > 0
|
|
6341
|
+
);
|
|
6342
|
+
this.treeNodeToBlockId.set(event.nodeId, blockId);
|
|
6343
|
+
const startNode = tree.getNode(event.nodeId);
|
|
6344
|
+
if (startNode?.type === "llm_call" && startNode.request) {
|
|
6345
|
+
this.callbacks.onSetLLMCallRequest(blockId, startNode.request);
|
|
6346
|
+
}
|
|
6347
|
+
break;
|
|
6348
|
+
}
|
|
6349
|
+
case "llm_call_complete": {
|
|
6350
|
+
this.callbacks.onCompleteThinking();
|
|
6351
|
+
const blockId = this.treeNodeToBlockId.get(event.nodeId);
|
|
6352
|
+
if (blockId) {
|
|
6353
|
+
this.callbacks.onCompleteLLMCall(blockId, {
|
|
6354
|
+
inputTokens: event.usage?.inputTokens,
|
|
6355
|
+
cachedInputTokens: event.usage?.cachedInputTokens,
|
|
6356
|
+
outputTokens: event.usage?.outputTokens,
|
|
6357
|
+
reasoningTokens: event.usage?.reasoningTokens,
|
|
6358
|
+
cost: event.cost,
|
|
6359
|
+
finishReason: event.finishReason ?? void 0
|
|
6360
|
+
});
|
|
6361
|
+
const completeNode = tree.getNode(event.nodeId);
|
|
6362
|
+
if (completeNode?.type === "llm_call" && completeNode.response) {
|
|
6363
|
+
this.callbacks.onSetLLMCallResponse(blockId, completeNode.response);
|
|
6364
|
+
}
|
|
6365
|
+
}
|
|
6366
|
+
break;
|
|
6367
|
+
}
|
|
6368
|
+
case "thinking": {
|
|
6369
|
+
this.callbacks.onAddThinking(event.content, event.thinkingType);
|
|
6370
|
+
break;
|
|
6371
|
+
}
|
|
6372
|
+
case "gadget_call": {
|
|
6373
|
+
let parentBlockId;
|
|
6374
|
+
if (event.parentId) {
|
|
6375
|
+
parentBlockId = this.treeNodeToBlockId.get(event.parentId);
|
|
6376
|
+
}
|
|
6377
|
+
const previousLLMCallId = this.callbacks.onGetCurrentLLMCallId();
|
|
6378
|
+
if (parentBlockId) {
|
|
6379
|
+
this.callbacks.onSetCurrentLLMCall(parentBlockId);
|
|
6380
|
+
}
|
|
6381
|
+
const blockId = this.callbacks.onAddGadget(
|
|
6382
|
+
event.invocationId,
|
|
6383
|
+
event.name,
|
|
6384
|
+
event.parameters
|
|
6385
|
+
);
|
|
6386
|
+
this.treeNodeToBlockId.set(event.nodeId, blockId);
|
|
6387
|
+
this.callbacks.onSetCurrentLLMCall(previousLLMCallId);
|
|
6388
|
+
break;
|
|
6389
|
+
}
|
|
6390
|
+
case "gadget_complete": {
|
|
6391
|
+
const mediaOutputs = event.storedMedia?.map((m) => ({
|
|
6392
|
+
kind: m.kind,
|
|
6393
|
+
path: m.path,
|
|
6394
|
+
mimeType: m.mimeType,
|
|
6395
|
+
description: m.description
|
|
6396
|
+
}));
|
|
6397
|
+
this.callbacks.onCompleteGadget(event.invocationId, {
|
|
6398
|
+
result: event.result,
|
|
6399
|
+
executionTimeMs: event.executionTimeMs,
|
|
6400
|
+
cost: event.cost,
|
|
6401
|
+
mediaOutputs
|
|
6402
|
+
});
|
|
6403
|
+
break;
|
|
6404
|
+
}
|
|
6405
|
+
case "gadget_error": {
|
|
6406
|
+
this.callbacks.onCompleteGadget(event.invocationId, {
|
|
6407
|
+
error: event.error,
|
|
6408
|
+
executionTimeMs: event.executionTimeMs
|
|
6409
|
+
});
|
|
6410
|
+
break;
|
|
6411
|
+
}
|
|
6412
|
+
case "gadget_skipped": {
|
|
6413
|
+
this.callbacks.onSkipGadget(event.invocationId, `Skipped: ${event.failedDependencyError}`);
|
|
6414
|
+
break;
|
|
6415
|
+
}
|
|
6416
|
+
}
|
|
6417
|
+
}
|
|
6418
|
+
};
|
|
6419
|
+
|
|
5859
6420
|
// src/tui/block-renderer.ts
|
|
5860
6421
|
var BlockRenderer = class {
|
|
5861
6422
|
container;
|
|
@@ -5865,6 +6426,8 @@ var BlockRenderer = class {
|
|
|
5865
6426
|
nodeStore;
|
|
5866
6427
|
/** Scroll manager — manages scroll position, follow mode, and bottom alignment */
|
|
5867
6428
|
scrollManager;
|
|
6429
|
+
/** Tree bridge — manages ExecutionTree subscriptions and tree→block mapping */
|
|
6430
|
+
treeBridge;
|
|
5868
6431
|
/** Rendered blocks with UI state */
|
|
5869
6432
|
blocks = /* @__PURE__ */ new Map();
|
|
5870
6433
|
/** IDs of selectable blocks in display order */
|
|
@@ -5894,6 +6457,21 @@ var BlockRenderer = class {
|
|
|
5894
6457
|
getBlock: (id) => this.blocks.get(id),
|
|
5895
6458
|
getSelectedBlock: () => this.getSelectedBlock()
|
|
5896
6459
|
});
|
|
6460
|
+
this.treeBridge = new TreeBridge({
|
|
6461
|
+
onResetThinking: () => this.nodeStore.resetCurrentThinking(),
|
|
6462
|
+
onSetCurrentLLMCall: (llmCallId) => this.setCurrentLLMCall(llmCallId),
|
|
6463
|
+
onClearIdempotencyMaps: () => this.nodeStore.clearIdempotencyMaps(),
|
|
6464
|
+
onAddLLMCall: (iteration, model, parentGadgetId, isNested) => this.addLLMCall(iteration, model, parentGadgetId, isNested),
|
|
6465
|
+
onCompleteLLMCall: (id, details, rawResponse) => this.completeLLMCall(id, details, rawResponse),
|
|
6466
|
+
onSetLLMCallRequest: (id, messages) => this.setLLMCallRequest(id, messages),
|
|
6467
|
+
onSetLLMCallResponse: (id, rawResponse) => this.setLLMCallResponse(id, rawResponse),
|
|
6468
|
+
onCompleteThinking: () => this.completeThinking(),
|
|
6469
|
+
onAddThinking: (content, thinkingType) => this.addThinking(content, thinkingType),
|
|
6470
|
+
onAddGadget: (invocationId, name, parameters) => this.addGadget(invocationId, name, parameters),
|
|
6471
|
+
onCompleteGadget: (invocationId, options) => this.completeGadget(invocationId, options),
|
|
6472
|
+
onSkipGadget: (invocationId, reason) => this.skipGadget(invocationId, reason),
|
|
6473
|
+
onGetCurrentLLMCallId: () => this.getCurrentLLMCallId()
|
|
6474
|
+
});
|
|
5897
6475
|
}
|
|
5898
6476
|
// ───────────────────────────────────────────────────────────────────────────
|
|
5899
6477
|
// Public API - Node Management
|
|
@@ -5939,6 +6517,12 @@ var BlockRenderer = class {
|
|
|
5939
6517
|
completeGadget(invocationId, options = {}) {
|
|
5940
6518
|
this.nodeStore.completeGadget(invocationId, options);
|
|
5941
6519
|
}
|
|
6520
|
+
/**
|
|
6521
|
+
* Mark a gadget as skipped with a rendered reason.
|
|
6522
|
+
*/
|
|
6523
|
+
skipGadget(invocationId, reason) {
|
|
6524
|
+
this.nodeStore.completeGadget(invocationId, { error: reason });
|
|
6525
|
+
}
|
|
5942
6526
|
/**
|
|
5943
6527
|
* Add a text node (flows between LLM calls).
|
|
5944
6528
|
*/
|
|
@@ -6006,9 +6590,8 @@ var BlockRenderer = class {
|
|
|
6006
6590
|
/**
|
|
6007
6591
|
* Get the current LLM call ID.
|
|
6008
6592
|
*
|
|
6009
|
-
* In tree mode,
|
|
6010
|
-
*
|
|
6011
|
-
* determined by event.parentId in handleTreeEvent(), not by this method.
|
|
6593
|
+
* In tree mode, TreeBridge uses this to restore the current parent context
|
|
6594
|
+
* while translating gadget events into renderer updates.
|
|
6012
6595
|
*/
|
|
6013
6596
|
getCurrentLLMCallId() {
|
|
6014
6597
|
return this.nodeStore.currentLLMCallId;
|
|
@@ -6018,7 +6601,7 @@ var BlockRenderer = class {
|
|
|
6018
6601
|
* When active, external code should skip block creation (tree handles it).
|
|
6019
6602
|
*/
|
|
6020
6603
|
isTreeSubscribed() {
|
|
6021
|
-
return this.
|
|
6604
|
+
return this.treeBridge.isSubscribed();
|
|
6022
6605
|
}
|
|
6023
6606
|
/**
|
|
6024
6607
|
* Store raw response for an LLM call (enrichment only).
|
|
@@ -6189,22 +6772,7 @@ var BlockRenderer = class {
|
|
|
6189
6772
|
* Called when nodes are added/removed.
|
|
6190
6773
|
*/
|
|
6191
6774
|
rebuildBlocks() {
|
|
6192
|
-
|
|
6193
|
-
child.detach();
|
|
6194
|
-
}
|
|
6195
|
-
this.container.setContent("");
|
|
6196
|
-
this.blocks.clear();
|
|
6197
|
-
this.selectableIds = [];
|
|
6198
|
-
let top = 0;
|
|
6199
|
-
for (const rootId of this.nodeStore.rootIds) {
|
|
6200
|
-
top = this.renderNodeTree(rootId, top);
|
|
6201
|
-
}
|
|
6202
|
-
if (this.selectedIndex >= this.selectableIds.length) {
|
|
6203
|
-
this.selectedIndex = this.selectableIds.length - 1;
|
|
6204
|
-
}
|
|
6205
|
-
this.applyBottomAlignmentAndScroll();
|
|
6206
|
-
this.renderCallback();
|
|
6207
|
-
this.notifyHasContentChange();
|
|
6775
|
+
this.rebuildBlocksCore(false);
|
|
6208
6776
|
}
|
|
6209
6777
|
/**
|
|
6210
6778
|
* Render a node and its children recursively.
|
|
@@ -6218,64 +6786,29 @@ var BlockRenderer = class {
|
|
|
6218
6786
|
* (no headers, just content) for a clean chat-like experience.
|
|
6219
6787
|
*/
|
|
6220
6788
|
renderNodeTree(nodeId, top) {
|
|
6221
|
-
|
|
6222
|
-
|
|
6223
|
-
|
|
6224
|
-
|
|
6225
|
-
|
|
6226
|
-
|
|
6227
|
-
|
|
6228
|
-
|
|
6229
|
-
this.
|
|
6230
|
-
|
|
6231
|
-
|
|
6232
|
-
|
|
6233
|
-
|
|
6234
|
-
|
|
6235
|
-
|
|
6236
|
-
|
|
6237
|
-
}
|
|
6238
|
-
}
|
|
6239
|
-
return top;
|
|
6240
|
-
}
|
|
6241
|
-
/**
|
|
6242
|
-
* Check if a gadget should render as plain text in focused mode.
|
|
6243
|
-
* TellUser, AskUser, and Finish render as text for a chat-like experience.
|
|
6244
|
-
*/
|
|
6245
|
-
shouldRenderAsText(node) {
|
|
6246
|
-
if (this.contentFilterMode !== "focused") return false;
|
|
6247
|
-
if (node.type !== "gadget") return false;
|
|
6248
|
-
const name = node.name;
|
|
6249
|
-
return name === "TellUser" || name === "AskUser" || name === "Finish";
|
|
6789
|
+
return traverseNodeTree(
|
|
6790
|
+
nodeId,
|
|
6791
|
+
(id) => this.getNode(id),
|
|
6792
|
+
(currentNodeId, node, currentTop) => {
|
|
6793
|
+
if (!this.isNodeVisible(node)) {
|
|
6794
|
+
return currentTop;
|
|
6795
|
+
}
|
|
6796
|
+
const block = shouldRenderAsText(node, this.contentFilterMode) ? this.createTextLikeBlock(node, currentTop) : this.createBlock(node, currentTop);
|
|
6797
|
+
this.blocks.set(currentNodeId, block);
|
|
6798
|
+
if (block.selectable) {
|
|
6799
|
+
this.selectableIds.push(currentNodeId);
|
|
6800
|
+
}
|
|
6801
|
+
return currentTop + getBlockHeight(block);
|
|
6802
|
+
},
|
|
6803
|
+
top
|
|
6804
|
+
);
|
|
6250
6805
|
}
|
|
6251
6806
|
/**
|
|
6252
6807
|
* Create a text-like block for TellUser/AskUser/Finish gadgets in focused mode.
|
|
6253
6808
|
* Renders just the content without the gadget header.
|
|
6254
6809
|
*/
|
|
6255
6810
|
createTextLikeBlock(node, top) {
|
|
6256
|
-
|
|
6257
|
-
if (node.name === "TellUser") {
|
|
6258
|
-
const message = node.parameters?.message;
|
|
6259
|
-
if (typeof message === "string") {
|
|
6260
|
-
content = `
|
|
6261
|
-
${renderMarkdown(message)}
|
|
6262
|
-
`;
|
|
6263
|
-
}
|
|
6264
|
-
} else if (node.name === "AskUser") {
|
|
6265
|
-
const question = node.parameters?.question;
|
|
6266
|
-
if (typeof question === "string") {
|
|
6267
|
-
content = `
|
|
6268
|
-
? ${question}
|
|
6269
|
-
`;
|
|
6270
|
-
}
|
|
6271
|
-
} else if (node.name === "Finish") {
|
|
6272
|
-
const message = node.parameters?.message;
|
|
6273
|
-
if (typeof message === "string" && message.trim()) {
|
|
6274
|
-
content = `
|
|
6275
|
-
\x1B[32m\u2713\x1B[0m ${renderMarkdown(message)}
|
|
6276
|
-
`;
|
|
6277
|
-
}
|
|
6278
|
-
}
|
|
6811
|
+
const content = formatGadgetAsText(node);
|
|
6279
6812
|
const box = new Box({
|
|
6280
6813
|
parent: this.container,
|
|
6281
6814
|
top,
|
|
@@ -6300,7 +6833,7 @@ ${renderMarkdown(message)}
|
|
|
6300
6833
|
const isSelected = this.selectableIds.length === this.selectedIndex;
|
|
6301
6834
|
const selectable = node.type !== "text" || !node.id.startsWith("user_");
|
|
6302
6835
|
const expanded = this.expandedStates.get(node.id) ?? false;
|
|
6303
|
-
const content =
|
|
6836
|
+
const content = formatBlockContent(node, isSelected, expanded);
|
|
6304
6837
|
const box = new Box({
|
|
6305
6838
|
parent: this.container,
|
|
6306
6839
|
top,
|
|
@@ -6318,133 +6851,6 @@ ${renderMarkdown(message)}
|
|
|
6318
6851
|
selectable
|
|
6319
6852
|
};
|
|
6320
6853
|
}
|
|
6321
|
-
/**
|
|
6322
|
-
* Format block content based on type and state.
|
|
6323
|
-
*/
|
|
6324
|
-
formatBlockContent(node, selected, expanded) {
|
|
6325
|
-
const indent = getIndent(node.depth);
|
|
6326
|
-
switch (node.type) {
|
|
6327
|
-
case "llm_call": {
|
|
6328
|
-
const collapsed = formatLLMCallCollapsed(node, selected);
|
|
6329
|
-
if (!expanded) {
|
|
6330
|
-
return indent + collapsed;
|
|
6331
|
-
}
|
|
6332
|
-
const expandedLines = formatLLMCallExpanded(node);
|
|
6333
|
-
const contIndent = getContinuationIndent(node.depth);
|
|
6334
|
-
return [indent + collapsed, ...expandedLines.map((line) => contIndent + line)].join("\n");
|
|
6335
|
-
}
|
|
6336
|
-
case "gadget": {
|
|
6337
|
-
const collapsed = formatGadgetCollapsed(node, selected);
|
|
6338
|
-
if (!expanded) {
|
|
6339
|
-
return indent + collapsed;
|
|
6340
|
-
}
|
|
6341
|
-
const expandedLines = formatGadgetExpanded(node);
|
|
6342
|
-
const contIndent = getContinuationIndent(node.depth);
|
|
6343
|
-
return [indent + collapsed, ...expandedLines.map((line) => contIndent + line)].join("\n");
|
|
6344
|
-
}
|
|
6345
|
-
case "text": {
|
|
6346
|
-
if (node.id.startsWith("user_")) {
|
|
6347
|
-
return formatUserMessage(node.content);
|
|
6348
|
-
}
|
|
6349
|
-
const fullContent = renderMarkdown(node.content);
|
|
6350
|
-
if (expanded) {
|
|
6351
|
-
return `
|
|
6352
|
-
${fullContent}
|
|
6353
|
-
`;
|
|
6354
|
-
}
|
|
6355
|
-
return this.abbreviateToLines(fullContent, 2, selected);
|
|
6356
|
-
}
|
|
6357
|
-
case "thinking": {
|
|
6358
|
-
const DIM2 = "\x1B[2m";
|
|
6359
|
-
const RED_DIM = "\x1B[2;31m";
|
|
6360
|
-
const RESET3 = "\x1B[0m";
|
|
6361
|
-
const contIndent = getContinuationIndent(node.depth);
|
|
6362
|
-
if (node.thinkingType === "redacted") {
|
|
6363
|
-
const header2 = `${indent}${RED_DIM}\u{1F512} [Redacted thinking block]${RESET3}`;
|
|
6364
|
-
return header2;
|
|
6365
|
-
}
|
|
6366
|
-
if (!expanded) {
|
|
6367
|
-
const firstLine = node.content.split("\n")[0]?.slice(0, 60) ?? "";
|
|
6368
|
-
const suffix = node.isComplete ? "" : "...";
|
|
6369
|
-
return `${indent}${DIM2}\u{1F4AD} Thinking${suffix} ${firstLine}${RESET3}`;
|
|
6370
|
-
}
|
|
6371
|
-
const tokenInfo = node.isComplete ? ` (${Math.ceil(node.content.length / 4)} tokens est.)` : "";
|
|
6372
|
-
const header = `${indent}${DIM2}\u25BC \u{1F4AD} Thinking${tokenInfo}${RESET3}`;
|
|
6373
|
-
const contentLines = node.content.split("\n").map((line) => `${contIndent}${DIM2}${line}${RESET3}`);
|
|
6374
|
-
return [header, ...contentLines].join("\n");
|
|
6375
|
-
}
|
|
6376
|
-
case "system_message": {
|
|
6377
|
-
const icon = this.getSystemMessageIcon(node.category);
|
|
6378
|
-
const color = this.getSystemMessageColor(node.category);
|
|
6379
|
-
const RESET3 = "\x1B[0m";
|
|
6380
|
-
return `${indent}${color}${icon} ${node.message}${RESET3}`;
|
|
6381
|
-
}
|
|
6382
|
-
}
|
|
6383
|
-
}
|
|
6384
|
-
/**
|
|
6385
|
-
* Get icon for system message category.
|
|
6386
|
-
*/
|
|
6387
|
-
getSystemMessageIcon(category) {
|
|
6388
|
-
switch (category) {
|
|
6389
|
-
case "throttle":
|
|
6390
|
-
return "\u23F8";
|
|
6391
|
-
case "retry":
|
|
6392
|
-
return "\u{1F504}";
|
|
6393
|
-
case "info":
|
|
6394
|
-
return "\u2139\uFE0F";
|
|
6395
|
-
case "warning":
|
|
6396
|
-
return "\u26A0\uFE0F";
|
|
6397
|
-
case "error":
|
|
6398
|
-
return "\u274C";
|
|
6399
|
-
}
|
|
6400
|
-
}
|
|
6401
|
-
/**
|
|
6402
|
-
* Get ANSI color code for system message category.
|
|
6403
|
-
*/
|
|
6404
|
-
getSystemMessageColor(category) {
|
|
6405
|
-
const YELLOW2 = "\x1B[33m";
|
|
6406
|
-
const BLUE = "\x1B[34m";
|
|
6407
|
-
const GRAY2 = "\x1B[90m";
|
|
6408
|
-
const RED2 = "\x1B[31m";
|
|
6409
|
-
switch (category) {
|
|
6410
|
-
case "throttle":
|
|
6411
|
-
return YELLOW2;
|
|
6412
|
-
case "retry":
|
|
6413
|
-
return BLUE;
|
|
6414
|
-
case "info":
|
|
6415
|
-
return GRAY2;
|
|
6416
|
-
case "warning":
|
|
6417
|
-
return YELLOW2;
|
|
6418
|
-
case "error":
|
|
6419
|
-
return RED2;
|
|
6420
|
-
}
|
|
6421
|
-
}
|
|
6422
|
-
/**
|
|
6423
|
-
* Abbreviate text content to a maximum number of lines.
|
|
6424
|
-
* Shows truncation indicator if content exceeds limit.
|
|
6425
|
-
*
|
|
6426
|
-
* @param text - The text to abbreviate
|
|
6427
|
-
* @param maxLines - Maximum number of lines to show
|
|
6428
|
-
* @param selected - Whether this block is selected (for indicator styling)
|
|
6429
|
-
* @returns Abbreviated text with truncation indicator if needed
|
|
6430
|
-
*/
|
|
6431
|
-
abbreviateToLines(text3, maxLines, selected) {
|
|
6432
|
-
const lines = text3.split("\n");
|
|
6433
|
-
let startIndex = 0;
|
|
6434
|
-
while (startIndex < lines.length && lines[startIndex].trim() === "") {
|
|
6435
|
-
startIndex++;
|
|
6436
|
-
}
|
|
6437
|
-
const contentLines = lines.slice(startIndex);
|
|
6438
|
-
if (contentLines.length <= maxLines) {
|
|
6439
|
-
return `
|
|
6440
|
-
${contentLines.join("\n")}`;
|
|
6441
|
-
}
|
|
6442
|
-
const truncatedLines = contentLines.slice(0, maxLines);
|
|
6443
|
-
const indicator = selected ? "\u25B6 ..." : " ...";
|
|
6444
|
-
return `
|
|
6445
|
-
${truncatedLines.join("\n")}
|
|
6446
|
-
${indicator}`;
|
|
6447
|
-
}
|
|
6448
6854
|
/**
|
|
6449
6855
|
* Update a single block (after state change).
|
|
6450
6856
|
*/
|
|
@@ -6453,7 +6859,7 @@ ${indicator}`;
|
|
|
6453
6859
|
const node = this.getNode(nodeId);
|
|
6454
6860
|
if (!block || !node) return;
|
|
6455
6861
|
const isSelected = this.selectableIds[this.selectedIndex] === nodeId;
|
|
6456
|
-
const content =
|
|
6862
|
+
const content = formatBlockContent(node, isSelected, block.expanded);
|
|
6457
6863
|
const oldHeight = getBlockHeight(block);
|
|
6458
6864
|
block.box.setContent(content);
|
|
6459
6865
|
const newHeight = content.split("\n").length;
|
|
@@ -6471,7 +6877,7 @@ ${indicator}`;
|
|
|
6471
6877
|
const block = this.blocks.get(id);
|
|
6472
6878
|
if (block) {
|
|
6473
6879
|
const isSelected = this.selectableIds[this.selectedIndex] === id;
|
|
6474
|
-
const content =
|
|
6880
|
+
const content = formatBlockContent(block.node, isSelected, block.expanded);
|
|
6475
6881
|
block.box.setContent(content);
|
|
6476
6882
|
}
|
|
6477
6883
|
}
|
|
@@ -6486,20 +6892,19 @@ ${indicator}`;
|
|
|
6486
6892
|
this.scrollManager.repositionBlocks((rootId, top) => this.repositionNodeTree(rootId, top));
|
|
6487
6893
|
}
|
|
6488
6894
|
repositionNodeTree(nodeId, top) {
|
|
6489
|
-
|
|
6490
|
-
|
|
6491
|
-
|
|
6492
|
-
|
|
6493
|
-
|
|
6494
|
-
|
|
6495
|
-
|
|
6496
|
-
|
|
6497
|
-
|
|
6498
|
-
|
|
6499
|
-
|
|
6500
|
-
|
|
6501
|
-
|
|
6502
|
-
return top;
|
|
6895
|
+
return traverseNodeTree(
|
|
6896
|
+
nodeId,
|
|
6897
|
+
(id) => this.getNode(id),
|
|
6898
|
+
(currentNodeId, _node, currentTop) => {
|
|
6899
|
+
const block = this.blocks.get(currentNodeId);
|
|
6900
|
+
if (!block) {
|
|
6901
|
+
return currentTop;
|
|
6902
|
+
}
|
|
6903
|
+
block.box.top = currentTop;
|
|
6904
|
+
return currentTop + getBlockHeight(block);
|
|
6905
|
+
},
|
|
6906
|
+
top
|
|
6907
|
+
);
|
|
6503
6908
|
}
|
|
6504
6909
|
/**
|
|
6505
6910
|
* Scroll container to keep selected block visible.
|
|
@@ -6566,13 +6971,25 @@ ${indicator}`;
|
|
|
6566
6971
|
* Used for mode switches where we need to ensure the screen is fully cleared.
|
|
6567
6972
|
*/
|
|
6568
6973
|
rebuildBlocksImmediate() {
|
|
6974
|
+
this.rebuildBlocksCore(true);
|
|
6975
|
+
}
|
|
6976
|
+
/**
|
|
6977
|
+
* Shared implementation for rebuildBlocks and rebuildBlocksImmediate.
|
|
6978
|
+
*
|
|
6979
|
+
* @param immediate - When true, forces a synchronous render pass before
|
|
6980
|
+
* and after building blocks to clear visual artifacts (used on mode switch).
|
|
6981
|
+
* When false, defers a single render to the end via renderCallback.
|
|
6982
|
+
*/
|
|
6983
|
+
rebuildBlocksCore(immediate) {
|
|
6569
6984
|
for (const child of [...this.container.children]) {
|
|
6570
6985
|
child.detach();
|
|
6571
6986
|
}
|
|
6572
6987
|
this.container.setContent("");
|
|
6573
6988
|
this.blocks.clear();
|
|
6574
6989
|
this.selectableIds = [];
|
|
6575
|
-
|
|
6990
|
+
if (immediate) {
|
|
6991
|
+
this.renderNowCallback();
|
|
6992
|
+
}
|
|
6576
6993
|
let top = 0;
|
|
6577
6994
|
for (const rootId of this.nodeStore.rootIds) {
|
|
6578
6995
|
top = this.renderNodeTree(rootId, top);
|
|
@@ -6581,7 +6998,11 @@ ${indicator}`;
|
|
|
6581
6998
|
this.selectedIndex = this.selectableIds.length - 1;
|
|
6582
6999
|
}
|
|
6583
7000
|
this.applyBottomAlignmentAndScroll();
|
|
6584
|
-
|
|
7001
|
+
if (immediate) {
|
|
7002
|
+
this.renderNowCallback();
|
|
7003
|
+
} else {
|
|
7004
|
+
this.renderCallback();
|
|
7005
|
+
}
|
|
6585
7006
|
this.notifyHasContentChange();
|
|
6586
7007
|
}
|
|
6587
7008
|
/**
|
|
@@ -6593,38 +7014,15 @@ ${indicator}`;
|
|
|
6593
7014
|
/**
|
|
6594
7015
|
* Check if a node should be visible in the current content filter mode.
|
|
6595
7016
|
*
|
|
6596
|
-
* In focused mode
|
|
6597
|
-
*
|
|
6598
|
-
* - LLM call blocks are hidden
|
|
6599
|
-
* - Gadget blocks are hidden EXCEPT TellUser and AskUser
|
|
6600
|
-
* (Finish is hidden - status bar indicates completion)
|
|
7017
|
+
* In focused mode, only text and user-facing gadgets remain visible.
|
|
7018
|
+
* TellUser, AskUser, and Finish render as plain text for a chat-like view.
|
|
6601
7019
|
*/
|
|
6602
7020
|
isNodeVisible(node) {
|
|
6603
|
-
|
|
6604
|
-
return true;
|
|
6605
|
-
}
|
|
6606
|
-
switch (node.type) {
|
|
6607
|
-
case "text":
|
|
6608
|
-
return true;
|
|
6609
|
-
case "llm_call":
|
|
6610
|
-
return false;
|
|
6611
|
-
case "thinking":
|
|
6612
|
-
return false;
|
|
6613
|
-
case "gadget": {
|
|
6614
|
-
const name = node.name;
|
|
6615
|
-
return name === "TellUser" || name === "AskUser" || name === "Finish";
|
|
6616
|
-
}
|
|
6617
|
-
default:
|
|
6618
|
-
return false;
|
|
6619
|
-
}
|
|
7021
|
+
return isNodeVisibleInFilterMode(node, this.contentFilterMode);
|
|
6620
7022
|
}
|
|
6621
7023
|
// ───────────────────────────────────────────────────────────────────────────
|
|
6622
7024
|
// ExecutionTree Integration
|
|
6623
7025
|
// ───────────────────────────────────────────────────────────────────────────
|
|
6624
|
-
/** Unsubscribe function for tree events */
|
|
6625
|
-
treeUnsubscribe = null;
|
|
6626
|
-
/** Map tree node IDs to block node IDs */
|
|
6627
|
-
treeNodeToBlockId = /* @__PURE__ */ new Map();
|
|
6628
7026
|
/**
|
|
6629
7027
|
* Subscribe to an ExecutionTree for automatic block updates.
|
|
6630
7028
|
*
|
|
@@ -6648,121 +7046,14 @@ ${indicator}`;
|
|
|
6648
7046
|
* ```
|
|
6649
7047
|
*/
|
|
6650
7048
|
subscribeToTree(tree) {
|
|
6651
|
-
|
|
6652
|
-
this.treeUnsubscribe();
|
|
6653
|
-
}
|
|
6654
|
-
this.treeNodeToBlockId.clear();
|
|
6655
|
-
this.nodeStore.clearIdempotencyMaps();
|
|
6656
|
-
this.treeUnsubscribe = tree.onAll((event) => {
|
|
6657
|
-
this.handleTreeEvent(event, tree);
|
|
6658
|
-
});
|
|
6659
|
-
return () => {
|
|
6660
|
-
if (this.treeUnsubscribe) {
|
|
6661
|
-
this.treeUnsubscribe();
|
|
6662
|
-
this.treeUnsubscribe = null;
|
|
6663
|
-
}
|
|
6664
|
-
};
|
|
6665
|
-
}
|
|
6666
|
-
/**
|
|
6667
|
-
* Handle an ExecutionTree event.
|
|
6668
|
-
*/
|
|
6669
|
-
handleTreeEvent(event, tree) {
|
|
6670
|
-
switch (event.type) {
|
|
6671
|
-
case "llm_call_start": {
|
|
6672
|
-
this.nodeStore.resetCurrentThinking();
|
|
6673
|
-
let parentBlockId;
|
|
6674
|
-
if (event.parentId) {
|
|
6675
|
-
parentBlockId = this.treeNodeToBlockId.get(event.parentId);
|
|
6676
|
-
}
|
|
6677
|
-
const blockId = this.addLLMCall(
|
|
6678
|
-
event.iteration + 1,
|
|
6679
|
-
event.model,
|
|
6680
|
-
parentBlockId,
|
|
6681
|
-
event.depth > 0
|
|
6682
|
-
);
|
|
6683
|
-
this.treeNodeToBlockId.set(event.nodeId, blockId);
|
|
6684
|
-
const startNode = tree.getNode(event.nodeId);
|
|
6685
|
-
if (startNode?.type === "llm_call" && startNode.request) {
|
|
6686
|
-
this.setLLMCallRequest(blockId, startNode.request);
|
|
6687
|
-
}
|
|
6688
|
-
break;
|
|
6689
|
-
}
|
|
6690
|
-
case "llm_call_complete": {
|
|
6691
|
-
this.completeThinking();
|
|
6692
|
-
const blockId = this.treeNodeToBlockId.get(event.nodeId);
|
|
6693
|
-
if (blockId) {
|
|
6694
|
-
this.completeLLMCall(blockId, {
|
|
6695
|
-
inputTokens: event.usage?.inputTokens,
|
|
6696
|
-
cachedInputTokens: event.usage?.cachedInputTokens,
|
|
6697
|
-
outputTokens: event.usage?.outputTokens,
|
|
6698
|
-
reasoningTokens: event.usage?.reasoningTokens,
|
|
6699
|
-
cost: event.cost,
|
|
6700
|
-
finishReason: event.finishReason ?? void 0
|
|
6701
|
-
});
|
|
6702
|
-
const completeNode = tree.getNode(event.nodeId);
|
|
6703
|
-
if (completeNode?.type === "llm_call" && completeNode.response) {
|
|
6704
|
-
this.setLLMCallResponse(blockId, completeNode.response);
|
|
6705
|
-
}
|
|
6706
|
-
}
|
|
6707
|
-
break;
|
|
6708
|
-
}
|
|
6709
|
-
case "thinking": {
|
|
6710
|
-
this.addThinking(event.content, event.thinkingType);
|
|
6711
|
-
break;
|
|
6712
|
-
}
|
|
6713
|
-
case "gadget_call": {
|
|
6714
|
-
let parentBlockId;
|
|
6715
|
-
if (event.parentId) {
|
|
6716
|
-
parentBlockId = this.treeNodeToBlockId.get(event.parentId);
|
|
6717
|
-
}
|
|
6718
|
-
const previousLLMCallId = this.getCurrentLLMCallId();
|
|
6719
|
-
if (parentBlockId) {
|
|
6720
|
-
this.setCurrentLLMCall(parentBlockId);
|
|
6721
|
-
}
|
|
6722
|
-
const blockId = this.addGadget(event.invocationId, event.name, event.parameters);
|
|
6723
|
-
this.treeNodeToBlockId.set(event.nodeId, blockId);
|
|
6724
|
-
this.setCurrentLLMCall(previousLLMCallId);
|
|
6725
|
-
break;
|
|
6726
|
-
}
|
|
6727
|
-
case "gadget_complete": {
|
|
6728
|
-
const mediaOutputs = event.storedMedia?.map((m) => ({
|
|
6729
|
-
kind: m.kind,
|
|
6730
|
-
path: m.path,
|
|
6731
|
-
mimeType: m.mimeType,
|
|
6732
|
-
description: m.description
|
|
6733
|
-
}));
|
|
6734
|
-
this.completeGadget(event.invocationId, {
|
|
6735
|
-
result: event.result,
|
|
6736
|
-
executionTimeMs: event.executionTimeMs,
|
|
6737
|
-
cost: event.cost,
|
|
6738
|
-
mediaOutputs
|
|
6739
|
-
});
|
|
6740
|
-
break;
|
|
6741
|
-
}
|
|
6742
|
-
case "gadget_error": {
|
|
6743
|
-
this.completeGadget(event.invocationId, {
|
|
6744
|
-
error: event.error,
|
|
6745
|
-
executionTimeMs: event.executionTimeMs
|
|
6746
|
-
});
|
|
6747
|
-
break;
|
|
6748
|
-
}
|
|
6749
|
-
case "gadget_skipped": {
|
|
6750
|
-
const node = this.findGadgetByInvocationId(event.invocationId);
|
|
6751
|
-
if (node) {
|
|
6752
|
-
node.isComplete = true;
|
|
6753
|
-
node.error = `Skipped: ${event.failedDependencyError}`;
|
|
6754
|
-
this.updateBlock(node.id);
|
|
6755
|
-
}
|
|
6756
|
-
break;
|
|
6757
|
-
}
|
|
6758
|
-
}
|
|
7049
|
+
return this.treeBridge.subscribeToTree(tree);
|
|
6759
7050
|
}
|
|
6760
7051
|
/**
|
|
6761
7052
|
* Get block ID for a tree node ID.
|
|
6762
7053
|
* Useful for external code that needs to correlate tree nodes with blocks.
|
|
6763
7054
|
*/
|
|
6764
7055
|
getBlockIdForTreeNode(treeNodeId) {
|
|
6765
|
-
return this.
|
|
7056
|
+
return this.treeBridge.getBlockIdForTreeNode(treeNodeId);
|
|
6766
7057
|
}
|
|
6767
7058
|
};
|
|
6768
7059
|
|
|
@@ -7047,11 +7338,31 @@ var TUIController = class {
|
|
|
7047
7338
|
this.callbacks.onCancel?.();
|
|
7048
7339
|
}
|
|
7049
7340
|
/**
|
|
7050
|
-
* Trigger the mid-session input callback.
|
|
7051
|
-
* Called when user submits input during a running session.
|
|
7341
|
+
* Trigger the mid-session input callback.
|
|
7342
|
+
* Called when user submits input during a running session.
|
|
7343
|
+
*/
|
|
7344
|
+
triggerMidSessionInput(message) {
|
|
7345
|
+
this.callbacks.onMidSessionInput?.(message);
|
|
7346
|
+
}
|
|
7347
|
+
};
|
|
7348
|
+
|
|
7349
|
+
// src/tui/event-router.ts
|
|
7350
|
+
var EventRouter = class {
|
|
7351
|
+
constructor(blockRenderer) {
|
|
7352
|
+
this.blockRenderer = blockRenderer;
|
|
7353
|
+
}
|
|
7354
|
+
/**
|
|
7355
|
+
* Route an agent stream event to the appropriate renderer method.
|
|
7356
|
+
*
|
|
7357
|
+
* Only handles text/thinking events - gadgets and LLM calls are managed
|
|
7358
|
+
* via ExecutionTree subscription in TreeSubscriptionManager.
|
|
7052
7359
|
*/
|
|
7053
|
-
|
|
7054
|
-
|
|
7360
|
+
handleEvent(event) {
|
|
7361
|
+
if (event.type === "text") {
|
|
7362
|
+
this.blockRenderer.addText(event.content);
|
|
7363
|
+
} else if (event.type === "thinking") {
|
|
7364
|
+
this.blockRenderer.addThinking(event.content, event.thinkingType);
|
|
7365
|
+
}
|
|
7055
7366
|
}
|
|
7056
7367
|
};
|
|
7057
7368
|
|
|
@@ -7657,6 +7968,104 @@ var InputHandler = class {
|
|
|
7657
7968
|
}
|
|
7658
7969
|
};
|
|
7659
7970
|
|
|
7971
|
+
// src/tui/key-action-handler.ts
|
|
7972
|
+
var KeyActionHandler = class {
|
|
7973
|
+
constructor(controller, blockRenderer, statusBar, screenCtx, modalManager, layout) {
|
|
7974
|
+
this.controller = controller;
|
|
7975
|
+
this.blockRenderer = blockRenderer;
|
|
7976
|
+
this.statusBar = statusBar;
|
|
7977
|
+
this.screenCtx = screenCtx;
|
|
7978
|
+
this.modalManager = modalManager;
|
|
7979
|
+
this.layout = layout;
|
|
7980
|
+
}
|
|
7981
|
+
/**
|
|
7982
|
+
* Handle high-level keyboard actions from KeyboardManager or InputHandler.
|
|
7983
|
+
*/
|
|
7984
|
+
handleKeyAction(action) {
|
|
7985
|
+
switch (action.type) {
|
|
7986
|
+
case "ctrl_c": {
|
|
7987
|
+
const result = this.controller.handleCtrlC();
|
|
7988
|
+
if (result === "show_hint") {
|
|
7989
|
+
this.blockRenderer.addText("\n[Press Ctrl+C again to quit]\n");
|
|
7990
|
+
} else if (result === "quit") {
|
|
7991
|
+
process.exit(130);
|
|
7992
|
+
}
|
|
7993
|
+
break;
|
|
7994
|
+
}
|
|
7995
|
+
case "cancel":
|
|
7996
|
+
this.controller.triggerCancel();
|
|
7997
|
+
this.controller.abort();
|
|
7998
|
+
break;
|
|
7999
|
+
case "toggle_focus_mode":
|
|
8000
|
+
this.controller.toggleFocusMode();
|
|
8001
|
+
break;
|
|
8002
|
+
case "toggle_content_filter":
|
|
8003
|
+
this.controller.toggleContentFilterMode();
|
|
8004
|
+
break;
|
|
8005
|
+
case "cycle_profile":
|
|
8006
|
+
this.statusBar.cycleProfile();
|
|
8007
|
+
break;
|
|
8008
|
+
case "scroll_page": {
|
|
8009
|
+
const body = this.layout.body;
|
|
8010
|
+
if (!body.scroll) return;
|
|
8011
|
+
const containerHeight = body.height;
|
|
8012
|
+
const scrollAmount = Math.max(1, containerHeight - 2);
|
|
8013
|
+
if (action.direction < 0) {
|
|
8014
|
+
body.scroll(-scrollAmount);
|
|
8015
|
+
} else {
|
|
8016
|
+
body.scroll(scrollAmount);
|
|
8017
|
+
}
|
|
8018
|
+
this.blockRenderer.handleUserScroll();
|
|
8019
|
+
this.screenCtx.renderNow();
|
|
8020
|
+
break;
|
|
8021
|
+
}
|
|
8022
|
+
case "scroll_line": {
|
|
8023
|
+
const body = this.layout.body;
|
|
8024
|
+
if (!body.scroll) return;
|
|
8025
|
+
body.scroll(action.direction);
|
|
8026
|
+
this.blockRenderer.handleUserScroll();
|
|
8027
|
+
this.screenCtx.renderNow();
|
|
8028
|
+
break;
|
|
8029
|
+
}
|
|
8030
|
+
case "navigation":
|
|
8031
|
+
switch (action.action) {
|
|
8032
|
+
case "select_next":
|
|
8033
|
+
this.blockRenderer.selectNext();
|
|
8034
|
+
break;
|
|
8035
|
+
case "select_previous":
|
|
8036
|
+
this.blockRenderer.selectPrevious();
|
|
8037
|
+
break;
|
|
8038
|
+
case "select_first":
|
|
8039
|
+
this.blockRenderer.selectFirst();
|
|
8040
|
+
break;
|
|
8041
|
+
case "select_last":
|
|
8042
|
+
this.blockRenderer.selectLast();
|
|
8043
|
+
this.blockRenderer.enableFollowMode();
|
|
8044
|
+
break;
|
|
8045
|
+
case "toggle_expand":
|
|
8046
|
+
this.blockRenderer.toggleExpand();
|
|
8047
|
+
break;
|
|
8048
|
+
case "collapse":
|
|
8049
|
+
this.blockRenderer.collapseOrDeselect();
|
|
8050
|
+
break;
|
|
8051
|
+
}
|
|
8052
|
+
this.screenCtx.renderNow();
|
|
8053
|
+
break;
|
|
8054
|
+
case "raw_viewer":
|
|
8055
|
+
void (async () => {
|
|
8056
|
+
const selected = this.blockRenderer.getSelectedBlock();
|
|
8057
|
+
if (!selected) return;
|
|
8058
|
+
if (!isRawViewerNode(selected.node)) return;
|
|
8059
|
+
await this.modalManager.showRawViewer(
|
|
8060
|
+
this.screenCtx.screen,
|
|
8061
|
+
createRawViewerData(selected.node, action.mode)
|
|
8062
|
+
);
|
|
8063
|
+
})();
|
|
8064
|
+
break;
|
|
8065
|
+
}
|
|
8066
|
+
}
|
|
8067
|
+
};
|
|
8068
|
+
|
|
7660
8069
|
// src/tui/keymap.ts
|
|
7661
8070
|
var KeyboardManager = class {
|
|
7662
8071
|
config;
|
|
@@ -8354,6 +8763,47 @@ function createScreen(options) {
|
|
|
8354
8763
|
};
|
|
8355
8764
|
}
|
|
8356
8765
|
|
|
8766
|
+
// src/tui/session-manager.ts
|
|
8767
|
+
var SessionManager = class {
|
|
8768
|
+
constructor(blockRenderer, statusBar) {
|
|
8769
|
+
this.blockRenderer = blockRenderer;
|
|
8770
|
+
this.statusBar = statusBar;
|
|
8771
|
+
}
|
|
8772
|
+
/**
|
|
8773
|
+
* Start a new session. Called at the start of each REPL turn.
|
|
8774
|
+
*/
|
|
8775
|
+
startNewSession() {
|
|
8776
|
+
this.blockRenderer.startNewSession();
|
|
8777
|
+
}
|
|
8778
|
+
/**
|
|
8779
|
+
* Clear blocks from the previous session only.
|
|
8780
|
+
* Keeps current content visible during the session.
|
|
8781
|
+
*/
|
|
8782
|
+
clearPreviousSession() {
|
|
8783
|
+
this.blockRenderer.clearPreviousSession();
|
|
8784
|
+
}
|
|
8785
|
+
/**
|
|
8786
|
+
* Clear all blocks and reset BlockRenderer state.
|
|
8787
|
+
* Prevents memory leaks between iterations.
|
|
8788
|
+
*/
|
|
8789
|
+
clearAllBlocks() {
|
|
8790
|
+
this.blockRenderer.clear();
|
|
8791
|
+
}
|
|
8792
|
+
/**
|
|
8793
|
+
* Clear status bar activity state.
|
|
8794
|
+
*/
|
|
8795
|
+
clearStatusBar() {
|
|
8796
|
+
this.statusBar.clearActivity();
|
|
8797
|
+
}
|
|
8798
|
+
/**
|
|
8799
|
+
* Full cleanup between REPL iterations.
|
|
8800
|
+
*/
|
|
8801
|
+
resetAll() {
|
|
8802
|
+
this.clearAllBlocks();
|
|
8803
|
+
this.clearStatusBar();
|
|
8804
|
+
}
|
|
8805
|
+
};
|
|
8806
|
+
|
|
8357
8807
|
// src/tui/status-bar.ts
|
|
8358
8808
|
var CHARS_PER_TOKEN = 4;
|
|
8359
8809
|
var SPINNER_FRAMES2 = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
@@ -8880,6 +9330,195 @@ var StatusBar = class {
|
|
|
8880
9330
|
}
|
|
8881
9331
|
};
|
|
8882
9332
|
|
|
9333
|
+
// src/tui/tree-subscription-manager.ts
|
|
9334
|
+
var TreeSubscriptionManager = class {
|
|
9335
|
+
constructor(blockRenderer, statusBar) {
|
|
9336
|
+
this.blockRenderer = blockRenderer;
|
|
9337
|
+
this.statusBar = statusBar;
|
|
9338
|
+
}
|
|
9339
|
+
treeUnsubscribe = null;
|
|
9340
|
+
/**
|
|
9341
|
+
* Subscribe to an ExecutionTree for automatic updates.
|
|
9342
|
+
* Handles previous unsubscription and returns a combined unsubscribe function.
|
|
9343
|
+
*/
|
|
9344
|
+
subscribe(tree) {
|
|
9345
|
+
this.unsubscribe();
|
|
9346
|
+
const unsubBlock = this.blockRenderer.subscribeToTree(tree);
|
|
9347
|
+
const unsubStatus = this.statusBar.subscribeToTree(tree);
|
|
9348
|
+
this.treeUnsubscribe = () => {
|
|
9349
|
+
unsubBlock();
|
|
9350
|
+
unsubStatus();
|
|
9351
|
+
};
|
|
9352
|
+
return () => this.unsubscribe();
|
|
9353
|
+
}
|
|
9354
|
+
/**
|
|
9355
|
+
* Unsubscribe from the current tree if any.
|
|
9356
|
+
*/
|
|
9357
|
+
unsubscribe() {
|
|
9358
|
+
if (this.treeUnsubscribe) {
|
|
9359
|
+
this.treeUnsubscribe();
|
|
9360
|
+
this.treeUnsubscribe = null;
|
|
9361
|
+
}
|
|
9362
|
+
}
|
|
9363
|
+
};
|
|
9364
|
+
|
|
9365
|
+
// src/tui/tui-app-effects.ts
|
|
9366
|
+
function applyFocusMode(mode, layout, statusBar, inputHandler, screenCtx) {
|
|
9367
|
+
statusBar.setFocusMode(mode);
|
|
9368
|
+
if (mode === "input") {
|
|
9369
|
+
inputHandler.activate();
|
|
9370
|
+
} else {
|
|
9371
|
+
inputHandler.deactivate();
|
|
9372
|
+
layout.body.focus();
|
|
9373
|
+
}
|
|
9374
|
+
screenCtx.renderNow();
|
|
9375
|
+
}
|
|
9376
|
+
function applyContentFilterMode(mode, blockRenderer, statusBar, screenCtx) {
|
|
9377
|
+
blockRenderer.setContentFilterMode(mode);
|
|
9378
|
+
statusBar.setContentFilterMode(mode);
|
|
9379
|
+
screenCtx.renderNow();
|
|
9380
|
+
}
|
|
9381
|
+
|
|
9382
|
+
// src/tui/tui-app-bootstrap.ts
|
|
9383
|
+
function createTUIAppDependencies(options) {
|
|
9384
|
+
const screenCtx = createScreen({
|
|
9385
|
+
stdin: options.stdin,
|
|
9386
|
+
stdout: options.stdout,
|
|
9387
|
+
title: "llmist"
|
|
9388
|
+
});
|
|
9389
|
+
const { screen } = screenCtx;
|
|
9390
|
+
const showHints = options.showHints ?? true;
|
|
9391
|
+
const layout = createBlockLayout(screen, showHints);
|
|
9392
|
+
const hintsBar = createHintsBar(layout, screenCtx, showHints);
|
|
9393
|
+
const statusBar = createStatusBar(layout, options.model, screenCtx);
|
|
9394
|
+
const inputHandler = createInputHandler(layout, screenCtx, screen, showHints);
|
|
9395
|
+
const blockRenderer = createBlockRenderer(layout, screenCtx);
|
|
9396
|
+
wireHintsBar(blockRenderer, hintsBar);
|
|
9397
|
+
const controller = createController(
|
|
9398
|
+
layout,
|
|
9399
|
+
statusBar,
|
|
9400
|
+
inputHandler,
|
|
9401
|
+
screenCtx,
|
|
9402
|
+
blockRenderer,
|
|
9403
|
+
hintsBar
|
|
9404
|
+
);
|
|
9405
|
+
const modalManager = new ModalManager();
|
|
9406
|
+
const keyActionHandler = new KeyActionHandler(
|
|
9407
|
+
controller,
|
|
9408
|
+
blockRenderer,
|
|
9409
|
+
statusBar,
|
|
9410
|
+
screenCtx,
|
|
9411
|
+
modalManager,
|
|
9412
|
+
layout
|
|
9413
|
+
);
|
|
9414
|
+
const keyboardManager = new KeyboardManager({
|
|
9415
|
+
screen,
|
|
9416
|
+
getFocusMode: () => controller.getFocusMode(),
|
|
9417
|
+
getContentFilterMode: () => controller.getContentFilterMode(),
|
|
9418
|
+
isWaitingForREPLPrompt: () => inputHandler.isWaitingForREPLPrompt(),
|
|
9419
|
+
hasPendingInput: () => inputHandler.hasPendingInput(),
|
|
9420
|
+
isBlockExpanded: () => blockRenderer.getSelectedBlock()?.expanded ?? false,
|
|
9421
|
+
onAction: (action) => {
|
|
9422
|
+
keyActionHandler.handleKeyAction(action);
|
|
9423
|
+
}
|
|
9424
|
+
});
|
|
9425
|
+
const subscriptionManager = new TreeSubscriptionManager(blockRenderer, statusBar);
|
|
9426
|
+
const sessionManager = new SessionManager(blockRenderer, statusBar);
|
|
9427
|
+
const eventRouter = new EventRouter(blockRenderer);
|
|
9428
|
+
wireInputHandlers(inputHandler, keyboardManager, keyActionHandler, controller);
|
|
9429
|
+
wireScreenEvents(layout, screen, blockRenderer);
|
|
9430
|
+
keyboardManager.setup();
|
|
9431
|
+
applyFocusMode(controller.getFocusMode(), layout, statusBar, inputHandler, screenCtx);
|
|
9432
|
+
screenCtx.requestRender();
|
|
9433
|
+
return {
|
|
9434
|
+
screenCtx,
|
|
9435
|
+
statusBar,
|
|
9436
|
+
inputHandler,
|
|
9437
|
+
blockRenderer,
|
|
9438
|
+
controller,
|
|
9439
|
+
modalManager,
|
|
9440
|
+
subscriptionManager,
|
|
9441
|
+
sessionManager,
|
|
9442
|
+
eventRouter
|
|
9443
|
+
};
|
|
9444
|
+
}
|
|
9445
|
+
function createHintsBar(layout, screenCtx, showHints) {
|
|
9446
|
+
if (!showHints || !layout.hintsBar) {
|
|
9447
|
+
return null;
|
|
9448
|
+
}
|
|
9449
|
+
return new HintsBar(layout.hintsBar, () => screenCtx.requestRender());
|
|
9450
|
+
}
|
|
9451
|
+
function createStatusBar(layout, model, screenCtx) {
|
|
9452
|
+
return new StatusBar(
|
|
9453
|
+
layout.statusBar,
|
|
9454
|
+
model,
|
|
9455
|
+
() => screenCtx.requestRender(),
|
|
9456
|
+
() => screenCtx.renderNow()
|
|
9457
|
+
);
|
|
9458
|
+
}
|
|
9459
|
+
function createInputHandler(layout, screenCtx, screen, showHints) {
|
|
9460
|
+
return new InputHandler(
|
|
9461
|
+
layout.inputBar,
|
|
9462
|
+
layout.promptLabel,
|
|
9463
|
+
layout.body,
|
|
9464
|
+
screen,
|
|
9465
|
+
() => screenCtx.requestRender(),
|
|
9466
|
+
() => screenCtx.renderNow(),
|
|
9467
|
+
showHints
|
|
9468
|
+
);
|
|
9469
|
+
}
|
|
9470
|
+
function createBlockRenderer(layout, screenCtx) {
|
|
9471
|
+
return new BlockRenderer(
|
|
9472
|
+
layout.body,
|
|
9473
|
+
() => screenCtx.requestRender(),
|
|
9474
|
+
() => screenCtx.renderNow()
|
|
9475
|
+
);
|
|
9476
|
+
}
|
|
9477
|
+
function wireHintsBar(blockRenderer, hintsBar) {
|
|
9478
|
+
if (!hintsBar) {
|
|
9479
|
+
return;
|
|
9480
|
+
}
|
|
9481
|
+
blockRenderer.onHasContentChange((hasContent) => {
|
|
9482
|
+
hintsBar.setHasContent(hasContent);
|
|
9483
|
+
});
|
|
9484
|
+
}
|
|
9485
|
+
function createController(layout, statusBar, inputHandler, screenCtx, blockRenderer, hintsBar) {
|
|
9486
|
+
return new TUIController({
|
|
9487
|
+
onFocusModeChange: (mode) => {
|
|
9488
|
+
applyFocusMode(mode, layout, statusBar, inputHandler, screenCtx);
|
|
9489
|
+
hintsBar?.setFocusMode(mode);
|
|
9490
|
+
},
|
|
9491
|
+
onContentFilterModeChange: (mode) => {
|
|
9492
|
+
applyContentFilterMode(mode, blockRenderer, statusBar, screenCtx);
|
|
9493
|
+
hintsBar?.setContentFilterMode(mode);
|
|
9494
|
+
}
|
|
9495
|
+
});
|
|
9496
|
+
}
|
|
9497
|
+
function wireInputHandlers(inputHandler, keyboardManager, keyActionHandler, controller) {
|
|
9498
|
+
inputHandler.onCtrlC(() => keyboardManager.handleForwardedKey("C-c"));
|
|
9499
|
+
inputHandler.onCtrlB(() => keyboardManager.handleForwardedKey("C-b"));
|
|
9500
|
+
inputHandler.onCtrlK(() => keyboardManager.handleForwardedKey("C-k"));
|
|
9501
|
+
inputHandler.onCtrlI(() => keyboardManager.handleForwardedKey("C-i"));
|
|
9502
|
+
inputHandler.onCtrlJ(() => keyboardManager.handleForwardedKey("C-j"));
|
|
9503
|
+
inputHandler.onCtrlP(() => keyboardManager.handleForwardedKey("C-p"));
|
|
9504
|
+
inputHandler.onArrowUp(() => {
|
|
9505
|
+
keyActionHandler.handleKeyAction({ type: "scroll_line", direction: -1 });
|
|
9506
|
+
});
|
|
9507
|
+
inputHandler.onArrowDown(() => {
|
|
9508
|
+
keyActionHandler.handleKeyAction({ type: "scroll_line", direction: 1 });
|
|
9509
|
+
});
|
|
9510
|
+
inputHandler.setGetFocusMode(() => controller.getFocusMode());
|
|
9511
|
+
inputHandler.setGetContentFilterMode(() => controller.getContentFilterMode());
|
|
9512
|
+
}
|
|
9513
|
+
function wireScreenEvents(layout, screen, blockRenderer) {
|
|
9514
|
+
layout.body.on("scroll", () => {
|
|
9515
|
+
blockRenderer.handleUserScroll();
|
|
9516
|
+
});
|
|
9517
|
+
screen.on("resize", () => {
|
|
9518
|
+
blockRenderer.handleResize();
|
|
9519
|
+
});
|
|
9520
|
+
}
|
|
9521
|
+
|
|
8883
9522
|
// src/tui/index.ts
|
|
8884
9523
|
var TUIApp = class _TUIApp {
|
|
8885
9524
|
screenCtx;
|
|
@@ -8889,135 +9528,36 @@ var TUIApp = class _TUIApp {
|
|
|
8889
9528
|
// New extracted components
|
|
8890
9529
|
controller;
|
|
8891
9530
|
modalManager;
|
|
8892
|
-
|
|
8893
|
-
|
|
8894
|
-
|
|
9531
|
+
subscriptionManager;
|
|
9532
|
+
sessionManager;
|
|
9533
|
+
eventRouter;
|
|
9534
|
+
constructor(screenCtx, statusBar, inputHandler, blockRenderer, controller, modalManager, subscriptionManager, sessionManager, eventRouter) {
|
|
8895
9535
|
this.screenCtx = screenCtx;
|
|
8896
9536
|
this.statusBar = statusBar;
|
|
8897
9537
|
this.inputHandler = inputHandler;
|
|
8898
9538
|
this.blockRenderer = blockRenderer;
|
|
8899
9539
|
this.controller = controller;
|
|
8900
9540
|
this.modalManager = modalManager;
|
|
9541
|
+
this.subscriptionManager = subscriptionManager;
|
|
9542
|
+
this.sessionManager = sessionManager;
|
|
9543
|
+
this.eventRouter = eventRouter;
|
|
8901
9544
|
}
|
|
8902
9545
|
/**
|
|
8903
9546
|
* Create a new TUI application instance.
|
|
8904
9547
|
*/
|
|
8905
9548
|
static async create(options) {
|
|
8906
|
-
const
|
|
8907
|
-
|
|
8908
|
-
|
|
8909
|
-
|
|
8910
|
-
|
|
8911
|
-
|
|
8912
|
-
|
|
8913
|
-
|
|
8914
|
-
|
|
8915
|
-
|
|
8916
|
-
|
|
8917
|
-
}
|
|
8918
|
-
const statusBar = new StatusBar(
|
|
8919
|
-
layout.statusBar,
|
|
8920
|
-
options.model,
|
|
8921
|
-
() => screenCtx.requestRender(),
|
|
8922
|
-
() => screenCtx.renderNow()
|
|
8923
|
-
);
|
|
8924
|
-
const inputHandler = new InputHandler(
|
|
8925
|
-
layout.inputBar,
|
|
8926
|
-
layout.promptLabel,
|
|
8927
|
-
layout.body,
|
|
8928
|
-
screen,
|
|
8929
|
-
() => screenCtx.requestRender(),
|
|
8930
|
-
() => screenCtx.renderNow(),
|
|
8931
|
-
showHints
|
|
8932
|
-
);
|
|
8933
|
-
const blockRenderer = new BlockRenderer(
|
|
8934
|
-
layout.body,
|
|
8935
|
-
() => screenCtx.requestRender(),
|
|
8936
|
-
() => screenCtx.renderNow()
|
|
8937
|
-
);
|
|
8938
|
-
if (hintsBar) {
|
|
8939
|
-
blockRenderer.onHasContentChange((hasContent) => {
|
|
8940
|
-
hintsBar.setHasContent(hasContent);
|
|
8941
|
-
});
|
|
8942
|
-
}
|
|
8943
|
-
const controller = new TUIController({
|
|
8944
|
-
onFocusModeChange: (mode) => {
|
|
8945
|
-
applyFocusMode(mode, layout, statusBar, inputHandler, screenCtx);
|
|
8946
|
-
hintsBar?.setFocusMode(mode);
|
|
8947
|
-
},
|
|
8948
|
-
onContentFilterModeChange: (mode) => {
|
|
8949
|
-
applyContentFilterMode(mode, blockRenderer, statusBar, screenCtx);
|
|
8950
|
-
hintsBar?.setContentFilterMode(mode);
|
|
8951
|
-
}
|
|
8952
|
-
});
|
|
8953
|
-
const modalManager = new ModalManager();
|
|
8954
|
-
const keyboardManager = new KeyboardManager({
|
|
8955
|
-
screen,
|
|
8956
|
-
getFocusMode: () => controller.getFocusMode(),
|
|
8957
|
-
getContentFilterMode: () => controller.getContentFilterMode(),
|
|
8958
|
-
isWaitingForREPLPrompt: () => inputHandler.isWaitingForREPLPrompt(),
|
|
8959
|
-
hasPendingInput: () => inputHandler.hasPendingInput(),
|
|
8960
|
-
isBlockExpanded: () => blockRenderer.getSelectedBlock()?.expanded ?? false,
|
|
8961
|
-
onAction: (action) => {
|
|
8962
|
-
handleKeyAction(
|
|
8963
|
-
action,
|
|
8964
|
-
controller,
|
|
8965
|
-
blockRenderer,
|
|
8966
|
-
statusBar,
|
|
8967
|
-
screenCtx,
|
|
8968
|
-
modalManager,
|
|
8969
|
-
layout
|
|
8970
|
-
);
|
|
8971
|
-
}
|
|
8972
|
-
});
|
|
8973
|
-
const app = new _TUIApp(
|
|
8974
|
-
screenCtx,
|
|
8975
|
-
statusBar,
|
|
8976
|
-
inputHandler,
|
|
8977
|
-
blockRenderer,
|
|
8978
|
-
controller,
|
|
8979
|
-
modalManager
|
|
9549
|
+
const dependencies = createTUIAppDependencies(options);
|
|
9550
|
+
return new _TUIApp(
|
|
9551
|
+
dependencies.screenCtx,
|
|
9552
|
+
dependencies.statusBar,
|
|
9553
|
+
dependencies.inputHandler,
|
|
9554
|
+
dependencies.blockRenderer,
|
|
9555
|
+
dependencies.controller,
|
|
9556
|
+
dependencies.modalManager,
|
|
9557
|
+
dependencies.subscriptionManager,
|
|
9558
|
+
dependencies.sessionManager,
|
|
9559
|
+
dependencies.eventRouter
|
|
8980
9560
|
);
|
|
8981
|
-
keyboardManager.setup();
|
|
8982
|
-
inputHandler.onCtrlC(() => keyboardManager.handleForwardedKey("C-c"));
|
|
8983
|
-
inputHandler.onCtrlB(() => keyboardManager.handleForwardedKey("C-b"));
|
|
8984
|
-
inputHandler.onCtrlK(() => keyboardManager.handleForwardedKey("C-k"));
|
|
8985
|
-
inputHandler.onCtrlI(() => keyboardManager.handleForwardedKey("C-i"));
|
|
8986
|
-
inputHandler.onCtrlJ(() => keyboardManager.handleForwardedKey("C-j"));
|
|
8987
|
-
inputHandler.onCtrlP(() => keyboardManager.handleForwardedKey("C-p"));
|
|
8988
|
-
inputHandler.onArrowUp(() => {
|
|
8989
|
-
handleKeyAction(
|
|
8990
|
-
{ type: "scroll_line", direction: -1 },
|
|
8991
|
-
controller,
|
|
8992
|
-
blockRenderer,
|
|
8993
|
-
statusBar,
|
|
8994
|
-
screenCtx,
|
|
8995
|
-
modalManager,
|
|
8996
|
-
layout
|
|
8997
|
-
);
|
|
8998
|
-
});
|
|
8999
|
-
inputHandler.onArrowDown(() => {
|
|
9000
|
-
handleKeyAction(
|
|
9001
|
-
{ type: "scroll_line", direction: 1 },
|
|
9002
|
-
controller,
|
|
9003
|
-
blockRenderer,
|
|
9004
|
-
statusBar,
|
|
9005
|
-
screenCtx,
|
|
9006
|
-
modalManager,
|
|
9007
|
-
layout
|
|
9008
|
-
);
|
|
9009
|
-
});
|
|
9010
|
-
inputHandler.setGetFocusMode(() => controller.getFocusMode());
|
|
9011
|
-
inputHandler.setGetContentFilterMode(() => controller.getContentFilterMode());
|
|
9012
|
-
layout.body.on("scroll", () => {
|
|
9013
|
-
blockRenderer.handleUserScroll();
|
|
9014
|
-
});
|
|
9015
|
-
screen.on("resize", () => {
|
|
9016
|
-
blockRenderer.handleResize();
|
|
9017
|
-
});
|
|
9018
|
-
applyFocusMode(controller.getFocusMode(), layout, statusBar, inputHandler, screenCtx);
|
|
9019
|
-
screenCtx.requestRender();
|
|
9020
|
-
return app;
|
|
9021
9561
|
}
|
|
9022
9562
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
9023
9563
|
// Focus Mode Management (delegated to controller)
|
|
@@ -9053,11 +9593,7 @@ var TUIApp = class _TUIApp {
|
|
|
9053
9593
|
* automatically by ExecutionTree subscription via subscribeToTree().
|
|
9054
9594
|
*/
|
|
9055
9595
|
handleEvent(event) {
|
|
9056
|
-
|
|
9057
|
-
this.blockRenderer.addText(event.content);
|
|
9058
|
-
} else if (event.type === "thinking") {
|
|
9059
|
-
this.blockRenderer.addThinking(event.content, event.thinkingType);
|
|
9060
|
-
}
|
|
9596
|
+
this.eventRouter.handleEvent(event);
|
|
9061
9597
|
}
|
|
9062
9598
|
/**
|
|
9063
9599
|
* Show an LLM call starting.
|
|
@@ -9082,26 +9618,11 @@ var TUIApp = class _TUIApp {
|
|
|
9082
9618
|
async showRawViewer(mode) {
|
|
9083
9619
|
if (this.controller.getFocusMode() !== "browse") return;
|
|
9084
9620
|
const selected = this.blockRenderer.getSelectedBlock();
|
|
9085
|
-
if (!selected) return;
|
|
9086
|
-
|
|
9087
|
-
|
|
9088
|
-
|
|
9089
|
-
|
|
9090
|
-
request: node.rawRequest,
|
|
9091
|
-
response: node.rawResponse,
|
|
9092
|
-
iteration: node.iteration,
|
|
9093
|
-
model: node.model
|
|
9094
|
-
});
|
|
9095
|
-
} else if (selected.node.type === "gadget") {
|
|
9096
|
-
const node = selected.node;
|
|
9097
|
-
await this.modalManager.showRawViewer(this.screenCtx.screen, {
|
|
9098
|
-
mode,
|
|
9099
|
-
gadgetName: node.name,
|
|
9100
|
-
parameters: node.parameters,
|
|
9101
|
-
result: node.result,
|
|
9102
|
-
error: node.error
|
|
9103
|
-
});
|
|
9104
|
-
}
|
|
9621
|
+
if (!selected || !isRawViewerNode(selected.node)) return;
|
|
9622
|
+
await this.modalManager.showRawViewer(
|
|
9623
|
+
this.screenCtx.screen,
|
|
9624
|
+
createRawViewerData(selected.node, mode)
|
|
9625
|
+
);
|
|
9105
9626
|
}
|
|
9106
9627
|
/**
|
|
9107
9628
|
* Show approval dialog for gadget execution.
|
|
@@ -9156,21 +9677,7 @@ var TUIApp = class _TUIApp {
|
|
|
9156
9677
|
* Subscribe to an ExecutionTree for automatic block updates.
|
|
9157
9678
|
*/
|
|
9158
9679
|
subscribeToTree(tree) {
|
|
9159
|
-
|
|
9160
|
-
this.treeUnsubscribe();
|
|
9161
|
-
}
|
|
9162
|
-
const unsubBlock = this.blockRenderer.subscribeToTree(tree);
|
|
9163
|
-
const unsubStatus = this.statusBar.subscribeToTree(tree);
|
|
9164
|
-
this.treeUnsubscribe = () => {
|
|
9165
|
-
unsubBlock();
|
|
9166
|
-
unsubStatus();
|
|
9167
|
-
};
|
|
9168
|
-
return () => {
|
|
9169
|
-
if (this.treeUnsubscribe) {
|
|
9170
|
-
this.treeUnsubscribe();
|
|
9171
|
-
this.treeUnsubscribe = null;
|
|
9172
|
-
}
|
|
9173
|
-
};
|
|
9680
|
+
return this.subscriptionManager.subscribe(tree);
|
|
9174
9681
|
}
|
|
9175
9682
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
9176
9683
|
// Memory Cleanup (REPL mode)
|
|
@@ -9182,21 +9689,21 @@ var TUIApp = class _TUIApp {
|
|
|
9182
9689
|
* Call this after each agent run completes (after unsubscribing from tree).
|
|
9183
9690
|
*/
|
|
9184
9691
|
clearBlockRenderer() {
|
|
9185
|
-
this.
|
|
9692
|
+
this.sessionManager.clearAllBlocks();
|
|
9186
9693
|
}
|
|
9187
9694
|
/**
|
|
9188
9695
|
* Clear status bar activity state.
|
|
9189
9696
|
* Called between REPL turns to prevent stale state.
|
|
9190
9697
|
*/
|
|
9191
9698
|
clearStatusBar() {
|
|
9192
|
-
this.
|
|
9699
|
+
this.sessionManager.clearStatusBar();
|
|
9193
9700
|
}
|
|
9194
9701
|
/**
|
|
9195
9702
|
* Start a new session. Called at the start of each REPL turn.
|
|
9196
9703
|
* Increments the session counter so new blocks get the new sessionId.
|
|
9197
9704
|
*/
|
|
9198
9705
|
startNewSession() {
|
|
9199
|
-
this.
|
|
9706
|
+
this.sessionManager.startNewSession();
|
|
9200
9707
|
}
|
|
9201
9708
|
/**
|
|
9202
9709
|
* Clear blocks from the previous session only.
|
|
@@ -9204,7 +9711,7 @@ var TUIApp = class _TUIApp {
|
|
|
9204
9711
|
* The previous session's content was kept visible during this session for context.
|
|
9205
9712
|
*/
|
|
9206
9713
|
clearPreviousSession() {
|
|
9207
|
-
this.
|
|
9714
|
+
this.sessionManager.clearPreviousSession();
|
|
9208
9715
|
}
|
|
9209
9716
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
9210
9717
|
// Abort Control (delegated to controller)
|
|
@@ -9339,127 +9846,12 @@ var TUIApp = class _TUIApp {
|
|
|
9339
9846
|
* Clean up and restore terminal.
|
|
9340
9847
|
*/
|
|
9341
9848
|
destroy() {
|
|
9342
|
-
|
|
9343
|
-
this.treeUnsubscribe();
|
|
9344
|
-
this.treeUnsubscribe = null;
|
|
9345
|
-
}
|
|
9849
|
+
this.subscriptionManager.unsubscribe();
|
|
9346
9850
|
this.modalManager.closeAll();
|
|
9347
9851
|
this.inputHandler.cancelPending();
|
|
9348
9852
|
this.screenCtx.destroy();
|
|
9349
9853
|
}
|
|
9350
9854
|
};
|
|
9351
|
-
function applyFocusMode(mode, layout, statusBar, inputHandler, screenCtx) {
|
|
9352
|
-
statusBar.setFocusMode(mode);
|
|
9353
|
-
if (mode === "input") {
|
|
9354
|
-
inputHandler.activate();
|
|
9355
|
-
} else {
|
|
9356
|
-
inputHandler.deactivate();
|
|
9357
|
-
layout.body.focus();
|
|
9358
|
-
}
|
|
9359
|
-
screenCtx.renderNow();
|
|
9360
|
-
}
|
|
9361
|
-
function applyContentFilterMode(mode, blockRenderer, statusBar, screenCtx) {
|
|
9362
|
-
blockRenderer.setContentFilterMode(mode);
|
|
9363
|
-
statusBar.setContentFilterMode(mode);
|
|
9364
|
-
screenCtx.renderNow();
|
|
9365
|
-
}
|
|
9366
|
-
function handleKeyAction(action, controller, blockRenderer, statusBar, screenCtx, modalManager, layout) {
|
|
9367
|
-
switch (action.type) {
|
|
9368
|
-
case "ctrl_c": {
|
|
9369
|
-
const result = controller.handleCtrlC();
|
|
9370
|
-
if (result === "show_hint") {
|
|
9371
|
-
blockRenderer.addText("\n[Press Ctrl+C again to quit]\n");
|
|
9372
|
-
} else if (result === "quit") {
|
|
9373
|
-
process.exit(130);
|
|
9374
|
-
}
|
|
9375
|
-
break;
|
|
9376
|
-
}
|
|
9377
|
-
case "cancel":
|
|
9378
|
-
controller.triggerCancel();
|
|
9379
|
-
controller.abort();
|
|
9380
|
-
break;
|
|
9381
|
-
case "toggle_focus_mode":
|
|
9382
|
-
controller.toggleFocusMode();
|
|
9383
|
-
break;
|
|
9384
|
-
case "toggle_content_filter":
|
|
9385
|
-
controller.toggleContentFilterMode();
|
|
9386
|
-
break;
|
|
9387
|
-
case "cycle_profile":
|
|
9388
|
-
statusBar.cycleProfile();
|
|
9389
|
-
break;
|
|
9390
|
-
case "scroll_page": {
|
|
9391
|
-
const body = layout.body;
|
|
9392
|
-
if (!body.scroll) return;
|
|
9393
|
-
const containerHeight = body.height;
|
|
9394
|
-
const scrollAmount = Math.max(1, containerHeight - 2);
|
|
9395
|
-
if (action.direction < 0) {
|
|
9396
|
-
body.scroll(-scrollAmount);
|
|
9397
|
-
} else {
|
|
9398
|
-
body.scroll(scrollAmount);
|
|
9399
|
-
}
|
|
9400
|
-
blockRenderer.handleUserScroll();
|
|
9401
|
-
screenCtx.renderNow();
|
|
9402
|
-
break;
|
|
9403
|
-
}
|
|
9404
|
-
case "scroll_line": {
|
|
9405
|
-
const body = layout.body;
|
|
9406
|
-
if (!body.scroll) return;
|
|
9407
|
-
body.scroll(action.direction);
|
|
9408
|
-
blockRenderer.handleUserScroll();
|
|
9409
|
-
screenCtx.renderNow();
|
|
9410
|
-
break;
|
|
9411
|
-
}
|
|
9412
|
-
case "navigation":
|
|
9413
|
-
switch (action.action) {
|
|
9414
|
-
case "select_next":
|
|
9415
|
-
blockRenderer.selectNext();
|
|
9416
|
-
break;
|
|
9417
|
-
case "select_previous":
|
|
9418
|
-
blockRenderer.selectPrevious();
|
|
9419
|
-
break;
|
|
9420
|
-
case "select_first":
|
|
9421
|
-
blockRenderer.selectFirst();
|
|
9422
|
-
break;
|
|
9423
|
-
case "select_last":
|
|
9424
|
-
blockRenderer.selectLast();
|
|
9425
|
-
blockRenderer.enableFollowMode();
|
|
9426
|
-
break;
|
|
9427
|
-
case "toggle_expand":
|
|
9428
|
-
blockRenderer.toggleExpand();
|
|
9429
|
-
break;
|
|
9430
|
-
case "collapse":
|
|
9431
|
-
blockRenderer.collapseOrDeselect();
|
|
9432
|
-
break;
|
|
9433
|
-
}
|
|
9434
|
-
screenCtx.renderNow();
|
|
9435
|
-
break;
|
|
9436
|
-
case "raw_viewer":
|
|
9437
|
-
void (async () => {
|
|
9438
|
-
const selected = blockRenderer.getSelectedBlock();
|
|
9439
|
-
if (!selected) return;
|
|
9440
|
-
if (selected.node.type === "llm_call") {
|
|
9441
|
-
const node = selected.node;
|
|
9442
|
-
await modalManager.showRawViewer(screenCtx.screen, {
|
|
9443
|
-
mode: action.mode,
|
|
9444
|
-
request: node.rawRequest,
|
|
9445
|
-
response: node.rawResponse,
|
|
9446
|
-
iteration: node.iteration,
|
|
9447
|
-
model: node.model
|
|
9448
|
-
});
|
|
9449
|
-
} else if (selected.node.type === "gadget") {
|
|
9450
|
-
const node = selected.node;
|
|
9451
|
-
await modalManager.showRawViewer(screenCtx.screen, {
|
|
9452
|
-
mode: action.mode,
|
|
9453
|
-
gadgetName: node.name,
|
|
9454
|
-
parameters: node.parameters,
|
|
9455
|
-
result: node.result,
|
|
9456
|
-
error: node.error
|
|
9457
|
-
});
|
|
9458
|
-
}
|
|
9459
|
-
})();
|
|
9460
|
-
break;
|
|
9461
|
-
}
|
|
9462
|
-
}
|
|
9463
9855
|
|
|
9464
9856
|
// src/agent-command.ts
|
|
9465
9857
|
async function executeAgent(promptArg, options, env, commandName) {
|
|
@@ -9534,7 +9926,7 @@ async function executeAgent(promptArg, options, env, commandName) {
|
|
|
9534
9926
|
if (!useTUI) {
|
|
9535
9927
|
process.once("SIGINT", () => process.exit(130));
|
|
9536
9928
|
}
|
|
9537
|
-
const DEFAULT_APPROVAL_REQUIRED = ["RunCommand", "WriteFile", "EditFile"];
|
|
9929
|
+
const DEFAULT_APPROVAL_REQUIRED = ["RunCommand", "WriteFile", "EditFile", "DeleteFile"];
|
|
9538
9930
|
const userApprovals = options.gadgetApproval ?? {};
|
|
9539
9931
|
const gadgetApprovals = {
|
|
9540
9932
|
...userApprovals
|
|
@@ -9671,6 +10063,11 @@ ${ctx.gadgetName} is denied by configuration.`
|
|
|
9671
10063
|
gadgetName: ctx.gadgetName,
|
|
9672
10064
|
parameters: ctx.parameters
|
|
9673
10065
|
});
|
|
10066
|
+
if (response === "always") {
|
|
10067
|
+
gadgetApprovals[ctx.gadgetName] = "allowed";
|
|
10068
|
+
} else if (response === "deny") {
|
|
10069
|
+
gadgetApprovals[ctx.gadgetName] = "denied";
|
|
10070
|
+
}
|
|
9674
10071
|
if (response === "yes" || response === "always") {
|
|
9675
10072
|
return { action: "proceed" };
|
|
9676
10073
|
}
|
|
@@ -9685,7 +10082,7 @@ Denied by user`
|
|
|
9685
10082
|
action: "skip",
|
|
9686
10083
|
syntheticResult: `status=denied
|
|
9687
10084
|
|
|
9688
|
-
${ctx.gadgetName} requires interactive approval.
|
|
10085
|
+
${ctx.gadgetName} requires interactive approval. Enable TUI mode or adjust 'gadget-approval' in your config to allow this gadget.`
|
|
9689
10086
|
};
|
|
9690
10087
|
}
|
|
9691
10088
|
}
|
|
@@ -10156,7 +10553,7 @@ Profile: ${name}
|
|
|
10156
10553
|
` Temperature: ${section.temperature !== void 0 ? section.temperature : "(default)"}
|
|
10157
10554
|
`
|
|
10158
10555
|
);
|
|
10159
|
-
const gadgets = section.gadgets
|
|
10556
|
+
const gadgets = section.gadgets;
|
|
10160
10557
|
if (gadgets && gadgets.length > 0) {
|
|
10161
10558
|
env.stdout.write("\nGadgets:\n");
|
|
10162
10559
|
for (const g of gadgets) {
|
|
@@ -10208,7 +10605,7 @@ System Prompt (${chars.toLocaleString()} chars, ${lines} lines):
|
|
|
10208
10605
|
// src/environment.ts
|
|
10209
10606
|
import { join as join4 } from "path";
|
|
10210
10607
|
import readline from "readline";
|
|
10211
|
-
import
|
|
10608
|
+
import chalk7 from "chalk";
|
|
10212
10609
|
import { createLogger, LLMist } from "llmist";
|
|
10213
10610
|
var LOG_LEVEL_MAP = {
|
|
10214
10611
|
silly: 0,
|
|
@@ -10254,14 +10651,14 @@ function createPromptFunction(stdin, stdout) {
|
|
|
10254
10651
|
output: stdout
|
|
10255
10652
|
});
|
|
10256
10653
|
stdout.write("\n");
|
|
10257
|
-
stdout.write(`${
|
|
10654
|
+
stdout.write(`${chalk7.cyan("\u2500".repeat(60))}
|
|
10258
10655
|
`);
|
|
10259
|
-
stdout.write(
|
|
10656
|
+
stdout.write(chalk7.cyan.bold("\u{1F916} Agent asks:\n"));
|
|
10260
10657
|
stdout.write(`${question}
|
|
10261
10658
|
`);
|
|
10262
|
-
stdout.write(`${
|
|
10659
|
+
stdout.write(`${chalk7.cyan("\u2500".repeat(60))}
|
|
10263
10660
|
`);
|
|
10264
|
-
rl.question(
|
|
10661
|
+
rl.question(chalk7.green.bold("You: "), (answer) => {
|
|
10265
10662
|
rl.close();
|
|
10266
10663
|
resolve3(answer);
|
|
10267
10664
|
});
|
|
@@ -10342,12 +10739,12 @@ function registerCustomCommand(program, name, config, env, globalSubagents, glob
|
|
|
10342
10739
|
}
|
|
10343
10740
|
|
|
10344
10741
|
// src/gadget-command.ts
|
|
10345
|
-
import
|
|
10742
|
+
import chalk9 from "chalk";
|
|
10346
10743
|
import { schemaToJSONSchema as schemaToJSONSchema2, validateGadgetSchema } from "llmist";
|
|
10347
10744
|
|
|
10348
10745
|
// src/gadget-prompts.ts
|
|
10349
10746
|
import { createInterface } from "readline/promises";
|
|
10350
|
-
import
|
|
10747
|
+
import chalk8 from "chalk";
|
|
10351
10748
|
import { schemaToJSONSchema } from "llmist";
|
|
10352
10749
|
async function promptForParameters(schema, ctx) {
|
|
10353
10750
|
if (!schema) {
|
|
@@ -10380,16 +10777,16 @@ ${issues}`);
|
|
|
10380
10777
|
async function promptForField(rl, key, prop, required) {
|
|
10381
10778
|
const isRequired = required.includes(key);
|
|
10382
10779
|
const typeHint = formatTypeHint(prop);
|
|
10383
|
-
const defaultHint = prop.default !== void 0 ?
|
|
10384
|
-
const requiredMarker = isRequired ?
|
|
10780
|
+
const defaultHint = prop.default !== void 0 ? chalk8.dim(` [default: ${JSON.stringify(prop.default)}]`) : "";
|
|
10781
|
+
const requiredMarker = isRequired ? chalk8.red("*") : "";
|
|
10385
10782
|
let prompt = `
|
|
10386
|
-
${
|
|
10783
|
+
${chalk8.cyan.bold(key)}${requiredMarker}`;
|
|
10387
10784
|
if (prop.description) {
|
|
10388
|
-
prompt +=
|
|
10785
|
+
prompt += chalk8.dim(` - ${prop.description}`);
|
|
10389
10786
|
}
|
|
10390
10787
|
prompt += `
|
|
10391
10788
|
${typeHint}${defaultHint}
|
|
10392
|
-
${
|
|
10789
|
+
${chalk8.green(">")} `;
|
|
10393
10790
|
const answer = await rl.question(prompt);
|
|
10394
10791
|
const trimmed = answer.trim();
|
|
10395
10792
|
if (!trimmed) {
|
|
@@ -10405,20 +10802,20 @@ ${chalk7.cyan.bold(key)}${requiredMarker}`;
|
|
|
10405
10802
|
}
|
|
10406
10803
|
function formatTypeHint(prop) {
|
|
10407
10804
|
if (prop.enum) {
|
|
10408
|
-
return
|
|
10805
|
+
return chalk8.yellow(`(${prop.enum.join(" | ")})`);
|
|
10409
10806
|
}
|
|
10410
10807
|
if (prop.type === "array") {
|
|
10411
10808
|
const items = prop.items;
|
|
10412
10809
|
if (items?.enum) {
|
|
10413
|
-
return
|
|
10810
|
+
return chalk8.yellow(`(${items.enum.join(" | ")})[] comma-separated`);
|
|
10414
10811
|
}
|
|
10415
10812
|
const itemType = items?.type ?? "any";
|
|
10416
|
-
return
|
|
10813
|
+
return chalk8.yellow(`(${itemType}[]) comma-separated`);
|
|
10417
10814
|
}
|
|
10418
10815
|
if (prop.type === "object" && prop.properties) {
|
|
10419
|
-
return
|
|
10816
|
+
return chalk8.yellow("(object) enter as JSON");
|
|
10420
10817
|
}
|
|
10421
|
-
return
|
|
10818
|
+
return chalk8.yellow(`(${prop.type ?? "any"})`);
|
|
10422
10819
|
}
|
|
10423
10820
|
function parseValue(input, prop, key) {
|
|
10424
10821
|
const type = prop.type;
|
|
@@ -10529,7 +10926,7 @@ Available gadgets:
|
|
|
10529
10926
|
async function executeGadgetRun(file, options, env) {
|
|
10530
10927
|
const cwd = process.cwd();
|
|
10531
10928
|
const { gadget, name } = await selectGadget(file, options.name, cwd);
|
|
10532
|
-
env.stderr.write(
|
|
10929
|
+
env.stderr.write(chalk9.cyan.bold(`
|
|
10533
10930
|
\u{1F527} Running gadget: ${name}
|
|
10534
10931
|
`));
|
|
10535
10932
|
let params;
|
|
@@ -10540,7 +10937,7 @@ async function executeGadgetRun(file, options, env) {
|
|
|
10540
10937
|
// Prompts go to stderr to keep stdout clean
|
|
10541
10938
|
});
|
|
10542
10939
|
} else {
|
|
10543
|
-
env.stderr.write(
|
|
10940
|
+
env.stderr.write(chalk9.dim("Reading parameters from stdin...\n"));
|
|
10544
10941
|
const stdinParams = await readStdinJson(env.stdin);
|
|
10545
10942
|
if (gadget.parameterSchema) {
|
|
10546
10943
|
const result2 = gadget.parameterSchema.safeParse(stdinParams);
|
|
@@ -10554,7 +10951,7 @@ ${issues}`);
|
|
|
10554
10951
|
params = stdinParams;
|
|
10555
10952
|
}
|
|
10556
10953
|
}
|
|
10557
|
-
env.stderr.write(
|
|
10954
|
+
env.stderr.write(chalk9.dim("\nExecuting...\n"));
|
|
10558
10955
|
const startTime = Date.now();
|
|
10559
10956
|
let result;
|
|
10560
10957
|
let cost;
|
|
@@ -10581,7 +10978,7 @@ ${issues}`);
|
|
|
10581
10978
|
}
|
|
10582
10979
|
const elapsed = Date.now() - startTime;
|
|
10583
10980
|
const costInfo = cost !== void 0 && cost > 0 ? ` (Cost: $${cost.toFixed(6)})` : "";
|
|
10584
|
-
env.stderr.write(
|
|
10981
|
+
env.stderr.write(chalk9.green(`
|
|
10585
10982
|
\u2713 Completed in ${elapsed}ms${costInfo}
|
|
10586
10983
|
|
|
10587
10984
|
`));
|
|
@@ -10617,37 +11014,37 @@ async function executeGadgetInfo(file, options, env) {
|
|
|
10617
11014
|
return;
|
|
10618
11015
|
}
|
|
10619
11016
|
env.stdout.write("\n");
|
|
10620
|
-
env.stdout.write(
|
|
11017
|
+
env.stdout.write(chalk9.cyan.bold(`${name}
|
|
10621
11018
|
`));
|
|
10622
|
-
env.stdout.write(
|
|
10623
|
-
env.stdout.write(
|
|
11019
|
+
env.stdout.write(chalk9.cyan("\u2550".repeat(name.length)) + "\n\n");
|
|
11020
|
+
env.stdout.write(chalk9.bold("Description:\n"));
|
|
10624
11021
|
env.stdout.write(` ${gadget.description}
|
|
10625
11022
|
|
|
10626
11023
|
`);
|
|
10627
11024
|
if (gadget.parameterSchema) {
|
|
10628
|
-
env.stdout.write(
|
|
11025
|
+
env.stdout.write(chalk9.bold("Parameters:\n"));
|
|
10629
11026
|
const jsonSchema = schemaToJSONSchema2(gadget.parameterSchema, { target: "draft-7" });
|
|
10630
11027
|
env.stdout.write(formatSchemaAsText(jsonSchema, " ") + "\n\n");
|
|
10631
11028
|
} else {
|
|
10632
|
-
env.stdout.write(
|
|
11029
|
+
env.stdout.write(chalk9.dim("No parameters required.\n\n"));
|
|
10633
11030
|
}
|
|
10634
11031
|
if (gadget.timeoutMs) {
|
|
10635
|
-
env.stdout.write(
|
|
11032
|
+
env.stdout.write(chalk9.bold("Timeout:\n"));
|
|
10636
11033
|
env.stdout.write(` ${gadget.timeoutMs}ms
|
|
10637
11034
|
|
|
10638
11035
|
`);
|
|
10639
11036
|
}
|
|
10640
11037
|
if (gadget.examples && gadget.examples.length > 0) {
|
|
10641
|
-
env.stdout.write(
|
|
11038
|
+
env.stdout.write(chalk9.bold("Examples:\n"));
|
|
10642
11039
|
for (const example of gadget.examples) {
|
|
10643
11040
|
if (example.comment) {
|
|
10644
|
-
env.stdout.write(
|
|
11041
|
+
env.stdout.write(chalk9.dim(` # ${example.comment}
|
|
10645
11042
|
`));
|
|
10646
11043
|
}
|
|
10647
|
-
env.stdout.write(` Input: ${
|
|
11044
|
+
env.stdout.write(` Input: ${chalk9.cyan(JSON.stringify(example.params))}
|
|
10648
11045
|
`);
|
|
10649
11046
|
if (example.output !== void 0) {
|
|
10650
|
-
env.stdout.write(` Output: ${
|
|
11047
|
+
env.stdout.write(` Output: ${chalk9.green(example.output)}
|
|
10651
11048
|
`);
|
|
10652
11049
|
}
|
|
10653
11050
|
env.stdout.write("\n");
|
|
@@ -10680,27 +11077,27 @@ function formatSchemaAsText(schema, indent = "") {
|
|
|
10680
11077
|
const isRequired = required.includes(key);
|
|
10681
11078
|
const enumValues = prop.enum;
|
|
10682
11079
|
const defaultValue = prop.default;
|
|
10683
|
-
let line = `${indent}${
|
|
11080
|
+
let line = `${indent}${chalk9.cyan(key)}`;
|
|
10684
11081
|
if (isRequired) {
|
|
10685
|
-
line +=
|
|
11082
|
+
line += chalk9.red("*");
|
|
10686
11083
|
}
|
|
10687
11084
|
if (type === "array") {
|
|
10688
11085
|
const items = prop.items;
|
|
10689
11086
|
const itemType = items?.type || "any";
|
|
10690
|
-
line +=
|
|
11087
|
+
line += chalk9.dim(` (${itemType}[])`);
|
|
10691
11088
|
} else if (type === "object" && prop.properties) {
|
|
10692
|
-
line +=
|
|
11089
|
+
line += chalk9.dim(" (object)");
|
|
10693
11090
|
} else {
|
|
10694
|
-
line +=
|
|
11091
|
+
line += chalk9.dim(` (${type})`);
|
|
10695
11092
|
}
|
|
10696
11093
|
if (defaultValue !== void 0) {
|
|
10697
|
-
line +=
|
|
11094
|
+
line += chalk9.dim(` [default: ${JSON.stringify(defaultValue)}]`);
|
|
10698
11095
|
}
|
|
10699
11096
|
if (description) {
|
|
10700
11097
|
line += `: ${description}`;
|
|
10701
11098
|
}
|
|
10702
11099
|
if (enumValues) {
|
|
10703
|
-
line +=
|
|
11100
|
+
line += chalk9.yellow(` - one of: ${enumValues.join(", ")}`);
|
|
10704
11101
|
}
|
|
10705
11102
|
lines.push(line);
|
|
10706
11103
|
if (type === "object" && prop.properties) {
|
|
@@ -10740,20 +11137,20 @@ async function executeGadgetValidate(file, env) {
|
|
|
10740
11137
|
throw new Error(`Validation issues:
|
|
10741
11138
|
${issues.map((i) => ` - ${i}`).join("\n")}`);
|
|
10742
11139
|
}
|
|
10743
|
-
env.stdout.write(
|
|
10744
|
-
env.stdout.write(
|
|
11140
|
+
env.stdout.write(chalk9.green.bold("\n\u2713 Valid\n\n"));
|
|
11141
|
+
env.stdout.write(chalk9.bold("Gadgets found:\n"));
|
|
10745
11142
|
for (const gadget of gadgets) {
|
|
10746
11143
|
const name = gadget.name ?? gadget.constructor.name;
|
|
10747
|
-
const schemaInfo = gadget.parameterSchema ?
|
|
10748
|
-
env.stdout.write(` ${
|
|
11144
|
+
const schemaInfo = gadget.parameterSchema ? chalk9.cyan("(with schema)") : chalk9.dim("(no schema)");
|
|
11145
|
+
env.stdout.write(` ${chalk9.bold(name)} ${schemaInfo}
|
|
10749
11146
|
`);
|
|
10750
|
-
env.stdout.write(
|
|
11147
|
+
env.stdout.write(chalk9.dim(` ${gadget.description}
|
|
10751
11148
|
`));
|
|
10752
11149
|
}
|
|
10753
11150
|
env.stdout.write("\n");
|
|
10754
11151
|
} catch (error) {
|
|
10755
11152
|
const message = error instanceof Error ? error.message : String(error);
|
|
10756
|
-
env.stdout.write(
|
|
11153
|
+
env.stdout.write(chalk9.red.bold(`
|
|
10757
11154
|
\u2717 Invalid
|
|
10758
11155
|
|
|
10759
11156
|
`));
|
|
@@ -10937,7 +11334,7 @@ function registerInitCommand(program, env) {
|
|
|
10937
11334
|
}
|
|
10938
11335
|
|
|
10939
11336
|
// src/models-command.ts
|
|
10940
|
-
import
|
|
11337
|
+
import chalk10 from "chalk";
|
|
10941
11338
|
import { MODEL_ALIASES } from "llmist";
|
|
10942
11339
|
async function handleModelsCommand(options, env) {
|
|
10943
11340
|
const client = env.createClient();
|
|
@@ -10956,11 +11353,11 @@ async function handleModelsCommand(options, env) {
|
|
|
10956
11353
|
function renderAllTables(textModels, imageModels, speechModels, verbose, stream) {
|
|
10957
11354
|
const hasAnyModels = textModels.length > 0 || imageModels.length > 0 || speechModels.length > 0;
|
|
10958
11355
|
if (!hasAnyModels) {
|
|
10959
|
-
stream.write(
|
|
11356
|
+
stream.write(chalk10.yellow("\nNo models found matching the specified criteria.\n\n"));
|
|
10960
11357
|
return;
|
|
10961
11358
|
}
|
|
10962
|
-
stream.write(
|
|
10963
|
-
stream.write(
|
|
11359
|
+
stream.write(chalk10.bold.cyan("\nAvailable Models\n"));
|
|
11360
|
+
stream.write(chalk10.cyan("=".repeat(80)) + "\n\n");
|
|
10964
11361
|
if (textModels.length > 0) {
|
|
10965
11362
|
renderTextTable(textModels, verbose, stream);
|
|
10966
11363
|
}
|
|
@@ -10971,12 +11368,12 @@ function renderAllTables(textModels, imageModels, speechModels, verbose, stream)
|
|
|
10971
11368
|
renderSpeechTable(speechModels, verbose, stream);
|
|
10972
11369
|
}
|
|
10973
11370
|
if (textModels.length > 0) {
|
|
10974
|
-
stream.write(
|
|
10975
|
-
stream.write(
|
|
11371
|
+
stream.write(chalk10.bold.magenta("Model Shortcuts\n"));
|
|
11372
|
+
stream.write(chalk10.dim("\u2500".repeat(80)) + "\n");
|
|
10976
11373
|
const shortcuts = Object.entries(MODEL_ALIASES).sort((a, b) => a[0].localeCompare(b[0]));
|
|
10977
11374
|
for (const [shortcut, fullName] of shortcuts) {
|
|
10978
11375
|
stream.write(
|
|
10979
|
-
|
|
11376
|
+
chalk10.cyan(` ${shortcut.padEnd(15)}`) + chalk10.dim(" \u2192 ") + chalk10.white(fullName) + "\n"
|
|
10980
11377
|
);
|
|
10981
11378
|
}
|
|
10982
11379
|
stream.write("\n");
|
|
@@ -10991,13 +11388,13 @@ function renderTextTable(models, verbose, stream) {
|
|
|
10991
11388
|
}
|
|
10992
11389
|
grouped.get(provider).push(model);
|
|
10993
11390
|
}
|
|
10994
|
-
stream.write(
|
|
10995
|
-
stream.write(
|
|
11391
|
+
stream.write(chalk10.bold.blue("\u{1F4DD} Text/LLM Models\n"));
|
|
11392
|
+
stream.write(chalk10.dim("\u2500".repeat(80)) + "\n\n");
|
|
10996
11393
|
const providers = Array.from(grouped.keys()).sort();
|
|
10997
11394
|
for (const provider of providers) {
|
|
10998
11395
|
const providerModels = grouped.get(provider);
|
|
10999
11396
|
const providerName = provider.charAt(0).toUpperCase() + provider.slice(1);
|
|
11000
|
-
stream.write(
|
|
11397
|
+
stream.write(chalk10.bold.yellow(`${providerName}
|
|
11001
11398
|
`));
|
|
11002
11399
|
if (verbose) {
|
|
11003
11400
|
renderVerboseTable(providerModels, stream);
|
|
@@ -11014,58 +11411,58 @@ function renderCompactTable(models, stream) {
|
|
|
11014
11411
|
const inputWidth = 10;
|
|
11015
11412
|
const outputWidth = 10;
|
|
11016
11413
|
stream.write(
|
|
11017
|
-
|
|
11414
|
+
chalk10.dim("\u2500".repeat(idWidth + nameWidth + contextWidth + inputWidth + outputWidth + 8)) + "\n"
|
|
11018
11415
|
);
|
|
11019
11416
|
stream.write(
|
|
11020
|
-
|
|
11417
|
+
chalk10.bold(
|
|
11021
11418
|
"Model ID".padEnd(idWidth) + " " + "Display Name".padEnd(nameWidth) + " " + "Context".padEnd(contextWidth) + " " + "Input".padEnd(inputWidth) + " " + "Output".padEnd(outputWidth)
|
|
11022
11419
|
) + "\n"
|
|
11023
11420
|
);
|
|
11024
11421
|
stream.write(
|
|
11025
|
-
|
|
11422
|
+
chalk10.dim("\u2500".repeat(idWidth + nameWidth + contextWidth + inputWidth + outputWidth + 8)) + "\n"
|
|
11026
11423
|
);
|
|
11027
11424
|
for (const model of models) {
|
|
11028
11425
|
const contextFormatted = formatTokensLong(model.contextWindow);
|
|
11029
11426
|
const inputPrice = `$${model.pricing.input.toFixed(2)}`;
|
|
11030
11427
|
const outputPrice = `$${model.pricing.output.toFixed(2)}`;
|
|
11031
11428
|
stream.write(
|
|
11032
|
-
|
|
11429
|
+
chalk10.green(model.modelId.padEnd(idWidth)) + " " + chalk10.white(model.displayName.padEnd(nameWidth)) + " " + chalk10.yellow(contextFormatted.padEnd(contextWidth)) + " " + chalk10.cyan(inputPrice.padEnd(inputWidth)) + " " + chalk10.cyan(outputPrice.padEnd(outputWidth)) + "\n"
|
|
11033
11430
|
);
|
|
11034
11431
|
}
|
|
11035
11432
|
stream.write(
|
|
11036
|
-
|
|
11433
|
+
chalk10.dim("\u2500".repeat(idWidth + nameWidth + contextWidth + inputWidth + outputWidth + 8)) + "\n"
|
|
11037
11434
|
);
|
|
11038
|
-
stream.write(
|
|
11435
|
+
stream.write(chalk10.dim(` * Prices are per 1M tokens
|
|
11039
11436
|
`));
|
|
11040
11437
|
}
|
|
11041
11438
|
function renderVerboseTable(models, stream) {
|
|
11042
11439
|
for (const model of models) {
|
|
11043
|
-
stream.write(
|
|
11440
|
+
stream.write(chalk10.bold.green(`
|
|
11044
11441
|
${model.modelId}
|
|
11045
11442
|
`));
|
|
11046
|
-
stream.write(
|
|
11047
|
-
stream.write(` ${
|
|
11443
|
+
stream.write(chalk10.dim(" " + "\u2500".repeat(60)) + "\n");
|
|
11444
|
+
stream.write(` ${chalk10.dim("Name:")} ${chalk10.white(model.displayName)}
|
|
11048
11445
|
`);
|
|
11049
11446
|
stream.write(
|
|
11050
|
-
` ${
|
|
11447
|
+
` ${chalk10.dim("Context:")} ${chalk10.yellow(formatTokensLong(model.contextWindow))}
|
|
11051
11448
|
`
|
|
11052
11449
|
);
|
|
11053
11450
|
stream.write(
|
|
11054
|
-
` ${
|
|
11451
|
+
` ${chalk10.dim("Max Output:")} ${chalk10.yellow(formatTokensLong(model.maxOutputTokens))}
|
|
11055
11452
|
`
|
|
11056
11453
|
);
|
|
11057
11454
|
stream.write(
|
|
11058
|
-
` ${
|
|
11455
|
+
` ${chalk10.dim("Pricing:")} ${chalk10.cyan(`$${model.pricing.input.toFixed(2)} input`)} ${chalk10.dim("/")} ${chalk10.cyan(`$${model.pricing.output.toFixed(2)} output`)} ${chalk10.dim("(per 1M tokens)")}
|
|
11059
11456
|
`
|
|
11060
11457
|
);
|
|
11061
11458
|
if (model.pricing.cachedInput !== void 0) {
|
|
11062
11459
|
stream.write(
|
|
11063
|
-
` ${
|
|
11460
|
+
` ${chalk10.dim("Cached Input:")} ${chalk10.cyan(`$${model.pricing.cachedInput.toFixed(2)} per 1M tokens`)}
|
|
11064
11461
|
`
|
|
11065
11462
|
);
|
|
11066
11463
|
}
|
|
11067
11464
|
if (model.knowledgeCutoff) {
|
|
11068
|
-
stream.write(` ${
|
|
11465
|
+
stream.write(` ${chalk10.dim("Knowledge:")} ${model.knowledgeCutoff}
|
|
11069
11466
|
`);
|
|
11070
11467
|
}
|
|
11071
11468
|
const features = [];
|
|
@@ -11076,20 +11473,20 @@ function renderVerboseTable(models, stream) {
|
|
|
11076
11473
|
if (model.features.structuredOutputs) features.push("structured-outputs");
|
|
11077
11474
|
if (model.features.fineTuning) features.push("fine-tuning");
|
|
11078
11475
|
if (features.length > 0) {
|
|
11079
|
-
stream.write(` ${
|
|
11476
|
+
stream.write(` ${chalk10.dim("Features:")} ${chalk10.blue(features.join(", "))}
|
|
11080
11477
|
`);
|
|
11081
11478
|
}
|
|
11082
11479
|
if (model.metadata) {
|
|
11083
11480
|
if (model.metadata.family) {
|
|
11084
|
-
stream.write(` ${
|
|
11481
|
+
stream.write(` ${chalk10.dim("Family:")} ${model.metadata.family}
|
|
11085
11482
|
`);
|
|
11086
11483
|
}
|
|
11087
11484
|
if (model.metadata.releaseDate) {
|
|
11088
|
-
stream.write(` ${
|
|
11485
|
+
stream.write(` ${chalk10.dim("Released:")} ${model.metadata.releaseDate}
|
|
11089
11486
|
`);
|
|
11090
11487
|
}
|
|
11091
11488
|
if (model.metadata.notes) {
|
|
11092
|
-
stream.write(` ${
|
|
11489
|
+
stream.write(` ${chalk10.dim("Notes:")} ${chalk10.italic(model.metadata.notes)}
|
|
11093
11490
|
`);
|
|
11094
11491
|
}
|
|
11095
11492
|
}
|
|
@@ -11097,8 +11494,8 @@ function renderVerboseTable(models, stream) {
|
|
|
11097
11494
|
stream.write("\n");
|
|
11098
11495
|
}
|
|
11099
11496
|
function renderImageTable(models, verbose, stream) {
|
|
11100
|
-
stream.write(
|
|
11101
|
-
stream.write(
|
|
11497
|
+
stream.write(chalk10.bold.green("\u{1F3A8} Image Generation Models\n"));
|
|
11498
|
+
stream.write(chalk10.dim("\u2500".repeat(80)) + "\n\n");
|
|
11102
11499
|
const grouped = /* @__PURE__ */ new Map();
|
|
11103
11500
|
for (const model of models) {
|
|
11104
11501
|
if (!grouped.has(model.provider)) {
|
|
@@ -11108,29 +11505,29 @@ function renderImageTable(models, verbose, stream) {
|
|
|
11108
11505
|
}
|
|
11109
11506
|
for (const [provider, providerModels] of Array.from(grouped.entries()).sort()) {
|
|
11110
11507
|
const providerName = provider.charAt(0).toUpperCase() + provider.slice(1);
|
|
11111
|
-
stream.write(
|
|
11508
|
+
stream.write(chalk10.bold.yellow(`${providerName}
|
|
11112
11509
|
`));
|
|
11113
11510
|
if (verbose) {
|
|
11114
11511
|
for (const model of providerModels) {
|
|
11115
|
-
stream.write(
|
|
11512
|
+
stream.write(chalk10.bold.green(`
|
|
11116
11513
|
${model.modelId}
|
|
11117
11514
|
`));
|
|
11118
|
-
stream.write(
|
|
11119
|
-
stream.write(` ${
|
|
11515
|
+
stream.write(chalk10.dim(" " + "\u2500".repeat(60)) + "\n");
|
|
11516
|
+
stream.write(` ${chalk10.dim("Name:")} ${chalk10.white(model.displayName)}
|
|
11120
11517
|
`);
|
|
11121
11518
|
stream.write(
|
|
11122
|
-
` ${
|
|
11519
|
+
` ${chalk10.dim("Sizes:")} ${chalk10.yellow(model.supportedSizes.join(", "))}
|
|
11123
11520
|
`
|
|
11124
11521
|
);
|
|
11125
11522
|
if (model.supportedQualities) {
|
|
11126
11523
|
stream.write(
|
|
11127
|
-
` ${
|
|
11524
|
+
` ${chalk10.dim("Qualities:")} ${chalk10.yellow(model.supportedQualities.join(", "))}
|
|
11128
11525
|
`
|
|
11129
11526
|
);
|
|
11130
11527
|
}
|
|
11131
|
-
stream.write(` ${
|
|
11528
|
+
stream.write(` ${chalk10.dim("Max Images:")} ${chalk10.yellow(model.maxImages.toString())}
|
|
11132
11529
|
`);
|
|
11133
|
-
stream.write(` ${
|
|
11530
|
+
stream.write(` ${chalk10.dim("Pricing:")} ${chalk10.cyan(formatImagePrice(model))}
|
|
11134
11531
|
`);
|
|
11135
11532
|
if (model.features) {
|
|
11136
11533
|
const features = [];
|
|
@@ -11138,7 +11535,7 @@ function renderImageTable(models, verbose, stream) {
|
|
|
11138
11535
|
if (model.features.transparency) features.push("transparency");
|
|
11139
11536
|
if (model.features.conversational) features.push("conversational");
|
|
11140
11537
|
if (features.length > 0) {
|
|
11141
|
-
stream.write(` ${
|
|
11538
|
+
stream.write(` ${chalk10.dim("Features:")} ${chalk10.blue(features.join(", "))}
|
|
11142
11539
|
`);
|
|
11143
11540
|
}
|
|
11144
11541
|
}
|
|
@@ -11148,27 +11545,27 @@ function renderImageTable(models, verbose, stream) {
|
|
|
11148
11545
|
const nameWidth = 25;
|
|
11149
11546
|
const sizesWidth = 20;
|
|
11150
11547
|
const priceWidth = 15;
|
|
11151
|
-
stream.write(
|
|
11548
|
+
stream.write(chalk10.dim("\u2500".repeat(idWidth + nameWidth + sizesWidth + priceWidth + 6)) + "\n");
|
|
11152
11549
|
stream.write(
|
|
11153
|
-
|
|
11550
|
+
chalk10.bold(
|
|
11154
11551
|
"Model ID".padEnd(idWidth) + " " + "Display Name".padEnd(nameWidth) + " " + "Sizes".padEnd(sizesWidth) + " " + "Price".padEnd(priceWidth)
|
|
11155
11552
|
) + "\n"
|
|
11156
11553
|
);
|
|
11157
|
-
stream.write(
|
|
11554
|
+
stream.write(chalk10.dim("\u2500".repeat(idWidth + nameWidth + sizesWidth + priceWidth + 6)) + "\n");
|
|
11158
11555
|
for (const model of providerModels) {
|
|
11159
11556
|
const sizes = model.supportedSizes.length > 2 ? model.supportedSizes.slice(0, 2).join(", ") + "..." : model.supportedSizes.join(", ");
|
|
11160
11557
|
stream.write(
|
|
11161
|
-
|
|
11558
|
+
chalk10.green(model.modelId.padEnd(idWidth)) + " " + chalk10.white(model.displayName.substring(0, nameWidth - 1).padEnd(nameWidth)) + " " + chalk10.yellow(sizes.padEnd(sizesWidth)) + " " + chalk10.cyan(formatImagePrice(model).padEnd(priceWidth)) + "\n"
|
|
11162
11559
|
);
|
|
11163
11560
|
}
|
|
11164
|
-
stream.write(
|
|
11561
|
+
stream.write(chalk10.dim("\u2500".repeat(idWidth + nameWidth + sizesWidth + priceWidth + 6)) + "\n");
|
|
11165
11562
|
}
|
|
11166
11563
|
stream.write("\n");
|
|
11167
11564
|
}
|
|
11168
11565
|
}
|
|
11169
11566
|
function renderSpeechTable(models, verbose, stream) {
|
|
11170
|
-
stream.write(
|
|
11171
|
-
stream.write(
|
|
11567
|
+
stream.write(chalk10.bold.magenta("\u{1F3A4} Speech (TTS) Models\n"));
|
|
11568
|
+
stream.write(chalk10.dim("\u2500".repeat(80)) + "\n\n");
|
|
11172
11569
|
const grouped = /* @__PURE__ */ new Map();
|
|
11173
11570
|
for (const model of models) {
|
|
11174
11571
|
if (!grouped.has(model.provider)) {
|
|
@@ -11178,34 +11575,34 @@ function renderSpeechTable(models, verbose, stream) {
|
|
|
11178
11575
|
}
|
|
11179
11576
|
for (const [provider, providerModels] of Array.from(grouped.entries()).sort()) {
|
|
11180
11577
|
const providerName = provider.charAt(0).toUpperCase() + provider.slice(1);
|
|
11181
|
-
stream.write(
|
|
11578
|
+
stream.write(chalk10.bold.yellow(`${providerName}
|
|
11182
11579
|
`));
|
|
11183
11580
|
if (verbose) {
|
|
11184
11581
|
for (const model of providerModels) {
|
|
11185
|
-
stream.write(
|
|
11582
|
+
stream.write(chalk10.bold.green(`
|
|
11186
11583
|
${model.modelId}
|
|
11187
11584
|
`));
|
|
11188
|
-
stream.write(
|
|
11189
|
-
stream.write(` ${
|
|
11585
|
+
stream.write(chalk10.dim(" " + "\u2500".repeat(60)) + "\n");
|
|
11586
|
+
stream.write(` ${chalk10.dim("Name:")} ${chalk10.white(model.displayName)}
|
|
11190
11587
|
`);
|
|
11191
11588
|
stream.write(
|
|
11192
|
-
` ${
|
|
11589
|
+
` ${chalk10.dim("Voices:")} ${chalk10.yellow(model.voices.length.toString())} voices
|
|
11193
11590
|
`
|
|
11194
11591
|
);
|
|
11195
11592
|
if (model.voices.length <= 6) {
|
|
11196
|
-
stream.write(` ${
|
|
11593
|
+
stream.write(` ${chalk10.dim(model.voices.join(", "))}
|
|
11197
11594
|
`);
|
|
11198
11595
|
} else {
|
|
11199
|
-
stream.write(` ${
|
|
11596
|
+
stream.write(` ${chalk10.dim(model.voices.slice(0, 6).join(", ") + "...")}
|
|
11200
11597
|
`);
|
|
11201
11598
|
}
|
|
11202
|
-
stream.write(` ${
|
|
11599
|
+
stream.write(` ${chalk10.dim("Formats:")} ${chalk10.yellow(model.formats.join(", "))}
|
|
11203
11600
|
`);
|
|
11204
11601
|
stream.write(
|
|
11205
|
-
` ${
|
|
11602
|
+
` ${chalk10.dim("Max Input:")} ${chalk10.yellow(model.maxInputLength.toString())} chars
|
|
11206
11603
|
`
|
|
11207
11604
|
);
|
|
11208
|
-
stream.write(` ${
|
|
11605
|
+
stream.write(` ${chalk10.dim("Pricing:")} ${chalk10.cyan(formatSpeechPrice(model))}
|
|
11209
11606
|
`);
|
|
11210
11607
|
if (model.features) {
|
|
11211
11608
|
const features = [];
|
|
@@ -11213,7 +11610,7 @@ function renderSpeechTable(models, verbose, stream) {
|
|
|
11213
11610
|
if (model.features.voiceInstructions) features.push("voice-instructions");
|
|
11214
11611
|
if (model.features.languages) features.push(`${model.features.languages} languages`);
|
|
11215
11612
|
if (features.length > 0) {
|
|
11216
|
-
stream.write(` ${
|
|
11613
|
+
stream.write(` ${chalk10.dim("Features:")} ${chalk10.blue(features.join(", "))}
|
|
11217
11614
|
`);
|
|
11218
11615
|
}
|
|
11219
11616
|
}
|
|
@@ -11224,23 +11621,23 @@ function renderSpeechTable(models, verbose, stream) {
|
|
|
11224
11621
|
const voicesWidth = 12;
|
|
11225
11622
|
const priceWidth = 18;
|
|
11226
11623
|
stream.write(
|
|
11227
|
-
|
|
11624
|
+
chalk10.dim("\u2500".repeat(idWidth + nameWidth + voicesWidth + priceWidth + 6)) + "\n"
|
|
11228
11625
|
);
|
|
11229
11626
|
stream.write(
|
|
11230
|
-
|
|
11627
|
+
chalk10.bold(
|
|
11231
11628
|
"Model ID".padEnd(idWidth) + " " + "Display Name".padEnd(nameWidth) + " " + "Voices".padEnd(voicesWidth) + " " + "Price".padEnd(priceWidth)
|
|
11232
11629
|
) + "\n"
|
|
11233
11630
|
);
|
|
11234
11631
|
stream.write(
|
|
11235
|
-
|
|
11632
|
+
chalk10.dim("\u2500".repeat(idWidth + nameWidth + voicesWidth + priceWidth + 6)) + "\n"
|
|
11236
11633
|
);
|
|
11237
11634
|
for (const model of providerModels) {
|
|
11238
11635
|
stream.write(
|
|
11239
|
-
|
|
11636
|
+
chalk10.green(model.modelId.padEnd(idWidth)) + " " + chalk10.white(model.displayName.substring(0, nameWidth - 1).padEnd(nameWidth)) + " " + chalk10.yellow(`${model.voices.length} voices`.padEnd(voicesWidth)) + " " + chalk10.cyan(formatSpeechPrice(model).padEnd(priceWidth)) + "\n"
|
|
11240
11637
|
);
|
|
11241
11638
|
}
|
|
11242
11639
|
stream.write(
|
|
11243
|
-
|
|
11640
|
+
chalk10.dim("\u2500".repeat(idWidth + nameWidth + voicesWidth + priceWidth + 6)) + "\n"
|
|
11244
11641
|
);
|
|
11245
11642
|
}
|
|
11246
11643
|
stream.write("\n");
|