personal memory agent
0

Configure Feed

Select the types of activity you want to include in your feed.

solstone / tests / test_chat_reasons.py
8.3 kB 258 lines
1# SPDX-License-Identifier: AGPL-3.0-only 2# Copyright (c) 2026 sol pbc 3 4import json 5from pathlib import Path 6 7from solstone.convey.provider_readiness import ( 8 DISPLAY_NAMES, 9 chat_reason_projection, 10 chat_view, 11) 12 13EXPECTED_CODES = { 14 "provider_key_missing", 15 "thinking_engine_not_chosen", 16 "ram_insufficient", 17 "gpu_unavailable", 18 "gpu_probe_failed", 19 "local_artifact_proof_unavailable", 20 "local_model_missing", 21 "model_missing", 22 "binary_missing", 23 "install_busy", 24 "local_model_installing", 25 "local_model_loading", 26 "local_model_not_ready", 27 "local_server_unhealthy", 28 "local_endpoint_unreachable", 29 "local_endpoint_contract_failed", 30 "unsupported_platform", 31 "host_unfit", 32 "unsupported_model", 33 "sha256_mismatch", 34 "archive_path_traversal", 35 "cuda_runtime_incomplete", 36 "provider_key_invalid", 37 "model_not_found", 38 "provider_quota_exceeded", 39 "provider_request_rejected", 40 "network_unreachable", 41 "provider_response_invalid", 42 "provider_unavailable", 43 "chat_pipeline_unavailable", 44 "chat_timeout", 45 "local_queue_timeout", 46 "local_capacity_exhausted", 47 "context_window_exceeded", 48 "context_budget_exceeded", 49 "incomplete_json_length", 50 "incomplete_text_length", 51 "max_turns_exhausted", 52 "no_output", 53 "token_budget_exceeded", 54 "wall_clock_exceeded", 55 "unknown", 56} 57 58 59def _extract_frozen_object(text: str, name: str) -> dict: 60 marker = f"const {name} = Object.freeze(" 61 start = text.index(marker) + len(marker) 62 depth = 0 63 in_string = False 64 escaped = False 65 object_start = None 66 67 for index in range(start, len(text)): 68 char = text[index] 69 if in_string: 70 if escaped: 71 escaped = False 72 elif char == "\\": 73 escaped = True 74 elif char == '"': 75 in_string = False 76 continue 77 if char == '"': 78 in_string = True 79 continue 80 if char == "{": 81 if object_start is None: 82 object_start = index 83 depth += 1 84 elif char == "}": 85 depth -= 1 86 if depth == 0 and object_start is not None: 87 return json.loads(text[object_start : index + 1]) 88 89 raise AssertionError(f"Could not extract {name}") 90 91 92def _render_js_chat_reason( 93 reasons: dict, display_names: dict, code: str, provider: str 94) -> dict: 95 reason = reasons.get(code) 96 if reason is None: 97 return {"code": code, "message": code, "action": None} 98 99 provider_slug = str(provider or "") 100 if code == "unknown": 101 display_name = display_names.get(provider_slug) 102 message = ( 103 f"something went wrong with {display_name}" 104 if display_name 105 else reason["template"] 106 ) 107 return {"code": code, "message": message, "action": None} 108 109 display_name = display_names.get(provider_slug, provider_slug) 110 message = reason["template"].replace("{provider}", display_name) 111 action = ( 112 {"label": reason["action"]["label"], "href": reason["action"]["href"]} 113 if reason["action"] 114 else None 115 ) 116 return {"code": code, "message": message, "action": action} 117 118 119def test_registry_shape(): 120 reasons = chat_reason_projection() 121 assert set(reasons) == EXPECTED_CODES 122 for reason in reasons.values(): 123 assert reason["template"] 124 action = reason["action"] 125 assert action is None or set(action) == {"label", "href"} 126 127 128def test_model_not_found_static_projection_has_no_model_placeholder(): 129 reason = chat_reason_projection()["model_not_found"] 130 131 assert "{model}" not in reason["template"] 132 assert "{model}" not in chat_view("model_not_found", "google")["message"] 133 134 135def test_render_known_codes(): 136 for code, reason in chat_reason_projection().items(): 137 rendered = chat_view(code, "google") 138 assert rendered["code"] == code 139 assert rendered["message"] 140 if code == "unknown": 141 assert rendered["message"] == "something went wrong with Gemini" 142 elif "{provider}" in reason["template"]: 143 assert "Gemini" in rendered["message"] 144 if code == "provider_key_invalid": 145 assert rendered["action"] == { 146 "label": "Open Thinking", 147 "href": "/app/thinking/#main", 148 } 149 else: 150 assert rendered["action"] == reason["action"] 151 152 153def test_render_display_names(): 154 for slug, display in DISPLAY_NAMES.items(): 155 rendered = chat_view("provider_key_invalid", slug) 156 assert display in rendered["message"] 157 158 159def test_render_unknown_code(): 160 assert chat_view("not_a_real_code", "") == { 161 "code": "not_a_real_code", 162 "message": "not_a_real_code", 163 "action": None, 164 } 165 166 167def test_render_unknown_with_known_provider(): 168 for slug, display_name in DISPLAY_NAMES.items(): 169 assert chat_view("unknown", slug) == { 170 "code": "unknown", 171 "message": f"something went wrong with {display_name}", 172 "action": None, 173 } 174 175 176def test_render_unknown_with_empty_or_unknown_provider(): 177 for provider in ("", "weirdslug"): 178 assert chat_view("unknown", provider) == { 179 "code": "unknown", 180 "message": "chat had trouble", 181 "action": None, 182 } 183 184 185def test_render_empty_provider(): 186 assert chat_view("network_unreachable", "") == { 187 "code": "network_unreachable", 188 "message": "I couldn't reach the network", 189 "action": None, 190 } 191 192 193def test_render_local_runtime_codes(): 194 expected = { 195 "local_queue_timeout": "the local model was busy and couldn't start in time", 196 "local_capacity_exhausted": ( 197 "the local model was busy and could not finish this request" 198 ), 199 "context_budget_exceeded": "the request was too long for the local model", 200 } 201 for code, message in expected.items(): 202 rendered = chat_view(code, "local") 203 assert rendered == {"code": code, "message": message, "action": None} 204 assert rendered["message"] != code 205 206 207def test_render_no_placeholder_artifacts_for_all_providers(): 208 providers = ["", "google", "openai", "anthropic", "local", "weirdslug"] 209 for code in chat_reason_projection(): 210 for provider in providers: 211 message = chat_view(code, provider)["message"] 212 assert "{provider}" not in message 213 assert "None" not in message 214 215 216def test_js_parity(): 217 js_path = Path("solstone/convey/static/chat_reasons.js") 218 text = js_path.read_text(encoding="utf-8") 219 js_reasons = _extract_frozen_object(text, "CHAT_REASONS") 220 js_display_names = _extract_frozen_object(text, "CHAT_REASON_DISPLAY_NAMES") 221 222 py_reasons = chat_reason_projection() 223 224 assert js_reasons == py_reasons 225 assert js_display_names == DISPLAY_NAMES 226 227 for code, reason in py_reasons.items(): 228 for provider, display in DISPLAY_NAMES.items(): 229 js_rendered = _render_js_chat_reason( 230 js_reasons, js_display_names, code, provider 231 ) 232 py_rendered = chat_view(code, provider) 233 assert js_rendered == py_rendered 234 if code == "unknown": 235 continue 236 expected = reason["template"].replace("{provider}", display) 237 assert py_rendered["message"] == expected 238 239 removed_constants = ("CHAT_" + "TROUBLE_REASON", "CHAT_" + "WATCHDOG_REASON") 240 assert all(name not in text for name in removed_constants) 241 242 243def test_no_hardcoded_chat_had_trouble_literal(): 244 roots = [Path("solstone/apps/chat"), Path("solstone/convey")] 245 excluded_names = {"provider_readiness.py", "chat_reasons.js"} 246 text_suffixes = {".css", ".html", ".js", ".json", ".md", ".py", ".txt"} 247 248 offenders = [] 249 for root in roots: 250 for path in root.rglob("*"): 251 if not path.is_file() or path.suffix not in text_suffixes: 252 continue 253 if path.name in excluded_names or "tests" in path.parts: 254 continue 255 if "chat had trouble" in path.read_text(encoding="utf-8"): 256 offenders.append(str(path)) 257 258 assert offenders == []