From 4410cbb3f57fc48c17fe20d66f2920d5454db327 Mon Sep 17 00:00:00 2001 From: Amit Bezalel Date: Wed, 6 May 2026 12:23:04 +0300 Subject: [PATCH 1/2] Fix Claude context usage normalization for UI meter Treat Claude accumulated totals as processed-token telemetry unless a context-accurate used-token field is present, so context percentage indicators no longer pin to 100% on result-only snapshots. Co-authored-by: Cursor --- .../src/provider/Layers/ClaudeAdapter.test.ts | 9 ++--- .../src/provider/Layers/ClaudeAdapter.ts | 37 ++++++++++++++----- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/apps/server/src/provider/Layers/ClaudeAdapter.test.ts b/apps/server/src/provider/Layers/ClaudeAdapter.test.ts index 4c71a016f32..f33e299d1b6 100644 --- a/apps/server/src/provider/Layers/ClaudeAdapter.test.ts +++ b/apps/server/src/provider/Layers/ClaudeAdapter.test.ts @@ -1637,7 +1637,6 @@ describe("ClaudeAdapterLive", () => { lastUsedTokens: 24542, inputTokens: 23863, outputTokens: 679, - maxTokens: 200000, }, }); } @@ -1647,7 +1646,7 @@ describe("ClaudeAdapterLive", () => { ); }); - it.effect("clamps oversized Claude usage to the reported context window", () => { + it.effect("keeps oversized Claude result totals when no context-accurate usage exists", () => { const harness = makeHarness(); return Effect.gen(function* () { const adapter = yield* ClaudeAdapter; @@ -1697,10 +1696,8 @@ describe("ClaudeAdapterLive", () => { if (usageEvent?.type === "thread.token-usage.updated") { assert.deepEqual(usageEvent.payload, { usage: { - usedTokens: 200000, - lastUsedTokens: 200000, - totalProcessedTokens: 535000, - maxTokens: 200000, + usedTokens: 535000, + lastUsedTokens: 535000, }, }); } diff --git a/apps/server/src/provider/Layers/ClaudeAdapter.ts b/apps/server/src/provider/Layers/ClaudeAdapter.ts index 556504d6cf4..43fd986157e 100644 --- a/apps/server/src/provider/Layers/ClaudeAdapter.ts +++ b/apps/server/src/provider/Layers/ClaudeAdapter.ts @@ -337,7 +337,23 @@ function normalizeClaudeTokenUsage( (typeof usage.total_tokens === "number" && Number.isFinite(usage.total_tokens) ? usage.total_tokens : undefined) ?? (derivedTotalProcessedTokens > 0 ? derivedTotalProcessedTokens : undefined); - if (totalProcessedTokens === undefined || totalProcessedTokens <= 0) { + const directUsedTokens = + (typeof usage.used_tokens === "number" && Number.isFinite(usage.used_tokens) + ? usage.used_tokens + : undefined) ?? + (typeof usage.usedTokens === "number" && Number.isFinite(usage.usedTokens) + ? usage.usedTokens + : undefined) ?? + (typeof usage.last_used_tokens === "number" && Number.isFinite(usage.last_used_tokens) + ? usage.last_used_tokens + : undefined) ?? + (typeof usage.lastUsedTokens === "number" && Number.isFinite(usage.lastUsedTokens) + ? usage.lastUsedTokens + : undefined); + if ( + (totalProcessedTokens === undefined || totalProcessedTokens <= 0) && + (directUsedTokens === undefined || directUsedTokens <= 0) + ) { return undefined; } @@ -345,16 +361,22 @@ function normalizeClaudeTokenUsage( typeof contextWindow === "number" && Number.isFinite(contextWindow) && contextWindow > 0 ? contextWindow : undefined; - const usedTokens = - maxTokens !== undefined ? Math.min(totalProcessedTokens, maxTokens) : totalProcessedTokens; + const usedTokens = (() => { + if (directUsedTokens !== undefined && directUsedTokens > 0) { + return maxTokens !== undefined ? Math.min(directUsedTokens, maxTokens) : directUsedTokens; + } + return totalProcessedTokens ?? 0; + })(); return { usedTokens, lastUsedTokens: usedTokens, - ...(totalProcessedTokens > usedTokens ? { totalProcessedTokens } : {}), + ...(totalProcessedTokens !== undefined && totalProcessedTokens > usedTokens + ? { totalProcessedTokens } + : {}), ...(inputTokens > 0 ? { inputTokens } : {}), ...(outputTokens > 0 ? { outputTokens } : {}), - ...(maxTokens !== undefined ? { maxTokens } : {}), + ...(maxTokens !== undefined && directUsedTokens !== undefined ? { maxTokens } : {}), ...(typeof usage.tool_uses === "number" && Number.isFinite(usage.tool_uses) ? { toolUses: usage.tool_uses } : {}), @@ -1417,10 +1439,7 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* ( // This does NOT represent the current context window size. // Instead, use the last known context-window-accurate usage from task_progress // events and treat the accumulated total as totalProcessedTokens. - const accumulatedSnapshot = normalizeClaudeTokenUsage( - result?.usage, - resultContextWindow ?? context.lastKnownContextWindow, - ); + const accumulatedSnapshot = normalizeClaudeTokenUsage(result?.usage); const accumulatedTotalProcessedTokens = accumulatedSnapshot?.totalProcessedTokens ?? accumulatedSnapshot?.usedTokens; const lastGoodUsage = context.lastKnownTokenUsage; From ee63cc279f4572ce783e7fa0dc8a9ff4e62031e9 Mon Sep 17 00:00:00 2001 From: Amit Bezalel Date: Wed, 6 May 2026 12:39:37 +0300 Subject: [PATCH 2/2] fixed Cursor Bugbot comment --- apps/server/src/provider/Layers/ClaudeAdapter.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/server/src/provider/Layers/ClaudeAdapter.ts b/apps/server/src/provider/Layers/ClaudeAdapter.ts index 43fd986157e..196d5d00e15 100644 --- a/apps/server/src/provider/Layers/ClaudeAdapter.ts +++ b/apps/server/src/provider/Layers/ClaudeAdapter.ts @@ -361,8 +361,9 @@ function normalizeClaudeTokenUsage( typeof contextWindow === "number" && Number.isFinite(contextWindow) && contextWindow > 0 ? contextWindow : undefined; + const hasDirectContextUsedTokens = directUsedTokens !== undefined && directUsedTokens > 0; const usedTokens = (() => { - if (directUsedTokens !== undefined && directUsedTokens > 0) { + if (hasDirectContextUsedTokens) { return maxTokens !== undefined ? Math.min(directUsedTokens, maxTokens) : directUsedTokens; } return totalProcessedTokens ?? 0; @@ -376,7 +377,7 @@ function normalizeClaudeTokenUsage( : {}), ...(inputTokens > 0 ? { inputTokens } : {}), ...(outputTokens > 0 ? { outputTokens } : {}), - ...(maxTokens !== undefined && directUsedTokens !== undefined ? { maxTokens } : {}), + ...(maxTokens !== undefined && hasDirectContextUsedTokens ? { maxTokens } : {}), ...(typeof usage.tool_uses === "number" && Number.isFinite(usage.tool_uses) ? { toolUses: usage.tool_uses } : {}),