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.
Files changed (208) hide show
  1. package/.claude/CLAUDE.md +16 -1
  2. package/.claude/workflows/cli-templates/protocols/analysis-protocol.md +11 -4
  3. package/.claude/workflows/cli-templates/protocols/write-protocol.md +10 -75
  4. package/.claude/workflows/cli-tools-usage.md +14 -24
  5. package/.codex/AGENTS.md +51 -1
  6. package/.codex/prompts/compact.md +378 -0
  7. package/.gemini/GEMINI.md +57 -20
  8. package/ccw/dist/cli.d.ts.map +1 -1
  9. package/ccw/dist/cli.js +21 -8
  10. package/ccw/dist/cli.js.map +1 -1
  11. package/ccw/dist/commands/cli.d.ts +2 -0
  12. package/ccw/dist/commands/cli.d.ts.map +1 -1
  13. package/ccw/dist/commands/cli.js +129 -8
  14. package/ccw/dist/commands/cli.js.map +1 -1
  15. package/ccw/dist/commands/hook.d.ts.map +1 -1
  16. package/ccw/dist/commands/hook.js +3 -2
  17. package/ccw/dist/commands/hook.js.map +1 -1
  18. package/ccw/dist/config/litellm-api-config-manager.d.ts +180 -0
  19. package/ccw/dist/config/litellm-api-config-manager.d.ts.map +1 -0
  20. package/ccw/dist/config/litellm-api-config-manager.js +770 -0
  21. package/ccw/dist/config/litellm-api-config-manager.js.map +1 -0
  22. package/ccw/dist/config/provider-models.d.ts +73 -0
  23. package/ccw/dist/config/provider-models.d.ts.map +1 -0
  24. package/ccw/dist/config/provider-models.js +172 -0
  25. package/ccw/dist/config/provider-models.js.map +1 -0
  26. package/ccw/dist/core/cache-manager.d.ts.map +1 -1
  27. package/ccw/dist/core/cache-manager.js +3 -5
  28. package/ccw/dist/core/cache-manager.js.map +1 -1
  29. package/ccw/dist/core/dashboard-generator.d.ts.map +1 -1
  30. package/ccw/dist/core/dashboard-generator.js +3 -1
  31. package/ccw/dist/core/dashboard-generator.js.map +1 -1
  32. package/ccw/dist/core/routes/cli-routes.d.ts.map +1 -1
  33. package/ccw/dist/core/routes/cli-routes.js +169 -0
  34. package/ccw/dist/core/routes/cli-routes.js.map +1 -1
  35. package/ccw/dist/core/routes/codexlens-routes.d.ts.map +1 -1
  36. package/ccw/dist/core/routes/codexlens-routes.js +234 -18
  37. package/ccw/dist/core/routes/codexlens-routes.js.map +1 -1
  38. package/ccw/dist/core/routes/hooks-routes.d.ts.map +1 -1
  39. package/ccw/dist/core/routes/hooks-routes.js +30 -32
  40. package/ccw/dist/core/routes/hooks-routes.js.map +1 -1
  41. package/ccw/dist/core/routes/litellm-api-routes.d.ts +21 -0
  42. package/ccw/dist/core/routes/litellm-api-routes.d.ts.map +1 -0
  43. package/ccw/dist/core/routes/litellm-api-routes.js +780 -0
  44. package/ccw/dist/core/routes/litellm-api-routes.js.map +1 -0
  45. package/ccw/dist/core/routes/litellm-routes.d.ts +20 -0
  46. package/ccw/dist/core/routes/litellm-routes.d.ts.map +1 -0
  47. package/ccw/dist/core/routes/litellm-routes.js +85 -0
  48. package/ccw/dist/core/routes/litellm-routes.js.map +1 -0
  49. package/ccw/dist/core/routes/mcp-routes.js +2 -2
  50. package/ccw/dist/core/routes/mcp-routes.js.map +1 -1
  51. package/ccw/dist/core/routes/status-routes.d.ts.map +1 -1
  52. package/ccw/dist/core/routes/status-routes.js +39 -0
  53. package/ccw/dist/core/routes/status-routes.js.map +1 -1
  54. package/ccw/dist/core/routes/system-routes.js +1 -1
  55. package/ccw/dist/core/routes/system-routes.js.map +1 -1
  56. package/ccw/dist/core/server.d.ts.map +1 -1
  57. package/ccw/dist/core/server.js +15 -1
  58. package/ccw/dist/core/server.js.map +1 -1
  59. package/ccw/dist/mcp-server/index.js +1 -1
  60. package/ccw/dist/mcp-server/index.js.map +1 -1
  61. package/ccw/dist/tools/claude-cli-tools.d.ts +82 -0
  62. package/ccw/dist/tools/claude-cli-tools.d.ts.map +1 -0
  63. package/ccw/dist/tools/claude-cli-tools.js +216 -0
  64. package/ccw/dist/tools/claude-cli-tools.js.map +1 -0
  65. package/ccw/dist/tools/cli-executor.d.ts.map +1 -1
  66. package/ccw/dist/tools/cli-executor.js +76 -14
  67. package/ccw/dist/tools/cli-executor.js.map +1 -1
  68. package/ccw/dist/tools/codex-lens.d.ts +9 -2
  69. package/ccw/dist/tools/codex-lens.d.ts.map +1 -1
  70. package/ccw/dist/tools/codex-lens.js +114 -9
  71. package/ccw/dist/tools/codex-lens.js.map +1 -1
  72. package/ccw/dist/tools/context-cache-store.d.ts +136 -0
  73. package/ccw/dist/tools/context-cache-store.d.ts.map +1 -0
  74. package/ccw/dist/tools/context-cache-store.js +256 -0
  75. package/ccw/dist/tools/context-cache-store.js.map +1 -0
  76. package/ccw/dist/tools/context-cache.d.ts +56 -0
  77. package/ccw/dist/tools/context-cache.d.ts.map +1 -0
  78. package/ccw/dist/tools/context-cache.js +294 -0
  79. package/ccw/dist/tools/context-cache.js.map +1 -0
  80. package/ccw/dist/tools/core-memory.d.ts.map +1 -1
  81. package/ccw/dist/tools/core-memory.js +33 -19
  82. package/ccw/dist/tools/core-memory.js.map +1 -1
  83. package/ccw/dist/tools/index.d.ts.map +1 -1
  84. package/ccw/dist/tools/index.js +2 -0
  85. package/ccw/dist/tools/index.js.map +1 -1
  86. package/ccw/dist/tools/litellm-client.d.ts +85 -0
  87. package/ccw/dist/tools/litellm-client.d.ts.map +1 -0
  88. package/ccw/dist/tools/litellm-client.js +188 -0
  89. package/ccw/dist/tools/litellm-client.js.map +1 -0
  90. package/ccw/dist/tools/litellm-executor.d.ts +34 -0
  91. package/ccw/dist/tools/litellm-executor.d.ts.map +1 -0
  92. package/ccw/dist/tools/litellm-executor.js +192 -0
  93. package/ccw/dist/tools/litellm-executor.js.map +1 -0
  94. package/ccw/dist/tools/pattern-parser.d.ts +55 -0
  95. package/ccw/dist/tools/pattern-parser.d.ts.map +1 -0
  96. package/ccw/dist/tools/pattern-parser.js +237 -0
  97. package/ccw/dist/tools/pattern-parser.js.map +1 -0
  98. package/ccw/dist/tools/smart-search.d.ts +1 -0
  99. package/ccw/dist/tools/smart-search.d.ts.map +1 -1
  100. package/ccw/dist/tools/smart-search.js +117 -41
  101. package/ccw/dist/tools/smart-search.js.map +1 -1
  102. package/ccw/dist/types/litellm-api-config.d.ts +294 -0
  103. package/ccw/dist/types/litellm-api-config.d.ts.map +1 -0
  104. package/ccw/dist/types/litellm-api-config.js +8 -0
  105. package/ccw/dist/types/litellm-api-config.js.map +1 -0
  106. package/ccw/src/cli.ts +258 -244
  107. package/ccw/src/commands/cli.ts +153 -9
  108. package/ccw/src/commands/hook.ts +3 -2
  109. package/ccw/src/config/.litellm-api-config-manager.ts.2025-12-23T11-57-43-727Z.bak +441 -0
  110. package/ccw/src/config/litellm-api-config-manager.ts +1012 -0
  111. package/ccw/src/config/provider-models.ts +222 -0
  112. package/ccw/src/core/cache-manager.ts +292 -294
  113. package/ccw/src/core/dashboard-generator.ts +3 -1
  114. package/ccw/src/core/routes/cli-routes.ts +192 -0
  115. package/ccw/src/core/routes/codexlens-routes.ts +241 -19
  116. package/ccw/src/core/routes/hooks-routes.ts +399 -405
  117. package/ccw/src/core/routes/litellm-api-routes.ts +930 -0
  118. package/ccw/src/core/routes/litellm-routes.ts +107 -0
  119. package/ccw/src/core/routes/mcp-routes.ts +1271 -1271
  120. package/ccw/src/core/routes/status-routes.ts +51 -0
  121. package/ccw/src/core/routes/system-routes.ts +1 -1
  122. package/ccw/src/core/server.ts +15 -1
  123. package/ccw/src/mcp-server/index.ts +1 -1
  124. package/ccw/src/templates/dashboard-css/12-cli-legacy.css +44 -0
  125. package/ccw/src/templates/dashboard-css/31-api-settings.css +2265 -0
  126. package/ccw/src/templates/dashboard-js/components/cli-history.js +15 -8
  127. package/ccw/src/templates/dashboard-js/components/cli-status.js +323 -9
  128. package/ccw/src/templates/dashboard-js/components/navigation.js +329 -313
  129. package/ccw/src/templates/dashboard-js/i18n.js +583 -1
  130. package/ccw/src/templates/dashboard-js/views/api-settings.js +3362 -0
  131. package/ccw/src/templates/dashboard-js/views/cli-manager.js +199 -24
  132. package/ccw/src/templates/dashboard-js/views/codexlens-manager.js +1265 -27
  133. package/ccw/src/templates/dashboard.html +840 -831
  134. package/ccw/src/tools/claude-cli-tools.ts +300 -0
  135. package/ccw/src/tools/cli-executor.ts +83 -14
  136. package/ccw/src/tools/codex-lens.ts +146 -9
  137. package/ccw/src/tools/context-cache-store.ts +368 -0
  138. package/ccw/src/tools/context-cache.ts +393 -0
  139. package/ccw/src/tools/core-memory.ts +33 -19
  140. package/ccw/src/tools/index.ts +2 -0
  141. package/ccw/src/tools/litellm-client.ts +246 -0
  142. package/ccw/src/tools/litellm-executor.ts +241 -0
  143. package/ccw/src/tools/pattern-parser.ts +329 -0
  144. package/ccw/src/tools/smart-search.ts +142 -41
  145. package/ccw/src/types/litellm-api-config.ts +402 -0
  146. package/ccw-litellm/README.md +180 -0
  147. package/ccw-litellm/pyproject.toml +35 -0
  148. package/ccw-litellm/src/ccw_litellm/__init__.py +47 -0
  149. package/ccw-litellm/src/ccw_litellm/__pycache__/__init__.cpython-313.pyc +0 -0
  150. package/ccw-litellm/src/ccw_litellm/__pycache__/cli.cpython-313.pyc +0 -0
  151. package/ccw-litellm/src/ccw_litellm/cli.py +108 -0
  152. package/ccw-litellm/src/ccw_litellm/clients/__init__.py +12 -0
  153. package/ccw-litellm/src/ccw_litellm/clients/__pycache__/__init__.cpython-313.pyc +0 -0
  154. package/ccw-litellm/src/ccw_litellm/clients/__pycache__/litellm_embedder.cpython-313.pyc +0 -0
  155. package/ccw-litellm/src/ccw_litellm/clients/__pycache__/litellm_llm.cpython-313.pyc +0 -0
  156. package/ccw-litellm/src/ccw_litellm/clients/litellm_embedder.py +251 -0
  157. package/ccw-litellm/src/ccw_litellm/clients/litellm_llm.py +165 -0
  158. package/ccw-litellm/src/ccw_litellm/config/__init__.py +22 -0
  159. package/ccw-litellm/src/ccw_litellm/config/__pycache__/__init__.cpython-313.pyc +0 -0
  160. package/ccw-litellm/src/ccw_litellm/config/__pycache__/loader.cpython-313.pyc +0 -0
  161. package/ccw-litellm/src/ccw_litellm/config/__pycache__/models.cpython-313.pyc +0 -0
  162. package/ccw-litellm/src/ccw_litellm/config/loader.py +316 -0
  163. package/ccw-litellm/src/ccw_litellm/config/models.py +130 -0
  164. package/ccw-litellm/src/ccw_litellm/interfaces/__init__.py +14 -0
  165. package/ccw-litellm/src/ccw_litellm/interfaces/__pycache__/__init__.cpython-313.pyc +0 -0
  166. package/ccw-litellm/src/ccw_litellm/interfaces/__pycache__/embedder.cpython-313.pyc +0 -0
  167. package/ccw-litellm/src/ccw_litellm/interfaces/__pycache__/llm.cpython-313.pyc +0 -0
  168. package/ccw-litellm/src/ccw_litellm/interfaces/embedder.py +52 -0
  169. package/ccw-litellm/src/ccw_litellm/interfaces/llm.py +45 -0
  170. package/codex-lens/src/codexlens/__pycache__/config.cpython-313.pyc +0 -0
  171. package/codex-lens/src/codexlens/cli/__pycache__/commands.cpython-313.pyc +0 -0
  172. package/codex-lens/src/codexlens/cli/__pycache__/embedding_manager.cpython-313.pyc +0 -0
  173. package/codex-lens/src/codexlens/cli/__pycache__/model_manager.cpython-313.pyc +0 -0
  174. package/codex-lens/src/codexlens/cli/__pycache__/output.cpython-313.pyc +0 -0
  175. package/codex-lens/src/codexlens/cli/commands.py +378 -23
  176. package/codex-lens/src/codexlens/cli/embedding_manager.py +660 -56
  177. package/codex-lens/src/codexlens/cli/model_manager.py +31 -18
  178. package/codex-lens/src/codexlens/cli/output.py +12 -1
  179. package/codex-lens/src/codexlens/config.py +93 -0
  180. package/codex-lens/src/codexlens/search/__pycache__/chain_search.cpython-313.pyc +0 -0
  181. package/codex-lens/src/codexlens/search/__pycache__/hybrid_search.cpython-313.pyc +0 -0
  182. package/codex-lens/src/codexlens/search/__pycache__/ranking.cpython-313.pyc +0 -0
  183. package/codex-lens/src/codexlens/search/chain_search.py +6 -2
  184. package/codex-lens/src/codexlens/search/hybrid_search.py +44 -21
  185. package/codex-lens/src/codexlens/search/ranking.py +1 -1
  186. package/codex-lens/src/codexlens/semantic/__init__.py +42 -0
  187. package/codex-lens/src/codexlens/semantic/__pycache__/__init__.cpython-313.pyc +0 -0
  188. package/codex-lens/src/codexlens/semantic/__pycache__/base.cpython-313.pyc +0 -0
  189. package/codex-lens/src/codexlens/semantic/__pycache__/chunker.cpython-313.pyc +0 -0
  190. package/codex-lens/src/codexlens/semantic/__pycache__/embedder.cpython-313.pyc +0 -0
  191. package/codex-lens/src/codexlens/semantic/__pycache__/factory.cpython-313.pyc +0 -0
  192. package/codex-lens/src/codexlens/semantic/__pycache__/gpu_support.cpython-313.pyc +0 -0
  193. package/codex-lens/src/codexlens/semantic/__pycache__/litellm_embedder.cpython-313.pyc +0 -0
  194. package/codex-lens/src/codexlens/semantic/__pycache__/vector_store.cpython-313.pyc +0 -0
  195. package/codex-lens/src/codexlens/semantic/base.py +61 -0
  196. package/codex-lens/src/codexlens/semantic/chunker.py +43 -20
  197. package/codex-lens/src/codexlens/semantic/embedder.py +60 -13
  198. package/codex-lens/src/codexlens/semantic/factory.py +98 -0
  199. package/codex-lens/src/codexlens/semantic/gpu_support.py +225 -3
  200. package/codex-lens/src/codexlens/semantic/litellm_embedder.py +144 -0
  201. package/codex-lens/src/codexlens/semantic/rotational_embedder.py +434 -0
  202. package/codex-lens/src/codexlens/semantic/vector_store.py +33 -8
  203. package/codex-lens/src/codexlens/storage/__pycache__/path_mapper.cpython-313.pyc +0 -0
  204. package/codex-lens/src/codexlens/storage/migrations/__pycache__/migration_004_dual_fts.cpython-313.pyc +0 -0
  205. package/codex-lens/src/codexlens/storage/path_mapper.py +27 -1
  206. package/package.json +15 -5
  207. package/.codex/prompts.zip +0 -0
  208. 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
+ }