a13nDocs

Python EIP 客户端

用于实现环境 provider 和其他可信 EIP 客户端的底层 Python 客户端。

客户端以 a13n-envd-client 发布。多数 agent 应用应使用环境 provider,它们已经处理目标生命周期和适配。

此包不会发现、安装、下载或启动 Envd,也不会创建反向 WebSocket 监听器、签发凭据、保留目标状态或运行 agent。

安装

Terminal
uv add a13n-envd-client

需要 Python 3.13 或更新版本。Python 客户端与原生守护进程使用相同的 Envd 发布版本;EIP 协商的线上协议版本是独立标识。源码示例应使用仓库锁文件,不要将发布版客户端与源码版本守护进程混用。

连接已有 HTTP 守护进程

这个完整示例需要运行中的守护进程、预期设备身份和 Host 签发的凭据。它不会配置基础设施,也不会离线测试账号访问。

Python
import asyncio
import os

from a13n_envd_client import EIPDeviceConnection, HttpTransport


async def main() -> None:
    transport = HttpTransport(
        endpoint=os.environ["ENVD_ENDPOINT"],
        credential=os.environ["ENVD_CREDENTIAL"],
    )
    device = await EIPDeviceConnection.initialize(
        transport, expected_device_id=os.environ["ENVD_DEVICE_ID"],
    )
    async with device:
        info = await device.describe()  # Discovery does not open a Session.
        print(info.device_id, info.default_working_directory)
        async with await device.open_session(
            working_directory=info.default_working_directory,
            required_methods=("file.read_text",),
        ) as session:
            print(session.session_id, session.descriptor.working_directory)



if __name__ == "__main__":
    asyncio.run(main())

端点是守护进程基础 URL,不是 /eip/control。设备初始化协商协议并验证身份,不打开会话。open_session() 创建独立的固定 cwd 范围并检查就绪状态。退出会话上下文仅关闭该会话。退出设备上下文会关闭其会话和物理传输,不关闭外部守护进程,也不影响设备上的文件。

选择传输方式

传输提供内容归属与约束
StdioTransport(reader, writer, process=...)父进程管理的 asyncio 流通过可信管道复用 EIP 控制和二进制帧
StdioTransport.from_process(process)已启动且通过管道连接 stdin/stdout 的 asyncio 子进程启动策略、必需运行时目录、stderr 消费和隔离由 Host 负责
HttpTransport(endpoint, credential, ...)守护进程基础 URL 和附加凭据经身份验证的请求和原始传输流;客户端管理内部 HTTP 连接池
AcceptedWebSocketTransport(connection, ...)已接受并完成身份验证的 WebSocketConnection要求已协商 eip.v1;不监听、拨号或验证升级

所有传输的 max_request_bytes、max_response_bytes 和 max_transfer_frame_bytes 均默认为 1 MiB;限制必须为正数,并收窄到协商后的描述符范围。提高客户端限制不能授予服务器不支持的方法。

HTTP 选项

选项默认值含义
verifyTrueTLS 验证;支持 SSL context 或 CA 路径,拒绝 False
request_timeout30.0 秒连接/请求超时;控制读取预算会计入操作超时
allow_plaintext_private_linkFalse显式允许支持的私有链路 HTTP 场景,并非不受限制的明文访问
请求、响应和帧限制各 1048576 字节传输侧边界

传输不跟随重定向。HTTPS 附加连接通过 httpx2 遵循 HTTPS_PROXY、ALL_PROXY、NO_PROXY 及其小写形式;信任部署运维人员的代理来路由连接。明文回环和 provider 私有链路连接保持直连,确保凭据不会离开预期链路。TLS 验证仍使用 verify,不使用 SSL_CERT_FILE 或 SSL_CERT_DIR。normalize_http_endpoint() 应用相同端点策略,但不打开连接。允许的 URL 格式、TLS 和凭据归属见 Remote Envd。

自定义 WebSocketConnection 提供 subprotocol、异步 send、recv、close 和 wait_closed。Host 在交出连接前验证凭据。框架适配器将断开转换为 EOFError 或 OSError;wait_closed 不能与传输的 recv 循环竞争。

设备与会话归属

EIPDeviceConnection.initialize() 接受传输、expected_device_id、可选客户端名称/版本、initialization_timeout=10.0、request_timeout=None,以及逐会话的 max_in_flight=32。仅在显式首次接触注册时使用 expected_device_id=None,随后保存并验证返回的身份。

设备提供:

  • descriptor 和 describe():缓存或最新观测的设备信息,包括路径格式、默认工作目录和目录发现可用性。
  • list_directories(DirectoryListParams(...)):有界的单层目录列表,携带精确预期设备 ID 与代次、绝对路径、偏移量和上限。不打开会话。
  • open_session(working_directory=None, egress=None, required_methods=(), readiness_timeout=10.0):新的独立会话。省略 cwd 时选择设备默认值。受控出站网络设备要求提供 egress;其他设备会拒绝它。必需方法用于断言兼容性,不是权限。
  • attach_session(descriptor):在断线宽限期内,显式附加到同一设备代次中的精确已有会话。绝不重放操作或恢复传输。
  • close():关闭本地管理的会话,然后关闭物理连接。

每个会话具有固定 session_id、代次和工作目录,管理自己的生成式 client、文件传输、命令、输出和回执命名空间。独立会话可在任意传输通道上并发运行。不同会话可以复用操作 ID,但不共享证据。

EIPSession 提供 describe()、readiness(timeout=10.0)、open_reader()、open_writer()、open_output()、observe_computer()、close() 和 abort()。客户端在会话打开期间维护会话内保活。关闭或中止会话绝不会关闭同级会话或借用的传输通道。abort() 会在有限时间内尽力关闭会话,但不声称结果不确定的工作已经结束。

刷新会话描述符可以收窄方法和限制;身份、代次和固定 cwd 不能改变。未就绪响应或会话内协议失败会隔离该会话。传输损坏或丢失会终止该连接上的全部本地范围。在每个借用适配器的整个生命周期中,保持设备所有者存活。

读写二进制文件

EIP 路径是设备文件系统命名空间 中的绝对路径,不是挂载相对路径或 Harness 聚合路径。工作目录是默认值,不是访问边界。POSIX 使用 /work/report.txt;Windows 使用 /C:/work/report.txt 或 /UNC/server/share/report.txt:

Python
from a13n_envd_client.eip.v1 import EIPPath

path = EIPPath(path="/work/report.txt")

async with session.open_writer(path, mode="upsert") as writer:
    await writer.write(b"Hello from EIP\n")
    committed = await writer.commit()

async with session.open_reader(path) as reader:
    async for chunk in reader:
        print(chunk.decode("utf-8"), end="")
    completion = reader.completion

这些片段要求声明了对应方法,并具有操作系统写入权限。任意文本流应使用增量解码器:分块边界不一定与 UTF-8 字符边界一致。大型传输应将字节转发到有界的应用接收端,不要在内存中累积全部数据。

文件读取器(Reader)

open_reader(path, byte_range=None, transfer_timeout_ms=None) 返回只能进入一次的 EIPFileReader。它管理附加、偏移量/摘要验证、完成证据和类型化 reader 关闭。opened 提供协商后的打开结果;open_context 提供生成的操作上下文。在出现终结证据前,completion 不可用。

文件写入器(Writer)

open_writer(path, mode=..., executable=None, transfer_timeout_ms=None) 返回只能进入一次、采用暂存写入的 EIPFileWriter。mode 接受生成的 FileWriteMode 或字符串值。write() 接受 bytes、bytearray 或 memoryview,实施传输限制,并按协商后的帧大小拆分块。

重要

退出上下文不会提交。 必须显式调用 await writer.commit();否则退出会中止暂存 writer。提交封闭流,验证 SHA-256 和字节计数证据,并通过守护进程提交操作发布文件。opened、transferred_bytes、open_context、commit_context 和提交后的 result 提供相应证据。

可能需要恢复时,保留提交操作 ID。分派后超时不能证明已经回滚;即使中止也可能报告提交正在进行或已经完成。

读取保留的进程输出

open_output(reference, start_offset=0, observed=None) 返回 EIPOutputReader,不是活跃进程句柄。reference 接受生成的类型或其字符串值;observed 可携带匹配的先前 OutputInfo。

Python
reader = session.open_output(output_reference)
while not reader.eof:
    page = await reader.read_page(wait_ms=1000)
    consume_bytes(page.data)
    # Persist offsets only under the Host's own output-retention policy.

片段中的 output_reference 和 consume_bytes 由应用管理。EIPOutputPage 包含 start_offset、next_offset、data、output 和 eof。reader 属性提供 reference、offset、最新 output 和 eof。异步迭代产出非空字节块,以一秒一页的方式等待直到 EOF。

reader 验证连续偏移量、精确引用、单调计数器与完成状态、不可变预览前缀和终结字节计数。证据无效会因协议错误隔离所属会话。EOF 表示生产方完成且已读到保留内容末尾,不代表所有产生的字节都被保留。声称输出完整前,检查 OutputInfo.content_complete 和产生/保留计数器。

超时、取消与回执

RequestCoordinator 管理单个设备 reader,以及有界的请求关联和接纳。SessionRequester 为操作调用和二进制传输限定范围。已发送但被调用者放弃的请求,在收到响应或传输终结事件前仍占用关联状态和容量;调用者取消不会取消共享 stdio 写入。两者都是高级传输集成基础组件;普通调用者使用 EIPSession 及其生成客户端。

EIPCallContext 要求 1–128 字符的操作 ID,可选提供正 uint64 timeout_ms。操作 ID 与 JSON-RPC 请求 ID 不同。方法的回执/重放语义要求结果核对时,提供稳定的操作 ID。

提供 timeout_ms 时,本地接纳单独限时;响应等待允许操作预算加协调器余量(未配置请求超时时为 30 秒)。未提供操作预算时,应用配置的请求超时。因此,60 秒操作加 30 秒余量允许等待响应 90 秒。初始化和就绪仍受外层期限限制。

取消、传输断开或本地超时不能证明已发送修改失败或不存在。客户端不会自动重试结果不确定的操作。发出不同操作前,通过 receipt.get、方法和证据窗口允许的重放,或原生状态检查核对结果。operation.cancel 本身不会把不确定性变为回滚。

错误参考

异常含义 / 证据
EIPClientError客户端失败基类
EIPProtocolError无效的消息帧、关联或协议证据
EIPTransportError收到有效关联响应前传输失败
EIPTransportClosedError传输通道已关闭,可能仍有在途请求
EIPConnectionError连接建立/交换失败,不能证明未分派
EIPRequestTimeoutError本地等待过期;检查 dispatched
EIPMethodError有效关联的 EIP 错误;检查类型化 error
EIPSessionStateError本地会话/辅助组件状态无效,或方法不可用
EIPTransferError传输重置/失败;可选 status 和 offset 证据

自定义传输集成实现 EIPTransport,通过 EIPTransportFrame 交换 ControlFrame 或生成的二进制 DataFrame 值。必须保留消息帧、大小限制、序列化和生命周期语义;这些导出项不是另一套资源供应 API。

生成的方法参考

下面的当前生成接口来自 a13n_envd_client.eip.v1.METHODS。从该模块导入参数/结果类型、枚举、编解码器、EIP_PROTOCOL_VERSION 和底层 EIPClient。IDL 和生成器管理线上 schema;不要手动编辑生成文件。

可用性仍由初始化后的描述符决定。Python 中存在生成的方法,不代表每个配置的守护进程都提供该方法。

EIP 方法Python 方法参数结果重放类别
computer.describecomputer_describeComputerDescribeParamsComputerDescribeResultactive_only
computer.observecomputer_observeComputerObserveParamsComputerObserveResultactive_only
computer.close_observationcomputer_close_observationFileReaderCloseParamsFileReaderCloseResultactive_only
computer.clickcomputer_clickComputerClickParamsComputerActionResultterminal_evidence
computer.movecomputer_moveComputerMoveParamsComputerActionResultterminal_evidence
computer.dragcomputer_dragComputerDragParamsComputerActionResultterminal_evidence
computer.scrollcomputer_scrollComputerScrollParamsComputerActionResultterminal_evidence
computer.type_textcomputer_type_textComputerTypeTextParamsComputerActionResultterminal_evidence
computer.press_keyscomputer_press_keysComputerPressKeysParamsComputerActionResultterminal_evidence
device.describedevice_describeDeviceDescribeParamsDeviceDescribeResultledger_external
directory.listdirectory_listDirectoryListParamsDirectoryListResultledger_external
egress.updateegress_updateEgressUpdateParamsEgressUpdateResultledger_external
environment.describeenvironment_describeEnvironmentDescribeParamsEnvironmentDescribeResultactive_only
environment.readinessenvironment_readinessEnvironmentReadinessParamsEnvironmentReadinessResultactive_only
file.abort_writerfile_abort_writerFileWriterAbortParamsFileWriterAbortResultactive_only
file.close_readerfile_close_readerFileReaderCloseParamsFileReaderCloseResultactive_only
file.commit_writerfile_commit_writerFileWriterCommitParamsFileWriterCommitResultterminal_evidence
file.copyfile_copyFileCopyParamsFileCopyResultterminal_evidence
file.findfile_findFileFindParamsFileFindResultactive_only
file.listfile_listFileListParamsFileListResultactive_only
file.mkdirfile_mkdirFileMkdirParamsFileMkdirResultterminal_evidence
file.movefile_moveFileMoveParamsFileMoveResultterminal_evidence
file.open_readerfile_open_readerFileReaderOpenParamsFileReaderOpenResultactive_only
file.open_writerfile_open_writerFileWriterOpenParamsFileWriterOpenResultactive_only
file.patch_textfile_patch_textFilePatchTextParamsFilePatchTextResultterminal_evidence
file.read_textfile_read_textFileReadTextParamsFileReadTextResultactive_only
file.removefile_removeFileRemoveParamsFileRemoveResultterminal_evidence
file.searchfile_searchFileSearchParamsFileSearchResultactive_only
file.statfile_statFileStatParamsFileStatResultactive_only
file.write_textfile_write_textFileWriteTextParamsFileWriteTextResultterminal_evidence
initializeinitializeInitializeParamsInitializeResultledger_external
operation.canceloperation_cancelOperationCancelParamsOperationCancelResultactive_only
output.readoutput_readOutputReadParamsOutputReadResultactive_only
output.releaseoutput_releaseOutputReleaseParamsOutputReleaseResultterminal_evidence
port.inspectport_inspectPortInspectParamsPortInspectResultactive_only
port.waitport_waitPortWaitParamsPortWaitResultactive_only
process.close_stdinprocess_close_stdinProcessCloseStdinParamsProcessCloseStdinResultterminal_evidence
process.inspectprocess_inspectProcessInspectParamsProcessInspectResultactive_only
process.killprocess_killProcessKillParamsProcessKillResultterminal_evidence
process.releaseprocess_releaseProcessReleaseParamsProcessReleaseResultterminal_evidence
process.signalprocess_signalProcessSignalParamsProcessSignalResultterminal_evidence
process.startprocess_startProcessStartParamsProcessStartResultterminal_evidence
process.waitprocess_waitProcessWaitParamsProcessWaitResultactive_only
process.write_stdinprocess_write_stdinProcessWriteStdinParamsProcessWriteStdinResultterminal_evidence
receipt.getreceipt_getReceiptGetParamsReceiptGetResultactive_only
session.attachsession_attachSessionAttachParamsSessionOpenResultledger_external
session.closesession_closeSessionCloseParamsSessionCloseResultledger_external
session.keepalivesession_keepaliveSessionKeepaliveParamsSessionKeepaliveResultledger_external
session.opensession_openSessionOpenParamsSessionOpenResultledger_external
shell.execshell_execShellExecParamsShellExecResultterminal_evidence

构建请求时检查精确的版本字段和验证约束:

Python
from a13n_envd_client.eip.v1 import FileReadTextParams, METHODS

schema = FileReadTextParams.model_json_schema()
assert "context" in schema["properties"]
assert "file.read_text" in METHODS

EIP 契约定义操作结果、回执窗口、传输完整性和兼容性。生成的方法元数据记录重放类别,不代表允许用新操作 ID 重复结果不确定的修改。

验证与选择高层 API

Terminal
uv run --locked pytest packages/a13n-envd-client/tests

客户端套件通过协议 fixture 覆盖消息帧、会话、错误、传输和输出。原生进程清理和守护进程可用性需要独立的 Envd 集成检查。外层沙箱由 Host 建立,而非 Envd。Host 管理进程启动和运行时初始化时,使用 Local Envd;应用工具使用环境操作。

本页目录