@evident-ai/cli 3.4.1-dev.74a16b2 → 3.4.1-dev.77d5cb6
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 +10 -1
- package/dist/index.js +617 -75
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -722,6 +722,14 @@ function toReportedWindow(window) {
|
|
|
722
722
|
if (!window) return null;
|
|
723
723
|
return { utilization: window.utilization, resets_at: window.resetsAt };
|
|
724
724
|
}
|
|
725
|
+
function toReportedOwner(snapshot) {
|
|
726
|
+
if (!snapshot.owner) return null;
|
|
727
|
+
return {
|
|
728
|
+
email: snapshot.owner.email,
|
|
729
|
+
organization_name: snapshot.owner.organizationName,
|
|
730
|
+
rate_limit_tier: snapshot.owner.rateLimitTier
|
|
731
|
+
};
|
|
732
|
+
}
|
|
725
733
|
async function reportClaudeUsage(agentId, authHeader, snapshot) {
|
|
726
734
|
try {
|
|
727
735
|
const apiUrl = getApiUrlConfig();
|
|
@@ -730,7 +738,42 @@ async function reportClaudeUsage(agentId, authHeader, snapshot) {
|
|
|
730
738
|
headers: { Authorization: authHeader, "Content-Type": "application/json" },
|
|
731
739
|
body: JSON.stringify({
|
|
732
740
|
five_hour: toReportedWindow(snapshot.fiveHour),
|
|
733
|
-
seven_day: toReportedWindow(snapshot.sevenDay)
|
|
741
|
+
seven_day: toReportedWindow(snapshot.sevenDay),
|
|
742
|
+
owner: toReportedOwner(snapshot)
|
|
743
|
+
}),
|
|
744
|
+
signal: AbortSignal.timeout(BEST_EFFORT_NOTIFY_TIMEOUT_MS)
|
|
745
|
+
});
|
|
746
|
+
if (!response.ok) {
|
|
747
|
+
const serverMessage = await readErrorMessage(response);
|
|
748
|
+
return {
|
|
749
|
+
ok: false,
|
|
750
|
+
error: `HTTP ${response.status}${serverMessage ? `: ${serverMessage}` : ""}`
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
return { ok: true };
|
|
754
|
+
} catch (error2) {
|
|
755
|
+
return { ok: false, error: describeBestEffortError(error2) };
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
function toReportedOpenAiWindow(window) {
|
|
759
|
+
if (!window) return null;
|
|
760
|
+
return {
|
|
761
|
+
utilization: window.utilization,
|
|
762
|
+
window_minutes: window.windowMinutes,
|
|
763
|
+
resets_at: window.resetsAt
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
async function reportOpenAiUsage(agentId, authHeader, snapshot) {
|
|
767
|
+
try {
|
|
768
|
+
const apiUrl = getApiUrlConfig();
|
|
769
|
+
const response = await fetch(`${apiUrl}/runners/${agentId}/openai-usage`, {
|
|
770
|
+
method: "POST",
|
|
771
|
+
headers: { Authorization: authHeader, "Content-Type": "application/json" },
|
|
772
|
+
body: JSON.stringify({
|
|
773
|
+
primary: toReportedOpenAiWindow(snapshot.primary),
|
|
774
|
+
secondary: toReportedOpenAiWindow(snapshot.secondary),
|
|
775
|
+
has_credits: snapshot.hasCredits,
|
|
776
|
+
credits_unlimited: snapshot.creditsUnlimited
|
|
734
777
|
}),
|
|
735
778
|
signal: AbortSignal.timeout(BEST_EFFORT_NOTIFY_TIMEOUT_MS)
|
|
736
779
|
});
|
|
@@ -962,7 +1005,10 @@ import { readFileSync } from "fs";
|
|
|
962
1005
|
import { homedir } from "os";
|
|
963
1006
|
import { join } from "path";
|
|
964
1007
|
var CLAUDE_USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
|
|
1008
|
+
var CLAUDE_PROFILE_URL = "https://api.anthropic.com/api/oauth/profile";
|
|
1009
|
+
var CLAUDE_PROFILE_TIMEOUT_MS = 2e3;
|
|
965
1010
|
var KEYCHAIN_SERVICE = "Claude Code-credentials";
|
|
1011
|
+
var cachedOwner = null;
|
|
966
1012
|
var CLAUDE_CREDENTIALS_SEGMENTS = [".claude", ".credentials.json"];
|
|
967
1013
|
function parseClaudeCliCredentials(raw) {
|
|
968
1014
|
let parsed;
|
|
@@ -1036,6 +1082,47 @@ function toWindow(value) {
|
|
|
1036
1082
|
}
|
|
1037
1083
|
return { utilization: window.utilization, resetsAt };
|
|
1038
1084
|
}
|
|
1085
|
+
function ownerLookupFailure(error2) {
|
|
1086
|
+
const name = error2?.name;
|
|
1087
|
+
return name === "TimeoutError" || name === "AbortError" ? "timed out" : "request failed";
|
|
1088
|
+
}
|
|
1089
|
+
async function getClaudeUsageOwner(accessToken) {
|
|
1090
|
+
if (cachedOwner?.accessToken === accessToken) {
|
|
1091
|
+
return { owner: cachedOwner.owner, ownerLookupError: null };
|
|
1092
|
+
}
|
|
1093
|
+
try {
|
|
1094
|
+
const response = await fetch(CLAUDE_PROFILE_URL, {
|
|
1095
|
+
headers: {
|
|
1096
|
+
Authorization: `Bearer ${accessToken}`,
|
|
1097
|
+
"Content-Type": "application/json",
|
|
1098
|
+
"anthropic-version": "2023-06-01"
|
|
1099
|
+
},
|
|
1100
|
+
signal: AbortSignal.timeout(CLAUDE_PROFILE_TIMEOUT_MS)
|
|
1101
|
+
});
|
|
1102
|
+
if (!response.ok) {
|
|
1103
|
+
return { owner: null, ownerLookupError: `HTTP ${response.status}` };
|
|
1104
|
+
}
|
|
1105
|
+
let body;
|
|
1106
|
+
try {
|
|
1107
|
+
body = await response.json();
|
|
1108
|
+
} catch (error2) {
|
|
1109
|
+
return { owner: null, ownerLookupError: "malformed response" };
|
|
1110
|
+
}
|
|
1111
|
+
const profile = body;
|
|
1112
|
+
if (typeof profile.account?.email !== "string" || !profile.account.email.trim()) {
|
|
1113
|
+
return { owner: null, ownerLookupError: "malformed response" };
|
|
1114
|
+
}
|
|
1115
|
+
const owner = {
|
|
1116
|
+
email: profile.account.email,
|
|
1117
|
+
organizationName: typeof profile.organization?.name === "string" && profile.organization.name.trim() ? profile.organization.name.trim() : null,
|
|
1118
|
+
rateLimitTier: typeof profile.organization?.rate_limit_tier === "string" && profile.organization.rate_limit_tier.trim() ? profile.organization.rate_limit_tier.trim() : null
|
|
1119
|
+
};
|
|
1120
|
+
cachedOwner = { accessToken, owner };
|
|
1121
|
+
return { owner, ownerLookupError: null };
|
|
1122
|
+
} catch (error2) {
|
|
1123
|
+
return { owner: null, ownerLookupError: ownerLookupFailure(error2) };
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1039
1126
|
async function getClaudeUsage() {
|
|
1040
1127
|
const credentials2 = readClaudeCliCredentials();
|
|
1041
1128
|
if (!credentials2) {
|
|
@@ -1055,15 +1142,19 @@ async function getClaudeUsage() {
|
|
|
1055
1142
|
Authorization: `Bearer ${credentials2.accessToken}`,
|
|
1056
1143
|
"Content-Type": "application/json",
|
|
1057
1144
|
"anthropic-version": "2023-06-01"
|
|
1058
|
-
}
|
|
1145
|
+
},
|
|
1146
|
+
signal: AbortSignal.timeout(CLAUDE_PROFILE_TIMEOUT_MS)
|
|
1059
1147
|
});
|
|
1060
1148
|
if (!res.ok) {
|
|
1061
1149
|
throw new ClaudeUsageError(`Claude usage request failed: HTTP ${res.status}`, "request_failed");
|
|
1062
1150
|
}
|
|
1063
1151
|
const body = await res.json();
|
|
1152
|
+
const { owner, ownerLookupError } = await getClaudeUsageOwner(credentials2.accessToken);
|
|
1064
1153
|
return {
|
|
1065
1154
|
fiveHour: toWindow(body.five_hour),
|
|
1066
|
-
sevenDay: toWindow(body.seven_day)
|
|
1155
|
+
sevenDay: toWindow(body.seven_day),
|
|
1156
|
+
owner,
|
|
1157
|
+
ownerLookupError
|
|
1067
1158
|
};
|
|
1068
1159
|
}
|
|
1069
1160
|
|
|
@@ -1092,8 +1183,8 @@ async function claudeUsage() {
|
|
|
1092
1183
|
}
|
|
1093
1184
|
|
|
1094
1185
|
// src/commands/run.ts
|
|
1095
|
-
import { homedir as
|
|
1096
|
-
import { isAbsolute as isAbsolute2, join as
|
|
1186
|
+
import { homedir as homedir4 } from "os";
|
|
1187
|
+
import { isAbsolute as isAbsolute2, join as join7, parse, resolve as resolvePath } from "path";
|
|
1097
1188
|
import chalk6 from "chalk";
|
|
1098
1189
|
|
|
1099
1190
|
// ../../packages/types/src/agents/index.ts
|
|
@@ -1326,6 +1417,8 @@ var SEVERITY_BY_LEVEL = {
|
|
|
1326
1417
|
error: "error"
|
|
1327
1418
|
};
|
|
1328
1419
|
var MAX_MESSAGE_LENGTH = 500;
|
|
1420
|
+
var MAX_METADATA_VALUE_LENGTH = 200;
|
|
1421
|
+
var MAX_METADATA_ENTRIES = 20;
|
|
1329
1422
|
var TRUNCATION_MARKER = "\u2026";
|
|
1330
1423
|
function redact(message) {
|
|
1331
1424
|
return message.replace(/esk_[A-Za-z0-9_-]+/g, "esk_***").replace(/ct_[A-Za-z0-9_-]+/g, "ct_***").replace(/https?:\/\/\S+/g, "<url>");
|
|
@@ -1334,6 +1427,24 @@ function truncate(message) {
|
|
|
1334
1427
|
if (message.length <= MAX_MESSAGE_LENGTH) return message;
|
|
1335
1428
|
return message.slice(0, MAX_MESSAGE_LENGTH - TRUNCATION_MARKER.length) + TRUNCATION_MARKER;
|
|
1336
1429
|
}
|
|
1430
|
+
function sanitiseMetadata(metadata) {
|
|
1431
|
+
if (!metadata) return {};
|
|
1432
|
+
const entries = Object.entries(metadata).slice(0, MAX_METADATA_ENTRIES);
|
|
1433
|
+
if (Object.keys(metadata).length > entries.length) {
|
|
1434
|
+
console.error(
|
|
1435
|
+
`[runner-activity-telemetry] dropped ${Object.keys(metadata).length - entries.length} metadata entries`
|
|
1436
|
+
);
|
|
1437
|
+
}
|
|
1438
|
+
const sanitised = [];
|
|
1439
|
+
for (const [key, value] of entries) {
|
|
1440
|
+
if (typeof value === "string") {
|
|
1441
|
+
sanitised.push([key, truncate(redact(value).slice(0, MAX_METADATA_VALUE_LENGTH))]);
|
|
1442
|
+
} else if (value === null || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value)) {
|
|
1443
|
+
sanitised.push([key, value]);
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
return Object.fromEntries(sanitised);
|
|
1447
|
+
}
|
|
1337
1448
|
var RATE_LIMIT_WINDOW_MS = 6e4;
|
|
1338
1449
|
var RATE_LIMIT_MAX_EVENTS = 30;
|
|
1339
1450
|
var windowStartedAt = 0;
|
|
@@ -1372,7 +1483,7 @@ function forwardRunnerActivity(entry, context) {
|
|
|
1372
1483
|
logEvent(TelemetryEventTypes.RUNNER_ACTIVITY, {
|
|
1373
1484
|
severity: SEVERITY_BY_LEVEL[entry.level],
|
|
1374
1485
|
message,
|
|
1375
|
-
metadata: { source: "cli.run" },
|
|
1486
|
+
metadata: { ...sanitiseMetadata(entry.metadata), source: "cli.run" },
|
|
1376
1487
|
agentId: context.agentId
|
|
1377
1488
|
});
|
|
1378
1489
|
} catch (err) {
|
|
@@ -1382,6 +1493,150 @@ function forwardRunnerActivity(entry, context) {
|
|
|
1382
1493
|
}
|
|
1383
1494
|
}
|
|
1384
1495
|
|
|
1496
|
+
// src/lib/opencode/session-db-recovery-report.ts
|
|
1497
|
+
import { readFileSync as readFileSync2, unlinkSync } from "fs";
|
|
1498
|
+
import { join as join2 } from "path";
|
|
1499
|
+
function sessionDbRecoveryReportPath(homeDir, env) {
|
|
1500
|
+
const override = env.EVIDENT_SESSION_DB_RECOVERY_REPORT?.trim();
|
|
1501
|
+
return override || join2(homeDir, ".local", "state", "evident", "session-db-recovery.jsonl");
|
|
1502
|
+
}
|
|
1503
|
+
function drainSessionDbRecoveryReport({
|
|
1504
|
+
homeDir,
|
|
1505
|
+
env
|
|
1506
|
+
}) {
|
|
1507
|
+
const path = sessionDbRecoveryReportPath(homeDir, env);
|
|
1508
|
+
let content;
|
|
1509
|
+
try {
|
|
1510
|
+
content = readFileSync2(path, "utf8");
|
|
1511
|
+
} catch (error2) {
|
|
1512
|
+
if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT")
|
|
1513
|
+
return { path, records: [], skippedLines: 0, readError: null };
|
|
1514
|
+
const readError = error2 instanceof Error ? error2.message : String(error2);
|
|
1515
|
+
console.error(`[session-db-recovery-report] could not read ${path}: ${readError}`);
|
|
1516
|
+
return { path, records: [], skippedLines: 0, readError };
|
|
1517
|
+
}
|
|
1518
|
+
let skippedLines = 0;
|
|
1519
|
+
const records = content.split("\n").flatMap((line) => {
|
|
1520
|
+
if (!line.trim()) return [];
|
|
1521
|
+
try {
|
|
1522
|
+
const value = JSON.parse(line);
|
|
1523
|
+
if (!isSessionDbRecoveryRecord(value)) {
|
|
1524
|
+
skippedLines++;
|
|
1525
|
+
return [];
|
|
1526
|
+
}
|
|
1527
|
+
return [value];
|
|
1528
|
+
} catch (error2) {
|
|
1529
|
+
skippedLines++;
|
|
1530
|
+
console.error(
|
|
1531
|
+
`[session-db-recovery-report] skipped malformed record in ${path}: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
1532
|
+
);
|
|
1533
|
+
return [];
|
|
1534
|
+
}
|
|
1535
|
+
});
|
|
1536
|
+
return { path, records, skippedLines, readError: null };
|
|
1537
|
+
}
|
|
1538
|
+
function acknowledgeSessionDbRecoveryReport(path) {
|
|
1539
|
+
try {
|
|
1540
|
+
unlinkSync(path);
|
|
1541
|
+
} catch (error2) {
|
|
1542
|
+
if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT") return;
|
|
1543
|
+
console.error(
|
|
1544
|
+
`[session-db-recovery-report] could not remove ${path}: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
1545
|
+
);
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
function buildSessionDbRecoveryActivity(record) {
|
|
1549
|
+
const level = record.severity === "warning" ? "warn" : record.severity === "error" ? "error" : null;
|
|
1550
|
+
if (!level) return null;
|
|
1551
|
+
const noVerifiedPoint = record.verified_restore_point ? ` The verified restore point is ${record.verified_restore_point}.` : " No verified restore point is known.";
|
|
1552
|
+
switch (record.outcome) {
|
|
1553
|
+
case "fresh_session_db":
|
|
1554
|
+
return {
|
|
1555
|
+
level,
|
|
1556
|
+
metadata: withoutContractFields(record),
|
|
1557
|
+
message: `This runner started with a fresh session database. Earlier sessions are unavailable, and queued conversation or session references from before this start may no longer resolve.${noVerifiedPoint} Review the runner's backup configuration before relying on restored session history.`
|
|
1558
|
+
};
|
|
1559
|
+
case "restore_retried":
|
|
1560
|
+
return {
|
|
1561
|
+
level,
|
|
1562
|
+
metadata: withoutContractFields(record),
|
|
1563
|
+
message: "The initial session database restore failed; the runner will retry it. Monitor the runner backup for another restore failure."
|
|
1564
|
+
};
|
|
1565
|
+
case "replica_recovered":
|
|
1566
|
+
if (record.reason === "quarantine")
|
|
1567
|
+
return {
|
|
1568
|
+
level,
|
|
1569
|
+
metadata: withoutContractFields(record),
|
|
1570
|
+
message: `Session database recovery quarantined ${record.quarantined_objects ?? "unknown"} objects (${record.quarantined_bytes ?? "unknown"} bytes); ${record.quarantine_failed_objects ?? "unknown"} moves failed. Review the preserved backup at ${record.quarantine_destination ?? "an unknown destination"} before deleting it.`
|
|
1571
|
+
};
|
|
1572
|
+
if (record.reason === "prune")
|
|
1573
|
+
return {
|
|
1574
|
+
level,
|
|
1575
|
+
metadata: withoutContractFields(record),
|
|
1576
|
+
message: "Session database recovery discarded a damaged newest backup and retried. Sessions recorded after the previous backup point may be unavailable. Review the runner backup for another restore failure."
|
|
1577
|
+
};
|
|
1578
|
+
if (record.reason === "clear")
|
|
1579
|
+
return {
|
|
1580
|
+
level,
|
|
1581
|
+
metadata: withoutContractFields(record),
|
|
1582
|
+
message: "Session database recovery deleted the damaged backup and prior session history is unavailable. Review the runner backup configuration before relying on restored session history."
|
|
1583
|
+
};
|
|
1584
|
+
return null;
|
|
1585
|
+
case "history_rolled_back":
|
|
1586
|
+
return {
|
|
1587
|
+
level,
|
|
1588
|
+
metadata: withoutContractFields(record),
|
|
1589
|
+
message: `Session history was rolled back to verified restore point ${record.verified_restore_point ?? "unknown"}; everything after it is unavailable. Review the runner backup for another restore failure.`
|
|
1590
|
+
};
|
|
1591
|
+
case "restore_misconfigured":
|
|
1592
|
+
return {
|
|
1593
|
+
level,
|
|
1594
|
+
metadata: withoutContractFields(record),
|
|
1595
|
+
message: "This runner started with a fresh session database because it could not verify where its session history is backed up. Nothing in the backup was changed; check the runner backup configuration and permissions."
|
|
1596
|
+
};
|
|
1597
|
+
case "session_db_boot_refused":
|
|
1598
|
+
return {
|
|
1599
|
+
level,
|
|
1600
|
+
metadata: withoutContractFields(record),
|
|
1601
|
+
message: `This runner did not come online because its damaged session database could not be safely separated from its active backup or proven removed. Backed-up session history remains readable at ${record.quarantine_destination ?? "its original location or the quarantine destination named in the boot logs"}; any local session-database files that remain were left in place and nothing opened or wrote them. See the runner boot logs for SESSION-DB-LOCAL-DISCARD-FAILED details.`
|
|
1602
|
+
};
|
|
1603
|
+
default:
|
|
1604
|
+
return null;
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
function withoutContractFields(record) {
|
|
1608
|
+
const { v: _v, event: _event, ...metadata } = record;
|
|
1609
|
+
return metadata;
|
|
1610
|
+
}
|
|
1611
|
+
var OUTCOMES = /* @__PURE__ */ new Set([
|
|
1612
|
+
"replica_recovered",
|
|
1613
|
+
"restore_retried",
|
|
1614
|
+
"fresh_session_db",
|
|
1615
|
+
"history_rolled_back",
|
|
1616
|
+
"restore_misconfigured",
|
|
1617
|
+
"session_db_boot_refused"
|
|
1618
|
+
]);
|
|
1619
|
+
var STAGES = /* @__PURE__ */ new Set(["restore", "verify"]);
|
|
1620
|
+
var SEVERITIES = /* @__PURE__ */ new Set(["warning", "error"]);
|
|
1621
|
+
var NUMBER_FIELDS = [
|
|
1622
|
+
"litestream_exit_code",
|
|
1623
|
+
"attempt",
|
|
1624
|
+
"replica_objects",
|
|
1625
|
+
"replica_bytes",
|
|
1626
|
+
"quarantined_objects",
|
|
1627
|
+
"quarantine_failed_objects",
|
|
1628
|
+
"quarantined_bytes",
|
|
1629
|
+
"restore_points_tried"
|
|
1630
|
+
];
|
|
1631
|
+
var STRING_OR_NULL_FIELDS = ["quarantine_destination", "verified_restore_point"];
|
|
1632
|
+
function isSessionDbRecoveryRecord(value) {
|
|
1633
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
1634
|
+
const record = value;
|
|
1635
|
+
return record.v === 1 && record.event === "session_db_recovery" && typeof record.at === "string" && STAGES.has(record.stage) && OUTCOMES.has(record.outcome) && SEVERITIES.has(record.severity) && typeof record.reason === "string" && NUMBER_FIELDS.every((field) => record[field] === null || Number.isInteger(record[field])) && STRING_OR_NULL_FIELDS.every(
|
|
1636
|
+
(field) => record[field] === null || typeof record[field] === "string"
|
|
1637
|
+
);
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1385
1640
|
// src/lib/opencode/health.ts
|
|
1386
1641
|
async function checkOpenCodeHealth(port) {
|
|
1387
1642
|
try {
|
|
@@ -2419,10 +2674,10 @@ function resolveSessionCleanupConfig(flags, env = process.env) {
|
|
|
2419
2674
|
|
|
2420
2675
|
// src/lib/opencode/session-db-size.ts
|
|
2421
2676
|
import { statSync as statSync2 } from "fs";
|
|
2422
|
-
import { join as
|
|
2677
|
+
import { join as join3 } from "path";
|
|
2423
2678
|
var LARGE_DB_THRESHOLD_BYTES = 268435456;
|
|
2424
2679
|
function statSessionDbBytes(homeDir) {
|
|
2425
|
-
const dbPath =
|
|
2680
|
+
const dbPath = join3(homeDir, ".local", "share", "opencode", "opencode.db");
|
|
2426
2681
|
try {
|
|
2427
2682
|
return statSync2(dbPath).size;
|
|
2428
2683
|
} catch (err) {
|
|
@@ -2991,6 +3246,177 @@ function writeTunnelReadyMarker(path, agentId) {
|
|
|
2991
3246
|
}
|
|
2992
3247
|
}
|
|
2993
3248
|
|
|
3249
|
+
// src/lib/openai-usage.ts
|
|
3250
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
3251
|
+
import { homedir as homedir2 } from "os";
|
|
3252
|
+
import { join as join4 } from "path";
|
|
3253
|
+
var CODEX_RESPONSES_URL = "https://chatgpt.com/backend-api/codex/responses";
|
|
3254
|
+
var OPENCODE_AUTH_SEGMENTS = [".local", "share", "opencode", "auth.json"];
|
|
3255
|
+
var OpenAiUsageError = class extends Error {
|
|
3256
|
+
constructor(message, reason) {
|
|
3257
|
+
super(message);
|
|
3258
|
+
this.reason = reason;
|
|
3259
|
+
}
|
|
3260
|
+
};
|
|
3261
|
+
function isLocalCredentialProblem2(err) {
|
|
3262
|
+
return err instanceof OpenAiUsageError && (err.reason === "no_credentials" || err.reason === "credentials_expired");
|
|
3263
|
+
}
|
|
3264
|
+
function readOpenCodeChatGptCredentials() {
|
|
3265
|
+
try {
|
|
3266
|
+
const raw = readFileSync3(join4(homedir2(), ...OPENCODE_AUTH_SEGMENTS), "utf-8");
|
|
3267
|
+
let parsed;
|
|
3268
|
+
try {
|
|
3269
|
+
parsed = JSON.parse(raw);
|
|
3270
|
+
} catch {
|
|
3271
|
+
return null;
|
|
3272
|
+
}
|
|
3273
|
+
const entry = parsed.openai;
|
|
3274
|
+
if (entry?.type !== "oauth" || typeof entry.access !== "string" || !entry.access || typeof entry.expires !== "number") {
|
|
3275
|
+
return null;
|
|
3276
|
+
}
|
|
3277
|
+
return { accessToken: entry.access, expiresAt: entry.expires };
|
|
3278
|
+
} catch (err) {
|
|
3279
|
+
const code = err.code;
|
|
3280
|
+
if (code !== "ENOENT" && code !== "ENOTDIR") {
|
|
3281
|
+
console.warn(
|
|
3282
|
+
`readOpenCodeChatGptCredentials: reading auth.json failed (${code ?? "unknown"})`
|
|
3283
|
+
);
|
|
3284
|
+
}
|
|
3285
|
+
return null;
|
|
3286
|
+
}
|
|
3287
|
+
}
|
|
3288
|
+
function toWindow2(headers, name) {
|
|
3289
|
+
const utilizationHeader = headers.get(`x-codex-${name}-used-percent`);
|
|
3290
|
+
const windowMinutesHeader = headers.get(`x-codex-${name}-window-minutes`);
|
|
3291
|
+
if (utilizationHeader == null || utilizationHeader === "" || windowMinutesHeader == null) {
|
|
3292
|
+
return null;
|
|
3293
|
+
}
|
|
3294
|
+
const utilization = Number(utilizationHeader);
|
|
3295
|
+
const windowMinutes = Number(windowMinutesHeader);
|
|
3296
|
+
if (!Number.isFinite(utilization) || !Number.isFinite(windowMinutes) || windowMinutes <= 0) {
|
|
3297
|
+
return null;
|
|
3298
|
+
}
|
|
3299
|
+
const resetAtHeader = headers.get(`x-codex-${name}-reset-at`);
|
|
3300
|
+
const resetSeconds = resetAtHeader == null || resetAtHeader === "" ? NaN : Number(resetAtHeader);
|
|
3301
|
+
const resetsAt = Number.isFinite(resetSeconds) ? new Date(resetSeconds * 1e3).toISOString() : null;
|
|
3302
|
+
return { utilization: Math.min(100, Math.max(0, utilization)), windowMinutes, resetsAt };
|
|
3303
|
+
}
|
|
3304
|
+
function parseCodexUsageHeaders(headers) {
|
|
3305
|
+
return {
|
|
3306
|
+
primary: toWindow2(headers, "primary"),
|
|
3307
|
+
secondary: toWindow2(headers, "secondary"),
|
|
3308
|
+
hasCredits: headers.has("x-codex-credits-has-credits") ? headers.get("x-codex-credits-has-credits")?.toLowerCase() === "true" : null,
|
|
3309
|
+
creditsUnlimited: headers.has("x-codex-credits-unlimited") ? headers.get("x-codex-credits-unlimited")?.toLowerCase() === "true" : null
|
|
3310
|
+
};
|
|
3311
|
+
}
|
|
3312
|
+
function normalizeProbeModel(model) {
|
|
3313
|
+
return model.endsWith("-fast") ? model.slice(0, -"-fast".length) : model;
|
|
3314
|
+
}
|
|
3315
|
+
async function resolveProbeModels(port) {
|
|
3316
|
+
try {
|
|
3317
|
+
const res = await withRequestTimeout(
|
|
3318
|
+
fetch,
|
|
3319
|
+
REQUEST_TIMEOUT_MS
|
|
3320
|
+
)(`${opencodeBase(port)}/config/providers`);
|
|
3321
|
+
if (!res.ok) {
|
|
3322
|
+
console.error(
|
|
3323
|
+
`[resolveProbeModels] GET /config/providers returned HTTP ${res.status} (port ${port})`
|
|
3324
|
+
);
|
|
3325
|
+
return [];
|
|
3326
|
+
}
|
|
3327
|
+
const body = await res.json();
|
|
3328
|
+
const provider = body?.providers?.find((candidate) => candidate?.id === "openai");
|
|
3329
|
+
if (!provider || !provider.models || typeof provider.models !== "object") return [];
|
|
3330
|
+
const candidates = [
|
|
3331
|
+
...typeof body?.default?.openai === "string" ? [body.default.openai] : [],
|
|
3332
|
+
...Object.keys(provider.models)
|
|
3333
|
+
].map(normalizeProbeModel);
|
|
3334
|
+
return [...new Set(candidates)].slice(0, 4);
|
|
3335
|
+
} catch (err) {
|
|
3336
|
+
console.error(
|
|
3337
|
+
`[resolveProbeModels] GET /config/providers failed (port ${port}): ${err instanceof Error ? err.message : String(err)}`
|
|
3338
|
+
);
|
|
3339
|
+
return [];
|
|
3340
|
+
}
|
|
3341
|
+
}
|
|
3342
|
+
function hasPrimaryHeaders(headers) {
|
|
3343
|
+
return [
|
|
3344
|
+
"x-codex-primary-used-percent",
|
|
3345
|
+
"x-codex-primary-window-minutes",
|
|
3346
|
+
"x-codex-primary-reset-at"
|
|
3347
|
+
].some((name) => headers.has(name));
|
|
3348
|
+
}
|
|
3349
|
+
async function getOpenAiUsage(port) {
|
|
3350
|
+
const credentials2 = readOpenCodeChatGptCredentials();
|
|
3351
|
+
if (!credentials2) {
|
|
3352
|
+
throw new OpenAiUsageError(
|
|
3353
|
+
"No ChatGPT login found. Connect a ChatGPT account to this runner, or run `opencode auth login`.",
|
|
3354
|
+
"no_credentials"
|
|
3355
|
+
);
|
|
3356
|
+
}
|
|
3357
|
+
if (credentials2.expiresAt < Date.now()) {
|
|
3358
|
+
throw new OpenAiUsageError(
|
|
3359
|
+
"ChatGPT credentials have expired. Run `opencode auth login` to refresh them.",
|
|
3360
|
+
"credentials_expired"
|
|
3361
|
+
);
|
|
3362
|
+
}
|
|
3363
|
+
const models = await resolveProbeModels(port);
|
|
3364
|
+
if (models.length === 0) {
|
|
3365
|
+
throw new OpenAiUsageError("No supported OpenAI probe model is available.", "no_probe_model");
|
|
3366
|
+
}
|
|
3367
|
+
let lastStatus;
|
|
3368
|
+
for (const model of models) {
|
|
3369
|
+
let res;
|
|
3370
|
+
try {
|
|
3371
|
+
res = await withRequestTimeout(fetch, REQUEST_TIMEOUT_MS)(CODEX_RESPONSES_URL, {
|
|
3372
|
+
method: "POST",
|
|
3373
|
+
headers: {
|
|
3374
|
+
Authorization: `Bearer ${credentials2.accessToken}`,
|
|
3375
|
+
"Content-Type": "application/json"
|
|
3376
|
+
},
|
|
3377
|
+
body: JSON.stringify({ model, store: false, stream: true })
|
|
3378
|
+
});
|
|
3379
|
+
} catch (err) {
|
|
3380
|
+
throw new OpenAiUsageError(
|
|
3381
|
+
`OpenAI usage probe request failed: ${err instanceof Error ? err.message : String(err)}.`,
|
|
3382
|
+
"request_failed"
|
|
3383
|
+
);
|
|
3384
|
+
}
|
|
3385
|
+
try {
|
|
3386
|
+
lastStatus = res.status;
|
|
3387
|
+
if (hasPrimaryHeaders(res.headers)) {
|
|
3388
|
+
const usage = parseCodexUsageHeaders(res.headers);
|
|
3389
|
+
if (!usage.primary && !usage.secondary) {
|
|
3390
|
+
throw new OpenAiUsageError(
|
|
3391
|
+
`OpenAI usage probe returned no usable window (HTTP ${res.status}).`,
|
|
3392
|
+
"no_usable_window"
|
|
3393
|
+
);
|
|
3394
|
+
}
|
|
3395
|
+
return usage;
|
|
3396
|
+
}
|
|
3397
|
+
if (res.status === 401) {
|
|
3398
|
+
throw new OpenAiUsageError(
|
|
3399
|
+
"ChatGPT credentials have expired (HTTP 401).",
|
|
3400
|
+
"credentials_expired"
|
|
3401
|
+
);
|
|
3402
|
+
}
|
|
3403
|
+
if (res.status === 403 || res.status === 429) {
|
|
3404
|
+
throw new OpenAiUsageError(
|
|
3405
|
+
`OpenAI usage probe was blocked (HTTP ${res.status}).`,
|
|
3406
|
+
"probe_blocked"
|
|
3407
|
+
);
|
|
3408
|
+
}
|
|
3409
|
+
} finally {
|
|
3410
|
+
await res.body?.cancel().catch(() => {
|
|
3411
|
+
});
|
|
3412
|
+
}
|
|
3413
|
+
}
|
|
3414
|
+
throw new OpenAiUsageError(
|
|
3415
|
+
`OpenAI usage probe failed: HTTP ${lastStatus ?? "unknown"}.`,
|
|
3416
|
+
"request_failed"
|
|
3417
|
+
);
|
|
3418
|
+
}
|
|
3419
|
+
|
|
2994
3420
|
// src/lib/reporting-schedule.ts
|
|
2995
3421
|
function jitteredDelayMs(baseMs, jitterFraction, random = Math.random) {
|
|
2996
3422
|
const jitterRangeMs = baseMs * jitterFraction;
|
|
@@ -2999,6 +3425,31 @@ function jitteredDelayMs(baseMs, jitterFraction, random = Math.random) {
|
|
|
2999
3425
|
function firstReportDelayMs(random = Math.random) {
|
|
3000
3426
|
return 5e3 + random() * 1e4;
|
|
3001
3427
|
}
|
|
3428
|
+
var VALID_USAGE_REPORTING_MODES = ["auto", "on", "off"];
|
|
3429
|
+
function resolveUsageReportingMode(flagValue, env, names) {
|
|
3430
|
+
const raw = flagValue ?? env[names.envVar];
|
|
3431
|
+
if (raw === void 0 || raw === "") return { mode: "auto", warnings: [] };
|
|
3432
|
+
const normalized = raw.trim().toLowerCase();
|
|
3433
|
+
if (VALID_USAGE_REPORTING_MODES.includes(normalized)) {
|
|
3434
|
+
return { mode: normalized, warnings: [] };
|
|
3435
|
+
}
|
|
3436
|
+
const source = flagValue !== void 0 ? names.flagName : names.envVar;
|
|
3437
|
+
return {
|
|
3438
|
+
mode: "auto",
|
|
3439
|
+
warnings: [
|
|
3440
|
+
`Ignoring invalid ${source} "${raw}": expected one of ${VALID_USAGE_REPORTING_MODES.join(", ")}; using auto`
|
|
3441
|
+
]
|
|
3442
|
+
};
|
|
3443
|
+
}
|
|
3444
|
+
var BASE_USAGE_REPORT_DELAY_MS = 10 * 6e4;
|
|
3445
|
+
var USAGE_REPORT_DELAY_JITTER_FRACTION = 0.2;
|
|
3446
|
+
function usageReportDelayMs(random = Math.random) {
|
|
3447
|
+
return jitteredDelayMs(BASE_USAGE_REPORT_DELAY_MS, USAGE_REPORT_DELAY_JITTER_FRACTION, random);
|
|
3448
|
+
}
|
|
3449
|
+
var USAGE_REPORT_FAILURE_REESCALATION_TICKS = 6;
|
|
3450
|
+
function usageReportFailureLogLevel(consecutiveFailures) {
|
|
3451
|
+
return reportFailureLogLevel(consecutiveFailures, USAGE_REPORT_FAILURE_REESCALATION_TICKS);
|
|
3452
|
+
}
|
|
3002
3453
|
function reportFailureLogLevel(consecutiveFailures, reescalationTicks) {
|
|
3003
3454
|
return consecutiveFailures === 1 || consecutiveFailures % reescalationTicks === 0 ? "warn" : "debug";
|
|
3004
3455
|
}
|
|
@@ -3007,33 +3458,26 @@ function failureStreakSuffix(consecutiveFailures) {
|
|
|
3007
3458
|
}
|
|
3008
3459
|
|
|
3009
3460
|
// src/lib/claude-usage-reporting.ts
|
|
3010
|
-
var VALID_MODES = ["auto", "on", "off"];
|
|
3011
3461
|
function resolveClaudeUsageReportingMode(flagValue, env) {
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
}
|
|
3016
|
-
const normalized = raw.trim().toLowerCase();
|
|
3017
|
-
if (VALID_MODES.includes(normalized)) {
|
|
3018
|
-
return { mode: normalized, warnings: [] };
|
|
3019
|
-
}
|
|
3020
|
-
const source = flagValue !== void 0 ? "--claude-usage-reporting" : "EVIDENT_CLAUDE_USAGE_REPORTING";
|
|
3021
|
-
return {
|
|
3022
|
-
mode: "auto",
|
|
3023
|
-
warnings: [
|
|
3024
|
-
`Ignoring invalid ${source} "${raw}": expected one of ${VALID_MODES.join(", ")}; using auto`
|
|
3025
|
-
]
|
|
3026
|
-
};
|
|
3462
|
+
return resolveUsageReportingMode(flagValue, env, {
|
|
3463
|
+
flagName: "--claude-usage-reporting",
|
|
3464
|
+
envVar: "EVIDENT_CLAUDE_USAGE_REPORTING"
|
|
3465
|
+
});
|
|
3027
3466
|
}
|
|
3028
|
-
var BASE_REPORT_DELAY_MS = 10 * 6e4;
|
|
3029
|
-
var REPORT_DELAY_JITTER_FRACTION = 0.2;
|
|
3030
3467
|
function nextReportDelayMs(random = Math.random) {
|
|
3031
|
-
return
|
|
3468
|
+
return usageReportDelayMs(random);
|
|
3032
3469
|
}
|
|
3033
3470
|
var FIRST_REPORT_DELAY_MS = firstReportDelayMs();
|
|
3034
|
-
var CLAUDE_USAGE_FAILURE_REESCALATION_TICKS = 6;
|
|
3035
3471
|
function claudeUsageFailureLogLevel(consecutiveFailures) {
|
|
3036
|
-
return
|
|
3472
|
+
return usageReportFailureLogLevel(consecutiveFailures);
|
|
3473
|
+
}
|
|
3474
|
+
|
|
3475
|
+
// src/lib/openai-usage-reporting.ts
|
|
3476
|
+
function resolveOpenAiUsageReportingMode(flagValue, env) {
|
|
3477
|
+
return resolveUsageReportingMode(flagValue, env, {
|
|
3478
|
+
flagName: "--openai-usage-reporting",
|
|
3479
|
+
envVar: "EVIDENT_OPENAI_USAGE_REPORTING"
|
|
3480
|
+
});
|
|
3037
3481
|
}
|
|
3038
3482
|
|
|
3039
3483
|
// src/lib/resource-usage-reporting.ts
|
|
@@ -3192,15 +3636,15 @@ function createResourceUsageCollector(homeDir) {
|
|
|
3192
3636
|
}
|
|
3193
3637
|
|
|
3194
3638
|
// src/lib/channels/driver.ts
|
|
3195
|
-
import { homedir as
|
|
3639
|
+
import { homedir as homedir3 } from "os";
|
|
3196
3640
|
|
|
3197
3641
|
// src/lib/runner-file-sync.ts
|
|
3198
|
-
import { join as
|
|
3642
|
+
import { join as join6 } from "path";
|
|
3199
3643
|
|
|
3200
3644
|
// src/lib/file-push.ts
|
|
3201
3645
|
import { randomUUID } from "crypto";
|
|
3202
3646
|
import { chmod, mkdir, open as open2, realpath, rename, unlink } from "fs/promises";
|
|
3203
|
-
import { basename, dirname as dirname3, isAbsolute, join as
|
|
3647
|
+
import { basename, dirname as dirname3, isAbsolute, join as join5, relative, resolve as resolve2, sep } from "path";
|
|
3204
3648
|
var FILE_MODE = 384;
|
|
3205
3649
|
var DIRECTORY_MODE = 448;
|
|
3206
3650
|
async function writePushedFile(request) {
|
|
@@ -3233,7 +3677,7 @@ async function writePushedFile(request) {
|
|
|
3233
3677
|
const { existingAncestor, missingSegments } = await resolveNearestExistingAncestor(
|
|
3234
3678
|
dirname3(candidate)
|
|
3235
3679
|
);
|
|
3236
|
-
const realTarget =
|
|
3680
|
+
const realTarget = join5(existingAncestor, ...missingSegments, basename(candidate));
|
|
3237
3681
|
const allowedDirectory = await findContainingAllowedDirectory(allowedDirectories, realTarget);
|
|
3238
3682
|
if (allowedDirectory === null) {
|
|
3239
3683
|
return refuse("path_not_allowed", "The runner does not allow writing to that location.", {
|
|
@@ -3269,7 +3713,7 @@ function expandAndValidate(requestedPath, homeDir) {
|
|
|
3269
3713
|
if (requestedPath.trim() === "" || requestedPath.includes("\0")) {
|
|
3270
3714
|
return null;
|
|
3271
3715
|
}
|
|
3272
|
-
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ?
|
|
3716
|
+
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join5(homeDir, requestedPath.slice(2)) : requestedPath;
|
|
3273
3717
|
if (expanded.split(/[/\\]/).includes("..")) {
|
|
3274
3718
|
return null;
|
|
3275
3719
|
}
|
|
@@ -3342,13 +3786,13 @@ function contains(realDirectory, realTarget) {
|
|
|
3342
3786
|
async function createMissingDirectories(existingAncestor, missingSegments) {
|
|
3343
3787
|
let current = existingAncestor;
|
|
3344
3788
|
for (const segment of missingSegments) {
|
|
3345
|
-
current =
|
|
3789
|
+
current = join5(current, segment);
|
|
3346
3790
|
await mkdir(current, { recursive: true, mode: DIRECTORY_MODE });
|
|
3347
3791
|
await chmod(current, DIRECTORY_MODE);
|
|
3348
3792
|
}
|
|
3349
3793
|
}
|
|
3350
3794
|
async function writeAtomically(realTarget, content) {
|
|
3351
|
-
const temporaryPath =
|
|
3795
|
+
const temporaryPath = join5(dirname3(realTarget), `.evident-push-${randomUUID()}.tmp`);
|
|
3352
3796
|
let handle;
|
|
3353
3797
|
try {
|
|
3354
3798
|
handle = await open2(temporaryPath, "wx", FILE_MODE);
|
|
@@ -3390,20 +3834,28 @@ async function syncPendingRunnerFiles(options) {
|
|
|
3390
3834
|
for (const id of options.ackFailures.keys()) {
|
|
3391
3835
|
if (!pendingIds.has(id)) options.ackFailures.delete(id);
|
|
3392
3836
|
}
|
|
3393
|
-
if (pending.length === 0)
|
|
3837
|
+
if (pending.length === 0) {
|
|
3838
|
+
return { applied: 0, claudeCredentialApplied: false, opencodeAuthApplied: false };
|
|
3839
|
+
}
|
|
3394
3840
|
options.log({
|
|
3395
3841
|
level: "info",
|
|
3396
3842
|
message: `Runner file sync: ${pending.length} file(s) queued for this runner`
|
|
3397
3843
|
});
|
|
3398
3844
|
let applied = 0;
|
|
3399
3845
|
let claudeCredentialApplied = false;
|
|
3846
|
+
let opencodeAuthApplied = false;
|
|
3400
3847
|
for (const file of pending) {
|
|
3401
3848
|
if ((options.ackFailures.get(file.id) ?? 0) >= MAX_ACK_ATTEMPTS) continue;
|
|
3402
3849
|
const outcome = await applyOne(options, file);
|
|
3403
3850
|
if (outcome.applied) applied += 1;
|
|
3404
3851
|
if (outcome.claudeCredentialApplied) claudeCredentialApplied = true;
|
|
3852
|
+
if (outcome.opencodeAuthApplied) opencodeAuthApplied = true;
|
|
3405
3853
|
}
|
|
3406
|
-
return {
|
|
3854
|
+
return {
|
|
3855
|
+
applied,
|
|
3856
|
+
claudeCredentialApplied,
|
|
3857
|
+
opencodeAuthApplied
|
|
3858
|
+
};
|
|
3407
3859
|
}
|
|
3408
3860
|
async function listPendingFiles(options) {
|
|
3409
3861
|
let res;
|
|
@@ -3464,10 +3916,18 @@ function asPendingFile(entry) {
|
|
|
3464
3916
|
if (typeof size !== "number" || !Number.isFinite(size) || size < 0) return null;
|
|
3465
3917
|
return { id, path, size };
|
|
3466
3918
|
}
|
|
3467
|
-
var NOT_APPLIED = {
|
|
3919
|
+
var NOT_APPLIED = {
|
|
3920
|
+
applied: false,
|
|
3921
|
+
claudeCredentialApplied: false,
|
|
3922
|
+
opencodeAuthApplied: false
|
|
3923
|
+
};
|
|
3468
3924
|
function isClaudeCredentialPath(requestedPath, homeDir) {
|
|
3469
|
-
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ?
|
|
3470
|
-
return expanded ===
|
|
3925
|
+
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join6(homeDir, requestedPath.slice(2)) : requestedPath;
|
|
3926
|
+
return expanded === join6(homeDir, ...CLAUDE_CREDENTIALS_SEGMENTS);
|
|
3927
|
+
}
|
|
3928
|
+
function isOpenCodeAuthPath(requestedPath, homeDir) {
|
|
3929
|
+
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join6(homeDir, requestedPath.slice(2)) : requestedPath;
|
|
3930
|
+
return expanded === join6(homeDir, ...OPENCODE_AUTH_SEGMENTS);
|
|
3471
3931
|
}
|
|
3472
3932
|
async function applyOne(options, file) {
|
|
3473
3933
|
const label = `${file.id.slice(0, 8)} (${file.path})`;
|
|
@@ -3523,7 +3983,8 @@ async function applyOne(options, file) {
|
|
|
3523
3983
|
await ack(options, file, "applied");
|
|
3524
3984
|
return {
|
|
3525
3985
|
applied: true,
|
|
3526
|
-
claudeCredentialApplied: isClaudeCredentialPath(file.path, options.homeDir)
|
|
3986
|
+
claudeCredentialApplied: isClaudeCredentialPath(file.path, options.homeDir),
|
|
3987
|
+
opencodeAuthApplied: isOpenCodeAuthPath(file.path, options.homeDir)
|
|
3527
3988
|
};
|
|
3528
3989
|
}
|
|
3529
3990
|
function durableDownloadCode(status2) {
|
|
@@ -3977,6 +4438,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
3977
4438
|
* that way rather than "fixing" it into a count.
|
|
3978
4439
|
*/
|
|
3979
4440
|
claudeCredentialApplyCount = 0;
|
|
4441
|
+
opencodeAuthApplyCount = 0;
|
|
3980
4442
|
/**
|
|
3981
4443
|
* The currently-executing `drainPending()` promise, or null when idle. Lets a
|
|
3982
4444
|
* graceful shutdown (`waitForInFlight`) await an in-progress drain so a turn it
|
|
@@ -4011,7 +4473,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
4011
4473
|
this.stuckQueuedMs = config.stuckQueuedMs ?? DEFAULT_STUCK_QUEUED_MS;
|
|
4012
4474
|
this.now = config.now ?? (() => Date.now());
|
|
4013
4475
|
this.fileSyncDirectories = config.fileSyncDirectories ?? [];
|
|
4014
|
-
this.homeDir = config.homeDir ??
|
|
4476
|
+
this.homeDir = config.homeDir ?? homedir3();
|
|
4015
4477
|
this.maxActiveSessions = config.maxActiveSessions;
|
|
4016
4478
|
this.watcherStallMs = config.watcherStallMs ?? WATCHER_STALL_MS;
|
|
4017
4479
|
this.wedgeWarningIntervalMs = config.wedgeWarningIntervalMs ?? WEDGE_WARNING_INTERVAL_MS;
|
|
@@ -4081,6 +4543,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
4081
4543
|
});
|
|
4082
4544
|
this.appliedFileCount += result.applied;
|
|
4083
4545
|
if (result.claudeCredentialApplied) this.claudeCredentialApplyCount += 1;
|
|
4546
|
+
if (result.opencodeAuthApplied) this.opencodeAuthApplyCount += 1;
|
|
4084
4547
|
return result.applied;
|
|
4085
4548
|
} catch (err) {
|
|
4086
4549
|
this.log({
|
|
@@ -4188,7 +4651,8 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
4188
4651
|
return {
|
|
4189
4652
|
appliedFiles: this.appliedFileCount,
|
|
4190
4653
|
inFlight: this.syncingFiles,
|
|
4191
|
-
claudeCredentialApplies: this.claudeCredentialApplyCount
|
|
4654
|
+
claudeCredentialApplies: this.claudeCredentialApplyCount,
|
|
4655
|
+
opencodeAuthApplyCount: this.opencodeAuthApplyCount
|
|
4192
4656
|
};
|
|
4193
4657
|
}
|
|
4194
4658
|
/**
|
|
@@ -7525,7 +7989,7 @@ function resolveFileSyncDirectories(raw, homeDir) {
|
|
|
7525
7989
|
if (trimmed === "") {
|
|
7526
7990
|
throw new Error("--enable-file-sync-to requires a directory path (got an empty value)");
|
|
7527
7991
|
}
|
|
7528
|
-
const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ?
|
|
7992
|
+
const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ? join7(homeDir, trimmed.slice(2)) : trimmed;
|
|
7529
7993
|
if (!isAbsolute2(expanded)) {
|
|
7530
7994
|
throw new Error(`--enable-file-sync-to requires an absolute directory path; got "${entry}"`);
|
|
7531
7995
|
}
|
|
@@ -7623,7 +8087,7 @@ function logActivity(state, entry) {
|
|
|
7623
8087
|
const level = entry.level ?? (entry.type === "error" ? "error" : "info");
|
|
7624
8088
|
if (!meetsThreshold(state, level)) return;
|
|
7625
8089
|
forwardRunnerActivity(
|
|
7626
|
-
{ level, message: entry.message, error: entry.error },
|
|
8090
|
+
{ level, message: entry.message, error: entry.error, metadata: entry.metadata },
|
|
7627
8091
|
{ agentId: state.agentId, authHeader: state.authHeader }
|
|
7628
8092
|
);
|
|
7629
8093
|
const fullEntry = {
|
|
@@ -7643,6 +8107,26 @@ function logActivity(state, entry) {
|
|
|
7643
8107
|
}
|
|
7644
8108
|
}
|
|
7645
8109
|
}
|
|
8110
|
+
function reportSessionDbRecovery(state) {
|
|
8111
|
+
try {
|
|
8112
|
+
const report = drainSessionDbRecoveryReport({ homeDir: homedir4(), env: process.env });
|
|
8113
|
+
for (const record of report.records) {
|
|
8114
|
+
const activity = buildSessionDbRecoveryActivity(record);
|
|
8115
|
+
if (!activity) throw new Error("could not map session-DB recovery record");
|
|
8116
|
+
logActivity(state, {
|
|
8117
|
+
type: activity.level === "error" ? "error" : "info",
|
|
8118
|
+
level: activity.level,
|
|
8119
|
+
...activity.level === "error" ? { error: activity.message } : { message: activity.message },
|
|
8120
|
+
metadata: activity.metadata
|
|
8121
|
+
});
|
|
8122
|
+
}
|
|
8123
|
+
acknowledgeSessionDbRecoveryReport(report.path);
|
|
8124
|
+
} catch (error2) {
|
|
8125
|
+
console.error(
|
|
8126
|
+
`[run] could not report session-DB recovery activity: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
8127
|
+
);
|
|
8128
|
+
}
|
|
8129
|
+
}
|
|
7646
8130
|
function displayStatus(state) {
|
|
7647
8131
|
if (!state.interactive) return;
|
|
7648
8132
|
const attempt = state.connection?.reconnectAttempt ?? 0;
|
|
@@ -7733,6 +8217,7 @@ async function driveChannels(state, driver) {
|
|
|
7733
8217
|
let lastSeenProxiedActivityAt = state.lastProxiedActivityAt;
|
|
7734
8218
|
let lastSeenAppliedFiles = driver.fileSyncActivity().appliedFiles;
|
|
7735
8219
|
let lastSeenClaudeApplies = driver.fileSyncActivity().claudeCredentialApplies;
|
|
8220
|
+
let lastSeenOpencodeAuthApplies = driver.fileSyncActivity().opencodeAuthApplyCount;
|
|
7736
8221
|
while (state.running) {
|
|
7737
8222
|
const cycleStartedAtMs = performance.now();
|
|
7738
8223
|
let idleThisCycle = false;
|
|
@@ -7765,6 +8250,10 @@ async function driveChannels(state, driver) {
|
|
|
7765
8250
|
const claudeCredentialApplied = claudeCredentialApplies !== lastSeenClaudeApplies;
|
|
7766
8251
|
lastSeenClaudeApplies = claudeCredentialApplies;
|
|
7767
8252
|
if (claudeCredentialApplied) state.claudeUsageRearm?.();
|
|
8253
|
+
const opencodeAuthApplies = fileActivitySnapshot.opencodeAuthApplyCount;
|
|
8254
|
+
const opencodeAuthApplied = opencodeAuthApplies !== lastSeenOpencodeAuthApplies;
|
|
8255
|
+
lastSeenOpencodeAuthApplies = opencodeAuthApplies;
|
|
8256
|
+
if (opencodeAuthApplied) state.openaiUsageRearm?.();
|
|
7768
8257
|
if (processed > 0 || driver.hasInFlightWatchers() || proxiedActivity || fileActivity) {
|
|
7769
8258
|
idlePolls = 0;
|
|
7770
8259
|
idleMs = 0;
|
|
@@ -7833,7 +8322,7 @@ async function driveChannels(state, driver) {
|
|
|
7833
8322
|
var SESSION_CLEANUP_FIRST_SWEEP_MS = 1e4;
|
|
7834
8323
|
var SESSION_DB_RECLAIM_MAX_PAGES = 2e3;
|
|
7835
8324
|
function sessionDbPath() {
|
|
7836
|
-
return
|
|
8325
|
+
return join7(homedir4(), ".local", "share", "opencode", "opencode.db");
|
|
7837
8326
|
}
|
|
7838
8327
|
async function runSweep(state, driver, config) {
|
|
7839
8328
|
const mode = `age=${config.maxAgeMs ?? "\u2014"} count=${config.maxCount ?? "\u2014"}`;
|
|
@@ -7916,7 +8405,7 @@ function scheduleSessionCleanup(state, driver, options) {
|
|
|
7916
8405
|
for (const warning2 of config.warnings) {
|
|
7917
8406
|
logActivity(state, { type: "info", level: "warn", message: `Session cleanup: ${warning2}` });
|
|
7918
8407
|
}
|
|
7919
|
-
const dbBytes = statSessionDbBytes(
|
|
8408
|
+
const dbBytes = statSessionDbBytes(homedir4());
|
|
7920
8409
|
void (async () => {
|
|
7921
8410
|
const reclaimSkipReason = dbBytes !== null && config.enabled ? await probeReclaimAvailability({ dbPath: sessionDbPath(), requiredBytes: dbBytes }) : null;
|
|
7922
8411
|
const sizeWarning = buildSessionStoreSizeWarning({
|
|
@@ -7944,23 +8433,20 @@ function scheduleSessionCleanup(state, driver, options) {
|
|
|
7944
8433
|
);
|
|
7945
8434
|
state.sessionCleanupTimers.push(interval, firstSweep);
|
|
7946
8435
|
}
|
|
7947
|
-
function
|
|
7948
|
-
const { mode, warnings } =
|
|
7949
|
-
options.claudeUsageReporting,
|
|
7950
|
-
process.env
|
|
7951
|
-
);
|
|
8436
|
+
function scheduleUsageReporting(state, params) {
|
|
8437
|
+
const { mode, warnings } = params.resolved;
|
|
7952
8438
|
for (const warning2 of warnings) {
|
|
7953
8439
|
logActivity(state, {
|
|
7954
8440
|
type: "info",
|
|
7955
8441
|
level: "warn",
|
|
7956
|
-
message:
|
|
8442
|
+
message: `${params.label} usage reporting: ${warning2}`
|
|
7957
8443
|
});
|
|
7958
8444
|
}
|
|
7959
8445
|
if (mode === "off") {
|
|
7960
8446
|
logActivity(state, {
|
|
7961
8447
|
type: "info",
|
|
7962
8448
|
level: "debug",
|
|
7963
|
-
message:
|
|
8449
|
+
message: `${params.label} usage reporting is off (${params.offFlagHint})`
|
|
7964
8450
|
});
|
|
7965
8451
|
return null;
|
|
7966
8452
|
}
|
|
@@ -7969,7 +8455,7 @@ function scheduleClaudeUsageReporting(state, options) {
|
|
|
7969
8455
|
let rearmRequested = false;
|
|
7970
8456
|
const armProbe = () => {
|
|
7971
8457
|
phase = "probe-pending";
|
|
7972
|
-
|
|
8458
|
+
params.setTimer(setTimeout(() => void tick(true), params.firstDelayMs()));
|
|
7973
8459
|
};
|
|
7974
8460
|
const scheduleNextTick = () => {
|
|
7975
8461
|
if (rearmRequested) {
|
|
@@ -7978,7 +8464,7 @@ function scheduleClaudeUsageReporting(state, options) {
|
|
|
7978
8464
|
return;
|
|
7979
8465
|
}
|
|
7980
8466
|
phase = "steady-pending";
|
|
7981
|
-
|
|
8467
|
+
params.setTimer(setTimeout(() => void tick(false), params.nextDelayMs()));
|
|
7982
8468
|
};
|
|
7983
8469
|
const rearm = () => {
|
|
7984
8470
|
switch (phase) {
|
|
@@ -7988,9 +8474,9 @@ function scheduleClaudeUsageReporting(state, options) {
|
|
|
7988
8474
|
case "probe-pending":
|
|
7989
8475
|
return;
|
|
7990
8476
|
case "steady-pending":
|
|
7991
|
-
if (
|
|
7992
|
-
clearTimeout(
|
|
7993
|
-
|
|
8477
|
+
if (params.getTimer()) {
|
|
8478
|
+
clearTimeout(params.getTimer());
|
|
8479
|
+
params.setTimer(null);
|
|
7994
8480
|
}
|
|
7995
8481
|
rearmRequested = false;
|
|
7996
8482
|
armProbe();
|
|
@@ -8004,45 +8490,45 @@ function scheduleClaudeUsageReporting(state, options) {
|
|
|
8004
8490
|
const tick = async (isProbe) => {
|
|
8005
8491
|
phase = "tick-in-flight";
|
|
8006
8492
|
try {
|
|
8007
|
-
const usage = await
|
|
8008
|
-
const result = await
|
|
8493
|
+
const usage = await params.fetchUsage();
|
|
8494
|
+
const result = await params.report(usage);
|
|
8009
8495
|
if (result.ok) {
|
|
8010
8496
|
if (consecutiveFailures > 0) {
|
|
8011
8497
|
logActivity(state, {
|
|
8012
8498
|
type: "info",
|
|
8013
8499
|
level: "info",
|
|
8014
|
-
message:
|
|
8500
|
+
message: `${params.label} usage reporting recovered`
|
|
8015
8501
|
});
|
|
8016
8502
|
}
|
|
8017
8503
|
consecutiveFailures = 0;
|
|
8018
8504
|
logActivity(state, {
|
|
8019
8505
|
type: "info",
|
|
8020
8506
|
level: "debug",
|
|
8021
|
-
message:
|
|
8507
|
+
message: `Reported ${params.label} usage to Evident`
|
|
8022
8508
|
});
|
|
8023
8509
|
} else {
|
|
8024
8510
|
consecutiveFailures++;
|
|
8025
8511
|
logActivity(state, {
|
|
8026
8512
|
type: "info",
|
|
8027
|
-
level:
|
|
8028
|
-
message: `Failed to report
|
|
8513
|
+
level: params.failureLogLevel(consecutiveFailures),
|
|
8514
|
+
message: `Failed to report ${params.label} usage: ${result.error}${failureStreakSuffix(consecutiveFailures)}`
|
|
8029
8515
|
});
|
|
8030
8516
|
}
|
|
8031
8517
|
scheduleNextTick();
|
|
8032
8518
|
} catch (error2) {
|
|
8033
|
-
if (
|
|
8519
|
+
if (params.isLocalCredentialProblem(error2)) {
|
|
8034
8520
|
if (mode === "on") {
|
|
8035
8521
|
logActivity(state, {
|
|
8036
8522
|
type: "info",
|
|
8037
8523
|
level: "warn",
|
|
8038
|
-
message:
|
|
8524
|
+
message: `${params.label} usage reporting is forced on but no usable login was found \u2014 ${params.forcedOnHint}; reporting will keep retrying`
|
|
8039
8525
|
});
|
|
8040
8526
|
scheduleNextTick();
|
|
8041
8527
|
} else if (isProbe) {
|
|
8042
8528
|
logActivity(state, {
|
|
8043
8529
|
type: "info",
|
|
8044
8530
|
level: "debug",
|
|
8045
|
-
message:
|
|
8531
|
+
message: `${params.label} usage reporting: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
8046
8532
|
});
|
|
8047
8533
|
phase = "dormant";
|
|
8048
8534
|
if (rearmRequested) rearm();
|
|
@@ -8050,7 +8536,7 @@ function scheduleClaudeUsageReporting(state, options) {
|
|
|
8050
8536
|
logActivity(state, {
|
|
8051
8537
|
type: "info",
|
|
8052
8538
|
level: "debug",
|
|
8053
|
-
message:
|
|
8539
|
+
message: `${params.label} usage reporting: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
8054
8540
|
});
|
|
8055
8541
|
scheduleNextTick();
|
|
8056
8542
|
}
|
|
@@ -8059,8 +8545,8 @@ function scheduleClaudeUsageReporting(state, options) {
|
|
|
8059
8545
|
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
8060
8546
|
logActivity(state, {
|
|
8061
8547
|
type: "info",
|
|
8062
|
-
level:
|
|
8063
|
-
message:
|
|
8548
|
+
level: params.failureLogLevel(consecutiveFailures),
|
|
8549
|
+
message: `${params.label} usage reporting failed: ${message}${failureStreakSuffix(consecutiveFailures)}`
|
|
8064
8550
|
});
|
|
8065
8551
|
scheduleNextTick();
|
|
8066
8552
|
}
|
|
@@ -8069,6 +8555,34 @@ function scheduleClaudeUsageReporting(state, options) {
|
|
|
8069
8555
|
armProbe();
|
|
8070
8556
|
return rearm;
|
|
8071
8557
|
}
|
|
8558
|
+
function scheduleClaudeUsageReporting(state, options) {
|
|
8559
|
+
return scheduleUsageReporting(state, {
|
|
8560
|
+
label: "Claude",
|
|
8561
|
+
resolved: resolveClaudeUsageReportingMode(options.claudeUsageReporting, process.env),
|
|
8562
|
+
offFlagHint: "--claude-usage-reporting off",
|
|
8563
|
+
getTimer: () => state.claudeUsageTimer,
|
|
8564
|
+
setTimer: (timer) => {
|
|
8565
|
+
state.claudeUsageTimer = timer;
|
|
8566
|
+
},
|
|
8567
|
+
fetchUsage: async () => {
|
|
8568
|
+
const usage = await getClaudeUsage();
|
|
8569
|
+
if (usage.ownerLookupError) {
|
|
8570
|
+
logActivity(state, {
|
|
8571
|
+
type: "info",
|
|
8572
|
+
level: "debug",
|
|
8573
|
+
message: `Claude usage owner lookup failed: ${usage.ownerLookupError}`
|
|
8574
|
+
});
|
|
8575
|
+
}
|
|
8576
|
+
return usage;
|
|
8577
|
+
},
|
|
8578
|
+
report: (usage) => reportClaudeUsage(state.agentId, state.authHeader, usage),
|
|
8579
|
+
isLocalCredentialProblem,
|
|
8580
|
+
forcedOnHint: "run `claude` to sign in",
|
|
8581
|
+
firstDelayMs: () => FIRST_REPORT_DELAY_MS,
|
|
8582
|
+
nextDelayMs: nextReportDelayMs,
|
|
8583
|
+
failureLogLevel: claudeUsageFailureLogLevel
|
|
8584
|
+
});
|
|
8585
|
+
}
|
|
8072
8586
|
var RESOURCE_USAGE_BASE_REPORT_DELAY_MS = 10 * 6e4;
|
|
8073
8587
|
var RESOURCE_USAGE_REPORT_DELAY_JITTER_FRACTION = 0.2;
|
|
8074
8588
|
var RESOURCE_USAGE_FAILURE_REESCALATION_TICKS = 6;
|
|
@@ -8092,7 +8606,7 @@ function scheduleResourceUsageReporting(state, options) {
|
|
|
8092
8606
|
});
|
|
8093
8607
|
return;
|
|
8094
8608
|
}
|
|
8095
|
-
const collect = createResourceUsageCollector(
|
|
8609
|
+
const collect = createResourceUsageCollector(homedir4());
|
|
8096
8610
|
let consecutiveFailures = 0;
|
|
8097
8611
|
const tick = async () => {
|
|
8098
8612
|
try {
|
|
@@ -8193,6 +8707,11 @@ async function cleanup(state, opts = {}) {
|
|
|
8193
8707
|
state.claudeUsageTimer = null;
|
|
8194
8708
|
}
|
|
8195
8709
|
state.claudeUsageRearm = null;
|
|
8710
|
+
if (state.openaiUsageTimer) {
|
|
8711
|
+
clearTimeout(state.openaiUsageTimer);
|
|
8712
|
+
state.openaiUsageTimer = null;
|
|
8713
|
+
}
|
|
8714
|
+
state.openaiUsageRearm = null;
|
|
8196
8715
|
if (state.resourceUsageTimer) {
|
|
8197
8716
|
clearTimeout(state.resourceUsageTimer);
|
|
8198
8717
|
state.resourceUsageTimer = null;
|
|
@@ -8244,7 +8763,7 @@ async function run(options) {
|
|
|
8244
8763
|
let fileSyncDirectories;
|
|
8245
8764
|
try {
|
|
8246
8765
|
logLevel = resolveLogLevel(options);
|
|
8247
|
-
fileSyncDirectories = resolveFileSyncDirectories(options.enableFileSyncTo,
|
|
8766
|
+
fileSyncDirectories = resolveFileSyncDirectories(options.enableFileSyncTo, homedir4());
|
|
8248
8767
|
} catch (error2) {
|
|
8249
8768
|
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
8250
8769
|
if (options.json) {
|
|
@@ -8279,6 +8798,8 @@ async function run(options) {
|
|
|
8279
8798
|
sessionCleanupTimers: [],
|
|
8280
8799
|
claudeUsageTimer: null,
|
|
8281
8800
|
claudeUsageRearm: null,
|
|
8801
|
+
openaiUsageTimer: null,
|
|
8802
|
+
openaiUsageRearm: null,
|
|
8282
8803
|
resourceUsageTimer: null,
|
|
8283
8804
|
authHeader: ""
|
|
8284
8805
|
};
|
|
@@ -8474,6 +8995,7 @@ async function run(options) {
|
|
|
8474
8995
|
} else {
|
|
8475
8996
|
log2(state, "Not running in a MicroVM (MICROVM_ID unset) \u2014 nothing to report", "debug");
|
|
8476
8997
|
}
|
|
8998
|
+
reportSessionDbRecovery(state);
|
|
8477
8999
|
const { timeoutMs: opencodeStartTimeoutMs, warnings: opencodeStartTimeoutWarnings } = resolveOpenCodeStartTimeoutMs(options, process.env);
|
|
8478
9000
|
for (const warning2 of opencodeStartTimeoutWarnings) {
|
|
8479
9001
|
logActivity(state, { type: "info", level: "warn", message: warning2 });
|
|
@@ -8541,7 +9063,7 @@ async function run(options) {
|
|
|
8541
9063
|
// #559: the `--enable-file-sync-to` allow-list. Empty ⇒ pending files are
|
|
8542
9064
|
// REJECTED with `file_sync_disabled` on the ack, not silently ignored.
|
|
8543
9065
|
fileSyncDirectories,
|
|
8544
|
-
homeDir:
|
|
9066
|
+
homeDir: homedir4(),
|
|
8545
9067
|
maxActiveSessions,
|
|
8546
9068
|
log: (entry) => (
|
|
8547
9069
|
// Thread the driver's real level straight through so `debug`/`warn`
|
|
@@ -8679,6 +9201,22 @@ async function run(options) {
|
|
|
8679
9201
|
}
|
|
8680
9202
|
scheduleSessionCleanup(state, channelDriver, options);
|
|
8681
9203
|
state.claudeUsageRearm = scheduleClaudeUsageReporting(state, options);
|
|
9204
|
+
state.openaiUsageRearm = scheduleUsageReporting(state, {
|
|
9205
|
+
label: "OpenAI",
|
|
9206
|
+
resolved: resolveOpenAiUsageReportingMode(options.openaiUsageReporting, process.env),
|
|
9207
|
+
offFlagHint: "--openai-usage-reporting off",
|
|
9208
|
+
getTimer: () => state.openaiUsageTimer,
|
|
9209
|
+
setTimer: (timer) => {
|
|
9210
|
+
state.openaiUsageTimer = timer;
|
|
9211
|
+
},
|
|
9212
|
+
fetchUsage: () => getOpenAiUsage(state.port),
|
|
9213
|
+
report: (usage) => reportOpenAiUsage(state.agentId, state.authHeader, usage),
|
|
9214
|
+
isLocalCredentialProblem: isLocalCredentialProblem2,
|
|
9215
|
+
forcedOnHint: "connect a ChatGPT account to this runner, or run `opencode auth login`",
|
|
9216
|
+
firstDelayMs: firstReportDelayMs,
|
|
9217
|
+
nextDelayMs: usageReportDelayMs,
|
|
9218
|
+
failureLogLevel: usageReportFailureLogLevel
|
|
9219
|
+
});
|
|
8682
9220
|
scheduleResourceUsageReporting(state, options);
|
|
8683
9221
|
if (!interactive || state.json) {
|
|
8684
9222
|
log2(state, "Driving channel messages...");
|
|
@@ -8760,6 +9298,9 @@ program.command("run").description("Connect to Evident and process messages").op
|
|
|
8760
9298
|
).option(
|
|
8761
9299
|
"--claude-usage-reporting <mode>",
|
|
8762
9300
|
"Report Claude subscription usage to Evident: auto | on | off (default: auto). Env: EVIDENT_CLAUDE_USAGE_REPORTING"
|
|
9301
|
+
).option(
|
|
9302
|
+
"--openai-usage-reporting <mode>",
|
|
9303
|
+
"Report OpenAI plan usage to Evident: auto | on | off (default: auto). Env: EVIDENT_OPENAI_USAGE_REPORTING"
|
|
8763
9304
|
).option(
|
|
8764
9305
|
"--no-resource-usage-reporting",
|
|
8765
9306
|
"Don't report this machine's CPU and memory usage to Evident (reporting is on by default). Env: EVIDENT_RESOURCE_USAGE_REPORTING=off"
|
|
@@ -8795,6 +9336,7 @@ program.command("run").description("Connect to Evident and process messages").op
|
|
|
8795
9336
|
// Raw string — the resolver in run.ts single-sources parsing
|
|
8796
9337
|
// (resolveClaudeUsageReportingMode).
|
|
8797
9338
|
claudeUsageReporting: options.claudeUsageReporting,
|
|
9339
|
+
openaiUsageReporting: options.openaiUsageReporting,
|
|
8798
9340
|
// Raw value — resolution is single-sourced in run.ts's
|
|
8799
9341
|
// resolveResourceUsageReportingEnabled.
|
|
8800
9342
|
resourceUsageReporting: options.resourceUsageReporting,
|