Spaces:
Runtime error
Runtime error
| from concurrent.futures import ThreadPoolExecutor | |
| from threading import Event, Lock | |
| import pytest | |
| from fastapi.testclient import TestClient | |
| from gradio import Server | |
| from watch_my_escape.agent.escape_run import EntityDisplay, EscapeRunFrame, TranscriptIntroEvent, TranscriptTurnEvent | |
| from watch_my_escape.app import server | |
| from watch_my_escape.app.server import ( | |
| GENERATED_STATIC_DIR, | |
| SOURCE_STATIC_DIR, | |
| TEMPLATES_DIR, | |
| SynchronizedInferenceProvider, | |
| WarmProviderStore, | |
| app_data, | |
| build_escape_run_response, | |
| create_app, | |
| model_preset_options, | |
| premade_map_options, | |
| ) | |
| from watch_my_escape.game.runtime import ActionEffectSummary | |
| from watch_my_escape.llm.client import LlmConfigurationError, ZeroGpuQuotaExceededError | |
| from watch_my_escape.llm.config import MODEL_PRESETS | |
| from watch_my_escape.llm.models import ChatMessage, InferenceRequest, InferenceResponse | |
| def test_create_app_returns_gradio_server(): | |
| app = create_app() | |
| assert isinstance(app, Server) | |
| def test_web_assets_live_inside_package(): | |
| assert TEMPLATES_DIR.joinpath("index.html.jinja").is_file() | |
| assert TEMPLATES_DIR.joinpath("screens", "_game.html.jinja").is_file() | |
| assert SOURCE_STATIC_DIR.joinpath("input.css").is_file() | |
| assert SOURCE_STATIC_DIR.joinpath("app.js").is_file() | |
| def test_keyboard_flow_focus_contract_is_wired(): | |
| app_script = SOURCE_STATIC_DIR.joinpath("app.js").read_text(encoding="utf-8") | |
| base_styles = SOURCE_STATIC_DIR.joinpath("styles", "base.css").read_text(encoding="utf-8") | |
| game_runner_script = SOURCE_STATIC_DIR.joinpath("app", "game-runner.js").read_text(encoding="utf-8") | |
| maps_script = SOURCE_STATIC_DIR.joinpath("app", "maps.js").read_text(encoding="utf-8") | |
| screens_script = SOURCE_STATIC_DIR.joinpath("app", "screens.js").read_text(encoding="utf-8") | |
| warmup_script = SOURCE_STATIC_DIR.joinpath("app", "model-warmup.js").read_text(encoding="utf-8") | |
| assert 'dom.screens.get("menu").addEventListener("keydown", screens.handleMainMenuKeydown);' in app_script | |
| assert 'if (event.key === "Escape" && handleBackAction())' in app_script | |
| assert "screens.handleMainMenuKeydown(event)" not in app_script | |
| assert "modelSelector.handleKeydown(event)" not in app_script | |
| assert "mapSelector.handleKeydown(event)" not in app_script | |
| assert 'screen.toggleAttribute("aria-hidden", !isActive);' in screens_script | |
| assert "screen.inert = !isActive;" in screens_script | |
| assert 'focusScreen("warmup");' in warmup_script | |
| assert "focusElement(dom.screens.get(name), { silent: true });" in screens_script | |
| assert "screens.focusElement(dom.runButton, { silent: true });" in app_script | |
| assert 'if (event.key === "Enter" || event.key === " ")' not in screens_script | |
| assert 'if (event.key === "Enter" || event.key === " ")' not in maps_script | |
| assert 'element.dataset.silentFocus = "true";' in screens_script | |
| assert "[data-silent-focus]:focus" in base_styles | |
| assert "outline: none !important;" in base_styles | |
| assert "mapSelector.focusSelectedMapOption();" in app_script | |
| assert "showSetupScreen: backToModelSelect" in app_script | |
| assert "showSetupScreen();" in game_runner_script | |
| assert "function isTerminalFrame(frame)" in game_runner_script | |
| assert 'frame.error_code === "zerogpu_quota_exhausted"' in game_runner_script | |
| assert "renderTranscript(dom.transcriptOutput, frame, pixelSprite);" in game_runner_script | |
| assert "transcriptCard(event, pixelSprite, deliberationOpenStates)" in game_runner_script | |
| assert 'event.kind === "turn"' in game_runner_script | |
| assert "pixelSprite(event.action_emoji" in game_runner_script | |
| assert 'pixelSprite(item.icon || "?"' in game_runner_script | |
| assert "transcriptDeliberationOpenStates(element)" in game_runner_script | |
| assert "details.dataset.transcriptKey = key;" in game_runner_script | |
| assert 'fetch("/runs/cancel"' in game_runner_script | |
| assert "run_id: runId" in game_runner_script | |
| assert 'error.errorCode === "zerogpu_quota_exhausted"' in warmup_script | |
| assert '"ZEROGPU TIME EXHAUSTED"' in warmup_script | |
| def test_keyboard_escape_in_model_settings_stays_on_model_screen(): | |
| models_script = SOURCE_STATIC_DIR.joinpath("app", "models.js").read_text(encoding="utf-8") | |
| assert "event.stopPropagation();" in models_script | |
| def test_main_menu_keyboard_focus_does_not_add_visual_border(): | |
| screens_styles = SOURCE_STATIC_DIR.joinpath("styles", "screens.css").read_text(encoding="utf-8") | |
| assert ".main-menu-option:focus-visible" in screens_styles | |
| assert "outline: none;" in screens_styles | |
| def test_editor_typing_uses_quiet_validation_schedule(): | |
| behavior_form_script = SOURCE_STATIC_DIR.joinpath("app", "editor", "behavior-form.js").read_text(encoding="utf-8") | |
| controller_script = SOURCE_STATIC_DIR.joinpath("app", "editor", "controller.js").read_text(encoding="utf-8") | |
| entity_form_script = SOURCE_STATIC_DIR.joinpath("app", "editor", "entity-form.js").read_text(encoding="utf-8") | |
| validation_script = SOURCE_STATIC_DIR.joinpath("app", "editor", "validation.js").read_text(encoding="utf-8") | |
| assert "function scheduleTyping()" in validation_script | |
| assert "showPendingImmediately: false" in validation_script | |
| assert 'dom.editorMapName.addEventListener("input"' in controller_script | |
| assert "validation.scheduleTyping();" in controller_script | |
| assert 'input.type === "checkbox" ? validation.schedule : validation.scheduleTyping' in entity_form_script | |
| assert "function scheduleFieldValidation(input)" in behavior_form_script | |
| assert "validation.scheduleTyping();" in behavior_form_script | |
| def test_generated_assets_live_outside_package_source(): | |
| assert GENERATED_STATIC_DIR.parts[-3:] == ("build", "web", "static") | |
| assert "src" not in GENERATED_STATIC_DIR.parts | |
| def test_homepage_renders_without_request_query_parameter(): | |
| client = TestClient(create_app()) | |
| response = client.get("/") | |
| assert response.status_code == 200 | |
| assert "WATCH" in response.text | |
| assert "Play Game" in response.text | |
| assert "Map Editor" in response.text | |
| assert "Select Model" in response.text | |
| assert 'id="model-menu"' in response.text | |
| assert 'aria-label="Premade and custom maps"' in response.text | |
| assert 'id="warmup-screen"' in response.text | |
| assert 'id="saved-map-list"' in response.text | |
| assert 'id="saved-map-preview"' in response.text | |
| assert 'id="save-map"' in response.text | |
| assert 'id="load-map"' in response.text | |
| assert 'id="save-map-dialog"' in response.text | |
| assert 'id="load-saved-map"' in response.text | |
| assert 'id="delete-saved-map"' in response.text | |
| assert 'id="escape-result-icon"' in response.text | |
| assert 'id="escape-result-message"' in response.text | |
| assert 'id="transcript" class="transcript-log" role="log"' in response.text | |
| assert 'aria-label="Undo"' in response.text | |
| assert 'aria-label="Redo"' in response.text | |
| assert "Main Menu" in response.text | |
| assert "key-door-room" in response.text | |
| assert next(iter(MODEL_PRESETS)) in response.text | |
| def test_app_data_includes_browser_options(): | |
| assert set(app_data()) == {"models", "maps"} | |
| def test_model_warmup_endpoint_uses_short_non_thinking_completion(monkeypatch): | |
| seen = {} | |
| preset_id = next(iter(MODEL_PRESETS)) | |
| session_id = "warmup-short-completion" | |
| class FakeProvider: | |
| def complete(self, request): | |
| seen["request"] = request | |
| return InferenceResponse(content="OK") | |
| provider = FakeProvider() | |
| monkeypatch.setattr(server, "create_provider", lambda _config: provider) | |
| client = TestClient(create_app()) | |
| response = client.post("/models/warmup", json={"session_id": session_id, "model_preset": preset_id}) | |
| payload = response.json() | |
| assert response.status_code == 200 | |
| assert payload == {"warmed": True} | |
| assert seen["request"].messages[0].content == "Reply with OK." | |
| assert seen["request"].phase == "warmup" | |
| assert seen["request"].settings.max_tokens == 8 | |
| assert seen["request"].settings.temperature == 0.0 | |
| assert seen["request"].enable_thinking is False | |
| assert _inner_provider(server.warm_provider_store.get(session_id=session_id, model_preset=preset_id)) is provider | |
| def test_model_warmup_endpoint_reuses_existing_session_provider(monkeypatch): | |
| preset_id = next(iter(MODEL_PRESETS)) | |
| session_id = "warmup-existing-provider" | |
| provider = _RecordingProvider() | |
| server.warm_provider_store.add(session_id=session_id, model_preset=preset_id, provider=provider) | |
| monkeypatch.setattr(server, "create_provider", lambda _config: None) | |
| client = TestClient(create_app()) | |
| response = client.post("/models/warmup", json={"session_id": session_id, "model_preset": preset_id}) | |
| assert response.status_code == 200 | |
| assert response.json() == {"warmed": True} | |
| assert provider.requests | |
| assert provider.requests[0].phase == "warmup" | |
| assert provider.requests[0].enable_thinking is False | |
| assert provider.requests[0].settings.max_tokens == 8 | |
| assert _inner_provider(server.warm_provider_store.get(session_id=session_id, model_preset=preset_id)) is provider | |
| def test_model_warmup_endpoint_reports_zerogpu_quota_exhaustion(monkeypatch): | |
| preset_id = next(iter(MODEL_PRESETS)) | |
| class QuotaProvider: | |
| def complete(self, request): | |
| del request | |
| message = "ZeroGPU time is exhausted for this Hugging Face account." | |
| raise ZeroGpuQuotaExceededError(message) | |
| monkeypatch.setattr(server, "create_provider", lambda _config: QuotaProvider()) | |
| client = TestClient(create_app()) | |
| response = client.post("/models/warmup", json={"session_id": "quota-session", "model_preset": preset_id}) | |
| assert response.status_code == 503 | |
| assert response.json() == { | |
| "detail": { | |
| "error_code": "zerogpu_quota_exhausted", | |
| "message": "ZeroGPU time is exhausted for this Hugging Face account.", | |
| }, | |
| } | |
| def test_escape_stream_uses_session_warmed_provider(monkeypatch): | |
| seen = {} | |
| preset_id = next(iter(MODEL_PRESETS)) | |
| session_id = "stream-warmed-provider" | |
| provider = _NoopProvider() | |
| server.warm_provider_store.add(session_id=session_id, model_preset=preset_id, provider=provider) | |
| monkeypatch.setattr(server, "create_provider", lambda _config: None) | |
| monkeypatch.setattr(server, "run_model_escape_steps", lambda **kwargs: _fake_stream_steps(seen, **kwargs)) | |
| client = TestClient(create_app()) | |
| response = client.get(f"/escape-stream?model_preset={preset_id}&map_id=key-door-room&session_id={session_id}") | |
| assert response.status_code == 200 | |
| assert _inner_provider(seen["provider"]) is provider | |
| def test_escape_stream_reuses_session_provider_for_multiple_runs(monkeypatch): | |
| seen_providers = [] | |
| preset_id = next(iter(MODEL_PRESETS)) | |
| session_id = "stream-reusable-provider" | |
| warmed_provider = _NoopProvider() | |
| fallback_provider = _NoopProvider() | |
| server.warm_provider_store.add(session_id=session_id, model_preset=preset_id, provider=warmed_provider) | |
| monkeypatch.setattr(server, "create_provider", lambda _config: fallback_provider) | |
| monkeypatch.setattr( | |
| server, | |
| "run_model_escape_steps", | |
| lambda **kwargs: _fake_stream_steps({"providers": seen_providers}, **kwargs), | |
| ) | |
| client = TestClient(create_app()) | |
| first = client.get(f"/escape-stream?model_preset={preset_id}&map_id=key-door-room&session_id={session_id}") | |
| second = client.get(f"/escape-stream?model_preset={preset_id}&map_id=key-door-room&session_id={session_id}") | |
| assert first.status_code == 200 | |
| assert second.status_code == 200 | |
| assert [_inner_provider(provider) for provider in seen_providers] == [warmed_provider, warmed_provider] | |
| def test_escape_stream_ignores_session_provider_for_different_preset(monkeypatch): | |
| seen = {} | |
| preset_id, other_preset_id = tuple(MODEL_PRESETS)[:2] | |
| session_id = "stream-different-preset" | |
| fallback_provider = _NoopProvider() | |
| warmed_provider = _NoopProvider() | |
| server.warm_provider_store.add(session_id=session_id, model_preset=other_preset_id, provider=warmed_provider) | |
| monkeypatch.setattr(server, "create_provider", lambda _config: fallback_provider) | |
| monkeypatch.setattr(server, "run_model_escape_steps", lambda **kwargs: _fake_stream_steps(seen, **kwargs)) | |
| client = TestClient(create_app()) | |
| response = client.get(f"/escape-stream?model_preset={preset_id}&map_id=key-door-room&session_id={session_id}") | |
| assert response.status_code == 200 | |
| assert _inner_provider(seen["provider"]) is fallback_provider | |
| assert ( | |
| _inner_provider(server.warm_provider_store.get(session_id=session_id, model_preset=other_preset_id)) | |
| is warmed_provider | |
| ) | |
| def test_warm_provider_store_drops_expired_sessions(): | |
| provider = _NoopProvider() | |
| store = WarmProviderStore(ttl_seconds=0) | |
| store.add(session_id="expired-session", model_preset="example", provider=provider) | |
| assert store.get(session_id="expired-session", model_preset="example") is None | |
| def test_synchronized_provider_prevents_instance_reentry(): | |
| first_entered = Event() | |
| first_release = Event() | |
| second_entered = Event() | |
| state_lock = Lock() | |
| active_completions = 0 | |
| max_active_completions = 0 | |
| entered_phases: list[str] = [] | |
| class BlockingProvider: | |
| def complete(self, request): | |
| nonlocal active_completions, max_active_completions | |
| marker = request.messages[0].content | |
| with state_lock: | |
| active_completions += 1 | |
| max_active_completions = max(max_active_completions, active_completions) | |
| entered_phases.append(marker) | |
| if marker == "first": | |
| first_entered.set() | |
| assert first_release.wait(timeout=2) | |
| if marker == "second": | |
| second_entered.set() | |
| with state_lock: | |
| active_completions -= 1 | |
| return InferenceResponse(content=marker) | |
| provider = SynchronizedInferenceProvider(BlockingProvider()) | |
| with ThreadPoolExecutor(max_workers=2) as executor: | |
| first = executor.submit(provider.complete, _inference_request("first")) | |
| assert first_entered.wait(timeout=1) | |
| second = executor.submit(provider.complete, _inference_request("second")) | |
| assert not second_entered.wait(timeout=0.1) | |
| first_release.set() | |
| assert first.result(timeout=2).content == "first" | |
| assert second.result(timeout=2).content == "second" | |
| assert entered_phases == ["first", "second"] | |
| assert max_active_completions == 1 | |
| def test_cancellable_provider_stops_before_cancelled_completion(): | |
| cancelled = False | |
| provider = _RecordingProvider() | |
| def is_cancelled(): | |
| return cancelled | |
| wrapped_provider = server.CancellableInferenceProvider(provider, is_cancelled) | |
| cancelled = True | |
| with pytest.raises(server.EscapeRunCancelledError): | |
| wrapped_provider.complete(_inference_request("after-cancel")) | |
| assert not provider.requests | |
| def test_cancellable_provider_stops_after_inflight_completion_is_cancelled(): | |
| cancelled = False | |
| class CancellingProvider: | |
| def complete(self, request): | |
| nonlocal cancelled | |
| cancelled = True | |
| return InferenceResponse(content=request.messages[0].content) | |
| def is_cancelled(): | |
| return cancelled | |
| wrapped_provider = server.CancellableInferenceProvider(CancellingProvider(), is_cancelled) | |
| with pytest.raises(server.EscapeRunCancelledError): | |
| wrapped_provider.complete(_inference_request("during-call")) | |
| def test_model_preset_options_include_selector_metadata(): | |
| options = model_preset_options() | |
| tiny_aya = next(option for option in options if option["id"] == "tiny-aya-global") | |
| assert options | |
| assert {option["id"] for option in options} == set(MODEL_PRESETS) | |
| assert all(option["agent_icon"] for option in options) | |
| assert all(isinstance(option["parameter_size_b"], int | float) for option in options) | |
| assert all("thinking_supported" in option for option in options) | |
| assert all("thinking_enabled" in option for option in options) | |
| assert tiny_aya["thinking_supported"] is False | |
| assert tiny_aya["thinking_enabled"] is False | |
| def test_premade_map_options_include_preview_metadata(): | |
| options = premade_map_options() | |
| key_door_room = next(option for option in options if option["id"] == "key-door-room") | |
| mission_impawsible = next(option for option in options if option["id"] == "mission-impawsible") | |
| assert key_door_room["name"] == "Key Door Room" | |
| assert key_door_room["description"] | |
| assert key_door_room["agent_position"] == "(7, 7)" | |
| assert len(key_door_room["preview_map"].splitlines()) == 15 | |
| assert len(key_door_room["preview_map_colors"].splitlines()) == 15 | |
| assert all(len(row.split(" ")) == 15 for row in key_door_room["preview_map"].splitlines()) | |
| assert all(len(row.split(" ")) == 15 for row in key_door_room["preview_map_colors"].splitlines()) | |
| assert mission_impawsible["name"] == "Mission: Impawsible" | |
| assert mission_impawsible["description"] == "someone is hungry." | |
| assert len(mission_impawsible["preview_map"].splitlines()) == 15 | |
| def test_escape_run_response_reports_model_configuration_error(monkeypatch): | |
| def raise_configuration_error(): | |
| message = "Configure WME_MODEL_PATH before running inference." | |
| raise LlmConfigurationError(message) | |
| monkeypatch.setattr(server, "run_model_escape", raise_configuration_error) | |
| response = build_escape_run_response() | |
| assert response["status"] == "Model is not configured." | |
| assert response["sanity"] == "100" | |
| assert response["visible_entities"] == "- None." | |
| assert response["inventory"] == "- Empty." | |
| assert response["visibility"] == "" | |
| assert "Configure WME_MODEL_PATH" in response["transcript"] | |
| def test_escape_run_response_formats_successful_run(monkeypatch): | |
| class FakeResult: | |
| status = "Escaped with 98 sanity remaining." | |
| sanity = 98 | |
| visible_entities = ("locked-door: A locked door bars the exit.",) | |
| inventory = ("brass-key",) | |
| map_view = ((".", "door"), ("key", ".")) | |
| map_color_view = ((".", "#C8793A"), ("#FFD447", ".")) | |
| visibility_view = ((True, False), (False, True)) | |
| transcript = "Turn 1 - sanity 100 -> 99" | |
| monkeypatch.setattr(server, "run_model_escape", FakeResult) | |
| response = build_escape_run_response() | |
| assert response["status"] == "Escaped with 98 sanity remaining." | |
| assert response["sanity"] == "98" | |
| assert response["visible_entities"] == "- locked-door: A locked door bars the exit." | |
| assert response["inventory"] == "- brass-key" | |
| assert response["map"] == ". door\nkey ." | |
| assert response["map_colors"] == ". #C8793A\n#FFD447 ." | |
| assert response["visibility"] == "1 0\n0 1" | |
| assert response["transcript"] == "Turn 1 - sanity 100 -> 99" | |
| def test_escape_stream_returns_turn_frames(monkeypatch): | |
| seen = {} | |
| preset_id = next(iter(MODEL_PRESETS)) | |
| preset = MODEL_PRESETS[preset_id] | |
| def fake_steps(**kwargs): | |
| seen.update(kwargs) | |
| yield EscapeRunFrame( | |
| escaped=False, | |
| sanity=99, | |
| position="(8, 8)", | |
| visible_entities=("locked-door: A locked door bars the exit.",), | |
| inventory=("brass-key",), | |
| visible_entity_details=( | |
| EntityDisplay( | |
| id="locked-door", | |
| icon="\U0001f6aa", | |
| description="A locked door bars the exit.", | |
| color="#C8793A", | |
| ), | |
| ), | |
| inventory_details=( | |
| EntityDisplay(id="brass-key", icon="\U0001f511", description="A brass key.", color="#FFD447"), | |
| ), | |
| map_view=((".", "\U0001f642"), ("\U0001f511", "\U0001f6aa")), | |
| map_color_view=((".", "."), ("#FFD447", "#C8793A")), | |
| visibility_view=((True, True), (False, True)), | |
| transcript="Turn 1 - sanity 100 -> 99", | |
| status="Still searching with 99 sanity remaining.", | |
| action_label="open", | |
| transcript_events=( | |
| TranscriptIntroEvent( | |
| visible_entities=( | |
| EntityDisplay( | |
| id="locked-door", | |
| icon="\U0001f6aa", | |
| description="A locked door bars the exit.", | |
| color="#C8793A", | |
| ), | |
| ), | |
| message="", | |
| ), | |
| TranscriptTurnEvent( | |
| turn_number=1, | |
| sanity_before=100, | |
| sanity_after=99, | |
| deliberation="I should try the door.", | |
| action_type="open", | |
| action_emoji="\U0001f6aa", | |
| action_text="Open locked-door", | |
| result="The door is locked.", | |
| effects=( | |
| ActionEffectSummary( | |
| kind="set_entity_state", | |
| entity_id="locked-door", | |
| text="locked-door state changed to unlocked.", | |
| ), | |
| ), | |
| ), | |
| ), | |
| ) | |
| provider = object() | |
| monkeypatch.setattr(server, "create_provider", lambda _config: provider) | |
| monkeypatch.setattr(server, "run_model_escape_steps", fake_steps) | |
| client = TestClient(create_app()) | |
| response = client.get(f"/escape-stream?model_preset={preset_id}&map_id=key-door-room&startup_delay_ms=2000") | |
| assert response.status_code == 200 | |
| assert _inner_provider(seen["provider"]) is provider | |
| assert seen["game_map"].id == "key-door-room" | |
| assert seen["startup_delay_ms"] == 2000 | |
| assert seen["settings"].deliberation.temperature == preset.thinking_temperature | |
| assert seen["settings"].deliberation.top_p == preset.thinking_top_p | |
| assert seen["settings"].deliberation.top_k == preset.thinking_top_k | |
| assert seen["settings"].deliberation_enable_thinking is True | |
| assert seen["settings"].action.temperature == 0.0 | |
| assert "objective" not in seen | |
| assert "Still searching with 99 sanity remaining." in response.text | |
| assert "(8, 8)" in response.text | |
| assert '"action_label": "open"' in response.text | |
| assert '"visibility": "1 1\\n0 1"' in response.text | |
| assert '"map_colors": ". .\\n#FFD447 #C8793A"' in response.text | |
| assert '"visible_entity_details": [{"id": "locked-door"' in response.text | |
| assert '"color": "#C8793A"' in response.text | |
| assert '"inventory_details": [{"id": "brass-key"' in response.text | |
| assert '"transcript_events": [{"kind": "intro"' in response.text | |
| assert '"kind": "turn"' in response.text | |
| assert '"action_type": "open"' in response.text | |
| assert '"action_text": "Open locked-door"' in response.text | |
| assert '"effects": [{"kind": "set_entity_state"' in response.text | |
| assert '"text": "locked-door state changed to unlocked."' in response.text | |
| assert "locked-door" in response.text | |
| assert "Turn 1 - sanity 100 -> 99" in response.text | |
| def test_escape_stream_handles_unexpected_provider_errors(monkeypatch): | |
| preset_id = next(iter(MODEL_PRESETS)) | |
| def raise_error(**_kwargs): | |
| message = "index 1510 is out of bounds for axis 0 with size 1" | |
| raise IndexError(message) | |
| monkeypatch.setattr(server, "create_provider", lambda _config: object()) | |
| monkeypatch.setattr(server, "run_model_escape_steps", raise_error) | |
| client = TestClient(create_app()) | |
| response = client.get(f"/escape-stream?model_preset={preset_id}&map_id=key-door-room") | |
| assert response.status_code == 200 | |
| assert '"status": "Model run failed."' in response.text | |
| assert "index 1510 is out of bounds" in response.text | |
| def test_escape_stream_reports_zerogpu_quota_exhaustion(monkeypatch): | |
| preset_id = next(iter(MODEL_PRESETS)) | |
| def raise_quota_error(**_kwargs): | |
| message = "ZeroGPU time is exhausted for this Hugging Face account." | |
| raise ZeroGpuQuotaExceededError(message) | |
| monkeypatch.setattr(server, "create_provider", lambda _config: object()) | |
| monkeypatch.setattr(server, "run_model_escape_steps", raise_quota_error) | |
| client = TestClient(create_app()) | |
| response = client.get(f"/escape-stream?model_preset={preset_id}&map_id=key-door-room") | |
| assert response.status_code == 200 | |
| assert '"status": "ZeroGPU time exhausted."' in response.text | |
| assert '"error_code": "zerogpu_quota_exhausted"' in response.text | |
| assert "ZeroGPU time is exhausted for this Hugging Face account." in response.text | |
| def test_escape_stream_stops_after_cancellation(monkeypatch): | |
| cancel_checks = 0 | |
| def fake_steps(**_kwargs): | |
| yield _frame_with_transcript("first") | |
| yield _frame_with_transcript("second") | |
| def is_cancelled(): | |
| nonlocal cancel_checks | |
| cancel_checks += 1 | |
| return cancel_checks > 1 | |
| monkeypatch.setattr(server, "run_model_escape_steps", fake_steps) | |
| chunks = tuple( | |
| server._escape_event_stream( # noqa: SLF001 | |
| lifecycle=server.EscapeStreamLifecycle(is_cancelled=is_cancelled, on_complete=lambda: None) | |
| ) | |
| ) | |
| assert len(chunks) == 1 | |
| assert "first" in chunks[0] | |
| assert "second" not in chunks[0] | |
| def test_cancel_escape_run_endpoint_marks_run_cancelled(): | |
| client = TestClient(create_app()) | |
| session_id = "cancel-session" | |
| run_id = "cancel-run" | |
| server.escape_run_store.start(session_id=session_id, run_id=run_id) | |
| response = client.post("/runs/cancel", json={"session_id": session_id, "run_id": run_id}) | |
| assert response.status_code == 200 | |
| assert response.json() == {"cancelled": True} | |
| assert server.escape_run_store.is_cancelled(session_id=session_id, run_id=run_id) is True | |
| def test_escape_stream_applies_deliberation_query_overrides(monkeypatch): | |
| seen = {} | |
| preset_id = "gemma-4-12b-it" | |
| monkeypatch.setattr(server, "create_provider", lambda _config: object()) | |
| monkeypatch.setattr(server, "run_model_escape_steps", lambda **kwargs: _fake_stream_steps(seen, **kwargs)) | |
| client = TestClient(create_app()) | |
| response = client.get( | |
| "/escape-stream", | |
| params={ | |
| "model_preset": preset_id, | |
| "map_id": "key-door-room", | |
| "deliberation_enable_thinking": "false", | |
| "deliberation_temperature": "0.35", | |
| }, | |
| ) | |
| assert response.status_code == 200 | |
| assert seen["settings"].deliberation_enable_thinking is False | |
| assert seen["settings"].deliberation.temperature == 0.35 | |
| assert seen["settings"].action.temperature == 0.0 | |
| def test_escape_stream_rejects_deliberation_temperature_above_slider_range(): | |
| client = TestClient(create_app()) | |
| response = client.get( | |
| "/escape-stream", | |
| params={ | |
| "model_preset": "gemma-4-12b-it", | |
| "map_id": "key-door-room", | |
| "deliberation_temperature": "1.5", | |
| }, | |
| ) | |
| assert response.status_code == 422 | |
| def test_escape_stream_keeps_thinking_disabled_for_unsupported_model(monkeypatch): | |
| seen = {} | |
| monkeypatch.setattr(server, "create_provider", lambda _config: object()) | |
| monkeypatch.setattr(server, "run_model_escape_steps", lambda **kwargs: _fake_stream_steps(seen, **kwargs)) | |
| client = TestClient(create_app()) | |
| response = client.get( | |
| "/escape-stream", | |
| params={ | |
| "model_preset": "tiny-aya-global", | |
| "map_id": "key-door-room", | |
| "deliberation_enable_thinking": "true", | |
| }, | |
| ) | |
| assert response.status_code == 200 | |
| assert seen["settings"].deliberation_enable_thinking is False | |
| assert seen["settings"].deliberation.temperature == MODEL_PRESETS["tiny-aya-global"].thinking_temperature | |
| def test_escape_stream_rejects_unknown_model_preset(): | |
| client = TestClient(create_app()) | |
| response = client.get("/escape-stream?model_preset=missing&map_id=key-door-room") | |
| assert response.status_code == 400 | |
| assert "Unknown model preset" in response.text | |
| def test_escape_stream_rejects_unknown_map(monkeypatch): | |
| monkeypatch.setattr(server, "create_provider", lambda _config: object()) | |
| client = TestClient(create_app()) | |
| response = client.get(f"/escape-stream?model_preset={next(iter(MODEL_PRESETS))}&map_id=missing") | |
| assert response.status_code == 400 | |
| assert "Unknown map" in response.text | |
| def test_custom_map_run_token_accepts_export_document(): | |
| client = TestClient(create_app()) | |
| response = client.post("/maps/custom-run-token", json=_custom_map_document()) | |
| assert response.status_code == 200 | |
| assert response.json()["token"] | |
| def test_escape_stream_returns_custom_map_turn_frames(monkeypatch): | |
| seen = {} | |
| preset_id = next(iter(MODEL_PRESETS)) | |
| def fake_steps(**kwargs): | |
| seen.update(kwargs) | |
| yield EscapeRunFrame( | |
| escaped=False, | |
| sanity=99, | |
| position="(1, 1)", | |
| visible_entities=("custom-exit: A custom way out.",), | |
| inventory=(), | |
| visible_entity_details=(), | |
| inventory_details=(), | |
| map_view=((".", "\U0001f642"), (".", "\U0001f3c1")), | |
| map_color_view=((".", "."), (".", "#71F7B1")), | |
| visibility_view=((True, True), (True, True)), | |
| transcript="Turn 1 - custom map", | |
| status="Still searching with 99 sanity remaining.", | |
| action_label="examine", | |
| ) | |
| provider = object() | |
| monkeypatch.setattr(server, "create_provider", lambda _config: provider) | |
| monkeypatch.setattr(server, "run_model_escape_steps", fake_steps) | |
| client = TestClient(create_app()) | |
| token_response = client.post("/maps/custom-run-token", json=_custom_map_document()) | |
| token = token_response.json()["token"] | |
| response = client.get(f"/escape-stream?model_preset={preset_id}&custom_map_token={token}") | |
| assert response.status_code == 200 | |
| assert _inner_provider(seen["provider"]) is provider | |
| assert seen["game_map"].id == "custom-room" | |
| assert "Turn 1 - custom map" in response.text | |
| def test_escape_stream_rejects_unknown_custom_map_token(): | |
| client = TestClient(create_app()) | |
| response = client.get(f"/escape-stream?model_preset={next(iter(MODEL_PRESETS))}&custom_map_token=missing") | |
| assert response.status_code == 400 | |
| assert "Unknown or expired custom map token" in response.text | |
| def test_escape_stream_rejects_missing_map_source(): | |
| client = TestClient(create_app()) | |
| response = client.get(f"/escape-stream?model_preset={next(iter(MODEL_PRESETS))}") | |
| assert response.status_code == 400 | |
| assert "Choose exactly one map source" in response.text | |
| def test_escape_stream_rejects_ambiguous_map_source(): | |
| client = TestClient(create_app()) | |
| response = client.get( | |
| f"/escape-stream?model_preset={next(iter(MODEL_PRESETS))}&map_id=key-door-room&custom_map_token=extra" | |
| ) | |
| assert response.status_code == 400 | |
| assert "Choose exactly one map source" in response.text | |
| def test_map_validation_accepts_export_document(): | |
| client = TestClient(create_app()) | |
| response = client.post( | |
| "/maps/validate", | |
| json={ | |
| "description": "A small room.", | |
| "map": { | |
| "id": "small-room", | |
| "name": "Small Room", | |
| "agent_start": {"x": 1, "y": 1}, | |
| "entities": [ | |
| { | |
| "position": {"x": 2, "y": 1}, | |
| "entity": { | |
| "id": "exit", | |
| "icon": "\U0001f3c1", | |
| "description": "The way out.", | |
| "passable": True, | |
| "behaviors": [{"trigger": {"action": "operate"}, "effects": [{"type": "escape_map"}]}], | |
| }, | |
| } | |
| ], | |
| }, | |
| }, | |
| ) | |
| assert response.status_code == 200 | |
| assert response.json()["map"]["id"] == "small-room" | |
| def test_map_validation_rejects_objective_field(): | |
| client = TestClient(create_app()) | |
| response = client.post( | |
| "/maps/validate", | |
| json={ | |
| "description": "A small room.", | |
| "objective": "Escape with coaching.", | |
| "map": {"id": "small-room", "name": "Small Room", "agent_start": {"x": 1, "y": 1}, "entities": []}, | |
| }, | |
| ) | |
| assert response.status_code == 422 | |
| assert "objective" in response.text | |
| def _fake_stream_steps(seen, **kwargs): | |
| if "providers" in seen: | |
| seen["providers"].append(kwargs["provider"]) | |
| else: | |
| seen.update(kwargs) | |
| yield EscapeRunFrame( | |
| escaped=False, | |
| sanity=99, | |
| position="(8, 8)", | |
| visible_entities=(), | |
| inventory=(), | |
| visible_entity_details=(), | |
| inventory_details=(), | |
| map_view=((".",),), | |
| map_color_view=((".",),), | |
| visibility_view=((True,),), | |
| transcript="Turn 1", | |
| status="Still searching with 99 sanity remaining.", | |
| action_label="wait", | |
| ) | |
| class _NoopProvider: | |
| def complete(self, request): | |
| del request | |
| return InferenceResponse(content="OK") | |
| class _RecordingProvider: | |
| def __init__(self): | |
| self.requests = [] | |
| def complete(self, request): | |
| self.requests.append(request) | |
| return InferenceResponse(content="OK") | |
| def _inner_provider(provider): | |
| if isinstance(provider, SynchronizedInferenceProvider): | |
| return provider.inner | |
| return provider | |
| def _inference_request(phase: str) -> InferenceRequest: | |
| return InferenceRequest(messages=(ChatMessage(role="user", content=phase),), phase="warmup") | |
| def _frame_with_transcript(transcript: str) -> EscapeRunFrame: | |
| return EscapeRunFrame( | |
| escaped=False, | |
| sanity=99, | |
| position="(1, 1)", | |
| visible_entities=(), | |
| inventory=(), | |
| visible_entity_details=(), | |
| inventory_details=(), | |
| map_view=((".",),), | |
| map_color_view=((".",),), | |
| visibility_view=((True,),), | |
| transcript=transcript, | |
| status="Still searching with 99 sanity remaining.", | |
| action_label="wait", | |
| ) | |
| def _custom_map_document(): | |
| return { | |
| "description": "A custom room.", | |
| "map": { | |
| "id": "custom-room", | |
| "name": "Custom Room", | |
| "agent_start": {"x": 1, "y": 1}, | |
| "entities": [ | |
| { | |
| "position": {"x": 2, "y": 1}, | |
| "entity": { | |
| "id": "custom-exit", | |
| "icon": "\U0001f3c1", | |
| "color": "#71F7B1", | |
| "description": "A custom way out.", | |
| "passable": True, | |
| "behaviors": [{"trigger": {"action": "operate"}, "effects": [{"type": "escape_map"}]}], | |
| }, | |
| } | |
| ], | |
| }, | |
| } | |