cascade-ai 0.51.0 → 0.53.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 +195 -34
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +195 -34
- package/dist/cli.js.map +1 -1
- package/dist/desktop-core.cjs +194 -39
- package/dist/index.cjs +186 -31
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +44 -10
- package/dist/index.d.ts +44 -10
- package/dist/index.js +186 -32
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1740,7 +1740,7 @@ var CASCADE_VERSION, CASCADE_CONFIG_FILE, CASCADE_DB_FILE, CASCADE_DASHBOARD_SEC
|
|
|
1740
1740
|
var init_constants = __esm({
|
|
1741
1741
|
"src/constants.ts"() {
|
|
1742
1742
|
init_pricing();
|
|
1743
|
-
CASCADE_VERSION = "0.
|
|
1743
|
+
CASCADE_VERSION = "0.53.0";
|
|
1744
1744
|
CASCADE_CONFIG_FILE = ".cascade/config.json";
|
|
1745
1745
|
CASCADE_DB_FILE = ".cascade/memory.db";
|
|
1746
1746
|
CASCADE_DASHBOARD_SECRET_FILE = ".cascade/dashboard-secret";
|
|
@@ -2739,26 +2739,14 @@ var init_openai = __esm({
|
|
|
2739
2739
|
// src/providers/azure.ts
|
|
2740
2740
|
var azure_exports = {};
|
|
2741
2741
|
__export(azure_exports, {
|
|
2742
|
+
AZURE_BASE_MODELS: () => AZURE_BASE_MODELS,
|
|
2742
2743
|
AzureOpenAIProvider: () => AzureOpenAIProvider,
|
|
2743
2744
|
azureModelForDeployment: () => azureModelForDeployment,
|
|
2744
2745
|
inferAzureBaseModel: () => inferAzureBaseModel
|
|
2745
2746
|
});
|
|
2746
2747
|
function inferAzureBaseModel(deploymentName) {
|
|
2747
2748
|
const n = deploymentName.toLowerCase();
|
|
2748
|
-
const
|
|
2749
|
-
[/gpt-?5\.5/, "gpt-5.5"],
|
|
2750
|
-
[/gpt-?5\.4.*mini/, "gpt-5.4-mini"],
|
|
2751
|
-
[/gpt-?5\.4/, "gpt-5.4"],
|
|
2752
|
-
[/gpt-?5.*nano/, "gpt-5-nano"],
|
|
2753
|
-
[/gpt-?5.*mini/, "gpt-5-mini"],
|
|
2754
|
-
[/gpt-?5/, "gpt-5"],
|
|
2755
|
-
[/gpt-?4\.1-nano/, "gpt-4.1-nano"],
|
|
2756
|
-
[/gpt-?4\.1-mini/, "gpt-4.1-mini"],
|
|
2757
|
-
[/gpt-?4\.1/, "gpt-4.1"],
|
|
2758
|
-
[/gpt-?4o-mini/, "gpt-4o-mini"],
|
|
2759
|
-
[/gpt-?4o/, "gpt-4o"]
|
|
2760
|
-
];
|
|
2761
|
-
for (const [re, base] of rules) if (re.test(n)) return base;
|
|
2749
|
+
for (const [re, base] of AZURE_BASE_MODEL_RULES) if (re.test(n)) return base;
|
|
2762
2750
|
return null;
|
|
2763
2751
|
}
|
|
2764
2752
|
function azureModelForDeployment(cfg) {
|
|
@@ -2803,13 +2791,27 @@ function azureModelForDeployment(cfg) {
|
|
|
2803
2791
|
supportsToolUse: true
|
|
2804
2792
|
};
|
|
2805
2793
|
}
|
|
2806
|
-
var DEFAULT_AZURE_API_VERSION, AzureOpenAIProvider;
|
|
2794
|
+
var DEFAULT_AZURE_API_VERSION, AZURE_BASE_MODEL_RULES, AZURE_BASE_MODELS, AzureOpenAIProvider;
|
|
2807
2795
|
var init_azure = __esm({
|
|
2808
2796
|
"src/providers/azure.ts"() {
|
|
2809
2797
|
init_constants();
|
|
2810
2798
|
init_openai();
|
|
2811
2799
|
init_pricing();
|
|
2812
2800
|
DEFAULT_AZURE_API_VERSION = "2024-12-01-preview";
|
|
2801
|
+
AZURE_BASE_MODEL_RULES = [
|
|
2802
|
+
[/gpt-?5\.5/, "gpt-5.5"],
|
|
2803
|
+
[/gpt-?5\.4.*mini/, "gpt-5.4-mini"],
|
|
2804
|
+
[/gpt-?5\.4/, "gpt-5.4"],
|
|
2805
|
+
[/gpt-?5.*nano/, "gpt-5-nano"],
|
|
2806
|
+
[/gpt-?5.*mini/, "gpt-5-mini"],
|
|
2807
|
+
[/gpt-?5/, "gpt-5"],
|
|
2808
|
+
[/gpt-?4\.1-nano/, "gpt-4.1-nano"],
|
|
2809
|
+
[/gpt-?4\.1-mini/, "gpt-4.1-mini"],
|
|
2810
|
+
[/gpt-?4\.1/, "gpt-4.1"],
|
|
2811
|
+
[/gpt-?4o-mini/, "gpt-4o-mini"],
|
|
2812
|
+
[/gpt-?4o/, "gpt-4o"]
|
|
2813
|
+
];
|
|
2814
|
+
AZURE_BASE_MODELS = AZURE_BASE_MODEL_RULES.map(([, id]) => id);
|
|
2813
2815
|
AzureOpenAIProvider = class extends OpenAIProvider {
|
|
2814
2816
|
constructor(config, model) {
|
|
2815
2817
|
const rawUrl = config.baseUrl ?? AZURE_BASE_URL_TEMPLATE.replace("{resource}", "YOUR_RESOURCE");
|
|
@@ -4997,7 +4999,14 @@ var ToolsConfigSchema = z.object({
|
|
|
4997
4999
|
* built-in tool (shell/file/git/…) from existing at all, rather than just
|
|
4998
5000
|
* gating it behind approval. Omitted = full default set (unchanged).
|
|
4999
5001
|
*/
|
|
5000
|
-
enabledTools: z.array(z.string()).optional()
|
|
5002
|
+
enabledTools: z.array(z.string()).optional(),
|
|
5003
|
+
/**
|
|
5004
|
+
* Tools to remove even when they would otherwise be registered. Unlike
|
|
5005
|
+
* `enabledTools` (an allowlist over the built-in set), this also reaches
|
|
5006
|
+
* tools registered outside it — MCP tools and media generation — which is
|
|
5007
|
+
* the only way to turn those off.
|
|
5008
|
+
*/
|
|
5009
|
+
disabledTools: z.array(z.string()).optional()
|
|
5001
5010
|
});
|
|
5002
5011
|
var HookDefinitionSchema = z.object({
|
|
5003
5012
|
name: z.string().optional(),
|
|
@@ -5503,6 +5512,26 @@ var ConfigManager = class {
|
|
|
5503
5512
|
}
|
|
5504
5513
|
};
|
|
5505
5514
|
|
|
5515
|
+
// src/tools/tool-name.ts
|
|
5516
|
+
var MCP_TOOL_PREFIX = "mcp__";
|
|
5517
|
+
function sanitizeToolNameSegment(segment) {
|
|
5518
|
+
const cleaned = segment.replace(/[^a-zA-Z0-9-]+/g, "_");
|
|
5519
|
+
let start = 0;
|
|
5520
|
+
let end = cleaned.length;
|
|
5521
|
+
while (start < end && cleaned[start] === "_") start++;
|
|
5522
|
+
while (end > start && cleaned[end - 1] === "_") end--;
|
|
5523
|
+
return cleaned.slice(start, end) || "_";
|
|
5524
|
+
}
|
|
5525
|
+
function mcpToolName(serverName, toolName) {
|
|
5526
|
+
return `${MCP_TOOL_PREFIX}${sanitizeToolNameSegment(serverName)}__${sanitizeToolNameSegment(toolName)}`;
|
|
5527
|
+
}
|
|
5528
|
+
function isMcpToolName(name) {
|
|
5529
|
+
return name.startsWith(MCP_TOOL_PREFIX);
|
|
5530
|
+
}
|
|
5531
|
+
function mcpServerPrefix(serverName) {
|
|
5532
|
+
return `${MCP_TOOL_PREFIX}${sanitizeToolNameSegment(serverName)}__`;
|
|
5533
|
+
}
|
|
5534
|
+
|
|
5506
5535
|
// src/cli/utils/terminal-input.ts
|
|
5507
5536
|
var PASTE_START = "\x1B[200~";
|
|
5508
5537
|
var PASTE_END = "\x1B[201~";
|
|
@@ -11022,17 +11051,13 @@ var CAPABILITIES = [
|
|
|
11022
11051
|
},
|
|
11023
11052
|
// ── Video ──
|
|
11024
11053
|
{
|
|
11025
|
-
//
|
|
11026
|
-
//
|
|
11027
|
-
//
|
|
11028
|
-
// signed URL. That is a different execution shape from every other entry
|
|
11029
|
-
// here and is not wired up — claiming otherwise would produce a tool that
|
|
11030
|
-
// fails at call time instead of an honest "not yet".
|
|
11054
|
+
// Long-running: submit returns an operation name, the video lands minutes
|
|
11055
|
+
// later, and the bytes come from a short-lived signed URL. generate.ts owns
|
|
11056
|
+
// that polling loop; the registry only records the shape via `api`.
|
|
11031
11057
|
modality: "video",
|
|
11032
11058
|
provider: "gemini",
|
|
11033
11059
|
modelId: "veo-3.1-generate-001",
|
|
11034
|
-
api: "gemini-predict"
|
|
11035
|
-
unsupported: "Veo runs as a long-running operation (submit \u2192 poll \u2192 fetch). Not wired up yet."
|
|
11060
|
+
api: "gemini-predict-lro"
|
|
11036
11061
|
}
|
|
11037
11062
|
];
|
|
11038
11063
|
function capabilityCost(cap) {
|
|
@@ -11345,6 +11370,75 @@ async function generateSpeech(cap, cfg, req, signal) {
|
|
|
11345
11370
|
};
|
|
11346
11371
|
}, cap.modelId);
|
|
11347
11372
|
}
|
|
11373
|
+
var VIDEO_POLL_INTERVAL_MS = 1e4;
|
|
11374
|
+
var VIDEO_TIMEOUT_MS = 8 * 6e4;
|
|
11375
|
+
function sleep(ms, signal) {
|
|
11376
|
+
return new Promise((resolve2, reject) => {
|
|
11377
|
+
if (signal?.aborted) return reject(new Error("Cancelled."));
|
|
11378
|
+
const timer = setTimeout(() => {
|
|
11379
|
+
signal?.removeEventListener("abort", onAbort);
|
|
11380
|
+
resolve2();
|
|
11381
|
+
}, ms);
|
|
11382
|
+
function onAbort() {
|
|
11383
|
+
clearTimeout(timer);
|
|
11384
|
+
reject(new Error("Cancelled."));
|
|
11385
|
+
}
|
|
11386
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
11387
|
+
});
|
|
11388
|
+
}
|
|
11389
|
+
async function generateVideo(cap, cfg, req, signal, onProgress) {
|
|
11390
|
+
return callProvider(async () => {
|
|
11391
|
+
if (cap.api !== "gemini-predict-lro") throw new Error(`${cap.modelId} is not a video generator.`);
|
|
11392
|
+
const base = baseUrlFor(cfg, "https://generativelanguage.googleapis.com/v1beta");
|
|
11393
|
+
const key = cfg.apiKey ?? "";
|
|
11394
|
+
const headers = { "x-goog-api-key": key };
|
|
11395
|
+
const submit = await postJson(
|
|
11396
|
+
`${base}/models/${encodeURIComponent(cap.modelId)}:predictLongRunning`,
|
|
11397
|
+
{
|
|
11398
|
+
instances: [{ prompt: req.prompt }],
|
|
11399
|
+
parameters: {
|
|
11400
|
+
aspectRatio: req.aspectRatio ?? "16:9",
|
|
11401
|
+
...req.seconds ? { durationSeconds: req.seconds } : {}
|
|
11402
|
+
}
|
|
11403
|
+
},
|
|
11404
|
+
headers,
|
|
11405
|
+
signal
|
|
11406
|
+
);
|
|
11407
|
+
const started = await submit.json();
|
|
11408
|
+
if (!started.name) throw new Error("Video request was accepted but returned no operation to poll.");
|
|
11409
|
+
onProgress?.(`Video generation started (${cap.modelId}). This usually takes 1-3 minutes.`);
|
|
11410
|
+
const deadline = Date.now() + VIDEO_TIMEOUT_MS;
|
|
11411
|
+
let operation = {};
|
|
11412
|
+
while (Date.now() < deadline) {
|
|
11413
|
+
await sleep(VIDEO_POLL_INTERVAL_MS, signal);
|
|
11414
|
+
const poll = await fetch(`${base}/${started.name}`, { headers, signal });
|
|
11415
|
+
if (!poll.ok) {
|
|
11416
|
+
const text = await poll.text().catch(() => "");
|
|
11417
|
+
throw Object.assign(new Error(text || poll.statusText), { status: poll.status });
|
|
11418
|
+
}
|
|
11419
|
+
operation = await poll.json();
|
|
11420
|
+
if (operation.done) break;
|
|
11421
|
+
onProgress?.("Still rendering\u2026");
|
|
11422
|
+
}
|
|
11423
|
+
if (!operation.done) {
|
|
11424
|
+
throw new Error(
|
|
11425
|
+
`Video was still rendering after ${Math.round(VIDEO_TIMEOUT_MS / 6e4)} minutes. It may still complete on the provider side, but Cascade stopped waiting.`
|
|
11426
|
+
);
|
|
11427
|
+
}
|
|
11428
|
+
if (operation.error?.message) throw new Error(operation.error.message);
|
|
11429
|
+
const uri = operation.response?.generateVideoResponse?.generatedSamples?.[0]?.video?.uri;
|
|
11430
|
+
if (!uri) throw new Error("Video finished but the response contained no video.");
|
|
11431
|
+
const media = await fetch(uri, { headers, signal });
|
|
11432
|
+
if (!media.ok) throw new Error(`Video finished but could not be downloaded (HTTP ${media.status}).`);
|
|
11433
|
+
return {
|
|
11434
|
+
data: Buffer.from(await media.arrayBuffer()),
|
|
11435
|
+
mimeType: "video/mp4",
|
|
11436
|
+
filename: stamp("mp4"),
|
|
11437
|
+
modelId: cap.modelId,
|
|
11438
|
+
provider: cap.provider
|
|
11439
|
+
};
|
|
11440
|
+
}, cap.modelId);
|
|
11441
|
+
}
|
|
11348
11442
|
async function transcribeAudio(cap, cfg, req, signal) {
|
|
11349
11443
|
return callProvider(async () => {
|
|
11350
11444
|
if (cap.api !== "openai-transcriptions") throw new Error(`${cap.modelId} is not a transcription model.`);
|
|
@@ -11469,6 +11563,58 @@ var GenerateSpeechTool = class extends BaseTool {
|
|
|
11469
11563
|
Model: ${asset.modelId} \u2014 ${r.selected.reason}`;
|
|
11470
11564
|
}
|
|
11471
11565
|
};
|
|
11566
|
+
var DEFAULT_VIDEO_SECONDS = 5;
|
|
11567
|
+
var MAX_VIDEO_SECONDS = 8;
|
|
11568
|
+
var GenerateVideoTool = class extends BaseTool {
|
|
11569
|
+
constructor(deps) {
|
|
11570
|
+
super();
|
|
11571
|
+
this.deps = deps;
|
|
11572
|
+
}
|
|
11573
|
+
deps;
|
|
11574
|
+
name = "generate_video";
|
|
11575
|
+
// The price is in the description on purpose. Video is ~1000x the cost of an
|
|
11576
|
+
// image per call, it is billed per SECOND, and the model choosing the
|
|
11577
|
+
// duration is the one deciding the bill — so the number has to be where the
|
|
11578
|
+
// model reads before calling, not only in the result afterwards.
|
|
11579
|
+
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.";
|
|
11580
|
+
inputSchema = {
|
|
11581
|
+
type: "object",
|
|
11582
|
+
properties: {
|
|
11583
|
+
prompt: { type: "string", description: "What the video should show: subject, action, camera movement, style." },
|
|
11584
|
+
seconds: { type: "number", description: `Clip length, 1-${MAX_VIDEO_SECONDS} seconds (default ${DEFAULT_VIDEO_SECONDS}). Billed per second \u2014 keep it short.` },
|
|
11585
|
+
aspectRatio: { type: "string", description: 'Frame shape: "16:9" (default) or "9:16" for vertical.' },
|
|
11586
|
+
model: { type: "string", description: "Optional: force a specific video model." }
|
|
11587
|
+
},
|
|
11588
|
+
required: ["prompt"]
|
|
11589
|
+
};
|
|
11590
|
+
async execute(input, options) {
|
|
11591
|
+
const prompt = String(input["prompt"] ?? "").trim();
|
|
11592
|
+
if (!prompt) return 'Provide a "prompt" describing the video.';
|
|
11593
|
+
const r = resolve(this.deps, "video", input["model"] ? String(input["model"]) : void 0);
|
|
11594
|
+
if (!r.ok) return r.error;
|
|
11595
|
+
const asked = typeof input["seconds"] === "number" ? input["seconds"] : DEFAULT_VIDEO_SECONDS;
|
|
11596
|
+
const seconds = Math.max(1, Math.min(MAX_VIDEO_SECONDS, Math.round(asked)));
|
|
11597
|
+
const asset = await generateVideo(
|
|
11598
|
+
r.selected.capability,
|
|
11599
|
+
r.cfg,
|
|
11600
|
+
{
|
|
11601
|
+
prompt,
|
|
11602
|
+
seconds,
|
|
11603
|
+
...input["aspectRatio"] ? { aspectRatio: String(input["aspectRatio"]) } : {}
|
|
11604
|
+
},
|
|
11605
|
+
options.signal,
|
|
11606
|
+
// Progress goes to the run log so a two-minute wait doesn't look like a
|
|
11607
|
+
// hang — the single most likely reason someone kills a working render.
|
|
11608
|
+
(note) => options.onProgress?.(note)
|
|
11609
|
+
);
|
|
11610
|
+
const location = await this.deps.sink(asset);
|
|
11611
|
+
const unit = r.selected.cost;
|
|
11612
|
+
const estimate = unit ? ` Estimated cost: $${(unit.amount * seconds).toFixed(2)} (${seconds}s x ${unit.label}).` : "";
|
|
11613
|
+
const clamped = seconds !== Math.round(asked) ? ` Duration was clamped to ${seconds}s (max ${MAX_VIDEO_SECONDS}).` : "";
|
|
11614
|
+
return `Video generated and saved to ${location}.
|
|
11615
|
+
Model: ${asset.modelId}.${estimate}${clamped}`;
|
|
11616
|
+
}
|
|
11617
|
+
};
|
|
11472
11618
|
var TranscribeAudioTool = class extends BaseTool {
|
|
11473
11619
|
constructor(deps, readFile) {
|
|
11474
11620
|
super();
|
|
@@ -11519,6 +11665,7 @@ function buildMediaTools(deps, readFile) {
|
|
|
11519
11665
|
const modalities = new Set(deps.registry.availableModalities());
|
|
11520
11666
|
if (modalities.has("image")) tools.push(new GenerateImageTool(deps));
|
|
11521
11667
|
if (modalities.has("speech")) tools.push(new GenerateSpeechTool(deps));
|
|
11668
|
+
if (modalities.has("video")) tools.push(new GenerateVideoTool(deps));
|
|
11522
11669
|
if (modalities.has("transcription") && readFile) tools.push(new TranscribeAudioTool(deps, readFile));
|
|
11523
11670
|
return tools;
|
|
11524
11671
|
}
|
|
@@ -13019,7 +13166,7 @@ var McpToolWrapper = class extends BaseTool {
|
|
|
13019
13166
|
this.mcpClient = mcpClient;
|
|
13020
13167
|
this.serverName = serverName;
|
|
13021
13168
|
this.toolName = toolName;
|
|
13022
|
-
this.name =
|
|
13169
|
+
this.name = mcpToolName(serverName, toolName);
|
|
13023
13170
|
this.description = `[MCP:${serverName}] ${description}`;
|
|
13024
13171
|
this.inputSchema = inputSchema;
|
|
13025
13172
|
}
|
|
@@ -15938,6 +16085,7 @@ var Cascade = class _Cascade extends EventEmitter {
|
|
|
15938
16085
|
router;
|
|
15939
16086
|
toolRegistry;
|
|
15940
16087
|
multimodal;
|
|
16088
|
+
mediaSink;
|
|
15941
16089
|
mcpClient;
|
|
15942
16090
|
codeIndex;
|
|
15943
16091
|
config;
|
|
@@ -16257,14 +16405,27 @@ ${input}` }],
|
|
|
16257
16405
|
* server stores a file row) constructs the tools itself via buildMediaTools
|
|
16258
16406
|
* with its own sink.
|
|
16259
16407
|
*/
|
|
16408
|
+
/**
|
|
16409
|
+
* Replace where generated media is written.
|
|
16410
|
+
*
|
|
16411
|
+
* The default sink writes into the workspace, which is what a desktop or CLI
|
|
16412
|
+
* run wants. A host with different storage — the cloud server keeps a file
|
|
16413
|
+
* row plus bytes under the tenant's directory — supplies its own here, and
|
|
16414
|
+
* the media tools are rebuilt around it. Re-registering by name replaces the
|
|
16415
|
+
* existing tools rather than duplicating them.
|
|
16416
|
+
*/
|
|
16417
|
+
setMediaSink(sink) {
|
|
16418
|
+
this.mediaSink = sink;
|
|
16419
|
+
this.registerMediaTools(this.workspacePath);
|
|
16420
|
+
}
|
|
16260
16421
|
registerMediaTools(workspacePath) {
|
|
16261
|
-
const
|
|
16262
|
-
const permitted = (name) => !
|
|
16422
|
+
const denied = new Set(this.config.tools?.disabledTools ?? []);
|
|
16423
|
+
const permitted = (name) => !denied.has(name);
|
|
16263
16424
|
const tools = buildMediaTools(
|
|
16264
16425
|
{
|
|
16265
16426
|
registry: this.multimodal,
|
|
16266
16427
|
lookupProvider: (provider) => (this.config.providers ?? []).find((p) => p.type === provider),
|
|
16267
|
-
sink: async (asset) => {
|
|
16428
|
+
sink: this.mediaSink ?? (async (asset) => {
|
|
16268
16429
|
const fsp = await import('fs/promises');
|
|
16269
16430
|
const nodePath = await import('path');
|
|
16270
16431
|
const dir = nodePath.join(workspacePath, "generated");
|
|
@@ -16272,7 +16433,7 @@ ${input}` }],
|
|
|
16272
16433
|
const dest = nodePath.join(dir, asset.filename);
|
|
16273
16434
|
await fsp.writeFile(dest, asset.data);
|
|
16274
16435
|
return nodePath.relative(workspacePath, dest);
|
|
16275
|
-
}
|
|
16436
|
+
})
|
|
16276
16437
|
},
|
|
16277
16438
|
async (p) => {
|
|
16278
16439
|
const fsp = await import('fs/promises');
|
|
@@ -17276,7 +17437,7 @@ var CloudClient = class _CloudClient {
|
|
|
17276
17437
|
const deadline = Date.now() + start.expiresIn * 1e3;
|
|
17277
17438
|
while (Date.now() < deadline) {
|
|
17278
17439
|
if (opts.signal?.aborted) throw new Error("Sign-in cancelled.");
|
|
17279
|
-
await
|
|
17440
|
+
await sleep2(interval * 1e3, opts.signal);
|
|
17280
17441
|
const r = await this.pollDevice(start.deviceCode);
|
|
17281
17442
|
if (r === "slow_down") {
|
|
17282
17443
|
interval += 5;
|
|
@@ -17487,7 +17648,7 @@ var CloudClient = class _CloudClient {
|
|
|
17487
17648
|
this.store.clear();
|
|
17488
17649
|
}
|
|
17489
17650
|
};
|
|
17490
|
-
function
|
|
17651
|
+
function sleep2(ms, signal) {
|
|
17491
17652
|
return new Promise((resolve2, reject) => {
|
|
17492
17653
|
const t = setTimeout(resolve2, ms);
|
|
17493
17654
|
signal?.addEventListener("abort", () => {
|
|
@@ -19366,13 +19527,13 @@ ${msg.content}`).join("\n\n");
|
|
|
19366
19527
|
const cascade = cascadeRef.current;
|
|
19367
19528
|
if (!cascade) return "Cascade not initialized.";
|
|
19368
19529
|
const toolRegistry = cascade.getToolRegistry();
|
|
19369
|
-
const tools = toolRegistry.getToolDefinitions().filter((t) => t.name
|
|
19530
|
+
const tools = toolRegistry.getToolDefinitions().filter((t) => isMcpToolName(t.name));
|
|
19370
19531
|
if (tools.length === 0) return "No MCP tools connected.";
|
|
19371
19532
|
const servers = /* @__PURE__ */ new Set();
|
|
19372
19533
|
tools.forEach((t) => servers.add(t.name.split("::")[1]));
|
|
19373
19534
|
const lines = [`Connected MCP Servers (${servers.size}):`];
|
|
19374
19535
|
for (const server of servers) {
|
|
19375
|
-
const serverTools = tools.filter((t) => t.name.startsWith(
|
|
19536
|
+
const serverTools = tools.filter((t) => t.name.startsWith(mcpServerPrefix(server)));
|
|
19376
19537
|
lines.push(`
|
|
19377
19538
|
\u25CF ${server} (${serverTools.length} tools)`);
|
|
19378
19539
|
serverTools.forEach((t) => lines.push(` - ${t.name.split("::")[2]} \u2014 ${t.description.replace(`[MCP:${server}] `, "")}`));
|