@evident-ai/cli 3.4.1-dev.1633d8c → 3.4.1-dev.180238a
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +738 -178
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -683,12 +683,15 @@ async function resolveAgentIdFromKey(authHeader) {
|
|
|
683
683
|
}
|
|
684
684
|
}
|
|
685
685
|
var BEST_EFFORT_NOTIFY_TIMEOUT_MS = 2e3;
|
|
686
|
-
async function
|
|
687
|
-
const apiUrl = getApiUrlConfig();
|
|
686
|
+
async function postBestEffort(path, authHeader, body) {
|
|
688
687
|
try {
|
|
689
|
-
const
|
|
688
|
+
const apiUrl = getApiUrlConfig();
|
|
689
|
+
const headers = { Authorization: authHeader };
|
|
690
|
+
if (body !== void 0) headers["Content-Type"] = "application/json";
|
|
691
|
+
const response = await fetch(`${apiUrl}${path}`, {
|
|
690
692
|
method: "POST",
|
|
691
|
-
headers
|
|
693
|
+
headers,
|
|
694
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
692
695
|
signal: AbortSignal.timeout(BEST_EFFORT_NOTIFY_TIMEOUT_MS)
|
|
693
696
|
});
|
|
694
697
|
if (!response.ok) {
|
|
@@ -700,65 +703,32 @@ async function notifyAgentDisconnected(agentId, authHeader) {
|
|
|
700
703
|
}
|
|
701
704
|
return { ok: true };
|
|
702
705
|
} catch (error2) {
|
|
703
|
-
return { ok: false, error:
|
|
706
|
+
return { ok: false, error: describeTimeoutError(error2, BEST_EFFORT_NOTIFY_TIMEOUT_MS) };
|
|
704
707
|
}
|
|
705
708
|
}
|
|
706
|
-
function
|
|
709
|
+
function describeTimeoutError(error2, timeoutMs) {
|
|
707
710
|
const name = error2?.name;
|
|
708
711
|
if (name === "TimeoutError" || name === "AbortError") {
|
|
709
|
-
return `timed out after ${
|
|
712
|
+
return `timed out after ${timeoutMs}ms`;
|
|
710
713
|
}
|
|
711
714
|
return error2 instanceof Error ? error2.message : String(error2);
|
|
712
715
|
}
|
|
716
|
+
async function notifyAgentDisconnected(agentId, authHeader) {
|
|
717
|
+
return postBestEffort(`/runners/${agentId}/disconnect`, authHeader);
|
|
718
|
+
}
|
|
713
719
|
async function reportMicrovmId(agentId, authHeader, microvmId) {
|
|
714
|
-
|
|
715
|
-
const apiUrl = getApiUrlConfig();
|
|
716
|
-
const response = await fetch(`${apiUrl}/runners/${agentId}/microvm`, {
|
|
717
|
-
method: "POST",
|
|
718
|
-
headers: { Authorization: authHeader, "Content-Type": "application/json" },
|
|
719
|
-
body: JSON.stringify({ microvm_id: microvmId }),
|
|
720
|
-
signal: AbortSignal.timeout(BEST_EFFORT_NOTIFY_TIMEOUT_MS)
|
|
721
|
-
});
|
|
722
|
-
if (!response.ok) {
|
|
723
|
-
const serverMessage = await readErrorMessage(response);
|
|
724
|
-
return {
|
|
725
|
-
ok: false,
|
|
726
|
-
error: `HTTP ${response.status}${serverMessage ? `: ${serverMessage}` : ""}`
|
|
727
|
-
};
|
|
728
|
-
}
|
|
729
|
-
return { ok: true };
|
|
730
|
-
} catch (error2) {
|
|
731
|
-
return { ok: false, error: describeBestEffortError(error2) };
|
|
732
|
-
}
|
|
720
|
+
return postBestEffort(`/runners/${agentId}/microvm`, authHeader, { microvm_id: microvmId });
|
|
733
721
|
}
|
|
734
722
|
function toReportedWindow(window) {
|
|
735
723
|
if (!window) return null;
|
|
736
724
|
return { utilization: window.utilization, resets_at: window.resetsAt };
|
|
737
725
|
}
|
|
738
726
|
async function reportClaudeUsage(agentId, authHeader, snapshot) {
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
body: JSON.stringify({
|
|
745
|
-
five_hour: toReportedWindow(snapshot.fiveHour),
|
|
746
|
-
seven_day: toReportedWindow(snapshot.sevenDay),
|
|
747
|
-
subscription: toReportedSubscription(snapshot.subscription)
|
|
748
|
-
}),
|
|
749
|
-
signal: AbortSignal.timeout(BEST_EFFORT_NOTIFY_TIMEOUT_MS)
|
|
750
|
-
});
|
|
751
|
-
if (!response.ok) {
|
|
752
|
-
const serverMessage = await readErrorMessage(response);
|
|
753
|
-
return {
|
|
754
|
-
ok: false,
|
|
755
|
-
error: `HTTP ${response.status}${serverMessage ? `: ${serverMessage}` : ""}`
|
|
756
|
-
};
|
|
757
|
-
}
|
|
758
|
-
return { ok: true };
|
|
759
|
-
} catch (error2) {
|
|
760
|
-
return { ok: false, error: describeBestEffortError(error2) };
|
|
761
|
-
}
|
|
727
|
+
return postBestEffort(`/runners/${agentId}/claude-usage`, authHeader, {
|
|
728
|
+
five_hour: toReportedWindow(snapshot.fiveHour),
|
|
729
|
+
seven_day: toReportedWindow(snapshot.sevenDay),
|
|
730
|
+
subscription: toReportedSubscription(snapshot.subscription)
|
|
731
|
+
});
|
|
762
732
|
}
|
|
763
733
|
function toReportedOpenAiWindow(window) {
|
|
764
734
|
if (!window) return null;
|
|
@@ -769,61 +739,25 @@ function toReportedOpenAiWindow(window) {
|
|
|
769
739
|
};
|
|
770
740
|
}
|
|
771
741
|
async function reportOpenAiUsage(agentId, authHeader, snapshot) {
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
secondary: toReportedOpenAiWindow(snapshot.secondary),
|
|
780
|
-
has_credits: snapshot.hasCredits,
|
|
781
|
-
credits_unlimited: snapshot.creditsUnlimited,
|
|
782
|
-
subscription: toReportedSubscription(snapshot.subscription)
|
|
783
|
-
}),
|
|
784
|
-
signal: AbortSignal.timeout(BEST_EFFORT_NOTIFY_TIMEOUT_MS)
|
|
785
|
-
});
|
|
786
|
-
if (!response.ok) {
|
|
787
|
-
const serverMessage = await readErrorMessage(response);
|
|
788
|
-
return {
|
|
789
|
-
ok: false,
|
|
790
|
-
error: `HTTP ${response.status}${serverMessage ? `: ${serverMessage}` : ""}`
|
|
791
|
-
};
|
|
792
|
-
}
|
|
793
|
-
return { ok: true };
|
|
794
|
-
} catch (error2) {
|
|
795
|
-
return { ok: false, error: describeBestEffortError(error2) };
|
|
796
|
-
}
|
|
742
|
+
return postBestEffort(`/runners/${agentId}/openai-usage`, authHeader, {
|
|
743
|
+
primary: toReportedOpenAiWindow(snapshot.primary),
|
|
744
|
+
secondary: toReportedOpenAiWindow(snapshot.secondary),
|
|
745
|
+
has_credits: snapshot.hasCredits,
|
|
746
|
+
credits_unlimited: snapshot.creditsUnlimited,
|
|
747
|
+
subscription: toReportedSubscription(snapshot.subscription)
|
|
748
|
+
});
|
|
797
749
|
}
|
|
798
750
|
async function reportResourceUsage(agentId, authHeader, usage) {
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
memory_available_bytes: usage.memoryAvailableBytes,
|
|
810
|
-
disk_total_bytes: usage.diskTotalBytes,
|
|
811
|
-
disk_free_bytes: usage.diskFreeBytes,
|
|
812
|
-
opencode_db_bytes: usage.opencodeDbBytes
|
|
813
|
-
}),
|
|
814
|
-
signal: AbortSignal.timeout(BEST_EFFORT_NOTIFY_TIMEOUT_MS)
|
|
815
|
-
});
|
|
816
|
-
if (!response.ok) {
|
|
817
|
-
const serverMessage = await readErrorMessage(response);
|
|
818
|
-
return {
|
|
819
|
-
ok: false,
|
|
820
|
-
error: `HTTP ${response.status}${serverMessage ? `: ${serverMessage}` : ""}`
|
|
821
|
-
};
|
|
822
|
-
}
|
|
823
|
-
return { ok: true };
|
|
824
|
-
} catch (error2) {
|
|
825
|
-
return { ok: false, error: describeBestEffortError(error2) };
|
|
826
|
-
}
|
|
751
|
+
return postBestEffort(`/runners/${agentId}/resource-usage`, authHeader, {
|
|
752
|
+
cpu_percent: usage.cpuPercent,
|
|
753
|
+
cpu_peak_percent: usage.cpuPeakPercent,
|
|
754
|
+
cpu_count: usage.cpuCount,
|
|
755
|
+
memory_total_bytes: usage.memoryTotalBytes,
|
|
756
|
+
memory_available_bytes: usage.memoryAvailableBytes,
|
|
757
|
+
disk_total_bytes: usage.diskTotalBytes,
|
|
758
|
+
disk_free_bytes: usage.diskFreeBytes,
|
|
759
|
+
opencode_db_bytes: usage.opencodeDbBytes
|
|
760
|
+
});
|
|
827
761
|
}
|
|
828
762
|
async function getAgentInfo(agentId, authHeader) {
|
|
829
763
|
const apiUrl = getApiUrlConfig();
|
|
@@ -875,13 +809,6 @@ function authLabelFor(credentials2) {
|
|
|
875
809
|
}
|
|
876
810
|
return "user token";
|
|
877
811
|
}
|
|
878
|
-
function describeFetchError(error2) {
|
|
879
|
-
const name = error2?.name;
|
|
880
|
-
if (name === "TimeoutError" || name === "AbortError") {
|
|
881
|
-
return `timed out after ${STATUS_TIMEOUT_MS}ms waiting for a response`;
|
|
882
|
-
}
|
|
883
|
-
return error2 instanceof Error ? error2.message : String(error2);
|
|
884
|
-
}
|
|
885
812
|
async function checkStatus(jsonMode) {
|
|
886
813
|
const apiUrl = getApiUrlConfig();
|
|
887
814
|
const credentials2 = await getAuthCredentials();
|
|
@@ -909,7 +836,7 @@ async function checkStatus(jsonMode) {
|
|
|
909
836
|
endpoint: apiUrl,
|
|
910
837
|
authLabel: authLabelFor(credentials2),
|
|
911
838
|
reason: "unreachable",
|
|
912
|
-
error: `Could not reach ${apiUrl}: ${
|
|
839
|
+
error: `Could not reach ${apiUrl}: ${describeTimeoutError(error2, STATUS_TIMEOUT_MS)}. The credentials were NOT validated.`,
|
|
913
840
|
exitCode: 75
|
|
914
841
|
};
|
|
915
842
|
}
|
|
@@ -3261,21 +3188,74 @@ function collectSubagentSessions(messages, userMessageId) {
|
|
|
3261
3188
|
}
|
|
3262
3189
|
return refs;
|
|
3263
3190
|
}
|
|
3264
|
-
function
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3191
|
+
function finiteNumber(value) {
|
|
3192
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
3193
|
+
}
|
|
3194
|
+
function taskCallModel(value) {
|
|
3195
|
+
if (!value || typeof value !== "object") return null;
|
|
3196
|
+
const model = value;
|
|
3197
|
+
const modelID = typeof model.modelID === "string" ? model.modelID : void 0;
|
|
3198
|
+
const providerID = typeof model.providerID === "string" ? model.providerID : void 0;
|
|
3199
|
+
return modelID || providerID ? { modelID, providerID } : null;
|
|
3200
|
+
}
|
|
3201
|
+
function collectTaskCalls(messages, userMessageId) {
|
|
3202
|
+
if (!messages || messages.length === 0) return [];
|
|
3203
|
+
const calls = [];
|
|
3204
|
+
for (const message of messages) {
|
|
3205
|
+
if (roleOf(message) !== "assistant" || parentIdOf(message) !== userMessageId) continue;
|
|
3206
|
+
for (const part of message.parts ?? []) {
|
|
3207
|
+
if (part.tool !== "task" || !part.callID || !part.state || part.state.status === "pending") {
|
|
3208
|
+
continue;
|
|
3209
|
+
}
|
|
3210
|
+
const rawName = part.state.input?.subagent_type;
|
|
3211
|
+
const subagentName = typeof rawName === "string" && rawName.trim().length > 0 ? rawName : rawName === void 0 ? "general" : "unknown";
|
|
3212
|
+
const metadata = part.state.metadata;
|
|
3213
|
+
calls.push({
|
|
3214
|
+
callID: part.callID,
|
|
3215
|
+
subagentName,
|
|
3216
|
+
childSessionId: typeof metadata?.sessionId === "string" ? metadata.sessionId : null,
|
|
3217
|
+
parentSessionId: typeof metadata?.parentSessionId === "string" ? metadata.parentSessionId : null,
|
|
3218
|
+
model: taskCallModel(metadata?.model),
|
|
3219
|
+
status: part.state.status ?? "unknown",
|
|
3220
|
+
timeStart: finiteNumber(part.state.time?.start),
|
|
3221
|
+
timeEnd: finiteNumber(part.state.time?.end)
|
|
3222
|
+
});
|
|
3223
|
+
}
|
|
3224
|
+
}
|
|
3225
|
+
return calls;
|
|
3226
|
+
}
|
|
3227
|
+
function attributeTaskCallUsage(messages, windows) {
|
|
3228
|
+
const eligibleWindows = windows.filter(
|
|
3229
|
+
(window) => window.timeStart !== null && Number.isFinite(window.timeStart)
|
|
3268
3230
|
);
|
|
3269
|
-
const
|
|
3270
|
-
const
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
const
|
|
3276
|
-
|
|
3231
|
+
const assignments = /* @__PURE__ */ new Map();
|
|
3232
|
+
for (const window of eligibleWindows) assignments.set(window.callID, []);
|
|
3233
|
+
const unattributed = [];
|
|
3234
|
+
for (const message of messages ?? []) {
|
|
3235
|
+
if (roleOf(message) !== "assistant") continue;
|
|
3236
|
+
const created = finiteNumber(createdOf(message));
|
|
3237
|
+
const matching = created === null ? [] : eligibleWindows.filter(
|
|
3238
|
+
(window) => window.timeStart <= created && (window.timeEnd === null || window.timeEnd === void 0 || created <= window.timeEnd)
|
|
3239
|
+
);
|
|
3240
|
+
if (matching.length === 0) {
|
|
3241
|
+
unattributed.push(message);
|
|
3242
|
+
continue;
|
|
3243
|
+
}
|
|
3244
|
+
matching.sort((a, b) => a.timeStart - b.timeStart);
|
|
3245
|
+
assignments.get(matching[0].callID)?.push(message);
|
|
3277
3246
|
}
|
|
3278
|
-
|
|
3247
|
+
return {
|
|
3248
|
+
invocations: eligibleWindows.map((window) => {
|
|
3249
|
+
const assigned = assignments.get(window.callID) ?? [];
|
|
3250
|
+
return { callID: window.callID, messages: assigned, usage: sumAssistantUsage(assigned) };
|
|
3251
|
+
}),
|
|
3252
|
+
unattributed
|
|
3253
|
+
};
|
|
3254
|
+
}
|
|
3255
|
+
function sumAssistantUsage(messages) {
|
|
3256
|
+
if (!messages || messages.length === 0) return null;
|
|
3257
|
+
const nonErrored = messages.filter((message) => errorOf(message) == null);
|
|
3258
|
+
const selected = nonErrored.length > 0 ? nonErrored : messages;
|
|
3279
3259
|
let sawAnyUsage = false;
|
|
3280
3260
|
let inputSum = 0;
|
|
3281
3261
|
let outputSum = 0;
|
|
@@ -3286,7 +3266,7 @@ function messageUsage(messages, userMessageId) {
|
|
|
3286
3266
|
let sawCost = false;
|
|
3287
3267
|
let modelId = null;
|
|
3288
3268
|
let providerId = null;
|
|
3289
|
-
for (const m of
|
|
3269
|
+
for (const m of selected) {
|
|
3290
3270
|
const info = m.info;
|
|
3291
3271
|
if (!info) continue;
|
|
3292
3272
|
const tokens = info.tokens;
|
|
@@ -3321,12 +3301,28 @@ function messageUsage(messages, userMessageId) {
|
|
|
3321
3301
|
usage_tokens_reasoning: reasoningSum,
|
|
3322
3302
|
usage_tokens_cache_read: cacheReadSum,
|
|
3323
3303
|
usage_tokens_cache_write: cacheWriteSum,
|
|
3324
|
-
// NULL means
|
|
3325
|
-
//
|
|
3326
|
-
// `sawCost` true with `costSum === 0`.
|
|
3304
|
+
// NULL means OpenCode never reported a cost; it is distinct from a genuine
|
|
3305
|
+
// zero-cost message, which sets `sawCost` with `costSum === 0`.
|
|
3327
3306
|
usage_cost_usd: sawCost ? costSum : null
|
|
3328
3307
|
};
|
|
3329
3308
|
}
|
|
3309
|
+
function messageUsage(messages, userMessageId) {
|
|
3310
|
+
if (!messages || messages.length === 0) return null;
|
|
3311
|
+
const byParentAll = messages.filter(
|
|
3312
|
+
(m) => roleOf(m) === "assistant" && parentIdOf(m) === userMessageId
|
|
3313
|
+
);
|
|
3314
|
+
const byParentNonErrored = byParentAll.filter((m) => errorOf(m) == null);
|
|
3315
|
+
const byParent = byParentNonErrored.length > 0 ? byParentNonErrored : byParentAll;
|
|
3316
|
+
let correlated;
|
|
3317
|
+
if (byParent.length > 0) {
|
|
3318
|
+
correlated = byParent;
|
|
3319
|
+
} else {
|
|
3320
|
+
const reply = findAssistantReplyAfter(messages, userMessageId);
|
|
3321
|
+
correlated = reply ? [reply] : [];
|
|
3322
|
+
}
|
|
3323
|
+
if (correlated.length === 0) return null;
|
|
3324
|
+
return sumAssistantUsage(correlated);
|
|
3325
|
+
}
|
|
3330
3326
|
function messageRunState(messages, userMessageId) {
|
|
3331
3327
|
if (!messages || messages.length === 0) return "unknown";
|
|
3332
3328
|
const hasUser = messages.some((m) => idOf(m) === userMessageId);
|
|
@@ -3456,6 +3452,28 @@ function hasRunningAssistantExcept(messages, exceptUserMessageId) {
|
|
|
3456
3452
|
(m) => roleOf(m) === "assistant" && parentIdOf(m) !== exceptUserMessageId && isAssistantInFlight(m)
|
|
3457
3453
|
);
|
|
3458
3454
|
}
|
|
3455
|
+
function hasLaterSiblingTurnStarted(messages, userMessageId, siblingUserMessageIds) {
|
|
3456
|
+
if (!messages || messages.length === 0) return false;
|
|
3457
|
+
const userIndex = messages.findIndex((message) => idOf(message) === userMessageId);
|
|
3458
|
+
if (userIndex === -1) return false;
|
|
3459
|
+
let hasLaterUser = false;
|
|
3460
|
+
let hasStartedLaterUser = false;
|
|
3461
|
+
for (let i = userIndex + 1; i < messages.length; i++) {
|
|
3462
|
+
const message = messages[i];
|
|
3463
|
+
if (roleOf(message) !== "user") continue;
|
|
3464
|
+
hasLaterUser = true;
|
|
3465
|
+
const laterUserMessageId = idOf(message);
|
|
3466
|
+
if (laterUserMessageId === void 0 || !siblingUserMessageIds.has(laterUserMessageId)) {
|
|
3467
|
+
return false;
|
|
3468
|
+
}
|
|
3469
|
+
if (messages.some(
|
|
3470
|
+
(candidate) => roleOf(candidate) === "assistant" && parentIdOf(candidate) === laterUserMessageId
|
|
3471
|
+
)) {
|
|
3472
|
+
hasStartedLaterUser = true;
|
|
3473
|
+
}
|
|
3474
|
+
}
|
|
3475
|
+
return hasLaterUser && hasStartedLaterUser;
|
|
3476
|
+
}
|
|
3459
3477
|
async function hasAnyConfiguredProvider(port) {
|
|
3460
3478
|
try {
|
|
3461
3479
|
const res = await timedFetch(`${opencodeBase(port)}/config/providers`);
|
|
@@ -3487,6 +3505,76 @@ async function hasAnyConfiguredProvider(port) {
|
|
|
3487
3505
|
return null;
|
|
3488
3506
|
}
|
|
3489
3507
|
}
|
|
3508
|
+
function sessionErrorReason(error2) {
|
|
3509
|
+
const record = typeof error2 === "object" && error2 !== null ? error2 : null;
|
|
3510
|
+
const data = record?.data;
|
|
3511
|
+
const dataRecord = typeof data === "object" && data !== null ? data : null;
|
|
3512
|
+
const rawReason = typeof dataRecord?.message === "string" && dataRecord.message || typeof record?.message === "string" && record.message || typeof error2 === "string" && error2 || typeof record?.name === "string" && record.name || "OpenCode reported a session error with no details";
|
|
3513
|
+
const reason = rawReason.replace(/\s+/g, " ").trim().slice(0, 500);
|
|
3514
|
+
return reason || "OpenCode reported a session error with no details";
|
|
3515
|
+
}
|
|
3516
|
+
function parseSessionErrorFrame(data) {
|
|
3517
|
+
let parsed;
|
|
3518
|
+
try {
|
|
3519
|
+
parsed = JSON.parse(data);
|
|
3520
|
+
} catch (error2) {
|
|
3521
|
+
void error2;
|
|
3522
|
+
return null;
|
|
3523
|
+
}
|
|
3524
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
3525
|
+
const parsedRecord = parsed;
|
|
3526
|
+
const payload = parsedRecord.payload;
|
|
3527
|
+
const event = payload !== null && typeof payload === "object" && !Array.isArray(payload) ? payload : parsedRecord;
|
|
3528
|
+
if (event.type !== "session.error") return null;
|
|
3529
|
+
const properties = event.properties;
|
|
3530
|
+
if (properties === null || typeof properties !== "object" || Array.isArray(properties)) {
|
|
3531
|
+
return null;
|
|
3532
|
+
}
|
|
3533
|
+
const propertiesRecord = properties;
|
|
3534
|
+
const sessionId = propertiesRecord.sessionID;
|
|
3535
|
+
if (typeof sessionId !== "string" || sessionId.length === 0) return null;
|
|
3536
|
+
return {
|
|
3537
|
+
sessionId,
|
|
3538
|
+
reason: sessionErrorReason(propertiesRecord.error)
|
|
3539
|
+
};
|
|
3540
|
+
}
|
|
3541
|
+
async function readSessionErrorStream(port, options) {
|
|
3542
|
+
let reader = null;
|
|
3543
|
+
try {
|
|
3544
|
+
const response = await fetch(`${opencodeBase(port)}/event`, {
|
|
3545
|
+
headers: { accept: "text/event-stream" },
|
|
3546
|
+
signal: options.signal
|
|
3547
|
+
});
|
|
3548
|
+
if (!response.ok || !response.body) {
|
|
3549
|
+
return { reason: "unavailable", detail: `HTTP ${response.status}` };
|
|
3550
|
+
}
|
|
3551
|
+
reader = response.body.getReader();
|
|
3552
|
+
const decoder = new TextDecoder();
|
|
3553
|
+
let buffer = "";
|
|
3554
|
+
const processLine = (line) => {
|
|
3555
|
+
const trimmed = line.trimEnd();
|
|
3556
|
+
if (!trimmed.startsWith("data:")) return;
|
|
3557
|
+
const event = parseSessionErrorFrame(trimmed.slice("data:".length).replace(/^ /, ""));
|
|
3558
|
+
if (event) options.onSessionError(event);
|
|
3559
|
+
};
|
|
3560
|
+
while (true) {
|
|
3561
|
+
const { done, value } = await reader.read();
|
|
3562
|
+
if (done) return { reason: "ended" };
|
|
3563
|
+
buffer += decoder.decode(value, { stream: true });
|
|
3564
|
+
const lines = buffer.split("\n");
|
|
3565
|
+
buffer = lines.pop() ?? "";
|
|
3566
|
+
for (const line of lines) processLine(line);
|
|
3567
|
+
}
|
|
3568
|
+
} catch (err) {
|
|
3569
|
+
if (options.signal.aborted) return { reason: "aborted" };
|
|
3570
|
+
return {
|
|
3571
|
+
reason: "unavailable",
|
|
3572
|
+
detail: err instanceof Error ? err.message : String(err)
|
|
3573
|
+
};
|
|
3574
|
+
} finally {
|
|
3575
|
+
if (reader) void reader.cancel().catch(() => void 0);
|
|
3576
|
+
}
|
|
3577
|
+
}
|
|
3490
3578
|
async function reloadProviderCache(port) {
|
|
3491
3579
|
try {
|
|
3492
3580
|
const res = await timedFetch(`${opencodeBase(port)}/config`, {
|
|
@@ -4564,13 +4652,6 @@ function resolveClaudeUsageReportingMode(flagValue, env) {
|
|
|
4564
4652
|
envVar: "EVIDENT_CLAUDE_USAGE_REPORTING"
|
|
4565
4653
|
});
|
|
4566
4654
|
}
|
|
4567
|
-
function nextReportDelayMs(random = Math.random) {
|
|
4568
|
-
return usageReportDelayMs(random);
|
|
4569
|
-
}
|
|
4570
|
-
var FIRST_REPORT_DELAY_MS = firstReportDelayMs();
|
|
4571
|
-
function claudeUsageFailureLogLevel(consecutiveFailures) {
|
|
4572
|
-
return usageReportFailureLogLevel(consecutiveFailures);
|
|
4573
|
-
}
|
|
4574
4655
|
|
|
4575
4656
|
// src/lib/openai-usage-reporting.ts
|
|
4576
4657
|
function resolveOpenAiUsageReportingMode(flagValue, env) {
|
|
@@ -5226,6 +5307,10 @@ var DEFAULT_RETRY_POLICY = {
|
|
|
5226
5307
|
baseDelayMs: 500,
|
|
5227
5308
|
maxDelayMs: 3e4
|
|
5228
5309
|
};
|
|
5310
|
+
var SUBAGENT_TELEMETRY_FETCH_TIMEOUT_MS = 2e3;
|
|
5311
|
+
var SESSION_ERROR_STREAM_HEALTHY_MS = 5e3;
|
|
5312
|
+
var SESSION_ERROR_BUFFER_TTL_MS = SESSION_ERROR_STREAM_HEALTHY_MS;
|
|
5313
|
+
var MAX_BUFFERED_SESSION_ERRORS = 256;
|
|
5229
5314
|
var DEFAULT_PAUSED_POLL_INTERVAL_MS = 2e3;
|
|
5230
5315
|
var DEFAULT_PAUSED_MAX_WAIT_MS = 10 * 60 * 1e3;
|
|
5231
5316
|
var DEFAULT_STUCK_QUEUED_MS = 6e4;
|
|
@@ -5366,6 +5451,17 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
5366
5451
|
* message; it is removed once its in-flight set empties.
|
|
5367
5452
|
*/
|
|
5368
5453
|
watchers = /* @__PURE__ */ new Map();
|
|
5454
|
+
sessionErrorStream = null;
|
|
5455
|
+
/**
|
|
5456
|
+
* Session-error failures currently being reported; entries are empty at rest
|
|
5457
|
+
* because each handoff deletes its id in `finally`.
|
|
5458
|
+
*/
|
|
5459
|
+
sessionErrorHandled = /* @__PURE__ */ new Set();
|
|
5460
|
+
/**
|
|
5461
|
+
* Session errors that arrived before their dispatch was registered. Bounded FIFO
|
|
5462
|
+
* with a short TTL so an unmatched session cannot retain an event indefinitely.
|
|
5463
|
+
*/
|
|
5464
|
+
bufferedSessionErrors = /* @__PURE__ */ new Map();
|
|
5369
5465
|
/**
|
|
5370
5466
|
* AUTHORITATIVE local dedup (WI-3): Evident message ids that have been
|
|
5371
5467
|
* dispatched and are still in-flight. A message in this set is never
|
|
@@ -5549,6 +5645,13 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
5549
5645
|
* no watcher) can resolve the title.
|
|
5550
5646
|
*/
|
|
5551
5647
|
sessionTitles = /* @__PURE__ */ new Map();
|
|
5648
|
+
/** One best-effort terminal subagent collection per Evident message id. */
|
|
5649
|
+
subagentInvocationCollections = /* @__PURE__ */ new Map();
|
|
5650
|
+
/**
|
|
5651
|
+
* Early snapshots are only liveness hints; they must not become the terminal
|
|
5652
|
+
* collection when the task parts or child transcript have advanced.
|
|
5653
|
+
*/
|
|
5654
|
+
subagentInvocationPrefetches = /* @__PURE__ */ new Map();
|
|
5552
5655
|
/** Serialises drains so a reconnect during a drain doesn't double-process. */
|
|
5553
5656
|
draining = false;
|
|
5554
5657
|
/**
|
|
@@ -5763,6 +5866,21 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
5763
5866
|
}
|
|
5764
5867
|
return ids;
|
|
5765
5868
|
}
|
|
5869
|
+
/**
|
|
5870
|
+
* OpenCode user-message ids tracked for other Evident messages in a session.
|
|
5871
|
+
* Excluding this message makes an unattributed later row fail safe; a missing
|
|
5872
|
+
* watcher yields no attributions, per `hasLaterSiblingTurnStarted`'s docblock.
|
|
5873
|
+
*/
|
|
5874
|
+
siblingOpencodeMessageIds(watcher, ownEvidentMessageId) {
|
|
5875
|
+
const ids = /* @__PURE__ */ new Set();
|
|
5876
|
+
if (!watcher) return ids;
|
|
5877
|
+
for (const inFlight of watcher.inFlight.values()) {
|
|
5878
|
+
if (inFlight.evidentMessageId !== ownEvidentMessageId) {
|
|
5879
|
+
ids.add(inFlight.opencodeMessageId);
|
|
5880
|
+
}
|
|
5881
|
+
}
|
|
5882
|
+
return ids;
|
|
5883
|
+
}
|
|
5766
5884
|
/**
|
|
5767
5885
|
* File-pull work, for `run.ts`'s idle accounting (#559).
|
|
5768
5886
|
*
|
|
@@ -5829,6 +5947,8 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
5829
5947
|
*/
|
|
5830
5948
|
stop() {
|
|
5831
5949
|
this.stopped = true;
|
|
5950
|
+
this.sessionErrorStream?.abort.abort();
|
|
5951
|
+
this.sessionErrorStream = null;
|
|
5832
5952
|
}
|
|
5833
5953
|
/**
|
|
5834
5954
|
* The server clears this request when a new MicroVM identity is recorded, so a
|
|
@@ -5903,6 +6023,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
5903
6023
|
*/
|
|
5904
6024
|
async processConversation(conv) {
|
|
5905
6025
|
const { sessionId, refusedSessionId, created: sessionCreated } = await this.ensureSession(conv);
|
|
6026
|
+
this.ensureSessionErrorStream();
|
|
5906
6027
|
const messages = await this.getPendingMessages(conv.id);
|
|
5907
6028
|
let dispatched = 0;
|
|
5908
6029
|
let skippedAlreadyDispatched = 0;
|
|
@@ -6249,6 +6370,23 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
6249
6370
|
if (state === "running" || state === "queued") {
|
|
6250
6371
|
const ongoing = await isSessionOngoing(this.port, sessionId);
|
|
6251
6372
|
if (ongoing === true) {
|
|
6373
|
+
if (state === "queued") {
|
|
6374
|
+
const siblingOcIds = this.siblingOpencodeMessageIds(
|
|
6375
|
+
this.watchers.get(sessionId),
|
|
6376
|
+
message.id
|
|
6377
|
+
);
|
|
6378
|
+
if (hasLaterSiblingTurnStarted(messages, ocId ?? "", siblingOcIds)) {
|
|
6379
|
+
this.log({
|
|
6380
|
+
level: "warn",
|
|
6381
|
+
message: `Re-drive: OpenCode already served a later, different Evident message's turn in session ${sessionId.slice(0, 8)} while message ${message.id.slice(0, 8)} produced no reply \u2014 re-dispatching instead of reattaching to someone else's turn`,
|
|
6382
|
+
conversation_id: conv.id,
|
|
6383
|
+
message_id: message.id
|
|
6384
|
+
});
|
|
6385
|
+
this.clearRedriveUnresolved(message.id);
|
|
6386
|
+
void this.postSignal(conv.id, message.id, "redrive_redispatched");
|
|
6387
|
+
return "dispatch";
|
|
6388
|
+
}
|
|
6389
|
+
}
|
|
6252
6390
|
return this.reattachRedrive(conv, sessionId, message, ocId);
|
|
6253
6391
|
}
|
|
6254
6392
|
if (ongoing === false) {
|
|
@@ -6338,16 +6476,37 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
6338
6476
|
if (state === "done") {
|
|
6339
6477
|
const title = await this.resolveSessionTitle(sessionId, conv.id);
|
|
6340
6478
|
const usage = messageUsage(messages, ocId ?? "");
|
|
6479
|
+
const usageAgentName = this.usageAgentName(messages, ocId ?? "");
|
|
6480
|
+
const subagentInvocations = await this.resolveSubagentInvocations(
|
|
6481
|
+
messages,
|
|
6482
|
+
ocId ?? "",
|
|
6483
|
+
message.id
|
|
6484
|
+
);
|
|
6341
6485
|
this.log({
|
|
6342
6486
|
level: "info",
|
|
6343
6487
|
message: `Re-drive: message ${message.id.slice(0, 8)} completed while its row was wrongly reclaimed to pending \u2014 marking done instead of re-dispatching`,
|
|
6344
6488
|
conversation_id: conv.id,
|
|
6345
6489
|
message_id: message.id
|
|
6346
6490
|
});
|
|
6347
|
-
await this.markDone(
|
|
6491
|
+
await this.markDone(
|
|
6492
|
+
conv.id,
|
|
6493
|
+
message.id,
|
|
6494
|
+
sessionId,
|
|
6495
|
+
ocId,
|
|
6496
|
+
title,
|
|
6497
|
+
usage,
|
|
6498
|
+
usageAgentName,
|
|
6499
|
+
subagentInvocations
|
|
6500
|
+
);
|
|
6348
6501
|
} else {
|
|
6349
6502
|
const error2 = messageError(messages, ocId ?? "") ?? void 0;
|
|
6350
6503
|
const usage = messageUsage(messages, ocId ?? "");
|
|
6504
|
+
const usageAgentName = this.usageAgentName(messages, ocId ?? "");
|
|
6505
|
+
const subagentInvocations = await this.resolveSubagentInvocations(
|
|
6506
|
+
messages,
|
|
6507
|
+
ocId ?? "",
|
|
6508
|
+
message.id
|
|
6509
|
+
);
|
|
6351
6510
|
const failure = await this.classifyModelAuthFailure(messages, ocId ?? "");
|
|
6352
6511
|
this.log({
|
|
6353
6512
|
level: "error",
|
|
@@ -6355,7 +6514,16 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
6355
6514
|
conversation_id: conv.id,
|
|
6356
6515
|
message_id: message.id
|
|
6357
6516
|
});
|
|
6358
|
-
await this.markFailed(
|
|
6517
|
+
await this.markFailed(
|
|
6518
|
+
conv.id,
|
|
6519
|
+
message.id,
|
|
6520
|
+
sessionId,
|
|
6521
|
+
error2,
|
|
6522
|
+
usage,
|
|
6523
|
+
failure,
|
|
6524
|
+
usageAgentName,
|
|
6525
|
+
subagentInvocations
|
|
6526
|
+
);
|
|
6359
6527
|
}
|
|
6360
6528
|
if (ocId !== null) {
|
|
6361
6529
|
await this.reportSubagentAuthFailures(conv.id, ocId, message.id, messages);
|
|
@@ -6899,6 +7067,24 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
6899
7067
|
ambiguousPinnedSinceMs: 0,
|
|
6900
7068
|
ambiguousResolved: false
|
|
6901
7069
|
});
|
|
7070
|
+
const buffered = this.bufferedSessionErrors.get(sessionId);
|
|
7071
|
+
if (!buffered) return;
|
|
7072
|
+
this.bufferedSessionErrors.delete(sessionId);
|
|
7073
|
+
if (this.now() - buffered.receivedAt < SESSION_ERROR_BUFFER_TTL_MS) {
|
|
7074
|
+
this.handleSessionError(buffered.event);
|
|
7075
|
+
}
|
|
7076
|
+
}
|
|
7077
|
+
bufferSessionError(event) {
|
|
7078
|
+
this.bufferedSessionErrors.delete(event.sessionId);
|
|
7079
|
+
this.bufferedSessionErrors.set(event.sessionId, {
|
|
7080
|
+
event,
|
|
7081
|
+
receivedAt: this.now()
|
|
7082
|
+
});
|
|
7083
|
+
while (this.bufferedSessionErrors.size > MAX_BUFFERED_SESSION_ERRORS) {
|
|
7084
|
+
const oldest = this.bufferedSessionErrors.keys().next().value;
|
|
7085
|
+
if (typeof oldest !== "string") break;
|
|
7086
|
+
this.bufferedSessionErrors.delete(oldest);
|
|
7087
|
+
}
|
|
6902
7088
|
}
|
|
6903
7089
|
/**
|
|
6904
7090
|
* Build a fresh `SessionWatcher`. Seeds `lastTickAt`/`lastObservedTickAt`
|
|
@@ -7103,6 +7289,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
7103
7289
|
ensureWatcherRunning(sessionId) {
|
|
7104
7290
|
const watcher = this.watchers.get(sessionId);
|
|
7105
7291
|
if (!watcher) return;
|
|
7292
|
+
this.ensureSessionErrorStream();
|
|
7106
7293
|
if (watcher.loop) return;
|
|
7107
7294
|
if (watcher.inFlight.size === 0) {
|
|
7108
7295
|
this.watchers.delete(sessionId);
|
|
@@ -7118,6 +7305,154 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
7118
7305
|
});
|
|
7119
7306
|
watcher.loop = loop;
|
|
7120
7307
|
}
|
|
7308
|
+
ensureSessionErrorStream() {
|
|
7309
|
+
if (this.sessionErrorStream || this.stopped) return;
|
|
7310
|
+
const abort = new AbortController();
|
|
7311
|
+
const loop = this.runSessionErrorStream(abort.signal);
|
|
7312
|
+
this.sessionErrorStream = { abort, loop };
|
|
7313
|
+
}
|
|
7314
|
+
async runSessionErrorStream(signal) {
|
|
7315
|
+
let attempt = 0;
|
|
7316
|
+
let warned = false;
|
|
7317
|
+
while (!this.stopped && !signal.aborted) {
|
|
7318
|
+
const openedAt = this.now();
|
|
7319
|
+
try {
|
|
7320
|
+
const outcome = await readSessionErrorStream(this.port, {
|
|
7321
|
+
signal,
|
|
7322
|
+
onSessionError: (event) => this.handleSessionError(event)
|
|
7323
|
+
});
|
|
7324
|
+
if (outcome.reason === "aborted" || signal.aborted) return;
|
|
7325
|
+
const healthy = this.now() - openedAt >= SESSION_ERROR_STREAM_HEALTHY_MS;
|
|
7326
|
+
if (outcome.reason === "unavailable" || outcome.reason === "ended") {
|
|
7327
|
+
if (!healthy) {
|
|
7328
|
+
const detail = outcome.reason === "unavailable" ? outcome.detail : "stream ended";
|
|
7329
|
+
this.log({
|
|
7330
|
+
level: warned ? "debug" : "warn",
|
|
7331
|
+
message: `OpenCode session-error stream ${warned ? "still unavailable" : "unavailable"} (${detail}); transcript polling remains the evidence path`
|
|
7332
|
+
});
|
|
7333
|
+
warned = true;
|
|
7334
|
+
}
|
|
7335
|
+
}
|
|
7336
|
+
if (healthy) {
|
|
7337
|
+
if (warned) {
|
|
7338
|
+
this.log({
|
|
7339
|
+
level: "info",
|
|
7340
|
+
message: "OpenCode session-error stream reconnected; transcript polling remains the evidence path"
|
|
7341
|
+
});
|
|
7342
|
+
warned = false;
|
|
7343
|
+
}
|
|
7344
|
+
attempt = 0;
|
|
7345
|
+
} else {
|
|
7346
|
+
attempt += 1;
|
|
7347
|
+
}
|
|
7348
|
+
if (this.stopped || signal.aborted) return;
|
|
7349
|
+
await this.sleep(backoffDelay(healthy ? 0 : attempt - 1, this.retry));
|
|
7350
|
+
} catch (err) {
|
|
7351
|
+
if (this.stopped || signal.aborted) return;
|
|
7352
|
+
this.log({
|
|
7353
|
+
level: "error",
|
|
7354
|
+
message: `OpenCode session-error stream failed unexpectedly: ${err instanceof Error ? err.message : String(err)}`
|
|
7355
|
+
});
|
|
7356
|
+
const healthy = this.now() - openedAt >= SESSION_ERROR_STREAM_HEALTHY_MS;
|
|
7357
|
+
const delayAttempt = healthy ? 0 : attempt;
|
|
7358
|
+
attempt = healthy ? 0 : attempt + 1;
|
|
7359
|
+
try {
|
|
7360
|
+
await this.sleep(backoffDelay(delayAttempt, this.retry));
|
|
7361
|
+
} catch (sleepErr) {
|
|
7362
|
+
this.log({
|
|
7363
|
+
level: "error",
|
|
7364
|
+
message: `OpenCode session-error stream backoff failed unexpectedly: ${sleepErr instanceof Error ? sleepErr.message : String(sleepErr)}`
|
|
7365
|
+
});
|
|
7366
|
+
}
|
|
7367
|
+
}
|
|
7368
|
+
}
|
|
7369
|
+
}
|
|
7370
|
+
handleSessionError(event) {
|
|
7371
|
+
try {
|
|
7372
|
+
const watcher = this.watchers.get(event.sessionId);
|
|
7373
|
+
if (!watcher) {
|
|
7374
|
+
this.bufferSessionError(event);
|
|
7375
|
+
this.log({
|
|
7376
|
+
level: "debug",
|
|
7377
|
+
message: `Ignoring session error for unknown session ${event.sessionId.slice(0, 8)}`
|
|
7378
|
+
});
|
|
7379
|
+
return;
|
|
7380
|
+
}
|
|
7381
|
+
if ([...watcher.inFlight.values()].some((message) => message.started && !message.done)) {
|
|
7382
|
+
this.log({
|
|
7383
|
+
level: "debug",
|
|
7384
|
+
message: `A turn is already running in session ${event.sessionId.slice(0, 8)} \u2014 deferring to transcript polling`,
|
|
7385
|
+
conversation_id: watcher.conv.id
|
|
7386
|
+
});
|
|
7387
|
+
return;
|
|
7388
|
+
}
|
|
7389
|
+
const inFlight = [...watcher.inFlight.values()].filter((message) => !message.started && !message.done).sort((a, b) => a.dispatchedAt - b.dispatchedAt)[0];
|
|
7390
|
+
if (!inFlight) {
|
|
7391
|
+
this.bufferSessionError(event);
|
|
7392
|
+
this.log({
|
|
7393
|
+
level: "debug",
|
|
7394
|
+
message: `No queued in-flight turn to correlate with session error in ${event.sessionId.slice(0, 8)}`,
|
|
7395
|
+
conversation_id: watcher.conv.id
|
|
7396
|
+
});
|
|
7397
|
+
return;
|
|
7398
|
+
}
|
|
7399
|
+
if (this.sessionErrorHandled.has(inFlight.evidentMessageId)) return;
|
|
7400
|
+
this.sessionErrorHandled.add(inFlight.evidentMessageId);
|
|
7401
|
+
void this.failFromSessionError(watcher, event, inFlight);
|
|
7402
|
+
} catch (err) {
|
|
7403
|
+
this.log({
|
|
7404
|
+
level: "error",
|
|
7405
|
+
message: `Failed to handle OpenCode session error for ${event.sessionId.slice(0, 8)}: ${err instanceof Error ? err.message : String(err)}`
|
|
7406
|
+
});
|
|
7407
|
+
}
|
|
7408
|
+
}
|
|
7409
|
+
async failFromSessionError(watcher, event, inFlight) {
|
|
7410
|
+
try {
|
|
7411
|
+
const messages = await getSessionMessages(this.port, event.sessionId);
|
|
7412
|
+
const state = messageRunState(messages, inFlight.opencodeMessageId);
|
|
7413
|
+
if (state !== "queued") {
|
|
7414
|
+
this.log({
|
|
7415
|
+
level: "debug",
|
|
7416
|
+
message: `Session error for message ${inFlight.evidentMessageId.slice(0, 8)} observed state ${state}; leaving it to transcript polling`,
|
|
7417
|
+
conversation_id: watcher.conv.id,
|
|
7418
|
+
message_id: inFlight.evidentMessageId
|
|
7419
|
+
});
|
|
7420
|
+
return;
|
|
7421
|
+
}
|
|
7422
|
+
this.log({
|
|
7423
|
+
level: "error",
|
|
7424
|
+
message: `OpenCode could not run message ${inFlight.evidentMessageId.slice(0, 8)} in session ${event.sessionId.slice(0, 8)}: ${event.reason}`,
|
|
7425
|
+
conversation_id: watcher.conv.id,
|
|
7426
|
+
message_id: inFlight.evidentMessageId
|
|
7427
|
+
});
|
|
7428
|
+
await this.markFailed(
|
|
7429
|
+
watcher.conv.id,
|
|
7430
|
+
inFlight.evidentMessageId,
|
|
7431
|
+
event.sessionId,
|
|
7432
|
+
`OpenCode could not run this turn: ${event.reason}`
|
|
7433
|
+
);
|
|
7434
|
+
inFlight.done = true;
|
|
7435
|
+
this.removeInFlight(watcher, inFlight.evidentMessageId);
|
|
7436
|
+
} catch (err) {
|
|
7437
|
+
if (err instanceof ChannelAuthError) {
|
|
7438
|
+
this.log({
|
|
7439
|
+
level: "warn",
|
|
7440
|
+
message: `OpenCode session error could not mark message ${inFlight.evidentMessageId.slice(0, 8)} failed because authentication failed: ${err.message}; leaving it to transcript polling / the existing give-up path`,
|
|
7441
|
+
conversation_id: watcher.conv.id,
|
|
7442
|
+
message_id: inFlight.evidentMessageId
|
|
7443
|
+
});
|
|
7444
|
+
} else {
|
|
7445
|
+
this.log({
|
|
7446
|
+
level: "warn",
|
|
7447
|
+
message: `OpenCode session error could not mark message ${inFlight.evidentMessageId.slice(0, 8)} failed: ${err instanceof Error ? err.message : String(err)}; leaving it to transcript polling / the existing give-up path`,
|
|
7448
|
+
conversation_id: watcher.conv.id,
|
|
7449
|
+
message_id: inFlight.evidentMessageId
|
|
7450
|
+
});
|
|
7451
|
+
}
|
|
7452
|
+
} finally {
|
|
7453
|
+
this.sessionErrorHandled.delete(inFlight.evidentMessageId);
|
|
7454
|
+
}
|
|
7455
|
+
}
|
|
7121
7456
|
/**
|
|
7122
7457
|
* The per-session polling loop (WI-3). Once per tick it:
|
|
7123
7458
|
* 1. polls `GET /session/:id/message` once and, per in-flight message,
|
|
@@ -7230,6 +7565,21 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
7230
7565
|
const conv = watcher.conv;
|
|
7231
7566
|
const state = messageRunState(messages, inFlight.opencodeMessageId);
|
|
7232
7567
|
const id = inFlight.evidentMessageId;
|
|
7568
|
+
if (messages && collectTaskCalls(messages, inFlight.opencodeMessageId).length > 0 && !this.subagentInvocationPrefetches.has(id)) {
|
|
7569
|
+
void this.resolveSubagentInvocations(
|
|
7570
|
+
messages,
|
|
7571
|
+
inFlight.opencodeMessageId,
|
|
7572
|
+
id,
|
|
7573
|
+
"prefetch"
|
|
7574
|
+
).catch((err) => {
|
|
7575
|
+
this.log({
|
|
7576
|
+
level: "warn",
|
|
7577
|
+
message: `Best-effort subagent usage prefetch failed for message ${id.slice(0, 8)} \u2014 omitting invocation telemetry: ${err instanceof Error ? err.message : String(err)}`,
|
|
7578
|
+
conversation_id: conv.id,
|
|
7579
|
+
message_id: id
|
|
7580
|
+
});
|
|
7581
|
+
});
|
|
7582
|
+
}
|
|
7233
7583
|
if (openQuestions.has(id)) inFlight.pausedOnQuestion = true;
|
|
7234
7584
|
else if (questionsPolledOk) inFlight.pausedOnQuestion = false;
|
|
7235
7585
|
if (openPermissions.has(id)) inFlight.pausedOnPermission = true;
|
|
@@ -7283,6 +7633,12 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
7283
7633
|
message_id: inFlight.evidentMessageId
|
|
7284
7634
|
});
|
|
7285
7635
|
const usage = messageUsage(messages, inFlight.opencodeMessageId);
|
|
7636
|
+
const usageAgentName = this.usageAgentName(messages ?? [], inFlight.opencodeMessageId);
|
|
7637
|
+
const subagentInvocations = await this.resolveSubagentInvocations(
|
|
7638
|
+
messages,
|
|
7639
|
+
inFlight.opencodeMessageId,
|
|
7640
|
+
inFlight.evidentMessageId
|
|
7641
|
+
);
|
|
7286
7642
|
const failure = await this.classifyModelAuthFailure(messages, inFlight.opencodeMessageId);
|
|
7287
7643
|
try {
|
|
7288
7644
|
await this.markFailed(
|
|
@@ -7291,7 +7647,9 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
7291
7647
|
sessionId,
|
|
7292
7648
|
error2,
|
|
7293
7649
|
usage,
|
|
7294
|
-
failure
|
|
7650
|
+
failure,
|
|
7651
|
+
usageAgentName,
|
|
7652
|
+
subagentInvocations
|
|
7295
7653
|
);
|
|
7296
7654
|
} catch (err) {
|
|
7297
7655
|
if (err instanceof ChannelAuthError) throw err;
|
|
@@ -7334,9 +7692,11 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
7334
7692
|
this.removeInFlight(watcher, inFlight.evidentMessageId);
|
|
7335
7693
|
return;
|
|
7336
7694
|
}
|
|
7695
|
+
const siblingOcIds = this.siblingOpencodeMessageIds(watcher, inFlight.evidentMessageId);
|
|
7696
|
+
const skippedByOpencode = state === "queued" && hasLaterSiblingTurnStarted(messages, inFlight.opencodeMessageId, siblingOcIds);
|
|
7337
7697
|
const pastStuckBound = this.now() - inFlight.dispatchedAt >= this.stuckQueuedMs;
|
|
7338
7698
|
const sessionIdle = state === "queued" && !hasRunningAssistantExcept(messages, inFlight.opencodeMessageId);
|
|
7339
|
-
if (state === "queued" && pastStuckBound && sessionIdle && !inFlight.stuckReported) {
|
|
7699
|
+
if (state === "queued" && pastStuckBound && (sessionIdle || skippedByOpencode) && !inFlight.stuckReported) {
|
|
7340
7700
|
inFlight.stuckReported = true;
|
|
7341
7701
|
void this.postSignal(conv.id, inFlight.evidentMessageId, "stuck_queued", {
|
|
7342
7702
|
stuck_for_ms: this.now() - inFlight.dispatchedAt
|
|
@@ -7497,7 +7857,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
7497
7857
|
const hasActivelyRunningSibling = [...watcher.inFlight.values()].some(
|
|
7498
7858
|
(sib) => sib.evidentMessageId !== inFlight.evidentMessageId && messageRunState(messages, sib.opencodeMessageId) === "running" && !siblingPaused(sib)
|
|
7499
7859
|
);
|
|
7500
|
-
const queuedBehindRunningSibling = state === "queued" && hasActivelyRunningSibling;
|
|
7860
|
+
const queuedBehindRunningSibling = state === "queued" && hasActivelyRunningSibling && !skippedByOpencode;
|
|
7501
7861
|
if (!activelyRunning && !queuedBehindRunningSibling && this.now() >= inFlight.deadline) {
|
|
7502
7862
|
this.log({
|
|
7503
7863
|
level: "debug",
|
|
@@ -7532,6 +7892,12 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
7532
7892
|
});
|
|
7533
7893
|
const title = await this.resolveSessionTitle(sessionId, watcher.conv.id);
|
|
7534
7894
|
const usage = messageUsage(messages, inFlight.opencodeMessageId);
|
|
7895
|
+
const usageAgentName = this.usageAgentName(messages ?? [], inFlight.opencodeMessageId);
|
|
7896
|
+
const subagentInvocations = await this.resolveSubagentInvocations(
|
|
7897
|
+
messages,
|
|
7898
|
+
inFlight.opencodeMessageId,
|
|
7899
|
+
inFlight.evidentMessageId
|
|
7900
|
+
);
|
|
7535
7901
|
try {
|
|
7536
7902
|
await this.markDone(
|
|
7537
7903
|
conv.id,
|
|
@@ -7539,7 +7905,9 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
7539
7905
|
sessionId,
|
|
7540
7906
|
inFlight.opencodeMessageId,
|
|
7541
7907
|
title,
|
|
7542
|
-
usage
|
|
7908
|
+
usage,
|
|
7909
|
+
usageAgentName,
|
|
7910
|
+
subagentInvocations
|
|
7543
7911
|
);
|
|
7544
7912
|
} catch (err) {
|
|
7545
7913
|
if (err instanceof ChannelAuthError) throw err;
|
|
@@ -7718,6 +8086,12 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
7718
8086
|
if (state === "failed" && !restartAborted) {
|
|
7719
8087
|
const error2 = messageError(messages, ocId ?? "") ?? void 0;
|
|
7720
8088
|
const usage = messageUsage(messages, ocId ?? "");
|
|
8089
|
+
const usageAgentName = this.usageAgentName(messages, ocId ?? "");
|
|
8090
|
+
const subagentInvocations = await this.resolveSubagentInvocations(
|
|
8091
|
+
messages,
|
|
8092
|
+
ocId ?? "",
|
|
8093
|
+
row.id
|
|
8094
|
+
);
|
|
7721
8095
|
const failure = await this.classifyModelAuthFailure(messages, ocId ?? "");
|
|
7722
8096
|
this.log({
|
|
7723
8097
|
level: "error",
|
|
@@ -7726,7 +8100,16 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
7726
8100
|
message_id: row.id
|
|
7727
8101
|
});
|
|
7728
8102
|
try {
|
|
7729
|
-
await this.markFailed(
|
|
8103
|
+
await this.markFailed(
|
|
8104
|
+
row.conversation_id,
|
|
8105
|
+
row.id,
|
|
8106
|
+
sessionId,
|
|
8107
|
+
error2,
|
|
8108
|
+
usage,
|
|
8109
|
+
failure,
|
|
8110
|
+
usageAgentName,
|
|
8111
|
+
subagentInvocations
|
|
8112
|
+
);
|
|
7730
8113
|
} catch (err) {
|
|
7731
8114
|
if (err instanceof ChannelAuthError) throw err;
|
|
7732
8115
|
if (err instanceof ChannelTerminalError) {
|
|
@@ -7888,7 +8271,22 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
7888
8271
|
try {
|
|
7889
8272
|
const title = await this.resolveSessionTitle(sessionId, row.conversation_id);
|
|
7890
8273
|
const usage = messageUsage(messages, ocId ?? "");
|
|
7891
|
-
|
|
8274
|
+
const usageAgentName = this.usageAgentName(messages, ocId ?? "");
|
|
8275
|
+
const subagentInvocations = await this.resolveSubagentInvocations(
|
|
8276
|
+
messages,
|
|
8277
|
+
ocId ?? "",
|
|
8278
|
+
row.id
|
|
8279
|
+
);
|
|
8280
|
+
await this.markDone(
|
|
8281
|
+
row.conversation_id,
|
|
8282
|
+
row.id,
|
|
8283
|
+
sessionId,
|
|
8284
|
+
ocId,
|
|
8285
|
+
title,
|
|
8286
|
+
usage,
|
|
8287
|
+
usageAgentName,
|
|
8288
|
+
subagentInvocations
|
|
8289
|
+
);
|
|
7892
8290
|
} catch (err) {
|
|
7893
8291
|
if (err instanceof ChannelAuthError) throw err;
|
|
7894
8292
|
if (err instanceof ChannelTerminalError) {
|
|
@@ -8292,6 +8690,166 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8292
8690
|
if (parent !== void 0) this.sessionParents.set(sessionId, parent);
|
|
8293
8691
|
return parent;
|
|
8294
8692
|
}
|
|
8693
|
+
usageAgentName(messages, userMessageId) {
|
|
8694
|
+
const reply = findLastAssistantReplyFor(messages, userMessageId);
|
|
8695
|
+
const mode = reply?.info?.mode;
|
|
8696
|
+
if (typeof mode === "string" && mode.length > 0) return mode;
|
|
8697
|
+
const agent = reply?.info?.agent;
|
|
8698
|
+
return typeof agent === "string" && agent.length > 0 ? agent : null;
|
|
8699
|
+
}
|
|
8700
|
+
async resolveSubagentInvocations(messages, userMessageId, messageId, phase = "terminal") {
|
|
8701
|
+
if (!messages) return void 0;
|
|
8702
|
+
const cache = phase === "prefetch" ? this.subagentInvocationPrefetches : this.subagentInvocationCollections;
|
|
8703
|
+
const cached = cache.get(messageId);
|
|
8704
|
+
if (cached) return cached;
|
|
8705
|
+
const collection = this.buildSubagentInvocations(messages, userMessageId, messageId).catch(
|
|
8706
|
+
(err) => {
|
|
8707
|
+
this.log({
|
|
8708
|
+
level: "warn",
|
|
8709
|
+
message: `Best-effort subagent usage collection failed for message ${messageId.slice(0, 8)} \u2014 omitting invocation telemetry: ${err instanceof Error ? err.message : String(err)}`,
|
|
8710
|
+
message_id: messageId
|
|
8711
|
+
});
|
|
8712
|
+
return void 0;
|
|
8713
|
+
}
|
|
8714
|
+
);
|
|
8715
|
+
cache.set(messageId, collection);
|
|
8716
|
+
const result = await collection;
|
|
8717
|
+
if (result === void 0 && cache.get(messageId) === collection) cache.delete(messageId);
|
|
8718
|
+
return result;
|
|
8719
|
+
}
|
|
8720
|
+
clearSubagentInvocationCaches(messageId) {
|
|
8721
|
+
this.subagentInvocationCollections.delete(messageId);
|
|
8722
|
+
this.subagentInvocationPrefetches.delete(messageId);
|
|
8723
|
+
}
|
|
8724
|
+
async buildSubagentInvocations(messages, userMessageId, messageId) {
|
|
8725
|
+
const rootCalls = collectTaskCalls(messages, userMessageId);
|
|
8726
|
+
if (rootCalls.length === 0) return void 0;
|
|
8727
|
+
const childMessages = /* @__PURE__ */ new Map();
|
|
8728
|
+
const seenCallIds = new Set(rootCalls.map((call) => call.callID));
|
|
8729
|
+
const work = rootCalls.map((call) => ({
|
|
8730
|
+
call,
|
|
8731
|
+
depth: 1
|
|
8732
|
+
}));
|
|
8733
|
+
const payload = [];
|
|
8734
|
+
const fetchChildMessages = (sessionId) => {
|
|
8735
|
+
const cached = childMessages.get(sessionId);
|
|
8736
|
+
if (cached) return cached;
|
|
8737
|
+
const pending = (async () => {
|
|
8738
|
+
try {
|
|
8739
|
+
const res = await this.fetchImpl(`${this.opencodeBase}/session/${sessionId}/message`);
|
|
8740
|
+
if (!res.ok) {
|
|
8741
|
+
this.log({
|
|
8742
|
+
level: "warn",
|
|
8743
|
+
message: `Best-effort subagent session fetch for message ${messageId.slice(0, 8)} and child session ${sessionId.slice(0, 8)} returned HTTP ${res.status} \u2014 omitting invocation telemetry`,
|
|
8744
|
+
message_id: messageId
|
|
8745
|
+
});
|
|
8746
|
+
return null;
|
|
8747
|
+
}
|
|
8748
|
+
const body = await res.json();
|
|
8749
|
+
if (!Array.isArray(body)) throw new Error("response body was not a message array");
|
|
8750
|
+
return body;
|
|
8751
|
+
} catch (err) {
|
|
8752
|
+
this.log({
|
|
8753
|
+
level: "warn",
|
|
8754
|
+
message: `Best-effort subagent session fetch failed for message ${messageId.slice(0, 8)} and child session ${sessionId.slice(0, 8)} \u2014 omitting invocation telemetry: ${err instanceof Error ? err.message : String(err)}`,
|
|
8755
|
+
message_id: messageId
|
|
8756
|
+
});
|
|
8757
|
+
return null;
|
|
8758
|
+
}
|
|
8759
|
+
})();
|
|
8760
|
+
childMessages.set(sessionId, pending);
|
|
8761
|
+
return pending;
|
|
8762
|
+
};
|
|
8763
|
+
const fetchChildWithoutBlocking = async (sessionId) => {
|
|
8764
|
+
const pending = fetchChildMessages(sessionId);
|
|
8765
|
+
let timer;
|
|
8766
|
+
const timeout = new Promise((resolve4) => {
|
|
8767
|
+
timer = setTimeout(() => {
|
|
8768
|
+
this.log({
|
|
8769
|
+
level: "warn",
|
|
8770
|
+
message: `Best-effort subagent session fetch for message ${messageId.slice(0, 8)} and child session ${sessionId.slice(0, 8)} was slow \u2014 omitting invocation telemetry without delaying completion`,
|
|
8771
|
+
message_id: messageId
|
|
8772
|
+
});
|
|
8773
|
+
resolve4(null);
|
|
8774
|
+
}, SUBAGENT_TELEMETRY_FETCH_TIMEOUT_MS);
|
|
8775
|
+
});
|
|
8776
|
+
try {
|
|
8777
|
+
return await Promise.race([pending, timeout]);
|
|
8778
|
+
} finally {
|
|
8779
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
8780
|
+
}
|
|
8781
|
+
};
|
|
8782
|
+
while (work.length > 0) {
|
|
8783
|
+
const groups = /* @__PURE__ */ new Map();
|
|
8784
|
+
for (const item of work.splice(0)) {
|
|
8785
|
+
const group = groups.get(item.call.childSessionId) ?? [];
|
|
8786
|
+
group.push(item);
|
|
8787
|
+
groups.set(item.call.childSessionId, group);
|
|
8788
|
+
}
|
|
8789
|
+
const groupResults = await Promise.all(
|
|
8790
|
+
[...groups].map(async ([sessionId, items]) => ({
|
|
8791
|
+
sessionId,
|
|
8792
|
+
items,
|
|
8793
|
+
messages: sessionId === null ? [] : await fetchChildWithoutBlocking(sessionId)
|
|
8794
|
+
}))
|
|
8795
|
+
);
|
|
8796
|
+
for (const { sessionId, items, messages: child } of groupResults) {
|
|
8797
|
+
if (sessionId !== null && child === null) continue;
|
|
8798
|
+
const attribution = sessionId === null ? { invocations: [], unattributed: [] } : attributeTaskCallUsage(
|
|
8799
|
+
child,
|
|
8800
|
+
items.map(({ call }) => ({
|
|
8801
|
+
callID: call.callID,
|
|
8802
|
+
timeStart: call.timeStart,
|
|
8803
|
+
timeEnd: call.timeEnd
|
|
8804
|
+
}))
|
|
8805
|
+
);
|
|
8806
|
+
if (sessionId !== null && attribution.unattributed.length > 0) {
|
|
8807
|
+
this.log({
|
|
8808
|
+
level: "warn",
|
|
8809
|
+
message: `Omitted ${attribution.unattributed.length} unattributable assistant message(s) from subagent usage for message ${messageId.slice(0, 8)} and child session ${sessionId.slice(0, 8)} \u2014 assigned to no invocation window`,
|
|
8810
|
+
message_id: messageId
|
|
8811
|
+
});
|
|
8812
|
+
}
|
|
8813
|
+
const usageByCall = new Map(
|
|
8814
|
+
attribution.invocations.map((invocation) => [invocation.callID, invocation.usage])
|
|
8815
|
+
);
|
|
8816
|
+
const messagesByCall = new Map(
|
|
8817
|
+
attribution.invocations.map((invocation) => [invocation.callID, invocation.messages])
|
|
8818
|
+
);
|
|
8819
|
+
for (const { call, depth } of items) {
|
|
8820
|
+
const usage = usageByCall.get(call.callID) ?? null;
|
|
8821
|
+
payload.push({
|
|
8822
|
+
tool_call_id: call.callID,
|
|
8823
|
+
agent_name: call.subagentName,
|
|
8824
|
+
opencode_session_id: call.childSessionId,
|
|
8825
|
+
parent_opencode_session_id: call.parentSessionId,
|
|
8826
|
+
depth,
|
|
8827
|
+
status: call.status,
|
|
8828
|
+
started_at: call.timeStart === null ? null : new Date(call.timeStart).toISOString(),
|
|
8829
|
+
ended_at: call.timeEnd === null ? null : new Date(call.timeEnd).toISOString(),
|
|
8830
|
+
usage_provider_id: usage?.usage_provider_id ?? call.model?.providerID ?? null,
|
|
8831
|
+
usage_model_id: usage?.usage_model_id ?? call.model?.modelID ?? null,
|
|
8832
|
+
usage_tokens_input: usage?.usage_tokens_input ?? null,
|
|
8833
|
+
usage_tokens_output: usage?.usage_tokens_output ?? null,
|
|
8834
|
+
usage_tokens_reasoning: usage?.usage_tokens_reasoning ?? null,
|
|
8835
|
+
usage_tokens_cache_read: usage?.usage_tokens_cache_read ?? null,
|
|
8836
|
+
usage_tokens_cache_write: usage?.usage_tokens_cache_write ?? null,
|
|
8837
|
+
usage_cost_usd: usage?.usage_cost_usd ?? null
|
|
8838
|
+
});
|
|
8839
|
+
for (const assigned of messagesByCall.get(call.callID) ?? []) {
|
|
8840
|
+
const parentId = assigned.info?.parentID ?? assigned.parentID;
|
|
8841
|
+
if (!parentId) continue;
|
|
8842
|
+
for (const nested of collectTaskCalls([assigned], parentId)) {
|
|
8843
|
+
if (seenCallIds.has(nested.callID)) continue;
|
|
8844
|
+
seenCallIds.add(nested.callID);
|
|
8845
|
+
work.push({ call: nested, depth: depth + 1 });
|
|
8846
|
+
}
|
|
8847
|
+
}
|
|
8848
|
+
}
|
|
8849
|
+
}
|
|
8850
|
+
}
|
|
8851
|
+
return payload.length > 0 ? payload : void 0;
|
|
8852
|
+
}
|
|
8295
8853
|
/**
|
|
8296
8854
|
* OpenCode's synchronous default session title (e.g.
|
|
8297
8855
|
* `"New session - 1737800000000"`), assigned immediately when a session is
|
|
@@ -8775,7 +9333,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8775
9333
|
* watcher retries next tick within the
|
|
8776
9334
|
* deadline, Finding 4).
|
|
8777
9335
|
*/
|
|
8778
|
-
async markDone(conversationId, messageId, sessionId, opencodeMessageId, title, usage) {
|
|
9336
|
+
async markDone(conversationId, messageId, sessionId, opencodeMessageId, title, usage, usageAgentName, subagentInvocations) {
|
|
8779
9337
|
const res = await this.fetchImpl(
|
|
8780
9338
|
`${this.apiUrl}/runners/${this.agentId}/threads/${conversationId}/messages/${messageId}`,
|
|
8781
9339
|
{
|
|
@@ -8791,15 +9349,21 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8791
9349
|
opencode_session_id: sessionId,
|
|
8792
9350
|
...opencodeMessageId ? { opencode_message_id: opencodeMessageId } : {},
|
|
8793
9351
|
...title ? { title } : {},
|
|
8794
|
-
...usage ? usage : {}
|
|
9352
|
+
...usage ? usage : {},
|
|
9353
|
+
...usageAgentName ? { usage_agent_name: usageAgentName } : {},
|
|
9354
|
+
...subagentInvocations && subagentInvocations.length > 0 ? { subagent_invocations: subagentInvocations } : {}
|
|
8795
9355
|
})
|
|
8796
9356
|
}
|
|
8797
9357
|
);
|
|
8798
9358
|
this.assertAuth(res, "marking message as done");
|
|
8799
|
-
if (res.ok)
|
|
9359
|
+
if (res.ok) {
|
|
9360
|
+
this.clearSubagentInvocationCaches(messageId);
|
|
9361
|
+
return;
|
|
9362
|
+
}
|
|
8800
9363
|
if (isRetryableStatus(res.status)) {
|
|
8801
9364
|
throw new Error(`marking message as done: HTTP ${res.status}`);
|
|
8802
9365
|
}
|
|
9366
|
+
this.clearSubagentInvocationCaches(messageId);
|
|
8803
9367
|
throw new ChannelTerminalError(`marking message as done: HTTP ${res.status}`, res.status);
|
|
8804
9368
|
}
|
|
8805
9369
|
/**
|
|
@@ -8814,7 +9378,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8814
9378
|
* exists but is wedged, so the next attempt must get a fresh one
|
|
8815
9379
|
* instead of reusing it — see WI-1's server-side null-clearing PATCH).
|
|
8816
9380
|
*/
|
|
8817
|
-
async markFailed(conversationId, messageId, sessionId, error2, usage, failure) {
|
|
9381
|
+
async markFailed(conversationId, messageId, sessionId, error2, usage, failure, usageAgentName, subagentInvocations) {
|
|
8818
9382
|
const body = { status: "failed" };
|
|
8819
9383
|
if (sessionId === null) {
|
|
8820
9384
|
body.opencode_session_id = null;
|
|
@@ -8823,23 +9387,33 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8823
9387
|
}
|
|
8824
9388
|
if (error2 !== void 0) body.error = error2;
|
|
8825
9389
|
if (usage) Object.assign(body, usage);
|
|
9390
|
+
if (usageAgentName) body.usage_agent_name = usageAgentName;
|
|
9391
|
+
if (subagentInvocations && subagentInvocations.length > 0) {
|
|
9392
|
+
body.subagent_invocations = subagentInvocations;
|
|
9393
|
+
}
|
|
8826
9394
|
if (failure) {
|
|
8827
9395
|
body.failure_kind = failure.kind;
|
|
8828
9396
|
body.failure_provider_id = failure.providerId;
|
|
8829
9397
|
body.failure_model_id = failure.modelId;
|
|
8830
9398
|
body.failure_reason = failure.reason;
|
|
8831
9399
|
}
|
|
8832
|
-
|
|
8833
|
-
|
|
8834
|
-
|
|
8835
|
-
|
|
8836
|
-
|
|
8837
|
-
|
|
8838
|
-
|
|
8839
|
-
|
|
8840
|
-
|
|
8841
|
-
|
|
8842
|
-
|
|
9400
|
+
try {
|
|
9401
|
+
await this.callWithRetry(
|
|
9402
|
+
"marking message as failed",
|
|
9403
|
+
() => this.fetchImpl(
|
|
9404
|
+
`${this.apiUrl}/runners/${this.agentId}/threads/${conversationId}/messages/${messageId}`,
|
|
9405
|
+
{
|
|
9406
|
+
method: "PATCH",
|
|
9407
|
+
headers: { Authorization: this.getAuthHeader(), "Content-Type": "application/json" },
|
|
9408
|
+
body: JSON.stringify(body)
|
|
9409
|
+
}
|
|
9410
|
+
)
|
|
9411
|
+
);
|
|
9412
|
+
} catch (err) {
|
|
9413
|
+
if (err instanceof ChannelTerminalError) this.clearSubagentInvocationCaches(messageId);
|
|
9414
|
+
throw err;
|
|
9415
|
+
}
|
|
9416
|
+
this.clearSubagentInvocationCaches(messageId);
|
|
8843
9417
|
}
|
|
8844
9418
|
/**
|
|
8845
9419
|
* Classify an errored turn as a model-auth failure (#736 P1-4), for `markFailed`.
|
|
@@ -10635,14 +11209,11 @@ function scheduleClaudeUsageReporting(state, options) {
|
|
|
10635
11209
|
report: (usage) => reportClaudeUsage(state.agentId, state.authHeader, usage),
|
|
10636
11210
|
isLocalCredentialProblem,
|
|
10637
11211
|
forcedOnHint: "run `claude` to sign in",
|
|
10638
|
-
firstDelayMs:
|
|
10639
|
-
nextDelayMs:
|
|
10640
|
-
failureLogLevel:
|
|
11212
|
+
firstDelayMs: firstReportDelayMs,
|
|
11213
|
+
nextDelayMs: usageReportDelayMs,
|
|
11214
|
+
failureLogLevel: usageReportFailureLogLevel
|
|
10641
11215
|
});
|
|
10642
11216
|
}
|
|
10643
|
-
var RESOURCE_USAGE_BASE_REPORT_DELAY_MS = 10 * 6e4;
|
|
10644
|
-
var RESOURCE_USAGE_REPORT_DELAY_JITTER_FRACTION = 0.2;
|
|
10645
|
-
var RESOURCE_USAGE_FAILURE_REESCALATION_TICKS = 6;
|
|
10646
11217
|
function scheduleResourceUsageReporting(state, options) {
|
|
10647
11218
|
const { enabled, warnings } = resolveResourceUsageReportingEnabled(
|
|
10648
11219
|
options.resourceUsageReporting,
|
|
@@ -10695,10 +11266,7 @@ function scheduleResourceUsageReporting(state, options) {
|
|
|
10695
11266
|
consecutiveFailures++;
|
|
10696
11267
|
logActivity(state, {
|
|
10697
11268
|
type: "info",
|
|
10698
|
-
level:
|
|
10699
|
-
consecutiveFailures,
|
|
10700
|
-
RESOURCE_USAGE_FAILURE_REESCALATION_TICKS
|
|
10701
|
-
),
|
|
11269
|
+
level: usageReportFailureLogLevel(consecutiveFailures),
|
|
10702
11270
|
message: `Failed to report resource usage: ${result.error}${failureStreakSuffix(consecutiveFailures)}`
|
|
10703
11271
|
});
|
|
10704
11272
|
}
|
|
@@ -10707,20 +11275,11 @@ function scheduleResourceUsageReporting(state, options) {
|
|
|
10707
11275
|
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
10708
11276
|
logActivity(state, {
|
|
10709
11277
|
type: "info",
|
|
10710
|
-
level:
|
|
10711
|
-
consecutiveFailures,
|
|
10712
|
-
RESOURCE_USAGE_FAILURE_REESCALATION_TICKS
|
|
10713
|
-
),
|
|
11278
|
+
level: usageReportFailureLogLevel(consecutiveFailures),
|
|
10714
11279
|
message: `Resource usage reporting failed: ${message}${failureStreakSuffix(consecutiveFailures)}`
|
|
10715
11280
|
});
|
|
10716
11281
|
} finally {
|
|
10717
|
-
state.resourceUsageTimer = setTimeout(
|
|
10718
|
-
() => void tick(),
|
|
10719
|
-
jitteredDelayMs(
|
|
10720
|
-
RESOURCE_USAGE_BASE_REPORT_DELAY_MS,
|
|
10721
|
-
RESOURCE_USAGE_REPORT_DELAY_JITTER_FRACTION
|
|
10722
|
-
)
|
|
10723
|
-
);
|
|
11282
|
+
state.resourceUsageTimer = setTimeout(() => void tick(), usageReportDelayMs());
|
|
10724
11283
|
}
|
|
10725
11284
|
};
|
|
10726
11285
|
state.resourceUsageTimer = setTimeout(() => void tick(), firstReportDelayMs());
|
|
@@ -11267,6 +11826,7 @@ async function run(options) {
|
|
|
11267
11826
|
logActivity(state, { type: "info", level: "warn", message: versionWarning });
|
|
11268
11827
|
}
|
|
11269
11828
|
}
|
|
11829
|
+
await reloadProviderCache(state.port);
|
|
11270
11830
|
const noProviderWarning = buildNoProviderWarning(
|
|
11271
11831
|
await hasAnyConfiguredProvider(state.port)
|
|
11272
11832
|
);
|