From 0d4f8d126f3cb2dedde4f7d904243b0ba42ce42a Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Sun, 10 May 2026 19:15:46 -0400 Subject: [PATCH] refactor(llm): drop Usage.totalInput / totalOutput helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The additive contract delivers value at the mapper boundary — every field is non-overlapping and non-negative, so any caller summing arbitrary subsets is correct by construction. Two-line helpers that just sum three or two known fields add API surface without paying for themselves, and there are no in-tree consumers today. If v2 wants them at integration time, the right place is a getter on the `Schema.Class` (matching the `LLMResponse.text` / `reasoning` / `toolCalls` pattern in the same file), not a static namespace helper. --- packages/llm/src/schema/events.ts | 9 --------- packages/llm/test/schema.test.ts | 18 +++--------------- 2 files changed, 3 insertions(+), 24 deletions(-) diff --git a/packages/llm/src/schema/events.ts b/packages/llm/src/schema/events.ts index e8a2a18892..187291e685 100644 --- a/packages/llm/src/schema/events.ts +++ b/packages/llm/src/schema/events.ts @@ -45,15 +45,6 @@ export class Usage extends Schema.Class("LLM.Usage")({ native: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)), }) {} -export namespace Usage { - /** Sum of every input-side category. Monotonic under the additive contract. */ - export const totalInput = (usage: Usage) => - (usage.inputTokens ?? 0) + (usage.cacheReadInputTokens ?? 0) + (usage.cacheWriteInputTokens ?? 0) - - /** Sum of every output-side category. Monotonic under the additive contract. */ - export const totalOutput = (usage: Usage) => (usage.outputTokens ?? 0) + (usage.reasoningTokens ?? 0) -} - export const RequestStart = Schema.Struct({ type: Schema.tag("request-start"), id: ResponseID, diff --git a/packages/llm/test/schema.test.ts b/packages/llm/test/schema.test.ts index 7ef3247f8b..a64b0ff71c 100644 --- a/packages/llm/test/schema.test.ts +++ b/packages/llm/test/schema.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "bun:test" import { Schema } from "effect" -import { ContentPart, LLMEvent, LLMRequest, ModelID, ModelLimits, ModelRef, ProviderID, Usage } from "../src/schema" +import { ContentPart, LLMEvent, LLMRequest, ModelID, ModelLimits, ModelRef, ProviderID } from "../src/schema" import { ProviderShared } from "../src/protocols/shared" const model = new ModelRef({ @@ -53,24 +53,12 @@ describe("llm schema", () => { describe("LLM.Usage additive contract", () => { test("subtractTokens clamps non-sensical breakdowns to zero", () => { // Defense against a provider reporting cached_tokens > prompt_tokens or - // reasoning_tokens > completion_tokens. The clamp prevents the negative - // values that triggered opencode#26620 from ever entering the pipeline. + // reasoning_tokens > completion_tokens — the negative would otherwise + // round-trip through the pipeline and crash strict downstream schemas. expect(ProviderShared.subtractTokens(5, 3)).toBe(2) expect(ProviderShared.subtractTokens(5, 10)).toBe(0) expect(ProviderShared.subtractTokens(5, undefined)).toBe(5) expect(ProviderShared.subtractTokens(undefined, 3)).toBeUndefined() expect(ProviderShared.subtractTokens(undefined, undefined)).toBeUndefined() }) - - test("totalInput sums every input-side category", () => { - expect(Usage.totalInput(new Usage({ inputTokens: 10, cacheReadInputTokens: 3, cacheWriteInputTokens: 2 }))).toBe(15) - expect(Usage.totalInput(new Usage({ inputTokens: 10 }))).toBe(10) - expect(Usage.totalInput(new Usage({}))).toBe(0) - }) - - test("totalOutput sums every output-side category", () => { - expect(Usage.totalOutput(new Usage({ outputTokens: 7, reasoningTokens: 4 }))).toBe(11) - expect(Usage.totalOutput(new Usage({ outputTokens: 7 }))).toBe(7) - expect(Usage.totalOutput(new Usage({}))).toBe(0) - }) })