tlc-claude-code 1.4.9 → 1.5.2

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 (122) hide show
  1. package/CLAUDE.md +23 -0
  2. package/CODING-STANDARDS.md +408 -0
  3. package/bin/install.js +2 -0
  4. package/dashboard/dist/components/QualityGatePane.d.ts +38 -0
  5. package/dashboard/dist/components/QualityGatePane.js +31 -0
  6. package/dashboard/dist/components/QualityGatePane.test.d.ts +1 -0
  7. package/dashboard/dist/components/QualityGatePane.test.js +147 -0
  8. package/dashboard/dist/components/orchestration/AgentCard.d.ts +26 -0
  9. package/dashboard/dist/components/orchestration/AgentCard.js +60 -0
  10. package/dashboard/dist/components/orchestration/AgentCard.test.d.ts +1 -0
  11. package/dashboard/dist/components/orchestration/AgentCard.test.js +63 -0
  12. package/dashboard/dist/components/orchestration/AgentControls.d.ts +11 -0
  13. package/dashboard/dist/components/orchestration/AgentControls.js +20 -0
  14. package/dashboard/dist/components/orchestration/AgentControls.test.d.ts +1 -0
  15. package/dashboard/dist/components/orchestration/AgentControls.test.js +52 -0
  16. package/dashboard/dist/components/orchestration/AgentDetail.d.ts +35 -0
  17. package/dashboard/dist/components/orchestration/AgentDetail.js +37 -0
  18. package/dashboard/dist/components/orchestration/AgentDetail.test.d.ts +1 -0
  19. package/dashboard/dist/components/orchestration/AgentDetail.test.js +79 -0
  20. package/dashboard/dist/components/orchestration/AgentList.d.ts +31 -0
  21. package/dashboard/dist/components/orchestration/AgentList.js +47 -0
  22. package/dashboard/dist/components/orchestration/AgentList.test.d.ts +1 -0
  23. package/dashboard/dist/components/orchestration/AgentList.test.js +64 -0
  24. package/dashboard/dist/components/orchestration/CostMeter.d.ts +11 -0
  25. package/dashboard/dist/components/orchestration/CostMeter.js +28 -0
  26. package/dashboard/dist/components/orchestration/CostMeter.test.d.ts +1 -0
  27. package/dashboard/dist/components/orchestration/CostMeter.test.js +50 -0
  28. package/dashboard/dist/components/orchestration/ModelSelector.d.ts +20 -0
  29. package/dashboard/dist/components/orchestration/ModelSelector.js +12 -0
  30. package/dashboard/dist/components/orchestration/ModelSelector.test.d.ts +1 -0
  31. package/dashboard/dist/components/orchestration/ModelSelector.test.js +56 -0
  32. package/dashboard/dist/components/orchestration/OrchestrationDashboard.d.ts +28 -0
  33. package/dashboard/dist/components/orchestration/OrchestrationDashboard.js +28 -0
  34. package/dashboard/dist/components/orchestration/OrchestrationDashboard.test.d.ts +1 -0
  35. package/dashboard/dist/components/orchestration/OrchestrationDashboard.test.js +56 -0
  36. package/dashboard/dist/components/orchestration/QualityIndicator.d.ts +11 -0
  37. package/dashboard/dist/components/orchestration/QualityIndicator.js +37 -0
  38. package/dashboard/dist/components/orchestration/QualityIndicator.test.d.ts +1 -0
  39. package/dashboard/dist/components/orchestration/QualityIndicator.test.js +52 -0
  40. package/dashboard/dist/components/orchestration/index.d.ts +8 -0
  41. package/dashboard/dist/components/orchestration/index.js +8 -0
  42. package/package.json +1 -1
  43. package/server/lib/access-control.js +352 -0
  44. package/server/lib/access-control.test.js +322 -0
  45. package/server/lib/agents-cancel-command.js +139 -0
  46. package/server/lib/agents-cancel-command.test.js +180 -0
  47. package/server/lib/agents-get-command.js +159 -0
  48. package/server/lib/agents-get-command.test.js +167 -0
  49. package/server/lib/agents-list-command.js +150 -0
  50. package/server/lib/agents-list-command.test.js +149 -0
  51. package/server/lib/agents-logs-command.js +126 -0
  52. package/server/lib/agents-logs-command.test.js +198 -0
  53. package/server/lib/agents-retry-command.js +117 -0
  54. package/server/lib/agents-retry-command.test.js +192 -0
  55. package/server/lib/budget-limits.js +222 -0
  56. package/server/lib/budget-limits.test.js +214 -0
  57. package/server/lib/code-generator.js +291 -0
  58. package/server/lib/code-generator.test.js +307 -0
  59. package/server/lib/cost-command.js +290 -0
  60. package/server/lib/cost-command.test.js +202 -0
  61. package/server/lib/cost-optimizer.js +404 -0
  62. package/server/lib/cost-optimizer.test.js +232 -0
  63. package/server/lib/cost-projections.js +302 -0
  64. package/server/lib/cost-projections.test.js +217 -0
  65. package/server/lib/cost-reports.js +277 -0
  66. package/server/lib/cost-reports.test.js +254 -0
  67. package/server/lib/cost-tracker.js +216 -0
  68. package/server/lib/cost-tracker.test.js +302 -0
  69. package/server/lib/crypto-patterns.js +433 -0
  70. package/server/lib/crypto-patterns.test.js +346 -0
  71. package/server/lib/design-command.js +385 -0
  72. package/server/lib/design-command.test.js +249 -0
  73. package/server/lib/design-parser.js +237 -0
  74. package/server/lib/design-parser.test.js +290 -0
  75. package/server/lib/gemini-vision.js +377 -0
  76. package/server/lib/gemini-vision.test.js +282 -0
  77. package/server/lib/input-validator.js +360 -0
  78. package/server/lib/input-validator.test.js +295 -0
  79. package/server/lib/litellm-client.js +232 -0
  80. package/server/lib/litellm-client.test.js +267 -0
  81. package/server/lib/litellm-command.js +291 -0
  82. package/server/lib/litellm-command.test.js +260 -0
  83. package/server/lib/litellm-config.js +273 -0
  84. package/server/lib/litellm-config.test.js +212 -0
  85. package/server/lib/model-pricing.js +189 -0
  86. package/server/lib/model-pricing.test.js +178 -0
  87. package/server/lib/models-command.js +223 -0
  88. package/server/lib/models-command.test.js +193 -0
  89. package/server/lib/optimize-command.js +197 -0
  90. package/server/lib/optimize-command.test.js +193 -0
  91. package/server/lib/orchestration-integration.js +206 -0
  92. package/server/lib/orchestration-integration.test.js +235 -0
  93. package/server/lib/output-encoder.js +308 -0
  94. package/server/lib/output-encoder.test.js +312 -0
  95. package/server/lib/quality-evaluator.js +396 -0
  96. package/server/lib/quality-evaluator.test.js +337 -0
  97. package/server/lib/quality-gate-command.js +340 -0
  98. package/server/lib/quality-gate-command.test.js +321 -0
  99. package/server/lib/quality-gate-scorer.js +378 -0
  100. package/server/lib/quality-gate-scorer.test.js +376 -0
  101. package/server/lib/quality-history.js +265 -0
  102. package/server/lib/quality-history.test.js +359 -0
  103. package/server/lib/quality-presets.js +288 -0
  104. package/server/lib/quality-presets.test.js +269 -0
  105. package/server/lib/quality-retry.js +323 -0
  106. package/server/lib/quality-retry.test.js +325 -0
  107. package/server/lib/quality-thresholds.js +255 -0
  108. package/server/lib/quality-thresholds.test.js +237 -0
  109. package/server/lib/secure-auth.js +333 -0
  110. package/server/lib/secure-auth.test.js +288 -0
  111. package/server/lib/secure-code-command.js +540 -0
  112. package/server/lib/secure-code-command.test.js +309 -0
  113. package/server/lib/secure-errors.js +521 -0
  114. package/server/lib/secure-errors.test.js +298 -0
  115. package/server/lib/vision-command.js +372 -0
  116. package/server/lib/vision-command.test.js +255 -0
  117. package/server/lib/visual-command.js +350 -0
  118. package/server/lib/visual-command.test.js +256 -0
  119. package/server/lib/visual-testing.js +315 -0
  120. package/server/lib/visual-testing.test.js +357 -0
  121. package/server/package-lock.json +2 -2
  122. package/server/package.json +1 -1
@@ -0,0 +1,232 @@
1
+ /**
2
+ * LiteLLM Client Module
3
+ *
4
+ * Client for interacting with LiteLLM proxy
5
+ */
6
+
7
+ /**
8
+ * Create a LiteLLM client
9
+ * @param {Object} options - Client options
10
+ * @param {string} options.baseUrl - LiteLLM proxy URL
11
+ * @param {string} [options.apiKey] - API key for authentication
12
+ * @returns {Object} Client instance
13
+ */
14
+ function createClient(options) {
15
+ const { baseUrl, apiKey } = options;
16
+
17
+ return {
18
+ baseUrl,
19
+ apiKey,
20
+ _fetch: globalThis.fetch,
21
+ };
22
+ }
23
+
24
+ /**
25
+ * Make a completion request
26
+ * @param {Object} client - Client instance
27
+ * @param {Object} options - Completion options
28
+ * @param {string} options.model - Model to use
29
+ * @param {string} options.prompt - Input prompt
30
+ * @param {number} [options.maxTokens] - Maximum tokens
31
+ * @param {number} [options.temperature] - Temperature
32
+ * @returns {Promise<Object>} Completion response
33
+ */
34
+ async function completion(client, options) {
35
+ const { model, prompt, maxTokens, temperature } = options;
36
+
37
+ const body = {
38
+ model,
39
+ prompt,
40
+ };
41
+
42
+ if (maxTokens) body.max_tokens = maxTokens;
43
+ if (temperature !== undefined) body.temperature = temperature;
44
+
45
+ const response = await client._fetch(`${client.baseUrl}/v1/completions`, {
46
+ method: 'POST',
47
+ headers: {
48
+ 'Content-Type': 'application/json',
49
+ ...(client.apiKey && { 'Authorization': `Bearer ${client.apiKey}` }),
50
+ },
51
+ body: JSON.stringify(body),
52
+ });
53
+
54
+ if (!response.ok) {
55
+ const error = await response.text();
56
+ throw new Error(`Completion failed: ${error}`);
57
+ }
58
+
59
+ return response.json();
60
+ }
61
+
62
+ /**
63
+ * Make a chat completion request
64
+ * @param {Object} client - Client instance
65
+ * @param {Object} options - Chat options
66
+ * @param {string} options.model - Model to use
67
+ * @param {Array} options.messages - Chat messages
68
+ * @param {boolean} [options.stream] - Enable streaming
69
+ * @param {number} [options.maxTokens] - Maximum tokens
70
+ * @param {number} [options.temperature] - Temperature
71
+ * @returns {Promise<Object>} Chat response
72
+ */
73
+ async function chat(client, options) {
74
+ const { model, messages, stream, maxTokens, temperature } = options;
75
+
76
+ const body = {
77
+ model,
78
+ messages,
79
+ stream: stream || false,
80
+ };
81
+
82
+ if (maxTokens) body.max_tokens = maxTokens;
83
+ if (temperature !== undefined) body.temperature = temperature;
84
+
85
+ const response = await client._fetch(`${client.baseUrl}/v1/chat/completions`, {
86
+ method: 'POST',
87
+ headers: {
88
+ 'Content-Type': 'application/json',
89
+ ...(client.apiKey && { 'Authorization': `Bearer ${client.apiKey}` }),
90
+ },
91
+ body: JSON.stringify(body),
92
+ });
93
+
94
+ if (!response.ok) {
95
+ const error = await response.text();
96
+ throw new Error(`Chat failed: ${error}`);
97
+ }
98
+
99
+ if (stream && response.body) {
100
+ return { stream: response.body };
101
+ }
102
+
103
+ return response.json();
104
+ }
105
+
106
+ /**
107
+ * Get usage statistics
108
+ * @param {Object} client - Client instance
109
+ * @param {Object} [options] - Query options
110
+ * @param {string} [options.startDate] - Start date
111
+ * @param {string} [options.endDate] - End date
112
+ * @returns {Promise<Object>} Usage statistics
113
+ */
114
+ async function getUsage(client, options = {}) {
115
+ const { startDate, endDate } = options;
116
+
117
+ let url = `${client.baseUrl}/spend/logs`;
118
+ const params = new URLSearchParams();
119
+
120
+ if (startDate) params.set('start_date', startDate);
121
+ if (endDate) params.set('end_date', endDate);
122
+
123
+ if (params.toString()) {
124
+ url += `?${params.toString()}`;
125
+ }
126
+
127
+ const response = await client._fetch(url, {
128
+ headers: {
129
+ ...(client.apiKey && { 'Authorization': `Bearer ${client.apiKey}` }),
130
+ },
131
+ });
132
+
133
+ if (!response.ok) {
134
+ throw new Error('Failed to fetch usage');
135
+ }
136
+
137
+ return response.json();
138
+ }
139
+
140
+ /**
141
+ * Get available models
142
+ * @param {Object} client - Client instance
143
+ * @returns {Promise<Array>} List of models
144
+ */
145
+ async function getModels(client) {
146
+ const response = await client._fetch(`${client.baseUrl}/v1/models`, {
147
+ headers: {
148
+ ...(client.apiKey && { 'Authorization': `Bearer ${client.apiKey}` }),
149
+ },
150
+ });
151
+
152
+ if (!response.ok) {
153
+ throw new Error('Failed to fetch models');
154
+ }
155
+
156
+ const result = await response.json();
157
+ return result.data || [];
158
+ }
159
+
160
+ /**
161
+ * Check proxy health
162
+ * @param {Object} client - Client instance
163
+ * @returns {Promise<Object>} Health status
164
+ */
165
+ async function healthCheck(client) {
166
+ try {
167
+ const response = await client._fetch(`${client.baseUrl}/health`, {
168
+ headers: {
169
+ ...(client.apiKey && { 'Authorization': `Bearer ${client.apiKey}` }),
170
+ },
171
+ });
172
+
173
+ if (!response.ok) {
174
+ return { status: 'unhealthy', error: 'Bad response' };
175
+ }
176
+
177
+ const result = await response.json();
178
+ return { status: result.status || 'healthy', ...result };
179
+ } catch (error) {
180
+ return { status: 'unhealthy', error: error.message };
181
+ }
182
+ }
183
+
184
+ /**
185
+ * Set API key
186
+ * @param {Object} client - Client instance
187
+ * @param {string} apiKey - New API key
188
+ */
189
+ function setApiKey(client, apiKey) {
190
+ client.apiKey = apiKey;
191
+ }
192
+
193
+ /**
194
+ * Execute request with fallback
195
+ * @param {Object} client - Client instance
196
+ * @param {Object} options - Fallback options
197
+ * @param {string} options.primary - Primary model
198
+ * @param {string[]} options.fallbacks - Fallback models
199
+ * @param {Object} options.request - Request options
200
+ * @returns {Promise<Object>} Response
201
+ */
202
+ async function withFallback(client, options) {
203
+ const { primary, fallbacks, request } = options;
204
+
205
+ const models = [primary, ...fallbacks];
206
+ const errors = [];
207
+
208
+ for (const model of models) {
209
+ try {
210
+ const result = await completion(client, {
211
+ ...request,
212
+ model,
213
+ });
214
+ return result;
215
+ } catch (error) {
216
+ errors.push({ model, error: error.message });
217
+ }
218
+ }
219
+
220
+ throw new Error(`All models failed: ${errors.map(e => `${e.model}: ${e.error}`).join(', ')}`);
221
+ }
222
+
223
+ module.exports = {
224
+ createClient,
225
+ completion,
226
+ chat,
227
+ getUsage,
228
+ getModels,
229
+ healthCheck,
230
+ setApiKey,
231
+ withFallback,
232
+ };
@@ -0,0 +1,267 @@
1
+ /**
2
+ * LiteLLM Client Tests
3
+ *
4
+ * Client for interacting with LiteLLM proxy
5
+ */
6
+
7
+ const { describe, it, beforeEach } = require('node:test');
8
+ const assert = require('node:assert');
9
+
10
+ const {
11
+ createClient,
12
+ completion,
13
+ chat,
14
+ getUsage,
15
+ getModels,
16
+ healthCheck,
17
+ setApiKey,
18
+ withFallback,
19
+ } = require('./litellm-client.js');
20
+
21
+ describe('LiteLLM Client', () => {
22
+ let client;
23
+
24
+ beforeEach(() => {
25
+ client = createClient({
26
+ baseUrl: 'http://localhost:4000',
27
+ });
28
+ });
29
+
30
+ describe('createClient', () => {
31
+ it('creates client with base URL', () => {
32
+ assert.ok(client);
33
+ assert.strictEqual(client.baseUrl, 'http://localhost:4000');
34
+ });
35
+
36
+ it('accepts API key', () => {
37
+ const clientWithKey = createClient({
38
+ baseUrl: 'http://localhost:4000',
39
+ apiKey: 'test-key',
40
+ });
41
+
42
+ assert.ok(clientWithKey.apiKey);
43
+ });
44
+ });
45
+
46
+ describe('completion', () => {
47
+ it('sends completion request', async () => {
48
+ // Mock the fetch for testing
49
+ client._fetch = async (url, options) => ({
50
+ ok: true,
51
+ json: async () => ({
52
+ choices: [{ text: 'Hello world' }],
53
+ usage: { prompt_tokens: 10, completion_tokens: 5 },
54
+ }),
55
+ });
56
+
57
+ const result = await completion(client, {
58
+ model: 'claude-3-sonnet',
59
+ prompt: 'Say hello',
60
+ });
61
+
62
+ assert.ok(result.choices);
63
+ assert.strictEqual(result.choices[0].text, 'Hello world');
64
+ });
65
+
66
+ it('includes usage stats', async () => {
67
+ client._fetch = async () => ({
68
+ ok: true,
69
+ json: async () => ({
70
+ choices: [{ text: 'Response' }],
71
+ usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 },
72
+ }),
73
+ });
74
+
75
+ const result = await completion(client, {
76
+ model: 'claude-3-sonnet',
77
+ prompt: 'Test',
78
+ });
79
+
80
+ assert.ok(result.usage);
81
+ assert.strictEqual(result.usage.total_tokens, 15);
82
+ });
83
+ });
84
+
85
+ describe('chat', () => {
86
+ it('sends chat request', async () => {
87
+ client._fetch = async () => ({
88
+ ok: true,
89
+ json: async () => ({
90
+ choices: [{ message: { role: 'assistant', content: 'Hello!' } }],
91
+ usage: { prompt_tokens: 10, completion_tokens: 5 },
92
+ }),
93
+ });
94
+
95
+ const result = await chat(client, {
96
+ model: 'claude-3-sonnet',
97
+ messages: [{ role: 'user', content: 'Hi' }],
98
+ });
99
+
100
+ assert.ok(result.choices);
101
+ assert.strictEqual(result.choices[0].message.content, 'Hello!');
102
+ });
103
+
104
+ it('handles streaming', async () => {
105
+ let chunks = [];
106
+ client._fetch = async () => ({
107
+ ok: true,
108
+ body: {
109
+ getReader: () => ({
110
+ read: async () => {
111
+ if (chunks.length < 3) {
112
+ chunks.push({ content: 'chunk' });
113
+ return { done: false, value: new TextEncoder().encode('data: {"choices":[{"delta":{"content":"chunk"}}]}\n\n') };
114
+ }
115
+ return { done: true };
116
+ },
117
+ }),
118
+ },
119
+ });
120
+
121
+ const result = await chat(client, {
122
+ model: 'claude-3-sonnet',
123
+ messages: [{ role: 'user', content: 'Hi' }],
124
+ stream: true,
125
+ });
126
+
127
+ assert.ok(result.stream || result.choices);
128
+ });
129
+ });
130
+
131
+ describe('getUsage', () => {
132
+ it('returns usage statistics', async () => {
133
+ client._fetch = async () => ({
134
+ ok: true,
135
+ json: async () => ({
136
+ total_spend: 15.50,
137
+ total_tokens: 50000,
138
+ by_model: {
139
+ 'claude-3-sonnet': { spend: 10.00, tokens: 30000 },
140
+ 'gpt-4': { spend: 5.50, tokens: 20000 },
141
+ },
142
+ }),
143
+ });
144
+
145
+ const usage = await getUsage(client);
146
+
147
+ assert.ok(usage.total_spend >= 0);
148
+ assert.ok(usage.by_model);
149
+ });
150
+
151
+ it('filters by date range', async () => {
152
+ let capturedOptions;
153
+ client._fetch = async (url, options) => {
154
+ capturedOptions = { url };
155
+ return {
156
+ ok: true,
157
+ json: async () => ({ total_spend: 5.00, by_model: {} }),
158
+ };
159
+ };
160
+
161
+ await getUsage(client, {
162
+ startDate: '2025-01-01',
163
+ endDate: '2025-01-31',
164
+ });
165
+
166
+ assert.ok(capturedOptions.url.includes('start') || capturedOptions.url.includes('2025'));
167
+ });
168
+ });
169
+
170
+ describe('getModels', () => {
171
+ it('lists available models', async () => {
172
+ client._fetch = async () => ({
173
+ ok: true,
174
+ json: async () => ({
175
+ data: [
176
+ { id: 'claude-3-opus', provider: 'anthropic' },
177
+ { id: 'gpt-4', provider: 'openai' },
178
+ ],
179
+ }),
180
+ });
181
+
182
+ const models = await getModels(client);
183
+
184
+ assert.ok(Array.isArray(models));
185
+ assert.ok(models.length > 0);
186
+ });
187
+ });
188
+
189
+ describe('healthCheck', () => {
190
+ it('returns healthy status', async () => {
191
+ client._fetch = async () => ({
192
+ ok: true,
193
+ json: async () => ({ status: 'healthy' }),
194
+ });
195
+
196
+ const health = await healthCheck(client);
197
+
198
+ assert.strictEqual(health.status, 'healthy');
199
+ });
200
+
201
+ it('returns unhealthy on error', async () => {
202
+ client._fetch = async () => {
203
+ throw new Error('Connection refused');
204
+ };
205
+
206
+ const health = await healthCheck(client);
207
+
208
+ assert.strictEqual(health.status, 'unhealthy');
209
+ });
210
+ });
211
+
212
+ describe('setApiKey', () => {
213
+ it('updates API key', () => {
214
+ setApiKey(client, 'new-key');
215
+
216
+ assert.strictEqual(client.apiKey, 'new-key');
217
+ });
218
+ });
219
+
220
+ describe('withFallback', () => {
221
+ it('tries fallback on primary failure', async () => {
222
+ let attempts = [];
223
+ client._fetch = async (url, options) => {
224
+ const body = JSON.parse(options.body);
225
+ attempts.push(body.model);
226
+
227
+ if (body.model === 'claude-3-opus') {
228
+ throw new Error('Rate limited');
229
+ }
230
+
231
+ return {
232
+ ok: true,
233
+ json: async () => ({
234
+ choices: [{ text: 'Response from fallback' }],
235
+ }),
236
+ };
237
+ };
238
+
239
+ const result = await withFallback(client, {
240
+ primary: 'claude-3-opus',
241
+ fallbacks: ['claude-3-sonnet', 'gpt-4'],
242
+ request: { prompt: 'Test' },
243
+ });
244
+
245
+ assert.ok(attempts.includes('claude-3-opus'));
246
+ assert.ok(attempts.includes('claude-3-sonnet'));
247
+ assert.ok(result.choices);
248
+ });
249
+
250
+ it('returns error if all models fail', async () => {
251
+ client._fetch = async () => {
252
+ throw new Error('All models unavailable');
253
+ };
254
+
255
+ try {
256
+ await withFallback(client, {
257
+ primary: 'claude-3-opus',
258
+ fallbacks: ['claude-3-sonnet'],
259
+ request: { prompt: 'Test' },
260
+ });
261
+ assert.fail('Should have thrown');
262
+ } catch (err) {
263
+ assert.ok(err.message.includes('unavailable') || err.message.includes('failed'));
264
+ }
265
+ });
266
+ });
267
+ });