@ycodium-ai/agent-codex 0.2.2690 → 0.2.2696
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -0
- package/dist/index.js +1287 -250
- package/locales/en.json +12 -0
- package/locales/zh-CN.json +12 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -127,10 +127,11 @@ var CODEX_CARD = {
|
|
|
127
127
|
};
|
|
128
128
|
|
|
129
129
|
// src/codexAccount.ts
|
|
130
|
-
import * as
|
|
130
|
+
import * as NodeFS2 from "node:fs";
|
|
131
131
|
import * as NodePath3 from "node:path";
|
|
132
132
|
|
|
133
133
|
// src/homeLayout.ts
|
|
134
|
+
import * as NodeFS from "node:fs";
|
|
134
135
|
import * as NodeFSP from "node:fs/promises";
|
|
135
136
|
import * as NodeOS2 from "node:os";
|
|
136
137
|
import * as NodePath2 from "node:path";
|
|
@@ -314,6 +315,13 @@ async function materializeCodexShadowHome(layout, log) {
|
|
|
314
315
|
function accountHomeFromLayout(layout) {
|
|
315
316
|
return layout.effectiveHomePath ?? layout.sharedHomePath;
|
|
316
317
|
}
|
|
318
|
+
function homeExists(homePath) {
|
|
319
|
+
try {
|
|
320
|
+
return NodeFS.statSync(homePath).isDirectory();
|
|
321
|
+
} catch {
|
|
322
|
+
return false;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
317
325
|
|
|
318
326
|
// src/codexAccount.ts
|
|
319
327
|
var AUTH_FILE_NAME = "auth.json";
|
|
@@ -339,14 +347,14 @@ var parseJsonRecord = (raw) => {
|
|
|
339
347
|
};
|
|
340
348
|
var directoryExists = (homePath) => {
|
|
341
349
|
try {
|
|
342
|
-
return
|
|
350
|
+
return NodeFS2.statSync(homePath).isDirectory();
|
|
343
351
|
} catch {
|
|
344
352
|
return false;
|
|
345
353
|
}
|
|
346
354
|
};
|
|
347
355
|
var readCredentialFile = (path) => {
|
|
348
356
|
try {
|
|
349
|
-
return
|
|
357
|
+
return NodeFS2.readFileSync(path, "utf8");
|
|
350
358
|
} catch {
|
|
351
359
|
return void 0;
|
|
352
360
|
}
|
|
@@ -796,6 +804,11 @@ function isRecord3(value) {
|
|
|
796
804
|
function asString(value) {
|
|
797
805
|
return typeof value === "string" ? value : null;
|
|
798
806
|
}
|
|
807
|
+
function trimText(value) {
|
|
808
|
+
if (typeof value !== "string") return void 0;
|
|
809
|
+
const trimmed = value.trim();
|
|
810
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
811
|
+
}
|
|
799
812
|
function asNumber(value) {
|
|
800
813
|
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
801
814
|
}
|
|
@@ -1353,6 +1366,500 @@ function buildCodexDeveloperInstructions(interactionMode, runtime, browserToolsA
|
|
|
1353
1366
|
|
|
1354
1367
|
// src/events.ts
|
|
1355
1368
|
import * as NodeCrypto from "node:crypto";
|
|
1369
|
+
|
|
1370
|
+
// src/approvalMapping.ts
|
|
1371
|
+
function fileChangeEntries(params) {
|
|
1372
|
+
if (!isRecord3(params)) return [];
|
|
1373
|
+
const item = isRecord3(params["item"]) ? params["item"] : params;
|
|
1374
|
+
const changes = item["changes"] ?? item["fileChanges"] ?? params["fileChanges"];
|
|
1375
|
+
if (!isRecord3(changes) && !Array.isArray(changes)) return [];
|
|
1376
|
+
const entries = Array.isArray(changes) ? changes : Object.entries(changes).map(([path, change]) => ({
|
|
1377
|
+
path,
|
|
1378
|
+
...isRecord3(change) ? change : {}
|
|
1379
|
+
}));
|
|
1380
|
+
const out = [];
|
|
1381
|
+
for (const entry of entries) {
|
|
1382
|
+
if (!isRecord3(entry)) continue;
|
|
1383
|
+
const path = asString(entry["path"]) ?? asString(entry["file"]);
|
|
1384
|
+
if (!path) continue;
|
|
1385
|
+
out.push({
|
|
1386
|
+
path,
|
|
1387
|
+
kind: (asString(entry["type"]) ?? asString(entry["kind"]) ?? "update").toLowerCase(),
|
|
1388
|
+
movePath: asString(entry["move_path"]) ?? asString(entry["movePath"]),
|
|
1389
|
+
diff: asString(entry["unified_diff"]) ?? asString(entry["unifiedDiff"]),
|
|
1390
|
+
content: asString(entry["content"])
|
|
1391
|
+
});
|
|
1392
|
+
}
|
|
1393
|
+
return out;
|
|
1394
|
+
}
|
|
1395
|
+
var MAX_DESCRIBED_FILE_CHANGES = 20;
|
|
1396
|
+
function describeFileChanges(params) {
|
|
1397
|
+
const entries = fileChangeEntries(params).toSorted(
|
|
1398
|
+
(left, right) => left.path.localeCompare(right.path)
|
|
1399
|
+
);
|
|
1400
|
+
if (entries.length === 0) return void 0;
|
|
1401
|
+
const lines = entries.slice(0, MAX_DESCRIBED_FILE_CHANGES).map(
|
|
1402
|
+
(entry) => entry.movePath ? `${entry.kind} ${entry.path} -> ${entry.movePath}` : `${entry.kind} ${entry.path}`
|
|
1403
|
+
);
|
|
1404
|
+
const remaining = entries.length - lines.length;
|
|
1405
|
+
return remaining > 0 ? `${lines.join("\n")}
|
|
1406
|
+
+${remaining} more` : lines.join("\n");
|
|
1407
|
+
}
|
|
1408
|
+
function toApplyPatchFileChanges(params) {
|
|
1409
|
+
const out = [];
|
|
1410
|
+
for (const { path, kind, movePath, diff, content } of fileChangeEntries(params)) {
|
|
1411
|
+
if (kind === "add") {
|
|
1412
|
+
out.push({
|
|
1413
|
+
path,
|
|
1414
|
+
changeKind: "add",
|
|
1415
|
+
...content !== void 0 ? { content: { kind: "replace", newText: content } } : {}
|
|
1416
|
+
});
|
|
1417
|
+
} else if (kind === "delete") {
|
|
1418
|
+
out.push({
|
|
1419
|
+
path,
|
|
1420
|
+
changeKind: "delete",
|
|
1421
|
+
...content !== void 0 ? { content: { kind: "replace", oldText: content, newText: "" } } : {}
|
|
1422
|
+
});
|
|
1423
|
+
} else if (movePath) {
|
|
1424
|
+
out.push({
|
|
1425
|
+
path,
|
|
1426
|
+
changeKind: "rename",
|
|
1427
|
+
...diff ? { content: { kind: "unified", diff } } : {}
|
|
1428
|
+
});
|
|
1429
|
+
} else {
|
|
1430
|
+
out.push({
|
|
1431
|
+
path,
|
|
1432
|
+
changeKind: "modify",
|
|
1433
|
+
...diff ? { content: { kind: "unified", diff } } : {}
|
|
1434
|
+
});
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
return out;
|
|
1438
|
+
}
|
|
1439
|
+
function describeCommandApproval(params) {
|
|
1440
|
+
const record = isRecord3(params) ? params : {};
|
|
1441
|
+
const commandValue = record["command"];
|
|
1442
|
+
const command = Array.isArray(commandValue) ? commandValue.filter((entry) => typeof entry === "string").join(" ") : asString(commandValue) ?? "";
|
|
1443
|
+
const cwd = asString(record["cwd"]);
|
|
1444
|
+
const reason = trimText(record["reason"]);
|
|
1445
|
+
const callId = asString(record["callId"]);
|
|
1446
|
+
return {
|
|
1447
|
+
command,
|
|
1448
|
+
...cwd ? { cwd } : {},
|
|
1449
|
+
...reason ? { reason } : {},
|
|
1450
|
+
...callId ? { callId } : {}
|
|
1451
|
+
};
|
|
1452
|
+
}
|
|
1453
|
+
function describeFileChangeApproval(params) {
|
|
1454
|
+
const record = isRecord3(params) ? params : {};
|
|
1455
|
+
const reason = trimText(record["reason"]);
|
|
1456
|
+
const grantRoot = asString(record["grantRoot"]);
|
|
1457
|
+
const callId = asString(record["callId"]);
|
|
1458
|
+
const detail = reason ?? describeFileChanges(record) ?? trimText(grantRoot);
|
|
1459
|
+
return {
|
|
1460
|
+
...reason ? { reason } : {},
|
|
1461
|
+
...detail ? { detail } : {},
|
|
1462
|
+
...grantRoot ? { grantRoot } : {},
|
|
1463
|
+
...callId ? { callId } : {},
|
|
1464
|
+
fileChanges: toApplyPatchFileChanges(record)
|
|
1465
|
+
};
|
|
1466
|
+
}
|
|
1467
|
+
function describePermissionsApproval(params) {
|
|
1468
|
+
const record = isRecord3(params) ? params : {};
|
|
1469
|
+
const permissions = isRecord3(record["permissions"]) ? record["permissions"] : {};
|
|
1470
|
+
const fileSystem = isRecord3(permissions["fileSystem"]) ? permissions["fileSystem"] : {};
|
|
1471
|
+
const network = isRecord3(permissions["network"]) ? permissions["network"] : {};
|
|
1472
|
+
const pathsOf = (value) => Array.isArray(value) ? value.flatMap((entry) => typeof entry === "string" && entry.length > 0 ? [entry] : []) : [];
|
|
1473
|
+
const read = pathsOf(fileSystem["read"]);
|
|
1474
|
+
const write = pathsOf(fileSystem["write"]);
|
|
1475
|
+
const entries = Array.isArray(fileSystem["entries"]) ? fileSystem["entries"] : [];
|
|
1476
|
+
for (const entry of entries) {
|
|
1477
|
+
if (!isRecord3(entry) || !isRecord3(entry["path"])) continue;
|
|
1478
|
+
const target = entry["path"];
|
|
1479
|
+
const path = target["type"] === "path" ? asString(target["path"]) : target["type"] === "glob_pattern" ? asString(target["pattern"]) : void 0;
|
|
1480
|
+
if (!path) continue;
|
|
1481
|
+
if (entry["access"] === "read") read.push(path);
|
|
1482
|
+
if (entry["access"] === "write") write.push(path);
|
|
1483
|
+
}
|
|
1484
|
+
const reason = trimText(record["reason"]);
|
|
1485
|
+
return {
|
|
1486
|
+
...reason ? { reason } : {},
|
|
1487
|
+
read: [...new Set(read)],
|
|
1488
|
+
write: [...new Set(write)],
|
|
1489
|
+
network: network["enabled"] === true
|
|
1490
|
+
};
|
|
1491
|
+
}
|
|
1492
|
+
function toPermissionsResponse(decision, params, persistent) {
|
|
1493
|
+
if (decision !== "allow") return { permissions: {} };
|
|
1494
|
+
const requested = isRecord3(params) && isRecord3(params["permissions"]) ? params["permissions"] : {};
|
|
1495
|
+
return {
|
|
1496
|
+
permissions: {
|
|
1497
|
+
...requested["fileSystem"] !== void 0 ? { fileSystem: requested["fileSystem"] } : {},
|
|
1498
|
+
...requested["network"] !== void 0 ? { network: requested["network"] } : {}
|
|
1499
|
+
},
|
|
1500
|
+
scope: persistent ? "session" : "turn"
|
|
1501
|
+
};
|
|
1502
|
+
}
|
|
1503
|
+
function describeMcpElicitation(params) {
|
|
1504
|
+
const record = isRecord3(params) ? params : {};
|
|
1505
|
+
const appName = asString(record["appName"]) ?? asString(record["serverName"]) ?? void 0;
|
|
1506
|
+
const serverName = asString(record["serverName"]);
|
|
1507
|
+
const message = trimText(record["message"]);
|
|
1508
|
+
const url = asString(record["url"]) ?? asString(record["authorizationUrl"]);
|
|
1509
|
+
const identity = appName ?? url ?? "unknown";
|
|
1510
|
+
return {
|
|
1511
|
+
...appName ? { appName } : {},
|
|
1512
|
+
...serverName ? { serverName } : {},
|
|
1513
|
+
...message ? { message } : {},
|
|
1514
|
+
...url ? { url } : {},
|
|
1515
|
+
identity,
|
|
1516
|
+
mode: url ? "url" : "form",
|
|
1517
|
+
...record["requestedSchema"] !== void 0 ? { schema: record["requestedSchema"] } : {}
|
|
1518
|
+
};
|
|
1519
|
+
}
|
|
1520
|
+
function persistFromMeta(meta) {
|
|
1521
|
+
if (!isRecord3(meta)) return void 0;
|
|
1522
|
+
const persist = asString(meta["persist"]) ?? asString(meta["persistence"]);
|
|
1523
|
+
if (persist === "session" || persist === "acceptForSession") return "session";
|
|
1524
|
+
if (persist === "always" || persist === "permanent" || persist === "forever" || persist === "persistent" || persist === "acceptAlways") {
|
|
1525
|
+
return "always";
|
|
1526
|
+
}
|
|
1527
|
+
return void 0;
|
|
1528
|
+
}
|
|
1529
|
+
function toMcpElicitationResponse(decision, params) {
|
|
1530
|
+
const described = describeMcpElicitation(params);
|
|
1531
|
+
if (decision !== "allow" || described.mode === "url") {
|
|
1532
|
+
return {
|
|
1533
|
+
action: decision === "allow" ? "decline" : decision === "cancel" ? "cancel" : "decline"
|
|
1534
|
+
};
|
|
1535
|
+
}
|
|
1536
|
+
const schema = isRecord3(described.schema) ? described.schema : void 0;
|
|
1537
|
+
const properties = schema && isRecord3(schema["properties"]) ? schema["properties"] : {};
|
|
1538
|
+
const required = schema && Array.isArray(schema["required"]) ? schema["required"] : [];
|
|
1539
|
+
const content = {};
|
|
1540
|
+
for (const [key, spec] of Object.entries(properties)) {
|
|
1541
|
+
if (!isRecord3(spec)) continue;
|
|
1542
|
+
if (Array.isArray(spec["oneOf"]) || Array.isArray(spec["enum"])) {
|
|
1543
|
+
const options = Array.isArray(spec["oneOf"]) ? spec["oneOf"] : Array.isArray(spec["enum"]) ? spec["enum"] : [];
|
|
1544
|
+
const first = options[0];
|
|
1545
|
+
content[key] = isRecord3(first) ? first["const"] ?? first["value"] ?? first : first;
|
|
1546
|
+
} else if (spec["type"] === "boolean") {
|
|
1547
|
+
content[key] = true;
|
|
1548
|
+
} else if (typeof spec["default"] !== "undefined") {
|
|
1549
|
+
content[key] = spec["default"];
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
for (const key of required) {
|
|
1553
|
+
if (typeof key === "string" && !(key in content)) {
|
|
1554
|
+
return { action: "decline" };
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
const persist = persistFromMeta(isRecord3(params) ? params["_meta"] : void 0);
|
|
1558
|
+
return {
|
|
1559
|
+
action: "accept",
|
|
1560
|
+
content,
|
|
1561
|
+
...persist ? { _meta: { persist } } : {}
|
|
1562
|
+
};
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
// src/usage/codexQuota.ts
|
|
1566
|
+
var asRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
1567
|
+
var asFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
1568
|
+
var asNonEmptyString = (value) => typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
1569
|
+
var clampPercent = (value) => Math.min(100, Math.max(0, value));
|
|
1570
|
+
var epochSecondsToIso = (value) => {
|
|
1571
|
+
const seconds = asFiniteNumber(value);
|
|
1572
|
+
if (seconds === null) return null;
|
|
1573
|
+
const date = new Date(seconds * 1e3);
|
|
1574
|
+
return Number.isNaN(date.getTime()) ? null : date.toISOString();
|
|
1575
|
+
};
|
|
1576
|
+
var SESSION_WINDOW_MINUTES = 5 * 60;
|
|
1577
|
+
var WEEKLY_WINDOW_MINUTES = 7 * 24 * 60;
|
|
1578
|
+
var formatWindowDuration = (minutes) => {
|
|
1579
|
+
if (minutes % (24 * 60) === 0) return `${String(minutes / (24 * 60))}d`;
|
|
1580
|
+
if (minutes % 60 === 0) return `${String(minutes / 60)}h`;
|
|
1581
|
+
return `${String(minutes)}m`;
|
|
1582
|
+
};
|
|
1583
|
+
var classifyWindow = (minutes, slot) => {
|
|
1584
|
+
if (minutes === SESSION_WINDOW_MINUTES) {
|
|
1585
|
+
return { id: "session", label: { key: "usage.window.session" } };
|
|
1586
|
+
}
|
|
1587
|
+
if (minutes === WEEKLY_WINDOW_MINUTES) {
|
|
1588
|
+
return { id: "weekly", label: { key: "usage.window.weekly" } };
|
|
1589
|
+
}
|
|
1590
|
+
if (minutes === null) return { id: slot, label: { key: "usage.window.limit" } };
|
|
1591
|
+
return {
|
|
1592
|
+
id: `window-${String(minutes)}m`,
|
|
1593
|
+
label: { key: "usage.window.duration", values: { duration: formatWindowDuration(minutes) } }
|
|
1594
|
+
};
|
|
1595
|
+
};
|
|
1596
|
+
var mapWindow = (raw, slot, describe) => {
|
|
1597
|
+
const window = asRecord(raw);
|
|
1598
|
+
const usedPercent = asFiniteNumber(window?.["usedPercent"]);
|
|
1599
|
+
if (window === null || usedPercent === null) return null;
|
|
1600
|
+
const rawMinutes = asFiniteNumber(window["windowDurationMins"]);
|
|
1601
|
+
const minutes = rawMinutes !== null && rawMinutes > 0 ? Math.trunc(rawMinutes) : null;
|
|
1602
|
+
const { id, label } = describe(minutes, slot);
|
|
1603
|
+
return {
|
|
1604
|
+
id,
|
|
1605
|
+
label,
|
|
1606
|
+
usedPercent: clampPercent(usedPercent),
|
|
1607
|
+
resetsAt: epochSecondsToIso(window["resetsAt"]),
|
|
1608
|
+
...minutes === null ? {} : { windowMs: minutes * 6e4 }
|
|
1609
|
+
};
|
|
1610
|
+
};
|
|
1611
|
+
var PLAN_LABELS = {
|
|
1612
|
+
free: "Free",
|
|
1613
|
+
go: "Go",
|
|
1614
|
+
plus: "Plus",
|
|
1615
|
+
pro: "Pro",
|
|
1616
|
+
prolite: "Pro Lite",
|
|
1617
|
+
team: "Team",
|
|
1618
|
+
business: "Business",
|
|
1619
|
+
enterprise: "Enterprise",
|
|
1620
|
+
edu: "Edu"
|
|
1621
|
+
};
|
|
1622
|
+
var planLabel = (planType) => {
|
|
1623
|
+
const id = asNonEmptyString(planType);
|
|
1624
|
+
if (id === null || id === "unknown") return void 0;
|
|
1625
|
+
return PLAN_LABELS[id] ?? id.split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
1626
|
+
};
|
|
1627
|
+
var creditBalance = (value) => {
|
|
1628
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : null;
|
|
1629
|
+
if (typeof value !== "string" || value.trim().length === 0) return null;
|
|
1630
|
+
const numeric = Number(value.trim());
|
|
1631
|
+
return Number.isFinite(numeric) ? numeric : null;
|
|
1632
|
+
};
|
|
1633
|
+
var MAIN_LIMIT_ID = "codex";
|
|
1634
|
+
var isMainCodexLimit = (snapshot) => {
|
|
1635
|
+
const limitId = asNonEmptyString(asRecord(snapshot)?.["limitId"]);
|
|
1636
|
+
return limitId === null || limitId === MAIN_LIMIT_ID;
|
|
1637
|
+
};
|
|
1638
|
+
var mapCodexSnapshotWindows = (snapshot) => {
|
|
1639
|
+
const bucket = asRecord(snapshot);
|
|
1640
|
+
if (bucket === null || !isMainCodexLimit(bucket)) return [];
|
|
1641
|
+
const windows = [];
|
|
1642
|
+
const takenIds = /* @__PURE__ */ new Set();
|
|
1643
|
+
for (const slot of ["primary", "secondary"]) {
|
|
1644
|
+
const window = mapWindow(bucket[slot], slot, classifyWindow);
|
|
1645
|
+
if (window === null) continue;
|
|
1646
|
+
const id = takenIds.has(window.id) ? `${window.id}:${slot}` : window.id;
|
|
1647
|
+
takenIds.add(id);
|
|
1648
|
+
windows.push({ ...window, id });
|
|
1649
|
+
}
|
|
1650
|
+
return windows;
|
|
1651
|
+
};
|
|
1652
|
+
var mapCodexRateLimits = (response, accountKey) => {
|
|
1653
|
+
const root = asRecord(response);
|
|
1654
|
+
const legacy = asRecord(root?.["rateLimits"]);
|
|
1655
|
+
const byLimitId = asRecord(root?.["rateLimitsByLimitId"]);
|
|
1656
|
+
const codexBucket = asRecord(byLimitId?.[MAIN_LIMIT_ID]);
|
|
1657
|
+
const main = codexBucket ?? (legacy !== null && isMainCodexLimit(legacy) ? legacy : null);
|
|
1658
|
+
if (root === null || legacy === null && codexBucket === null) return null;
|
|
1659
|
+
const windows = mapCodexSnapshotWindows(main);
|
|
1660
|
+
const takenIds = new Set(windows.map((window) => window.id));
|
|
1661
|
+
const push = (window, slot) => {
|
|
1662
|
+
if (window === null) return;
|
|
1663
|
+
const id = takenIds.has(window.id) ? `${window.id}:${slot}` : window.id;
|
|
1664
|
+
takenIds.add(id);
|
|
1665
|
+
windows.push({ ...window, id });
|
|
1666
|
+
};
|
|
1667
|
+
const others = /* @__PURE__ */ new Map();
|
|
1668
|
+
for (const [limitId, rawBucket] of Object.entries(byLimitId ?? {})) {
|
|
1669
|
+
const bucket = asRecord(rawBucket);
|
|
1670
|
+
if (limitId !== MAIN_LIMIT_ID && bucket !== null) others.set(limitId, bucket);
|
|
1671
|
+
}
|
|
1672
|
+
const legacyLimitId = asNonEmptyString(legacy?.["limitId"]);
|
|
1673
|
+
if (legacy !== null && legacyLimitId !== null && !isMainCodexLimit(legacy)) {
|
|
1674
|
+
if (!others.has(legacyLimitId)) others.set(legacyLimitId, legacy);
|
|
1675
|
+
}
|
|
1676
|
+
for (const [limitId, bucket] of others) {
|
|
1677
|
+
const name = asNonEmptyString(bucket["limitName"]) ?? limitId;
|
|
1678
|
+
for (const slot of ["primary", "secondary"]) {
|
|
1679
|
+
push(
|
|
1680
|
+
mapWindow(bucket[slot], slot, (minutes) => ({
|
|
1681
|
+
id: `${limitId}:${slot}`,
|
|
1682
|
+
label: minutes === null ? { text: name } : {
|
|
1683
|
+
key: "usage.window.named",
|
|
1684
|
+
values: { name, duration: formatWindowDuration(minutes) }
|
|
1685
|
+
}
|
|
1686
|
+
})),
|
|
1687
|
+
slot
|
|
1688
|
+
);
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
const balances = [];
|
|
1692
|
+
const credits = asRecord(main?.["credits"]);
|
|
1693
|
+
const balance = creditBalance(credits?.["balance"]);
|
|
1694
|
+
if (credits !== null && credits["unlimited"] !== true && balance !== null) {
|
|
1695
|
+
if (credits["hasCredits"] === true || balance > 0) {
|
|
1696
|
+
balances.push({
|
|
1697
|
+
id: "credits",
|
|
1698
|
+
label: { key: "usage.balance.credits" },
|
|
1699
|
+
remaining: balance,
|
|
1700
|
+
used: null,
|
|
1701
|
+
limit: null,
|
|
1702
|
+
unit: { kind: "credits" },
|
|
1703
|
+
resetsAt: null
|
|
1704
|
+
});
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
const plan = planLabel((main ?? legacy)?.["planType"]);
|
|
1708
|
+
const resetSummary = asRecord(root["rateLimitResetCredits"]);
|
|
1709
|
+
const availableCount = asFiniteNumber(resetSummary?.["availableCount"]);
|
|
1710
|
+
return {
|
|
1711
|
+
status: "available",
|
|
1712
|
+
...plan === void 0 ? {} : { plan },
|
|
1713
|
+
windows,
|
|
1714
|
+
balances,
|
|
1715
|
+
accountKey,
|
|
1716
|
+
// 回包没带摘要(老版本 CLI)就是不知道,不填;填 0 会让界面说「没有可用的重置额度」。
|
|
1717
|
+
...availableCount === null || availableCount < 0 ? {} : { resetCredits: { availableCount: Math.trunc(availableCount) } }
|
|
1718
|
+
};
|
|
1719
|
+
};
|
|
1720
|
+
var classifyRateLimitsError = (message) => {
|
|
1721
|
+
const text = message.toLowerCase();
|
|
1722
|
+
if (text.includes("chatgpt authentication required")) return "unsupportedLogin";
|
|
1723
|
+
if (text.includes("authentication required")) return "missingCredentials";
|
|
1724
|
+
if (/\b(401|403)\b|unauthorized|forbidden/.test(text)) return "authRejected";
|
|
1725
|
+
if (/\b429\b|too many requests/.test(text)) return "rateLimited";
|
|
1726
|
+
return "unreachable";
|
|
1727
|
+
};
|
|
1728
|
+
var CODEX_OFFICIAL_HOSTS = /* @__PURE__ */ new Set(["chatgpt.com"]);
|
|
1729
|
+
var CODEX_DEFAULT_MODEL_PROVIDER = "openai";
|
|
1730
|
+
var isOfficialUrl = (value) => {
|
|
1731
|
+
const parsed = URL.parse(value.trim());
|
|
1732
|
+
return parsed === null ? false : CODEX_OFFICIAL_HOSTS.has(parsed.hostname.toLowerCase());
|
|
1733
|
+
};
|
|
1734
|
+
var readTomlScalar = (raw) => {
|
|
1735
|
+
const quoted = /^(["'])(.*?)\1/.exec(raw.trim());
|
|
1736
|
+
if (quoted?.[2] !== void 0) return quoted[2];
|
|
1737
|
+
const [beforeComment = ""] = raw.split("#");
|
|
1738
|
+
return beforeComment.trim();
|
|
1739
|
+
};
|
|
1740
|
+
var TABLE_HEADER = /^\[\s*([^\][]+?)\s*\]\s*(?:#.*)?$/;
|
|
1741
|
+
var KEY_VALUE = /^([A-Za-z0-9_.\-"']+)\s*=\s*(.*)$/;
|
|
1742
|
+
var BASE_URL_KEYS = /* @__PURE__ */ new Set([
|
|
1743
|
+
"chatgpt_base_url",
|
|
1744
|
+
"openai_base_url",
|
|
1745
|
+
"base_url"
|
|
1746
|
+
]);
|
|
1747
|
+
var codexConfigSignal = (contents, options) => {
|
|
1748
|
+
let table = null;
|
|
1749
|
+
let selectedProvider = CODEX_DEFAULT_MODEL_PROVIDER;
|
|
1750
|
+
const providerBaseUrls = /* @__PURE__ */ new Map();
|
|
1751
|
+
for (const rawLine of contents.split("\n")) {
|
|
1752
|
+
const line = rawLine.trim();
|
|
1753
|
+
if (line.length === 0 || line.startsWith("#")) continue;
|
|
1754
|
+
const header = TABLE_HEADER.exec(line);
|
|
1755
|
+
if (header !== null) {
|
|
1756
|
+
table = header[1] ?? "";
|
|
1757
|
+
if (table === "model_providers") return "config.toml:model_providers";
|
|
1758
|
+
continue;
|
|
1759
|
+
}
|
|
1760
|
+
const assignment = KEY_VALUE.exec(line);
|
|
1761
|
+
if (assignment === null) continue;
|
|
1762
|
+
const key = assignment[1] ?? "";
|
|
1763
|
+
const value = readTomlScalar(assignment[2] ?? "");
|
|
1764
|
+
if (table === null) {
|
|
1765
|
+
if (key === "model_provider") {
|
|
1766
|
+
selectedProvider = value;
|
|
1767
|
+
} else if (key === "profile" || key.startsWith("model_providers")) {
|
|
1768
|
+
return `config.toml:${key.split(".")[0] ?? key}`;
|
|
1769
|
+
} else if (BASE_URL_KEYS.has(key) && !isOfficialUrl(value)) {
|
|
1770
|
+
return `config.toml:${key}`;
|
|
1771
|
+
}
|
|
1772
|
+
continue;
|
|
1773
|
+
}
|
|
1774
|
+
if (table.startsWith("model_providers.") && key === "base_url") {
|
|
1775
|
+
providerBaseUrls.set(
|
|
1776
|
+
table.slice("model_providers.".length).replace(/^["']|["']$/g, ""),
|
|
1777
|
+
value
|
|
1778
|
+
);
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
const selected = options.providerOverridden ? CODEX_DEFAULT_MODEL_PROVIDER : selectedProvider;
|
|
1782
|
+
if (selected === CODEX_DEFAULT_MODEL_PROVIDER) return null;
|
|
1783
|
+
const baseUrl = providerBaseUrls.get(selected);
|
|
1784
|
+
if (baseUrl === void 0 || !isOfficialUrl(baseUrl)) return "config.toml:model_provider";
|
|
1785
|
+
return null;
|
|
1786
|
+
};
|
|
1787
|
+
var LAUNCH_ARG_MARKERS = [
|
|
1788
|
+
"model_provider",
|
|
1789
|
+
"chatgpt_base_url",
|
|
1790
|
+
"openai_base_url",
|
|
1791
|
+
"base_url",
|
|
1792
|
+
"--profile"
|
|
1793
|
+
];
|
|
1794
|
+
var codexLaunchArgsSignal = (launchArgs) => {
|
|
1795
|
+
for (const marker of LAUNCH_ARG_MARKERS) {
|
|
1796
|
+
if (launchArgs.includes(marker)) return `launchArgs:${marker}`;
|
|
1797
|
+
}
|
|
1798
|
+
return null;
|
|
1799
|
+
};
|
|
1800
|
+
|
|
1801
|
+
// src/usageLimit.ts
|
|
1802
|
+
var asRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
1803
|
+
var mergeCodexRateLimits = (previous, update) => {
|
|
1804
|
+
const next = asRecord2(update);
|
|
1805
|
+
if (next === void 0 || !isMainCodexLimit(next)) return previous;
|
|
1806
|
+
if (previous === void 0) return next;
|
|
1807
|
+
const merged = { ...previous };
|
|
1808
|
+
for (const key of ["limitId", "planType", "rateLimitReachedType", "primary", "secondary"]) {
|
|
1809
|
+
if (next[key] !== void 0) merged[key] = next[key];
|
|
1810
|
+
}
|
|
1811
|
+
return merged;
|
|
1812
|
+
};
|
|
1813
|
+
var formatUsageLimitWait = (waitMs) => {
|
|
1814
|
+
const totalMinutes = Math.max(1, Math.ceil(waitMs / 6e4));
|
|
1815
|
+
const days = Math.floor(totalMinutes / (24 * 60));
|
|
1816
|
+
const hours = Math.floor(totalMinutes % (24 * 60) / 60);
|
|
1817
|
+
const minutes = totalMinutes % 60;
|
|
1818
|
+
if (days > 0) return hours === 0 ? `${String(days)}d` : `${String(days)}d ${String(hours)}h`;
|
|
1819
|
+
if (hours === 0) return `${String(totalMinutes)}m`;
|
|
1820
|
+
return minutes === 0 ? `${String(hours)}h` : `${String(hours)}h ${String(minutes)}m`;
|
|
1821
|
+
};
|
|
1822
|
+
var windowName = (window) => {
|
|
1823
|
+
if (window.id === "session") return "5-hour";
|
|
1824
|
+
if (window.id === "weekly") return "weekly";
|
|
1825
|
+
return window.windowMs === void 0 ? "usage" : formatWindowDuration(window.windowMs / 6e4);
|
|
1826
|
+
};
|
|
1827
|
+
var nextStep = (rateLimitReachedType) => {
|
|
1828
|
+
switch (rateLimitReachedType) {
|
|
1829
|
+
case "workspace_owner_credits_depleted":
|
|
1830
|
+
case "workspace_member_credits_depleted":
|
|
1831
|
+
return " The workspace has no credits left to continue sooner: ask the workspace owner to add credits, or send the message again once the limit resets.";
|
|
1832
|
+
case "workspace_owner_usage_limit_reached":
|
|
1833
|
+
case "workspace_member_usage_limit_reached":
|
|
1834
|
+
return " The workspace spend limit is reached: ask the workspace owner to raise it, or send the message again once the limit resets.";
|
|
1835
|
+
default:
|
|
1836
|
+
return void 0;
|
|
1837
|
+
}
|
|
1838
|
+
};
|
|
1839
|
+
var codexUsageLimitMessage = (snapshot, atIso, providerMessage) => {
|
|
1840
|
+
const atMs = Date.parse(atIso);
|
|
1841
|
+
let name;
|
|
1842
|
+
let waitMs = 0;
|
|
1843
|
+
let latestResetMs = Number.NEGATIVE_INFINITY;
|
|
1844
|
+
if (snapshot !== void 0 && Number.isFinite(atMs)) {
|
|
1845
|
+
for (const window of mapCodexSnapshotWindows(snapshot)) {
|
|
1846
|
+
if (window.usedPercent < 100 || window.resetsAt === null) continue;
|
|
1847
|
+
const resetMs = Date.parse(window.resetsAt);
|
|
1848
|
+
if (!Number.isFinite(resetMs) || resetMs <= atMs || resetMs <= latestResetMs) continue;
|
|
1849
|
+
latestResetMs = resetMs;
|
|
1850
|
+
name = windowName(window);
|
|
1851
|
+
waitMs = resetMs - atMs;
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
const step = nextStep(snapshot?.["rateLimitReachedType"]);
|
|
1855
|
+
if (name === void 0) {
|
|
1856
|
+
return `${providerMessage ?? "Codex usage limit reached."}${step ?? ""}`;
|
|
1857
|
+
}
|
|
1858
|
+
return `Codex usage limit reached. The ${name} limit resets in ${formatUsageLimitWait(waitMs)}.${step ?? " Send the message again once the limit resets."}`;
|
|
1859
|
+
};
|
|
1860
|
+
var isUsageLimitExceeded = (error) => asRecord2(error)?.["codexErrorInfo"] === "usageLimitExceeded";
|
|
1861
|
+
|
|
1862
|
+
// src/events.ts
|
|
1356
1863
|
var CODEX_PROVIDER = "codex";
|
|
1357
1864
|
var EVENT_ID_PREFIX = `codex-evt-${NodeCrypto.randomUUID()}`;
|
|
1358
1865
|
var eventSeq = 0;
|
|
@@ -1360,11 +1867,6 @@ function nextEventId() {
|
|
|
1360
1867
|
eventSeq += 1;
|
|
1361
1868
|
return `${EVENT_ID_PREFIX}-${eventSeq}`;
|
|
1362
1869
|
}
|
|
1363
|
-
function trimText(value) {
|
|
1364
|
-
if (typeof value !== "string") return void 0;
|
|
1365
|
-
const trimmed = value.trim();
|
|
1366
|
-
return trimmed.length > 0 ? trimmed : void 0;
|
|
1367
|
-
}
|
|
1368
1870
|
function nowIso2() {
|
|
1369
1871
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
1370
1872
|
}
|
|
@@ -1489,74 +1991,6 @@ function turnCompletedControlFlow(params) {
|
|
|
1489
1991
|
const error = isRecord3(turn["error"]) ? trimText(turn["error"]["message"]) : void 0;
|
|
1490
1992
|
return error ? { state, errorMessage: error } : { state };
|
|
1491
1993
|
}
|
|
1492
|
-
function fileChangeEntries(params) {
|
|
1493
|
-
if (!isRecord3(params)) return [];
|
|
1494
|
-
const item = isRecord3(params["item"]) ? params["item"] : params;
|
|
1495
|
-
const changes = item["changes"] ?? item["fileChanges"] ?? params["fileChanges"];
|
|
1496
|
-
if (!isRecord3(changes) && !Array.isArray(changes)) return [];
|
|
1497
|
-
const entries = Array.isArray(changes) ? changes : Object.entries(changes).map(([path, change]) => ({
|
|
1498
|
-
path,
|
|
1499
|
-
...isRecord3(change) ? change : {}
|
|
1500
|
-
}));
|
|
1501
|
-
const out = [];
|
|
1502
|
-
for (const entry of entries) {
|
|
1503
|
-
if (!isRecord3(entry)) continue;
|
|
1504
|
-
const path = asString(entry["path"]) ?? asString(entry["file"]);
|
|
1505
|
-
if (!path) continue;
|
|
1506
|
-
out.push({
|
|
1507
|
-
path,
|
|
1508
|
-
kind: (asString(entry["type"]) ?? asString(entry["kind"]) ?? "update").toLowerCase(),
|
|
1509
|
-
movePath: asString(entry["move_path"]) ?? asString(entry["movePath"]),
|
|
1510
|
-
diff: asString(entry["unified_diff"]) ?? asString(entry["unifiedDiff"]),
|
|
1511
|
-
content: asString(entry["content"])
|
|
1512
|
-
});
|
|
1513
|
-
}
|
|
1514
|
-
return out;
|
|
1515
|
-
}
|
|
1516
|
-
var MAX_DESCRIBED_FILE_CHANGES = 20;
|
|
1517
|
-
function describeFileChanges(params) {
|
|
1518
|
-
const entries = fileChangeEntries(params).toSorted(
|
|
1519
|
-
(left, right) => left.path.localeCompare(right.path)
|
|
1520
|
-
);
|
|
1521
|
-
if (entries.length === 0) return void 0;
|
|
1522
|
-
const lines = entries.slice(0, MAX_DESCRIBED_FILE_CHANGES).map(
|
|
1523
|
-
(entry) => entry.movePath ? `${entry.kind} ${entry.path} -> ${entry.movePath}` : `${entry.kind} ${entry.path}`
|
|
1524
|
-
);
|
|
1525
|
-
const remaining = entries.length - lines.length;
|
|
1526
|
-
return remaining > 0 ? `${lines.join("\n")}
|
|
1527
|
-
+${remaining} more` : lines.join("\n");
|
|
1528
|
-
}
|
|
1529
|
-
function toApplyPatchFileChanges(params) {
|
|
1530
|
-
const out = [];
|
|
1531
|
-
for (const { path, kind, movePath, diff, content } of fileChangeEntries(params)) {
|
|
1532
|
-
if (kind === "add") {
|
|
1533
|
-
out.push({
|
|
1534
|
-
path,
|
|
1535
|
-
changeKind: "add",
|
|
1536
|
-
...content !== void 0 ? { content: { kind: "replace", newText: content } } : {}
|
|
1537
|
-
});
|
|
1538
|
-
} else if (kind === "delete") {
|
|
1539
|
-
out.push({
|
|
1540
|
-
path,
|
|
1541
|
-
changeKind: "delete",
|
|
1542
|
-
...content !== void 0 ? { content: { kind: "replace", oldText: content, newText: "" } } : {}
|
|
1543
|
-
});
|
|
1544
|
-
} else if (movePath) {
|
|
1545
|
-
out.push({
|
|
1546
|
-
path,
|
|
1547
|
-
changeKind: "rename",
|
|
1548
|
-
...diff ? { content: { kind: "unified", diff } } : {}
|
|
1549
|
-
});
|
|
1550
|
-
} else {
|
|
1551
|
-
out.push({
|
|
1552
|
-
path,
|
|
1553
|
-
changeKind: "modify",
|
|
1554
|
-
...diff ? { content: { kind: "unified", diff } } : {}
|
|
1555
|
-
});
|
|
1556
|
-
}
|
|
1557
|
-
}
|
|
1558
|
-
return out;
|
|
1559
|
-
}
|
|
1560
1994
|
function toUserInputQuestions(params) {
|
|
1561
1995
|
if (!isRecord3(params)) return void 0;
|
|
1562
1996
|
const questions = Array.isArray(params["questions"]) ? params["questions"] : [];
|
|
@@ -1765,174 +2199,50 @@ function mapCollabAgentEvent(input) {
|
|
|
1765
2199
|
payload: { taskId: agentThreadId, status, ...linkage },
|
|
1766
2200
|
method,
|
|
1767
2201
|
providerInstanceId: input.providerInstanceId
|
|
1768
|
-
})
|
|
1769
|
-
];
|
|
1770
|
-
}
|
|
1771
|
-
if (method === "collabAgent/tokenUsage") {
|
|
1772
|
-
const tokenUsage = isRecord3(input.params["tokenUsage"]) ? input.params["tokenUsage"] : void 0;
|
|
1773
|
-
const total = tokenUsage && isRecord3(tokenUsage["total"]) ? tokenUsage["total"] : void 0;
|
|
1774
|
-
const count = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
|
|
1775
|
-
const totalTokens = total ? count(total["totalTokens"]) : void 0;
|
|
1776
|
-
if (total === void 0 || totalTokens === void 0) return [];
|
|
1777
|
-
const inputTokens = count(total["inputTokens"]);
|
|
1778
|
-
const cachedInputTokens = count(total["cachedInputTokens"]);
|
|
1779
|
-
const outputTokens = count(total["outputTokens"]);
|
|
1780
|
-
const reasoningOutputTokens = count(total["reasoningOutputTokens"]);
|
|
1781
|
-
const typedUsage = {
|
|
1782
|
-
totalTokens,
|
|
1783
|
-
...inputTokens !== void 0 ? { inputTokens } : {},
|
|
1784
|
-
...cachedInputTokens !== void 0 ? { cachedInputTokens } : {},
|
|
1785
|
-
...outputTokens !== void 0 ? { outputTokens } : {},
|
|
1786
|
-
...reasoningOutputTokens !== void 0 ? { reasoningOutputTokens } : {}
|
|
1787
|
-
};
|
|
1788
|
-
return [
|
|
1789
|
-
baseEvent({
|
|
1790
|
-
type: "task.progress",
|
|
1791
|
-
threadId,
|
|
1792
|
-
payload: {
|
|
1793
|
-
taskId: agentThreadId,
|
|
1794
|
-
description: title,
|
|
1795
|
-
typedUsage,
|
|
1796
|
-
...linkage
|
|
1797
|
-
},
|
|
1798
|
-
method,
|
|
1799
|
-
providerInstanceId: input.providerInstanceId
|
|
1800
|
-
})
|
|
1801
|
-
];
|
|
1802
|
-
}
|
|
1803
|
-
return [];
|
|
1804
|
-
}
|
|
1805
|
-
function describeCommandApproval(params) {
|
|
1806
|
-
const record = isRecord3(params) ? params : {};
|
|
1807
|
-
const commandValue = record["command"];
|
|
1808
|
-
const command = Array.isArray(commandValue) ? commandValue.filter((entry) => typeof entry === "string").join(" ") : asString(commandValue) ?? "";
|
|
1809
|
-
const cwd = asString(record["cwd"]);
|
|
1810
|
-
const reason = trimText(record["reason"]);
|
|
1811
|
-
const callId = asString(record["callId"]);
|
|
1812
|
-
return {
|
|
1813
|
-
command,
|
|
1814
|
-
...cwd ? { cwd } : {},
|
|
1815
|
-
...reason ? { reason } : {},
|
|
1816
|
-
...callId ? { callId } : {}
|
|
1817
|
-
};
|
|
1818
|
-
}
|
|
1819
|
-
function describeFileChangeApproval(params) {
|
|
1820
|
-
const record = isRecord3(params) ? params : {};
|
|
1821
|
-
const reason = trimText(record["reason"]);
|
|
1822
|
-
const grantRoot = asString(record["grantRoot"]);
|
|
1823
|
-
const callId = asString(record["callId"]);
|
|
1824
|
-
const detail = reason ?? describeFileChanges(record) ?? trimText(grantRoot);
|
|
1825
|
-
return {
|
|
1826
|
-
...reason ? { reason } : {},
|
|
1827
|
-
...detail ? { detail } : {},
|
|
1828
|
-
...grantRoot ? { grantRoot } : {},
|
|
1829
|
-
...callId ? { callId } : {},
|
|
1830
|
-
fileChanges: toApplyPatchFileChanges(record)
|
|
1831
|
-
};
|
|
1832
|
-
}
|
|
1833
|
-
function describePermissionsApproval(params) {
|
|
1834
|
-
const record = isRecord3(params) ? params : {};
|
|
1835
|
-
const permissions = isRecord3(record["permissions"]) ? record["permissions"] : {};
|
|
1836
|
-
const fileSystem = isRecord3(permissions["fileSystem"]) ? permissions["fileSystem"] : {};
|
|
1837
|
-
const network = isRecord3(permissions["network"]) ? permissions["network"] : {};
|
|
1838
|
-
const pathsOf = (value) => Array.isArray(value) ? value.flatMap((entry) => typeof entry === "string" && entry.length > 0 ? [entry] : []) : [];
|
|
1839
|
-
const read = pathsOf(fileSystem["read"]);
|
|
1840
|
-
const write = pathsOf(fileSystem["write"]);
|
|
1841
|
-
const entries = Array.isArray(fileSystem["entries"]) ? fileSystem["entries"] : [];
|
|
1842
|
-
for (const entry of entries) {
|
|
1843
|
-
if (!isRecord3(entry) || !isRecord3(entry["path"])) continue;
|
|
1844
|
-
const target = entry["path"];
|
|
1845
|
-
const path = target["type"] === "path" ? asString(target["path"]) : target["type"] === "glob_pattern" ? asString(target["pattern"]) : void 0;
|
|
1846
|
-
if (!path) continue;
|
|
1847
|
-
if (entry["access"] === "read") read.push(path);
|
|
1848
|
-
if (entry["access"] === "write") write.push(path);
|
|
1849
|
-
}
|
|
1850
|
-
const reason = trimText(record["reason"]);
|
|
1851
|
-
return {
|
|
1852
|
-
...reason ? { reason } : {},
|
|
1853
|
-
read: [...new Set(read)],
|
|
1854
|
-
write: [...new Set(write)],
|
|
1855
|
-
network: network["enabled"] === true
|
|
1856
|
-
};
|
|
1857
|
-
}
|
|
1858
|
-
function toPermissionsResponse(decision, params, persistent) {
|
|
1859
|
-
if (decision !== "allow") return { permissions: {} };
|
|
1860
|
-
const requested = isRecord3(params) && isRecord3(params["permissions"]) ? params["permissions"] : {};
|
|
1861
|
-
return {
|
|
1862
|
-
permissions: {
|
|
1863
|
-
...requested["fileSystem"] !== void 0 ? { fileSystem: requested["fileSystem"] } : {},
|
|
1864
|
-
...requested["network"] !== void 0 ? { network: requested["network"] } : {}
|
|
1865
|
-
},
|
|
1866
|
-
scope: persistent ? "session" : "turn"
|
|
1867
|
-
};
|
|
1868
|
-
}
|
|
1869
|
-
function describeMcpElicitation(params) {
|
|
1870
|
-
const record = isRecord3(params) ? params : {};
|
|
1871
|
-
const appName = asString(record["appName"]) ?? asString(record["serverName"]) ?? void 0;
|
|
1872
|
-
const serverName = asString(record["serverName"]);
|
|
1873
|
-
const message = trimText(record["message"]);
|
|
1874
|
-
const url = asString(record["url"]) ?? asString(record["authorizationUrl"]);
|
|
1875
|
-
const identity = appName ?? url ?? "unknown";
|
|
1876
|
-
return {
|
|
1877
|
-
...appName ? { appName } : {},
|
|
1878
|
-
...serverName ? { serverName } : {},
|
|
1879
|
-
...message ? { message } : {},
|
|
1880
|
-
...url ? { url } : {},
|
|
1881
|
-
identity,
|
|
1882
|
-
mode: url ? "url" : "form",
|
|
1883
|
-
...record["requestedSchema"] !== void 0 ? { schema: record["requestedSchema"] } : {}
|
|
1884
|
-
};
|
|
1885
|
-
}
|
|
1886
|
-
function persistFromMeta(meta) {
|
|
1887
|
-
if (!isRecord3(meta)) return void 0;
|
|
1888
|
-
const persist = asString(meta["persist"]) ?? asString(meta["persistence"]);
|
|
1889
|
-
if (persist === "session" || persist === "acceptForSession") return "session";
|
|
1890
|
-
if (persist === "always" || persist === "permanent" || persist === "forever" || persist === "persistent" || persist === "acceptAlways") {
|
|
1891
|
-
return "always";
|
|
1892
|
-
}
|
|
1893
|
-
return void 0;
|
|
1894
|
-
}
|
|
1895
|
-
function toMcpElicitationResponse(decision, params) {
|
|
1896
|
-
const described = describeMcpElicitation(params);
|
|
1897
|
-
if (decision !== "allow" || described.mode === "url") {
|
|
1898
|
-
return {
|
|
1899
|
-
action: decision === "allow" ? "decline" : decision === "cancel" ? "cancel" : "decline"
|
|
1900
|
-
};
|
|
1901
|
-
}
|
|
1902
|
-
const schema = isRecord3(described.schema) ? described.schema : void 0;
|
|
1903
|
-
const properties = schema && isRecord3(schema["properties"]) ? schema["properties"] : {};
|
|
1904
|
-
const required = schema && Array.isArray(schema["required"]) ? schema["required"] : [];
|
|
1905
|
-
const content = {};
|
|
1906
|
-
for (const [key, spec] of Object.entries(properties)) {
|
|
1907
|
-
if (!isRecord3(spec)) continue;
|
|
1908
|
-
if (Array.isArray(spec["oneOf"]) || Array.isArray(spec["enum"])) {
|
|
1909
|
-
const options = Array.isArray(spec["oneOf"]) ? spec["oneOf"] : Array.isArray(spec["enum"]) ? spec["enum"] : [];
|
|
1910
|
-
const first = options[0];
|
|
1911
|
-
content[key] = isRecord3(first) ? first["const"] ?? first["value"] ?? first : first;
|
|
1912
|
-
} else if (spec["type"] === "boolean") {
|
|
1913
|
-
content[key] = true;
|
|
1914
|
-
} else if (typeof spec["default"] !== "undefined") {
|
|
1915
|
-
content[key] = spec["default"];
|
|
1916
|
-
}
|
|
2202
|
+
})
|
|
2203
|
+
];
|
|
1917
2204
|
}
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
2205
|
+
if (method === "collabAgent/tokenUsage") {
|
|
2206
|
+
const tokenUsage = isRecord3(input.params["tokenUsage"]) ? input.params["tokenUsage"] : void 0;
|
|
2207
|
+
const total = tokenUsage && isRecord3(tokenUsage["total"]) ? tokenUsage["total"] : void 0;
|
|
2208
|
+
const count = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
|
|
2209
|
+
const totalTokens = total ? count(total["totalTokens"]) : void 0;
|
|
2210
|
+
if (total === void 0 || totalTokens === void 0) return [];
|
|
2211
|
+
const inputTokens = count(total["inputTokens"]);
|
|
2212
|
+
const cachedInputTokens = count(total["cachedInputTokens"]);
|
|
2213
|
+
const outputTokens = count(total["outputTokens"]);
|
|
2214
|
+
const reasoningOutputTokens = count(total["reasoningOutputTokens"]);
|
|
2215
|
+
const typedUsage = {
|
|
2216
|
+
totalTokens,
|
|
2217
|
+
...inputTokens !== void 0 ? { inputTokens } : {},
|
|
2218
|
+
...cachedInputTokens !== void 0 ? { cachedInputTokens } : {},
|
|
2219
|
+
...outputTokens !== void 0 ? { outputTokens } : {},
|
|
2220
|
+
...reasoningOutputTokens !== void 0 ? { reasoningOutputTokens } : {}
|
|
2221
|
+
};
|
|
2222
|
+
return [
|
|
2223
|
+
baseEvent({
|
|
2224
|
+
type: "task.progress",
|
|
2225
|
+
threadId,
|
|
2226
|
+
payload: {
|
|
2227
|
+
taskId: agentThreadId,
|
|
2228
|
+
description: title,
|
|
2229
|
+
typedUsage,
|
|
2230
|
+
...linkage
|
|
2231
|
+
},
|
|
2232
|
+
method,
|
|
2233
|
+
providerInstanceId: input.providerInstanceId
|
|
2234
|
+
})
|
|
2235
|
+
];
|
|
1922
2236
|
}
|
|
1923
|
-
|
|
1924
|
-
return {
|
|
1925
|
-
action: "accept",
|
|
1926
|
-
content,
|
|
1927
|
-
...persist ? { _meta: { persist } } : {}
|
|
1928
|
-
};
|
|
2237
|
+
return [];
|
|
1929
2238
|
}
|
|
1930
2239
|
function mapNotificationToEvents(input) {
|
|
1931
|
-
const { method, params, threadId, providerInstanceId } = input;
|
|
2240
|
+
const { method, params, threadId, providerInstanceId, rateLimits } = input;
|
|
1932
2241
|
if (method.startsWith("collabAgent/")) {
|
|
1933
2242
|
return mapCollabAgentEvent({ method, params, rootThreadId: threadId, providerInstanceId });
|
|
1934
2243
|
}
|
|
1935
2244
|
if (method === "error") {
|
|
2245
|
+
if (isUsageLimitExceeded(isRecord3(params) ? params["error"] : void 0)) return [];
|
|
1936
2246
|
const willRetry = isRecord3(params) && params["willRetry"] === true;
|
|
1937
2247
|
const nested = isRecord3(params) && isRecord3(params["error"]) ? params["error"] : void 0;
|
|
1938
2248
|
const message = (nested ? trimText(nested["message"]) : void 0) ?? (isRecord3(params) ? trimText(params["message"]) : void 0) ?? "Codex error";
|
|
@@ -2108,17 +2418,50 @@ function mapNotificationToEvents(input) {
|
|
|
2108
2418
|
providerInstanceId
|
|
2109
2419
|
})
|
|
2110
2420
|
];
|
|
2111
|
-
case "turn/completed":
|
|
2421
|
+
case "turn/completed": {
|
|
2422
|
+
const controlFlow = turnCompletedControlFlow(params);
|
|
2423
|
+
const turn = isRecord3(params) ? isRecord3(params["turn"]) ? params["turn"] : params : {};
|
|
2424
|
+
const turnError = isRecord3(turn["error"]) ? turn["error"] : void 0;
|
|
2425
|
+
if (controlFlow.state === "failed" && isUsageLimitExceeded(turnError)) {
|
|
2426
|
+
const message = codexUsageLimitMessage(
|
|
2427
|
+
rateLimits,
|
|
2428
|
+
nowIso2(),
|
|
2429
|
+
trimText(turnError?.["message"])
|
|
2430
|
+
);
|
|
2431
|
+
return [
|
|
2432
|
+
baseEvent({
|
|
2433
|
+
type: "runtime.error",
|
|
2434
|
+
threadId,
|
|
2435
|
+
turnId,
|
|
2436
|
+
payload: {
|
|
2437
|
+
message,
|
|
2438
|
+
class: "provider_error",
|
|
2439
|
+
...params !== void 0 ? { detail: params } : {}
|
|
2440
|
+
},
|
|
2441
|
+
method,
|
|
2442
|
+
providerInstanceId
|
|
2443
|
+
}),
|
|
2444
|
+
baseEvent({
|
|
2445
|
+
type: "turn.completed",
|
|
2446
|
+
threadId,
|
|
2447
|
+
turnId,
|
|
2448
|
+
payload: { ...controlFlow, errorMessage: message },
|
|
2449
|
+
method,
|
|
2450
|
+
providerInstanceId
|
|
2451
|
+
})
|
|
2452
|
+
];
|
|
2453
|
+
}
|
|
2112
2454
|
return [
|
|
2113
2455
|
baseEvent({
|
|
2114
2456
|
type: "turn.completed",
|
|
2115
2457
|
threadId,
|
|
2116
2458
|
turnId,
|
|
2117
|
-
payload:
|
|
2459
|
+
payload: controlFlow,
|
|
2118
2460
|
method,
|
|
2119
2461
|
providerInstanceId
|
|
2120
2462
|
})
|
|
2121
2463
|
];
|
|
2464
|
+
}
|
|
2122
2465
|
case "turn/aborted":
|
|
2123
2466
|
return [
|
|
2124
2467
|
baseEvent({
|
|
@@ -2638,6 +2981,8 @@ var CodexSessionRuntime = class {
|
|
|
2638
2981
|
liveForeignTurns = /* @__PURE__ */ new Map();
|
|
2639
2982
|
collabReceiverTurns = /* @__PURE__ */ new Map();
|
|
2640
2983
|
memoryThreadIds = /* @__PURE__ */ new Set();
|
|
2984
|
+
/** 这个会话见过的主额度快照(按字段合并),额度用尽停轮时点名窗口用。 */
|
|
2985
|
+
rateLimits;
|
|
2641
2986
|
appServerArgs;
|
|
2642
2987
|
host;
|
|
2643
2988
|
attachments;
|
|
@@ -2794,11 +3139,15 @@ var CodexSessionRuntime = class {
|
|
|
2794
3139
|
});
|
|
2795
3140
|
return;
|
|
2796
3141
|
}
|
|
3142
|
+
if (method === "account/rateLimits/updated" && isRecord3(params)) {
|
|
3143
|
+
this.rateLimits = mergeCodexRateLimits(this.rateLimits, params["rateLimits"]);
|
|
3144
|
+
}
|
|
2797
3145
|
for (const event of mapNotificationToEvents({
|
|
2798
3146
|
method,
|
|
2799
3147
|
params,
|
|
2800
3148
|
threadId: this.threadId,
|
|
2801
|
-
providerInstanceId: this.host.instanceId
|
|
3149
|
+
providerInstanceId: this.host.instanceId,
|
|
3150
|
+
rateLimits: this.rateLimits
|
|
2802
3151
|
})) {
|
|
2803
3152
|
this.emit(event);
|
|
2804
3153
|
}
|
|
@@ -3473,7 +3822,7 @@ async function markProviderRejection(run) {
|
|
|
3473
3822
|
}
|
|
3474
3823
|
|
|
3475
3824
|
// src/sessions.ts
|
|
3476
|
-
import * as
|
|
3825
|
+
import * as NodeFS3 from "node:fs";
|
|
3477
3826
|
import * as NodePath4 from "node:path";
|
|
3478
3827
|
import * as NodeReadline from "node:readline";
|
|
3479
3828
|
var EXTENDED_LENGTH_UNC = "//?/UNC/";
|
|
@@ -3647,7 +3996,7 @@ async function readRolloutModel(input, rolloutPath, sameDirectory) {
|
|
|
3647
3996
|
let verified = false;
|
|
3648
3997
|
let model = null;
|
|
3649
3998
|
try {
|
|
3650
|
-
const stream =
|
|
3999
|
+
const stream = NodeFS3.createReadStream(resolved, { encoding: "utf8" });
|
|
3651
4000
|
const rl = NodeReadline.createInterface({ input: stream, crlfDelay: Infinity });
|
|
3652
4001
|
for await (const line of rl) {
|
|
3653
4002
|
if (!line.includes('"session_meta"') && !line.includes('"turn_context"')) continue;
|
|
@@ -3863,12 +4212,12 @@ async function listCodexModels(rpc) {
|
|
|
3863
4212
|
|
|
3864
4213
|
// src/skills.ts
|
|
3865
4214
|
var SKILLS_LIST_TIMEOUT_MS = 15e3;
|
|
3866
|
-
function codexProbeSkill(raw) {
|
|
4215
|
+
function codexProbeSkill(raw, includeRepo = false) {
|
|
3867
4216
|
if (!isRecord3(raw)) return null;
|
|
3868
4217
|
const name = asString(raw["name"]);
|
|
3869
4218
|
const path = asString(raw["path"]);
|
|
3870
4219
|
const scope = asString(raw["scope"]);
|
|
3871
|
-
if (!name || !path || scope === "repo") return null;
|
|
4220
|
+
if (!name || !path || scope === "repo" && !includeRepo) return null;
|
|
3872
4221
|
const face = isRecord3(raw["interface"]) ? raw["interface"] : void 0;
|
|
3873
4222
|
const description = asString(raw["description"]);
|
|
3874
4223
|
const displayName = asString(face?.["displayName"]);
|
|
@@ -3883,6 +4232,21 @@ function codexProbeSkill(raw) {
|
|
|
3883
4232
|
...shortDescription ? { shortDescription } : {}
|
|
3884
4233
|
};
|
|
3885
4234
|
}
|
|
4235
|
+
async function listCodexWorkspaceSkills(rpc, cwd) {
|
|
4236
|
+
const response = await rpc.request("skills/list", { cwds: [cwd] }, SKILLS_LIST_TIMEOUT_MS);
|
|
4237
|
+
if (!isRecord3(response) || !Array.isArray(response["data"])) {
|
|
4238
|
+
throw new Error("skills/list returned an unexpected shape");
|
|
4239
|
+
}
|
|
4240
|
+
const skills = /* @__PURE__ */ new Map();
|
|
4241
|
+
for (const entry of response["data"]) {
|
|
4242
|
+
if (!isRecord3(entry) || !Array.isArray(entry["skills"])) continue;
|
|
4243
|
+
for (const raw of entry["skills"]) {
|
|
4244
|
+
const skill = codexProbeSkill(raw, true);
|
|
4245
|
+
if (skill !== null) skills.set(skill.path, skill);
|
|
4246
|
+
}
|
|
4247
|
+
}
|
|
4248
|
+
return [...skills.values()].toSorted((left, right) => left.name.localeCompare(right.name));
|
|
4249
|
+
}
|
|
3886
4250
|
async function listCodexSkills(rpc) {
|
|
3887
4251
|
let response;
|
|
3888
4252
|
try {
|
|
@@ -3902,6 +4266,34 @@ async function listCodexSkills(rpc) {
|
|
|
3902
4266
|
return [...skills.values()].toSorted((left, right) => left.name.localeCompare(right.name));
|
|
3903
4267
|
}
|
|
3904
4268
|
|
|
4269
|
+
// src/workspaceInventory.ts
|
|
4270
|
+
import * as NodeFS4 from "node:fs";
|
|
4271
|
+
import * as NodePath5 from "node:path";
|
|
4272
|
+
var MAX_ANCESTORS = 12;
|
|
4273
|
+
var codexWorkspaceWatchPaths = (cwd, skills) => {
|
|
4274
|
+
const watched = /* @__PURE__ */ new Set();
|
|
4275
|
+
let directory = cwd;
|
|
4276
|
+
for (let level = 0; level < MAX_ANCESTORS; level += 1) {
|
|
4277
|
+
watched.add(NodePath5.join(directory, ".agents", "skills"));
|
|
4278
|
+
watched.add(NodePath5.join(directory, ".codex", "skills"));
|
|
4279
|
+
if (NodeFS4.existsSync(NodePath5.join(directory, ".git"))) break;
|
|
4280
|
+
const parent = NodePath5.dirname(directory);
|
|
4281
|
+
if (parent === directory) break;
|
|
4282
|
+
directory = parent;
|
|
4283
|
+
}
|
|
4284
|
+
for (const skill of skills) {
|
|
4285
|
+
watched.add(skill.path);
|
|
4286
|
+
const skillDirectory = NodePath5.dirname(skill.path);
|
|
4287
|
+
watched.add(skillDirectory);
|
|
4288
|
+
watched.add(NodePath5.dirname(skillDirectory));
|
|
4289
|
+
}
|
|
4290
|
+
return [...watched];
|
|
4291
|
+
};
|
|
4292
|
+
var codexWorkspaceInventory = async (rpc, cwd) => {
|
|
4293
|
+
const skills = await listCodexWorkspaceSkills(rpc, cwd);
|
|
4294
|
+
return { skills, watchPaths: codexWorkspaceWatchPaths(cwd, skills) };
|
|
4295
|
+
};
|
|
4296
|
+
|
|
3905
4297
|
// src/signIn.ts
|
|
3906
4298
|
var DEVICE_CODE_URL = /https?:\/\/\S+/;
|
|
3907
4299
|
var DEVICE_CODE_VALUE = /\b([A-Z0-9]{4,}(?:-[A-Z0-9]{4,})+)\b/;
|
|
@@ -4089,7 +4481,7 @@ async function verifyCodexSignIn(input) {
|
|
|
4089
4481
|
// src/textGeneration.ts
|
|
4090
4482
|
import * as NodeFSP2 from "node:fs/promises";
|
|
4091
4483
|
import * as NodeOS3 from "node:os";
|
|
4092
|
-
import * as
|
|
4484
|
+
import * as NodePath6 from "node:path";
|
|
4093
4485
|
import {
|
|
4094
4486
|
mergeLaunchEnv as mergeLaunchEnv2
|
|
4095
4487
|
} from "@ycodium-ai/plugin-api/executor";
|
|
@@ -4241,7 +4633,7 @@ function absoluteImagePaths(attachments) {
|
|
|
4241
4633
|
for (const attachment of attachments) {
|
|
4242
4634
|
if (!isRecord3(attachment) || attachment.type !== "image") continue;
|
|
4243
4635
|
const candidate = asString(attachment.path) ?? asString(attachment.filePath) ?? asString(attachment.savedPath);
|
|
4244
|
-
if (candidate &&
|
|
4636
|
+
if (candidate && NodePath6.isAbsolute(candidate)) paths.push(candidate);
|
|
4245
4637
|
}
|
|
4246
4638
|
return paths;
|
|
4247
4639
|
}
|
|
@@ -4256,9 +4648,9 @@ function createCodexTextGeneration(input) {
|
|
|
4256
4648
|
if (input.recipe.binaryPath.length === 0) {
|
|
4257
4649
|
throw new Error("Codex CLI is required but not available on PATH.");
|
|
4258
4650
|
}
|
|
4259
|
-
const tmp = await NodeFSP2.mkdtemp(
|
|
4260
|
-
const schemaPath =
|
|
4261
|
-
const outputPath =
|
|
4651
|
+
const tmp = await NodeFSP2.mkdtemp(NodePath6.join(NodeOS3.tmpdir(), "ycodium-codex-tg-"));
|
|
4652
|
+
const schemaPath = NodePath6.join(tmp, "schema.json");
|
|
4653
|
+
const outputPath = NodePath6.join(tmp, "output.json");
|
|
4262
4654
|
await NodeFSP2.writeFile(schemaPath, JSON.stringify(schema));
|
|
4263
4655
|
await NodeFSP2.writeFile(outputPath, "");
|
|
4264
4656
|
const reasoningEffort = modelSelectionStringOption(modelSelection, "reasoningEffort") ?? DEFAULT_TEXT_GENERATION_REASONING_EFFORT;
|
|
@@ -4539,6 +4931,588 @@ ${extra}` : ""}`;
|
|
|
4539
4931
|
};
|
|
4540
4932
|
}
|
|
4541
4933
|
|
|
4934
|
+
// src/usage/codexUsage.ts
|
|
4935
|
+
import * as NodeFS7 from "node:fs";
|
|
4936
|
+
import * as NodePath10 from "node:path";
|
|
4937
|
+
|
|
4938
|
+
// src/usage/codexTokenUsage.ts
|
|
4939
|
+
import * as NodeFS6 from "node:fs";
|
|
4940
|
+
import * as NodePath9 from "node:path";
|
|
4941
|
+
|
|
4942
|
+
// src/usage/codexScanCache.ts
|
|
4943
|
+
import * as NodePath8 from "node:path";
|
|
4944
|
+
|
|
4945
|
+
// src/usage/codexTranscriptScan.ts
|
|
4946
|
+
import * as NodeFS5 from "node:fs";
|
|
4947
|
+
import * as NodePath7 from "node:path";
|
|
4948
|
+
|
|
4949
|
+
// src/usage/codexTranscripts.ts
|
|
4950
|
+
var int = (value) => typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.trunc(value) : 0;
|
|
4951
|
+
var parseTimestampMs = (value) => {
|
|
4952
|
+
if (typeof value !== "string") return null;
|
|
4953
|
+
const parsed = Date.parse(value);
|
|
4954
|
+
return Number.isNaN(parsed) ? null : parsed;
|
|
4955
|
+
};
|
|
4956
|
+
var asRecord3 = (value) => typeof value === "object" && value !== null ? value : null;
|
|
4957
|
+
var mightCarryUsage = (line) => line.includes('"token_count"');
|
|
4958
|
+
var isReducerLine = (line) => mightCarryUsage(line) || line.includes('"turn_context"') || line.includes('"session_meta"');
|
|
4959
|
+
var initialCodexScanState = () => ({
|
|
4960
|
+
model: "",
|
|
4961
|
+
sessionId: "",
|
|
4962
|
+
lastUsageSignature: null,
|
|
4963
|
+
sawSessionMeta: false,
|
|
4964
|
+
suppressingForkCopies: false,
|
|
4965
|
+
forkCopyAnchorMs: 0
|
|
4966
|
+
});
|
|
4967
|
+
var FORK_COPY_MAX_GAP_MS = 1e3;
|
|
4968
|
+
var isForkedSessionMeta = (payload) => {
|
|
4969
|
+
if (typeof payload["forked_from_id"] === "string") return true;
|
|
4970
|
+
const spawn = asRecord3(asRecord3(asRecord3(payload["source"])?.["subagent"])?.["thread_spawn"]);
|
|
4971
|
+
return typeof spawn?.["parent_thread_id"] === "string";
|
|
4972
|
+
};
|
|
4973
|
+
var parseCodexLine = (line, state) => {
|
|
4974
|
+
let parsed;
|
|
4975
|
+
try {
|
|
4976
|
+
parsed = JSON.parse(line);
|
|
4977
|
+
} catch {
|
|
4978
|
+
return null;
|
|
4979
|
+
}
|
|
4980
|
+
const record = asRecord3(parsed);
|
|
4981
|
+
const payload = asRecord3(record?.["payload"]);
|
|
4982
|
+
if (record === null || payload === null) return null;
|
|
4983
|
+
if (record["type"] === "session_meta") {
|
|
4984
|
+
if (state.sawSessionMeta) return null;
|
|
4985
|
+
state.sawSessionMeta = true;
|
|
4986
|
+
const id = payload["id"] ?? payload["session_id"];
|
|
4987
|
+
if (typeof id === "string") state.sessionId = id;
|
|
4988
|
+
const metaTimestampMs = parseTimestampMs(record["timestamp"]);
|
|
4989
|
+
if (metaTimestampMs !== null && isForkedSessionMeta(payload)) {
|
|
4990
|
+
state.suppressingForkCopies = true;
|
|
4991
|
+
state.forkCopyAnchorMs = metaTimestampMs;
|
|
4992
|
+
}
|
|
4993
|
+
return null;
|
|
4994
|
+
}
|
|
4995
|
+
if (record["type"] === "turn_context") {
|
|
4996
|
+
if (typeof payload["model"] === "string") state.model = payload["model"];
|
|
4997
|
+
return null;
|
|
4998
|
+
}
|
|
4999
|
+
if (payload["type"] !== "token_count") return null;
|
|
5000
|
+
const last = asRecord3(asRecord3(payload["info"])?.["last_token_usage"]);
|
|
5001
|
+
if (last === null) return null;
|
|
5002
|
+
const timestampMs = parseTimestampMs(record["timestamp"]);
|
|
5003
|
+
if (timestampMs === null) return null;
|
|
5004
|
+
if (state.model.length === 0) return null;
|
|
5005
|
+
const signature = JSON.stringify(last);
|
|
5006
|
+
if (signature === state.lastUsageSignature) return null;
|
|
5007
|
+
state.lastUsageSignature = signature;
|
|
5008
|
+
if (state.suppressingForkCopies) {
|
|
5009
|
+
if (timestampMs - state.forkCopyAnchorMs < FORK_COPY_MAX_GAP_MS) {
|
|
5010
|
+
state.forkCopyAnchorMs = timestampMs;
|
|
5011
|
+
return null;
|
|
5012
|
+
}
|
|
5013
|
+
state.suppressingForkCopies = false;
|
|
5014
|
+
}
|
|
5015
|
+
const inputTokens = int(last["input_tokens"]);
|
|
5016
|
+
const cachedInputTokens = int(last["cached_input_tokens"]);
|
|
5017
|
+
const cacheCreationTokens = int(last["cache_write_input_tokens"]);
|
|
5018
|
+
const outputTokens = int(last["output_tokens"]);
|
|
5019
|
+
const totals = {
|
|
5020
|
+
// Codex 报的 `input_tokens` 含缓存部分,三个输入桶要互斥就得拆开。
|
|
5021
|
+
uncachedInputTokens: Math.max(0, inputTokens - cachedInputTokens - cacheCreationTokens),
|
|
5022
|
+
cachedInputTokens,
|
|
5023
|
+
cacheCreationTokens,
|
|
5024
|
+
outputTokens,
|
|
5025
|
+
// 推理 token 已含在 output_tokens 里,单列只为看构成。
|
|
5026
|
+
reasoningTokens: Math.min(outputTokens, int(last["reasoning_output_tokens"]))
|
|
5027
|
+
};
|
|
5028
|
+
if (totals.uncachedInputTokens + totals.cachedInputTokens + totals.cacheCreationTokens + outputTokens === 0) {
|
|
5029
|
+
return null;
|
|
5030
|
+
}
|
|
5031
|
+
return { timestampMs, model: state.model, sessionId: state.sessionId, totals };
|
|
5032
|
+
};
|
|
5033
|
+
|
|
5034
|
+
// src/usage/codexTranscriptScan.ts
|
|
5035
|
+
var TRANSCRIPT_SUFFIX = ".jsonl";
|
|
5036
|
+
var listTranscriptFiles = async (root, sinceMs) => {
|
|
5037
|
+
const found = [];
|
|
5038
|
+
const walk = async (dir) => {
|
|
5039
|
+
let entries;
|
|
5040
|
+
try {
|
|
5041
|
+
entries = await NodeFS5.promises.readdir(dir, { withFileTypes: true });
|
|
5042
|
+
} catch {
|
|
5043
|
+
return;
|
|
5044
|
+
}
|
|
5045
|
+
for (const entry of entries) {
|
|
5046
|
+
const child = NodePath7.join(dir, entry.name);
|
|
5047
|
+
if (entry.isDirectory()) {
|
|
5048
|
+
await walk(child);
|
|
5049
|
+
continue;
|
|
5050
|
+
}
|
|
5051
|
+
if (!entry.isFile() || !entry.name.endsWith(TRANSCRIPT_SUFFIX)) continue;
|
|
5052
|
+
try {
|
|
5053
|
+
const stat2 = await NodeFS5.promises.stat(child);
|
|
5054
|
+
if (stat2.mtimeMs >= sinceMs)
|
|
5055
|
+
found.push({ path: child, size: stat2.size, mtimeMs: stat2.mtimeMs });
|
|
5056
|
+
} catch {
|
|
5057
|
+
}
|
|
5058
|
+
}
|
|
5059
|
+
};
|
|
5060
|
+
await walk(root);
|
|
5061
|
+
return found;
|
|
5062
|
+
};
|
|
5063
|
+
var GUARD_LENGTH = 64;
|
|
5064
|
+
var NEWLINE = 10;
|
|
5065
|
+
var CARRIAGE_RETURN = 13;
|
|
5066
|
+
var fnv1a = (bytes) => {
|
|
5067
|
+
let hash = 2166136261;
|
|
5068
|
+
for (let index = 0; index < bytes.length; index += 1) {
|
|
5069
|
+
hash ^= bytes[index];
|
|
5070
|
+
hash = Math.imul(hash, 16777619);
|
|
5071
|
+
}
|
|
5072
|
+
return hash >>> 0;
|
|
5073
|
+
};
|
|
5074
|
+
var asBuffer = (chunk) => typeof chunk === "string" ? Buffer.from(chunk) : Buffer.from(chunk.buffer, chunk.byteOffset, chunk.byteLength);
|
|
5075
|
+
var readMatchingGuard = async (filePath, position) => {
|
|
5076
|
+
const { guardLength, resumeOffset } = position;
|
|
5077
|
+
if (guardLength <= 0 || guardLength > GUARD_LENGTH || guardLength > resumeOffset) return null;
|
|
5078
|
+
try {
|
|
5079
|
+
const chunks = [];
|
|
5080
|
+
for await (const chunk of NodeFS5.createReadStream(filePath, {
|
|
5081
|
+
start: resumeOffset - guardLength,
|
|
5082
|
+
end: resumeOffset - 1
|
|
5083
|
+
})) {
|
|
5084
|
+
chunks.push(asBuffer(chunk));
|
|
5085
|
+
}
|
|
5086
|
+
const window = Buffer.concat(chunks);
|
|
5087
|
+
return window.length === guardLength && fnv1a(window) === position.guardHash ? window : null;
|
|
5088
|
+
} catch {
|
|
5089
|
+
return null;
|
|
5090
|
+
}
|
|
5091
|
+
};
|
|
5092
|
+
var readCodexRollout = async (filePath, resumeFrom) => {
|
|
5093
|
+
try {
|
|
5094
|
+
let state = initialCodexScanState();
|
|
5095
|
+
let resumed = false;
|
|
5096
|
+
let start = 0;
|
|
5097
|
+
let guardWindow = Buffer.alloc(0);
|
|
5098
|
+
if (resumeFrom !== void 0 && resumeFrom.resumeOffset > 0) {
|
|
5099
|
+
const window = await readMatchingGuard(filePath, resumeFrom);
|
|
5100
|
+
if (window !== null) {
|
|
5101
|
+
state = { ...resumeFrom.state };
|
|
5102
|
+
start = resumeFrom.resumeOffset;
|
|
5103
|
+
guardWindow = window;
|
|
5104
|
+
resumed = true;
|
|
5105
|
+
}
|
|
5106
|
+
}
|
|
5107
|
+
const parseLine = (line, lineState, out) => {
|
|
5108
|
+
if (!isReducerLine(line)) return;
|
|
5109
|
+
const record = parseCodexLine(line, lineState);
|
|
5110
|
+
if (record !== null) out.push(record);
|
|
5111
|
+
};
|
|
5112
|
+
const toLineString = (lineBuffer) => {
|
|
5113
|
+
const content = lineBuffer.length > 0 && lineBuffer[lineBuffer.length - 1] === CARRIAGE_RETURN ? lineBuffer.subarray(0, -1) : lineBuffer;
|
|
5114
|
+
return content.toString("utf8");
|
|
5115
|
+
};
|
|
5116
|
+
const records = [];
|
|
5117
|
+
let resumeOffset = start;
|
|
5118
|
+
let pendingChunks = [];
|
|
5119
|
+
for await (const raw of NodeFS5.createReadStream(filePath, start > 0 ? { start } : void 0)) {
|
|
5120
|
+
const chunk = asBuffer(raw);
|
|
5121
|
+
if (!chunk.includes(NEWLINE)) {
|
|
5122
|
+
pendingChunks.push(chunk);
|
|
5123
|
+
continue;
|
|
5124
|
+
}
|
|
5125
|
+
const buffer = pendingChunks.length === 0 ? chunk : Buffer.concat([...pendingChunks, chunk]);
|
|
5126
|
+
pendingChunks = [];
|
|
5127
|
+
let lineStart = 0;
|
|
5128
|
+
for (; ; ) {
|
|
5129
|
+
const newlineIndex = buffer.indexOf(NEWLINE, lineStart);
|
|
5130
|
+
if (newlineIndex === -1) break;
|
|
5131
|
+
parseLine(toLineString(buffer.subarray(lineStart, newlineIndex)), state, records);
|
|
5132
|
+
lineStart = newlineIndex + 1;
|
|
5133
|
+
}
|
|
5134
|
+
resumeOffset += lineStart;
|
|
5135
|
+
const consumed = buffer.subarray(0, lineStart);
|
|
5136
|
+
guardWindow = consumed.length >= GUARD_LENGTH ? Buffer.from(consumed.subarray(-GUARD_LENGTH)) : Buffer.concat([guardWindow, consumed]).subarray(-GUARD_LENGTH);
|
|
5137
|
+
if (lineStart < buffer.length) pendingChunks.push(buffer.subarray(lineStart));
|
|
5138
|
+
}
|
|
5139
|
+
const tailRecords = [];
|
|
5140
|
+
if (pendingChunks.length > 0) {
|
|
5141
|
+
const pending = pendingChunks.length === 1 ? pendingChunks[0] : Buffer.concat(pendingChunks);
|
|
5142
|
+
if (pending.length > 0) parseLine(toLineString(pending), { ...state }, tailRecords);
|
|
5143
|
+
}
|
|
5144
|
+
return {
|
|
5145
|
+
records,
|
|
5146
|
+
tailRecords,
|
|
5147
|
+
position: {
|
|
5148
|
+
resumeOffset,
|
|
5149
|
+
guardLength: guardWindow.length,
|
|
5150
|
+
guardHash: guardWindow.length > 0 ? fnv1a(guardWindow) : 0,
|
|
5151
|
+
state
|
|
5152
|
+
},
|
|
5153
|
+
resumed
|
|
5154
|
+
};
|
|
5155
|
+
} catch {
|
|
5156
|
+
return null;
|
|
5157
|
+
}
|
|
5158
|
+
};
|
|
5159
|
+
|
|
5160
|
+
// src/usage/codexScanCache.ts
|
|
5161
|
+
var CODEX_SCAN_CACHE_VERSION = 1;
|
|
5162
|
+
var encodeScanCache = (cache) => {
|
|
5163
|
+
const models = [];
|
|
5164
|
+
const sessions = [];
|
|
5165
|
+
const modelIndex = /* @__PURE__ */ new Map();
|
|
5166
|
+
const sessionIndex = /* @__PURE__ */ new Map();
|
|
5167
|
+
const intern = (table, index, value) => {
|
|
5168
|
+
const existing = index.get(value);
|
|
5169
|
+
if (existing !== void 0) return existing;
|
|
5170
|
+
const next = table.length;
|
|
5171
|
+
table.push(value);
|
|
5172
|
+
index.set(value, next);
|
|
5173
|
+
return next;
|
|
5174
|
+
};
|
|
5175
|
+
const serializeRecord = (record) => [
|
|
5176
|
+
record.timestampMs,
|
|
5177
|
+
intern(models, modelIndex, record.model),
|
|
5178
|
+
intern(sessions, sessionIndex, record.sessionId),
|
|
5179
|
+
record.totals.uncachedInputTokens,
|
|
5180
|
+
record.totals.cachedInputTokens,
|
|
5181
|
+
record.totals.cacheCreationTokens,
|
|
5182
|
+
record.totals.outputTokens,
|
|
5183
|
+
record.totals.reasoningTokens
|
|
5184
|
+
];
|
|
5185
|
+
const files = {};
|
|
5186
|
+
for (const [path, entry] of cache) {
|
|
5187
|
+
files[path] = {
|
|
5188
|
+
s: entry.size,
|
|
5189
|
+
m: entry.mtimeMs,
|
|
5190
|
+
r: entry.records.map(serializeRecord),
|
|
5191
|
+
t: entry.tailRecords.map(serializeRecord),
|
|
5192
|
+
o: entry.position.resumeOffset,
|
|
5193
|
+
gl: entry.position.guardLength,
|
|
5194
|
+
gh: entry.position.guardHash,
|
|
5195
|
+
cs: entry.position.state
|
|
5196
|
+
};
|
|
5197
|
+
}
|
|
5198
|
+
return { version: CODEX_SCAN_CACHE_VERSION, models, sessions, files };
|
|
5199
|
+
};
|
|
5200
|
+
var isArray = (value) => Array.isArray(value);
|
|
5201
|
+
var decodeCodexState = (value) => {
|
|
5202
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
5203
|
+
const state = value;
|
|
5204
|
+
if (typeof state.model !== "string" || typeof state.sessionId !== "string" || state.lastUsageSignature !== null && typeof state.lastUsageSignature !== "string" || typeof state.sawSessionMeta !== "boolean" || typeof state.suppressingForkCopies !== "boolean" || typeof state.forkCopyAnchorMs !== "number" || !Number.isFinite(state.forkCopyAnchorMs)) {
|
|
5205
|
+
return void 0;
|
|
5206
|
+
}
|
|
5207
|
+
return {
|
|
5208
|
+
model: state.model,
|
|
5209
|
+
sessionId: state.sessionId,
|
|
5210
|
+
lastUsageSignature: state.lastUsageSignature,
|
|
5211
|
+
sawSessionMeta: state.sawSessionMeta,
|
|
5212
|
+
suppressingForkCopies: state.suppressingForkCopies,
|
|
5213
|
+
forkCopyAnchorMs: state.forkCopyAnchorMs
|
|
5214
|
+
};
|
|
5215
|
+
};
|
|
5216
|
+
var decodeScanCache = (document) => {
|
|
5217
|
+
const cache = /* @__PURE__ */ new Map();
|
|
5218
|
+
if (typeof document !== "object" || document === null) return cache;
|
|
5219
|
+
const root = document;
|
|
5220
|
+
if (root.version !== CODEX_SCAN_CACHE_VERSION) return cache;
|
|
5221
|
+
if (!isArray(root.models) || !isArray(root.sessions)) return cache;
|
|
5222
|
+
if (typeof root.files !== "object" || root.files === null) return cache;
|
|
5223
|
+
if (!root.models.every((value) => typeof value === "string")) return cache;
|
|
5224
|
+
if (!root.sessions.every((value) => typeof value === "string")) return cache;
|
|
5225
|
+
const models = root.models;
|
|
5226
|
+
const sessions = root.sessions;
|
|
5227
|
+
const decodeRecords = (rows) => {
|
|
5228
|
+
const records = [];
|
|
5229
|
+
for (const row of rows) {
|
|
5230
|
+
if (!isArray(row) || row.length < 8) return null;
|
|
5231
|
+
const [timestampMs, modelIdx, sessionIdx, uncached, cached, creation, output, reasoning] = row;
|
|
5232
|
+
const model = typeof modelIdx === "number" ? models[modelIdx] : void 0;
|
|
5233
|
+
if (typeof timestampMs !== "number" || !Number.isFinite(timestampMs) || model === void 0 || !Number.isFinite(uncached) || !Number.isFinite(cached) || !Number.isFinite(creation) || !Number.isFinite(output) || !Number.isFinite(reasoning)) {
|
|
5234
|
+
return null;
|
|
5235
|
+
}
|
|
5236
|
+
records.push({
|
|
5237
|
+
timestampMs,
|
|
5238
|
+
model,
|
|
5239
|
+
sessionId: (typeof sessionIdx === "number" ? sessions[sessionIdx] : void 0) ?? "",
|
|
5240
|
+
totals: {
|
|
5241
|
+
uncachedInputTokens: uncached,
|
|
5242
|
+
cachedInputTokens: cached,
|
|
5243
|
+
cacheCreationTokens: creation,
|
|
5244
|
+
outputTokens: output,
|
|
5245
|
+
reasoningTokens: reasoning
|
|
5246
|
+
}
|
|
5247
|
+
});
|
|
5248
|
+
}
|
|
5249
|
+
return records;
|
|
5250
|
+
};
|
|
5251
|
+
for (const [path, raw] of Object.entries(root.files)) {
|
|
5252
|
+
if (typeof raw !== "object" || raw === null) continue;
|
|
5253
|
+
const entry = raw;
|
|
5254
|
+
if (typeof entry.s !== "number" || typeof entry.m !== "number") continue;
|
|
5255
|
+
if (!isArray(entry.r) || !isArray(entry.t)) continue;
|
|
5256
|
+
if (typeof entry.o !== "number" || !Number.isSafeInteger(entry.o) || entry.o < 0 || typeof entry.gl !== "number" || !Number.isSafeInteger(entry.gl) || entry.gl < 0 || entry.gl > GUARD_LENGTH || entry.gl > entry.o || typeof entry.gh !== "number" || !Number.isFinite(entry.gh)) {
|
|
5257
|
+
continue;
|
|
5258
|
+
}
|
|
5259
|
+
const state = decodeCodexState(entry.cs);
|
|
5260
|
+
if (state === void 0) continue;
|
|
5261
|
+
const records = decodeRecords(entry.r);
|
|
5262
|
+
const tailRecords = decodeRecords(entry.t);
|
|
5263
|
+
if (records === null || tailRecords === null) continue;
|
|
5264
|
+
cache.set(path, {
|
|
5265
|
+
size: entry.s,
|
|
5266
|
+
mtimeMs: entry.m,
|
|
5267
|
+
records,
|
|
5268
|
+
tailRecords,
|
|
5269
|
+
position: { resumeOffset: entry.o, guardLength: entry.gl, guardHash: entry.gh, state }
|
|
5270
|
+
});
|
|
5271
|
+
}
|
|
5272
|
+
return cache;
|
|
5273
|
+
};
|
|
5274
|
+
var pruneScanCache = (cache, options) => {
|
|
5275
|
+
let removed = 0;
|
|
5276
|
+
for (const [path, entry] of cache) {
|
|
5277
|
+
const agedOut = entry.mtimeMs < options.retentionCutoffMs;
|
|
5278
|
+
const underWalkedRoot = options.walkedRoots.some((root) => {
|
|
5279
|
+
const relative2 = NodePath8.relative(root, path);
|
|
5280
|
+
return relative2 === "" || relative2 !== ".." && !relative2.startsWith(`..${NodePath8.sep}`) && !NodePath8.isAbsolute(relative2);
|
|
5281
|
+
});
|
|
5282
|
+
const deleted = underWalkedRoot && entry.mtimeMs >= options.windowStartMs && !options.livePaths.has(path);
|
|
5283
|
+
if (agedOut || deleted) {
|
|
5284
|
+
cache.delete(path);
|
|
5285
|
+
removed += 1;
|
|
5286
|
+
}
|
|
5287
|
+
}
|
|
5288
|
+
return removed;
|
|
5289
|
+
};
|
|
5290
|
+
|
|
5291
|
+
// src/usage/codexTokenUsage.ts
|
|
5292
|
+
var CACHE_RETENTION_DAYS = 90;
|
|
5293
|
+
var TRANSCRIPT_SUBDIRECTORIES = ["sessions", "archived_sessions"];
|
|
5294
|
+
var cacheKeyFor = (sharedHomePath) => `codex-rollouts:v1:${sharedHomePath}`;
|
|
5295
|
+
var homeStates = /* @__PURE__ */ new Map();
|
|
5296
|
+
var stateFor = (sharedHomePath) => {
|
|
5297
|
+
let state = homeStates.get(sharedHomePath);
|
|
5298
|
+
if (state === void 0) {
|
|
5299
|
+
state = { fileCache: /* @__PURE__ */ new Map(), loaded: null, dirty: false, chain: Promise.resolve() };
|
|
5300
|
+
homeStates.set(sharedHomePath, state);
|
|
5301
|
+
}
|
|
5302
|
+
return state;
|
|
5303
|
+
};
|
|
5304
|
+
var loadCache = (state, sharedHomePath, cache) => {
|
|
5305
|
+
state.loaded ??= (async () => {
|
|
5306
|
+
if (cache === void 0) return;
|
|
5307
|
+
const cached = await cache.getJson(cacheKeyFor(sharedHomePath)).catch(() => null);
|
|
5308
|
+
if (cached === null) return;
|
|
5309
|
+
for (const [path, entry] of decodeScanCache(cached.value)) state.fileCache.set(path, entry);
|
|
5310
|
+
})();
|
|
5311
|
+
return state.loaded;
|
|
5312
|
+
};
|
|
5313
|
+
var persistCache = async (state, sharedHomePath, cache) => {
|
|
5314
|
+
if (cache === void 0 || !state.dirty) return;
|
|
5315
|
+
try {
|
|
5316
|
+
await cache.setJson(cacheKeyFor(sharedHomePath), encodeScanCache(state.fileCache));
|
|
5317
|
+
state.dirty = false;
|
|
5318
|
+
} catch {
|
|
5319
|
+
}
|
|
5320
|
+
};
|
|
5321
|
+
var statDirectory = async (path) => {
|
|
5322
|
+
try {
|
|
5323
|
+
const stat2 = await NodeFS6.promises.stat(path, { bigint: true });
|
|
5324
|
+
if (!stat2.isDirectory()) return null;
|
|
5325
|
+
return { volumeId: stat2.ino === 0n ? "" : `${String(stat2.dev)}:${String(stat2.ino)}` };
|
|
5326
|
+
} catch {
|
|
5327
|
+
return null;
|
|
5328
|
+
}
|
|
5329
|
+
};
|
|
5330
|
+
var readFileRecords = async (state, filePath, size, mtimeMs) => {
|
|
5331
|
+
const cached = state.fileCache.get(filePath);
|
|
5332
|
+
if (cached !== void 0 && cached.size === size && cached.mtimeMs === mtimeMs) {
|
|
5333
|
+
return cached.tailRecords.length === 0 ? cached.records : [...cached.records, ...cached.tailRecords];
|
|
5334
|
+
}
|
|
5335
|
+
const resumeFrom = cached !== void 0 && size > cached.size ? cached.position : void 0;
|
|
5336
|
+
const parsed = await readCodexRollout(filePath, resumeFrom);
|
|
5337
|
+
if (parsed === null) return null;
|
|
5338
|
+
const records = parsed.resumed && cached !== void 0 ? [...cached.records, ...parsed.records] : parsed.records;
|
|
5339
|
+
state.fileCache.set(filePath, {
|
|
5340
|
+
size,
|
|
5341
|
+
mtimeMs,
|
|
5342
|
+
records,
|
|
5343
|
+
tailRecords: parsed.tailRecords,
|
|
5344
|
+
position: parsed.position
|
|
5345
|
+
});
|
|
5346
|
+
state.dirty = true;
|
|
5347
|
+
return parsed.tailRecords.length === 0 ? records : [...records, ...parsed.tailRecords];
|
|
5348
|
+
};
|
|
5349
|
+
var toRecord = (record) => ({
|
|
5350
|
+
at: new Date(record.timestampMs).toISOString(),
|
|
5351
|
+
model: record.model,
|
|
5352
|
+
...record.sessionId.length === 0 ? {} : { sessionId: record.sessionId },
|
|
5353
|
+
totals: record.totals
|
|
5354
|
+
});
|
|
5355
|
+
var readOnce = async (input) => {
|
|
5356
|
+
const sinceMs = Date.parse(input.since);
|
|
5357
|
+
const untilMs = Date.parse(input.until);
|
|
5358
|
+
if (Number.isNaN(sinceMs) || Number.isNaN(untilMs)) {
|
|
5359
|
+
throw new Error("readTokenUsage needs ISO 8601 since and until instants");
|
|
5360
|
+
}
|
|
5361
|
+
const state = stateFor(input.sharedHomePath);
|
|
5362
|
+
await loadCache(state, input.sharedHomePath, input.cache);
|
|
5363
|
+
const home = await statDirectory(input.sharedHomePath);
|
|
5364
|
+
const roots = [];
|
|
5365
|
+
for (const subdirectory of TRANSCRIPT_SUBDIRECTORIES) {
|
|
5366
|
+
const root = NodePath9.join(input.sharedHomePath, subdirectory);
|
|
5367
|
+
if (await statDirectory(root) !== null) roots.push(root);
|
|
5368
|
+
}
|
|
5369
|
+
if (home === null || roots.length === 0) {
|
|
5370
|
+
return {
|
|
5371
|
+
records: [],
|
|
5372
|
+
source: {
|
|
5373
|
+
homePath: input.sharedHomePath,
|
|
5374
|
+
status: "missing",
|
|
5375
|
+
scannedFiles: 0,
|
|
5376
|
+
skippedFiles: 0,
|
|
5377
|
+
message: "No Codex session directory on this machine."
|
|
5378
|
+
}
|
|
5379
|
+
};
|
|
5380
|
+
}
|
|
5381
|
+
const startedAtMs = Date.now();
|
|
5382
|
+
const records = [];
|
|
5383
|
+
const livePaths = /* @__PURE__ */ new Set();
|
|
5384
|
+
let scannedFiles = 0;
|
|
5385
|
+
let skippedFiles = 0;
|
|
5386
|
+
let failedFiles = 0;
|
|
5387
|
+
for (const root of roots) {
|
|
5388
|
+
for (const file of await listTranscriptFiles(root, sinceMs)) {
|
|
5389
|
+
if (input.signal?.aborted === true) throw new Error("aborted");
|
|
5390
|
+
livePaths.add(file.path);
|
|
5391
|
+
const fileRecords = await readFileRecords(state, file.path, file.size, file.mtimeMs);
|
|
5392
|
+
if (fileRecords === null) {
|
|
5393
|
+
failedFiles += 1;
|
|
5394
|
+
continue;
|
|
5395
|
+
}
|
|
5396
|
+
let inWindow = 0;
|
|
5397
|
+
for (const record of fileRecords) {
|
|
5398
|
+
if (record.timestampMs < sinceMs || record.timestampMs >= untilMs) continue;
|
|
5399
|
+
records.push(toRecord(record));
|
|
5400
|
+
inWindow += 1;
|
|
5401
|
+
}
|
|
5402
|
+
if (inWindow === 0) skippedFiles += 1;
|
|
5403
|
+
else scannedFiles += 1;
|
|
5404
|
+
}
|
|
5405
|
+
}
|
|
5406
|
+
const pruned = pruneScanCache(state.fileCache, {
|
|
5407
|
+
livePaths,
|
|
5408
|
+
walkedRoots: roots,
|
|
5409
|
+
windowStartMs: sinceMs,
|
|
5410
|
+
retentionCutoffMs: startedAtMs - CACHE_RETENTION_DAYS * 24 * 60 * 60 * 1e3
|
|
5411
|
+
});
|
|
5412
|
+
if (pruned > 0) state.dirty = true;
|
|
5413
|
+
await persistCache(state, input.sharedHomePath, input.cache);
|
|
5414
|
+
return {
|
|
5415
|
+
records,
|
|
5416
|
+
source: {
|
|
5417
|
+
homePath: input.sharedHomePath,
|
|
5418
|
+
...home.volumeId.length === 0 ? {} : { volumeId: home.volumeId },
|
|
5419
|
+
status: failedFiles > 0 ? "partial" : "ok",
|
|
5420
|
+
scannedFiles,
|
|
5421
|
+
skippedFiles,
|
|
5422
|
+
...failedFiles > 0 ? { message: `${String(failedFiles)} session file(s) could not be read.` } : {}
|
|
5423
|
+
}
|
|
5424
|
+
};
|
|
5425
|
+
};
|
|
5426
|
+
var readCodexTokenUsage = (input) => {
|
|
5427
|
+
const state = stateFor(input.sharedHomePath);
|
|
5428
|
+
const run = state.chain.then(
|
|
5429
|
+
() => readOnce(input),
|
|
5430
|
+
() => readOnce(input)
|
|
5431
|
+
);
|
|
5432
|
+
state.chain = run.catch(() => void 0);
|
|
5433
|
+
return run;
|
|
5434
|
+
};
|
|
5435
|
+
|
|
5436
|
+
// src/usage/codexUsage.ts
|
|
5437
|
+
var RATE_LIMITS_TIMEOUT_MS = 2e4;
|
|
5438
|
+
var CONSUME_TIMEOUT_MS = 3e4;
|
|
5439
|
+
var RESET_OUTCOMES = /* @__PURE__ */ new Set([
|
|
5440
|
+
"reset",
|
|
5441
|
+
"nothingToReset",
|
|
5442
|
+
"noCredit",
|
|
5443
|
+
"alreadyRedeemed"
|
|
5444
|
+
]);
|
|
5445
|
+
var createCodexUsage = (input) => {
|
|
5446
|
+
const { recipe, layout } = input;
|
|
5447
|
+
const accountHome = accountHomeFromLayout(layout);
|
|
5448
|
+
const endpointSignal = async () => {
|
|
5449
|
+
if (recipe.authMode === "apiKey") return "authMode=apiKey";
|
|
5450
|
+
const fromLaunchArgs = codexLaunchArgsSignal(recipe.launchArgs);
|
|
5451
|
+
if (fromLaunchArgs !== null) return fromLaunchArgs;
|
|
5452
|
+
let contents;
|
|
5453
|
+
try {
|
|
5454
|
+
contents = await NodeFS7.promises.readFile(NodePath10.join(accountHome, "config.toml"), "utf8");
|
|
5455
|
+
} catch (error) {
|
|
5456
|
+
const code = error.code;
|
|
5457
|
+
return code === "ENOENT" || code === "ENOTDIR" ? null : "config.toml:unreadable";
|
|
5458
|
+
}
|
|
5459
|
+
return codexConfigSignal(contents, { providerOverridden: recipe.authMode === "chatgpt" });
|
|
5460
|
+
};
|
|
5461
|
+
const readQuota = async ({ signal }) => {
|
|
5462
|
+
if (recipe.binaryPath.length === 0) {
|
|
5463
|
+
return { status: "unavailable", reason: "unreachable" };
|
|
5464
|
+
}
|
|
5465
|
+
if (await endpointSignal() !== null) {
|
|
5466
|
+
return { status: "unavailable", reason: "customEndpoint" };
|
|
5467
|
+
}
|
|
5468
|
+
if (layout.effectiveHomePath !== void 0 && !homeExists(accountHome)) {
|
|
5469
|
+
return { status: "unavailable", reason: "missingCredentials" };
|
|
5470
|
+
}
|
|
5471
|
+
let response;
|
|
5472
|
+
try {
|
|
5473
|
+
response = await input.runAppServer(
|
|
5474
|
+
(rpc) => rpc.request("account/rateLimits/read", void 0, RATE_LIMITS_TIMEOUT_MS),
|
|
5475
|
+
signal
|
|
5476
|
+
);
|
|
5477
|
+
} catch (error) {
|
|
5478
|
+
if (signal?.aborted === true) throw error;
|
|
5479
|
+
return {
|
|
5480
|
+
status: "unavailable",
|
|
5481
|
+
reason: error instanceof CodexRpcResponseError ? classifyRateLimitsError(error.message) : "unreachable"
|
|
5482
|
+
};
|
|
5483
|
+
}
|
|
5484
|
+
return mapCodexRateLimits(response, NodePath10.join(accountHome, "auth.json")) ?? {
|
|
5485
|
+
status: "unavailable",
|
|
5486
|
+
reason: "unreachable"
|
|
5487
|
+
};
|
|
5488
|
+
};
|
|
5489
|
+
const readTokenUsage = (request) => readCodexTokenUsage({
|
|
5490
|
+
sharedHomePath: layout.sharedHomePath,
|
|
5491
|
+
cache: input.cache,
|
|
5492
|
+
since: request.since,
|
|
5493
|
+
until: request.until,
|
|
5494
|
+
signal: request.signal
|
|
5495
|
+
});
|
|
5496
|
+
const redeemQuotaReset = async ({
|
|
5497
|
+
idempotencyKey,
|
|
5498
|
+
signal
|
|
5499
|
+
}) => {
|
|
5500
|
+
if (await endpointSignal() !== null) {
|
|
5501
|
+
throw new Error("Quota reset is not available for an instance with its own model endpoint");
|
|
5502
|
+
}
|
|
5503
|
+
const response = await input.runAppServer(
|
|
5504
|
+
(rpc) => rpc.request("account/rateLimitResetCredit/consume", { idempotencyKey }, CONSUME_TIMEOUT_MS),
|
|
5505
|
+
signal
|
|
5506
|
+
);
|
|
5507
|
+
const outcome = response?.outcome;
|
|
5508
|
+
if (typeof outcome !== "string" || !RESET_OUTCOMES.has(outcome)) {
|
|
5509
|
+
throw new Error(`Codex returned an unrecognised reset outcome: ${String(outcome)}`);
|
|
5510
|
+
}
|
|
5511
|
+
return { outcome };
|
|
5512
|
+
};
|
|
5513
|
+
return { readQuota, readTokenUsage, redeemQuotaReset };
|
|
5514
|
+
};
|
|
5515
|
+
|
|
4542
5516
|
// src/codexInstance.ts
|
|
4543
5517
|
function missingBinaryDetail(binaryPath) {
|
|
4544
5518
|
const relyOnPath = !/[\\/]/.test(binaryPath);
|
|
@@ -4601,7 +5575,7 @@ function layoutFor(recipe) {
|
|
|
4601
5575
|
...environmentHome === void 0 ? {} : { environmentHome }
|
|
4602
5576
|
});
|
|
4603
5577
|
}
|
|
4604
|
-
var createCodexInstance = (config, host, attachments) => {
|
|
5578
|
+
var createCodexInstance = (config, host, attachments, cache) => {
|
|
4605
5579
|
const recipe = parseCodexInstanceConfig(config);
|
|
4606
5580
|
const events = new EventBus();
|
|
4607
5581
|
const threads = /* @__PURE__ */ new Map();
|
|
@@ -4665,6 +5639,58 @@ var createCodexInstance = (config, host, attachments) => {
|
|
|
4665
5639
|
await handle?.kill("SIGTERM").catch(() => void 0);
|
|
4666
5640
|
}
|
|
4667
5641
|
};
|
|
5642
|
+
const runAppServer = async (run, signal) => {
|
|
5643
|
+
if (disposed) throw new Error("instance disposed");
|
|
5644
|
+
if (signal?.aborted === true) throw new Error("aborted");
|
|
5645
|
+
const layout = layoutFor(recipe);
|
|
5646
|
+
const env = buildCodexSpawnEnv({
|
|
5647
|
+
recipe,
|
|
5648
|
+
layout,
|
|
5649
|
+
crushApiKeys: shouldCrushApiKeys(recipe.authMode)
|
|
5650
|
+
});
|
|
5651
|
+
let handle;
|
|
5652
|
+
try {
|
|
5653
|
+
handle = await host.process.spawn({
|
|
5654
|
+
program: recipe.binaryPath,
|
|
5655
|
+
args: [...codexSpawnArgs(recipe, layout)],
|
|
5656
|
+
owner: { bindingId: host.bindingId },
|
|
5657
|
+
...Object.keys(env).length > 0 ? { env } : {}
|
|
5658
|
+
});
|
|
5659
|
+
} catch (error) {
|
|
5660
|
+
if (error instanceof ExecutorSpawnRefusedError) {
|
|
5661
|
+
const borrowed = [...threads.values()][0];
|
|
5662
|
+
if (borrowed !== void 0) return await run(borrowed.rpc);
|
|
5663
|
+
}
|
|
5664
|
+
throw error;
|
|
5665
|
+
}
|
|
5666
|
+
trackHandle(handle, `${recipe.binaryPath} (usage)`);
|
|
5667
|
+
let rpc;
|
|
5668
|
+
let onAbort;
|
|
5669
|
+
const aborted = new Promise((_, reject) => {
|
|
5670
|
+
onAbort = () => reject(new Error("aborted"));
|
|
5671
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
5672
|
+
});
|
|
5673
|
+
try {
|
|
5674
|
+
rpc = await createCodexRpcClient(handle, {
|
|
5675
|
+
onNotification: () => void 0,
|
|
5676
|
+
onServerRequest: () => void 0,
|
|
5677
|
+
onStderrLine: () => void 0
|
|
5678
|
+
});
|
|
5679
|
+
const connected = rpc;
|
|
5680
|
+
return await Promise.race([
|
|
5681
|
+
(async () => {
|
|
5682
|
+
await connected.request("initialize", CODEX_INITIALIZE_PARAMS);
|
|
5683
|
+
await connected.notify("initialized");
|
|
5684
|
+
return await run(connected);
|
|
5685
|
+
})(),
|
|
5686
|
+
aborted
|
|
5687
|
+
]);
|
|
5688
|
+
} finally {
|
|
5689
|
+
if (onAbort !== void 0) signal?.removeEventListener("abort", onAbort);
|
|
5690
|
+
await rpc?.close().catch(() => void 0);
|
|
5691
|
+
await handle.kill("SIGTERM").catch(() => void 0);
|
|
5692
|
+
}
|
|
5693
|
+
};
|
|
4668
5694
|
const trackHandle = (handle, command, threadId) => {
|
|
4669
5695
|
live.set(handle.pid, { command, ...threadId === void 0 ? {} : { threadId } });
|
|
4670
5696
|
void handle.exited.finally(() => {
|
|
@@ -4873,6 +5899,15 @@ var createCodexInstance = (config, host, attachments) => {
|
|
|
4873
5899
|
await Promise.all(booked.map((entry) => stopBooked(entry)));
|
|
4874
5900
|
events.close();
|
|
4875
5901
|
},
|
|
5902
|
+
// 项目里的 repo 级技能:探测的 app-server 没有工作区,只报得出用户与系统级;这里按目录问
|
|
5903
|
+
// 一次性 app-server(与读额度同一条进程通道),宿主缓存结果。
|
|
5904
|
+
workspaceInventory: ({ cwd, signal }) => runAppServer((rpc) => codexWorkspaceInventory(rpc, cwd), signal),
|
|
5905
|
+
usage: createCodexUsage({
|
|
5906
|
+
recipe,
|
|
5907
|
+
layout: layoutFor(recipe),
|
|
5908
|
+
cache,
|
|
5909
|
+
runAppServer
|
|
5910
|
+
}),
|
|
4876
5911
|
textGeneration: createCodexTextGeneration({
|
|
4877
5912
|
host,
|
|
4878
5913
|
recipe,
|
|
@@ -5045,12 +6080,13 @@ var createCodexInstance = (config, host, attachments) => {
|
|
|
5045
6080
|
|
|
5046
6081
|
// src/index.ts
|
|
5047
6082
|
var capturedAttachments;
|
|
6083
|
+
var capturedCache;
|
|
5048
6084
|
var executors = [
|
|
5049
6085
|
{
|
|
5050
6086
|
card: CODEX_CARD,
|
|
5051
6087
|
createInstance: async (config, host) => {
|
|
5052
6088
|
host.log.info("createInstance");
|
|
5053
|
-
return createCodexInstance(config, host, capturedAttachments);
|
|
6089
|
+
return createCodexInstance(config, host, capturedAttachments, capturedCache);
|
|
5054
6090
|
},
|
|
5055
6091
|
probeAccount: async ({ accountPath }) => probeCodexAccount(accountPath)
|
|
5056
6092
|
}
|
|
@@ -5061,6 +6097,7 @@ var index_default = definePlugin({
|
|
|
5061
6097
|
executors,
|
|
5062
6098
|
activate(ctx) {
|
|
5063
6099
|
capturedAttachments = ctx.attachments;
|
|
6100
|
+
capturedCache = ctx.cache;
|
|
5064
6101
|
ctx.log.info("codex executor ready");
|
|
5065
6102
|
}
|
|
5066
6103
|
});
|