mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-21 14:07:20 +08:00
fix(cli): count subagent stats
This commit is contained in:
@@ -1,8 +1,7 @@
|
||||
// Verifies `kilo stats` does not double-count subagent cost. The task tool
|
||||
// propagates each child session's total cost up to the parent's tool-wrapper
|
||||
// assistant message. If stats summed every session indiscriminately, that
|
||||
// propagated cost would appear in both the parent wrapper and the child's
|
||||
// own messages. The aggregator is now filtered to root sessions (#6321).
|
||||
// Verifies `kilo stats` does not double-count subagent cost while still
|
||||
// including child-session messages, tokens, tools, and model usage. The task
|
||||
// tool propagates each child session's total cost up to the parent's
|
||||
// tool-wrapper assistant message (#6321).
|
||||
|
||||
import { afterEach, describe, expect, test } from "bun:test"
|
||||
import { aggregateSessionStats } from "../../src/cli/cmd/stats"
|
||||
@@ -10,7 +9,7 @@ import { MessageV2 } from "../../src/session/message-v2"
|
||||
import { Instance } from "../../src/project/instance"
|
||||
import { ProviderID, ModelID } from "../../src/provider/schema"
|
||||
import { Session } from "../../src/session"
|
||||
import { MessageID } from "../../src/session/schema"
|
||||
import { MessageID, PartID } from "../../src/session/schema"
|
||||
import { Log } from "../../src/util"
|
||||
import { tmpdir } from "../fixture/fixture"
|
||||
|
||||
@@ -42,8 +41,40 @@ function assistant(sessionID: string, parentID: string, cost: number): MessageV2
|
||||
}
|
||||
}
|
||||
|
||||
async function step(sessionID: string, messageID: string, cost: number) {
|
||||
await Session.updatePart({
|
||||
id: PartID.ascending(),
|
||||
messageID: messageID as any,
|
||||
sessionID: sessionID as any,
|
||||
type: "step-finish",
|
||||
reason: "stop",
|
||||
cost,
|
||||
tokens: { total: 15, input: 10, output: 5, reasoning: 0, cache: { read: 0, write: 0 } },
|
||||
})
|
||||
}
|
||||
|
||||
async function tool(sessionID: string, messageID: string) {
|
||||
const time = Date.now()
|
||||
await Session.updatePart({
|
||||
id: PartID.ascending(),
|
||||
messageID: messageID as any,
|
||||
sessionID: sessionID as any,
|
||||
type: "tool",
|
||||
callID: "call_1",
|
||||
tool: "bash",
|
||||
state: {
|
||||
status: "completed",
|
||||
input: {},
|
||||
output: "ok",
|
||||
title: "bash",
|
||||
metadata: {},
|
||||
time: { start: time, end: time },
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
describe("stats subagent cost", () => {
|
||||
test("totalCost excludes children whose cost was propagated into the parent", async () => {
|
||||
test("counts child usage without double-counting propagated cost", async () => {
|
||||
await using tmp = await tmpdir({ git: true })
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
@@ -51,7 +82,6 @@ describe("stats subagent cost", () => {
|
||||
const parent = await Session.create({ title: "root" })
|
||||
const child = await Session.create({ parentID: parent.id, title: "subagent" })
|
||||
|
||||
// The parent's tool-wrapper assistant message shows the propagated total (own LLM + child).
|
||||
const userMsg = await Session.updateMessage({
|
||||
id: MessageID.ascending(),
|
||||
role: "user",
|
||||
@@ -60,8 +90,9 @@ describe("stats subagent cost", () => {
|
||||
model: ref,
|
||||
time: { created: Date.now() },
|
||||
} as any)
|
||||
await Session.updateMessage(assistant(parent.id, userMsg.id, 1.5))
|
||||
// The child session independently records its own LLM cost.
|
||||
const parentMsg = await Session.updateMessage(assistant(parent.id, userMsg.id, 1.5))
|
||||
await step(parent.id, parentMsg.id, 1)
|
||||
|
||||
const childUser = await Session.updateMessage({
|
||||
id: MessageID.ascending(),
|
||||
role: "user",
|
||||
@@ -70,14 +101,22 @@ describe("stats subagent cost", () => {
|
||||
model: ref,
|
||||
time: { created: Date.now() },
|
||||
} as any)
|
||||
await Session.updateMessage(assistant(child.id, childUser.id, 0.5))
|
||||
const childMsg = await Session.updateMessage(assistant(child.id, childUser.id, 0.5))
|
||||
await step(child.id, childMsg.id, 0.5)
|
||||
await tool(child.id, childMsg.id)
|
||||
|
||||
const stats = await aggregateSessionStats()
|
||||
// Without the fix, totalCost would be 1.5 + 0.5 = 2.0 (child counted twice).
|
||||
// With the fix, only the parent (root) session contributes: 1.5 (which already
|
||||
// includes the 0.5 propagated from the child).
|
||||
const model = stats.modelUsage["test/test-model"]!
|
||||
expect(stats.totalCost).toBeCloseTo(1.5, 6)
|
||||
expect(stats.totalSessions).toBe(1)
|
||||
expect(stats.totalSessions).toBe(2)
|
||||
expect(stats.totalMessages).toBe(4)
|
||||
expect(stats.totalTokens.input).toBe(20)
|
||||
expect(stats.totalTokens.output).toBe(10)
|
||||
expect(stats.toolUsage.bash).toBe(1)
|
||||
expect(model.messages).toBe(2)
|
||||
expect(model.tokens.input).toBe(20)
|
||||
expect(model.tokens.output).toBe(10)
|
||||
expect(model.cost).toBeCloseTo(1.5, 6)
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user