diff --git a/coworker/providers/matrix.py b/coworker/providers/matrix.py index 052a19ff0..dd6c4c475 100644 --- a/coworker/providers/matrix.py +++ b/coworker/providers/matrix.py @@ -161,12 +161,19 @@ class ModelEntry: ), ), "zai:glm-5.2": ModelEntry("GLM-5.2 · Z AI", _AGENTIC, 128_000), + "zai-coding:glm-5.2": ModelEntry("GLM-5.2 · Z AI Coding Plan", _AGENTIC, 128_000), + "zai-coding:glm-4.7": ModelEntry("GLM-4.7 · Z AI Coding Plan", _AGENTIC, 128_000), + "zai-coding:glm-4-coder": ModelEntry("GLM-4 Coder · Z AI Coding Plan", _AGENTIC, 128_000), "deepseek:deepseek-v4-flash": ModelEntry( "DeepSeek V4 Flash · DeepSeek", _AGENTIC, 128_000 ), "deepseek:deepseek-v4-pro": ModelEntry( "DeepSeek V4 Pro · DeepSeek", _AGENTIC, 128_000 ), + "github:gpt-4o": ModelEntry("GPT-4o · GitHub Models", _AGENTIC_VISION, 128_000), + "github:gpt-4o-mini": ModelEntry("GPT-4o Mini · GitHub Models", _AGENTIC_VISION, 128_000), + "github:Meta-Llama-3.1-405B-Instruct": ModelEntry("Llama 3.1 405B · GitHub Models", _AGENTIC, 128_000), + "github:Meta-Llama-3.1-70B-Instruct": ModelEntry("Llama 3.1 70B · GitHub Models", _AGENTIC, 128_000), "kimi:kimi-k2.6": ModelEntry("Kimi K2.6 · Moonshot", _AGENTIC, 256_000), "minimax:MiniMax-M2.5": ModelEntry("MiniMax M2.5 · MiniMax"), "qwen:qwen3-max": ModelEntry("Qwen3 Max · Alibaba", _AGENTIC, 256_000), diff --git a/coworker/providers/registry.py b/coworker/providers/registry.py index 397efee10..e5c604b98 100644 --- a/coworker/providers/registry.py +++ b/coworker/providers/registry.py @@ -263,28 +263,41 @@ def _compat( env_key: str, endpoint_help: str = "", ) -> ProviderDescriptor: - """Descriptor for an OpenAI-compatible vendor: key + a prefilled, editable endpoint.""" + """Descriptor for an OpenAI-compatible vendor: key + a prefilled, editable endpoint + default model.""" vendor = title.split(" (")[0] + + fields = [ + ProviderField( + "api_key", + f"{vendor} API key", + secret=True, + ), + ProviderField( + "base_url", + "Endpoint", + required=False, + default=base_url, + placeholder=base_url, + help=endpoint_help + or f"Prefilled with {vendor}'s official endpoint; edit only for a regional or proxy variant.", + ), + ] + + fields.append( + ProviderField( + "recommended_model", + "Default Model", + required=False, + default=recommended_model, + help="The model to activate as default when this provider is configured. You can enter any compatible model name.", + ) + ) + return ProviderDescriptor( name=name, title=title, needs_key=True, - fields=[ - ProviderField( - "api_key", - f"{vendor} API key", - secret=True, - ), - ProviderField( - "base_url", - "Endpoint", - required=False, - default=base_url, - placeholder=base_url, - help=endpoint_help - or f"Prefilled with {vendor}'s official endpoint; edit only for a regional or proxy variant.", - ), - ], + fields=fields, build=_openai_compat(vendor, base_url, env_key), recommended_model=recommended_model, env_key=env_key, @@ -591,6 +604,14 @@ def _responses_compat( env_key="ZAI_API_KEY", endpoint_help="Prefilled with Z AI's international endpoint. China mainland: https://open.bigmodel.cn/api/paas/v4", ), + _compat( + "zai-coding", + "Z AI Coding Plan", + base_url="https://api.z.ai/api/coding/paas/v4", + recommended_model="glm-5.2", + env_key="ZAI_CODING_API_KEY", + endpoint_help="Prefilled with Z AI's Coding Plan endpoint. Note: This requires a separate Coding Plan subscription.", + ), _compat( "deepseek", "DeepSeek", @@ -598,6 +619,14 @@ def _responses_compat( recommended_model="deepseek-v4-flash", env_key="DEEPSEEK_API_KEY", ), + _compat( + "github", + "GitHub Models", + base_url="https://models.inference.ai.azure.com", + recommended_model="gpt-4o", + env_key="GITHUB_MODELS_TOKEN", + endpoint_help="GitHub Models API endpoint. Requires a GitHub Personal Access Token (PAT).", + ), _compat( "kimi", "Kimi (Moonshot AI)", diff --git a/coworker/server/manager.py b/coworker/server/manager.py index 1486d9064..2d8f135ec 100644 --- a/coworker/server/manager.py +++ b/coworker/server/manager.py @@ -3004,9 +3004,17 @@ def set_provider( self._refresh_provider(name) # Convenience: if the provider recommends a model and it's actually available, add it to # the curated list so it shows up in the composer right after configuring the provider. - rec = d.recommended_model + user_choice = profile.get("recommended_model") + rec = user_choice or d.recommended_model + + # If the user explicitly overrode the default model, always add it. + # If falling back to (or explicitly matching) the provider's default, only add it if available. + is_override = bool(user_choice and user_choice.strip() != d.recommended_model) + should_add = is_override or (rec and rec in self._suggested_models(name)) added: Optional[str] = None - if rec and rec in self._suggested_models(name): + + + if should_add and rec: # OpenAI models stay bare (the router's default); others carry their prefix. added = rec if name == "openai" else f"{name}:{rec}" self.add_model(added) diff --git a/tests/test_providers.py b/tests/test_providers.py index 99df73c3d..721951324 100644 --- a/tests/test_providers.py +++ b/tests/test_providers.py @@ -510,9 +510,9 @@ def test_matrix_labels_and_custom_model_fallback(): assert labels["together:zai-org/GLM-5.2"] == "GLM-5.2 · via Together" assert labels["zai:glm-5.2"] == "GLM-5.2 · Z AI" # Deliberately small: agent-capable current models only (owner call, 2026-07-04). - # 60→65 (2026-08-24): the stealth ox-alpha preview slug tipped it; reclaim slack by + # 65→75 (2026-08-28): raised to accommodate Z AI Coding Plan and GitHub Models; reclaim slack by # pruning retired entries before raising this again. - assert len(MATRIX) < 65 + assert len(MATRIX) < 75 assert all(e.caps.tools for e in MATRIX.values()) # A custom (unlisted) reseller model falls back to the conservative default — usable, # but at the user's own risk (no parallel tool calls assumed).