litevision 0.2.0 → 0.3.0
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 +7 -6
- package/dist/cli.js +1233 -824
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -12866,6 +12866,14 @@ function object(shape, params) {
|
|
|
12866
12866
|
};
|
|
12867
12867
|
return new ZodObject(def);
|
|
12868
12868
|
}
|
|
12869
|
+
function strictObject(shape, params) {
|
|
12870
|
+
return new ZodObject({
|
|
12871
|
+
type: "object",
|
|
12872
|
+
shape,
|
|
12873
|
+
catchall: never(),
|
|
12874
|
+
...normalizeParams(params)
|
|
12875
|
+
});
|
|
12876
|
+
}
|
|
12869
12877
|
function looseObject(shape, params) {
|
|
12870
12878
|
return new ZodObject({
|
|
12871
12879
|
type: "object",
|
|
@@ -21700,11 +21708,7 @@ import { readFile } from "node:fs/promises";
|
|
|
21700
21708
|
import { join as join2 } from "node:path";
|
|
21701
21709
|
|
|
21702
21710
|
// src/protocols/protocol.ts
|
|
21703
|
-
var aspectRatios = ["1:1", "16:9", "9:16", "4:3", "3:4", "3:2", "2:3"];
|
|
21704
21711
|
var modalities = ["image", "video"];
|
|
21705
|
-
function formatSize(size) {
|
|
21706
|
-
return `${size.width}x${size.height}`;
|
|
21707
|
-
}
|
|
21708
21712
|
|
|
21709
21713
|
class ProviderError extends Error {
|
|
21710
21714
|
kind;
|
|
@@ -21733,7 +21737,8 @@ var modelSchema = object({
|
|
|
21733
21737
|
protocol: string2().min(1),
|
|
21734
21738
|
modality: _enum(modalities).default("image"),
|
|
21735
21739
|
notes: string2().optional(),
|
|
21736
|
-
notesHistory: notesHistorySchema.optional()
|
|
21740
|
+
notesHistory: notesHistorySchema.optional(),
|
|
21741
|
+
standingOptions: record(string2(), unknown()).optional()
|
|
21737
21742
|
});
|
|
21738
21743
|
var configSchema = object({
|
|
21739
21744
|
providers: array(providerSchema).default([]),
|
|
@@ -22208,128 +22213,90 @@ function mp4Facts(bytes, view) {
|
|
|
22208
22213
|
return { ...size, ...seconds === undefined ? {} : { seconds: Math.round(seconds * 100) / 100 } };
|
|
22209
22214
|
}
|
|
22210
22215
|
|
|
22211
|
-
// src/protocols/
|
|
22212
|
-
function
|
|
22213
|
-
|
|
22214
|
-
const references = capabilities.maxReferences === 0 ? "no reference images" : `up to ${capabilities.maxReferences} reference image${capabilities.maxReferences === 1 ? "" : "s"}`;
|
|
22215
|
-
return `aspect ratios ${ratios}; up to ${capabilities.maxImages} image${capabilities.maxImages === 1 ? "" : "s"} per Job; ${references}.`;
|
|
22216
|
+
// src/protocols/parameter-guide.ts
|
|
22217
|
+
function guideSummary(guide) {
|
|
22218
|
+
return `Parameter Guide for ${guide.family} (checked ${guide.checked}): ${guide.parameters.map((parameter) => parameter.name).join(", ")}.`;
|
|
22216
22219
|
}
|
|
22217
|
-
function
|
|
22218
|
-
const
|
|
22219
|
-
|
|
22220
|
-
|
|
22221
|
-
|
|
22220
|
+
function guideText(guide) {
|
|
22221
|
+
const lines = [
|
|
22222
|
+
`Parameter Guide for ${guide.family}, checked ${guide.checked} (sources: ${guide.sources.join(", ")}):`,
|
|
22223
|
+
...guide.parameters.map((parameter) => `- ${parameter.name}: ${parameter.values} Default: ${parameter.default}. Price: ${parameter.price}`),
|
|
22224
|
+
...(guide.notes ?? []).map((note) => `Note: ${note}`)
|
|
22225
|
+
];
|
|
22226
|
+
return lines.join(`
|
|
22227
|
+
`);
|
|
22222
22228
|
}
|
|
22223
|
-
function
|
|
22224
|
-
|
|
22225
|
-
|
|
22226
|
-
|
|
22227
|
-
|
|
22228
|
-
|
|
22229
|
-
|
|
22229
|
+
function guideOutput(guide) {
|
|
22230
|
+
return {
|
|
22231
|
+
family: guide.family,
|
|
22232
|
+
checked: guide.checked,
|
|
22233
|
+
sources: guide.sources,
|
|
22234
|
+
parameters: guide.parameters.map((parameter) => ({ ...parameter })),
|
|
22235
|
+
notes: guide.notes ?? []
|
|
22236
|
+
};
|
|
22230
22237
|
}
|
|
22231
|
-
function
|
|
22232
|
-
const
|
|
22233
|
-
return
|
|
22238
|
+
function guideFor(entries, modelId) {
|
|
22239
|
+
const bare = modelId.replace(/^.*\//, "");
|
|
22240
|
+
return entries.find((entry) => entry.matches.test(modelId) || entry.matches.test(bare))?.guide;
|
|
22234
22241
|
}
|
|
22235
|
-
|
|
22236
|
-
const
|
|
22237
|
-
const
|
|
22238
|
-
if (
|
|
22239
|
-
|
|
22240
|
-
}
|
|
22241
|
-
if (parameters.references.length > 0 && capabilities.maxReferences === 0) {
|
|
22242
|
-
throw unsupported(`Model "${model.alias}" cannot take reference images.`);
|
|
22243
|
-
}
|
|
22244
|
-
if (parameters.references.length > capabilities.maxReferences) {
|
|
22245
|
-
throw unsupported(`Model "${model.alias}" cannot take ${parameters.references.length} reference images.`);
|
|
22246
|
-
}
|
|
22247
|
-
const reserved = Object.keys(parameters.providerOptions).filter((key) => adapter.reservedOptions.includes(key));
|
|
22248
|
-
if (reserved.length > 0) {
|
|
22249
|
-
throw new ConfigError(`provider_options may not set ${reserved.map((key) => `\`${key}\``).join(", ")}: litevision sets ${reserved.length === 1 ? "it" : "them"} from the \`generate_image\` parameters. Use those instead.`);
|
|
22250
|
-
}
|
|
22251
|
-
const { size, adjusted } = closestSize(capabilities.sizes, parameters.aspectRatio);
|
|
22252
|
-
const references = await readReferences(parameters.references, cwd);
|
|
22242
|
+
function fieldsToRead(adapter, modelId, modality) {
|
|
22243
|
+
const guide = adapter.parameterGuide?.(modelId, modality);
|
|
22244
|
+
const fallback = (modality === "video" ? adapter.requestFields.video : adapter.requestFields.image) ?? { shape: [] };
|
|
22245
|
+
if (guide === undefined)
|
|
22246
|
+
return fallback;
|
|
22253
22247
|
return {
|
|
22254
|
-
|
|
22255
|
-
|
|
22256
|
-
|
|
22257
|
-
size,
|
|
22258
|
-
references,
|
|
22259
|
-
providerOptions: parameters.providerOptions,
|
|
22260
|
-
model: { capabilities, ...described.parameterSchema === undefined ? {} : { parameterSchema: described.parameterSchema } }
|
|
22261
|
-
},
|
|
22262
|
-
requestedAspectRatio: parameters.aspectRatio,
|
|
22263
|
-
aspectRatioAdjusted: adjusted
|
|
22248
|
+
shape: guide.shapeFields,
|
|
22249
|
+
...guide.durationField === undefined ? {} : { duration: guide.durationField },
|
|
22250
|
+
...fallback.resolution === undefined ? {} : { resolution: fallback.resolution }
|
|
22264
22251
|
};
|
|
22265
22252
|
}
|
|
22266
|
-
function
|
|
22267
|
-
|
|
22268
|
-
|
|
22269
|
-
|
|
22270
|
-
|
|
22271
|
-
|
|
22272
|
-
|
|
22273
|
-
|
|
22274
|
-
const
|
|
22275
|
-
|
|
22276
|
-
|
|
22277
|
-
|
|
22278
|
-
|
|
22279
|
-
|
|
22280
|
-
|
|
22281
|
-
|
|
22282
|
-
|
|
22283
|
-
|
|
22284
|
-
|
|
22285
|
-
|
|
22286
|
-
|
|
22287
|
-
|
|
22288
|
-
|
|
22289
|
-
|
|
22290
|
-
|
|
22291
|
-
|
|
22292
|
-
|
|
22293
|
-
|
|
22294
|
-
|
|
22295
|
-
|
|
22296
|
-
|
|
22297
|
-
|
|
22298
|
-
if (parameters.references.length > 0 && capabilities.maxReferences === 0)
|
|
22299
|
-
throw unsupported(`Model "${model.alias}" cannot take reference images.`);
|
|
22300
|
-
if (parameters.references.length > capabilities.maxReferences) {
|
|
22301
|
-
throw unsupported(`Model "${model.alias}" cannot take ${parameters.references.length} reference images.`);
|
|
22302
|
-
}
|
|
22303
|
-
const reservedOptions = adapter.reservedVideoOptions ?? [];
|
|
22304
|
-
const reserved = Object.keys(parameters.providerOptions).filter((key) => reservedOptions.includes(key));
|
|
22305
|
-
if (reserved.length > 0) {
|
|
22306
|
-
throw new ConfigError(`provider_options may not set ${reserved.map((key) => `\`${key}\``).join(", ")}: litevision sets ${reserved.length === 1 ? "it" : "them"} from the \`generate_video\` parameters. Use those instead.`);
|
|
22253
|
+
function valueAt(body, path) {
|
|
22254
|
+
return path.split(".").reduce((value, key) => typeof value === "object" && value !== null ? value[key] : undefined, body);
|
|
22255
|
+
}
|
|
22256
|
+
var positive = (value) => {
|
|
22257
|
+
const number = typeof value === "string" && value.trim() !== "" ? Number(value) : value;
|
|
22258
|
+
return typeof number === "number" && Number.isFinite(number) && number > 0 ? number : undefined;
|
|
22259
|
+
};
|
|
22260
|
+
function parseShape(text) {
|
|
22261
|
+
const match = /^\s*(\d+(?:\.\d+)?)\s*([x×:])\s*(\d+(?:\.\d+)?)\s*$/i.exec(text);
|
|
22262
|
+
if (match === null)
|
|
22263
|
+
return;
|
|
22264
|
+
const width = Number(match[1]);
|
|
22265
|
+
const height = Number(match[3]);
|
|
22266
|
+
if (!(width > 0 && height > 0))
|
|
22267
|
+
return;
|
|
22268
|
+
return { label: `${match[1]}${match[2] === ":" ? ":" : "x"}${match[3]}`, ratio: width / height };
|
|
22269
|
+
}
|
|
22270
|
+
function readShape(fields, body) {
|
|
22271
|
+
for (const field of fields) {
|
|
22272
|
+
if (typeof field === "string") {
|
|
22273
|
+
const value = valueAt(body, field);
|
|
22274
|
+
if (value === undefined)
|
|
22275
|
+
continue;
|
|
22276
|
+
return typeof value === "string" ? parseShape(value)?.label ?? null : null;
|
|
22277
|
+
}
|
|
22278
|
+
const width = valueAt(body, field.width);
|
|
22279
|
+
const height = valueAt(body, field.height);
|
|
22280
|
+
if (width === undefined && height === undefined)
|
|
22281
|
+
continue;
|
|
22282
|
+
const w = positive(width);
|
|
22283
|
+
const h = positive(height);
|
|
22284
|
+
return w === undefined || h === undefined ? null : `${w}x${h}`;
|
|
22307
22285
|
}
|
|
22308
|
-
|
|
22309
|
-
|
|
22310
|
-
|
|
22311
|
-
|
|
22312
|
-
|
|
22313
|
-
|
|
22314
|
-
|
|
22315
|
-
|
|
22316
|
-
|
|
22286
|
+
return null;
|
|
22287
|
+
}
|
|
22288
|
+
function readResolution(field, body) {
|
|
22289
|
+
const value = field === undefined ? undefined : valueAt(body, field);
|
|
22290
|
+
return typeof value === "string" && value.trim() !== "" ? value.trim() : null;
|
|
22291
|
+
}
|
|
22292
|
+
function readSeconds(field, body) {
|
|
22293
|
+
return field === undefined ? null : positive(valueAt(body, field)) ?? null;
|
|
22294
|
+
}
|
|
22295
|
+
function readRequested(fields, body, modality) {
|
|
22317
22296
|
return {
|
|
22318
|
-
|
|
22319
|
-
|
|
22320
|
-
|
|
22321
|
-
...tier.name === undefined ? {} : { resolution: tier.name },
|
|
22322
|
-
seconds,
|
|
22323
|
-
...firstFrame === undefined ? {} : { firstFrame },
|
|
22324
|
-
...lastFrame === undefined ? {} : { lastFrame },
|
|
22325
|
-
references,
|
|
22326
|
-
providerOptions: parameters.providerOptions
|
|
22327
|
-
},
|
|
22328
|
-
requestedAspectRatio: frameFacts === undefined ? parameters.aspectRatio ?? "16:9" : size.aspectRatio,
|
|
22329
|
-
aspectRatioAdjusted: adjusted,
|
|
22330
|
-
followsFirstFrame: frameFacts !== undefined || firstFrame !== undefined && adjusted,
|
|
22331
|
-
...parameters.duration === undefined ? {} : { requestedDuration: parameters.duration },
|
|
22332
|
-
durationAdjusted: parameters.duration !== undefined && seconds !== parameters.duration
|
|
22297
|
+
shape: readShape(fields.shape, body),
|
|
22298
|
+
resolution: readResolution(fields.resolution, body),
|
|
22299
|
+
...modality === "video" ? { seconds: readSeconds(fields.duration, body) } : {}
|
|
22333
22300
|
};
|
|
22334
22301
|
}
|
|
22335
22302
|
|
|
@@ -22389,6 +22356,243 @@ function httpProviderError(providerId, response) {
|
|
|
22389
22356
|
return providerError(classifyProviderError(response), providerId, detail);
|
|
22390
22357
|
}
|
|
22391
22358
|
|
|
22359
|
+
// src/protocols/guides/apimart.ts
|
|
22360
|
+
var checked = "2026-11-05";
|
|
22361
|
+
var page = (path) => `https://docs.apimart.ai/en/api-reference/images/${path}`;
|
|
22362
|
+
var pricing = "https://apimart.ai/pricing";
|
|
22363
|
+
var pricingNote = "APIMart publishes prices only on its pricing page, which depends on the account tier; figures here are approximate.";
|
|
22364
|
+
var references = (count) => ({
|
|
22365
|
+
name: "image_urls",
|
|
22366
|
+
values: `${count}; litevision uploads \`references\` through APIMart's upload endpoint and fills this with the links.`,
|
|
22367
|
+
default: "none",
|
|
22368
|
+
price: "not documented."
|
|
22369
|
+
});
|
|
22370
|
+
var count = (range) => ({
|
|
22371
|
+
name: "n",
|
|
22372
|
+
values: `${range} images per task; litevision sends \`n\` here, or one task per image where the model makes one at a time.`,
|
|
22373
|
+
default: "1",
|
|
22374
|
+
price: "each image is billed."
|
|
22375
|
+
});
|
|
22376
|
+
var fifteenRatios = "1:1, 3:2, 2:3, 4:3, 3:4, 5:4, 4:5, 16:9, 9:16, 2:1, 1:2, 3:1, 1:3, 21:9, 9:21";
|
|
22377
|
+
var exactPixels = "or exact WIDTHxHEIGHT under OpenAI's rule (multiples of 16, at most 3840 a side and 3:1, 655,360–8,294,400 pixels)";
|
|
22378
|
+
var gptTiers = "1k, 2k, 4k (16:9 is 1536x864, 2048x1152 or 3840x2160).";
|
|
22379
|
+
var gptImage25 = {
|
|
22380
|
+
family: "GPT Image 2.5 / 2.6 on APIMart",
|
|
22381
|
+
parameters: [
|
|
22382
|
+
{ name: "size", values: `auto, ${fifteenRatios}, ${exactPixels}.`, default: "auto", price: "set by resolution." },
|
|
22383
|
+
{ name: "resolution", values: `${gptTiers} Ignored for exact pixels.`, default: "1k", price: "billed by output tokens, which grow with the tier and the quality." },
|
|
22384
|
+
{ name: "quality", values: "auto, low, medium, high, xhigh, max.", default: "auto", price: "at 1024x1024 about $0.006 low to $0.21 max ($30 per 1M output tokens)." },
|
|
22385
|
+
{ name: "background", values: "auto, opaque, transparent.", default: "auto", price: "no effect." },
|
|
22386
|
+
{ name: "moderation", values: "auto, low.", default: "low", price: "no effect." },
|
|
22387
|
+
references("up to 16 public links"),
|
|
22388
|
+
count("1–4")
|
|
22389
|
+
],
|
|
22390
|
+
sources: [page("gpt-image-2.5/generation"), page("gpt-image-2.6/generation"), pricing],
|
|
22391
|
+
checked,
|
|
22392
|
+
notes: ["GPT Image 2.6 adds complexity, markup_url, GIF and SVG output.", pricingNote],
|
|
22393
|
+
shapeFields: ["size"]
|
|
22394
|
+
};
|
|
22395
|
+
var gptImage2 = {
|
|
22396
|
+
family: "GPT Image 2 on APIMart",
|
|
22397
|
+
parameters: [
|
|
22398
|
+
{ name: "size", values: `auto (= 1:1), ${fifteenRatios}, ${exactPixels}.`, default: "1:1", price: "set by resolution." },
|
|
22399
|
+
{ name: "resolution", values: gptTiers, default: "1k", price: "about $0.0085 at 1K, $0.015 at 2K, $0.022 at 4K per image." },
|
|
22400
|
+
references("up to 15 links or data URIs (gpt-image-2-official: up to 16 public links)"),
|
|
22401
|
+
count("1 (gpt-image-2-official: 1–4)")
|
|
22402
|
+
],
|
|
22403
|
+
sources: [page("gpt-image-2/generation"), page("gpt-image-2/official"), pricing],
|
|
22404
|
+
checked,
|
|
22405
|
+
notes: [
|
|
22406
|
+
"Without size, the output takes the input image's resolution.",
|
|
22407
|
+
"The gpt-image-2 page gives two pixel sizes for many 1k ratios (1:1 is 1024x1024 or 1254x1254), so the exact 1k output is uncertain.",
|
|
22408
|
+
pricingNote
|
|
22409
|
+
],
|
|
22410
|
+
shapeFields: ["size"]
|
|
22411
|
+
};
|
|
22412
|
+
var gptImageExt = {
|
|
22413
|
+
family: "GPT Image 2.5 / 2.6 Ext on APIMart",
|
|
22414
|
+
parameters: [
|
|
22415
|
+
{ name: "size", values: "auto, 1:1, 16:9, 9:16, 4:3, 3:4, 3:2, 2:3, 5:4, 4:5, 21:9; no exact pixels.", default: "auto", price: "no effect." },
|
|
22416
|
+
{ name: "resolution", values: "1K, 2K, 4K (gpt-image-2.6-ext: only 1k today).", default: "1K", price: "about $0.0085 at 1K, $0.03 at 2K, $0.04 at 4K per image." },
|
|
22417
|
+
{ name: "version", values: "flare, sunburst.", default: "the Provider picks", price: "billed by version × resolution × images delivered." },
|
|
22418
|
+
references("up to 16 links or data URIs"),
|
|
22419
|
+
count("1–4 (gpt-image-2.6-ext: 1)")
|
|
22420
|
+
],
|
|
22421
|
+
sources: [page("gpt-image-2.5-ext/generation"), page("gpt-image-2.6-ext/generation"), pricing],
|
|
22422
|
+
checked,
|
|
22423
|
+
notes: [pricingNote],
|
|
22424
|
+
shapeFields: ["size"]
|
|
22425
|
+
};
|
|
22426
|
+
var gptImage1 = {
|
|
22427
|
+
family: "GPT Image 1 / 1.5 on APIMart",
|
|
22428
|
+
parameters: [
|
|
22429
|
+
{ name: "size", values: "1:1, 3:2, 2:3 (1024x1024, 1536x1024, 1024x1536).", default: "1:1", price: "not documented." },
|
|
22430
|
+
{ name: "quality", values: "auto, low, medium, high.", default: "auto", price: "not documented." },
|
|
22431
|
+
{ name: "background", values: "auto, opaque, transparent.", default: "auto", price: "no effect." },
|
|
22432
|
+
references("up to 15 public links"),
|
|
22433
|
+
count("1–4")
|
|
22434
|
+
],
|
|
22435
|
+
sources: [page("gpt-image-1/generation")],
|
|
22436
|
+
checked,
|
|
22437
|
+
notes: ["OpenAI shut gpt-image-1 down on 2026-10-23."],
|
|
22438
|
+
shapeFields: ["size"]
|
|
22439
|
+
};
|
|
22440
|
+
var geminiRatios10 = "auto, 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9";
|
|
22441
|
+
var gemini = (family, path, ratios, tiers, refs, n, notes) => ({
|
|
22442
|
+
family,
|
|
22443
|
+
parameters: [
|
|
22444
|
+
{ name: "size", values: `${ratios}.`, default: "auto: the model decides, and image-to-image follows the reference", price: "no effect." },
|
|
22445
|
+
{ name: "resolution", values: tiers, default: "1K", price: "higher tiers cost more." },
|
|
22446
|
+
references(refs),
|
|
22447
|
+
count(n)
|
|
22448
|
+
],
|
|
22449
|
+
sources: [page(path), pricing],
|
|
22450
|
+
checked,
|
|
22451
|
+
notes: [...notes, pricingNote],
|
|
22452
|
+
shapeFields: ["size"]
|
|
22453
|
+
});
|
|
22454
|
+
var seedream = (family, path, ratios, tiers, defaultTier, refs, n, notes, defaultSize = "1:1") => ({
|
|
22455
|
+
family,
|
|
22456
|
+
parameters: [
|
|
22457
|
+
{ name: "size", values: `${ratios}.`, default: defaultSize, price: "no effect." },
|
|
22458
|
+
{ name: "resolution", values: tiers, default: defaultTier, price: "higher tiers cost more." },
|
|
22459
|
+
references(refs),
|
|
22460
|
+
count(n)
|
|
22461
|
+
],
|
|
22462
|
+
sources: [page(path), pricing],
|
|
22463
|
+
checked,
|
|
22464
|
+
notes: [...notes, pricingNote],
|
|
22465
|
+
shapeFields: ["size"]
|
|
22466
|
+
});
|
|
22467
|
+
var seedreamRatios = "1:1, 4:3, 3:4, 16:9, 9:16, 3:2, 2:3, 2:1, 1:2, 21:9, 9:21, auto (needs reference images)";
|
|
22468
|
+
var flux2 = {
|
|
22469
|
+
family: "FLUX.2 on APIMart",
|
|
22470
|
+
parameters: [
|
|
22471
|
+
{ name: "size", values: "1:1, 4:3, 3:4, 16:9, 9:16, 3:2, 2:3, 21:9, 9:21, auto, or WIDTHxHEIGHT.", default: "1:1", price: "set by resolution." },
|
|
22472
|
+
{ name: "resolution", values: "1MP, 2MP, 3MP, 4MP (1K, 2K, 4K are taken as 2MP, 3MP, 4MP; 16:9 is 1344x752 at 1MP, 2720x1536 at 4MP).", default: "2MP", price: "flux-2-pro about $0.024, $0.036, $0.048, $0.06." },
|
|
22473
|
+
{ name: "width", values: "with height, in place of size: at least 64 each, at most 4,194,304 pixels.", default: "none", price: "by megapixels." },
|
|
22474
|
+
{ name: "height", values: "with width, as above.", default: "none", price: "by megapixels." },
|
|
22475
|
+
{ name: "safety_tolerance", values: "0–5.", default: "the Provider picks", price: "no effect." },
|
|
22476
|
+
{ name: "seed", values: "any integer.", default: "random", price: "no effect." },
|
|
22477
|
+
references("up to 8"),
|
|
22478
|
+
count("1")
|
|
22479
|
+
],
|
|
22480
|
+
sources: [page("flux-2/generation"), pricing],
|
|
22481
|
+
checked,
|
|
22482
|
+
notes: ["steps (1–50) and guidance (1.5–10) apply to flux-2-flex only.", pricingNote],
|
|
22483
|
+
shapeFields: [{ width: "width", height: "height" }, "size"]
|
|
22484
|
+
};
|
|
22485
|
+
var fluxKontext = {
|
|
22486
|
+
family: "FLUX.1 Kontext on APIMart",
|
|
22487
|
+
parameters: [
|
|
22488
|
+
{ name: "size", values: "1:1, 4:3, 3:4, 16:9, 9:16, 3:2, 2:3, 21:9, 9:21, auto; about a megapixel (16:9 is 1392x752). Pixels are mapped to the nearest ratio; width and height fail.", default: "1:1", price: "not documented." },
|
|
22489
|
+
references("up to 4"),
|
|
22490
|
+
count("1")
|
|
22491
|
+
],
|
|
22492
|
+
sources: [page("flux-kontext/generation")],
|
|
22493
|
+
checked,
|
|
22494
|
+
shapeFields: ["size"]
|
|
22495
|
+
};
|
|
22496
|
+
var flux3 = {
|
|
22497
|
+
family: "FLUX 3 Image on APIMart",
|
|
22498
|
+
parameters: [
|
|
22499
|
+
{ name: "aspect_ratio", values: "21:9, 2:1, 16:9, 3:2, 7:5, 4:3, 5:4, 1:1, 4:5, 3:4, 5:7, 2:3, 9:16, 1:2, 9:21, auto; also taken as size. Pixels are rejected.", default: "auto", price: "no effect." },
|
|
22500
|
+
{ name: "resolution", values: "768sq, 1k, 1.5k, 2k, 4k (about 0.6, 1, 2, 4 and 16 MP).", default: "1k", price: "about $0.033, $0.038, $0.056, $0.08, $0.49." },
|
|
22501
|
+
{ name: "safety_tolerance", values: "0–4.", default: "the Provider picks", price: "no effect." },
|
|
22502
|
+
references("up to 10"),
|
|
22503
|
+
count("1")
|
|
22504
|
+
],
|
|
22505
|
+
sources: [page("flux-3-image/generation"), pricing],
|
|
22506
|
+
checked,
|
|
22507
|
+
notes: ["seed, steps, guidance and negative_prompt are rejected.", pricingNote],
|
|
22508
|
+
shapeFields: ["aspect_ratio", "size"]
|
|
22509
|
+
};
|
|
22510
|
+
var entries = [
|
|
22511
|
+
{ matches: /^gpt-image-2\.[56]-ext$/, guide: gptImageExt },
|
|
22512
|
+
{ matches: /^gpt-image-2\.[56]-(flare|sunburst)$/, guide: gptImage25 },
|
|
22513
|
+
{ matches: /^gpt-image-2(-ext|-official)?$/, guide: gptImage2 },
|
|
22514
|
+
{ matches: /^gpt-image-1(\.5)?(-official)?$/, guide: gptImage1 },
|
|
22515
|
+
{
|
|
22516
|
+
matches: /^gemini-nano-banana-2\.1(-ext)?$/,
|
|
22517
|
+
guide: gemini("Nano Banana 2.1 on APIMart", "gemini-nano-banana-2.1/generation", `${geminiRatios10}, 1:4, 4:1, 1:8, 8:1`, "1K, 2K, 4K; 0.5K and 512 are rejected, an unknown tier is taken as 1K.", "no fixed count, links or data URIs, up to 20 MB each", "1–4 (Ext: 1)", [])
|
|
22518
|
+
},
|
|
22519
|
+
{
|
|
22520
|
+
matches: /^(gemini-3\.1-flash-image-preview(-official)?|nano-banana-2(-ext)?)$/,
|
|
22521
|
+
guide: gemini("Nano Banana 2 (Gemini 3.1 Flash Image) on APIMart", "gemini-3.1-flash/generation", `${geminiRatios10}, 1:4, 4:1, 1:8, 8:1`, "0.5K, 1K, 2K, 4K.", "up to 14", "1", ["APIMart calls the small tier 0.5K where Google's own API says 512."])
|
|
22522
|
+
},
|
|
22523
|
+
{
|
|
22524
|
+
matches: /^(gemini-3\.1-flash-lite-image(-ext)?|nano-banana-2-lite)$/,
|
|
22525
|
+
guide: gemini("Nano Banana 2 Lite on APIMart", "gemini-3.1-flash/generation-lite", geminiRatios10, "1K only; 2K, 4K and 0.5K are silently taken as 1K.", "up to 14", "1–4", [])
|
|
22526
|
+
},
|
|
22527
|
+
{
|
|
22528
|
+
matches: /^(gemini-3-pro-image-preview(-official)?|nano-banana-pro(-ext)?)$/,
|
|
22529
|
+
guide: gemini("Nano Banana Pro on APIMart", "gemini-3-pro/generation", geminiRatios10, "1K, 2K, 4K.", "up to 14", "1", [
|
|
22530
|
+
"With size auto and no reference, the docs say the result is 1:1 or 16:9."
|
|
22531
|
+
])
|
|
22532
|
+
},
|
|
22533
|
+
{
|
|
22534
|
+
matches: /^(gemini-2\.5-flash-image-preview(-official)?|nano-banana(-ext)?)$/,
|
|
22535
|
+
guide: gemini("Nano Banana on APIMart", "gemini-2.5-flash/generation", geminiRatios10, "1K only.", "up to 14", "1", [
|
|
22536
|
+
"APIMart allows 14 reference images; Google says the model works best with up to 3.",
|
|
22537
|
+
"Prompts of at most 1000 characters."
|
|
22538
|
+
])
|
|
22539
|
+
},
|
|
22540
|
+
{
|
|
22541
|
+
matches: /^seedream-4[.-]0$/i,
|
|
22542
|
+
guide: seedream("Seedream 4.0 on APIMart", "seedream-4/generation", seedreamRatios, "1K, 2K, 4K.", "2K", "references and n together at most 15", "1–15", [])
|
|
22543
|
+
},
|
|
22544
|
+
{
|
|
22545
|
+
matches: /^seedream-4[.-]5$/i,
|
|
22546
|
+
guide: seedream("Seedream 4.5 on APIMart", "seedream-4.5/generation", seedreamRatios, "2K, 4K; no 1K (16:9 is 2560x1440 at 2K, 5404x3040 at 4K).", "2K", "references and n together at most 15", "1–15", [
|
|
22547
|
+
"More than one image switches to sequential mode; text-to-image alone cannot make several."
|
|
22548
|
+
])
|
|
22549
|
+
},
|
|
22550
|
+
{
|
|
22551
|
+
matches: /^seedream-5[.-]0-lite$/i,
|
|
22552
|
+
guide: seedream("Seedream 5.0 Lite on APIMart", "seedream-5-lite/generation", seedreamRatios.replace(", 9:21", ""), "2K, 3K, 4K; no 1K.", "2K", "references and n together at most 15", "1–15", [
|
|
22553
|
+
'The page header says "2K / 3K" while the field lists 2K, 3K and 4K.'
|
|
22554
|
+
])
|
|
22555
|
+
},
|
|
22556
|
+
{
|
|
22557
|
+
matches: /^seedream-5[.-]0-(pro|flash)$/i,
|
|
22558
|
+
guide: {
|
|
22559
|
+
...seedream("Seedream 5.0 Pro / Flash on APIMart", "seedream-5-0-pro/generation", "auto, 1:1, 4:3, 3:4, 16:9, 9:16, 3:2, 2:3, 2:1, 1:2, 21:9, or exact pixels (921,600–4,624,220 pixels, ratio 1/16–16)", "1K, 1.5K, 2K; 3K and 4K are rejected.", "1K", "up to 10, the first free and each further one a fixed surcharge", "1", ['1.5K costs the same as 1K. The Flash page says "also accepted: seedream-5.0-pro", probably a copy error.'], "auto"),
|
|
22560
|
+
sources: [page("seedream-5-0-pro/generation"), page("seedream-5-0-flash/generation"), pricing]
|
|
22561
|
+
}
|
|
22562
|
+
},
|
|
22563
|
+
{ matches: /^flux-2-(flex|pro|max)$/, guide: flux2 },
|
|
22564
|
+
{ matches: /^flux-kontext-(pro|max)$/, guide: fluxKontext },
|
|
22565
|
+
{ matches: /^flux-3-image$/, guide: flux3 }
|
|
22566
|
+
];
|
|
22567
|
+
var apimartGuide = (modelId) => guideFor(entries, modelId);
|
|
22568
|
+
|
|
22569
|
+
// src/protocols/multipart.ts
|
|
22570
|
+
import { randomUUID } from "node:crypto";
|
|
22571
|
+
function multipartBody(fields, files) {
|
|
22572
|
+
const boundary = `litevision-${randomUUID()}`;
|
|
22573
|
+
const encoder = new TextEncoder;
|
|
22574
|
+
const quote = (value) => value.replace(/["\r\n\\]/g, (char) => encodeURIComponent(char));
|
|
22575
|
+
const parts = [];
|
|
22576
|
+
for (const [name, value] of fields) {
|
|
22577
|
+
parts.push(encoder.encode(`--${boundary}\r
|
|
22578
|
+
Content-Disposition: form-data; name="${quote(name)}"\r
|
|
22579
|
+
\r
|
|
22580
|
+
${value}\r
|
|
22581
|
+
`));
|
|
22582
|
+
}
|
|
22583
|
+
for (const file of files) {
|
|
22584
|
+
parts.push(encoder.encode(`--${boundary}\r
|
|
22585
|
+
Content-Disposition: form-data; name="${quote(file.name)}"; filename="${quote(file.fileName)}"\r
|
|
22586
|
+
Content-Type: ${file.mimeType}\r
|
|
22587
|
+
\r
|
|
22588
|
+
`), file.bytes, encoder.encode(`\r
|
|
22589
|
+
`));
|
|
22590
|
+
}
|
|
22591
|
+
parts.push(encoder.encode(`--${boundary}--\r
|
|
22592
|
+
`));
|
|
22593
|
+
return { body: Buffer.concat(parts), contentType: `multipart/form-data; boundary=${boundary}` };
|
|
22594
|
+
}
|
|
22595
|
+
|
|
22392
22596
|
// src/protocols/send.ts
|
|
22393
22597
|
var maxRetries = 2;
|
|
22394
22598
|
async function sendToProvider(http, providerId, url, init, { context, poll = false }) {
|
|
@@ -22453,7 +22657,6 @@ function networkError(providerId, error, stage, poll, attempts) {
|
|
|
22453
22657
|
|
|
22454
22658
|
// src/protocols/apimart.ts
|
|
22455
22659
|
var docsBase = "https://docs.apimart.ai/en/api-reference/images";
|
|
22456
|
-
var maxImagesPerJob = 4;
|
|
22457
22660
|
var errorSchema = object({
|
|
22458
22661
|
error: object({
|
|
22459
22662
|
code: union([number2(), string2()]).nullish(),
|
|
@@ -22475,82 +22678,19 @@ var taskSchema = object({
|
|
|
22475
22678
|
})
|
|
22476
22679
|
});
|
|
22477
22680
|
var resumeSchema = object({ taskIds: array(string2()).min(1) });
|
|
22478
|
-
var sizes = (entries) => entries.map(([aspectRatio, width, height]) => ({ aspectRatio, width, height }));
|
|
22479
22681
|
var knownModels = [
|
|
22480
|
-
{
|
|
22481
|
-
|
|
22482
|
-
|
|
22483
|
-
|
|
22484
|
-
|
|
22485
|
-
|
|
22486
|
-
|
|
22487
|
-
|
|
22488
|
-
|
|
22489
|
-
|
|
22490
|
-
|
|
22491
|
-
["9:16", 864, 1536]
|
|
22492
|
-
]),
|
|
22493
|
-
maxReferences: 15,
|
|
22494
|
-
maxImages: maxImagesPerJob
|
|
22495
|
-
},
|
|
22496
|
-
batches: false
|
|
22497
|
-
},
|
|
22498
|
-
{
|
|
22499
|
-
matches: /^gpt-image-1(\.5)?(-official)?$/,
|
|
22500
|
-
docsPage: "gpt-image-1/generation",
|
|
22501
|
-
capabilities: {
|
|
22502
|
-
sizes: sizes([
|
|
22503
|
-
["1:1", 1024, 1024],
|
|
22504
|
-
["3:2", 1536, 1024],
|
|
22505
|
-
["2:3", 1024, 1536]
|
|
22506
|
-
]),
|
|
22507
|
-
maxReferences: 0,
|
|
22508
|
-
maxImages: maxImagesPerJob
|
|
22509
|
-
},
|
|
22510
|
-
batches: true
|
|
22511
|
-
},
|
|
22512
|
-
{
|
|
22513
|
-
matches: /^(gemini-3-pro-image-preview(-official)?|nano-banana-pro(-ext)?)$/,
|
|
22514
|
-
docsPage: "gemini-3-pro/generation",
|
|
22515
|
-
capabilities: {
|
|
22516
|
-
sizes: sizes([
|
|
22517
|
-
["1:1", 1024, 1024],
|
|
22518
|
-
["3:2", 1264, 848],
|
|
22519
|
-
["2:3", 848, 1264],
|
|
22520
|
-
["4:3", 1200, 896],
|
|
22521
|
-
["3:4", 896, 1200],
|
|
22522
|
-
["16:9", 1376, 768],
|
|
22523
|
-
["9:16", 768, 1376]
|
|
22524
|
-
]),
|
|
22525
|
-
maxReferences: 14,
|
|
22526
|
-
maxImages: maxImagesPerJob
|
|
22527
|
-
},
|
|
22528
|
-
batches: false
|
|
22529
|
-
},
|
|
22530
|
-
{
|
|
22531
|
-
matches: /^seedream-4[.-]5$/i,
|
|
22532
|
-
docsPage: "seedream-4.5/generation",
|
|
22533
|
-
capabilities: {
|
|
22534
|
-
sizes: sizes([
|
|
22535
|
-
["1:1", 2048, 2048],
|
|
22536
|
-
["3:2", 2496, 1664],
|
|
22537
|
-
["2:3", 1664, 2496],
|
|
22538
|
-
["4:3", 2304, 1728],
|
|
22539
|
-
["3:4", 1728, 2304],
|
|
22540
|
-
["16:9", 2560, 1440],
|
|
22541
|
-
["9:16", 1440, 2560]
|
|
22542
|
-
]),
|
|
22543
|
-
maxReferences: 14,
|
|
22544
|
-
maxImages: maxImagesPerJob
|
|
22545
|
-
},
|
|
22546
|
-
batches: false
|
|
22547
|
-
}
|
|
22682
|
+
{ matches: /^gpt-image-2-official$/, docsPage: "gpt-image-2/official", batches: true },
|
|
22683
|
+
{ matches: /^gpt-image-2(-ext)?$/, docsPage: "gpt-image-2/generation", batches: false },
|
|
22684
|
+
{ matches: /^gpt-image-2\.5-(flare|sunburst)$/, docsPage: "gpt-image-2.5/generation", batches: true },
|
|
22685
|
+
{ matches: /^gpt-image-2\.6-(flare|sunburst)$/, docsPage: "gpt-image-2.6/generation", batches: true },
|
|
22686
|
+
{ matches: /^gpt-image-2\.5-ext$/, docsPage: "gpt-image-2.5-ext/generation", batches: true },
|
|
22687
|
+
{ matches: /^gpt-image-2\.6-ext$/, docsPage: "gpt-image-2.6-ext/generation", batches: false },
|
|
22688
|
+
{ matches: /^gpt-image-1(\.5)?(-official)?$/, docsPage: "gpt-image-1/generation", batches: true },
|
|
22689
|
+
{ matches: /^gemini-nano-banana-2\.1$/, docsPage: "gemini-nano-banana-2.1/generation", batches: true },
|
|
22690
|
+
{ matches: /^(gemini-3\.1-flash-lite-image(-ext)?|nano-banana-2-lite)$/, docsPage: "gemini-3.1-flash/generation-lite", batches: true },
|
|
22691
|
+
{ matches: /^(gemini-3-pro-image-preview(-official)?|nano-banana-pro(-ext)?)$/, docsPage: "gemini-3-pro/generation", batches: false },
|
|
22692
|
+
{ matches: /^seedream-4[.-]5$/i, docsPage: "seedream-4.5/generation", batches: false }
|
|
22548
22693
|
];
|
|
22549
|
-
var unknownModel = {
|
|
22550
|
-
sizes: sizes([["1:1", 1024, 1024]]),
|
|
22551
|
-
maxReferences: 0,
|
|
22552
|
-
maxImages: maxImagesPerJob
|
|
22553
|
-
};
|
|
22554
22694
|
var known = (modelId) => knownModels.find((model) => model.matches.test(modelId));
|
|
22555
22695
|
var headers = (credential) => ({ authorization: `Bearer ${credential}` });
|
|
22556
22696
|
var baseUrlOf = (target) => target.baseUrl.replace(/\/+$/, "");
|
|
@@ -22566,6 +22706,26 @@ async function errorOf(response, providerId, options = []) {
|
|
|
22566
22706
|
options
|
|
22567
22707
|
});
|
|
22568
22708
|
}
|
|
22709
|
+
var uploadedSchema = object({ url: string2().min(1) });
|
|
22710
|
+
async function uploadReference(reference, target, http, context) {
|
|
22711
|
+
const { body, contentType } = multipartBody([], [{ name: "file", ...reference }]);
|
|
22712
|
+
const init = { method: "POST", headers: { ...headers(target.credential), "content-type": contentType }, body };
|
|
22713
|
+
const failed = (error) => new ProviderError(error instanceof ProviderError ? error.kind : "other", `Could not upload the reference image ${reference.fileName} to APIMart. ${error.message}`);
|
|
22714
|
+
let response;
|
|
22715
|
+
try {
|
|
22716
|
+
response = await sendToProvider(http, target.providerId, `${baseUrlOf(target)}/uploads/images`, init, { context });
|
|
22717
|
+
} catch (error) {
|
|
22718
|
+
throw error instanceof ProviderError ? failed(error) : error;
|
|
22719
|
+
}
|
|
22720
|
+
if (!response.ok)
|
|
22721
|
+
throw failed(await errorOf(response, target.providerId));
|
|
22722
|
+
const parsed = uploadedSchema.safeParse(await response.json().catch(() => {
|
|
22723
|
+
return;
|
|
22724
|
+
}));
|
|
22725
|
+
if (!parsed.success)
|
|
22726
|
+
throw failed(providerError("other", target.providerId, "its answer to the upload had no link."));
|
|
22727
|
+
return parsed.data.url;
|
|
22728
|
+
}
|
|
22569
22729
|
async function submitTask(body, options, target, http, context) {
|
|
22570
22730
|
const init = {
|
|
22571
22731
|
method: "POST",
|
|
@@ -22637,32 +22797,6 @@ function waitForTasks(taskIds, target, http, context, submissionFailures = []) {
|
|
|
22637
22797
|
}
|
|
22638
22798
|
};
|
|
22639
22799
|
}
|
|
22640
|
-
function capabilitiesFromSchema(schema, modelId) {
|
|
22641
|
-
const properties = object({
|
|
22642
|
-
properties: object({
|
|
22643
|
-
size: object({ enum: array(unknown()) }).optional(),
|
|
22644
|
-
image_urls: object({ maxItems: number2().int().nonnegative().optional() }).optional()
|
|
22645
|
-
})
|
|
22646
|
-
}).safeParse(schema);
|
|
22647
|
-
if (!properties.success)
|
|
22648
|
-
return;
|
|
22649
|
-
const { size, image_urls } = properties.data.properties;
|
|
22650
|
-
const ratios = aspectRatios.filter((ratio) => size?.enum.includes(ratio));
|
|
22651
|
-
if (ratios.length === 0)
|
|
22652
|
-
return;
|
|
22653
|
-
const builtIn = known(modelId)?.capabilities ?? unknownModel;
|
|
22654
|
-
return {
|
|
22655
|
-
sizes: ratios.map((ratio) => builtIn.sizes.find((candidate) => candidate.aspectRatio === ratio) ?? nominalSize(ratio)),
|
|
22656
|
-
maxReferences: image_urls === undefined ? 0 : image_urls.maxItems ?? Math.max(builtIn.maxReferences, 1),
|
|
22657
|
-
maxImages: maxImagesPerJob
|
|
22658
|
-
};
|
|
22659
|
-
}
|
|
22660
|
-
function nominalSize(aspectRatio) {
|
|
22661
|
-
const [width, height] = aspectRatio.split(":").map(Number);
|
|
22662
|
-
const scale = Math.sqrt(1024 * 1024 / (width * height));
|
|
22663
|
-
const round = (value) => Math.round(value * scale / 16) * 16;
|
|
22664
|
-
return { aspectRatio, width: round(width), height: round(height) };
|
|
22665
|
-
}
|
|
22666
22800
|
var schemaResponse = object({ parameters: object({ input_schema: unknown() }).optional() });
|
|
22667
22801
|
var modelListSchema = object({
|
|
22668
22802
|
data: array(object({ id: string2(), category: string2().optional() }))
|
|
@@ -22691,7 +22825,6 @@ async function fetchModelIds(target, http) {
|
|
|
22691
22825
|
return { ok: true, modelIds: ids.sort() };
|
|
22692
22826
|
}
|
|
22693
22827
|
var apimart = {
|
|
22694
|
-
builtInCapabilities: (modelId) => known(modelId)?.capabilities ?? unknownModel,
|
|
22695
22828
|
async fetchModelInfo(target, http) {
|
|
22696
22829
|
const response = await http(`${baseUrlOf(target)}/models/${encodeURIComponent(target.modelId)}/schema`, {
|
|
22697
22830
|
headers: headers(target.credential)
|
|
@@ -22703,24 +22836,23 @@ var apimart = {
|
|
|
22703
22836
|
const body = await response.json();
|
|
22704
22837
|
const item = schemaResponse.safeParse(body?.data ?? body);
|
|
22705
22838
|
const parameterSchema = item.success ? item.data.parameters?.input_schema : undefined;
|
|
22706
|
-
|
|
22707
|
-
return;
|
|
22708
|
-
const capabilities = capabilitiesFromSchema(parameterSchema, target.modelId);
|
|
22709
|
-
return { parameterSchema, ...capabilities === undefined ? {} : { capabilities } };
|
|
22839
|
+
return parameterSchema === undefined ? undefined : { parameterSchema };
|
|
22710
22840
|
},
|
|
22711
22841
|
docsUrl: (modelId) => {
|
|
22712
22842
|
const model = known(modelId);
|
|
22713
22843
|
return model === undefined ? "https://docs.apimart.ai/en" : `${docsBase}/${model.docsPage}`;
|
|
22714
22844
|
},
|
|
22715
|
-
|
|
22845
|
+
parameterGuide: (modelId, modality) => modality === "image" ? apimartGuide(modelId) : undefined,
|
|
22846
|
+
requestFields: { image: { shape: ["size"], resolution: "resolution" } },
|
|
22847
|
+
reservedOptions: ["model", "prompt", "image_urls"],
|
|
22716
22848
|
async submit(request, target, http, context) {
|
|
22717
22849
|
const batches = known(target.modelId)?.batches ?? false;
|
|
22850
|
+
const imageUrls = await Promise.all(request.references.map((reference) => uploadReference(reference, target, http, context)));
|
|
22718
22851
|
const body = {
|
|
22719
22852
|
model: target.modelId,
|
|
22720
22853
|
prompt: request.prompt,
|
|
22721
|
-
size: request.size.aspectRatio,
|
|
22722
22854
|
n: batches ? request.n : 1,
|
|
22723
|
-
...
|
|
22855
|
+
...imageUrls.length === 0 ? {} : { image_urls: imageUrls },
|
|
22724
22856
|
...request.providerOptions
|
|
22725
22857
|
};
|
|
22726
22858
|
const settled = await Promise.allSettled(Array.from({ length: batches ? 1 : request.n }, () => submitTask(body, Object.keys(request.providerOptions), target, http, context)));
|
|
@@ -22745,6 +22877,96 @@ var apimart = {
|
|
|
22745
22877
|
listModelIds: fetchModelIds
|
|
22746
22878
|
};
|
|
22747
22879
|
|
|
22880
|
+
// src/protocols/guides/gemini.ts
|
|
22881
|
+
var checked2 = "2026-11-05";
|
|
22882
|
+
var sources = [
|
|
22883
|
+
"https://ai.google.dev/gemini-api/docs/image-generation",
|
|
22884
|
+
"https://aistudio.google.com/api-reference/interactions-api/create-interaction",
|
|
22885
|
+
"https://ai.google.dev/gemini-api/docs/pricing"
|
|
22886
|
+
];
|
|
22887
|
+
var tenRatios = "1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9";
|
|
22888
|
+
var fourteenRatios = `${tenRatios}, 1:4, 4:1, 1:8, 8:1`;
|
|
22889
|
+
var ratio = (ratios) => ({
|
|
22890
|
+
name: "response_format.aspect_ratio",
|
|
22891
|
+
values: `${ratios}.`,
|
|
22892
|
+
default: "the shape of the input image, else 1:1",
|
|
22893
|
+
price: "no effect: every ratio of a tier costs the same."
|
|
22894
|
+
});
|
|
22895
|
+
var mimeType = { name: "response_format.mime_type", values: "image/jpeg.", default: "the Provider picks", price: "no effect." };
|
|
22896
|
+
var imageSize = (values, price) => ({
|
|
22897
|
+
name: "response_format.image_size",
|
|
22898
|
+
values: `${values} Uppercase K: a lowercase 1k is rejected.`,
|
|
22899
|
+
default: "1K",
|
|
22900
|
+
price
|
|
22901
|
+
});
|
|
22902
|
+
var shape = ["response_format.aspect_ratio"];
|
|
22903
|
+
var nanoBanana21 = {
|
|
22904
|
+
family: "Nano Banana 2.1 (gemini-nano-banana-2.1)",
|
|
22905
|
+
parameters: [
|
|
22906
|
+
ratio(fourteenRatios),
|
|
22907
|
+
imageSize("1K, 2K, 4K (16:9: 1376x768, 2752x1536, 5504x3072).", "about $0.034 at 1K, $0.050 at 2K, $0.113 at 4K ($30 per 1M image tokens)."),
|
|
22908
|
+
mimeType
|
|
22909
|
+
],
|
|
22910
|
+
sources: [...sources, "https://ai.google.dev/gemini-api/docs/models/gemini-nano-banana-2.1"],
|
|
22911
|
+
checked: checked2,
|
|
22912
|
+
notes: ["4K tokens: the image guide says 2520, the pricing page 3780 ($0.113); the price above follows the pricing page."],
|
|
22913
|
+
shapeFields: shape
|
|
22914
|
+
};
|
|
22915
|
+
var flash31 = {
|
|
22916
|
+
family: "Nano Banana 2 (gemini-3.1-flash-image)",
|
|
22917
|
+
parameters: [
|
|
22918
|
+
ratio(fourteenRatios),
|
|
22919
|
+
imageSize("512, 1K, 2K, 4K (16:9: 688x384, 1376x768, 2752x1536, 5504x3072).", "about $0.045 at 512, $0.067 at 1K, $0.101 at 2K, $0.151 at 4K."),
|
|
22920
|
+
mimeType
|
|
22921
|
+
],
|
|
22922
|
+
sources: [...sources, "https://ai.google.dev/gemini-api/docs/models/gemini-3.1-flash-image"],
|
|
22923
|
+
checked: checked2,
|
|
22924
|
+
notes: [
|
|
22925
|
+
`The small tier's value is "512" (the guide also calls it 0.5K).`,
|
|
22926
|
+
"The guide prints 21:9 at 512 as 792x168, which is not 21:9; probably 792x336."
|
|
22927
|
+
],
|
|
22928
|
+
shapeFields: shape
|
|
22929
|
+
};
|
|
22930
|
+
var flashLite31 = {
|
|
22931
|
+
family: "Nano Banana 2 Lite (gemini-3.1-flash-lite-image)",
|
|
22932
|
+
parameters: [ratio(tenRatios), imageSize("1K only; 2K and 4K are not supported.", "about $0.034 per image."), mimeType],
|
|
22933
|
+
sources: [...sources, "https://ai.google.dev/gemini-api/docs/models/gemini-3.1-flash-lite-image"],
|
|
22934
|
+
checked: checked2,
|
|
22935
|
+
notes: ['The model page speaks of "14 aspect ratios" but lists the 10 above.', "Not optimized for several reference images."],
|
|
22936
|
+
shapeFields: shape
|
|
22937
|
+
};
|
|
22938
|
+
var pro3 = {
|
|
22939
|
+
family: "Nano Banana Pro (gemini-3-pro-image)",
|
|
22940
|
+
parameters: [
|
|
22941
|
+
ratio(tenRatios),
|
|
22942
|
+
imageSize("1K, 2K, 4K (16:9: 1376x768, 2752x1536, 5504x3072).", "about $0.134 at 1K, the same at 2K, $0.24 at 4K."),
|
|
22943
|
+
mimeType
|
|
22944
|
+
],
|
|
22945
|
+
sources: [...sources, "https://ai.google.dev/gemini-api/docs/models/gemini-3-pro-image"],
|
|
22946
|
+
checked: checked2,
|
|
22947
|
+
notes: ["Up to 14 reference images, 5 of them with high fidelity."],
|
|
22948
|
+
shapeFields: shape
|
|
22949
|
+
};
|
|
22950
|
+
var flash25 = {
|
|
22951
|
+
family: "Nano Banana (gemini-2.5-flash-image)",
|
|
22952
|
+
parameters: [
|
|
22953
|
+
{ ...ratio(tenRatios), values: `${tenRatios}; one size each (16:9 is 1344x768).` },
|
|
22954
|
+
mimeType
|
|
22955
|
+
],
|
|
22956
|
+
sources: [...sources, "https://ai.google.dev/gemini-api/docs/models/gemini-2.5-flash-image"],
|
|
22957
|
+
checked: checked2,
|
|
22958
|
+
notes: ["Takes no image_size. Works best with up to 3 reference images.", "Costs $0.039 per image."],
|
|
22959
|
+
shapeFields: shape
|
|
22960
|
+
};
|
|
22961
|
+
var entries2 = [
|
|
22962
|
+
{ matches: /gemini-nano-banana-2\.1/, guide: nanoBanana21 },
|
|
22963
|
+
{ matches: /gemini-3\.1-flash-lite-image/, guide: flashLite31 },
|
|
22964
|
+
{ matches: /gemini-3\.1-flash-image/, guide: flash31 },
|
|
22965
|
+
{ matches: /gemini-3-pro-image/, guide: pro3 },
|
|
22966
|
+
{ matches: /gemini-2\.5-flash-image/, guide: flash25 }
|
|
22967
|
+
];
|
|
22968
|
+
var geminiGuide = (modelId) => guideFor(entries2, modelId);
|
|
22969
|
+
|
|
22748
22970
|
// src/protocols/parallel.ts
|
|
22749
22971
|
async function inParallel(n, providerId, send) {
|
|
22750
22972
|
const settled = await Promise.allSettled(Array.from({ length: n }, () => send()));
|
|
@@ -22793,50 +23015,8 @@ var blockedCodes = new Set([
|
|
|
22793
23015
|
"image_other",
|
|
22794
23016
|
"content_blocked"
|
|
22795
23017
|
]);
|
|
22796
|
-
var sizes2 = (entries) => entries.map(([aspectRatio, width, height]) => ({ aspectRatio, width, height }));
|
|
22797
|
-
var gemini3 = {
|
|
22798
|
-
capabilities: {
|
|
22799
|
-
sizes: sizes2([
|
|
22800
|
-
["1:1", 1024, 1024],
|
|
22801
|
-
["16:9", 1376, 768],
|
|
22802
|
-
["9:16", 768, 1376],
|
|
22803
|
-
["4:3", 1200, 896],
|
|
22804
|
-
["3:4", 896, 1200],
|
|
22805
|
-
["3:2", 1264, 848],
|
|
22806
|
-
["2:3", 848, 1264]
|
|
22807
|
-
]),
|
|
22808
|
-
maxReferences: 14,
|
|
22809
|
-
maxImages: 4
|
|
22810
|
-
},
|
|
22811
|
-
imageSize: "1K"
|
|
22812
|
-
};
|
|
22813
|
-
var gemini25 = {
|
|
22814
|
-
capabilities: {
|
|
22815
|
-
sizes: sizes2([
|
|
22816
|
-
["1:1", 1024, 1024],
|
|
22817
|
-
["16:9", 1344, 768],
|
|
22818
|
-
["9:16", 768, 1344],
|
|
22819
|
-
["4:3", 1184, 864],
|
|
22820
|
-
["3:4", 864, 1184],
|
|
22821
|
-
["3:2", 1248, 832],
|
|
22822
|
-
["2:3", 832, 1248]
|
|
22823
|
-
]),
|
|
22824
|
-
maxReferences: 3,
|
|
22825
|
-
maxImages: 4
|
|
22826
|
-
}
|
|
22827
|
-
};
|
|
22828
|
-
var unknownModel2 = { capabilities: { ...gemini3.capabilities, maxReferences: 3 } };
|
|
22829
|
-
var gemini3Ids = ["gemini-nano-banana-2.1", "gemini-3.1-flash-image", "gemini-3.1-flash-lite-image", "gemini-3-pro-image"];
|
|
22830
|
-
function geminiModel(modelId) {
|
|
22831
|
-
if (modelId.includes("gemini-2.5-flash-image"))
|
|
22832
|
-
return gemini25;
|
|
22833
|
-
if (gemini3Ids.some((id) => modelId.includes(id)))
|
|
22834
|
-
return gemini3;
|
|
22835
|
-
return unknownModel2;
|
|
22836
|
-
}
|
|
22837
23018
|
function interactionBody(request, target) {
|
|
22838
23019
|
const { response_format: extraFormat, ...options } = request.providerOptions;
|
|
22839
|
-
const { imageSize } = geminiModel(target.modelId);
|
|
22840
23020
|
return {
|
|
22841
23021
|
model: target.modelId,
|
|
22842
23022
|
input: [
|
|
@@ -22848,12 +23028,7 @@ function interactionBody(request, target) {
|
|
|
22848
23028
|
}))
|
|
22849
23029
|
],
|
|
22850
23030
|
...options,
|
|
22851
|
-
response_format: {
|
|
22852
|
-
...imageSize === undefined ? {} : { image_size: imageSize },
|
|
22853
|
-
...isObject2(extraFormat) ? extraFormat : {},
|
|
22854
|
-
type: "image",
|
|
22855
|
-
aspect_ratio: request.size.aspectRatio
|
|
22856
|
-
}
|
|
23031
|
+
response_format: { ...isObject2(extraFormat) ? extraFormat : {}, type: "image" }
|
|
22857
23032
|
};
|
|
22858
23033
|
}
|
|
22859
23034
|
function isObject2(value) {
|
|
@@ -22901,10 +23076,11 @@ async function sendInteraction(request, target, http, context) {
|
|
|
22901
23076
|
const said = content.flatMap((block) => block.text === undefined ? [] : [block.text]).join(" ");
|
|
22902
23077
|
throw providerError("other", target.providerId, `the Interaction ended "${interaction.status}" with no image.${said === "" ? "" : ` The model wrote: ${said}`}`);
|
|
22903
23078
|
}
|
|
22904
|
-
var
|
|
22905
|
-
builtInCapabilities: (modelId) => geminiModel(modelId).capabilities,
|
|
23079
|
+
var gemini2 = {
|
|
22906
23080
|
docsUrl: () => "https://ai.google.dev/gemini-api/docs/image-generation",
|
|
22907
|
-
|
|
23081
|
+
parameterGuide: (modelId, modality) => modality === "image" ? geminiGuide(modelId) : undefined,
|
|
23082
|
+
requestFields: { image: { shape: ["response_format.aspect_ratio"], resolution: "response_format.image_size" } },
|
|
23083
|
+
reservedOptions: ["model", "input", "response_format.type"],
|
|
22908
23084
|
async submit(request, target, http, context) {
|
|
22909
23085
|
return { wait: () => inParallel(request.n, target.providerId, () => sendInteraction(request, target, http, context)) };
|
|
22910
23086
|
},
|
|
@@ -22957,32 +23133,121 @@ function trimmed(baseUrl) {
|
|
|
22957
23133
|
return baseUrl.replace(/\/+$/, "");
|
|
22958
23134
|
}
|
|
22959
23135
|
|
|
22960
|
-
// src/protocols/
|
|
22961
|
-
|
|
22962
|
-
|
|
22963
|
-
|
|
22964
|
-
|
|
22965
|
-
|
|
22966
|
-
|
|
22967
|
-
|
|
22968
|
-
|
|
22969
|
-
|
|
22970
|
-
|
|
22971
|
-
|
|
22972
|
-
|
|
22973
|
-
}
|
|
22974
|
-
|
|
22975
|
-
|
|
22976
|
-
|
|
22977
|
-
|
|
22978
|
-
|
|
22979
|
-
|
|
22980
|
-
|
|
22981
|
-
|
|
22982
|
-
|
|
22983
|
-
|
|
22984
|
-
|
|
22985
|
-
}
|
|
23136
|
+
// src/protocols/guides/openai-images.ts
|
|
23137
|
+
var checked3 = "2026-11-05";
|
|
23138
|
+
var sources2 = [
|
|
23139
|
+
"https://developers.openai.com/api/reference/resources/images/methods/generate",
|
|
23140
|
+
"https://developers.openai.com/api/reference/resources/images/methods/edit",
|
|
23141
|
+
"https://developers.openai.com/api/docs/guides/image-generation"
|
|
23142
|
+
];
|
|
23143
|
+
var shared = {
|
|
23144
|
+
background: {
|
|
23145
|
+
name: "background",
|
|
23146
|
+
values: "auto, opaque, transparent (transparent needs output_format png or webp).",
|
|
23147
|
+
default: "auto",
|
|
23148
|
+
price: "no effect."
|
|
23149
|
+
},
|
|
23150
|
+
outputFormat: {
|
|
23151
|
+
name: "output_format",
|
|
23152
|
+
values: "png, jpeg, webp.",
|
|
23153
|
+
default: "png",
|
|
23154
|
+
price: "no effect."
|
|
23155
|
+
},
|
|
23156
|
+
outputCompression: {
|
|
23157
|
+
name: "output_compression",
|
|
23158
|
+
values: "0–100, for jpeg and webp.",
|
|
23159
|
+
default: "100",
|
|
23160
|
+
price: "no effect."
|
|
23161
|
+
},
|
|
23162
|
+
moderation: { name: "moderation", values: "auto, low.", default: "auto", price: "no effect." },
|
|
23163
|
+
n: { name: "n", values: "1–10 images per request; litevision sends `n` here.", default: "1", price: "each image is billed." }
|
|
23164
|
+
};
|
|
23165
|
+
var arbitrarySize = "auto, or any WIDTHxHEIGHT: both sides multiples of 16, the long side at most 3:1 to the short, at most 3840 a side, 655,360–8,294,400 pixels in all (above 2560x1440 is experimental). Popular: 1024x1024, 1536x1024, 1024x1536, 2048x2048, 2048x1152, 3840x2160, 2160x3840.";
|
|
23166
|
+
var sizePrice = "billed by output tokens, which grow with the size and the quality; a larger non-square size can cost fewer tokens than a square.";
|
|
23167
|
+
var gptImage252 = {
|
|
23168
|
+
family: "GPT Image 2.5",
|
|
23169
|
+
parameters: [
|
|
23170
|
+
{ name: "size", values: arbitrarySize, default: "auto", price: sizePrice },
|
|
23171
|
+
{
|
|
23172
|
+
name: "quality",
|
|
23173
|
+
values: "auto, low, medium, high, xhigh, max.",
|
|
23174
|
+
default: "auto",
|
|
23175
|
+
price: "the main price lever: at 1024x1024 about $0.006 low, $0.013 medium, $0.053 high, up to $0.21 max ($30 per 1M image output tokens)."
|
|
23176
|
+
},
|
|
23177
|
+
shared.background,
|
|
23178
|
+
shared.outputFormat,
|
|
23179
|
+
shared.outputCompression,
|
|
23180
|
+
shared.moderation,
|
|
23181
|
+
shared.n
|
|
23182
|
+
],
|
|
23183
|
+
sources: [...sources2, "https://developers.openai.com/api/docs/models/gpt-image-2.5-flare"],
|
|
23184
|
+
checked: checked3,
|
|
23185
|
+
shapeFields: ["size"]
|
|
23186
|
+
};
|
|
23187
|
+
var gptImage26 = {
|
|
23188
|
+
...gptImage252,
|
|
23189
|
+
family: "GPT Image 2.6",
|
|
23190
|
+
parameters: [
|
|
23191
|
+
...gptImage252.parameters,
|
|
23192
|
+
{ name: "complexity", values: "low, medium, high.", default: "high", price: "no separate fee." }
|
|
23193
|
+
],
|
|
23194
|
+
sources: [...sources2, "https://developers.openai.com/api/docs/models/gpt-image-2.6-sunburst"],
|
|
23195
|
+
notes: ["The size rule above is GPT Image 2.5's: OpenAI does not say outright that 2.6 still images take arbitrary sizes."]
|
|
23196
|
+
};
|
|
23197
|
+
var gptImage22 = {
|
|
23198
|
+
family: "GPT Image 2",
|
|
23199
|
+
parameters: [
|
|
23200
|
+
{ name: "size", values: arbitrarySize, default: "auto", price: sizePrice },
|
|
23201
|
+
{
|
|
23202
|
+
name: "quality",
|
|
23203
|
+
values: "auto, low, medium, high.",
|
|
23204
|
+
default: "auto",
|
|
23205
|
+
price: "1024x1024 costs $0.006 low, $0.053 medium, $0.211 high; 1536x1024 and 1024x1536 cost $0.005, $0.041, $0.165."
|
|
23206
|
+
},
|
|
23207
|
+
{ ...shared.background, values: "auto, opaque, transparent (transparent is a preview on gpt-image-2)." },
|
|
23208
|
+
shared.outputFormat,
|
|
23209
|
+
shared.outputCompression,
|
|
23210
|
+
shared.moderation,
|
|
23211
|
+
shared.n
|
|
23212
|
+
],
|
|
23213
|
+
sources: [...sources2, "https://developers.openai.com/api/docs/models/gpt-image-2"],
|
|
23214
|
+
checked: checked3,
|
|
23215
|
+
shapeFields: ["size"]
|
|
23216
|
+
};
|
|
23217
|
+
var gptImage12 = {
|
|
23218
|
+
family: "GPT Image 1.x",
|
|
23219
|
+
parameters: [
|
|
23220
|
+
{
|
|
23221
|
+
name: "size",
|
|
23222
|
+
values: "auto, 1024x1024, 1536x1024, 1024x1536.",
|
|
23223
|
+
default: "auto",
|
|
23224
|
+
price: "gpt-image-1.5: portrait and landscape cost about 1.5 times the square."
|
|
23225
|
+
},
|
|
23226
|
+
{
|
|
23227
|
+
name: "quality",
|
|
23228
|
+
values: "auto, low, medium, high.",
|
|
23229
|
+
default: "auto",
|
|
23230
|
+
price: "gpt-image-1.5 at 1024x1024: $0.009 low, $0.034 medium, $0.133 high; gpt-image-1-mini: $0.005, $0.011, $0.036."
|
|
23231
|
+
},
|
|
23232
|
+
{ name: "input_fidelity", values: "high, low (gpt-image-1-mini takes low only).", default: "low", price: "not documented." },
|
|
23233
|
+
shared.background,
|
|
23234
|
+
shared.outputFormat,
|
|
23235
|
+
shared.outputCompression,
|
|
23236
|
+
shared.moderation,
|
|
23237
|
+
shared.n
|
|
23238
|
+
],
|
|
23239
|
+
sources: [...sources2, "https://developers.openai.com/api/docs/models/gpt-image-1.5"],
|
|
23240
|
+
checked: checked3,
|
|
23241
|
+
notes: ["OpenAI marks gpt-image-1.5 deprecated and shut gpt-image-1 down on 2026-10-23; a relay may still serve them."],
|
|
23242
|
+
shapeFields: ["size"]
|
|
23243
|
+
};
|
|
23244
|
+
var entries3 = [
|
|
23245
|
+
{ matches: /^gpt-image-2\.6(-|$)/, guide: gptImage26 },
|
|
23246
|
+
{ matches: /^gpt-image-2\.5(-|$)/, guide: gptImage252 },
|
|
23247
|
+
{ matches: /^gpt-image-2(-\d{4}-\d{2}-\d{2})?$/, guide: gptImage22 },
|
|
23248
|
+
{ matches: /^(gpt-image-1(\.5|-mini)?|chatgpt-image-latest)$/, guide: gptImage12 }
|
|
23249
|
+
];
|
|
23250
|
+
var openaiImagesGuide = (modelId) => guideFor(entries3, modelId);
|
|
22986
23251
|
|
|
22987
23252
|
// src/protocols/openai-images.ts
|
|
22988
23253
|
var successSchema = object({
|
|
@@ -22996,29 +23261,6 @@ var errorSchema3 = object({
|
|
|
22996
23261
|
param: string2().nullish()
|
|
22997
23262
|
})
|
|
22998
23263
|
});
|
|
22999
|
-
var gptImage = {
|
|
23000
|
-
sizes: [
|
|
23001
|
-
{ aspectRatio: "1:1", width: 1024, height: 1024 },
|
|
23002
|
-
{ aspectRatio: "3:2", width: 1536, height: 1024 },
|
|
23003
|
-
{ aspectRatio: "2:3", width: 1024, height: 1536 }
|
|
23004
|
-
],
|
|
23005
|
-
maxReferences: 16,
|
|
23006
|
-
maxImages: 10
|
|
23007
|
-
};
|
|
23008
|
-
var dalle3 = {
|
|
23009
|
-
sizes: [
|
|
23010
|
-
{ aspectRatio: "1:1", width: 1024, height: 1024 },
|
|
23011
|
-
{ aspectRatio: "16:9", width: 1792, height: 1024 },
|
|
23012
|
-
{ aspectRatio: "9:16", width: 1024, height: 1792 }
|
|
23013
|
-
],
|
|
23014
|
-
maxReferences: 0,
|
|
23015
|
-
maxImages: 1
|
|
23016
|
-
};
|
|
23017
|
-
var dalle2 = {
|
|
23018
|
-
sizes: [{ aspectRatio: "1:1", width: 1024, height: 1024 }],
|
|
23019
|
-
maxReferences: 1,
|
|
23020
|
-
maxImages: 10
|
|
23021
|
-
};
|
|
23022
23264
|
function buildRequest(request, target) {
|
|
23023
23265
|
const baseUrl = target.baseUrl.replace(/\/+$/, "");
|
|
23024
23266
|
const headers = { authorization: `Bearer ${target.credential}` };
|
|
@@ -23026,7 +23268,6 @@ function buildRequest(request, target) {
|
|
|
23026
23268
|
model: target.modelId,
|
|
23027
23269
|
prompt: request.prompt,
|
|
23028
23270
|
n: request.n,
|
|
23029
|
-
size: formatSize(request.size),
|
|
23030
23271
|
...request.providerOptions
|
|
23031
23272
|
};
|
|
23032
23273
|
if (request.references.length === 0) {
|
|
@@ -23040,15 +23281,10 @@ function buildRequest(request, target) {
|
|
|
23040
23281
|
return { url: `${baseUrl}/images/edits`, init: { method: "POST", headers: { ...headers, "content-type": contentType }, body } };
|
|
23041
23282
|
}
|
|
23042
23283
|
var openaiImages = {
|
|
23043
|
-
builtInCapabilities(modelId) {
|
|
23044
|
-
if (modelId.startsWith("dall-e-3"))
|
|
23045
|
-
return dalle3;
|
|
23046
|
-
if (modelId.startsWith("dall-e-2"))
|
|
23047
|
-
return dalle2;
|
|
23048
|
-
return gptImage;
|
|
23049
|
-
},
|
|
23050
23284
|
docsUrl: () => "https://platform.openai.com/docs/api-reference/images/create",
|
|
23051
|
-
|
|
23285
|
+
parameterGuide: (modelId, modality) => modality === "image" ? openaiImagesGuide(modelId) : undefined,
|
|
23286
|
+
requestFields: { image: { shape: ["size"] } },
|
|
23287
|
+
reservedOptions: ["model", "prompt", "image", "image[]"],
|
|
23052
23288
|
async submit(request, target, http, context) {
|
|
23053
23289
|
const { url, init } = buildRequest(request, target);
|
|
23054
23290
|
const response = await sendToProvider(http, target.providerId, url, init, { context });
|
|
@@ -23113,6 +23349,182 @@ async function fetchModelIds3(target, http) {
|
|
|
23113
23349
|
return { ok: true, modelIds: parsed.data.data.map((model) => model.id).sort() };
|
|
23114
23350
|
}
|
|
23115
23351
|
|
|
23352
|
+
// src/protocols/guides/together.ts
|
|
23353
|
+
var checked4 = "2026-11-05";
|
|
23354
|
+
var imageSources = [
|
|
23355
|
+
"https://docs.together.ai/docs/inference/images/parameters",
|
|
23356
|
+
"https://docs.together.ai/docs/inference/images/reference-images",
|
|
23357
|
+
"https://docs.together.ai/docs/serverless/models"
|
|
23358
|
+
];
|
|
23359
|
+
var videoSources = [
|
|
23360
|
+
"https://docs.together.ai/docs/inference/videos/parameters",
|
|
23361
|
+
"https://docs.together.ai/docs/inference/videos/reference-and-keyframes",
|
|
23362
|
+
"https://docs.together.ai/docs/serverless/models#video-models"
|
|
23363
|
+
];
|
|
23364
|
+
var pixels = (range, price) => [
|
|
23365
|
+
{ name: "width", values: `${range}, a multiple of 8.`, default: "1024", price },
|
|
23366
|
+
{ name: "height", values: `${range}, a multiple of 8.`, default: "1024", price }
|
|
23367
|
+
];
|
|
23368
|
+
var common = [
|
|
23369
|
+
{ name: "n", values: "1–4 images per request; litevision sends `n` here.", default: "1", price: "each image is billed." },
|
|
23370
|
+
{ name: "seed", values: "any integer.", default: "random", price: "no effect." },
|
|
23371
|
+
{ name: "output_format", values: "jpeg, png.", default: "jpeg", price: "no effect." }
|
|
23372
|
+
];
|
|
23373
|
+
var imageShape = [{ width: "width", height: "height" }, "aspect_ratio"];
|
|
23374
|
+
var flux22 = {
|
|
23375
|
+
family: "FLUX.2 [pro], [flex], [dev]",
|
|
23376
|
+
parameters: [
|
|
23377
|
+
...pixels("256–1920", "the [pro] and [flex] estimate is $0.03+ per image, [dev] $0.0154+; larger images cost more."),
|
|
23378
|
+
{
|
|
23379
|
+
name: "reference_images",
|
|
23380
|
+
values: "an array of image links; litevision fills it from `references`. Together gives no count; BFL says up to 10.",
|
|
23381
|
+
default: "none",
|
|
23382
|
+
price: "not documented."
|
|
23383
|
+
},
|
|
23384
|
+
{ name: "prompt_upsampling", values: "true, false.", default: "true", price: "no effect." },
|
|
23385
|
+
{ name: "steps", values: "[dev] and [flex] only.", default: "the Provider picks", price: "not documented." },
|
|
23386
|
+
{ name: "guidance", values: "[dev] and [flex] only.", default: "the Provider picks", price: "not documented." },
|
|
23387
|
+
...common
|
|
23388
|
+
],
|
|
23389
|
+
sources: [...imageSources, "https://docs.together.ai/docs/quickstart-flux", "https://docs.bfl.ml/flux_2/flux2_overview"],
|
|
23390
|
+
checked: checked4,
|
|
23391
|
+
notes: [
|
|
23392
|
+
"Default size: the quickstart says 1024x768, the API reference 1024x1024.",
|
|
23393
|
+
"Most output: Together's quickstart says up to 9 MP for [pro], BFL says up to 4 MP."
|
|
23394
|
+
],
|
|
23395
|
+
shapeFields: imageShape
|
|
23396
|
+
};
|
|
23397
|
+
var flux2Max = {
|
|
23398
|
+
family: "FLUX.2 [max]",
|
|
23399
|
+
parameters: [...pixels("assumed 256–1920 as for the other FLUX.2 models", "$0.07 per megapixel at 50 steps: doubling the pixels doubles the price."), ...common],
|
|
23400
|
+
sources: [...imageSources, "https://docs.together.ai/docs/quickstart-flux"],
|
|
23401
|
+
checked: checked4,
|
|
23402
|
+
notes: ["Together documents neither its size range nor reference images."],
|
|
23403
|
+
shapeFields: imageShape
|
|
23404
|
+
};
|
|
23405
|
+
var kontext = {
|
|
23406
|
+
family: "FLUX.1 Kontext [pro], [max]",
|
|
23407
|
+
parameters: [
|
|
23408
|
+
{
|
|
23409
|
+
name: "aspect_ratio",
|
|
23410
|
+
values: 'a ratio such as "1:1", "16:9", "9:16", "4:3", "3:2" (examples Together gives).',
|
|
23411
|
+
default: "the Provider picks",
|
|
23412
|
+
price: "[pro] $0.04 and [max] $0.08 per megapixel."
|
|
23413
|
+
},
|
|
23414
|
+
...pixels("multiples of 8, in place of aspect_ratio", "billed per megapixel."),
|
|
23415
|
+
{ name: "image_url", values: "one image link; litevision fills it from the first of `references`.", default: "none", price: "not documented." },
|
|
23416
|
+
{ name: "steps", values: "an integer; more than the default costs proportionally more.", default: "28", price: "price × steps ÷ 28 above the default." },
|
|
23417
|
+
...common
|
|
23418
|
+
],
|
|
23419
|
+
sources: [...imageSources, "https://docs.together.ai/docs/quickstart-flux"],
|
|
23420
|
+
checked: checked4,
|
|
23421
|
+
notes: ["`aspect_ratio` is in Together's docs but not in its OpenAPI schema."],
|
|
23422
|
+
shapeFields: imageShape
|
|
23423
|
+
};
|
|
23424
|
+
var flux11 = {
|
|
23425
|
+
family: "FLUX1.1 [pro]",
|
|
23426
|
+
parameters: [...pixels("multiples of 8", "$0.04 per megapixel: doubling the pixels doubles the price."), ...common],
|
|
23427
|
+
sources: imageSources,
|
|
23428
|
+
checked: checked4,
|
|
23429
|
+
notes: ["Takes no reference images."],
|
|
23430
|
+
shapeFields: imageShape
|
|
23431
|
+
};
|
|
23432
|
+
var gemini3Pro = {
|
|
23433
|
+
family: "Gemini 3 Pro Image on Together",
|
|
23434
|
+
parameters: [
|
|
23435
|
+
{
|
|
23436
|
+
name: "width",
|
|
23437
|
+
values: "with height, one of Together's sizes: 1K 1024x1024, 1264x848, 848x1264, 1200x896, 896x1200, 928x1152, 1152x928, 768x1376, 1376x768, 1584x672; 2K 2048x2048, 2528x1696, 2752x1536 and the like; 4K 4096x4096, 5504x3072 and the like.",
|
|
23438
|
+
default: "1024",
|
|
23439
|
+
price: "$0.134 at 1K or 2K (the same), about $0.24 at 4K."
|
|
23440
|
+
},
|
|
23441
|
+
{ name: "height", values: "with width, as above.", default: "1024", price: "as width." },
|
|
23442
|
+
{ name: "reference_images", values: "an array of image links; litevision fills it from `references`. Count not documented by Together (Google: up to 14).", default: "none", price: "not documented." },
|
|
23443
|
+
...common
|
|
23444
|
+
],
|
|
23445
|
+
sources: imageSources,
|
|
23446
|
+
checked: checked4,
|
|
23447
|
+
notes: ['Together gives some sizes as alternatives ("1548x672 or 1584x672", "5096x3392 or 5056x3392"); Google says 1584x672 and 5056x3392.'],
|
|
23448
|
+
shapeFields: imageShape
|
|
23449
|
+
};
|
|
23450
|
+
var flash252 = {
|
|
23451
|
+
family: "Gemini 2.5 Flash Image on Together",
|
|
23452
|
+
parameters: [
|
|
23453
|
+
...pixels("not documented by Together", "estimated at $0.039+ per image."),
|
|
23454
|
+
{ name: "reference_images", values: "an array of image links; litevision fills it from `references`. Count not documented.", default: "none", price: "not documented." },
|
|
23455
|
+
...common
|
|
23456
|
+
],
|
|
23457
|
+
sources: imageSources,
|
|
23458
|
+
checked: checked4,
|
|
23459
|
+
notes: ["Together documents no sizes for this model; Google's own sizes are about a megapixel at each ratio."],
|
|
23460
|
+
shapeFields: imageShape
|
|
23461
|
+
};
|
|
23462
|
+
var imageEntries = [
|
|
23463
|
+
{ matches: /^black-forest-labs\/FLUX\.2-max$/i, guide: flux2Max },
|
|
23464
|
+
{ matches: /^black-forest-labs\/FLUX\.2-(pro|flex|dev)$/i, guide: flux22 },
|
|
23465
|
+
{ matches: /^black-forest-labs\/FLUX\.1-kontext-(pro|max)$/i, guide: kontext },
|
|
23466
|
+
{ matches: /^black-forest-labs\/FLUX\.1\.1-pro$/i, guide: flux11 },
|
|
23467
|
+
{ matches: /^google\/gemini-3-pro-image$/i, guide: gemini3Pro },
|
|
23468
|
+
{ matches: /^google\/flash-image-2\.5$/i, guide: flash252 }
|
|
23469
|
+
];
|
|
23470
|
+
var videoCommon = [
|
|
23471
|
+
{ name: "seed", values: "0–2,147,483,647.", default: "random", price: "no effect." },
|
|
23472
|
+
{ name: "negative_prompt", values: "text, at most 500 characters.", default: "none", price: "no effect." },
|
|
23473
|
+
{
|
|
23474
|
+
name: "media.frame_images",
|
|
23475
|
+
values: 'litevision fills it from `first_frame` and `last_frame` as { input_image, frame: "first" | "last" }.',
|
|
23476
|
+
default: "none",
|
|
23477
|
+
price: "not documented."
|
|
23478
|
+
}
|
|
23479
|
+
];
|
|
23480
|
+
var wan = (variant, seconds, media) => ({
|
|
23481
|
+
family: `Wan 2.7 ${variant}`,
|
|
23482
|
+
parameters: [
|
|
23483
|
+
{
|
|
23484
|
+
name: "resolution",
|
|
23485
|
+
values: "720P, 1080P (16:9 is 1280x720 or 1920x1080; 4:3 is 1104x832 or 1648x1248).",
|
|
23486
|
+
default: "1080P",
|
|
23487
|
+
price: "$0.10 per video in Together's catalog, which gives no resolution or length."
|
|
23488
|
+
},
|
|
23489
|
+
{ name: "ratio", values: "16:9, 9:16, 1:1, 4:3, 3:4.", default: "16:9", price: "no effect." },
|
|
23490
|
+
{ name: "seconds", values: `${seconds}, as a string.`, default: '"5"', price: "not documented." },
|
|
23491
|
+
...videoCommon
|
|
23492
|
+
],
|
|
23493
|
+
sources: [...videoSources, "https://docs.together.ai/docs/wan2.7-quickstart"],
|
|
23494
|
+
checked: checked4,
|
|
23495
|
+
notes: [media, "30 fps MP4."],
|
|
23496
|
+
shapeFields: ["ratio"],
|
|
23497
|
+
durationField: "seconds"
|
|
23498
|
+
});
|
|
23499
|
+
var seedance25 = {
|
|
23500
|
+
family: "Seedance 2.5",
|
|
23501
|
+
parameters: [
|
|
23502
|
+
{ name: "resolution", values: "480p, 720p.", default: "720p", price: "480p $0.115 per second; 720p from $0.249 per second." },
|
|
23503
|
+
{ name: "seconds", values: "4–30 whole seconds, as a string.", default: '"5"', price: "billed per second of output." },
|
|
23504
|
+
{ name: "media.reference_images", values: "up to 30 image links; litevision fills it from `references`.", default: "none", price: "not documented." },
|
|
23505
|
+
...videoCommon
|
|
23506
|
+
],
|
|
23507
|
+
sources: [...videoSources, "https://docs.together.ai/docs/seedance2.5-quickstart"],
|
|
23508
|
+
checked: checked4,
|
|
23509
|
+
notes: [
|
|
23510
|
+
"Takes no ratio, width or height: the shape follows the first frame, else the model picks. Audio is always on.",
|
|
23511
|
+
"Frames and references cannot be combined.",
|
|
23512
|
+
'The catalog lists $0.115 under "Price per video"; the quickstart says per second, which is what is given here.'
|
|
23513
|
+
],
|
|
23514
|
+
shapeFields: [],
|
|
23515
|
+
durationField: "seconds"
|
|
23516
|
+
};
|
|
23517
|
+
var videoEntries = [
|
|
23518
|
+
{ matches: /^Wan-AI\/wan2\.7-t2v$/i, guide: wan("text-to-video", "2–15", "Takes no frames or reference images.") },
|
|
23519
|
+
{ matches: /^Wan-AI\/wan2\.7-i2v$/i, guide: wan("image-to-video", "2–15", "Takes a first and/or last frame.") },
|
|
23520
|
+
{
|
|
23521
|
+
matches: /^Wan-AI\/wan2\.7-r2v$/i,
|
|
23522
|
+
guide: wan("reference-to-video", "2–10", "Takes several reference images in media.reference_images; Together gives no count.")
|
|
23523
|
+
},
|
|
23524
|
+
{ matches: /^ByteDance\/Seedance-2\.5$/i, guide: seedance25 }
|
|
23525
|
+
];
|
|
23526
|
+
var togetherGuide = (modelId, modality) => guideFor(modality === "video" ? videoEntries : imageEntries, modelId);
|
|
23527
|
+
|
|
23116
23528
|
// src/protocols/together.ts
|
|
23117
23529
|
var docs = {
|
|
23118
23530
|
flux: "https://docs.together.ai/docs/quickstart-flux",
|
|
@@ -23122,185 +23534,18 @@ var docs = {
|
|
|
23122
23534
|
seedance: "https://docs.together.ai/docs/seedance2.5-quickstart",
|
|
23123
23535
|
videoParameters: "https://docs.together.ai/docs/inference/videos/parameters"
|
|
23124
23536
|
};
|
|
23125
|
-
var sizes3 = (entries) => entries.map(([aspectRatio, width, height]) => ({ aspectRatio, width, height }));
|
|
23126
|
-
var megapixelSizes = sizes3([
|
|
23127
|
-
["1:1", 1024, 1024],
|
|
23128
|
-
["16:9", 1344, 768],
|
|
23129
|
-
["9:16", 768, 1344],
|
|
23130
|
-
["4:3", 1152, 896],
|
|
23131
|
-
["3:4", 896, 1152],
|
|
23132
|
-
["3:2", 1216, 832],
|
|
23133
|
-
["2:3", 832, 1216]
|
|
23134
|
-
]);
|
|
23135
|
-
var cappedSizes = sizes3([
|
|
23136
|
-
["1:1", 1024, 1024],
|
|
23137
|
-
["16:9", 1024, 576],
|
|
23138
|
-
["9:16", 576, 1024],
|
|
23139
|
-
["4:3", 1024, 768],
|
|
23140
|
-
["3:4", 768, 1024],
|
|
23141
|
-
["3:2", 1024, 680],
|
|
23142
|
-
["2:3", 680, 1024]
|
|
23143
|
-
]);
|
|
23144
|
-
var maxImagesPerJob2 = 4;
|
|
23145
23537
|
var imageFamilies = [
|
|
23146
|
-
{
|
|
23147
|
-
|
|
23148
|
-
|
|
23149
|
-
sizing: "ratio",
|
|
23150
|
-
references: "image_url",
|
|
23151
|
-
docsUrl: docs.flux
|
|
23152
|
-
},
|
|
23153
|
-
{
|
|
23154
|
-
matches: /^black-forest-labs\/FLUX\.2-(pro|flex|dev)$/i,
|
|
23155
|
-
capabilities: { sizes: megapixelSizes, maxReferences: 4, maxImages: maxImagesPerJob2 },
|
|
23156
|
-
sizing: "pixels",
|
|
23157
|
-
references: "reference_images",
|
|
23158
|
-
docsUrl: docs.flux
|
|
23159
|
-
},
|
|
23160
|
-
{
|
|
23161
|
-
matches: /^black-forest-labs\//i,
|
|
23162
|
-
capabilities: { sizes: megapixelSizes, maxReferences: 0, maxImages: maxImagesPerJob2 },
|
|
23163
|
-
sizing: "pixels",
|
|
23164
|
-
references: "reference_images",
|
|
23165
|
-
docsUrl: docs.flux
|
|
23166
|
-
},
|
|
23167
|
-
{
|
|
23168
|
-
matches: /^google\/gemini-3-pro-image$/i,
|
|
23169
|
-
capabilities: {
|
|
23170
|
-
sizes: sizes3([
|
|
23171
|
-
["1:1", 1024, 1024],
|
|
23172
|
-
["16:9", 1376, 768],
|
|
23173
|
-
["9:16", 768, 1376],
|
|
23174
|
-
["4:3", 1200, 896],
|
|
23175
|
-
["3:4", 896, 1200],
|
|
23176
|
-
["3:2", 1264, 848],
|
|
23177
|
-
["2:3", 848, 1264]
|
|
23178
|
-
]),
|
|
23179
|
-
maxReferences: 14,
|
|
23180
|
-
maxImages: maxImagesPerJob2
|
|
23181
|
-
},
|
|
23182
|
-
sizing: "pixels",
|
|
23183
|
-
references: "reference_images",
|
|
23184
|
-
docsUrl: docs.referenceImages
|
|
23185
|
-
},
|
|
23186
|
-
{
|
|
23187
|
-
matches: /^google\/flash-image-2\.5$/i,
|
|
23188
|
-
capabilities: {
|
|
23189
|
-
sizes: sizes3([
|
|
23190
|
-
["1:1", 1024, 1024],
|
|
23191
|
-
["16:9", 1344, 768],
|
|
23192
|
-
["9:16", 768, 1344],
|
|
23193
|
-
["4:3", 1184, 864],
|
|
23194
|
-
["3:4", 864, 1184],
|
|
23195
|
-
["3:2", 1248, 832],
|
|
23196
|
-
["2:3", 832, 1248]
|
|
23197
|
-
]),
|
|
23198
|
-
maxReferences: 3,
|
|
23199
|
-
maxImages: maxImagesPerJob2
|
|
23200
|
-
},
|
|
23201
|
-
sizing: "pixels",
|
|
23202
|
-
references: "reference_images",
|
|
23203
|
-
docsUrl: docs.referenceImages
|
|
23204
|
-
}
|
|
23538
|
+
{ matches: /^black-forest-labs\/FLUX\.1-kontext-/i, references: "image_url", docsUrl: docs.flux },
|
|
23539
|
+
{ matches: /^black-forest-labs\//i, references: "reference_images", docsUrl: docs.flux },
|
|
23540
|
+
{ matches: /^google\/(gemini-3-pro-image|flash-image-2\.5)$/i, references: "reference_images", docsUrl: docs.referenceImages }
|
|
23205
23541
|
];
|
|
23206
|
-
var otherImages = {
|
|
23207
|
-
matches: /.*/,
|
|
23208
|
-
capabilities: { sizes: cappedSizes, maxReferences: 0, maxImages: maxImagesPerJob2 },
|
|
23209
|
-
sizing: "pixels",
|
|
23210
|
-
references: "reference_images",
|
|
23211
|
-
docsUrl: docs.imageParameters
|
|
23212
|
-
};
|
|
23542
|
+
var otherImages = { matches: /.*/, references: "reference_images", docsUrl: docs.imageParameters };
|
|
23213
23543
|
var imageFamily = (modelId) => imageFamilies.find((family) => family.matches.test(modelId)) ?? otherImages;
|
|
23214
|
-
var range = (from, to) => Array.from({ length: to - from + 1 }, (_, index) => from + index);
|
|
23215
|
-
var wanResolutions = [
|
|
23216
|
-
{
|
|
23217
|
-
name: "1080P",
|
|
23218
|
-
sizes: sizes3([
|
|
23219
|
-
["16:9", 1920, 1080],
|
|
23220
|
-
["9:16", 1080, 1920],
|
|
23221
|
-
["1:1", 1440, 1440],
|
|
23222
|
-
["4:3", 1632, 1248],
|
|
23223
|
-
["3:4", 1248, 1632]
|
|
23224
|
-
])
|
|
23225
|
-
},
|
|
23226
|
-
{
|
|
23227
|
-
name: "720P",
|
|
23228
|
-
sizes: sizes3([
|
|
23229
|
-
["16:9", 1280, 720],
|
|
23230
|
-
["9:16", 720, 1280],
|
|
23231
|
-
["1:1", 960, 960],
|
|
23232
|
-
["4:3", 1088, 832],
|
|
23233
|
-
["3:4", 832, 1088]
|
|
23234
|
-
])
|
|
23235
|
-
}
|
|
23236
|
-
];
|
|
23237
|
-
var wan = (capabilities) => ({
|
|
23238
|
-
resolutions: wanResolutions,
|
|
23239
|
-
defaultDuration: 5,
|
|
23240
|
-
...capabilities
|
|
23241
|
-
});
|
|
23242
23544
|
var videoFamilies = [
|
|
23243
|
-
{
|
|
23244
|
-
|
|
23245
|
-
capabilities: wan({ durations: range(2, 15), firstFrame: false, lastFrame: false, maxReferences: 0 }),
|
|
23246
|
-
sizing: "tier+ratio",
|
|
23247
|
-
docsUrl: docs.wan
|
|
23248
|
-
},
|
|
23249
|
-
{
|
|
23250
|
-
matches: /^Wan-AI\/wan2\.7-i2v$/i,
|
|
23251
|
-
capabilities: wan({ durations: range(2, 15), firstFrame: true, lastFrame: true, maxReferences: 0 }),
|
|
23252
|
-
sizing: "tier+ratio",
|
|
23253
|
-
docsUrl: docs.wan
|
|
23254
|
-
},
|
|
23255
|
-
{
|
|
23256
|
-
matches: /^Wan-AI\/wan2\.7-r2v$/i,
|
|
23257
|
-
capabilities: wan({ durations: range(2, 10), firstFrame: false, lastFrame: false, maxReferences: 5 }),
|
|
23258
|
-
sizing: "tier+ratio",
|
|
23259
|
-
docsUrl: docs.wan
|
|
23260
|
-
},
|
|
23261
|
-
{
|
|
23262
|
-
matches: /^ByteDance\/Seedance-2\.5$/i,
|
|
23263
|
-
capabilities: {
|
|
23264
|
-
resolutions: [
|
|
23265
|
-
{ name: "720p", sizes: sizes3([["16:9", 1280, 720]]) },
|
|
23266
|
-
{ name: "480p", sizes: sizes3([["16:9", 854, 480]]) }
|
|
23267
|
-
],
|
|
23268
|
-
durations: range(4, 30),
|
|
23269
|
-
defaultDuration: 5,
|
|
23270
|
-
firstFrame: true,
|
|
23271
|
-
lastFrame: true,
|
|
23272
|
-
maxReferences: 30
|
|
23273
|
-
},
|
|
23274
|
-
sizing: "tier",
|
|
23275
|
-
docsUrl: docs.seedance
|
|
23276
|
-
}
|
|
23545
|
+
{ matches: /^Wan-AI\/wan2\.7-/i, docsUrl: docs.wan },
|
|
23546
|
+
{ matches: /^ByteDance\/Seedance-2\.5$/i, docsUrl: docs.seedance }
|
|
23277
23547
|
];
|
|
23278
|
-
var
|
|
23279
|
-
matches: /.*/,
|
|
23280
|
-
capabilities: {
|
|
23281
|
-
resolutions: [
|
|
23282
|
-
{
|
|
23283
|
-
sizes: sizes3([
|
|
23284
|
-
["16:9", 1280, 720],
|
|
23285
|
-
["9:16", 720, 1280],
|
|
23286
|
-
["1:1", 960, 960],
|
|
23287
|
-
["4:3", 1088, 816],
|
|
23288
|
-
["3:4", 816, 1088],
|
|
23289
|
-
["3:2", 1152, 768],
|
|
23290
|
-
["2:3", 768, 1152]
|
|
23291
|
-
])
|
|
23292
|
-
}
|
|
23293
|
-
],
|
|
23294
|
-
durations: range(1, 10),
|
|
23295
|
-
defaultDuration: 5,
|
|
23296
|
-
firstFrame: true,
|
|
23297
|
-
lastFrame: true,
|
|
23298
|
-
maxReferences: 4
|
|
23299
|
-
},
|
|
23300
|
-
sizing: "pixels",
|
|
23301
|
-
docsUrl: docs.videoParameters
|
|
23302
|
-
};
|
|
23303
|
-
var videoFamily = (modelId) => videoFamilies.find((family) => family.matches.test(modelId)) ?? otherVideos;
|
|
23548
|
+
var videoDocsUrl = (modelId) => videoFamilies.find((family) => family.matches.test(modelId))?.docsUrl ?? docs.videoParameters;
|
|
23304
23549
|
var errorSchema4 = object({
|
|
23305
23550
|
error: object({
|
|
23306
23551
|
message: string2().optional(),
|
|
@@ -23422,19 +23667,21 @@ async function fetchModelIds4(target, http) {
|
|
|
23422
23667
|
return { ok: true, modelIds: Object.keys(modalities).sort(), modalities };
|
|
23423
23668
|
}
|
|
23424
23669
|
var together = {
|
|
23425
|
-
|
|
23426
|
-
|
|
23427
|
-
|
|
23428
|
-
|
|
23429
|
-
|
|
23670
|
+
parameterGuide: (modelId, modality) => togetherGuide(modelId, modality),
|
|
23671
|
+
docsUrl: (modelId, modality = "image") => modality === "video" ? videoDocsUrl(modelId) : imageFamily(modelId).docsUrl,
|
|
23672
|
+
requestFields: {
|
|
23673
|
+
image: { shape: [{ width: "width", height: "height" }, "aspect_ratio"] },
|
|
23674
|
+
video: { shape: [{ width: "width", height: "height" }, "ratio"], duration: "seconds", resolution: "resolution" }
|
|
23675
|
+
},
|
|
23676
|
+
reservedOptions: ["model", "prompt", "response_format", "reference_images", "image_url"],
|
|
23677
|
+
reservedVideoOptions: ["model", "prompt", "media"],
|
|
23430
23678
|
async submit(request, target, http, context) {
|
|
23431
23679
|
const family = imageFamily(target.modelId);
|
|
23432
|
-
const {
|
|
23680
|
+
const { references } = request;
|
|
23433
23681
|
const body = {
|
|
23434
23682
|
model: target.modelId,
|
|
23435
23683
|
prompt: request.prompt,
|
|
23436
23684
|
n: request.n,
|
|
23437
|
-
...family.sizing === "ratio" ? { aspect_ratio: size.aspectRatio } : { width: size.width, height: size.height },
|
|
23438
23685
|
response_format: "base64",
|
|
23439
23686
|
...references.length === 0 ? {} : family.references === "image_url" ? { image_url: dataUrl(references[0]) } : { reference_images: references.map(dataUrl) },
|
|
23440
23687
|
...request.providerOptions
|
|
@@ -23455,8 +23702,6 @@ var together = {
|
|
|
23455
23702
|
return { wait: async () => ({ images, ...shortfall === undefined ? {} : { shortfall } }) };
|
|
23456
23703
|
},
|
|
23457
23704
|
async submitVideo(request, target, http, context) {
|
|
23458
|
-
const family = videoFamily(target.modelId);
|
|
23459
|
-
const { size } = request;
|
|
23460
23705
|
const frames = [
|
|
23461
23706
|
...request.firstFrame === undefined ? [] : [{ input_image: dataUrl(request.firstFrame), frame: "first" }],
|
|
23462
23707
|
...request.lastFrame === undefined ? [] : [{ input_image: dataUrl(request.lastFrame), frame: "last" }]
|
|
@@ -23465,12 +23710,9 @@ var together = {
|
|
|
23465
23710
|
...frames.length === 0 ? {} : { frame_images: frames },
|
|
23466
23711
|
...request.references.length === 0 ? {} : { reference_images: request.references.map(dataUrl) }
|
|
23467
23712
|
};
|
|
23468
|
-
const sizing = family.sizing === "pixels" ? { width: size.width, height: size.height } : { resolution: request.resolution, ...family.sizing === "tier+ratio" ? { ratio: size.aspectRatio } : {} };
|
|
23469
23713
|
const body = {
|
|
23470
23714
|
model: target.modelId,
|
|
23471
23715
|
prompt: request.prompt,
|
|
23472
|
-
seconds: String(request.seconds),
|
|
23473
|
-
...sizing,
|
|
23474
23716
|
...Object.keys(media).length === 0 ? {} : { media },
|
|
23475
23717
|
...request.providerOptions
|
|
23476
23718
|
};
|
|
@@ -23500,25 +23742,142 @@ var together = {
|
|
|
23500
23742
|
listModelIds: fetchModelIds4
|
|
23501
23743
|
};
|
|
23502
23744
|
|
|
23745
|
+
// src/protocols/guides/venice.ts
|
|
23746
|
+
var checked5 = "2026-11-05";
|
|
23747
|
+
var generateSources = [
|
|
23748
|
+
"https://docs.venice.ai/api-reference/endpoint/image/generate",
|
|
23749
|
+
"https://docs.venice.ai/models/image",
|
|
23750
|
+
"https://api.venice.ai/api/v1/models?type=image"
|
|
23751
|
+
];
|
|
23752
|
+
var editSources = [
|
|
23753
|
+
"https://docs.venice.ai/api-reference/endpoint/image/edit",
|
|
23754
|
+
"https://docs.venice.ai/api-reference/endpoint/image/multi-edit",
|
|
23755
|
+
"https://api.venice.ai/api/v1/models?type=inpaint"
|
|
23756
|
+
];
|
|
23757
|
+
var generateCommon = [
|
|
23758
|
+
{ name: "variants", values: "1–4 images per request; litevision sends `n` here.", default: "1", price: "each image is billed." },
|
|
23759
|
+
{ name: "format", values: "jpeg, png, webp.", default: "png (litevision's choice; Venice's own is webp)", price: "no effect." },
|
|
23760
|
+
{ name: "negative_prompt", values: "text.", default: "none", price: "no effect." },
|
|
23761
|
+
{ name: "seed", values: "−999,999,999 to 999,999,999.", default: "random", price: "no effect." },
|
|
23762
|
+
{ name: "safe_mode", values: "true, false.", default: "false (litevision's choice; Venice's own is true)", price: "no effect." },
|
|
23763
|
+
{ name: "hide_watermark", values: "true, false.", default: "true (litevision's choice)", price: "no effect." }
|
|
23764
|
+
];
|
|
23765
|
+
var ratioShape = [{ width: "width", height: "height" }, "aspect_ratio"];
|
|
23766
|
+
var pixelModels = {
|
|
23767
|
+
family: "Venice pixel-sized models (venice-sd35, wai-Illustrious, lustify, z-image-turbo, chroma)",
|
|
23768
|
+
parameters: [
|
|
23769
|
+
{
|
|
23770
|
+
name: "width",
|
|
23771
|
+
values: "1–1280, divisible by the model's widthHeightDivisor (16 for venice-sd35 and wai-Illustrious, 8 for the rest).",
|
|
23772
|
+
default: "1024",
|
|
23773
|
+
price: "$0.01 per image whatever the size."
|
|
23774
|
+
},
|
|
23775
|
+
{ name: "height", values: "as width.", default: "1024", price: "as width." },
|
|
23776
|
+
{ name: "cfg_scale", values: "0–20.", default: "the Provider picks", price: "no effect." },
|
|
23777
|
+
{ name: "steps", values: "per model (venice-sd35: up to 30).", default: "per model (venice-sd35: 25)", price: "no effect." },
|
|
23778
|
+
...generateCommon
|
|
23779
|
+
],
|
|
23780
|
+
sources: generateSources,
|
|
23781
|
+
checked: checked5,
|
|
23782
|
+
shapeFields: ratioShape
|
|
23783
|
+
};
|
|
23784
|
+
var tierModels = {
|
|
23785
|
+
family: "Venice models sized by aspect ratio and resolution tier",
|
|
23786
|
+
parameters: [
|
|
23787
|
+
{
|
|
23788
|
+
name: "aspect_ratio",
|
|
23789
|
+
values: "per model, most of 1:1, 3:2, 16:9, 21:9, 9:16, 2:3, 3:4, 4:5 (grok-imagine-* lack 21:9 and 4:5 in part; 4:3 only on seedream-v5-*).",
|
|
23790
|
+
default: "1:1",
|
|
23791
|
+
price: "no effect."
|
|
23792
|
+
},
|
|
23793
|
+
{
|
|
23794
|
+
name: "resolution",
|
|
23795
|
+
values: "1K, 2K, and on some models 4K; per model.",
|
|
23796
|
+
default: "1K",
|
|
23797
|
+
price: "per model at 1K / 2K / 4K: gpt-image-2-5-flare and -sunburst $0.07 / $0.15 / $0.23; gpt-image-2 $0.27 / $0.51 / $0.84; nano-banana-2 and -2-1 $0.10 / $0.14 / $0.19; nano-banana-pro $0.18 / $0.23 / $0.35; flux-3-image $0.12 / $0.29 / $0.98; krea-2-turbo $0.04 / $0.06; grok-imagine-image $0.03 / $0.04; seedream-v5-pro $0.06 / $0.11; qwen-image-3-pro $0.05 / $0.09; the same at 1K and 2K on ideogram-v4-5 ($0.26), qwen-image-3 ($0.04), qwen-image-2-1-turbo ($0.02) and qwen-image-2-1-pro ($0.05)."
|
|
23798
|
+
},
|
|
23799
|
+
{
|
|
23800
|
+
name: "quality",
|
|
23801
|
+
values: "low, medium, high on models that list qualities (gpt-image-2*, ideogram-v4-5; grok-imagine-image-2-0 low, medium).",
|
|
23802
|
+
default: "high",
|
|
23803
|
+
price: "gpt-image-2 at 1K: $0.02 low, $0.07 medium, $0.26 high; gpt-image-2.5 at 1K: $0.03, $0.04, $0.07."
|
|
23804
|
+
},
|
|
23805
|
+
...generateCommon
|
|
23806
|
+
],
|
|
23807
|
+
sources: generateSources,
|
|
23808
|
+
checked: checked5,
|
|
23809
|
+
notes: ["Venice publishes no pixel sizes for these models.", "gpt-image-2's headline prices are $0.01 above its per-quality high prices; both are in Venice's model list."],
|
|
23810
|
+
shapeFields: ratioShape
|
|
23811
|
+
};
|
|
23812
|
+
var ratioModels = {
|
|
23813
|
+
family: "Venice models sized by aspect ratio",
|
|
23814
|
+
parameters: [
|
|
23815
|
+
{
|
|
23816
|
+
name: "aspect_ratio",
|
|
23817
|
+
values: "per model, most of 1:1, 3:2, 16:9, 21:9, 9:16, 2:3, 3:4, 4:5 (gpt-image-1-5: 1:1, 3:2, 2:3; flux-2-max also auto).",
|
|
23818
|
+
default: "1:1 (flux-2-max: auto)",
|
|
23819
|
+
price: "no effect; per image: flux-2-pro $0.03, flux-2-max $0.09, gpt-image-1-5 $0.26, hunyuan-image-v3 $0.09, ideogram-v4 $0.06, krea-v2-large $0.07, krea-v2-medium $0.04, luma-uni-1 $0.05, luma-uni-1-max $0.12, muse-image $0.02, nano-banana-2-lite $0.06, recraft-v4 $0.05, recraft-v4-pro $0.29, seedream-v4 $0.05, seedream-v5-lite $0.05, qwen-image $0.03, qwen-image-2 $0.05, qwen-image-2-pro $0.10, wan-2-7-text-to-image $0.0375, wan-2-7-pro-text-to-image $0.094."
|
|
23820
|
+
},
|
|
23821
|
+
...generateCommon
|
|
23822
|
+
],
|
|
23823
|
+
sources: generateSources,
|
|
23824
|
+
checked: checked5,
|
|
23825
|
+
notes: ["The Qwen Image models reject width and height with a 400.", "krea-v2-* and luma-uni-1* take up to 3 style_references."],
|
|
23826
|
+
shapeFields: ratioShape
|
|
23827
|
+
};
|
|
23828
|
+
var editModels = {
|
|
23829
|
+
family: "Venice edit models (`-edit`)",
|
|
23830
|
+
parameters: [
|
|
23831
|
+
{
|
|
23832
|
+
name: "aspect_ratio",
|
|
23833
|
+
values: "auto, 1:1, 3:2, 16:9, 21:9, 9:16, 2:3, 3:4, 4:3, 4:5; a subset per model.",
|
|
23834
|
+
default: "auto",
|
|
23835
|
+
price: "no effect."
|
|
23836
|
+
},
|
|
23837
|
+
{
|
|
23838
|
+
name: "resolution",
|
|
23839
|
+
values: "1K, 2K, and on some models 4K; per model.",
|
|
23840
|
+
default: "1K",
|
|
23841
|
+
price: "per model, e.g. nano-banana-pro-edit $0.18 / $0.23 / $0.35, gpt-image-2-5-flare-edit $0.08 / $0.15 / $0.24."
|
|
23842
|
+
},
|
|
23843
|
+
{
|
|
23844
|
+
name: "quality",
|
|
23845
|
+
values: "low, medium, high; only on /image/multi-edit (several references), where the model lists qualities. /image/edit rejects it.",
|
|
23846
|
+
default: "the model's default tier",
|
|
23847
|
+
price: "per model."
|
|
23848
|
+
},
|
|
23849
|
+
{ name: "output_format", values: "jpeg, png, webp.", default: "png (litevision's choice)", price: "no effect." }
|
|
23850
|
+
],
|
|
23851
|
+
sources: editSources,
|
|
23852
|
+
checked: checked5,
|
|
23853
|
+
notes: [
|
|
23854
|
+
"Reference images per Job: the model list's maxInputImages, else 3, or 1 when combineImages is false. The multi-edit docs name capabilities.maxInputImages, the model list puts it in constraints.",
|
|
23855
|
+
"One reference image goes to /image/edit, several to /image/multi-edit; each image of a Job is its own request."
|
|
23856
|
+
],
|
|
23857
|
+
shapeFields: ["aspect_ratio"]
|
|
23858
|
+
};
|
|
23859
|
+
var entries4 = [
|
|
23860
|
+
{ matches: /(^|-)edit(-|$)/, guide: editModels },
|
|
23861
|
+
{ matches: /^(venice-sd35|wai-Illustrious|lustify-(sdxl|v7|v8)|z-image-turbo|chroma)$/, guide: pixelModels },
|
|
23862
|
+
{
|
|
23863
|
+
matches: /^(gpt-image-2(-5-(flare|sunburst))?|nano-banana-(2|2-1|pro)|flux-3-image|krea-2-turbo|grok-imagine-image(-2-0)?|ideogram-v4-5|seedream-v5-pro|qwen-image-3(-pro)?|qwen-image-2-1-(turbo|pro))$/,
|
|
23864
|
+
guide: tierModels
|
|
23865
|
+
},
|
|
23866
|
+
{
|
|
23867
|
+
matches: /^(flux-2-(pro|max)|gpt-image-1-5|hunyuan-image-v3|ideogram-v4|krea-v2-(large|medium)|luma-uni-1(-max)?|muse-image|nano-banana-2-lite|recraft-v4(-pro)?|seedream-v4|seedream-v5-lite|qwen-image(-2(-pro)?)?|wan-2-7(-pro)?-text-to-image)$/,
|
|
23868
|
+
guide: ratioModels
|
|
23869
|
+
}
|
|
23870
|
+
];
|
|
23871
|
+
var veniceGuide = (modelId) => guideFor(entries4, modelId);
|
|
23872
|
+
|
|
23503
23873
|
// src/protocols/venice.ts
|
|
23504
23874
|
var generateDocs = "https://docs.venice.ai/api-reference/endpoint/image/generate";
|
|
23505
23875
|
var editDocs = "https://docs.venice.ai/api-reference/endpoint/image/edit";
|
|
23506
|
-
var maxImages = 4;
|
|
23507
|
-
var constraintsSchema = object({
|
|
23508
|
-
aspectRatios: array(string2()).optional(),
|
|
23509
|
-
widthHeightDivisor: number2().positive().optional(),
|
|
23510
|
-
combineImages: boolean2().optional(),
|
|
23511
|
-
maxInputImages: number2().int().positive().optional()
|
|
23512
|
-
});
|
|
23513
|
-
var sizingOf = (constraints) => (constraints.widthHeightDivisor ?? 1) > 1 ? "pixels" : "ratio";
|
|
23514
23876
|
var modelListSchema5 = object({
|
|
23515
23877
|
data: array(object({
|
|
23516
23878
|
id: string2(),
|
|
23517
23879
|
type: string2().optional(),
|
|
23518
|
-
model_spec: object({
|
|
23519
|
-
constraints: record(string2(), unknown()).optional(),
|
|
23520
|
-
capabilities: object({ maxInputImages: number2().int().positive().optional() }).passthrough().optional()
|
|
23521
|
-
}).passthrough().optional()
|
|
23880
|
+
model_spec: object({ constraints: record(string2(), unknown()).optional() }).passthrough().optional()
|
|
23522
23881
|
}))
|
|
23523
23882
|
});
|
|
23524
23883
|
var errorSchema5 = object({
|
|
@@ -23531,69 +23890,9 @@ var generateSchema = object({
|
|
|
23531
23890
|
images: array(string2()),
|
|
23532
23891
|
content_policy_violation_count: number2().int().nonnegative().optional()
|
|
23533
23892
|
});
|
|
23534
|
-
function sizeFor(aspectRatio, divisor, maxSide = Infinity) {
|
|
23535
|
-
const [w, h] = aspectRatio.split(":").map(Number);
|
|
23536
|
-
const area = 1024 * 1024;
|
|
23537
|
-
let width = Math.sqrt(area * w / h);
|
|
23538
|
-
let height = Math.sqrt(area * h / w);
|
|
23539
|
-
const scale = Math.min(1, maxSide / width, maxSide / height);
|
|
23540
|
-
width *= scale;
|
|
23541
|
-
height *= scale;
|
|
23542
|
-
const snap = (value) => Math.max(divisor, Math.round(value / divisor) * divisor);
|
|
23543
|
-
return { aspectRatio, width: snap(width), height: snap(height) };
|
|
23544
|
-
}
|
|
23545
|
-
function pixelSizes(divisor) {
|
|
23546
|
-
return aspectRatios.map((ratio) => sizeFor(ratio, divisor, 1280));
|
|
23547
|
-
}
|
|
23548
|
-
function ratioSizes(ratios) {
|
|
23549
|
-
return aspectRatios.filter((ratio) => ratios.includes(ratio)).map((ratio) => sizeFor(ratio, 16));
|
|
23550
|
-
}
|
|
23551
23893
|
function looksLikeEdit(modelId) {
|
|
23552
23894
|
return /(^|-)edit(-|$)/.test(modelId);
|
|
23553
23895
|
}
|
|
23554
|
-
var pixelModels = ["venice-sd35", "hidream", "lustify-sdxl", "flux-dev", "flux-dev-uncensored", "pony-realism"];
|
|
23555
|
-
function builtInModel(modelId) {
|
|
23556
|
-
if (looksLikeEdit(modelId)) {
|
|
23557
|
-
return { kind: "edit", sizing: "ratio", capabilities: { sizes: ratioSizes(aspectRatios), maxReferences: 3, maxImages } };
|
|
23558
|
-
}
|
|
23559
|
-
if (pixelModels.includes(modelId)) {
|
|
23560
|
-
return { kind: "generate", sizing: "pixels", capabilities: { sizes: pixelSizes(16), maxReferences: 0, maxImages } };
|
|
23561
|
-
}
|
|
23562
|
-
return {
|
|
23563
|
-
kind: "generate",
|
|
23564
|
-
sizing: "ratio",
|
|
23565
|
-
capabilities: { sizes: ratioSizes(["1:1", "16:9", "9:16", "3:2", "2:3"]), maxReferences: 0, maxImages }
|
|
23566
|
-
};
|
|
23567
|
-
}
|
|
23568
|
-
function describeModel(listed, listType) {
|
|
23569
|
-
const raw = listed.model_spec?.constraints;
|
|
23570
|
-
const parsed = constraintsSchema.safeParse(raw ?? {});
|
|
23571
|
-
const constraints = parsed.success ? parsed.data : {};
|
|
23572
|
-
const nonEmpty = (capabilities) => capabilities.sizes.length === 0 ? undefined : capabilities;
|
|
23573
|
-
if ((listed.type ?? listType) === "inpaint") {
|
|
23574
|
-
const maxReferences = constraints.maxInputImages ?? listed.model_spec?.capabilities?.maxInputImages ?? (constraints.combineImages === false ? 1 : 3);
|
|
23575
|
-
return {
|
|
23576
|
-
kind: "edit",
|
|
23577
|
-
sizing: "ratio",
|
|
23578
|
-
capabilities: nonEmpty({ sizes: ratioSizes(constraints.aspectRatios ?? []), maxReferences, maxImages }),
|
|
23579
|
-
constraints: raw
|
|
23580
|
-
};
|
|
23581
|
-
}
|
|
23582
|
-
if (sizingOf(constraints) === "pixels") {
|
|
23583
|
-
return {
|
|
23584
|
-
kind: "generate",
|
|
23585
|
-
sizing: "pixels",
|
|
23586
|
-
capabilities: { sizes: pixelSizes(constraints.widthHeightDivisor), maxReferences: 0, maxImages },
|
|
23587
|
-
constraints: raw
|
|
23588
|
-
};
|
|
23589
|
-
}
|
|
23590
|
-
return {
|
|
23591
|
-
kind: "generate",
|
|
23592
|
-
sizing: "ratio",
|
|
23593
|
-
capabilities: nonEmpty({ sizes: ratioSizes(constraints.aspectRatios ?? []), maxReferences: 0, maxImages }),
|
|
23594
|
-
constraints: raw
|
|
23595
|
-
};
|
|
23596
|
-
}
|
|
23597
23896
|
var apiUrl = (baseUrl, path) => `${baseUrl.replace(/\/+$/, "")}${path}`;
|
|
23598
23897
|
var authorization2 = (credential) => ({ authorization: `Bearer ${credential}` });
|
|
23599
23898
|
async function listModels(target, type, http) {
|
|
@@ -23616,13 +23915,6 @@ async function listModels(target, type, http) {
|
|
|
23616
23915
|
}
|
|
23617
23916
|
return { ok: true, models: parsed.data.data };
|
|
23618
23917
|
}
|
|
23619
|
-
function describedModel(described, modelId) {
|
|
23620
|
-
const kind = described.capabilities.maxReferences > 0 ? "edit" : "generate";
|
|
23621
|
-
const constraints = constraintsSchema.safeParse(described.parameterSchema);
|
|
23622
|
-
if (described.parameterSchema === undefined || !constraints.success)
|
|
23623
|
-
return { kind, sizing: builtInModel(modelId).sizing };
|
|
23624
|
-
return { kind, sizing: sizingOf(constraints.data) };
|
|
23625
|
-
}
|
|
23626
23918
|
async function lookUpModel(target, http) {
|
|
23627
23919
|
const types = looksLikeEdit(target.modelId) ? ["inpaint", "image"] : ["image", "inpaint"];
|
|
23628
23920
|
for (const type of types) {
|
|
@@ -23631,7 +23923,7 @@ async function lookUpModel(target, http) {
|
|
|
23631
23923
|
throw new Error(list.message);
|
|
23632
23924
|
const listed = list.models.find((model) => model.id === target.modelId);
|
|
23633
23925
|
if (listed !== undefined)
|
|
23634
|
-
return
|
|
23926
|
+
return listed.model_spec?.constraints === undefined ? {} : { constraints: listed.model_spec.constraints };
|
|
23635
23927
|
}
|
|
23636
23928
|
return;
|
|
23637
23929
|
}
|
|
@@ -23667,15 +23959,12 @@ async function veniceError(response, providerId, options) {
|
|
|
23667
23959
|
});
|
|
23668
23960
|
}
|
|
23669
23961
|
var sharedDefaults = { safe_mode: false, enhance_prompt: false };
|
|
23670
|
-
async function generate(request, target,
|
|
23671
|
-
const { size } = request;
|
|
23672
|
-
const sizing = model.sizing === "pixels" ? { width: size.width, height: size.height } : { aspect_ratio: size.aspectRatio };
|
|
23962
|
+
async function generate(request, target, http, context) {
|
|
23673
23963
|
const body = {
|
|
23674
23964
|
model: target.modelId,
|
|
23675
23965
|
prompt: request.prompt,
|
|
23676
23966
|
variants: request.n,
|
|
23677
23967
|
return_binary: false,
|
|
23678
|
-
...sizing,
|
|
23679
23968
|
...sharedDefaults,
|
|
23680
23969
|
hide_watermark: true,
|
|
23681
23970
|
format: "png",
|
|
@@ -23711,7 +24000,6 @@ async function edit(request, target, http, context) {
|
|
|
23711
24000
|
const body = {
|
|
23712
24001
|
...inputs,
|
|
23713
24002
|
prompt: request.prompt,
|
|
23714
|
-
aspect_ratio: request.size.aspectRatio,
|
|
23715
24003
|
...sharedDefaults,
|
|
23716
24004
|
output_format: "png",
|
|
23717
24005
|
...request.providerOptions
|
|
@@ -23729,27 +24017,21 @@ async function edit(request, target, http, context) {
|
|
|
23729
24017
|
return [{ bytes: new Uint8Array(await response.arrayBuffer()) }];
|
|
23730
24018
|
}
|
|
23731
24019
|
var venice = {
|
|
23732
|
-
builtInCapabilities: (modelId) => builtInModel(modelId).capabilities,
|
|
23733
24020
|
async fetchModelInfo(target, http) {
|
|
23734
24021
|
const model = await lookUpModel(target, http);
|
|
23735
24022
|
if (model === undefined)
|
|
23736
24023
|
return;
|
|
23737
|
-
return {
|
|
23738
|
-
...model.capabilities === undefined ? {} : { capabilities: model.capabilities },
|
|
23739
|
-
...model.constraints === undefined ? {} : { parameterSchema: model.constraints }
|
|
23740
|
-
};
|
|
24024
|
+
return model.constraints === undefined ? {} : { parameterSchema: model.constraints };
|
|
23741
24025
|
},
|
|
23742
24026
|
docsUrl: (modelId) => looksLikeEdit(modelId) ? editDocs : generateDocs,
|
|
23743
|
-
|
|
24027
|
+
parameterGuide: (modelId, modality) => modality === "image" ? veniceGuide(modelId) : undefined,
|
|
24028
|
+
requestFields: { image: { shape: [{ width: "width", height: "height" }, "aspect_ratio"], resolution: "resolution" } },
|
|
24029
|
+
reservedOptions: ["model", "modelId", "prompt", "return_binary", "image", "images"],
|
|
23744
24030
|
async submit(request, target, http, context) {
|
|
23745
|
-
const model = describedModel(request.model, target.modelId);
|
|
23746
24031
|
if (request.references.length > 0) {
|
|
23747
24032
|
return { wait: () => inParallel(request.n, target.providerId, () => edit(request, target, http, context)) };
|
|
23748
24033
|
}
|
|
23749
|
-
|
|
23750
|
-
throw providerError("unsupported", target.providerId, `"${target.modelId}" is an edit model and needs at least one reference image. Pass \`references\`, or choose a text-to-image Model.`);
|
|
23751
|
-
}
|
|
23752
|
-
const images = await generate(request, target, model, http, context);
|
|
24034
|
+
const images = await generate(request, target, http, context);
|
|
23753
24035
|
return { wait: async () => images };
|
|
23754
24036
|
},
|
|
23755
24037
|
async testConnection(target, http) {
|
|
@@ -23771,7 +24053,7 @@ var venice = {
|
|
|
23771
24053
|
// src/protocols/index.ts
|
|
23772
24054
|
var adapters = {
|
|
23773
24055
|
"openai-images": openaiImages,
|
|
23774
|
-
gemini,
|
|
24056
|
+
gemini: gemini2,
|
|
23775
24057
|
apimart,
|
|
23776
24058
|
venice,
|
|
23777
24059
|
together
|
|
@@ -23830,8 +24112,7 @@ class JobRunner {
|
|
|
23830
24112
|
const { request } = spec.normalised;
|
|
23831
24113
|
const base = this.baseFor(spec, "image", request.prompt, {
|
|
23832
24114
|
n: request.n,
|
|
23833
|
-
|
|
23834
|
-
size: request.size,
|
|
24115
|
+
requested: spec.normalised.requested,
|
|
23835
24116
|
references: request.references.map((reference) => reference.path),
|
|
23836
24117
|
...spec.outputDir === undefined ? {} : { outputPath: spec.outputDir },
|
|
23837
24118
|
providerOptions: request.providerOptions
|
|
@@ -23854,12 +24135,8 @@ class JobRunner {
|
|
|
23854
24135
|
}
|
|
23855
24136
|
const base = this.baseFor(spec, "video", request.prompt, {
|
|
23856
24137
|
n: 1,
|
|
23857
|
-
|
|
23858
|
-
size: request.size,
|
|
24138
|
+
requested: spec.normalised.requested,
|
|
23859
24139
|
references: request.references.map((reference) => reference.path),
|
|
23860
|
-
seconds: request.seconds,
|
|
23861
|
-
...request.resolution === undefined ? {} : { resolution: request.resolution },
|
|
23862
|
-
...spec.normalised.followsFirstFrame ? { followsFirstFrame: true } : {},
|
|
23863
24140
|
...request.firstFrame === undefined ? {} : { firstFrame: request.firstFrame.path },
|
|
23864
24141
|
...request.lastFrame === undefined ? {} : { lastFrame: request.lastFrame.path },
|
|
23865
24142
|
...spec.outputDir === undefined ? {} : { outputPath: spec.outputDir },
|
|
@@ -23904,7 +24181,8 @@ class JobRunner {
|
|
|
23904
24181
|
files: record.files,
|
|
23905
24182
|
observed: record.observed ?? record.files.map(() => null),
|
|
23906
24183
|
...record.shortfall === undefined ? {} : { shortfall: record.shortfall },
|
|
23907
|
-
...record.mismatch === undefined ? {} : { mismatch: record.mismatch }
|
|
24184
|
+
...record.mismatch === undefined ? {} : { mismatch: record.mismatch },
|
|
24185
|
+
...record.parameters.requested === undefined ? {} : { requested: record.parameters.requested }
|
|
23908
24186
|
};
|
|
23909
24187
|
}
|
|
23910
24188
|
if (record.outcome === "failed") {
|
|
@@ -24009,7 +24287,8 @@ class JobRunner {
|
|
|
24009
24287
|
files: saved.files,
|
|
24010
24288
|
observed,
|
|
24011
24289
|
...shortfall === undefined ? {} : { shortfall },
|
|
24012
|
-
...mismatch === undefined ? {} : { mismatch }
|
|
24290
|
+
...mismatch === undefined ? {} : { mismatch },
|
|
24291
|
+
...base.parameters.requested === undefined ? {} : { requested: base.parameters.requested }
|
|
24013
24292
|
};
|
|
24014
24293
|
} catch (error) {
|
|
24015
24294
|
if (error instanceof JobAbandoned || this.closed)
|
|
@@ -24046,20 +24325,27 @@ function videoAsImages(result) {
|
|
|
24046
24325
|
var aspectTolerance = Math.log(1.05);
|
|
24047
24326
|
var durationToleranceSeconds = 0.5;
|
|
24048
24327
|
function expectedShape(parameters) {
|
|
24328
|
+
if (parameters.requested !== undefined) {
|
|
24329
|
+
const parsed = parameters.requested.shape === null ? undefined : parseShape(parameters.requested.shape);
|
|
24330
|
+
return parsed === undefined ? undefined : { ratio: parsed.ratio, asked: parsed.label };
|
|
24331
|
+
}
|
|
24049
24332
|
const { width, height } = parameters.providerOptions;
|
|
24050
24333
|
if (typeof width === "number" && typeof height === "number" && width > 0 && height > 0) {
|
|
24051
24334
|
return { ratio: width / height, asked: `${width}x${height} in provider_options` };
|
|
24052
24335
|
}
|
|
24053
|
-
|
|
24336
|
+
const { size } = parameters;
|
|
24337
|
+
const ratio = size === undefined ? undefined : parseShape(size.aspectRatio);
|
|
24338
|
+
return size === undefined || ratio === undefined ? undefined : { ratio: ratio.ratio, asked: `${size.aspectRatio} (${size.width}x${size.height})` };
|
|
24054
24339
|
}
|
|
24055
24340
|
function mismatchOf(parameters, observed) {
|
|
24056
|
-
const {
|
|
24341
|
+
const { followsFirstFrame } = parameters;
|
|
24342
|
+
const seconds = parameters.requested !== undefined ? parameters.requested.seconds ?? undefined : parameters.seconds;
|
|
24057
24343
|
const expected = expectedShape(parameters);
|
|
24058
24344
|
const problems = new Set;
|
|
24059
24345
|
for (const facts of observed) {
|
|
24060
24346
|
if (facts === null)
|
|
24061
24347
|
continue;
|
|
24062
|
-
if (!followsFirstFrame && Math.abs(Math.log(facts.width / facts.height / expected.ratio)) > aspectTolerance) {
|
|
24348
|
+
if (!followsFirstFrame && expected !== undefined && Math.abs(Math.log(facts.width / facts.height / expected.ratio)) > aspectTolerance) {
|
|
24063
24349
|
problems.add(`asked for ${expected.asked}, got ${facts.width}x${facts.height}`);
|
|
24064
24350
|
}
|
|
24065
24351
|
if (seconds !== undefined && facts.seconds !== undefined && Math.abs(facts.seconds - seconds) > durationToleranceSeconds) {
|
|
@@ -24205,26 +24491,19 @@ function isPlainObject3(value) {
|
|
|
24205
24491
|
// src/model-info.ts
|
|
24206
24492
|
function videoModelInfo({ model }) {
|
|
24207
24493
|
const adapter = adapterFor(model);
|
|
24208
|
-
if (adapter.
|
|
24494
|
+
if (adapter.submitVideo === undefined) {
|
|
24209
24495
|
throw new ConfigError(`Model "${model.alias}" is a video Model, but its Protocol "${model.protocol}" makes no videos in litevision.`);
|
|
24210
24496
|
}
|
|
24211
|
-
|
|
24497
|
+
const guide = adapter.parameterGuide?.(model.modelId, "video");
|
|
24498
|
+
return {
|
|
24499
|
+
docsUrl: adapter.docsUrl(model.modelId, "video"),
|
|
24500
|
+
...guide === undefined ? {} : { guide }
|
|
24501
|
+
};
|
|
24212
24502
|
}
|
|
24213
24503
|
var modelInfoTtlMs = 24 * 60 * 60 * 1000;
|
|
24214
24504
|
var defaultModelInfoTimeoutMs = 5000;
|
|
24215
|
-
var sizeSchema = object({
|
|
24216
|
-
aspectRatio: _enum(aspectRatios),
|
|
24217
|
-
width: number2().int().positive(),
|
|
24218
|
-
height: number2().int().positive()
|
|
24219
|
-
});
|
|
24220
|
-
var capabilitiesSchema = object({
|
|
24221
|
-
sizes: array(sizeSchema).min(1),
|
|
24222
|
-
maxReferences: number2().int().nonnegative(),
|
|
24223
|
-
maxImages: number2().int().positive()
|
|
24224
|
-
});
|
|
24225
24505
|
var entrySchema = object({
|
|
24226
24506
|
fetchedAt: string2(),
|
|
24227
|
-
capabilities: capabilitiesSchema.nullable(),
|
|
24228
24507
|
parameterSchema: unknown().optional()
|
|
24229
24508
|
});
|
|
24230
24509
|
var cachePath = (dirs) => join6(dirs.data, "capabilities.json");
|
|
@@ -24233,10 +24512,10 @@ function cacheKey({ model, provider }) {
|
|
|
24233
24512
|
}
|
|
24234
24513
|
async function modelInfo(deps, resolved, options = {}) {
|
|
24235
24514
|
const adapter = adapterFor(resolved.model);
|
|
24515
|
+
const guide = adapter.parameterGuide?.(resolved.model.modelId, "image");
|
|
24236
24516
|
const builtIn = {
|
|
24237
|
-
|
|
24238
|
-
|
|
24239
|
-
docsUrl: adapter.docsUrl(resolved.model.modelId, "image")
|
|
24517
|
+
docsUrl: adapter.docsUrl(resolved.model.modelId, "image"),
|
|
24518
|
+
...guide === undefined ? {} : { guide }
|
|
24240
24519
|
};
|
|
24241
24520
|
const fetchModelInfo = adapter.fetchModelInfo;
|
|
24242
24521
|
if (fetchModelInfo === undefined)
|
|
@@ -24261,13 +24540,9 @@ async function modelInfo(deps, resolved, options = {}) {
|
|
|
24261
24540
|
entry = cached;
|
|
24262
24541
|
}
|
|
24263
24542
|
}
|
|
24264
|
-
if (entry === undefined)
|
|
24543
|
+
if (entry?.parameterSchema === undefined)
|
|
24265
24544
|
return builtIn;
|
|
24266
|
-
return {
|
|
24267
|
-
...builtIn,
|
|
24268
|
-
...entry.capabilities === null ? {} : { capabilities: entry.capabilities, source: "provider" },
|
|
24269
|
-
...entry.parameterSchema === undefined ? {} : { parameterSchema: entry.parameterSchema }
|
|
24270
|
-
};
|
|
24545
|
+
return { ...builtIn, parameterSchema: entry.parameterSchema };
|
|
24271
24546
|
}
|
|
24272
24547
|
async function refreshModelInfo(deps, alias) {
|
|
24273
24548
|
await refreshMatching(deps, (resolved) => resolved.model.alias === alias);
|
|
@@ -24288,12 +24563,7 @@ async function refreshMatching(deps, wanted) {
|
|
|
24288
24563
|
} catch {}
|
|
24289
24564
|
}
|
|
24290
24565
|
function toEntry(info, now) {
|
|
24291
|
-
|
|
24292
|
-
return {
|
|
24293
|
-
fetchedAt: now.toISOString(),
|
|
24294
|
-
capabilities: capabilities.success ? capabilities.data : null,
|
|
24295
|
-
...info?.parameterSchema === undefined ? {} : { parameterSchema: info.parameterSchema }
|
|
24296
|
-
};
|
|
24566
|
+
return { fetchedAt: now.toISOString(), ...info?.parameterSchema === undefined ? {} : { parameterSchema: info.parameterSchema } };
|
|
24297
24567
|
}
|
|
24298
24568
|
async function readCache(dirs) {
|
|
24299
24569
|
let value;
|
|
@@ -24377,6 +24647,85 @@ function updateNotesAsAgent(dirs, alias, text, at) {
|
|
|
24377
24647
|
});
|
|
24378
24648
|
}
|
|
24379
24649
|
|
|
24650
|
+
// src/protocols/requests.ts
|
|
24651
|
+
function reservedConflicts(options, reservedFields) {
|
|
24652
|
+
const conflictOf = (field) => {
|
|
24653
|
+
const [parent, child] = field.split(".");
|
|
24654
|
+
if (!Object.hasOwn(options, parent))
|
|
24655
|
+
return;
|
|
24656
|
+
const value = options[parent];
|
|
24657
|
+
if (child === undefined)
|
|
24658
|
+
return `\`${field}\``;
|
|
24659
|
+
if (!isPlainObject3(value))
|
|
24660
|
+
return `\`${parent}\` as anything but an object (litevision sets \`${field}\` in it)`;
|
|
24661
|
+
return Object.hasOwn(value, child) ? `\`${field}\`` : undefined;
|
|
24662
|
+
};
|
|
24663
|
+
const order = Object.keys(options);
|
|
24664
|
+
return [...reservedFields].sort((a, b) => order.indexOf(a.split(".")[0]) - order.indexOf(b.split(".")[0])).map(conflictOf).filter((conflict) => conflict !== undefined);
|
|
24665
|
+
}
|
|
24666
|
+
function refuseReserved(providerOptions, reservedFields, fromWhat) {
|
|
24667
|
+
const conflicts = reservedConflicts(providerOptions, reservedFields);
|
|
24668
|
+
if (conflicts.length === 0)
|
|
24669
|
+
return;
|
|
24670
|
+
throw new ConfigError(`provider_options, and the Model's Standing Options they are merged over, may not set ${conflicts.join(", ")}: litevision fills ${conflicts.length === 1 ? "that field" : "those fields"} itself, from ${fromWhat}. Use those instead.`);
|
|
24671
|
+
}
|
|
24672
|
+
async function normaliseImageRequest(parameters, model, adapter, cwd) {
|
|
24673
|
+
refuseReserved(parameters.providerOptions, adapter.reservedOptions, "the prompt, `references` and `model`");
|
|
24674
|
+
const references = await readReferences(parameters.references, cwd);
|
|
24675
|
+
return {
|
|
24676
|
+
request: { prompt: parameters.prompt, n: parameters.n, references, providerOptions: parameters.providerOptions },
|
|
24677
|
+
requested: readRequested(fieldsToRead(adapter, model.modelId, "image"), parameters.providerOptions, "image")
|
|
24678
|
+
};
|
|
24679
|
+
}
|
|
24680
|
+
async function normaliseVideoRequest(parameters, model, adapter, cwd) {
|
|
24681
|
+
refuseReserved(parameters.providerOptions, adapter.reservedVideoOptions ?? [], "the prompt, `first_frame`, `last_frame`, `references` and `model`");
|
|
24682
|
+
const frame = async (path) => path === undefined ? undefined : (await readReferences([path], cwd))[0];
|
|
24683
|
+
const [firstFrame, lastFrame, references] = await Promise.all([
|
|
24684
|
+
frame(parameters.firstFrame),
|
|
24685
|
+
frame(parameters.lastFrame),
|
|
24686
|
+
readReferences(parameters.references, cwd)
|
|
24687
|
+
]);
|
|
24688
|
+
return {
|
|
24689
|
+
request: {
|
|
24690
|
+
prompt: parameters.prompt,
|
|
24691
|
+
...firstFrame === undefined ? {} : { firstFrame },
|
|
24692
|
+
...lastFrame === undefined ? {} : { lastFrame },
|
|
24693
|
+
references,
|
|
24694
|
+
providerOptions: parameters.providerOptions
|
|
24695
|
+
},
|
|
24696
|
+
requested: readRequested(fieldsToRead(adapter, model.modelId, "video"), parameters.providerOptions, "video")
|
|
24697
|
+
};
|
|
24698
|
+
}
|
|
24699
|
+
|
|
24700
|
+
// src/standing-options.ts
|
|
24701
|
+
function mergeOptions(standing, agent) {
|
|
24702
|
+
const merged = { ...standing };
|
|
24703
|
+
for (const [key, value] of Object.entries(agent)) {
|
|
24704
|
+
if (value === null)
|
|
24705
|
+
delete merged[key];
|
|
24706
|
+
else if (isPlainObject3(value))
|
|
24707
|
+
merged[key] = mergeOptions(isPlainObject3(merged[key]) ? merged[key] : {}, value);
|
|
24708
|
+
else
|
|
24709
|
+
merged[key] = value;
|
|
24710
|
+
}
|
|
24711
|
+
return merged;
|
|
24712
|
+
}
|
|
24713
|
+
function reservedFields(protocol, modality) {
|
|
24714
|
+
const adapter = adapterForProtocol(protocol);
|
|
24715
|
+
if (adapter === undefined)
|
|
24716
|
+
return [];
|
|
24717
|
+
return modality === "video" ? adapter.reservedVideoOptions ?? [] : adapter.reservedOptions;
|
|
24718
|
+
}
|
|
24719
|
+
function standingOptionsProblem(value, protocol, modality) {
|
|
24720
|
+
if (!isPlainObject3(value)) {
|
|
24721
|
+
return 'Standing Options must be a JSON object of request fields, such as { "quality": "high" }.';
|
|
24722
|
+
}
|
|
24723
|
+
const conflicts = reservedConflicts(value, reservedFields(protocol, modality));
|
|
24724
|
+
if (conflicts.length === 0)
|
|
24725
|
+
return;
|
|
24726
|
+
return `Standing Options may not set ${conflicts.join(", ")}: litevision fills ${conflicts.length === 1 ? "that field" : "those fields"} itself for Protocol "${protocol}".`;
|
|
24727
|
+
}
|
|
24728
|
+
|
|
24380
24729
|
// src/settings/browser.ts
|
|
24381
24730
|
import { spawn } from "node:child_process";
|
|
24382
24731
|
var openInBrowser = (url) => {
|
|
@@ -24666,7 +25015,8 @@ var addModelInput = object({
|
|
|
24666
25015
|
modelId: string2().trim().min(1, "Enter a model id.").max(200, "A model id is at most 200 characters."),
|
|
24667
25016
|
protocol: string2().refine((value) => supportedProtocols().includes(value), { error: () => `Choose a supported Protocol: ${supportedProtocols().join(", ")}.` }),
|
|
24668
25017
|
modality: _enum(modalities, { error: () => "Choose whether the Model makes images or videos." }).default("image"),
|
|
24669
|
-
notes: notes.optional()
|
|
25018
|
+
notes: notes.optional(),
|
|
25019
|
+
standingOptions: unknown().optional()
|
|
24670
25020
|
});
|
|
24671
25021
|
var updateModelInput = addModelInput.omit({ modality: true }).partial().extend({ modality: addModelInput.shape.modality.unwrap().optional() });
|
|
24672
25022
|
var storedModels = array(modelSchema);
|
|
@@ -24700,6 +25050,8 @@ async function addModel(dirs, input, at) {
|
|
|
24700
25050
|
stored.notes = input.notes;
|
|
24701
25051
|
stored.notesHistory = { changedBy: "User", changedAt: at.toISOString() };
|
|
24702
25052
|
}
|
|
25053
|
+
if (input.standingOptions !== undefined)
|
|
25054
|
+
setStandingOptions(stored, input.standingOptions);
|
|
24703
25055
|
config.models = [...models, stored];
|
|
24704
25056
|
});
|
|
24705
25057
|
return getModel(dirs, input.alias);
|
|
@@ -24732,6 +25084,8 @@ async function updateModel(dirs, wanted, input, at) {
|
|
|
24732
25084
|
model.protocol = input.protocol;
|
|
24733
25085
|
if (input.notes !== undefined)
|
|
24734
25086
|
replaceNotes(model, input.notes, "User", at);
|
|
25087
|
+
if (input.standingOptions !== undefined)
|
|
25088
|
+
setStandingOptions(model, input.standingOptions);
|
|
24735
25089
|
});
|
|
24736
25090
|
return getModel(dirs, input.alias ?? wanted);
|
|
24737
25091
|
}
|
|
@@ -24774,6 +25128,16 @@ async function setDefaultModel(dirs, modality, wanted) {
|
|
|
24774
25128
|
});
|
|
24775
25129
|
return { alias: wanted };
|
|
24776
25130
|
}
|
|
25131
|
+
function setStandingOptions(model, value) {
|
|
25132
|
+
if (value === null || isPlainObject3(value) && Object.keys(value).length === 0) {
|
|
25133
|
+
delete model.standingOptions;
|
|
25134
|
+
return;
|
|
25135
|
+
}
|
|
25136
|
+
const problem = standingOptionsProblem(value, model.protocol, modalityOf(model));
|
|
25137
|
+
if (problem !== undefined)
|
|
25138
|
+
throw new SettingsError(400, problem);
|
|
25139
|
+
model.standingOptions = value;
|
|
25140
|
+
}
|
|
24777
25141
|
function requireFreeAlias(models, wanted) {
|
|
24778
25142
|
if (models.some((model) => model.alias === wanted)) {
|
|
24779
25143
|
throw new SettingsError(409, `A Model with the Alias "${wanted}" already exists. Choose another Alias.`);
|
|
@@ -24806,6 +25170,7 @@ function view2(model, defaults) {
|
|
|
24806
25170
|
modality,
|
|
24807
25171
|
notes: model.notes ?? "",
|
|
24808
25172
|
...model.notesHistory && { notesHistory: model.notesHistory },
|
|
25173
|
+
...model.standingOptions && { standingOptions: model.standingOptions },
|
|
24809
25174
|
default: model.alias === (modality === "video" ? defaults.defaultVideoModel : defaults.defaultImageModel)
|
|
24810
25175
|
};
|
|
24811
25176
|
}
|
|
@@ -25377,6 +25742,31 @@ function notesHistoryRow(model, path) {
|
|
|
25377
25742
|
return [el("div", { className: "note" }, ...parts)];
|
|
25378
25743
|
}
|
|
25379
25744
|
|
|
25745
|
+
/** A JSON textarea for a Model's Standing Options: request fields every Job on it sends unless the Agent overrides them. */
|
|
25746
|
+
function standingOptionsField(value) {
|
|
25747
|
+
const textarea = el("textarea", {
|
|
25748
|
+
value: value ? JSON.stringify(value, null, 2) : "",
|
|
25749
|
+
spellcheck: false,
|
|
25750
|
+
placeholder: '{ "quality": "high" }',
|
|
25751
|
+
});
|
|
25752
|
+
const wrapper = field("Standing Options", textarea);
|
|
25753
|
+
wrapper.classList.add("wide");
|
|
25754
|
+
wrapper.append(el("div", { className: "note", textContent: "A JSON object of the Provider's own request fields, sent with every Job on this Model. Your Agent's provider_options override them for a Job. Leave empty for none." }));
|
|
25755
|
+
/** The value to save: null when empty; throws when the text is not a JSON object. */
|
|
25756
|
+
const read = () => {
|
|
25757
|
+
const text = textarea.value.trim();
|
|
25758
|
+
if (text === "") return null;
|
|
25759
|
+
let parsed;
|
|
25760
|
+
try {
|
|
25761
|
+
parsed = JSON.parse(text);
|
|
25762
|
+
} catch {
|
|
25763
|
+
throw new Error('Standing Options must be a JSON object, such as { "quality": "high" }. This is not valid JSON: ' + text.slice(0, 40));
|
|
25764
|
+
}
|
|
25765
|
+
return parsed;
|
|
25766
|
+
};
|
|
25767
|
+
return { wrapper, read };
|
|
25768
|
+
}
|
|
25769
|
+
|
|
25380
25770
|
function modelCard(model) {
|
|
25381
25771
|
const alias = el("input", { value: model.alias, spellcheck: false });
|
|
25382
25772
|
const provider = el("select", {}, ...options(providerIds.includes(model.provider) ? providerIds : [model.provider, ...providerIds], model.provider));
|
|
@@ -25384,11 +25774,12 @@ function modelCard(model) {
|
|
|
25384
25774
|
const protocol = el("select", {}, ...options(protocols.includes(model.protocol) ? protocols : [model.protocol, ...protocols], model.protocol));
|
|
25385
25775
|
const modality = el("select", {}, ...options(["image", "video"], model.modality));
|
|
25386
25776
|
const notes = notesField(model.notes);
|
|
25777
|
+
const standing = standingOptionsField(model.standingOptions);
|
|
25387
25778
|
const path = "/api/models/" + encodeURIComponent(model.alias);
|
|
25388
25779
|
|
|
25389
25780
|
const save = el("button", { type: "button", className: "primary", textContent: "Save" });
|
|
25390
25781
|
save.onclick = () => act(save, async () => {
|
|
25391
|
-
await api("PATCH", path, { alias: alias.value, provider: provider.value, modelId: modelId.value, protocol: protocol.value, modality: modality.value, notes: notes.textarea.value });
|
|
25782
|
+
await api("PATCH", path, { alias: alias.value, provider: provider.value, modelId: modelId.value, protocol: protocol.value, modality: modality.value, notes: notes.textarea.value, standingOptions: standing.read() });
|
|
25392
25783
|
await loadModels();
|
|
25393
25784
|
}, "Model saved.");
|
|
25394
25785
|
|
|
@@ -25413,14 +25804,21 @@ function modelCard(model) {
|
|
|
25413
25804
|
el("div", { className: "card-head" }, el("h3", { textContent: model.alias }),
|
|
25414
25805
|
model.default ? el("span", { className: "chip accent", textContent: "Default " + model.modality }) : makeDefault),
|
|
25415
25806
|
el("div", { className: "fields" },
|
|
25416
|
-
field("Alias", alias), field("Provider", provider), field("Model id", modelId), field("Protocol", protocol), field("Makes", modality), notes.wrapper),
|
|
25807
|
+
field("Alias", alias), field("Provider", provider), field("Model id", modelId), field("Protocol", protocol), field("Makes", modality), notes.wrapper, standing.wrapper),
|
|
25417
25808
|
el("div", { className: "actions" }, save, el("span", { className: "spacer" }), remove),
|
|
25418
25809
|
);
|
|
25419
25810
|
}
|
|
25420
25811
|
|
|
25812
|
+
// The saved Models, so the fetched model list can say which model ids are already added.
|
|
25813
|
+
let savedModels = [];
|
|
25814
|
+
// Redraws the fetched model list; set by setUpAddModelForm.
|
|
25815
|
+
let showModelIds = () => {};
|
|
25816
|
+
|
|
25421
25817
|
async function loadModels() {
|
|
25422
25818
|
const list = document.getElementById("model-list");
|
|
25423
25819
|
const { models } = await api("GET", "/api/models");
|
|
25820
|
+
savedModels = models;
|
|
25821
|
+
showModelIds();
|
|
25424
25822
|
list.replaceChildren(...(models.length === 0
|
|
25425
25823
|
? [emptyState(providerIds.length === 0 ? "Add a Provider first, then add its Models below." : "No Models yet. Add one below.")]
|
|
25426
25824
|
: models.map(modelCard)));
|
|
@@ -25436,34 +25834,56 @@ function setUpAddModelForm() {
|
|
|
25436
25834
|
const fetchResult = document.getElementById("fetch-result");
|
|
25437
25835
|
const ids = document.getElementById("model-ids");
|
|
25438
25836
|
const modality = document.getElementById("model-modality");
|
|
25439
|
-
//
|
|
25837
|
+
// The model ids the last fetch found, and what each one makes when the Provider says.
|
|
25838
|
+
let fetchedIds = [];
|
|
25440
25839
|
let knownModalities = {};
|
|
25441
25840
|
protocol.replaceChildren(...options(protocols));
|
|
25442
25841
|
|
|
25842
|
+
const forgetModelIds = () => {
|
|
25843
|
+
fetchedIds = [];
|
|
25844
|
+
knownModalities = {};
|
|
25845
|
+
showModelIds();
|
|
25846
|
+
};
|
|
25847
|
+
|
|
25848
|
+
// Lists the fetched model ids that make what Makes says (and those the Provider says nothing about),
|
|
25849
|
+
// marking the ones already added as a Model on this Provider.
|
|
25850
|
+
showModelIds = () => {
|
|
25851
|
+
const shown = fetchedIds.filter((id) => (knownModalities[id] ?? modality.value) === modality.value);
|
|
25852
|
+
ids.replaceChildren(...shown.map((id) => {
|
|
25853
|
+
const aliases = savedModels.filter((m) => m.provider === provider.value && m.modelId === id).map((m) => m.alias);
|
|
25854
|
+
return el("option", aliases.length === 0 ? { value: id } : { value: id, label: "Already added as " + aliases.join(", ") });
|
|
25855
|
+
}));
|
|
25856
|
+
if (fetchedIds.length === 0) return;
|
|
25857
|
+
const hidden = fetchedIds.length - shown.length;
|
|
25858
|
+
const other = modality.value === "image" ? "video" : "image";
|
|
25859
|
+
fetchResult.textContent = shown.length + (hidden === 0 ? "" : " " + modality.value) + " model id(s) found"
|
|
25860
|
+
+ (hidden === 0 ? "" : ", and " + hidden + " " + other + " model id(s) hidden until Makes says " + (other === "image" ? "Images" : "Videos"))
|
|
25861
|
+
+ ". Pick one in the Model id field, or type your own.";
|
|
25862
|
+
};
|
|
25863
|
+
|
|
25443
25864
|
// Suggest the Protocol a preset Provider's Models usually speak; the User can still change it.
|
|
25444
25865
|
provider.onchange = () => {
|
|
25445
25866
|
const suggested = presets.find((p) => p.id === provider.value);
|
|
25446
25867
|
if (suggested && protocols.includes(suggested.protocol)) protocol.value = suggested.protocol;
|
|
25447
|
-
|
|
25448
|
-
knownModalities = {};
|
|
25868
|
+
forgetModelIds();
|
|
25449
25869
|
fetchResult.textContent = "";
|
|
25450
25870
|
};
|
|
25451
|
-
protocol.onchange =
|
|
25452
|
-
|
|
25453
|
-
knownModalities = {};
|
|
25454
|
-
};
|
|
25871
|
+
protocol.onchange = forgetModelIds;
|
|
25872
|
+
modality.onchange = () => showModelIds();
|
|
25455
25873
|
|
|
25456
25874
|
fetchButton.onclick = () => act(fetchButton, async () => {
|
|
25457
25875
|
fetchResult.textContent = "Fetching…";
|
|
25458
25876
|
fetchResult.className = "result";
|
|
25459
25877
|
const outcome = await api("POST", "/api/providers/" + encodeURIComponent(provider.value) + "/model-ids", { protocol: protocol.value });
|
|
25460
25878
|
if (outcome.result === "success") {
|
|
25879
|
+
fetchedIds = outcome.modelIds;
|
|
25461
25880
|
knownModalities = outcome.modalities || {};
|
|
25462
|
-
ids.replaceChildren(...outcome.modelIds.map((id) => el("option", { value: id, label: knownModalities[id] ? id + " (" + knownModalities[id] + ")" : id })));
|
|
25463
|
-
fetchResult.textContent = outcome.modelIds.length + " model id(s) found. Pick one in the Model id field, or type your own.";
|
|
25464
25881
|
fetchResult.className = "result success";
|
|
25882
|
+
showModelIds();
|
|
25883
|
+
if (fetchedIds.length === 0) fetchResult.textContent = "No model ids found. Type the model id.";
|
|
25465
25884
|
modelId.focus();
|
|
25466
25885
|
} else {
|
|
25886
|
+
forgetModelIds();
|
|
25467
25887
|
fetchResult.textContent = outcome.message;
|
|
25468
25888
|
fetchResult.className = "result " + outcome.result;
|
|
25469
25889
|
}
|
|
@@ -25474,7 +25894,10 @@ function setUpAddModelForm() {
|
|
|
25474
25894
|
alias.oninput = () => (aliasEdited = alias.value !== "");
|
|
25475
25895
|
modelId.oninput = () => {
|
|
25476
25896
|
const known = knownModalities[modelId.value.trim()];
|
|
25477
|
-
if (known
|
|
25897
|
+
if (known && known !== modality.value) {
|
|
25898
|
+
modality.value = known;
|
|
25899
|
+
showModelIds();
|
|
25900
|
+
}
|
|
25478
25901
|
if (!aliasEdited) alias.value = modelId.value.trim().replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^[^A-Za-z0-9]+/, "").slice(0, 64);
|
|
25479
25902
|
};
|
|
25480
25903
|
|
|
@@ -25831,16 +26254,16 @@ function settingsOpener(options) {
|
|
|
25831
26254
|
};
|
|
25832
26255
|
}
|
|
25833
26256
|
// package.json
|
|
25834
|
-
var version2 = "0.
|
|
26257
|
+
var version2 = "0.3.0";
|
|
25835
26258
|
|
|
25836
26259
|
// src/server.ts
|
|
25837
26260
|
var instructions = `litevision generates images and videos through Models the User has configured with their own Providers. Before writing a prompt, use the litevision Skill. If you do not have it, mention once that the User can install it with \`npx skills add levyxdev/litevision\`.
|
|
25838
26261
|
|
|
25839
26262
|
- Never ask the User for an API key or any other Credential. If litevision reports that nothing is configured, call \`open_settings\` so the User can set it up in their browser.
|
|
25840
26263
|
- Use the Model the User names. When they name none, omit \`model\` for the default, unless the request needs something the default lacks; then choose with \`list_models\`.
|
|
25841
|
-
-
|
|
26264
|
+
- litevision passes \`provider_options\` to the Provider as they are and translates nothing: write each Model's own fields, such as the size, resolution or length, from its Parameter Guide (\`list_models\` with \`model\`). Without them the Provider's defaults apply. Reuse Known-good Options; for anything else, or an option marked suspect, read the Parameter Guide and docs first.
|
|
25842
26265
|
- If the Provider rejects an option (the error says it "rejected an option"), fix it from the Provider's message and the docs and try again, at most twice. If it is still rejected, stop and tell the User what the Provider said.
|
|
25843
|
-
- Tell the User where the files are, and pass on whatever a result says
|
|
26266
|
+
- Tell the User where the files are, and pass on whatever a result says did not match the request or came back short.
|
|
25844
26267
|
- \`generate_comparison\` costs as much as all its Jobs. Start a Comparison only when the User asks for one or agrees to it.
|
|
25845
26268
|
- When the User says what they think of a result, call \`rate_job\` with their verdict and a one-line reason in their words; when it is about one result of a Job with several, pass \`result\`. Never invent a verdict, and never rate a result yourself.
|
|
25846
26269
|
- When the User chooses among a Comparison's results, call \`pick_result\`, only when the User actually chooses. A Pick is not a Rating: if they also give a verdict, call \`rate_job\` too.
|
|
@@ -25856,24 +26279,21 @@ var listedModelSchema = object({
|
|
|
25856
26279
|
modality: _enum(modalities).describe("What the Model generates: images with `generate_image`, videos with `generate_video`."),
|
|
25857
26280
|
default: boolean2().describe("True for the default Model of its Modality."),
|
|
25858
26281
|
notes: string2().describe("Model Notes: what the Model is good or bad at, agreed with the User."),
|
|
25859
|
-
|
|
25860
|
-
aspect_ratios: array(object({ aspect_ratio: _enum(aspectRatios), size: string2() })),
|
|
25861
|
-
reference_images: boolean2(),
|
|
25862
|
-
max_reference_images: number2(),
|
|
25863
|
-
max_images_per_job: number2()
|
|
25864
|
-
}).nullable().describe("An image Model's capabilities; null for a video Model, or when the Model cannot be used (`problem` then says why)."),
|
|
25865
|
-
video_capabilities: object({
|
|
25866
|
-
resolutions: array(object({
|
|
25867
|
-
resolution: string2().nullable().describe("The tier's name to pass as `resolution`; null when the Model takes none."),
|
|
25868
|
-
aspect_ratios: array(object({ aspect_ratio: _enum(aspectRatios), size: string2() }))
|
|
25869
|
-
})),
|
|
25870
|
-
durations: array(number2()).describe("Every clip length in seconds the Model can make."),
|
|
25871
|
-
default_duration: number2(),
|
|
25872
|
-
first_frame: boolean2(),
|
|
25873
|
-
last_frame: boolean2(),
|
|
25874
|
-
max_reference_images: number2()
|
|
25875
|
-
}).optional().describe("A video Model's capabilities; the first resolution is the default."),
|
|
26282
|
+
standing_options: record(string2(), unknown()).describe("Standing Options the User set: request fields every Job on this Model sends. Your `provider_options` are merged over them: your values win, objects merge key by key, and `null` removes a key for that Job."),
|
|
25876
26283
|
docs_url: string2().nullable().describe("Where to read which `provider_options` this Model accepts."),
|
|
26284
|
+
parameters: string2().optional().describe("In brief, what litevision knows about the Model's request parameters: its Parameter Guide, the Provider's own description, or that it knows neither."),
|
|
26285
|
+
parameter_guide: object({
|
|
26286
|
+
family: string2(),
|
|
26287
|
+
checked: string2().describe("When the sources were last checked; read the docs first when this is old."),
|
|
26288
|
+
sources: array(string2()),
|
|
26289
|
+
parameters: array(object({
|
|
26290
|
+
name: string2().describe("The Provider's own field name for `provider_options`, dotted for nested fields."),
|
|
26291
|
+
values: string2(),
|
|
26292
|
+
default: string2(),
|
|
26293
|
+
price: string2().describe("How the value changes the price.")
|
|
26294
|
+
})),
|
|
26295
|
+
notes: array(string2())
|
|
26296
|
+
}).nullable().optional().describe("Only with `model`: the built-in Parameter Guide for the Model's family. Null when litevision has none, or when the Provider describes the Model itself (see `parameter_schema`)."),
|
|
25877
26297
|
known_good_options: record(string2(), unknown()).optional().describe("Known-good Options: `provider_options` whose results matched the request and that the User has not rated bad. Reuse them."),
|
|
25878
26298
|
suspect_options: record(string2(), object({ value: unknown(), reason: string2(), job_id: string2(), cause: _enum(["rating", "mismatch"]) })).optional().describe('`provider_options` last used by a Job the User rated bad (cause "rating", with the User\'s reason) or whose result did not match the request (cause "mismatch"). Check the docs before sending them again.'),
|
|
25879
26299
|
problem: string2().optional(),
|
|
@@ -25904,10 +26324,11 @@ async function listedModel(deps, config, model, isDefault, detailed, ratedJobs)
|
|
|
25904
26324
|
model_id: model.modelId,
|
|
25905
26325
|
modality: model.modality,
|
|
25906
26326
|
default: isDefault,
|
|
25907
|
-
notes: model.notes ?? ""
|
|
26327
|
+
notes: model.notes ?? "",
|
|
26328
|
+
standing_options: model.standingOptions ?? {}
|
|
25908
26329
|
};
|
|
25909
26330
|
const heading = `${model.alias}${isDefault ? ` (default ${model.modality})` : ""}: ${model.modality} Model ${model.modelId} on ${model.provider} via ${model.protocol}.`;
|
|
25910
|
-
const notes = model.notes ? ` Notes: ${model.notes}` : ""
|
|
26331
|
+
const notes = `${model.notes ? ` Notes: ${model.notes}` : ""}${model.standingOptions ? ` Standing Options: ${JSON.stringify(model.standingOptions)}.` : ""}`;
|
|
25911
26332
|
let described;
|
|
25912
26333
|
try {
|
|
25913
26334
|
const provider = config.providers.find((candidate) => candidate.id === model.provider);
|
|
@@ -25916,45 +26337,36 @@ async function listedModel(deps, config, model, isDefault, detailed, ratedJobs)
|
|
|
25916
26337
|
if (model.modality === "video") {
|
|
25917
26338
|
const info = videoModelInfo({ model, provider });
|
|
25918
26339
|
described = {
|
|
25919
|
-
|
|
25920
|
-
|
|
25921
|
-
docsUrl: info.docsUrl
|
|
26340
|
+
docsUrl: info.docsUrl,
|
|
26341
|
+
...info.guide === undefined ? {} : { guide: info.guide }
|
|
25922
26342
|
};
|
|
25923
26343
|
} else {
|
|
25924
26344
|
const info = await modelInfo(deps, { model, provider });
|
|
25925
|
-
const { capabilities } = info;
|
|
25926
26345
|
described = {
|
|
25927
|
-
output: {
|
|
25928
|
-
capabilities: {
|
|
25929
|
-
aspect_ratios: capabilities.sizes.map((size) => ({ aspect_ratio: size.aspectRatio, size: formatSize(size) })),
|
|
25930
|
-
reference_images: capabilities.maxReferences > 0,
|
|
25931
|
-
max_reference_images: capabilities.maxReferences,
|
|
25932
|
-
max_images_per_job: capabilities.maxImages
|
|
25933
|
-
}
|
|
25934
|
-
},
|
|
25935
|
-
summary: describeCapabilities(capabilities),
|
|
25936
26346
|
docsUrl: info.docsUrl,
|
|
25937
|
-
...info.parameterSchema === undefined ? {} : { parameterSchema: info.parameterSchema }
|
|
26347
|
+
...info.parameterSchema === undefined ? {} : { parameterSchema: info.parameterSchema },
|
|
26348
|
+
...info.guide === undefined ? {} : { guide: info.guide }
|
|
25938
26349
|
};
|
|
25939
26350
|
}
|
|
25940
26351
|
} catch (error) {
|
|
25941
26352
|
if (!(error instanceof ConfigError))
|
|
25942
26353
|
throw error;
|
|
25943
26354
|
return {
|
|
25944
|
-
output: { ...base,
|
|
26355
|
+
output: { ...base, docs_url: null, problem: error.message },
|
|
25945
26356
|
text: `${heading} Cannot be used: ${error.message}${notes}`
|
|
25946
26357
|
};
|
|
25947
26358
|
}
|
|
25948
26359
|
const record2 = optionsOf(ratedJobs, model);
|
|
25949
26360
|
const output = {
|
|
25950
26361
|
...base,
|
|
25951
|
-
capabilities: null,
|
|
25952
|
-
...described.output,
|
|
25953
26362
|
docs_url: described.docsUrl,
|
|
26363
|
+
parameters: parametersSummary(described, model.provider),
|
|
25954
26364
|
known_good_options: record2.knownGood,
|
|
25955
26365
|
suspect_options: suspectOutput(record2)
|
|
25956
26366
|
};
|
|
25957
|
-
let text = `${heading}
|
|
26367
|
+
let text = `${heading}${notes} provider_options docs: ${described.docsUrl}`;
|
|
26368
|
+
if (!detailed)
|
|
26369
|
+
text += ` ${output.parameters}`;
|
|
25958
26370
|
const goodKeys = Object.keys(record2.knownGood);
|
|
25959
26371
|
const suspectKeys = Object.keys(record2.suspect);
|
|
25960
26372
|
if (detailed) {
|
|
@@ -25980,11 +26392,11 @@ Recent Picks in Comparisons, newest first:`;
|
|
|
25980
26392
|
for (const picked of picks)
|
|
25981
26393
|
text += `
|
|
25982
26394
|
- ${describeRecentPick(picked)}`;
|
|
25983
|
-
|
|
26395
|
+
output.parameter_guide = described.parameterSchema === undefined && described.guide !== undefined ? guideOutput(described.guide) : null;
|
|
26396
|
+
if (described.parameterSchema !== undefined)
|
|
25984
26397
|
output.parameter_schema = described.parameterSchema;
|
|
25985
|
-
|
|
25986
|
-
|
|
25987
|
-
}
|
|
26398
|
+
text += `
|
|
26399
|
+
${parameterHelp(described, model.provider)}`;
|
|
25988
26400
|
} else {
|
|
25989
26401
|
if (goodKeys.length > 0)
|
|
25990
26402
|
text += ` Known-good Options: ${goodKeys.join(", ")}.`;
|
|
@@ -25993,6 +26405,25 @@ Parameter schema from the Provider: ${JSON.stringify(described.parameterSchema)}
|
|
|
25993
26405
|
}
|
|
25994
26406
|
return { output, text };
|
|
25995
26407
|
}
|
|
26408
|
+
function parametersSummary(known, providerId) {
|
|
26409
|
+
if (known.parameterSchema !== undefined) {
|
|
26410
|
+
return `Provider "${providerId}" describes this Model's parameters itself; \`list_models\` with \`model\` shows its description.`;
|
|
26411
|
+
}
|
|
26412
|
+
if (known.guide !== undefined)
|
|
26413
|
+
return guideSummary(known.guide);
|
|
26414
|
+
return noGuide(known.docsUrl);
|
|
26415
|
+
}
|
|
26416
|
+
function parameterHelp(known, providerId) {
|
|
26417
|
+
if (known.parameterSchema !== undefined) {
|
|
26418
|
+
return `Parameter schema from Provider "${providerId}", which wins over litevision's own Parameter Guide: ${JSON.stringify(known.parameterSchema)}`;
|
|
26419
|
+
}
|
|
26420
|
+
if (known.guide !== undefined)
|
|
26421
|
+
return guideText(known.guide);
|
|
26422
|
+
return noGuide(known.docsUrl);
|
|
26423
|
+
}
|
|
26424
|
+
function noGuide(docsUrl) {
|
|
26425
|
+
return `litevision has no Parameter Guide for this model id; read ${docsUrl} before setting provider_options.`;
|
|
26426
|
+
}
|
|
25996
26427
|
function ratingOutput(rating) {
|
|
25997
26428
|
return {
|
|
25998
26429
|
job_id: rating.jobId,
|
|
@@ -26041,19 +26472,6 @@ function promptStart(prompt) {
|
|
|
26041
26472
|
const line = prompt.replace(/\s+/g, " ").trim();
|
|
26042
26473
|
return line.length <= promptStartLength ? line : `${line.slice(0, promptStartLength).trimEnd()}…`;
|
|
26043
26474
|
}
|
|
26044
|
-
function videoCapabilitiesOutput(capabilities) {
|
|
26045
|
-
return {
|
|
26046
|
-
resolutions: capabilities.resolutions.map((tier) => ({
|
|
26047
|
-
resolution: tier.name ?? null,
|
|
26048
|
-
aspect_ratios: tier.sizes.map((size) => ({ aspect_ratio: size.aspectRatio, size: formatSize(size) }))
|
|
26049
|
-
})),
|
|
26050
|
-
durations: capabilities.durations,
|
|
26051
|
-
default_duration: capabilities.defaultDuration,
|
|
26052
|
-
first_frame: capabilities.firstFrame,
|
|
26053
|
-
last_frame: capabilities.lastFrame,
|
|
26054
|
-
max_reference_images: capabilities.maxReferences
|
|
26055
|
-
};
|
|
26056
|
-
}
|
|
26057
26475
|
function suspectOutput(record2) {
|
|
26058
26476
|
return Object.fromEntries(Object.entries(record2.suspect).map(([key, suspect]) => [
|
|
26059
26477
|
key,
|
|
@@ -26066,22 +26484,47 @@ async function modelOptions(deps, model) {
|
|
|
26066
26484
|
function optionsOf(ratedJobs, model) {
|
|
26067
26485
|
return optionsRecord(ratedJobs, { provider: model.provider, protocol: model.protocol, modelId: model.modelId });
|
|
26068
26486
|
}
|
|
26069
|
-
async function jobDocs(
|
|
26070
|
-
const job = await readJob(dirs, jobId);
|
|
26071
|
-
const
|
|
26072
|
-
if (job === undefined ||
|
|
26487
|
+
async function jobDocs(deps, jobId) {
|
|
26488
|
+
const job = await readJob(deps.dirs, jobId);
|
|
26489
|
+
const adapter = job === undefined ? undefined : adapterForProtocol(job.protocol);
|
|
26490
|
+
if (job === undefined || adapter === undefined)
|
|
26491
|
+
return;
|
|
26492
|
+
const modality = job.modality ?? "image";
|
|
26493
|
+
const guide = adapter.parameterGuide?.(job.modelId, modality);
|
|
26494
|
+
let known = { docsUrl: adapter.docsUrl(job.modelId, modality), ...guide === undefined ? {} : { guide } };
|
|
26495
|
+
const provider = modality === "image" ? (await loadConfig(deps.dirs).catch(() => {
|
|
26073
26496
|
return;
|
|
26074
|
-
|
|
26497
|
+
}))?.providers.find((candidate) => candidate.id === job.provider) : undefined;
|
|
26498
|
+
if (provider !== undefined) {
|
|
26499
|
+
const model = { alias: job.model, provider: job.provider, modelId: job.modelId, protocol: job.protocol, modality };
|
|
26500
|
+
known = await modelInfo(deps, { model, provider });
|
|
26501
|
+
}
|
|
26502
|
+
return { known, provider: job.provider, options: optionsRecord(await readRatedJobs(deps.dirs), job) };
|
|
26075
26503
|
}
|
|
26076
26504
|
function suspectWhy(suspect) {
|
|
26077
26505
|
return suspect.cause === "rating" ? `which the User rated bad: "${suspect.reason}"` : `whose result did not match the request: ${suspect.reason}`;
|
|
26078
26506
|
}
|
|
26507
|
+
function failureHint(known, providerId, record2, kind) {
|
|
26508
|
+
const help = kind === "rejected_option" || kind === "unsupported" ? `
|
|
26509
|
+
${parameterHelp(known, providerId)}` : "";
|
|
26510
|
+
return `${optionsHint(known.docsUrl, record2)}${help}`;
|
|
26511
|
+
}
|
|
26079
26512
|
function optionsHint(docsUrl, record2) {
|
|
26080
26513
|
const good = Object.keys(record2.knownGood).length === 0 ? "none recorded yet" : JSON.stringify(record2.knownGood);
|
|
26081
26514
|
const suspect = Object.entries(record2.suspect).map(([key, entry]) => ` Suspect: ${key}=${JSON.stringify(entry.value)}, used by a Job ${suspectWhy(entry)}.`).join("");
|
|
26082
26515
|
return ` Docs for this Model's provider_options: ${docsUrl}. Known-good Options: ${good}.${suspect}`;
|
|
26083
26516
|
}
|
|
26084
26517
|
var observedSchema = array(object({ width: number2(), height: number2(), seconds: number2().optional() }).nullable()).describe("What each saved file turned out to be, in the order of `files`, read from the file itself; null where litevision could not tell.");
|
|
26518
|
+
var requestedShapeSchema = string2().nullable().describe("The shape the request sent asks for, as litevision read it from the Model's own fields, e.g. 16:9 or 3840x2160; null when it asks for none litevision can read, so the result's shape is not checked. Compare it with `observed`.");
|
|
26519
|
+
var requestedResolutionSchema = string2().nullable().describe("The resolution tier the request sent asks for, as written in its Protocol's tier field, e.g. 4K or 1080P; null when it sets none. Not checked against the result.");
|
|
26520
|
+
var requestedDurationSchema = number2().nullable().describe("Video Jobs: the length in seconds the request sent asks for, as litevision read it; null when unreadable, so the length is not checked.");
|
|
26521
|
+
function requestedOutput(requested) {
|
|
26522
|
+
return {
|
|
26523
|
+
requested_shape: requested.shape,
|
|
26524
|
+
requested_resolution: requested.resolution,
|
|
26525
|
+
...requested.seconds === undefined ? {} : { requested_duration: requested.seconds }
|
|
26526
|
+
};
|
|
26527
|
+
}
|
|
26085
26528
|
var mismatchSchema = string2().optional().describe("Present when the result's shape or length differs from the request: an option may not have taken effect. Tell the User.");
|
|
26086
26529
|
function observedText(observed) {
|
|
26087
26530
|
const known = [...new Set(observed.flatMap((facts) => facts === null ? [] : [describeFacts(facts)]))];
|
|
@@ -26090,6 +26533,14 @@ function observedText(observed) {
|
|
|
26090
26533
|
function describeFacts(facts) {
|
|
26091
26534
|
return `${facts.width}x${facts.height}${facts.seconds === undefined ? "" : `, ${facts.seconds} s`}`;
|
|
26092
26535
|
}
|
|
26536
|
+
function requestedText({ shape, resolution, seconds }) {
|
|
26537
|
+
const size = shape === null ? resolution : resolution === null ? shape : `${shape} at ${resolution}`;
|
|
26538
|
+
const read = [size, typeof seconds === "number" ? `${seconds} s` : null].filter((part) => part !== null);
|
|
26539
|
+
const unread = [shape === null ? "shape" : null, seconds === null ? "length" : null].filter((part) => part !== null);
|
|
26540
|
+
const asked = read.length === 0 ? "" : `The request asks for ${read.join(", ")}.`;
|
|
26541
|
+
const unchecked = unread.length === 0 ? "" : `The request asks for no ${unread.join(" or ")} litevision can read, so the result's ${unread.join(" and ")} ${unread.length === 1 ? "was" : "were"} not checked.`;
|
|
26542
|
+
return [asked, unchecked].filter(Boolean).join(" ");
|
|
26543
|
+
}
|
|
26093
26544
|
function mismatchText(mismatch, docsUrl) {
|
|
26094
26545
|
if (mismatch === undefined)
|
|
26095
26546
|
return "";
|
|
@@ -26104,7 +26555,8 @@ function jobOutput(job) {
|
|
|
26104
26555
|
files: job.status === "completed" ? job.files : [],
|
|
26105
26556
|
observed: job.status === "completed" ? job.observed : [],
|
|
26106
26557
|
...job.status === "completed" && job.shortfall !== undefined ? { shortfall: job.shortfall } : {},
|
|
26107
|
-
...job.status === "completed" && job.mismatch !== undefined ? { mismatch: job.mismatch } : {}
|
|
26558
|
+
...job.status === "completed" && job.mismatch !== undefined ? { mismatch: job.mismatch } : {},
|
|
26559
|
+
...job.status === "completed" && job.requested !== undefined ? requestedOutput(job.requested) : {}
|
|
26108
26560
|
};
|
|
26109
26561
|
}
|
|
26110
26562
|
function jobText(job) {
|
|
@@ -26122,7 +26574,7 @@ async function failureReason(error, hint) {
|
|
|
26122
26574
|
return error.message;
|
|
26123
26575
|
if (error instanceof JobFailedError) {
|
|
26124
26576
|
const helpful = hint !== undefined && (error.kind === "unsupported" || error.kind === "rejected_option" || error.kind === "other");
|
|
26125
|
-
return `${error.message}${helpful ? await hint() : ""}`;
|
|
26577
|
+
return `${error.message}${helpful ? await hint(error.kind) : ""}`;
|
|
26126
26578
|
}
|
|
26127
26579
|
throw error;
|
|
26128
26580
|
}
|
|
@@ -26131,10 +26583,8 @@ var imageJobShape = {
|
|
|
26131
26583
|
status: jobStatusSchema,
|
|
26132
26584
|
model: string2().describe("Alias of the Model that ran the Job."),
|
|
26133
26585
|
files: array(string2()).describe("Paths of the saved images; empty while the Job is running."),
|
|
26134
|
-
|
|
26135
|
-
|
|
26136
|
-
requested_aspect_ratio: _enum(aspectRatios),
|
|
26137
|
-
aspect_ratio_adjusted: boolean2().describe("True when the Model does not support the requested aspect ratio and the closest one was used."),
|
|
26586
|
+
requested_shape: requestedShapeSchema,
|
|
26587
|
+
requested_resolution: requestedResolutionSchema,
|
|
26138
26588
|
shortfall: string2().optional().describe("Present when fewer images than `n` were saved: how many, and why."),
|
|
26139
26589
|
observed: observedSchema,
|
|
26140
26590
|
mismatch: mismatchSchema
|
|
@@ -26144,13 +26594,9 @@ var videoJobShape = {
|
|
|
26144
26594
|
status: jobStatusSchema,
|
|
26145
26595
|
model: string2().describe("Alias of the Model that ran the Job."),
|
|
26146
26596
|
files: array(string2()).describe("Path of the saved video; empty while the Job is running."),
|
|
26147
|
-
|
|
26148
|
-
|
|
26149
|
-
|
|
26150
|
-
duration: number2().describe("Length in seconds actually requested."),
|
|
26151
|
-
requested_aspect_ratio: _enum(aspectRatios),
|
|
26152
|
-
aspect_ratio_adjusted: boolean2(),
|
|
26153
|
-
duration_adjusted: boolean2().describe("True when the Model cannot make the requested length and the closest one was used."),
|
|
26597
|
+
requested_shape: requestedShapeSchema,
|
|
26598
|
+
requested_duration: requestedDurationSchema,
|
|
26599
|
+
requested_resolution: requestedResolutionSchema,
|
|
26154
26600
|
observed: observedSchema,
|
|
26155
26601
|
mismatch: mismatchSchema
|
|
26156
26602
|
};
|
|
@@ -26183,23 +26629,21 @@ function createServer2(deps) {
|
|
|
26183
26629
|
const adapter = adapterFor(model);
|
|
26184
26630
|
const credential = await requireCredential(deps.dirs, resolved.provider);
|
|
26185
26631
|
const info = await modelInfo(infoDeps, resolved);
|
|
26186
|
-
const normalised = await normaliseImageRequest({
|
|
26632
|
+
const normalised = await normaliseImageRequest({
|
|
26633
|
+
prompt: args.prompt,
|
|
26634
|
+
n: args.n,
|
|
26635
|
+
references: args.references,
|
|
26636
|
+
providerOptions: mergeOptions(model.standingOptions ?? {}, args.provider_options)
|
|
26637
|
+
}, model, adapter, cwd);
|
|
26187
26638
|
const outputDir = args.output_path === undefined ? undefined : resolve2(cwd, args.output_path);
|
|
26188
26639
|
return {
|
|
26189
26640
|
model,
|
|
26190
26641
|
start: (comparison) => jobs.start({ ...resolved, adapter, credential, normalised, outputDir, ...comparison === undefined ? {} : { comparison } }),
|
|
26191
|
-
hint: async () =>
|
|
26642
|
+
hint: async (kind) => failureHint(info, model.provider, await modelOptions(deps, model), kind),
|
|
26192
26643
|
report: (job) => {
|
|
26193
|
-
const
|
|
26194
|
-
const
|
|
26195
|
-
|
|
26196
|
-
aspect_ratio: size.aspectRatio,
|
|
26197
|
-
size: formatSize(size),
|
|
26198
|
-
requested_aspect_ratio: normalised.requestedAspectRatio,
|
|
26199
|
-
aspect_ratio_adjusted: normalised.aspectRatioAdjusted
|
|
26200
|
-
};
|
|
26201
|
-
const adjustment = normalised.aspectRatioAdjusted ? ` ${model.alias} does not support ${normalised.requestedAspectRatio}, so the closest supported aspect ratio was used.` : "";
|
|
26202
|
-
const text = job.status === "running" ? `${jobText(job)} It asks ${model.alias} for ${size.aspectRatio} (${formatSize(size)}).${adjustment}` : `Saved ${job.files.length} image(s) from ${model.alias} at ${size.aspectRatio} (${formatSize(size)}), Job ${job.id}.${adjustment}${observedText(job.observed)}${mismatchText(job.mismatch, info.docsUrl)}${job.shortfall === undefined ? "" : ` ${job.shortfall}`}
|
|
26644
|
+
const output = { ...jobOutput(job), ...requestedOutput(normalised.requested) };
|
|
26645
|
+
const asked = requestedText(normalised.requested);
|
|
26646
|
+
const text = job.status === "running" ? `${jobText(job)} ${asked}` : `Saved ${job.files.length} image(s) from ${model.alias}, Job ${job.id}. ${asked}${observedText(job.observed)}${mismatchText(job.mismatch, info.docsUrl)}${job.shortfall === undefined ? "" : ` ${job.shortfall}`}
|
|
26203
26647
|
${job.files.join(`
|
|
26204
26648
|
`)}`;
|
|
26205
26649
|
return { output, text };
|
|
@@ -26213,37 +26657,20 @@ ${job.files.join(`
|
|
|
26213
26657
|
const credential = await requireCredential(deps.dirs, resolved.provider);
|
|
26214
26658
|
const normalised = await normaliseVideoRequest({
|
|
26215
26659
|
prompt: args.prompt,
|
|
26216
|
-
...args.aspect_ratio === undefined ? {} : { aspectRatio: args.aspect_ratio },
|
|
26217
|
-
...args.duration === undefined ? {} : { duration: args.duration },
|
|
26218
|
-
...args.resolution === undefined ? {} : { resolution: args.resolution },
|
|
26219
26660
|
...args.first_frame === undefined ? {} : { firstFrame: args.first_frame },
|
|
26220
26661
|
...args.last_frame === undefined ? {} : { lastFrame: args.last_frame },
|
|
26221
26662
|
references: args.references,
|
|
26222
|
-
providerOptions: args.provider_options
|
|
26223
|
-
}, model, adapter,
|
|
26663
|
+
providerOptions: mergeOptions(model.standingOptions ?? {}, args.provider_options)
|
|
26664
|
+
}, model, adapter, cwd);
|
|
26224
26665
|
const outputDir = args.output_path === undefined ? undefined : resolve2(cwd, args.output_path);
|
|
26225
26666
|
return {
|
|
26226
26667
|
model,
|
|
26227
26668
|
start: (comparison) => jobs.startVideo({ ...resolved, adapter, credential, normalised, outputDir, ...comparison === undefined ? {} : { comparison } }),
|
|
26228
|
-
hint: async () =>
|
|
26669
|
+
hint: async (kind) => failureHint(info, model.provider, await modelOptions(deps, model), kind),
|
|
26229
26670
|
report: (job) => {
|
|
26230
|
-
const {
|
|
26231
|
-
const
|
|
26232
|
-
|
|
26233
|
-
aspect_ratio: size.aspectRatio,
|
|
26234
|
-
size: formatSize(size),
|
|
26235
|
-
resolution: normalised.request.resolution ?? null,
|
|
26236
|
-
duration: seconds,
|
|
26237
|
-
requested_aspect_ratio: normalised.requestedAspectRatio,
|
|
26238
|
-
aspect_ratio_adjusted: normalised.aspectRatioAdjusted,
|
|
26239
|
-
duration_adjusted: normalised.durationAdjusted
|
|
26240
|
-
};
|
|
26241
|
-
const asked = `${seconds} s at ${size.aspectRatio}${normalised.request.resolution === undefined ? "" : ` ${normalised.request.resolution}`} (${formatSize(size)})`;
|
|
26242
|
-
const adjustments = [
|
|
26243
|
-
normalised.aspectRatioAdjusted ? `${model.alias} does not support ${normalised.requestedAspectRatio}, so the closest supported aspect ratio was used.` : "",
|
|
26244
|
-
normalised.durationAdjusted ? `${model.alias} cannot make ${normalised.requestedDuration} s, so the closest length it can was used.` : ""
|
|
26245
|
-
].filter(Boolean).map((sentence) => ` ${sentence}`).join("");
|
|
26246
|
-
const text = job.status === "running" ? `${jobText(job)} It asks ${model.alias} for ${asked}.${adjustments}` : `Saved a video from ${model.alias}, ${asked}, Job ${job.id}.${adjustments}${observedText(job.observed)}${mismatchText(job.mismatch, info.docsUrl)}
|
|
26671
|
+
const output = { ...jobOutput(job), ...requestedOutput(normalised.requested) };
|
|
26672
|
+
const asked = requestedText(normalised.requested);
|
|
26673
|
+
const text = job.status === "running" ? `${jobText(job)} ${asked}` : `Saved a video from ${model.alias}, Job ${job.id}. ${asked}${observedText(job.observed)}${mismatchText(job.mismatch, info.docsUrl)}
|
|
26247
26674
|
${job.files.join(`
|
|
26248
26675
|
`)}`;
|
|
26249
26676
|
return { output, text };
|
|
@@ -26262,68 +26689,60 @@ ${job.files.join(`
|
|
|
26262
26689
|
};
|
|
26263
26690
|
server.registerTool("generate_image", {
|
|
26264
26691
|
description: "Generate images from a text prompt with one of the User's configured Models. Saves the images locally and returns their file paths.",
|
|
26265
|
-
inputSchema: {
|
|
26692
|
+
inputSchema: strictObject({
|
|
26266
26693
|
prompt: string2().min(1).describe("Sent to the Model verbatim."),
|
|
26267
26694
|
model: string2().optional().describe("Alias of the Model to use. Omit to use the default image Model."),
|
|
26268
|
-
n: number2().int().positive().default(1).describe("How many images
|
|
26269
|
-
aspect_ratio: _enum(aspectRatios).default("1:1").describe("Wanted aspect ratio. If the Model does not support it, the closest supported one is used and reported."),
|
|
26695
|
+
n: number2().int().positive().default(1).describe("How many images this Job brings back: sent as the Model's own count field, or as that many requests where it has none."),
|
|
26270
26696
|
references: array(string2().min(1)).default([]).describe("Local paths of reference images to edit or draw on. Relative paths resolve against litevision's working directory."),
|
|
26271
26697
|
output_path: string2().min(1).optional().describe("Folder to save the images in, e.g. the project's assets folder; created if missing. Relative paths resolve against litevision's working directory. Omit to save under a dated folder in the User's home directory."),
|
|
26272
|
-
provider_options: record(string2(), unknown()).default({}).describe('
|
|
26273
|
-
},
|
|
26698
|
+
provider_options: record(string2(), unknown()).default({}).describe(`The Model's own request fields, passed through untouched, e.g. { "size": "2048x1152", "quality": "high" } for OpenAI: see its Parameter Guide in \`list_models\`. Merged over the Model's Standing Options. Only the model id, prompt, reference image fields and response format are litevision's.`)
|
|
26699
|
+
}),
|
|
26274
26700
|
outputSchema: imageJobShape
|
|
26275
26701
|
}, async ({ model: alias, ...args }) => runSingle(async () => prepareImage(resolveModel(await loadConfig(deps.dirs), alias), args)));
|
|
26276
26702
|
server.registerTool("generate_video", {
|
|
26277
26703
|
description: "Generate one video from a text prompt with one of the User's configured video Models, optionally from a first and last frame or reference images. Saves the video locally and returns its file path. Videos often take minutes: the Job then keeps running in the background.",
|
|
26278
|
-
inputSchema: {
|
|
26704
|
+
inputSchema: strictObject({
|
|
26279
26705
|
prompt: string2().min(1).describe("Sent to the Model verbatim. Describe motion and camera movement, not only the scene."),
|
|
26280
26706
|
model: string2().optional().describe("Alias of the video Model to use. Omit to use the default video Model."),
|
|
26281
|
-
aspect_ratio: _enum(aspectRatios).optional().describe("Wanted aspect ratio. Omit for 16:9, or for the first frame's shape when `first_frame` is given. If the Model does not support it, the closest supported one is used and reported."),
|
|
26282
|
-
duration: number2().int().positive().optional().describe("Wanted length in seconds. Omit for the Model's default. If the Model cannot make it, the closest length it can is used and reported."),
|
|
26283
|
-
resolution: string2().min(1).optional().describe("A resolution tier the Model lists in `list_models`, e.g. 720P. Omit for the Model's default."),
|
|
26284
26707
|
first_frame: string2().min(1).optional().describe("Local path of an image the video starts from."),
|
|
26285
26708
|
last_frame: string2().min(1).optional().describe("Local path of an image the video ends on."),
|
|
26286
26709
|
references: array(string2().min(1)).default([]).describe("Local paths of reference images for characters, objects or style. Relative paths resolve against litevision's working directory."),
|
|
26287
26710
|
output_path: string2().min(1).optional().describe("Folder to save the video in; created if missing. Omit to save under a dated folder in the User's home directory."),
|
|
26288
|
-
provider_options: record(string2(), unknown()).default({}).describe('
|
|
26289
|
-
},
|
|
26711
|
+
provider_options: record(string2(), unknown()).default({}).describe(`The Model's own request fields, passed through untouched, e.g. { "resolution": "1080P", "ratio": "9:16", "seconds": "10" } for Wan 2.7: see its Parameter Guide in \`list_models\`. Merged over the Model's Standing Options. Only the model id, prompt and media fields are litevision's.`)
|
|
26712
|
+
}),
|
|
26290
26713
|
outputSchema: videoJobShape
|
|
26291
26714
|
}, async ({ model: alias, ...args }) => runSingle(async () => prepareVideo(resolveModel(await loadConfig(deps.dirs), alias, "video"), args)));
|
|
26292
26715
|
server.registerTool("generate_comparison", {
|
|
26293
26716
|
description: `Start a Comparison: 2 to ${maxComparisonVariants} Jobs together for one request, differing in prompt, in Model, or both, so the User can choose among their results. All variants' Models must share one Modality. Start one only when the User asks for a Comparison or agrees to one: it costs as much as all its Jobs. Saves each Job's files locally and returns them under one Comparison id.`,
|
|
26294
|
-
inputSchema: {
|
|
26295
|
-
variants: array(
|
|
26717
|
+
inputSchema: strictObject({
|
|
26718
|
+
variants: array(strictObject({
|
|
26296
26719
|
prompt: string2().min(1).describe("Sent to this variant's Model verbatim."),
|
|
26297
26720
|
model: string2().optional().describe("Alias of this variant's Model. Omit to use the default Model of the Comparison's Modality."),
|
|
26298
|
-
n: number2().int().positive().optional().describe("Image Comparisons only: how many images this variant's Job makes
|
|
26721
|
+
n: number2().int().positive().optional().describe("Image Comparisons only: how many images this variant's Job makes. Defaults to 1."),
|
|
26722
|
+
provider_options: record(string2(), unknown()).default({}).describe("This variant's own request fields, in its Model's own names (see its Parameter Guide), merged over its Standing Options.")
|
|
26299
26723
|
})).min(2).max(maxComparisonVariants).describe(`The Jobs to start, 2 to ${maxComparisonVariants}, one per variant.`),
|
|
26300
26724
|
modality: _enum(modalities).optional().describe("image or video. Omit to take it from the variants' Models; when no variant names one, image."),
|
|
26301
|
-
aspect_ratio: _enum(aspectRatios).optional().describe("Wanted aspect ratio for every Job. Omit for 1:1 images or 16:9 videos. Each Model that does not support it uses its closest one."),
|
|
26302
26725
|
references: array(string2().min(1)).default([]).describe("Local paths of reference images every Job gets. Relative paths resolve against litevision's working directory."),
|
|
26303
|
-
output_path: string2().min(1).optional().describe("Folder to save every Job's files in; created if missing. Omit to save under a dated folder in the User's home directory.")
|
|
26304
|
-
|
|
26305
|
-
},
|
|
26726
|
+
output_path: string2().min(1).optional().describe("Folder to save every Job's files in; created if missing. Omit to save under a dated folder in the User's home directory.")
|
|
26727
|
+
}),
|
|
26306
26728
|
outputSchema: {
|
|
26307
26729
|
comparison_id: string2(),
|
|
26308
26730
|
modality: _enum(modalities),
|
|
26309
26731
|
jobs: array(union([object(imageJobShape), object(videoJobShape), failedJobSchema])).describe('One per variant, in the order given: each as `generate_image` or `generate_video` returns it, or status "failed" with why. A "running" Job continues in the background; call `get_job` with its `job_id`.')
|
|
26310
26732
|
}
|
|
26311
|
-
}, async ({ variants, modality: wanted,
|
|
26733
|
+
}, async ({ variants, modality: wanted, references, output_path }) => {
|
|
26312
26734
|
let prepared;
|
|
26313
26735
|
let modality;
|
|
26314
26736
|
try {
|
|
26315
26737
|
const config = await loadConfig(deps.dirs);
|
|
26316
26738
|
modality = comparisonModality(config, variants, wanted);
|
|
26317
|
-
if (modality === "image" && duration !== undefined) {
|
|
26318
|
-
throw new ConfigError("`duration` applies only to a video Comparison; leave it out for images.");
|
|
26319
|
-
}
|
|
26320
26739
|
if (modality === "video" && variants.some((variant) => (variant.n ?? 1) > 1)) {
|
|
26321
26740
|
throw new ConfigError("A video Job makes one video, so leave `n` out of a video Comparison's variants.");
|
|
26322
26741
|
}
|
|
26323
26742
|
prepared = await Promise.all(variants.map((variant) => {
|
|
26324
26743
|
const resolved = resolveModel(config, variant.model, modality);
|
|
26325
|
-
const shared = { prompt: variant.prompt, references, provider_options:
|
|
26326
|
-
return modality === "video" ? prepareVideo(resolved,
|
|
26744
|
+
const shared = { prompt: variant.prompt, references, provider_options: variant.provider_options, ...output_path === undefined ? {} : { output_path } };
|
|
26745
|
+
return modality === "video" ? prepareVideo(resolved, shared) : prepareImage(resolved, { ...shared, n: variant.n ?? 1 });
|
|
26327
26746
|
}));
|
|
26328
26747
|
} catch (error) {
|
|
26329
26748
|
return failure2(error, undefined);
|
|
@@ -26363,21 +26782,24 @@ ${job.files.join(`
|
|
|
26363
26782
|
model: string2().describe("Alias of the Model that ran the Job."),
|
|
26364
26783
|
files: array(string2()).describe("Paths of the saved images or video; empty while the Job is running."),
|
|
26365
26784
|
shortfall: string2().optional().describe("Present when fewer images than asked for were saved: how many, and why."),
|
|
26785
|
+
requested_shape: requestedShapeSchema.optional(),
|
|
26786
|
+
requested_duration: requestedDurationSchema.optional(),
|
|
26787
|
+
requested_resolution: requestedResolutionSchema.optional(),
|
|
26366
26788
|
observed: observedSchema,
|
|
26367
26789
|
mismatch: mismatchSchema
|
|
26368
26790
|
}
|
|
26369
26791
|
}, async ({ job_id }) => {
|
|
26370
26792
|
try {
|
|
26371
26793
|
const job = await jobs.get(job_id);
|
|
26372
|
-
const docsUrl = job.status === "completed" && job.mismatch !== undefined ? (await jobDocs(
|
|
26794
|
+
const docsUrl = job.status === "completed" && job.mismatch !== undefined ? (await jobDocs(infoDeps, job_id))?.known.docsUrl ?? "the Model's docs link in `list_models`" : "";
|
|
26373
26795
|
const text = job.status === "running" ? jobText(job) : `Job ${job.id} completed: saved ${job.files.length} file(s) from ${job.model}.${observedText(job.observed)}${mismatchText(job.mismatch, docsUrl)}${job.shortfall === undefined ? "" : ` ${job.shortfall}`}
|
|
26374
26796
|
${job.files.join(`
|
|
26375
26797
|
`)}`;
|
|
26376
26798
|
return { content: [{ type: "text", text }], structuredContent: jobOutput(job) };
|
|
26377
26799
|
} catch (error) {
|
|
26378
|
-
return failure2(error, async () => {
|
|
26379
|
-
const docs = await jobDocs(
|
|
26380
|
-
return docs === undefined ? "" :
|
|
26800
|
+
return failure2(error, async (kind) => {
|
|
26801
|
+
const docs = await jobDocs(infoDeps, job_id);
|
|
26802
|
+
return docs === undefined ? "" : failureHint(docs.known, docs.provider, docs.options, kind);
|
|
26381
26803
|
});
|
|
26382
26804
|
}
|
|
26383
26805
|
});
|
|
@@ -26437,7 +26859,7 @@ ${job.files.join(`
|
|
|
26437
26859
|
}
|
|
26438
26860
|
});
|
|
26439
26861
|
server.registerTool("list_models", {
|
|
26440
|
-
description: "List the User's configured Models: Alias, Modality (image or video), Provider, Protocol,
|
|
26862
|
+
description: "List the User's configured Models: Alias, Modality (image or video), Provider, Protocol, Model Notes, Standing Options, a summary of each Model's Parameter Guide, the Known-good Options for `provider_options` and a docs link, and which Models are the defaults. Use it to choose a Model by what it accepts and costs. Pass `model` for one Model in detail: its full Parameter Guide (or the Provider's own parameter schema, which wins), the values of its Known-good and suspect options, its five most recent Ratings, and the Picks of up to five Comparisons it took part in (most recently picked first).",
|
|
26441
26863
|
inputSchema: {
|
|
26442
26864
|
model: string2().optional().describe("Alias of one Model to describe in detail. Omit to list every Model.")
|
|
26443
26865
|
},
|
|
@@ -26533,19 +26955,6 @@ Now: ${change.notes || "(none)"}`;
|
|
|
26533
26955
|
const text = "Opened the litevision settings page in the User's browser. Ask the User to finish setting up there and tell you when they are done. Never ask the User for a Credential in the conversation.";
|
|
26534
26956
|
return { content: [{ type: "text", text }] };
|
|
26535
26957
|
});
|
|
26536
|
-
server.registerPrompt("settings", {
|
|
26537
|
-
title: "litevision settings",
|
|
26538
|
-
description: "Open litevision's settings page to add Providers, enter Credentials and set up Models."
|
|
26539
|
-
}, async () => {
|
|
26540
|
-
let text;
|
|
26541
|
-
try {
|
|
26542
|
-
await settings.open();
|
|
26543
|
-
text = "I have already opened litevision's settings page in my browser. Do not call `open_settings`. Wait until I say I am done there. Never ask me for a Credential in the conversation.";
|
|
26544
|
-
} catch {
|
|
26545
|
-
text = "I tried to open litevision's settings page, but it could not open my browser. Tell me to run `litevision settings` in a terminal, which opens the page and prints its address.";
|
|
26546
|
-
}
|
|
26547
|
-
return { messages: [{ role: "user", content: { type: "text", text } }] };
|
|
26548
|
-
});
|
|
26549
26958
|
return server;
|
|
26550
26959
|
}
|
|
26551
26960
|
|