package main import ( "bytes" "encoding/base64" "encoding/json" "errors" "io" "os" "strings" "git.ipao.vip/rogee/go-sip/internal/ai" ) const maxApprovedMockScenarioBytes = 4 << 20 type approvedMockScenario struct { InboundPCM16 []byte Script ai.ApprovedMockScript MaxWAVBytes int64 ExpectedRecording bool Outcome string ReasonMessage string } // loadApprovedMockScenario reads only explicit synthetic media facts. It does // not read or override the AI business configuration delivered by Dispatcher. func loadApprovedMockScenario(path string) (approvedMockScenario, error) { if strings.TrimSpace(path) == "" { return approvedMockScenario{}, errors.New("AGENT_MOCK_SCENARIO_FILE is required") } file, err := os.Open(path) if err != nil { return approvedMockScenario{}, errors.New("AGENT_MOCK_SCENARIO_FILE cannot be opened") } defer file.Close() data, err := io.ReadAll(io.LimitReader(file, maxApprovedMockScenarioBytes+1)) if err != nil || len(data) > maxApprovedMockScenarioBytes { return approvedMockScenario{}, errors.New("AGENT_MOCK_SCENARIO_FILE exceeds its size limit or cannot be read") } var encoded struct { InboundPCM16 *string `json:"inbound_pcm16"` Script *ai.ApprovedMockScript `json:"script"` MaxWAVBytes *int64 `json:"max_wav_bytes"` ExpectedRecording *bool `json:"expected_recording"` Outcome *string `json:"outcome"` ReasonMessage *string `json:"reason_message"` } decoder := json.NewDecoder(bytes.NewReader(data)) decoder.DisallowUnknownFields() if err := decoder.Decode(&encoded); err != nil { return approvedMockScenario{}, errors.New("AGENT_MOCK_SCENARIO_FILE has invalid fields") } if err := decoder.Decode(new(any)); err != io.EOF { return approvedMockScenario{}, errors.New("AGENT_MOCK_SCENARIO_FILE contains extra data") } if encoded.InboundPCM16 == nil || encoded.Script == nil || encoded.MaxWAVBytes == nil || encoded.ExpectedRecording == nil || encoded.Outcome == nil || encoded.ReasonMessage == nil { return approvedMockScenario{}, errors.New("AGENT_MOCK_SCENARIO_FILE requires explicit media, script, outcome and recording facts") } pcm, err := base64.StdEncoding.DecodeString(*encoded.InboundPCM16) if err != nil || len(encoded.Script.Turns) == 0 || *encoded.MaxWAVBytes <= 44 || strings.TrimSpace(*encoded.ReasonMessage) == "" || (*encoded.Outcome != "answered" && *encoded.Outcome != "no_answer" && *encoded.Outcome != "failed") { return approvedMockScenario{}, errors.New("AGENT_MOCK_SCENARIO_FILE has invalid synthetic media or outcome") } return approvedMockScenario{ InboundPCM16: pcm, Script: *encoded.Script, MaxWAVBytes: *encoded.MaxWAVBytes, ExpectedRecording: *encoded.ExpectedRecording, Outcome: *encoded.Outcome, ReasonMessage: *encoded.ReasonMessage, }, nil }