mirror of
https://github.com/Nezumi-2711/9router.git
synced 2026-09-22 13:38:31 +00:00
chore: add proper-lockfile for safe database read/write operations and implement retry logic for file access
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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 };
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user