Parse a synthesized OpenCode JSONL transcript into condensed entries. The line shape is produced by `crate::transcript::opencode` from OpenCode's SQLite store: `user`/`assistant` lines carry `text`, `tool` lines carry the tool name and an optional title. `reasoning` lines are skipped — reasoning is carried separately by the change provenance and the session graph's Decision nodes.
(raw: &[u8])
| 98 | /// skipped — reasoning is carried separately by the change provenance and |
| 99 | /// the session graph's Decision nodes. |
| 100 | pub fn condense_opencode_transcript(raw: &[u8]) -> Vec<CondensedEntry> { |
| 101 | let mut entries = Vec::new(); |
| 102 | |
| 103 | for line in raw.split(|&b| b == b'\n') { |
| 104 | if line.is_empty() { |
| 105 | continue; |
| 106 | } |
| 107 | let Ok(parsed) = serde_json::from_slice::<serde_json::Value>(line) else { |
| 108 | continue; |
| 109 | }; |
| 110 | |
| 111 | match parsed.get("type").and_then(|v| v.as_str()) { |
| 112 | Some("user") | Some("assistant") => { |
| 113 | let Some(text) = parsed.get("text").and_then(|v| v.as_str()) else { |
| 114 | continue; |
| 115 | }; |
| 116 | if text.trim().is_empty() { |
| 117 | continue; |
| 118 | } |
| 119 | if parsed["type"] == "user" { |
| 120 | entries.push(CondensedEntry::user(text)); |
| 121 | } else { |
| 122 | entries.push(CondensedEntry::assistant(text)); |
| 123 | } |
| 124 | } |
| 125 | Some("tool") => { |
| 126 | let name = parsed |
| 127 | .get("tool") |
| 128 | .and_then(|v| v.as_str()) |
| 129 | .unwrap_or("tool"); |
| 130 | let title = parsed |
| 131 | .get("title") |
| 132 | .and_then(|v| v.as_str()) |
| 133 | .map(|s| s.to_string()); |
| 134 | entries.push(CondensedEntry::tool(name, title)); |
| 135 | } |
| 136 | _ => {} |
| 137 | } |
| 138 | } |
| 139 | |
| 140 | entries |
| 141 | } |
| 142 | |
| 143 | /// Extract the agent's final response text from a transcript. |
| 144 | /// |