package clients import ( "math" "strconv" "strings" ) const ( openAIGPTImage2MaxEdge = 3840 openAIGPTImage2MinPixels = 655360 openAIGPTImage2MaxPixels = 8294400 openAIGPTImage2MaxRatio = 3 openAIGPTImage2Multiple = 16 ) type providerImageDimensions struct { width int height int } func normalizedProviderAspectRatio(value any) string { compact := strings.Map(func(r rune) rune { switch r { case ' ', '\t', '\r', '\n': return -1 default: return r } }, strings.TrimSpace(stringFromAny(value))) parts := strings.Split(compact, ":") if len(parts) != 2 { return "" } width, widthErr := strconv.ParseFloat(parts[0], 64) height, heightErr := strconv.ParseFloat(parts[1], 64) if widthErr != nil || heightErr != nil || width <= 0 || height <= 0 { return "" } return parts[0] + ":" + parts[1] } func normalizedProviderImageResolution(value any) string { text := strings.ToLower(strings.TrimSpace(stringFromAny(value))) if len(text) < 2 || text[len(text)-1] != 'k' { return "" } magnitude, err := strconv.Atoi(text[:len(text)-1]) if err != nil || magnitude <= 0 { return "" } return strconv.Itoa(magnitude) + "K" } func normalizeOfficialOpenAIImageSize(body map[string]any, originalBody map[string]any) { if originalBody == nil { originalBody = body } if width, height, ok := providerPixelDimensions(stringFromAny(originalBody["size"])); ok { body["size"] = strconv.Itoa(width) + "x" + strconv.Itoa(height) return } model := strings.ToLower(strings.TrimSpace(stringFromAny(body["model"]))) resolution := normalizedProviderImageResolution(firstNonEmptyString( originalBody["resolution"], originalBody["size"], body["resolution"], body["size"], )) if resolution != "" { width, height, ok := providerBodyPixelDimensions(body) if !ok { width, height, ok = providerResolutionDimensions( resolution, firstNonEmptyString(originalBody["aspect_ratio"], originalBody["aspectRatio"], originalBody["ratio"]), ) } if !ok { delete(body, "size") return } switch { case strings.HasPrefix(model, "gpt-image-2"): dimensions := normalizeOpenAIGPTImage2Dimensions(width, height) body["size"] = strconv.Itoa(dimensions.width) + "x" + strconv.Itoa(dimensions.height) case strings.HasPrefix(model, "gpt-image-"): body["size"] = officialOpenAILegacyImageSize(width, height) default: body["size"] = strconv.Itoa(width) + "x" + strconv.Itoa(height) } return } if !strings.HasPrefix(model, "gpt-image-") { return } size := strings.ToLower(strings.TrimSpace(stringFromAny(body["size"]))) if size == "auto" { body["size"] = "auto" return } if strings.HasPrefix(model, "gpt-image-2") { if width, height, ok := providerPixelDimensions(size); ok { body["size"] = strconv.Itoa(width) + "x" + strconv.Itoa(height) } return } switch size { case "1024x1024", "1536x1024", "1024x1536": body["size"] = size return } width, height, ok := requestedProviderDimensions(body) if !ok { body["size"] = "auto" return } body["size"] = officialOpenAILegacyImageSize(width, height) } func officialOpenAILegacyImageSize(width int, height int) string { switch { case width > height: return "1536x1024" case height > width: return "1024x1536" default: return "1024x1024" } } func providerBodyPixelDimensions(body map[string]any) (int, int, bool) { if width, height, ok := providerPixelDimensions(stringFromAny(body["size"])); ok { return width, height, true } width := intFromAny(body["width"]) height := intFromAny(body["height"]) return width, height, width > 0 && height > 0 } func providerResolutionDimensions(resolution string, aspectRatio any) (int, int, bool) { resolution = normalizedProviderImageResolution(resolution) if resolution == "" { return 0, 0, false } magnitude, err := strconv.Atoi(strings.TrimSuffix(resolution, "K")) if err != nil || magnitude <= 0 { return 0, 0, false } baseSide := magnitude * 1024 ratio := providerAspectRatioNumber(aspectRatio) width, height := baseSide, baseSide if ratio > 1 { height = max(1, int(math.Round(float64(baseSide)/ratio))) } else if ratio > 0 && ratio < 1 { width = max(1, int(math.Round(float64(baseSide)*ratio))) } return width, height, true } func requestedProviderDimensions(body map[string]any) (int, int, bool) { if width, height, ok := providerPixelDimensions(stringFromAny(body["size"])); ok { return width, height, true } width := intFromAny(body["width"]) height := intFromAny(body["height"]) if width > 0 && height > 0 { return width, height, true } ratio := providerAspectRatioNumber(firstNonEmptyString(body["aspect_ratio"], body["aspectRatio"], body["ratio"])) if ratio > 1 { return 2, 1, true } if ratio > 0 && ratio < 1 { return 1, 2, true } if ratio == 1 { return 1, 1, true } return 0, 0, false } func providerPixelDimensions(value string) (int, int, bool) { parts := strings.Split(strings.ToLower(strings.TrimSpace(value)), "x") if len(parts) != 2 { return 0, 0, false } width, widthErr := strconv.Atoi(strings.TrimSpace(parts[0])) height, heightErr := strconv.Atoi(strings.TrimSpace(parts[1])) if widthErr != nil || heightErr != nil || width <= 0 || height <= 0 { return 0, 0, false } return width, height, true } func providerAspectRatioNumber(value any) float64 { normalized := normalizedProviderAspectRatio(value) if normalized == "" { return 0 } parts := strings.Split(normalized, ":") width, _ := strconv.ParseFloat(parts[0], 64) height, _ := strconv.ParseFloat(parts[1], 64) return width / height } func normalizeOpenAIGPTImage2Dimensions(width int, height int) providerImageDimensions { if width <= 0 || height <= 0 { return providerImageDimensions{} } rawRatio := float64(width) / float64(height) ratio := rawRatio if ratio < 1 { ratio = 1 / ratio } ratio = min(ratio, float64(openAIGPTImage2MaxRatio)) landscape := rawRatio >= 1 longEdge := min(max(width, height), openAIGPTImage2MaxEdge) dimensions := buildOpenAIGPTImage2Dimensions(longEdge, ratio, landscape, false) for dimensions.width*dimensions.height > openAIGPTImage2MaxPixels { longEdge = max(openAIGPTImage2Multiple, max(dimensions.width, dimensions.height)-openAIGPTImage2Multiple) dimensions = buildOpenAIGPTImage2Dimensions(longEdge, ratio, landscape, false) } for dimensions.width*dimensions.height < openAIGPTImage2MinPixels && max(dimensions.width, dimensions.height) < openAIGPTImage2MaxEdge { longEdge = min(openAIGPTImage2MaxEdge, max(dimensions.width, dimensions.height)+openAIGPTImage2Multiple) dimensions = buildOpenAIGPTImage2Dimensions(longEdge, ratio, landscape, true) } return dimensions } func buildOpenAIGPTImage2Dimensions(longEdge int, ratio float64, landscape bool, roundUp bool) providerImageDimensions { normalizedLongEdge := roundOpenAIGPTImage2Multiple(float64(longEdge), roundUp) normalizedShortEdge := roundOpenAIGPTImage2Multiple(float64(normalizedLongEdge)/ratio, roundUp) if float64(normalizedLongEdge)/float64(normalizedShortEdge) > openAIGPTImage2MaxRatio { normalizedShortEdge += openAIGPTImage2Multiple } if landscape { return providerImageDimensions{width: normalizedLongEdge, height: normalizedShortEdge} } return providerImageDimensions{width: normalizedShortEdge, height: normalizedLongEdge} } func roundOpenAIGPTImage2Multiple(value float64, roundUp bool) int { rounded := math.Round(value / openAIGPTImage2Multiple) if roundUp { rounded = math.Ceil(value / openAIGPTImage2Multiple) } return max(openAIGPTImage2Multiple, int(rounded)*openAIGPTImage2Multiple) }