Files
claw/src/pipe/protocol.rs
木炎 3e18350320 feat: add websocket browser service runtime
Wire the service/browser runtime onto the websocket-driven execution path and add the new browser/service modules needed for the submit flow and runtime integration.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 23:42:27 +08:00

221 lines
5.4 KiB
Rust

use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
pub const PROTOCOL_VERSION: &str = "1.0";
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ExecutionSurfaceKind {
PrivilegedBrowserPipe,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct BrowserContext {
pub page_url: String,
pub page_title: String,
}
impl BrowserContext {
pub fn is_empty(&self) -> bool {
self.page_url.trim().is_empty() && self.page_title.trim().is_empty()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ExecutionSurfaceMetadata {
pub kind: ExecutionSurfaceKind,
pub privileged: bool,
pub defines_runtime_identity: bool,
pub guard: String,
#[serde(default)]
pub allowed_domains: Vec<String>,
#[serde(default)]
pub allowed_actions: Vec<String>,
}
impl ExecutionSurfaceMetadata {
pub fn privileged_browser_pipe(guard: impl Into<String>) -> Self {
Self {
kind: ExecutionSurfaceKind::PrivilegedBrowserPipe,
privileged: true,
defines_runtime_identity: false,
guard: guard.into(),
allowed_domains: Vec::new(),
allowed_actions: Vec::new(),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum BrowserMessage {
Init {
version: String,
hmac_seed: String,
#[serde(default)]
capabilities: Vec<String>,
},
Connect,
Start,
Stop,
SubmitTask {
instruction: String,
#[serde(default)]
conversation_id: String,
#[serde(default)]
messages: Vec<ConversationMessage>,
#[serde(default)]
page_url: String,
#[serde(default)]
page_title: String,
},
Response {
seq: u64,
success: bool,
#[serde(default = "default_object")]
data: Value,
#[serde(default)]
aom_snapshot: Vec<Value>,
timing: Timing,
},
}
impl BrowserMessage {
pub fn browser_context(&self) -> Option<BrowserContext> {
match self {
Self::SubmitTask {
page_url,
page_title,
..
} => {
let context = BrowserContext {
page_url: page_url.clone(),
page_title: page_title.clone(),
};
(!context.is_empty()).then_some(context)
}
_ => None,
}
}
pub fn requested_surface_metadata(&self) -> Option<ExecutionSurfaceMetadata> {
match self {
Self::SubmitTask { .. } => Some(ExecutionSurfaceMetadata::privileged_browser_pipe(
"browser_host_and_mac_policy",
)),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ConversationMessage {
pub role: String,
pub content: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum AgentMessage {
InitAck {
version: String,
agent_id: String,
supported_actions: Vec<Action>,
},
StatusChanged {
state: String,
},
LogEntry {
level: String,
message: String,
},
TaskComplete {
success: bool,
summary: String,
},
Command {
seq: u64,
action: Action,
params: Value,
security: SecurityFields,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum Action {
Click,
Type,
Navigate,
GetText,
Eval,
GetHtml,
WaitForSelector,
PageScreenshot,
Select,
ScrollTo,
GetAomSnapshot,
StorageSet,
StorageGet,
ZombieSpawn,
ZombieKill,
}
impl Action {
pub fn as_str(&self) -> &'static str {
match self {
Action::Click => "click",
Action::Type => "type",
Action::Navigate => "navigate",
Action::GetText => "getText",
Action::Eval => "eval",
Action::GetHtml => "getHtml",
Action::WaitForSelector => "waitForSelector",
Action::PageScreenshot => "pageScreenshot",
Action::Select => "select",
Action::ScrollTo => "scrollTo",
Action::GetAomSnapshot => "getAomSnapshot",
Action::StorageSet => "storageSet",
Action::StorageGet => "storageGet",
Action::ZombieSpawn => "zombieSpawn",
Action::ZombieKill => "zombieKill",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SecurityFields {
pub expected_domain: String,
pub hmac: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Timing {
pub queue_ms: u64,
pub exec_ms: u64,
}
pub fn supported_actions() -> Vec<Action> {
vec![
Action::Click,
Action::Type,
Action::Navigate,
Action::GetText,
Action::Eval,
Action::GetHtml,
Action::WaitForSelector,
Action::PageScreenshot,
Action::Select,
Action::ScrollTo,
Action::GetAomSnapshot,
Action::StorageSet,
Action::StorageGet,
Action::ZombieSpawn,
Action::ZombieKill,
]
}
fn default_object() -> Value {
json!({})
}