@brotu/ai 0.7.0 → 0.8.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/dist/index.cjs CHANGED
@@ -50,6 +50,7 @@ __export(index_exports, {
50
50
  KLING_CAPABILITIES: () => KLING_CAPABILITIES,
51
51
  KLING_CATALOG: () => KLING_CATALOG,
52
52
  KLING_MODELS: () => KLING_MODELS,
53
+ KieAdapter: () => KieAdapter,
53
54
  KlingAdapter: () => KlingAdapter,
54
55
  OPENAI_CATALOG: () => OPENAI_CATALOG,
55
56
  OPENAI_IMAGE_MODELS: () => OPENAI_IMAGE_MODELS,
@@ -78,7 +79,9 @@ __export(index_exports, {
78
79
  getProviders: () => getProviders,
79
80
  hasModel: () => hasModel,
80
81
  isPendingJob: () => isPendingJob,
82
+ kieSnapshot: () => kieSnapshot,
81
83
  ok: () => ok,
84
+ parseKieCallback: () => parseKieCallback,
82
85
  persistOutputs: () => persistOutputs,
83
86
  registerModels: () => registerModels,
84
87
  resetCatalog: () => resetCatalog,
@@ -1349,6 +1352,7 @@ var PROVIDER_BASE_URLS = {
1349
1352
  openai: "https://api.openai.com",
1350
1353
  qwen: "https://dashscope-intl.aliyuncs.com",
1351
1354
  topaz: "https://api.topazlabs.com",
1355
+ kie: "https://api.kie.ai/api/v1",
1352
1356
  brotu: "https://api.brotu.app"
1353
1357
  };
1354
1358
  var DEFAULT_BROTU_API_URL = PROVIDER_BASE_URLS.brotu;
@@ -1424,6 +1428,14 @@ function resolveProvider(modelId, options) {
1424
1428
  }
1425
1429
  return { id, apiKey: configured.apiKey, baseUrl: baseUrl.replace(/\/$/, "") };
1426
1430
  }
1431
+ const kie = vendorProviders(options).kie;
1432
+ if (kie && id !== "kie") {
1433
+ return {
1434
+ id: "kie",
1435
+ apiKey: kie.apiKey,
1436
+ baseUrl: (kie.baseUrl ?? PROVIDER_BASE_URLS.kie).replace(/\/$/, "")
1437
+ };
1438
+ }
1427
1439
  const platformKey = brotuApiKey(options);
1428
1440
  if (platformKey) {
1429
1441
  return {
@@ -2827,10 +2839,383 @@ var GoogleAdapter = class {
2827
2839
  }
2828
2840
  };
2829
2841
 
2842
+ // src/lib/webhook.ts
2843
+ function resolveWebhook(value) {
2844
+ if (!value) return void 0;
2845
+ if (typeof value === "string") {
2846
+ return isHttpUrl(value) ? { url: value } : void 0;
2847
+ }
2848
+ if (!isHttpUrl(value.url)) return void 0;
2849
+ return {
2850
+ url: value.url,
2851
+ secret: value.secret,
2852
+ headers: value.headers
2853
+ };
2854
+ }
2855
+ function isHttpUrl(value) {
2856
+ try {
2857
+ const parsed = new URL(value);
2858
+ return parsed.protocol === "https:" || parsed.protocol === "http:";
2859
+ } catch {
2860
+ return false;
2861
+ }
2862
+ }
2863
+ async function deliverWebhook(config, payload) {
2864
+ try {
2865
+ await fetch(config.url, {
2866
+ method: "POST",
2867
+ headers: {
2868
+ "content-type": "application/json",
2869
+ "user-agent": "@brotu/ai",
2870
+ "x-brotu-event": payload.event,
2871
+ ...config.secret ? { "x-brotu-webhook-secret": config.secret } : {},
2872
+ ...config.headers
2873
+ },
2874
+ body: JSON.stringify(payload),
2875
+ signal: AbortSignal.timeout(1e4)
2876
+ });
2877
+ } catch {
2878
+ }
2879
+ }
2880
+
2881
+ // src/adapters/kie.adapter.ts
2882
+ var DEFAULT_BASE_URL4 = "https://api.kie.ai/api/v1";
2883
+ var POLL_INTERVAL_MS4 = 3e3;
2884
+ var DEFAULT_MAX_POLL_ATTEMPTS4 = 400;
2885
+ var KIE_MODEL_IDS = {
2886
+ "kling/v2-6": "kling-2.6/{mode}-to-video",
2887
+ "kling/v3": "kling-3.0/video",
2888
+ "dreamina-seedance-2-5-260628": "bytedance/seedance-2-5",
2889
+ "dreamina-seedance-2-0-260128": "bytedance/seedance-2",
2890
+ "dreamina-seedance-2-0-fast-260128": "bytedance/seedance-2-fast",
2891
+ "dreamina-seedance-2-0-mini-260615": "bytedance/seedance-2-mini",
2892
+ "wan2.6-t2v": "wan/2-6-text-to-video",
2893
+ "wan2.6-i2v": "wan/2-6-image-to-video",
2894
+ "wan2.7-t2v": "wan/2-7-text-to-video",
2895
+ "wan2.7-i2v": "wan/2-7-image-to-video",
2896
+ "gpt-image-1.5": "gpt-image/1.5-{mode}-to-image",
2897
+ "gpt-image-2": "gpt-image-2-{mode}-to-image",
2898
+ "topaz/video-upscale": "topaz/video-upscale"
2899
+ };
2900
+ var KieAdapter = class {
2901
+ constructor(opts) {
2902
+ this.opts = opts;
2903
+ }
2904
+ opts;
2905
+ providerName = "kie";
2906
+ supportedTypes = [
2907
+ "image",
2908
+ "video",
2909
+ "text",
2910
+ "audio"
2911
+ ];
2912
+ get origin() {
2913
+ return (this.opts.baseUrl ?? DEFAULT_BASE_URL4).replace(/\/$/, "");
2914
+ }
2915
+ async request(path, init) {
2916
+ const response = await fetch(`${this.origin}${path}`, {
2917
+ ...init,
2918
+ headers: {
2919
+ Authorization: `Bearer ${this.opts.apiKey}`,
2920
+ "Content-Type": "application/json",
2921
+ ...init?.headers
2922
+ }
2923
+ });
2924
+ const text = await response.text();
2925
+ let body;
2926
+ try {
2927
+ body = text ? JSON.parse(text) : void 0;
2928
+ } catch {
2929
+ body = void 0;
2930
+ }
2931
+ const code = body?.code;
2932
+ if (!response.ok || typeof code === "number" && code !== 200) {
2933
+ const message = body?.msg || body?.message || text;
2934
+ throw new Error(message || `kie API ${response.status}`);
2935
+ }
2936
+ if (body?.data === void 0) {
2937
+ throw new Error("kie answered without a payload.");
2938
+ }
2939
+ return body.data;
2940
+ }
2941
+ /** Which market model serves this request. */
2942
+ marketModel(modelId, params) {
2943
+ const fromCatalog = getModel(modelId)?.endpoint?.replace(
2944
+ /^\/market\//,
2945
+ ""
2946
+ );
2947
+ const mapped = fromCatalog || KIE_MODEL_IDS[modelId] || modelId;
2948
+ if (!mapped.includes("{mode}")) return mapped;
2949
+ return mapped.replace("{mode}", hasInputImage(params) ? "image" : "text");
2950
+ }
2951
+ callbackFor(params) {
2952
+ const perRequest = resolveWebhook(params.webhook);
2953
+ return perRequest?.url ?? this.opts.callbackUrl;
2954
+ }
2955
+ async createTask(kind, params) {
2956
+ const modelId = params.model ?? "";
2957
+ const body = {
2958
+ model: this.marketModel(modelId, params),
2959
+ input: buildInput(kind, params, modelId)
2960
+ };
2961
+ const callBackUrl = this.callbackFor(params);
2962
+ if (callBackUrl) body.callBackUrl = callBackUrl;
2963
+ const data = await this.request("/jobs/createTask", {
2964
+ method: "POST",
2965
+ body: JSON.stringify(body)
2966
+ });
2967
+ const taskId = data.taskId?.trim();
2968
+ if (!taskId) {
2969
+ throw new Error("kie accepted the task but returned no taskId.");
2970
+ }
2971
+ return taskId;
2972
+ }
2973
+ /** Ask kie about one task. The same shape a callback carries. */
2974
+ async task(taskId) {
2975
+ const record = await this.request(
2976
+ `/jobs/recordInfo?taskId=${encodeURIComponent(taskId)}`
2977
+ );
2978
+ return readTaskRecord(record, taskId);
2979
+ }
2980
+ async run(kind, params) {
2981
+ const startedAt = Date.now();
2982
+ const modelId = params.model ?? "(none)";
2983
+ const failure = (error) => ({
2984
+ success: false,
2985
+ outputs: [],
2986
+ creditsUsed: 0,
2987
+ provider: this.providerName,
2988
+ model: modelId,
2989
+ processingTimeMs: Date.now() - startedAt,
2990
+ error
2991
+ });
2992
+ let taskId;
2993
+ try {
2994
+ taskId = await this.createTask(kind, params);
2995
+ } catch (error) {
2996
+ return failure(error instanceof Error ? error.message : String(error));
2997
+ }
2998
+ const pollEndpoint = `/jobs/recordInfo?taskId=${encodeURIComponent(taskId)}`;
2999
+ if (isSubmitMode()) {
3000
+ throw new PendingJob(taskId, pollEndpoint);
3001
+ }
3002
+ const job = {
3003
+ id: taskId,
3004
+ provider: this.providerName,
3005
+ model: modelId,
3006
+ kind,
3007
+ pollEndpoint,
3008
+ params,
3009
+ submittedAt: new Date(startedAt).toISOString()
3010
+ };
3011
+ const maxAttempts = this.opts.maxPollAttempts ?? DEFAULT_MAX_POLL_ATTEMPTS4;
3012
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
3013
+ const snapshot = await this.completeJob(job);
3014
+ if (snapshot.status === "failed") {
3015
+ return failure(snapshot.error ?? "kie task failed.");
3016
+ }
3017
+ if (snapshot.status === "succeeded" && snapshot.result) {
3018
+ return { ...snapshot.result, processingTimeMs: Date.now() - startedAt };
3019
+ }
3020
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS4));
3021
+ }
3022
+ return failure(
3023
+ `kie task ${taskId} did not finish after ${maxAttempts} checks.`
3024
+ );
3025
+ }
3026
+ async completeJob(job) {
3027
+ const settled = await this.task(job.id);
3028
+ return snapshotFrom(settled, job.model);
3029
+ }
3030
+ generateImage(params) {
3031
+ return this.run("image", params);
3032
+ }
3033
+ generateVideo(params) {
3034
+ return this.run("video", params);
3035
+ }
3036
+ generateText(params) {
3037
+ return this.run("text", params);
3038
+ }
3039
+ generateAudio(params) {
3040
+ return this.run("audio", params);
3041
+ }
3042
+ async estimateCost(type, params) {
3043
+ return estimateFor(this.providerName, type, params);
3044
+ }
3045
+ supportsModel() {
3046
+ return true;
3047
+ }
3048
+ getAvailableModels() {
3049
+ return [];
3050
+ }
3051
+ };
3052
+ function parseKieCallback(body) {
3053
+ let parsed = body;
3054
+ if (typeof body === "string") {
3055
+ try {
3056
+ parsed = JSON.parse(body);
3057
+ } catch {
3058
+ throw new Error("kie callback body is not JSON.");
3059
+ }
3060
+ }
3061
+ const root = asRecord(parsed);
3062
+ const record = asRecord(root?.data) ?? root ?? {};
3063
+ const taskId = record.taskId?.trim();
3064
+ if (!taskId) {
3065
+ throw new Error("kie callback carries no taskId.");
3066
+ }
3067
+ return readTaskRecord(record, taskId);
3068
+ }
3069
+ function kieSnapshot(settled, model) {
3070
+ return snapshotFrom(settled, model ?? settled.model ?? "");
3071
+ }
3072
+ function snapshotFrom(settled, model) {
3073
+ if (settled.status === "failed") {
3074
+ return { status: "failed", error: settled.error ?? "kie task failed." };
3075
+ }
3076
+ if (settled.status === "pending") return { status: "pending" };
3077
+ return {
3078
+ status: "succeeded",
3079
+ result: {
3080
+ success: true,
3081
+ outputs: settled.outputs,
3082
+ creditsUsed: settled.creditsUsed,
3083
+ provider: "kie",
3084
+ model,
3085
+ processingTimeMs: 0
3086
+ }
3087
+ };
3088
+ }
3089
+ function readTaskRecord(record, taskId) {
3090
+ const state = (record.state ?? "").toLowerCase();
3091
+ const creditsUsed = record.creditsConsumed ?? 0;
3092
+ if (state === "fail") {
3093
+ return {
3094
+ taskId,
3095
+ model: record.model,
3096
+ status: "failed",
3097
+ outputs: [],
3098
+ creditsUsed,
3099
+ error: record.failMsg || (record.failCode ? `kie failCode ${record.failCode}` : void 0) || "kie task failed."
3100
+ };
3101
+ }
3102
+ if (state !== "success") {
3103
+ return {
3104
+ taskId,
3105
+ model: record.model,
3106
+ status: "pending",
3107
+ outputs: [],
3108
+ creditsUsed
3109
+ };
3110
+ }
3111
+ return {
3112
+ taskId,
3113
+ model: record.model,
3114
+ status: "succeeded",
3115
+ outputs: outputsFrom2(record.resultJson),
3116
+ creditsUsed
3117
+ };
3118
+ }
3119
+ function outputsFrom2(resultJson) {
3120
+ if (!resultJson) return [];
3121
+ let parsed;
3122
+ try {
3123
+ parsed = JSON.parse(resultJson);
3124
+ } catch {
3125
+ return [];
3126
+ }
3127
+ const record = asRecord(parsed);
3128
+ const urls = Array.isArray(record?.resultUrls) ? record.resultUrls.filter(
3129
+ (url) => typeof url === "string" && url.length > 0
3130
+ ) : [];
3131
+ if (urls.length > 0) return urls.map((url) => ({ url, mimeType: mimeFor(url) }));
3132
+ const object = record?.resultObject;
3133
+ if (object !== void 0) {
3134
+ const text = typeof object === "string" ? object : JSON.stringify(object);
3135
+ return [
3136
+ {
3137
+ url: `data:text/plain;base64,${Buffer.from(text).toString("base64")}`,
3138
+ mimeType: "text/plain",
3139
+ raw: { text }
3140
+ }
3141
+ ];
3142
+ }
3143
+ return [];
3144
+ }
3145
+ function mimeFor(url) {
3146
+ const path = url.split("?")[0]?.toLowerCase() ?? "";
3147
+ if (/\.(mp4|mov|webm|m4v)$/.test(path)) return "video/mp4";
3148
+ if (/\.(mp3|wav|m4a|aac)$/.test(path)) return "audio/mpeg";
3149
+ if (/\.jpe?g$/.test(path)) return "image/jpeg";
3150
+ if (/\.webp$/.test(path)) return "image/webp";
3151
+ return "image/png";
3152
+ }
3153
+ function asRecord(value) {
3154
+ return value && typeof value === "object" ? value : void 0;
3155
+ }
3156
+ function hasInputImage(params) {
3157
+ const p = params;
3158
+ return Boolean(
3159
+ p.imageUrl || p.imageUrls?.length || p.referenceImages?.length
3160
+ );
3161
+ }
3162
+ function buildInput(kind, params, modelId) {
3163
+ const raw = params.providerOptions?.kie ?? {};
3164
+ const base = modelId.startsWith("kling/") ? klingInput(params) : modelId.includes("seedance") ? seedanceInput(params) : commonInput(kind, params);
3165
+ return prune({ ...base, ...raw });
3166
+ }
3167
+ function klingInput(params) {
3168
+ const p = params;
3169
+ return {
3170
+ prompt: p.prompt,
3171
+ negative_prompt: p.negativePrompt,
3172
+ aspect_ratio: p.aspectRatio,
3173
+ // kie's kling enum is a string, unlike every other family.
3174
+ duration: p.duration === void 0 ? void 0 : String(p.duration),
3175
+ sound: p.withAudio,
3176
+ image_url: p.imageUrl ?? p.referenceImages?.[0]
3177
+ };
3178
+ }
3179
+ function seedanceInput(params) {
3180
+ const p = params;
3181
+ return {
3182
+ prompt: p.prompt,
3183
+ duration: p.duration,
3184
+ resolution: p.resolution,
3185
+ aspect_ratio: p.aspectRatio,
3186
+ first_frame_url: p.imageUrl ?? p.referenceImages?.[0],
3187
+ last_frame_url: p.referenceImages?.[1],
3188
+ reference_image_urls: p.imageUrls,
3189
+ reference_video_urls: p.videoUrls ?? (p.videoUrl ? [p.videoUrl] : void 0),
3190
+ generate_audio: p.withAudio
3191
+ };
3192
+ }
3193
+ function commonInput(kind, params) {
3194
+ const p = params;
3195
+ const images = p.imageUrls ?? p.referenceImages;
3196
+ return {
3197
+ prompt: p.prompt,
3198
+ negative_prompt: p.negativePrompt,
3199
+ aspect_ratio: p.aspectRatio,
3200
+ resolution: p.resolution,
3201
+ duration: kind === "video" ? p.duration : void 0,
3202
+ seed: p.seed,
3203
+ output_format: p.outputFormat,
3204
+ image_url: p.imageUrl ?? images?.[0],
3205
+ image_urls: images && images.length > 1 ? images : void 0,
3206
+ video_url: p.videoUrl
3207
+ };
3208
+ }
3209
+ function prune(input) {
3210
+ return Object.fromEntries(
3211
+ Object.entries(input).filter(([, value]) => value !== void 0)
3212
+ );
3213
+ }
3214
+
2830
3215
  // src/adapters/kling.adapter.ts
2831
- var DEFAULT_BASE_URL4 = "https://api-singapore.klingai.com";
2832
- var POLL_INTERVAL_MS4 = 5e3;
2833
- var DEFAULT_MAX_POLL_ATTEMPTS4 = 240;
3216
+ var DEFAULT_BASE_URL5 = "https://api-singapore.klingai.com";
3217
+ var POLL_INTERVAL_MS5 = 5e3;
3218
+ var DEFAULT_MAX_POLL_ATTEMPTS5 = 240;
2834
3219
  function stateOf(task) {
2835
3220
  const raw = (task.status ?? task.task_status ?? "").toLowerCase();
2836
3221
  if (raw === "failed") return "failed";
@@ -2845,7 +3230,7 @@ var KlingAdapter = class {
2845
3230
  this.opts = opts;
2846
3231
  }
2847
3232
  get baseUrl() {
2848
- return (this.opts.baseUrl ?? DEFAULT_BASE_URL4).replace(/\/$/, "");
3233
+ return (this.opts.baseUrl ?? DEFAULT_BASE_URL5).replace(/\/$/, "");
2849
3234
  }
2850
3235
  async request(path, init) {
2851
3236
  const response = await fetch(`${this.baseUrl}${path}`, {
@@ -3027,7 +3412,7 @@ var KlingAdapter = class {
3027
3412
  params,
3028
3413
  submittedAt: new Date(startedAt).toISOString()
3029
3414
  };
3030
- const maxAttempts = this.opts.maxPollAttempts ?? DEFAULT_MAX_POLL_ATTEMPTS4;
3415
+ const maxAttempts = this.opts.maxPollAttempts ?? DEFAULT_MAX_POLL_ATTEMPTS5;
3031
3416
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
3032
3417
  const snapshot = await this.completeJob(job);
3033
3418
  if (snapshot.status === "failed") {
@@ -3036,7 +3421,7 @@ var KlingAdapter = class {
3036
3421
  if (snapshot.status === "succeeded" && snapshot.result) {
3037
3422
  return { ...snapshot.result, processingTimeMs: Date.now() - startedAt };
3038
3423
  }
3039
- await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS4));
3424
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS5));
3040
3425
  }
3041
3426
  return failure(
3042
3427
  `Kling task ${submitted.taskId} did not finish after ${maxAttempts} checks.`
@@ -3173,7 +3558,7 @@ var KlingAdapter = class {
3173
3558
  };
3174
3559
 
3175
3560
  // src/adapters/openai.adapter.ts
3176
- var DEFAULT_BASE_URL5 = "https://api.openai.com";
3561
+ var DEFAULT_BASE_URL6 = "https://api.openai.com";
3177
3562
  var IMAGES_PATH2 = "/v1/images/generations";
3178
3563
  var OpenAIAdapter = class {
3179
3564
  providerName = "openai";
@@ -3184,7 +3569,7 @@ var OpenAIAdapter = class {
3184
3569
  this.opts = opts;
3185
3570
  }
3186
3571
  get baseUrl() {
3187
- return (this.opts.baseUrl ?? DEFAULT_BASE_URL5).replace(/\/$/, "");
3572
+ return (this.opts.baseUrl ?? DEFAULT_BASE_URL6).replace(/\/$/, "");
3188
3573
  }
3189
3574
  binding(modelId) {
3190
3575
  const id = modelId ?? "";
@@ -3405,10 +3790,10 @@ var OpenAIAdapter = class {
3405
3790
  };
3406
3791
 
3407
3792
  // src/adapters/qwen.adapter.ts
3408
- var DEFAULT_BASE_URL6 = "https://dashscope-intl.aliyuncs.com";
3793
+ var DEFAULT_BASE_URL7 = "https://dashscope-intl.aliyuncs.com";
3409
3794
  var TASKS_PATH2 = "/api/v1/tasks";
3410
- var POLL_INTERVAL_MS5 = 5e3;
3411
- var DEFAULT_MAX_POLL_ATTEMPTS5 = 240;
3795
+ var POLL_INTERVAL_MS6 = 5e3;
3796
+ var DEFAULT_MAX_POLL_ATTEMPTS6 = 240;
3412
3797
  var QwenAdapter = class {
3413
3798
  providerName = "qwen";
3414
3799
  supportedTypes = ["image", "video"];
@@ -3417,7 +3802,7 @@ var QwenAdapter = class {
3417
3802
  this.opts = opts;
3418
3803
  }
3419
3804
  get baseUrl() {
3420
- return (this.opts.baseUrl ?? DEFAULT_BASE_URL6).replace(/\/$/, "");
3805
+ return (this.opts.baseUrl ?? DEFAULT_BASE_URL7).replace(/\/$/, "");
3421
3806
  }
3422
3807
  async request(path, init) {
3423
3808
  const headers = {
@@ -3615,7 +4000,7 @@ var QwenAdapter = class {
3615
4000
  params,
3616
4001
  submittedAt: new Date(startedAt).toISOString()
3617
4002
  };
3618
- const maxAttempts = this.opts.maxPollAttempts ?? DEFAULT_MAX_POLL_ATTEMPTS5;
4003
+ const maxAttempts = this.opts.maxPollAttempts ?? DEFAULT_MAX_POLL_ATTEMPTS6;
3619
4004
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
3620
4005
  const snapshot = await this.completeJob(job);
3621
4006
  if (snapshot.status === "failed") {
@@ -3624,7 +4009,7 @@ var QwenAdapter = class {
3624
4009
  if (snapshot.status === "succeeded" && snapshot.result) {
3625
4010
  return { ...snapshot.result, processingTimeMs: Date.now() - startedAt };
3626
4011
  }
3627
- await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS5));
4012
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS6));
3628
4013
  }
3629
4014
  return failure(
3630
4015
  `DashScope task ${taskId} did not finish after ${maxAttempts} checks.`
@@ -3869,9 +4254,9 @@ var QwenAdapter = class {
3869
4254
  };
3870
4255
 
3871
4256
  // src/adapters/topaz.adapter.ts
3872
- var DEFAULT_BASE_URL7 = "https://api.topazlabs.com";
3873
- var POLL_INTERVAL_MS6 = 5e3;
3874
- var DEFAULT_MAX_POLL_ATTEMPTS6 = 240;
4257
+ var DEFAULT_BASE_URL8 = "https://api.topazlabs.com";
4258
+ var POLL_INTERVAL_MS7 = 5e3;
4259
+ var DEFAULT_MAX_POLL_ATTEMPTS7 = 240;
3875
4260
  var DEFAULT_FPS = 30;
3876
4261
  var PIXELS2 = {
3877
4262
  "720p": { width: 1280, height: 720 },
@@ -3891,7 +4276,7 @@ var TopazAdapter = class {
3891
4276
  this.opts = opts;
3892
4277
  }
3893
4278
  get baseUrl() {
3894
- return (this.opts.baseUrl ?? DEFAULT_BASE_URL7).replace(/\/$/, "");
4279
+ return (this.opts.baseUrl ?? DEFAULT_BASE_URL8).replace(/\/$/, "");
3895
4280
  }
3896
4281
  get headers() {
3897
4282
  return {
@@ -4104,7 +4489,7 @@ var TopazAdapter = class {
4104
4489
  params,
4105
4490
  submittedAt: new Date(startedAt).toISOString()
4106
4491
  };
4107
- const maxAttempts = this.opts.maxPollAttempts ?? DEFAULT_MAX_POLL_ATTEMPTS6;
4492
+ const maxAttempts = this.opts.maxPollAttempts ?? DEFAULT_MAX_POLL_ATTEMPTS7;
4108
4493
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
4109
4494
  const snapshot = await this.completeJob(job);
4110
4495
  if (snapshot.status === "failed") {
@@ -4113,7 +4498,7 @@ var TopazAdapter = class {
4113
4498
  if (snapshot.status === "succeeded" && snapshot.result) {
4114
4499
  return { ...snapshot.result, processingTimeMs: Date.now() - startedAt };
4115
4500
  }
4116
- await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS6));
4501
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS7));
4117
4502
  }
4118
4503
  return failure(
4119
4504
  `Topaz request ${submitted.taskId} did not finish after ${maxAttempts} checks.`
@@ -4279,7 +4664,7 @@ var TopazAdapter = class {
4279
4664
  params,
4280
4665
  submittedAt: new Date(startedAt).toISOString()
4281
4666
  };
4282
- const maxAttempts = this.opts.maxPollAttempts ?? DEFAULT_MAX_POLL_ATTEMPTS6;
4667
+ const maxAttempts = this.opts.maxPollAttempts ?? DEFAULT_MAX_POLL_ATTEMPTS7;
4283
4668
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
4284
4669
  const snapshot = await this.completeJob(job);
4285
4670
  if (snapshot.status === "failed") {
@@ -4288,7 +4673,7 @@ var TopazAdapter = class {
4288
4673
  if (snapshot.status === "succeeded" && snapshot.result) {
4289
4674
  return { ...snapshot.result, processingTimeMs: Date.now() - startedAt };
4290
4675
  }
4291
- await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS6));
4676
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS7));
4292
4677
  }
4293
4678
  return failure(
4294
4679
  `Topaz image ${submitted.taskId} did not finish after ${maxAttempts} checks.`
@@ -4491,50 +4876,12 @@ async function persistOutputs(storage, outputs) {
4491
4876
  );
4492
4877
  }
4493
4878
 
4494
- // src/lib/webhook.ts
4495
- function resolveWebhook(value) {
4496
- if (!value) return void 0;
4497
- if (typeof value === "string") {
4498
- return isHttpUrl(value) ? { url: value } : void 0;
4499
- }
4500
- if (!isHttpUrl(value.url)) return void 0;
4501
- return {
4502
- url: value.url,
4503
- secret: value.secret,
4504
- headers: value.headers
4505
- };
4506
- }
4507
- function isHttpUrl(value) {
4508
- try {
4509
- const parsed = new URL(value);
4510
- return parsed.protocol === "https:" || parsed.protocol === "http:";
4511
- } catch {
4512
- return false;
4513
- }
4514
- }
4515
- async function deliverWebhook(config, payload) {
4516
- try {
4517
- await fetch(config.url, {
4518
- method: "POST",
4519
- headers: {
4520
- "content-type": "application/json",
4521
- "user-agent": "@brotu/ai",
4522
- "x-brotu-event": payload.event,
4523
- ...config.secret ? { "x-brotu-webhook-secret": config.secret } : {},
4524
- ...config.headers
4525
- },
4526
- body: JSON.stringify(payload),
4527
- signal: AbortSignal.timeout(1e4)
4528
- });
4529
- } catch {
4530
- }
4531
- }
4532
-
4533
4879
  // src/client.ts
4534
4880
  var NATIVE_PROVIDERS = [
4535
4881
  "byteplus",
4536
4882
  "elevenlabs",
4537
4883
  "google",
4884
+ "kie",
4538
4885
  "kling",
4539
4886
  "openai",
4540
4887
  "qwen",
@@ -4600,6 +4947,16 @@ function brotu(options) {
4600
4947
  workspaceId: optionsWithKey.workspaceId
4601
4948
  });
4602
4949
  }
4950
+ if (provider.id === "kie") {
4951
+ return new KieAdapter({
4952
+ apiKey: provider.apiKey,
4953
+ baseUrl: provider.baseUrl,
4954
+ // The client-level webhook doubles as kie's callback, so a submit
4955
+ // never has to be polled. kie posts its own payload there — read it
4956
+ // with `parseKieCallback`.
4957
+ callbackUrl: registeredWebhook?.url
4958
+ });
4959
+ }
4603
4960
  if (provider.id === "kling") {
4604
4961
  return new KlingAdapter({
4605
4962
  apiKey: provider.apiKey,
@@ -5131,6 +5488,7 @@ function brotu(options) {
5131
5488
  KLING_CAPABILITIES,
5132
5489
  KLING_CATALOG,
5133
5490
  KLING_MODELS,
5491
+ KieAdapter,
5134
5492
  KlingAdapter,
5135
5493
  OPENAI_CATALOG,
5136
5494
  OPENAI_IMAGE_MODELS,
@@ -5159,7 +5517,9 @@ function brotu(options) {
5159
5517
  getProviders,
5160
5518
  hasModel,
5161
5519
  isPendingJob,
5520
+ kieSnapshot,
5162
5521
  ok,
5522
+ parseKieCallback,
5163
5523
  persistOutputs,
5164
5524
  registerModels,
5165
5525
  resetCatalog,
package/dist/index.d.ts CHANGED
@@ -6,6 +6,8 @@ export type { ElevenLabsAdapterOptions, ElevenLabsVoice, } from "./adapters/elev
6
6
  export { ElevenLabsAdapter } from "./adapters/elevenlabs.adapter";
7
7
  export type { GoogleAdapterOptions } from "./adapters/google.adapter";
8
8
  export { GoogleAdapter } from "./adapters/google.adapter";
9
+ export type { KieAdapterOptions, KieTaskResult } from "./adapters/kie.adapter";
10
+ export { KieAdapter, kieSnapshot, parseKieCallback } from "./adapters/kie.adapter";
9
11
  export type { KlingAdapterOptions } from "./adapters/kling.adapter";
10
12
  export { KlingAdapter } from "./adapters/kling.adapter";
11
13
  export type { OpenAIAdapterOptions } from "./adapters/openai.adapter";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}