feat: add Chinese UI, Responses models, and mirrored Windows setup
Localize the web console and persist custom model and capability settings. Use OpenAI Responses exclusively and move runtime limits into environment configuration. Bootstrap uv and the virtual environment on Windows, support optional China mirrors, and package source distributions without environments, build output, or credentials. Update regression tests and repository quality checks for the new behavior.
This commit is contained in:
1 parent
371aa6df56
commit
2ddec53954
183 files changed
+3427
-11857
No files matched your search
@@ -25,7 +25,7 @@ def test_get_active_model_info_pro_architecture():
|
||||
info = ModelService.get_active_model_info("pro")
|
||||
assert info["name"] == "Pro"
|
||||
assert info["architecture"] == "ARTEMIS Pro"
|
||||
assert info["provider"] == "google"
|
||||
assert info["provider"] == "openai"
|
||||
assert "id" in info
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ def test_get_active_model_info_flash_architecture():
|
||||
info = ModelService.get_active_model_info("flash")
|
||||
assert info["name"] == "Flash"
|
||||
assert info["architecture"] == "ARTEMIS Flash"
|
||||
assert info["provider"] == "google"
|
||||
assert info["provider"] == "openai"
|
||||
|
||||
|
||||
def test_resolve_session_profile_from_device_info():
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from dotenv import dotenv_values
|
||||
import pytest
|
||||
from pydantic import SecretStr, ValidationError
|
||||
|
||||
from apps.admin_console.services import model_settings_service as service
|
||||
from artemis.config.settings import Settings, settings
|
||||
from artemis.config.agent import load_agent_config
|
||||
from artemis.config.llm import parse_llm_config
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def isolated_settings(monkeypatch, tmp_path):
|
||||
env = tmp_path / ".env"
|
||||
env.write_text("# user settings\nADB_PORT=5037\nUNRELATED=keep\n", encoding="utf-8")
|
||||
monkeypatch.setattr(service, "get_env_file", lambda: env)
|
||||
keys = [
|
||||
"OPENAI_API_KEY",
|
||||
"OPENAI_MODEL",
|
||||
"OPENAI_BASE_URL",
|
||||
"OPENAI_TIMEOUT_SECONDS",
|
||||
"OPENAI_MAX_RETRIES",
|
||||
"ARTEMIS_VIDEO_ENABLED",
|
||||
"ARTEMIS_OUTPUTTER_ENABLED",
|
||||
"ARTEMIS_STEP_SUMMARIZER_ENABLED",
|
||||
]
|
||||
for key in keys:
|
||||
monkeypatch.setattr(settings, key, getattr(settings, key))
|
||||
monkeypatch.delenv(key, raising=False)
|
||||
monkeypatch.setattr(settings, "OPENAI_API_KEY", SecretStr("existing-test-secret"))
|
||||
return env
|
||||
|
||||
|
||||
def test_save_reload_and_blank_key_preserves_secret(isolated_settings):
|
||||
update = service.ModelSettingsUpdate(
|
||||
base_url="https://gateway.example/v1/",
|
||||
model="my-model",
|
||||
api_key="new-test-secret",
|
||||
timeout_seconds=75,
|
||||
max_retries=2,
|
||||
)
|
||||
result = service.save_model_settings(update)
|
||||
assert result["api_key_configured"]
|
||||
assert "new-test-secret" not in repr(result)
|
||||
saved = dotenv_values(isolated_settings)
|
||||
assert saved["OPENAI_API_KEY"] == "new-test-secret"
|
||||
assert saved["OPENAI_MODEL"] == "my-model"
|
||||
assert saved["UNRELATED"] == "keep"
|
||||
assert "# user settings" in isolated_settings.read_text(encoding="utf-8")
|
||||
reloaded = Settings(_env_file=isolated_settings)
|
||||
assert reloaded.OPENAI_MODEL == "my-model"
|
||||
assert parse_llm_config().operator.model == "my-model"
|
||||
assert parse_llm_config().operator.timeout == 75
|
||||
service.save_model_settings(update.model_copy(update={"api_key": SecretStr("")}))
|
||||
assert dotenv_values(isolated_settings)["OPENAI_API_KEY"] == "new-test-secret"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"url",
|
||||
[
|
||||
"ftp://example.com",
|
||||
"https://user:pass@example.com",
|
||||
"https://example.com/v1/responses",
|
||||
"https://example.com/v1?key=secret",
|
||||
],
|
||||
)
|
||||
def test_rejects_invalid_base_url(url):
|
||||
with pytest.raises(ValidationError):
|
||||
service.ModelSettingsUpdate(base_url=url, model="test")
|
||||
|
||||
|
||||
def test_capabilities_persist_and_apply_to_new_tasks(isolated_settings):
|
||||
result = service.save_capability_settings(
|
||||
service.CapabilitySettings(
|
||||
video_enabled=False, outputter_enabled=False, step_summarizer_enabled=False
|
||||
)
|
||||
)
|
||||
assert result.video_enabled is False
|
||||
saved = dotenv_values(isolated_settings)
|
||||
assert saved["ARTEMIS_VIDEO_ENABLED"] == "false"
|
||||
config = load_agent_config()
|
||||
assert config.video_analyzer.enabled is False
|
||||
assert config.pro.video_analyzer.enabled is False
|
||||
assert config.outputter.enabled is False
|
||||
assert config.flash.step_summarizer.enabled is False
|
||||
service.save_capability_settings(service.CapabilitySettings())
|
||||
assert load_agent_config().video_analyzer.enabled is True
|
||||
|
||||
|
||||
def test_failed_write_keeps_running_settings(monkeypatch, isolated_settings):
|
||||
previous = settings.OPENAI_MODEL
|
||||
monkeypatch.setattr(
|
||||
service.os, "replace", lambda *args: (_ for _ in ()).throw(OSError("read only"))
|
||||
)
|
||||
with pytest.raises(OSError):
|
||||
service.save_model_settings(service.ModelSettingsUpdate(model="not-saved"))
|
||||
assert settings.OPENAI_MODEL == previous
|
||||
assert "not-saved" not in isolated_settings.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def test_key_not_in_validation_errors(isolated_settings):
|
||||
with pytest.raises(ValueError) as exc:
|
||||
service.save_model_settings(
|
||||
service.ModelSettingsUpdate(model="test", api_key="secret\nvalue")
|
||||
)
|
||||
assert "secret" not in str(exc.value)
|
||||
@@ -57,7 +57,7 @@ async def test_credentials_endpoint_never_returns_key_material(monkeypatch):
|
||||
assert res.status_code == 200
|
||||
assert honeytoken not in res.text
|
||||
providers = {entry["name"]: entry["configured"] for entry in res.json()["providers"]}
|
||||
assert providers.get("google") is True
|
||||
assert providers.get("openai") is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
@@ -56,7 +56,7 @@ def test_retrying_llm_payload_keeps_user_visible_retry_details():
|
||||
{
|
||||
"error": "503 UNAVAILABLE: high demand",
|
||||
"delay": 1.18,
|
||||
"provider": "google",
|
||||
"provider": "openai",
|
||||
"source": "provider_sdk",
|
||||
"recoverable": True,
|
||||
"internal_noise": "do not expose",
|
||||
@@ -69,7 +69,7 @@ def test_retrying_llm_payload_keeps_user_visible_retry_details():
|
||||
assert normalized["payload"] == {
|
||||
"error": "503 UNAVAILABLE: high demand",
|
||||
"delay": 1.18,
|
||||
"provider": "google",
|
||||
"provider": "openai",
|
||||
"source": "provider_sdk",
|
||||
"recoverable": True,
|
||||
}
|
||||
@@ -81,7 +81,7 @@ def test_terminal_llm_payload_keeps_wait_and_retry_aggregation():
|
||||
{
|
||||
"delay": 5.0,
|
||||
"scheduled_at": 100.0,
|
||||
"provider": "google",
|
||||
"provider": "openai",
|
||||
"source": "provider_sdk",
|
||||
}
|
||||
]
|
||||
@@ -94,7 +94,7 @@ def test_terminal_llm_payload_keeps_wait_and_retry_aggregation():
|
||||
"error": "503 UNAVAILABLE",
|
||||
"pause": True,
|
||||
"request_id": "request-1",
|
||||
"provider": "google",
|
||||
"provider": "openai",
|
||||
"waited_seconds": 18.5,
|
||||
"retries": retries,
|
||||
"messages": ["private prompt"],
|
||||
@@ -106,7 +106,7 @@ def test_terminal_llm_payload_keeps_wait_and_retry_aggregation():
|
||||
|
||||
assert normalized["payload"] == {
|
||||
"error": "503 UNAVAILABLE",
|
||||
"provider": "google",
|
||||
"provider": "openai",
|
||||
"pause": True,
|
||||
"request_id": "request-1",
|
||||
"waited_seconds": 18.5,
|
||||
|
||||
Reference in new issue
Block a user