Roo-Code/src/api/providers/utils/image-generation.ts
2025-11-25 14:18:14 -05:00

313 lines
7 KiB
TypeScript

import { t } from "../../../i18n"
// Image generation types
interface ImageGenerationResponse {
choices?: Array<{
message?: {
content?: string
images?: Array<{
type?: string
image_url?: {
url?: string
}
}>
}
}>
error?: {
message?: string
type?: string
code?: string
}
}
interface ImagesApiResponse {
data?: Array<{
b64_json?: string
url?: string
}>
error?: {
message?: string
type?: string
code?: string
}
}
export interface ImageGenerationResult {
success: boolean
imageData?: string
imageFormat?: string
error?: string
}
interface ImageGenerationOptions {
baseURL: string
authToken: string
model: string
prompt: string
inputImage?: string
}
interface ImagesApiOptions {
baseURL: string
authToken: string
model: string
prompt: string
inputImage?: string
size?: string
quality?: string
outputFormat?: string
}
/**
* Shared image generation implementation for OpenRouter and Roo Code Cloud providers
*/
export async function generateImageWithProvider(options: ImageGenerationOptions): Promise<ImageGenerationResult> {
const { baseURL, authToken, model, prompt, inputImage } = options
try {
const response = await fetch(`${baseURL}/chat/completions`, {
method: "POST",
headers: {
Authorization: `Bearer ${authToken}`,
"Content-Type": "application/json",
"HTTP-Referer": "https://github.com/RooVetGit/Roo-Code",
"X-Title": "Roo Code",
},
body: JSON.stringify({
model,
messages: [
{
role: "user",
content: inputImage
? [
{
type: "text",
text: prompt,
},
{
type: "image_url",
image_url: {
url: inputImage,
},
},
]
: prompt,
},
],
modalities: ["image", "text"],
}),
})
if (!response.ok) {
const errorText = await response.text()
let errorMessage = t("tools:generateImage.failedWithStatus", {
status: response.status,
statusText: response.statusText,
})
try {
const errorJson = JSON.parse(errorText)
if (errorJson.error?.message) {
errorMessage = t("tools:generateImage.failedWithMessage", {
message: errorJson.error.message,
})
}
} catch {
// Use default error message
}
return {
success: false,
error: errorMessage,
}
}
const result: ImageGenerationResponse = await response.json()
if (result.error) {
return {
success: false,
error: t("tools:generateImage.failedWithMessage", {
message: result.error.message,
}),
}
}
// Extract the generated image from the response
const images = result.choices?.[0]?.message?.images
if (!images || images.length === 0) {
return {
success: false,
error: t("tools:generateImage.noImageGenerated"),
}
}
const imageData = images[0]?.image_url?.url
if (!imageData) {
return {
success: false,
error: t("tools:generateImage.invalidImageData"),
}
}
// Extract base64 data from data URL
const base64Match = imageData.match(/^data:image\/(png|jpeg|jpg);base64,(.+)$/)
if (!base64Match) {
return {
success: false,
error: t("tools:generateImage.invalidImageFormat"),
}
}
return {
success: true,
imageData: imageData,
imageFormat: base64Match[1],
}
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : t("tools:generateImage.unknownError"),
}
}
}
/**
* Generate an image using OpenAI's Images API (/v1/images/generations)
* Supports BFL models (Flux) with provider-specific options for image editing
*/
export async function generateImageWithImagesApi(options: ImagesApiOptions): Promise<ImageGenerationResult> {
const { baseURL, authToken, model, prompt, inputImage, outputFormat = "png" } = options
try {
const url = `${baseURL}/images/generations`
// Build the request body
// For BFL models, inputImage is passed via providerOptions.blackForestLabs.inputImage
const requestBody: Record<string, unknown> = {
model,
prompt,
n: 1,
}
// Add optional parameters
if (options.size) {
requestBody.size = options.size
}
if (options.quality) {
requestBody.quality = options.quality
}
// For BFL (Black Forest Labs) models like flux-pro-1.1, use providerOptions
if (model.startsWith("bfl/")) {
requestBody.providerOptions = {
blackForestLabs: {
outputFormat: outputFormat,
// inputImage: Base64 encoded image or URL of image to use as reference
...(inputImage && { inputImage }),
},
}
} else {
// For other models, use standard output_format parameter
requestBody.output_format = outputFormat
}
const fetchOptions: RequestInit = {
method: "POST",
headers: {
Authorization: `Bearer ${authToken}`,
"Content-Type": "application/json",
"HTTP-Referer": "https://github.com/RooVetGit/Roo-Code",
"X-Title": "Roo Code",
},
body: JSON.stringify(requestBody),
}
const response = await fetch(url, fetchOptions)
if (!response.ok) {
const errorText = await response.text()
let errorMessage = t("tools:generateImage.failedWithStatus", {
status: response.status,
statusText: response.statusText,
})
try {
const errorJson = JSON.parse(errorText)
if (errorJson.error?.message) {
errorMessage = t("tools:generateImage.failedWithMessage", {
message: errorJson.error.message,
})
}
} catch {
// Use default error message
}
return {
success: false,
error: errorMessage,
}
}
const result: ImagesApiResponse = await response.json()
if (result.error) {
return {
success: false,
error: t("tools:generateImage.failedWithMessage", {
message: result.error.message,
}),
}
}
// Extract the generated image from the response
const images = result.data
if (!images || images.length === 0) {
return {
success: false,
error: t("tools:generateImage.noImageGenerated"),
}
}
const imageItem = images[0]
// Handle b64_json response (most common)
if (imageItem?.b64_json) {
// Convert base64 to data URL
const dataUrl = `data:image/${outputFormat};base64,${imageItem.b64_json}`
return {
success: true,
imageData: dataUrl,
imageFormat: outputFormat,
}
}
// Handle URL response (fallback)
if (imageItem?.url) {
// If it's already a data URL, use it directly
if (imageItem.url.startsWith("data:image/")) {
const formatMatch = imageItem.url.match(/^data:image\/(\w+);/)
const format = formatMatch?.[1] || outputFormat
return {
success: true,
imageData: imageItem.url,
imageFormat: format,
}
}
// For external URLs, return as-is (the caller will need to handle fetching)
return {
success: true,
imageData: imageItem.url,
imageFormat: outputFormat,
}
}
return {
success: false,
error: t("tools:generateImage.invalidImageData"),
}
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : t("tools:generateImage.unknownError"),
}
}
}