UNPKG

@ai-sdk/luma

Version:

The **Luma provider** for the [AI SDK](https://ai-sdk.dev/docs) contains support for Luma AI's state-of-the-art image generation models - Photon and Photon Flash.

225 lines (223 loc) 6.9 kB
// src/luma-provider.ts import { NoSuchModelError } from "@ai-sdk/provider"; import { loadApiKey, withoutTrailingSlash } from "@ai-sdk/provider-utils"; // src/luma-image-model.ts import { InvalidResponseDataError } from "@ai-sdk/provider"; import { combineHeaders, createBinaryResponseHandler, createJsonResponseHandler, createJsonErrorResponseHandler, createStatusCodeErrorResponseHandler, delay, getFromApi, postJsonToApi } from "@ai-sdk/provider-utils"; import { z } from "zod/v4"; var DEFAULT_POLL_INTERVAL_MILLIS = 500; var DEFAULT_MAX_POLL_ATTEMPTS = 6e4 / DEFAULT_POLL_INTERVAL_MILLIS; var LumaImageModel = class { constructor(modelId, config) { this.modelId = modelId; this.config = config; this.specificationVersion = "v2"; this.maxImagesPerCall = 1; this.pollIntervalMillis = DEFAULT_POLL_INTERVAL_MILLIS; this.maxPollAttempts = DEFAULT_MAX_POLL_ATTEMPTS; } get provider() { return this.config.provider; } async doGenerate({ prompt, n, size, aspectRatio, seed, providerOptions, headers, abortSignal }) { var _a, _b, _c, _d; const warnings = []; if (seed != null) { warnings.push({ type: "unsupported-setting", setting: "seed", details: "This model does not support the `seed` option." }); } if (size != null) { warnings.push({ type: "unsupported-setting", setting: "size", details: "This model does not support the `size` option. Use `aspectRatio` instead." }); } const { pollIntervalMillis, maxPollAttempts, ...providerRequestOptions } = (_a = providerOptions.luma) != null ? _a : {}; const currentDate = (_d = (_c = (_b = this.config._internal) == null ? void 0 : _b.currentDate) == null ? void 0 : _c.call(_b)) != null ? _d : /* @__PURE__ */ new Date(); const fullHeaders = combineHeaders(this.config.headers(), headers); const { value: generationResponse, responseHeaders } = await postJsonToApi({ url: this.getLumaGenerationsUrl(), headers: fullHeaders, body: { prompt, ...aspectRatio ? { aspect_ratio: aspectRatio } : {}, model: this.modelId, ...providerRequestOptions }, abortSignal, fetch: this.config.fetch, failedResponseHandler: this.createLumaErrorHandler(), successfulResponseHandler: createJsonResponseHandler( lumaGenerationResponseSchema ) }); const imageUrl = await this.pollForImageUrl( generationResponse.id, fullHeaders, abortSignal, providerOptions.luma ); const downloadedImage = await this.downloadImage(imageUrl, abortSignal); return { images: [downloadedImage], warnings, response: { modelId: this.modelId, timestamp: currentDate, headers: responseHeaders } }; } async pollForImageUrl(generationId, headers, abortSignal, imageSettings) { var _a, _b, _c; const url = this.getLumaGenerationsUrl(generationId); const maxPollAttempts = (_a = imageSettings == null ? void 0 : imageSettings.maxPollAttempts) != null ? _a : this.maxPollAttempts; const pollIntervalMillis = (_b = imageSettings == null ? void 0 : imageSettings.pollIntervalMillis) != null ? _b : this.pollIntervalMillis; for (let i = 0; i < maxPollAttempts; i++) { const { value: statusResponse } = await getFromApi({ url, headers, abortSignal, fetch: this.config.fetch, failedResponseHandler: this.createLumaErrorHandler(), successfulResponseHandler: createJsonResponseHandler( lumaGenerationResponseSchema ) }); switch (statusResponse.state) { case "completed": if (!((_c = statusResponse.assets) == null ? void 0 : _c.image)) { throw new InvalidResponseDataError({ data: statusResponse, message: `Image generation completed but no image was found.` }); } return statusResponse.assets.image; case "failed": throw new InvalidResponseDataError({ data: statusResponse, message: `Image generation failed.` }); } await delay(pollIntervalMillis); } throw new Error( `Image generation timed out after ${this.maxPollAttempts} attempts.` ); } createLumaErrorHandler() { return createJsonErrorResponseHandler({ errorSchema: lumaErrorSchema, errorToMessage: (error) => { var _a; return (_a = error.detail[0].msg) != null ? _a : "Unknown error"; } }); } getLumaGenerationsUrl(generationId) { return `${this.config.baseURL}/dream-machine/v1/generations/${generationId != null ? generationId : "image"}`; } async downloadImage(url, abortSignal) { const { value: response } = await getFromApi({ url, // No specific headers should be needed for this request as it's a // generated image provided by Luma. abortSignal, failedResponseHandler: createStatusCodeErrorResponseHandler(), successfulResponseHandler: createBinaryResponseHandler(), fetch: this.config.fetch }); return response; } }; var lumaGenerationResponseSchema = z.object({ id: z.string(), state: z.enum(["queued", "dreaming", "completed", "failed"]), failure_reason: z.string().nullish(), assets: z.object({ image: z.string() // URL of the generated image }).nullish() }); var lumaErrorSchema = z.object({ detail: z.array( z.object({ type: z.string(), loc: z.array(z.string()), msg: z.string(), input: z.string(), ctx: z.object({ expected: z.string() }).nullish() }) ) }); // src/luma-provider.ts var defaultBaseURL = "https://api.lumalabs.ai"; function createLuma(options = {}) { var _a; const baseURL = withoutTrailingSlash((_a = options.baseURL) != null ? _a : defaultBaseURL); const getHeaders = () => ({ Authorization: `Bearer ${loadApiKey({ apiKey: options.apiKey, environmentVariableName: "LUMA_API_KEY", description: "Luma" })}`, ...options.headers }); const createImageModel = (modelId) => new LumaImageModel(modelId, { provider: "luma.image", baseURL: baseURL != null ? baseURL : defaultBaseURL, headers: getHeaders, fetch: options.fetch }); return { image: createImageModel, imageModel: createImageModel, languageModel: () => { throw new NoSuchModelError({ modelId: "languageModel", modelType: "languageModel" }); }, textEmbeddingModel: () => { throw new NoSuchModelError({ modelId: "textEmbeddingModel", modelType: "textEmbeddingModel" }); } }; } var luma = createLuma(); export { createLuma, luma }; //# sourceMappingURL=index.mjs.map