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/desktop-core.cjs
CHANGED
|
@@ -224026,7 +224026,7 @@ var pricing_data_default = {
|
|
|
224026
224026
|
]
|
|
224027
224027
|
},
|
|
224028
224028
|
{
|
|
224029
|
-
model: "veo-3.1-generate-
|
|
224029
|
+
model: "veo-3.1-generate-preview",
|
|
224030
224030
|
provider: "gemini",
|
|
224031
224031
|
modality: "video",
|
|
224032
224032
|
currency: "USD",
|
|
@@ -224264,7 +224264,7 @@ function reconcilePrice(model, live) {
|
|
|
224264
224264
|
}
|
|
224265
224265
|
|
|
224266
224266
|
// src/constants.ts
|
|
224267
|
-
var CASCADE_VERSION = "0.
|
|
224267
|
+
var CASCADE_VERSION = "0.65.0";
|
|
224268
224268
|
var CASCADE_CONFIG_DIR = ".cascade";
|
|
224269
224269
|
var CASCADE_MD_FILE = "CASCADE.md";
|
|
224270
224270
|
var CASCADE_IGNORE_FILE = ".cascadeignore";
|
|
@@ -224671,6 +224671,9 @@ var DEFAULT_THEME = "cascade";
|
|
|
224671
224671
|
var OLLAMA_BASE_URL = "http://localhost:11434";
|
|
224672
224672
|
var LM_STUDIO_BASE_URL = "http://localhost:1234";
|
|
224673
224673
|
var AZURE_BASE_URL_TEMPLATE = "https://{resource}.openai.azure.com";
|
|
224674
|
+
var GITHUB_MODELS_INFERENCE_URL = "https://models.github.ai/inference";
|
|
224675
|
+
var GITHUB_MODELS_CATALOG_URL = "https://models.github.ai/catalog/models";
|
|
224676
|
+
var GITHUB_MODELS_API_VERSION = "2026-03-10";
|
|
224674
224677
|
var TOOL_NAMES = {
|
|
224675
224678
|
SHELL: "shell",
|
|
224676
224679
|
FILE_READ: "file_read",
|
|
@@ -224705,7 +224708,8 @@ var PROVIDER_DISPLAY_NAMES = {
|
|
|
224705
224708
|
gemini: "Google Gemini",
|
|
224706
224709
|
azure: "Azure OpenAI",
|
|
224707
224710
|
"openai-compatible": "OpenAI-Compatible",
|
|
224708
|
-
ollama: "Ollama (Local)"
|
|
224711
|
+
ollama: "Ollama (Local)",
|
|
224712
|
+
"github-models": "GitHub Models"
|
|
224709
224713
|
};
|
|
224710
224714
|
|
|
224711
224715
|
// src/utils/cost.ts
|
|
@@ -251583,7 +251587,7 @@ function preferIpv4Host(url2) {
|
|
|
251583
251587
|
return url2;
|
|
251584
251588
|
}
|
|
251585
251589
|
var MAX_REDIRECTS = 5;
|
|
251586
|
-
async function nodeHttpFetch(input, init2 = {}, redirectCount = 0) {
|
|
251590
|
+
async function nodeHttpFetch(input, init2 = {}, redirectCount = 0, opts = {}) {
|
|
251587
251591
|
const urlStr = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
251588
251592
|
const u2 = new URL(preferIpv4Host(urlStr) ?? urlStr);
|
|
251589
251593
|
const lib = u2.protocol === "https:" ? https2__default.default : http2__default.default;
|
|
@@ -251614,9 +251618,13 @@ async function nodeHttpFetch(input, init2 = {}, redirectCount = 0) {
|
|
|
251614
251618
|
if (status >= 300 && status < 400 && location && redirectCount < MAX_REDIRECTS) {
|
|
251615
251619
|
res.resume();
|
|
251616
251620
|
const nextUrl = new URL(location, u2).href;
|
|
251621
|
+
if (opts.allowedRedirectOrigin && new URL(nextUrl).origin !== opts.allowedRedirectOrigin) {
|
|
251622
|
+
reject(new Error(`Refusing cross-origin redirect to ${new URL(nextUrl).origin}`));
|
|
251623
|
+
return;
|
|
251624
|
+
}
|
|
251617
251625
|
const downgrade = status === 303 || (status === 301 || status === 302) && method !== "GET" && method !== "HEAD";
|
|
251618
251626
|
const nextInit = downgrade ? { ...init2, method: "GET", body: void 0 } : init2;
|
|
251619
|
-
resolve2(nodeHttpFetch(nextUrl, nextInit, redirectCount + 1));
|
|
251627
|
+
resolve2(nodeHttpFetch(nextUrl, nextInit, redirectCount + 1, opts));
|
|
251620
251628
|
return;
|
|
251621
251629
|
}
|
|
251622
251630
|
const encoding = (res.headers["content-encoding"] ?? "").toLowerCase();
|
|
@@ -251645,6 +251653,206 @@ async function nodeHttpFetch(input, init2 = {}, redirectCount = 0) {
|
|
|
251645
251653
|
});
|
|
251646
251654
|
}
|
|
251647
251655
|
|
|
251656
|
+
// src/providers/github-models.ts
|
|
251657
|
+
var GITHUB_MODELS_MAX_OUTPUT_TOKENS = 4e3;
|
|
251658
|
+
var GITHUB_MODELS_MAX_INPUT_TOKENS = 8e3;
|
|
251659
|
+
var DEFAULT_CONTEXT_WINDOW = 128e3;
|
|
251660
|
+
var GITHUB_MODELS_MAX_CATALOG_PAGES = 20;
|
|
251661
|
+
var GITHUB_MODELS_CATALOG_ORIGIN = new URL(GITHUB_MODELS_CATALOG_URL).origin;
|
|
251662
|
+
function stripModelOwnerPrefix(id) {
|
|
251663
|
+
const slash = (id || "").indexOf("/");
|
|
251664
|
+
return slash === -1 ? id : id.slice(slash + 1);
|
|
251665
|
+
}
|
|
251666
|
+
function pickString(o3, keys) {
|
|
251667
|
+
for (const k4 of keys) {
|
|
251668
|
+
const v6 = o3[k4];
|
|
251669
|
+
if (typeof v6 === "string" && v6.length > 0) return v6;
|
|
251670
|
+
}
|
|
251671
|
+
return void 0;
|
|
251672
|
+
}
|
|
251673
|
+
function pickNumber(o3, keys) {
|
|
251674
|
+
for (const k4 of keys) {
|
|
251675
|
+
const v6 = k4.includes(".") ? o3[k4.split(".")[0]]?.[k4.split(".")[1]] : o3[k4];
|
|
251676
|
+
if (typeof v6 === "number" && Number.isFinite(v6) && v6 > 0) return v6;
|
|
251677
|
+
}
|
|
251678
|
+
return void 0;
|
|
251679
|
+
}
|
|
251680
|
+
function looksVisionCapable(o3) {
|
|
251681
|
+
for (const k4 of ["supported_input_modalities", "input_modalities", "modalities", "capabilities"]) {
|
|
251682
|
+
const v6 = o3[k4];
|
|
251683
|
+
if (Array.isArray(v6) && v6.some((x5) => typeof x5 === "string" && /image|vision/i.test(x5))) return true;
|
|
251684
|
+
}
|
|
251685
|
+
return false;
|
|
251686
|
+
}
|
|
251687
|
+
function looksToolCapable(o3) {
|
|
251688
|
+
const params = o3["supported_parameters"];
|
|
251689
|
+
if (Array.isArray(params) && params.some((x5) => typeof x5 === "string" && /^tools?$|function.?call/i.test(x5))) {
|
|
251690
|
+
return true;
|
|
251691
|
+
}
|
|
251692
|
+
for (const k4 of ["capabilities", "supported_output_modalities"]) {
|
|
251693
|
+
const v6 = o3[k4];
|
|
251694
|
+
if (Array.isArray(v6) && v6.some((x5) => typeof x5 === "string" && /tool|function.?call/i.test(x5))) return true;
|
|
251695
|
+
}
|
|
251696
|
+
const caps = o3["capabilities"];
|
|
251697
|
+
if (caps && typeof caps === "object" && !Array.isArray(caps)) {
|
|
251698
|
+
const c4 = caps;
|
|
251699
|
+
if (c4["function_calling"] === true || c4["tool_calling"] === true || c4["tools"] === true) return true;
|
|
251700
|
+
}
|
|
251701
|
+
return false;
|
|
251702
|
+
}
|
|
251703
|
+
function parseNextLink(linkHeader) {
|
|
251704
|
+
if (!linkHeader) return null;
|
|
251705
|
+
for (const part of linkHeader.split(",")) {
|
|
251706
|
+
const match = part.match(/<([^>]+)>\s*;\s*rel="next"/);
|
|
251707
|
+
if (match) return match[1];
|
|
251708
|
+
}
|
|
251709
|
+
return null;
|
|
251710
|
+
}
|
|
251711
|
+
var GitHubModelsProvider = class extends OpenAIProvider {
|
|
251712
|
+
constructor(config2, model) {
|
|
251713
|
+
super({ ...config2, apiKey: config2.apiKey ?? "" }, model);
|
|
251714
|
+
this.useMaxCompletionTokens = isReasoningModel(stripModelOwnerPrefix(model.id));
|
|
251715
|
+
this.client = new openai_default({
|
|
251716
|
+
apiKey: config2.apiKey ?? "",
|
|
251717
|
+
baseURL: GITHUB_MODELS_INFERENCE_URL,
|
|
251718
|
+
defaultHeaders: { "X-GitHub-Api-Version": GITHUB_MODELS_API_VERSION, "User-Agent": "Cascade-AI" }
|
|
251719
|
+
});
|
|
251720
|
+
}
|
|
251721
|
+
/**
|
|
251722
|
+
* `OpenAIProvider.generateStream()` builds its request budget as
|
|
251723
|
+
* `options.maxTokens ?? this.model.maxOutputTokens` — an explicit per-call
|
|
251724
|
+
* `maxTokens` (T1Administrator's final compilation asks for 8,000, well
|
|
251725
|
+
* above GitHub's real ~4K hard cap) wins outright and is sent unclamped.
|
|
251726
|
+
* GitHub then rejects the request instead of answering, so a run routed to
|
|
251727
|
+
* this provider for that step fails outright rather than getting a
|
|
251728
|
+
* (perfectly serviceable, just shorter) completion. Clamp here, once, right
|
|
251729
|
+
* before delegating to the inherited implementation, rather than teaching
|
|
251730
|
+
* every caller GitHub's specific cap.
|
|
251731
|
+
*/
|
|
251732
|
+
async generateStream(options, onChunk) {
|
|
251733
|
+
const clamped = options.maxTokens !== void 0 && options.maxTokens > GITHUB_MODELS_MAX_OUTPUT_TOKENS ? { ...options, maxTokens: GITHUB_MODELS_MAX_OUTPUT_TOKENS } : options;
|
|
251734
|
+
return super.generateStream(clamped, onChunk);
|
|
251735
|
+
}
|
|
251736
|
+
/**
|
|
251737
|
+
* The catalog is a GitHub REST resource, not an OpenAI `/models` list, and
|
|
251738
|
+
* wants GitHub's own header trio — the plain `Accept: application/json` the
|
|
251739
|
+
* openai-compatible provider sends is not the same request.
|
|
251740
|
+
*/
|
|
251741
|
+
catalogHeaders() {
|
|
251742
|
+
return {
|
|
251743
|
+
Accept: "application/vnd.github+json",
|
|
251744
|
+
Authorization: `Bearer ${this.config.apiKey ?? ""}`,
|
|
251745
|
+
"X-GitHub-Api-Version": GITHUB_MODELS_API_VERSION,
|
|
251746
|
+
// GitHub's REST API rejects any request with no User-Agent — a plain 403
|
|
251747
|
+
// regardless of how valid the PAT is. nodeHttpFetch is a thin wrapper
|
|
251748
|
+
// over node:http/https, neither of which sets one the way a browser's
|
|
251749
|
+
// fetch or a full HTTP client library would, so this has to be explicit.
|
|
251750
|
+
"User-Agent": "Cascade-AI"
|
|
251751
|
+
};
|
|
251752
|
+
}
|
|
251753
|
+
async listModels() {
|
|
251754
|
+
const raw = [];
|
|
251755
|
+
let url2 = GITHUB_MODELS_CATALOG_URL;
|
|
251756
|
+
let pages = 0;
|
|
251757
|
+
const visited = /* @__PURE__ */ new Set();
|
|
251758
|
+
while (url2 && pages < GITHUB_MODELS_MAX_CATALOG_PAGES) {
|
|
251759
|
+
if (visited.has(url2)) break;
|
|
251760
|
+
visited.add(url2);
|
|
251761
|
+
const res = await nodeHttpFetch(url2, { headers: this.catalogHeaders() }, 0, {
|
|
251762
|
+
allowedRedirectOrigin: GITHUB_MODELS_CATALOG_ORIGIN
|
|
251763
|
+
});
|
|
251764
|
+
if (!res.ok) throw new Error(`GitHub Models catalog ${url2} returned HTTP ${res.status}`);
|
|
251765
|
+
const body = await res.json();
|
|
251766
|
+
const pageItems = Array.isArray(body) ? body : Array.isArray(body?.models) ? body.models : Array.isArray(body?.data) ? body.data : [];
|
|
251767
|
+
raw.push(...pageItems);
|
|
251768
|
+
const next = parseNextLink(res.headers.get("link"));
|
|
251769
|
+
if (next) {
|
|
251770
|
+
const resolved = new URL(next, url2);
|
|
251771
|
+
if (resolved.origin !== GITHUB_MODELS_CATALOG_ORIGIN) {
|
|
251772
|
+
throw new Error(`Refusing cross-origin GitHub Models pagination URL: ${resolved.origin}`);
|
|
251773
|
+
}
|
|
251774
|
+
url2 = resolved.href;
|
|
251775
|
+
} else {
|
|
251776
|
+
url2 = null;
|
|
251777
|
+
}
|
|
251778
|
+
pages++;
|
|
251779
|
+
}
|
|
251780
|
+
const entries = raw.map((m3) => {
|
|
251781
|
+
if (typeof m3 === "string") {
|
|
251782
|
+
return { id: m3, name: m3, contextWindow: DEFAULT_CONTEXT_WINDOW, vision: false, tools: false };
|
|
251783
|
+
}
|
|
251784
|
+
if (!m3 || typeof m3 !== "object") return void 0;
|
|
251785
|
+
const o3 = m3;
|
|
251786
|
+
const id = pickString(o3, ["id", "name", "model"]);
|
|
251787
|
+
if (!id) return void 0;
|
|
251788
|
+
return {
|
|
251789
|
+
id,
|
|
251790
|
+
name: pickString(o3, ["friendly_name", "display_name", "name"]) ?? id,
|
|
251791
|
+
contextWindow: pickNumber(o3, [
|
|
251792
|
+
"context_window",
|
|
251793
|
+
"context_length",
|
|
251794
|
+
"max_input_tokens",
|
|
251795
|
+
"limits.max_input_tokens",
|
|
251796
|
+
"limits.max_context_tokens"
|
|
251797
|
+
]) ?? DEFAULT_CONTEXT_WINDOW,
|
|
251798
|
+
vision: looksVisionCapable(o3),
|
|
251799
|
+
tools: looksToolCapable(o3)
|
|
251800
|
+
};
|
|
251801
|
+
}).filter((e3) => e3 !== void 0);
|
|
251802
|
+
if (entries.length === 0) return [];
|
|
251803
|
+
const chat = entries.filter((e3) => isChatModel(e3.id));
|
|
251804
|
+
return (chat.length ? chat : entries).map((e3) => ({
|
|
251805
|
+
id: e3.id,
|
|
251806
|
+
// full `owner/model` — what the API is addressed by
|
|
251807
|
+
name: e3.name,
|
|
251808
|
+
provider: "github-models",
|
|
251809
|
+
// GitHub's real per-request input cap, not the base model's larger one
|
|
251810
|
+
// (see GITHUB_MODELS_MAX_INPUT_TOKENS above) — the same reasoning
|
|
251811
|
+
// already applied to maxOutputTokens below, for the input side.
|
|
251812
|
+
contextWindow: Math.min(e3.contextWindow, GITHUB_MODELS_MAX_INPUT_TOKENS),
|
|
251813
|
+
isVisionCapable: e3.vision,
|
|
251814
|
+
// A REAL $0, not a missing number — usage is bundled into the quota of
|
|
251815
|
+
// the plan the PAT belongs to and is never billed per token, so
|
|
251816
|
+
// `pricingUnknown: false` is the honest value (see the flag's contract in
|
|
251817
|
+
// core/router/pricing.ts: 0 alone is ambiguous). Set here rather than in
|
|
251818
|
+
// pricing-data.json because resolvePricing() matches exactly on
|
|
251819
|
+
// provider|model|region with no wildcard: every catalog model would need
|
|
251820
|
+
// a hand-curated row, and the next one GitHub adds would read as "cost
|
|
251821
|
+
// unknown" — an inconsistent picture for a billing reality that is
|
|
251822
|
+
// identical across the entire catalog.
|
|
251823
|
+
//
|
|
251824
|
+
// `isLocal` is the OTHER way to reach a real $0, and it is the wrong one:
|
|
251825
|
+
// it means "runs on this machine" and carries consequences that are
|
|
251826
|
+
// simply false for a hosted API — the shared concurrency-1
|
|
251827
|
+
// LocalRequestQueue (serialising these calls behind actual Ollama GPU
|
|
251828
|
+
// inference), the 300s local rather than 120s cloud inference timeout,
|
|
251829
|
+
// and a literal "[local]" tag in `cascade models`.
|
|
251830
|
+
inputCostPer1kTokens: 0,
|
|
251831
|
+
outputCostPer1kTokens: 0,
|
|
251832
|
+
pricingUnknown: false,
|
|
251833
|
+
maxOutputTokens: GITHUB_MODELS_MAX_OUTPUT_TOKENS,
|
|
251834
|
+
supportsStreaming: true,
|
|
251835
|
+
// Not every catalog model supports function/tool calling — a hardcoded
|
|
251836
|
+
// `true` sent `tools` to models that reject the parameter outright and
|
|
251837
|
+
// never engaged t3-worker.ts's existing text-tool fallback (only an
|
|
251838
|
+
// explicit `false` does). See looksToolCapable's own comment for why
|
|
251839
|
+
// "not positively advertised" defaults to false rather than true here.
|
|
251840
|
+
supportsToolUse: e3.tools,
|
|
251841
|
+
isLocal: false
|
|
251842
|
+
}));
|
|
251843
|
+
}
|
|
251844
|
+
async isAvailable() {
|
|
251845
|
+
try {
|
|
251846
|
+
const res = await nodeHttpFetch(GITHUB_MODELS_CATALOG_URL, { headers: this.catalogHeaders() }, 0, {
|
|
251847
|
+
allowedRedirectOrigin: GITHUB_MODELS_CATALOG_ORIGIN
|
|
251848
|
+
});
|
|
251849
|
+
return res.ok;
|
|
251850
|
+
} catch {
|
|
251851
|
+
return false;
|
|
251852
|
+
}
|
|
251853
|
+
}
|
|
251854
|
+
};
|
|
251855
|
+
|
|
251648
251856
|
// src/providers/ollama.ts
|
|
251649
251857
|
var TOOL_CAPABLE_FAMILIES = [
|
|
251650
251858
|
"llama3.1",
|
|
@@ -252063,16 +252271,23 @@ var ModelSelector = class {
|
|
|
252063
252271
|
return model;
|
|
252064
252272
|
}
|
|
252065
252273
|
}
|
|
252274
|
+
for (const model of this.availableModels.values()) {
|
|
252275
|
+
if (this.availableProviders.has(model.provider) && model.isVisionCapable) return model;
|
|
252276
|
+
}
|
|
252066
252277
|
return null;
|
|
252067
252278
|
}
|
|
252068
252279
|
getNextFallback(currentModelId, tier) {
|
|
252069
252280
|
const priority = this.getPriorityList(tier);
|
|
252070
252281
|
const currentIdx = priority.indexOf(currentModelId);
|
|
252071
|
-
if (currentIdx
|
|
252072
|
-
|
|
252073
|
-
|
|
252074
|
-
|
|
252075
|
-
|
|
252282
|
+
if (currentIdx !== -1) {
|
|
252283
|
+
for (let i4 = currentIdx + 1; i4 < priority.length; i4++) {
|
|
252284
|
+
const key = priority[i4];
|
|
252285
|
+
const model = this.availableModels.get(key);
|
|
252286
|
+
if (model && this.isUsable(model)) return model;
|
|
252287
|
+
}
|
|
252288
|
+
}
|
|
252289
|
+
for (const [id, model] of this.availableModels) {
|
|
252290
|
+
if (id !== currentModelId && this.isUsable(model)) return model;
|
|
252076
252291
|
}
|
|
252077
252292
|
return null;
|
|
252078
252293
|
}
|
|
@@ -252148,7 +252363,7 @@ var ModelSelector = class {
|
|
|
252148
252363
|
if (overrideModelId.includes(":")) {
|
|
252149
252364
|
const parts = overrideModelId.split(":");
|
|
252150
252365
|
const prefix = parts[0].toLowerCase();
|
|
252151
|
-
const validProviders = ["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama"];
|
|
252366
|
+
const validProviders = ["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama", "github-models"];
|
|
252152
252367
|
if (validProviders.includes(prefix)) {
|
|
252153
252368
|
providerStr = prefix;
|
|
252154
252369
|
actualId = parts.slice(1).join(":");
|
|
@@ -252168,6 +252383,31 @@ var ModelSelector = class {
|
|
|
252168
252383
|
else if (this.availableProviders.has("openai-compatible")) providerStr = "openai-compatible";
|
|
252169
252384
|
else if (this.availableProviders.size === 1) providerStr = Array.from(this.availableProviders)[0];
|
|
252170
252385
|
}
|
|
252386
|
+
if (providerStr === "github-models" && this.availableProviders.has("github-models")) {
|
|
252387
|
+
const dynamicModel = {
|
|
252388
|
+
id: actualId,
|
|
252389
|
+
name: actualId,
|
|
252390
|
+
provider: "github-models",
|
|
252391
|
+
contextWindow: GITHUB_MODELS_MAX_INPUT_TOKENS,
|
|
252392
|
+
isVisionCapable: false,
|
|
252393
|
+
inputCostPer1kTokens: 0,
|
|
252394
|
+
outputCostPer1kTokens: 0,
|
|
252395
|
+
pricingUnknown: false,
|
|
252396
|
+
maxOutputTokens: GITHUB_MODELS_MAX_OUTPUT_TOKENS,
|
|
252397
|
+
supportsStreaming: true,
|
|
252398
|
+
// Unconfirmed (no live catalog entry to read a real capability from)
|
|
252399
|
+
// must default to false, not left undefined: t3-worker.ts's text-tool
|
|
252400
|
+
// fallback only engages on a strict `=== false`, so an undefined
|
|
252401
|
+
// value here sends native `tools` to an unverified multi-vendor
|
|
252402
|
+
// model and any that don't support function calling reject the
|
|
252403
|
+
// request outright. Same default-to-false-when-unconfirmed policy
|
|
252404
|
+
// listModels() already applies for a real catalog entry.
|
|
252405
|
+
supportsToolUse: false,
|
|
252406
|
+
isLocal: false
|
|
252407
|
+
};
|
|
252408
|
+
this.addDynamicModel(dynamicModel);
|
|
252409
|
+
return dynamicModel;
|
|
252410
|
+
}
|
|
252171
252411
|
if (providerStr && this.availableProviders.has(providerStr)) {
|
|
252172
252412
|
const dynamicModel = withResolvedPricing({
|
|
252173
252413
|
id: actualId,
|
|
@@ -252260,7 +252500,20 @@ var DEFAULT_PROVIDER_TPM = {
|
|
|
252260
252500
|
gemini: 6e4,
|
|
252261
252501
|
azure: 3e4,
|
|
252262
252502
|
"openai-compatible": 3e4,
|
|
252263
|
-
ollama: Number.POSITIVE_INFINITY
|
|
252503
|
+
ollama: Number.POSITIVE_INFINITY,
|
|
252504
|
+
// An order of magnitude below the others, on purpose. GitHub Models is
|
|
252505
|
+
// rate-limited by REQUESTS (Free ≈10 RPM, Pro ≈20, plus low daily caps), and
|
|
252506
|
+
// this bucket meters TOKENS — there is no request-count limiter in this
|
|
252507
|
+
// codebase, and building one is not in scope. The approximation works because
|
|
252508
|
+
// the router reserves `model.maxOutputTokens + 512` per call, and the
|
|
252509
|
+
// provider reports GitHub's real ~4K per-request cap rather than the
|
|
252510
|
+
// underlying model's larger one — so every call withdraws a near-constant
|
|
252511
|
+
// ~4.5K regardless of how short the reply actually turns out to be. At 8K/min
|
|
252512
|
+
// that lands around 1–2 calls per minute of headroom: comfortably inside even
|
|
252513
|
+
// the Free tier, with room for the daily cap to matter more than this does.
|
|
252514
|
+
// Users on a higher tier can raise it with no code change through the generic
|
|
252515
|
+
// `config.rateLimits.providerTpm['github-models']` override.
|
|
252516
|
+
"github-models": 8e3
|
|
252264
252517
|
};
|
|
252265
252518
|
var TpmLimiter = class {
|
|
252266
252519
|
buckets = /* @__PURE__ */ new Map();
|
|
@@ -252616,6 +252869,7 @@ var ModelProfiler = class {
|
|
|
252616
252869
|
(m3) => !alreadyProfiled.has(m3.id) && !SKIP_PATTERN.test(m3.id) && !SKIP_PATTERN.test(m3.name)
|
|
252617
252870
|
);
|
|
252618
252871
|
if (toProfile.length === 0) return;
|
|
252872
|
+
const probesHitGithubModels = this.router?.getModelForTier("T3")?.provider === "github-models";
|
|
252619
252873
|
const openRouterModels = await fetchOpenRouterModels();
|
|
252620
252874
|
const orByNormalizedId = /* @__PURE__ */ new Map();
|
|
252621
252875
|
for (const m3 of openRouterModels) {
|
|
@@ -252630,7 +252884,7 @@ var ModelProfiler = class {
|
|
|
252630
252884
|
if (orMatch?.description) {
|
|
252631
252885
|
specializations = extractSpecializations(orMatch.description);
|
|
252632
252886
|
}
|
|
252633
|
-
if (specializations.length === 0 && this.router) {
|
|
252887
|
+
if (specializations.length === 0 && this.router && !probesHitGithubModels) {
|
|
252634
252888
|
specializations = await queryModelDirectly(this.router);
|
|
252635
252889
|
}
|
|
252636
252890
|
this.store.saveModelProfile(model.id, model.provider, specializations);
|
|
@@ -252850,6 +253104,7 @@ var LiveDataProvider = class {
|
|
|
252850
253104
|
*/
|
|
252851
253105
|
applyLivePricing(models) {
|
|
252852
253106
|
return models.map((m3) => {
|
|
253107
|
+
if (m3.provider === "github-models") return m3;
|
|
252853
253108
|
const p3 = this.getLivePrice(m3.baseModelId ?? m3.id);
|
|
252854
253109
|
if (!p3) return m3;
|
|
252855
253110
|
const reconciled = reconcilePrice(m3, p3);
|
|
@@ -252888,6 +253143,7 @@ var LiveDataProvider = class {
|
|
|
252888
253143
|
*/
|
|
252889
253144
|
applyLiveCapabilities(models) {
|
|
252890
253145
|
return models.map((m3) => {
|
|
253146
|
+
if (m3.provider === "github-models") return m3;
|
|
252891
253147
|
const c4 = this.getCapability(m3.baseModelId ?? m3.id);
|
|
252892
253148
|
if (!c4) return m3;
|
|
252893
253149
|
const next = { ...m3 };
|
|
@@ -253191,13 +253447,16 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
253191
253447
|
for (const model of azureDeployments) this.selector.addDynamicModel(model);
|
|
253192
253448
|
availableProviders.add("azure");
|
|
253193
253449
|
}
|
|
253450
|
+
if (availableProviders.has("github-models")) {
|
|
253451
|
+
await this.discoverGitHubModelsCatalog(config2);
|
|
253452
|
+
}
|
|
253194
253453
|
await this.validateCloudProviderModels(config2);
|
|
253195
253454
|
for (const tier of ["T1", "T2", "T3"]) {
|
|
253196
253455
|
const override = tier === "T1" ? config2.models.t1 : tier === "T2" ? config2.models.t2 : config2.models.t3;
|
|
253197
253456
|
if (!override || override === "auto") continue;
|
|
253198
253457
|
const model = this.selector.selectForTier(tier, override);
|
|
253199
253458
|
if (!model) {
|
|
253200
|
-
const knownProviders = ["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama"];
|
|
253459
|
+
const knownProviders = ["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama", "github-models"];
|
|
253201
253460
|
const hasProviderPrefix = override.includes(":") && knownProviders.some((p3) => override.startsWith(p3 + ":"));
|
|
253202
253461
|
if (hasProviderPrefix) {
|
|
253203
253462
|
const provider = override.split(":")[0];
|
|
@@ -253352,7 +253611,7 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
253352
253611
|
* models without a package upgrade. Mirrors discoverOllamaModels.
|
|
253353
253612
|
*/
|
|
253354
253613
|
async discoverProviderModels() {
|
|
253355
|
-
const cloud = ["anthropic", "openai", "gemini", "azure", "openai-compatible"];
|
|
253614
|
+
const cloud = ["anthropic", "openai", "gemini", "azure", "openai-compatible", "github-models"];
|
|
253356
253615
|
const tasks = cloud.map(async (type) => {
|
|
253357
253616
|
if (!this.selector.isProviderAvailable(type)) return;
|
|
253358
253617
|
const seed = this.getAnyModelForProvider(type);
|
|
@@ -253407,6 +253666,9 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
253407
253666
|
this.ensureProvider(options.model, this.config.providers);
|
|
253408
253667
|
}
|
|
253409
253668
|
let model = requireVision ? this.selector.selectVisionModel() : options.model ?? this.tierModels.get(tier) ?? this.selector.selectForTier(tier) ?? void 0;
|
|
253669
|
+
if (requireVision && model) {
|
|
253670
|
+
this.ensureProvider(model, this.config.providers);
|
|
253671
|
+
}
|
|
253410
253672
|
if (options.forceLocal && model && !this.isPrivateModel(model)) {
|
|
253411
253673
|
const localModel = this.selector.getAllAvailableModels().find((m3) => this.isPrivateModel(m3));
|
|
253412
253674
|
if (!localModel) {
|
|
@@ -253420,7 +253682,8 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
253420
253682
|
if (!model) throw new Error(`No model available for tier ${tier}`);
|
|
253421
253683
|
const provider = this.getProvider(model);
|
|
253422
253684
|
if (!provider) throw new Error(`No provider for model ${model.id}`);
|
|
253423
|
-
const
|
|
253685
|
+
const requestedTokens = options.maxTokens ?? model.maxOutputTokens ?? 1024;
|
|
253686
|
+
const estimatedTokens = Math.min(requestedTokens, model.maxOutputTokens ?? requestedTokens) + 512;
|
|
253424
253687
|
if (this.tpmLimiter) {
|
|
253425
253688
|
await this.tpmLimiter.acquire(model.provider, estimatedTokens);
|
|
253426
253689
|
}
|
|
@@ -253511,7 +253774,8 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
253511
253774
|
});
|
|
253512
253775
|
releaseLocalSlot?.();
|
|
253513
253776
|
releaseLocalSlot = void 0;
|
|
253514
|
-
|
|
253777
|
+
const retryOpts = options.model && options.model.id === model.id ? { ...options, model: void 0 } : options;
|
|
253778
|
+
return this.generate(tier, retryOpts, onChunk, requireVision);
|
|
253515
253779
|
}
|
|
253516
253780
|
}
|
|
253517
253781
|
if (isModelNotFoundError(errMsg)) {
|
|
@@ -253862,6 +254126,26 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
253862
254126
|
return false;
|
|
253863
254127
|
}
|
|
253864
254128
|
}
|
|
254129
|
+
/**
|
|
254130
|
+
* Registers GitHub Models' real catalog ids via addDynamicModel so init()'s
|
|
254131
|
+
* tier-fill loop can pick one. Registration alone does not create a bound
|
|
254132
|
+
* BaseProvider instance (see ensureProvider) — the tier-fill loop's own
|
|
254133
|
+
* ensureProvider call does that for whichever model it actually selects,
|
|
254134
|
+
* which is why this runs BEFORE that loop rather than only in the
|
|
254135
|
+
* background discoverProviderModels() refresh.
|
|
254136
|
+
*/
|
|
254137
|
+
async discoverGitHubModelsCatalog(config2) {
|
|
254138
|
+
const cfg = config2.providers.find((p3) => p3.type === "github-models") ?? { type: "github-models" };
|
|
254139
|
+
const seed = this.getAnyModelForProvider("github-models");
|
|
254140
|
+
if (!seed) return;
|
|
254141
|
+
try {
|
|
254142
|
+
const provider = new GitHubModelsProvider(cfg, seed);
|
|
254143
|
+
const models = await provider.listModels();
|
|
254144
|
+
for (const m3 of models) this.selector.addDynamicModel(m3);
|
|
254145
|
+
} catch (err) {
|
|
254146
|
+
console.warn("[router] GitHub Models catalog discovery failed:", err instanceof Error ? err.message : err);
|
|
254147
|
+
}
|
|
254148
|
+
}
|
|
253865
254149
|
ensureProvider(model, configs) {
|
|
253866
254150
|
const key = `${model.provider}:${model.id}`;
|
|
253867
254151
|
if (this.providers.has(key)) return;
|
|
@@ -253886,6 +254170,8 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
253886
254170
|
return new OllamaProvider(cfg, model);
|
|
253887
254171
|
case "openai-compatible":
|
|
253888
254172
|
return new OpenAICompatibleProvider(cfg, model);
|
|
254173
|
+
case "github-models":
|
|
254174
|
+
return new GitHubModelsProvider(cfg, model);
|
|
253889
254175
|
default:
|
|
253890
254176
|
throw new Error(`Unsupported provider type: ${String(cfg.type)}`);
|
|
253891
254177
|
}
|
|
@@ -253893,7 +254179,7 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
253893
254179
|
getAnyModelForProvider(type) {
|
|
253894
254180
|
const fromCatalog = Object.values(MODELS).find((m3) => m3.provider === type);
|
|
253895
254181
|
if (fromCatalog) return fromCatalog;
|
|
253896
|
-
if (type === "openai-compatible" || type === "azure") {
|
|
254182
|
+
if (type === "openai-compatible" || type === "azure" || type === "github-models") {
|
|
253897
254183
|
return {
|
|
253898
254184
|
id: type,
|
|
253899
254185
|
name: type,
|
|
@@ -257577,9 +257863,17 @@ var CAPABILITIES = [
|
|
|
257577
257863
|
// Long-running: submit returns an operation name, the video lands minutes
|
|
257578
257864
|
// later, and the bytes come from a short-lived signed URL. generate.ts owns
|
|
257579
257865
|
// that polling loop; the registry only records the shape via `api`.
|
|
257866
|
+
//
|
|
257867
|
+
// `veo-3.1-generate-001` (no "-preview") is VERTEX AI's id for this model,
|
|
257868
|
+
// not the Gemini Developer API's — this file calls
|
|
257869
|
+
// generativelanguage.googleapis.com (see generate.ts), which 404s on it:
|
|
257870
|
+
// "models/veo-3.1-generate-001 is not found for API version v1beta". On the
|
|
257871
|
+
// Gemini API specifically, Veo 3.1 is still preview-only, so the correct id
|
|
257872
|
+
// there is `veo-3.1-generate-preview`.
|
|
257873
|
+
// https://ai.google.dev/gemini-api/docs/veo
|
|
257580
257874
|
modality: "video",
|
|
257581
257875
|
provider: "gemini",
|
|
257582
|
-
modelId: "veo-3.1-generate-
|
|
257876
|
+
modelId: "veo-3.1-generate-preview",
|
|
257583
257877
|
api: "gemini-predict-lro"
|
|
257584
257878
|
}
|
|
257585
257879
|
];
|
|
@@ -300988,7 +301282,7 @@ var PermissionEscalator = class extends EventEmitter__default.default {
|
|
|
300988
301282
|
|
|
300989
301283
|
// src/config/schema.ts
|
|
300990
301284
|
var ProviderConfigSchema = external_exports.object({
|
|
300991
|
-
type: external_exports.enum(["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama"]),
|
|
301285
|
+
type: external_exports.enum(["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama", "github-models"]),
|
|
300992
301286
|
label: external_exports.string().optional(),
|
|
300993
301287
|
apiKey: external_exports.string().optional(),
|
|
300994
301288
|
baseUrl: external_exports.string().url().optional(),
|
|
@@ -301168,6 +301462,18 @@ var CascadeConfigSchema = external_exports.object({
|
|
|
301168
301462
|
budget: BudgetConfigSchema.default({}),
|
|
301169
301463
|
theme: external_exports.string().default("cascade"),
|
|
301170
301464
|
workspace: WorkspaceConfigSchema.default({}),
|
|
301465
|
+
/**
|
|
301466
|
+
* Per-provider tokens-per-minute overrides for the router's TpmLimiter (see
|
|
301467
|
+
* DEFAULT_PROVIDER_TPM in core/router/tpm-limiter.ts). Documented as a raw
|
|
301468
|
+
* config field for a while before this — since CascadeConfigSchema strips
|
|
301469
|
+
* unknown keys, a config file setting it was silently discarded and the
|
|
301470
|
+
* override never reached TpmLimiter's constructor. Optional/permissive on
|
|
301471
|
+
* provider name (not a ProviderType enum) so a config still validates
|
|
301472
|
+
* against a slightly newer/older provider list than this schema knows.
|
|
301473
|
+
*/
|
|
301474
|
+
rateLimits: external_exports.object({
|
|
301475
|
+
providerTpm: external_exports.record(external_exports.string(), external_exports.number().positive()).optional()
|
|
301476
|
+
}).optional(),
|
|
301171
301477
|
/**
|
|
301172
301478
|
* Cascade Auto: when true, the TaskAnalyzer selects the optimal model for each
|
|
301173
301479
|
* tier based on task type and complexity, overriding the static priority lists.
|
|
@@ -306212,7 +306518,14 @@ var ConfigManager = class {
|
|
|
306212
306518
|
anthropic: "ANTHROPIC_API_KEY",
|
|
306213
306519
|
openai: "OPENAI_API_KEY",
|
|
306214
306520
|
gemini: "GOOGLE_API_KEY",
|
|
306215
|
-
azure: "AZURE_OPENAI_KEY"
|
|
306521
|
+
azure: "AZURE_OPENAI_KEY",
|
|
306522
|
+
// Deliberately NOT `GITHUB_TOKEN`: that one is injected automatically by
|
|
306523
|
+
// GitHub Actions and is commonly exported in developer shells for `gh`
|
|
306524
|
+
// and git auth, and it will essentially never carry the fine-grained
|
|
306525
|
+
// `models: read` scope this provider needs. Adopting it would wire up a
|
|
306526
|
+
// provider that 403s on its first real call — a confusing failure for
|
|
306527
|
+
// something that is meant to be explicit opt-in BYOK.
|
|
306528
|
+
"github-models": "GITHUB_MODELS_TOKEN"
|
|
306216
306529
|
};
|
|
306217
306530
|
const envKey = envMap[provider];
|
|
306218
306531
|
if (envKey && process.env[envKey]) return process.env[envKey];
|
|
@@ -306241,7 +306554,10 @@ var ConfigManager = class {
|
|
|
306241
306554
|
{ env: "ANTHROPIC_API_KEY", type: "anthropic" },
|
|
306242
306555
|
{ env: "OPENAI_API_KEY", type: "openai" },
|
|
306243
306556
|
{ env: "GOOGLE_API_KEY", type: "gemini" },
|
|
306244
|
-
{ env: "AZURE_OPENAI_KEY", type: "azure" }
|
|
306557
|
+
{ env: "AZURE_OPENAI_KEY", type: "azure" },
|
|
306558
|
+
// See getApiKey() above for why this is GITHUB_MODELS_TOKEN and never
|
|
306559
|
+
// the ambient GITHUB_TOKEN.
|
|
306560
|
+
{ env: "GITHUB_MODELS_TOKEN", type: "github-models" }
|
|
306245
306561
|
];
|
|
306246
306562
|
for (const { env, type } of envProviders) {
|
|
306247
306563
|
const key = process.env[env];
|
|
@@ -310128,6 +310444,9 @@ exports.DEFAULT_RETENTION_DAYS = DEFAULT_RETENTION_DAYS;
|
|
|
310128
310444
|
exports.DEFAULT_THEME = DEFAULT_THEME;
|
|
310129
310445
|
exports.DashboardServer = DashboardServer;
|
|
310130
310446
|
exports.FileMcpOAuthStore = FileMcpOAuthStore;
|
|
310447
|
+
exports.GITHUB_MODELS_API_VERSION = GITHUB_MODELS_API_VERSION;
|
|
310448
|
+
exports.GITHUB_MODELS_CATALOG_URL = GITHUB_MODELS_CATALOG_URL;
|
|
310449
|
+
exports.GITHUB_MODELS_INFERENCE_URL = GITHUB_MODELS_INFERENCE_URL;
|
|
310131
310450
|
exports.GLOBAL_CONFIG_DIR = GLOBAL_CONFIG_DIR;
|
|
310132
310451
|
exports.GLOBAL_CREDENTIALS_FILE = GLOBAL_CREDENTIALS_FILE;
|
|
310133
310452
|
exports.GLOBAL_DB_FILE = GLOBAL_DB_FILE;
|