package agent import ( "encoding/json" "errors" "fmt" "time" "git.ipao.vip/rogee/go-sip/internal/contract" ) // EventWriter keeps Agent-produced realtime facts in the approved event // vocabulary before they are handed to Dispatcher/MQ. Transcript text is // written to the Agent spool as an archive as well as returned for realtime // publication; the archive is not a substitute for transcript.updated. type EventWriter struct { DispatcherID string TenantID string TenantKey string TraceID string } func (w EventWriter) TranscriptUpdated(now time.Time, eventID, callID, turnID, segmentID, role, text string, revision int64, final bool, startMS, endMS int64) ([]byte, error) { if eventID == "" || callID == "" || turnID == "" || segmentID == "" || role == "" { return nil, errors.New("transcript event identity is required") } if revision < 1 || startMS < 0 || endMS < startMS { return nil, errors.New("transcript timing or revision is invalid") } return (contract.EventBuilder{ DispatcherID: w.DispatcherID, TenantID: w.TenantID, TenantKey: w.TenantKey, TraceID: w.TraceID, EventType: "transcript.updated", Aggregate: "transcript_segment", AggregateID: segmentID, Version: revision, Payload: map[string]any{ "call_id": callID, "turn_id": turnID, "segment_id": segmentID, "role": role, "revision": revision, "text": text, "is_final": final, "start_ms": startMS, "end_ms": endMS, "playback_state": "not_applicable", }, }).Marshal(now, eventID) } func (s *Spool) AppendApprovedEvent(executionID string, event []byte) error { var envelope struct { EventType string `json:"event_type"` } if err := json.Unmarshal(event, &envelope); err != nil { return fmt.Errorf("decode event envelope: %w", err) } if envelope.EventType != "transcript.updated" { return fmt.Errorf("Agent transcript archive accepts transcript.updated only, got %q", envelope.EventType) } if err := contract.ValidateEvent(event); err != nil { return err } return s.AppendTranscript(executionID, event) }