orcommit 1.0.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.
- package/LICENSE +21 -0
- package/README.md +318 -0
- package/dist/cli.d.ts +70 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +391 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/modules/api.d.ts +48 -0
- package/dist/modules/api.d.ts.map +1 -0
- package/dist/modules/api.js +286 -0
- package/dist/modules/api.js.map +1 -0
- package/dist/modules/cache.d.ts +74 -0
- package/dist/modules/cache.d.ts.map +1 -0
- package/dist/modules/cache.js +284 -0
- package/dist/modules/cache.js.map +1 -0
- package/dist/modules/config.d.ts +53 -0
- package/dist/modules/config.d.ts.map +1 -0
- package/dist/modules/config.js +180 -0
- package/dist/modules/config.js.map +1 -0
- package/dist/modules/core.d.ts +54 -0
- package/dist/modules/core.d.ts.map +1 -0
- package/dist/modules/core.js +474 -0
- package/dist/modules/core.js.map +1 -0
- package/dist/modules/diff-filter.d.ts +71 -0
- package/dist/modules/diff-filter.d.ts.map +1 -0
- package/dist/modules/diff-filter.js +332 -0
- package/dist/modules/diff-filter.js.map +1 -0
- package/dist/modules/git.d.ts +61 -0
- package/dist/modules/git.d.ts.map +1 -0
- package/dist/modules/git.js +362 -0
- package/dist/modules/git.js.map +1 -0
- package/dist/modules/logger.d.ts +67 -0
- package/dist/modules/logger.d.ts.map +1 -0
- package/dist/modules/logger.js +212 -0
- package/dist/modules/logger.js.map +1 -0
- package/dist/modules/tokenizer.d.ts +43 -0
- package/dist/modules/tokenizer.d.ts.map +1 -0
- package/dist/modules/tokenizer.js +200 -0
- package/dist/modules/tokenizer.js.map +1 -0
- package/dist/types/index.d.ts +141 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +70 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/index.d.ts +64 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +154 -0
- package/dist/utils/index.js.map +1 -0
- package/package.json +75 -0
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import axiosRetry from 'axios-retry';
|
|
3
|
+
import PQueue from 'p-queue';
|
|
4
|
+
import { ApiError, NetworkError, RETRY_CONFIG, CHUNK_LIMITS } from '../types/index.js';
|
|
5
|
+
import { logger } from './logger.js';
|
|
6
|
+
export class ApiManager {
|
|
7
|
+
queue;
|
|
8
|
+
clients = new Map();
|
|
9
|
+
constructor(concurrency = CHUNK_LIMITS.MAX_CONCURRENT_REQUESTS) {
|
|
10
|
+
this.queue = new PQueue({
|
|
11
|
+
concurrency,
|
|
12
|
+
interval: 1000, // 1 second
|
|
13
|
+
intervalCap: concurrency * 2, // Allow burst capacity
|
|
14
|
+
});
|
|
15
|
+
// Handle graceful shutdown
|
|
16
|
+
this.setupGracefulShutdown();
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Initialize API client for a provider
|
|
20
|
+
*/
|
|
21
|
+
initializeProvider(provider, config) {
|
|
22
|
+
const providerConfig = config.providers[provider];
|
|
23
|
+
if (!providerConfig.apiKey) {
|
|
24
|
+
throw new ApiError(`API key not configured for ${provider}`);
|
|
25
|
+
}
|
|
26
|
+
const client = axios.create({
|
|
27
|
+
baseURL: providerConfig.baseUrl,
|
|
28
|
+
timeout: providerConfig.timeout || 60000,
|
|
29
|
+
headers: {
|
|
30
|
+
'Content-Type': 'application/json',
|
|
31
|
+
'Authorization': `Bearer ${providerConfig.apiKey}`,
|
|
32
|
+
'User-Agent': 'orcommit/1.0.0',
|
|
33
|
+
...(provider === 'openrouter' && {
|
|
34
|
+
'HTTP-Referer': 'https://github.com/markolofsen/openrouter-commit',
|
|
35
|
+
'X-Title': 'OpenRouter Commit CLI',
|
|
36
|
+
}),
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
// Configure retry logic
|
|
40
|
+
axiosRetry(client, {
|
|
41
|
+
retries: RETRY_CONFIG.MAX_RETRIES,
|
|
42
|
+
retryDelay: axiosRetry.exponentialDelay,
|
|
43
|
+
retryCondition: (error) => {
|
|
44
|
+
return axiosRetry.isNetworkOrIdempotentRequestError(error) ||
|
|
45
|
+
error.response?.status === 429 ||
|
|
46
|
+
(error.response?.status !== undefined && error.response.status >= 500);
|
|
47
|
+
},
|
|
48
|
+
shouldResetTimeout: true,
|
|
49
|
+
onRetry: (retryCount, error) => {
|
|
50
|
+
logger.warn(`Retry attempt ${retryCount} for ${provider}: ${error.message}`);
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
// Add response interceptor for better error handling
|
|
54
|
+
client.interceptors.response.use(response => response, error => this.handleApiError(error, provider));
|
|
55
|
+
this.clients.set(provider, client);
|
|
56
|
+
logger.debug(`Initialized ${provider} client`, { baseUrl: providerConfig.baseUrl });
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Generate commit message using the specified provider
|
|
60
|
+
*/
|
|
61
|
+
async generateCommitMessage(request, provider) {
|
|
62
|
+
return this.queue.add(async () => {
|
|
63
|
+
try {
|
|
64
|
+
const client = this.clients.get(provider);
|
|
65
|
+
if (!client) {
|
|
66
|
+
throw new ApiError(`Client not initialized for ${provider}`);
|
|
67
|
+
}
|
|
68
|
+
logger.debug(`Sending request to ${provider}`, {
|
|
69
|
+
model: request.model,
|
|
70
|
+
messageCount: request.messages.length,
|
|
71
|
+
maxTokens: request.maxTokens
|
|
72
|
+
});
|
|
73
|
+
const response = await this.makeRequest(client, request, provider);
|
|
74
|
+
const commitMessage = this.extractCommitMessage(response, provider);
|
|
75
|
+
logger.debug(`Received response from ${provider}`, {
|
|
76
|
+
messageLength: commitMessage.length,
|
|
77
|
+
usage: response.usage
|
|
78
|
+
});
|
|
79
|
+
return {
|
|
80
|
+
success: true,
|
|
81
|
+
data: commitMessage,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
logger.error(`API request failed for ${provider}`, error);
|
|
86
|
+
if (error instanceof ApiError && error.isRetryable) {
|
|
87
|
+
return {
|
|
88
|
+
success: false,
|
|
89
|
+
error: error,
|
|
90
|
+
retryAfter: this.calculateRetryDelay(error.statusCode),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
success: false,
|
|
95
|
+
error: error instanceof Error ? new ApiError(error.message, undefined, error) :
|
|
96
|
+
new ApiError('Unknown API error'),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Process multiple chunks in parallel
|
|
103
|
+
*/
|
|
104
|
+
async processChunks(chunks, baseRequest, provider) {
|
|
105
|
+
try {
|
|
106
|
+
const promises = chunks.map(chunk => this.generateCommitMessage({
|
|
107
|
+
provider: baseRequest.provider,
|
|
108
|
+
model: baseRequest.model,
|
|
109
|
+
maxTokens: baseRequest.maxTokens,
|
|
110
|
+
temperature: baseRequest.temperature,
|
|
111
|
+
messages: [
|
|
112
|
+
{ role: 'system', content: baseRequest.systemPrompt },
|
|
113
|
+
{ role: 'user', content: chunk }
|
|
114
|
+
],
|
|
115
|
+
}, provider));
|
|
116
|
+
const results = await Promise.allSettled(promises);
|
|
117
|
+
const successfulResults = [];
|
|
118
|
+
const errors = [];
|
|
119
|
+
for (const result of results) {
|
|
120
|
+
if (result.status === 'fulfilled' && result.value.success && result.value.data) {
|
|
121
|
+
successfulResults.push(result.value.data);
|
|
122
|
+
}
|
|
123
|
+
else if (result.status === 'fulfilled' && result.value.error) {
|
|
124
|
+
errors.push(result.value.error instanceof ApiError ? result.value.error : new ApiError('Unknown error'));
|
|
125
|
+
}
|
|
126
|
+
else if (result.status === 'rejected') {
|
|
127
|
+
errors.push(new ApiError('Promise rejected', undefined, result.reason));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (successfulResults.length === 0) {
|
|
131
|
+
return {
|
|
132
|
+
success: false,
|
|
133
|
+
error: new ApiError(`All chunk processing failed. ${errors.length} errors occurred.`),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
success: true,
|
|
138
|
+
data: successfulResults,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
return {
|
|
143
|
+
success: false,
|
|
144
|
+
error: error instanceof Error ? new ApiError(error.message, undefined, error) :
|
|
145
|
+
new ApiError('Unknown chunk processing error'),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Test API connection
|
|
151
|
+
*/
|
|
152
|
+
async testConnection(provider) {
|
|
153
|
+
try {
|
|
154
|
+
const client = this.clients.get(provider);
|
|
155
|
+
if (!client) {
|
|
156
|
+
throw new ApiError(`Client not initialized for ${provider}`);
|
|
157
|
+
}
|
|
158
|
+
const testRequest = {
|
|
159
|
+
provider,
|
|
160
|
+
model: provider === 'openrouter' ? 'openai/gpt-3.5-turbo' : 'gpt-3.5-turbo',
|
|
161
|
+
messages: [{ role: 'user', content: 'test' }],
|
|
162
|
+
maxTokens: 10,
|
|
163
|
+
temperature: 0.1,
|
|
164
|
+
};
|
|
165
|
+
const result = await this.generateCommitMessage(testRequest, provider);
|
|
166
|
+
return result.success;
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
logger.error(`Connection test failed for ${provider}`, error);
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Get queue status
|
|
175
|
+
*/
|
|
176
|
+
getQueueStatus() {
|
|
177
|
+
return {
|
|
178
|
+
pending: this.queue.pending,
|
|
179
|
+
size: this.queue.size,
|
|
180
|
+
isPaused: this.queue.isPaused,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Clear the queue and wait for ongoing requests
|
|
185
|
+
*/
|
|
186
|
+
async shutdown() {
|
|
187
|
+
logger.info('Shutting down API manager...');
|
|
188
|
+
this.queue.clear();
|
|
189
|
+
await this.queue.onIdle();
|
|
190
|
+
logger.info('API manager shutdown complete');
|
|
191
|
+
}
|
|
192
|
+
// Private methods
|
|
193
|
+
async makeRequest(client, request, provider) {
|
|
194
|
+
const endpoint = '/chat/completions';
|
|
195
|
+
const payload = {
|
|
196
|
+
model: request.model,
|
|
197
|
+
messages: request.messages,
|
|
198
|
+
max_tokens: request.maxTokens,
|
|
199
|
+
temperature: request.temperature,
|
|
200
|
+
stream: request.stream ?? false,
|
|
201
|
+
};
|
|
202
|
+
const config = {
|
|
203
|
+
timeout: 60000,
|
|
204
|
+
};
|
|
205
|
+
const response = await client.post(endpoint, payload, config);
|
|
206
|
+
return this.parseResponse(response.data, provider);
|
|
207
|
+
}
|
|
208
|
+
parseResponse(data, provider) {
|
|
209
|
+
if (!data.choices || !Array.isArray(data.choices) || data.choices.length === 0) {
|
|
210
|
+
throw new ApiError(`Invalid response format from ${provider}: no choices found`);
|
|
211
|
+
}
|
|
212
|
+
const choice = data.choices[0];
|
|
213
|
+
const message = choice.message?.content;
|
|
214
|
+
if (!message) {
|
|
215
|
+
throw new ApiError(`Invalid response format from ${provider}: no message content`);
|
|
216
|
+
}
|
|
217
|
+
return {
|
|
218
|
+
message,
|
|
219
|
+
usage: data.usage ? {
|
|
220
|
+
promptTokens: data.usage.prompt_tokens,
|
|
221
|
+
completionTokens: data.usage.completion_tokens,
|
|
222
|
+
totalTokens: data.usage.total_tokens,
|
|
223
|
+
} : undefined,
|
|
224
|
+
model: data.model || 'unknown',
|
|
225
|
+
finishReason: choice.finish_reason || 'unknown',
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
extractCommitMessage(response, provider) {
|
|
229
|
+
let message = response.message.trim();
|
|
230
|
+
// Remove common prefixes and formatting
|
|
231
|
+
message = message.replace(/^(commit message:|commit:|message:)\s*/i, '');
|
|
232
|
+
message = message.replace(/^["']|["']$/g, ''); // Remove surrounding quotes
|
|
233
|
+
message = message.replace(/^\*\s*/, ''); // Remove leading asterisk
|
|
234
|
+
// Ensure it's not empty
|
|
235
|
+
if (!message || message.length < 3) {
|
|
236
|
+
throw new ApiError(`Generated commit message too short from ${provider}: "${message}"`);
|
|
237
|
+
}
|
|
238
|
+
// Limit length (conventional commits should be concise)
|
|
239
|
+
if (message.length > 200) {
|
|
240
|
+
logger.warn(`Commit message truncated (was ${message.length} characters)`);
|
|
241
|
+
message = message.substring(0, 197) + '...';
|
|
242
|
+
}
|
|
243
|
+
return message;
|
|
244
|
+
}
|
|
245
|
+
handleApiError(error, provider) {
|
|
246
|
+
if (error.response) {
|
|
247
|
+
// HTTP error response
|
|
248
|
+
const status = error.response.status;
|
|
249
|
+
const data = error.response.data;
|
|
250
|
+
let message = `${provider} API error (${status})`;
|
|
251
|
+
if (data?.error?.message) {
|
|
252
|
+
message += `: ${data.error.message}`;
|
|
253
|
+
}
|
|
254
|
+
else if (data?.message) {
|
|
255
|
+
message += `: ${data.message}`;
|
|
256
|
+
}
|
|
257
|
+
throw new ApiError(message, status, error);
|
|
258
|
+
}
|
|
259
|
+
else if (error.request) {
|
|
260
|
+
// Network error
|
|
261
|
+
throw new NetworkError(`Network error communicating with ${provider}: ${error.message}`, error);
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
// Other error
|
|
265
|
+
throw new ApiError(`Request setup error for ${provider}: ${error.message}`, undefined, error);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
calculateRetryDelay(statusCode) {
|
|
269
|
+
if (statusCode === 429) {
|
|
270
|
+
return RETRY_CONFIG.BASE_DELAY * 2; // Rate limited, wait longer
|
|
271
|
+
}
|
|
272
|
+
return RETRY_CONFIG.BASE_DELAY;
|
|
273
|
+
}
|
|
274
|
+
setupGracefulShutdown() {
|
|
275
|
+
const gracefulShutdown = async (signal) => {
|
|
276
|
+
logger.info(`Received ${signal}, shutting down gracefully...`);
|
|
277
|
+
await this.shutdown();
|
|
278
|
+
process.exit(0);
|
|
279
|
+
};
|
|
280
|
+
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
|
|
281
|
+
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
// Singleton instance
|
|
285
|
+
export const apiManager = new ApiManager();
|
|
286
|
+
//# sourceMappingURL=api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/modules/api.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
export interface CacheEntry<T> {
|
|
2
|
+
data: T;
|
|
3
|
+
timestamp: number;
|
|
4
|
+
hash: string;
|
|
5
|
+
model: string;
|
|
6
|
+
provider: string;
|
|
7
|
+
}
|
|
8
|
+
export interface CacheOptions {
|
|
9
|
+
ttl?: number;
|
|
10
|
+
maxSize?: number;
|
|
11
|
+
enabled?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare class CacheManager {
|
|
14
|
+
private readonly cacheDir;
|
|
15
|
+
private readonly options;
|
|
16
|
+
private memoryCache;
|
|
17
|
+
constructor(options?: CacheOptions);
|
|
18
|
+
/**
|
|
19
|
+
* Generate cache key from diff content and request parameters
|
|
20
|
+
*/
|
|
21
|
+
private generateCacheKey;
|
|
22
|
+
/**
|
|
23
|
+
* Get cached commit message if available and valid
|
|
24
|
+
*/
|
|
25
|
+
get(content: string, model: string, provider: string, temperature: number): Promise<string | null>;
|
|
26
|
+
/**
|
|
27
|
+
* Store commit message in cache
|
|
28
|
+
*/
|
|
29
|
+
set(content: string, model: string, provider: string, temperature: number, commitMessage: string): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Check if cache entry is still valid
|
|
32
|
+
*/
|
|
33
|
+
private isEntryValid;
|
|
34
|
+
/**
|
|
35
|
+
* Get entry from disk cache
|
|
36
|
+
*/
|
|
37
|
+
private getFromDisk;
|
|
38
|
+
/**
|
|
39
|
+
* Save entry to disk cache
|
|
40
|
+
*/
|
|
41
|
+
private saveToDisk;
|
|
42
|
+
/**
|
|
43
|
+
* Ensure cache directory exists
|
|
44
|
+
*/
|
|
45
|
+
private ensureCacheDir;
|
|
46
|
+
/**
|
|
47
|
+
* Evict old entries from memory cache to keep it under size limit
|
|
48
|
+
*/
|
|
49
|
+
private evictMemoryCache;
|
|
50
|
+
/**
|
|
51
|
+
* Clean up expired entries from disk cache
|
|
52
|
+
*/
|
|
53
|
+
cleanup(): Promise<void>;
|
|
54
|
+
/**
|
|
55
|
+
* Get cache statistics
|
|
56
|
+
*/
|
|
57
|
+
getStats(): Promise<{
|
|
58
|
+
memoryEntries: number;
|
|
59
|
+
diskEntries: number;
|
|
60
|
+
totalSize: string;
|
|
61
|
+
oldestEntry?: Date;
|
|
62
|
+
newestEntry?: Date;
|
|
63
|
+
}>;
|
|
64
|
+
/**
|
|
65
|
+
* Clear all cache (memory and disk)
|
|
66
|
+
*/
|
|
67
|
+
clear(): Promise<void>;
|
|
68
|
+
/**
|
|
69
|
+
* Format bytes as human readable
|
|
70
|
+
*/
|
|
71
|
+
private formatBytes;
|
|
72
|
+
}
|
|
73
|
+
export declare const cacheManager: CacheManager;
|
|
74
|
+
//# sourceMappingURL=cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/modules/cache.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,UAAU,CAAC,CAAC;IAC3B,IAAI,EAAE,CAAC,CAAC;IACR,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAyB;IACjD,OAAO,CAAC,WAAW,CAA8C;gBAErD,OAAO,GAAE,YAAiB;IAStC;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAWxB;;OAEG;IACG,GAAG,CACP,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAiCzB;;OAEG;IACG,GAAG,CACP,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,IAAI,CAAC;IAiChB;;OAEG;IACH,OAAO,CAAC,YAAY;IAOpB;;OAEG;YACW,WAAW;IAiBzB;;OAEG;YACW,UAAU;IAcxB;;OAEG;YACW,cAAc;IAQ5B;;OAEG;YACW,gBAAgB;IAqB9B;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IA8C9B;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC;QACxB,aAAa,EAAE,MAAM,CAAC;QACtB,WAAW,EAAE,MAAM,CAAC;QACpB,SAAS,EAAE,MAAM,CAAC;QAClB,WAAW,CAAC,EAAE,IAAI,CAAC;QACnB,WAAW,CAAC,EAAE,IAAI,CAAC;KACpB,CAAC;IAgDF;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAsB5B;;OAEG;IACH,OAAO,CAAC,WAAW;CAYpB;AAGD,eAAO,MAAM,YAAY,cAAqB,CAAC"}
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import { createHash } from 'crypto';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { homedir } from 'os';
|
|
5
|
+
import { logger } from './logger.js';
|
|
6
|
+
export class CacheManager {
|
|
7
|
+
cacheDir;
|
|
8
|
+
options;
|
|
9
|
+
memoryCache = new Map();
|
|
10
|
+
constructor(options = {}) {
|
|
11
|
+
this.cacheDir = join(homedir(), '.cache', 'orcommit');
|
|
12
|
+
this.options = {
|
|
13
|
+
ttl: options.ttl ?? 24 * 60 * 60 * 1000, // 24 hours
|
|
14
|
+
maxSize: options.maxSize ?? 50, // 50 MB
|
|
15
|
+
enabled: options.enabled ?? true,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Generate cache key from diff content and request parameters
|
|
20
|
+
*/
|
|
21
|
+
generateCacheKey(content, model, provider, temperature) {
|
|
22
|
+
const hash = createHash('sha256')
|
|
23
|
+
.update(content)
|
|
24
|
+
.update(model)
|
|
25
|
+
.update(provider)
|
|
26
|
+
.update(temperature.toString())
|
|
27
|
+
.digest('hex');
|
|
28
|
+
return hash.slice(0, 16); // Use first 16 chars for shorter filenames
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Get cached commit message if available and valid
|
|
32
|
+
*/
|
|
33
|
+
async get(content, model, provider, temperature) {
|
|
34
|
+
if (!this.options.enabled) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
const key = this.generateCacheKey(content, model, provider, temperature);
|
|
38
|
+
try {
|
|
39
|
+
// Check memory cache first
|
|
40
|
+
const memoryEntry = this.memoryCache.get(key);
|
|
41
|
+
if (memoryEntry && this.isEntryValid(memoryEntry)) {
|
|
42
|
+
logger.debug('Cache hit (memory)', { key });
|
|
43
|
+
return memoryEntry.data;
|
|
44
|
+
}
|
|
45
|
+
// Check disk cache
|
|
46
|
+
const diskEntry = await this.getFromDisk(key);
|
|
47
|
+
if (diskEntry && this.isEntryValid(diskEntry)) {
|
|
48
|
+
// Promote to memory cache
|
|
49
|
+
this.memoryCache.set(key, diskEntry);
|
|
50
|
+
logger.debug('Cache hit (disk)', { key });
|
|
51
|
+
return diskEntry.data;
|
|
52
|
+
}
|
|
53
|
+
logger.debug('Cache miss', { key });
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
logger.warn(`Cache read error: ${error.message}`);
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Store commit message in cache
|
|
63
|
+
*/
|
|
64
|
+
async set(content, model, provider, temperature, commitMessage) {
|
|
65
|
+
if (!this.options.enabled) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const key = this.generateCacheKey(content, model, provider, temperature);
|
|
69
|
+
const contentHash = createHash('sha256').update(content).digest('hex');
|
|
70
|
+
const entry = {
|
|
71
|
+
data: commitMessage,
|
|
72
|
+
timestamp: Date.now(),
|
|
73
|
+
hash: contentHash,
|
|
74
|
+
model,
|
|
75
|
+
provider,
|
|
76
|
+
};
|
|
77
|
+
try {
|
|
78
|
+
// Store in memory cache
|
|
79
|
+
this.memoryCache.set(key, entry);
|
|
80
|
+
// Limit memory cache size
|
|
81
|
+
await this.evictMemoryCache();
|
|
82
|
+
// Store in disk cache
|
|
83
|
+
await this.saveToDisk(key, entry);
|
|
84
|
+
logger.debug('Cache stored', { key, provider, model });
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
logger.warn(`Cache write error: ${error.message}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Check if cache entry is still valid
|
|
92
|
+
*/
|
|
93
|
+
isEntryValid(entry) {
|
|
94
|
+
const now = Date.now();
|
|
95
|
+
const age = now - entry.timestamp;
|
|
96
|
+
return age < this.options.ttl;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Get entry from disk cache
|
|
100
|
+
*/
|
|
101
|
+
async getFromDisk(key) {
|
|
102
|
+
try {
|
|
103
|
+
await this.ensureCacheDir();
|
|
104
|
+
const filePath = join(this.cacheDir, `${key}.json`);
|
|
105
|
+
const data = await fs.readFile(filePath, 'utf-8');
|
|
106
|
+
return JSON.parse(data);
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
if (error.code !== 'ENOENT') {
|
|
110
|
+
logger.debug('Disk cache read error', error);
|
|
111
|
+
}
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Save entry to disk cache
|
|
117
|
+
*/
|
|
118
|
+
async saveToDisk(key, entry) {
|
|
119
|
+
try {
|
|
120
|
+
await this.ensureCacheDir();
|
|
121
|
+
const filePath = join(this.cacheDir, `${key}.json`);
|
|
122
|
+
const data = JSON.stringify(entry, null, 2);
|
|
123
|
+
await fs.writeFile(filePath, data, 'utf-8');
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
logger.debug('Disk cache write error', error);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Ensure cache directory exists
|
|
131
|
+
*/
|
|
132
|
+
async ensureCacheDir() {
|
|
133
|
+
try {
|
|
134
|
+
await fs.access(this.cacheDir);
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
await fs.mkdir(this.cacheDir, { recursive: true });
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Evict old entries from memory cache to keep it under size limit
|
|
142
|
+
*/
|
|
143
|
+
async evictMemoryCache() {
|
|
144
|
+
const maxEntries = 100; // Keep reasonable number in memory
|
|
145
|
+
if (this.memoryCache.size <= maxEntries) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
// Convert to array and sort by timestamp (oldest first)
|
|
149
|
+
const entries = Array.from(this.memoryCache.entries())
|
|
150
|
+
.sort(([, a], [, b]) => a.timestamp - b.timestamp);
|
|
151
|
+
// Remove oldest entries
|
|
152
|
+
const toRemove = entries.slice(0, entries.length - maxEntries);
|
|
153
|
+
for (const [key] of toRemove) {
|
|
154
|
+
this.memoryCache.delete(key);
|
|
155
|
+
}
|
|
156
|
+
logger.debug(`Evicted ${toRemove.length} entries from memory cache`);
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Clean up expired entries from disk cache
|
|
160
|
+
*/
|
|
161
|
+
async cleanup() {
|
|
162
|
+
if (!this.options.enabled) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
try {
|
|
166
|
+
await this.ensureCacheDir();
|
|
167
|
+
const files = await fs.readdir(this.cacheDir);
|
|
168
|
+
const now = Date.now();
|
|
169
|
+
let cleanedCount = 0;
|
|
170
|
+
for (const file of files) {
|
|
171
|
+
if (!file.endsWith('.json'))
|
|
172
|
+
continue;
|
|
173
|
+
try {
|
|
174
|
+
const filePath = join(this.cacheDir, file);
|
|
175
|
+
const data = await fs.readFile(filePath, 'utf-8');
|
|
176
|
+
const entry = JSON.parse(data);
|
|
177
|
+
const age = now - entry.timestamp;
|
|
178
|
+
if (age > this.options.ttl) {
|
|
179
|
+
await fs.unlink(filePath);
|
|
180
|
+
cleanedCount++;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
// If we can't parse the file, delete it
|
|
185
|
+
try {
|
|
186
|
+
await fs.unlink(join(this.cacheDir, file));
|
|
187
|
+
cleanedCount++;
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
// Ignore deletion errors
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (cleanedCount > 0) {
|
|
195
|
+
logger.debug(`Cleaned up ${cleanedCount} expired cache entries`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
logger.warn(`Cache cleanup error: ${error.message}`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Get cache statistics
|
|
204
|
+
*/
|
|
205
|
+
async getStats() {
|
|
206
|
+
const memoryEntries = this.memoryCache.size;
|
|
207
|
+
let diskEntries = 0;
|
|
208
|
+
let totalSize = 0;
|
|
209
|
+
let oldestTimestamp = Number.MAX_SAFE_INTEGER;
|
|
210
|
+
let newestTimestamp = 0;
|
|
211
|
+
try {
|
|
212
|
+
await this.ensureCacheDir();
|
|
213
|
+
const files = await fs.readdir(this.cacheDir);
|
|
214
|
+
for (const file of files) {
|
|
215
|
+
if (!file.endsWith('.json'))
|
|
216
|
+
continue;
|
|
217
|
+
try {
|
|
218
|
+
const filePath = join(this.cacheDir, file);
|
|
219
|
+
const stats = await fs.stat(filePath);
|
|
220
|
+
const data = await fs.readFile(filePath, 'utf-8');
|
|
221
|
+
const entry = JSON.parse(data);
|
|
222
|
+
diskEntries++;
|
|
223
|
+
totalSize += stats.size;
|
|
224
|
+
if (entry.timestamp < oldestTimestamp) {
|
|
225
|
+
oldestTimestamp = entry.timestamp;
|
|
226
|
+
}
|
|
227
|
+
if (entry.timestamp > newestTimestamp) {
|
|
228
|
+
newestTimestamp = entry.timestamp;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
// Skip invalid files
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
catch (error) {
|
|
237
|
+
logger.warn(`Error getting cache stats: ${error.message}`);
|
|
238
|
+
}
|
|
239
|
+
return {
|
|
240
|
+
memoryEntries,
|
|
241
|
+
diskEntries,
|
|
242
|
+
totalSize: this.formatBytes(totalSize),
|
|
243
|
+
oldestEntry: oldestTimestamp !== Number.MAX_SAFE_INTEGER ? new Date(oldestTimestamp) : undefined,
|
|
244
|
+
newestEntry: newestTimestamp > 0 ? new Date(newestTimestamp) : undefined,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Clear all cache (memory and disk)
|
|
249
|
+
*/
|
|
250
|
+
async clear() {
|
|
251
|
+
// Clear memory cache
|
|
252
|
+
this.memoryCache.clear();
|
|
253
|
+
// Clear disk cache
|
|
254
|
+
try {
|
|
255
|
+
await this.ensureCacheDir();
|
|
256
|
+
const files = await fs.readdir(this.cacheDir);
|
|
257
|
+
for (const file of files) {
|
|
258
|
+
if (file.endsWith('.json')) {
|
|
259
|
+
await fs.unlink(join(this.cacheDir, file));
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
logger.debug('Cache cleared');
|
|
263
|
+
}
|
|
264
|
+
catch (error) {
|
|
265
|
+
logger.warn(`Error clearing cache: ${error.message}`);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Format bytes as human readable
|
|
270
|
+
*/
|
|
271
|
+
formatBytes(bytes) {
|
|
272
|
+
const units = ['B', 'KB', 'MB', 'GB'];
|
|
273
|
+
let size = bytes;
|
|
274
|
+
let unitIndex = 0;
|
|
275
|
+
while (size >= 1024 && unitIndex < units.length - 1) {
|
|
276
|
+
size /= 1024;
|
|
277
|
+
unitIndex++;
|
|
278
|
+
}
|
|
279
|
+
return `${size.toFixed(1)} ${units[unitIndex]}`;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
// Singleton instance
|
|
283
|
+
export const cacheManager = new CacheManager();
|
|
284
|
+
//# sourceMappingURL=cache.js.map
|