package eino import ( "encoding/json" "sync/atomic" "time" "github.com/sundynix/sundynix-shared/contract" ) // ExecSink 是执行可视化事件的回流出口(由 NATS bus 实现): // 把节点/阶段生命周期事件发到 sundynix.exec.,供"运行·观测"实时点亮轨迹。 type ExecSink interface { PublishExec(taskID string, data []byte) error CompleteExec(taskID string) error } // execTracer 为一个任务发结构化执行事件,自增 Seq 保序、span 自动计耗时。 type execTracer struct { sink ExecSink task string seq int32 } // tracer 为某任务建一个事件发射器(sink 为空时所有方法变空操作)。 func (o *Orchestrator) tracer(taskID string) *execTracer { return &execTracer{sink: o.exec, task: taskID} } func (e *execTracer) emit(node, kind, phase, label, detail string, ms int64) { if e == nil || e.sink == nil { return } ev := contract.ExecEvent{ Seq: int(atomic.AddInt32(&e.seq, 1)), TS: time.Now().UnixMilli(), Node: node, Kind: kind, Phase: phase, Label: label, Detail: detail, MS: ms, } if data, err := json.Marshal(&ev); err == nil { _ = e.sink.PublishExec(e.task, data) } } // info 发一条瞬时事件(无耗时)。 func (e *execTracer) info(node, kind, label, detail string) { e.emit(node, kind, "info", label, detail, 0) } // span 发 start,并返回一个结束函数:调用时按 err 发 end / error,附带耗时。 func (e *execTracer) span(node, kind, label string) func(detail string, err error) { e.emit(node, kind, "start", label, "", 0) t0 := time.Now() return func(detail string, err error) { ms := time.Since(t0).Milliseconds() if err != nil { e.emit(node, kind, "error", label, err.Error(), ms) return } e.emit(node, kind, "end", label, detail, ms) } } // done 关闭该任务的执行事件流(让 SSE 客户端收尾)。 func (e *execTracer) done() { if e == nil || e.sink == nil { return } _ = e.sink.CompleteExec(e.task) }