package transcript import ( "git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/history" "git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/model" ) // DocumentPathPrefix 是文档读取的版本化路径前缀。转录还原出的文档部件必须与网关读侧 // (`GET /api/v1/documents/{name}`)指向同一个 URL,否则续接的回合会把模型指到一个取不到的 // 地址。它是这个契约字符串的**唯一字面量**:网关的 `documentFilePrefix` 直接取自这里。 const DocumentPathPrefix = "/api/v1/documents/" // documentParts 把已存 document 列里的**全部**引用还原为该回合的文档部件(design/24 §9.5)。 // 部件只交给 RoleUser 回合(§2.3-③):文档在任何别的角色上都会让编码器 fail-loud,而一行 // 坏数据不该把整个会话卡死。 // // 列里是引用数组(一个回合最多 5 个附件),因此这里逐条还原、保持部件顺序 —— 少还原一条, // 那个附件就会在发起它的回合之后的所有回合里消失,与「清单恒在」的承诺自相矛盾。 // // 列被写坏(或附件落在非用户行上)时**不静默**:打一条带来源的 WARN 并省掉该部件,回合照常 // 继续 —— 附件凭空消失正是这条清单要修的缺陷,因此必须留下痕迹。日志只带消息 id 与错误, // 绝不带正文。 func documentParts(role model.Role, h history.Message) []model.Content { if h.Document == "" { return nil } if role != model.RoleUser { transcriptLog().Warn("transcript document on non-user row", "message", h.ID, "role", h.Role) return nil } refs, err := history.DecodeDocuments(h.Document) if err != nil { transcriptLog().Warn("transcript document decode", "message", h.ID, "err", err) return nil } parts := make([]model.Content, 0, len(refs)) for _, ref := range refs { if ref.Name == "" { transcriptLog().Warn("transcript document without name", "message", h.ID) continue } doc := model.Document{ Name: ref.Name, MIME: ref.MIME, URL: DocumentPathPrefix + ref.Name, Size: ref.Size, } parts = append(parts, model.DocumentMessage(model.RoleUser, "", doc).Content...) } return parts }