Covonaut 的 A2UI 协议支持
a2ui 实现 A2UI 0.9.1。Agent 描述界面结构,客户端将其渲染为自己的控件。界面结构与数据模型分开传输,可以逐步更新,无需执行 Agent 提供的任意代码。
功能
- 支持服务端的 createSurface、updateComponents、updateDataModel 和 deleteSurface 消息。
- 支持客户端 action、error 消息及能力交换。
Dynamic支持字面量、JSON Pointer 数据绑定和函数;ChildList支持固定子组件列表和按模板生成的列表。- 提供标准组件目录(catalog),也可以注册自定义组件。
- 按 RFC 6901 读取、设置和删除数据,支持插入或更新值、操作数组及通过
-追加元素。 SurfaceStore保存界面状态、组件关系和客户端数据模型。- 检查组件结构、根节点、不存在的引用和循环,并返回
VALIDATION_FAILED错误。 - 提供 JSONL 编码与解码、Builder、组件构造器,以及 A2A 和 AG-UI 传输适配。
除 Covonaut 模块外没有额外外部依赖。
架构
dynamic 和 component 定义数据绑定与组件,message 和 client 定义双向消息。catalog 管理组件类型,datamodel 按 JSON Pointer 操作数据,surface 保存界面状态,validate 检查结构。stream 处理 JSONL,builder 帮助构建界面,两个 binding 文件负责传输适配。
快速开始
生成界面:服务端/Agent
程序创建名为 profile 的界面,将姓名控件绑定到数据模型,并以 JSON Lines 格式输出消息。
go
package main
import (
"log"
"os"
"github.com/covoyage/covonaut/a2ui"
)
func main() {
if err := run(); err != nil {
log.Fatal(err)
}
}
func run() error {
envs := a2ui.NewSurface("profile", a2ui.BasicCatalogID).
Theme(map[string]any{"primaryColor": "#00BFFF"}).
Add(a2ui.Column("root", "name", "title")).
Add(a2ui.Text("name", a2ui.Bind("/user/name"))).
Add(a2ui.Text("title", "Mathematician")).
Data("/user/name", "Ada Lovelace").
Build()
// Stream as JSON Lines.
enc := a2ui.NewEncoder(os.Stdout)
if err := enc.EncodeAll(envs); err != nil {
return err
}
return nil
}读取界面:客户端/渲染器
Decoder 逐条解析消息,SurfaceStore 根据消息更新状态。渲染器需要从 root 开始,沿组件之间的引用遍历,并解析数据绑定。
go
store := a2ui.NewSurfaceStore()
dec := a2ui.NewDecoder(reader)
for {
env, err := dec.Decode()
if err == io.EOF {
break
}
if err != nil {
return err
}
if err := store.Apply(env); err != nil {
return err
}
}
surface, ok := store.Surface("profile")
if !ok {
return errors.New("profile surface not found")
}
root, ok := surface.Root()
if !ok {
return errors.New("profile root not found")
}
name, ok := surface.Get("/user/name")
if !ok {
return errors.New("profile name not found")
}
fmt.Println(root, name)呈现前验证
先按组件目录验证每条消息,再检查整棵组件树。消息的外层结构在协议中称为“信封”。验证错误可以按标准格式反馈给模型,请它修正。
go
cat := a2ui.BasicCatalog()
for _, env := range envs {
if errs := a2ui.ValidateEnvelope(env, cat); len(errs) > 0 {
// Each error marshals to the protocol's VALIDATION_FAILED format,
// ready to feed back to an LLM for self-correction.
return fmt.Errorf("invalid envelope: %v", errs)
}
}
surface, ok := store.Surface("profile")
if !ok {
return errors.New("profile surface not found")
}
treeErrs := a2ui.ValidateSurfaceTree(surface, cat) // root, refs, cycles
if len(treeErrs) > 0 {
return fmt.Errorf("invalid surface tree: %v", treeErrs)
}传输绑定
A2UI 不要求使用特定传输协议,当前提供以下适配:
A2A
每条 A2UI 消息封装为一个数据 Part,并标记为 application/a2ui+json。
go
msg, err := a2ui.EnvelopesToMessage(string(a2a.RoleAgent), envs)
if err != nil {
return err
}
decodedEnvs, err := a2ui.MessageEnvelopes(msg)
if err != nil {
return err
}
fmt.Println(len(decodedEnvs))AG-UI
每条 A2UI 消息通过一个 CUSTOM 事件发送。
go
ev := a2ui.ToCustomEvent(env)
decodedEnv, ok, err := a2ui.FromCustomEvent(ev)
if err != nil {
return err
}
if !ok {
return errors.New("event does not contain an A2UI envelope")
}
fmt.Println(decodedEnv.Kind())协议注意事项
- 组件以扁平列表传输,通过 ID 引用形成树,必须恰好有一个
root。组件定义可以乱序到达;根节点尚未出现时,客户端应先缓存消息。 - 每个界面的数据通过 JSON Pointer 定位。更新会按 path 插入或修改值,省略 value 表示删除。删除数组元素时,会将其设为 null 并保留数组长度;路径
/用于替换整个模型。 - 开启
sendDataModel后,客户端通过消息元数据中的a2uiClientDataModel附带数据模型,具体见ClientDataModel。 - 当前支持 0.9.1,尚未实现 1.0 候选版本中的 actionResponse 和 action ID。