reasonix 0.5.13 → 0.5.20
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/cli/index.js +560 -49
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +77 -1
- package/dist/index.js +113 -14
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -69,6 +69,14 @@ interface ChatMessage {
|
|
|
69
69
|
name?: string;
|
|
70
70
|
tool_call_id?: string;
|
|
71
71
|
tool_calls?: ToolCall[];
|
|
72
|
+
/**
|
|
73
|
+
* R1 `reasoning_content` captured from the assistant's thinking turn.
|
|
74
|
+
* DeepSeek's thinking mode 400s with "reasoning_content in the
|
|
75
|
+
* thinking mode must be passed back" when a tool-loop continuation
|
|
76
|
+
* omits it from the preceding assistant message. Round-tripped for
|
|
77
|
+
* deepseek-reasoner turns with tool_calls; absent for deepseek-chat.
|
|
78
|
+
*/
|
|
79
|
+
reasoning_content?: string | null;
|
|
72
80
|
}
|
|
73
81
|
interface RawUsage {
|
|
74
82
|
prompt_tokens?: number;
|
|
@@ -790,6 +798,19 @@ interface LoopEvent {
|
|
|
790
798
|
toolArgs?: string;
|
|
791
799
|
/** Cumulative arguments-string length for `role === "tool_call_delta"`. */
|
|
792
800
|
toolCallArgsChars?: number;
|
|
801
|
+
/**
|
|
802
|
+
* Zero-based index of the tool call this delta belongs to. Surfaces
|
|
803
|
+
* multi-tool turns: on a response emitting 4 write_file calls the UI
|
|
804
|
+
* can show "building call 3/?" instead of a context-free spinner.
|
|
805
|
+
*/
|
|
806
|
+
toolCallIndex?: number;
|
|
807
|
+
/**
|
|
808
|
+
* Count of prior tool calls (this turn) whose arguments have finished
|
|
809
|
+
* streaming into valid JSON. Not all ready calls have been dispatched
|
|
810
|
+
* yet — dispatch still happens post-stream — but the user gets "2
|
|
811
|
+
* ready" progress feedback while later calls keep streaming.
|
|
812
|
+
*/
|
|
813
|
+
toolCallReadyCount?: number;
|
|
793
814
|
stats?: TurnStats;
|
|
794
815
|
planState?: TypedPlanState;
|
|
795
816
|
repair?: RepairReport;
|
|
@@ -922,7 +943,7 @@ declare class CacheFirstLoop {
|
|
|
922
943
|
tokensSaved: number;
|
|
923
944
|
charsSaved: number;
|
|
924
945
|
};
|
|
925
|
-
|
|
946
|
+
appendAndPersist(message: ChatMessage): void;
|
|
926
947
|
/**
|
|
927
948
|
* Start a fresh conversation WITHOUT exiting. Drops every message
|
|
928
949
|
* in the in-memory log AND rewrites the session file to empty so
|
|
@@ -970,6 +991,15 @@ declare class CacheFirstLoop {
|
|
|
970
991
|
private forceSummaryAfterIterLimit;
|
|
971
992
|
run(userInput: string, onEvent?: (ev: LoopEvent) => void): Promise<string>;
|
|
972
993
|
private assistantMessage;
|
|
994
|
+
/**
|
|
995
|
+
* Build a synthetic assistant message we insert into the log without
|
|
996
|
+
* a real API round trip (abort notices, future system injections).
|
|
997
|
+
* Reasoner models reject follow-up requests whose assistant history
|
|
998
|
+
* is missing `reasoning_content`, so we stamp an empty-string
|
|
999
|
+
* placeholder on reasoner sessions to satisfy the validator. V3
|
|
1000
|
+
* doesn't care — field stays absent there.
|
|
1001
|
+
*/
|
|
1002
|
+
private syntheticAssistantMessage;
|
|
973
1003
|
}
|
|
974
1004
|
/**
|
|
975
1005
|
* R1 occasionally hallucinates tool-call markup as plain text when the
|
|
@@ -1550,6 +1580,10 @@ interface SubagentEvent {
|
|
|
1550
1580
|
kind: "start" | "progress" | "end";
|
|
1551
1581
|
/** First ~30 chars of the task prompt — used for the TUI status row. */
|
|
1552
1582
|
task: string;
|
|
1583
|
+
/** Skill that spawned this subagent, when applicable. Stamped on every event so the TUI/logger can attribute without extra plumbing. */
|
|
1584
|
+
skillName?: string;
|
|
1585
|
+
/** Model id the child loop ran on. Stamped alongside skillName. */
|
|
1586
|
+
model?: string;
|
|
1553
1587
|
/** Iteration count inside the child loop (number of tool results so far). */
|
|
1554
1588
|
iter?: number;
|
|
1555
1589
|
/** Wall-clock ms since the subagent started. */
|
|
@@ -1560,6 +1594,10 @@ interface SubagentEvent {
|
|
|
1560
1594
|
error?: string;
|
|
1561
1595
|
/** Total turns the subagent took. Set on `end`. */
|
|
1562
1596
|
turns?: number;
|
|
1597
|
+
/** Total USD spent inside the child loop. Set on `end`. */
|
|
1598
|
+
costUsd?: number;
|
|
1599
|
+
/** Aggregated child-loop Usage (sum across turns). Set on `end`. */
|
|
1600
|
+
usage?: Usage;
|
|
1563
1601
|
}
|
|
1564
1602
|
/**
|
|
1565
1603
|
* Mutable ref the registration writes through. The TUI sets `.current`
|
|
@@ -3066,6 +3104,22 @@ interface UsageRecord {
|
|
|
3066
3104
|
costUsd: number;
|
|
3067
3105
|
/** What the same turn would have cost at Claude Sonnet 4.6 rates. */
|
|
3068
3106
|
claudeEquivUsd: number;
|
|
3107
|
+
/**
|
|
3108
|
+
* Distinguishes ordinary parent-loop turns from subagent summary rows.
|
|
3109
|
+
* Absent on pre-0.5.14 records — treat as "turn" when missing.
|
|
3110
|
+
*/
|
|
3111
|
+
kind?: "turn" | "subagent";
|
|
3112
|
+
/** Present when `kind === "subagent"`. Attribution metadata for the /stats roll-up. */
|
|
3113
|
+
subagent?: {
|
|
3114
|
+
/** Skill that spawned it, when the spawn came from a `runAs: subagent` skill. */
|
|
3115
|
+
skillName?: string;
|
|
3116
|
+
/** First ~60 chars of the task prompt — enough context to recognize a run, never the full text. */
|
|
3117
|
+
taskPreview: string;
|
|
3118
|
+
/** Tool calls the child loop dispatched before returning. */
|
|
3119
|
+
toolIters: number;
|
|
3120
|
+
/** Wall-clock ms. */
|
|
3121
|
+
durationMs: number;
|
|
3122
|
+
};
|
|
3069
3123
|
}
|
|
3070
3124
|
/** Where the log lives. Tests override via `opts.path`. */
|
|
3071
3125
|
declare function defaultUsageLogPath(homeDirOverride?: string): string;
|
|
@@ -3077,6 +3131,9 @@ interface AppendUsageInput {
|
|
|
3077
3131
|
now?: number;
|
|
3078
3132
|
/** Override the log path (tests). */
|
|
3079
3133
|
path?: string;
|
|
3134
|
+
/** When appending a subagent summary row, set `kind: "subagent"` and populate `subagent`. */
|
|
3135
|
+
kind?: "turn" | "subagent";
|
|
3136
|
+
subagent?: UsageRecord["subagent"];
|
|
3080
3137
|
}
|
|
3081
3138
|
/**
|
|
3082
3139
|
* Append one record and return it. Swallows disk errors — the TUI
|
|
@@ -3131,6 +3188,25 @@ interface UsageAggregate {
|
|
|
3131
3188
|
firstSeen: number | null;
|
|
3132
3189
|
/** Latest record's ts, or `null` when the log is empty. */
|
|
3133
3190
|
lastSeen: number | null;
|
|
3191
|
+
/**
|
|
3192
|
+
* Subagent-specific rollup. Undefined when no subagent records exist
|
|
3193
|
+
* in the log so consumers can cheaply skip the section. Counts reflect
|
|
3194
|
+
* subagent SPAWNS (not internal child-loop turns) — one row per run.
|
|
3195
|
+
*/
|
|
3196
|
+
subagents?: SubagentAggregate;
|
|
3197
|
+
}
|
|
3198
|
+
/** Rolled-up view of all `kind: "subagent"` records. */
|
|
3199
|
+
interface SubagentAggregate {
|
|
3200
|
+
total: number;
|
|
3201
|
+
costUsd: number;
|
|
3202
|
+
totalDurationMs: number;
|
|
3203
|
+
/** Per-skill breakdown. Records without `skillName` (raw spawn_subagent calls) group under `"(adhoc)"`. */
|
|
3204
|
+
bySkill: Array<{
|
|
3205
|
+
skillName: string;
|
|
3206
|
+
count: number;
|
|
3207
|
+
costUsd: number;
|
|
3208
|
+
durationMs: number;
|
|
3209
|
+
}>;
|
|
3134
3210
|
}
|
|
3135
3211
|
/**
|
|
3136
3212
|
* Fold a flat record list into the dashboard shape — rolling windows
|
package/dist/index.js
CHANGED
|
@@ -1563,6 +1563,11 @@ function deleteSession(name) {
|
|
|
1563
1563
|
const path = sessionPath(name);
|
|
1564
1564
|
try {
|
|
1565
1565
|
unlinkSync(path);
|
|
1566
|
+
const sidecar = path.replace(/\.jsonl$/, ".pending.json");
|
|
1567
|
+
try {
|
|
1568
|
+
unlinkSync(sidecar);
|
|
1569
|
+
} catch {
|
|
1570
|
+
}
|
|
1566
1571
|
return true;
|
|
1567
1572
|
} catch {
|
|
1568
1573
|
return false;
|
|
@@ -1590,13 +1595,18 @@ function countLines(path) {
|
|
|
1590
1595
|
|
|
1591
1596
|
// src/telemetry.ts
|
|
1592
1597
|
var DEEPSEEK_PRICING = {
|
|
1593
|
-
"deepseek-
|
|
1594
|
-
"deepseek-
|
|
1598
|
+
"deepseek-v4-flash": { inputCacheHit: 0.028, inputCacheMiss: 0.139, output: 0.278 },
|
|
1599
|
+
"deepseek-v4-pro": { inputCacheHit: 0.139, inputCacheMiss: 1.667, output: 3.333 },
|
|
1600
|
+
// Compat aliases — priced as v4-flash per the deprecation notice.
|
|
1601
|
+
"deepseek-chat": { inputCacheHit: 0.028, inputCacheMiss: 0.139, output: 0.278 },
|
|
1602
|
+
"deepseek-reasoner": { inputCacheHit: 0.028, inputCacheMiss: 0.139, output: 0.278 }
|
|
1595
1603
|
};
|
|
1596
1604
|
var CLAUDE_SONNET_PRICING = { input: 3, output: 15 };
|
|
1597
1605
|
var DEEPSEEK_CONTEXT_TOKENS = {
|
|
1598
|
-
"deepseek-
|
|
1599
|
-
"deepseek-
|
|
1606
|
+
"deepseek-v4-flash": 1e6,
|
|
1607
|
+
"deepseek-v4-pro": 1e6,
|
|
1608
|
+
"deepseek-chat": 1e6,
|
|
1609
|
+
"deepseek-reasoner": 1e6
|
|
1600
1610
|
};
|
|
1601
1611
|
var DEFAULT_CONTEXT_TOKENS = 131072;
|
|
1602
1612
|
function costUsd(model, usage) {
|
|
@@ -1924,7 +1934,7 @@ var CacheFirstLoop = class {
|
|
|
1924
1934
|
content: `aborted at iter ${iter}/${this.maxToolIters} \u2014 stopped without producing a summary (press \u2191 + Enter or /retry to resume)`
|
|
1925
1935
|
};
|
|
1926
1936
|
const stoppedMsg = "[aborted by user (Esc) \u2014 no summary produced. Ask again or /retry when ready; prior tool output is still in the log.]";
|
|
1927
|
-
this.appendAndPersist(
|
|
1937
|
+
this.appendAndPersist(this.syntheticAssistantMessage(stoppedMsg));
|
|
1928
1938
|
yield {
|
|
1929
1939
|
turn: this._turn,
|
|
1930
1940
|
role: "assistant_final",
|
|
@@ -2060,6 +2070,7 @@ var CacheFirstLoop = class {
|
|
|
2060
2070
|
};
|
|
2061
2071
|
} else if (this.stream) {
|
|
2062
2072
|
const callBuf = /* @__PURE__ */ new Map();
|
|
2073
|
+
const readyIndices = /* @__PURE__ */ new Set();
|
|
2063
2074
|
for await (const chunk of this.client.stream({
|
|
2064
2075
|
model: this.model,
|
|
2065
2076
|
messages,
|
|
@@ -2095,13 +2106,18 @@ var CacheFirstLoop = class {
|
|
|
2095
2106
|
if (d.argumentsDelta)
|
|
2096
2107
|
cur.function.arguments = (cur.function.arguments ?? "") + d.argumentsDelta;
|
|
2097
2108
|
callBuf.set(d.index, cur);
|
|
2109
|
+
if (!readyIndices.has(d.index) && cur.function.name && looksLikeCompleteJson(cur.function.arguments ?? "")) {
|
|
2110
|
+
readyIndices.add(d.index);
|
|
2111
|
+
}
|
|
2098
2112
|
if (cur.function.name) {
|
|
2099
2113
|
yield {
|
|
2100
2114
|
turn: this._turn,
|
|
2101
2115
|
role: "tool_call_delta",
|
|
2102
2116
|
content: "",
|
|
2103
2117
|
toolName: cur.function.name,
|
|
2104
|
-
toolCallArgsChars: (cur.function.arguments ?? "").length
|
|
2118
|
+
toolCallArgsChars: (cur.function.arguments ?? "").length,
|
|
2119
|
+
toolCallIndex: d.index,
|
|
2120
|
+
toolCallReadyCount: readyIndices.size
|
|
2105
2121
|
};
|
|
2106
2122
|
}
|
|
2107
2123
|
}
|
|
@@ -2152,7 +2168,9 @@ var CacheFirstLoop = class {
|
|
|
2152
2168
|
reasoningContent || null,
|
|
2153
2169
|
assistantContent || null
|
|
2154
2170
|
);
|
|
2155
|
-
this.appendAndPersist(
|
|
2171
|
+
this.appendAndPersist(
|
|
2172
|
+
this.assistantMessage(assistantContent, repairedCalls, reasoningContent)
|
|
2173
|
+
);
|
|
2156
2174
|
yield {
|
|
2157
2175
|
turn: this._turn,
|
|
2158
2176
|
role: "assistant_final",
|
|
@@ -2314,7 +2332,7 @@ ${reason}`;
|
|
|
2314
2332
|
|
|
2315
2333
|
${summary}`;
|
|
2316
2334
|
const summaryStats = this.stats.record(this._turn, this.model, resp.usage ?? new Usage());
|
|
2317
|
-
this.appendAndPersist(
|
|
2335
|
+
this.appendAndPersist(this.assistantMessage(summary, [], resp.reasoningContent ?? void 0));
|
|
2318
2336
|
yield {
|
|
2319
2337
|
turn: this._turn,
|
|
2320
2338
|
role: "assistant_final",
|
|
@@ -2343,12 +2361,35 @@ ${summary}`;
|
|
|
2343
2361
|
}
|
|
2344
2362
|
return final;
|
|
2345
2363
|
}
|
|
2346
|
-
assistantMessage(content, toolCalls) {
|
|
2364
|
+
assistantMessage(content, toolCalls, reasoningContent) {
|
|
2347
2365
|
const msg = { role: "assistant", content };
|
|
2348
2366
|
if (toolCalls.length > 0) msg.tool_calls = toolCalls;
|
|
2367
|
+
if (reasoningContent && reasoningContent.length > 0) {
|
|
2368
|
+
msg.reasoning_content = reasoningContent;
|
|
2369
|
+
}
|
|
2370
|
+
return msg;
|
|
2371
|
+
}
|
|
2372
|
+
/**
|
|
2373
|
+
* Build a synthetic assistant message we insert into the log without
|
|
2374
|
+
* a real API round trip (abort notices, future system injections).
|
|
2375
|
+
* Reasoner models reject follow-up requests whose assistant history
|
|
2376
|
+
* is missing `reasoning_content`, so we stamp an empty-string
|
|
2377
|
+
* placeholder on reasoner sessions to satisfy the validator. V3
|
|
2378
|
+
* doesn't care — field stays absent there.
|
|
2379
|
+
*/
|
|
2380
|
+
syntheticAssistantMessage(content) {
|
|
2381
|
+
const msg = { role: "assistant", content };
|
|
2382
|
+
if (isThinkingModeModel(this.model)) {
|
|
2383
|
+
msg.reasoning_content = "";
|
|
2384
|
+
}
|
|
2349
2385
|
return msg;
|
|
2350
2386
|
}
|
|
2351
2387
|
};
|
|
2388
|
+
function isThinkingModeModel(model) {
|
|
2389
|
+
if (model.includes("reasoner")) return true;
|
|
2390
|
+
if (model === "deepseek-v4-flash" || model === "deepseek-v4-pro") return true;
|
|
2391
|
+
return false;
|
|
2392
|
+
}
|
|
2352
2393
|
function stripHallucinatedToolMarkup(s) {
|
|
2353
2394
|
let out = s;
|
|
2354
2395
|
out = out.replace(/<|DSML|function_calls>[\s\S]*?<\/?|DSML|function_calls>/g, "");
|
|
@@ -2364,6 +2405,15 @@ function safeParseToolArgs(raw) {
|
|
|
2364
2405
|
return raw;
|
|
2365
2406
|
}
|
|
2366
2407
|
}
|
|
2408
|
+
function looksLikeCompleteJson(s) {
|
|
2409
|
+
if (!s || !s.trim()) return false;
|
|
2410
|
+
try {
|
|
2411
|
+
JSON.parse(s);
|
|
2412
|
+
return true;
|
|
2413
|
+
} catch {
|
|
2414
|
+
return false;
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2367
2417
|
function* hookWarnings(outcomes, turn) {
|
|
2368
2418
|
for (const o of outcomes) {
|
|
2369
2419
|
if (o.decision === "pass") continue;
|
|
@@ -3961,11 +4011,14 @@ async function spawnSubagent(opts) {
|
|
|
3961
4011
|
const maxToolIters = opts.maxToolIters ?? DEFAULT_MAX_ITERS;
|
|
3962
4012
|
const maxResultChars = opts.maxResultChars ?? DEFAULT_MAX_RESULT_CHARS2;
|
|
3963
4013
|
const sink = opts.sink;
|
|
4014
|
+
const skillName = opts.skillName;
|
|
3964
4015
|
const startedAt = Date.now();
|
|
3965
4016
|
const taskPreview = opts.task.length > 30 ? `${opts.task.slice(0, 30)}\u2026` : opts.task;
|
|
3966
4017
|
sink?.current?.({
|
|
3967
4018
|
kind: "start",
|
|
3968
4019
|
task: taskPreview,
|
|
4020
|
+
skillName,
|
|
4021
|
+
model,
|
|
3969
4022
|
iter: 0,
|
|
3970
4023
|
elapsedMs: 0
|
|
3971
4024
|
});
|
|
@@ -3995,6 +4048,8 @@ async function spawnSubagent(opts) {
|
|
|
3995
4048
|
sink?.current?.({
|
|
3996
4049
|
kind: "progress",
|
|
3997
4050
|
task: taskPreview,
|
|
4051
|
+
skillName,
|
|
4052
|
+
model,
|
|
3998
4053
|
iter: toolIter,
|
|
3999
4054
|
elapsedMs: Date.now() - startedAt
|
|
4000
4055
|
});
|
|
@@ -4017,17 +4072,22 @@ async function spawnSubagent(opts) {
|
|
|
4017
4072
|
const elapsedMs = Date.now() - startedAt;
|
|
4018
4073
|
const turns = childLoop.stats.turns.length;
|
|
4019
4074
|
const costUsd2 = childLoop.stats.totalCost;
|
|
4075
|
+
const usage = aggregateChildUsage(childLoop);
|
|
4020
4076
|
const truncated = final.length > maxResultChars ? `${final.slice(0, maxResultChars)}
|
|
4021
4077
|
|
|
4022
4078
|
[\u2026truncated ${final.length - maxResultChars} chars; ask the subagent for a tighter summary if you need more.]` : final;
|
|
4023
4079
|
sink?.current?.({
|
|
4024
4080
|
kind: "end",
|
|
4025
4081
|
task: taskPreview,
|
|
4082
|
+
skillName,
|
|
4083
|
+
model,
|
|
4026
4084
|
iter: toolIter,
|
|
4027
4085
|
elapsedMs,
|
|
4028
4086
|
summary: errorMessage ? void 0 : truncated.slice(0, 120),
|
|
4029
4087
|
error: errorMessage,
|
|
4030
|
-
turns
|
|
4088
|
+
turns,
|
|
4089
|
+
costUsd: costUsd2,
|
|
4090
|
+
usage
|
|
4031
4091
|
});
|
|
4032
4092
|
return {
|
|
4033
4093
|
success: !errorMessage,
|
|
@@ -4036,9 +4096,23 @@ async function spawnSubagent(opts) {
|
|
|
4036
4096
|
turns,
|
|
4037
4097
|
toolIters: toolIter,
|
|
4038
4098
|
elapsedMs,
|
|
4039
|
-
costUsd: costUsd2
|
|
4099
|
+
costUsd: costUsd2,
|
|
4100
|
+
model,
|
|
4101
|
+
skillName,
|
|
4102
|
+
usage
|
|
4040
4103
|
};
|
|
4041
4104
|
}
|
|
4105
|
+
function aggregateChildUsage(loop) {
|
|
4106
|
+
const agg = new Usage();
|
|
4107
|
+
for (const t of loop.stats.turns) {
|
|
4108
|
+
agg.promptTokens += t.usage.promptTokens;
|
|
4109
|
+
agg.completionTokens += t.usage.completionTokens;
|
|
4110
|
+
agg.totalTokens += t.usage.totalTokens;
|
|
4111
|
+
agg.promptCacheHitTokens += t.usage.promptCacheHitTokens;
|
|
4112
|
+
agg.promptCacheMissTokens += t.usage.promptCacheMissTokens;
|
|
4113
|
+
}
|
|
4114
|
+
return agg;
|
|
4115
|
+
}
|
|
4042
4116
|
function formatSubagentResult(r) {
|
|
4043
4117
|
if (!r.success) {
|
|
4044
4118
|
return JSON.stringify({
|
|
@@ -4081,8 +4155,8 @@ function registerSubagentTool(parentRegistry, opts) {
|
|
|
4081
4155
|
},
|
|
4082
4156
|
model: {
|
|
4083
4157
|
type: "string",
|
|
4084
|
-
enum: ["deepseek-chat", "deepseek-reasoner"],
|
|
4085
|
-
description: "Which DeepSeek model the subagent runs on. 'deepseek-
|
|
4158
|
+
enum: ["deepseek-v4-flash", "deepseek-v4-pro", "deepseek-chat", "deepseek-reasoner"],
|
|
4159
|
+
description: "Which DeepSeek model the subagent runs on. 'deepseek-v4-flash' (default; thinking mode) is fast and cheap and is what the legacy 'deepseek-chat' / 'deepseek-reasoner' aliases route to today. Use 'deepseek-v4-pro' only when the subtask needs the strongest model \u2014 roughly 12\xD7 the input cost and 12\xD7 the output cost vs flash."
|
|
4086
4160
|
}
|
|
4087
4161
|
},
|
|
4088
4162
|
required: ["task"]
|
|
@@ -6245,6 +6319,8 @@ function appendUsage(input) {
|
|
|
6245
6319
|
costUsd: costUsd(input.model, input.usage),
|
|
6246
6320
|
claudeEquivUsd: claudeEquivalentCost(input.usage)
|
|
6247
6321
|
};
|
|
6322
|
+
if (input.kind === "subagent") record.kind = "subagent";
|
|
6323
|
+
if (input.subagent) record.subagent = input.subagent;
|
|
6248
6324
|
const path = input.path ?? defaultUsageLogPath();
|
|
6249
6325
|
try {
|
|
6250
6326
|
mkdirSync6(dirname7(path), { recursive: true });
|
|
@@ -6318,6 +6394,10 @@ function aggregateUsage(records, opts = {}) {
|
|
|
6318
6394
|
const sessionCounts = /* @__PURE__ */ new Map();
|
|
6319
6395
|
let firstSeen = null;
|
|
6320
6396
|
let lastSeen = null;
|
|
6397
|
+
const skillCounts = /* @__PURE__ */ new Map();
|
|
6398
|
+
let subagentTotal = 0;
|
|
6399
|
+
let subagentCost = 0;
|
|
6400
|
+
let subagentDuration = 0;
|
|
6321
6401
|
for (const r of records) {
|
|
6322
6402
|
addToBucket(all, r);
|
|
6323
6403
|
if (r.ts >= today.since) addToBucket(today, r);
|
|
@@ -6328,15 +6408,34 @@ function aggregateUsage(records, opts = {}) {
|
|
|
6328
6408
|
sessionCounts.set(sessKey, (sessionCounts.get(sessKey) ?? 0) + 1);
|
|
6329
6409
|
if (firstSeen === null || r.ts < firstSeen) firstSeen = r.ts;
|
|
6330
6410
|
if (lastSeen === null || r.ts > lastSeen) lastSeen = r.ts;
|
|
6411
|
+
if (r.kind === "subagent") {
|
|
6412
|
+
subagentTotal += 1;
|
|
6413
|
+
subagentCost += r.costUsd;
|
|
6414
|
+
const dur = r.subagent?.durationMs ?? 0;
|
|
6415
|
+
subagentDuration += dur;
|
|
6416
|
+
const key = r.subagent?.skillName?.trim() || "(adhoc)";
|
|
6417
|
+
const prev = skillCounts.get(key) ?? { count: 0, costUsd: 0, durationMs: 0 };
|
|
6418
|
+
prev.count += 1;
|
|
6419
|
+
prev.costUsd += r.costUsd;
|
|
6420
|
+
prev.durationMs += dur;
|
|
6421
|
+
skillCounts.set(key, prev);
|
|
6422
|
+
}
|
|
6331
6423
|
}
|
|
6332
6424
|
const byModel = Array.from(modelCounts.entries()).map(([model, turns]) => ({ model, turns })).sort((a, b) => b.turns - a.turns);
|
|
6333
6425
|
const bySession = Array.from(sessionCounts.entries()).map(([session, turns]) => ({ session, turns })).sort((a, b) => b.turns - a.turns);
|
|
6426
|
+
const subagents = subagentTotal > 0 ? {
|
|
6427
|
+
total: subagentTotal,
|
|
6428
|
+
costUsd: subagentCost,
|
|
6429
|
+
totalDurationMs: subagentDuration,
|
|
6430
|
+
bySkill: Array.from(skillCounts.entries()).map(([skillName, v]) => ({ skillName, ...v })).sort((a, b) => b.count - a.count)
|
|
6431
|
+
} : void 0;
|
|
6334
6432
|
return {
|
|
6335
6433
|
buckets: [today, week, month, all],
|
|
6336
6434
|
byModel,
|
|
6337
6435
|
bySession,
|
|
6338
6436
|
firstSeen,
|
|
6339
|
-
lastSeen
|
|
6437
|
+
lastSeen,
|
|
6438
|
+
subagents
|
|
6340
6439
|
};
|
|
6341
6440
|
}
|
|
6342
6441
|
function formatLogSize(path = defaultUsageLogPath()) {
|