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/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/spawn_subprocess: subagent type or name to run."},
17            "message": {"type": "string", "description": "spawn/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/spawn_subprocess: model override. Omit to use parent model."},
25            "reasoning_effort": {"type": "string", "description": "spawn/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/spawn_subprocess: optional turn limit for the child."},
28            "id": {"type": "string", "description": "send_input/resume/close: child agent id."},
29            "interrupt": {"type": "boolean", "description": "send_input: abort current child work and restart with this input; false (default) 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 request_user_input_description() -> &'static str {
45    "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."
46}
47
48#[must_use]
49pub fn request_user_input_parameters() -> Value {
50    json!({
51        "type": "object",
52        "additionalProperties": false,
53        "required": ["questions"],
54        "properties": {
55            "questions": {
56                "type": "array",
57                "description": "Questions to show the user (1-3). Prefer 1 unless multiple independent decisions block progress.",
58                "minItems": 1,
59                "maxItems": 3,
60                "items": {
61                    "type": "object",
62                    "additionalProperties": false,
63                    "required": ["id", "header", "question"],
64                    "properties": {
65                        "id": {
66                            "type": "string",
67                            "description": "Stable identifier for mapping answers (snake_case)."
68                        },
69                        "header": {
70                            "type": "string",
71                            "description": "Short header label shown in the UI (12 or fewer chars)."
72                        },
73                        "question": {
74                            "type": "string",
75                            "description": "Single-sentence prompt shown to the user."
76                        },
77                        "focus_area": {
78                            "type": "string",
79                            "description": "Optional short topic hint used to bias auto-suggested choices when options are omitted."
80                        },
81                        "analysis_hints": {
82                            "type": "array",
83                            "description": "Optional weakness/risk hints used by the UI to generate suggested options.",
84                            "items": {
85                                "type": "string"
86                            },
87                            "maxItems": 8
88                        },
89                        "options": {
90                            "type": "array",
91                            "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.",
92                            "minItems": 2,
93                            "maxItems": 3,
94                            "items": {
95                                "type": "object",
96                                "additionalProperties": false,
97                                "required": ["label", "description"],
98                                "properties": {
99                                    "label": {
100                                        "type": "string",
101                                        "description": "User-facing label (1-5 words)."
102                                    },
103                                    "description": {
104                                        "type": "string",
105                                        "description": "One short sentence explaining impact/tradeoff if selected."
106                                    }
107                                }
108                            }
109                        }
110                    }
111                }
112            }
113        }
114    })
115}
116
117fn collaboration_input_item_schema() -> Value {
118    json!({
119        "type": "object",
120        "properties": {
121            "type": {"type": "string"},
122            "text": {"type": "string"},
123            "path": {"type": "string"},
124            "name": {"type": "string"},
125            "image_url": {"type": "string"}
126        },
127        "additionalProperties": false
128    })
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134    use serde_json::json;
135
136    #[test]
137    fn collaboration_schemas_keep_structured_items_consistent() {
138        let schema = agent_parameters();
139        let items = &schema["properties"]["items"]["items"];
140
141        assert_eq!(items["additionalProperties"], json!(false));
142        assert_eq!(items["properties"]["image_url"]["type"], json!("string"));
143    }
144
145    #[test]
146    fn collaboration_schemas_expose_updated_agent_description_text() {
147        let schema = agent_parameters();
148
149        assert_eq!(
150            schema["properties"]["action"]["enum"],
151            json!([
152                "spawn",
153                "spawn_subprocess",
154                "send_input",
155                "resume",
156                "wait",
157                "close"
158            ])
159        );
160        assert_eq!(
161            schema["properties"]["message"]["description"],
162            json!(
163                "spawn/spawn_subprocess: task prompt. send_input: follow-up prompt for the child."
164            )
165        );
166        assert_eq!(
167            schema["properties"]["id"]["description"],
168            json!("send_input/resume/close: child agent id.")
169        );
170        assert_eq!(
171            schema["properties"]["background"]["description"],
172            json!("spawn: run the child agent in background and return immediately.")
173        );
174        assert_eq!(
175            schema["properties"]["ids"]["description"],
176            json!(
177                "wait: child agent ids to wait for. Blocks the current foreground turn until one target reaches a terminal state or the wait times out."
178            )
179        );
180        assert_eq!(
181            schema["properties"]["timeout_ms"]["description"],
182            json!(
183                "wait: optional wait timeout in milliseconds. Uses the session default timeout when omitted."
184            )
185        );
186    }
187
188    #[test]
189    fn request_user_input_schema_preserves_description_field_name() {
190        let schema = request_user_input_parameters();
191
192        assert_eq!(schema["required"], json!(["questions"]));
193        assert_eq!(
194            schema["properties"]["questions"]["items"]["properties"]["options"]["items"]["required"],
195            json!(["label", "description"])
196        );
197        assert_eq!(
198            schema["properties"]["questions"]["items"]["properties"]["options"]["items"]["properties"]
199                ["description"]["type"],
200            json!("string")
201        );
202    }
203}