1use serde::{Deserialize, Serialize};
6
7pub const DEFAULT_SESSION_TIMEOUT_MS: u64 = 24 * 60 * 60 * 1000;
13
14pub const BRIDGE_LOGIN_INSTRUCTION: &str = "Remote Control is only available with \
16 claude.ai subscriptions. Please use `/login` to sign in with your claude.ai account.";
17
18pub const BRIDGE_LOGIN_ERROR: &str = "Error: You must be logged in to use Remote Control.\n\n\
20 Remote Control is only available with claude.ai subscriptions. Please use `/login` to \
21 sign in with your claude.ai account.";
22
23pub const REMOTE_CONTROL_DISCONNECTED_MSG: &str = "Remote Control disconnected.";
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct WorkData {
33 #[serde(rename = "type")]
34 pub data_type: String,
35 pub id: String,
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct WorkResponse {
41 pub id: String,
42 #[serde(rename = "type")]
43 pub response_type: String,
44 #[serde(rename = "environment_id")]
45 pub environment_id: String,
46 pub state: String,
47 pub data: WorkData,
48 pub secret: String, #[serde(rename = "created_at")]
50 pub created_at: String,
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct WorkSecret {
56 pub version: u32,
57 #[serde(rename = "session_ingress_token")]
58 pub session_ingress_token: String,
59 #[serde(rename = "api_base_url")]
60 pub api_base_url: String,
61 pub sources: Vec<WorkSource>,
62 pub auth: Vec<WorkAuth>,
63 #[serde(rename = "claude_code_args")]
64 pub claude_code_args: Option<std::collections::HashMap<String, String>>,
65 #[serde(rename = "mcp_config")]
66 pub mcp_config: Option<serde_json::Value>,
67 #[serde(rename = "environment_variables")]
68 pub environment_variables: Option<std::collections::HashMap<String, String>>,
69 #[serde(rename = "use_code_sessions")]
73 pub use_code_sessions: Option<bool>,
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct WorkSource {
79 #[serde(rename = "type")]
80 pub source_type: String,
81 #[serde(rename = "git_info")]
82 pub git_info: Option<GitInfo>,
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct GitInfo {
88 #[serde(rename = "type")]
89 pub info_type: String,
90 pub repo: String,
91 pub r#ref: Option<String>,
92 pub token: Option<String>,
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize)]
97pub struct WorkAuth {
98 #[serde(rename = "type")]
99 pub auth_type: String,
100 pub token: String,
101}
102
103#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
105#[serde(rename_all = "lowercase")]
106pub enum SessionDoneStatus {
107 Completed,
108 Failed,
109 Interrupted,
110}
111
112#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
114#[serde(rename_all = "snake_case")]
115pub enum SessionActivityType {
116 ToolStart,
117 Text,
118 Result,
119 Error,
120}
121
122#[derive(Debug, Clone, Serialize, Deserialize)]
124pub struct SessionActivity {
125 #[serde(rename = "type")]
126 pub activity_type: SessionActivityType,
127 pub summary: String,
129 pub timestamp: u64,
130}
131
132#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
141#[serde(rename_all = "snake_case")]
142pub enum SpawnMode {
143 SingleSession,
144 Worktree,
145 SameDir,
146}
147
148impl Default for SpawnMode {
149 fn default() -> Self {
150 SpawnMode::SingleSession
151 }
152}
153
154#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
165#[serde(rename_all = "snake_case")]
166pub enum BridgeWorkerType {
167 ClaudeCode,
168 ClaudeCodeAssistant,
169}
170
171impl BridgeWorkerType {
172 pub fn as_str(&self) -> &'static str {
173 match self {
174 BridgeWorkerType::ClaudeCode => "claude_code",
175 BridgeWorkerType::ClaudeCodeAssistant => "claude_code_assistant",
176 }
177 }
178}
179
180#[derive(Debug, Clone, Serialize, Deserialize)]
186pub struct BridgeConfig {
187 pub dir: String,
188 #[serde(rename = "machineName")]
189 pub machine_name: String,
190 pub branch: String,
191 #[serde(rename = "gitRepoUrl")]
192 pub git_repo_url: Option<String>,
193 #[serde(rename = "maxSessions")]
194 pub max_sessions: u32,
195 pub spawn_mode: SpawnMode,
196 pub verbose: bool,
197 pub sandbox: bool,
198 #[serde(rename = "bridgeId")]
200 pub bridge_id: String,
201 #[serde(rename = "workerType")]
204 pub worker_type: String,
205 #[serde(rename = "environmentId")]
207 pub environment_id: String,
208 #[serde(rename = "reuseEnvironmentId")]
214 pub reuse_environment_id: Option<String>,
215 #[serde(rename = "apiBaseUrl")]
217 pub api_base_url: String,
218 #[serde(rename = "sessionIngressUrl")]
220 pub session_ingress_url: String,
221 #[serde(rename = "debugFile")]
223 pub debug_file: Option<String>,
224 #[serde(rename = "sessionTimeoutMs")]
226 pub session_timeout_ms: Option<u64>,
227}
228
229#[derive(Debug, Clone, Serialize, Deserialize)]
237pub struct PermissionResponseEvent {
238 #[serde(rename = "type")]
239 pub event_type: String,
240 pub response: PermissionResponseInner,
241}
242
243#[derive(Debug, Clone, Serialize, Deserialize)]
244pub struct PermissionResponseInner {
245 #[serde(rename = "subtype")]
246 pub response_subtype: String,
247 #[serde(rename = "request_id")]
248 pub request_id: String,
249 pub response: serde_json::Value,
250}
251
252pub trait BridgeApiClient: Send + Sync {
258 fn register_bridge_environment(
259 &self,
260 config: &BridgeConfig,
261 ) -> impl std::future::Future<Output = Result<(String, String), String>> + Send;
262
263 fn poll_for_work(
264 &self,
265 environment_id: &str,
266 environment_secret: &str,
267 signal: Option<&std::sync::atomic::AtomicBool>,
268 reclaim_older_than_ms: Option<u64>,
269 ) -> impl std::future::Future<Output = Option<WorkResponse>> + Send;
270
271 fn acknowledge_work(
272 &self,
273 environment_id: &str,
274 work_id: &str,
275 session_token: &str,
276 ) -> impl std::future::Future<Output = Result<(), String>> + Send;
277
278 fn stop_work(
280 &self,
281 environment_id: &str,
282 work_id: &str,
283 force: bool,
284 ) -> impl std::future::Future<Output = Result<(), String>> + Send;
285
286 fn deregister_environment(
288 &self,
289 environment_id: &str,
290 ) -> impl std::future::Future<Output = Result<(), String>> + Send;
291
292 fn send_permission_response_event(
294 &self,
295 session_id: &str,
296 event: &PermissionResponseEvent,
297 session_token: &str,
298 ) -> impl std::future::Future<Output = Result<(), String>> + Send;
299
300 fn archive_session(
302 &self,
303 session_id: &str,
304 ) -> impl std::future::Future<Output = Result<(), String>> + Send;
305
306 fn reconnect_session(
309 &self,
310 environment_id: &str,
311 session_id: &str,
312 ) -> impl std::future::Future<Output = Result<(), String>> + Send;
313
314 fn heartbeat_work(
318 &self,
319 environment_id: &str,
320 work_id: &str,
321 session_token: &str,
322 ) -> impl std::future::Future<Output = Result<HeartbeatResponse, String>> + Send;
323}
324
325#[derive(Debug, Clone, Serialize, Deserialize)]
327pub struct HeartbeatResponse {
328 #[serde(rename = "lease_extended")]
329 pub lease_extended: bool,
330 pub state: String,
331}
332
333pub struct SessionHandle {
339 pub session_id: String,
341 pub done: bool,
343 pub kill: Box<dyn Fn() + Send + Sync>,
345 pub force_kill: Box<dyn Fn() + Send + Sync>,
347 pub activities: Vec<SessionActivity>,
349 pub current_activity: Option<SessionActivity>,
351 pub access_token: String,
353 pub last_stderr: Vec<String>,
355 pub write_stdin: Box<dyn Fn(String) + Send + Sync>,
357 pub update_access_token: Box<dyn Fn(String) + Send + Sync>,
359}
360
361impl SessionHandle {
362 pub fn new(session_id: String, access_token: String) -> Self {
363 Self {
364 session_id,
365 done: false,
366 kill: Box::new(|| {}),
367 force_kill: Box::new(|| {}),
368 activities: Vec::new(),
369 current_activity: None,
370 access_token,
371 last_stderr: Vec::new(),
372 write_stdin: Box::new(|_| {}),
373 update_access_token: Box::new(|_| {}),
374 }
375 }
376}
377
378pub struct SessionSpawnOpts {
384 pub session_id: String,
386 pub sdk_url: String,
388 pub access_token: String,
390 pub use_ccr_v2: Option<bool>,
392 pub worker_epoch: Option<i64>,
394 pub on_first_user_message: Option<Box<dyn Fn(String) + Send + Sync>>,
399}
400
401impl Clone for SessionSpawnOpts {
402 fn clone(&self) -> Self {
403 Self {
404 session_id: self.session_id.clone(),
405 sdk_url: self.sdk_url.clone(),
406 access_token: self.access_token.clone(),
407 use_ccr_v2: self.use_ccr_v2,
408 worker_epoch: self.worker_epoch,
409 on_first_user_message: None,
411 }
412 }
413}
414
415pub trait SessionSpawner: Send + Sync {
421 fn spawn(&self, opts: &SessionSpawnOpts, dir: &str) -> SessionHandle;
422}
423
424pub trait BridgeLogger: Send + Sync {
430 fn print_banner(&self, config: &BridgeConfig, environment_id: &str);
432
433 fn log_session_start(&self, session_id: &str, prompt: &str);
435
436 fn log_session_complete(&self, session_id: &str, duration_ms: u64);
438
439 fn log_session_failed(&self, session_id: &str, error: &str);
441
442 fn log_status(&self, message: &str);
444
445 fn log_verbose(&self, message: &str);
447
448 fn log_error(&self, message: &str);
450
451 fn log_reconnected(&self, disconnected_ms: u64);
453
454 fn set_repo_info(&self, repo_name: &str, branch: &str);
456
457 fn set_debug_log_path(&self, path: &str);
459
460 fn update_idle_status(&self);
462
463 fn set_attached(&self, session_id: &str);
465
466 fn update_reconnecting_status(&self, delay_str: &str, elapsed_str: &str);
468
469 fn update_session_status(
471 &self,
472 session_id: &str,
473 elapsed: &str,
474 activity: &SessionActivity,
475 trail: &[String],
476 );
477
478 fn clear_status(&self);
480
481 fn toggle_qr(&self);
483
484 fn update_session_count(&self, active: u32, max: u32, mode: SpawnMode);
486
487 fn set_spawn_mode_display(&self, mode: Option<SpawnMode>);
489
490 fn add_session(&self, session_id: &str, url: &str);
492
493 fn update_session_activity(&self, session_id: &str, activity: &SessionActivity);
495
496 fn set_session_title(&self, session_id: &str, title: &str);
498
499 fn remove_session(&self, session_id: &str);
501
502 fn refresh_display(&self);
504}