@shop-os/modelkit 0.4.5 → 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/dist/index.d.cts CHANGED
@@ -1144,6 +1144,8 @@ interface Adapter {
1144
1144
  id: string;
1145
1145
  generateImage?(req: ImageRequest, ctx: AdapterContext): Promise<ImageResult>;
1146
1146
  generateVideo?(req: VideoRequest, ctx: AdapterContext): Promise<VideoJob>;
1147
+ /** Reconnect to a job `generateVideo` submitted earlier; never submits. */
1148
+ resumeVideo?(req: VideoRequest, jobId: string, ctx: AdapterContext): VideoJob;
1147
1149
  generateAudio?(req: AudioRequest, ctx: AdapterContext): Promise<AudioResult>;
1148
1150
  /** Video tools run as provider jobs the caller tracks; see ModelKit.submitVideoTool. */
1149
1151
  submitVideoTool?(req: VideoToolRequest, ctx: AdapterContext, opts: {
@@ -1350,6 +1352,12 @@ interface ModelKit extends TextMethods {
1350
1352
  * `model` is a catalog id or an AI SDK video model instance.
1351
1353
  */
1352
1354
  generateVideo(req: VideoCallOptions, call?: CallContext): Promise<VideoCallJob>;
1355
+ /**
1356
+ * Reconnect to a video job submitted earlier with `generateVideo` (same request), by its
1357
+ * `jobId`. Never submits a new job. Await `job.result()` as usual; usage is emitted once.
1358
+ * A resumed job reports only the resumed wait in `latencyMs`.
1359
+ */
1360
+ resumeVideo(req: VideoCallOptions, jobId: string, call?: CallContext): Promise<VideoCallJob>;
1353
1361
  /** AI SDK LanguageModel for chat models. Pair with the catalog's providerOptions. */
1354
1362
  languageModel(modelId: string, call?: CallContext): LanguageModelHandle;
1355
1363
  /** Usage-event `provider` for a catalog model, without building it: "bedrock" on Bedrock, else the vendor. */
package/dist/index.d.ts CHANGED
@@ -1144,6 +1144,8 @@ interface Adapter {
1144
1144
  id: string;
1145
1145
  generateImage?(req: ImageRequest, ctx: AdapterContext): Promise<ImageResult>;
1146
1146
  generateVideo?(req: VideoRequest, ctx: AdapterContext): Promise<VideoJob>;
1147
+ /** Reconnect to a job `generateVideo` submitted earlier; never submits. */
1148
+ resumeVideo?(req: VideoRequest, jobId: string, ctx: AdapterContext): VideoJob;
1147
1149
  generateAudio?(req: AudioRequest, ctx: AdapterContext): Promise<AudioResult>;
1148
1150
  /** Video tools run as provider jobs the caller tracks; see ModelKit.submitVideoTool. */
1149
1151
  submitVideoTool?(req: VideoToolRequest, ctx: AdapterContext, opts: {
@@ -1350,6 +1352,12 @@ interface ModelKit extends TextMethods {
1350
1352
  * `model` is a catalog id or an AI SDK video model instance.
1351
1353
  */
1352
1354
  generateVideo(req: VideoCallOptions, call?: CallContext): Promise<VideoCallJob>;
1355
+ /**
1356
+ * Reconnect to a video job submitted earlier with `generateVideo` (same request), by its
1357
+ * `jobId`. Never submits a new job. Await `job.result()` as usual; usage is emitted once.
1358
+ * A resumed job reports only the resumed wait in `latencyMs`.
1359
+ */
1360
+ resumeVideo(req: VideoCallOptions, jobId: string, call?: CallContext): Promise<VideoCallJob>;
1353
1361
  /** AI SDK LanguageModel for chat models. Pair with the catalog's providerOptions. */
1354
1362
  languageModel(modelId: string, call?: CallContext): LanguageModelHandle;
1355
1363
  /** Usage-event `provider` for a catalog model, without building it: "bedrock" on Bedrock, else the vendor. */
package/dist/index.js CHANGED
@@ -3651,6 +3651,11 @@ var arkAdapter = {
3651
3651
  async generateVideo(req, ctx) {
3652
3652
  throwIfAborted(ctx.signal);
3653
3653
  return generateSeedanceVideo(req, ctx);
3654
+ },
3655
+ resumeVideo(req, jobId, ctx) {
3656
+ throwIfAborted(ctx.signal);
3657
+ const key = arkKey(ctx);
3658
+ return seedanceJob(ctx, key, jobId, seedanceUsage(ctx, req, seedanceInitialDuration(req)));
3654
3659
  }
3655
3660
  };
3656
3661
  function arkKey(ctx) {
@@ -3820,6 +3825,9 @@ function seedanceUsage(ctx, req, duration) {
3820
3825
  if (req.resolution !== void 0) usage2.resolution = req.resolution;
3821
3826
  return usage2;
3822
3827
  }
3828
+ function seedanceInitialDuration(req) {
3829
+ return req.durationSeconds ?? 5;
3830
+ }
3823
3831
  function seedanceErrorMessage(error, status) {
3824
3832
  const asString = z7.string().safeParse(error);
3825
3833
  if (asString.success) return asString.data;
@@ -3827,6 +3835,78 @@ function seedanceErrorMessage(error, status) {
3827
3835
  if (asObject.success && asObject.data.message !== void 0) return asObject.data.message;
3828
3836
  return `Seedance task ${status}`;
3829
3837
  }
3838
+ function seedanceJob(ctx, key, jobId, usage2) {
3839
+ const modelId = ctx.model.id;
3840
+ const fetchStatus = async () => {
3841
+ const res = await arkFetch(ctx, `${arkBaseUrl(ctx)}${SUBMIT_PATH}/${jobId}`, {
3842
+ headers: { Authorization: `Bearer ${key}` }
3843
+ });
3844
+ if (!res.ok) {
3845
+ if (ctx.call.maxRetries !== 0 && (res.status === 429 || res.status >= 500))
3846
+ return void 0;
3847
+ const text2 = await res.text().catch(() => "");
3848
+ throwArkError(res.status, text2, modelId);
3849
+ }
3850
+ const parsed = SeedanceResponseSchema.safeParse(await res.json());
3851
+ if (!parsed.success) return {};
3852
+ return parsed.data;
3853
+ };
3854
+ return {
3855
+ jobId,
3856
+ async poll() {
3857
+ const polled = await fetchStatus();
3858
+ if (!polled) return { status: "running" };
3859
+ const status = (polled.status ?? "queued").toLowerCase();
3860
+ if (status === "succeeded") return { status: "succeeded", raw: polled };
3861
+ if (status === "failed" || status === "expired" || status === "cancelled") {
3862
+ return { status: "failed", raw: polled };
3863
+ }
3864
+ if (status === "queued" || status === "pending")
3865
+ return { status: "pending", raw: polled };
3866
+ return { status: "running", raw: polled };
3867
+ },
3868
+ async result(opts) {
3869
+ const optSignal = opts?.signal ?? ctx.signal;
3870
+ const pollOpts = {
3871
+ intervalMs: opts?.pollIntervalMs ?? SEEDANCE_POLL_INTERVAL_MS,
3872
+ timeoutMs: opts?.timeoutMs ?? RESULT_TIMEOUT_MS
3873
+ };
3874
+ if (optSignal !== void 0) pollOpts.signal = optSignal;
3875
+ return pollUntil(async () => {
3876
+ const polled = await fetchStatus();
3877
+ if (!polled) return void 0;
3878
+ const status = (polled.status ?? "queued").toLowerCase();
3879
+ if (status === "succeeded") {
3880
+ const url = polled.content?.video_url;
3881
+ if (!url)
3882
+ throwArkError(
3883
+ void 0,
3884
+ "Seedance task succeeded but returned no video_url",
3885
+ modelId
3886
+ );
3887
+ if (polled.duration !== void 0 && polled.duration > 0) {
3888
+ usage2.quantity = polled.duration;
3889
+ usage2.durationSeconds = polled.duration;
3890
+ }
3891
+ const tokens = polled.usage;
3892
+ if (tokens?.total_tokens !== void 0 && tokens.completion_tokens !== void 0) {
3893
+ usage2.inputTokens = tokens.total_tokens - tokens.completion_tokens;
3894
+ usage2.outputTokens = tokens.completion_tokens;
3895
+ }
3896
+ return {
3897
+ video: { url, expiresAt: new Date(Date.now() + VIDEO_URL_TTL_MS) },
3898
+ usage: usage2,
3899
+ raw: polled
3900
+ };
3901
+ }
3902
+ if (status === "failed" || status === "expired" || status === "cancelled") {
3903
+ throwArkError(void 0, seedanceErrorMessage(polled.error, status), modelId);
3904
+ }
3905
+ return void 0;
3906
+ }, pollOpts);
3907
+ }
3908
+ };
3909
+ }
3830
3910
  async function generateSeedanceVideo(req, ctx) {
3831
3911
  const modelId = ctx.model.id;
3832
3912
  const taskType = req.taskType;
@@ -3841,7 +3921,7 @@ async function generateSeedanceVideo(req, ctx) {
3841
3921
  });
3842
3922
  }
3843
3923
  const key = arkKey(ctx);
3844
- const requestedDuration = req.durationSeconds ?? 5;
3924
+ const requestedDuration = seedanceInitialDuration(req);
3845
3925
  let duration = requestedDuration;
3846
3926
  if (taskType === "edit") duration = -1;
3847
3927
  const resolution = req.resolution ?? "720p";
@@ -3922,75 +4002,7 @@ async function generateSeedanceVideo(req, ctx) {
3922
4002
  if (!submit.id) throwArkError(void 0, "Seedance task create returned no id", modelId);
3923
4003
  const jobId = submit.id;
3924
4004
  const usage2 = seedanceUsage(ctx, req, taskType === "edit" ? requestedDuration : duration);
3925
- const fetchStatus = async () => {
3926
- const res = await arkFetch(ctx, `${arkBaseUrl(ctx)}${SUBMIT_PATH}/${jobId}`, {
3927
- headers: { Authorization: `Bearer ${key}` }
3928
- });
3929
- if (!res.ok) {
3930
- if (ctx.call.maxRetries !== 0 && (res.status === 429 || res.status >= 500))
3931
- return void 0;
3932
- const text2 = await res.text().catch(() => "");
3933
- throwArkError(res.status, text2, modelId);
3934
- }
3935
- const parsed = SeedanceResponseSchema.safeParse(await res.json());
3936
- if (!parsed.success) return {};
3937
- return parsed.data;
3938
- };
3939
- return {
3940
- jobId,
3941
- async poll() {
3942
- const polled = await fetchStatus();
3943
- if (!polled) return { status: "running" };
3944
- const status = (polled.status ?? "queued").toLowerCase();
3945
- if (status === "succeeded") return { status: "succeeded", raw: polled };
3946
- if (status === "failed" || status === "expired" || status === "cancelled") {
3947
- return { status: "failed", raw: polled };
3948
- }
3949
- if (status === "queued" || status === "pending")
3950
- return { status: "pending", raw: polled };
3951
- return { status: "running", raw: polled };
3952
- },
3953
- async result(opts) {
3954
- const optSignal = opts?.signal ?? ctx.signal;
3955
- const pollOpts = {
3956
- intervalMs: opts?.pollIntervalMs ?? SEEDANCE_POLL_INTERVAL_MS,
3957
- timeoutMs: opts?.timeoutMs ?? RESULT_TIMEOUT_MS
3958
- };
3959
- if (optSignal !== void 0) pollOpts.signal = optSignal;
3960
- return pollUntil(async () => {
3961
- const polled = await fetchStatus();
3962
- if (!polled) return void 0;
3963
- const status = (polled.status ?? "queued").toLowerCase();
3964
- if (status === "succeeded") {
3965
- const url = polled.content?.video_url;
3966
- if (!url)
3967
- throwArkError(
3968
- void 0,
3969
- "Seedance task succeeded but returned no video_url",
3970
- modelId
3971
- );
3972
- if (polled.duration !== void 0 && polled.duration > 0) {
3973
- usage2.quantity = polled.duration;
3974
- usage2.durationSeconds = polled.duration;
3975
- }
3976
- const tokens = polled.usage;
3977
- if (tokens?.total_tokens !== void 0 && tokens.completion_tokens !== void 0) {
3978
- usage2.inputTokens = tokens.total_tokens - tokens.completion_tokens;
3979
- usage2.outputTokens = tokens.completion_tokens;
3980
- }
3981
- return {
3982
- video: { url, expiresAt: new Date(Date.now() + VIDEO_URL_TTL_MS) },
3983
- usage: usage2,
3984
- raw: polled
3985
- };
3986
- }
3987
- if (status === "failed" || status === "expired" || status === "cancelled") {
3988
- throwArkError(void 0, seedanceErrorMessage(polled.error, status), modelId);
3989
- }
3990
- return void 0;
3991
- }, pollOpts);
3992
- }
3993
- };
4005
+ return seedanceJob(ctx, key, jobId, usage2);
3994
4006
  }
3995
4007
 
3996
4008
  // src/adapters/fal.ts
@@ -9579,6 +9591,33 @@ function createModelKit(config, adapters) {
9579
9591
  result: () => done
9580
9592
  };
9581
9593
  }
9594
+ function wrapVideoJob(model, job, warnings, startedAt) {
9595
+ const result = job.result.bind(job);
9596
+ let settled;
9597
+ return {
9598
+ ...job,
9599
+ warnings,
9600
+ result(opts) {
9601
+ if (settled) return settled;
9602
+ const pending = result(opts).then(
9603
+ async (out) => {
9604
+ const usage2 = timed(
9605
+ { ...out.usage, model: model.id, unit: model.usage.unit },
9606
+ startedAt
9607
+ );
9608
+ await emit(usage2);
9609
+ return { ...out, usage: usage2 };
9610
+ },
9611
+ (cause) => {
9612
+ settled = void 0;
9613
+ throw cause;
9614
+ }
9615
+ );
9616
+ settled = pending;
9617
+ return pending;
9618
+ }
9619
+ };
9620
+ }
9582
9621
  return {
9583
9622
  catalog,
9584
9623
  describe: (id) => catalog.require(id),
@@ -9663,31 +9702,52 @@ function createModelKit(config, adapters) {
9663
9702
  });
9664
9703
  const startedAt = Date.now();
9665
9704
  const job = await adapter.generateVideo(request, ctx);
9666
- const result = job.result.bind(job);
9667
- let settled;
9668
- return {
9669
- ...job,
9670
- warnings,
9671
- result(opts) {
9672
- if (settled) return settled;
9673
- const pending = result(opts).then(
9674
- async (out) => {
9675
- const usage2 = timed(
9676
- { ...out.usage, model: model.id, unit: model.usage.unit },
9677
- startedAt
9678
- );
9679
- await emit(usage2);
9680
- return { ...out, usage: usage2 };
9681
- },
9682
- (cause) => {
9683
- settled = void 0;
9684
- throw cause;
9685
- }
9686
- );
9687
- settled = pending;
9688
- return pending;
9689
- }
9690
- };
9705
+ return wrapVideoJob(model, job, warnings, startedAt);
9706
+ },
9707
+ async resumeVideo(req, jobId, call = {}) {
9708
+ mediaRetryOptions(call);
9709
+ if (!jobId) {
9710
+ throw new ModelKitError({
9711
+ code: "missing_input",
9712
+ message: "resumeVideo needs a jobId",
9713
+ retryable: false
9714
+ });
9715
+ }
9716
+ if (!isCatalogId(req.model)) {
9717
+ throw new ModelKitError({
9718
+ code: "unsupported_option",
9719
+ message: "resumeVideo needs a catalog model id"
9720
+ });
9721
+ }
9722
+ const model = catalog.require(req.model);
9723
+ if (model.type !== "video")
9724
+ throw new ModelKitError({
9725
+ code: "unsupported_option",
9726
+ message: `${model.id} is not a video model`,
9727
+ model: model.id
9728
+ });
9729
+ const { request: input, warnings: inputWarnings } = videoRequestOf({
9730
+ ...req,
9731
+ model: req.model
9732
+ });
9733
+ const { request, warnings } = resolveVideoOptions(
9734
+ model,
9735
+ input,
9736
+ call.onUnsupported ?? "error"
9737
+ );
9738
+ warnings.push(...inputWarnings);
9739
+ logCoercions(logger, model.id, warnings);
9740
+ const signal = combineSignals(req.abortSignal, call.signal);
9741
+ const { adapter, ctx } = context(model, signal ? { ...call, signal } : call, warnings);
9742
+ if (!adapter.resumeVideo)
9743
+ throw new ModelKitError({
9744
+ code: "unsupported_option",
9745
+ message: `${adapter.id} cannot resume a video job`,
9746
+ model: model.id
9747
+ });
9748
+ const startedAt = Date.now();
9749
+ const job = adapter.resumeVideo(request, jobId, ctx);
9750
+ return wrapVideoJob(model, { ...job, jobId }, warnings, startedAt);
9691
9751
  },
9692
9752
  async generateAudio(req, call = {}) {
9693
9753
  mediaRetryOptions(call);