package app import ( "encoding/json" "log/slog" "yingce/backend/internal/model" ) func cloudAgentCallSnapshotHash(call cloudAgentCall) string { var args struct { SnapshotHash string `json:"snapshotHash"` } if err := json.Unmarshal([]byte(call.Function.Arguments), &args); err != nil { return "" } return args.SnapshotHash } func cloudAgentCaptureStepSnapshotHash(calls []cloudAgentCall) string { for _, call := range calls { if hash := cloudAgentCallSnapshotHash(call); hash != "" { return hash } } return "" } func cloudAgentRewriteCallSnapshotHash(call cloudAgentCall, hash string) cloudAgentCall { args := map[string]any{} if err := json.Unmarshal([]byte(call.Function.Arguments), &args); err != nil { return call } args["snapshotHash"] = hash raw, err := json.Marshal(args) if err != nil { return call } call.Function.Arguments = string(raw) return call } // cloudAgentRefreshStepSnapshotHash 把同一轮里后续写操作的 snapshotHash 接到当前画布上。 // 只放宽同一轮内、且哈希仍等于本轮读时基线的调用;跨轮和模型自己换过哈希的调用仍走原校验。 // generate_media 漏传快照时补当前媒体内容哈希,避免写画布后还要再读一轮。 func (s *Service) cloudAgentRefreshStepSnapshotHash(run *model.CloudAgentExecution, state *cloudAgentRuntime, call cloudAgentCall) cloudAgentCall { if state == nil || state.CallIndex <= 0 || len(state.Calls) == 0 || !cloudAgentWrite(call.Function.Name) { return call } var current struct { SnapshotHash string `json:"snapshotHash"` } if err := json.Unmarshal([]byte(call.Function.Arguments), ¤t); err != nil { return call } canvas, err := s.repo.CanvasProjectForUser(run.UserID, state.Request.CanvasID) if err != nil { return call } doc, err := creationDocument(canvas.PayloadJSON) if err != nil { return call } if current.SnapshotHash == "" { if call.Function.Name != "generate_media" { return call } latest := cloudAgentMediaContentHash(doc) if latest == "" { return call } call = cloudAgentRewriteCallSnapshotHash(call, latest) slog.Debug("agent step hash filled", "run", run.ID, "step", state.Step, "index", state.CallIndex, "tool", call.Function.Name) return call } baseline := state.StepSnapshotHash if baseline == "" || current.SnapshotHash != baseline { return call } latest := cloudAgentCanvasHash(doc) if latest == "" || latest == current.SnapshotHash { return call } latestMutation, err := s.repo.LatestCloudAgentCanvasMutationForCanvas(run.UserID, state.Request.CanvasID) if err != nil || latestMutation.RunID != run.ID || latestMutation.AfterSnapshotHash != latest { return call } chain, err := s.repo.CloudAgentCanvasMutationChain(run.UserID, run.ID, state.Request.CanvasID) if err != nil { return call } stepIDs := map[string]bool{} for _, candidate := range state.Calls { if candidate.ID != "" { stepIDs[candidate.ID] = true } } expected := baseline advanced := false for _, mutation := range chain { if !stepIDs[mutation.StepID] { continue } if !advanced { if mutation.BeforeSnapshotHash != expected { continue } advanced = true } else if mutation.BeforeSnapshotHash != expected { return call } expected = mutation.AfterSnapshotHash if expected == latest { break } } if !advanced || expected != latest { return call } call = cloudAgentRewriteCallSnapshotHash(call, latest) slog.Debug("agent step hash refreshed", "run", run.ID, "step", state.Step, "index", state.CallIndex, "tool", call.Function.Name) return call }