antigravity_codes/protocol.rs
1//! The protobuf-JSON wire types, re-exported flat.
2//!
3//! Every type here is generated from the descriptor the shipped harness binary
4//! was built with — see `scripts/codegen_antigravity.py`. The two envelopes
5//! worth knowing are [`InputEvent`] (client to harness) and [`OutputEvent`]
6//! (harness to client); the rest hang off those.
7//!
8//! # How a protobuf `oneof` is modelled
9//!
10//! The JSON mapping flattens a `oneof` — only the set arm appears, as an
11//! ordinary member of the parent object. So the generated struct keeps one
12//! `Option` per arm rather than a Rust `enum`, which is what makes an unknown
13//! future arm decode cleanly instead of failing the whole frame. For matching,
14//! each `oneof` also gets an owned view:
15//!
16//! ```
17//! use antigravity_codes::protocol::{OutputEvent, OutputEventEvent, StepUpdate};
18//!
19//! let event = OutputEvent {
20//! step_update: Some(StepUpdate { text: Some("hi".into()), ..Default::default() }),
21//! ..Default::default()
22//! };
23//!
24//! match event.into_event() {
25//! Some(OutputEventEvent::StepUpdate(step)) => assert_eq!(step.text.as_deref(), Some("hi")),
26//! other => panic!("unexpected arm: {other:?}"),
27//! }
28//! ```
29
30pub use crate::protocol_generated::types::*;
31
32impl InputEvent {
33 /// A plain text turn: `InputEvent { user_input: Some(text) }`.
34 pub fn user(text: impl Into<String>) -> Self {
35 Self {
36 user_input: Some(text.into()),
37 ..Default::default()
38 }
39 }
40
41 /// Asks the harness to abandon the turn in flight.
42 pub fn halt() -> Self {
43 Self {
44 halt_request: Some(true),
45 ..Default::default()
46 }
47 }
48
49 /// Asks the harness to end the session and flush its state to disk.
50 pub fn session_end() -> Self {
51 Self {
52 session_end_request: Some(true),
53 ..Default::default()
54 }
55 }
56
57 /// The result of a client-side tool call, answering an [`OutputEvent`]'s
58 /// [`ToolCall`].
59 pub fn tool_response(response: ToolResponse) -> Self {
60 Self {
61 tool_response: Some(response),
62 ..Default::default()
63 }
64 }
65
66 /// A reply to a [`CallHookRequest`].
67 pub fn hook_response(response: CallHookResponse) -> Self {
68 Self {
69 call_hook_response: Some(response),
70 ..Default::default()
71 }
72 }
73
74 /// A reply to a [`PolicyDecisionRequest`].
75 pub fn policy_response(response: PolicyDecisionResponse) -> Self {
76 Self {
77 policy_decision_response: Some(response),
78 ..Default::default()
79 }
80 }
81}
82
83impl ToolResponse {
84 /// A successful result, carrying the tool's JSON-encoded return value.
85 pub fn ok(id: impl Into<String>, response_json: impl Into<String>) -> Self {
86 Self {
87 id: Some(id.into()),
88 response_json: Some(response_json.into()),
89 ..Default::default()
90 }
91 }
92
93 /// A failed result. The harness surfaces `message` to the model, and may
94 /// route it through an `on_tool_error` hook first.
95 pub fn error(id: impl Into<String>, message: impl Into<String>) -> Self {
96 Self {
97 id: Some(id.into()),
98 error_message: Some(message.into()),
99 ..Default::default()
100 }
101 }
102}
103
104impl HarnessSideTools {
105 /// Nothing enabled — the agent can only talk.
106 ///
107 /// This is what the harness does when the field is absent altogether, and
108 /// it is rarely what you want: an agent with no tools answers questions
109 /// about a workspace by explaining that it cannot read it.
110 pub fn none() -> Self {
111 Self::default()
112 }
113
114 /// Tools that only read state: list, search, find, view, and URL fetch.
115 ///
116 /// This mirrors the default in the reference Python SDK, and is the default
117 /// for [`HarnessOptions`](crate::HarnessOptions). Nothing here writes to the
118 /// workspace or runs a command.
119 pub fn read_only() -> Self {
120 Self {
121 list_dir: Some(ListDirToolConfig {
122 enabled: Some(true),
123 }),
124 grep_search: Some(GrepSearchToolConfig {
125 enabled: Some(true),
126 }),
127 find: Some(FindToolConfig {
128 enabled: Some(true),
129 }),
130 view_file: Some(ViewFileToolConfig {
131 enabled: Some(true),
132 }),
133 read_url_content: Some(ReadUrlContentToolConfig {
134 enabled: Some(true),
135 }),
136 ..Default::default()
137 }
138 }
139
140 /// Everything the harness offers, including shell execution and file writes.
141 ///
142 /// Only reach for this against a workspace you are willing to have modified.
143 /// Enabling `user_questions` also means the harness may block a turn waiting
144 /// on an answer — register a
145 /// [`Handlers::on_questions`](crate::handlers::Handlers::on_questions) or the
146 /// default will cancel it.
147 pub fn all() -> Self {
148 Self {
149 file_edit: Some(FileEditToolConfig {
150 enabled: Some(true),
151 }),
152 write_to_file: Some(WriteToFileToolConfig {
153 enabled: Some(true),
154 }),
155 run_command: Some(RunCommandToolConfig {
156 enabled: Some(true),
157 }),
158 subagents: Some(SubagentsConfig {
159 enabled: Some(true),
160 }),
161 user_questions: Some(UserQuestionsConfig {
162 enabled: Some(true),
163 }),
164 generate_image: Some(GenerateImageToolConfig {
165 enabled: Some(true),
166 }),
167 search_web: Some(SearchWebToolConfig {
168 enabled: Some(true),
169 }),
170 ..Self::read_only()
171 }
172 }
173}
174
175impl StepUpdate {
176 /// True once this step will not be updated again.
177 pub fn is_terminal(&self) -> bool {
178 matches!(
179 self.state,
180 Some(StepUpdateState::Done) | Some(StepUpdateState::Error)
181 )
182 }
183
184 /// The text this step contributes, preferring the accumulated `text` the
185 /// harness sends on completion over the incremental delta.
186 pub fn text_or_delta(&self) -> Option<&str> {
187 self.text
188 .as_deref()
189 .filter(|t| !t.is_empty())
190 .or(self.text_delta.as_deref().filter(|t| !t.is_empty()))
191 }
192}
193
194impl OutputEvent {
195 /// The monotonically increasing sequence number the harness stamps on every
196 /// frame, useful for ordering assertions and gap detection.
197 pub fn sequence(&self) -> Option<i64> {
198 self.seq_num
199 }
200}