Skip to main content

vtcode_llm/open_responses/bridge/
mod.rs

1//! Bridge layer for converting VT Code events to Open Responses format.
2//!
3//! This module provides adapters to convert VT Code's internal `ThreadEvent`
4//! and `ThreadItem` types to Open Responses-conformant structures, enabling
5//! backwards compatibility during migration.
6
7use serde_json::json;
8
9use super::{
10    ContentPart, CustomItem, FunctionCallItem, ItemStatus, MessageItem, MessageRole, OpenResponseError, OpenUsage,
11    OutputItem, ReasoningItem, Response, ResponseStatus, ResponseStreamEvent, StreamEventEmitter,
12    response::{generate_item_id, generate_response_id},
13};
14use crate::provider::{FinishReason, NormalizedStreamEvent, ToolCall};
15use vtcode_exec_events::{
16    CommandExecutionStatus, McpToolCallStatus, PatchApplyStatus, ThreadEvent, ThreadItem, ThreadItemDetails,
17    ToolOutputItem,
18};
19
20/// Builder for constructing Open Responses `Response` objects from VT Code events.
21///
22/// Tracks streaming state and maintains the mapping between VT Code item IDs
23/// and Open Responses output item indices.
24#[derive(Debug)]
25#[must_use = "build the response before discarding the builder"]
26pub struct ResponseBuilder {
27    response: Response,
28    next_output_index: usize,
29    item_id_to_index: hashbrown::HashMap<String, usize>,
30    active_items: hashbrown::HashMap<String, ActiveItemState>,
31    tool_call_correlation_ids: hashbrown::HashMap<String, String>,
32    used_tool_call_ids: hashbrown::HashSet<String>,
33    normalized: NormalizedBridgeState,
34}
35
36/// State for an active (in-progress) streaming item.
37#[derive(Debug, Clone)]
38struct ActiveItemState {
39    output_index: usize,
40    content_index: usize,
41    /// Previous text content for safe delta computation (avoids UTF-8 slicing issues)
42    prev_text: String,
43}
44
45#[derive(Debug, Clone)]
46struct NormalizedFunctionCallState {
47    item_id: String,
48    output_index: usize,
49    name: Option<String>,
50    arguments: String,
51}
52
53#[derive(Debug, Default)]
54struct NormalizedBridgeState {
55    response_started: bool,
56    message_item_id: Option<String>,
57    reasoning_item_id: Option<String>,
58    tool_calls: hashbrown::HashMap<String, NormalizedFunctionCallState>,
59}
60impl ResponseBuilder {
61    /// Creates a new response builder with the given model.
62    pub fn new(model: impl Into<String>) -> Self {
63        let response = Response::new(generate_response_id(), model);
64        Self {
65            response,
66            next_output_index: 0,
67            item_id_to_index: hashbrown::HashMap::new(),
68            active_items: hashbrown::HashMap::new(),
69            tool_call_correlation_ids: hashbrown::HashMap::new(),
70            used_tool_call_ids: hashbrown::HashSet::new(),
71            normalized: NormalizedBridgeState::default(),
72        }
73    }
74
75    /// Returns a reference to the current response.
76    pub(crate) fn response(&self) -> &Response {
77        &self.response
78    }
79
80    /// Returns a mutable reference to the current response.
81    pub fn response_mut(&mut self) -> &mut Response {
82        &mut self.response
83    }
84
85    /// Returns the response ID.
86    pub fn response_id(&self) -> &str {
87        &self.response.id
88    }
89
90    /// Consumes the builder and returns the final response.
91    #[must_use]
92    pub fn build(self) -> Response {
93        self.response
94    }
95}
96
97mod emitter;
98mod items;
99mod normalized;
100
101pub use emitter::DualEventEmitter;
102
103#[cfg(test)]
104mod tests;