@ordewell/core 0.10.9 → 0.10.10
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/{IFileSystem-C9W5mRYA.d.mts → IFileSystem-1PqRCjhu.d.mts} +21 -3
- package/dist/{IFileSystem-B8qRNC_6.d.ts → IFileSystem-lTRL_G9Z.d.ts} +21 -3
- package/dist/{ModeResolver-C1PN5FYG.d.mts → ModeResolver-6TQXVsde.d.mts} +1 -1
- package/dist/{ModeResolver-CaPnoU8N.d.ts → ModeResolver-CoJAJD-f.d.ts} +1 -1
- package/dist/{Task-hFfRS9rN.d.mts → Task-CfBM0wkV.d.mts} +94 -21
- package/dist/{Task-hFfRS9rN.d.ts → Task-CfBM0wkV.d.ts} +94 -21
- package/dist/{chunk-H7QX32BH.mjs → chunk-24EL4Z2W.mjs} +2 -2
- package/dist/{chunk-O6LQU3W6.mjs → chunk-ESKK5I4F.mjs} +138 -21
- package/dist/chunk-ESKK5I4F.mjs.map +1 -0
- package/dist/{chunk-LTHKFBKM.mjs → chunk-KWS3VEER.mjs} +3 -3
- package/dist/{chunk-EEE2GBLG.mjs → chunk-PFH7WFIB.mjs} +1 -1
- package/dist/chunk-PFH7WFIB.mjs.map +1 -0
- package/dist/index.d.mts +119 -17
- package/dist/index.d.ts +119 -17
- package/dist/index.js +521 -88
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +397 -72
- package/dist/index.mjs.map +1 -1
- package/dist/parsing.d.mts +2 -2
- package/dist/parsing.d.ts +2 -2
- package/dist/parsing.js.map +1 -1
- package/dist/parsing.mjs +3 -3
- package/dist/plan-utils.d.mts +4 -4
- package/dist/plan-utils.d.ts +4 -4
- package/dist/plan-utils.js +144 -18
- package/dist/plan-utils.js.map +1 -1
- package/dist/plan-utils.mjs +22 -4
- package/dist/{runSettingsText-rLoYQlUH.d.mts → runSettingsText-C1i9wBG1.d.mts} +26 -15
- package/dist/{runSettingsText-CGKX3aeV.d.ts → runSettingsText-CWNrmF5U.d.ts} +26 -15
- package/dist/testing.d.mts +7 -2
- package/dist/testing.d.ts +7 -2
- package/dist/testing.js +11 -0
- package/dist/testing.js.map +1 -1
- package/dist/testing.mjs +12 -1
- package/dist/testing.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-EEE2GBLG.mjs.map +0 -1
- package/dist/chunk-O6LQU3W6.mjs.map +0 -1
- /package/dist/{chunk-H7QX32BH.mjs.map → chunk-24EL4Z2W.mjs.map} +0 -0
- /package/dist/{chunk-LTHKFBKM.mjs.map → chunk-KWS3VEER.mjs.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -203,7 +203,10 @@ __export(index_exports, {
|
|
|
203
203
|
collectProviderCredentials: () => collectProviderCredentials,
|
|
204
204
|
commandAllowedBy: () => commandAllowedBy,
|
|
205
205
|
configuredProviders: () => configuredProviders,
|
|
206
|
+
contextMeterText: () => contextMeterText,
|
|
207
|
+
contextMeterTitle: () => contextMeterTitle,
|
|
206
208
|
continuability: () => continuability,
|
|
209
|
+
costText: () => costText,
|
|
207
210
|
countedSessionCost: () => countedSessionCost,
|
|
208
211
|
createAiService: () => createAiService,
|
|
209
212
|
createEmptyPlan: () => createEmptyPlan,
|
|
@@ -246,7 +249,9 @@ __export(index_exports, {
|
|
|
246
249
|
flattenTasksWithParents: () => flattenTasksWithParents,
|
|
247
250
|
followTaskLog: () => followTaskLog,
|
|
248
251
|
followTurn: () => followTurn,
|
|
252
|
+
formatCost: () => formatCost,
|
|
249
253
|
formatSearchOutput: () => formatSearchOutput,
|
|
254
|
+
formatTokenCount: () => formatTokenCount,
|
|
250
255
|
fromTranscript: () => fromTranscript,
|
|
251
256
|
getLatestSession: () => getLatestSession,
|
|
252
257
|
getProviderMeta: () => getProviderMeta,
|
|
@@ -298,6 +303,7 @@ __export(index_exports, {
|
|
|
298
303
|
migratePlanState: () => migratePlanState,
|
|
299
304
|
mintDaemonToken: () => mintDaemonToken,
|
|
300
305
|
mintSessionId: () => mintSessionId,
|
|
306
|
+
modelCostTitle: () => modelCostTitle,
|
|
301
307
|
namedRepos: () => namedRepos,
|
|
302
308
|
newTaskFields: () => newTaskFields,
|
|
303
309
|
normalizeAgentArgs: () => normalizeAgentArgs,
|
|
@@ -383,6 +389,7 @@ __export(index_exports, {
|
|
|
383
389
|
stopTurn: () => stopTurn,
|
|
384
390
|
stripAnsi: () => stripAnsi,
|
|
385
391
|
stripModelPrefix: () => stripModelPrefix,
|
|
392
|
+
subagentCostTitle: () => subagentCostTitle,
|
|
386
393
|
subtreeIds: () => subtreeIds,
|
|
387
394
|
summarizeToolCall: () => summarizeToolCall,
|
|
388
395
|
supervisorSummary: () => supervisorSummary,
|
|
@@ -401,6 +408,7 @@ __export(index_exports, {
|
|
|
401
408
|
taskStartedNotice: () => taskStartedNotice,
|
|
402
409
|
taskStatusKind: () => taskStatusKind,
|
|
403
410
|
taskUsageSummary: () => taskUsageSummary,
|
|
411
|
+
tasksTotalText: () => tasksTotalText,
|
|
404
412
|
textHasTaskOps: () => textHasTaskOps,
|
|
405
413
|
textHasTaskQuery: () => textHasTaskQuery,
|
|
406
414
|
titledRefs: () => titledRefs,
|
|
@@ -418,6 +426,7 @@ __export(index_exports, {
|
|
|
418
426
|
unsendAll: () => unsendAll,
|
|
419
427
|
unsendLatest: () => unsendLatest,
|
|
420
428
|
usageLine: () => usageLine,
|
|
429
|
+
usageText: () => usageText,
|
|
421
430
|
waitsForApproval: () => waitsForApproval,
|
|
422
431
|
walkPlan: () => walkPlan,
|
|
423
432
|
wellKnownBinDirs: () => wellKnownBinDirs,
|
|
@@ -880,25 +889,66 @@ function addUsage(totals, record) {
|
|
|
880
889
|
}
|
|
881
890
|
if (record.reportedCost) {
|
|
882
891
|
const { amount, currency } = record.reportedCost;
|
|
883
|
-
|
|
892
|
+
const provenance = record.reportedCost.kind || record.sessionBilling ? {
|
|
893
|
+
kind: record.reportedCost.kind ?? "unknown",
|
|
894
|
+
billing: record.sessionBilling?.type ?? "unknown",
|
|
895
|
+
...record.reportedCost.basis ? { basis: record.reportedCost.basis } : {}
|
|
896
|
+
} : void 0;
|
|
897
|
+
addCost(next, currency, amount, provenance);
|
|
884
898
|
}
|
|
885
899
|
return next;
|
|
886
900
|
}
|
|
901
|
+
function addCost(totals, currency, amount, provenance) {
|
|
902
|
+
const hadCost = totals.reportedCost?.[currency] !== void 0;
|
|
903
|
+
const previous = totals.costProvenance?.[currency];
|
|
904
|
+
totals.reportedCost = { ...totals.reportedCost, [currency]: (totals.reportedCost?.[currency] ?? 0) + amount };
|
|
905
|
+
if (!previous && !provenance) return;
|
|
906
|
+
const unknown = { kind: "unknown", billing: "unknown" };
|
|
907
|
+
const a = previous ?? unknown;
|
|
908
|
+
const b = provenance ?? unknown;
|
|
909
|
+
const merged = !hadCost ? b : {
|
|
910
|
+
kind: a.kind === "estimate" || b.kind === "estimate" ? "estimate" : a.kind === "billed" && b.kind === "billed" ? "billed" : "unknown",
|
|
911
|
+
billing: a.billing === b.billing ? a.billing : "mixed",
|
|
912
|
+
...a.basis && a.basis === b.basis ? { basis: a.basis } : {}
|
|
913
|
+
};
|
|
914
|
+
totals.costProvenance = { ...totals.costProvenance, [currency]: merged };
|
|
915
|
+
}
|
|
887
916
|
function addPlannerUsage(usage, record) {
|
|
888
|
-
|
|
917
|
+
if (record.compacted) {
|
|
918
|
+
if (record.subagentId || record.aggregate) return usage;
|
|
919
|
+
const next2 = { ...usage };
|
|
920
|
+
if (record.source !== void 0) next2.source = record.source;
|
|
921
|
+
if (record.model !== void 0) {
|
|
922
|
+
if (record.source !== usage.source || record.model !== usage.model) delete next2.contextWindow;
|
|
923
|
+
next2.model = record.model;
|
|
924
|
+
}
|
|
925
|
+
if (record.contextTokens !== void 0) next2.lastPromptTokens = record.contextTokens;
|
|
926
|
+
else delete next2.lastPromptTokens;
|
|
927
|
+
if (record.contextWindow !== void 0) next2.contextWindow = record.contextWindow > 0 ? record.contextWindow : void 0;
|
|
928
|
+
return next2;
|
|
929
|
+
}
|
|
930
|
+
if (record.sessionBilling && !record.reportedCost && record.inputTokens === void 0 && record.outputTokens === void 0 && record.cachedInputTokens === void 0 && record.contextWindow === void 0) {
|
|
931
|
+
return record.subagentId ? usage : { ...usage, sessionBilling: record.sessionBilling, source: record.source };
|
|
932
|
+
}
|
|
933
|
+
record = { ...record, sessionBilling: record.sessionBilling ?? (usage.source === void 0 || record.source === usage.source ? usage.sessionBilling : void 0) };
|
|
934
|
+
const next = { ...usage, ...!record.subagentId && record.sessionBilling ? { sessionBilling: record.sessionBilling } : {}, totals: addUsage(usage.totals, record), byModel: addModelUsage(usage.byModel ?? [], record) };
|
|
889
935
|
if (record.subagentId) {
|
|
890
936
|
next.bySubagent = { ...usage.bySubagent, [record.subagentId]: addUsage(usage.bySubagent?.[record.subagentId] ?? {}, record) };
|
|
891
937
|
return next;
|
|
892
938
|
}
|
|
893
|
-
if (
|
|
894
|
-
if (record.source !== usage.source
|
|
895
|
-
|
|
896
|
-
delete next.contextWindow;
|
|
897
|
-
}
|
|
898
|
-
next.source = record.source;
|
|
899
|
-
next.model = record.model;
|
|
939
|
+
if (record.aggregate) {
|
|
940
|
+
if (record.source !== usage.source && !record.sessionBilling) delete next.sessionBilling;
|
|
941
|
+
return next;
|
|
900
942
|
}
|
|
901
|
-
if (record.
|
|
943
|
+
if (record.source !== usage.source || record.model !== usage.model) {
|
|
944
|
+
delete next.lastPromptTokens;
|
|
945
|
+
delete next.contextWindow;
|
|
946
|
+
}
|
|
947
|
+
if (record.source !== usage.source && !record.sessionBilling) delete next.sessionBilling;
|
|
948
|
+
next.source = record.source;
|
|
949
|
+
next.model = record.model;
|
|
950
|
+
const used = record.contextTokens ?? record.inputTokens;
|
|
951
|
+
if (used !== void 0) next.lastPromptTokens = used;
|
|
902
952
|
if (record.contextWindow !== void 0) next.contextWindow = record.contextWindow > 0 ? record.contextWindow : void 0;
|
|
903
953
|
return next;
|
|
904
954
|
}
|
|
@@ -909,6 +959,7 @@ function usageLine(usage) {
|
|
|
909
959
|
const contextFill = plannerContextFill(usage);
|
|
910
960
|
return {
|
|
911
961
|
totals: usage.totals,
|
|
962
|
+
...usage.sessionBilling ? { sessionBilling: usage.sessionBilling } : {},
|
|
912
963
|
...usage.byModel ? { byModel: usage.byModel } : {},
|
|
913
964
|
...usage.model ? { model: usage.model } : {},
|
|
914
965
|
...usage.bySubagent ? { bySubagent: usage.bySubagent } : {},
|
|
@@ -916,8 +967,8 @@ function usageLine(usage) {
|
|
|
916
967
|
};
|
|
917
968
|
}
|
|
918
969
|
function plannerContextFill(usage) {
|
|
919
|
-
if (usage.lastPromptTokens === void 0
|
|
920
|
-
return { usedTokens: usage.lastPromptTokens, windowTokens: usage.contextWindow };
|
|
970
|
+
if (usage.lastPromptTokens === void 0) return void 0;
|
|
971
|
+
return { usedTokens: usage.lastPromptTokens, ...usage.contextWindow !== void 0 && usage.contextWindow > 0 ? { windowTokens: usage.contextWindow } : {} };
|
|
921
972
|
}
|
|
922
973
|
function addModelUsage(models, record) {
|
|
923
974
|
const index = models.findIndex((m) => m.source === record.source && m.model === record.model);
|
|
@@ -929,13 +980,14 @@ function startAttemptUsage(attempt, runner, startedAt) {
|
|
|
929
980
|
}
|
|
930
981
|
function addAttemptUsage(usage, record, now) {
|
|
931
982
|
const folded = addPlannerUsage(usage, record);
|
|
932
|
-
return { ...usage, ...folded, lastPromptTokens: folded.lastPromptTokens, contextWindow: folded.contextWindow, wallTimeMs: Math.max(0, Date.parse(now) - Date.parse(usage.startedAt)) };
|
|
983
|
+
return { ...usage, ...folded, lastPromptTokens: folded.lastPromptTokens, contextWindow: folded.contextWindow, wallTimeMs: Math.max(0, Date.parse(usage.endedAt ?? now) - Date.parse(usage.startedAt)) };
|
|
933
984
|
}
|
|
934
985
|
function endAttemptUsage(usage, now, runnerSessionCost, sessionId) {
|
|
986
|
+
const endedAt = usage.endedAt ?? now;
|
|
935
987
|
return {
|
|
936
988
|
...usage,
|
|
937
|
-
endedAt
|
|
938
|
-
wallTimeMs: Math.max(0, Date.parse(
|
|
989
|
+
endedAt,
|
|
990
|
+
wallTimeMs: Math.max(0, Date.parse(endedAt) - Date.parse(usage.startedAt)),
|
|
939
991
|
...sessionId ? { sessionId } : {},
|
|
940
992
|
...runnerSessionCost ? { runnerSessionCost } : {}
|
|
941
993
|
};
|
|
@@ -948,8 +1000,8 @@ function resumedSessionWindow(attempts, runner, attempt, sessionId) {
|
|
|
948
1000
|
return last ? { source: last.source, model: last.model, contextWindow: last.contextWindow } : void 0;
|
|
949
1001
|
}
|
|
950
1002
|
function addUsageTotals(totals, addition) {
|
|
951
|
-
|
|
952
|
-
for (const [currency, amount] of Object.entries(addition.reportedCost ?? {}))
|
|
1003
|
+
const next = addUsage(totals, { source: "", inputTokens: addition.inputTokens, outputTokens: addition.outputTokens, cachedInputTokens: addition.cachedInputTokens });
|
|
1004
|
+
for (const [currency, amount] of Object.entries(addition.reportedCost ?? {})) addCost(next, currency, amount, addition.costProvenance?.[currency]);
|
|
953
1005
|
return next;
|
|
954
1006
|
}
|
|
955
1007
|
function planUsage(tasks) {
|
|
@@ -984,6 +1036,71 @@ function taskUsageSummary(task) {
|
|
|
984
1036
|
...contextFill ? { contextFill } : {}
|
|
985
1037
|
};
|
|
986
1038
|
}
|
|
1039
|
+
function formatTokenCount(n) {
|
|
1040
|
+
if (n < 1e3) return String(n);
|
|
1041
|
+
if (n < 1e4) return `${(n / 1e3).toFixed(1).replace(/\.0$/, "")}k`;
|
|
1042
|
+
if (n < 999500) return `${Math.round(n / 1e3)}k`;
|
|
1043
|
+
return `${(n / 1e6).toFixed(1).replace(/\.0$/, "")}M`;
|
|
1044
|
+
}
|
|
1045
|
+
function formatCost(currency, amount) {
|
|
1046
|
+
const value = amount >= 1 ? amount.toFixed(2) : (([whole, frac = ""]) => `${whole}.${frac.padEnd(2, "0")}`)(amount.toFixed(4).replace(/0+$/, "").split("."));
|
|
1047
|
+
return currency.toLowerCase() === "usd" ? `$${value}` : `${value} ${currency.toUpperCase()}`;
|
|
1048
|
+
}
|
|
1049
|
+
function contextMeterText(fill) {
|
|
1050
|
+
if (!fill) return null;
|
|
1051
|
+
const used = `ctx ${formatTokenCount(fill.usedTokens)}`;
|
|
1052
|
+
if (!fill.windowTokens) return used;
|
|
1053
|
+
const share = fill.usedTokens / fill.windowTokens * 100;
|
|
1054
|
+
return `${used} (${share < 1 ? "<1" : Math.round(share)}%)`;
|
|
1055
|
+
}
|
|
1056
|
+
function contextMeterTitle(fill) {
|
|
1057
|
+
const used = fill.usedTokens.toLocaleString("en-US");
|
|
1058
|
+
return fill.windowTokens ? `${used} of ${fill.windowTokens.toLocaleString("en-US")} tokens` : `${used} tokens`;
|
|
1059
|
+
}
|
|
1060
|
+
function costParts(totals) {
|
|
1061
|
+
const rank = (currency) => currency.toLowerCase() === "usd" ? "" : currency.toUpperCase();
|
|
1062
|
+
return Object.entries(totals.reportedCost ?? {}).sort(([a], [b]) => rank(a).localeCompare(rank(b))).map(([currency, amount]) => {
|
|
1063
|
+
const provenance = totals.costProvenance?.[currency];
|
|
1064
|
+
const equivalent = provenance?.billing === "subscription";
|
|
1065
|
+
return `${provenance?.kind === "estimate" || equivalent ? "\u2248" : ""}${formatCost(currency, amount)}${equivalent ? " API-equivalent" : ""}`;
|
|
1066
|
+
});
|
|
1067
|
+
}
|
|
1068
|
+
function costText(totals) {
|
|
1069
|
+
const parts = costParts(totals);
|
|
1070
|
+
return parts.length > 0 ? parts.join(" \xB7 ") : null;
|
|
1071
|
+
}
|
|
1072
|
+
function usageText(usage) {
|
|
1073
|
+
const parts = [contextMeterText(usage.contextFill), costText(usage.totals)].filter((p) => p !== null);
|
|
1074
|
+
return parts.length > 0 ? parts.join(" \xB7 ") : null;
|
|
1075
|
+
}
|
|
1076
|
+
function modelCostTitle(byModel) {
|
|
1077
|
+
const lines2 = byModel.flatMap((m) => {
|
|
1078
|
+
const cost = costParts(m.totals);
|
|
1079
|
+
const descriptions = [...new Set(Object.values(m.totals.costProvenance ?? {}).map((p) => {
|
|
1080
|
+
const source = m.source === "claude-code" ? "Claude Code" : m.source === "opencode" ? "OpenCode" : m.source === "openrouter" ? "OpenRouter" : m.source;
|
|
1081
|
+
const basis = p.basis === "list" ? " at list price" : p.basis === "managed" ? " at managed price" : "";
|
|
1082
|
+
const kind = p.kind === "estimate" ? `${source}'s estimate${basis}` : p.kind === "billed" ? `Amount billed by ${source}` : "Reported cost";
|
|
1083
|
+
return `${kind}${p.billing === "subscription" ? "; not billed on a subscription" : ""}`;
|
|
1084
|
+
}))];
|
|
1085
|
+
return cost.length > 0 ? [`${m.source} / ${m.model ?? "unattributed"}: ${cost.join(" \xB7 ")}${descriptions.length ? ` \u2014 ${descriptions.join("; ")}` : ""}`] : [];
|
|
1086
|
+
});
|
|
1087
|
+
return lines2.length > 0 ? lines2.join("\n") : void 0;
|
|
1088
|
+
}
|
|
1089
|
+
function subagentCostTitle(bySubagent) {
|
|
1090
|
+
return Object.entries(bySubagent).map(([id, totals]) => `${id}: ${costParts(totals).join(" \xB7 ") || "reported no cost"}`).join("\n");
|
|
1091
|
+
}
|
|
1092
|
+
function runnersOf(tasks) {
|
|
1093
|
+
return tasks.flatMap((task) => [...task.attemptUsage ?? [], ...runnersOf(task.subtasks ?? [])]);
|
|
1094
|
+
}
|
|
1095
|
+
function tasksTotalText(tasks) {
|
|
1096
|
+
const attempts = runnersOf(tasks);
|
|
1097
|
+
if (attempts.length === 0) return null;
|
|
1098
|
+
const costed = (a) => Object.keys(a.totals.reportedCost ?? {}).length > 0;
|
|
1099
|
+
const costless = [...new Set(attempts.filter((a) => a.endedAt !== void 0 && isMeasured(a.totals)).map((a) => a.runner))].filter((runner) => !attempts.some((a) => a.runner === runner && costed(a)));
|
|
1100
|
+
const note = costless.length === 0 ? [] : [`${costless.length === 1 ? costless[0] : `${costless.slice(0, -1).join(", ")} and ${costless.at(-1)}`} ${costless.length === 1 ? "reports" : "report"} no cost`];
|
|
1101
|
+
const parts = [...costParts(planUsage(tasks).totals), ...note];
|
|
1102
|
+
return parts.length > 0 ? `Tasks total: ${parts.join(" \xB7 ")}` : null;
|
|
1103
|
+
}
|
|
987
1104
|
|
|
988
1105
|
// src/models/TaskLog.ts
|
|
989
1106
|
var HEAD_LINES = 60;
|
|
@@ -7523,7 +7640,7 @@ var RunnerApprovals = class {
|
|
|
7523
7640
|
},
|
|
7524
7641
|
stop: (sessionId) => {
|
|
7525
7642
|
this.denySession(sessionId);
|
|
7526
|
-
runner.stop(sessionId);
|
|
7643
|
+
return runner.stop(sessionId);
|
|
7527
7644
|
},
|
|
7528
7645
|
stopAll: () => {
|
|
7529
7646
|
this.approvals.clear(isRunnerApproval);
|
|
@@ -11628,6 +11745,14 @@ function canUseToolAnswer({ input, suggestions }, decision) {
|
|
|
11628
11745
|
}
|
|
11629
11746
|
var MODEL_COMMAND = "model";
|
|
11630
11747
|
var SUBAGENT_TOOLS = /* @__PURE__ */ new Set(["Agent", "Task"]);
|
|
11748
|
+
var VARIANT_SUFFIX = /^(.*?)(\[[^\]]*\])?$/;
|
|
11749
|
+
function sameModel(a, b) {
|
|
11750
|
+
if (a === void 0 || b === void 0) return false;
|
|
11751
|
+
if (a === b) return true;
|
|
11752
|
+
const [, aBase, aVariant] = VARIANT_SUFFIX.exec(a) ?? [];
|
|
11753
|
+
const [, bBase, bVariant] = VARIANT_SUFFIX.exec(b) ?? [];
|
|
11754
|
+
return aBase === bBase && (aVariant === void 0 || bVariant === void 0 || aVariant === bVariant);
|
|
11755
|
+
}
|
|
11631
11756
|
function usageRecord(usage, model, subagentId) {
|
|
11632
11757
|
const record = {
|
|
11633
11758
|
source: "claude-code",
|
|
@@ -11636,6 +11761,7 @@ function usageRecord(usage, model, subagentId) {
|
|
|
11636
11761
|
if (model) record.model = model;
|
|
11637
11762
|
if (usage.output_tokens !== void 0) record.outputTokens = usage.output_tokens;
|
|
11638
11763
|
if (subagentId) record.subagentId = subagentId;
|
|
11764
|
+
else if (record.inputTokens !== void 0) record.contextTokens = record.inputTokens + (record.outputTokens ?? 0);
|
|
11639
11765
|
return record;
|
|
11640
11766
|
}
|
|
11641
11767
|
function subagentFinalCall(result) {
|
|
@@ -11685,16 +11811,27 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
|
|
|
11685
11811
|
replyRuns = new ReplyRuns();
|
|
11686
11812
|
/** The model answering the planner's current message, and the message's id, as its `message_start` named them. */
|
|
11687
11813
|
plannerModel;
|
|
11814
|
+
/**
|
|
11815
|
+
* The id the turn runs as, as `init` names it. A `[1m]` variant is the
|
|
11816
|
+
* selected id, while a message names the API's canonical id without it.
|
|
11817
|
+
*/
|
|
11818
|
+
sessionModel;
|
|
11819
|
+
billing;
|
|
11688
11820
|
streamingMessage;
|
|
11689
11821
|
/** The message the last `assistant` line was part of, and how many of its blocks the lines have carried. */
|
|
11690
11822
|
lineMessage = null;
|
|
11691
11823
|
/**
|
|
11692
|
-
*
|
|
11693
|
-
*
|
|
11694
|
-
*
|
|
11824
|
+
* What has been counted of the session's running cost: a resume starts from
|
|
11825
|
+
* it, and it is undefined without a kept count — the next result then only
|
|
11826
|
+
* sets the baseline, since the CLI restores spend from turns before it. It
|
|
11827
|
+
* grows by what each result is counted for, never by a total that includes
|
|
11828
|
+
* cost the runner marked unknown, so that spend can be counted when its basis
|
|
11829
|
+
* is reported; and it never lowers, since counted dollars are not un-spent.
|
|
11695
11830
|
*/
|
|
11696
11831
|
reportedCostUsd = 0;
|
|
11697
11832
|
reportedModelCostUsd = /* @__PURE__ */ new Map();
|
|
11833
|
+
/** What this turn's streamed calls already reported, so its result only adds what did not stream. */
|
|
11834
|
+
turnCallUsage = { inputTokens: 0, cachedInputTokens: 0, outputTokens: 0 };
|
|
11698
11835
|
/**
|
|
11699
11836
|
* Subagents started and not yet finished, keyed by the `Agent` call's id.
|
|
11700
11837
|
* Kept across turns: a backgrounded one reports after its turn has ended.
|
|
@@ -11781,6 +11918,14 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
|
|
|
11781
11918
|
if (!sessionId || this.reportedCostUsd === void 0) return null;
|
|
11782
11919
|
return { sessionId, usd: this.reportedCostUsd, modelUsd: Object.fromEntries(this.reportedModelCostUsd) };
|
|
11783
11920
|
}
|
|
11921
|
+
/**
|
|
11922
|
+
* A cleared conversation's totals start again from zero, so what follows is
|
|
11923
|
+
* all new spend, even for a resume that had no count kept.
|
|
11924
|
+
*/
|
|
11925
|
+
restartCount() {
|
|
11926
|
+
this.reportedCostUsd = 0;
|
|
11927
|
+
this.reportedModelCostUsd = /* @__PURE__ */ new Map();
|
|
11928
|
+
}
|
|
11784
11929
|
/** A count kept for another session says nothing of this one's. */
|
|
11785
11930
|
countFrom(sessionId, counted) {
|
|
11786
11931
|
const known = counted?.sessionId === sessionId ? counted : void 0;
|
|
@@ -11854,11 +11999,10 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
|
|
|
11854
11999
|
* the alias resolves to now; that id is pinned before the first turn.
|
|
11855
12000
|
*/
|
|
11856
12001
|
async handshake(opts) {
|
|
11857
|
-
if (opts.kind !== "task") return;
|
|
11858
12002
|
const initialized = (await this.control({ subtype: "initialize" }, MODEL_PIN_TIMEOUT_MS))?.response;
|
|
11859
12003
|
this.listedCommands = initialized?.commands ?? [];
|
|
11860
12004
|
this.listedModels = initialized?.models ?? [];
|
|
11861
|
-
if (opts.flags.permissionMode !== "plan" || !opts.model) return;
|
|
12005
|
+
if (opts.kind !== "task" || opts.flags.permissionMode !== "plan" || !opts.model) return;
|
|
11862
12006
|
const resolved = initialized?.models?.find((m) => m.value === opts.model)?.resolvedModel;
|
|
11863
12007
|
if (resolved && resolved !== opts.model) await this.control({ subtype: "set_model", model: resolved }, MODEL_PIN_TIMEOUT_MS);
|
|
11864
12008
|
}
|
|
@@ -11899,6 +12043,15 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
|
|
|
11899
12043
|
this.interruptRequested = true;
|
|
11900
12044
|
return (await this.control({ subtype: "interrupt" }, timeoutMs))?.subtype === "success";
|
|
11901
12045
|
}
|
|
12046
|
+
/**
|
|
12047
|
+
* On stdin's end the CLI exits by itself once its turn has closed, and only
|
|
12048
|
+
* an exit of its own saves the session's running totals for a `--resume` to
|
|
12049
|
+
* restore (checked on 2.1.303: about a second later, code 0, or 1 after an
|
|
12050
|
+
* interrupted turn).
|
|
12051
|
+
*/
|
|
12052
|
+
endInput() {
|
|
12053
|
+
this.process?.stdin?.end();
|
|
12054
|
+
}
|
|
11902
12055
|
/**
|
|
11903
12056
|
* A `user` line written mid-turn is Claude Code's own queue (ADR-0023): the
|
|
11904
12057
|
* CLI attaches it to the next tool result, or runs it as a turn of its own
|
|
@@ -11986,6 +12139,7 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
|
|
|
11986
12139
|
this.resultHeld = false;
|
|
11987
12140
|
this.turnClosed = false;
|
|
11988
12141
|
this.clearFollowOnTimer();
|
|
12142
|
+
this.plannerModel = void 0;
|
|
11989
12143
|
}
|
|
11990
12144
|
/**
|
|
11991
12145
|
* The echo of a steered message: the model has it. After a closed turn it
|
|
@@ -12040,6 +12194,8 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
|
|
|
12040
12194
|
this.permissions.withdraw(msg.request_id, emit);
|
|
12041
12195
|
return;
|
|
12042
12196
|
case "control_response": {
|
|
12197
|
+
const account = msg.response?.response?.account;
|
|
12198
|
+
if (account) this.reportBilling(account.apiKeySource, account.subscriptionType, emit);
|
|
12043
12199
|
const requestId = msg.response?.request_id;
|
|
12044
12200
|
if (requestId) this.pendingControl.get(requestId)?.(msg.response ?? null);
|
|
12045
12201
|
return;
|
|
@@ -12083,12 +12239,24 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
|
|
|
12083
12239
|
emit({ type: "tool_result", id, name: "", output: diff || output, success: block.is_error !== true, subagentId });
|
|
12084
12240
|
}
|
|
12085
12241
|
return;
|
|
12242
|
+
// `/clear` (and `/reset`, `/new`) emit this top-level line before their
|
|
12243
|
+
// own result, and a task's chat can send them. The CLI's totals restart.
|
|
12244
|
+
case "conversation_reset": {
|
|
12245
|
+
if (subagentId) return;
|
|
12246
|
+
this.restartCount();
|
|
12247
|
+
const resetModel = this.sessionModel ?? (this.plannerModel !== "<synthetic>" ? this.plannerModel : void 0);
|
|
12248
|
+
emit({ type: "usage", record: { source: "claude-code", ...resetModel ? { model: resetModel } : {}, compacted: true } });
|
|
12249
|
+
return;
|
|
12250
|
+
}
|
|
12086
12251
|
case "system":
|
|
12252
|
+
if (subagentId && (msg.subtype === "init" || msg.subtype === "compact_boundary")) return;
|
|
12087
12253
|
if (msg.subtype === "init") {
|
|
12088
12254
|
this.clearFollowOnTimer();
|
|
12089
12255
|
if (this.refuseOtherMode(msg, emit)) return;
|
|
12256
|
+
if (msg.model && msg.model !== "<synthetic>") this.sessionModel = msg.model;
|
|
12090
12257
|
this.readLoaded(msg, emit);
|
|
12091
12258
|
this.followModel(msg.model, emit);
|
|
12259
|
+
this.reportBilling(msg.apiKeySource, void 0, emit);
|
|
12092
12260
|
}
|
|
12093
12261
|
if (msg.subtype === "commands_changed" && Array.isArray(msg.commands)) this.listedCommands = msg.commands;
|
|
12094
12262
|
if (msg.subtype === "compact_boundary") {
|
|
@@ -12099,6 +12267,8 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
|
|
|
12099
12267
|
...typeof meta?.pre_tokens === "number" ? { preTokens: meta.pre_tokens } : {},
|
|
12100
12268
|
...typeof meta?.post_tokens === "number" ? { postTokens: meta.post_tokens } : {}
|
|
12101
12269
|
});
|
|
12270
|
+
const model = this.sessionModel ?? (this.plannerModel !== "<synthetic>" ? this.plannerModel : void 0);
|
|
12271
|
+
emit({ type: "usage", record: { source: "claude-code", ...model ? { model } : {}, compacted: true, ...typeof meta?.post_tokens === "number" ? { contextTokens: meta.post_tokens } : {} } });
|
|
12102
12272
|
}
|
|
12103
12273
|
if (msg.subtype === "background_tasks_changed" && Array.isArray(msg.tasks)) {
|
|
12104
12274
|
this.backgroundTaskCount = msg.tasks.length;
|
|
@@ -12110,6 +12280,7 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
|
|
|
12110
12280
|
}
|
|
12111
12281
|
return;
|
|
12112
12282
|
case "result":
|
|
12283
|
+
if (subagentId) return;
|
|
12113
12284
|
this.reportSessionCost(msg, emit);
|
|
12114
12285
|
for (const command of this.modelCommands) command.turnsLeft = Math.max(0, command.turnsLeft - 1);
|
|
12115
12286
|
if (this.interruptRequested && (msg.is_error || msg.subtype !== "success")) {
|
|
@@ -12274,16 +12445,69 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
|
|
|
12274
12445
|
*/
|
|
12275
12446
|
handleStreamEvent(event, emit) {
|
|
12276
12447
|
if (event.type === "message_start") {
|
|
12277
|
-
this.plannerModel = event.message?.model;
|
|
12448
|
+
this.plannerModel = this.modelId(event.message?.model);
|
|
12278
12449
|
this.streamingMessage = event.message?.id;
|
|
12279
12450
|
this.followModel(this.plannerModel, emit);
|
|
12280
|
-
} else if (event.type === "message_delta" && event.usage)
|
|
12281
|
-
|
|
12451
|
+
} else if (event.type === "message_delta" && event.usage) {
|
|
12452
|
+
const record = usageRecord(event.usage, this.plannerModel);
|
|
12453
|
+
this.turnCallUsage.inputTokens += record.inputTokens ?? 0;
|
|
12454
|
+
this.turnCallUsage.cachedInputTokens += record.cachedInputTokens ?? 0;
|
|
12455
|
+
this.turnCallUsage.outputTokens += record.outputTokens ?? 0;
|
|
12456
|
+
emit({ type: "usage", record });
|
|
12457
|
+
} else if (event.type === "content_block_delta" && event.delta?.type === "text_delta" && event.delta.text) {
|
|
12282
12458
|
this.replyRuns.delta(`${this.streamingMessage ?? ""}:${event.index ?? 0}`, event.delta.text, emit);
|
|
12283
12459
|
} else if (event.type === "content_block_delta" && event.delta?.type === "thinking_delta" && event.delta.thinking) {
|
|
12284
12460
|
emit({ type: "thinking_delta", text: event.delta.thinking });
|
|
12285
12461
|
}
|
|
12286
12462
|
}
|
|
12463
|
+
/**
|
|
12464
|
+
* The id a reported model runs as. `init` names the selected value — a
|
|
12465
|
+
* `[1m]` variant included — while a message names the API's canonical id
|
|
12466
|
+
* without it. Keeping the variant is what lets the meter find the window
|
|
12467
|
+
* `modelUsage` keys it by, and keeps that window off the base model.
|
|
12468
|
+
*/
|
|
12469
|
+
modelId(reported) {
|
|
12470
|
+
if (!reported) return void 0;
|
|
12471
|
+
if (!sameModel(this.sessionModel, reported)) return reported;
|
|
12472
|
+
return this.sessionModel?.includes("[") ? this.sessionModel : reported;
|
|
12473
|
+
}
|
|
12474
|
+
reportBilling(apiKeySource, plan, emit) {
|
|
12475
|
+
const type = apiKeySource ? apiKeySource === "none" ? "subscription" : "api-key" : plan ? "subscription" : void 0;
|
|
12476
|
+
if (!type) return;
|
|
12477
|
+
const knownPlan = plan ?? (this.billing?.type === type ? this.billing.plan : void 0);
|
|
12478
|
+
this.billing = { type, ...knownPlan ? { plan: knownPlan } : {} };
|
|
12479
|
+
emit({ type: "usage", record: { source: this.agentId, aggregate: true, sessionBilling: this.billing } });
|
|
12480
|
+
}
|
|
12481
|
+
/**
|
|
12482
|
+
* A turn's `result` states its own token sum. Every call that streamed its
|
|
12483
|
+
* `message_delta` usage has already reported, so only the remainder is added:
|
|
12484
|
+
* all of it when nothing streamed (a recording without partial messages), or
|
|
12485
|
+
* a lost call's share. It is a sum across calls, marked `aggregate` so it
|
|
12486
|
+
* never moves the meter — only a call says what the context was left at.
|
|
12487
|
+
*/
|
|
12488
|
+
reportUnstreamedUsage(msg, emit) {
|
|
12489
|
+
const { inputTokens: streamedInput, cachedInputTokens: streamedCached, outputTokens: streamedOutput } = this.turnCallUsage;
|
|
12490
|
+
this.turnCallUsage = { inputTokens: 0, cachedInputTokens: 0, outputTokens: 0 };
|
|
12491
|
+
if (!msg.usage) return;
|
|
12492
|
+
const models = Object.keys(msg.modelUsage ?? {});
|
|
12493
|
+
const model = this.plannerModel ?? this.mainModel ?? (models.length === 1 ? models[0] : void 0);
|
|
12494
|
+
const total = usageRecord(msg.usage, model);
|
|
12495
|
+
const inputTokens = (total.inputTokens ?? 0) - streamedInput;
|
|
12496
|
+
const cachedInputTokens = (total.cachedInputTokens ?? 0) - streamedCached;
|
|
12497
|
+
const outputTokens = (total.outputTokens ?? 0) - streamedOutput;
|
|
12498
|
+
if (inputTokens <= 0 && outputTokens <= 0) return;
|
|
12499
|
+
emit({
|
|
12500
|
+
type: "usage",
|
|
12501
|
+
record: {
|
|
12502
|
+
source: "claude-code",
|
|
12503
|
+
...model ? { model } : {},
|
|
12504
|
+
aggregate: true,
|
|
12505
|
+
...inputTokens > 0 ? { inputTokens } : {},
|
|
12506
|
+
...inputTokens > 0 && cachedInputTokens > 0 ? { cachedInputTokens } : {},
|
|
12507
|
+
...outputTokens > 0 ? { outputTokens } : {}
|
|
12508
|
+
}
|
|
12509
|
+
});
|
|
12510
|
+
}
|
|
12287
12511
|
/**
|
|
12288
12512
|
* What only the result line knows: the cost, which covers every call the
|
|
12289
12513
|
* session made — subagents included, since none of their lines carries one —
|
|
@@ -12292,24 +12516,54 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
|
|
|
12292
12516
|
* the session before; with no count kept, the first turn only sets the
|
|
12293
12517
|
* baseline: its total includes turns counted before, and a turn's own share
|
|
12294
12518
|
* cannot be told apart from them.
|
|
12519
|
+
*
|
|
12520
|
+
* The running total never lowers: money already reported and counted is not
|
|
12521
|
+
* un-spent because a later or resumed process remembers less of it, and
|
|
12522
|
+
* counting its return would count the same dollars twice. A zero is not a
|
|
12523
|
+
* measurement at all — a crash can end with one — and only `/clear` restarts
|
|
12524
|
+
* the count, through {@link restartCount}. Per-model baselines never lower
|
|
12525
|
+
* either, for the same reason.
|
|
12526
|
+
*
|
|
12527
|
+
* A report without a model breakdown is counted as a session total; one with
|
|
12528
|
+
* a breakdown is counted per model. A breakdown that would count more than
|
|
12529
|
+
* the session total's own growth (the count before it had no model split, or
|
|
12530
|
+
* a lossy report) is counted as that growth alone, so the two shapes
|
|
12531
|
+
* together still add each dollar once.
|
|
12295
12532
|
*/
|
|
12296
12533
|
reportSessionCost(msg, emit) {
|
|
12534
|
+
this.reportUnstreamedUsage(msg, emit);
|
|
12297
12535
|
const total = msg.total_cost_usd;
|
|
12298
12536
|
const canReport = this.reportedCostUsd !== void 0;
|
|
12299
|
-
const modelCosts = Object.entries(msg.modelUsage ?? {}).filter((entry) => typeof entry[1].costUSD === "number");
|
|
12537
|
+
const modelCosts = Object.entries(msg.modelUsage ?? {}).filter((entry) => typeof entry[1].costUSD === "number" && entry[1].costUSD > 0 && entry[1].costBasis !== "unknown");
|
|
12538
|
+
const growth = canReport && typeof total === "number" && total > 0 ? total - this.reportedCostUsd : void 0;
|
|
12539
|
+
let emitted = 0;
|
|
12540
|
+
const emitCost = (record) => {
|
|
12541
|
+
if (!(record.amount > 0)) return;
|
|
12542
|
+
emitted += record.amount;
|
|
12543
|
+
emit({ type: "usage", record: { source: "claude-code", ...record.model ? { model: record.model } : {}, aggregate: true, ...this.billing ? { sessionBilling: this.billing } : {}, reportedCost: { amount: record.amount, currency: "USD", kind: "estimate", ...record.basis ? { basis: record.basis } : {} } } });
|
|
12544
|
+
};
|
|
12300
12545
|
if (modelCosts.length > 0) {
|
|
12301
|
-
|
|
12302
|
-
|
|
12303
|
-
|
|
12304
|
-
|
|
12305
|
-
|
|
12546
|
+
const modelGrowth = modelCosts.reduce((sum, [model2, usage]) => sum + Math.max(0, usage.costUSD - (this.reportedModelCostUsd.get(model2) ?? 0)), 0);
|
|
12547
|
+
if (canReport) {
|
|
12548
|
+
if (growth !== void 0 && modelGrowth > growth + 1e-9) {
|
|
12549
|
+
emitCost({ amount: growth });
|
|
12550
|
+
} else {
|
|
12551
|
+
for (const [model2, usage] of modelCosts) emitCost({ model: model2, amount: usage.costUSD - (this.reportedModelCostUsd.get(model2) ?? 0), ...usage.costBasis ? { basis: usage.costBasis } : {} });
|
|
12552
|
+
}
|
|
12306
12553
|
}
|
|
12307
|
-
|
|
12308
|
-
|
|
12554
|
+
for (const [model2, usage] of modelCosts) this.reportedModelCostUsd.set(model2, Math.max(this.reportedModelCostUsd.get(model2) ?? 0, usage.costUSD));
|
|
12555
|
+
} else if (!Object.values(msg.modelUsage ?? {}).some((usage) => usage.costBasis === "unknown") && typeof total === "number" && total > 0 && canReport && total >= this.reportedCostUsd) {
|
|
12556
|
+
emitCost({ amount: total - this.reportedCostUsd });
|
|
12557
|
+
}
|
|
12558
|
+
if (!canReport) {
|
|
12559
|
+
if (typeof total === "number" && total > 0) this.reportedCostUsd = total;
|
|
12560
|
+
else if (modelCosts.length > 0) this.reportedCostUsd = modelCosts.reduce((sum, [, usage]) => sum + usage.costUSD, 0);
|
|
12561
|
+
} else {
|
|
12562
|
+
this.reportedCostUsd = (this.reportedCostUsd ?? 0) + emitted;
|
|
12309
12563
|
}
|
|
12310
|
-
|
|
12311
|
-
const contextWindow =
|
|
12312
|
-
if (contextWindow
|
|
12564
|
+
const model = this.plannerModel && this.plannerModel !== "<synthetic>" ? this.plannerModel : this.sessionModel;
|
|
12565
|
+
const contextWindow = model ? msg.modelUsage?.[model]?.contextWindow : void 0;
|
|
12566
|
+
if (typeof contextWindow === "number" && contextWindow > 0) emit({ type: "usage", record: { source: "claude-code", model, contextWindow } });
|
|
12313
12567
|
}
|
|
12314
12568
|
};
|
|
12315
12569
|
|
|
@@ -12944,9 +13198,13 @@ function mergeCodexRateLimits(known, params) {
|
|
|
12944
13198
|
const id = snapshot.limitId ?? "";
|
|
12945
13199
|
const before = known.get(id);
|
|
12946
13200
|
const next = new Map(known);
|
|
12947
|
-
next.set(id, { limitId: id, primary: snapshot.primary ?? before?.primary, secondary: snapshot.secondary ?? before?.secondary });
|
|
13201
|
+
next.set(id, { limitId: id, planType: snapshot.planType ?? before?.planType, primary: snapshot.primary ?? before?.primary, secondary: snapshot.secondary ?? before?.secondary });
|
|
12948
13202
|
return next;
|
|
12949
13203
|
}
|
|
13204
|
+
function codexSessionBilling(limits) {
|
|
13205
|
+
const plan = [...limits.values()].find((snapshot) => snapshot.planType && snapshot.planType !== "unknown")?.planType;
|
|
13206
|
+
return plan ? { type: "subscription", plan } : void 0;
|
|
13207
|
+
}
|
|
12950
13208
|
function isCodexUsageLimit(error) {
|
|
12951
13209
|
const info = error?.codexErrorInfo;
|
|
12952
13210
|
if (info === void 0 || info === null || info === "other") return void 0;
|
|
@@ -13505,8 +13763,21 @@ ${this.exitMessage()}`);
|
|
|
13505
13763
|
case "thread/tokenUsage/updated":
|
|
13506
13764
|
this.emitUsage(msg.params, emit);
|
|
13507
13765
|
return;
|
|
13766
|
+
case "account/updated": {
|
|
13767
|
+
const account = msg.params;
|
|
13768
|
+
if (account?.authMode === "apikey") emit({ type: "usage", record: { source: this.agentId, aggregate: true, sessionBilling: { type: "api-key" } } });
|
|
13769
|
+
else if (account?.authMode === "chatgpt" || account?.authMode === "chatgptAuthTokens") {
|
|
13770
|
+
const plan = account.planType;
|
|
13771
|
+
emit({ type: "usage", record: { source: this.agentId, aggregate: true, sessionBilling: { type: "subscription", ...plan && plan !== "unknown" ? { plan } : {} } } });
|
|
13772
|
+
}
|
|
13773
|
+
return;
|
|
13774
|
+
}
|
|
13508
13775
|
case "account/rateLimits/updated":
|
|
13509
13776
|
this.rateLimits = mergeCodexRateLimits(this.rateLimits, msg.params);
|
|
13777
|
+
{
|
|
13778
|
+
const sessionBilling = codexSessionBilling(this.rateLimits);
|
|
13779
|
+
if (sessionBilling) emit({ type: "usage", record: { source: this.agentId, aggregate: true, sessionBilling } });
|
|
13780
|
+
}
|
|
13510
13781
|
return;
|
|
13511
13782
|
case "turn/started": {
|
|
13512
13783
|
if (this.subagentOf(msg.params?.threadId)) return;
|
|
@@ -13635,6 +13906,13 @@ ${this.exitMessage()}`);
|
|
|
13635
13906
|
* `last.outputTokens` already includes `reasoningOutputTokens` — the thread
|
|
13636
13907
|
* total is `inputTokens + outputTokens`, not a sum of three — so reasoning is
|
|
13637
13908
|
* not added a second time. Codex reports no price.
|
|
13909
|
+
*
|
|
13910
|
+
* The main thread's context is what Codex itself shows as in its window:
|
|
13911
|
+
* `TokenUsage::tokens_in_context_window`, which is `total_tokens` over
|
|
13912
|
+
* `last` as codex-rs defines it. `total_tokens` is input plus output, and
|
|
13913
|
+
* reasoning is part of output, so this is not `input + output − reasoning`
|
|
13914
|
+
* (checked against codex-rs and codex-cli 0.163.0's token_count events,
|
|
13915
|
+
* where `total_tokens` is `input_tokens + output_tokens`).
|
|
13638
13916
|
*/
|
|
13639
13917
|
emitUsage(params, emit) {
|
|
13640
13918
|
const last = params?.tokenUsage?.last;
|
|
@@ -13662,6 +13940,8 @@ ${this.exitMessage()}`);
|
|
|
13662
13940
|
} else {
|
|
13663
13941
|
const window = params?.tokenUsage?.modelContextWindow;
|
|
13664
13942
|
if (typeof window === "number" && window > 0) record.contextWindow = window;
|
|
13943
|
+
const inContext = last.totalTokens !== void 0 && last.totalTokens > 0 ? last.totalTokens : last.inputTokens !== void 0 ? last.inputTokens + (last.outputTokens ?? 0) : void 0;
|
|
13944
|
+
if (inContext !== void 0) record.contextTokens = Math.max(0, inContext);
|
|
13665
13945
|
}
|
|
13666
13946
|
emit({ type: "usage", record });
|
|
13667
13947
|
}
|
|
@@ -13823,7 +14103,9 @@ ${this.exitMessage()}`);
|
|
|
13823
14103
|
}
|
|
13824
14104
|
switch (item.type) {
|
|
13825
14105
|
case "contextCompaction":
|
|
13826
|
-
if (
|
|
14106
|
+
if (subagentId) return;
|
|
14107
|
+
emit({ type: "compaction", trigger: this.command === "compact" ? "manual" : "auto" });
|
|
14108
|
+
emit({ type: "usage", record: { source: this.agentId, compacted: true } });
|
|
13827
14109
|
return;
|
|
13828
14110
|
// Codex announces the item started and completed; whichever lands first delivers.
|
|
13829
14111
|
case "userMessage":
|
|
@@ -14119,8 +14401,9 @@ function usageRecord2(tokens, call) {
|
|
|
14119
14401
|
if ((prompt.inputTokens ?? 0) + outputTokens === 0) return null;
|
|
14120
14402
|
const record = { source: "opencode", ...prompt, outputTokens };
|
|
14121
14403
|
if (call.model) record.model = call.model;
|
|
14122
|
-
if (typeof call.cost === "number" && call.cost > 0) record.reportedCost = { amount: call.cost, currency: "USD" };
|
|
14404
|
+
if (typeof call.cost === "number" && call.cost > 0) record.reportedCost = { amount: call.cost, currency: "USD", kind: "estimate" };
|
|
14123
14405
|
if (call.subagentId) record.subagentId = call.subagentId;
|
|
14406
|
+
else record.contextTokens = (prompt.inputTokens ?? 0) + outputTokens;
|
|
14124
14407
|
return record;
|
|
14125
14408
|
}
|
|
14126
14409
|
function permissionReply(decision, key) {
|
|
@@ -14683,6 +14966,7 @@ var OpenCodeV2 = class {
|
|
|
14683
14966
|
}
|
|
14684
14967
|
if (frame.type === "session.compaction.ended") {
|
|
14685
14968
|
onEvent({ type: "compaction", trigger: data.reason === "manual" ? "manual" : "auto" });
|
|
14969
|
+
onEvent({ type: "usage", record: { source: "opencode", compacted: true } });
|
|
14686
14970
|
return;
|
|
14687
14971
|
}
|
|
14688
14972
|
if (frame.type === "session.execution.started") {
|
|
@@ -15548,6 +15832,7 @@ var OpenCodeAdapter = class {
|
|
|
15548
15832
|
if (session && session !== this.sessionId && !turn.children.follows(session)) return;
|
|
15549
15833
|
if (frame.type === "session.compacted" && session === this.sessionId) {
|
|
15550
15834
|
onEvent({ type: "compaction", trigger: this.manualCompaction ? "manual" : "auto" });
|
|
15835
|
+
onEvent({ type: "usage", record: { source: "opencode", compacted: true } });
|
|
15551
15836
|
return;
|
|
15552
15837
|
}
|
|
15553
15838
|
if (session === this.sessionId && this.taskTurn) this.followTaskTurn(frame, this.taskTurn, onEvent);
|
|
@@ -20570,6 +20855,7 @@ var PlannerConversation = class {
|
|
|
20570
20855
|
const request = summaryRequest(this.currentPlanLines());
|
|
20571
20856
|
const onProgress = (p) => {
|
|
20572
20857
|
if (p.type === "liveness") this.host.onProgress(p);
|
|
20858
|
+
else if (p.type === "usage" && p.record) this.host.onProgress({ ...p, record: { ...p.record, aggregate: true } });
|
|
20573
20859
|
};
|
|
20574
20860
|
let turn;
|
|
20575
20861
|
if (this.canContinueLive()) {
|
|
@@ -24260,6 +24546,9 @@ function nonPromptingFs(fs18) {
|
|
|
24260
24546
|
}
|
|
24261
24547
|
};
|
|
24262
24548
|
}
|
|
24549
|
+
function withoutContext({ contextTokens: _context, ...record }) {
|
|
24550
|
+
return record;
|
|
24551
|
+
}
|
|
24263
24552
|
async function runLoop(prompt, deps) {
|
|
24264
24553
|
if (deps.signal?.aborted) return "[research agent aborted before starting]";
|
|
24265
24554
|
const fs18 = nonPromptingFs(deps.fs);
|
|
@@ -24267,7 +24556,8 @@ async function runLoop(prompt, deps) {
|
|
|
24267
24556
|
(delta) => deps.onProgress?.({ type: "thinking", text: delta, subagentId: deps.subagentId }),
|
|
24268
24557
|
// The record says who made the call: unstamped, the ledger would read the
|
|
24269
24558
|
// subagent's prompt as the planner's own and measure its context by it.
|
|
24270
|
-
|
|
24559
|
+
// The chat states the context its call leaves; a subagent's is no meter's.
|
|
24560
|
+
(record) => deps.onProgress?.({ type: "usage", record: deps.subagentId ? { ...withoutContext(record), subagentId: deps.subagentId } : record })
|
|
24271
24561
|
);
|
|
24272
24562
|
let turn = await chat.sendMessage(prompt, deps.signal);
|
|
24273
24563
|
for (let step = 0; turn.hasToolCalls && step < SUBAGENT_LIMITS.maxSteps; step++) {
|
|
@@ -24775,7 +25065,8 @@ function usageRecordFrom(usage, model) {
|
|
|
24775
25065
|
model,
|
|
24776
25066
|
...inputTokens !== void 0 ? { inputTokens } : {},
|
|
24777
25067
|
...reportedOutput.length > 0 ? { outputTokens: reportedOutput.reduce((a, b) => a + b, 0) } : {},
|
|
24778
|
-
...tokenCount(usage.cachedContentTokenCount) !== void 0 ? { cachedInputTokens: tokenCount(usage.cachedContentTokenCount) } : {}
|
|
25068
|
+
...tokenCount(usage.cachedContentTokenCount) !== void 0 ? { cachedInputTokens: tokenCount(usage.cachedContentTokenCount) } : {},
|
|
25069
|
+
...inputTokens !== void 0 ? { contextTokens: inputTokens + (answerTokens ?? 0) } : {}
|
|
24779
25070
|
};
|
|
24780
25071
|
const anyReported = record.inputTokens !== void 0 || record.outputTokens !== void 0 || record.cachedInputTokens !== void 0;
|
|
24781
25072
|
return anyReported ? record : void 0;
|
|
@@ -25178,7 +25469,9 @@ var OpenAiResearchChat = class {
|
|
|
25178
25469
|
/**
|
|
25179
25470
|
* The one record for this API call, built only from what the provider
|
|
25180
25471
|
* reported. OpenRouter's `cost` is in USD (its credits are dollar-pegged);
|
|
25181
|
-
* nothing is estimated. A field the provider left out stays absent.
|
|
25472
|
+
* nothing is estimated. A field the provider left out stays absent. The
|
|
25473
|
+
* context the call leaves is its prompt and its completion: the API's own
|
|
25474
|
+
* `total_tokens`, and the reply the next call's prompt carries.
|
|
25182
25475
|
*/
|
|
25183
25476
|
usageRecord(reported) {
|
|
25184
25477
|
if (!reported) return void 0;
|
|
@@ -25197,9 +25490,10 @@ var OpenAiResearchChat = class {
|
|
|
25197
25490
|
hasMeasure = true;
|
|
25198
25491
|
}
|
|
25199
25492
|
if (reported.cost !== void 0) {
|
|
25200
|
-
record.reportedCost = { amount: reported.cost, currency: "USD" };
|
|
25493
|
+
record.reportedCost = { amount: reported.cost, currency: "USD", ...record.source === "openrouter" ? { kind: "billed" } : {} };
|
|
25201
25494
|
hasMeasure = true;
|
|
25202
25495
|
}
|
|
25496
|
+
if (record.inputTokens !== void 0) record.contextTokens = record.inputTokens + (record.outputTokens ?? 0);
|
|
25203
25497
|
return hasMeasure ? record : void 0;
|
|
25204
25498
|
}
|
|
25205
25499
|
};
|
|
@@ -27370,6 +27664,10 @@ var TaskOrchestrator = class _TaskOrchestrator {
|
|
|
27370
27664
|
store;
|
|
27371
27665
|
output;
|
|
27372
27666
|
attempts = /* @__PURE__ */ new Map();
|
|
27667
|
+
/** Bumped by every plan load: an ended attempt's wind-down belongs to the scope before it. */
|
|
27668
|
+
planScope = 0;
|
|
27669
|
+
/** Each task's runners still winding down once stopped: a continue waits for them (ADR-0018, L1). */
|
|
27670
|
+
windDowns = /* @__PURE__ */ new Map();
|
|
27373
27671
|
verifier = new VerdictEngine();
|
|
27374
27672
|
/** Changed only by start, stop, a plan load, a usage-limit pause, a move off the limit, and {@link settleRun}. */
|
|
27375
27673
|
runState = "idle";
|
|
@@ -27918,8 +28216,9 @@ var TaskOrchestrator = class _TaskOrchestrator {
|
|
|
27918
28216
|
return blockingFailures(this.store.planTasks).get(taskId) ?? [];
|
|
27919
28217
|
}
|
|
27920
28218
|
loadPlan(tasks, planRunners = [...DEFAULT_RUNNERS]) {
|
|
27921
|
-
this.store.load(tasks, planRunners);
|
|
27922
28219
|
const repairs = this.endAllAttempts("load").filter((a) => a.kind.kind === "repair" && a.worktree);
|
|
28220
|
+
this.planScope += 1;
|
|
28221
|
+
this.store.load(tasks, planRunners);
|
|
27923
28222
|
for (const a of repairs) this.unawaited(this.runs.release(a.taskId, { keep: true }, a.integration), `Could not keep the worktree of task "${this.store.get(a.taskId)?.title ?? a.taskId}"`);
|
|
27924
28223
|
this.runs.interrupt({ keepOpen: this.isScheduling });
|
|
27925
28224
|
if (this.runState !== "scheduling") this.runState = "idle";
|
|
@@ -28005,6 +28304,7 @@ var TaskOrchestrator = class _TaskOrchestrator {
|
|
|
28005
28304
|
if (reason === "user") this.stopGeneration++;
|
|
28006
28305
|
this.runState = "idle";
|
|
28007
28306
|
this.verifier.reset();
|
|
28307
|
+
for (const attempt of this.attempts.values()) this.stopRunner(attempt);
|
|
28008
28308
|
this.runner.stopAll();
|
|
28009
28309
|
const stopped = [];
|
|
28010
28310
|
for (const a of this.endAllAttempts("stop")) {
|
|
@@ -28118,6 +28418,7 @@ var TaskOrchestrator = class _TaskOrchestrator {
|
|
|
28118
28418
|
if (!task || !attempt) return;
|
|
28119
28419
|
attempt.decided = true;
|
|
28120
28420
|
const stoppedMidTurn = endedByExit(verdict) ? saidInFlight(attempt) : null;
|
|
28421
|
+
this.stopRunner(attempt);
|
|
28121
28422
|
console.error(`[TaskOrchestrator] Task ${taskRefOf(this.store.planTasks, task.id) ?? `#${task.order}`} "${task.title}" verdict=${verdict.outcome}`);
|
|
28122
28423
|
console.error(`[TaskOrchestrator] Runner: ${task.assignedRunner}, Model: ${task.assignedModel?.modelId ?? "default"}`);
|
|
28123
28424
|
console.error(`[TaskOrchestrator] Prompt preview: ${(task.prompt ?? "").slice(0, 200)}`);
|
|
@@ -29029,7 +29330,9 @@ ${now.message}` : LIMIT_RESET_MESSAGE;
|
|
|
29029
29330
|
autoResumed: opts.autoResumed === true,
|
|
29030
29331
|
turnError: null,
|
|
29031
29332
|
turnOutputFrom: 0,
|
|
29032
|
-
reminded: false
|
|
29333
|
+
reminded: false,
|
|
29334
|
+
runnerStopped: false,
|
|
29335
|
+
planScope: this.planScope
|
|
29033
29336
|
};
|
|
29034
29337
|
this.spawnCounts.set(task.id, attempt.attempt);
|
|
29035
29338
|
this.attempts.set(task.id, attempt);
|
|
@@ -29066,6 +29369,7 @@ ${now.message}` : LIMIT_RESET_MESSAGE;
|
|
|
29066
29369
|
const manifest = this.registry?.get(attempt.runner)?.manifest;
|
|
29067
29370
|
const unavailable = manifest && unavailableTaskModeMessage(manifest, task.taskMode);
|
|
29068
29371
|
if (unavailable) throw new Error(unavailable);
|
|
29372
|
+
if (kind.kind === "continuation") await this.windDowns.get(task.id);
|
|
29069
29373
|
const { cwd, worktree, previousWork } = await attemptCwd(kind, task, this.runs);
|
|
29070
29374
|
attempt.cwd = cwd;
|
|
29071
29375
|
attempt.worktree = worktree;
|
|
@@ -29121,10 +29425,11 @@ ${now.message}` : LIMIT_RESET_MESSAGE;
|
|
|
29121
29425
|
}
|
|
29122
29426
|
if (takesSkills(kind)) this.store.setTaskAttemptSkills(task.id, [...attempt.skills]);
|
|
29123
29427
|
session.onEvent((event) => {
|
|
29124
|
-
if (event.type !== "usage" || this.attempts.get(task.id) !== attempt) return;
|
|
29428
|
+
if (event.type !== "usage" || attempt.planScope !== this.planScope || this.attempts.get(task.id) !== attempt && !attempt.runnerStopped) return;
|
|
29125
29429
|
this.store.recordAttemptUsage(task.id, attempt.attempt, event.record, (/* @__PURE__ */ new Date()).toISOString());
|
|
29126
29430
|
this.emit("onUsageChanged");
|
|
29127
29431
|
});
|
|
29432
|
+
session.onExit(() => this.woundDown(attempt, session));
|
|
29128
29433
|
this.output.attach(task.id, session);
|
|
29129
29434
|
this.verifier.watch(task, session);
|
|
29130
29435
|
session.onEvent((event) => {
|
|
@@ -29293,10 +29598,35 @@ ${now.message}` : LIMIT_RESET_MESSAGE;
|
|
|
29293
29598
|
if (stopsRunner(reason)) {
|
|
29294
29599
|
const task = this.store.get(taskId);
|
|
29295
29600
|
if (task) this.verifier.clear(task);
|
|
29296
|
-
if (
|
|
29601
|
+
if (attempt) this.stopRunner(attempt);
|
|
29297
29602
|
}
|
|
29298
29603
|
return attempt;
|
|
29299
29604
|
}
|
|
29605
|
+
/**
|
|
29606
|
+
* Let the attempt's runner go the way it ends a turn (ADR-0018, L1): it
|
|
29607
|
+
* winds down on its own time, bounded by its interrupt grace, and nothing
|
|
29608
|
+
* here waits on it but a continue of the same task.
|
|
29609
|
+
*/
|
|
29610
|
+
stopRunner(attempt) {
|
|
29611
|
+
const session = attempt.session;
|
|
29612
|
+
if (!session || attempt.runnerStopped) return;
|
|
29613
|
+
attempt.runnerStopped = true;
|
|
29614
|
+
const { taskId } = attempt;
|
|
29615
|
+
const windDown = Promise.all([this.windDowns.get(taskId), this.runner.stop(session.id)]).catch(() => void 0).then(() => {
|
|
29616
|
+
if (this.windDowns.get(taskId) === windDown) this.windDowns.delete(taskId);
|
|
29617
|
+
});
|
|
29618
|
+
this.windDowns.set(taskId, windDown);
|
|
29619
|
+
}
|
|
29620
|
+
/**
|
|
29621
|
+
* An ended attempt's runner is gone: the count its session ended on is the
|
|
29622
|
+
* one a continue of that session counts from. One still current is ended
|
|
29623
|
+
* with that count instead.
|
|
29624
|
+
*/
|
|
29625
|
+
woundDown(attempt, session) {
|
|
29626
|
+
if (attempt.planScope !== this.planScope || !attempt.runnerStopped || this.attempts.get(attempt.taskId) === attempt) return;
|
|
29627
|
+
this.store.endAttemptUsage(attempt.taskId, attempt.attempt, (/* @__PURE__ */ new Date()).toISOString(), session.sessionCost?.() ?? void 0, session.nativeSessionId() ?? void 0);
|
|
29628
|
+
this.emit("onUsageChanged");
|
|
29629
|
+
}
|
|
29300
29630
|
/** Kept on the task once the attempt ends, so a continue can resume it after a reload (ADR-0018, K1). */
|
|
29301
29631
|
saveNativeSession(taskId, nativeSessionId, runner) {
|
|
29302
29632
|
if (nativeSessionId) this.store.setTaskRunnerSessionId(taskId, nativeSessionId, runner);
|
|
@@ -29654,7 +29984,7 @@ function taskReadPage(text3, { lines: lines2, since }) {
|
|
|
29654
29984
|
|
|
29655
29985
|
// src/services/plannerTools.ts
|
|
29656
29986
|
var PLANNER_TURN_ENDED = "The planning turn this call was made in has ended, so nothing was submitted.";
|
|
29657
|
-
function
|
|
29987
|
+
function runnersOf2(tasks) {
|
|
29658
29988
|
return [...new Set(flattenTasks(tasks).filter((t) => t.type !== "user").map((t) => t.assignedRunner))];
|
|
29659
29989
|
}
|
|
29660
29990
|
function plannerToolHandler(host) {
|
|
@@ -32204,19 +32534,50 @@ var TaskLogRecorder = class {
|
|
|
32204
32534
|
};
|
|
32205
32535
|
|
|
32206
32536
|
// src/services/ContextWindowFill.ts
|
|
32207
|
-
var
|
|
32208
|
-
constructor(catalog
|
|
32537
|
+
var SessionWindows = class {
|
|
32538
|
+
constructor(catalog = () => void 0) {
|
|
32209
32539
|
this.catalog = catalog;
|
|
32210
|
-
if (last.contextWindow !== void 0) this.known.set(modelKey(last.source, last.model), last.contextWindow);
|
|
32211
32540
|
}
|
|
32212
32541
|
catalog;
|
|
32213
|
-
|
|
32542
|
+
reported = /* @__PURE__ */ new Map();
|
|
32543
|
+
report(source, model, contextWindow) {
|
|
32544
|
+
if (contextWindow > 0) this.reported.set(modelKey(source, model), { source, model, contextWindow });
|
|
32545
|
+
}
|
|
32546
|
+
get(source, model) {
|
|
32547
|
+
return this.reported.get(modelKey(source, model))?.contextWindow;
|
|
32548
|
+
}
|
|
32549
|
+
snapshot() {
|
|
32550
|
+
return [...this.reported.values()];
|
|
32551
|
+
}
|
|
32552
|
+
restore(saved) {
|
|
32553
|
+
this.reported.clear();
|
|
32554
|
+
for (const { source, model, contextWindow } of saved ?? []) this.report(source, model, contextWindow);
|
|
32555
|
+
}
|
|
32556
|
+
clear() {
|
|
32557
|
+
this.reported.clear();
|
|
32558
|
+
}
|
|
32559
|
+
};
|
|
32560
|
+
var ContextWindowFill = class {
|
|
32561
|
+
constructor(windows, last = {}) {
|
|
32562
|
+
this.windows = windows;
|
|
32563
|
+
if (last.contextWindow !== void 0) this.known.set(modelKey(last.source, last.model), last.contextWindow);
|
|
32564
|
+
}
|
|
32565
|
+
windows;
|
|
32566
|
+
/** The window this stream used for each of its models, by reporter and model. */
|
|
32214
32567
|
known = /* @__PURE__ */ new Map();
|
|
32215
32568
|
fill(record) {
|
|
32216
32569
|
if (record.subagentId || record.aggregate) return record;
|
|
32217
32570
|
const key = modelKey(record.source, record.model);
|
|
32571
|
+
if (record.compacted) {
|
|
32572
|
+
if (!record.model) return record;
|
|
32573
|
+
const known = this.windows.get(record.source, record.model) ?? this.known.get(key) ?? positive(this.windows.catalog(record.source, record.model));
|
|
32574
|
+
if (known === void 0) return record;
|
|
32575
|
+
this.known.set(key, known);
|
|
32576
|
+
return { ...record, contextWindow: known };
|
|
32577
|
+
}
|
|
32218
32578
|
const reported = record.contextWindow !== void 0 && record.contextWindow > 0 ? record.contextWindow : void 0;
|
|
32219
|
-
|
|
32579
|
+
if (reported !== void 0 && record.model) this.windows.report(record.source, record.model, reported);
|
|
32580
|
+
const window = reported ?? (record.model ? this.windows.get(record.source, record.model) : void 0) ?? this.known.get(key) ?? (record.model ? positive(this.windows.catalog(record.source, record.model)) : void 0);
|
|
32220
32581
|
if (window !== void 0) this.known.set(key, window);
|
|
32221
32582
|
return window === void 0 || window === record.contextWindow ? record : { ...record, contextWindow: window };
|
|
32222
32583
|
}
|
|
@@ -32224,9 +32585,9 @@ var ContextWindowFill = class {
|
|
|
32224
32585
|
function modelKey(source, model) {
|
|
32225
32586
|
return JSON.stringify([source ?? null, model ?? null]);
|
|
32226
32587
|
}
|
|
32227
|
-
function withContextWindows(runner,
|
|
32588
|
+
function withContextWindows(runner, windows) {
|
|
32228
32589
|
return {
|
|
32229
|
-
spawn: async (opts) => windowedSession(await runner.spawn(opts), new ContextWindowFill(
|
|
32590
|
+
spawn: async (opts) => windowedSession(await runner.spawn(opts), new ContextWindowFill(windows, opts.resumeWindow)),
|
|
32230
32591
|
stop: (sessionId) => runner.stop(sessionId),
|
|
32231
32592
|
stopAll: () => runner.stopAll(),
|
|
32232
32593
|
get activeCount() {
|
|
@@ -32257,12 +32618,12 @@ function positive(n) {
|
|
|
32257
32618
|
|
|
32258
32619
|
// src/services/PlannerUsage.ts
|
|
32259
32620
|
var PlannerUsageLedger = class {
|
|
32260
|
-
/** `
|
|
32261
|
-
constructor(
|
|
32262
|
-
this.
|
|
32263
|
-
this.windows = new ContextWindowFill(
|
|
32621
|
+
/** `sessionWindows` serves the window of a planner model whose backend reports none. */
|
|
32622
|
+
constructor(sessionWindows = new SessionWindows()) {
|
|
32623
|
+
this.sessionWindows = sessionWindows;
|
|
32624
|
+
this.windows = new ContextWindowFill(sessionWindows);
|
|
32264
32625
|
}
|
|
32265
|
-
|
|
32626
|
+
sessionWindows;
|
|
32266
32627
|
usage = { totals: {} };
|
|
32267
32628
|
windows;
|
|
32268
32629
|
/** Fold one model call's record into the totals; returns the running ledger. */
|
|
@@ -32273,16 +32634,16 @@ var PlannerUsageLedger = class {
|
|
|
32273
32634
|
/** Adopt a persisted ledger (a reopened session) or start from zero. */
|
|
32274
32635
|
restore(usage) {
|
|
32275
32636
|
this.usage = usage ?? { totals: {} };
|
|
32276
|
-
this.windows = new ContextWindowFill(this.
|
|
32637
|
+
this.windows = new ContextWindowFill(this.sessionWindows, this.usage);
|
|
32277
32638
|
}
|
|
32278
32639
|
/** Start a fresh session's ledger from zero. */
|
|
32279
32640
|
clear() {
|
|
32280
32641
|
this.usage = { totals: {} };
|
|
32281
|
-
this.windows = new ContextWindowFill(this.
|
|
32642
|
+
this.windows = new ContextWindowFill(this.sessionWindows);
|
|
32282
32643
|
}
|
|
32283
|
-
/** Whether
|
|
32644
|
+
/** Whether the ledger has measures or session billing facts to broadcast. */
|
|
32284
32645
|
get hasUsage() {
|
|
32285
|
-
return isMeasured(this.usage.totals);
|
|
32646
|
+
return isMeasured(this.usage.totals) || this.usage.sessionBilling !== void 0;
|
|
32286
32647
|
}
|
|
32287
32648
|
/** The value persisted onto the plan state. */
|
|
32288
32649
|
snapshot() {
|
|
@@ -33731,7 +34092,7 @@ function createSession(deps) {
|
|
|
33731
34092
|
const backend = new PlannerBackend(deps.aiService ? { pinned: deps.aiService, busy } : { provider: () => deps.config.aiProvider, create: () => createAiService(deps.config, { workspaceRoot: deps.workspaceRoot, mcpServer }), busy });
|
|
33732
34093
|
const aiService = () => backend.current();
|
|
33733
34094
|
const store = new PlanStore();
|
|
33734
|
-
const
|
|
34095
|
+
const windows = new SessionWindows((source, model) => deps.modelResolver.contextWindowFor?.(source, model));
|
|
33735
34096
|
const taskLogs = new TaskLogRecorder({
|
|
33736
34097
|
broadcast: deps.broadcast,
|
|
33737
34098
|
// Read through the Session, whose id and workspace are the ones it saves
|
|
@@ -33764,7 +34125,7 @@ function createSession(deps) {
|
|
|
33764
34125
|
const orchestrator = TaskOrchestrator.compose({
|
|
33765
34126
|
config: deps.config,
|
|
33766
34127
|
notifications: deps.notifications,
|
|
33767
|
-
runner: taskLogs.wrap(runnerApprovals.wrap(withContextWindows(deps.runner,
|
|
34128
|
+
runner: taskLogs.wrap(runnerApprovals.wrap(withContextWindows(deps.runner, windows))),
|
|
33768
34129
|
store,
|
|
33769
34130
|
output: deps.taskOutput,
|
|
33770
34131
|
isolation: deps.isolation,
|
|
@@ -33776,7 +34137,7 @@ function createSession(deps) {
|
|
|
33776
34137
|
runSettings: () => session.planState?.runSettings,
|
|
33777
34138
|
isApproved: (taskId) => session.isApproved(taskId)
|
|
33778
34139
|
});
|
|
33779
|
-
const usage = new PlannerUsageLedger(
|
|
34140
|
+
const usage = new PlannerUsageLedger(windows);
|
|
33780
34141
|
const events = new SessionEventRelay({
|
|
33781
34142
|
broadcast: deps.broadcast,
|
|
33782
34143
|
onNotice: deps.onNotice,
|
|
@@ -33846,6 +34207,7 @@ function createSession(deps) {
|
|
|
33846
34207
|
runs: orchestrator.runs,
|
|
33847
34208
|
events,
|
|
33848
34209
|
usage,
|
|
34210
|
+
windows,
|
|
33849
34211
|
approvals,
|
|
33850
34212
|
approvalPolicy,
|
|
33851
34213
|
taskLogs,
|
|
@@ -33873,6 +34235,7 @@ function configSecrets(config) {
|
|
|
33873
34235
|
var Session = class {
|
|
33874
34236
|
backend;
|
|
33875
34237
|
usage;
|
|
34238
|
+
windows;
|
|
33876
34239
|
events;
|
|
33877
34240
|
planner;
|
|
33878
34241
|
orchestrator;
|
|
@@ -33978,6 +34341,7 @@ var Session = class {
|
|
|
33978
34341
|
this.runs = parts.runs;
|
|
33979
34342
|
this.events = parts.events;
|
|
33980
34343
|
this.usage = parts.usage;
|
|
34344
|
+
this.windows = parts.windows;
|
|
33981
34345
|
this.approvals = parts.approvals;
|
|
33982
34346
|
this.approvalPolicy = parts.approvalPolicy;
|
|
33983
34347
|
this.taskLogs = parts.taskLogs;
|
|
@@ -34456,6 +34820,7 @@ var Session = class {
|
|
|
34456
34820
|
this.syncPlanTasks();
|
|
34457
34821
|
this.plan.isolation = this.runs.planIsolation ?? void 0;
|
|
34458
34822
|
this.plan.plannerUsage = this.usage.snapshot();
|
|
34823
|
+
this.plan.contextWindows = this.windows.snapshot();
|
|
34459
34824
|
this.plan.supervision = this.supervision.snapshot();
|
|
34460
34825
|
this.plan.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
|
|
34461
34826
|
this.save(this.plan, this.goal, this.workspace, this.currentSessionId);
|
|
@@ -34485,6 +34850,7 @@ var Session = class {
|
|
|
34485
34850
|
this.owingEffects.clear();
|
|
34486
34851
|
this.conversation.reset();
|
|
34487
34852
|
this.usage.clear();
|
|
34853
|
+
this.windows.clear();
|
|
34488
34854
|
this.approvals.clear(isPlannerApproval);
|
|
34489
34855
|
if (!keepGrants) this.approvalPolicy.reset();
|
|
34490
34856
|
this.store.clearLog();
|
|
@@ -34825,7 +35191,18 @@ var Session = class {
|
|
|
34825
35191
|
if (!this.plan) throw new ConversationEditError("No planning conversation to condense");
|
|
34826
35192
|
const opening = this.plannerTurnMayOpen(signal);
|
|
34827
35193
|
if (opening) await opening;
|
|
34828
|
-
|
|
35194
|
+
const compaction = await this.conversation.compact(signal);
|
|
35195
|
+
const usage = this.usage.snapshot();
|
|
35196
|
+
if (usage.lastPromptTokens !== void 0) {
|
|
35197
|
+
this.usage.record({
|
|
35198
|
+
source: usage.source ?? this.config.aiProvider,
|
|
35199
|
+
...usage.model ? { model: usage.model } : {},
|
|
35200
|
+
compacted: true
|
|
35201
|
+
});
|
|
35202
|
+
this.persist();
|
|
35203
|
+
this.broadcast(this.usage.message());
|
|
35204
|
+
}
|
|
35205
|
+
return compaction;
|
|
34829
35206
|
}
|
|
34830
35207
|
rewindTargets() {
|
|
34831
35208
|
return this.conversation.rewindTargets();
|
|
@@ -34912,7 +35289,7 @@ var Session = class {
|
|
|
34912
35289
|
*/
|
|
34913
35290
|
adoptPlannerTasks(tasks, how) {
|
|
34914
35291
|
this.turnEffects.landed(how);
|
|
34915
|
-
for (const runner of
|
|
35292
|
+
for (const runner of runnersOf2(tasks)) this.editor.admitRunner(runner, this.catalog.known(runner));
|
|
34916
35293
|
const runners = this.plan.runners;
|
|
34917
35294
|
const before = this.store.planTasks;
|
|
34918
35295
|
let coerced = resolveAssignments(tasks, before, this.catalog.edit(runners)).tasks;
|
|
@@ -35313,7 +35690,10 @@ var Session = class {
|
|
|
35313
35690
|
if (!plan.approvedTasks && plan.approved) {
|
|
35314
35691
|
plan.approvedTasks = flattenTasks(plan.tasks).map((t) => t.id);
|
|
35315
35692
|
}
|
|
35316
|
-
if (adopting)
|
|
35693
|
+
if (adopting) {
|
|
35694
|
+
this.usage.restore(plan.plannerUsage);
|
|
35695
|
+
this.windows.restore(plan.contextWindows);
|
|
35696
|
+
}
|
|
35317
35697
|
if (opts?.sessionId) this.currentSessionId = opts.sessionId;
|
|
35318
35698
|
if (adopting) {
|
|
35319
35699
|
for (const task of flattenTasks(plan.tasks)) delete task.resumeAt;
|
|
@@ -35395,7 +35775,7 @@ function openTurn(view, { turnId, prompt = "", skills = [], wakeUps = [], forced
|
|
|
35395
35775
|
wakeUps = wakeUps.slice(adopted.wakeUps);
|
|
35396
35776
|
if (wakeUps.length === 0) return adopted.prompt || prompt === "" && adopted.wakeUps > 0 ? view : promptLine(view, turnId, prompt, skills, forced);
|
|
35397
35777
|
const said2 = prompt === "" || adopted.prompt ? view : promptLine(view, turnId, prompt, skills, forced);
|
|
35398
|
-
const at = prompt === "" ? said2.blocks.length : findLastIndex(said2.blocks, (b) => b.type === "message" && b.role === "user" && b.turnId === turnId);
|
|
35778
|
+
const at = prompt === "" ? said2.blocks.length - (said2.blocks.at(-1)?.type === "usage" ? 1 : 0) : findLastIndex(said2.blocks, (b) => b.type === "message" && b.role === "user" && b.turnId === turnId);
|
|
35399
35779
|
const updates = wakeUps.map((text3, n) => settledMessage(`b${said2.nextId + n}`, "wake_up", text3, turnId));
|
|
35400
35780
|
return { ...said2, blocks: [...said2.blocks.slice(0, at), ...updates, ...said2.blocks.slice(at)], nextId: said2.nextId + updates.length };
|
|
35401
35781
|
}
|
|
@@ -36082,17 +36462,12 @@ var AbstractRunner = class {
|
|
|
36082
36462
|
get activeCount() {
|
|
36083
36463
|
return this.sessions.size;
|
|
36084
36464
|
}
|
|
36465
|
+
/** A session winding down stays registered until it exits, so a second stop gets the same wind-down. */
|
|
36085
36466
|
stop(sessionId) {
|
|
36086
|
-
|
|
36087
|
-
if (!session) return;
|
|
36088
|
-
session.kill();
|
|
36089
|
-
this.sessions.delete(sessionId);
|
|
36467
|
+
return this.sessions.get(sessionId)?.stop() ?? Promise.resolve();
|
|
36090
36468
|
}
|
|
36091
36469
|
stopAll() {
|
|
36092
|
-
for (const
|
|
36093
|
-
session.kill();
|
|
36094
|
-
}
|
|
36095
|
-
this.sessions.clear();
|
|
36470
|
+
for (const session of this.sessions.values()) void session.stop();
|
|
36096
36471
|
}
|
|
36097
36472
|
registerSession(id, session) {
|
|
36098
36473
|
this.sessions.set(id, session);
|
|
@@ -36250,6 +36625,8 @@ var StructuredSession = class extends AbstractRunnerSession {
|
|
|
36250
36625
|
queue = [];
|
|
36251
36626
|
messageCount = 0;
|
|
36252
36627
|
interrupting = null;
|
|
36628
|
+
/** The wind-down once {@link stop} was asked for: the same for every caller, and nothing new is sent meanwhile. */
|
|
36629
|
+
stopping = null;
|
|
36253
36630
|
lastSessionId = null;
|
|
36254
36631
|
lastSessionCost = null;
|
|
36255
36632
|
structuredEmitter = new import_events2.EventEmitter();
|
|
@@ -36275,7 +36652,7 @@ var StructuredSession = class extends AbstractRunnerSession {
|
|
|
36275
36652
|
await this.startAttached(this.launch.startOptions);
|
|
36276
36653
|
this.state = "working";
|
|
36277
36654
|
setImmediate(() => {
|
|
36278
|
-
if (!this.exited) this.deliver(prompt);
|
|
36655
|
+
if (!this.exited && !this.stopping) this.deliver(prompt);
|
|
36279
36656
|
});
|
|
36280
36657
|
}
|
|
36281
36658
|
getOutput() {
|
|
@@ -36355,6 +36732,53 @@ var StructuredSession = class extends AbstractRunnerSession {
|
|
|
36355
36732
|
this.withdrawPermissions();
|
|
36356
36733
|
this.baseHandleExit(-1);
|
|
36357
36734
|
}
|
|
36735
|
+
stop() {
|
|
36736
|
+
if (this.stopping) return this.stopping;
|
|
36737
|
+
if (this.exited) return Promise.resolve();
|
|
36738
|
+
let woundDown;
|
|
36739
|
+
this.stopping = new Promise((resolve9) => {
|
|
36740
|
+
woundDown = resolve9;
|
|
36741
|
+
});
|
|
36742
|
+
void this.windDown().catch(() => this.kill()).then(woundDown);
|
|
36743
|
+
return this.stopping;
|
|
36744
|
+
}
|
|
36745
|
+
/**
|
|
36746
|
+
* The attempt is over (ADR-0018, L1), so what is still owed goes undelivered
|
|
36747
|
+
* and every tool request unanswered at once. A turn still open is
|
|
36748
|
+
* interrupted, and its close — where Claude Code reports the turn's cost and
|
|
36749
|
+
* its model's window — is waited for; then the runner is told nothing more
|
|
36750
|
+
* is coming and left to exit by itself. All of it within the interrupt
|
|
36751
|
+
* grace: a runner still there once it runs out is killed.
|
|
36752
|
+
*/
|
|
36753
|
+
async windDown() {
|
|
36754
|
+
const adapter = this.adapterAttached ? this.adapter : null;
|
|
36755
|
+
const deadline = Date.now() + this.launch.interruptGraceMs;
|
|
36756
|
+
this.revokeTools();
|
|
36757
|
+
this.undeliverQueued();
|
|
36758
|
+
this.withdrawPermissions();
|
|
36759
|
+
const open = this.turn && !this.turn.ended ? this.turn : null;
|
|
36760
|
+
if (adapter && (!open || await this.turnClosed(open, adapter, deadline)) && adapter.endInput && !this.exited) {
|
|
36761
|
+
adapter.endInput();
|
|
36762
|
+
await this.within(this.gone(), deadline);
|
|
36763
|
+
}
|
|
36764
|
+
if (this.exited) adapter?.dispose();
|
|
36765
|
+
else this.kill();
|
|
36766
|
+
}
|
|
36767
|
+
/** Whether `turn` closed within the deadline once interrupted. */
|
|
36768
|
+
async turnClosed(turn, adapter, deadline) {
|
|
36769
|
+
const closed = new Promise((resolve9) => this.structuredEmitter.once("turnEnd", () => resolve9()));
|
|
36770
|
+
adapter.interrupt(Math.max(0, deadline - Date.now())).catch(() => false);
|
|
36771
|
+
return await this.within(closed, deadline) && turn.ended;
|
|
36772
|
+
}
|
|
36773
|
+
/** Whether `done` settled before the deadline, or the session ended meanwhile. */
|
|
36774
|
+
within(done, deadline) {
|
|
36775
|
+
return settleWithin(done.then(() => true), { timeoutMs: Math.max(0, deadline - Date.now()), onTimeout: () => false, ended: this.gone(), onEnded: () => true });
|
|
36776
|
+
}
|
|
36777
|
+
gone() {
|
|
36778
|
+
const { signal } = this.ended;
|
|
36779
|
+
if (signal.aborted) return Promise.resolve();
|
|
36780
|
+
return new Promise((resolve9) => signal.addEventListener("abort", () => resolve9(), { once: true }));
|
|
36781
|
+
}
|
|
36358
36782
|
turnState() {
|
|
36359
36783
|
return this.state;
|
|
36360
36784
|
}
|
|
@@ -36380,7 +36804,7 @@ var StructuredSession = class extends AbstractRunnerSession {
|
|
|
36380
36804
|
this.messageCount += 1;
|
|
36381
36805
|
const id = `msg-${this.messageCount}`;
|
|
36382
36806
|
const sender2 = opening.from ? { from: opening.from } : {};
|
|
36383
|
-
if (this.exited) this.emitEvent({ type: "message_undelivered", messageId: id, text: text3, ...sender2 });
|
|
36807
|
+
if (this.exited || this.stopping) this.emitEvent({ type: "message_undelivered", messageId: id, text: text3, ...sender2 });
|
|
36384
36808
|
else if (this.state === "idle" && this.adapterStarted && !this.sending) this.deliver(text3, opening);
|
|
36385
36809
|
else {
|
|
36386
36810
|
this.state = "working";
|
|
@@ -36435,7 +36859,7 @@ var StructuredSession = class extends AbstractRunnerSession {
|
|
|
36435
36859
|
});
|
|
36436
36860
|
}
|
|
36437
36861
|
runningTurn() {
|
|
36438
|
-
return !this.exited && this.adapter !== null && this.turn !== null && !this.turn.ended;
|
|
36862
|
+
return !this.exited && !this.stopping && this.adapter !== null && this.turn !== null && !this.turn.ended;
|
|
36439
36863
|
}
|
|
36440
36864
|
/** No runner can recall a message once offered to it, so only a queued one comes back. */
|
|
36441
36865
|
removeQueued(id) {
|
|
@@ -36496,7 +36920,7 @@ var StructuredSession = class extends AbstractRunnerSession {
|
|
|
36496
36920
|
if (this.interrupting) return this.interrupting;
|
|
36497
36921
|
const turn = this.turn;
|
|
36498
36922
|
const adapter = this.adapter;
|
|
36499
|
-
if (!turn || turn.ended || !adapter) return Promise.resolve();
|
|
36923
|
+
if (!turn || turn.ended || !adapter || this.stopping) return Promise.resolve();
|
|
36500
36924
|
this.interrupting = this.interruptTurn(turn, adapter).finally(() => {
|
|
36501
36925
|
this.interrupting = null;
|
|
36502
36926
|
this.offerNext();
|
|
@@ -36510,7 +36934,7 @@ var StructuredSession = class extends AbstractRunnerSession {
|
|
|
36510
36934
|
const settled = await settleWithin(ended.then(() => true), { timeoutMs: grace, onTimeout: () => false });
|
|
36511
36935
|
if (settled) return;
|
|
36512
36936
|
}
|
|
36513
|
-
if (turn.ended || this.exited) return;
|
|
36937
|
+
if (turn.ended || this.exited || this.stopping) return;
|
|
36514
36938
|
await this.restartInterrupted(turn);
|
|
36515
36939
|
}
|
|
36516
36940
|
/**
|
|
@@ -36614,7 +37038,7 @@ var StructuredSession = class extends AbstractRunnerSession {
|
|
|
36614
37038
|
offerNext() {
|
|
36615
37039
|
const adapter = this.adapter;
|
|
36616
37040
|
const turn = this.turn;
|
|
36617
|
-
if (!adapter?.steer || !turn || turn.ended || turn.steerRefused || this.interrupting || this.exited) return;
|
|
37041
|
+
if (!adapter?.steer || !turn || turn.ended || turn.steerRefused || this.interrupting || this.exited || this.stopping) return;
|
|
36618
37042
|
if (this.queue.some((m) => m.stage === "steering")) return;
|
|
36619
37043
|
const next = this.queue.find((m) => m.stage === "queued");
|
|
36620
37044
|
if (!next || next.command) return;
|
|
@@ -36689,7 +37113,7 @@ var StructuredSession = class extends AbstractRunnerSession {
|
|
|
36689
37113
|
}
|
|
36690
37114
|
return;
|
|
36691
37115
|
}
|
|
36692
|
-
if (!turn && TURN_OPENING_EVENTS.has(event.type) && !("subagentId" in event && event.subagentId)) {
|
|
37116
|
+
if (!turn && !this.stopping && TURN_OPENING_EVENTS.has(event.type) && !("subagentId" in event && event.subagentId)) {
|
|
36693
37117
|
turn = this.openTurn("");
|
|
36694
37118
|
this.offerNext();
|
|
36695
37119
|
}
|
|
@@ -36760,7 +37184,7 @@ var StructuredSession = class extends AbstractRunnerSession {
|
|
|
36760
37184
|
* back in Ordewell's hands: while it is on offer or owed, nothing overtakes it.
|
|
36761
37185
|
*/
|
|
36762
37186
|
deliverNext() {
|
|
36763
|
-
if (this.exited || this.sending || this.turn && !this.turn.ended) return;
|
|
37187
|
+
if (this.exited || this.stopping || this.sending || this.turn && !this.turn.ended) return;
|
|
36764
37188
|
const head = this.queue[0];
|
|
36765
37189
|
if (!head || this.queue.some((message) => message.stage !== "queued")) return;
|
|
36766
37190
|
this.queue.shift();
|
|
@@ -37210,7 +37634,10 @@ var PlannerModelMemory = class {
|
|
|
37210
37634
|
collectProviderCredentials,
|
|
37211
37635
|
commandAllowedBy,
|
|
37212
37636
|
configuredProviders,
|
|
37637
|
+
contextMeterText,
|
|
37638
|
+
contextMeterTitle,
|
|
37213
37639
|
continuability,
|
|
37640
|
+
costText,
|
|
37214
37641
|
countedSessionCost,
|
|
37215
37642
|
createAiService,
|
|
37216
37643
|
createEmptyPlan,
|
|
@@ -37253,7 +37680,9 @@ var PlannerModelMemory = class {
|
|
|
37253
37680
|
flattenTasksWithParents,
|
|
37254
37681
|
followTaskLog,
|
|
37255
37682
|
followTurn,
|
|
37683
|
+
formatCost,
|
|
37256
37684
|
formatSearchOutput,
|
|
37685
|
+
formatTokenCount,
|
|
37257
37686
|
fromTranscript,
|
|
37258
37687
|
getLatestSession,
|
|
37259
37688
|
getProviderMeta,
|
|
@@ -37305,6 +37734,7 @@ var PlannerModelMemory = class {
|
|
|
37305
37734
|
migratePlanState,
|
|
37306
37735
|
mintDaemonToken,
|
|
37307
37736
|
mintSessionId,
|
|
37737
|
+
modelCostTitle,
|
|
37308
37738
|
namedRepos,
|
|
37309
37739
|
newTaskFields,
|
|
37310
37740
|
normalizeAgentArgs,
|
|
@@ -37390,6 +37820,7 @@ var PlannerModelMemory = class {
|
|
|
37390
37820
|
stopTurn,
|
|
37391
37821
|
stripAnsi,
|
|
37392
37822
|
stripModelPrefix,
|
|
37823
|
+
subagentCostTitle,
|
|
37393
37824
|
subtreeIds,
|
|
37394
37825
|
summarizeToolCall,
|
|
37395
37826
|
supervisorSummary,
|
|
@@ -37408,6 +37839,7 @@ var PlannerModelMemory = class {
|
|
|
37408
37839
|
taskStartedNotice,
|
|
37409
37840
|
taskStatusKind,
|
|
37410
37841
|
taskUsageSummary,
|
|
37842
|
+
tasksTotalText,
|
|
37411
37843
|
textHasTaskOps,
|
|
37412
37844
|
textHasTaskQuery,
|
|
37413
37845
|
titledRefs,
|
|
@@ -37425,6 +37857,7 @@ var PlannerModelMemory = class {
|
|
|
37425
37857
|
unsendAll,
|
|
37426
37858
|
unsendLatest,
|
|
37427
37859
|
usageLine,
|
|
37860
|
+
usageText,
|
|
37428
37861
|
waitsForApproval,
|
|
37429
37862
|
walkPlan,
|
|
37430
37863
|
wellKnownBinDirs,
|