@ai-sdk/deepinfra
Version:
The **[DeepInfra provider](https://ai-sdk.dev/providers/ai-sdk-providers/deepinfra)** for the [AI SDK](https://ai-sdk.dev/docs) contains language model support for the DeepInfra API, giving you access to models like Llama 3, Mixtral, and other state-of-th
195 lines (176 loc) • 5.55 kB
text/typescript
import {
ImageModelV3,
ImageModelV3File,
SharedV3Warning,
} from '@ai-sdk/provider';
import {
combineHeaders,
convertBase64ToUint8Array,
convertToFormData,
createJsonErrorResponseHandler,
createJsonResponseHandler,
downloadBlob,
FetchFunction,
postFormDataToApi,
postJsonToApi,
} from '@ai-sdk/provider-utils';
import { DeepInfraImageModelId } from './deepinfra-image-settings';
import { z } from 'zod/v4';
interface DeepInfraImageModelConfig {
provider: string;
baseURL: string;
headers: () => Record<string, string>;
fetch?: FetchFunction;
_internal?: {
currentDate?: () => Date;
};
}
export class DeepInfraImageModel implements ImageModelV3 {
readonly specificationVersion = 'v3';
readonly maxImagesPerCall = 1;
get provider(): string {
return this.config.provider;
}
constructor(
readonly modelId: DeepInfraImageModelId,
private config: DeepInfraImageModelConfig,
) {}
async doGenerate({
prompt,
n,
size,
aspectRatio,
seed,
providerOptions,
headers,
abortSignal,
files,
mask,
}: Parameters<ImageModelV3['doGenerate']>[0]): Promise<
Awaited<ReturnType<ImageModelV3['doGenerate']>>
> {
const warnings: Array<SharedV3Warning> = [];
const currentDate = this.config._internal?.currentDate?.() ?? new Date();
// Image editing mode - use OpenAI-compatible /images/edits endpoint
if (files != null && files.length > 0) {
const { value: response, responseHeaders } = await postFormDataToApi({
url: this.getEditUrl(),
headers: combineHeaders(this.config.headers(), headers),
formData: convertToFormData<DeepInfraFormDataInput>(
{
model: this.modelId,
prompt,
image: await Promise.all(files.map(file => fileToBlob(file))),
mask: mask != null ? await fileToBlob(mask) : undefined,
n,
size,
...(providerOptions.deepinfra ?? {}),
},
{ useArrayBrackets: false },
),
failedResponseHandler: createJsonErrorResponseHandler({
errorSchema: deepInfraEditErrorSchema,
errorToMessage: error => error.error?.message ?? 'Unknown error',
}),
successfulResponseHandler: createJsonResponseHandler(
deepInfraEditResponseSchema,
),
abortSignal,
fetch: this.config.fetch,
});
return {
images: response.data.map(item => item.b64_json),
warnings,
response: {
timestamp: currentDate,
modelId: this.modelId,
headers: responseHeaders,
},
};
}
// Standard image generation mode
// Some deepinfra models support size while others support aspect ratio.
// Allow passing either and leave it up to the server to validate.
const splitSize = size?.split('x');
const { value: response, responseHeaders } = await postJsonToApi({
url: `${this.config.baseURL}/${this.modelId}`,
headers: combineHeaders(this.config.headers(), headers),
body: {
prompt,
num_images: n,
...(aspectRatio && { aspect_ratio: aspectRatio }),
...(splitSize && { width: splitSize[0], height: splitSize[1] }),
...(seed != null && { seed }),
...(providerOptions.deepinfra ?? {}),
},
failedResponseHandler: createJsonErrorResponseHandler({
errorSchema: deepInfraErrorSchema,
errorToMessage: error => error.detail.error,
}),
successfulResponseHandler: createJsonResponseHandler(
deepInfraImageResponseSchema,
),
abortSignal,
fetch: this.config.fetch,
});
return {
images: response.images.map(image =>
image.replace(/^data:image\/\w+;base64,/, ''),
),
warnings,
response: {
timestamp: currentDate,
modelId: this.modelId,
headers: responseHeaders,
},
};
}
private getEditUrl(): string {
// Use OpenAI-compatible endpoint for image editing
// baseURL is typically https://api.deepinfra.com/v1/inference
// We need to use https://api.deepinfra.com/v1/openai/images/edits
const baseUrl = this.config.baseURL.replace('/inference', '/openai');
return `${baseUrl}/images/edits`;
}
}
export const deepInfraErrorSchema = z.object({
detail: z.object({
error: z.string(),
}),
});
// limited version of the schema, focussed on what is needed for the implementation
// this approach limits breakages when the API changes and increases efficiency
export const deepInfraImageResponseSchema = z.object({
images: z.array(z.string()),
});
// Schema for OpenAI-compatible image edit endpoint errors
export const deepInfraEditErrorSchema = z.object({
error: z
.object({
message: z.string(),
})
.optional(),
});
// Schema for OpenAI-compatible image edit endpoint response
export const deepInfraEditResponseSchema = z.object({
data: z.array(z.object({ b64_json: z.string() })),
});
type DeepInfraFormDataInput = {
model: string;
prompt: string | undefined;
image: Blob | Blob[];
mask?: Blob;
n: number;
size: `${number}x${number}` | undefined;
[key: string]: unknown;
};
async function fileToBlob(file: ImageModelV3File): Promise<Blob> {
if (file.type === 'url') {
return downloadBlob(file.url);
}
const data =
file.data instanceof Uint8Array
? file.data
: convertBase64ToUint8Array(file.data);
return new Blob([data as BlobPart], { type: file.mediaType });
}