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
5 changes: 4 additions & 1 deletion src/types/Settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,10 @@ export const SettingsSchema = z.object({
message: z.string().nullable().optional(),
remaining: z.number().nullable().optional()
}).optional(),
installation: InstallationMetadataSchema.optional()
installation: InstallationMetadataSchema.optional(),
// Usable context ratio as a percentage (e.g., 80 = 80% = 0.8 ratio)
// Used by ContextPercentageUsable widget for auto-compact threshold calculation
autoCompactWindow: z.number().min(1).max(99).default(80)
});

// Inferred type from schema
Expand Down
9 changes: 8 additions & 1 deletion src/utils/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,9 +156,16 @@ export async function loadSettings(): Promise<Settings> {
export async function saveSettings(settings: Settings): Promise<void> {
const paths = getSettingsPaths();

// Validate settings before writing to prevent data loss
const parsed = SettingsSchema.safeParse(settings);
if (!parsed.success) {
console.error('Invalid settings format, not saving:', parsed.error);
return;
}

// Always include version when saving
const settingsWithVersion = {
...settings,
...parsed.data,
version: CURRENT_VERSION
};

Expand Down
61 changes: 57 additions & 4 deletions src/utils/model-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,59 @@ interface ModelIdentifier {
}

const DEFAULT_CONTEXT_WINDOW_SIZE = 200000;
const USABLE_CONTEXT_RATIO = 0.8;
const DEFAULT_USABLE_CONTEXT_RATIO = 0.8;

/**
* Gets the usable context ratio from environment variable or settings.
* Priority: CLAUDE_AUTOCOMPACT_PCT_OVERRIDE env var > autoCompactWindow setting > default 0.8
* The ratio is expressed as a percentage (e.g., 80 = 80% = 0.8)
*/
function getUsableContextRatio(): number {
// First check environment variable override
const envValue = process.env.CLAUDE_AUTOCOMPACT_PCT_OVERRIDE;
if (envValue !== undefined) {
const parsed = Number.parseFloat(envValue);
if (Number.isFinite(parsed) && parsed > 0 && parsed <= 100) {
return parsed / 100;
}
}

// Settings are loaded lazily to avoid circular dependencies
// The autoCompactWindow setting stores the percentage directly
try {
const settingsPath = require('path').join(
require('os').homedir(),
'.config',
'ccstatusline',
'settings.json'
);
const fs = require('fs');
if (fs.existsSync(settingsPath)) {
const content = fs.readFileSync(settingsPath, 'utf-8');
const settings = JSON.parse(content);
if (settings.autoCompactWindow !== undefined) {
const parsed = Number.parseFloat(String(settings.autoCompactWindow));
if (Number.isFinite(parsed) && parsed > 0 && parsed <= 100) {
return parsed / 100;
}
}
}
} catch {
// Ignore errors loading settings, fall back to default
}

return DEFAULT_USABLE_CONTEXT_RATIO;
}

// Cache the ratio so we don't re-read settings on every call
let cachedUsableContextRatio: number | null = null;

function getCachedUsableContextRatio(): number {
if (cachedUsableContextRatio === null) {
cachedUsableContextRatio = getUsableContextRatio();
}
return cachedUsableContextRatio;
}

function toValidWindowSize(value: number | null | undefined): number | null {
if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) {
Expand Down Expand Up @@ -74,18 +126,19 @@ export function getModelContextIdentifier(model?: string | ModelIdentifier): str
}

export function getContextConfig(modelIdentifier?: string, contextWindowSize?: number | null): ModelContextConfig {
const usableRatio = getCachedUsableContextRatio();
const statusWindowSize = toValidWindowSize(contextWindowSize);
if (statusWindowSize !== null) {
return {
maxTokens: statusWindowSize,
usableTokens: Math.floor(statusWindowSize * USABLE_CONTEXT_RATIO)
usableTokens: Math.floor(statusWindowSize * usableRatio)
};
}

// Default to 200k for older models
const defaultConfig = {
maxTokens: DEFAULT_CONTEXT_WINDOW_SIZE,
usableTokens: Math.floor(DEFAULT_CONTEXT_WINDOW_SIZE * USABLE_CONTEXT_RATIO)
usableTokens: Math.floor(DEFAULT_CONTEXT_WINDOW_SIZE * usableRatio)
};

if (!modelIdentifier) {
Expand All @@ -96,7 +149,7 @@ export function getContextConfig(modelIdentifier?: string, contextWindowSize?: n
if (inferredWindowSize !== null) {
return {
maxTokens: inferredWindowSize,
usableTokens: Math.floor(inferredWindowSize * USABLE_CONTEXT_RATIO)
usableTokens: Math.floor(inferredWindowSize * usableRatio)
};
}

Expand Down