ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

microsandbox-agent-client:microsandbox Rust 侧 Agent 协议客户端的消息、流与握手全解

microsandbox-agent-client:microsandbox Rust 侧 Agent 协议客户端的消息、流与握手全解 Agent 沙箱虚拟化【免费下载链接】microsandbox fast branchable microVM for any workload项目地址https://gitcode.com/gh_mirrors/mon/microsandbox点击查看免费下载microsandbox 是一个可快速分支branchable的 microVM 沙箱运行时宿主程序需要与沙箱内部的 agentd 控制代理relay通信来完成执行、文件系统、网络等操作。本篇以packages/agent-client/rust/README.md为核心结合lib/下的实现源码与回归测试系统讲解 Rust 包microsandbox-agent-client的连接建立含当前/遗留双协议握手、四种消息形态typed / encoded / raw / exact packet、所有权流模型、特性开关uds/named-pipe/stream以及从旧版客户端 API 到新共享客户端 API 的完整迁移对照帮助读者在任意字节流或本地 socket 上直接驱动 agent 协议而不必依赖 SDK 领域对象。一、包定位AgentClient就是共享引擎的一个特化包入口文件 对该 crate 的职责定义非常明确它“拥有低层客户端层握手、关联 ID、请求/流路由、消息编码和传输适配器”而高层 SDK crate 继续负责沙箱生命周期与名称解析。从源码看AgentClient并不是独立实现的一套客户端而是microsandbox-protocol-client提供的泛型引擎在 agent 协议上的特化// lib/client.rs pub type AgentClient microsandbox_protocol_client::ClientAgentProtocol;README 中的架构示意说明了整条数据通路TypedMessage / EncodedMessage / raw envelope / exact packet | ClientAgentProtocol | current or legacy relay handshake | agent relay也就是说无论上层发出的是强类型消息、编码好的载荷、原始信封还是精确字节包最终都汇入同一个ClientAgentProtocol共享引擎提供一个 reader、一个 writer、一个 ID 分配器和一个所有权注册表SDK 中的执行结果收集、文件系统句柄等服务仍然留在microsandbox主 SDK 中本包刻意不拥有这些领域对象见 lib.rs 中的模块划分client、protocol、stream、transport、message、error、transports。二、消息模型native、encoded 与 raw 三种形态README 给出的第一段示例展示了 typed 与 encoded 两种消息在同一个连接上的用法use microsandbox_agent_client::{AgentClient, TypedMessage, EncodedMessage}; use microsandbox_protocol::{fs::{FsOp, FsRequest, FsResponse}, message::MessageType}; async fn inspect(client: AgentClient) - Result(), Boxdyn std::error::Error { let request FsRequest { bulk: None, op: FsOp::Stat { path: /etc/os-release.into(), follow_symlink: true }, }; let reply client.request(TypedMessage::new(MessageType::FsRequest, request)).await?; let response: FsResponse reply.payload()?; println!({}, response.ok); // The callers payload bytes are not parsed or normalized by this path. let payload vec![0xa0]; let _reply client.request(EncodedMessage::new(MessageType::Ping, payload)).await?; Ok(()) }三种形态的关键区别TypedMessagenative调用方给出MessageType与可序列化的 Rust 结构体客户端负责 CBOR 编码响应通过reply.payload()解码回强类型。示例中的FsRequest/FsResponse都来自microsandbox-protocolcrate。EncodedMessageencoded调用方直接提供已编码的载荷字节该路径“不解析也不归一化”载荷内容适合转发或调试预编码信封的场景。raw / exact packet完全绕过 CBOR 的原始信封与精确字节写入见第四节的request_raw等 API。另外Message.t是线上真实的 wire 字符串而非枚举名未知消息名与未知信封字段依然可检查Message::raw()借用原始 frameinto_raw()将其移走。普通请求会把对端错误以 message 形式返回。可选的RequestAgentProtocol实现可在不收缩泛型 API 的前提下提供带校验的一元解码。三、所有权流与显式 IDexec 会话的标准写法对于执行exec这类“一发多收”的交互README 给出了所有权流的完整示例use microsandbox_agent_client::{AgentClient, TypedMessage}; use microsandbox_protocol::{exec::{ExecRequest, ExecStdin}, message::MessageType}; async fn exchange(client: AgentClient, request: ExecRequest) - Result(), Boxdyn std::error::Error { let stream client.stream(TypedMessage::new(MessageType::ExecRequest, request)).await?; let id stream.id(); let (sender, mut receiver) stream.into_parts(); client.send(id, TypedMessage::new(MessageType::ExecStdin, ExecStdin { data: vec![] })).await?; sender.send(TypedMessage::new(MessageType::ExecStdin, ExecStdin { data: binput.to_vec() })).await?; while let Some(message) receiver.recv().await? { println!({}, message.t); } Ok(()) }这里的行为契约在 README 中有精确描述源码中AgentStream的类型别名也印证了它直接建立在共享路由器之上stream.rsAgentStream Streamcrate::AgentProtocol另有RawAgentStream RawStreamAgentProtocol供原始流使用stream()返回一个持有连接与 ID 租约的 owned streaminto_parts()拆出 sender 与 receiver两者都保留连接与 ID 租约。sender handle 可以 clone接收方只有一个消费型 receiver。收到终止帧terminal frame后发送被禁用终止帧只投递一次。关闭或 drop receiver禁用发送并保留有界 drain 状态直到终止完成它不会发送任何进程信号或文件系统 EOF。在终止帧之前发生传输丢失是错误与“流自然耗尽”是两种不同结果。四、原始 APIraw 信封、精确字节与独立 packet 助手在 typed/encoded 之外README 明确列出了四组绕过 CBOR 解码的接口request_raw(flags, body)/stream_raw(flags, body)/send_raw(id, flags, body)以不透明信封opaque envelope交换数据不做 CBOR 解码。write_unchecked(packet_bytes)直接序列化调用方给出的精确字节不分配 ID、不订阅回复。TransportPacket是一个可选的独立分帧助手packet.into_bytes()恰好对接上面的精确写入 API独立的 packet reader 则需要独占传输所有权。transport.rs 展示了这些接口的具体实现细节。传输层被刻意设计为“CBOR-blind”只搬完整包不做消息类型校验。TransportPacket承载的线上格式为[len: u32 BE][id: u32 BE][flags: u8][body...]。TransportPacket::from_bytes虽然服务于 unchecked 写入路径但会做结构性校验长度前缀不小于 9 字节、frame 长度在5..MAX_FRAME_SIZE之内、且恰好一个完整包防止调用方意外拼接多个包或传入截断帧。独立读取侧的read_packet_from_io对 EOF 语义做了精细区分只有在新包第一字节之前收到 EOF 才返回Ok(None)干净耗尽一旦读到了部分长度前缀后续字节不足就是截断错误。独立的写助手write_packet_to_io负责write_allflush。错误方面error.rs 中的AgentClientError汇总了各层失败连接失败Connect、握手失败Handshake、协议/分帧错误Protocol、InvalidPacketpacket 结构不合法、IdRangeExhaustedrelay 分配的关联 ID 区间耗尽、ReaderClosed响应前 reader 已关闭等而共享路由层的失败通过Client(#[from] ClientError)透传并保留其投递不确定性语义。五、连接建立特性开关、单一截止时间与双协议握手特性与入口Cargo.toml 中特性定义如下默认不启用任何传输stream提供通用字节流路径任何AsyncRead AsyncWrite不引入额外依赖uds在通用路径之上启用 Unix domain socket并引入bytes、libc、memmap2、nix、tempfile等可选依赖named-pipe启用 Windows 命名管道。README 提醒stream这个特性名仍然被接受以兼容既有 manifest但泛型 owned transport 现在无需任何额外依赖即可使用。连接入口因此分三类use microsandbox_agent_client::AgentClient; use std::time::Duration; async fn connect(path: std::path::Path) - Result(), Boxdyn std::error::Error { let client AgentClient::connect_with(path, |o| o.setup_timeout(Duration::from_secs(5))).await?; println!({}, client.ready().agent_version()); println!({}, client.ready().negotiated_version); println!({} ready bytes, client.ready().ready_bytes().len()); client.close().await; Ok(()) }connect(path)/connect_withUnix 下走uds特性Windows 下走named-pipe此时path是管道名。connect_stream(stream)/connect_stream_with接受任意 owned 的AsyncRead AsyncWrite Unpin Send传输例如UdsTransport、NamedPipeTransport或调用方自有的 WebSocket 字节适配。单一截止时间与 relay 前导prologueREADME 指出setup 只有一个截止时间跨越“拨号 握手”全过程。protocol.rs 的establish()实现印证了这一点共享引擎对整个 setup future 施加同一个 deadline客户端先读取 8 字节前导随后据此分叉出两种握手// 8 个字节区分当前 [min,max] 与遗留 [offset,len] let mut prologue [0u8; 8]; stream.read_exact(mut prologue).await?; let first u32::from_be_bytes(prologue[..4].try_into().unwrap()); let second u32::from_be_bytes(prologue[4..].try_into().unwrap()); let legacy (FRAME_HEADER_SIZE as u32..MAX_FRAME_SIZE).contains(second) (first 0 || first second);当前 relay前导是[id_min, id_max]ID 区间为[max(1, first), second)ID 0 仅用于 setup 本身。遗留 relaypre-0.5generation 1前导是[id_offset, len]其中len是一个合法的帧长度FRAME_HEADER_SIZE..MAX_FRAME_SIZE且第二字大于等于第一字此时客户端从 relay 侧偏移量起步分配区间为[offset1, offsetLEGACY_RELAY_ID_RANGE_STEP)其中LEGACY_RELAY_ID_RANGE_STEP u32::MAX / 16。之后两种路径都解析core.ready帧必须为Ready消息类型否则报InvalidData并记录AgentReady { wire_format, // Current 或 LegacyV1 negotiated_version: wire_format.version().min(ready_message.v), agent, // 解码出的 core::Ready ready_body: frame.body, // 原始 ready 字节保留未知字段 }几个值得注意的字节契约README 与源码一致当前连接继续发出宿主既有的 envelope generation遗留连接固定发出 generation 1LEGACY_PROTOCOL_VERSION 1。这是历史兼容行为AgentWireFormat::version()的注释明确说明“保留该既有字节契约”。已知操作的可用性门槛取“宿主/对端较小代际”negotiated_version min(wire_format.version(), ready.v)AgentReady::supports(type)就是按这个值做is_available_at检查。源码层 API 的变化不会淘汰遗留 wire 路径。回归测试 legacy_and_current.rs 覆盖了两代握手connect_accepts_legacy_relay_handshake用id_offset 0与268_435_455两种取值验证遗留路径legacy_relay_requests_use_v1_and_legacy_id_range验证遗留连接发出的请求message.v 1且 ID 落在id_offset 1connect_negotiates_down_to_older_guest_generation则验证当前 codec 遇上“代际落后一格的 guest”时协商版本被钉到 guest 上报的 1且FsRequest不可用而ExecRequest可用测试注释说明generation 1 是 pre-0.5 遗留运行时无文件系统generation 2 引入 Fs* 类型。六、AgentReady元数据连接完成后能拿到什么client.ready()返回的AgentReady定义见 protocol.rs是所有元数据访问的统一入口字段与方法包括访问方式含义ready().wire_format握手选定的 wire 形态Current或LegacyV1ready().negotiated_version宿主与对端较小代际作为已知操作的能力门槛ready().agent解码后的core::Ready载荷含 boot/init/ready 时间戳、agent 自报版本等ready().ready_bytes()原始 ready 信封字节含未知字段可交给ciborium自行解析ready().supports(type)当前代际是否支持某个已知MessageTypeready().agent_version()运行时自报包版本旧 agent 无该字段时为空ready().is_legacy_protocol()是否为 pre-0.5 wire 形态测试用例connect_decodes_ready_payload展示了这些字段的实际取值fake relay 写入[1, 8]前导加一条agent_version 9.9.9的 ready 消息后客户端断言wire_format Current、negotiated_version PROTOCOL_VERSION、agent_version() 9.9.9并断言ready_bytes()可以独立 CBOR 解析回Messagetests/legacy_and_current.rs。版本兼容校验可以由调用方手动完成AgentProtocol::ensure_version_compat_for(type, generation)只依赖一个代际值u8适合那些只保存了 generation 而没有持有AgentReady的场景不满足时返回带ErrorKind::UnsupportedOperation的共享ClientError且发生在任何字节发出之前prepare()在发送前即调用该校验见 protocol.rs。七、从旧版客户端迁移完整 API 对照表README 提供了一张从“上一代活跃客户端”到新共享客户端 API 的完整迁移表这里原样继承新 API 均位于microsandbox-protocol-clientAgentClient只是其特化旧 API共享客户端 APIconnect(path)/connect_stream(stream)入口不变owned stream 不再需要stream特性request(type, payload)request(TypedMessage::new(type, payload))stream(type, payload) - (id, receiver)stream(TypedMessage::new(type, payload))再取id()/into_parts()send(id, type, payload)send(id, TypedMessage::new(type, payload))request_raw(flags, body)调用不变非终止首响应后保留 drain 状态stream_raw(flags, body) - (id, receiver)owned raw stream再取id()/into_parts()send_raw(id, flags, body)调用不变要求 ID 处于存活状态connect_with_timeout(path, duration)connect_with(path, \|o\| o.setup_timeout(duration))connect_stream_with_timeout(stream, duration)connect_stream_with(stream, \|o\| o.setup_timeout(duration))connect_with_deadline(path, deadline)connect_with(path, \|o\| o.setup_timeout(deadline.saturating_duration_since(Instant::now())))connect_stream_with_deadline(stream, deadline)connect_stream_with(stream, \|o\| o.setup_timeout(deadline.saturating_duration_since(Instant::now())))ready()ready().agentready_bytes()ready().ready_bytes()negotiated_version()/supports(type)ready().negotiated_version/ready().supports(type)protocol()/is_legacy_protocol()ready().wire_format/ready().is_legacy_protocol()agent_version()ready().agent_version()ensure_version_compat(type)AgentProtocol::ensure_version_compat_for(type, client.ready().negotiated_version)AgentClient::ensure_version_compat_for(type, generation)AgentProtocol::ensure_version_compat_for(type, generation)未接线的AgentStreamT/ 仅 packet 的AgentTransport活跃的AgentStream/ owned 字节传输直接被connect_stream接受通过所有权转移关闭连接共享的close(self)最后一个 client/stream 属主也关闭传输迁移时还有三条语义要点投递不确定性ClientError.delivery在 writer 已接纳admission之后区分NotSent与Unknown请求永远不会被自动重放。截止时间换算deadline 换算使用tokio::time::Instant与原绝对截止时间 API 对齐README 建议把换算放在 options 闭包内部以便在 setup 真正开始时才取“剩余时长”测试文件中的connect_with_deadline辅助函数正是这一写法的示范。优化客户端的边界SDK 内部仍保留一个针对 generation-8 bulk 传输与 Unix 共享内存shared arena的优化客户端OptimizedAgentClient在 lib.rs 中以#[doc(hidden)]重导出配套的local_shm模块仅在uds unix下可见其既有 bulk API 继续可用而AgentClient是公开的、传输无关的 framed API。原始流访问、已知 ID 发送、自定义传输、ready bytes、动态消息名与编码载荷在不引入 SDK 领域对象的前提下全部保留。八、验证与测试README 给出的官方验证命令为cargo test -p microsandbox-agent-client --all-features --locked其中 Unix socket 测试需要权限去 bind 临时本地 socket测试通过tempfile在临时目录创建agent.sock。迁移过来的历史握手用例tests/legacy_and_current.rs属于源码级回归测试README 特别强调它们不能替代对历史运行时/SDK 的活体验证。测试文件内按特性做了精细的cfg分层uds unix用例使用真实UnixListener扮演 relaynamed-pipe windows用例使用命名管道服务端而stream用例则使用tokio::io::duplex内存字节流完成“握手 exec 流stdout → exited 终止帧”的端到端断言覆盖了三类传输入口在行为上的一致性。小结microsandbox-agent-client的设计取舍可以概括为三点其一把协议客户端能力下沉到共享的ClientAgentProtocol引擎自身只保留 relay 握手特化AgentProtocol::establish双前导解析、元数据AgentReady与传输适配器其二以“typed / encoded / raw / exact packet”四级消息模型兼顾类型安全与字节级可控配合 owned stream 的显式 ID 租约与终止帧语义其三用 generation 门槛supports/ensure_version_compat_for在发送前拦截不兼容操作同时完整保留 pre-0.5 遗留 wire 路径的字节契约。对于需要绕过 SDK 领域对象、直接驱动 agent 协议例如自定义传输上的调试器、转发器或测试桩的 Rust 项目本包提供了完整的公开入口。赞分享Agent 沙箱虚拟化【免费下载链接】microsandbox fast branchable microVM for any workload项目地址https://gitcode.com/gh_mirrors/mon/microsandbox点击查看免费下载相关推荐microsandbox agent-client 深度解析:Rust 与 TypeScript 双实现的跨传输 Agent 协议客户端microsandbox agent client 深度解析:Rust 与 TypeScript 双实现的跨传输 Agent 协议客户端 本文基于仓库中的 paAgent 沙箱虚拟化AgentMesh Wire Protocol v1.0 全解析面向 AI Agent 的端到端加密消息协议AgentMesh Wire Protocol v1.0 全解析面向 AI Agent 的端到端加密消息协议 本篇文章基于 docs/specs/AGENTM人工智能AI AgentAI 安全治理策略引擎Agent 沙箱认证鉴权agents24 Agent Teams 实战指南七套多 Agent 通信消息模板与协议全解agents24 Agent Teams 实战指南七套多 Agent 通信消息模板与协议全解 本篇围绕 agents24 仓库中 agent teams 插件AI 插件AI 技能开发工具上一篇memoize-one性能优化终极指南为什么它比传统记忆化库快10倍下一篇EB Garamond 12古典字体与现代学术排版的终极融合指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表