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.js
CHANGED
|
@@ -1753,11 +1753,11 @@ var init_pricing = __esm({
|
|
|
1753
1753
|
});
|
|
1754
1754
|
|
|
1755
1755
|
// src/constants.ts
|
|
1756
|
-
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;
|
|
1756
|
+
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;
|
|
1757
1757
|
var init_constants = __esm({
|
|
1758
1758
|
"src/constants.ts"() {
|
|
1759
1759
|
init_pricing();
|
|
1760
|
-
CASCADE_VERSION = "0.
|
|
1760
|
+
CASCADE_VERSION = "0.65.0";
|
|
1761
1761
|
CASCADE_CONFIG_FILE = ".cascade/config.json";
|
|
1762
1762
|
CASCADE_DB_FILE = ".cascade/memory.db";
|
|
1763
1763
|
CASCADE_DASHBOARD_SECRET_FILE = ".cascade/dashboard-secret";
|
|
@@ -2156,6 +2156,9 @@ var init_constants = __esm({
|
|
|
2156
2156
|
OLLAMA_BASE_URL = "http://localhost:11434";
|
|
2157
2157
|
LM_STUDIO_BASE_URL = "http://localhost:1234";
|
|
2158
2158
|
AZURE_BASE_URL_TEMPLATE = "https://{resource}.openai.azure.com";
|
|
2159
|
+
GITHUB_MODELS_INFERENCE_URL = "https://models.github.ai/inference";
|
|
2160
|
+
GITHUB_MODELS_CATALOG_URL = "https://models.github.ai/catalog/models";
|
|
2161
|
+
GITHUB_MODELS_API_VERSION = "2026-03-10";
|
|
2159
2162
|
TOOL_NAMES = {
|
|
2160
2163
|
SHELL: "shell",
|
|
2161
2164
|
FILE_READ: "file_read",
|
|
@@ -3237,7 +3240,7 @@ var init_gemini = __esm({
|
|
|
3237
3240
|
function preferIpv4Host(url) {
|
|
3238
3241
|
return url;
|
|
3239
3242
|
}
|
|
3240
|
-
async function nodeHttpFetch(input, init = {}, redirectCount = 0) {
|
|
3243
|
+
async function nodeHttpFetch(input, init = {}, redirectCount = 0, opts = {}) {
|
|
3241
3244
|
const urlStr = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
3242
3245
|
const u = new URL(preferIpv4Host(urlStr) ?? urlStr);
|
|
3243
3246
|
const lib = u.protocol === "https:" ? https : http;
|
|
@@ -3268,9 +3271,13 @@ async function nodeHttpFetch(input, init = {}, redirectCount = 0) {
|
|
|
3268
3271
|
if (status >= 300 && status < 400 && location && redirectCount < MAX_REDIRECTS) {
|
|
3269
3272
|
res.resume();
|
|
3270
3273
|
const nextUrl = new URL(location, u).href;
|
|
3274
|
+
if (opts.allowedRedirectOrigin && new URL(nextUrl).origin !== opts.allowedRedirectOrigin) {
|
|
3275
|
+
reject(new Error(`Refusing cross-origin redirect to ${new URL(nextUrl).origin}`));
|
|
3276
|
+
return;
|
|
3277
|
+
}
|
|
3271
3278
|
const downgrade = status === 303 || (status === 301 || status === 302) && method !== "GET" && method !== "HEAD";
|
|
3272
3279
|
const nextInit = downgrade ? { ...init, method: "GET", body: void 0 } : init;
|
|
3273
|
-
resolve2(nodeHttpFetch(nextUrl, nextInit, redirectCount + 1));
|
|
3280
|
+
resolve2(nodeHttpFetch(nextUrl, nextInit, redirectCount + 1, opts));
|
|
3274
3281
|
return;
|
|
3275
3282
|
}
|
|
3276
3283
|
const encoding = (res.headers["content-encoding"] ?? "").toLowerCase();
|
|
@@ -3309,6 +3316,222 @@ var init_net = __esm({
|
|
|
3309
3316
|
}
|
|
3310
3317
|
});
|
|
3311
3318
|
|
|
3319
|
+
// src/providers/github-models.ts
|
|
3320
|
+
var github_models_exports = {};
|
|
3321
|
+
__export(github_models_exports, {
|
|
3322
|
+
GITHUB_MODELS_MAX_INPUT_TOKENS: () => GITHUB_MODELS_MAX_INPUT_TOKENS,
|
|
3323
|
+
GITHUB_MODELS_MAX_OUTPUT_TOKENS: () => GITHUB_MODELS_MAX_OUTPUT_TOKENS,
|
|
3324
|
+
GitHubModelsProvider: () => GitHubModelsProvider,
|
|
3325
|
+
stripModelOwnerPrefix: () => stripModelOwnerPrefix
|
|
3326
|
+
});
|
|
3327
|
+
function stripModelOwnerPrefix(id) {
|
|
3328
|
+
const slash = (id || "").indexOf("/");
|
|
3329
|
+
return slash === -1 ? id : id.slice(slash + 1);
|
|
3330
|
+
}
|
|
3331
|
+
function pickString(o, keys) {
|
|
3332
|
+
for (const k of keys) {
|
|
3333
|
+
const v = o[k];
|
|
3334
|
+
if (typeof v === "string" && v.length > 0) return v;
|
|
3335
|
+
}
|
|
3336
|
+
return void 0;
|
|
3337
|
+
}
|
|
3338
|
+
function pickNumber(o, keys) {
|
|
3339
|
+
for (const k of keys) {
|
|
3340
|
+
const v = k.includes(".") ? o[k.split(".")[0]]?.[k.split(".")[1]] : o[k];
|
|
3341
|
+
if (typeof v === "number" && Number.isFinite(v) && v > 0) return v;
|
|
3342
|
+
}
|
|
3343
|
+
return void 0;
|
|
3344
|
+
}
|
|
3345
|
+
function looksVisionCapable(o) {
|
|
3346
|
+
for (const k of ["supported_input_modalities", "input_modalities", "modalities", "capabilities"]) {
|
|
3347
|
+
const v = o[k];
|
|
3348
|
+
if (Array.isArray(v) && v.some((x) => typeof x === "string" && /image|vision/i.test(x))) return true;
|
|
3349
|
+
}
|
|
3350
|
+
return false;
|
|
3351
|
+
}
|
|
3352
|
+
function looksToolCapable(o) {
|
|
3353
|
+
const params = o["supported_parameters"];
|
|
3354
|
+
if (Array.isArray(params) && params.some((x) => typeof x === "string" && /^tools?$|function.?call/i.test(x))) {
|
|
3355
|
+
return true;
|
|
3356
|
+
}
|
|
3357
|
+
for (const k of ["capabilities", "supported_output_modalities"]) {
|
|
3358
|
+
const v = o[k];
|
|
3359
|
+
if (Array.isArray(v) && v.some((x) => typeof x === "string" && /tool|function.?call/i.test(x))) return true;
|
|
3360
|
+
}
|
|
3361
|
+
const caps = o["capabilities"];
|
|
3362
|
+
if (caps && typeof caps === "object" && !Array.isArray(caps)) {
|
|
3363
|
+
const c = caps;
|
|
3364
|
+
if (c["function_calling"] === true || c["tool_calling"] === true || c["tools"] === true) return true;
|
|
3365
|
+
}
|
|
3366
|
+
return false;
|
|
3367
|
+
}
|
|
3368
|
+
function parseNextLink(linkHeader) {
|
|
3369
|
+
if (!linkHeader) return null;
|
|
3370
|
+
for (const part of linkHeader.split(",")) {
|
|
3371
|
+
const match = part.match(/<([^>]+)>\s*;\s*rel="next"/);
|
|
3372
|
+
if (match) return match[1];
|
|
3373
|
+
}
|
|
3374
|
+
return null;
|
|
3375
|
+
}
|
|
3376
|
+
var GITHUB_MODELS_MAX_OUTPUT_TOKENS, GITHUB_MODELS_MAX_INPUT_TOKENS, DEFAULT_CONTEXT_WINDOW, GITHUB_MODELS_MAX_CATALOG_PAGES, GITHUB_MODELS_CATALOG_ORIGIN, GitHubModelsProvider;
|
|
3377
|
+
var init_github_models = __esm({
|
|
3378
|
+
"src/providers/github-models.ts"() {
|
|
3379
|
+
init_constants();
|
|
3380
|
+
init_openai();
|
|
3381
|
+
init_model_filter();
|
|
3382
|
+
init_net();
|
|
3383
|
+
GITHUB_MODELS_MAX_OUTPUT_TOKENS = 4e3;
|
|
3384
|
+
GITHUB_MODELS_MAX_INPUT_TOKENS = 8e3;
|
|
3385
|
+
DEFAULT_CONTEXT_WINDOW = 128e3;
|
|
3386
|
+
GITHUB_MODELS_MAX_CATALOG_PAGES = 20;
|
|
3387
|
+
GITHUB_MODELS_CATALOG_ORIGIN = new URL(GITHUB_MODELS_CATALOG_URL).origin;
|
|
3388
|
+
GitHubModelsProvider = class extends OpenAIProvider {
|
|
3389
|
+
constructor(config, model) {
|
|
3390
|
+
super({ ...config, apiKey: config.apiKey ?? "" }, model);
|
|
3391
|
+
this.useMaxCompletionTokens = isReasoningModel(stripModelOwnerPrefix(model.id));
|
|
3392
|
+
this.client = new OpenAI({
|
|
3393
|
+
apiKey: config.apiKey ?? "",
|
|
3394
|
+
baseURL: GITHUB_MODELS_INFERENCE_URL,
|
|
3395
|
+
defaultHeaders: { "X-GitHub-Api-Version": GITHUB_MODELS_API_VERSION, "User-Agent": "Cascade-AI" }
|
|
3396
|
+
});
|
|
3397
|
+
}
|
|
3398
|
+
/**
|
|
3399
|
+
* `OpenAIProvider.generateStream()` builds its request budget as
|
|
3400
|
+
* `options.maxTokens ?? this.model.maxOutputTokens` — an explicit per-call
|
|
3401
|
+
* `maxTokens` (T1Administrator's final compilation asks for 8,000, well
|
|
3402
|
+
* above GitHub's real ~4K hard cap) wins outright and is sent unclamped.
|
|
3403
|
+
* GitHub then rejects the request instead of answering, so a run routed to
|
|
3404
|
+
* this provider for that step fails outright rather than getting a
|
|
3405
|
+
* (perfectly serviceable, just shorter) completion. Clamp here, once, right
|
|
3406
|
+
* before delegating to the inherited implementation, rather than teaching
|
|
3407
|
+
* every caller GitHub's specific cap.
|
|
3408
|
+
*/
|
|
3409
|
+
async generateStream(options, onChunk) {
|
|
3410
|
+
const clamped = options.maxTokens !== void 0 && options.maxTokens > GITHUB_MODELS_MAX_OUTPUT_TOKENS ? { ...options, maxTokens: GITHUB_MODELS_MAX_OUTPUT_TOKENS } : options;
|
|
3411
|
+
return super.generateStream(clamped, onChunk);
|
|
3412
|
+
}
|
|
3413
|
+
/**
|
|
3414
|
+
* The catalog is a GitHub REST resource, not an OpenAI `/models` list, and
|
|
3415
|
+
* wants GitHub's own header trio — the plain `Accept: application/json` the
|
|
3416
|
+
* openai-compatible provider sends is not the same request.
|
|
3417
|
+
*/
|
|
3418
|
+
catalogHeaders() {
|
|
3419
|
+
return {
|
|
3420
|
+
Accept: "application/vnd.github+json",
|
|
3421
|
+
Authorization: `Bearer ${this.config.apiKey ?? ""}`,
|
|
3422
|
+
"X-GitHub-Api-Version": GITHUB_MODELS_API_VERSION,
|
|
3423
|
+
// GitHub's REST API rejects any request with no User-Agent — a plain 403
|
|
3424
|
+
// regardless of how valid the PAT is. nodeHttpFetch is a thin wrapper
|
|
3425
|
+
// over node:http/https, neither of which sets one the way a browser's
|
|
3426
|
+
// fetch or a full HTTP client library would, so this has to be explicit.
|
|
3427
|
+
"User-Agent": "Cascade-AI"
|
|
3428
|
+
};
|
|
3429
|
+
}
|
|
3430
|
+
async listModels() {
|
|
3431
|
+
const raw = [];
|
|
3432
|
+
let url = GITHUB_MODELS_CATALOG_URL;
|
|
3433
|
+
let pages = 0;
|
|
3434
|
+
const visited = /* @__PURE__ */ new Set();
|
|
3435
|
+
while (url && pages < GITHUB_MODELS_MAX_CATALOG_PAGES) {
|
|
3436
|
+
if (visited.has(url)) break;
|
|
3437
|
+
visited.add(url);
|
|
3438
|
+
const res = await nodeHttpFetch(url, { headers: this.catalogHeaders() }, 0, {
|
|
3439
|
+
allowedRedirectOrigin: GITHUB_MODELS_CATALOG_ORIGIN
|
|
3440
|
+
});
|
|
3441
|
+
if (!res.ok) throw new Error(`GitHub Models catalog ${url} returned HTTP ${res.status}`);
|
|
3442
|
+
const body = await res.json();
|
|
3443
|
+
const pageItems = Array.isArray(body) ? body : Array.isArray(body?.models) ? body.models : Array.isArray(body?.data) ? body.data : [];
|
|
3444
|
+
raw.push(...pageItems);
|
|
3445
|
+
const next = parseNextLink(res.headers.get("link"));
|
|
3446
|
+
if (next) {
|
|
3447
|
+
const resolved = new URL(next, url);
|
|
3448
|
+
if (resolved.origin !== GITHUB_MODELS_CATALOG_ORIGIN) {
|
|
3449
|
+
throw new Error(`Refusing cross-origin GitHub Models pagination URL: ${resolved.origin}`);
|
|
3450
|
+
}
|
|
3451
|
+
url = resolved.href;
|
|
3452
|
+
} else {
|
|
3453
|
+
url = null;
|
|
3454
|
+
}
|
|
3455
|
+
pages++;
|
|
3456
|
+
}
|
|
3457
|
+
const entries = raw.map((m) => {
|
|
3458
|
+
if (typeof m === "string") {
|
|
3459
|
+
return { id: m, name: m, contextWindow: DEFAULT_CONTEXT_WINDOW, vision: false, tools: false };
|
|
3460
|
+
}
|
|
3461
|
+
if (!m || typeof m !== "object") return void 0;
|
|
3462
|
+
const o = m;
|
|
3463
|
+
const id = pickString(o, ["id", "name", "model"]);
|
|
3464
|
+
if (!id) return void 0;
|
|
3465
|
+
return {
|
|
3466
|
+
id,
|
|
3467
|
+
name: pickString(o, ["friendly_name", "display_name", "name"]) ?? id,
|
|
3468
|
+
contextWindow: pickNumber(o, [
|
|
3469
|
+
"context_window",
|
|
3470
|
+
"context_length",
|
|
3471
|
+
"max_input_tokens",
|
|
3472
|
+
"limits.max_input_tokens",
|
|
3473
|
+
"limits.max_context_tokens"
|
|
3474
|
+
]) ?? DEFAULT_CONTEXT_WINDOW,
|
|
3475
|
+
vision: looksVisionCapable(o),
|
|
3476
|
+
tools: looksToolCapable(o)
|
|
3477
|
+
};
|
|
3478
|
+
}).filter((e) => e !== void 0);
|
|
3479
|
+
if (entries.length === 0) return [];
|
|
3480
|
+
const chat = entries.filter((e) => isChatModel(e.id));
|
|
3481
|
+
return (chat.length ? chat : entries).map((e) => ({
|
|
3482
|
+
id: e.id,
|
|
3483
|
+
// full `owner/model` — what the API is addressed by
|
|
3484
|
+
name: e.name,
|
|
3485
|
+
provider: "github-models",
|
|
3486
|
+
// GitHub's real per-request input cap, not the base model's larger one
|
|
3487
|
+
// (see GITHUB_MODELS_MAX_INPUT_TOKENS above) — the same reasoning
|
|
3488
|
+
// already applied to maxOutputTokens below, for the input side.
|
|
3489
|
+
contextWindow: Math.min(e.contextWindow, GITHUB_MODELS_MAX_INPUT_TOKENS),
|
|
3490
|
+
isVisionCapable: e.vision,
|
|
3491
|
+
// A REAL $0, not a missing number — usage is bundled into the quota of
|
|
3492
|
+
// the plan the PAT belongs to and is never billed per token, so
|
|
3493
|
+
// `pricingUnknown: false` is the honest value (see the flag's contract in
|
|
3494
|
+
// core/router/pricing.ts: 0 alone is ambiguous). Set here rather than in
|
|
3495
|
+
// pricing-data.json because resolvePricing() matches exactly on
|
|
3496
|
+
// provider|model|region with no wildcard: every catalog model would need
|
|
3497
|
+
// a hand-curated row, and the next one GitHub adds would read as "cost
|
|
3498
|
+
// unknown" — an inconsistent picture for a billing reality that is
|
|
3499
|
+
// identical across the entire catalog.
|
|
3500
|
+
//
|
|
3501
|
+
// `isLocal` is the OTHER way to reach a real $0, and it is the wrong one:
|
|
3502
|
+
// it means "runs on this machine" and carries consequences that are
|
|
3503
|
+
// simply false for a hosted API — the shared concurrency-1
|
|
3504
|
+
// LocalRequestQueue (serialising these calls behind actual Ollama GPU
|
|
3505
|
+
// inference), the 300s local rather than 120s cloud inference timeout,
|
|
3506
|
+
// and a literal "[local]" tag in `cascade models`.
|
|
3507
|
+
inputCostPer1kTokens: 0,
|
|
3508
|
+
outputCostPer1kTokens: 0,
|
|
3509
|
+
pricingUnknown: false,
|
|
3510
|
+
maxOutputTokens: GITHUB_MODELS_MAX_OUTPUT_TOKENS,
|
|
3511
|
+
supportsStreaming: true,
|
|
3512
|
+
// Not every catalog model supports function/tool calling — a hardcoded
|
|
3513
|
+
// `true` sent `tools` to models that reject the parameter outright and
|
|
3514
|
+
// never engaged t3-worker.ts's existing text-tool fallback (only an
|
|
3515
|
+
// explicit `false` does). See looksToolCapable's own comment for why
|
|
3516
|
+
// "not positively advertised" defaults to false rather than true here.
|
|
3517
|
+
supportsToolUse: e.tools,
|
|
3518
|
+
isLocal: false
|
|
3519
|
+
}));
|
|
3520
|
+
}
|
|
3521
|
+
async isAvailable() {
|
|
3522
|
+
try {
|
|
3523
|
+
const res = await nodeHttpFetch(GITHUB_MODELS_CATALOG_URL, { headers: this.catalogHeaders() }, 0, {
|
|
3524
|
+
allowedRedirectOrigin: GITHUB_MODELS_CATALOG_ORIGIN
|
|
3525
|
+
});
|
|
3526
|
+
return res.ok;
|
|
3527
|
+
} catch {
|
|
3528
|
+
return false;
|
|
3529
|
+
}
|
|
3530
|
+
}
|
|
3531
|
+
};
|
|
3532
|
+
}
|
|
3533
|
+
});
|
|
3534
|
+
|
|
3312
3535
|
// src/providers/ollama.ts
|
|
3313
3536
|
var ollama_exports = {};
|
|
3314
3537
|
__export(ollama_exports, {
|
|
@@ -4943,7 +5166,7 @@ Original error: ${err.message}`
|
|
|
4943
5166
|
}
|
|
4944
5167
|
};
|
|
4945
5168
|
var ProviderConfigSchema = z.object({
|
|
4946
|
-
type: z.enum(["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama"]),
|
|
5169
|
+
type: z.enum(["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama", "github-models"]),
|
|
4947
5170
|
label: z.string().optional(),
|
|
4948
5171
|
apiKey: z.string().optional(),
|
|
4949
5172
|
baseUrl: z.string().url().optional(),
|
|
@@ -5123,6 +5346,18 @@ var CascadeConfigSchema = z.object({
|
|
|
5123
5346
|
budget: BudgetConfigSchema.default({}),
|
|
5124
5347
|
theme: z.string().default("cascade"),
|
|
5125
5348
|
workspace: WorkspaceConfigSchema.default({}),
|
|
5349
|
+
/**
|
|
5350
|
+
* Per-provider tokens-per-minute overrides for the router's TpmLimiter (see
|
|
5351
|
+
* DEFAULT_PROVIDER_TPM in core/router/tpm-limiter.ts). Documented as a raw
|
|
5352
|
+
* config field for a while before this — since CascadeConfigSchema strips
|
|
5353
|
+
* unknown keys, a config file setting it was silently discarded and the
|
|
5354
|
+
* override never reached TpmLimiter's constructor. Optional/permissive on
|
|
5355
|
+
* provider name (not a ProviderType enum) so a config still validates
|
|
5356
|
+
* against a slightly newer/older provider list than this schema knows.
|
|
5357
|
+
*/
|
|
5358
|
+
rateLimits: z.object({
|
|
5359
|
+
providerTpm: z.record(z.string(), z.number().positive()).optional()
|
|
5360
|
+
}).optional(),
|
|
5126
5361
|
/**
|
|
5127
5362
|
* Cascade Auto: when true, the TaskAnalyzer selects the optimal model for each
|
|
5128
5363
|
* tier based on task type and complexity, overriding the static priority lists.
|
|
@@ -5654,7 +5889,14 @@ var ConfigManager = class {
|
|
|
5654
5889
|
anthropic: "ANTHROPIC_API_KEY",
|
|
5655
5890
|
openai: "OPENAI_API_KEY",
|
|
5656
5891
|
gemini: "GOOGLE_API_KEY",
|
|
5657
|
-
azure: "AZURE_OPENAI_KEY"
|
|
5892
|
+
azure: "AZURE_OPENAI_KEY",
|
|
5893
|
+
// Deliberately NOT `GITHUB_TOKEN`: that one is injected automatically by
|
|
5894
|
+
// GitHub Actions and is commonly exported in developer shells for `gh`
|
|
5895
|
+
// and git auth, and it will essentially never carry the fine-grained
|
|
5896
|
+
// `models: read` scope this provider needs. Adopting it would wire up a
|
|
5897
|
+
// provider that 403s on its first real call — a confusing failure for
|
|
5898
|
+
// something that is meant to be explicit opt-in BYOK.
|
|
5899
|
+
"github-models": "GITHUB_MODELS_TOKEN"
|
|
5658
5900
|
};
|
|
5659
5901
|
const envKey = envMap[provider];
|
|
5660
5902
|
if (envKey && process.env[envKey]) return process.env[envKey];
|
|
@@ -5683,7 +5925,10 @@ var ConfigManager = class {
|
|
|
5683
5925
|
{ env: "ANTHROPIC_API_KEY", type: "anthropic" },
|
|
5684
5926
|
{ env: "OPENAI_API_KEY", type: "openai" },
|
|
5685
5927
|
{ env: "GOOGLE_API_KEY", type: "gemini" },
|
|
5686
|
-
{ env: "AZURE_OPENAI_KEY", type: "azure" }
|
|
5928
|
+
{ env: "AZURE_OPENAI_KEY", type: "azure" },
|
|
5929
|
+
// See getApiKey() above for why this is GITHUB_MODELS_TOKEN and never
|
|
5930
|
+
// the ambient GITHUB_TOKEN.
|
|
5931
|
+
{ env: "GITHUB_MODELS_TOKEN", type: "github-models" }
|
|
5687
5932
|
];
|
|
5688
5933
|
for (const { env, type } of envProviders) {
|
|
5689
5934
|
const key = process.env[env];
|
|
@@ -6065,6 +6310,7 @@ init_constants();
|
|
|
6065
6310
|
init_anthropic();
|
|
6066
6311
|
init_azure();
|
|
6067
6312
|
init_gemini();
|
|
6313
|
+
init_github_models();
|
|
6068
6314
|
init_ollama();
|
|
6069
6315
|
init_openai_compatible();
|
|
6070
6316
|
init_openai();
|
|
@@ -6072,6 +6318,7 @@ init_openai();
|
|
|
6072
6318
|
// src/core/router/selector.ts
|
|
6073
6319
|
init_constants();
|
|
6074
6320
|
init_pricing();
|
|
6321
|
+
init_github_models();
|
|
6075
6322
|
function normalizeModelId(id) {
|
|
6076
6323
|
return id.replace(/^models\//, "").toLowerCase();
|
|
6077
6324
|
}
|
|
@@ -6181,16 +6428,23 @@ var ModelSelector = class {
|
|
|
6181
6428
|
return model;
|
|
6182
6429
|
}
|
|
6183
6430
|
}
|
|
6431
|
+
for (const model of this.availableModels.values()) {
|
|
6432
|
+
if (this.availableProviders.has(model.provider) && model.isVisionCapable) return model;
|
|
6433
|
+
}
|
|
6184
6434
|
return null;
|
|
6185
6435
|
}
|
|
6186
6436
|
getNextFallback(currentModelId, tier) {
|
|
6187
6437
|
const priority = this.getPriorityList(tier);
|
|
6188
6438
|
const currentIdx = priority.indexOf(currentModelId);
|
|
6189
|
-
if (currentIdx
|
|
6190
|
-
|
|
6191
|
-
|
|
6192
|
-
|
|
6193
|
-
|
|
6439
|
+
if (currentIdx !== -1) {
|
|
6440
|
+
for (let i = currentIdx + 1; i < priority.length; i++) {
|
|
6441
|
+
const key = priority[i];
|
|
6442
|
+
const model = this.availableModels.get(key);
|
|
6443
|
+
if (model && this.isUsable(model)) return model;
|
|
6444
|
+
}
|
|
6445
|
+
}
|
|
6446
|
+
for (const [id, model] of this.availableModels) {
|
|
6447
|
+
if (id !== currentModelId && this.isUsable(model)) return model;
|
|
6194
6448
|
}
|
|
6195
6449
|
return null;
|
|
6196
6450
|
}
|
|
@@ -6266,7 +6520,7 @@ var ModelSelector = class {
|
|
|
6266
6520
|
if (overrideModelId.includes(":")) {
|
|
6267
6521
|
const parts = overrideModelId.split(":");
|
|
6268
6522
|
const prefix = parts[0].toLowerCase();
|
|
6269
|
-
const validProviders = ["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama"];
|
|
6523
|
+
const validProviders = ["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama", "github-models"];
|
|
6270
6524
|
if (validProviders.includes(prefix)) {
|
|
6271
6525
|
providerStr = prefix;
|
|
6272
6526
|
actualId = parts.slice(1).join(":");
|
|
@@ -6286,6 +6540,31 @@ var ModelSelector = class {
|
|
|
6286
6540
|
else if (this.availableProviders.has("openai-compatible")) providerStr = "openai-compatible";
|
|
6287
6541
|
else if (this.availableProviders.size === 1) providerStr = Array.from(this.availableProviders)[0];
|
|
6288
6542
|
}
|
|
6543
|
+
if (providerStr === "github-models" && this.availableProviders.has("github-models")) {
|
|
6544
|
+
const dynamicModel = {
|
|
6545
|
+
id: actualId,
|
|
6546
|
+
name: actualId,
|
|
6547
|
+
provider: "github-models",
|
|
6548
|
+
contextWindow: GITHUB_MODELS_MAX_INPUT_TOKENS,
|
|
6549
|
+
isVisionCapable: false,
|
|
6550
|
+
inputCostPer1kTokens: 0,
|
|
6551
|
+
outputCostPer1kTokens: 0,
|
|
6552
|
+
pricingUnknown: false,
|
|
6553
|
+
maxOutputTokens: GITHUB_MODELS_MAX_OUTPUT_TOKENS,
|
|
6554
|
+
supportsStreaming: true,
|
|
6555
|
+
// Unconfirmed (no live catalog entry to read a real capability from)
|
|
6556
|
+
// must default to false, not left undefined: t3-worker.ts's text-tool
|
|
6557
|
+
// fallback only engages on a strict `=== false`, so an undefined
|
|
6558
|
+
// value here sends native `tools` to an unverified multi-vendor
|
|
6559
|
+
// model and any that don't support function calling reject the
|
|
6560
|
+
// request outright. Same default-to-false-when-unconfirmed policy
|
|
6561
|
+
// listModels() already applies for a real catalog entry.
|
|
6562
|
+
supportsToolUse: false,
|
|
6563
|
+
isLocal: false
|
|
6564
|
+
};
|
|
6565
|
+
this.addDynamicModel(dynamicModel);
|
|
6566
|
+
return dynamicModel;
|
|
6567
|
+
}
|
|
6289
6568
|
if (providerStr && this.availableProviders.has(providerStr)) {
|
|
6290
6569
|
const dynamicModel = withResolvedPricing({
|
|
6291
6570
|
id: actualId,
|
|
@@ -6378,7 +6657,20 @@ var DEFAULT_PROVIDER_TPM = {
|
|
|
6378
6657
|
gemini: 6e4,
|
|
6379
6658
|
azure: 3e4,
|
|
6380
6659
|
"openai-compatible": 3e4,
|
|
6381
|
-
ollama: Number.POSITIVE_INFINITY
|
|
6660
|
+
ollama: Number.POSITIVE_INFINITY,
|
|
6661
|
+
// An order of magnitude below the others, on purpose. GitHub Models is
|
|
6662
|
+
// rate-limited by REQUESTS (Free ≈10 RPM, Pro ≈20, plus low daily caps), and
|
|
6663
|
+
// this bucket meters TOKENS — there is no request-count limiter in this
|
|
6664
|
+
// codebase, and building one is not in scope. The approximation works because
|
|
6665
|
+
// the router reserves `model.maxOutputTokens + 512` per call, and the
|
|
6666
|
+
// provider reports GitHub's real ~4K per-request cap rather than the
|
|
6667
|
+
// underlying model's larger one — so every call withdraws a near-constant
|
|
6668
|
+
// ~4.5K regardless of how short the reply actually turns out to be. At 8K/min
|
|
6669
|
+
// that lands around 1–2 calls per minute of headroom: comfortably inside even
|
|
6670
|
+
// the Free tier, with room for the daily cap to matter more than this does.
|
|
6671
|
+
// Users on a higher tier can raise it with no code change through the generic
|
|
6672
|
+
// `config.rateLimits.providerTpm['github-models']` override.
|
|
6673
|
+
"github-models": 8e3
|
|
6382
6674
|
};
|
|
6383
6675
|
var TpmLimiter = class {
|
|
6384
6676
|
buckets = /* @__PURE__ */ new Map();
|
|
@@ -6725,6 +7017,7 @@ var ModelProfiler = class {
|
|
|
6725
7017
|
(m) => !alreadyProfiled.has(m.id) && !SKIP_PATTERN.test(m.id) && !SKIP_PATTERN.test(m.name)
|
|
6726
7018
|
);
|
|
6727
7019
|
if (toProfile.length === 0) return;
|
|
7020
|
+
const probesHitGithubModels = this.router?.getModelForTier("T3")?.provider === "github-models";
|
|
6728
7021
|
const openRouterModels = await fetchOpenRouterModels();
|
|
6729
7022
|
const orByNormalizedId = /* @__PURE__ */ new Map();
|
|
6730
7023
|
for (const m of openRouterModels) {
|
|
@@ -6739,7 +7032,7 @@ var ModelProfiler = class {
|
|
|
6739
7032
|
if (orMatch?.description) {
|
|
6740
7033
|
specializations = extractSpecializations(orMatch.description);
|
|
6741
7034
|
}
|
|
6742
|
-
if (specializations.length === 0 && this.router) {
|
|
7035
|
+
if (specializations.length === 0 && this.router && !probesHitGithubModels) {
|
|
6743
7036
|
specializations = await queryModelDirectly(this.router);
|
|
6744
7037
|
}
|
|
6745
7038
|
this.store.saveModelProfile(model.id, model.provider, specializations);
|
|
@@ -6964,6 +7257,7 @@ var LiveDataProvider = class {
|
|
|
6964
7257
|
*/
|
|
6965
7258
|
applyLivePricing(models) {
|
|
6966
7259
|
return models.map((m) => {
|
|
7260
|
+
if (m.provider === "github-models") return m;
|
|
6967
7261
|
const p = this.getLivePrice(m.baseModelId ?? m.id);
|
|
6968
7262
|
if (!p) return m;
|
|
6969
7263
|
const reconciled = reconcilePrice(m, p);
|
|
@@ -7002,6 +7296,7 @@ var LiveDataProvider = class {
|
|
|
7002
7296
|
*/
|
|
7003
7297
|
applyLiveCapabilities(models) {
|
|
7004
7298
|
return models.map((m) => {
|
|
7299
|
+
if (m.provider === "github-models") return m;
|
|
7005
7300
|
const c = this.getCapability(m.baseModelId ?? m.id);
|
|
7006
7301
|
if (!c) return m;
|
|
7007
7302
|
const next = { ...m };
|
|
@@ -7305,13 +7600,16 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
7305
7600
|
for (const model of azureDeployments) this.selector.addDynamicModel(model);
|
|
7306
7601
|
availableProviders.add("azure");
|
|
7307
7602
|
}
|
|
7603
|
+
if (availableProviders.has("github-models")) {
|
|
7604
|
+
await this.discoverGitHubModelsCatalog(config);
|
|
7605
|
+
}
|
|
7308
7606
|
await this.validateCloudProviderModels(config);
|
|
7309
7607
|
for (const tier of ["T1", "T2", "T3"]) {
|
|
7310
7608
|
const override = tier === "T1" ? config.models.t1 : tier === "T2" ? config.models.t2 : config.models.t3;
|
|
7311
7609
|
if (!override || override === "auto") continue;
|
|
7312
7610
|
const model = this.selector.selectForTier(tier, override);
|
|
7313
7611
|
if (!model) {
|
|
7314
|
-
const knownProviders = ["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama"];
|
|
7612
|
+
const knownProviders = ["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama", "github-models"];
|
|
7315
7613
|
const hasProviderPrefix = override.includes(":") && knownProviders.some((p) => override.startsWith(p + ":"));
|
|
7316
7614
|
if (hasProviderPrefix) {
|
|
7317
7615
|
const provider = override.split(":")[0];
|
|
@@ -7466,7 +7764,7 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
7466
7764
|
* models without a package upgrade. Mirrors discoverOllamaModels.
|
|
7467
7765
|
*/
|
|
7468
7766
|
async discoverProviderModels() {
|
|
7469
|
-
const cloud = ["anthropic", "openai", "gemini", "azure", "openai-compatible"];
|
|
7767
|
+
const cloud = ["anthropic", "openai", "gemini", "azure", "openai-compatible", "github-models"];
|
|
7470
7768
|
const tasks = cloud.map(async (type) => {
|
|
7471
7769
|
if (!this.selector.isProviderAvailable(type)) return;
|
|
7472
7770
|
const seed = this.getAnyModelForProvider(type);
|
|
@@ -7521,6 +7819,9 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
7521
7819
|
this.ensureProvider(options.model, this.config.providers);
|
|
7522
7820
|
}
|
|
7523
7821
|
let model = requireVision ? this.selector.selectVisionModel() : options.model ?? this.tierModels.get(tier) ?? this.selector.selectForTier(tier) ?? void 0;
|
|
7822
|
+
if (requireVision && model) {
|
|
7823
|
+
this.ensureProvider(model, this.config.providers);
|
|
7824
|
+
}
|
|
7524
7825
|
if (options.forceLocal && model && !this.isPrivateModel(model)) {
|
|
7525
7826
|
const localModel = this.selector.getAllAvailableModels().find((m) => this.isPrivateModel(m));
|
|
7526
7827
|
if (!localModel) {
|
|
@@ -7534,7 +7835,8 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
7534
7835
|
if (!model) throw new Error(`No model available for tier ${tier}`);
|
|
7535
7836
|
const provider = this.getProvider(model);
|
|
7536
7837
|
if (!provider) throw new Error(`No provider for model ${model.id}`);
|
|
7537
|
-
const
|
|
7838
|
+
const requestedTokens = options.maxTokens ?? model.maxOutputTokens ?? 1024;
|
|
7839
|
+
const estimatedTokens = Math.min(requestedTokens, model.maxOutputTokens ?? requestedTokens) + 512;
|
|
7538
7840
|
if (this.tpmLimiter) {
|
|
7539
7841
|
await this.tpmLimiter.acquire(model.provider, estimatedTokens);
|
|
7540
7842
|
}
|
|
@@ -7625,7 +7927,8 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
7625
7927
|
});
|
|
7626
7928
|
releaseLocalSlot?.();
|
|
7627
7929
|
releaseLocalSlot = void 0;
|
|
7628
|
-
|
|
7930
|
+
const retryOpts = options.model && options.model.id === model.id ? { ...options, model: void 0 } : options;
|
|
7931
|
+
return this.generate(tier, retryOpts, onChunk, requireVision);
|
|
7629
7932
|
}
|
|
7630
7933
|
}
|
|
7631
7934
|
if (isModelNotFoundError(errMsg)) {
|
|
@@ -7976,6 +8279,26 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
7976
8279
|
return false;
|
|
7977
8280
|
}
|
|
7978
8281
|
}
|
|
8282
|
+
/**
|
|
8283
|
+
* Registers GitHub Models' real catalog ids via addDynamicModel so init()'s
|
|
8284
|
+
* tier-fill loop can pick one. Registration alone does not create a bound
|
|
8285
|
+
* BaseProvider instance (see ensureProvider) — the tier-fill loop's own
|
|
8286
|
+
* ensureProvider call does that for whichever model it actually selects,
|
|
8287
|
+
* which is why this runs BEFORE that loop rather than only in the
|
|
8288
|
+
* background discoverProviderModels() refresh.
|
|
8289
|
+
*/
|
|
8290
|
+
async discoverGitHubModelsCatalog(config) {
|
|
8291
|
+
const cfg = config.providers.find((p) => p.type === "github-models") ?? { type: "github-models" };
|
|
8292
|
+
const seed = this.getAnyModelForProvider("github-models");
|
|
8293
|
+
if (!seed) return;
|
|
8294
|
+
try {
|
|
8295
|
+
const provider = new GitHubModelsProvider(cfg, seed);
|
|
8296
|
+
const models = await provider.listModels();
|
|
8297
|
+
for (const m of models) this.selector.addDynamicModel(m);
|
|
8298
|
+
} catch (err) {
|
|
8299
|
+
console.warn("[router] GitHub Models catalog discovery failed:", err instanceof Error ? err.message : err);
|
|
8300
|
+
}
|
|
8301
|
+
}
|
|
7979
8302
|
ensureProvider(model, configs) {
|
|
7980
8303
|
const key = `${model.provider}:${model.id}`;
|
|
7981
8304
|
if (this.providers.has(key)) return;
|
|
@@ -8000,6 +8323,8 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
8000
8323
|
return new OllamaProvider(cfg, model);
|
|
8001
8324
|
case "openai-compatible":
|
|
8002
8325
|
return new OpenAICompatibleProvider(cfg, model);
|
|
8326
|
+
case "github-models":
|
|
8327
|
+
return new GitHubModelsProvider(cfg, model);
|
|
8003
8328
|
default:
|
|
8004
8329
|
throw new Error(`Unsupported provider type: ${String(cfg.type)}`);
|
|
8005
8330
|
}
|
|
@@ -8007,7 +8332,7 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
8007
8332
|
getAnyModelForProvider(type) {
|
|
8008
8333
|
const fromCatalog = Object.values(MODELS).find((m) => m.provider === type);
|
|
8009
8334
|
if (fromCatalog) return fromCatalog;
|
|
8010
|
-
if (type === "openai-compatible" || type === "azure") {
|
|
8335
|
+
if (type === "openai-compatible" || type === "azure" || type === "github-models") {
|
|
8011
8336
|
return {
|
|
8012
8337
|
id: type,
|
|
8013
8338
|
name: type,
|
|
@@ -20097,7 +20422,10 @@ async function refreshModelCache(store, providers) {
|
|
|
20097
20422
|
else if (provider.type === "anthropic") instance = new AnthropicProvider(provider, dummyModel);
|
|
20098
20423
|
else if (provider.type === "ollama") instance = new OllamaProvider(provider, dummyModel);
|
|
20099
20424
|
else if (provider.type === "openai-compatible") instance = new OpenAICompatibleProvider(provider, dummyModel);
|
|
20100
|
-
else if (provider.type === "
|
|
20425
|
+
else if (provider.type === "github-models") {
|
|
20426
|
+
const { GitHubModelsProvider: GitHubModelsProvider2 } = await Promise.resolve().then(() => (init_github_models(), github_models_exports));
|
|
20427
|
+
instance = new GitHubModelsProvider2(provider, dummyModel);
|
|
20428
|
+
} else if (provider.type === "azure") {
|
|
20101
20429
|
const { AzureOpenAIProvider: AzureOpenAIProvider2 } = await Promise.resolve().then(() => (init_azure(), azure_exports));
|
|
20102
20430
|
instance = new AzureOpenAIProvider2(provider, dummyModel);
|
|
20103
20431
|
}
|
|
@@ -21204,7 +21532,7 @@ async function validateConfiguredModels(config) {
|
|
|
21204
21532
|
function inferProviderFromModelId(id, providers) {
|
|
21205
21533
|
if (id.includes(":")) {
|
|
21206
21534
|
const prefix = id.split(":")[0].toLowerCase();
|
|
21207
|
-
const validProviders = ["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama"];
|
|
21535
|
+
const validProviders = ["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama", "github-models"];
|
|
21208
21536
|
if (validProviders.includes(prefix)) {
|
|
21209
21537
|
return prefix;
|
|
21210
21538
|
}
|
|
@@ -21501,9 +21829,10 @@ var PROVIDER_LABELS = {
|
|
|
21501
21829
|
gemini: "Google Gemini",
|
|
21502
21830
|
azure: "Azure OpenAI",
|
|
21503
21831
|
ollama: "Ollama (local)",
|
|
21504
|
-
"openai-compatible": "OpenAI-Compatible"
|
|
21832
|
+
"openai-compatible": "OpenAI-Compatible",
|
|
21833
|
+
"github-models": "GitHub Models"
|
|
21505
21834
|
};
|
|
21506
|
-
var providerOrder = ["anthropic", "openai", "gemini", "azure", "ollama", "openai-compatible"];
|
|
21835
|
+
var providerOrder = ["anthropic", "openai", "gemini", "azure", "ollama", "openai-compatible", "github-models"];
|
|
21507
21836
|
function buildInitialEntries(types) {
|
|
21508
21837
|
return [...types].map((type) => ({
|
|
21509
21838
|
id: randomUUID(),
|
|
@@ -21675,6 +22004,13 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
21675
22004
|
const fetched = await p.listModels();
|
|
21676
22005
|
fetched.forEach((m) => models.push({ id: m.id, name: m.name, providerLabel: entry.label }));
|
|
21677
22006
|
dispatchRef.current({ type: "SET_FETCH_LOG", line: ` \u2714 ${entry.label} \u2014 ${fetched.length} models` });
|
|
22007
|
+
} else if (type === "github-models") {
|
|
22008
|
+
const { GitHubModelsProvider: GitHubModelsProvider2 } = await Promise.resolve().then(() => (init_github_models(), github_models_exports));
|
|
22009
|
+
const dummyModel = { id: "dummy", name: "dummy", provider: type, contextWindow: 0, isVisionCapable: false, inputCostPer1kTokens: 0, outputCostPer1kTokens: 0, maxOutputTokens: 0, supportsStreaming: false, isLocal: false };
|
|
22010
|
+
const p = new GitHubModelsProvider2({ type, apiKey }, dummyModel);
|
|
22011
|
+
const fetched = await p.listModels();
|
|
22012
|
+
fetched.forEach((m) => models.push({ id: `${type}:${m.id}`, name: m.name, providerLabel: entry.label }));
|
|
22013
|
+
dispatchRef.current({ type: "SET_FETCH_LOG", line: ` \u2714 ${entry.label} \u2014 ${fetched.length} models` });
|
|
21678
22014
|
} else if (type === "gemini") {
|
|
21679
22015
|
const { GeminiProvider: GeminiProvider2 } = await Promise.resolve().then(() => (init_gemini(), gemini_exports));
|
|
21680
22016
|
const dummyModel = { id: "dummy", name: "dummy", provider: type, contextWindow: 0, isVisionCapable: false, inputCostPer1kTokens: 0, outputCostPer1kTokens: 0, maxOutputTokens: 0, supportsStreaming: false, isLocal: false };
|
|
@@ -21853,7 +22189,8 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
21853
22189
|
/* @__PURE__ */ jsx(Text, { color: focused ? theme.colors.primary : selected ? theme.colors.foreground : theme.colors.muted, children: PROVIDER_LABELS[p] }),
|
|
21854
22190
|
p === "azure" && /* @__PURE__ */ jsx(Text, { color: theme.colors.muted, dimColor: true, children: " \u2014 multiple deployments supported" }),
|
|
21855
22191
|
p === "openai-compatible" && /* @__PURE__ */ jsx(Text, { color: theme.colors.muted, dimColor: true, children: " \u2014 Groq, Together, custom" }),
|
|
21856
|
-
p === "ollama" && /* @__PURE__ */ jsx(Text, { color: theme.colors.muted, dimColor: true, children: " \u2014 no API key needed" })
|
|
22192
|
+
p === "ollama" && /* @__PURE__ */ jsx(Text, { color: theme.colors.muted, dimColor: true, children: " \u2014 no API key needed" }),
|
|
22193
|
+
p === "github-models" && /* @__PURE__ */ jsx(Text, { color: theme.colors.muted, dimColor: true, children: " \u2014 GitHub PAT with `models: read`" })
|
|
21857
22194
|
] }, p);
|
|
21858
22195
|
}) }),
|
|
21859
22196
|
/* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { color: theme.colors.muted, dimColor: true, children: "space select \xB7 a all \xB7 i invert \xB7 enter continue" }) }),
|
|
@@ -22260,7 +22597,8 @@ async function doctorCommand() {
|
|
|
22260
22597
|
{ type: "anthropic", name: "Anthropic" },
|
|
22261
22598
|
{ type: "openai", name: "OpenAI" },
|
|
22262
22599
|
{ type: "gemini", name: "Google Gemini" },
|
|
22263
|
-
{ type: "azure", name: "Azure OpenAI" }
|
|
22600
|
+
{ type: "azure", name: "Azure OpenAI" },
|
|
22601
|
+
{ type: "github-models", name: "GitHub Models" }
|
|
22264
22602
|
];
|
|
22265
22603
|
for (const { type, name } of providers) {
|
|
22266
22604
|
const key = cm.getApiKey(type);
|