cascade-ai 0.71.0 → 0.73.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 +777 -237
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +777 -237
- package/dist/cli.js.map +1 -1
- package/dist/desktop-core.cjs +765 -237
- package/dist/index.cjs +764 -236
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +113 -0
- package/dist/index.d.ts +113 -0
- package/dist/index.js +764 -236
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1746,6 +1746,15 @@ function findPricing(modelId, provider, opts = {}) {
|
|
|
1746
1746
|
}
|
|
1747
1747
|
return null;
|
|
1748
1748
|
}
|
|
1749
|
+
function hasContextBands(model) {
|
|
1750
|
+
if (model.isLocal) return false;
|
|
1751
|
+
const fallbackProviders = model.provider === "azure" ? ["openai"] : model.provider === "gemini" ? ["vertex"] : [];
|
|
1752
|
+
const match = findPricing(model.id, model.provider, {
|
|
1753
|
+
baseModelId: model.baseModelId,
|
|
1754
|
+
fallbackProviders
|
|
1755
|
+
});
|
|
1756
|
+
return (match?.entry.tiers?.length ?? 0) > 1;
|
|
1757
|
+
}
|
|
1749
1758
|
function tierFor(entry, inputTokens = 0) {
|
|
1750
1759
|
const tiers = entry.tiers;
|
|
1751
1760
|
if (!tiers?.length) return null;
|
|
@@ -1862,7 +1871,7 @@ function reconcilePrice(model, live) {
|
|
|
1862
1871
|
}
|
|
1863
1872
|
|
|
1864
1873
|
// src/constants.ts
|
|
1865
|
-
var CASCADE_VERSION = "0.
|
|
1874
|
+
var CASCADE_VERSION = "0.73.0";
|
|
1866
1875
|
var CASCADE_CONFIG_DIR = ".cascade";
|
|
1867
1876
|
var CASCADE_MD_FILE = "CASCADE.md";
|
|
1868
1877
|
var CASCADE_IGNORE_FILE = ".cascadeignore";
|
|
@@ -2307,8 +2316,23 @@ var PROVIDER_DISPLAY_NAMES = {
|
|
|
2307
2316
|
};
|
|
2308
2317
|
|
|
2309
2318
|
// src/utils/cost.ts
|
|
2310
|
-
function resolveModelPricing(model) {
|
|
2319
|
+
function resolveModelPricing(model, opts = {}) {
|
|
2311
2320
|
if (model.isLocal) return { input: 0, output: 0, unknown: false };
|
|
2321
|
+
if (opts.inputTokens != null && hasContextBands(model)) {
|
|
2322
|
+
const banded = resolvePricing(model, { inputTokens: opts.inputTokens });
|
|
2323
|
+
const base = resolvePricing(model, { inputTokens: 0 });
|
|
2324
|
+
if (!banded.unknown && !base.unknown) {
|
|
2325
|
+
const scale = (stamped, baseRate) => stamped > 0 && baseRate > 0 ? stamped / baseRate : 1;
|
|
2326
|
+
const inScale = scale(model.inputCostPer1kTokens, base.input);
|
|
2327
|
+
const outScale = scale(model.outputCostPer1kTokens, base.output);
|
|
2328
|
+
return {
|
|
2329
|
+
input: banded.input * inScale,
|
|
2330
|
+
output: banded.output * outScale,
|
|
2331
|
+
unknown: false,
|
|
2332
|
+
...banded.estimatedFromProvider ? { estimatedFromProvider: banded.estimatedFromProvider } : {}
|
|
2333
|
+
};
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2312
2336
|
if (model.inputCostPer1kTokens > 0 || model.outputCostPer1kTokens > 0) {
|
|
2313
2337
|
return {
|
|
2314
2338
|
input: model.inputCostPer1kTokens,
|
|
@@ -2316,7 +2340,7 @@ function resolveModelPricing(model) {
|
|
|
2316
2340
|
unknown: false
|
|
2317
2341
|
};
|
|
2318
2342
|
}
|
|
2319
|
-
const fromDataset = resolvePricing(model);
|
|
2343
|
+
const fromDataset = resolvePricing(model, { inputTokens: opts.inputTokens });
|
|
2320
2344
|
if (!fromDataset.unknown) {
|
|
2321
2345
|
return {
|
|
2322
2346
|
input: fromDataset.input,
|
|
@@ -2338,11 +2362,11 @@ function resolveModelPricing(model) {
|
|
|
2338
2362
|
return { input: 0, output: 0, unknown: true };
|
|
2339
2363
|
}
|
|
2340
2364
|
function calculateCost(inputTokens, outputTokens, model) {
|
|
2341
|
-
const { input, output } = resolveModelPricing(model);
|
|
2365
|
+
const { input, output } = resolveModelPricing(model, { inputTokens });
|
|
2342
2366
|
return inputTokens / 1e3 * input + outputTokens / 1e3 * output;
|
|
2343
2367
|
}
|
|
2344
2368
|
function buildTokenUsage(inputTokens, outputTokens, model) {
|
|
2345
|
-
const { input, output, unknown } = resolveModelPricing(model);
|
|
2369
|
+
const { input, output, unknown } = resolveModelPricing(model, { inputTokens });
|
|
2346
2370
|
return {
|
|
2347
2371
|
inputTokens,
|
|
2348
2372
|
outputTokens,
|
|
@@ -2421,6 +2445,21 @@ function anthropicThinkingParam(modelId, maxTokens) {
|
|
|
2421
2445
|
if (budget < 1024) return {};
|
|
2422
2446
|
return { thinking: { type: "enabled", budget_tokens: budget } };
|
|
2423
2447
|
}
|
|
2448
|
+
function toAnthropicTools(tools) {
|
|
2449
|
+
return tools.map((t) => ({
|
|
2450
|
+
name: t.name,
|
|
2451
|
+
description: t.description,
|
|
2452
|
+
input_schema: t.inputSchema
|
|
2453
|
+
}));
|
|
2454
|
+
}
|
|
2455
|
+
function toAnthropicToolUse(toolCalls) {
|
|
2456
|
+
return toolCalls.map((tc) => ({
|
|
2457
|
+
type: "tool_use",
|
|
2458
|
+
id: tc.id,
|
|
2459
|
+
name: tc.name,
|
|
2460
|
+
input: tc.input
|
|
2461
|
+
}));
|
|
2462
|
+
}
|
|
2424
2463
|
var AnthropicProvider = class extends BaseProvider {
|
|
2425
2464
|
client;
|
|
2426
2465
|
constructor(config, model) {
|
|
@@ -2442,11 +2481,7 @@ var AnthropicProvider = class extends BaseProvider {
|
|
|
2442
2481
|
}
|
|
2443
2482
|
async generateStream(options, onChunk) {
|
|
2444
2483
|
const messages = this.convertMessages(options.messages);
|
|
2445
|
-
const tools = options.tools
|
|
2446
|
-
name: t.name,
|
|
2447
|
-
description: t.description,
|
|
2448
|
-
input_schema: t.inputSchema
|
|
2449
|
-
}));
|
|
2484
|
+
const tools = options.tools && toAnthropicTools(options.tools);
|
|
2450
2485
|
let fullContent = "";
|
|
2451
2486
|
let inputTokens = 0;
|
|
2452
2487
|
let outputTokens = 0;
|
|
@@ -2574,14 +2609,7 @@ var AnthropicProvider = class extends BaseProvider {
|
|
|
2574
2609
|
const content = [];
|
|
2575
2610
|
const text = typeof m.content === "string" ? m.content : "";
|
|
2576
2611
|
if (text) content.push({ type: "text", text });
|
|
2577
|
-
|
|
2578
|
-
content.push({
|
|
2579
|
-
type: "tool_use",
|
|
2580
|
-
id: tc.id,
|
|
2581
|
-
name: tc.name,
|
|
2582
|
-
input: tc.input
|
|
2583
|
-
});
|
|
2584
|
-
}
|
|
2612
|
+
content.push(...toAnthropicToolUse(m.toolCalls ?? []));
|
|
2585
2613
|
if (content.length > 0) {
|
|
2586
2614
|
result.push({ role: "assistant", content });
|
|
2587
2615
|
}
|
|
@@ -2621,6 +2649,26 @@ function isParamShapeError(err) {
|
|
|
2621
2649
|
const msg = String(e?.message ?? e?.error?.message ?? "").toLowerCase();
|
|
2622
2650
|
return msg.includes("max_completion_tokens") || msg.includes("max_tokens") && (msg.includes("unsupported") || msg.includes("not supported") || msg.includes("use")) || msg.includes("temperature") && (msg.includes("unsupported") || msg.includes("does not support") || msg.includes("only the default"));
|
|
2623
2651
|
}
|
|
2652
|
+
function toOpenAITools(tools) {
|
|
2653
|
+
return tools.map((t) => ({
|
|
2654
|
+
type: "function",
|
|
2655
|
+
function: {
|
|
2656
|
+
name: t.name,
|
|
2657
|
+
description: t.description,
|
|
2658
|
+
parameters: t.inputSchema
|
|
2659
|
+
}
|
|
2660
|
+
}));
|
|
2661
|
+
}
|
|
2662
|
+
function toOpenAIToolCalls(toolCalls) {
|
|
2663
|
+
return toolCalls.map((tc) => ({
|
|
2664
|
+
id: tc.id,
|
|
2665
|
+
type: "function",
|
|
2666
|
+
function: {
|
|
2667
|
+
name: tc.name,
|
|
2668
|
+
arguments: JSON.stringify(tc.input)
|
|
2669
|
+
}
|
|
2670
|
+
}));
|
|
2671
|
+
}
|
|
2624
2672
|
var OpenAIProvider = class extends BaseProvider {
|
|
2625
2673
|
client;
|
|
2626
2674
|
/** Once we learn (from the model id or an API error) that this deployment
|
|
@@ -2640,14 +2688,7 @@ var OpenAIProvider = class extends BaseProvider {
|
|
|
2640
2688
|
}
|
|
2641
2689
|
async generateStream(options, onChunk) {
|
|
2642
2690
|
const messages = this.convertMessages(options.messages, options.systemPrompt);
|
|
2643
|
-
const tools = options.tools
|
|
2644
|
-
type: "function",
|
|
2645
|
-
function: {
|
|
2646
|
-
name: t.name,
|
|
2647
|
-
description: t.description,
|
|
2648
|
-
parameters: t.inputSchema
|
|
2649
|
-
}
|
|
2650
|
-
}));
|
|
2691
|
+
const tools = options.tools && toOpenAITools(options.tools);
|
|
2651
2692
|
let fullContent = "";
|
|
2652
2693
|
let inputTokens = 0;
|
|
2653
2694
|
let outputTokens = 0;
|
|
@@ -2793,14 +2834,7 @@ var OpenAIProvider = class extends BaseProvider {
|
|
|
2793
2834
|
result.push({
|
|
2794
2835
|
role: "assistant",
|
|
2795
2836
|
content: m.content || "",
|
|
2796
|
-
tool_calls: m.toolCalls
|
|
2797
|
-
id: toolCall.id,
|
|
2798
|
-
type: "function",
|
|
2799
|
-
function: {
|
|
2800
|
-
name: toolCall.name,
|
|
2801
|
-
arguments: JSON.stringify(toolCall.input)
|
|
2802
|
-
}
|
|
2803
|
-
}))
|
|
2837
|
+
tool_calls: toOpenAIToolCalls(m.toolCalls)
|
|
2804
2838
|
});
|
|
2805
2839
|
} else {
|
|
2806
2840
|
result.push({ role: m.role, content: m.content });
|
|
@@ -3043,6 +3077,26 @@ function toGeminiParameters(inputSchema) {
|
|
|
3043
3077
|
}
|
|
3044
3078
|
|
|
3045
3079
|
// src/providers/gemini.ts
|
|
3080
|
+
function toGeminiTools(tools) {
|
|
3081
|
+
return tools.map((tool) => ({
|
|
3082
|
+
name: tool.name,
|
|
3083
|
+
description: tool.description,
|
|
3084
|
+
// Convert, never cast. Gemini's parameters field is an OpenAPI subset
|
|
3085
|
+
// that 400s on unknown keys, and MCP servers ship schemas carrying their
|
|
3086
|
+
// own extensions (GitHub's adds `x-mcp-header` to every header-bound
|
|
3087
|
+
// property). Casting sent those through verbatim and every request failed
|
|
3088
|
+
// before the model saw it. See providers/gemini-schema.ts.
|
|
3089
|
+
parameters: toGeminiParameters(tool.inputSchema)
|
|
3090
|
+
}));
|
|
3091
|
+
}
|
|
3092
|
+
function toGeminiFunctionCalls(toolCalls) {
|
|
3093
|
+
return toolCalls.map((tc) => ({
|
|
3094
|
+
functionCall: {
|
|
3095
|
+
name: tc.name,
|
|
3096
|
+
args: tc.input
|
|
3097
|
+
}
|
|
3098
|
+
}));
|
|
3099
|
+
}
|
|
3046
3100
|
var GeminiProvider = class extends BaseProvider {
|
|
3047
3101
|
client;
|
|
3048
3102
|
constructor(config, model) {
|
|
@@ -3064,7 +3118,7 @@ var GeminiProvider = class extends BaseProvider {
|
|
|
3064
3118
|
{ category: HarmCategory.HARM_CATEGORY_HARASSMENT, threshold: HarmBlockThreshold.BLOCK_NONE },
|
|
3065
3119
|
{ category: HarmCategory.HARM_CATEGORY_HATE_SPEECH, threshold: HarmBlockThreshold.BLOCK_NONE }
|
|
3066
3120
|
],
|
|
3067
|
-
tools: options.tools?.length ? [{ functionDeclarations: options.tools
|
|
3121
|
+
tools: options.tools?.length ? [{ functionDeclarations: toGeminiTools(options.tools) }] : void 0,
|
|
3068
3122
|
abortSignal: options.signal
|
|
3069
3123
|
}
|
|
3070
3124
|
});
|
|
@@ -3205,14 +3259,7 @@ var GeminiProvider = class extends BaseProvider {
|
|
|
3205
3259
|
const parts = [];
|
|
3206
3260
|
const textContent = typeof m.content === "string" ? m.content : "";
|
|
3207
3261
|
if (textContent) parts.push({ text: textContent });
|
|
3208
|
-
|
|
3209
|
-
parts.push({
|
|
3210
|
-
functionCall: {
|
|
3211
|
-
name: tc.name,
|
|
3212
|
-
args: tc.input
|
|
3213
|
-
}
|
|
3214
|
-
});
|
|
3215
|
-
}
|
|
3262
|
+
parts.push(...toGeminiFunctionCalls(m.toolCalls ?? []));
|
|
3216
3263
|
if (parts.length > 0) {
|
|
3217
3264
|
contents.push({ role: "model", parts });
|
|
3218
3265
|
}
|
|
@@ -3259,18 +3306,6 @@ var GeminiProvider = class extends BaseProvider {
|
|
|
3259
3306
|
}
|
|
3260
3307
|
return parts;
|
|
3261
3308
|
}
|
|
3262
|
-
convertTool(tool) {
|
|
3263
|
-
return {
|
|
3264
|
-
name: tool.name,
|
|
3265
|
-
description: tool.description,
|
|
3266
|
-
// Convert, never cast. Gemini's parameters field is an OpenAPI subset
|
|
3267
|
-
// that 400s on unknown keys, and MCP servers ship schemas carrying their
|
|
3268
|
-
// own extensions (GitHub's adds `x-mcp-header` to every header-bound
|
|
3269
|
-
// property). Casting sent those through verbatim and every request failed
|
|
3270
|
-
// before the model saw it. See providers/gemini-schema.ts.
|
|
3271
|
-
parameters: toGeminiParameters(tool.inputSchema)
|
|
3272
|
-
};
|
|
3273
|
-
}
|
|
3274
3309
|
};
|
|
3275
3310
|
try {
|
|
3276
3311
|
dns.setDefaultResultOrder("ipv4first");
|
|
@@ -3363,6 +3398,24 @@ function isToolCapable(modelName) {
|
|
|
3363
3398
|
const name = modelName.toLowerCase();
|
|
3364
3399
|
return TOOL_CAPABLE_FAMILIES.some((family) => name.includes(family));
|
|
3365
3400
|
}
|
|
3401
|
+
function toOllamaTools(tools) {
|
|
3402
|
+
return tools.map((t) => ({
|
|
3403
|
+
type: "function",
|
|
3404
|
+
function: {
|
|
3405
|
+
name: t.name,
|
|
3406
|
+
description: t.description,
|
|
3407
|
+
parameters: t.inputSchema
|
|
3408
|
+
}
|
|
3409
|
+
}));
|
|
3410
|
+
}
|
|
3411
|
+
function toOllamaToolCalls(toolCalls) {
|
|
3412
|
+
return toolCalls.map((tc) => ({
|
|
3413
|
+
function: {
|
|
3414
|
+
name: tc.name,
|
|
3415
|
+
arguments: tc.input
|
|
3416
|
+
}
|
|
3417
|
+
}));
|
|
3418
|
+
}
|
|
3366
3419
|
var OllamaProvider = class extends BaseProvider {
|
|
3367
3420
|
baseUrl;
|
|
3368
3421
|
constructor(config, model) {
|
|
@@ -3375,14 +3428,7 @@ var OllamaProvider = class extends BaseProvider {
|
|
|
3375
3428
|
}
|
|
3376
3429
|
async generateStream(options, onChunk) {
|
|
3377
3430
|
const messages = this.convertMessages(options.messages, options.systemPrompt);
|
|
3378
|
-
const ollamaTools = options.tools
|
|
3379
|
-
type: "function",
|
|
3380
|
-
function: {
|
|
3381
|
-
name: t.name,
|
|
3382
|
-
description: t.description,
|
|
3383
|
-
parameters: t.inputSchema
|
|
3384
|
-
}
|
|
3385
|
-
}));
|
|
3431
|
+
const ollamaTools = options.tools && toOllamaTools(options.tools);
|
|
3386
3432
|
const response = await fetch(`${this.baseUrl}/api/chat`, {
|
|
3387
3433
|
method: "POST",
|
|
3388
3434
|
headers: { "Content-Type": "application/json" },
|
|
@@ -3562,12 +3608,7 @@ var OllamaProvider = class extends BaseProvider {
|
|
|
3562
3608
|
result.push({
|
|
3563
3609
|
role: "assistant",
|
|
3564
3610
|
content: typeof m.content === "string" ? m.content : "",
|
|
3565
|
-
tool_calls: m.toolCalls
|
|
3566
|
-
function: {
|
|
3567
|
-
name: tc.name,
|
|
3568
|
-
arguments: tc.input
|
|
3569
|
-
}
|
|
3570
|
-
}))
|
|
3611
|
+
tool_calls: toOllamaToolCalls(m.toolCalls)
|
|
3571
3612
|
});
|
|
3572
3613
|
continue;
|
|
3573
3614
|
}
|
|
@@ -3968,6 +4009,21 @@ var DEFAULT_PROVIDER_TPM = {
|
|
|
3968
4009
|
"openai-compatible": 3e4,
|
|
3969
4010
|
ollama: Number.POSITIVE_INFINITY
|
|
3970
4011
|
};
|
|
4012
|
+
function abortReason(signal) {
|
|
4013
|
+
return signal.reason instanceof Error ? signal.reason : new Error("Aborted while waiting for rate-limit capacity");
|
|
4014
|
+
}
|
|
4015
|
+
function sleepUnlessAborted(ms, signal) {
|
|
4016
|
+
if (!signal) return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
4017
|
+
return new Promise((resolve2) => {
|
|
4018
|
+
const done = () => {
|
|
4019
|
+
clearTimeout(timer);
|
|
4020
|
+
signal.removeEventListener("abort", done);
|
|
4021
|
+
resolve2();
|
|
4022
|
+
};
|
|
4023
|
+
const timer = setTimeout(done, ms);
|
|
4024
|
+
signal.addEventListener("abort", done, { once: true });
|
|
4025
|
+
});
|
|
4026
|
+
}
|
|
3971
4027
|
var TpmLimiter = class {
|
|
3972
4028
|
buckets = /* @__PURE__ */ new Map();
|
|
3973
4029
|
constructor(overrides = {}) {
|
|
@@ -3986,9 +4042,10 @@ var TpmLimiter = class {
|
|
|
3986
4042
|
* are reported back via `refund` when short, or simply settled at the next
|
|
3987
4043
|
* refill.
|
|
3988
4044
|
*/
|
|
3989
|
-
async acquire(provider, estimatedTokens) {
|
|
4045
|
+
async acquire(provider, estimatedTokens, signal) {
|
|
3990
4046
|
const bucket = this.buckets.get(provider);
|
|
3991
4047
|
if (!bucket || bucket.tokensPerMinute === Number.POSITIVE_INFINITY) return;
|
|
4048
|
+
if (signal?.aborted) throw abortReason(signal);
|
|
3992
4049
|
const want = Math.min(estimatedTokens, bucket.tokensPerMinute);
|
|
3993
4050
|
for (; ; ) {
|
|
3994
4051
|
this.refill(bucket);
|
|
@@ -3998,7 +4055,8 @@ var TpmLimiter = class {
|
|
|
3998
4055
|
}
|
|
3999
4056
|
const deficit = want - bucket.available;
|
|
4000
4057
|
const waitMs = Math.max(50, Math.ceil(deficit / bucket.tokensPerMinute * 6e4));
|
|
4001
|
-
await
|
|
4058
|
+
await sleepUnlessAborted(waitMs, signal);
|
|
4059
|
+
if (signal?.aborted) throw abortReason(signal);
|
|
4002
4060
|
}
|
|
4003
4061
|
}
|
|
4004
4062
|
/**
|
|
@@ -4044,6 +4102,9 @@ var TpmLimiter = class {
|
|
|
4044
4102
|
};
|
|
4045
4103
|
|
|
4046
4104
|
// src/core/router/local-queue.ts
|
|
4105
|
+
function abortReason2(signal) {
|
|
4106
|
+
return signal.reason instanceof Error ? signal.reason : new Error("Aborted while waiting for a local inference slot");
|
|
4107
|
+
}
|
|
4047
4108
|
var LocalRequestQueue = class {
|
|
4048
4109
|
maxConcurrent;
|
|
4049
4110
|
active = 0;
|
|
@@ -4054,9 +4115,16 @@ var LocalRequestQueue = class {
|
|
|
4054
4115
|
/**
|
|
4055
4116
|
* Acquire a queue slot. Returns a `release` function that MUST be called
|
|
4056
4117
|
* when the inference call is done (even on error). Rejects if the slot
|
|
4057
|
-
* cannot be acquired within `timeoutMs
|
|
4118
|
+
* cannot be acquired within `timeoutMs`, or as soon as `signal` fires.
|
|
4119
|
+
*
|
|
4120
|
+
* The signal matters as much as the timeout: with concurrency of 1 a caller
|
|
4121
|
+
* can sit here for half the inference timeout — 150 seconds by default —
|
|
4122
|
+
* and until it was wired up, neither cancelling the run nor spending its
|
|
4123
|
+
* budget got it out. It waited out the window for a slot it would drop the
|
|
4124
|
+
* moment it received one.
|
|
4058
4125
|
*/
|
|
4059
|
-
async acquire(timeoutMs) {
|
|
4126
|
+
async acquire(timeoutMs, signal) {
|
|
4127
|
+
if (signal?.aborted) throw abortReason2(signal);
|
|
4060
4128
|
if (this.active < this.maxConcurrent) {
|
|
4061
4129
|
this.active++;
|
|
4062
4130
|
return this.makeRelease();
|
|
@@ -4064,23 +4132,36 @@ var LocalRequestQueue = class {
|
|
|
4064
4132
|
return new Promise((resolve2, reject) => {
|
|
4065
4133
|
let settled = false;
|
|
4066
4134
|
let timer;
|
|
4135
|
+
const cleanup = () => {
|
|
4136
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
4137
|
+
signal?.removeEventListener("abort", onAbort);
|
|
4138
|
+
};
|
|
4067
4139
|
const resolver = (release) => {
|
|
4068
4140
|
if (settled) return;
|
|
4069
4141
|
settled = true;
|
|
4070
|
-
|
|
4142
|
+
cleanup();
|
|
4071
4143
|
resolve2(release);
|
|
4072
4144
|
};
|
|
4145
|
+
const abandon = () => {
|
|
4146
|
+
if (settled) return false;
|
|
4147
|
+
settled = true;
|
|
4148
|
+
cleanup();
|
|
4149
|
+
const idx = this.queue.indexOf(resolver);
|
|
4150
|
+
if (idx !== -1) this.queue.splice(idx, 1);
|
|
4151
|
+
return true;
|
|
4152
|
+
};
|
|
4153
|
+
function onAbort() {
|
|
4154
|
+
if (abandon()) reject(abortReason2(signal));
|
|
4155
|
+
}
|
|
4073
4156
|
if (timeoutMs !== void 0 && timeoutMs > 0) {
|
|
4074
4157
|
timer = setTimeout(() => {
|
|
4075
|
-
if (
|
|
4076
|
-
settled = true;
|
|
4077
|
-
const idx = this.queue.indexOf(resolver);
|
|
4078
|
-
if (idx !== -1) this.queue.splice(idx, 1);
|
|
4158
|
+
if (!abandon()) return;
|
|
4079
4159
|
reject(new Error(
|
|
4080
4160
|
`Local model queue: timed out waiting for a free slot after ${timeoutMs}ms. Active: ${this.active}, Queued: ${this.queue.length}. Consider increasing localConcurrency or localInferenceTimeoutMs in your config.`
|
|
4081
4161
|
));
|
|
4082
4162
|
}, timeoutMs);
|
|
4083
4163
|
}
|
|
4164
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
4084
4165
|
this.queue.push(resolver);
|
|
4085
4166
|
});
|
|
4086
4167
|
}
|
|
@@ -4217,6 +4298,116 @@ function fileDeadModelPersistence(filePath) {
|
|
|
4217
4298
|
};
|
|
4218
4299
|
}
|
|
4219
4300
|
|
|
4301
|
+
// src/core/context/compaction.ts
|
|
4302
|
+
var CHARS_PER_TOKEN = 4;
|
|
4303
|
+
function estimateTokens(text) {
|
|
4304
|
+
if (!text) return 0;
|
|
4305
|
+
return Math.max(1, Math.ceil(text.length / CHARS_PER_TOKEN));
|
|
4306
|
+
}
|
|
4307
|
+
function contentToText(content) {
|
|
4308
|
+
if (typeof content === "string") return content;
|
|
4309
|
+
return content.map((c) => c.type === "text" ? c.text : c.type === "tool_result" ? c.content : `[${c.type}]`).join(" ");
|
|
4310
|
+
}
|
|
4311
|
+
function messagesTokens(messages) {
|
|
4312
|
+
return messages.reduce((n, m) => n + estimateTokens(contentToText(m.content)), 0);
|
|
4313
|
+
}
|
|
4314
|
+
function atomize(text, maxChars) {
|
|
4315
|
+
const seps = ["\n\n", "\n", ". ", " "];
|
|
4316
|
+
let units = [text];
|
|
4317
|
+
for (const sep of seps) {
|
|
4318
|
+
if (units.every((u) => u.length <= maxChars)) break;
|
|
4319
|
+
const next = [];
|
|
4320
|
+
for (const u of units) {
|
|
4321
|
+
if (u.length <= maxChars) {
|
|
4322
|
+
next.push(u);
|
|
4323
|
+
continue;
|
|
4324
|
+
}
|
|
4325
|
+
const parts = u.split(sep);
|
|
4326
|
+
parts.forEach((p, i) => next.push(i < parts.length - 1 ? p + sep : p));
|
|
4327
|
+
}
|
|
4328
|
+
units = next;
|
|
4329
|
+
}
|
|
4330
|
+
const out = [];
|
|
4331
|
+
for (const u of units) {
|
|
4332
|
+
if (u.length <= maxChars) out.push(u);
|
|
4333
|
+
else for (let i = 0; i < u.length; i += maxChars) out.push(u.slice(i, i + maxChars));
|
|
4334
|
+
}
|
|
4335
|
+
return out;
|
|
4336
|
+
}
|
|
4337
|
+
function chunkText(text, opts) {
|
|
4338
|
+
const maxChars = Math.max(1, Math.floor(opts.maxTokens * 4));
|
|
4339
|
+
const overlap = Math.min(maxChars - 1, Math.max(0, Math.floor(maxChars * (opts.overlapRatio ?? 0.1))));
|
|
4340
|
+
const trimmed = text.trim();
|
|
4341
|
+
if (!trimmed) return [];
|
|
4342
|
+
if (trimmed.length <= maxChars) return [trimmed];
|
|
4343
|
+
const units = atomize(trimmed, maxChars);
|
|
4344
|
+
const chunks = [];
|
|
4345
|
+
let cur = "";
|
|
4346
|
+
for (const u of units) {
|
|
4347
|
+
if (cur && cur.length + u.length > maxChars) {
|
|
4348
|
+
chunks.push(cur.trim());
|
|
4349
|
+
cur = overlap > 0 ? cur.slice(-overlap) : "";
|
|
4350
|
+
}
|
|
4351
|
+
cur += u;
|
|
4352
|
+
}
|
|
4353
|
+
if (cur.trim()) chunks.push(cur.trim());
|
|
4354
|
+
return chunks;
|
|
4355
|
+
}
|
|
4356
|
+
function needsCompaction(totalTokens, contextWindow, reserve = 0.2) {
|
|
4357
|
+
if (!contextWindow || contextWindow <= 0) return false;
|
|
4358
|
+
return totalTokens > contextWindow * (1 - reserve);
|
|
4359
|
+
}
|
|
4360
|
+
async function mapReduceCompact(text, opts) {
|
|
4361
|
+
const capChars = opts.capTokens * 4;
|
|
4362
|
+
let working = text;
|
|
4363
|
+
let truncated = false;
|
|
4364
|
+
if (working.length > capChars) {
|
|
4365
|
+
working = working.slice(0, capChars);
|
|
4366
|
+
truncated = true;
|
|
4367
|
+
}
|
|
4368
|
+
const instruction = opts.instruction ?? "Summarize this content, preserving key facts, names, numbers, and decisions.";
|
|
4369
|
+
let chunks = chunkText(working, { maxTokens: opts.chunkTokens });
|
|
4370
|
+
if (chunks.length <= 1) return { text: working, chunks: chunks.length, calls: 0, truncated };
|
|
4371
|
+
let calls = 0;
|
|
4372
|
+
let parts = await Promise.all(
|
|
4373
|
+
chunks.map((c, i) => {
|
|
4374
|
+
calls++;
|
|
4375
|
+
return opts.summarize(c, `${instruction} (part ${i + 1} of ${chunks.length})`);
|
|
4376
|
+
})
|
|
4377
|
+
);
|
|
4378
|
+
let combined = parts.join("\n\n");
|
|
4379
|
+
const maxPasses = opts.maxReducePasses ?? 3;
|
|
4380
|
+
for (let pass = 0; pass < maxPasses && estimateTokens(combined) > opts.targetTokens; pass++) {
|
|
4381
|
+
const groups = chunkText(combined, { maxTokens: opts.chunkTokens });
|
|
4382
|
+
if (groups.length <= 1) break;
|
|
4383
|
+
parts = await Promise.all(
|
|
4384
|
+
groups.map((g) => {
|
|
4385
|
+
calls++;
|
|
4386
|
+
return opts.summarize(g, "Merge these partial summaries into one cohesive summary; keep all distinct facts.");
|
|
4387
|
+
})
|
|
4388
|
+
);
|
|
4389
|
+
combined = parts.join("\n\n");
|
|
4390
|
+
}
|
|
4391
|
+
const note = truncated ? "\n\n[Note: input exceeded the extended-context cap and was truncated.]" : "";
|
|
4392
|
+
return { text: combined + note, chunks: chunks.length, calls, truncated };
|
|
4393
|
+
}
|
|
4394
|
+
async function rollingSummary(messages, opts) {
|
|
4395
|
+
if (messages.length <= opts.keepRecent) return messages;
|
|
4396
|
+
const older = messages.slice(0, messages.length - opts.keepRecent);
|
|
4397
|
+
const recent = messages.slice(messages.length - opts.keepRecent);
|
|
4398
|
+
const transcript = older.map((m) => `${m.role}: ${contentToText(m.content)}`).join("\n");
|
|
4399
|
+
const summary = await opts.summarize(
|
|
4400
|
+
transcript,
|
|
4401
|
+
"Summarize this earlier conversation. Preserve facts, decisions, names, numbers, and any open threads or todos."
|
|
4402
|
+
);
|
|
4403
|
+
const summaryMessage = {
|
|
4404
|
+
role: "system",
|
|
4405
|
+
content: `Summary of earlier conversation (folded to fit the context window):
|
|
4406
|
+
${summary}`
|
|
4407
|
+
};
|
|
4408
|
+
return [summaryMessage, ...recent];
|
|
4409
|
+
}
|
|
4410
|
+
|
|
4220
4411
|
// src/utils/retry.ts
|
|
4221
4412
|
var CascadeCancelledError = class extends Error {
|
|
4222
4413
|
constructor(reason) {
|
|
@@ -4251,6 +4442,158 @@ async function withTimeout(promise, timeoutMs, errorMessage = "Operation timed o
|
|
|
4251
4442
|
if (timer !== void 0) clearTimeout(timer);
|
|
4252
4443
|
}
|
|
4253
4444
|
}
|
|
4445
|
+
async function withTimeoutAbort(run, timeoutMs, errorMessage = "Operation timed out", outer) {
|
|
4446
|
+
const outers = (Array.isArray(outer) ? outer : [outer]).filter((sig) => sig !== void 0);
|
|
4447
|
+
const controller = new AbortController();
|
|
4448
|
+
let timer;
|
|
4449
|
+
let rejectRace;
|
|
4450
|
+
const settle = (reason) => {
|
|
4451
|
+
rejectRace?.(reason);
|
|
4452
|
+
controller.abort(reason);
|
|
4453
|
+
};
|
|
4454
|
+
const abortOuter = () => {
|
|
4455
|
+
const fired = outers.find((sig) => sig.aborted);
|
|
4456
|
+
settle(fired?.reason instanceof Error ? fired.reason : new CascadeCancelledError("Run cancelled"));
|
|
4457
|
+
};
|
|
4458
|
+
const failPromise = new Promise((_, reject) => {
|
|
4459
|
+
rejectRace = reject;
|
|
4460
|
+
timer = setTimeout(() => settle(new Error(errorMessage)), timeoutMs);
|
|
4461
|
+
});
|
|
4462
|
+
try {
|
|
4463
|
+
if (outers.some((sig) => sig.aborted)) {
|
|
4464
|
+
abortOuter();
|
|
4465
|
+
return await failPromise;
|
|
4466
|
+
}
|
|
4467
|
+
for (const sig of outers) sig.addEventListener("abort", abortOuter, { once: true });
|
|
4468
|
+
return await Promise.race([run(controller.signal), failPromise]);
|
|
4469
|
+
} finally {
|
|
4470
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
4471
|
+
for (const sig of outers) sig.removeEventListener("abort", abortOuter);
|
|
4472
|
+
}
|
|
4473
|
+
}
|
|
4474
|
+
function anySignal(sources) {
|
|
4475
|
+
const live = sources.filter((s) => s !== void 0);
|
|
4476
|
+
if (live.length === 0) return { release: () => {
|
|
4477
|
+
} };
|
|
4478
|
+
if (live.length === 1) return { signal: live[0], release: () => {
|
|
4479
|
+
} };
|
|
4480
|
+
const controller = new AbortController();
|
|
4481
|
+
function detach() {
|
|
4482
|
+
for (const s of live) s.removeEventListener("abort", onAbort);
|
|
4483
|
+
}
|
|
4484
|
+
function onAbort() {
|
|
4485
|
+
const fired = live.find((s) => s.aborted);
|
|
4486
|
+
controller.abort(fired?.reason instanceof Error ? fired.reason : new CascadeCancelledError("Run cancelled"));
|
|
4487
|
+
detach();
|
|
4488
|
+
}
|
|
4489
|
+
if (live.some((s) => s.aborted)) {
|
|
4490
|
+
onAbort();
|
|
4491
|
+
return { signal: controller.signal, release: detach };
|
|
4492
|
+
}
|
|
4493
|
+
for (const s of live) s.addEventListener("abort", onAbort, { once: true });
|
|
4494
|
+
return { signal: controller.signal, release: detach };
|
|
4495
|
+
}
|
|
4496
|
+
|
|
4497
|
+
// src/core/router/wire-profile.ts
|
|
4498
|
+
var textAndImageOnly = (block) => block.type === "text" || block.type === "image";
|
|
4499
|
+
var ANTHROPIC = {
|
|
4500
|
+
// convertMessages: `if (m.role === 'system') continue` — history system
|
|
4501
|
+
// messages are skipped outright; only options.systemPrompt goes as system
|
|
4502
|
+
// input. Compaction emits exactly such a message, holding a summary of the
|
|
4503
|
+
// whole conversation, so charging it refused runs over input never sent.
|
|
4504
|
+
dropsMessage: (m) => m.role === "system",
|
|
4505
|
+
blockHandling: (m) => {
|
|
4506
|
+
if (m.role === "tool") return "stringified";
|
|
4507
|
+
if (m.role === "assistant") return "dropped";
|
|
4508
|
+
return "blocks";
|
|
4509
|
+
},
|
|
4510
|
+
sendsToolCalls: (m) => m.role === "assistant",
|
|
4511
|
+
sendsBlock: textAndImageOnly,
|
|
4512
|
+
sendsToolCallId: true,
|
|
4513
|
+
sizeToolCalls: toAnthropicToolUse,
|
|
4514
|
+
sendsUrlImages: true,
|
|
4515
|
+
readsTopLevelImages: false,
|
|
4516
|
+
sizeTools: toAnthropicTools
|
|
4517
|
+
};
|
|
4518
|
+
var GEMINI = {
|
|
4519
|
+
// buildContents folds a system message into the next user turn, but only
|
|
4520
|
+
// when it has text: array content collapses to '' and the message is
|
|
4521
|
+
// skipped, as is a whitespace-only string.
|
|
4522
|
+
dropsMessage: (m) => m.role === "system" && !(typeof m.content === "string" ? m.content : "").trim(),
|
|
4523
|
+
blockHandling: (m) => {
|
|
4524
|
+
if (m.role === "tool") return "stringified";
|
|
4525
|
+
if (m.role === "assistant" || m.role === "system") return "dropped";
|
|
4526
|
+
return "blocks";
|
|
4527
|
+
},
|
|
4528
|
+
sendsToolCalls: (m) => m.role === "assistant",
|
|
4529
|
+
sendsBlock: textAndImageOnly,
|
|
4530
|
+
sendsToolCallId: true,
|
|
4531
|
+
sizeToolCalls: toGeminiFunctionCalls,
|
|
4532
|
+
sendsUrlImages: false,
|
|
4533
|
+
readsTopLevelImages: true,
|
|
4534
|
+
sizeTools: toGeminiTools
|
|
4535
|
+
};
|
|
4536
|
+
var OPENAI = {
|
|
4537
|
+
dropsMessage: () => false,
|
|
4538
|
+
blockHandling: (m) => {
|
|
4539
|
+
if (m.role === "system" || m.role === "tool") return "dropped";
|
|
4540
|
+
return "blocks";
|
|
4541
|
+
},
|
|
4542
|
+
// The tool_calls branch is inside `if (typeof m.content === 'string')`; an
|
|
4543
|
+
// assistant turn with array content falls through to the parts branch and
|
|
4544
|
+
// its tool calls are never attached.
|
|
4545
|
+
sendsToolCalls: (m) => m.role === "assistant" && typeof m.content === "string",
|
|
4546
|
+
sendsBlock: textAndImageOnly,
|
|
4547
|
+
sendsToolCallId: true,
|
|
4548
|
+
sizeToolCalls: toOpenAIToolCalls,
|
|
4549
|
+
sendsUrlImages: true,
|
|
4550
|
+
readsTopLevelImages: false,
|
|
4551
|
+
sizeTools: toOpenAITools
|
|
4552
|
+
};
|
|
4553
|
+
var OLLAMA = {
|
|
4554
|
+
dropsMessage: () => false,
|
|
4555
|
+
blockHandling: (m) => {
|
|
4556
|
+
if (m.role === "system") return "dropped";
|
|
4557
|
+
if (m.role === "tool") return "stringified";
|
|
4558
|
+
if (m.role === "assistant" && m.toolCalls?.length) return "dropped";
|
|
4559
|
+
return "blocks";
|
|
4560
|
+
},
|
|
4561
|
+
sendsToolCalls: (m) => m.role === "assistant",
|
|
4562
|
+
// The image split maps `b.image.data` with no check on `type`, so a URL is
|
|
4563
|
+
// submitted too (as a URL string in the `images` array).
|
|
4564
|
+
sendsBlock: textAndImageOnly,
|
|
4565
|
+
sendsToolCallId: false,
|
|
4566
|
+
sizeToolCalls: toOllamaToolCalls,
|
|
4567
|
+
sendsUrlImages: true,
|
|
4568
|
+
readsTopLevelImages: false,
|
|
4569
|
+
sizeTools: toOllamaTools
|
|
4570
|
+
};
|
|
4571
|
+
var PROFILES = {
|
|
4572
|
+
anthropic: ANTHROPIC,
|
|
4573
|
+
gemini: GEMINI,
|
|
4574
|
+
openai: OPENAI,
|
|
4575
|
+
// AzureOpenAIProvider and OpenAICompatibleProvider both extend OpenAIProvider
|
|
4576
|
+
// and neither overrides convertMessages.
|
|
4577
|
+
azure: OPENAI,
|
|
4578
|
+
"openai-compatible": OPENAI,
|
|
4579
|
+
ollama: OLLAMA
|
|
4580
|
+
};
|
|
4581
|
+
function wireProfile(provider) {
|
|
4582
|
+
return PROFILES[provider] ?? OPENAI;
|
|
4583
|
+
}
|
|
4584
|
+
function geminiImageCopies(messages) {
|
|
4585
|
+
for (const m of messages) {
|
|
4586
|
+
if (m.role === "user") {
|
|
4587
|
+
const laterUserTurn = messages.slice(messages.indexOf(m) + 1).some((x) => x.role === "user");
|
|
4588
|
+
return laterUserTurn ? 2 : 1;
|
|
4589
|
+
}
|
|
4590
|
+
const text = typeof m.content === "string" ? m.content : "";
|
|
4591
|
+
if (m.role === "system" && text.trim()) return 1;
|
|
4592
|
+
if (m.role === "tool") return 1;
|
|
4593
|
+
if (m.role === "assistant" && (text || m.toolCalls?.length)) return 1;
|
|
4594
|
+
}
|
|
4595
|
+
return 1;
|
|
4596
|
+
}
|
|
4254
4597
|
|
|
4255
4598
|
// src/core/router/model-profiler.ts
|
|
4256
4599
|
var SKIP_PATTERN = /embed|dall-e|whisper|tts|vision|instruct-vision|rerank/i;
|
|
@@ -4755,6 +5098,36 @@ function azureModelForTier(tier, ranked) {
|
|
|
4755
5098
|
if (tier === "T3") return ranked[ranked.length - 1];
|
|
4756
5099
|
return ranked[Math.min(1, ranked.length - 1)];
|
|
4757
5100
|
}
|
|
5101
|
+
var IMAGE_TOKENS_MAX = 2e3;
|
|
5102
|
+
var IMAGE_TOKENS_EACH = IMAGE_TOKENS_MAX;
|
|
5103
|
+
function safeJson(value) {
|
|
5104
|
+
try {
|
|
5105
|
+
return JSON.stringify(value) ?? "";
|
|
5106
|
+
} catch {
|
|
5107
|
+
return "";
|
|
5108
|
+
}
|
|
5109
|
+
}
|
|
5110
|
+
function newRunAbort() {
|
|
5111
|
+
const controller = new AbortController();
|
|
5112
|
+
setMaxListeners(64, controller.signal);
|
|
5113
|
+
return controller;
|
|
5114
|
+
}
|
|
5115
|
+
var TOKENS_PER_MESSAGE_FRAMING = 4;
|
|
5116
|
+
function guardTokens(text) {
|
|
5117
|
+
if (!text) return 0;
|
|
5118
|
+
let asciiChars = 0;
|
|
5119
|
+
let denseBytes = 0;
|
|
5120
|
+
for (const ch of text) {
|
|
5121
|
+
const cp = ch.codePointAt(0);
|
|
5122
|
+
if (cp <= 127) {
|
|
5123
|
+
asciiChars++;
|
|
5124
|
+
continue;
|
|
5125
|
+
}
|
|
5126
|
+
denseBytes += cp <= 2047 ? 2 : cp <= 65535 ? 3 : 4;
|
|
5127
|
+
}
|
|
5128
|
+
if (denseBytes === 0) return estimateTokens(text);
|
|
5129
|
+
return Math.ceil(asciiChars / CHARS_PER_TOKEN) + denseBytes;
|
|
5130
|
+
}
|
|
4758
5131
|
var DISCOVERY_TTL_MS = 15 * 60 * 1e3;
|
|
4759
5132
|
var DISCOVERY_TIMEOUT_MS = 4e3;
|
|
4760
5133
|
var cloudDiscoveryCache = /* @__PURE__ */ new Map();
|
|
@@ -4807,6 +5180,33 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
4807
5180
|
// start of every `cascade run`, independent of the session-wide budget.
|
|
4808
5181
|
runTokens = 0;
|
|
4809
5182
|
runCostUsd = 0;
|
|
5183
|
+
/**
|
|
5184
|
+
* Budget held for calls that are IN FLIGHT — admitted by the preflight check
|
|
5185
|
+
* but not yet counted by recordStats. Without these, concurrent calls all
|
|
5186
|
+
* measure themselves against the same unspent allowance and are all admitted.
|
|
5187
|
+
* Released when each call settles; reset by beginRun() alongside the totals.
|
|
5188
|
+
*/
|
|
5189
|
+
reservedTokens = 0;
|
|
5190
|
+
reservedCostUsd = 0;
|
|
5191
|
+
/**
|
|
5192
|
+
* Fires when this run's budget is spent, cancelling work already in flight.
|
|
5193
|
+
*
|
|
5194
|
+
* The kill switch was previously only a flag, which stops calls that have
|
|
5195
|
+
* not yet submitted. A parallel wave's earlier members are already at the
|
|
5196
|
+
* provider by then, and they carried on generating for a run whose output
|
|
5197
|
+
* would be discarded. Every provider call chains to this, so tripping the
|
|
5198
|
+
* ceiling reaches the requests that are running, not just the ones queued.
|
|
5199
|
+
*/
|
|
5200
|
+
runAbort = newRunAbort();
|
|
5201
|
+
/**
|
|
5202
|
+
* Bumped by every beginRun(). A release handle remembers the generation it
|
|
5203
|
+
* was issued in and does nothing if that has moved on: beginRun() zeroes the
|
|
5204
|
+
* counters, so a call still in flight from the previous run would otherwise
|
|
5205
|
+
* subtract its estimate from the NEW run's totals and drive them negative —
|
|
5206
|
+
* leaving later preflights believing there is more allowance than the cap
|
|
5207
|
+
* actually holds, which is worse than no gate at all.
|
|
5208
|
+
*/
|
|
5209
|
+
runGeneration = 0;
|
|
4810
5210
|
runBudgetExceeded = false;
|
|
4811
5211
|
runBudgetExceededReason;
|
|
4812
5212
|
/**
|
|
@@ -4976,7 +5376,7 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
4976
5376
|
try {
|
|
4977
5377
|
const cfg = this.config.providers.find((p) => p.type === model.provider) ?? { type: model.provider };
|
|
4978
5378
|
const provider = this.createProvider(cfg, model);
|
|
4979
|
-
const result = await
|
|
5379
|
+
const result = await withTimeoutAbort((signal) => provider.generate({
|
|
4980
5380
|
messages: [{ role: "user", content: "Call the echo tool with text set to 'ping'. Use the tool; do not answer in prose." }],
|
|
4981
5381
|
tools: [{
|
|
4982
5382
|
name: "echo",
|
|
@@ -4984,7 +5384,8 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
4984
5384
|
inputSchema: { type: "object", properties: { text: { type: "string", description: "Text to echo" } }, required: ["text"] }
|
|
4985
5385
|
}],
|
|
4986
5386
|
maxTokens: 80,
|
|
4987
|
-
temperature: 0
|
|
5387
|
+
temperature: 0,
|
|
5388
|
+
signal
|
|
4988
5389
|
}), 3e4, "tool-support probe timed out");
|
|
4989
5390
|
return (result.toolCalls?.length ?? 0) > 0;
|
|
4990
5391
|
} catch {
|
|
@@ -5105,9 +5506,12 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
5105
5506
|
this.runBudgetExceededReason ?? "Per-task budget exceeded."
|
|
5106
5507
|
);
|
|
5107
5508
|
}
|
|
5509
|
+
const runGeneration = this.runGeneration;
|
|
5510
|
+
const runAbort = this.runAbort;
|
|
5511
|
+
const runSignal = this.runSignal;
|
|
5108
5512
|
options = applyTierLimits(options, tier, this.config?.tierLimits);
|
|
5109
|
-
if (
|
|
5110
|
-
options = { ...options, signal:
|
|
5513
|
+
if (runSignal && !options.signal) {
|
|
5514
|
+
options = { ...options, signal: runSignal };
|
|
5111
5515
|
}
|
|
5112
5516
|
if (options.model && !requireVision) {
|
|
5113
5517
|
this.ensureProvider(options.model, this.config.providers);
|
|
@@ -5129,58 +5533,84 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
5129
5533
|
if (!model) throw new Error(`No model available for tier ${tier}`);
|
|
5130
5534
|
const provider = this.getProvider(model);
|
|
5131
5535
|
if (!provider) throw new Error(`No provider for model ${model.id}`);
|
|
5536
|
+
let releaseReservation = this.enforcePreflightBudget(model, options);
|
|
5132
5537
|
const requestedTokens = options.maxTokens ?? model.maxOutputTokens ?? 1024;
|
|
5133
5538
|
const estimatedTokens = Math.min(requestedTokens, model.maxOutputTokens ?? requestedTokens) + 512;
|
|
5134
5539
|
if (this.tpmLimiter) {
|
|
5135
|
-
|
|
5540
|
+
const wait = anySignal([options.signal, runAbort.signal]);
|
|
5541
|
+
try {
|
|
5542
|
+
await this.tpmLimiter.acquire(model.provider, estimatedTokens, wait.signal);
|
|
5543
|
+
} catch (err) {
|
|
5544
|
+
releaseReservation?.();
|
|
5545
|
+
throw this.asAbortFailure(err, options.signal, runSignal);
|
|
5546
|
+
} finally {
|
|
5547
|
+
wait.release();
|
|
5548
|
+
}
|
|
5136
5549
|
}
|
|
5137
5550
|
const useStream = Boolean(onChunk) && model.supportsStreaming && typeof provider.generateStream === "function";
|
|
5138
5551
|
let releaseLocalSlot;
|
|
5139
5552
|
if (model.isLocal) {
|
|
5140
5553
|
const inferenceTimeoutMs = this.config.localInferenceTimeoutMs ?? 3e5;
|
|
5141
5554
|
const queueWaitMs = Math.round(inferenceTimeoutMs / 2);
|
|
5142
|
-
|
|
5555
|
+
const wait = anySignal([options.signal, runAbort.signal]);
|
|
5556
|
+
try {
|
|
5557
|
+
releaseLocalSlot = await this.localQueue.acquire(queueWaitMs, wait.signal);
|
|
5558
|
+
} catch (err) {
|
|
5559
|
+
releaseReservation?.();
|
|
5560
|
+
throw this.asAbortFailure(err, options.signal, runSignal);
|
|
5561
|
+
} finally {
|
|
5562
|
+
wait.release();
|
|
5563
|
+
}
|
|
5143
5564
|
}
|
|
5144
5565
|
try {
|
|
5566
|
+
if (runGeneration === this.runGeneration && this.runBudgetExceeded) {
|
|
5567
|
+
this.tpmLimiter?.refund(model.provider, estimatedTokens);
|
|
5568
|
+
throw new _CascadeRouter.BudgetExceededError(
|
|
5569
|
+
this.runBudgetExceededReason ?? "Per-task budget exceeded."
|
|
5570
|
+
);
|
|
5571
|
+
}
|
|
5145
5572
|
let result;
|
|
5146
5573
|
if (model.isLocal) {
|
|
5147
5574
|
const inferenceTimeoutMs = this.config.localInferenceTimeoutMs ?? 3e5;
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5153
|
-
inferencePromise,
|
|
5575
|
+
result = await withTimeoutAbort(
|
|
5576
|
+
(signal) => useStream && onChunk ? provider.generateStream({ ...options, signal }, (chunk) => {
|
|
5577
|
+
const text = typeof chunk?.text === "string" ? chunk.text : "";
|
|
5578
|
+
if (text) onChunk({ ...chunk, text });
|
|
5579
|
+
}) : provider.generate({ ...options, signal }),
|
|
5154
5580
|
inferenceTimeoutMs,
|
|
5155
|
-
`Local model ${model.id} inference timed out after ${inferenceTimeoutMs}ms
|
|
5581
|
+
`Local model ${model.id} inference timed out after ${inferenceTimeoutMs}ms`,
|
|
5582
|
+
[options.signal, runAbort.signal]
|
|
5156
5583
|
);
|
|
5157
5584
|
} else if (useStream && onChunk) {
|
|
5158
5585
|
const cloudTimeoutMs = this.config.cloudInferenceTimeoutMs ?? 12e4;
|
|
5159
5586
|
try {
|
|
5160
|
-
result = await
|
|
5161
|
-
provider.generateStream(options, (chunk) => {
|
|
5587
|
+
result = await withTimeoutAbort(
|
|
5588
|
+
(signal) => provider.generateStream({ ...options, signal }, (chunk) => {
|
|
5162
5589
|
const text = typeof chunk?.text === "string" ? chunk.text : "";
|
|
5163
5590
|
if (text) onChunk({ ...chunk, text });
|
|
5164
5591
|
}),
|
|
5165
5592
|
cloudTimeoutMs,
|
|
5166
|
-
`Model ${model.id} stream timed out after ${cloudTimeoutMs}ms
|
|
5593
|
+
`Model ${model.id} stream timed out after ${cloudTimeoutMs}ms`,
|
|
5594
|
+
[options.signal, runAbort.signal]
|
|
5167
5595
|
);
|
|
5168
5596
|
} catch (streamErr) {
|
|
5169
|
-
if (streamErr instanceof Error && streamErr.name === "AbortError" ||
|
|
5597
|
+
if (streamErr instanceof Error && streamErr.name === "AbortError" || runSignal?.aborted || options.signal?.aborted) {
|
|
5170
5598
|
throw streamErr;
|
|
5171
5599
|
}
|
|
5172
|
-
result = await
|
|
5173
|
-
provider.generate(options),
|
|
5600
|
+
result = await withTimeoutAbort(
|
|
5601
|
+
(signal) => provider.generate({ ...options, signal }),
|
|
5174
5602
|
cloudTimeoutMs,
|
|
5175
|
-
`Model ${model.id} inference timed out after ${cloudTimeoutMs}ms
|
|
5603
|
+
`Model ${model.id} inference timed out after ${cloudTimeoutMs}ms`,
|
|
5604
|
+
[options.signal, runAbort.signal]
|
|
5176
5605
|
);
|
|
5177
5606
|
}
|
|
5178
5607
|
} else {
|
|
5179
5608
|
const cloudTimeoutMs = this.config.cloudInferenceTimeoutMs ?? 12e4;
|
|
5180
|
-
result = await
|
|
5181
|
-
provider.generate(options),
|
|
5609
|
+
result = await withTimeoutAbort(
|
|
5610
|
+
(signal) => provider.generate({ ...options, signal }),
|
|
5182
5611
|
cloudTimeoutMs,
|
|
5183
|
-
`Model ${model.id} inference timed out after ${cloudTimeoutMs}ms
|
|
5612
|
+
`Model ${model.id} inference timed out after ${cloudTimeoutMs}ms`,
|
|
5613
|
+
[options.signal, runAbort.signal]
|
|
5184
5614
|
);
|
|
5185
5615
|
}
|
|
5186
5616
|
const corrected = buildTokenUsage(
|
|
@@ -5199,11 +5629,12 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
5199
5629
|
if (!result || typeof result.content !== "string" || !result.usage) {
|
|
5200
5630
|
throw new Error(`Provider ${model.provider}:${model.id} returned an invalid generation result.`);
|
|
5201
5631
|
}
|
|
5202
|
-
this.recordStats(tier, model, result.usage, options.featureTag);
|
|
5632
|
+
this.recordStats(tier, model, result.usage, options.featureTag, runGeneration);
|
|
5203
5633
|
this.failover.recordSuccess(model.provider);
|
|
5204
5634
|
return result;
|
|
5205
5635
|
} catch (err) {
|
|
5206
|
-
if (err instanceof
|
|
5636
|
+
if (err instanceof _CascadeRouter.BudgetExceededError) throw err;
|
|
5637
|
+
if (err instanceof Error && err.name === "AbortError" || runSignal?.aborted || options.signal?.aborted) {
|
|
5207
5638
|
throw new CascadeCancelledError("Run cancelled");
|
|
5208
5639
|
}
|
|
5209
5640
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
@@ -5221,6 +5652,8 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
5221
5652
|
});
|
|
5222
5653
|
releaseLocalSlot?.();
|
|
5223
5654
|
releaseLocalSlot = void 0;
|
|
5655
|
+
releaseReservation?.();
|
|
5656
|
+
releaseReservation = void 0;
|
|
5224
5657
|
const retryOpts = options.model && options.model.id === model.id ? { ...options, model: void 0 } : options;
|
|
5225
5658
|
return this.generate(tier, retryOpts, onChunk, requireVision);
|
|
5226
5659
|
}
|
|
@@ -5242,6 +5675,8 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
5242
5675
|
});
|
|
5243
5676
|
releaseLocalSlot?.();
|
|
5244
5677
|
releaseLocalSlot = void 0;
|
|
5678
|
+
releaseReservation?.();
|
|
5679
|
+
releaseReservation = void 0;
|
|
5245
5680
|
const base = options.model && options.model.id === model.id ? { ...options, model: void 0 } : options;
|
|
5246
5681
|
const retryOpts = { ...base, _notFoundDepth: depth };
|
|
5247
5682
|
return this.generate(tier, retryOpts, onChunk, requireVision);
|
|
@@ -5250,6 +5685,8 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
5250
5685
|
throw err;
|
|
5251
5686
|
} finally {
|
|
5252
5687
|
releaseLocalSlot?.();
|
|
5688
|
+
releaseReservation?.();
|
|
5689
|
+
releaseReservation = void 0;
|
|
5253
5690
|
}
|
|
5254
5691
|
}
|
|
5255
5692
|
getModelForTier(tier) {
|
|
@@ -5620,7 +6057,14 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
5620
6057
|
}
|
|
5621
6058
|
return void 0;
|
|
5622
6059
|
}
|
|
5623
|
-
|
|
6060
|
+
/**
|
|
6061
|
+
* `runGeneration` is the generation the call was ADMITTED in — see
|
|
6062
|
+
* `generate()`. Session-wide accounting always applies (the spend is real
|
|
6063
|
+
* whenever it lands), but the per-RUN ceiling is only meaningful for the run
|
|
6064
|
+
* the call belongs to, so a straggler from a finished run is left out of it.
|
|
6065
|
+
* Omitted by the direct callers in tests, which have no run to straddle.
|
|
6066
|
+
*/
|
|
6067
|
+
recordStats(tier, model, usage, featureTag, runGeneration) {
|
|
5624
6068
|
this.stats.totalTokens += usage.totalTokens;
|
|
5625
6069
|
this.stats.totalCostUsd += usage.estimatedCostUsd;
|
|
5626
6070
|
this.sessionCostUsd += usage.estimatedCostUsd;
|
|
@@ -5645,10 +6089,13 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
5645
6089
|
});
|
|
5646
6090
|
}
|
|
5647
6091
|
}
|
|
5648
|
-
|
|
5649
|
-
|
|
6092
|
+
const currentRun = runGeneration === void 0 || runGeneration === this.runGeneration;
|
|
6093
|
+
if (currentRun) {
|
|
6094
|
+
this.runTokens += usage.totalTokens;
|
|
6095
|
+
this.runCostUsd += usage.estimatedCostUsd;
|
|
6096
|
+
}
|
|
5650
6097
|
this.updateBudgetState();
|
|
5651
|
-
this.enforceRunBudget();
|
|
6098
|
+
if (currentRun) this.enforceRunBudget();
|
|
5652
6099
|
}
|
|
5653
6100
|
/**
|
|
5654
6101
|
* Resets per-run accounting at the start of each `cascade run`. Session
|
|
@@ -5658,9 +6105,158 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
5658
6105
|
beginRun() {
|
|
5659
6106
|
this.runTokens = 0;
|
|
5660
6107
|
this.runCostUsd = 0;
|
|
6108
|
+
this.reservedTokens = 0;
|
|
6109
|
+
this.reservedCostUsd = 0;
|
|
6110
|
+
this.runAbort = newRunAbort();
|
|
6111
|
+
this.runGeneration++;
|
|
5661
6112
|
this.runBudgetExceeded = false;
|
|
5662
6113
|
this.runBudgetExceededReason = void 0;
|
|
5663
6114
|
}
|
|
6115
|
+
/**
|
|
6116
|
+
* Refuse a call whose input alone cannot fit what is left of the run budget.
|
|
6117
|
+
*
|
|
6118
|
+
* `enforceRunBudget()` below runs AFTER a call returns, which makes the caps
|
|
6119
|
+
* a stop rather than a pre-authorisation: the tokens are already bought by
|
|
6120
|
+
* the time it looks. That was tolerable while a prompt could not exceed
|
|
6121
|
+
* 20,000 characters, and stopped being so when that cap was removed in
|
|
6122
|
+
* 0.72.0 — a multi-megabyte prompt is billed in full on the very first call
|
|
6123
|
+
* (the complexity classifier sends the whole thing), and no ceiling
|
|
6124
|
+
* configured downstream can give that money back.
|
|
6125
|
+
*
|
|
6126
|
+
* Deliberately narrow, because a false refusal is worse than a late stop:
|
|
6127
|
+
*
|
|
6128
|
+
* • INPUT ONLY. What a call returns is not knowable in advance, and
|
|
6129
|
+
* charging a worst-case `maxTokens` of output against the budget would
|
|
6130
|
+
* decline runs that would have finished comfortably. Input is the part
|
|
6131
|
+
* that is already determined, and it is the part the removed cap used to
|
|
6132
|
+
* bound.
|
|
6133
|
+
* • Compared against what REMAINS (`cap - spent`), not the whole cap, so a
|
|
6134
|
+
* call is judged on the budget it can actually draw from.
|
|
6135
|
+
* • Skipped when the model has no usable price. An estimate cannot be made,
|
|
6136
|
+
* and refusing on ignorance would break every self-hosted and local model
|
|
6137
|
+
* the moment a cost cap is set. The post-hoc stop still applies there.
|
|
6138
|
+
* • Skipped entirely when no cap is configured — there is nothing to
|
|
6139
|
+
* pre-authorise against.
|
|
6140
|
+
*
|
|
6141
|
+
* The message names the cap AND the estimate, because "too expensive" with
|
|
6142
|
+
* no numbers gives the user nothing to change.
|
|
6143
|
+
*/
|
|
6144
|
+
enforcePreflightBudget(model, options) {
|
|
6145
|
+
const budget = this.config?.budget;
|
|
6146
|
+
const maxCost = budget?.maxCostPerRunUsd;
|
|
6147
|
+
const maxTokens = budget?.maxTokensPerRun;
|
|
6148
|
+
if (maxCost == null && maxTokens == null) return void 0;
|
|
6149
|
+
const inputTokens = this.estimateInputTokens(options, model);
|
|
6150
|
+
if (maxTokens != null) {
|
|
6151
|
+
const remaining = maxTokens - this.runTokens - this.reservedTokens;
|
|
6152
|
+
if (inputTokens > remaining) {
|
|
6153
|
+
this.failPreflight(
|
|
6154
|
+
`This request's input is about ${inputTokens.toLocaleString()} tokens, but only ${Math.max(0, remaining).toLocaleString()} of the per-task cap of ${maxTokens.toLocaleString()} remain. Shorten the input, or raise budget.maxTokensPerRun.`
|
|
6155
|
+
);
|
|
6156
|
+
}
|
|
6157
|
+
}
|
|
6158
|
+
let estimatedUsd = 0;
|
|
6159
|
+
if (maxCost != null) {
|
|
6160
|
+
const { input: inputPer1k, unknown } = resolveModelPricing(model, { inputTokens });
|
|
6161
|
+
if (!unknown && inputPer1k > 0) {
|
|
6162
|
+
estimatedUsd = inputTokens / 1e3 * inputPer1k;
|
|
6163
|
+
const remaining = maxCost - this.runCostUsd - this.reservedCostUsd;
|
|
6164
|
+
if (estimatedUsd > remaining) {
|
|
6165
|
+
this.failPreflight(
|
|
6166
|
+
`Sending this request to ${model.provider}:${model.id} would cost about $${estimatedUsd.toFixed(4)} in input alone (~${inputTokens.toLocaleString()} tokens), and only $${Math.max(0, remaining).toFixed(4)} of the per-task cap of $${maxCost.toFixed(4)} remains. Shorten the input, choose a cheaper model, or raise budget.maxCostPerRunUsd.`
|
|
6167
|
+
);
|
|
6168
|
+
}
|
|
6169
|
+
}
|
|
6170
|
+
}
|
|
6171
|
+
this.reservedTokens += inputTokens;
|
|
6172
|
+
this.reservedCostUsd += estimatedUsd;
|
|
6173
|
+
const generation = this.runGeneration;
|
|
6174
|
+
let released = false;
|
|
6175
|
+
return () => {
|
|
6176
|
+
if (released || generation !== this.runGeneration) return;
|
|
6177
|
+
released = true;
|
|
6178
|
+
this.reservedTokens -= inputTokens;
|
|
6179
|
+
this.reservedCostUsd -= estimatedUsd;
|
|
6180
|
+
};
|
|
6181
|
+
}
|
|
6182
|
+
/**
|
|
6183
|
+
* What this call will send, in tokens.
|
|
6184
|
+
*
|
|
6185
|
+
* Text is the bulk of it, but not all of it: providers bill the serialized
|
|
6186
|
+
* TOOL DEFINITIONS on every native-tool call (Anthropic sends each name,
|
|
6187
|
+
* description and input schema), and an image is billed by its own size
|
|
6188
|
+
* rather than as the `[image]` placeholder `contentToText` reduces it to. A
|
|
6189
|
+
* tool-heavy or vision request could therefore pass either cap while its real
|
|
6190
|
+
* input already exceeded the allowance.
|
|
6191
|
+
*/
|
|
6192
|
+
estimateInputTokens(options, model) {
|
|
6193
|
+
let tokens = guardTokens(options.systemPrompt ?? "");
|
|
6194
|
+
let images = 0;
|
|
6195
|
+
const wire = wireProfile(model.provider);
|
|
6196
|
+
const countsImage = (img) => wire.sendsUrlImages || img?.type === "base64";
|
|
6197
|
+
for (const message of options.messages) {
|
|
6198
|
+
if (wire.dropsMessage(message)) continue;
|
|
6199
|
+
tokens += TOKENS_PER_MESSAGE_FRAMING;
|
|
6200
|
+
if (typeof message.content === "string") {
|
|
6201
|
+
tokens += guardTokens(message.content);
|
|
6202
|
+
} else {
|
|
6203
|
+
switch (wire.blockHandling(message)) {
|
|
6204
|
+
case "blocks":
|
|
6205
|
+
for (const block of message.content) {
|
|
6206
|
+
if (!wire.sendsBlock(block)) continue;
|
|
6207
|
+
if (block.type === "image") {
|
|
6208
|
+
if (countsImage(block.image)) images++;
|
|
6209
|
+
continue;
|
|
6210
|
+
}
|
|
6211
|
+
tokens += guardTokens(contentToText([block]));
|
|
6212
|
+
}
|
|
6213
|
+
break;
|
|
6214
|
+
case "stringified":
|
|
6215
|
+
tokens += guardTokens(safeJson(message.content));
|
|
6216
|
+
break;
|
|
6217
|
+
}
|
|
6218
|
+
}
|
|
6219
|
+
if (message.toolCalls?.length && wire.sendsToolCalls(message)) {
|
|
6220
|
+
tokens += guardTokens(safeJson(wire.sizeToolCalls(message.toolCalls)));
|
|
6221
|
+
}
|
|
6222
|
+
if (message.toolCallId && message.role === "tool" && wire.sendsToolCallId) {
|
|
6223
|
+
tokens += guardTokens(message.toolCallId);
|
|
6224
|
+
}
|
|
6225
|
+
}
|
|
6226
|
+
if (options.tools?.length) {
|
|
6227
|
+
tokens += guardTokens(safeJson(wire.sizeTools(options.tools)));
|
|
6228
|
+
}
|
|
6229
|
+
if (wire.readsTopLevelImages) {
|
|
6230
|
+
const topLevel = (options.images ?? []).filter(countsImage).length;
|
|
6231
|
+
images += topLevel * geminiImageCopies(options.messages);
|
|
6232
|
+
}
|
|
6233
|
+
tokens += images * IMAGE_TOKENS_EACH;
|
|
6234
|
+
return tokens;
|
|
6235
|
+
}
|
|
6236
|
+
/**
|
|
6237
|
+
* Report a wait that ended because something asked it to stop, the same way
|
|
6238
|
+
* the provider call below reports one.
|
|
6239
|
+
*
|
|
6240
|
+
* The queue waits sit OUTSIDE the try/catch that maps aborts, so without
|
|
6241
|
+
* this a cancellation surfaced as whatever the queue happened to throw and
|
|
6242
|
+
* was retried or failed over like an ordinary error. A budget abort keeps
|
|
6243
|
+
* saying it ran out of budget — otherwise the reason the run stopped is lost
|
|
6244
|
+
* on the way up — and anything else that was aborted becomes a cancellation.
|
|
6245
|
+
*/
|
|
6246
|
+
asAbortFailure(err, callSignal, runSignal) {
|
|
6247
|
+
if (err instanceof _CascadeRouter.BudgetExceededError) return err;
|
|
6248
|
+
if (err instanceof CascadeCancelledError) return err;
|
|
6249
|
+
if (callSignal?.aborted || runSignal?.aborted) return new CascadeCancelledError("Run cancelled");
|
|
6250
|
+
return err;
|
|
6251
|
+
}
|
|
6252
|
+
/** Trips the same run-level flag a post-hoc overrun does, and reports it the same way. */
|
|
6253
|
+
failPreflight(reason) {
|
|
6254
|
+
this.runBudgetExceeded = true;
|
|
6255
|
+
this.runBudgetExceededReason = reason;
|
|
6256
|
+
this.runAbort.abort(new _CascadeRouter.BudgetExceededError(reason));
|
|
6257
|
+
this.emit("budget:exceeded", { reason, spentUsd: this.sessionCostUsd });
|
|
6258
|
+
throw new _CascadeRouter.BudgetExceededError(reason);
|
|
6259
|
+
}
|
|
5664
6260
|
/**
|
|
5665
6261
|
* Enforce the hard per-task ceiling. Once tripped, the flag makes every
|
|
5666
6262
|
* subsequent (and concurrent) generate() call in this run fail fast.
|
|
@@ -5676,6 +6272,7 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
5676
6272
|
const reason = overTokens ? `Per-task token cap of ${maxTokens.toLocaleString()} reached (used ${this.runTokens.toLocaleString()}). Stopping this run to avoid runaway cost \u2014 raise budget.maxTokensPerRun for larger jobs.` : `Per-task cost cap of $${maxCost.toFixed(4)} reached (spent $${this.runCostUsd.toFixed(4)}). Stopping this run to avoid runaway cost.`;
|
|
5677
6273
|
this.runBudgetExceeded = true;
|
|
5678
6274
|
this.runBudgetExceededReason = reason;
|
|
6275
|
+
this.runAbort.abort(new _CascadeRouter.BudgetExceededError(reason));
|
|
5679
6276
|
this.emit("budget:exceeded", { reason, spentUsd: this.sessionCostUsd });
|
|
5680
6277
|
throw new _CascadeRouter.BudgetExceededError(reason);
|
|
5681
6278
|
}
|
|
@@ -6912,6 +7509,12 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : "") + textToolSuffix,
|
|
|
6912
7509
|
maxTokens: 4096,
|
|
6913
7510
|
...subtaskModel ? { model: subtaskModel } : {},
|
|
6914
7511
|
featureTag: this.assignment?.sectionTitle,
|
|
7512
|
+
// The wave signal, so cancel-and-respawn reaches the request that is
|
|
7513
|
+
// RUNNING rather than only stopping the worker at its next checkpoint.
|
|
7514
|
+
// It is a superset of the run signal the router injects when this is
|
|
7515
|
+
// absent (T2 composes it from that signal plus its own per-wave one),
|
|
7516
|
+
// so wiring it here loses no cancellation and adds the wave's.
|
|
7517
|
+
...this.signal ? { signal: this.signal } : {},
|
|
6915
7518
|
...this.localOnlyMatch ? { forceLocal: true } : {}
|
|
6916
7519
|
};
|
|
6917
7520
|
const result = await this.router.generate(
|
|
@@ -7252,6 +7855,22 @@ ${assignment.expectedOutput}`;
|
|
|
7252
7855
|
requiresArtifact() {
|
|
7253
7856
|
return shouldRequireArtifact(this.assignment, this.tools.map((t) => t.name));
|
|
7254
7857
|
}
|
|
7858
|
+
/**
|
|
7859
|
+
* Where a file a tool wrote actually lives.
|
|
7860
|
+
*
|
|
7861
|
+
* This used to be `process.cwd()`, which is only the workspace in a plain
|
|
7862
|
+
* CLI run. The hosted server runs with cwd at the app directory while the
|
|
7863
|
+
* run's workspace is the tenant's scratch dir, and Electron's cwd is the app
|
|
7864
|
+
* bundle rather than the folder the user picked. So the tool wrote
|
|
7865
|
+
* `<workspace>/report.docx`, verification stat'd `<cwd>/report.docx`, found
|
|
7866
|
+
* nothing, and the worker looped and then threw WorkerStallError — surfacing
|
|
7867
|
+
* as "no file was created" on a file that had in fact been created, followed
|
|
7868
|
+
* by a failed node. Asking the registry is exact: it is the same root every
|
|
7869
|
+
* registered tool was configured with (registry.registerDefaults).
|
|
7870
|
+
*/
|
|
7871
|
+
artifactRoot() {
|
|
7872
|
+
return this.toolRegistry.getWorkspaceRoot?.() ?? process.cwd();
|
|
7873
|
+
}
|
|
7255
7874
|
extractArtifactPaths(assignment) {
|
|
7256
7875
|
const declared = (assignment.files ?? []).map((f) => f.trim()).filter((f) => f.includes("."));
|
|
7257
7876
|
const haystack = `${assignment.description}
|
|
@@ -7263,11 +7882,11 @@ ${assignment.expectedOutput}`;
|
|
|
7263
7882
|
const artifactPaths = this.extractArtifactPaths(assignment);
|
|
7264
7883
|
if (!artifactPaths.length) return { ok: true, issues: [] };
|
|
7265
7884
|
const issues = [];
|
|
7266
|
-
const {
|
|
7885
|
+
const { execFile: execFile4 } = await import('child_process');
|
|
7267
7886
|
const { promisify: promisify4 } = await import('util');
|
|
7268
|
-
const
|
|
7887
|
+
const execFileAsync3 = promisify4(execFile4);
|
|
7269
7888
|
for (const artifactPath of artifactPaths) {
|
|
7270
|
-
const absolutePath = path26.resolve(
|
|
7889
|
+
const absolutePath = path26.resolve(this.artifactRoot(), artifactPath);
|
|
7271
7890
|
try {
|
|
7272
7891
|
const stat = await fs5.stat(absolutePath);
|
|
7273
7892
|
if (!stat.isFile()) {
|
|
@@ -7303,20 +7922,18 @@ ${assignment.expectedOutput}`;
|
|
|
7303
7922
|
continue;
|
|
7304
7923
|
}
|
|
7305
7924
|
const ext = path26.extname(absolutePath).toLowerCase();
|
|
7306
|
-
|
|
7307
|
-
|
|
7308
|
-
|
|
7309
|
-
|
|
7310
|
-
|
|
7311
|
-
|
|
7312
|
-
|
|
7925
|
+
const check = ext === ".ts" || ext === ".tsx" ? { cmd: "npx", args: ["tsc", "--noEmit", absolutePath] } : ext === ".js" || ext === ".jsx" ? { cmd: process.execPath, args: ["--check", absolutePath] } : ext === ".py" ? { cmd: "python", args: ["-m", "py_compile", absolutePath] } : null;
|
|
7926
|
+
if (check) {
|
|
7927
|
+
try {
|
|
7928
|
+
await execFileAsync3(check.cmd, check.args, { timeout: 1e4 });
|
|
7929
|
+
} catch (err) {
|
|
7930
|
+
const code = err.code;
|
|
7931
|
+
if (code === "ENOENT" || code === "EINVAL") continue;
|
|
7932
|
+
const e = err;
|
|
7933
|
+
issues.push(`Semantic error in ${artifactPath}:
|
|
7934
|
+
${e.stderr || String(err)}
|
|
7935
|
+
${e.stdout || ""}`);
|
|
7313
7936
|
}
|
|
7314
|
-
} catch (err) {
|
|
7315
|
-
const stderr = err?.stderr || String(err);
|
|
7316
|
-
const stdout = err?.stdout || "";
|
|
7317
|
-
issues.push(`Semantic error in ${artifactPath}:
|
|
7318
|
-
${stderr}
|
|
7319
|
-
${stdout}`);
|
|
7320
7937
|
}
|
|
7321
7938
|
} catch {
|
|
7322
7939
|
issues.push(`Required artifact was not created: ${artifactPath}`);
|
|
@@ -7376,6 +7993,7 @@ ${current}`
|
|
|
7376
7993
|
HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
7377
7994
|
maxTokens: 4096,
|
|
7378
7995
|
featureTag: assignment.sectionTitle,
|
|
7996
|
+
...this.signal ? { signal: this.signal } : {},
|
|
7379
7997
|
...this.localOnlyMatch ? { forceLocal: true } : {}
|
|
7380
7998
|
});
|
|
7381
7999
|
const next = (improved.content ?? "").trim();
|
|
@@ -7395,7 +8013,7 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
7395
8013
|
async checkAcceptance(assignment) {
|
|
7396
8014
|
const criteria = assignment.acceptance ?? [];
|
|
7397
8015
|
if (!criteria.length) return [];
|
|
7398
|
-
const resolve2 = (target) => path26.resolve(
|
|
8016
|
+
const resolve2 = (target) => path26.resolve(this.artifactRoot(), target);
|
|
7399
8017
|
return evaluateAcceptance(criteria, assignment.files ?? [], {
|
|
7400
8018
|
stat: async (target) => {
|
|
7401
8019
|
try {
|
|
@@ -7437,6 +8055,7 @@ Reply with JSON: { "completeness": "pass"|"fail", "correctness": "pass"|"fail",
|
|
|
7437
8055
|
|
|
7438
8056
|
HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
7439
8057
|
featureTag: assignment.sectionTitle,
|
|
8058
|
+
...this.signal ? { signal: this.signal } : {},
|
|
7440
8059
|
...this.localOnlyMatch ? { forceLocal: true } : {}
|
|
7441
8060
|
});
|
|
7442
8061
|
try {
|
|
@@ -7476,6 +8095,7 @@ ${output.slice(0, 4e3)}`;
|
|
|
7476
8095
|
messages: [{ role: "user", content: prompt }],
|
|
7477
8096
|
maxTokens: 300,
|
|
7478
8097
|
temperature: 0,
|
|
8098
|
+
...this.signal ? { signal: this.signal } : {},
|
|
7479
8099
|
...this.localOnlyMatch ? { forceLocal: true } : {}
|
|
7480
8100
|
});
|
|
7481
8101
|
const match = /\[[\s\S]*\]/.exec(result.content);
|
|
@@ -7587,6 +8207,10 @@ Begin execution now.`;
|
|
|
7587
8207
|
}
|
|
7588
8208
|
};
|
|
7589
8209
|
var PeerBus = class extends EventEmitter {
|
|
8210
|
+
constructor() {
|
|
8211
|
+
super();
|
|
8212
|
+
this.setMaxListeners(64);
|
|
8213
|
+
}
|
|
7590
8214
|
outputs = /* @__PURE__ */ new Map();
|
|
7591
8215
|
waiters = /* @__PURE__ */ new Map();
|
|
7592
8216
|
members = /* @__PURE__ */ new Set();
|
|
@@ -8834,6 +9458,7 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
8834
9458
|
const waveSignal = AbortSignal.any(
|
|
8835
9459
|
[this.signal, this.waveAbortController.signal].filter(Boolean)
|
|
8836
9460
|
);
|
|
9461
|
+
setMaxListeners(64, waveSignal);
|
|
8837
9462
|
const runOne = async (id) => {
|
|
8838
9463
|
const assignment = sanitizedAssignments.find((a) => a.subtaskId === id);
|
|
8839
9464
|
const worker = workerMap.get(id);
|
|
@@ -12983,6 +13608,18 @@ var ToolRegistry = class extends EventEmitter {
|
|
|
12983
13608
|
this.tools.set(tool.name, tool);
|
|
12984
13609
|
this.emit("tool:added", tool.name);
|
|
12985
13610
|
}
|
|
13611
|
+
/**
|
|
13612
|
+
* The directory every registered tool resolves relative paths against.
|
|
13613
|
+
*
|
|
13614
|
+
* Exposed so a caller that has to reason about a file a tool WROTE can look
|
|
13615
|
+
* in the same place the tool wrote it. `process.cwd()` is not that place
|
|
13616
|
+
* outside a plain CLI run: the hosted server's cwd is the app directory
|
|
13617
|
+
* while the run's workspace is the tenant's scratch dir, and Electron's cwd
|
|
13618
|
+
* is the app bundle rather than the user's chosen folder.
|
|
13619
|
+
*/
|
|
13620
|
+
getWorkspaceRoot() {
|
|
13621
|
+
return this.workspaceRoot;
|
|
13622
|
+
}
|
|
12986
13623
|
/**
|
|
12987
13624
|
* Wait until a named tool is registered, resolving immediately if it already exists.
|
|
12988
13625
|
* T3 workers can call this after encountering a missing-tool error to resume
|
|
@@ -16465,115 +17102,6 @@ var GraphSearchTool = class extends BaseTool {
|
|
|
16465
17102
|
}
|
|
16466
17103
|
};
|
|
16467
17104
|
|
|
16468
|
-
// src/core/context/compaction.ts
|
|
16469
|
-
function estimateTokens(text) {
|
|
16470
|
-
if (!text) return 0;
|
|
16471
|
-
return Math.max(1, Math.ceil(text.length / 4));
|
|
16472
|
-
}
|
|
16473
|
-
function contentToText(content) {
|
|
16474
|
-
if (typeof content === "string") return content;
|
|
16475
|
-
return content.map((c) => c.type === "text" ? c.text : c.type === "tool_result" ? c.content : `[${c.type}]`).join(" ");
|
|
16476
|
-
}
|
|
16477
|
-
function messagesTokens(messages) {
|
|
16478
|
-
return messages.reduce((n, m) => n + estimateTokens(contentToText(m.content)), 0);
|
|
16479
|
-
}
|
|
16480
|
-
function atomize(text, maxChars) {
|
|
16481
|
-
const seps = ["\n\n", "\n", ". ", " "];
|
|
16482
|
-
let units = [text];
|
|
16483
|
-
for (const sep of seps) {
|
|
16484
|
-
if (units.every((u) => u.length <= maxChars)) break;
|
|
16485
|
-
const next = [];
|
|
16486
|
-
for (const u of units) {
|
|
16487
|
-
if (u.length <= maxChars) {
|
|
16488
|
-
next.push(u);
|
|
16489
|
-
continue;
|
|
16490
|
-
}
|
|
16491
|
-
const parts = u.split(sep);
|
|
16492
|
-
parts.forEach((p, i) => next.push(i < parts.length - 1 ? p + sep : p));
|
|
16493
|
-
}
|
|
16494
|
-
units = next;
|
|
16495
|
-
}
|
|
16496
|
-
const out = [];
|
|
16497
|
-
for (const u of units) {
|
|
16498
|
-
if (u.length <= maxChars) out.push(u);
|
|
16499
|
-
else for (let i = 0; i < u.length; i += maxChars) out.push(u.slice(i, i + maxChars));
|
|
16500
|
-
}
|
|
16501
|
-
return out;
|
|
16502
|
-
}
|
|
16503
|
-
function chunkText(text, opts) {
|
|
16504
|
-
const maxChars = Math.max(1, Math.floor(opts.maxTokens * 4));
|
|
16505
|
-
const overlap = Math.min(maxChars - 1, Math.max(0, Math.floor(maxChars * (opts.overlapRatio ?? 0.1))));
|
|
16506
|
-
const trimmed = text.trim();
|
|
16507
|
-
if (!trimmed) return [];
|
|
16508
|
-
if (trimmed.length <= maxChars) return [trimmed];
|
|
16509
|
-
const units = atomize(trimmed, maxChars);
|
|
16510
|
-
const chunks = [];
|
|
16511
|
-
let cur = "";
|
|
16512
|
-
for (const u of units) {
|
|
16513
|
-
if (cur && cur.length + u.length > maxChars) {
|
|
16514
|
-
chunks.push(cur.trim());
|
|
16515
|
-
cur = overlap > 0 ? cur.slice(-overlap) : "";
|
|
16516
|
-
}
|
|
16517
|
-
cur += u;
|
|
16518
|
-
}
|
|
16519
|
-
if (cur.trim()) chunks.push(cur.trim());
|
|
16520
|
-
return chunks;
|
|
16521
|
-
}
|
|
16522
|
-
function needsCompaction(totalTokens, contextWindow, reserve = 0.2) {
|
|
16523
|
-
if (!contextWindow || contextWindow <= 0) return false;
|
|
16524
|
-
return totalTokens > contextWindow * (1 - reserve);
|
|
16525
|
-
}
|
|
16526
|
-
async function mapReduceCompact(text, opts) {
|
|
16527
|
-
const capChars = opts.capTokens * 4;
|
|
16528
|
-
let working = text;
|
|
16529
|
-
let truncated = false;
|
|
16530
|
-
if (working.length > capChars) {
|
|
16531
|
-
working = working.slice(0, capChars);
|
|
16532
|
-
truncated = true;
|
|
16533
|
-
}
|
|
16534
|
-
const instruction = opts.instruction ?? "Summarize this content, preserving key facts, names, numbers, and decisions.";
|
|
16535
|
-
let chunks = chunkText(working, { maxTokens: opts.chunkTokens });
|
|
16536
|
-
if (chunks.length <= 1) return { text: working, chunks: chunks.length, calls: 0, truncated };
|
|
16537
|
-
let calls = 0;
|
|
16538
|
-
let parts = await Promise.all(
|
|
16539
|
-
chunks.map((c, i) => {
|
|
16540
|
-
calls++;
|
|
16541
|
-
return opts.summarize(c, `${instruction} (part ${i + 1} of ${chunks.length})`);
|
|
16542
|
-
})
|
|
16543
|
-
);
|
|
16544
|
-
let combined = parts.join("\n\n");
|
|
16545
|
-
const maxPasses = opts.maxReducePasses ?? 3;
|
|
16546
|
-
for (let pass = 0; pass < maxPasses && estimateTokens(combined) > opts.targetTokens; pass++) {
|
|
16547
|
-
const groups = chunkText(combined, { maxTokens: opts.chunkTokens });
|
|
16548
|
-
if (groups.length <= 1) break;
|
|
16549
|
-
parts = await Promise.all(
|
|
16550
|
-
groups.map((g) => {
|
|
16551
|
-
calls++;
|
|
16552
|
-
return opts.summarize(g, "Merge these partial summaries into one cohesive summary; keep all distinct facts.");
|
|
16553
|
-
})
|
|
16554
|
-
);
|
|
16555
|
-
combined = parts.join("\n\n");
|
|
16556
|
-
}
|
|
16557
|
-
const note = truncated ? "\n\n[Note: input exceeded the extended-context cap and was truncated.]" : "";
|
|
16558
|
-
return { text: combined + note, chunks: chunks.length, calls, truncated };
|
|
16559
|
-
}
|
|
16560
|
-
async function rollingSummary(messages, opts) {
|
|
16561
|
-
if (messages.length <= opts.keepRecent) return messages;
|
|
16562
|
-
const older = messages.slice(0, messages.length - opts.keepRecent);
|
|
16563
|
-
const recent = messages.slice(messages.length - opts.keepRecent);
|
|
16564
|
-
const transcript = older.map((m) => `${m.role}: ${contentToText(m.content)}`).join("\n");
|
|
16565
|
-
const summary = await opts.summarize(
|
|
16566
|
-
transcript,
|
|
16567
|
-
"Summarize this earlier conversation. Preserve facts, decisions, names, numbers, and any open threads or todos."
|
|
16568
|
-
);
|
|
16569
|
-
const summaryMessage = {
|
|
16570
|
-
role: "system",
|
|
16571
|
-
content: `Summary of earlier conversation (folded to fit the context window):
|
|
16572
|
-
${summary}`
|
|
16573
|
-
};
|
|
16574
|
-
return [summaryMessage, ...recent];
|
|
16575
|
-
}
|
|
16576
|
-
|
|
16577
17105
|
// src/core/steering/guidance.ts
|
|
16578
17106
|
var GuidanceQueue = class {
|
|
16579
17107
|
entries = [];
|
|
@@ -21716,10 +22244,10 @@ ${cur}`;
|
|
|
21716
22244
|
}
|
|
21717
22245
|
|
|
21718
22246
|
// src/retrieval/plan.ts
|
|
21719
|
-
var
|
|
22247
|
+
var CHARS_PER_TOKEN2 = 4;
|
|
21720
22248
|
function cagCharBudget(contextWindowTokens, opts = {}) {
|
|
21721
22249
|
const docFraction = opts.docFraction ?? 0.5;
|
|
21722
|
-
const charsPerToken = opts.charsPerToken ??
|
|
22250
|
+
const charsPerToken = opts.charsPerToken ?? CHARS_PER_TOKEN2;
|
|
21723
22251
|
return Math.max(0, Math.floor(contextWindowTokens * docFraction * charsPerToken));
|
|
21724
22252
|
}
|
|
21725
22253
|
function planRetrieval(ctx) {
|
|
@@ -22286,6 +22814,6 @@ function applySyncBundle(bundle, config, cleanup) {
|
|
|
22286
22814
|
return next;
|
|
22287
22815
|
}
|
|
22288
22816
|
|
|
22289
|
-
export { AZURE_BASE_MODELS, AZURE_BASE_URL_TEMPLATE, AuditLogger, CASCADE_AUDIT_FILE, CASCADE_CONFIG_DIR, CASCADE_CONFIG_FILE, CASCADE_DASHBOARD_SECRET_FILE, CASCADE_DB_FILE, CASCADE_IGNORE_FILE, CASCADE_KEYSTORE_FILE, CASCADE_MD_FILE, CASCADE_VERSION, CHARS_PER_TOKEN, COMPLEXITY_T2_COUNT, Cascade, CascadeCancelledError, CascadeIgnore, CascadeRouter, CascadeToolError, CloudClient, CodeSearchTool, ConfigManager, CurrentPageTool, DEFAULT_API_PORT, DEFAULT_APPROVAL_REQUIRED, DEFAULT_AUTO_SUMMARIZE_AT, DEFAULT_CLOUD_URL, DEFAULT_CONTEXT_LIMIT, DEFAULT_DASHBOARD_PORT, DEFAULT_MAX_SESSION_MESSAGES, DEFAULT_RETENTION_DAYS, DEFAULT_THEME, DOCUMENT_MIME, DashboardServer, DependencyScheduler, FileMcpOAuthStore, GLOBAL_CONFIG_DIR, GLOBAL_CREDENTIALS_FILE, GLOBAL_DB_FILE, GLOBAL_KEYSTORE_FILE, GLOBAL_RUNTIME_DB_FILE, GenerateDocumentTool, GraphRetriever, GraphSearchTool, HooksRunner, Keystore, LLMReranker, LM_STUDIO_BASE_URL, MAX_PAGE_TEXT_CHARS, MCP_TOOL_PREFIX, MODELS, McpClient, McpOAuthProvider, MemoryStore, OLLAMA_BASE_URL, OpenAICompatibleEmbedder, PROVIDER_DISPLAY_NAMES, RESUME_SCHEMA_VERSION, ResumeStore, Retriever, SqliteVectorStore, T1Administrator, T1_MODEL_PRIORITY, T2Manager, T2_MODEL_PRIORITY, T3Worker, T3_MODEL_PRIORITY, THEME_NAMES, TOOL_NAMES, TaskGraphValidationError, TaskScheduler, Telemetry, ToolRegistry, VISION_MODEL_PRIORITY, WorkspaceIndex, applySyncBundle, azureModelForDeployment, beginMcpOAuth, buildDocumentTools, buildManifest, buildSessionTranscript, bytesToBase64, cagCharBudget, chartKind, chartToTableRows, chatCompleterFromProviders, chunkCode, chunkText2 as chunkText, compileSectionGraph, compileSubtaskGraph, compileTaskGraph, completeMcpOAuth, connectMcpWithLoopbackOAuth, createCascade, decryptJSON as decryptSyncBlob, diffManifest, disambiguateMcpServerNames, discoverMcpAuthServer, discoverMcpTools, distillSessionFacts, embedderFromProviders, encryptJSON as encryptSyncBlob, evaluateAcceptance, extractCharts, failures, fileExt, fileOAuthProvider, gatherSyncBundle, hasUsableProvider, hashContent, heuristicCodeChunker, inferAzureBaseModel, inlineRuns, isDocumentFormat, isMcpToolName, matchImageLine, mcpServerPrefix, mcpToolName, nodeHttpFetch, parseBlocks, parseChartSpec, parseDelimited, parseRankOrder, parseSlide, planRetrieval, preferIpv4Host, reciprocalRankFusion, refreshMcpToken, removeMcpServerDenials, renderDocument, renderDocx, renderPptx, renderXlsx, runCascade, sessionWorthRemembering, sniffImage, splitSlides, streamCascade, stripInline, summarizeCompleted, undecided, uniqueMcpServerName };
|
|
22817
|
+
export { AZURE_BASE_MODELS, AZURE_BASE_URL_TEMPLATE, AuditLogger, CASCADE_AUDIT_FILE, CASCADE_CONFIG_DIR, CASCADE_CONFIG_FILE, CASCADE_DASHBOARD_SECRET_FILE, CASCADE_DB_FILE, CASCADE_IGNORE_FILE, CASCADE_KEYSTORE_FILE, CASCADE_MD_FILE, CASCADE_VERSION, CHARS_PER_TOKEN2 as CHARS_PER_TOKEN, COMPLEXITY_T2_COUNT, Cascade, CascadeCancelledError, CascadeIgnore, CascadeRouter, CascadeToolError, CloudClient, CodeSearchTool, ConfigManager, CurrentPageTool, DEFAULT_API_PORT, DEFAULT_APPROVAL_REQUIRED, DEFAULT_AUTO_SUMMARIZE_AT, DEFAULT_CLOUD_URL, DEFAULT_CONTEXT_LIMIT, DEFAULT_DASHBOARD_PORT, DEFAULT_MAX_SESSION_MESSAGES, DEFAULT_RETENTION_DAYS, DEFAULT_THEME, DOCUMENT_MIME, DashboardServer, DependencyScheduler, FileMcpOAuthStore, GLOBAL_CONFIG_DIR, GLOBAL_CREDENTIALS_FILE, GLOBAL_DB_FILE, GLOBAL_KEYSTORE_FILE, GLOBAL_RUNTIME_DB_FILE, GenerateDocumentTool, GraphRetriever, GraphSearchTool, HooksRunner, Keystore, LLMReranker, LM_STUDIO_BASE_URL, MAX_PAGE_TEXT_CHARS, MCP_TOOL_PREFIX, MODELS, McpClient, McpOAuthProvider, MemoryStore, OLLAMA_BASE_URL, OpenAICompatibleEmbedder, PROVIDER_DISPLAY_NAMES, RESUME_SCHEMA_VERSION, ResumeStore, Retriever, SqliteVectorStore, T1Administrator, T1_MODEL_PRIORITY, T2Manager, T2_MODEL_PRIORITY, T3Worker, T3_MODEL_PRIORITY, THEME_NAMES, TOOL_NAMES, TaskGraphValidationError, TaskScheduler, Telemetry, ToolRegistry, VISION_MODEL_PRIORITY, WorkspaceIndex, applySyncBundle, azureModelForDeployment, beginMcpOAuth, buildDocumentTools, buildManifest, buildSessionTranscript, bytesToBase64, cagCharBudget, chartKind, chartToTableRows, chatCompleterFromProviders, chunkCode, chunkText2 as chunkText, compileSectionGraph, compileSubtaskGraph, compileTaskGraph, completeMcpOAuth, connectMcpWithLoopbackOAuth, createCascade, decryptJSON as decryptSyncBlob, diffManifest, disambiguateMcpServerNames, discoverMcpAuthServer, discoverMcpTools, distillSessionFacts, embedderFromProviders, encryptJSON as encryptSyncBlob, evaluateAcceptance, extractCharts, failures, fileExt, fileOAuthProvider, gatherSyncBundle, hasUsableProvider, hashContent, heuristicCodeChunker, inferAzureBaseModel, inlineRuns, isDocumentFormat, isMcpToolName, matchImageLine, mcpServerPrefix, mcpToolName, nodeHttpFetch, parseBlocks, parseChartSpec, parseDelimited, parseRankOrder, parseSlide, planRetrieval, preferIpv4Host, reciprocalRankFusion, refreshMcpToken, removeMcpServerDenials, renderDocument, renderDocx, renderPptx, renderXlsx, runCascade, sessionWorthRemembering, sniffImage, splitSlides, streamCascade, stripInline, summarizeCompleted, undecided, uniqueMcpServerName };
|
|
22290
22818
|
//# sourceMappingURL=index.js.map
|
|
22291
22819
|
//# sourceMappingURL=index.js.map
|