@mindstudio-ai/agent 0.1.143 → 0.1.145

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.js CHANGED
@@ -2861,6 +2861,11 @@ function resolveTable(databases, tableName, databaseHint) {
2861
2861
 
2862
2862
  // src/files/store.ts
2863
2863
  import { createHash, randomUUID } from "crypto";
2864
+ import { createReadStream, createWriteStream } from "fs";
2865
+ import { readFile, stat, writeFile } from "fs/promises";
2866
+ import { basename } from "path";
2867
+ import { Readable } from "stream";
2868
+ import { pipeline } from "stream/promises";
2864
2869
  function fileUrl(access, store, key) {
2865
2870
  const path = key.split("/").map(encodeURIComponent).join("/");
2866
2871
  return `/_/files/${access}/${store}/${path}`;
@@ -2871,6 +2876,72 @@ function toBase64(content) {
2871
2876
  ).toString("base64");
2872
2877
  }
2873
2878
  var PRESIGN_THRESHOLD = 4 * 1024 * 1024;
2879
+ var MAX_FILE_BYTES = 5 * 1024 * 1024 * 1024;
2880
+ var PART_CONCURRENCY = 4;
2881
+ var PART_ATTEMPTS = 4;
2882
+ var PART_URL_BATCH = 50;
2883
+ var sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
2884
+ function isStream(content) {
2885
+ return typeof content === "object" && content !== null && !(content instanceof Uint8Array) && typeof content[Symbol.asyncIterator] === "function";
2886
+ }
2887
+ async function* inParts(stream2, size) {
2888
+ let pending = [];
2889
+ let pendingBytes = 0;
2890
+ for await (const chunk of stream2) {
2891
+ let rest = chunk;
2892
+ while (pendingBytes + rest.byteLength >= size) {
2893
+ const take = size - pendingBytes;
2894
+ pending.push(rest.subarray(0, take));
2895
+ yield Buffer.concat(pending);
2896
+ rest = rest.subarray(take);
2897
+ pending = [];
2898
+ pendingBytes = 0;
2899
+ }
2900
+ if (rest.byteLength > 0) {
2901
+ pending.push(rest);
2902
+ pendingBytes += rest.byteLength;
2903
+ }
2904
+ }
2905
+ if (pendingBytes > 0) {
2906
+ yield Buffer.concat(pending);
2907
+ }
2908
+ }
2909
+ async function sendPart(partNumber, bytes, urlFor) {
2910
+ let lastStatus = 0;
2911
+ for (let attempt = 1; attempt <= PART_ATTEMPTS; attempt += 1) {
2912
+ try {
2913
+ const res = await fetch(await urlFor(partNumber, lastStatus === 403), {
2914
+ method: "PUT",
2915
+ body: bytes
2916
+ });
2917
+ await res.body?.cancel();
2918
+ if (res.ok) {
2919
+ return;
2920
+ }
2921
+ lastStatus = res.status;
2922
+ if (res.status >= 400 && res.status < 500 && res.status !== 403) {
2923
+ break;
2924
+ }
2925
+ } catch {
2926
+ lastStatus = 0;
2927
+ }
2928
+ if (attempt < PART_ATTEMPTS) {
2929
+ await sleep2(1e3 * 2 ** (attempt - 1));
2930
+ }
2931
+ }
2932
+ throw new MindStudioError(
2933
+ `Upload failed: part ${partNumber}${lastStatus ? ` (${lastStatus})` : ""}`,
2934
+ "upload_error",
2935
+ lastStatus || 502
2936
+ );
2937
+ }
2938
+ async function sha256OfFile(path) {
2939
+ const hash = createHash("sha256");
2940
+ for await (const chunk of createReadStream(path)) {
2941
+ hash.update(chunk);
2942
+ }
2943
+ return hash.digest("hex");
2944
+ }
2874
2945
  function extensionFor(filename) {
2875
2946
  if (!filename) {
2876
2947
  return "";
@@ -2879,16 +2950,18 @@ function extensionFor(filename) {
2879
2950
  return dot > 0 ? filename.slice(dot) : "";
2880
2951
  }
2881
2952
  var Store = class {
2882
- constructor(_store, _access, _call, _policy = {}) {
2953
+ constructor(_store, _access, _call, _policy = {}, _runStep) {
2883
2954
  this._store = _store;
2884
2955
  this._access = _access;
2885
2956
  this._call = _call;
2886
2957
  this._policy = _policy;
2958
+ this._runStep = _runStep;
2887
2959
  }
2888
2960
  _store;
2889
2961
  _access;
2890
2962
  _call;
2891
2963
  _policy;
2964
+ _runStep;
2892
2965
  /** The store's name. */
2893
2966
  get name() {
2894
2967
  return this._store;
@@ -2897,8 +2970,17 @@ var Store = class {
2897
2970
  get access() {
2898
2971
  return this._access;
2899
2972
  }
2900
- /** Store bytes. Returns a {@link StoredFile} with a ready-to-use `url`. */
2973
+ /**
2974
+ * Store bytes, or a stream of them (a Node `Readable`, a web
2975
+ * `ReadableStream`, any async iterable of `Uint8Array`), which goes up a
2976
+ * part at a time and is never held whole. Returns a {@link StoredFile} with
2977
+ * a ready-to-use `url`. A stream needs a `key`, or a `filename` for one,
2978
+ * since a content-addressed key needs every byte before the first is sent.
2979
+ */
2901
2980
  async put(content, options) {
2981
+ if (isStream(content)) {
2982
+ return this._putInParts(content, void 0, options);
2983
+ }
2902
2984
  const ext = extensionFor(options?.filename);
2903
2985
  const key = options?.key ?? (options?.contentAddressed ? `${createHash("sha256").update(content).digest("hex")}${ext}` : `${randomUUID()}${ext}`);
2904
2986
  const cacheControl = options?.cacheControl ?? (!options?.key && this._access === "public" ? "public, max-age=31536000, immutable" : void 0);
@@ -2972,14 +3054,211 @@ var Store = class {
2972
3054
  });
2973
3055
  return this._toFile(key, meta);
2974
3056
  }
2975
- /** Read an object's bytes (backend / trusted context). */
3057
+ /**
3058
+ * Store a file from disk, read a part at a time, so a large one is never
3059
+ * held in memory: a recording, an ffmpeg output. `filename` defaults to the
3060
+ * file's own name, for the key's extension.
3061
+ */
3062
+ async putFile(path, options) {
3063
+ const { size } = await stat(path);
3064
+ const named = { filename: basename(path), ...options };
3065
+ if (size < PRESIGN_THRESHOLD) {
3066
+ return this.put(await readFile(path), named);
3067
+ }
3068
+ const key = named.key ?? (named.contentAddressed ? `${await sha256OfFile(path)}${extensionFor(named.filename)}` : void 0);
3069
+ return this._putInParts(
3070
+ createReadStream(path, { highWaterMark: 1024 * 1024 }),
3071
+ size,
3072
+ { ...named, ...key ? { key } : {} }
3073
+ );
3074
+ }
3075
+ /**
3076
+ * Copy the file at a URL into this store, without its bytes passing
3077
+ * through the method: the platform fetches it straight into storage. For a
3078
+ * direct link to a file (a recording, an export, a podcast episode's mp3),
3079
+ * up to 5 GiB; a share page (Google Drive's, Dropbox's) isn't the file. The
3080
+ * key is chosen for it, the URL's extension kept. The `importFromUrl`
3081
+ * action, with this store as its `store`.
3082
+ */
3083
+ async importFromUrl(url, options) {
3084
+ if (!this._runStep) {
3085
+ throw new MindStudioError(
3086
+ "importFromUrl needs the agent this store came from.",
3087
+ "import_unavailable",
3088
+ 400
3089
+ );
3090
+ }
3091
+ const out = await this._runStep(
3092
+ "importFromUrl",
3093
+ {
3094
+ url,
3095
+ ...options?.contentType ? { contentType: options.contentType } : {}
3096
+ },
3097
+ this
3098
+ );
3099
+ return this._toFile(out.key, {
3100
+ size: out.size,
3101
+ contentType: out.contentType
3102
+ });
3103
+ }
3104
+ /**
3105
+ * A file too large for one request: a multipart upload, its parts sent
3106
+ * straight to storage `PART_CONCURRENCY` at a time, each retried on its own.
3107
+ * `size` is the file's when known; a stream's is learned as it ends.
3108
+ */
3109
+ async _putInParts(stream2, size, options) {
3110
+ if (!options?.key && options?.contentAddressed) {
3111
+ throw new MindStudioError(
3112
+ "A content-addressed key needs every byte before the upload starts: pass a Buffer, or use putFile.",
3113
+ "content_addressed_stream",
3114
+ 400
3115
+ );
3116
+ }
3117
+ const key = options?.key ?? `${randomUUID()}${extensionFor(options?.filename)}`;
3118
+ const cacheControl = options?.cacheControl ?? (!options?.key && this._access === "public" ? "public, max-age=31536000, immutable" : void 0);
3119
+ const grant = await this._call("create-upload", {
3120
+ store: this._store,
3121
+ access: this._access,
3122
+ key,
3123
+ maxSize: size ?? MAX_FILE_BYTES,
3124
+ ...options?.contentType ? { contentType: options.contentType } : {},
3125
+ ...cacheControl ? { cacheControl } : {}
3126
+ });
3127
+ const { session: session2, partSize } = await this._call("uploads/start", {
3128
+ grant: grant.grant,
3129
+ ...size !== void 0 ? { size } : {}
3130
+ });
3131
+ let sent;
3132
+ try {
3133
+ sent = await this._sendParts(session2, inParts(stream2, partSize));
3134
+ } catch (err) {
3135
+ await this._call("uploads/abort", { session: session2 }).catch(() => {
3136
+ });
3137
+ throw err;
3138
+ }
3139
+ if (sent === 0) {
3140
+ await this._call("uploads/abort", { session: session2 }).catch(() => {
3141
+ });
3142
+ return this.put(new Uint8Array(0), { ...options, key });
3143
+ }
3144
+ const meta = await this._call("uploads/complete", { session: session2 });
3145
+ return this._toFile(key, meta);
3146
+ }
3147
+ /** Send every part, at most `PART_CONCURRENCY` at once; how many were sent. */
3148
+ async _sendParts(session2, parts) {
3149
+ const batches = /* @__PURE__ */ new Map();
3150
+ const urlFor = (partNumber, fresh = false) => {
3151
+ const batch = Math.floor((partNumber - 1) / PART_URL_BATCH);
3152
+ if (fresh) {
3153
+ batches.delete(batch);
3154
+ }
3155
+ let urls = batches.get(batch);
3156
+ if (!urls) {
3157
+ const first = batch * PART_URL_BATCH + 1;
3158
+ urls = this._call("uploads/parts", {
3159
+ session: session2,
3160
+ parts: Array.from({ length: PART_URL_BATCH }, (_, i) => first + i)
3161
+ }).then(
3162
+ (res) => new Map(res.urls.map(({ partNumber: n, url }) => [n, url]))
3163
+ );
3164
+ batches.set(batch, urls);
3165
+ }
3166
+ return urls.then((map) => map.get(partNumber));
3167
+ };
3168
+ const inflight = /* @__PURE__ */ new Set();
3169
+ let failure;
3170
+ let count = 0;
3171
+ for await (const bytes of parts) {
3172
+ if (failure) {
3173
+ break;
3174
+ }
3175
+ count += 1;
3176
+ const partNumber = count;
3177
+ const sending = sendPart(partNumber, bytes, urlFor).catch((err) => {
3178
+ failure ??= err instanceof Error ? err : new Error(String(err));
3179
+ }).finally(() => inflight.delete(sending));
3180
+ inflight.add(sending);
3181
+ if (inflight.size >= PART_CONCURRENCY) {
3182
+ await Promise.race(inflight);
3183
+ }
3184
+ }
3185
+ await Promise.all(inflight);
3186
+ if (failure) {
3187
+ throw failure;
3188
+ }
3189
+ return count;
3190
+ }
3191
+ /**
3192
+ * Read an object's bytes (backend / trusted context), straight from storage.
3193
+ * For a large file, {@link getStream} or {@link download} reads it without
3194
+ * holding it whole.
3195
+ */
2976
3196
  async get(key) {
2977
- const res = await this._call("get", {
3197
+ const res = await this._read(key);
3198
+ return Buffer.from(await res.arrayBuffer());
3199
+ }
3200
+ /**
3201
+ * Read an object as a stream, straight from storage: the whole of it, or
3202
+ * the bytes from `start` to `end`, both included. A web `ReadableStream`
3203
+ * (`Readable.fromWeb` makes it a Node stream).
3204
+ */
3205
+ async getStream(key, options) {
3206
+ const res = await this._read(key, options);
3207
+ return res.body ?? new ReadableStream({
3208
+ start: (controller) => controller.close()
3209
+ });
3210
+ }
3211
+ /** Write an object to a file on disk, streamed, so it's never held whole. */
3212
+ async download(key, path) {
3213
+ const res = await this._read(key);
3214
+ if (!res.body) {
3215
+ await writeFile(path, new Uint8Array(0));
3216
+ return;
3217
+ }
3218
+ await pipeline(
3219
+ Readable.fromWeb(res.body),
3220
+ createWriteStream(path)
3221
+ );
3222
+ }
3223
+ /** A GET straight to storage, through a short-lived URL the platform signs. */
3224
+ async _read(key, range) {
3225
+ const { url } = await this._call("read-url", {
2978
3226
  store: this._store,
2979
3227
  access: this._access,
2980
3228
  key
2981
3229
  });
2982
- return Buffer.from(res.body, "base64");
3230
+ const ranged = range && (range.start !== void 0 || range.end !== void 0);
3231
+ const res = await fetch(
3232
+ url,
3233
+ ranged ? {
3234
+ headers: {
3235
+ Range: `bytes=${range.start ?? 0}-${range.end ?? ""}`
3236
+ }
3237
+ } : void 0
3238
+ );
3239
+ if (res.ok) {
3240
+ return res;
3241
+ }
3242
+ await res.body?.cancel();
3243
+ if (res.status === 404) {
3244
+ throw new MindStudioError(
3245
+ `No file "${key}" in store "${this._store}".`,
3246
+ "file_not_found",
3247
+ 404
3248
+ );
3249
+ }
3250
+ if (res.status === 416) {
3251
+ throw new MindStudioError(
3252
+ `The range is outside "${key}".`,
3253
+ "range_not_satisfiable",
3254
+ 416
3255
+ );
3256
+ }
3257
+ throw new MindStudioError(
3258
+ `Read failed: ${res.status} ${res.statusText}`,
3259
+ "file_error",
3260
+ res.status
3261
+ );
2983
3262
  }
2984
3263
  /** Metadata without downloading. Rejects if the object doesn't exist. */
2985
3264
  async head(key) {
@@ -3083,7 +3362,11 @@ var Store = class {
3083
3362
  return {
3084
3363
  key,
3085
3364
  url: fileUrl(this._access, this._store, key),
3086
- upload: { url: res.uploadUrl, fields: res.uploadFields }
3365
+ upload: {
3366
+ url: res.uploadUrl,
3367
+ fields: res.uploadFields,
3368
+ ...res.grant ? { grant: res.grant } : {}
3369
+ }
3087
3370
  };
3088
3371
  }
3089
3372
  _toFile(key, meta) {
@@ -3112,13 +3395,19 @@ var Store = class {
3112
3395
  };
3113
3396
 
3114
3397
  // src/files/index.ts
3115
- function createFiles(call) {
3398
+ function createFiles(call, runStep) {
3116
3399
  return {
3117
3400
  defineStore(name, options) {
3118
- return new Store(name, options?.access ?? "private", call, {
3119
- ...options?.maxSize !== void 0 ? { maxSize: options.maxSize } : {},
3120
- ...options?.contentTypes ? { contentTypes: options.contentTypes } : {}
3121
- });
3401
+ return new Store(
3402
+ name,
3403
+ options?.access ?? "private",
3404
+ call,
3405
+ {
3406
+ ...options?.maxSize !== void 0 ? { maxSize: options.maxSize } : {},
3407
+ ...options?.contentTypes ? { contentTypes: options.contentTypes } : {}
3408
+ },
3409
+ runStep
3410
+ );
3122
3411
  }
3123
3412
  };
3124
3413
  }
@@ -4322,6 +4611,13 @@ var stepMetadata = {
4322
4611
  inputSchema: { "type": "object", "properties": { "imageUrl": { "type": "string", "description": "URL of the image to remove the watermark from" }, "engine": { "type": "string", "description": "Watermark removal engine to use" }, "intermediateAsset": { "type": "boolean", "description": "When true, the asset is created but hidden from the user's gallery (tagged as intermediate)" } }, "required": ["imageUrl", "engine"] },
4323
4612
  outputSchema: { "type": "object", "properties": { "imageUrl": { "type": "string", "description": "CDN URL of the processed image with watermark removed (PNG)" } }, "required": ["imageUrl"] }
4324
4613
  },
4614
+ "importFromUrl": {
4615
+ stepType: "importFromUrl",
4616
+ description: "Copy a file at a URL into one of the app's file stores, without the bytes passing through the method",
4617
+ usageNotes: "- For a direct link to a file (a recording, an export, a podcast episode's mp3), up to 5 GiB. The file is fetched and stored as it is.\n- Requires a store: pass the store handle as `store` in the options. `Store.importFromUrl(url)` is the same call, returning a StoredFile.\n- A share page (a Google Drive or Dropbox page, YouTube) is not the file: pass the file's own download link, or use downloadVideo for a hosted video.",
4618
+ inputSchema: { "type": "object", "properties": { "url": { "type": "string", "description": "A direct link to the file" }, "contentType": { "type": "string", "description": "The type to store it as; the response's own when omitted" } }, "required": ["url"] },
4619
+ outputSchema: { "type": "object", "properties": { "fileUrl": { "type": "string", "description": "The stored file's stable on-domain URL" }, "key": { "type": "string", "description": "The stored file's key in its store" }, "size": { "type": "number", "description": "Its size in bytes" }, "contentType": { "type": "string", "description": "The type it was stored as" } }, "required": ["fileUrl", "key", "size", "contentType"] }
4620
+ },
4325
4621
  "insertVideoClips": {
4326
4622
  stepType: "insertVideoClips",
4327
4623
  description: "Insert b-roll clips into a base video at a timecode, optionally with an xfade transition.",
@@ -4550,6 +4846,13 @@ var stepMetadata = {
4550
4846
  inputSchema: { "type": "object", "properties": { "type": { "enum": ["standard", "advanced"], "type": "string", "description": "Background removal quality tier" }, "imageUrl": { "type": "string", "description": "URL of the source image to remove the background from" }, "autoCrop": { "type": "boolean", "description": "Whether to automatically trim transparent padding from the result, on by default" } }, "required": ["imageUrl"] },
4551
4847
  outputSchema: { "type": "object", "properties": { "imageUrl": { "type": "string", "description": "CDN URL of the image with background removed (transparent PNG)" } }, "required": ["imageUrl"] }
4552
4848
  },
4849
+ "renderVideoFromHtmlComposition": {
4850
+ stepType: "renderVideoFromHtmlComposition",
4851
+ description: "Render an HTML composition (HTML, CSS, media and seekable animation on a timeline) to a video file",
4852
+ usageNotes: "- Renders an HTML composition in HyperFrames' format (https://hyperframes.heygen.com) with HyperFrames 0.8.97, in Chrome off the app's own sandbox; author for that version\n- Pass the composition as `html`, the page itself (up to 10 MB), or as `project`, the URL of a zip of a project's folder (`index.html` at its root, or the file `entryFile` names) or of one HTML file, up to 2 GiB: an app file or any public URL\n- `variables` overrides the composition's `data-composition-variables` defaults, which it reads with `window.__hyperframes.getVariables()`\n- Whatever the composition loads itself (fonts, scripts, images, video) is fetched as it renders, so each is in the project or at a public URL; a private app file needs a signed URL\n- fps 24, 30 or 60 (default 30); quality draft, standard or high (default standard); format mp4 (default), webm or mov (both keep transparency), or gif\n- One render per call, finishing within 15 minutes\n- A composition HyperFrames can't render fails with HyperFrames' own reason",
4853
+ inputSchema: { "type": "object", "properties": { "html": { "type": "string", "description": "The composition's HTML, up to 10 MB; give this or project" }, "project": { "type": "string", "description": "URL of a zip of the project's folder, or of one self-contained HTML file; give this or html" }, "entryFile": { "type": "string", "description": "The entry HTML inside the zip, relative to its root; index.html when omitted" }, "variables": { "type": "object", "properties": {}, "required": [], "description": "Values over the composition's declared variables" }, "fps": { "enum": [24, 30, 60], "type": "number", "description": "Frames per second: 24, 30 (default) or 60" }, "quality": { "enum": ["draft", "standard", "high"], "type": "string", "description": "Encode quality: draft, standard (default) or high" }, "format": { "enum": ["mp4", "webm", "mov", "gif"], "type": "string", "description": "mp4 (default), webm or mov (both keep transparency), or gif" }, "intermediateAsset": { "type": "boolean", "description": "When true, the asset is created but hidden from the user's gallery (tagged as intermediate)" } } },
4854
+ outputSchema: { "type": "object", "properties": { "videoUrl": { "type": "string", "description": "URL of the rendered video" }, "durationSec": { "type": "number", "description": "Its length in seconds" }, "frames": { "type": "number", "description": "How many frames it has" } }, "required": ["videoUrl", "durationSec", "frames"] }
4855
+ },
4553
4856
  "replyToGmailEmail": {
4554
4857
  stepType: "replyToGmailEmail",
4555
4858
  description: "Reply to an existing email in Gmail. The reply is threaded under the original message.",
@@ -4564,6 +4867,13 @@ var stepMetadata = {
4564
4867
  inputSchema: { "type": "object", "properties": { "videoUrl": { "type": "string", "description": "URL of the source video to resize" }, "mode": { "enum": ["fit", "exact"], "type": "string", "description": "Resize mode: 'fit' scales within max dimensions, 'exact' forces exact dimensions" }, "maxWidth": { "type": "number", "description": "Maximum width in pixels (used with 'fit' mode)" }, "maxHeight": { "type": "number", "description": "Maximum height in pixels (used with 'fit' mode)" }, "width": { "type": "number", "description": "Exact width in pixels (used with 'exact' mode)" }, "height": { "type": "number", "description": "Exact height in pixels (used with 'exact' mode)" }, "strategy": { "enum": ["pad", "crop"], "type": "string", "description": "Strategy for handling aspect ratio mismatch in 'exact' mode" }, "intermediateAsset": { "type": "boolean", "description": "When true, the asset is created but hidden from the user's gallery (tagged as intermediate)" } }, "required": ["videoUrl", "mode"] },
4565
4868
  outputSchema: { "type": "object", "properties": { "videoUrl": { "type": "string", "description": "URL of the resized video" } }, "required": ["videoUrl"] }
4566
4869
  },
4870
+ "runFfmpeg": {
4871
+ stepType: "runFfmpeg",
4872
+ description: "Run an ffmpeg command on media files",
4873
+ usageNotes: "- Runs one ffmpeg command, its arguments exactly as given, off the app's own sandbox (ffmpeg 9, a static build with the common codecs and filters)\n- `inputs` names each input file by URL; `{in:<name>}` anywhere in an argument, a filter graph included, is that file's local path\n- `{out:<file.ext>}` anywhere in an argument is an output's path; every output the arguments name comes back in `outputs` by its file name, its type from its extension\n- One command per call, finishing within 15 minutes; up to 10 GB of inputs and outputs together, 5 GB per output\n- The system fonts are Noto Sans (`drawtext`'s default), DejaVu Sans, Liberation Sans and Noto Color Emoji: pass a font file as an input for any other\n- A command ffmpeg refuses fails with ffmpeg's own error lines",
4874
+ inputSchema: { "type": "object", "properties": { "inputs": { "type": "object", "properties": {}, "required": [], "description": "Input files by name, each a URL; `{in:<name>}` in an argument is its local path" }, "args": { "type": "array", "items": { "type": "string" }, "description": "ffmpeg's arguments, run as given; `{out:<file.ext>}` in an argument is an output's path" }, "intermediateAsset": { "type": "boolean", "description": "When true, the assets are created but hidden from the user's gallery (tagged as intermediate)" } }, "required": ["inputs", "args"] },
4875
+ outputSchema: { "type": "object", "properties": { "outputs": { "type": "object", "properties": {}, "required": [], "description": "Each output's URL, by the file name `{out:<file.ext>}` gave it" } }, "required": ["outputs"] }
4876
+ },
4567
4877
  "runFromConnectorRegistry": {
4568
4878
  stepType: "runFromConnectorRegistry",
4569
4879
  description: "Run a raw API connector to a third-party service",
@@ -4965,7 +5275,7 @@ function mapTools(tools) {
4965
5275
  return defaults ? { stepType, defaults } : { stepType };
4966
5276
  });
4967
5277
  }
4968
- function sleep2(ms) {
5278
+ function sleep3(ms) {
4969
5279
  return new Promise((resolve) => setTimeout(resolve, ms));
4970
5280
  }
4971
5281
  function isDevMode() {
@@ -5339,7 +5649,7 @@ async function runTurnWithRetry(httpConfig, body) {
5339
5649
  `[task] connection lost, retrying turn (attempt ${attempt + 2} of ${MAX_TURN_ATTEMPTS})`
5340
5650
  );
5341
5651
  }
5342
- await sleep2(INITIAL_BACKOFF_MS * 2 ** attempt);
5652
+ await sleep3(INITIAL_BACKOFF_MS * 2 ** attempt);
5343
5653
  }
5344
5654
  }
5345
5655
  throw new TurnError("Turn retries exhausted", false, "request");
@@ -5908,6 +6218,9 @@ function applyStepMethods(AgentClass) {
5908
6218
  proto.imageRemoveWatermark = function(step, options) {
5909
6219
  return this.executeStep("imageRemoveWatermark", step, options);
5910
6220
  };
6221
+ proto.importFromUrl = function(step, options) {
6222
+ return this.executeStep("importFromUrl", step, options);
6223
+ };
5911
6224
  proto.insertVideoClips = function(step, options) {
5912
6225
  return this.executeStep("insertVideoClips", step, options);
5913
6226
  };
@@ -6004,12 +6317,18 @@ function applyStepMethods(AgentClass) {
6004
6317
  proto.removeBackgroundFromImage = function(step, options) {
6005
6318
  return this.executeStep("removeBackgroundFromImage", step, options);
6006
6319
  };
6320
+ proto.renderVideoFromHtmlComposition = function(step, options) {
6321
+ return this.executeStep("renderVideoFromHtmlComposition", step, options);
6322
+ };
6007
6323
  proto.replyToGmailEmail = function(step, options) {
6008
6324
  return this.executeStep("replyToGmailEmail", step, options);
6009
6325
  };
6010
6326
  proto.resizeVideo = function(step, options) {
6011
6327
  return this.executeStep("resizeVideo", step, options);
6012
6328
  };
6329
+ proto.runFfmpeg = function(step, options) {
6330
+ return this.executeStep("runFfmpeg", step, options);
6331
+ };
6013
6332
  proto.runFromConnectorRegistry = function(step, options) {
6014
6333
  return this.executeStep("runFromConnectorRegistry", step, options);
6015
6334
  };
@@ -6159,6 +6478,13 @@ function applyStepMethods(AgentClass) {
6159
6478
  // src/client.ts
6160
6479
  var DEFAULT_BASE_URL = "https://v1.mindstudio-api.com";
6161
6480
  var DEFAULT_MAX_RETRIES = 3;
6481
+ var toStepLogEvent = (event) => ({
6482
+ value: event.value,
6483
+ tag: event.tag,
6484
+ ts: event.ts,
6485
+ ...event.ephemeral === true ? { ephemeral: true } : {},
6486
+ ...typeof event.progress === "number" ? { progress: event.progress } : {}
6487
+ });
6162
6488
  var MindStudioAgent = class {
6163
6489
  /** @internal */
6164
6490
  _httpConfig;
@@ -6320,7 +6646,7 @@ var MindStudioAgent = class {
6320
6646
  const pollUrl = `${this._currentHttpConfig.baseUrl}/developer/v2/steps/${stepType}/execute-async/poll/${asyncData.executionToken}`;
6321
6647
  let pollDelay = 100;
6322
6648
  while (true) {
6323
- await sleep3(pollDelay);
6649
+ await sleep4(pollDelay);
6324
6650
  pollDelay = Math.min(pollDelay * 2, 5e3);
6325
6651
  const res = await fetch(pollUrl, {
6326
6652
  headers: { "User-Agent": "@mindstudio-ai/agent" }
@@ -6472,11 +6798,7 @@ var MindStudioAgent = class {
6472
6798
  try {
6473
6799
  const event = JSON.parse(line.slice(6));
6474
6800
  if (event.type === "log") {
6475
- options.onLog({
6476
- value: event.value,
6477
- tag: event.tag,
6478
- ts: event.ts
6479
- });
6801
+ options.onLog(toStepLogEvent(event));
6480
6802
  } else if (event.type === "done") {
6481
6803
  doneEvent = {
6482
6804
  output: event.output,
@@ -6515,11 +6837,7 @@ var MindStudioAgent = class {
6515
6837
  500
6516
6838
  );
6517
6839
  } else if (event.type === "log") {
6518
- options.onLog({
6519
- value: event.value,
6520
- tag: event.tag,
6521
- ts: event.ts
6522
- });
6840
+ options.onLog(toStepLogEvent(event));
6523
6841
  }
6524
6842
  } catch (err) {
6525
6843
  if (err instanceof MindStudioError) {
@@ -6613,7 +6931,7 @@ var MindStudioAgent = class {
6613
6931
  const pollUrl = `${this._currentHttpConfig.baseUrl}/developer/v2/steps/execute-batch-async/poll/${asyncData.batchToken}`;
6614
6932
  let pollDelay = 300;
6615
6933
  while (true) {
6616
- await sleep3(pollDelay);
6934
+ await sleep4(pollDelay);
6617
6935
  pollDelay = Math.min(pollDelay * 1.5, 3e3);
6618
6936
  const res = await fetch(pollUrl, {
6619
6937
  headers: { "User-Agent": "@mindstudio-ai/agent" }
@@ -6879,7 +7197,7 @@ var MindStudioAgent = class {
6879
7197
  const token = data.callbackToken;
6880
7198
  const pollUrl = `${this._currentHttpConfig.baseUrl}/developer/v2/agents/run/poll/${token}`;
6881
7199
  while (true) {
6882
- await sleep3(pollInterval);
7200
+ await sleep4(pollInterval);
6883
7201
  const res = await fetch(pollUrl, {
6884
7202
  headers: { "User-Agent": "@mindstudio-ai/agent" }
6885
7203
  });
@@ -7237,7 +7555,10 @@ var MindStudioAgent = class {
7237
7555
  * ```
7238
7556
  */
7239
7557
  get files() {
7240
- return this._files ??= createFiles(this._filesRequest.bind(this));
7558
+ return this._files ??= createFiles(
7559
+ this._filesRequest.bind(this),
7560
+ (stepType, step, store) => this.executeStep(stepType, step, { store })
7561
+ );
7241
7562
  }
7242
7563
  /**
7243
7564
  * The `analytics` namespace — read the app's own traffic + event analytics
@@ -8003,7 +8324,7 @@ var MindStudioAgent = class {
8003
8324
  return { url: data.publicUrl };
8004
8325
  }
8005
8326
  };
8006
- function sleep3(ms) {
8327
+ function sleep4(ms) {
8007
8328
  return new Promise((resolve) => setTimeout(resolve, ms));
8008
8329
  }
8009
8330
  applyStepMethods(MindStudioAgent);
@@ -8687,6 +9008,7 @@ var monacoSnippets = {
8687
9008
  "hunterApiPersonEnrichment": { fields: [["email", "string"]], outputKeys: ["data"] },
8688
9009
  "imageFaceSwap": { fields: [["imageUrl", "string"], ["faceImageUrl", "string"], ["engine", "string"]], outputKeys: ["imageUrl"] },
8689
9010
  "imageRemoveWatermark": { fields: [["imageUrl", "string"], ["engine", "string"]], outputKeys: ["imageUrl"] },
9011
+ "importFromUrl": { fields: [["url", "string"]], outputKeys: ["fileUrl", "key", "size", "contentType"] },
8690
9012
  "insertVideoClips": { fields: [["baseVideoUrl", "string"], ["overlayVideos", "array"]], outputKeys: ["videoUrl"] },
8691
9013
  "listDataSources": { fields: [], outputKeys: [] },
8692
9014
  "listGmailDrafts": { fields: [["exportType", ["json", "text"]]], outputKeys: ["drafts"] },
@@ -8719,8 +9041,10 @@ var monacoSnippets = {
8719
9041
  "quickbooksApiRequest": { fields: [["method", "string"], ["route", "string"], ["queryParams", "object"], ["body", "string"]], outputKeys: ["ok", "status", "body"] },
8720
9042
  "redactPII": { fields: [["input", "string"], ["language", "string"], ["entities", "array"]], outputKeys: ["text"] },
8721
9043
  "removeBackgroundFromImage": { fields: [["imageUrl", "string"]], outputKeys: ["imageUrl"] },
9044
+ "renderVideoFromHtmlComposition": { fields: [], outputKeys: ["videoUrl", "durationSec", "frames"] },
8722
9045
  "replyToGmailEmail": { fields: [["messageId", "string"], ["message", "string"], ["messageType", ["plain", "html", "markdown"]]], outputKeys: ["messageId"] },
8723
9046
  "resizeVideo": { fields: [["videoUrl", "string"], ["mode", ["fit", "exact"]]], outputKeys: ["videoUrl"] },
9047
+ "runFfmpeg": { fields: [["inputs", "object"], ["args", "array"]], outputKeys: ["outputs"] },
8724
9048
  "runFromConnectorRegistry": { fields: [["actionId", "string"], ["displayName", "string"], ["icon", "string"], ["configurationValues", "object"]], outputKeys: ["data"] },
8725
9049
  "scrapeLinkedInCompany": { fields: [["url", "string"]], outputKeys: ["company"] },
8726
9050
  "scrapeLinkedInProfile": { fields: [["url", "string"]], outputKeys: ["profile"] },