Skip to content

Commit a6d3545

Browse files
committed
fix: reviews from the reviewer and ai
1 parent 838ff0f commit a6d3545

7 files changed

Lines changed: 235 additions & 117 deletions

File tree

api/search.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ def search():
159159
bid = parts[2]
160160
try:
161161
bubble = Bubble.from_dict(json.loads(row["value"]), bubble_id=bid)
162-
text = extract_text_from_bubble(bubble.raw)
162+
text = extract_text_from_bubble(bubble)
163163
bubble_map[bid] = {"text": text, "raw": bubble.raw}
164164
except SchemaError as e:
165165
# Drift logged so the operator can see why a chat dropped

models/conversation.py

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -246,8 +246,18 @@ def context(self) -> dict[str, Any]:
246246
return value
247247

248248
@property
249-
def token_count(self) -> Any | None:
250-
return self.raw.get("tokenCount")
249+
def token_count(self) -> dict[str, Any] | None:
250+
value = self.raw.get("tokenCount")
251+
if value is None:
252+
return None
253+
if not isinstance(value, dict):
254+
_logger.warning(
255+
"Schema drift in Bubble %s: invalid type for tokenCount (expected dict, got %s)",
256+
self.bubble_id,
257+
type(value).__name__,
258+
)
259+
return None
260+
return value
251261

252262
@property
253263
def tool_former_data(self) -> dict[str, Any] | None:
@@ -279,11 +289,23 @@ def model_info(self) -> dict[str, Any]:
279289

280290
@property
281291
def thinking(self) -> Any | None:
292+
if "thinking" not in self.raw:
293+
return None
282294
return self.raw.get("thinking")
283295

284296
@property
285-
def thinking_duration_ms(self) -> Any | None:
286-
return self.raw.get("thinkingDurationMs")
297+
def thinking_duration_ms(self) -> int | float | None:
298+
value = self.raw.get("thinkingDurationMs")
299+
if value is None:
300+
return None
301+
if isinstance(value, bool) or not isinstance(value, (int, float)):
302+
_logger.warning(
303+
"Schema drift in Bubble %s: invalid type for thinkingDurationMs (expected number, got %s)",
304+
self.bubble_id,
305+
type(value).__name__,
306+
)
307+
return None
308+
return value
287309

288310
@property
289311
def context_window_status_at_creation(self) -> dict[str, Any]:

models/raw_access.py

Lines changed: 118 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -32,23 +32,35 @@ def optional_raw_value(
3232
model: str,
3333
entity_id: str = "",
3434
expected_type: type[Any] | tuple[type[Any], ...] | None = None,
35+
warn_if_missing: bool = True,
3536
) -> Any | None:
3637
"""Return ``raw[key]`` when present and typed; log drift and return ``None`` otherwise."""
3738
if key not in raw:
38-
warn_missing_raw_key(raw, key, model=model, entity_id=entity_id)
39+
if warn_if_missing:
40+
warn_missing_raw_key(raw, key, model=model, entity_id=entity_id)
3941
return None
4042
value = raw[key]
41-
if expected_type is not None and not isinstance(value, expected_type):
42-
suffix = f" {entity_id}" if entity_id else ""
43-
_logger.warning(
44-
"Schema drift in %s%s: invalid type for %s (expected %s, got %s)",
45-
model,
46-
suffix,
47-
key,
48-
expected_type,
49-
type(value).__name__,
50-
)
51-
return None
43+
if expected_type is not None:
44+
if isinstance(value, bool) and expected_type in ((int, float), int, float):
45+
suffix = f" {entity_id}" if entity_id else ""
46+
_logger.warning(
47+
"Schema drift in %s%s: invalid type for %s (expected number, got bool)",
48+
model,
49+
suffix,
50+
key,
51+
)
52+
return None
53+
if not isinstance(value, expected_type):
54+
suffix = f" {entity_id}" if entity_id else ""
55+
_logger.warning(
56+
"Schema drift in %s%s: invalid type for %s (expected %s, got %s)",
57+
model,
58+
suffix,
59+
key,
60+
expected_type,
61+
type(value).__name__,
62+
)
63+
return None
5264
return value
5365

5466

@@ -58,13 +70,15 @@ def optional_raw_list(
5870
*,
5971
model: str,
6072
entity_id: str = "",
73+
warn_if_missing: bool = True,
6174
) -> list[Any] | None:
6275
return optional_raw_value(
6376
raw,
6477
key,
6578
model=model,
6679
entity_id=entity_id,
6780
expected_type=list,
81+
warn_if_missing=warn_if_missing,
6882
)
6983

7084

@@ -74,16 +88,66 @@ def optional_raw_dict(
7488
*,
7589
model: str,
7690
entity_id: str = "",
91+
warn_if_missing: bool = True,
7792
) -> dict[str, Any] | None:
7893
return optional_raw_value(
7994
raw,
8095
key,
8196
model=model,
8297
entity_id=entity_id,
8398
expected_type=dict,
99+
warn_if_missing=warn_if_missing,
84100
)
85101

86102

103+
def _optional_list_absent_ok(
104+
raw: dict[str, Any],
105+
key: str,
106+
*,
107+
model: str,
108+
entity_id: str = "",
109+
) -> list[Any]:
110+
"""List field often omitted by Cursor; warn only on wrong type."""
111+
value = raw.get(key)
112+
if value is None:
113+
return []
114+
if not isinstance(value, list):
115+
suffix = f" {entity_id}" if entity_id else ""
116+
_logger.warning(
117+
"Schema drift in %s%s: invalid type for %s (expected list, got %s)",
118+
model,
119+
suffix,
120+
key,
121+
type(value).__name__,
122+
)
123+
return []
124+
return value
125+
126+
127+
def _optional_dict_absent_ok(
128+
raw: dict[str, Any],
129+
key: str,
130+
*,
131+
model: str,
132+
entity_id: str = "",
133+
) -> dict[str, Any] | None:
134+
"""Dict field often omitted; warn only on wrong type; ``None`` when absent."""
135+
value = raw.get(key)
136+
if value is None:
137+
return None
138+
if not isinstance(value, dict):
139+
suffix = f" {entity_id}" if entity_id else ""
140+
_logger.warning(
141+
"Schema drift in %s%s: invalid type for %s (expected dict, got %s)",
142+
model,
143+
suffix,
144+
key,
145+
type(value).__name__,
146+
)
147+
return None
148+
return value
149+
150+
87151
def optional_raw_str(
88152
raw: dict[str, Any],
89153
key: str,
@@ -100,29 +164,28 @@ def optional_raw_str(
100164
)
101165

102166

103-
def optional_raw_number(
167+
def _optional_str_absent_ok(
104168
raw: dict[str, Any],
105169
key: str,
106170
*,
107171
model: str,
108172
entity_id: str = "",
109-
default: int | float = 0,
110-
) -> int | float:
111-
"""Numeric composer counters; warn on missing key, return *default* when absent."""
112-
if key not in raw:
113-
warn_missing_raw_key(raw, key, model=model, entity_id=entity_id)
114-
return default
115-
value = raw[key]
116-
if isinstance(value, bool) or not isinstance(value, (int, float)):
117-
suffix = f" {entity_id}" if entity_id else ""
118-
_logger.warning(
119-
"Schema drift in %s%s: invalid type for %s (expected number, got %s)",
120-
model,
121-
suffix,
122-
key,
123-
type(value).__name__,
124-
)
125-
return default
173+
) -> str | None:
174+
"""String field often omitted on headers; warn only on wrong type."""
175+
value = raw.get(key)
176+
if value is None:
177+
return None
178+
if not isinstance(value, str) or not value:
179+
if key in raw:
180+
suffix = f" {entity_id}" if entity_id else ""
181+
_logger.warning(
182+
"Schema drift in %s%s: invalid type for %s (expected non-empty str, got %s)",
183+
model,
184+
suffix,
185+
key,
186+
type(value).__name__,
187+
)
188+
return None
126189
return value
127190

128191

@@ -132,13 +195,12 @@ def conversation_header_bubble_id(
132195
composer_id: str = "",
133196
) -> str | None:
134197
"""``bubbleId`` from a ``fullConversationHeadersOnly`` entry."""
135-
value = optional_raw_str(
198+
return _optional_str_absent_ok(
136199
header,
137200
"bubbleId",
138201
model="ConversationHeader",
139202
entity_id=composer_id,
140203
)
141-
return value if value else None
142204

143205

144206
def message_request_context_project_layouts(
@@ -152,6 +214,7 @@ def message_request_context_project_layouts(
152214
"projectLayouts",
153215
model="MessageRequestContext",
154216
entity_id=composer_id,
217+
warn_if_missing=False,
155218
)
156219

157220

@@ -165,44 +228,41 @@ def composer_headers(
165228
return data.full_conversation_headers_only
166229
if not isinstance(data, dict):
167230
return []
168-
value = data.get("fullConversationHeadersOnly")
169-
if value is None:
170-
return []
171-
if not isinstance(value, list):
172-
_logger.warning(
173-
"Schema drift in Composer %s: invalid type for fullConversationHeadersOnly (expected list, got %s)",
174-
composer_id,
175-
type(value).__name__,
176-
)
177-
return []
178-
return value
231+
return _optional_list_absent_ok(
232+
data,
233+
"fullConversationHeadersOnly",
234+
model="Composer",
235+
entity_id=composer_id,
236+
)
179237

180238

181239
def composer_newly_created_files(data: Any, composer_id: str) -> list[Any]:
182240
from models.conversation import Composer
183241

184242
if isinstance(data, Composer):
185243
return data.newly_created_files
186-
value = data.get("newlyCreatedFiles") if isinstance(data, dict) else None
187-
if value is None:
188-
return []
189-
if not isinstance(value, list):
190-
_logger.warning(
191-
"Schema drift in Composer %s: invalid type for newlyCreatedFiles (expected list, got %s)",
192-
composer_id,
193-
type(value).__name__,
194-
)
244+
if not isinstance(data, dict):
195245
return []
196-
return value
246+
return _optional_list_absent_ok(
247+
data,
248+
"newlyCreatedFiles",
249+
model="Composer",
250+
entity_id=composer_id,
251+
)
197252

198253

199254
def composer_code_block_data(data: Any, composer_id: str) -> dict[str, Any] | None:
200255
from models.conversation import Composer
201256

202257
if isinstance(data, Composer):
203258
return data.code_block_data
204-
return optional_raw_dict(
205-
data, "codeBlockData", model="Composer", entity_id=composer_id
259+
if not isinstance(data, dict):
260+
return None
261+
return _optional_dict_absent_ok(
262+
data,
263+
"codeBlockData",
264+
model="Composer",
265+
entity_id=composer_id,
206266
)
207267

208268

@@ -212,13 +272,12 @@ def bubble_relevant_files(bubble: Any, bubble_id: str = "") -> list[Any]:
212272
if isinstance(bubble, Bubble):
213273
return bubble.relevant_files
214274
if isinstance(bubble, dict):
215-
files = optional_raw_list(
275+
return _optional_list_absent_ok(
216276
bubble,
217277
"relevantFiles",
218278
model="Bubble",
219279
entity_id=bubble_id,
220280
)
221-
return files if files is not None else []
222281
return []
223282

224283

@@ -228,13 +287,12 @@ def bubble_attached_file_uris(bubble: Any, bubble_id: str = "") -> list[Any]:
228287
if isinstance(bubble, Bubble):
229288
return bubble.attached_file_code_chunks_uris
230289
if isinstance(bubble, dict):
231-
uris = optional_raw_list(
290+
return _optional_list_absent_ok(
232291
bubble,
233292
"attachedFileCodeChunksUris",
234293
model="Bubble",
235294
entity_id=bubble_id,
236295
)
237-
return uris if uris is not None else []
238296
return []
239297

240298

@@ -244,7 +302,7 @@ def bubble_context(bubble: Any, bubble_id: str = "") -> dict[str, Any]:
244302
if isinstance(bubble, Bubble):
245303
return bubble.context
246304
if isinstance(bubble, dict):
247-
ctx = optional_raw_dict(
305+
ctx = _optional_dict_absent_ok(
248306
bubble,
249307
"context",
250308
model="Bubble",

0 commit comments

Comments
 (0)