@shop-os/modelkit 0.3.0 → 0.3.1

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
@@ -1031,6 +1031,7 @@ declare const InputSpecSchema: z.ZodObject<{
1031
1031
  fields: z.ZodRecord<z.ZodString, z.ZodString>;
1032
1032
  values: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>>;
1033
1033
  static: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
1034
+ editEndpoint: z.ZodOptional<z.ZodString>;
1034
1035
  }, z.core.$strict>;
1035
1036
  type InputSpec = z.infer<typeof InputSpecSchema>;
1036
1037
  interface InputSpecIssue {
package/dist/index.d.ts CHANGED
@@ -1031,6 +1031,7 @@ declare const InputSpecSchema: z.ZodObject<{
1031
1031
  fields: z.ZodRecord<z.ZodString, z.ZodString>;
1032
1032
  values: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>>;
1033
1033
  static: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
1034
+ editEndpoint: z.ZodOptional<z.ZodString>;
1034
1035
  }, z.core.$strict>;
1035
1036
  type InputSpec = z.infer<typeof InputSpecSchema>;
1036
1037
  interface InputSpecIssue {
package/dist/index.js CHANGED
@@ -2252,6 +2252,10 @@ var ALL_KEY_SET = /* @__PURE__ */ new Set([
2252
2252
  ...IMAGE_INPUT_SPEC_KEYS,
2253
2253
  ...AUDIO_INPUT_SPEC_KEYS
2254
2254
  ]);
2255
+ var TYPES_BY_ADAPTER = /* @__PURE__ */ new Map([
2256
+ ["replicate", ["video", "image", "audio"]],
2257
+ ["fal", ["image"]]
2258
+ ]);
2255
2259
  var ALLOWED_KEYS_BY_TYPE = /* @__PURE__ */ new Map([
2256
2260
  ["video", VIDEO_KEY_SET],
2257
2261
  ["image", IMAGE_KEY_SET],
@@ -2290,7 +2294,8 @@ var StaticSchema = z2.record(z2.string(), ProviderScalarSchema);
2290
2294
  var InputSpecSchema = z2.strictObject({
2291
2295
  fields: FieldsSchema,
2292
2296
  values: ValuesSchema.optional(),
2293
- static: StaticSchema.optional()
2297
+ static: StaticSchema.optional(),
2298
+ editEndpoint: z2.string().min(1).optional()
2294
2299
  }).superRefine((spec, ctx) => {
2295
2300
  const mappedNames = new Set(Object.values(spec.fields));
2296
2301
  for (const key of Object.keys(spec.static ?? {})) {
@@ -2318,18 +2323,21 @@ function validateInputSpec(raw, row) {
2318
2323
  };
2319
2324
  }
2320
2325
  const spec = parsed.data;
2321
- if (row.adapter !== "replicate") {
2326
+ const supportedTypes = TYPES_BY_ADAPTER.get(row.adapter);
2327
+ if (supportedTypes === void 0) {
2322
2328
  return {
2323
2329
  ok: false,
2324
- issues: [{ path: [], message: "inputSpec is only supported on replicate rows" }]
2330
+ issues: [
2331
+ { path: [], message: "inputSpec is only supported on replicate and fal rows" }
2332
+ ]
2325
2333
  };
2326
2334
  }
2327
2335
  const issues = [];
2328
2336
  const allowed = ALLOWED_KEYS_BY_TYPE.get(row.type);
2329
- if (allowed === void 0) {
2337
+ if (allowed === void 0 || !supportedTypes.includes(row.type)) {
2330
2338
  issues.push({
2331
2339
  path: [],
2332
- message: `inputSpec is not supported for "${row.type}" rows`
2340
+ message: row.adapter === "fal" ? `inputSpec on fal rows is only supported for image rows, not "${row.type}"` : `inputSpec is not supported for "${row.type}" rows`
2333
2341
  });
2334
2342
  } else {
2335
2343
  for (const key of Object.keys(spec.fields)) {
@@ -2349,6 +2357,19 @@ function validateInputSpec(raw, row) {
2349
2357
  }
2350
2358
  }
2351
2359
  }
2360
+ if (spec.editEndpoint !== void 0) {
2361
+ if (row.adapter !== "fal") {
2362
+ issues.push({
2363
+ path: ["editEndpoint"],
2364
+ message: "inputSpec.editEndpoint is only supported on fal rows"
2365
+ });
2366
+ } else if (!("references" in spec.fields)) {
2367
+ issues.push({
2368
+ path: ["editEndpoint"],
2369
+ message: 'inputSpec.editEndpoint needs a "references" entry in inputSpec.fields'
2370
+ });
2371
+ }
2372
+ }
2352
2373
  const seenNames = /* @__PURE__ */ new Map();
2353
2374
  for (const [key, name] of Object.entries(spec.fields)) {
2354
2375
  const first = seenNames.get(name);
@@ -4093,6 +4114,70 @@ async function cancelFalVideoTool(sub, ctx) {
4093
4114
  throw falError(res.status, await readBody(res), ctx.model.id);
4094
4115
  }
4095
4116
 
4117
+ // src/adapters/input-spec-mapper.ts
4118
+ function canonicalValue(req, key) {
4119
+ return req[key];
4120
+ }
4121
+ function isMediaRef(value) {
4122
+ return value instanceof Object && ("url" in value || "bytes" in value);
4123
+ }
4124
+ function toProviderValue(value, refToUrl2) {
4125
+ if (Array.isArray(value)) {
4126
+ return value.map(refToUrl2);
4127
+ }
4128
+ if (isMediaRef(value)) {
4129
+ return refToUrl2(value);
4130
+ }
4131
+ return value;
4132
+ }
4133
+ function applyInputSpec(spec, req, refToUrl2, opts) {
4134
+ const out = {};
4135
+ for (const [canonical, providerField] of Object.entries(spec.fields)) {
4136
+ if (!isInputSpecKey(canonical)) continue;
4137
+ const value = canonicalValue(req, canonical);
4138
+ if (value === void 0) continue;
4139
+ if (opts?.omit?.(canonical, value)) continue;
4140
+ const serialised = toProviderValue(value, refToUrl2);
4141
+ const mapped = spec.values?.[canonical]?.[String(value)];
4142
+ out[providerField] = mapped ?? serialised;
4143
+ }
4144
+ for (const [key, value] of Object.entries(spec.static ?? {})) {
4145
+ out[key] = value;
4146
+ }
4147
+ return out;
4148
+ }
4149
+ function resolveRowInputSpec(req, ctx) {
4150
+ const raw = ctx.model.providerOptions?.["modelkit"]?.["inputSpec"];
4151
+ if (raw === void 0) return { kind: "absent" };
4152
+ const result = validateInputSpec(raw, ctx.model);
4153
+ if (!result.ok) {
4154
+ ctx.logger.warn(
4155
+ `[modelkit] ${ctx.model.id} has an invalid providerOptions.modelkit.inputSpec; the call fails`
4156
+ );
4157
+ return { kind: "invalid", issues: result.issues };
4158
+ }
4159
+ return {
4160
+ kind: "ok",
4161
+ spec: result.spec,
4162
+ input: applyInputSpec(result.spec, req, refToUrl, {
4163
+ omit: (key, value) => key === "aspectRatio" && value === "adaptive"
4164
+ })
4165
+ };
4166
+ }
4167
+ function describeInputSpecIssue(issue) {
4168
+ if (!issue) return "invalid inputSpec";
4169
+ return issue.path.length === 0 ? issue.message : `${issue.path.map((part) => String(part)).join(".")} ${issue.message}`;
4170
+ }
4171
+ function invalidInputSpecError(provider, ctx, issues) {
4172
+ return new ModelKitError({
4173
+ code: "unsupported_option",
4174
+ message: `${provider} row "${ctx.model.id}" has an invalid providerOptions.modelkit.inputSpec: ${describeInputSpecIssue(issues[0])}`,
4175
+ provider,
4176
+ model: ctx.model.id,
4177
+ retryable: false
4178
+ });
4179
+ }
4180
+
4096
4181
  // src/adapters/fal.ts
4097
4182
  var MUSE_T2I_ENDPOINT = "meta/muse-image/text-to-image";
4098
4183
  var MUSE_EDIT_ENDPOINT = "meta/muse-image/edit";
@@ -4147,6 +4232,11 @@ function createFalAdapter(deps = defaultDeps) {
4147
4232
  id: "fal",
4148
4233
  generateImage: (req, ctx) => {
4149
4234
  throwIfAborted(ctx.signal);
4235
+ const spec = resolveRowInputSpec(req, ctx);
4236
+ if (spec.kind === "invalid") {
4237
+ return Promise.reject(invalidInputSpecError("fal", ctx, spec.issues));
4238
+ }
4239
+ if (spec.kind === "ok") return generateSpecImage(req, ctx, deps, spec);
4150
4240
  if (isIdeogram(ctx.model.providerModelId)) return generateIdeogramImage(req, ctx, deps);
4151
4241
  return generateMuseImage(req, ctx, deps);
4152
4242
  },
@@ -4326,6 +4416,36 @@ var museImageResultSchema = z12.object({
4326
4416
  })
4327
4417
  ).optional()
4328
4418
  });
4419
+ async function collectFalImages(payload, ctx, req, label) {
4420
+ const parsed = museImageResultSchema.safeParse(payload);
4421
+ const images = parsed.success ? parsed.data.images ?? [] : [];
4422
+ const files = await Promise.all(
4423
+ images.flatMap((img) => {
4424
+ if (img.url === void 0) return [];
4425
+ return [decodeImage(ctx, img.url, img.content_type ?? void 0)];
4426
+ })
4427
+ );
4428
+ if (files.length === 0) {
4429
+ throw new ModelKitError({
4430
+ code: "provider_rejected",
4431
+ message: `${label} returned no image`,
4432
+ provider: "fal",
4433
+ model: ctx.model.id,
4434
+ retryable: false
4435
+ });
4436
+ }
4437
+ return {
4438
+ images: files.map((f) => ({ bytes: f.bytes, mime: f.mime })),
4439
+ usage: imageUsage(ctx, req, files.length),
4440
+ raw: payload
4441
+ };
4442
+ }
4443
+ async function generateSpecImage(req, ctx, deps, spec) {
4444
+ const hasReferences = (req.references ?? []).length > 0;
4445
+ const endpoint = hasReferences && spec.spec.editEndpoint !== void 0 ? spec.spec.editEndpoint : ctx.model.providerModelId;
4446
+ const payload = await runJob(ctx, deps, endpoint, spec.input);
4447
+ return collectFalImages(payload, ctx, req, `fal ${endpoint}`);
4448
+ }
4329
4449
  async function generateMuseImage(req, ctx, deps) {
4330
4450
  const refs = (req.references ?? []).map(refToUrl);
4331
4451
  const isEdit = refs.length > 0;
@@ -4342,29 +4462,7 @@ async function generateMuseImage(req, ctx, deps) {
4342
4462
  const custom = id.includes("/") && !id.startsWith("meta/muse-image") ? id : void 0;
4343
4463
  const endpoint = custom ?? (isEdit ? MUSE_EDIT_ENDPOINT : MUSE_T2I_ENDPOINT);
4344
4464
  const payload = await runJob(ctx, deps, endpoint, input);
4345
- const parsed = museImageResultSchema.safeParse(payload);
4346
- const images = parsed.success ? parsed.data.images ?? [] : [];
4347
- const firstUrl = images[0]?.url;
4348
- if (images.length === 0 || firstUrl === void 0) {
4349
- throw new ModelKitError({
4350
- code: "provider_rejected",
4351
- message: "fal Meta Muse returned no image",
4352
- provider: "fal",
4353
- model: ctx.model.id,
4354
- retryable: false
4355
- });
4356
- }
4357
- const files = await Promise.all(
4358
- images.flatMap((img) => {
4359
- if (img.url === void 0) return [];
4360
- return [decodeImage(ctx, img.url, img.content_type ?? void 0)];
4361
- })
4362
- );
4363
- return {
4364
- images: files.map((f) => ({ bytes: f.bytes, mime: f.mime })),
4365
- usage: imageUsage(ctx, req, files.length),
4366
- raw: payload
4367
- };
4465
+ return collectFalImages(payload, ctx, req, "fal Meta Muse");
4368
4466
  }
4369
4467
  var IDEOGRAM_MODEL_IDS = /* @__PURE__ */ new Set(["ideogram/v4.5"]);
4370
4468
  function isIdeogram(providerModelId) {
@@ -4409,28 +4507,7 @@ async function generateIdeogramImage(req, ctx, deps) {
4409
4507
  if (references.length > 0) input.reference_image_urls = references;
4410
4508
  }
4411
4509
  const payload = await runJob(ctx, deps, endpoint, input);
4412
- const parsed = museImageResultSchema.safeParse(payload);
4413
- const images = parsed.success ? parsed.data.images ?? [] : [];
4414
- const files = await Promise.all(
4415
- images.flatMap((img) => {
4416
- if (img.url === void 0) return [];
4417
- return [decodeImage(ctx, img.url, img.content_type ?? void 0)];
4418
- })
4419
- );
4420
- if (files.length === 0) {
4421
- throw new ModelKitError({
4422
- code: "provider_rejected",
4423
- message: "fal Ideogram returned no image",
4424
- provider: "fal",
4425
- model: ctx.model.id,
4426
- retryable: false
4427
- });
4428
- }
4429
- return {
4430
- images: files.map((f) => ({ bytes: f.bytes, mime: f.mime })),
4431
- usage: imageUsage(ctx, req, files.length),
4432
- raw: payload
4433
- };
4510
+ return collectFalImages(payload, ctx, req, "fal Ideogram");
4434
4511
  }
4435
4512
  var H3_MAX_MODEL_IDS = /* @__PURE__ */ new Set([
4436
4513
  "minimax/h3-max",
@@ -6781,41 +6858,6 @@ function createReplicateHttpClient(opts) {
6781
6858
 
6782
6859
  // src/adapters/replicate.ts
6783
6860
  import { z as z19 } from "zod";
6784
-
6785
- // src/adapters/input-spec-mapper.ts
6786
- function canonicalValue(req, key) {
6787
- return req[key];
6788
- }
6789
- function isMediaRef(value) {
6790
- return value instanceof Object && ("url" in value || "bytes" in value);
6791
- }
6792
- function toProviderValue(value, refToUrl2) {
6793
- if (Array.isArray(value)) {
6794
- return value.map(refToUrl2);
6795
- }
6796
- if (isMediaRef(value)) {
6797
- return refToUrl2(value);
6798
- }
6799
- return value;
6800
- }
6801
- function applyInputSpec(spec, req, refToUrl2, opts) {
6802
- const out = {};
6803
- for (const [canonical, providerField] of Object.entries(spec.fields)) {
6804
- if (!isInputSpecKey(canonical)) continue;
6805
- const value = canonicalValue(req, canonical);
6806
- if (value === void 0) continue;
6807
- if (opts?.omit?.(canonical, value)) continue;
6808
- const serialised = toProviderValue(value, refToUrl2);
6809
- const mapped = spec.values?.[canonical]?.[String(value)];
6810
- out[providerField] = mapped ?? serialised;
6811
- }
6812
- for (const [key, value] of Object.entries(spec.static ?? {})) {
6813
- out[key] = value;
6814
- }
6815
- return out;
6816
- }
6817
-
6818
- // src/adapters/replicate.ts
6819
6861
  var POLL_INTERVAL_MS = 1e3;
6820
6862
  var POLL_TIMEOUT_MS = 15 * 6e4;
6821
6863
  var defaultReplicateDeps = {
@@ -7058,38 +7100,6 @@ function replicateNoMapper(ctx, section) {
7058
7100
  retryable: false
7059
7101
  });
7060
7102
  }
7061
- function describeInputSpecIssue(issue) {
7062
- if (!issue) return "invalid inputSpec";
7063
- return issue.path.length === 0 ? issue.message : `${issue.path.map((part) => String(part)).join(".")} ${issue.message}`;
7064
- }
7065
- function replicateInvalidSpec(ctx, issues) {
7066
- const detail = describeInputSpecIssue(issues[0]);
7067
- return new ModelKitError({
7068
- code: "unsupported_option",
7069
- message: `replicate row "${ctx.model.id}" has an invalid providerOptions.modelkit.inputSpec: ${detail}`,
7070
- provider: "replicate",
7071
- model: ctx.model.id,
7072
- retryable: false
7073
- });
7074
- }
7075
- function omitAdaptive(key, value) {
7076
- return key === "aspectRatio" && value === "adaptive";
7077
- }
7078
- function replicateInputSpec(req, ctx) {
7079
- const raw = ctx.model.providerOptions?.["modelkit"]?.["inputSpec"];
7080
- if (raw === void 0) return { kind: "absent" };
7081
- const result = validateInputSpec(raw, ctx.model);
7082
- if (!result.ok) {
7083
- ctx.logger.warn(
7084
- `[modelkit] ${ctx.model.id} has an invalid providerOptions.modelkit.inputSpec; the call fails`
7085
- );
7086
- return { kind: "invalid", issues: result.issues };
7087
- }
7088
- return {
7089
- kind: "ok",
7090
- input: applyInputSpec(result.spec, req, refToUrl, { omit: omitAdaptive })
7091
- };
7092
- }
7093
7103
  var QWEN_IMAGE_RATIOS = /* @__PURE__ */ new Set([
7094
7104
  "1:1",
7095
7105
  "16:9",
@@ -7233,8 +7243,9 @@ async function generateReplicateImage(req, ctx, deps) {
7233
7243
  break;
7234
7244
  }
7235
7245
  default: {
7236
- const specResult = replicateInputSpec(req, ctx);
7237
- if (specResult.kind === "invalid") throw replicateInvalidSpec(ctx, specResult.issues);
7246
+ const specResult = resolveRowInputSpec(req, ctx);
7247
+ if (specResult.kind === "invalid")
7248
+ throw invalidInputSpecError("replicate", ctx, specResult.issues);
7238
7249
  if (specResult.kind === "ok") {
7239
7250
  input = specResult.input;
7240
7251
  break;
@@ -7314,9 +7325,10 @@ async function generateReplicateVideo(req, ctx, deps) {
7314
7325
  else if (kind === "kling-omni") input = klingOmniInput(req);
7315
7326
  else if (kind === "hailuo") input = hailuoInput(req);
7316
7327
  else {
7317
- const specResult = replicateInputSpec(req, ctx);
7328
+ const specResult = resolveRowInputSpec(req, ctx);
7318
7329
  if (specResult.kind === "absent") throw replicateNoMapper(ctx, "video");
7319
- if (specResult.kind === "invalid") throw replicateInvalidSpec(ctx, specResult.issues);
7330
+ if (specResult.kind === "invalid")
7331
+ throw invalidInputSpecError("replicate", ctx, specResult.issues);
7320
7332
  input = specResult.input;
7321
7333
  }
7322
7334
  const api = client(ctx, deps);
@@ -7471,9 +7483,10 @@ async function generateReplicateAudio(req, ctx, deps) {
7471
7483
  else if (kind === "elevenlabs") input = elevenLabsMusicInput(req, ctx);
7472
7484
  else if (kind === "lyria") input = lyriaAudioInput(req, ctx);
7473
7485
  else {
7474
- const specResult = replicateInputSpec(req, ctx);
7486
+ const specResult = resolveRowInputSpec(req, ctx);
7475
7487
  if (specResult.kind === "absent") throw replicateNoMapper(ctx, "audio");
7476
- if (specResult.kind === "invalid") throw replicateInvalidSpec(ctx, specResult.issues);
7488
+ if (specResult.kind === "invalid")
7489
+ throw invalidInputSpecError("replicate", ctx, specResult.issues);
7477
7490
  input = specResult.input;
7478
7491
  }
7479
7492
  const { url, predictTime, raw } = await runPrediction(ctx, input, deps);