Files
claude-mem/src/shared/SettingsDefaultsManager.ts
T
bigphoot ec8dd08c32 feat: add Gemini API as alternative AI provider
Adds support for Google's Gemini API as an alternative to Claude Agent SDK
for observation extraction. Users can now choose between providers in the
settings UI.

Features:
- New GeminiAgent class using Gemini REST API
- Provider selection in Settings (Claude vs Gemini)
- Gemini API key configuration (via UI or GEMINI_API_KEY env var)
- Model selection: gemini-2.0-flash-exp, gemini-1.5-flash, gemini-1.5-pro
- Graceful fallback to Claude SDK if Gemini selected but no API key
- Seamless transition between providers without worker restart

Settings:
- CLAUDE_MEM_PROVIDER: 'claude' | 'gemini'
- CLAUDE_MEM_GEMINI_API_KEY: API key for Gemini
- CLAUDE_MEM_GEMINI_MODEL: Model selection

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-24 11:02:56 -08:00

159 lines
5.6 KiB
TypeScript

/**
* SettingsDefaultsManager
*
* Single source of truth for all default configuration values.
* Provides methods to get defaults with optional environment variable overrides.
*/
import { readFileSync, writeFileSync, existsSync } from 'fs';
import { join } from 'path';
import { homedir } from 'os';
import { DEFAULT_OBSERVATION_TYPES_STRING, DEFAULT_OBSERVATION_CONCEPTS_STRING } from '../constants/observation-metadata.js';
import { logger } from '../utils/logger.js';
export interface SettingsDefaults {
CLAUDE_MEM_MODEL: string;
CLAUDE_MEM_CONTEXT_OBSERVATIONS: string;
CLAUDE_MEM_WORKER_PORT: string;
CLAUDE_MEM_WORKER_HOST: string;
CLAUDE_MEM_SKIP_TOOLS: string;
// AI Provider Configuration
CLAUDE_MEM_PROVIDER: string; // 'claude' | 'gemini'
CLAUDE_MEM_GEMINI_API_KEY: string;
CLAUDE_MEM_GEMINI_MODEL: string; // 'gemini-2.0-flash-exp' | 'gemini-1.5-flash' | 'gemini-1.5-pro'
// System Configuration
CLAUDE_MEM_DATA_DIR: string;
CLAUDE_MEM_LOG_LEVEL: string;
CLAUDE_MEM_PYTHON_VERSION: string;
CLAUDE_CODE_PATH: string;
CLAUDE_MEM_MODE: string;
// Token Economics
CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS: string;
CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS: string;
CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT: string;
CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT: string;
// Observation Filtering
CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES: string;
CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS: string;
// Display Configuration
CLAUDE_MEM_CONTEXT_FULL_COUNT: string;
CLAUDE_MEM_CONTEXT_FULL_FIELD: string;
CLAUDE_MEM_CONTEXT_SESSION_COUNT: string;
// Feature Toggles
CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY: string;
CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE: string;
}
export class SettingsDefaultsManager {
/**
* Default values for all settings
*/
private static readonly DEFAULTS: SettingsDefaults = {
CLAUDE_MEM_MODEL: 'claude-sonnet-4-5',
CLAUDE_MEM_CONTEXT_OBSERVATIONS: '50',
CLAUDE_MEM_WORKER_PORT: '37777',
CLAUDE_MEM_WORKER_HOST: '127.0.0.1',
CLAUDE_MEM_SKIP_TOOLS: 'ListMcpResourcesTool,SlashCommand,Skill,TodoWrite,AskUserQuestion',
// AI Provider Configuration
CLAUDE_MEM_PROVIDER: 'claude', // Default to Claude
CLAUDE_MEM_GEMINI_API_KEY: '', // Empty by default, can be set via UI or env
CLAUDE_MEM_GEMINI_MODEL: 'gemini-2.0-flash-exp', // Default Gemini model
// System Configuration
CLAUDE_MEM_DATA_DIR: join(homedir(), '.claude-mem'),
CLAUDE_MEM_LOG_LEVEL: 'INFO',
CLAUDE_MEM_PYTHON_VERSION: '3.13',
CLAUDE_CODE_PATH: '', // Empty means auto-detect via 'which claude'
CLAUDE_MEM_MODE: 'code', // Default mode profile
// Token Economics
CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS: 'true',
CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS: 'true',
CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT: 'true',
CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT: 'true',
// Observation Filtering
CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES: DEFAULT_OBSERVATION_TYPES_STRING,
CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS: DEFAULT_OBSERVATION_CONCEPTS_STRING,
// Display Configuration
CLAUDE_MEM_CONTEXT_FULL_COUNT: '5',
CLAUDE_MEM_CONTEXT_FULL_FIELD: 'narrative',
CLAUDE_MEM_CONTEXT_SESSION_COUNT: '10',
// Feature Toggles
CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY: 'true',
CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE: 'false',
};
/**
* Get all defaults as an object
*/
static getAllDefaults(): SettingsDefaults {
return { ...this.DEFAULTS };
}
/**
* Get a default value from defaults (no environment variable override)
*/
static get(key: keyof SettingsDefaults): string {
return this.DEFAULTS[key];
}
/**
* Get an integer default value
*/
static getInt(key: keyof SettingsDefaults): number {
const value = this.get(key);
return parseInt(value, 10);
}
/**
* Get a boolean default value
*/
static getBool(key: keyof SettingsDefaults): boolean {
const value = this.get(key);
return value === 'true';
}
/**
* Load settings from file with fallback to defaults
* Returns merged settings with defaults as fallback
* Handles all errors (missing file, corrupted JSON, permissions) by returning defaults
*/
static loadFromFile(settingsPath: string): SettingsDefaults {
try {
if (!existsSync(settingsPath)) {
return this.getAllDefaults();
}
const settingsData = readFileSync(settingsPath, 'utf-8');
const settings = JSON.parse(settingsData);
// MIGRATION: Handle old nested schema { env: {...} }
let flatSettings = settings;
if (settings.env && typeof settings.env === 'object') {
// Migrate from nested to flat schema
flatSettings = settings.env;
// Auto-migrate the file to flat schema
try {
writeFileSync(settingsPath, JSON.stringify(flatSettings, null, 2), 'utf-8');
logger.info('SETTINGS', 'Migrated settings file from nested to flat schema', { settingsPath });
} catch (error) {
logger.warn('SETTINGS', 'Failed to auto-migrate settings file', { settingsPath }, error);
// Continue with in-memory migration even if write fails
}
}
// Merge file settings with defaults (flat schema)
const result: SettingsDefaults = { ...this.DEFAULTS };
for (const key of Object.keys(this.DEFAULTS) as Array<keyof SettingsDefaults>) {
if (flatSettings[key] !== undefined) {
result[key] = flatSettings[key];
}
}
return result;
} catch (error) {
logger.warn('SETTINGS', 'Failed to load settings, using defaults', { settingsPath }, error);
return this.getAllDefaults();
}
}
}