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:
@ -338,10 +338,39 @@ func (p *Plugin) invokeOutput(channel string, args map[string]interface{}) (inte
|
||||
if p.proc == nil {
|
||||
return nil, ErrProcessExited
|
||||
}
|
||||
raw, err := p.proc.Call(MethodOutputInvoke, OutputInvokeParams{
|
||||
Channel: channel,
|
||||
Args: args,
|
||||
})
|
||||
|
||||
argJSON, err := json.Marshal(args)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("proc: %s 输出通道 %s 参数序列化失败: %w", p.name, channel, err)
|
||||
}
|
||||
|
||||
// 与工具调用同一个 funccall 帧模型(§13.6):payload 全在共享内存,
|
||||
// RPC 只传偏移描述符。输出 payload 在真机上可能含图片/文件描述等大字段,
|
||||
// 内联会撑爆 stdin/stdout 管道。
|
||||
//
|
||||
// 帧尾不预留结果区:output 的应答很小("ok" 或 status map),直接走
|
||||
// RPC 应答字段即可,不像 tool.invoke 那样需要几十 KB 的结果预算。
|
||||
var params OutputInvokeParams
|
||||
params.Channel = channel
|
||||
if len(argJSON) > 0 {
|
||||
arena := p.host.Arena()
|
||||
gen := p.host.Generation()
|
||||
frame, err := arena.Alloc(OwnerHost, len(argJSON), gen)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("proc: %s 输出通道 %s 分配调用帧失败: %w", p.name, channel, err)
|
||||
}
|
||||
defer func() { _ = arena.Free(OwnerHost, frame) }()
|
||||
|
||||
area, err := arena.Read(frame, gen)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("proc: %s 输出通道 %s 读取调用帧失败: %w", p.name, channel, err)
|
||||
}
|
||||
copy(area[:len(argJSON)], argJSON)
|
||||
params.Frame = frame
|
||||
params.ArgsLen = uint32(len(argJSON))
|
||||
}
|
||||
|
||||
raw, err := p.proc.Call(MethodOutputInvoke, params)
|
||||
if err != nil {
|
||||
return nil, err // 真实失败上报,模型可感知并重试
|
||||
}
|
||||
@ -349,6 +378,27 @@ func (p *Plugin) invokeOutput(channel string, args map[string]interface{}) (inte
|
||||
return map[string]interface{}{"status": "ok"}, nil
|
||||
}
|
||||
|
||||
// 结果可能在共享内存里(插件把大结果写回帧结果区)。
|
||||
// 先按结构化应答解析;ResultRef 为零则回退到内联字段。
|
||||
var outRes OutputInvokeResult
|
||||
if jerr := json.Unmarshal(raw, &outRes); jerr == nil && !outRes.ResultRef.IsZero() {
|
||||
arena := p.host.Arena()
|
||||
gen := p.host.Generation()
|
||||
// 插件申请了扩容块:内核负责归还(与 invokeTool 一致)。
|
||||
if outRes.ResultRef.Flags&sharedRefFlagExpand != 0 {
|
||||
defer func() { _ = arena.Free(OwnerHost, outRes.ResultRef) }()
|
||||
}
|
||||
data, err := arena.Read(outRes.ResultRef, gen)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("proc: %s 输出通道 %s 读取共享结果失败: %w", p.name, channel, err)
|
||||
}
|
||||
var out interface{}
|
||||
if err := json.Unmarshal(data, &out); err != nil {
|
||||
return nil, fmt.Errorf("proc: %s 输出通道 %s 解析共享结果失败: %w", p.name, channel, err)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
var res map[string]interface{}
|
||||
if err := json.Unmarshal(raw, &res); err == nil {
|
||||
if _, ok := res["status"]; !ok {
|
||||
|
||||
Reference in New Issue
Block a user