fix(opencode): fix free tier 403 error and improve China region handling

- Force stream:true and cloak decoy tools (bash, read) for OpenCode free tier
- Support connection testing for opencode in testUtils
- Expand error message slice limits in auth and ping to preserve workspace link
- Add concise China region link chip in provider detail page

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
decolua
2026-09-18 16:57:06 +07:00
parent 52917a6d49
commit 93837af09f
6 changed files with 140 additions and 3 deletions

View File

@@ -1783,7 +1783,33 @@ export default function ProviderDetailPage() {
})()}
</div>
{!!modelsTestError && (
<p className="text-xs text-red-500 mb-3 break-words">{modelsTestError}</p>
<div className="mb-3">
<p className="text-xs text-red-500 break-words">{modelsTestError}</p>
{/RegionError|hosted in China|regionNotAllowed/i.test(modelsTestError) && (() => {
const str = typeof modelsTestError === "string" ? modelsTestError : JSON.stringify(modelsTestError);
const linkMatch = str.match(/https:\/\/opencode\.ai\/workspace\/[^\s"')]+/);
const wrkMatch = str.match(/wrk_[0-9A-Za-z]+/);
const targetUrl = linkMatch
? (linkMatch[0].endsWith("/go") ? linkMatch[0] : `${linkMatch[0]}/go`)
: wrkMatch
? `https://opencode.ai/workspace/${wrkMatch[0]}/go`
: "https://opencode.ai";
return (
<div className="mt-1.5">
<a
href={targetUrl}
target="_blank"
rel="noreferrer"
className="inline-flex items-center gap-1 rounded-md bg-amber-500/10 px-2 py-0.5 text-xs font-medium text-amber-600 hover:bg-amber-500/20 dark:text-amber-400 transition-colors"
>
<span>Allow China-hosted models</span>
<span className="material-symbols-outlined text-[13px]">open_in_new</span>
</a>
</div>
);
})()}
</div>
)}
{providerId === "zed" && !!liveModelsError && (
<p className="text-xs text-red-500 mb-3 break-words">{liveModelsError}</p>

View File

@@ -160,7 +160,7 @@ export async function pingModelByKind(model, kind, baseUrl = `http://127.0.0.1:$
if (!res.ok) {
const detail = parsed?.error?.message || parsed?.msg || parsed?.message || parsed?.error || rawText;
return { ok: false, latencyMs, error: `HTTP ${res.status}${detail ? `: ${String(detail).slice(0, 240)}` : ""}`, status: res.status };
return { ok: false, latencyMs, error: `HTTP ${res.status}${detail ? `: ${String(detail).slice(0, 500)}` : ""}`, status: res.status };
}
const providerStatus = parsed?.status;

View File

@@ -751,6 +751,12 @@ async function testApiKeyConnection(connection, effectiveProxy = null) {
const valid = !!(data && data.user);
return { valid, error: valid ? null : "Session expired — re-paste cookie" };
}
case "opencode": {
const res = await fetchWithConnectionProxy("https://opencode.ai/zen/v1/models", {
headers: { Authorization: "Bearer public", "User-Agent": "opencode/1.18.31" },
}, effectiveProxy);
return { valid: res.ok, error: res.ok ? null : "OpenCode free tier unavailable" };
}
case "opencode-go": {
const res = await fetchWithConnectionProxy("https://opencode.ai/zen/go/v1/chat/completions", {
method: "POST",

View File

@@ -263,7 +263,7 @@ export async function markAccountUnavailable(connectionId, status, errorText, pr
}
if (!shouldFallback) return { shouldFallback: false, cooldownMs: 0 };
const reason = typeof errorText === "string" ? errorText.slice(0, 100) : "Provider error";
const reason = typeof errorText === "string" ? errorText.slice(0, 200) : "Provider error";
const lockUpdate = buildModelLockUpdate(githubResetAtMs ? null : model, cooldownMs);
await updateProviderConnection(connectionId, {