cascade-ai 0.50.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 +930 -133
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +930 -133
- package/dist/cli.js.map +1 -1
- package/dist/desktop-core.cjs +1137 -347
- package/dist/index.cjs +916 -124
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +102 -0
- package/dist/index.d.ts +102 -0
- package/dist/index.js +916 -124
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -1793,7 +1793,7 @@ var CASCADE_VERSION, CASCADE_CONFIG_FILE, CASCADE_DB_FILE, CASCADE_DASHBOARD_SEC
|
|
|
1793
1793
|
var init_constants = __esm({
|
|
1794
1794
|
"src/constants.ts"() {
|
|
1795
1795
|
init_pricing();
|
|
1796
|
-
CASCADE_VERSION = "0.
|
|
1796
|
+
CASCADE_VERSION = "0.52.0";
|
|
1797
1797
|
CASCADE_CONFIG_FILE = ".cascade/config.json";
|
|
1798
1798
|
CASCADE_DB_FILE = ".cascade/memory.db";
|
|
1799
1799
|
CASCADE_DASHBOARD_SECRET_FILE = ".cascade/dashboard-secret";
|
|
@@ -2311,49 +2311,40 @@ function isChatModel(id, methods) {
|
|
|
2311
2311
|
}
|
|
2312
2312
|
return !NON_CHAT.test(id);
|
|
2313
2313
|
}
|
|
2314
|
-
var
|
|
2314
|
+
var MODALITY_PATTERNS, NON_CHAT;
|
|
2315
2315
|
var init_model_filter = __esm({
|
|
2316
2316
|
"src/providers/model-filter.ts"() {
|
|
2317
|
-
|
|
2318
|
-
"embed",
|
|
2317
|
+
MODALITY_PATTERNS = [
|
|
2318
|
+
["embedding", ["embed"]],
|
|
2319
2319
|
// text-embedding-3, nomic-embed, mxbai-embed, snowflake-arctic-embed
|
|
2320
|
-
"(?:^|[-_/])
|
|
2321
|
-
"text-to-speech",
|
|
2322
|
-
"
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
"
|
|
2334
|
-
"
|
|
2335
|
-
"
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
"veo",
|
|
2346
|
-
"sora",
|
|
2347
|
-
"(?:^|[-_/])video(?:[-_/]|$)",
|
|
2348
|
-
// video
|
|
2349
|
-
"(?:^|[-_/])aqa(?:[-_/]|$)",
|
|
2350
|
-
// attributed QA
|
|
2351
|
-
"(?:^|[-_/])live(?:[-_/]|$)",
|
|
2352
|
-
// live-audio variants
|
|
2353
|
-
"(?:^|[-_/])(?:davinci|babbage)(?:[-_/]|$)"
|
|
2354
|
-
// legacy completion-only base models
|
|
2320
|
+
["transcription", ["whisper", "transcrib", "(?:^|[-_/])stt(?:[-_/]|$)"]],
|
|
2321
|
+
["speech", ["(?:^|[-_/])tts(?:[-_/]|$)", "text-to-speech", "speech"]],
|
|
2322
|
+
["video", ["veo", "sora", "(?:^|[-_/])video(?:[-_/]|$)"]],
|
|
2323
|
+
["image", [
|
|
2324
|
+
"dall[-]?e",
|
|
2325
|
+
"imagen",
|
|
2326
|
+
"image-generation",
|
|
2327
|
+
"gpt-image",
|
|
2328
|
+
"(?:^|[-_/])image(?:[-_/]|$)",
|
|
2329
|
+
"stable-diffusion",
|
|
2330
|
+
"sdxl",
|
|
2331
|
+
"flux"
|
|
2332
|
+
]],
|
|
2333
|
+
["moderation", ["moderation", "(?:^|[-_/])guard(?:[-_/]|$)"]],
|
|
2334
|
+
["rerank", ["rerank"]],
|
|
2335
|
+
["realtime", [
|
|
2336
|
+
"(?:^|[-_/])audio(?:[-_/]|$)",
|
|
2337
|
+
"native-audio",
|
|
2338
|
+
"realtime",
|
|
2339
|
+
"(?:^|[-_/])live(?:[-_/]|$)",
|
|
2340
|
+
// live-audio variants
|
|
2341
|
+
"(?:^|[-_/])aqa(?:[-_/]|$)"
|
|
2342
|
+
// attributed QA
|
|
2343
|
+
]],
|
|
2344
|
+
["legacy-completion", ["(?:^|[-_/])(?:davinci|babbage)(?:[-_/]|$)"]]
|
|
2355
2345
|
];
|
|
2356
|
-
|
|
2346
|
+
MODALITY_PATTERNS.map(([m, pats]) => [m, new RegExp(pats.join("|"), "i")]);
|
|
2347
|
+
NON_CHAT = new RegExp(MODALITY_PATTERNS.flatMap(([, p]) => p).join("|"), "i");
|
|
2357
2348
|
}
|
|
2358
2349
|
});
|
|
2359
2350
|
|
|
@@ -2921,6 +2912,118 @@ var init_azure = __esm({
|
|
|
2921
2912
|
}
|
|
2922
2913
|
});
|
|
2923
2914
|
|
|
2915
|
+
// src/providers/gemini-schema.ts
|
|
2916
|
+
function isPlainObject(v) {
|
|
2917
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
2918
|
+
}
|
|
2919
|
+
function normalizeType(value) {
|
|
2920
|
+
if (typeof value === "string") {
|
|
2921
|
+
return ALLOWED_TYPES.has(value) ? { type: value } : {};
|
|
2922
|
+
}
|
|
2923
|
+
if (Array.isArray(value)) {
|
|
2924
|
+
const types = value.filter((t) => typeof t === "string");
|
|
2925
|
+
const nullable = types.includes("null");
|
|
2926
|
+
const concrete = types.find((t) => t !== "null" && ALLOWED_TYPES.has(t));
|
|
2927
|
+
return {
|
|
2928
|
+
...concrete ? { type: concrete } : {},
|
|
2929
|
+
...nullable ? { nullable: true } : {}
|
|
2930
|
+
};
|
|
2931
|
+
}
|
|
2932
|
+
return {};
|
|
2933
|
+
}
|
|
2934
|
+
function normalizeEnum(value) {
|
|
2935
|
+
if (!Array.isArray(value) || value.length === 0) return void 0;
|
|
2936
|
+
return value.map((v) => typeof v === "string" ? v : JSON.stringify(v));
|
|
2937
|
+
}
|
|
2938
|
+
function sanitizeGeminiSchema(schema) {
|
|
2939
|
+
if (!isPlainObject(schema)) return void 0;
|
|
2940
|
+
const out = {};
|
|
2941
|
+
for (const [key, value] of Object.entries(schema)) {
|
|
2942
|
+
if (!ALLOWED_KEYS.has(key)) continue;
|
|
2943
|
+
switch (key) {
|
|
2944
|
+
case "type": {
|
|
2945
|
+
Object.assign(out, normalizeType(value));
|
|
2946
|
+
break;
|
|
2947
|
+
}
|
|
2948
|
+
case "enum": {
|
|
2949
|
+
const e = normalizeEnum(value);
|
|
2950
|
+
if (e) out["enum"] = e;
|
|
2951
|
+
break;
|
|
2952
|
+
}
|
|
2953
|
+
case "properties": {
|
|
2954
|
+
if (!isPlainObject(value)) break;
|
|
2955
|
+
const props = {};
|
|
2956
|
+
for (const [propName, propSchema] of Object.entries(value)) {
|
|
2957
|
+
const cleaned = sanitizeGeminiSchema(propSchema);
|
|
2958
|
+
props[propName] = cleaned && Object.keys(cleaned).length ? cleaned : { type: "string" };
|
|
2959
|
+
}
|
|
2960
|
+
out["properties"] = props;
|
|
2961
|
+
break;
|
|
2962
|
+
}
|
|
2963
|
+
case "items": {
|
|
2964
|
+
const cleaned = sanitizeGeminiSchema(value);
|
|
2965
|
+
if (cleaned) out["items"] = cleaned;
|
|
2966
|
+
break;
|
|
2967
|
+
}
|
|
2968
|
+
case "anyOf": {
|
|
2969
|
+
if (!Array.isArray(value)) break;
|
|
2970
|
+
const variants = value.map((v) => sanitizeGeminiSchema(v)).filter((v) => !!v && Object.keys(v).length > 0);
|
|
2971
|
+
if (variants.length) out["anyOf"] = variants;
|
|
2972
|
+
break;
|
|
2973
|
+
}
|
|
2974
|
+
case "required": {
|
|
2975
|
+
if (Array.isArray(value)) {
|
|
2976
|
+
out["required"] = value.filter((r) => typeof r === "string");
|
|
2977
|
+
}
|
|
2978
|
+
break;
|
|
2979
|
+
}
|
|
2980
|
+
default:
|
|
2981
|
+
out[key] = value;
|
|
2982
|
+
}
|
|
2983
|
+
}
|
|
2984
|
+
if (!out["type"] && out["properties"]) out["type"] = "object";
|
|
2985
|
+
if (!out["type"] && out["items"]) out["type"] = "array";
|
|
2986
|
+
return out;
|
|
2987
|
+
}
|
|
2988
|
+
function toGeminiParameters(inputSchema) {
|
|
2989
|
+
const cleaned = sanitizeGeminiSchema(inputSchema);
|
|
2990
|
+
if (!cleaned) return void 0;
|
|
2991
|
+
const props = cleaned["properties"];
|
|
2992
|
+
if (isPlainObject(props) && Object.keys(props).length === 0) return void 0;
|
|
2993
|
+
if (Object.keys(cleaned).length === 0) return void 0;
|
|
2994
|
+
return cleaned;
|
|
2995
|
+
}
|
|
2996
|
+
var ALLOWED_KEYS, ALLOWED_TYPES;
|
|
2997
|
+
var init_gemini_schema = __esm({
|
|
2998
|
+
"src/providers/gemini-schema.ts"() {
|
|
2999
|
+
ALLOWED_KEYS = /* @__PURE__ */ new Set([
|
|
3000
|
+
"type",
|
|
3001
|
+
"format",
|
|
3002
|
+
"title",
|
|
3003
|
+
"description",
|
|
3004
|
+
"nullable",
|
|
3005
|
+
"enum",
|
|
3006
|
+
"items",
|
|
3007
|
+
"properties",
|
|
3008
|
+
"required",
|
|
3009
|
+
"propertyOrdering",
|
|
3010
|
+
"minItems",
|
|
3011
|
+
"maxItems",
|
|
3012
|
+
"minProperties",
|
|
3013
|
+
"maxProperties",
|
|
3014
|
+
"minimum",
|
|
3015
|
+
"maximum",
|
|
3016
|
+
"minLength",
|
|
3017
|
+
"maxLength",
|
|
3018
|
+
"pattern",
|
|
3019
|
+
"example",
|
|
3020
|
+
"default",
|
|
3021
|
+
"anyOf"
|
|
3022
|
+
]);
|
|
3023
|
+
ALLOWED_TYPES = /* @__PURE__ */ new Set(["string", "number", "integer", "boolean", "array", "object"]);
|
|
3024
|
+
}
|
|
3025
|
+
});
|
|
3026
|
+
|
|
2924
3027
|
// src/providers/gemini.ts
|
|
2925
3028
|
var gemini_exports = {};
|
|
2926
3029
|
__export(gemini_exports, {
|
|
@@ -2933,6 +3036,7 @@ var init_gemini = __esm({
|
|
|
2933
3036
|
init_base();
|
|
2934
3037
|
init_pricing();
|
|
2935
3038
|
init_model_filter();
|
|
3039
|
+
init_gemini_schema();
|
|
2936
3040
|
GeminiProvider = class extends BaseProvider {
|
|
2937
3041
|
client;
|
|
2938
3042
|
constructor(config, model) {
|
|
@@ -3153,7 +3257,12 @@ var init_gemini = __esm({
|
|
|
3153
3257
|
return {
|
|
3154
3258
|
name: tool.name,
|
|
3155
3259
|
description: tool.description,
|
|
3156
|
-
parameters
|
|
3260
|
+
// Convert, never cast. Gemini's parameters field is an OpenAPI subset
|
|
3261
|
+
// that 400s on unknown keys, and MCP servers ship schemas carrying their
|
|
3262
|
+
// own extensions (GitHub's adds `x-mcp-header` to every header-bound
|
|
3263
|
+
// property). Casting sent those through verbatim and every request failed
|
|
3264
|
+
// before the model saw it. See providers/gemini-schema.ts.
|
|
3265
|
+
parameters: toGeminiParameters(tool.inputSchema)
|
|
3157
3266
|
};
|
|
3158
3267
|
}
|
|
3159
3268
|
};
|
|
@@ -3178,7 +3287,7 @@ async function nodeHttpFetch(input, init = {}, redirectCount = 0) {
|
|
|
3178
3287
|
headers["accept-encoding"] = "gzip, deflate, br";
|
|
3179
3288
|
}
|
|
3180
3289
|
const body = init.body == null ? void 0 : typeof init.body === "string" ? init.body : Buffer.from(init.body);
|
|
3181
|
-
return new Promise((
|
|
3290
|
+
return new Promise((resolve2, reject) => {
|
|
3182
3291
|
const req = lib.request(
|
|
3183
3292
|
{
|
|
3184
3293
|
hostname: u.hostname,
|
|
@@ -3195,7 +3304,7 @@ async function nodeHttpFetch(input, init = {}, redirectCount = 0) {
|
|
|
3195
3304
|
const nextUrl = new URL(location, u).href;
|
|
3196
3305
|
const downgrade = status === 303 || (status === 301 || status === 302) && method !== "GET" && method !== "HEAD";
|
|
3197
3306
|
const nextInit = downgrade ? { ...init, method: "GET", body: void 0 } : init;
|
|
3198
|
-
|
|
3307
|
+
resolve2(nodeHttpFetch(nextUrl, nextInit, redirectCount + 1));
|
|
3199
3308
|
return;
|
|
3200
3309
|
}
|
|
3201
3310
|
const encoding = (res.headers["content-encoding"] ?? "").toLowerCase();
|
|
@@ -3210,7 +3319,7 @@ async function nodeHttpFetch(input, init = {}, redirectCount = 0) {
|
|
|
3210
3319
|
if (Array.isArray(v)) respHeaders.set(k, v.join(", "));
|
|
3211
3320
|
else if (typeof v === "string") respHeaders.set(k, v);
|
|
3212
3321
|
}
|
|
3213
|
-
|
|
3322
|
+
resolve2(new Response(stream$1, {
|
|
3214
3323
|
status,
|
|
3215
3324
|
statusText: res.statusMessage ?? "",
|
|
3216
3325
|
headers: respHeaders
|
|
@@ -4941,7 +5050,14 @@ var ToolsConfigSchema = zod.z.object({
|
|
|
4941
5050
|
* built-in tool (shell/file/git/…) from existing at all, rather than just
|
|
4942
5051
|
* gating it behind approval. Omitted = full default set (unchanged).
|
|
4943
5052
|
*/
|
|
4944
|
-
enabledTools: zod.z.array(zod.z.string()).optional()
|
|
5053
|
+
enabledTools: zod.z.array(zod.z.string()).optional(),
|
|
5054
|
+
/**
|
|
5055
|
+
* Tools to remove even when they would otherwise be registered. Unlike
|
|
5056
|
+
* `enabledTools` (an allowlist over the built-in set), this also reaches
|
|
5057
|
+
* tools registered outside it — MCP tools and media generation — which is
|
|
5058
|
+
* the only way to turn those off.
|
|
5059
|
+
*/
|
|
5060
|
+
disabledTools: zod.z.array(zod.z.string()).optional()
|
|
4945
5061
|
});
|
|
4946
5062
|
var HookDefinitionSchema = zod.z.object({
|
|
4947
5063
|
name: zod.z.string().optional(),
|
|
@@ -5009,6 +5125,14 @@ var BudgetConfigSchema = zod.z.object({
|
|
|
5009
5125
|
maxTokensPerRun: zod.z.number().int().positive().default(2e5),
|
|
5010
5126
|
/** Optional hard per-task cost ceiling (USD). Unset = only the token cap applies. */
|
|
5011
5127
|
maxCostPerRunUsd: zod.z.number().positive().optional(),
|
|
5128
|
+
/**
|
|
5129
|
+
* Consecutive systemic failures against ONE model before the run stops
|
|
5130
|
+
* launching work that would fail the same way (dead key, wrong model id,
|
|
5131
|
+
* exhausted quota). Lower reacts sooner and wastes less; higher absorbs a
|
|
5132
|
+
* longer transient before giving up. Only failures that will certainly repeat
|
|
5133
|
+
* are counted — see core/run-breaker.ts.
|
|
5134
|
+
*/
|
|
5135
|
+
failureThreshold: zod.z.number().int().min(1).max(20).default(3),
|
|
5012
5136
|
warnAtPct: zod.z.number().default(80)
|
|
5013
5137
|
});
|
|
5014
5138
|
var WorkspaceConfigSchema = zod.z.object({
|
|
@@ -6061,7 +6185,7 @@ var TpmLimiter = class {
|
|
|
6061
6185
|
}
|
|
6062
6186
|
const deficit = want - bucket.available;
|
|
6063
6187
|
const waitMs = Math.max(50, Math.ceil(deficit / bucket.tokensPerMinute * 6e4));
|
|
6064
|
-
await new Promise((
|
|
6188
|
+
await new Promise((resolve2) => setTimeout(resolve2, waitMs));
|
|
6065
6189
|
}
|
|
6066
6190
|
}
|
|
6067
6191
|
/**
|
|
@@ -6124,14 +6248,14 @@ var LocalRequestQueue = class {
|
|
|
6124
6248
|
this.active++;
|
|
6125
6249
|
return this.makeRelease();
|
|
6126
6250
|
}
|
|
6127
|
-
return new Promise((
|
|
6251
|
+
return new Promise((resolve2, reject) => {
|
|
6128
6252
|
let settled = false;
|
|
6129
6253
|
let timer;
|
|
6130
6254
|
const resolver = (release) => {
|
|
6131
6255
|
if (settled) return;
|
|
6132
6256
|
settled = true;
|
|
6133
6257
|
if (timer !== void 0) clearTimeout(timer);
|
|
6134
|
-
|
|
6258
|
+
resolve2(release);
|
|
6135
6259
|
};
|
|
6136
6260
|
if (timeoutMs !== void 0 && timeoutMs > 0) {
|
|
6137
6261
|
timer = setTimeout(() => {
|
|
@@ -6758,6 +6882,7 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
6758
6882
|
tpmLimiter;
|
|
6759
6883
|
localQueue;
|
|
6760
6884
|
taskAnalyzer;
|
|
6885
|
+
pendingFeedbackSource;
|
|
6761
6886
|
worldStateDB;
|
|
6762
6887
|
privacyPaths;
|
|
6763
6888
|
guidanceQueue;
|
|
@@ -7212,8 +7337,18 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
7212
7337
|
return windows.length ? Math.min(...windows) : void 0;
|
|
7213
7338
|
}
|
|
7214
7339
|
/** Wire the Cascade Auto task analyzer used for per-subtask model routing. */
|
|
7340
|
+
/**
|
|
7341
|
+
* Feed per-model thumbs counts into Auto routing. Forwarded to the analyzer
|
|
7342
|
+
* because that is where the public benchmark score is combined; the prior
|
|
7343
|
+
* adjusts that number rather than sitting anywhere else in the pipeline.
|
|
7344
|
+
*/
|
|
7345
|
+
setFeedbackSource(source) {
|
|
7346
|
+
this.taskAnalyzer?.setFeedbackSource(source);
|
|
7347
|
+
this.pendingFeedbackSource = source;
|
|
7348
|
+
}
|
|
7215
7349
|
setTaskAnalyzer(analyzer) {
|
|
7216
7350
|
this.taskAnalyzer = analyzer;
|
|
7351
|
+
if (this.pendingFeedbackSource) analyzer.setFeedbackSource(this.pendingFeedbackSource);
|
|
7217
7352
|
}
|
|
7218
7353
|
setWorldStateDB(db) {
|
|
7219
7354
|
this.worldStateDB = db;
|
|
@@ -8308,8 +8443,11 @@ Now execute your subtask using this context where relevant.`
|
|
|
8308
8443
|
const result = await this.runAgentLoop(systemPrompt, this.tools);
|
|
8309
8444
|
output = result.output;
|
|
8310
8445
|
toolCalls = result.toolCalls;
|
|
8311
|
-
this.
|
|
8312
|
-
|
|
8446
|
+
const mustProduceArtifact = this.requiresArtifact();
|
|
8447
|
+
if (mustProduceArtifact) {
|
|
8448
|
+
this.sendStatusUpdate({ progressPct: 65, currentAction: "Verifying required artifacts", status: "IN_PROGRESS" });
|
|
8449
|
+
}
|
|
8450
|
+
const artifactCheck = mustProduceArtifact ? await this.verifyArtifacts(assignment) : { ok: true, issues: [] };
|
|
8313
8451
|
if (!artifactCheck.ok) {
|
|
8314
8452
|
correctionAttempts = 1;
|
|
8315
8453
|
issues.push(...artifactCheck.issues);
|
|
@@ -8589,11 +8727,11 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : "") + textToolSuffix,
|
|
|
8589
8727
|
if (!wasApproved) return `Tool ${tc.name} was denied by user.`;
|
|
8590
8728
|
} else {
|
|
8591
8729
|
const LEGACY_APPROVAL_TIMEOUT_MS = 6e5;
|
|
8592
|
-
const legacyDecision = await new Promise((
|
|
8730
|
+
const legacyDecision = await new Promise((resolve2) => {
|
|
8593
8731
|
const eventName = `tool:approval-response:${this.id}-${tc.id}`;
|
|
8594
8732
|
const timer = setTimeout(() => {
|
|
8595
8733
|
this.removeAllListeners(eventName);
|
|
8596
|
-
|
|
8734
|
+
resolve2({ approved: false });
|
|
8597
8735
|
}, LEGACY_APPROVAL_TIMEOUT_MS);
|
|
8598
8736
|
timer.unref?.();
|
|
8599
8737
|
this.emit("tool:approval-request", {
|
|
@@ -8606,7 +8744,7 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : "") + textToolSuffix,
|
|
|
8606
8744
|
});
|
|
8607
8745
|
this.once(eventName, (d) => {
|
|
8608
8746
|
clearTimeout(timer);
|
|
8609
|
-
|
|
8747
|
+
resolve2(d);
|
|
8610
8748
|
});
|
|
8611
8749
|
});
|
|
8612
8750
|
if (legacyDecision.always) this.sessionApprovals.set(tc.name, legacyDecision.approved);
|
|
@@ -8626,6 +8764,9 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : "") + textToolSuffix,
|
|
|
8626
8764
|
tierId: this.id,
|
|
8627
8765
|
sessionId: this.taskId,
|
|
8628
8766
|
requireApproval: false,
|
|
8767
|
+
// Media generation can run for a minute; without this a cancelled run
|
|
8768
|
+
// still pays for an image nobody will see.
|
|
8769
|
+
...this.signal ? { signal: this.signal } : {},
|
|
8629
8770
|
saveSnapshot: async (path37, content) => {
|
|
8630
8771
|
this.store?.addFileSnapshot(this.taskId, path37, content);
|
|
8631
8772
|
},
|
|
@@ -8673,7 +8814,8 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : "") + textToolSuffix,
|
|
|
8673
8814
|
const result = await this.toolRegistry.execute(altTool, tc.input, {
|
|
8674
8815
|
tierId: this.id,
|
|
8675
8816
|
sessionId: this.taskId,
|
|
8676
|
-
requireApproval: false
|
|
8817
|
+
requireApproval: false,
|
|
8818
|
+
...this.signal ? { signal: this.signal } : {}
|
|
8677
8819
|
});
|
|
8678
8820
|
const str2 = typeof result === "string" ? result : JSON.stringify(result);
|
|
8679
8821
|
if (!str2.startsWith("Tool error:") && !str2.startsWith("Error:")) {
|
|
@@ -8695,7 +8837,8 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : "") + textToolSuffix,
|
|
|
8695
8837
|
const result = await this.toolRegistry.execute(newToolName, tc.input, {
|
|
8696
8838
|
tierId: this.id,
|
|
8697
8839
|
sessionId: this.taskId,
|
|
8698
|
-
requireApproval: false
|
|
8840
|
+
requireApproval: false,
|
|
8841
|
+
...this.signal ? { signal: this.signal } : {}
|
|
8699
8842
|
});
|
|
8700
8843
|
const str2 = typeof result === "string" ? result : JSON.stringify(result);
|
|
8701
8844
|
if (!str2.startsWith("Tool error:")) return `[Synthesized ${newToolName}]: ${str2}`;
|
|
@@ -9113,7 +9256,7 @@ var PeerBus = class extends EventEmitter__default.default {
|
|
|
9113
9256
|
this.outputs.set(subtaskId, entry);
|
|
9114
9257
|
this.emit("output:ready", entry);
|
|
9115
9258
|
const waiting = this.waiters.get(subtaskId) ?? [];
|
|
9116
|
-
for (const
|
|
9259
|
+
for (const resolve2 of waiting) resolve2(entry);
|
|
9117
9260
|
this.waiters.delete(subtaskId);
|
|
9118
9261
|
}
|
|
9119
9262
|
/**
|
|
@@ -9144,10 +9287,10 @@ var PeerBus = class extends EventEmitter__default.default {
|
|
|
9144
9287
|
waitFor(subtaskId, timeoutMs = 12e4) {
|
|
9145
9288
|
const existing = this.outputs.get(subtaskId);
|
|
9146
9289
|
if (existing && !this.retryPending.has(subtaskId)) return Promise.resolve(existing);
|
|
9147
|
-
return new Promise((
|
|
9290
|
+
return new Promise((resolve2, reject) => {
|
|
9148
9291
|
const resolver = (output) => {
|
|
9149
9292
|
clearTimeout(timer);
|
|
9150
|
-
|
|
9293
|
+
resolve2(output);
|
|
9151
9294
|
};
|
|
9152
9295
|
const timer = setTimeout(() => {
|
|
9153
9296
|
const waiting = this.waiters.get(subtaskId);
|
|
@@ -9201,10 +9344,10 @@ var PeerBus = class extends EventEmitter__default.default {
|
|
|
9201
9344
|
*/
|
|
9202
9345
|
collect(timeoutMs) {
|
|
9203
9346
|
const collected = [...this.broadcastLog];
|
|
9204
|
-
return new Promise((
|
|
9347
|
+
return new Promise((resolve2) => {
|
|
9205
9348
|
const timer = setTimeout(() => {
|
|
9206
9349
|
off();
|
|
9207
|
-
|
|
9350
|
+
resolve2(collected);
|
|
9208
9351
|
}, timeoutMs);
|
|
9209
9352
|
const handler = (msg) => {
|
|
9210
9353
|
collected.push({ fromId: msg.fromId, payload: msg.payload, timestamp: msg.timestamp });
|
|
@@ -9218,7 +9361,7 @@ var PeerBus = class extends EventEmitter__default.default {
|
|
|
9218
9361
|
const broadcasters = new Set(collected.map((m) => m.fromId));
|
|
9219
9362
|
if (broadcasters.size >= this.members.size) {
|
|
9220
9363
|
off();
|
|
9221
|
-
|
|
9364
|
+
resolve2(collected);
|
|
9222
9365
|
}
|
|
9223
9366
|
};
|
|
9224
9367
|
this.on("broadcast", checkComplete);
|
|
@@ -9236,7 +9379,7 @@ var PeerBus = class extends EventEmitter__default.default {
|
|
|
9236
9379
|
return;
|
|
9237
9380
|
}
|
|
9238
9381
|
this.emitCoordination(tierId, `\u23F3 waiting for ${filePath} (held by ${existing.holderId})`);
|
|
9239
|
-
return new Promise((
|
|
9382
|
+
return new Promise((resolve2, reject) => {
|
|
9240
9383
|
const timer = setTimeout(() => {
|
|
9241
9384
|
reject(new Error(`File lock timeout for ${filePath} (held by ${existing.holderId})`));
|
|
9242
9385
|
}, timeoutMs);
|
|
@@ -9244,7 +9387,7 @@ var PeerBus = class extends EventEmitter__default.default {
|
|
|
9244
9387
|
clearTimeout(timer);
|
|
9245
9388
|
this.fileLocks.set(filePath, { holderId: tierId, lockedAt: (/* @__PURE__ */ new Date()).toISOString(), waiters: [] });
|
|
9246
9389
|
this.emitCoordination(tierId, `\u{1F512} locked ${filePath}`);
|
|
9247
|
-
|
|
9390
|
+
resolve2();
|
|
9248
9391
|
});
|
|
9249
9392
|
});
|
|
9250
9393
|
}
|
|
@@ -9269,11 +9412,11 @@ var PeerBus = class extends EventEmitter__default.default {
|
|
|
9269
9412
|
*/
|
|
9270
9413
|
waitForFileRelease(filePath, timeoutMs = 3e4) {
|
|
9271
9414
|
if (!this.fileLocks.has(filePath)) return Promise.resolve();
|
|
9272
|
-
return new Promise((
|
|
9415
|
+
return new Promise((resolve2, reject) => {
|
|
9273
9416
|
const timer = setTimeout(() => reject(new Error(`Timeout waiting for file release: ${filePath}`)), timeoutMs);
|
|
9274
9417
|
this.once(`file:released:${filePath}`, () => {
|
|
9275
9418
|
clearTimeout(timer);
|
|
9276
|
-
|
|
9419
|
+
resolve2();
|
|
9277
9420
|
});
|
|
9278
9421
|
});
|
|
9279
9422
|
}
|
|
@@ -9339,13 +9482,13 @@ var PeerBus = class extends EventEmitter__default.default {
|
|
|
9339
9482
|
this.emit(`barrier:${barrierName}`);
|
|
9340
9483
|
return;
|
|
9341
9484
|
}
|
|
9342
|
-
return new Promise((
|
|
9485
|
+
return new Promise((resolve2, reject) => {
|
|
9343
9486
|
const timer = setTimeout(() => {
|
|
9344
9487
|
reject(new Error(`Barrier timeout: ${barrierName} (${bar.arrived.size}/${bar.total} arrived)`));
|
|
9345
9488
|
}, 12e4);
|
|
9346
9489
|
this.once(`barrier:${barrierName}`, () => {
|
|
9347
9490
|
clearTimeout(timer);
|
|
9348
|
-
|
|
9491
|
+
resolve2();
|
|
9349
9492
|
});
|
|
9350
9493
|
});
|
|
9351
9494
|
}
|
|
@@ -10896,6 +11039,171 @@ Reply with exactly one word: YES, NO, or UNSURE.
|
|
|
10896
11039
|
// src/core/cascade.ts
|
|
10897
11040
|
init_cost();
|
|
10898
11041
|
|
|
11042
|
+
// src/core/multimodal/registry.ts
|
|
11043
|
+
init_pricing();
|
|
11044
|
+
var CAPABILITIES = [
|
|
11045
|
+
// ── Image ──
|
|
11046
|
+
{
|
|
11047
|
+
modality: "image",
|
|
11048
|
+
provider: "openai",
|
|
11049
|
+
modelId: "dall-e-3",
|
|
11050
|
+
api: "openai-images"
|
|
11051
|
+
},
|
|
11052
|
+
{
|
|
11053
|
+
modality: "image",
|
|
11054
|
+
provider: "gemini",
|
|
11055
|
+
modelId: "gemini-3-pro-image",
|
|
11056
|
+
api: "gemini-predict"
|
|
11057
|
+
},
|
|
11058
|
+
// ── Speech (text → audio) ──
|
|
11059
|
+
{
|
|
11060
|
+
modality: "speech",
|
|
11061
|
+
provider: "openai",
|
|
11062
|
+
modelId: "tts-1",
|
|
11063
|
+
api: "openai-speech"
|
|
11064
|
+
},
|
|
11065
|
+
{
|
|
11066
|
+
// The only quality claim in this table that needs no leaderboard: OpenAI
|
|
11067
|
+
// ships these as the same voice at two fidelities and charges double for
|
|
11068
|
+
// this one. That is the vendor's own ordering, not an inference.
|
|
11069
|
+
modality: "speech",
|
|
11070
|
+
provider: "openai",
|
|
11071
|
+
modelId: "tts-1-hd",
|
|
11072
|
+
api: "openai-speech",
|
|
11073
|
+
quality: 70,
|
|
11074
|
+
qualityBasis: "OpenAI's own hd variant of tts-1, priced 2x"
|
|
11075
|
+
},
|
|
11076
|
+
// ── Transcription (audio → text) ──
|
|
11077
|
+
{
|
|
11078
|
+
modality: "transcription",
|
|
11079
|
+
provider: "openai",
|
|
11080
|
+
modelId: "whisper-1",
|
|
11081
|
+
api: "openai-transcriptions"
|
|
11082
|
+
},
|
|
11083
|
+
// ── Video ──
|
|
11084
|
+
{
|
|
11085
|
+
// Long-running: submit returns an operation name, the video lands minutes
|
|
11086
|
+
// later, and the bytes come from a short-lived signed URL. generate.ts owns
|
|
11087
|
+
// that polling loop; the registry only records the shape via `api`.
|
|
11088
|
+
modality: "video",
|
|
11089
|
+
provider: "gemini",
|
|
11090
|
+
modelId: "veo-3.1-generate-001",
|
|
11091
|
+
api: "gemini-predict-lro"
|
|
11092
|
+
}
|
|
11093
|
+
];
|
|
11094
|
+
function capabilityCost(cap) {
|
|
11095
|
+
const match = findPricing(cap.modelId, cap.provider, {
|
|
11096
|
+
fallbackProviders: cap.provider === "gemini" ? ["vertex"] : []
|
|
11097
|
+
});
|
|
11098
|
+
const rate = match?.entry.rates?.[0];
|
|
11099
|
+
if (!rate) return null;
|
|
11100
|
+
const unitLabel = {
|
|
11101
|
+
per_image: "per image",
|
|
11102
|
+
per_second: "per second of video",
|
|
11103
|
+
per_character: "per character",
|
|
11104
|
+
per_audio_minute: "per audio minute"
|
|
11105
|
+
};
|
|
11106
|
+
const unit = unitLabel[rate.unit] ?? rate.unit;
|
|
11107
|
+
const amount = rate.amount;
|
|
11108
|
+
const shown = amount < 1e-3 ? amount.toExponential(2) : amount.toFixed(4);
|
|
11109
|
+
return { label: `$${shown} ${unit}`, amount, unit };
|
|
11110
|
+
}
|
|
11111
|
+
var MultimodalRegistry = class {
|
|
11112
|
+
/** Provider types the user has actually configured a key for. */
|
|
11113
|
+
available;
|
|
11114
|
+
constructor(availableProviders) {
|
|
11115
|
+
this.available = new Set(availableProviders);
|
|
11116
|
+
}
|
|
11117
|
+
/** Every capability whose provider is configured AND which Cascade can call. */
|
|
11118
|
+
usable(modality) {
|
|
11119
|
+
return CAPABILITIES.filter(
|
|
11120
|
+
(c) => !c.unsupported && this.available.has(c.provider) && (!modality || c.modality === modality)
|
|
11121
|
+
);
|
|
11122
|
+
}
|
|
11123
|
+
/** Modalities this account can actually generate right now. */
|
|
11124
|
+
availableModalities() {
|
|
11125
|
+
return [...new Set(this.usable().map((c) => c.modality))];
|
|
11126
|
+
}
|
|
11127
|
+
/**
|
|
11128
|
+
* Pick a model for a modality.
|
|
11129
|
+
*
|
|
11130
|
+
* Ranked models beat unranked ones, then higher quality, then lower unit
|
|
11131
|
+
* price. An explicit `preferModel` wins outright when it is usable — the user
|
|
11132
|
+
* naming a model is a stronger signal than any prior.
|
|
11133
|
+
*/
|
|
11134
|
+
select(modality, opts = {}) {
|
|
11135
|
+
const pool = this.usable(modality);
|
|
11136
|
+
if (!pool.length) return null;
|
|
11137
|
+
if (opts.preferModel) {
|
|
11138
|
+
const wanted = pool.find((c) => c.modelId === opts.preferModel);
|
|
11139
|
+
if (wanted) {
|
|
11140
|
+
return { capability: wanted, cost: capabilityCost(wanted), reason: `Requested explicitly: ${wanted.modelId}.` };
|
|
11141
|
+
}
|
|
11142
|
+
}
|
|
11143
|
+
const scored = pool.map((c) => ({ c, cost: capabilityCost(c) })).sort((a, b) => {
|
|
11144
|
+
const qa = a.c.quality ?? -1;
|
|
11145
|
+
const qb = b.c.quality ?? -1;
|
|
11146
|
+
if (qa !== qb) return qb - qa;
|
|
11147
|
+
const pa = a.cost?.amount ?? Number.POSITIVE_INFINITY;
|
|
11148
|
+
const pb = b.cost?.amount ?? Number.POSITIVE_INFINITY;
|
|
11149
|
+
return pa - pb;
|
|
11150
|
+
});
|
|
11151
|
+
const best = scored[0];
|
|
11152
|
+
const ranked = best.c.quality !== void 0;
|
|
11153
|
+
const reason = ranked ? `${best.c.modelId} (${best.c.provider}) \u2014 ranked highest for ${modality}: ${best.c.qualityBasis}.` + (best.cost ? ` ${best.cost.label}.` : "") : `${best.c.modelId} (${best.c.provider}) \u2014 cheapest usable ${modality} model` + (best.cost ? ` at ${best.cost.label}` : "") + ". No published quality ranking is applied for this modality, so this is a cost choice, not a quality one.";
|
|
11154
|
+
return { capability: best.c, cost: best.cost, reason };
|
|
11155
|
+
}
|
|
11156
|
+
/**
|
|
11157
|
+
* A plain-language account of what this account can and cannot generate —
|
|
11158
|
+
* including the things it cannot, and why. The planner sees this, so being
|
|
11159
|
+
* candid here is what stops it inventing a `generate_music` call.
|
|
11160
|
+
*/
|
|
11161
|
+
describe() {
|
|
11162
|
+
const lines = [];
|
|
11163
|
+
const usable = this.usable();
|
|
11164
|
+
if (usable.length) {
|
|
11165
|
+
lines.push("Generation models available on your configured providers:");
|
|
11166
|
+
for (const modality of ["image", "speech", "transcription", "video"]) {
|
|
11167
|
+
const forModality = usable.filter((c) => c.modality === modality);
|
|
11168
|
+
if (!forModality.length) continue;
|
|
11169
|
+
const shown = forModality.map((c) => {
|
|
11170
|
+
const cost = capabilityCost(c);
|
|
11171
|
+
return `${c.modelId}${cost ? ` (${cost.label})` : ""}`;
|
|
11172
|
+
});
|
|
11173
|
+
lines.push(` ${modality}: ${shown.join(", ")}`);
|
|
11174
|
+
}
|
|
11175
|
+
} else {
|
|
11176
|
+
lines.push("No generation models are available \u2014 none of your configured providers offers one.");
|
|
11177
|
+
}
|
|
11178
|
+
const blocked = CAPABILITIES.filter((c) => c.unsupported && this.available.has(c.provider));
|
|
11179
|
+
for (const c of blocked) {
|
|
11180
|
+
lines.push(` ${c.modality}: ${c.modelId} exists on your account but is not callable. ${c.unsupported}`);
|
|
11181
|
+
}
|
|
11182
|
+
lines.push(
|
|
11183
|
+
" music: no supported provider exposes a music-generation API, so Cascade cannot generate music."
|
|
11184
|
+
);
|
|
11185
|
+
return lines.join("\n");
|
|
11186
|
+
}
|
|
11187
|
+
};
|
|
11188
|
+
|
|
11189
|
+
// src/tools/base.ts
|
|
11190
|
+
var BaseTool = class {
|
|
11191
|
+
workspaceRoot = process.cwd();
|
|
11192
|
+
setWorkspaceRoot(root) {
|
|
11193
|
+
this.workspaceRoot = root;
|
|
11194
|
+
}
|
|
11195
|
+
isDangerous() {
|
|
11196
|
+
return false;
|
|
11197
|
+
}
|
|
11198
|
+
getDefinition() {
|
|
11199
|
+
return {
|
|
11200
|
+
name: this.name,
|
|
11201
|
+
description: this.description,
|
|
11202
|
+
inputSchema: this.inputSchema
|
|
11203
|
+
};
|
|
11204
|
+
}
|
|
11205
|
+
};
|
|
11206
|
+
|
|
10899
11207
|
// src/core/router/provider-errors.ts
|
|
10900
11208
|
var SYSTEMIC = /* @__PURE__ */ new Set([
|
|
10901
11209
|
"rate_limit",
|
|
@@ -10960,7 +11268,7 @@ function classifyProviderError(err) {
|
|
|
10960
11268
|
return { kind, systemic: isSystemicKind(kind), status, raw };
|
|
10961
11269
|
}
|
|
10962
11270
|
function describeProviderError(c, modelId) {
|
|
10963
|
-
const on = ` on ${modelId}` ;
|
|
11271
|
+
const on = modelId ? ` on ${modelId}` : "";
|
|
10964
11272
|
const detail = c.raw ? ` Provider said: ${c.raw}` : "";
|
|
10965
11273
|
switch (c.kind) {
|
|
10966
11274
|
case "rate_limit":
|
|
@@ -10982,6 +11290,417 @@ function describeProviderError(c, modelId) {
|
|
|
10982
11290
|
}
|
|
10983
11291
|
}
|
|
10984
11292
|
|
|
11293
|
+
// src/core/multimodal/generate.ts
|
|
11294
|
+
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
11295
|
+
async function callProvider(fn, modelId) {
|
|
11296
|
+
try {
|
|
11297
|
+
return await fn();
|
|
11298
|
+
} catch (err) {
|
|
11299
|
+
const classified = classifyProviderError(err);
|
|
11300
|
+
throw new Error(describeProviderError(classified, modelId));
|
|
11301
|
+
}
|
|
11302
|
+
}
|
|
11303
|
+
async function postJson(url, body, headers, signal) {
|
|
11304
|
+
const ac = new AbortController();
|
|
11305
|
+
const timer = setTimeout(() => ac.abort(), DEFAULT_TIMEOUT_MS);
|
|
11306
|
+
const onAbort = () => ac.abort();
|
|
11307
|
+
signal?.addEventListener("abort", onAbort);
|
|
11308
|
+
try {
|
|
11309
|
+
const res = await fetch(url, {
|
|
11310
|
+
method: "POST",
|
|
11311
|
+
headers: { "Content-Type": "application/json", ...headers },
|
|
11312
|
+
body: JSON.stringify(body),
|
|
11313
|
+
signal: ac.signal
|
|
11314
|
+
});
|
|
11315
|
+
if (!res.ok) {
|
|
11316
|
+
const text = await res.text().catch(() => "");
|
|
11317
|
+
throw Object.assign(new Error(text || res.statusText), { status: res.status });
|
|
11318
|
+
}
|
|
11319
|
+
return res;
|
|
11320
|
+
} finally {
|
|
11321
|
+
clearTimeout(timer);
|
|
11322
|
+
signal?.removeEventListener("abort", onAbort);
|
|
11323
|
+
}
|
|
11324
|
+
}
|
|
11325
|
+
function baseUrlFor(cfg, fallback) {
|
|
11326
|
+
return (cfg.baseUrl ?? fallback).replace(/\/+$/, "");
|
|
11327
|
+
}
|
|
11328
|
+
function stamp(ext) {
|
|
11329
|
+
return `cascade-${Date.now()}.${ext}`;
|
|
11330
|
+
}
|
|
11331
|
+
async function generateImage(cap, cfg, req, signal) {
|
|
11332
|
+
return callProvider(async () => {
|
|
11333
|
+
if (cap.api === "openai-images") {
|
|
11334
|
+
const url = `${baseUrlFor(cfg, "https://api.openai.com/v1")}/images/generations`;
|
|
11335
|
+
const res = await postJson(url, {
|
|
11336
|
+
model: cap.modelId,
|
|
11337
|
+
prompt: req.prompt,
|
|
11338
|
+
size: req.size ?? "1024x1024",
|
|
11339
|
+
n: 1,
|
|
11340
|
+
// b64 rather than a URL: see the file header — provider URLs expire.
|
|
11341
|
+
response_format: "b64_json"
|
|
11342
|
+
}, { Authorization: `Bearer ${cfg.apiKey ?? ""}` }, signal);
|
|
11343
|
+
const body = await res.json();
|
|
11344
|
+
const first = body.data?.[0];
|
|
11345
|
+
if (!first?.b64_json) throw new Error("Image response contained no image data.");
|
|
11346
|
+
return {
|
|
11347
|
+
data: Buffer.from(first.b64_json, "base64"),
|
|
11348
|
+
mimeType: "image/png",
|
|
11349
|
+
filename: stamp("png"),
|
|
11350
|
+
modelId: cap.modelId,
|
|
11351
|
+
provider: cap.provider,
|
|
11352
|
+
...first.revised_prompt ? { revisedPrompt: first.revised_prompt } : {}
|
|
11353
|
+
};
|
|
11354
|
+
}
|
|
11355
|
+
if (cap.api === "gemini-predict") {
|
|
11356
|
+
const base = baseUrlFor(cfg, "https://generativelanguage.googleapis.com/v1beta");
|
|
11357
|
+
const url = `${base}/models/${encodeURIComponent(cap.modelId)}:predict`;
|
|
11358
|
+
const res = await postJson(url, {
|
|
11359
|
+
instances: [{ prompt: req.prompt }],
|
|
11360
|
+
parameters: { sampleCount: 1 }
|
|
11361
|
+
}, { "x-goog-api-key": cfg.apiKey ?? "" }, signal);
|
|
11362
|
+
const body = await res.json();
|
|
11363
|
+
const first = body.predictions?.[0];
|
|
11364
|
+
if (!first?.bytesBase64Encoded) throw new Error("Image response contained no image data.");
|
|
11365
|
+
const mimeType = first.mimeType ?? "image/png";
|
|
11366
|
+
return {
|
|
11367
|
+
data: Buffer.from(first.bytesBase64Encoded, "base64"),
|
|
11368
|
+
mimeType,
|
|
11369
|
+
filename: stamp(mimeType.includes("jpeg") ? "jpg" : "png"),
|
|
11370
|
+
modelId: cap.modelId,
|
|
11371
|
+
provider: cap.provider
|
|
11372
|
+
};
|
|
11373
|
+
}
|
|
11374
|
+
throw new Error(`${cap.modelId} is not an image generator.`);
|
|
11375
|
+
}, cap.modelId);
|
|
11376
|
+
}
|
|
11377
|
+
async function generateSpeech(cap, cfg, req, signal) {
|
|
11378
|
+
return callProvider(async () => {
|
|
11379
|
+
if (cap.api !== "openai-speech") throw new Error(`${cap.modelId} is not a speech model.`);
|
|
11380
|
+
const format = req.format ?? "mp3";
|
|
11381
|
+
const url = `${baseUrlFor(cfg, "https://api.openai.com/v1")}/audio/speech`;
|
|
11382
|
+
const res = await postJson(url, {
|
|
11383
|
+
model: cap.modelId,
|
|
11384
|
+
input: req.text,
|
|
11385
|
+
voice: req.voice ?? "alloy",
|
|
11386
|
+
response_format: format
|
|
11387
|
+
}, { Authorization: `Bearer ${cfg.apiKey ?? ""}` }, signal);
|
|
11388
|
+
const mime = {
|
|
11389
|
+
mp3: "audio/mpeg",
|
|
11390
|
+
opus: "audio/opus",
|
|
11391
|
+
aac: "audio/aac",
|
|
11392
|
+
flac: "audio/flac",
|
|
11393
|
+
wav: "audio/wav"
|
|
11394
|
+
};
|
|
11395
|
+
return {
|
|
11396
|
+
data: Buffer.from(await res.arrayBuffer()),
|
|
11397
|
+
mimeType: mime[format] ?? "application/octet-stream",
|
|
11398
|
+
filename: stamp(format),
|
|
11399
|
+
modelId: cap.modelId,
|
|
11400
|
+
provider: cap.provider
|
|
11401
|
+
};
|
|
11402
|
+
}, cap.modelId);
|
|
11403
|
+
}
|
|
11404
|
+
var VIDEO_POLL_INTERVAL_MS = 1e4;
|
|
11405
|
+
var VIDEO_TIMEOUT_MS = 8 * 6e4;
|
|
11406
|
+
function sleep(ms, signal) {
|
|
11407
|
+
return new Promise((resolve2, reject) => {
|
|
11408
|
+
if (signal?.aborted) return reject(new Error("Cancelled."));
|
|
11409
|
+
const timer = setTimeout(() => {
|
|
11410
|
+
signal?.removeEventListener("abort", onAbort);
|
|
11411
|
+
resolve2();
|
|
11412
|
+
}, ms);
|
|
11413
|
+
function onAbort() {
|
|
11414
|
+
clearTimeout(timer);
|
|
11415
|
+
reject(new Error("Cancelled."));
|
|
11416
|
+
}
|
|
11417
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
11418
|
+
});
|
|
11419
|
+
}
|
|
11420
|
+
async function generateVideo(cap, cfg, req, signal, onProgress) {
|
|
11421
|
+
return callProvider(async () => {
|
|
11422
|
+
if (cap.api !== "gemini-predict-lro") throw new Error(`${cap.modelId} is not a video generator.`);
|
|
11423
|
+
const base = baseUrlFor(cfg, "https://generativelanguage.googleapis.com/v1beta");
|
|
11424
|
+
const key = cfg.apiKey ?? "";
|
|
11425
|
+
const headers = { "x-goog-api-key": key };
|
|
11426
|
+
const submit = await postJson(
|
|
11427
|
+
`${base}/models/${encodeURIComponent(cap.modelId)}:predictLongRunning`,
|
|
11428
|
+
{
|
|
11429
|
+
instances: [{ prompt: req.prompt }],
|
|
11430
|
+
parameters: {
|
|
11431
|
+
aspectRatio: req.aspectRatio ?? "16:9",
|
|
11432
|
+
...req.seconds ? { durationSeconds: req.seconds } : {}
|
|
11433
|
+
}
|
|
11434
|
+
},
|
|
11435
|
+
headers,
|
|
11436
|
+
signal
|
|
11437
|
+
);
|
|
11438
|
+
const started = await submit.json();
|
|
11439
|
+
if (!started.name) throw new Error("Video request was accepted but returned no operation to poll.");
|
|
11440
|
+
onProgress?.(`Video generation started (${cap.modelId}). This usually takes 1-3 minutes.`);
|
|
11441
|
+
const deadline = Date.now() + VIDEO_TIMEOUT_MS;
|
|
11442
|
+
let operation = {};
|
|
11443
|
+
while (Date.now() < deadline) {
|
|
11444
|
+
await sleep(VIDEO_POLL_INTERVAL_MS, signal);
|
|
11445
|
+
const poll = await fetch(`${base}/${started.name}`, { headers, signal });
|
|
11446
|
+
if (!poll.ok) {
|
|
11447
|
+
const text = await poll.text().catch(() => "");
|
|
11448
|
+
throw Object.assign(new Error(text || poll.statusText), { status: poll.status });
|
|
11449
|
+
}
|
|
11450
|
+
operation = await poll.json();
|
|
11451
|
+
if (operation.done) break;
|
|
11452
|
+
onProgress?.("Still rendering\u2026");
|
|
11453
|
+
}
|
|
11454
|
+
if (!operation.done) {
|
|
11455
|
+
throw new Error(
|
|
11456
|
+
`Video was still rendering after ${Math.round(VIDEO_TIMEOUT_MS / 6e4)} minutes. It may still complete on the provider side, but Cascade stopped waiting.`
|
|
11457
|
+
);
|
|
11458
|
+
}
|
|
11459
|
+
if (operation.error?.message) throw new Error(operation.error.message);
|
|
11460
|
+
const uri = operation.response?.generateVideoResponse?.generatedSamples?.[0]?.video?.uri;
|
|
11461
|
+
if (!uri) throw new Error("Video finished but the response contained no video.");
|
|
11462
|
+
const media = await fetch(uri, { headers, signal });
|
|
11463
|
+
if (!media.ok) throw new Error(`Video finished but could not be downloaded (HTTP ${media.status}).`);
|
|
11464
|
+
return {
|
|
11465
|
+
data: Buffer.from(await media.arrayBuffer()),
|
|
11466
|
+
mimeType: "video/mp4",
|
|
11467
|
+
filename: stamp("mp4"),
|
|
11468
|
+
modelId: cap.modelId,
|
|
11469
|
+
provider: cap.provider
|
|
11470
|
+
};
|
|
11471
|
+
}, cap.modelId);
|
|
11472
|
+
}
|
|
11473
|
+
async function transcribeAudio(cap, cfg, req, signal) {
|
|
11474
|
+
return callProvider(async () => {
|
|
11475
|
+
if (cap.api !== "openai-transcriptions") throw new Error(`${cap.modelId} is not a transcription model.`);
|
|
11476
|
+
const url = `${baseUrlFor(cfg, "https://api.openai.com/v1")}/audio/transcriptions`;
|
|
11477
|
+
const form = new FormData();
|
|
11478
|
+
form.append("model", cap.modelId);
|
|
11479
|
+
if (req.language) form.append("language", req.language);
|
|
11480
|
+
form.append("file", new Blob([new Uint8Array(req.audio)]), req.filename);
|
|
11481
|
+
const ac = new AbortController();
|
|
11482
|
+
const timer = setTimeout(() => ac.abort(), DEFAULT_TIMEOUT_MS);
|
|
11483
|
+
const onAbort = () => ac.abort();
|
|
11484
|
+
signal?.addEventListener("abort", onAbort);
|
|
11485
|
+
try {
|
|
11486
|
+
const res = await fetch(url, {
|
|
11487
|
+
method: "POST",
|
|
11488
|
+
headers: { Authorization: `Bearer ${cfg.apiKey ?? ""}` },
|
|
11489
|
+
body: form,
|
|
11490
|
+
signal: ac.signal
|
|
11491
|
+
});
|
|
11492
|
+
if (!res.ok) {
|
|
11493
|
+
const text = await res.text().catch(() => "");
|
|
11494
|
+
throw Object.assign(new Error(text || res.statusText), { status: res.status });
|
|
11495
|
+
}
|
|
11496
|
+
const body = await res.json();
|
|
11497
|
+
return { text: body.text ?? "", modelId: cap.modelId };
|
|
11498
|
+
} finally {
|
|
11499
|
+
clearTimeout(timer);
|
|
11500
|
+
signal?.removeEventListener("abort", onAbort);
|
|
11501
|
+
}
|
|
11502
|
+
}, cap.modelId);
|
|
11503
|
+
}
|
|
11504
|
+
|
|
11505
|
+
// src/tools/generate-media.ts
|
|
11506
|
+
function resolve(deps, modality, preferModel) {
|
|
11507
|
+
const selected = deps.registry.select(modality, preferModel ? { preferModel } : {});
|
|
11508
|
+
if (!selected) {
|
|
11509
|
+
return { ok: false, error: `No ${modality} model is available on your configured providers.` };
|
|
11510
|
+
}
|
|
11511
|
+
const cfg = deps.lookupProvider(selected.capability.provider);
|
|
11512
|
+
if (!cfg) {
|
|
11513
|
+
return {
|
|
11514
|
+
ok: false,
|
|
11515
|
+
error: `${selected.capability.modelId} needs the ${selected.capability.provider} provider, which is not configured.`
|
|
11516
|
+
};
|
|
11517
|
+
}
|
|
11518
|
+
return { ok: true, selected, cfg };
|
|
11519
|
+
}
|
|
11520
|
+
var GenerateImageTool = class extends BaseTool {
|
|
11521
|
+
constructor(deps) {
|
|
11522
|
+
super();
|
|
11523
|
+
this.deps = deps;
|
|
11524
|
+
}
|
|
11525
|
+
deps;
|
|
11526
|
+
name = "generate_image";
|
|
11527
|
+
description = "Generate an image from a text description using the best available image model. Returns where the image was saved. Use when the user asks for a picture, illustration, diagram render, logo or any visual asset.";
|
|
11528
|
+
inputSchema = {
|
|
11529
|
+
type: "object",
|
|
11530
|
+
properties: {
|
|
11531
|
+
prompt: { type: "string", description: "What the image should show. Be specific about subject, style and composition." },
|
|
11532
|
+
size: { type: "string", description: 'Pixel size, e.g. "1024x1024" (default), "1792x1024", "1024x1792".' },
|
|
11533
|
+
model: { type: "string", description: "Optional: force a specific image model instead of the automatic choice." }
|
|
11534
|
+
},
|
|
11535
|
+
required: ["prompt"]
|
|
11536
|
+
};
|
|
11537
|
+
async execute(input, options) {
|
|
11538
|
+
const prompt = String(input["prompt"] ?? "").trim();
|
|
11539
|
+
if (!prompt) return 'Provide a "prompt" describing the image.';
|
|
11540
|
+
const r = resolve(this.deps, "image", input["model"] ? String(input["model"]) : void 0);
|
|
11541
|
+
if (!r.ok) return r.error;
|
|
11542
|
+
const size = input["size"] ? String(input["size"]) : void 0;
|
|
11543
|
+
const asset = await generateImage(
|
|
11544
|
+
r.selected.capability,
|
|
11545
|
+
r.cfg,
|
|
11546
|
+
{ prompt, ...size ? { size } : {} },
|
|
11547
|
+
options.signal
|
|
11548
|
+
);
|
|
11549
|
+
const location = await this.deps.sink(asset);
|
|
11550
|
+
return [
|
|
11551
|
+
`Image generated and saved to ${location}.`,
|
|
11552
|
+
`Model: ${asset.modelId} \u2014 ${r.selected.reason}`,
|
|
11553
|
+
// DALL·E rewrites the prompt before drawing; the model needs to know what
|
|
11554
|
+
// was actually rendered, not what it asked for, or its description of the
|
|
11555
|
+
// result will not match the image.
|
|
11556
|
+
asset.revisedPrompt ? `The provider rewrote the prompt to: ${asset.revisedPrompt}` : ""
|
|
11557
|
+
].filter(Boolean).join("\n");
|
|
11558
|
+
}
|
|
11559
|
+
};
|
|
11560
|
+
var GenerateSpeechTool = class extends BaseTool {
|
|
11561
|
+
constructor(deps) {
|
|
11562
|
+
super();
|
|
11563
|
+
this.deps = deps;
|
|
11564
|
+
}
|
|
11565
|
+
deps;
|
|
11566
|
+
name = "generate_speech";
|
|
11567
|
+
description = "Turn text into spoken audio using the best available text-to-speech model. Returns where the audio was saved. Use when the user asks for narration, a voiceover or an audio version of some text.";
|
|
11568
|
+
inputSchema = {
|
|
11569
|
+
type: "object",
|
|
11570
|
+
properties: {
|
|
11571
|
+
text: { type: "string", description: "The text to speak." },
|
|
11572
|
+
voice: { type: "string", description: "Voice id: alloy, echo, fable, onyx, nova, shimmer. Default alloy." },
|
|
11573
|
+
format: { type: "string", description: "Audio format: mp3 (default), opus, aac, flac, wav." },
|
|
11574
|
+
model: { type: "string", description: "Optional: force a specific speech model." }
|
|
11575
|
+
},
|
|
11576
|
+
required: ["text"]
|
|
11577
|
+
};
|
|
11578
|
+
async execute(input, options) {
|
|
11579
|
+
const text = String(input["text"] ?? "").trim();
|
|
11580
|
+
if (!text) return 'Provide "text" to speak.';
|
|
11581
|
+
const r = resolve(this.deps, "speech", input["model"] ? String(input["model"]) : void 0);
|
|
11582
|
+
if (!r.ok) return r.error;
|
|
11583
|
+
const fmt = String(input["format"] ?? "mp3");
|
|
11584
|
+
const allowed = ["mp3", "opus", "aac", "flac", "wav"];
|
|
11585
|
+
const format = allowed.includes(fmt) ? fmt : "mp3";
|
|
11586
|
+
const asset = await generateSpeech(
|
|
11587
|
+
r.selected.capability,
|
|
11588
|
+
r.cfg,
|
|
11589
|
+
{ text, voice: input["voice"] ? String(input["voice"]) : void 0, format },
|
|
11590
|
+
options.signal
|
|
11591
|
+
);
|
|
11592
|
+
const location = await this.deps.sink(asset);
|
|
11593
|
+
return `Speech generated and saved to ${location}.
|
|
11594
|
+
Model: ${asset.modelId} \u2014 ${r.selected.reason}`;
|
|
11595
|
+
}
|
|
11596
|
+
};
|
|
11597
|
+
var DEFAULT_VIDEO_SECONDS = 5;
|
|
11598
|
+
var MAX_VIDEO_SECONDS = 8;
|
|
11599
|
+
var GenerateVideoTool = class extends BaseTool {
|
|
11600
|
+
constructor(deps) {
|
|
11601
|
+
super();
|
|
11602
|
+
this.deps = deps;
|
|
11603
|
+
}
|
|
11604
|
+
deps;
|
|
11605
|
+
name = "generate_video";
|
|
11606
|
+
// The price is in the description on purpose. Video is ~1000x the cost of an
|
|
11607
|
+
// image per call, it is billed per SECOND, and the model choosing the
|
|
11608
|
+
// duration is the one deciding the bill — so the number has to be where the
|
|
11609
|
+
// model reads before calling, not only in the result afterwards.
|
|
11610
|
+
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.";
|
|
11611
|
+
inputSchema = {
|
|
11612
|
+
type: "object",
|
|
11613
|
+
properties: {
|
|
11614
|
+
prompt: { type: "string", description: "What the video should show: subject, action, camera movement, style." },
|
|
11615
|
+
seconds: { type: "number", description: `Clip length, 1-${MAX_VIDEO_SECONDS} seconds (default ${DEFAULT_VIDEO_SECONDS}). Billed per second \u2014 keep it short.` },
|
|
11616
|
+
aspectRatio: { type: "string", description: 'Frame shape: "16:9" (default) or "9:16" for vertical.' },
|
|
11617
|
+
model: { type: "string", description: "Optional: force a specific video model." }
|
|
11618
|
+
},
|
|
11619
|
+
required: ["prompt"]
|
|
11620
|
+
};
|
|
11621
|
+
async execute(input, options) {
|
|
11622
|
+
const prompt = String(input["prompt"] ?? "").trim();
|
|
11623
|
+
if (!prompt) return 'Provide a "prompt" describing the video.';
|
|
11624
|
+
const r = resolve(this.deps, "video", input["model"] ? String(input["model"]) : void 0);
|
|
11625
|
+
if (!r.ok) return r.error;
|
|
11626
|
+
const asked = typeof input["seconds"] === "number" ? input["seconds"] : DEFAULT_VIDEO_SECONDS;
|
|
11627
|
+
const seconds = Math.max(1, Math.min(MAX_VIDEO_SECONDS, Math.round(asked)));
|
|
11628
|
+
const asset = await generateVideo(
|
|
11629
|
+
r.selected.capability,
|
|
11630
|
+
r.cfg,
|
|
11631
|
+
{
|
|
11632
|
+
prompt,
|
|
11633
|
+
seconds,
|
|
11634
|
+
...input["aspectRatio"] ? { aspectRatio: String(input["aspectRatio"]) } : {}
|
|
11635
|
+
},
|
|
11636
|
+
options.signal,
|
|
11637
|
+
// Progress goes to the run log so a two-minute wait doesn't look like a
|
|
11638
|
+
// hang — the single most likely reason someone kills a working render.
|
|
11639
|
+
(note) => options.onProgress?.(note)
|
|
11640
|
+
);
|
|
11641
|
+
const location = await this.deps.sink(asset);
|
|
11642
|
+
const unit = r.selected.cost;
|
|
11643
|
+
const estimate = unit ? ` Estimated cost: $${(unit.amount * seconds).toFixed(2)} (${seconds}s x ${unit.label}).` : "";
|
|
11644
|
+
const clamped = seconds !== Math.round(asked) ? ` Duration was clamped to ${seconds}s (max ${MAX_VIDEO_SECONDS}).` : "";
|
|
11645
|
+
return `Video generated and saved to ${location}.
|
|
11646
|
+
Model: ${asset.modelId}.${estimate}${clamped}`;
|
|
11647
|
+
}
|
|
11648
|
+
};
|
|
11649
|
+
var TranscribeAudioTool = class extends BaseTool {
|
|
11650
|
+
constructor(deps, readFile) {
|
|
11651
|
+
super();
|
|
11652
|
+
this.deps = deps;
|
|
11653
|
+
this.readFile = readFile;
|
|
11654
|
+
}
|
|
11655
|
+
deps;
|
|
11656
|
+
readFile;
|
|
11657
|
+
name = "transcribe_audio";
|
|
11658
|
+
description = "Transcribe a local audio file to text using the best available speech-to-text model. Use when the user supplies audio and wants its contents in writing.";
|
|
11659
|
+
inputSchema = {
|
|
11660
|
+
type: "object",
|
|
11661
|
+
properties: {
|
|
11662
|
+
path: { type: "string", description: "Path to the audio file to transcribe." },
|
|
11663
|
+
language: { type: "string", description: 'Optional ISO-639-1 language hint, e.g. "en", improves accuracy.' },
|
|
11664
|
+
model: { type: "string", description: "Optional: force a specific transcription model." }
|
|
11665
|
+
},
|
|
11666
|
+
required: ["path"]
|
|
11667
|
+
};
|
|
11668
|
+
async execute(input, options) {
|
|
11669
|
+
const filePath = String(input["path"] ?? "").trim();
|
|
11670
|
+
if (!filePath) return 'Provide a "path" to the audio file.';
|
|
11671
|
+
const r = resolve(this.deps, "transcription", input["model"] ? String(input["model"]) : void 0);
|
|
11672
|
+
if (!r.ok) return r.error;
|
|
11673
|
+
let audio;
|
|
11674
|
+
try {
|
|
11675
|
+
audio = await this.readFile(filePath);
|
|
11676
|
+
} catch {
|
|
11677
|
+
return `Could not read the audio file at ${filePath}.`;
|
|
11678
|
+
}
|
|
11679
|
+
const { text, modelId } = await transcribeAudio(
|
|
11680
|
+
r.selected.capability,
|
|
11681
|
+
r.cfg,
|
|
11682
|
+
{
|
|
11683
|
+
audio,
|
|
11684
|
+
filename: filePath.split(/[\\/]/).pop() || "audio",
|
|
11685
|
+
...input["language"] ? { language: String(input["language"]) } : {}
|
|
11686
|
+
},
|
|
11687
|
+
options.signal
|
|
11688
|
+
);
|
|
11689
|
+
return text ? `Transcript (${modelId}):
|
|
11690
|
+
|
|
11691
|
+
${text}` : `${modelId} returned an empty transcript \u2014 the file may be silent or in an unsupported format.`;
|
|
11692
|
+
}
|
|
11693
|
+
};
|
|
11694
|
+
function buildMediaTools(deps, readFile) {
|
|
11695
|
+
const tools = [];
|
|
11696
|
+
const modalities = new Set(deps.registry.availableModalities());
|
|
11697
|
+
if (modalities.has("image")) tools.push(new GenerateImageTool(deps));
|
|
11698
|
+
if (modalities.has("speech")) tools.push(new GenerateSpeechTool(deps));
|
|
11699
|
+
if (modalities.has("video")) tools.push(new GenerateVideoTool(deps));
|
|
11700
|
+
if (modalities.has("transcription") && readFile) tools.push(new TranscribeAudioTool(deps, readFile));
|
|
11701
|
+
return tools;
|
|
11702
|
+
}
|
|
11703
|
+
|
|
10985
11704
|
// src/core/run-breaker.ts
|
|
10986
11705
|
var DEFAULT_FAILURE_THRESHOLD = 3;
|
|
10987
11706
|
var RunBreaker = class {
|
|
@@ -11050,26 +11769,6 @@ var RunBreaker = class {
|
|
|
11050
11769
|
|
|
11051
11770
|
// src/tools/registry.ts
|
|
11052
11771
|
init_constants();
|
|
11053
|
-
|
|
11054
|
-
// src/tools/base.ts
|
|
11055
|
-
var BaseTool = class {
|
|
11056
|
-
workspaceRoot = process.cwd();
|
|
11057
|
-
setWorkspaceRoot(root) {
|
|
11058
|
-
this.workspaceRoot = root;
|
|
11059
|
-
}
|
|
11060
|
-
isDangerous() {
|
|
11061
|
-
return false;
|
|
11062
|
-
}
|
|
11063
|
-
getDefinition() {
|
|
11064
|
-
return {
|
|
11065
|
-
name: this.name,
|
|
11066
|
-
description: this.description,
|
|
11067
|
-
inputSchema: this.inputSchema
|
|
11068
|
-
};
|
|
11069
|
-
}
|
|
11070
|
-
};
|
|
11071
|
-
|
|
11072
|
-
// src/tools/shell.ts
|
|
11073
11772
|
var execAsync = util.promisify(child_process.exec);
|
|
11074
11773
|
var ShellTool = class extends BaseTool {
|
|
11075
11774
|
name = "shell";
|
|
@@ -11765,7 +12464,7 @@ var PDFCreateTool = class extends BaseTool {
|
|
|
11765
12464
|
if (!fs29__default.default.existsSync(dir)) {
|
|
11766
12465
|
fs29__default.default.mkdirSync(dir, { recursive: true });
|
|
11767
12466
|
}
|
|
11768
|
-
return new Promise((
|
|
12467
|
+
return new Promise((resolve2, reject) => {
|
|
11769
12468
|
try {
|
|
11770
12469
|
const doc = new PDFDocument__default.default({ margin: 50 });
|
|
11771
12470
|
const stream = fs29__default.default.createWriteStream(filePath);
|
|
@@ -11783,7 +12482,7 @@ var PDFCreateTool = class extends BaseTool {
|
|
|
11783
12482
|
});
|
|
11784
12483
|
doc.end();
|
|
11785
12484
|
stream.on("finish", () => {
|
|
11786
|
-
|
|
12485
|
+
resolve2(`Successfully created PDF at ${filePath} (${content.length} characters)`);
|
|
11787
12486
|
});
|
|
11788
12487
|
stream.on("error", (err) => {
|
|
11789
12488
|
reject(new Error(`Failed to write PDF: ${err.message}`));
|
|
@@ -11855,7 +12554,7 @@ var CodeInterpreterTool = class extends BaseTool {
|
|
|
11855
12554
|
const filePath = path32__default.default.join(tmpDir, fileName);
|
|
11856
12555
|
fs29__default.default.writeFileSync(filePath, code, "utf-8");
|
|
11857
12556
|
const execArgs = [filePath, ...args];
|
|
11858
|
-
return new Promise((
|
|
12557
|
+
return new Promise((resolve2) => {
|
|
11859
12558
|
const startMs = Date.now();
|
|
11860
12559
|
child_process.execFile(cmdPrefix, execArgs, { cwd: this.workspaceRoot, timeout: 3e4 }, (error, stdout, stderr) => {
|
|
11861
12560
|
const duration = Date.now() - startMs;
|
|
@@ -11869,17 +12568,17 @@ var CodeInterpreterTool = class extends BaseTool {
|
|
|
11869
12568
|
if (error) {
|
|
11870
12569
|
const timedOut = error.killed && duration >= 3e4;
|
|
11871
12570
|
if (timedOut) {
|
|
11872
|
-
|
|
12571
|
+
resolve2(`Execution timed out after 30s. Consider breaking the task into smaller pieces.
|
|
11873
12572
|
Partial stdout: ${stdout}
|
|
11874
12573
|
Stderr: ${stderr}`);
|
|
11875
12574
|
} else {
|
|
11876
|
-
|
|
12575
|
+
resolve2(`Execution failed (${duration}ms):
|
|
11877
12576
|
Error: ${error.message}
|
|
11878
12577
|
Stderr: ${stderr}
|
|
11879
12578
|
Stdout: ${stdout}`);
|
|
11880
12579
|
}
|
|
11881
12580
|
} else {
|
|
11882
|
-
|
|
12581
|
+
resolve2(`Execution successful (${duration}ms):
|
|
11883
12582
|
Stdout: ${stdout}
|
|
11884
12583
|
Stderr: ${stderr}`);
|
|
11885
12584
|
}
|
|
@@ -12533,7 +13232,7 @@ var ToolRegistry = class extends EventEmitter__default.default {
|
|
|
12533
13232
|
*/
|
|
12534
13233
|
waitForTool(toolName, timeoutMs = 6e4) {
|
|
12535
13234
|
if (this.tools.has(toolName)) return Promise.resolve();
|
|
12536
|
-
return new Promise((
|
|
13235
|
+
return new Promise((resolve2, reject) => {
|
|
12537
13236
|
const timer = setTimeout(() => {
|
|
12538
13237
|
this.off("tool:added", handler);
|
|
12539
13238
|
reject(new Error(`Timeout waiting for tool: ${toolName}`));
|
|
@@ -12542,7 +13241,7 @@ var ToolRegistry = class extends EventEmitter__default.default {
|
|
|
12542
13241
|
if (name === toolName) {
|
|
12543
13242
|
clearTimeout(timer);
|
|
12544
13243
|
this.off("tool:added", handler);
|
|
12545
|
-
|
|
13244
|
+
resolve2();
|
|
12546
13245
|
}
|
|
12547
13246
|
};
|
|
12548
13247
|
this.on("tool:added", handler);
|
|
@@ -13195,20 +13894,20 @@ var PermissionEscalator = class extends EventEmitter__default.default {
|
|
|
13195
13894
|
resolver(decision);
|
|
13196
13895
|
}
|
|
13197
13896
|
waitForUserDecision(req) {
|
|
13198
|
-
return new Promise((
|
|
13897
|
+
return new Promise((resolve2) => {
|
|
13199
13898
|
let timer;
|
|
13200
13899
|
const wrappedResolver = (decision) => {
|
|
13201
13900
|
if (timer) clearTimeout(timer);
|
|
13202
13901
|
if (decision.always) {
|
|
13203
13902
|
this.taskWideCache.set(req.toolName, decision.approved);
|
|
13204
13903
|
}
|
|
13205
|
-
|
|
13904
|
+
resolve2(decision);
|
|
13206
13905
|
};
|
|
13207
13906
|
this.pendingUserDecisions.set(req.id, wrappedResolver);
|
|
13208
13907
|
if (this.approvalTimeoutMs > 0 && Number.isFinite(this.approvalTimeoutMs)) {
|
|
13209
13908
|
timer = setTimeout(() => {
|
|
13210
13909
|
if (this.pendingUserDecisions.delete(req.id)) {
|
|
13211
|
-
|
|
13910
|
+
resolve2({
|
|
13212
13911
|
requestId: req.id,
|
|
13213
13912
|
approved: false,
|
|
13214
13913
|
decidedBy: "USER",
|
|
@@ -13276,6 +13975,23 @@ var noopTelemetry = {
|
|
|
13276
13975
|
}
|
|
13277
13976
|
};
|
|
13278
13977
|
|
|
13978
|
+
// src/core/router/feedback-prior.ts
|
|
13979
|
+
var MAX_FEEDBACK_ADJUSTMENT = 0.05;
|
|
13980
|
+
var PRIOR_STRENGTH = 10;
|
|
13981
|
+
function feedbackAdjustment(counts) {
|
|
13982
|
+
const good = Math.max(0, Math.floor(counts.good));
|
|
13983
|
+
const bad = Math.max(0, Math.floor(counts.bad));
|
|
13984
|
+
const n = good + bad;
|
|
13985
|
+
if (n === 0) return 0;
|
|
13986
|
+
const half = PRIOR_STRENGTH / 2;
|
|
13987
|
+
const shrunk = (good + half) / (n + PRIOR_STRENGTH);
|
|
13988
|
+
return (shrunk - 0.5) * 2 * MAX_FEEDBACK_ADJUSTMENT;
|
|
13989
|
+
}
|
|
13990
|
+
function applyFeedback(score01, counts) {
|
|
13991
|
+
const adjusted = score01 + feedbackAdjustment(counts);
|
|
13992
|
+
return Math.max(0, Math.min(1, adjusted));
|
|
13993
|
+
}
|
|
13994
|
+
|
|
13279
13995
|
// src/core/router/task-analyzer.ts
|
|
13280
13996
|
init_pricing();
|
|
13281
13997
|
var CODE_SIGNALS = [
|
|
@@ -13351,6 +14067,8 @@ var TASK_TYPE_TAGS = {
|
|
|
13351
14067
|
};
|
|
13352
14068
|
var TaskAnalyzer = class {
|
|
13353
14069
|
tracker;
|
|
14070
|
+
/** Per-model thumbs counts, when the host collects them. */
|
|
14071
|
+
feedback;
|
|
13354
14072
|
bias;
|
|
13355
14073
|
lastProfile = null;
|
|
13356
14074
|
lastSelectedModels = /* @__PURE__ */ new Map();
|
|
@@ -13358,6 +14076,14 @@ var TaskAnalyzer = class {
|
|
|
13358
14076
|
this.tracker = tracker;
|
|
13359
14077
|
this.bias = bias;
|
|
13360
14078
|
}
|
|
14079
|
+
/**
|
|
14080
|
+
* Supply per-model rating counts. Optional by design: a host that collects no
|
|
14081
|
+
* feedback (the CLI, a fresh install) scores exactly as it did before, since
|
|
14082
|
+
* feedbackAdjustment returns 0 for an empty record.
|
|
14083
|
+
*/
|
|
14084
|
+
setFeedbackSource(source) {
|
|
14085
|
+
this.feedback = source;
|
|
14086
|
+
}
|
|
13361
14087
|
setTracker(tracker) {
|
|
13362
14088
|
this.tracker = tracker;
|
|
13363
14089
|
}
|
|
@@ -13441,7 +14167,10 @@ var TaskAnalyzer = class {
|
|
|
13441
14167
|
const perf = this.tracker?.performanceScore(model.id, profile.type) ?? 0.5;
|
|
13442
14168
|
const costEff = this.costEfficiency(model, profile.complexity);
|
|
13443
14169
|
const match = this.taskMatchScore(model, profile);
|
|
13444
|
-
const
|
|
14170
|
+
const rated = this.feedback?.(model.id);
|
|
14171
|
+
const publicScore = benchmarkScore01(model, profile.type);
|
|
14172
|
+
const adjusted = rated ? applyFeedback(publicScore, rated) : publicScore;
|
|
14173
|
+
const benchmark = 0.3 + 0.7 * adjusted;
|
|
13445
14174
|
switch (this.bias) {
|
|
13446
14175
|
case "quality":
|
|
13447
14176
|
return perf * match * benchmark ** 2 * (0.85 + 0.15 * costEff);
|
|
@@ -13846,10 +14575,10 @@ var DynamicTool = class extends BaseTool {
|
|
|
13846
14575
|
`;
|
|
13847
14576
|
const script = await isolate.compileScript(bootstrap);
|
|
13848
14577
|
const runPromise = script.run(context, { timeout: timeoutMs, promise: true }).then((v) => String(v ?? ""));
|
|
13849
|
-
const timeoutPromise = new Promise((
|
|
14578
|
+
const timeoutPromise = new Promise((resolve2) => {
|
|
13850
14579
|
const t = setTimeout(() => {
|
|
13851
14580
|
dispose();
|
|
13852
|
-
|
|
14581
|
+
resolve2(`Dynamic tool "${this.name}" timed out after ${timeoutMs}ms and was terminated.`);
|
|
13853
14582
|
}, timeoutMs + 500);
|
|
13854
14583
|
t.unref?.();
|
|
13855
14584
|
});
|
|
@@ -13867,7 +14596,7 @@ var DynamicTool = class extends BaseTool {
|
|
|
13867
14596
|
/** Spawn the worker, service its callTool/fetch bridge, enforce the kill timeout. */
|
|
13868
14597
|
runInWorker(input, callTool) {
|
|
13869
14598
|
const timeoutMs = Math.max(200, Number(process.env["CASCADE_DYNAMIC_TOOL_TIMEOUT_MS"]) || DYNAMIC_TOOL_TIMEOUT_MS);
|
|
13870
|
-
return new Promise((
|
|
14599
|
+
return new Promise((resolve2) => {
|
|
13871
14600
|
let settled = false;
|
|
13872
14601
|
const worker = new worker_threads.Worker(HARNESS_SRC, {
|
|
13873
14602
|
eval: true,
|
|
@@ -13879,7 +14608,7 @@ var DynamicTool = class extends BaseTool {
|
|
|
13879
14608
|
settled = true;
|
|
13880
14609
|
clearTimeout(timer);
|
|
13881
14610
|
void worker.terminate();
|
|
13882
|
-
|
|
14611
|
+
resolve2(value);
|
|
13883
14612
|
};
|
|
13884
14613
|
const timer = setTimeout(
|
|
13885
14614
|
() => finish(`Dynamic tool "${this.name}" timed out after ${timeoutMs}ms and was terminated.`),
|
|
@@ -15386,6 +16115,8 @@ ${prompt}`;
|
|
|
15386
16115
|
var Cascade = class _Cascade extends EventEmitter__default.default {
|
|
15387
16116
|
router;
|
|
15388
16117
|
toolRegistry;
|
|
16118
|
+
multimodal;
|
|
16119
|
+
mediaSink;
|
|
15389
16120
|
mcpClient;
|
|
15390
16121
|
codeIndex;
|
|
15391
16122
|
config;
|
|
@@ -15424,6 +16155,10 @@ var Cascade = class _Cascade extends EventEmitter__default.default {
|
|
|
15424
16155
|
}
|
|
15425
16156
|
});
|
|
15426
16157
|
this.toolRegistry = new ToolRegistry(this.config.tools, workspacePath);
|
|
16158
|
+
this.multimodal = new MultimodalRegistry(
|
|
16159
|
+
(this.config.providers ?? []).map((p) => p.type)
|
|
16160
|
+
);
|
|
16161
|
+
this.registerMediaTools(workspacePath);
|
|
15427
16162
|
this.telemetry = config.telemetry?.enabled ? new Telemetry(config.telemetry, config.telemetry.distinctId ?? "anonymous") : noopTelemetry;
|
|
15428
16163
|
this.worldStateDB = new WorldStateDB(this.workspacePath, this.config.workspace?.debugWorldState ?? false);
|
|
15429
16164
|
this.router.setWorldStateDB(this.worldStateDB);
|
|
@@ -15477,14 +16212,14 @@ var Cascade = class _Cascade extends EventEmitter__default.default {
|
|
|
15477
16212
|
if (this.listenerCount("mcp:approval-required") === 0) {
|
|
15478
16213
|
return false;
|
|
15479
16214
|
}
|
|
15480
|
-
return await new Promise((
|
|
15481
|
-
this.pendingMcpApprovals.set(server.name,
|
|
16215
|
+
return await new Promise((resolve2) => {
|
|
16216
|
+
this.pendingMcpApprovals.set(server.name, resolve2);
|
|
15482
16217
|
const timeout = setTimeout(() => {
|
|
15483
|
-
if (this.pendingMcpApprovals.delete(server.name))
|
|
16218
|
+
if (this.pendingMcpApprovals.delete(server.name)) resolve2(false);
|
|
15484
16219
|
}, 3e4);
|
|
15485
16220
|
const wrap = (approved) => {
|
|
15486
16221
|
clearTimeout(timeout);
|
|
15487
|
-
|
|
16222
|
+
resolve2(approved);
|
|
15488
16223
|
};
|
|
15489
16224
|
this.pendingMcpApprovals.set(server.name, wrap);
|
|
15490
16225
|
this.emit("mcp:approval-required", { server });
|
|
@@ -15558,17 +16293,17 @@ var Cascade = class _Cascade extends EventEmitter__default.default {
|
|
|
15558
16293
|
}
|
|
15559
16294
|
const t2Count = plan.sections.length;
|
|
15560
16295
|
const t3Count = plan.sections.reduce((sum, s) => sum + (s.t3Subtasks?.length ?? 0), 0);
|
|
15561
|
-
return await new Promise((
|
|
16296
|
+
return await new Promise((resolve2) => {
|
|
15562
16297
|
const timeout = setTimeout(() => {
|
|
15563
16298
|
if (this.pendingPlanApproval) {
|
|
15564
16299
|
this.pendingPlanApproval = void 0;
|
|
15565
|
-
|
|
16300
|
+
resolve2({ approved: true });
|
|
15566
16301
|
}
|
|
15567
16302
|
}, 12e4);
|
|
15568
16303
|
this.pendingPlanApproval = (decision) => {
|
|
15569
16304
|
clearTimeout(timeout);
|
|
15570
16305
|
this.pendingPlanApproval = void 0;
|
|
15571
|
-
|
|
16306
|
+
resolve2(decision);
|
|
15572
16307
|
};
|
|
15573
16308
|
this.emit("plan:approval-required", {
|
|
15574
16309
|
taskId,
|
|
@@ -15601,17 +16336,17 @@ var Cascade = class _Cascade extends EventEmitter__default.default {
|
|
|
15601
16336
|
}
|
|
15602
16337
|
async requestContextApproval(info) {
|
|
15603
16338
|
if (this.listenerCount("context:approval-required") === 0) return true;
|
|
15604
|
-
return await new Promise((
|
|
16339
|
+
return await new Promise((resolve2) => {
|
|
15605
16340
|
const timeout = setTimeout(() => {
|
|
15606
16341
|
if (this.pendingContextApproval) {
|
|
15607
16342
|
this.pendingContextApproval = void 0;
|
|
15608
|
-
|
|
16343
|
+
resolve2(true);
|
|
15609
16344
|
}
|
|
15610
16345
|
}, 12e4);
|
|
15611
16346
|
this.pendingContextApproval = (approved) => {
|
|
15612
16347
|
clearTimeout(timeout);
|
|
15613
16348
|
this.pendingContextApproval = void 0;
|
|
15614
|
-
|
|
16349
|
+
resolve2(approved);
|
|
15615
16350
|
};
|
|
15616
16351
|
this.emit("context:approval-required", info);
|
|
15617
16352
|
});
|
|
@@ -15691,6 +16426,68 @@ ${input}` }],
|
|
|
15691
16426
|
if (this.store) t1.setStore(this.store);
|
|
15692
16427
|
return t1.previewPlan(prompt);
|
|
15693
16428
|
}
|
|
16429
|
+
/**
|
|
16430
|
+
* Register image / speech / transcription tools for the modalities this
|
|
16431
|
+
* config can actually serve.
|
|
16432
|
+
*
|
|
16433
|
+
* The sink writes bytes into the workspace, because that is what a desktop or
|
|
16434
|
+
* CLI run wants and it keeps a generated asset indistinguishable from any
|
|
16435
|
+
* other file the run produced. A host with different storage (the cloud
|
|
16436
|
+
* server stores a file row) constructs the tools itself via buildMediaTools
|
|
16437
|
+
* with its own sink.
|
|
16438
|
+
*/
|
|
16439
|
+
/**
|
|
16440
|
+
* Replace where generated media is written.
|
|
16441
|
+
*
|
|
16442
|
+
* The default sink writes into the workspace, which is what a desktop or CLI
|
|
16443
|
+
* run wants. A host with different storage — the cloud server keeps a file
|
|
16444
|
+
* row plus bytes under the tenant's directory — supplies its own here, and
|
|
16445
|
+
* the media tools are rebuilt around it. Re-registering by name replaces the
|
|
16446
|
+
* existing tools rather than duplicating them.
|
|
16447
|
+
*/
|
|
16448
|
+
setMediaSink(sink) {
|
|
16449
|
+
this.mediaSink = sink;
|
|
16450
|
+
this.registerMediaTools(this.workspacePath);
|
|
16451
|
+
}
|
|
16452
|
+
registerMediaTools(workspacePath) {
|
|
16453
|
+
const denied = new Set(this.config.tools?.disabledTools ?? []);
|
|
16454
|
+
const permitted = (name) => !denied.has(name);
|
|
16455
|
+
const tools = buildMediaTools(
|
|
16456
|
+
{
|
|
16457
|
+
registry: this.multimodal,
|
|
16458
|
+
lookupProvider: (provider) => (this.config.providers ?? []).find((p) => p.type === provider),
|
|
16459
|
+
sink: this.mediaSink ?? (async (asset) => {
|
|
16460
|
+
const fsp = await import('fs/promises');
|
|
16461
|
+
const nodePath = await import('path');
|
|
16462
|
+
const dir = nodePath.join(workspacePath, "generated");
|
|
16463
|
+
await fsp.mkdir(dir, { recursive: true });
|
|
16464
|
+
const dest = nodePath.join(dir, asset.filename);
|
|
16465
|
+
await fsp.writeFile(dest, asset.data);
|
|
16466
|
+
return nodePath.relative(workspacePath, dest);
|
|
16467
|
+
})
|
|
16468
|
+
},
|
|
16469
|
+
async (p) => {
|
|
16470
|
+
const fsp = await import('fs/promises');
|
|
16471
|
+
return fsp.readFile(p);
|
|
16472
|
+
}
|
|
16473
|
+
);
|
|
16474
|
+
for (const tool of tools) {
|
|
16475
|
+
if (!permitted(tool.name)) continue;
|
|
16476
|
+
tool.setWorkspaceRoot(workspacePath);
|
|
16477
|
+
this.toolRegistry.register(tool);
|
|
16478
|
+
}
|
|
16479
|
+
}
|
|
16480
|
+
/**
|
|
16481
|
+
* Supply per-model thumbs-up/down counts so Auto routing can weigh how models
|
|
16482
|
+
* have actually performed for this user, on top of public benchmarks.
|
|
16483
|
+
*
|
|
16484
|
+
* Optional everywhere: with no source, or an empty record, scoring is
|
|
16485
|
+
* byte-identical to before. The adjustment is capped and sample-size shrunk —
|
|
16486
|
+
* see core/router/feedback-prior.ts.
|
|
16487
|
+
*/
|
|
16488
|
+
setFeedbackSource(source) {
|
|
16489
|
+
this.router.setFeedbackSource(source);
|
|
16490
|
+
}
|
|
15694
16491
|
/** True when a task stopped at the budget cap and can be resumed via /continue. */
|
|
15695
16492
|
hasResumableRun() {
|
|
15696
16493
|
return this.lastInterruptedRun != null;
|
|
@@ -16180,7 +16977,7 @@ ${prompt}` : prompt;
|
|
|
16180
16977
|
if (toolCreator) toolCreator.setPermissionEscalator(escalator);
|
|
16181
16978
|
const rootPrompt = buildContextualPrompt(options.prompt, options.conversationHistory);
|
|
16182
16979
|
let finalOutput = "";
|
|
16183
|
-
const runBreaker = new RunBreaker();
|
|
16980
|
+
const runBreaker = new RunBreaker(this.config.budget?.failureThreshold);
|
|
16184
16981
|
let t2Results = [];
|
|
16185
16982
|
let runError = null;
|
|
16186
16983
|
let identityPrompt = "";
|
|
@@ -16671,7 +17468,7 @@ var CloudClient = class _CloudClient {
|
|
|
16671
17468
|
const deadline = Date.now() + start.expiresIn * 1e3;
|
|
16672
17469
|
while (Date.now() < deadline) {
|
|
16673
17470
|
if (opts.signal?.aborted) throw new Error("Sign-in cancelled.");
|
|
16674
|
-
await
|
|
17471
|
+
await sleep2(interval * 1e3, opts.signal);
|
|
16675
17472
|
const r = await this.pollDevice(start.deviceCode);
|
|
16676
17473
|
if (r === "slow_down") {
|
|
16677
17474
|
interval += 5;
|
|
@@ -16882,9 +17679,9 @@ var CloudClient = class _CloudClient {
|
|
|
16882
17679
|
this.store.clear();
|
|
16883
17680
|
}
|
|
16884
17681
|
};
|
|
16885
|
-
function
|
|
16886
|
-
return new Promise((
|
|
16887
|
-
const t = setTimeout(
|
|
17682
|
+
function sleep2(ms, signal) {
|
|
17683
|
+
return new Promise((resolve2, reject) => {
|
|
17684
|
+
const t = setTimeout(resolve2, ms);
|
|
16888
17685
|
signal?.addEventListener("abort", () => {
|
|
16889
17686
|
clearTimeout(t);
|
|
16890
17687
|
reject(new Error("aborted"));
|
|
@@ -18676,8 +19473,8 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName, a
|
|
|
18676
19473
|
onExit: () => exit(),
|
|
18677
19474
|
onThemeChange: (name) => setTheme(getTheme(name)),
|
|
18678
19475
|
onExport: async (fmt) => {
|
|
18679
|
-
const
|
|
18680
|
-
const exportPath = path32__default.default.join(workspacePath, `cascade-export-${
|
|
19476
|
+
const stamp2 = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
19477
|
+
const exportPath = path32__default.default.join(workspacePath, `cascade-export-${stamp2}.${fmt === "json" ? "json" : "md"}`);
|
|
18681
19478
|
if (fmt === "json") {
|
|
18682
19479
|
await fs10__default.default.writeFile(exportPath, JSON.stringify({ sessionId: sessionIdRef.current, messages: state.messages }, null, 2), "utf-8");
|
|
18683
19480
|
} else {
|
|
@@ -19109,8 +19906,8 @@ ${lastUser.content}`;
|
|
|
19109
19906
|
conversationHistory: toConversationHistory(state.messages),
|
|
19110
19907
|
approvalCallback: async (req) => {
|
|
19111
19908
|
dispatch({ type: "SET_APPROVAL", request: req });
|
|
19112
|
-
return new Promise((
|
|
19113
|
-
approvalResolverRef.current =
|
|
19909
|
+
return new Promise((resolve2) => {
|
|
19910
|
+
approvalResolverRef.current = resolve2;
|
|
19114
19911
|
});
|
|
19115
19912
|
}
|
|
19116
19913
|
});
|
|
@@ -20376,13 +21173,13 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
20376
21173
|
}
|
|
20377
21174
|
async function runSetupWizard(workspacePath) {
|
|
20378
21175
|
printTelemetryBanner();
|
|
20379
|
-
return new Promise((
|
|
21176
|
+
return new Promise((resolve2) => {
|
|
20380
21177
|
const { unmount } = ink.render(
|
|
20381
21178
|
React2__default.default.createElement(SetupWizard, {
|
|
20382
21179
|
workspacePath,
|
|
20383
21180
|
onComplete: (config) => {
|
|
20384
21181
|
unmount();
|
|
20385
|
-
|
|
21182
|
+
resolve2(config);
|
|
20386
21183
|
}
|
|
20387
21184
|
}),
|
|
20388
21185
|
{ exitOnCtrlC: true }
|
|
@@ -20850,7 +21647,7 @@ function notSignedIn() {
|
|
|
20850
21647
|
process.exitCode = 1;
|
|
20851
21648
|
}
|
|
20852
21649
|
function promptHidden(question) {
|
|
20853
|
-
return new Promise((
|
|
21650
|
+
return new Promise((resolve2) => {
|
|
20854
21651
|
const rl = readline__default.default.createInterface({ input: process.stdin, output: process.stdout, terminal: true });
|
|
20855
21652
|
let masked = false;
|
|
20856
21653
|
rl._writeToOutput = (s) => {
|
|
@@ -20859,7 +21656,7 @@ function promptHidden(question) {
|
|
|
20859
21656
|
rl.question(question, (answer) => {
|
|
20860
21657
|
rl.close();
|
|
20861
21658
|
process.stdout.write("\n");
|
|
20862
|
-
|
|
21659
|
+
resolve2(answer.trim());
|
|
20863
21660
|
});
|
|
20864
21661
|
masked = true;
|
|
20865
21662
|
});
|
|
@@ -21786,12 +22583,12 @@ var DashboardServer = class {
|
|
|
21786
22583
|
console.warn("\u26A0 Dashboard auth is DISABLED while bound to a non-loopback interface \u2014 this allows unauthenticated remote task execution.");
|
|
21787
22584
|
}
|
|
21788
22585
|
}
|
|
21789
|
-
await new Promise((
|
|
22586
|
+
await new Promise((resolve2, reject) => {
|
|
21790
22587
|
const onError = (err) => reject(err);
|
|
21791
22588
|
this.httpServer.once("error", onError);
|
|
21792
22589
|
this.httpServer.listen(this.port, this.host, () => {
|
|
21793
22590
|
this.httpServer.off("error", onError);
|
|
21794
|
-
|
|
22591
|
+
resolve2();
|
|
21795
22592
|
});
|
|
21796
22593
|
});
|
|
21797
22594
|
try {
|
|
@@ -21815,8 +22612,8 @@ var DashboardServer = class {
|
|
|
21815
22612
|
} catch {
|
|
21816
22613
|
}
|
|
21817
22614
|
this.globalStore = null;
|
|
21818
|
-
await new Promise((
|
|
21819
|
-
this.httpServer.close((err) => err ? reject(err) :
|
|
22615
|
+
await new Promise((resolve2, reject) => {
|
|
22616
|
+
this.httpServer.close((err) => err ? reject(err) : resolve2());
|
|
21820
22617
|
});
|
|
21821
22618
|
}
|
|
21822
22619
|
getSocket() {
|
|
@@ -22013,8 +22810,8 @@ var DashboardServer = class {
|
|
|
22013
22810
|
* timeout denies it.
|
|
22014
22811
|
*/
|
|
22015
22812
|
makeApprovalCallback(sessionId) {
|
|
22016
|
-
return (request) => new Promise((
|
|
22017
|
-
this.pendingApprovals.set(request.id, { resolve, sessionId });
|
|
22813
|
+
return (request) => new Promise((resolve2) => {
|
|
22814
|
+
this.pendingApprovals.set(request.id, { resolve: resolve2, sessionId });
|
|
22018
22815
|
});
|
|
22019
22816
|
}
|
|
22020
22817
|
/**
|