feat(opencode-zen): add OpenCode Zen (PAYG) provider with free-tier fingerprint, alias ocz

Multi-endpoint provider on https://opencode.ai/zen/v1 (openai / claude /
openai-responses transports mirroring opencode-go) with 71 models across
paid + free tiers. Free-tier fingerprint: opencode/1.18.x UA spoof,
ses_-session header, built-in tool quartet, forced stream. Usage endpoint
/zen/v1/usage wired into the dashboard.
This commit is contained in:
Alexander Radchenko
2026-09-19 10:01:51 +07:00
parent 73e021b8a0
commit 49185137b8
10 changed files with 716 additions and 1 deletions

View File

@@ -15,6 +15,7 @@ import { getGrokCliUsage } from "./usage/grok-cli.js";
import { getKimiUsage } from "./usage/kimi.js";
import { getDeepseekUsage } from "./usage/deepseek.js";
import { getOpenCodeGoUsage } from "./usage/opencode-go.js";
import { getOpenCodeZenUsage } from "./usage/opencode-zen.js";
import { getGroqUsage } from "./usage/groq.js";
import { getZedUsage } from "./usage/zed.js";
import { getXiaomiMimoUsage } from "./usage/xiaomi-mimo.js";
@@ -59,6 +60,7 @@ const USAGE_HANDLERS = {
"grok-cli": (c) => getGrokCliUsage(c.accessToken, c.providerSpecificData, c.proxyOptions),
kimi: (c) => getKimiUsage(c.accessToken, c.apiKey, c.proxyOptions, c.providerSpecificData),
"opencode-go": (c) => getOpenCodeGoUsage(c.apiKey, c.proxyOptions),
"opencode-zen": (c) => getOpenCodeZenUsage(c.apiKey, c.proxyOptions),
deepseek: (c) => getDeepseekUsage(c.apiKey, c.proxyOptions),
groq: (c) => getGroqUsage(c.apiKey, c.proxyOptions),
zed: (c) => getZedUsage(c.accessToken, c.providerSpecificData, c.proxyOptions),

View File

@@ -0,0 +1,107 @@
/**
* OpenCode Zen usage — GET https://opencode.ai/zen/v1/usage
* Auth: Bearer <apiKey>
*/
import { proxyAwareFetch } from "../../utils/proxyFetch.js";
import { parseResetTime, toFiniteNumber, U } from "./shared.js";
const USAGE_URL = U("opencode-zen").url;
const QUOTA_NAMES = {
rolling: "Rolling",
weekly: "Weekly",
monthly: "Monthly",
};
function parsePercent(value) {
if (typeof value === "number" && Number.isFinite(value)) return value;
if (typeof value === "string" && value.trim()) {
const parsed = Number(value);
if (Number.isFinite(parsed)) return parsed;
}
return null;
}
export async function getOpenCodeZenUsage(apiKey = null, proxyOptions = null) {
if (!apiKey || typeof apiKey !== "string" || !apiKey.trim()) {
return {
message: "OpenCode Zen API key not available. Add a key to view usage.",
};
}
try {
const response = await proxyAwareFetch(
USAGE_URL,
{
method: "GET",
headers: {
Authorization: `Bearer ${apiKey.trim()}`,
Accept: "application/json",
},
},
proxyOptions,
);
if (response.status === 401) {
return {
plan: "OpenCode Zen",
message: "OpenCode Zen authentication failed. Check the API key.",
};
}
if (response.status === 403) {
const error = await response.json().catch(() => null);
const subscriptionRequired = error?.error?.type === "EntitlementError";
return {
plan: "OpenCode Zen",
message: subscriptionRequired
? "OpenCode Zen billing required for this API key."
: "OpenCode Zen access forbidden for this API key.",
};
}
if (!response.ok) {
return {
plan: "OpenCode Zen",
message: `OpenCode Zen usage API error (${response.status}).`,
};
}
const data = await response.json().catch(() => null);
if (!data?.usage || typeof data.usage !== "object") {
return {
plan: "OpenCode Zen",
message: "OpenCode Zen usage response did not contain quota data.",
};
}
const quotas = {};
for (const [period, name] of Object.entries(QUOTA_NAMES)) {
const quota = data.usage[period];
if (!quota || typeof quota !== "object") continue;
const percent = parsePercent(quota.percent);
if (percent === null) continue;
const used = Math.max(0, Math.min(100, toFiniteNumber(percent, 0)));
quotas[name] = {
used,
total: 100,
remaining: 100 - used,
remainingPercentage: 100 - used,
resetAt: parseResetTime(quota.resetsAt),
unlimited: false,
};
}
if (Object.keys(quotas).length === 0) {
return {
plan: "OpenCode Zen",
message: "OpenCode Zen usage response did not contain valid quota data.",
};
}
return { plan: "OpenCode Zen", quotas };
} catch (error) {
return { message: `OpenCode Zen error: ${error.message}` };
}
}