@wrongstack/core 0.291.2 → 0.292.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/coordination/director-tools.d.ts.map +1 -1
- package/dist/coordination/index.js +113 -113
- package/dist/coordination/index.js.map +3 -3
- package/dist/core/agent-loop.d.ts.map +1 -1
- package/dist/core/agent-response.d.ts.map +1 -1
- package/dist/core/context.d.ts +10 -0
- package/dist/core/context.d.ts.map +1 -1
- package/dist/core/system-prompt-builder.d.ts +20 -1
- package/dist/core/system-prompt-builder.d.ts.map +1 -1
- package/dist/defaults/index.d.ts +1 -0
- package/dist/defaults/index.d.ts.map +1 -1
- package/dist/defaults/index.js +3132 -2440
- package/dist/defaults/index.js.map +4 -4
- package/dist/execution/auto-compaction-middleware.d.ts +34 -2
- package/dist/execution/auto-compaction-middleware.d.ts.map +1 -1
- package/dist/execution/compaction-core.d.ts +59 -0
- package/dist/execution/compaction-core.d.ts.map +1 -1
- package/dist/execution/compactor.d.ts.map +1 -1
- package/dist/execution/error-handler.d.ts +7 -0
- package/dist/execution/error-handler.d.ts.map +1 -1
- package/dist/execution/index.d.ts +1 -0
- package/dist/execution/index.d.ts.map +1 -1
- package/dist/execution/index.js +4677 -3924
- package/dist/execution/index.js.map +4 -4
- package/dist/execution/intelligent-compactor.d.ts.map +1 -1
- package/dist/execution/model-runtime.d.ts.map +1 -1
- package/dist/execution/selective-compactor.d.ts +10 -0
- package/dist/execution/selective-compactor.d.ts.map +1 -1
- package/dist/execution/subagent-compaction.d.ts +24 -0
- package/dist/execution/subagent-compaction.d.ts.map +1 -0
- package/dist/hq/alerts.d.ts +5 -0
- package/dist/hq/alerts.d.ts.map +1 -1
- package/dist/hq/auth-audit.d.ts +115 -0
- package/dist/hq/auth-audit.d.ts.map +1 -0
- package/dist/hq/auth-store.d.ts +98 -2
- package/dist/hq/auth-store.d.ts.map +1 -1
- package/dist/hq/brain-bridge.d.ts +2 -8
- package/dist/hq/brain-bridge.d.ts.map +1 -1
- package/dist/hq/bridge-context.d.ts +74 -0
- package/dist/hq/bridge-context.d.ts.map +1 -0
- package/dist/hq/cost-bridge.d.ts +4 -10
- package/dist/hq/cost-bridge.d.ts.map +1 -1
- package/dist/hq/fleet-bridge.d.ts +2 -8
- package/dist/hq/fleet-bridge.d.ts.map +1 -1
- package/dist/hq/index.d.ts +2 -0
- package/dist/hq/index.d.ts.map +1 -1
- package/dist/hq/index.js +358 -222
- package/dist/hq/index.js.map +4 -4
- package/dist/hq/protocol/session.d.ts +13 -0
- package/dist/hq/protocol/session.d.ts.map +1 -1
- package/dist/hq/tool-bridge.d.ts +2 -8
- package/dist/hq/tool-bridge.d.ts.map +1 -1
- package/dist/hq/worktree-bridge.d.ts +2 -8
- package/dist/hq/worktree-bridge.d.ts.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6054 -4741
- package/dist/index.js.map +4 -4
- package/dist/infrastructure/index.js +32 -15
- package/dist/infrastructure/index.js.map +2 -2
- package/dist/infrastructure/provider-cache-ledger.d.ts +48 -0
- package/dist/infrastructure/provider-cache-ledger.d.ts.map +1 -0
- package/dist/infrastructure/token-counter.d.ts +4 -2
- package/dist/infrastructure/token-counter.d.ts.map +1 -1
- package/dist/kernel/events/brain-events.d.ts +2 -0
- package/dist/kernel/events/brain-events.d.ts.map +1 -1
- package/dist/kernel/events/memory-events.d.ts +102 -0
- package/dist/kernel/events/memory-events.d.ts.map +1 -1
- package/dist/kernel/events/session-events.d.ts +35 -0
- package/dist/kernel/events/session-events.d.ts.map +1 -1
- package/dist/models/index.js +161 -78
- package/dist/models/index.js.map +3 -3
- package/dist/models/llm-selector.d.ts.map +1 -1
- package/dist/notifications/index.d.ts +40 -0
- package/dist/notifications/index.d.ts.map +1 -0
- package/dist/notifications/notifier-impl.d.ts +34 -0
- package/dist/notifications/notifier-impl.d.ts.map +1 -0
- package/dist/notifications/notifier.d.ts +95 -0
- package/dist/notifications/notifier.d.ts.map +1 -0
- package/dist/notifications/types.d.ts +111 -0
- package/dist/notifications/types.d.ts.map +1 -0
- package/dist/plugin/api.d.ts +10 -1
- package/dist/plugin/api.d.ts.map +1 -1
- package/dist/registry/provider-registry.d.ts +1 -1
- package/dist/registry/provider-registry.d.ts.map +1 -1
- package/dist/security/index.js +15 -15
- package/dist/security/index.js.map +3 -3
- package/dist/skills/index.js +80 -80
- package/dist/skills/index.js.map +3 -3
- package/dist/storage/config-loader.d.ts +3 -5
- package/dist/storage/config-loader.d.ts.map +1 -1
- package/dist/storage/index.js +325 -259
- package/dist/storage/index.js.map +4 -4
- package/dist/storage/memory-consolidator.d.ts +14 -2
- package/dist/storage/memory-consolidator.d.ts.map +1 -1
- package/dist/storage/queue-store.d.ts +2 -0
- package/dist/storage/queue-store.d.ts.map +1 -1
- package/dist/tools/index.js +6 -6
- package/dist/tools/index.js.map +3 -3
- package/dist/tools/mcp-control.d.ts +1 -1
- package/dist/types/config.d.ts +64 -20
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/index.js +60 -34
- package/dist/types/index.js.map +3 -3
- package/dist/types/plugin.d.ts +11 -0
- package/dist/types/plugin.d.ts.map +1 -1
- package/dist/types/provider.d.ts +31 -0
- package/dist/types/provider.d.ts.map +1 -1
- package/dist/types/token-counter.d.ts +8 -1
- package/dist/types/token-counter.d.ts.map +1 -1
- package/dist/utils/cache-key.d.ts +19 -0
- package/dist/utils/cache-key.d.ts.map +1 -0
- package/dist/utils/compaction-preview.d.ts +8 -0
- package/dist/utils/compaction-preview.d.ts.map +1 -0
- package/dist/utils/context-breakdown.d.ts +59 -0
- package/dist/utils/context-breakdown.d.ts.map +1 -0
- package/dist/utils/context-evidence.d.ts +22 -0
- package/dist/utils/context-evidence.d.ts.map +1 -1
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1730 -1543
- package/dist/utils/index.js.map +4 -4
- package/dist/utils/token-estimate.d.ts +27 -0
- package/dist/utils/token-estimate.d.ts.map +1 -1
- package/instructions/llm/memory-consolidator.md +19 -3
- package/instructions/system.md +10 -2
- package/package.json +2 -2
package/dist/utils/index.js
CHANGED
|
@@ -727,6 +727,19 @@ async function backupConfigFile(filePath, paths) {
|
|
|
727
727
|
}
|
|
728
728
|
}
|
|
729
729
|
|
|
730
|
+
// src/utils/cache-key.ts
|
|
731
|
+
import { createHash } from "node:crypto";
|
|
732
|
+
var keyCache = /* @__PURE__ */ new WeakMap();
|
|
733
|
+
function deriveCachePrefixKey(systemPrompt) {
|
|
734
|
+
const cached = keyCache.get(systemPrompt);
|
|
735
|
+
if (cached !== void 0) return cached;
|
|
736
|
+
const h = createHash("sha256");
|
|
737
|
+
for (const block of systemPrompt) h.update(block.text).update("\0");
|
|
738
|
+
const key = `ws-${h.digest("hex").slice(0, 32)}`;
|
|
739
|
+
keyCache.set(systemPrompt, key);
|
|
740
|
+
return key;
|
|
741
|
+
}
|
|
742
|
+
|
|
730
743
|
// src/utils/config-json.ts
|
|
731
744
|
import * as fs3 from "node:fs/promises";
|
|
732
745
|
async function readJsonObjectFile(filePath) {
|
|
@@ -1209,217 +1222,6 @@ function metadataReferencedByText(metadata, haystack) {
|
|
|
1209
1222
|
return false;
|
|
1210
1223
|
}
|
|
1211
1224
|
|
|
1212
|
-
// src/utils/deep-merge.ts
|
|
1213
|
-
var FORBIDDEN_PROTO_KEYS = /* @__PURE__ */ new Set([
|
|
1214
|
-
"__proto__",
|
|
1215
|
-
"constructor",
|
|
1216
|
-
"prototype",
|
|
1217
|
-
"__defineGetter__",
|
|
1218
|
-
"__defineSetter__",
|
|
1219
|
-
"__lookupGetter__",
|
|
1220
|
-
"__lookupSetter__"
|
|
1221
|
-
]);
|
|
1222
|
-
function isPrimitiveArray(a) {
|
|
1223
|
-
return a.every((v) => v === null || typeof v !== "object" && typeof v !== "function");
|
|
1224
|
-
}
|
|
1225
|
-
function deepMerge(base, patch, options = {}) {
|
|
1226
|
-
const {
|
|
1227
|
-
conflictResolution = "prefer-patch",
|
|
1228
|
-
arrayMode = "replace",
|
|
1229
|
-
protectProto = true,
|
|
1230
|
-
onNonPrimitiveArrayReplace
|
|
1231
|
-
} = options;
|
|
1232
|
-
if (typeof base !== "object" || base === null) {
|
|
1233
|
-
return conflictResolution === "prefer-patch" ? patch : base;
|
|
1234
|
-
}
|
|
1235
|
-
if (typeof patch !== "object" || patch === null) {
|
|
1236
|
-
return conflictResolution === "prefer-patch" ? patch : base;
|
|
1237
|
-
}
|
|
1238
|
-
if (Array.isArray(base) && Array.isArray(patch)) {
|
|
1239
|
-
if (arrayMode === "concat-primitives" && isPrimitiveArray(base) && isPrimitiveArray(patch)) {
|
|
1240
|
-
return [.../* @__PURE__ */ new Set([...base, ...patch])];
|
|
1241
|
-
}
|
|
1242
|
-
return conflictResolution === "prefer-patch" ? patch : base;
|
|
1243
|
-
}
|
|
1244
|
-
if (Array.isArray(base) || Array.isArray(patch)) {
|
|
1245
|
-
return conflictResolution === "prefer-patch" ? patch : base;
|
|
1246
|
-
}
|
|
1247
|
-
const baseObj = base;
|
|
1248
|
-
const patchObj = patch;
|
|
1249
|
-
const out = { ...baseObj };
|
|
1250
|
-
for (const [k, v] of Object.entries(patchObj)) {
|
|
1251
|
-
if (protectProto && FORBIDDEN_PROTO_KEYS.has(k)) continue;
|
|
1252
|
-
const existing = out[k];
|
|
1253
|
-
if (v !== null && typeof v === "object" && !Array.isArray(v) && existing !== null && typeof existing === "object" && !Array.isArray(existing)) {
|
|
1254
|
-
out[k] = deepMerge(existing, v, options);
|
|
1255
|
-
} else if (Array.isArray(v) && Array.isArray(existing)) {
|
|
1256
|
-
if (onNonPrimitiveArrayReplace && !isPrimitiveArray(v)) {
|
|
1257
|
-
onNonPrimitiveArrayReplace(k, existing.length, v.length);
|
|
1258
|
-
}
|
|
1259
|
-
out[k] = deepMerge(existing, v, options);
|
|
1260
|
-
} else if (v !== void 0) {
|
|
1261
|
-
if (onNonPrimitiveArrayReplace && Array.isArray(v) && !isPrimitiveArray(v)) {
|
|
1262
|
-
const existingLen = Array.isArray(existing) ? existing.length : 0;
|
|
1263
|
-
onNonPrimitiveArrayReplace(k, existingLen, v.length);
|
|
1264
|
-
}
|
|
1265
|
-
out[k] = v;
|
|
1266
|
-
}
|
|
1267
|
-
}
|
|
1268
|
-
return out;
|
|
1269
|
-
}
|
|
1270
|
-
|
|
1271
|
-
// src/utils/diff.ts
|
|
1272
|
-
function myersDiff(a, b) {
|
|
1273
|
-
const N = a.length;
|
|
1274
|
-
const M = b.length;
|
|
1275
|
-
const max = N + M;
|
|
1276
|
-
if (max === 0) return [];
|
|
1277
|
-
const v = /* @__PURE__ */ new Map();
|
|
1278
|
-
v.set(1, 0);
|
|
1279
|
-
const trace = [];
|
|
1280
|
-
for (let d = 0; d <= max; d++) {
|
|
1281
|
-
const snapshot = new Map(v);
|
|
1282
|
-
trace.push(snapshot);
|
|
1283
|
-
for (let k = -d; k <= d; k += 2) {
|
|
1284
|
-
const left = v.get(k - 1) ?? -1;
|
|
1285
|
-
const right = v.get(k + 1) ?? -1;
|
|
1286
|
-
let x;
|
|
1287
|
-
if (k === -d || k !== d && left < right) {
|
|
1288
|
-
x = right;
|
|
1289
|
-
} else {
|
|
1290
|
-
x = left + 1;
|
|
1291
|
-
}
|
|
1292
|
-
let y = x - k;
|
|
1293
|
-
while (x < N && y < M && a[x] === b[y]) {
|
|
1294
|
-
x++;
|
|
1295
|
-
y++;
|
|
1296
|
-
}
|
|
1297
|
-
v.set(k, x);
|
|
1298
|
-
if (x >= N && y >= M) {
|
|
1299
|
-
return backtrack(trace, a, b, N, M, d);
|
|
1300
|
-
}
|
|
1301
|
-
}
|
|
1302
|
-
}
|
|
1303
|
-
return [];
|
|
1304
|
-
}
|
|
1305
|
-
function backtrack(trace, a, b, N, M, finalD) {
|
|
1306
|
-
const edits = [];
|
|
1307
|
-
let x = N;
|
|
1308
|
-
let y = M;
|
|
1309
|
-
for (let d = finalD; d > 0; d--) {
|
|
1310
|
-
const v = trace[d];
|
|
1311
|
-
if (!v) break;
|
|
1312
|
-
const k = x - y;
|
|
1313
|
-
const left = v.get(k - 1) ?? -1;
|
|
1314
|
-
const right = v.get(k + 1) ?? -1;
|
|
1315
|
-
let prevK;
|
|
1316
|
-
if (k === -d || k !== d && left < right) {
|
|
1317
|
-
prevK = k + 1;
|
|
1318
|
-
} else {
|
|
1319
|
-
prevK = k - 1;
|
|
1320
|
-
}
|
|
1321
|
-
const prevX = v.get(prevK) ?? 0;
|
|
1322
|
-
const prevY = prevX - prevK;
|
|
1323
|
-
while (x > prevX && y > prevY) {
|
|
1324
|
-
edits.push({ op: "equal", a: x - 1, b: y - 1, line: a[x - 1] ?? "" });
|
|
1325
|
-
x--;
|
|
1326
|
-
y--;
|
|
1327
|
-
}
|
|
1328
|
-
if (d > 0) {
|
|
1329
|
-
if (x === prevX) {
|
|
1330
|
-
edits.push({ op: "insert", a: x, b: y - 1, line: b[y - 1] ?? "" });
|
|
1331
|
-
} else {
|
|
1332
|
-
edits.push({ op: "delete", a: x - 1, b: y, line: a[x - 1] ?? "" });
|
|
1333
|
-
}
|
|
1334
|
-
x = prevX;
|
|
1335
|
-
y = prevY;
|
|
1336
|
-
}
|
|
1337
|
-
}
|
|
1338
|
-
while (x > 0 && y > 0) {
|
|
1339
|
-
edits.push({ op: "equal", a: x - 1, b: y - 1, line: a[x - 1] ?? "" });
|
|
1340
|
-
x--;
|
|
1341
|
-
y--;
|
|
1342
|
-
}
|
|
1343
|
-
return edits.reverse();
|
|
1344
|
-
}
|
|
1345
|
-
function unifiedDiff(oldText, newText, opts = {}) {
|
|
1346
|
-
const context = opts.context ?? 3;
|
|
1347
|
-
const a = oldText.split("\n");
|
|
1348
|
-
const b = newText.split("\n");
|
|
1349
|
-
if (a[a.length - 1] === "") a.pop();
|
|
1350
|
-
if (b[b.length - 1] === "") b.pop();
|
|
1351
|
-
const edits = myersDiff(a, b);
|
|
1352
|
-
if (edits.every((e) => e.op === "equal")) return "";
|
|
1353
|
-
const hunks = [];
|
|
1354
|
-
let i = 0;
|
|
1355
|
-
while (i < edits.length) {
|
|
1356
|
-
while (i < edits.length && edits[i]?.op === "equal") i++;
|
|
1357
|
-
if (i >= edits.length) break;
|
|
1358
|
-
const hunkStart = Math.max(0, i - context);
|
|
1359
|
-
const lines = [];
|
|
1360
|
-
let aStart = (edits[hunkStart]?.a ?? 0) + 1;
|
|
1361
|
-
let bStart = (edits[hunkStart]?.b ?? 0) + 1;
|
|
1362
|
-
let aCount = 0;
|
|
1363
|
-
let bCount = 0;
|
|
1364
|
-
let cursor = hunkStart;
|
|
1365
|
-
let trailing = 0;
|
|
1366
|
-
while (cursor < edits.length) {
|
|
1367
|
-
const e = edits[cursor];
|
|
1368
|
-
if (!e) break;
|
|
1369
|
-
if (e.op === "equal") {
|
|
1370
|
-
trailing++;
|
|
1371
|
-
if (trailing > context * 2) break;
|
|
1372
|
-
} else {
|
|
1373
|
-
trailing = 0;
|
|
1374
|
-
}
|
|
1375
|
-
if (e.op === "equal") {
|
|
1376
|
-
lines.push(` ${e.line}`);
|
|
1377
|
-
aCount++;
|
|
1378
|
-
bCount++;
|
|
1379
|
-
} else if (e.op === "delete") {
|
|
1380
|
-
lines.push(`-${e.line}`);
|
|
1381
|
-
aCount++;
|
|
1382
|
-
} else {
|
|
1383
|
-
lines.push(`+${e.line}`);
|
|
1384
|
-
bCount++;
|
|
1385
|
-
}
|
|
1386
|
-
cursor++;
|
|
1387
|
-
}
|
|
1388
|
-
while (lines.length > 0 && lines[lines.length - 1]?.startsWith(" ") && trailing > context) {
|
|
1389
|
-
lines.pop();
|
|
1390
|
-
aCount--;
|
|
1391
|
-
bCount--;
|
|
1392
|
-
trailing--;
|
|
1393
|
-
}
|
|
1394
|
-
if (aCount === 0) aStart = 0;
|
|
1395
|
-
if (bCount === 0) bStart = 0;
|
|
1396
|
-
hunks.push({ aStart, bStart, lines });
|
|
1397
|
-
i = cursor;
|
|
1398
|
-
}
|
|
1399
|
-
if (hunks.length === 0) return "";
|
|
1400
|
-
let out = "";
|
|
1401
|
-
out += `--- ${opts.fromFile ?? "a"}
|
|
1402
|
-
`;
|
|
1403
|
-
out += `+++ ${opts.toFile ?? "b"}
|
|
1404
|
-
`;
|
|
1405
|
-
for (const h of hunks) {
|
|
1406
|
-
let aCount = 0;
|
|
1407
|
-
let bCount = 0;
|
|
1408
|
-
for (const l of h.lines) {
|
|
1409
|
-
if (l.startsWith(" ")) {
|
|
1410
|
-
aCount++;
|
|
1411
|
-
bCount++;
|
|
1412
|
-
} else if (l.startsWith("-")) aCount++;
|
|
1413
|
-
else if (l.startsWith("+")) bCount++;
|
|
1414
|
-
}
|
|
1415
|
-
out += `@@ -${h.aStart},${aCount} +${h.bStart},${bCount} @@
|
|
1416
|
-
`;
|
|
1417
|
-
out += `${h.lines.join("\n")}
|
|
1418
|
-
`;
|
|
1419
|
-
}
|
|
1420
|
-
return out;
|
|
1421
|
-
}
|
|
1422
|
-
|
|
1423
1225
|
// src/utils/error.ts
|
|
1424
1226
|
function toErrorMessage(err) {
|
|
1425
1227
|
return err instanceof Error ? err.message : String(err);
|
|
@@ -1435,1511 +1237,1894 @@ function expectDefined(value, label) {
|
|
|
1435
1237
|
return value;
|
|
1436
1238
|
}
|
|
1437
1239
|
|
|
1438
|
-
// src/utils/
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
for (
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
if (pat[i] === "/") i++;
|
|
1460
|
-
} else {
|
|
1461
|
-
re += "[^/\\\\]*";
|
|
1462
|
-
i++;
|
|
1463
|
-
}
|
|
1464
|
-
} else if (c === "?") {
|
|
1465
|
-
re += "[^/\\\\]";
|
|
1466
|
-
i++;
|
|
1467
|
-
} else if (c === "[") {
|
|
1468
|
-
let cls = "[";
|
|
1469
|
-
i++;
|
|
1470
|
-
if (pat[i] === "!" || pat[i] === "^") {
|
|
1471
|
-
cls += "^";
|
|
1472
|
-
i++;
|
|
1473
|
-
}
|
|
1474
|
-
while (i < pat.length && pat[i] !== "]") {
|
|
1475
|
-
const ch = pat[i] ?? "";
|
|
1476
|
-
if (ch === "\\") cls += "\\\\";
|
|
1477
|
-
else if (ch === "]" || ch === "^") cls += `\\${ch}`;
|
|
1478
|
-
else cls += ch;
|
|
1479
|
-
i++;
|
|
1480
|
-
}
|
|
1481
|
-
cls += "]";
|
|
1482
|
-
re += cls;
|
|
1483
|
-
i++;
|
|
1484
|
-
} else {
|
|
1485
|
-
re += c.replace(/[.+^${}()|\\]/g, "\\$&");
|
|
1486
|
-
i++;
|
|
1487
|
-
}
|
|
1488
|
-
}
|
|
1489
|
-
return new RegExp(re + "$");
|
|
1490
|
-
}
|
|
1491
|
-
function baseDir(pat) {
|
|
1492
|
-
let firstGlob = pat.length;
|
|
1493
|
-
for (let i = 0; i < pat.length; i++) {
|
|
1494
|
-
if (GLOB_CHARS.has(expectDefined(pat[i]))) {
|
|
1495
|
-
firstGlob = i;
|
|
1496
|
-
break;
|
|
1497
|
-
}
|
|
1498
|
-
}
|
|
1499
|
-
const cut = Math.max(
|
|
1500
|
-
pat.lastIndexOf(SEP, firstGlob - 1),
|
|
1501
|
-
pat.lastIndexOf("/", firstGlob - 1)
|
|
1502
|
-
);
|
|
1503
|
-
return cut < 0 ? "." : pat.slice(0, cut);
|
|
1504
|
-
}
|
|
1505
|
-
async function expandGlob(pattern) {
|
|
1506
|
-
if (!isGlob(pattern)) return [pattern];
|
|
1507
|
-
const results = /* @__PURE__ */ new Set();
|
|
1508
|
-
const abs = isAbsolute2(pattern);
|
|
1509
|
-
const base = abs ? baseDir(pattern) : baseDir(pattern);
|
|
1510
|
-
const relPat = base === "." ? pattern : pattern.slice(base.length + 1);
|
|
1511
|
-
async function walk2(dir, pat) {
|
|
1512
|
-
let entries;
|
|
1513
|
-
try {
|
|
1514
|
-
entries = await fsp.readdir(dir);
|
|
1515
|
-
} catch {
|
|
1516
|
-
return;
|
|
1517
|
-
}
|
|
1518
|
-
const firstGlob = pat.search(/[*?[[]/);
|
|
1519
|
-
if (firstGlob < 0) {
|
|
1520
|
-
const re = globToRegex(pat);
|
|
1521
|
-
for (const e of entries) {
|
|
1522
|
-
if (re.test(e)) {
|
|
1523
|
-
const full = `${dir}${SEP}${e}`;
|
|
1524
|
-
results.add(abs ? resolve2(full) : full);
|
|
1240
|
+
// src/utils/message-invariants.ts
|
|
1241
|
+
function repairToolUseAdjacency(messages) {
|
|
1242
|
+
const removedToolUses = [];
|
|
1243
|
+
const removedToolResults = [];
|
|
1244
|
+
let removedMessages = 0;
|
|
1245
|
+
let changed = false;
|
|
1246
|
+
const out = [];
|
|
1247
|
+
for (let i = 0; i < messages.length; i++) {
|
|
1248
|
+
const original = expectDefined(messages[i]);
|
|
1249
|
+
let msg = original;
|
|
1250
|
+
if (hasToolUse(msg)) {
|
|
1251
|
+
const nextIds = toolResultIds(messages[i + 1]);
|
|
1252
|
+
const filtered = mapContent(msg, (blocks) => {
|
|
1253
|
+
const next = [];
|
|
1254
|
+
for (const block of blocks) {
|
|
1255
|
+
if (block.type === "tool_use" && !nextIds.has(block.id)) {
|
|
1256
|
+
removedToolUses.push(block.id);
|
|
1257
|
+
changed = true;
|
|
1258
|
+
continue;
|
|
1259
|
+
}
|
|
1260
|
+
next.push(block);
|
|
1525
1261
|
}
|
|
1526
|
-
|
|
1527
|
-
|
|
1262
|
+
return next;
|
|
1263
|
+
});
|
|
1264
|
+
msg = filtered ?? msg;
|
|
1528
1265
|
}
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
}
|
|
1541
|
-
} else if (before === "") {
|
|
1542
|
-
const re = globToRegex(rest);
|
|
1543
|
-
for (const e of entries) {
|
|
1544
|
-
if (re.test(e)) {
|
|
1545
|
-
const full = `${dir}${SEP}${e}`;
|
|
1546
|
-
results.add(abs ? resolve2(full) : full);
|
|
1547
|
-
}
|
|
1548
|
-
}
|
|
1549
|
-
} else {
|
|
1550
|
-
const seg = before.replace(/[*?[\]]/g, "").replace(/\/$/, "");
|
|
1551
|
-
if (entries.includes(seg)) {
|
|
1552
|
-
const full = `${dir}${SEP}${seg}`;
|
|
1553
|
-
try {
|
|
1554
|
-
const stat3 = await fsp.stat(full);
|
|
1555
|
-
if (stat3.isDirectory()) await walk2(full, rest);
|
|
1556
|
-
} catch {
|
|
1266
|
+
if (hasToolResult(msg)) {
|
|
1267
|
+
const allowed = toolUseIds(out[out.length - 1]);
|
|
1268
|
+
const filtered = mapContent(msg, (blocks) => {
|
|
1269
|
+
const next = [];
|
|
1270
|
+
for (const block of blocks) {
|
|
1271
|
+
if (block.type === "tool_result" && !allowed.has(block.tool_use_id)) {
|
|
1272
|
+
removedToolResults.push(block.tool_use_id);
|
|
1273
|
+
changed = true;
|
|
1274
|
+
continue;
|
|
1275
|
+
}
|
|
1276
|
+
next.push(block);
|
|
1557
1277
|
}
|
|
1558
|
-
|
|
1278
|
+
return next;
|
|
1279
|
+
});
|
|
1280
|
+
msg = filtered ?? msg;
|
|
1281
|
+
}
|
|
1282
|
+
if (isEmptyMessage(msg)) {
|
|
1283
|
+
removedMessages++;
|
|
1284
|
+
changed = true;
|
|
1285
|
+
continue;
|
|
1559
1286
|
}
|
|
1287
|
+
out.push(msg);
|
|
1560
1288
|
}
|
|
1561
|
-
|
|
1562
|
-
|
|
1289
|
+
return {
|
|
1290
|
+
messages: changed ? out : messages,
|
|
1291
|
+
report: { changed, removedToolUses, removedToolResults, removedMessages }
|
|
1292
|
+
};
|
|
1563
1293
|
}
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
function escapeRegex(s) {
|
|
1567
|
-
return s.replace(/[.+^${}()|\\]/g, "\\$&");
|
|
1294
|
+
function hasToolUse(msg) {
|
|
1295
|
+
return contentBlocks(msg).some((b) => b.type === "tool_use");
|
|
1568
1296
|
}
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
function
|
|
1573
|
-
const
|
|
1574
|
-
if (
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
for (let i = 0; i < Math.floor(CACHE_MAX_SIZE / 4); i++) {
|
|
1578
|
-
COMPILED_GLOB_CACHE.delete(expectDefined(keys[i]));
|
|
1579
|
-
}
|
|
1297
|
+
function hasToolResult(msg) {
|
|
1298
|
+
return contentBlocks(msg).some((b) => b.type === "tool_result");
|
|
1299
|
+
}
|
|
1300
|
+
function toolUseIds(msg) {
|
|
1301
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1302
|
+
if (msg?.role !== "assistant") return ids;
|
|
1303
|
+
for (const block of contentBlocks(msg)) {
|
|
1304
|
+
if (block.type === "tool_use") ids.add(block.id);
|
|
1580
1305
|
}
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1306
|
+
return ids;
|
|
1307
|
+
}
|
|
1308
|
+
function toolResultIds(msg) {
|
|
1309
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1310
|
+
if (msg?.role !== "user") return ids;
|
|
1311
|
+
for (const block of contentBlocks(msg)) {
|
|
1312
|
+
if (block.type === "tool_result") ids.add(block.tool_use_id);
|
|
1586
1313
|
}
|
|
1587
|
-
|
|
1588
|
-
return re;
|
|
1314
|
+
return ids;
|
|
1589
1315
|
}
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1316
|
+
function contentBlocks(msg) {
|
|
1317
|
+
return msg && Array.isArray(msg.content) ? msg.content : [];
|
|
1318
|
+
}
|
|
1319
|
+
function mapContent(msg, fn) {
|
|
1320
|
+
if (!Array.isArray(msg.content)) return msg;
|
|
1321
|
+
const next = fn(msg.content);
|
|
1322
|
+
if (next.length === msg.content.length && next.every((b, idx) => b === msg.content[idx])) {
|
|
1323
|
+
return msg;
|
|
1594
1324
|
}
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
}
|
|
1608
|
-
} else if (c === "?") {
|
|
1609
|
-
re += "[^/]";
|
|
1610
|
-
i++;
|
|
1611
|
-
} else if (c === "[") {
|
|
1612
|
-
let cls = "[";
|
|
1613
|
-
i++;
|
|
1614
|
-
if (pattern[i] === "!" || pattern[i] === "^") {
|
|
1615
|
-
cls += "^";
|
|
1616
|
-
i++;
|
|
1617
|
-
}
|
|
1618
|
-
while (i < pattern.length && pattern[i] !== "]") {
|
|
1619
|
-
const ch = pattern[i] ?? "";
|
|
1620
|
-
if (ch === "\\") {
|
|
1621
|
-
cls += "\\\\";
|
|
1622
|
-
} else if (ch === "]" || ch === "^") {
|
|
1623
|
-
cls += `\\${ch}`;
|
|
1624
|
-
} else {
|
|
1625
|
-
cls += ch;
|
|
1626
|
-
}
|
|
1627
|
-
i++;
|
|
1628
|
-
}
|
|
1629
|
-
cls += "]";
|
|
1630
|
-
re += cls;
|
|
1631
|
-
i++;
|
|
1632
|
-
} else {
|
|
1633
|
-
re += escapeRegex(c ?? "");
|
|
1634
|
-
i++;
|
|
1325
|
+
return { ...msg, content: next };
|
|
1326
|
+
}
|
|
1327
|
+
function hasMeaningfulContent(content) {
|
|
1328
|
+
if (typeof content === "string") return content.trim().length > 0;
|
|
1329
|
+
for (const block of content) {
|
|
1330
|
+
if (block.type === "text") {
|
|
1331
|
+
if (block.text.trim().length > 0) return true;
|
|
1332
|
+
continue;
|
|
1333
|
+
}
|
|
1334
|
+
if (block.type === "thinking") {
|
|
1335
|
+
if (block.thinking.trim().length > 0 || block.signature) return true;
|
|
1336
|
+
continue;
|
|
1635
1337
|
}
|
|
1338
|
+
return true;
|
|
1636
1339
|
}
|
|
1637
|
-
|
|
1638
|
-
return new RegExp(re);
|
|
1340
|
+
return false;
|
|
1639
1341
|
}
|
|
1640
|
-
function
|
|
1641
|
-
return
|
|
1342
|
+
function isEmptyMessage(msg) {
|
|
1343
|
+
return !hasMeaningfulContent(msg.content);
|
|
1642
1344
|
}
|
|
1643
|
-
|
|
1644
|
-
|
|
1345
|
+
|
|
1346
|
+
// src/core/agent-response.ts
|
|
1347
|
+
var MAX_TODO_SNAPSHOT_ITEMS = 10;
|
|
1348
|
+
var MAX_TODO_SNAPSHOT_CONTENT = 180;
|
|
1349
|
+
function buildLiveNextStepsGateBlock(ctx) {
|
|
1350
|
+
if (ctx.agentId !== "leader") return void 0;
|
|
1351
|
+
const openTodos = ctx.todos.filter(
|
|
1352
|
+
(todo) => todo.status === "pending" || todo.status === "in_progress"
|
|
1353
|
+
);
|
|
1354
|
+
if (openTodos.length === 0) {
|
|
1355
|
+
return {
|
|
1356
|
+
type: "text",
|
|
1357
|
+
text: [
|
|
1358
|
+
"[nextsteps_gate]",
|
|
1359
|
+
"Authoritative live state for this request: open todos = 0.",
|
|
1360
|
+
"On the final response, you MUST take exactly one branch:",
|
|
1361
|
+
"1. If at least one genuinely useful follow-on action exists, include a balanced <nextsteps> block containing 1-4 exact prompt messages that can be submitted back to you through the current TUI or WebUI input.",
|
|
1362
|
+
"Every item must ask the agent to perform work. Never put a human-only chore or an instruction addressed to the user inside <nextsteps>; natural-language agent-directed imperatives are valid and need not be shell commands.",
|
|
1363
|
+
"2. If no useful follow-on action truly exists, omit <nextsteps> and explicitly tell the user in normal prose that no further steps are needed for this task.",
|
|
1364
|
+
"Silently omitting both is invalid. Do not decide by chance, tone, or response length, and do not invent filler suggestions.",
|
|
1365
|
+
"[/nextsteps_gate]"
|
|
1366
|
+
].join("\n"),
|
|
1367
|
+
cache_control: { type: "ephemeral" }
|
|
1368
|
+
};
|
|
1369
|
+
}
|
|
1370
|
+
const todoSnapshot = openTodos.slice(0, MAX_TODO_SNAPSHOT_ITEMS).map((todo) => {
|
|
1371
|
+
const normalized = todo.content.replace(/\s+/g, " ").trim();
|
|
1372
|
+
const content = normalized.length > MAX_TODO_SNAPSHOT_CONTENT ? `${normalized.slice(0, MAX_TODO_SNAPSHOT_CONTENT - 1)}\u2026` : normalized;
|
|
1373
|
+
return `- [${todo.status}] ${content}`;
|
|
1374
|
+
});
|
|
1375
|
+
const omitted = openTodos.length - todoSnapshot.length;
|
|
1376
|
+
if (omitted > 0) todoSnapshot.push(`- \u2026and ${omitted} more open todo(s)`);
|
|
1377
|
+
return {
|
|
1378
|
+
type: "text",
|
|
1379
|
+
text: [
|
|
1380
|
+
"[nextsteps_gate]",
|
|
1381
|
+
`Authoritative live state for this request: open todos = ${openTodos.length}.`,
|
|
1382
|
+
"You MUST omit <nextsteps> entirely while these todos remain open. Continue or finish the tracked work; do not propose unrelated follow-on work.",
|
|
1383
|
+
"Open todo snapshot:",
|
|
1384
|
+
...todoSnapshot,
|
|
1385
|
+
"[/nextsteps_gate]"
|
|
1386
|
+
].join("\n"),
|
|
1387
|
+
cache_control: { type: "ephemeral" }
|
|
1388
|
+
};
|
|
1645
1389
|
}
|
|
1646
1390
|
|
|
1647
|
-
// src/
|
|
1648
|
-
var
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
this.name = "IncomingImageError";
|
|
1391
|
+
// src/core/system-prompt-builder.ts
|
|
1392
|
+
var SYSTEM_BLOCK_SOURCE = /* @__PURE__ */ new WeakMap();
|
|
1393
|
+
|
|
1394
|
+
// src/utils/tool-wire-compact.ts
|
|
1395
|
+
var TOOL_DESCRIPTION_MAX_CHARS = 400;
|
|
1396
|
+
var SCHEMA_DESCRIPTION_MAX_CHARS = 120;
|
|
1397
|
+
var compactCache = /* @__PURE__ */ new WeakMap();
|
|
1398
|
+
function compactToolDefinitionForWire(tool, opts = {}) {
|
|
1399
|
+
const useDefaultOptions = opts.descriptionMaxChars === void 0 && opts.schemaDescriptionMaxChars === void 0;
|
|
1400
|
+
if (useDefaultOptions && typeof tool === "object" && tool !== null) {
|
|
1401
|
+
const cached = compactCache.get(tool);
|
|
1402
|
+
if (cached) return cached;
|
|
1660
1403
|
}
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1404
|
+
const compact = {
|
|
1405
|
+
name: tool.name,
|
|
1406
|
+
description: compactDescription(
|
|
1407
|
+
tool.description ?? "",
|
|
1408
|
+
opts.descriptionMaxChars ?? TOOL_DESCRIPTION_MAX_CHARS
|
|
1409
|
+
),
|
|
1410
|
+
inputSchema: normalizeTopLevelToolSchema(
|
|
1411
|
+
compactSchemaDescriptions(
|
|
1412
|
+
tool.inputSchema,
|
|
1413
|
+
opts.schemaDescriptionMaxChars ?? SCHEMA_DESCRIPTION_MAX_CHARS
|
|
1414
|
+
)
|
|
1415
|
+
)
|
|
1669
1416
|
};
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
const raw = [...images ?? []];
|
|
1673
|
-
if (legacyImageBase64) raw.push({ data: legacyImageBase64 });
|
|
1674
|
-
if (raw.length === 0) return [];
|
|
1675
|
-
if (raw.length > MAX_INCOMING_IMAGES) {
|
|
1676
|
-
throw new IncomingImageError(
|
|
1677
|
-
`Too many images: ${raw.length} (max ${MAX_INCOMING_IMAGES} per message).`
|
|
1678
|
-
);
|
|
1417
|
+
if (useDefaultOptions && typeof tool === "object" && tool !== null) {
|
|
1418
|
+
compactCache.set(tool, compact);
|
|
1679
1419
|
}
|
|
1680
|
-
return
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1420
|
+
return compact;
|
|
1421
|
+
}
|
|
1422
|
+
function normalizeTopLevelToolSchema(schema) {
|
|
1423
|
+
const combinators = ["oneOf", "anyOf", "allOf"].map((keyword) => ({ keyword, branches: schema[keyword] })).filter(
|
|
1424
|
+
(entry) => Array.isArray(entry.branches)
|
|
1425
|
+
);
|
|
1426
|
+
if (combinators.length === 0) return schema;
|
|
1427
|
+
const out = { ...schema, type: "object" };
|
|
1428
|
+
delete out["oneOf"];
|
|
1429
|
+
delete out["anyOf"];
|
|
1430
|
+
delete out["allOf"];
|
|
1431
|
+
const properties = isRecord(schema["properties"]) ? { ...schema["properties"] } : {};
|
|
1432
|
+
let required = stringSet(schema["required"]);
|
|
1433
|
+
for (const { keyword, branches } of combinators) {
|
|
1434
|
+
const objectBranches = branches.filter(isRecord);
|
|
1435
|
+
for (const branch of objectBranches) {
|
|
1436
|
+
if (isRecord(branch["properties"])) Object.assign(properties, branch["properties"]);
|
|
1693
1437
|
}
|
|
1694
|
-
const
|
|
1695
|
-
if (
|
|
1696
|
-
|
|
1697
|
-
|
|
1438
|
+
const branchRequired = objectBranches.map((branch) => stringSet(branch["required"]));
|
|
1439
|
+
if (keyword === "allOf") {
|
|
1440
|
+
for (const fields of branchRequired) for (const field of fields) required.add(field);
|
|
1441
|
+
} else if (branchRequired.length > 0) {
|
|
1442
|
+
const common = new Set(
|
|
1443
|
+
[...branchRequired[0]].filter(
|
|
1444
|
+
(field) => branchRequired.slice(1).every((fields) => fields.has(field))
|
|
1445
|
+
)
|
|
1698
1446
|
);
|
|
1447
|
+
required = /* @__PURE__ */ new Set([...required, ...common]);
|
|
1699
1448
|
}
|
|
1700
|
-
return {
|
|
1701
|
-
type: "image",
|
|
1702
|
-
source: { type: "base64", media_type: mediaType, data: base64 }
|
|
1703
|
-
};
|
|
1704
|
-
});
|
|
1705
|
-
}
|
|
1706
|
-
function buildUserContentBlocks(text, images) {
|
|
1707
|
-
const blocks = [...images];
|
|
1708
|
-
if (text) blocks.push({ type: "text", text });
|
|
1709
|
-
return blocks;
|
|
1710
|
-
}
|
|
1711
|
-
|
|
1712
|
-
// src/utils/ip-guard.ts
|
|
1713
|
-
import * as dns from "node:dns/promises";
|
|
1714
|
-
import * as net from "node:net";
|
|
1715
|
-
function isPrivateIPv4(addr) {
|
|
1716
|
-
const parts = addr.split(".").map((p) => Number.parseInt(p, 10));
|
|
1717
|
-
if (parts.length !== 4 || parts.some((n) => Number.isNaN(n) || n < 0 || n > 255)) {
|
|
1718
|
-
return true;
|
|
1719
1449
|
}
|
|
1720
|
-
|
|
1721
|
-
if (
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
if (a === 169 && b === 254) return true;
|
|
1725
|
-
if (a === 172 && b >= 16 && b <= 31) return true;
|
|
1726
|
-
if (a === 192 && b === 168) return true;
|
|
1727
|
-
if (a === 192 && b === 0 && c === 0) return true;
|
|
1728
|
-
if (a === 100 && b >= 64 && b <= 127) return true;
|
|
1729
|
-
if (a >= 224) return true;
|
|
1730
|
-
return false;
|
|
1450
|
+
out["properties"] = properties;
|
|
1451
|
+
if (required.size > 0) out["required"] = [...required];
|
|
1452
|
+
else delete out["required"];
|
|
1453
|
+
return out;
|
|
1731
1454
|
}
|
|
1732
|
-
function
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
if (!groups) return true;
|
|
1737
|
-
if (groups[0] === 0 && groups[1] === 0 && groups[2] === 0 && groups[3] === 0 && groups[4] === 0 && groups[5] === 65535) {
|
|
1738
|
-
const a = (groups[6] ?? 0) >> 8;
|
|
1739
|
-
const b = (groups[6] ?? 0) & 255;
|
|
1740
|
-
const c = (groups[7] ?? 0) >> 8;
|
|
1741
|
-
const d = (groups[7] ?? 0) & 255;
|
|
1742
|
-
return isPrivateIPv4(`${a}.${b}.${c}.${d}`);
|
|
1743
|
-
}
|
|
1744
|
-
const high = groups[0] ?? 0;
|
|
1745
|
-
if ((high & 65024) === 64512) return true;
|
|
1746
|
-
if ((high & 65472) === 65152) return true;
|
|
1747
|
-
if ((high & 65280) === 65280) return true;
|
|
1748
|
-
return false;
|
|
1455
|
+
function stringSet(value) {
|
|
1456
|
+
return new Set(
|
|
1457
|
+
Array.isArray(value) ? value.filter((item) => typeof item === "string") : []
|
|
1458
|
+
);
|
|
1749
1459
|
}
|
|
1750
|
-
function
|
|
1751
|
-
const
|
|
1752
|
-
|
|
1753
|
-
const parseGroups = (s) => {
|
|
1754
|
-
if (s === "") return [];
|
|
1755
|
-
const out = [];
|
|
1756
|
-
for (const g of s.split(":")) {
|
|
1757
|
-
if (g.length === 0 || g.length > 4) return null;
|
|
1758
|
-
const n = Number.parseInt(g, 16);
|
|
1759
|
-
if (Number.isNaN(n) || n < 0 || n > 65535) return null;
|
|
1760
|
-
out.push(n);
|
|
1761
|
-
}
|
|
1762
|
-
return out;
|
|
1763
|
-
};
|
|
1764
|
-
if (parts.length === 1) {
|
|
1765
|
-
const groups = parseGroups(parts[0] ?? "");
|
|
1766
|
-
if (groups?.length !== 8) return null;
|
|
1767
|
-
return groups;
|
|
1768
|
-
}
|
|
1769
|
-
const head = parseGroups(parts[0] ?? "");
|
|
1770
|
-
const tail = parseGroups(parts[1] ?? "");
|
|
1771
|
-
if (!head || !tail) return null;
|
|
1772
|
-
const fill = 8 - head.length - tail.length;
|
|
1773
|
-
if (fill < 0) return null;
|
|
1774
|
-
return [...head, ...new Array(fill).fill(0), ...tail];
|
|
1460
|
+
function compactSchemaDescriptions(schema, maxDescriptionChars = SCHEMA_DESCRIPTION_MAX_CHARS) {
|
|
1461
|
+
const compact = compactSchemaNode(schema, maxDescriptionChars);
|
|
1462
|
+
return isRecord(compact) ? compact : { type: "object", properties: {} };
|
|
1775
1463
|
}
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
throw new Error("fetch: blocked localhost target");
|
|
1464
|
+
function compactSchemaNode(node, maxDescriptionChars) {
|
|
1465
|
+
if (Array.isArray(node)) {
|
|
1466
|
+
return node.map((item) => compactSchemaNode(item, maxDescriptionChars));
|
|
1780
1467
|
}
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
throw new Error(`fetch: blocked private/loopback address "${host}"`);
|
|
1789
|
-
}
|
|
1790
|
-
} else {
|
|
1791
|
-
try {
|
|
1792
|
-
const records = await dns.lookup(host, { all: true });
|
|
1793
|
-
for (const r of records) {
|
|
1794
|
-
const bad = r.family === 4 ? isPrivateIPv4(r.address) : isPrivateIPv6(r.address);
|
|
1795
|
-
if (bad) {
|
|
1796
|
-
throw new Error(`fetch: resolved to private address ${r.address}`);
|
|
1797
|
-
}
|
|
1798
|
-
}
|
|
1799
|
-
} catch (err) {
|
|
1800
|
-
if (err instanceof Error && err.message.startsWith("fetch:")) throw err;
|
|
1468
|
+
if (!isRecord(node)) return node;
|
|
1469
|
+
const out = {};
|
|
1470
|
+
for (const [key, value] of Object.entries(node)) {
|
|
1471
|
+
if (key === "description" && typeof value === "string") {
|
|
1472
|
+
out[key] = compactDescription(value, maxDescriptionChars);
|
|
1473
|
+
} else {
|
|
1474
|
+
out[key] = compactSchemaNode(value, maxDescriptionChars);
|
|
1801
1475
|
}
|
|
1802
1476
|
}
|
|
1477
|
+
return out;
|
|
1478
|
+
}
|
|
1479
|
+
function compactDescription(text, maxChars) {
|
|
1480
|
+
const normalized = text.replace(/\s+/g, " ").trim();
|
|
1481
|
+
if (normalized.length <= maxChars) return normalized;
|
|
1482
|
+
if (maxChars <= 20) return normalized.slice(0, maxChars);
|
|
1483
|
+
const hardLimit = maxChars - 12;
|
|
1484
|
+
const boundary = findSemanticBoundary(normalized, hardLimit);
|
|
1485
|
+
const head = normalized.slice(0, boundary > 0 ? boundary : hardLimit).trimEnd();
|
|
1486
|
+
return `${head} ...`;
|
|
1487
|
+
}
|
|
1488
|
+
function findSemanticBoundary(text, limit) {
|
|
1489
|
+
const punctuation = Math.max(
|
|
1490
|
+
text.lastIndexOf(". ", limit),
|
|
1491
|
+
text.lastIndexOf("; ", limit),
|
|
1492
|
+
text.lastIndexOf(": ", limit)
|
|
1493
|
+
);
|
|
1494
|
+
if (punctuation >= Math.floor(limit * 0.45)) return punctuation + 1;
|
|
1495
|
+
const comma = text.lastIndexOf(", ", limit);
|
|
1496
|
+
if (comma >= Math.floor(limit * 0.6)) return comma + 1;
|
|
1497
|
+
const space = text.lastIndexOf(" ", limit);
|
|
1498
|
+
return space >= Math.floor(limit * 0.6) ? space : limit;
|
|
1499
|
+
}
|
|
1500
|
+
function isRecord(value) {
|
|
1501
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
1803
1502
|
}
|
|
1804
1503
|
|
|
1805
|
-
// src/utils/
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1504
|
+
// src/utils/token-estimate.ts
|
|
1505
|
+
var RoughTokenEstimate = (text, charsPerToken = 3.5) => Math.max(1, Math.ceil(text.length / charsPerToken));
|
|
1506
|
+
var CAL_ALPHA = 0.3;
|
|
1507
|
+
var CALIBRATION_GLOBAL_KEY = "__global__";
|
|
1508
|
+
var _cals = /* @__PURE__ */ new Map();
|
|
1509
|
+
function calState(key) {
|
|
1510
|
+
let state = _cals.get(key);
|
|
1511
|
+
if (!state) {
|
|
1512
|
+
state = { ratio: 1, count: 0, prevEst: 0 };
|
|
1513
|
+
_cals.set(key, state);
|
|
1514
|
+
}
|
|
1515
|
+
return state;
|
|
1810
1516
|
}
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
prevSig = '"';
|
|
1834
|
-
stringBraceDepth = 0;
|
|
1835
|
-
continue;
|
|
1836
|
-
}
|
|
1837
|
-
if (ch === "{") stringBraceDepth++;
|
|
1838
|
-
else if (ch === "}" && stringBraceDepth > 0) stringBraceDepth--;
|
|
1839
|
-
continue;
|
|
1840
|
-
}
|
|
1841
|
-
if (ch === " " || ch === " " || ch === "\n" || ch === "\r") continue;
|
|
1842
|
-
contentEnd = i + 1;
|
|
1843
|
-
if (ch === '"') {
|
|
1844
|
-
inString = true;
|
|
1845
|
-
sawKey = true;
|
|
1846
|
-
stringBraceDepth = 0;
|
|
1847
|
-
prevSig = '"';
|
|
1848
|
-
} else if (ch === "{" || ch === "[") {
|
|
1849
|
-
stack.push(ch);
|
|
1850
|
-
prevSig = ch;
|
|
1851
|
-
} else if (ch === "}" || ch === "]") {
|
|
1852
|
-
stack.pop();
|
|
1853
|
-
prevSig = ch;
|
|
1854
|
-
} else {
|
|
1855
|
-
prevSig = ch;
|
|
1856
|
-
}
|
|
1857
|
-
}
|
|
1858
|
-
if (!sawKey && !inString) return s;
|
|
1859
|
-
let result = s.slice(0, contentEnd);
|
|
1860
|
-
if (inString) {
|
|
1861
|
-
if (escaped) {
|
|
1862
|
-
result = result.slice(0, -1);
|
|
1863
|
-
} else if (endsWithInvalidEscape(result)) {
|
|
1864
|
-
result = result.slice(0, -2);
|
|
1517
|
+
var MIN_SAMPLES_FOR_CALIBRATION = 3;
|
|
1518
|
+
var MODEL_FAMILY_RATIO = {
|
|
1519
|
+
// Anthropic: ~3.8-4.0 chars/token depending on model
|
|
1520
|
+
claude: 3.8,
|
|
1521
|
+
// OpenAI: ~4.0 chars/token
|
|
1522
|
+
"gpt-4": 4,
|
|
1523
|
+
"gpt-3.5": 4,
|
|
1524
|
+
// Google: ~3.5 chars/token
|
|
1525
|
+
gemini: 3.5,
|
|
1526
|
+
// DeepSeek: ~3.5 chars/token
|
|
1527
|
+
deepseek: 3.5
|
|
1528
|
+
};
|
|
1529
|
+
var ESTIMATE_CACHE = /* @__PURE__ */ new Map();
|
|
1530
|
+
var _estimateCacheOrder = [];
|
|
1531
|
+
var ESTIMATE_CACHE_MAX_SIZE = 5e4;
|
|
1532
|
+
function getCachedEstimate(key, compute) {
|
|
1533
|
+
const existing = ESTIMATE_CACHE.get(key);
|
|
1534
|
+
if (existing !== void 0) return existing;
|
|
1535
|
+
if (ESTIMATE_CACHE.size >= ESTIMATE_CACHE_MAX_SIZE) {
|
|
1536
|
+
while (ESTIMATE_CACHE.size > Math.floor(ESTIMATE_CACHE_MAX_SIZE / 2)) {
|
|
1537
|
+
const oldest = _estimateCacheOrder.shift();
|
|
1538
|
+
if (oldest !== void 0) ESTIMATE_CACHE.delete(oldest);
|
|
1865
1539
|
}
|
|
1866
|
-
if (stringBraceDepth > 0) result += "}".repeat(stringBraceDepth);
|
|
1867
|
-
result += '"';
|
|
1868
|
-
} else if (prevSig === ":") {
|
|
1869
|
-
result += "null";
|
|
1870
|
-
}
|
|
1871
|
-
for (let k = stack.length - 1; k >= 0; k--) {
|
|
1872
|
-
result += stack[k] === "{" ? "}" : "]";
|
|
1873
|
-
}
|
|
1874
|
-
if (!tryParse(result).ok) {
|
|
1875
|
-
const patched = result.replace(/:(\s*)([}\]])/g, ":null$2");
|
|
1876
|
-
if (tryParse(patched).ok) result = patched;
|
|
1877
1540
|
}
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
const last = str[str.length - 1];
|
|
1883
|
-
if (str[str.length - 2] !== "\\" || last === void 0) return false;
|
|
1884
|
-
if (VALID_ESCAPE.has(last)) return false;
|
|
1885
|
-
let backslashes = 0;
|
|
1886
|
-
for (let k = str.length - 2; k >= 0 && str[k] === "\\"; k--) backslashes++;
|
|
1887
|
-
return backslashes % 2 === 1;
|
|
1541
|
+
const estimate = compute(key);
|
|
1542
|
+
ESTIMATE_CACHE.set(key, estimate);
|
|
1543
|
+
_estimateCacheOrder.push(key);
|
|
1544
|
+
return estimate;
|
|
1888
1545
|
}
|
|
1889
|
-
function
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
return { ok: false };
|
|
1546
|
+
function estimateToolInputTokens(input) {
|
|
1547
|
+
if (typeof input === "string") return RoughTokenEstimate(input);
|
|
1548
|
+
if (input === null || typeof input !== "object") {
|
|
1549
|
+
return RoughTokenEstimate(String(input));
|
|
1894
1550
|
}
|
|
1551
|
+
return getCachedEstimate(JSON.stringify(input), (key) => RoughTokenEstimate(key));
|
|
1895
1552
|
}
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
const errors = [];
|
|
1900
|
-
walk(value, schema, "", errors, 0);
|
|
1901
|
-
return { ok: errors.length === 0, errors };
|
|
1553
|
+
function estimateToolResultTokens(content) {
|
|
1554
|
+
if (typeof content === "string") return RoughTokenEstimate(content);
|
|
1555
|
+
return getCachedEstimate(JSON.stringify(content), (key) => RoughTokenEstimate(key));
|
|
1902
1556
|
}
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
errors.push({
|
|
1915
|
-
path: path6 || "<root>",
|
|
1916
|
-
message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
|
|
1917
|
-
});
|
|
1918
|
-
return;
|
|
1919
|
-
}
|
|
1920
|
-
}
|
|
1921
|
-
if (typeof schema.type === "string") {
|
|
1922
|
-
if (!checkType(value, schema.type)) {
|
|
1923
|
-
errors.push({
|
|
1924
|
-
path: path6 || "<root>",
|
|
1925
|
-
message: `expected ${schema.type}, got ${describeType(value)} (${previewValue(value)})`
|
|
1926
|
-
});
|
|
1927
|
-
return;
|
|
1928
|
-
}
|
|
1929
|
-
}
|
|
1930
|
-
if (schema.type === "object" && isPlainObject(value)) {
|
|
1931
|
-
const obj = value;
|
|
1932
|
-
for (const req of schema.required ?? []) {
|
|
1933
|
-
if (!(req in obj)) {
|
|
1934
|
-
const expected = schema.properties?.[req]?.type;
|
|
1935
|
-
errors.push({
|
|
1936
|
-
path: joinPath(path6, req),
|
|
1937
|
-
message: `required property missing${typeof expected === "string" ? ` (expected ${expected})` : ""}`
|
|
1938
|
-
});
|
|
1939
|
-
}
|
|
1940
|
-
}
|
|
1941
|
-
if (schema.properties) {
|
|
1942
|
-
for (const [key, subSchema] of Object.entries(schema.properties)) {
|
|
1943
|
-
if (key in obj) {
|
|
1944
|
-
walk(obj[key], subSchema, joinPath(path6, key), errors, depth + 1);
|
|
1945
|
-
}
|
|
1946
|
-
}
|
|
1947
|
-
}
|
|
1557
|
+
function estimateTextTokens(text) {
|
|
1558
|
+
return RoughTokenEstimate(text);
|
|
1559
|
+
}
|
|
1560
|
+
function computeMessageTokens(msg) {
|
|
1561
|
+
if (typeof msg.content === "string") return estimateTextTokens(msg.content);
|
|
1562
|
+
let total = 0;
|
|
1563
|
+
for (const b of msg.content) {
|
|
1564
|
+
if (b.type === "text") total += estimateTextTokens(b.text);
|
|
1565
|
+
else if (b.type === "tool_use") total += estimateToolInputTokens(b.input);
|
|
1566
|
+
else if (b.type === "tool_result") total += estimateToolResultTokens(b.content);
|
|
1567
|
+
else total += RoughTokenEstimate(JSON.stringify(b));
|
|
1948
1568
|
}
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1569
|
+
return total;
|
|
1570
|
+
}
|
|
1571
|
+
function estimateMessageTokens(messages) {
|
|
1572
|
+
let total = 0;
|
|
1573
|
+
for (const m of messages) {
|
|
1574
|
+
if (typeof m._estTokens === "number" && m._estTokens > 0) {
|
|
1575
|
+
total += m._estTokens;
|
|
1576
|
+
continue;
|
|
1952
1577
|
}
|
|
1578
|
+
total += computeMessageTokens(m);
|
|
1953
1579
|
}
|
|
1580
|
+
return total;
|
|
1954
1581
|
}
|
|
1955
|
-
function
|
|
1956
|
-
|
|
1582
|
+
function estimateToolDefTokens(tool) {
|
|
1583
|
+
const cached = tool._estDefTokens;
|
|
1584
|
+
if (typeof cached === "number" && cached > 0) return cached;
|
|
1585
|
+
const compact = compactToolDefinitionForWire(tool);
|
|
1586
|
+
return RoughTokenEstimate(tool.name) + RoughTokenEstimate(compact.description) + RoughTokenEstimate(JSON.stringify(compact.inputSchema));
|
|
1957
1587
|
}
|
|
1958
|
-
function
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1588
|
+
function estimateRequestTokens(messages, systemPrompt, tools, calibrationKey = CALIBRATION_GLOBAL_KEY) {
|
|
1589
|
+
let messagesTokens = 0;
|
|
1590
|
+
if (typeof messages === "string") {
|
|
1591
|
+
messagesTokens = RoughTokenEstimate(messages);
|
|
1592
|
+
} else if (Array.isArray(messages)) {
|
|
1593
|
+
for (const m of messages) {
|
|
1594
|
+
if (typeof m === "object" && m !== null && "content" in m) {
|
|
1595
|
+
const cached = m._estTokens;
|
|
1596
|
+
if (typeof cached === "number" && cached > 0) {
|
|
1597
|
+
messagesTokens += cached;
|
|
1598
|
+
continue;
|
|
1599
|
+
}
|
|
1600
|
+
const content = m.content;
|
|
1601
|
+
if (typeof content === "string") {
|
|
1602
|
+
messagesTokens += RoughTokenEstimate(content);
|
|
1603
|
+
} else if (Array.isArray(content)) {
|
|
1604
|
+
for (const b of content) {
|
|
1605
|
+
if (typeof b === "object" && b !== null) {
|
|
1606
|
+
if (b.type === "text") {
|
|
1607
|
+
messagesTokens += RoughTokenEstimate(b.text);
|
|
1608
|
+
} else {
|
|
1609
|
+
messagesTokens += RoughTokenEstimate(JSON.stringify(b));
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1971
1613
|
}
|
|
1972
1614
|
}
|
|
1973
|
-
return { value, changed: false };
|
|
1974
|
-
}
|
|
1975
|
-
if (type === "boolean" && typeof value === "string") {
|
|
1976
|
-
const lowered = value.trim().toLowerCase();
|
|
1977
|
-
if (lowered === "true") return { value: true, changed: true };
|
|
1978
|
-
if (lowered === "false") return { value: false, changed: true };
|
|
1979
|
-
return { value, changed: false };
|
|
1980
1615
|
}
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1616
|
+
}
|
|
1617
|
+
let systemTokens = 0;
|
|
1618
|
+
if (typeof systemPrompt === "string") {
|
|
1619
|
+
systemTokens = RoughTokenEstimate(systemPrompt);
|
|
1620
|
+
} else if (Array.isArray(systemPrompt)) {
|
|
1621
|
+
for (const b of systemPrompt) {
|
|
1622
|
+
if (typeof b === "object" && b !== null && b.type === "text") {
|
|
1623
|
+
systemTokens += RoughTokenEstimate(b.text);
|
|
1988
1624
|
}
|
|
1989
|
-
return { value, changed: false };
|
|
1990
1625
|
}
|
|
1991
|
-
return { value, changed: false };
|
|
1992
1626
|
}
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
const out = { ...obj };
|
|
1997
|
-
for (const [key, subSchema] of Object.entries(schema.properties)) {
|
|
1998
|
-
if (!(key in obj)) continue;
|
|
1999
|
-
const r = coerceWalk(obj[key], subSchema, depth + 1);
|
|
2000
|
-
if (r.changed) {
|
|
2001
|
-
out[key] = r.value;
|
|
2002
|
-
changed = true;
|
|
2003
|
-
}
|
|
2004
|
-
}
|
|
2005
|
-
return changed ? { value: out, changed } : { value, changed: false };
|
|
2006
|
-
}
|
|
2007
|
-
if (type === "array" && Array.isArray(value) && schema.items) {
|
|
2008
|
-
let changed = false;
|
|
2009
|
-
const out = value.map((item) => {
|
|
2010
|
-
const r = coerceWalk(item, schema.items, depth + 1);
|
|
2011
|
-
if (r.changed) changed = true;
|
|
2012
|
-
return r.value;
|
|
2013
|
-
});
|
|
2014
|
-
return changed ? { value: out, changed } : { value, changed: false };
|
|
2015
|
-
}
|
|
2016
|
-
return { value, changed: false };
|
|
2017
|
-
}
|
|
2018
|
-
function checkType(value, type) {
|
|
2019
|
-
switch (type) {
|
|
2020
|
-
case "string":
|
|
2021
|
-
return typeof value === "string";
|
|
2022
|
-
case "number":
|
|
2023
|
-
return typeof value === "number" && !Number.isNaN(value);
|
|
2024
|
-
case "integer":
|
|
2025
|
-
return typeof value === "number" && Number.isInteger(value);
|
|
2026
|
-
case "boolean":
|
|
2027
|
-
return typeof value === "boolean";
|
|
2028
|
-
case "null":
|
|
2029
|
-
return value === null;
|
|
2030
|
-
case "array":
|
|
2031
|
-
return Array.isArray(value);
|
|
2032
|
-
case "object":
|
|
2033
|
-
return isPlainObject(value);
|
|
2034
|
-
default:
|
|
2035
|
-
return true;
|
|
1627
|
+
let toolsTokens = 0;
|
|
1628
|
+
for (const t of tools) {
|
|
1629
|
+
toolsTokens += estimateToolDefTokens(t);
|
|
2036
1630
|
}
|
|
1631
|
+
const total = messagesTokens + systemTokens + toolsTokens;
|
|
1632
|
+
calState(calibrationKey).prevEst = total;
|
|
1633
|
+
return {
|
|
1634
|
+
messages: messagesTokens,
|
|
1635
|
+
systemPrompt: systemTokens,
|
|
1636
|
+
tools: toolsTokens,
|
|
1637
|
+
total
|
|
1638
|
+
};
|
|
2037
1639
|
}
|
|
2038
|
-
function
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
if (
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
const s = JSON.stringify(v);
|
|
2049
|
-
if (s === void 0) return String(v);
|
|
2050
|
-
return s.length > 80 ? `${s.slice(0, 80)}\u2026` : s;
|
|
2051
|
-
} catch {
|
|
2052
|
-
return String(v);
|
|
1640
|
+
function recordActualUsage(actualInputTokens, estimatedInputTokens, calibrationKey = CALIBRATION_GLOBAL_KEY) {
|
|
1641
|
+
if (actualInputTokens <= 0) return;
|
|
1642
|
+
const cal = calState(calibrationKey);
|
|
1643
|
+
const est = estimatedInputTokens ?? cal.prevEst;
|
|
1644
|
+
if (est <= 0) return;
|
|
1645
|
+
const sampleRatio = actualInputTokens / est;
|
|
1646
|
+
if (cal.count === 0) {
|
|
1647
|
+
cal.ratio = sampleRatio;
|
|
1648
|
+
} else {
|
|
1649
|
+
cal.ratio = CAL_ALPHA * sampleRatio + (1 - CAL_ALPHA) * cal.ratio;
|
|
2053
1650
|
}
|
|
1651
|
+
cal.ratio = Math.min(1.5, Math.max(0.5, cal.ratio));
|
|
1652
|
+
cal.count++;
|
|
2054
1653
|
}
|
|
2055
|
-
function
|
|
2056
|
-
|
|
2057
|
-
return
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
1654
|
+
function getCalibrationState(calibrationKey = CALIBRATION_GLOBAL_KEY) {
|
|
1655
|
+
const cal = calState(calibrationKey);
|
|
1656
|
+
return {
|
|
1657
|
+
ratio: cal.ratio,
|
|
1658
|
+
count: cal.count,
|
|
1659
|
+
calibrated: cal.count >= MIN_SAMPLES_FOR_CALIBRATION
|
|
1660
|
+
};
|
|
2062
1661
|
}
|
|
2063
|
-
function
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
if (
|
|
2067
|
-
|
|
2068
|
-
return
|
|
1662
|
+
function estimateRequestTokensCalibrated(messages, systemPrompt, tools, calibrationKey = CALIBRATION_GLOBAL_KEY) {
|
|
1663
|
+
const result = estimateRequestTokens(messages, systemPrompt, tools, calibrationKey);
|
|
1664
|
+
const cal = calState(calibrationKey);
|
|
1665
|
+
if (cal.count >= MIN_SAMPLES_FOR_CALIBRATION) {
|
|
1666
|
+
const safeRatio = Math.min(1.5, Math.max(0.5, cal.ratio));
|
|
1667
|
+
return {
|
|
1668
|
+
messages: Math.round(result.messages * safeRatio),
|
|
1669
|
+
systemPrompt: Math.round(result.systemPrompt * safeRatio),
|
|
1670
|
+
tools: Math.round(result.tools * safeRatio),
|
|
1671
|
+
total: Math.round(result.total * safeRatio)
|
|
1672
|
+
};
|
|
2069
1673
|
}
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
(
|
|
2076
|
-
|
|
1674
|
+
const fallbackRatio = getModelFamilyRatio(calibrationKey);
|
|
1675
|
+
if (fallbackRatio !== null) {
|
|
1676
|
+
return {
|
|
1677
|
+
messages: Math.round(result.messages * fallbackRatio),
|
|
1678
|
+
systemPrompt: Math.round(result.systemPrompt * fallbackRatio),
|
|
1679
|
+
tools: Math.round(result.tools * fallbackRatio),
|
|
1680
|
+
total: Math.round(result.total * fallbackRatio)
|
|
1681
|
+
};
|
|
2077
1682
|
}
|
|
2078
|
-
return
|
|
1683
|
+
return result;
|
|
2079
1684
|
}
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
if (providerCustomModels) {
|
|
2085
|
-
for (const [id, def] of Object.entries(providerCustomModels)) {
|
|
2086
|
-
out[id] = { ...def };
|
|
2087
|
-
}
|
|
1685
|
+
function getModelFamilyRatio(calibrationKey) {
|
|
1686
|
+
const lower = calibrationKey.toLowerCase();
|
|
1687
|
+
for (const [family, ratio] of Object.entries(MODEL_FAMILY_RATIO)) {
|
|
1688
|
+
if (lower.includes(family)) return ratio / 3.5;
|
|
2088
1689
|
}
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
1690
|
+
return null;
|
|
1691
|
+
}
|
|
1692
|
+
function resetCalibration(calibrationKey) {
|
|
1693
|
+
if (calibrationKey === void 0) {
|
|
1694
|
+
_cals.clear();
|
|
1695
|
+
return;
|
|
2093
1696
|
}
|
|
2094
|
-
|
|
2095
|
-
return out;
|
|
1697
|
+
_cals.delete(calibrationKey);
|
|
2096
1698
|
}
|
|
2097
1699
|
|
|
2098
|
-
// src/utils/
|
|
2099
|
-
|
|
1700
|
+
// src/utils/context-breakdown.ts
|
|
1701
|
+
var SYSTEM_BLOCK_SOURCES = [
|
|
1702
|
+
"identity",
|
|
1703
|
+
"tool-usage",
|
|
1704
|
+
"environment",
|
|
1705
|
+
"skills",
|
|
1706
|
+
"mode",
|
|
1707
|
+
"plan",
|
|
1708
|
+
"leader-after-task",
|
|
1709
|
+
"contributor",
|
|
1710
|
+
"ledger",
|
|
1711
|
+
"nextsteps",
|
|
1712
|
+
"other"
|
|
1713
|
+
];
|
|
1714
|
+
function emptyBySource() {
|
|
2100
1715
|
const out = {};
|
|
2101
|
-
for (const
|
|
2102
|
-
out[id] = cloneProvider(provider);
|
|
2103
|
-
}
|
|
2104
|
-
for (const [id, ovProvider] of Object.entries(overlay)) {
|
|
2105
|
-
const existing = out[id];
|
|
2106
|
-
out[id] = existing ? mergeProvider(existing, ovProvider) : cloneProvider(ovProvider);
|
|
2107
|
-
}
|
|
1716
|
+
for (const key of SYSTEM_BLOCK_SOURCES) out[key] = 0;
|
|
2108
1717
|
return out;
|
|
2109
1718
|
}
|
|
2110
|
-
function
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
1719
|
+
function isMcpTool(tool) {
|
|
1720
|
+
return (tool.capabilities?.includes("mcp.proxy") ?? false) || tool.name.startsWith("mcp__");
|
|
1721
|
+
}
|
|
1722
|
+
function mcpServerOf(tool) {
|
|
1723
|
+
const parts = tool.name.split("__");
|
|
1724
|
+
return parts.length >= 3 && parts[0] === "mcp" ? parts[1] ?? "mcp" : "mcp";
|
|
1725
|
+
}
|
|
1726
|
+
function safeBuild(fn, warn = () => {
|
|
1727
|
+
}) {
|
|
1728
|
+
try {
|
|
1729
|
+
return fn();
|
|
1730
|
+
} catch (e) {
|
|
1731
|
+
warn(e);
|
|
1732
|
+
return void 0;
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
function resolveEffectiveMaxContext(ctx) {
|
|
1736
|
+
const metaLimit = ctx.meta?.["effectiveMaxContext"];
|
|
1737
|
+
const providerMax = ctx.provider.capabilities.maxContext;
|
|
1738
|
+
return typeof metaLimit === "number" && metaLimit > 0 ? metaLimit : typeof providerMax === "number" && providerMax > 0 ? providerMax : 2e5;
|
|
1739
|
+
}
|
|
1740
|
+
function getContextBreakdown(ctx) {
|
|
1741
|
+
const bySource = emptyBySource();
|
|
1742
|
+
let systemTotal = 0;
|
|
1743
|
+
for (const block of ctx.systemPrompt) {
|
|
1744
|
+
const tokens = estimateTextTokens(block.text);
|
|
1745
|
+
systemTotal += tokens;
|
|
1746
|
+
bySource[SYSTEM_BLOCK_SOURCE.get(block) ?? "other"] += tokens;
|
|
1747
|
+
}
|
|
1748
|
+
let toolsBuiltin = 0;
|
|
1749
|
+
let toolsMcp = 0;
|
|
1750
|
+
const mcpByServer = {};
|
|
1751
|
+
for (const tool of ctx.tools) {
|
|
1752
|
+
const tokens = estimateToolDefTokens(tool);
|
|
1753
|
+
if (isMcpTool(tool)) {
|
|
1754
|
+
toolsMcp += tokens;
|
|
1755
|
+
const server = mcpServerOf(tool);
|
|
1756
|
+
mcpByServer[server] = (mcpByServer[server] ?? 0) + tokens;
|
|
1757
|
+
} else {
|
|
1758
|
+
toolsBuiltin += tokens;
|
|
1759
|
+
}
|
|
2114
1760
|
}
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
1761
|
+
let histText = 0;
|
|
1762
|
+
let histToolResults = 0;
|
|
1763
|
+
for (const msg of ctx.messages) {
|
|
1764
|
+
if (typeof msg.content === "string") {
|
|
1765
|
+
histText += estimateTextTokens(msg.content);
|
|
1766
|
+
continue;
|
|
1767
|
+
}
|
|
1768
|
+
for (const b of msg.content) {
|
|
1769
|
+
switch (b.type) {
|
|
1770
|
+
case "text":
|
|
1771
|
+
histText += estimateTextTokens(b.text);
|
|
1772
|
+
break;
|
|
1773
|
+
case "tool_use":
|
|
1774
|
+
histText += estimateToolInputTokens(b.input);
|
|
1775
|
+
break;
|
|
1776
|
+
case "tool_result":
|
|
1777
|
+
histToolResults += estimateToolResultTokens(b.content);
|
|
1778
|
+
break;
|
|
1779
|
+
case "thinking":
|
|
1780
|
+
histText += estimateTextTokens(b.thinking);
|
|
1781
|
+
break;
|
|
1782
|
+
default:
|
|
1783
|
+
histText += estimateTextTokens(JSON.stringify(b));
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
2118
1786
|
}
|
|
1787
|
+
const warnings = [];
|
|
1788
|
+
const warn = (e) => {
|
|
1789
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
1790
|
+
warnings.push(msg);
|
|
1791
|
+
};
|
|
1792
|
+
const ledgerBlock = safeBuild(() => buildCompletedWorkLedgerBlock(ctx), warn);
|
|
1793
|
+
const nextstepsBlock = safeBuild(() => buildLiveNextStepsGateBlock(ctx), warn);
|
|
1794
|
+
const ledger = ledgerBlock ? estimateTextTokens(ledgerBlock.text) : 0;
|
|
1795
|
+
const nextsteps = nextstepsBlock ? estimateTextTokens(nextstepsBlock.text) : 0;
|
|
1796
|
+
const toolsTotal = toolsBuiltin + toolsMcp;
|
|
1797
|
+
const historyTotal = histText + histToolResults;
|
|
1798
|
+
const volatileTotal = ledger + nextsteps;
|
|
1799
|
+
const total = systemTotal + toolsTotal + historyTotal + volatileTotal;
|
|
1800
|
+
const effectiveMaxContext = resolveEffectiveMaxContext(ctx);
|
|
2119
1801
|
return {
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
1802
|
+
system: { total: systemTotal, bySource },
|
|
1803
|
+
tools: {
|
|
1804
|
+
total: toolsTotal,
|
|
1805
|
+
builtin: toolsBuiltin,
|
|
1806
|
+
mcp: toolsMcp,
|
|
1807
|
+
count: ctx.tools.length,
|
|
1808
|
+
mcpByServer
|
|
1809
|
+
},
|
|
1810
|
+
history: {
|
|
1811
|
+
total: historyTotal,
|
|
1812
|
+
text: histText,
|
|
1813
|
+
toolResults: histToolResults,
|
|
1814
|
+
messageCount: ctx.messages.length
|
|
1815
|
+
},
|
|
1816
|
+
volatile: { ledger, nextsteps, total: volatileTotal },
|
|
1817
|
+
total,
|
|
1818
|
+
effectiveMaxContext,
|
|
1819
|
+
usedPct: effectiveMaxContext > 0 ? total / effectiveMaxContext : 0,
|
|
1820
|
+
warnings
|
|
2131
1821
|
};
|
|
2132
1822
|
}
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
1823
|
+
|
|
1824
|
+
// src/utils/deep-merge.ts
|
|
1825
|
+
var FORBIDDEN_PROTO_KEYS = /* @__PURE__ */ new Set([
|
|
1826
|
+
"__proto__",
|
|
1827
|
+
"constructor",
|
|
1828
|
+
"prototype",
|
|
1829
|
+
"__defineGetter__",
|
|
1830
|
+
"__defineSetter__",
|
|
1831
|
+
"__lookupGetter__",
|
|
1832
|
+
"__lookupSetter__"
|
|
1833
|
+
]);
|
|
1834
|
+
function isPrimitiveArray(a) {
|
|
1835
|
+
return a.every((v) => v === null || typeof v !== "object" && typeof v !== "function");
|
|
1836
|
+
}
|
|
1837
|
+
function deepMerge(base, patch, options = {}) {
|
|
1838
|
+
const {
|
|
1839
|
+
conflictResolution = "prefer-patch",
|
|
1840
|
+
arrayMode = "replace",
|
|
1841
|
+
protectProto = true,
|
|
1842
|
+
onNonPrimitiveArrayReplace
|
|
1843
|
+
} = options;
|
|
1844
|
+
if (typeof base !== "object" || base === null) {
|
|
1845
|
+
return conflictResolution === "prefer-patch" ? patch : base;
|
|
2140
1846
|
}
|
|
2141
|
-
if (
|
|
2142
|
-
|
|
1847
|
+
if (typeof patch !== "object" || patch === null) {
|
|
1848
|
+
return conflictResolution === "prefer-patch" ? patch : base;
|
|
2143
1849
|
}
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
models[mid] = { ...m };
|
|
1850
|
+
if (Array.isArray(base) && Array.isArray(patch)) {
|
|
1851
|
+
if (arrayMode === "concat-primitives" && isPrimitiveArray(base) && isPrimitiveArray(patch)) {
|
|
1852
|
+
return [.../* @__PURE__ */ new Set([...base, ...patch])];
|
|
1853
|
+
}
|
|
1854
|
+
return conflictResolution === "prefer-patch" ? patch : base;
|
|
2150
1855
|
}
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
function stripUndefined(obj) {
|
|
2154
|
-
const out = {};
|
|
2155
|
-
for (const [k, v] of Object.entries(obj)) {
|
|
2156
|
-
if (v !== void 0) out[k] = v;
|
|
1856
|
+
if (Array.isArray(base) || Array.isArray(patch)) {
|
|
1857
|
+
return conflictResolution === "prefer-patch" ? patch : base;
|
|
2157
1858
|
}
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
if (
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
1859
|
+
const baseObj = base;
|
|
1860
|
+
const patchObj = patch;
|
|
1861
|
+
const out = { ...baseObj };
|
|
1862
|
+
for (const [k, v] of Object.entries(patchObj)) {
|
|
1863
|
+
if (protectProto && FORBIDDEN_PROTO_KEYS.has(k)) continue;
|
|
1864
|
+
const existing = out[k];
|
|
1865
|
+
if (v !== null && typeof v === "object" && !Array.isArray(v) && existing !== null && typeof existing === "object" && !Array.isArray(existing)) {
|
|
1866
|
+
out[k] = deepMerge(existing, v, options);
|
|
1867
|
+
} else if (Array.isArray(v) && Array.isArray(existing)) {
|
|
1868
|
+
if (onNonPrimitiveArrayReplace && !isPrimitiveArray(v)) {
|
|
1869
|
+
onNonPrimitiveArrayReplace(k, existing.length, v.length);
|
|
1870
|
+
}
|
|
1871
|
+
out[k] = deepMerge(existing, v, options);
|
|
1872
|
+
} else if (v !== void 0) {
|
|
1873
|
+
if (onNonPrimitiveArrayReplace && Array.isArray(v) && !isPrimitiveArray(v)) {
|
|
1874
|
+
const existingLen = Array.isArray(existing) ? existing.length : 0;
|
|
1875
|
+
onNonPrimitiveArrayReplace(k, existingLen, v.length);
|
|
1876
|
+
}
|
|
1877
|
+
out[k] = v;
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
return out;
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
// src/utils/diff.ts
|
|
1884
|
+
function myersDiff(a, b) {
|
|
1885
|
+
const N = a.length;
|
|
1886
|
+
const M = b.length;
|
|
1887
|
+
const max = N + M;
|
|
1888
|
+
if (max === 0) return [];
|
|
1889
|
+
const v = /* @__PURE__ */ new Map();
|
|
1890
|
+
v.set(1, 0);
|
|
1891
|
+
const trace = [];
|
|
1892
|
+
for (let d = 0; d <= max; d++) {
|
|
1893
|
+
const snapshot = new Map(v);
|
|
1894
|
+
trace.push(snapshot);
|
|
1895
|
+
for (let k = -d; k <= d; k += 2) {
|
|
1896
|
+
const left = v.get(k - 1) ?? -1;
|
|
1897
|
+
const right = v.get(k + 1) ?? -1;
|
|
1898
|
+
let x;
|
|
1899
|
+
if (k === -d || k !== d && left < right) {
|
|
1900
|
+
x = right;
|
|
1901
|
+
} else {
|
|
1902
|
+
x = left + 1;
|
|
1903
|
+
}
|
|
1904
|
+
let y = x - k;
|
|
1905
|
+
while (x < N && y < M && a[x] === b[y]) {
|
|
1906
|
+
x++;
|
|
1907
|
+
y++;
|
|
1908
|
+
}
|
|
1909
|
+
v.set(k, x);
|
|
1910
|
+
if (x >= N && y >= M) {
|
|
1911
|
+
return backtrack(trace, a, b, N, M, d);
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
return [];
|
|
1916
|
+
}
|
|
1917
|
+
function backtrack(trace, a, b, N, M, finalD) {
|
|
1918
|
+
const edits = [];
|
|
1919
|
+
let x = N;
|
|
1920
|
+
let y = M;
|
|
1921
|
+
for (let d = finalD; d > 0; d--) {
|
|
1922
|
+
const v = trace[d];
|
|
1923
|
+
if (!v) break;
|
|
1924
|
+
const k = x - y;
|
|
1925
|
+
const left = v.get(k - 1) ?? -1;
|
|
1926
|
+
const right = v.get(k + 1) ?? -1;
|
|
1927
|
+
let prevK;
|
|
1928
|
+
if (k === -d || k !== d && left < right) {
|
|
1929
|
+
prevK = k + 1;
|
|
1930
|
+
} else {
|
|
1931
|
+
prevK = k - 1;
|
|
1932
|
+
}
|
|
1933
|
+
const prevX = v.get(prevK) ?? 0;
|
|
1934
|
+
const prevY = prevX - prevK;
|
|
1935
|
+
while (x > prevX && y > prevY) {
|
|
1936
|
+
edits.push({ op: "equal", a: x - 1, b: y - 1, line: a[x - 1] ?? "" });
|
|
1937
|
+
x--;
|
|
1938
|
+
y--;
|
|
1939
|
+
}
|
|
1940
|
+
if (d > 0) {
|
|
1941
|
+
if (x === prevX) {
|
|
1942
|
+
edits.push({ op: "insert", a: x, b: y - 1, line: b[y - 1] ?? "" });
|
|
1943
|
+
} else {
|
|
1944
|
+
edits.push({ op: "delete", a: x - 1, b: y, line: a[x - 1] ?? "" });
|
|
1945
|
+
}
|
|
1946
|
+
x = prevX;
|
|
1947
|
+
y = prevY;
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
while (x > 0 && y > 0) {
|
|
1951
|
+
edits.push({ op: "equal", a: x - 1, b: y - 1, line: a[x - 1] ?? "" });
|
|
1952
|
+
x--;
|
|
1953
|
+
y--;
|
|
1954
|
+
}
|
|
1955
|
+
return edits.reverse();
|
|
1956
|
+
}
|
|
1957
|
+
function unifiedDiff(oldText, newText, opts = {}) {
|
|
1958
|
+
const context = opts.context ?? 3;
|
|
1959
|
+
const a = oldText.split("\n");
|
|
1960
|
+
const b = newText.split("\n");
|
|
1961
|
+
if (a[a.length - 1] === "") a.pop();
|
|
1962
|
+
if (b[b.length - 1] === "") b.pop();
|
|
1963
|
+
const edits = myersDiff(a, b);
|
|
1964
|
+
if (edits.every((e) => e.op === "equal")) return "";
|
|
1965
|
+
const hunks = [];
|
|
1966
|
+
let i = 0;
|
|
1967
|
+
while (i < edits.length) {
|
|
1968
|
+
while (i < edits.length && edits[i]?.op === "equal") i++;
|
|
1969
|
+
if (i >= edits.length) break;
|
|
1970
|
+
const hunkStart = Math.max(0, i - context);
|
|
1971
|
+
const lines = [];
|
|
1972
|
+
let aStart = (edits[hunkStart]?.a ?? 0) + 1;
|
|
1973
|
+
let bStart = (edits[hunkStart]?.b ?? 0) + 1;
|
|
1974
|
+
let aCount = 0;
|
|
1975
|
+
let bCount = 0;
|
|
1976
|
+
let cursor = hunkStart;
|
|
1977
|
+
let trailing = 0;
|
|
1978
|
+
while (cursor < edits.length) {
|
|
1979
|
+
const e = edits[cursor];
|
|
1980
|
+
if (!e) break;
|
|
1981
|
+
if (e.op === "equal") {
|
|
1982
|
+
trailing++;
|
|
1983
|
+
if (trailing > context * 2) break;
|
|
1984
|
+
} else {
|
|
1985
|
+
trailing = 0;
|
|
1986
|
+
}
|
|
1987
|
+
if (e.op === "equal") {
|
|
1988
|
+
lines.push(` ${e.line}`);
|
|
1989
|
+
aCount++;
|
|
1990
|
+
bCount++;
|
|
1991
|
+
} else if (e.op === "delete") {
|
|
1992
|
+
lines.push(`-${e.line}`);
|
|
1993
|
+
aCount++;
|
|
1994
|
+
} else {
|
|
1995
|
+
lines.push(`+${e.line}`);
|
|
1996
|
+
bCount++;
|
|
1997
|
+
}
|
|
1998
|
+
cursor++;
|
|
1999
|
+
}
|
|
2000
|
+
while (lines.length > 0 && lines[lines.length - 1]?.startsWith(" ") && trailing > context) {
|
|
2001
|
+
lines.pop();
|
|
2002
|
+
aCount--;
|
|
2003
|
+
bCount--;
|
|
2004
|
+
trailing--;
|
|
2005
|
+
}
|
|
2006
|
+
if (aCount === 0) aStart = 0;
|
|
2007
|
+
if (bCount === 0) bStart = 0;
|
|
2008
|
+
hunks.push({ aStart, bStart, lines });
|
|
2009
|
+
i = cursor;
|
|
2010
|
+
}
|
|
2011
|
+
if (hunks.length === 0) return "";
|
|
2012
|
+
let out = "";
|
|
2013
|
+
out += `--- ${opts.fromFile ?? "a"}
|
|
2014
|
+
`;
|
|
2015
|
+
out += `+++ ${opts.toFile ?? "b"}
|
|
2016
|
+
`;
|
|
2017
|
+
for (const h of hunks) {
|
|
2018
|
+
let aCount = 0;
|
|
2019
|
+
let bCount = 0;
|
|
2020
|
+
for (const l of h.lines) {
|
|
2021
|
+
if (l.startsWith(" ")) {
|
|
2022
|
+
aCount++;
|
|
2023
|
+
bCount++;
|
|
2024
|
+
} else if (l.startsWith("-")) aCount++;
|
|
2025
|
+
else if (l.startsWith("+")) bCount++;
|
|
2026
|
+
}
|
|
2027
|
+
out += `@@ -${h.aStart},${aCount} +${h.bStart},${bCount} @@
|
|
2028
|
+
`;
|
|
2029
|
+
out += `${h.lines.join("\n")}
|
|
2030
|
+
`;
|
|
2031
|
+
}
|
|
2032
|
+
return out;
|
|
2033
|
+
}
|
|
2034
|
+
|
|
2035
|
+
// src/utils/glob-expand.ts
|
|
2036
|
+
import * as fsp from "node:fs/promises";
|
|
2037
|
+
import { isAbsolute as isAbsolute2, resolve as resolve2 } from "node:path";
|
|
2038
|
+
var GLOB_CHARS = /* @__PURE__ */ new Set(["*", "?", "["]);
|
|
2039
|
+
var IS_WINDOWS = process.platform === "win32";
|
|
2040
|
+
var SEP = IS_WINDOWS ? "\\" : "/";
|
|
2041
|
+
function isGlob(p) {
|
|
2042
|
+
for (const c of p) {
|
|
2043
|
+
if (GLOB_CHARS.has(c)) return true;
|
|
2044
|
+
}
|
|
2045
|
+
return false;
|
|
2046
|
+
}
|
|
2047
|
+
function globToRegex(pat) {
|
|
2048
|
+
let i = 0;
|
|
2049
|
+
let re = "^";
|
|
2050
|
+
while (i < pat.length) {
|
|
2051
|
+
const c = expectDefined(pat[i]);
|
|
2052
|
+
if (c === "*") {
|
|
2053
|
+
if (pat[i + 1] === "*") {
|
|
2054
|
+
re += ".*";
|
|
2055
|
+
i += 2;
|
|
2056
|
+
if (pat[i] === "/") i++;
|
|
2057
|
+
} else {
|
|
2058
|
+
re += "[^/\\\\]*";
|
|
2059
|
+
i++;
|
|
2060
|
+
}
|
|
2061
|
+
} else if (c === "?") {
|
|
2062
|
+
re += "[^/\\\\]";
|
|
2063
|
+
i++;
|
|
2064
|
+
} else if (c === "[") {
|
|
2065
|
+
let cls = "[";
|
|
2066
|
+
i++;
|
|
2067
|
+
if (pat[i] === "!" || pat[i] === "^") {
|
|
2068
|
+
cls += "^";
|
|
2069
|
+
i++;
|
|
2070
|
+
}
|
|
2071
|
+
while (i < pat.length && pat[i] !== "]") {
|
|
2072
|
+
const ch = pat[i] ?? "";
|
|
2073
|
+
if (ch === "\\") cls += "\\\\";
|
|
2074
|
+
else if (ch === "]" || ch === "^") cls += `\\${ch}`;
|
|
2075
|
+
else cls += ch;
|
|
2076
|
+
i++;
|
|
2077
|
+
}
|
|
2078
|
+
cls += "]";
|
|
2079
|
+
re += cls;
|
|
2080
|
+
i++;
|
|
2081
|
+
} else {
|
|
2082
|
+
re += c.replace(/[.+^${}()|\\]/g, "\\$&");
|
|
2083
|
+
i++;
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
return new RegExp(re + "$");
|
|
2087
|
+
}
|
|
2088
|
+
function baseDir(pat) {
|
|
2089
|
+
let firstGlob = pat.length;
|
|
2090
|
+
for (let i = 0; i < pat.length; i++) {
|
|
2091
|
+
if (GLOB_CHARS.has(expectDefined(pat[i]))) {
|
|
2092
|
+
firstGlob = i;
|
|
2093
|
+
break;
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
2096
|
+
const cut = Math.max(
|
|
2097
|
+
pat.lastIndexOf(SEP, firstGlob - 1),
|
|
2098
|
+
pat.lastIndexOf("/", firstGlob - 1)
|
|
2099
|
+
);
|
|
2100
|
+
return cut < 0 ? "." : pat.slice(0, cut);
|
|
2101
|
+
}
|
|
2102
|
+
async function expandGlob(pattern) {
|
|
2103
|
+
if (!isGlob(pattern)) return [pattern];
|
|
2104
|
+
const results = /* @__PURE__ */ new Set();
|
|
2105
|
+
const abs = isAbsolute2(pattern);
|
|
2106
|
+
const base = abs ? baseDir(pattern) : baseDir(pattern);
|
|
2107
|
+
const relPat = base === "." ? pattern : pattern.slice(base.length + 1);
|
|
2108
|
+
async function walk2(dir, pat) {
|
|
2109
|
+
let entries;
|
|
2110
|
+
try {
|
|
2111
|
+
entries = await fsp.readdir(dir);
|
|
2112
|
+
} catch {
|
|
2113
|
+
return;
|
|
2114
|
+
}
|
|
2115
|
+
const firstGlob = pat.search(/[*?[[]/);
|
|
2116
|
+
if (firstGlob < 0) {
|
|
2117
|
+
const re = globToRegex(pat);
|
|
2118
|
+
for (const e of entries) {
|
|
2119
|
+
if (re.test(e)) {
|
|
2120
|
+
const full = `${dir}${SEP}${e}`;
|
|
2121
|
+
results.add(abs ? resolve2(full) : full);
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
return;
|
|
2125
|
+
}
|
|
2126
|
+
const before = pat.slice(0, firstGlob);
|
|
2127
|
+
const rest = pat.slice(firstGlob);
|
|
2128
|
+
if (before.endsWith("**")) {
|
|
2129
|
+
await walk2(dir, rest);
|
|
2130
|
+
for (const e of entries) {
|
|
2131
|
+
const full = `${dir}${SEP}${e}`;
|
|
2132
|
+
try {
|
|
2133
|
+
const stat3 = await fsp.stat(full);
|
|
2134
|
+
if (stat3.isDirectory()) await walk2(full, rest);
|
|
2135
|
+
} catch {
|
|
2136
|
+
}
|
|
2137
|
+
}
|
|
2138
|
+
} else if (before === "") {
|
|
2139
|
+
const re = globToRegex(rest);
|
|
2140
|
+
for (const e of entries) {
|
|
2141
|
+
if (re.test(e)) {
|
|
2142
|
+
const full = `${dir}${SEP}${e}`;
|
|
2143
|
+
results.add(abs ? resolve2(full) : full);
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
} else {
|
|
2147
|
+
const seg = before.replace(/[*?[\]]/g, "").replace(/\/$/, "");
|
|
2148
|
+
if (entries.includes(seg)) {
|
|
2149
|
+
const full = `${dir}${SEP}${seg}`;
|
|
2150
|
+
try {
|
|
2151
|
+
const stat3 = await fsp.stat(full);
|
|
2152
|
+
if (stat3.isDirectory()) await walk2(full, rest);
|
|
2153
|
+
} catch {
|
|
2182
2154
|
}
|
|
2183
|
-
|
|
2184
|
-
});
|
|
2185
|
-
msg = filtered ?? msg;
|
|
2155
|
+
}
|
|
2186
2156
|
}
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2157
|
+
}
|
|
2158
|
+
await walk2(base === "." ? "." : base, relPat);
|
|
2159
|
+
return [...results];
|
|
2160
|
+
}
|
|
2161
|
+
|
|
2162
|
+
// src/utils/glob-match.ts
|
|
2163
|
+
function escapeRegex(s) {
|
|
2164
|
+
return s.replace(/[.+^${}()|\\]/g, "\\$&");
|
|
2165
|
+
}
|
|
2166
|
+
var COMPILED_GLOB_CACHE = /* @__PURE__ */ new Map();
|
|
2167
|
+
var CACHE_MAX_SIZE = 2e3;
|
|
2168
|
+
var NEVER_MATCH = /[^\s\S]/;
|
|
2169
|
+
function getCachedGlob(pattern) {
|
|
2170
|
+
const cached = COMPILED_GLOB_CACHE.get(pattern);
|
|
2171
|
+
if (cached) return cached;
|
|
2172
|
+
if (COMPILED_GLOB_CACHE.size >= CACHE_MAX_SIZE) {
|
|
2173
|
+
const keys = [...COMPILED_GLOB_CACHE.keys()];
|
|
2174
|
+
for (let i = 0; i < Math.floor(CACHE_MAX_SIZE / 4); i++) {
|
|
2175
|
+
COMPILED_GLOB_CACHE.delete(expectDefined(keys[i]));
|
|
2202
2176
|
}
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2177
|
+
}
|
|
2178
|
+
let re;
|
|
2179
|
+
try {
|
|
2180
|
+
re = compileGlob(pattern);
|
|
2181
|
+
} catch {
|
|
2182
|
+
re = NEVER_MATCH;
|
|
2183
|
+
}
|
|
2184
|
+
COMPILED_GLOB_CACHE.set(pattern, re);
|
|
2185
|
+
return re;
|
|
2186
|
+
}
|
|
2187
|
+
var MAX_GLOB_PATTERN_LEN = 1024;
|
|
2188
|
+
function compileGlob(pattern) {
|
|
2189
|
+
if (pattern.length > MAX_GLOB_PATTERN_LEN) {
|
|
2190
|
+
throw new Error(`Glob pattern exceeds ${MAX_GLOB_PATTERN_LEN} characters`);
|
|
2191
|
+
}
|
|
2192
|
+
let i = 0;
|
|
2193
|
+
let re = "^";
|
|
2194
|
+
while (i < pattern.length) {
|
|
2195
|
+
const c = pattern[i];
|
|
2196
|
+
if (c === "*") {
|
|
2197
|
+
if (pattern[i + 1] === "*") {
|
|
2198
|
+
re += ".*";
|
|
2199
|
+
i += 2;
|
|
2200
|
+
if (pattern[i] === "/") i++;
|
|
2201
|
+
} else {
|
|
2202
|
+
re += "[^/]*";
|
|
2203
|
+
i++;
|
|
2204
|
+
}
|
|
2205
|
+
} else if (c === "?") {
|
|
2206
|
+
re += "[^/]";
|
|
2207
|
+
i++;
|
|
2208
|
+
} else if (c === "[") {
|
|
2209
|
+
let cls = "[";
|
|
2210
|
+
i++;
|
|
2211
|
+
if (pattern[i] === "!" || pattern[i] === "^") {
|
|
2212
|
+
cls += "^";
|
|
2213
|
+
i++;
|
|
2214
|
+
}
|
|
2215
|
+
while (i < pattern.length && pattern[i] !== "]") {
|
|
2216
|
+
const ch = pattern[i] ?? "";
|
|
2217
|
+
if (ch === "\\") {
|
|
2218
|
+
cls += "\\\\";
|
|
2219
|
+
} else if (ch === "]" || ch === "^") {
|
|
2220
|
+
cls += `\\${ch}`;
|
|
2221
|
+
} else {
|
|
2222
|
+
cls += ch;
|
|
2223
|
+
}
|
|
2224
|
+
i++;
|
|
2225
|
+
}
|
|
2226
|
+
cls += "]";
|
|
2227
|
+
re += cls;
|
|
2228
|
+
i++;
|
|
2229
|
+
} else {
|
|
2230
|
+
re += escapeRegex(c ?? "");
|
|
2231
|
+
i++;
|
|
2207
2232
|
}
|
|
2208
|
-
out.push(msg);
|
|
2209
2233
|
}
|
|
2234
|
+
re += "$";
|
|
2235
|
+
return new RegExp(re);
|
|
2236
|
+
}
|
|
2237
|
+
function matchGlob(pattern, input) {
|
|
2238
|
+
return getCachedGlob(pattern).test(input);
|
|
2239
|
+
}
|
|
2240
|
+
function matchAny(patterns, input) {
|
|
2241
|
+
return patterns.some((p) => matchGlob(p, input));
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
// src/utils/incoming-images.ts
|
|
2245
|
+
var MAX_INCOMING_IMAGES = 8;
|
|
2246
|
+
var MAX_INCOMING_IMAGE_BYTES = 8 * 1024 * 1024;
|
|
2247
|
+
var ALLOWED_IMAGE_MEDIA_TYPES = /* @__PURE__ */ new Set([
|
|
2248
|
+
"image/png",
|
|
2249
|
+
"image/jpeg",
|
|
2250
|
+
"image/webp",
|
|
2251
|
+
"image/gif"
|
|
2252
|
+
]);
|
|
2253
|
+
var IncomingImageError = class extends Error {
|
|
2254
|
+
constructor(message) {
|
|
2255
|
+
super(message);
|
|
2256
|
+
this.name = "IncomingImageError";
|
|
2257
|
+
}
|
|
2258
|
+
};
|
|
2259
|
+
var DATA_URL_RE = /^data:([a-z0-9.+-]+\/[a-z0-9.+-]+)?(?:;[a-z0-9-]+=[^;,]*)*(;base64)?,/i;
|
|
2260
|
+
function splitDataUrl(data) {
|
|
2261
|
+
const match = DATA_URL_RE.exec(data);
|
|
2262
|
+
if (!match) return { base64: data.trim() };
|
|
2210
2263
|
return {
|
|
2211
|
-
|
|
2212
|
-
|
|
2264
|
+
base64: data.slice(match[0].length).trim(),
|
|
2265
|
+
mediaType: match[1]?.toLowerCase()
|
|
2213
2266
|
};
|
|
2214
2267
|
}
|
|
2215
|
-
function
|
|
2216
|
-
|
|
2268
|
+
function parseIncomingImages(images, legacyImageBase64) {
|
|
2269
|
+
const raw = [...images ?? []];
|
|
2270
|
+
if (legacyImageBase64) raw.push({ data: legacyImageBase64 });
|
|
2271
|
+
if (raw.length === 0) return [];
|
|
2272
|
+
if (raw.length > MAX_INCOMING_IMAGES) {
|
|
2273
|
+
throw new IncomingImageError(
|
|
2274
|
+
`Too many images: ${raw.length} (max ${MAX_INCOMING_IMAGES} per message).`
|
|
2275
|
+
);
|
|
2276
|
+
}
|
|
2277
|
+
return raw.map((img, i) => {
|
|
2278
|
+
const { base64, mediaType: fromUrl } = splitDataUrl(img.data ?? "");
|
|
2279
|
+
const mediaType = (img.mediaType ?? fromUrl ?? "image/png").toLowerCase();
|
|
2280
|
+
if (!ALLOWED_IMAGE_MEDIA_TYPES.has(mediaType)) {
|
|
2281
|
+
throw new IncomingImageError(
|
|
2282
|
+
`Image ${i + 1}: unsupported media type "${mediaType}" (allowed: ${[...ALLOWED_IMAGE_MEDIA_TYPES].join(", ")}).`
|
|
2283
|
+
);
|
|
2284
|
+
}
|
|
2285
|
+
if (!base64) {
|
|
2286
|
+
throw new IncomingImageError(`Image ${i + 1}: empty image data.`);
|
|
2287
|
+
}
|
|
2288
|
+
if (!/^[A-Za-z0-9+/]+={0,2}$/.test(base64)) {
|
|
2289
|
+
throw new IncomingImageError(`Image ${i + 1}: data is not valid base64.`);
|
|
2290
|
+
}
|
|
2291
|
+
const bytes = Math.floor(base64.length * 3 / 4);
|
|
2292
|
+
if (bytes > MAX_INCOMING_IMAGE_BYTES) {
|
|
2293
|
+
throw new IncomingImageError(
|
|
2294
|
+
`Image ${i + 1}: ${(bytes / (1024 * 1024)).toFixed(1)} MB exceeds the ${MAX_INCOMING_IMAGE_BYTES / (1024 * 1024)} MB limit.`
|
|
2295
|
+
);
|
|
2296
|
+
}
|
|
2297
|
+
return {
|
|
2298
|
+
type: "image",
|
|
2299
|
+
source: { type: "base64", media_type: mediaType, data: base64 }
|
|
2300
|
+
};
|
|
2301
|
+
});
|
|
2217
2302
|
}
|
|
2218
|
-
function
|
|
2219
|
-
|
|
2303
|
+
function buildUserContentBlocks(text, images) {
|
|
2304
|
+
const blocks = [...images];
|
|
2305
|
+
if (text) blocks.push({ type: "text", text });
|
|
2306
|
+
return blocks;
|
|
2220
2307
|
}
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2308
|
+
|
|
2309
|
+
// src/utils/ip-guard.ts
|
|
2310
|
+
import * as dns from "node:dns/promises";
|
|
2311
|
+
import * as net from "node:net";
|
|
2312
|
+
function isPrivateIPv4(addr) {
|
|
2313
|
+
const parts = addr.split(".").map((p) => Number.parseInt(p, 10));
|
|
2314
|
+
if (parts.length !== 4 || parts.some((n) => Number.isNaN(n) || n < 0 || n > 255)) {
|
|
2315
|
+
return true;
|
|
2226
2316
|
}
|
|
2227
|
-
|
|
2317
|
+
const [a, b, c] = parts;
|
|
2318
|
+
if (a === 0) return true;
|
|
2319
|
+
if (a === 10) return true;
|
|
2320
|
+
if (a === 127) return true;
|
|
2321
|
+
if (a === 169 && b === 254) return true;
|
|
2322
|
+
if (a === 172 && b >= 16 && b <= 31) return true;
|
|
2323
|
+
if (a === 192 && b === 168) return true;
|
|
2324
|
+
if (a === 192 && b === 0 && c === 0) return true;
|
|
2325
|
+
if (a === 100 && b >= 64 && b <= 127) return true;
|
|
2326
|
+
if (a >= 224) return true;
|
|
2327
|
+
return false;
|
|
2228
2328
|
}
|
|
2229
|
-
function
|
|
2230
|
-
const
|
|
2231
|
-
if (
|
|
2232
|
-
|
|
2233
|
-
|
|
2329
|
+
function isPrivateIPv6(raw) {
|
|
2330
|
+
const lower = raw.toLowerCase();
|
|
2331
|
+
if (lower === "::" || lower === "::1") return true;
|
|
2332
|
+
const groups = expandIPv6(lower);
|
|
2333
|
+
if (!groups) return true;
|
|
2334
|
+
if (groups[0] === 0 && groups[1] === 0 && groups[2] === 0 && groups[3] === 0 && groups[4] === 0 && groups[5] === 65535) {
|
|
2335
|
+
const a = (groups[6] ?? 0) >> 8;
|
|
2336
|
+
const b = (groups[6] ?? 0) & 255;
|
|
2337
|
+
const c = (groups[7] ?? 0) >> 8;
|
|
2338
|
+
const d = (groups[7] ?? 0) & 255;
|
|
2339
|
+
return isPrivateIPv4(`${a}.${b}.${c}.${d}`);
|
|
2234
2340
|
}
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2341
|
+
const high = groups[0] ?? 0;
|
|
2342
|
+
if ((high & 65024) === 64512) return true;
|
|
2343
|
+
if ((high & 65472) === 65152) return true;
|
|
2344
|
+
if ((high & 65280) === 65280) return true;
|
|
2345
|
+
return false;
|
|
2239
2346
|
}
|
|
2240
|
-
function
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
return
|
|
2347
|
+
function expandIPv6(addr) {
|
|
2348
|
+
const parts = addr.split("::");
|
|
2349
|
+
if (parts.length > 2) return null;
|
|
2350
|
+
const parseGroups = (s) => {
|
|
2351
|
+
if (s === "") return [];
|
|
2352
|
+
const out = [];
|
|
2353
|
+
for (const g of s.split(":")) {
|
|
2354
|
+
if (g.length === 0 || g.length > 4) return null;
|
|
2355
|
+
const n = Number.parseInt(g, 16);
|
|
2356
|
+
if (Number.isNaN(n) || n < 0 || n > 65535) return null;
|
|
2357
|
+
out.push(n);
|
|
2358
|
+
}
|
|
2359
|
+
return out;
|
|
2360
|
+
};
|
|
2361
|
+
if (parts.length === 1) {
|
|
2362
|
+
const groups = parseGroups(parts[0] ?? "");
|
|
2363
|
+
if (groups?.length !== 8) return null;
|
|
2364
|
+
return groups;
|
|
2245
2365
|
}
|
|
2246
|
-
|
|
2366
|
+
const head = parseGroups(parts[0] ?? "");
|
|
2367
|
+
const tail = parseGroups(parts[1] ?? "");
|
|
2368
|
+
if (!head || !tail) return null;
|
|
2369
|
+
const fill = 8 - head.length - tail.length;
|
|
2370
|
+
if (fill < 0) return null;
|
|
2371
|
+
return [...head, ...new Array(fill).fill(0), ...tail];
|
|
2247
2372
|
}
|
|
2248
|
-
function
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2373
|
+
async function assertNotPrivateHost(hostname) {
|
|
2374
|
+
const host = hostname.startsWith("[") && hostname.endsWith("]") ? hostname.slice(1, -1) : hostname;
|
|
2375
|
+
if (host === "localhost" || host.endsWith(".localhost")) {
|
|
2376
|
+
throw new Error("fetch: blocked localhost target");
|
|
2377
|
+
}
|
|
2378
|
+
const ipVersion = net.isIP(host);
|
|
2379
|
+
if (ipVersion === 4) {
|
|
2380
|
+
if (isPrivateIPv4(host)) {
|
|
2381
|
+
throw new Error(`fetch: blocked private/loopback address "${host}"`);
|
|
2254
2382
|
}
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2383
|
+
} else if (ipVersion === 6) {
|
|
2384
|
+
if (isPrivateIPv6(host)) {
|
|
2385
|
+
throw new Error(`fetch: blocked private/loopback address "${host}"`);
|
|
2258
2386
|
}
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
}
|
|
2266
|
-
|
|
2267
|
-
// src/utils/newline-normalize.ts
|
|
2268
|
-
function detectNewlineStyle(text) {
|
|
2269
|
-
let lf = 0;
|
|
2270
|
-
let crlf = 0;
|
|
2271
|
-
let cr = 0;
|
|
2272
|
-
for (let i = 0; i < text.length; i++) {
|
|
2273
|
-
const c = text.charCodeAt(i);
|
|
2274
|
-
if (c === 13) {
|
|
2275
|
-
if (text.charCodeAt(i + 1) === 10) {
|
|
2276
|
-
crlf++;
|
|
2277
|
-
i++;
|
|
2278
|
-
} else {
|
|
2279
|
-
cr++;
|
|
2387
|
+
} else {
|
|
2388
|
+
try {
|
|
2389
|
+
const records = await dns.lookup(host, { all: true });
|
|
2390
|
+
for (const r of records) {
|
|
2391
|
+
const bad = r.family === 4 ? isPrivateIPv4(r.address) : isPrivateIPv6(r.address);
|
|
2392
|
+
if (bad) {
|
|
2393
|
+
throw new Error(`fetch: resolved to private address ${r.address}`);
|
|
2394
|
+
}
|
|
2280
2395
|
}
|
|
2281
|
-
}
|
|
2282
|
-
|
|
2396
|
+
} catch (err) {
|
|
2397
|
+
if (err instanceof Error && err.message.startsWith("fetch:")) throw err;
|
|
2283
2398
|
}
|
|
2284
2399
|
}
|
|
2285
|
-
if (crlf > lf && crlf > cr) return "crlf";
|
|
2286
|
-
if (cr > lf && cr > crlf) return "cr";
|
|
2287
|
-
return "lf";
|
|
2288
|
-
}
|
|
2289
|
-
function toStyle(text, style) {
|
|
2290
|
-
const normalized = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
2291
|
-
if (style === "lf") return normalized;
|
|
2292
|
-
if (style === "crlf") return normalized.replace(/\n/g, "\r\n");
|
|
2293
|
-
return normalized.replace(/\n/g, "\r");
|
|
2294
|
-
}
|
|
2295
|
-
function normalizeToLf(text) {
|
|
2296
|
-
return text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
2297
2400
|
}
|
|
2298
2401
|
|
|
2299
|
-
// src/utils/
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
];
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2402
|
+
// src/utils/json-repair.ts
|
|
2403
|
+
function completePartialObject(s) {
|
|
2404
|
+
if (!s.trim().startsWith("{")) return s;
|
|
2405
|
+
if (tryParse(s).ok) return s;
|
|
2406
|
+
return repairTruncated(s);
|
|
2407
|
+
}
|
|
2408
|
+
function repairTruncated(s) {
|
|
2409
|
+
const stack = [];
|
|
2410
|
+
let inString = false;
|
|
2411
|
+
let escaped = false;
|
|
2412
|
+
let sawKey = false;
|
|
2413
|
+
let prevSig = "";
|
|
2414
|
+
let contentEnd = 0;
|
|
2415
|
+
let stringBraceDepth = 0;
|
|
2416
|
+
for (let i = 0; i < s.length; i++) {
|
|
2417
|
+
const ch = expectDefined(s[i]);
|
|
2418
|
+
if (inString) {
|
|
2419
|
+
contentEnd = i + 1;
|
|
2420
|
+
if (escaped) {
|
|
2421
|
+
escaped = false;
|
|
2422
|
+
continue;
|
|
2423
|
+
}
|
|
2424
|
+
if (ch === "\\") {
|
|
2425
|
+
escaped = true;
|
|
2426
|
+
continue;
|
|
2427
|
+
}
|
|
2428
|
+
if (ch === '"') {
|
|
2429
|
+
inString = false;
|
|
2430
|
+
prevSig = '"';
|
|
2431
|
+
stringBraceDepth = 0;
|
|
2432
|
+
continue;
|
|
2433
|
+
}
|
|
2434
|
+
if (ch === "{") stringBraceDepth++;
|
|
2435
|
+
else if (ch === "}" && stringBraceDepth > 0) stringBraceDepth--;
|
|
2436
|
+
continue;
|
|
2437
|
+
}
|
|
2438
|
+
if (ch === " " || ch === " " || ch === "\n" || ch === "\r") continue;
|
|
2439
|
+
contentEnd = i + 1;
|
|
2440
|
+
if (ch === '"') {
|
|
2441
|
+
inString = true;
|
|
2442
|
+
sawKey = true;
|
|
2443
|
+
stringBraceDepth = 0;
|
|
2444
|
+
prevSig = '"';
|
|
2445
|
+
} else if (ch === "{" || ch === "[") {
|
|
2446
|
+
stack.push(ch);
|
|
2447
|
+
prevSig = ch;
|
|
2448
|
+
} else if (ch === "}" || ch === "]") {
|
|
2449
|
+
stack.pop();
|
|
2450
|
+
prevSig = ch;
|
|
2451
|
+
} else {
|
|
2452
|
+
prevSig = ch;
|
|
2453
|
+
}
|
|
2310
2454
|
}
|
|
2311
|
-
if (
|
|
2312
|
-
|
|
2455
|
+
if (!sawKey && !inString) return s;
|
|
2456
|
+
let result = s.slice(0, contentEnd);
|
|
2457
|
+
if (inString) {
|
|
2458
|
+
if (escaped) {
|
|
2459
|
+
result = result.slice(0, -1);
|
|
2460
|
+
} else if (endsWithInvalidEscape(result)) {
|
|
2461
|
+
result = result.slice(0, -2);
|
|
2462
|
+
}
|
|
2463
|
+
if (stringBraceDepth > 0) result += "}".repeat(stringBraceDepth);
|
|
2464
|
+
result += '"';
|
|
2465
|
+
} else if (prevSig === ":") {
|
|
2466
|
+
result += "null";
|
|
2313
2467
|
}
|
|
2314
|
-
|
|
2315
|
-
|
|
2468
|
+
for (let k = stack.length - 1; k >= 0; k--) {
|
|
2469
|
+
result += stack[k] === "{" ? "}" : "]";
|
|
2316
2470
|
}
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
ok: false,
|
|
2321
|
-
reason: "pattern looks vulnerable to catastrophic backtracking \u2014 rewrite without nested quantifiers"
|
|
2322
|
-
};
|
|
2323
|
-
}
|
|
2471
|
+
if (!tryParse(result).ok) {
|
|
2472
|
+
const patched = result.replace(/:(\s*)([}\]])/g, ":null$2");
|
|
2473
|
+
if (tryParse(patched).ok) result = patched;
|
|
2324
2474
|
}
|
|
2475
|
+
return result;
|
|
2476
|
+
}
|
|
2477
|
+
var VALID_ESCAPE = /* @__PURE__ */ new Set(['"', "\\", "/", "b", "f", "n", "r", "t", "u"]);
|
|
2478
|
+
function endsWithInvalidEscape(str) {
|
|
2479
|
+
const last = str[str.length - 1];
|
|
2480
|
+
if (str[str.length - 2] !== "\\" || last === void 0) return false;
|
|
2481
|
+
if (VALID_ESCAPE.has(last)) return false;
|
|
2482
|
+
let backslashes = 0;
|
|
2483
|
+
for (let k = str.length - 2; k >= 0 && str[k] === "\\"; k--) backslashes++;
|
|
2484
|
+
return backslashes % 2 === 1;
|
|
2485
|
+
}
|
|
2486
|
+
function tryParse(s) {
|
|
2325
2487
|
try {
|
|
2326
|
-
return { ok: true,
|
|
2327
|
-
} catch
|
|
2328
|
-
return {
|
|
2329
|
-
ok: false,
|
|
2330
|
-
reason: err instanceof Error ? err.message : "invalid regex"
|
|
2331
|
-
};
|
|
2488
|
+
return { ok: true, value: JSON.parse(s) };
|
|
2489
|
+
} catch {
|
|
2490
|
+
return { ok: false };
|
|
2332
2491
|
}
|
|
2333
2492
|
}
|
|
2334
2493
|
|
|
2335
|
-
// src/utils/
|
|
2336
|
-
function
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
}
|
|
2340
|
-
try {
|
|
2341
|
-
return { ok: true, value: JSON.parse(input) };
|
|
2342
|
-
} catch (err) {
|
|
2343
|
-
return {
|
|
2344
|
-
ok: false,
|
|
2345
|
-
error: toErrorMessage(err)
|
|
2346
|
-
};
|
|
2347
|
-
}
|
|
2494
|
+
// src/utils/json-schema-validate.ts
|
|
2495
|
+
function validateAgainstSchema(value, schema) {
|
|
2496
|
+
const errors = [];
|
|
2497
|
+
walk(value, schema, "", errors, 0);
|
|
2498
|
+
return { ok: errors.length === 0, errors };
|
|
2348
2499
|
}
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
}
|
|
2356
|
-
if (typeof v === "object" && v !== null) {
|
|
2357
|
-
if (seen.has(v)) return "[Circular]";
|
|
2358
|
-
seen.add(v);
|
|
2359
|
-
}
|
|
2360
|
-
return v;
|
|
2361
|
-
};
|
|
2362
|
-
try {
|
|
2363
|
-
return JSON.stringify(value, replacer, pretty ? 2 : void 0) ?? "null";
|
|
2364
|
-
} catch (err) {
|
|
2365
|
-
return JSON.stringify({
|
|
2366
|
-
__serialization_error: toErrorMessage(err)
|
|
2500
|
+
var MAX_SCHEMA_DEPTH = 64;
|
|
2501
|
+
function walk(value, schema, path6, errors, depth) {
|
|
2502
|
+
if (depth > MAX_SCHEMA_DEPTH) {
|
|
2503
|
+
errors.push({
|
|
2504
|
+
path: path6 || "<root>",
|
|
2505
|
+
message: `schema nesting exceeds maximum depth (${MAX_SCHEMA_DEPTH})`
|
|
2367
2506
|
});
|
|
2507
|
+
return;
|
|
2368
2508
|
}
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
return out;
|
|
2378
|
-
} catch {
|
|
2379
|
-
return null;
|
|
2509
|
+
if (schema.enum !== void 0) {
|
|
2510
|
+
if (!enumIncludes(schema.enum, value)) {
|
|
2511
|
+
errors.push({
|
|
2512
|
+
path: path6 || "<root>",
|
|
2513
|
+
message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
|
|
2514
|
+
});
|
|
2515
|
+
return;
|
|
2516
|
+
}
|
|
2380
2517
|
}
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2518
|
+
if (typeof schema.type === "string") {
|
|
2519
|
+
if (!checkType(value, schema.type)) {
|
|
2520
|
+
errors.push({
|
|
2521
|
+
path: path6 || "<root>",
|
|
2522
|
+
message: `expected ${schema.type}, got ${describeType(value)} (${previewValue(value)})`
|
|
2523
|
+
});
|
|
2524
|
+
return;
|
|
2525
|
+
}
|
|
2388
2526
|
}
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
inString = !inString;
|
|
2400
|
-
out += c;
|
|
2401
|
-
continue;
|
|
2527
|
+
if (schema.type === "object" && isPlainObject(value)) {
|
|
2528
|
+
const obj = value;
|
|
2529
|
+
for (const req of schema.required ?? []) {
|
|
2530
|
+
if (!(req in obj)) {
|
|
2531
|
+
const expected = schema.properties?.[req]?.type;
|
|
2532
|
+
errors.push({
|
|
2533
|
+
path: joinPath(path6, req),
|
|
2534
|
+
message: `required property missing${typeof expected === "string" ? ` (expected ${expected})` : ""}`
|
|
2535
|
+
});
|
|
2536
|
+
}
|
|
2402
2537
|
}
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
break;
|
|
2409
|
-
case "\r":
|
|
2410
|
-
out += "\\r";
|
|
2411
|
-
break;
|
|
2412
|
-
case " ":
|
|
2413
|
-
out += "\\t";
|
|
2414
|
-
break;
|
|
2415
|
-
case "\b":
|
|
2416
|
-
out += "\\b";
|
|
2417
|
-
break;
|
|
2418
|
-
case "\f":
|
|
2419
|
-
out += "\\f";
|
|
2420
|
-
break;
|
|
2421
|
-
default:
|
|
2422
|
-
out += `\\u${code.toString(16).padStart(4, "0")}`;
|
|
2538
|
+
if (schema.properties) {
|
|
2539
|
+
for (const [key, subSchema] of Object.entries(schema.properties)) {
|
|
2540
|
+
if (key in obj) {
|
|
2541
|
+
walk(obj[key], subSchema, joinPath(path6, key), errors, depth + 1);
|
|
2542
|
+
}
|
|
2423
2543
|
}
|
|
2424
|
-
continue;
|
|
2425
2544
|
}
|
|
2426
|
-
out += c;
|
|
2427
2545
|
}
|
|
2428
|
-
|
|
2546
|
+
if (schema.type === "array" && Array.isArray(value) && schema.items) {
|
|
2547
|
+
for (let i = 0; i < value.length; i++) {
|
|
2548
|
+
walk(value[i], schema.items, `${path6}[${i}]`, errors, depth + 1);
|
|
2549
|
+
}
|
|
2550
|
+
}
|
|
2429
2551
|
}
|
|
2430
|
-
function
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
if (
|
|
2438
|
-
|
|
2439
|
-
if (escaped) {
|
|
2440
|
-
escaped = false;
|
|
2441
|
-
} else if (c === "\\") {
|
|
2442
|
-
escaped = true;
|
|
2443
|
-
} else if (c === '"') {
|
|
2444
|
-
inString = false;
|
|
2445
|
-
}
|
|
2446
|
-
i++;
|
|
2447
|
-
continue;
|
|
2552
|
+
function coerceAgainstSchema(value, schema) {
|
|
2553
|
+
return coerceWalk(value, schema, 0);
|
|
2554
|
+
}
|
|
2555
|
+
function coerceWalk(value, schema, depth) {
|
|
2556
|
+
if (depth > MAX_SCHEMA_DEPTH) return { value, changed: false };
|
|
2557
|
+
const type = typeof schema.type === "string" ? schema.type : void 0;
|
|
2558
|
+
if (type && !checkType(value, type)) {
|
|
2559
|
+
if (type === "string" && (typeof value === "number" || typeof value === "boolean")) {
|
|
2560
|
+
return { value: String(value), changed: true };
|
|
2448
2561
|
}
|
|
2449
|
-
if (
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2562
|
+
if ((type === "number" || type === "integer") && typeof value === "string") {
|
|
2563
|
+
const trimmed = value.trim();
|
|
2564
|
+
if (trimmed !== "" && /^-?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$/.test(trimmed)) {
|
|
2565
|
+
const num = Number(trimmed);
|
|
2566
|
+
if (!Number.isNaN(num) && (type === "number" || Number.isInteger(num))) {
|
|
2567
|
+
return { value: num, changed: true };
|
|
2568
|
+
}
|
|
2569
|
+
}
|
|
2570
|
+
return { value, changed: false };
|
|
2454
2571
|
}
|
|
2455
|
-
if (
|
|
2456
|
-
|
|
2457
|
-
|
|
2572
|
+
if (type === "boolean" && typeof value === "string") {
|
|
2573
|
+
const lowered = value.trim().toLowerCase();
|
|
2574
|
+
if (lowered === "true") return { value: true, changed: true };
|
|
2575
|
+
if (lowered === "false") return { value: false, changed: true };
|
|
2576
|
+
return { value, changed: false };
|
|
2458
2577
|
}
|
|
2459
|
-
if (
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2578
|
+
if ((type === "object" || type === "array") && typeof value === "string") {
|
|
2579
|
+
try {
|
|
2580
|
+
const parsed = JSON.parse(value);
|
|
2581
|
+
if (checkType(parsed, type)) {
|
|
2582
|
+
return { value: coerceWalk(parsed, schema, depth + 1).value, changed: true };
|
|
2583
|
+
}
|
|
2584
|
+
} catch {
|
|
2464
2585
|
}
|
|
2465
|
-
|
|
2466
|
-
continue;
|
|
2586
|
+
return { value, changed: false };
|
|
2467
2587
|
}
|
|
2468
|
-
|
|
2469
|
-
i++;
|
|
2470
|
-
}
|
|
2471
|
-
return chars.join("");
|
|
2472
|
-
}
|
|
2473
|
-
|
|
2474
|
-
// src/utils/session-scoped-path.ts
|
|
2475
|
-
import * as path4 from "node:path";
|
|
2476
|
-
function sessionScopedPath(dir, sessionId, suffix) {
|
|
2477
|
-
if (!sessionId || sessionId.includes("\\") || sessionId.includes("..")) {
|
|
2478
|
-
throw invalid(sessionId);
|
|
2479
|
-
}
|
|
2480
|
-
const resolved = path4.resolve(dir, `${sessionId}${suffix}`);
|
|
2481
|
-
const rel = path4.relative(path4.resolve(dir), resolved);
|
|
2482
|
-
if (rel.startsWith("..") || path4.isAbsolute(rel)) {
|
|
2483
|
-
throw invalid(sessionId);
|
|
2588
|
+
return { value, changed: false };
|
|
2484
2589
|
}
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
// src/utils/sleep.ts
|
|
2497
|
-
function sleep(ms) {
|
|
2498
|
-
return new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
2499
|
-
}
|
|
2500
|
-
|
|
2501
|
-
// src/utils/slug.ts
|
|
2502
|
-
function slugify(name, fallback = "prompt", maxLen = 64) {
|
|
2503
|
-
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, maxLen).replace(/-+$/g, "") || fallback;
|
|
2504
|
-
}
|
|
2505
|
-
|
|
2506
|
-
// src/utils/string.ts
|
|
2507
|
-
function truncate(s, max) {
|
|
2508
|
-
return s.length <= max ? s : `${s.slice(0, max - 1)}\u2026`;
|
|
2509
|
-
}
|
|
2510
|
-
|
|
2511
|
-
// src/utils/task-format.ts
|
|
2512
|
-
function computeTaskItemProgress(tasks) {
|
|
2513
|
-
let completed = 0;
|
|
2514
|
-
let pending = 0;
|
|
2515
|
-
let inProgress = 0;
|
|
2516
|
-
let blocked = 0;
|
|
2517
|
-
let failed = 0;
|
|
2518
|
-
let review = 0;
|
|
2519
|
-
let estimatedHours = 0;
|
|
2520
|
-
const actualHours = 0;
|
|
2521
|
-
for (const t of tasks) {
|
|
2522
|
-
switch (t.status) {
|
|
2523
|
-
case "completed":
|
|
2524
|
-
completed++;
|
|
2525
|
-
break;
|
|
2526
|
-
case "pending":
|
|
2527
|
-
pending++;
|
|
2528
|
-
break;
|
|
2529
|
-
case "in_progress":
|
|
2530
|
-
inProgress++;
|
|
2531
|
-
break;
|
|
2532
|
-
case "blocked":
|
|
2533
|
-
blocked++;
|
|
2534
|
-
break;
|
|
2535
|
-
case "failed":
|
|
2536
|
-
failed++;
|
|
2537
|
-
break;
|
|
2538
|
-
case "review":
|
|
2539
|
-
review++;
|
|
2540
|
-
break;
|
|
2590
|
+
if (type === "object" && isPlainObject(value) && schema.properties) {
|
|
2591
|
+
const obj = value;
|
|
2592
|
+
let changed = false;
|
|
2593
|
+
const out = { ...obj };
|
|
2594
|
+
for (const [key, subSchema] of Object.entries(schema.properties)) {
|
|
2595
|
+
if (!(key in obj)) continue;
|
|
2596
|
+
const r = coerceWalk(obj[key], subSchema, depth + 1);
|
|
2597
|
+
if (r.changed) {
|
|
2598
|
+
out[key] = r.value;
|
|
2599
|
+
changed = true;
|
|
2600
|
+
}
|
|
2541
2601
|
}
|
|
2542
|
-
|
|
2602
|
+
return changed ? { value: out, changed } : { value, changed: false };
|
|
2543
2603
|
}
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
actualHours
|
|
2555
|
-
};
|
|
2556
|
-
}
|
|
2557
|
-
var STATUS_ICON = {
|
|
2558
|
-
pending: "\u25CB",
|
|
2559
|
-
in_progress: "\u25D0",
|
|
2560
|
-
blocked: "\u2298",
|
|
2561
|
-
failed: "\u2717",
|
|
2562
|
-
review: "\u25D1",
|
|
2563
|
-
completed: "\u25CF"
|
|
2564
|
-
};
|
|
2565
|
-
var PRIORITY_ICON = {
|
|
2566
|
-
critical: "\u{1F534}",
|
|
2567
|
-
high: "\u{1F7E0}",
|
|
2568
|
-
medium: "\u{1F7E1}",
|
|
2569
|
-
low: "\u{1F7E2}"
|
|
2570
|
-
};
|
|
2571
|
-
var TYPE_ICON = {
|
|
2572
|
-
feature: "\u26A1",
|
|
2573
|
-
bugfix: "\u{1F41B}",
|
|
2574
|
-
refactor: "\u267B\uFE0F",
|
|
2575
|
-
docs: "\u{1F4DD}",
|
|
2576
|
-
test: "\u{1F9EA}",
|
|
2577
|
-
chore: "\u{1F527}"
|
|
2578
|
-
};
|
|
2579
|
-
function formatTaskProgress(tasks) {
|
|
2580
|
-
const p = computeTaskItemProgress(tasks);
|
|
2581
|
-
if (p.total === 0) return "No tasks.";
|
|
2582
|
-
const barWidth = 24;
|
|
2583
|
-
const filled = Math.round(p.percentComplete / 100 * barWidth);
|
|
2584
|
-
const empty = barWidth - filled;
|
|
2585
|
-
const bar = "\u2588".repeat(filled) + "\u2591".repeat(empty);
|
|
2586
|
-
return [
|
|
2587
|
-
`${color.bold("Tasks")} [${bar}] ${p.percentComplete}%`,
|
|
2588
|
-
` ${color.green("\u25CF")} ${p.completed} done \u2502 ${color.yellow("\u25D0")} ${p.inProgress} active \u2502 ${color.dim("\u25CB")} ${p.pending} pending \u2502 \u2298 ${p.blocked} blocked \u2502 \u2717 ${p.failed} failed`,
|
|
2589
|
-
p.estimatedHours > 0 ? ` ${color.dim(`est. ${p.estimatedHours}h`)}` : ""
|
|
2590
|
-
].filter(Boolean).join("\n");
|
|
2604
|
+
if (type === "array" && Array.isArray(value) && schema.items) {
|
|
2605
|
+
let changed = false;
|
|
2606
|
+
const out = value.map((item) => {
|
|
2607
|
+
const r = coerceWalk(item, schema.items, depth + 1);
|
|
2608
|
+
if (r.changed) changed = true;
|
|
2609
|
+
return r.value;
|
|
2610
|
+
});
|
|
2611
|
+
return changed ? { value: out, changed } : { value, changed: false };
|
|
2612
|
+
}
|
|
2613
|
+
return { value, changed: false };
|
|
2591
2614
|
}
|
|
2592
|
-
function
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2615
|
+
function checkType(value, type) {
|
|
2616
|
+
switch (type) {
|
|
2617
|
+
case "string":
|
|
2618
|
+
return typeof value === "string";
|
|
2619
|
+
case "number":
|
|
2620
|
+
return typeof value === "number" && !Number.isNaN(value);
|
|
2621
|
+
case "integer":
|
|
2622
|
+
return typeof value === "number" && Number.isInteger(value);
|
|
2623
|
+
case "boolean":
|
|
2624
|
+
return typeof value === "boolean";
|
|
2625
|
+
case "null":
|
|
2626
|
+
return value === null;
|
|
2627
|
+
case "array":
|
|
2628
|
+
return Array.isArray(value);
|
|
2629
|
+
case "object":
|
|
2630
|
+
return isPlainObject(value);
|
|
2631
|
+
default:
|
|
2632
|
+
return true;
|
|
2600
2633
|
}
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2634
|
+
}
|
|
2635
|
+
function isPlainObject(v) {
|
|
2636
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
2637
|
+
}
|
|
2638
|
+
function describeType(v) {
|
|
2639
|
+
if (v === null) return "null";
|
|
2640
|
+
if (Array.isArray(v)) return "array";
|
|
2641
|
+
return typeof v;
|
|
2642
|
+
}
|
|
2643
|
+
function previewValue(v) {
|
|
2644
|
+
try {
|
|
2645
|
+
const s = JSON.stringify(v);
|
|
2646
|
+
if (s === void 0) return String(v);
|
|
2647
|
+
return s.length > 80 ? `${s.slice(0, 80)}\u2026` : s;
|
|
2648
|
+
} catch {
|
|
2649
|
+
return String(v);
|
|
2616
2650
|
}
|
|
2617
|
-
return lines.join("\n");
|
|
2618
2651
|
}
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
if (todos.length === 0) return "No todos.";
|
|
2623
|
-
const lines = [];
|
|
2624
|
-
const done = todos.filter((t) => t.status === "completed").length;
|
|
2625
|
-
lines.push(color.dim(`Todos (${done}/${todos.length} done):`));
|
|
2626
|
-
todos.forEach((t, i) => {
|
|
2627
|
-
const mark = t.status === "completed" ? color.green("[x]") : t.status === "in_progress" ? color.yellow("[~]") : color.dim("[ ]");
|
|
2628
|
-
const text = t.status === "in_progress" && t.activeForm ? t.activeForm : t.content;
|
|
2629
|
-
const label = t.status === "completed" ? color.dim(text) : text;
|
|
2630
|
-
lines.push(` ${color.dim(String(i + 1).padStart(2))}. ${mark} ${label}`);
|
|
2631
|
-
});
|
|
2632
|
-
return lines.join("\n");
|
|
2652
|
+
function joinPath(parent, key) {
|
|
2653
|
+
if (!parent) return key;
|
|
2654
|
+
return `${parent}.${key}`;
|
|
2633
2655
|
}
|
|
2634
|
-
function
|
|
2635
|
-
if (
|
|
2636
|
-
return
|
|
2656
|
+
function enumIncludes(values, value) {
|
|
2657
|
+
if (value === null || typeof value !== "object") return values.includes(value);
|
|
2658
|
+
return values.some((candidate) => deepEqual(candidate, value));
|
|
2637
2659
|
}
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
const useDefaultOptions = opts.descriptionMaxChars === void 0 && opts.schemaDescriptionMaxChars === void 0;
|
|
2645
|
-
if (useDefaultOptions && typeof tool === "object" && tool !== null) {
|
|
2646
|
-
const cached = compactCache.get(tool);
|
|
2647
|
-
if (cached) return cached;
|
|
2660
|
+
function deepEqual(a, b) {
|
|
2661
|
+
if (a === b) return true;
|
|
2662
|
+
if (typeof a !== typeof b) return false;
|
|
2663
|
+
if (a === null || b === null) return a === b;
|
|
2664
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
2665
|
+
return a.length === b.length && a.every((v, i) => deepEqual(v, b[i]));
|
|
2648
2666
|
}
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
compactSchemaDescriptions(
|
|
2657
|
-
tool.inputSchema,
|
|
2658
|
-
opts.schemaDescriptionMaxChars ?? SCHEMA_DESCRIPTION_MAX_CHARS
|
|
2659
|
-
)
|
|
2660
|
-
)
|
|
2661
|
-
};
|
|
2662
|
-
if (useDefaultOptions && typeof tool === "object" && tool !== null) {
|
|
2663
|
-
compactCache.set(tool, compact);
|
|
2667
|
+
if (typeof a === "object" && typeof b === "object") {
|
|
2668
|
+
const ak = Object.keys(a);
|
|
2669
|
+
const bk = Object.keys(b);
|
|
2670
|
+
if (ak.length !== bk.length) return false;
|
|
2671
|
+
return ak.every(
|
|
2672
|
+
(k) => deepEqual(a[k], b[k])
|
|
2673
|
+
);
|
|
2664
2674
|
}
|
|
2665
|
-
return
|
|
2675
|
+
return false;
|
|
2666
2676
|
}
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
if (
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
delete out["anyOf"];
|
|
2675
|
-
delete out["allOf"];
|
|
2676
|
-
const properties = isRecord(schema["properties"]) ? { ...schema["properties"] } : {};
|
|
2677
|
-
let required = stringSet(schema["required"]);
|
|
2678
|
-
for (const { keyword, branches } of combinators) {
|
|
2679
|
-
const objectBranches = branches.filter(isRecord);
|
|
2680
|
-
for (const branch of objectBranches) {
|
|
2681
|
-
if (isRecord(branch["properties"])) Object.assign(properties, branch["properties"]);
|
|
2677
|
+
|
|
2678
|
+
// src/utils/merge-custom-models.ts
|
|
2679
|
+
function mergeCustomModelDefs(providerCustomModels, configModels) {
|
|
2680
|
+
const out = {};
|
|
2681
|
+
if (providerCustomModels) {
|
|
2682
|
+
for (const [id, def] of Object.entries(providerCustomModels)) {
|
|
2683
|
+
out[id] = { ...def };
|
|
2682
2684
|
}
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
const common = new Set(
|
|
2688
|
-
[...branchRequired[0]].filter(
|
|
2689
|
-
(field) => branchRequired.slice(1).every((fields) => fields.has(field))
|
|
2690
|
-
)
|
|
2691
|
-
);
|
|
2692
|
-
required = /* @__PURE__ */ new Set([...required, ...common]);
|
|
2685
|
+
}
|
|
2686
|
+
if (configModels) {
|
|
2687
|
+
for (const [id, def] of Object.entries(configModels)) {
|
|
2688
|
+
out[id] = { ...def };
|
|
2693
2689
|
}
|
|
2694
2690
|
}
|
|
2695
|
-
out
|
|
2696
|
-
if (required.size > 0) out["required"] = [...required];
|
|
2697
|
-
else delete out["required"];
|
|
2691
|
+
if (Object.keys(out).length === 0) return void 0;
|
|
2698
2692
|
return out;
|
|
2699
2693
|
}
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2694
|
+
|
|
2695
|
+
// src/utils/merge-models-payload.ts
|
|
2696
|
+
function mergeModelsPayload(base, overlay) {
|
|
2697
|
+
const out = {};
|
|
2698
|
+
for (const [id, provider] of Object.entries(base)) {
|
|
2699
|
+
out[id] = cloneProvider(provider);
|
|
2700
|
+
}
|
|
2701
|
+
for (const [id, ovProvider] of Object.entries(overlay)) {
|
|
2702
|
+
const existing = out[id];
|
|
2703
|
+
out[id] = existing ? mergeProvider(existing, ovProvider) : cloneProvider(ovProvider);
|
|
2704
|
+
}
|
|
2705
|
+
return out;
|
|
2704
2706
|
}
|
|
2705
|
-
function
|
|
2706
|
-
const
|
|
2707
|
-
|
|
2707
|
+
function mergeProvider(base, overlay) {
|
|
2708
|
+
const models = {};
|
|
2709
|
+
for (const [mid, m] of Object.entries(base.models ?? {})) {
|
|
2710
|
+
models[mid] = { ...m };
|
|
2711
|
+
}
|
|
2712
|
+
for (const [mid, ovModel] of Object.entries(overlay.models ?? {})) {
|
|
2713
|
+
const existing = models[mid];
|
|
2714
|
+
models[mid] = existing ? mergeModel(existing, ovModel) : { ...ovModel };
|
|
2715
|
+
}
|
|
2716
|
+
return {
|
|
2717
|
+
...base,
|
|
2718
|
+
// Overlay scalar fields win when explicitly provided; otherwise keep base.
|
|
2719
|
+
...stripUndefined({
|
|
2720
|
+
id: overlay.id,
|
|
2721
|
+
name: overlay.name,
|
|
2722
|
+
npm: overlay.npm,
|
|
2723
|
+
api: overlay.api,
|
|
2724
|
+
env: overlay.env,
|
|
2725
|
+
doc: overlay.doc
|
|
2726
|
+
}),
|
|
2727
|
+
models
|
|
2728
|
+
};
|
|
2708
2729
|
}
|
|
2709
|
-
function
|
|
2710
|
-
|
|
2711
|
-
|
|
2730
|
+
function mergeModel(base, overlay) {
|
|
2731
|
+
const merged = { ...base, ...overlay };
|
|
2732
|
+
if (base.limit || overlay.limit) {
|
|
2733
|
+
merged.limit = { ...base.limit, ...overlay.limit };
|
|
2712
2734
|
}
|
|
2713
|
-
if (
|
|
2735
|
+
if (base.cost || overlay.cost) {
|
|
2736
|
+
merged.cost = { ...base.cost, ...overlay.cost };
|
|
2737
|
+
}
|
|
2738
|
+
if (base.modalities || overlay.modalities) {
|
|
2739
|
+
merged.modalities = { ...base.modalities, ...overlay.modalities };
|
|
2740
|
+
}
|
|
2741
|
+
return merged;
|
|
2742
|
+
}
|
|
2743
|
+
function cloneProvider(p) {
|
|
2744
|
+
const models = {};
|
|
2745
|
+
for (const [mid, m] of Object.entries(p.models ?? {})) {
|
|
2746
|
+
models[mid] = { ...m };
|
|
2747
|
+
}
|
|
2748
|
+
return { ...p, models };
|
|
2749
|
+
}
|
|
2750
|
+
function stripUndefined(obj) {
|
|
2714
2751
|
const out = {};
|
|
2715
|
-
for (const [
|
|
2716
|
-
if (
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2752
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
2753
|
+
if (v !== void 0) out[k] = v;
|
|
2754
|
+
}
|
|
2755
|
+
return out;
|
|
2756
|
+
}
|
|
2757
|
+
|
|
2758
|
+
// src/utils/newline-normalize.ts
|
|
2759
|
+
function detectNewlineStyle(text) {
|
|
2760
|
+
let lf = 0;
|
|
2761
|
+
let crlf = 0;
|
|
2762
|
+
let cr = 0;
|
|
2763
|
+
for (let i = 0; i < text.length; i++) {
|
|
2764
|
+
const c = text.charCodeAt(i);
|
|
2765
|
+
if (c === 13) {
|
|
2766
|
+
if (text.charCodeAt(i + 1) === 10) {
|
|
2767
|
+
crlf++;
|
|
2768
|
+
i++;
|
|
2769
|
+
} else {
|
|
2770
|
+
cr++;
|
|
2771
|
+
}
|
|
2772
|
+
} else if (c === 10) {
|
|
2773
|
+
lf++;
|
|
2720
2774
|
}
|
|
2721
2775
|
}
|
|
2722
|
-
return
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
const normalized = text.replace(/\s+/g, " ").trim();
|
|
2726
|
-
if (normalized.length <= maxChars) return normalized;
|
|
2727
|
-
if (maxChars <= 20) return normalized.slice(0, maxChars);
|
|
2728
|
-
const hardLimit = maxChars - 12;
|
|
2729
|
-
const boundary = findSemanticBoundary(normalized, hardLimit);
|
|
2730
|
-
const head = normalized.slice(0, boundary > 0 ? boundary : hardLimit).trimEnd();
|
|
2731
|
-
return `${head} ...`;
|
|
2776
|
+
if (crlf > lf && crlf > cr) return "crlf";
|
|
2777
|
+
if (cr > lf && cr > crlf) return "cr";
|
|
2778
|
+
return "lf";
|
|
2732
2779
|
}
|
|
2733
|
-
function
|
|
2734
|
-
const
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
);
|
|
2739
|
-
if (punctuation >= Math.floor(limit * 0.45)) return punctuation + 1;
|
|
2740
|
-
const comma = text.lastIndexOf(", ", limit);
|
|
2741
|
-
if (comma >= Math.floor(limit * 0.6)) return comma + 1;
|
|
2742
|
-
const space = text.lastIndexOf(" ", limit);
|
|
2743
|
-
return space >= Math.floor(limit * 0.6) ? space : limit;
|
|
2780
|
+
function toStyle(text, style) {
|
|
2781
|
+
const normalized = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
2782
|
+
if (style === "lf") return normalized;
|
|
2783
|
+
if (style === "crlf") return normalized.replace(/\n/g, "\r\n");
|
|
2784
|
+
return normalized.replace(/\n/g, "\r");
|
|
2744
2785
|
}
|
|
2745
|
-
function
|
|
2746
|
-
return
|
|
2786
|
+
function normalizeToLf(text) {
|
|
2787
|
+
return text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
2747
2788
|
}
|
|
2748
2789
|
|
|
2749
|
-
// src/utils/
|
|
2750
|
-
var
|
|
2751
|
-
var
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2790
|
+
// src/utils/regex-guard.ts
|
|
2791
|
+
var MAX_PATTERN_LEN = 512;
|
|
2792
|
+
var DANGEROUS_PATTERNS = [
|
|
2793
|
+
/(\([^)]*[+*][^)]*\))[+*]/,
|
|
2794
|
+
// (a+)+, (.*)+, etc
|
|
2795
|
+
/(\(\?:[^)]*[+*][^)]*\))[+*]/
|
|
2796
|
+
// same, with non-capturing group
|
|
2797
|
+
];
|
|
2798
|
+
function compileUserRegex(pattern, flags) {
|
|
2799
|
+
if (typeof pattern !== "string") {
|
|
2800
|
+
return { ok: false, reason: "pattern must be a string" };
|
|
2759
2801
|
}
|
|
2760
|
-
|
|
2761
|
-
}
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
deepseek: 3.5
|
|
2773
|
-
};
|
|
2774
|
-
var ESTIMATE_CACHE = /* @__PURE__ */ new Map();
|
|
2775
|
-
var _estimateCacheOrder = [];
|
|
2776
|
-
var ESTIMATE_CACHE_MAX_SIZE = 5e4;
|
|
2777
|
-
function getCachedEstimate(key, compute) {
|
|
2778
|
-
const existing = ESTIMATE_CACHE.get(key);
|
|
2779
|
-
if (existing !== void 0) return existing;
|
|
2780
|
-
if (ESTIMATE_CACHE.size >= ESTIMATE_CACHE_MAX_SIZE) {
|
|
2781
|
-
while (ESTIMATE_CACHE.size > Math.floor(ESTIMATE_CACHE_MAX_SIZE / 2)) {
|
|
2782
|
-
const oldest = _estimateCacheOrder.shift();
|
|
2783
|
-
if (oldest !== void 0) ESTIMATE_CACHE.delete(oldest);
|
|
2802
|
+
if (pattern.length === 0) {
|
|
2803
|
+
return { ok: false, reason: "pattern is empty" };
|
|
2804
|
+
}
|
|
2805
|
+
if (pattern.length > MAX_PATTERN_LEN) {
|
|
2806
|
+
return { ok: false, reason: `pattern exceeds ${MAX_PATTERN_LEN} characters` };
|
|
2807
|
+
}
|
|
2808
|
+
for (const rx of DANGEROUS_PATTERNS) {
|
|
2809
|
+
if (rx.test(pattern)) {
|
|
2810
|
+
return {
|
|
2811
|
+
ok: false,
|
|
2812
|
+
reason: "pattern looks vulnerable to catastrophic backtracking \u2014 rewrite without nested quantifiers"
|
|
2813
|
+
};
|
|
2784
2814
|
}
|
|
2785
2815
|
}
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2816
|
+
try {
|
|
2817
|
+
return { ok: true, regex: new RegExp(pattern, flags) };
|
|
2818
|
+
} catch (err) {
|
|
2819
|
+
return {
|
|
2820
|
+
ok: false,
|
|
2821
|
+
reason: err instanceof Error ? err.message : "invalid regex"
|
|
2822
|
+
};
|
|
2823
|
+
}
|
|
2790
2824
|
}
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2825
|
+
|
|
2826
|
+
// src/utils/safe-json.ts
|
|
2827
|
+
function safeParse(input, maxBytes = 5e6) {
|
|
2828
|
+
if (Buffer.byteLength(input, "utf8") > maxBytes) {
|
|
2829
|
+
return { ok: false, error: `Input exceeds limit (${maxBytes} bytes)` };
|
|
2830
|
+
}
|
|
2831
|
+
try {
|
|
2832
|
+
return { ok: true, value: JSON.parse(input) };
|
|
2833
|
+
} catch (err) {
|
|
2834
|
+
return {
|
|
2835
|
+
ok: false,
|
|
2836
|
+
error: toErrorMessage(err)
|
|
2837
|
+
};
|
|
2795
2838
|
}
|
|
2796
|
-
return getCachedEstimate(JSON.stringify(input), (key) => RoughTokenEstimate(key));
|
|
2797
2839
|
}
|
|
2798
|
-
function
|
|
2799
|
-
|
|
2800
|
-
|
|
2840
|
+
function safeStringify(value, pretty = false) {
|
|
2841
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
2842
|
+
const replacer = (_k, v) => {
|
|
2843
|
+
if (typeof v === "bigint") return v.toString();
|
|
2844
|
+
if (v instanceof Error) {
|
|
2845
|
+
return { name: v.name, message: v.message, stack: v.stack };
|
|
2846
|
+
}
|
|
2847
|
+
if (typeof v === "object" && v !== null) {
|
|
2848
|
+
if (seen.has(v)) return "[Circular]";
|
|
2849
|
+
seen.add(v);
|
|
2850
|
+
}
|
|
2851
|
+
return v;
|
|
2852
|
+
};
|
|
2853
|
+
try {
|
|
2854
|
+
return JSON.stringify(value, replacer, pretty ? 2 : void 0) ?? "null";
|
|
2855
|
+
} catch (err) {
|
|
2856
|
+
return JSON.stringify({
|
|
2857
|
+
__serialization_error: toErrorMessage(err)
|
|
2858
|
+
});
|
|
2859
|
+
}
|
|
2801
2860
|
}
|
|
2802
|
-
function
|
|
2803
|
-
|
|
2861
|
+
function sanitizeJsonString(s) {
|
|
2862
|
+
let out = s.trim();
|
|
2863
|
+
out = stripJsonComments(out);
|
|
2864
|
+
out = out.replace(/,(\s*[}\]])/g, "$1");
|
|
2865
|
+
out = escapeControlCharsInStrings(out);
|
|
2866
|
+
try {
|
|
2867
|
+
JSON.parse(out);
|
|
2868
|
+
return out;
|
|
2869
|
+
} catch {
|
|
2870
|
+
return null;
|
|
2871
|
+
}
|
|
2804
2872
|
}
|
|
2805
|
-
function
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
else if (b.type === "tool_result") total += estimateToolResultTokens(b.content);
|
|
2812
|
-
else total += RoughTokenEstimate(JSON.stringify(b));
|
|
2873
|
+
function stripCodeFences(s) {
|
|
2874
|
+
const trimmed = s.trim();
|
|
2875
|
+
const opener = /^```[\w+-]*[ \t]*\r?\n?/.exec(trimmed);
|
|
2876
|
+
if (opener) {
|
|
2877
|
+
const inner = trimmed.slice(opener[0].length).replace(/(\r?\n)?[ \t]*```[ \t]*$/, "");
|
|
2878
|
+
return inner.trim();
|
|
2813
2879
|
}
|
|
2814
|
-
|
|
2880
|
+
const embedded = /```[\w+-]*[ \t]*\r?\n([\s\S]*?)\r?\n[ \t]*```/.exec(trimmed);
|
|
2881
|
+
if (embedded) return (embedded[1] ?? "").trim();
|
|
2882
|
+
return null;
|
|
2815
2883
|
}
|
|
2816
|
-
function
|
|
2817
|
-
let
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2884
|
+
function escapeControlCharsInStrings(s) {
|
|
2885
|
+
let inString = false;
|
|
2886
|
+
let out = "";
|
|
2887
|
+
for (let i = 0; i < s.length; i++) {
|
|
2888
|
+
const c = s.charAt(i);
|
|
2889
|
+
if (c === '"' && (i === 0 || s[i - 1] !== "\\")) {
|
|
2890
|
+
inString = !inString;
|
|
2891
|
+
out += c;
|
|
2821
2892
|
continue;
|
|
2822
2893
|
}
|
|
2823
|
-
|
|
2894
|
+
const code = c.charCodeAt(0);
|
|
2895
|
+
if (inString && code < 32) {
|
|
2896
|
+
switch (c) {
|
|
2897
|
+
case "\n":
|
|
2898
|
+
out += "\\n";
|
|
2899
|
+
break;
|
|
2900
|
+
case "\r":
|
|
2901
|
+
out += "\\r";
|
|
2902
|
+
break;
|
|
2903
|
+
case " ":
|
|
2904
|
+
out += "\\t";
|
|
2905
|
+
break;
|
|
2906
|
+
case "\b":
|
|
2907
|
+
out += "\\b";
|
|
2908
|
+
break;
|
|
2909
|
+
case "\f":
|
|
2910
|
+
out += "\\f";
|
|
2911
|
+
break;
|
|
2912
|
+
default:
|
|
2913
|
+
out += `\\u${code.toString(16).padStart(4, "0")}`;
|
|
2914
|
+
}
|
|
2915
|
+
continue;
|
|
2916
|
+
}
|
|
2917
|
+
out += c;
|
|
2824
2918
|
}
|
|
2825
|
-
return
|
|
2826
|
-
}
|
|
2827
|
-
function estimateToolDefTokens(tool) {
|
|
2828
|
-
const cached = tool._estDefTokens;
|
|
2829
|
-
if (typeof cached === "number" && cached > 0) return cached;
|
|
2830
|
-
const compact = compactToolDefinitionForWire(tool);
|
|
2831
|
-
return RoughTokenEstimate(tool.name) + RoughTokenEstimate(compact.description) + RoughTokenEstimate(JSON.stringify(compact.inputSchema));
|
|
2919
|
+
return out;
|
|
2832
2920
|
}
|
|
2833
|
-
function
|
|
2834
|
-
let
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
} else if (Array.isArray(content)) {
|
|
2849
|
-
for (const b of content) {
|
|
2850
|
-
if (typeof b === "object" && b !== null) {
|
|
2851
|
-
if (b.type === "text") {
|
|
2852
|
-
messagesTokens += RoughTokenEstimate(b.text);
|
|
2853
|
-
} else {
|
|
2854
|
-
messagesTokens += RoughTokenEstimate(JSON.stringify(b));
|
|
2855
|
-
}
|
|
2856
|
-
}
|
|
2857
|
-
}
|
|
2858
|
-
}
|
|
2921
|
+
function stripJsonComments(s) {
|
|
2922
|
+
let inString = false;
|
|
2923
|
+
let escaped = false;
|
|
2924
|
+
const chars = [];
|
|
2925
|
+
let i = 0;
|
|
2926
|
+
while (i < s.length) {
|
|
2927
|
+
const c = s.charAt(i);
|
|
2928
|
+
if (inString) {
|
|
2929
|
+
chars.push(c);
|
|
2930
|
+
if (escaped) {
|
|
2931
|
+
escaped = false;
|
|
2932
|
+
} else if (c === "\\") {
|
|
2933
|
+
escaped = true;
|
|
2934
|
+
} else if (c === '"') {
|
|
2935
|
+
inString = false;
|
|
2859
2936
|
}
|
|
2937
|
+
i++;
|
|
2938
|
+
continue;
|
|
2860
2939
|
}
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2940
|
+
if (c === '"') {
|
|
2941
|
+
inString = true;
|
|
2942
|
+
chars.push(c);
|
|
2943
|
+
i++;
|
|
2944
|
+
continue;
|
|
2945
|
+
}
|
|
2946
|
+
if (c === "/" && s.charAt(i + 1) === "/") {
|
|
2947
|
+
while (i < s.length && s.charAt(i) !== "\n") i++;
|
|
2948
|
+
continue;
|
|
2949
|
+
}
|
|
2950
|
+
if (c === "/" && s.charAt(i + 1) === "*") {
|
|
2951
|
+
const end = s.indexOf("*/", i + 2);
|
|
2952
|
+
if (end === -1) {
|
|
2953
|
+
chars.push(s.slice(i));
|
|
2954
|
+
break;
|
|
2869
2955
|
}
|
|
2956
|
+
i = end + 2;
|
|
2957
|
+
continue;
|
|
2870
2958
|
}
|
|
2959
|
+
chars.push(c);
|
|
2960
|
+
i++;
|
|
2871
2961
|
}
|
|
2872
|
-
|
|
2873
|
-
for (const t of tools) {
|
|
2874
|
-
toolsTokens += estimateToolDefTokens(t);
|
|
2875
|
-
}
|
|
2876
|
-
const total = messagesTokens + systemTokens + toolsTokens;
|
|
2877
|
-
calState(calibrationKey).prevEst = total;
|
|
2878
|
-
return {
|
|
2879
|
-
messages: messagesTokens,
|
|
2880
|
-
systemPrompt: systemTokens,
|
|
2881
|
-
tools: toolsTokens,
|
|
2882
|
-
total
|
|
2883
|
-
};
|
|
2962
|
+
return chars.join("");
|
|
2884
2963
|
}
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
if (
|
|
2890
|
-
|
|
2891
|
-
if (cal.count === 0) {
|
|
2892
|
-
cal.ratio = sampleRatio;
|
|
2893
|
-
} else {
|
|
2894
|
-
cal.ratio = CAL_ALPHA * sampleRatio + (1 - CAL_ALPHA) * cal.ratio;
|
|
2964
|
+
|
|
2965
|
+
// src/utils/session-scoped-path.ts
|
|
2966
|
+
import * as path4 from "node:path";
|
|
2967
|
+
function sessionScopedPath(dir, sessionId, suffix) {
|
|
2968
|
+
if (!sessionId || sessionId.includes("\\") || sessionId.includes("..")) {
|
|
2969
|
+
throw invalid(sessionId);
|
|
2895
2970
|
}
|
|
2896
|
-
|
|
2897
|
-
|
|
2971
|
+
const resolved = path4.resolve(dir, `${sessionId}${suffix}`);
|
|
2972
|
+
const rel = path4.relative(path4.resolve(dir), resolved);
|
|
2973
|
+
if (rel.startsWith("..") || path4.isAbsolute(rel)) {
|
|
2974
|
+
throw invalid(sessionId);
|
|
2975
|
+
}
|
|
2976
|
+
return resolved;
|
|
2898
2977
|
}
|
|
2899
|
-
function
|
|
2900
|
-
|
|
2978
|
+
function invalid(sessionId) {
|
|
2979
|
+
return new FsError({
|
|
2980
|
+
message: `Invalid sessionId: ${sessionId}`,
|
|
2981
|
+
code: ERROR_CODES.FS_DELETE_FAILED,
|
|
2982
|
+
path: sessionId,
|
|
2983
|
+
context: { reason: "path_traversal" }
|
|
2984
|
+
});
|
|
2985
|
+
}
|
|
2986
|
+
|
|
2987
|
+
// src/utils/sleep.ts
|
|
2988
|
+
function sleep(ms) {
|
|
2989
|
+
return new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
2990
|
+
}
|
|
2991
|
+
|
|
2992
|
+
// src/utils/slug.ts
|
|
2993
|
+
function slugify(name, fallback = "prompt", maxLen = 64) {
|
|
2994
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, maxLen).replace(/-+$/g, "") || fallback;
|
|
2995
|
+
}
|
|
2996
|
+
|
|
2997
|
+
// src/utils/string.ts
|
|
2998
|
+
function truncate(s, max) {
|
|
2999
|
+
return s.length <= max ? s : `${s.slice(0, max - 1)}\u2026`;
|
|
3000
|
+
}
|
|
3001
|
+
|
|
3002
|
+
// src/utils/task-format.ts
|
|
3003
|
+
function computeTaskItemProgress(tasks) {
|
|
3004
|
+
let completed = 0;
|
|
3005
|
+
let pending = 0;
|
|
3006
|
+
let inProgress = 0;
|
|
3007
|
+
let blocked = 0;
|
|
3008
|
+
let failed = 0;
|
|
3009
|
+
let review = 0;
|
|
3010
|
+
let estimatedHours = 0;
|
|
3011
|
+
const actualHours = 0;
|
|
3012
|
+
for (const t of tasks) {
|
|
3013
|
+
switch (t.status) {
|
|
3014
|
+
case "completed":
|
|
3015
|
+
completed++;
|
|
3016
|
+
break;
|
|
3017
|
+
case "pending":
|
|
3018
|
+
pending++;
|
|
3019
|
+
break;
|
|
3020
|
+
case "in_progress":
|
|
3021
|
+
inProgress++;
|
|
3022
|
+
break;
|
|
3023
|
+
case "blocked":
|
|
3024
|
+
blocked++;
|
|
3025
|
+
break;
|
|
3026
|
+
case "failed":
|
|
3027
|
+
failed++;
|
|
3028
|
+
break;
|
|
3029
|
+
case "review":
|
|
3030
|
+
review++;
|
|
3031
|
+
break;
|
|
3032
|
+
}
|
|
3033
|
+
estimatedHours += t.estimateHours ?? 0;
|
|
3034
|
+
}
|
|
2901
3035
|
return {
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
3036
|
+
total: tasks.length,
|
|
3037
|
+
pending,
|
|
3038
|
+
inProgress,
|
|
3039
|
+
blocked,
|
|
3040
|
+
failed,
|
|
3041
|
+
review,
|
|
3042
|
+
completed,
|
|
3043
|
+
percentComplete: tasks.length > 0 ? Math.round(completed / tasks.length * 100) : 0,
|
|
3044
|
+
estimatedHours,
|
|
3045
|
+
actualHours
|
|
2905
3046
|
};
|
|
2906
3047
|
}
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
3048
|
+
var STATUS_ICON = {
|
|
3049
|
+
pending: "\u25CB",
|
|
3050
|
+
in_progress: "\u25D0",
|
|
3051
|
+
blocked: "\u2298",
|
|
3052
|
+
failed: "\u2717",
|
|
3053
|
+
review: "\u25D1",
|
|
3054
|
+
completed: "\u25CF"
|
|
3055
|
+
};
|
|
3056
|
+
var PRIORITY_ICON = {
|
|
3057
|
+
critical: "\u{1F534}",
|
|
3058
|
+
high: "\u{1F7E0}",
|
|
3059
|
+
medium: "\u{1F7E1}",
|
|
3060
|
+
low: "\u{1F7E2}"
|
|
3061
|
+
};
|
|
3062
|
+
var TYPE_ICON = {
|
|
3063
|
+
feature: "\u26A1",
|
|
3064
|
+
bugfix: "\u{1F41B}",
|
|
3065
|
+
refactor: "\u267B\uFE0F",
|
|
3066
|
+
docs: "\u{1F4DD}",
|
|
3067
|
+
test: "\u{1F9EA}",
|
|
3068
|
+
chore: "\u{1F527}"
|
|
3069
|
+
};
|
|
3070
|
+
function formatTaskProgress(tasks) {
|
|
3071
|
+
const p = computeTaskItemProgress(tasks);
|
|
3072
|
+
if (p.total === 0) return "No tasks.";
|
|
3073
|
+
const barWidth = 24;
|
|
3074
|
+
const filled = Math.round(p.percentComplete / 100 * barWidth);
|
|
3075
|
+
const empty = barWidth - filled;
|
|
3076
|
+
const bar = "\u2588".repeat(filled) + "\u2591".repeat(empty);
|
|
3077
|
+
return [
|
|
3078
|
+
`${color.bold("Tasks")} [${bar}] ${p.percentComplete}%`,
|
|
3079
|
+
` ${color.green("\u25CF")} ${p.completed} done \u2502 ${color.yellow("\u25D0")} ${p.inProgress} active \u2502 ${color.dim("\u25CB")} ${p.pending} pending \u2502 \u2298 ${p.blocked} blocked \u2502 \u2717 ${p.failed} failed`,
|
|
3080
|
+
p.estimatedHours > 0 ? ` ${color.dim(`est. ${p.estimatedHours}h`)}` : ""
|
|
3081
|
+
].filter(Boolean).join("\n");
|
|
3082
|
+
}
|
|
3083
|
+
function formatTaskList(tasks) {
|
|
3084
|
+
if (tasks.length === 0) return "No tasks.";
|
|
3085
|
+
const order = ["in_progress", "blocked", "review", "pending", "failed", "completed"];
|
|
3086
|
+
const groups = /* @__PURE__ */ new Map();
|
|
3087
|
+
for (const t of tasks) {
|
|
3088
|
+
const list = groups.get(t.status) ?? [];
|
|
3089
|
+
list.push(t);
|
|
3090
|
+
groups.set(t.status, list);
|
|
2918
3091
|
}
|
|
2919
|
-
const
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
3092
|
+
const lines = [];
|
|
3093
|
+
lines.push(color.dim(`Tasks (${tasks.length} total):`));
|
|
3094
|
+
for (const status of order) {
|
|
3095
|
+
const group = groups.get(status);
|
|
3096
|
+
if (!group || group.length === 0) continue;
|
|
3097
|
+
const icon = STATUS_ICON[status];
|
|
3098
|
+
lines.push(` ${icon} ${status.toUpperCase()} (${group.length})`);
|
|
3099
|
+
for (const t of group) {
|
|
3100
|
+
const prio = PRIORITY_ICON[t.priority];
|
|
3101
|
+
const type = TYPE_ICON[t.type];
|
|
3102
|
+
const deps = t.dependsOn && t.dependsOn.length > 0 ? ` ${color.dim("\u2190")} ${color.dim(t.dependsOn.map((d) => d.slice(0, 8)).join(", "))}` : "";
|
|
3103
|
+
const who = t.assignee ? ` ${color.dim(`@${t.assignee}`)}` : "";
|
|
3104
|
+
const hrs = t.estimateHours ? ` ${color.dim(`${t.estimateHours}h`)}` : "";
|
|
3105
|
+
lines.push(` ${type} ${prio} ${t.title}${deps}${who}${hrs}`);
|
|
3106
|
+
}
|
|
2927
3107
|
}
|
|
2928
|
-
return
|
|
3108
|
+
return lines.join("\n");
|
|
2929
3109
|
}
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
3110
|
+
|
|
3111
|
+
// src/utils/todos-format.ts
|
|
3112
|
+
function formatTodosList(todos) {
|
|
3113
|
+
if (todos.length === 0) return "No todos.";
|
|
3114
|
+
const lines = [];
|
|
3115
|
+
const done = todos.filter((t) => t.status === "completed").length;
|
|
3116
|
+
lines.push(color.dim(`Todos (${done}/${todos.length} done):`));
|
|
3117
|
+
todos.forEach((t, i) => {
|
|
3118
|
+
const mark = t.status === "completed" ? color.green("[x]") : t.status === "in_progress" ? color.yellow("[~]") : color.dim("[ ]");
|
|
3119
|
+
const text = t.status === "in_progress" && t.activeForm ? t.activeForm : t.content;
|
|
3120
|
+
const label = t.status === "completed" ? color.dim(text) : text;
|
|
3121
|
+
lines.push(` ${color.dim(String(i + 1).padStart(2))}. ${mark} ${label}`);
|
|
3122
|
+
});
|
|
3123
|
+
return lines.join("\n");
|
|
2936
3124
|
}
|
|
2937
|
-
function
|
|
2938
|
-
if (
|
|
2939
|
-
|
|
2940
|
-
return;
|
|
2941
|
-
}
|
|
2942
|
-
_cals.delete(calibrationKey);
|
|
3125
|
+
function hasOpenTodos(todos) {
|
|
3126
|
+
if (!Array.isArray(todos) || todos.length === 0) return false;
|
|
3127
|
+
return todos.some((t) => t.status === "pending" || t.status === "in_progress");
|
|
2943
3128
|
}
|
|
2944
3129
|
|
|
2945
3130
|
// src/utils/tool-description-mode.ts
|
|
@@ -3821,7 +4006,7 @@ function isUlid(value) {
|
|
|
3821
4006
|
}
|
|
3822
4007
|
|
|
3823
4008
|
// src/utils/wstack-paths.ts
|
|
3824
|
-
import { createHash } from "node:crypto";
|
|
4009
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
3825
4010
|
import * as fs4 from "node:fs";
|
|
3826
4011
|
import * as os from "node:os";
|
|
3827
4012
|
import * as path5 from "node:path";
|
|
@@ -3844,12 +4029,12 @@ function canonicalProjectRoot(absRoot) {
|
|
|
3844
4029
|
}
|
|
3845
4030
|
}
|
|
3846
4031
|
function projectHash(absRoot) {
|
|
3847
|
-
return
|
|
4032
|
+
return createHash2("sha256").update(canonicalProjectRoot(absRoot)).digest("hex").slice(0, 12);
|
|
3848
4033
|
}
|
|
3849
4034
|
function projectSlug(absRoot) {
|
|
3850
4035
|
const identityRoot = canonicalProjectRoot(absRoot);
|
|
3851
4036
|
const base = slugify2(path5.basename(identityRoot));
|
|
3852
|
-
const hash =
|
|
4037
|
+
const hash = createHash2("sha256").update(identityRoot).digest("hex").slice(0, 6);
|
|
3853
4038
|
return `${base}-${hash}`;
|
|
3854
4039
|
}
|
|
3855
4040
|
function slugify2(name) {
|
|
@@ -3974,6 +4159,7 @@ export {
|
|
|
3974
4159
|
createContextEvidenceState,
|
|
3975
4160
|
createToolOutputSerializer,
|
|
3976
4161
|
deepMerge,
|
|
4162
|
+
deriveCachePrefixKey,
|
|
3977
4163
|
detectNewlineStyle,
|
|
3978
4164
|
detectTerminal,
|
|
3979
4165
|
ensureDir,
|
|
@@ -3994,6 +4180,7 @@ export {
|
|
|
3994
4180
|
formatTodosList,
|
|
3995
4181
|
getCalibrationState,
|
|
3996
4182
|
getChildEnvGitIdentity,
|
|
4183
|
+
getContextBreakdown,
|
|
3997
4184
|
getJsonPath,
|
|
3998
4185
|
getToolDescriptionMode,
|
|
3999
4186
|
getToolResultRenderMode,
|