10 // imageUserMessage attaches pixels only to messages selected as new by a turn.
11 func imageUserMessage(store *ImageStore, text string) (any, error) {
12 names := imageFiles(text)
14 return modelUserMessage(text), nil
17 parts, err := imageParts(store, text, names)
19 return nil, fmt.Errorf("make image message: %w", err)
22 return modelUserParts(parts), nil
25 // imageParts is the shared wire format for image messages and zoom results.
26 // Tool results use this list directly as function_call_output.output.
27 func imageParts(store *ImageStore, text string, names []string) ([]ModelInputPart, error) {
28 parts := []ModelInputPart{{
33 for _, name := range names {
35 return nil, errors.New("image store is not available")
38 data, err := store.Read(name)
40 return nil, fmt.Errorf("read image part: %w", err)
43 mime := "image/" + strings.TrimPrefix(name[64:], ".")
44 if strings.HasSuffix(name, ".jpg") {
48 parts = append(parts, ModelInputPart{
50 ImageURL: "data:" + mime + ";base64," + base64.StdEncoding.EncodeToString(data),
57 // imageToolOutput leaves all results as text except zoom of one image message.
58 // Only the text goes to the log, never the data URLs.
59 func imageToolOutput(box *Toolbox, name, arguments, text string) (any, error) {
60 if name != "zoom" || strings.HasPrefix(text, "error:") {
64 var args ToolZoomArguments
66 err := toolDecodeArguments(arguments, &args)
68 return nil, fmt.Errorf("decode image zoom: %w", err)
71 if args.Name != nil || args.N != 1 {
75 message, err := box.chat.Message(args.ID)
77 return nil, fmt.Errorf("read image zoom: %w", err)
80 names := imageFiles(message.Text)
85 parts, err := imageParts(box.images, text, names)
87 return nil, fmt.Errorf("make image zoom: %w", err)