package loop import ( "context" "git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/model" ) // ImageResolver is the loop's seam to the multimodal vision layer (design/06 // §0). It is called once at the start of Run for every message that carries an // image content part, and may rewrite that message so the main model can reason // over the image. The concrete resolver lives in the vision package and decides, // via the vision Router, whether to keep the image for a vision-capable main // model or fold it into a text description produced by the vision/OCR tool ("先 // 看图再对话"). Nil leaves the image content untouched so it flows straight to a // vision-capable main model. type ImageResolver interface { // Resolve rewrites msg, which carries at least one image content part. It // returns the (possibly rewritten) message. An error leaves the original // message in place so the turn still proceeds. Resolve(ctx context.Context, msg model.Message) (model.Message, error) } // ImageResolveFunc adapts a function to the ImageResolver interface. type ImageResolveFunc func(ctx context.Context, msg model.Message) (model.Message, error) // Resolve implements ImageResolver. func (f ImageResolveFunc) Resolve(ctx context.Context, msg model.Message) (model.Message, error) { return f(ctx, msg) } // resolveImages rewrites every image-bearing message in messages using the // injected resolver. It returns the input slice unchanged when no resolver is // configured, no image is present, or a message fails to resolve (the failure is // logged and the original message is kept so the turn still runs). A fresh slice // is returned when any message changed. func (l *Loop) resolveImages(ctx context.Context, messages []model.Message) []model.Message { if l.imageResolver == nil { return messages } out := make([]model.Message, len(messages)) changed := false for i, m := range messages { if !messageHasImage(m) { out[i] = m continue } resolved, err := l.imageResolver.Resolve(ctx, m) if err != nil { l.log.Warn("image resolve", "error", err) out[i] = m continue } out[i] = resolved changed = true } if !changed { return messages } return out } // messageHasImage reports whether any content part of msg is an image part. func messageHasImage(m model.Message) bool { for _, part := range m.Content { if part.Type == model.ContentTypeImage { return true } } return false }