cascade-ai 0.51.0 → 0.52.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/cli.cjs +155 -16
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +155 -16
- package/dist/cli.js.map +1 -1
- package/dist/desktop-core.cjs +155 -16
- package/dist/index.cjs +155 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +41 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.js +155 -16
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -202,6 +202,12 @@ interface ToolExecuteOptions {
|
|
|
202
202
|
* paying it out and discarding the result.
|
|
203
203
|
*/
|
|
204
204
|
signal?: AbortSignal;
|
|
205
|
+
/**
|
|
206
|
+
* Progress notes from a long-running tool. Video generation polls for minutes;
|
|
207
|
+
* without a channel back, a working render is indistinguishable from a hang —
|
|
208
|
+
* which is the most likely reason someone kills one.
|
|
209
|
+
*/
|
|
210
|
+
onProgress?: (note: string) => void;
|
|
205
211
|
saveSnapshot?: (filePath: string, content: string) => Promise<void>;
|
|
206
212
|
sendPeerSync?: (to: string, syncType: PeerSyncType, content: string | Record<string, unknown>) => void;
|
|
207
213
|
getPeerMessages?: () => Array<{
|
|
@@ -592,6 +598,12 @@ interface ToolsConfig {
|
|
|
592
598
|
* behavior). An empty array registers no tools at all.
|
|
593
599
|
*/
|
|
594
600
|
enabledTools?: string[];
|
|
601
|
+
/**
|
|
602
|
+
* Tools to remove even when they would otherwise be registered. Reaches
|
|
603
|
+
* tools registered outside `enabledTools` (MCP, media generation), which is
|
|
604
|
+
* the only way to switch those off.
|
|
605
|
+
*/
|
|
606
|
+
disabledTools?: string[];
|
|
595
607
|
}
|
|
596
608
|
interface HooksConfig {
|
|
597
609
|
preToolUse?: HookDefinition[];
|
|
@@ -2182,6 +2194,24 @@ declare class T1Administrator extends BaseTier {
|
|
|
2182
2194
|
private evaluatePermissionAtT1;
|
|
2183
2195
|
}
|
|
2184
2196
|
|
|
2197
|
+
interface GeneratedAsset {
|
|
2198
|
+
/** Raw bytes of the asset. */
|
|
2199
|
+
data: Buffer;
|
|
2200
|
+
mimeType: string;
|
|
2201
|
+
/** Suggested filename, extension included. */
|
|
2202
|
+
filename: string;
|
|
2203
|
+
modelId: string;
|
|
2204
|
+
provider: string;
|
|
2205
|
+
/** Provider-side revision of the prompt, when it reports one (DALL·E does). */
|
|
2206
|
+
revisedPrompt?: string;
|
|
2207
|
+
}
|
|
2208
|
+
|
|
2209
|
+
/**
|
|
2210
|
+
* Where finished bytes go. Returns a short human-readable locator (a path, a
|
|
2211
|
+
* file id, a URL) that the tool reports back to the model.
|
|
2212
|
+
*/
|
|
2213
|
+
type AssetSink = (asset: GeneratedAsset) => Promise<string>;
|
|
2214
|
+
|
|
2185
2215
|
/** One entry in the per-run orchestration decision trail (see /why). */
|
|
2186
2216
|
interface DecisionLogEntry {
|
|
2187
2217
|
at: string;
|
|
@@ -2192,6 +2222,7 @@ declare class Cascade extends EventEmitter {
|
|
|
2192
2222
|
private router;
|
|
2193
2223
|
private toolRegistry;
|
|
2194
2224
|
private multimodal;
|
|
2225
|
+
private mediaSink?;
|
|
2195
2226
|
private mcpClient;
|
|
2196
2227
|
private codeIndex?;
|
|
2197
2228
|
private config;
|
|
@@ -2287,6 +2318,16 @@ declare class Cascade extends EventEmitter {
|
|
|
2287
2318
|
* server stores a file row) constructs the tools itself via buildMediaTools
|
|
2288
2319
|
* with its own sink.
|
|
2289
2320
|
*/
|
|
2321
|
+
/**
|
|
2322
|
+
* Replace where generated media is written.
|
|
2323
|
+
*
|
|
2324
|
+
* The default sink writes into the workspace, which is what a desktop or CLI
|
|
2325
|
+
* run wants. A host with different storage — the cloud server keeps a file
|
|
2326
|
+
* row plus bytes under the tenant's directory — supplies its own here, and
|
|
2327
|
+
* the media tools are rebuilt around it. Re-registering by name replaces the
|
|
2328
|
+
* existing tools rather than duplicating them.
|
|
2329
|
+
*/
|
|
2330
|
+
setMediaSink(sink: AssetSink): void;
|
|
2290
2331
|
private registerMediaTools;
|
|
2291
2332
|
/**
|
|
2292
2333
|
* Supply per-model thumbs-up/down counts so Auto routing can weigh how models
|
package/dist/index.d.ts
CHANGED
|
@@ -202,6 +202,12 @@ interface ToolExecuteOptions {
|
|
|
202
202
|
* paying it out and discarding the result.
|
|
203
203
|
*/
|
|
204
204
|
signal?: AbortSignal;
|
|
205
|
+
/**
|
|
206
|
+
* Progress notes from a long-running tool. Video generation polls for minutes;
|
|
207
|
+
* without a channel back, a working render is indistinguishable from a hang —
|
|
208
|
+
* which is the most likely reason someone kills one.
|
|
209
|
+
*/
|
|
210
|
+
onProgress?: (note: string) => void;
|
|
205
211
|
saveSnapshot?: (filePath: string, content: string) => Promise<void>;
|
|
206
212
|
sendPeerSync?: (to: string, syncType: PeerSyncType, content: string | Record<string, unknown>) => void;
|
|
207
213
|
getPeerMessages?: () => Array<{
|
|
@@ -592,6 +598,12 @@ interface ToolsConfig {
|
|
|
592
598
|
* behavior). An empty array registers no tools at all.
|
|
593
599
|
*/
|
|
594
600
|
enabledTools?: string[];
|
|
601
|
+
/**
|
|
602
|
+
* Tools to remove even when they would otherwise be registered. Reaches
|
|
603
|
+
* tools registered outside `enabledTools` (MCP, media generation), which is
|
|
604
|
+
* the only way to switch those off.
|
|
605
|
+
*/
|
|
606
|
+
disabledTools?: string[];
|
|
595
607
|
}
|
|
596
608
|
interface HooksConfig {
|
|
597
609
|
preToolUse?: HookDefinition[];
|
|
@@ -2182,6 +2194,24 @@ declare class T1Administrator extends BaseTier {
|
|
|
2182
2194
|
private evaluatePermissionAtT1;
|
|
2183
2195
|
}
|
|
2184
2196
|
|
|
2197
|
+
interface GeneratedAsset {
|
|
2198
|
+
/** Raw bytes of the asset. */
|
|
2199
|
+
data: Buffer;
|
|
2200
|
+
mimeType: string;
|
|
2201
|
+
/** Suggested filename, extension included. */
|
|
2202
|
+
filename: string;
|
|
2203
|
+
modelId: string;
|
|
2204
|
+
provider: string;
|
|
2205
|
+
/** Provider-side revision of the prompt, when it reports one (DALL·E does). */
|
|
2206
|
+
revisedPrompt?: string;
|
|
2207
|
+
}
|
|
2208
|
+
|
|
2209
|
+
/**
|
|
2210
|
+
* Where finished bytes go. Returns a short human-readable locator (a path, a
|
|
2211
|
+
* file id, a URL) that the tool reports back to the model.
|
|
2212
|
+
*/
|
|
2213
|
+
type AssetSink = (asset: GeneratedAsset) => Promise<string>;
|
|
2214
|
+
|
|
2185
2215
|
/** One entry in the per-run orchestration decision trail (see /why). */
|
|
2186
2216
|
interface DecisionLogEntry {
|
|
2187
2217
|
at: string;
|
|
@@ -2192,6 +2222,7 @@ declare class Cascade extends EventEmitter {
|
|
|
2192
2222
|
private router;
|
|
2193
2223
|
private toolRegistry;
|
|
2194
2224
|
private multimodal;
|
|
2225
|
+
private mediaSink?;
|
|
2195
2226
|
private mcpClient;
|
|
2196
2227
|
private codeIndex?;
|
|
2197
2228
|
private config;
|
|
@@ -2287,6 +2318,16 @@ declare class Cascade extends EventEmitter {
|
|
|
2287
2318
|
* server stores a file row) constructs the tools itself via buildMediaTools
|
|
2288
2319
|
* with its own sink.
|
|
2289
2320
|
*/
|
|
2321
|
+
/**
|
|
2322
|
+
* Replace where generated media is written.
|
|
2323
|
+
*
|
|
2324
|
+
* The default sink writes into the workspace, which is what a desktop or CLI
|
|
2325
|
+
* run wants. A host with different storage — the cloud server keeps a file
|
|
2326
|
+
* row plus bytes under the tenant's directory — supplies its own here, and
|
|
2327
|
+
* the media tools are rebuilt around it. Re-registering by name replaces the
|
|
2328
|
+
* existing tools rather than duplicating them.
|
|
2329
|
+
*/
|
|
2330
|
+
setMediaSink(sink: AssetSink): void;
|
|
2290
2331
|
private registerMediaTools;
|
|
2291
2332
|
/**
|
|
2292
2333
|
* Supply per-model thumbs-up/down counts so Auto routing can weigh how models
|
package/dist/index.js
CHANGED
|
@@ -1845,7 +1845,7 @@ function reconcilePrice(model, live) {
|
|
|
1845
1845
|
}
|
|
1846
1846
|
|
|
1847
1847
|
// src/constants.ts
|
|
1848
|
-
var CASCADE_VERSION = "0.
|
|
1848
|
+
var CASCADE_VERSION = "0.52.0";
|
|
1849
1849
|
var CASCADE_CONFIG_DIR = ".cascade";
|
|
1850
1850
|
var CASCADE_MD_FILE = "CASCADE.md";
|
|
1851
1851
|
var CASCADE_IGNORE_FILE = ".cascadeignore";
|
|
@@ -8848,17 +8848,13 @@ var CAPABILITIES = [
|
|
|
8848
8848
|
},
|
|
8849
8849
|
// ── Video ──
|
|
8850
8850
|
{
|
|
8851
|
-
//
|
|
8852
|
-
//
|
|
8853
|
-
//
|
|
8854
|
-
// signed URL. That is a different execution shape from every other entry
|
|
8855
|
-
// here and is not wired up — claiming otherwise would produce a tool that
|
|
8856
|
-
// fails at call time instead of an honest "not yet".
|
|
8851
|
+
// Long-running: submit returns an operation name, the video lands minutes
|
|
8852
|
+
// later, and the bytes come from a short-lived signed URL. generate.ts owns
|
|
8853
|
+
// that polling loop; the registry only records the shape via `api`.
|
|
8857
8854
|
modality: "video",
|
|
8858
8855
|
provider: "gemini",
|
|
8859
8856
|
modelId: "veo-3.1-generate-001",
|
|
8860
|
-
api: "gemini-predict"
|
|
8861
|
-
unsupported: "Veo runs as a long-running operation (submit \u2192 poll \u2192 fetch). Not wired up yet."
|
|
8857
|
+
api: "gemini-predict-lro"
|
|
8862
8858
|
}
|
|
8863
8859
|
];
|
|
8864
8860
|
function capabilityCost(cap) {
|
|
@@ -9171,6 +9167,75 @@ async function generateSpeech(cap, cfg, req, signal) {
|
|
|
9171
9167
|
};
|
|
9172
9168
|
}, cap.modelId);
|
|
9173
9169
|
}
|
|
9170
|
+
var VIDEO_POLL_INTERVAL_MS = 1e4;
|
|
9171
|
+
var VIDEO_TIMEOUT_MS = 8 * 6e4;
|
|
9172
|
+
function sleep(ms, signal) {
|
|
9173
|
+
return new Promise((resolve2, reject) => {
|
|
9174
|
+
if (signal?.aborted) return reject(new Error("Cancelled."));
|
|
9175
|
+
const timer = setTimeout(() => {
|
|
9176
|
+
signal?.removeEventListener("abort", onAbort);
|
|
9177
|
+
resolve2();
|
|
9178
|
+
}, ms);
|
|
9179
|
+
function onAbort() {
|
|
9180
|
+
clearTimeout(timer);
|
|
9181
|
+
reject(new Error("Cancelled."));
|
|
9182
|
+
}
|
|
9183
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
9184
|
+
});
|
|
9185
|
+
}
|
|
9186
|
+
async function generateVideo(cap, cfg, req, signal, onProgress) {
|
|
9187
|
+
return callProvider(async () => {
|
|
9188
|
+
if (cap.api !== "gemini-predict-lro") throw new Error(`${cap.modelId} is not a video generator.`);
|
|
9189
|
+
const base = baseUrlFor(cfg, "https://generativelanguage.googleapis.com/v1beta");
|
|
9190
|
+
const key = cfg.apiKey ?? "";
|
|
9191
|
+
const headers = { "x-goog-api-key": key };
|
|
9192
|
+
const submit = await postJson(
|
|
9193
|
+
`${base}/models/${encodeURIComponent(cap.modelId)}:predictLongRunning`,
|
|
9194
|
+
{
|
|
9195
|
+
instances: [{ prompt: req.prompt }],
|
|
9196
|
+
parameters: {
|
|
9197
|
+
aspectRatio: req.aspectRatio ?? "16:9",
|
|
9198
|
+
...req.seconds ? { durationSeconds: req.seconds } : {}
|
|
9199
|
+
}
|
|
9200
|
+
},
|
|
9201
|
+
headers,
|
|
9202
|
+
signal
|
|
9203
|
+
);
|
|
9204
|
+
const started = await submit.json();
|
|
9205
|
+
if (!started.name) throw new Error("Video request was accepted but returned no operation to poll.");
|
|
9206
|
+
onProgress?.(`Video generation started (${cap.modelId}). This usually takes 1-3 minutes.`);
|
|
9207
|
+
const deadline = Date.now() + VIDEO_TIMEOUT_MS;
|
|
9208
|
+
let operation = {};
|
|
9209
|
+
while (Date.now() < deadline) {
|
|
9210
|
+
await sleep(VIDEO_POLL_INTERVAL_MS, signal);
|
|
9211
|
+
const poll = await fetch(`${base}/${started.name}`, { headers, signal });
|
|
9212
|
+
if (!poll.ok) {
|
|
9213
|
+
const text = await poll.text().catch(() => "");
|
|
9214
|
+
throw Object.assign(new Error(text || poll.statusText), { status: poll.status });
|
|
9215
|
+
}
|
|
9216
|
+
operation = await poll.json();
|
|
9217
|
+
if (operation.done) break;
|
|
9218
|
+
onProgress?.("Still rendering\u2026");
|
|
9219
|
+
}
|
|
9220
|
+
if (!operation.done) {
|
|
9221
|
+
throw new Error(
|
|
9222
|
+
`Video was still rendering after ${Math.round(VIDEO_TIMEOUT_MS / 6e4)} minutes. It may still complete on the provider side, but Cascade stopped waiting.`
|
|
9223
|
+
);
|
|
9224
|
+
}
|
|
9225
|
+
if (operation.error?.message) throw new Error(operation.error.message);
|
|
9226
|
+
const uri = operation.response?.generateVideoResponse?.generatedSamples?.[0]?.video?.uri;
|
|
9227
|
+
if (!uri) throw new Error("Video finished but the response contained no video.");
|
|
9228
|
+
const media = await fetch(uri, { headers, signal });
|
|
9229
|
+
if (!media.ok) throw new Error(`Video finished but could not be downloaded (HTTP ${media.status}).`);
|
|
9230
|
+
return {
|
|
9231
|
+
data: Buffer.from(await media.arrayBuffer()),
|
|
9232
|
+
mimeType: "video/mp4",
|
|
9233
|
+
filename: stamp("mp4"),
|
|
9234
|
+
modelId: cap.modelId,
|
|
9235
|
+
provider: cap.provider
|
|
9236
|
+
};
|
|
9237
|
+
}, cap.modelId);
|
|
9238
|
+
}
|
|
9174
9239
|
async function transcribeAudio(cap, cfg, req, signal) {
|
|
9175
9240
|
return callProvider(async () => {
|
|
9176
9241
|
if (cap.api !== "openai-transcriptions") throw new Error(`${cap.modelId} is not a transcription model.`);
|
|
@@ -9295,6 +9360,58 @@ var GenerateSpeechTool = class extends BaseTool {
|
|
|
9295
9360
|
Model: ${asset.modelId} \u2014 ${r.selected.reason}`;
|
|
9296
9361
|
}
|
|
9297
9362
|
};
|
|
9363
|
+
var DEFAULT_VIDEO_SECONDS = 5;
|
|
9364
|
+
var MAX_VIDEO_SECONDS = 8;
|
|
9365
|
+
var GenerateVideoTool = class extends BaseTool {
|
|
9366
|
+
constructor(deps) {
|
|
9367
|
+
super();
|
|
9368
|
+
this.deps = deps;
|
|
9369
|
+
}
|
|
9370
|
+
deps;
|
|
9371
|
+
name = "generate_video";
|
|
9372
|
+
// The price is in the description on purpose. Video is ~1000x the cost of an
|
|
9373
|
+
// image per call, it is billed per SECOND, and the model choosing the
|
|
9374
|
+
// duration is the one deciding the bill — so the number has to be where the
|
|
9375
|
+
// model reads before calling, not only in the result afterwards.
|
|
9376
|
+
description = "Generate a short video clip from a text description. EXPENSIVE and slow: billed per second of output (about $0.40/second, so a 5-second clip costs roughly $2) and takes 1-3 minutes. Only use when the user explicitly asks for a video, and prefer the shortest duration that satisfies the request.";
|
|
9377
|
+
inputSchema = {
|
|
9378
|
+
type: "object",
|
|
9379
|
+
properties: {
|
|
9380
|
+
prompt: { type: "string", description: "What the video should show: subject, action, camera movement, style." },
|
|
9381
|
+
seconds: { type: "number", description: `Clip length, 1-${MAX_VIDEO_SECONDS} seconds (default ${DEFAULT_VIDEO_SECONDS}). Billed per second \u2014 keep it short.` },
|
|
9382
|
+
aspectRatio: { type: "string", description: 'Frame shape: "16:9" (default) or "9:16" for vertical.' },
|
|
9383
|
+
model: { type: "string", description: "Optional: force a specific video model." }
|
|
9384
|
+
},
|
|
9385
|
+
required: ["prompt"]
|
|
9386
|
+
};
|
|
9387
|
+
async execute(input, options) {
|
|
9388
|
+
const prompt = String(input["prompt"] ?? "").trim();
|
|
9389
|
+
if (!prompt) return 'Provide a "prompt" describing the video.';
|
|
9390
|
+
const r = resolve(this.deps, "video", input["model"] ? String(input["model"]) : void 0);
|
|
9391
|
+
if (!r.ok) return r.error;
|
|
9392
|
+
const asked = typeof input["seconds"] === "number" ? input["seconds"] : DEFAULT_VIDEO_SECONDS;
|
|
9393
|
+
const seconds = Math.max(1, Math.min(MAX_VIDEO_SECONDS, Math.round(asked)));
|
|
9394
|
+
const asset = await generateVideo(
|
|
9395
|
+
r.selected.capability,
|
|
9396
|
+
r.cfg,
|
|
9397
|
+
{
|
|
9398
|
+
prompt,
|
|
9399
|
+
seconds,
|
|
9400
|
+
...input["aspectRatio"] ? { aspectRatio: String(input["aspectRatio"]) } : {}
|
|
9401
|
+
},
|
|
9402
|
+
options.signal,
|
|
9403
|
+
// Progress goes to the run log so a two-minute wait doesn't look like a
|
|
9404
|
+
// hang — the single most likely reason someone kills a working render.
|
|
9405
|
+
(note) => options.onProgress?.(note)
|
|
9406
|
+
);
|
|
9407
|
+
const location = await this.deps.sink(asset);
|
|
9408
|
+
const unit = r.selected.cost;
|
|
9409
|
+
const estimate = unit ? ` Estimated cost: $${(unit.amount * seconds).toFixed(2)} (${seconds}s x ${unit.label}).` : "";
|
|
9410
|
+
const clamped = seconds !== Math.round(asked) ? ` Duration was clamped to ${seconds}s (max ${MAX_VIDEO_SECONDS}).` : "";
|
|
9411
|
+
return `Video generated and saved to ${location}.
|
|
9412
|
+
Model: ${asset.modelId}.${estimate}${clamped}`;
|
|
9413
|
+
}
|
|
9414
|
+
};
|
|
9298
9415
|
var TranscribeAudioTool = class extends BaseTool {
|
|
9299
9416
|
constructor(deps, readFile) {
|
|
9300
9417
|
super();
|
|
@@ -9345,6 +9462,7 @@ function buildMediaTools(deps, readFile) {
|
|
|
9345
9462
|
const modalities = new Set(deps.registry.availableModalities());
|
|
9346
9463
|
if (modalities.has("image")) tools.push(new GenerateImageTool(deps));
|
|
9347
9464
|
if (modalities.has("speech")) tools.push(new GenerateSpeechTool(deps));
|
|
9465
|
+
if (modalities.has("video")) tools.push(new GenerateVideoTool(deps));
|
|
9348
9466
|
if (modalities.has("transcription") && readFile) tools.push(new TranscribeAudioTool(deps, readFile));
|
|
9349
9467
|
return tools;
|
|
9350
9468
|
}
|
|
@@ -11673,7 +11791,14 @@ var ToolsConfigSchema = z.object({
|
|
|
11673
11791
|
* built-in tool (shell/file/git/…) from existing at all, rather than just
|
|
11674
11792
|
* gating it behind approval. Omitted = full default set (unchanged).
|
|
11675
11793
|
*/
|
|
11676
|
-
enabledTools: z.array(z.string()).optional()
|
|
11794
|
+
enabledTools: z.array(z.string()).optional(),
|
|
11795
|
+
/**
|
|
11796
|
+
* Tools to remove even when they would otherwise be registered. Unlike
|
|
11797
|
+
* `enabledTools` (an allowlist over the built-in set), this also reaches
|
|
11798
|
+
* tools registered outside it — MCP tools and media generation — which is
|
|
11799
|
+
* the only way to turn those off.
|
|
11800
|
+
*/
|
|
11801
|
+
disabledTools: z.array(z.string()).optional()
|
|
11677
11802
|
});
|
|
11678
11803
|
var HookDefinitionSchema = z.object({
|
|
11679
11804
|
name: z.string().optional(),
|
|
@@ -14212,6 +14337,7 @@ var Cascade = class _Cascade extends EventEmitter {
|
|
|
14212
14337
|
router;
|
|
14213
14338
|
toolRegistry;
|
|
14214
14339
|
multimodal;
|
|
14340
|
+
mediaSink;
|
|
14215
14341
|
mcpClient;
|
|
14216
14342
|
codeIndex;
|
|
14217
14343
|
config;
|
|
@@ -14531,14 +14657,27 @@ ${input}` }],
|
|
|
14531
14657
|
* server stores a file row) constructs the tools itself via buildMediaTools
|
|
14532
14658
|
* with its own sink.
|
|
14533
14659
|
*/
|
|
14660
|
+
/**
|
|
14661
|
+
* Replace where generated media is written.
|
|
14662
|
+
*
|
|
14663
|
+
* The default sink writes into the workspace, which is what a desktop or CLI
|
|
14664
|
+
* run wants. A host with different storage — the cloud server keeps a file
|
|
14665
|
+
* row plus bytes under the tenant's directory — supplies its own here, and
|
|
14666
|
+
* the media tools are rebuilt around it. Re-registering by name replaces the
|
|
14667
|
+
* existing tools rather than duplicating them.
|
|
14668
|
+
*/
|
|
14669
|
+
setMediaSink(sink) {
|
|
14670
|
+
this.mediaSink = sink;
|
|
14671
|
+
this.registerMediaTools(this.workspacePath);
|
|
14672
|
+
}
|
|
14534
14673
|
registerMediaTools(workspacePath) {
|
|
14535
|
-
const
|
|
14536
|
-
const permitted = (name) => !
|
|
14674
|
+
const denied = new Set(this.config.tools?.disabledTools ?? []);
|
|
14675
|
+
const permitted = (name) => !denied.has(name);
|
|
14537
14676
|
const tools = buildMediaTools(
|
|
14538
14677
|
{
|
|
14539
14678
|
registry: this.multimodal,
|
|
14540
14679
|
lookupProvider: (provider) => (this.config.providers ?? []).find((p) => p.type === provider),
|
|
14541
|
-
sink: async (asset) => {
|
|
14680
|
+
sink: this.mediaSink ?? (async (asset) => {
|
|
14542
14681
|
const fsp = await import('fs/promises');
|
|
14543
14682
|
const nodePath = await import('path');
|
|
14544
14683
|
const dir = nodePath.join(workspacePath, "generated");
|
|
@@ -14546,7 +14685,7 @@ ${input}` }],
|
|
|
14546
14685
|
const dest = nodePath.join(dir, asset.filename);
|
|
14547
14686
|
await fsp.writeFile(dest, asset.data);
|
|
14548
14687
|
return nodePath.relative(workspacePath, dest);
|
|
14549
|
-
}
|
|
14688
|
+
})
|
|
14550
14689
|
},
|
|
14551
14690
|
async (p) => {
|
|
14552
14691
|
const fsp = await import('fs/promises');
|
|
@@ -18560,7 +18699,7 @@ var CloudClient = class _CloudClient {
|
|
|
18560
18699
|
const deadline = Date.now() + start.expiresIn * 1e3;
|
|
18561
18700
|
while (Date.now() < deadline) {
|
|
18562
18701
|
if (opts.signal?.aborted) throw new Error("Sign-in cancelled.");
|
|
18563
|
-
await
|
|
18702
|
+
await sleep2(interval * 1e3, opts.signal);
|
|
18564
18703
|
const r = await this.pollDevice(start.deviceCode);
|
|
18565
18704
|
if (r === "slow_down") {
|
|
18566
18705
|
interval += 5;
|
|
@@ -18771,7 +18910,7 @@ var CloudClient = class _CloudClient {
|
|
|
18771
18910
|
this.store.clear();
|
|
18772
18911
|
}
|
|
18773
18912
|
};
|
|
18774
|
-
function
|
|
18913
|
+
function sleep2(ms, signal) {
|
|
18775
18914
|
return new Promise((resolve2, reject) => {
|
|
18776
18915
|
const t = setTimeout(resolve2, ms);
|
|
18777
18916
|
signal?.addEventListener("abort", () => {
|