@ai-sdk/luma 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,9 @@
1
+ # @ai-sdk/luma
2
+
3
+ ## 0.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 39e5c1f: feat (provider/luma): add Luma provider
8
+ - Updated dependencies [39e5c1f]
9
+ - @ai-sdk/provider-utils@2.1.3
package/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2023 Vercel, Inc.
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
package/README.md ADDED
@@ -0,0 +1,50 @@
1
+ # AI SDK - Luma Provider
2
+
3
+ The **Luma provider** for the [AI SDK](https://sdk.vercel.ai/docs) contains support for Luma AI's state-of-the-art image generation models - Photon and Photon Flash.
4
+
5
+ ## About Luma Photon Models
6
+
7
+ Luma Photon and Photon Flash are groundbreaking image generation models that deliver:
8
+
9
+ - Ultra-high quality image generation
10
+ - 10x higher cost efficiency compared to similar models
11
+ - Superior prompt understanding and adherence
12
+ - Unique character consistency capabilities from single reference images
13
+ - Multi-image reference support for precise style matching
14
+
15
+ ## Setup
16
+
17
+ The Luma provider is available in the `@ai-sdk/luma` module. You can install it with:
18
+
19
+ ```bash
20
+ npm i @ai-sdk/luma
21
+ ```
22
+
23
+ ## Provider Instance
24
+
25
+ You can import the default provider instance `luma` from `@ai-sdk/luma`:
26
+
27
+ ```ts
28
+ import { luma } from '@ai-sdk/luma';
29
+ ```
30
+
31
+ ## Image Generation Example
32
+
33
+ ```ts
34
+ import { luma } from '@ai-sdk/luma';
35
+ import { experimental_generateImage as generateImage } from 'ai';
36
+ import fs from 'fs';
37
+
38
+ const { image } = await generateImage({
39
+ model: luma.image('photon'),
40
+ prompt: 'A serene mountain landscape at sunset',
41
+ });
42
+
43
+ const filename = `image-${Date.now()}.png`;
44
+ fs.writeFileSync(filename, image.uint8Array);
45
+ console.log(`Image saved to ${filename}`);
46
+ ```
47
+
48
+ ## Documentation
49
+
50
+ For more detailed information about the Luma models and their capabilities, please visit [Luma AI](https://lumalabs.ai/).
@@ -0,0 +1,114 @@
1
+ import { ImageModelV1 } from '@ai-sdk/provider';
2
+ import { FetchFunction } from '@ai-sdk/provider-utils';
3
+ import { z } from 'zod';
4
+
5
+ type LumaImageModelId = 'photon-1' | 'photon-flash-1' | (string & {});
6
+ /**
7
+ Configuration settings for Luma image generation.
8
+
9
+ Since the Luma API processes images through an asynchronous queue system, these
10
+ settings allow you to tune the polling behavior when waiting for image
11
+ generation to complete.
12
+ */
13
+ interface LumaImageSettings {
14
+ /**
15
+ Override the maximum number of images per call (default 1)
16
+ */
17
+ maxImagesPerCall?: number;
18
+ /**
19
+ Override the polling interval in milliseconds (default 500). This controls how
20
+ frequently the API is checked for completed images while they are being
21
+ processed in Luma's queue.
22
+ */
23
+ pollIntervalMillis?: number;
24
+ /**
25
+ Override the maximum number of polling attempts (default 120). Since image
26
+ generation is queued and processed asynchronously, this limits how long to wait
27
+ for results before timing out.
28
+ */
29
+ maxPollAttempts?: number;
30
+ }
31
+
32
+ interface LumaProviderSettings {
33
+ /**
34
+ Luma API key. Default value is taken from the `LUMA_API_KEY` environment
35
+ variable.
36
+ */
37
+ apiKey?: string;
38
+ /**
39
+ Base URL for the API calls.
40
+ */
41
+ baseURL?: string;
42
+ /**
43
+ Custom headers to include in the requests.
44
+ */
45
+ headers?: Record<string, string>;
46
+ /**
47
+ Custom fetch implementation. You can use it as a middleware to intercept requests,
48
+ or to provide a custom fetch implementation for e.g. testing.
49
+ */
50
+ fetch?: FetchFunction;
51
+ }
52
+ interface LumaProvider {
53
+ /**
54
+ Creates a model for image generation.
55
+ */
56
+ image(modelId: LumaImageModelId, settings?: LumaImageSettings): ImageModelV1;
57
+ }
58
+ declare function createLuma(options?: LumaProviderSettings): LumaProvider;
59
+ declare const luma: LumaProvider;
60
+
61
+ declare const lumaErrorSchema: z.ZodObject<{
62
+ detail: z.ZodArray<z.ZodObject<{
63
+ type: z.ZodString;
64
+ loc: z.ZodArray<z.ZodString, "many">;
65
+ msg: z.ZodString;
66
+ input: z.ZodString;
67
+ ctx: z.ZodOptional<z.ZodNullable<z.ZodObject<{
68
+ expected: z.ZodString;
69
+ }, "strip", z.ZodTypeAny, {
70
+ expected: string;
71
+ }, {
72
+ expected: string;
73
+ }>>>;
74
+ }, "strip", z.ZodTypeAny, {
75
+ type: string;
76
+ loc: string[];
77
+ msg: string;
78
+ input: string;
79
+ ctx?: {
80
+ expected: string;
81
+ } | null | undefined;
82
+ }, {
83
+ type: string;
84
+ loc: string[];
85
+ msg: string;
86
+ input: string;
87
+ ctx?: {
88
+ expected: string;
89
+ } | null | undefined;
90
+ }>, "many">;
91
+ }, "strip", z.ZodTypeAny, {
92
+ detail: {
93
+ type: string;
94
+ loc: string[];
95
+ msg: string;
96
+ input: string;
97
+ ctx?: {
98
+ expected: string;
99
+ } | null | undefined;
100
+ }[];
101
+ }, {
102
+ detail: {
103
+ type: string;
104
+ loc: string[];
105
+ msg: string;
106
+ input: string;
107
+ ctx?: {
108
+ expected: string;
109
+ } | null | undefined;
110
+ }[];
111
+ }>;
112
+ type LumaErrorData = z.infer<typeof lumaErrorSchema>;
113
+
114
+ export { type LumaErrorData, type LumaProvider, type LumaProviderSettings, createLuma, luma };
@@ -0,0 +1,114 @@
1
+ import { ImageModelV1 } from '@ai-sdk/provider';
2
+ import { FetchFunction } from '@ai-sdk/provider-utils';
3
+ import { z } from 'zod';
4
+
5
+ type LumaImageModelId = 'photon-1' | 'photon-flash-1' | (string & {});
6
+ /**
7
+ Configuration settings for Luma image generation.
8
+
9
+ Since the Luma API processes images through an asynchronous queue system, these
10
+ settings allow you to tune the polling behavior when waiting for image
11
+ generation to complete.
12
+ */
13
+ interface LumaImageSettings {
14
+ /**
15
+ Override the maximum number of images per call (default 1)
16
+ */
17
+ maxImagesPerCall?: number;
18
+ /**
19
+ Override the polling interval in milliseconds (default 500). This controls how
20
+ frequently the API is checked for completed images while they are being
21
+ processed in Luma's queue.
22
+ */
23
+ pollIntervalMillis?: number;
24
+ /**
25
+ Override the maximum number of polling attempts (default 120). Since image
26
+ generation is queued and processed asynchronously, this limits how long to wait
27
+ for results before timing out.
28
+ */
29
+ maxPollAttempts?: number;
30
+ }
31
+
32
+ interface LumaProviderSettings {
33
+ /**
34
+ Luma API key. Default value is taken from the `LUMA_API_KEY` environment
35
+ variable.
36
+ */
37
+ apiKey?: string;
38
+ /**
39
+ Base URL for the API calls.
40
+ */
41
+ baseURL?: string;
42
+ /**
43
+ Custom headers to include in the requests.
44
+ */
45
+ headers?: Record<string, string>;
46
+ /**
47
+ Custom fetch implementation. You can use it as a middleware to intercept requests,
48
+ or to provide a custom fetch implementation for e.g. testing.
49
+ */
50
+ fetch?: FetchFunction;
51
+ }
52
+ interface LumaProvider {
53
+ /**
54
+ Creates a model for image generation.
55
+ */
56
+ image(modelId: LumaImageModelId, settings?: LumaImageSettings): ImageModelV1;
57
+ }
58
+ declare function createLuma(options?: LumaProviderSettings): LumaProvider;
59
+ declare const luma: LumaProvider;
60
+
61
+ declare const lumaErrorSchema: z.ZodObject<{
62
+ detail: z.ZodArray<z.ZodObject<{
63
+ type: z.ZodString;
64
+ loc: z.ZodArray<z.ZodString, "many">;
65
+ msg: z.ZodString;
66
+ input: z.ZodString;
67
+ ctx: z.ZodOptional<z.ZodNullable<z.ZodObject<{
68
+ expected: z.ZodString;
69
+ }, "strip", z.ZodTypeAny, {
70
+ expected: string;
71
+ }, {
72
+ expected: string;
73
+ }>>>;
74
+ }, "strip", z.ZodTypeAny, {
75
+ type: string;
76
+ loc: string[];
77
+ msg: string;
78
+ input: string;
79
+ ctx?: {
80
+ expected: string;
81
+ } | null | undefined;
82
+ }, {
83
+ type: string;
84
+ loc: string[];
85
+ msg: string;
86
+ input: string;
87
+ ctx?: {
88
+ expected: string;
89
+ } | null | undefined;
90
+ }>, "many">;
91
+ }, "strip", z.ZodTypeAny, {
92
+ detail: {
93
+ type: string;
94
+ loc: string[];
95
+ msg: string;
96
+ input: string;
97
+ ctx?: {
98
+ expected: string;
99
+ } | null | undefined;
100
+ }[];
101
+ }, {
102
+ detail: {
103
+ type: string;
104
+ loc: string[];
105
+ msg: string;
106
+ input: string;
107
+ ctx?: {
108
+ expected: string;
109
+ } | null | undefined;
110
+ }[];
111
+ }>;
112
+ type LumaErrorData = z.infer<typeof lumaErrorSchema>;
113
+
114
+ export { type LumaErrorData, type LumaProvider, type LumaProviderSettings, createLuma, luma };
package/dist/index.js ADDED
@@ -0,0 +1,230 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var src_exports = {};
22
+ __export(src_exports, {
23
+ createLuma: () => createLuma,
24
+ luma: () => luma
25
+ });
26
+ module.exports = __toCommonJS(src_exports);
27
+
28
+ // src/luma-provider.ts
29
+ var import_provider_utils2 = require("@ai-sdk/provider-utils");
30
+
31
+ // src/luma-image-model.ts
32
+ var import_provider = require("@ai-sdk/provider");
33
+ var import_provider_utils = require("@ai-sdk/provider-utils");
34
+ var import_zod = require("zod");
35
+ var DEFAULT_POLL_INTERVAL_MILLIS = 500;
36
+ var DEFAULT_MAX_POLL_ATTEMPTS = 6e4 / DEFAULT_POLL_INTERVAL_MILLIS;
37
+ async function delay(delayInMs) {
38
+ return delayInMs == null ? Promise.resolve() : new Promise((resolve) => setTimeout(resolve, delayInMs));
39
+ }
40
+ var LumaImageModel = class {
41
+ constructor(modelId, settings, config) {
42
+ this.modelId = modelId;
43
+ this.settings = settings;
44
+ this.config = config;
45
+ this.specificationVersion = "v1";
46
+ var _a, _b;
47
+ this.pollIntervalMillis = (_a = settings.pollIntervalMillis) != null ? _a : DEFAULT_POLL_INTERVAL_MILLIS;
48
+ this.maxPollAttempts = (_b = settings.maxPollAttempts) != null ? _b : DEFAULT_MAX_POLL_ATTEMPTS;
49
+ }
50
+ get provider() {
51
+ return this.config.provider;
52
+ }
53
+ get maxImagesPerCall() {
54
+ var _a;
55
+ return (_a = this.settings.maxImagesPerCall) != null ? _a : 1;
56
+ }
57
+ async doGenerate({
58
+ prompt,
59
+ n,
60
+ size,
61
+ aspectRatio,
62
+ seed,
63
+ providerOptions,
64
+ headers,
65
+ abortSignal
66
+ }) {
67
+ var _a, _b, _c, _d;
68
+ const warnings = [];
69
+ if (seed != null) {
70
+ warnings.push({
71
+ type: "unsupported-setting",
72
+ setting: "seed",
73
+ details: "This model does not support the `seed` option."
74
+ });
75
+ }
76
+ if (size != null) {
77
+ warnings.push({
78
+ type: "unsupported-setting",
79
+ setting: "size",
80
+ details: "This model does not support the `size` option. Use `aspectRatio` instead."
81
+ });
82
+ }
83
+ const currentDate = (_c = (_b = (_a = this.config._internal) == null ? void 0 : _a.currentDate) == null ? void 0 : _b.call(_a)) != null ? _c : /* @__PURE__ */ new Date();
84
+ const fullHeaders = (0, import_provider_utils.combineHeaders)(this.config.headers(), headers);
85
+ const { value: generationResponse, responseHeaders } = await (0, import_provider_utils.postJsonToApi)({
86
+ url: this.getLumaGenerationsUrl(),
87
+ headers: fullHeaders,
88
+ body: {
89
+ prompt,
90
+ ...aspectRatio ? { aspect_ratio: aspectRatio } : {},
91
+ model: this.modelId,
92
+ ...(_d = providerOptions.luma) != null ? _d : {}
93
+ },
94
+ abortSignal,
95
+ fetch: this.config.fetch,
96
+ failedResponseHandler: this.createLumaErrorHandler(),
97
+ successfulResponseHandler: (0, import_provider_utils.createJsonResponseHandler)(
98
+ lumaGenerationResponseSchema
99
+ )
100
+ });
101
+ const imageUrl = await this.pollForImageUrl(
102
+ generationResponse.id,
103
+ fullHeaders,
104
+ abortSignal
105
+ );
106
+ const downloadedImage = await this.downloadImage(imageUrl, abortSignal);
107
+ return {
108
+ images: [downloadedImage],
109
+ warnings,
110
+ response: {
111
+ modelId: this.modelId,
112
+ timestamp: currentDate,
113
+ headers: responseHeaders
114
+ }
115
+ };
116
+ }
117
+ async pollForImageUrl(generationId, headers, abortSignal) {
118
+ var _a;
119
+ let attemptCount = 0;
120
+ const url = this.getLumaGenerationsUrl(generationId);
121
+ for (let i = 0; i < this.maxPollAttempts; i++) {
122
+ const { value: statusResponse } = await (0, import_provider_utils.getFromApi)({
123
+ url,
124
+ headers,
125
+ abortSignal,
126
+ fetch: this.config.fetch,
127
+ failedResponseHandler: this.createLumaErrorHandler(),
128
+ successfulResponseHandler: (0, import_provider_utils.createJsonResponseHandler)(
129
+ lumaGenerationResponseSchema
130
+ )
131
+ });
132
+ switch (statusResponse.state) {
133
+ case "completed":
134
+ if (!((_a = statusResponse.assets) == null ? void 0 : _a.image)) {
135
+ throw new import_provider.InvalidResponseDataError({
136
+ data: statusResponse,
137
+ message: `Image generation completed but no image was found.`
138
+ });
139
+ }
140
+ return statusResponse.assets.image;
141
+ case "failed":
142
+ throw new import_provider.InvalidResponseDataError({
143
+ data: statusResponse,
144
+ message: `Image generation failed.`
145
+ });
146
+ }
147
+ await delay(this.pollIntervalMillis);
148
+ }
149
+ throw new Error(
150
+ `Image generation timed out after ${this.maxPollAttempts} attempts.`
151
+ );
152
+ }
153
+ createLumaErrorHandler() {
154
+ return (0, import_provider_utils.createJsonErrorResponseHandler)({
155
+ errorSchema: lumaErrorSchema,
156
+ errorToMessage: (error) => {
157
+ var _a;
158
+ return (_a = error.detail[0].msg) != null ? _a : "Unknown error";
159
+ }
160
+ });
161
+ }
162
+ getLumaGenerationsUrl(generationId) {
163
+ return `${this.config.baseURL}/dream-machine/v1/generations/${generationId != null ? generationId : "image"}`;
164
+ }
165
+ async downloadImage(url, abortSignal) {
166
+ const { value: response } = await (0, import_provider_utils.getFromApi)({
167
+ url,
168
+ // No specific headers should be needed for this request as it's a
169
+ // generated image provided by Luma.
170
+ abortSignal,
171
+ failedResponseHandler: (0, import_provider_utils.createStatusCodeErrorResponseHandler)(),
172
+ successfulResponseHandler: (0, import_provider_utils.createBinaryResponseHandler)(),
173
+ fetch: this.config.fetch
174
+ });
175
+ return response;
176
+ }
177
+ };
178
+ var lumaGenerationResponseSchema = import_zod.z.object({
179
+ id: import_zod.z.string(),
180
+ state: import_zod.z.enum(["queued", "dreaming", "completed", "failed"]),
181
+ failure_reason: import_zod.z.string().nullish(),
182
+ assets: import_zod.z.object({
183
+ image: import_zod.z.string()
184
+ // URL of the generated image
185
+ }).nullish()
186
+ });
187
+ var lumaErrorSchema = import_zod.z.object({
188
+ detail: import_zod.z.array(
189
+ import_zod.z.object({
190
+ type: import_zod.z.string(),
191
+ loc: import_zod.z.array(import_zod.z.string()),
192
+ msg: import_zod.z.string(),
193
+ input: import_zod.z.string(),
194
+ ctx: import_zod.z.object({
195
+ expected: import_zod.z.string()
196
+ }).nullish()
197
+ })
198
+ )
199
+ });
200
+
201
+ // src/luma-provider.ts
202
+ var defaultBaseURL = "https://api.lumalabs.ai";
203
+ function createLuma(options = {}) {
204
+ var _a;
205
+ const baseURL = (0, import_provider_utils2.withoutTrailingSlash)((_a = options.baseURL) != null ? _a : defaultBaseURL);
206
+ const getHeaders = () => ({
207
+ Authorization: `Bearer ${(0, import_provider_utils2.loadApiKey)({
208
+ apiKey: options.apiKey,
209
+ environmentVariableName: "LUMA_API_KEY",
210
+ description: "Luma"
211
+ })}`,
212
+ ...options.headers
213
+ });
214
+ const createImageModel = (modelId, settings = {}) => new LumaImageModel(modelId, settings, {
215
+ provider: "luma.image",
216
+ baseURL: baseURL != null ? baseURL : defaultBaseURL,
217
+ headers: getHeaders,
218
+ fetch: options.fetch
219
+ });
220
+ const provider = (modelId, settings) => createImageModel(modelId, settings);
221
+ provider.image = createImageModel;
222
+ return provider;
223
+ }
224
+ var luma = createLuma();
225
+ // Annotate the CommonJS export names for ESM import in node:
226
+ 0 && (module.exports = {
227
+ createLuma,
228
+ luma
229
+ });
230
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/luma-provider.ts","../src/luma-image-model.ts"],"sourcesContent":["export { createLuma, luma } from './luma-provider';\nexport type { LumaProvider, LumaProviderSettings } from './luma-provider';\nexport type { LumaErrorData } from './luma-image-model';\n","import { ImageModelV1 } from '@ai-sdk/provider';\nimport {\n FetchFunction,\n loadApiKey,\n withoutTrailingSlash,\n} from '@ai-sdk/provider-utils';\nimport { LumaImageModel } from './luma-image-model';\nimport { LumaImageModelId, LumaImageSettings } from './luma-image-settings';\n\nexport interface LumaProviderSettings {\n /**\nLuma API key. Default value is taken from the `LUMA_API_KEY` environment\nvariable.\n */\n apiKey?: string;\n /**\nBase URL for the API calls.\n */\n baseURL?: string;\n /**\nCustom headers to include in the requests.\n */\n headers?: Record<string, string>;\n /**\nCustom fetch implementation. You can use it as a middleware to intercept requests,\nor to provide a custom fetch implementation for e.g. testing.\n */\n fetch?: FetchFunction;\n}\n\nexport interface LumaProvider {\n /**\nCreates a model for image generation.\n */\n image(modelId: LumaImageModelId, settings?: LumaImageSettings): ImageModelV1;\n}\n\nconst defaultBaseURL = 'https://api.lumalabs.ai';\n\nexport function createLuma(options: LumaProviderSettings = {}): LumaProvider {\n const baseURL = withoutTrailingSlash(options.baseURL ?? defaultBaseURL);\n const getHeaders = () => ({\n Authorization: `Bearer ${loadApiKey({\n apiKey: options.apiKey,\n environmentVariableName: 'LUMA_API_KEY',\n description: 'Luma',\n })}`,\n ...options.headers,\n });\n\n const createImageModel = (\n modelId: LumaImageModelId,\n settings: LumaImageSettings = {},\n ) =>\n new LumaImageModel(modelId, settings, {\n provider: 'luma.image',\n baseURL: baseURL ?? defaultBaseURL,\n headers: getHeaders,\n fetch: options.fetch,\n });\n\n const provider = (modelId: LumaImageModelId, settings?: LumaImageSettings) =>\n createImageModel(modelId, settings);\n\n provider.image = createImageModel;\n\n return provider as LumaProvider;\n}\n\nexport const luma = createLuma();\n","import {\n ImageModelV1,\n ImageModelV1CallWarning,\n InvalidResponseDataError,\n} from '@ai-sdk/provider';\nimport {\n FetchFunction,\n combineHeaders,\n createBinaryResponseHandler,\n createJsonResponseHandler,\n createJsonErrorResponseHandler,\n createStatusCodeErrorResponseHandler,\n getFromApi,\n postJsonToApi,\n} from '@ai-sdk/provider-utils';\nimport { LumaImageSettings } from './luma-image-settings';\nimport { z } from 'zod';\n\nconst DEFAULT_POLL_INTERVAL_MILLIS = 500;\nconst DEFAULT_MAX_POLL_ATTEMPTS = 60000 / DEFAULT_POLL_INTERVAL_MILLIS;\n\ninterface LumaImageModelConfig {\n provider: string;\n baseURL: string;\n headers: () => Record<string, string>;\n fetch?: FetchFunction;\n _internal?: {\n currentDate?: () => Date;\n };\n}\n\nasync function delay(delayInMs?: number | null): Promise<void> {\n return delayInMs == null\n ? Promise.resolve()\n : new Promise(resolve => setTimeout(resolve, delayInMs));\n}\n\nexport class LumaImageModel implements ImageModelV1 {\n readonly specificationVersion = 'v1';\n\n private readonly pollIntervalMillis: number;\n private readonly maxPollAttempts: number;\n\n get provider(): string {\n return this.config.provider;\n }\n\n get maxImagesPerCall(): number {\n return this.settings.maxImagesPerCall ?? 1;\n }\n\n constructor(\n readonly modelId: string,\n private readonly settings: LumaImageSettings,\n private readonly config: LumaImageModelConfig,\n ) {\n this.pollIntervalMillis =\n settings.pollIntervalMillis ?? DEFAULT_POLL_INTERVAL_MILLIS;\n this.maxPollAttempts =\n settings.maxPollAttempts ?? DEFAULT_MAX_POLL_ATTEMPTS;\n }\n\n async doGenerate({\n prompt,\n n,\n size,\n aspectRatio,\n seed,\n providerOptions,\n headers,\n abortSignal,\n }: Parameters<ImageModelV1['doGenerate']>[0]): Promise<\n Awaited<ReturnType<ImageModelV1['doGenerate']>>\n > {\n const warnings: Array<ImageModelV1CallWarning> = [];\n\n if (seed != null) {\n warnings.push({\n type: 'unsupported-setting',\n setting: 'seed',\n details: 'This model does not support the `seed` option.',\n });\n }\n\n if (size != null) {\n warnings.push({\n type: 'unsupported-setting',\n setting: 'size',\n details:\n 'This model does not support the `size` option. Use `aspectRatio` instead.',\n });\n }\n\n const currentDate = this.config._internal?.currentDate?.() ?? new Date();\n const fullHeaders = combineHeaders(this.config.headers(), headers);\n const { value: generationResponse, responseHeaders } = await postJsonToApi({\n url: this.getLumaGenerationsUrl(),\n headers: fullHeaders,\n body: {\n prompt,\n ...(aspectRatio ? { aspect_ratio: aspectRatio } : {}),\n model: this.modelId,\n ...(providerOptions.luma ?? {}),\n },\n abortSignal,\n fetch: this.config.fetch,\n failedResponseHandler: this.createLumaErrorHandler(),\n successfulResponseHandler: createJsonResponseHandler(\n lumaGenerationResponseSchema,\n ),\n });\n\n const imageUrl = await this.pollForImageUrl(\n generationResponse.id,\n fullHeaders,\n abortSignal,\n );\n\n const downloadedImage = await this.downloadImage(imageUrl, abortSignal);\n\n return {\n images: [downloadedImage],\n warnings,\n response: {\n modelId: this.modelId,\n timestamp: currentDate,\n headers: responseHeaders,\n },\n };\n }\n\n private async pollForImageUrl(\n generationId: string,\n headers: Record<string, string | undefined>,\n abortSignal: AbortSignal | undefined,\n ): Promise<string> {\n let attemptCount = 0;\n const url = this.getLumaGenerationsUrl(generationId);\n for (let i = 0; i < this.maxPollAttempts; i++) {\n const { value: statusResponse } = await getFromApi({\n url,\n headers,\n abortSignal,\n fetch: this.config.fetch,\n failedResponseHandler: this.createLumaErrorHandler(),\n successfulResponseHandler: createJsonResponseHandler(\n lumaGenerationResponseSchema,\n ),\n });\n\n switch (statusResponse.state) {\n case 'completed':\n if (!statusResponse.assets?.image) {\n throw new InvalidResponseDataError({\n data: statusResponse,\n message: `Image generation completed but no image was found.`,\n });\n }\n return statusResponse.assets.image;\n case 'failed':\n throw new InvalidResponseDataError({\n data: statusResponse,\n message: `Image generation failed.`,\n });\n }\n await delay(this.pollIntervalMillis);\n }\n\n throw new Error(\n `Image generation timed out after ${this.maxPollAttempts} attempts.`,\n );\n }\n\n private createLumaErrorHandler() {\n return createJsonErrorResponseHandler({\n errorSchema: lumaErrorSchema,\n errorToMessage: (error: LumaErrorData) =>\n error.detail[0].msg ?? 'Unknown error',\n });\n }\n\n private getLumaGenerationsUrl(generationId?: string) {\n return `${this.config.baseURL}/dream-machine/v1/generations/${\n generationId ?? 'image'\n }`;\n }\n\n private async downloadImage(\n url: string,\n abortSignal: AbortSignal | undefined,\n ): Promise<Uint8Array> {\n const { value: response } = await getFromApi({\n url,\n // No specific headers should be needed for this request as it's a\n // generated image provided by Luma.\n abortSignal,\n failedResponseHandler: createStatusCodeErrorResponseHandler(),\n successfulResponseHandler: createBinaryResponseHandler(),\n fetch: this.config.fetch,\n });\n return response;\n }\n}\n\n// limited version of the schema, focussed on what is needed for the implementation\n// this approach limits breakages when the API changes and increases efficiency\nconst lumaGenerationResponseSchema = z.object({\n id: z.string(),\n state: z.enum(['queued', 'dreaming', 'completed', 'failed']),\n failure_reason: z.string().nullish(),\n assets: z\n .object({\n image: z.string(), // URL of the generated image\n })\n .nullish(),\n});\n\nconst lumaErrorSchema = z.object({\n detail: z.array(\n z.object({\n type: z.string(),\n loc: z.array(z.string()),\n msg: z.string(),\n input: z.string(),\n ctx: z\n .object({\n expected: z.string(),\n })\n .nullish(),\n }),\n ),\n});\n\nexport type LumaErrorData = z.infer<typeof lumaErrorSchema>;\n"],"mappings":"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","names":["import_provider_utils"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,215 @@
1
+ // src/luma-provider.ts
2
+ import {
3
+ loadApiKey,
4
+ withoutTrailingSlash
5
+ } from "@ai-sdk/provider-utils";
6
+
7
+ // src/luma-image-model.ts
8
+ import {
9
+ InvalidResponseDataError
10
+ } from "@ai-sdk/provider";
11
+ import {
12
+ combineHeaders,
13
+ createBinaryResponseHandler,
14
+ createJsonResponseHandler,
15
+ createJsonErrorResponseHandler,
16
+ createStatusCodeErrorResponseHandler,
17
+ getFromApi,
18
+ postJsonToApi
19
+ } from "@ai-sdk/provider-utils";
20
+ import { z } from "zod";
21
+ var DEFAULT_POLL_INTERVAL_MILLIS = 500;
22
+ var DEFAULT_MAX_POLL_ATTEMPTS = 6e4 / DEFAULT_POLL_INTERVAL_MILLIS;
23
+ async function delay(delayInMs) {
24
+ return delayInMs == null ? Promise.resolve() : new Promise((resolve) => setTimeout(resolve, delayInMs));
25
+ }
26
+ var LumaImageModel = class {
27
+ constructor(modelId, settings, config) {
28
+ this.modelId = modelId;
29
+ this.settings = settings;
30
+ this.config = config;
31
+ this.specificationVersion = "v1";
32
+ var _a, _b;
33
+ this.pollIntervalMillis = (_a = settings.pollIntervalMillis) != null ? _a : DEFAULT_POLL_INTERVAL_MILLIS;
34
+ this.maxPollAttempts = (_b = settings.maxPollAttempts) != null ? _b : DEFAULT_MAX_POLL_ATTEMPTS;
35
+ }
36
+ get provider() {
37
+ return this.config.provider;
38
+ }
39
+ get maxImagesPerCall() {
40
+ var _a;
41
+ return (_a = this.settings.maxImagesPerCall) != null ? _a : 1;
42
+ }
43
+ async doGenerate({
44
+ prompt,
45
+ n,
46
+ size,
47
+ aspectRatio,
48
+ seed,
49
+ providerOptions,
50
+ headers,
51
+ abortSignal
52
+ }) {
53
+ var _a, _b, _c, _d;
54
+ const warnings = [];
55
+ if (seed != null) {
56
+ warnings.push({
57
+ type: "unsupported-setting",
58
+ setting: "seed",
59
+ details: "This model does not support the `seed` option."
60
+ });
61
+ }
62
+ if (size != null) {
63
+ warnings.push({
64
+ type: "unsupported-setting",
65
+ setting: "size",
66
+ details: "This model does not support the `size` option. Use `aspectRatio` instead."
67
+ });
68
+ }
69
+ const currentDate = (_c = (_b = (_a = this.config._internal) == null ? void 0 : _a.currentDate) == null ? void 0 : _b.call(_a)) != null ? _c : /* @__PURE__ */ new Date();
70
+ const fullHeaders = combineHeaders(this.config.headers(), headers);
71
+ const { value: generationResponse, responseHeaders } = await postJsonToApi({
72
+ url: this.getLumaGenerationsUrl(),
73
+ headers: fullHeaders,
74
+ body: {
75
+ prompt,
76
+ ...aspectRatio ? { aspect_ratio: aspectRatio } : {},
77
+ model: this.modelId,
78
+ ...(_d = providerOptions.luma) != null ? _d : {}
79
+ },
80
+ abortSignal,
81
+ fetch: this.config.fetch,
82
+ failedResponseHandler: this.createLumaErrorHandler(),
83
+ successfulResponseHandler: createJsonResponseHandler(
84
+ lumaGenerationResponseSchema
85
+ )
86
+ });
87
+ const imageUrl = await this.pollForImageUrl(
88
+ generationResponse.id,
89
+ fullHeaders,
90
+ abortSignal
91
+ );
92
+ const downloadedImage = await this.downloadImage(imageUrl, abortSignal);
93
+ return {
94
+ images: [downloadedImage],
95
+ warnings,
96
+ response: {
97
+ modelId: this.modelId,
98
+ timestamp: currentDate,
99
+ headers: responseHeaders
100
+ }
101
+ };
102
+ }
103
+ async pollForImageUrl(generationId, headers, abortSignal) {
104
+ var _a;
105
+ let attemptCount = 0;
106
+ const url = this.getLumaGenerationsUrl(generationId);
107
+ for (let i = 0; i < this.maxPollAttempts; i++) {
108
+ const { value: statusResponse } = await getFromApi({
109
+ url,
110
+ headers,
111
+ abortSignal,
112
+ fetch: this.config.fetch,
113
+ failedResponseHandler: this.createLumaErrorHandler(),
114
+ successfulResponseHandler: createJsonResponseHandler(
115
+ lumaGenerationResponseSchema
116
+ )
117
+ });
118
+ switch (statusResponse.state) {
119
+ case "completed":
120
+ if (!((_a = statusResponse.assets) == null ? void 0 : _a.image)) {
121
+ throw new InvalidResponseDataError({
122
+ data: statusResponse,
123
+ message: `Image generation completed but no image was found.`
124
+ });
125
+ }
126
+ return statusResponse.assets.image;
127
+ case "failed":
128
+ throw new InvalidResponseDataError({
129
+ data: statusResponse,
130
+ message: `Image generation failed.`
131
+ });
132
+ }
133
+ await delay(this.pollIntervalMillis);
134
+ }
135
+ throw new Error(
136
+ `Image generation timed out after ${this.maxPollAttempts} attempts.`
137
+ );
138
+ }
139
+ createLumaErrorHandler() {
140
+ return createJsonErrorResponseHandler({
141
+ errorSchema: lumaErrorSchema,
142
+ errorToMessage: (error) => {
143
+ var _a;
144
+ return (_a = error.detail[0].msg) != null ? _a : "Unknown error";
145
+ }
146
+ });
147
+ }
148
+ getLumaGenerationsUrl(generationId) {
149
+ return `${this.config.baseURL}/dream-machine/v1/generations/${generationId != null ? generationId : "image"}`;
150
+ }
151
+ async downloadImage(url, abortSignal) {
152
+ const { value: response } = await getFromApi({
153
+ url,
154
+ // No specific headers should be needed for this request as it's a
155
+ // generated image provided by Luma.
156
+ abortSignal,
157
+ failedResponseHandler: createStatusCodeErrorResponseHandler(),
158
+ successfulResponseHandler: createBinaryResponseHandler(),
159
+ fetch: this.config.fetch
160
+ });
161
+ return response;
162
+ }
163
+ };
164
+ var lumaGenerationResponseSchema = z.object({
165
+ id: z.string(),
166
+ state: z.enum(["queued", "dreaming", "completed", "failed"]),
167
+ failure_reason: z.string().nullish(),
168
+ assets: z.object({
169
+ image: z.string()
170
+ // URL of the generated image
171
+ }).nullish()
172
+ });
173
+ var lumaErrorSchema = z.object({
174
+ detail: z.array(
175
+ z.object({
176
+ type: z.string(),
177
+ loc: z.array(z.string()),
178
+ msg: z.string(),
179
+ input: z.string(),
180
+ ctx: z.object({
181
+ expected: z.string()
182
+ }).nullish()
183
+ })
184
+ )
185
+ });
186
+
187
+ // src/luma-provider.ts
188
+ var defaultBaseURL = "https://api.lumalabs.ai";
189
+ function createLuma(options = {}) {
190
+ var _a;
191
+ const baseURL = withoutTrailingSlash((_a = options.baseURL) != null ? _a : defaultBaseURL);
192
+ const getHeaders = () => ({
193
+ Authorization: `Bearer ${loadApiKey({
194
+ apiKey: options.apiKey,
195
+ environmentVariableName: "LUMA_API_KEY",
196
+ description: "Luma"
197
+ })}`,
198
+ ...options.headers
199
+ });
200
+ const createImageModel = (modelId, settings = {}) => new LumaImageModel(modelId, settings, {
201
+ provider: "luma.image",
202
+ baseURL: baseURL != null ? baseURL : defaultBaseURL,
203
+ headers: getHeaders,
204
+ fetch: options.fetch
205
+ });
206
+ const provider = (modelId, settings) => createImageModel(modelId, settings);
207
+ provider.image = createImageModel;
208
+ return provider;
209
+ }
210
+ var luma = createLuma();
211
+ export {
212
+ createLuma,
213
+ luma
214
+ };
215
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/luma-provider.ts","../src/luma-image-model.ts"],"sourcesContent":["import { ImageModelV1 } from '@ai-sdk/provider';\nimport {\n FetchFunction,\n loadApiKey,\n withoutTrailingSlash,\n} from '@ai-sdk/provider-utils';\nimport { LumaImageModel } from './luma-image-model';\nimport { LumaImageModelId, LumaImageSettings } from './luma-image-settings';\n\nexport interface LumaProviderSettings {\n /**\nLuma API key. Default value is taken from the `LUMA_API_KEY` environment\nvariable.\n */\n apiKey?: string;\n /**\nBase URL for the API calls.\n */\n baseURL?: string;\n /**\nCustom headers to include in the requests.\n */\n headers?: Record<string, string>;\n /**\nCustom fetch implementation. You can use it as a middleware to intercept requests,\nor to provide a custom fetch implementation for e.g. testing.\n */\n fetch?: FetchFunction;\n}\n\nexport interface LumaProvider {\n /**\nCreates a model for image generation.\n */\n image(modelId: LumaImageModelId, settings?: LumaImageSettings): ImageModelV1;\n}\n\nconst defaultBaseURL = 'https://api.lumalabs.ai';\n\nexport function createLuma(options: LumaProviderSettings = {}): LumaProvider {\n const baseURL = withoutTrailingSlash(options.baseURL ?? defaultBaseURL);\n const getHeaders = () => ({\n Authorization: `Bearer ${loadApiKey({\n apiKey: options.apiKey,\n environmentVariableName: 'LUMA_API_KEY',\n description: 'Luma',\n })}`,\n ...options.headers,\n });\n\n const createImageModel = (\n modelId: LumaImageModelId,\n settings: LumaImageSettings = {},\n ) =>\n new LumaImageModel(modelId, settings, {\n provider: 'luma.image',\n baseURL: baseURL ?? defaultBaseURL,\n headers: getHeaders,\n fetch: options.fetch,\n });\n\n const provider = (modelId: LumaImageModelId, settings?: LumaImageSettings) =>\n createImageModel(modelId, settings);\n\n provider.image = createImageModel;\n\n return provider as LumaProvider;\n}\n\nexport const luma = createLuma();\n","import {\n ImageModelV1,\n ImageModelV1CallWarning,\n InvalidResponseDataError,\n} from '@ai-sdk/provider';\nimport {\n FetchFunction,\n combineHeaders,\n createBinaryResponseHandler,\n createJsonResponseHandler,\n createJsonErrorResponseHandler,\n createStatusCodeErrorResponseHandler,\n getFromApi,\n postJsonToApi,\n} from '@ai-sdk/provider-utils';\nimport { LumaImageSettings } from './luma-image-settings';\nimport { z } from 'zod';\n\nconst DEFAULT_POLL_INTERVAL_MILLIS = 500;\nconst DEFAULT_MAX_POLL_ATTEMPTS = 60000 / DEFAULT_POLL_INTERVAL_MILLIS;\n\ninterface LumaImageModelConfig {\n provider: string;\n baseURL: string;\n headers: () => Record<string, string>;\n fetch?: FetchFunction;\n _internal?: {\n currentDate?: () => Date;\n };\n}\n\nasync function delay(delayInMs?: number | null): Promise<void> {\n return delayInMs == null\n ? Promise.resolve()\n : new Promise(resolve => setTimeout(resolve, delayInMs));\n}\n\nexport class LumaImageModel implements ImageModelV1 {\n readonly specificationVersion = 'v1';\n\n private readonly pollIntervalMillis: number;\n private readonly maxPollAttempts: number;\n\n get provider(): string {\n return this.config.provider;\n }\n\n get maxImagesPerCall(): number {\n return this.settings.maxImagesPerCall ?? 1;\n }\n\n constructor(\n readonly modelId: string,\n private readonly settings: LumaImageSettings,\n private readonly config: LumaImageModelConfig,\n ) {\n this.pollIntervalMillis =\n settings.pollIntervalMillis ?? DEFAULT_POLL_INTERVAL_MILLIS;\n this.maxPollAttempts =\n settings.maxPollAttempts ?? DEFAULT_MAX_POLL_ATTEMPTS;\n }\n\n async doGenerate({\n prompt,\n n,\n size,\n aspectRatio,\n seed,\n providerOptions,\n headers,\n abortSignal,\n }: Parameters<ImageModelV1['doGenerate']>[0]): Promise<\n Awaited<ReturnType<ImageModelV1['doGenerate']>>\n > {\n const warnings: Array<ImageModelV1CallWarning> = [];\n\n if (seed != null) {\n warnings.push({\n type: 'unsupported-setting',\n setting: 'seed',\n details: 'This model does not support the `seed` option.',\n });\n }\n\n if (size != null) {\n warnings.push({\n type: 'unsupported-setting',\n setting: 'size',\n details:\n 'This model does not support the `size` option. Use `aspectRatio` instead.',\n });\n }\n\n const currentDate = this.config._internal?.currentDate?.() ?? new Date();\n const fullHeaders = combineHeaders(this.config.headers(), headers);\n const { value: generationResponse, responseHeaders } = await postJsonToApi({\n url: this.getLumaGenerationsUrl(),\n headers: fullHeaders,\n body: {\n prompt,\n ...(aspectRatio ? { aspect_ratio: aspectRatio } : {}),\n model: this.modelId,\n ...(providerOptions.luma ?? {}),\n },\n abortSignal,\n fetch: this.config.fetch,\n failedResponseHandler: this.createLumaErrorHandler(),\n successfulResponseHandler: createJsonResponseHandler(\n lumaGenerationResponseSchema,\n ),\n });\n\n const imageUrl = await this.pollForImageUrl(\n generationResponse.id,\n fullHeaders,\n abortSignal,\n );\n\n const downloadedImage = await this.downloadImage(imageUrl, abortSignal);\n\n return {\n images: [downloadedImage],\n warnings,\n response: {\n modelId: this.modelId,\n timestamp: currentDate,\n headers: responseHeaders,\n },\n };\n }\n\n private async pollForImageUrl(\n generationId: string,\n headers: Record<string, string | undefined>,\n abortSignal: AbortSignal | undefined,\n ): Promise<string> {\n let attemptCount = 0;\n const url = this.getLumaGenerationsUrl(generationId);\n for (let i = 0; i < this.maxPollAttempts; i++) {\n const { value: statusResponse } = await getFromApi({\n url,\n headers,\n abortSignal,\n fetch: this.config.fetch,\n failedResponseHandler: this.createLumaErrorHandler(),\n successfulResponseHandler: createJsonResponseHandler(\n lumaGenerationResponseSchema,\n ),\n });\n\n switch (statusResponse.state) {\n case 'completed':\n if (!statusResponse.assets?.image) {\n throw new InvalidResponseDataError({\n data: statusResponse,\n message: `Image generation completed but no image was found.`,\n });\n }\n return statusResponse.assets.image;\n case 'failed':\n throw new InvalidResponseDataError({\n data: statusResponse,\n message: `Image generation failed.`,\n });\n }\n await delay(this.pollIntervalMillis);\n }\n\n throw new Error(\n `Image generation timed out after ${this.maxPollAttempts} attempts.`,\n );\n }\n\n private createLumaErrorHandler() {\n return createJsonErrorResponseHandler({\n errorSchema: lumaErrorSchema,\n errorToMessage: (error: LumaErrorData) =>\n error.detail[0].msg ?? 'Unknown error',\n });\n }\n\n private getLumaGenerationsUrl(generationId?: string) {\n return `${this.config.baseURL}/dream-machine/v1/generations/${\n generationId ?? 'image'\n }`;\n }\n\n private async downloadImage(\n url: string,\n abortSignal: AbortSignal | undefined,\n ): Promise<Uint8Array> {\n const { value: response } = await getFromApi({\n url,\n // No specific headers should be needed for this request as it's a\n // generated image provided by Luma.\n abortSignal,\n failedResponseHandler: createStatusCodeErrorResponseHandler(),\n successfulResponseHandler: createBinaryResponseHandler(),\n fetch: this.config.fetch,\n });\n return response;\n }\n}\n\n// limited version of the schema, focussed on what is needed for the implementation\n// this approach limits breakages when the API changes and increases efficiency\nconst lumaGenerationResponseSchema = z.object({\n id: z.string(),\n state: z.enum(['queued', 'dreaming', 'completed', 'failed']),\n failure_reason: z.string().nullish(),\n assets: z\n .object({\n image: z.string(), // URL of the generated image\n })\n .nullish(),\n});\n\nconst lumaErrorSchema = z.object({\n detail: z.array(\n z.object({\n type: z.string(),\n loc: z.array(z.string()),\n msg: z.string(),\n input: z.string(),\n ctx: z\n .object({\n expected: z.string(),\n })\n .nullish(),\n }),\n ),\n});\n\nexport type LumaErrorData = z.infer<typeof lumaErrorSchema>;\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@ai-sdk/luma",
3
+ "version": "0.0.1",
4
+ "license": "Apache-2.0",
5
+ "sideEffects": false,
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.mjs",
8
+ "types": "./dist/index.d.ts",
9
+ "files": [
10
+ "dist/**/*",
11
+ "CHANGELOG.md"
12
+ ],
13
+ "exports": {
14
+ "./package.json": "./package.json",
15
+ ".": {
16
+ "types": "./dist/index.d.ts",
17
+ "import": "./dist/index.mjs",
18
+ "require": "./dist/index.js"
19
+ }
20
+ },
21
+ "dependencies": {
22
+ "@ai-sdk/provider": "1.0.6",
23
+ "@ai-sdk/provider-utils": "2.1.3"
24
+ },
25
+ "devDependencies": {
26
+ "@types/node": "^18",
27
+ "tsup": "^8",
28
+ "typescript": "5.6.3",
29
+ "zod": "3.23.8",
30
+ "@vercel/ai-tsconfig": "0.0.0"
31
+ },
32
+ "peerDependencies": {
33
+ "zod": "^3.0.0"
34
+ },
35
+ "engines": {
36
+ "node": ">=18"
37
+ },
38
+ "publishConfig": {
39
+ "access": "public"
40
+ },
41
+ "homepage": "https://sdk.vercel.ai/docs",
42
+ "repository": {
43
+ "type": "git",
44
+ "url": "git+https://github.com/vercel/ai.git"
45
+ },
46
+ "bugs": {
47
+ "url": "https://github.com/vercel/ai/issues"
48
+ },
49
+ "keywords": [
50
+ "ai"
51
+ ],
52
+ "scripts": {
53
+ "build": "tsup",
54
+ "build:watch": "tsup --watch",
55
+ "clean": "rm -rf dist",
56
+ "lint": "eslint \"./**/*.ts*\"",
57
+ "type-check": "tsc --noEmit",
58
+ "prettier-check": "prettier --check \"./**/*.ts*\"",
59
+ "test": "pnpm test:node && pnpm test:edge",
60
+ "test:edge": "vitest --config vitest.edge.config.js --run",
61
+ "test:node": "vitest --config vitest.node.config.js --run"
62
+ }
63
+ }