Help us shape the future of Go! Take the 2026 Go Developer Survey

protocol

package module
v0.9.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 25, 2026 License: Apache-2.0 Imports: 3 Imported by: 0

README

agentdock-protocol

Shared contracts used by AgentDock and NexusDock.

The repository intentionally has two logical layers:

  • the root protocol package owns only the AgentDock ↔ NexusDock Bridge wire protocol: node pairing and connect HTTP shapes, envelopes, Hello capabilities, operations, and MCP App resource identities/contracts;
  • mcpcontract owns only the canonical model-facing MCP contracts shared by the two entrypoints: input/output schemas, annotations, and bounded behavior vectors.

Neither package owns AgentDock runtime behavior, NexusDock stores, renderer HTML, Recall persistence, Workflow persistence, or HTTP handlers. Those remain in their application repositories.

共享 MCP Apps

独立的 mcpapps 包维护两个入口复用的 UI 文档。HTML("view_image", ...) 返回图片组件,资源身份与兼容契约由根包的 ImageUIResourceURI / ImageUIContract 定义。图片内容仍使用标准 MCP image 块,各入口自行注册资源和绑定工具,不将执行或 HTTP 逻辑放进共享包。

Task Progress v2 由 task_create 唯一挂载。组件保存创建结果中的 task_id,经 NexusDock 时同时保存原始工具输入中的 node_id,之后通过 task_manage action=get 读取同一设备上的权威任务状态。旧的 Task Progress v1 URI/contract 仅用于滚动升级期间识别旧节点,不承载新的 live-card 行为。

图片组件只处理本次工具结果,并先显示预览;只有用户点击确认按钮后才把图片提供给模型。点击后优先使用标准 MCP Apps ui/update-model-context / ui/message,宿主未暴露标准图片能力时再回退到 ChatGPT 的 uploadFile / widgetState.imageIds。未提供任何模型图片通道的客户端只显示预览;不保证同轮视觉,不读取额外文件、不默认保存到文件库,也不扩大 CSP 网络域名。

验证命令:go test ./...、go test -race ./...、go vet ./...、node --test mcpapps/app.test.mjs mcpapps/image.test.mjs mcpapps/task_live.test.mjs(Node 20+)。组件上传、宿主状态更新、错误、重复点击、换图竞争、ACP 会话详情与历史消息投影及非父 frame 消息均有行为测试。真实宿主交互仍须独立验收,不能仅凭单元测试推定。

节点配对与连接

pairing.go 定义节点建立 Bridge 之前仅有的两个 HTTP 入口,路径都相对于节点配置的 Nexus endpoint:

  • POST /v1/nodes/pair:请求体 PairRequest(code、device_id、name),成功时返回 201 Created 与 PairResponse(node.id、device_token)。其他状态码均表示失败,错误体由各实现自行定义。服务端可以在响应中增加字段,节点必须忽略未知字段。
  • GET /v1/nodes/connect:WebSocket 升级请求携带 Authorization: Bearer <device_token>;节点先发送 node.hello,服务端回复 node.ready(protocol_version、heartbeat_ms、不带路径的 HTTPS public_url)。Hello.device_id 必须等于配对时的 device_id。

这些消息不携带任何账号、租户或工作区选择字段:配对码在服务端签发时已经决定归属,节点只认识自己配置的 endpoint。testdata/ 中的 golden fixture 是两端实现的对照样例。

Bridge capability negotiation

Connection Protocol v2 通过加法 capability token 协商可选 Bridge operation,不因此升级 v3:

  • bridge.capabilities.v1:节点采用显式 capability negotiation;
  • bridge.context.local.v1:支持 context.local;
  • bridge.runtime.request.v1:支持 runtime.request;
  • bridge.resource.read.v1:支持 resource.read;
  • bridge.artifact.read.v1:继续表示支持 artifact.read。

节点包含 bridge.capabilities.v1 时,未声明的可选 Bridge operation 必须视为不支持。节点不包含该 marker 时,视为 legacy AgentDock v2;迁移窗口内消费者必须保持历史的 context/runtime/resource 行为,不能因为旧节点没有新 token 而降级。

Hello.Tools 继续作为 tool.call 能力和完整 Tool Descriptor 的事实来源,因此公共协议不增加 bridge.tool.call.v1。这些 token 与 Runtime 平台无关,不增加 Android / Phone 专属 Bridge capability。

Documentation

Index

Constants

View Source
const (
	// PairPath accepts POST with a JSON PairRequest and answers 201 Created with a PairResponse.
	// Any other status means pairing failed; the error body is implementation specific.
	PairPath = "/v1/nodes/pair"
	// ConnectPath is the WebSocket upgrade endpoint (ws/wss matching the endpoint scheme).
	// The upgrade request carries "Authorization: Bearer <device_token>". After the upgrade the
	// node sends MessageNodeHello first and the server answers MessageNodeReady.
	ConnectPath = "/v1/nodes/connect"
)

Node pairing and connection are the only HTTP surfaces a node uses before the Bridge WebSocket is established. Both paths are relative to the Nexus endpoint the node was configured with; a node never discovers or selects anything beyond that endpoint.

View Source
const (
	MessageNodeHello     = "node.hello"
	MessageNodeReady     = "node.ready"
	MessageNodeHeartbeat = "node.heartbeat"
	MessageToolInvoke    = "tool.invoke"
	MessageToolResult    = "tool.result"
	MessageToolError     = "tool.error"
	MessageToolCancel    = "tool.cancel"
)
View Source
const (
	OperationRuntimeRequest = "runtime.request"
	OperationContextLocal   = "context.local"
	OperationToolCall       = "tool.call"
	OperationResourceRead   = "resource.read"
	OperationArtifactRead   = "artifact.read"
)
View Source
const (
	// CapabilitiesNegotiationCapability marks a node that uses explicit Bridge feature negotiation.
	// Nodes without this marker are legacy v2 nodes and retain the historical optional-operation behavior.
	CapabilitiesNegotiationCapability = "bridge.capabilities.v1"
	// ContextLocalCapability advertises support for context.local.
	ContextLocalCapability = "bridge.context.local.v1"
	// RuntimeRequestCapability advertises support for runtime.request.
	RuntimeRequestCapability = "bridge.runtime.request.v1"
	// ResourceReadCapability advertises support for resource.read.
	ResourceReadCapability = "bridge.resource.read.v1"
	// ArtifactReadCapability is the frozen Bridge capability token for private Artifact reads.
	ArtifactReadCapability = "bridge.artifact.read.v1"
	// MaxArtifactChunkBytes bounds one raw Artifact chunk before JSON/base64 encoding on the Bridge.
	MaxArtifactChunkBytes = 512 << 10
)
View Source
const (
	ContextUIResourceURI   = "ui://agentdock/context"
	WorkspaceUIResourceURI = "ui://agentdock/workspace-context"
	// LegacyTaskProgressUIResourceURI identifies the original one-shot task renderer.
	// Keep it known so NexusDock can relay old AgentDock nodes during rolling upgrades.
	LegacyTaskProgressUIResourceURI = "ui://agentdock/task-progress"
	TaskProgressUIResourceURI       = "ui://agentdock/task-progress/v2"
	FileChangeUIResourceURI         = "ui://agentdock/file-change"
	RecallUIResourceURI             = "ui://agentdock/recall"
	WorkflowUIResourceURI           = "ui://agentdock/workflow"
	DynamicMCPUIResourceURI         = "ui://agentdock/dynamic-mcp"
	ArtifactUIResourceURI           = "ui://agentdock/artifact"
	ACPStatusUIResourceURI          = "ui://agentdock/acp-status"
	ACPPromptUIResourceURI          = "ui://agentdock/acp-prompt"
	ImageUIResourceURI              = "ui://agentdock/view-image/v1.html"
)
View Source
const (
	ContextUIContract            = "agentdock.context.fleet.v1"
	WorkspaceUIContract          = "agentdock.workspace-context.v1"
	LegacyTaskProgressUIContract = "agentdock.task-progress.v1"
	TaskProgressUIContract       = "agentdock.task-progress.v2"
	FileChangeUIContract         = "agentdock.file-change.v1"
	RecallUIContract             = "agentdock.recall.v1"
	WorkflowUIContract           = "agentdock.workflow.v1"
	DynamicMCPUIContract         = "agentdock.dynamic-mcp.v1"
	ArtifactUIContract           = "agentdock.artifact.v1"
	ACPStatusUIContract          = "agentdock.acp-status.v1"
	ACPPromptUIContract          = "agentdock.acp-prompt.v1"
	ImageUIContract              = "agentdock.view-image.v1"
)
View Source
const AgentDockUIResourcePrefix = "ui://agentdock/"
View Source
const ConnectionProtocolVersion = "2"
View Source
const MCPAppMIMEType = "text/html;profile=mcp-app"
View Source
const (
	// MaxNodeNameRunes bounds PairRequest.Name after trimming surrounding whitespace.
	MaxNodeNameRunes = 100
)

Variables

This section is empty.

Functions

func UIResourceContract

func UIResourceContract(uri string) (string, bool)

UIResourceContract returns the renderer contract bound to one AgentDock MCP App URI. URIs identify resources and remain stable; renderer compatibility evolves through the contract string.

func ValidDeviceID added in v0.9.0

func ValidDeviceID(deviceID string) bool

ValidDeviceID reports whether a node-generated device ID is acceptable on the wire.

func ValidNodeName added in v0.9.0

func ValidNodeName(name string) bool

ValidNodeName reports whether a trimmed node display name is acceptable on the wire.

Types

type Hello

type Hello struct {
	DeviceID           string                 `json:"device_id"`
	Version            string                 `json:"version"`
	ProtocolVersion    string                 `json:"protocol_version"`
	OS                 string                 `json:"os"`
	Arch               string                 `json:"arch"`
	Capabilities       []string               `json:"capabilities"`
	BridgeCapabilities []string               `json:"bridge_capabilities,omitempty"`
	ToolContractHash   string                 `json:"tool_contract_hash"`
	Tools              []ToolDescriptor       `json:"tools"`
	UIResources        []UIResourceCapability `json:"ui_resources"`
}

Hello is the complete node capability snapshot sent at connection time.

type Message

type Message struct {
	Type            string          `json:"type"`
	RequestID       string          `json:"request_id,omitempty"`
	Traceparent     string          `json:"traceparent,omitempty"`
	Tracestate      string          `json:"tracestate,omitempty"`
	Operation       string          `json:"operation,omitempty"`
	Arguments       json.RawMessage `json:"arguments,omitempty"`
	Result          json.RawMessage `json:"result,omitempty"`
	Error           *RemoteError    `json:"error,omitempty"`
	Hello           *Hello          `json:"hello,omitempty"`
	ProtocolVersion string          `json:"protocol_version,omitempty"`
	HeartbeatMS     int             `json:"heartbeat_ms,omitempty"`
	// PublicURL is set on MessageNodeReady: the server's public HTTPS origin without a path.
	// Nodes derive server callback URLs from it, so servers must not require an extra path prefix.
	PublicURL string `json:"public_url,omitempty"`
}

Message is the AgentDock ↔ NexusDock WebSocket envelope.

type PairRequest added in v0.9.0

type PairRequest struct {
	Code     string `json:"code"`
	DeviceID string `json:"device_id"`
	Name     string `json:"name"`
}

PairRequest exchanges a short-lived single-use pairing code for a device token.

DeviceID is generated by the node for every pairing and must satisfy ValidDeviceID. The same DeviceID is later sent in Hello.DeviceID, and servers reject a connection whose Hello.DeviceID differs from the DeviceID recorded at pairing.

type PairResponse added in v0.9.0

type PairResponse struct {
	Node              PairedNode `json:"node"`
	DeviceToken       string     `json:"device_token"`
	WebSocketEndpoint string     `json:"websocket_endpoint,omitempty"`
}

PairResponse is the 201 Created body of PairPath. Servers may add fields; nodes must ignore fields they do not know.

type PairedNode added in v0.9.0

type PairedNode struct {
	ID string `json:"id"`
}

PairedNode identifies the node record created by pairing.

type RemoteError

type RemoteError struct {
	Code      string         `json:"code"`
	Message   string         `json:"message"`
	Category  string         `json:"category,omitempty"`
	Retryable bool           `json:"retryable,omitempty"`
	Details   map[string]any `json:"details,omitempty"`
}

func (*RemoteError) Error

func (e *RemoteError) Error() string

type ToolDescriptor

type ToolDescriptor struct {
	Name         string         `json:"name"`
	Title        string         `json:"title,omitempty"`
	Description  string         `json:"description,omitempty"`
	InputSchema  map[string]any `json:"inputSchema"`
	OutputSchema map[string]any `json:"outputSchema,omitempty"`
	Meta         map[string]any `json:"_meta,omitempty"`
	Annotations  map[string]any `json:"annotations,omitempty"`
}

ToolDescriptor is the Bridge projection of an MCP tool descriptor. UI resource capability is deliberately absent: per-tool _meta.ui is presentation binding only.

type UIResourceCapability

type UIResourceCapability struct {
	URI      string `json:"uri"`
	Contract string `json:"contract"`
	MIMEType string `json:"mime_type"`
}

UIResourceCapability advertises a resource the node can actually serve through resource.read.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL