Skip to main content

opendev_models/
api.rs

1//! Shared API response models used by Web routes.
2
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6use crate::message::ToolCall;
7
8/// Serialization view of a ToolCall for API responses.
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct ToolCallResponse {
11    pub id: String,
12    pub name: String,
13    pub parameters: HashMap<String, serde_json::Value>,
14    #[serde(skip_serializing_if = "Option::is_none")]
15    pub result: Option<String>,
16    #[serde(skip_serializing_if = "Option::is_none")]
17    pub error: Option<String>,
18    #[serde(skip_serializing_if = "Option::is_none")]
19    pub result_summary: Option<String>,
20    #[serde(skip_serializing_if = "Option::is_none")]
21    pub approved: Option<bool>,
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub nested_tool_calls: Option<Vec<ToolCallResponse>>,
24}
25
26/// Response model for a chat message.
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct MessageResponse {
29    pub role: String,
30    pub content: String,
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub timestamp: Option<String>,
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub tool_calls: Option<Vec<ToolCallResponse>>,
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub thinking_trace: Option<String>,
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub reasoning_content: Option<String>,
39}
40
41/// Session information model.
42#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct SessionResponse {
44    pub id: String,
45    pub working_dir: String,
46    pub created_at: String,
47    pub updated_at: String,
48    pub message_count: usize,
49    pub total_tokens: u64,
50    #[serde(skip_serializing_if = "Option::is_none")]
51    pub title: Option<String>,
52    #[serde(default)]
53    pub has_session_model: bool,
54}
55
56/// Recursively convert a ToolCall to ToolCallResponse.
57///
58/// Handles nested calls and coerces non-string results to JSON strings.
59pub fn tool_call_to_response(tc: &ToolCall) -> ToolCallResponse {
60    let nested = if tc.nested_tool_calls.is_empty() {
61        None
62    } else {
63        Some(
64            tc.nested_tool_calls
65                .iter()
66                .map(tool_call_to_response)
67                .collect(),
68        )
69    };
70
71    let result = tc.result.as_ref().map(|r| {
72        if let serde_json::Value::String(s) = r {
73            s.clone()
74        } else {
75            serde_json::to_string(r).unwrap_or_else(|_| r.to_string())
76        }
77    });
78
79    ToolCallResponse {
80        id: tc.id.clone(),
81        name: tc.name.clone(),
82        parameters: tc.parameters.clone(),
83        result,
84        error: tc.error.clone(),
85        result_summary: tc.result_summary.clone(),
86        approved: Some(tc.approved),
87        nested_tool_calls: nested,
88    }
89}
90
91#[cfg(test)]
92#[path = "api_tests.rs"]
93mod tests;