personal memory agent
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 == []