package loop // OutboundVoice 是一次 Run 的**出站语音结论**:模型在回合内调用 speak 工具 // 请求合成语音时,循环把这次请求的结局归一化到本结构,随 Result 交还调用方 // (网关据此决定这条回复是发音频还是发文本,持久化层据此记下「这条是语音」 // 与降级原因)。它是进程内类型,不直接序列化给客户端。 // // 三种状态互斥且由字段组合判定: // // - Result.Voice == nil —— 本回合**没有**请求语音,回复就是纯文本; // - Name != "" —— 合成成功,且音频副本**已经落盘**(Name 即服务端存储名, // Mime/Size 描述该副本); // - Requested && Name == "" && FallbackReason != "" —— 她**请求了**语音, // 但合成降级为文本(如 tts_timeout);FallbackReason 是机器可读的降级码, // 客户端可以据此特殊显示「这条本来想用语音」。 // // 语音是她的表达选择,不是身份或权限判定 —— 谁能用语音仍由渠道层 actor // 元数据 + 工具可达性标记决定,本结构只记录结局。 type OutboundVoice struct { // Requested 报告本回合是否发起过语音合成请求(成功与降级都为 true)。 Requested bool // Name 是落盘音频副本的服务端存储名;空表示没有可用音频(降级或未请求)。 Name string // Mime 是音频副本的媒体类型(如 audio/wav),随 Name 一起有意义。 Mime string // Size 是音频副本的字节数。 Size int64 // DurationMs 是音频时长(毫秒)。**不知道就是 0** —— 本项目绝不用比特率 // 或字数估算时长(与 voice.Copy.DurationMs 同一口径);0 表示「未解析」, // 不表示零长度音频。 DurationMs int64 // CostMs 是这次合成实际耗时(毫秒),用于对照预算看是否接近超时。 CostMs int // BudgetMs 是本次合成被允许的超时预算(毫秒),由文本 rune 数推导 // (见 speakBudgetMs);未请求时为 0。 BudgetMs int // FallbackReason 是降级原因码(如 tts_timeout),仅在「请求了但没有音频」 // 时非空;它让调用方与客户端能把「想用语音但没成」与「本来就用文本」区分开。 FallbackReason string }