tlc-claude-code 1.2.27 → 1.2.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dashboard/dist/components/ActivityFeed.d.ts +17 -0
- package/dashboard/dist/components/ActivityFeed.js +42 -0
- package/dashboard/dist/components/ActivityFeed.test.d.ts +1 -0
- package/dashboard/dist/components/ActivityFeed.test.js +162 -0
- package/dashboard/dist/components/BranchSelector.d.ts +16 -0
- package/dashboard/dist/components/BranchSelector.js +49 -0
- package/dashboard/dist/components/BranchSelector.test.d.ts +1 -0
- package/dashboard/dist/components/BranchSelector.test.js +166 -0
- package/dashboard/dist/components/CommandPalette.d.ts +17 -0
- package/dashboard/dist/components/CommandPalette.js +118 -0
- package/dashboard/dist/components/CommandPalette.test.d.ts +1 -0
- package/dashboard/dist/components/CommandPalette.test.js +181 -0
- package/dashboard/dist/components/ConnectionStatus.d.ts +16 -0
- package/dashboard/dist/components/ConnectionStatus.js +27 -0
- package/dashboard/dist/components/ConnectionStatus.test.d.ts +1 -0
- package/dashboard/dist/components/ConnectionStatus.test.js +121 -0
- package/dashboard/dist/components/DeviceFrame.d.ts +19 -0
- package/dashboard/dist/components/DeviceFrame.js +52 -0
- package/dashboard/dist/components/DeviceFrame.test.d.ts +1 -0
- package/dashboard/dist/components/DeviceFrame.test.js +118 -0
- package/dashboard/dist/components/EnvironmentBadge.d.ts +11 -0
- package/dashboard/dist/components/EnvironmentBadge.js +16 -0
- package/dashboard/dist/components/EnvironmentBadge.test.d.ts +1 -0
- package/dashboard/dist/components/EnvironmentBadge.test.js +102 -0
- package/dashboard/dist/components/FocusIndicator.d.ts +19 -0
- package/dashboard/dist/components/FocusIndicator.js +47 -0
- package/dashboard/dist/components/FocusIndicator.test.d.ts +1 -0
- package/dashboard/dist/components/FocusIndicator.test.js +117 -0
- package/dashboard/dist/components/KeyboardHelp.d.ts +15 -0
- package/dashboard/dist/components/KeyboardHelp.js +61 -0
- package/dashboard/dist/components/KeyboardHelp.test.d.ts +1 -0
- package/dashboard/dist/components/KeyboardHelp.test.js +131 -0
- package/dashboard/dist/components/LogSearch.d.ts +13 -0
- package/dashboard/dist/components/LogSearch.js +43 -0
- package/dashboard/dist/components/LogSearch.test.d.ts +1 -0
- package/dashboard/dist/components/LogSearch.test.js +100 -0
- package/dashboard/dist/components/LogStream.d.ts +21 -0
- package/dashboard/dist/components/LogStream.js +123 -0
- package/dashboard/dist/components/LogStream.test.d.ts +1 -0
- package/dashboard/dist/components/LogStream.test.js +159 -0
- package/dashboard/dist/components/PreviewPanel.d.ts +18 -0
- package/dashboard/dist/components/PreviewPanel.js +73 -0
- package/dashboard/dist/components/PreviewPanel.test.d.ts +1 -0
- package/dashboard/dist/components/PreviewPanel.test.js +124 -0
- package/dashboard/dist/components/ProjectCard.d.ts +18 -0
- package/dashboard/dist/components/ProjectCard.js +19 -0
- package/dashboard/dist/components/ProjectCard.test.d.ts +1 -0
- package/dashboard/dist/components/ProjectCard.test.js +53 -0
- package/dashboard/dist/components/ProjectDetail.d.ts +44 -0
- package/dashboard/dist/components/ProjectDetail.js +65 -0
- package/dashboard/dist/components/ProjectDetail.test.d.ts +1 -0
- package/dashboard/dist/components/ProjectDetail.test.js +196 -0
- package/dashboard/dist/components/ProjectList.d.ts +11 -0
- package/dashboard/dist/components/ProjectList.js +62 -0
- package/dashboard/dist/components/ProjectList.test.d.ts +1 -0
- package/dashboard/dist/components/ProjectList.test.js +93 -0
- package/dashboard/dist/components/SettingsPanel.d.ts +32 -0
- package/dashboard/dist/components/SettingsPanel.js +154 -0
- package/dashboard/dist/components/SettingsPanel.test.d.ts +1 -0
- package/dashboard/dist/components/SettingsPanel.test.js +196 -0
- package/dashboard/dist/components/StatusBar.d.ts +16 -0
- package/dashboard/dist/components/StatusBar.js +47 -0
- package/dashboard/dist/components/StatusBar.test.d.ts +1 -0
- package/dashboard/dist/components/StatusBar.test.js +123 -0
- package/dashboard/dist/components/TaskBoard.d.ts +22 -0
- package/dashboard/dist/components/TaskBoard.js +102 -0
- package/dashboard/dist/components/TaskBoard.test.d.ts +1 -0
- package/dashboard/dist/components/TaskBoard.test.js +113 -0
- package/dashboard/dist/components/TaskCard.d.ts +17 -0
- package/dashboard/dist/components/TaskCard.js +29 -0
- package/dashboard/dist/components/TaskCard.test.d.ts +1 -0
- package/dashboard/dist/components/TaskCard.test.js +109 -0
- package/dashboard/dist/components/TaskDetail.d.ts +36 -0
- package/dashboard/dist/components/TaskDetail.js +41 -0
- package/dashboard/dist/components/TaskDetail.test.d.ts +1 -0
- package/dashboard/dist/components/TaskDetail.test.js +164 -0
- package/dashboard/dist/components/TaskFilter.d.ts +12 -0
- package/dashboard/dist/components/TaskFilter.js +138 -0
- package/dashboard/dist/components/TaskFilter.test.d.ts +1 -0
- package/dashboard/dist/components/TaskFilter.test.js +109 -0
- package/dashboard/dist/components/TeamPanel.d.ts +15 -0
- package/dashboard/dist/components/TeamPanel.js +24 -0
- package/dashboard/dist/components/TeamPanel.test.d.ts +1 -0
- package/dashboard/dist/components/TeamPanel.test.js +109 -0
- package/dashboard/dist/components/TeamPresence.d.ts +14 -0
- package/dashboard/dist/components/TeamPresence.js +31 -0
- package/dashboard/dist/components/TeamPresence.test.d.ts +1 -0
- package/dashboard/dist/components/TeamPresence.test.js +144 -0
- package/dashboard/dist/components/layout/Header.d.ts +9 -0
- package/dashboard/dist/components/layout/Header.js +11 -0
- package/dashboard/dist/components/layout/Header.test.d.ts +1 -0
- package/dashboard/dist/components/layout/Header.test.js +35 -0
- package/dashboard/dist/components/layout/Shell.d.ts +10 -0
- package/dashboard/dist/components/layout/Shell.js +5 -0
- package/dashboard/dist/components/layout/Shell.test.d.ts +1 -0
- package/dashboard/dist/components/layout/Shell.test.js +34 -0
- package/dashboard/dist/components/layout/Sidebar.d.ts +14 -0
- package/dashboard/dist/components/layout/Sidebar.js +8 -0
- package/dashboard/dist/components/layout/Sidebar.test.d.ts +1 -0
- package/dashboard/dist/components/layout/Sidebar.test.js +40 -0
- package/dashboard/dist/components/ui/Badge.d.ts +9 -0
- package/dashboard/dist/components/ui/Badge.js +13 -0
- package/dashboard/dist/components/ui/Badge.test.d.ts +1 -0
- package/dashboard/dist/components/ui/Badge.test.js +69 -0
- package/dashboard/dist/components/ui/Button.d.ts +12 -0
- package/dashboard/dist/components/ui/Button.js +14 -0
- package/dashboard/dist/components/ui/Button.test.d.ts +1 -0
- package/dashboard/dist/components/ui/Button.test.js +81 -0
- package/dashboard/dist/components/ui/Card.d.ts +21 -0
- package/dashboard/dist/components/ui/Card.js +20 -0
- package/dashboard/dist/components/ui/Card.test.d.ts +1 -0
- package/dashboard/dist/components/ui/Card.test.js +82 -0
- package/dashboard/dist/components/ui/Input.d.ts +13 -0
- package/dashboard/dist/components/ui/Input.js +8 -0
- package/dashboard/dist/components/ui/Input.test.d.ts +1 -0
- package/dashboard/dist/components/ui/Input.test.js +68 -0
- package/dashboard/dist/styles/tokens.d.ts +150 -0
- package/dashboard/dist/styles/tokens.js +184 -0
- package/dashboard/dist/styles/tokens.test.d.ts +1 -0
- package/dashboard/dist/styles/tokens.test.js +95 -0
- package/dashboard/dist/test/setup.d.ts +1 -0
- package/dashboard/dist/test/setup.js +1 -0
- package/dashboard/package.json +3 -0
- package/package.json +1 -1
- package/server/lib/adapters/base-adapter.js +114 -0
- package/server/lib/adapters/base-adapter.test.js +90 -0
- package/server/lib/adapters/claude-adapter.js +141 -0
- package/server/lib/adapters/claude-adapter.test.js +180 -0
- package/server/lib/adapters/deepseek-adapter.js +153 -0
- package/server/lib/adapters/deepseek-adapter.test.js +193 -0
- package/server/lib/adapters/openai-adapter.js +190 -0
- package/server/lib/adapters/openai-adapter.test.js +231 -0
- package/server/lib/budget-tracker.js +169 -0
- package/server/lib/budget-tracker.test.js +165 -0
- package/server/lib/claude-injector.js +85 -0
- package/server/lib/claude-injector.test.js +161 -0
- package/server/lib/consensus-engine.js +135 -0
- package/server/lib/consensus-engine.test.js +152 -0
- package/server/lib/context-builder.js +112 -0
- package/server/lib/context-builder.test.js +120 -0
- package/server/lib/file-collector.js +322 -0
- package/server/lib/file-collector.test.js +307 -0
- package/server/lib/memory-classifier.js +175 -0
- package/server/lib/memory-classifier.test.js +169 -0
- package/server/lib/memory-committer.js +138 -0
- package/server/lib/memory-committer.test.js +136 -0
- package/server/lib/memory-hooks.js +127 -0
- package/server/lib/memory-hooks.test.js +136 -0
- package/server/lib/memory-init.js +104 -0
- package/server/lib/memory-init.test.js +119 -0
- package/server/lib/memory-observer.js +149 -0
- package/server/lib/memory-observer.test.js +158 -0
- package/server/lib/memory-reader.js +243 -0
- package/server/lib/memory-reader.test.js +216 -0
- package/server/lib/memory-storage.js +120 -0
- package/server/lib/memory-storage.test.js +136 -0
- package/server/lib/memory-writer.js +176 -0
- package/server/lib/memory-writer.test.js +231 -0
- package/server/lib/overdrive-command.js +30 -6
- package/server/lib/overdrive-command.test.js +8 -1
- package/server/lib/pattern-detector.js +216 -0
- package/server/lib/pattern-detector.test.js +241 -0
- package/server/lib/relevance-scorer.js +175 -0
- package/server/lib/relevance-scorer.test.js +107 -0
- package/server/lib/review-command.js +238 -0
- package/server/lib/review-command.test.js +245 -0
- package/server/lib/review-orchestrator.js +273 -0
- package/server/lib/review-orchestrator.test.js +300 -0
- package/server/lib/review-reporter.js +288 -0
- package/server/lib/review-reporter.test.js +240 -0
- package/server/lib/session-summary.js +90 -0
- package/server/lib/session-summary.test.js +156 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Adapter - Adapter for Claude API
|
|
3
|
+
* Note: In TLC context, Claude is subscription-based so no per-request cost
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { BaseAdapter } = require('./base-adapter.js');
|
|
7
|
+
|
|
8
|
+
// Latest model: claude-opus-4-5-20251101 (Claude Opus 4.5)
|
|
9
|
+
const CLAUDE_MODEL = 'claude-opus-4-5-20251101';
|
|
10
|
+
|
|
11
|
+
const CLAUDE_PRICING = {
|
|
12
|
+
// Pricing per 1M tokens (Claude Opus 4.5)
|
|
13
|
+
inputPerMillion: 15.00,
|
|
14
|
+
outputPerMillion: 75.00,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
class ClaudeAdapter extends BaseAdapter {
|
|
18
|
+
constructor(config = {}) {
|
|
19
|
+
super({
|
|
20
|
+
name: 'claude',
|
|
21
|
+
...config,
|
|
22
|
+
});
|
|
23
|
+
this.budgetTracker = config.budgetTracker || null;
|
|
24
|
+
this.pricing = config.pricing || CLAUDE_PRICING;
|
|
25
|
+
this.model = config.model || CLAUDE_MODEL;
|
|
26
|
+
this.trackCost = config.trackCost !== false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Review code for issues and suggestions
|
|
31
|
+
* @param {string} code - Code to review
|
|
32
|
+
* @param {Object} context - Additional context
|
|
33
|
+
* @returns {Promise<Object>} Standardized review response
|
|
34
|
+
*/
|
|
35
|
+
async review(code, context = {}) {
|
|
36
|
+
if (!code || code.trim() === '') {
|
|
37
|
+
return this.createEmptyResponse();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Check budget if tracking
|
|
41
|
+
const estimatedTokens = this.estimateTokens(code);
|
|
42
|
+
const estimatedCost = this.estimateCost(estimatedTokens);
|
|
43
|
+
|
|
44
|
+
if (this.trackCost && !this.canAfford(estimatedCost)) {
|
|
45
|
+
throw new Error('Budget exceeded for Claude');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
// Simulate API call - in real implementation, call Claude API
|
|
50
|
+
const result = await this.callAPI(code, context);
|
|
51
|
+
|
|
52
|
+
// Record cost if tracking
|
|
53
|
+
if (this.trackCost && this.budgetTracker) {
|
|
54
|
+
this.budgetTracker.record('claude', result.cost || 0);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return result;
|
|
58
|
+
} catch (error) {
|
|
59
|
+
// Return empty response on error, don't throw
|
|
60
|
+
return {
|
|
61
|
+
...this.createEmptyResponse(),
|
|
62
|
+
error: error.message,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Call Claude API (mock implementation)
|
|
69
|
+
* @param {string} code - Code to review
|
|
70
|
+
* @param {Object} context - Additional context
|
|
71
|
+
* @returns {Promise<Object>} API response
|
|
72
|
+
*/
|
|
73
|
+
async callAPI(code, context) {
|
|
74
|
+
// This is a mock - in production, call actual Claude API
|
|
75
|
+
// For now, simulate a response
|
|
76
|
+
const tokensUsed = this.estimateTokens(code);
|
|
77
|
+
const cost = this.estimateCost(tokensUsed);
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
issues: [],
|
|
81
|
+
suggestions: [],
|
|
82
|
+
score: 100,
|
|
83
|
+
model: this.name,
|
|
84
|
+
tokensUsed,
|
|
85
|
+
cost,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Estimate tokens for code
|
|
91
|
+
* @param {string} code - Code to estimate
|
|
92
|
+
* @returns {number} Estimated tokens
|
|
93
|
+
*/
|
|
94
|
+
estimateTokens(code) {
|
|
95
|
+
// Rough estimate: ~4 chars per token for code
|
|
96
|
+
return Math.ceil(code.length / 4);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Estimate cost for tokens
|
|
101
|
+
* @param {number} tokens - Number of tokens
|
|
102
|
+
* @returns {number} Estimated cost in USD
|
|
103
|
+
*/
|
|
104
|
+
estimateCost(tokens) {
|
|
105
|
+
// Assume 50% input, 50% output for review
|
|
106
|
+
const inputCost = (tokens * 0.5 * this.pricing.inputPerMillion) / 1_000_000;
|
|
107
|
+
const outputCost = (tokens * 0.5 * this.pricing.outputPerMillion) / 1_000_000;
|
|
108
|
+
return inputCost + outputCost;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Check if adapter can afford a request
|
|
113
|
+
* @param {number} estimatedCost - Estimated cost
|
|
114
|
+
* @returns {boolean} Whether request is within budget
|
|
115
|
+
*/
|
|
116
|
+
canAfford(estimatedCost = 0) {
|
|
117
|
+
if (!this.trackCost || !this.budgetTracker) {
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const budgetConfig = this.config.budget || { budgetDaily: 10, budgetMonthly: 100 };
|
|
122
|
+
return this.budgetTracker.canSpend('claude', estimatedCost, budgetConfig);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Get current usage stats
|
|
127
|
+
* @returns {Object} Usage stats
|
|
128
|
+
*/
|
|
129
|
+
getUsage() {
|
|
130
|
+
if (!this.budgetTracker) {
|
|
131
|
+
return { daily: 0, monthly: 0, requests: 0 };
|
|
132
|
+
}
|
|
133
|
+
return this.budgetTracker.getUsage('claude');
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
module.exports = {
|
|
138
|
+
ClaudeAdapter,
|
|
139
|
+
CLAUDE_PRICING,
|
|
140
|
+
CLAUDE_MODEL,
|
|
141
|
+
};
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { ClaudeAdapter, CLAUDE_PRICING, CLAUDE_MODEL } from './claude-adapter.js';
|
|
3
|
+
import { BaseAdapter } from './base-adapter.js';
|
|
4
|
+
|
|
5
|
+
describe('ClaudeAdapter', () => {
|
|
6
|
+
describe('constructor', () => {
|
|
7
|
+
it('sets name to claude by default', () => {
|
|
8
|
+
const adapter = new ClaudeAdapter();
|
|
9
|
+
expect(adapter.name).toBe('claude');
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('accepts custom config', () => {
|
|
13
|
+
const adapter = new ClaudeAdapter({ apiKey: 'test-key' });
|
|
14
|
+
expect(adapter.config.apiKey).toBe('test-key');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('accepts budget tracker', () => {
|
|
18
|
+
const mockTracker = { canSpend: vi.fn() };
|
|
19
|
+
const adapter = new ClaudeAdapter({ budgetTracker: mockTracker });
|
|
20
|
+
expect(adapter.budgetTracker).toBe(mockTracker);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe('review', () => {
|
|
25
|
+
it('returns empty response for empty code', async () => {
|
|
26
|
+
const adapter = new ClaudeAdapter();
|
|
27
|
+
const result = await adapter.review('');
|
|
28
|
+
|
|
29
|
+
expect(result.issues).toEqual([]);
|
|
30
|
+
expect(result.score).toBe(100);
|
|
31
|
+
expect(result.model).toBe('claude');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('returns empty response for whitespace-only code', async () => {
|
|
35
|
+
const adapter = new ClaudeAdapter();
|
|
36
|
+
const result = await adapter.review(' \n\t ');
|
|
37
|
+
|
|
38
|
+
expect(result.issues).toEqual([]);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('returns standardized response format', async () => {
|
|
42
|
+
const adapter = new ClaudeAdapter();
|
|
43
|
+
const result = await adapter.review('const x = 1;');
|
|
44
|
+
|
|
45
|
+
expect(BaseAdapter.validateResponse(result)).toBe(true);
|
|
46
|
+
expect(result.model).toBe('claude');
|
|
47
|
+
expect(typeof result.tokensUsed).toBe('number');
|
|
48
|
+
expect(typeof result.cost).toBe('number');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('checks budget before review', async () => {
|
|
52
|
+
const mockTracker = {
|
|
53
|
+
canSpend: vi.fn(() => false),
|
|
54
|
+
record: vi.fn(),
|
|
55
|
+
getUsage: vi.fn(() => ({ daily: 10, monthly: 100, requests: 50 })),
|
|
56
|
+
};
|
|
57
|
+
const adapter = new ClaudeAdapter({
|
|
58
|
+
budgetTracker: mockTracker,
|
|
59
|
+
budget: { budgetDaily: 5, budgetMonthly: 50 },
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
await expect(adapter.review('const x = 1;')).rejects.toThrow('Budget exceeded');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('records cost after successful review', async () => {
|
|
66
|
+
const mockTracker = {
|
|
67
|
+
canSpend: vi.fn(() => true),
|
|
68
|
+
record: vi.fn(),
|
|
69
|
+
getUsage: vi.fn(() => ({ daily: 0, monthly: 0, requests: 0 })),
|
|
70
|
+
};
|
|
71
|
+
const adapter = new ClaudeAdapter({ budgetTracker: mockTracker });
|
|
72
|
+
|
|
73
|
+
await adapter.review('const x = 1;');
|
|
74
|
+
|
|
75
|
+
expect(mockTracker.record).toHaveBeenCalledWith('claude', expect.any(Number));
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('handles API errors gracefully', async () => {
|
|
79
|
+
const adapter = new ClaudeAdapter();
|
|
80
|
+
adapter.callAPI = vi.fn().mockRejectedValue(new Error('API timeout'));
|
|
81
|
+
|
|
82
|
+
const result = await adapter.review('const x = 1;');
|
|
83
|
+
|
|
84
|
+
expect(result.error).toBe('API timeout');
|
|
85
|
+
expect(result.issues).toEqual([]);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe('estimateTokens', () => {
|
|
90
|
+
it('estimates tokens based on code length', () => {
|
|
91
|
+
const adapter = new ClaudeAdapter();
|
|
92
|
+
|
|
93
|
+
// ~4 chars per token
|
|
94
|
+
expect(adapter.estimateTokens('abcd')).toBe(1);
|
|
95
|
+
expect(adapter.estimateTokens('abcdefgh')).toBe(2);
|
|
96
|
+
expect(adapter.estimateTokens('a'.repeat(100))).toBe(25);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe('estimateCost', () => {
|
|
101
|
+
it('calculates cost based on token count', () => {
|
|
102
|
+
const adapter = new ClaudeAdapter();
|
|
103
|
+
const cost = adapter.estimateCost(1000);
|
|
104
|
+
|
|
105
|
+
// At default pricing (Opus 4.5): (500 * 15 + 500 * 75) / 1M = 0.045
|
|
106
|
+
expect(cost).toBeCloseTo(0.045, 4);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('uses custom pricing if provided', () => {
|
|
110
|
+
const adapter = new ClaudeAdapter({
|
|
111
|
+
pricing: { inputPerMillion: 1.00, outputPerMillion: 2.00 },
|
|
112
|
+
});
|
|
113
|
+
const cost = adapter.estimateCost(1000);
|
|
114
|
+
|
|
115
|
+
// (500 * 1 + 500 * 2) / 1M = 0.0015
|
|
116
|
+
expect(cost).toBeCloseTo(0.0015, 5);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe('canAfford', () => {
|
|
121
|
+
it('returns true without budget tracker', () => {
|
|
122
|
+
const adapter = new ClaudeAdapter();
|
|
123
|
+
expect(adapter.canAfford(100)).toBe(true);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('returns true when tracking disabled', () => {
|
|
127
|
+
const adapter = new ClaudeAdapter({ trackCost: false });
|
|
128
|
+
expect(adapter.canAfford(100)).toBe(true);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('checks budget tracker when enabled', () => {
|
|
132
|
+
const mockTracker = {
|
|
133
|
+
canSpend: vi.fn(() => true),
|
|
134
|
+
};
|
|
135
|
+
const adapter = new ClaudeAdapter({
|
|
136
|
+
budgetTracker: mockTracker,
|
|
137
|
+
budget: { budgetDaily: 10, budgetMonthly: 100 },
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
adapter.canAfford(0.5);
|
|
141
|
+
|
|
142
|
+
expect(mockTracker.canSpend).toHaveBeenCalledWith('claude', 0.5, expect.any(Object));
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
describe('getUsage', () => {
|
|
147
|
+
it('returns zero without budget tracker', () => {
|
|
148
|
+
const adapter = new ClaudeAdapter();
|
|
149
|
+
const usage = adapter.getUsage();
|
|
150
|
+
|
|
151
|
+
expect(usage).toEqual({ daily: 0, monthly: 0, requests: 0 });
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('returns tracker usage when available', () => {
|
|
155
|
+
const mockTracker = {
|
|
156
|
+
getUsage: vi.fn(() => ({ daily: 2.50, monthly: 25.00, requests: 10 })),
|
|
157
|
+
};
|
|
158
|
+
const adapter = new ClaudeAdapter({ budgetTracker: mockTracker });
|
|
159
|
+
|
|
160
|
+
const usage = adapter.getUsage();
|
|
161
|
+
|
|
162
|
+
expect(usage.daily).toBe(2.50);
|
|
163
|
+
expect(usage.monthly).toBe(25.00);
|
|
164
|
+
expect(usage.requests).toBe(10);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
describe('CLAUDE_PRICING', () => {
|
|
169
|
+
it('exports default pricing for Opus 4.5', () => {
|
|
170
|
+
expect(CLAUDE_PRICING.inputPerMillion).toBe(15.00);
|
|
171
|
+
expect(CLAUDE_PRICING.outputPerMillion).toBe(75.00);
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
describe('CLAUDE_MODEL', () => {
|
|
176
|
+
it('exports latest model identifier', () => {
|
|
177
|
+
expect(CLAUDE_MODEL).toBe('claude-opus-4-5-20251101');
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
});
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DeepSeek Adapter - Adapter for DeepSeek API (budget-friendly model)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const { BaseAdapter } = require('./base-adapter.js');
|
|
6
|
+
|
|
7
|
+
// Latest model: deepseek-r1 (January 2025)
|
|
8
|
+
const DEEPSEEK_MODEL = 'deepseek-r1';
|
|
9
|
+
|
|
10
|
+
const DEEPSEEK_PRICING = {
|
|
11
|
+
// Pricing per 1M tokens (DeepSeek R1)
|
|
12
|
+
// Significantly cheaper than OpenAI
|
|
13
|
+
inputPerMillion: 0.55,
|
|
14
|
+
outputPerMillion: 2.19,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
class DeepSeekAdapter extends BaseAdapter {
|
|
18
|
+
constructor(config = {}) {
|
|
19
|
+
super({
|
|
20
|
+
name: 'deepseek',
|
|
21
|
+
...config,
|
|
22
|
+
});
|
|
23
|
+
this.budgetTracker = config.budgetTracker || null;
|
|
24
|
+
this.pricing = config.pricing || DEEPSEEK_PRICING;
|
|
25
|
+
this.model = config.model || DEEPSEEK_MODEL;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Review code for issues and suggestions
|
|
30
|
+
* @param {string} code - Code to review
|
|
31
|
+
* @param {Object} context - Additional context
|
|
32
|
+
* @returns {Promise<Object>} Standardized review response
|
|
33
|
+
*/
|
|
34
|
+
async review(code, context = {}) {
|
|
35
|
+
if (!code || code.trim() === '') {
|
|
36
|
+
return this.createEmptyResponse();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const estimatedTokens = this.estimateTokens(code);
|
|
40
|
+
const estimatedCost = this.estimateCost(estimatedTokens);
|
|
41
|
+
|
|
42
|
+
if (!this.canAfford(estimatedCost)) {
|
|
43
|
+
throw new Error('Budget exceeded for DeepSeek');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const result = await this.callAPI(code, context);
|
|
48
|
+
|
|
49
|
+
if (this.budgetTracker) {
|
|
50
|
+
this.budgetTracker.record('deepseek', result.cost || 0);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return result;
|
|
54
|
+
} catch (error) {
|
|
55
|
+
// DeepSeek errors return empty response with warning
|
|
56
|
+
return {
|
|
57
|
+
...this.createEmptyResponse(),
|
|
58
|
+
warning: `DeepSeek unavailable: ${error.message}`,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Call DeepSeek API (mock implementation)
|
|
65
|
+
* @param {string} code - Code to review
|
|
66
|
+
* @param {Object} context - Additional context
|
|
67
|
+
* @returns {Promise<Object>} API response
|
|
68
|
+
*/
|
|
69
|
+
async callAPI(code, context) {
|
|
70
|
+
const tokensUsed = this.estimateTokens(code);
|
|
71
|
+
const cost = this.estimateCost(tokensUsed);
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
issues: [],
|
|
75
|
+
suggestions: [],
|
|
76
|
+
score: 100,
|
|
77
|
+
model: this.name,
|
|
78
|
+
tokensUsed,
|
|
79
|
+
cost,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Estimate tokens for code
|
|
85
|
+
* @param {string} code - Code to estimate
|
|
86
|
+
* @returns {number} Estimated tokens
|
|
87
|
+
*/
|
|
88
|
+
estimateTokens(code) {
|
|
89
|
+
return Math.ceil(code.length / 4);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Estimate cost for tokens
|
|
94
|
+
* @param {number} tokens - Number of tokens
|
|
95
|
+
* @returns {number} Estimated cost in USD
|
|
96
|
+
*/
|
|
97
|
+
estimateCost(tokens) {
|
|
98
|
+
const inputCost = (tokens * 0.5 * this.pricing.inputPerMillion) / 1_000_000;
|
|
99
|
+
const outputCost = (tokens * 0.5 * this.pricing.outputPerMillion) / 1_000_000;
|
|
100
|
+
return inputCost + outputCost;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Check if adapter can afford a request
|
|
105
|
+
* @param {number} estimatedCost - Estimated cost
|
|
106
|
+
* @returns {boolean}
|
|
107
|
+
*/
|
|
108
|
+
canAfford(estimatedCost = 0) {
|
|
109
|
+
if (!this.budgetTracker) {
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const budgetConfig = this.config.budget || { budgetDaily: 5, budgetMonthly: 50 };
|
|
114
|
+
return this.budgetTracker.canSpend('deepseek', estimatedCost, budgetConfig);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Get current usage stats
|
|
119
|
+
* @returns {Object} Usage stats
|
|
120
|
+
*/
|
|
121
|
+
getUsage() {
|
|
122
|
+
if (!this.budgetTracker) {
|
|
123
|
+
return { daily: 0, monthly: 0, requests: 0 };
|
|
124
|
+
}
|
|
125
|
+
return this.budgetTracker.getUsage('deepseek');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Compare cost with OpenAI equivalent
|
|
130
|
+
* @param {number} tokens - Token count
|
|
131
|
+
* @returns {Object} Cost comparison
|
|
132
|
+
*/
|
|
133
|
+
compareCostWithOpenAI(tokens) {
|
|
134
|
+
const deepseekCost = this.estimateCost(tokens);
|
|
135
|
+
// OpenAI GPT-4 Turbo pricing
|
|
136
|
+
const openaiCost = (tokens * 0.5 * 10.00 + tokens * 0.5 * 30.00) / 1_000_000;
|
|
137
|
+
const savings = openaiCost - deepseekCost;
|
|
138
|
+
const savingsPercent = (savings / openaiCost) * 100;
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
deepseek: deepseekCost,
|
|
142
|
+
openai: openaiCost,
|
|
143
|
+
savings,
|
|
144
|
+
savingsPercent,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
module.exports = {
|
|
150
|
+
DeepSeekAdapter,
|
|
151
|
+
DEEPSEEK_PRICING,
|
|
152
|
+
DEEPSEEK_MODEL,
|
|
153
|
+
};
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { DeepSeekAdapter, DEEPSEEK_PRICING, DEEPSEEK_MODEL } from './deepseek-adapter.js';
|
|
3
|
+
import { BaseAdapter } from './base-adapter.js';
|
|
4
|
+
|
|
5
|
+
describe('DeepSeekAdapter', () => {
|
|
6
|
+
describe('constructor', () => {
|
|
7
|
+
it('sets name to deepseek by default', () => {
|
|
8
|
+
const adapter = new DeepSeekAdapter();
|
|
9
|
+
expect(adapter.name).toBe('deepseek');
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('uses default pricing', () => {
|
|
13
|
+
const adapter = new DeepSeekAdapter();
|
|
14
|
+
expect(adapter.pricing).toBe(DEEPSEEK_PRICING);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('accepts custom pricing', () => {
|
|
18
|
+
const customPricing = { inputPerMillion: 0.20, outputPerMillion: 0.40 };
|
|
19
|
+
const adapter = new DeepSeekAdapter({ pricing: customPricing });
|
|
20
|
+
expect(adapter.pricing).toBe(customPricing);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe('review', () => {
|
|
25
|
+
it('returns empty response for empty code', async () => {
|
|
26
|
+
const adapter = new DeepSeekAdapter();
|
|
27
|
+
const result = await adapter.review('');
|
|
28
|
+
|
|
29
|
+
expect(result.issues).toEqual([]);
|
|
30
|
+
expect(result.model).toBe('deepseek');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('returns standardized response format', async () => {
|
|
34
|
+
const adapter = new DeepSeekAdapter();
|
|
35
|
+
const result = await adapter.review('const x = 1;');
|
|
36
|
+
|
|
37
|
+
expect(BaseAdapter.validateResponse(result)).toBe(true);
|
|
38
|
+
expect(result.model).toBe('deepseek');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('throws when budget exceeded', async () => {
|
|
42
|
+
const mockTracker = {
|
|
43
|
+
canSpend: vi.fn(() => false),
|
|
44
|
+
record: vi.fn(),
|
|
45
|
+
getUsage: vi.fn(() => ({ daily: 5, monthly: 50, requests: 100 })),
|
|
46
|
+
};
|
|
47
|
+
const adapter = new DeepSeekAdapter({ budgetTracker: mockTracker });
|
|
48
|
+
|
|
49
|
+
await expect(adapter.review('const x = 1;')).rejects.toThrow('Budget exceeded');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('records cost after successful review', async () => {
|
|
53
|
+
const mockTracker = {
|
|
54
|
+
canSpend: vi.fn(() => true),
|
|
55
|
+
record: vi.fn(),
|
|
56
|
+
getUsage: vi.fn(() => ({ daily: 0, monthly: 0, requests: 0 })),
|
|
57
|
+
};
|
|
58
|
+
const adapter = new DeepSeekAdapter({ budgetTracker: mockTracker });
|
|
59
|
+
|
|
60
|
+
await adapter.review('const x = 1;');
|
|
61
|
+
|
|
62
|
+
expect(mockTracker.record).toHaveBeenCalledWith('deepseek', expect.any(Number));
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('handles API errors with warning instead of error', async () => {
|
|
66
|
+
const adapter = new DeepSeekAdapter();
|
|
67
|
+
adapter.callAPI = vi.fn().mockRejectedValue(new Error('Connection failed'));
|
|
68
|
+
|
|
69
|
+
const result = await adapter.review('const x = 1;');
|
|
70
|
+
|
|
71
|
+
expect(result.warning).toBe('DeepSeek unavailable: Connection failed');
|
|
72
|
+
expect(result.issues).toEqual([]);
|
|
73
|
+
expect(result.error).toBeUndefined();
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe('estimateCost', () => {
|
|
78
|
+
it('calculates cost based on token count', () => {
|
|
79
|
+
const adapter = new DeepSeekAdapter();
|
|
80
|
+
const cost = adapter.estimateCost(1000);
|
|
81
|
+
|
|
82
|
+
// At default pricing (R1): (500 * 0.55 + 500 * 2.19) / 1M = 0.00137
|
|
83
|
+
expect(cost).toBeCloseTo(0.00137, 5);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('is much cheaper than OpenAI', () => {
|
|
87
|
+
const adapter = new DeepSeekAdapter();
|
|
88
|
+
const comparison = adapter.compareCostWithOpenAI(1000);
|
|
89
|
+
|
|
90
|
+
expect(comparison.deepseek).toBeLessThan(comparison.openai);
|
|
91
|
+
expect(comparison.savingsPercent).toBeGreaterThan(90); // >90% savings
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe('compareCostWithOpenAI', () => {
|
|
96
|
+
it('returns detailed cost comparison', () => {
|
|
97
|
+
const adapter = new DeepSeekAdapter();
|
|
98
|
+
const comparison = adapter.compareCostWithOpenAI(10000);
|
|
99
|
+
|
|
100
|
+
expect(comparison).toHaveProperty('deepseek');
|
|
101
|
+
expect(comparison).toHaveProperty('openai');
|
|
102
|
+
expect(comparison).toHaveProperty('savings');
|
|
103
|
+
expect(comparison).toHaveProperty('savingsPercent');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('calculates savings correctly', () => {
|
|
107
|
+
const adapter = new DeepSeekAdapter();
|
|
108
|
+
const comparison = adapter.compareCostWithOpenAI(1000000);
|
|
109
|
+
|
|
110
|
+
// For 1M tokens:
|
|
111
|
+
// DeepSeek R1: (500k * 0.55 + 500k * 2.19) / 1M = 1.37
|
|
112
|
+
// OpenAI (comparison uses hardcoded $10/$30): (500k * 10 + 500k * 30) / 1M = 20
|
|
113
|
+
expect(comparison.deepseek).toBeCloseTo(1.37, 2);
|
|
114
|
+
expect(comparison.openai).toBeCloseTo(20, 2);
|
|
115
|
+
expect(comparison.savings).toBeCloseTo(18.63, 2);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe('canAfford', () => {
|
|
120
|
+
it('returns true without budget tracker', () => {
|
|
121
|
+
const adapter = new DeepSeekAdapter();
|
|
122
|
+
expect(adapter.canAfford(100)).toBe(true);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('checks budget tracker when available', () => {
|
|
126
|
+
const mockTracker = {
|
|
127
|
+
canSpend: vi.fn(() => true),
|
|
128
|
+
};
|
|
129
|
+
const adapter = new DeepSeekAdapter({
|
|
130
|
+
budgetTracker: mockTracker,
|
|
131
|
+
budget: { budgetDaily: 5, budgetMonthly: 50 },
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
adapter.canAfford(0.01);
|
|
135
|
+
|
|
136
|
+
expect(mockTracker.canSpend).toHaveBeenCalledWith('deepseek', 0.01, expect.any(Object));
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('uses lower default budget than OpenAI', () => {
|
|
140
|
+
const mockTracker = {
|
|
141
|
+
canSpend: vi.fn(() => true),
|
|
142
|
+
};
|
|
143
|
+
const adapter = new DeepSeekAdapter({ budgetTracker: mockTracker });
|
|
144
|
+
|
|
145
|
+
adapter.canAfford(0.01);
|
|
146
|
+
|
|
147
|
+
// Default budget is 5 daily, 50 monthly (lower than typical OpenAI)
|
|
148
|
+
expect(mockTracker.canSpend).toHaveBeenCalledWith(
|
|
149
|
+
'deepseek',
|
|
150
|
+
0.01,
|
|
151
|
+
expect.objectContaining({ budgetDaily: 5, budgetMonthly: 50 })
|
|
152
|
+
);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
describe('getUsage', () => {
|
|
157
|
+
it('returns zero without budget tracker', () => {
|
|
158
|
+
const adapter = new DeepSeekAdapter();
|
|
159
|
+
expect(adapter.getUsage()).toEqual({ daily: 0, monthly: 0, requests: 0 });
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('returns tracker usage when available', () => {
|
|
163
|
+
const mockTracker = {
|
|
164
|
+
getUsage: vi.fn(() => ({ daily: 0.50, monthly: 5.00, requests: 100 })),
|
|
165
|
+
};
|
|
166
|
+
const adapter = new DeepSeekAdapter({ budgetTracker: mockTracker });
|
|
167
|
+
|
|
168
|
+
const usage = adapter.getUsage();
|
|
169
|
+
|
|
170
|
+
expect(usage.daily).toBe(0.50);
|
|
171
|
+
expect(usage.requests).toBe(100);
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
describe('DEEPSEEK_PRICING', () => {
|
|
176
|
+
it('exports pricing constants for R1', () => {
|
|
177
|
+
expect(DEEPSEEK_PRICING.inputPerMillion).toBe(0.55);
|
|
178
|
+
expect(DEEPSEEK_PRICING.outputPerMillion).toBe(2.19);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('is significantly cheaper than OpenAI', () => {
|
|
182
|
+
// OpenAI o3: $10/1M input, $40/1M output
|
|
183
|
+
expect(DEEPSEEK_PRICING.inputPerMillion).toBeLessThan(10);
|
|
184
|
+
expect(DEEPSEEK_PRICING.outputPerMillion).toBeLessThan(40);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
describe('DEEPSEEK_MODEL', () => {
|
|
189
|
+
it('exports latest model identifier', () => {
|
|
190
|
+
expect(DEEPSEEK_MODEL).toBe('deepseek-r1');
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
});
|