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{{
32 for _, name := range names {
34 return nil, errors.New("image store is not available")
37 data, err := store.Read(name)
39 return nil, fmt.Errorf("read image part: %w", err)
42 mime := "image/" + strings.TrimPrefix(name[64:], ".")
43 if strings.HasSuffix(name, ".jpg") {
47 parts = append(parts, ModelInputPart{
49 ImageURL: "data:" + mime + ";base64," + base64.StdEncoding.EncodeToString(data),
56 // imageToolOutput leaves all results as text except zoom of one image message.
57 // Only the text goes to the log, never the data URLs.
58 func imageToolOutput(box *Toolbox, name, arguments, text string) (any, error) {
59 if name != "zoom" || strings.HasPrefix(text, "error:") {
63 var args ToolZoomArguments
65 err := toolDecodeArguments(arguments, &args)
67 return nil, fmt.Errorf("decode image zoom: %w", err)
70 if args.Name != nil || args.N != 1 {
74 message, err := box.chat.Message(args.ID)
76 return nil, fmt.Errorf("read image zoom: %w", err)
79 names := imageFiles(message.Text)
84 parts, err := imageParts(box.images, text, names)
86 return nil, fmt.Errorf("make image zoom: %w", err)