From 8aaab77876b2840eee02109bc3fdb3b28e2c4b64 Mon Sep 17 00:00:00 2001 From: Gordon Lee Date: Sun, 17 May 2026 23:24:25 +0700 Subject: [PATCH 1/2] fix(app): scale MAX_CONTEXT_SIZE for 1M-context models MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Context-remaining indicator was hardcoded to 190K, so any session on a 1M-context Anthropic model (e.g. claude-opus-4-7[1m]) clamped to 0% within minutes and the toggle appeared broken. Plumb the assistant `model` field from the raw Claude API event through NormalizedMessage → reducer latestUsage → Session.latestUsage → AgentInput.usageData, then pick the limit dynamically: 950K for model ids matching `[1m]`, 190K otherwise. Closes #910 --- .../sources/-session/SessionView.tsx | 1 + .../sources/components/AgentInput.tsx | 21 +++++++++++++++---- .../happy-app/sources/sync/reducer/reducer.ts | 10 ++++++--- .../happy-app/sources/sync/storageTypes.ts | 1 + packages/happy-app/sources/sync/typesRaw.ts | 4 +++- 5 files changed, 29 insertions(+), 8 deletions(-) diff --git a/packages/happy-app/sources/-session/SessionView.tsx b/packages/happy-app/sources/-session/SessionView.tsx index ee39874f2a..8d9653a0be 100644 --- a/packages/happy-app/sources/-session/SessionView.tsx +++ b/packages/happy-app/sources/-session/SessionView.tsx @@ -571,6 +571,7 @@ function SessionViewLoaded({ sessionId, session }: { sessionId: string, session: cacheCreation: source.cacheCreation, cacheRead: source.cacheRead, contextSize: source.contextSize, + model: source.model, }; }, [sessionUsage, session.latestUsage]); diff --git a/packages/happy-app/sources/components/AgentInput.tsx b/packages/happy-app/sources/components/AgentInput.tsx index f5f4c35a37..ba789e17b9 100644 --- a/packages/happy-app/sources/components/AgentInput.tsx +++ b/packages/happy-app/sources/components/AgentInput.tsx @@ -72,6 +72,7 @@ interface AgentInputProps { cacheCreation: number; cacheRead: number; contextSize: number; + model?: string; }; alwaysShowContextSize?: boolean; onFileViewerPress?: () => void; @@ -93,7 +94,18 @@ interface AgentInputProps { onAddImages?: (images: AttachmentPreview[]) => void; } -const MAX_CONTEXT_SIZE = 190000; +// Effective usable context per model. Anthropic 1M-context variants +// expose ~950K usable tokens; everything else is treated as 200K (~190K usable). +// See https://github.com/slopus/happy/issues/910 +const DEFAULT_MAX_CONTEXT_SIZE = 190000; +const ONE_MILLION_MAX_CONTEXT_SIZE = 950000; + +function getMaxContextSize(model?: string): number { + if (model && /\[1m\]/i.test(model)) { + return ONE_MILLION_MAX_CONTEXT_SIZE; + } + return DEFAULT_MAX_CONTEXT_SIZE; +} const stylesheet = StyleSheet.create((theme, runtime) => ({ container: { @@ -300,8 +312,9 @@ const stylesheet = StyleSheet.create((theme, runtime) => ({ }, })); -const getContextWarning = (contextSize: number, alwaysShow: boolean = false, theme: Theme) => { - const percentageUsed = (contextSize / MAX_CONTEXT_SIZE) * 100; +const getContextWarning = (contextSize: number, alwaysShow: boolean = false, theme: Theme, model?: string) => { + const maxContextSize = getMaxContextSize(model); + const percentageUsed = (contextSize / maxContextSize) * 100; const percentageRemaining = Math.max(0, Math.min(100, 100 - percentageUsed)); if (percentageRemaining <= 5) { @@ -598,7 +611,7 @@ export const AgentInput = React.memo(React.forwardRef state.latestUsage.timestamp) { state.latestUsage = { @@ -1152,6 +1155,7 @@ function processUsageData(state: ReducerState, usage: UsageData, timestamp: numb cacheCreation: usage.cache_creation_input_tokens || 0, cacheRead: usage.cache_read_input_tokens || 0, contextSize: (usage.cache_creation_input_tokens || 0) + (usage.cache_read_input_tokens || 0) + usage.input_tokens, + model, timestamp: timestamp }; } diff --git a/packages/happy-app/sources/sync/storageTypes.ts b/packages/happy-app/sources/sync/storageTypes.ts index b3e9eaa715..498be942db 100644 --- a/packages/happy-app/sources/sync/storageTypes.ts +++ b/packages/happy-app/sources/sync/storageTypes.ts @@ -125,6 +125,7 @@ export interface Session { cacheCreation: number; cacheRead: number; contextSize: number; + model?: string; timestamp: number; } | null; } diff --git a/packages/happy-app/sources/sync/typesRaw.ts b/packages/happy-app/sources/sync/typesRaw.ts index ef05147f64..77b11c4c06 100644 --- a/packages/happy-app/sources/sync/typesRaw.ts +++ b/packages/happy-app/sources/sync/typesRaw.ts @@ -526,6 +526,7 @@ export type NormalizedMessage = ({ isSidechain: boolean, meta?: MessageMeta, usage?: UsageData, + model?: string, /** * Underlying Claude `uuid` for this message — used as the rewind point * for the session fork / duplicate flow. Optional because some message @@ -835,7 +836,8 @@ export function normalizeRawMessage(id: string, localId: string | null, createdA isSidechain: raw.content.data.isSidechain ?? false, content, meta: raw.meta, - usage: raw.content.data.message.usage + usage: raw.content.data.message.usage, + model: raw.content.data.message.model }; } else if (raw.content.data.type === 'user') { if (!raw.content.data.uuid) { From 266731c4d97e8bf1ed7abf2932e24ad665966f2d Mon Sep 17 00:00:00 2001 From: Gordon Lee Date: Sun, 17 May 2026 23:24:42 +0700 Subject: [PATCH 2/2] fix(app): render alwaysShowContextSize before usage streams in MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Always Show Context Size toggle wired through SessionView and AgentInput, but the indicator never appeared on Android sessions until the first assistant turn delivered a non-zero `contextSize`. The outer gate at the call site (`props.usageData?.contextSize ? ... : null`) short-circuited to null whenever `contextSize` was 0 or undefined, swallowing the alwaysShow signal that `getContextWarning` itself honors. Two failure modes this resolves: 1. Fresh session with no agent reply yet — `latestUsage` is undefined so the indicator never showed despite the toggle being on. 2. Sessions where the reducer reset `contextSize` to 0 on a session event (reducer.ts lines 324, 338) and no subsequent assistant message with usage arrived to repopulate it. Now the gate is `alwaysShow || contextSize`, and `getContextWarning` is called with `contextSize ?? 0`. On fresh always-show sessions the indicator renders as "100% remaining" until real usage arrives. Closes #1274 --- packages/happy-app/sources/components/AgentInput.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/happy-app/sources/components/AgentInput.tsx b/packages/happy-app/sources/components/AgentInput.tsx index ba789e17b9..8b34949e47 100644 --- a/packages/happy-app/sources/components/AgentInput.tsx +++ b/packages/happy-app/sources/components/AgentInput.tsx @@ -609,9 +609,10 @@ export const AgentInput = React.memo(React.forwardRef