@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
JavaScript
;
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