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. Blocks the agent loop until the user responds. Returns the user's answers mapped by question id. Canonical HITL tool for the Planning workflow."
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}