@shop-os/modelkit 0.4.5 → 0.4.7

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/dist/index.cjs CHANGED
@@ -52,6 +52,7 @@ __export(index_exports, {
52
52
  SurfaceSchema: () => SurfaceSchema,
53
53
  VIDEO_INPUT_SPEC_KEYS: () => VIDEO_INPUT_SPEC_KEYS,
54
54
  VIDEO_TOOL_MODELS: () => VIDEO_TOOL_MODELS,
55
+ VideoResolutionSchema: () => VideoResolutionSchema,
55
56
  adapterServesType: () => adapterServesType,
56
57
  aiSdkTextAdapter: () => aiSdkTextAdapter,
57
58
  arkAdapter: () => arkAdapter,
@@ -3770,6 +3771,11 @@ var arkAdapter = {
3770
3771
  async generateVideo(req, ctx) {
3771
3772
  throwIfAborted(ctx.signal);
3772
3773
  return generateSeedanceVideo(req, ctx);
3774
+ },
3775
+ resumeVideo(req, jobId, ctx) {
3776
+ throwIfAborted(ctx.signal);
3777
+ const key = arkKey(ctx);
3778
+ return seedanceJob(ctx, key, jobId, seedanceUsage(ctx, req, seedanceInitialDuration(req)));
3773
3779
  }
3774
3780
  };
3775
3781
  function arkKey(ctx) {
@@ -3927,17 +3933,22 @@ var SeedanceResponseSchema = import_zod7.z.object({
3927
3933
  error: import_zod7.z.union([import_zod7.z.string(), import_zod7.z.object({ message: import_zod7.z.string().optional() })]).optional()
3928
3934
  });
3929
3935
  var SeedanceErrorMessageSchema = import_zod7.z.object({ message: import_zod7.z.string().optional() });
3936
+ function seedanceResolution(req) {
3937
+ return req.resolution ?? "720p";
3938
+ }
3930
3939
  function seedanceUsage(ctx, req, duration) {
3931
- const usage2 = {
3940
+ return {
3932
3941
  model: ctx.model.id,
3933
3942
  provider: "ark",
3934
3943
  modality: "VIDEO",
3935
3944
  unit: "seconds",
3936
3945
  quantity: duration,
3937
- durationSeconds: duration
3946
+ durationSeconds: duration,
3947
+ resolution: seedanceResolution(req)
3938
3948
  };
3939
- if (req.resolution !== void 0) usage2.resolution = req.resolution;
3940
- return usage2;
3949
+ }
3950
+ function seedanceInitialDuration(req) {
3951
+ return req.durationSeconds ?? 5;
3941
3952
  }
3942
3953
  function seedanceErrorMessage(error, status) {
3943
3954
  const asString = import_zod7.z.string().safeParse(error);
@@ -3946,6 +3957,82 @@ function seedanceErrorMessage(error, status) {
3946
3957
  if (asObject.success && asObject.data.message !== void 0) return asObject.data.message;
3947
3958
  return `Seedance task ${status}`;
3948
3959
  }
3960
+ function seedanceJob(ctx, key, jobId, usage2) {
3961
+ const modelId = ctx.model.id;
3962
+ const fetchStatus = async () => {
3963
+ const res = await arkFetch(ctx, `${arkBaseUrl(ctx)}${SUBMIT_PATH}/${jobId}`, {
3964
+ headers: { Authorization: `Bearer ${key}` }
3965
+ });
3966
+ if (!res.ok) {
3967
+ if (ctx.call.maxRetries !== 0 && (res.status === 429 || res.status >= 500))
3968
+ return void 0;
3969
+ const text2 = await res.text().catch(() => "");
3970
+ throwArkError(res.status, text2, modelId);
3971
+ }
3972
+ const parsed = SeedanceResponseSchema.safeParse(await res.json());
3973
+ if (!parsed.success) return {};
3974
+ return parsed.data;
3975
+ };
3976
+ return {
3977
+ jobId,
3978
+ async poll() {
3979
+ const polled = await fetchStatus();
3980
+ if (!polled) return { status: "running" };
3981
+ const status = (polled.status ?? "queued").toLowerCase();
3982
+ if (status === "succeeded") return { status: "succeeded", raw: polled };
3983
+ if (status === "failed" || status === "expired" || status === "cancelled") {
3984
+ return { status: "failed", raw: polled };
3985
+ }
3986
+ if (status === "queued" || status === "pending")
3987
+ return { status: "pending", raw: polled };
3988
+ return { status: "running", raw: polled };
3989
+ },
3990
+ async result(opts) {
3991
+ const optSignal = opts?.signal ?? ctx.signal;
3992
+ const pollOpts = {
3993
+ intervalMs: opts?.pollIntervalMs ?? SEEDANCE_POLL_INTERVAL_MS,
3994
+ timeoutMs: opts?.timeoutMs ?? RESULT_TIMEOUT_MS
3995
+ };
3996
+ if (optSignal !== void 0) pollOpts.signal = optSignal;
3997
+ return pollUntil(async () => {
3998
+ const polled = await fetchStatus();
3999
+ if (!polled) return void 0;
4000
+ const status = (polled.status ?? "queued").toLowerCase();
4001
+ if (status === "succeeded") {
4002
+ const url = polled.content?.video_url;
4003
+ if (!url)
4004
+ throwArkError(
4005
+ void 0,
4006
+ "Seedance task succeeded but returned no video_url",
4007
+ modelId
4008
+ );
4009
+ if (polled.duration !== void 0 && polled.duration > 0) {
4010
+ usage2.quantity = polled.duration;
4011
+ usage2.durationSeconds = polled.duration;
4012
+ }
4013
+ const rendered = VideoResolutionSchema.safeParse(
4014
+ polled.resolution?.toLowerCase()
4015
+ );
4016
+ if (rendered.success) usage2.resolution = rendered.data;
4017
+ const tokens = polled.usage;
4018
+ if (tokens?.total_tokens !== void 0 && tokens.completion_tokens !== void 0) {
4019
+ usage2.inputTokens = tokens.total_tokens - tokens.completion_tokens;
4020
+ usage2.outputTokens = tokens.completion_tokens;
4021
+ }
4022
+ return {
4023
+ video: { url, expiresAt: new Date(Date.now() + VIDEO_URL_TTL_MS) },
4024
+ usage: usage2,
4025
+ raw: polled
4026
+ };
4027
+ }
4028
+ if (status === "failed" || status === "expired" || status === "cancelled") {
4029
+ throwArkError(void 0, seedanceErrorMessage(polled.error, status), modelId);
4030
+ }
4031
+ return void 0;
4032
+ }, pollOpts);
4033
+ }
4034
+ };
4035
+ }
3949
4036
  async function generateSeedanceVideo(req, ctx) {
3950
4037
  const modelId = ctx.model.id;
3951
4038
  const taskType = req.taskType;
@@ -3960,10 +4047,10 @@ async function generateSeedanceVideo(req, ctx) {
3960
4047
  });
3961
4048
  }
3962
4049
  const key = arkKey(ctx);
3963
- const requestedDuration = req.durationSeconds ?? 5;
4050
+ const requestedDuration = seedanceInitialDuration(req);
3964
4051
  let duration = requestedDuration;
3965
4052
  if (taskType === "edit") duration = -1;
3966
- const resolution = req.resolution ?? "720p";
4053
+ const resolution = seedanceResolution(req);
3967
4054
  const content = [{ type: "text", text: req.prompt }];
3968
4055
  if (req.firstFrame) {
3969
4056
  content.push({
@@ -4041,75 +4128,7 @@ async function generateSeedanceVideo(req, ctx) {
4041
4128
  if (!submit.id) throwArkError(void 0, "Seedance task create returned no id", modelId);
4042
4129
  const jobId = submit.id;
4043
4130
  const usage2 = seedanceUsage(ctx, req, taskType === "edit" ? requestedDuration : duration);
4044
- const fetchStatus = async () => {
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
- };
4131
+ return seedanceJob(ctx, key, jobId, usage2);
4113
4132
  }
4114
4133
 
4115
4134
  // src/adapters/fal.ts
@@ -4899,9 +4918,15 @@ function videoUsage(ctx, req, duration) {
4899
4918
  quantity: duration,
4900
4919
  durationSeconds: duration
4901
4920
  };
4902
- if (req.resolution !== void 0) event.resolution = req.resolution;
4921
+ const resolution = falVideoResolution(ctx.model.providerModelId, req);
4922
+ if (resolution !== void 0) event.resolution = resolution;
4903
4923
  return event;
4904
4924
  }
4925
+ function falVideoResolution(providerModelId, req) {
4926
+ if (isWan(providerModelId)) return wanResolution(req);
4927
+ if (isMinimax(providerModelId)) return minimaxResolution(req, minimaxFamily(providerModelId));
4928
+ return req.resolution;
4929
+ }
4905
4930
  async function decodeImage(ctx, url, contentType) {
4906
4931
  if (url.startsWith("data:")) {
4907
4932
  const comma = url.indexOf(",");
@@ -5033,12 +5058,14 @@ function isFalVideoKnown(providerModelId) {
5033
5058
  function minimaxFamily(providerModelId) {
5034
5059
  return H3_MAX_MODEL_IDS.has(providerModelId.toLowerCase()) ? "h3-max" : "h3";
5035
5060
  }
5061
+ function minimaxResolution(req, family) {
5062
+ return req.resolution ?? (family === "h3-max" ? "768p" : "2k");
5063
+ }
5036
5064
  function minimaxBase(req, family) {
5037
- const resolution = (family === "h3-max" ? req.resolution ?? "768p" : req.resolution ?? "2k").toUpperCase();
5038
5065
  const input = {
5039
5066
  prompt: req.prompt.trim(),
5040
5067
  duration: req.durationSeconds ?? 5,
5041
- resolution,
5068
+ resolution: minimaxResolution(req, family).toUpperCase(),
5042
5069
  prompt_expansion_mode: req.promptExpansion ?? "balanced"
5043
5070
  };
5044
5071
  if (req.safetyChecker !== void 0) input.enable_safety_checker = req.safetyChecker;
@@ -5049,11 +5076,14 @@ var WAN_MODEL_IDS = /* @__PURE__ */ new Set(["alibaba/wan-3.0"]);
5049
5076
  function isWan(providerModelId) {
5050
5077
  return WAN_MODEL_IDS.has(providerModelId.toLowerCase());
5051
5078
  }
5079
+ function wanResolution(req) {
5080
+ return req.resolution ?? "1080p";
5081
+ }
5052
5082
  function wanVideo(req, family) {
5053
5083
  const input = {
5054
5084
  prompt: req.prompt.trim(),
5055
5085
  duration: req.durationSeconds ?? 5,
5056
- resolution: req.resolution ?? "1080p",
5086
+ resolution: wanResolution(req),
5057
5087
  aspect_ratio: req.aspectRatio ?? "adaptive",
5058
5088
  audio: req.generateAudio ?? true
5059
5089
  };
@@ -5652,7 +5682,7 @@ async function downloadVideo(ctx, url, apiKey, signal) {
5652
5682
  mime: res.headers.get("content-type")?.split(";")[0]?.trim() || "video/mp4"
5653
5683
  };
5654
5684
  }
5655
- function videoUsage2(ctx, req, duration) {
5685
+ function videoUsage2(ctx, duration, resolution) {
5656
5686
  const event = {
5657
5687
  model: ctx.model.id,
5658
5688
  provider: "google-genai",
@@ -5661,7 +5691,7 @@ function videoUsage2(ctx, req, duration) {
5661
5691
  quantity: duration,
5662
5692
  durationSeconds: duration
5663
5693
  };
5664
- if (req.resolution !== void 0) event.resolution = req.resolution;
5694
+ if (resolution !== void 0) event.resolution = resolution;
5665
5695
  return event;
5666
5696
  }
5667
5697
  async function generateVeoVideo(req, ctx, createGenAIClient) {
@@ -5755,7 +5785,7 @@ async function generateVeoVideo(req, ctx, createGenAIClient) {
5755
5785
  }
5756
5786
  };
5757
5787
  const jobId = operation.name ?? crypto.randomUUID();
5758
- const usage2 = videoUsage2(ctx, req, duration);
5788
+ const usage2 = videoUsage2(ctx, duration, req.resolution ?? "720p");
5759
5789
  return {
5760
5790
  jobId,
5761
5791
  async poll() {
@@ -5879,10 +5909,8 @@ async function generateOmniVideo(req, ctx, createGenAIClient) {
5879
5909
  if (req.sourceVideo === void 0 && !extend && req.aspectRatio !== void 0 && req.aspectRatio !== "adaptive") {
5880
5910
  responseFormat.aspect_ratio = toOmniAspect(req.aspectRatio);
5881
5911
  }
5882
- if (!isOmniV1) {
5883
- const omniResolution = toOmniResolution(req.resolution);
5884
- if (omniResolution !== void 0) responseFormat.resolution = omniResolution;
5885
- }
5912
+ const omniResolution = isOmniV1 ? void 0 : toOmniResolution(req.resolution);
5913
+ if (omniResolution !== void 0) responseFormat.resolution = omniResolution;
5886
5914
  if (req.durationSeconds !== void 0) responseFormat.duration = `${req.durationSeconds}s`;
5887
5915
  const client2 = createGenAIClient({
5888
5916
  apiKey,
@@ -6000,7 +6028,7 @@ async function generateOmniVideo(req, ctx, createGenAIClient) {
6000
6028
  if (!(extend && !chained)) await cleanupUploads();
6001
6029
  }
6002
6030
  const jobId = interaction.id ?? crypto.randomUUID();
6003
- const usage2 = videoUsage2(ctx, req, duration);
6031
+ const usage2 = videoUsage2(ctx, duration, omniResolution ?? "720p");
6004
6032
  const getInteraction = async () => {
6005
6033
  try {
6006
6034
  const getParams = { stream: false };
@@ -7582,7 +7610,8 @@ function videoUsage4(ctx, req) {
7582
7610
  unit: "compute_seconds",
7583
7611
  quantity: 0
7584
7612
  };
7585
- if (req.resolution !== void 0) event.resolution = req.resolution;
7613
+ const resolution = replicateVideoResolution(ctx.model.providerModelId, req);
7614
+ if (resolution !== void 0) event.resolution = resolution;
7586
7615
  if (req.durationSeconds !== void 0) event.durationSeconds = req.durationSeconds;
7587
7616
  return event;
7588
7617
  }
@@ -7819,7 +7848,7 @@ function klingInput(req) {
7819
7848
  const input = {
7820
7849
  prompt: req.prompt,
7821
7850
  duration: req.durationSeconds ?? 10,
7822
- mode: req.mode ?? "standard",
7851
+ mode: klingTier(req).mode,
7823
7852
  generate_audio: req.generateAudio ?? false
7824
7853
  };
7825
7854
  const startImage = images[0];
@@ -7833,17 +7862,25 @@ function klingInput(req) {
7833
7862
  if (req.negativePrompt !== void 0) input.negative_prompt = req.negativePrompt;
7834
7863
  return input;
7835
7864
  }
7836
- function klingOmniMode(req) {
7837
- if (req.resolution === "4k") return "4k";
7838
- if (req.resolution === "1080p") return "pro";
7839
- if (req.resolution === "720p") return "standard";
7840
- return req.mode ?? "standard";
7865
+ function replicateVideoResolution(providerModelId, req) {
7866
+ const kind = videoKind(providerModelId);
7867
+ if (kind === "kling" || kind === "kling-omni") return klingTier(req).resolution;
7868
+ if (kind === "hailuo") return hailuoResolution(req);
7869
+ return req.resolution;
7870
+ }
7871
+ var KLING_TIERS = [
7872
+ { mode: "standard", resolution: "720p" },
7873
+ { mode: "pro", resolution: "1080p" },
7874
+ { mode: "4k", resolution: "4k" }
7875
+ ];
7876
+ function klingTier(req) {
7877
+ return KLING_TIERS.find((tier) => tier.resolution === req.resolution) ?? KLING_TIERS.find((tier) => tier.mode === req.mode) ?? KLING_TIERS[0];
7841
7878
  }
7842
7879
  function klingOmniInput(req) {
7843
7880
  const input = {
7844
7881
  prompt: req.prompt,
7845
7882
  duration: req.durationSeconds ?? 10,
7846
- mode: klingOmniMode(req),
7883
+ mode: klingTier(req).mode,
7847
7884
  generate_audio: req.generateAudio ?? false
7848
7885
  };
7849
7886
  const start = req.firstFrame ?? req.lastFrame;
@@ -7857,11 +7894,14 @@ function klingOmniInput(req) {
7857
7894
  if (refs.length > 0) input.reference_images = refs;
7858
7895
  return input;
7859
7896
  }
7897
+ function hailuoResolution(req) {
7898
+ return req.resolution ?? "768p";
7899
+ }
7860
7900
  function hailuoInput(req) {
7861
7901
  const input = {
7862
7902
  prompt: req.prompt,
7863
7903
  duration: req.durationSeconds ?? 6,
7864
- resolution: req.resolution ?? "768p",
7904
+ resolution: hailuoResolution(req),
7865
7905
  prompt_optimizer: true
7866
7906
  };
7867
7907
  if (req.firstFrame) input.first_frame_image = refToUrl(req.firstFrame);
@@ -9689,6 +9729,33 @@ function createModelKit(config, adapters) {
9689
9729
  result: () => done
9690
9730
  };
9691
9731
  }
9732
+ function wrapVideoJob(model, job, warnings, startedAt) {
9733
+ const result = job.result.bind(job);
9734
+ let settled;
9735
+ return {
9736
+ ...job,
9737
+ warnings,
9738
+ result(opts) {
9739
+ if (settled) return settled;
9740
+ const pending = result(opts).then(
9741
+ async (out) => {
9742
+ const usage2 = timed(
9743
+ { ...out.usage, model: model.id, unit: model.usage.unit },
9744
+ startedAt
9745
+ );
9746
+ await emit(usage2);
9747
+ return { ...out, usage: usage2 };
9748
+ },
9749
+ (cause) => {
9750
+ settled = void 0;
9751
+ throw cause;
9752
+ }
9753
+ );
9754
+ settled = pending;
9755
+ return pending;
9756
+ }
9757
+ };
9758
+ }
9692
9759
  return {
9693
9760
  catalog,
9694
9761
  describe: (id) => catalog.require(id),
@@ -9773,31 +9840,52 @@ function createModelKit(config, adapters) {
9773
9840
  });
9774
9841
  const startedAt = Date.now();
9775
9842
  const job = await adapter.generateVideo(request, ctx);
9776
- const result = job.result.bind(job);
9777
- let settled;
9778
- return {
9779
- ...job,
9780
- warnings,
9781
- result(opts) {
9782
- if (settled) return settled;
9783
- const pending = result(opts).then(
9784
- async (out) => {
9785
- const usage2 = timed(
9786
- { ...out.usage, model: model.id, unit: model.usage.unit },
9787
- startedAt
9788
- );
9789
- await emit(usage2);
9790
- return { ...out, usage: usage2 };
9791
- },
9792
- (cause) => {
9793
- settled = void 0;
9794
- throw cause;
9795
- }
9796
- );
9797
- settled = pending;
9798
- return pending;
9799
- }
9800
- };
9843
+ return wrapVideoJob(model, job, warnings, startedAt);
9844
+ },
9845
+ async resumeVideo(req, jobId, call = {}) {
9846
+ mediaRetryOptions(call);
9847
+ if (!jobId) {
9848
+ throw new ModelKitError({
9849
+ code: "missing_input",
9850
+ message: "resumeVideo needs a jobId",
9851
+ retryable: false
9852
+ });
9853
+ }
9854
+ if (!isCatalogId(req.model)) {
9855
+ throw new ModelKitError({
9856
+ code: "unsupported_option",
9857
+ message: "resumeVideo needs a catalog model id"
9858
+ });
9859
+ }
9860
+ const model = catalog.require(req.model);
9861
+ if (model.type !== "video")
9862
+ throw new ModelKitError({
9863
+ code: "unsupported_option",
9864
+ message: `${model.id} is not a video model`,
9865
+ model: model.id
9866
+ });
9867
+ const { request: input, warnings: inputWarnings } = videoRequestOf({
9868
+ ...req,
9869
+ model: req.model
9870
+ });
9871
+ const { request, warnings } = resolveVideoOptions(
9872
+ model,
9873
+ input,
9874
+ call.onUnsupported ?? "error"
9875
+ );
9876
+ warnings.push(...inputWarnings);
9877
+ logCoercions(logger, model.id, warnings);
9878
+ const signal = combineSignals(req.abortSignal, call.signal);
9879
+ const { adapter, ctx } = context(model, signal ? { ...call, signal } : call, warnings);
9880
+ if (!adapter.resumeVideo)
9881
+ throw new ModelKitError({
9882
+ code: "unsupported_option",
9883
+ message: `${adapter.id} cannot resume a video job`,
9884
+ model: model.id
9885
+ });
9886
+ const startedAt = Date.now();
9887
+ const job = adapter.resumeVideo(request, jobId, ctx);
9888
+ return wrapVideoJob(model, { ...job, jobId }, warnings, startedAt);
9801
9889
  },
9802
9890
  async generateAudio(req, call = {}) {
9803
9891
  mediaRetryOptions(call);
@@ -10042,6 +10130,7 @@ var describe = (modelId) => createDefaultModelKit().describe(modelId);
10042
10130
  SurfaceSchema,
10043
10131
  VIDEO_INPUT_SPEC_KEYS,
10044
10132
  VIDEO_TOOL_MODELS,
10133
+ VideoResolutionSchema,
10045
10134
  adapterServesType,
10046
10135
  aiSdkTextAdapter,
10047
10136
  arkAdapter,