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..565ea4b9a 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"] @@ -276,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", ( @@ -469,7 +494,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 +1544,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 "