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 agent_parameters() -> Value {
7    json!({
8        "type": "object",
9        "required": ["action"],
10        "properties": {
11            "action": {
12                "type": "string",
13                "enum": ["spawn", "spawn_subprocess", "send_input", "resume", "wait", "close"],
14                "description": "spawn: delegate a scoped task to a child agent (requires message). spawn_subprocess: launch a managed background subprocess for long-running daemons (requires message). send_input: send follow-up input to a running child (requires id + message or items). resume: reopen a completed or closed child from saved context (requires id). wait: block the current foreground turn until one or more children reach a terminal state (requires ids). close: cancel and free a child's tool budget (requires id)."
15            },
16            "agent_type": {"type": "string", "description": "spawn or spawn_subprocess: subagent type or name to run."},
17            "message": {"type": "string", "description": "spawn or spawn_subprocess: task prompt. send_input: follow-up prompt for the child."},
18            "items": {
19                "type": "array",
20                "description": "Structured context items for the child.",
21                "items": collaboration_input_item_schema()
22            },
23            "fork_context": {"type": "boolean", "description": "spawn: seed the child with the current thread history.", "default": false},
24            "model": {"type": "string", "description": "spawn or spawn_subprocess: model override. Omit to use parent model."},
25            "reasoning_effort": {"type": "string", "description": "spawn or spawn_subprocess: reasoning effort override."},
26            "background": {"type": "boolean", "description": "spawn: run the child agent in background and return immediately.", "default": false},
27            "max_turns": {"type": "integer", "description": "spawn or spawn_subprocess: optional turn limit for the child."},
28            "id": {"type": "string", "description": "send_input, resume, or close: child agent id."},
29            "interrupt": {"type": "boolean", "description": "send_input: abort current child work and restart with this input; false queues it.", "default": false},
30            "ids": {
31                "type": "array",
32                "items": {"type": "string"},
33                "description": "wait: child agent ids to wait for. Blocks the current foreground turn until one target reaches a terminal state or the wait times out."
34            },
35            "timeout_ms": {
36                "type": "integer",
37                "description": "wait: optional wait timeout in milliseconds. Uses the session default timeout when omitted."
38            }
39        }
40    })
41}
42
43#[must_use]
44pub fn spawn_agent_parameters() -> Value {
45    json!({
46        "type": "object",
47        "properties": {
48            "agent_type": {"type": "string", "description": "Subagent type or name to run."},
49            "message": {"type": "string", "description": "Task prompt for the child agent."},
50            "items": {
51                "type": "array",
52                "description": "Structured context items for the child agent.",
53                "items": collaboration_input_item_schema()
54            },
55            "fork_context": {"type": "boolean", "description": "Seed the child with the current thread history.", "default": false},
56            "model": {
57                "type": "string",
58                "description": "Model override. Omit to use parent model."
59            },
60            "reasoning_effort": {"type": "string", "description": "Reasoning effort override."},
61            "background": {
62                "type": "boolean",
63                "description": "Run agent in background. Returns immediately.",
64                "default": false
65            },
66            "max_turns": {
67                "type": "integer",
68                "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."
69            }
70        }
71    })
72}
73
74#[must_use]
75pub fn spawn_background_subprocess_parameters() -> Value {
76    json!({
77        "type": "object",
78        "properties": {
79            "agent_type": {"type": "string", "description": "Background-enabled subagent type or name to run."},
80            "message": {"type": "string", "description": "Task prompt for the background subprocess."},
81            "items": {
82                "type": "array",
83                "description": "Structured context items for the background subprocess.",
84                "items": collaboration_input_item_schema()
85            },
86            "model": {
87                "type": "string",
88                "description": "Model override. Omit to use parent model."
89            },
90            "reasoning_effort": {"type": "string", "description": "Reasoning effort override."},
91            "max_turns": {
92                "type": "integer",
93                "description": "Optional turn limit for the launched background subprocess task before it reports readiness. Values below 4 are promoted to 4 for background launches."
94            }
95        }
96    })
97}
98
99#[must_use]
100pub fn send_input_parameters() -> Value {
101    json!({
102        "type": "object",
103        "required": ["id"],
104        "properties": {
105            "id": {"type": "string", "description": "Child agent id to message."},
106            "message": {"type": "string", "description": "Follow-up prompt for the child."},
107            "items": {
108                "type": "array",
109                "description": "Structured follow-up items.",
110                "items": collaboration_input_item_schema()
111            },
112            "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}
113        }
114    })
115}
116
117#[must_use]
118pub fn wait_agent_parameters() -> Value {
119    json!({
120        "type": "object",
121        "required": ["ids"],
122        "properties": {
123            "ids": {
124                "type": "array",
125                "items": {"type": "string"},
126                "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."
127            },
128            "timeout_ms": {
129                "type": "integer",
130                "description": "Optional wait timeout in milliseconds. Uses the session default timeout when omitted."
131            }
132        }
133    })
134}
135
136#[must_use]
137pub fn resume_agent_parameters() -> Value {
138    json!({
139        "type": "object",
140        "required": ["id"],
141        "properties": {
142            "id": {"type": "string", "description": "Child agent id to resume."}
143        }
144    })
145}
146
147#[must_use]
148pub fn close_agent_parameters() -> Value {
149    json!({
150        "type": "object",
151        "required": ["id"],
152        "properties": {
153            "id": {"type": "string", "description": "Child agent id to close."}
154        }
155    })
156}
157
158#[must_use]
159pub fn request_user_input_description() -> &'static str {
160    "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."
161}
162
163#[must_use]
164pub fn request_user_input_parameters() -> Value {
165    json!({
166        "type": "object",
167        "additionalProperties": false,
168        "required": ["questions"],
169        "properties": {
170            "questions": {
171                "type": "array",
172                "description": "Questions to show the user (1-3). Prefer 1 unless multiple independent decisions block progress.",
173                "minItems": 1,
174                "maxItems": 3,
175                "items": {
176                    "type": "object",
177                    "additionalProperties": false,
178                    "required": ["id", "header", "question"],
179                    "properties": {
180                        "id": {
181                            "type": "string",
182                            "description": "Stable identifier for mapping answers (snake_case)."
183                        },
184                        "header": {
185                            "type": "string",
186                            "description": "Short header label shown in the UI (12 or fewer chars)."
187                        },
188                        "question": {
189                            "type": "string",
190                            "description": "Single-sentence prompt shown to the user."
191                        },
192                        "focus_area": {
193                            "type": "string",
194                            "description": "Optional short topic hint used to bias auto-suggested choices when options are omitted."
195                        },
196                        "analysis_hints": {
197                            "type": "array",
198                            "description": "Optional weakness/risk hints used by the UI to generate suggested options.",
199                            "items": {
200                                "type": "string"
201                            },
202                            "maxItems": 8
203                        },
204                        "options": {
205                            "type": "array",
206                            "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.",
207                            "minItems": 2,
208                            "maxItems": 3,
209                            "items": {
210                                "type": "object",
211                                "additionalProperties": false,
212                                "required": ["label", "description"],
213                                "properties": {
214                                    "label": {
215                                        "type": "string",
216                                        "description": "User-facing label (1-5 words)."
217                                    },
218                                    "description": {
219                                        "type": "string",
220                                        "description": "One short sentence explaining impact/tradeoff if selected."
221                                    }
222                                }
223                            }
224                        }
225                    }
226                }
227            }
228        }
229    })
230}
231
232fn collaboration_input_item_schema() -> Value {
233    json!({
234        "type": "object",
235        "properties": {
236            "type": {"type": "string"},
237            "text": {"type": "string"},
238            "path": {"type": "string"},
239            "name": {"type": "string"},
240            "image_url": {"type": "string"}
241        },
242        "additionalProperties": false
243    })
244}
245
246#[cfg(test)]
247mod tests {
248    use super::*;
249    use serde_json::json;
250
251    #[test]
252    fn collaboration_schemas_keep_structured_items_consistent() {
253        let spawn_items = &spawn_agent_parameters()["properties"]["items"]["items"];
254        let send_items = &send_input_parameters()["properties"]["items"]["items"];
255
256        assert_eq!(spawn_items, send_items);
257        assert_eq!(spawn_items["additionalProperties"], json!(false));
258        assert_eq!(
259            spawn_items["properties"]["image_url"]["type"],
260            json!("string")
261        );
262    }
263    #[test]
264    fn collaboration_schemas_expose_updated_agent_description_text() {
265        let spawn = spawn_agent_parameters();
266        let spawn_background = spawn_background_subprocess_parameters();
267        let send = send_input_parameters();
268        let wait = wait_agent_parameters();
269
270        assert_eq!(
271            spawn["properties"]["message"]["description"],
272            json!("Task prompt for the child agent.")
273        );
274        assert_eq!(
275            send["properties"]["id"]["description"],
276            json!("Child agent id to message.")
277        );
278        assert_eq!(
279            spawn["properties"]["background"]["description"],
280            json!("Run agent in background. Returns immediately.")
281        );
282        assert_eq!(
283            spawn_background["properties"]["message"]["description"],
284            json!("Task prompt for the background subprocess.")
285        );
286        assert_eq!(
287            wait["properties"]["ids"]["description"],
288            json!(
289                "Child agent ids to wait for. This blocks the current foreground turn until one target reaches a terminal state or the wait times out."
290            )
291        );
292        assert_eq!(
293            wait["properties"]["timeout_ms"]["description"],
294            json!(
295                "Optional wait timeout in milliseconds. Uses the session default timeout when omitted."
296            )
297        );
298    }
299
300    #[test]
301    fn request_user_input_schema_preserves_description_field_name() {
302        let schema = request_user_input_parameters();
303
304        assert_eq!(schema["required"], json!(["questions"]));
305        assert_eq!(
306            schema["properties"]["questions"]["items"]["properties"]["options"]["items"]["required"],
307            json!(["label", "description"])
308        );
309        assert_eq!(
310            schema["properties"]["questions"]["items"]["properties"]["options"]["items"]["properties"]
311                ["description"]["type"],
312            json!("string")
313        );
314    }
315}