@neta-art/generation 0.1.0 → 0.1.2
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/README.md +36 -0
- package/dist/{builtins-hmNIcYXN.js → builtins-B1AheaEa.js} +9 -2
- package/dist/builtins-B1AheaEa.js.map +1 -0
- package/dist/{builtins-BuI-rf8X.d.ts → builtins-BE6FhnwP.d.ts} +32 -2
- package/dist/builtins-BE6FhnwP.d.ts.map +1 -0
- package/dist/builtins.d.ts +1 -1
- package/dist/builtins.js +1 -1
- package/dist/cli/index.js +84 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/export-config-BPnZYpmN.js +884 -0
- package/dist/export-config-BPnZYpmN.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -601
- package/package.json +17 -18
- package/dist/builtins-BuI-rf8X.d.ts.map +0 -1
- package/dist/builtins-hmNIcYXN.js.map +0 -1
- package/dist/export-config-ZxwkQoZ6.js +0 -128
- package/dist/export-config-ZxwkQoZ6.js.map +0 -1
- package/dist/index.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,602 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { l as MODEL_SCHEMA, n as getBuiltinGenerationModel, r as listBuiltinGenerationModels, t as builtinGenerationModels } from "./builtins-B1AheaEa.js";
|
|
2
|
+
import { A as GenerationUnsupportedAdapterError, C as mergeTextBlocks, D as GenerationError, E as GenerationConfigError, O as GenerationProviderError, S as arkVideoGenerationsAdapter, T as validateGenerationContent, _ as writeGenerationModelDeclarations, a as createGenerationClientFromDirectory, b as openAiImagesAdapter, c as defaultGenerationSourceResolver, d as parseGenerationModelDeclaration, f as readGenerationModelDeclaration, g as writeGenerationModelDeclaration, h as stringifyGenerationModelDeclaration, i as createGenerationClient, j as GenerationValidationError, k as GenerationTimeoutError, l as isGenerationModelDeclaration, m as readGenerationModelDeclarationsFromFiles, n as exportBuiltinModelConfigs, o as createGenerationClientFromFile, p as readGenerationModelDeclarationsFromDirectory, r as stringifyBuiltinModelConfig, s as createGenerationClientFromFiles, t as exportBuiltinModelConfig, u as mergeGenerationModelDeclarations, v as builtinGenerationAdapters, w as resolveGenerationParameters, x as geminiGenerateContentAdapter, y as getGenerationAdapter } from "./export-config-BPnZYpmN.js";
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
async function fetchWithTimeout(fetchFn, url, init, timeoutMs) {
|
|
6
|
-
const controller = new AbortController();
|
|
7
|
-
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
8
|
-
try {
|
|
9
|
-
return await fetchFn(url, {
|
|
10
|
-
...init,
|
|
11
|
-
signal: controller.signal
|
|
12
|
-
});
|
|
13
|
-
} catch (error) {
|
|
14
|
-
if (error instanceof Error && error.name === "AbortError") throw new GenerationTimeoutError();
|
|
15
|
-
throw error;
|
|
16
|
-
} finally {
|
|
17
|
-
clearTimeout(timeout);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
function joinUrl(baseUrl, path) {
|
|
21
|
-
return `${baseUrl.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
//#endregion
|
|
25
|
-
//#region src/validation.ts
|
|
26
|
-
function specsByType(specs) {
|
|
27
|
-
const map = /* @__PURE__ */ new Map();
|
|
28
|
-
for (const spec of specs) map.set(spec.type, spec);
|
|
29
|
-
return map;
|
|
30
|
-
}
|
|
31
|
-
function validateGenerationContent(declaration, content) {
|
|
32
|
-
const inputSpecs = specsByType(declaration.content.input);
|
|
33
|
-
const counts = /* @__PURE__ */ new Map();
|
|
34
|
-
for (const block of content) {
|
|
35
|
-
const spec = inputSpecs.get(block.type);
|
|
36
|
-
if (!spec) throw new GenerationValidationError(`Content block type is not supported by ${declaration.model}: ${block.type}`);
|
|
37
|
-
counts.set(block.type, (counts.get(block.type) ?? 0) + 1);
|
|
38
|
-
if ("source" in block && spec.sources && !spec.sources.includes(block.source.type)) throw new GenerationValidationError(`${block.type} source is not supported by ${declaration.model}: ${block.source.type}`);
|
|
39
|
-
}
|
|
40
|
-
for (const spec of declaration.content.input) {
|
|
41
|
-
const count = counts.get(spec.type) ?? 0;
|
|
42
|
-
if (spec.required && count === 0) throw new GenerationValidationError(`Missing required ${spec.type} content block`);
|
|
43
|
-
if (spec.min !== void 0 && count < spec.min) throw new GenerationValidationError(`Expected at least ${spec.min} ${spec.type} content block(s)`);
|
|
44
|
-
if (spec.max !== void 0 && count > spec.max) throw new GenerationValidationError(`Expected at most ${spec.max} ${spec.type} content block(s)`);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
function resolveGenerationParameters(declaration, parameters) {
|
|
48
|
-
const specs = declaration.parameters ?? {};
|
|
49
|
-
const resolved = {};
|
|
50
|
-
for (const key of Object.keys(parameters ?? {})) if (!specs[key]) throw new GenerationValidationError(`Unknown parameter: ${key}`);
|
|
51
|
-
for (const [key, spec] of Object.entries(specs)) {
|
|
52
|
-
const value = parameters?.[key];
|
|
53
|
-
if (value === void 0) {
|
|
54
|
-
if (spec.default !== void 0) resolved[key] = spec.default;
|
|
55
|
-
else if (!spec.optional) throw new GenerationValidationError(`Missing required parameter: ${key}`);
|
|
56
|
-
continue;
|
|
57
|
-
}
|
|
58
|
-
switch (spec.type) {
|
|
59
|
-
case "string":
|
|
60
|
-
if (typeof value !== "string") throw new GenerationValidationError(`Parameter ${key} must be a string`);
|
|
61
|
-
if (spec.enum && !spec.enum.includes(value)) throw new GenerationValidationError(`Parameter ${key} must be one of: ${spec.enum.join(", ")}`);
|
|
62
|
-
break;
|
|
63
|
-
case "number":
|
|
64
|
-
if (typeof value !== "number" || !Number.isFinite(value)) throw new GenerationValidationError(`Parameter ${key} must be a number`);
|
|
65
|
-
if (spec.min !== void 0 && value < spec.min) throw new GenerationValidationError(`Parameter ${key} must be >= ${spec.min}`);
|
|
66
|
-
if (spec.max !== void 0 && value > spec.max) throw new GenerationValidationError(`Parameter ${key} must be <= ${spec.max}`);
|
|
67
|
-
break;
|
|
68
|
-
case "integer":
|
|
69
|
-
if (typeof value !== "number" || !Number.isInteger(value)) throw new GenerationValidationError(`Parameter ${key} must be an integer`);
|
|
70
|
-
if (spec.min !== void 0 && value < spec.min) throw new GenerationValidationError(`Parameter ${key} must be >= ${spec.min}`);
|
|
71
|
-
if (spec.max !== void 0 && value > spec.max) throw new GenerationValidationError(`Parameter ${key} must be <= ${spec.max}`);
|
|
72
|
-
break;
|
|
73
|
-
case "boolean":
|
|
74
|
-
if (typeof value !== "boolean") throw new GenerationValidationError(`Parameter ${key} must be a boolean`);
|
|
75
|
-
break;
|
|
76
|
-
}
|
|
77
|
-
resolved[key] = value;
|
|
78
|
-
}
|
|
79
|
-
return resolved;
|
|
80
|
-
}
|
|
81
|
-
function mergeTextBlocks(declaration, content) {
|
|
82
|
-
const textSpec = declaration.content.input.find((spec) => spec.type === "text");
|
|
83
|
-
const separator = textSpec?.merge === "space" ? " " : textSpec?.merge === "concat" ? "" : "\n";
|
|
84
|
-
return content.filter((block) => block.type === "text").map((block) => block.text).join(separator).trim();
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
//#endregion
|
|
88
|
-
//#region src/adapters/ark-video-generations.ts
|
|
89
|
-
const REQUEST_TIMEOUT_MS$2 = 6e4;
|
|
90
|
-
const DEFAULT_POLL_INTERVAL_SEC = 2;
|
|
91
|
-
const DEFAULT_MAX_WAIT_SEC = 600;
|
|
92
|
-
const RESOLUTION_SHORT_EDGE = {
|
|
93
|
-
"480p": 480,
|
|
94
|
-
"720p": 720,
|
|
95
|
-
"1080p": 1080,
|
|
96
|
-
"2K": 1440
|
|
97
|
-
};
|
|
98
|
-
const ASPECT_RATIOS = {
|
|
99
|
-
"16:9": [16, 9],
|
|
100
|
-
"9:16": [9, 16],
|
|
101
|
-
"1:1": [1, 1],
|
|
102
|
-
"4:3": [4, 3],
|
|
103
|
-
"3:2": [3, 2],
|
|
104
|
-
"2:3": [2, 3],
|
|
105
|
-
"3:4": [3, 4],
|
|
106
|
-
"21:9": [21, 9],
|
|
107
|
-
adaptive: null
|
|
108
|
-
};
|
|
109
|
-
function sleep(ms) {
|
|
110
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
111
|
-
}
|
|
112
|
-
function asString(value) {
|
|
113
|
-
return typeof value === "string" && value ? value : void 0;
|
|
114
|
-
}
|
|
115
|
-
function asNumber(value) {
|
|
116
|
-
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
117
|
-
}
|
|
118
|
-
function asBoolean(value) {
|
|
119
|
-
return typeof value === "boolean" ? value : void 0;
|
|
120
|
-
}
|
|
121
|
-
function getIntegerParameter(parameters, key, fallback) {
|
|
122
|
-
const value = parameters[key];
|
|
123
|
-
return typeof value === "number" && Number.isInteger(value) ? value : fallback;
|
|
124
|
-
}
|
|
125
|
-
function resolveSize(resolution, aspectRatio) {
|
|
126
|
-
const ratio = ASPECT_RATIOS[aspectRatio];
|
|
127
|
-
if (!ratio) return null;
|
|
128
|
-
const shortEdge = RESOLUTION_SHORT_EDGE[resolution];
|
|
129
|
-
if (!shortEdge) throw new GenerationValidationError(`Unsupported resolution: ${resolution}`);
|
|
130
|
-
const [wRatio, hRatio] = ratio;
|
|
131
|
-
const width = wRatio >= hRatio ? Math.round(shortEdge * wRatio / hRatio) : shortEdge;
|
|
132
|
-
const height = wRatio >= hRatio ? shortEdge : Math.round(shortEdge * hRatio / wRatio);
|
|
133
|
-
return {
|
|
134
|
-
width: width % 2 === 0 ? width : width + 1,
|
|
135
|
-
height: height % 2 === 0 ? height : height + 1
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
function getImageRole(block) {
|
|
139
|
-
const role = getBlockMeta(block)?.role;
|
|
140
|
-
return typeof role === "string" && role ? role : void 0;
|
|
141
|
-
}
|
|
142
|
-
function classifyImages(images) {
|
|
143
|
-
if (images.length === 0) return null;
|
|
144
|
-
const hasFirstOrLast = images.some((image) => image.role === "first_frame" || image.role === "last_frame");
|
|
145
|
-
const hasReference = images.some((image) => image.role === "reference_image");
|
|
146
|
-
if ([
|
|
147
|
-
images.some((image) => !image.role),
|
|
148
|
-
hasFirstOrLast,
|
|
149
|
-
hasReference
|
|
150
|
-
].filter(Boolean).length > 1) throw new GenerationValidationError("Cannot mix video image modes: use only plain image, first_frame/last_frame, or reference_image");
|
|
151
|
-
if (hasReference) return "reference";
|
|
152
|
-
if (hasFirstOrLast) return "frame";
|
|
153
|
-
return "image";
|
|
154
|
-
}
|
|
155
|
-
function buildMetadataContent(prompt, images, mode) {
|
|
156
|
-
const content = [{
|
|
157
|
-
type: "text",
|
|
158
|
-
text: prompt
|
|
159
|
-
}];
|
|
160
|
-
for (const image of images) {
|
|
161
|
-
if (mode === "frame" && image.role !== "first_frame" && image.role !== "last_frame") throw new GenerationValidationError("Frame mode images must use meta.role first_frame or last_frame");
|
|
162
|
-
if (mode === "reference" && image.role !== "reference_image") throw new GenerationValidationError("Reference mode images must use meta.role reference_image");
|
|
163
|
-
content.push({
|
|
164
|
-
type: "image_url",
|
|
165
|
-
image_url: { url: image.url },
|
|
166
|
-
role: image.role
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
return content;
|
|
170
|
-
}
|
|
171
|
-
function extractTaskId(response) {
|
|
172
|
-
const taskId = asString(response.task_id) ?? asString(response.id);
|
|
173
|
-
if (!taskId) throw new GenerationProviderError("Video generation provider did not return a task id");
|
|
174
|
-
return taskId;
|
|
175
|
-
}
|
|
176
|
-
function normalizeTaskStatus(response) {
|
|
177
|
-
if (response.data) {
|
|
178
|
-
const wrapper = response.data;
|
|
179
|
-
const native = wrapper.data;
|
|
180
|
-
const status = (asString(native?.status) ?? asString(wrapper.status) ?? "unknown").toLowerCase();
|
|
181
|
-
const videoUrl = asString(wrapper.result_url) ?? asString(native?.content?.video_url);
|
|
182
|
-
const lastFrameUrl = asString(native?.content?.last_frame_url);
|
|
183
|
-
const metadata = {
|
|
184
|
-
progress: wrapper.progress,
|
|
185
|
-
resolution: native?.resolution,
|
|
186
|
-
ratio: native?.ratio,
|
|
187
|
-
duration: native?.duration,
|
|
188
|
-
framespersecond: native?.framespersecond,
|
|
189
|
-
seed: native?.seed,
|
|
190
|
-
generate_audio: native?.generate_audio,
|
|
191
|
-
model: native?.model,
|
|
192
|
-
usage: native?.usage
|
|
193
|
-
};
|
|
194
|
-
for (const key of Object.keys(metadata)) if (metadata[key] === void 0) delete metadata[key];
|
|
195
|
-
return {
|
|
196
|
-
status,
|
|
197
|
-
videoUrl,
|
|
198
|
-
lastFrameUrl,
|
|
199
|
-
metadata
|
|
200
|
-
};
|
|
201
|
-
}
|
|
202
|
-
return {
|
|
203
|
-
status: (asString(response.status) ?? "unknown").toLowerCase(),
|
|
204
|
-
videoUrl: asString(response.url),
|
|
205
|
-
lastFrameUrl: asString(response.last_frame_url),
|
|
206
|
-
metadata: response.metadata && typeof response.metadata === "object" ? response.metadata : {}
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
async function requestJson(input, path, init) {
|
|
210
|
-
const response = await fetchWithTimeout(input.context.fetch, joinUrl(input.context.baseUrl, path), {
|
|
211
|
-
...init,
|
|
212
|
-
headers: {
|
|
213
|
-
Authorization: `Bearer ${input.context.apiKey}`,
|
|
214
|
-
"Content-Type": "application/json",
|
|
215
|
-
...init.headers
|
|
216
|
-
}
|
|
217
|
-
}, REQUEST_TIMEOUT_MS$2);
|
|
218
|
-
if (!response.ok) {
|
|
219
|
-
const body = await response.text().catch(() => response.statusText);
|
|
220
|
-
throw new GenerationProviderError("Video generation provider request failed", {
|
|
221
|
-
status: response.status,
|
|
222
|
-
body
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
return response.json();
|
|
226
|
-
}
|
|
227
|
-
async function arkVideoGenerationsAdapter(input) {
|
|
228
|
-
const prompt = mergeTextBlocks(input.declaration, input.request.content);
|
|
229
|
-
if (!prompt) throw new GenerationValidationError("Prompt text is required");
|
|
230
|
-
const imageBlocks = input.request.content.filter((block) => block.type === "image");
|
|
231
|
-
const images = await Promise.all(imageBlocks.map(async (block) => ({
|
|
232
|
-
url: await input.context.resolveSource(block.source),
|
|
233
|
-
role: getImageRole(block)
|
|
234
|
-
})));
|
|
235
|
-
const mode = classifyImages(images);
|
|
236
|
-
const resolution = asString(input.parameters.resolution) ?? "720p";
|
|
237
|
-
const aspectRatio = asString(input.parameters.aspect_ratio) ?? "16:9";
|
|
238
|
-
const duration = getIntegerParameter(input.parameters, "duration", 5);
|
|
239
|
-
const fps = getIntegerParameter(input.parameters, "fps", 30);
|
|
240
|
-
const pollIntervalSec = getIntegerParameter(input.parameters, "poll_interval", DEFAULT_POLL_INTERVAL_SEC);
|
|
241
|
-
const maxWaitSec = getIntegerParameter(input.parameters, "max_wait", DEFAULT_MAX_WAIT_SEC);
|
|
242
|
-
const generateAudio = asBoolean(input.parameters.generate_audio) ?? true;
|
|
243
|
-
const returnLastFrame = asBoolean(input.parameters.return_last_frame) ?? true;
|
|
244
|
-
const cameraFixed = asBoolean(input.parameters.camera_fixed) ?? false;
|
|
245
|
-
const watermark = asBoolean(input.parameters.watermark) ?? false;
|
|
246
|
-
const seed = asNumber(input.parameters.seed);
|
|
247
|
-
const payload = {
|
|
248
|
-
model: input.declaration.model,
|
|
249
|
-
prompt
|
|
250
|
-
};
|
|
251
|
-
const metadata = {
|
|
252
|
-
duration,
|
|
253
|
-
fps,
|
|
254
|
-
generate_audio: generateAudio
|
|
255
|
-
};
|
|
256
|
-
if (seed !== void 0) metadata.seed = seed;
|
|
257
|
-
if (returnLastFrame) metadata.return_last_frame = true;
|
|
258
|
-
if (cameraFixed) metadata.camera_fixed = true;
|
|
259
|
-
if (watermark) metadata.watermark = true;
|
|
260
|
-
if (mode === "frame" || mode === "reference") {
|
|
261
|
-
metadata.content = buildMetadataContent(prompt, images, mode);
|
|
262
|
-
metadata.resolution = resolution;
|
|
263
|
-
metadata.ratio = aspectRatio;
|
|
264
|
-
} else {
|
|
265
|
-
const size = resolveSize(resolution, aspectRatio);
|
|
266
|
-
if (size) {
|
|
267
|
-
payload.width = size.width;
|
|
268
|
-
payload.height = size.height;
|
|
269
|
-
}
|
|
270
|
-
if (images[0]) payload.image = images[0].url;
|
|
271
|
-
}
|
|
272
|
-
payload.metadata = metadata;
|
|
273
|
-
const taskId = extractTaskId(await requestJson(input, "/v1/video/generations", {
|
|
274
|
-
method: "POST",
|
|
275
|
-
body: JSON.stringify(payload)
|
|
276
|
-
}));
|
|
277
|
-
const startedAt = Date.now();
|
|
278
|
-
while (Date.now() - startedAt <= maxWaitSec * 1e3) {
|
|
279
|
-
await sleep(pollIntervalSec * 1e3);
|
|
280
|
-
const rawStatus = await requestJson(input, `/v1/video/generations/${encodeURIComponent(taskId)}`, { method: "GET" });
|
|
281
|
-
const status = normalizeTaskStatus(rawStatus);
|
|
282
|
-
if (status.status === "succeeded") {
|
|
283
|
-
if (!status.videoUrl) throw new GenerationProviderError("Video generation succeeded but returned no video URL");
|
|
284
|
-
const output = [{
|
|
285
|
-
type: "video",
|
|
286
|
-
source: {
|
|
287
|
-
type: "url",
|
|
288
|
-
url: status.videoUrl
|
|
289
|
-
},
|
|
290
|
-
meta: {
|
|
291
|
-
task_id: taskId,
|
|
292
|
-
status: status.status,
|
|
293
|
-
...status.metadata
|
|
294
|
-
}
|
|
295
|
-
}];
|
|
296
|
-
if (status.lastFrameUrl) output.push({
|
|
297
|
-
type: "image",
|
|
298
|
-
source: {
|
|
299
|
-
type: "url",
|
|
300
|
-
url: status.lastFrameUrl
|
|
301
|
-
},
|
|
302
|
-
meta: {
|
|
303
|
-
role: "last_frame",
|
|
304
|
-
task_id: taskId
|
|
305
|
-
}
|
|
306
|
-
});
|
|
307
|
-
return output;
|
|
308
|
-
}
|
|
309
|
-
if ([
|
|
310
|
-
"failed",
|
|
311
|
-
"expired",
|
|
312
|
-
"cancelled"
|
|
313
|
-
].includes(status.status)) throw new GenerationProviderError(`Video generation ${status.status}`, { details: {
|
|
314
|
-
taskId,
|
|
315
|
-
rawStatus
|
|
316
|
-
} });
|
|
317
|
-
}
|
|
318
|
-
throw new GenerationTimeoutError("Timed out waiting for video generation", { taskId });
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
//#endregion
|
|
322
|
-
//#region src/adapters/gemini-generate-content.ts
|
|
323
|
-
const REQUEST_TIMEOUT_MS$1 = 3e5;
|
|
324
|
-
const IMAGE_FETCH_TIMEOUT_MS = 6e4;
|
|
325
|
-
const MAX_REFERENCE_IMAGE_BYTES = 10 * 1024 * 1024;
|
|
326
|
-
const DATA_URI_PATTERN = /^data:([^;]+);base64,(.+)$/s;
|
|
327
|
-
const MARKDOWN_IMAGE_DATA_URI_PATTERN = /!\[[^\]]*\]\(data:([^;]+);base64,([^)]+)\)/;
|
|
328
|
-
function dataUriToInlineData(value) {
|
|
329
|
-
const match = DATA_URI_PATTERN.exec(value);
|
|
330
|
-
if (!match) return null;
|
|
331
|
-
const [, mimeType, data] = match;
|
|
332
|
-
if (!mimeType || !data) return null;
|
|
333
|
-
return { inlineData: {
|
|
334
|
-
mimeType,
|
|
335
|
-
data
|
|
336
|
-
} };
|
|
337
|
-
}
|
|
338
|
-
async function urlToInlineData(fetchFn, url) {
|
|
339
|
-
const response = await fetchWithTimeout(fetchFn, url, {
|
|
340
|
-
method: "GET",
|
|
341
|
-
headers: { "User-Agent": "NetaGeneration/1.0" }
|
|
342
|
-
}, IMAGE_FETCH_TIMEOUT_MS);
|
|
343
|
-
if (!response.ok) throw new GenerationProviderError("Failed to fetch reference image", { status: response.status });
|
|
344
|
-
const contentLength = response.headers.get("content-length");
|
|
345
|
-
if (contentLength && Number(contentLength) > MAX_REFERENCE_IMAGE_BYTES) throw new GenerationValidationError("Reference image is too large");
|
|
346
|
-
const bytes = Buffer.from(await response.arrayBuffer());
|
|
347
|
-
if (bytes.byteLength > MAX_REFERENCE_IMAGE_BYTES) throw new GenerationValidationError("Reference image is too large");
|
|
348
|
-
const contentType = response.headers.get("content-type")?.split(";")[0]?.trim();
|
|
349
|
-
return { inlineData: {
|
|
350
|
-
mimeType: contentType?.startsWith("image/") ? contentType : "image/png",
|
|
351
|
-
data: bytes.toString("base64")
|
|
352
|
-
} };
|
|
353
|
-
}
|
|
354
|
-
async function sourceToInlineData(input, value) {
|
|
355
|
-
const inline = dataUriToInlineData(value);
|
|
356
|
-
if (inline) return inline;
|
|
357
|
-
if (value.startsWith("http://") || value.startsWith("https://")) return urlToInlineData(input.context.fetch, value);
|
|
358
|
-
throw new GenerationValidationError("Unsupported image source for Gemini image generation");
|
|
359
|
-
}
|
|
360
|
-
function extractMarkdownDataUriImage(text) {
|
|
361
|
-
const match = MARKDOWN_IMAGE_DATA_URI_PATTERN.exec(text);
|
|
362
|
-
if (!match) return null;
|
|
363
|
-
const [, mediaType, data] = match;
|
|
364
|
-
if (!mediaType || !data) return null;
|
|
365
|
-
return {
|
|
366
|
-
type: "image",
|
|
367
|
-
source: {
|
|
368
|
-
type: "base64",
|
|
369
|
-
mediaType,
|
|
370
|
-
data
|
|
371
|
-
}
|
|
372
|
-
};
|
|
373
|
-
}
|
|
374
|
-
function appendGeminiPartOutput(output, part) {
|
|
375
|
-
if (typeof part.text === "string" && part.text.trim()) {
|
|
376
|
-
const image = extractMarkdownDataUriImage(part.text);
|
|
377
|
-
if (image) {
|
|
378
|
-
output.push(image);
|
|
379
|
-
return;
|
|
380
|
-
}
|
|
381
|
-
output.push({
|
|
382
|
-
type: "text",
|
|
383
|
-
text: part.text
|
|
384
|
-
});
|
|
385
|
-
return;
|
|
386
|
-
}
|
|
387
|
-
if (part.inlineData && typeof part.inlineData.data === "string" && part.inlineData.data) {
|
|
388
|
-
output.push({
|
|
389
|
-
type: "image",
|
|
390
|
-
source: {
|
|
391
|
-
type: "base64",
|
|
392
|
-
mediaType: typeof part.inlineData.mimeType === "string" ? part.inlineData.mimeType : "image/png",
|
|
393
|
-
data: part.inlineData.data
|
|
394
|
-
}
|
|
395
|
-
});
|
|
396
|
-
return;
|
|
397
|
-
}
|
|
398
|
-
const inline = part.inline_data;
|
|
399
|
-
if (!inline || typeof inline.data !== "string" || !inline.data) return;
|
|
400
|
-
const mediaType = typeof inline.mime_type === "string" ? inline.mime_type : typeof inline.mimeType === "string" ? inline.mimeType : "image/png";
|
|
401
|
-
output.push({
|
|
402
|
-
type: "image",
|
|
403
|
-
source: {
|
|
404
|
-
type: "base64",
|
|
405
|
-
mediaType,
|
|
406
|
-
data: inline.data
|
|
407
|
-
}
|
|
408
|
-
});
|
|
409
|
-
}
|
|
410
|
-
async function geminiGenerateContentAdapter(input) {
|
|
411
|
-
const prompt = mergeTextBlocks(input.declaration, input.request.content);
|
|
412
|
-
if (!prompt) throw new GenerationValidationError("Prompt text is required");
|
|
413
|
-
const imageParts = await Promise.all(input.request.content.filter((block) => block.type === "image").map(async (block) => sourceToInlineData(input, await input.context.resolveSource(block.source))));
|
|
414
|
-
const generationConfig = { responseModalities: ["IMAGE"] };
|
|
415
|
-
const aspectRatio = input.parameters.aspect_ratio;
|
|
416
|
-
const imageSize = input.parameters.image_size;
|
|
417
|
-
if (typeof aspectRatio === "string" || typeof imageSize === "string") {
|
|
418
|
-
const image = {};
|
|
419
|
-
if (typeof aspectRatio === "string") image.aspectRatio = aspectRatio;
|
|
420
|
-
if (typeof imageSize === "string") image.imageSize = imageSize;
|
|
421
|
-
generationConfig.responseFormat = { image };
|
|
422
|
-
}
|
|
423
|
-
const payload = {
|
|
424
|
-
contents: [{ parts: [{ text: prompt }, ...imageParts] }],
|
|
425
|
-
generationConfig
|
|
426
|
-
};
|
|
427
|
-
const response = await fetchWithTimeout(input.context.fetch, joinUrl(input.context.baseUrl, `/v1beta/models/${encodeURIComponent(input.declaration.model)}:generateContent`), {
|
|
428
|
-
method: "POST",
|
|
429
|
-
headers: {
|
|
430
|
-
Authorization: `Bearer ${input.context.apiKey}`,
|
|
431
|
-
"Content-Type": "application/json"
|
|
432
|
-
},
|
|
433
|
-
body: JSON.stringify(payload)
|
|
434
|
-
}, REQUEST_TIMEOUT_MS$1);
|
|
435
|
-
if (!response.ok) {
|
|
436
|
-
const body = await response.text().catch(() => response.statusText);
|
|
437
|
-
throw new GenerationProviderError("Gemini generation provider request failed", {
|
|
438
|
-
status: response.status,
|
|
439
|
-
body
|
|
440
|
-
});
|
|
441
|
-
}
|
|
442
|
-
const raw = await response.json();
|
|
443
|
-
const output = [];
|
|
444
|
-
for (const candidate of raw.candidates ?? []) for (const part of candidate.content?.parts ?? []) appendGeminiPartOutput(output, part);
|
|
445
|
-
if (output.length === 0) throw new GenerationProviderError("Gemini generation returned no output");
|
|
446
|
-
return output;
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
//#endregion
|
|
450
|
-
//#region src/adapters/openai-images.ts
|
|
451
|
-
const REQUEST_TIMEOUT_MS = 3e5;
|
|
452
|
-
async function openAiImagesAdapter(input) {
|
|
453
|
-
const prompt = mergeTextBlocks(input.declaration, input.request.content);
|
|
454
|
-
if (!prompt) throw new GenerationValidationError("Prompt text is required");
|
|
455
|
-
const images = await Promise.all(input.request.content.filter((block) => block.type === "image").map((block) => input.context.resolveSource(block.source)));
|
|
456
|
-
const payload = {
|
|
457
|
-
model: input.declaration.model,
|
|
458
|
-
prompt,
|
|
459
|
-
...input.parameters
|
|
460
|
-
};
|
|
461
|
-
if (images.length > 0) payload.image = images;
|
|
462
|
-
const response = await fetchWithTimeout(input.context.fetch, joinUrl(input.context.baseUrl, "/v1/images/generations"), {
|
|
463
|
-
method: "POST",
|
|
464
|
-
headers: {
|
|
465
|
-
Authorization: `Bearer ${input.context.apiKey}`,
|
|
466
|
-
"Content-Type": "application/json"
|
|
467
|
-
},
|
|
468
|
-
body: JSON.stringify(payload)
|
|
469
|
-
}, REQUEST_TIMEOUT_MS);
|
|
470
|
-
if (!response.ok) {
|
|
471
|
-
const body = await response.text().catch(() => response.statusText);
|
|
472
|
-
throw new GenerationProviderError("Image generation provider request failed", {
|
|
473
|
-
status: response.status,
|
|
474
|
-
body
|
|
475
|
-
});
|
|
476
|
-
}
|
|
477
|
-
const raw = await response.json();
|
|
478
|
-
const output = [];
|
|
479
|
-
for (const item of raw.data ?? []) {
|
|
480
|
-
if (typeof item.url === "string" && item.url) output.push({
|
|
481
|
-
type: "image",
|
|
482
|
-
source: {
|
|
483
|
-
type: "url",
|
|
484
|
-
url: item.url
|
|
485
|
-
}
|
|
486
|
-
});
|
|
487
|
-
if (typeof item.revised_prompt === "string" && item.revised_prompt.trim()) output.push({
|
|
488
|
-
type: "text",
|
|
489
|
-
text: item.revised_prompt,
|
|
490
|
-
meta: { role: "revised_prompt" }
|
|
491
|
-
});
|
|
492
|
-
}
|
|
493
|
-
return output;
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
//#endregion
|
|
497
|
-
//#region src/adapters/index.ts
|
|
498
|
-
const builtinGenerationAdapters = {
|
|
499
|
-
"ark.videoGenerations": arkVideoGenerationsAdapter,
|
|
500
|
-
"gemini.generateContent": geminiGenerateContentAdapter,
|
|
501
|
-
"openai.images": openAiImagesAdapter
|
|
502
|
-
};
|
|
503
|
-
function getGenerationAdapter(type, adapters = {}) {
|
|
504
|
-
const adapter = adapters[type] ?? builtinGenerationAdapters[type];
|
|
505
|
-
if (!adapter) throw new GenerationUnsupportedAdapterError(type);
|
|
506
|
-
return adapter;
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
//#endregion
|
|
510
|
-
//#region src/source.ts
|
|
511
|
-
const defaultGenerationSourceResolver = (source) => {
|
|
512
|
-
switch (source.type) {
|
|
513
|
-
case "url": return source.url;
|
|
514
|
-
case "base64": return `data:${source.mediaType};base64,${source.data}`;
|
|
515
|
-
}
|
|
516
|
-
};
|
|
517
|
-
|
|
518
|
-
//#endregion
|
|
519
|
-
//#region src/client.ts
|
|
520
|
-
const DEFAULT_BASE_URL = "https://router.neta.art";
|
|
521
|
-
function resolveModels(options) {
|
|
522
|
-
const models = [...options.includeBuiltinModels ?? !options.models ? builtinGenerationModels : [], ...options.models ?? []];
|
|
523
|
-
const byModel = /* @__PURE__ */ new Map();
|
|
524
|
-
for (const model of models) byModel.set(model.model, cloneJson(model));
|
|
525
|
-
return [...byModel.values()].sort((a, b) => a.model.localeCompare(b.model));
|
|
526
|
-
}
|
|
527
|
-
function createGenerationClient(options = {}) {
|
|
528
|
-
const models = resolveModels(options);
|
|
529
|
-
const byModel = new Map(models.map((declaration) => [declaration.model, declaration]));
|
|
530
|
-
const fetchFn = options.fetch ?? globalThis.fetch;
|
|
531
|
-
if (!fetchFn) throw new GenerationConfigError("A fetch implementation is required");
|
|
532
|
-
function requireModel(model) {
|
|
533
|
-
const declaration = byModel.get(model);
|
|
534
|
-
if (!declaration) throw new GenerationConfigError(`Generation model is unavailable: ${model}`);
|
|
535
|
-
return declaration;
|
|
536
|
-
}
|
|
537
|
-
return {
|
|
538
|
-
validate(request) {
|
|
539
|
-
const declaration = requireModel(request.model);
|
|
540
|
-
validateGenerationContent(declaration, request.content);
|
|
541
|
-
const parameters = resolveGenerationParameters(declaration, request.parameters);
|
|
542
|
-
return {
|
|
543
|
-
declaration: cloneJson(declaration),
|
|
544
|
-
request: cloneJson(request),
|
|
545
|
-
parameters
|
|
546
|
-
};
|
|
547
|
-
},
|
|
548
|
-
async generate(request) {
|
|
549
|
-
const resolved = this.validate(request);
|
|
550
|
-
const apiKey = request.apiKey ?? options.apiKey;
|
|
551
|
-
if (!apiKey) throw new GenerationConfigError("apiKey is required");
|
|
552
|
-
return getGenerationAdapter(resolved.declaration.adapter.type, options.adapters)({
|
|
553
|
-
...resolved,
|
|
554
|
-
context: {
|
|
555
|
-
apiKey,
|
|
556
|
-
baseUrl: request.baseUrl ?? options.baseUrl ?? DEFAULT_BASE_URL,
|
|
557
|
-
fetch: fetchFn,
|
|
558
|
-
resolveSource: options.sourceResolver ?? defaultGenerationSourceResolver
|
|
559
|
-
}
|
|
560
|
-
});
|
|
561
|
-
},
|
|
562
|
-
listModels() {
|
|
563
|
-
return cloneJson(models);
|
|
564
|
-
},
|
|
565
|
-
getModel(model) {
|
|
566
|
-
const declaration = byModel.get(model);
|
|
567
|
-
return declaration ? cloneJson(declaration) : null;
|
|
568
|
-
},
|
|
569
|
-
stringifyModelConfig(model, stringifyOptions = {}) {
|
|
570
|
-
return stringifyGenerationModelDeclaration(requireModel(model), stringifyOptions);
|
|
571
|
-
},
|
|
572
|
-
exportModelConfig(model, filePath) {
|
|
573
|
-
return writeGenerationModelDeclaration(requireModel(model), filePath);
|
|
574
|
-
},
|
|
575
|
-
exportModelConfigs(directory) {
|
|
576
|
-
return writeGenerationModelDeclarations(models, directory);
|
|
577
|
-
}
|
|
578
|
-
};
|
|
579
|
-
}
|
|
580
|
-
async function createGenerationClientFromFiles(filePaths, options = {}) {
|
|
581
|
-
const models = await readGenerationModelDeclarationsFromFiles(filePaths);
|
|
582
|
-
return createGenerationClient({
|
|
583
|
-
...options,
|
|
584
|
-
models,
|
|
585
|
-
includeBuiltinModels: options.includeBuiltinModels ?? true
|
|
586
|
-
});
|
|
587
|
-
}
|
|
588
|
-
async function createGenerationClientFromDirectory(directory, options = {}) {
|
|
589
|
-
const models = await readGenerationModelDeclarationsFromDirectory(directory);
|
|
590
|
-
return createGenerationClient({
|
|
591
|
-
...options,
|
|
592
|
-
models,
|
|
593
|
-
includeBuiltinModels: options.includeBuiltinModels ?? true
|
|
594
|
-
});
|
|
595
|
-
}
|
|
596
|
-
async function createGenerationClientFromFile(filePath, options = {}) {
|
|
597
|
-
return createGenerationClientFromFiles([filePath], options);
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
//#endregion
|
|
601
|
-
export { GenerationConfigError, GenerationError, GenerationProviderError, GenerationTimeoutError, GenerationUnsupportedAdapterError, GenerationValidationError, MODEL_SCHEMA, arkVideoGenerationsAdapter, builtinGenerationAdapters, builtinGenerationModels, createGenerationClient, createGenerationClientFromDirectory, createGenerationClientFromFile, createGenerationClientFromFiles, defaultGenerationSourceResolver, exportBuiltinModelConfig, exportBuiltinModelConfigs, geminiGenerateContentAdapter, getBuiltinGenerationModel, getGenerationAdapter, isGenerationModelDeclaration, listBuiltinGenerationModels, mergeGenerationModelDeclarations, mergeTextBlocks, openAiImagesAdapter, parseGenerationModelDeclaration, readGenerationModelDeclaration, readGenerationModelDeclarationsFromDirectory, readGenerationModelDeclarationsFromFiles, resolveGenerationParameters, stringifyBuiltinModelConfig, stringifyGenerationModelDeclaration, validateGenerationContent, writeGenerationModelDeclaration, writeGenerationModelDeclarations };
|
|
602
|
-
//# sourceMappingURL=index.js.map
|
|
4
|
+
export { GenerationConfigError, GenerationError, GenerationProviderError, GenerationTimeoutError, GenerationUnsupportedAdapterError, GenerationValidationError, MODEL_SCHEMA, arkVideoGenerationsAdapter, builtinGenerationAdapters, builtinGenerationModels, createGenerationClient, createGenerationClientFromDirectory, createGenerationClientFromFile, createGenerationClientFromFiles, defaultGenerationSourceResolver, exportBuiltinModelConfig, exportBuiltinModelConfigs, geminiGenerateContentAdapter, getBuiltinGenerationModel, getGenerationAdapter, isGenerationModelDeclaration, listBuiltinGenerationModels, mergeGenerationModelDeclarations, mergeTextBlocks, openAiImagesAdapter, parseGenerationModelDeclaration, readGenerationModelDeclaration, readGenerationModelDeclarationsFromDirectory, readGenerationModelDeclarationsFromFiles, resolveGenerationParameters, stringifyBuiltinModelConfig, stringifyGenerationModelDeclaration, validateGenerationContent, writeGenerationModelDeclaration, writeGenerationModelDeclarations };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neta-art/generation",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "A lightweight multimodal generation SDK with built-in model presets and adapter-based provider calls.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -34,26 +34,13 @@
|
|
|
34
34
|
}
|
|
35
35
|
},
|
|
36
36
|
"bin": {
|
|
37
|
-
"neta-generation": "
|
|
37
|
+
"neta-generation": "dist/cli/index.js"
|
|
38
38
|
},
|
|
39
39
|
"files": [
|
|
40
40
|
"dist",
|
|
41
41
|
"models",
|
|
42
42
|
"README.md"
|
|
43
43
|
],
|
|
44
|
-
"scripts": {
|
|
45
|
-
"build": "tsdown",
|
|
46
|
-
"example:basic-image": "node --env-file=.env --experimental-strip-types examples/basic-image.ts",
|
|
47
|
-
"example:image-editing": "node --env-file=.env --experimental-strip-types examples/image-editing.ts",
|
|
48
|
-
"example:text-to-video": "node --env-file=.env --experimental-strip-types examples/text-to-video.ts",
|
|
49
|
-
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
50
|
-
"test": "vitest run",
|
|
51
|
-
"test:watch": "vitest",
|
|
52
|
-
"lint": "biome check .",
|
|
53
|
-
"lint:fix": "biome check --write .",
|
|
54
|
-
"format": "biome format --write .",
|
|
55
|
-
"prepublishOnly": "pnpm lint && pnpm typecheck && pnpm test && pnpm build"
|
|
56
|
-
},
|
|
57
44
|
"dependencies": {
|
|
58
45
|
"yaml": "^2.8.3"
|
|
59
46
|
},
|
|
@@ -67,8 +54,20 @@
|
|
|
67
54
|
"engines": {
|
|
68
55
|
"node": ">=20.0.0"
|
|
69
56
|
},
|
|
70
|
-
"packageManager": "pnpm@10.32.1",
|
|
71
57
|
"publishConfig": {
|
|
72
|
-
"access": "public"
|
|
58
|
+
"access": "public",
|
|
59
|
+
"registry": "https://registry.npmjs.org/"
|
|
60
|
+
},
|
|
61
|
+
"scripts": {
|
|
62
|
+
"build": "tsdown",
|
|
63
|
+
"example:basic-image": "node --env-file=.env --experimental-strip-types examples/basic-image.ts",
|
|
64
|
+
"example:image-editing": "node --env-file=.env --experimental-strip-types examples/image-editing.ts",
|
|
65
|
+
"example:text-to-video": "node --env-file=.env --experimental-strip-types examples/text-to-video.ts",
|
|
66
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
67
|
+
"test": "vitest run",
|
|
68
|
+
"test:watch": "vitest",
|
|
69
|
+
"lint": "biome check .",
|
|
70
|
+
"lint:fix": "biome check --write .",
|
|
71
|
+
"format": "biome format --write ."
|
|
73
72
|
}
|
|
74
|
-
}
|
|
73
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"builtins-BuI-rf8X.d.ts","names":[],"sources":["../src/types.ts","../src/builtins.ts"],"sourcesContent":[],"mappings":"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"}
|