chore: add proper-lockfile for safe database read/write operations and implement retry logic for file access

This commit is contained in:
decolua
2026-03-27 10:31:35 +07:00
parent 3059df4014
commit 8759545260
15 changed files with 648 additions and 57 deletions
+1
View File
@@ -33,6 +33,7 @@ export const PROVIDERS = {
claude: {
baseUrl: "https://api.anthropic.com/v1/messages",
format: "claude",
retry: { 429: 0 },
headers: {
"Anthropic-Version": "2023-06-01",
"Anthropic-Beta": "claude-code-20250219,oauth-2025-04-20,interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14,context-management-2025-06-27,prompt-caching-scope-2026-01-05",
+8 -1
View File
@@ -60,12 +60,19 @@ export const MEMORY_CONFIG = {
export const DEFAULT_MAX_TOKENS = 64000;
export const DEFAULT_MIN_TOKENS = 32000;
// Retry config for 429 responses
// Retry config for 429 responses (legacy - kept for backward compatibility)
export const RETRY_CONFIG = {
maxAttempts: 2,
delayMs: 2000
};
// Default retry config by status code (number of retry attempts)
export const DEFAULT_RETRY_CONFIG = {
429: 2, // Rate limit - retry 2 times
503: 0, // Service unavailable - no retry
502: 0 // Bad gateway - no retry
};
// Exponential backoff config for rate limits
export const BACKOFF_CONFIG = {
base: 1000,
+8 -4
View File
@@ -1,4 +1,4 @@
import { HTTP_STATUS, RETRY_CONFIG } from "../config/runtimeConfig.js";
import { HTTP_STATUS, RETRY_CONFIG, DEFAULT_RETRY_CONFIG } from "../config/runtimeConfig.js";
import { proxyAwareFetch } from "../utils/proxyFetch.js";
/**
@@ -81,6 +81,9 @@ export class BaseExecutor {
let lastError = null;
let lastStatus = 0;
const retryAttemptsByUrl = {};
// Merge default retry config with provider-specific config
const retryConfig = { ...DEFAULT_RETRY_CONFIG, ...this.config.retry };
for (let urlIndex = 0; urlIndex < fallbackCount; urlIndex++) {
const url = this.buildUrl(model, stream, urlIndex, credentials);
@@ -97,10 +100,11 @@ export class BaseExecutor {
signal
}, proxyOptions);
// Retry 429 with fixed delay before falling back to next URL
if (response.status === HTTP_STATUS.RATE_LIMITED && retryAttemptsByUrl[urlIndex] < RETRY_CONFIG.maxAttempts) {
// Retry based on status code config
const maxRetries = retryConfig[response.status] || 0;
if (maxRetries > 0 && retryAttemptsByUrl[urlIndex] < maxRetries) {
retryAttemptsByUrl[urlIndex]++;
log?.debug?.("RETRY", `429 retry ${retryAttemptsByUrl[urlIndex]}/${RETRY_CONFIG.maxAttempts} after ${RETRY_CONFIG.delayMs / 1000}s`);
log?.debug?.("RETRY", `${response.status} retry ${retryAttemptsByUrl[urlIndex]}/${maxRetries} after ${RETRY_CONFIG.delayMs / 1000}s`);
await new Promise(resolve => setTimeout(resolve, RETRY_CONFIG.delayMs));
urlIndex--;
continue;
+33 -16
View File
@@ -3,6 +3,7 @@ import { PROVIDERS } from "../config/providers.js";
import { v4 as uuidv4 } from "uuid";
import { refreshKiroToken } from "../services/tokenRefresh.js";
import { proxyAwareFetch } from "../utils/proxyFetch.js";
import { HTTP_STATUS, RETRY_CONFIG, DEFAULT_RETRY_CONFIG } from "../config/runtimeConfig.js";
/**
* KiroExecutor - Executor for Kiro AI (AWS CodeWhisperer)
@@ -32,29 +33,45 @@ export class KiroExecutor extends BaseExecutor {
}
/**
* Custom execute for Kiro - handles AWS EventStream binary response
* Custom execute for Kiro - handles AWS EventStream binary response with retry support
*/
async execute({ model, body, stream, credentials, signal, log, proxyOptions = null }) {
const url = this.buildUrl(model, stream, 0);
const headers = this.buildHeaders(credentials, stream);
const transformedBody = this.transformRequest(model, body, stream, credentials);
// Merge default retry config with provider-specific config
const retryConfig = { ...DEFAULT_RETRY_CONFIG, ...this.config.retry };
let retryAttempts = 0;
const response = await proxyAwareFetch(url, {
method: "POST",
headers,
body: JSON.stringify(transformedBody),
signal
}, proxyOptions);
while (true) {
const headers = this.buildHeaders(credentials, stream);
const response = await proxyAwareFetch(url, {
method: "POST",
headers,
body: JSON.stringify(transformedBody),
signal
}, proxyOptions);
if (!response.ok) {
return { response, url, headers, transformedBody };
// Check if should retry based on status code
const maxRetries = retryConfig[response.status] || 0;
if (!response.ok && maxRetries > 0 && retryAttempts < maxRetries) {
retryAttempts++;
log?.debug?.("RETRY", `${response.status} retry ${retryAttempts}/${maxRetries} after ${RETRY_CONFIG.delayMs / 1000}s`);
await new Promise(resolve => setTimeout(resolve, RETRY_CONFIG.delayMs));
continue;
}
if (!response.ok) {
return { response, url, headers, transformedBody };
}
// Success - transform and return
// For Kiro, we need to transform the binary EventStream to SSE
// Create a TransformStream to convert binary to SSE text
const transformedResponse = this.transformEventStreamToSSE(response, model);
return { response: transformedResponse, url, headers, transformedBody };
}
// For Kiro, we need to transform the binary EventStream to SSE
// Create a TransformStream to convert binary to SSE text
const transformedResponse = this.transformEventStreamToSSE(response, model);
return { response: transformedResponse, url, headers, transformedBody };
}
/**