infinite-canvas-local-proxy 0.2.1 → 0.2.2
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/README.md +2 -3
- package/index.js +2 -15
- package/package.json +1 -1
- package/script-runner.js +184 -18
package/README.md
CHANGED
|
@@ -56,8 +56,8 @@ http://127.0.0.1:23210/https://api.openai.com/v1/models
|
|
|
56
56
|
- 默认数据目录为用户主目录下的 `.canvas-proxy`(Windows 为 `%USERPROFILE%\.canvas-proxy`),包括 `tasks.sqlite`、SQLite 日志文件和 `results/` 响应文件。备份时需停止代理后复制整个目录。
|
|
57
57
|
- API Key、请求头和请求体仅在代理内存中用于上游请求;任务请求体、脚本上下文及 API Key 不写入数据库或任务日志。若上游响应或脚本错误中发现已识别的凭证原文,该结果不会保存或会在错误中脱敏。
|
|
58
58
|
- 图片/视频脚本请求支持 JSON、文本、URL 编码和 multipart/form-data;multipart 文件在本地任务创建请求中临时 Base64 编码,代理端重建为 multipart 后发送。请求体总大小上限 80 MiB;独立二进制请求体不支持。
|
|
59
|
-
-
|
|
60
|
-
- 可通过环境变量 `CANVAS_PROXY_DATA_DIR`、`
|
|
59
|
+
- 任务数量、并发数和单个生成脚本的请求次数均不设上限;任务提交后会立即启动。生成任务仍受单任务 30 分钟等待时限、单次上游请求 10 分钟超时、80 MiB 请求体和 100 MiB 响应大小限制。成功/失败记录保留 24 小时。
|
|
60
|
+
- 可通过环境变量 `CANVAS_PROXY_DATA_DIR`、`CANVAS_PROXY_TASK_REQUEST_LIMIT_BYTES`、`CANVAS_PROXY_TASK_RESPONSE_LIMIT_BYTES`、`CANVAS_PROXY_TASK_TIMEOUT_MS` 和 `CANVAS_PROXY_TASK_RETENTION_MS` 调整目录与限制。
|
|
61
61
|
- 代理进程重启时,原来排队或运行中的任务标记为“结果未知”,不会自动再次提交上游请求,以避免重复计费。已完成结果仍可从数据库记录和响应文件恢复,直至过期。
|
|
62
62
|
- 代理只报告 queued/running/polling/完成/失败等本地状态,不提供或伪造供应商生成百分比。代理必须保持运行,才能在浏览器关闭后继续执行代理持有的请求或自定义脚本轮询。
|
|
63
63
|
|
|
@@ -106,4 +106,3 @@ Node 22.5 及以上首次加载内置 SQLite 时会打印一行 `ExperimentalWar
|
|
|
106
106
|
rmdir /s /q "%LocalAppData%\npm-cache\_npx" # Windows
|
|
107
107
|
npm cache clean --force
|
|
108
108
|
```
|
|
109
|
-
|
package/index.js
CHANGED
|
@@ -28,8 +28,6 @@ const TASK_TIMEOUT_MS = 30 * 60 * 1000;
|
|
|
28
28
|
const TASK_RETENTION_MS = 24 * 60 * 60 * 1000;
|
|
29
29
|
const TASK_REQUEST_LIMIT = 80 * 1024 * 1024;
|
|
30
30
|
const TASK_RESPONSE_LIMIT = 100 * 1024 * 1024;
|
|
31
|
-
const TASK_CONCURRENCY = 2;
|
|
32
|
-
const TASK_OUTSTANDING_LIMIT = 4;
|
|
33
31
|
|
|
34
32
|
function positiveInteger(value, fallback) {
|
|
35
33
|
const parsed = Number(value);
|
|
@@ -236,20 +234,16 @@ export function createProxyServer(options = {}) {
|
|
|
236
234
|
const taskRetentionMs = positiveInteger(options.taskRetentionMs ?? process.env.CANVAS_PROXY_TASK_RETENTION_MS, TASK_RETENTION_MS);
|
|
237
235
|
const taskRequestLimit = positiveInteger(options.taskRequestLimit ?? process.env.CANVAS_PROXY_TASK_REQUEST_LIMIT_BYTES, TASK_REQUEST_LIMIT);
|
|
238
236
|
const taskResponseLimit = positiveInteger(options.taskResponseLimit ?? process.env.CANVAS_PROXY_TASK_RESPONSE_LIMIT_BYTES, TASK_RESPONSE_LIMIT);
|
|
239
|
-
const taskConcurrency = positiveInteger(options.taskConcurrency ?? process.env.CANVAS_PROXY_TASK_CONCURRENCY, TASK_CONCURRENCY);
|
|
240
|
-
const taskOutstandingLimit = positiveInteger(options.taskOutstandingLimit ?? process.env.CANVAS_PROXY_TASK_OUTSTANDING_LIMIT, TASK_OUTSTANDING_LIMIT);
|
|
241
237
|
const queuedRequests = new Map();
|
|
242
|
-
let runningTasks = 0;
|
|
243
238
|
|
|
244
239
|
const sendTaskError = (res, status, error) => sendJson(res, status, { error });
|
|
245
240
|
|
|
246
241
|
const startQueuedTasks = () => {
|
|
247
|
-
while (
|
|
242
|
+
while (true) {
|
|
248
243
|
const next = queuedRequests.entries().next();
|
|
249
244
|
if (next.done) return;
|
|
250
245
|
const [taskId, taskRequest] = next.value;
|
|
251
246
|
queuedRequests.delete(taskId);
|
|
252
|
-
runningTasks += 1;
|
|
253
247
|
const execute = taskRequest.runner === "script" ? executeScriptTask : taskRequest.runner === "video" ? executeVideoTask : executeTask;
|
|
254
248
|
void execute(taskId, taskRequest)
|
|
255
249
|
.catch(() => {
|
|
@@ -260,10 +254,7 @@ export function createProxyServer(options = {}) {
|
|
|
260
254
|
}
|
|
261
255
|
console.error("Persistent task worker failed; it will not be retried automatically.");
|
|
262
256
|
})
|
|
263
|
-
.finally(
|
|
264
|
-
runningTasks -= 1;
|
|
265
|
-
startQueuedTasks();
|
|
266
|
-
});
|
|
257
|
+
.finally(startQueuedTasks);
|
|
267
258
|
}
|
|
268
259
|
};
|
|
269
260
|
|
|
@@ -713,10 +704,6 @@ export function createProxyServer(options = {}) {
|
|
|
713
704
|
return;
|
|
714
705
|
}
|
|
715
706
|
}
|
|
716
|
-
if (runningTasks + queuedRequests.size >= taskOutstandingLimit) {
|
|
717
|
-
sendTaskError(res, 429, "The local image task queue is full.");
|
|
718
|
-
return;
|
|
719
|
-
}
|
|
720
707
|
const taskId = randomBytes(32).toString("base64url");
|
|
721
708
|
const createdAt = Date.now();
|
|
722
709
|
const created = store.createOrGet({
|
package/package.json
CHANGED
package/script-runner.js
CHANGED
|
@@ -4,9 +4,27 @@ import vm from "node:vm";
|
|
|
4
4
|
|
|
5
5
|
const SCRIPT_TIMEOUT_MS = 30 * 60 * 1000;
|
|
6
6
|
const REQUEST_TIMEOUT_MS = 10 * 60 * 1000;
|
|
7
|
-
const MAX_SCRIPT_REQUESTS = 32;
|
|
8
7
|
const mediaUploadCache = new Map();
|
|
9
8
|
const MEDIA_UPLOAD_CACHE_LIMIT = 500;
|
|
9
|
+
const abortPromises = new WeakMap();
|
|
10
|
+
|
|
11
|
+
/** 每个任务信号只注册一次 abort 监听:轮询会反复执行脚本,逐次注册会累积成内存泄漏。 */
|
|
12
|
+
function abortPromiseFor(signal) {
|
|
13
|
+
if (!signal) return new Promise(() => {});
|
|
14
|
+
const cached = abortPromises.get(signal);
|
|
15
|
+
if (cached) return cached;
|
|
16
|
+
const promise = new Promise((_, reject) => {
|
|
17
|
+
const fail = () => reject(new DOMException("Aborted", "AbortError"));
|
|
18
|
+
if (signal.aborted) {
|
|
19
|
+
fail();
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
signal.addEventListener("abort", fail, { once: true });
|
|
23
|
+
});
|
|
24
|
+
promise.catch(() => {});
|
|
25
|
+
abortPromises.set(signal, promise);
|
|
26
|
+
return promise;
|
|
27
|
+
}
|
|
10
28
|
|
|
11
29
|
class ScriptFileReader {
|
|
12
30
|
result = null;
|
|
@@ -75,6 +93,100 @@ function makeFile(value, fallbackName) {
|
|
|
75
93
|
});
|
|
76
94
|
}
|
|
77
95
|
|
|
96
|
+
function sniffMime(bytes) {
|
|
97
|
+
const ascii = (start, end) => bytes.toString("ascii", start, end);
|
|
98
|
+
if (bytes.subarray(0, 8).equals(Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]))) return "image/png";
|
|
99
|
+
if (bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff) return "image/jpeg";
|
|
100
|
+
if (ascii(0, 4) === "RIFF" && ascii(8, 12) === "WEBP") return "image/webp";
|
|
101
|
+
if (ascii(0, 4) === "GIF8") return "image/gif";
|
|
102
|
+
if (ascii(0, 4) === "RIFF" && ascii(8, 12) === "WAVE") return "audio/wav";
|
|
103
|
+
if (ascii(0, 3) === "ID3" || (bytes[0] === 0xff && (bytes[1] & 0xe0) === 0xe0 && (bytes[1] & 0x06) !== 0)) return "audio/mpeg";
|
|
104
|
+
if (bytes.subarray(0, 4).equals(Buffer.from([0x1a, 0x45, 0xdf, 0xa3]))) return "video/webm";
|
|
105
|
+
if (ascii(0, 4) === "OggS") return "audio/ogg";
|
|
106
|
+
if (ascii(4, 8) === "ftyp") {
|
|
107
|
+
const brands = [ascii(8, 12), ...Array.from({ length: Math.floor(Math.max(0, bytes.length - 16) / 4) }, (_, index) => ascii(16 + index * 4, 20 + index * 4))];
|
|
108
|
+
if (brands.includes("qt ")) return "video/quicktime";
|
|
109
|
+
if (brands.some((brand) => ["M4A ", "M4B ", "M4P "].includes(brand))) return "audio/mp4";
|
|
110
|
+
if (brands.some((brand) => ["avif", "avis"].includes(brand))) return "image/avif";
|
|
111
|
+
if (brands.some((brand) => ["heic", "heix", "hevc", "hevx"].includes(brand))) return "image/heic";
|
|
112
|
+
if (brands.some((brand) => ["mif1", "msf1"].includes(brand))) return "image/heif";
|
|
113
|
+
return "video/mp4";
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function createScriptOssUploader(endpoint, namespace, scriptFetch, signal) {
|
|
119
|
+
const mimeExtensions = {
|
|
120
|
+
"image/avif": "avif",
|
|
121
|
+
"image/gif": "gif",
|
|
122
|
+
"image/heic": "heic",
|
|
123
|
+
"image/heif": "heif",
|
|
124
|
+
"image/jpeg": "jpg",
|
|
125
|
+
"image/png": "png",
|
|
126
|
+
"image/webp": "webp",
|
|
127
|
+
"video/mp4": "mp4",
|
|
128
|
+
"video/quicktime": "mov",
|
|
129
|
+
"video/webm": "webm",
|
|
130
|
+
"audio/mp4": "m4a",
|
|
131
|
+
"audio/mpeg": "mp3",
|
|
132
|
+
"audio/ogg": "ogg",
|
|
133
|
+
"audio/wav": "wav",
|
|
134
|
+
"audio/x-wav": "wav",
|
|
135
|
+
};
|
|
136
|
+
return async (file) => {
|
|
137
|
+
if (!(file instanceof Blob)) throw new Error("uploadToOss requires a Blob or File.");
|
|
138
|
+
if (typeof endpoint !== "string" || !endpoint.trim()) throw new Error("The application OSS presign endpoint was not provided to the local proxy.");
|
|
139
|
+
let presignEndpoint;
|
|
140
|
+
try {
|
|
141
|
+
presignEndpoint = new URL(endpoint);
|
|
142
|
+
} catch {
|
|
143
|
+
throw new Error("The application OSS presign endpoint must be an absolute URL.");
|
|
144
|
+
}
|
|
145
|
+
if (!["http:", "https:"].includes(presignEndpoint.protocol)) throw new Error("The application OSS presign endpoint must use HTTP or HTTPS.");
|
|
146
|
+
return cacheMediaUpload(file, `${namespace}:oss-mime-v2`, async () => {
|
|
147
|
+
const declaredType = file.type.split(";", 1)[0].trim().toLowerCase();
|
|
148
|
+
const contentType = declaredType && declaredType !== "application/octet-stream" ? declaredType : sniffMime(Buffer.from(await file.slice(0, 32).arrayBuffer()));
|
|
149
|
+
if (!contentType) throw new Error("Could not identify the reference media type from its content.");
|
|
150
|
+
const extension = mimeExtensions[contentType];
|
|
151
|
+
if (!extension) throw new Error(`OSS upload does not support MIME type "${contentType}".`);
|
|
152
|
+
const originalName = typeof file.name === "string" ? file.name : "reference";
|
|
153
|
+
const basename = originalName.split(/[\\/]/).at(-1)?.replace(/\.[^.]*$/, "") || "reference";
|
|
154
|
+
const presignResponse = await scriptFetch(endpoint, {
|
|
155
|
+
method: "POST",
|
|
156
|
+
headers: { "Content-Type": "application/json" },
|
|
157
|
+
body: JSON.stringify({ filename: `${basename}.${extension}`, contentType }),
|
|
158
|
+
signal,
|
|
159
|
+
});
|
|
160
|
+
const presignText = await presignResponse.text();
|
|
161
|
+
let presign;
|
|
162
|
+
try {
|
|
163
|
+
presign = presignText ? JSON.parse(presignText) : {};
|
|
164
|
+
} catch {
|
|
165
|
+
throw new Error(`OSS presign returned non-JSON (HTTP ${presignResponse.status}).`);
|
|
166
|
+
}
|
|
167
|
+
if (!presignResponse.ok) {
|
|
168
|
+
const detail = typeof presign.error === "string" ? presign.error : typeof presign.message === "string" ? presign.message : "";
|
|
169
|
+
throw new Error(detail || `Could not get OSS upload URL (HTTP ${presignResponse.status}).`);
|
|
170
|
+
}
|
|
171
|
+
if (typeof presign.uploadUrl !== "string" || typeof presign.publicUrl !== "string") {
|
|
172
|
+
throw new Error("OSS presign response must include uploadUrl and publicUrl.");
|
|
173
|
+
}
|
|
174
|
+
if (new URL(presign.uploadUrl).protocol !== "https:" || new URL(presign.publicUrl).protocol !== "https:") {
|
|
175
|
+
throw new Error("OSS upload and public media URLs must use HTTPS.");
|
|
176
|
+
}
|
|
177
|
+
const uploadContentType = typeof presign.contentType === "string" && presign.contentType ? presign.contentType : contentType;
|
|
178
|
+
const uploaded = await scriptFetch(presign.uploadUrl, {
|
|
179
|
+
method: "PUT",
|
|
180
|
+
headers: { "Content-Type": uploadContentType },
|
|
181
|
+
body: file,
|
|
182
|
+
signal,
|
|
183
|
+
});
|
|
184
|
+
if (!uploaded.ok) throw new Error(`OSS upload failed: HTTP ${uploaded.status}.`);
|
|
185
|
+
return presign.publicUrl;
|
|
186
|
+
});
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
78
190
|
function bodySize(data) {
|
|
79
191
|
if (data === undefined || data === null) return 0;
|
|
80
192
|
if (typeof data === "string") return Buffer.byteLength(data);
|
|
@@ -90,6 +202,29 @@ function bodySize(data) {
|
|
|
90
202
|
return Buffer.byteLength(JSON.stringify(data));
|
|
91
203
|
}
|
|
92
204
|
|
|
205
|
+
function composeAbortSignal(signals, timeoutMs) {
|
|
206
|
+
const controller = new AbortController();
|
|
207
|
+
const listeners = [];
|
|
208
|
+
for (const signal of signals) {
|
|
209
|
+
if (!signal) continue;
|
|
210
|
+
if (signal.aborted) {
|
|
211
|
+
controller.abort(signal.reason);
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
const abort = () => controller.abort(signal.reason);
|
|
215
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
216
|
+
listeners.push([signal, abort]);
|
|
217
|
+
}
|
|
218
|
+
const timeout = setTimeout(() => controller.abort(new DOMException("The operation timed out.", "TimeoutError")), timeoutMs);
|
|
219
|
+
return {
|
|
220
|
+
signal: controller.signal,
|
|
221
|
+
dispose() {
|
|
222
|
+
clearTimeout(timeout);
|
|
223
|
+
for (const [signal, abort] of listeners) signal.removeEventListener("abort", abort);
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
93
228
|
async function responseData(response, responseType, limitBytes) {
|
|
94
229
|
const contentType = response.headers.get("content-type") || "";
|
|
95
230
|
const bytes = Buffer.from(await response.arrayBuffer());
|
|
@@ -108,11 +243,6 @@ async function responseData(response, responseType, limitBytes) {
|
|
|
108
243
|
}
|
|
109
244
|
|
|
110
245
|
function createRequest({ signal, requestLimitBytes, responseLimitBytes, credentials, baseUrl }) {
|
|
111
|
-
let requestCount = 0;
|
|
112
|
-
const countRequest = () => {
|
|
113
|
-
requestCount += 1;
|
|
114
|
-
if (requestCount > MAX_SCRIPT_REQUESTS) throw new Error("A generation script exceeded the request count limit.");
|
|
115
|
-
};
|
|
116
246
|
const recordCredentials = (target, headers) => {
|
|
117
247
|
for (const [name, value] of headers.entries()) {
|
|
118
248
|
if (/authorization|api.?key/i.test(name)) credentials.add(value.replace(/^bearer\s+/i, ""));
|
|
@@ -124,7 +254,6 @@ function createRequest({ signal, requestLimitBytes, responseLimitBytes, credenti
|
|
|
124
254
|
}
|
|
125
255
|
};
|
|
126
256
|
const request = async (options) => {
|
|
127
|
-
countRequest();
|
|
128
257
|
if (!isRecord(options) || typeof options.url !== "string") throw new Error("A generation script request needs a URL.");
|
|
129
258
|
const requestBase = String(baseUrl || "").trim();
|
|
130
259
|
const target = /^https?:\/\//i.test(options.url) ? new URL(options.url) : new URL(options.url, `${requestBase.replace(/\/+$/, "")}/`);
|
|
@@ -180,7 +309,6 @@ function createRequest({ signal, requestLimitBytes, responseLimitBytes, credenti
|
|
|
180
309
|
}
|
|
181
310
|
};
|
|
182
311
|
const scriptFetch = async (input, init = {}) => {
|
|
183
|
-
countRequest();
|
|
184
312
|
const requestInput = input instanceof Request ? input : null;
|
|
185
313
|
const target = new URL(requestInput ? requestInput.url : String(input));
|
|
186
314
|
if (!["http:", "https:", "data:"].includes(target.protocol)) throw new Error("Generation scripts may only fetch HTTP, HTTPS or data URLs.");
|
|
@@ -190,17 +318,53 @@ function createRequest({ signal, requestLimitBytes, responseLimitBytes, credenti
|
|
|
190
318
|
if (init.body instanceof FormData && /^multipart\/form-data\s*$/i.test(headers.get("content-type") || "")) headers.delete("content-type");
|
|
191
319
|
recordCredentials(target, headers);
|
|
192
320
|
if (bodySize(init.body) > requestLimitBytes) throw new Error("Generation script request exceeds the configured size limit.");
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
321
|
+
const composed = composeAbortSignal([signal, init.signal || requestInput?.signal], REQUEST_TIMEOUT_MS);
|
|
322
|
+
let response;
|
|
323
|
+
try {
|
|
324
|
+
response = await fetch(input, { ...init, headers, signal: composed.signal });
|
|
325
|
+
} catch (error) {
|
|
326
|
+
composed.dispose();
|
|
327
|
+
throw error;
|
|
328
|
+
}
|
|
329
|
+
if (!response.body) {
|
|
330
|
+
composed.dispose();
|
|
331
|
+
return response;
|
|
332
|
+
}
|
|
333
|
+
const reader = response.body.getReader();
|
|
196
334
|
let received = 0;
|
|
197
|
-
const limited =
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
335
|
+
const limited = new ReadableStream({
|
|
336
|
+
async pull(controller) {
|
|
337
|
+
try {
|
|
338
|
+
const { done, value } = await reader.read();
|
|
339
|
+
if (done) {
|
|
340
|
+
controller.close();
|
|
341
|
+
composed.dispose();
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
received += value.byteLength;
|
|
345
|
+
if (received > responseLimitBytes) {
|
|
346
|
+
try {
|
|
347
|
+
await reader.cancel();
|
|
348
|
+
} finally {
|
|
349
|
+
composed.dispose();
|
|
350
|
+
}
|
|
351
|
+
controller.error(new Error("Script response exceeds the configured size limit."));
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
controller.enqueue(value);
|
|
355
|
+
} catch (error) {
|
|
356
|
+
controller.error(error);
|
|
357
|
+
composed.dispose();
|
|
358
|
+
}
|
|
202
359
|
},
|
|
203
|
-
|
|
360
|
+
async cancel(reason) {
|
|
361
|
+
try {
|
|
362
|
+
await reader.cancel(reason);
|
|
363
|
+
} finally {
|
|
364
|
+
composed.dispose();
|
|
365
|
+
}
|
|
366
|
+
},
|
|
367
|
+
});
|
|
204
368
|
const wrapped = new Response(limited, { status: response.status, statusText: response.statusText, headers: response.headers });
|
|
205
369
|
Object.defineProperties(wrapped, {
|
|
206
370
|
url: { value: response.url },
|
|
@@ -282,6 +446,7 @@ async function runScript(script, input, request, scriptFetch, signal, timeoutMs)
|
|
|
282
446
|
sleep,
|
|
283
447
|
signal,
|
|
284
448
|
cacheMediaUpload,
|
|
449
|
+
uploadToOss: createScriptOssUploader(input.ossPresignUrl, input.baseUrl, scriptFetch, signal),
|
|
285
450
|
console: Object.freeze({ log() {}, warn() {}, error() {} }),
|
|
286
451
|
});
|
|
287
452
|
const context = vm.createContext(sandbox, { codeGeneration: { strings: false, wasm: false } });
|
|
@@ -291,7 +456,7 @@ async function runScript(script, input, request, scriptFetch, signal, timeoutMs)
|
|
|
291
456
|
const timedOut = new Promise((_, reject) => {
|
|
292
457
|
timeout = setTimeout(() => reject(new Error("Generation script timed out.")), timeoutMs);
|
|
293
458
|
});
|
|
294
|
-
const aborted =
|
|
459
|
+
const aborted = abortPromiseFor(signal);
|
|
295
460
|
try {
|
|
296
461
|
return await Promise.race([result, timedOut, aborted]);
|
|
297
462
|
} finally {
|
|
@@ -344,6 +509,7 @@ export async function runGenerationScriptTask(task, options) {
|
|
|
344
509
|
apiKey: String(task.context.apiKey || ""),
|
|
345
510
|
systemPrompt: String(task.context.systemPrompt || ""),
|
|
346
511
|
reasoningEffort: String(task.context.reasoningEffort || ""),
|
|
512
|
+
ossPresignUrl: task.context.ossPresignUrl,
|
|
347
513
|
};
|
|
348
514
|
let params = isRecord(task.context.params) ? { ...task.context.params, taskAction: "create" } : { taskAction: "create" };
|
|
349
515
|
let result = await runScript(task.script, { ...common, params }, request, scriptFetch, options.signal, timeoutMs);
|