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.
52 lines
1.6 KiB
JavaScript
52 lines
1.6 KiB
JavaScript
import { BaseExecutor } from "./base.js";
|
|
import { PROVIDERS } from "../config/providers.js";
|
|
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);
|
|
}
|
|
|
|
transformRequest(model, body) {
|
|
return injectReasoningContent({ provider: this.provider, model, body });
|
|
}
|
|
|
|
buildUrl(model) {
|
|
const base = this.config.baseUrl;
|
|
return MESSAGES_MODELS.has(model)
|
|
? `${base}/zen/v1/messages`
|
|
: `${base}/zen/v1/chat/completions`;
|
|
}
|
|
|
|
buildHeaders() {
|
|
return {
|
|
"Content-Type": "application/json",
|
|
"Authorization": "Bearer public",
|
|
"x-opencode-client": "desktop",
|
|
"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
|
|
}
|
|
}
|