feat: Enhance configuration management and embedding capabilities

- Added JSON-based settings management in Config class for embedding and LLM configurations.
- Introduced methods to save and load settings from a JSON file.
- Updated BaseEmbedder and its subclasses to include max_tokens property for better token management.
- Enhanced chunking strategy to support recursive splitting of large symbols with improved overlap handling.
- Implemented comprehensive tests for recursive splitting and chunking behavior.
- Added CLI tools configuration management for better integration with external tools.
- Introduced a new command for compacting session memory into structured text for recovery.
This commit is contained in:
catlog22
2025-12-24 16:32:27 +08:00
parent b00113d212
commit e671b45948
25 changed files with 2889 additions and 153 deletions

View File

@@ -33,6 +33,13 @@ import {
getFullConfigResponse,
PREDEFINED_MODELS
} from '../../tools/cli-config-manager.js';
import {
loadClaudeCliTools,
saveClaudeCliTools,
updateClaudeToolEnabled,
updateClaudeCacheSettings,
getClaudeCliToolsInfo
} from '../../tools/claude-cli-tools.js';
export interface RouteContext {
pathname: string;
@@ -558,5 +565,101 @@ export async function handleCliRoutes(ctx: RouteContext): Promise<boolean> {
return true;
}
// API: Get CLI Tools Config from .claude/cli-tools.json (with fallback to global)
if (pathname === '/api/cli/tools-config' && req.method === 'GET') {
try {
const config = loadClaudeCliTools(initialPath);
const info = getClaudeCliToolsInfo(initialPath);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
...config,
_configInfo: info
}));
} catch (err) {
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: (err as Error).message }));
}
return true;
}
// API: Update CLI Tools Config
if (pathname === '/api/cli/tools-config' && req.method === 'PUT') {
handlePostRequest(req, res, async (body: unknown) => {
try {
const updates = body as Partial<any>;
const config = loadClaudeCliTools(initialPath);
// Merge updates
const updatedConfig = {
...config,
...updates,
tools: { ...config.tools, ...(updates.tools || {}) },
settings: {
...config.settings,
...(updates.settings || {}),
cache: {
...config.settings.cache,
...(updates.settings?.cache || {})
}
}
};
saveClaudeCliTools(initialPath, updatedConfig);
broadcastToClients({
type: 'CLI_TOOLS_CONFIG_UPDATED',
payload: { config: updatedConfig, timestamp: new Date().toISOString() }
});
return { success: true, config: updatedConfig };
} catch (err) {
return { error: (err as Error).message, status: 500 };
}
});
return true;
}
// API: Update specific tool enabled status
const toolsConfigMatch = pathname.match(/^\/api\/cli\/tools-config\/([a-zA-Z0-9_-]+)$/);
if (toolsConfigMatch && req.method === 'PUT') {
const toolName = toolsConfigMatch[1];
handlePostRequest(req, res, async (body: unknown) => {
try {
const { enabled } = body as { enabled: boolean };
const config = updateClaudeToolEnabled(initialPath, toolName, enabled);
broadcastToClients({
type: 'CLI_TOOL_TOGGLED',
payload: { tool: toolName, enabled, timestamp: new Date().toISOString() }
});
return { success: true, config };
} catch (err) {
return { error: (err as Error).message, status: 500 };
}
});
return true;
}
// API: Update cache settings
if (pathname === '/api/cli/tools-config/cache' && req.method === 'PUT') {
handlePostRequest(req, res, async (body: unknown) => {
try {
const cacheSettings = body as { injectionMode?: string; defaultPrefix?: string; defaultSuffix?: string };
const config = updateClaudeCacheSettings(initialPath, cacheSettings as any);
broadcastToClients({
type: 'CLI_CACHE_SETTINGS_UPDATED',
payload: { cache: config.settings.cache, timestamp: new Date().toISOString() }
});
return { success: true, config };
} catch (err) {
return { error: (err as Error).message, status: 500 };
}
});
return true;
}
return false;
}

View File

@@ -405,7 +405,7 @@ export async function handleCodexLensRoutes(ctx: RouteContext): Promise<boolean>
// API: CodexLens Init (Initialize workspace index)
if (pathname === '/api/codexlens/init' && req.method === 'POST') {
handlePostRequest(req, res, async (body) => {
const { path: projectPath, indexType = 'vector', embeddingModel = 'code' } = body;
const { path: projectPath, indexType = 'vector', embeddingModel = 'code', embeddingBackend = 'fastembed' } = body;
const targetPath = projectPath || initialPath;
// Build CLI arguments based on index type
@@ -415,6 +415,10 @@ export async function handleCodexLensRoutes(ctx: RouteContext): Promise<boolean>
} else {
// Add embedding model selection for vector index
args.push('--embedding-model', embeddingModel);
// Add embedding backend if not using default fastembed
if (embeddingBackend && embeddingBackend !== 'fastembed') {
args.push('--embedding-backend', embeddingBackend);
}
}
// Broadcast start event

View File

@@ -20,6 +20,8 @@ import {
getGlobalCacheSettings,
updateGlobalCacheSettings,
loadLiteLLMApiConfig,
saveLiteLLMYamlConfig,
generateLiteLLMYamlConfig,
type ProviderCredential,
type CustomEndpoint,
type ProviderType,
@@ -481,5 +483,150 @@ export async function handleLiteLLMApiRoutes(ctx: RouteContext): Promise<boolean
return true;
}
// ===========================
// Config Sync Routes
// ===========================
// POST /api/litellm-api/config/sync - Sync UI config to ccw_litellm YAML config
if (pathname === '/api/litellm-api/config/sync' && req.method === 'POST') {
try {
const yamlPath = saveLiteLLMYamlConfig(initialPath);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
success: true,
message: 'Config synced to ccw_litellm',
yamlPath,
}));
} catch (err) {
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: (err as Error).message }));
}
return true;
}
// GET /api/litellm-api/config/yaml-preview - Preview YAML config without saving
if (pathname === '/api/litellm-api/config/yaml-preview' && req.method === 'GET') {
try {
const yamlConfig = generateLiteLLMYamlConfig(initialPath);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
success: true,
config: yamlConfig,
}));
} catch (err) {
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: (err as Error).message }));
}
return true;
}
// ===========================
// CCW-LiteLLM Package Management
// ===========================
// GET /api/litellm-api/ccw-litellm/status - Check ccw-litellm installation status
if (pathname === '/api/litellm-api/ccw-litellm/status' && req.method === 'GET') {
try {
const { spawn } = await import('child_process');
const result = await new Promise<{ installed: boolean; version?: string }>((resolve) => {
const proc = spawn('python', ['-c', 'import ccw_litellm; print(ccw_litellm.__version__ if hasattr(ccw_litellm, "__version__") else "installed")'], {
shell: true,
timeout: 10000
});
let output = '';
proc.stdout?.on('data', (data) => { output += data.toString(); });
proc.on('close', (code) => {
if (code === 0) {
resolve({ installed: true, version: output.trim() || 'unknown' });
} else {
resolve({ installed: false });
}
});
proc.on('error', () => resolve({ installed: false }));
});
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(result));
} catch (err) {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ installed: false, error: (err as Error).message }));
}
return true;
}
// POST /api/litellm-api/ccw-litellm/install - Install ccw-litellm package
if (pathname === '/api/litellm-api/ccw-litellm/install' && req.method === 'POST') {
handlePostRequest(req, res, async () => {
try {
const { spawn } = await import('child_process');
const path = await import('path');
const fs = await import('fs');
// Try to find ccw-litellm package in distribution
const possiblePaths = [
path.join(initialPath, 'ccw-litellm'),
path.join(initialPath, '..', 'ccw-litellm'),
path.join(process.cwd(), 'ccw-litellm'),
];
let packagePath = '';
for (const p of possiblePaths) {
const pyproject = path.join(p, 'pyproject.toml');
if (fs.existsSync(pyproject)) {
packagePath = p;
break;
}
}
if (!packagePath) {
// Try pip install from PyPI as fallback
return new Promise((resolve) => {
const proc = spawn('pip', ['install', 'ccw-litellm'], { shell: true, timeout: 300000 });
let output = '';
let error = '';
proc.stdout?.on('data', (data) => { output += data.toString(); });
proc.stderr?.on('data', (data) => { error += data.toString(); });
proc.on('close', (code) => {
if (code === 0) {
resolve({ success: true, message: 'ccw-litellm installed from PyPI' });
} else {
resolve({ success: false, error: error || 'Installation failed' });
}
});
proc.on('error', (err) => resolve({ success: false, error: err.message }));
});
}
// Install from local package
return new Promise((resolve) => {
const proc = spawn('pip', ['install', '-e', packagePath], { shell: true, timeout: 300000 });
let output = '';
let error = '';
proc.stdout?.on('data', (data) => { output += data.toString(); });
proc.stderr?.on('data', (data) => { error += data.toString(); });
proc.on('close', (code) => {
if (code === 0) {
// Broadcast installation event
broadcastToClients({
type: 'CCW_LITELLM_INSTALLED',
payload: { timestamp: new Date().toISOString() }
});
resolve({ success: true, message: 'ccw-litellm installed successfully', path: packagePath });
} else {
resolve({ success: false, error: error || output || 'Installation failed' });
}
});
proc.on('error', (err) => resolve({ success: false, error: err.message }));
});
} catch (err) {
return { success: false, error: (err as Error).message };
}
});
return true;
}
return false;
}