Skip to main content

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}