mirrorneuron-cli 1.2.8__tar.gz → 1.2.15__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mirrorneuron_cli-1.2.8/mirrorneuron_cli.egg-info → mirrorneuron_cli-1.2.15}/PKG-INFO +1 -1
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15/mirrorneuron_cli.egg-info}/PKG-INFO +1 -1
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/mn_cli/libs/model_cmds.py +24 -3
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/mn_cli/libs/skill_dependencies.py +12 -1
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/mn_cli/libs/sys_cmds.py +11 -5
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/mn_cli/server_cmds.py +129 -19
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/tests/test_model_cmds.py +35 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/tests/test_run_cmds.py +24 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/tests/test_run_helpers.py +3 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/tests/test_server_cmds.py +92 -1
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/.github/workflows/ci.yml +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/.github/workflows/release.yml +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/.gitignore +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/.python-version +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/AGENTS.md +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/LICENSE +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/README.md +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/RELEASE.md +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/mirrorneuron_cli.egg-info/SOURCES.txt +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/mirrorneuron_cli.egg-info/dependency_links.txt +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/mirrorneuron_cli.egg-info/entry_points.txt +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/mirrorneuron_cli.egg-info/requires.txt +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/mirrorneuron_cli.egg-info/top_level.txt +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/mn_cli/__init__.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/mn_cli/banner.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/mn_cli/config.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/mn_cli/error_handler.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/mn_cli/libs/__init__.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/mn_cli/libs/artifacts.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/mn_cli/libs/backup_cmds.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/mn_cli/libs/blueprint_cmds.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/mn_cli/libs/blueprint_models.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/mn_cli/libs/blueprint_observability.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/mn_cli/libs/blueprint_repository.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/mn_cli/libs/blueprint_resources.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/mn_cli/libs/bundles.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/mn_cli/libs/deployment_cmds.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/mn_cli/libs/event_relay.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/mn_cli/libs/job_cmds.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/mn_cli/libs/resource_cmds.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/mn_cli/libs/run_cmds.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/mn_cli/libs/run_logs.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/mn_cli/libs/run_manifest.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/mn_cli/libs/runtime_health.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/mn_cli/libs/schedule_cmds.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/mn_cli/libs/service_cmds.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/mn_cli/libs/skill_runtime.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/mn_cli/libs/ui.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/mn_cli/libs/workflow_progress.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/mn_cli/libs/workflow_validation.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/mn_cli/logging_config.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/mn_cli/main.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/mn_cli/runtime_mode.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/mn_cli/runtime_state.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/mn_cli/schemas/workflow_manifest.schema.json +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/mn_cli/sdk_path.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/mn_cli/shared.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/mn_cli/terminal.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/mn_cli/update_cmds.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/pyproject.toml +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/scripts/check-release-artifacts.sh +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/scripts/make-release-zip.sh +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/scripts/validate-version-tag.sh +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/setup.cfg +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/tests/conftest.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/tests/test_backup_cmds.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/tests/test_blueprint_cmds.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/tests/test_blueprint_repository.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/tests/test_blueprint_resources.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/tests/test_deployment_cmds.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/tests/test_docker_network_integration.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/tests/test_job_cmds.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/tests/test_main.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/tests/test_repo_hygiene.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/tests/test_resource_cmds.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/tests/test_runtime_health.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/tests/test_runtime_mode.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/tests/test_runtime_state.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/tests/test_schedule_cmds.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/tests/test_service_cmds.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/tests/test_shared.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/tests/test_sys_cmds.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/tests/test_terminal.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/tests/test_ui.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/tests/test_update_cmds.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/tests/test_workflow_validation.py +0 -0
- {mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/uv.lock +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
+
import os
|
|
4
5
|
import subprocess
|
|
5
6
|
from typing import Annotated, Any, Optional
|
|
6
7
|
|
|
@@ -250,16 +251,21 @@ def install_model_entry(
|
|
|
250
251
|
target = docker_model_name(entry)
|
|
251
252
|
if _docker_model_cli_available():
|
|
252
253
|
_ensure_runner(compatibility.backend, compatibility.accelerator)
|
|
253
|
-
|
|
254
|
+
pull_result = _pull_model(target)
|
|
254
255
|
run_command = ["model", "run", "--detach"]
|
|
255
256
|
resolved_context = context_size or entry.get("context_size")
|
|
256
257
|
if resolved_context and _docker_model_run_supports_context_size():
|
|
257
258
|
run_command.extend(["--context-size", str(resolved_context)])
|
|
258
259
|
run_command.append(target)
|
|
259
260
|
_docker(run_command, timeout=300)
|
|
260
|
-
return {
|
|
261
|
+
return {
|
|
262
|
+
"entry": entry,
|
|
263
|
+
"docker_model": target,
|
|
264
|
+
"compatibility": payload,
|
|
265
|
+
**pull_result,
|
|
266
|
+
}
|
|
261
267
|
|
|
262
|
-
api_result = dmr_api_pull_model(target, timeout=
|
|
268
|
+
api_result = dmr_api_pull_model(target, timeout=_model_pull_timeout_seconds())
|
|
263
269
|
return {
|
|
264
270
|
"entry": entry,
|
|
265
271
|
"docker_model": target,
|
|
@@ -269,6 +275,21 @@ def install_model_entry(
|
|
|
269
275
|
}
|
|
270
276
|
|
|
271
277
|
|
|
278
|
+
def _model_pull_timeout_seconds() -> float:
|
|
279
|
+
try:
|
|
280
|
+
return max(float(os.getenv("MN_DOCKER_MODEL_PULL_TIMEOUT_SECONDS", "3600")), 1.0)
|
|
281
|
+
except ValueError:
|
|
282
|
+
return 3600.0
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def _pull_model(target: str) -> dict[str, Any]:
|
|
286
|
+
if _endpoint_responds():
|
|
287
|
+
api_result = dmr_api_pull_model(target, timeout=_model_pull_timeout_seconds())
|
|
288
|
+
return {"transport": "docker_model_runner_api", "api": api_result}
|
|
289
|
+
_docker_model_pull(target)
|
|
290
|
+
return {"transport": "docker_cli"}
|
|
291
|
+
|
|
292
|
+
|
|
272
293
|
def _docker_model_pull(target: str, *, attempts: int = 2) -> None:
|
|
273
294
|
last_error: RuntimeError | None = None
|
|
274
295
|
for attempt in range(1, attempts + 1):
|
|
@@ -79,8 +79,19 @@ def gar_requirement_lines(manifest: dict[str, Any] | None) -> list[str]:
|
|
|
79
79
|
]
|
|
80
80
|
|
|
81
81
|
|
|
82
|
+
def gar_requirements_file_lines(manifest: dict[str, Any] | None) -> list[str]:
|
|
83
|
+
requirements = pinned_skill_dependency_requirements(manifest)
|
|
84
|
+
if not requirements:
|
|
85
|
+
return []
|
|
86
|
+
return [
|
|
87
|
+
f"--index-url {GAR_PIP_INDEX_URL}",
|
|
88
|
+
f"--extra-index-url {PYPI_PIP_INDEX_URL}",
|
|
89
|
+
*requirements,
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
|
|
82
93
|
def gar_requirements_text(manifest: dict[str, Any] | None) -> str:
|
|
83
|
-
lines =
|
|
94
|
+
lines = gar_requirements_file_lines(manifest)
|
|
84
95
|
return "\n".join(lines).strip() + ("\n" if lines else "")
|
|
85
96
|
|
|
86
97
|
|
|
@@ -310,15 +310,21 @@ def ensure_context_engine(
|
|
|
310
310
|
)
|
|
311
311
|
summary = ensure_context_engine_runtime(force=force)
|
|
312
312
|
progress.update(task, description="[green]Context memory is ready.")
|
|
313
|
+
details = [
|
|
314
|
+
("Service", summary["service"]),
|
|
315
|
+
("Model", summary["model"]),
|
|
316
|
+
("Model status", summary.get("model_status", "unknown")),
|
|
317
|
+
]
|
|
318
|
+
if summary.get("membrane_dir"):
|
|
319
|
+
details.append(("Membrane", summary["membrane_dir"]))
|
|
320
|
+
if summary.get("engine_image"):
|
|
321
|
+
details.append(("Engine image", summary["engine_image"]))
|
|
322
|
+
|
|
313
323
|
print_success_confirmation(
|
|
314
324
|
console,
|
|
315
325
|
"Context engine",
|
|
316
326
|
status=summary["status"],
|
|
317
|
-
details=
|
|
318
|
-
("Service", summary["service"]),
|
|
319
|
-
("Model", summary["model"]),
|
|
320
|
-
("Membrane", summary["membrane_dir"]),
|
|
321
|
-
],
|
|
327
|
+
details=details,
|
|
322
328
|
next_steps="mn runtime health",
|
|
323
329
|
)
|
|
324
330
|
except Exception as exc:
|
|
@@ -9,6 +9,7 @@ import shutil
|
|
|
9
9
|
import socket
|
|
10
10
|
import subprocess
|
|
11
11
|
import sys
|
|
12
|
+
import tempfile
|
|
12
13
|
import time
|
|
13
14
|
import urllib.request
|
|
14
15
|
from datetime import datetime, timezone
|
|
@@ -116,6 +117,7 @@ DEFAULT_MEMBRANE_ENGINE_IMAGE_REPOSITORY = (
|
|
|
116
117
|
"us-central1-docker.pkg.dev/mirrorneuron-public-packages/"
|
|
117
118
|
"mirrorneuron-runtime/membrane-context-engine"
|
|
118
119
|
)
|
|
120
|
+
PUBLIC_GAR_PROJECT_PATH = "/mirrorneuron-public-packages/"
|
|
119
121
|
CONTEXT_ENGINE_SERVICE = "membrane-context-engine"
|
|
120
122
|
CONTEXT_ENGINE_CONTAINER = "mirror-neuron-context-engine"
|
|
121
123
|
CONTEXT_ENGINE_MODEL_CONTAINER = "mirror-neuron-context-engine-model"
|
|
@@ -2027,32 +2029,45 @@ def ensure_context_engine_runtime(*, force: bool = False) -> dict[str, str]:
|
|
|
2027
2029
|
_remove_non_mirror_neuron_container(CONTEXT_ENGINE_CONTAINER)
|
|
2028
2030
|
_remove_non_mirror_neuron_container(CONTEXT_ENGINE_MODEL_CONTAINER)
|
|
2029
2031
|
_ensure_docker_model_runner()
|
|
2032
|
+
model_already_installed = _docker_model_inspect_ok(model)
|
|
2033
|
+
model_status = "already_installed" if model_already_installed else "compose_pending"
|
|
2030
2034
|
|
|
2031
2035
|
already_running = _docker_container_running(CONTEXT_ENGINE_CONTAINER)
|
|
2032
|
-
if force or not already_running:
|
|
2036
|
+
if force or not already_running or not model_already_installed:
|
|
2037
|
+
compose_env = env
|
|
2038
|
+
anonymous_docker_config: Path | None = None
|
|
2033
2039
|
if use_engine_image:
|
|
2040
|
+
compose_env, anonymous_docker_config = _anonymous_public_gar_docker_env(env, engine_image)
|
|
2041
|
+
compose_process_env = _compose_subprocess_env(compose_env)
|
|
2042
|
+
try:
|
|
2043
|
+
if use_engine_image:
|
|
2044
|
+
subprocess.run(
|
|
2045
|
+
runtime_compose_cmd("pull", CONTEXT_ENGINE_SERVICE),
|
|
2046
|
+
check=True,
|
|
2047
|
+
stdout=subprocess.DEVNULL,
|
|
2048
|
+
env=compose_process_env,
|
|
2049
|
+
)
|
|
2050
|
+
up_args = ("up", "-d", "--no-build", CONTEXT_ENGINE_SERVICE)
|
|
2051
|
+
else:
|
|
2052
|
+
subprocess.run(
|
|
2053
|
+
runtime_compose_cmd("build", CONTEXT_ENGINE_SERVICE),
|
|
2054
|
+
check=True,
|
|
2055
|
+
stdout=subprocess.DEVNULL,
|
|
2056
|
+
env=compose_process_env,
|
|
2057
|
+
)
|
|
2058
|
+
up_args = ("up", "-d", CONTEXT_ENGINE_SERVICE)
|
|
2034
2059
|
subprocess.run(
|
|
2035
|
-
runtime_compose_cmd(
|
|
2036
|
-
check=True,
|
|
2037
|
-
stdout=subprocess.DEVNULL,
|
|
2038
|
-
env=env,
|
|
2039
|
-
)
|
|
2040
|
-
up_args = ("up", "-d", "--no-build", CONTEXT_ENGINE_SERVICE)
|
|
2041
|
-
else:
|
|
2042
|
-
subprocess.run(
|
|
2043
|
-
runtime_compose_cmd("build", CONTEXT_ENGINE_SERVICE),
|
|
2060
|
+
runtime_compose_cmd(*up_args),
|
|
2044
2061
|
check=True,
|
|
2045
2062
|
stdout=subprocess.DEVNULL,
|
|
2046
|
-
env=
|
|
2063
|
+
env=compose_process_env,
|
|
2047
2064
|
)
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
check=True,
|
|
2052
|
-
stdout=subprocess.DEVNULL,
|
|
2053
|
-
env=env,
|
|
2054
|
-
)
|
|
2065
|
+
finally:
|
|
2066
|
+
if anonymous_docker_config is not None:
|
|
2067
|
+
shutil.rmtree(anonymous_docker_config, ignore_errors=True)
|
|
2055
2068
|
status = "restarted" if already_running and force else "started"
|
|
2069
|
+
if not model_already_installed:
|
|
2070
|
+
model_status = "installed" if _docker_model_inspect_ok(model) else "compose_managed"
|
|
2056
2071
|
else:
|
|
2057
2072
|
status = "already_running"
|
|
2058
2073
|
|
|
@@ -2061,10 +2076,106 @@ def ensure_context_engine_runtime(*, force: bool = False) -> dict[str, str]:
|
|
|
2061
2076
|
"service": CONTEXT_ENGINE_SERVICE,
|
|
2062
2077
|
"container": CONTEXT_ENGINE_CONTAINER,
|
|
2063
2078
|
"model": model,
|
|
2079
|
+
"model_status": model_status,
|
|
2064
2080
|
"compose_profiles": profiles,
|
|
2065
2081
|
**({"engine_image": engine_image} if use_engine_image else {"membrane_dir": str(source_dir)}),
|
|
2066
2082
|
}
|
|
2067
2083
|
|
|
2084
|
+
def _compose_subprocess_env(env: dict[str, str]) -> dict[str, str]:
|
|
2085
|
+
merged = dict(os.environ)
|
|
2086
|
+
merged.update({str(key): str(value) for key, value in env.items()})
|
|
2087
|
+
return merged
|
|
2088
|
+
|
|
2089
|
+
def _docker_model_inspect_ok(model: str) -> bool:
|
|
2090
|
+
if not model:
|
|
2091
|
+
return False
|
|
2092
|
+
return _docker_command_ok(["docker", "model", "inspect", model])
|
|
2093
|
+
|
|
2094
|
+
def _anonymous_public_gar_docker_env(env: dict[str, str], image: str) -> tuple[dict[str, str], Path | None]:
|
|
2095
|
+
if not _is_public_gar_image(image):
|
|
2096
|
+
return env, None
|
|
2097
|
+
|
|
2098
|
+
registry_host = _docker_image_registry_host(image)
|
|
2099
|
+
config_dir = Path(tempfile.mkdtemp(prefix="mn-public-gar-docker-config-"))
|
|
2100
|
+
source_config = _docker_config_dir(env) / "config.json"
|
|
2101
|
+
config: dict[str, Any] = {}
|
|
2102
|
+
if source_config.exists():
|
|
2103
|
+
try:
|
|
2104
|
+
loaded = json.loads(source_config.read_text(encoding="utf-8"))
|
|
2105
|
+
if isinstance(loaded, dict):
|
|
2106
|
+
config = loaded
|
|
2107
|
+
except Exception:
|
|
2108
|
+
logger.debug("Could not read Docker config for public GAR pull; using anonymous config", exc_info=True)
|
|
2109
|
+
|
|
2110
|
+
config = _docker_config_without_public_gar_credentials(config, registry_host)
|
|
2111
|
+
(config_dir / "config.json").write_text(json.dumps(config, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
|
2112
|
+
_expose_docker_cli_plugins(source_config.parent, config_dir)
|
|
2113
|
+
_expose_docker_contexts(source_config.parent, config_dir)
|
|
2114
|
+
|
|
2115
|
+
next_env = dict(env)
|
|
2116
|
+
next_env["DOCKER_CONFIG"] = str(config_dir)
|
|
2117
|
+
return next_env, config_dir
|
|
2118
|
+
|
|
2119
|
+
def _expose_docker_cli_plugins(source_config_dir: Path, target_config_dir: Path) -> None:
|
|
2120
|
+
source_plugins = source_config_dir / "cli-plugins"
|
|
2121
|
+
if not source_plugins.is_dir():
|
|
2122
|
+
return
|
|
2123
|
+
target_plugins = target_config_dir / "cli-plugins"
|
|
2124
|
+
try:
|
|
2125
|
+
os.symlink(source_plugins, target_plugins, target_is_directory=True)
|
|
2126
|
+
except Exception:
|
|
2127
|
+
try:
|
|
2128
|
+
shutil.copytree(source_plugins, target_plugins, symlinks=True)
|
|
2129
|
+
except Exception:
|
|
2130
|
+
logger.debug("Could not expose Docker CLI plugins for anonymous GAR pull", exc_info=True)
|
|
2131
|
+
|
|
2132
|
+
def _expose_docker_contexts(source_config_dir: Path, target_config_dir: Path) -> None:
|
|
2133
|
+
source_contexts = source_config_dir / "contexts"
|
|
2134
|
+
if not source_contexts.is_dir():
|
|
2135
|
+
return
|
|
2136
|
+
target_contexts = target_config_dir / "contexts"
|
|
2137
|
+
try:
|
|
2138
|
+
os.symlink(source_contexts, target_contexts, target_is_directory=True)
|
|
2139
|
+
except Exception:
|
|
2140
|
+
try:
|
|
2141
|
+
shutil.copytree(source_contexts, target_contexts, symlinks=True)
|
|
2142
|
+
except Exception:
|
|
2143
|
+
logger.debug("Could not expose Docker contexts for anonymous GAR pull", exc_info=True)
|
|
2144
|
+
|
|
2145
|
+
def _is_public_gar_image(image: str) -> bool:
|
|
2146
|
+
text = str(image or "").strip()
|
|
2147
|
+
return PUBLIC_GAR_PROJECT_PATH in text and _docker_image_registry_host(text).endswith(".pkg.dev")
|
|
2148
|
+
|
|
2149
|
+
def _docker_image_registry_host(image: str) -> str:
|
|
2150
|
+
return str(image or "").split("/", 1)[0].strip().lower()
|
|
2151
|
+
|
|
2152
|
+
def _docker_config_dir(env: dict[str, str]) -> Path:
|
|
2153
|
+
configured = str(env.get("DOCKER_CONFIG") or os.environ.get("DOCKER_CONFIG") or "").strip()
|
|
2154
|
+
return Path(configured).expanduser() if configured else Path.home() / ".docker"
|
|
2155
|
+
|
|
2156
|
+
def _docker_config_without_public_gar_credentials(config: dict[str, Any], registry_host: str) -> dict[str, Any]:
|
|
2157
|
+
sanitized = dict(config)
|
|
2158
|
+
sanitized.pop("credsStore", None)
|
|
2159
|
+
for key in ("credHelpers", "auths"):
|
|
2160
|
+
value = sanitized.get(key)
|
|
2161
|
+
if not isinstance(value, dict):
|
|
2162
|
+
continue
|
|
2163
|
+
filtered = {
|
|
2164
|
+
str(registry): details
|
|
2165
|
+
for registry, details in value.items()
|
|
2166
|
+
if not _docker_registry_matches_public_gar(str(registry), registry_host)
|
|
2167
|
+
}
|
|
2168
|
+
if filtered:
|
|
2169
|
+
sanitized[key] = filtered
|
|
2170
|
+
else:
|
|
2171
|
+
sanitized.pop(key, None)
|
|
2172
|
+
return sanitized
|
|
2173
|
+
|
|
2174
|
+
def _docker_registry_matches_public_gar(registry: str, registry_host: str) -> bool:
|
|
2175
|
+
normalized = registry.strip().lower()
|
|
2176
|
+
normalized = normalized.removeprefix("https://").removeprefix("http://").split("/", 1)[0]
|
|
2177
|
+
return normalized == registry_host or normalized.endswith(".pkg.dev")
|
|
2178
|
+
|
|
2068
2179
|
def _compose_profiles_with(value: object, required_profile: str) -> str:
|
|
2069
2180
|
profiles: list[str] = []
|
|
2070
2181
|
seen: set[str] = set()
|
|
@@ -3725,7 +3836,6 @@ def _start_server(
|
|
|
3725
3836
|
if token:
|
|
3726
3837
|
_write_network_token(network_token)
|
|
3727
3838
|
env = _runtime_base_env(compose_runtime)
|
|
3728
|
-
persisted_join_profile_before_network = bool(compose_runtime and not ip and _persisted_join_profile(env))
|
|
3729
3839
|
mode_override = docker_network_mode or os.getenv("MN_DOCKER_NETWORK_MODE", "").strip()
|
|
3730
3840
|
requested_docker_mode = _docker_network_mode(
|
|
3731
3841
|
mode_override or None,
|
|
@@ -15,6 +15,7 @@ runner = CliRunner()
|
|
|
15
15
|
@pytest.fixture(autouse=True)
|
|
16
16
|
def isolate_model_ownership(monkeypatch, tmp_path):
|
|
17
17
|
monkeypatch.setenv("MN_MODEL_OWNERSHIP_PATH", str(tmp_path / "ownership.json"))
|
|
18
|
+
monkeypatch.setattr("mn_cli.libs.model_cmds._endpoint_responds", lambda: False)
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
def _completed(command, returncode=0, stdout="", stderr=""):
|
|
@@ -271,6 +272,40 @@ def test_model_install_falls_back_to_dmr_rest_when_cli_plugin_missing(mocker):
|
|
|
271
272
|
assert {"from": "ai/gemma4:E2B"} in payloads
|
|
272
273
|
|
|
273
274
|
|
|
275
|
+
def test_model_install_prefers_dmr_rest_pull_when_runner_api_reachable(mocker, monkeypatch):
|
|
276
|
+
calls = []
|
|
277
|
+
requests = []
|
|
278
|
+
monkeypatch.setattr("mn_cli.libs.model_cmds._endpoint_responds", lambda: True)
|
|
279
|
+
|
|
280
|
+
def fake_run(command, **kwargs):
|
|
281
|
+
calls.append(command)
|
|
282
|
+
if command[:4] == ["docker", "model", "status", "--json"]:
|
|
283
|
+
return _completed(command, stdout=json.dumps({"running": True, "backends": {"llama.cpp": "Running"}}))
|
|
284
|
+
if command[:4] == ["docker", "model", "run", "--help"]:
|
|
285
|
+
return _completed(command, stdout="Options:\n")
|
|
286
|
+
return _completed(command)
|
|
287
|
+
|
|
288
|
+
def fake_urlopen(request, timeout=0):
|
|
289
|
+
requests.append((request.full_url, request.get_method(), request.data, timeout))
|
|
290
|
+
return FakeResponse("{}")
|
|
291
|
+
|
|
292
|
+
mocker.patch("subprocess.run", side_effect=fake_run)
|
|
293
|
+
mocker.patch("urllib.request.urlopen", side_effect=fake_urlopen)
|
|
294
|
+
mocker.patch(
|
|
295
|
+
"mn_sdk.model_runtime.detect_host_hardware",
|
|
296
|
+
return_value=HostHardwareProfile("darwin", "arm64", total_memory_gb=16, unified_memory_gb=16, has_apple_silicon=True),
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
result = runner.invoke(app, ["model", "install", "gemma4:e2b"])
|
|
300
|
+
|
|
301
|
+
assert result.exit_code == 0
|
|
302
|
+
assert ["docker", "model", "pull", "ai/gemma4:E2B"] not in calls
|
|
303
|
+
assert ["docker", "model", "run", "--detach", "ai/gemma4:E2B"] in calls
|
|
304
|
+
assert any(url.endswith("/models/create") and method == "POST" for url, method, _data, _timeout in requests)
|
|
305
|
+
payloads = [json.loads(data.decode("utf-8")) for _url, _method, data, _timeout in requests if data]
|
|
306
|
+
assert {"from": "ai/gemma4:E2B"} in payloads
|
|
307
|
+
|
|
308
|
+
|
|
274
309
|
def test_model_list_reads_dmr_rest_tags_when_cli_plugin_missing(mocker):
|
|
275
310
|
def fake_run(command, **kwargs):
|
|
276
311
|
if command[:3] == ["docker", "model", "--help"] or command[:3] == ["docker", "model", "list"]:
|
|
@@ -789,6 +789,27 @@ def test_runtime_ensure_context_engine_explains_first_launch(mocker):
|
|
|
789
789
|
assert "First launch may download the context model" in stdout_text
|
|
790
790
|
assert "Context engine" in result.stdout
|
|
791
791
|
assert "hf.co/example/context-model" in result.stdout
|
|
792
|
+
assert "/tmp/Membrane" in result.stdout
|
|
793
|
+
mock_ensure.assert_called_once_with(force=False)
|
|
794
|
+
|
|
795
|
+
|
|
796
|
+
def test_runtime_ensure_context_engine_reports_release_image(mocker):
|
|
797
|
+
mock_ensure = mocker.patch(
|
|
798
|
+
"mn_cli.libs.sys_cmds.ensure_context_engine_runtime",
|
|
799
|
+
return_value={
|
|
800
|
+
"status": "started",
|
|
801
|
+
"service": "membrane-context-engine",
|
|
802
|
+
"model": "hf.co/example/context-model",
|
|
803
|
+
"engine_image": "us-central1-docker.pkg.dev/example/runtime/membrane-context-engine:v1.2.14",
|
|
804
|
+
},
|
|
805
|
+
)
|
|
806
|
+
|
|
807
|
+
result = runner.invoke(app, ["runtime", "ensure-context-engine"])
|
|
808
|
+
|
|
809
|
+
assert result.exit_code == 0
|
|
810
|
+
assert "Context engine" in result.stdout
|
|
811
|
+
assert "hf.co/example/context-model" in result.stdout
|
|
812
|
+
assert "membrane-context-engine:v1.2.14" in result.stdout
|
|
792
813
|
mock_ensure.assert_called_once_with(force=False)
|
|
793
814
|
|
|
794
815
|
|
|
@@ -983,6 +1004,9 @@ def test_openshell_skill_dependency_context_injects_pinned_gar_install(tmp_path)
|
|
|
983
1004
|
assert context != sandbox_dir
|
|
984
1005
|
assert "mirrorneuron-websocket-stream-skill==1.2.7" in requirements
|
|
985
1006
|
assert "https://us-central1-python.pkg.dev/mirrorneuron-public-packages/agent-skills/simple/" in requirements
|
|
1007
|
+
assert "--index-url\n" not in requirements
|
|
1008
|
+
assert "--index-url https://us-central1-python.pkg.dev/mirrorneuron-public-packages/agent-skills/simple/" in requirements
|
|
1009
|
+
assert "--extra-index-url https://pypi.org/simple" in requirements
|
|
986
1010
|
assert "COPY __mn_skill_dependencies/requirements.txt" in dockerfile
|
|
987
1011
|
assert "pip install --break-system-packages --no-cache-dir -r /tmp/mn-skill-dependencies/requirements.txt" in dockerfile
|
|
988
1012
|
|
|
@@ -384,6 +384,9 @@ def test_stage_skill_dependency_payloads_injects_pinned_gar_requirements_for_doc
|
|
|
384
384
|
dockerfile = payloads["worker/docker_worker/Dockerfile"].decode()
|
|
385
385
|
assert "mirrorneuron-rag-skill==1.2.7" in requirements
|
|
386
386
|
assert "https://us-central1-python.pkg.dev/mirrorneuron-public-packages/agent-skills/simple/" in requirements
|
|
387
|
+
assert "--index-url\n" not in requirements
|
|
388
|
+
assert "--index-url https://us-central1-python.pkg.dev/mirrorneuron-public-packages/agent-skills/simple/" in requirements
|
|
389
|
+
assert "--extra-index-url https://pypi.org/simple" in requirements
|
|
387
390
|
assert "COPY __mn_skill_dependencies/requirements.txt" in dockerfile
|
|
388
391
|
assert "pip install --break-system-packages --no-cache-dir -r /tmp/mn-skill-dependencies/requirements.txt" in dockerfile
|
|
389
392
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import pytest
|
|
3
|
+
import shutil
|
|
3
4
|
import subprocess
|
|
4
5
|
import sys
|
|
5
6
|
from io import StringIO
|
|
@@ -324,6 +325,9 @@ def test_deploy_compose_passes_host_shared_storage_to_core():
|
|
|
324
325
|
|
|
325
326
|
assert "MN_HOST_SHARED_STORAGE_ROOT:" in compose_text
|
|
326
327
|
assert "${MN_HOST_SHARED_STORAGE_ROOT:-${MN_SHARED_STORAGE_ROOT:-" in compose_text
|
|
328
|
+
assert "membrane-context-engine:" in compose_text
|
|
329
|
+
assert "context-engine-model:" in compose_text
|
|
330
|
+
assert "MN_CONTEXT_MODEL_ENDPOINT" in compose_text
|
|
327
331
|
|
|
328
332
|
def test_runtime_blueprint_env_updates_ignores_legacy_host_mn_dir(tmp_path):
|
|
329
333
|
legacy_home = tmp_path / "legacy-mn-home"
|
|
@@ -574,6 +578,7 @@ def test_ensure_context_engine_runtime_persists_profile_and_starts_compose(mocke
|
|
|
574
578
|
server_cmds.RUNTIME_COMPOSE_FILE.write_text("services: {}\n", encoding="utf-8")
|
|
575
579
|
mocker.patch("mn_cli.server_cmds._ensure_context_engine_source", return_value=membrane_dir)
|
|
576
580
|
mocker.patch("mn_cli.server_cmds._ensure_docker_model_runner")
|
|
581
|
+
inspect_model = mocker.patch("mn_cli.server_cmds._docker_model_inspect_ok", side_effect=[False, True])
|
|
577
582
|
mocker.patch("mn_cli.server_cmds._remove_non_mirror_neuron_container")
|
|
578
583
|
mocker.patch("mn_cli.server_cmds._docker_container_running", return_value=False)
|
|
579
584
|
run = mocker.patch("mn_cli.server_cmds.subprocess.run")
|
|
@@ -585,10 +590,13 @@ def test_ensure_context_engine_runtime_persists_profile_and_starts_compose(mocke
|
|
|
585
590
|
assert env["MEMBRANE_DIR"] == str(membrane_dir)
|
|
586
591
|
assert env["MN_CONTEXT_MODEL_RUNNER_MODEL"] == server_cmds.DEFAULT_CONTEXT_MODEL_RUNNER_MODEL
|
|
587
592
|
assert result["status"] == "started"
|
|
593
|
+
assert result["model_status"] == "installed"
|
|
594
|
+
assert inspect_model.call_args_list[0].args[0] == server_cmds.DEFAULT_CONTEXT_MODEL_RUNNER_MODEL
|
|
588
595
|
assert run.call_args_list[0].args[0] == runtime_compose_cmd("build", "membrane-context-engine")
|
|
589
596
|
assert run.call_args_list[1].args[0] == runtime_compose_cmd("up", "-d", "membrane-context-engine")
|
|
590
597
|
|
|
591
|
-
def test_ensure_context_engine_runtime_uses_release_image_without_source_clone(mocker):
|
|
598
|
+
def test_ensure_context_engine_runtime_uses_release_image_without_source_clone(mocker, monkeypatch):
|
|
599
|
+
monkeypatch.setenv("PATH", "/usr/local/bin:/usr/bin:/bin")
|
|
592
600
|
server_cmds.RUNTIME_COMPOSE_ENV.parent.mkdir(parents=True, exist_ok=True)
|
|
593
601
|
server_cmds.RUNTIME_COMPOSE_ENV.write_text(
|
|
594
602
|
"COMPOSE_PROJECT_NAME=mirror-neuron\n"
|
|
@@ -600,6 +608,7 @@ def test_ensure_context_engine_runtime_uses_release_image_without_source_clone(m
|
|
|
600
608
|
server_cmds.RUNTIME_COMPOSE_FILE.write_text("services: {}\n", encoding="utf-8")
|
|
601
609
|
ensure_source = mocker.patch("mn_cli.server_cmds._ensure_context_engine_source")
|
|
602
610
|
mocker.patch("mn_cli.server_cmds._ensure_docker_model_runner")
|
|
611
|
+
inspect_model = mocker.patch("mn_cli.server_cmds._docker_model_inspect_ok", return_value=True)
|
|
603
612
|
mocker.patch("mn_cli.server_cmds._remove_non_mirror_neuron_container")
|
|
604
613
|
mocker.patch("mn_cli.server_cmds._docker_container_running", return_value=False)
|
|
605
614
|
run = mocker.patch("mn_cli.server_cmds.subprocess.run")
|
|
@@ -616,10 +625,63 @@ def test_ensure_context_engine_runtime_uses_release_image_without_source_clone(m
|
|
|
616
625
|
assert env["MN_MEMBRANE_ENGINE_IMAGE"] == expected_image
|
|
617
626
|
assert "MEMBRANE_DIR" not in env
|
|
618
627
|
assert result["status"] == "started"
|
|
628
|
+
assert result["model_status"] == "already_installed"
|
|
619
629
|
assert result["engine_image"] == expected_image
|
|
620
630
|
ensure_source.assert_not_called()
|
|
631
|
+
inspect_model.assert_called_once_with(server_cmds.DEFAULT_CONTEXT_MODEL_RUNNER_MODEL)
|
|
621
632
|
assert run.call_args_list[0].args[0] == runtime_compose_cmd("pull", "membrane-context-engine")
|
|
622
633
|
assert run.call_args_list[1].args[0] == runtime_compose_cmd("up", "-d", "--no-build", "membrane-context-engine")
|
|
634
|
+
assert run.call_args_list[0].kwargs["env"]["DOCKER_CONFIG"] != str(Path.home() / ".docker")
|
|
635
|
+
assert run.call_args_list[0].kwargs["env"]["PATH"] == "/usr/local/bin:/usr/bin:/bin"
|
|
636
|
+
|
|
637
|
+
def test_public_gar_docker_env_strips_gcloud_helpers(monkeypatch, tmp_path):
|
|
638
|
+
docker_config = tmp_path / "docker-config"
|
|
639
|
+
docker_config.mkdir()
|
|
640
|
+
docker_plugins = docker_config / "cli-plugins"
|
|
641
|
+
docker_plugins.mkdir()
|
|
642
|
+
(docker_plugins / "docker-compose").write_text("#!/bin/sh\n", encoding="utf-8")
|
|
643
|
+
docker_context = docker_config / "contexts" / "meta" / "desktop-linux"
|
|
644
|
+
docker_context.mkdir(parents=True)
|
|
645
|
+
(docker_context / "meta.json").write_text('{"Name":"desktop-linux"}\n', encoding="utf-8")
|
|
646
|
+
(docker_config / "config.json").write_text(
|
|
647
|
+
json.dumps(
|
|
648
|
+
{
|
|
649
|
+
"currentContext": "desktop-linux",
|
|
650
|
+
"auths": {
|
|
651
|
+
"https://us-central1-docker.pkg.dev": {"auth": "private"},
|
|
652
|
+
"ghcr.io": {"auth": "keep"},
|
|
653
|
+
},
|
|
654
|
+
"credHelpers": {
|
|
655
|
+
"us-central1-docker.pkg.dev": "gcloud",
|
|
656
|
+
"ghcr.io": "ghcr",
|
|
657
|
+
},
|
|
658
|
+
"credsStore": "desktop",
|
|
659
|
+
"proxies": {"default": {"httpProxy": "http://proxy.test"}},
|
|
660
|
+
}
|
|
661
|
+
),
|
|
662
|
+
encoding="utf-8",
|
|
663
|
+
)
|
|
664
|
+
env = {"DOCKER_CONFIG": str(docker_config)}
|
|
665
|
+
|
|
666
|
+
next_env, temp_config_dir = server_cmds._anonymous_public_gar_docker_env(
|
|
667
|
+
env,
|
|
668
|
+
"us-central1-docker.pkg.dev/mirrorneuron-public-packages/mirrorneuron-runtime/membrane-context-engine:v1.2.8",
|
|
669
|
+
)
|
|
670
|
+
|
|
671
|
+
try:
|
|
672
|
+
assert temp_config_dir is not None
|
|
673
|
+
assert next_env["DOCKER_CONFIG"] == str(temp_config_dir)
|
|
674
|
+
assert next_env["DOCKER_CONFIG"] != env["DOCKER_CONFIG"]
|
|
675
|
+
sanitized = json.loads((temp_config_dir / "config.json").read_text(encoding="utf-8"))
|
|
676
|
+
assert "credsStore" not in sanitized
|
|
677
|
+
assert sanitized["credHelpers"] == {"ghcr.io": "ghcr"}
|
|
678
|
+
assert sanitized["auths"] == {"ghcr.io": {"auth": "keep"}}
|
|
679
|
+
assert sanitized["proxies"] == {"default": {"httpProxy": "http://proxy.test"}}
|
|
680
|
+
assert sanitized["currentContext"] == "desktop-linux"
|
|
681
|
+
assert (temp_config_dir / "cli-plugins" / "docker-compose").exists()
|
|
682
|
+
assert (temp_config_dir / "contexts" / "meta" / "desktop-linux" / "meta.json").exists()
|
|
683
|
+
finally:
|
|
684
|
+
shutil.rmtree(temp_config_dir, ignore_errors=True)
|
|
623
685
|
|
|
624
686
|
def test_ensure_context_engine_runtime_skips_compose_when_already_running(mocker, tmp_path):
|
|
625
687
|
membrane_dir = tmp_path / "Membrane"
|
|
@@ -635,6 +697,7 @@ def test_ensure_context_engine_runtime_skips_compose_when_already_running(mocker
|
|
|
635
697
|
server_cmds.RUNTIME_COMPOSE_FILE.write_text("services: {}\n", encoding="utf-8")
|
|
636
698
|
mocker.patch("mn_cli.server_cmds._ensure_context_engine_source", return_value=membrane_dir)
|
|
637
699
|
mocker.patch("mn_cli.server_cmds._ensure_docker_model_runner")
|
|
700
|
+
inspect_model = mocker.patch("mn_cli.server_cmds._docker_model_inspect_ok", return_value=True)
|
|
638
701
|
mocker.patch("mn_cli.server_cmds._remove_non_mirror_neuron_container")
|
|
639
702
|
mocker.patch("mn_cli.server_cmds._docker_container_running", return_value=True)
|
|
640
703
|
run = mocker.patch("mn_cli.server_cmds.subprocess.run")
|
|
@@ -643,8 +706,36 @@ def test_ensure_context_engine_runtime_skips_compose_when_already_running(mocker
|
|
|
643
706
|
|
|
644
707
|
assert result["status"] == "already_running"
|
|
645
708
|
assert result["model"] == "hf.co/acme/context"
|
|
709
|
+
assert result["model_status"] == "already_installed"
|
|
710
|
+
inspect_model.assert_called_once_with("hf.co/acme/context")
|
|
646
711
|
run.assert_not_called()
|
|
647
712
|
|
|
713
|
+
def test_ensure_context_engine_runtime_reconciles_missing_model_with_compose(mocker, tmp_path):
|
|
714
|
+
membrane_dir = tmp_path / "Membrane"
|
|
715
|
+
membrane_dir.mkdir()
|
|
716
|
+
(membrane_dir / "Dockerfile").write_text("FROM scratch\n", encoding="utf-8")
|
|
717
|
+
server_cmds.RUNTIME_COMPOSE_ENV.parent.mkdir(parents=True, exist_ok=True)
|
|
718
|
+
server_cmds.RUNTIME_COMPOSE_ENV.write_text(
|
|
719
|
+
"COMPOSE_PROJECT_NAME=mirror-neuron\n"
|
|
720
|
+
"COMPOSE_PROFILES=context\n"
|
|
721
|
+
"MN_CONTEXT_MODEL_RUNNER_MODEL=hf.co/acme/context\n",
|
|
722
|
+
encoding="utf-8",
|
|
723
|
+
)
|
|
724
|
+
server_cmds.RUNTIME_COMPOSE_FILE.write_text("services: {}\n", encoding="utf-8")
|
|
725
|
+
mocker.patch("mn_cli.server_cmds._ensure_context_engine_source", return_value=membrane_dir)
|
|
726
|
+
mocker.patch("mn_cli.server_cmds._ensure_docker_model_runner")
|
|
727
|
+
mocker.patch("mn_cli.server_cmds._docker_model_inspect_ok", side_effect=[False, True])
|
|
728
|
+
mocker.patch("mn_cli.server_cmds._remove_non_mirror_neuron_container")
|
|
729
|
+
mocker.patch("mn_cli.server_cmds._docker_container_running", return_value=True)
|
|
730
|
+
run = mocker.patch("mn_cli.server_cmds.subprocess.run")
|
|
731
|
+
|
|
732
|
+
result = server_cmds.ensure_context_engine_runtime()
|
|
733
|
+
|
|
734
|
+
assert result["status"] == "started"
|
|
735
|
+
assert result["model_status"] == "installed"
|
|
736
|
+
assert run.call_args_list[0].args[0] == runtime_compose_cmd("build", "membrane-context-engine")
|
|
737
|
+
assert run.call_args_list[1].args[0] == runtime_compose_cmd("up", "-d", "membrane-context-engine")
|
|
738
|
+
|
|
648
739
|
def test_runtime_compose_cmd_includes_models_override():
|
|
649
740
|
server_cmds.RUNTIME_COMPOSE_ENV.parent.mkdir(parents=True, exist_ok=True)
|
|
650
741
|
server_cmds.RUNTIME_COMPOSE_ENV.write_text("COMPOSE_PROJECT_NAME=mirror-neuron\n", encoding="utf-8")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/mirrorneuron_cli.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/mirrorneuron_cli.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mirrorneuron_cli-1.2.8 → mirrorneuron_cli-1.2.15}/mn_cli/schemas/workflow_manifest.schema.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|