Convert opencode transcripts to otel (or agent) traces
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Parse agent and model separately from assistant header

- Added agent field to LogMessage and ParsedLogEntry
- Updated parser to split 'Agent · Model' header into two separate fields
- Agent now goes to 'Build' (or other agent name)
- Model now correctly captures 'glm-4.7' (or other model names)
- Added opencode.agent attribute to formatter and exporter
- Updated table format to show Agent and Model as separate columns
- gen_ai.model.name now correctly maps to actual model, not agent

rektide d3bb3cef d9a8f640

+29 -10
+4
src/exporter.rs
··· 113 113 KeyValue::new("jsonrpc.protocol.version", "2.0"), 114 114 ]; 115 115 116 + if let Some(agent) = &entry.agent { 117 + attributes.push(KeyValue::new("opencode.agent", agent.clone())); 118 + } 119 + 116 120 if let Some(model) = &entry.model { 117 121 attributes.push(KeyValue::new("gen_ai.model.name", model.clone())); 118 122 }
+16 -2
src/formatter.rs
··· 8 8 pub kind: String, 9 9 pub session_id: String, 10 10 pub role: String, 11 + pub agent: Option<String>, 11 12 pub model: Option<String>, 12 13 pub tool_name: Option<String>, 13 14 pub mcp_method: Option<String>, ··· 26 27 ("network.transport".to_string(), "tcp".to_string()), 27 28 ("jsonrpc.protocol.version".to_string(), "2.0".to_string()), 28 29 ]; 30 + 31 + if let Some(agent) = &entry.agent { 32 + attributes.push(("opencode.agent".to_string(), agent.clone())); 33 + } 29 34 30 35 if let Some(model) = &entry.model { 31 36 attributes.push(("gen_ai.model.name".to_string(), model.clone())); ··· 62 67 kind: "client".to_string(), 63 68 session_id: entry.session_id.clone(), 64 69 role: entry.role.clone(), 70 + agent: entry.agent.clone(), 65 71 model: entry.model.clone(), 66 72 tool_name: entry.tool_name.clone(), 67 73 mcp_method: entry.mcp_method.clone(), ··· 111 117 112 118 let mut table = Table::new(); 113 119 table 114 - .set_header(vec!["Span Name", "Role", "Tool", "Method", "Model"]) 120 + .set_header(vec![ 121 + "Span Name", 122 + "Role", 123 + "Agent", 124 + "Model", 125 + "Tool", 126 + "Method", 127 + ]) 115 128 .load_preset(comfy_table::presets::UTF8_FULL); 116 129 117 130 for span in spans { 118 131 table.add_row(vec![ 119 132 Cell::new(&span.name).fg(Color::Blue), 120 133 Cell::new(&span.role).fg(Color::Green), 134 + Cell::new(span.agent.as_deref().unwrap_or("-")).fg(Color::Yellow), 135 + Cell::new(span.model.as_deref().unwrap_or("-")).fg(Color::Cyan), 121 136 Cell::new(span.tool_name.as_deref().unwrap_or("-")), 122 137 Cell::new(span.mcp_method.as_deref().unwrap_or("-")), 123 - Cell::new(span.model.as_deref().unwrap_or("-")), 124 138 ]); 125 139 } 126 140
+9 -8
src/parser.rs
··· 17 17 #[serde(rename = "type")] 18 18 pub msg_type: String, 19 19 pub role: String, 20 + pub agent: Option<String>, 20 21 pub model: Option<String>, 21 22 pub timestamp: Option<Timestamp>, 22 23 pub content: String, ··· 37 38 pub struct ParsedLogEntry { 38 39 pub session_id: String, 39 40 pub role: String, 41 + pub agent: Option<String>, 40 42 pub model: Option<String>, 41 43 pub timestamp: Timestamp, 42 44 pub tool_name: Option<String>, ··· 90 92 let line = lines[i]; 91 93 92 94 if let Some(caps) = self.assistant_regex.captures(line) { 95 + let parts: Vec<&str> = caps[1].split("·").map(|s| s.trim()).collect(); 96 + let agent = parts.get(0).map(|s| s.to_string()); 97 + let model = parts.get(1).map(|s| s.to_string()); 98 + 93 99 let mut message = LogMessage { 94 100 msg_type: "assistant".to_string(), 95 101 role: "assistant".to_string(), 96 - model: Some( 97 - caps[1] 98 - .split("·") 99 - .next() 100 - .unwrap_or("unknown") 101 - .trim() 102 - .to_string(), 103 - ), 102 + agent, 103 + model, 104 104 timestamp: None, 105 105 content: String::new(), 106 106 thinking: None, ··· 181 181 entries.push(ParsedLogEntry { 182 182 session_id: log.session_id.clone(), 183 183 role: msg.role.clone(), 184 + agent: msg.agent.clone(), 184 185 model: msg.model.clone(), 185 186 timestamp: msg.timestamp.unwrap_or_else(|| Timestamp::now()), 186 187 tool_name,