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,246 @@
1
+ /**
2
+ * LiteLLM Client - Bridge between CCW and ccw-litellm Python package
3
+ * Provides LLM chat and embedding capabilities via spawned Python process
4
+ *
5
+ * Features:
6
+ * - Chat completions with multiple models
7
+ * - Text embeddings generation
8
+ * - Configuration management
9
+ * - JSON protocol communication
10
+ */
11
+
12
+ import { spawn } from 'child_process';
13
+ import { promisify } from 'util';
14
+
15
+ export interface LiteLLMConfig {
16
+ pythonPath?: string; // Default 'python'
17
+ configPath?: string; // Configuration file path
18
+ timeout?: number; // Default 60000ms
19
+ }
20
+
21
+ export interface ChatMessage {
22
+ role: 'system' | 'user' | 'assistant';
23
+ content: string;
24
+ }
25
+
26
+ export interface ChatResponse {
27
+ content: string;
28
+ model: string;
29
+ usage?: {
30
+ prompt_tokens: number;
31
+ completion_tokens: number;
32
+ total_tokens: number;
33
+ };
34
+ }
35
+
36
+ export interface EmbedResponse {
37
+ vectors: number[][];
38
+ dimensions: number;
39
+ model: string;
40
+ }
41
+
42
+ export interface LiteLLMStatus {
43
+ available: boolean;
44
+ version?: string;
45
+ error?: string;
46
+ }
47
+
48
+ export class LiteLLMClient {
49
+ private pythonPath: string;
50
+ private configPath?: string;
51
+ private timeout: number;
52
+
53
+ constructor(config: LiteLLMConfig = {}) {
54
+ this.pythonPath = config.pythonPath || 'python';
55
+ this.configPath = config.configPath;
56
+ this.timeout = config.timeout || 60000;
57
+ }
58
+
59
+ /**
60
+ * Execute Python ccw-litellm command
61
+ */
62
+ private async executePython(args: string[], options: { timeout?: number } = {}): Promise<string> {
63
+ const timeout = options.timeout || this.timeout;
64
+
65
+ return new Promise((resolve, reject) => {
66
+ const proc = spawn(this.pythonPath, ['-m', 'ccw_litellm.cli', ...args], {
67
+ stdio: ['pipe', 'pipe', 'pipe'],
68
+ env: { ...process.env }
69
+ });
70
+
71
+ let stdout = '';
72
+ let stderr = '';
73
+ let timedOut = false;
74
+
75
+ // Set up timeout
76
+ const timeoutId = setTimeout(() => {
77
+ timedOut = true;
78
+ proc.kill('SIGTERM');
79
+ reject(new Error(`Command timed out after ${timeout}ms`));
80
+ }, timeout);
81
+
82
+ proc.stdout.on('data', (data) => {
83
+ stdout += data.toString();
84
+ });
85
+
86
+ proc.stderr.on('data', (data) => {
87
+ stderr += data.toString();
88
+ });
89
+
90
+ proc.on('error', (error) => {
91
+ clearTimeout(timeoutId);
92
+ reject(new Error(`Failed to spawn Python process: ${error.message}`));
93
+ });
94
+
95
+ proc.on('close', (code) => {
96
+ clearTimeout(timeoutId);
97
+
98
+ if (timedOut) {
99
+ return; // Already rejected
100
+ }
101
+
102
+ if (code === 0) {
103
+ resolve(stdout.trim());
104
+ } else {
105
+ const errorMsg = stderr.trim() || `Process exited with code ${code}`;
106
+ reject(new Error(errorMsg));
107
+ }
108
+ });
109
+ });
110
+ }
111
+
112
+ /**
113
+ * Check if ccw-litellm is available
114
+ */
115
+ async isAvailable(): Promise<boolean> {
116
+ try {
117
+ await this.executePython(['version'], { timeout: 5000 });
118
+ return true;
119
+ } catch {
120
+ return false;
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Get status information
126
+ */
127
+ async getStatus(): Promise<LiteLLMStatus> {
128
+ try {
129
+ const output = await this.executePython(['version'], { timeout: 5000 });
130
+ return {
131
+ available: true,
132
+ version: output.trim()
133
+ };
134
+ } catch (error: any) {
135
+ return {
136
+ available: false,
137
+ error: error.message
138
+ };
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Get current configuration
144
+ */
145
+ async getConfig(): Promise<any> {
146
+ const output = await this.executePython(['config', '--json']);
147
+ return JSON.parse(output);
148
+ }
149
+
150
+ /**
151
+ * Generate embeddings for texts
152
+ */
153
+ async embed(texts: string[], model: string = 'default'): Promise<EmbedResponse> {
154
+ if (!texts || texts.length === 0) {
155
+ throw new Error('texts array cannot be empty');
156
+ }
157
+
158
+ const args = ['embed', '--model', model, '--output', 'json'];
159
+
160
+ // Add texts as arguments
161
+ for (const text of texts) {
162
+ args.push(text);
163
+ }
164
+
165
+ const output = await this.executePython(args, { timeout: this.timeout * 2 });
166
+ const vectors = JSON.parse(output);
167
+
168
+ return {
169
+ vectors,
170
+ dimensions: vectors[0]?.length || 0,
171
+ model
172
+ };
173
+ }
174
+
175
+ /**
176
+ * Chat with LLM
177
+ */
178
+ async chat(message: string, model: string = 'default'): Promise<string> {
179
+ if (!message) {
180
+ throw new Error('message cannot be empty');
181
+ }
182
+
183
+ const args = ['chat', '--model', model, message];
184
+ return this.executePython(args, { timeout: this.timeout * 2 });
185
+ }
186
+
187
+ /**
188
+ * Multi-turn chat with messages array
189
+ */
190
+ async chatMessages(messages: ChatMessage[], model: string = 'default'): Promise<ChatResponse> {
191
+ if (!messages || messages.length === 0) {
192
+ throw new Error('messages array cannot be empty');
193
+ }
194
+
195
+ // For now, just use the last user message
196
+ // TODO: Implement full message history support in ccw-litellm
197
+ const lastMessage = messages[messages.length - 1];
198
+ const content = await this.chat(lastMessage.content, model);
199
+
200
+ return {
201
+ content,
202
+ model,
203
+ usage: undefined // TODO: Add usage tracking
204
+ };
205
+ }
206
+ }
207
+
208
+ // Singleton instance
209
+ let _client: LiteLLMClient | null = null;
210
+
211
+ /**
212
+ * Get or create singleton LiteLLM client
213
+ */
214
+ export function getLiteLLMClient(config?: LiteLLMConfig): LiteLLMClient {
215
+ if (!_client) {
216
+ _client = new LiteLLMClient(config);
217
+ }
218
+ return _client;
219
+ }
220
+
221
+ /**
222
+ * Check if LiteLLM is available
223
+ */
224
+ export async function checkLiteLLMAvailable(): Promise<boolean> {
225
+ try {
226
+ const client = getLiteLLMClient();
227
+ return await client.isAvailable();
228
+ } catch {
229
+ return false;
230
+ }
231
+ }
232
+
233
+ /**
234
+ * Get LiteLLM status
235
+ */
236
+ export async function getLiteLLMStatus(): Promise<LiteLLMStatus> {
237
+ try {
238
+ const client = getLiteLLMClient();
239
+ return await client.getStatus();
240
+ } catch (error: any) {
241
+ return {
242
+ available: false,
243
+ error: error.message
244
+ };
245
+ }
246
+ }
@@ -0,0 +1,241 @@
1
+ /**
2
+ * LiteLLM Executor - Execute LiteLLM endpoints with context caching
3
+ * Integrates with context-cache for file packing and LiteLLM client for API calls
4
+ */
5
+
6
+ import { getLiteLLMClient } from './litellm-client.js';
7
+ import { handler as contextCacheHandler } from './context-cache.js';
8
+ import {
9
+ findEndpointById,
10
+ getProviderWithResolvedEnvVars,
11
+ } from '../config/litellm-api-config-manager.js';
12
+ import type { CustomEndpoint, ProviderCredential } from '../types/litellm-api-config.js';
13
+
14
+ export interface LiteLLMExecutionOptions {
15
+ prompt: string;
16
+ endpointId: string; // Custom endpoint ID (e.g., "my-gpt4o")
17
+ baseDir: string; // Project base directory
18
+ cwd?: string; // Working directory for file resolution
19
+ includeDirs?: string[]; // Additional directories for @patterns
20
+ enableCache?: boolean; // Override endpoint cache setting
21
+ onOutput?: (data: { type: string; data: string }) => void;
22
+ }
23
+
24
+ export interface LiteLLMExecutionResult {
25
+ success: boolean;
26
+ output: string;
27
+ model: string;
28
+ provider: string;
29
+ cacheUsed: boolean;
30
+ cachedFiles?: string[];
31
+ error?: string;
32
+ }
33
+
34
+ /**
35
+ * Extract @patterns from prompt text
36
+ */
37
+ export function extractPatterns(prompt: string): string[] {
38
+ // Match @path patterns: @src/**/*.ts, @CLAUDE.md, @../shared/**/*
39
+ const regex = /@([^\s]+)/g;
40
+ const patterns: string[] = [];
41
+ let match;
42
+ while ((match = regex.exec(prompt)) !== null) {
43
+ patterns.push('@' + match[1]);
44
+ }
45
+ return patterns;
46
+ }
47
+
48
+ /**
49
+ * Execute LiteLLM endpoint with optional context caching
50
+ */
51
+ export async function executeLiteLLMEndpoint(
52
+ options: LiteLLMExecutionOptions
53
+ ): Promise<LiteLLMExecutionResult> {
54
+ const { prompt, endpointId, baseDir, cwd, includeDirs, enableCache, onOutput } = options;
55
+
56
+ // 1. Find endpoint configuration
57
+ const endpoint = findEndpointById(baseDir, endpointId);
58
+ if (!endpoint) {
59
+ return {
60
+ success: false,
61
+ output: '',
62
+ model: '',
63
+ provider: '',
64
+ cacheUsed: false,
65
+ error: `Endpoint not found: ${endpointId}`,
66
+ };
67
+ }
68
+
69
+ // 2. Get provider with resolved env vars
70
+ const provider = getProviderWithResolvedEnvVars(baseDir, endpoint.providerId);
71
+ if (!provider) {
72
+ return {
73
+ success: false,
74
+ output: '',
75
+ model: '',
76
+ provider: '',
77
+ cacheUsed: false,
78
+ error: `Provider not found: ${endpoint.providerId}`,
79
+ };
80
+ }
81
+
82
+ // Verify API key is available
83
+ if (!provider.resolvedApiKey) {
84
+ return {
85
+ success: false,
86
+ output: '',
87
+ model: endpoint.model,
88
+ provider: provider.type,
89
+ cacheUsed: false,
90
+ error: `API key not configured for provider: ${provider.name}`,
91
+ };
92
+ }
93
+
94
+ // 3. Process context cache if enabled
95
+ let finalPrompt = prompt;
96
+ let cacheUsed = false;
97
+ let cachedFiles: string[] = [];
98
+
99
+ const shouldCache = enableCache ?? endpoint.cacheStrategy.enabled;
100
+ if (shouldCache) {
101
+ const patterns = extractPatterns(prompt);
102
+ if (patterns.length > 0) {
103
+ if (onOutput) {
104
+ onOutput({ type: 'stderr', data: `[Context cache: Found ${patterns.length} @patterns]\n` });
105
+ }
106
+
107
+ // Pack files into cache
108
+ const packResult = await contextCacheHandler({
109
+ operation: 'pack',
110
+ patterns,
111
+ cwd: cwd || process.cwd(),
112
+ include_dirs: includeDirs,
113
+ ttl: endpoint.cacheStrategy.ttlMinutes * 60 * 1000,
114
+ max_file_size: endpoint.cacheStrategy.maxSizeKB * 1024,
115
+ });
116
+
117
+ if (packResult.success && packResult.result) {
118
+ const pack = packResult.result as any;
119
+
120
+ if (onOutput) {
121
+ onOutput({
122
+ type: 'stderr',
123
+ data: `[Context cache: Packed ${pack.files_packed} files, ${pack.total_bytes} bytes]\n`,
124
+ });
125
+ }
126
+
127
+ // Read cached content
128
+ const readResult = await contextCacheHandler({
129
+ operation: 'read',
130
+ session_id: pack.session_id,
131
+ limit: endpoint.cacheStrategy.maxSizeKB * 1024,
132
+ });
133
+
134
+ if (readResult.success && readResult.result) {
135
+ const read = readResult.result as any;
136
+ // Prepend cached content to prompt
137
+ finalPrompt = `${read.content}\n\n---\n\n${prompt}`;
138
+ cacheUsed = true;
139
+ cachedFiles = pack.files_packed ? Array(pack.files_packed).fill('...') : [];
140
+
141
+ if (onOutput) {
142
+ onOutput({ type: 'stderr', data: `[Context cache: Applied to prompt]\n` });
143
+ }
144
+ }
145
+ } else if (packResult.error) {
146
+ if (onOutput) {
147
+ onOutput({ type: 'stderr', data: `[Context cache warning: ${packResult.error}]\n` });
148
+ }
149
+ }
150
+ }
151
+ }
152
+
153
+ // 4. Call LiteLLM
154
+ try {
155
+ if (onOutput) {
156
+ onOutput({
157
+ type: 'stderr',
158
+ data: `[LiteLLM: Calling ${provider.type}/${endpoint.model}]\n`,
159
+ });
160
+ }
161
+
162
+ const client = getLiteLLMClient({
163
+ pythonPath: 'python',
164
+ timeout: 120000, // 2 minutes
165
+ });
166
+
167
+ // Configure provider credentials via environment
168
+ // LiteLLM uses standard env vars like OPENAI_API_KEY, ANTHROPIC_API_KEY
169
+ const envVarName = getProviderEnvVarName(provider.type);
170
+ if (envVarName) {
171
+ process.env[envVarName] = provider.resolvedApiKey;
172
+ }
173
+
174
+ // Set base URL if custom
175
+ if (provider.apiBase) {
176
+ const baseUrlEnvVar = getProviderBaseUrlEnvVarName(provider.type);
177
+ if (baseUrlEnvVar) {
178
+ process.env[baseUrlEnvVar] = provider.apiBase;
179
+ }
180
+ }
181
+
182
+ // Use litellm-client to call chat
183
+ const response = await client.chat(finalPrompt, endpoint.model);
184
+
185
+ if (onOutput) {
186
+ onOutput({ type: 'stdout', data: response });
187
+ }
188
+
189
+ return {
190
+ success: true,
191
+ output: response,
192
+ model: endpoint.model,
193
+ provider: provider.type,
194
+ cacheUsed,
195
+ cachedFiles,
196
+ };
197
+ } catch (error) {
198
+ const errorMsg = (error as Error).message;
199
+ if (onOutput) {
200
+ onOutput({ type: 'stderr', data: `[LiteLLM error: ${errorMsg}]\n` });
201
+ }
202
+
203
+ return {
204
+ success: false,
205
+ output: '',
206
+ model: endpoint.model,
207
+ provider: provider.type,
208
+ cacheUsed,
209
+ error: errorMsg,
210
+ };
211
+ }
212
+ }
213
+
214
+ /**
215
+ * Get environment variable name for provider API key
216
+ */
217
+ function getProviderEnvVarName(providerType: string): string | null {
218
+ const envVarMap: Record<string, string> = {
219
+ openai: 'OPENAI_API_KEY',
220
+ anthropic: 'ANTHROPIC_API_KEY',
221
+ google: 'GOOGLE_API_KEY',
222
+ azure: 'AZURE_API_KEY',
223
+ mistral: 'MISTRAL_API_KEY',
224
+ deepseek: 'DEEPSEEK_API_KEY',
225
+ };
226
+
227
+ return envVarMap[providerType] || null;
228
+ }
229
+
230
+ /**
231
+ * Get environment variable name for provider base URL
232
+ */
233
+ function getProviderBaseUrlEnvVarName(providerType: string): string | null {
234
+ const envVarMap: Record<string, string> = {
235
+ openai: 'OPENAI_API_BASE',
236
+ anthropic: 'ANTHROPIC_API_BASE',
237
+ azure: 'AZURE_API_BASE',
238
+ };
239
+
240
+ return envVarMap[providerType] || null;
241
+ }