feat(runtime): IM 主人私聊补历史转录(与 Web 主会话同源:抽共享 transcript 包 + OwnerEnvFor 单入口)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled

This commit is contained in:
AskaEth committed 2026-10-05 11:36:18 +08:00
1 parent df8fc660c0
commit 9b903a89bc
14 files changed
+676 -181

No files matched your search

+51
View File
@@ -0,0 +1,51 @@
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
}