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,930 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* LiteLLM API Routes Module
|
|
4
|
+
* Handles LiteLLM provider management, endpoint configuration, and cache management
|
|
5
|
+
*/
|
|
6
|
+
import type { IncomingMessage, ServerResponse } from 'http';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
import { dirname, join as pathJoin } from 'path';
|
|
9
|
+
|
|
10
|
+
// Get current module path for package-relative lookups
|
|
11
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
+
const __dirname = dirname(__filename);
|
|
13
|
+
// Package root: routes -> core -> src -> ccw -> package root
|
|
14
|
+
const PACKAGE_ROOT = pathJoin(__dirname, '..', '..', '..', '..');
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
getAllProviders,
|
|
18
|
+
getProvider,
|
|
19
|
+
addProvider,
|
|
20
|
+
updateProvider,
|
|
21
|
+
deleteProvider,
|
|
22
|
+
getAllEndpoints,
|
|
23
|
+
getEndpoint,
|
|
24
|
+
addEndpoint,
|
|
25
|
+
updateEndpoint,
|
|
26
|
+
deleteEndpoint,
|
|
27
|
+
getDefaultEndpoint,
|
|
28
|
+
setDefaultEndpoint,
|
|
29
|
+
getGlobalCacheSettings,
|
|
30
|
+
updateGlobalCacheSettings,
|
|
31
|
+
loadLiteLLMApiConfig,
|
|
32
|
+
saveLiteLLMYamlConfig,
|
|
33
|
+
generateLiteLLMYamlConfig,
|
|
34
|
+
getCodexLensEmbeddingRotation,
|
|
35
|
+
updateCodexLensEmbeddingRotation,
|
|
36
|
+
getEmbeddingProvidersForRotation,
|
|
37
|
+
generateRotationEndpoints,
|
|
38
|
+
syncCodexLensConfig,
|
|
39
|
+
getEmbeddingPoolConfig,
|
|
40
|
+
updateEmbeddingPoolConfig,
|
|
41
|
+
discoverProvidersForModel,
|
|
42
|
+
type ProviderCredential,
|
|
43
|
+
type CustomEndpoint,
|
|
44
|
+
type ProviderType,
|
|
45
|
+
type CodexLensEmbeddingRotation,
|
|
46
|
+
type EmbeddingPoolConfig,
|
|
47
|
+
} from '../../config/litellm-api-config-manager.js';
|
|
48
|
+
import { getContextCacheStore } from '../../tools/context-cache-store.js';
|
|
49
|
+
import { getLiteLLMClient } from '../../tools/litellm-client.js';
|
|
50
|
+
|
|
51
|
+
// Cache for ccw-litellm status check
|
|
52
|
+
let ccwLitellmStatusCache: {
|
|
53
|
+
data: { installed: boolean; version?: string; error?: string } | null;
|
|
54
|
+
timestamp: number;
|
|
55
|
+
ttl: number;
|
|
56
|
+
} = {
|
|
57
|
+
data: null,
|
|
58
|
+
timestamp: 0,
|
|
59
|
+
ttl: 5 * 60 * 1000, // 5 minutes
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// Clear cache (call after install)
|
|
63
|
+
export function clearCcwLitellmStatusCache() {
|
|
64
|
+
ccwLitellmStatusCache.data = null;
|
|
65
|
+
ccwLitellmStatusCache.timestamp = 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface RouteContext {
|
|
69
|
+
pathname: string;
|
|
70
|
+
url: URL;
|
|
71
|
+
req: IncomingMessage;
|
|
72
|
+
res: ServerResponse;
|
|
73
|
+
initialPath: string;
|
|
74
|
+
handlePostRequest: (req: IncomingMessage, res: ServerResponse, handler: (body: unknown) => Promise<any>) => void;
|
|
75
|
+
broadcastToClients: (data: unknown) => void;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ===========================
|
|
79
|
+
// Model Information
|
|
80
|
+
// ===========================
|
|
81
|
+
|
|
82
|
+
interface ModelInfo {
|
|
83
|
+
id: string;
|
|
84
|
+
name: string;
|
|
85
|
+
provider: ProviderType;
|
|
86
|
+
description?: string;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const PROVIDER_MODELS: Record<ProviderType, ModelInfo[]> = {
|
|
90
|
+
openai: [
|
|
91
|
+
{ id: 'gpt-4-turbo', name: 'GPT-4 Turbo', provider: 'openai', description: '128K context' },
|
|
92
|
+
{ id: 'gpt-4', name: 'GPT-4', provider: 'openai', description: '8K context' },
|
|
93
|
+
{ id: 'gpt-3.5-turbo', name: 'GPT-3.5 Turbo', provider: 'openai', description: '16K context' },
|
|
94
|
+
],
|
|
95
|
+
anthropic: [
|
|
96
|
+
{ id: 'claude-3-opus-20240229', name: 'Claude 3 Opus', provider: 'anthropic', description: '200K context' },
|
|
97
|
+
{ id: 'claude-3-sonnet-20240229', name: 'Claude 3 Sonnet', provider: 'anthropic', description: '200K context' },
|
|
98
|
+
{ id: 'claude-3-haiku-20240307', name: 'Claude 3 Haiku', provider: 'anthropic', description: '200K context' },
|
|
99
|
+
],
|
|
100
|
+
google: [
|
|
101
|
+
{ id: 'gemini-pro', name: 'Gemini Pro', provider: 'google', description: '32K context' },
|
|
102
|
+
{ id: 'gemini-pro-vision', name: 'Gemini Pro Vision', provider: 'google', description: '16K context' },
|
|
103
|
+
],
|
|
104
|
+
ollama: [
|
|
105
|
+
{ id: 'llama2', name: 'Llama 2', provider: 'ollama', description: 'Local model' },
|
|
106
|
+
{ id: 'mistral', name: 'Mistral', provider: 'ollama', description: 'Local model' },
|
|
107
|
+
],
|
|
108
|
+
azure: [],
|
|
109
|
+
mistral: [
|
|
110
|
+
{ id: 'mistral-large-latest', name: 'Mistral Large', provider: 'mistral', description: '32K context' },
|
|
111
|
+
{ id: 'mistral-medium-latest', name: 'Mistral Medium', provider: 'mistral', description: '32K context' },
|
|
112
|
+
],
|
|
113
|
+
deepseek: [
|
|
114
|
+
{ id: 'deepseek-chat', name: 'DeepSeek Chat', provider: 'deepseek', description: '64K context' },
|
|
115
|
+
{ id: 'deepseek-coder', name: 'DeepSeek Coder', provider: 'deepseek', description: '64K context' },
|
|
116
|
+
],
|
|
117
|
+
custom: [],
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Handle LiteLLM API routes
|
|
122
|
+
* @returns true if route was handled, false otherwise
|
|
123
|
+
*/
|
|
124
|
+
export async function handleLiteLLMApiRoutes(ctx: RouteContext): Promise<boolean> {
|
|
125
|
+
const { pathname, url, req, res, initialPath, handlePostRequest, broadcastToClients } = ctx;
|
|
126
|
+
|
|
127
|
+
// ===========================
|
|
128
|
+
// Provider Management Routes
|
|
129
|
+
// ===========================
|
|
130
|
+
|
|
131
|
+
// GET /api/litellm-api/providers - List all providers
|
|
132
|
+
if (pathname === '/api/litellm-api/providers' && req.method === 'GET') {
|
|
133
|
+
try {
|
|
134
|
+
const providers = getAllProviders(initialPath);
|
|
135
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
136
|
+
res.end(JSON.stringify({ providers, count: providers.length }));
|
|
137
|
+
} catch (err) {
|
|
138
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
139
|
+
res.end(JSON.stringify({ error: (err as Error).message }));
|
|
140
|
+
}
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// POST /api/litellm-api/providers - Create provider
|
|
145
|
+
if (pathname === '/api/litellm-api/providers' && req.method === 'POST') {
|
|
146
|
+
handlePostRequest(req, res, async (body: unknown) => {
|
|
147
|
+
const providerData = body as Omit<ProviderCredential, 'id' | 'createdAt' | 'updatedAt'>;
|
|
148
|
+
|
|
149
|
+
if (!providerData.name || !providerData.type || !providerData.apiKey) {
|
|
150
|
+
return { error: 'Provider name, type, and apiKey are required', status: 400 };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
const provider = addProvider(initialPath, providerData);
|
|
155
|
+
|
|
156
|
+
broadcastToClients({
|
|
157
|
+
type: 'LITELLM_PROVIDER_CREATED',
|
|
158
|
+
payload: { provider, timestamp: new Date().toISOString() }
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
return { success: true, provider };
|
|
162
|
+
} catch (err) {
|
|
163
|
+
return { error: (err as Error).message, status: 500 };
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// GET /api/litellm-api/providers/:id - Get provider by ID
|
|
170
|
+
const providerGetMatch = pathname.match(/^\/api\/litellm-api\/providers\/([^/]+)$/);
|
|
171
|
+
if (providerGetMatch && req.method === 'GET') {
|
|
172
|
+
const providerId = providerGetMatch[1];
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
const provider = getProvider(initialPath, providerId);
|
|
176
|
+
if (!provider) {
|
|
177
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
178
|
+
res.end(JSON.stringify({ error: 'Provider not found' }));
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
183
|
+
res.end(JSON.stringify(provider));
|
|
184
|
+
} catch (err) {
|
|
185
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
186
|
+
res.end(JSON.stringify({ error: (err as Error).message }));
|
|
187
|
+
}
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// PUT /api/litellm-api/providers/:id - Update provider
|
|
192
|
+
const providerUpdateMatch = pathname.match(/^\/api\/litellm-api\/providers\/([^/]+)$/);
|
|
193
|
+
if (providerUpdateMatch && req.method === 'PUT') {
|
|
194
|
+
const providerId = providerUpdateMatch[1];
|
|
195
|
+
|
|
196
|
+
handlePostRequest(req, res, async (body: unknown) => {
|
|
197
|
+
const updates = body as Partial<Omit<ProviderCredential, 'id' | 'createdAt' | 'updatedAt'>>;
|
|
198
|
+
|
|
199
|
+
try {
|
|
200
|
+
const provider = updateProvider(initialPath, providerId, updates);
|
|
201
|
+
|
|
202
|
+
broadcastToClients({
|
|
203
|
+
type: 'LITELLM_PROVIDER_UPDATED',
|
|
204
|
+
payload: { provider, timestamp: new Date().toISOString() }
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
return { success: true, provider };
|
|
208
|
+
} catch (err) {
|
|
209
|
+
return { error: (err as Error).message, status: 404 };
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// DELETE /api/litellm-api/providers/:id - Delete provider
|
|
216
|
+
const providerDeleteMatch = pathname.match(/^\/api\/litellm-api\/providers\/([^/]+)$/);
|
|
217
|
+
if (providerDeleteMatch && req.method === 'DELETE') {
|
|
218
|
+
const providerId = providerDeleteMatch[1];
|
|
219
|
+
|
|
220
|
+
try {
|
|
221
|
+
const success = deleteProvider(initialPath, providerId);
|
|
222
|
+
|
|
223
|
+
if (!success) {
|
|
224
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
225
|
+
res.end(JSON.stringify({ error: 'Provider not found' }));
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
broadcastToClients({
|
|
230
|
+
type: 'LITELLM_PROVIDER_DELETED',
|
|
231
|
+
payload: { providerId, timestamp: new Date().toISOString() }
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
235
|
+
res.end(JSON.stringify({ success: true, message: 'Provider deleted' }));
|
|
236
|
+
} catch (err) {
|
|
237
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
238
|
+
res.end(JSON.stringify({ error: (err as Error).message }));
|
|
239
|
+
}
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// POST /api/litellm-api/providers/:id/test - Test provider connection
|
|
244
|
+
const providerTestMatch = pathname.match(/^\/api\/litellm-api\/providers\/([^/]+)\/test$/);
|
|
245
|
+
if (providerTestMatch && req.method === 'POST') {
|
|
246
|
+
const providerId = providerTestMatch[1];
|
|
247
|
+
|
|
248
|
+
try {
|
|
249
|
+
const provider = getProvider(initialPath, providerId);
|
|
250
|
+
|
|
251
|
+
if (!provider) {
|
|
252
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
253
|
+
res.end(JSON.stringify({ success: false, error: 'Provider not found' }));
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (!provider.enabled) {
|
|
258
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
259
|
+
res.end(JSON.stringify({ success: false, error: 'Provider is disabled' }));
|
|
260
|
+
return true;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Test connection using litellm client
|
|
264
|
+
const client = getLiteLLMClient();
|
|
265
|
+
const available = await client.isAvailable();
|
|
266
|
+
|
|
267
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
268
|
+
res.end(JSON.stringify({ success: available, provider: provider.type }));
|
|
269
|
+
} catch (err) {
|
|
270
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
271
|
+
res.end(JSON.stringify({ success: false, error: (err as Error).message }));
|
|
272
|
+
}
|
|
273
|
+
return true;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// ===========================
|
|
277
|
+
// Endpoint Management Routes
|
|
278
|
+
// ===========================
|
|
279
|
+
|
|
280
|
+
// GET /api/litellm-api/endpoints - List all endpoints
|
|
281
|
+
if (pathname === '/api/litellm-api/endpoints' && req.method === 'GET') {
|
|
282
|
+
try {
|
|
283
|
+
const endpoints = getAllEndpoints(initialPath);
|
|
284
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
285
|
+
res.end(JSON.stringify({ endpoints, count: endpoints.length }));
|
|
286
|
+
} catch (err) {
|
|
287
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
288
|
+
res.end(JSON.stringify({ error: (err as Error).message }));
|
|
289
|
+
}
|
|
290
|
+
return true;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// POST /api/litellm-api/endpoints - Create endpoint
|
|
294
|
+
if (pathname === '/api/litellm-api/endpoints' && req.method === 'POST') {
|
|
295
|
+
handlePostRequest(req, res, async (body: unknown) => {
|
|
296
|
+
const endpointData = body as Omit<CustomEndpoint, 'createdAt' | 'updatedAt'>;
|
|
297
|
+
|
|
298
|
+
if (!endpointData.id || !endpointData.name || !endpointData.providerId || !endpointData.model) {
|
|
299
|
+
return { error: 'Endpoint id, name, providerId, and model are required', status: 400 };
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
try {
|
|
303
|
+
const endpoint = addEndpoint(initialPath, endpointData);
|
|
304
|
+
|
|
305
|
+
broadcastToClients({
|
|
306
|
+
type: 'LITELLM_ENDPOINT_CREATED',
|
|
307
|
+
payload: { endpoint, timestamp: new Date().toISOString() }
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
return { success: true, endpoint };
|
|
311
|
+
} catch (err) {
|
|
312
|
+
return { error: (err as Error).message, status: 500 };
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
return true;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// GET /api/litellm-api/endpoints/:id - Get endpoint by ID
|
|
319
|
+
const endpointGetMatch = pathname.match(/^\/api\/litellm-api\/endpoints\/([^/]+)$/);
|
|
320
|
+
if (endpointGetMatch && req.method === 'GET') {
|
|
321
|
+
const endpointId = endpointGetMatch[1];
|
|
322
|
+
|
|
323
|
+
try {
|
|
324
|
+
const endpoint = getEndpoint(initialPath, endpointId);
|
|
325
|
+
if (!endpoint) {
|
|
326
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
327
|
+
res.end(JSON.stringify({ error: 'Endpoint not found' }));
|
|
328
|
+
return true;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
332
|
+
res.end(JSON.stringify(endpoint));
|
|
333
|
+
} catch (err) {
|
|
334
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
335
|
+
res.end(JSON.stringify({ error: (err as Error).message }));
|
|
336
|
+
}
|
|
337
|
+
return true;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// PUT /api/litellm-api/endpoints/:id - Update endpoint
|
|
341
|
+
const endpointUpdateMatch = pathname.match(/^\/api\/litellm-api\/endpoints\/([^/]+)$/);
|
|
342
|
+
if (endpointUpdateMatch && req.method === 'PUT') {
|
|
343
|
+
const endpointId = endpointUpdateMatch[1];
|
|
344
|
+
|
|
345
|
+
handlePostRequest(req, res, async (body: unknown) => {
|
|
346
|
+
const updates = body as Partial<Omit<CustomEndpoint, 'id' | 'createdAt' | 'updatedAt'>>;
|
|
347
|
+
|
|
348
|
+
try {
|
|
349
|
+
const endpoint = updateEndpoint(initialPath, endpointId, updates);
|
|
350
|
+
|
|
351
|
+
broadcastToClients({
|
|
352
|
+
type: 'LITELLM_ENDPOINT_UPDATED',
|
|
353
|
+
payload: { endpoint, timestamp: new Date().toISOString() }
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
return { success: true, endpoint };
|
|
357
|
+
} catch (err) {
|
|
358
|
+
return { error: (err as Error).message, status: 404 };
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
return true;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// DELETE /api/litellm-api/endpoints/:id - Delete endpoint
|
|
365
|
+
const endpointDeleteMatch = pathname.match(/^\/api\/litellm-api\/endpoints\/([^/]+)$/);
|
|
366
|
+
if (endpointDeleteMatch && req.method === 'DELETE') {
|
|
367
|
+
const endpointId = endpointDeleteMatch[1];
|
|
368
|
+
|
|
369
|
+
try {
|
|
370
|
+
const success = deleteEndpoint(initialPath, endpointId);
|
|
371
|
+
|
|
372
|
+
if (!success) {
|
|
373
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
374
|
+
res.end(JSON.stringify({ error: 'Endpoint not found' }));
|
|
375
|
+
return true;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
broadcastToClients({
|
|
379
|
+
type: 'LITELLM_ENDPOINT_DELETED',
|
|
380
|
+
payload: { endpointId, timestamp: new Date().toISOString() }
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
384
|
+
res.end(JSON.stringify({ success: true, message: 'Endpoint deleted' }));
|
|
385
|
+
} catch (err) {
|
|
386
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
387
|
+
res.end(JSON.stringify({ error: (err as Error).message }));
|
|
388
|
+
}
|
|
389
|
+
return true;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// ===========================
|
|
393
|
+
// Model Discovery Routes
|
|
394
|
+
// ===========================
|
|
395
|
+
|
|
396
|
+
// GET /api/litellm-api/models/:providerType - Get available models for provider type
|
|
397
|
+
const modelsMatch = pathname.match(/^\/api\/litellm-api\/models\/([^/]+)$/);
|
|
398
|
+
if (modelsMatch && req.method === 'GET') {
|
|
399
|
+
const providerType = modelsMatch[1] as ProviderType;
|
|
400
|
+
|
|
401
|
+
try {
|
|
402
|
+
const models = PROVIDER_MODELS[providerType];
|
|
403
|
+
|
|
404
|
+
if (!models) {
|
|
405
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
406
|
+
res.end(JSON.stringify({ error: 'Provider type not found' }));
|
|
407
|
+
return true;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
411
|
+
res.end(JSON.stringify({ providerType, models, count: models.length }));
|
|
412
|
+
} catch (err) {
|
|
413
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
414
|
+
res.end(JSON.stringify({ error: (err as Error).message }));
|
|
415
|
+
}
|
|
416
|
+
return true;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// ===========================
|
|
420
|
+
// Cache Management Routes
|
|
421
|
+
// ===========================
|
|
422
|
+
|
|
423
|
+
// GET /api/litellm-api/cache/stats - Get cache statistics
|
|
424
|
+
if (pathname === '/api/litellm-api/cache/stats' && req.method === 'GET') {
|
|
425
|
+
try {
|
|
426
|
+
const cacheStore = getContextCacheStore();
|
|
427
|
+
const stats = cacheStore.getStatus();
|
|
428
|
+
|
|
429
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
430
|
+
res.end(JSON.stringify(stats));
|
|
431
|
+
} catch (err) {
|
|
432
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
433
|
+
res.end(JSON.stringify({ error: (err as Error).message }));
|
|
434
|
+
}
|
|
435
|
+
return true;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// POST /api/litellm-api/cache/clear - Clear cache
|
|
439
|
+
if (pathname === '/api/litellm-api/cache/clear' && req.method === 'POST') {
|
|
440
|
+
try {
|
|
441
|
+
const cacheStore = getContextCacheStore();
|
|
442
|
+
const result = cacheStore.clear();
|
|
443
|
+
|
|
444
|
+
broadcastToClients({
|
|
445
|
+
type: 'LITELLM_CACHE_CLEARED',
|
|
446
|
+
payload: { removed: result.removed, timestamp: new Date().toISOString() }
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
450
|
+
res.end(JSON.stringify({ success: true, removed: result.removed }));
|
|
451
|
+
} catch (err) {
|
|
452
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
453
|
+
res.end(JSON.stringify({ error: (err as Error).message }));
|
|
454
|
+
}
|
|
455
|
+
return true;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// ===========================
|
|
459
|
+
// Config Management Routes
|
|
460
|
+
// ===========================
|
|
461
|
+
|
|
462
|
+
// GET /api/litellm-api/config - Get full config
|
|
463
|
+
if (pathname === '/api/litellm-api/config' && req.method === 'GET') {
|
|
464
|
+
try {
|
|
465
|
+
const config = loadLiteLLMApiConfig(initialPath);
|
|
466
|
+
|
|
467
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
468
|
+
res.end(JSON.stringify(config));
|
|
469
|
+
} catch (err) {
|
|
470
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
471
|
+
res.end(JSON.stringify({ error: (err as Error).message }));
|
|
472
|
+
}
|
|
473
|
+
return true;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// PUT /api/litellm-api/config/cache - Update global cache settings
|
|
477
|
+
if (pathname === '/api/litellm-api/config/cache' && req.method === 'PUT') {
|
|
478
|
+
handlePostRequest(req, res, async (body: unknown) => {
|
|
479
|
+
const settings = body as Partial<{ enabled: boolean; cacheDir: string; maxTotalSizeMB: number }>;
|
|
480
|
+
|
|
481
|
+
try {
|
|
482
|
+
updateGlobalCacheSettings(initialPath, settings);
|
|
483
|
+
|
|
484
|
+
const updatedSettings = getGlobalCacheSettings(initialPath);
|
|
485
|
+
|
|
486
|
+
broadcastToClients({
|
|
487
|
+
type: 'LITELLM_CACHE_SETTINGS_UPDATED',
|
|
488
|
+
payload: { settings: updatedSettings, timestamp: new Date().toISOString() }
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
return { success: true, settings: updatedSettings };
|
|
492
|
+
} catch (err) {
|
|
493
|
+
return { error: (err as Error).message, status: 500 };
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
return true;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// PUT /api/litellm-api/config/default-endpoint - Set default endpoint
|
|
500
|
+
if (pathname === '/api/litellm-api/config/default-endpoint' && req.method === 'PUT') {
|
|
501
|
+
handlePostRequest(req, res, async (body: unknown) => {
|
|
502
|
+
const { endpointId } = body as { endpointId?: string };
|
|
503
|
+
|
|
504
|
+
try {
|
|
505
|
+
setDefaultEndpoint(initialPath, endpointId);
|
|
506
|
+
|
|
507
|
+
const defaultEndpoint = getDefaultEndpoint(initialPath);
|
|
508
|
+
|
|
509
|
+
broadcastToClients({
|
|
510
|
+
type: 'LITELLM_DEFAULT_ENDPOINT_UPDATED',
|
|
511
|
+
payload: { endpointId, defaultEndpoint, timestamp: new Date().toISOString() }
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
return { success: true, defaultEndpoint };
|
|
515
|
+
} catch (err) {
|
|
516
|
+
return { error: (err as Error).message, status: 500 };
|
|
517
|
+
}
|
|
518
|
+
});
|
|
519
|
+
return true;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// ===========================
|
|
523
|
+
// Config Sync Routes
|
|
524
|
+
// ===========================
|
|
525
|
+
|
|
526
|
+
// POST /api/litellm-api/config/sync - Sync UI config to ccw_litellm YAML config
|
|
527
|
+
if (pathname === '/api/litellm-api/config/sync' && req.method === 'POST') {
|
|
528
|
+
try {
|
|
529
|
+
const yamlPath = saveLiteLLMYamlConfig(initialPath);
|
|
530
|
+
|
|
531
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
532
|
+
res.end(JSON.stringify({
|
|
533
|
+
success: true,
|
|
534
|
+
message: 'Config synced to ccw_litellm',
|
|
535
|
+
yamlPath,
|
|
536
|
+
}));
|
|
537
|
+
} catch (err) {
|
|
538
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
539
|
+
res.end(JSON.stringify({ error: (err as Error).message }));
|
|
540
|
+
}
|
|
541
|
+
return true;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// GET /api/litellm-api/config/yaml-preview - Preview YAML config without saving
|
|
545
|
+
if (pathname === '/api/litellm-api/config/yaml-preview' && req.method === 'GET') {
|
|
546
|
+
try {
|
|
547
|
+
const yamlConfig = generateLiteLLMYamlConfig(initialPath);
|
|
548
|
+
|
|
549
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
550
|
+
res.end(JSON.stringify({
|
|
551
|
+
success: true,
|
|
552
|
+
config: yamlConfig,
|
|
553
|
+
}));
|
|
554
|
+
} catch (err) {
|
|
555
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
556
|
+
res.end(JSON.stringify({ error: (err as Error).message }));
|
|
557
|
+
}
|
|
558
|
+
return true;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// ===========================
|
|
562
|
+
// CCW-LiteLLM Package Management
|
|
563
|
+
// ===========================
|
|
564
|
+
|
|
565
|
+
// GET /api/litellm-api/ccw-litellm/status - Check ccw-litellm installation status
|
|
566
|
+
// Supports ?refresh=true to bypass cache
|
|
567
|
+
if (pathname === '/api/litellm-api/ccw-litellm/status' && req.method === 'GET') {
|
|
568
|
+
const forceRefresh = url.searchParams.get('refresh') === 'true';
|
|
569
|
+
|
|
570
|
+
// Check cache first (unless force refresh)
|
|
571
|
+
if (!forceRefresh && ccwLitellmStatusCache.data &&
|
|
572
|
+
Date.now() - ccwLitellmStatusCache.timestamp < ccwLitellmStatusCache.ttl) {
|
|
573
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
574
|
+
res.end(JSON.stringify(ccwLitellmStatusCache.data));
|
|
575
|
+
return true;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// Async check - use pip show for more reliable detection
|
|
579
|
+
try {
|
|
580
|
+
const { exec } = await import('child_process');
|
|
581
|
+
const { promisify } = await import('util');
|
|
582
|
+
const execAsync = promisify(exec);
|
|
583
|
+
|
|
584
|
+
let result: { installed: boolean; version?: string; error?: string } = { installed: false };
|
|
585
|
+
|
|
586
|
+
// Method 1: Try pip show ccw-litellm (most reliable)
|
|
587
|
+
try {
|
|
588
|
+
const { stdout } = await execAsync('pip show ccw-litellm', {
|
|
589
|
+
timeout: 10000,
|
|
590
|
+
windowsHide: true,
|
|
591
|
+
shell: true,
|
|
592
|
+
});
|
|
593
|
+
// Parse version from pip show output
|
|
594
|
+
const versionMatch = stdout.match(/Version:\s*(.+)/i);
|
|
595
|
+
if (versionMatch) {
|
|
596
|
+
result = { installed: true, version: versionMatch[1].trim() };
|
|
597
|
+
console.log(`[ccw-litellm status] Found via pip show: ${result.version}`);
|
|
598
|
+
}
|
|
599
|
+
} catch (pipErr) {
|
|
600
|
+
console.log('[ccw-litellm status] pip show failed, trying python import...');
|
|
601
|
+
|
|
602
|
+
// Method 2: Fallback to Python import
|
|
603
|
+
const pythonExecutables = ['python', 'python3', 'py'];
|
|
604
|
+
for (const pythonExe of pythonExecutables) {
|
|
605
|
+
try {
|
|
606
|
+
// Use simpler Python code without complex quotes
|
|
607
|
+
const { stdout } = await execAsync(`${pythonExe} -c "import ccw_litellm; print(ccw_litellm.__version__)"`, {
|
|
608
|
+
timeout: 5000,
|
|
609
|
+
windowsHide: true,
|
|
610
|
+
shell: true,
|
|
611
|
+
});
|
|
612
|
+
const version = stdout.trim();
|
|
613
|
+
if (version) {
|
|
614
|
+
result = { installed: true, version };
|
|
615
|
+
console.log(`[ccw-litellm status] Found with ${pythonExe}: ${version}`);
|
|
616
|
+
break;
|
|
617
|
+
}
|
|
618
|
+
} catch (err) {
|
|
619
|
+
result.error = (err as Error).message;
|
|
620
|
+
console.log(`[ccw-litellm status] ${pythonExe} failed:`, result.error.substring(0, 100));
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// Update cache
|
|
626
|
+
ccwLitellmStatusCache = {
|
|
627
|
+
data: result,
|
|
628
|
+
timestamp: Date.now(),
|
|
629
|
+
ttl: 5 * 60 * 1000,
|
|
630
|
+
};
|
|
631
|
+
|
|
632
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
633
|
+
res.end(JSON.stringify(result));
|
|
634
|
+
} catch (err) {
|
|
635
|
+
const errorResult = { installed: false, error: (err as Error).message };
|
|
636
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
637
|
+
res.end(JSON.stringify(errorResult));
|
|
638
|
+
}
|
|
639
|
+
return true;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
// ===========================
|
|
643
|
+
// CodexLens Embedding Rotation Routes
|
|
644
|
+
// ===========================
|
|
645
|
+
|
|
646
|
+
// GET /api/litellm-api/codexlens/rotation - Get rotation config
|
|
647
|
+
if (pathname === '/api/litellm-api/codexlens/rotation' && req.method === 'GET') {
|
|
648
|
+
try {
|
|
649
|
+
const rotationConfig = getCodexLensEmbeddingRotation(initialPath);
|
|
650
|
+
const availableProviders = getEmbeddingProvidersForRotation(initialPath);
|
|
651
|
+
|
|
652
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
653
|
+
res.end(JSON.stringify({
|
|
654
|
+
rotationConfig: rotationConfig || null,
|
|
655
|
+
availableProviders,
|
|
656
|
+
}));
|
|
657
|
+
} catch (err) {
|
|
658
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
659
|
+
res.end(JSON.stringify({ error: (err as Error).message }));
|
|
660
|
+
}
|
|
661
|
+
return true;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// PUT /api/litellm-api/codexlens/rotation - Update rotation config
|
|
665
|
+
if (pathname === '/api/litellm-api/codexlens/rotation' && req.method === 'PUT') {
|
|
666
|
+
handlePostRequest(req, res, async (body: unknown) => {
|
|
667
|
+
const rotationConfig = body as CodexLensEmbeddingRotation | null;
|
|
668
|
+
|
|
669
|
+
try {
|
|
670
|
+
const { syncResult } = updateCodexLensEmbeddingRotation(initialPath, rotationConfig || undefined);
|
|
671
|
+
|
|
672
|
+
broadcastToClients({
|
|
673
|
+
type: 'CODEXLENS_ROTATION_UPDATED',
|
|
674
|
+
payload: { rotationConfig, syncResult, timestamp: new Date().toISOString() }
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
return { success: true, rotationConfig, syncResult };
|
|
678
|
+
} catch (err) {
|
|
679
|
+
return { error: (err as Error).message, status: 500 };
|
|
680
|
+
}
|
|
681
|
+
});
|
|
682
|
+
return true;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// GET /api/litellm-api/codexlens/rotation/endpoints - Get generated rotation endpoints
|
|
686
|
+
if (pathname === '/api/litellm-api/codexlens/rotation/endpoints' && req.method === 'GET') {
|
|
687
|
+
try {
|
|
688
|
+
const endpoints = generateRotationEndpoints(initialPath);
|
|
689
|
+
|
|
690
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
691
|
+
res.end(JSON.stringify({
|
|
692
|
+
endpoints,
|
|
693
|
+
count: endpoints.length,
|
|
694
|
+
}));
|
|
695
|
+
} catch (err) {
|
|
696
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
697
|
+
res.end(JSON.stringify({ error: (err as Error).message }));
|
|
698
|
+
}
|
|
699
|
+
return true;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// POST /api/litellm-api/codexlens/rotation/sync - Manually sync rotation config to CodexLens
|
|
703
|
+
if (pathname === '/api/litellm-api/codexlens/rotation/sync' && req.method === 'POST') {
|
|
704
|
+
try {
|
|
705
|
+
const syncResult = syncCodexLensConfig(initialPath);
|
|
706
|
+
|
|
707
|
+
if (syncResult.success) {
|
|
708
|
+
broadcastToClients({
|
|
709
|
+
type: 'CODEXLENS_CONFIG_SYNCED',
|
|
710
|
+
payload: { ...syncResult, timestamp: new Date().toISOString() }
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
715
|
+
res.end(JSON.stringify(syncResult));
|
|
716
|
+
} catch (err) {
|
|
717
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
718
|
+
res.end(JSON.stringify({ success: false, message: (err as Error).message }));
|
|
719
|
+
}
|
|
720
|
+
return true;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// ===========================
|
|
724
|
+
// Embedding Pool Routes (New Generic API)
|
|
725
|
+
// ===========================
|
|
726
|
+
|
|
727
|
+
// GET /api/litellm-api/embedding-pool - Get pool config and available models
|
|
728
|
+
if (pathname === '/api/litellm-api/embedding-pool' && req.method === 'GET') {
|
|
729
|
+
try {
|
|
730
|
+
const poolConfig = getEmbeddingPoolConfig(initialPath);
|
|
731
|
+
|
|
732
|
+
// Get list of all available embedding models from all providers
|
|
733
|
+
const config = loadLiteLLMApiConfig(initialPath);
|
|
734
|
+
const availableModels: Array<{ modelId: string; modelName: string; providers: string[] }> = [];
|
|
735
|
+
const modelMap = new Map<string, { modelId: string; modelName: string; providers: string[] }>();
|
|
736
|
+
|
|
737
|
+
for (const provider of config.providers) {
|
|
738
|
+
if (!provider.enabled || !provider.embeddingModels) continue;
|
|
739
|
+
|
|
740
|
+
for (const model of provider.embeddingModels) {
|
|
741
|
+
if (!model.enabled) continue;
|
|
742
|
+
|
|
743
|
+
const key = model.id;
|
|
744
|
+
if (modelMap.has(key)) {
|
|
745
|
+
modelMap.get(key)!.providers.push(provider.name);
|
|
746
|
+
} else {
|
|
747
|
+
modelMap.set(key, {
|
|
748
|
+
modelId: model.id,
|
|
749
|
+
modelName: model.name,
|
|
750
|
+
providers: [provider.name],
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
availableModels.push(...Array.from(modelMap.values()));
|
|
757
|
+
|
|
758
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
759
|
+
res.end(JSON.stringify({
|
|
760
|
+
poolConfig: poolConfig || null,
|
|
761
|
+
availableModels,
|
|
762
|
+
}));
|
|
763
|
+
} catch (err) {
|
|
764
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
765
|
+
res.end(JSON.stringify({ error: (err as Error).message }));
|
|
766
|
+
}
|
|
767
|
+
return true;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// PUT /api/litellm-api/embedding-pool - Update pool config
|
|
771
|
+
if (pathname === '/api/litellm-api/embedding-pool' && req.method === 'PUT') {
|
|
772
|
+
handlePostRequest(req, res, async (body: unknown) => {
|
|
773
|
+
const poolConfig = body as EmbeddingPoolConfig | null;
|
|
774
|
+
|
|
775
|
+
try {
|
|
776
|
+
const { syncResult } = updateEmbeddingPoolConfig(initialPath, poolConfig || undefined);
|
|
777
|
+
|
|
778
|
+
broadcastToClients({
|
|
779
|
+
type: 'EMBEDDING_POOL_UPDATED',
|
|
780
|
+
payload: { poolConfig, syncResult, timestamp: new Date().toISOString() }
|
|
781
|
+
});
|
|
782
|
+
|
|
783
|
+
return { success: true, poolConfig, syncResult };
|
|
784
|
+
} catch (err) {
|
|
785
|
+
return { error: (err as Error).message, status: 500 };
|
|
786
|
+
}
|
|
787
|
+
});
|
|
788
|
+
return true;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
// GET /api/litellm-api/embedding-pool/discover/:model - Preview auto-discovery results
|
|
792
|
+
const discoverMatch = pathname.match(/^\/api\/litellm-api\/embedding-pool\/discover\/([^/]+)$/);
|
|
793
|
+
if (discoverMatch && req.method === 'GET') {
|
|
794
|
+
const targetModel = decodeURIComponent(discoverMatch[1]);
|
|
795
|
+
|
|
796
|
+
try {
|
|
797
|
+
const discovered = discoverProvidersForModel(initialPath, targetModel);
|
|
798
|
+
|
|
799
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
800
|
+
res.end(JSON.stringify({
|
|
801
|
+
targetModel,
|
|
802
|
+
discovered,
|
|
803
|
+
count: discovered.length,
|
|
804
|
+
}));
|
|
805
|
+
} catch (err) {
|
|
806
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
807
|
+
res.end(JSON.stringify({ error: (err as Error).message }));
|
|
808
|
+
}
|
|
809
|
+
return true;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// POST /api/litellm-api/ccw-litellm/install - Install ccw-litellm package
|
|
813
|
+
if (pathname === '/api/litellm-api/ccw-litellm/install' && req.method === 'POST') {
|
|
814
|
+
handlePostRequest(req, res, async () => {
|
|
815
|
+
try {
|
|
816
|
+
const { spawn } = await import('child_process');
|
|
817
|
+
const path = await import('path');
|
|
818
|
+
const fs = await import('fs');
|
|
819
|
+
|
|
820
|
+
// Try to find ccw-litellm package in distribution
|
|
821
|
+
const possiblePaths = [
|
|
822
|
+
path.join(initialPath, 'ccw-litellm'),
|
|
823
|
+
path.join(initialPath, '..', 'ccw-litellm'),
|
|
824
|
+
path.join(process.cwd(), 'ccw-litellm'),
|
|
825
|
+
path.join(PACKAGE_ROOT, 'ccw-litellm'), // npm package internal path
|
|
826
|
+
];
|
|
827
|
+
|
|
828
|
+
let packagePath = '';
|
|
829
|
+
for (const p of possiblePaths) {
|
|
830
|
+
const pyproject = path.join(p, 'pyproject.toml');
|
|
831
|
+
if (fs.existsSync(pyproject)) {
|
|
832
|
+
packagePath = p;
|
|
833
|
+
break;
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
if (!packagePath) {
|
|
838
|
+
// Try pip install from PyPI as fallback
|
|
839
|
+
return new Promise((resolve) => {
|
|
840
|
+
const proc = spawn('pip', ['install', 'ccw-litellm'], { shell: true, timeout: 300000 });
|
|
841
|
+
let output = '';
|
|
842
|
+
let error = '';
|
|
843
|
+
proc.stdout?.on('data', (data) => { output += data.toString(); });
|
|
844
|
+
proc.stderr?.on('data', (data) => { error += data.toString(); });
|
|
845
|
+
proc.on('close', (code) => {
|
|
846
|
+
if (code === 0) {
|
|
847
|
+
// Clear status cache after successful installation
|
|
848
|
+
clearCcwLitellmStatusCache();
|
|
849
|
+
resolve({ success: true, message: 'ccw-litellm installed from PyPI' });
|
|
850
|
+
} else {
|
|
851
|
+
resolve({ success: false, error: error || 'Installation failed' });
|
|
852
|
+
}
|
|
853
|
+
});
|
|
854
|
+
proc.on('error', (err) => resolve({ success: false, error: err.message }));
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
// Install from local package
|
|
859
|
+
return new Promise((resolve) => {
|
|
860
|
+
const proc = spawn('pip', ['install', '-e', packagePath], { shell: true, timeout: 300000 });
|
|
861
|
+
let output = '';
|
|
862
|
+
let error = '';
|
|
863
|
+
proc.stdout?.on('data', (data) => { output += data.toString(); });
|
|
864
|
+
proc.stderr?.on('data', (data) => { error += data.toString(); });
|
|
865
|
+
proc.on('close', (code) => {
|
|
866
|
+
if (code === 0) {
|
|
867
|
+
// Clear status cache after successful installation
|
|
868
|
+
clearCcwLitellmStatusCache();
|
|
869
|
+
|
|
870
|
+
// Broadcast installation event
|
|
871
|
+
broadcastToClients({
|
|
872
|
+
type: 'CCW_LITELLM_INSTALLED',
|
|
873
|
+
payload: { timestamp: new Date().toISOString() }
|
|
874
|
+
});
|
|
875
|
+
resolve({ success: true, message: 'ccw-litellm installed successfully', path: packagePath });
|
|
876
|
+
} else {
|
|
877
|
+
resolve({ success: false, error: error || output || 'Installation failed' });
|
|
878
|
+
}
|
|
879
|
+
});
|
|
880
|
+
proc.on('error', (err) => resolve({ success: false, error: err.message }));
|
|
881
|
+
});
|
|
882
|
+
} catch (err) {
|
|
883
|
+
return { success: false, error: (err as Error).message };
|
|
884
|
+
}
|
|
885
|
+
});
|
|
886
|
+
return true;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
// POST /api/litellm-api/ccw-litellm/uninstall - Uninstall ccw-litellm package
|
|
890
|
+
if (pathname === '/api/litellm-api/ccw-litellm/uninstall' && req.method === 'POST') {
|
|
891
|
+
handlePostRequest(req, res, async () => {
|
|
892
|
+
try {
|
|
893
|
+
const { spawn } = await import('child_process');
|
|
894
|
+
|
|
895
|
+
return new Promise((resolve) => {
|
|
896
|
+
const proc = spawn('pip', ['uninstall', '-y', 'ccw-litellm'], { shell: true, timeout: 120000 });
|
|
897
|
+
let output = '';
|
|
898
|
+
let error = '';
|
|
899
|
+
proc.stdout?.on('data', (data) => { output += data.toString(); });
|
|
900
|
+
proc.stderr?.on('data', (data) => { error += data.toString(); });
|
|
901
|
+
proc.on('close', (code) => {
|
|
902
|
+
// Clear status cache after uninstallation attempt
|
|
903
|
+
clearCcwLitellmStatusCache();
|
|
904
|
+
|
|
905
|
+
if (code === 0) {
|
|
906
|
+
broadcastToClients({
|
|
907
|
+
type: 'CCW_LITELLM_UNINSTALLED',
|
|
908
|
+
payload: { timestamp: new Date().toISOString() }
|
|
909
|
+
});
|
|
910
|
+
resolve({ success: true, message: 'ccw-litellm uninstalled successfully' });
|
|
911
|
+
} else {
|
|
912
|
+
// Check if package was not installed
|
|
913
|
+
if (error.includes('not installed') || output.includes('not installed')) {
|
|
914
|
+
resolve({ success: true, message: 'ccw-litellm was not installed' });
|
|
915
|
+
} else {
|
|
916
|
+
resolve({ success: false, error: error || output || 'Uninstallation failed' });
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
});
|
|
920
|
+
proc.on('error', (err) => resolve({ success: false, error: err.message }));
|
|
921
|
+
});
|
|
922
|
+
} catch (err) {
|
|
923
|
+
return { success: false, error: (err as Error).message };
|
|
924
|
+
}
|
|
925
|
+
});
|
|
926
|
+
return true;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
return false;
|
|
930
|
+
}
|