Skip to content

从原生绑定使用 RemoteAdapter(Use RemoteAdapter from a native binding) ​

**原生绑定(native bindings)**把远程 Adapter 契约暴露为同步 FFI 面:你的宿主代码实现一个消息导向 Transport(传输接口);adapter 经它拨号并收发信封,然后你调用与 Rust 参考实现、TypeScript 语言原生客户端相同的 BaselinePorts 方法。共享库拥有一个进程级 tokio 运行时;每个导出调用都是该运行时之上的同步 block-on-async(同步阻塞执行异步调用),会话核心始终封装在 Rust 侧 —— 握手签名/校验、allowlist、nonce 单次使用、sequence、关联校验与信封认证全部在绑定内部运行,而你的宿主代码提供 Transport 并调用 port 方法。

导出的对象是 RemoteAdapterFFI(单对等节点)、MultiPeerRouterFFI(跨多个对等节点路由)与 ConnectResponderFFI(接受侧),外加用于工具服务的 ToolHandler(工具处理器)回调。本页以 Python 绑定走完完整流程;C#、Go、Kotlin 与 Swift 存在相同的面,只是使用各语言惯用名称(见符号对照表)。通用 RemoteAdapter 契约 —— 与 TypeScript、Rust 库共享的消息导向 Transport 接缝、拨号选项与错误映射 —— 见通过 Transport 使用 RemoteAdapter;本页覆盖 FFI 面。

1. 实现回调 Transport ​

你的宿主代码实现消息导向的 Transport 接口:

方法行为
send(envelope)接受恰好一个 connect 信封的字节
recv()返回下一个入站信封;阻塞直到一个信封到达或连接关闭
close()释放资源;幂等

crate 导出 loopback_transport_pair() —— 一个内存中的客户端/服务端对 —— 让你无需网络即可跑通完整流程。参考回环(loopback)smoke 把一个 LoopbackTransport 端交给仅测试用的 smoke host,从另一端通过回调 transport 驱动 adapter:

python
import spoke_connect

class LoopbackCallbackTransport:
    """委托给回环对客户端端的回调 transport。"""

    def __init__(self, inner: spoke_connect.LoopbackTransport) -> None:
        self._inner = inner

    def send(self, envelope: bytes) -> None:
        self._inner.send(envelope)

    def recv(self) -> bytes:
        return self._inner.recv()

    def close(self) -> None:
        self._inner.close()

真实部署中,用同样的三个方法覆盖你的承载载体 —— socket、WebSocket 或消息通道。Transport 每次 send / recv 调用投递恰好一个信封;字节流载体在把信封交给 adapter 之前应用长度前缀(或等价方式)定界。

2. 拨号并构造 RemoteAdapterFFI ​

connect_remote_adapter_ffi 通过你的 transport 执行拨号与握手签名交换,返回一个已建立的 adapter 句柄:

python
pair = spoke_connect.loopback_transport_pair()

adapter = spoke_connect.connect_remote_adapter_ffi(
    LoopbackCallbackTransport(pair.client()),
    seed_client,           # 本地 Ed25519 身份种子,恰好 32 字节
    client_manifest_json,  # 本地 HostCapabilityManifest(主机能力清单),JSON
    pubkey_host,           # 远端对等节点的 Ed25519 公钥,恰好 32 字节
    [peer_id_host],        # allowlist:远端 peer_id
    None,                  # 调用超时(毫秒);None 使用默认值
)
参数含义
transport你的 Transport 实现;adapter 经它收发信封
local_seed本地身份的 32 字节 Ed25519 种子(原始字节)
local_manifest_json你的 HostCapabilityManifest,JSON 字符串
remote_pubkey远端对等节点的 32 字节 Ed25519 公钥(原始字节)
allowlist该 adapter 接受的对等节点标识;远端 peer_id 必须列入
invoke_timeout_ms可选的每次调用超时;超时只让该调用失败,会话保持可用

配置、握手、版本不匹配或拨号超时失败时,构造函数在 adapter 存在之前失败 —— 错误为 FfiError.Dial,kind 为 config、handshake、protocol_version_mismatch 或 timeout。你的 transport 自身失败映射为 TransportError(连接关闭时为 Closed,I/O 错误时为 Io)。

3. 调用 port 方法 ​

port 方法接收 JSON 载荷,返回 JSON 字符串。在已建立的会话上做一个 put / get 往返:

python
entry_json = json.dumps({
    "schema_version": 1,
    "entry_id": entry_id,
    "entry_type": "character",
    "canonical_name": "Ada",
    "status": "provisional",
    "body": {"summary": "Upserted over the connect session"},
    "extensions": {},
})

put_json = adapter.put_knowledge_entry(entry_json, None)
get_json = adapter.get_knowledge_entry(entry_id)

每个 BaselinePorts 族映射为一个方法,形状相同(JSON 进 / JSON 出):get_host_capability_manifest、get_relation / put_relation、list_knowledge_entries / list_timeline_events、put_findings、list_rules 与 list_peer_host_capability_manifests。调用路径的失败以 FfiError.Rejected 呈现,SpokeResult 码原样保留,并在映射定义的场景携带 kind 与 wire_code(例如 INTERNAL_ERROR 且 kind = "transport",或 CAPABILITY_PORT_MISSING 且 wire_code = "no_capable_peer")。

adapter 还以相同的 JSON 进 / JSON 出形状暴露可选 port 面:project(project_request_json) 与 compute(compute_request_json)(l2-computable 族),以及 list_fork_timeline_events(scope_json)(l5-fork 族)。该族必须位于会话的协商能力中 —— 双方 manifest 都声明它;未协商该族的对等节点以 FfiError.Rejected 拒绝(code: "CAPABILITY_PORT_MISSING",保留 wire_code: "op_unsupported")。完整目录见可选 port 族。

同一 adapter 上的并发调用被允许;响应按 request_id 解复用,可能乱序到达。

4. 远程抽取与归属门禁 ​

另有两个受能力门控的面,与 port 方法搭载在同一已建立会话上。请在双方对等节点的 HostCapabilityManifest 中声明每个标志 —— 会话的协商集是双方握手的交集,因此双方都声明的标志即进入协商集并得到服务。

远程抽取(ke-extraction) ​

extract 是核心 op,由主机本地的抽取服务在与 port 方法相同的 ports 对象上提供。载荷就是 JSON 形式的 ExtractRequest 本身,仅含引用,服务方主机通过自己的机制完成源加载与抽取,loader 值保留在主机本地:

python
extract_json = adapter.extract(
    json.dumps(
        {
            "run_id": "run-harbor-1",
            "sources": [
                {
                    "schema_version": 1,
                    "source_id": "harbor/source/log",
                    "extensions": {},
                }
            ],
        }
    )
)

返回的 JSON 是 ExtractResponse 成功分支:candidates(每个都是 provisional)加上已关联的 run,其 run_id 回显请求。各绑定以自己的命名风格暴露该方法 —— C# 与 Go 为 Extract,Kotlin、Swift 与 Python 为 extract(见符号对照表)。manifest 独立声明能力与角色:提供抽取的主机把 input-source 角色通告为描述该主机的元数据,而门禁该 op 分派的是 ke-extraction 能力标志。在 FFI 上服务该 op 的是 PortsHandler.extract(extract_request_json) 回调,它执行完整的宿主本地抽取并应答线上 ExtractResponse JSON。

归属门禁(ke-ownership) ​

Scope 承载 port 操作在 scope 携带非空 viewpoint 字符串时,除其行能力外还要求 ke-ownership —— 门禁按原样读取 payload.scope.viewpoint,并把任何非空字符串视为承载归属。FFI 上的见证是既有的 RemoteAdapterFFI.list_knowledge_entries(scope_json) 调用,保持不变:

python
listed_json = adapter.list_knowledge_entries(
    json.dumps({"scope_id": "toy-scope-001", "viewpoint": "kb_tw_mira"})
)

各绑定以自己的命名风格拼写该方法 —— C# 与 Go 为 ListKnowledgeEntries,Kotlin 与 Swift 为 listKnowledgeEntries,Python 为 list_knowledge_entries(见符号对照表)。请在双方 manifest 中为视角承载查询声明 ke-ownership;viewpoint 为空或未设置的 Scope 仍仅按行能力放行。

抽取与归属的拒绝面 ​

两个面都经既有 FfiError 行结算,该行承载全部拒绝词汇:

拒绝来源FfiError 行
所需能力位于协商集之外,或主机不服务抽取Rejected,code: "CAPABILITY_PORT_MISSING",并保留 wire_code: "op_unsupported"
带外抽取回调自行拒绝该请求Rejected,保留回调自身的 code —— 拒绝抽取时该 code 为 CAPABILITY_PORT_MISSING,wire_code 保持未设置,因此被拒绝的抽取与缺失的能力可区分
畸形的 extract_request_json,或偏离契约载荷的回调输出Rejected,code: "INVALID_INPUT"(零线上流量)/ code: "INTERNAL_ERROR"(遏制,会话继续存活)

任一握手省略某个标志,它就不在协商交集中,且响应方会在带外回调运行之前拒绝 —— 缺失的能力以该拒绝呈现。

5. 读取会话信息 ​

adapter 暴露只读的会话元数据:

python
adapter.state()            # "Established"、"Handshaking"、"Closed"、……
adapter.session_id()       # 会话建立后的会话 id
adapter.remote_peer_id()   # 已认证的远端 peer_id
adapter.remote_manifest()  # 远端对等节点的 HostCapabilityManifest,JSON

session_id / remote_peer_id / remote_manifest 在会话建立后填充;会话信息来自已认证握手与会话核心,在建立时捕获。

6. 用 MultiPeerRouterFFI 跨多个对等节点路由 ​

new_multi_peer_router_ffi() 返回一个空路由器。拨号每个对等节点的 RemoteAdapterFFI(第 2 步),注册已建立的句柄,之后每次 port 调用都会路由到恰好一个有能力的对等节点:

python
router = spoke_connect.new_multi_peer_router_ffi()

north_id = router.register_peer(north_adapter)  # 返回远端 peer_id
router.register_peer(south_adapter)             # 对 peer_id 幂等

router.list_peers()       # 已注册的 peer_id,注册顺序
router.unregister_peer(north_id)  # 移出选择;adapter 保持开启

result_json = router.get_knowledge_entry(entry_id)  # 路由到有能力的对等节点

选择过程读取每个已注册对等节点缓存的 HostCapabilityManifest —— 对操作必需能力与精确命名空间的硬门禁、软角色偏好,以及确定性的最小 peer_id 决胜规则。当没有已注册对等节点通过硬门禁时,调用以 CAPABILITY_PORT_MISSING 与 wire_code = "no_capable_peer" 拒绝;注册一个满足条件的对等节点,然后用新的 request_id 重新调用。路由器还暴露合成视图与逐对等节点视图的 HostManifestPort(get_host_capability_manifest 与 list_peer_host_capability_manifests)。完整选择契约见跨多个对等节点路由。

7. 在 FFI 上服务并调用工具 ​

FFI 面在两个方向携带工具契约:拨号方调用响应方经带外 ToolHandler(工具处理器)回调服务的工具;响应方反向调用拨号方经 register_tool_handler 注册的处理器服务的工具。invoke_tool 存在于 RemoteAdapterFFI、MultiPeerRouterFFI 与 ConnectResponderFFI 上;register_tool_handler 存在于 RemoteAdapterFFI 与 ConnectResponderFFI 上。

发现是已认证会话的属性:remote_manifest() 返回对等节点的 HostCapabilityManifest(主机能力清单)JSON,其 tools[] 描述符即对等节点能服务的工具:

python
manifest = json.loads(adapter.remote_manifest())
tool_ids = [tool["capability_id"] for tool in manifest["tools"]]

工具 id 遵循语法 tools.<ns>.<tool_id>,且清单必须在 tools[] 与 capabilities[] 中同时列出该工具 —— 仅当能力字符串位于会话的协商能力(negotiated capabilities)中时,tools.* op 才会被分派。库侧契约见暴露并调用远程工具,本面与其镜像一致。

ToolHandler(工具处理器)回调 ​

处理器接收 JSON 字符串形式的工具参数,并返回 JSON 字符串形式的结果。参考形状随 Python 回环 smoke 发布(bindings/python/Smoke/test_loopback_remote_adapter.py):

python
class _SumToolHandler:
    """Foreign-callback tool handler: sums `a` + `b` (Rust `add_handler`
    parity) and records the invocation count."""

    def __init__(self) -> None:
        self._calls = 0

    def handle(self, arguments_json: str) -> str:
        self._calls += 1
        arguments = json.loads(arguments_json)
        return json.dumps({"sum": arguments.get("a", 0) + arguments.get("b", 0)})

    def calls(self) -> int:
        return self._calls

从 handle 抛出的 FfiError.Rejected 作为应用拒绝原样传递给调用方 —— code / message 保留,kind / wire_code 保留在错误字段上。任何其它结果(Dial 错误、非契约异常或 panic)被遏制为不带 kind / wire_code 的 INTERNAL_ERROR 拒绝,会话继续存活。

在拨号方注册处理器 ​

RemoteAdapterFFI.register_tool_handler(capability_id, handler) 服务响应方→拨号方的反向调用。对重复 id 的注册后者胜出,且注册从不修改清单;非 tools. id 以 INVALID_INPUT 拒绝,违规 id 在 message 中,零线上流量。

接受侧:connect_responder_ffi ​

FFI 面从不构建 listener(监听器)—— 主机产品在其自身网络栈中拥有 listen/accept(监听/接受连接)。接受流程与拨号对称:主机接受一条连接,将其包装为回调 Transport,并把已连接的 transport 传给 connect_responder_ffi:

python
responder = spoke_connect.connect_responder_ffi(
    LoopbackCallbackTransport(pair.server()),
    seed_host,
    _tool_manifest_json("test-responder"),  # 带 tools[] 的 HostCapabilityManifest
    [peer_id_client],                      # fail-closed 拨号方 allowlist
    {peer_id_client: pubkey_client},       # peer_id -> 32 字节 Ed25519 公钥
    ports,                                 # 可选带外 PortsHandler;None 保留拒绝分支
    None,                                  # 调用超时(毫秒);None 使用默认值
)

构造函数立即返回 —— 拨号方 hello 落定期间,响应方处于 Handshaking。在调用前以有界方式轮询 state() 至 Established;握手失败(allowlist 拒绝、hello 校验拒绝)以 state() → "Closed" 且 session_id() → None 呈现,绝不会抛出构造函数错误:

python
import time

deadline = time.monotonic() + 5.0
while responder.state() != "Established":
    if time.monotonic() >= deadline:
        raise RuntimeError(f"handshake timeout (last: {responder.state()!r})")
    time.sleep(0.01)

构造函数的 Result 槽只携带配置校验失败 —— manifest JSON、种子长度或对等节点密钥长度 → Dial { kind: "config" }。ports 是一个可选的带外回调 ports 面:传入 PortsHandler 以经回调桥服务 port.* 调用(基线 + 可选族)与 extract 核心 op,或传 None 保留文档化的缺席 ports 拒绝分支 —— 此时每条 port.* 调用与每条 extract 调用都以 CAPABILITY_PORT_MISSING(wire_code: "op_unsupported")应答(与不带 ports 的库响应方相同的 fail-closed 行)。见下文PortsHandler 回调。

PortsHandler 回调 ​

PortsHandler 是响应方的带外回调 ports 面:每个方法以 JSON 字符串接收请求载荷,以 JSON 字符串返回成功载荷 —— 与库响应方经其 ports 选项服务的目录相同,外加 extract 服务面。接口有十三个方法(九个基线 + 三个可选 + extract):

方法族服务的 op
get_knowledge_entry(entry_id)基线port.knowledge.get
put_knowledge_entry(entry_json, expected_base_revision)基线port.knowledge.put
get_relation(relation_id) / put_relation(relation_json, expected_base_revision)基线port.relation.get / port.relation.put
list_knowledge_entries(scope_json) / list_timeline_events(scope_json)基线port.scope.list_knowledge_entries / port.scope.list_timeline_events
put_findings(findings_json)基线port.finding.put
list_rules(rule_refs)基线port.rule.list
list_peer_host_capability_manifests()基线port.host.list_peer_manifests
project(project_request_json)l2-computableport.computable.project
compute(compute_request_json)l2-computableport.computable.compute
list_fork_timeline_events(scope_json)l5-forkport.fork.list_timeline_events
extract(extract_request_json)ke-extractionextract

get_host_capability_manifest 不在目录中 —— 它是会话缓存,绝不经过 ports handler 服务。方法可以抛出 FfiError.Rejected 拒绝它不服务的 op;该拒绝原样穿透给调用方作为应用拒绝 —— 抽取回调正是以此拒绝抽取。可选族仅在双方协商了该族(双方 manifest 都声明)时才被服务 —— 能力门禁先于回调运行,声明了某族却不提供其方法的主机应答与缺席 ports 相同的拒绝分支。

参考形状随 Python 回环 smoke 交付(bindings/python/Smoke/test_ports_loopback.py);可选方法展示 JSON 契约:

python
    def project(self, project_request_json: str) -> str:
        request = json.loads(project_request_json)
        return json.dumps(
            {
                "session_id": request["session_id"],
                "entry_id": request["entry_id"],
                "computable": {"tide_level": 2.4, "cargo_tons": 38},
            }
        )

    def compute(self, compute_request_json: str) -> str:
        request = json.loads(compute_request_json)
        return json.dumps(
            {
                "session_id": request["session_id"],
                "entry_id": request["entry_id"],
                "computable": request["computable"],
                "state": request["computable"],
            }
        )

    def list_fork_timeline_events(self, scope_json: str) -> str:
        scope = json.loads(scope_json)
        if scope["fork_id"] != "fork_tw_ffi_events":
            return "[]"
        return json.dumps(
            [
                {
                    "schema_version": 1,
                    "timeline_event_id": "evt_tw_ffi_storm",
                    "canonical_name": "FFI Fork Storm",
                    "fork_id": "fork_tw_ffi_events",
                    "extensions": {},
                }
            ]
        )

与工具处理器相同的重入规则适用:绝不要在 ports 回调内部回调 FFI 面 —— 把工作交给你自己的异步机制然后返回。

端到端回环对 ​

双向 smoke 把两端都作为 FFI 对象驱动 —— 响应方服务带外 ToolHandler,拨号方服务反向调用,未注册工具被拒绝,处理器抛出的拒绝原样穿透:

python
# 拨号方 FFI invoke_tool -> 响应方 FFI 带外 ToolHandler。
responder_sum = _SumToolHandler()
responder.register_tool_handler("tools.math.add", responder_sum)
sum_json = dialer.invoke_tool("tools.math.add", '{"a": 1, "b": 2}')
# sum_json == '{"sum": 3}'

# 响应方 FFI invoke_tool -> 经 RemoteAdapterFfi.register_tool_handler
# 注册的拨号侧处理器。
dialer_sum = _SumToolHandler()
dialer.register_tool_handler("tools.math.add", dialer_sum)
reverse_sum_json = responder.invoke_tool("tools.math.add", '{"a": 21, "b": 21}')
# reverse_sum_json == '{"sum": 42}'

# 已协商但未注册的工具 -> fail-closed op_unsupported。
try:
    dialer.invoke_tool("tools.echo.boom", "{}")
except spoke_connect.FfiError.Rejected as denied:
    assert denied.code == "CAPABILITY_PORT_MISSING"
    assert denied.wire_code == "op_unsupported"

处理器抛出的应用拒绝以相同形状原样穿透:

python
class _ThrowingToolHandler:
    """Foreign-callback tool handler that always raises the given
    application reject."""

    def __init__(self, reject: spoke_connect.FfiError.Rejected) -> None:
        self._reject = reject

    def handle(self, arguments_json: str) -> str:
        raise self._reject


dialer.register_tool_handler(
    "tools.echo.boom",
    _ThrowingToolHandler(
        spoke_connect.FfiError.Rejected(
            "REVISION_CONFLICT", "foreign handler rejected", None, "op_unsupported"
        )
    ),
)
try:
    responder.invoke_tool("tools.echo.boom", "{}")
except spoke_connect.FfiError.Rejected as passed:
    assert passed.code == "REVISION_CONFLICT"
    assert passed.message == "foreign handler rejected"
    assert passed.wire_code == "op_unsupported"

工具路径错误 ​

失败FfiError 行
invoke_tool / register_tool_handler 上非 tools. 的 capability_id(两个包装对象皆然)Rejected,code: "INVALID_INPUT",零线上流量,违规 id 在 message 中
invoke_tool 上畸形的 arguments_jsonRejected,code: "INVALID_INPUT",零线上流量,解析错误在 message 中
分派拒绝 —— 工具未协商,或对等节点没有已注册处理器(对等节点应答 op_unsupported / capability_missing)Rejected,code: "CAPABILITY_PORT_MISSING",携带保留的对等节点 wire_code
路由器没有有能力的对等节点Rejected,code: "CAPABILITY_PORT_MISSING",kind = wire_code = "no_capable_peer",能力 id 在 message 中
处理器抛出的 Rejected原样穿透 —— code / message 保留,kind / wire_code 保留在错误字段上
任何其它处理器结果(非契约 Dial、异常、panic)Rejected,code: "INTERNAL_ERROR",无 kind / wire_code —— 遏制,会话继续存活
单等待者调用超时Rejected,code: "INTERNAL_ERROR",kind: "timeout" —— 仅该等待者,会话保持可用
调用期间会话关闭 / 传输 I/ORejected,code: "INTERNAL_ERROR",kind: "session_closed" / "transport"

线程与容量说明 ​

处理器运行在 FFI 阻塞线程池(blocking pool)上:每次 handle 调用(与每个回调 Transport 方法一样)都经共享运行时的 spawn_blocking 池桥接,因此阻塞的带外调用绝不会独占 async worker。处理器不得同步回调 FFI 面 —— 重入(re-entrancy)会卡死会话;请把工作交给宿主自己的异步机制,然后从 handle 返回。

每个已建立的 FFI 会话在每个传输端固定占用一个阻塞线程池线程(接收循环阻塞在带外 recv 上)。按 tokio 默认的 512 个阻塞线程计,宿主进程在新增回调工作排队前大约可支撑 256 个全双工会话 —— 请据此为长期连接的规模设限。

8. 错误 ​

每个 FFI 调用都经 FfiError 面结算 —— adapter 存在之前的拨号失败、invoke 路径的 SpokeResult 拒绝,以及回调 transport 自身的失败:

FfiError.Dial —— 构造函数 / 拨号失败 ​

{ kind, message },在拨号于 adapter 存在之前失败时返回:

kind何时
config本地种子或远端公钥不是恰好 32 字节、本地 manifest JSON 无效,或远端 peer_id 不在 allowlist 上(fail-closed)
handshake握手签名失败、nonce 单次使用违规、拨号绑定断言或 ConnectSession 快照校验失败(版本不匹配以 protocol_version_mismatch 呈现)
protocol_version_mismatchhello 通告混合或未知的 protocol_version —— 版本门禁是握手校验第 1 步,先于签名校验,因此任何版本不匹配的 hello(无论签名是否有效)都以该种类失败
timeout拨号截止时间已过(有界等待握手)

FfiError.Rejected —— invoke 路径的 SpokeResult 拒绝 ​

{ code, message, kind, wire_code },由已建立面上的 port 方法返回:

行形状
应用拒绝code / message 原样保留(例如 KNOWLEDGE_ENTRY_NOT_FOUND),kind / wire_code 仅在映射定义的场景存在
载荷 JSON 解析失败INVALID_INPUT,无 kind / wire_code
INTERNAL_ERROR 行kind ∈ {transport、session_closed、timeout、panic、correlation_mismatch、sequence_exhausted、envelope_auth_missing、envelope_auth_invalid、envelope_auth_session_unbound}
分派拒绝CAPABILITY_PORT_MISSING,wire_code = op_unsupported / capability_missing
未知线上码INVALID_INPUT,带 wire_code
路由器终结拒绝CAPABILITY_PORT_MISSING,wire_code = kind = no_capable_peer

kind = "panic" 是 panic 遏制行:导出 block-on-async 调用周围捕获的 panic 只让该等待者失败,绝不会跨 FFI 边界展开 —— message 携带原始 panic 载荷。spawn_blocking 池内的带外回调 panic 以传输 Io 失败呈现。

TransportError —— 回调 transport 失败 ​

变体何时
Closed连接关闭;挂起的 recv 快速失败
Io传输级 I/O 失败,包括带外回调 panic 使阻塞 join 失败

各绑定符号对照表 ​

面C#GoKotlinPythonSwift
拨号并构造ConnectRemoteAdapterFfi(...)ConnectRemoteAdapterFfi(...)connectRemoteAdapterFfi(...)connect_remote_adapter_ffi(...)connectRemoteAdapterFfi(...)
Adapter 对象RemoteAdapterFfiRemoteAdapterFfiRemoteAdapterFfiRemoteAdapterFfiRemoteAdapterFfi
路由器构造NewMultiPeerRouterFfi()NewMultiPeerRouterFfi()newMultiPeerRouterFfi()new_multi_peer_router_ffi()newMultiPeerRouterFfi()
路由器对象MultiPeerRouterFfiMultiPeerRouterFfiMultiPeerRouterFfiMultiPeerRouterFfiMultiPeerRouterFfi
port 方法PascalCase(GetKnowledgeEntry)PascalCase(GetKnowledgeEntry)camelCase(getKnowledgeEntry)snake_case(get_knowledge_entry)camelCase(getKnowledgeEntry)
可选 port 方法Project / Compute / ListForkTimelineEventsProject / Compute / ListForkTimelineEventsproject / compute / listForkTimelineEventsproject / compute / list_fork_timeline_eventsproject / compute / listForkTimelineEvents
远程抽取(ke-extraction)Extract(extractRequestJson)Extract(extractRequestJson)extract(extractRequestJson)extract(extract_request_json)extract(extractRequestJson:)
归属作用域查询(ke-ownership)ListKnowledgeEntries(scopeJson)ListKnowledgeEntries(scopeJson)listKnowledgeEntries(scopeJson)list_knowledge_entries(scope_json)listKnowledgeEntries(scopeJson:)
工具调用InvokeTool(...)InvokeTool(...)invokeTool(...)invoke_tool(...)invokeTool(capabilityId:argumentsJson:)
工具服务注册RegisterToolHandler(...)RegisterToolHandler(...)registerToolHandler(...)register_tool_handler(...)registerToolHandler(capabilityId:handler:)
工具处理器回调ToolHandlerToolHandlerToolHandlerToolHandlerToolHandler
ports 回调PortsHandlerPortsHandlerPortsHandlerPortsHandlerPortsHandler
响应方构造SpokeConnectMethods.ConnectResponderFfi(...)NewConnectResponderFfi(...)connectResponderFfi(...)connect_responder_ffi(...)connectResponderFfi(...)
响应方对象ConnectResponderFfiConnectResponderFfiConnectResponderFfiConnectResponderFfiConnectResponderFfi

每个绑定都携带相同的面:golden-parity smoke 断言字节级一致的会话核心行为,回环 smoke 从各宿主侧驱动回调 Transport + RemoteAdapterFFI 流程。

下一步 ​