@shop-os/modelkit 0.4.4 → 0.4.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/README.md +4 -0
- package/dist/catalog.cjs +23 -6
- package/dist/catalog.cjs.map +1 -1
- package/dist/catalog.js +23 -6
- package/dist/catalog.js.map +1 -1
- package/dist/index.cjs +178 -101
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +178 -101
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -3770,6 +3770,11 @@ var arkAdapter = {
|
|
|
3770
3770
|
async generateVideo(req, ctx) {
|
|
3771
3771
|
throwIfAborted(ctx.signal);
|
|
3772
3772
|
return generateSeedanceVideo(req, ctx);
|
|
3773
|
+
},
|
|
3774
|
+
resumeVideo(req, jobId, ctx) {
|
|
3775
|
+
throwIfAborted(ctx.signal);
|
|
3776
|
+
const key = arkKey(ctx);
|
|
3777
|
+
return seedanceJob(ctx, key, jobId, seedanceUsage(ctx, req, seedanceInitialDuration(req)));
|
|
3773
3778
|
}
|
|
3774
3779
|
};
|
|
3775
3780
|
function arkKey(ctx) {
|
|
@@ -3939,6 +3944,9 @@ function seedanceUsage(ctx, req, duration) {
|
|
|
3939
3944
|
if (req.resolution !== void 0) usage2.resolution = req.resolution;
|
|
3940
3945
|
return usage2;
|
|
3941
3946
|
}
|
|
3947
|
+
function seedanceInitialDuration(req) {
|
|
3948
|
+
return req.durationSeconds ?? 5;
|
|
3949
|
+
}
|
|
3942
3950
|
function seedanceErrorMessage(error, status) {
|
|
3943
3951
|
const asString = import_zod7.z.string().safeParse(error);
|
|
3944
3952
|
if (asString.success) return asString.data;
|
|
@@ -3946,6 +3954,78 @@ function seedanceErrorMessage(error, status) {
|
|
|
3946
3954
|
if (asObject.success && asObject.data.message !== void 0) return asObject.data.message;
|
|
3947
3955
|
return `Seedance task ${status}`;
|
|
3948
3956
|
}
|
|
3957
|
+
function seedanceJob(ctx, key, jobId, usage2) {
|
|
3958
|
+
const modelId = ctx.model.id;
|
|
3959
|
+
const fetchStatus = async () => {
|
|
3960
|
+
const res = await arkFetch(ctx, `${arkBaseUrl(ctx)}${SUBMIT_PATH}/${jobId}`, {
|
|
3961
|
+
headers: { Authorization: `Bearer ${key}` }
|
|
3962
|
+
});
|
|
3963
|
+
if (!res.ok) {
|
|
3964
|
+
if (ctx.call.maxRetries !== 0 && (res.status === 429 || res.status >= 500))
|
|
3965
|
+
return void 0;
|
|
3966
|
+
const text2 = await res.text().catch(() => "");
|
|
3967
|
+
throwArkError(res.status, text2, modelId);
|
|
3968
|
+
}
|
|
3969
|
+
const parsed = SeedanceResponseSchema.safeParse(await res.json());
|
|
3970
|
+
if (!parsed.success) return {};
|
|
3971
|
+
return parsed.data;
|
|
3972
|
+
};
|
|
3973
|
+
return {
|
|
3974
|
+
jobId,
|
|
3975
|
+
async poll() {
|
|
3976
|
+
const polled = await fetchStatus();
|
|
3977
|
+
if (!polled) return { status: "running" };
|
|
3978
|
+
const status = (polled.status ?? "queued").toLowerCase();
|
|
3979
|
+
if (status === "succeeded") return { status: "succeeded", raw: polled };
|
|
3980
|
+
if (status === "failed" || status === "expired" || status === "cancelled") {
|
|
3981
|
+
return { status: "failed", raw: polled };
|
|
3982
|
+
}
|
|
3983
|
+
if (status === "queued" || status === "pending")
|
|
3984
|
+
return { status: "pending", raw: polled };
|
|
3985
|
+
return { status: "running", raw: polled };
|
|
3986
|
+
},
|
|
3987
|
+
async result(opts) {
|
|
3988
|
+
const optSignal = opts?.signal ?? ctx.signal;
|
|
3989
|
+
const pollOpts = {
|
|
3990
|
+
intervalMs: opts?.pollIntervalMs ?? SEEDANCE_POLL_INTERVAL_MS,
|
|
3991
|
+
timeoutMs: opts?.timeoutMs ?? RESULT_TIMEOUT_MS
|
|
3992
|
+
};
|
|
3993
|
+
if (optSignal !== void 0) pollOpts.signal = optSignal;
|
|
3994
|
+
return pollUntil(async () => {
|
|
3995
|
+
const polled = await fetchStatus();
|
|
3996
|
+
if (!polled) return void 0;
|
|
3997
|
+
const status = (polled.status ?? "queued").toLowerCase();
|
|
3998
|
+
if (status === "succeeded") {
|
|
3999
|
+
const url = polled.content?.video_url;
|
|
4000
|
+
if (!url)
|
|
4001
|
+
throwArkError(
|
|
4002
|
+
void 0,
|
|
4003
|
+
"Seedance task succeeded but returned no video_url",
|
|
4004
|
+
modelId
|
|
4005
|
+
);
|
|
4006
|
+
if (polled.duration !== void 0 && polled.duration > 0) {
|
|
4007
|
+
usage2.quantity = polled.duration;
|
|
4008
|
+
usage2.durationSeconds = polled.duration;
|
|
4009
|
+
}
|
|
4010
|
+
const tokens = polled.usage;
|
|
4011
|
+
if (tokens?.total_tokens !== void 0 && tokens.completion_tokens !== void 0) {
|
|
4012
|
+
usage2.inputTokens = tokens.total_tokens - tokens.completion_tokens;
|
|
4013
|
+
usage2.outputTokens = tokens.completion_tokens;
|
|
4014
|
+
}
|
|
4015
|
+
return {
|
|
4016
|
+
video: { url, expiresAt: new Date(Date.now() + VIDEO_URL_TTL_MS) },
|
|
4017
|
+
usage: usage2,
|
|
4018
|
+
raw: polled
|
|
4019
|
+
};
|
|
4020
|
+
}
|
|
4021
|
+
if (status === "failed" || status === "expired" || status === "cancelled") {
|
|
4022
|
+
throwArkError(void 0, seedanceErrorMessage(polled.error, status), modelId);
|
|
4023
|
+
}
|
|
4024
|
+
return void 0;
|
|
4025
|
+
}, pollOpts);
|
|
4026
|
+
}
|
|
4027
|
+
};
|
|
4028
|
+
}
|
|
3949
4029
|
async function generateSeedanceVideo(req, ctx) {
|
|
3950
4030
|
const modelId = ctx.model.id;
|
|
3951
4031
|
const taskType = req.taskType;
|
|
@@ -3960,7 +4040,7 @@ async function generateSeedanceVideo(req, ctx) {
|
|
|
3960
4040
|
});
|
|
3961
4041
|
}
|
|
3962
4042
|
const key = arkKey(ctx);
|
|
3963
|
-
const requestedDuration = req
|
|
4043
|
+
const requestedDuration = seedanceInitialDuration(req);
|
|
3964
4044
|
let duration = requestedDuration;
|
|
3965
4045
|
if (taskType === "edit") duration = -1;
|
|
3966
4046
|
const resolution = req.resolution ?? "720p";
|
|
@@ -4041,75 +4121,7 @@ async function generateSeedanceVideo(req, ctx) {
|
|
|
4041
4121
|
if (!submit.id) throwArkError(void 0, "Seedance task create returned no id", modelId);
|
|
4042
4122
|
const jobId = submit.id;
|
|
4043
4123
|
const usage2 = seedanceUsage(ctx, req, taskType === "edit" ? requestedDuration : duration);
|
|
4044
|
-
|
|
4045
|
-
const res = await arkFetch(ctx, `${arkBaseUrl(ctx)}${SUBMIT_PATH}/${jobId}`, {
|
|
4046
|
-
headers: { Authorization: `Bearer ${key}` }
|
|
4047
|
-
});
|
|
4048
|
-
if (!res.ok) {
|
|
4049
|
-
if (ctx.call.maxRetries !== 0 && (res.status === 429 || res.status >= 500))
|
|
4050
|
-
return void 0;
|
|
4051
|
-
const text2 = await res.text().catch(() => "");
|
|
4052
|
-
throwArkError(res.status, text2, modelId);
|
|
4053
|
-
}
|
|
4054
|
-
const parsed = SeedanceResponseSchema.safeParse(await res.json());
|
|
4055
|
-
if (!parsed.success) return {};
|
|
4056
|
-
return parsed.data;
|
|
4057
|
-
};
|
|
4058
|
-
return {
|
|
4059
|
-
jobId,
|
|
4060
|
-
async poll() {
|
|
4061
|
-
const polled = await fetchStatus();
|
|
4062
|
-
if (!polled) return { status: "running" };
|
|
4063
|
-
const status = (polled.status ?? "queued").toLowerCase();
|
|
4064
|
-
if (status === "succeeded") return { status: "succeeded", raw: polled };
|
|
4065
|
-
if (status === "failed" || status === "expired" || status === "cancelled") {
|
|
4066
|
-
return { status: "failed", raw: polled };
|
|
4067
|
-
}
|
|
4068
|
-
if (status === "queued" || status === "pending")
|
|
4069
|
-
return { status: "pending", raw: polled };
|
|
4070
|
-
return { status: "running", raw: polled };
|
|
4071
|
-
},
|
|
4072
|
-
async result(opts) {
|
|
4073
|
-
const optSignal = opts?.signal ?? ctx.signal;
|
|
4074
|
-
const pollOpts = {
|
|
4075
|
-
intervalMs: opts?.pollIntervalMs ?? SEEDANCE_POLL_INTERVAL_MS,
|
|
4076
|
-
timeoutMs: opts?.timeoutMs ?? RESULT_TIMEOUT_MS
|
|
4077
|
-
};
|
|
4078
|
-
if (optSignal !== void 0) pollOpts.signal = optSignal;
|
|
4079
|
-
return pollUntil(async () => {
|
|
4080
|
-
const polled = await fetchStatus();
|
|
4081
|
-
if (!polled) return void 0;
|
|
4082
|
-
const status = (polled.status ?? "queued").toLowerCase();
|
|
4083
|
-
if (status === "succeeded") {
|
|
4084
|
-
const url = polled.content?.video_url;
|
|
4085
|
-
if (!url)
|
|
4086
|
-
throwArkError(
|
|
4087
|
-
void 0,
|
|
4088
|
-
"Seedance task succeeded but returned no video_url",
|
|
4089
|
-
modelId
|
|
4090
|
-
);
|
|
4091
|
-
if (polled.duration !== void 0 && polled.duration > 0) {
|
|
4092
|
-
usage2.quantity = polled.duration;
|
|
4093
|
-
usage2.durationSeconds = polled.duration;
|
|
4094
|
-
}
|
|
4095
|
-
const tokens = polled.usage;
|
|
4096
|
-
if (tokens?.total_tokens !== void 0 && tokens.completion_tokens !== void 0) {
|
|
4097
|
-
usage2.inputTokens = tokens.total_tokens - tokens.completion_tokens;
|
|
4098
|
-
usage2.outputTokens = tokens.completion_tokens;
|
|
4099
|
-
}
|
|
4100
|
-
return {
|
|
4101
|
-
video: { url, expiresAt: new Date(Date.now() + VIDEO_URL_TTL_MS) },
|
|
4102
|
-
usage: usage2,
|
|
4103
|
-
raw: polled
|
|
4104
|
-
};
|
|
4105
|
-
}
|
|
4106
|
-
if (status === "failed" || status === "expired" || status === "cancelled") {
|
|
4107
|
-
throwArkError(void 0, seedanceErrorMessage(polled.error, status), modelId);
|
|
4108
|
-
}
|
|
4109
|
-
return void 0;
|
|
4110
|
-
}, pollOpts);
|
|
4111
|
-
}
|
|
4112
|
-
};
|
|
4124
|
+
return seedanceJob(ctx, key, jobId, usage2);
|
|
4113
4125
|
}
|
|
4114
4126
|
|
|
4115
4127
|
// src/adapters/fal.ts
|
|
@@ -4251,6 +4263,20 @@ var import_zod11 = require("zod");
|
|
|
4251
4263
|
|
|
4252
4264
|
// src/catalog/video-tools-spec.ts
|
|
4253
4265
|
var import_zod10 = require("zod");
|
|
4266
|
+
|
|
4267
|
+
// src/video-length.ts
|
|
4268
|
+
function tenths(seconds2) {
|
|
4269
|
+
return Math.round(seconds2 * 10) / 10;
|
|
4270
|
+
}
|
|
4271
|
+
function overMax(seconds2, max) {
|
|
4272
|
+
return seconds2 > max && tenths(seconds2) > max;
|
|
4273
|
+
}
|
|
4274
|
+
function overTotal(lengths, total) {
|
|
4275
|
+
const sum = (values) => values.reduce((acc, len) => acc + len, 0);
|
|
4276
|
+
return sum(lengths) > total && tenths(sum(lengths.map(tenths))) > total;
|
|
4277
|
+
}
|
|
4278
|
+
|
|
4279
|
+
// src/catalog/video-tools-spec.ts
|
|
4254
4280
|
var RangeSchema2 = import_zod10.z.object({ min: import_zod10.z.number(), max: import_zod10.z.number() });
|
|
4255
4281
|
var ToolSchema = import_zod10.z.object({
|
|
4256
4282
|
kind: import_zod10.z.enum(["upscale", "interpolate", "hdr"]),
|
|
@@ -4294,7 +4320,7 @@ function planVideoTool(row, req) {
|
|
|
4294
4320
|
return fail("The source video's width, height and duration are required");
|
|
4295
4321
|
}
|
|
4296
4322
|
const maxSeconds = row.constraints.sourceVideoSeconds?.max;
|
|
4297
|
-
if (maxSeconds !== void 0 && durationSeconds
|
|
4323
|
+
if (maxSeconds !== void 0 && overMax(durationSeconds, maxSeconds)) {
|
|
4298
4324
|
return fail(`${row.label} takes source videos up to ${maxSeconds} seconds.`);
|
|
4299
4325
|
}
|
|
4300
4326
|
const base = {
|
|
@@ -8696,7 +8722,7 @@ function videoLengthViolation(model, input) {
|
|
|
8696
8722
|
const lengths = (input.referenceVideos ?? []).map(knownLength);
|
|
8697
8723
|
if (limits?.max !== void 0) {
|
|
8698
8724
|
const max = limits.max;
|
|
8699
|
-
const i = lengths.findIndex((len) => len !== void 0 && len
|
|
8725
|
+
const i = lengths.findIndex((len) => len !== void 0 && overMax(len, max));
|
|
8700
8726
|
if (i >= 0)
|
|
8701
8727
|
return `${model.label} takes reference videos up to ${String(max)} s each; video ${i + 1} is ${seconds(lengths[i], max)} s.`;
|
|
8702
8728
|
}
|
|
@@ -8708,14 +8734,17 @@ function videoLengthViolation(model, input) {
|
|
|
8708
8734
|
const source = knownLength(input.sourceVideo);
|
|
8709
8735
|
const sourceLimits = model.constraints.sourceVideoSeconds;
|
|
8710
8736
|
if (limits?.total !== void 0) {
|
|
8711
|
-
const
|
|
8712
|
-
|
|
8713
|
-
|
|
8737
|
+
const counted = [
|
|
8738
|
+
...lengths.filter((len) => len !== void 0),
|
|
8739
|
+
...sourceLimits?.inReferenceTotal && source !== void 0 ? [source] : []
|
|
8740
|
+
];
|
|
8741
|
+
const sum = counted.reduce((acc, len) => acc + len, 0);
|
|
8742
|
+
if (overTotal(counted, limits.total))
|
|
8714
8743
|
return `${model.label} takes reference videos up to ${String(limits.total)} s combined; these add up to ${seconds(sum, limits.total)} s.`;
|
|
8715
8744
|
}
|
|
8716
8745
|
if (source === void 0) return void 0;
|
|
8717
8746
|
const sourceMin = effectiveMin(sourceLimits, input.taskType);
|
|
8718
|
-
if (sourceLimits?.max !== void 0 && source
|
|
8747
|
+
if (sourceLimits?.max !== void 0 && overMax(source, sourceLimits.max))
|
|
8719
8748
|
return `${model.label} takes a source video up to ${String(sourceLimits.max)} s; this one is ${seconds(source, sourceLimits.max)} s.`;
|
|
8720
8749
|
if (sourceMin !== void 0 && source < sourceMin)
|
|
8721
8750
|
return `${model.label} takes a source video of at least ${String(sourceMin)} s; this one is ${seconds(source, sourceMin)} s.`;
|
|
@@ -9672,6 +9701,33 @@ function createModelKit(config, adapters) {
|
|
|
9672
9701
|
result: () => done
|
|
9673
9702
|
};
|
|
9674
9703
|
}
|
|
9704
|
+
function wrapVideoJob(model, job, warnings, startedAt) {
|
|
9705
|
+
const result = job.result.bind(job);
|
|
9706
|
+
let settled;
|
|
9707
|
+
return {
|
|
9708
|
+
...job,
|
|
9709
|
+
warnings,
|
|
9710
|
+
result(opts) {
|
|
9711
|
+
if (settled) return settled;
|
|
9712
|
+
const pending = result(opts).then(
|
|
9713
|
+
async (out) => {
|
|
9714
|
+
const usage2 = timed(
|
|
9715
|
+
{ ...out.usage, model: model.id, unit: model.usage.unit },
|
|
9716
|
+
startedAt
|
|
9717
|
+
);
|
|
9718
|
+
await emit(usage2);
|
|
9719
|
+
return { ...out, usage: usage2 };
|
|
9720
|
+
},
|
|
9721
|
+
(cause) => {
|
|
9722
|
+
settled = void 0;
|
|
9723
|
+
throw cause;
|
|
9724
|
+
}
|
|
9725
|
+
);
|
|
9726
|
+
settled = pending;
|
|
9727
|
+
return pending;
|
|
9728
|
+
}
|
|
9729
|
+
};
|
|
9730
|
+
}
|
|
9675
9731
|
return {
|
|
9676
9732
|
catalog,
|
|
9677
9733
|
describe: (id) => catalog.require(id),
|
|
@@ -9756,31 +9812,52 @@ function createModelKit(config, adapters) {
|
|
|
9756
9812
|
});
|
|
9757
9813
|
const startedAt = Date.now();
|
|
9758
9814
|
const job = await adapter.generateVideo(request, ctx);
|
|
9759
|
-
|
|
9760
|
-
|
|
9761
|
-
|
|
9762
|
-
|
|
9763
|
-
|
|
9764
|
-
|
|
9765
|
-
|
|
9766
|
-
|
|
9767
|
-
|
|
9768
|
-
|
|
9769
|
-
|
|
9770
|
-
|
|
9771
|
-
|
|
9772
|
-
|
|
9773
|
-
|
|
9774
|
-
|
|
9775
|
-
|
|
9776
|
-
|
|
9777
|
-
|
|
9778
|
-
|
|
9779
|
-
|
|
9780
|
-
|
|
9781
|
-
|
|
9782
|
-
}
|
|
9783
|
-
}
|
|
9815
|
+
return wrapVideoJob(model, job, warnings, startedAt);
|
|
9816
|
+
},
|
|
9817
|
+
async resumeVideo(req, jobId, call = {}) {
|
|
9818
|
+
mediaRetryOptions(call);
|
|
9819
|
+
if (!jobId) {
|
|
9820
|
+
throw new ModelKitError({
|
|
9821
|
+
code: "missing_input",
|
|
9822
|
+
message: "resumeVideo needs a jobId",
|
|
9823
|
+
retryable: false
|
|
9824
|
+
});
|
|
9825
|
+
}
|
|
9826
|
+
if (!isCatalogId(req.model)) {
|
|
9827
|
+
throw new ModelKitError({
|
|
9828
|
+
code: "unsupported_option",
|
|
9829
|
+
message: "resumeVideo needs a catalog model id"
|
|
9830
|
+
});
|
|
9831
|
+
}
|
|
9832
|
+
const model = catalog.require(req.model);
|
|
9833
|
+
if (model.type !== "video")
|
|
9834
|
+
throw new ModelKitError({
|
|
9835
|
+
code: "unsupported_option",
|
|
9836
|
+
message: `${model.id} is not a video model`,
|
|
9837
|
+
model: model.id
|
|
9838
|
+
});
|
|
9839
|
+
const { request: input, warnings: inputWarnings } = videoRequestOf({
|
|
9840
|
+
...req,
|
|
9841
|
+
model: req.model
|
|
9842
|
+
});
|
|
9843
|
+
const { request, warnings } = resolveVideoOptions(
|
|
9844
|
+
model,
|
|
9845
|
+
input,
|
|
9846
|
+
call.onUnsupported ?? "error"
|
|
9847
|
+
);
|
|
9848
|
+
warnings.push(...inputWarnings);
|
|
9849
|
+
logCoercions(logger, model.id, warnings);
|
|
9850
|
+
const signal = combineSignals(req.abortSignal, call.signal);
|
|
9851
|
+
const { adapter, ctx } = context(model, signal ? { ...call, signal } : call, warnings);
|
|
9852
|
+
if (!adapter.resumeVideo)
|
|
9853
|
+
throw new ModelKitError({
|
|
9854
|
+
code: "unsupported_option",
|
|
9855
|
+
message: `${adapter.id} cannot resume a video job`,
|
|
9856
|
+
model: model.id
|
|
9857
|
+
});
|
|
9858
|
+
const startedAt = Date.now();
|
|
9859
|
+
const job = adapter.resumeVideo(request, jobId, ctx);
|
|
9860
|
+
return wrapVideoJob(model, { ...job, jobId }, warnings, startedAt);
|
|
9784
9861
|
},
|
|
9785
9862
|
async generateAudio(req, call = {}) {
|
|
9786
9863
|
mediaRetryOptions(call);
|