tlc-claude-code 1.4.4 → 1.4.5

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 (70) hide show
  1. package/dashboard/dist/App.js +28 -2
  2. package/dashboard/dist/api/health-diagnostics.d.ts +26 -0
  3. package/dashboard/dist/api/health-diagnostics.js +85 -0
  4. package/dashboard/dist/api/health-diagnostics.test.d.ts +1 -0
  5. package/dashboard/dist/api/health-diagnostics.test.js +126 -0
  6. package/dashboard/dist/api/index.d.ts +5 -0
  7. package/dashboard/dist/api/index.js +5 -0
  8. package/dashboard/dist/api/notes-api.d.ts +18 -0
  9. package/dashboard/dist/api/notes-api.js +68 -0
  10. package/dashboard/dist/api/notes-api.test.d.ts +1 -0
  11. package/dashboard/dist/api/notes-api.test.js +113 -0
  12. package/dashboard/dist/api/safeFetch.d.ts +50 -0
  13. package/dashboard/dist/api/safeFetch.js +135 -0
  14. package/dashboard/dist/api/safeFetch.test.d.ts +1 -0
  15. package/dashboard/dist/api/safeFetch.test.js +215 -0
  16. package/dashboard/dist/api/tasks-api.d.ts +32 -0
  17. package/dashboard/dist/api/tasks-api.js +98 -0
  18. package/dashboard/dist/api/tasks-api.test.d.ts +1 -0
  19. package/dashboard/dist/api/tasks-api.test.js +383 -0
  20. package/dashboard/dist/components/BugsPane.d.ts +20 -0
  21. package/dashboard/dist/components/BugsPane.js +210 -0
  22. package/dashboard/dist/components/BugsPane.test.d.ts +1 -0
  23. package/dashboard/dist/components/BugsPane.test.js +256 -0
  24. package/dashboard/dist/components/HealthPane.d.ts +3 -1
  25. package/dashboard/dist/components/HealthPane.js +44 -6
  26. package/dashboard/dist/components/HealthPane.test.js +105 -2
  27. package/dashboard/dist/components/RouterPane.d.ts +4 -3
  28. package/dashboard/dist/components/RouterPane.js +60 -57
  29. package/dashboard/dist/components/RouterPane.test.js +150 -96
  30. package/dashboard/dist/components/UpdateBanner.d.ts +26 -0
  31. package/dashboard/dist/components/UpdateBanner.js +30 -0
  32. package/dashboard/dist/components/UpdateBanner.test.d.ts +1 -0
  33. package/dashboard/dist/components/UpdateBanner.test.js +96 -0
  34. package/dashboard/dist/components/ui/EmptyState.d.ts +14 -0
  35. package/dashboard/dist/components/ui/EmptyState.js +58 -0
  36. package/dashboard/dist/components/ui/EmptyState.test.d.ts +1 -0
  37. package/dashboard/dist/components/ui/EmptyState.test.js +97 -0
  38. package/dashboard/dist/components/ui/ErrorState.d.ts +17 -0
  39. package/dashboard/dist/components/ui/ErrorState.js +80 -0
  40. package/dashboard/dist/components/ui/ErrorState.test.d.ts +1 -0
  41. package/dashboard/dist/components/ui/ErrorState.test.js +166 -0
  42. package/dashboard/package.json +3 -0
  43. package/package.json +1 -1
  44. package/server/dashboard/index.html +205 -8
  45. package/server/index.js +64 -0
  46. package/server/lib/api-provider.js +104 -186
  47. package/server/lib/api-provider.test.js +238 -336
  48. package/server/lib/cli-detector.js +90 -166
  49. package/server/lib/cli-detector.test.js +114 -269
  50. package/server/lib/cli-provider.js +142 -212
  51. package/server/lib/cli-provider.test.js +196 -349
  52. package/server/lib/debug.test.js +1 -1
  53. package/server/lib/devserver-router-api.js +54 -249
  54. package/server/lib/devserver-router-api.test.js +126 -426
  55. package/server/lib/introspect.js +309 -0
  56. package/server/lib/introspect.test.js +286 -0
  57. package/server/lib/model-router.js +107 -245
  58. package/server/lib/model-router.test.js +122 -313
  59. package/server/lib/output-schemas.js +146 -269
  60. package/server/lib/output-schemas.test.js +106 -307
  61. package/server/lib/provider-interface.js +99 -153
  62. package/server/lib/provider-interface.test.js +228 -394
  63. package/server/lib/provider-queue.js +164 -158
  64. package/server/lib/provider-queue.test.js +186 -315
  65. package/server/lib/router-config.js +99 -221
  66. package/server/lib/router-config.test.js +83 -237
  67. package/server/lib/router-setup-command.js +94 -419
  68. package/server/lib/router-setup-command.test.js +96 -375
  69. package/server/lib/router-status-api.js +93 -0
  70. package/server/lib/router-status-api.test.js +270 -0
@@ -1,307 +1,106 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
- import fs from 'fs/promises';
3
- import {
4
- loadSchema,
5
- validateOutput,
6
- buildPromptWithSchema,
7
- schemaToPromptInstructions,
8
- BUILTIN_SCHEMAS,
9
- } from './output-schemas.js';
10
-
11
- // Mock fs/promises
12
- vi.mock('fs/promises');
13
-
14
- describe('output-schemas', () => {
15
- beforeEach(() => {
16
- vi.clearAllMocks();
17
- });
18
-
19
- describe('loadSchema', () => {
20
- it('reads schema from file', async () => {
21
- const schema = {
22
- type: 'object',
23
- properties: { result: { type: 'string' } },
24
- };
25
-
26
- fs.readFile.mockResolvedValue(JSON.stringify(schema));
27
-
28
- const loaded = await loadSchema('/.tlc/schemas/test-schema.json');
29
-
30
- expect(loaded).toEqual(schema);
31
- expect(fs.readFile).toHaveBeenCalledWith('/.tlc/schemas/test-schema.json', 'utf8');
32
- });
33
-
34
- it('returns parsed JSON', async () => {
35
- const schema = {
36
- type: 'object',
37
- properties: {
38
- summary: { type: 'string' },
39
- score: { type: 'number' },
40
- },
41
- required: ['summary'],
42
- };
43
-
44
- fs.readFile.mockResolvedValue(JSON.stringify(schema));
45
-
46
- const loaded = await loadSchema('/.tlc/schemas/review.json');
47
-
48
- expect(loaded.type).toBe('object');
49
- expect(loaded.properties.summary.type).toBe('string');
50
- expect(loaded.required).toContain('summary');
51
- });
52
-
53
- it('handles missing schema file', async () => {
54
- fs.readFile.mockRejectedValue(new Error('ENOENT: no such file'));
55
-
56
- await expect(loadSchema('/.tlc/schemas/missing.json'))
57
- .rejects.toThrow(/ENOENT|no such file/i);
58
- });
59
-
60
- it('handles invalid JSON', async () => {
61
- fs.readFile.mockResolvedValue('not valid json {');
62
-
63
- await expect(loadSchema('/.tlc/schemas/invalid.json'))
64
- .rejects.toThrow();
65
- });
66
- });
67
-
68
- describe('validateOutput', () => {
69
- const schema = {
70
- type: 'object',
71
- properties: {
72
- summary: { type: 'string' },
73
- score: { type: 'integer', minimum: 0, maximum: 100 },
74
- approved: { type: 'boolean' },
75
- },
76
- required: ['summary', 'score', 'approved'],
77
- };
78
-
79
- it('passes valid data', () => {
80
- const data = {
81
- summary: 'Code looks good',
82
- score: 85,
83
- approved: true,
84
- };
85
-
86
- const result = validateOutput(data, schema);
87
-
88
- expect(result.valid).toBe(true);
89
- expect(result.errors).toEqual([]);
90
- });
91
-
92
- it('rejects invalid data', () => {
93
- const data = {
94
- summary: 'Code looks good',
95
- score: 'not a number', // Should be integer
96
- approved: true,
97
- };
98
-
99
- const result = validateOutput(data, schema);
100
-
101
- expect(result.valid).toBe(false);
102
- expect(result.errors.length).toBeGreaterThan(0);
103
- });
104
-
105
- it('rejects missing required fields', () => {
106
- const data = {
107
- summary: 'Code looks good',
108
- // Missing score and approved
109
- };
110
-
111
- const result = validateOutput(data, schema);
112
-
113
- expect(result.valid).toBe(false);
114
- });
115
-
116
- it('validates nested objects', () => {
117
- const nestedSchema = {
118
- type: 'object',
119
- properties: {
120
- issue: {
121
- type: 'object',
122
- properties: {
123
- severity: { type: 'string', enum: ['critical', 'moderate', 'suggestion'] },
124
- line: { type: 'integer' },
125
- },
126
- required: ['severity'],
127
- },
128
- },
129
- };
130
-
131
- const validData = {
132
- issue: { severity: 'critical', line: 42 },
133
- };
134
-
135
- const invalidData = {
136
- issue: { severity: 'invalid-severity' },
137
- };
138
-
139
- expect(validateOutput(validData, nestedSchema).valid).toBe(true);
140
- expect(validateOutput(invalidData, nestedSchema).valid).toBe(false);
141
- });
142
- });
143
-
144
- describe('BUILTIN_SCHEMAS', () => {
145
- it('has review-result schema', () => {
146
- const schema = BUILTIN_SCHEMAS['review-result'];
147
-
148
- expect(schema).toBeDefined();
149
- expect(schema.properties.summary).toBeDefined();
150
- expect(schema.properties.issues).toBeDefined();
151
- expect(schema.properties.score).toBeDefined();
152
- expect(schema.properties.approved).toBeDefined();
153
- });
154
-
155
- it('review-result has required fields', () => {
156
- const schema = BUILTIN_SCHEMAS['review-result'];
157
-
158
- expect(schema.required).toContain('summary');
159
- expect(schema.required).toContain('issues');
160
- expect(schema.required).toContain('score');
161
- expect(schema.required).toContain('approved');
162
- });
163
-
164
- it('has design-result schema', () => {
165
- const schema = BUILTIN_SCHEMAS['design-result'];
166
-
167
- expect(schema).toBeDefined();
168
- expect(schema.properties.mockups).toBeDefined();
169
- expect(schema.properties.rationale).toBeDefined();
170
- });
171
-
172
- it('design-result has required fields', () => {
173
- const schema = BUILTIN_SCHEMAS['design-result'];
174
-
175
- expect(schema.required).toContain('mockups');
176
- expect(schema.required).toContain('rationale');
177
- });
178
-
179
- it('has code-result schema', () => {
180
- const schema = BUILTIN_SCHEMAS['code-result'];
181
-
182
- expect(schema).toBeDefined();
183
- expect(schema.properties.files).toBeDefined();
184
- expect(schema.properties.explanation).toBeDefined();
185
- });
186
-
187
- it('code-result has required fields', () => {
188
- const schema = BUILTIN_SCHEMAS['code-result'];
189
-
190
- expect(schema.required).toContain('files');
191
- expect(schema.required).toContain('explanation');
192
- });
193
- });
194
-
195
- describe('buildPromptWithSchema', () => {
196
- it('injects schema into prompt', () => {
197
- const prompt = 'Review this code';
198
- const schema = {
199
- type: 'object',
200
- properties: { result: { type: 'string' } },
201
- };
202
-
203
- const result = buildPromptWithSchema(prompt, schema);
204
-
205
- expect(result).toContain('Review this code');
206
- expect(result).toContain('JSON');
207
- });
208
-
209
- it('includes schema structure', () => {
210
- const prompt = 'Analyze';
211
- const schema = {
212
- type: 'object',
213
- properties: {
214
- summary: { type: 'string' },
215
- score: { type: 'integer' },
216
- },
217
- required: ['summary'],
218
- };
219
-
220
- const result = buildPromptWithSchema(prompt, schema);
221
-
222
- expect(result).toContain('summary');
223
- expect(result).toContain('score');
224
- });
225
-
226
- it('returns original prompt when no schema', () => {
227
- const prompt = 'Just a prompt';
228
-
229
- const result = buildPromptWithSchema(prompt, null);
230
-
231
- expect(result).toBe(prompt);
232
- });
233
- });
234
-
235
- describe('schemaToPromptInstructions', () => {
236
- it('creates text instructions from schema', () => {
237
- const schema = {
238
- type: 'object',
239
- properties: {
240
- summary: { type: 'string', description: 'A brief summary' },
241
- score: { type: 'integer', minimum: 0, maximum: 100 },
242
- },
243
- required: ['summary', 'score'],
244
- };
245
-
246
- const instructions = schemaToPromptInstructions(schema);
247
-
248
- expect(instructions).toContain('summary');
249
- expect(instructions).toContain('score');
250
- expect(instructions).toContain('required');
251
- });
252
-
253
- it('handles nested objects', () => {
254
- const schema = {
255
- type: 'object',
256
- properties: {
257
- data: {
258
- type: 'object',
259
- properties: {
260
- value: { type: 'string' },
261
- },
262
- },
263
- },
264
- };
265
-
266
- const instructions = schemaToPromptInstructions(schema);
267
-
268
- expect(instructions).toContain('data');
269
- expect(instructions).toContain('value');
270
- });
271
-
272
- it('handles arrays', () => {
273
- const schema = {
274
- type: 'object',
275
- properties: {
276
- items: {
277
- type: 'array',
278
- items: { type: 'string' },
279
- },
280
- },
281
- };
282
-
283
- const instructions = schemaToPromptInstructions(schema);
284
-
285
- expect(instructions).toContain('items');
286
- expect(instructions).toContain('array');
287
- });
288
-
289
- it('includes enum values', () => {
290
- const schema = {
291
- type: 'object',
292
- properties: {
293
- status: {
294
- type: 'string',
295
- enum: ['pending', 'approved', 'rejected'],
296
- },
297
- },
298
- };
299
-
300
- const instructions = schemaToPromptInstructions(schema);
301
-
302
- expect(instructions).toContain('pending');
303
- expect(instructions).toContain('approved');
304
- expect(instructions).toContain('rejected');
305
- });
306
- });
307
- });
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import {
3
+ loadSchema,
4
+ validateOutput,
5
+ reviewResultSchema,
6
+ designResultSchema,
7
+ codeResultSchema,
8
+ buildPromptWithSchema,
9
+ schemaToPromptInstructions,
10
+ } from './output-schemas.js';
11
+
12
+ describe('Output Schemas', () => {
13
+ describe('loadSchema', () => {
14
+ it('reads from file', async () => {
15
+ const schema = await loadSchema('review-result');
16
+ expect(schema).toBeDefined();
17
+ });
18
+
19
+ it('returns parsed JSON', async () => {
20
+ const schema = await loadSchema('review-result');
21
+ expect(schema).toHaveProperty('type');
22
+ });
23
+
24
+ it('handles missing schema file', async () => {
25
+ await expect(loadSchema('nonexistent')).rejects.toThrow();
26
+ });
27
+ });
28
+
29
+ describe('validateOutput', () => {
30
+ it('passes valid data', () => {
31
+ const schema = {
32
+ type: 'object',
33
+ required: ['summary'],
34
+ properties: {
35
+ summary: { type: 'string' },
36
+ },
37
+ };
38
+
39
+ const result = validateOutput({ summary: 'Test' }, schema);
40
+ expect(result.valid).toBe(true);
41
+ });
42
+
43
+ it('rejects invalid data', () => {
44
+ const schema = {
45
+ type: 'object',
46
+ required: ['summary'],
47
+ properties: {
48
+ summary: { type: 'string' },
49
+ },
50
+ };
51
+
52
+ const result = validateOutput({ other: 'field' }, schema);
53
+ expect(result.valid).toBe(false);
54
+ });
55
+ });
56
+
57
+ describe('Built-in Schemas', () => {
58
+ it('reviewResultSchema has required fields', () => {
59
+ expect(reviewResultSchema.properties).toHaveProperty('summary');
60
+ expect(reviewResultSchema.properties).toHaveProperty('issues');
61
+ expect(reviewResultSchema.properties).toHaveProperty('score');
62
+ expect(reviewResultSchema.properties).toHaveProperty('approved');
63
+ });
64
+
65
+ it('designResultSchema has required fields', () => {
66
+ expect(designResultSchema.properties).toHaveProperty('mockups');
67
+ expect(designResultSchema.properties).toHaveProperty('rationale');
68
+ expect(designResultSchema.properties).toHaveProperty('alternatives');
69
+ });
70
+
71
+ it('codeResultSchema has required fields', () => {
72
+ expect(codeResultSchema.properties).toHaveProperty('files');
73
+ expect(codeResultSchema.properties).toHaveProperty('explanation');
74
+ expect(codeResultSchema.properties).toHaveProperty('tests');
75
+ });
76
+ });
77
+
78
+ describe('buildPromptWithSchema', () => {
79
+ it('injects schema into prompt', () => {
80
+ const prompt = 'Review this code';
81
+ const schema = { type: 'object', properties: { summary: { type: 'string' } } };
82
+
83
+ const enhanced = buildPromptWithSchema(prompt, schema);
84
+
85
+ expect(enhanced).toContain('Review this code');
86
+ expect(enhanced).toContain('JSON');
87
+ });
88
+ });
89
+
90
+ describe('schemaToPromptInstructions', () => {
91
+ it('creates text instructions', () => {
92
+ const schema = {
93
+ type: 'object',
94
+ properties: {
95
+ summary: { type: 'string', description: 'Brief summary' },
96
+ score: { type: 'number', description: 'Quality score 0-100' },
97
+ },
98
+ };
99
+
100
+ const instructions = schemaToPromptInstructions(schema);
101
+
102
+ expect(instructions).toContain('summary');
103
+ expect(instructions).toContain('score');
104
+ });
105
+ });
106
+ });
@@ -1,153 +1,99 @@
1
- /**
2
- * Provider Interface - Unified interface for CLI and API providers
3
- *
4
- * Supports two provider types:
5
- * - CLI: Wraps AI coding CLI tools (claude, codex, gemini)
6
- * - API: Direct REST calls to OpenAI-compatible endpoints
7
- */
8
-
9
- /**
10
- * Provider types enum
11
- */
12
- export const PROVIDER_TYPES = {
13
- CLI: 'cli',
14
- API: 'api',
15
- };
16
-
17
- /**
18
- * Validate provider configuration
19
- * @param {Object} config - Provider configuration
20
- * @throws {Error} If configuration is invalid
21
- */
22
- export function validateConfig(config) {
23
- if (!config.name) {
24
- throw new Error('name is required');
25
- }
26
-
27
- if (!config.type) {
28
- throw new Error('type is required');
29
- }
30
-
31
- if (config.type !== PROVIDER_TYPES.CLI && config.type !== PROVIDER_TYPES.API) {
32
- throw new Error(`Invalid provider type: ${config.type}. Must be 'cli' or 'api'`);
33
- }
34
-
35
- if (config.type === PROVIDER_TYPES.CLI && !config.command) {
36
- throw new Error('command is required for CLI providers');
37
- }
38
-
39
- if (config.type === PROVIDER_TYPES.API && !config.baseUrl) {
40
- throw new Error('baseUrl is required for API providers');
41
- }
42
- }
43
-
44
- /**
45
- * Create a ProviderResult object
46
- * @param {Object} data - Result data
47
- * @param {string} data.raw - Raw output string
48
- * @param {any} data.parsed - Parsed output (typically JSON)
49
- * @param {number} data.exitCode - Exit code (0 for success)
50
- * @param {Object} [data.tokenUsage] - Token usage statistics
51
- * @param {number} [data.cost] - Cost in USD
52
- * @returns {Object} ProviderResult
53
- */
54
- export function createProviderResult({ raw, parsed, exitCode, tokenUsage = null, cost = null }) {
55
- return {
56
- raw,
57
- parsed,
58
- exitCode,
59
- tokenUsage,
60
- cost,
61
- };
62
- }
63
-
64
- /**
65
- * Calculate cost from token usage
66
- * @param {Object} tokenUsage - Token usage { input, output }
67
- * @param {Object} pricing - Pricing { input, output } per 1K tokens
68
- * @returns {number} Cost in USD
69
- */
70
- function calculateCost(tokenUsage, pricing) {
71
- if (!tokenUsage || !pricing) return null;
72
-
73
- const inputCost = (tokenUsage.input * pricing.input) / 1000;
74
- const outputCost = (tokenUsage.output * pricing.output) / 1000;
75
-
76
- return inputCost + outputCost;
77
- }
78
-
79
- /**
80
- * Create a provider instance
81
- * @param {Object} config - Provider configuration
82
- * @param {string} config.name - Provider name
83
- * @param {string} config.type - Provider type ('cli' or 'api')
84
- * @param {string} [config.command] - CLI command (required for CLI type)
85
- * @param {string} [config.baseUrl] - API base URL (required for API type)
86
- * @param {string} [config.model] - Model identifier
87
- * @param {string[]} [config.capabilities] - List of capabilities
88
- * @param {string[]} [config.headlessArgs] - Headless mode arguments
89
- * @param {Object} [config.pricing] - Token pricing { input, output }
90
- * @param {Function} [config.runner] - Custom runner function (for testing)
91
- * @returns {Object} Provider instance
92
- */
93
- export function createProvider(config) {
94
- // Validate
95
- if (!config.name) {
96
- throw new Error('name is required');
97
- }
98
-
99
- if (config.type !== PROVIDER_TYPES.CLI && config.type !== PROVIDER_TYPES.API) {
100
- throw new Error(`Invalid provider type: ${config.type}. Must be 'cli' or 'api'`);
101
- }
102
-
103
- // Freeze capabilities to make them immutable
104
- const capabilities = Object.freeze([...(config.capabilities || [])]);
105
-
106
- // Default runner that throws (should be overridden)
107
- const defaultRunner = async () => {
108
- throw new Error('Provider runner not implemented');
109
- };
110
-
111
- const runner = config.runner || defaultRunner;
112
-
113
- /**
114
- * Run a prompt through the provider
115
- * @param {string} prompt - The prompt to send
116
- * @param {Object} opts - Run options
117
- * @param {string} [opts.outputFormat] - Output format ('json' or 'text')
118
- * @param {string} [opts.sandbox] - Sandbox mode
119
- * @param {Object} [opts.outputSchema] - JSON schema for output
120
- * @param {string} [opts.cwd] - Working directory
121
- * @returns {Promise<Object>} ProviderResult
122
- */
123
- async function run(prompt, opts = {}) {
124
- const result = await runner(prompt, opts);
125
-
126
- // Calculate cost if we have token usage and pricing
127
- if (result.tokenUsage && config.pricing) {
128
- result.cost = calculateCost(result.tokenUsage, config.pricing);
129
- }
130
-
131
- return createProviderResult({
132
- raw: result.raw,
133
- parsed: result.parsed,
134
- exitCode: result.exitCode,
135
- tokenUsage: result.tokenUsage || null,
136
- cost: result.cost || null,
137
- });
138
- }
139
-
140
- return {
141
- name: config.name,
142
- type: config.type,
143
- command: config.command,
144
- baseUrl: config.baseUrl,
145
- model: config.model,
146
- headlessArgs: config.headlessArgs,
147
- pricing: config.pricing,
148
- detected: config.detected || false,
149
- devserverOnly: config.devserverOnly,
150
- capabilities,
151
- run,
152
- };
153
- }
1
+ /**
2
+ * Provider Interface - Unified interface for CLI and API providers
3
+ * Phase 33, Task 1
4
+ */
5
+
6
+ export const ProviderType = {
7
+ CLI: 'cli',
8
+ API: 'api',
9
+ };
10
+
11
+ /**
12
+ * Validate provider configuration
13
+ */
14
+ export function validateProviderConfig(config) {
15
+ const errors = [];
16
+
17
+ if (!config.name) {
18
+ errors.push('name is required');
19
+ }
20
+
21
+ if (!config.type) {
22
+ errors.push('type is required');
23
+ } else if (config.type !== ProviderType.CLI && config.type !== ProviderType.API) {
24
+ errors.push('invalid type: ' + config.type);
25
+ }
26
+
27
+ if (config.type === ProviderType.CLI && !config.command) {
28
+ errors.push('command is required for CLI providers');
29
+ }
30
+
31
+ if (config.type === ProviderType.API && !config.baseUrl) {
32
+ errors.push('baseUrl is required for API providers');
33
+ }
34
+
35
+ return {
36
+ valid: errors.length === 0,
37
+ errors,
38
+ };
39
+ }
40
+
41
+ /**
42
+ * Create a provider instance
43
+ */
44
+ export function createProvider(config) {
45
+ const validation = validateProviderConfig(config);
46
+ if (!validation.valid) {
47
+ throw new Error('Invalid provider config: ' + validation.errors.join(', '));
48
+ }
49
+
50
+ const provider = {
51
+ name: config.name,
52
+ type: config.type,
53
+ capabilities: config.capabilities || [],
54
+ config,
55
+ _execute: null,
56
+
57
+ async run(prompt, options = {}) {
58
+ if (this._execute) {
59
+ return this._execute(prompt, options);
60
+ }
61
+ if (this.type === ProviderType.CLI) {
62
+ return this._runCLI(prompt, options);
63
+ } else {
64
+ return this._runAPI(prompt, options);
65
+ }
66
+ },
67
+
68
+ async _runCLI(prompt, options) {
69
+ return {
70
+ raw: '',
71
+ parsed: null,
72
+ exitCode: 0,
73
+ tokenUsage: { input: 0, output: 0 },
74
+ cost: 0,
75
+ };
76
+ },
77
+
78
+ async _runAPI(prompt, options) {
79
+ return {
80
+ raw: '',
81
+ parsed: null,
82
+ exitCode: 0,
83
+ tokenUsage: { input: 0, output: 0 },
84
+ cost: 0,
85
+ };
86
+ },
87
+
88
+ calculateCost(tokenUsage) {
89
+ const pricing = config.pricing || { inputPer1k: 0, outputPer1k: 0 };
90
+ const inputCost = (tokenUsage.input / 1000) * pricing.inputPer1k;
91
+ const outputCost = (tokenUsage.output / 1000) * pricing.outputPer1k;
92
+ return inputCost + outputCost;
93
+ },
94
+ };
95
+
96
+ return provider;
97
+ }
98
+
99
+ export default { createProvider, validateProviderConfig, ProviderType };