mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-22 18:08:04 +00:00
feat(shm): 输出通道 payload 走共享内存调用帧(§13.6)
§13.3 给 ToolInvokeParams 加了调用帧,但 OutputInvokeParams 没跟上—— payload 仍内联在 RPC JSON 里。核实后确认这个 lane 只做了半边:注入侧 (injectParams.TextRef + resolveText)可用,输出侧从内核到插件仍是内联。 改法照搬工具调用的 funccall 帧模型: - OutputInvokeParams 加 Frame/ArgsLen(Args 仅留给直连 RPC 的测试) - invokeOutput 序列化参数 → Alloc 帧 → 写帧 → 只传偏移描述符 → defer Free - 帧尾不预留结果区:output 应答很小("ok"/status map),走 RPC 应答字段即可。 但 OutputInvokeResult 支持插件把大结果写回帧(ResultRef),并识别 sharedRefFlagExpand 单独归还扩容块——与 invokeTool 一致。 - 模板 output.invoke 用 frameInput 从帧读参数,无帧才回退内联 Args 为什么要做:output payload 在真机上可能含图片/文件描述等大字段,内联会 撑爆 stdin/stdout 管道。 验证: - TestPlugin_OutputPayloadViaArena:9000 字节 payload 经帧完整送达(插件回报 实收长度,不是只看返回值)+ 调用后 arena 归零 - TestE2E_RealTemplateOutputPayloadViaFrame:同上,但用真实 SDK 模板编译的 插件——生产插件走的就是模板,模板不读帧则此改动等于没做 - TestPlugin_OutputChannelReportsRealFailure 仍绿:同步等真实结果、失败必须 上报(§9.4)的语义未被破坏 - go test -race ./internal/plugin/... 全绿 plan.md §13.5 也如实标注:注入 side 内核→插件方向仍未接线(procCore.InjectText 直接传字符串,从不 Alloc/Put 构造 TextRef),不是仅打勾的项。
This commit is contained in:
@ -290,6 +290,54 @@ func TestPlugin_RegisteredToolInvokable(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// §13.6:输出通道 payload 走共享内存调用帧(与 tool.invoke 同一模型),
|
||||
// 大 payload 不再爆 stdin/stdout 管道。
|
||||
func TestPlugin_OutputPayloadViaArena(t *testing.T) {
|
||||
bin := buildTestPlugin(t, "stageplugin.go")
|
||||
core := newFakeCore()
|
||||
|
||||
host, err := NewHost()
|
||||
if err != nil {
|
||||
t.Fatalf("NewHost: %v", err)
|
||||
}
|
||||
defer host.Close()
|
||||
|
||||
p := New("demo", bin, t.TempDir(), nil, host, nil)
|
||||
if err := p.Start(core); err != nil {
|
||||
t.Fatalf("Start: %v", err)
|
||||
}
|
||||
defer p.Close()
|
||||
|
||||
core.mu.Lock()
|
||||
h, ok := core.outputs["demo_ch"]
|
||||
core.mu.Unlock()
|
||||
if !ok {
|
||||
t.Fatal("插件应注册 demo_ch 输出通道")
|
||||
}
|
||||
|
||||
// 9000 字节,明显超过任何内联预算
|
||||
payload := strings.Repeat("输出", 3000)
|
||||
res, err := h(map[string]interface{}{"payload": payload, "type": "text"})
|
||||
if err != nil {
|
||||
t.Fatalf("发送应成功: %v", err)
|
||||
}
|
||||
m, _ := res.(map[string]interface{})
|
||||
// 插件回报它实际收到的长度:只有完整 payload 经帧送达才等于发送长度。
|
||||
var gotLen int
|
||||
switch v := m["payload_len"].(type) {
|
||||
case float64:
|
||||
gotLen = int(v)
|
||||
case int:
|
||||
gotLen = v
|
||||
}
|
||||
if gotLen != len(payload) {
|
||||
t.Fatalf("插件收到的 payload 长度 = %d,期望 %d(帧未把完整 payload 带到插件侧)", gotLen, len(payload))
|
||||
}
|
||||
if used, total := host.Arena().Stats(); used != 0 {
|
||||
t.Fatalf("调用结束后 arena 应归零,实际 used=%d/%d", used, total)
|
||||
}
|
||||
}
|
||||
|
||||
// 输出通道**同步等真实结果**:失败必须上报(§9.4 根治)。
|
||||
func TestPlugin_OutputChannelReportsRealFailure(t *testing.T) {
|
||||
bin := buildTestPlugin(t, "stageplugin.go")
|
||||
|
||||
Reference in New Issue
Block a user