cascade-ai 0.72.0 → 0.74.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 +839 -233
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +839 -233
- package/dist/cli.js.map +1 -1
- package/dist/desktop-core.cjs +825 -232
- package/dist/index.cjs +824 -231
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +104 -0
- package/dist/index.d.ts +104 -0
- package/dist/index.js +824 -231
- 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.74.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,
|
|
@@ -2353,6 +2377,13 @@ function buildTokenUsage(inputTokens, outputTokens, model) {
|
|
|
2353
2377
|
}
|
|
2354
2378
|
|
|
2355
2379
|
// src/providers/base.ts
|
|
2380
|
+
var ProviderUnreachableError = class extends Error {
|
|
2381
|
+
transient = true;
|
|
2382
|
+
constructor(message) {
|
|
2383
|
+
super(message);
|
|
2384
|
+
this.name = "ProviderUnreachableError";
|
|
2385
|
+
}
|
|
2386
|
+
};
|
|
2356
2387
|
var BaseProvider = class {
|
|
2357
2388
|
config;
|
|
2358
2389
|
model;
|
|
@@ -2421,6 +2452,21 @@ function anthropicThinkingParam(modelId, maxTokens) {
|
|
|
2421
2452
|
if (budget < 1024) return {};
|
|
2422
2453
|
return { thinking: { type: "enabled", budget_tokens: budget } };
|
|
2423
2454
|
}
|
|
2455
|
+
function toAnthropicTools(tools) {
|
|
2456
|
+
return tools.map((t) => ({
|
|
2457
|
+
name: t.name,
|
|
2458
|
+
description: t.description,
|
|
2459
|
+
input_schema: t.inputSchema
|
|
2460
|
+
}));
|
|
2461
|
+
}
|
|
2462
|
+
function toAnthropicToolUse(toolCalls) {
|
|
2463
|
+
return toolCalls.map((tc) => ({
|
|
2464
|
+
type: "tool_use",
|
|
2465
|
+
id: tc.id,
|
|
2466
|
+
name: tc.name,
|
|
2467
|
+
input: tc.input
|
|
2468
|
+
}));
|
|
2469
|
+
}
|
|
2424
2470
|
var AnthropicProvider = class extends BaseProvider {
|
|
2425
2471
|
client;
|
|
2426
2472
|
constructor(config, model) {
|
|
@@ -2442,11 +2488,7 @@ var AnthropicProvider = class extends BaseProvider {
|
|
|
2442
2488
|
}
|
|
2443
2489
|
async generateStream(options, onChunk) {
|
|
2444
2490
|
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
|
-
}));
|
|
2491
|
+
const tools = options.tools && toAnthropicTools(options.tools);
|
|
2450
2492
|
let fullContent = "";
|
|
2451
2493
|
let inputTokens = 0;
|
|
2452
2494
|
let outputTokens = 0;
|
|
@@ -2574,14 +2616,7 @@ var AnthropicProvider = class extends BaseProvider {
|
|
|
2574
2616
|
const content = [];
|
|
2575
2617
|
const text = typeof m.content === "string" ? m.content : "";
|
|
2576
2618
|
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
|
-
}
|
|
2619
|
+
content.push(...toAnthropicToolUse(m.toolCalls ?? []));
|
|
2585
2620
|
if (content.length > 0) {
|
|
2586
2621
|
result.push({ role: "assistant", content });
|
|
2587
2622
|
}
|
|
@@ -2621,6 +2656,26 @@ function isParamShapeError(err) {
|
|
|
2621
2656
|
const msg = String(e?.message ?? e?.error?.message ?? "").toLowerCase();
|
|
2622
2657
|
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
2658
|
}
|
|
2659
|
+
function toOpenAITools(tools) {
|
|
2660
|
+
return tools.map((t) => ({
|
|
2661
|
+
type: "function",
|
|
2662
|
+
function: {
|
|
2663
|
+
name: t.name,
|
|
2664
|
+
description: t.description,
|
|
2665
|
+
parameters: t.inputSchema
|
|
2666
|
+
}
|
|
2667
|
+
}));
|
|
2668
|
+
}
|
|
2669
|
+
function toOpenAIToolCalls(toolCalls) {
|
|
2670
|
+
return toolCalls.map((tc) => ({
|
|
2671
|
+
id: tc.id,
|
|
2672
|
+
type: "function",
|
|
2673
|
+
function: {
|
|
2674
|
+
name: tc.name,
|
|
2675
|
+
arguments: JSON.stringify(tc.input)
|
|
2676
|
+
}
|
|
2677
|
+
}));
|
|
2678
|
+
}
|
|
2624
2679
|
var OpenAIProvider = class extends BaseProvider {
|
|
2625
2680
|
client;
|
|
2626
2681
|
/** Once we learn (from the model id or an API error) that this deployment
|
|
@@ -2640,14 +2695,7 @@ var OpenAIProvider = class extends BaseProvider {
|
|
|
2640
2695
|
}
|
|
2641
2696
|
async generateStream(options, onChunk) {
|
|
2642
2697
|
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
|
-
}));
|
|
2698
|
+
const tools = options.tools && toOpenAITools(options.tools);
|
|
2651
2699
|
let fullContent = "";
|
|
2652
2700
|
let inputTokens = 0;
|
|
2653
2701
|
let outputTokens = 0;
|
|
@@ -2793,14 +2841,7 @@ var OpenAIProvider = class extends BaseProvider {
|
|
|
2793
2841
|
result.push({
|
|
2794
2842
|
role: "assistant",
|
|
2795
2843
|
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
|
-
}))
|
|
2844
|
+
tool_calls: toOpenAIToolCalls(m.toolCalls)
|
|
2804
2845
|
});
|
|
2805
2846
|
} else {
|
|
2806
2847
|
result.push({ role: m.role, content: m.content });
|
|
@@ -3043,6 +3084,38 @@ function toGeminiParameters(inputSchema) {
|
|
|
3043
3084
|
}
|
|
3044
3085
|
|
|
3045
3086
|
// src/providers/gemini.ts
|
|
3087
|
+
function toGeminiTools(tools) {
|
|
3088
|
+
return tools.map((tool) => ({
|
|
3089
|
+
name: tool.name,
|
|
3090
|
+
description: tool.description,
|
|
3091
|
+
// Convert, never cast. Gemini's parameters field is an OpenAPI subset
|
|
3092
|
+
// that 400s on unknown keys, and MCP servers ship schemas carrying their
|
|
3093
|
+
// own extensions (GitHub's adds `x-mcp-header` to every header-bound
|
|
3094
|
+
// property). Casting sent those through verbatim and every request failed
|
|
3095
|
+
// before the model saw it. See providers/gemini-schema.ts.
|
|
3096
|
+
parameters: toGeminiParameters(tool.inputSchema)
|
|
3097
|
+
}));
|
|
3098
|
+
}
|
|
3099
|
+
function toGeminiFunctionCalls(toolCalls) {
|
|
3100
|
+
return toolCalls.map((tc) => ({
|
|
3101
|
+
functionCall: {
|
|
3102
|
+
name: tc.name,
|
|
3103
|
+
args: tc.input
|
|
3104
|
+
}
|
|
3105
|
+
}));
|
|
3106
|
+
}
|
|
3107
|
+
function isGeminiAuthStatus(status) {
|
|
3108
|
+
return status === 400 || status === 401 || status === 403;
|
|
3109
|
+
}
|
|
3110
|
+
async function describeGeminiError(resp) {
|
|
3111
|
+
try {
|
|
3112
|
+
const body = await resp.json();
|
|
3113
|
+
const detail = body?.error?.message ?? body?.error?.status;
|
|
3114
|
+
return detail ? ` \u2014 ${detail}` : "";
|
|
3115
|
+
} catch {
|
|
3116
|
+
return "";
|
|
3117
|
+
}
|
|
3118
|
+
}
|
|
3046
3119
|
var GeminiProvider = class extends BaseProvider {
|
|
3047
3120
|
client;
|
|
3048
3121
|
constructor(config, model) {
|
|
@@ -3064,7 +3137,7 @@ var GeminiProvider = class extends BaseProvider {
|
|
|
3064
3137
|
{ category: HarmCategory.HARM_CATEGORY_HARASSMENT, threshold: HarmBlockThreshold.BLOCK_NONE },
|
|
3065
3138
|
{ category: HarmCategory.HARM_CATEGORY_HATE_SPEECH, threshold: HarmBlockThreshold.BLOCK_NONE }
|
|
3066
3139
|
],
|
|
3067
|
-
tools: options.tools?.length ? [{ functionDeclarations: options.tools
|
|
3140
|
+
tools: options.tools?.length ? [{ functionDeclarations: toGeminiTools(options.tools) }] : void 0,
|
|
3068
3141
|
abortSignal: options.signal
|
|
3069
3142
|
}
|
|
3070
3143
|
});
|
|
@@ -3126,9 +3199,7 @@ var GeminiProvider = class extends BaseProvider {
|
|
|
3126
3199
|
}
|
|
3127
3200
|
async listModels() {
|
|
3128
3201
|
try {
|
|
3129
|
-
const resp = await
|
|
3130
|
-
`https://generativelanguage.googleapis.com/v1beta/models?key=${this.config.apiKey}`
|
|
3131
|
-
);
|
|
3202
|
+
const resp = await this.fetchModelList();
|
|
3132
3203
|
if (!resp.ok) {
|
|
3133
3204
|
return Object.values(MODELS).filter((m) => m.provider === "gemini");
|
|
3134
3205
|
}
|
|
@@ -3159,18 +3230,61 @@ var GeminiProvider = class extends BaseProvider {
|
|
|
3159
3230
|
return Object.values(MODELS).filter((m) => m.provider === "gemini");
|
|
3160
3231
|
}
|
|
3161
3232
|
}
|
|
3233
|
+
/**
|
|
3234
|
+
* Is this KEY usable — not "does one particular model answer".
|
|
3235
|
+
*
|
|
3236
|
+
* This used to call countTokens() against `this.model.id`, which the router
|
|
3237
|
+
* fills from the first Gemini entry in the bundled catalogue. That made the
|
|
3238
|
+
* whole provider's fate depend on one hard-coded model id: a key that cannot
|
|
3239
|
+
* reach that one model — retired, not enabled for the key's project, served
|
|
3240
|
+
* on a different API version — failed the probe, and because every later step
|
|
3241
|
+
* is gated on the result (`validateCloudProviderModels` and
|
|
3242
|
+
* `discoverProviderModels` both return early for an unavailable provider) the
|
|
3243
|
+
* real model list was never fetched. Gemini vanished entirely, and the CLI
|
|
3244
|
+
* reported "add a provider API key first" about a key that was present and
|
|
3245
|
+
* working.
|
|
3246
|
+
*
|
|
3247
|
+
* Listing models asks the account-level question instead, the same one
|
|
3248
|
+
* OpenAI's probe asks, and it is the same request `listModels()` already
|
|
3249
|
+
* makes for discovery.
|
|
3250
|
+
*
|
|
3251
|
+
* Throws rather than returning false. The router logs the reason it catches,
|
|
3252
|
+
* and "bad key, wrong endpoint/deployment, or unreachable" — three guesses,
|
|
3253
|
+
* no answer — is what made this take a user report to find. A swallowed probe
|
|
3254
|
+
* error is the same defect that once hid a misconfigured Azure deployment.
|
|
3255
|
+
*/
|
|
3162
3256
|
async isAvailable() {
|
|
3257
|
+
if (!this.config.apiKey) throw new Error("no Gemini API key configured");
|
|
3258
|
+
let resp;
|
|
3163
3259
|
try {
|
|
3164
|
-
await this.
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
}
|
|
3170
|
-
|
|
3260
|
+
resp = await this.fetchModelList();
|
|
3261
|
+
} catch (err) {
|
|
3262
|
+
throw new ProviderUnreachableError(
|
|
3263
|
+
`could not reach the Gemini API: ${err instanceof Error ? err.message : String(err)}`
|
|
3264
|
+
);
|
|
3265
|
+
}
|
|
3266
|
+
if (resp.ok) return true;
|
|
3267
|
+
const status = `HTTP ${resp.status} ${resp.statusText}`.trim();
|
|
3268
|
+
const detail = await describeGeminiError(resp);
|
|
3269
|
+
if (isGeminiAuthStatus(resp.status)) {
|
|
3270
|
+
throw new Error(`Gemini rejected the API key: ${status}${detail}`);
|
|
3171
3271
|
}
|
|
3272
|
+
throw new ProviderUnreachableError(`Gemini model list failed: ${status}${detail}`);
|
|
3172
3273
|
}
|
|
3173
3274
|
// ── Private ──────────────────────────────────
|
|
3275
|
+
/**
|
|
3276
|
+
* The account's model list. One place so the availability probe and
|
|
3277
|
+
* discovery cannot ask different questions of the same endpoint.
|
|
3278
|
+
*
|
|
3279
|
+
* The key goes in a header, not the query string: a URL carries into proxy
|
|
3280
|
+
* logs, error reports and shell history, and this one would carry the key
|
|
3281
|
+
* with it.
|
|
3282
|
+
*/
|
|
3283
|
+
fetchModelList() {
|
|
3284
|
+
return fetch("https://generativelanguage.googleapis.com/v1beta/models", {
|
|
3285
|
+
headers: { "x-goog-api-key": this.config.apiKey ?? "" }
|
|
3286
|
+
});
|
|
3287
|
+
}
|
|
3174
3288
|
buildContents(messages, extraImages) {
|
|
3175
3289
|
const contents = [];
|
|
3176
3290
|
for (const m of messages) {
|
|
@@ -3205,14 +3319,7 @@ var GeminiProvider = class extends BaseProvider {
|
|
|
3205
3319
|
const parts = [];
|
|
3206
3320
|
const textContent = typeof m.content === "string" ? m.content : "";
|
|
3207
3321
|
if (textContent) parts.push({ text: textContent });
|
|
3208
|
-
|
|
3209
|
-
parts.push({
|
|
3210
|
-
functionCall: {
|
|
3211
|
-
name: tc.name,
|
|
3212
|
-
args: tc.input
|
|
3213
|
-
}
|
|
3214
|
-
});
|
|
3215
|
-
}
|
|
3322
|
+
parts.push(...toGeminiFunctionCalls(m.toolCalls ?? []));
|
|
3216
3323
|
if (parts.length > 0) {
|
|
3217
3324
|
contents.push({ role: "model", parts });
|
|
3218
3325
|
}
|
|
@@ -3259,18 +3366,6 @@ var GeminiProvider = class extends BaseProvider {
|
|
|
3259
3366
|
}
|
|
3260
3367
|
return parts;
|
|
3261
3368
|
}
|
|
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
3369
|
};
|
|
3275
3370
|
try {
|
|
3276
3371
|
dns.setDefaultResultOrder("ipv4first");
|
|
@@ -3363,6 +3458,24 @@ function isToolCapable(modelName) {
|
|
|
3363
3458
|
const name = modelName.toLowerCase();
|
|
3364
3459
|
return TOOL_CAPABLE_FAMILIES.some((family) => name.includes(family));
|
|
3365
3460
|
}
|
|
3461
|
+
function toOllamaTools(tools) {
|
|
3462
|
+
return tools.map((t) => ({
|
|
3463
|
+
type: "function",
|
|
3464
|
+
function: {
|
|
3465
|
+
name: t.name,
|
|
3466
|
+
description: t.description,
|
|
3467
|
+
parameters: t.inputSchema
|
|
3468
|
+
}
|
|
3469
|
+
}));
|
|
3470
|
+
}
|
|
3471
|
+
function toOllamaToolCalls(toolCalls) {
|
|
3472
|
+
return toolCalls.map((tc) => ({
|
|
3473
|
+
function: {
|
|
3474
|
+
name: tc.name,
|
|
3475
|
+
arguments: tc.input
|
|
3476
|
+
}
|
|
3477
|
+
}));
|
|
3478
|
+
}
|
|
3366
3479
|
var OllamaProvider = class extends BaseProvider {
|
|
3367
3480
|
baseUrl;
|
|
3368
3481
|
constructor(config, model) {
|
|
@@ -3375,14 +3488,7 @@ var OllamaProvider = class extends BaseProvider {
|
|
|
3375
3488
|
}
|
|
3376
3489
|
async generateStream(options, onChunk) {
|
|
3377
3490
|
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
|
-
}));
|
|
3491
|
+
const ollamaTools = options.tools && toOllamaTools(options.tools);
|
|
3386
3492
|
const response = await fetch(`${this.baseUrl}/api/chat`, {
|
|
3387
3493
|
method: "POST",
|
|
3388
3494
|
headers: { "Content-Type": "application/json" },
|
|
@@ -3562,12 +3668,7 @@ var OllamaProvider = class extends BaseProvider {
|
|
|
3562
3668
|
result.push({
|
|
3563
3669
|
role: "assistant",
|
|
3564
3670
|
content: typeof m.content === "string" ? m.content : "",
|
|
3565
|
-
tool_calls: m.toolCalls
|
|
3566
|
-
function: {
|
|
3567
|
-
name: tc.name,
|
|
3568
|
-
arguments: tc.input
|
|
3569
|
-
}
|
|
3570
|
-
}))
|
|
3671
|
+
tool_calls: toOllamaToolCalls(m.toolCalls)
|
|
3571
3672
|
});
|
|
3572
3673
|
continue;
|
|
3573
3674
|
}
|
|
@@ -3968,6 +4069,21 @@ var DEFAULT_PROVIDER_TPM = {
|
|
|
3968
4069
|
"openai-compatible": 3e4,
|
|
3969
4070
|
ollama: Number.POSITIVE_INFINITY
|
|
3970
4071
|
};
|
|
4072
|
+
function abortReason(signal) {
|
|
4073
|
+
return signal.reason instanceof Error ? signal.reason : new Error("Aborted while waiting for rate-limit capacity");
|
|
4074
|
+
}
|
|
4075
|
+
function sleepUnlessAborted(ms, signal) {
|
|
4076
|
+
if (!signal) return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
4077
|
+
return new Promise((resolve2) => {
|
|
4078
|
+
const done = () => {
|
|
4079
|
+
clearTimeout(timer);
|
|
4080
|
+
signal.removeEventListener("abort", done);
|
|
4081
|
+
resolve2();
|
|
4082
|
+
};
|
|
4083
|
+
const timer = setTimeout(done, ms);
|
|
4084
|
+
signal.addEventListener("abort", done, { once: true });
|
|
4085
|
+
});
|
|
4086
|
+
}
|
|
3971
4087
|
var TpmLimiter = class {
|
|
3972
4088
|
buckets = /* @__PURE__ */ new Map();
|
|
3973
4089
|
constructor(overrides = {}) {
|
|
@@ -3986,9 +4102,10 @@ var TpmLimiter = class {
|
|
|
3986
4102
|
* are reported back via `refund` when short, or simply settled at the next
|
|
3987
4103
|
* refill.
|
|
3988
4104
|
*/
|
|
3989
|
-
async acquire(provider, estimatedTokens) {
|
|
4105
|
+
async acquire(provider, estimatedTokens, signal) {
|
|
3990
4106
|
const bucket = this.buckets.get(provider);
|
|
3991
4107
|
if (!bucket || bucket.tokensPerMinute === Number.POSITIVE_INFINITY) return;
|
|
4108
|
+
if (signal?.aborted) throw abortReason(signal);
|
|
3992
4109
|
const want = Math.min(estimatedTokens, bucket.tokensPerMinute);
|
|
3993
4110
|
for (; ; ) {
|
|
3994
4111
|
this.refill(bucket);
|
|
@@ -3998,7 +4115,8 @@ var TpmLimiter = class {
|
|
|
3998
4115
|
}
|
|
3999
4116
|
const deficit = want - bucket.available;
|
|
4000
4117
|
const waitMs = Math.max(50, Math.ceil(deficit / bucket.tokensPerMinute * 6e4));
|
|
4001
|
-
await
|
|
4118
|
+
await sleepUnlessAborted(waitMs, signal);
|
|
4119
|
+
if (signal?.aborted) throw abortReason(signal);
|
|
4002
4120
|
}
|
|
4003
4121
|
}
|
|
4004
4122
|
/**
|
|
@@ -4044,6 +4162,9 @@ var TpmLimiter = class {
|
|
|
4044
4162
|
};
|
|
4045
4163
|
|
|
4046
4164
|
// src/core/router/local-queue.ts
|
|
4165
|
+
function abortReason2(signal) {
|
|
4166
|
+
return signal.reason instanceof Error ? signal.reason : new Error("Aborted while waiting for a local inference slot");
|
|
4167
|
+
}
|
|
4047
4168
|
var LocalRequestQueue = class {
|
|
4048
4169
|
maxConcurrent;
|
|
4049
4170
|
active = 0;
|
|
@@ -4054,9 +4175,16 @@ var LocalRequestQueue = class {
|
|
|
4054
4175
|
/**
|
|
4055
4176
|
* Acquire a queue slot. Returns a `release` function that MUST be called
|
|
4056
4177
|
* when the inference call is done (even on error). Rejects if the slot
|
|
4057
|
-
* cannot be acquired within `timeoutMs
|
|
4178
|
+
* cannot be acquired within `timeoutMs`, or as soon as `signal` fires.
|
|
4179
|
+
*
|
|
4180
|
+
* The signal matters as much as the timeout: with concurrency of 1 a caller
|
|
4181
|
+
* can sit here for half the inference timeout — 150 seconds by default —
|
|
4182
|
+
* and until it was wired up, neither cancelling the run nor spending its
|
|
4183
|
+
* budget got it out. It waited out the window for a slot it would drop the
|
|
4184
|
+
* moment it received one.
|
|
4058
4185
|
*/
|
|
4059
|
-
async acquire(timeoutMs) {
|
|
4186
|
+
async acquire(timeoutMs, signal) {
|
|
4187
|
+
if (signal?.aborted) throw abortReason2(signal);
|
|
4060
4188
|
if (this.active < this.maxConcurrent) {
|
|
4061
4189
|
this.active++;
|
|
4062
4190
|
return this.makeRelease();
|
|
@@ -4064,23 +4192,36 @@ var LocalRequestQueue = class {
|
|
|
4064
4192
|
return new Promise((resolve2, reject) => {
|
|
4065
4193
|
let settled = false;
|
|
4066
4194
|
let timer;
|
|
4195
|
+
const cleanup = () => {
|
|
4196
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
4197
|
+
signal?.removeEventListener("abort", onAbort);
|
|
4198
|
+
};
|
|
4067
4199
|
const resolver = (release) => {
|
|
4068
4200
|
if (settled) return;
|
|
4069
4201
|
settled = true;
|
|
4070
|
-
|
|
4202
|
+
cleanup();
|
|
4071
4203
|
resolve2(release);
|
|
4072
4204
|
};
|
|
4205
|
+
const abandon = () => {
|
|
4206
|
+
if (settled) return false;
|
|
4207
|
+
settled = true;
|
|
4208
|
+
cleanup();
|
|
4209
|
+
const idx = this.queue.indexOf(resolver);
|
|
4210
|
+
if (idx !== -1) this.queue.splice(idx, 1);
|
|
4211
|
+
return true;
|
|
4212
|
+
};
|
|
4213
|
+
function onAbort() {
|
|
4214
|
+
if (abandon()) reject(abortReason2(signal));
|
|
4215
|
+
}
|
|
4073
4216
|
if (timeoutMs !== void 0 && timeoutMs > 0) {
|
|
4074
4217
|
timer = setTimeout(() => {
|
|
4075
|
-
if (
|
|
4076
|
-
settled = true;
|
|
4077
|
-
const idx = this.queue.indexOf(resolver);
|
|
4078
|
-
if (idx !== -1) this.queue.splice(idx, 1);
|
|
4218
|
+
if (!abandon()) return;
|
|
4079
4219
|
reject(new Error(
|
|
4080
4220
|
`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
4221
|
));
|
|
4082
4222
|
}, timeoutMs);
|
|
4083
4223
|
}
|
|
4224
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
4084
4225
|
this.queue.push(resolver);
|
|
4085
4226
|
});
|
|
4086
4227
|
}
|
|
@@ -4217,6 +4358,116 @@ function fileDeadModelPersistence(filePath) {
|
|
|
4217
4358
|
};
|
|
4218
4359
|
}
|
|
4219
4360
|
|
|
4361
|
+
// src/core/context/compaction.ts
|
|
4362
|
+
var CHARS_PER_TOKEN = 4;
|
|
4363
|
+
function estimateTokens(text) {
|
|
4364
|
+
if (!text) return 0;
|
|
4365
|
+
return Math.max(1, Math.ceil(text.length / CHARS_PER_TOKEN));
|
|
4366
|
+
}
|
|
4367
|
+
function contentToText(content) {
|
|
4368
|
+
if (typeof content === "string") return content;
|
|
4369
|
+
return content.map((c) => c.type === "text" ? c.text : c.type === "tool_result" ? c.content : `[${c.type}]`).join(" ");
|
|
4370
|
+
}
|
|
4371
|
+
function messagesTokens(messages) {
|
|
4372
|
+
return messages.reduce((n, m) => n + estimateTokens(contentToText(m.content)), 0);
|
|
4373
|
+
}
|
|
4374
|
+
function atomize(text, maxChars) {
|
|
4375
|
+
const seps = ["\n\n", "\n", ". ", " "];
|
|
4376
|
+
let units = [text];
|
|
4377
|
+
for (const sep of seps) {
|
|
4378
|
+
if (units.every((u) => u.length <= maxChars)) break;
|
|
4379
|
+
const next = [];
|
|
4380
|
+
for (const u of units) {
|
|
4381
|
+
if (u.length <= maxChars) {
|
|
4382
|
+
next.push(u);
|
|
4383
|
+
continue;
|
|
4384
|
+
}
|
|
4385
|
+
const parts = u.split(sep);
|
|
4386
|
+
parts.forEach((p, i) => next.push(i < parts.length - 1 ? p + sep : p));
|
|
4387
|
+
}
|
|
4388
|
+
units = next;
|
|
4389
|
+
}
|
|
4390
|
+
const out = [];
|
|
4391
|
+
for (const u of units) {
|
|
4392
|
+
if (u.length <= maxChars) out.push(u);
|
|
4393
|
+
else for (let i = 0; i < u.length; i += maxChars) out.push(u.slice(i, i + maxChars));
|
|
4394
|
+
}
|
|
4395
|
+
return out;
|
|
4396
|
+
}
|
|
4397
|
+
function chunkText(text, opts) {
|
|
4398
|
+
const maxChars = Math.max(1, Math.floor(opts.maxTokens * 4));
|
|
4399
|
+
const overlap = Math.min(maxChars - 1, Math.max(0, Math.floor(maxChars * (opts.overlapRatio ?? 0.1))));
|
|
4400
|
+
const trimmed = text.trim();
|
|
4401
|
+
if (!trimmed) return [];
|
|
4402
|
+
if (trimmed.length <= maxChars) return [trimmed];
|
|
4403
|
+
const units = atomize(trimmed, maxChars);
|
|
4404
|
+
const chunks = [];
|
|
4405
|
+
let cur = "";
|
|
4406
|
+
for (const u of units) {
|
|
4407
|
+
if (cur && cur.length + u.length > maxChars) {
|
|
4408
|
+
chunks.push(cur.trim());
|
|
4409
|
+
cur = overlap > 0 ? cur.slice(-overlap) : "";
|
|
4410
|
+
}
|
|
4411
|
+
cur += u;
|
|
4412
|
+
}
|
|
4413
|
+
if (cur.trim()) chunks.push(cur.trim());
|
|
4414
|
+
return chunks;
|
|
4415
|
+
}
|
|
4416
|
+
function needsCompaction(totalTokens, contextWindow, reserve = 0.2) {
|
|
4417
|
+
if (!contextWindow || contextWindow <= 0) return false;
|
|
4418
|
+
return totalTokens > contextWindow * (1 - reserve);
|
|
4419
|
+
}
|
|
4420
|
+
async function mapReduceCompact(text, opts) {
|
|
4421
|
+
const capChars = opts.capTokens * 4;
|
|
4422
|
+
let working = text;
|
|
4423
|
+
let truncated = false;
|
|
4424
|
+
if (working.length > capChars) {
|
|
4425
|
+
working = working.slice(0, capChars);
|
|
4426
|
+
truncated = true;
|
|
4427
|
+
}
|
|
4428
|
+
const instruction = opts.instruction ?? "Summarize this content, preserving key facts, names, numbers, and decisions.";
|
|
4429
|
+
let chunks = chunkText(working, { maxTokens: opts.chunkTokens });
|
|
4430
|
+
if (chunks.length <= 1) return { text: working, chunks: chunks.length, calls: 0, truncated };
|
|
4431
|
+
let calls = 0;
|
|
4432
|
+
let parts = await Promise.all(
|
|
4433
|
+
chunks.map((c, i) => {
|
|
4434
|
+
calls++;
|
|
4435
|
+
return opts.summarize(c, `${instruction} (part ${i + 1} of ${chunks.length})`);
|
|
4436
|
+
})
|
|
4437
|
+
);
|
|
4438
|
+
let combined = parts.join("\n\n");
|
|
4439
|
+
const maxPasses = opts.maxReducePasses ?? 3;
|
|
4440
|
+
for (let pass = 0; pass < maxPasses && estimateTokens(combined) > opts.targetTokens; pass++) {
|
|
4441
|
+
const groups = chunkText(combined, { maxTokens: opts.chunkTokens });
|
|
4442
|
+
if (groups.length <= 1) break;
|
|
4443
|
+
parts = await Promise.all(
|
|
4444
|
+
groups.map((g) => {
|
|
4445
|
+
calls++;
|
|
4446
|
+
return opts.summarize(g, "Merge these partial summaries into one cohesive summary; keep all distinct facts.");
|
|
4447
|
+
})
|
|
4448
|
+
);
|
|
4449
|
+
combined = parts.join("\n\n");
|
|
4450
|
+
}
|
|
4451
|
+
const note = truncated ? "\n\n[Note: input exceeded the extended-context cap and was truncated.]" : "";
|
|
4452
|
+
return { text: combined + note, chunks: chunks.length, calls, truncated };
|
|
4453
|
+
}
|
|
4454
|
+
async function rollingSummary(messages, opts) {
|
|
4455
|
+
if (messages.length <= opts.keepRecent) return messages;
|
|
4456
|
+
const older = messages.slice(0, messages.length - opts.keepRecent);
|
|
4457
|
+
const recent = messages.slice(messages.length - opts.keepRecent);
|
|
4458
|
+
const transcript = older.map((m) => `${m.role}: ${contentToText(m.content)}`).join("\n");
|
|
4459
|
+
const summary = await opts.summarize(
|
|
4460
|
+
transcript,
|
|
4461
|
+
"Summarize this earlier conversation. Preserve facts, decisions, names, numbers, and any open threads or todos."
|
|
4462
|
+
);
|
|
4463
|
+
const summaryMessage = {
|
|
4464
|
+
role: "system",
|
|
4465
|
+
content: `Summary of earlier conversation (folded to fit the context window):
|
|
4466
|
+
${summary}`
|
|
4467
|
+
};
|
|
4468
|
+
return [summaryMessage, ...recent];
|
|
4469
|
+
}
|
|
4470
|
+
|
|
4220
4471
|
// src/utils/retry.ts
|
|
4221
4472
|
var CascadeCancelledError = class extends Error {
|
|
4222
4473
|
constructor(reason) {
|
|
@@ -4251,6 +4502,158 @@ async function withTimeout(promise, timeoutMs, errorMessage = "Operation timed o
|
|
|
4251
4502
|
if (timer !== void 0) clearTimeout(timer);
|
|
4252
4503
|
}
|
|
4253
4504
|
}
|
|
4505
|
+
async function withTimeoutAbort(run, timeoutMs, errorMessage = "Operation timed out", outer) {
|
|
4506
|
+
const outers = (Array.isArray(outer) ? outer : [outer]).filter((sig) => sig !== void 0);
|
|
4507
|
+
const controller = new AbortController();
|
|
4508
|
+
let timer;
|
|
4509
|
+
let rejectRace;
|
|
4510
|
+
const settle = (reason) => {
|
|
4511
|
+
rejectRace?.(reason);
|
|
4512
|
+
controller.abort(reason);
|
|
4513
|
+
};
|
|
4514
|
+
const abortOuter = () => {
|
|
4515
|
+
const fired = outers.find((sig) => sig.aborted);
|
|
4516
|
+
settle(fired?.reason instanceof Error ? fired.reason : new CascadeCancelledError("Run cancelled"));
|
|
4517
|
+
};
|
|
4518
|
+
const failPromise = new Promise((_, reject) => {
|
|
4519
|
+
rejectRace = reject;
|
|
4520
|
+
timer = setTimeout(() => settle(new Error(errorMessage)), timeoutMs);
|
|
4521
|
+
});
|
|
4522
|
+
try {
|
|
4523
|
+
if (outers.some((sig) => sig.aborted)) {
|
|
4524
|
+
abortOuter();
|
|
4525
|
+
return await failPromise;
|
|
4526
|
+
}
|
|
4527
|
+
for (const sig of outers) sig.addEventListener("abort", abortOuter, { once: true });
|
|
4528
|
+
return await Promise.race([run(controller.signal), failPromise]);
|
|
4529
|
+
} finally {
|
|
4530
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
4531
|
+
for (const sig of outers) sig.removeEventListener("abort", abortOuter);
|
|
4532
|
+
}
|
|
4533
|
+
}
|
|
4534
|
+
function anySignal(sources) {
|
|
4535
|
+
const live = sources.filter((s) => s !== void 0);
|
|
4536
|
+
if (live.length === 0) return { release: () => {
|
|
4537
|
+
} };
|
|
4538
|
+
if (live.length === 1) return { signal: live[0], release: () => {
|
|
4539
|
+
} };
|
|
4540
|
+
const controller = new AbortController();
|
|
4541
|
+
function detach() {
|
|
4542
|
+
for (const s of live) s.removeEventListener("abort", onAbort);
|
|
4543
|
+
}
|
|
4544
|
+
function onAbort() {
|
|
4545
|
+
const fired = live.find((s) => s.aborted);
|
|
4546
|
+
controller.abort(fired?.reason instanceof Error ? fired.reason : new CascadeCancelledError("Run cancelled"));
|
|
4547
|
+
detach();
|
|
4548
|
+
}
|
|
4549
|
+
if (live.some((s) => s.aborted)) {
|
|
4550
|
+
onAbort();
|
|
4551
|
+
return { signal: controller.signal, release: detach };
|
|
4552
|
+
}
|
|
4553
|
+
for (const s of live) s.addEventListener("abort", onAbort, { once: true });
|
|
4554
|
+
return { signal: controller.signal, release: detach };
|
|
4555
|
+
}
|
|
4556
|
+
|
|
4557
|
+
// src/core/router/wire-profile.ts
|
|
4558
|
+
var textAndImageOnly = (block) => block.type === "text" || block.type === "image";
|
|
4559
|
+
var ANTHROPIC = {
|
|
4560
|
+
// convertMessages: `if (m.role === 'system') continue` — history system
|
|
4561
|
+
// messages are skipped outright; only options.systemPrompt goes as system
|
|
4562
|
+
// input. Compaction emits exactly such a message, holding a summary of the
|
|
4563
|
+
// whole conversation, so charging it refused runs over input never sent.
|
|
4564
|
+
dropsMessage: (m) => m.role === "system",
|
|
4565
|
+
blockHandling: (m) => {
|
|
4566
|
+
if (m.role === "tool") return "stringified";
|
|
4567
|
+
if (m.role === "assistant") return "dropped";
|
|
4568
|
+
return "blocks";
|
|
4569
|
+
},
|
|
4570
|
+
sendsToolCalls: (m) => m.role === "assistant",
|
|
4571
|
+
sendsBlock: textAndImageOnly,
|
|
4572
|
+
sendsToolCallId: true,
|
|
4573
|
+
sizeToolCalls: toAnthropicToolUse,
|
|
4574
|
+
sendsUrlImages: true,
|
|
4575
|
+
readsTopLevelImages: false,
|
|
4576
|
+
sizeTools: toAnthropicTools
|
|
4577
|
+
};
|
|
4578
|
+
var GEMINI = {
|
|
4579
|
+
// buildContents folds a system message into the next user turn, but only
|
|
4580
|
+
// when it has text: array content collapses to '' and the message is
|
|
4581
|
+
// skipped, as is a whitespace-only string.
|
|
4582
|
+
dropsMessage: (m) => m.role === "system" && !(typeof m.content === "string" ? m.content : "").trim(),
|
|
4583
|
+
blockHandling: (m) => {
|
|
4584
|
+
if (m.role === "tool") return "stringified";
|
|
4585
|
+
if (m.role === "assistant" || m.role === "system") return "dropped";
|
|
4586
|
+
return "blocks";
|
|
4587
|
+
},
|
|
4588
|
+
sendsToolCalls: (m) => m.role === "assistant",
|
|
4589
|
+
sendsBlock: textAndImageOnly,
|
|
4590
|
+
sendsToolCallId: true,
|
|
4591
|
+
sizeToolCalls: toGeminiFunctionCalls,
|
|
4592
|
+
sendsUrlImages: false,
|
|
4593
|
+
readsTopLevelImages: true,
|
|
4594
|
+
sizeTools: toGeminiTools
|
|
4595
|
+
};
|
|
4596
|
+
var OPENAI = {
|
|
4597
|
+
dropsMessage: () => false,
|
|
4598
|
+
blockHandling: (m) => {
|
|
4599
|
+
if (m.role === "system" || m.role === "tool") return "dropped";
|
|
4600
|
+
return "blocks";
|
|
4601
|
+
},
|
|
4602
|
+
// The tool_calls branch is inside `if (typeof m.content === 'string')`; an
|
|
4603
|
+
// assistant turn with array content falls through to the parts branch and
|
|
4604
|
+
// its tool calls are never attached.
|
|
4605
|
+
sendsToolCalls: (m) => m.role === "assistant" && typeof m.content === "string",
|
|
4606
|
+
sendsBlock: textAndImageOnly,
|
|
4607
|
+
sendsToolCallId: true,
|
|
4608
|
+
sizeToolCalls: toOpenAIToolCalls,
|
|
4609
|
+
sendsUrlImages: true,
|
|
4610
|
+
readsTopLevelImages: false,
|
|
4611
|
+
sizeTools: toOpenAITools
|
|
4612
|
+
};
|
|
4613
|
+
var OLLAMA = {
|
|
4614
|
+
dropsMessage: () => false,
|
|
4615
|
+
blockHandling: (m) => {
|
|
4616
|
+
if (m.role === "system") return "dropped";
|
|
4617
|
+
if (m.role === "tool") return "stringified";
|
|
4618
|
+
if (m.role === "assistant" && m.toolCalls?.length) return "dropped";
|
|
4619
|
+
return "blocks";
|
|
4620
|
+
},
|
|
4621
|
+
sendsToolCalls: (m) => m.role === "assistant",
|
|
4622
|
+
// The image split maps `b.image.data` with no check on `type`, so a URL is
|
|
4623
|
+
// submitted too (as a URL string in the `images` array).
|
|
4624
|
+
sendsBlock: textAndImageOnly,
|
|
4625
|
+
sendsToolCallId: false,
|
|
4626
|
+
sizeToolCalls: toOllamaToolCalls,
|
|
4627
|
+
sendsUrlImages: true,
|
|
4628
|
+
readsTopLevelImages: false,
|
|
4629
|
+
sizeTools: toOllamaTools
|
|
4630
|
+
};
|
|
4631
|
+
var PROFILES = {
|
|
4632
|
+
anthropic: ANTHROPIC,
|
|
4633
|
+
gemini: GEMINI,
|
|
4634
|
+
openai: OPENAI,
|
|
4635
|
+
// AzureOpenAIProvider and OpenAICompatibleProvider both extend OpenAIProvider
|
|
4636
|
+
// and neither overrides convertMessages.
|
|
4637
|
+
azure: OPENAI,
|
|
4638
|
+
"openai-compatible": OPENAI,
|
|
4639
|
+
ollama: OLLAMA
|
|
4640
|
+
};
|
|
4641
|
+
function wireProfile(provider) {
|
|
4642
|
+
return PROFILES[provider] ?? OPENAI;
|
|
4643
|
+
}
|
|
4644
|
+
function geminiImageCopies(messages) {
|
|
4645
|
+
for (const m of messages) {
|
|
4646
|
+
if (m.role === "user") {
|
|
4647
|
+
const laterUserTurn = messages.slice(messages.indexOf(m) + 1).some((x) => x.role === "user");
|
|
4648
|
+
return laterUserTurn ? 2 : 1;
|
|
4649
|
+
}
|
|
4650
|
+
const text = typeof m.content === "string" ? m.content : "";
|
|
4651
|
+
if (m.role === "system" && text.trim()) return 1;
|
|
4652
|
+
if (m.role === "tool") return 1;
|
|
4653
|
+
if (m.role === "assistant" && (text || m.toolCalls?.length)) return 1;
|
|
4654
|
+
}
|
|
4655
|
+
return 1;
|
|
4656
|
+
}
|
|
4254
4657
|
|
|
4255
4658
|
// src/core/router/model-profiler.ts
|
|
4256
4659
|
var SKIP_PATTERN = /embed|dall-e|whisper|tts|vision|instruct-vision|rerank/i;
|
|
@@ -4755,6 +5158,36 @@ function azureModelForTier(tier, ranked) {
|
|
|
4755
5158
|
if (tier === "T3") return ranked[ranked.length - 1];
|
|
4756
5159
|
return ranked[Math.min(1, ranked.length - 1)];
|
|
4757
5160
|
}
|
|
5161
|
+
var IMAGE_TOKENS_MAX = 2e3;
|
|
5162
|
+
var IMAGE_TOKENS_EACH = IMAGE_TOKENS_MAX;
|
|
5163
|
+
function safeJson(value) {
|
|
5164
|
+
try {
|
|
5165
|
+
return JSON.stringify(value) ?? "";
|
|
5166
|
+
} catch {
|
|
5167
|
+
return "";
|
|
5168
|
+
}
|
|
5169
|
+
}
|
|
5170
|
+
function newRunAbort() {
|
|
5171
|
+
const controller = new AbortController();
|
|
5172
|
+
setMaxListeners(64, controller.signal);
|
|
5173
|
+
return controller;
|
|
5174
|
+
}
|
|
5175
|
+
var TOKENS_PER_MESSAGE_FRAMING = 4;
|
|
5176
|
+
function guardTokens(text) {
|
|
5177
|
+
if (!text) return 0;
|
|
5178
|
+
let asciiChars = 0;
|
|
5179
|
+
let denseBytes = 0;
|
|
5180
|
+
for (const ch of text) {
|
|
5181
|
+
const cp = ch.codePointAt(0);
|
|
5182
|
+
if (cp <= 127) {
|
|
5183
|
+
asciiChars++;
|
|
5184
|
+
continue;
|
|
5185
|
+
}
|
|
5186
|
+
denseBytes += cp <= 2047 ? 2 : cp <= 65535 ? 3 : 4;
|
|
5187
|
+
}
|
|
5188
|
+
if (denseBytes === 0) return estimateTokens(text);
|
|
5189
|
+
return Math.ceil(asciiChars / CHARS_PER_TOKEN) + denseBytes;
|
|
5190
|
+
}
|
|
4758
5191
|
var DISCOVERY_TTL_MS = 15 * 60 * 1e3;
|
|
4759
5192
|
var DISCOVERY_TIMEOUT_MS = 4e3;
|
|
4760
5193
|
var cloudDiscoveryCache = /* @__PURE__ */ new Map();
|
|
@@ -4807,6 +5240,33 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
4807
5240
|
// start of every `cascade run`, independent of the session-wide budget.
|
|
4808
5241
|
runTokens = 0;
|
|
4809
5242
|
runCostUsd = 0;
|
|
5243
|
+
/**
|
|
5244
|
+
* Budget held for calls that are IN FLIGHT — admitted by the preflight check
|
|
5245
|
+
* but not yet counted by recordStats. Without these, concurrent calls all
|
|
5246
|
+
* measure themselves against the same unspent allowance and are all admitted.
|
|
5247
|
+
* Released when each call settles; reset by beginRun() alongside the totals.
|
|
5248
|
+
*/
|
|
5249
|
+
reservedTokens = 0;
|
|
5250
|
+
reservedCostUsd = 0;
|
|
5251
|
+
/**
|
|
5252
|
+
* Fires when this run's budget is spent, cancelling work already in flight.
|
|
5253
|
+
*
|
|
5254
|
+
* The kill switch was previously only a flag, which stops calls that have
|
|
5255
|
+
* not yet submitted. A parallel wave's earlier members are already at the
|
|
5256
|
+
* provider by then, and they carried on generating for a run whose output
|
|
5257
|
+
* would be discarded. Every provider call chains to this, so tripping the
|
|
5258
|
+
* ceiling reaches the requests that are running, not just the ones queued.
|
|
5259
|
+
*/
|
|
5260
|
+
runAbort = newRunAbort();
|
|
5261
|
+
/**
|
|
5262
|
+
* Bumped by every beginRun(). A release handle remembers the generation it
|
|
5263
|
+
* was issued in and does nothing if that has moved on: beginRun() zeroes the
|
|
5264
|
+
* counters, so a call still in flight from the previous run would otherwise
|
|
5265
|
+
* subtract its estimate from the NEW run's totals and drive them negative —
|
|
5266
|
+
* leaving later preflights believing there is more allowance than the cap
|
|
5267
|
+
* actually holds, which is worse than no gate at all.
|
|
5268
|
+
*/
|
|
5269
|
+
runGeneration = 0;
|
|
4810
5270
|
runBudgetExceeded = false;
|
|
4811
5271
|
runBudgetExceededReason;
|
|
4812
5272
|
/**
|
|
@@ -4976,7 +5436,7 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
4976
5436
|
try {
|
|
4977
5437
|
const cfg = this.config.providers.find((p) => p.type === model.provider) ?? { type: model.provider };
|
|
4978
5438
|
const provider = this.createProvider(cfg, model);
|
|
4979
|
-
const result = await
|
|
5439
|
+
const result = await withTimeoutAbort((signal) => provider.generate({
|
|
4980
5440
|
messages: [{ role: "user", content: "Call the echo tool with text set to 'ping'. Use the tool; do not answer in prose." }],
|
|
4981
5441
|
tools: [{
|
|
4982
5442
|
name: "echo",
|
|
@@ -4984,7 +5444,8 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
4984
5444
|
inputSchema: { type: "object", properties: { text: { type: "string", description: "Text to echo" } }, required: ["text"] }
|
|
4985
5445
|
}],
|
|
4986
5446
|
maxTokens: 80,
|
|
4987
|
-
temperature: 0
|
|
5447
|
+
temperature: 0,
|
|
5448
|
+
signal
|
|
4988
5449
|
}), 3e4, "tool-support probe timed out");
|
|
4989
5450
|
return (result.toolCalls?.length ?? 0) > 0;
|
|
4990
5451
|
} catch {
|
|
@@ -5105,9 +5566,12 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
5105
5566
|
this.runBudgetExceededReason ?? "Per-task budget exceeded."
|
|
5106
5567
|
);
|
|
5107
5568
|
}
|
|
5569
|
+
const runGeneration = this.runGeneration;
|
|
5570
|
+
const runAbort = this.runAbort;
|
|
5571
|
+
const runSignal = this.runSignal;
|
|
5108
5572
|
options = applyTierLimits(options, tier, this.config?.tierLimits);
|
|
5109
|
-
if (
|
|
5110
|
-
options = { ...options, signal:
|
|
5573
|
+
if (runSignal && !options.signal) {
|
|
5574
|
+
options = { ...options, signal: runSignal };
|
|
5111
5575
|
}
|
|
5112
5576
|
if (options.model && !requireVision) {
|
|
5113
5577
|
this.ensureProvider(options.model, this.config.providers);
|
|
@@ -5129,58 +5593,84 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
5129
5593
|
if (!model) throw new Error(`No model available for tier ${tier}`);
|
|
5130
5594
|
const provider = this.getProvider(model);
|
|
5131
5595
|
if (!provider) throw new Error(`No provider for model ${model.id}`);
|
|
5596
|
+
let releaseReservation = this.enforcePreflightBudget(model, options);
|
|
5132
5597
|
const requestedTokens = options.maxTokens ?? model.maxOutputTokens ?? 1024;
|
|
5133
5598
|
const estimatedTokens = Math.min(requestedTokens, model.maxOutputTokens ?? requestedTokens) + 512;
|
|
5134
5599
|
if (this.tpmLimiter) {
|
|
5135
|
-
|
|
5600
|
+
const wait = anySignal([options.signal, runAbort.signal]);
|
|
5601
|
+
try {
|
|
5602
|
+
await this.tpmLimiter.acquire(model.provider, estimatedTokens, wait.signal);
|
|
5603
|
+
} catch (err) {
|
|
5604
|
+
releaseReservation?.();
|
|
5605
|
+
throw this.asAbortFailure(err, options.signal, runSignal);
|
|
5606
|
+
} finally {
|
|
5607
|
+
wait.release();
|
|
5608
|
+
}
|
|
5136
5609
|
}
|
|
5137
5610
|
const useStream = Boolean(onChunk) && model.supportsStreaming && typeof provider.generateStream === "function";
|
|
5138
5611
|
let releaseLocalSlot;
|
|
5139
5612
|
if (model.isLocal) {
|
|
5140
5613
|
const inferenceTimeoutMs = this.config.localInferenceTimeoutMs ?? 3e5;
|
|
5141
5614
|
const queueWaitMs = Math.round(inferenceTimeoutMs / 2);
|
|
5142
|
-
|
|
5615
|
+
const wait = anySignal([options.signal, runAbort.signal]);
|
|
5616
|
+
try {
|
|
5617
|
+
releaseLocalSlot = await this.localQueue.acquire(queueWaitMs, wait.signal);
|
|
5618
|
+
} catch (err) {
|
|
5619
|
+
releaseReservation?.();
|
|
5620
|
+
throw this.asAbortFailure(err, options.signal, runSignal);
|
|
5621
|
+
} finally {
|
|
5622
|
+
wait.release();
|
|
5623
|
+
}
|
|
5143
5624
|
}
|
|
5144
5625
|
try {
|
|
5626
|
+
if (runGeneration === this.runGeneration && this.runBudgetExceeded) {
|
|
5627
|
+
this.tpmLimiter?.refund(model.provider, estimatedTokens);
|
|
5628
|
+
throw new _CascadeRouter.BudgetExceededError(
|
|
5629
|
+
this.runBudgetExceededReason ?? "Per-task budget exceeded."
|
|
5630
|
+
);
|
|
5631
|
+
}
|
|
5145
5632
|
let result;
|
|
5146
5633
|
if (model.isLocal) {
|
|
5147
5634
|
const inferenceTimeoutMs = this.config.localInferenceTimeoutMs ?? 3e5;
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5153
|
-
inferencePromise,
|
|
5635
|
+
result = await withTimeoutAbort(
|
|
5636
|
+
(signal) => useStream && onChunk ? provider.generateStream({ ...options, signal }, (chunk) => {
|
|
5637
|
+
const text = typeof chunk?.text === "string" ? chunk.text : "";
|
|
5638
|
+
if (text) onChunk({ ...chunk, text });
|
|
5639
|
+
}) : provider.generate({ ...options, signal }),
|
|
5154
5640
|
inferenceTimeoutMs,
|
|
5155
|
-
`Local model ${model.id} inference timed out after ${inferenceTimeoutMs}ms
|
|
5641
|
+
`Local model ${model.id} inference timed out after ${inferenceTimeoutMs}ms`,
|
|
5642
|
+
[options.signal, runAbort.signal]
|
|
5156
5643
|
);
|
|
5157
5644
|
} else if (useStream && onChunk) {
|
|
5158
5645
|
const cloudTimeoutMs = this.config.cloudInferenceTimeoutMs ?? 12e4;
|
|
5159
5646
|
try {
|
|
5160
|
-
result = await
|
|
5161
|
-
provider.generateStream(options, (chunk) => {
|
|
5647
|
+
result = await withTimeoutAbort(
|
|
5648
|
+
(signal) => provider.generateStream({ ...options, signal }, (chunk) => {
|
|
5162
5649
|
const text = typeof chunk?.text === "string" ? chunk.text : "";
|
|
5163
5650
|
if (text) onChunk({ ...chunk, text });
|
|
5164
5651
|
}),
|
|
5165
5652
|
cloudTimeoutMs,
|
|
5166
|
-
`Model ${model.id} stream timed out after ${cloudTimeoutMs}ms
|
|
5653
|
+
`Model ${model.id} stream timed out after ${cloudTimeoutMs}ms`,
|
|
5654
|
+
[options.signal, runAbort.signal]
|
|
5167
5655
|
);
|
|
5168
5656
|
} catch (streamErr) {
|
|
5169
|
-
if (streamErr instanceof Error && streamErr.name === "AbortError" ||
|
|
5657
|
+
if (streamErr instanceof Error && streamErr.name === "AbortError" || runSignal?.aborted || options.signal?.aborted) {
|
|
5170
5658
|
throw streamErr;
|
|
5171
5659
|
}
|
|
5172
|
-
result = await
|
|
5173
|
-
provider.generate(options),
|
|
5660
|
+
result = await withTimeoutAbort(
|
|
5661
|
+
(signal) => provider.generate({ ...options, signal }),
|
|
5174
5662
|
cloudTimeoutMs,
|
|
5175
|
-
`Model ${model.id} inference timed out after ${cloudTimeoutMs}ms
|
|
5663
|
+
`Model ${model.id} inference timed out after ${cloudTimeoutMs}ms`,
|
|
5664
|
+
[options.signal, runAbort.signal]
|
|
5176
5665
|
);
|
|
5177
5666
|
}
|
|
5178
5667
|
} else {
|
|
5179
5668
|
const cloudTimeoutMs = this.config.cloudInferenceTimeoutMs ?? 12e4;
|
|
5180
|
-
result = await
|
|
5181
|
-
provider.generate(options),
|
|
5669
|
+
result = await withTimeoutAbort(
|
|
5670
|
+
(signal) => provider.generate({ ...options, signal }),
|
|
5182
5671
|
cloudTimeoutMs,
|
|
5183
|
-
`Model ${model.id} inference timed out after ${cloudTimeoutMs}ms
|
|
5672
|
+
`Model ${model.id} inference timed out after ${cloudTimeoutMs}ms`,
|
|
5673
|
+
[options.signal, runAbort.signal]
|
|
5184
5674
|
);
|
|
5185
5675
|
}
|
|
5186
5676
|
const corrected = buildTokenUsage(
|
|
@@ -5199,11 +5689,12 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
5199
5689
|
if (!result || typeof result.content !== "string" || !result.usage) {
|
|
5200
5690
|
throw new Error(`Provider ${model.provider}:${model.id} returned an invalid generation result.`);
|
|
5201
5691
|
}
|
|
5202
|
-
this.recordStats(tier, model, result.usage, options.featureTag);
|
|
5692
|
+
this.recordStats(tier, model, result.usage, options.featureTag, runGeneration);
|
|
5203
5693
|
this.failover.recordSuccess(model.provider);
|
|
5204
5694
|
return result;
|
|
5205
5695
|
} catch (err) {
|
|
5206
|
-
if (err instanceof
|
|
5696
|
+
if (err instanceof _CascadeRouter.BudgetExceededError) throw err;
|
|
5697
|
+
if (err instanceof Error && err.name === "AbortError" || runSignal?.aborted || options.signal?.aborted) {
|
|
5207
5698
|
throw new CascadeCancelledError("Run cancelled");
|
|
5208
5699
|
}
|
|
5209
5700
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
@@ -5221,6 +5712,8 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
5221
5712
|
});
|
|
5222
5713
|
releaseLocalSlot?.();
|
|
5223
5714
|
releaseLocalSlot = void 0;
|
|
5715
|
+
releaseReservation?.();
|
|
5716
|
+
releaseReservation = void 0;
|
|
5224
5717
|
const retryOpts = options.model && options.model.id === model.id ? { ...options, model: void 0 } : options;
|
|
5225
5718
|
return this.generate(tier, retryOpts, onChunk, requireVision);
|
|
5226
5719
|
}
|
|
@@ -5242,6 +5735,8 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
5242
5735
|
});
|
|
5243
5736
|
releaseLocalSlot?.();
|
|
5244
5737
|
releaseLocalSlot = void 0;
|
|
5738
|
+
releaseReservation?.();
|
|
5739
|
+
releaseReservation = void 0;
|
|
5245
5740
|
const base = options.model && options.model.id === model.id ? { ...options, model: void 0 } : options;
|
|
5246
5741
|
const retryOpts = { ...base, _notFoundDepth: depth };
|
|
5247
5742
|
return this.generate(tier, retryOpts, onChunk, requireVision);
|
|
@@ -5250,6 +5745,8 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
5250
5745
|
throw err;
|
|
5251
5746
|
} finally {
|
|
5252
5747
|
releaseLocalSlot?.();
|
|
5748
|
+
releaseReservation?.();
|
|
5749
|
+
releaseReservation = void 0;
|
|
5253
5750
|
}
|
|
5254
5751
|
}
|
|
5255
5752
|
getModelForTier(tier) {
|
|
@@ -5512,12 +6009,28 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
5512
6009
|
return this.selector?.getAllAvailableModels() ?? [];
|
|
5513
6010
|
}
|
|
5514
6011
|
// ── Private ──────────────────────────────────
|
|
6012
|
+
/**
|
|
6013
|
+
* Why each configured provider failed its availability probe, kept so the
|
|
6014
|
+
* surfaces can say something better than "add an API key" to a user who
|
|
6015
|
+
* already has one. Cleared and refilled by each detectAvailableProviders().
|
|
6016
|
+
*/
|
|
6017
|
+
probeFailures = /* @__PURE__ */ new Map();
|
|
6018
|
+
/**
|
|
6019
|
+
* What stopped the configured providers from being usable, if anything.
|
|
6020
|
+
* Empty when every configured provider passed — or when none was configured,
|
|
6021
|
+
* which is a different problem with a different answer.
|
|
6022
|
+
*/
|
|
6023
|
+
providerProbeFailures() {
|
|
6024
|
+
return [...this.probeFailures].map(([provider, reason]) => ({ provider, reason }));
|
|
6025
|
+
}
|
|
5515
6026
|
/** Logs why a configured provider failed its availability probe. */
|
|
5516
6027
|
emitProbeFailure(type, reason) {
|
|
6028
|
+
this.probeFailures.set(type, reason);
|
|
5517
6029
|
console.warn(`[router] provider "${type}" is not available: ${reason}`);
|
|
5518
6030
|
}
|
|
5519
6031
|
async detectAvailableProviders(configs) {
|
|
5520
6032
|
const available = /* @__PURE__ */ new Set();
|
|
6033
|
+
this.probeFailures.clear();
|
|
5521
6034
|
const checks = configs.map(async (cfg) => {
|
|
5522
6035
|
try {
|
|
5523
6036
|
const testModel = this.getAnyModelForProvider(cfg.type);
|
|
@@ -5527,7 +6040,13 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
5527
6040
|
if (ok) available.add(cfg.type);
|
|
5528
6041
|
else this.emitProbeFailure(cfg.type, "availability check returned false (bad key, wrong endpoint/deployment, or unreachable)");
|
|
5529
6042
|
} catch (err) {
|
|
5530
|
-
|
|
6043
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
6044
|
+
if (err instanceof ProviderUnreachableError) {
|
|
6045
|
+
available.add(cfg.type);
|
|
6046
|
+
console.warn(`[router] provider "${cfg.type}" probe did not complete: ${reason} \u2014 continuing with it enabled`);
|
|
6047
|
+
return;
|
|
6048
|
+
}
|
|
6049
|
+
this.emitProbeFailure(cfg.type, reason);
|
|
5531
6050
|
}
|
|
5532
6051
|
});
|
|
5533
6052
|
await Promise.allSettled(checks);
|
|
@@ -5620,7 +6139,14 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
5620
6139
|
}
|
|
5621
6140
|
return void 0;
|
|
5622
6141
|
}
|
|
5623
|
-
|
|
6142
|
+
/**
|
|
6143
|
+
* `runGeneration` is the generation the call was ADMITTED in — see
|
|
6144
|
+
* `generate()`. Session-wide accounting always applies (the spend is real
|
|
6145
|
+
* whenever it lands), but the per-RUN ceiling is only meaningful for the run
|
|
6146
|
+
* the call belongs to, so a straggler from a finished run is left out of it.
|
|
6147
|
+
* Omitted by the direct callers in tests, which have no run to straddle.
|
|
6148
|
+
*/
|
|
6149
|
+
recordStats(tier, model, usage, featureTag, runGeneration) {
|
|
5624
6150
|
this.stats.totalTokens += usage.totalTokens;
|
|
5625
6151
|
this.stats.totalCostUsd += usage.estimatedCostUsd;
|
|
5626
6152
|
this.sessionCostUsd += usage.estimatedCostUsd;
|
|
@@ -5645,10 +6171,13 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
5645
6171
|
});
|
|
5646
6172
|
}
|
|
5647
6173
|
}
|
|
5648
|
-
|
|
5649
|
-
|
|
6174
|
+
const currentRun = runGeneration === void 0 || runGeneration === this.runGeneration;
|
|
6175
|
+
if (currentRun) {
|
|
6176
|
+
this.runTokens += usage.totalTokens;
|
|
6177
|
+
this.runCostUsd += usage.estimatedCostUsd;
|
|
6178
|
+
}
|
|
5650
6179
|
this.updateBudgetState();
|
|
5651
|
-
this.enforceRunBudget();
|
|
6180
|
+
if (currentRun) this.enforceRunBudget();
|
|
5652
6181
|
}
|
|
5653
6182
|
/**
|
|
5654
6183
|
* Resets per-run accounting at the start of each `cascade run`. Session
|
|
@@ -5658,9 +6187,158 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
5658
6187
|
beginRun() {
|
|
5659
6188
|
this.runTokens = 0;
|
|
5660
6189
|
this.runCostUsd = 0;
|
|
6190
|
+
this.reservedTokens = 0;
|
|
6191
|
+
this.reservedCostUsd = 0;
|
|
6192
|
+
this.runAbort = newRunAbort();
|
|
6193
|
+
this.runGeneration++;
|
|
5661
6194
|
this.runBudgetExceeded = false;
|
|
5662
6195
|
this.runBudgetExceededReason = void 0;
|
|
5663
6196
|
}
|
|
6197
|
+
/**
|
|
6198
|
+
* Refuse a call whose input alone cannot fit what is left of the run budget.
|
|
6199
|
+
*
|
|
6200
|
+
* `enforceRunBudget()` below runs AFTER a call returns, which makes the caps
|
|
6201
|
+
* a stop rather than a pre-authorisation: the tokens are already bought by
|
|
6202
|
+
* the time it looks. That was tolerable while a prompt could not exceed
|
|
6203
|
+
* 20,000 characters, and stopped being so when that cap was removed in
|
|
6204
|
+
* 0.72.0 — a multi-megabyte prompt is billed in full on the very first call
|
|
6205
|
+
* (the complexity classifier sends the whole thing), and no ceiling
|
|
6206
|
+
* configured downstream can give that money back.
|
|
6207
|
+
*
|
|
6208
|
+
* Deliberately narrow, because a false refusal is worse than a late stop:
|
|
6209
|
+
*
|
|
6210
|
+
* • INPUT ONLY. What a call returns is not knowable in advance, and
|
|
6211
|
+
* charging a worst-case `maxTokens` of output against the budget would
|
|
6212
|
+
* decline runs that would have finished comfortably. Input is the part
|
|
6213
|
+
* that is already determined, and it is the part the removed cap used to
|
|
6214
|
+
* bound.
|
|
6215
|
+
* • Compared against what REMAINS (`cap - spent`), not the whole cap, so a
|
|
6216
|
+
* call is judged on the budget it can actually draw from.
|
|
6217
|
+
* • Skipped when the model has no usable price. An estimate cannot be made,
|
|
6218
|
+
* and refusing on ignorance would break every self-hosted and local model
|
|
6219
|
+
* the moment a cost cap is set. The post-hoc stop still applies there.
|
|
6220
|
+
* • Skipped entirely when no cap is configured — there is nothing to
|
|
6221
|
+
* pre-authorise against.
|
|
6222
|
+
*
|
|
6223
|
+
* The message names the cap AND the estimate, because "too expensive" with
|
|
6224
|
+
* no numbers gives the user nothing to change.
|
|
6225
|
+
*/
|
|
6226
|
+
enforcePreflightBudget(model, options) {
|
|
6227
|
+
const budget = this.config?.budget;
|
|
6228
|
+
const maxCost = budget?.maxCostPerRunUsd;
|
|
6229
|
+
const maxTokens = budget?.maxTokensPerRun;
|
|
6230
|
+
if (maxCost == null && maxTokens == null) return void 0;
|
|
6231
|
+
const inputTokens = this.estimateInputTokens(options, model);
|
|
6232
|
+
if (maxTokens != null) {
|
|
6233
|
+
const remaining = maxTokens - this.runTokens - this.reservedTokens;
|
|
6234
|
+
if (inputTokens > remaining) {
|
|
6235
|
+
this.failPreflight(
|
|
6236
|
+
`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.`
|
|
6237
|
+
);
|
|
6238
|
+
}
|
|
6239
|
+
}
|
|
6240
|
+
let estimatedUsd = 0;
|
|
6241
|
+
if (maxCost != null) {
|
|
6242
|
+
const { input: inputPer1k, unknown } = resolveModelPricing(model, { inputTokens });
|
|
6243
|
+
if (!unknown && inputPer1k > 0) {
|
|
6244
|
+
estimatedUsd = inputTokens / 1e3 * inputPer1k;
|
|
6245
|
+
const remaining = maxCost - this.runCostUsd - this.reservedCostUsd;
|
|
6246
|
+
if (estimatedUsd > remaining) {
|
|
6247
|
+
this.failPreflight(
|
|
6248
|
+
`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.`
|
|
6249
|
+
);
|
|
6250
|
+
}
|
|
6251
|
+
}
|
|
6252
|
+
}
|
|
6253
|
+
this.reservedTokens += inputTokens;
|
|
6254
|
+
this.reservedCostUsd += estimatedUsd;
|
|
6255
|
+
const generation = this.runGeneration;
|
|
6256
|
+
let released = false;
|
|
6257
|
+
return () => {
|
|
6258
|
+
if (released || generation !== this.runGeneration) return;
|
|
6259
|
+
released = true;
|
|
6260
|
+
this.reservedTokens -= inputTokens;
|
|
6261
|
+
this.reservedCostUsd -= estimatedUsd;
|
|
6262
|
+
};
|
|
6263
|
+
}
|
|
6264
|
+
/**
|
|
6265
|
+
* What this call will send, in tokens.
|
|
6266
|
+
*
|
|
6267
|
+
* Text is the bulk of it, but not all of it: providers bill the serialized
|
|
6268
|
+
* TOOL DEFINITIONS on every native-tool call (Anthropic sends each name,
|
|
6269
|
+
* description and input schema), and an image is billed by its own size
|
|
6270
|
+
* rather than as the `[image]` placeholder `contentToText` reduces it to. A
|
|
6271
|
+
* tool-heavy or vision request could therefore pass either cap while its real
|
|
6272
|
+
* input already exceeded the allowance.
|
|
6273
|
+
*/
|
|
6274
|
+
estimateInputTokens(options, model) {
|
|
6275
|
+
let tokens = guardTokens(options.systemPrompt ?? "");
|
|
6276
|
+
let images = 0;
|
|
6277
|
+
const wire = wireProfile(model.provider);
|
|
6278
|
+
const countsImage = (img) => wire.sendsUrlImages || img?.type === "base64";
|
|
6279
|
+
for (const message of options.messages) {
|
|
6280
|
+
if (wire.dropsMessage(message)) continue;
|
|
6281
|
+
tokens += TOKENS_PER_MESSAGE_FRAMING;
|
|
6282
|
+
if (typeof message.content === "string") {
|
|
6283
|
+
tokens += guardTokens(message.content);
|
|
6284
|
+
} else {
|
|
6285
|
+
switch (wire.blockHandling(message)) {
|
|
6286
|
+
case "blocks":
|
|
6287
|
+
for (const block of message.content) {
|
|
6288
|
+
if (!wire.sendsBlock(block)) continue;
|
|
6289
|
+
if (block.type === "image") {
|
|
6290
|
+
if (countsImage(block.image)) images++;
|
|
6291
|
+
continue;
|
|
6292
|
+
}
|
|
6293
|
+
tokens += guardTokens(contentToText([block]));
|
|
6294
|
+
}
|
|
6295
|
+
break;
|
|
6296
|
+
case "stringified":
|
|
6297
|
+
tokens += guardTokens(safeJson(message.content));
|
|
6298
|
+
break;
|
|
6299
|
+
}
|
|
6300
|
+
}
|
|
6301
|
+
if (message.toolCalls?.length && wire.sendsToolCalls(message)) {
|
|
6302
|
+
tokens += guardTokens(safeJson(wire.sizeToolCalls(message.toolCalls)));
|
|
6303
|
+
}
|
|
6304
|
+
if (message.toolCallId && message.role === "tool" && wire.sendsToolCallId) {
|
|
6305
|
+
tokens += guardTokens(message.toolCallId);
|
|
6306
|
+
}
|
|
6307
|
+
}
|
|
6308
|
+
if (options.tools?.length) {
|
|
6309
|
+
tokens += guardTokens(safeJson(wire.sizeTools(options.tools)));
|
|
6310
|
+
}
|
|
6311
|
+
if (wire.readsTopLevelImages) {
|
|
6312
|
+
const topLevel = (options.images ?? []).filter(countsImage).length;
|
|
6313
|
+
images += topLevel * geminiImageCopies(options.messages);
|
|
6314
|
+
}
|
|
6315
|
+
tokens += images * IMAGE_TOKENS_EACH;
|
|
6316
|
+
return tokens;
|
|
6317
|
+
}
|
|
6318
|
+
/**
|
|
6319
|
+
* Report a wait that ended because something asked it to stop, the same way
|
|
6320
|
+
* the provider call below reports one.
|
|
6321
|
+
*
|
|
6322
|
+
* The queue waits sit OUTSIDE the try/catch that maps aborts, so without
|
|
6323
|
+
* this a cancellation surfaced as whatever the queue happened to throw and
|
|
6324
|
+
* was retried or failed over like an ordinary error. A budget abort keeps
|
|
6325
|
+
* saying it ran out of budget — otherwise the reason the run stopped is lost
|
|
6326
|
+
* on the way up — and anything else that was aborted becomes a cancellation.
|
|
6327
|
+
*/
|
|
6328
|
+
asAbortFailure(err, callSignal, runSignal) {
|
|
6329
|
+
if (err instanceof _CascadeRouter.BudgetExceededError) return err;
|
|
6330
|
+
if (err instanceof CascadeCancelledError) return err;
|
|
6331
|
+
if (callSignal?.aborted || runSignal?.aborted) return new CascadeCancelledError("Run cancelled");
|
|
6332
|
+
return err;
|
|
6333
|
+
}
|
|
6334
|
+
/** Trips the same run-level flag a post-hoc overrun does, and reports it the same way. */
|
|
6335
|
+
failPreflight(reason) {
|
|
6336
|
+
this.runBudgetExceeded = true;
|
|
6337
|
+
this.runBudgetExceededReason = reason;
|
|
6338
|
+
this.runAbort.abort(new _CascadeRouter.BudgetExceededError(reason));
|
|
6339
|
+
this.emit("budget:exceeded", { reason, spentUsd: this.sessionCostUsd });
|
|
6340
|
+
throw new _CascadeRouter.BudgetExceededError(reason);
|
|
6341
|
+
}
|
|
5664
6342
|
/**
|
|
5665
6343
|
* Enforce the hard per-task ceiling. Once tripped, the flag makes every
|
|
5666
6344
|
* subsequent (and concurrent) generate() call in this run fail fast.
|
|
@@ -5676,6 +6354,7 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
5676
6354
|
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
6355
|
this.runBudgetExceeded = true;
|
|
5678
6356
|
this.runBudgetExceededReason = reason;
|
|
6357
|
+
this.runAbort.abort(new _CascadeRouter.BudgetExceededError(reason));
|
|
5679
6358
|
this.emit("budget:exceeded", { reason, spentUsd: this.sessionCostUsd });
|
|
5680
6359
|
throw new _CascadeRouter.BudgetExceededError(reason);
|
|
5681
6360
|
}
|
|
@@ -6912,6 +7591,12 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : "") + textToolSuffix,
|
|
|
6912
7591
|
maxTokens: 4096,
|
|
6913
7592
|
...subtaskModel ? { model: subtaskModel } : {},
|
|
6914
7593
|
featureTag: this.assignment?.sectionTitle,
|
|
7594
|
+
// The wave signal, so cancel-and-respawn reaches the request that is
|
|
7595
|
+
// RUNNING rather than only stopping the worker at its next checkpoint.
|
|
7596
|
+
// It is a superset of the run signal the router injects when this is
|
|
7597
|
+
// absent (T2 composes it from that signal plus its own per-wave one),
|
|
7598
|
+
// so wiring it here loses no cancellation and adds the wave's.
|
|
7599
|
+
...this.signal ? { signal: this.signal } : {},
|
|
6915
7600
|
...this.localOnlyMatch ? { forceLocal: true } : {}
|
|
6916
7601
|
};
|
|
6917
7602
|
const result = await this.router.generate(
|
|
@@ -7390,6 +8075,7 @@ ${current}`
|
|
|
7390
8075
|
HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
7391
8076
|
maxTokens: 4096,
|
|
7392
8077
|
featureTag: assignment.sectionTitle,
|
|
8078
|
+
...this.signal ? { signal: this.signal } : {},
|
|
7393
8079
|
...this.localOnlyMatch ? { forceLocal: true } : {}
|
|
7394
8080
|
});
|
|
7395
8081
|
const next = (improved.content ?? "").trim();
|
|
@@ -7451,6 +8137,7 @@ Reply with JSON: { "completeness": "pass"|"fail", "correctness": "pass"|"fail",
|
|
|
7451
8137
|
|
|
7452
8138
|
HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
7453
8139
|
featureTag: assignment.sectionTitle,
|
|
8140
|
+
...this.signal ? { signal: this.signal } : {},
|
|
7454
8141
|
...this.localOnlyMatch ? { forceLocal: true } : {}
|
|
7455
8142
|
});
|
|
7456
8143
|
try {
|
|
@@ -7490,6 +8177,7 @@ ${output.slice(0, 4e3)}`;
|
|
|
7490
8177
|
messages: [{ role: "user", content: prompt }],
|
|
7491
8178
|
maxTokens: 300,
|
|
7492
8179
|
temperature: 0,
|
|
8180
|
+
...this.signal ? { signal: this.signal } : {},
|
|
7493
8181
|
...this.localOnlyMatch ? { forceLocal: true } : {}
|
|
7494
8182
|
});
|
|
7495
8183
|
const match = /\[[\s\S]*\]/.exec(result.content);
|
|
@@ -7601,6 +8289,10 @@ Begin execution now.`;
|
|
|
7601
8289
|
}
|
|
7602
8290
|
};
|
|
7603
8291
|
var PeerBus = class extends EventEmitter {
|
|
8292
|
+
constructor() {
|
|
8293
|
+
super();
|
|
8294
|
+
this.setMaxListeners(64);
|
|
8295
|
+
}
|
|
7604
8296
|
outputs = /* @__PURE__ */ new Map();
|
|
7605
8297
|
waiters = /* @__PURE__ */ new Map();
|
|
7606
8298
|
members = /* @__PURE__ */ new Set();
|
|
@@ -8848,6 +9540,7 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
8848
9540
|
const waveSignal = AbortSignal.any(
|
|
8849
9541
|
[this.signal, this.waveAbortController.signal].filter(Boolean)
|
|
8850
9542
|
);
|
|
9543
|
+
setMaxListeners(64, waveSignal);
|
|
8851
9544
|
const runOne = async (id) => {
|
|
8852
9545
|
const assignment = sanitizedAssignments.find((a) => a.subtaskId === id);
|
|
8853
9546
|
const worker = workerMap.get(id);
|
|
@@ -16491,115 +17184,6 @@ var GraphSearchTool = class extends BaseTool {
|
|
|
16491
17184
|
}
|
|
16492
17185
|
};
|
|
16493
17186
|
|
|
16494
|
-
// src/core/context/compaction.ts
|
|
16495
|
-
function estimateTokens(text) {
|
|
16496
|
-
if (!text) return 0;
|
|
16497
|
-
return Math.max(1, Math.ceil(text.length / 4));
|
|
16498
|
-
}
|
|
16499
|
-
function contentToText(content) {
|
|
16500
|
-
if (typeof content === "string") return content;
|
|
16501
|
-
return content.map((c) => c.type === "text" ? c.text : c.type === "tool_result" ? c.content : `[${c.type}]`).join(" ");
|
|
16502
|
-
}
|
|
16503
|
-
function messagesTokens(messages) {
|
|
16504
|
-
return messages.reduce((n, m) => n + estimateTokens(contentToText(m.content)), 0);
|
|
16505
|
-
}
|
|
16506
|
-
function atomize(text, maxChars) {
|
|
16507
|
-
const seps = ["\n\n", "\n", ". ", " "];
|
|
16508
|
-
let units = [text];
|
|
16509
|
-
for (const sep of seps) {
|
|
16510
|
-
if (units.every((u) => u.length <= maxChars)) break;
|
|
16511
|
-
const next = [];
|
|
16512
|
-
for (const u of units) {
|
|
16513
|
-
if (u.length <= maxChars) {
|
|
16514
|
-
next.push(u);
|
|
16515
|
-
continue;
|
|
16516
|
-
}
|
|
16517
|
-
const parts = u.split(sep);
|
|
16518
|
-
parts.forEach((p, i) => next.push(i < parts.length - 1 ? p + sep : p));
|
|
16519
|
-
}
|
|
16520
|
-
units = next;
|
|
16521
|
-
}
|
|
16522
|
-
const out = [];
|
|
16523
|
-
for (const u of units) {
|
|
16524
|
-
if (u.length <= maxChars) out.push(u);
|
|
16525
|
-
else for (let i = 0; i < u.length; i += maxChars) out.push(u.slice(i, i + maxChars));
|
|
16526
|
-
}
|
|
16527
|
-
return out;
|
|
16528
|
-
}
|
|
16529
|
-
function chunkText(text, opts) {
|
|
16530
|
-
const maxChars = Math.max(1, Math.floor(opts.maxTokens * 4));
|
|
16531
|
-
const overlap = Math.min(maxChars - 1, Math.max(0, Math.floor(maxChars * (opts.overlapRatio ?? 0.1))));
|
|
16532
|
-
const trimmed = text.trim();
|
|
16533
|
-
if (!trimmed) return [];
|
|
16534
|
-
if (trimmed.length <= maxChars) return [trimmed];
|
|
16535
|
-
const units = atomize(trimmed, maxChars);
|
|
16536
|
-
const chunks = [];
|
|
16537
|
-
let cur = "";
|
|
16538
|
-
for (const u of units) {
|
|
16539
|
-
if (cur && cur.length + u.length > maxChars) {
|
|
16540
|
-
chunks.push(cur.trim());
|
|
16541
|
-
cur = overlap > 0 ? cur.slice(-overlap) : "";
|
|
16542
|
-
}
|
|
16543
|
-
cur += u;
|
|
16544
|
-
}
|
|
16545
|
-
if (cur.trim()) chunks.push(cur.trim());
|
|
16546
|
-
return chunks;
|
|
16547
|
-
}
|
|
16548
|
-
function needsCompaction(totalTokens, contextWindow, reserve = 0.2) {
|
|
16549
|
-
if (!contextWindow || contextWindow <= 0) return false;
|
|
16550
|
-
return totalTokens > contextWindow * (1 - reserve);
|
|
16551
|
-
}
|
|
16552
|
-
async function mapReduceCompact(text, opts) {
|
|
16553
|
-
const capChars = opts.capTokens * 4;
|
|
16554
|
-
let working = text;
|
|
16555
|
-
let truncated = false;
|
|
16556
|
-
if (working.length > capChars) {
|
|
16557
|
-
working = working.slice(0, capChars);
|
|
16558
|
-
truncated = true;
|
|
16559
|
-
}
|
|
16560
|
-
const instruction = opts.instruction ?? "Summarize this content, preserving key facts, names, numbers, and decisions.";
|
|
16561
|
-
let chunks = chunkText(working, { maxTokens: opts.chunkTokens });
|
|
16562
|
-
if (chunks.length <= 1) return { text: working, chunks: chunks.length, calls: 0, truncated };
|
|
16563
|
-
let calls = 0;
|
|
16564
|
-
let parts = await Promise.all(
|
|
16565
|
-
chunks.map((c, i) => {
|
|
16566
|
-
calls++;
|
|
16567
|
-
return opts.summarize(c, `${instruction} (part ${i + 1} of ${chunks.length})`);
|
|
16568
|
-
})
|
|
16569
|
-
);
|
|
16570
|
-
let combined = parts.join("\n\n");
|
|
16571
|
-
const maxPasses = opts.maxReducePasses ?? 3;
|
|
16572
|
-
for (let pass = 0; pass < maxPasses && estimateTokens(combined) > opts.targetTokens; pass++) {
|
|
16573
|
-
const groups = chunkText(combined, { maxTokens: opts.chunkTokens });
|
|
16574
|
-
if (groups.length <= 1) break;
|
|
16575
|
-
parts = await Promise.all(
|
|
16576
|
-
groups.map((g) => {
|
|
16577
|
-
calls++;
|
|
16578
|
-
return opts.summarize(g, "Merge these partial summaries into one cohesive summary; keep all distinct facts.");
|
|
16579
|
-
})
|
|
16580
|
-
);
|
|
16581
|
-
combined = parts.join("\n\n");
|
|
16582
|
-
}
|
|
16583
|
-
const note = truncated ? "\n\n[Note: input exceeded the extended-context cap and was truncated.]" : "";
|
|
16584
|
-
return { text: combined + note, chunks: chunks.length, calls, truncated };
|
|
16585
|
-
}
|
|
16586
|
-
async function rollingSummary(messages, opts) {
|
|
16587
|
-
if (messages.length <= opts.keepRecent) return messages;
|
|
16588
|
-
const older = messages.slice(0, messages.length - opts.keepRecent);
|
|
16589
|
-
const recent = messages.slice(messages.length - opts.keepRecent);
|
|
16590
|
-
const transcript = older.map((m) => `${m.role}: ${contentToText(m.content)}`).join("\n");
|
|
16591
|
-
const summary = await opts.summarize(
|
|
16592
|
-
transcript,
|
|
16593
|
-
"Summarize this earlier conversation. Preserve facts, decisions, names, numbers, and any open threads or todos."
|
|
16594
|
-
);
|
|
16595
|
-
const summaryMessage = {
|
|
16596
|
-
role: "system",
|
|
16597
|
-
content: `Summary of earlier conversation (folded to fit the context window):
|
|
16598
|
-
${summary}`
|
|
16599
|
-
};
|
|
16600
|
-
return [summaryMessage, ...recent];
|
|
16601
|
-
}
|
|
16602
|
-
|
|
16603
17187
|
// src/core/steering/guidance.ts
|
|
16604
17188
|
var GuidanceQueue = class {
|
|
16605
17189
|
entries = [];
|
|
@@ -18151,7 +18735,16 @@ ${finalOutput}` : ""
|
|
|
18151
18735
|
const selector = this.router.getSelector();
|
|
18152
18736
|
const model = options.fastAnswerModel ? selector.selectForTier(tier, options.fastAnswerModel) : selector.getCandidatesForTier(tier)[0] ?? selector.selectForTier(tier);
|
|
18153
18737
|
if (!model) {
|
|
18154
|
-
|
|
18738
|
+
const failures2 = this.router.providerProbeFailures();
|
|
18739
|
+
const context = failures2.length ? ` Configured providers: ${failures2.map((f) => `${f.provider}: ${f.reason}`).join("; ")}` : "";
|
|
18740
|
+
if (options.fastAnswerModel) {
|
|
18741
|
+
throw new Error(
|
|
18742
|
+
`No model is available for a fast answer: the pinned model "${options.fastAnswerModel}" could not be resolved \u2014 check it is spelled correctly and served by a configured provider.${context}`
|
|
18743
|
+
);
|
|
18744
|
+
}
|
|
18745
|
+
throw new Error(
|
|
18746
|
+
failures2.length ? `No model is available for a fast answer.${context}` : "No model is available for a fast answer \u2014 add a provider API key first."
|
|
18747
|
+
);
|
|
18155
18748
|
}
|
|
18156
18749
|
this.recordDecision(
|
|
18157
18750
|
"model",
|
|
@@ -21742,10 +22335,10 @@ ${cur}`;
|
|
|
21742
22335
|
}
|
|
21743
22336
|
|
|
21744
22337
|
// src/retrieval/plan.ts
|
|
21745
|
-
var
|
|
22338
|
+
var CHARS_PER_TOKEN2 = 4;
|
|
21746
22339
|
function cagCharBudget(contextWindowTokens, opts = {}) {
|
|
21747
22340
|
const docFraction = opts.docFraction ?? 0.5;
|
|
21748
|
-
const charsPerToken = opts.charsPerToken ??
|
|
22341
|
+
const charsPerToken = opts.charsPerToken ?? CHARS_PER_TOKEN2;
|
|
21749
22342
|
return Math.max(0, Math.floor(contextWindowTokens * docFraction * charsPerToken));
|
|
21750
22343
|
}
|
|
21751
22344
|
function planRetrieval(ctx) {
|
|
@@ -22312,6 +22905,6 @@ function applySyncBundle(bundle, config, cleanup) {
|
|
|
22312
22905
|
return next;
|
|
22313
22906
|
}
|
|
22314
22907
|
|
|
22315
|
-
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 };
|
|
22908
|
+
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 };
|
|
22316
22909
|
//# sourceMappingURL=index.js.map
|
|
22317
22910
|
//# sourceMappingURL=index.js.map
|