Files
9router/open-sse/executors/opencode.js
T

52 lines
1.6 KiB
JavaScript
Raw Normal View History

2026-04-06 17:32:44 +07:00
import { BaseExecutor } from "./base.js";
import { PROVIDERS } from "../config/providers.js";
2026-05-29 17:48:01 +07:00
import { injectReasoningContent } from "../utils/reasoningContentInjector.js";
2026-04-06 17:32:44 +07:00
// Models that use /zen/v1/messages (claude format)
2026-05-29 17:48:01 +07:00
const MESSAGES_MODELS = new Set();
2026-04-06 17:32:44 +07:00
// 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;
2026-04-06 17:32:44 +07:00
export class OpenCodeExecutor extends BaseExecutor {
constructor() {
super("opencode", PROVIDERS.opencode);
}
2026-05-29 17:48:01 +07:00
transformRequest(model, body) {
return injectReasoningContent({ provider: this.provider, model, body });
}
2026-04-06 17:32:44 +07:00
buildUrl(model) {
const base = this.config.baseUrl;
2026-04-06 17:32:44 +07:00
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
}
2026-04-06 17:32:44 +07:00
}