@mindstudio-ai/agent 0.1.142 → 0.1.144
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/cli.js +354 -32
- package/dist/index.d.ts +185 -7
- package/dist/index.js +343 -18
- package/dist/index.js.map +1 -1
- package/dist/postinstall.js +354 -32
- package/llms.txt +37 -2
- package/package.json +1 -1
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
|
-
/**
|
|
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
|
-
/**
|
|
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.
|
|
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
|
-
|
|
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: {
|
|
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(
|
|
3119
|
-
|
|
3120
|
-
|
|
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
|
|
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
|
|
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
|
};
|
|
@@ -6320,7 +6639,7 @@ var MindStudioAgent = class {
|
|
|
6320
6639
|
const pollUrl = `${this._currentHttpConfig.baseUrl}/developer/v2/steps/${stepType}/execute-async/poll/${asyncData.executionToken}`;
|
|
6321
6640
|
let pollDelay = 100;
|
|
6322
6641
|
while (true) {
|
|
6323
|
-
await
|
|
6642
|
+
await sleep4(pollDelay);
|
|
6324
6643
|
pollDelay = Math.min(pollDelay * 2, 5e3);
|
|
6325
6644
|
const res = await fetch(pollUrl, {
|
|
6326
6645
|
headers: { "User-Agent": "@mindstudio-ai/agent" }
|
|
@@ -6613,7 +6932,7 @@ var MindStudioAgent = class {
|
|
|
6613
6932
|
const pollUrl = `${this._currentHttpConfig.baseUrl}/developer/v2/steps/execute-batch-async/poll/${asyncData.batchToken}`;
|
|
6614
6933
|
let pollDelay = 300;
|
|
6615
6934
|
while (true) {
|
|
6616
|
-
await
|
|
6935
|
+
await sleep4(pollDelay);
|
|
6617
6936
|
pollDelay = Math.min(pollDelay * 1.5, 3e3);
|
|
6618
6937
|
const res = await fetch(pollUrl, {
|
|
6619
6938
|
headers: { "User-Agent": "@mindstudio-ai/agent" }
|
|
@@ -6879,7 +7198,7 @@ var MindStudioAgent = class {
|
|
|
6879
7198
|
const token = data.callbackToken;
|
|
6880
7199
|
const pollUrl = `${this._currentHttpConfig.baseUrl}/developer/v2/agents/run/poll/${token}`;
|
|
6881
7200
|
while (true) {
|
|
6882
|
-
await
|
|
7201
|
+
await sleep4(pollInterval);
|
|
6883
7202
|
const res = await fetch(pollUrl, {
|
|
6884
7203
|
headers: { "User-Agent": "@mindstudio-ai/agent" }
|
|
6885
7204
|
});
|
|
@@ -7237,7 +7556,10 @@ var MindStudioAgent = class {
|
|
|
7237
7556
|
* ```
|
|
7238
7557
|
*/
|
|
7239
7558
|
get files() {
|
|
7240
|
-
return this._files ??= createFiles(
|
|
7559
|
+
return this._files ??= createFiles(
|
|
7560
|
+
this._filesRequest.bind(this),
|
|
7561
|
+
(stepType, step, store) => this.executeStep(stepType, step, { store })
|
|
7562
|
+
);
|
|
7241
7563
|
}
|
|
7242
7564
|
/**
|
|
7243
7565
|
* The `analytics` namespace — read the app's own traffic + event analytics
|
|
@@ -8003,7 +8325,7 @@ var MindStudioAgent = class {
|
|
|
8003
8325
|
return { url: data.publicUrl };
|
|
8004
8326
|
}
|
|
8005
8327
|
};
|
|
8006
|
-
function
|
|
8328
|
+
function sleep4(ms) {
|
|
8007
8329
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
8008
8330
|
}
|
|
8009
8331
|
applyStepMethods(MindStudioAgent);
|
|
@@ -8687,6 +9009,7 @@ var monacoSnippets = {
|
|
|
8687
9009
|
"hunterApiPersonEnrichment": { fields: [["email", "string"]], outputKeys: ["data"] },
|
|
8688
9010
|
"imageFaceSwap": { fields: [["imageUrl", "string"], ["faceImageUrl", "string"], ["engine", "string"]], outputKeys: ["imageUrl"] },
|
|
8689
9011
|
"imageRemoveWatermark": { fields: [["imageUrl", "string"], ["engine", "string"]], outputKeys: ["imageUrl"] },
|
|
9012
|
+
"importFromUrl": { fields: [["url", "string"]], outputKeys: ["fileUrl", "key", "size", "contentType"] },
|
|
8690
9013
|
"insertVideoClips": { fields: [["baseVideoUrl", "string"], ["overlayVideos", "array"]], outputKeys: ["videoUrl"] },
|
|
8691
9014
|
"listDataSources": { fields: [], outputKeys: [] },
|
|
8692
9015
|
"listGmailDrafts": { fields: [["exportType", ["json", "text"]]], outputKeys: ["drafts"] },
|
|
@@ -8719,8 +9042,10 @@ var monacoSnippets = {
|
|
|
8719
9042
|
"quickbooksApiRequest": { fields: [["method", "string"], ["route", "string"], ["queryParams", "object"], ["body", "string"]], outputKeys: ["ok", "status", "body"] },
|
|
8720
9043
|
"redactPII": { fields: [["input", "string"], ["language", "string"], ["entities", "array"]], outputKeys: ["text"] },
|
|
8721
9044
|
"removeBackgroundFromImage": { fields: [["imageUrl", "string"]], outputKeys: ["imageUrl"] },
|
|
9045
|
+
"renderVideoFromHtmlComposition": { fields: [], outputKeys: ["videoUrl", "durationSec", "frames"] },
|
|
8722
9046
|
"replyToGmailEmail": { fields: [["messageId", "string"], ["message", "string"], ["messageType", ["plain", "html", "markdown"]]], outputKeys: ["messageId"] },
|
|
8723
9047
|
"resizeVideo": { fields: [["videoUrl", "string"], ["mode", ["fit", "exact"]]], outputKeys: ["videoUrl"] },
|
|
9048
|
+
"runFfmpeg": { fields: [["inputs", "object"], ["args", "array"]], outputKeys: ["outputs"] },
|
|
8724
9049
|
"runFromConnectorRegistry": { fields: [["actionId", "string"], ["displayName", "string"], ["icon", "string"], ["configurationValues", "object"]], outputKeys: ["data"] },
|
|
8725
9050
|
"scrapeLinkedInCompany": { fields: [["url", "string"]], outputKeys: ["company"] },
|
|
8726
9051
|
"scrapeLinkedInProfile": { fields: [["url", "string"]], outputKeys: ["profile"] },
|