@kernelonpanic/kitcode 1.3.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/README.ru.md +3 -2
- package/dist/index.js +680 -255
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -210,8 +210,8 @@ async function readHead(stream) {
|
|
|
210
210
|
let text = "";
|
|
211
211
|
try {
|
|
212
212
|
while (text.length < CAPTURE_CHARS) {
|
|
213
|
-
const { done, value } = await reader.read();
|
|
214
|
-
if (
|
|
213
|
+
const { done: done2, value } = await reader.read();
|
|
214
|
+
if (done2) break;
|
|
215
215
|
text += decoder.decode(value, { stream: true });
|
|
216
216
|
}
|
|
217
217
|
} catch {
|
|
@@ -420,39 +420,52 @@ async function detectProvider(rawUrl, apiKey2, opts = {}) {
|
|
|
420
420
|
`${anthropicBaseUrl}/v1/models?limit=1000`,
|
|
421
421
|
anthropicHeaders(apiKey2)
|
|
422
422
|
);
|
|
423
|
-
return
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
423
|
+
return [
|
|
424
|
+
{
|
|
425
|
+
id,
|
|
426
|
+
config: { type: "anthropic", baseUrl: anthropicBaseUrl },
|
|
427
|
+
models: probed.ok ? parseModelList(probed.body) : []
|
|
428
|
+
}
|
|
429
|
+
];
|
|
428
430
|
}
|
|
431
|
+
const found = [];
|
|
429
432
|
let last;
|
|
430
433
|
for (const candidate of candidateBaseUrls(baseUrl)) {
|
|
434
|
+
if (found.length === schemes.length) break;
|
|
431
435
|
const url = `${candidate}/models`;
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
if (attempt.ok && isModelListBody(attempt.body)) {
|
|
435
|
-
const anthropic = advertisesAnthropicProtocol(attempt.body);
|
|
436
|
-
return {
|
|
437
|
-
id,
|
|
438
|
-
config: anthropic ? { type: "anthropic", baseUrl: anthropicRoot(candidate) } : { type: "openai", baseUrl: candidate },
|
|
439
|
-
models: parseModelList(attempt.body)
|
|
440
|
-
};
|
|
441
|
-
}
|
|
442
|
-
if (attempt.status === 401 || attempt.status === 403) {
|
|
443
|
-
attempt = await probe(fetchImpl, url, anthropicHeaders(apiKey2));
|
|
436
|
+
for (const scheme of schemes) {
|
|
437
|
+
const attempt = await probe(fetchImpl, url, schemeHeaders(scheme, apiKey2));
|
|
444
438
|
last = attempt;
|
|
445
|
-
if (attempt.
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
config: { type: "anthropic", baseUrl: anthropicRoot(candidate) },
|
|
449
|
-
models: parseModelList(attempt.body)
|
|
450
|
-
};
|
|
451
|
-
}
|
|
439
|
+
if (attempt.status === 404 || attempt.status === 0) break;
|
|
440
|
+
if (attempt.ok && isModelListBody(attempt.body)) record(found, candidate, scheme, attempt.body);
|
|
441
|
+
if (found.length === schemes.length) break;
|
|
452
442
|
}
|
|
453
|
-
if (!
|
|
443
|
+
if (found.length === 0 && !last.ok && last.status !== 404 && last.status !== 0) break;
|
|
454
444
|
}
|
|
455
|
-
throw new Error(describeFailure(last, apiKey2));
|
|
445
|
+
if (found.length === 0) throw new Error(describeFailure(last, apiKey2));
|
|
446
|
+
return found.map((entry, index) => ({
|
|
447
|
+
id: index === 0 ? id : `${id.slice(0, MAX_ID_STEM)}-${entry.type}`,
|
|
448
|
+
config: { type: entry.type, baseUrl: entry.baseUrl },
|
|
449
|
+
models: entry.models
|
|
450
|
+
}));
|
|
451
|
+
}
|
|
452
|
+
var schemes = ["bearer", "anthropic"];
|
|
453
|
+
var MAX_ID_STEM = 120;
|
|
454
|
+
function schemeHeaders(scheme, apiKey2) {
|
|
455
|
+
return scheme === "bearer" ? { Authorization: `Bearer ${apiKey2}` } : anthropicHeaders(apiKey2);
|
|
456
|
+
}
|
|
457
|
+
function record(found, candidate, scheme, body) {
|
|
458
|
+
const type = scheme === "anthropic" || advertisesAnthropicProtocol(body) ? "anthropic" : "openai";
|
|
459
|
+
if (found.some((entry) => entry.type === type)) return;
|
|
460
|
+
const models = parseModelList(body);
|
|
461
|
+
const fingerprint2 = models.map((model) => model.id).sort().join("\n");
|
|
462
|
+
if (found.some((entry) => entry.fingerprint === fingerprint2)) return;
|
|
463
|
+
found.push({
|
|
464
|
+
type,
|
|
465
|
+
baseUrl: type === "anthropic" ? anthropicRoot(candidate) : candidate,
|
|
466
|
+
models,
|
|
467
|
+
fingerprint: fingerprint2
|
|
468
|
+
});
|
|
456
469
|
}
|
|
457
470
|
function normaliseBaseUrl(rawUrl) {
|
|
458
471
|
return rawUrl.trim().replace(/\/+$/, "");
|
|
@@ -519,8 +532,8 @@ async function boundedResponseText(response) {
|
|
|
519
532
|
let text = "";
|
|
520
533
|
try {
|
|
521
534
|
for (; ; ) {
|
|
522
|
-
const { done, value } = await reader.read();
|
|
523
|
-
if (
|
|
535
|
+
const { done: done2, value } = await reader.read();
|
|
536
|
+
if (done2) break;
|
|
524
537
|
bytes += value.byteLength;
|
|
525
538
|
if (bytes > MAX_DETECT_BODY_BYTES) {
|
|
526
539
|
throw new Error(`response exceeded ${MAX_DETECT_BODY_BYTES / 1e6} MB`);
|
|
@@ -566,11 +579,9 @@ function anthropicRoot(baseUrl) {
|
|
|
566
579
|
function advertisesAnthropicProtocol(body) {
|
|
567
580
|
const entries = modelEntries(body);
|
|
568
581
|
if (entries.length === 0) return false;
|
|
569
|
-
const
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
if (endpointTypes.size > 0) {
|
|
573
|
-
return endpointTypes.has("anthropic") && !endpointTypes.has("openai");
|
|
582
|
+
const advertised = entries.map((entry) => stringList(entry["supported_endpoint_types"]));
|
|
583
|
+
if (advertised.some((types) => types.length > 0)) {
|
|
584
|
+
return advertised.every((types) => types.includes("anthropic"));
|
|
574
585
|
}
|
|
575
586
|
if (entries.some(isVendorNamespaced)) return false;
|
|
576
587
|
return entries.every((entry) => entry["owned_by"] === "anthropic");
|
|
@@ -827,9 +838,9 @@ function conventionalKeyEnv(providerId) {
|
|
|
827
838
|
function expandEnvRefs(value) {
|
|
828
839
|
return value.replace(/\$\{env:([^}]+)\}/g, (_match, name) => process.env[name] ?? "");
|
|
829
840
|
}
|
|
830
|
-
function expandEnvRecord(
|
|
841
|
+
function expandEnvRecord(record3) {
|
|
831
842
|
return Object.fromEntries(
|
|
832
|
-
Object.entries(
|
|
843
|
+
Object.entries(record3).map(([key, value]) => [key, expandEnvRefs(value)])
|
|
833
844
|
);
|
|
834
845
|
}
|
|
835
846
|
async function readJson(file) {
|
|
@@ -895,12 +906,15 @@ async function addProvider(url, key, options = {}) {
|
|
|
895
906
|
const auth = await loadAuth();
|
|
896
907
|
const configBefore = structuredClone(config);
|
|
897
908
|
const authBefore = { ...auth };
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
909
|
+
for (const provider of detected) {
|
|
910
|
+
config.providers[provider.id] = provider.config;
|
|
911
|
+
auth[provider.id] = key;
|
|
912
|
+
}
|
|
913
|
+
const primary = detected[0];
|
|
914
|
+
const models = detected.flatMap((provider) => provider.models);
|
|
901
915
|
if (!config.model) {
|
|
902
|
-
const chosen =
|
|
903
|
-
if (chosen) config.model =
|
|
916
|
+
const chosen = pickDefaultRef(detected);
|
|
917
|
+
if (chosen) config.model = chosen;
|
|
904
918
|
}
|
|
905
919
|
try {
|
|
906
920
|
await saveConfig(config);
|
|
@@ -913,14 +927,15 @@ async function addProvider(url, key, options = {}) {
|
|
|
913
927
|
throw error;
|
|
914
928
|
}
|
|
915
929
|
const location = await configLocation();
|
|
916
|
-
const
|
|
917
|
-
|
|
918
|
-
|
|
930
|
+
const rows = detected.map((provider) => [
|
|
931
|
+
"provider",
|
|
932
|
+
`${provider.id} \u2014 this endpoint speaks the ${protocolLabel(provider)} protocol`
|
|
933
|
+
]);
|
|
934
|
+
rows.push(
|
|
919
935
|
["models", models.length === 0 ? "none listed by this endpoint" : `${models.length} available`],
|
|
920
|
-
["default", describeDefault(config.model, detected
|
|
921
|
-
|
|
922
|
-
alternatives(
|
|
923
|
-
const ref = formatModelRef(detected.id, model.id);
|
|
936
|
+
["default", describeDefault(config.model, detected)]
|
|
937
|
+
);
|
|
938
|
+
alternatives(detected, config.model).forEach(([ref, model], index) => {
|
|
924
939
|
rows.push([index === 0 ? "also try" : "", `${ref} ${price(model)}`]);
|
|
925
940
|
});
|
|
926
941
|
rows.push(["config", `${location.path} (${location.scope})`]);
|
|
@@ -928,11 +943,15 @@ async function addProvider(url, key, options = {}) {
|
|
|
928
943
|
rows.push(["next", "run kitcode to start a session; /model switches models"]);
|
|
929
944
|
for (const [label, value] of rows) console.log(label.padEnd(LABEL_WIDTH) + value);
|
|
930
945
|
}
|
|
931
|
-
function
|
|
946
|
+
function protocolLabel(provider) {
|
|
947
|
+
return provider.config.type === "anthropic" ? "Anthropic" : "OpenAI-compatible";
|
|
948
|
+
}
|
|
949
|
+
function describeDefault(ref, detected) {
|
|
932
950
|
if (!ref) return "not set \u2014 start kitcode and pick one with /model";
|
|
933
951
|
const parsed = parseModelRef(ref);
|
|
934
|
-
|
|
935
|
-
return `${ref}
|
|
952
|
+
const owner = detected.find((provider) => provider.id === parsed?.provider);
|
|
953
|
+
if (!owner) return `${ref} \u2014 already in your config, left alone`;
|
|
954
|
+
return `${ref} ${price(owner.models.find((model) => model.id === parsed.model))}`;
|
|
936
955
|
}
|
|
937
956
|
function price(model) {
|
|
938
957
|
const pricing = model?.pricing;
|
|
@@ -942,25 +961,35 @@ function price(model) {
|
|
|
942
961
|
function money(value) {
|
|
943
962
|
return `$${value >= 1 ? value.toFixed(2) : Number(value.toFixed(4))}`;
|
|
944
963
|
}
|
|
945
|
-
function
|
|
964
|
+
function pickDefaultRef(detected) {
|
|
965
|
+
const refs = modelRefs(detected);
|
|
946
966
|
for (const preferred of PREFERRED) {
|
|
947
|
-
const match =
|
|
948
|
-
if (match) return match
|
|
967
|
+
const match = refs.find(([, model]) => model.id.endsWith(preferred));
|
|
968
|
+
if (match) return match[0];
|
|
949
969
|
}
|
|
950
|
-
return
|
|
970
|
+
return refs[0]?.[0];
|
|
951
971
|
}
|
|
952
|
-
function alternatives(
|
|
972
|
+
function alternatives(detected, defaultRef) {
|
|
973
|
+
const refs = modelRefs(detected);
|
|
953
974
|
const ranked = [
|
|
954
|
-
...PREFERRED.flatMap((preferred) =>
|
|
955
|
-
...
|
|
956
|
-
...
|
|
975
|
+
...PREFERRED.flatMap((preferred) => refs.filter(([, model]) => model.id.endsWith(preferred))),
|
|
976
|
+
...refs.filter(([, model]) => model.pricing),
|
|
977
|
+
...refs
|
|
957
978
|
];
|
|
958
979
|
const picked = /* @__PURE__ */ new Map();
|
|
959
|
-
for (const model of ranked) {
|
|
980
|
+
for (const [ref, model] of ranked) {
|
|
960
981
|
if (picked.size === ALTERNATIVES) break;
|
|
961
|
-
if (
|
|
982
|
+
if (ref !== defaultRef) picked.set(ref, model);
|
|
962
983
|
}
|
|
963
|
-
return [...picked.
|
|
984
|
+
return [...picked.entries()];
|
|
985
|
+
}
|
|
986
|
+
function modelRefs(detected) {
|
|
987
|
+
return detected.flatMap(
|
|
988
|
+
(provider) => provider.models.map((model) => [
|
|
989
|
+
formatModelRef(provider.id, model.id),
|
|
990
|
+
model
|
|
991
|
+
])
|
|
992
|
+
);
|
|
964
993
|
}
|
|
965
994
|
|
|
966
995
|
// src/app/ask.ts
|
|
@@ -1087,6 +1116,261 @@ ${store.read()}`, display: { kind: "diff", path: "project memory", before, after
|
|
|
1087
1116
|
};
|
|
1088
1117
|
}
|
|
1089
1118
|
|
|
1119
|
+
// src/ui/workspace-files.ts
|
|
1120
|
+
import fg2 from "fast-glob";
|
|
1121
|
+
|
|
1122
|
+
// src/tools/glob.ts
|
|
1123
|
+
import { join } from "path";
|
|
1124
|
+
import fg from "fast-glob";
|
|
1125
|
+
|
|
1126
|
+
// src/tools/safepath.ts
|
|
1127
|
+
import { realpathSync } from "fs";
|
|
1128
|
+
import { basename, dirname, isAbsolute, relative, resolve, sep } from "path";
|
|
1129
|
+
function canonicalize(target) {
|
|
1130
|
+
try {
|
|
1131
|
+
return realpathSync(target);
|
|
1132
|
+
} catch {
|
|
1133
|
+
let current = target;
|
|
1134
|
+
const trailing = [];
|
|
1135
|
+
for (let i = 0; i < 64; i++) {
|
|
1136
|
+
try {
|
|
1137
|
+
return resolve(realpathSync(current), ...trailing);
|
|
1138
|
+
} catch {
|
|
1139
|
+
const parent = dirname(current);
|
|
1140
|
+
if (parent === current) break;
|
|
1141
|
+
trailing.unshift(basename(current));
|
|
1142
|
+
current = parent;
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
return resolve(current, ...trailing);
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
function resolveInside(cwd, userPath) {
|
|
1149
|
+
const root = canonicalize(resolve(cwd));
|
|
1150
|
+
const target = canonicalize(resolve(root, userPath));
|
|
1151
|
+
const rel = relative(root, target);
|
|
1152
|
+
if (rel !== "" && (isAbsolute(rel) || rel.split(sep)[0] === "..")) {
|
|
1153
|
+
return { ok: false, reason: `Path ${userPath} resolves outside the workspace root ${root}` };
|
|
1154
|
+
}
|
|
1155
|
+
return { ok: true, path: target, relative: rel };
|
|
1156
|
+
}
|
|
1157
|
+
function patternEscapes(pattern) {
|
|
1158
|
+
return isAbsolute(pattern) || pattern.split("/").includes("..") || pattern.split("\\").includes("..");
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
// src/tools/glob.ts
|
|
1162
|
+
var MAX_RESULTS = 500;
|
|
1163
|
+
var IGNORED_DIRECTORIES = ["**/node_modules/**", "**/.git/**", "**/dist/**"];
|
|
1164
|
+
var globTool = {
|
|
1165
|
+
name: "glob",
|
|
1166
|
+
description: 'Find files by glob pattern, most recently modified first. Supports patterns like "src/**/*.ts". Hidden files, node_modules, .git and dist are skipped.',
|
|
1167
|
+
inputSchema: {
|
|
1168
|
+
type: "object",
|
|
1169
|
+
properties: {
|
|
1170
|
+
pattern: { type: "string", description: "Glob pattern, e.g. **/*.test.ts" },
|
|
1171
|
+
path: { type: "string", description: "Directory to search in, relative to the workspace root" }
|
|
1172
|
+
},
|
|
1173
|
+
required: ["pattern"],
|
|
1174
|
+
additionalProperties: false
|
|
1175
|
+
},
|
|
1176
|
+
defaultPermission: "allow",
|
|
1177
|
+
readOnly: true,
|
|
1178
|
+
summarize(input) {
|
|
1179
|
+
return `glob(${brief(input.pattern)})`;
|
|
1180
|
+
},
|
|
1181
|
+
async execute(input, ctx) {
|
|
1182
|
+
const { pattern, path: path15 = "." } = input;
|
|
1183
|
+
if (patternEscapes(pattern)) {
|
|
1184
|
+
return { content: `Pattern ${pattern} must be relative to the workspace root.`, isError: true };
|
|
1185
|
+
}
|
|
1186
|
+
const safe = resolveInside(ctx.cwd, path15);
|
|
1187
|
+
if (!safe.ok) return { content: safe.reason, isError: true };
|
|
1188
|
+
const entries = await fg(pattern, {
|
|
1189
|
+
cwd: safe.path,
|
|
1190
|
+
dot: false,
|
|
1191
|
+
onlyFiles: true,
|
|
1192
|
+
followSymbolicLinks: false,
|
|
1193
|
+
suppressErrors: true,
|
|
1194
|
+
ignore: IGNORED_DIRECTORIES,
|
|
1195
|
+
stats: true
|
|
1196
|
+
});
|
|
1197
|
+
if (entries.length === 0) return { content: `No files matched ${pattern}` };
|
|
1198
|
+
entries.sort((a, b) => (b.stats?.mtimeMs ?? 0) - (a.stats?.mtimeMs ?? 0));
|
|
1199
|
+
const paths = entries.slice(0, MAX_RESULTS).map((entry) => join(safe.relative, entry.path));
|
|
1200
|
+
if (entries.length > MAX_RESULTS) {
|
|
1201
|
+
paths.push(`... truncated: ${entries.length - MAX_RESULTS} more files matched.`);
|
|
1202
|
+
}
|
|
1203
|
+
return { content: paths.join("\n") };
|
|
1204
|
+
}
|
|
1205
|
+
};
|
|
1206
|
+
|
|
1207
|
+
// src/ui/workspace-files.ts
|
|
1208
|
+
var MAX_SCANNED = 2e4;
|
|
1209
|
+
var MAX_RESULTS2 = 12;
|
|
1210
|
+
var CACHE_TTL_MS = 1e4;
|
|
1211
|
+
var cached;
|
|
1212
|
+
var inFlight;
|
|
1213
|
+
async function searchWorkspaceFiles(cwd, query) {
|
|
1214
|
+
const files = await workspaceFiles(cwd);
|
|
1215
|
+
const needle = query.trim().toLowerCase();
|
|
1216
|
+
if (needle === "") return files.slice(0, MAX_RESULTS2);
|
|
1217
|
+
const exact = [];
|
|
1218
|
+
const loose = [];
|
|
1219
|
+
for (const file of files) {
|
|
1220
|
+
const haystack = file.toLowerCase();
|
|
1221
|
+
if (haystack.includes(needle)) exact.push(file);
|
|
1222
|
+
else if (isSubsequence(needle, haystack)) loose.push(file);
|
|
1223
|
+
if (exact.length >= MAX_RESULTS2) break;
|
|
1224
|
+
}
|
|
1225
|
+
exact.sort((a, b) => score(a, needle) - score(b, needle));
|
|
1226
|
+
return [...exact, ...loose].slice(0, MAX_RESULTS2);
|
|
1227
|
+
}
|
|
1228
|
+
function workspaceFiles(cwd) {
|
|
1229
|
+
const fresh = cached && cached.cwd === cwd && Date.now() - cached.at < CACHE_TTL_MS;
|
|
1230
|
+
if (fresh) return Promise.resolve(cached.files);
|
|
1231
|
+
if (inFlight) return inFlight;
|
|
1232
|
+
inFlight = fg2("**/*", {
|
|
1233
|
+
cwd,
|
|
1234
|
+
dot: false,
|
|
1235
|
+
onlyFiles: true,
|
|
1236
|
+
followSymbolicLinks: false,
|
|
1237
|
+
suppressErrors: true,
|
|
1238
|
+
ignore: IGNORED_DIRECTORIES
|
|
1239
|
+
}).then((entries) => {
|
|
1240
|
+
const files = entries.slice(0, MAX_SCANNED).sort((a, b) => a.length - b.length);
|
|
1241
|
+
cached = { cwd, files, at: Date.now() };
|
|
1242
|
+
return files;
|
|
1243
|
+
}).catch(() => []).finally(() => {
|
|
1244
|
+
inFlight = void 0;
|
|
1245
|
+
});
|
|
1246
|
+
return inFlight;
|
|
1247
|
+
}
|
|
1248
|
+
function score(file, needle) {
|
|
1249
|
+
const basename3 = file.slice(file.lastIndexOf("/") + 1).toLowerCase();
|
|
1250
|
+
if (basename3.startsWith(needle)) return 0;
|
|
1251
|
+
if (basename3.includes(needle)) return 1;
|
|
1252
|
+
return 2;
|
|
1253
|
+
}
|
|
1254
|
+
function isSubsequence(needle, haystack) {
|
|
1255
|
+
let index = 0;
|
|
1256
|
+
for (const character of haystack) {
|
|
1257
|
+
if (character === needle[index]) index += 1;
|
|
1258
|
+
if (index === needle.length) return true;
|
|
1259
|
+
}
|
|
1260
|
+
return needle.length === 0;
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
// src/tools/jobs.ts
|
|
1264
|
+
import { spawn } from "child_process";
|
|
1265
|
+
var MAX_JOBS = 8;
|
|
1266
|
+
var MAX_JOB_OUTPUT = 1e5;
|
|
1267
|
+
var jobs = /* @__PURE__ */ new Map();
|
|
1268
|
+
var counter = 0;
|
|
1269
|
+
function startJob(command, cwd) {
|
|
1270
|
+
const live = [...jobs.values()].filter((job2) => job2.state === "running");
|
|
1271
|
+
if (live.length >= MAX_JOBS) {
|
|
1272
|
+
return { error: `At most ${MAX_JOBS} background commands can run at once.` };
|
|
1273
|
+
}
|
|
1274
|
+
counter += 1;
|
|
1275
|
+
const id = `bg-${counter}`;
|
|
1276
|
+
const child = spawn(command, {
|
|
1277
|
+
shell: true,
|
|
1278
|
+
cwd,
|
|
1279
|
+
detached: process.platform !== "win32",
|
|
1280
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1281
|
+
windowsHide: true
|
|
1282
|
+
});
|
|
1283
|
+
const job = {
|
|
1284
|
+
id,
|
|
1285
|
+
command,
|
|
1286
|
+
state: "running",
|
|
1287
|
+
exitCode: null,
|
|
1288
|
+
signal: null,
|
|
1289
|
+
child,
|
|
1290
|
+
buffer: "",
|
|
1291
|
+
reported: 0,
|
|
1292
|
+
truncated: false
|
|
1293
|
+
};
|
|
1294
|
+
jobs.set(id, job);
|
|
1295
|
+
const collect = (chunk) => {
|
|
1296
|
+
const text = chunk.toString("utf8");
|
|
1297
|
+
const room = MAX_JOB_OUTPUT - job.buffer.length;
|
|
1298
|
+
if (text.length <= room) {
|
|
1299
|
+
job.buffer += text;
|
|
1300
|
+
return;
|
|
1301
|
+
}
|
|
1302
|
+
job.truncated = true;
|
|
1303
|
+
job.buffer = (job.buffer + text).slice(-MAX_JOB_OUTPUT);
|
|
1304
|
+
job.reported = Math.min(job.reported, job.buffer.length);
|
|
1305
|
+
};
|
|
1306
|
+
child.stdout?.on("data", collect);
|
|
1307
|
+
child.stderr?.on("data", collect);
|
|
1308
|
+
child.on("error", (error) => {
|
|
1309
|
+
job.buffer += `
|
|
1310
|
+
Command failed to start: ${error.message}`;
|
|
1311
|
+
job.state = "exited";
|
|
1312
|
+
job.exitCode = job.exitCode ?? 1;
|
|
1313
|
+
});
|
|
1314
|
+
child.on("close", (code, signal) => {
|
|
1315
|
+
if (job.state !== "killed") job.state = "exited";
|
|
1316
|
+
job.exitCode = code;
|
|
1317
|
+
job.signal = signal;
|
|
1318
|
+
});
|
|
1319
|
+
return { id };
|
|
1320
|
+
}
|
|
1321
|
+
function readJob(id) {
|
|
1322
|
+
const job = jobs.get(id);
|
|
1323
|
+
if (!job) return void 0;
|
|
1324
|
+
const output = job.buffer.slice(job.reported);
|
|
1325
|
+
job.reported = job.buffer.length;
|
|
1326
|
+
return { ...summary(job), output, truncated: job.truncated };
|
|
1327
|
+
}
|
|
1328
|
+
function killJob(id) {
|
|
1329
|
+
const job = jobs.get(id);
|
|
1330
|
+
if (!job) return false;
|
|
1331
|
+
if (job.state === "running") {
|
|
1332
|
+
job.state = "killed";
|
|
1333
|
+
killTree(job.child);
|
|
1334
|
+
}
|
|
1335
|
+
return true;
|
|
1336
|
+
}
|
|
1337
|
+
function listJobs() {
|
|
1338
|
+
return [...jobs.values()].map(summary);
|
|
1339
|
+
}
|
|
1340
|
+
function killAllJobs() {
|
|
1341
|
+
for (const job of jobs.values()) {
|
|
1342
|
+
if (job.state === "running") {
|
|
1343
|
+
job.state = "killed";
|
|
1344
|
+
killTree(job.child);
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
function summary(job) {
|
|
1349
|
+
return {
|
|
1350
|
+
id: job.id,
|
|
1351
|
+
command: job.command,
|
|
1352
|
+
state: job.state,
|
|
1353
|
+
exitCode: job.exitCode,
|
|
1354
|
+
signal: job.signal
|
|
1355
|
+
};
|
|
1356
|
+
}
|
|
1357
|
+
function killTree(child) {
|
|
1358
|
+
if (child.pid == null) return;
|
|
1359
|
+
if (process.platform === "win32") {
|
|
1360
|
+
try {
|
|
1361
|
+
spawn("taskkill", ["/pid", String(child.pid), "/t", "/f"], { stdio: "ignore" });
|
|
1362
|
+
} catch {
|
|
1363
|
+
child.kill("SIGKILL");
|
|
1364
|
+
}
|
|
1365
|
+
return;
|
|
1366
|
+
}
|
|
1367
|
+
try {
|
|
1368
|
+
process.kill(-child.pid, "SIGKILL");
|
|
1369
|
+
} catch {
|
|
1370
|
+
child.kill("SIGKILL");
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1090
1374
|
// src/core/memory.ts
|
|
1091
1375
|
import { createHash } from "crypto";
|
|
1092
1376
|
import { readFile as readFile3, writeFile as writeFile3, rm as rm3 } from "fs/promises";
|
|
@@ -1778,7 +2062,7 @@ async function runOneCall(cfg, call, hooks, signal) {
|
|
|
1778
2062
|
if (!tool) {
|
|
1779
2063
|
return toolError(call.id, `Unknown tool "${call.name}". Use only the tools provided.`);
|
|
1780
2064
|
}
|
|
1781
|
-
const
|
|
2065
|
+
const summary2 = describeCall(tool, call.input);
|
|
1782
2066
|
const ctx = {
|
|
1783
2067
|
cwd: cfg.cwd,
|
|
1784
2068
|
signal,
|
|
@@ -1802,7 +2086,7 @@ async function runOneCall(cfg, call, hooks, signal) {
|
|
|
1802
2086
|
if (gate === "ask") {
|
|
1803
2087
|
const decision = await hooks.requestPermission({
|
|
1804
2088
|
toolName: tool.name,
|
|
1805
|
-
summary,
|
|
2089
|
+
summary: summary2,
|
|
1806
2090
|
input: call.input,
|
|
1807
2091
|
display: await previewCall(tool, call.input, ctx),
|
|
1808
2092
|
allowAlways: canAlwaysApprove(tool)
|
|
@@ -1815,7 +2099,7 @@ async function runOneCall(cfg, call, hooks, signal) {
|
|
|
1815
2099
|
if (signal.aborted) {
|
|
1816
2100
|
return toolError(call.id, "Interrupted by the user before this tool ran.");
|
|
1817
2101
|
}
|
|
1818
|
-
hooks.onEvent({ type: "tool_start", id: call.id, name: tool.name, summary });
|
|
2102
|
+
hooks.onEvent({ type: "tool_start", id: call.id, name: tool.name, summary: summary2 });
|
|
1819
2103
|
try {
|
|
1820
2104
|
const result = await tool.execute(call.input, ctx);
|
|
1821
2105
|
hooks.onEvent({
|
|
@@ -1987,43 +2271,6 @@ import {
|
|
|
1987
2271
|
writeFile as writeFile5
|
|
1988
2272
|
} from "fs/promises";
|
|
1989
2273
|
import path6 from "path";
|
|
1990
|
-
|
|
1991
|
-
// src/tools/safepath.ts
|
|
1992
|
-
import { realpathSync } from "fs";
|
|
1993
|
-
import { basename, dirname, isAbsolute, relative, resolve, sep } from "path";
|
|
1994
|
-
function canonicalize(target) {
|
|
1995
|
-
try {
|
|
1996
|
-
return realpathSync(target);
|
|
1997
|
-
} catch {
|
|
1998
|
-
let current = target;
|
|
1999
|
-
const trailing = [];
|
|
2000
|
-
for (let i = 0; i < 64; i++) {
|
|
2001
|
-
try {
|
|
2002
|
-
return resolve(realpathSync(current), ...trailing);
|
|
2003
|
-
} catch {
|
|
2004
|
-
const parent = dirname(current);
|
|
2005
|
-
if (parent === current) break;
|
|
2006
|
-
trailing.unshift(basename(current));
|
|
2007
|
-
current = parent;
|
|
2008
|
-
}
|
|
2009
|
-
}
|
|
2010
|
-
return resolve(current, ...trailing);
|
|
2011
|
-
}
|
|
2012
|
-
}
|
|
2013
|
-
function resolveInside(cwd, userPath) {
|
|
2014
|
-
const root = canonicalize(resolve(cwd));
|
|
2015
|
-
const target = canonicalize(resolve(root, userPath));
|
|
2016
|
-
const rel = relative(root, target);
|
|
2017
|
-
if (rel !== "" && (isAbsolute(rel) || rel.split(sep)[0] === "..")) {
|
|
2018
|
-
return { ok: false, reason: `Path ${userPath} resolves outside the workspace root ${root}` };
|
|
2019
|
-
}
|
|
2020
|
-
return { ok: true, path: target, relative: rel };
|
|
2021
|
-
}
|
|
2022
|
-
function patternEscapes(pattern) {
|
|
2023
|
-
return isAbsolute(pattern) || pattern.split("/").includes("..") || pattern.split("\\").includes("..");
|
|
2024
|
-
}
|
|
2025
|
-
|
|
2026
|
-
// src/core/checkpoint.ts
|
|
2027
2274
|
var VERSION = 1;
|
|
2028
2275
|
var MAX_FILE_BYTES = 5e6;
|
|
2029
2276
|
var MAX_CHECKPOINT_BYTES = 2e7;
|
|
@@ -2335,14 +2582,14 @@ import { readFile as readFile6, stat as stat4 } from "fs/promises";
|
|
|
2335
2582
|
import path7 from "path";
|
|
2336
2583
|
|
|
2337
2584
|
// src/tools/bash.ts
|
|
2338
|
-
import { spawn } from "child_process";
|
|
2585
|
+
import { spawn as spawn2 } from "child_process";
|
|
2339
2586
|
var DEFAULT_TIMEOUT = 12e4;
|
|
2340
2587
|
var MAX_TIMEOUT = 6e5;
|
|
2341
2588
|
var MAX_OUTPUT = 1e5;
|
|
2342
2589
|
var MAX_COMMAND_CHARS = 2e4;
|
|
2343
2590
|
var bashTool = {
|
|
2344
2591
|
name: "bash",
|
|
2345
|
-
description: "Run a shell command in the workspace root. stdout and stderr are merged. Avoid commands that wait for interactive input.",
|
|
2592
|
+
description: "Run a shell command in the workspace root. stdout and stderr are merged. Avoid commands that wait for interactive input. Set background: true to keep a long command (dev server, test watcher, build) running while you carry on with other work, then collect its output with bash_output.",
|
|
2346
2593
|
inputSchema: {
|
|
2347
2594
|
type: "object",
|
|
2348
2595
|
properties: {
|
|
@@ -2353,8 +2600,12 @@ var bashTool = {
|
|
|
2353
2600
|
},
|
|
2354
2601
|
timeoutMs: {
|
|
2355
2602
|
type: "integer",
|
|
2356
|
-
description: `Timeout in milliseconds (default ${DEFAULT_TIMEOUT}, maximum ${MAX_TIMEOUT})`,
|
|
2603
|
+
description: `Timeout in milliseconds (default ${DEFAULT_TIMEOUT}, maximum ${MAX_TIMEOUT}); ignored for background commands`,
|
|
2357
2604
|
minimum: 1
|
|
2605
|
+
},
|
|
2606
|
+
background: {
|
|
2607
|
+
type: "boolean",
|
|
2608
|
+
description: "Start the command and return at once instead of waiting for it. Read its output later with bash_output. Background commands have no timeout and are killed when the session ends."
|
|
2358
2609
|
}
|
|
2359
2610
|
},
|
|
2360
2611
|
required: ["command"],
|
|
@@ -2362,13 +2613,14 @@ var bashTool = {
|
|
|
2362
2613
|
},
|
|
2363
2614
|
defaultPermission: "ask",
|
|
2364
2615
|
summarize(input) {
|
|
2365
|
-
|
|
2616
|
+
const { command, background } = input;
|
|
2617
|
+
return background ? `bash(${brief(command)}, background)` : `bash(${brief(command)})`;
|
|
2366
2618
|
},
|
|
2367
2619
|
async preview(input) {
|
|
2368
2620
|
return { kind: "text", text: input.command };
|
|
2369
2621
|
},
|
|
2370
2622
|
execute(input, ctx) {
|
|
2371
|
-
const { command, timeoutMs } = input;
|
|
2623
|
+
const { command, timeoutMs, background } = input;
|
|
2372
2624
|
if (command.length > MAX_COMMAND_CHARS) {
|
|
2373
2625
|
return Promise.resolve({
|
|
2374
2626
|
content: `Command exceeds the ${MAX_COMMAND_CHARS} character limit.`,
|
|
@@ -2380,8 +2632,17 @@ var bashTool = {
|
|
|
2380
2632
|
if (ctx.signal.aborted) {
|
|
2381
2633
|
return Promise.resolve({ content: "[cancelled before the command started]", isError: true });
|
|
2382
2634
|
}
|
|
2635
|
+
if (background) {
|
|
2636
|
+
const started = startJob(command, ctx.cwd);
|
|
2637
|
+
if ("error" in started) {
|
|
2638
|
+
return Promise.resolve({ content: started.error, isError: true });
|
|
2639
|
+
}
|
|
2640
|
+
return Promise.resolve({
|
|
2641
|
+
content: `[started in the background as ${started.id}] Read its output with bash_output({ id: "${started.id}" }).`
|
|
2642
|
+
});
|
|
2643
|
+
}
|
|
2383
2644
|
return new Promise((settle) => {
|
|
2384
|
-
const child =
|
|
2645
|
+
const child = spawn2(command, {
|
|
2385
2646
|
shell: true,
|
|
2386
2647
|
cwd: ctx.cwd,
|
|
2387
2648
|
detached: process.platform !== "win32",
|
|
@@ -2409,11 +2670,11 @@ var bashTool = {
|
|
|
2409
2670
|
};
|
|
2410
2671
|
child.stdout.on("data", collect);
|
|
2411
2672
|
child.stderr.on("data", collect);
|
|
2412
|
-
const
|
|
2673
|
+
const killTree2 = () => {
|
|
2413
2674
|
if (child.pid == null) return;
|
|
2414
2675
|
if (process.platform === "win32") {
|
|
2415
2676
|
try {
|
|
2416
|
-
|
|
2677
|
+
spawn2("taskkill", ["/pid", String(child.pid), "/t", "/f"], { stdio: "ignore" });
|
|
2417
2678
|
} catch {
|
|
2418
2679
|
child.kill("SIGKILL");
|
|
2419
2680
|
}
|
|
@@ -2429,11 +2690,11 @@ var bashTool = {
|
|
|
2429
2690
|
let aborted = false;
|
|
2430
2691
|
const timer = setTimeout(() => {
|
|
2431
2692
|
timedOut = true;
|
|
2432
|
-
|
|
2693
|
+
killTree2();
|
|
2433
2694
|
}, timeout);
|
|
2434
2695
|
const onAbort = () => {
|
|
2435
2696
|
aborted = true;
|
|
2436
|
-
|
|
2697
|
+
killTree2();
|
|
2437
2698
|
};
|
|
2438
2699
|
ctx.signal.addEventListener("abort", onAbort, { once: true });
|
|
2439
2700
|
const finish = (result) => {
|
|
@@ -2495,20 +2756,20 @@ async function detectDiagnosticCommands(cwd, configured = []) {
|
|
|
2495
2756
|
}
|
|
2496
2757
|
async function runAutomaticDiagnostics(options) {
|
|
2497
2758
|
const commands = await detectDiagnosticCommands(options.cwd, options.configuredCommands);
|
|
2498
|
-
const
|
|
2759
|
+
const summary2 = { commands, failed: [], skipped: false };
|
|
2499
2760
|
if (options.changedFiles.length === 0 || commands.length === 0 || options.signal.aborted) {
|
|
2500
|
-
|
|
2501
|
-
return
|
|
2761
|
+
summary2.skipped = true;
|
|
2762
|
+
return summary2;
|
|
2502
2763
|
}
|
|
2503
2764
|
const gate = options.permissions.decide(diagnosticsTool);
|
|
2504
2765
|
if (gate === "deny") {
|
|
2505
|
-
|
|
2766
|
+
summary2.skipped = true;
|
|
2506
2767
|
options.hooks.onEvent({
|
|
2507
2768
|
type: "notice",
|
|
2508
2769
|
level: "warn",
|
|
2509
2770
|
text: "Automatic project checks were skipped by the current permission settings."
|
|
2510
2771
|
});
|
|
2511
|
-
return
|
|
2772
|
+
return summary2;
|
|
2512
2773
|
}
|
|
2513
2774
|
if (gate === "ask") {
|
|
2514
2775
|
const decision = await options.hooks.requestPermission({
|
|
@@ -2523,17 +2784,17 @@ async function runAutomaticDiagnostics(options) {
|
|
|
2523
2784
|
allowAlways: true
|
|
2524
2785
|
});
|
|
2525
2786
|
if (options.signal.aborted) {
|
|
2526
|
-
|
|
2527
|
-
return
|
|
2787
|
+
summary2.skipped = true;
|
|
2788
|
+
return summary2;
|
|
2528
2789
|
}
|
|
2529
2790
|
if (decision === "deny") {
|
|
2530
|
-
|
|
2791
|
+
summary2.skipped = true;
|
|
2531
2792
|
options.hooks.onEvent({
|
|
2532
2793
|
type: "notice",
|
|
2533
2794
|
level: "warn",
|
|
2534
2795
|
text: "Automatic project checks were skipped."
|
|
2535
2796
|
});
|
|
2536
|
-
return
|
|
2797
|
+
return summary2;
|
|
2537
2798
|
}
|
|
2538
2799
|
if (decision === "always") options.permissions.grantForSession(diagnosticsTool.name);
|
|
2539
2800
|
}
|
|
@@ -2546,7 +2807,7 @@ async function runAutomaticDiagnostics(options) {
|
|
|
2546
2807
|
const execute = options.execute ?? ((command, context) => bashTool.execute({ command, timeoutMs: DIAGNOSTIC_TIMEOUT_MS }, context));
|
|
2547
2808
|
for (const [index, command] of commands.entries()) {
|
|
2548
2809
|
if (options.signal.aborted) {
|
|
2549
|
-
|
|
2810
|
+
summary2.skipped = true;
|
|
2550
2811
|
break;
|
|
2551
2812
|
}
|
|
2552
2813
|
const id = `diagnostics-${Date.now()}-${index}`;
|
|
@@ -2554,7 +2815,7 @@ async function runAutomaticDiagnostics(options) {
|
|
|
2554
2815
|
options.hooks.onEvent({ type: "tool_start", id, name: diagnosticsTool.name, summary: callSummary });
|
|
2555
2816
|
try {
|
|
2556
2817
|
const result = await execute(command, ctx);
|
|
2557
|
-
if (result.isError)
|
|
2818
|
+
if (result.isError) summary2.failed.push(command);
|
|
2558
2819
|
options.hooks.onEvent({
|
|
2559
2820
|
type: "tool_end",
|
|
2560
2821
|
id,
|
|
@@ -2563,7 +2824,7 @@ async function runAutomaticDiagnostics(options) {
|
|
|
2563
2824
|
display: result.display
|
|
2564
2825
|
});
|
|
2565
2826
|
} catch (error) {
|
|
2566
|
-
|
|
2827
|
+
summary2.failed.push(command);
|
|
2567
2828
|
options.hooks.onEvent({
|
|
2568
2829
|
type: "tool_end",
|
|
2569
2830
|
id,
|
|
@@ -2572,7 +2833,7 @@ async function runAutomaticDiagnostics(options) {
|
|
|
2572
2833
|
});
|
|
2573
2834
|
}
|
|
2574
2835
|
}
|
|
2575
|
-
return
|
|
2836
|
+
return summary2;
|
|
2576
2837
|
}
|
|
2577
2838
|
async function readPackageJson(file) {
|
|
2578
2839
|
try {
|
|
@@ -2991,7 +3252,7 @@ async function compactHistory(options) {
|
|
|
2991
3252
|
if (!rendered.trim()) {
|
|
2992
3253
|
return { history: options.history, compacted: false, removedMessages: 0 };
|
|
2993
3254
|
}
|
|
2994
|
-
let
|
|
3255
|
+
let summary2;
|
|
2995
3256
|
let usage;
|
|
2996
3257
|
let rateLimits;
|
|
2997
3258
|
if (rendered.length <= SINGLE_REQUEST_MAX_CHARS) {
|
|
@@ -3005,7 +3266,7 @@ async function compactHistory(options) {
|
|
|
3005
3266
|
SYSTEM_SUMMARIZE,
|
|
3006
3267
|
requestMax
|
|
3007
3268
|
);
|
|
3008
|
-
|
|
3269
|
+
summary2 = result.text;
|
|
3009
3270
|
usage = result.usage;
|
|
3010
3271
|
rateLimits = result.rateLimits;
|
|
3011
3272
|
} else {
|
|
@@ -3040,19 +3301,19 @@ async function compactHistory(options) {
|
|
|
3040
3301
|
chunkResults.map((result) => result.text),
|
|
3041
3302
|
limits.merge
|
|
3042
3303
|
);
|
|
3043
|
-
|
|
3304
|
+
summary2 = mergeResult.text;
|
|
3044
3305
|
if (mergeResult.usage) {
|
|
3045
3306
|
usage = usage ? mergeUsage([usage, mergeResult.usage]) : mergeResult.usage;
|
|
3046
3307
|
}
|
|
3047
3308
|
if (mergeResult.rateLimits) rateLimits = mergeResult.rateLimits;
|
|
3048
3309
|
}
|
|
3049
|
-
|
|
3050
|
-
if (!
|
|
3310
|
+
summary2 = summary2.trim();
|
|
3311
|
+
if (!summary2) throw new Error("The provider returned an empty context summary.");
|
|
3051
3312
|
const prefix = [
|
|
3052
3313
|
{
|
|
3053
3314
|
role: "user",
|
|
3054
3315
|
content: [{ type: "text", text: `[Earlier conversation summary]
|
|
3055
|
-
${
|
|
3316
|
+
${summary2}` }]
|
|
3056
3317
|
},
|
|
3057
3318
|
{
|
|
3058
3319
|
role: "assistant",
|
|
@@ -3196,8 +3457,8 @@ async function mergePartialSummaries(provider, model, maxTokens, signal, summari
|
|
|
3196
3457
|
while (current.length > 1) {
|
|
3197
3458
|
const groups = groupSummaries(current, MERGE_TARGET_CHARS);
|
|
3198
3459
|
const sources = groups.map(
|
|
3199
|
-
(group) => group.map((
|
|
3200
|
-
${
|
|
3460
|
+
(group) => group.map((summary2, index) => `## Part ${index + 1}
|
|
3461
|
+
${summary2}`).join("\n\n")
|
|
3201
3462
|
);
|
|
3202
3463
|
const merged = await summarizeSourcesParallel(
|
|
3203
3464
|
provider,
|
|
@@ -3256,22 +3517,22 @@ function groupSummaries(summaries, targetChars) {
|
|
|
3256
3517
|
const groups = [];
|
|
3257
3518
|
let current = [];
|
|
3258
3519
|
let size = 0;
|
|
3259
|
-
for (const
|
|
3260
|
-
if (current.length >= 2 && size + 12 +
|
|
3520
|
+
for (const summary2 of summaries) {
|
|
3521
|
+
if (current.length >= 2 && size + 12 + summary2.length > targetChars) {
|
|
3261
3522
|
groups.push(current);
|
|
3262
3523
|
current = [];
|
|
3263
3524
|
size = 0;
|
|
3264
3525
|
}
|
|
3265
3526
|
const separator = current.length > 0 ? 12 : 0;
|
|
3266
|
-
current.push(
|
|
3267
|
-
size += separator +
|
|
3527
|
+
current.push(summary2);
|
|
3528
|
+
size += separator + summary2.length;
|
|
3268
3529
|
}
|
|
3269
3530
|
if (current.length > 0) groups.push(current);
|
|
3270
3531
|
return groups;
|
|
3271
3532
|
}
|
|
3272
3533
|
async function summarizeText(provider, model, maxTokens, signal, source, systemPrompt, maxOutputTokens) {
|
|
3273
3534
|
if (signal.aborted) throw signal.reason ?? new Error("Context compaction was cancelled.");
|
|
3274
|
-
let
|
|
3535
|
+
let summary2 = "";
|
|
3275
3536
|
let finalText2 = "";
|
|
3276
3537
|
let usage;
|
|
3277
3538
|
let rateLimits;
|
|
@@ -3285,7 +3546,7 @@ async function summarizeText(provider, model, maxTokens, signal, source, systemP
|
|
|
3285
3546
|
signal
|
|
3286
3547
|
});
|
|
3287
3548
|
for await (const event of stream) {
|
|
3288
|
-
if (event.type === "text_delta")
|
|
3549
|
+
if (event.type === "text_delta") summary2 += event.text;
|
|
3289
3550
|
else if (event.type === "usage") usage = event.usage;
|
|
3290
3551
|
else if (event.type === "rate_limits") rateLimits = event.limits;
|
|
3291
3552
|
else if (event.type === "done") {
|
|
@@ -3293,7 +3554,7 @@ async function summarizeText(provider, model, maxTokens, signal, source, systemP
|
|
|
3293
3554
|
}
|
|
3294
3555
|
}
|
|
3295
3556
|
if (signal.aborted) throw signal.reason ?? new Error("Context compaction was cancelled.");
|
|
3296
|
-
return { text: (finalText2 ||
|
|
3557
|
+
return { text: (finalText2 || summary2).trim(), usage, rateLimits };
|
|
3297
3558
|
}
|
|
3298
3559
|
function mergeUsage(usages) {
|
|
3299
3560
|
return usages.reduce(
|
|
@@ -3637,7 +3898,7 @@ function clip(value) {
|
|
|
3637
3898
|
// package.json
|
|
3638
3899
|
var package_default = {
|
|
3639
3900
|
name: "@kernelonpanic/kitcode",
|
|
3640
|
-
version: "1.3.
|
|
3901
|
+
version: "1.3.1",
|
|
3641
3902
|
description: "Terminal coding agent with a config you never have to write by hand",
|
|
3642
3903
|
type: "module",
|
|
3643
3904
|
license: "MIT",
|
|
@@ -3705,7 +3966,7 @@ var package_default = {
|
|
|
3705
3966
|
|
|
3706
3967
|
// src/version.ts
|
|
3707
3968
|
var KITCODE_VERSION = package_default.version;
|
|
3708
|
-
var KITCODE_COMMIT = true ? "
|
|
3969
|
+
var KITCODE_COMMIT = true ? "4320a2bd340f59e42397dd4afbfde0051ddfeb27" : "development";
|
|
3709
3970
|
|
|
3710
3971
|
// src/mcp/client.ts
|
|
3711
3972
|
var clientInfo = { name: "kitcode", version: KITCODE_VERSION };
|
|
@@ -3903,8 +4164,8 @@ function describeFailure2(error, stderr, secrets) {
|
|
|
3903
4164
|
return redactSecrets(stderr ? `${message} \u2014 ${stderr}` : message, secrets);
|
|
3904
4165
|
}
|
|
3905
4166
|
function configuredSecrets(config) {
|
|
3906
|
-
const
|
|
3907
|
-
return Object.values(expandEnvRecord(
|
|
4167
|
+
const record3 = config.type === "stdio" ? config.env : config.headers;
|
|
4168
|
+
return Object.values(expandEnvRecord(record3)).filter((value) => value.length >= 4);
|
|
3908
4169
|
}
|
|
3909
4170
|
function inheritedEnv(source = process.env) {
|
|
3910
4171
|
const env = {};
|
|
@@ -4000,10 +4261,10 @@ function endpoint(origin, path15, parse2) {
|
|
|
4000
4261
|
return { url: `${origin}${path15}`, parse: parse2 };
|
|
4001
4262
|
}
|
|
4002
4263
|
function deepSeekBalance(body) {
|
|
4003
|
-
const infos =
|
|
4264
|
+
const infos = record2(body)?.["balance_infos"];
|
|
4004
4265
|
if (!Array.isArray(infos)) return null;
|
|
4005
4266
|
const balances = infos.flatMap((item) => {
|
|
4006
|
-
const entry =
|
|
4267
|
+
const entry = record2(item);
|
|
4007
4268
|
const currency = currencyCode(entry?.["currency"]);
|
|
4008
4269
|
const amount = decimal(entry?.["total_balance"]);
|
|
4009
4270
|
return currency && amount ? [{ kind: "balance", currency, amount }] : [];
|
|
@@ -4011,11 +4272,11 @@ function deepSeekBalance(body) {
|
|
|
4011
4272
|
return balances.length > 0 ? balances : null;
|
|
4012
4273
|
}
|
|
4013
4274
|
function openRouterKeyBalance(body) {
|
|
4014
|
-
const remaining = finiteNumber(
|
|
4275
|
+
const remaining = finiteNumber(record2(record2(body)?.["data"])?.["limit_remaining"]);
|
|
4015
4276
|
return remaining === null ? null : [{ kind: "key-limit", currency: "USD", amount: String(remaining) }];
|
|
4016
4277
|
}
|
|
4017
4278
|
function openRouterCreditsBalance(body) {
|
|
4018
|
-
const data =
|
|
4279
|
+
const data = record2(record2(body)?.["data"]);
|
|
4019
4280
|
const credits = finiteNumber(data?.["total_credits"]);
|
|
4020
4281
|
const usage = finiteNumber(data?.["total_usage"]);
|
|
4021
4282
|
if (credits === null || usage === null) return null;
|
|
@@ -4028,26 +4289,26 @@ function openRouterCreditsBalance(body) {
|
|
|
4028
4289
|
];
|
|
4029
4290
|
}
|
|
4030
4291
|
function siliconFlowBalance(body) {
|
|
4031
|
-
const root =
|
|
4292
|
+
const root = record2(body);
|
|
4032
4293
|
if (root?.["status"] !== true) return null;
|
|
4033
|
-
const amount = decimal(
|
|
4294
|
+
const amount = decimal(record2(root["data"])?.["balance"]);
|
|
4034
4295
|
return amount ? [{ kind: "balance", currency: "credits", amount }] : null;
|
|
4035
4296
|
}
|
|
4036
4297
|
function apiMartBalance(body) {
|
|
4037
|
-
const root =
|
|
4298
|
+
const root = record2(body);
|
|
4038
4299
|
if (root?.["success"] !== true) return null;
|
|
4039
4300
|
const amount = decimal(root["remain_balance"]);
|
|
4040
4301
|
return amount ? [{ kind: "balance", currency: "credits", amount }] : null;
|
|
4041
4302
|
}
|
|
4042
4303
|
function genericBalance(body) {
|
|
4043
|
-
const root =
|
|
4044
|
-
const data =
|
|
4304
|
+
const root = record2(body);
|
|
4305
|
+
const data = record2(root?.["data"]);
|
|
4045
4306
|
const amount = decimal(root?.["balance"] ?? data?.["balance"]);
|
|
4046
4307
|
if (!amount) return null;
|
|
4047
4308
|
const currency = currencyCode(root?.["currency"] ?? data?.["currency"]) ?? "credits";
|
|
4048
4309
|
return [{ kind: "balance", currency, amount }];
|
|
4049
4310
|
}
|
|
4050
|
-
function
|
|
4311
|
+
function record2(value) {
|
|
4051
4312
|
return typeof value === "object" && value !== null ? value : null;
|
|
4052
4313
|
}
|
|
4053
4314
|
function currencyCode(value) {
|
|
@@ -4075,8 +4336,8 @@ async function limitedResponseText(response, limit) {
|
|
|
4075
4336
|
let text = "";
|
|
4076
4337
|
try {
|
|
4077
4338
|
while (true) {
|
|
4078
|
-
const { done, value } = await reader.read();
|
|
4079
|
-
if (
|
|
4339
|
+
const { done: done2, value } = await reader.read();
|
|
4340
|
+
if (done2) return text + decoder.decode();
|
|
4080
4341
|
size += value.byteLength;
|
|
4081
4342
|
if (size > limit) {
|
|
4082
4343
|
await reader.cancel();
|
|
@@ -4096,15 +4357,15 @@ var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
|
4096
4357
|
var CACHE_VERSION = 3;
|
|
4097
4358
|
async function loadModels(provider, refresh = false) {
|
|
4098
4359
|
const file = cacheFile(provider.id);
|
|
4099
|
-
const
|
|
4100
|
-
if (!refresh &&
|
|
4101
|
-
return
|
|
4360
|
+
const cached2 = await readCache(file);
|
|
4361
|
+
if (!refresh && cached2?.version === CACHE_VERSION && Date.now() - cached2.fetchedAt < MAX_AGE_MS) {
|
|
4362
|
+
return cached2.models;
|
|
4102
4363
|
}
|
|
4103
4364
|
let models;
|
|
4104
4365
|
try {
|
|
4105
4366
|
models = await provider.listModels();
|
|
4106
4367
|
} catch (error) {
|
|
4107
|
-
if (
|
|
4368
|
+
if (cached2) return cached2.models;
|
|
4108
4369
|
const known = provider.knownModels();
|
|
4109
4370
|
if (known.length > 0) return known;
|
|
4110
4371
|
throw error;
|
|
@@ -4112,6 +4373,9 @@ async function loadModels(provider, refresh = false) {
|
|
|
4112
4373
|
await writeCache(file, models);
|
|
4113
4374
|
return models;
|
|
4114
4375
|
}
|
|
4376
|
+
function refreshModels(provider) {
|
|
4377
|
+
return loadModels(provider, true);
|
|
4378
|
+
}
|
|
4115
4379
|
function cacheFile(providerId) {
|
|
4116
4380
|
return path9.join(cacheDir, "models", `${providerId.replace(/[^\w.-]/g, "_")}.json`);
|
|
4117
4381
|
}
|
|
@@ -4696,8 +4960,8 @@ function normalizeOpenAiUsage(usage) {
|
|
|
4696
4960
|
function createRegistry(config, auth) {
|
|
4697
4961
|
const built = /* @__PURE__ */ new Map();
|
|
4698
4962
|
function get(id) {
|
|
4699
|
-
const
|
|
4700
|
-
if (
|
|
4963
|
+
const cached2 = built.get(id);
|
|
4964
|
+
if (cached2) return cached2;
|
|
4701
4965
|
const entry = config.providers[id];
|
|
4702
4966
|
if (!entry) throw new Error(unknownProviderMessage(id, Object.keys(config.providers)));
|
|
4703
4967
|
const provider = build(id, entry, apiKey(id, entry, auth));
|
|
@@ -4789,13 +5053,66 @@ function isFileEdit(tool) {
|
|
|
4789
5053
|
return tool.name === "write" || tool.name === "edit";
|
|
4790
5054
|
}
|
|
4791
5055
|
|
|
5056
|
+
// src/tools/bash-output.ts
|
|
5057
|
+
var bashOutputTool = {
|
|
5058
|
+
name: "bash_output",
|
|
5059
|
+
description: "Collect output from commands started with bash({ background: true }). Returns only the output that arrived since the last read. Omit id to list every background command. Set kill to stop one.",
|
|
5060
|
+
inputSchema: {
|
|
5061
|
+
type: "object",
|
|
5062
|
+
properties: {
|
|
5063
|
+
id: { type: "string", description: 'Background command id, e.g. "bg-1"' },
|
|
5064
|
+
kill: { type: "boolean", description: "Stop the command instead of reading it" }
|
|
5065
|
+
},
|
|
5066
|
+
additionalProperties: false
|
|
5067
|
+
},
|
|
5068
|
+
defaultPermission: "allow",
|
|
5069
|
+
readOnly: true,
|
|
5070
|
+
summarize(input) {
|
|
5071
|
+
const { id, kill } = input;
|
|
5072
|
+
if (!id) return "bash_output(list)";
|
|
5073
|
+
return kill ? `bash_output(${id}, kill)` : `bash_output(${id})`;
|
|
5074
|
+
},
|
|
5075
|
+
execute(input) {
|
|
5076
|
+
const { id, kill } = input;
|
|
5077
|
+
if (!id) {
|
|
5078
|
+
const jobs2 = listJobs();
|
|
5079
|
+
if (jobs2.length === 0) return done({ content: "No background commands have been started." });
|
|
5080
|
+
const lines = jobs2.map((job2) => `${job2.id} ${describeState(job2.state, job2.exitCode, job2.signal)} ${job2.command}`);
|
|
5081
|
+
return done({ content: lines.join("\n") });
|
|
5082
|
+
}
|
|
5083
|
+
if (kill) {
|
|
5084
|
+
const stopped = killJob(id);
|
|
5085
|
+
return done(
|
|
5086
|
+
stopped ? { content: `[${id} stopped]` } : { content: `No background command with id "${id}".`, isError: true }
|
|
5087
|
+
);
|
|
5088
|
+
}
|
|
5089
|
+
const job = readJob(id);
|
|
5090
|
+
if (!job) return done({ content: `No background command with id "${id}".`, isError: true });
|
|
5091
|
+
const notes = [`[${id} ${describeState(job.state, job.exitCode, job.signal)}]`];
|
|
5092
|
+
if (job.truncated) notes.push("[output buffer full; earlier output was dropped]");
|
|
5093
|
+
const body = job.output.trim() === "" ? "(no new output)" : job.output.replace(/\n+$/, "");
|
|
5094
|
+
return done({
|
|
5095
|
+
content: [body, ...notes].join("\n"),
|
|
5096
|
+
isError: job.state === "exited" && job.exitCode !== null && job.exitCode !== 0
|
|
5097
|
+
});
|
|
5098
|
+
}
|
|
5099
|
+
};
|
|
5100
|
+
function describeState(state, exitCode, signal) {
|
|
5101
|
+
if (state === "running") return "still running";
|
|
5102
|
+
if (state === "killed") return `stopped${signal ? ` by ${signal}` : ""}`;
|
|
5103
|
+
return exitCode === 0 ? "finished" : `exit code ${exitCode}`;
|
|
5104
|
+
}
|
|
5105
|
+
function done(result) {
|
|
5106
|
+
return Promise.resolve(result);
|
|
5107
|
+
}
|
|
5108
|
+
|
|
4792
5109
|
// src/tools/edit.ts
|
|
4793
5110
|
import { readFile as readFile9, stat as stat6 } from "fs/promises";
|
|
4794
5111
|
|
|
4795
5112
|
// src/tools/safe-write.ts
|
|
4796
5113
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
4797
5114
|
import { chmod as chmod5, lstat as lstat3, open, rename as rename5, rm as rm4 } from "fs/promises";
|
|
4798
|
-
import { basename as basename2, dirname as dirname2, join } from "path";
|
|
5115
|
+
import { basename as basename2, dirname as dirname2, join as join2 } from "path";
|
|
4799
5116
|
var UnsafeFileChangeError = class extends Error {
|
|
4800
5117
|
constructor(message) {
|
|
4801
5118
|
super(message);
|
|
@@ -4840,7 +5157,7 @@ async function readSafeFileSnapshot(file, maxBytes) {
|
|
|
4840
5157
|
async function atomicWriteSafeFile(file, data, snapshot) {
|
|
4841
5158
|
const parent = dirname2(file);
|
|
4842
5159
|
await assertSameParent(parent, snapshot.parent);
|
|
4843
|
-
const temp =
|
|
5160
|
+
const temp = join2(parent, `.${basename2(file)}.${process.pid}.${randomUUID3()}.tmp`);
|
|
4844
5161
|
let handle;
|
|
4845
5162
|
try {
|
|
4846
5163
|
handle = await open(temp, "wx", snapshot.mode);
|
|
@@ -5030,58 +5347,10 @@ function replacement(before, oldString, newString, replaceAll) {
|
|
|
5030
5347
|
return before.slice(0, at) + newString + before.slice(at + oldString.length);
|
|
5031
5348
|
}
|
|
5032
5349
|
|
|
5033
|
-
// src/tools/glob.ts
|
|
5034
|
-
import { join as join2 } from "path";
|
|
5035
|
-
import fg from "fast-glob";
|
|
5036
|
-
var MAX_RESULTS = 500;
|
|
5037
|
-
var IGNORED_DIRECTORIES = ["**/node_modules/**", "**/.git/**", "**/dist/**"];
|
|
5038
|
-
var globTool = {
|
|
5039
|
-
name: "glob",
|
|
5040
|
-
description: 'Find files by glob pattern, most recently modified first. Supports patterns like "src/**/*.ts". Hidden files, node_modules, .git and dist are skipped.',
|
|
5041
|
-
inputSchema: {
|
|
5042
|
-
type: "object",
|
|
5043
|
-
properties: {
|
|
5044
|
-
pattern: { type: "string", description: "Glob pattern, e.g. **/*.test.ts" },
|
|
5045
|
-
path: { type: "string", description: "Directory to search in, relative to the workspace root" }
|
|
5046
|
-
},
|
|
5047
|
-
required: ["pattern"],
|
|
5048
|
-
additionalProperties: false
|
|
5049
|
-
},
|
|
5050
|
-
defaultPermission: "allow",
|
|
5051
|
-
readOnly: true,
|
|
5052
|
-
summarize(input) {
|
|
5053
|
-
return `glob(${brief(input.pattern)})`;
|
|
5054
|
-
},
|
|
5055
|
-
async execute(input, ctx) {
|
|
5056
|
-
const { pattern, path: path15 = "." } = input;
|
|
5057
|
-
if (patternEscapes(pattern)) {
|
|
5058
|
-
return { content: `Pattern ${pattern} must be relative to the workspace root.`, isError: true };
|
|
5059
|
-
}
|
|
5060
|
-
const safe = resolveInside(ctx.cwd, path15);
|
|
5061
|
-
if (!safe.ok) return { content: safe.reason, isError: true };
|
|
5062
|
-
const entries = await fg(pattern, {
|
|
5063
|
-
cwd: safe.path,
|
|
5064
|
-
dot: false,
|
|
5065
|
-
onlyFiles: true,
|
|
5066
|
-
followSymbolicLinks: false,
|
|
5067
|
-
suppressErrors: true,
|
|
5068
|
-
ignore: IGNORED_DIRECTORIES,
|
|
5069
|
-
stats: true
|
|
5070
|
-
});
|
|
5071
|
-
if (entries.length === 0) return { content: `No files matched ${pattern}` };
|
|
5072
|
-
entries.sort((a, b) => (b.stats?.mtimeMs ?? 0) - (a.stats?.mtimeMs ?? 0));
|
|
5073
|
-
const paths = entries.slice(0, MAX_RESULTS).map((entry) => join2(safe.relative, entry.path));
|
|
5074
|
-
if (entries.length > MAX_RESULTS) {
|
|
5075
|
-
paths.push(`... truncated: ${entries.length - MAX_RESULTS} more files matched.`);
|
|
5076
|
-
}
|
|
5077
|
-
return { content: paths.join("\n") };
|
|
5078
|
-
}
|
|
5079
|
-
};
|
|
5080
|
-
|
|
5081
5350
|
// src/tools/grep.ts
|
|
5082
5351
|
import { readFile as readFile10, stat as stat7 } from "fs/promises";
|
|
5083
5352
|
import { join as join3 } from "path";
|
|
5084
|
-
import
|
|
5353
|
+
import fg3 from "fast-glob";
|
|
5085
5354
|
|
|
5086
5355
|
// src/tools/sensitive.ts
|
|
5087
5356
|
import path10 from "path";
|
|
@@ -5259,7 +5528,7 @@ var grepTool = {
|
|
|
5259
5528
|
if (!safe.ok) return { content: safe.reason, isError: true };
|
|
5260
5529
|
const target = await stat7(safe.path).catch(() => null);
|
|
5261
5530
|
if (!target) return { content: `Path not found: ${path15}`, isError: true };
|
|
5262
|
-
const discovered = target.isDirectory() ? (await
|
|
5531
|
+
const discovered = target.isDirectory() ? (await fg3(glob, {
|
|
5263
5532
|
cwd: safe.path,
|
|
5264
5533
|
dot: false,
|
|
5265
5534
|
onlyFiles: true,
|
|
@@ -5482,7 +5751,7 @@ var writeTool = {
|
|
|
5482
5751
|
|
|
5483
5752
|
// src/tools/registry.ts
|
|
5484
5753
|
function builtinTools() {
|
|
5485
|
-
return [readTool, writeTool, editTool, bashTool, globTool, grepTool];
|
|
5754
|
+
return [readTool, writeTool, editTool, bashTool, bashOutputTool, globTool, grepTool];
|
|
5486
5755
|
}
|
|
5487
5756
|
function createToolRegistry(tools) {
|
|
5488
5757
|
const byName = /* @__PURE__ */ new Map();
|
|
@@ -6217,8 +6486,8 @@ async function readJsonBounded(response) {
|
|
|
6217
6486
|
let bytes = 0;
|
|
6218
6487
|
try {
|
|
6219
6488
|
while (true) {
|
|
6220
|
-
const { done, value } = await reader.read();
|
|
6221
|
-
if (
|
|
6489
|
+
const { done: done2, value } = await reader.read();
|
|
6490
|
+
if (done2) break;
|
|
6222
6491
|
bytes += value.byteLength;
|
|
6223
6492
|
if (bytes > MAX_RESPONSE_BYTES2) {
|
|
6224
6493
|
await reader.cancel();
|
|
@@ -6391,9 +6660,9 @@ async function boot(options) {
|
|
|
6391
6660
|
activeAgents.push(entry);
|
|
6392
6661
|
const trackedRequest = {
|
|
6393
6662
|
...request,
|
|
6394
|
-
onProgress: (
|
|
6395
|
-
entry.progress.push(
|
|
6396
|
-
request.onProgress(
|
|
6663
|
+
onProgress: (summary2) => {
|
|
6664
|
+
entry.progress.push(summary2);
|
|
6665
|
+
request.onProgress(summary2);
|
|
6397
6666
|
}
|
|
6398
6667
|
};
|
|
6399
6668
|
try {
|
|
@@ -6461,13 +6730,15 @@ async function boot(options) {
|
|
|
6461
6730
|
needsSetup: () => Object.keys(config.providers).length === 0,
|
|
6462
6731
|
async addProvider(url, key) {
|
|
6463
6732
|
const detected = await detectProvider(url, key);
|
|
6464
|
-
rememberModels(
|
|
6733
|
+
for (const provider of detected) rememberModels(provider.id, provider.models);
|
|
6465
6734
|
const configBefore = structuredClone(config);
|
|
6466
6735
|
const authBefore = { ...auth };
|
|
6467
|
-
|
|
6468
|
-
|
|
6469
|
-
|
|
6470
|
-
|
|
6736
|
+
for (const provider of detected) {
|
|
6737
|
+
config.providers[provider.id] = provider.config;
|
|
6738
|
+
auth[provider.id] = key;
|
|
6739
|
+
}
|
|
6740
|
+
const chosen = preferredDetectedModel(detected);
|
|
6741
|
+
if (chosen) config.model = formatModelRef(chosen.providerId, chosen.model.id);
|
|
6471
6742
|
try {
|
|
6472
6743
|
await saveConfig(config);
|
|
6473
6744
|
await saveAuth(auth);
|
|
@@ -6480,11 +6751,12 @@ async function boot(options) {
|
|
|
6480
6751
|
}
|
|
6481
6752
|
registry = createRegistry(config, auth);
|
|
6482
6753
|
const nextRef = config.model ?? "";
|
|
6483
|
-
|
|
6484
|
-
activateModel(nextRef, void 0, discovered);
|
|
6754
|
+
activateModel(nextRef, void 0, chosen?.model.contextWindow);
|
|
6485
6755
|
void refreshModelContextWindow();
|
|
6486
|
-
|
|
6487
|
-
|
|
6756
|
+
return detected.map((provider) => {
|
|
6757
|
+
const kind = provider.config.type === "anthropic" ? "Anthropic" : "OpenAI-compatible";
|
|
6758
|
+
return `${provider.id} \xB7 ${kind} \xB7 ${provider.models.length} models`;
|
|
6759
|
+
}).join("\n");
|
|
6488
6760
|
},
|
|
6489
6761
|
currentProviderId: () => parseModelRef(modelRef)?.provider,
|
|
6490
6762
|
listProviderItems: () => Object.entries(config.providers).map(([id, entry]) => ({
|
|
@@ -6838,6 +7110,15 @@ ${lines.join("\n")}` : null;
|
|
|
6838
7110
|
}
|
|
6839
7111
|
return items;
|
|
6840
7112
|
},
|
|
7113
|
+
async refreshProviderModels(providerId) {
|
|
7114
|
+
const models = await refreshModels(registry.get(providerId));
|
|
7115
|
+
rememberModels(providerId, models);
|
|
7116
|
+
if (parseModelRef(modelRef)?.provider === providerId) void refreshModelContextWindow();
|
|
7117
|
+
return models.length;
|
|
7118
|
+
},
|
|
7119
|
+
async listWorkspaceFiles(query) {
|
|
7120
|
+
return searchWorkspaceFiles(options.cwd, query);
|
|
7121
|
+
},
|
|
6841
7122
|
async listPromptItems() {
|
|
6842
7123
|
const prompts2 = await listPrompts();
|
|
6843
7124
|
return prompts2.map((prompt) => ({
|
|
@@ -7033,6 +7314,7 @@ ${lines.join("\n")}` : null;
|
|
|
7033
7314
|
try {
|
|
7034
7315
|
await Promise.all([persistQueue, configQueue]);
|
|
7035
7316
|
} finally {
|
|
7317
|
+
killAllJobs();
|
|
7036
7318
|
await mcp.close();
|
|
7037
7319
|
}
|
|
7038
7320
|
}
|
|
@@ -7045,6 +7327,16 @@ function preferredModel(models) {
|
|
|
7045
7327
|
}
|
|
7046
7328
|
return models[0]?.id;
|
|
7047
7329
|
}
|
|
7330
|
+
function preferredDetectedModel(detected) {
|
|
7331
|
+
const candidates = detected.flatMap(
|
|
7332
|
+
(provider) => provider.models.map((model) => ({ providerId: provider.id, model }))
|
|
7333
|
+
);
|
|
7334
|
+
for (const preferred of PREFERRED2) {
|
|
7335
|
+
const match = candidates.find((candidate) => candidate.model.id.endsWith(preferred));
|
|
7336
|
+
if (match) return match;
|
|
7337
|
+
}
|
|
7338
|
+
return candidates[0];
|
|
7339
|
+
}
|
|
7048
7340
|
|
|
7049
7341
|
// src/app/ask.ts
|
|
7050
7342
|
var RU_YES = /* @__PURE__ */ new Set(["y", "\u043D"]);
|
|
@@ -7313,6 +7605,7 @@ var COMMANDS = [
|
|
|
7313
7605
|
{ name: "provider" },
|
|
7314
7606
|
{ name: "logout", args: "[provider]" },
|
|
7315
7607
|
{ name: "key", args: "[provider]" },
|
|
7608
|
+
{ name: "refresh", args: "[provider]" },
|
|
7316
7609
|
{ name: "effort" },
|
|
7317
7610
|
{ name: "thinking" },
|
|
7318
7611
|
{ name: "budget", args: "[tokens]" },
|
|
@@ -7362,22 +7655,22 @@ function matchCommands(line) {
|
|
|
7362
7655
|
if (starts.length > 0) return starts;
|
|
7363
7656
|
const contains = COMMANDS.filter((command) => command.name.includes(needle));
|
|
7364
7657
|
if (contains.length > 0) return contains;
|
|
7365
|
-
return COMMANDS.filter((command) =>
|
|
7658
|
+
return COMMANDS.filter((command) => isSubsequence2(needle, command.name));
|
|
7366
7659
|
}
|
|
7367
7660
|
function closestCommand(name) {
|
|
7368
7661
|
const needle = name.toLowerCase();
|
|
7369
7662
|
let best;
|
|
7370
7663
|
let bestScore = Number.POSITIVE_INFINITY;
|
|
7371
7664
|
for (const command of COMMANDS) {
|
|
7372
|
-
const
|
|
7373
|
-
if (
|
|
7374
|
-
bestScore =
|
|
7665
|
+
const score2 = editDistance(needle, command.name);
|
|
7666
|
+
if (score2 < bestScore) {
|
|
7667
|
+
bestScore = score2;
|
|
7375
7668
|
best = command;
|
|
7376
7669
|
}
|
|
7377
7670
|
}
|
|
7378
7671
|
return bestScore <= Math.max(2, Math.floor(needle.length / 2)) ? best : void 0;
|
|
7379
7672
|
}
|
|
7380
|
-
function
|
|
7673
|
+
function isSubsequence2(needle, haystack) {
|
|
7381
7674
|
let index = 0;
|
|
7382
7675
|
for (const character of haystack) {
|
|
7383
7676
|
if (character === needle[index]) index += 1;
|
|
@@ -7402,6 +7695,43 @@ function editDistance(a, b) {
|
|
|
7402
7695
|
return previous[b.length] ?? 0;
|
|
7403
7696
|
}
|
|
7404
7697
|
|
|
7698
|
+
// src/ui/mentions.ts
|
|
7699
|
+
var MENTION_PATTERN = /(?:^|\s)@([^\s@]+)/g;
|
|
7700
|
+
var TRAILING_PUNCTUATION = /[,;:!?]+$/;
|
|
7701
|
+
function mentionSpan(value, cursor) {
|
|
7702
|
+
const at = clamp(cursor, 0, value.length);
|
|
7703
|
+
const before = value.slice(0, at);
|
|
7704
|
+
const start = before.lastIndexOf("@");
|
|
7705
|
+
if (start === -1) return null;
|
|
7706
|
+
const preceding = start === 0 ? "" : value[start - 1] ?? "";
|
|
7707
|
+
if (preceding !== "" && !/\s/.test(preceding)) return null;
|
|
7708
|
+
const query = before.slice(start + 1);
|
|
7709
|
+
if (/[\s@]/.test(query)) return null;
|
|
7710
|
+
let end = at;
|
|
7711
|
+
while (end < value.length && !/[\s@]/.test(value[end] ?? "")) end += 1;
|
|
7712
|
+
return { start, end, query };
|
|
7713
|
+
}
|
|
7714
|
+
function completeMention(value, span, path15) {
|
|
7715
|
+
const rest = value.slice(span.end);
|
|
7716
|
+
const spaced = rest.startsWith(" ");
|
|
7717
|
+
const inserted = `@${path15}${spaced ? "" : " "}`;
|
|
7718
|
+
return {
|
|
7719
|
+
value: value.slice(0, span.start) + inserted + rest,
|
|
7720
|
+
cursor: span.start + inserted.length + (spaced ? 1 : 0)
|
|
7721
|
+
};
|
|
7722
|
+
}
|
|
7723
|
+
function extractMentions(text) {
|
|
7724
|
+
const found = [];
|
|
7725
|
+
for (const match of text.matchAll(MENTION_PATTERN)) {
|
|
7726
|
+
const path15 = (match[1] ?? "").replace(TRAILING_PUNCTUATION, "");
|
|
7727
|
+
if (path15 !== "" && !found.includes(path15)) found.push(path15);
|
|
7728
|
+
}
|
|
7729
|
+
return found;
|
|
7730
|
+
}
|
|
7731
|
+
function clamp(value, min, max) {
|
|
7732
|
+
return Math.max(min, Math.min(value, max));
|
|
7733
|
+
}
|
|
7734
|
+
|
|
7405
7735
|
// src/ui/App.tsx
|
|
7406
7736
|
import TextInput2 from "ink-text-input";
|
|
7407
7737
|
|
|
@@ -7503,6 +7833,7 @@ var en = {
|
|
|
7503
7833
|
titleProvider: "Provider",
|
|
7504
7834
|
titleLogout: "Sign out of a provider",
|
|
7505
7835
|
titleKeyChange: "Change API key for a provider",
|
|
7836
|
+
titleModelRefresh: "Reload models for a provider",
|
|
7506
7837
|
titleSessions: "Resume a session",
|
|
7507
7838
|
titleSessionAction: "Session action",
|
|
7508
7839
|
resumed: (id, count) => `Resumed ${id} \u2014 ${count} messages restored`,
|
|
@@ -7582,6 +7913,7 @@ ${url}`,
|
|
|
7582
7913
|
budgetUnlimited: "Token budget: unlimited (0). Set /budget <tokens> to restore a limit.",
|
|
7583
7914
|
budgetInvalid: "Invalid budget: must be between 1,000 and 10,000,000",
|
|
7584
7915
|
modelSet: (value) => `Model: ${value}`,
|
|
7916
|
+
modelsRefreshed: (provider, count) => `${provider}: ${count} models reloaded from the endpoint.`,
|
|
7585
7917
|
noModels: "No models available. Use /login to add a provider.",
|
|
7586
7918
|
bypassOff: "Approval prompts are back on.",
|
|
7587
7919
|
bypassAsk1: "Disable approval prompts for this session?",
|
|
@@ -7629,6 +7961,7 @@ ${url}`,
|
|
|
7629
7961
|
provider: "switch between configured providers",
|
|
7630
7962
|
logout: "choose a provider to sign out from",
|
|
7631
7963
|
key: "change API key for a provider",
|
|
7964
|
+
refresh: "reload the model list for a provider",
|
|
7632
7965
|
effort: "set reasoning depth",
|
|
7633
7966
|
thinking: "toggle reasoning",
|
|
7634
7967
|
budget: "set token budget per turn",
|
|
@@ -7692,6 +8025,7 @@ var ru = {
|
|
|
7692
8025
|
titleProvider: "\u041F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440",
|
|
7693
8026
|
titleLogout: "\u0412\u044B\u0431\u0440\u0430\u0442\u044C API \u0434\u043B\u044F \u0432\u044B\u0445\u043E\u0434\u0430",
|
|
7694
8027
|
titleKeyChange: "\u0421\u043C\u0435\u043D\u0438\u0442\u044C \u043A\u043B\u044E\u0447 \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440\u0430",
|
|
8028
|
+
titleModelRefresh: "\u041E\u0431\u043D\u043E\u0432\u0438\u0442\u044C \u043C\u043E\u0434\u0435\u043B\u0438 \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440\u0430",
|
|
7695
8029
|
titleSessions: "\u0412\u043E\u0441\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u044C \u0441\u0435\u0441\u0441\u0438\u044E",
|
|
7696
8030
|
titleSessionAction: "\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u0441 \u0441\u0435\u0441\u0441\u0438\u0435\u0439",
|
|
7697
8031
|
resumed: (id, count) => `\u0421\u0435\u0441\u0441\u0438\u044F ${id} \u0432\u043E\u0441\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u0430 \u2014 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0439: ${count}`,
|
|
@@ -7771,6 +8105,7 @@ ${url}`,
|
|
|
7771
8105
|
budgetUnlimited: "\u041B\u0438\u043C\u0438\u0442 \u0442\u043E\u043A\u0435\u043D\u043E\u0432: \u0431\u0435\u0437\u043B\u0438\u043C\u0438\u0442\u043D\u044B\u0439 (0). \u0412\u0432\u0435\u0434\u0438 /budget <\u0447\u0438\u0441\u043B\u043E> \u0447\u0442\u043E\u0431\u044B \u0432\u0435\u0440\u043D\u0443\u0442\u044C \u043B\u0438\u043C\u0438\u0442.",
|
|
7772
8106
|
budgetInvalid: "\u041D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 \u043B\u0438\u043C\u0438\u0442: \u043E\u0442 1 000 \u0434\u043E 10 000 000",
|
|
7773
8107
|
modelSet: (value) => `\u041C\u043E\u0434\u0435\u043B\u044C: ${value}`,
|
|
8108
|
+
modelsRefreshed: (provider, count) => `${provider}: \u0441 \u044D\u043D\u0434\u043F\u043E\u0438\u043D\u0442\u0430 \u043F\u043E\u043B\u0443\u0447\u0435\u043D\u043E \u043C\u043E\u0434\u0435\u043B\u0435\u0439 \u2014 ${count}.`,
|
|
7774
8109
|
noModels: "\u041C\u043E\u0434\u0435\u043B\u0435\u0439 \u043D\u0435\u0442. \u0414\u043E\u0431\u0430\u0432\u044C \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440\u0430 \u0447\u0435\u0440\u0435\u0437 /login.",
|
|
7775
8110
|
bypassOff: "\u041F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F \u0441\u043D\u043E\u0432\u0430 \u0432\u043A\u043B\u044E\u0447\u0435\u043D\u044B.",
|
|
7776
8111
|
bypassAsk1: "\u041E\u0442\u043A\u043B\u044E\u0447\u0438\u0442\u044C \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F \u043D\u0430 \u044D\u0442\u0443 \u0441\u0435\u0441\u0441\u0438\u044E?",
|
|
@@ -7818,6 +8153,7 @@ ${url}`,
|
|
|
7818
8153
|
provider: "\u043F\u0435\u0440\u0435\u043A\u043B\u044E\u0447\u0438\u0442\u044C\u0441\u044F \u043C\u0435\u0436\u0434\u0443 \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440\u0430\u043C\u0438",
|
|
7819
8154
|
logout: "\u0432\u044B\u0431\u0440\u0430\u0442\u044C \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440\u0430 \u0438 \u0443\u0434\u0430\u043B\u0438\u0442\u044C \u0435\u0433\u043E \u043A\u043B\u044E\u0447",
|
|
7820
8155
|
key: "\u0441\u043C\u0435\u043D\u0438\u0442\u044C \u043A\u043B\u044E\u0447 \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440\u0430",
|
|
8156
|
+
refresh: "\u043E\u0431\u043D\u043E\u0432\u0438\u0442\u044C \u0441\u043F\u0438\u0441\u043E\u043A \u043C\u043E\u0434\u0435\u043B\u0435\u0439 \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440\u0430",
|
|
7821
8157
|
effort: "\u0433\u043B\u0443\u0431\u0438\u043D\u0430 \u0440\u0430\u0437\u043C\u044B\u0448\u043B\u0435\u043D\u0438\u0439",
|
|
7822
8158
|
thinking: "\u0432\u043A\u043B\u044E\u0447\u0438\u0442\u044C/\u0432\u044B\u043A\u043B\u044E\u0447\u0438\u0442\u044C \u0440\u0430\u0437\u043C\u044B\u0448\u043B\u0435\u043D\u0438\u044F",
|
|
7823
8159
|
budget: "\u043B\u0438\u043C\u0438\u0442 \u0442\u043E\u043A\u0435\u043D\u043E\u0432 \u0437\u0430 \u0445\u043E\u0434 (0 = \u0431\u0435\u0437\u043B\u0438\u043C\u0438\u0442)",
|
|
@@ -8306,7 +8642,8 @@ var PromptInput = memo(function PromptInput2({
|
|
|
8306
8642
|
pending,
|
|
8307
8643
|
hint,
|
|
8308
8644
|
history,
|
|
8309
|
-
attachments = []
|
|
8645
|
+
attachments = [],
|
|
8646
|
+
onListFiles
|
|
8310
8647
|
}) {
|
|
8311
8648
|
const theme = useTheme();
|
|
8312
8649
|
const { columns } = useTerminalSize();
|
|
@@ -8321,9 +8658,28 @@ var PromptInput = memo(function PromptInput2({
|
|
|
8321
8658
|
const cursorRef = useRef2(inputCursor);
|
|
8322
8659
|
valueRef.current = safeValue;
|
|
8323
8660
|
cursorRef.current = inputCursor;
|
|
8324
|
-
const
|
|
8325
|
-
const
|
|
8326
|
-
const
|
|
8661
|
+
const commandSuggestions = matchCommands(safeValue);
|
|
8662
|
+
const mention = onListFiles ? mentionSpan(safeValue, inputCursor) : null;
|
|
8663
|
+
const [files, setFiles] = useState4([]);
|
|
8664
|
+
const mentionQuery = mention?.query ?? null;
|
|
8665
|
+
useEffect(() => {
|
|
8666
|
+
if (mentionQuery === null || !onListFiles) {
|
|
8667
|
+
setFiles([]);
|
|
8668
|
+
return;
|
|
8669
|
+
}
|
|
8670
|
+
let live = true;
|
|
8671
|
+
void onListFiles(mentionQuery).then((found) => {
|
|
8672
|
+
if (live) setFiles(found);
|
|
8673
|
+
});
|
|
8674
|
+
return () => {
|
|
8675
|
+
live = false;
|
|
8676
|
+
};
|
|
8677
|
+
}, [mentionQuery, onListFiles]);
|
|
8678
|
+
const showFiles = mention !== null && files.length > 0;
|
|
8679
|
+
const suggestions = showFiles ? [] : commandSuggestions;
|
|
8680
|
+
const open3 = suggestions.length > 0 || showFiles;
|
|
8681
|
+
const optionCount = showFiles ? files.length : suggestions.length;
|
|
8682
|
+
const active2 = Math.min(selectionCursor, Math.max(0, optionCount - 1));
|
|
8327
8683
|
useEffect(() => {
|
|
8328
8684
|
setSelectionCursor(0);
|
|
8329
8685
|
}, [safeValue]);
|
|
@@ -8331,14 +8687,14 @@ var PromptInput = memo(function PromptInput2({
|
|
|
8331
8687
|
const length = characters2(safeValue).length;
|
|
8332
8688
|
if (pendingValue.current === safeValue) {
|
|
8333
8689
|
pendingValue.current = null;
|
|
8334
|
-
setInputCursor((cursor) =>
|
|
8690
|
+
setInputCursor((cursor) => clamp2(cursor, 0, length));
|
|
8335
8691
|
return;
|
|
8336
8692
|
}
|
|
8337
8693
|
setInputCursor(length);
|
|
8338
8694
|
}, [safeValue]);
|
|
8339
8695
|
const change = (next, cursor = characters2(next).length) => {
|
|
8340
8696
|
next = sanitizeTerminalText(next);
|
|
8341
|
-
const nextCursor2 =
|
|
8697
|
+
const nextCursor2 = clamp2(cursor, 0, characters2(next).length);
|
|
8342
8698
|
pendingValue.current = next;
|
|
8343
8699
|
valueRef.current = next;
|
|
8344
8700
|
cursorRef.current = nextCursor2;
|
|
@@ -8356,7 +8712,7 @@ var PromptInput = memo(function PromptInput2({
|
|
|
8356
8712
|
const inserted = characters2(sanitizeTerminalText(pasted));
|
|
8357
8713
|
if (inserted.length === 0) return;
|
|
8358
8714
|
const valueCharacters = characters2(valueRef.current);
|
|
8359
|
-
const cursor =
|
|
8715
|
+
const cursor = clamp2(cursorRef.current, 0, valueCharacters.length);
|
|
8360
8716
|
valueCharacters.splice(cursor, 0, ...inserted);
|
|
8361
8717
|
change(valueCharacters.join(""), cursor + inserted.length);
|
|
8362
8718
|
};
|
|
@@ -8380,7 +8736,15 @@ var PromptInput = memo(function PromptInput2({
|
|
|
8380
8736
|
return;
|
|
8381
8737
|
}
|
|
8382
8738
|
if (open3 && key.downArrow) {
|
|
8383
|
-
setSelectionCursor(Math.min(
|
|
8739
|
+
setSelectionCursor(Math.min(optionCount - 1, active2 + 1));
|
|
8740
|
+
return;
|
|
8741
|
+
}
|
|
8742
|
+
if (showFiles && (key.tab || key.return) && !key.shift) {
|
|
8743
|
+
const chosen = files[active2];
|
|
8744
|
+
if (chosen && mention) {
|
|
8745
|
+
const completed = completeMention(safeValue, mention, chosen);
|
|
8746
|
+
change(completed.value, completed.cursor);
|
|
8747
|
+
}
|
|
8384
8748
|
return;
|
|
8385
8749
|
}
|
|
8386
8750
|
if (open3 && key.tab && !key.shift) {
|
|
@@ -8418,7 +8782,7 @@ var PromptInput = memo(function PromptInput2({
|
|
|
8418
8782
|
return;
|
|
8419
8783
|
}
|
|
8420
8784
|
const valueCharacters = characters2(safeValue);
|
|
8421
|
-
const cursor =
|
|
8785
|
+
const cursor = clamp2(inputCursor, 0, valueCharacters.length);
|
|
8422
8786
|
if (key.leftArrow || key.rightArrow || key.home || key.end) {
|
|
8423
8787
|
setInputCursor(nextCursor(cursor, valueCharacters.length, key));
|
|
8424
8788
|
return;
|
|
@@ -8445,6 +8809,7 @@ var PromptInput = memo(function PromptInput2({
|
|
|
8445
8809
|
});
|
|
8446
8810
|
const start = Math.max(0, Math.min(active2 - WINDOW2 + 2, suggestions.length - WINDOW2));
|
|
8447
8811
|
const visible = suggestions.slice(start, start + WINDOW2);
|
|
8812
|
+
const fileStart = Math.max(0, Math.min(active2 - WINDOW2 + 2, files.length - WINDOW2));
|
|
8448
8813
|
return /* @__PURE__ */ jsxs8(Box8, { width: columns, maxWidth: "100%", flexDirection: "column", marginTop: 1, flexShrink: 0, children: [
|
|
8449
8814
|
/* @__PURE__ */ jsxs8(
|
|
8450
8815
|
Box8,
|
|
@@ -8473,6 +8838,18 @@ var PromptInput = memo(function PromptInput2({
|
|
|
8473
8838
|
" ",
|
|
8474
8839
|
sanitizeTerminalText(hint)
|
|
8475
8840
|
] }),
|
|
8841
|
+
showFiles && /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginLeft: 2, children: [
|
|
8842
|
+
files.slice(fileStart, fileStart + WINDOW2).map((file, index) => {
|
|
8843
|
+
const selected = fileStart + index === active2;
|
|
8844
|
+
return /* @__PURE__ */ jsxs8(Text8, { color: selected ? theme.accent : void 0, dimColor: !selected, children: [
|
|
8845
|
+
selected ? "\u276F " : " ",
|
|
8846
|
+
"@",
|
|
8847
|
+
sanitizeTerminalText(file)
|
|
8848
|
+
] }, file);
|
|
8849
|
+
}),
|
|
8850
|
+
files.length > WINDOW2 && /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: strings.more(files.length - WINDOW2) }),
|
|
8851
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: strings.suggestHelp })
|
|
8852
|
+
] }),
|
|
8476
8853
|
open3 && /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginLeft: 2, children: [
|
|
8477
8854
|
visible.map((command, index) => {
|
|
8478
8855
|
const selected = start + index === active2;
|
|
@@ -8498,7 +8875,7 @@ function EditableText({
|
|
|
8498
8875
|
placeholder
|
|
8499
8876
|
}) {
|
|
8500
8877
|
const valueCharacters = characters2(value);
|
|
8501
|
-
const at =
|
|
8878
|
+
const at = clamp2(cursor, 0, valueCharacters.length);
|
|
8502
8879
|
if (valueCharacters.length === 0) {
|
|
8503
8880
|
const placeholderCharacters = characters2(sanitizeTerminalText(placeholder));
|
|
8504
8881
|
return /* @__PURE__ */ jsxs8(Text8, { children: [
|
|
@@ -8522,7 +8899,7 @@ function nextCursor(cursor, length, key) {
|
|
|
8522
8899
|
function characters2(value) {
|
|
8523
8900
|
return [...value];
|
|
8524
8901
|
}
|
|
8525
|
-
function
|
|
8902
|
+
function clamp2(value, min, max) {
|
|
8526
8903
|
return Math.max(min, Math.min(value, max));
|
|
8527
8904
|
}
|
|
8528
8905
|
|
|
@@ -8771,13 +9148,18 @@ import Spinner2 from "ink-spinner";
|
|
|
8771
9148
|
import stringWidth2 from "string-width";
|
|
8772
9149
|
|
|
8773
9150
|
// src/ui/markdown.tsx
|
|
8774
|
-
import { Box as Box11, Text as
|
|
8775
|
-
import { Fragment as Fragment4, useMemo as useMemo3 } from "react";
|
|
9151
|
+
import { Box as Box11, Text as InkText } from "ink";
|
|
9152
|
+
import { createContext as createContext3, Fragment as Fragment4, useContext as useContext3, useMemo as useMemo3 } from "react";
|
|
8776
9153
|
import stringWidth from "string-width";
|
|
8777
9154
|
import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
8778
|
-
|
|
9155
|
+
var DimContext = createContext3(false);
|
|
9156
|
+
function Text10({ dimColor, ...props }) {
|
|
9157
|
+
const dim = useContext3(DimContext);
|
|
9158
|
+
return /* @__PURE__ */ jsx11(InkText, { ...props, dimColor: dimColor ?? dim });
|
|
9159
|
+
}
|
|
9160
|
+
function Markdown({ children, dim = false }) {
|
|
8779
9161
|
const blocks = useMemo3(() => extractBlocks(children), [children]);
|
|
8780
|
-
return /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", children: blocks.map((block, i) => /* @__PURE__ */ jsx11(BlockView, { block }, i)) });
|
|
9162
|
+
return /* @__PURE__ */ jsx11(DimContext.Provider, { value: dim, children: /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", children: blocks.map((block, i) => /* @__PURE__ */ jsx11(BlockView, { block }, i)) }) });
|
|
8781
9163
|
}
|
|
8782
9164
|
function BlockView({ block }) {
|
|
8783
9165
|
switch (block.type) {
|
|
@@ -9425,7 +9807,7 @@ function AssistantView({ bubble: source, maxRows }) {
|
|
|
9425
9807
|
const usedThinkingRows = frameThinking === "" ? 0 : frameThinking.split("\n").length;
|
|
9426
9808
|
const frameText = liveBudget === void 0 ? bubble.text : clipTextToRows(bubble.text, Math.max(1, liveBudget - usedThinkingRows), columns);
|
|
9427
9809
|
return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", marginTop: 1, children: [
|
|
9428
|
-
frameThinking !== "" && /* @__PURE__ */ jsx12(Box12, { flexDirection: "column", maxHeight: thinkingBudget, overflowY: "hidden", children: /* @__PURE__ */ jsx12(Markdown, { children: frameThinking }) }),
|
|
9810
|
+
frameThinking !== "" && /* @__PURE__ */ jsx12(Box12, { flexDirection: "column", maxHeight: thinkingBudget, overflowY: "hidden", children: /* @__PURE__ */ jsx12(Markdown, { dim: true, children: frameThinking }) }),
|
|
9429
9811
|
bubble.streaming ? /* @__PURE__ */ jsx12(Text11, { children: frameText }) : /* @__PURE__ */ jsx12(Markdown, { children: bubble.text }),
|
|
9430
9812
|
bubble.streaming && bubble.text === "" && /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
|
|
9431
9813
|
/* @__PURE__ */ jsx12(Spinner2, { type: "dots" }),
|
|
@@ -9929,6 +10311,11 @@ function App({
|
|
|
9929
10311
|
})
|
|
9930
10312
|
};
|
|
9931
10313
|
try {
|
|
10314
|
+
try {
|
|
10315
|
+
await runtime.persist(history.current);
|
|
10316
|
+
} catch (error) {
|
|
10317
|
+
notice("error", error instanceof Error ? error.message : String(error));
|
|
10318
|
+
}
|
|
9932
10319
|
history.current = await runtime.run(history.current, hooks, controller.signal);
|
|
9933
10320
|
void runtime.persist(history.current).catch((error) => notice("error", error instanceof Error ? error.message : String(error)));
|
|
9934
10321
|
} catch (error) {
|
|
@@ -10256,6 +10643,29 @@ ${strings.modelSet(result.nextModel)}` : ""}`;
|
|
|
10256
10643
|
}
|
|
10257
10644
|
return;
|
|
10258
10645
|
}
|
|
10646
|
+
case "refresh": {
|
|
10647
|
+
const providers = runtime.listProviderItems();
|
|
10648
|
+
if (providers.length === 0) {
|
|
10649
|
+
notice("warn", strings.logoutNothing);
|
|
10650
|
+
return;
|
|
10651
|
+
}
|
|
10652
|
+
let providerId = rest[0];
|
|
10653
|
+
if (!providerId) {
|
|
10654
|
+
providerId = providers.length === 1 ? providers[0]?.key : await pick(strings.titleModelRefresh, providers) ?? void 0;
|
|
10655
|
+
}
|
|
10656
|
+
if (!providerId) return;
|
|
10657
|
+
if (!providers.some((provider) => provider.key === providerId)) {
|
|
10658
|
+
notice("warn", `Provider "${providerId}" is not configured.`);
|
|
10659
|
+
return;
|
|
10660
|
+
}
|
|
10661
|
+
try {
|
|
10662
|
+
const count = await runtime.refreshProviderModels(providerId);
|
|
10663
|
+
notice("info", strings.modelsRefreshed(providerId, count));
|
|
10664
|
+
} catch (error) {
|
|
10665
|
+
notice("error", error instanceof Error ? error.message : String(error));
|
|
10666
|
+
}
|
|
10667
|
+
return;
|
|
10668
|
+
}
|
|
10259
10669
|
case "lang": {
|
|
10260
10670
|
const choice = await pick(
|
|
10261
10671
|
strings.titleLang,
|
|
@@ -10850,6 +11260,10 @@ Rename the file if you want a different name.`);
|
|
|
10850
11260
|
},
|
|
10851
11261
|
[appendAttachment, notice, runtime, strings]
|
|
10852
11262
|
);
|
|
11263
|
+
const listWorkspaceFiles = useCallback(
|
|
11264
|
+
(query) => runtime.listWorkspaceFiles(query).catch(() => []),
|
|
11265
|
+
[runtime]
|
|
11266
|
+
);
|
|
10853
11267
|
const pasteClipboardImage = useCallback(() => {
|
|
10854
11268
|
if (clipboardPasteTask.current) return;
|
|
10855
11269
|
if (attachmentsRef.current.length >= MAX_ATTACHMENTS) {
|
|
@@ -10913,11 +11327,21 @@ Rename the file if you want a different name.`);
|
|
|
10913
11327
|
}
|
|
10914
11328
|
const queuedAttachments = attachmentsRef.current;
|
|
10915
11329
|
if (!text && queuedAttachments.length === 0) return;
|
|
11330
|
+
const mentioned = [];
|
|
11331
|
+
for (const path15 of extractMentions(text)) {
|
|
11332
|
+
if (queuedAttachments.length + mentioned.length >= MAX_ATTACHMENTS) break;
|
|
11333
|
+
try {
|
|
11334
|
+
mentioned.push(await runtime.loadAttachment(path15));
|
|
11335
|
+
} catch {
|
|
11336
|
+
}
|
|
11337
|
+
}
|
|
11338
|
+
if (generation !== attachmentGeneration.current) return;
|
|
10916
11339
|
if (text) setPromptHistory((state) => appendInputHistory(state, text));
|
|
10917
11340
|
if (!detachedInput) setInput("");
|
|
10918
11341
|
const content = [
|
|
10919
11342
|
...text ? [{ type: "text", text }] : [],
|
|
10920
|
-
...queuedAttachments
|
|
11343
|
+
...queuedAttachments,
|
|
11344
|
+
...mentioned
|
|
10921
11345
|
];
|
|
10922
11346
|
replaceAttachments([]);
|
|
10923
11347
|
const item = { kind: "message", text, content };
|
|
@@ -10992,11 +11416,11 @@ Rename the file if you want a different name.`);
|
|
|
10992
11416
|
Onboarding,
|
|
10993
11417
|
{
|
|
10994
11418
|
onSubmit: async (url, key) => {
|
|
10995
|
-
const
|
|
11419
|
+
const summary2 = await runtime.addProvider(url, key);
|
|
10996
11420
|
setSetup(false);
|
|
10997
|
-
notice("info", `${
|
|
11421
|
+
notice("info", `${summary2}
|
|
10998
11422
|
${strings.configAt(runtime.configPath())}`);
|
|
10999
|
-
return
|
|
11423
|
+
return summary2;
|
|
11000
11424
|
}
|
|
11001
11425
|
}
|
|
11002
11426
|
)
|
|
@@ -11073,6 +11497,7 @@ ${strings.configAt(runtime.configPath())}`);
|
|
|
11073
11497
|
onSubmit: submit,
|
|
11074
11498
|
onPastePath: tryQueueAutomaticAttachment,
|
|
11075
11499
|
onPasteImage: pasteClipboardImage,
|
|
11500
|
+
onListFiles: listWorkspaceFiles,
|
|
11076
11501
|
disabled: busy,
|
|
11077
11502
|
pending: pendingCount,
|
|
11078
11503
|
hint: busy ? strings.escCancel : void 0,
|