Skip to main content

objectiveai_cli/db/logs/
row.rs

1//! Row-shape types: [`RowTable`] / [`MessageTable`] / [`RowValue`] /
2//! [`RowKey`] / [`OwnedRowKey`] / [`RowBody`].
3//!
4//! - [`RowValue<'a>`] — what the chunk-row iterators yield. Borrowed
5//!   sum type, one variant per streaming-content table. Every variant
6//!   carries `response_id` (the enclosing agent_completion_chunk's id)
7//!   AND `agent_instance_hierarchy` (the enclosing chunk's spawned
8//!   agent id) so the writer can address `objectiveai.messages` /
9//!   `objectiveai.messages_queue` without going back to the chunk.
10//! - [`RowKey<'a>`] — what [`RowValue::key`] returns. Borrowed key
11//!   used for shadow-map lookups; no allocation.
12//! - [`OwnedRowKey`] — same variants as `RowKey`, but with `String`
13//!   instead of `&str`. Only built on insert (cold path).
14//! - [`RowBody`] — owned body sum, one variant per table. Used by the
15//!   shadow to store the last-written body so the next tick can
16//!   [`RowValue::body_eq`] against it without re-hashing or
17//!   re-serializing.
18//!
19//! Hash invariant: `RowKey<'a>` and `OwnedRowKey` MUST produce the
20//! same `u64` hash for the same logical key. Since `&str` and `String`
21//! hash identically and the variant tag is determined by source
22//! position (matching variants are at matching positions in the two
23//! enums), `derive(Hash)` is sufficient — no manual impl needed.
24
25use objectiveai_sdk::agent::completions::message::{File, ImageUrl, InputAudio, VideoUrl};
26
27/// Every table in the `logs.*` schema, plus the synthetic
28/// `MessageQueueContent` variant for queue-consumption rows.
29/// The latter writes to `objectiveai.messages` with a `"table"` value
30/// chosen at write time via SQL CASE (one of `message_queue_text`,
31/// `_image`, `_audio`, `_video`, `_file`) — the Rust-side variant
32/// is kind-less because the dispatch happens in SQL.
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
34pub enum RowTable {
35    AgentCompletionRequests,
36    AgentCompletionResponses,
37    VectorCompletionRequests,
38    VectorCompletionResponses,
39    FunctionExecutionRequests,
40    FunctionExecutionResponses,
41
42    /// Synthetic — kind-less at the Rust level. The writer's
43    /// helper picks the per-kind `objectiveai.message_table` enum value
44    /// via SQL CASE against `message_queue_contents.kind` and
45    /// flips the parent `message_queue.active = FALSE` in the
46    /// same statement.
47    MessageQueueContent,
48
49    ToolResponse,
50
51    AssistantResponseRefusal,
52    AssistantResponseReasoning,
53    AssistantResponseToolCalls,
54
55    AssistantResponseContentText,
56    AssistantResponseContentImage,
57    AssistantResponseContentAudio,
58    AssistantResponseContentVideo,
59    AssistantResponseContentFile,
60
61    ToolResponseContentText,
62    ToolResponseContentImage,
63    ToolResponseContentAudio,
64    ToolResponseContentVideo,
65    ToolResponseContentFile,
66
67    // ---- request_message: user content ----
68    RequestMessageUserContentText,
69    RequestMessageUserContentImage,
70    RequestMessageUserContentAudio,
71    RequestMessageUserContentVideo,
72    RequestMessageUserContentFile,
73
74    // ---- request_message: assistant ----
75    RequestMessageAssistantRefusal,
76    RequestMessageAssistantReasoning,
77    RequestMessageAssistantToolCalls,
78    RequestMessageAssistantContentText,
79    RequestMessageAssistantContentImage,
80    RequestMessageAssistantContentAudio,
81    RequestMessageAssistantContentVideo,
82    RequestMessageAssistantContentFile,
83
84    // ---- request_message: tool ----
85    /// Head row (JOIN target for `tool_call_id`) — no messages event,
86    /// like [`RowTable::ToolResponse`].
87    RequestMessageTool,
88    RequestMessageToolContentText,
89    RequestMessageToolContentImage,
90    RequestMessageToolContentAudio,
91    RequestMessageToolContentVideo,
92    RequestMessageToolContentFile,
93
94    // ---- vector request choices ----
95    /// Head row carrying the per-agent inline `key` per choice — no
96    /// messages event, JOIN target for the key.
97    RequestVectorChoice,
98    RequestVectorChoiceContentText,
99    RequestVectorChoiceContentImage,
100    RequestVectorChoiceContentAudio,
101    RequestVectorChoiceContentVideo,
102    RequestVectorChoiceContentFile,
103
104    // ---- vector response vote (inline closer) ----
105    ResponseVectorVote,
106
107    /// Logged failures (`objectiveai.errors`). Written by the dedicated
108    /// error helper (`db::logs::insert_error`), never by the
109    /// chunk-driven writer — no [`RowValue`] variant exists for it.
110    Error,
111}
112
113/// The subset of [`RowTable`] that produces a `objectiveai.messages` event
114/// row when written. Maps 1:1 to the postgres `objectiveai.message_table`
115/// ENUM in `schema.sql` — same names, same order. The three
116/// response-blob tables are intentionally absent; they're not events,
117/// just the latest snapshot. `tool_response` is also absent: its head
118/// row is written purely as the `tool_call_id` lookup for tool-response
119/// content rows (JOINed at read time) and emits no event of its own.
120#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, sqlx::Type)]
121#[sqlx(type_name = "objectiveai.message_table", rename_all = "snake_case")]
122pub enum MessageTable {
123    AgentCompletionRequest,
124    VectorCompletionRequest,
125    FunctionExecutionRequest,
126    MessageQueueText,
127    MessageQueueImage,
128    MessageQueueAudio,
129    MessageQueueVideo,
130    MessageQueueFile,
131    AssistantResponseRefusal,
132    AssistantResponseReasoning,
133    AssistantResponseToolCalls,
134    AssistantResponseContentText,
135    AssistantResponseContentImage,
136    AssistantResponseContentAudio,
137    AssistantResponseContentVideo,
138    AssistantResponseContentFile,
139    ToolResponseContentText,
140    ToolResponseContentImage,
141    ToolResponseContentAudio,
142    ToolResponseContentVideo,
143    ToolResponseContentFile,
144    RequestMessageUserContentText,
145    RequestMessageUserContentImage,
146    RequestMessageUserContentAudio,
147    RequestMessageUserContentVideo,
148    RequestMessageUserContentFile,
149    RequestMessageAssistantRefusal,
150    RequestMessageAssistantReasoning,
151    RequestMessageAssistantToolCalls,
152    RequestMessageAssistantContentText,
153    RequestMessageAssistantContentImage,
154    RequestMessageAssistantContentAudio,
155    RequestMessageAssistantContentVideo,
156    RequestMessageAssistantContentFile,
157    RequestMessageToolContentText,
158    RequestMessageToolContentImage,
159    RequestMessageToolContentAudio,
160    RequestMessageToolContentVideo,
161    RequestMessageToolContentFile,
162    RequestVectorChoiceContentText,
163    RequestVectorChoiceContentImage,
164    RequestVectorChoiceContentAudio,
165    RequestVectorChoiceContentVideo,
166    RequestVectorChoiceContentFile,
167    ResponseVectorVote,
168    Error,
169}
170
171impl MessageTable {
172    /// snake_case schema name — matches the postgres enum value
173    /// declared in `schema.sql`. Used to build `IN (...)` filter
174    /// clauses inline since sqlx doesn't auto-derive
175    /// `PgHasArrayType` for custom enum types.
176    pub fn schema_name(self) -> &'static str {
177        match self {
178            MessageTable::AgentCompletionRequest => "agent_completion_request",
179            MessageTable::VectorCompletionRequest => "vector_completion_request",
180            MessageTable::FunctionExecutionRequest => "function_execution_request",
181            MessageTable::MessageQueueText => "message_queue_text",
182            MessageTable::MessageQueueImage => "message_queue_image",
183            MessageTable::MessageQueueAudio => "message_queue_audio",
184            MessageTable::MessageQueueVideo => "message_queue_video",
185            MessageTable::MessageQueueFile => "message_queue_file",
186            MessageTable::AssistantResponseRefusal => "assistant_response_refusal",
187            MessageTable::AssistantResponseReasoning => "assistant_response_reasoning",
188            MessageTable::AssistantResponseToolCalls => "assistant_response_tool_calls",
189            MessageTable::AssistantResponseContentText => "assistant_response_content_text",
190            MessageTable::AssistantResponseContentImage => "assistant_response_content_image",
191            MessageTable::AssistantResponseContentAudio => "assistant_response_content_audio",
192            MessageTable::AssistantResponseContentVideo => "assistant_response_content_video",
193            MessageTable::AssistantResponseContentFile => "assistant_response_content_file",
194            MessageTable::ToolResponseContentText => "tool_response_content_text",
195            MessageTable::ToolResponseContentImage => "tool_response_content_image",
196            MessageTable::ToolResponseContentAudio => "tool_response_content_audio",
197            MessageTable::ToolResponseContentVideo => "tool_response_content_video",
198            MessageTable::ToolResponseContentFile => "tool_response_content_file",
199            MessageTable::RequestMessageUserContentText => "request_message_user_content_text",
200            MessageTable::RequestMessageUserContentImage => "request_message_user_content_image",
201            MessageTable::RequestMessageUserContentAudio => "request_message_user_content_audio",
202            MessageTable::RequestMessageUserContentVideo => "request_message_user_content_video",
203            MessageTable::RequestMessageUserContentFile => "request_message_user_content_file",
204            MessageTable::RequestMessageAssistantRefusal => "request_message_assistant_refusal",
205            MessageTable::RequestMessageAssistantReasoning => "request_message_assistant_reasoning",
206            MessageTable::RequestMessageAssistantToolCalls => "request_message_assistant_tool_calls",
207            MessageTable::RequestMessageAssistantContentText => "request_message_assistant_content_text",
208            MessageTable::RequestMessageAssistantContentImage => "request_message_assistant_content_image",
209            MessageTable::RequestMessageAssistantContentAudio => "request_message_assistant_content_audio",
210            MessageTable::RequestMessageAssistantContentVideo => "request_message_assistant_content_video",
211            MessageTable::RequestMessageAssistantContentFile => "request_message_assistant_content_file",
212            MessageTable::RequestMessageToolContentText => "request_message_tool_content_text",
213            MessageTable::RequestMessageToolContentImage => "request_message_tool_content_image",
214            MessageTable::RequestMessageToolContentAudio => "request_message_tool_content_audio",
215            MessageTable::RequestMessageToolContentVideo => "request_message_tool_content_video",
216            MessageTable::RequestMessageToolContentFile => "request_message_tool_content_file",
217            MessageTable::RequestVectorChoiceContentText => "request_vector_choice_content_text",
218            MessageTable::RequestVectorChoiceContentImage => "request_vector_choice_content_image",
219            MessageTable::RequestVectorChoiceContentAudio => "request_vector_choice_content_audio",
220            MessageTable::RequestVectorChoiceContentVideo => "request_vector_choice_content_video",
221            MessageTable::RequestVectorChoiceContentFile => "request_vector_choice_content_file",
222            MessageTable::ResponseVectorVote => "response_vector_vote",
223            MessageTable::Error => "error",
224        }
225    }
226}
227
228impl RowTable {
229    /// The [`MessageTable`] for this table's events. Returns `None` for
230    /// the three response-blob tables (which don't emit messages).
231    pub fn message_table(self) -> Option<MessageTable> {
232        Some(match self {
233            RowTable::AgentCompletionRequests => MessageTable::AgentCompletionRequest,
234            RowTable::VectorCompletionRequests => MessageTable::VectorCompletionRequest,
235            RowTable::FunctionExecutionRequests => MessageTable::FunctionExecutionRequest,
236            // MessageQueueContent's table value is resolved at
237            // write time via SQL CASE — the standard
238            // `message_table()` path can't pick from the 5
239            // per-kind variants without the kind. Callers writing
240            // these rows skip this helper.
241            RowTable::MessageQueueContent => return None,
242            // The tool-response head row is written to
243            // `objectiveai.tool_response` purely as the `tool_call_id`
244            // lookup for its content rows (JOINed at read time). It
245            // emits no `messages` event, so it's never addressable by
246            // `agents logs read id` and never appears as its own part
247            // in `read all`. write.rs MUST early-branch this variant
248            // before calling `RowValue::message_table()` (which
249            // `.expect()`s a Some) — see `insert_value`/`update_value`.
250            RowTable::ToolResponse => return None,
251            RowTable::AssistantResponseRefusal => MessageTable::AssistantResponseRefusal,
252            RowTable::AssistantResponseReasoning => MessageTable::AssistantResponseReasoning,
253            RowTable::AssistantResponseToolCalls => MessageTable::AssistantResponseToolCalls,
254            RowTable::AssistantResponseContentText => MessageTable::AssistantResponseContentText,
255            RowTable::AssistantResponseContentImage => MessageTable::AssistantResponseContentImage,
256            RowTable::AssistantResponseContentAudio => MessageTable::AssistantResponseContentAudio,
257            RowTable::AssistantResponseContentVideo => MessageTable::AssistantResponseContentVideo,
258            RowTable::AssistantResponseContentFile => MessageTable::AssistantResponseContentFile,
259            RowTable::ToolResponseContentText => MessageTable::ToolResponseContentText,
260            RowTable::ToolResponseContentImage => MessageTable::ToolResponseContentImage,
261            RowTable::ToolResponseContentAudio => MessageTable::ToolResponseContentAudio,
262            RowTable::ToolResponseContentVideo => MessageTable::ToolResponseContentVideo,
263            RowTable::ToolResponseContentFile => MessageTable::ToolResponseContentFile,
264            RowTable::RequestMessageUserContentText => MessageTable::RequestMessageUserContentText,
265            RowTable::RequestMessageUserContentImage => MessageTable::RequestMessageUserContentImage,
266            RowTable::RequestMessageUserContentAudio => MessageTable::RequestMessageUserContentAudio,
267            RowTable::RequestMessageUserContentVideo => MessageTable::RequestMessageUserContentVideo,
268            RowTable::RequestMessageUserContentFile => MessageTable::RequestMessageUserContentFile,
269            RowTable::RequestMessageAssistantRefusal => MessageTable::RequestMessageAssistantRefusal,
270            RowTable::RequestMessageAssistantReasoning => MessageTable::RequestMessageAssistantReasoning,
271            RowTable::RequestMessageAssistantToolCalls => MessageTable::RequestMessageAssistantToolCalls,
272            RowTable::RequestMessageAssistantContentText => MessageTable::RequestMessageAssistantContentText,
273            RowTable::RequestMessageAssistantContentImage => MessageTable::RequestMessageAssistantContentImage,
274            RowTable::RequestMessageAssistantContentAudio => MessageTable::RequestMessageAssistantContentAudio,
275            RowTable::RequestMessageAssistantContentVideo => MessageTable::RequestMessageAssistantContentVideo,
276            RowTable::RequestMessageAssistantContentFile => MessageTable::RequestMessageAssistantContentFile,
277            // Head row — no messages event (like `ToolResponse`).
278            RowTable::RequestMessageTool => return None,
279            RowTable::RequestMessageToolContentText => MessageTable::RequestMessageToolContentText,
280            RowTable::RequestMessageToolContentImage => MessageTable::RequestMessageToolContentImage,
281            RowTable::RequestMessageToolContentAudio => MessageTable::RequestMessageToolContentAudio,
282            RowTable::RequestMessageToolContentVideo => MessageTable::RequestMessageToolContentVideo,
283            RowTable::RequestMessageToolContentFile => MessageTable::RequestMessageToolContentFile,
284            // Head row — no messages event; JOIN target for the key.
285            RowTable::RequestVectorChoice => return None,
286            RowTable::RequestVectorChoiceContentText => MessageTable::RequestVectorChoiceContentText,
287            RowTable::RequestVectorChoiceContentImage => MessageTable::RequestVectorChoiceContentImage,
288            RowTable::RequestVectorChoiceContentAudio => MessageTable::RequestVectorChoiceContentAudio,
289            RowTable::RequestVectorChoiceContentVideo => MessageTable::RequestVectorChoiceContentVideo,
290            RowTable::RequestVectorChoiceContentFile => MessageTable::RequestVectorChoiceContentFile,
291            RowTable::ResponseVectorVote => MessageTable::ResponseVectorVote,
292            RowTable::Error => MessageTable::Error,
293            RowTable::AgentCompletionResponses
294            | RowTable::VectorCompletionResponses
295            | RowTable::FunctionExecutionResponses => return None,
296        })
297    }
298
299    pub fn fq_name(self) -> &'static str {
300        match self {
301            RowTable::AgentCompletionRequests => "objectiveai.agent_completion_requests",
302            RowTable::AgentCompletionResponses => "objectiveai.agent_completion_responses",
303            RowTable::VectorCompletionRequests => "objectiveai.vector_completion_requests",
304            RowTable::VectorCompletionResponses => "objectiveai.vector_completion_responses",
305            RowTable::FunctionExecutionRequests => "objectiveai.function_execution_requests",
306            RowTable::FunctionExecutionResponses => "objectiveai.function_execution_responses",
307            // Synthetic — kind chosen at write time via SQL CASE.
308            // No per-kind table name surfaces through fq_name();
309            // the writer's helper builds its SQL inline.
310            RowTable::MessageQueueContent => "message_queue_contents",
311            RowTable::ToolResponse => "objectiveai.tool_response",
312            RowTable::AssistantResponseRefusal => "objectiveai.assistant_response_refusal",
313            RowTable::AssistantResponseReasoning => "objectiveai.assistant_response_reasoning",
314            RowTable::AssistantResponseToolCalls => "objectiveai.assistant_response_tool_calls",
315            RowTable::AssistantResponseContentText => "objectiveai.assistant_response_content_text",
316            RowTable::AssistantResponseContentImage => "objectiveai.assistant_response_content_image",
317            RowTable::AssistantResponseContentAudio => "objectiveai.assistant_response_content_audio",
318            RowTable::AssistantResponseContentVideo => "objectiveai.assistant_response_content_video",
319            RowTable::AssistantResponseContentFile => "objectiveai.assistant_response_content_file",
320            RowTable::ToolResponseContentText => "objectiveai.tool_response_content_text",
321            RowTable::ToolResponseContentImage => "objectiveai.tool_response_content_image",
322            RowTable::ToolResponseContentAudio => "objectiveai.tool_response_content_audio",
323            RowTable::ToolResponseContentVideo => "objectiveai.tool_response_content_video",
324            RowTable::ToolResponseContentFile => "objectiveai.tool_response_content_file",
325            RowTable::RequestMessageUserContentText => "objectiveai.request_message_user_content_text",
326            RowTable::RequestMessageUserContentImage => "objectiveai.request_message_user_content_image",
327            RowTable::RequestMessageUserContentAudio => "objectiveai.request_message_user_content_audio",
328            RowTable::RequestMessageUserContentVideo => "objectiveai.request_message_user_content_video",
329            RowTable::RequestMessageUserContentFile => "objectiveai.request_message_user_content_file",
330            RowTable::RequestMessageAssistantRefusal => "objectiveai.request_message_assistant_refusal",
331            RowTable::RequestMessageAssistantReasoning => "objectiveai.request_message_assistant_reasoning",
332            RowTable::RequestMessageAssistantToolCalls => "objectiveai.request_message_assistant_tool_calls",
333            RowTable::RequestMessageAssistantContentText => "objectiveai.request_message_assistant_content_text",
334            RowTable::RequestMessageAssistantContentImage => "objectiveai.request_message_assistant_content_image",
335            RowTable::RequestMessageAssistantContentAudio => "objectiveai.request_message_assistant_content_audio",
336            RowTable::RequestMessageAssistantContentVideo => "objectiveai.request_message_assistant_content_video",
337            RowTable::RequestMessageAssistantContentFile => "objectiveai.request_message_assistant_content_file",
338            RowTable::RequestMessageTool => "objectiveai.request_message_tool",
339            RowTable::RequestMessageToolContentText => "objectiveai.request_message_tool_content_text",
340            RowTable::RequestMessageToolContentImage => "objectiveai.request_message_tool_content_image",
341            RowTable::RequestMessageToolContentAudio => "objectiveai.request_message_tool_content_audio",
342            RowTable::RequestMessageToolContentVideo => "objectiveai.request_message_tool_content_video",
343            RowTable::RequestMessageToolContentFile => "objectiveai.request_message_tool_content_file",
344            RowTable::RequestVectorChoice => "objectiveai.request_vector_choice",
345            RowTable::RequestVectorChoiceContentText => "objectiveai.request_vector_choice_content_text",
346            RowTable::RequestVectorChoiceContentImage => "objectiveai.request_vector_choice_content_image",
347            RowTable::RequestVectorChoiceContentAudio => "objectiveai.request_vector_choice_content_audio",
348            RowTable::RequestVectorChoiceContentVideo => "objectiveai.request_vector_choice_content_video",
349            RowTable::RequestVectorChoiceContentFile => "objectiveai.request_vector_choice_content_file",
350            RowTable::ResponseVectorVote => "objectiveai.response_vector_vote",
351            RowTable::Error => "objectiveai.errors",
352        }
353    }
354}
355
356/// One streaming-content row to INSERT or UPDATE. Borrowed: every
357/// variant lifts string / media payloads from the owning chunk by
358/// reference. Every variant also carries the enclosing chunk's
359/// `agent_instance_hierarchy` so the writer can populate
360/// `objectiveai.messages.agent_instance_hierarchy` and key the
361/// `objectiveai.messages_queue` downgrade against the right spawned agent.
362#[derive(Debug, Clone)]
363pub enum RowValue<'a> {
364    /// Consumption signal: the API stamped this
365    /// `message_queue_contents.id` onto the chunk's
366    /// `request_message_ids` field. The writer's helper:
367    /// 1) resolves the row's kind via SQL CASE against
368    ///    `message_queue_contents.kind`,
369    /// 2) `UPDATE objectiveai.message_queue SET active=FALSE WHERE id =
370    ///    (SELECT message_queue_id FROM objectiveai.message_queue_contents
371    ///     WHERE id = $content_id) AND active=TRUE`, and
372    /// 3) `INSERT objectiveai.messages` with `"table"` picked from the
373    ///    five `message_queue_*` enum values matching the kind
374    ///    and `row_index = content_id`, so the read path
375    ///    dispatches directly to the right per-kind table.
376    ///
377    /// Multiple content_ids sharing one parent fire the flip
378    /// once; the rest are no-ops via the `AND active=TRUE` guard.
379    ///
380    /// Iterators yield these AHEAD of the per-message content
381    /// rows so the log chronicles consumption before the body
382    /// the agent produced.
383    MessageQueueContent {
384        response_id: &'a str,
385        agent_instance_hierarchy: &'a str,
386        message_queue_content_id: i64,
387    },
388    ToolResponse {
389        response_id: &'a str,
390        agent_instance_hierarchy: &'a str,
391        index: u64,
392        tool_call_id: &'a str,
393    },
394    AssistantResponseRefusal {
395        response_id: &'a str,
396        agent_instance_hierarchy: &'a str,
397        index: u64,
398        text: &'a str,
399    },
400    AssistantResponseReasoning {
401        response_id: &'a str,
402        agent_instance_hierarchy: &'a str,
403        index: u64,
404        text: &'a str,
405    },
406    AssistantResponseToolCalls {
407        response_id: &'a str,
408        agent_instance_hierarchy: &'a str,
409        index: u64,
410        tool_call_index: u64,
411        tool_call_id: &'a str,
412        function_name: &'a str,
413        arguments: &'a str,
414    },
415
416    AssistantResponseContentText {
417        response_id: &'a str,
418        agent_instance_hierarchy: &'a str,
419        index: u64,
420        part_index: u64,
421        text: &'a str,
422    },
423    AssistantResponseContentImage {
424        response_id: &'a str,
425        agent_instance_hierarchy: &'a str,
426        index: u64,
427        part_index: u64,
428        image_url: &'a ImageUrl,
429    },
430    AssistantResponseContentAudio {
431        response_id: &'a str,
432        agent_instance_hierarchy: &'a str,
433        index: u64,
434        part_index: u64,
435        input_audio: &'a InputAudio,
436    },
437    AssistantResponseContentVideo {
438        response_id: &'a str,
439        agent_instance_hierarchy: &'a str,
440        index: u64,
441        part_index: u64,
442        video_url: &'a VideoUrl,
443    },
444    AssistantResponseContentFile {
445        response_id: &'a str,
446        agent_instance_hierarchy: &'a str,
447        index: u64,
448        part_index: u64,
449        file: &'a File,
450    },
451
452    ToolResponseContentText {
453        response_id: &'a str,
454        agent_instance_hierarchy: &'a str,
455        index: u64,
456        part_index: u64,
457        text: &'a str,
458    },
459    ToolResponseContentImage {
460        response_id: &'a str,
461        agent_instance_hierarchy: &'a str,
462        index: u64,
463        part_index: u64,
464        image_url: &'a ImageUrl,
465    },
466    ToolResponseContentAudio {
467        response_id: &'a str,
468        agent_instance_hierarchy: &'a str,
469        index: u64,
470        part_index: u64,
471        input_audio: &'a InputAudio,
472    },
473    ToolResponseContentVideo {
474        response_id: &'a str,
475        agent_instance_hierarchy: &'a str,
476        index: u64,
477        part_index: u64,
478        video_url: &'a VideoUrl,
479    },
480    ToolResponseContentFile {
481        response_id: &'a str,
482        agent_instance_hierarchy: &'a str,
483        index: u64,
484        part_index: u64,
485        file: &'a File,
486    },
487
488    // ---- request_message: user content ----
489    RequestMessageUserContentText {
490        response_id: &'a str,
491        agent_instance_hierarchy: &'a str,
492        index: u64,
493        part_index: u64,
494        text: &'a str,
495    },
496    RequestMessageUserContentImage {
497        response_id: &'a str,
498        agent_instance_hierarchy: &'a str,
499        index: u64,
500        part_index: u64,
501        image_url: &'a ImageUrl,
502    },
503    RequestMessageUserContentAudio {
504        response_id: &'a str,
505        agent_instance_hierarchy: &'a str,
506        index: u64,
507        part_index: u64,
508        input_audio: &'a InputAudio,
509    },
510    RequestMessageUserContentVideo {
511        response_id: &'a str,
512        agent_instance_hierarchy: &'a str,
513        index: u64,
514        part_index: u64,
515        video_url: &'a VideoUrl,
516    },
517    RequestMessageUserContentFile {
518        response_id: &'a str,
519        agent_instance_hierarchy: &'a str,
520        index: u64,
521        part_index: u64,
522        file: &'a File,
523    },
524
525    // ---- request_message: assistant ----
526    RequestMessageAssistantRefusal {
527        response_id: &'a str,
528        agent_instance_hierarchy: &'a str,
529        index: u64,
530        text: &'a str,
531    },
532    RequestMessageAssistantReasoning {
533        response_id: &'a str,
534        agent_instance_hierarchy: &'a str,
535        index: u64,
536        text: &'a str,
537    },
538    RequestMessageAssistantToolCalls {
539        response_id: &'a str,
540        agent_instance_hierarchy: &'a str,
541        index: u64,
542        tool_call_index: u64,
543        tool_call_id: &'a str,
544        function_name: &'a str,
545        arguments: &'a str,
546    },
547    RequestMessageAssistantContentText {
548        response_id: &'a str,
549        agent_instance_hierarchy: &'a str,
550        index: u64,
551        part_index: u64,
552        text: &'a str,
553    },
554    RequestMessageAssistantContentImage {
555        response_id: &'a str,
556        agent_instance_hierarchy: &'a str,
557        index: u64,
558        part_index: u64,
559        image_url: &'a ImageUrl,
560    },
561    RequestMessageAssistantContentAudio {
562        response_id: &'a str,
563        agent_instance_hierarchy: &'a str,
564        index: u64,
565        part_index: u64,
566        input_audio: &'a InputAudio,
567    },
568    RequestMessageAssistantContentVideo {
569        response_id: &'a str,
570        agent_instance_hierarchy: &'a str,
571        index: u64,
572        part_index: u64,
573        video_url: &'a VideoUrl,
574    },
575    RequestMessageAssistantContentFile {
576        response_id: &'a str,
577        agent_instance_hierarchy: &'a str,
578        index: u64,
579        part_index: u64,
580        file: &'a File,
581    },
582
583    // ---- request_message: tool ----
584    /// Head row (JOIN target for `tool_call_id`) — emits no messages
585    /// event, like [`RowValue::ToolResponse`].
586    RequestMessageTool {
587        response_id: &'a str,
588        agent_instance_hierarchy: &'a str,
589        index: u64,
590        tool_call_id: &'a str,
591    },
592    RequestMessageToolContentText {
593        response_id: &'a str,
594        agent_instance_hierarchy: &'a str,
595        index: u64,
596        part_index: u64,
597        text: &'a str,
598    },
599    RequestMessageToolContentImage {
600        response_id: &'a str,
601        agent_instance_hierarchy: &'a str,
602        index: u64,
603        part_index: u64,
604        image_url: &'a ImageUrl,
605    },
606    RequestMessageToolContentAudio {
607        response_id: &'a str,
608        agent_instance_hierarchy: &'a str,
609        index: u64,
610        part_index: u64,
611        input_audio: &'a InputAudio,
612    },
613    RequestMessageToolContentVideo {
614        response_id: &'a str,
615        agent_instance_hierarchy: &'a str,
616        index: u64,
617        part_index: u64,
618        video_url: &'a VideoUrl,
619    },
620    RequestMessageToolContentFile {
621        response_id: &'a str,
622        agent_instance_hierarchy: &'a str,
623        index: u64,
624        part_index: u64,
625        file: &'a File,
626    },
627
628    // ---- vector request choices ----
629    /// Head row carrying this agent's inline voting `key` for the
630    /// choice. Emits no messages event; JOIN target for the key.
631    RequestVectorChoice {
632        response_id: &'a str,
633        agent_instance_hierarchy: &'a str,
634        choice_index: u64,
635        key: &'a str,
636    },
637    RequestVectorChoiceContentText {
638        response_id: &'a str,
639        agent_instance_hierarchy: &'a str,
640        choice_index: u64,
641        part_index: u64,
642        text: &'a str,
643    },
644    RequestVectorChoiceContentImage {
645        response_id: &'a str,
646        agent_instance_hierarchy: &'a str,
647        choice_index: u64,
648        part_index: u64,
649        image_url: &'a ImageUrl,
650    },
651    RequestVectorChoiceContentAudio {
652        response_id: &'a str,
653        agent_instance_hierarchy: &'a str,
654        choice_index: u64,
655        part_index: u64,
656        input_audio: &'a InputAudio,
657    },
658    RequestVectorChoiceContentVideo {
659        response_id: &'a str,
660        agent_instance_hierarchy: &'a str,
661        choice_index: u64,
662        part_index: u64,
663        video_url: &'a VideoUrl,
664    },
665    RequestVectorChoiceContentFile {
666        response_id: &'a str,
667        agent_instance_hierarchy: &'a str,
668        choice_index: u64,
669        part_index: u64,
670        file: &'a File,
671    },
672
673    // ---- vector response vote (inline closer) ----
674    /// The per-agent vote (score per choice, in choice order). Inline
675    /// value, NULL row indices; written via a dedicated helper that
676    /// this generic path early-branches.
677    ResponseVectorVote {
678        response_id: &'a str,
679        agent_instance_hierarchy: &'a str,
680        vote: &'a [rust_decimal::Decimal],
681    },
682}
683
684impl<'a> RowValue<'a> {
685    pub fn table(&self) -> RowTable {
686        match self {
687            RowValue::MessageQueueContent { .. } => RowTable::MessageQueueContent,
688            RowValue::ToolResponse { .. } => RowTable::ToolResponse,
689            RowValue::AssistantResponseRefusal { .. } => RowTable::AssistantResponseRefusal,
690            RowValue::AssistantResponseReasoning { .. } => RowTable::AssistantResponseReasoning,
691            RowValue::AssistantResponseToolCalls { .. } => RowTable::AssistantResponseToolCalls,
692            RowValue::AssistantResponseContentText { .. } => RowTable::AssistantResponseContentText,
693            RowValue::AssistantResponseContentImage { .. } => RowTable::AssistantResponseContentImage,
694            RowValue::AssistantResponseContentAudio { .. } => RowTable::AssistantResponseContentAudio,
695            RowValue::AssistantResponseContentVideo { .. } => RowTable::AssistantResponseContentVideo,
696            RowValue::AssistantResponseContentFile { .. } => RowTable::AssistantResponseContentFile,
697            RowValue::ToolResponseContentText { .. } => RowTable::ToolResponseContentText,
698            RowValue::ToolResponseContentImage { .. } => RowTable::ToolResponseContentImage,
699            RowValue::ToolResponseContentAudio { .. } => RowTable::ToolResponseContentAudio,
700            RowValue::ToolResponseContentVideo { .. } => RowTable::ToolResponseContentVideo,
701            RowValue::ToolResponseContentFile { .. } => RowTable::ToolResponseContentFile,
702            RowValue::RequestMessageUserContentText { .. } => RowTable::RequestMessageUserContentText,
703            RowValue::RequestMessageUserContentImage { .. } => RowTable::RequestMessageUserContentImage,
704            RowValue::RequestMessageUserContentAudio { .. } => RowTable::RequestMessageUserContentAudio,
705            RowValue::RequestMessageUserContentVideo { .. } => RowTable::RequestMessageUserContentVideo,
706            RowValue::RequestMessageUserContentFile { .. } => RowTable::RequestMessageUserContentFile,
707            RowValue::RequestMessageAssistantRefusal { .. } => RowTable::RequestMessageAssistantRefusal,
708            RowValue::RequestMessageAssistantReasoning { .. } => RowTable::RequestMessageAssistantReasoning,
709            RowValue::RequestMessageAssistantToolCalls { .. } => RowTable::RequestMessageAssistantToolCalls,
710            RowValue::RequestMessageAssistantContentText { .. } => RowTable::RequestMessageAssistantContentText,
711            RowValue::RequestMessageAssistantContentImage { .. } => RowTable::RequestMessageAssistantContentImage,
712            RowValue::RequestMessageAssistantContentAudio { .. } => RowTable::RequestMessageAssistantContentAudio,
713            RowValue::RequestMessageAssistantContentVideo { .. } => RowTable::RequestMessageAssistantContentVideo,
714            RowValue::RequestMessageAssistantContentFile { .. } => RowTable::RequestMessageAssistantContentFile,
715            RowValue::RequestMessageTool { .. } => RowTable::RequestMessageTool,
716            RowValue::RequestMessageToolContentText { .. } => RowTable::RequestMessageToolContentText,
717            RowValue::RequestMessageToolContentImage { .. } => RowTable::RequestMessageToolContentImage,
718            RowValue::RequestMessageToolContentAudio { .. } => RowTable::RequestMessageToolContentAudio,
719            RowValue::RequestMessageToolContentVideo { .. } => RowTable::RequestMessageToolContentVideo,
720            RowValue::RequestMessageToolContentFile { .. } => RowTable::RequestMessageToolContentFile,
721            RowValue::RequestVectorChoice { .. } => RowTable::RequestVectorChoice,
722            RowValue::RequestVectorChoiceContentText { .. } => RowTable::RequestVectorChoiceContentText,
723            RowValue::RequestVectorChoiceContentImage { .. } => RowTable::RequestVectorChoiceContentImage,
724            RowValue::RequestVectorChoiceContentAudio { .. } => RowTable::RequestVectorChoiceContentAudio,
725            RowValue::RequestVectorChoiceContentVideo { .. } => RowTable::RequestVectorChoiceContentVideo,
726            RowValue::RequestVectorChoiceContentFile { .. } => RowTable::RequestVectorChoiceContentFile,
727            RowValue::ResponseVectorVote { .. } => RowTable::ResponseVectorVote,
728        }
729    }
730
731    /// [`MessageTable`] for this row's table. Streaming-content rows
732    /// always have one — EXCEPT `RowValue::ToolResponse`, whose head
733    /// row emits no `messages` event (it's a `tool_call_id` lookup
734    /// only). Callers MUST early-branch `ToolResponse` before calling
735    /// this (`write.rs::insert_value`/`update_value` do), or the
736    /// `.expect()` below panics.
737    pub fn message_table(&self) -> MessageTable {
738        self.table()
739            .message_table()
740            .expect("RowValue variants (except ToolResponse) cover messages-emitting tables")
741    }
742
743    /// `response_id` borrowed from the immediately-enclosing
744    /// agent-completion chunk's `id`. Even for rows emitted under
745    /// `vector_completion_chunk_rows` /
746    /// `function_execution_chunk_rows`, the recursion bottoms out
747    /// at an agent-completion chunk and uses that chunk's id —
748    /// never the outer vector/function wrapper's id. This is what
749    /// `agents logs read all` / `read pending` use as the
750    /// per-block `response_id` boundary key.
751    pub fn response_id(&self) -> &'a str {
752        match self {
753            RowValue::MessageQueueContent { response_id, .. }
754            | RowValue::ToolResponse { response_id, .. }
755            | RowValue::AssistantResponseRefusal { response_id, .. }
756            | RowValue::AssistantResponseReasoning { response_id, .. }
757            | RowValue::AssistantResponseToolCalls { response_id, .. }
758            | RowValue::AssistantResponseContentText { response_id, .. }
759            | RowValue::AssistantResponseContentImage { response_id, .. }
760            | RowValue::AssistantResponseContentAudio { response_id, .. }
761            | RowValue::AssistantResponseContentVideo { response_id, .. }
762            | RowValue::AssistantResponseContentFile { response_id, .. }
763            | RowValue::ToolResponseContentText { response_id, .. }
764            | RowValue::ToolResponseContentImage { response_id, .. }
765            | RowValue::ToolResponseContentAudio { response_id, .. }
766            | RowValue::ToolResponseContentVideo { response_id, .. }
767            | RowValue::ToolResponseContentFile { response_id, .. }
768            | RowValue::RequestMessageUserContentText { response_id, .. }
769            | RowValue::RequestMessageUserContentImage { response_id, .. }
770            | RowValue::RequestMessageUserContentAudio { response_id, .. }
771            | RowValue::RequestMessageUserContentVideo { response_id, .. }
772            | RowValue::RequestMessageUserContentFile { response_id, .. }
773            | RowValue::RequestMessageAssistantRefusal { response_id, .. }
774            | RowValue::RequestMessageAssistantReasoning { response_id, .. }
775            | RowValue::RequestMessageAssistantToolCalls { response_id, .. }
776            | RowValue::RequestMessageAssistantContentText { response_id, .. }
777            | RowValue::RequestMessageAssistantContentImage { response_id, .. }
778            | RowValue::RequestMessageAssistantContentAudio { response_id, .. }
779            | RowValue::RequestMessageAssistantContentVideo { response_id, .. }
780            | RowValue::RequestMessageAssistantContentFile { response_id, .. }
781            | RowValue::RequestMessageTool { response_id, .. }
782            | RowValue::RequestMessageToolContentText { response_id, .. }
783            | RowValue::RequestMessageToolContentImage { response_id, .. }
784            | RowValue::RequestMessageToolContentAudio { response_id, .. }
785            | RowValue::RequestMessageToolContentVideo { response_id, .. }
786            | RowValue::RequestMessageToolContentFile { response_id, .. }
787            | RowValue::RequestVectorChoice { response_id, .. }
788            | RowValue::RequestVectorChoiceContentText { response_id, .. }
789            | RowValue::RequestVectorChoiceContentImage { response_id, .. }
790            | RowValue::RequestVectorChoiceContentAudio { response_id, .. }
791            | RowValue::RequestVectorChoiceContentVideo { response_id, .. }
792            | RowValue::RequestVectorChoiceContentFile { response_id, .. }
793            | RowValue::ResponseVectorVote { response_id, .. } => response_id,
794        }
795    }
796
797    /// `agent_instance_hierarchy` borrowed from the enclosing
798    /// agent-completion chunk. Every streaming-content row lives
799    /// inside an agent completion, so this is always non-NULL.
800    pub fn agent_instance_hierarchy(&self) -> &'a str {
801        match self {
802            RowValue::MessageQueueContent { agent_instance_hierarchy, .. }
803            | RowValue::ToolResponse { agent_instance_hierarchy, .. }
804            | RowValue::AssistantResponseRefusal { agent_instance_hierarchy, .. }
805            | RowValue::AssistantResponseReasoning { agent_instance_hierarchy, .. }
806            | RowValue::AssistantResponseToolCalls { agent_instance_hierarchy, .. }
807            | RowValue::AssistantResponseContentText { agent_instance_hierarchy, .. }
808            | RowValue::AssistantResponseContentImage { agent_instance_hierarchy, .. }
809            | RowValue::AssistantResponseContentAudio { agent_instance_hierarchy, .. }
810            | RowValue::AssistantResponseContentVideo { agent_instance_hierarchy, .. }
811            | RowValue::AssistantResponseContentFile { agent_instance_hierarchy, .. }
812            | RowValue::ToolResponseContentText { agent_instance_hierarchy, .. }
813            | RowValue::ToolResponseContentImage { agent_instance_hierarchy, .. }
814            | RowValue::ToolResponseContentAudio { agent_instance_hierarchy, .. }
815            | RowValue::ToolResponseContentVideo { agent_instance_hierarchy, .. }
816            | RowValue::ToolResponseContentFile { agent_instance_hierarchy, .. }
817            | RowValue::RequestMessageUserContentText { agent_instance_hierarchy, .. }
818            | RowValue::RequestMessageUserContentImage { agent_instance_hierarchy, .. }
819            | RowValue::RequestMessageUserContentAudio { agent_instance_hierarchy, .. }
820            | RowValue::RequestMessageUserContentVideo { agent_instance_hierarchy, .. }
821            | RowValue::RequestMessageUserContentFile { agent_instance_hierarchy, .. }
822            | RowValue::RequestMessageAssistantRefusal { agent_instance_hierarchy, .. }
823            | RowValue::RequestMessageAssistantReasoning { agent_instance_hierarchy, .. }
824            | RowValue::RequestMessageAssistantToolCalls { agent_instance_hierarchy, .. }
825            | RowValue::RequestMessageAssistantContentText { agent_instance_hierarchy, .. }
826            | RowValue::RequestMessageAssistantContentImage { agent_instance_hierarchy, .. }
827            | RowValue::RequestMessageAssistantContentAudio { agent_instance_hierarchy, .. }
828            | RowValue::RequestMessageAssistantContentVideo { agent_instance_hierarchy, .. }
829            | RowValue::RequestMessageAssistantContentFile { agent_instance_hierarchy, .. }
830            | RowValue::RequestMessageTool { agent_instance_hierarchy, .. }
831            | RowValue::RequestMessageToolContentText { agent_instance_hierarchy, .. }
832            | RowValue::RequestMessageToolContentImage { agent_instance_hierarchy, .. }
833            | RowValue::RequestMessageToolContentAudio { agent_instance_hierarchy, .. }
834            | RowValue::RequestMessageToolContentVideo { agent_instance_hierarchy, .. }
835            | RowValue::RequestMessageToolContentFile { agent_instance_hierarchy, .. }
836            | RowValue::RequestVectorChoice { agent_instance_hierarchy, .. }
837            | RowValue::RequestVectorChoiceContentText { agent_instance_hierarchy, .. }
838            | RowValue::RequestVectorChoiceContentImage { agent_instance_hierarchy, .. }
839            | RowValue::RequestVectorChoiceContentAudio { agent_instance_hierarchy, .. }
840            | RowValue::RequestVectorChoiceContentVideo { agent_instance_hierarchy, .. }
841            | RowValue::RequestVectorChoiceContentFile { agent_instance_hierarchy, .. }
842            | RowValue::ResponseVectorVote { agent_instance_hierarchy, .. } => {
843                agent_instance_hierarchy
844            }
845        }
846    }
847
848    /// `row_index` column value for the postgres `messages` /
849    /// `messages_queue` entry. Always populated for streaming-content
850    /// rows.
851    pub fn row_index(&self) -> i64 {
852        match self {
853            RowValue::MessageQueueContent { message_queue_content_id, .. } => {
854                *message_queue_content_id
855            }
856            RowValue::ToolResponse { index, .. }
857            | RowValue::AssistantResponseRefusal { index, .. }
858            | RowValue::AssistantResponseReasoning { index, .. }
859            | RowValue::AssistantResponseToolCalls { index, .. }
860            | RowValue::AssistantResponseContentText { index, .. }
861            | RowValue::AssistantResponseContentImage { index, .. }
862            | RowValue::AssistantResponseContentAudio { index, .. }
863            | RowValue::AssistantResponseContentVideo { index, .. }
864            | RowValue::AssistantResponseContentFile { index, .. }
865            | RowValue::ToolResponseContentText { index, .. }
866            | RowValue::ToolResponseContentImage { index, .. }
867            | RowValue::ToolResponseContentAudio { index, .. }
868            | RowValue::ToolResponseContentVideo { index, .. }
869            | RowValue::ToolResponseContentFile { index, .. }
870            | RowValue::RequestMessageUserContentText { index, .. }
871            | RowValue::RequestMessageUserContentImage { index, .. }
872            | RowValue::RequestMessageUserContentAudio { index, .. }
873            | RowValue::RequestMessageUserContentVideo { index, .. }
874            | RowValue::RequestMessageUserContentFile { index, .. }
875            | RowValue::RequestMessageAssistantRefusal { index, .. }
876            | RowValue::RequestMessageAssistantReasoning { index, .. }
877            | RowValue::RequestMessageAssistantToolCalls { index, .. }
878            | RowValue::RequestMessageAssistantContentText { index, .. }
879            | RowValue::RequestMessageAssistantContentImage { index, .. }
880            | RowValue::RequestMessageAssistantContentAudio { index, .. }
881            | RowValue::RequestMessageAssistantContentVideo { index, .. }
882            | RowValue::RequestMessageAssistantContentFile { index, .. }
883            | RowValue::RequestMessageTool { index, .. }
884            | RowValue::RequestMessageToolContentText { index, .. }
885            | RowValue::RequestMessageToolContentImage { index, .. }
886            | RowValue::RequestMessageToolContentAudio { index, .. }
887            | RowValue::RequestMessageToolContentVideo { index, .. }
888            | RowValue::RequestMessageToolContentFile { index, .. } => *index as i64,
889            RowValue::RequestVectorChoice { choice_index, .. }
890            | RowValue::RequestVectorChoiceContentText { choice_index, .. }
891            | RowValue::RequestVectorChoiceContentImage { choice_index, .. }
892            | RowValue::RequestVectorChoiceContentAudio { choice_index, .. }
893            | RowValue::RequestVectorChoiceContentVideo { choice_index, .. }
894            | RowValue::RequestVectorChoiceContentFile { choice_index, .. } => {
895                *choice_index as i64
896            }
897            // NULL row_index — written via a dedicated helper that
898            // early-branches this generic path; never actually read.
899            RowValue::ResponseVectorVote { .. } => 0,
900        }
901    }
902
903    /// `row_sub_index` column value: `tool_call_index` for tool-call
904    /// rows, `part_index` for content-part rows, `None` for
905    /// tool_response / assistant refusal / assistant reasoning (whose
906    /// shape has no sub-index). The matching SQL column is nullable.
907    pub fn row_sub_index(&self) -> Option<i64> {
908        match self {
909            RowValue::MessageQueueContent { .. }
910            | RowValue::ToolResponse { .. }
911            | RowValue::AssistantResponseRefusal { .. }
912            | RowValue::AssistantResponseReasoning { .. }
913            | RowValue::RequestMessageAssistantRefusal { .. }
914            | RowValue::RequestMessageAssistantReasoning { .. }
915            | RowValue::RequestMessageTool { .. }
916            | RowValue::RequestVectorChoice { .. }
917            | RowValue::ResponseVectorVote { .. } => None,
918            RowValue::AssistantResponseToolCalls { tool_call_index, .. }
919            | RowValue::RequestMessageAssistantToolCalls { tool_call_index, .. } => {
920                Some(*tool_call_index as i64)
921            }
922            RowValue::AssistantResponseContentText { part_index, .. }
923            | RowValue::AssistantResponseContentImage { part_index, .. }
924            | RowValue::AssistantResponseContentAudio { part_index, .. }
925            | RowValue::AssistantResponseContentVideo { part_index, .. }
926            | RowValue::AssistantResponseContentFile { part_index, .. }
927            | RowValue::ToolResponseContentText { part_index, .. }
928            | RowValue::ToolResponseContentImage { part_index, .. }
929            | RowValue::ToolResponseContentAudio { part_index, .. }
930            | RowValue::ToolResponseContentVideo { part_index, .. }
931            | RowValue::ToolResponseContentFile { part_index, .. }
932            | RowValue::RequestMessageUserContentText { part_index, .. }
933            | RowValue::RequestMessageUserContentImage { part_index, .. }
934            | RowValue::RequestMessageUserContentAudio { part_index, .. }
935            | RowValue::RequestMessageUserContentVideo { part_index, .. }
936            | RowValue::RequestMessageUserContentFile { part_index, .. }
937            | RowValue::RequestMessageAssistantContentText { part_index, .. }
938            | RowValue::RequestMessageAssistantContentImage { part_index, .. }
939            | RowValue::RequestMessageAssistantContentAudio { part_index, .. }
940            | RowValue::RequestMessageAssistantContentVideo { part_index, .. }
941            | RowValue::RequestMessageAssistantContentFile { part_index, .. }
942            | RowValue::RequestMessageToolContentText { part_index, .. }
943            | RowValue::RequestMessageToolContentImage { part_index, .. }
944            | RowValue::RequestMessageToolContentAudio { part_index, .. }
945            | RowValue::RequestMessageToolContentVideo { part_index, .. }
946            | RowValue::RequestMessageToolContentFile { part_index, .. }
947            | RowValue::RequestVectorChoiceContentText { part_index, .. }
948            | RowValue::RequestVectorChoiceContentImage { part_index, .. }
949            | RowValue::RequestVectorChoiceContentAudio { part_index, .. }
950            | RowValue::RequestVectorChoiceContentVideo { part_index, .. }
951            | RowValue::RequestVectorChoiceContentFile { part_index, .. } => Some(*part_index as i64),
952        }
953    }
954
955    /// The borrowed key that identifies this row's slot in postgres.
956    /// Hashable + Eq — used by the shadow map to find the previously
957    /// stored body without any allocation. Two `RowValue`s targeting
958    /// the same row produce equal `RowKey`s.
959    pub fn key(&self) -> RowKey<'a> {
960        match self {
961            RowValue::MessageQueueContent {
962                response_id,
963                message_queue_content_id,
964                ..
965            } => RowKey::MessageQueueContent {
966                response_id,
967                message_queue_content_id: *message_queue_content_id,
968            },
969            RowValue::ToolResponse { response_id, index, .. } => {
970                RowKey::ToolResponse { response_id, index: *index }
971            }
972            RowValue::AssistantResponseRefusal { response_id, index, .. } => {
973                RowKey::AssistantRefusal { response_id, index: *index }
974            }
975            RowValue::AssistantResponseReasoning { response_id, index, .. } => {
976                RowKey::AssistantReasoning { response_id, index: *index }
977            }
978            RowValue::AssistantResponseToolCalls {
979                response_id, index, tool_call_index, ..
980            } => RowKey::AssistantToolCall {
981                response_id,
982                index: *index,
983                tool_call_index: *tool_call_index,
984            },
985            RowValue::AssistantResponseContentText { response_id, index, part_index, .. } => {
986                RowKey::AssistantContentText { response_id, index: *index, part_index: *part_index }
987            }
988            RowValue::AssistantResponseContentImage { response_id, index, part_index, .. } => {
989                RowKey::AssistantContentImage { response_id, index: *index, part_index: *part_index }
990            }
991            RowValue::AssistantResponseContentAudio { response_id, index, part_index, .. } => {
992                RowKey::AssistantContentAudio { response_id, index: *index, part_index: *part_index }
993            }
994            RowValue::AssistantResponseContentVideo { response_id, index, part_index, .. } => {
995                RowKey::AssistantContentVideo { response_id, index: *index, part_index: *part_index }
996            }
997            RowValue::AssistantResponseContentFile { response_id, index, part_index, .. } => {
998                RowKey::AssistantContentFile { response_id, index: *index, part_index: *part_index }
999            }
1000            RowValue::ToolResponseContentText { response_id, index, part_index, .. } => {
1001                RowKey::ToolContentText { response_id, index: *index, part_index: *part_index }
1002            }
1003            RowValue::ToolResponseContentImage { response_id, index, part_index, .. } => {
1004                RowKey::ToolContentImage { response_id, index: *index, part_index: *part_index }
1005            }
1006            RowValue::ToolResponseContentAudio { response_id, index, part_index, .. } => {
1007                RowKey::ToolContentAudio { response_id, index: *index, part_index: *part_index }
1008            }
1009            RowValue::ToolResponseContentVideo { response_id, index, part_index, .. } => {
1010                RowKey::ToolContentVideo { response_id, index: *index, part_index: *part_index }
1011            }
1012            RowValue::ToolResponseContentFile { response_id, index, part_index, .. } => {
1013                RowKey::ToolContentFile { response_id, index: *index, part_index: *part_index }
1014            }
1015            RowValue::RequestMessageUserContentText { response_id, index, part_index, .. } => {
1016                RowKey::RequestUserContentText { response_id, index: *index, part_index: *part_index }
1017            }
1018            RowValue::RequestMessageUserContentImage { response_id, index, part_index, .. } => {
1019                RowKey::RequestUserContentImage { response_id, index: *index, part_index: *part_index }
1020            }
1021            RowValue::RequestMessageUserContentAudio { response_id, index, part_index, .. } => {
1022                RowKey::RequestUserContentAudio { response_id, index: *index, part_index: *part_index }
1023            }
1024            RowValue::RequestMessageUserContentVideo { response_id, index, part_index, .. } => {
1025                RowKey::RequestUserContentVideo { response_id, index: *index, part_index: *part_index }
1026            }
1027            RowValue::RequestMessageUserContentFile { response_id, index, part_index, .. } => {
1028                RowKey::RequestUserContentFile { response_id, index: *index, part_index: *part_index }
1029            }
1030            RowValue::RequestMessageAssistantRefusal { response_id, index, .. } => {
1031                RowKey::RequestAssistantRefusal { response_id, index: *index }
1032            }
1033            RowValue::RequestMessageAssistantReasoning { response_id, index, .. } => {
1034                RowKey::RequestAssistantReasoning { response_id, index: *index }
1035            }
1036            RowValue::RequestMessageAssistantToolCalls { response_id, index, tool_call_index, .. } => {
1037                RowKey::RequestAssistantToolCall {
1038                    response_id,
1039                    index: *index,
1040                    tool_call_index: *tool_call_index,
1041                }
1042            }
1043            RowValue::RequestMessageAssistantContentText { response_id, index, part_index, .. } => {
1044                RowKey::RequestAssistantContentText { response_id, index: *index, part_index: *part_index }
1045            }
1046            RowValue::RequestMessageAssistantContentImage { response_id, index, part_index, .. } => {
1047                RowKey::RequestAssistantContentImage { response_id, index: *index, part_index: *part_index }
1048            }
1049            RowValue::RequestMessageAssistantContentAudio { response_id, index, part_index, .. } => {
1050                RowKey::RequestAssistantContentAudio { response_id, index: *index, part_index: *part_index }
1051            }
1052            RowValue::RequestMessageAssistantContentVideo { response_id, index, part_index, .. } => {
1053                RowKey::RequestAssistantContentVideo { response_id, index: *index, part_index: *part_index }
1054            }
1055            RowValue::RequestMessageAssistantContentFile { response_id, index, part_index, .. } => {
1056                RowKey::RequestAssistantContentFile { response_id, index: *index, part_index: *part_index }
1057            }
1058            RowValue::RequestMessageTool { response_id, index, .. } => {
1059                RowKey::RequestTool { response_id, index: *index }
1060            }
1061            RowValue::RequestMessageToolContentText { response_id, index, part_index, .. } => {
1062                RowKey::RequestToolContentText { response_id, index: *index, part_index: *part_index }
1063            }
1064            RowValue::RequestMessageToolContentImage { response_id, index, part_index, .. } => {
1065                RowKey::RequestToolContentImage { response_id, index: *index, part_index: *part_index }
1066            }
1067            RowValue::RequestMessageToolContentAudio { response_id, index, part_index, .. } => {
1068                RowKey::RequestToolContentAudio { response_id, index: *index, part_index: *part_index }
1069            }
1070            RowValue::RequestMessageToolContentVideo { response_id, index, part_index, .. } => {
1071                RowKey::RequestToolContentVideo { response_id, index: *index, part_index: *part_index }
1072            }
1073            RowValue::RequestMessageToolContentFile { response_id, index, part_index, .. } => {
1074                RowKey::RequestToolContentFile { response_id, index: *index, part_index: *part_index }
1075            }
1076            RowValue::RequestVectorChoice { response_id, choice_index, .. } => {
1077                RowKey::RequestVectorChoice { response_id, choice_index: *choice_index }
1078            }
1079            RowValue::RequestVectorChoiceContentText { response_id, choice_index, part_index, .. } => {
1080                RowKey::RequestVectorChoiceContentText { response_id, choice_index: *choice_index, part_index: *part_index }
1081            }
1082            RowValue::RequestVectorChoiceContentImage { response_id, choice_index, part_index, .. } => {
1083                RowKey::RequestVectorChoiceContentImage { response_id, choice_index: *choice_index, part_index: *part_index }
1084            }
1085            RowValue::RequestVectorChoiceContentAudio { response_id, choice_index, part_index, .. } => {
1086                RowKey::RequestVectorChoiceContentAudio { response_id, choice_index: *choice_index, part_index: *part_index }
1087            }
1088            RowValue::RequestVectorChoiceContentVideo { response_id, choice_index, part_index, .. } => {
1089                RowKey::RequestVectorChoiceContentVideo { response_id, choice_index: *choice_index, part_index: *part_index }
1090            }
1091            RowValue::RequestVectorChoiceContentFile { response_id, choice_index, part_index, .. } => {
1092                RowKey::RequestVectorChoiceContentFile { response_id, choice_index: *choice_index, part_index: *part_index }
1093            }
1094            RowValue::ResponseVectorVote { response_id, agent_instance_hierarchy, .. } => {
1095                RowKey::ResponseVectorVote { response_id, agent_instance_hierarchy }
1096            }
1097        }
1098    }
1099
1100    /// Field-by-field equality against a stored body. Returns true
1101    /// when this row would write a byte-identical body — the writer
1102    /// uses that signal to short-circuit the SQL.
1103    pub fn body_eq(&self, stored: &RowBody) -> bool {
1104        match (self, stored) {
1105            // MessageQueueContent has no body — the row's identity is
1106            // entirely in the (response_id, content_id) key; shadow
1107            // skip-dedup makes the second write a no-op via this true.
1108            (
1109                RowValue::MessageQueueContent { .. },
1110                RowBody::MessageQueueContent {},
1111            ) => true,
1112            (
1113                RowValue::ToolResponse { tool_call_id: a, .. },
1114                RowBody::ToolResponse { tool_call_id: b },
1115            ) => *a == b.as_str(),
1116            (
1117                RowValue::AssistantResponseRefusal { text: a, .. },
1118                RowBody::AssistantRefusal { text: b },
1119            ) => *a == b.as_str(),
1120            (
1121                RowValue::AssistantResponseReasoning { text: a, .. },
1122                RowBody::AssistantReasoning { text: b },
1123            ) => *a == b.as_str(),
1124            (
1125                RowValue::AssistantResponseToolCalls { tool_call_id: a, arguments: aa, .. },
1126                RowBody::AssistantToolCall { tool_call_id: b, arguments: bb },
1127            ) => *a == b.as_str() && *aa == bb.as_str(),
1128            (
1129                RowValue::AssistantResponseContentText { text: a, .. },
1130                RowBody::AssistantContentText { text: b },
1131            ) => *a == b.as_str(),
1132            (
1133                RowValue::AssistantResponseContentImage { image_url: a, .. },
1134                RowBody::AssistantContentImage { image_url: b },
1135            ) => *a == b,
1136            (
1137                RowValue::AssistantResponseContentAudio { input_audio: a, .. },
1138                RowBody::AssistantContentAudio { input_audio: b },
1139            ) => *a == b,
1140            (
1141                RowValue::AssistantResponseContentVideo { video_url: a, .. },
1142                RowBody::AssistantContentVideo { video_url: b },
1143            ) => *a == b,
1144            (
1145                RowValue::AssistantResponseContentFile { file: a, .. },
1146                RowBody::AssistantContentFile { file: b },
1147            ) => *a == b,
1148            (
1149                RowValue::ToolResponseContentText { text: a, .. },
1150                RowBody::ToolContentText { text: b },
1151            ) => *a == b.as_str(),
1152            (
1153                RowValue::ToolResponseContentImage { image_url: a, .. },
1154                RowBody::ToolContentImage { image_url: b },
1155            ) => *a == b,
1156            (
1157                RowValue::ToolResponseContentAudio { input_audio: a, .. },
1158                RowBody::ToolContentAudio { input_audio: b },
1159            ) => *a == b,
1160            (
1161                RowValue::ToolResponseContentVideo { video_url: a, .. },
1162                RowBody::ToolContentVideo { video_url: b },
1163            ) => *a == b,
1164            (
1165                RowValue::ToolResponseContentFile { file: a, .. },
1166                RowBody::ToolContentFile { file: b },
1167            ) => *a == b,
1168            (
1169                RowValue::RequestMessageUserContentText { text: a, .. },
1170                RowBody::RequestUserContentText { text: b },
1171            ) => *a == b.as_str(),
1172            (
1173                RowValue::RequestMessageUserContentImage { image_url: a, .. },
1174                RowBody::RequestUserContentImage { image_url: b },
1175            ) => *a == b,
1176            (
1177                RowValue::RequestMessageUserContentAudio { input_audio: a, .. },
1178                RowBody::RequestUserContentAudio { input_audio: b },
1179            ) => *a == b,
1180            (
1181                RowValue::RequestMessageUserContentVideo { video_url: a, .. },
1182                RowBody::RequestUserContentVideo { video_url: b },
1183            ) => *a == b,
1184            (
1185                RowValue::RequestMessageUserContentFile { file: a, .. },
1186                RowBody::RequestUserContentFile { file: b },
1187            ) => *a == b,
1188            (
1189                RowValue::RequestMessageAssistantRefusal { text: a, .. },
1190                RowBody::RequestAssistantRefusal { text: b },
1191            ) => *a == b.as_str(),
1192            (
1193                RowValue::RequestMessageAssistantReasoning { text: a, .. },
1194                RowBody::RequestAssistantReasoning { text: b },
1195            ) => *a == b.as_str(),
1196            (
1197                RowValue::RequestMessageAssistantToolCalls { tool_call_id: a, arguments: aa, .. },
1198                RowBody::RequestAssistantToolCall { tool_call_id: b, arguments: bb },
1199            ) => *a == b.as_str() && *aa == bb.as_str(),
1200            (
1201                RowValue::RequestMessageAssistantContentText { text: a, .. },
1202                RowBody::RequestAssistantContentText { text: b },
1203            ) => *a == b.as_str(),
1204            (
1205                RowValue::RequestMessageAssistantContentImage { image_url: a, .. },
1206                RowBody::RequestAssistantContentImage { image_url: b },
1207            ) => *a == b,
1208            (
1209                RowValue::RequestMessageAssistantContentAudio { input_audio: a, .. },
1210                RowBody::RequestAssistantContentAudio { input_audio: b },
1211            ) => *a == b,
1212            (
1213                RowValue::RequestMessageAssistantContentVideo { video_url: a, .. },
1214                RowBody::RequestAssistantContentVideo { video_url: b },
1215            ) => *a == b,
1216            (
1217                RowValue::RequestMessageAssistantContentFile { file: a, .. },
1218                RowBody::RequestAssistantContentFile { file: b },
1219            ) => *a == b,
1220            (
1221                RowValue::RequestMessageTool { tool_call_id: a, .. },
1222                RowBody::RequestTool { tool_call_id: b },
1223            ) => *a == b.as_str(),
1224            (
1225                RowValue::RequestMessageToolContentText { text: a, .. },
1226                RowBody::RequestToolContentText { text: b },
1227            ) => *a == b.as_str(),
1228            (
1229                RowValue::RequestMessageToolContentImage { image_url: a, .. },
1230                RowBody::RequestToolContentImage { image_url: b },
1231            ) => *a == b,
1232            (
1233                RowValue::RequestMessageToolContentAudio { input_audio: a, .. },
1234                RowBody::RequestToolContentAudio { input_audio: b },
1235            ) => *a == b,
1236            (
1237                RowValue::RequestMessageToolContentVideo { video_url: a, .. },
1238                RowBody::RequestToolContentVideo { video_url: b },
1239            ) => *a == b,
1240            (
1241                RowValue::RequestMessageToolContentFile { file: a, .. },
1242                RowBody::RequestToolContentFile { file: b },
1243            ) => *a == b,
1244            (
1245                RowValue::RequestVectorChoice { key: a, .. },
1246                RowBody::RequestVectorChoice { key: b },
1247            ) => *a == b.as_str(),
1248            (
1249                RowValue::RequestVectorChoiceContentText { text: a, .. },
1250                RowBody::RequestVectorChoiceContentText { text: b },
1251            ) => *a == b.as_str(),
1252            (
1253                RowValue::RequestVectorChoiceContentImage { image_url: a, .. },
1254                RowBody::RequestVectorChoiceContentImage { image_url: b },
1255            ) => *a == b,
1256            (
1257                RowValue::RequestVectorChoiceContentAudio { input_audio: a, .. },
1258                RowBody::RequestVectorChoiceContentAudio { input_audio: b },
1259            ) => *a == b,
1260            (
1261                RowValue::RequestVectorChoiceContentVideo { video_url: a, .. },
1262                RowBody::RequestVectorChoiceContentVideo { video_url: b },
1263            ) => *a == b,
1264            (
1265                RowValue::RequestVectorChoiceContentFile { file: a, .. },
1266                RowBody::RequestVectorChoiceContentFile { file: b },
1267            ) => *a == b,
1268            (
1269                RowValue::ResponseVectorVote { vote: a, .. },
1270                RowBody::ResponseVectorVote { vote: b },
1271            ) => *a == b.as_slice(),
1272            _ => false,
1273        }
1274    }
1275
1276    /// Build an owned [`RowBody`] for storing on Insert / Update. Only
1277    /// called on the cold path (when the shadow needs to remember a
1278    /// new value); the Skip path never allocates here.
1279    pub fn to_body(&self) -> RowBody {
1280        match self {
1281            RowValue::MessageQueueContent { .. } => RowBody::MessageQueueContent {},
1282            RowValue::ToolResponse { tool_call_id, .. } => RowBody::ToolResponse {
1283                tool_call_id: (*tool_call_id).to_owned(),
1284            },
1285            RowValue::AssistantResponseRefusal { text, .. } => RowBody::AssistantRefusal {
1286                text: (*text).to_owned(),
1287            },
1288            RowValue::AssistantResponseReasoning { text, .. } => RowBody::AssistantReasoning {
1289                text: (*text).to_owned(),
1290            },
1291            RowValue::AssistantResponseToolCalls { tool_call_id, arguments, .. } => {
1292                RowBody::AssistantToolCall {
1293                    tool_call_id: (*tool_call_id).to_owned(),
1294                    arguments: (*arguments).to_owned(),
1295                }
1296            }
1297            RowValue::AssistantResponseContentText { text, .. } => RowBody::AssistantContentText {
1298                text: (*text).to_owned(),
1299            },
1300            RowValue::AssistantResponseContentImage { image_url, .. } => {
1301                RowBody::AssistantContentImage { image_url: (*image_url).clone() }
1302            }
1303            RowValue::AssistantResponseContentAudio { input_audio, .. } => {
1304                RowBody::AssistantContentAudio { input_audio: (*input_audio).clone() }
1305            }
1306            RowValue::AssistantResponseContentVideo { video_url, .. } => {
1307                RowBody::AssistantContentVideo {
1308                    video_url: (*video_url).clone(),
1309                }
1310            }
1311            RowValue::AssistantResponseContentFile { file, .. } => {
1312                RowBody::AssistantContentFile { file: (*file).clone() }
1313            }
1314            RowValue::ToolResponseContentText { text, .. } => RowBody::ToolContentText {
1315                text: (*text).to_owned(),
1316            },
1317            RowValue::ToolResponseContentImage { image_url, .. } => {
1318                RowBody::ToolContentImage { image_url: (*image_url).clone() }
1319            }
1320            RowValue::ToolResponseContentAudio { input_audio, .. } => {
1321                RowBody::ToolContentAudio { input_audio: (*input_audio).clone() }
1322            }
1323            RowValue::ToolResponseContentVideo { video_url, .. } => RowBody::ToolContentVideo {
1324                video_url: (*video_url).clone(),
1325            },
1326            RowValue::ToolResponseContentFile { file, .. } => RowBody::ToolContentFile {
1327                file: (*file).clone(),
1328            },
1329            RowValue::RequestMessageUserContentText { text, .. } => RowBody::RequestUserContentText { text: (*text).to_owned() },
1330            RowValue::RequestMessageUserContentImage { image_url, .. } => RowBody::RequestUserContentImage { image_url: (*image_url).clone() },
1331            RowValue::RequestMessageUserContentAudio { input_audio, .. } => RowBody::RequestUserContentAudio { input_audio: (*input_audio).clone() },
1332            RowValue::RequestMessageUserContentVideo { video_url, .. } => RowBody::RequestUserContentVideo { video_url: (*video_url).clone() },
1333            RowValue::RequestMessageUserContentFile { file, .. } => RowBody::RequestUserContentFile { file: (*file).clone() },
1334            RowValue::RequestMessageAssistantRefusal { text, .. } => RowBody::RequestAssistantRefusal { text: (*text).to_owned() },
1335            RowValue::RequestMessageAssistantReasoning { text, .. } => RowBody::RequestAssistantReasoning { text: (*text).to_owned() },
1336            RowValue::RequestMessageAssistantToolCalls { tool_call_id, arguments, .. } => RowBody::RequestAssistantToolCall {
1337                tool_call_id: (*tool_call_id).to_owned(),
1338                arguments: (*arguments).to_owned(),
1339            },
1340            RowValue::RequestMessageAssistantContentText { text, .. } => RowBody::RequestAssistantContentText { text: (*text).to_owned() },
1341            RowValue::RequestMessageAssistantContentImage { image_url, .. } => RowBody::RequestAssistantContentImage { image_url: (*image_url).clone() },
1342            RowValue::RequestMessageAssistantContentAudio { input_audio, .. } => RowBody::RequestAssistantContentAudio { input_audio: (*input_audio).clone() },
1343            RowValue::RequestMessageAssistantContentVideo { video_url, .. } => RowBody::RequestAssistantContentVideo { video_url: (*video_url).clone() },
1344            RowValue::RequestMessageAssistantContentFile { file, .. } => RowBody::RequestAssistantContentFile { file: (*file).clone() },
1345            RowValue::RequestMessageTool { tool_call_id, .. } => RowBody::RequestTool { tool_call_id: (*tool_call_id).to_owned() },
1346            RowValue::RequestMessageToolContentText { text, .. } => RowBody::RequestToolContentText { text: (*text).to_owned() },
1347            RowValue::RequestMessageToolContentImage { image_url, .. } => RowBody::RequestToolContentImage { image_url: (*image_url).clone() },
1348            RowValue::RequestMessageToolContentAudio { input_audio, .. } => RowBody::RequestToolContentAudio { input_audio: (*input_audio).clone() },
1349            RowValue::RequestMessageToolContentVideo { video_url, .. } => RowBody::RequestToolContentVideo { video_url: (*video_url).clone() },
1350            RowValue::RequestMessageToolContentFile { file, .. } => RowBody::RequestToolContentFile { file: (*file).clone() },
1351            RowValue::RequestVectorChoice { key, .. } => RowBody::RequestVectorChoice { key: (*key).to_owned() },
1352            RowValue::RequestVectorChoiceContentText { text, .. } => RowBody::RequestVectorChoiceContentText { text: (*text).to_owned() },
1353            RowValue::RequestVectorChoiceContentImage { image_url, .. } => RowBody::RequestVectorChoiceContentImage { image_url: (*image_url).clone() },
1354            RowValue::RequestVectorChoiceContentAudio { input_audio, .. } => RowBody::RequestVectorChoiceContentAudio { input_audio: (*input_audio).clone() },
1355            RowValue::RequestVectorChoiceContentVideo { video_url, .. } => RowBody::RequestVectorChoiceContentVideo { video_url: (*video_url).clone() },
1356            RowValue::RequestVectorChoiceContentFile { file, .. } => RowBody::RequestVectorChoiceContentFile { file: (*file).clone() },
1357            RowValue::ResponseVectorVote { vote, .. } => RowBody::ResponseVectorVote { vote: (*vote).to_vec() },
1358        }
1359    }
1360}
1361
1362/// Boxed-iterator alias used at the recursive boundaries
1363/// (function execution → vector completion → agent completion).
1364/// One Box per recursive descent, never per leaf row.
1365pub type RowsIter<'a> = Box<dyn Iterator<Item = RowValue<'a>> + Send + 'a>;
1366
1367/// One item from a top-level chunk walk: either a streaming-content
1368/// [`RowValue`] or a per-AIH agent-completion token-usage snapshot.
1369/// Usage rides the SAME single traversal as the content rows — the
1370/// walk already descends into every nested agent completion, so the
1371/// writer never re-walks the chunk tree for usage.
1372#[derive(Debug)]
1373pub enum WriterItem<'a> {
1374    Row(RowValue<'a>),
1375    Usage {
1376        agent_instance_hierarchy: &'a str,
1377        total_tokens: u64,
1378    },
1379    /// An in-band completion error: the agent completion ITSELF
1380    /// carries `error` (an upstream/API failure delivered as part of
1381    /// the response, not a transport failure). The writer persists it
1382    /// as an `error` log row — once per `(aih, response_id)`; the
1383    /// cumulative accumulator re-yields it every tick.
1384    Error {
1385        agent_instance_hierarchy: &'a str,
1386        response_id: &'a str,
1387        error: &'a objectiveai_sdk::error::ResponseError,
1388    },
1389}
1390
1391/// Boxed iterator of [`WriterItem`]s — the return type of the three
1392/// entry-point chunk walkers. Internal per-message helpers still yield
1393/// bare [`RowValue`] via [`RowsIter`]; only the entry points interleave
1394/// the usage item.
1395pub type WriterItems<'a> = Box<dyn Iterator<Item = WriterItem<'a>> + Send + 'a>;
1396
1397// ---------------------------------------------------------------------
1398// Shadow keys (borrowed + owned)
1399// ---------------------------------------------------------------------
1400
1401/// Borrowed key that identifies one row's slot in postgres. Two
1402/// `RowKey`s with equal variant + fields hash identically to an
1403/// [`OwnedRowKey`] with the matching shape — that invariant lets the
1404/// shadow's hashbrown `raw_entry_mut` look up by borrowed key without
1405/// converting to owned first.
1406#[derive(Debug, Clone, Hash, PartialEq, Eq)]
1407pub enum RowKey<'a> {
1408    /// `message_queue_contents.id` is globally unique across
1409    /// kinds, so the key needs no kind discriminator — only the
1410    /// id (the kind is recovered from `RowValue` for write
1411    /// dispatch).
1412    MessageQueueContent { response_id: &'a str, message_queue_content_id: i64 },
1413    ToolResponse { response_id: &'a str, index: u64 },
1414    AssistantRefusal { response_id: &'a str, index: u64 },
1415    AssistantReasoning { response_id: &'a str, index: u64 },
1416    AssistantToolCall { response_id: &'a str, index: u64, tool_call_index: u64 },
1417    AssistantContentText { response_id: &'a str, index: u64, part_index: u64 },
1418    AssistantContentImage { response_id: &'a str, index: u64, part_index: u64 },
1419    AssistantContentAudio { response_id: &'a str, index: u64, part_index: u64 },
1420    AssistantContentVideo { response_id: &'a str, index: u64, part_index: u64 },
1421    AssistantContentFile { response_id: &'a str, index: u64, part_index: u64 },
1422    ToolContentText { response_id: &'a str, index: u64, part_index: u64 },
1423    ToolContentImage { response_id: &'a str, index: u64, part_index: u64 },
1424    ToolContentAudio { response_id: &'a str, index: u64, part_index: u64 },
1425    ToolContentVideo { response_id: &'a str, index: u64, part_index: u64 },
1426    ToolContentFile { response_id: &'a str, index: u64, part_index: u64 },
1427    RequestUserContentText { response_id: &'a str, index: u64, part_index: u64 },
1428    RequestUserContentImage { response_id: &'a str, index: u64, part_index: u64 },
1429    RequestUserContentAudio { response_id: &'a str, index: u64, part_index: u64 },
1430    RequestUserContentVideo { response_id: &'a str, index: u64, part_index: u64 },
1431    RequestUserContentFile { response_id: &'a str, index: u64, part_index: u64 },
1432    RequestAssistantRefusal { response_id: &'a str, index: u64 },
1433    RequestAssistantReasoning { response_id: &'a str, index: u64 },
1434    RequestAssistantToolCall { response_id: &'a str, index: u64, tool_call_index: u64 },
1435    RequestAssistantContentText { response_id: &'a str, index: u64, part_index: u64 },
1436    RequestAssistantContentImage { response_id: &'a str, index: u64, part_index: u64 },
1437    RequestAssistantContentAudio { response_id: &'a str, index: u64, part_index: u64 },
1438    RequestAssistantContentVideo { response_id: &'a str, index: u64, part_index: u64 },
1439    RequestAssistantContentFile { response_id: &'a str, index: u64, part_index: u64 },
1440    RequestTool { response_id: &'a str, index: u64 },
1441    RequestToolContentText { response_id: &'a str, index: u64, part_index: u64 },
1442    RequestToolContentImage { response_id: &'a str, index: u64, part_index: u64 },
1443    RequestToolContentAudio { response_id: &'a str, index: u64, part_index: u64 },
1444    RequestToolContentVideo { response_id: &'a str, index: u64, part_index: u64 },
1445    RequestToolContentFile { response_id: &'a str, index: u64, part_index: u64 },
1446    RequestVectorChoice { response_id: &'a str, choice_index: u64 },
1447    RequestVectorChoiceContentText { response_id: &'a str, choice_index: u64, part_index: u64 },
1448    RequestVectorChoiceContentImage { response_id: &'a str, choice_index: u64, part_index: u64 },
1449    RequestVectorChoiceContentAudio { response_id: &'a str, choice_index: u64, part_index: u64 },
1450    RequestVectorChoiceContentVideo { response_id: &'a str, choice_index: u64, part_index: u64 },
1451    RequestVectorChoiceContentFile { response_id: &'a str, choice_index: u64, part_index: u64 },
1452    ResponseVectorVote { response_id: &'a str, agent_instance_hierarchy: &'a str },
1453}
1454
1455/// Owned counterpart to [`RowKey`]. Stored in the shadow map. Built
1456/// only on Insert.
1457#[derive(Debug, Clone, Hash, PartialEq, Eq)]
1458pub enum OwnedRowKey {
1459    MessageQueueContent { response_id: String, message_queue_content_id: i64 },
1460    ToolResponse { response_id: String, index: u64 },
1461    AssistantRefusal { response_id: String, index: u64 },
1462    AssistantReasoning { response_id: String, index: u64 },
1463    AssistantToolCall { response_id: String, index: u64, tool_call_index: u64 },
1464    AssistantContentText { response_id: String, index: u64, part_index: u64 },
1465    AssistantContentImage { response_id: String, index: u64, part_index: u64 },
1466    AssistantContentAudio { response_id: String, index: u64, part_index: u64 },
1467    AssistantContentVideo { response_id: String, index: u64, part_index: u64 },
1468    AssistantContentFile { response_id: String, index: u64, part_index: u64 },
1469    ToolContentText { response_id: String, index: u64, part_index: u64 },
1470    ToolContentImage { response_id: String, index: u64, part_index: u64 },
1471    ToolContentAudio { response_id: String, index: u64, part_index: u64 },
1472    ToolContentVideo { response_id: String, index: u64, part_index: u64 },
1473    ToolContentFile { response_id: String, index: u64, part_index: u64 },
1474    RequestUserContentText { response_id: String, index: u64, part_index: u64 },
1475    RequestUserContentImage { response_id: String, index: u64, part_index: u64 },
1476    RequestUserContentAudio { response_id: String, index: u64, part_index: u64 },
1477    RequestUserContentVideo { response_id: String, index: u64, part_index: u64 },
1478    RequestUserContentFile { response_id: String, index: u64, part_index: u64 },
1479    RequestAssistantRefusal { response_id: String, index: u64 },
1480    RequestAssistantReasoning { response_id: String, index: u64 },
1481    RequestAssistantToolCall { response_id: String, index: u64, tool_call_index: u64 },
1482    RequestAssistantContentText { response_id: String, index: u64, part_index: u64 },
1483    RequestAssistantContentImage { response_id: String, index: u64, part_index: u64 },
1484    RequestAssistantContentAudio { response_id: String, index: u64, part_index: u64 },
1485    RequestAssistantContentVideo { response_id: String, index: u64, part_index: u64 },
1486    RequestAssistantContentFile { response_id: String, index: u64, part_index: u64 },
1487    RequestTool { response_id: String, index: u64 },
1488    RequestToolContentText { response_id: String, index: u64, part_index: u64 },
1489    RequestToolContentImage { response_id: String, index: u64, part_index: u64 },
1490    RequestToolContentAudio { response_id: String, index: u64, part_index: u64 },
1491    RequestToolContentVideo { response_id: String, index: u64, part_index: u64 },
1492    RequestToolContentFile { response_id: String, index: u64, part_index: u64 },
1493    RequestVectorChoice { response_id: String, choice_index: u64 },
1494    RequestVectorChoiceContentText { response_id: String, choice_index: u64, part_index: u64 },
1495    RequestVectorChoiceContentImage { response_id: String, choice_index: u64, part_index: u64 },
1496    RequestVectorChoiceContentAudio { response_id: String, choice_index: u64, part_index: u64 },
1497    RequestVectorChoiceContentVideo { response_id: String, choice_index: u64, part_index: u64 },
1498    RequestVectorChoiceContentFile { response_id: String, choice_index: u64, part_index: u64 },
1499    ResponseVectorVote { response_id: String, agent_instance_hierarchy: String },
1500}
1501
1502impl<'a> RowKey<'a> {
1503    pub fn matches_owned(&self, owned: &OwnedRowKey) -> bool {
1504        match (self, owned) {
1505            (
1506                RowKey::MessageQueueContent { response_id: a, message_queue_content_id: ai },
1507                OwnedRowKey::MessageQueueContent { response_id: b, message_queue_content_id: bi },
1508            ) => *a == b.as_str() && ai == bi,
1509            (
1510                RowKey::ToolResponse { response_id: a, index: ai },
1511                OwnedRowKey::ToolResponse { response_id: b, index: bi },
1512            ) => *a == b.as_str() && ai == bi,
1513            (
1514                RowKey::AssistantRefusal { response_id: a, index: ai },
1515                OwnedRowKey::AssistantRefusal { response_id: b, index: bi },
1516            ) => *a == b.as_str() && ai == bi,
1517            (
1518                RowKey::AssistantReasoning { response_id: a, index: ai },
1519                OwnedRowKey::AssistantReasoning { response_id: b, index: bi },
1520            ) => *a == b.as_str() && ai == bi,
1521            (
1522                RowKey::AssistantToolCall { response_id: a, index: ai, tool_call_index: at },
1523                OwnedRowKey::AssistantToolCall { response_id: b, index: bi, tool_call_index: bt },
1524            ) => *a == b.as_str() && ai == bi && at == bt,
1525            (
1526                RowKey::AssistantContentText { response_id: a, index: ai, part_index: ap },
1527                OwnedRowKey::AssistantContentText { response_id: b, index: bi, part_index: bp },
1528            ) => *a == b.as_str() && ai == bi && ap == bp,
1529            (
1530                RowKey::AssistantContentImage { response_id: a, index: ai, part_index: ap },
1531                OwnedRowKey::AssistantContentImage { response_id: b, index: bi, part_index: bp },
1532            ) => *a == b.as_str() && ai == bi && ap == bp,
1533            (
1534                RowKey::AssistantContentAudio { response_id: a, index: ai, part_index: ap },
1535                OwnedRowKey::AssistantContentAudio { response_id: b, index: bi, part_index: bp },
1536            ) => *a == b.as_str() && ai == bi && ap == bp,
1537            (
1538                RowKey::AssistantContentVideo { response_id: a, index: ai, part_index: ap },
1539                OwnedRowKey::AssistantContentVideo { response_id: b, index: bi, part_index: bp },
1540            ) => *a == b.as_str() && ai == bi && ap == bp,
1541            (
1542                RowKey::AssistantContentFile { response_id: a, index: ai, part_index: ap },
1543                OwnedRowKey::AssistantContentFile { response_id: b, index: bi, part_index: bp },
1544            ) => *a == b.as_str() && ai == bi && ap == bp,
1545            (
1546                RowKey::ToolContentText { response_id: a, index: ai, part_index: ap },
1547                OwnedRowKey::ToolContentText { response_id: b, index: bi, part_index: bp },
1548            ) => *a == b.as_str() && ai == bi && ap == bp,
1549            (
1550                RowKey::ToolContentImage { response_id: a, index: ai, part_index: ap },
1551                OwnedRowKey::ToolContentImage { response_id: b, index: bi, part_index: bp },
1552            ) => *a == b.as_str() && ai == bi && ap == bp,
1553            (
1554                RowKey::ToolContentAudio { response_id: a, index: ai, part_index: ap },
1555                OwnedRowKey::ToolContentAudio { response_id: b, index: bi, part_index: bp },
1556            ) => *a == b.as_str() && ai == bi && ap == bp,
1557            (
1558                RowKey::ToolContentVideo { response_id: a, index: ai, part_index: ap },
1559                OwnedRowKey::ToolContentVideo { response_id: b, index: bi, part_index: bp },
1560            ) => *a == b.as_str() && ai == bi && ap == bp,
1561            (
1562                RowKey::ToolContentFile { response_id: a, index: ai, part_index: ap },
1563                OwnedRowKey::ToolContentFile { response_id: b, index: bi, part_index: bp },
1564            ) => *a == b.as_str() && ai == bi && ap == bp,
1565            (
1566                RowKey::RequestUserContentText { response_id: a, index: ai, part_index: ap },
1567                OwnedRowKey::RequestUserContentText { response_id: b, index: bi, part_index: bp },
1568            ) => *a == b.as_str() && ai == bi && ap == bp,
1569            (
1570                RowKey::RequestUserContentImage { response_id: a, index: ai, part_index: ap },
1571                OwnedRowKey::RequestUserContentImage { response_id: b, index: bi, part_index: bp },
1572            ) => *a == b.as_str() && ai == bi && ap == bp,
1573            (
1574                RowKey::RequestUserContentAudio { response_id: a, index: ai, part_index: ap },
1575                OwnedRowKey::RequestUserContentAudio { response_id: b, index: bi, part_index: bp },
1576            ) => *a == b.as_str() && ai == bi && ap == bp,
1577            (
1578                RowKey::RequestUserContentVideo { response_id: a, index: ai, part_index: ap },
1579                OwnedRowKey::RequestUserContentVideo { response_id: b, index: bi, part_index: bp },
1580            ) => *a == b.as_str() && ai == bi && ap == bp,
1581            (
1582                RowKey::RequestUserContentFile { response_id: a, index: ai, part_index: ap },
1583                OwnedRowKey::RequestUserContentFile { response_id: b, index: bi, part_index: bp },
1584            ) => *a == b.as_str() && ai == bi && ap == bp,
1585            (
1586                RowKey::RequestAssistantRefusal { response_id: a, index: ai },
1587                OwnedRowKey::RequestAssistantRefusal { response_id: b, index: bi },
1588            ) => *a == b.as_str() && ai == bi,
1589            (
1590                RowKey::RequestAssistantReasoning { response_id: a, index: ai },
1591                OwnedRowKey::RequestAssistantReasoning { response_id: b, index: bi },
1592            ) => *a == b.as_str() && ai == bi,
1593            (
1594                RowKey::RequestAssistantToolCall { response_id: a, index: ai, tool_call_index: at },
1595                OwnedRowKey::RequestAssistantToolCall { response_id: b, index: bi, tool_call_index: bt },
1596            ) => *a == b.as_str() && ai == bi && at == bt,
1597            (
1598                RowKey::RequestAssistantContentText { response_id: a, index: ai, part_index: ap },
1599                OwnedRowKey::RequestAssistantContentText { response_id: b, index: bi, part_index: bp },
1600            ) => *a == b.as_str() && ai == bi && ap == bp,
1601            (
1602                RowKey::RequestAssistantContentImage { response_id: a, index: ai, part_index: ap },
1603                OwnedRowKey::RequestAssistantContentImage { response_id: b, index: bi, part_index: bp },
1604            ) => *a == b.as_str() && ai == bi && ap == bp,
1605            (
1606                RowKey::RequestAssistantContentAudio { response_id: a, index: ai, part_index: ap },
1607                OwnedRowKey::RequestAssistantContentAudio { response_id: b, index: bi, part_index: bp },
1608            ) => *a == b.as_str() && ai == bi && ap == bp,
1609            (
1610                RowKey::RequestAssistantContentVideo { response_id: a, index: ai, part_index: ap },
1611                OwnedRowKey::RequestAssistantContentVideo { response_id: b, index: bi, part_index: bp },
1612            ) => *a == b.as_str() && ai == bi && ap == bp,
1613            (
1614                RowKey::RequestAssistantContentFile { response_id: a, index: ai, part_index: ap },
1615                OwnedRowKey::RequestAssistantContentFile { response_id: b, index: bi, part_index: bp },
1616            ) => *a == b.as_str() && ai == bi && ap == bp,
1617            (
1618                RowKey::RequestTool { response_id: a, index: ai },
1619                OwnedRowKey::RequestTool { response_id: b, index: bi },
1620            ) => *a == b.as_str() && ai == bi,
1621            (
1622                RowKey::RequestToolContentText { response_id: a, index: ai, part_index: ap },
1623                OwnedRowKey::RequestToolContentText { response_id: b, index: bi, part_index: bp },
1624            ) => *a == b.as_str() && ai == bi && ap == bp,
1625            (
1626                RowKey::RequestToolContentImage { response_id: a, index: ai, part_index: ap },
1627                OwnedRowKey::RequestToolContentImage { response_id: b, index: bi, part_index: bp },
1628            ) => *a == b.as_str() && ai == bi && ap == bp,
1629            (
1630                RowKey::RequestToolContentAudio { response_id: a, index: ai, part_index: ap },
1631                OwnedRowKey::RequestToolContentAudio { response_id: b, index: bi, part_index: bp },
1632            ) => *a == b.as_str() && ai == bi && ap == bp,
1633            (
1634                RowKey::RequestToolContentVideo { response_id: a, index: ai, part_index: ap },
1635                OwnedRowKey::RequestToolContentVideo { response_id: b, index: bi, part_index: bp },
1636            ) => *a == b.as_str() && ai == bi && ap == bp,
1637            (
1638                RowKey::RequestToolContentFile { response_id: a, index: ai, part_index: ap },
1639                OwnedRowKey::RequestToolContentFile { response_id: b, index: bi, part_index: bp },
1640            ) => *a == b.as_str() && ai == bi && ap == bp,
1641            (
1642                RowKey::RequestVectorChoice { response_id: a, choice_index: ai },
1643                OwnedRowKey::RequestVectorChoice { response_id: b, choice_index: bi },
1644            ) => *a == b.as_str() && ai == bi,
1645            (
1646                RowKey::RequestVectorChoiceContentText { response_id: a, choice_index: ai, part_index: ap },
1647                OwnedRowKey::RequestVectorChoiceContentText { response_id: b, choice_index: bi, part_index: bp },
1648            ) => *a == b.as_str() && ai == bi && ap == bp,
1649            (
1650                RowKey::RequestVectorChoiceContentImage { response_id: a, choice_index: ai, part_index: ap },
1651                OwnedRowKey::RequestVectorChoiceContentImage { response_id: b, choice_index: bi, part_index: bp },
1652            ) => *a == b.as_str() && ai == bi && ap == bp,
1653            (
1654                RowKey::RequestVectorChoiceContentAudio { response_id: a, choice_index: ai, part_index: ap },
1655                OwnedRowKey::RequestVectorChoiceContentAudio { response_id: b, choice_index: bi, part_index: bp },
1656            ) => *a == b.as_str() && ai == bi && ap == bp,
1657            (
1658                RowKey::RequestVectorChoiceContentVideo { response_id: a, choice_index: ai, part_index: ap },
1659                OwnedRowKey::RequestVectorChoiceContentVideo { response_id: b, choice_index: bi, part_index: bp },
1660            ) => *a == b.as_str() && ai == bi && ap == bp,
1661            (
1662                RowKey::RequestVectorChoiceContentFile { response_id: a, choice_index: ai, part_index: ap },
1663                OwnedRowKey::RequestVectorChoiceContentFile { response_id: b, choice_index: bi, part_index: bp },
1664            ) => *a == b.as_str() && ai == bi && ap == bp,
1665            (
1666                RowKey::ResponseVectorVote { response_id: a, agent_instance_hierarchy: ah },
1667                OwnedRowKey::ResponseVectorVote { response_id: b, agent_instance_hierarchy: bh },
1668            ) => *a == b.as_str() && *ah == bh.as_str(),
1669            _ => false,
1670        }
1671    }
1672
1673    pub fn to_owned_key(&self) -> OwnedRowKey {
1674        match self {
1675            RowKey::MessageQueueContent { response_id, message_queue_content_id } => {
1676                OwnedRowKey::MessageQueueContent {
1677                    response_id: (*response_id).to_owned(),
1678                    message_queue_content_id: *message_queue_content_id,
1679                }
1680            }
1681            RowKey::ToolResponse { response_id, index } => OwnedRowKey::ToolResponse {
1682                response_id: (*response_id).to_owned(),
1683                index: *index,
1684            },
1685            RowKey::AssistantRefusal { response_id, index } => OwnedRowKey::AssistantRefusal {
1686                response_id: (*response_id).to_owned(),
1687                index: *index,
1688            },
1689            RowKey::AssistantReasoning { response_id, index } => OwnedRowKey::AssistantReasoning {
1690                response_id: (*response_id).to_owned(),
1691                index: *index,
1692            },
1693            RowKey::AssistantToolCall { response_id, index, tool_call_index } => {
1694                OwnedRowKey::AssistantToolCall {
1695                    response_id: (*response_id).to_owned(),
1696                    index: *index,
1697                    tool_call_index: *tool_call_index,
1698                }
1699            }
1700            RowKey::AssistantContentText { response_id, index, part_index } => {
1701                OwnedRowKey::AssistantContentText {
1702                    response_id: (*response_id).to_owned(),
1703                    index: *index,
1704                    part_index: *part_index,
1705                }
1706            }
1707            RowKey::AssistantContentImage { response_id, index, part_index } => {
1708                OwnedRowKey::AssistantContentImage {
1709                    response_id: (*response_id).to_owned(),
1710                    index: *index,
1711                    part_index: *part_index,
1712                }
1713            }
1714            RowKey::AssistantContentAudio { response_id, index, part_index } => {
1715                OwnedRowKey::AssistantContentAudio {
1716                    response_id: (*response_id).to_owned(),
1717                    index: *index,
1718                    part_index: *part_index,
1719                }
1720            }
1721            RowKey::AssistantContentVideo { response_id, index, part_index } => {
1722                OwnedRowKey::AssistantContentVideo {
1723                    response_id: (*response_id).to_owned(),
1724                    index: *index,
1725                    part_index: *part_index,
1726                }
1727            }
1728            RowKey::AssistantContentFile { response_id, index, part_index } => {
1729                OwnedRowKey::AssistantContentFile {
1730                    response_id: (*response_id).to_owned(),
1731                    index: *index,
1732                    part_index: *part_index,
1733                }
1734            }
1735            RowKey::ToolContentText { response_id, index, part_index } => {
1736                OwnedRowKey::ToolContentText {
1737                    response_id: (*response_id).to_owned(),
1738                    index: *index,
1739                    part_index: *part_index,
1740                }
1741            }
1742            RowKey::ToolContentImage { response_id, index, part_index } => {
1743                OwnedRowKey::ToolContentImage {
1744                    response_id: (*response_id).to_owned(),
1745                    index: *index,
1746                    part_index: *part_index,
1747                }
1748            }
1749            RowKey::ToolContentAudio { response_id, index, part_index } => {
1750                OwnedRowKey::ToolContentAudio {
1751                    response_id: (*response_id).to_owned(),
1752                    index: *index,
1753                    part_index: *part_index,
1754                }
1755            }
1756            RowKey::ToolContentVideo { response_id, index, part_index } => {
1757                OwnedRowKey::ToolContentVideo {
1758                    response_id: (*response_id).to_owned(),
1759                    index: *index,
1760                    part_index: *part_index,
1761                }
1762            }
1763            RowKey::ToolContentFile { response_id, index, part_index } => {
1764                OwnedRowKey::ToolContentFile {
1765                    response_id: (*response_id).to_owned(),
1766                    index: *index,
1767                    part_index: *part_index,
1768                }
1769            }
1770            RowKey::RequestUserContentText { response_id, index, part_index } => OwnedRowKey::RequestUserContentText { response_id: (*response_id).to_owned(), index: *index, part_index: *part_index },
1771            RowKey::RequestUserContentImage { response_id, index, part_index } => OwnedRowKey::RequestUserContentImage { response_id: (*response_id).to_owned(), index: *index, part_index: *part_index },
1772            RowKey::RequestUserContentAudio { response_id, index, part_index } => OwnedRowKey::RequestUserContentAudio { response_id: (*response_id).to_owned(), index: *index, part_index: *part_index },
1773            RowKey::RequestUserContentVideo { response_id, index, part_index } => OwnedRowKey::RequestUserContentVideo { response_id: (*response_id).to_owned(), index: *index, part_index: *part_index },
1774            RowKey::RequestUserContentFile { response_id, index, part_index } => OwnedRowKey::RequestUserContentFile { response_id: (*response_id).to_owned(), index: *index, part_index: *part_index },
1775            RowKey::RequestAssistantRefusal { response_id, index } => OwnedRowKey::RequestAssistantRefusal { response_id: (*response_id).to_owned(), index: *index },
1776            RowKey::RequestAssistantReasoning { response_id, index } => OwnedRowKey::RequestAssistantReasoning { response_id: (*response_id).to_owned(), index: *index },
1777            RowKey::RequestAssistantToolCall { response_id, index, tool_call_index } => OwnedRowKey::RequestAssistantToolCall { response_id: (*response_id).to_owned(), index: *index, tool_call_index: *tool_call_index },
1778            RowKey::RequestAssistantContentText { response_id, index, part_index } => OwnedRowKey::RequestAssistantContentText { response_id: (*response_id).to_owned(), index: *index, part_index: *part_index },
1779            RowKey::RequestAssistantContentImage { response_id, index, part_index } => OwnedRowKey::RequestAssistantContentImage { response_id: (*response_id).to_owned(), index: *index, part_index: *part_index },
1780            RowKey::RequestAssistantContentAudio { response_id, index, part_index } => OwnedRowKey::RequestAssistantContentAudio { response_id: (*response_id).to_owned(), index: *index, part_index: *part_index },
1781            RowKey::RequestAssistantContentVideo { response_id, index, part_index } => OwnedRowKey::RequestAssistantContentVideo { response_id: (*response_id).to_owned(), index: *index, part_index: *part_index },
1782            RowKey::RequestAssistantContentFile { response_id, index, part_index } => OwnedRowKey::RequestAssistantContentFile { response_id: (*response_id).to_owned(), index: *index, part_index: *part_index },
1783            RowKey::RequestTool { response_id, index } => OwnedRowKey::RequestTool { response_id: (*response_id).to_owned(), index: *index },
1784            RowKey::RequestToolContentText { response_id, index, part_index } => OwnedRowKey::RequestToolContentText { response_id: (*response_id).to_owned(), index: *index, part_index: *part_index },
1785            RowKey::RequestToolContentImage { response_id, index, part_index } => OwnedRowKey::RequestToolContentImage { response_id: (*response_id).to_owned(), index: *index, part_index: *part_index },
1786            RowKey::RequestToolContentAudio { response_id, index, part_index } => OwnedRowKey::RequestToolContentAudio { response_id: (*response_id).to_owned(), index: *index, part_index: *part_index },
1787            RowKey::RequestToolContentVideo { response_id, index, part_index } => OwnedRowKey::RequestToolContentVideo { response_id: (*response_id).to_owned(), index: *index, part_index: *part_index },
1788            RowKey::RequestToolContentFile { response_id, index, part_index } => OwnedRowKey::RequestToolContentFile { response_id: (*response_id).to_owned(), index: *index, part_index: *part_index },
1789            RowKey::RequestVectorChoice { response_id, choice_index } => OwnedRowKey::RequestVectorChoice { response_id: (*response_id).to_owned(), choice_index: *choice_index },
1790            RowKey::RequestVectorChoiceContentText { response_id, choice_index, part_index } => OwnedRowKey::RequestVectorChoiceContentText { response_id: (*response_id).to_owned(), choice_index: *choice_index, part_index: *part_index },
1791            RowKey::RequestVectorChoiceContentImage { response_id, choice_index, part_index } => OwnedRowKey::RequestVectorChoiceContentImage { response_id: (*response_id).to_owned(), choice_index: *choice_index, part_index: *part_index },
1792            RowKey::RequestVectorChoiceContentAudio { response_id, choice_index, part_index } => OwnedRowKey::RequestVectorChoiceContentAudio { response_id: (*response_id).to_owned(), choice_index: *choice_index, part_index: *part_index },
1793            RowKey::RequestVectorChoiceContentVideo { response_id, choice_index, part_index } => OwnedRowKey::RequestVectorChoiceContentVideo { response_id: (*response_id).to_owned(), choice_index: *choice_index, part_index: *part_index },
1794            RowKey::RequestVectorChoiceContentFile { response_id, choice_index, part_index } => OwnedRowKey::RequestVectorChoiceContentFile { response_id: (*response_id).to_owned(), choice_index: *choice_index, part_index: *part_index },
1795            RowKey::ResponseVectorVote { response_id, agent_instance_hierarchy } => OwnedRowKey::ResponseVectorVote { response_id: (*response_id).to_owned(), agent_instance_hierarchy: (*agent_instance_hierarchy).to_owned() },
1796        }
1797    }
1798}
1799
1800// ---------------------------------------------------------------------
1801// Shadow body
1802// ---------------------------------------------------------------------
1803
1804/// Owned body stored in the shadow map. Compared by `PartialEq`
1805/// against an incoming [`RowValue`] via [`RowValue::body_eq`].
1806#[derive(Debug, Clone, PartialEq)]
1807pub enum RowBody {
1808    /// Empty marker — `MessageQueueContent` rows have no body; the
1809    /// shadow uses presence-only for skip detection (body_eq
1810    /// returns true for any matching key, so the second sight of
1811    /// the same content_id is treated as Skip).
1812    MessageQueueContent {},
1813    ToolResponse { tool_call_id: String },
1814    AssistantRefusal { text: String },
1815    AssistantReasoning { text: String },
1816    AssistantToolCall { tool_call_id: String, arguments: String },
1817    AssistantContentText { text: String },
1818    AssistantContentImage { image_url: ImageUrl },
1819    AssistantContentAudio { input_audio: InputAudio },
1820    AssistantContentVideo { video_url: VideoUrl },
1821    AssistantContentFile { file: File },
1822    ToolContentText { text: String },
1823    ToolContentImage { image_url: ImageUrl },
1824    ToolContentAudio { input_audio: InputAudio },
1825    ToolContentVideo { video_url: VideoUrl },
1826    ToolContentFile { file: File },
1827    RequestUserContentText { text: String },
1828    RequestUserContentImage { image_url: ImageUrl },
1829    RequestUserContentAudio { input_audio: InputAudio },
1830    RequestUserContentVideo { video_url: VideoUrl },
1831    RequestUserContentFile { file: File },
1832    RequestAssistantRefusal { text: String },
1833    RequestAssistantReasoning { text: String },
1834    RequestAssistantToolCall { tool_call_id: String, arguments: String },
1835    RequestAssistantContentText { text: String },
1836    RequestAssistantContentImage { image_url: ImageUrl },
1837    RequestAssistantContentAudio { input_audio: InputAudio },
1838    RequestAssistantContentVideo { video_url: VideoUrl },
1839    RequestAssistantContentFile { file: File },
1840    RequestTool { tool_call_id: String },
1841    RequestToolContentText { text: String },
1842    RequestToolContentImage { image_url: ImageUrl },
1843    RequestToolContentAudio { input_audio: InputAudio },
1844    RequestToolContentVideo { video_url: VideoUrl },
1845    RequestToolContentFile { file: File },
1846    RequestVectorChoice { key: String },
1847    RequestVectorChoiceContentText { text: String },
1848    RequestVectorChoiceContentImage { image_url: ImageUrl },
1849    RequestVectorChoiceContentAudio { input_audio: InputAudio },
1850    RequestVectorChoiceContentVideo { video_url: VideoUrl },
1851    RequestVectorChoiceContentFile { file: File },
1852    ResponseVectorVote { vote: Vec<rust_decimal::Decimal> },
1853}