Inject an LLM response node into the ProvenanceAccumulator. The agent's closing message for the turn — what it *concluded*, as opposed to the tool-derived nodes that capture what it *did*. Sources, in priority order: 1. The stop payload, when the agent sends the response there (`last_assistant_message` for codex/grok, `prompt_response` for gemini-cli, `response` for plugins that supply it). 2. T
(
&mut self,
session_id: &str,
session: &AgentSession,
event: &TurnEvent,
)
| 264 | /// |
| 265 | /// Agents with neither source available get no node. |
| 266 | pub(crate) fn inject_response_node( |
| 267 | &mut self, |
| 268 | session_id: &str, |
| 269 | session: &AgentSession, |
| 270 | event: &TurnEvent, |
| 271 | ) { |
| 272 | let raw = event.raw_json.as_ref(); |
| 273 | let from_payload = |key: &str| -> Option<String> { |
| 274 | raw.and_then(|r| r.get(key)) |
| 275 | .and_then(|v| v.as_str()) |
| 276 | .map(|s| s.trim().to_string()) |
| 277 | .filter(|s| !s.is_empty()) |
| 278 | }; |
| 279 | |
| 280 | let response = from_payload("last_assistant_message") |
| 281 | .or_else(|| from_payload("prompt_response")) |
| 282 | .or_else(|| from_payload("response")) |
| 283 | .or_else(|| { |
| 284 | let path = session.transcript_path.as_ref()?; |
| 285 | let data = std::fs::read(path).ok()?; |
| 286 | transcript::last_assistant_text( |
| 287 | &data, |
| 288 | transcript::format_for_agent(&session.agent_name), |
| 289 | ) |
| 290 | }); |
| 291 | |
| 292 | let Some(response) = response else { |
| 293 | return; |
| 294 | }; |
| 295 | |
| 296 | let timestamp = event.timestamp.timestamp(); |
| 297 | self.with_accumulator(session_id, |acc| { |
| 298 | acc.append_llm_response(&response, timestamp); |
| 299 | true |
| 300 | }); |
| 301 | } |
| 302 | |
| 303 | /// OpenCode: recover transcript, reasoning, and response from |
| 304 | /// OpenCode's local SQLite store and fold them into the session and |
no test coverage detected