diff --git a/routes/cookbook_helpers.py b/routes/cookbook_helpers.py index bb819f3f8..cc2daebdb 100644 --- a/routes/cookbook_helpers.py +++ b/routes/cookbook_helpers.py @@ -505,6 +505,8 @@ def _cached_model_scan_script(model_dirs: list[str] | None = None, add_hf_cache: " if u.startswith('KB'): return int(n * 1024)", " return int(n)", "def scan_ollama():", + " if any(m.get('is_ollama') for m in models): return", + " if os.name == 'nt' and not os.environ.get('ODYSSEUS_ALLOW_OLLAMA_CLI_SCAN'): return", " if not shutil.which('ollama'): return", " try:", " p = subprocess.run(['ollama', 'list'], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True, timeout=6)", @@ -535,8 +537,8 @@ def _cached_model_scan_script(model_dirs: list[str] | None = None, add_hf_cache: " models.append({'repo_id':name,'size_bytes':size_bytes,'nb_files':1,'has_incomplete':False,'path':'ollama','backend':'ollama','is_ollama':True})", " return", "for _hf_cache in hf_cache_paths(): scan_hf(_hf_cache)", - "scan_ollama()", "scan_ollama_api()", + "scan_ollama()", ] for model_dir in model_dirs or []: lines.append(f"scan_dir(os.path.expanduser({model_dir!r}))") diff --git a/tests/test_cookbook_helpers.py b/tests/test_cookbook_helpers.py index b83cbdf93..72b72a079 100644 --- a/tests/test_cookbook_helpers.py +++ b/tests/test_cookbook_helpers.py @@ -786,6 +786,50 @@ def test_cached_model_scan_reports_plain_dir_gguf(tmp_path): assert ggufs[3]["quant"] == "BF16" +def test_cached_model_scan_uses_ollama_api_before_cli_and_windows_opt_in(): + script = _cached_model_scan_script() + + assert "scan_ollama_api()\nscan_ollama()" in script + assert "if any(m.get('is_ollama') for m in models): return" in script + assert "os.name == 'nt'" in script + assert "ODYSSEUS_ALLOW_OLLAMA_CLI_SCAN" in script + + +@pytest.mark.skipif(os.name != "nt", reason="Windows Ollama CLI startup guard") +def test_cached_model_scan_does_not_launch_ollama_cli_on_windows(tmp_path): + """Official Ollama for Windows can auto-start the tray/server on `ollama list`. + The read-only cache scanner must not invoke that CLI unless explicitly opted in. + """ + marker = tmp_path / "ollama-called.txt" + fake_ollama = tmp_path / "ollama.cmd" + fake_ollama.write_text( + "@echo off\r\n" + f'echo called>"{marker}"\r\n' + "echo NAME ID SIZE MODIFIED\r\n" + "echo local-model:latest abc 1 GB now\r\n", + encoding="utf-8", + ) + + empty_home = tmp_path / "home" + empty_home.mkdir() + scan_py = tmp_path / "scan_cache.py" + scan_py.write_text(_cached_model_scan_script(), encoding="utf-8") + env = dict(os.environ) + env["PATH"] = str(tmp_path) + os.pathsep + env.get("PATH", "") + env["HOME"] = str(empty_home) + env.pop("ODYSSEUS_ALLOW_OLLAMA_CLI_SCAN", None) + proc = subprocess.run( + [sys.executable, str(scan_py)], + check=True, + capture_output=True, + text=True, + env=env, + ) + + assert marker.exists() is False + assert all(m.get("backend") != "ollama" for m in json.loads(proc.stdout)) + + def test_cached_model_scan_uses_huggingface_cache_env(tmp_path): """Docker recreates can leave the persisted HF cache outside HOME. The Serve scanner should honor the cache env path instead of only ~/.cache.