antigravity_codes/lib.rs
1//! A typed Rust interface for the [Google Antigravity](https://antigravity.google)
2//! agent runtime.
3//!
4//! <div class="warning">
5//!
6//! **Maturity warning**: this crate is new and should be considered **highly
7//! untested**. The wire types are generated from the protobuf descriptor
8//! embedded in the shipped `localharness` binary, and the handshake is locked
9//! against captures from a live harness, but real-world mileage is minimal and
10//! the API may change between releases while the surface settles. The upstream
11//! SDK is itself alpha (`0.1.x`) and reserves extension ranges on its hottest
12//! messages, so expect churn. Bug reports and wire captures that break the
13//! types are very welcome at
14//! <https://github.com/meawoppl/rust-code-agent-sdks/issues>.
15//!
16//! </div>
17//!
18//! # What this actually wraps
19//!
20//! `google-antigravity` on PyPI is a Python client for a compiled Go binary
21//! called **`localharness`**, which is where the agent loop, the built-in tools,
22//! and the model calls all live. This crate is a client for that same binary —
23//! it is a sibling of the Python SDK, not a binding to it, and no Python is
24//! involved at runtime.
25//!
26//! The binary ships **only** inside the platform-specific wheels published to
27//! [PyPI](https://pypi.org/project/google-antigravity/). See
28//! [`process::find_harness`] for how the crate locates it.
29//!
30//! # The protocol in one screen
31//!
32//! Unlike its sibling crates — [`claude-codes`](https://docs.rs/claude-codes) and
33//! [`codex-codes`](https://docs.rs/codex-codes), which speak JSON-Lines over
34//! stdio, and [`opencode-codes`](https://docs.rs/opencode-codes), which speaks
35//! HTTP+SSE — Antigravity uses stdio *only* to bootstrap, then moves to a
36//! loopback WebSocket:
37//!
38//! 1. **Handshake** (binary protobuf, `u32le`-length-prefixed, over stdio).
39//! The client writes an [`protocol::InputConfig`]; the harness replies with
40//! an [`protocol::OutputConfig`] carrying the port it bound and a
41//! single-use API key. See [`handshake`].
42//! 2. **Connect** to `ws://127.0.0.1:{port}/` with an `x-goog-api-key` header.
43//! 3. **Initialize** by sending an [`protocol::InitializeConversationEvent`];
44//! the harness replies with an
45//! [`protocol::InitializeConversationResponse`] holding the conversation id
46//! and any replayed history.
47//! 4. **Converse** — send [`protocol::InputEvent`]s, receive
48//! [`protocol::OutputEvent`]s. Every frame after the handshake is protobuf's
49//! canonical **JSON** mapping, so the wire is text: `camelCase` members,
50//! 64-bit integers as strings, `bytes` as base64, enums as value names.
51//!
52//! Note that a conversation **must** be configured with at least one model. A
53//! harness initialised with no [`protocol::ModelConfig`] exits immediately and
54//! closes the socket without an error frame.
55//!
56//! # Choosing a client
57//!
58//! | Type | What it gives you |
59//! |------|-------------------|
60//! | [`RawClient`] | The frames, unchanged. You drive the loop and answer the harness's requests yourself. |
61//! | [`Client`] | Turn-oriented: [`Client::send`] returns a [`Turn`] that streams assembled [`Step`]s and answers tool calls, hooks, and policy checks from handlers you register. |
62//!
63//! A worked end-to-end example lives on [`Client`]. At the protocol tier, a
64//! frame off the wire decodes like this:
65//!
66//! ```
67//! use antigravity_codes::protocol::{OutputEvent, OutputEventEvent};
68//!
69//! // Exactly as a live harness emits it — note the stringified `seqNum`.
70//! let frame = r#"{
71//! "stepUpdate": {"cascadeId": "abc", "stepIndex": 0, "state": "STATE_DONE", "text": "hi"},
72//! "seqNum": "3",
73//! "timestampMicros": "1786220347646352"
74//! }"#;
75//!
76//! let event: OutputEvent = serde_json::from_str(frame).unwrap();
77//! assert_eq!(event.sequence(), Some(3));
78//!
79//! let Some(OutputEventEvent::StepUpdate(step)) = event.into_event() else {
80//! panic!("expected a step update")
81//! };
82//! assert_eq!(step.text.as_deref(), Some("hi"));
83//! assert!(step.is_terminal());
84//! ```
85//!
86//! # Feature Flags
87//!
88//! | Feature | Description | WASM-compatible |
89//! |---------|-------------|-----------------|
90//! | `types` | Wire types and the handshake codec only (serde) | Yes |
91//! | `async-client` | Async WebSocket client using tokio | No |
92//! | `integration-tests` | Enables tests that need a real harness binary | No |
93//!
94//! All features are enabled by default. For WASM or type-sharing use cases:
95//!
96//! ```toml
97//! [dependencies]
98//! antigravity-codes = { version = "0.1", default-features = false, features = ["types"] }
99//! ```
100//!
101//! # Versioning
102//!
103//! The crate version tracks the `google-antigravity` release whose harness it
104//! was generated from and tested against — see [`TESTED_SDK_VERSION`].
105
106#![cfg_attr(docsrs, feature(doc_cfg))]
107
108pub mod error;
109pub mod handshake;
110pub mod protocol;
111mod protocol_generated;
112pub mod wire;
113
114pub use error::{Error, Result};
115
116/// The `google-antigravity` release this crate's types were generated from and
117/// tested against.
118///
119/// The harness exposes no version of its own — it takes no arguments and its
120/// handshake reply carries only a port and a key — so the wheel version is the
121/// only handle there is. Mismatches are not detectable at runtime; unknown enum
122/// values and unknown `oneof` arms are absorbed by design instead.
123pub const TESTED_SDK_VERSION: &str = "0.1.10";
124
125#[cfg(feature = "async-client")]
126mod client;
127#[cfg(feature = "async-client")]
128mod client_raw;
129#[cfg(feature = "async-client")]
130pub mod handlers;
131#[cfg(feature = "async-client")]
132pub mod process;
133#[cfg(feature = "async-client")]
134pub mod steps;
135#[cfg(feature = "async-client")]
136mod ws;
137
138#[cfg(feature = "async-client")]
139pub use client::{Client, Turn};
140#[cfg(feature = "async-client")]
141pub use client_raw::RawClient;
142#[cfg(feature = "async-client")]
143pub use process::{Harness, HarnessOptions, ModelBuilder};
144#[cfg(feature = "async-client")]
145pub use steps::{Step, StepKind};