返回 CodeWhale
schema.rs
根目录 / crates / tui / src / tools / github / schema.rs
1 //! The input contracts the model sees: the canonical union schema, and the
2 //! per-action schemas the hidden legacy aliases still advertise.
3
4 use serde_json::{Value, json};
5
6 /// The union schema for the canonical `github` tool: `action` plus every
7 /// field any allowed action reads. `read_only` drops the write fields so the
8 /// Plan-mode surface cannot even describe a mutation.
9 pub(super) fn canonical_schema(allowed_actions: &[&str], read_only: bool) -> Value {
10 let actions: Vec<&str> = allowed_actions.to_vec();
11 let mut properties = serde_json::Map::new();
12 properties.insert(
13 "action".to_string(),
14 json!({
15 "type": "string",
16 "enum": actions,
17 "description": "Action to perform."
18 }),
19 );
20 properties.insert(
21 "number".to_string(),
22 json!({ "type": "integer", "minimum": 1, "description": "Issue/PR number (all actions)." }),
23 );
24 properties.insert(
25 "include_comments".to_string(),
26 json!({ "type": "boolean", "default": true, "description": "(action=issue_context)" }),
27 );
28 properties.insert("report_id".to_string(), json!({"type": "string", "maxLength": 73, "description": "(report_read) Opaque current-session draft ID returned by report_draft."}));
29 properties.insert(
30 "include_diff".to_string(),
31 json!({ "type": "boolean", "default": false, "description": "(action=pr_context)" }),
32 );
33 if !read_only {
34 properties.insert("revises".to_string(), json!({"type": "string", "maxLength": 73, "description": "(report_draft) Existing current-session draft to revise. Old bytes are preserved."}));
35 properties.insert("report".to_string(), report_schema());
36 properties.insert(
37 "target".to_string(),
38 json!({ "type": "string", "enum": ["issue", "pr"], "description": "(action=comment)" }),
39 );
40 properties.insert(
41 "body".to_string(),
42 json!({ "type": "string", "description": "Comment body (action=comment)." }),
43 );
44 properties.insert(
45 "evidence".to_string(),
46 json!({
47 "type": "object",
48 "description": "Evidence object (action=comment/close_*). Close actions require files_changed, tests_run, final_status.",
49 "properties": {
50 "files_changed": { "type": "array", "items": { "type": "string" } },
51 "tests_run": { "type": "array", "items": { "type": "string" } },
52 "commits": { "type": "array", "items": { "type": "string" } },
53 "final_status": { "type": "string" }
54 }
55 }),
56 );
57 properties.insert(
58 "acceptance_criteria".to_string(),
59 json!({ "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "(action=close_issue/close_pr)" }),
60 );
61 properties.insert(
62 "comment".to_string(),
63 json!({ "type": "string", "description": "Optional closing comment (action=close_issue/close_pr)." }),
64 );
65 properties.insert(
66 "allow_dirty".to_string(),
67 json!({ "type": "boolean", "default": false, "description": "(action=close_issue/close_pr)" }),
68 );
69 properties.insert(
70 "dry_run".to_string(),
71 json!({ "type": "boolean", "default": false, "description": "(action=comment/close_issue/close_pr)" }),
72 );
73 }
74 json!({
75 "type": "object",
76 "properties": properties,
77 "additionalProperties": false
78 })
79 }
80
81 fn report_schema() -> Value {
82 let narrative = json!({"type": "string", "minLength": 1, "maxLength": 1600});
83 let items = json!({"type": "array", "minItems": 1, "maxItems": 8, "items": {"type": "string", "minLength": 1, "maxLength": 800}});
84 let context = json!({"type": "string", "minLength": 1, "maxLength": 100});
85 json!({"type": "object", "additionalProperties": false,
86 "description": "(report_draft) Bounded narrative about a likely Codewhale defect, written by the active agent from observed evidence. No logs, code blocks, attachments, paths or private URLs. Only a local draft; publication unavailable.",
87 "properties": {
88 "title": {"type": "string", "minLength": 1, "maxLength": 160},
89 "expected": narrative, "actual": narrative, "impact": narrative,
90 "steps": items, "observed": items,
91 "inferred": {"type": "array", "maxItems": 4, "items": {"type": "string", "minLength": 1, "maxLength": 800}},
92 "reported_provider": context, "reported_tool": context, "reported_terminal": context,
93 "related_issues": {"type": "array", "maxItems": 5, "items": {"type": "integer", "minimum": 1, "maximum": u32::MAX}, "description": "Already-known Codewhale issue numbers; displayed as agent-supplied, unverified references. No search is performed."}
94 }, "required": ["title", "expected", "actual", "impact", "steps", "observed"]
95 })
96 }
97
98 /// The exact schema the legacy per-action tool exposed, kept so hidden alias
99 /// registrations report an identical contract to the pre-unification tools.
100 pub(super) fn legacy_action_schema(action: &str) -> Value {
101 match action {
102 "issue_context" => json!({
103 "type": "object",
104 "properties": {
105 "number": { "type": "integer", "minimum": 1 },
106 "include_comments": { "type": "boolean", "default": true }
107 },
108 "required": ["number"],
109 "additionalProperties": false
110 }),
111 "pr_context" => json!({
112 "type": "object",
113 "properties": {
114 "number": { "type": "integer", "minimum": 1 },
115 "include_diff": { "type": "boolean", "default": false }
116 },
117 "required": ["number"],
118 "additionalProperties": false
119 }),
120 "comment" => json!({
121 "type": "object",
122 "properties": {
123 "target": { "type": "string", "enum": ["issue", "pr"] },
124 "number": { "type": "integer", "minimum": 1 },
125 "body": { "type": "string" },
126 "evidence": { "type": "object" },
127 "dry_run": { "type": "boolean", "default": false }
128 },
129 "required": ["target", "number", "body", "evidence"],
130 "additionalProperties": false
131 }),
132 // close_issue / close_pr share the close schema.
133 _ => close_input_schema(),
134 }
135 }
136
137 fn close_input_schema() -> Value {
138 json!({
139 "type": "object",
140 "properties": {
141 "number": { "type": "integer", "minimum": 1 },
142 "acceptance_criteria": { "type": "array", "items": { "type": "string" }, "minItems": 1 },
143 "evidence": {
144 "type": "object",
145 "properties": {
146 "files_changed": { "type": "array", "items": { "type": "string" } },
147 "tests_run": { "type": "array", "items": { "type": "string" } },
148 "commits": { "type": "array", "items": { "type": "string" } },
149 "final_status": { "type": "string" }
150 },
151 "required": ["files_changed", "tests_run", "final_status"]
152 },
153 "comment": { "type": "string" },
154 "allow_dirty": { "type": "boolean", "default": false },
155 "dry_run": { "type": "boolean", "default": false }
156 },
157 "required": ["number", "acceptance_criteria", "evidence"],
158 "additionalProperties": false
159 })
160 }
161
161 lines RUST