Enhance error handling and validation in agents and routes
- Added logging for empty responses in GeminiAgent and OpenRouterAgent to track potential session context issues. - Refactored settings file path usage in OpenRouterAgent to use a constant for better maintainability. - Improved error handling in SessionRoutes to log generator failures with detailed context. - Implemented JSON parsing error handling in SettingsRoutes to manage corrupted settings files gracefully. - Added validation for CLAUDE_MEM_OPENROUTER_MAX_CONTEXT_MESSAGES, CLAUDE_MEM_OPENROUTER_MAX_TOKENS, and CLAUDE_MEM_OPENROUTER_SITE_URL in SettingsRoutes to ensure valid configuration.
This commit is contained in:
@@ -170,6 +170,11 @@ export class GeminiAgent {
|
||||
|
||||
// Process response (no original timestamp for init - not from queue)
|
||||
await this.processGeminiResponse(session, initResponse.content, worker, tokensUsed, null);
|
||||
} else {
|
||||
logger.warn('SDK', 'Empty Gemini init response - session may lack context', {
|
||||
sessionId: session.sessionDbId,
|
||||
model
|
||||
});
|
||||
}
|
||||
|
||||
// Process pending messages
|
||||
|
||||
@@ -11,14 +11,13 @@
|
||||
* - Support dynamic model selection across providers
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import { homedir } from 'os';
|
||||
import { DatabaseManager } from './DatabaseManager.js';
|
||||
import { SessionManager } from './SessionManager.js';
|
||||
import { logger } from '../../utils/logger.js';
|
||||
import { parseObservations, parseSummary } from '../../sdk/parser.js';
|
||||
import { buildInitPrompt, buildObservationPrompt, buildSummaryPrompt, buildContinuationPrompt } from '../../sdk/prompts.js';
|
||||
import { SettingsDefaultsManager } from '../../shared/SettingsDefaultsManager.js';
|
||||
import { USER_SETTINGS_PATH } from '../../shared/paths.js';
|
||||
import type { ActiveSession, ConversationMessage } from '../worker-types.js';
|
||||
import { ModeManager } from '../domain/ModeManager.js';
|
||||
|
||||
@@ -131,6 +130,11 @@ export class OpenRouterAgent {
|
||||
|
||||
// Process response (no original timestamp for init - not from queue)
|
||||
await this.processOpenRouterResponse(session, initResponse.content, worker, tokensUsed, null);
|
||||
} else {
|
||||
logger.warn('SDK', 'Empty OpenRouter init response - session may lack context', {
|
||||
sessionId: session.sessionDbId,
|
||||
model
|
||||
});
|
||||
}
|
||||
|
||||
// Process pending messages
|
||||
@@ -263,7 +267,7 @@ export class OpenRouterAgent {
|
||||
*/
|
||||
private truncateHistory(history: ConversationMessage[]): ConversationMessage[] {
|
||||
const settings = SettingsDefaultsManager.loadFromFile(
|
||||
path.join(homedir(), '.claude-mem', 'settings.json')
|
||||
USER_SETTINGS_PATH
|
||||
);
|
||||
|
||||
const MAX_CONTEXT_MESSAGES = parseInt(settings.CLAUDE_MEM_OPENROUTER_MAX_CONTEXT_MESSAGES) || DEFAULT_MAX_CONTEXT_MESSAGES;
|
||||
@@ -377,7 +381,7 @@ export class OpenRouterAgent {
|
||||
if (tokensUsed) {
|
||||
const inputTokens = data.usage?.prompt_tokens || 0;
|
||||
const outputTokens = data.usage?.completion_tokens || 0;
|
||||
// Rough cost estimate (Claude 3.5 Sonnet pricing: $3/MTok input, $15/MTok output)
|
||||
// Token usage (cost varies by model - many OpenRouter models are free)
|
||||
const estimatedCost = (inputTokens / 1000000 * 3) + (outputTokens / 1000000 * 15);
|
||||
|
||||
logger.info('SDK', 'OpenRouter API usage', {
|
||||
@@ -568,7 +572,7 @@ export class OpenRouterAgent {
|
||||
* Get OpenRouter configuration from settings or environment
|
||||
*/
|
||||
private getOpenRouterConfig(): { apiKey: string; model: string; siteUrl?: string; appName?: string } {
|
||||
const settingsPath = path.join(homedir(), '.claude-mem', 'settings.json');
|
||||
const settingsPath = USER_SETTINGS_PATH;
|
||||
const settings = SettingsDefaultsManager.loadFromFile(settingsPath);
|
||||
|
||||
// API key: check settings first, then environment variable
|
||||
@@ -589,7 +593,7 @@ export class OpenRouterAgent {
|
||||
* Check if OpenRouter is available (has API key configured)
|
||||
*/
|
||||
export function isOpenRouterAvailable(): boolean {
|
||||
const settingsPath = path.join(homedir(), '.claude-mem', 'settings.json');
|
||||
const settingsPath = USER_SETTINGS_PATH;
|
||||
const settings = SettingsDefaultsManager.loadFromFile(settingsPath);
|
||||
return !!(settings.CLAUDE_MEM_OPENROUTER_API_KEY || process.env.OPENROUTER_API_KEY);
|
||||
}
|
||||
@@ -598,7 +602,7 @@ export function isOpenRouterAvailable(): boolean {
|
||||
* Check if OpenRouter is the selected provider
|
||||
*/
|
||||
export function isOpenRouterSelected(): boolean {
|
||||
const settingsPath = path.join(homedir(), '.claude-mem', 'settings.json');
|
||||
const settingsPath = USER_SETTINGS_PATH;
|
||||
const settings = SettingsDefaultsManager.loadFromFile(settingsPath);
|
||||
return settings.CLAUDE_MEM_PROVIDER === 'openrouter';
|
||||
}
|
||||
|
||||
@@ -135,6 +135,13 @@ export class SessionRoutes extends BaseRouteHandler {
|
||||
session.currentProvider = provider;
|
||||
|
||||
session.generatorPromise = agent.startSession(session, this.workerService)
|
||||
.catch(error => {
|
||||
logger.error('SESSION', `Generator failed`, {
|
||||
sessionId: session.sessionDbId,
|
||||
provider: provider,
|
||||
error: error.message
|
||||
}, error);
|
||||
})
|
||||
.finally(() => {
|
||||
logger.info('SESSION', `Generator finished`, { sessionId: session.sessionDbId });
|
||||
session.generatorPromise = null;
|
||||
|
||||
@@ -71,7 +71,16 @@ export class SettingsRoutes extends BaseRouteHandler {
|
||||
|
||||
if (existsSync(settingsPath)) {
|
||||
const settingsData = readFileSync(settingsPath, 'utf-8');
|
||||
settings = JSON.parse(settingsData);
|
||||
try {
|
||||
settings = JSON.parse(settingsData);
|
||||
} catch (parseError) {
|
||||
logger.error('SETTINGS', 'Failed to parse settings file', { settingsPath }, parseError as Error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Settings file is corrupted. Delete ~/.claude-mem/settings.json to reset.'
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Update all settings from request body
|
||||
@@ -319,6 +328,31 @@ export class SettingsRoutes extends BaseRouteHandler {
|
||||
}
|
||||
}
|
||||
|
||||
// Validate CLAUDE_MEM_OPENROUTER_MAX_CONTEXT_MESSAGES
|
||||
if (settings.CLAUDE_MEM_OPENROUTER_MAX_CONTEXT_MESSAGES) {
|
||||
const count = parseInt(settings.CLAUDE_MEM_OPENROUTER_MAX_CONTEXT_MESSAGES, 10);
|
||||
if (isNaN(count) || count < 1 || count > 100) {
|
||||
return { valid: false, error: 'CLAUDE_MEM_OPENROUTER_MAX_CONTEXT_MESSAGES must be between 1 and 100' };
|
||||
}
|
||||
}
|
||||
|
||||
// Validate CLAUDE_MEM_OPENROUTER_MAX_TOKENS
|
||||
if (settings.CLAUDE_MEM_OPENROUTER_MAX_TOKENS) {
|
||||
const tokens = parseInt(settings.CLAUDE_MEM_OPENROUTER_MAX_TOKENS, 10);
|
||||
if (isNaN(tokens) || tokens < 1000 || tokens > 1000000) {
|
||||
return { valid: false, error: 'CLAUDE_MEM_OPENROUTER_MAX_TOKENS must be between 1000 and 1000000' };
|
||||
}
|
||||
}
|
||||
|
||||
// Validate CLAUDE_MEM_OPENROUTER_SITE_URL if provided
|
||||
if (settings.CLAUDE_MEM_OPENROUTER_SITE_URL) {
|
||||
try {
|
||||
new URL(settings.CLAUDE_MEM_OPENROUTER_SITE_URL);
|
||||
} catch {
|
||||
return { valid: false, error: 'CLAUDE_MEM_OPENROUTER_SITE_URL must be a valid URL' };
|
||||
}
|
||||
}
|
||||
|
||||
// Skip observation types validation - any type string is valid since modes define their own types
|
||||
// The database accepts any TEXT value, and mode-specific validation happens at parse time
|
||||
|
||||
|
||||
Reference in New Issue
Block a user