cascade-ai 0.63.0 → 0.65.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 +374 -28
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +374 -28
- package/dist/cli.js.map +1 -1
- package/dist/desktop-core.cjs +341 -22
- package/dist/index.cjs +341 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +33 -3
- package/dist/index.d.ts +33 -3
- package/dist/index.js +339 -23
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1624,7 +1624,7 @@ var pricing_data_default = {
|
|
|
1624
1624
|
]
|
|
1625
1625
|
},
|
|
1626
1626
|
{
|
|
1627
|
-
model: "veo-3.1-generate-
|
|
1627
|
+
model: "veo-3.1-generate-preview",
|
|
1628
1628
|
provider: "gemini",
|
|
1629
1629
|
modality: "video",
|
|
1630
1630
|
currency: "USD",
|
|
@@ -1862,7 +1862,7 @@ function reconcilePrice(model, live) {
|
|
|
1862
1862
|
}
|
|
1863
1863
|
|
|
1864
1864
|
// src/constants.ts
|
|
1865
|
-
var CASCADE_VERSION = "0.
|
|
1865
|
+
var CASCADE_VERSION = "0.65.0";
|
|
1866
1866
|
var CASCADE_CONFIG_DIR = ".cascade";
|
|
1867
1867
|
var CASCADE_MD_FILE = "CASCADE.md";
|
|
1868
1868
|
var CASCADE_IGNORE_FILE = ".cascadeignore";
|
|
@@ -2269,6 +2269,9 @@ var DEFAULT_THEME = "cascade";
|
|
|
2269
2269
|
var OLLAMA_BASE_URL = "http://localhost:11434";
|
|
2270
2270
|
var LM_STUDIO_BASE_URL = "http://localhost:1234";
|
|
2271
2271
|
var AZURE_BASE_URL_TEMPLATE = "https://{resource}.openai.azure.com";
|
|
2272
|
+
var GITHUB_MODELS_INFERENCE_URL = "https://models.github.ai/inference";
|
|
2273
|
+
var GITHUB_MODELS_CATALOG_URL = "https://models.github.ai/catalog/models";
|
|
2274
|
+
var GITHUB_MODELS_API_VERSION = "2026-03-10";
|
|
2272
2275
|
var TOOL_NAMES = {
|
|
2273
2276
|
SHELL: "shell",
|
|
2274
2277
|
FILE_READ: "file_read",
|
|
@@ -2303,7 +2306,8 @@ var PROVIDER_DISPLAY_NAMES = {
|
|
|
2303
2306
|
gemini: "Google Gemini",
|
|
2304
2307
|
azure: "Azure OpenAI",
|
|
2305
2308
|
"openai-compatible": "OpenAI-Compatible",
|
|
2306
|
-
ollama: "Ollama (Local)"
|
|
2309
|
+
ollama: "Ollama (Local)",
|
|
2310
|
+
"github-models": "GitHub Models"
|
|
2307
2311
|
};
|
|
2308
2312
|
|
|
2309
2313
|
// src/utils/cost.ts
|
|
@@ -3280,7 +3284,7 @@ function preferIpv4Host(url) {
|
|
|
3280
3284
|
return url;
|
|
3281
3285
|
}
|
|
3282
3286
|
var MAX_REDIRECTS = 5;
|
|
3283
|
-
async function nodeHttpFetch(input, init = {}, redirectCount = 0) {
|
|
3287
|
+
async function nodeHttpFetch(input, init = {}, redirectCount = 0, opts = {}) {
|
|
3284
3288
|
const urlStr = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
3285
3289
|
const u = new URL(preferIpv4Host(urlStr) ?? urlStr);
|
|
3286
3290
|
const lib = u.protocol === "https:" ? https : http;
|
|
@@ -3311,9 +3315,13 @@ async function nodeHttpFetch(input, init = {}, redirectCount = 0) {
|
|
|
3311
3315
|
if (status >= 300 && status < 400 && location && redirectCount < MAX_REDIRECTS) {
|
|
3312
3316
|
res.resume();
|
|
3313
3317
|
const nextUrl = new URL(location, u).href;
|
|
3318
|
+
if (opts.allowedRedirectOrigin && new URL(nextUrl).origin !== opts.allowedRedirectOrigin) {
|
|
3319
|
+
reject(new Error(`Refusing cross-origin redirect to ${new URL(nextUrl).origin}`));
|
|
3320
|
+
return;
|
|
3321
|
+
}
|
|
3314
3322
|
const downgrade = status === 303 || (status === 301 || status === 302) && method !== "GET" && method !== "HEAD";
|
|
3315
3323
|
const nextInit = downgrade ? { ...init, method: "GET", body: void 0 } : init;
|
|
3316
|
-
resolve2(nodeHttpFetch(nextUrl, nextInit, redirectCount + 1));
|
|
3324
|
+
resolve2(nodeHttpFetch(nextUrl, nextInit, redirectCount + 1, opts));
|
|
3317
3325
|
return;
|
|
3318
3326
|
}
|
|
3319
3327
|
const encoding = (res.headers["content-encoding"] ?? "").toLowerCase();
|
|
@@ -3342,6 +3350,206 @@ async function nodeHttpFetch(input, init = {}, redirectCount = 0) {
|
|
|
3342
3350
|
});
|
|
3343
3351
|
}
|
|
3344
3352
|
|
|
3353
|
+
// src/providers/github-models.ts
|
|
3354
|
+
var GITHUB_MODELS_MAX_OUTPUT_TOKENS = 4e3;
|
|
3355
|
+
var GITHUB_MODELS_MAX_INPUT_TOKENS = 8e3;
|
|
3356
|
+
var DEFAULT_CONTEXT_WINDOW = 128e3;
|
|
3357
|
+
var GITHUB_MODELS_MAX_CATALOG_PAGES = 20;
|
|
3358
|
+
var GITHUB_MODELS_CATALOG_ORIGIN = new URL(GITHUB_MODELS_CATALOG_URL).origin;
|
|
3359
|
+
function stripModelOwnerPrefix(id) {
|
|
3360
|
+
const slash = (id || "").indexOf("/");
|
|
3361
|
+
return slash === -1 ? id : id.slice(slash + 1);
|
|
3362
|
+
}
|
|
3363
|
+
function pickString(o, keys) {
|
|
3364
|
+
for (const k of keys) {
|
|
3365
|
+
const v = o[k];
|
|
3366
|
+
if (typeof v === "string" && v.length > 0) return v;
|
|
3367
|
+
}
|
|
3368
|
+
return void 0;
|
|
3369
|
+
}
|
|
3370
|
+
function pickNumber(o, keys) {
|
|
3371
|
+
for (const k of keys) {
|
|
3372
|
+
const v = k.includes(".") ? o[k.split(".")[0]]?.[k.split(".")[1]] : o[k];
|
|
3373
|
+
if (typeof v === "number" && Number.isFinite(v) && v > 0) return v;
|
|
3374
|
+
}
|
|
3375
|
+
return void 0;
|
|
3376
|
+
}
|
|
3377
|
+
function looksVisionCapable(o) {
|
|
3378
|
+
for (const k of ["supported_input_modalities", "input_modalities", "modalities", "capabilities"]) {
|
|
3379
|
+
const v = o[k];
|
|
3380
|
+
if (Array.isArray(v) && v.some((x) => typeof x === "string" && /image|vision/i.test(x))) return true;
|
|
3381
|
+
}
|
|
3382
|
+
return false;
|
|
3383
|
+
}
|
|
3384
|
+
function looksToolCapable(o) {
|
|
3385
|
+
const params = o["supported_parameters"];
|
|
3386
|
+
if (Array.isArray(params) && params.some((x) => typeof x === "string" && /^tools?$|function.?call/i.test(x))) {
|
|
3387
|
+
return true;
|
|
3388
|
+
}
|
|
3389
|
+
for (const k of ["capabilities", "supported_output_modalities"]) {
|
|
3390
|
+
const v = o[k];
|
|
3391
|
+
if (Array.isArray(v) && v.some((x) => typeof x === "string" && /tool|function.?call/i.test(x))) return true;
|
|
3392
|
+
}
|
|
3393
|
+
const caps = o["capabilities"];
|
|
3394
|
+
if (caps && typeof caps === "object" && !Array.isArray(caps)) {
|
|
3395
|
+
const c = caps;
|
|
3396
|
+
if (c["function_calling"] === true || c["tool_calling"] === true || c["tools"] === true) return true;
|
|
3397
|
+
}
|
|
3398
|
+
return false;
|
|
3399
|
+
}
|
|
3400
|
+
function parseNextLink(linkHeader) {
|
|
3401
|
+
if (!linkHeader) return null;
|
|
3402
|
+
for (const part of linkHeader.split(",")) {
|
|
3403
|
+
const match = part.match(/<([^>]+)>\s*;\s*rel="next"/);
|
|
3404
|
+
if (match) return match[1];
|
|
3405
|
+
}
|
|
3406
|
+
return null;
|
|
3407
|
+
}
|
|
3408
|
+
var GitHubModelsProvider = class extends OpenAIProvider {
|
|
3409
|
+
constructor(config, model) {
|
|
3410
|
+
super({ ...config, apiKey: config.apiKey ?? "" }, model);
|
|
3411
|
+
this.useMaxCompletionTokens = isReasoningModel(stripModelOwnerPrefix(model.id));
|
|
3412
|
+
this.client = new OpenAI({
|
|
3413
|
+
apiKey: config.apiKey ?? "",
|
|
3414
|
+
baseURL: GITHUB_MODELS_INFERENCE_URL,
|
|
3415
|
+
defaultHeaders: { "X-GitHub-Api-Version": GITHUB_MODELS_API_VERSION, "User-Agent": "Cascade-AI" }
|
|
3416
|
+
});
|
|
3417
|
+
}
|
|
3418
|
+
/**
|
|
3419
|
+
* `OpenAIProvider.generateStream()` builds its request budget as
|
|
3420
|
+
* `options.maxTokens ?? this.model.maxOutputTokens` — an explicit per-call
|
|
3421
|
+
* `maxTokens` (T1Administrator's final compilation asks for 8,000, well
|
|
3422
|
+
* above GitHub's real ~4K hard cap) wins outright and is sent unclamped.
|
|
3423
|
+
* GitHub then rejects the request instead of answering, so a run routed to
|
|
3424
|
+
* this provider for that step fails outright rather than getting a
|
|
3425
|
+
* (perfectly serviceable, just shorter) completion. Clamp here, once, right
|
|
3426
|
+
* before delegating to the inherited implementation, rather than teaching
|
|
3427
|
+
* every caller GitHub's specific cap.
|
|
3428
|
+
*/
|
|
3429
|
+
async generateStream(options, onChunk) {
|
|
3430
|
+
const clamped = options.maxTokens !== void 0 && options.maxTokens > GITHUB_MODELS_MAX_OUTPUT_TOKENS ? { ...options, maxTokens: GITHUB_MODELS_MAX_OUTPUT_TOKENS } : options;
|
|
3431
|
+
return super.generateStream(clamped, onChunk);
|
|
3432
|
+
}
|
|
3433
|
+
/**
|
|
3434
|
+
* The catalog is a GitHub REST resource, not an OpenAI `/models` list, and
|
|
3435
|
+
* wants GitHub's own header trio — the plain `Accept: application/json` the
|
|
3436
|
+
* openai-compatible provider sends is not the same request.
|
|
3437
|
+
*/
|
|
3438
|
+
catalogHeaders() {
|
|
3439
|
+
return {
|
|
3440
|
+
Accept: "application/vnd.github+json",
|
|
3441
|
+
Authorization: `Bearer ${this.config.apiKey ?? ""}`,
|
|
3442
|
+
"X-GitHub-Api-Version": GITHUB_MODELS_API_VERSION,
|
|
3443
|
+
// GitHub's REST API rejects any request with no User-Agent — a plain 403
|
|
3444
|
+
// regardless of how valid the PAT is. nodeHttpFetch is a thin wrapper
|
|
3445
|
+
// over node:http/https, neither of which sets one the way a browser's
|
|
3446
|
+
// fetch or a full HTTP client library would, so this has to be explicit.
|
|
3447
|
+
"User-Agent": "Cascade-AI"
|
|
3448
|
+
};
|
|
3449
|
+
}
|
|
3450
|
+
async listModels() {
|
|
3451
|
+
const raw = [];
|
|
3452
|
+
let url = GITHUB_MODELS_CATALOG_URL;
|
|
3453
|
+
let pages = 0;
|
|
3454
|
+
const visited = /* @__PURE__ */ new Set();
|
|
3455
|
+
while (url && pages < GITHUB_MODELS_MAX_CATALOG_PAGES) {
|
|
3456
|
+
if (visited.has(url)) break;
|
|
3457
|
+
visited.add(url);
|
|
3458
|
+
const res = await nodeHttpFetch(url, { headers: this.catalogHeaders() }, 0, {
|
|
3459
|
+
allowedRedirectOrigin: GITHUB_MODELS_CATALOG_ORIGIN
|
|
3460
|
+
});
|
|
3461
|
+
if (!res.ok) throw new Error(`GitHub Models catalog ${url} returned HTTP ${res.status}`);
|
|
3462
|
+
const body = await res.json();
|
|
3463
|
+
const pageItems = Array.isArray(body) ? body : Array.isArray(body?.models) ? body.models : Array.isArray(body?.data) ? body.data : [];
|
|
3464
|
+
raw.push(...pageItems);
|
|
3465
|
+
const next = parseNextLink(res.headers.get("link"));
|
|
3466
|
+
if (next) {
|
|
3467
|
+
const resolved = new URL(next, url);
|
|
3468
|
+
if (resolved.origin !== GITHUB_MODELS_CATALOG_ORIGIN) {
|
|
3469
|
+
throw new Error(`Refusing cross-origin GitHub Models pagination URL: ${resolved.origin}`);
|
|
3470
|
+
}
|
|
3471
|
+
url = resolved.href;
|
|
3472
|
+
} else {
|
|
3473
|
+
url = null;
|
|
3474
|
+
}
|
|
3475
|
+
pages++;
|
|
3476
|
+
}
|
|
3477
|
+
const entries = raw.map((m) => {
|
|
3478
|
+
if (typeof m === "string") {
|
|
3479
|
+
return { id: m, name: m, contextWindow: DEFAULT_CONTEXT_WINDOW, vision: false, tools: false };
|
|
3480
|
+
}
|
|
3481
|
+
if (!m || typeof m !== "object") return void 0;
|
|
3482
|
+
const o = m;
|
|
3483
|
+
const id = pickString(o, ["id", "name", "model"]);
|
|
3484
|
+
if (!id) return void 0;
|
|
3485
|
+
return {
|
|
3486
|
+
id,
|
|
3487
|
+
name: pickString(o, ["friendly_name", "display_name", "name"]) ?? id,
|
|
3488
|
+
contextWindow: pickNumber(o, [
|
|
3489
|
+
"context_window",
|
|
3490
|
+
"context_length",
|
|
3491
|
+
"max_input_tokens",
|
|
3492
|
+
"limits.max_input_tokens",
|
|
3493
|
+
"limits.max_context_tokens"
|
|
3494
|
+
]) ?? DEFAULT_CONTEXT_WINDOW,
|
|
3495
|
+
vision: looksVisionCapable(o),
|
|
3496
|
+
tools: looksToolCapable(o)
|
|
3497
|
+
};
|
|
3498
|
+
}).filter((e) => e !== void 0);
|
|
3499
|
+
if (entries.length === 0) return [];
|
|
3500
|
+
const chat = entries.filter((e) => isChatModel(e.id));
|
|
3501
|
+
return (chat.length ? chat : entries).map((e) => ({
|
|
3502
|
+
id: e.id,
|
|
3503
|
+
// full `owner/model` — what the API is addressed by
|
|
3504
|
+
name: e.name,
|
|
3505
|
+
provider: "github-models",
|
|
3506
|
+
// GitHub's real per-request input cap, not the base model's larger one
|
|
3507
|
+
// (see GITHUB_MODELS_MAX_INPUT_TOKENS above) — the same reasoning
|
|
3508
|
+
// already applied to maxOutputTokens below, for the input side.
|
|
3509
|
+
contextWindow: Math.min(e.contextWindow, GITHUB_MODELS_MAX_INPUT_TOKENS),
|
|
3510
|
+
isVisionCapable: e.vision,
|
|
3511
|
+
// A REAL $0, not a missing number — usage is bundled into the quota of
|
|
3512
|
+
// the plan the PAT belongs to and is never billed per token, so
|
|
3513
|
+
// `pricingUnknown: false` is the honest value (see the flag's contract in
|
|
3514
|
+
// core/router/pricing.ts: 0 alone is ambiguous). Set here rather than in
|
|
3515
|
+
// pricing-data.json because resolvePricing() matches exactly on
|
|
3516
|
+
// provider|model|region with no wildcard: every catalog model would need
|
|
3517
|
+
// a hand-curated row, and the next one GitHub adds would read as "cost
|
|
3518
|
+
// unknown" — an inconsistent picture for a billing reality that is
|
|
3519
|
+
// identical across the entire catalog.
|
|
3520
|
+
//
|
|
3521
|
+
// `isLocal` is the OTHER way to reach a real $0, and it is the wrong one:
|
|
3522
|
+
// it means "runs on this machine" and carries consequences that are
|
|
3523
|
+
// simply false for a hosted API — the shared concurrency-1
|
|
3524
|
+
// LocalRequestQueue (serialising these calls behind actual Ollama GPU
|
|
3525
|
+
// inference), the 300s local rather than 120s cloud inference timeout,
|
|
3526
|
+
// and a literal "[local]" tag in `cascade models`.
|
|
3527
|
+
inputCostPer1kTokens: 0,
|
|
3528
|
+
outputCostPer1kTokens: 0,
|
|
3529
|
+
pricingUnknown: false,
|
|
3530
|
+
maxOutputTokens: GITHUB_MODELS_MAX_OUTPUT_TOKENS,
|
|
3531
|
+
supportsStreaming: true,
|
|
3532
|
+
// Not every catalog model supports function/tool calling — a hardcoded
|
|
3533
|
+
// `true` sent `tools` to models that reject the parameter outright and
|
|
3534
|
+
// never engaged t3-worker.ts's existing text-tool fallback (only an
|
|
3535
|
+
// explicit `false` does). See looksToolCapable's own comment for why
|
|
3536
|
+
// "not positively advertised" defaults to false rather than true here.
|
|
3537
|
+
supportsToolUse: e.tools,
|
|
3538
|
+
isLocal: false
|
|
3539
|
+
}));
|
|
3540
|
+
}
|
|
3541
|
+
async isAvailable() {
|
|
3542
|
+
try {
|
|
3543
|
+
const res = await nodeHttpFetch(GITHUB_MODELS_CATALOG_URL, { headers: this.catalogHeaders() }, 0, {
|
|
3544
|
+
allowedRedirectOrigin: GITHUB_MODELS_CATALOG_ORIGIN
|
|
3545
|
+
});
|
|
3546
|
+
return res.ok;
|
|
3547
|
+
} catch {
|
|
3548
|
+
return false;
|
|
3549
|
+
}
|
|
3550
|
+
}
|
|
3551
|
+
};
|
|
3552
|
+
|
|
3345
3553
|
// src/providers/ollama.ts
|
|
3346
3554
|
var TOOL_CAPABLE_FAMILIES = [
|
|
3347
3555
|
"llama3.1",
|
|
@@ -3758,16 +3966,23 @@ var ModelSelector = class {
|
|
|
3758
3966
|
return model;
|
|
3759
3967
|
}
|
|
3760
3968
|
}
|
|
3969
|
+
for (const model of this.availableModels.values()) {
|
|
3970
|
+
if (this.availableProviders.has(model.provider) && model.isVisionCapable) return model;
|
|
3971
|
+
}
|
|
3761
3972
|
return null;
|
|
3762
3973
|
}
|
|
3763
3974
|
getNextFallback(currentModelId, tier) {
|
|
3764
3975
|
const priority = this.getPriorityList(tier);
|
|
3765
3976
|
const currentIdx = priority.indexOf(currentModelId);
|
|
3766
|
-
if (currentIdx
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3977
|
+
if (currentIdx !== -1) {
|
|
3978
|
+
for (let i = currentIdx + 1; i < priority.length; i++) {
|
|
3979
|
+
const key = priority[i];
|
|
3980
|
+
const model = this.availableModels.get(key);
|
|
3981
|
+
if (model && this.isUsable(model)) return model;
|
|
3982
|
+
}
|
|
3983
|
+
}
|
|
3984
|
+
for (const [id, model] of this.availableModels) {
|
|
3985
|
+
if (id !== currentModelId && this.isUsable(model)) return model;
|
|
3771
3986
|
}
|
|
3772
3987
|
return null;
|
|
3773
3988
|
}
|
|
@@ -3843,7 +4058,7 @@ var ModelSelector = class {
|
|
|
3843
4058
|
if (overrideModelId.includes(":")) {
|
|
3844
4059
|
const parts = overrideModelId.split(":");
|
|
3845
4060
|
const prefix = parts[0].toLowerCase();
|
|
3846
|
-
const validProviders = ["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama"];
|
|
4061
|
+
const validProviders = ["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama", "github-models"];
|
|
3847
4062
|
if (validProviders.includes(prefix)) {
|
|
3848
4063
|
providerStr = prefix;
|
|
3849
4064
|
actualId = parts.slice(1).join(":");
|
|
@@ -3863,6 +4078,31 @@ var ModelSelector = class {
|
|
|
3863
4078
|
else if (this.availableProviders.has("openai-compatible")) providerStr = "openai-compatible";
|
|
3864
4079
|
else if (this.availableProviders.size === 1) providerStr = Array.from(this.availableProviders)[0];
|
|
3865
4080
|
}
|
|
4081
|
+
if (providerStr === "github-models" && this.availableProviders.has("github-models")) {
|
|
4082
|
+
const dynamicModel = {
|
|
4083
|
+
id: actualId,
|
|
4084
|
+
name: actualId,
|
|
4085
|
+
provider: "github-models",
|
|
4086
|
+
contextWindow: GITHUB_MODELS_MAX_INPUT_TOKENS,
|
|
4087
|
+
isVisionCapable: false,
|
|
4088
|
+
inputCostPer1kTokens: 0,
|
|
4089
|
+
outputCostPer1kTokens: 0,
|
|
4090
|
+
pricingUnknown: false,
|
|
4091
|
+
maxOutputTokens: GITHUB_MODELS_MAX_OUTPUT_TOKENS,
|
|
4092
|
+
supportsStreaming: true,
|
|
4093
|
+
// Unconfirmed (no live catalog entry to read a real capability from)
|
|
4094
|
+
// must default to false, not left undefined: t3-worker.ts's text-tool
|
|
4095
|
+
// fallback only engages on a strict `=== false`, so an undefined
|
|
4096
|
+
// value here sends native `tools` to an unverified multi-vendor
|
|
4097
|
+
// model and any that don't support function calling reject the
|
|
4098
|
+
// request outright. Same default-to-false-when-unconfirmed policy
|
|
4099
|
+
// listModels() already applies for a real catalog entry.
|
|
4100
|
+
supportsToolUse: false,
|
|
4101
|
+
isLocal: false
|
|
4102
|
+
};
|
|
4103
|
+
this.addDynamicModel(dynamicModel);
|
|
4104
|
+
return dynamicModel;
|
|
4105
|
+
}
|
|
3866
4106
|
if (providerStr && this.availableProviders.has(providerStr)) {
|
|
3867
4107
|
const dynamicModel = withResolvedPricing({
|
|
3868
4108
|
id: actualId,
|
|
@@ -3955,7 +4195,20 @@ var DEFAULT_PROVIDER_TPM = {
|
|
|
3955
4195
|
gemini: 6e4,
|
|
3956
4196
|
azure: 3e4,
|
|
3957
4197
|
"openai-compatible": 3e4,
|
|
3958
|
-
ollama: Number.POSITIVE_INFINITY
|
|
4198
|
+
ollama: Number.POSITIVE_INFINITY,
|
|
4199
|
+
// An order of magnitude below the others, on purpose. GitHub Models is
|
|
4200
|
+
// rate-limited by REQUESTS (Free ≈10 RPM, Pro ≈20, plus low daily caps), and
|
|
4201
|
+
// this bucket meters TOKENS — there is no request-count limiter in this
|
|
4202
|
+
// codebase, and building one is not in scope. The approximation works because
|
|
4203
|
+
// the router reserves `model.maxOutputTokens + 512` per call, and the
|
|
4204
|
+
// provider reports GitHub's real ~4K per-request cap rather than the
|
|
4205
|
+
// underlying model's larger one — so every call withdraws a near-constant
|
|
4206
|
+
// ~4.5K regardless of how short the reply actually turns out to be. At 8K/min
|
|
4207
|
+
// that lands around 1–2 calls per minute of headroom: comfortably inside even
|
|
4208
|
+
// the Free tier, with room for the daily cap to matter more than this does.
|
|
4209
|
+
// Users on a higher tier can raise it with no code change through the generic
|
|
4210
|
+
// `config.rateLimits.providerTpm['github-models']` override.
|
|
4211
|
+
"github-models": 8e3
|
|
3959
4212
|
};
|
|
3960
4213
|
var TpmLimiter = class {
|
|
3961
4214
|
buckets = /* @__PURE__ */ new Map();
|
|
@@ -4311,6 +4564,7 @@ var ModelProfiler = class {
|
|
|
4311
4564
|
(m) => !alreadyProfiled.has(m.id) && !SKIP_PATTERN.test(m.id) && !SKIP_PATTERN.test(m.name)
|
|
4312
4565
|
);
|
|
4313
4566
|
if (toProfile.length === 0) return;
|
|
4567
|
+
const probesHitGithubModels = this.router?.getModelForTier("T3")?.provider === "github-models";
|
|
4314
4568
|
const openRouterModels = await fetchOpenRouterModels();
|
|
4315
4569
|
const orByNormalizedId = /* @__PURE__ */ new Map();
|
|
4316
4570
|
for (const m of openRouterModels) {
|
|
@@ -4325,7 +4579,7 @@ var ModelProfiler = class {
|
|
|
4325
4579
|
if (orMatch?.description) {
|
|
4326
4580
|
specializations = extractSpecializations(orMatch.description);
|
|
4327
4581
|
}
|
|
4328
|
-
if (specializations.length === 0 && this.router) {
|
|
4582
|
+
if (specializations.length === 0 && this.router && !probesHitGithubModels) {
|
|
4329
4583
|
specializations = await queryModelDirectly(this.router);
|
|
4330
4584
|
}
|
|
4331
4585
|
this.store.saveModelProfile(model.id, model.provider, specializations);
|
|
@@ -4545,6 +4799,7 @@ var LiveDataProvider = class {
|
|
|
4545
4799
|
*/
|
|
4546
4800
|
applyLivePricing(models) {
|
|
4547
4801
|
return models.map((m) => {
|
|
4802
|
+
if (m.provider === "github-models") return m;
|
|
4548
4803
|
const p = this.getLivePrice(m.baseModelId ?? m.id);
|
|
4549
4804
|
if (!p) return m;
|
|
4550
4805
|
const reconciled = reconcilePrice(m, p);
|
|
@@ -4583,6 +4838,7 @@ var LiveDataProvider = class {
|
|
|
4583
4838
|
*/
|
|
4584
4839
|
applyLiveCapabilities(models) {
|
|
4585
4840
|
return models.map((m) => {
|
|
4841
|
+
if (m.provider === "github-models") return m;
|
|
4586
4842
|
const c = this.getCapability(m.baseModelId ?? m.id);
|
|
4587
4843
|
if (!c) return m;
|
|
4588
4844
|
const next = { ...m };
|
|
@@ -4886,13 +5142,16 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
4886
5142
|
for (const model of azureDeployments) this.selector.addDynamicModel(model);
|
|
4887
5143
|
availableProviders.add("azure");
|
|
4888
5144
|
}
|
|
5145
|
+
if (availableProviders.has("github-models")) {
|
|
5146
|
+
await this.discoverGitHubModelsCatalog(config);
|
|
5147
|
+
}
|
|
4889
5148
|
await this.validateCloudProviderModels(config);
|
|
4890
5149
|
for (const tier of ["T1", "T2", "T3"]) {
|
|
4891
5150
|
const override = tier === "T1" ? config.models.t1 : tier === "T2" ? config.models.t2 : config.models.t3;
|
|
4892
5151
|
if (!override || override === "auto") continue;
|
|
4893
5152
|
const model = this.selector.selectForTier(tier, override);
|
|
4894
5153
|
if (!model) {
|
|
4895
|
-
const knownProviders = ["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama"];
|
|
5154
|
+
const knownProviders = ["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama", "github-models"];
|
|
4896
5155
|
const hasProviderPrefix = override.includes(":") && knownProviders.some((p) => override.startsWith(p + ":"));
|
|
4897
5156
|
if (hasProviderPrefix) {
|
|
4898
5157
|
const provider = override.split(":")[0];
|
|
@@ -5047,7 +5306,7 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
5047
5306
|
* models without a package upgrade. Mirrors discoverOllamaModels.
|
|
5048
5307
|
*/
|
|
5049
5308
|
async discoverProviderModels() {
|
|
5050
|
-
const cloud = ["anthropic", "openai", "gemini", "azure", "openai-compatible"];
|
|
5309
|
+
const cloud = ["anthropic", "openai", "gemini", "azure", "openai-compatible", "github-models"];
|
|
5051
5310
|
const tasks = cloud.map(async (type) => {
|
|
5052
5311
|
if (!this.selector.isProviderAvailable(type)) return;
|
|
5053
5312
|
const seed = this.getAnyModelForProvider(type);
|
|
@@ -5102,6 +5361,9 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
5102
5361
|
this.ensureProvider(options.model, this.config.providers);
|
|
5103
5362
|
}
|
|
5104
5363
|
let model = requireVision ? this.selector.selectVisionModel() : options.model ?? this.tierModels.get(tier) ?? this.selector.selectForTier(tier) ?? void 0;
|
|
5364
|
+
if (requireVision && model) {
|
|
5365
|
+
this.ensureProvider(model, this.config.providers);
|
|
5366
|
+
}
|
|
5105
5367
|
if (options.forceLocal && model && !this.isPrivateModel(model)) {
|
|
5106
5368
|
const localModel = this.selector.getAllAvailableModels().find((m) => this.isPrivateModel(m));
|
|
5107
5369
|
if (!localModel) {
|
|
@@ -5115,7 +5377,8 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
5115
5377
|
if (!model) throw new Error(`No model available for tier ${tier}`);
|
|
5116
5378
|
const provider = this.getProvider(model);
|
|
5117
5379
|
if (!provider) throw new Error(`No provider for model ${model.id}`);
|
|
5118
|
-
const
|
|
5380
|
+
const requestedTokens = options.maxTokens ?? model.maxOutputTokens ?? 1024;
|
|
5381
|
+
const estimatedTokens = Math.min(requestedTokens, model.maxOutputTokens ?? requestedTokens) + 512;
|
|
5119
5382
|
if (this.tpmLimiter) {
|
|
5120
5383
|
await this.tpmLimiter.acquire(model.provider, estimatedTokens);
|
|
5121
5384
|
}
|
|
@@ -5206,7 +5469,8 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
5206
5469
|
});
|
|
5207
5470
|
releaseLocalSlot?.();
|
|
5208
5471
|
releaseLocalSlot = void 0;
|
|
5209
|
-
|
|
5472
|
+
const retryOpts = options.model && options.model.id === model.id ? { ...options, model: void 0 } : options;
|
|
5473
|
+
return this.generate(tier, retryOpts, onChunk, requireVision);
|
|
5210
5474
|
}
|
|
5211
5475
|
}
|
|
5212
5476
|
if (isModelNotFoundError(errMsg)) {
|
|
@@ -5557,6 +5821,26 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
5557
5821
|
return false;
|
|
5558
5822
|
}
|
|
5559
5823
|
}
|
|
5824
|
+
/**
|
|
5825
|
+
* Registers GitHub Models' real catalog ids via addDynamicModel so init()'s
|
|
5826
|
+
* tier-fill loop can pick one. Registration alone does not create a bound
|
|
5827
|
+
* BaseProvider instance (see ensureProvider) — the tier-fill loop's own
|
|
5828
|
+
* ensureProvider call does that for whichever model it actually selects,
|
|
5829
|
+
* which is why this runs BEFORE that loop rather than only in the
|
|
5830
|
+
* background discoverProviderModels() refresh.
|
|
5831
|
+
*/
|
|
5832
|
+
async discoverGitHubModelsCatalog(config) {
|
|
5833
|
+
const cfg = config.providers.find((p) => p.type === "github-models") ?? { type: "github-models" };
|
|
5834
|
+
const seed = this.getAnyModelForProvider("github-models");
|
|
5835
|
+
if (!seed) return;
|
|
5836
|
+
try {
|
|
5837
|
+
const provider = new GitHubModelsProvider(cfg, seed);
|
|
5838
|
+
const models = await provider.listModels();
|
|
5839
|
+
for (const m of models) this.selector.addDynamicModel(m);
|
|
5840
|
+
} catch (err) {
|
|
5841
|
+
console.warn("[router] GitHub Models catalog discovery failed:", err instanceof Error ? err.message : err);
|
|
5842
|
+
}
|
|
5843
|
+
}
|
|
5560
5844
|
ensureProvider(model, configs) {
|
|
5561
5845
|
const key = `${model.provider}:${model.id}`;
|
|
5562
5846
|
if (this.providers.has(key)) return;
|
|
@@ -5581,6 +5865,8 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
5581
5865
|
return new OllamaProvider(cfg, model);
|
|
5582
5866
|
case "openai-compatible":
|
|
5583
5867
|
return new OpenAICompatibleProvider(cfg, model);
|
|
5868
|
+
case "github-models":
|
|
5869
|
+
return new GitHubModelsProvider(cfg, model);
|
|
5584
5870
|
default:
|
|
5585
5871
|
throw new Error(`Unsupported provider type: ${String(cfg.type)}`);
|
|
5586
5872
|
}
|
|
@@ -5588,7 +5874,7 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
5588
5874
|
getAnyModelForProvider(type) {
|
|
5589
5875
|
const fromCatalog = Object.values(MODELS).find((m) => m.provider === type);
|
|
5590
5876
|
if (fromCatalog) return fromCatalog;
|
|
5591
|
-
if (type === "openai-compatible" || type === "azure") {
|
|
5877
|
+
if (type === "openai-compatible" || type === "azure" || type === "github-models") {
|
|
5592
5878
|
return {
|
|
5593
5879
|
id: type,
|
|
5594
5880
|
name: type,
|
|
@@ -9272,9 +9558,17 @@ var CAPABILITIES = [
|
|
|
9272
9558
|
// Long-running: submit returns an operation name, the video lands minutes
|
|
9273
9559
|
// later, and the bytes come from a short-lived signed URL. generate.ts owns
|
|
9274
9560
|
// that polling loop; the registry only records the shape via `api`.
|
|
9561
|
+
//
|
|
9562
|
+
// `veo-3.1-generate-001` (no "-preview") is VERTEX AI's id for this model,
|
|
9563
|
+
// not the Gemini Developer API's — this file calls
|
|
9564
|
+
// generativelanguage.googleapis.com (see generate.ts), which 404s on it:
|
|
9565
|
+
// "models/veo-3.1-generate-001 is not found for API version v1beta". On the
|
|
9566
|
+
// Gemini API specifically, Veo 3.1 is still preview-only, so the correct id
|
|
9567
|
+
// there is `veo-3.1-generate-preview`.
|
|
9568
|
+
// https://ai.google.dev/gemini-api/docs/veo
|
|
9275
9569
|
modality: "video",
|
|
9276
9570
|
provider: "gemini",
|
|
9277
|
-
modelId: "veo-3.1-generate-
|
|
9571
|
+
modelId: "veo-3.1-generate-preview",
|
|
9278
9572
|
api: "gemini-predict-lro"
|
|
9279
9573
|
}
|
|
9280
9574
|
];
|
|
@@ -12389,7 +12683,7 @@ var PermissionEscalator = class extends EventEmitter {
|
|
|
12389
12683
|
}
|
|
12390
12684
|
};
|
|
12391
12685
|
var ProviderConfigSchema = z.object({
|
|
12392
|
-
type: z.enum(["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama"]),
|
|
12686
|
+
type: z.enum(["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama", "github-models"]),
|
|
12393
12687
|
label: z.string().optional(),
|
|
12394
12688
|
apiKey: z.string().optional(),
|
|
12395
12689
|
baseUrl: z.string().url().optional(),
|
|
@@ -12569,6 +12863,18 @@ var CascadeConfigSchema = z.object({
|
|
|
12569
12863
|
budget: BudgetConfigSchema.default({}),
|
|
12570
12864
|
theme: z.string().default("cascade"),
|
|
12571
12865
|
workspace: WorkspaceConfigSchema.default({}),
|
|
12866
|
+
/**
|
|
12867
|
+
* Per-provider tokens-per-minute overrides for the router's TpmLimiter (see
|
|
12868
|
+
* DEFAULT_PROVIDER_TPM in core/router/tpm-limiter.ts). Documented as a raw
|
|
12869
|
+
* config field for a while before this — since CascadeConfigSchema strips
|
|
12870
|
+
* unknown keys, a config file setting it was silently discarded and the
|
|
12871
|
+
* override never reached TpmLimiter's constructor. Optional/permissive on
|
|
12872
|
+
* provider name (not a ProviderType enum) so a config still validates
|
|
12873
|
+
* against a slightly newer/older provider list than this schema knows.
|
|
12874
|
+
*/
|
|
12875
|
+
rateLimits: z.object({
|
|
12876
|
+
providerTpm: z.record(z.string(), z.number().positive()).optional()
|
|
12877
|
+
}).optional(),
|
|
12572
12878
|
/**
|
|
12573
12879
|
* Cascade Auto: when true, the TaskAnalyzer selects the optimal model for each
|
|
12574
12880
|
* tier based on task type and complexity, overriding the static priority lists.
|
|
@@ -17603,7 +17909,14 @@ var ConfigManager = class {
|
|
|
17603
17909
|
anthropic: "ANTHROPIC_API_KEY",
|
|
17604
17910
|
openai: "OPENAI_API_KEY",
|
|
17605
17911
|
gemini: "GOOGLE_API_KEY",
|
|
17606
|
-
azure: "AZURE_OPENAI_KEY"
|
|
17912
|
+
azure: "AZURE_OPENAI_KEY",
|
|
17913
|
+
// Deliberately NOT `GITHUB_TOKEN`: that one is injected automatically by
|
|
17914
|
+
// GitHub Actions and is commonly exported in developer shells for `gh`
|
|
17915
|
+
// and git auth, and it will essentially never carry the fine-grained
|
|
17916
|
+
// `models: read` scope this provider needs. Adopting it would wire up a
|
|
17917
|
+
// provider that 403s on its first real call — a confusing failure for
|
|
17918
|
+
// something that is meant to be explicit opt-in BYOK.
|
|
17919
|
+
"github-models": "GITHUB_MODELS_TOKEN"
|
|
17607
17920
|
};
|
|
17608
17921
|
const envKey = envMap[provider];
|
|
17609
17922
|
if (envKey && process.env[envKey]) return process.env[envKey];
|
|
@@ -17632,7 +17945,10 @@ var ConfigManager = class {
|
|
|
17632
17945
|
{ env: "ANTHROPIC_API_KEY", type: "anthropic" },
|
|
17633
17946
|
{ env: "OPENAI_API_KEY", type: "openai" },
|
|
17634
17947
|
{ env: "GOOGLE_API_KEY", type: "gemini" },
|
|
17635
|
-
{ env: "AZURE_OPENAI_KEY", type: "azure" }
|
|
17948
|
+
{ env: "AZURE_OPENAI_KEY", type: "azure" },
|
|
17949
|
+
// See getApiKey() above for why this is GITHUB_MODELS_TOKEN and never
|
|
17950
|
+
// the ambient GITHUB_TOKEN.
|
|
17951
|
+
{ env: "GITHUB_MODELS_TOKEN", type: "github-models" }
|
|
17636
17952
|
];
|
|
17637
17953
|
for (const { env, type } of envProviders) {
|
|
17638
17954
|
const key = process.env[env];
|
|
@@ -20301,6 +20617,6 @@ function applySyncBundle(bundle, config) {
|
|
|
20301
20617
|
return next;
|
|
20302
20618
|
}
|
|
20303
20619
|
|
|
20304
|
-
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, DashboardServer, FileMcpOAuthStore, GLOBAL_CONFIG_DIR, GLOBAL_CREDENTIALS_FILE, GLOBAL_DB_FILE, GLOBAL_KEYSTORE_FILE, GLOBAL_RUNTIME_DB_FILE, 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, Retriever, SqliteVectorStore, T1Administrator, T1_MODEL_PRIORITY, T2Manager, T2_MODEL_PRIORITY, T3Worker, T3_MODEL_PRIORITY, THEME_NAMES, TOOL_NAMES, TaskScheduler, Telemetry, ToolRegistry, VISION_MODEL_PRIORITY, WorkspaceIndex, applySyncBundle, azureModelForDeployment, beginMcpOAuth, buildManifest, buildSessionTranscript, cagCharBudget, chatCompleterFromProviders, chunkCode, chunkText2 as chunkText, completeMcpOAuth, connectMcpWithLoopbackOAuth, createCascade, decryptJSON as decryptSyncBlob, diffManifest, disambiguateMcpServerNames, discoverMcpAuthServer, discoverMcpTools, distillSessionFacts, embedderFromProviders, encryptJSON as encryptSyncBlob, fileOAuthProvider, gatherSyncBundle, hashContent, heuristicCodeChunker, inferAzureBaseModel, isMcpToolName, mcpServerPrefix, mcpToolName, nodeHttpFetch, parseRankOrder, planRetrieval, preferIpv4Host, reciprocalRankFusion, refreshMcpToken, removeMcpServerDenials, runCascade, sessionWorthRemembering, streamCascade, uniqueMcpServerName };
|
|
20620
|
+
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, DashboardServer, FileMcpOAuthStore, GITHUB_MODELS_API_VERSION, GITHUB_MODELS_CATALOG_URL, GITHUB_MODELS_INFERENCE_URL, GLOBAL_CONFIG_DIR, GLOBAL_CREDENTIALS_FILE, GLOBAL_DB_FILE, GLOBAL_KEYSTORE_FILE, GLOBAL_RUNTIME_DB_FILE, 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, Retriever, SqliteVectorStore, T1Administrator, T1_MODEL_PRIORITY, T2Manager, T2_MODEL_PRIORITY, T3Worker, T3_MODEL_PRIORITY, THEME_NAMES, TOOL_NAMES, TaskScheduler, Telemetry, ToolRegistry, VISION_MODEL_PRIORITY, WorkspaceIndex, applySyncBundle, azureModelForDeployment, beginMcpOAuth, buildManifest, buildSessionTranscript, cagCharBudget, chatCompleterFromProviders, chunkCode, chunkText2 as chunkText, completeMcpOAuth, connectMcpWithLoopbackOAuth, createCascade, decryptJSON as decryptSyncBlob, diffManifest, disambiguateMcpServerNames, discoverMcpAuthServer, discoverMcpTools, distillSessionFacts, embedderFromProviders, encryptJSON as encryptSyncBlob, fileOAuthProvider, gatherSyncBundle, hashContent, heuristicCodeChunker, inferAzureBaseModel, isMcpToolName, mcpServerPrefix, mcpToolName, nodeHttpFetch, parseRankOrder, planRetrieval, preferIpv4Host, reciprocalRankFusion, refreshMcpToken, removeMcpServerDenials, runCascade, sessionWorthRemembering, streamCascade, uniqueMcpServerName };
|
|
20305
20621
|
//# sourceMappingURL=index.js.map
|
|
20306
20622
|
//# sourceMappingURL=index.js.map
|