Skip to main content

opencode_sdk/types/
question.rs

1//! Question types for opencode_rs.
2//!
3//! These types model interactive question/answer prompts exposed by the
4//! OpenCode HTTP API.
5
6use serde::{Deserialize, Serialize};
7
8/// A selectable option for a question.
9#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
10#[serde(rename_all = "camelCase")]
11pub struct QuestionOption {
12    /// Label returned when selected.
13    pub label: String,
14    /// Human-readable description shown to users.
15    pub description: String,
16}
17
18/// A single question item.
19#[derive(Debug, Clone, Serialize, Deserialize)]
20#[serde(rename_all = "camelCase")]
21pub struct QuestionInfo {
22    /// The question text.
23    pub question: String,
24    /// Short header/title for the question.
25    pub header: String,
26    /// Available options.
27    pub options: Vec<QuestionOption>,
28    /// Whether multiple options can be selected.
29    #[serde(default, skip_serializing_if = "Option::is_none")]
30    pub multiple: Option<bool>,
31    /// Whether custom free-form input is allowed.
32    #[serde(default, skip_serializing_if = "Option::is_none")]
33    pub custom: Option<bool>,
34}
35
36/// Optional tool context for a question request.
37#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
38#[serde(rename_all = "camelCase")]
39pub struct QuestionToolRef {
40    /// Message ID containing the originating tool call.
41    pub message_id: String,
42    /// Tool call ID.
43    pub call_id: String,
44}
45
46/// A pending question request from the server.
47#[derive(Debug, Clone, Serialize, Deserialize)]
48#[serde(rename_all = "camelCase")]
49pub struct QuestionRequest {
50    /// Unique request identifier.
51    pub id: String,
52    /// Session ID that this question belongs to.
53    pub session_id: String,
54    /// Question list (typically one item, but API supports many).
55    pub questions: Vec<QuestionInfo>,
56    /// Optional tool reference context.
57    #[serde(default, skip_serializing_if = "Option::is_none")]
58    pub tool: Option<QuestionToolRef>,
59}
60
61/// One answer entry corresponding to one question.
62///
63/// Each string is a selected option label.
64pub type QuestionAnswer = Vec<String>;
65
66/// Request body for replying to a question.
67#[derive(Debug, Clone, Serialize, Deserialize)]
68#[serde(rename_all = "camelCase")]
69pub struct QuestionReplyRequest {
70    /// User answers in order of questions.
71    pub answers: Vec<QuestionAnswer>,
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77
78    #[test]
79    fn test_question_request_deserialize() {
80        let json = r#"{
81            "id": "q-1",
82            "sessionId": "s-1",
83            "questions": [
84                {
85                    "question": "Pick one",
86                    "header": "Choice",
87                    "options": [
88                        {"label": "yes", "description": "Allow"},
89                        {"label": "no", "description": "Reject"}
90                    ],
91                    "multiple": false,
92                    "custom": true
93                }
94            ],
95            "tool": {"messageId": "m-1", "callId": "c-1"}
96        }"#;
97
98        let req: QuestionRequest = serde_json::from_str(json).unwrap();
99        assert_eq!(req.id, "q-1");
100        assert_eq!(req.session_id, "s-1");
101        assert_eq!(req.questions.len(), 1);
102        assert_eq!(req.questions[0].header, "Choice");
103        assert!(req.tool.is_some());
104    }
105
106    #[test]
107    fn test_question_reply_request_serialize() {
108        let body = QuestionReplyRequest {
109            answers: vec![vec!["yes".to_string()], vec!["custom-value".to_string()]],
110        };
111
112        let json = serde_json::to_string(&body).unwrap();
113        assert!(json.contains(r#""answers":[["yes"],["custom-value"]]"#));
114    }
115}