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": "Mark the delegated child thread as background-style work. This still creates a normal child agent thread in the current session; it does not launch the managed background subprocess runtime.",
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 spawn_background_subprocess_parameters() -> Value {
38    json!({
39        "type": "object",
40        "properties": {
41            "agent_type": {"type": "string", "description": "Background-enabled subagent type or name to run."},
42            "message": {"type": "string", "description": "Task prompt for the managed background subprocess. Use this for durable helper work that should be launched once and then managed outside the current foreground turn."},
43            "items": {
44                "type": "array",
45                "description": "Structured context items for the background subprocess.",
46                "items": collaboration_input_item_schema()
47            },
48            "model": {
49                "type": "string",
50                "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."
51            },
52            "reasoning_effort": {"type": "string", "description": "Optional subagent reasoning effort override."},
53            "max_turns": {
54                "type": "integer",
55                "description": "Optional turn limit for the launched background subprocess task before it reports readiness. Values below 4 are promoted to 4 for background launches."
56            }
57        }
58    })
59}
60
61#[must_use]
62pub fn send_input_parameters() -> Value {
63    json!({
64        "type": "object",
65        "required": ["target"],
66        "properties": {
67            "target": {"type": "string", "description": "Child agent id to message."},
68            "message": {"type": "string", "description": "Follow-up prompt for the child."},
69            "items": {
70                "type": "array",
71                "description": "Structured follow-up items.",
72                "items": collaboration_input_item_schema()
73            },
74            "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}
75        }
76    })
77}
78
79#[must_use]
80pub fn wait_agent_parameters() -> Value {
81    json!({
82        "type": "object",
83        "required": ["targets"],
84        "properties": {
85            "targets": {
86                "type": "array",
87                "items": {"type": "string"},
88                "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."
89            },
90            "timeout_ms": {
91                "type": "integer",
92                "description": "Optional wait timeout in milliseconds. Uses the session default timeout when omitted."
93            }
94        }
95    })
96}
97
98#[must_use]
99pub fn resume_agent_parameters() -> Value {
100    json!({
101        "type": "object",
102        "required": ["id"],
103        "properties": {
104            "id": {"type": "string", "description": "Child agent id to resume."}
105        }
106    })
107}
108
109#[must_use]
110pub fn close_agent_parameters() -> Value {
111    json!({
112        "type": "object",
113        "required": ["target"],
114        "properties": {
115            "target": {"type": "string", "description": "Child agent id to close."}
116        }
117    })
118}
119
120#[must_use]
121pub fn request_user_input_description() -> &'static str {
122    "Request user input for one to three short questions and wait for the response. Canonical HITL tool; only available in Plan mode."
123}
124
125#[must_use]
126pub fn request_user_input_parameters() -> Value {
127    json!({
128        "type": "object",
129        "additionalProperties": false,
130        "required": ["questions"],
131        "properties": {
132            "questions": {
133                "type": "array",
134                "description": "Questions to show the user (1-3). Prefer 1 unless multiple independent decisions block progress.",
135                "minItems": 1,
136                "maxItems": 3,
137                "items": {
138                    "type": "object",
139                    "additionalProperties": false,
140                    "required": ["id", "header", "question"],
141                    "properties": {
142                        "id": {
143                            "type": "string",
144                            "description": "Stable identifier for mapping answers (snake_case)."
145                        },
146                        "header": {
147                            "type": "string",
148                            "description": "Short header label shown in the UI (12 or fewer chars)."
149                        },
150                        "question": {
151                            "type": "string",
152                            "description": "Single-sentence prompt shown to the user."
153                        },
154                        "focus_area": {
155                            "type": "string",
156                            "description": "Optional short topic hint used to bias auto-suggested choices when options are omitted."
157                        },
158                        "analysis_hints": {
159                            "type": "array",
160                            "description": "Optional weakness/risk hints used by the UI to generate suggested options.",
161                            "items": {
162                                "type": "string"
163                            },
164                            "maxItems": 8
165                        },
166                        "options": {
167                            "type": "array",
168                            "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.",
169                            "minItems": 2,
170                            "maxItems": 3,
171                            "items": {
172                                "type": "object",
173                                "additionalProperties": false,
174                                "required": ["label", "description"],
175                                "properties": {
176                                    "label": {
177                                        "type": "string",
178                                        "description": "User-facing label (1-5 words)."
179                                    },
180                                    "description": {
181                                        "type": "string",
182                                        "description": "One short sentence explaining impact/tradeoff if selected."
183                                    }
184                                }
185                            }
186                        }
187                    }
188                }
189            }
190        }
191    })
192}
193
194fn collaboration_input_item_schema() -> Value {
195    json!({
196        "type": "object",
197        "properties": {
198            "type": {"type": "string"},
199            "text": {"type": "string"},
200            "path": {"type": "string"},
201            "name": {"type": "string"},
202            "image_url": {"type": "string"}
203        },
204        "additionalProperties": false
205    })
206}
207
208#[cfg(test)]
209mod tests {
210    use super::*;
211    use serde_json::json;
212
213    #[test]
214    fn collaboration_schemas_keep_structured_items_consistent() {
215        let spawn_items = &spawn_agent_parameters()["properties"]["items"]["items"];
216        let send_items = &send_input_parameters()["properties"]["items"]["items"];
217
218        assert_eq!(spawn_items, send_items);
219        assert_eq!(spawn_items["additionalProperties"], json!(false));
220        assert_eq!(
221            spawn_items["properties"]["image_url"]["type"],
222            json!("string")
223        );
224    }
225    #[test]
226    fn collaboration_schemas_expose_updated_agent_description_text() {
227        let spawn = spawn_agent_parameters();
228        let spawn_background = spawn_background_subprocess_parameters();
229        let send = send_input_parameters();
230        let wait = wait_agent_parameters();
231
232        assert_eq!(
233            spawn["properties"]["message"]["description"],
234            json!(
235                "Task prompt for the child agent. The child receives the same tools as the parent and may spawn its own child agents."
236            )
237        );
238        assert_eq!(
239            send["properties"]["target"]["description"],
240            json!("Child agent id to message.")
241        );
242        assert_eq!(
243            send["properties"]["interrupt"]["description"],
244            json!(
245                "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."
246            )
247        );
248        assert_eq!(
249            spawn["properties"]["background"]["description"],
250            json!(
251                "Mark the delegated child thread as background-style work. This still creates a normal child agent thread in the current session; it does not launch the managed background subprocess runtime."
252            )
253        );
254        assert_eq!(
255            spawn_background["properties"]["message"]["description"],
256            json!(
257                "Task prompt for the managed background subprocess. Use this for durable helper work that should be launched once and then managed outside the current foreground turn."
258            )
259        );
260        assert_eq!(
261            wait["properties"]["targets"]["description"],
262            json!(
263                "Child agent ids to wait for. This blocks the current foreground turn until one target reaches a terminal state or the wait times out."
264            )
265        );
266        assert_eq!(
267            wait["properties"]["timeout_ms"]["description"],
268            json!(
269                "Optional wait timeout in milliseconds. Uses the session default timeout when omitted."
270            )
271        );
272    }
273
274    #[test]
275    fn request_user_input_schema_preserves_description_field_name() {
276        let schema = request_user_input_parameters();
277
278        assert_eq!(schema["required"], json!(["questions"]));
279        assert_eq!(
280            schema["properties"]["questions"]["items"]["properties"]["options"]["items"]["required"],
281            json!(["label", "description"])
282        );
283        assert_eq!(
284            schema["properties"]["questions"]["items"]["properties"]["options"]["items"]["properties"]
285                ["description"]["type"],
286            json!("string")
287        );
288    }
289}