





















@@ -149,14 +149,14 @@ describe("session cost usage", () => {
149149});
150150});
151151152-it("counts token usage for an unpriced (all-zero cost) model as missing, not a confident $0", async () => {
152+it("counts token usage for an unpriced (unconfigured all-zero) model as missing, not a confident $0", async () => {
153153const root = await makeSessionCostRoot("cost-unknown-pricing");
154154const sessionsDir = path.join(root, "agents", "main", "sessions");
155155await fs.mkdir(sessionsDir, { recursive: true });
156156157157// A real assistant turn that burned tokens. The transport recorded cost.total: 0,
158-// derived from the model's all-zero catalog pricing — exactly what codex/gpt-5.x
159-// models produce, since the Codex backend exposes no per-token price.
158+// derived from an all-zero catalog price — exactly what codex/gpt-5.x models produce,
159+// since the Codex backend exposes no per-token price and the operator never set one.
160160const entry = {
161161type: "message",
162162timestamp: new Date().toISOString(),
@@ -181,7 +181,52 @@ describe("session cost usage", () => {
181181"utf-8",
182182);
183183184-// The model resolves to an all-zero cost config, i.e. its pricing is unknown.
184+// No operator-configured pricing for this model, so its all-zero cost is unknown,
185+// not an intentional "free" price.
186+clearGatewayModelPricingCacheState();
187+await withStateDir(root, async () => {
188+const summary = await loadCostUsageSummary({ days: 30 });
189+expect(summary.totals.totalTokens).toBe(23287);
190+expect(summary.totals.totalCost).toBe(0);
191+// Unknown pricing must be surfaced as missing rather than reported as a
192+// confident $0 that would blind budget/spike monitoring to real spend.
193+expect(summary.totals.missingCostEntries).toBe(1);
194+});
195+});
196+197+it("preserves an operator-configured zero-cost model as a complete $0, not missing", async () => {
198+const root = await makeSessionCostRoot("cost-intentional-free");
199+const sessionsDir = path.join(root, "agents", "main", "sessions");
200+await fs.mkdir(sessionsDir, { recursive: true });
201+202+// Same shape of turn, but here the operator deliberately priced the model at 0
203+// (e.g. a local/self-hosted free model). That intentional $0 must be respected.
204+const entry = {
205+type: "message",
206+timestamp: new Date().toISOString(),
207+message: {
208+role: "assistant",
209+provider: "openai",
210+model: "gpt-5.5",
211+usage: {
212+input: 881,
213+output: 6,
214+cacheRead: 22400,
215+cacheWrite: 0,
216+totalTokens: 23287,
217+cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
218+},
219+},
220+};
221+222+await fs.writeFile(
223+path.join(sessionsDir, "sess-1.jsonl"),
224+transcriptText("sess-1", entry),
225+"utf-8",
226+);
227+228+// The operator explicitly configured this model's price as 0 — an intentional
229+// "free" price that must be preserved as complete $0 cost data.
185230const config = {
186231models: {
187232providers: {
@@ -197,13 +242,13 @@ describe("session cost usage", () => {
197242},
198243} as unknown as OpenClawConfig;
199244245+clearGatewayModelPricingCacheState();
200246await withStateDir(root, async () => {
201247const summary = await loadCostUsageSummary({ days: 30, config });
202248expect(summary.totals.totalTokens).toBe(23287);
203249expect(summary.totals.totalCost).toBe(0);
204-// Unknown pricing must be surfaced as missing rather than reported as a
205-// confident $0 that would blind budget/spike monitoring to real spend.
206-expect(summary.totals.missingCostEntries).toBe(1);
250+// Operator-configured $0 is intentional and complete — not a missing entry.
251+expect(summary.totals.missingCostEntries).toBe(0);
207252});
208253});
209254此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。