Skip to content

Commit 5e39393

Browse files
committed
Browser surface: live CDP screencast of the conversation's handed-off browser
1 parent 0f28081 commit 5e39393

7 files changed

Lines changed: 863 additions & 10 deletions

File tree

‎Cargo.lock‎

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Cargo.toml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ libc = "0.2"
7878
gethostname = "1"
7979
tokio-tungstenite = { version = "0.24", features = ["rustls-tls-webpki-roots"] }
8080
futures = "0.3"
81+
# JPEG decode for the browser screencast; the same crate gpui already builds.
82+
image = { version = "0.25", default-features = false, features = ["jpeg"] }
8183
async-trait = "0.1"
8284

8385
# serialization

‎crates/keiki-api/src/lib.rs‎

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ pub enum ConversationAction {
7878
Messages,
7979
Steer,
8080
Terminal,
81+
Browser,
8182
/// Ends an inter-agent (`agent:`) thread — not a contact block.
8283
End,
8384
}
@@ -91,6 +92,7 @@ impl ConversationAction {
9192
Self::Messages => "messages",
9293
Self::Steer => "steer",
9394
Self::Terminal => "terminal",
95+
Self::Browser => "browser",
9496
Self::End => "end",
9597
}
9698
}
@@ -103,6 +105,19 @@ pub struct TerminalSize {
103105
pub rows: u16,
104106
}
105107

108+
/// A short-lived capability to watch and drive the browser the conversation's
109+
/// agent handed off: `url` is the runner's screencast WebSocket, `token` the
110+
/// first message's credential. The bearer secrets stay on the platform.
111+
#[derive(Debug, Clone, Deserialize)]
112+
#[serde(rename_all = "camelCase")]
113+
pub struct BrowserScreencast {
114+
pub session_id: String,
115+
/// Unix millis.
116+
pub expires_at: u64,
117+
pub url: String,
118+
pub token: String,
119+
}
120+
106121
#[derive(Debug, thiserror::Error)]
107122
pub enum Error {
108123
#[error("the OAuth callback was invalid")]
@@ -1147,6 +1162,39 @@ impl Client {
11471162
}
11481163

11491164
/// `/conversations/:identity/terminal[/:terminal_id/:tail]?agentId=`.
1165+
/// Whether the conversation's agent currently has a browser handed off.
1166+
pub async fn browser_available(
1167+
&self,
1168+
access_token: &str,
1169+
locator: &ConversationLocator,
1170+
) -> Result<bool, Error> {
1171+
#[derive(Deserialize)]
1172+
struct Availability {
1173+
available: bool,
1174+
}
1175+
let endpoint = self.conversation_endpoint(locator, Some(ConversationAction::Browser))?;
1176+
let response: Availability = self
1177+
.send_json(self.http.get(endpoint).bearer_auth(access_token))
1178+
.await?;
1179+
Ok(response.available)
1180+
}
1181+
1182+
/// Mint a screencast capability for the conversation's live browser.
1183+
pub async fn browser_screencast(
1184+
&self,
1185+
access_token: &str,
1186+
locator: &ConversationLocator,
1187+
) -> Result<BrowserScreencast, Error> {
1188+
let mut endpoint =
1189+
self.conversation_endpoint(locator, Some(ConversationAction::Browser))?;
1190+
endpoint
1191+
.path_segments_mut()
1192+
.map_err(|_| Error::InvalidContract)?
1193+
.push("screencast");
1194+
self.send_json(self.http.post(endpoint).bearer_auth(access_token))
1195+
.await
1196+
}
1197+
11501198
fn terminal_endpoint(
11511199
&self,
11521200
locator: &ConversationLocator,

‎crates/ui/Cargo.toml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ gpui_platform.workspace = true
2323
gpui_tokio.workspace = true
2424
tokio.workspace = true
2525
futures.workspace = true
26+
tokio-tungstenite.workspace = true
27+
image.workspace = true
2628
reqwest.workspace = true
2729
async-trait.workspace = true
2830
serde.workspace = true

0 commit comments

Comments
 (0)