Skip to main content

vtcode_utility_tool_specs/
collaboration.rs

1//! Collaboration and human-in-the-loop tool schemas.
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."},
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": "Model override. Omit to use parent model."
21            },
22            "reasoning_effort": {"type": "string", "description": "Reasoning effort override."},
23            "background": {
24                "type": "boolean",
25                "description": "Run agent in background. Returns immediately.",
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 background subprocess."},
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": "Model override. Omit to use parent model."
51            },
52            "reasoning_effort": {"type": "string", "description": "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": ["id"],
66        "properties": {
67            "id": {"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": ["ids"],
84        "properties": {
85            "ids": {
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": ["id"],
114        "properties": {
115            "id": {"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 for the Planning workflow when this tool is available."
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!("Task prompt for the child agent.")
235        );
236        assert_eq!(
237            send["properties"]["id"]["description"],
238            json!("Child agent id to message.")
239        );
240        assert_eq!(
241            spawn["properties"]["background"]["description"],
242            json!("Run agent in background. Returns immediately.")
243        );
244        assert_eq!(
245            spawn_background["properties"]["message"]["description"],
246            json!("Task prompt for the background subprocess.")
247        );
248        assert_eq!(
249            wait["properties"]["ids"]["description"],
250            json!(
251                "Child agent ids to wait for. This blocks the current foreground turn until one target reaches a terminal state or the wait times out."
252            )
253        );
254        assert_eq!(
255            wait["properties"]["timeout_ms"]["description"],
256            json!(
257                "Optional wait timeout in milliseconds. Uses the session default timeout when omitted."
258            )
259        );
260    }
261
262    #[test]
263    fn request_user_input_schema_preserves_description_field_name() {
264        let schema = request_user_input_parameters();
265
266        assert_eq!(schema["required"], json!(["questions"]));
267        assert_eq!(
268            schema["properties"]["questions"]["items"]["properties"]["options"]["items"]["required"],
269            json!(["label", "description"])
270        );
271        assert_eq!(
272            schema["properties"]["questions"]["items"]["properties"]["options"]["items"]["properties"]
273                ["description"]["type"],
274            json!("string")
275        );
276    }
277}