1111logger = get_contextual_logger (__name__ )
1212
1313EVENTS_FILENAME = "events.jsonl"
14+ _RUN_NOT_FOUND = "run not found"
1415
1516
1617def _runs_root () -> Path :
@@ -39,6 +40,28 @@ def _read_events_from_offset(
3940 return events , size
4041
4142
43+ def _apply_event_to_run_summary (
44+ summary : dict [str , Any ],
45+ et : str ,
46+ p : dict [str , Any ],
47+ ts : float ,
48+ flags : dict [str , bool ],
49+ ) -> None :
50+ if et == "tournament_started" :
51+ flags ["started" ] = True
52+ summary ["question" ] = p .get ("question" )
53+ summary ["model_count" ] = len (p .get ("models" , []))
54+ summary ["started_at" ] = ts
55+ elif et == "llm_response" :
56+ summary ["total_cost" ] += float (p .get ("cost" ) or 0.0 )
57+ elif et == "tournament_ended" :
58+ flags ["ended" ] = True
59+ summary ["champion" ] = p .get ("champion" )
60+ summary ["ended_at" ] = ts
61+ if p .get ("total_cost" ) is not None :
62+ summary ["total_cost" ] = float (p ["total_cost" ])
63+
64+
4265def _summarize_run (run_dir : Path ) -> dict [str , Any ]:
4366 events_path = run_dir / EVENTS_FILENAME
4467 summary : dict [str , Any ] = {
@@ -56,8 +79,7 @@ def _summarize_run(run_dir: Path) -> dict[str, Any]:
5679 summary ["status" ] = "missing"
5780 return summary
5881
59- started = False
60- ended = False
82+ flags = {"started" : False , "ended" : False }
6183 for raw in events_path .read_text (encoding = "utf-8" ).splitlines ():
6284 line = raw .strip ()
6385 if not line :
@@ -67,26 +89,17 @@ def _summarize_run(run_dir: Path) -> dict[str, Any]:
6789 except json .JSONDecodeError :
6890 continue
6991 summary ["event_count" ] += 1
70- et = event .get ("event_type" , "" )
71- p = event .get ("payload" , {})
72- ts = event .get ("ts" , 0 )
73- if et == "tournament_started" :
74- started = True
75- summary ["question" ] = p .get ("question" )
76- summary ["model_count" ] = len (p .get ("models" , []))
77- summary ["started_at" ] = ts
78- elif et == "llm_response" :
79- summary ["total_cost" ] += float (p .get ("cost" ) or 0.0 )
80- elif et == "tournament_ended" :
81- ended = True
82- summary ["champion" ] = p .get ("champion" )
83- summary ["ended_at" ] = ts
84- if p .get ("total_cost" ) is not None :
85- summary ["total_cost" ] = float (p ["total_cost" ])
86-
87- if ended :
92+ _apply_event_to_run_summary (
93+ summary ,
94+ event .get ("event_type" , "" ),
95+ event .get ("payload" , {}),
96+ event .get ("ts" , 0 ),
97+ flags ,
98+ )
99+
100+ if flags ["ended" ]:
88101 summary ["status" ] = "completed"
89- elif started :
102+ elif flags [ " started" ] :
90103 summary ["status" ] = "running"
91104 else :
92105 summary ["status" ] = "empty"
@@ -111,15 +124,15 @@ def get_run(request: web.Request) -> web.Response:
111124 run_id = request .match_info ["run_id" ]
112125 run_dir = _runs_root () / run_id
113126 if not run_dir .is_dir ():
114- return web .json_response ({"error" : "run not found" }, status = 404 )
127+ return web .json_response ({"error" : _RUN_NOT_FOUND }, status = 404 )
115128 return web .json_response (_summarize_run (run_dir ))
116129
117130
118- async def get_run_events (request : web .Request ) -> web .Response :
131+ def get_run_events (request : web .Request ) -> web .Response :
119132 run_id = request .match_info ["run_id" ]
120133 run_dir = _runs_root () / run_id
121134 if not run_dir .is_dir ():
122- return web .json_response ({"error" : "run not found" }, status = 404 )
135+ return web .json_response ({"error" : _RUN_NOT_FOUND }, status = 404 )
123136
124137 from_seq = int (request .query .get ("from_seq" , "0" ))
125138 limit = int (request .query .get ("limit" , "10000" ))
@@ -144,6 +157,26 @@ async def _replay_events(
144157 return last_seq , size
145158
146159
160+ async def _consume_ws_messages (ws : web .WebSocketResponse ) -> None :
161+ async for msg in ws :
162+ if msg .type == WSMsgType .ERROR :
163+ break
164+
165+
166+ async def _forward_new_events (
167+ ws : web .WebSocketResponse ,
168+ new_events : list [dict [str , Any ]],
169+ last_seq : int ,
170+ ) -> tuple [int , bool ]:
171+ for event in new_events :
172+ try :
173+ await ws .send_json ({"type" : "event" , "event" : event })
174+ last_seq = event .get ("seq" , last_seq )
175+ except ConnectionResetError :
176+ return last_seq , True
177+ return last_seq , False
178+
179+
147180async def _tail_events (
148181 ws : web .WebSocketResponse ,
149182 events_path : Path ,
@@ -154,12 +187,7 @@ async def _tail_events(
154187 idle_timeout = 600
155188 idle_seconds = 0.0
156189
157- async def consumer () -> None :
158- async for msg in ws :
159- if msg .type == WSMsgType .ERROR :
160- break
161-
162- consumer_task = asyncio .create_task (consumer ())
190+ consumer_task = asyncio .create_task (_consume_ws_messages (ws ))
163191 try :
164192 while not ws .closed :
165193 await asyncio .sleep (poll_interval )
@@ -178,12 +206,11 @@ async def consumer() -> None:
178206 new_events , last_size = _read_events_from_offset (
179207 events_path , last_seq
180208 )
181- for event in new_events :
182- try :
183- await ws .send_json ({"type" : "event" , "event" : event })
184- last_seq = event .get ("seq" , last_seq )
185- except ConnectionResetError :
186- break
209+ last_seq , disconnected = await _forward_new_events (
210+ ws , new_events , last_seq
211+ )
212+ if disconnected :
213+ break
187214 if any (
188215 e .get ("event_type" ) == "tournament_ended" for e in new_events
189216 ):
@@ -206,7 +233,7 @@ async def attach_run_websocket(
206233 await ws .prepare (request )
207234
208235 if not run_dir .is_dir ():
209- await ws .send_json ({"type" : "error" , "message" : "run not found" })
236+ await ws .send_json ({"type" : "error" , "message" : _RUN_NOT_FOUND })
210237 await ws .close ()
211238 return ws
212239
0 commit comments