feat(proxy-fitness): pool fitness registry + Smart rotation + pool-scoped retry
Pool/IP fitness registry (globalThis-backed, provider::model scopes, 5-min cooldown, provider::* wildcard) fed by pool-scoped failures: freebuff limited-IP / model-locked gates and opencode free per-IP limits. chatCore retries a failed pool via another pool without locking the account; new Smart rotation strategy (per-connection + no-auth providers) skips unfit pools. Proxy Fitness dashboard page: active-block table with provider/IP filters, per-record Clear and provider-scoped Clear All; egress column via pool geo enrichment. Unit tests for the registry.
This commit is contained in:
@@ -5,6 +5,12 @@ import { injectReasoningContent } from "../utils/reasoningContentInjector.js";
|
||||
// Models that use /zen/v1/messages (claude format)
|
||||
const MESSAGES_MODELS = new Set();
|
||||
|
||||
// OpenCode free tier is limited per egress IP — a 429/403 with a limit-ish
|
||||
// body means the POOL's IP is exhausted, not the account. Declare it
|
||||
// pool-scoped so chatCore marks the pool unfit, retries via another pool, and
|
||||
// it shows up (clearable) on the Proxy Fitness page.
|
||||
const IP_LIMIT_BODY = /limit|rate|quota|exhausted|capacity|too many|retry/i;
|
||||
|
||||
export class OpenCodeExecutor extends BaseExecutor {
|
||||
constructor() {
|
||||
super("opencode", PROVIDERS.opencode);
|
||||
@@ -29,4 +35,17 @@ export class OpenCodeExecutor extends BaseExecutor {
|
||||
"Accept": "text/event-stream"
|
||||
};
|
||||
}
|
||||
|
||||
parseError(response, bodyText) {
|
||||
const status = response?.status || 0;
|
||||
const text = String(bodyText || "");
|
||||
if ((status === 429 || status === 403) && IP_LIMIT_BODY.test(text)) {
|
||||
return {
|
||||
status,
|
||||
message: text.slice(0, 300) || `OpenCode free limit (${status})`,
|
||||
poolScoped: { reason: "ip-limit" },
|
||||
};
|
||||
}
|
||||
return null; // fall through to default parsing
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user