From c2df2cc69fc759c0882686f1e83cb3276b3a4e4e Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 3 Aug 2026 10:33:29 +0000 Subject: [PATCH 1/2] ci(opencode-review): add 5 free Zen models across distinct providers as deep failover Expand the anonymous OpenCode Zen free reviewer pool from 7 to 12 models by adding five zero-cost, tool_call-capable models from five distinct upstream providers: hy3-free (Tencent Hy), minimax-m3-free (MiniMax), glm-5-free (Zhipu GLM), kimi-k2.5-free (Moonshot Kimi), and qwen3.6-plus-free (Alibaba Qwen). Each is verified tool_call=true in the models.dev opencode registry and carries the reasoning high-effort options/variants structure the contract requires. They are appended after the active free models in OPENCODE_MODEL_CANDIDATES, so free-first ordering holds and they are only reached once the active free pool is exhausted -- the exact scenario (free-pool exhaustion blocking all PR reviews) this hardens against. Distinct providers mean distinct outage domains, so a bad day for one upstream no longer drains the whole free tier before the paid fallback. The per-run dynamic budget cap plus the fatal-provider-error fast-kill already bound worst-case failover latency, so extra candidates add resilience without multiplying bad-day cost. Also fix a pre-existing contract drift: the coverage-evidence assertion still pinned the download-artifact v7.0.0 SHA after dependabot bumped the workflow to v8.0.1 (#596); align the expected SHA with the workflow. Contract updated in lockstep (tests/test_opencode_agent_contract.py): the public-conditional candidate string, the candidates prefix, the full candidate_pairs ordering, and the set(free_models) equality. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01AzUnTqFnQqhRbaopvDdag7 --- .../workflows/opencode-review-dispatch.yml | 87 ++++++++++++++++++- tests/test_opencode_agent_contract.py | 31 ++++++- 2 files changed, 114 insertions(+), 4 deletions(-) diff --git a/.github/workflows/opencode-review-dispatch.yml b/.github/workflows/opencode-review-dispatch.yml index 47205030c..d826ce67a 100644 --- a/.github/workflows/opencode-review-dispatch.yml +++ b/.github/workflows/opencode-review-dispatch.yml @@ -3761,6 +3761,91 @@ jobs: "context": 200000, "output": 32000 } + }, + "hy3-free": { + "name": "Hy3 Free", + "tool_call": true, + "reasoning": true, + "options": { + "reasoningEffort": "high" + }, + "variants": { + "high": { + "reasoningEffort": "high" + } + }, + "limit": { + "context": 190000, + "output": 64000 + } + }, + "minimax-m3-free": { + "name": "MiniMax-M3 Free", + "tool_call": true, + "reasoning": true, + "options": { + "reasoningEffort": "high" + }, + "variants": { + "high": { + "reasoningEffort": "high" + } + }, + "limit": { + "context": 200000, + "output": 32000 + } + }, + "glm-5-free": { + "name": "GLM-5 Free", + "tool_call": true, + "reasoning": true, + "options": { + "reasoningEffort": "high" + }, + "variants": { + "high": { + "reasoningEffort": "high" + } + }, + "limit": { + "context": 204800, + "output": 131072 + } + }, + "kimi-k2.5-free": { + "name": "Kimi K2.5 Free", + "tool_call": true, + "reasoning": true, + "options": { + "reasoningEffort": "high" + }, + "variants": { + "high": { + "reasoningEffort": "high" + } + }, + "limit": { + "context": 262144, + "output": 262144 + } + }, + "qwen3.6-plus-free": { + "name": "Qwen3.6 Plus Free", + "tool_call": true, + "reasoning": true, + "options": { + "reasoningEffort": "high" + }, + "variants": { + "high": { + "reasoningEffort": "high" + } + }, + "limit": { + "context": 262144, + "output": 65536 + } } } }, @@ -4222,7 +4307,7 @@ jobs: # cost-efficient tier, cheaper than the legacy gpt-5 it replaced # ($1/$6 vs $1.25/$10 per 1M tokens) so the org OpenAI budget # stretches further between top-ups. - OPENCODE_MODEL_CANDIDATES: "${{ needs.validate-pr-metadata.outputs.is_private == 'false' && 'nvidia-nim/nvidia/llama-3.3-nemotron-super-49b-v1.5 nvidia-nim/nvidia/llama-3.1-nemotron-ultra-253b-v1 nvidia-nim/nvidia/nemotron-3-super-120b-a12b nvidia-nim/nvidia/nemotron-3-ultra-550b-a55b nvidia-nim/meta/llama-3.3-70b-instruct nvidia-nim/deepseek-ai/deepseek-v4-pro nvidia-nim/mistralai/codestral-22b-instruct-v0.1 opencode-free/nemotron-3-ultra-free opencode-free/deepseek-v4-flash-free opencode-free/north-mini-code-free opencode-free/laguna-s-2.1-free opencode-free/ling-3.0-flash-free opencode-free/big-pickle opencode-free/mimo-v2.5-free ' || '' }}opencode/gpt-5.6-terra github-models/deepseek/deepseek-v3-0324 openai/gpt-5.6-luna openrouter/deepseek/deepseek-v3.2 openrouter/qwen/qwen3-coder github-models/openai/gpt-4.1 github-models/openai/gpt-5 github-models/openai/gpt-5-chat github-models/openai/o3 github-models/deepseek/deepseek-r1-0528 github-models/deepseek/deepseek-r1" + OPENCODE_MODEL_CANDIDATES: "${{ needs.validate-pr-metadata.outputs.is_private == 'false' && 'nvidia-nim/nvidia/llama-3.3-nemotron-super-49b-v1.5 nvidia-nim/nvidia/llama-3.1-nemotron-ultra-253b-v1 nvidia-nim/nvidia/nemotron-3-super-120b-a12b nvidia-nim/nvidia/nemotron-3-ultra-550b-a55b nvidia-nim/meta/llama-3.3-70b-instruct nvidia-nim/deepseek-ai/deepseek-v4-pro nvidia-nim/mistralai/codestral-22b-instruct-v0.1 opencode-free/nemotron-3-ultra-free opencode-free/deepseek-v4-flash-free opencode-free/north-mini-code-free opencode-free/laguna-s-2.1-free opencode-free/ling-3.0-flash-free opencode-free/big-pickle opencode-free/mimo-v2.5-free opencode-free/hy3-free opencode-free/minimax-m3-free opencode-free/glm-5-free opencode-free/kimi-k2.5-free opencode-free/qwen3.6-plus-free ' || '' }}opencode/gpt-5.6-terra github-models/deepseek/deepseek-v3-0324 openai/gpt-5.6-luna openrouter/deepseek/deepseek-v3.2 openrouter/qwen/qwen3-coder github-models/openai/gpt-4.1 github-models/openai/gpt-5 github-models/openai/gpt-5-chat github-models/openai/o3 github-models/deepseek/deepseek-r1-0528 github-models/deepseek/deepseek-r1" # One attempt per model, then fall through to the next model. Retrying # the SAME model 5x let a rate-limited/hung leader consume the whole # step, so the pool never reached a healthy fallback model. diff --git a/tests/test_opencode_agent_contract.py b/tests/test_opencode_agent_contract.py index 963aaac8e..fbd0c3390 100644 --- a/tests/test_opencode_agent_contract.py +++ b/tests/test_opencode_agent_contract.py @@ -105,7 +105,12 @@ def test_opencode_model_pool_sets_high_effort_for_capable_candidates(): "opencode-free/laguna-s-2.1-free " "opencode-free/ling-3.0-flash-free " "opencode-free/big-pickle " - "opencode-free/mimo-v2.5-free ' || '' }}" + "opencode-free/mimo-v2.5-free " + "opencode-free/hy3-free " + "opencode-free/minimax-m3-free " + "opencode-free/glm-5-free " + "opencode-free/kimi-k2.5-free " + "opencode-free/qwen3.6-plus-free ' || '' }}" ) candidates_text = candidates_match.group(1) assert candidates_text.startswith(conditional_public_candidate) @@ -124,6 +129,11 @@ def test_opencode_model_pool_sets_high_effort_for_capable_candidates(): "opencode-free/ling-3.0-flash-free", "opencode-free/big-pickle", "opencode-free/mimo-v2.5-free", + "opencode-free/hy3-free", + "opencode-free/minimax-m3-free", + "opencode-free/glm-5-free", + "opencode-free/kimi-k2.5-free", + "opencode-free/qwen3.6-plus-free", *candidates_text.removeprefix(conditional_public_candidate).split(), ] candidate_pairs = [candidate.split("/", 1) for candidate in candidates] @@ -162,6 +172,11 @@ def test_opencode_model_pool_sets_high_effort_for_capable_candidates(): ["opencode-free", "ling-3.0-flash-free"], ["opencode-free", "big-pickle"], ["opencode-free", "mimo-v2.5-free"], + ["opencode-free", "hy3-free"], + ["opencode-free", "minimax-m3-free"], + ["opencode-free", "glm-5-free"], + ["opencode-free", "kimi-k2.5-free"], + ["opencode-free", "qwen3.6-plus-free"], ["opencode", "gpt-5.6-terra"], ["github-models", "deepseek/deepseek-v3-0324"], ["openai", "gpt-5.6-luna"], @@ -240,6 +255,11 @@ def test_opencode_model_pool_sets_high_effort_for_capable_candidates(): "ling-3.0-flash-free", "big-pickle", "mimo-v2.5-free", + "hy3-free", + "minimax-m3-free", + "glm-5-free", + "kimi-k2.5-free", + "qwen3.6-plus-free", } assert set(paid_zen_models) == {"gpt-5.6-terra"} terra_model = paid_zen_models["gpt-5.6-terra"] @@ -469,7 +489,7 @@ def test_opencode_target_coverage_materializes_only_after_authorized_dispatch(): assert "id-token: write" not in coverage_job assert "Report coverage source materialization failure" in coverage_job assert ( - "actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131" + "actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c" in coverage_job ) @@ -1519,7 +1539,12 @@ def test_workflow_provisions_sandbox_tool_and_reviewer_agent(): "opencode-free/laguna-s-2.1-free " "opencode-free/ling-3.0-flash-free " "opencode-free/big-pickle " - "opencode-free/mimo-v2.5-free ' || ''" + "opencode-free/mimo-v2.5-free " + "opencode-free/hy3-free " + "opencode-free/minimax-m3-free " + "opencode-free/glm-5-free " + "opencode-free/kimi-k2.5-free " + "opencode-free/qwen3.6-plus-free ' || ''" ) in workflow assert ( "opencode/gpt-5.6-terra " From f4efa05e49eb26e78fc0af8156c91f15774b6ae9 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 3 Aug 2026 10:38:32 +0000 Subject: [PATCH 2/2] test(opencode-review): assert tool_call support for the entire free pool The contract previously verified tool_call=true for only three hand-picked free models (nemotron-3-ultra-free, deepseek-v4-flash-free, north-mini-code-free). The reviewer drives CodeGraph/web-search tooling, so any free-pool candidate that lacks tool_call cannot emit a structured review and would burn its failover slot before yielding -- exactly the silent regression a future pool edit could introduce. Extend the existing free-model loop to assert tool_call is True for every candidate, securing coverage of the invariant the whole free-first failover chain depends on. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01AzUnTqFnQqhRbaopvDdag7 --- tests/test_opencode_agent_contract.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/test_opencode_agent_contract.py b/tests/test_opencode_agent_contract.py index fbd0c3390..565ea4b9a 100644 --- a/tests/test_opencode_agent_contract.py +++ b/tests/test_opencode_agent_contract.py @@ -296,6 +296,11 @@ def test_opencode_model_pool_sets_high_effort_for_capable_candidates(): "output": 32000, } for model_name, model_config in free_models.items(): + # Every free-pool candidate must declare tool_call support: the reviewer + # drives CodeGraph/web-search tooling, so a non-tool_call model in the + # pool cannot produce a structured review and would burn its failover + # slot before yielding. Guard the whole pool, not just a hand-picked few. + assert model_config["tool_call"] is True, model_name if model_config.get("reasoning") is True: assert model_config["options"]["reasoningEffort"] == "high", model_name assert model_config["variants"]["high"]["reasoningEffort"] == "high", (