Skip to main content

brickly-sdk

Brickly Brick Python runtime 官方 SDK。通过 loopback gRPC 接入 Host Runtime (invoke / interact),让 Python Brick 专注写命令逻辑。缺少 Host endpoint 时拒绝 BPP fallback。

业务日志请使用 brick.info / brick.warn / brick.error(或兼容旧名 brick.log),经 Host diagnostics.log 进入日志中心。不要手写旧 stdin/stdout 协议帧。平台未连接时这些方法是 no-op,不会抛错。

安装

pip install brickly-sdk==0.11.0

国内环境可以使用 PyPI 镜像:

pip install -i https://pypi.tuna.tsinghua.edu.cn/simple brickly-sdk==0.11.0

快速开始

from brickly import BricklyRuntime

brick = BricklyRuntime()


@brick.on_command("hello")
def hello(ctx, input_value):
    data = input_value if isinstance(input_value, dict) else {}
    name = str(data.get("name") or "Brickly")
    brick.info("hello", {"name": name})
    return {"message": f"Hello, {name}"}


brick.run()

SDK 会自动处理:

  • 连接 BRICKLY_HOST_ENDPOINT 并注册 gRPC Runtime
  • invoke / interact 命令分发
  • 取消信号、ctx.is_cancelled() 和 ctx.on_cancel(...)
  • invoke 一次一结果;interact 用 ctx.send / on_event / closed
  • 再跑自己的命令:invoke / interact / call(已有占用则不 dispose;没有占用则拒绝)
  • Host 平台 / UI / Resource 客户端
  • 可选 shutdown hook
  • 子窗口创建、窗口方法调用、window.* 事件路由
  • 事件总线 events.publish(...) / events.on(...)(公共事件 命名空间:主题;窗口寿命用 win.on)
  • alias-first 跨 Brick 调用与会话:dependencies.require(alias)
  • 平台能力 platform.screenshot、platform.screen、platform.input、platform.clipboard、platform.system

跨 Brick 的 invoke / interact 不设置 SDK 本地固定超时,由 Host 调用生命周期统一终止,避免大资源或长任务被误判超时。其他底层 Host API 仍保留默认超时,显式 timeout 的调用方式不变。

进阶:双向 live

ctx.send 只在 interact 里有意义,不要写进 hello。页面用 interact,不要用 call。

@brick.on_command("live")
def live(ctx, input_value):
    n = {"value": 0}

    def on_event(event):
        data = event if isinstance(event, dict) else {}
        ctx.send({"type": "reply", "text": "收到" + str(data.get("text") or "")})

    ctx.on_event(on_event)
    ctx.closed.wait()
    return {"n": n["value"]}

会话内 RPC 用 ctx.handle_requests。作者和调用方成对写法见 docs/invoke-interact.md 第 4.6 节。摘要:

from brickly import BppError

def complete(request, req_ctx):
    if req_ctx.is_cancelled():
        raise BppError("CANCELLED", "request 已取消")
    body = request if isinstance(request, dict) else {}
    return {"items": suggest(str(body.get("prefix") or ""))}

@brick.on_command("assist")
def assist(ctx, _input):
    ctx.handle_requests(complete)
    ctx.closed.wait()
    return {"ok": True}

调用方(require(alias)、ToolHandle、brick.interact 同一套):必须传 on_event,结果走 end()。

session = await client.interact(
    "assist",
    {"file": "main.ts"},
    on_event=lambda event: None,
)
items = await session.request({"prefix": "con"})
result = await session.end()

破坏性: request() 同步返回句柄,不是结果,也不是 async def。pending = session.request(...) 拿到的是句柄。await session.request(...) 仍可用(句柄可 await),但要 cancel() 必须先拿句柄再 await:

pending = session.request({"prefix": "con"})
pending.cancel()
await pending            # 也可 await pending.result()

错误看 error.code:CANCELLED / DEADLINE_EXCEEDED / SESSION_CLOSED。DEADLINE_EXCEEDED 同时也是 TimeoutError。invoke / call 本轮不改成 CallHandle。作者 ctx.cancel_event / is_cancelled() 不改。

call 是 interact + 立刻 end:

poem = await client.call(
    "complete",
    {"prompt": "写一首诗"},
    on_event=lambda event: None,
)

进阶:Call 窗与 Session 窗

ctx.ui 只开 Call 窗:跟这次命令走,命令结束就关。brick.ui 开 Session 窗:跟当前占用走;keepAlive: True 时 Claim 交回后窗仍可保住 Session。不要写 lifetime= 或清单 command.window。后台定时弹窗请 invoke 再在命令里开窗,不要在定时器里直接 create_browser_window。per-call 没有 Session,不能用 brick.ui。

与 Node SDK 的同步关系

Python SDK 的协议语义与 @syllm/brickly-sdk 保持一致:

  • Python 使用 snake_case 方法名,例如 create_browser_window()、set_full_screen()。
  • Node 使用 camelCase 方法名,例如 createBrowserWindow()、setFullScreen()。
  • 两者底层走同一套 gRPC Runtime / Host 服务,窗口方法名一致。
  • 当前 SDK 包版本为 0.11.0(__version__);生产协议是 brickly.runtime.v1。
  • Python 不提供 Node 的 TypeScript CommandMap 类型生成能力;Python 侧依赖类型标注和中文 docstring 提供 IDE 补全。

命令上下文

命令处理函数会收到 CommandContext:

@brick.on_command("process")
def process(ctx, input_value):
    if ctx.is_cancelled():
        return {"cancelled": True}

    ctx.send({"type": "status", "step": "start"})
    return {"ok": True}

长期占用使用 ToolSdk.start() / ToolHandle。Runtime 里占用依赖用 require(alias).start(),必须先进入自己的命令(brick.invoke 中转);占用跟这次 Call,return 自动放手。异步上下文管理器是 dispose() 语法糖。一次性 invoke 不会 pin 进程。

常用属性和方法:

  • ctx.request_id:当前请求 ID
  • ctx.command_id:当前命令 ID
  • ctx.invocation:宿主传入的可信调用来源和依赖 Profile 映射
  • ctx.config:当前 Profile 配置快照
  • ctx.storage:本机持久 KV / collection / secrets;与体验窗共库。看不见路径或 _rev
  • ctx.ui:子窗口 API
  • ctx.events:事件总线 API
  • ctx.platform:平台能力 API
  • ctx.system:ctx.platform.system 的快捷别名
  • ctx.send(event):推给调用方(仅 interact)
  • ctx.on_event(handler):收调用方 send(仅 interact)
  • ctx.handle_requests(handler, concurrency=8):注册会话内 request handler;return 就是那条 request 的结果(仅 interact)
  • ctx.closed.wait():等到调用方 closeInput / 断开
  • ctx.dependencies.require(alias):获取绑定当前 command parent、trace 与 Profile 的依赖客户端

子窗口

from brickly import BricklyRuntime

brick = BricklyRuntime()


@brick.on_command("open")
def open_window(ctx, _input):
    win = ctx.ui.create_browser_window("ui/index.html", {"width": 640, "height": 480})
    win.expose({
        "pause": lambda _payload, _session=None: None,
    })
    win.send("tick", {"remaining": 60})
    win.on("closed", lambda payload: brick.info("窗口已关闭", {"payload": payload}))
    win.set_title("Hello from Python")
    return {"windowId": win.id, "windowKey": win.window_key}


brick.run()

WindowHandle 提供与 Node SDK 对齐的常用窗口方法,例如:

  • 几何尺寸:set_bounds()、get_bounds()、set_position()、set_size()
  • 内容区域:set_content_bounds()、get_content_bounds()、set_content_size()
  • 状态切换:minimize()、maximize()、restore()、show()、hide()、focus()
  • 状态查询:is_visible()、is_focused()、is_minimized()、is_full_screen()
  • 外观能力:set_title()、set_opacity()、set_background_color()、set_has_shadow()
  • webContents:send()、execute_javascript()、open_dev_tools()、go_back()、set_zoom_factor()、copy()、paste()、undo()

关闭是显式生命周期操作:

result = win.close()
if result["status"] in ("pending", "prevented"):
    win.focus()  # 句柄仍可用

termination = win.force_close()

close() 返回 closed/prevented/pending/not-found。只有 closed/not-found 和 window.closed 终态事件会把句柄标记为 closed、从 Runtime Map 删除并清空 listener。终态 eventId 有界去重,transport 结束时也会释放全部窗口句柄。

跨 Brick 调用

命令处理函数内部只使用 manifest alias:

result = ctx.dependencies.require("openai").invoke(
    "chat",
    {"prompt": "hello"},
    profile_id="work",
)

from brickly.internal.grpc.client import call

poem = await call(
    ctx.dependencies.require("openai"),
    "complete",
    {"prompt": "写一首诗"},
    on_event=lambda event: None,
)

精确来源和版本由 Host 握手绑定。热键依赖 Profile 会按绑定的精确 BrickKey 自动使用,显式 profile_id 始终优先。

没有当前命令时,同一套 invoke / call / interact 就是 root:

result = brick.dependencies.require("openai").invoke(
    "chat",
    {"prompt": "hello"},
    profile_id="work",
)

大载荷与资源

普通 invoke 始终返回直接值,逻辑 JSON 输入和结果上限为 10 MiB,一次传完; 超限抛出 PAYLOAD_TOO_LARGE,不会静默改成资源类型。更大的字节走 Resource。

改限额时先改这张表,再对三语言 README 保持同一组数字:

限制 值 含义
单帧(wire chunk) 1 MiB Create/Read 每一帧上限。SDK 自动拆,调用方不必切块
gRPC 单条消息 4 MiB 传输天花板,给 protobuf 信封留余量
bytes() / text() / json() 200 MiB 整份进内存的上限;更大用 stream() / save_to()
单对象默认配额 8 GiB 真正的「能不能存 1G」
并发上传 8 每个 runtime 同时进行的 Create

str 默认 text/plain; charset=utf-8,bytes 默认 application/octet-stream。 创建受 Host 配额与 Call/Lifetime 治理。finish 前资源不可读。命令内创建会自动带 x-brickly-invocation-id。不要记录 capability token,也不要长期持久化 Ref。

已经在内存里、通常不大——超过 1 MiB 时 SDK 自动改走 Writer,调用方式不变:

note = brick.resources.create("hello", name="note.txt")
bin = brick.resources.create(data, name="input.bin")

文件、流、未知长度或 1G 级对象用 create_from,边读边传,不要先拼完整副本:

with open("large.bin", "rb") as source:
    resource = brick.resources.create_from(source, name="large.bin")
resource.save_to("out.bin")

也接受 Iterable[str | bytes] 或带 read() 的文件对象。来源失败会 abort。

边算边写用 Writer。write 接受任意大小;关闭后再写返回 RESOURCE_UPLOAD_CLOSED。 finish 幂等;finish 之后的 abort 不会撤销已发布对象:

writer = brick.resources.create_writer(name="out.bin")
try:
    writer.write(chunk)
    resource = writer.finish()
except Exception:
    writer.abort()
    raise

大结果由作者 create 后 return Handle;invoke 交回 ResourceRef,调用方再 open:

ref = ctx.dependencies.require("report").invoke("export", input_value)
resource = brick.resources.open(ref)

if resource.ref["sizeBytes"] <= 200 * 1024 * 1024:
    report = resource.json()
else:
    resource.save_to(output_path)

ctx.dependencies.require("consumer").invoke(
    "analyse",
    {"source": resource},
)

open() 只绑定句柄,不立刻访问 Host。ResourceHandle 支持 stream()、bytes()、 text()、json()、save_to()、close()、revoke();再次作为 input 时只传 ResourceRef。

resource = brick.resources.open(payload["attachment"])
try:
    resource.save_to(output_path)
finally:
    resource.close()

events.on() 回调收到的就是发布时的业务对象,不会再包一层资源,也不会水合成 ResourceHandle。若业务对象里本身带 ResourceRef,需要读内容时再 resources.open。

过程调用用 call,必须传入 on_event:

from brickly.internal.grpc.client import call

return await call(
    ctx.dependencies.require("openai"),
    "chat",
    {"prompt": "hello"},
    on_event=lambda event: ctx.send(event),
)

跨 Brick 调用需要在调用方 manifest 的 dependencies 中声明目标 Brick 和命令:

"dependencies": {
  "openai": {
    "target": {
      "brickId": "com.brickly.openai",
      "origin": "installed",
      "version": "2.1.0"
    },
    "commands": ["chat"]
  }
}

跨 Brick 会话

目标 Brick 有状态时,在 command handler 里 interact,不要另做 open()。收过程只走 on_event,说完用 end:

session = await ctx.dependencies.require("openai").interact(
    "chat",
    {"prompt": "继续这个话题"},
    on_event=lambda event: None,
)
return await session.end()

平台能力

系统 API:

@brick.on_command("show-app-info")
def show_app_info(ctx, _input):
    return {
        "appName": ctx.system.get_app_name(),
        "appVersion": ctx.system.get_app_version(),
        "userData": ctx.system.get_path("userData"),
        "isWindows": ctx.system.is_windows(),
    }

剪贴板 API:

@brick.on_command("replace-clipboard")
def replace_clipboard(ctx, _input):
    previous = ctx.platform.clipboard.read_content()
    updated = ctx.platform.clipboard.set_content({"kind": "text", "text": "来自 Python"})
    return {"previous": previous, "updated": updated}

输入和屏幕 API:

@brick.on_command("screen-info")
def screen_info(ctx, _input):
    point = ctx.platform.screen.get_cursor_screen_point()
    display = ctx.platform.screen.get_primary_display()
    return {"point": point, "display": display}


@brick.on_command("click")
def click(ctx, _input):
    ctx.platform.input.mouse_click(100, 100)
    ctx.platform.input.keyboard_tap("A", "control")
    return {"ok": True}

宿主错误会以 BppError 原样抛出。shell_open_external 仅允许 http / https / mailto。

错误处理

抛出 BppError 可以保留明确错误码:

from brickly import BppError

raise BppError("INVALID_INPUT", "url 不能为空")

普通异常会被 SDK 转换为 INTERNAL_ERROR 并返回给宿主。

日志

brick.info("开始处理", {"id": 1})

debug / info / warn / error 经 Host diagnostics.log 进入日志中心。命令 handler 内会带上当前 invocationId 挂到该 command 节点;on_ready 等无当前 command 时走顶级 diagnostic。handler 返回后的异步日志仍能靠 ContextVar 挂回(与 Node 一致)。不要手写 stdin/stdout 协议帧。

源码结构

实现按领域拆分为 transport / scope / command / session / window / events / platform / runtime,与 Node SDK 对齐。

详情见 brickly/README.md。

AI 对齐框架

本 SDK 是 Follower 实现。用 AI 跟进 Node 时请走:

  • specs/sdk/AGENT.md
  • specs/sdk/capability-matrix.yaml
  • specs/sdk/api-mapping.yaml
  • specs/sdk/prompts/follower-agent.md(target=python)

Metadata

Release files for brickly-sdk 0.11.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for brickly-sdk 0.11.0
File Size Uploaded Provenance
brickly_sdk-0.11.0.tar.gz 312.0 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for brickly-sdk 0.11.0
File Interpreter ABI Platform
brickly_sdk-0.11.0-py3-none-any.whl Python 3 none any Details

Total release size: 424.5 kB

Release files / brickly_sdk-0.11.0.tar.gz

Download URL brickly_sdk-0.11.0.tar.gz
Size 312.0 kB
Tags Source
SHA-256 checksum
How to use checksums
01061e7f8d96fb0e8df39c4be99d932790e967d73e5286ea0d074641ff111faf
BLAKE2b-256 checksum
How to use checksums
70409c3523be428a1ef7774d0d23cb7433abf20e2dbee1cc291c4b83b2e1dee3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.10.12 {"installer":{"name":"uv","version":"0.10.12","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / brickly_sdk-0.11.0-py3-none-any.whl

Download URL brickly_sdk-0.11.0-py3-none-any.whl
Size 112.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
9866eff54832a1678c26576032509fac453a61ee2638bc37d5b6fb0709266c7c
BLAKE2b-256 checksum
How to use checksums
0d3ec483f38040c3cc2d3bb83462c8b032ad7e10e53f87762646f2c27959f5ee
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.10.12 {"installer":{"name":"uv","version":"0.10.12","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Security for brickly-sdk 0.11.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page