claude-code-workflow 6.2.7 → 6.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/CLAUDE.md +16 -1
- package/.claude/workflows/cli-templates/protocols/analysis-protocol.md +11 -4
- package/.claude/workflows/cli-templates/protocols/write-protocol.md +10 -75
- package/.claude/workflows/cli-tools-usage.md +14 -24
- package/.codex/AGENTS.md +51 -1
- package/.codex/prompts/compact.md +378 -0
- package/.gemini/GEMINI.md +57 -20
- package/ccw/dist/cli.d.ts.map +1 -1
- package/ccw/dist/cli.js +21 -8
- package/ccw/dist/cli.js.map +1 -1
- package/ccw/dist/commands/cli.d.ts +2 -0
- package/ccw/dist/commands/cli.d.ts.map +1 -1
- package/ccw/dist/commands/cli.js +129 -8
- package/ccw/dist/commands/cli.js.map +1 -1
- package/ccw/dist/commands/hook.d.ts.map +1 -1
- package/ccw/dist/commands/hook.js +3 -2
- package/ccw/dist/commands/hook.js.map +1 -1
- package/ccw/dist/config/litellm-api-config-manager.d.ts +180 -0
- package/ccw/dist/config/litellm-api-config-manager.d.ts.map +1 -0
- package/ccw/dist/config/litellm-api-config-manager.js +770 -0
- package/ccw/dist/config/litellm-api-config-manager.js.map +1 -0
- package/ccw/dist/config/provider-models.d.ts +73 -0
- package/ccw/dist/config/provider-models.d.ts.map +1 -0
- package/ccw/dist/config/provider-models.js +172 -0
- package/ccw/dist/config/provider-models.js.map +1 -0
- package/ccw/dist/core/cache-manager.d.ts.map +1 -1
- package/ccw/dist/core/cache-manager.js +3 -5
- package/ccw/dist/core/cache-manager.js.map +1 -1
- package/ccw/dist/core/dashboard-generator.d.ts.map +1 -1
- package/ccw/dist/core/dashboard-generator.js +3 -1
- package/ccw/dist/core/dashboard-generator.js.map +1 -1
- package/ccw/dist/core/routes/cli-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/cli-routes.js +169 -0
- package/ccw/dist/core/routes/cli-routes.js.map +1 -1
- package/ccw/dist/core/routes/codexlens-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/codexlens-routes.js +234 -18
- package/ccw/dist/core/routes/codexlens-routes.js.map +1 -1
- package/ccw/dist/core/routes/hooks-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/hooks-routes.js +30 -32
- package/ccw/dist/core/routes/hooks-routes.js.map +1 -1
- package/ccw/dist/core/routes/litellm-api-routes.d.ts +21 -0
- package/ccw/dist/core/routes/litellm-api-routes.d.ts.map +1 -0
- package/ccw/dist/core/routes/litellm-api-routes.js +780 -0
- package/ccw/dist/core/routes/litellm-api-routes.js.map +1 -0
- package/ccw/dist/core/routes/litellm-routes.d.ts +20 -0
- package/ccw/dist/core/routes/litellm-routes.d.ts.map +1 -0
- package/ccw/dist/core/routes/litellm-routes.js +85 -0
- package/ccw/dist/core/routes/litellm-routes.js.map +1 -0
- package/ccw/dist/core/routes/mcp-routes.js +2 -2
- package/ccw/dist/core/routes/mcp-routes.js.map +1 -1
- package/ccw/dist/core/routes/status-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/status-routes.js +39 -0
- package/ccw/dist/core/routes/status-routes.js.map +1 -1
- package/ccw/dist/core/routes/system-routes.js +1 -1
- package/ccw/dist/core/routes/system-routes.js.map +1 -1
- package/ccw/dist/core/server.d.ts.map +1 -1
- package/ccw/dist/core/server.js +15 -1
- package/ccw/dist/core/server.js.map +1 -1
- package/ccw/dist/mcp-server/index.js +1 -1
- package/ccw/dist/mcp-server/index.js.map +1 -1
- package/ccw/dist/tools/claude-cli-tools.d.ts +82 -0
- package/ccw/dist/tools/claude-cli-tools.d.ts.map +1 -0
- package/ccw/dist/tools/claude-cli-tools.js +216 -0
- package/ccw/dist/tools/claude-cli-tools.js.map +1 -0
- package/ccw/dist/tools/cli-executor.d.ts.map +1 -1
- package/ccw/dist/tools/cli-executor.js +76 -14
- package/ccw/dist/tools/cli-executor.js.map +1 -1
- package/ccw/dist/tools/codex-lens.d.ts +9 -2
- package/ccw/dist/tools/codex-lens.d.ts.map +1 -1
- package/ccw/dist/tools/codex-lens.js +114 -9
- package/ccw/dist/tools/codex-lens.js.map +1 -1
- package/ccw/dist/tools/context-cache-store.d.ts +136 -0
- package/ccw/dist/tools/context-cache-store.d.ts.map +1 -0
- package/ccw/dist/tools/context-cache-store.js +256 -0
- package/ccw/dist/tools/context-cache-store.js.map +1 -0
- package/ccw/dist/tools/context-cache.d.ts +56 -0
- package/ccw/dist/tools/context-cache.d.ts.map +1 -0
- package/ccw/dist/tools/context-cache.js +294 -0
- package/ccw/dist/tools/context-cache.js.map +1 -0
- package/ccw/dist/tools/core-memory.d.ts.map +1 -1
- package/ccw/dist/tools/core-memory.js +33 -19
- package/ccw/dist/tools/core-memory.js.map +1 -1
- package/ccw/dist/tools/index.d.ts.map +1 -1
- package/ccw/dist/tools/index.js +2 -0
- package/ccw/dist/tools/index.js.map +1 -1
- package/ccw/dist/tools/litellm-client.d.ts +85 -0
- package/ccw/dist/tools/litellm-client.d.ts.map +1 -0
- package/ccw/dist/tools/litellm-client.js +188 -0
- package/ccw/dist/tools/litellm-client.js.map +1 -0
- package/ccw/dist/tools/litellm-executor.d.ts +34 -0
- package/ccw/dist/tools/litellm-executor.d.ts.map +1 -0
- package/ccw/dist/tools/litellm-executor.js +192 -0
- package/ccw/dist/tools/litellm-executor.js.map +1 -0
- package/ccw/dist/tools/pattern-parser.d.ts +55 -0
- package/ccw/dist/tools/pattern-parser.d.ts.map +1 -0
- package/ccw/dist/tools/pattern-parser.js +237 -0
- package/ccw/dist/tools/pattern-parser.js.map +1 -0
- package/ccw/dist/tools/smart-search.d.ts +1 -0
- package/ccw/dist/tools/smart-search.d.ts.map +1 -1
- package/ccw/dist/tools/smart-search.js +117 -41
- package/ccw/dist/tools/smart-search.js.map +1 -1
- package/ccw/dist/types/litellm-api-config.d.ts +294 -0
- package/ccw/dist/types/litellm-api-config.d.ts.map +1 -0
- package/ccw/dist/types/litellm-api-config.js +8 -0
- package/ccw/dist/types/litellm-api-config.js.map +1 -0
- package/ccw/src/cli.ts +258 -244
- package/ccw/src/commands/cli.ts +153 -9
- package/ccw/src/commands/hook.ts +3 -2
- package/ccw/src/config/.litellm-api-config-manager.ts.2025-12-23T11-57-43-727Z.bak +441 -0
- package/ccw/src/config/litellm-api-config-manager.ts +1012 -0
- package/ccw/src/config/provider-models.ts +222 -0
- package/ccw/src/core/cache-manager.ts +292 -294
- package/ccw/src/core/dashboard-generator.ts +3 -1
- package/ccw/src/core/routes/cli-routes.ts +192 -0
- package/ccw/src/core/routes/codexlens-routes.ts +241 -19
- package/ccw/src/core/routes/hooks-routes.ts +399 -405
- package/ccw/src/core/routes/litellm-api-routes.ts +930 -0
- package/ccw/src/core/routes/litellm-routes.ts +107 -0
- package/ccw/src/core/routes/mcp-routes.ts +1271 -1271
- package/ccw/src/core/routes/status-routes.ts +51 -0
- package/ccw/src/core/routes/system-routes.ts +1 -1
- package/ccw/src/core/server.ts +15 -1
- package/ccw/src/mcp-server/index.ts +1 -1
- package/ccw/src/templates/dashboard-css/12-cli-legacy.css +44 -0
- package/ccw/src/templates/dashboard-css/31-api-settings.css +2265 -0
- package/ccw/src/templates/dashboard-js/components/cli-history.js +15 -8
- package/ccw/src/templates/dashboard-js/components/cli-status.js +323 -9
- package/ccw/src/templates/dashboard-js/components/navigation.js +329 -313
- package/ccw/src/templates/dashboard-js/i18n.js +583 -1
- package/ccw/src/templates/dashboard-js/views/api-settings.js +3362 -0
- package/ccw/src/templates/dashboard-js/views/cli-manager.js +199 -24
- package/ccw/src/templates/dashboard-js/views/codexlens-manager.js +1265 -27
- package/ccw/src/templates/dashboard.html +840 -831
- package/ccw/src/tools/claude-cli-tools.ts +300 -0
- package/ccw/src/tools/cli-executor.ts +83 -14
- package/ccw/src/tools/codex-lens.ts +146 -9
- package/ccw/src/tools/context-cache-store.ts +368 -0
- package/ccw/src/tools/context-cache.ts +393 -0
- package/ccw/src/tools/core-memory.ts +33 -19
- package/ccw/src/tools/index.ts +2 -0
- package/ccw/src/tools/litellm-client.ts +246 -0
- package/ccw/src/tools/litellm-executor.ts +241 -0
- package/ccw/src/tools/pattern-parser.ts +329 -0
- package/ccw/src/tools/smart-search.ts +142 -41
- package/ccw/src/types/litellm-api-config.ts +402 -0
- package/ccw-litellm/README.md +180 -0
- package/ccw-litellm/pyproject.toml +35 -0
- package/ccw-litellm/src/ccw_litellm/__init__.py +47 -0
- package/ccw-litellm/src/ccw_litellm/__pycache__/__init__.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/__pycache__/cli.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/cli.py +108 -0
- package/ccw-litellm/src/ccw_litellm/clients/__init__.py +12 -0
- package/ccw-litellm/src/ccw_litellm/clients/__pycache__/__init__.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/clients/__pycache__/litellm_embedder.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/clients/__pycache__/litellm_llm.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/clients/litellm_embedder.py +251 -0
- package/ccw-litellm/src/ccw_litellm/clients/litellm_llm.py +165 -0
- package/ccw-litellm/src/ccw_litellm/config/__init__.py +22 -0
- package/ccw-litellm/src/ccw_litellm/config/__pycache__/__init__.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/config/__pycache__/loader.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/config/__pycache__/models.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/config/loader.py +316 -0
- package/ccw-litellm/src/ccw_litellm/config/models.py +130 -0
- package/ccw-litellm/src/ccw_litellm/interfaces/__init__.py +14 -0
- package/ccw-litellm/src/ccw_litellm/interfaces/__pycache__/__init__.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/interfaces/__pycache__/embedder.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/interfaces/__pycache__/llm.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/interfaces/embedder.py +52 -0
- package/ccw-litellm/src/ccw_litellm/interfaces/llm.py +45 -0
- package/codex-lens/src/codexlens/__pycache__/config.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/cli/__pycache__/commands.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/cli/__pycache__/embedding_manager.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/cli/__pycache__/model_manager.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/cli/__pycache__/output.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/cli/commands.py +378 -23
- package/codex-lens/src/codexlens/cli/embedding_manager.py +660 -56
- package/codex-lens/src/codexlens/cli/model_manager.py +31 -18
- package/codex-lens/src/codexlens/cli/output.py +12 -1
- package/codex-lens/src/codexlens/config.py +93 -0
- package/codex-lens/src/codexlens/search/__pycache__/chain_search.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/search/__pycache__/hybrid_search.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/search/__pycache__/ranking.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/search/chain_search.py +6 -2
- package/codex-lens/src/codexlens/search/hybrid_search.py +44 -21
- package/codex-lens/src/codexlens/search/ranking.py +1 -1
- package/codex-lens/src/codexlens/semantic/__init__.py +42 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/__init__.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/base.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/chunker.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/embedder.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/factory.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/gpu_support.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/litellm_embedder.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/vector_store.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/base.py +61 -0
- package/codex-lens/src/codexlens/semantic/chunker.py +43 -20
- package/codex-lens/src/codexlens/semantic/embedder.py +60 -13
- package/codex-lens/src/codexlens/semantic/factory.py +98 -0
- package/codex-lens/src/codexlens/semantic/gpu_support.py +225 -3
- package/codex-lens/src/codexlens/semantic/litellm_embedder.py +144 -0
- package/codex-lens/src/codexlens/semantic/rotational_embedder.py +434 -0
- package/codex-lens/src/codexlens/semantic/vector_store.py +33 -8
- package/codex-lens/src/codexlens/storage/__pycache__/path_mapper.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/storage/migrations/__pycache__/migration_004_dual_fts.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/storage/path_mapper.py +27 -1
- package/package.json +15 -5
- package/.codex/prompts.zip +0 -0
- package/ccw/package.json +0 -65
|
@@ -0,0 +1,770 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LiteLLM API Configuration Manager
|
|
3
|
+
* Manages provider credentials, custom endpoints, and cache settings
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
6
|
+
import { homedir } from 'os';
|
|
7
|
+
import { join } from 'path';
|
|
8
|
+
import { GlobalPaths, ensureStorageDir } from './storage-paths.js';
|
|
9
|
+
/**
|
|
10
|
+
* Default configuration
|
|
11
|
+
*/
|
|
12
|
+
function getDefaultConfig() {
|
|
13
|
+
return {
|
|
14
|
+
version: 1,
|
|
15
|
+
providers: [],
|
|
16
|
+
endpoints: [],
|
|
17
|
+
globalCacheSettings: {
|
|
18
|
+
enabled: true,
|
|
19
|
+
cacheDir: '~/.ccw/cache/context',
|
|
20
|
+
maxTotalSizeMB: 100,
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Get config file path (global, shared across all projects)
|
|
26
|
+
*/
|
|
27
|
+
function getConfigPath(_baseDir) {
|
|
28
|
+
const configDir = GlobalPaths.config();
|
|
29
|
+
ensureStorageDir(configDir);
|
|
30
|
+
return join(configDir, 'litellm-api-config.json');
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Load configuration from file
|
|
34
|
+
*/
|
|
35
|
+
export function loadLiteLLMApiConfig(baseDir) {
|
|
36
|
+
const configPath = getConfigPath(baseDir);
|
|
37
|
+
if (!existsSync(configPath)) {
|
|
38
|
+
return getDefaultConfig();
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
const content = readFileSync(configPath, 'utf-8');
|
|
42
|
+
return JSON.parse(content);
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
console.error('[LiteLLM Config] Failed to load config:', error);
|
|
46
|
+
return getDefaultConfig();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Save configuration to file
|
|
51
|
+
*/
|
|
52
|
+
function saveConfig(baseDir, config) {
|
|
53
|
+
const configPath = getConfigPath(baseDir);
|
|
54
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Resolve environment variables in API key
|
|
58
|
+
* Supports ${ENV_VAR} syntax
|
|
59
|
+
*/
|
|
60
|
+
export function resolveEnvVar(value) {
|
|
61
|
+
if (!value)
|
|
62
|
+
return value;
|
|
63
|
+
const envVarMatch = value.match(/^\$\{(.+)\}$/);
|
|
64
|
+
if (envVarMatch) {
|
|
65
|
+
const envVarName = envVarMatch[1];
|
|
66
|
+
return process.env[envVarName] || '';
|
|
67
|
+
}
|
|
68
|
+
return value;
|
|
69
|
+
}
|
|
70
|
+
// ===========================
|
|
71
|
+
// Provider Management
|
|
72
|
+
// ===========================
|
|
73
|
+
/**
|
|
74
|
+
* Get all providers
|
|
75
|
+
*/
|
|
76
|
+
export function getAllProviders(baseDir) {
|
|
77
|
+
const config = loadLiteLLMApiConfig(baseDir);
|
|
78
|
+
return config.providers;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Get provider by ID
|
|
82
|
+
*/
|
|
83
|
+
export function getProvider(baseDir, providerId) {
|
|
84
|
+
const config = loadLiteLLMApiConfig(baseDir);
|
|
85
|
+
return config.providers.find((p) => p.id === providerId) || null;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Get provider with resolved environment variables
|
|
89
|
+
*/
|
|
90
|
+
export function getProviderWithResolvedEnvVars(baseDir, providerId) {
|
|
91
|
+
const provider = getProvider(baseDir, providerId);
|
|
92
|
+
if (!provider)
|
|
93
|
+
return null;
|
|
94
|
+
return {
|
|
95
|
+
...provider,
|
|
96
|
+
resolvedApiKey: resolveEnvVar(provider.apiKey),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Add new provider
|
|
101
|
+
*/
|
|
102
|
+
export function addProvider(baseDir, providerData) {
|
|
103
|
+
const config = loadLiteLLMApiConfig(baseDir);
|
|
104
|
+
const provider = {
|
|
105
|
+
...providerData,
|
|
106
|
+
id: `${providerData.type}-${Date.now()}`,
|
|
107
|
+
createdAt: new Date().toISOString(),
|
|
108
|
+
updatedAt: new Date().toISOString(),
|
|
109
|
+
};
|
|
110
|
+
config.providers.push(provider);
|
|
111
|
+
saveConfig(baseDir, config);
|
|
112
|
+
return provider;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Update provider
|
|
116
|
+
*/
|
|
117
|
+
export function updateProvider(baseDir, providerId, updates) {
|
|
118
|
+
const config = loadLiteLLMApiConfig(baseDir);
|
|
119
|
+
const providerIndex = config.providers.findIndex((p) => p.id === providerId);
|
|
120
|
+
if (providerIndex === -1) {
|
|
121
|
+
throw new Error(`Provider not found: ${providerId}`);
|
|
122
|
+
}
|
|
123
|
+
config.providers[providerIndex] = {
|
|
124
|
+
...config.providers[providerIndex],
|
|
125
|
+
...updates,
|
|
126
|
+
updatedAt: new Date().toISOString(),
|
|
127
|
+
};
|
|
128
|
+
saveConfig(baseDir, config);
|
|
129
|
+
return config.providers[providerIndex];
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Delete provider
|
|
133
|
+
*/
|
|
134
|
+
export function deleteProvider(baseDir, providerId) {
|
|
135
|
+
const config = loadLiteLLMApiConfig(baseDir);
|
|
136
|
+
const initialLength = config.providers.length;
|
|
137
|
+
config.providers = config.providers.filter((p) => p.id !== providerId);
|
|
138
|
+
if (config.providers.length === initialLength) {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
// Also remove endpoints using this provider
|
|
142
|
+
config.endpoints = config.endpoints.filter((e) => e.providerId !== providerId);
|
|
143
|
+
saveConfig(baseDir, config);
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
// ===========================
|
|
147
|
+
// Endpoint Management
|
|
148
|
+
// ===========================
|
|
149
|
+
/**
|
|
150
|
+
* Get all endpoints
|
|
151
|
+
*/
|
|
152
|
+
export function getAllEndpoints(baseDir) {
|
|
153
|
+
const config = loadLiteLLMApiConfig(baseDir);
|
|
154
|
+
return config.endpoints;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Get endpoint by ID
|
|
158
|
+
*/
|
|
159
|
+
export function getEndpoint(baseDir, endpointId) {
|
|
160
|
+
const config = loadLiteLLMApiConfig(baseDir);
|
|
161
|
+
return config.endpoints.find((e) => e.id === endpointId) || null;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Find endpoint by ID (alias for getEndpoint)
|
|
165
|
+
*/
|
|
166
|
+
export function findEndpointById(baseDir, endpointId) {
|
|
167
|
+
return getEndpoint(baseDir, endpointId);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Add new endpoint
|
|
171
|
+
*/
|
|
172
|
+
export function addEndpoint(baseDir, endpointData) {
|
|
173
|
+
const config = loadLiteLLMApiConfig(baseDir);
|
|
174
|
+
// Check if ID already exists
|
|
175
|
+
if (config.endpoints.some((e) => e.id === endpointData.id)) {
|
|
176
|
+
throw new Error(`Endpoint ID already exists: ${endpointData.id}`);
|
|
177
|
+
}
|
|
178
|
+
// Verify provider exists
|
|
179
|
+
if (!config.providers.find((p) => p.id === endpointData.providerId)) {
|
|
180
|
+
throw new Error(`Provider not found: ${endpointData.providerId}`);
|
|
181
|
+
}
|
|
182
|
+
const endpoint = {
|
|
183
|
+
...endpointData,
|
|
184
|
+
createdAt: new Date().toISOString(),
|
|
185
|
+
updatedAt: new Date().toISOString(),
|
|
186
|
+
};
|
|
187
|
+
config.endpoints.push(endpoint);
|
|
188
|
+
saveConfig(baseDir, config);
|
|
189
|
+
return endpoint;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Update endpoint
|
|
193
|
+
*/
|
|
194
|
+
export function updateEndpoint(baseDir, endpointId, updates) {
|
|
195
|
+
const config = loadLiteLLMApiConfig(baseDir);
|
|
196
|
+
const endpointIndex = config.endpoints.findIndex((e) => e.id === endpointId);
|
|
197
|
+
if (endpointIndex === -1) {
|
|
198
|
+
throw new Error(`Endpoint not found: ${endpointId}`);
|
|
199
|
+
}
|
|
200
|
+
// Verify provider exists if updating providerId
|
|
201
|
+
if (updates.providerId && !config.providers.find((p) => p.id === updates.providerId)) {
|
|
202
|
+
throw new Error(`Provider not found: ${updates.providerId}`);
|
|
203
|
+
}
|
|
204
|
+
config.endpoints[endpointIndex] = {
|
|
205
|
+
...config.endpoints[endpointIndex],
|
|
206
|
+
...updates,
|
|
207
|
+
updatedAt: new Date().toISOString(),
|
|
208
|
+
};
|
|
209
|
+
saveConfig(baseDir, config);
|
|
210
|
+
return config.endpoints[endpointIndex];
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Delete endpoint
|
|
214
|
+
*/
|
|
215
|
+
export function deleteEndpoint(baseDir, endpointId) {
|
|
216
|
+
const config = loadLiteLLMApiConfig(baseDir);
|
|
217
|
+
const initialLength = config.endpoints.length;
|
|
218
|
+
config.endpoints = config.endpoints.filter((e) => e.id !== endpointId);
|
|
219
|
+
if (config.endpoints.length === initialLength) {
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
// Clear default endpoint if deleted
|
|
223
|
+
if (config.defaultEndpoint === endpointId) {
|
|
224
|
+
delete config.defaultEndpoint;
|
|
225
|
+
}
|
|
226
|
+
saveConfig(baseDir, config);
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
// ===========================
|
|
230
|
+
// Default Endpoint Management
|
|
231
|
+
// ===========================
|
|
232
|
+
/**
|
|
233
|
+
* Get default endpoint
|
|
234
|
+
*/
|
|
235
|
+
export function getDefaultEndpoint(baseDir) {
|
|
236
|
+
const config = loadLiteLLMApiConfig(baseDir);
|
|
237
|
+
return config.defaultEndpoint;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Set default endpoint
|
|
241
|
+
*/
|
|
242
|
+
export function setDefaultEndpoint(baseDir, endpointId) {
|
|
243
|
+
const config = loadLiteLLMApiConfig(baseDir);
|
|
244
|
+
if (endpointId) {
|
|
245
|
+
// Verify endpoint exists
|
|
246
|
+
if (!config.endpoints.find((e) => e.id === endpointId)) {
|
|
247
|
+
throw new Error(`Endpoint not found: ${endpointId}`);
|
|
248
|
+
}
|
|
249
|
+
config.defaultEndpoint = endpointId;
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
delete config.defaultEndpoint;
|
|
253
|
+
}
|
|
254
|
+
saveConfig(baseDir, config);
|
|
255
|
+
}
|
|
256
|
+
// ===========================
|
|
257
|
+
// Cache Settings Management
|
|
258
|
+
// ===========================
|
|
259
|
+
/**
|
|
260
|
+
* Get global cache settings
|
|
261
|
+
*/
|
|
262
|
+
export function getGlobalCacheSettings(baseDir) {
|
|
263
|
+
const config = loadLiteLLMApiConfig(baseDir);
|
|
264
|
+
return config.globalCacheSettings;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Update global cache settings
|
|
268
|
+
*/
|
|
269
|
+
export function updateGlobalCacheSettings(baseDir, settings) {
|
|
270
|
+
const config = loadLiteLLMApiConfig(baseDir);
|
|
271
|
+
config.globalCacheSettings = {
|
|
272
|
+
...config.globalCacheSettings,
|
|
273
|
+
...settings,
|
|
274
|
+
};
|
|
275
|
+
saveConfig(baseDir, config);
|
|
276
|
+
}
|
|
277
|
+
// ===========================
|
|
278
|
+
// CodexLens Embedding Rotation Management
|
|
279
|
+
// ===========================
|
|
280
|
+
/**
|
|
281
|
+
* Get CodexLens embedding rotation config
|
|
282
|
+
*/
|
|
283
|
+
export function getCodexLensEmbeddingRotation(baseDir) {
|
|
284
|
+
const config = loadLiteLLMApiConfig(baseDir);
|
|
285
|
+
return config.codexlensEmbeddingRotation;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Update CodexLens embedding rotation config
|
|
289
|
+
* Also triggers sync to CodexLens settings.json
|
|
290
|
+
*/
|
|
291
|
+
export function updateCodexLensEmbeddingRotation(baseDir, rotationConfig) {
|
|
292
|
+
const config = loadLiteLLMApiConfig(baseDir);
|
|
293
|
+
if (rotationConfig) {
|
|
294
|
+
config.codexlensEmbeddingRotation = rotationConfig;
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
delete config.codexlensEmbeddingRotation;
|
|
298
|
+
}
|
|
299
|
+
saveConfig(baseDir, config);
|
|
300
|
+
// Auto-sync to CodexLens settings.json
|
|
301
|
+
const syncResult = syncCodexLensConfig(baseDir);
|
|
302
|
+
return { syncResult };
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Get all enabled embedding providers with their API keys for rotation
|
|
306
|
+
* This aggregates all providers that have embedding models configured
|
|
307
|
+
*/
|
|
308
|
+
export function getEmbeddingProvidersForRotation(baseDir) {
|
|
309
|
+
const config = loadLiteLLMApiConfig(baseDir);
|
|
310
|
+
const result = [];
|
|
311
|
+
for (const provider of config.providers) {
|
|
312
|
+
if (!provider.enabled)
|
|
313
|
+
continue;
|
|
314
|
+
// Check if provider has embedding models
|
|
315
|
+
const embeddingModels = (provider.embeddingModels || [])
|
|
316
|
+
.filter(m => m.enabled)
|
|
317
|
+
.map(m => ({
|
|
318
|
+
modelId: m.id,
|
|
319
|
+
modelName: m.name,
|
|
320
|
+
dimensions: m.capabilities?.embeddingDimension || 1536,
|
|
321
|
+
}));
|
|
322
|
+
if (embeddingModels.length === 0)
|
|
323
|
+
continue;
|
|
324
|
+
// Get API keys (single key or multiple from apiKeys array)
|
|
325
|
+
const apiKeys = [];
|
|
326
|
+
if (provider.apiKeys && provider.apiKeys.length > 0) {
|
|
327
|
+
// Use multi-key configuration
|
|
328
|
+
for (const keyEntry of provider.apiKeys) {
|
|
329
|
+
apiKeys.push({
|
|
330
|
+
keyId: keyEntry.id,
|
|
331
|
+
keyLabel: keyEntry.label || keyEntry.id,
|
|
332
|
+
enabled: keyEntry.enabled,
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
else if (provider.apiKey) {
|
|
337
|
+
// Single key fallback
|
|
338
|
+
apiKeys.push({
|
|
339
|
+
keyId: 'default',
|
|
340
|
+
keyLabel: 'Default Key',
|
|
341
|
+
enabled: true,
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
result.push({
|
|
345
|
+
providerId: provider.id,
|
|
346
|
+
providerName: provider.name,
|
|
347
|
+
apiBase: provider.apiBase || getDefaultApiBaseForType(provider.type),
|
|
348
|
+
embeddingModels,
|
|
349
|
+
apiKeys,
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
return result;
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Generate rotation endpoints for ccw_litellm
|
|
356
|
+
* Creates endpoint list from rotation config for parallel embedding
|
|
357
|
+
* Supports both legacy codexlensEmbeddingRotation and new embeddingPoolConfig
|
|
358
|
+
*/
|
|
359
|
+
export function generateRotationEndpoints(baseDir) {
|
|
360
|
+
const config = loadLiteLLMApiConfig(baseDir);
|
|
361
|
+
// Prefer embeddingPoolConfig, fallback to codexlensEmbeddingRotation for backward compatibility
|
|
362
|
+
const poolConfig = config.embeddingPoolConfig;
|
|
363
|
+
const rotationConfig = config.codexlensEmbeddingRotation;
|
|
364
|
+
// Check if new poolConfig is enabled
|
|
365
|
+
if (poolConfig && poolConfig.enabled) {
|
|
366
|
+
return generateEndpointsFromPool(baseDir, poolConfig, config);
|
|
367
|
+
}
|
|
368
|
+
// Fallback to legacy rotation config
|
|
369
|
+
if (rotationConfig && rotationConfig.enabled) {
|
|
370
|
+
return generateEndpointsFromLegacyRotation(baseDir, rotationConfig, config);
|
|
371
|
+
}
|
|
372
|
+
return [];
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Generate endpoints from new embeddingPoolConfig (with auto-discovery support)
|
|
376
|
+
*/
|
|
377
|
+
function generateEndpointsFromPool(baseDir, poolConfig, config) {
|
|
378
|
+
const endpoints = [];
|
|
379
|
+
if (poolConfig.autoDiscover) {
|
|
380
|
+
// Auto-discover all providers offering targetModel
|
|
381
|
+
const discovered = discoverProvidersForModel(baseDir, poolConfig.targetModel);
|
|
382
|
+
const excludedIds = new Set(poolConfig.excludedProviderIds || []);
|
|
383
|
+
for (const disc of discovered) {
|
|
384
|
+
// Skip excluded providers
|
|
385
|
+
if (excludedIds.has(disc.providerId))
|
|
386
|
+
continue;
|
|
387
|
+
// Find the provider config
|
|
388
|
+
const provider = config.providers.find(p => p.id === disc.providerId);
|
|
389
|
+
if (!provider || !provider.enabled)
|
|
390
|
+
continue;
|
|
391
|
+
// Find the embedding model
|
|
392
|
+
const embeddingModel = provider.embeddingModels?.find(m => m.id === disc.modelId);
|
|
393
|
+
if (!embeddingModel || !embeddingModel.enabled)
|
|
394
|
+
continue;
|
|
395
|
+
// Get API base (model-specific or provider default)
|
|
396
|
+
const apiBase = embeddingModel.endpointSettings?.baseUrl ||
|
|
397
|
+
provider.apiBase ||
|
|
398
|
+
getDefaultApiBaseForType(provider.type);
|
|
399
|
+
// Get API keys to use
|
|
400
|
+
let keysToUse = [];
|
|
401
|
+
if (provider.apiKeys && provider.apiKeys.length > 0) {
|
|
402
|
+
// Use all enabled keys
|
|
403
|
+
keysToUse = provider.apiKeys
|
|
404
|
+
.filter(k => k.enabled)
|
|
405
|
+
.map(k => ({ id: k.id, key: k.key, label: k.label || k.id }));
|
|
406
|
+
}
|
|
407
|
+
else if (provider.apiKey) {
|
|
408
|
+
// Single key fallback
|
|
409
|
+
keysToUse = [{ id: 'default', key: provider.apiKey, label: 'Default' }];
|
|
410
|
+
}
|
|
411
|
+
// Create endpoint for each key
|
|
412
|
+
for (const keyInfo of keysToUse) {
|
|
413
|
+
endpoints.push({
|
|
414
|
+
name: `${provider.name}-${keyInfo.label}`,
|
|
415
|
+
api_key: resolveEnvVar(keyInfo.key),
|
|
416
|
+
api_base: apiBase,
|
|
417
|
+
model: embeddingModel.name,
|
|
418
|
+
weight: 1.0, // Default weight for auto-discovered providers
|
|
419
|
+
max_concurrent: poolConfig.defaultMaxConcurrentPerKey,
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
return endpoints;
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Generate endpoints from legacy codexlensEmbeddingRotation config
|
|
428
|
+
*/
|
|
429
|
+
function generateEndpointsFromLegacyRotation(baseDir, rotationConfig, config) {
|
|
430
|
+
const endpoints = [];
|
|
431
|
+
for (const rotationProvider of rotationConfig.providers) {
|
|
432
|
+
if (!rotationProvider.enabled)
|
|
433
|
+
continue;
|
|
434
|
+
// Find the provider config
|
|
435
|
+
const provider = config.providers.find(p => p.id === rotationProvider.providerId);
|
|
436
|
+
if (!provider || !provider.enabled)
|
|
437
|
+
continue;
|
|
438
|
+
// Find the embedding model
|
|
439
|
+
const embeddingModel = provider.embeddingModels?.find(m => m.id === rotationProvider.modelId);
|
|
440
|
+
if (!embeddingModel || !embeddingModel.enabled)
|
|
441
|
+
continue;
|
|
442
|
+
// Get API base (model-specific or provider default)
|
|
443
|
+
const apiBase = embeddingModel.endpointSettings?.baseUrl ||
|
|
444
|
+
provider.apiBase ||
|
|
445
|
+
getDefaultApiBaseForType(provider.type);
|
|
446
|
+
// Get API keys to use
|
|
447
|
+
let keysToUse = [];
|
|
448
|
+
if (provider.apiKeys && provider.apiKeys.length > 0) {
|
|
449
|
+
if (rotationProvider.useAllKeys) {
|
|
450
|
+
// Use all enabled keys
|
|
451
|
+
keysToUse = provider.apiKeys
|
|
452
|
+
.filter(k => k.enabled)
|
|
453
|
+
.map(k => ({ id: k.id, key: k.key, label: k.label || k.id }));
|
|
454
|
+
}
|
|
455
|
+
else if (rotationProvider.selectedKeyIds && rotationProvider.selectedKeyIds.length > 0) {
|
|
456
|
+
// Use only selected keys
|
|
457
|
+
keysToUse = provider.apiKeys
|
|
458
|
+
.filter(k => k.enabled && rotationProvider.selectedKeyIds.includes(k.id))
|
|
459
|
+
.map(k => ({ id: k.id, key: k.key, label: k.label || k.id }));
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
else if (provider.apiKey) {
|
|
463
|
+
// Single key fallback
|
|
464
|
+
keysToUse = [{ id: 'default', key: provider.apiKey, label: 'Default' }];
|
|
465
|
+
}
|
|
466
|
+
// Create endpoint for each key
|
|
467
|
+
for (const keyInfo of keysToUse) {
|
|
468
|
+
endpoints.push({
|
|
469
|
+
name: `${provider.name}-${keyInfo.label}`,
|
|
470
|
+
api_key: resolveEnvVar(keyInfo.key),
|
|
471
|
+
api_base: apiBase,
|
|
472
|
+
model: embeddingModel.name,
|
|
473
|
+
weight: rotationProvider.weight,
|
|
474
|
+
max_concurrent: rotationProvider.maxConcurrentPerKey,
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
return endpoints;
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Sync CodexLens settings with CCW API config
|
|
482
|
+
* Writes rotation endpoints to ~/.codexlens/settings.json
|
|
483
|
+
* This enables the Python backend to use UI-configured rotation
|
|
484
|
+
* Supports both new embeddingPoolConfig and legacy codexlensEmbeddingRotation
|
|
485
|
+
*/
|
|
486
|
+
export function syncCodexLensConfig(baseDir) {
|
|
487
|
+
try {
|
|
488
|
+
const config = loadLiteLLMApiConfig(baseDir);
|
|
489
|
+
// Prefer embeddingPoolConfig, fallback to codexlensEmbeddingRotation
|
|
490
|
+
const poolConfig = config.embeddingPoolConfig;
|
|
491
|
+
const rotationConfig = config.codexlensEmbeddingRotation;
|
|
492
|
+
// Get CodexLens settings path
|
|
493
|
+
const codexlensDir = join(homedir(), '.codexlens');
|
|
494
|
+
const settingsPath = join(codexlensDir, 'settings.json');
|
|
495
|
+
// Ensure directory exists
|
|
496
|
+
if (!existsSync(codexlensDir)) {
|
|
497
|
+
mkdirSync(codexlensDir, { recursive: true });
|
|
498
|
+
}
|
|
499
|
+
// Load existing settings or create new
|
|
500
|
+
let settings = {};
|
|
501
|
+
if (existsSync(settingsPath)) {
|
|
502
|
+
try {
|
|
503
|
+
settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
|
|
504
|
+
}
|
|
505
|
+
catch {
|
|
506
|
+
settings = {};
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
// Check if either config is enabled
|
|
510
|
+
const isPoolEnabled = poolConfig && poolConfig.enabled;
|
|
511
|
+
const isRotationEnabled = rotationConfig && rotationConfig.enabled;
|
|
512
|
+
// If neither is enabled, remove rotation endpoints and return
|
|
513
|
+
if (!isPoolEnabled && !isRotationEnabled) {
|
|
514
|
+
if (settings.litellm_rotation_endpoints) {
|
|
515
|
+
delete settings.litellm_rotation_endpoints;
|
|
516
|
+
delete settings.litellm_rotation_strategy;
|
|
517
|
+
delete settings.litellm_rotation_cooldown;
|
|
518
|
+
delete settings.litellm_target_model;
|
|
519
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf-8');
|
|
520
|
+
}
|
|
521
|
+
return { success: true, message: 'Rotation disabled, cleared endpoints', endpointCount: 0 };
|
|
522
|
+
}
|
|
523
|
+
// Generate rotation endpoints (function handles priority internally)
|
|
524
|
+
const endpoints = generateRotationEndpoints(baseDir);
|
|
525
|
+
if (endpoints.length === 0) {
|
|
526
|
+
return { success: false, message: 'No valid endpoints generated from rotation config' };
|
|
527
|
+
}
|
|
528
|
+
// Update settings with rotation config (use poolConfig if available)
|
|
529
|
+
settings.litellm_rotation_endpoints = endpoints;
|
|
530
|
+
if (isPoolEnabled) {
|
|
531
|
+
settings.litellm_rotation_strategy = poolConfig.strategy;
|
|
532
|
+
settings.litellm_rotation_cooldown = poolConfig.defaultCooldown;
|
|
533
|
+
settings.litellm_target_model = poolConfig.targetModel;
|
|
534
|
+
}
|
|
535
|
+
else {
|
|
536
|
+
settings.litellm_rotation_strategy = rotationConfig.strategy;
|
|
537
|
+
settings.litellm_rotation_cooldown = rotationConfig.defaultCooldown;
|
|
538
|
+
settings.litellm_target_model = rotationConfig.targetModel;
|
|
539
|
+
}
|
|
540
|
+
// Write updated settings
|
|
541
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf-8');
|
|
542
|
+
return {
|
|
543
|
+
success: true,
|
|
544
|
+
message: `Synced ${endpoints.length} rotation endpoints to CodexLens`,
|
|
545
|
+
endpointCount: endpoints.length,
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
catch (error) {
|
|
549
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
550
|
+
console.error('[LiteLLM Config] Failed to sync CodexLens config:', errorMessage);
|
|
551
|
+
return { success: false, message: `Sync failed: ${errorMessage}` };
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
// ===========================
|
|
555
|
+
// Embedding Pool Management (Generic, with Auto-Discovery)
|
|
556
|
+
// ===========================
|
|
557
|
+
/**
|
|
558
|
+
* Get embedding pool config
|
|
559
|
+
*/
|
|
560
|
+
export function getEmbeddingPoolConfig(baseDir) {
|
|
561
|
+
const config = loadLiteLLMApiConfig(baseDir);
|
|
562
|
+
return config.embeddingPoolConfig;
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* Update embedding pool config
|
|
566
|
+
* Also triggers sync to CodexLens settings.json if enabled
|
|
567
|
+
*/
|
|
568
|
+
export function updateEmbeddingPoolConfig(baseDir, poolConfig) {
|
|
569
|
+
const config = loadLiteLLMApiConfig(baseDir);
|
|
570
|
+
if (poolConfig) {
|
|
571
|
+
config.embeddingPoolConfig = poolConfig;
|
|
572
|
+
}
|
|
573
|
+
else {
|
|
574
|
+
delete config.embeddingPoolConfig;
|
|
575
|
+
}
|
|
576
|
+
saveConfig(baseDir, config);
|
|
577
|
+
// Auto-sync to CodexLens settings.json
|
|
578
|
+
const syncResult = syncCodexLensConfig(baseDir);
|
|
579
|
+
return { syncResult };
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* Discover all providers that offer a specific embedding model
|
|
583
|
+
* Returns list of {providerId, providerName, modelId, modelName, apiKeys[]}
|
|
584
|
+
*/
|
|
585
|
+
export function discoverProvidersForModel(baseDir, targetModel) {
|
|
586
|
+
const config = loadLiteLLMApiConfig(baseDir);
|
|
587
|
+
const result = [];
|
|
588
|
+
for (const provider of config.providers) {
|
|
589
|
+
if (!provider.enabled)
|
|
590
|
+
continue;
|
|
591
|
+
// Check if provider has embedding models matching targetModel
|
|
592
|
+
const matchingModels = (provider.embeddingModels || []).filter(m => m.enabled && (m.id === targetModel || m.name === targetModel));
|
|
593
|
+
if (matchingModels.length === 0)
|
|
594
|
+
continue;
|
|
595
|
+
// Get API keys (single key or multiple from apiKeys array)
|
|
596
|
+
const apiKeys = [];
|
|
597
|
+
if (provider.apiKeys && provider.apiKeys.length > 0) {
|
|
598
|
+
// Use multi-key configuration
|
|
599
|
+
for (const keyEntry of provider.apiKeys) {
|
|
600
|
+
apiKeys.push({
|
|
601
|
+
keyId: keyEntry.id,
|
|
602
|
+
keyLabel: keyEntry.label || keyEntry.id,
|
|
603
|
+
enabled: keyEntry.enabled,
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
else if (provider.apiKey) {
|
|
608
|
+
// Single key fallback
|
|
609
|
+
apiKeys.push({
|
|
610
|
+
keyId: 'default',
|
|
611
|
+
keyLabel: 'Default Key',
|
|
612
|
+
enabled: true,
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
// Add each matching model
|
|
616
|
+
for (const model of matchingModels) {
|
|
617
|
+
result.push({
|
|
618
|
+
providerId: provider.id,
|
|
619
|
+
providerName: provider.name,
|
|
620
|
+
modelId: model.id,
|
|
621
|
+
modelName: model.name,
|
|
622
|
+
apiKeys,
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
return result;
|
|
627
|
+
}
|
|
628
|
+
// ===========================
|
|
629
|
+
// YAML Config Generation for ccw_litellm
|
|
630
|
+
// ===========================
|
|
631
|
+
/**
|
|
632
|
+
* Convert UI config (JSON) to ccw_litellm config (YAML format object)
|
|
633
|
+
* This allows CodexLens to use UI-configured providers
|
|
634
|
+
*/
|
|
635
|
+
export function generateLiteLLMYamlConfig(baseDir) {
|
|
636
|
+
const config = loadLiteLLMApiConfig(baseDir);
|
|
637
|
+
// Build providers object
|
|
638
|
+
const providers = {};
|
|
639
|
+
for (const provider of config.providers) {
|
|
640
|
+
if (!provider.enabled)
|
|
641
|
+
continue;
|
|
642
|
+
providers[provider.id] = {
|
|
643
|
+
api_key: provider.apiKey,
|
|
644
|
+
api_base: provider.apiBase || getDefaultApiBaseForType(provider.type),
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
// Build embedding_models object from providers' embeddingModels
|
|
648
|
+
const embeddingModels = {};
|
|
649
|
+
for (const provider of config.providers) {
|
|
650
|
+
if (!provider.enabled || !provider.embeddingModels)
|
|
651
|
+
continue;
|
|
652
|
+
for (const model of provider.embeddingModels) {
|
|
653
|
+
if (!model.enabled)
|
|
654
|
+
continue;
|
|
655
|
+
embeddingModels[model.id] = {
|
|
656
|
+
provider: provider.id,
|
|
657
|
+
model: model.name,
|
|
658
|
+
dimensions: model.capabilities?.embeddingDimension || 1536,
|
|
659
|
+
// Use model-specific base URL if set, otherwise use provider's
|
|
660
|
+
...(model.endpointSettings?.baseUrl && {
|
|
661
|
+
api_base: model.endpointSettings.baseUrl,
|
|
662
|
+
}),
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
// Build llm_models object from providers' llmModels
|
|
667
|
+
const llmModels = {};
|
|
668
|
+
for (const provider of config.providers) {
|
|
669
|
+
if (!provider.enabled || !provider.llmModels)
|
|
670
|
+
continue;
|
|
671
|
+
for (const model of provider.llmModels) {
|
|
672
|
+
if (!model.enabled)
|
|
673
|
+
continue;
|
|
674
|
+
llmModels[model.id] = {
|
|
675
|
+
provider: provider.id,
|
|
676
|
+
model: model.name,
|
|
677
|
+
...(model.endpointSettings?.baseUrl && {
|
|
678
|
+
api_base: model.endpointSettings.baseUrl,
|
|
679
|
+
}),
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
// Find default provider
|
|
684
|
+
const defaultProvider = config.providers.find((p) => p.enabled)?.id || 'openai';
|
|
685
|
+
return {
|
|
686
|
+
version: 1,
|
|
687
|
+
default_provider: defaultProvider,
|
|
688
|
+
providers,
|
|
689
|
+
embedding_models: Object.keys(embeddingModels).length > 0 ? embeddingModels : {
|
|
690
|
+
default: {
|
|
691
|
+
provider: defaultProvider,
|
|
692
|
+
model: 'text-embedding-3-small',
|
|
693
|
+
dimensions: 1536,
|
|
694
|
+
},
|
|
695
|
+
},
|
|
696
|
+
llm_models: Object.keys(llmModels).length > 0 ? llmModels : {
|
|
697
|
+
default: {
|
|
698
|
+
provider: defaultProvider,
|
|
699
|
+
model: 'gpt-4',
|
|
700
|
+
},
|
|
701
|
+
},
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Get default API base URL for provider type
|
|
706
|
+
*/
|
|
707
|
+
function getDefaultApiBaseForType(type) {
|
|
708
|
+
const defaults = {
|
|
709
|
+
openai: 'https://api.openai.com/v1',
|
|
710
|
+
anthropic: 'https://api.anthropic.com/v1',
|
|
711
|
+
custom: 'https://api.example.com/v1',
|
|
712
|
+
};
|
|
713
|
+
return defaults[type] || 'https://api.openai.com/v1';
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* Save ccw_litellm YAML config file
|
|
717
|
+
* Writes to ~/.ccw/config/litellm-config.yaml
|
|
718
|
+
*/
|
|
719
|
+
export function saveLiteLLMYamlConfig(baseDir) {
|
|
720
|
+
const yamlConfig = generateLiteLLMYamlConfig(baseDir);
|
|
721
|
+
// Convert to YAML manually (simple format)
|
|
722
|
+
const yamlContent = objectToYaml(yamlConfig);
|
|
723
|
+
// Write to ~/.ccw/config/litellm-config.yaml
|
|
724
|
+
const homePath = process.env.HOME || process.env.USERPROFILE || '';
|
|
725
|
+
const yamlPath = join(homePath, '.ccw', 'config', 'litellm-config.yaml');
|
|
726
|
+
// Ensure directory exists
|
|
727
|
+
const configDir = join(homePath, '.ccw', 'config');
|
|
728
|
+
ensureStorageDir(configDir);
|
|
729
|
+
writeFileSync(yamlPath, yamlContent, 'utf-8');
|
|
730
|
+
return yamlPath;
|
|
731
|
+
}
|
|
732
|
+
/**
|
|
733
|
+
* Simple object to YAML converter
|
|
734
|
+
*/
|
|
735
|
+
function objectToYaml(obj, indent = 0) {
|
|
736
|
+
const spaces = ' '.repeat(indent);
|
|
737
|
+
if (obj === null || obj === undefined) {
|
|
738
|
+
return 'null';
|
|
739
|
+
}
|
|
740
|
+
if (typeof obj === 'string') {
|
|
741
|
+
// Quote strings that contain special characters
|
|
742
|
+
if (obj.includes(':') || obj.includes('#') || obj.includes('\n') || obj.startsWith('$')) {
|
|
743
|
+
return `"${obj.replace(/"/g, '\\"')}"`;
|
|
744
|
+
}
|
|
745
|
+
return obj;
|
|
746
|
+
}
|
|
747
|
+
if (typeof obj === 'number' || typeof obj === 'boolean') {
|
|
748
|
+
return String(obj);
|
|
749
|
+
}
|
|
750
|
+
if (Array.isArray(obj)) {
|
|
751
|
+
if (obj.length === 0)
|
|
752
|
+
return '[]';
|
|
753
|
+
return obj.map((item) => `${spaces}- ${objectToYaml(item, indent + 1).trimStart()}`).join('\n');
|
|
754
|
+
}
|
|
755
|
+
if (typeof obj === 'object') {
|
|
756
|
+
const entries = Object.entries(obj);
|
|
757
|
+
if (entries.length === 0)
|
|
758
|
+
return '{}';
|
|
759
|
+
return entries
|
|
760
|
+
.map(([key, value]) => {
|
|
761
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
762
|
+
return `${spaces}${key}:\n${objectToYaml(value, indent + 1)}`;
|
|
763
|
+
}
|
|
764
|
+
return `${spaces}${key}: ${objectToYaml(value, indent)}`;
|
|
765
|
+
})
|
|
766
|
+
.join('\n');
|
|
767
|
+
}
|
|
768
|
+
return String(obj);
|
|
769
|
+
}
|
|
770
|
+
//# sourceMappingURL=litellm-api-config-manager.js.map
|