Some checks failed
Forge CI / smoke-and-build (pull_request) Failing after 36s
`_classify_runtime` was missing from the codebase, and the existing `_PROVIDER_PREFIXES` set lacked several cloud vendor prefixes that users commonly encounter via OpenRouter-style model IDs. Changes: - Add `_CLOUD_MODEL_PREFIXES` frozenset covering all known cloud vendors, including the previously missing: deepseek, cohere, mistral/mistralai, meta-llama, databricks, together, togetherai - Add `_LOCAL_PROVIDER_NAMES` and `_CLOUD_PROVIDER_NAMES` frozensets for provider-name-based classification - Implement `_classify_runtime(model, base_url, provider)` that classifies a runtime as "cloud" or "local" using URL → provider → model-prefix priority - Extend `_PROVIDER_PREFIXES` with the same missing cloud vendors so that `_strip_provider_prefix` also handles cohere:, mistralai:, etc. - Add `TestClassifyRuntime` suite covering all previously-missing prefixes and edge cases Fixes #628 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
169 lines
6.7 KiB
Python
169 lines
6.7 KiB
Python
"""Tests for cron scheduler cloud-provider terminal disabling (#379).
|
|
|
|
When a cron job runs on a cloud inference endpoint (Nous, OpenRouter, etc.),
|
|
the terminal toolset must be disabled because SSH keys don't exist on cloud
|
|
servers. Only local endpoints (localhost, 127.0.0.1, RFC-1918) retain
|
|
terminal access.
|
|
"""
|
|
|
|
import pytest
|
|
from agent.model_metadata import is_local_endpoint, _classify_runtime
|
|
|
|
|
|
class TestIsLocalEndpoint:
|
|
"""Verify is_local_endpoint correctly classifies endpoints."""
|
|
|
|
def test_localhost(self):
|
|
assert is_local_endpoint("http://localhost:11434/v1") is True
|
|
|
|
def test_127_loopback(self):
|
|
assert is_local_endpoint("http://127.0.0.1:8080/v1") is True
|
|
|
|
def test_0_0_0_0(self):
|
|
assert is_local_endpoint("http://0.0.0.0:11434/v1") is True
|
|
|
|
def test_rfc1918_10(self):
|
|
assert is_local_endpoint("http://10.0.0.5:8080/v1") is True
|
|
|
|
def test_rfc1918_192(self):
|
|
assert is_local_endpoint("http://192.168.1.100:11434/v1") is True
|
|
|
|
def test_rfc1918_172(self):
|
|
assert is_local_endpoint("http://172.16.0.1:8080/v1") is True
|
|
|
|
def test_cloud_openrouter(self):
|
|
assert is_local_endpoint("https://openrouter.ai/api/v1") is False
|
|
|
|
def test_cloud_nous(self):
|
|
assert is_local_endpoint("https://inference-api.nousresearch.com/v1") is False
|
|
|
|
def test_cloud_anthropic(self):
|
|
assert is_local_endpoint("https://api.anthropic.com") is False
|
|
|
|
def test_empty_url(self):
|
|
assert is_local_endpoint("") is False
|
|
|
|
def test_none_url(self):
|
|
assert is_local_endpoint(None) is False
|
|
|
|
|
|
class TestCronDisabledToolsetsLogic:
|
|
"""Verify the disabled_toolsets logic matches scheduler expectations."""
|
|
|
|
def _build_disabled(self, base_url, job=None):
|
|
"""Mirror the scheduler's disabled_toolsets logic."""
|
|
from agent.model_metadata import is_local_endpoint
|
|
cron_disabled = ["cronjob", "messaging", "clarify"]
|
|
if not is_local_endpoint(base_url):
|
|
cron_disabled.append("terminal")
|
|
return cron_disabled
|
|
|
|
def test_local_keeps_terminal(self):
|
|
disabled = self._build_disabled("http://localhost:11434/v1")
|
|
assert "terminal" not in disabled
|
|
assert "cronjob" in disabled
|
|
|
|
def test_cloud_disables_terminal(self):
|
|
disabled = self._build_disabled("https://openrouter.ai/api/v1")
|
|
assert "terminal" in disabled
|
|
assert "cronjob" in disabled
|
|
|
|
def test_empty_url_disables_terminal(self):
|
|
disabled = self._build_disabled("")
|
|
assert "terminal" in disabled
|
|
|
|
|
|
class TestClassifyRuntime:
|
|
"""Verify _classify_runtime correctly classifies runtimes as cloud or local.
|
|
|
|
Covers the bug fixed in #628: missing cloud model prefixes for deepseek,
|
|
cohere, mistral, meta-llama, databricks, and together.
|
|
"""
|
|
|
|
# ── URL-based classification ──────────────────────────────────────────
|
|
|
|
def test_localhost_url_is_local(self):
|
|
assert _classify_runtime(base_url="http://localhost:11434/v1") == "local"
|
|
|
|
def test_127_loopback_is_local(self):
|
|
assert _classify_runtime(base_url="http://127.0.0.1:8080/v1") == "local"
|
|
|
|
def test_rfc1918_is_local(self):
|
|
assert _classify_runtime(base_url="http://192.168.1.10:11434/v1") == "local"
|
|
|
|
def test_openrouter_url_is_cloud(self):
|
|
assert _classify_runtime(base_url="https://openrouter.ai/api/v1") == "cloud"
|
|
|
|
def test_anthropic_url_is_cloud(self):
|
|
assert _classify_runtime(base_url="https://api.anthropic.com") == "cloud"
|
|
|
|
def test_deepseek_url_is_cloud(self):
|
|
assert _classify_runtime(base_url="https://api.deepseek.com/v1") == "cloud"
|
|
|
|
# ── Provider-name classification ──────────────────────────────────────
|
|
|
|
def test_ollama_provider_is_local(self):
|
|
assert _classify_runtime(provider="ollama") == "local"
|
|
|
|
def test_custom_provider_is_local(self):
|
|
assert _classify_runtime(provider="custom") == "local"
|
|
|
|
def test_openrouter_provider_is_cloud(self):
|
|
assert _classify_runtime(provider="openrouter") == "cloud"
|
|
|
|
def test_nous_provider_is_cloud(self):
|
|
assert _classify_runtime(provider="nous") == "cloud"
|
|
|
|
def test_anthropic_provider_is_cloud(self):
|
|
assert _classify_runtime(provider="anthropic") == "cloud"
|
|
|
|
# ── Previously-missing cloud prefixes (issue #628) ────────────────────
|
|
|
|
def test_deepseek_model_prefix_is_cloud(self):
|
|
assert _classify_runtime(model="deepseek/deepseek-v2") == "cloud"
|
|
|
|
def test_cohere_model_prefix_is_cloud(self):
|
|
assert _classify_runtime(model="cohere/command-r-plus") == "cloud"
|
|
|
|
def test_mistralai_model_prefix_is_cloud(self):
|
|
assert _classify_runtime(model="mistralai/mistral-large-2407") == "cloud"
|
|
|
|
def test_meta_llama_model_prefix_is_cloud(self):
|
|
assert _classify_runtime(model="meta-llama/llama-3.1-70b-instruct") == "cloud"
|
|
|
|
def test_databricks_model_prefix_is_cloud(self):
|
|
assert _classify_runtime(model="databricks/dbrx-instruct") == "cloud"
|
|
|
|
def test_together_model_prefix_is_cloud(self):
|
|
assert _classify_runtime(model="together/together-api-model") == "cloud"
|
|
|
|
# ── Providers that were already detected before #628 ─────────────────
|
|
|
|
def test_openai_model_prefix_is_cloud(self):
|
|
assert _classify_runtime(model="openai/gpt-4.1") == "cloud"
|
|
|
|
def test_anthropic_model_prefix_is_cloud(self):
|
|
assert _classify_runtime(model="anthropic/claude-opus-4.6") == "cloud"
|
|
|
|
def test_google_model_prefix_is_cloud(self):
|
|
assert _classify_runtime(model="google/gemini-3-pro") == "cloud"
|
|
|
|
def test_minimax_model_prefix_is_cloud(self):
|
|
assert _classify_runtime(model="minimax/minimax-m2.7") == "cloud"
|
|
|
|
# ── Fallback / edge cases ────────────────────────────────────────────
|
|
|
|
def test_no_args_defaults_to_cloud(self):
|
|
assert _classify_runtime() == "cloud"
|
|
|
|
def test_empty_strings_default_to_cloud(self):
|
|
assert _classify_runtime(model="", base_url="", provider="") == "cloud"
|
|
|
|
def test_url_takes_priority_over_provider(self):
|
|
# Explicit local URL wins even if provider looks like cloud
|
|
assert _classify_runtime(model="openai/gpt-4", base_url="http://localhost:11434/v1", provider="openai") == "local"
|
|
|
|
def test_bare_model_name_without_slash_defaults_to_cloud(self):
|
|
# No slash → can't infer vendor → cloud (safe default)
|
|
assert _classify_runtime(model="gpt-4o") == "cloud"
|