feat(opencode): add go deepseek reasoning variants (#4647)

Integrated into release/v3.8.34
This commit is contained in:
Ronald Estacion
2026-06-22 14:23:19 -07:00
committed by GitHub
parent 5ff2cbd5f7
commit d7b7a14a4b
6 changed files with 165 additions and 8 deletions

View File

@@ -13,6 +13,7 @@
"_rebaseline_2026_06_21_qg9_chatcore_service_tier": "QG v2 Fase 9 T5 (#3501) — chatCore.ts 5137->5110 (shrink -27). The two inline Codex service-tier resolvers (resolveEffectiveServiceTier / resolveReportedServiceTier, ~36 LOC) were extracted byte-identically into the new pure leaf open-sse/handlers/chatCore/serviceTier.ts; the handler now keeps a `let effectiveServiceTier` + two thin binding closures that pass provider/credentials?.providerSpecificData to the extracted functions, so every call site stays unchanged. The orphaned getCodexRequestDefaults/normalizeCodexServiceTier/CodexServiceTier imports moved to the leaf. Ratcheted the frozen value down to lock the freed budget. Covered by tests/unit/chatcore-service-tier.test.ts.",
"_rebaseline_2026_06_21_4489_sticky_weighted_limit": "PR #4489 (adivekar-utexas) own growth: src/app/(dashboard)/dashboard/combos/page.tsx 4385->4386 (+1 = the new Sticky Weighted Limit input in the weighted advanced section, a single FieldLabelWithHelp + number <input> block gated to strategy===weighted, analogous to the existing stickyRoundRobinLimit field for round-robin) and open-sse/services/combo.ts ->2761 (sticky-weighted feature: weightedStickyTargets state wiring + the isTargetSelectableForWeighted availability pre-filter + exhaustion-aware sticky clearing/migration in handleComboChat + the round-robin sticky pre-filter in handleRoundRobinCombo + the 4 gemini-review fixes — provider guard, startsWith-separator key match, stepGroups dedup, stale-sticky cleanup). Frozen to the merge-tree measurement (release/v3.8.33 base 2649 + the feature) since CI measures the merged tree, not the v3.8.32-based branch tip. Cohesive routing logic at the existing combo dispatch chokepoints; not a movable block. Structural shrink of combo.ts tracked in #3501. Covered by tests/unit/combo-strategy-fallbacks.test.ts (sticky-weighted batching/fallback-migration/stale-clear/nested-availability + RR sticky-clear) and tests/unit/combo-config.test.ts (stickyWeightedLimit schema).",
"_rebaseline_2026_06_20_reviewprs_mine_r2_filesize": "Reconciliacao file-size pos-lote /review-prs 'apenas minhas' r2: dois frozen cresceram cumulativamente sem bump (cada PR media OK na sua base, mas o crescimento empilhou acima do frozen no tip de merge; o fast-path do release nao roda check:file-size, so release->main). (1) src/shared/constants/pricing.ts 1620->1623 (+3 = linhas de pricing Claude Code (cc) do #4440, sobre o 1620 que o #4447 ja setara para gpt-4.1-mini/nano + o3/o4-mini). (2) open-sse/executors/base.ts 1399->1407 (+8 = handling granular de reasoning_effort para Claude no Copilot do #4443). Ambos dados/wiring coesos nos chokepoints existentes; nao extraiveis. Cobertos por tests/unit (claude-code pricing / base-executor-sanitize-effort + github-claude-reasoning-effort-granular).",
"_rebaseline_2026_06_22_4647_opencode_go_deepseek": "PR #4647 (DevEstacion/opencode-go DeepSeek V4 Pro effort variants) review feedback: open-sse/executors/base.ts 1407->1414 (+7 = supportsMaxEffortForProvider now opt-ins opencode-go+deepseek so the literal 'max' effort survives the post-transformReasoningEffortForProvider pass — without this, max was silently rewritten to xhigh (OmniRoute's internal top tier) and the opencode-go upstream rejected it. The check is scoped to opencode-go deliberately to preserve the OpenRouter-DeepSeek inverse invariant (pi#4055, asserted by base-executor-sanitize-effort test:OpenRouter DeepSeek normalizes max -> xhigh). The +5 explanatory comment is required: a naive maintainer could otherwise broaden the check to all deepseek models and break the OpenRouter contract. Cohesive at the existing supportsMaxEffortForProvider chokepoint, next to the Claude/CC-compatible check; not extractable. Covered by tests/unit/base-executor-sanitize-effort.test.ts (3 new opencode-go deepseek cases).",
"_rebaseline_2026_06_20_4023_web_cookie_noauth_validation": "PR #4023 (oyi77) own growth: src/lib/providers/validation.ts 4450->4518 (+68 = a new validateWebCookieProvider that probes the provider's /models endpoint — 401/403 => AUTH_007 SESSION_EXPIRED, any other status => valid session, empty cookie => invalid, provider-not-in-registry => unsupported — plus a local STANDARD_USER_AGENT const for the probe). Cohesive validator at the validateProviderApiKey dispatch; not extractable. Covered by tests/unit/provider-validation-web-cookie-auth007.test.ts. Heavily curated on merge — the PR's branch was badly stale-based (squash-base-stale), so its tree was DESTRUCTIVE: providers/index.ts deleted live providers openadapter/dit/tokenrouter (added by #4313) and the executor/base.ts edits reverted release fixes (#4037 duckduckgo host, theoldllm gpt5 models, base.ts fetch-start-timeout). Only the purely-additive validation feature was kept (validation.ts validateWebCookieProvider + errorCodes AUTH_007 + the test). Dropped: 5 malformed new registry entries (used non-RegistryEntry fields defaultModel/auth + referenced non-existent executors -> tsc TS2353), the destructive providers/index.ts + executor reverts, the unrelated pr-*.sh automation scripts, and evals/types.ts (belongs to the deferred evals modularization #4422). Also removed the PR's fragile 'Phase 2' executor probe (ran a live upstream chat during validation + classified any 'auth'-containing error as SESSION_EXPIRED) and rewrote the test to install its fetch mock before module load (the original mocked too late and silently hit live chatgpt.com).",
"_rebaseline_2026_06_20_1308_model_lockout_honors_reset": "port from 9router#1308 own growth: open-sse/services/accountFallback.ts 1731->1752 (+21 = the new exported pure helper selectLockoutCooldownMs + its doc comment — picks the parsed upstream reset as the model-lockout exactCooldownMs when it exceeds the base cooldown, e.g. Antigravity \"Resets in 160h\", else preserves the existing 0/base behavior) and open-sse/executors/antigravity.ts 1680->1686 (this PR +1 = parseRetryFromErrorMessage regex `reset` -> `resets?` so plural \"Resets in 160h27m24s\" matches, plus a comment line; frozen set to the SUM 1686 with the concurrent #1944 which adds +5 at the disjoint passthroughFields region of the same file, so either merge order passes — pair-file rule). The combo lockout call sites in combo.ts now pass selectLockoutCooldownMs(cooldownMs, mlSettings) instead of always base/exponential, so an exhausted model honors the real upstream reset instead of being retried within minutes. Both edits are cohesive at the existing lockout/parse chokepoints; the helper is its own pure function (not extractable further). Covered by tests/unit/combo-model-lockout-honors-reset-1308.test.ts.",
"_rebaseline_2026_06_20_1944_antigravity_strip_output_config": "port from 9router#1944: open-sse/executors/antigravity.ts frozen set to the measured cumulative 1687 of two concurrent PRs that touch disjoint regions of this file, so either merge order passes (pair-file rule). #1944 adds +6 at the envelope passthroughFields destructuring (~line 759: drop output_config/output_format — Anthropic/Claude-Code-only fields that Google's Cloud Code envelope rejects with `400 Unknown name \"output_config\"`, which broke every Claude model on Antigravity); #1308 adds +1 at parseRetryFromErrorMessage (~line 889: regex reset->resets?). Base 1680 + 6 + 1 = 1687 (re-measured on the real merge tip — the earlier 1686 estimate was off by one). Both edits are cohesive at their chokepoints; not extractable. Covered by tests/unit/antigravity-strip-output-config-1944.test.ts.",
@@ -108,7 +109,7 @@
"open-sse/translator/request/openai-to-kiro.ts": 807,
"open-sse/config/providerRegistry.ts": 4731,
"open-sse/executors/antigravity.ts": 1696,
"open-sse/executors/base.ts": 1407,
"open-sse/executors/base.ts": 1414,
"open-sse/executors/chatgpt-web.ts": 2870,
"open-sse/executors/claude-web.ts": 1057,
"open-sse/executors/codex.ts": 1449,

View File

@@ -43,6 +43,15 @@ export const opencode_goProvider: RegistryEntry = {
{ id: "qwen3.5-plus", name: "Qwen3.5 Plus", targetFormat: "claude", supportsVision: false },
{ id: "hy3-preview", name: "Hunyuan3 Preview" },
{ id: "deepseek-v4-pro", name: "DeepSeek V4 Pro", supportsReasoning: true },
// OpencodeExecutor rewrites these aliases to the canonical upstream id and injects reasoning_effort.
{ id: "deepseek-v4-pro-low", name: "DeepSeek V4 Pro (low effort)", supportsReasoning: true },
{
id: "deepseek-v4-pro-medium",
name: "DeepSeek V4 Pro (medium effort)",
supportsReasoning: true,
},
{ id: "deepseek-v4-pro-high", name: "DeepSeek V4 Pro (high effort)", supportsReasoning: true },
{ id: "deepseek-v4-pro-max", name: "DeepSeek V4 Pro (max effort)", supportsReasoning: true },
{ id: "deepseek-v4-flash", name: "DeepSeek V4 Flash", supportsReasoning: true },
],
};

View File

@@ -252,15 +252,21 @@ const MISTRAL_NO_REASONING_EFFORT_PATTERN = /devstral/i;
// models, and the `oswe-*` family (Raptor) which still rejects
// reasoning_effort.
// Order matters: the opt-in check must run BEFORE the broad Claude/haiku/oswe strip.
const GITHUB_REASONING_EFFORT_OPT_IN_PATTERN =
/claude[-_.]?(?:opus|sonnet)[-_.]?4[-_.]6/i;
const GITHUB_REASONING_EFFORT_OPT_IN_PATTERN = /claude[-_.]?(?:opus|sonnet)[-_.]?4[-_.]6/i;
const GITHUB_NO_REASONING_EFFORT_PATTERN = /(claude|haiku|oswe)/i;
function supportsMaxEffortForProvider(provider: string, model: string): boolean {
return (
const isClaude =
(provider === PROVIDER_CLAUDE || isClaudeCodeCompatible(provider)) &&
supportsClaudeMaxEffort(model)
);
supportsClaudeMaxEffort(model);
// opencode-go proxies DeepSeek with the native DeepSeek API contract, which
// accepts {high, max} literally. Without this opt-in, max would be
// normalized to xhigh (the OmniRoute-internal top tier) and rejected by the
// upstream. Scoped to opencode-go deliberately: OpenRouter's DeepSeek path
// (pi#4055) is the documented inverse and expects xhigh, not max.
const isOpencodeGoDeepSeek =
provider === "opencode-go" && model.toLowerCase().includes("deepseek");
return isClaude || isOpencodeGoDeepSeek;
}
export function sanitizeReasoningEffortForProvider(

View File

@@ -79,7 +79,9 @@ export class OpencodeExecutor extends BaseExecutor {
// Forward OpenCode request metadata headers from client
const findClientHeader = (name: string) =>
Object.entries(clientHeaders).find(([key]) => key.toLowerCase() === name.toLowerCase())?.[1];
Object.entries(clientHeaders).find(
([key]) => key.toLowerCase() === name.toLowerCase()
)?.[1];
const opencodeHeaderKeys = [
"x-opencode-session",
@@ -145,6 +147,21 @@ export class OpencodeExecutor extends BaseExecutor {
) {
modifiedBody.tools = modifiedBody.tools.slice(0, 128);
}
if (modifiedBody && typeof modifiedBody === "object" && !Array.isArray(modifiedBody)) {
const mb = modifiedBody as Record<string, unknown>;
const m = String(model || "");
const effortLevels = ["low", "medium", "high", "max"] as const;
const matchedLevel = effortLevels.find((level) => m.endsWith(`-${level}`));
if (matchedLevel) {
const base = m.slice(0, -matchedLevel.length - 1);
if (base.toLowerCase() === "deepseek-v4-pro") {
mb.model = "deepseek-v4-pro";
if (mb.reasoning_effort === undefined) {
mb.reasoning_effort = matchedLevel;
}
}
}
}
return modifiedBody;
}
}

View File

@@ -456,7 +456,7 @@ test("sanitizeReasoningEffortForProvider: OpenRouter DeepSeek still preserves xh
const body = {
model: "deepseek/deepseek-v4-pro",
reasoning_effort: "xhigh",
messages: [{ role: "user", content: "hi" }],
messages: [],
};
const result = sanitizeReasoningEffortForProvider(
body,
@@ -467,3 +467,54 @@ test("sanitizeReasoningEffortForProvider: OpenRouter DeepSeek still preserves xh
assert.equal(result, body);
assert.equal((result as any).reasoning_effort, "xhigh");
});
// ── opencode-go DeepSeek V4 Pro effort variants (#4647) ──────────────────────
// opencode-go proxies DeepSeek with the native DeepSeek API contract, which
// accepts {high, max} literally. The OpencodeExecutor's transformRequest sets
// reasoning_effort to the variant suffix (low|medium|high|max), and the
// sanitizer must NOT rewrite `max` → `xhigh` for this provider+model combo.
test("sanitizeReasoningEffortForProvider: opencode-go DeepSeek V4 Pro preserves max", () => {
const body = {
model: "deepseek-v4-pro",
reasoning_effort: "max",
messages: [],
};
const result = sanitizeReasoningEffortForProvider(body, "opencode-go", "deepseek-v4-pro", null);
assert.equal(result, body, "opencode-go DeepSeek max must pass through unchanged");
assert.equal((result as any).reasoning_effort, "max");
});
test("sanitizeReasoningEffortForProvider: opencode-go DeepSeek V4 Pro preserves variant suffix levels", () => {
for (const level of ["low", "medium", "high", "max"]) {
const body = {
model: `deepseek-v4-pro-${level}`,
reasoning_effort: level,
messages: [],
};
const result = sanitizeReasoningEffortForProvider(
body,
"opencode-go",
`deepseek-v4-pro-${level}`,
null
);
assert.equal(
(result as any).reasoning_effort,
level,
`opencode-go deepseek-v4-pro-${level} preserves reasoning_effort=${level}`
);
}
});
test("sanitizeReasoningEffortForProvider: opencode-go with non-DeepSeek model still normalizes max → xhigh", () => {
// The opt-in must be scoped to DeepSeek models on opencode-go only — other
// opencode-go models (e.g. glm/kimi/mimo) follow the default xhigh policy.
const body = {
model: "mimo-v2.5-pro",
reasoning_effort: "max",
messages: [],
};
const result = sanitizeReasoningEffortForProvider(body, "opencode-go", "mimo-v2.5-pro", null);
assert.notEqual(result, body);
assert.equal((result as any).reasoning_effort, "xhigh");
});

View File

@@ -77,6 +77,23 @@ describe("OpencodeExecutor", () => {
assert.equal(model.name, "DeepSeek V4 Flash Free");
assert.equal(model.supportsReasoning, true);
});
it("exposes DeepSeek V4 Pro effort variants on opencode-go only", () => {
const goModels = PROVIDER_MODELS["opencode-go"] || [];
const zenModels = PROVIDER_MODELS["opencode-zen"] || [];
const variants = ["low", "medium", "high", "max"].map((level) => `deepseek-v4-pro-${level}`);
for (const variant of variants) {
const model = goModels.find((m) => m.id === variant);
assert.ok(model, `${variant} should be in opencode-go model list`);
assert.equal(model?.supportsReasoning, true);
assert.equal(
zenModels.some((m) => m.id === variant),
false,
`${variant} should not be exposed on opencode-zen`
);
}
});
it("routes opencode zen default models to chat completions", async () => {
const minimaxResult = await zenExecutor.execute(createInput("minimax-m2.5-free"));
assert.equal(minimaxResult.url, "https://opencode.ai/zen/v1/chat/completions");
@@ -526,6 +543,62 @@ describe("OpencodeExecutor", () => {
);
});
});
describe("DeepSeek V4 Pro reasoning-effort variants", () => {
function baseBody(model) {
return {
model,
stream: false,
messages: [{ role: "user", content: "ok" }],
max_tokens: 16,
};
}
const levels = ["low", "medium", "high", "max"];
for (const level of levels) {
it(`maps deepseek-v4-pro-${level} to base id + reasoning_effort=${level}`, () => {
const variant = `deepseek-v4-pro-${level}`;
const out = goExecutor.transformRequest(variant, baseBody(variant), false, {
apiKey: "test-key",
});
assert.equal(out.model, "deepseek-v4-pro");
assert.equal(out.reasoning_effort, level);
assert.ok(!String(out.model).endsWith(`-${level}`));
});
}
it("preserves explicit reasoning_effort over the variant suffix", () => {
const body = baseBody("deepseek-v4-pro-high") as Record<string, unknown>;
body.reasoning_effort = "max";
const out = goExecutor.transformRequest("deepseek-v4-pro-high", body, false, {
apiKey: "test-key",
});
assert.equal(out.reasoning_effort, "max");
assert.equal(out.model, "deepseek-v4-pro");
});
it("leaves the base id (no suffix) untouched", () => {
const out = goExecutor.transformRequest(
"deepseek-v4-pro",
baseBody("deepseek-v4-pro"),
false,
{ apiKey: "test-key" }
);
assert.equal(out.model, "deepseek-v4-pro");
assert.equal(out.reasoning_effort, undefined);
});
it("does not rewrite unrelated models with matching suffixes", () => {
const out = goExecutor.transformRequest(
"some-other-model-high",
baseBody("some-other-model-high"),
false,
{ apiKey: "test-key" }
);
assert.equal(out.model, "some-other-model-high");
assert.equal(out.reasoning_effort, undefined);
});
});
});
describe("DefaultExecutor", () => {