use serde::{Deserialize, Serialize};
pub const DEFAULT_SESSION_TIMEOUT_MS: u64 = 24 * 60 * 60 * 1000;
pub const BRIDGE_LOGIN_INSTRUCTION: &str = "Remote Control is only available with \
claude.ai subscriptions. Please use `/login` to sign in with your claude.ai account.";
pub const BRIDGE_LOGIN_ERROR: &str = "Error: You must be logged in to use Remote Control.\n\n\
Remote Control is only available with claude.ai subscriptions. Please use `/login` to \
sign in with your claude.ai account.";
pub const REMOTE_CONTROL_DISCONNECTED_MSG: &str = "Remote Control disconnected.";
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WorkData {
#[serde(rename = "type")]
pub data_type: String,
pub id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WorkResponse {
pub id: String,
#[serde(rename = "type")]
pub response_type: String,
#[serde(rename = "environment_id")]
pub environment_id: String,
pub state: String,
pub data: WorkData,
pub secret: String, #[serde(rename = "created_at")]
pub created_at: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WorkSecret {
pub version: u32,
#[serde(rename = "session_ingress_token")]
pub session_ingress_token: String,
#[serde(rename = "api_base_url")]
pub api_base_url: String,
pub sources: Vec<WorkSource>,
pub auth: Vec<WorkAuth>,
#[serde(rename = "claude_code_args")]
pub claude_code_args: Option<std::collections::HashMap<String, String>>,
#[serde(rename = "mcp_config")]
pub mcp_config: Option<serde_json::Value>,
#[serde(rename = "environment_variables")]
pub environment_variables: Option<std::collections::HashMap<String, String>>,
#[serde(rename = "use_code_sessions")]
pub use_code_sessions: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WorkSource {
#[serde(rename = "type")]
pub source_type: String,
#[serde(rename = "git_info")]
pub git_info: Option<GitInfo>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GitInfo {
#[serde(rename = "type")]
pub info_type: String,
pub repo: String,
pub r#ref: Option<String>,
pub token: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WorkAuth {
#[serde(rename = "type")]
pub auth_type: String,
pub token: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum SessionDoneStatus {
Completed,
Failed,
Interrupted,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SessionActivityType {
ToolStart,
Text,
Result,
Error,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionActivity {
#[serde(rename = "type")]
pub activity_type: SessionActivityType,
pub summary: String,
pub timestamp: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SpawnMode {
SingleSession,
Worktree,
SameDir,
}
impl Default for SpawnMode {
fn default() -> Self {
SpawnMode::SingleSession
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum BridgeWorkerType {
ClaudeCode,
ClaudeCodeAssistant,
}
impl BridgeWorkerType {
pub fn as_str(&self) -> &'static str {
match self {
BridgeWorkerType::ClaudeCode => "claude_code",
BridgeWorkerType::ClaudeCodeAssistant => "claude_code_assistant",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BridgeConfig {
pub dir: String,
#[serde(rename = "machineName")]
pub machine_name: String,
pub branch: String,
#[serde(rename = "gitRepoUrl")]
pub git_repo_url: Option<String>,
#[serde(rename = "maxSessions")]
pub max_sessions: u32,
pub spawn_mode: SpawnMode,
pub verbose: bool,
pub sandbox: bool,
#[serde(rename = "bridgeId")]
pub bridge_id: String,
#[serde(rename = "workerType")]
pub worker_type: String,
#[serde(rename = "environmentId")]
pub environment_id: String,
#[serde(rename = "reuseEnvironmentId")]
pub reuse_environment_id: Option<String>,
#[serde(rename = "apiBaseUrl")]
pub api_base_url: String,
#[serde(rename = "sessionIngressUrl")]
pub session_ingress_url: String,
#[serde(rename = "debugFile")]
pub debug_file: Option<String>,
#[serde(rename = "sessionTimeoutMs")]
pub session_timeout_ms: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PermissionResponseEvent {
#[serde(rename = "type")]
pub event_type: String,
pub response: PermissionResponseInner,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PermissionResponseInner {
#[serde(rename = "subtype")]
pub response_subtype: String,
#[serde(rename = "request_id")]
pub request_id: String,
pub response: serde_json::Value,
}
pub trait BridgeApiClient: Send + Sync {
fn register_bridge_environment(
&self,
config: &BridgeConfig,
) -> impl std::future::Future<Output = Result<(String, String), String>> + Send;
fn poll_for_work(
&self,
environment_id: &str,
environment_secret: &str,
signal: Option<&std::sync::atomic::AtomicBool>,
reclaim_older_than_ms: Option<u64>,
) -> impl std::future::Future<Output = Option<WorkResponse>> + Send;
fn acknowledge_work(
&self,
environment_id: &str,
work_id: &str,
session_token: &str,
) -> impl std::future::Future<Output = Result<(), String>> + Send;
fn stop_work(
&self,
environment_id: &str,
work_id: &str,
force: bool,
) -> impl std::future::Future<Output = Result<(), String>> + Send;
fn deregister_environment(
&self,
environment_id: &str,
) -> impl std::future::Future<Output = Result<(), String>> + Send;
fn send_permission_response_event(
&self,
session_id: &str,
event: &PermissionResponseEvent,
session_token: &str,
) -> impl std::future::Future<Output = Result<(), String>> + Send;
fn archive_session(
&self,
session_id: &str,
) -> impl std::future::Future<Output = Result<(), String>> + Send;
fn reconnect_session(
&self,
environment_id: &str,
session_id: &str,
) -> impl std::future::Future<Output = Result<(), String>> + Send;
fn heartbeat_work(
&self,
environment_id: &str,
work_id: &str,
session_token: &str,
) -> impl std::future::Future<Output = Result<HeartbeatResponse, String>> + Send;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HeartbeatResponse {
#[serde(rename = "lease_extended")]
pub lease_extended: bool,
pub state: String,
}
pub struct SessionHandle {
pub session_id: String,
pub done: bool,
pub kill: Box<dyn Fn() + Send + Sync>,
pub force_kill: Box<dyn Fn() + Send + Sync>,
pub activities: Vec<SessionActivity>,
pub current_activity: Option<SessionActivity>,
pub access_token: String,
pub last_stderr: Vec<String>,
pub write_stdin: Box<dyn Fn(String) + Send + Sync>,
pub update_access_token: Box<dyn Fn(String) + Send + Sync>,
}
impl SessionHandle {
pub fn new(session_id: String, access_token: String) -> Self {
Self {
session_id,
done: false,
kill: Box::new(|| {}),
force_kill: Box::new(|| {}),
activities: Vec::new(),
current_activity: None,
access_token,
last_stderr: Vec::new(),
write_stdin: Box::new(|_| {}),
update_access_token: Box::new(|_| {}),
}
}
}
pub struct SessionSpawnOpts {
pub session_id: String,
pub sdk_url: String,
pub access_token: String,
pub use_ccr_v2: Option<bool>,
pub worker_epoch: Option<i64>,
pub on_first_user_message: Option<Box<dyn Fn(String) + Send + Sync>>,
}
impl Clone for SessionSpawnOpts {
fn clone(&self) -> Self {
Self {
session_id: self.session_id.clone(),
sdk_url: self.sdk_url.clone(),
access_token: self.access_token.clone(),
use_ccr_v2: self.use_ccr_v2,
worker_epoch: self.worker_epoch,
on_first_user_message: None,
}
}
}
pub trait SessionSpawner: Send + Sync {
fn spawn(&self, opts: &SessionSpawnOpts, dir: &str) -> SessionHandle;
}
pub trait BridgeLogger: Send + Sync {
fn print_banner(&self, config: &BridgeConfig, environment_id: &str);
fn log_session_start(&self, session_id: &str, prompt: &str);
fn log_session_complete(&self, session_id: &str, duration_ms: u64);
fn log_session_failed(&self, session_id: &str, error: &str);
fn log_status(&self, message: &str);
fn log_verbose(&self, message: &str);
fn log_error(&self, message: &str);
fn log_reconnected(&self, disconnected_ms: u64);
fn set_repo_info(&self, repo_name: &str, branch: &str);
fn set_debug_log_path(&self, path: &str);
fn update_idle_status(&self);
fn set_attached(&self, session_id: &str);
fn update_reconnecting_status(&self, delay_str: &str, elapsed_str: &str);
fn update_session_status(
&self,
session_id: &str,
elapsed: &str,
activity: &SessionActivity,
trail: &[String],
);
fn clear_status(&self);
fn toggle_qr(&self);
fn update_session_count(&self, active: u32, max: u32, mode: SpawnMode);
fn set_spawn_mode_display(&self, mode: Option<SpawnMode>);
fn add_session(&self, session_id: &str, url: &str);
fn update_session_activity(&self, session_id: &str, activity: &SessionActivity);
fn set_session_title(&self, session_id: &str, title: &str);
fn remove_session(&self, session_id: &str);
fn refresh_display(&self);
}