Merge pull request #31 from IfkumRfnl/fix/cache-accounting
fix(stream): correct cached input accounting
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
# Changelog
|
||||
|
||||
## 0.4.4 - 2026-08-03
|
||||
|
||||
- Fix cached input tokens being counted twice.
|
||||
|
||||
## 0.4.3 - 2026-08-02
|
||||
|
||||
- Allow pi to start when model discovery is unavailable. The provider now caches the last successfully fetched model catalog so previously discovered Command Code models remain selectable offline; a first offline start without a cache keeps Command Code unavailable until `/reload` succeeds.
|
||||
|
||||
+7
-3
@@ -388,10 +388,14 @@ export function createStreamCommandCode(deps: CoreDependencies) {
|
||||
const usage = commandCodeUsage(event)
|
||||
if (usage) {
|
||||
const details = commandCodeInputTokenDetails(usage)
|
||||
output.usage.input = numberValue(usage.inputTokens) ?? 0
|
||||
const totalInput = numberValue(usage.inputTokens) ?? 0
|
||||
const input = numberValue(details?.noCacheTokens)
|
||||
const cacheRead = numberValue(details?.cacheReadTokens) ?? 0
|
||||
const cacheWrite = numberValue(details?.cacheWriteTokens) ?? 0
|
||||
output.usage.input = input ?? Math.max(0, totalInput - cacheRead - cacheWrite)
|
||||
output.usage.output = numberValue(usage.outputTokens) ?? 0
|
||||
output.usage.cacheRead = numberValue(details?.cacheReadTokens) ?? 0
|
||||
output.usage.cacheWrite = numberValue(details?.cacheWriteTokens) ?? 0
|
||||
output.usage.cacheRead = cacheRead
|
||||
output.usage.cacheWrite = cacheWrite
|
||||
output.usage.totalTokens =
|
||||
output.usage.input +
|
||||
output.usage.output +
|
||||
|
||||
+71
-4
@@ -102,9 +102,9 @@ describe("streamCommandCode — successful streams", () => {
|
||||
type: "finish",
|
||||
finishReason: "stop",
|
||||
totalUsage: {
|
||||
inputTokens: 5,
|
||||
outputTokens: 2,
|
||||
inputTokenDetails: { cacheReadTokens: 3, cacheWriteTokens: 1 },
|
||||
inputTokens: 3124,
|
||||
outputTokens: 15,
|
||||
inputTokenDetails: { noCacheTokens: 52, cacheReadTokens: 3072 },
|
||||
},
|
||||
}),
|
||||
],
|
||||
@@ -134,10 +134,77 @@ describe("streamCommandCode — successful streams", () => {
|
||||
done.message.content[0]?.type === "text" ? done.message.content[0].text : "",
|
||||
"Hello",
|
||||
)
|
||||
assert.equal(done.message.usage.totalTokens, 11)
|
||||
assert.equal(done.message.usage.input, 52)
|
||||
assert.equal(done.message.usage.cacheRead, 3072)
|
||||
assert.equal(done.message.usage.cacheWrite, 0)
|
||||
assert.equal(done.message.usage.totalTokens, 3139)
|
||||
assert.equal(calculatedUsages.length, 1)
|
||||
})
|
||||
|
||||
it("derives uncached input when noCacheTokens is missing", async () => {
|
||||
server.mockResponse({
|
||||
type: "success",
|
||||
events: [
|
||||
JSON.stringify({
|
||||
type: "finish",
|
||||
finishReason: "stop",
|
||||
totalUsage: {
|
||||
inputTokens: 100,
|
||||
outputTokens: 10,
|
||||
inputTokenDetails: { cacheReadTokens: 75 },
|
||||
},
|
||||
}),
|
||||
],
|
||||
})
|
||||
const { streamCommandCode } = createTestDeps({ apiBase: server.baseUrl() })
|
||||
|
||||
const events = await collectEvents(
|
||||
streamCommandCode(makeModel(), makeContext(), { apiKey: "mock-key" }),
|
||||
)
|
||||
|
||||
const done = events.at(-1)
|
||||
assert.equal(done?.type, "done")
|
||||
if (done?.type !== "done") throw new Error("expected done")
|
||||
assert.equal(done.message.usage.input, 25)
|
||||
assert.equal(done.message.usage.cacheRead, 75)
|
||||
assert.equal(done.message.usage.cacheWrite, 0)
|
||||
assert.equal(done.message.usage.totalTokens, 110)
|
||||
})
|
||||
|
||||
it("accounts for cache writes separately from uncached input", async () => {
|
||||
server.mockResponse({
|
||||
type: "success",
|
||||
events: [
|
||||
JSON.stringify({
|
||||
type: "finish",
|
||||
finishReason: "stop",
|
||||
totalUsage: {
|
||||
inputTokens: 100,
|
||||
outputTokens: 10,
|
||||
inputTokenDetails: {
|
||||
noCacheTokens: 20,
|
||||
cacheReadTokens: 70,
|
||||
cacheWriteTokens: 10,
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
})
|
||||
const { streamCommandCode } = createTestDeps({ apiBase: server.baseUrl() })
|
||||
|
||||
const events = await collectEvents(
|
||||
streamCommandCode(makeModel(), makeContext(), { apiKey: "mock-key" }),
|
||||
)
|
||||
|
||||
const done = events.at(-1)
|
||||
assert.equal(done?.type, "done")
|
||||
if (done?.type !== "done") throw new Error("expected done")
|
||||
assert.equal(done.message.usage.input, 20)
|
||||
assert.equal(done.message.usage.cacheRead, 70)
|
||||
assert.equal(done.message.usage.cacheWrite, 10)
|
||||
assert.equal(done.message.usage.totalTokens, 110)
|
||||
})
|
||||
|
||||
it("ends on finish without waiting for an open upstream connection", async () => {
|
||||
server.mockResponse({
|
||||
type: "success",
|
||||
|
||||
Reference in New Issue
Block a user