(logPath: string)
| 253 | }; |
| 254 | |
| 255 | async function* iterOpenCodeEvents(logPath: string): AsyncIterable<TranscriptEvent> { |
| 256 | const parsed = parseLocator(logPath); |
| 257 | if (!parsed) return; |
| 258 | const db = openReadonlyDb(parsed.dbPath); |
| 259 | if (!db) return; |
| 260 | try { |
| 261 | if (!tableExists(db, 'message') || !tableExists(db, 'part')) return; |
| 262 | const rows = db |
| 263 | .prepare( |
| 264 | ` |
| 265 | SELECT |
| 266 | m.id AS message_id, |
| 267 | json_extract(m.data, '$.role') AS message_role, |
| 268 | json_extract(m.data, '$.agent') AS message_agent, |
| 269 | m.time_created AS message_time, |
| 270 | p.id AS part_id, |
| 271 | p.time_created AS part_time, |
| 272 | p.data AS part_data |
| 273 | FROM message m |
| 274 | JOIN part p ON p.message_id = m.id |
| 275 | WHERE m.session_id = ? |
| 276 | ORDER BY m.time_created ASC, p.time_created ASC, p.id ASC |
| 277 | `, |
| 278 | ) |
| 279 | .all(parsed.sessionId) as OpenCodePartRow[]; |
| 280 | |
| 281 | let lastMode: string | undefined; |
| 282 | let lastMessageId: string | undefined; |
| 283 | for (const row of rows) { |
| 284 | if (row.message_id !== lastMessageId) { |
| 285 | lastMessageId = row.message_id; |
| 286 | const mode = |
| 287 | typeof row.message_agent === 'string' && row.message_agent |
| 288 | ? row.message_agent |
| 289 | : undefined; |
| 290 | if (mode && mode !== lastMode) { |
| 291 | yield { ts: row.message_time, kind: 'mode_change', mode, prevMode: lastMode }; |
| 292 | lastMode = mode; |
| 293 | } |
| 294 | } |
| 295 | yield* extractOpenCodePart(row); |
| 296 | } |
| 297 | } finally { |
| 298 | db.close(); |
| 299 | } |
| 300 | } |
| 301 | |
| 302 | function* extractOpenCodePart(row: OpenCodePartRow): Generator<TranscriptEvent> { |
| 303 | const data = safeJsonParse(row.part_data); |
no test coverage detected