Skip to main content

vtcode_collaboration_tool_specs/
lib.rs

1//! Passive JSON schemas for collaboration and human-in-the-loop tools.
2
3use serde_json::{Value, json};
4
5#[must_use]
6pub fn spawn_agent_parameters() -> Value {
7    json!({
8        "type": "object",
9        "properties": {
10            "agent_type": {"type": "string", "description": "Subagent type or name to run."},
11            "message": {"type": "string", "description": "Task prompt for the child agent. The child receives the same tools as the parent and may spawn its own child agents."},
12            "items": {
13                "type": "array",
14                "description": "Structured context items for the child agent.",
15                "items": collaboration_input_item_schema()
16            },
17            "fork_context": {"type": "boolean", "description": "Seed the child with the current thread history.", "default": false},
18            "model": {
19                "type": "string",
20                "description": "Optional subagent model override. Omit this field to reuse the parent session model. VT Code only honors this override when the current user turn explicitly asks for that model."
21            },
22            "reasoning_effort": {"type": "string", "description": "Optional subagent reasoning effort override."},
23            "background": {
24                "type": "boolean",
25                "description": "Run the child as a background task. Prefer this for long-lived helper work instead of blocking the current foreground turn with `wait_agent`.",
26                "default": false
27            },
28            "max_turns": {
29                "type": "integer",
30                "description": "Optional turn limit for this child. Values below 2 are promoted to 2 so the child can recover from an initial blocked or denied tool call."
31            }
32        }
33    })
34}
35
36#[must_use]
37pub fn send_input_parameters() -> Value {
38    json!({
39        "type": "object",
40        "required": ["target"],
41        "properties": {
42            "target": {"type": "string", "description": "Child agent id to message."},
43            "message": {"type": "string", "description": "Follow-up prompt for the child."},
44            "items": {
45                "type": "array",
46                "description": "Structured follow-up items.",
47                "items": collaboration_input_item_schema()
48            },
49            "interrupt": {"type": "boolean", "description": "When true, abort current child work and restart with this input. When false (default), queue the input; if the child is already running, it starts the child's next turn after the current turn completes.", "default": false}
50        }
51    })
52}
53
54#[must_use]
55pub fn wait_agent_parameters() -> Value {
56    json!({
57        "type": "object",
58        "required": ["targets"],
59        "properties": {
60            "targets": {
61                "type": "array",
62                "items": {"type": "string"},
63                "description": "Child agent ids to wait for. This blocks the current foreground turn until one target reaches a terminal state or the wait times out."
64            },
65            "timeout_ms": {
66                "type": "integer",
67                "description": "Optional wait timeout in milliseconds. Uses the session default timeout when omitted."
68            }
69        }
70    })
71}
72
73#[must_use]
74pub fn resume_agent_parameters() -> Value {
75    json!({
76        "type": "object",
77        "required": ["id"],
78        "properties": {
79            "id": {"type": "string", "description": "Child agent id to resume."}
80        }
81    })
82}
83
84#[must_use]
85pub fn close_agent_parameters() -> Value {
86    json!({
87        "type": "object",
88        "required": ["target"],
89        "properties": {
90            "target": {"type": "string", "description": "Child agent id to close."}
91        }
92    })
93}
94
95#[must_use]
96pub fn request_user_input_description() -> &'static str {
97    "Request user input for one to three short questions and wait for the response. Canonical HITL tool; only available in Plan mode."
98}
99
100#[must_use]
101pub fn request_user_input_parameters() -> Value {
102    json!({
103        "type": "object",
104        "additionalProperties": false,
105        "required": ["questions"],
106        "properties": {
107            "questions": {
108                "type": "array",
109                "description": "Questions to show the user (1-3). Prefer 1 unless multiple independent decisions block progress.",
110                "minItems": 1,
111                "maxItems": 3,
112                "items": {
113                    "type": "object",
114                    "additionalProperties": false,
115                    "required": ["id", "header", "question"],
116                    "properties": {
117                        "id": {
118                            "type": "string",
119                            "description": "Stable identifier for mapping answers (snake_case)."
120                        },
121                        "header": {
122                            "type": "string",
123                            "description": "Short header label shown in the UI (12 or fewer chars)."
124                        },
125                        "question": {
126                            "type": "string",
127                            "description": "Single-sentence prompt shown to the user."
128                        },
129                        "focus_area": {
130                            "type": "string",
131                            "description": "Optional short topic hint used to bias auto-suggested choices when options are omitted."
132                        },
133                        "analysis_hints": {
134                            "type": "array",
135                            "description": "Optional weakness/risk hints used by the UI to generate suggested options.",
136                            "items": {
137                                "type": "string"
138                            },
139                            "maxItems": 8
140                        },
141                        "options": {
142                            "type": "array",
143                            "description": "Optional 2-3 mutually exclusive choices. Put the recommended option first and suffix its label with \"(Recommended)\". Do not include an \"Other\" option; the UI provides that automatically. If omitted, the UI auto-suggests options using question text and hints.",
144                            "minItems": 2,
145                            "maxItems": 3,
146                            "items": {
147                                "type": "object",
148                                "additionalProperties": false,
149                                "required": ["label", "description"],
150                                "properties": {
151                                    "label": {
152                                        "type": "string",
153                                        "description": "User-facing label (1-5 words)."
154                                    },
155                                    "description": {
156                                        "type": "string",
157                                        "description": "One short sentence explaining impact/tradeoff if selected."
158                                    }
159                                }
160                            }
161                        }
162                    }
163                }
164            }
165        }
166    })
167}
168
169fn collaboration_input_item_schema() -> Value {
170    json!({
171        "type": "object",
172        "properties": {
173            "type": {"type": "string"},
174            "text": {"type": "string"},
175            "path": {"type": "string"},
176            "name": {"type": "string"},
177            "image_url": {"type": "string"}
178        },
179        "additionalProperties": false
180    })
181}
182
183#[cfg(test)]
184mod tests {
185    use super::*;
186    use serde_json::json;
187
188    #[test]
189    fn collaboration_schemas_keep_structured_items_consistent() {
190        let spawn_items = &spawn_agent_parameters()["properties"]["items"]["items"];
191        let send_items = &send_input_parameters()["properties"]["items"]["items"];
192
193        assert_eq!(spawn_items, send_items);
194        assert_eq!(spawn_items["additionalProperties"], json!(false));
195        assert_eq!(
196            spawn_items["properties"]["image_url"]["type"],
197            json!("string")
198        );
199    }
200    #[test]
201    fn collaboration_schemas_expose_updated_agent_description_text() {
202        let spawn = spawn_agent_parameters();
203        let send = send_input_parameters();
204        let wait = wait_agent_parameters();
205
206        assert_eq!(
207            spawn["properties"]["message"]["description"],
208            json!(
209                "Task prompt for the child agent. The child receives the same tools as the parent and may spawn its own child agents."
210            )
211        );
212        assert_eq!(
213            send["properties"]["target"]["description"],
214            json!("Child agent id to message.")
215        );
216        assert_eq!(
217            send["properties"]["interrupt"]["description"],
218            json!(
219                "When true, abort current child work and restart with this input. When false (default), queue the input; if the child is already running, it starts the child's next turn after the current turn completes."
220            )
221        );
222        assert_eq!(
223            wait["properties"]["targets"]["description"],
224            json!(
225                "Child agent ids to wait for. This blocks the current foreground turn until one target reaches a terminal state or the wait times out."
226            )
227        );
228        assert_eq!(
229            wait["properties"]["timeout_ms"]["description"],
230            json!(
231                "Optional wait timeout in milliseconds. Uses the session default timeout when omitted."
232            )
233        );
234    }
235
236    #[test]
237    fn request_user_input_schema_preserves_description_field_name() {
238        let schema = request_user_input_parameters();
239
240        assert_eq!(schema["required"], json!(["questions"]));
241        assert_eq!(
242            schema["properties"]["questions"]["items"]["properties"]["options"]["items"]["required"],
243            json!(["label", "description"])
244        );
245        assert_eq!(
246            schema["properties"]["questions"]["items"]["properties"]["options"]["items"]["properties"]
247                ["description"]["type"],
248            json!("string")
249        );
250    }
251}