cascade-ai 0.72.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 +734 -220
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +734 -220
- package/dist/cli.js.map +1 -1
- package/dist/desktop-core.cjs +722 -220
- package/dist/index.cjs +721 -219
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +89 -0
- package/dist/index.d.ts +89 -0
- package/dist/index.js +721 -219
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/desktop-core.cjs
CHANGED
|
@@ -64564,7 +64564,7 @@ var init_dist2 = __esm({
|
|
|
64564
64564
|
}
|
|
64565
64565
|
this._maxListeners = this._maxListeners || void 0;
|
|
64566
64566
|
};
|
|
64567
|
-
EventEmitter9.prototype.setMaxListeners = function
|
|
64567
|
+
EventEmitter9.prototype.setMaxListeners = function setMaxListeners4(n8) {
|
|
64568
64568
|
if (typeof n8 !== "number" || n8 < 0 || NumberIsNaN(n8)) throw new RangeError('The value of "n" is out of range. It must be a non-negative number. Received ' + n8 + ".");
|
|
64569
64569
|
this._maxListeners = n8;
|
|
64570
64570
|
return this;
|
|
@@ -291665,6 +291665,15 @@ function findPricing(modelId, provider, opts = {}) {
|
|
|
291665
291665
|
}
|
|
291666
291666
|
return null;
|
|
291667
291667
|
}
|
|
291668
|
+
function hasContextBands(model) {
|
|
291669
|
+
if (model.isLocal) return false;
|
|
291670
|
+
const fallbackProviders = model.provider === "azure" ? ["openai"] : model.provider === "gemini" ? ["vertex"] : [];
|
|
291671
|
+
const match = findPricing(model.id, model.provider, {
|
|
291672
|
+
baseModelId: model.baseModelId,
|
|
291673
|
+
fallbackProviders
|
|
291674
|
+
});
|
|
291675
|
+
return (match?.entry.tiers?.length ?? 0) > 1;
|
|
291676
|
+
}
|
|
291668
291677
|
function tierFor(entry, inputTokens = 0) {
|
|
291669
291678
|
const tiers = entry.tiers;
|
|
291670
291679
|
if (!tiers?.length) return null;
|
|
@@ -291781,7 +291790,7 @@ function reconcilePrice(model, live) {
|
|
|
291781
291790
|
}
|
|
291782
291791
|
|
|
291783
291792
|
// src/constants.ts
|
|
291784
|
-
var CASCADE_VERSION = "0.
|
|
291793
|
+
var CASCADE_VERSION = "0.73.0";
|
|
291785
291794
|
var CASCADE_CONFIG_DIR = ".cascade";
|
|
291786
291795
|
var CASCADE_MD_FILE = "CASCADE.md";
|
|
291787
291796
|
var CASCADE_IGNORE_FILE = ".cascadeignore";
|
|
@@ -292226,8 +292235,23 @@ var PROVIDER_DISPLAY_NAMES = {
|
|
|
292226
292235
|
};
|
|
292227
292236
|
|
|
292228
292237
|
// src/utils/cost.ts
|
|
292229
|
-
function resolveModelPricing(model) {
|
|
292238
|
+
function resolveModelPricing(model, opts = {}) {
|
|
292230
292239
|
if (model.isLocal) return { input: 0, output: 0, unknown: false };
|
|
292240
|
+
if (opts.inputTokens != null && hasContextBands(model)) {
|
|
292241
|
+
const banded = resolvePricing(model, { inputTokens: opts.inputTokens });
|
|
292242
|
+
const base = resolvePricing(model, { inputTokens: 0 });
|
|
292243
|
+
if (!banded.unknown && !base.unknown) {
|
|
292244
|
+
const scale = (stamped, baseRate) => stamped > 0 && baseRate > 0 ? stamped / baseRate : 1;
|
|
292245
|
+
const inScale = scale(model.inputCostPer1kTokens, base.input);
|
|
292246
|
+
const outScale = scale(model.outputCostPer1kTokens, base.output);
|
|
292247
|
+
return {
|
|
292248
|
+
input: banded.input * inScale,
|
|
292249
|
+
output: banded.output * outScale,
|
|
292250
|
+
unknown: false,
|
|
292251
|
+
...banded.estimatedFromProvider ? { estimatedFromProvider: banded.estimatedFromProvider } : {}
|
|
292252
|
+
};
|
|
292253
|
+
}
|
|
292254
|
+
}
|
|
292231
292255
|
if (model.inputCostPer1kTokens > 0 || model.outputCostPer1kTokens > 0) {
|
|
292232
292256
|
return {
|
|
292233
292257
|
input: model.inputCostPer1kTokens,
|
|
@@ -292235,7 +292259,7 @@ function resolveModelPricing(model) {
|
|
|
292235
292259
|
unknown: false
|
|
292236
292260
|
};
|
|
292237
292261
|
}
|
|
292238
|
-
const fromDataset = resolvePricing(model);
|
|
292262
|
+
const fromDataset = resolvePricing(model, { inputTokens: opts.inputTokens });
|
|
292239
292263
|
if (!fromDataset.unknown) {
|
|
292240
292264
|
return {
|
|
292241
292265
|
input: fromDataset.input,
|
|
@@ -292257,11 +292281,11 @@ function resolveModelPricing(model) {
|
|
|
292257
292281
|
return { input: 0, output: 0, unknown: true };
|
|
292258
292282
|
}
|
|
292259
292283
|
function calculateCost(inputTokens, outputTokens, model) {
|
|
292260
|
-
const { input, output } = resolveModelPricing(model);
|
|
292284
|
+
const { input, output } = resolveModelPricing(model, { inputTokens });
|
|
292261
292285
|
return inputTokens / 1e3 * input + outputTokens / 1e3 * output;
|
|
292262
292286
|
}
|
|
292263
292287
|
function buildTokenUsage(inputTokens, outputTokens, model) {
|
|
292264
|
-
const { input, output, unknown: unknown2 } = resolveModelPricing(model);
|
|
292288
|
+
const { input, output, unknown: unknown2 } = resolveModelPricing(model, { inputTokens });
|
|
292265
292289
|
return {
|
|
292266
292290
|
inputTokens,
|
|
292267
292291
|
outputTokens,
|
|
@@ -292340,6 +292364,21 @@ function anthropicThinkingParam(modelId, maxTokens) {
|
|
|
292340
292364
|
if (budget < 1024) return {};
|
|
292341
292365
|
return { thinking: { type: "enabled", budget_tokens: budget } };
|
|
292342
292366
|
}
|
|
292367
|
+
function toAnthropicTools(tools) {
|
|
292368
|
+
return tools.map((t4) => ({
|
|
292369
|
+
name: t4.name,
|
|
292370
|
+
description: t4.description,
|
|
292371
|
+
input_schema: t4.inputSchema
|
|
292372
|
+
}));
|
|
292373
|
+
}
|
|
292374
|
+
function toAnthropicToolUse(toolCalls) {
|
|
292375
|
+
return toolCalls.map((tc) => ({
|
|
292376
|
+
type: "tool_use",
|
|
292377
|
+
id: tc.id,
|
|
292378
|
+
name: tc.name,
|
|
292379
|
+
input: tc.input
|
|
292380
|
+
}));
|
|
292381
|
+
}
|
|
292343
292382
|
var AnthropicProvider = class extends BaseProvider {
|
|
292344
292383
|
client;
|
|
292345
292384
|
constructor(config2, model) {
|
|
@@ -292361,11 +292400,7 @@ var AnthropicProvider = class extends BaseProvider {
|
|
|
292361
292400
|
}
|
|
292362
292401
|
async generateStream(options, onChunk) {
|
|
292363
292402
|
const messages = this.convertMessages(options.messages);
|
|
292364
|
-
const tools = options.tools
|
|
292365
|
-
name: t4.name,
|
|
292366
|
-
description: t4.description,
|
|
292367
|
-
input_schema: t4.inputSchema
|
|
292368
|
-
}));
|
|
292403
|
+
const tools = options.tools && toAnthropicTools(options.tools);
|
|
292369
292404
|
let fullContent = "";
|
|
292370
292405
|
let inputTokens = 0;
|
|
292371
292406
|
let outputTokens = 0;
|
|
@@ -292493,14 +292528,7 @@ var AnthropicProvider = class extends BaseProvider {
|
|
|
292493
292528
|
const content = [];
|
|
292494
292529
|
const text = typeof m3.content === "string" ? m3.content : "";
|
|
292495
292530
|
if (text) content.push({ type: "text", text });
|
|
292496
|
-
|
|
292497
|
-
content.push({
|
|
292498
|
-
type: "tool_use",
|
|
292499
|
-
id: tc.id,
|
|
292500
|
-
name: tc.name,
|
|
292501
|
-
input: tc.input
|
|
292502
|
-
});
|
|
292503
|
-
}
|
|
292531
|
+
content.push(...toAnthropicToolUse(m3.toolCalls ?? []));
|
|
292504
292532
|
if (content.length > 0) {
|
|
292505
292533
|
result.push({ role: "assistant", content });
|
|
292506
292534
|
}
|
|
@@ -300413,6 +300441,26 @@ function isParamShapeError(err) {
|
|
|
300413
300441
|
const msg = String(e3?.message ?? e3?.error?.message ?? "").toLowerCase();
|
|
300414
300442
|
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"));
|
|
300415
300443
|
}
|
|
300444
|
+
function toOpenAITools(tools) {
|
|
300445
|
+
return tools.map((t4) => ({
|
|
300446
|
+
type: "function",
|
|
300447
|
+
function: {
|
|
300448
|
+
name: t4.name,
|
|
300449
|
+
description: t4.description,
|
|
300450
|
+
parameters: t4.inputSchema
|
|
300451
|
+
}
|
|
300452
|
+
}));
|
|
300453
|
+
}
|
|
300454
|
+
function toOpenAIToolCalls(toolCalls) {
|
|
300455
|
+
return toolCalls.map((tc) => ({
|
|
300456
|
+
id: tc.id,
|
|
300457
|
+
type: "function",
|
|
300458
|
+
function: {
|
|
300459
|
+
name: tc.name,
|
|
300460
|
+
arguments: JSON.stringify(tc.input)
|
|
300461
|
+
}
|
|
300462
|
+
}));
|
|
300463
|
+
}
|
|
300416
300464
|
var OpenAIProvider = class extends BaseProvider {
|
|
300417
300465
|
client;
|
|
300418
300466
|
/** Once we learn (from the model id or an API error) that this deployment
|
|
@@ -300432,14 +300480,7 @@ var OpenAIProvider = class extends BaseProvider {
|
|
|
300432
300480
|
}
|
|
300433
300481
|
async generateStream(options, onChunk) {
|
|
300434
300482
|
const messages = this.convertMessages(options.messages, options.systemPrompt);
|
|
300435
|
-
const tools = options.tools
|
|
300436
|
-
type: "function",
|
|
300437
|
-
function: {
|
|
300438
|
-
name: t4.name,
|
|
300439
|
-
description: t4.description,
|
|
300440
|
-
parameters: t4.inputSchema
|
|
300441
|
-
}
|
|
300442
|
-
}));
|
|
300483
|
+
const tools = options.tools && toOpenAITools(options.tools);
|
|
300443
300484
|
let fullContent = "";
|
|
300444
300485
|
let inputTokens = 0;
|
|
300445
300486
|
let outputTokens = 0;
|
|
@@ -300585,14 +300626,7 @@ var OpenAIProvider = class extends BaseProvider {
|
|
|
300585
300626
|
result.push({
|
|
300586
300627
|
role: "assistant",
|
|
300587
300628
|
content: m3.content || "",
|
|
300588
|
-
tool_calls: m3.toolCalls
|
|
300589
|
-
id: toolCall.id,
|
|
300590
|
-
type: "function",
|
|
300591
|
-
function: {
|
|
300592
|
-
name: toolCall.name,
|
|
300593
|
-
arguments: JSON.stringify(toolCall.input)
|
|
300594
|
-
}
|
|
300595
|
-
}))
|
|
300629
|
+
tool_calls: toOpenAIToolCalls(m3.toolCalls)
|
|
300596
300630
|
});
|
|
300597
300631
|
} else {
|
|
300598
300632
|
result.push({ role: m3.role, content: m3.content });
|
|
@@ -318863,6 +318897,26 @@ function toGeminiParameters(inputSchema) {
|
|
|
318863
318897
|
}
|
|
318864
318898
|
|
|
318865
318899
|
// src/providers/gemini.ts
|
|
318900
|
+
function toGeminiTools(tools) {
|
|
318901
|
+
return tools.map((tool) => ({
|
|
318902
|
+
name: tool.name,
|
|
318903
|
+
description: tool.description,
|
|
318904
|
+
// Convert, never cast. Gemini's parameters field is an OpenAPI subset
|
|
318905
|
+
// that 400s on unknown keys, and MCP servers ship schemas carrying their
|
|
318906
|
+
// own extensions (GitHub's adds `x-mcp-header` to every header-bound
|
|
318907
|
+
// property). Casting sent those through verbatim and every request failed
|
|
318908
|
+
// before the model saw it. See providers/gemini-schema.ts.
|
|
318909
|
+
parameters: toGeminiParameters(tool.inputSchema)
|
|
318910
|
+
}));
|
|
318911
|
+
}
|
|
318912
|
+
function toGeminiFunctionCalls(toolCalls) {
|
|
318913
|
+
return toolCalls.map((tc) => ({
|
|
318914
|
+
functionCall: {
|
|
318915
|
+
name: tc.name,
|
|
318916
|
+
args: tc.input
|
|
318917
|
+
}
|
|
318918
|
+
}));
|
|
318919
|
+
}
|
|
318866
318920
|
var GeminiProvider = class extends BaseProvider {
|
|
318867
318921
|
client;
|
|
318868
318922
|
constructor(config2, model) {
|
|
@@ -318884,7 +318938,7 @@ var GeminiProvider = class extends BaseProvider {
|
|
|
318884
318938
|
{ category: HarmCategory.HARM_CATEGORY_HARASSMENT, threshold: HarmBlockThreshold.BLOCK_NONE },
|
|
318885
318939
|
{ category: HarmCategory.HARM_CATEGORY_HATE_SPEECH, threshold: HarmBlockThreshold.BLOCK_NONE }
|
|
318886
318940
|
],
|
|
318887
|
-
tools: options.tools?.length ? [{ functionDeclarations: options.tools
|
|
318941
|
+
tools: options.tools?.length ? [{ functionDeclarations: toGeminiTools(options.tools) }] : void 0,
|
|
318888
318942
|
abortSignal: options.signal
|
|
318889
318943
|
}
|
|
318890
318944
|
});
|
|
@@ -319025,14 +319079,7 @@ var GeminiProvider = class extends BaseProvider {
|
|
|
319025
319079
|
const parts = [];
|
|
319026
319080
|
const textContent = typeof m3.content === "string" ? m3.content : "";
|
|
319027
319081
|
if (textContent) parts.push({ text: textContent });
|
|
319028
|
-
|
|
319029
|
-
parts.push({
|
|
319030
|
-
functionCall: {
|
|
319031
|
-
name: tc.name,
|
|
319032
|
-
args: tc.input
|
|
319033
|
-
}
|
|
319034
|
-
});
|
|
319035
|
-
}
|
|
319082
|
+
parts.push(...toGeminiFunctionCalls(m3.toolCalls ?? []));
|
|
319036
319083
|
if (parts.length > 0) {
|
|
319037
319084
|
contents.push({ role: "model", parts });
|
|
319038
319085
|
}
|
|
@@ -319079,18 +319126,6 @@ var GeminiProvider = class extends BaseProvider {
|
|
|
319079
319126
|
}
|
|
319080
319127
|
return parts;
|
|
319081
319128
|
}
|
|
319082
|
-
convertTool(tool) {
|
|
319083
|
-
return {
|
|
319084
|
-
name: tool.name,
|
|
319085
|
-
description: tool.description,
|
|
319086
|
-
// Convert, never cast. Gemini's parameters field is an OpenAPI subset
|
|
319087
|
-
// that 400s on unknown keys, and MCP servers ship schemas carrying their
|
|
319088
|
-
// own extensions (GitHub's adds `x-mcp-header` to every header-bound
|
|
319089
|
-
// property). Casting sent those through verbatim and every request failed
|
|
319090
|
-
// before the model saw it. See providers/gemini-schema.ts.
|
|
319091
|
-
parameters: toGeminiParameters(tool.inputSchema)
|
|
319092
|
-
};
|
|
319093
|
-
}
|
|
319094
319129
|
};
|
|
319095
319130
|
try {
|
|
319096
319131
|
dns__default.default.setDefaultResultOrder("ipv4first");
|
|
@@ -319183,6 +319218,24 @@ function isToolCapable(modelName) {
|
|
|
319183
319218
|
const name = modelName.toLowerCase();
|
|
319184
319219
|
return TOOL_CAPABLE_FAMILIES.some((family) => name.includes(family));
|
|
319185
319220
|
}
|
|
319221
|
+
function toOllamaTools(tools) {
|
|
319222
|
+
return tools.map((t4) => ({
|
|
319223
|
+
type: "function",
|
|
319224
|
+
function: {
|
|
319225
|
+
name: t4.name,
|
|
319226
|
+
description: t4.description,
|
|
319227
|
+
parameters: t4.inputSchema
|
|
319228
|
+
}
|
|
319229
|
+
}));
|
|
319230
|
+
}
|
|
319231
|
+
function toOllamaToolCalls(toolCalls) {
|
|
319232
|
+
return toolCalls.map((tc) => ({
|
|
319233
|
+
function: {
|
|
319234
|
+
name: tc.name,
|
|
319235
|
+
arguments: tc.input
|
|
319236
|
+
}
|
|
319237
|
+
}));
|
|
319238
|
+
}
|
|
319186
319239
|
var OllamaProvider = class extends BaseProvider {
|
|
319187
319240
|
baseUrl;
|
|
319188
319241
|
constructor(config2, model) {
|
|
@@ -319195,14 +319248,7 @@ var OllamaProvider = class extends BaseProvider {
|
|
|
319195
319248
|
}
|
|
319196
319249
|
async generateStream(options, onChunk) {
|
|
319197
319250
|
const messages = this.convertMessages(options.messages, options.systemPrompt);
|
|
319198
|
-
const ollamaTools = options.tools
|
|
319199
|
-
type: "function",
|
|
319200
|
-
function: {
|
|
319201
|
-
name: t4.name,
|
|
319202
|
-
description: t4.description,
|
|
319203
|
-
parameters: t4.inputSchema
|
|
319204
|
-
}
|
|
319205
|
-
}));
|
|
319251
|
+
const ollamaTools = options.tools && toOllamaTools(options.tools);
|
|
319206
319252
|
const response = await fetch(`${this.baseUrl}/api/chat`, {
|
|
319207
319253
|
method: "POST",
|
|
319208
319254
|
headers: { "Content-Type": "application/json" },
|
|
@@ -319382,12 +319428,7 @@ var OllamaProvider = class extends BaseProvider {
|
|
|
319382
319428
|
result.push({
|
|
319383
319429
|
role: "assistant",
|
|
319384
319430
|
content: typeof m3.content === "string" ? m3.content : "",
|
|
319385
|
-
tool_calls: m3.toolCalls
|
|
319386
|
-
function: {
|
|
319387
|
-
name: tc.name,
|
|
319388
|
-
arguments: tc.input
|
|
319389
|
-
}
|
|
319390
|
-
}))
|
|
319431
|
+
tool_calls: toOllamaToolCalls(m3.toolCalls)
|
|
319391
319432
|
});
|
|
319392
319433
|
continue;
|
|
319393
319434
|
}
|
|
@@ -319790,6 +319831,21 @@ var DEFAULT_PROVIDER_TPM = {
|
|
|
319790
319831
|
"openai-compatible": 3e4,
|
|
319791
319832
|
ollama: Number.POSITIVE_INFINITY
|
|
319792
319833
|
};
|
|
319834
|
+
function abortReason(signal) {
|
|
319835
|
+
return signal.reason instanceof Error ? signal.reason : new Error("Aborted while waiting for rate-limit capacity");
|
|
319836
|
+
}
|
|
319837
|
+
function sleepUnlessAborted(ms2, signal) {
|
|
319838
|
+
if (!signal) return new Promise((resolve2) => setTimeout(resolve2, ms2));
|
|
319839
|
+
return new Promise((resolve2) => {
|
|
319840
|
+
const done = () => {
|
|
319841
|
+
clearTimeout(timer);
|
|
319842
|
+
signal.removeEventListener("abort", done);
|
|
319843
|
+
resolve2();
|
|
319844
|
+
};
|
|
319845
|
+
const timer = setTimeout(done, ms2);
|
|
319846
|
+
signal.addEventListener("abort", done, { once: true });
|
|
319847
|
+
});
|
|
319848
|
+
}
|
|
319793
319849
|
var TpmLimiter = class {
|
|
319794
319850
|
buckets = /* @__PURE__ */ new Map();
|
|
319795
319851
|
constructor(overrides = {}) {
|
|
@@ -319808,9 +319864,10 @@ var TpmLimiter = class {
|
|
|
319808
319864
|
* are reported back via `refund` when short, or simply settled at the next
|
|
319809
319865
|
* refill.
|
|
319810
319866
|
*/
|
|
319811
|
-
async acquire(provider, estimatedTokens) {
|
|
319867
|
+
async acquire(provider, estimatedTokens, signal) {
|
|
319812
319868
|
const bucket = this.buckets.get(provider);
|
|
319813
319869
|
if (!bucket || bucket.tokensPerMinute === Number.POSITIVE_INFINITY) return;
|
|
319870
|
+
if (signal?.aborted) throw abortReason(signal);
|
|
319814
319871
|
const want = Math.min(estimatedTokens, bucket.tokensPerMinute);
|
|
319815
319872
|
for (; ; ) {
|
|
319816
319873
|
this.refill(bucket);
|
|
@@ -319820,7 +319877,8 @@ var TpmLimiter = class {
|
|
|
319820
319877
|
}
|
|
319821
319878
|
const deficit = want - bucket.available;
|
|
319822
319879
|
const waitMs = Math.max(50, Math.ceil(deficit / bucket.tokensPerMinute * 6e4));
|
|
319823
|
-
await
|
|
319880
|
+
await sleepUnlessAborted(waitMs, signal);
|
|
319881
|
+
if (signal?.aborted) throw abortReason(signal);
|
|
319824
319882
|
}
|
|
319825
319883
|
}
|
|
319826
319884
|
/**
|
|
@@ -319866,6 +319924,9 @@ var TpmLimiter = class {
|
|
|
319866
319924
|
};
|
|
319867
319925
|
|
|
319868
319926
|
// src/core/router/local-queue.ts
|
|
319927
|
+
function abortReason2(signal) {
|
|
319928
|
+
return signal.reason instanceof Error ? signal.reason : new Error("Aborted while waiting for a local inference slot");
|
|
319929
|
+
}
|
|
319869
319930
|
var LocalRequestQueue = class {
|
|
319870
319931
|
maxConcurrent;
|
|
319871
319932
|
active = 0;
|
|
@@ -319876,9 +319937,16 @@ var LocalRequestQueue = class {
|
|
|
319876
319937
|
/**
|
|
319877
319938
|
* Acquire a queue slot. Returns a `release` function that MUST be called
|
|
319878
319939
|
* when the inference call is done (even on error). Rejects if the slot
|
|
319879
|
-
* cannot be acquired within `timeoutMs
|
|
319940
|
+
* cannot be acquired within `timeoutMs`, or as soon as `signal` fires.
|
|
319941
|
+
*
|
|
319942
|
+
* The signal matters as much as the timeout: with concurrency of 1 a caller
|
|
319943
|
+
* can sit here for half the inference timeout — 150 seconds by default —
|
|
319944
|
+
* and until it was wired up, neither cancelling the run nor spending its
|
|
319945
|
+
* budget got it out. It waited out the window for a slot it would drop the
|
|
319946
|
+
* moment it received one.
|
|
319880
319947
|
*/
|
|
319881
|
-
async acquire(timeoutMs) {
|
|
319948
|
+
async acquire(timeoutMs, signal) {
|
|
319949
|
+
if (signal?.aborted) throw abortReason2(signal);
|
|
319882
319950
|
if (this.active < this.maxConcurrent) {
|
|
319883
319951
|
this.active++;
|
|
319884
319952
|
return this.makeRelease();
|
|
@@ -319886,23 +319954,36 @@ var LocalRequestQueue = class {
|
|
|
319886
319954
|
return new Promise((resolve2, reject) => {
|
|
319887
319955
|
let settled = false;
|
|
319888
319956
|
let timer;
|
|
319957
|
+
const cleanup = () => {
|
|
319958
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
319959
|
+
signal?.removeEventListener("abort", onAbort);
|
|
319960
|
+
};
|
|
319889
319961
|
const resolver = (release) => {
|
|
319890
319962
|
if (settled) return;
|
|
319891
319963
|
settled = true;
|
|
319892
|
-
|
|
319964
|
+
cleanup();
|
|
319893
319965
|
resolve2(release);
|
|
319894
319966
|
};
|
|
319967
|
+
const abandon = () => {
|
|
319968
|
+
if (settled) return false;
|
|
319969
|
+
settled = true;
|
|
319970
|
+
cleanup();
|
|
319971
|
+
const idx = this.queue.indexOf(resolver);
|
|
319972
|
+
if (idx !== -1) this.queue.splice(idx, 1);
|
|
319973
|
+
return true;
|
|
319974
|
+
};
|
|
319975
|
+
function onAbort() {
|
|
319976
|
+
if (abandon()) reject(abortReason2(signal));
|
|
319977
|
+
}
|
|
319895
319978
|
if (timeoutMs !== void 0 && timeoutMs > 0) {
|
|
319896
319979
|
timer = setTimeout(() => {
|
|
319897
|
-
if (
|
|
319898
|
-
settled = true;
|
|
319899
|
-
const idx = this.queue.indexOf(resolver);
|
|
319900
|
-
if (idx !== -1) this.queue.splice(idx, 1);
|
|
319980
|
+
if (!abandon()) return;
|
|
319901
319981
|
reject(new Error(
|
|
319902
319982
|
`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.`
|
|
319903
319983
|
));
|
|
319904
319984
|
}, timeoutMs);
|
|
319905
319985
|
}
|
|
319986
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
319906
319987
|
this.queue.push(resolver);
|
|
319907
319988
|
});
|
|
319908
319989
|
}
|
|
@@ -320039,6 +320120,116 @@ function fileDeadModelPersistence(filePath) {
|
|
|
320039
320120
|
};
|
|
320040
320121
|
}
|
|
320041
320122
|
|
|
320123
|
+
// src/core/context/compaction.ts
|
|
320124
|
+
var CHARS_PER_TOKEN = 4;
|
|
320125
|
+
function estimateTokens(text) {
|
|
320126
|
+
if (!text) return 0;
|
|
320127
|
+
return Math.max(1, Math.ceil(text.length / CHARS_PER_TOKEN));
|
|
320128
|
+
}
|
|
320129
|
+
function contentToText(content) {
|
|
320130
|
+
if (typeof content === "string") return content;
|
|
320131
|
+
return content.map((c4) => c4.type === "text" ? c4.text : c4.type === "tool_result" ? c4.content : `[${c4.type}]`).join(" ");
|
|
320132
|
+
}
|
|
320133
|
+
function messagesTokens(messages) {
|
|
320134
|
+
return messages.reduce((n8, m3) => n8 + estimateTokens(contentToText(m3.content)), 0);
|
|
320135
|
+
}
|
|
320136
|
+
function atomize(text, maxChars) {
|
|
320137
|
+
const seps = ["\n\n", "\n", ". ", " "];
|
|
320138
|
+
let units = [text];
|
|
320139
|
+
for (const sep3 of seps) {
|
|
320140
|
+
if (units.every((u2) => u2.length <= maxChars)) break;
|
|
320141
|
+
const next = [];
|
|
320142
|
+
for (const u2 of units) {
|
|
320143
|
+
if (u2.length <= maxChars) {
|
|
320144
|
+
next.push(u2);
|
|
320145
|
+
continue;
|
|
320146
|
+
}
|
|
320147
|
+
const parts = u2.split(sep3);
|
|
320148
|
+
parts.forEach((p3, i4) => next.push(i4 < parts.length - 1 ? p3 + sep3 : p3));
|
|
320149
|
+
}
|
|
320150
|
+
units = next;
|
|
320151
|
+
}
|
|
320152
|
+
const out = [];
|
|
320153
|
+
for (const u2 of units) {
|
|
320154
|
+
if (u2.length <= maxChars) out.push(u2);
|
|
320155
|
+
else for (let i4 = 0; i4 < u2.length; i4 += maxChars) out.push(u2.slice(i4, i4 + maxChars));
|
|
320156
|
+
}
|
|
320157
|
+
return out;
|
|
320158
|
+
}
|
|
320159
|
+
function chunkText(text, opts) {
|
|
320160
|
+
const maxChars = Math.max(1, Math.floor(opts.maxTokens * 4));
|
|
320161
|
+
const overlap = Math.min(maxChars - 1, Math.max(0, Math.floor(maxChars * (opts.overlapRatio ?? 0.1))));
|
|
320162
|
+
const trimmed2 = text.trim();
|
|
320163
|
+
if (!trimmed2) return [];
|
|
320164
|
+
if (trimmed2.length <= maxChars) return [trimmed2];
|
|
320165
|
+
const units = atomize(trimmed2, maxChars);
|
|
320166
|
+
const chunks = [];
|
|
320167
|
+
let cur = "";
|
|
320168
|
+
for (const u2 of units) {
|
|
320169
|
+
if (cur && cur.length + u2.length > maxChars) {
|
|
320170
|
+
chunks.push(cur.trim());
|
|
320171
|
+
cur = overlap > 0 ? cur.slice(-overlap) : "";
|
|
320172
|
+
}
|
|
320173
|
+
cur += u2;
|
|
320174
|
+
}
|
|
320175
|
+
if (cur.trim()) chunks.push(cur.trim());
|
|
320176
|
+
return chunks;
|
|
320177
|
+
}
|
|
320178
|
+
function needsCompaction(totalTokens, contextWindow, reserve = 0.2) {
|
|
320179
|
+
if (!contextWindow || contextWindow <= 0) return false;
|
|
320180
|
+
return totalTokens > contextWindow * (1 - reserve);
|
|
320181
|
+
}
|
|
320182
|
+
async function mapReduceCompact(text, opts) {
|
|
320183
|
+
const capChars = opts.capTokens * 4;
|
|
320184
|
+
let working = text;
|
|
320185
|
+
let truncated = false;
|
|
320186
|
+
if (working.length > capChars) {
|
|
320187
|
+
working = working.slice(0, capChars);
|
|
320188
|
+
truncated = true;
|
|
320189
|
+
}
|
|
320190
|
+
const instruction = opts.instruction ?? "Summarize this content, preserving key facts, names, numbers, and decisions.";
|
|
320191
|
+
let chunks = chunkText(working, { maxTokens: opts.chunkTokens });
|
|
320192
|
+
if (chunks.length <= 1) return { text: working, chunks: chunks.length, calls: 0, truncated };
|
|
320193
|
+
let calls = 0;
|
|
320194
|
+
let parts = await Promise.all(
|
|
320195
|
+
chunks.map((c4, i4) => {
|
|
320196
|
+
calls++;
|
|
320197
|
+
return opts.summarize(c4, `${instruction} (part ${i4 + 1} of ${chunks.length})`);
|
|
320198
|
+
})
|
|
320199
|
+
);
|
|
320200
|
+
let combined = parts.join("\n\n");
|
|
320201
|
+
const maxPasses = opts.maxReducePasses ?? 3;
|
|
320202
|
+
for (let pass = 0; pass < maxPasses && estimateTokens(combined) > opts.targetTokens; pass++) {
|
|
320203
|
+
const groups = chunkText(combined, { maxTokens: opts.chunkTokens });
|
|
320204
|
+
if (groups.length <= 1) break;
|
|
320205
|
+
parts = await Promise.all(
|
|
320206
|
+
groups.map((g2) => {
|
|
320207
|
+
calls++;
|
|
320208
|
+
return opts.summarize(g2, "Merge these partial summaries into one cohesive summary; keep all distinct facts.");
|
|
320209
|
+
})
|
|
320210
|
+
);
|
|
320211
|
+
combined = parts.join("\n\n");
|
|
320212
|
+
}
|
|
320213
|
+
const note = truncated ? "\n\n[Note: input exceeded the extended-context cap and was truncated.]" : "";
|
|
320214
|
+
return { text: combined + note, chunks: chunks.length, calls, truncated };
|
|
320215
|
+
}
|
|
320216
|
+
async function rollingSummary(messages, opts) {
|
|
320217
|
+
if (messages.length <= opts.keepRecent) return messages;
|
|
320218
|
+
const older = messages.slice(0, messages.length - opts.keepRecent);
|
|
320219
|
+
const recent = messages.slice(messages.length - opts.keepRecent);
|
|
320220
|
+
const transcript = older.map((m3) => `${m3.role}: ${contentToText(m3.content)}`).join("\n");
|
|
320221
|
+
const summary = await opts.summarize(
|
|
320222
|
+
transcript,
|
|
320223
|
+
"Summarize this earlier conversation. Preserve facts, decisions, names, numbers, and any open threads or todos."
|
|
320224
|
+
);
|
|
320225
|
+
const summaryMessage = {
|
|
320226
|
+
role: "system",
|
|
320227
|
+
content: `Summary of earlier conversation (folded to fit the context window):
|
|
320228
|
+
${summary}`
|
|
320229
|
+
};
|
|
320230
|
+
return [summaryMessage, ...recent];
|
|
320231
|
+
}
|
|
320232
|
+
|
|
320042
320233
|
// src/utils/retry.ts
|
|
320043
320234
|
var CascadeCancelledError = class extends Error {
|
|
320044
320235
|
constructor(reason) {
|
|
@@ -320073,6 +320264,158 @@ async function withTimeout(promise, timeoutMs, errorMessage = "Operation timed o
|
|
|
320073
320264
|
if (timer !== void 0) clearTimeout(timer);
|
|
320074
320265
|
}
|
|
320075
320266
|
}
|
|
320267
|
+
async function withTimeoutAbort(run, timeoutMs, errorMessage = "Operation timed out", outer) {
|
|
320268
|
+
const outers = (Array.isArray(outer) ? outer : [outer]).filter((sig) => sig !== void 0);
|
|
320269
|
+
const controller = new AbortController();
|
|
320270
|
+
let timer;
|
|
320271
|
+
let rejectRace;
|
|
320272
|
+
const settle = (reason) => {
|
|
320273
|
+
rejectRace?.(reason);
|
|
320274
|
+
controller.abort(reason);
|
|
320275
|
+
};
|
|
320276
|
+
const abortOuter = () => {
|
|
320277
|
+
const fired = outers.find((sig) => sig.aborted);
|
|
320278
|
+
settle(fired?.reason instanceof Error ? fired.reason : new CascadeCancelledError("Run cancelled"));
|
|
320279
|
+
};
|
|
320280
|
+
const failPromise = new Promise((_4, reject) => {
|
|
320281
|
+
rejectRace = reject;
|
|
320282
|
+
timer = setTimeout(() => settle(new Error(errorMessage)), timeoutMs);
|
|
320283
|
+
});
|
|
320284
|
+
try {
|
|
320285
|
+
if (outers.some((sig) => sig.aborted)) {
|
|
320286
|
+
abortOuter();
|
|
320287
|
+
return await failPromise;
|
|
320288
|
+
}
|
|
320289
|
+
for (const sig of outers) sig.addEventListener("abort", abortOuter, { once: true });
|
|
320290
|
+
return await Promise.race([run(controller.signal), failPromise]);
|
|
320291
|
+
} finally {
|
|
320292
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
320293
|
+
for (const sig of outers) sig.removeEventListener("abort", abortOuter);
|
|
320294
|
+
}
|
|
320295
|
+
}
|
|
320296
|
+
function anySignal(sources) {
|
|
320297
|
+
const live = sources.filter((s3) => s3 !== void 0);
|
|
320298
|
+
if (live.length === 0) return { release: () => {
|
|
320299
|
+
} };
|
|
320300
|
+
if (live.length === 1) return { signal: live[0], release: () => {
|
|
320301
|
+
} };
|
|
320302
|
+
const controller = new AbortController();
|
|
320303
|
+
function detach() {
|
|
320304
|
+
for (const s3 of live) s3.removeEventListener("abort", onAbort);
|
|
320305
|
+
}
|
|
320306
|
+
function onAbort() {
|
|
320307
|
+
const fired = live.find((s3) => s3.aborted);
|
|
320308
|
+
controller.abort(fired?.reason instanceof Error ? fired.reason : new CascadeCancelledError("Run cancelled"));
|
|
320309
|
+
detach();
|
|
320310
|
+
}
|
|
320311
|
+
if (live.some((s3) => s3.aborted)) {
|
|
320312
|
+
onAbort();
|
|
320313
|
+
return { signal: controller.signal, release: detach };
|
|
320314
|
+
}
|
|
320315
|
+
for (const s3 of live) s3.addEventListener("abort", onAbort, { once: true });
|
|
320316
|
+
return { signal: controller.signal, release: detach };
|
|
320317
|
+
}
|
|
320318
|
+
|
|
320319
|
+
// src/core/router/wire-profile.ts
|
|
320320
|
+
var textAndImageOnly = (block) => block.type === "text" || block.type === "image";
|
|
320321
|
+
var ANTHROPIC = {
|
|
320322
|
+
// convertMessages: `if (m.role === 'system') continue` — history system
|
|
320323
|
+
// messages are skipped outright; only options.systemPrompt goes as system
|
|
320324
|
+
// input. Compaction emits exactly such a message, holding a summary of the
|
|
320325
|
+
// whole conversation, so charging it refused runs over input never sent.
|
|
320326
|
+
dropsMessage: (m3) => m3.role === "system",
|
|
320327
|
+
blockHandling: (m3) => {
|
|
320328
|
+
if (m3.role === "tool") return "stringified";
|
|
320329
|
+
if (m3.role === "assistant") return "dropped";
|
|
320330
|
+
return "blocks";
|
|
320331
|
+
},
|
|
320332
|
+
sendsToolCalls: (m3) => m3.role === "assistant",
|
|
320333
|
+
sendsBlock: textAndImageOnly,
|
|
320334
|
+
sendsToolCallId: true,
|
|
320335
|
+
sizeToolCalls: toAnthropicToolUse,
|
|
320336
|
+
sendsUrlImages: true,
|
|
320337
|
+
readsTopLevelImages: false,
|
|
320338
|
+
sizeTools: toAnthropicTools
|
|
320339
|
+
};
|
|
320340
|
+
var GEMINI = {
|
|
320341
|
+
// buildContents folds a system message into the next user turn, but only
|
|
320342
|
+
// when it has text: array content collapses to '' and the message is
|
|
320343
|
+
// skipped, as is a whitespace-only string.
|
|
320344
|
+
dropsMessage: (m3) => m3.role === "system" && !(typeof m3.content === "string" ? m3.content : "").trim(),
|
|
320345
|
+
blockHandling: (m3) => {
|
|
320346
|
+
if (m3.role === "tool") return "stringified";
|
|
320347
|
+
if (m3.role === "assistant" || m3.role === "system") return "dropped";
|
|
320348
|
+
return "blocks";
|
|
320349
|
+
},
|
|
320350
|
+
sendsToolCalls: (m3) => m3.role === "assistant",
|
|
320351
|
+
sendsBlock: textAndImageOnly,
|
|
320352
|
+
sendsToolCallId: true,
|
|
320353
|
+
sizeToolCalls: toGeminiFunctionCalls,
|
|
320354
|
+
sendsUrlImages: false,
|
|
320355
|
+
readsTopLevelImages: true,
|
|
320356
|
+
sizeTools: toGeminiTools
|
|
320357
|
+
};
|
|
320358
|
+
var OPENAI = {
|
|
320359
|
+
dropsMessage: () => false,
|
|
320360
|
+
blockHandling: (m3) => {
|
|
320361
|
+
if (m3.role === "system" || m3.role === "tool") return "dropped";
|
|
320362
|
+
return "blocks";
|
|
320363
|
+
},
|
|
320364
|
+
// The tool_calls branch is inside `if (typeof m.content === 'string')`; an
|
|
320365
|
+
// assistant turn with array content falls through to the parts branch and
|
|
320366
|
+
// its tool calls are never attached.
|
|
320367
|
+
sendsToolCalls: (m3) => m3.role === "assistant" && typeof m3.content === "string",
|
|
320368
|
+
sendsBlock: textAndImageOnly,
|
|
320369
|
+
sendsToolCallId: true,
|
|
320370
|
+
sizeToolCalls: toOpenAIToolCalls,
|
|
320371
|
+
sendsUrlImages: true,
|
|
320372
|
+
readsTopLevelImages: false,
|
|
320373
|
+
sizeTools: toOpenAITools
|
|
320374
|
+
};
|
|
320375
|
+
var OLLAMA = {
|
|
320376
|
+
dropsMessage: () => false,
|
|
320377
|
+
blockHandling: (m3) => {
|
|
320378
|
+
if (m3.role === "system") return "dropped";
|
|
320379
|
+
if (m3.role === "tool") return "stringified";
|
|
320380
|
+
if (m3.role === "assistant" && m3.toolCalls?.length) return "dropped";
|
|
320381
|
+
return "blocks";
|
|
320382
|
+
},
|
|
320383
|
+
sendsToolCalls: (m3) => m3.role === "assistant",
|
|
320384
|
+
// The image split maps `b.image.data` with no check on `type`, so a URL is
|
|
320385
|
+
// submitted too (as a URL string in the `images` array).
|
|
320386
|
+
sendsBlock: textAndImageOnly,
|
|
320387
|
+
sendsToolCallId: false,
|
|
320388
|
+
sizeToolCalls: toOllamaToolCalls,
|
|
320389
|
+
sendsUrlImages: true,
|
|
320390
|
+
readsTopLevelImages: false,
|
|
320391
|
+
sizeTools: toOllamaTools
|
|
320392
|
+
};
|
|
320393
|
+
var PROFILES = {
|
|
320394
|
+
anthropic: ANTHROPIC,
|
|
320395
|
+
gemini: GEMINI,
|
|
320396
|
+
openai: OPENAI,
|
|
320397
|
+
// AzureOpenAIProvider and OpenAICompatibleProvider both extend OpenAIProvider
|
|
320398
|
+
// and neither overrides convertMessages.
|
|
320399
|
+
azure: OPENAI,
|
|
320400
|
+
"openai-compatible": OPENAI,
|
|
320401
|
+
ollama: OLLAMA
|
|
320402
|
+
};
|
|
320403
|
+
function wireProfile(provider) {
|
|
320404
|
+
return PROFILES[provider] ?? OPENAI;
|
|
320405
|
+
}
|
|
320406
|
+
function geminiImageCopies(messages) {
|
|
320407
|
+
for (const m3 of messages) {
|
|
320408
|
+
if (m3.role === "user") {
|
|
320409
|
+
const laterUserTurn = messages.slice(messages.indexOf(m3) + 1).some((x5) => x5.role === "user");
|
|
320410
|
+
return laterUserTurn ? 2 : 1;
|
|
320411
|
+
}
|
|
320412
|
+
const text = typeof m3.content === "string" ? m3.content : "";
|
|
320413
|
+
if (m3.role === "system" && text.trim()) return 1;
|
|
320414
|
+
if (m3.role === "tool") return 1;
|
|
320415
|
+
if (m3.role === "assistant" && (text || m3.toolCalls?.length)) return 1;
|
|
320416
|
+
}
|
|
320417
|
+
return 1;
|
|
320418
|
+
}
|
|
320076
320419
|
|
|
320077
320420
|
// src/core/router/model-profiler.ts
|
|
320078
320421
|
var SKIP_PATTERN = /embed|dall-e|whisper|tts|vision|instruct-vision|rerank/i;
|
|
@@ -320577,6 +320920,36 @@ function azureModelForTier(tier, ranked) {
|
|
|
320577
320920
|
if (tier === "T3") return ranked[ranked.length - 1];
|
|
320578
320921
|
return ranked[Math.min(1, ranked.length - 1)];
|
|
320579
320922
|
}
|
|
320923
|
+
var IMAGE_TOKENS_MAX = 2e3;
|
|
320924
|
+
var IMAGE_TOKENS_EACH = IMAGE_TOKENS_MAX;
|
|
320925
|
+
function safeJson(value) {
|
|
320926
|
+
try {
|
|
320927
|
+
return JSON.stringify(value) ?? "";
|
|
320928
|
+
} catch {
|
|
320929
|
+
return "";
|
|
320930
|
+
}
|
|
320931
|
+
}
|
|
320932
|
+
function newRunAbort() {
|
|
320933
|
+
const controller = new AbortController();
|
|
320934
|
+
EventEmitter.setMaxListeners(64, controller.signal);
|
|
320935
|
+
return controller;
|
|
320936
|
+
}
|
|
320937
|
+
var TOKENS_PER_MESSAGE_FRAMING = 4;
|
|
320938
|
+
function guardTokens(text) {
|
|
320939
|
+
if (!text) return 0;
|
|
320940
|
+
let asciiChars = 0;
|
|
320941
|
+
let denseBytes = 0;
|
|
320942
|
+
for (const ch of text) {
|
|
320943
|
+
const cp = ch.codePointAt(0);
|
|
320944
|
+
if (cp <= 127) {
|
|
320945
|
+
asciiChars++;
|
|
320946
|
+
continue;
|
|
320947
|
+
}
|
|
320948
|
+
denseBytes += cp <= 2047 ? 2 : cp <= 65535 ? 3 : 4;
|
|
320949
|
+
}
|
|
320950
|
+
if (denseBytes === 0) return estimateTokens(text);
|
|
320951
|
+
return Math.ceil(asciiChars / CHARS_PER_TOKEN) + denseBytes;
|
|
320952
|
+
}
|
|
320580
320953
|
var DISCOVERY_TTL_MS = 15 * 60 * 1e3;
|
|
320581
320954
|
var DISCOVERY_TIMEOUT_MS = 4e3;
|
|
320582
320955
|
var cloudDiscoveryCache = /* @__PURE__ */ new Map();
|
|
@@ -320629,6 +321002,33 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
320629
321002
|
// start of every `cascade run`, independent of the session-wide budget.
|
|
320630
321003
|
runTokens = 0;
|
|
320631
321004
|
runCostUsd = 0;
|
|
321005
|
+
/**
|
|
321006
|
+
* Budget held for calls that are IN FLIGHT — admitted by the preflight check
|
|
321007
|
+
* but not yet counted by recordStats. Without these, concurrent calls all
|
|
321008
|
+
* measure themselves against the same unspent allowance and are all admitted.
|
|
321009
|
+
* Released when each call settles; reset by beginRun() alongside the totals.
|
|
321010
|
+
*/
|
|
321011
|
+
reservedTokens = 0;
|
|
321012
|
+
reservedCostUsd = 0;
|
|
321013
|
+
/**
|
|
321014
|
+
* Fires when this run's budget is spent, cancelling work already in flight.
|
|
321015
|
+
*
|
|
321016
|
+
* The kill switch was previously only a flag, which stops calls that have
|
|
321017
|
+
* not yet submitted. A parallel wave's earlier members are already at the
|
|
321018
|
+
* provider by then, and they carried on generating for a run whose output
|
|
321019
|
+
* would be discarded. Every provider call chains to this, so tripping the
|
|
321020
|
+
* ceiling reaches the requests that are running, not just the ones queued.
|
|
321021
|
+
*/
|
|
321022
|
+
runAbort = newRunAbort();
|
|
321023
|
+
/**
|
|
321024
|
+
* Bumped by every beginRun(). A release handle remembers the generation it
|
|
321025
|
+
* was issued in and does nothing if that has moved on: beginRun() zeroes the
|
|
321026
|
+
* counters, so a call still in flight from the previous run would otherwise
|
|
321027
|
+
* subtract its estimate from the NEW run's totals and drive them negative —
|
|
321028
|
+
* leaving later preflights believing there is more allowance than the cap
|
|
321029
|
+
* actually holds, which is worse than no gate at all.
|
|
321030
|
+
*/
|
|
321031
|
+
runGeneration = 0;
|
|
320632
321032
|
runBudgetExceeded = false;
|
|
320633
321033
|
runBudgetExceededReason;
|
|
320634
321034
|
/**
|
|
@@ -320798,7 +321198,7 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
320798
321198
|
try {
|
|
320799
321199
|
const cfg = this.config.providers.find((p3) => p3.type === model.provider) ?? { type: model.provider };
|
|
320800
321200
|
const provider = this.createProvider(cfg, model);
|
|
320801
|
-
const result = await
|
|
321201
|
+
const result = await withTimeoutAbort((signal) => provider.generate({
|
|
320802
321202
|
messages: [{ role: "user", content: "Call the echo tool with text set to 'ping'. Use the tool; do not answer in prose." }],
|
|
320803
321203
|
tools: [{
|
|
320804
321204
|
name: "echo",
|
|
@@ -320806,7 +321206,8 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
320806
321206
|
inputSchema: { type: "object", properties: { text: { type: "string", description: "Text to echo" } }, required: ["text"] }
|
|
320807
321207
|
}],
|
|
320808
321208
|
maxTokens: 80,
|
|
320809
|
-
temperature: 0
|
|
321209
|
+
temperature: 0,
|
|
321210
|
+
signal
|
|
320810
321211
|
}), 3e4, "tool-support probe timed out");
|
|
320811
321212
|
return (result.toolCalls?.length ?? 0) > 0;
|
|
320812
321213
|
} catch {
|
|
@@ -320927,9 +321328,12 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
320927
321328
|
this.runBudgetExceededReason ?? "Per-task budget exceeded."
|
|
320928
321329
|
);
|
|
320929
321330
|
}
|
|
321331
|
+
const runGeneration = this.runGeneration;
|
|
321332
|
+
const runAbort = this.runAbort;
|
|
321333
|
+
const runSignal = this.runSignal;
|
|
320930
321334
|
options = applyTierLimits(options, tier, this.config?.tierLimits);
|
|
320931
|
-
if (
|
|
320932
|
-
options = { ...options, signal:
|
|
321335
|
+
if (runSignal && !options.signal) {
|
|
321336
|
+
options = { ...options, signal: runSignal };
|
|
320933
321337
|
}
|
|
320934
321338
|
if (options.model && !requireVision) {
|
|
320935
321339
|
this.ensureProvider(options.model, this.config.providers);
|
|
@@ -320951,58 +321355,84 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
320951
321355
|
if (!model) throw new Error(`No model available for tier ${tier}`);
|
|
320952
321356
|
const provider = this.getProvider(model);
|
|
320953
321357
|
if (!provider) throw new Error(`No provider for model ${model.id}`);
|
|
321358
|
+
let releaseReservation = this.enforcePreflightBudget(model, options);
|
|
320954
321359
|
const requestedTokens = options.maxTokens ?? model.maxOutputTokens ?? 1024;
|
|
320955
321360
|
const estimatedTokens = Math.min(requestedTokens, model.maxOutputTokens ?? requestedTokens) + 512;
|
|
320956
321361
|
if (this.tpmLimiter) {
|
|
320957
|
-
|
|
321362
|
+
const wait = anySignal([options.signal, runAbort.signal]);
|
|
321363
|
+
try {
|
|
321364
|
+
await this.tpmLimiter.acquire(model.provider, estimatedTokens, wait.signal);
|
|
321365
|
+
} catch (err) {
|
|
321366
|
+
releaseReservation?.();
|
|
321367
|
+
throw this.asAbortFailure(err, options.signal, runSignal);
|
|
321368
|
+
} finally {
|
|
321369
|
+
wait.release();
|
|
321370
|
+
}
|
|
320958
321371
|
}
|
|
320959
321372
|
const useStream = Boolean(onChunk) && model.supportsStreaming && typeof provider.generateStream === "function";
|
|
320960
321373
|
let releaseLocalSlot;
|
|
320961
321374
|
if (model.isLocal) {
|
|
320962
321375
|
const inferenceTimeoutMs = this.config.localInferenceTimeoutMs ?? 3e5;
|
|
320963
321376
|
const queueWaitMs = Math.round(inferenceTimeoutMs / 2);
|
|
320964
|
-
|
|
321377
|
+
const wait = anySignal([options.signal, runAbort.signal]);
|
|
321378
|
+
try {
|
|
321379
|
+
releaseLocalSlot = await this.localQueue.acquire(queueWaitMs, wait.signal);
|
|
321380
|
+
} catch (err) {
|
|
321381
|
+
releaseReservation?.();
|
|
321382
|
+
throw this.asAbortFailure(err, options.signal, runSignal);
|
|
321383
|
+
} finally {
|
|
321384
|
+
wait.release();
|
|
321385
|
+
}
|
|
320965
321386
|
}
|
|
320966
321387
|
try {
|
|
321388
|
+
if (runGeneration === this.runGeneration && this.runBudgetExceeded) {
|
|
321389
|
+
this.tpmLimiter?.refund(model.provider, estimatedTokens);
|
|
321390
|
+
throw new _CascadeRouter.BudgetExceededError(
|
|
321391
|
+
this.runBudgetExceededReason ?? "Per-task budget exceeded."
|
|
321392
|
+
);
|
|
321393
|
+
}
|
|
320967
321394
|
let result;
|
|
320968
321395
|
if (model.isLocal) {
|
|
320969
321396
|
const inferenceTimeoutMs = this.config.localInferenceTimeoutMs ?? 3e5;
|
|
320970
|
-
|
|
320971
|
-
|
|
320972
|
-
|
|
320973
|
-
|
|
320974
|
-
|
|
320975
|
-
inferencePromise,
|
|
321397
|
+
result = await withTimeoutAbort(
|
|
321398
|
+
(signal) => useStream && onChunk ? provider.generateStream({ ...options, signal }, (chunk) => {
|
|
321399
|
+
const text = typeof chunk?.text === "string" ? chunk.text : "";
|
|
321400
|
+
if (text) onChunk({ ...chunk, text });
|
|
321401
|
+
}) : provider.generate({ ...options, signal }),
|
|
320976
321402
|
inferenceTimeoutMs,
|
|
320977
|
-
`Local model ${model.id} inference timed out after ${inferenceTimeoutMs}ms
|
|
321403
|
+
`Local model ${model.id} inference timed out after ${inferenceTimeoutMs}ms`,
|
|
321404
|
+
[options.signal, runAbort.signal]
|
|
320978
321405
|
);
|
|
320979
321406
|
} else if (useStream && onChunk) {
|
|
320980
321407
|
const cloudTimeoutMs = this.config.cloudInferenceTimeoutMs ?? 12e4;
|
|
320981
321408
|
try {
|
|
320982
|
-
result = await
|
|
320983
|
-
provider.generateStream(options, (chunk) => {
|
|
321409
|
+
result = await withTimeoutAbort(
|
|
321410
|
+
(signal) => provider.generateStream({ ...options, signal }, (chunk) => {
|
|
320984
321411
|
const text = typeof chunk?.text === "string" ? chunk.text : "";
|
|
320985
321412
|
if (text) onChunk({ ...chunk, text });
|
|
320986
321413
|
}),
|
|
320987
321414
|
cloudTimeoutMs,
|
|
320988
|
-
`Model ${model.id} stream timed out after ${cloudTimeoutMs}ms
|
|
321415
|
+
`Model ${model.id} stream timed out after ${cloudTimeoutMs}ms`,
|
|
321416
|
+
[options.signal, runAbort.signal]
|
|
320989
321417
|
);
|
|
320990
321418
|
} catch (streamErr) {
|
|
320991
|
-
if (streamErr instanceof Error && streamErr.name === "AbortError" ||
|
|
321419
|
+
if (streamErr instanceof Error && streamErr.name === "AbortError" || runSignal?.aborted || options.signal?.aborted) {
|
|
320992
321420
|
throw streamErr;
|
|
320993
321421
|
}
|
|
320994
|
-
result = await
|
|
320995
|
-
provider.generate(options),
|
|
321422
|
+
result = await withTimeoutAbort(
|
|
321423
|
+
(signal) => provider.generate({ ...options, signal }),
|
|
320996
321424
|
cloudTimeoutMs,
|
|
320997
|
-
`Model ${model.id} inference timed out after ${cloudTimeoutMs}ms
|
|
321425
|
+
`Model ${model.id} inference timed out after ${cloudTimeoutMs}ms`,
|
|
321426
|
+
[options.signal, runAbort.signal]
|
|
320998
321427
|
);
|
|
320999
321428
|
}
|
|
321000
321429
|
} else {
|
|
321001
321430
|
const cloudTimeoutMs = this.config.cloudInferenceTimeoutMs ?? 12e4;
|
|
321002
|
-
result = await
|
|
321003
|
-
provider.generate(options),
|
|
321431
|
+
result = await withTimeoutAbort(
|
|
321432
|
+
(signal) => provider.generate({ ...options, signal }),
|
|
321004
321433
|
cloudTimeoutMs,
|
|
321005
|
-
`Model ${model.id} inference timed out after ${cloudTimeoutMs}ms
|
|
321434
|
+
`Model ${model.id} inference timed out after ${cloudTimeoutMs}ms`,
|
|
321435
|
+
[options.signal, runAbort.signal]
|
|
321006
321436
|
);
|
|
321007
321437
|
}
|
|
321008
321438
|
const corrected = buildTokenUsage(
|
|
@@ -321021,11 +321451,12 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
321021
321451
|
if (!result || typeof result.content !== "string" || !result.usage) {
|
|
321022
321452
|
throw new Error(`Provider ${model.provider}:${model.id} returned an invalid generation result.`);
|
|
321023
321453
|
}
|
|
321024
|
-
this.recordStats(tier, model, result.usage, options.featureTag);
|
|
321454
|
+
this.recordStats(tier, model, result.usage, options.featureTag, runGeneration);
|
|
321025
321455
|
this.failover.recordSuccess(model.provider);
|
|
321026
321456
|
return result;
|
|
321027
321457
|
} catch (err) {
|
|
321028
|
-
if (err instanceof
|
|
321458
|
+
if (err instanceof _CascadeRouter.BudgetExceededError) throw err;
|
|
321459
|
+
if (err instanceof Error && err.name === "AbortError" || runSignal?.aborted || options.signal?.aborted) {
|
|
321029
321460
|
throw new CascadeCancelledError("Run cancelled");
|
|
321030
321461
|
}
|
|
321031
321462
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
@@ -321043,6 +321474,8 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
321043
321474
|
});
|
|
321044
321475
|
releaseLocalSlot?.();
|
|
321045
321476
|
releaseLocalSlot = void 0;
|
|
321477
|
+
releaseReservation?.();
|
|
321478
|
+
releaseReservation = void 0;
|
|
321046
321479
|
const retryOpts = options.model && options.model.id === model.id ? { ...options, model: void 0 } : options;
|
|
321047
321480
|
return this.generate(tier, retryOpts, onChunk, requireVision);
|
|
321048
321481
|
}
|
|
@@ -321064,6 +321497,8 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
321064
321497
|
});
|
|
321065
321498
|
releaseLocalSlot?.();
|
|
321066
321499
|
releaseLocalSlot = void 0;
|
|
321500
|
+
releaseReservation?.();
|
|
321501
|
+
releaseReservation = void 0;
|
|
321067
321502
|
const base = options.model && options.model.id === model.id ? { ...options, model: void 0 } : options;
|
|
321068
321503
|
const retryOpts = { ...base, _notFoundDepth: depth };
|
|
321069
321504
|
return this.generate(tier, retryOpts, onChunk, requireVision);
|
|
@@ -321072,6 +321507,8 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
321072
321507
|
throw err;
|
|
321073
321508
|
} finally {
|
|
321074
321509
|
releaseLocalSlot?.();
|
|
321510
|
+
releaseReservation?.();
|
|
321511
|
+
releaseReservation = void 0;
|
|
321075
321512
|
}
|
|
321076
321513
|
}
|
|
321077
321514
|
getModelForTier(tier) {
|
|
@@ -321442,7 +321879,14 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
321442
321879
|
}
|
|
321443
321880
|
return void 0;
|
|
321444
321881
|
}
|
|
321445
|
-
|
|
321882
|
+
/**
|
|
321883
|
+
* `runGeneration` is the generation the call was ADMITTED in — see
|
|
321884
|
+
* `generate()`. Session-wide accounting always applies (the spend is real
|
|
321885
|
+
* whenever it lands), but the per-RUN ceiling is only meaningful for the run
|
|
321886
|
+
* the call belongs to, so a straggler from a finished run is left out of it.
|
|
321887
|
+
* Omitted by the direct callers in tests, which have no run to straddle.
|
|
321888
|
+
*/
|
|
321889
|
+
recordStats(tier, model, usage, featureTag, runGeneration) {
|
|
321446
321890
|
this.stats.totalTokens += usage.totalTokens;
|
|
321447
321891
|
this.stats.totalCostUsd += usage.estimatedCostUsd;
|
|
321448
321892
|
this.sessionCostUsd += usage.estimatedCostUsd;
|
|
@@ -321467,10 +321911,13 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
321467
321911
|
});
|
|
321468
321912
|
}
|
|
321469
321913
|
}
|
|
321470
|
-
|
|
321471
|
-
|
|
321914
|
+
const currentRun = runGeneration === void 0 || runGeneration === this.runGeneration;
|
|
321915
|
+
if (currentRun) {
|
|
321916
|
+
this.runTokens += usage.totalTokens;
|
|
321917
|
+
this.runCostUsd += usage.estimatedCostUsd;
|
|
321918
|
+
}
|
|
321472
321919
|
this.updateBudgetState();
|
|
321473
|
-
this.enforceRunBudget();
|
|
321920
|
+
if (currentRun) this.enforceRunBudget();
|
|
321474
321921
|
}
|
|
321475
321922
|
/**
|
|
321476
321923
|
* Resets per-run accounting at the start of each `cascade run`. Session
|
|
@@ -321480,9 +321927,158 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
321480
321927
|
beginRun() {
|
|
321481
321928
|
this.runTokens = 0;
|
|
321482
321929
|
this.runCostUsd = 0;
|
|
321930
|
+
this.reservedTokens = 0;
|
|
321931
|
+
this.reservedCostUsd = 0;
|
|
321932
|
+
this.runAbort = newRunAbort();
|
|
321933
|
+
this.runGeneration++;
|
|
321483
321934
|
this.runBudgetExceeded = false;
|
|
321484
321935
|
this.runBudgetExceededReason = void 0;
|
|
321485
321936
|
}
|
|
321937
|
+
/**
|
|
321938
|
+
* Refuse a call whose input alone cannot fit what is left of the run budget.
|
|
321939
|
+
*
|
|
321940
|
+
* `enforceRunBudget()` below runs AFTER a call returns, which makes the caps
|
|
321941
|
+
* a stop rather than a pre-authorisation: the tokens are already bought by
|
|
321942
|
+
* the time it looks. That was tolerable while a prompt could not exceed
|
|
321943
|
+
* 20,000 characters, and stopped being so when that cap was removed in
|
|
321944
|
+
* 0.72.0 — a multi-megabyte prompt is billed in full on the very first call
|
|
321945
|
+
* (the complexity classifier sends the whole thing), and no ceiling
|
|
321946
|
+
* configured downstream can give that money back.
|
|
321947
|
+
*
|
|
321948
|
+
* Deliberately narrow, because a false refusal is worse than a late stop:
|
|
321949
|
+
*
|
|
321950
|
+
* • INPUT ONLY. What a call returns is not knowable in advance, and
|
|
321951
|
+
* charging a worst-case `maxTokens` of output against the budget would
|
|
321952
|
+
* decline runs that would have finished comfortably. Input is the part
|
|
321953
|
+
* that is already determined, and it is the part the removed cap used to
|
|
321954
|
+
* bound.
|
|
321955
|
+
* • Compared against what REMAINS (`cap - spent`), not the whole cap, so a
|
|
321956
|
+
* call is judged on the budget it can actually draw from.
|
|
321957
|
+
* • Skipped when the model has no usable price. An estimate cannot be made,
|
|
321958
|
+
* and refusing on ignorance would break every self-hosted and local model
|
|
321959
|
+
* the moment a cost cap is set. The post-hoc stop still applies there.
|
|
321960
|
+
* • Skipped entirely when no cap is configured — there is nothing to
|
|
321961
|
+
* pre-authorise against.
|
|
321962
|
+
*
|
|
321963
|
+
* The message names the cap AND the estimate, because "too expensive" with
|
|
321964
|
+
* no numbers gives the user nothing to change.
|
|
321965
|
+
*/
|
|
321966
|
+
enforcePreflightBudget(model, options) {
|
|
321967
|
+
const budget = this.config?.budget;
|
|
321968
|
+
const maxCost = budget?.maxCostPerRunUsd;
|
|
321969
|
+
const maxTokens = budget?.maxTokensPerRun;
|
|
321970
|
+
if (maxCost == null && maxTokens == null) return void 0;
|
|
321971
|
+
const inputTokens = this.estimateInputTokens(options, model);
|
|
321972
|
+
if (maxTokens != null) {
|
|
321973
|
+
const remaining = maxTokens - this.runTokens - this.reservedTokens;
|
|
321974
|
+
if (inputTokens > remaining) {
|
|
321975
|
+
this.failPreflight(
|
|
321976
|
+
`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.`
|
|
321977
|
+
);
|
|
321978
|
+
}
|
|
321979
|
+
}
|
|
321980
|
+
let estimatedUsd = 0;
|
|
321981
|
+
if (maxCost != null) {
|
|
321982
|
+
const { input: inputPer1k, unknown: unknown2 } = resolveModelPricing(model, { inputTokens });
|
|
321983
|
+
if (!unknown2 && inputPer1k > 0) {
|
|
321984
|
+
estimatedUsd = inputTokens / 1e3 * inputPer1k;
|
|
321985
|
+
const remaining = maxCost - this.runCostUsd - this.reservedCostUsd;
|
|
321986
|
+
if (estimatedUsd > remaining) {
|
|
321987
|
+
this.failPreflight(
|
|
321988
|
+
`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.`
|
|
321989
|
+
);
|
|
321990
|
+
}
|
|
321991
|
+
}
|
|
321992
|
+
}
|
|
321993
|
+
this.reservedTokens += inputTokens;
|
|
321994
|
+
this.reservedCostUsd += estimatedUsd;
|
|
321995
|
+
const generation = this.runGeneration;
|
|
321996
|
+
let released = false;
|
|
321997
|
+
return () => {
|
|
321998
|
+
if (released || generation !== this.runGeneration) return;
|
|
321999
|
+
released = true;
|
|
322000
|
+
this.reservedTokens -= inputTokens;
|
|
322001
|
+
this.reservedCostUsd -= estimatedUsd;
|
|
322002
|
+
};
|
|
322003
|
+
}
|
|
322004
|
+
/**
|
|
322005
|
+
* What this call will send, in tokens.
|
|
322006
|
+
*
|
|
322007
|
+
* Text is the bulk of it, but not all of it: providers bill the serialized
|
|
322008
|
+
* TOOL DEFINITIONS on every native-tool call (Anthropic sends each name,
|
|
322009
|
+
* description and input schema), and an image is billed by its own size
|
|
322010
|
+
* rather than as the `[image]` placeholder `contentToText` reduces it to. A
|
|
322011
|
+
* tool-heavy or vision request could therefore pass either cap while its real
|
|
322012
|
+
* input already exceeded the allowance.
|
|
322013
|
+
*/
|
|
322014
|
+
estimateInputTokens(options, model) {
|
|
322015
|
+
let tokens = guardTokens(options.systemPrompt ?? "");
|
|
322016
|
+
let images = 0;
|
|
322017
|
+
const wire = wireProfile(model.provider);
|
|
322018
|
+
const countsImage = (img) => wire.sendsUrlImages || img?.type === "base64";
|
|
322019
|
+
for (const message of options.messages) {
|
|
322020
|
+
if (wire.dropsMessage(message)) continue;
|
|
322021
|
+
tokens += TOKENS_PER_MESSAGE_FRAMING;
|
|
322022
|
+
if (typeof message.content === "string") {
|
|
322023
|
+
tokens += guardTokens(message.content);
|
|
322024
|
+
} else {
|
|
322025
|
+
switch (wire.blockHandling(message)) {
|
|
322026
|
+
case "blocks":
|
|
322027
|
+
for (const block of message.content) {
|
|
322028
|
+
if (!wire.sendsBlock(block)) continue;
|
|
322029
|
+
if (block.type === "image") {
|
|
322030
|
+
if (countsImage(block.image)) images++;
|
|
322031
|
+
continue;
|
|
322032
|
+
}
|
|
322033
|
+
tokens += guardTokens(contentToText([block]));
|
|
322034
|
+
}
|
|
322035
|
+
break;
|
|
322036
|
+
case "stringified":
|
|
322037
|
+
tokens += guardTokens(safeJson(message.content));
|
|
322038
|
+
break;
|
|
322039
|
+
}
|
|
322040
|
+
}
|
|
322041
|
+
if (message.toolCalls?.length && wire.sendsToolCalls(message)) {
|
|
322042
|
+
tokens += guardTokens(safeJson(wire.sizeToolCalls(message.toolCalls)));
|
|
322043
|
+
}
|
|
322044
|
+
if (message.toolCallId && message.role === "tool" && wire.sendsToolCallId) {
|
|
322045
|
+
tokens += guardTokens(message.toolCallId);
|
|
322046
|
+
}
|
|
322047
|
+
}
|
|
322048
|
+
if (options.tools?.length) {
|
|
322049
|
+
tokens += guardTokens(safeJson(wire.sizeTools(options.tools)));
|
|
322050
|
+
}
|
|
322051
|
+
if (wire.readsTopLevelImages) {
|
|
322052
|
+
const topLevel = (options.images ?? []).filter(countsImage).length;
|
|
322053
|
+
images += topLevel * geminiImageCopies(options.messages);
|
|
322054
|
+
}
|
|
322055
|
+
tokens += images * IMAGE_TOKENS_EACH;
|
|
322056
|
+
return tokens;
|
|
322057
|
+
}
|
|
322058
|
+
/**
|
|
322059
|
+
* Report a wait that ended because something asked it to stop, the same way
|
|
322060
|
+
* the provider call below reports one.
|
|
322061
|
+
*
|
|
322062
|
+
* The queue waits sit OUTSIDE the try/catch that maps aborts, so without
|
|
322063
|
+
* this a cancellation surfaced as whatever the queue happened to throw and
|
|
322064
|
+
* was retried or failed over like an ordinary error. A budget abort keeps
|
|
322065
|
+
* saying it ran out of budget — otherwise the reason the run stopped is lost
|
|
322066
|
+
* on the way up — and anything else that was aborted becomes a cancellation.
|
|
322067
|
+
*/
|
|
322068
|
+
asAbortFailure(err, callSignal, runSignal) {
|
|
322069
|
+
if (err instanceof _CascadeRouter.BudgetExceededError) return err;
|
|
322070
|
+
if (err instanceof CascadeCancelledError) return err;
|
|
322071
|
+
if (callSignal?.aborted || runSignal?.aborted) return new CascadeCancelledError("Run cancelled");
|
|
322072
|
+
return err;
|
|
322073
|
+
}
|
|
322074
|
+
/** Trips the same run-level flag a post-hoc overrun does, and reports it the same way. */
|
|
322075
|
+
failPreflight(reason) {
|
|
322076
|
+
this.runBudgetExceeded = true;
|
|
322077
|
+
this.runBudgetExceededReason = reason;
|
|
322078
|
+
this.runAbort.abort(new _CascadeRouter.BudgetExceededError(reason));
|
|
322079
|
+
this.emit("budget:exceeded", { reason, spentUsd: this.sessionCostUsd });
|
|
322080
|
+
throw new _CascadeRouter.BudgetExceededError(reason);
|
|
322081
|
+
}
|
|
321486
322082
|
/**
|
|
321487
322083
|
* Enforce the hard per-task ceiling. Once tripped, the flag makes every
|
|
321488
322084
|
* subsequent (and concurrent) generate() call in this run fail fast.
|
|
@@ -321498,6 +322094,7 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
321498
322094
|
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.`;
|
|
321499
322095
|
this.runBudgetExceeded = true;
|
|
321500
322096
|
this.runBudgetExceededReason = reason;
|
|
322097
|
+
this.runAbort.abort(new _CascadeRouter.BudgetExceededError(reason));
|
|
321501
322098
|
this.emit("budget:exceeded", { reason, spentUsd: this.sessionCostUsd });
|
|
321502
322099
|
throw new _CascadeRouter.BudgetExceededError(reason);
|
|
321503
322100
|
}
|
|
@@ -322734,6 +323331,12 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : "") + textToolSuffix,
|
|
|
322734
323331
|
maxTokens: 4096,
|
|
322735
323332
|
...subtaskModel ? { model: subtaskModel } : {},
|
|
322736
323333
|
featureTag: this.assignment?.sectionTitle,
|
|
323334
|
+
// The wave signal, so cancel-and-respawn reaches the request that is
|
|
323335
|
+
// RUNNING rather than only stopping the worker at its next checkpoint.
|
|
323336
|
+
// It is a superset of the run signal the router injects when this is
|
|
323337
|
+
// absent (T2 composes it from that signal plus its own per-wave one),
|
|
323338
|
+
// so wiring it here loses no cancellation and adds the wave's.
|
|
323339
|
+
...this.signal ? { signal: this.signal } : {},
|
|
322737
323340
|
...this.localOnlyMatch ? { forceLocal: true } : {}
|
|
322738
323341
|
};
|
|
322739
323342
|
const result = await this.router.generate(
|
|
@@ -323212,6 +323815,7 @@ ${current}`
|
|
|
323212
323815
|
HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
323213
323816
|
maxTokens: 4096,
|
|
323214
323817
|
featureTag: assignment.sectionTitle,
|
|
323818
|
+
...this.signal ? { signal: this.signal } : {},
|
|
323215
323819
|
...this.localOnlyMatch ? { forceLocal: true } : {}
|
|
323216
323820
|
});
|
|
323217
323821
|
const next = (improved.content ?? "").trim();
|
|
@@ -323273,6 +323877,7 @@ Reply with JSON: { "completeness": "pass"|"fail", "correctness": "pass"|"fail",
|
|
|
323273
323877
|
|
|
323274
323878
|
HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
323275
323879
|
featureTag: assignment.sectionTitle,
|
|
323880
|
+
...this.signal ? { signal: this.signal } : {},
|
|
323276
323881
|
...this.localOnlyMatch ? { forceLocal: true } : {}
|
|
323277
323882
|
});
|
|
323278
323883
|
try {
|
|
@@ -323312,6 +323917,7 @@ ${output.slice(0, 4e3)}`;
|
|
|
323312
323917
|
messages: [{ role: "user", content: prompt }],
|
|
323313
323918
|
maxTokens: 300,
|
|
323314
323919
|
temperature: 0,
|
|
323920
|
+
...this.signal ? { signal: this.signal } : {},
|
|
323315
323921
|
...this.localOnlyMatch ? { forceLocal: true } : {}
|
|
323316
323922
|
});
|
|
323317
323923
|
const match = /\[[\s\S]*\]/.exec(result.content);
|
|
@@ -323423,6 +324029,10 @@ Begin execution now.`;
|
|
|
323423
324029
|
}
|
|
323424
324030
|
};
|
|
323425
324031
|
var PeerBus = class extends EventEmitter__default.default {
|
|
324032
|
+
constructor() {
|
|
324033
|
+
super();
|
|
324034
|
+
this.setMaxListeners(64);
|
|
324035
|
+
}
|
|
323426
324036
|
outputs = /* @__PURE__ */ new Map();
|
|
323427
324037
|
waiters = /* @__PURE__ */ new Map();
|
|
323428
324038
|
members = /* @__PURE__ */ new Set();
|
|
@@ -324670,6 +325280,7 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
324670
325280
|
const waveSignal = AbortSignal.any(
|
|
324671
325281
|
[this.signal, this.waveAbortController.signal].filter(Boolean)
|
|
324672
325282
|
);
|
|
325283
|
+
EventEmitter.setMaxListeners(64, waveSignal);
|
|
324673
325284
|
const runOne = async (id) => {
|
|
324674
325285
|
const assignment = sanitizedAssignments.find((a2) => a2.subtaskId === id);
|
|
324675
325286
|
const worker = workerMap.get(id);
|
|
@@ -372617,115 +373228,6 @@ var GraphSearchTool = class extends BaseTool {
|
|
|
372617
373228
|
}
|
|
372618
373229
|
};
|
|
372619
373230
|
|
|
372620
|
-
// src/core/context/compaction.ts
|
|
372621
|
-
function estimateTokens(text) {
|
|
372622
|
-
if (!text) return 0;
|
|
372623
|
-
return Math.max(1, Math.ceil(text.length / 4));
|
|
372624
|
-
}
|
|
372625
|
-
function contentToText(content) {
|
|
372626
|
-
if (typeof content === "string") return content;
|
|
372627
|
-
return content.map((c4) => c4.type === "text" ? c4.text : c4.type === "tool_result" ? c4.content : `[${c4.type}]`).join(" ");
|
|
372628
|
-
}
|
|
372629
|
-
function messagesTokens(messages) {
|
|
372630
|
-
return messages.reduce((n8, m3) => n8 + estimateTokens(contentToText(m3.content)), 0);
|
|
372631
|
-
}
|
|
372632
|
-
function atomize(text, maxChars) {
|
|
372633
|
-
const seps = ["\n\n", "\n", ". ", " "];
|
|
372634
|
-
let units = [text];
|
|
372635
|
-
for (const sep3 of seps) {
|
|
372636
|
-
if (units.every((u2) => u2.length <= maxChars)) break;
|
|
372637
|
-
const next = [];
|
|
372638
|
-
for (const u2 of units) {
|
|
372639
|
-
if (u2.length <= maxChars) {
|
|
372640
|
-
next.push(u2);
|
|
372641
|
-
continue;
|
|
372642
|
-
}
|
|
372643
|
-
const parts = u2.split(sep3);
|
|
372644
|
-
parts.forEach((p3, i4) => next.push(i4 < parts.length - 1 ? p3 + sep3 : p3));
|
|
372645
|
-
}
|
|
372646
|
-
units = next;
|
|
372647
|
-
}
|
|
372648
|
-
const out = [];
|
|
372649
|
-
for (const u2 of units) {
|
|
372650
|
-
if (u2.length <= maxChars) out.push(u2);
|
|
372651
|
-
else for (let i4 = 0; i4 < u2.length; i4 += maxChars) out.push(u2.slice(i4, i4 + maxChars));
|
|
372652
|
-
}
|
|
372653
|
-
return out;
|
|
372654
|
-
}
|
|
372655
|
-
function chunkText(text, opts) {
|
|
372656
|
-
const maxChars = Math.max(1, Math.floor(opts.maxTokens * 4));
|
|
372657
|
-
const overlap = Math.min(maxChars - 1, Math.max(0, Math.floor(maxChars * (opts.overlapRatio ?? 0.1))));
|
|
372658
|
-
const trimmed2 = text.trim();
|
|
372659
|
-
if (!trimmed2) return [];
|
|
372660
|
-
if (trimmed2.length <= maxChars) return [trimmed2];
|
|
372661
|
-
const units = atomize(trimmed2, maxChars);
|
|
372662
|
-
const chunks = [];
|
|
372663
|
-
let cur = "";
|
|
372664
|
-
for (const u2 of units) {
|
|
372665
|
-
if (cur && cur.length + u2.length > maxChars) {
|
|
372666
|
-
chunks.push(cur.trim());
|
|
372667
|
-
cur = overlap > 0 ? cur.slice(-overlap) : "";
|
|
372668
|
-
}
|
|
372669
|
-
cur += u2;
|
|
372670
|
-
}
|
|
372671
|
-
if (cur.trim()) chunks.push(cur.trim());
|
|
372672
|
-
return chunks;
|
|
372673
|
-
}
|
|
372674
|
-
function needsCompaction(totalTokens, contextWindow, reserve = 0.2) {
|
|
372675
|
-
if (!contextWindow || contextWindow <= 0) return false;
|
|
372676
|
-
return totalTokens > contextWindow * (1 - reserve);
|
|
372677
|
-
}
|
|
372678
|
-
async function mapReduceCompact(text, opts) {
|
|
372679
|
-
const capChars = opts.capTokens * 4;
|
|
372680
|
-
let working = text;
|
|
372681
|
-
let truncated = false;
|
|
372682
|
-
if (working.length > capChars) {
|
|
372683
|
-
working = working.slice(0, capChars);
|
|
372684
|
-
truncated = true;
|
|
372685
|
-
}
|
|
372686
|
-
const instruction = opts.instruction ?? "Summarize this content, preserving key facts, names, numbers, and decisions.";
|
|
372687
|
-
let chunks = chunkText(working, { maxTokens: opts.chunkTokens });
|
|
372688
|
-
if (chunks.length <= 1) return { text: working, chunks: chunks.length, calls: 0, truncated };
|
|
372689
|
-
let calls = 0;
|
|
372690
|
-
let parts = await Promise.all(
|
|
372691
|
-
chunks.map((c4, i4) => {
|
|
372692
|
-
calls++;
|
|
372693
|
-
return opts.summarize(c4, `${instruction} (part ${i4 + 1} of ${chunks.length})`);
|
|
372694
|
-
})
|
|
372695
|
-
);
|
|
372696
|
-
let combined = parts.join("\n\n");
|
|
372697
|
-
const maxPasses = opts.maxReducePasses ?? 3;
|
|
372698
|
-
for (let pass = 0; pass < maxPasses && estimateTokens(combined) > opts.targetTokens; pass++) {
|
|
372699
|
-
const groups = chunkText(combined, { maxTokens: opts.chunkTokens });
|
|
372700
|
-
if (groups.length <= 1) break;
|
|
372701
|
-
parts = await Promise.all(
|
|
372702
|
-
groups.map((g2) => {
|
|
372703
|
-
calls++;
|
|
372704
|
-
return opts.summarize(g2, "Merge these partial summaries into one cohesive summary; keep all distinct facts.");
|
|
372705
|
-
})
|
|
372706
|
-
);
|
|
372707
|
-
combined = parts.join("\n\n");
|
|
372708
|
-
}
|
|
372709
|
-
const note = truncated ? "\n\n[Note: input exceeded the extended-context cap and was truncated.]" : "";
|
|
372710
|
-
return { text: combined + note, chunks: chunks.length, calls, truncated };
|
|
372711
|
-
}
|
|
372712
|
-
async function rollingSummary(messages, opts) {
|
|
372713
|
-
if (messages.length <= opts.keepRecent) return messages;
|
|
372714
|
-
const older = messages.slice(0, messages.length - opts.keepRecent);
|
|
372715
|
-
const recent = messages.slice(messages.length - opts.keepRecent);
|
|
372716
|
-
const transcript = older.map((m3) => `${m3.role}: ${contentToText(m3.content)}`).join("\n");
|
|
372717
|
-
const summary = await opts.summarize(
|
|
372718
|
-
transcript,
|
|
372719
|
-
"Summarize this earlier conversation. Preserve facts, decisions, names, numbers, and any open threads or todos."
|
|
372720
|
-
);
|
|
372721
|
-
const summaryMessage = {
|
|
372722
|
-
role: "system",
|
|
372723
|
-
content: `Summary of earlier conversation (folded to fit the context window):
|
|
372724
|
-
${summary}`
|
|
372725
|
-
};
|
|
372726
|
-
return [summaryMessage, ...recent];
|
|
372727
|
-
}
|
|
372728
|
-
|
|
372729
373231
|
// src/core/steering/guidance.ts
|
|
372730
373232
|
var GuidanceQueue = class {
|
|
372731
373233
|
entries = [];
|
|
@@ -378652,10 +379154,10 @@ ${cur}`;
|
|
|
378652
379154
|
}
|
|
378653
379155
|
|
|
378654
379156
|
// src/retrieval/plan.ts
|
|
378655
|
-
var
|
|
379157
|
+
var CHARS_PER_TOKEN2 = 4;
|
|
378656
379158
|
function cagCharBudget(contextWindowTokens, opts = {}) {
|
|
378657
379159
|
const docFraction = opts.docFraction ?? 0.5;
|
|
378658
|
-
const charsPerToken = opts.charsPerToken ??
|
|
379160
|
+
const charsPerToken = opts.charsPerToken ?? CHARS_PER_TOKEN2;
|
|
378659
379161
|
return Math.max(0, Math.floor(contextWindowTokens * docFraction * charsPerToken));
|
|
378660
379162
|
}
|
|
378661
379163
|
function planRetrieval(ctx) {
|
|
@@ -379659,7 +380161,7 @@ exports.CASCADE_IGNORE_FILE = CASCADE_IGNORE_FILE;
|
|
|
379659
380161
|
exports.CASCADE_KEYSTORE_FILE = CASCADE_KEYSTORE_FILE;
|
|
379660
380162
|
exports.CASCADE_MD_FILE = CASCADE_MD_FILE;
|
|
379661
380163
|
exports.CASCADE_VERSION = CASCADE_VERSION;
|
|
379662
|
-
exports.CHARS_PER_TOKEN =
|
|
380164
|
+
exports.CHARS_PER_TOKEN = CHARS_PER_TOKEN2;
|
|
379663
380165
|
exports.COMPLEXITY_T2_COUNT = COMPLEXITY_T2_COUNT;
|
|
379664
380166
|
exports.Cascade = Cascade;
|
|
379665
380167
|
exports.CascadeCancelledError = CascadeCancelledError;
|