Skip to main content

formal_ai/protocol/
output.rs

1use serde::{Deserialize, Serialize};
2
3use crate::engine::ThinkingStep;
4
5#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
6pub struct ResponseObject {
7    pub id: String,
8    pub object: String,
9    pub created_at: u64,
10    pub status: String,
11    pub model: String,
12    pub output: Vec<ResponseOutputItem>,
13    pub usage: ResponseUsage,
14    #[serde(default)]
15    pub evidence_links: Vec<String>,
16    #[serde(default, skip_serializing_if = "Vec::is_empty")]
17    pub thinking_steps: Vec<ThinkingStep>,
18}
19
20impl ResponseObject {
21    /// The assistant message items in `output` (text), skipping any tool calls.
22    #[must_use]
23    pub fn output_messages(&self) -> Vec<&ResponseOutputMessage> {
24        self.output
25            .iter()
26            .filter_map(|item| match item {
27                ResponseOutputItem::Message(message) => Some(message),
28                ResponseOutputItem::FunctionCall(_)
29                | ResponseOutputItem::WebSearchCall(_)
30                | ResponseOutputItem::Reasoning(_) => None,
31            })
32            .collect()
33    }
34
35    /// The function tool calls this response is requesting (issue #468 agentic
36    /// loop), if any. Non-empty exactly when the agentic planner emitted a step.
37    #[must_use]
38    pub fn function_calls(&self) -> Vec<&ResponseFunctionToolCall> {
39        self.output
40            .iter()
41            .filter_map(|item| match item {
42                ResponseOutputItem::FunctionCall(call) => Some(call),
43                ResponseOutputItem::Message(_)
44                | ResponseOutputItem::WebSearchCall(_)
45                | ResponseOutputItem::Reasoning(_) => None,
46            })
47            .collect()
48    }
49}
50
51/// One item in a Responses `output` array: an assistant message, a tool call, or
52/// a reasoning summary.
53///
54/// A `FunctionCall` is a tool the client must execute (issue #468). Serialized
55/// untagged so each item keeps its native OpenAI shape — a message carries
56/// `type:"message"`, a call carries `type:"function_call"`, and reasoning
57/// carries `type:"reasoning"`.
58#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
59#[serde(untagged)]
60pub enum ResponseOutputItem {
61    /// A function tool call (`type:"function_call"`).
62    FunctionCall(ResponseFunctionToolCall),
63    /// A server-hosted web search (`type:"web_search_call"`).
64    WebSearchCall(ResponseWebSearchToolCall),
65    /// An assistant message (`type:"message"`).
66    Message(ResponseOutputMessage),
67    /// A reasoning summary (`type:"reasoning"`).
68    Reasoning(ResponseReasoningItem),
69}
70
71/// A completed server-hosted web search on the Responses surface. Unlike a
72/// `function_call`, this item is observational: the client must not attempt to
73/// execute a local function named `web_search`.
74#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
75pub struct ResponseWebSearchToolCall {
76    pub id: String,
77    #[serde(rename = "type")]
78    pub kind: String,
79    pub status: String,
80    pub action: ResponseWebSearchAction,
81}
82
83#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
84pub struct ResponseWebSearchAction {
85    #[serde(rename = "type")]
86    pub kind: String,
87    pub query: String,
88    pub queries: Vec<String>,
89}
90
91/// A function tool call emitted on the Responses surface (`type:"function_call"`).
92///
93/// It mirrors the Chat Completions `tool_calls` shape so an agentic CLI can execute
94/// it and feed the result back as a `function_call_output` item.
95#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
96pub struct ResponseFunctionToolCall {
97    pub id: String,
98    #[serde(rename = "type", default = "function_call_kind")]
99    pub kind: String,
100    pub call_id: String,
101    pub name: String,
102    #[serde(default, skip_serializing_if = "Option::is_none")]
103    pub namespace: Option<String>,
104    pub arguments: String,
105    pub status: String,
106}
107
108pub(super) fn function_call_kind() -> String {
109    String::from("function_call")
110}
111
112#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
113pub struct ResponseOutputMessage {
114    pub id: String,
115    #[serde(rename = "type")]
116    pub kind: String,
117    pub role: String,
118    pub content: Vec<ResponseOutputContent>,
119    #[serde(default, skip_serializing_if = "Vec::is_empty")]
120    pub thinking_steps: Vec<ThinkingStep>,
121}
122
123#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
124pub struct ResponseOutputContent {
125    #[serde(rename = "type")]
126    pub kind: String,
127    pub text: String,
128}
129
130#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
131pub struct ResponseReasoningItem {
132    pub id: String,
133    #[serde(rename = "type")]
134    pub kind: String,
135    pub summary: Vec<ResponseReasoningSummaryText>,
136}
137
138#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
139pub struct ResponseReasoningSummaryText {
140    #[serde(rename = "type")]
141    pub kind: String,
142    pub text: String,
143}
144
145#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
146pub struct ResponseUsage {
147    pub input_tokens: u32,
148    pub output_tokens: u32,
149    pub total_tokens: u32,
150}