Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/happy-app/sources/-session/SessionView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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]);

Expand Down
26 changes: 20 additions & 6 deletions packages/happy-app/sources/components/AgentInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ interface AgentInputProps {
cacheCreation: number;
cacheRead: number;
contextSize: number;
model?: string;
};
alwaysShowContextSize?: boolean;
onFileViewerPress?: () => void;
Expand All @@ -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: {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -596,9 +609,10 @@ export const AgentInput = React.memo(React.forwardRef<MultiTextInputHandle, Agen
return label;
}, [isSandboxEnabled]);

// Calculate context warning
const contextWarning = props.usageData?.contextSize
? getContextWarning(props.usageData.contextSize, props.alwaysShowContextSize ?? false, theme)
// Gate on `alwaysShow || contextSize` so the toggle still renders before usage streams in.
const alwaysShowContextSize = props.alwaysShowContextSize ?? false;
const contextWarning = alwaysShowContextSize || props.usageData?.contextSize
? getContextWarning(props.usageData?.contextSize ?? 0, alwaysShowContextSize, theme, props.usageData?.model)
: null;

const agentInputEnterToSend = useSetting('agentInputEnterToSend');
Expand Down
10 changes: 7 additions & 3 deletions packages/happy-app/sources/sync/reducer/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ export type ReducerState = {
cacheCreation: number;
cacheRead: number;
contextSize: number;
model?: string;
timestamp: number;
};
};
Expand Down Expand Up @@ -239,6 +240,7 @@ export type ReducerResult = {
cacheCreation: number;
cacheRead: number;
contextSize: number;
model?: string;
};
hasReadyEvent?: boolean;
};
Expand Down Expand Up @@ -687,7 +689,7 @@ export function reducer(state: ReducerState, messages: NormalizedMessage[], agen

// Process usage data if present
if (msg.usage) {
processUsageData(state, msg.usage, msg.createdAt);
processUsageData(state, msg.usage, msg.createdAt, msg.model);
}

// Process text and thinking content (tool calls handled in Phase 2)
Expand Down Expand Up @@ -1129,7 +1131,8 @@ export function reducer(state: ReducerState, messages: NormalizedMessage[], agen
outputTokens: state.latestUsage.outputTokens,
cacheCreation: state.latestUsage.cacheCreation,
cacheRead: state.latestUsage.cacheRead,
contextSize: state.latestUsage.contextSize
contextSize: state.latestUsage.contextSize,
model: state.latestUsage.model
} : undefined,
hasReadyEvent: hasReadyEvent || undefined
};
Expand All @@ -1143,7 +1146,7 @@ function allocateId() {
return Math.random().toString(36).substring(2, 15);
}

function processUsageData(state: ReducerState, usage: UsageData, timestamp: number) {
function processUsageData(state: ReducerState, usage: UsageData, timestamp: number, model?: string) {
// Only update if this is newer than the current latest usage
if (!state.latestUsage || timestamp > state.latestUsage.timestamp) {
state.latestUsage = {
Expand All @@ -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
};
}
Expand Down
1 change: 1 addition & 0 deletions packages/happy-app/sources/sync/storageTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ export interface Session {
cacheCreation: number;
cacheRead: number;
contextSize: number;
model?: string;
timestamp: number;
} | null;
}
Expand Down
4 changes: 3 additions & 1 deletion packages/happy-app/sources/sync/typesRaw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down