swayrouter 1.0.8 → 1.0.9
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/package.json
CHANGED
|
@@ -59,8 +59,22 @@ export function isNonRetryableRequestStatus(status) {
|
|
|
59
59
|
export function isCancellationStatus(status) {
|
|
60
60
|
return Number(status) === 499;
|
|
61
61
|
}
|
|
62
|
-
|
|
63
|
-
const
|
|
62
|
+
|
|
63
|
+
const QUOTA_EXHAUSTION_PATTERNS = [
|
|
64
|
+
/\binsufficient[_\s-]+quota\b/i,
|
|
65
|
+
/\b(?:quota|resource)[\s_-]+(?:is[\s_-]+)?(?:exhausted|depleted)\b/i,
|
|
66
|
+
/\b(?:quota|resource)[\s_-]+(?:has[\s_-]+been[\s_-]+)?(?:exceeded|reached)\b/i,
|
|
67
|
+
/\b(?:exceeded|exhausted|depleted)\s+(?:(?:your|the)\s+)?(?:current\s+)?(?:quota|resource)\b/i,
|
|
68
|
+
/\b(?:daily|weekly|monthly)\s+(?:usage\s+)?(?:quota|limit)\s+(?:is\s+)?(?:exceeded|exhausted|reached)\b/i,
|
|
69
|
+
/\bno\s+(?:remaining\s+)?quota\b/i,
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
export function isQuotaExhaustionError(errorText) {
|
|
73
|
+
const text = typeof errorText === "string" ? errorText : JSON.stringify(errorText ?? "");
|
|
74
|
+
return QUOTA_EXHAUSTION_PATTERNS.some((pattern) => pattern.test(text));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const T1_BACKOFF_BASE = 30 * 1000;
|
|
64
78
|
const T1_BACKOFF_CAP = 5 * 60 * 1000;
|
|
65
79
|
|
|
66
80
|
const T3_DEPRIORITIZE_MS = 10 * 60 * 1000;
|
|
@@ -93,22 +107,30 @@ export const COOLDOWN_MS = {
|
|
|
93
107
|
requestNotAllowed: COOLDOWN.short,
|
|
94
108
|
};
|
|
95
109
|
|
|
96
|
-
export function classifyError(status, errorText, retryCount = 0, opts = {}) {
|
|
97
|
-
const lowerError = errorText
|
|
98
|
-
? (typeof errorText === "string" ? errorText : JSON.stringify(errorText)).toLowerCase()
|
|
99
|
-
: "";
|
|
100
|
-
const rng = typeof opts.rng === "function" ? opts.rng : null;
|
|
101
|
-
|
|
110
|
+
export function classifyError(status, errorText, retryCount = 0, opts = {}) {
|
|
111
|
+
const lowerError = errorText
|
|
112
|
+
? (typeof errorText === "string" ? errorText : JSON.stringify(errorText)).toLowerCase()
|
|
113
|
+
: "";
|
|
114
|
+
const rng = typeof opts.rng === "function" ? opts.rng : null;
|
|
115
|
+
|
|
116
|
+
const quotaExhausted = isQuotaExhaustionError(lowerError);
|
|
117
|
+
if (quotaExhausted || Number(status) === 429) {
|
|
118
|
+
const exp = T1_BACKOFF_BASE * Math.pow(2, retryCount);
|
|
119
|
+
const base = Math.min(exp, T1_BACKOFF_CAP);
|
|
120
|
+
const jitter = rng ? Math.floor(rng() * base * JITTER_FRACTION) : 0;
|
|
121
|
+
return { tier: ERROR_TIERS.T1, action: "cooldown", cooldownMs: base + jitter };
|
|
122
|
+
}
|
|
123
|
+
|
|
102
124
|
if (isNonRetryableRequestStatus(status) || isCancellationStatus(status)) {
|
|
103
|
-
return {
|
|
104
|
-
tier: ERROR_TIERS.T3,
|
|
105
|
-
action: "final",
|
|
106
|
-
cooldownMs: 0,
|
|
125
|
+
return {
|
|
126
|
+
tier: ERROR_TIERS.T3,
|
|
127
|
+
action: "final",
|
|
128
|
+
cooldownMs: 0,
|
|
107
129
|
};
|
|
108
130
|
}
|
|
109
131
|
|
|
110
132
|
const t1Texts = ["rate limit", "too many requests", "quota exceeded", "capacity", "overloaded"];
|
|
111
|
-
if (t1Texts.some((t) => lowerError.includes(t))
|
|
133
|
+
if (t1Texts.some((t) => lowerError.includes(t))) {
|
|
112
134
|
const exp = T1_BACKOFF_BASE * Math.pow(2, retryCount);
|
|
113
135
|
const base = Math.min(exp, T1_BACKOFF_CAP);
|
|
114
136
|
const jitter = rng ? Math.floor(rng() * base * JITTER_FRACTION) : 0;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { NextResponse } from "next/server";
|
|
2
|
-
import {
|
|
3
|
-
getProviderConnectionById,
|
|
4
|
-
getProxyPoolById,
|
|
5
|
-
updateProviderConnection,
|
|
6
|
-
deleteProviderConnection,
|
|
7
|
-
} from "@/models";
|
|
2
|
+
import {
|
|
3
|
+
getProviderConnectionById,
|
|
4
|
+
getProxyPoolById,
|
|
5
|
+
updateProviderConnection,
|
|
6
|
+
deleteProviderConnection,
|
|
7
|
+
} from "@/models";
|
|
8
|
+
import { clearAllLocksForConnection } from "@/lib/db/repos/accountModelLocksRepo.js";
|
|
8
9
|
|
|
9
10
|
function normalizeProxyConfig(body = {}) {
|
|
10
11
|
const hasAnyProxyField =
|
|
@@ -118,7 +119,12 @@ export async function PUT(request, { params }) {
|
|
|
118
119
|
if (priority !== undefined) updateData.priority = priority;
|
|
119
120
|
if (globalPriority !== undefined) updateData.globalPriority = globalPriority;
|
|
120
121
|
if (defaultModel !== undefined) updateData.defaultModel = defaultModel;
|
|
121
|
-
if (isActive !== undefined)
|
|
122
|
+
if (isActive !== undefined) {
|
|
123
|
+
updateData.isActive = isActive;
|
|
124
|
+
if (isActive === true && existing.isActive === false) {
|
|
125
|
+
await clearAllLocksForConnection(id);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
122
128
|
if (apiKey && existing.authType === "apikey") {
|
|
123
129
|
updateData.apiKey = typeof apiKey === "string" ? apiKey.trim() : apiKey;
|
|
124
130
|
}
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
isDeprioitized,
|
|
8
8
|
isCancellationStatus,
|
|
9
9
|
isNonRetryableRequestStatus,
|
|
10
|
+
isQuotaExhaustionError,
|
|
10
11
|
} from "../config/errorConfig.js";
|
|
11
12
|
|
|
12
13
|
import { fnv1a } from "@/lib/security/payloadCapture.js";
|
|
@@ -18,6 +19,11 @@ export function getQuotaCooldown(backoffLevel = 0) {
|
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
export function checkFallbackError(status, errorText, backoffLevel = 0) {
|
|
22
|
+
if (isQuotaExhaustionError(errorText)) {
|
|
23
|
+
const newLevel = Math.min(backoffLevel + 1, BACKOFF_CONFIG.maxLevel);
|
|
24
|
+
return { shouldFallback: true, cooldownMs: getQuotaCooldown(newLevel), newBackoffLevel: newLevel };
|
|
25
|
+
}
|
|
26
|
+
|
|
21
27
|
if (isNonRetryableRequestStatus(status) || isCancellationStatus(status)) {
|
|
22
28
|
return { shouldFallback: false, cooldownMs: 0, action: "final", newBackoffLevel: backoffLevel };
|
|
23
29
|
}
|
package/src/sse/services/auth.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { getProviderConnectionById, getProviderConnectionsForRouting, validateApiKey, updateProviderConnection, getSettings, getProxyPools } from "@/lib/localDb";
|
|
2
2
|
import { resolveConnectionProxyConfig, pickProxyPoolId } from "@/lib/network/connectionProxy";
|
|
3
3
|
import {
|
|
4
|
-
formatRetryAfter, checkFallbackError, isModelLockActive, buildModelLockUpdate,
|
|
5
|
-
getEarliestModelLockUntil,
|
|
6
|
-
|
|
7
|
-
isDeprioritized, sortWithDeprioritization, pickCacheAffine,
|
|
8
|
-
DEPRIORITIZE_FIELD,
|
|
9
|
-
} from "../../services/accountFallback.js";
|
|
10
|
-
import { MAX_RATE_LIMIT_COOLDOWN_MS, classifyError, ERROR_TIERS } from "../../config/errorConfig.js";
|
|
4
|
+
formatRetryAfter, checkFallbackError, isModelLockActive, buildModelLockUpdate,
|
|
5
|
+
getEarliestModelLockUntil,
|
|
6
|
+
|
|
7
|
+
isDeprioritized, sortWithDeprioritization, pickCacheAffine,
|
|
8
|
+
DEPRIORITIZE_FIELD,
|
|
9
|
+
} from "../../services/accountFallback.js";
|
|
10
|
+
import { MAX_RATE_LIMIT_COOLDOWN_MS, classifyError, ERROR_TIERS, isQuotaExhaustionError } from "../../config/errorConfig.js";
|
|
11
11
|
import { HTTP_STATUS } from "../../config/runtimeConfig.js";
|
|
12
12
|
import { recordLock, clearLock, clearAllLocksForConnection } from "@/lib/db/repos/accountModelLocksRepo.js";
|
|
13
13
|
import { accountFallbackDecisionsTotal, cacheAffineSelectionsTotal } from "@/observability/metrics.js";
|
|
@@ -397,15 +397,20 @@ export async function markAccountUnavailable(connectionId, status, errorText, pr
|
|
|
397
397
|
}
|
|
398
398
|
if (!shouldFallback) return { shouldFallback: false, cooldownMs: 0 };
|
|
399
399
|
|
|
400
|
-
const reason = typeof errorText === "string" ? errorText.slice(0, 100) : "Provider error";
|
|
401
|
-
const
|
|
400
|
+
const reason = typeof errorText === "string" ? errorText.slice(0, 100) : "Provider error";
|
|
401
|
+
const providerId = resolveProviderId(provider);
|
|
402
|
+
const codeBuddyQuotaExhausted = ["codebuddy-cn", "codebuddy-intl"].includes(providerId)
|
|
403
|
+
&& isQuotaExhaustionError(errorText);
|
|
404
|
+
const lockModel = githubResetAtMs || codeBuddyQuotaExhausted ? null : model;
|
|
405
|
+
const lockUpdate = buildModelLockUpdate(lockModel, cooldownMs);
|
|
402
406
|
|
|
403
407
|
const deprioUpdate = deprioitizeUntilMs ? { [DEPRIORITIZE_FIELD]: deprioitizeUntilMs } : {};
|
|
404
408
|
|
|
405
|
-
await updateProviderConnection(connectionId, {
|
|
406
|
-
...lockUpdate,
|
|
407
|
-
...deprioUpdate,
|
|
408
|
-
|
|
409
|
+
await updateProviderConnection(connectionId, {
|
|
410
|
+
...lockUpdate,
|
|
411
|
+
...deprioUpdate,
|
|
412
|
+
...(codeBuddyQuotaExhausted ? { isActive: false } : {}),
|
|
413
|
+
testStatus: "unavailable",
|
|
409
414
|
lastError: reason,
|
|
410
415
|
errorCode: status,
|
|
411
416
|
lastErrorAt: new Date().toISOString(),
|
|
@@ -413,7 +418,7 @@ export async function markAccountUnavailable(connectionId, status, errorText, pr
|
|
|
413
418
|
});
|
|
414
419
|
invalidateProviderAccountPool(provider);
|
|
415
420
|
|
|
416
|
-
await recordLock(connectionId,
|
|
421
|
+
await recordLock(connectionId, lockModel, {
|
|
417
422
|
tier, reason, expiresAt: new Date(Date.now() + cooldownMs).toISOString(),
|
|
418
423
|
}).catch(() => {});
|
|
419
424
|
|
package/src/utils/error.js
CHANGED
|
@@ -45,7 +45,7 @@ export async function parseUpstreamError(response, executor = null) {
|
|
|
45
45
|
if (parsed && typeof parsed === "object") {
|
|
46
46
|
return {
|
|
47
47
|
statusCode: parsed.status || response.status,
|
|
48
|
-
message:
|
|
48
|
+
message: ["codebuddy-cn", "codebuddy-intl"].includes(executor?.provider) && parsed.message
|
|
49
49
|
? parsed.message
|
|
50
50
|
: DEFAULT_ERROR_MESSAGES[response.status] || "Upstream provider request failed",
|
|
51
51
|
resetsAtMs: parsed.resetsAtMs,
|