cascade-ai 0.64.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 +364 -26
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +364 -26
- package/dist/cli.js.map +1 -1
- package/dist/desktop-core.cjs +331 -20
- package/dist/index.cjs +331 -20
- 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 +329 -21
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -1806,11 +1806,11 @@ var init_pricing = __esm({
|
|
|
1806
1806
|
});
|
|
1807
1807
|
|
|
1808
1808
|
// src/constants.ts
|
|
1809
|
-
var CASCADE_VERSION, CASCADE_CONFIG_FILE, CASCADE_DB_FILE, CASCADE_DASHBOARD_SECRET_FILE, GLOBAL_CONFIG_DIR, GLOBAL_DB_FILE, GLOBAL_KEYSTORE_FILE, GLOBAL_CREDENTIALS_FILE, GLOBAL_RUNTIME_DB_FILE, DEFAULT_DASHBOARD_PORT, DEFAULT_CONTEXT_LIMIT, DEFAULT_AUTO_SUMMARIZE_AT, MODELS, T1_MODEL_PRIORITY, T2_MODEL_PRIORITY, T3_MODEL_PRIORITY, VISION_MODEL_PRIORITY, COMPLEXITY_T2_COUNT, THEME_NAMES, DEFAULT_THEME, OLLAMA_BASE_URL, LM_STUDIO_BASE_URL, AZURE_BASE_URL_TEMPLATE, TOOL_NAMES, DEFAULT_APPROVAL_REQUIRED;
|
|
1809
|
+
var CASCADE_VERSION, CASCADE_CONFIG_FILE, CASCADE_DB_FILE, CASCADE_DASHBOARD_SECRET_FILE, GLOBAL_CONFIG_DIR, GLOBAL_DB_FILE, GLOBAL_KEYSTORE_FILE, GLOBAL_CREDENTIALS_FILE, GLOBAL_RUNTIME_DB_FILE, DEFAULT_DASHBOARD_PORT, DEFAULT_CONTEXT_LIMIT, DEFAULT_AUTO_SUMMARIZE_AT, MODELS, T1_MODEL_PRIORITY, T2_MODEL_PRIORITY, T3_MODEL_PRIORITY, VISION_MODEL_PRIORITY, COMPLEXITY_T2_COUNT, THEME_NAMES, DEFAULT_THEME, OLLAMA_BASE_URL, LM_STUDIO_BASE_URL, AZURE_BASE_URL_TEMPLATE, GITHUB_MODELS_INFERENCE_URL, GITHUB_MODELS_CATALOG_URL, GITHUB_MODELS_API_VERSION, TOOL_NAMES, DEFAULT_APPROVAL_REQUIRED;
|
|
1810
1810
|
var init_constants = __esm({
|
|
1811
1811
|
"src/constants.ts"() {
|
|
1812
1812
|
init_pricing();
|
|
1813
|
-
CASCADE_VERSION = "0.
|
|
1813
|
+
CASCADE_VERSION = "0.65.0";
|
|
1814
1814
|
CASCADE_CONFIG_FILE = ".cascade/config.json";
|
|
1815
1815
|
CASCADE_DB_FILE = ".cascade/memory.db";
|
|
1816
1816
|
CASCADE_DASHBOARD_SECRET_FILE = ".cascade/dashboard-secret";
|
|
@@ -2209,6 +2209,9 @@ var init_constants = __esm({
|
|
|
2209
2209
|
OLLAMA_BASE_URL = "http://localhost:11434";
|
|
2210
2210
|
LM_STUDIO_BASE_URL = "http://localhost:1234";
|
|
2211
2211
|
AZURE_BASE_URL_TEMPLATE = "https://{resource}.openai.azure.com";
|
|
2212
|
+
GITHUB_MODELS_INFERENCE_URL = "https://models.github.ai/inference";
|
|
2213
|
+
GITHUB_MODELS_CATALOG_URL = "https://models.github.ai/catalog/models";
|
|
2214
|
+
GITHUB_MODELS_API_VERSION = "2026-03-10";
|
|
2212
2215
|
TOOL_NAMES = {
|
|
2213
2216
|
SHELL: "shell",
|
|
2214
2217
|
FILE_READ: "file_read",
|
|
@@ -3290,7 +3293,7 @@ var init_gemini = __esm({
|
|
|
3290
3293
|
function preferIpv4Host(url) {
|
|
3291
3294
|
return url;
|
|
3292
3295
|
}
|
|
3293
|
-
async function nodeHttpFetch(input, init = {}, redirectCount = 0) {
|
|
3296
|
+
async function nodeHttpFetch(input, init = {}, redirectCount = 0, opts = {}) {
|
|
3294
3297
|
const urlStr = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
3295
3298
|
const u = new URL(preferIpv4Host(urlStr) ?? urlStr);
|
|
3296
3299
|
const lib = u.protocol === "https:" ? https__default.default : http__default.default;
|
|
@@ -3321,9 +3324,13 @@ async function nodeHttpFetch(input, init = {}, redirectCount = 0) {
|
|
|
3321
3324
|
if (status >= 300 && status < 400 && location && redirectCount < MAX_REDIRECTS) {
|
|
3322
3325
|
res.resume();
|
|
3323
3326
|
const nextUrl = new URL(location, u).href;
|
|
3327
|
+
if (opts.allowedRedirectOrigin && new URL(nextUrl).origin !== opts.allowedRedirectOrigin) {
|
|
3328
|
+
reject(new Error(`Refusing cross-origin redirect to ${new URL(nextUrl).origin}`));
|
|
3329
|
+
return;
|
|
3330
|
+
}
|
|
3324
3331
|
const downgrade = status === 303 || (status === 301 || status === 302) && method !== "GET" && method !== "HEAD";
|
|
3325
3332
|
const nextInit = downgrade ? { ...init, method: "GET", body: void 0 } : init;
|
|
3326
|
-
resolve2(nodeHttpFetch(nextUrl, nextInit, redirectCount + 1));
|
|
3333
|
+
resolve2(nodeHttpFetch(nextUrl, nextInit, redirectCount + 1, opts));
|
|
3327
3334
|
return;
|
|
3328
3335
|
}
|
|
3329
3336
|
const encoding = (res.headers["content-encoding"] ?? "").toLowerCase();
|
|
@@ -3362,6 +3369,222 @@ var init_net = __esm({
|
|
|
3362
3369
|
}
|
|
3363
3370
|
});
|
|
3364
3371
|
|
|
3372
|
+
// src/providers/github-models.ts
|
|
3373
|
+
var github_models_exports = {};
|
|
3374
|
+
__export(github_models_exports, {
|
|
3375
|
+
GITHUB_MODELS_MAX_INPUT_TOKENS: () => GITHUB_MODELS_MAX_INPUT_TOKENS,
|
|
3376
|
+
GITHUB_MODELS_MAX_OUTPUT_TOKENS: () => GITHUB_MODELS_MAX_OUTPUT_TOKENS,
|
|
3377
|
+
GitHubModelsProvider: () => GitHubModelsProvider,
|
|
3378
|
+
stripModelOwnerPrefix: () => stripModelOwnerPrefix
|
|
3379
|
+
});
|
|
3380
|
+
function stripModelOwnerPrefix(id) {
|
|
3381
|
+
const slash = (id || "").indexOf("/");
|
|
3382
|
+
return slash === -1 ? id : id.slice(slash + 1);
|
|
3383
|
+
}
|
|
3384
|
+
function pickString(o, keys) {
|
|
3385
|
+
for (const k of keys) {
|
|
3386
|
+
const v = o[k];
|
|
3387
|
+
if (typeof v === "string" && v.length > 0) return v;
|
|
3388
|
+
}
|
|
3389
|
+
return void 0;
|
|
3390
|
+
}
|
|
3391
|
+
function pickNumber(o, keys) {
|
|
3392
|
+
for (const k of keys) {
|
|
3393
|
+
const v = k.includes(".") ? o[k.split(".")[0]]?.[k.split(".")[1]] : o[k];
|
|
3394
|
+
if (typeof v === "number" && Number.isFinite(v) && v > 0) return v;
|
|
3395
|
+
}
|
|
3396
|
+
return void 0;
|
|
3397
|
+
}
|
|
3398
|
+
function looksVisionCapable(o) {
|
|
3399
|
+
for (const k of ["supported_input_modalities", "input_modalities", "modalities", "capabilities"]) {
|
|
3400
|
+
const v = o[k];
|
|
3401
|
+
if (Array.isArray(v) && v.some((x) => typeof x === "string" && /image|vision/i.test(x))) return true;
|
|
3402
|
+
}
|
|
3403
|
+
return false;
|
|
3404
|
+
}
|
|
3405
|
+
function looksToolCapable(o) {
|
|
3406
|
+
const params = o["supported_parameters"];
|
|
3407
|
+
if (Array.isArray(params) && params.some((x) => typeof x === "string" && /^tools?$|function.?call/i.test(x))) {
|
|
3408
|
+
return true;
|
|
3409
|
+
}
|
|
3410
|
+
for (const k of ["capabilities", "supported_output_modalities"]) {
|
|
3411
|
+
const v = o[k];
|
|
3412
|
+
if (Array.isArray(v) && v.some((x) => typeof x === "string" && /tool|function.?call/i.test(x))) return true;
|
|
3413
|
+
}
|
|
3414
|
+
const caps = o["capabilities"];
|
|
3415
|
+
if (caps && typeof caps === "object" && !Array.isArray(caps)) {
|
|
3416
|
+
const c = caps;
|
|
3417
|
+
if (c["function_calling"] === true || c["tool_calling"] === true || c["tools"] === true) return true;
|
|
3418
|
+
}
|
|
3419
|
+
return false;
|
|
3420
|
+
}
|
|
3421
|
+
function parseNextLink(linkHeader) {
|
|
3422
|
+
if (!linkHeader) return null;
|
|
3423
|
+
for (const part of linkHeader.split(",")) {
|
|
3424
|
+
const match = part.match(/<([^>]+)>\s*;\s*rel="next"/);
|
|
3425
|
+
if (match) return match[1];
|
|
3426
|
+
}
|
|
3427
|
+
return null;
|
|
3428
|
+
}
|
|
3429
|
+
var GITHUB_MODELS_MAX_OUTPUT_TOKENS, GITHUB_MODELS_MAX_INPUT_TOKENS, DEFAULT_CONTEXT_WINDOW, GITHUB_MODELS_MAX_CATALOG_PAGES, GITHUB_MODELS_CATALOG_ORIGIN, GitHubModelsProvider;
|
|
3430
|
+
var init_github_models = __esm({
|
|
3431
|
+
"src/providers/github-models.ts"() {
|
|
3432
|
+
init_constants();
|
|
3433
|
+
init_openai();
|
|
3434
|
+
init_model_filter();
|
|
3435
|
+
init_net();
|
|
3436
|
+
GITHUB_MODELS_MAX_OUTPUT_TOKENS = 4e3;
|
|
3437
|
+
GITHUB_MODELS_MAX_INPUT_TOKENS = 8e3;
|
|
3438
|
+
DEFAULT_CONTEXT_WINDOW = 128e3;
|
|
3439
|
+
GITHUB_MODELS_MAX_CATALOG_PAGES = 20;
|
|
3440
|
+
GITHUB_MODELS_CATALOG_ORIGIN = new URL(GITHUB_MODELS_CATALOG_URL).origin;
|
|
3441
|
+
GitHubModelsProvider = class extends OpenAIProvider {
|
|
3442
|
+
constructor(config, model) {
|
|
3443
|
+
super({ ...config, apiKey: config.apiKey ?? "" }, model);
|
|
3444
|
+
this.useMaxCompletionTokens = isReasoningModel(stripModelOwnerPrefix(model.id));
|
|
3445
|
+
this.client = new OpenAI__default.default({
|
|
3446
|
+
apiKey: config.apiKey ?? "",
|
|
3447
|
+
baseURL: GITHUB_MODELS_INFERENCE_URL,
|
|
3448
|
+
defaultHeaders: { "X-GitHub-Api-Version": GITHUB_MODELS_API_VERSION, "User-Agent": "Cascade-AI" }
|
|
3449
|
+
});
|
|
3450
|
+
}
|
|
3451
|
+
/**
|
|
3452
|
+
* `OpenAIProvider.generateStream()` builds its request budget as
|
|
3453
|
+
* `options.maxTokens ?? this.model.maxOutputTokens` — an explicit per-call
|
|
3454
|
+
* `maxTokens` (T1Administrator's final compilation asks for 8,000, well
|
|
3455
|
+
* above GitHub's real ~4K hard cap) wins outright and is sent unclamped.
|
|
3456
|
+
* GitHub then rejects the request instead of answering, so a run routed to
|
|
3457
|
+
* this provider for that step fails outright rather than getting a
|
|
3458
|
+
* (perfectly serviceable, just shorter) completion. Clamp here, once, right
|
|
3459
|
+
* before delegating to the inherited implementation, rather than teaching
|
|
3460
|
+
* every caller GitHub's specific cap.
|
|
3461
|
+
*/
|
|
3462
|
+
async generateStream(options, onChunk) {
|
|
3463
|
+
const clamped = options.maxTokens !== void 0 && options.maxTokens > GITHUB_MODELS_MAX_OUTPUT_TOKENS ? { ...options, maxTokens: GITHUB_MODELS_MAX_OUTPUT_TOKENS } : options;
|
|
3464
|
+
return super.generateStream(clamped, onChunk);
|
|
3465
|
+
}
|
|
3466
|
+
/**
|
|
3467
|
+
* The catalog is a GitHub REST resource, not an OpenAI `/models` list, and
|
|
3468
|
+
* wants GitHub's own header trio — the plain `Accept: application/json` the
|
|
3469
|
+
* openai-compatible provider sends is not the same request.
|
|
3470
|
+
*/
|
|
3471
|
+
catalogHeaders() {
|
|
3472
|
+
return {
|
|
3473
|
+
Accept: "application/vnd.github+json",
|
|
3474
|
+
Authorization: `Bearer ${this.config.apiKey ?? ""}`,
|
|
3475
|
+
"X-GitHub-Api-Version": GITHUB_MODELS_API_VERSION,
|
|
3476
|
+
// GitHub's REST API rejects any request with no User-Agent — a plain 403
|
|
3477
|
+
// regardless of how valid the PAT is. nodeHttpFetch is a thin wrapper
|
|
3478
|
+
// over node:http/https, neither of which sets one the way a browser's
|
|
3479
|
+
// fetch or a full HTTP client library would, so this has to be explicit.
|
|
3480
|
+
"User-Agent": "Cascade-AI"
|
|
3481
|
+
};
|
|
3482
|
+
}
|
|
3483
|
+
async listModels() {
|
|
3484
|
+
const raw = [];
|
|
3485
|
+
let url = GITHUB_MODELS_CATALOG_URL;
|
|
3486
|
+
let pages = 0;
|
|
3487
|
+
const visited = /* @__PURE__ */ new Set();
|
|
3488
|
+
while (url && pages < GITHUB_MODELS_MAX_CATALOG_PAGES) {
|
|
3489
|
+
if (visited.has(url)) break;
|
|
3490
|
+
visited.add(url);
|
|
3491
|
+
const res = await nodeHttpFetch(url, { headers: this.catalogHeaders() }, 0, {
|
|
3492
|
+
allowedRedirectOrigin: GITHUB_MODELS_CATALOG_ORIGIN
|
|
3493
|
+
});
|
|
3494
|
+
if (!res.ok) throw new Error(`GitHub Models catalog ${url} returned HTTP ${res.status}`);
|
|
3495
|
+
const body = await res.json();
|
|
3496
|
+
const pageItems = Array.isArray(body) ? body : Array.isArray(body?.models) ? body.models : Array.isArray(body?.data) ? body.data : [];
|
|
3497
|
+
raw.push(...pageItems);
|
|
3498
|
+
const next = parseNextLink(res.headers.get("link"));
|
|
3499
|
+
if (next) {
|
|
3500
|
+
const resolved = new URL(next, url);
|
|
3501
|
+
if (resolved.origin !== GITHUB_MODELS_CATALOG_ORIGIN) {
|
|
3502
|
+
throw new Error(`Refusing cross-origin GitHub Models pagination URL: ${resolved.origin}`);
|
|
3503
|
+
}
|
|
3504
|
+
url = resolved.href;
|
|
3505
|
+
} else {
|
|
3506
|
+
url = null;
|
|
3507
|
+
}
|
|
3508
|
+
pages++;
|
|
3509
|
+
}
|
|
3510
|
+
const entries = raw.map((m) => {
|
|
3511
|
+
if (typeof m === "string") {
|
|
3512
|
+
return { id: m, name: m, contextWindow: DEFAULT_CONTEXT_WINDOW, vision: false, tools: false };
|
|
3513
|
+
}
|
|
3514
|
+
if (!m || typeof m !== "object") return void 0;
|
|
3515
|
+
const o = m;
|
|
3516
|
+
const id = pickString(o, ["id", "name", "model"]);
|
|
3517
|
+
if (!id) return void 0;
|
|
3518
|
+
return {
|
|
3519
|
+
id,
|
|
3520
|
+
name: pickString(o, ["friendly_name", "display_name", "name"]) ?? id,
|
|
3521
|
+
contextWindow: pickNumber(o, [
|
|
3522
|
+
"context_window",
|
|
3523
|
+
"context_length",
|
|
3524
|
+
"max_input_tokens",
|
|
3525
|
+
"limits.max_input_tokens",
|
|
3526
|
+
"limits.max_context_tokens"
|
|
3527
|
+
]) ?? DEFAULT_CONTEXT_WINDOW,
|
|
3528
|
+
vision: looksVisionCapable(o),
|
|
3529
|
+
tools: looksToolCapable(o)
|
|
3530
|
+
};
|
|
3531
|
+
}).filter((e) => e !== void 0);
|
|
3532
|
+
if (entries.length === 0) return [];
|
|
3533
|
+
const chat = entries.filter((e) => isChatModel(e.id));
|
|
3534
|
+
return (chat.length ? chat : entries).map((e) => ({
|
|
3535
|
+
id: e.id,
|
|
3536
|
+
// full `owner/model` — what the API is addressed by
|
|
3537
|
+
name: e.name,
|
|
3538
|
+
provider: "github-models",
|
|
3539
|
+
// GitHub's real per-request input cap, not the base model's larger one
|
|
3540
|
+
// (see GITHUB_MODELS_MAX_INPUT_TOKENS above) — the same reasoning
|
|
3541
|
+
// already applied to maxOutputTokens below, for the input side.
|
|
3542
|
+
contextWindow: Math.min(e.contextWindow, GITHUB_MODELS_MAX_INPUT_TOKENS),
|
|
3543
|
+
isVisionCapable: e.vision,
|
|
3544
|
+
// A REAL $0, not a missing number — usage is bundled into the quota of
|
|
3545
|
+
// the plan the PAT belongs to and is never billed per token, so
|
|
3546
|
+
// `pricingUnknown: false` is the honest value (see the flag's contract in
|
|
3547
|
+
// core/router/pricing.ts: 0 alone is ambiguous). Set here rather than in
|
|
3548
|
+
// pricing-data.json because resolvePricing() matches exactly on
|
|
3549
|
+
// provider|model|region with no wildcard: every catalog model would need
|
|
3550
|
+
// a hand-curated row, and the next one GitHub adds would read as "cost
|
|
3551
|
+
// unknown" — an inconsistent picture for a billing reality that is
|
|
3552
|
+
// identical across the entire catalog.
|
|
3553
|
+
//
|
|
3554
|
+
// `isLocal` is the OTHER way to reach a real $0, and it is the wrong one:
|
|
3555
|
+
// it means "runs on this machine" and carries consequences that are
|
|
3556
|
+
// simply false for a hosted API — the shared concurrency-1
|
|
3557
|
+
// LocalRequestQueue (serialising these calls behind actual Ollama GPU
|
|
3558
|
+
// inference), the 300s local rather than 120s cloud inference timeout,
|
|
3559
|
+
// and a literal "[local]" tag in `cascade models`.
|
|
3560
|
+
inputCostPer1kTokens: 0,
|
|
3561
|
+
outputCostPer1kTokens: 0,
|
|
3562
|
+
pricingUnknown: false,
|
|
3563
|
+
maxOutputTokens: GITHUB_MODELS_MAX_OUTPUT_TOKENS,
|
|
3564
|
+
supportsStreaming: true,
|
|
3565
|
+
// Not every catalog model supports function/tool calling — a hardcoded
|
|
3566
|
+
// `true` sent `tools` to models that reject the parameter outright and
|
|
3567
|
+
// never engaged t3-worker.ts's existing text-tool fallback (only an
|
|
3568
|
+
// explicit `false` does). See looksToolCapable's own comment for why
|
|
3569
|
+
// "not positively advertised" defaults to false rather than true here.
|
|
3570
|
+
supportsToolUse: e.tools,
|
|
3571
|
+
isLocal: false
|
|
3572
|
+
}));
|
|
3573
|
+
}
|
|
3574
|
+
async isAvailable() {
|
|
3575
|
+
try {
|
|
3576
|
+
const res = await nodeHttpFetch(GITHUB_MODELS_CATALOG_URL, { headers: this.catalogHeaders() }, 0, {
|
|
3577
|
+
allowedRedirectOrigin: GITHUB_MODELS_CATALOG_ORIGIN
|
|
3578
|
+
});
|
|
3579
|
+
return res.ok;
|
|
3580
|
+
} catch {
|
|
3581
|
+
return false;
|
|
3582
|
+
}
|
|
3583
|
+
}
|
|
3584
|
+
};
|
|
3585
|
+
}
|
|
3586
|
+
});
|
|
3587
|
+
|
|
3365
3588
|
// src/providers/ollama.ts
|
|
3366
3589
|
var ollama_exports = {};
|
|
3367
3590
|
__export(ollama_exports, {
|
|
@@ -4996,7 +5219,7 @@ Original error: ${err.message}`
|
|
|
4996
5219
|
}
|
|
4997
5220
|
};
|
|
4998
5221
|
var ProviderConfigSchema = zod.z.object({
|
|
4999
|
-
type: zod.z.enum(["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama"]),
|
|
5222
|
+
type: zod.z.enum(["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama", "github-models"]),
|
|
5000
5223
|
label: zod.z.string().optional(),
|
|
5001
5224
|
apiKey: zod.z.string().optional(),
|
|
5002
5225
|
baseUrl: zod.z.string().url().optional(),
|
|
@@ -5176,6 +5399,18 @@ var CascadeConfigSchema = zod.z.object({
|
|
|
5176
5399
|
budget: BudgetConfigSchema.default({}),
|
|
5177
5400
|
theme: zod.z.string().default("cascade"),
|
|
5178
5401
|
workspace: WorkspaceConfigSchema.default({}),
|
|
5402
|
+
/**
|
|
5403
|
+
* Per-provider tokens-per-minute overrides for the router's TpmLimiter (see
|
|
5404
|
+
* DEFAULT_PROVIDER_TPM in core/router/tpm-limiter.ts). Documented as a raw
|
|
5405
|
+
* config field for a while before this — since CascadeConfigSchema strips
|
|
5406
|
+
* unknown keys, a config file setting it was silently discarded and the
|
|
5407
|
+
* override never reached TpmLimiter's constructor. Optional/permissive on
|
|
5408
|
+
* provider name (not a ProviderType enum) so a config still validates
|
|
5409
|
+
* against a slightly newer/older provider list than this schema knows.
|
|
5410
|
+
*/
|
|
5411
|
+
rateLimits: zod.z.object({
|
|
5412
|
+
providerTpm: zod.z.record(zod.z.string(), zod.z.number().positive()).optional()
|
|
5413
|
+
}).optional(),
|
|
5179
5414
|
/**
|
|
5180
5415
|
* Cascade Auto: when true, the TaskAnalyzer selects the optimal model for each
|
|
5181
5416
|
* tier based on task type and complexity, overriding the static priority lists.
|
|
@@ -5707,7 +5942,14 @@ var ConfigManager = class {
|
|
|
5707
5942
|
anthropic: "ANTHROPIC_API_KEY",
|
|
5708
5943
|
openai: "OPENAI_API_KEY",
|
|
5709
5944
|
gemini: "GOOGLE_API_KEY",
|
|
5710
|
-
azure: "AZURE_OPENAI_KEY"
|
|
5945
|
+
azure: "AZURE_OPENAI_KEY",
|
|
5946
|
+
// Deliberately NOT `GITHUB_TOKEN`: that one is injected automatically by
|
|
5947
|
+
// GitHub Actions and is commonly exported in developer shells for `gh`
|
|
5948
|
+
// and git auth, and it will essentially never carry the fine-grained
|
|
5949
|
+
// `models: read` scope this provider needs. Adopting it would wire up a
|
|
5950
|
+
// provider that 403s on its first real call — a confusing failure for
|
|
5951
|
+
// something that is meant to be explicit opt-in BYOK.
|
|
5952
|
+
"github-models": "GITHUB_MODELS_TOKEN"
|
|
5711
5953
|
};
|
|
5712
5954
|
const envKey = envMap[provider];
|
|
5713
5955
|
if (envKey && process.env[envKey]) return process.env[envKey];
|
|
@@ -5736,7 +5978,10 @@ var ConfigManager = class {
|
|
|
5736
5978
|
{ env: "ANTHROPIC_API_KEY", type: "anthropic" },
|
|
5737
5979
|
{ env: "OPENAI_API_KEY", type: "openai" },
|
|
5738
5980
|
{ env: "GOOGLE_API_KEY", type: "gemini" },
|
|
5739
|
-
{ env: "AZURE_OPENAI_KEY", type: "azure" }
|
|
5981
|
+
{ env: "AZURE_OPENAI_KEY", type: "azure" },
|
|
5982
|
+
// See getApiKey() above for why this is GITHUB_MODELS_TOKEN and never
|
|
5983
|
+
// the ambient GITHUB_TOKEN.
|
|
5984
|
+
{ env: "GITHUB_MODELS_TOKEN", type: "github-models" }
|
|
5740
5985
|
];
|
|
5741
5986
|
for (const { env, type } of envProviders) {
|
|
5742
5987
|
const key = process.env[env];
|
|
@@ -6118,6 +6363,7 @@ init_constants();
|
|
|
6118
6363
|
init_anthropic();
|
|
6119
6364
|
init_azure();
|
|
6120
6365
|
init_gemini();
|
|
6366
|
+
init_github_models();
|
|
6121
6367
|
init_ollama();
|
|
6122
6368
|
init_openai_compatible();
|
|
6123
6369
|
init_openai();
|
|
@@ -6125,6 +6371,7 @@ init_openai();
|
|
|
6125
6371
|
// src/core/router/selector.ts
|
|
6126
6372
|
init_constants();
|
|
6127
6373
|
init_pricing();
|
|
6374
|
+
init_github_models();
|
|
6128
6375
|
function normalizeModelId(id) {
|
|
6129
6376
|
return id.replace(/^models\//, "").toLowerCase();
|
|
6130
6377
|
}
|
|
@@ -6234,16 +6481,23 @@ var ModelSelector = class {
|
|
|
6234
6481
|
return model;
|
|
6235
6482
|
}
|
|
6236
6483
|
}
|
|
6484
|
+
for (const model of this.availableModels.values()) {
|
|
6485
|
+
if (this.availableProviders.has(model.provider) && model.isVisionCapable) return model;
|
|
6486
|
+
}
|
|
6237
6487
|
return null;
|
|
6238
6488
|
}
|
|
6239
6489
|
getNextFallback(currentModelId, tier) {
|
|
6240
6490
|
const priority = this.getPriorityList(tier);
|
|
6241
6491
|
const currentIdx = priority.indexOf(currentModelId);
|
|
6242
|
-
if (currentIdx
|
|
6243
|
-
|
|
6244
|
-
|
|
6245
|
-
|
|
6246
|
-
|
|
6492
|
+
if (currentIdx !== -1) {
|
|
6493
|
+
for (let i = currentIdx + 1; i < priority.length; i++) {
|
|
6494
|
+
const key = priority[i];
|
|
6495
|
+
const model = this.availableModels.get(key);
|
|
6496
|
+
if (model && this.isUsable(model)) return model;
|
|
6497
|
+
}
|
|
6498
|
+
}
|
|
6499
|
+
for (const [id, model] of this.availableModels) {
|
|
6500
|
+
if (id !== currentModelId && this.isUsable(model)) return model;
|
|
6247
6501
|
}
|
|
6248
6502
|
return null;
|
|
6249
6503
|
}
|
|
@@ -6319,7 +6573,7 @@ var ModelSelector = class {
|
|
|
6319
6573
|
if (overrideModelId.includes(":")) {
|
|
6320
6574
|
const parts = overrideModelId.split(":");
|
|
6321
6575
|
const prefix = parts[0].toLowerCase();
|
|
6322
|
-
const validProviders = ["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama"];
|
|
6576
|
+
const validProviders = ["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama", "github-models"];
|
|
6323
6577
|
if (validProviders.includes(prefix)) {
|
|
6324
6578
|
providerStr = prefix;
|
|
6325
6579
|
actualId = parts.slice(1).join(":");
|
|
@@ -6339,6 +6593,31 @@ var ModelSelector = class {
|
|
|
6339
6593
|
else if (this.availableProviders.has("openai-compatible")) providerStr = "openai-compatible";
|
|
6340
6594
|
else if (this.availableProviders.size === 1) providerStr = Array.from(this.availableProviders)[0];
|
|
6341
6595
|
}
|
|
6596
|
+
if (providerStr === "github-models" && this.availableProviders.has("github-models")) {
|
|
6597
|
+
const dynamicModel = {
|
|
6598
|
+
id: actualId,
|
|
6599
|
+
name: actualId,
|
|
6600
|
+
provider: "github-models",
|
|
6601
|
+
contextWindow: GITHUB_MODELS_MAX_INPUT_TOKENS,
|
|
6602
|
+
isVisionCapable: false,
|
|
6603
|
+
inputCostPer1kTokens: 0,
|
|
6604
|
+
outputCostPer1kTokens: 0,
|
|
6605
|
+
pricingUnknown: false,
|
|
6606
|
+
maxOutputTokens: GITHUB_MODELS_MAX_OUTPUT_TOKENS,
|
|
6607
|
+
supportsStreaming: true,
|
|
6608
|
+
// Unconfirmed (no live catalog entry to read a real capability from)
|
|
6609
|
+
// must default to false, not left undefined: t3-worker.ts's text-tool
|
|
6610
|
+
// fallback only engages on a strict `=== false`, so an undefined
|
|
6611
|
+
// value here sends native `tools` to an unverified multi-vendor
|
|
6612
|
+
// model and any that don't support function calling reject the
|
|
6613
|
+
// request outright. Same default-to-false-when-unconfirmed policy
|
|
6614
|
+
// listModels() already applies for a real catalog entry.
|
|
6615
|
+
supportsToolUse: false,
|
|
6616
|
+
isLocal: false
|
|
6617
|
+
};
|
|
6618
|
+
this.addDynamicModel(dynamicModel);
|
|
6619
|
+
return dynamicModel;
|
|
6620
|
+
}
|
|
6342
6621
|
if (providerStr && this.availableProviders.has(providerStr)) {
|
|
6343
6622
|
const dynamicModel = withResolvedPricing({
|
|
6344
6623
|
id: actualId,
|
|
@@ -6431,7 +6710,20 @@ var DEFAULT_PROVIDER_TPM = {
|
|
|
6431
6710
|
gemini: 6e4,
|
|
6432
6711
|
azure: 3e4,
|
|
6433
6712
|
"openai-compatible": 3e4,
|
|
6434
|
-
ollama: Number.POSITIVE_INFINITY
|
|
6713
|
+
ollama: Number.POSITIVE_INFINITY,
|
|
6714
|
+
// An order of magnitude below the others, on purpose. GitHub Models is
|
|
6715
|
+
// rate-limited by REQUESTS (Free ≈10 RPM, Pro ≈20, plus low daily caps), and
|
|
6716
|
+
// this bucket meters TOKENS — there is no request-count limiter in this
|
|
6717
|
+
// codebase, and building one is not in scope. The approximation works because
|
|
6718
|
+
// the router reserves `model.maxOutputTokens + 512` per call, and the
|
|
6719
|
+
// provider reports GitHub's real ~4K per-request cap rather than the
|
|
6720
|
+
// underlying model's larger one — so every call withdraws a near-constant
|
|
6721
|
+
// ~4.5K regardless of how short the reply actually turns out to be. At 8K/min
|
|
6722
|
+
// that lands around 1–2 calls per minute of headroom: comfortably inside even
|
|
6723
|
+
// the Free tier, with room for the daily cap to matter more than this does.
|
|
6724
|
+
// Users on a higher tier can raise it with no code change through the generic
|
|
6725
|
+
// `config.rateLimits.providerTpm['github-models']` override.
|
|
6726
|
+
"github-models": 8e3
|
|
6435
6727
|
};
|
|
6436
6728
|
var TpmLimiter = class {
|
|
6437
6729
|
buckets = /* @__PURE__ */ new Map();
|
|
@@ -6778,6 +7070,7 @@ var ModelProfiler = class {
|
|
|
6778
7070
|
(m) => !alreadyProfiled.has(m.id) && !SKIP_PATTERN.test(m.id) && !SKIP_PATTERN.test(m.name)
|
|
6779
7071
|
);
|
|
6780
7072
|
if (toProfile.length === 0) return;
|
|
7073
|
+
const probesHitGithubModels = this.router?.getModelForTier("T3")?.provider === "github-models";
|
|
6781
7074
|
const openRouterModels = await fetchOpenRouterModels();
|
|
6782
7075
|
const orByNormalizedId = /* @__PURE__ */ new Map();
|
|
6783
7076
|
for (const m of openRouterModels) {
|
|
@@ -6792,7 +7085,7 @@ var ModelProfiler = class {
|
|
|
6792
7085
|
if (orMatch?.description) {
|
|
6793
7086
|
specializations = extractSpecializations(orMatch.description);
|
|
6794
7087
|
}
|
|
6795
|
-
if (specializations.length === 0 && this.router) {
|
|
7088
|
+
if (specializations.length === 0 && this.router && !probesHitGithubModels) {
|
|
6796
7089
|
specializations = await queryModelDirectly(this.router);
|
|
6797
7090
|
}
|
|
6798
7091
|
this.store.saveModelProfile(model.id, model.provider, specializations);
|
|
@@ -7017,6 +7310,7 @@ var LiveDataProvider = class {
|
|
|
7017
7310
|
*/
|
|
7018
7311
|
applyLivePricing(models) {
|
|
7019
7312
|
return models.map((m) => {
|
|
7313
|
+
if (m.provider === "github-models") return m;
|
|
7020
7314
|
const p = this.getLivePrice(m.baseModelId ?? m.id);
|
|
7021
7315
|
if (!p) return m;
|
|
7022
7316
|
const reconciled = reconcilePrice(m, p);
|
|
@@ -7055,6 +7349,7 @@ var LiveDataProvider = class {
|
|
|
7055
7349
|
*/
|
|
7056
7350
|
applyLiveCapabilities(models) {
|
|
7057
7351
|
return models.map((m) => {
|
|
7352
|
+
if (m.provider === "github-models") return m;
|
|
7058
7353
|
const c = this.getCapability(m.baseModelId ?? m.id);
|
|
7059
7354
|
if (!c) return m;
|
|
7060
7355
|
const next = { ...m };
|
|
@@ -7358,13 +7653,16 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
7358
7653
|
for (const model of azureDeployments) this.selector.addDynamicModel(model);
|
|
7359
7654
|
availableProviders.add("azure");
|
|
7360
7655
|
}
|
|
7656
|
+
if (availableProviders.has("github-models")) {
|
|
7657
|
+
await this.discoverGitHubModelsCatalog(config);
|
|
7658
|
+
}
|
|
7361
7659
|
await this.validateCloudProviderModels(config);
|
|
7362
7660
|
for (const tier of ["T1", "T2", "T3"]) {
|
|
7363
7661
|
const override = tier === "T1" ? config.models.t1 : tier === "T2" ? config.models.t2 : config.models.t3;
|
|
7364
7662
|
if (!override || override === "auto") continue;
|
|
7365
7663
|
const model = this.selector.selectForTier(tier, override);
|
|
7366
7664
|
if (!model) {
|
|
7367
|
-
const knownProviders = ["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama"];
|
|
7665
|
+
const knownProviders = ["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama", "github-models"];
|
|
7368
7666
|
const hasProviderPrefix = override.includes(":") && knownProviders.some((p) => override.startsWith(p + ":"));
|
|
7369
7667
|
if (hasProviderPrefix) {
|
|
7370
7668
|
const provider = override.split(":")[0];
|
|
@@ -7519,7 +7817,7 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
7519
7817
|
* models without a package upgrade. Mirrors discoverOllamaModels.
|
|
7520
7818
|
*/
|
|
7521
7819
|
async discoverProviderModels() {
|
|
7522
|
-
const cloud = ["anthropic", "openai", "gemini", "azure", "openai-compatible"];
|
|
7820
|
+
const cloud = ["anthropic", "openai", "gemini", "azure", "openai-compatible", "github-models"];
|
|
7523
7821
|
const tasks = cloud.map(async (type) => {
|
|
7524
7822
|
if (!this.selector.isProviderAvailable(type)) return;
|
|
7525
7823
|
const seed = this.getAnyModelForProvider(type);
|
|
@@ -7574,6 +7872,9 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
7574
7872
|
this.ensureProvider(options.model, this.config.providers);
|
|
7575
7873
|
}
|
|
7576
7874
|
let model = requireVision ? this.selector.selectVisionModel() : options.model ?? this.tierModels.get(tier) ?? this.selector.selectForTier(tier) ?? void 0;
|
|
7875
|
+
if (requireVision && model) {
|
|
7876
|
+
this.ensureProvider(model, this.config.providers);
|
|
7877
|
+
}
|
|
7577
7878
|
if (options.forceLocal && model && !this.isPrivateModel(model)) {
|
|
7578
7879
|
const localModel = this.selector.getAllAvailableModels().find((m) => this.isPrivateModel(m));
|
|
7579
7880
|
if (!localModel) {
|
|
@@ -7587,7 +7888,8 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
7587
7888
|
if (!model) throw new Error(`No model available for tier ${tier}`);
|
|
7588
7889
|
const provider = this.getProvider(model);
|
|
7589
7890
|
if (!provider) throw new Error(`No provider for model ${model.id}`);
|
|
7590
|
-
const
|
|
7891
|
+
const requestedTokens = options.maxTokens ?? model.maxOutputTokens ?? 1024;
|
|
7892
|
+
const estimatedTokens = Math.min(requestedTokens, model.maxOutputTokens ?? requestedTokens) + 512;
|
|
7591
7893
|
if (this.tpmLimiter) {
|
|
7592
7894
|
await this.tpmLimiter.acquire(model.provider, estimatedTokens);
|
|
7593
7895
|
}
|
|
@@ -7678,7 +7980,8 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
7678
7980
|
});
|
|
7679
7981
|
releaseLocalSlot?.();
|
|
7680
7982
|
releaseLocalSlot = void 0;
|
|
7681
|
-
|
|
7983
|
+
const retryOpts = options.model && options.model.id === model.id ? { ...options, model: void 0 } : options;
|
|
7984
|
+
return this.generate(tier, retryOpts, onChunk, requireVision);
|
|
7682
7985
|
}
|
|
7683
7986
|
}
|
|
7684
7987
|
if (isModelNotFoundError(errMsg)) {
|
|
@@ -8029,6 +8332,26 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
8029
8332
|
return false;
|
|
8030
8333
|
}
|
|
8031
8334
|
}
|
|
8335
|
+
/**
|
|
8336
|
+
* Registers GitHub Models' real catalog ids via addDynamicModel so init()'s
|
|
8337
|
+
* tier-fill loop can pick one. Registration alone does not create a bound
|
|
8338
|
+
* BaseProvider instance (see ensureProvider) — the tier-fill loop's own
|
|
8339
|
+
* ensureProvider call does that for whichever model it actually selects,
|
|
8340
|
+
* which is why this runs BEFORE that loop rather than only in the
|
|
8341
|
+
* background discoverProviderModels() refresh.
|
|
8342
|
+
*/
|
|
8343
|
+
async discoverGitHubModelsCatalog(config) {
|
|
8344
|
+
const cfg = config.providers.find((p) => p.type === "github-models") ?? { type: "github-models" };
|
|
8345
|
+
const seed = this.getAnyModelForProvider("github-models");
|
|
8346
|
+
if (!seed) return;
|
|
8347
|
+
try {
|
|
8348
|
+
const provider = new GitHubModelsProvider(cfg, seed);
|
|
8349
|
+
const models = await provider.listModels();
|
|
8350
|
+
for (const m of models) this.selector.addDynamicModel(m);
|
|
8351
|
+
} catch (err) {
|
|
8352
|
+
console.warn("[router] GitHub Models catalog discovery failed:", err instanceof Error ? err.message : err);
|
|
8353
|
+
}
|
|
8354
|
+
}
|
|
8032
8355
|
ensureProvider(model, configs) {
|
|
8033
8356
|
const key = `${model.provider}:${model.id}`;
|
|
8034
8357
|
if (this.providers.has(key)) return;
|
|
@@ -8053,6 +8376,8 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
8053
8376
|
return new OllamaProvider(cfg, model);
|
|
8054
8377
|
case "openai-compatible":
|
|
8055
8378
|
return new OpenAICompatibleProvider(cfg, model);
|
|
8379
|
+
case "github-models":
|
|
8380
|
+
return new GitHubModelsProvider(cfg, model);
|
|
8056
8381
|
default:
|
|
8057
8382
|
throw new Error(`Unsupported provider type: ${String(cfg.type)}`);
|
|
8058
8383
|
}
|
|
@@ -8060,7 +8385,7 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
8060
8385
|
getAnyModelForProvider(type) {
|
|
8061
8386
|
const fromCatalog = Object.values(MODELS).find((m) => m.provider === type);
|
|
8062
8387
|
if (fromCatalog) return fromCatalog;
|
|
8063
|
-
if (type === "openai-compatible" || type === "azure") {
|
|
8388
|
+
if (type === "openai-compatible" || type === "azure" || type === "github-models") {
|
|
8064
8389
|
return {
|
|
8065
8390
|
id: type,
|
|
8066
8391
|
name: type,
|
|
@@ -20150,7 +20475,10 @@ async function refreshModelCache(store, providers) {
|
|
|
20150
20475
|
else if (provider.type === "anthropic") instance = new AnthropicProvider(provider, dummyModel);
|
|
20151
20476
|
else if (provider.type === "ollama") instance = new OllamaProvider(provider, dummyModel);
|
|
20152
20477
|
else if (provider.type === "openai-compatible") instance = new OpenAICompatibleProvider(provider, dummyModel);
|
|
20153
|
-
else if (provider.type === "
|
|
20478
|
+
else if (provider.type === "github-models") {
|
|
20479
|
+
const { GitHubModelsProvider: GitHubModelsProvider2 } = await Promise.resolve().then(() => (init_github_models(), github_models_exports));
|
|
20480
|
+
instance = new GitHubModelsProvider2(provider, dummyModel);
|
|
20481
|
+
} else if (provider.type === "azure") {
|
|
20154
20482
|
const { AzureOpenAIProvider: AzureOpenAIProvider2 } = await Promise.resolve().then(() => (init_azure(), azure_exports));
|
|
20155
20483
|
instance = new AzureOpenAIProvider2(provider, dummyModel);
|
|
20156
20484
|
}
|
|
@@ -21257,7 +21585,7 @@ async function validateConfiguredModels(config) {
|
|
|
21257
21585
|
function inferProviderFromModelId(id, providers) {
|
|
21258
21586
|
if (id.includes(":")) {
|
|
21259
21587
|
const prefix = id.split(":")[0].toLowerCase();
|
|
21260
|
-
const validProviders = ["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama"];
|
|
21588
|
+
const validProviders = ["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama", "github-models"];
|
|
21261
21589
|
if (validProviders.includes(prefix)) {
|
|
21262
21590
|
return prefix;
|
|
21263
21591
|
}
|
|
@@ -21554,9 +21882,10 @@ var PROVIDER_LABELS = {
|
|
|
21554
21882
|
gemini: "Google Gemini",
|
|
21555
21883
|
azure: "Azure OpenAI",
|
|
21556
21884
|
ollama: "Ollama (local)",
|
|
21557
|
-
"openai-compatible": "OpenAI-Compatible"
|
|
21885
|
+
"openai-compatible": "OpenAI-Compatible",
|
|
21886
|
+
"github-models": "GitHub Models"
|
|
21558
21887
|
};
|
|
21559
|
-
var providerOrder = ["anthropic", "openai", "gemini", "azure", "ollama", "openai-compatible"];
|
|
21888
|
+
var providerOrder = ["anthropic", "openai", "gemini", "azure", "ollama", "openai-compatible", "github-models"];
|
|
21560
21889
|
function buildInitialEntries(types) {
|
|
21561
21890
|
return [...types].map((type) => ({
|
|
21562
21891
|
id: crypto.randomUUID(),
|
|
@@ -21728,6 +22057,13 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
21728
22057
|
const fetched = await p.listModels();
|
|
21729
22058
|
fetched.forEach((m) => models.push({ id: m.id, name: m.name, providerLabel: entry.label }));
|
|
21730
22059
|
dispatchRef.current({ type: "SET_FETCH_LOG", line: ` \u2714 ${entry.label} \u2014 ${fetched.length} models` });
|
|
22060
|
+
} else if (type === "github-models") {
|
|
22061
|
+
const { GitHubModelsProvider: GitHubModelsProvider2 } = await Promise.resolve().then(() => (init_github_models(), github_models_exports));
|
|
22062
|
+
const dummyModel = { id: "dummy", name: "dummy", provider: type, contextWindow: 0, isVisionCapable: false, inputCostPer1kTokens: 0, outputCostPer1kTokens: 0, maxOutputTokens: 0, supportsStreaming: false, isLocal: false };
|
|
22063
|
+
const p = new GitHubModelsProvider2({ type, apiKey }, dummyModel);
|
|
22064
|
+
const fetched = await p.listModels();
|
|
22065
|
+
fetched.forEach((m) => models.push({ id: `${type}:${m.id}`, name: m.name, providerLabel: entry.label }));
|
|
22066
|
+
dispatchRef.current({ type: "SET_FETCH_LOG", line: ` \u2714 ${entry.label} \u2014 ${fetched.length} models` });
|
|
21731
22067
|
} else if (type === "gemini") {
|
|
21732
22068
|
const { GeminiProvider: GeminiProvider2 } = await Promise.resolve().then(() => (init_gemini(), gemini_exports));
|
|
21733
22069
|
const dummyModel = { id: "dummy", name: "dummy", provider: type, contextWindow: 0, isVisionCapable: false, inputCostPer1kTokens: 0, outputCostPer1kTokens: 0, maxOutputTokens: 0, supportsStreaming: false, isLocal: false };
|
|
@@ -21906,7 +22242,8 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
21906
22242
|
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: focused ? theme.colors.primary : selected ? theme.colors.foreground : theme.colors.muted, children: PROVIDER_LABELS[p] }),
|
|
21907
22243
|
p === "azure" && /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: theme.colors.muted, dimColor: true, children: " \u2014 multiple deployments supported" }),
|
|
21908
22244
|
p === "openai-compatible" && /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: theme.colors.muted, dimColor: true, children: " \u2014 Groq, Together, custom" }),
|
|
21909
|
-
p === "ollama" && /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: theme.colors.muted, dimColor: true, children: " \u2014 no API key needed" })
|
|
22245
|
+
p === "ollama" && /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: theme.colors.muted, dimColor: true, children: " \u2014 no API key needed" }),
|
|
22246
|
+
p === "github-models" && /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: theme.colors.muted, dimColor: true, children: " \u2014 GitHub PAT with `models: read`" })
|
|
21910
22247
|
] }, p);
|
|
21911
22248
|
}) }),
|
|
21912
22249
|
/* @__PURE__ */ jsxRuntime.jsx(ink.Box, { marginTop: 1, children: /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: theme.colors.muted, dimColor: true, children: "space select \xB7 a all \xB7 i invert \xB7 enter continue" }) }),
|
|
@@ -22313,7 +22650,8 @@ async function doctorCommand() {
|
|
|
22313
22650
|
{ type: "anthropic", name: "Anthropic" },
|
|
22314
22651
|
{ type: "openai", name: "OpenAI" },
|
|
22315
22652
|
{ type: "gemini", name: "Google Gemini" },
|
|
22316
|
-
{ type: "azure", name: "Azure OpenAI" }
|
|
22653
|
+
{ type: "azure", name: "Azure OpenAI" },
|
|
22654
|
+
{ type: "github-models", name: "GitHub Models" }
|
|
22317
22655
|
];
|
|
22318
22656
|
for (const { type, name } of providers) {
|
|
22319
22657
|
const key = cm.getApiKey(type);
|