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.

410 lines (405 loc) 14.4 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { VERSION: () => VERSION, createLuma: () => createLuma, luma: () => luma }); module.exports = __toCommonJS(src_exports); // src/luma-provider.ts var import_provider2 = require("@ai-sdk/provider"); var import_provider_utils2 = require("@ai-sdk/provider-utils"); // src/luma-image-model.ts var import_provider = require("@ai-sdk/provider"); var import_provider_utils = require("@ai-sdk/provider-utils"); var import_v4 = require("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 = "v3"; 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, files, mask }) { var _a, _b, _c; const warnings = []; if (seed != null) { warnings.push({ type: "unsupported", feature: "seed", details: "This model does not support the `seed` option." }); } if (size != null) { warnings.push({ type: "unsupported", feature: "size", details: "This model does not support the `size` option. Use `aspectRatio` instead." }); } const lumaOptions = await (0, import_provider_utils.parseProviderOptions)({ provider: "luma", providerOptions, schema: lumaImageProviderOptionsSchema }); const { pollIntervalMillis, maxPollAttempts, referenceType, images: imageConfigs, ...providerRequestOptions } = lumaOptions != null ? lumaOptions : {}; const editingOptions = this.getEditingOptions( files, mask, referenceType != null ? referenceType : void 0, imageConfigs != null ? imageConfigs : [] ); const currentDate = (_c = (_b = (_a = this.config._internal) == null ? void 0 : _a.currentDate) == null ? void 0 : _b.call(_a)) != null ? _c : /* @__PURE__ */ new Date(); const fullHeaders = (0, import_provider_utils.combineHeaders)(this.config.headers(), headers); const { value: generationResponse, responseHeaders } = await (0, import_provider_utils.postJsonToApi)({ url: this.getLumaGenerationsUrl(), headers: fullHeaders, body: { prompt, ...aspectRatio ? { aspect_ratio: aspectRatio } : {}, model: this.modelId, ...editingOptions, ...providerRequestOptions }, abortSignal, fetch: this.config.fetch, failedResponseHandler: this.createLumaErrorHandler(), successfulResponseHandler: (0, import_provider_utils.createJsonResponseHandler)( lumaGenerationResponseSchema ) }); const imageUrl = await this.pollForImageUrl( generationResponse.id, fullHeaders, abortSignal, { pollIntervalMillis: pollIntervalMillis != null ? pollIntervalMillis : void 0, maxPollAttempts: maxPollAttempts != null ? maxPollAttempts : void 0 } ); const downloadedImage = await this.downloadImage(imageUrl, abortSignal); return { images: [downloadedImage], warnings, response: { modelId: this.modelId, timestamp: currentDate, headers: responseHeaders } }; } async pollForImageUrl(generationId, headers, abortSignal, pollSettings) { var _a, _b, _c; const url = this.getLumaGenerationsUrl(generationId); const maxPollAttempts = (_a = pollSettings == null ? void 0 : pollSettings.maxPollAttempts) != null ? _a : this.maxPollAttempts; const pollIntervalMillis = (_b = pollSettings == null ? void 0 : pollSettings.pollIntervalMillis) != null ? _b : this.pollIntervalMillis; for (let i = 0; i < maxPollAttempts; i++) { const { value: statusResponse } = await (0, import_provider_utils.getFromApi)({ url, headers, abortSignal, fetch: this.config.fetch, failedResponseHandler: this.createLumaErrorHandler(), successfulResponseHandler: (0, import_provider_utils.createJsonResponseHandler)( lumaGenerationResponseSchema ) }); switch (statusResponse.state) { case "completed": if (!((_c = statusResponse.assets) == null ? void 0 : _c.image)) { throw new import_provider.InvalidResponseDataError({ data: statusResponse, message: `Image generation completed but no image was found.` }); } return statusResponse.assets.image; case "failed": throw new import_provider.InvalidResponseDataError({ data: statusResponse, message: `Image generation failed.` }); } await (0, import_provider_utils.delay)(pollIntervalMillis); } throw new Error( `Image generation timed out after ${this.maxPollAttempts} attempts.` ); } createLumaErrorHandler() { return (0, import_provider_utils.createJsonErrorResponseHandler)({ errorSchema: lumaErrorSchema, errorToMessage: (error) => { var _a; return (_a = error.detail[0].msg) != null ? _a : "Unknown error"; } }); } getEditingOptions(files, mask, referenceType = "image", imageConfigs = []) { var _a, _b, _c, _d; const options = {}; if (mask != null) { throw new Error( "Luma AI does not support mask-based image editing. Use the prompt to describe the changes you want to make, along with `prompt.images` containing the source image URL." ); } if (files == null || files.length === 0) { return options; } for (const file of files) { if (file.type !== "url") { throw new Error( "Luma AI only supports URL-based images. Please provide image URLs using `prompt.images` with publicly accessible URLs. Base64 and Uint8Array data are not supported." ); } } const defaultWeights = { image: 0.85, style: 0.8, character: 1, // Not used, but defined for completeness modify_image: 1 }; switch (referenceType) { case "image": { if (files.length > 4) { throw new Error( `Luma AI image supports up to 4 reference images. You provided ${files.length} images.` ); } options.image = files.map((file, index) => { var _a2, _b2; return { url: file.url, weight: (_b2 = (_a2 = imageConfigs[index]) == null ? void 0 : _a2.weight) != null ? _b2 : defaultWeights.image }; }); break; } case "style": { options.style = files.map((file, index) => { var _a2, _b2; return { url: file.url, weight: (_b2 = (_a2 = imageConfigs[index]) == null ? void 0 : _a2.weight) != null ? _b2 : defaultWeights.style }; }); break; } case "character": { const identities = {}; for (let i = 0; i < files.length; i++) { const file = files[i]; const identityId = (_b = (_a = imageConfigs[i]) == null ? void 0 : _a.id) != null ? _b : "identity0"; if (!identities[identityId]) { identities[identityId] = []; } identities[identityId].push(file.url); } for (const [identityId, images] of Object.entries(identities)) { if (images.length > 4) { throw new Error( `Luma AI character supports up to 4 images per identity. Identity '${identityId}' has ${images.length} images.` ); } } options.character = Object.fromEntries( Object.entries(identities).map(([id, images]) => [id, { images }]) ); break; } case "modify_image": { if (files.length > 1) { throw new Error( `Luma AI modify_image only supports a single input image. You provided ${files.length} images.` ); } options.modify_image = { url: files[0].url, weight: (_d = (_c = imageConfigs[0]) == null ? void 0 : _c.weight) != null ? _d : defaultWeights.modify_image }; break; } } return options; } getLumaGenerationsUrl(generationId) { return `${this.config.baseURL}/dream-machine/v1/generations/${generationId != null ? generationId : "image"}`; } async downloadImage(url, abortSignal) { const { value: response } = await (0, import_provider_utils.getFromApi)({ url, // No specific headers should be needed for this request as it's a // generated image provided by Luma. abortSignal, failedResponseHandler: (0, import_provider_utils.createStatusCodeErrorResponseHandler)(), successfulResponseHandler: (0, import_provider_utils.createBinaryResponseHandler)(), fetch: this.config.fetch }); return response; } }; var lumaGenerationResponseSchema = (0, import_provider_utils.lazySchema)( () => (0, import_provider_utils.zodSchema)( import_v4.z.object({ id: import_v4.z.string(), state: import_v4.z.enum(["queued", "dreaming", "completed", "failed"]), failure_reason: import_v4.z.string().nullish(), assets: import_v4.z.object({ image: import_v4.z.string() // URL of the generated image }).nullish() }) ) ); var lumaErrorSchema = import_v4.z.object({ detail: import_v4.z.array( import_v4.z.object({ type: import_v4.z.string(), loc: import_v4.z.array(import_v4.z.string()), msg: import_v4.z.string(), input: import_v4.z.string(), ctx: import_v4.z.object({ expected: import_v4.z.string() }).nullish() }) ) }); var lumaImageProviderOptionsSchema = (0, import_provider_utils.lazySchema)( () => (0, import_provider_utils.zodSchema)( import_v4.z.object({ /** * The type of image reference to use when providing input images. * - `image`: Guide generation using reference images (up to 4). Default. * - `style`: Apply a specific style from reference image(s). * - `character`: Create consistent characters from reference images (up to 4). * - `modify_image`: Transform a single input image with prompt guidance. */ referenceType: import_v4.z.enum(["image", "style", "character", "modify_image"]).nullish(), /** * Per-image configuration array. Each entry corresponds to an image in `prompt.images`. * Allows setting individual weights for each reference image. */ images: import_v4.z.array( import_v4.z.object({ /** * The weight of this image's influence on the generation. * - For `image`: Higher weight = closer to reference (default: 0.85) * - For `style`: Higher weight = stronger style influence (default: 0.8) * - For `modify_image`: Higher weight = closer to input, lower = more creative (default: 1.0) */ weight: import_v4.z.number().min(0).max(1).nullish(), /** * The identity name for character references. * Used with `character` to specify which identity group the image belongs to. * Luma supports multiple identities (e.g., 'identity0', 'identity1') for generating * images with multiple consistent characters. * Default: 'identity0' */ id: import_v4.z.string().nullish() }) ).nullish(), /** * Override the polling interval in milliseconds (default 500). */ pollIntervalMillis: import_v4.z.number().nullish(), /** * Override the maximum number of polling attempts (default 120). */ maxPollAttempts: import_v4.z.number().nullish() }).passthrough() ) ); // src/version.ts var VERSION = true ? "2.0.8" : "0.0.0-test"; // src/luma-provider.ts var defaultBaseURL = "https://api.lumalabs.ai"; function createLuma(options = {}) { var _a; const baseURL = (0, import_provider_utils2.withoutTrailingSlash)((_a = options.baseURL) != null ? _a : defaultBaseURL); const getHeaders = () => (0, import_provider_utils2.withUserAgentSuffix)( { Authorization: `Bearer ${(0, import_provider_utils2.loadApiKey)({ apiKey: options.apiKey, environmentVariableName: "LUMA_API_KEY", description: "Luma" })}`, ...options.headers }, `ai-sdk/luma/${VERSION}` ); const createImageModel = (modelId) => new LumaImageModel(modelId, { provider: "luma.image", baseURL: baseURL != null ? baseURL : defaultBaseURL, headers: getHeaders, fetch: options.fetch }); const embeddingModel = (modelId) => { throw new import_provider2.NoSuchModelError({ modelId, modelType: "embeddingModel" }); }; return { specificationVersion: "v3", image: createImageModel, imageModel: createImageModel, languageModel: (modelId) => { throw new import_provider2.NoSuchModelError({ modelId, modelType: "languageModel" }); }, embeddingModel, textEmbeddingModel: embeddingModel }; } var luma = createLuma(); // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { VERSION, createLuma, luma }); //# sourceMappingURL=index.js.map