@jacobbd/relay-ai 0.4.8 → 0.5.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/README.md +3 -1
- package/dist/{chunk-I3SSHXSP.js → chunk-44KQK6Y5.js} +294 -24
- package/dist/chunk-44KQK6Y5.js.map +1 -0
- package/dist/{chunk-DO5FAMNC.js → chunk-MVBA7ABV.js} +22 -2
- package/dist/chunk-MVBA7ABV.js.map +1 -0
- package/dist/cli.js +6 -6
- package/dist/cli.js.map +1 -1
- package/dist/{provider-templates-LYGIFRRT.js → provider-templates-6XYKAZB5.js} +2 -2
- package/dist/ui/public/app.js +182 -200
- package/dist/ui/public/oauth-device.js +30 -0
- package/dist/ui/public/provider-logo.js +63 -0
- package/dist/ui/public/style.css +123 -0
- package/dist/{ui-command-P27KHEJG.js → ui-command-M7KMKIQC.js} +20 -9
- package/dist/ui-command-M7KMKIQC.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-DO5FAMNC.js.map +0 -1
- package/dist/chunk-I3SSHXSP.js.map +0 -1
- package/dist/ui-command-P27KHEJG.js.map +0 -1
- /package/dist/{provider-templates-LYGIFRRT.js.map → provider-templates-6XYKAZB5.js.map} +0 -0
package/README.md
CHANGED
|
@@ -189,7 +189,7 @@ Opens a browser-based dashboard on a random local port. From the UI you can:
|
|
|
189
189
|
- **Keep Claude Code's Anthropic login** — on the Claude Code CLI card, check **Keep my Anthropic login and add Relay models** to keep your normal Claude models while adding the selected Relay model and compatible favorites. This option is not shown for Claude Desktop.
|
|
190
190
|
- **Manage General Favorites** — the sidebar shows your saved favorite models with a slot indicator (Slots used X/20). Favorites launch through all supported agents.
|
|
191
191
|
- **Manage Antigravity Favorites** — separate favorites panel for Antigravity sessions.
|
|
192
|
-
- **Manage providers** — add providers from templates, delete providers, and refresh model lists inline, all without leaving the browser.
|
|
192
|
+
- **Manage providers** — add providers from templates, delete providers, and refresh model lists inline, all without leaving the browser. For GitHub Copilot, ChatGPT, and xAI OAuth, the UI displays a one-time device code with **Copy code** and **Open sign-in page** buttons so you can complete sign-in without using the terminal. Connected Copilot cards also identify the account as Free, Paid, or Plan unverified so the visible model catalog is easier to understand.
|
|
193
193
|
- **Run the Server tab** — configure and start the same gateway as `relay-ai server` (favorites-only or specific providers, discovery id masking, local/network listen mode) and see the resulting URLs, API key, and model catalog right in the browser. Runs in the same process as the UI, so it stops when you close the dashboard. See [Registry gateway (`relay-ai server`)](#registry-gateway-relay-ai-server) below for what each option does.
|
|
194
194
|
|
|
195
195
|
Press `Ctrl+C` in the terminal where `relay-ai ui` is running to shut down the dashboard server (this also stops the gateway if you started it from the Server tab).
|
|
@@ -409,6 +409,8 @@ Read the full setup and risk notes in **[docs/ANTIGRAVITY.md](docs/ANTIGRAVITY.m
|
|
|
409
409
|
|
|
410
410
|
relay-ai supports OAuth providers that use device-code sign-in, so you can connect an existing subscription without pasting an API key. See **[docs/SUBSCRIPTION-OAUTH.md](docs/SUBSCRIPTION-OAUTH.md)** for setup details.
|
|
411
411
|
|
|
412
|
+
In `relay-ai ui`, select one of the OAuth providers and click **Get sign-in code**. Relay AI shows the code with a Copy button; click **Open sign-in page**, paste the code in the provider's page, and return to the UI while it finishes connecting.
|
|
413
|
+
|
|
412
414
|
Device code flows for existing subscriptions:
|
|
413
415
|
|
|
414
416
|
```bash
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
getTemplateById,
|
|
4
4
|
init_provider_templates
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-MVBA7ABV.js";
|
|
6
6
|
|
|
7
7
|
// src/constants.ts
|
|
8
8
|
import { homedir } from "os";
|
|
@@ -11,7 +11,7 @@ import { join } from "path";
|
|
|
11
11
|
// package.json
|
|
12
12
|
var package_default = {
|
|
13
13
|
name: "@jacobbd/relay-ai",
|
|
14
|
-
version: "0.
|
|
14
|
+
version: "0.5.0",
|
|
15
15
|
publishConfig: {
|
|
16
16
|
access: "public"
|
|
17
17
|
},
|
|
@@ -2409,7 +2409,8 @@ function decodeAuthEntry(value) {
|
|
|
2409
2409
|
refresh: record["refresh"],
|
|
2410
2410
|
expires: record["expires"],
|
|
2411
2411
|
accountId: typeof record["accountId"] === "string" ? record["accountId"] : void 0,
|
|
2412
|
-
enterpriseUrl: typeof record["enterpriseUrl"] === "string" ? record["enterpriseUrl"] : void 0
|
|
2412
|
+
enterpriseUrl: typeof record["enterpriseUrl"] === "string" ? record["enterpriseUrl"] : void 0,
|
|
2413
|
+
providerData: record["providerData"] && typeof record["providerData"] === "object" && !Array.isArray(record["providerData"]) ? record["providerData"] : void 0
|
|
2413
2414
|
};
|
|
2414
2415
|
}
|
|
2415
2416
|
if (record["type"] === "wellknown" && typeof record["key"] === "string" && typeof record["token"] === "string") {
|
|
@@ -2460,13 +2461,14 @@ function oauthCredentialToKeychainJson(cred) {
|
|
|
2460
2461
|
|
|
2461
2462
|
// src/oauth/types.ts
|
|
2462
2463
|
function tokensToStoredCredential(tokens, existingRefresh, accountId, providerData) {
|
|
2464
|
+
const mergedProviderData = providerData || tokens.providerData ? { ...providerData, ...tokens.providerData } : void 0;
|
|
2463
2465
|
return {
|
|
2464
2466
|
type: "oauth",
|
|
2465
2467
|
access: tokens.access_token,
|
|
2466
2468
|
refresh: tokens.refresh_token ?? existingRefresh ?? "",
|
|
2467
2469
|
expires: Date.now() + (tokens.expires_in ?? 3600) * 1e3,
|
|
2468
2470
|
...accountId ? { accountId } : {},
|
|
2469
|
-
...
|
|
2471
|
+
...mergedProviderData ? { providerData: mergedProviderData } : {}
|
|
2470
2472
|
};
|
|
2471
2473
|
}
|
|
2472
2474
|
function parseStoredOAuthCredential(raw) {
|
|
@@ -2512,10 +2514,16 @@ var CLIENT_ID2 = "Iv1.b507a08c87ecfe98";
|
|
|
2512
2514
|
var DEVICE_CODE_URL = "https://github.com/login/device/code";
|
|
2513
2515
|
var TOKEN_URL2 = "https://github.com/login/oauth/access_token";
|
|
2514
2516
|
var COPILOT_TOKEN_URL = "https://api.github.com/copilot_internal/v2/token";
|
|
2517
|
+
var COPILOT_USER_URL = "https://api.github.com/copilot_internal/user";
|
|
2515
2518
|
var SCOPE = "copilot";
|
|
2516
2519
|
var DEVICE_CODE_DEFAULT_INTERVAL_MS = 5e3;
|
|
2517
2520
|
var DEVICE_CODE_DEFAULT_EXPIRES_MS2 = 15 * 60 * 1e3;
|
|
2518
2521
|
var OAUTH_POLLING_SAFETY_MARGIN_MS2 = 1e3;
|
|
2522
|
+
var FREE_COPILOT_SKUS = /* @__PURE__ */ new Set([
|
|
2523
|
+
"free_limited_copilot",
|
|
2524
|
+
"free_educational_quota",
|
|
2525
|
+
"no_auth_limited_copilot"
|
|
2526
|
+
]);
|
|
2519
2527
|
function commonHeaders() {
|
|
2520
2528
|
return {
|
|
2521
2529
|
Accept: "application/json",
|
|
@@ -2523,6 +2531,46 @@ function commonHeaders() {
|
|
|
2523
2531
|
"User-Agent": `relay-ai/${VERSION}`
|
|
2524
2532
|
};
|
|
2525
2533
|
}
|
|
2534
|
+
function classifyCopilotAccount(user) {
|
|
2535
|
+
const login = typeof user["login"] === "string" && user["login"].trim() ? user["login"].trim() : void 0;
|
|
2536
|
+
const sku = typeof user["access_type_sku"] === "string" && user["access_type_sku"].trim() ? user["access_type_sku"].trim() : void 0;
|
|
2537
|
+
const plan = typeof user["copilot_plan"] === "string" && user["copilot_plan"].trim() ? user["copilot_plan"].trim() : void 0;
|
|
2538
|
+
if (!sku && !plan) {
|
|
2539
|
+
return {
|
|
2540
|
+
...login ? { login } : {},
|
|
2541
|
+
lookup_status: "unknown"
|
|
2542
|
+
};
|
|
2543
|
+
}
|
|
2544
|
+
const isFree = FREE_COPILOT_SKUS.has(sku?.toLowerCase() ?? "") || plan?.toLowerCase() === "free";
|
|
2545
|
+
return {
|
|
2546
|
+
...login ? { login } : {},
|
|
2547
|
+
...sku ? { access_type_sku: sku } : {},
|
|
2548
|
+
...plan ? { copilot_plan: plan } : {},
|
|
2549
|
+
is_free_plan: isFree,
|
|
2550
|
+
lookup_status: "known"
|
|
2551
|
+
};
|
|
2552
|
+
}
|
|
2553
|
+
async function fetchCopilotAccount(ghuToken) {
|
|
2554
|
+
const response = await fetch(COPILOT_USER_URL, {
|
|
2555
|
+
method: "GET",
|
|
2556
|
+
headers: {
|
|
2557
|
+
Authorization: `Bearer ${ghuToken}`,
|
|
2558
|
+
Accept: "application/json",
|
|
2559
|
+
"User-Agent": `relay-ai/${VERSION}`,
|
|
2560
|
+
"Editor-Version": "vscode/1.85.1",
|
|
2561
|
+
"X-GitHub-Api-Version": "2025-04-01"
|
|
2562
|
+
}
|
|
2563
|
+
});
|
|
2564
|
+
if (!response.ok) {
|
|
2565
|
+
const detail = await response.text().catch(() => "");
|
|
2566
|
+
throw new Error(`GitHub Copilot account lookup failed (${response.status})${detail ? `: ${detail}` : ""}`);
|
|
2567
|
+
}
|
|
2568
|
+
const json = await response.json();
|
|
2569
|
+
if (!json || typeof json !== "object" || Array.isArray(json)) {
|
|
2570
|
+
throw new Error("GitHub Copilot account lookup returned invalid JSON");
|
|
2571
|
+
}
|
|
2572
|
+
return classifyCopilotAccount(json);
|
|
2573
|
+
}
|
|
2526
2574
|
async function requestGithubDeviceCode() {
|
|
2527
2575
|
const response = await fetch(DEVICE_CODE_URL, {
|
|
2528
2576
|
method: "POST",
|
|
@@ -2561,7 +2609,16 @@ async function exchangeForCopilotToken(ghuToken) {
|
|
|
2561
2609
|
const expiresMs = new Date(json.expires_at).getTime() - Date.now();
|
|
2562
2610
|
if (expiresMs > 0) expiresIn = Math.floor(expiresMs / 1e3);
|
|
2563
2611
|
}
|
|
2564
|
-
|
|
2612
|
+
let account = { lookup_status: "unknown" };
|
|
2613
|
+
try {
|
|
2614
|
+
account = await fetchCopilotAccount(ghuToken);
|
|
2615
|
+
} catch {
|
|
2616
|
+
}
|
|
2617
|
+
return {
|
|
2618
|
+
access_token: json.token,
|
|
2619
|
+
expires_in: expiresIn,
|
|
2620
|
+
providerData: { copilot: account }
|
|
2621
|
+
};
|
|
2565
2622
|
}
|
|
2566
2623
|
async function refreshGithubCopilotToken(ghuToken) {
|
|
2567
2624
|
const copilot = await exchangeForCopilotToken(ghuToken);
|
|
@@ -2598,7 +2655,8 @@ async function pollGithubDeviceCodeToken(device, opts) {
|
|
|
2598
2655
|
access_token: copilot.access_token,
|
|
2599
2656
|
refresh_token: ghuToken,
|
|
2600
2657
|
// store ghu_ as refresh for re-exchange later
|
|
2601
|
-
expires_in: copilot.expires_in
|
|
2658
|
+
expires_in: copilot.expires_in,
|
|
2659
|
+
providerData: copilot.providerData
|
|
2602
2660
|
};
|
|
2603
2661
|
}
|
|
2604
2662
|
if (error === "authorization_pending") {
|
|
@@ -3105,6 +3163,28 @@ async function resolveProviderOAuthProviderData(authRef, diag) {
|
|
|
3105
3163
|
const raw = await readKeyringAccount(parsed.account, diag);
|
|
3106
3164
|
return parseStoredOAuthCredential(raw)?.providerData;
|
|
3107
3165
|
}
|
|
3166
|
+
async function enrichGithubCopilotOAuthProviderData(authRef, diag) {
|
|
3167
|
+
const parsed = parseAuthRef(authRef);
|
|
3168
|
+
if (!parsed || parsed.kind !== "keyring" || oauthProviderIdFromAccount(parsed.account) !== "github-copilot") {
|
|
3169
|
+
return void 0;
|
|
3170
|
+
}
|
|
3171
|
+
const raw = await readKeyringAccount(parsed.account, diag);
|
|
3172
|
+
const credential = parseStoredOAuthCredential(raw);
|
|
3173
|
+
if (!credential?.refresh) return credential?.providerData;
|
|
3174
|
+
try {
|
|
3175
|
+
const summary = await fetchCopilotAccount(credential.refresh);
|
|
3176
|
+
const providerData = { ...credential.providerData, copilot: summary };
|
|
3177
|
+
await writeKeyringAccount(
|
|
3178
|
+
parsed.account,
|
|
3179
|
+
oauthCredentialToKeychainJson({ ...credential, providerData }),
|
|
3180
|
+
diag
|
|
3181
|
+
);
|
|
3182
|
+
return providerData;
|
|
3183
|
+
} catch (err) {
|
|
3184
|
+
diag?.(`GitHub Copilot plan lookup unavailable \u2014 ${err instanceof Error ? err.message : String(err)}`);
|
|
3185
|
+
return credential.providerData;
|
|
3186
|
+
}
|
|
3187
|
+
}
|
|
3108
3188
|
function decodeProviderSecret(raw) {
|
|
3109
3189
|
if (!raw) return null;
|
|
3110
3190
|
const trimmed = raw.trim();
|
|
@@ -3333,6 +3413,14 @@ function migrateOAuthXaiProvider(registry) {
|
|
|
3333
3413
|
};
|
|
3334
3414
|
return true;
|
|
3335
3415
|
}
|
|
3416
|
+
function migrateAlibabaDashScopeChinaLabel(registry) {
|
|
3417
|
+
const provider = registry.providers.find(
|
|
3418
|
+
(p8) => p8.id === "alibaba" && p8.templateId === "alibaba" && p8.name === "Alibaba DashScope" && p8.api.url === "https://dashscope.aliyuncs.com/compatible-mode/v1"
|
|
3419
|
+
);
|
|
3420
|
+
if (!provider) return false;
|
|
3421
|
+
provider.name = "Alibaba DashScope (China)";
|
|
3422
|
+
return true;
|
|
3423
|
+
}
|
|
3336
3424
|
|
|
3337
3425
|
// src/registry/validate.ts
|
|
3338
3426
|
var PROVIDER_ID_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
|
|
@@ -3443,6 +3531,7 @@ function loadRegistry(path = getProvidersPath()) {
|
|
|
3443
3531
|
let migrated = migrateLegacyCloudProviders(registry);
|
|
3444
3532
|
if (migrateOAuthOpenAiProvider(registry)) migrated = true;
|
|
3445
3533
|
if (migrateOAuthXaiProvider(registry)) migrated = true;
|
|
3534
|
+
if (migrateAlibabaDashScopeChinaLabel(registry)) migrated = true;
|
|
3446
3535
|
if (migrated) {
|
|
3447
3536
|
try {
|
|
3448
3537
|
saveRegistry(registry, path);
|
|
@@ -3520,11 +3609,15 @@ var TEMPLATE_TO_PRICING_PLATFORM = {
|
|
|
3520
3609
|
openai: "openai",
|
|
3521
3610
|
google: "google_ai_studio",
|
|
3522
3611
|
alibaba: "alibaba",
|
|
3612
|
+
"qwen-cloud-payg": "alibaba",
|
|
3523
3613
|
openrouter: "openrouter",
|
|
3524
3614
|
anthropic: "anthropic",
|
|
3525
3615
|
nvidia: "nvidia",
|
|
3526
3616
|
venice: "openrouter"
|
|
3527
3617
|
};
|
|
3618
|
+
var PRICING_OPT_OUT_TEMPLATE_IDS = /* @__PURE__ */ new Set([
|
|
3619
|
+
"qwen-cloud-token-plan"
|
|
3620
|
+
]);
|
|
3528
3621
|
function loadBundledPricingCache() {
|
|
3529
3622
|
return pricing_cache_default;
|
|
3530
3623
|
}
|
|
@@ -3647,13 +3740,27 @@ function enrichModelsWithPricing(models, index, platform) {
|
|
|
3647
3740
|
return { ...model, cost, isFree: isFreeStatus(freeStatus), freeStatus };
|
|
3648
3741
|
});
|
|
3649
3742
|
}
|
|
3743
|
+
function enrichModelsForProviderPricing(models, index, templateId, providerId) {
|
|
3744
|
+
if (PRICING_OPT_OUT_TEMPLATE_IDS.has(templateId) || PRICING_OPT_OUT_TEMPLATE_IDS.has(providerId)) {
|
|
3745
|
+
return models.map(({ cost: _cost, isFree: _isFree, freeStatus: _freeStatus, ...model }) => model);
|
|
3746
|
+
}
|
|
3747
|
+
return enrichModelsWithPricing(
|
|
3748
|
+
models,
|
|
3749
|
+
index,
|
|
3750
|
+
pricingPlatformForProvider(templateId, providerId)
|
|
3751
|
+
);
|
|
3752
|
+
}
|
|
3650
3753
|
function applyPricingToRegistryProviders(registry, cache) {
|
|
3651
3754
|
const index = buildPricingIndex(cache);
|
|
3652
3755
|
let changed = false;
|
|
3653
3756
|
for (const provider of registry.providers) {
|
|
3654
3757
|
if (!provider.modelsCache?.models.length) continue;
|
|
3655
|
-
const
|
|
3656
|
-
|
|
3758
|
+
const enriched = enrichModelsForProviderPricing(
|
|
3759
|
+
provider.modelsCache.models,
|
|
3760
|
+
index,
|
|
3761
|
+
provider.templateId,
|
|
3762
|
+
provider.id
|
|
3763
|
+
);
|
|
3657
3764
|
if (JSON.stringify(enriched) !== JSON.stringify(provider.modelsCache.models)) {
|
|
3658
3765
|
provider.modelsCache = { ...provider.modelsCache, models: enriched };
|
|
3659
3766
|
changed = true;
|
|
@@ -3699,6 +3806,8 @@ var REGISTRY_TO_MODELS_DEV = {
|
|
|
3699
3806
|
perplexity: "perplexity",
|
|
3700
3807
|
cohere: "cohere",
|
|
3701
3808
|
alibaba: "alibaba",
|
|
3809
|
+
"qwen-cloud-token-plan": "alibaba-token-plan",
|
|
3810
|
+
"qwen-cloud-payg": "alibaba",
|
|
3702
3811
|
openrouter: "openrouter",
|
|
3703
3812
|
anthropic: "anthropic",
|
|
3704
3813
|
nvidia: "nvidia",
|
|
@@ -6781,6 +6890,106 @@ function materializeRegistry(registry, resolveCredential, opts) {
|
|
|
6781
6890
|
return result;
|
|
6782
6891
|
}
|
|
6783
6892
|
|
|
6893
|
+
// src/registry/copilot-models.ts
|
|
6894
|
+
var FREE_MODEL_IDS = /* @__PURE__ */ new Set([
|
|
6895
|
+
"gpt-4.1",
|
|
6896
|
+
"gpt-4o",
|
|
6897
|
+
"gpt-4o-mini",
|
|
6898
|
+
"raptor-mini",
|
|
6899
|
+
"goldeneye"
|
|
6900
|
+
]);
|
|
6901
|
+
var FREE_CHAT_BLOCKLIST = /* @__PURE__ */ new Set(["gpt-5-mini"]);
|
|
6902
|
+
function copilotPlanTier(providerData) {
|
|
6903
|
+
const copilot = providerData?.["copilot"];
|
|
6904
|
+
if (!copilot || typeof copilot !== "object" || Array.isArray(copilot)) return "unknown";
|
|
6905
|
+
const summary = copilot;
|
|
6906
|
+
if (summary["lookup_status"] === "unknown") return "unknown";
|
|
6907
|
+
if (summary["is_free_plan"] === true) return "free";
|
|
6908
|
+
if (summary["is_free_plan"] === false) return "paid";
|
|
6909
|
+
return "unknown";
|
|
6910
|
+
}
|
|
6911
|
+
function normalizeCopilotModels(rows, tier) {
|
|
6912
|
+
const models = [];
|
|
6913
|
+
for (const value of rows) {
|
|
6914
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) continue;
|
|
6915
|
+
const row = value;
|
|
6916
|
+
const id = typeof row["id"] === "string" ? row["id"].trim() : "";
|
|
6917
|
+
if (!id || !copilotModelAllowed(row, tier)) continue;
|
|
6918
|
+
const lowerId = id.toLowerCase();
|
|
6919
|
+
const isFree = tier !== "paid" || copilotModelIsIncluded(row);
|
|
6920
|
+
const family = lowerId.split(/[-/:]/)[0] ?? lowerId;
|
|
6921
|
+
const contextWindow = numericValue(row["context_length"]) ?? numericValue(row["contextWindow"]) ?? numericValue(row["context_window"]) ?? resolveContextWindow(id);
|
|
6922
|
+
models.push({
|
|
6923
|
+
id,
|
|
6924
|
+
name: `${id} [Copilot]`,
|
|
6925
|
+
upstreamModelId: id,
|
|
6926
|
+
family,
|
|
6927
|
+
brand: deriveBrand(family),
|
|
6928
|
+
contextWindow,
|
|
6929
|
+
isFree,
|
|
6930
|
+
freeStatus: isFree ? "verified_free" : "unknown",
|
|
6931
|
+
modelFormat: "openai",
|
|
6932
|
+
npm: "@ai-sdk/openai-compatible",
|
|
6933
|
+
apiUrl: "https://api.githubcopilot.com"
|
|
6934
|
+
});
|
|
6935
|
+
}
|
|
6936
|
+
return models;
|
|
6937
|
+
}
|
|
6938
|
+
function filterCachedCopilotModels(models, tier) {
|
|
6939
|
+
return models.flatMap((model) => {
|
|
6940
|
+
const id = model.id.toLowerCase();
|
|
6941
|
+
if (!copilotIdIsCallable(id)) return [];
|
|
6942
|
+
if (tier !== "paid" && (!FREE_MODEL_IDS.has(id) || FREE_CHAT_BLOCKLIST.has(id))) return [];
|
|
6943
|
+
if (tier === "paid") return [model];
|
|
6944
|
+
return [{ ...model, isFree: true, freeStatus: "verified_free" }];
|
|
6945
|
+
});
|
|
6946
|
+
}
|
|
6947
|
+
function copilotModelAllowed(row, tier) {
|
|
6948
|
+
const id = String(row["id"] ?? "").toLowerCase();
|
|
6949
|
+
if (!copilotIdIsCallable(id)) return false;
|
|
6950
|
+
if (row["model_picker_enabled"] === false) return false;
|
|
6951
|
+
const policy = row["policy"];
|
|
6952
|
+
if (policy && typeof policy === "object" && !Array.isArray(policy)) {
|
|
6953
|
+
if (String(policy["state"] ?? "").toLowerCase() === "disabled") return false;
|
|
6954
|
+
}
|
|
6955
|
+
const capabilities = row["capabilities"];
|
|
6956
|
+
if (capabilities && typeof capabilities === "object" && !Array.isArray(capabilities)) {
|
|
6957
|
+
const family = String(capabilities["family"] ?? "").toLowerCase();
|
|
6958
|
+
if (family.includes("embedding")) return false;
|
|
6959
|
+
}
|
|
6960
|
+
const endpoints = row["supported_endpoints"];
|
|
6961
|
+
if (Array.isArray(endpoints) && endpoints.length > 0) {
|
|
6962
|
+
const supportsChat = endpoints.some((endpoint) => {
|
|
6963
|
+
const normalized = String(endpoint).toLowerCase().replace(/\/$/, "");
|
|
6964
|
+
return normalized.endsWith("/chat/completions") || normalized === "chat/completions";
|
|
6965
|
+
});
|
|
6966
|
+
if (!supportsChat) return false;
|
|
6967
|
+
}
|
|
6968
|
+
if (tier !== "paid" && (!FREE_MODEL_IDS.has(id) || FREE_CHAT_BLOCKLIST.has(id))) return false;
|
|
6969
|
+
return true;
|
|
6970
|
+
}
|
|
6971
|
+
function copilotIdIsCallable(id) {
|
|
6972
|
+
return id !== "auto" && !id.endsWith("-auto") && !id.includes("embedding");
|
|
6973
|
+
}
|
|
6974
|
+
function copilotModelIsIncluded(row) {
|
|
6975
|
+
const id = String(row["id"] ?? "").toLowerCase();
|
|
6976
|
+
if (FREE_CHAT_BLOCKLIST.has(id) || !copilotIdIsCallable(id)) return false;
|
|
6977
|
+
const billing = row["billing"];
|
|
6978
|
+
if (billing && typeof billing === "object" && !Array.isArray(billing)) {
|
|
6979
|
+
const multiplier = numericValue(billing["multiplier"]);
|
|
6980
|
+
if (multiplier !== void 0) return multiplier === 0;
|
|
6981
|
+
}
|
|
6982
|
+
return FREE_MODEL_IDS.has(id);
|
|
6983
|
+
}
|
|
6984
|
+
function numericValue(value) {
|
|
6985
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
6986
|
+
if (typeof value === "string" && value.trim()) {
|
|
6987
|
+
const number = Number(value);
|
|
6988
|
+
if (Number.isFinite(number)) return number;
|
|
6989
|
+
}
|
|
6990
|
+
return void 0;
|
|
6991
|
+
}
|
|
6992
|
+
|
|
6784
6993
|
// src/registry/load.ts
|
|
6785
6994
|
async function loadRegistryProviders(diag, opts) {
|
|
6786
6995
|
const registry = loadRegistry();
|
|
@@ -6804,7 +7013,23 @@ async function loadRegistryProviders(diag, opts) {
|
|
|
6804
7013
|
}
|
|
6805
7014
|
}
|
|
6806
7015
|
}));
|
|
6807
|
-
|
|
7016
|
+
const runtimeRegistry = {
|
|
7017
|
+
...registry,
|
|
7018
|
+
providers: registry.providers.map((provider) => {
|
|
7019
|
+
if (provider.id !== "github-copilot" || !provider.modelsCache) return provider;
|
|
7020
|
+
return {
|
|
7021
|
+
...provider,
|
|
7022
|
+
modelsCache: {
|
|
7023
|
+
...provider.modelsCache,
|
|
7024
|
+
models: filterCachedCopilotModels(
|
|
7025
|
+
provider.modelsCache.models,
|
|
7026
|
+
copilotPlanTier(oauthProviderData.get(provider.id))
|
|
7027
|
+
)
|
|
7028
|
+
}
|
|
7029
|
+
};
|
|
7030
|
+
})
|
|
7031
|
+
};
|
|
7032
|
+
return materializeRegistry(runtimeRegistry, (provider) => keys.get(provider.id) ?? null, opts).map((provider) => ({
|
|
6808
7033
|
...provider,
|
|
6809
7034
|
oauthAccountId: oauthAccountIds.get(provider.id),
|
|
6810
7035
|
providerData: oauthProviderData.get(provider.id)
|
|
@@ -8992,11 +9217,11 @@ async function addProviderFromTemplate(template, apiKey, opts) {
|
|
|
8992
9217
|
}
|
|
8993
9218
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8994
9219
|
const pricingCache = loadPricingCache();
|
|
8995
|
-
const
|
|
8996
|
-
const pricedModels = enrichModelsWithPricing(
|
|
9220
|
+
const pricedModels = enrichModelsForProviderPricing(
|
|
8997
9221
|
usableModels.map((m) => ({ ...m, apiUrl: fetched.baseUrl })),
|
|
8998
9222
|
buildPricingIndex(pricingCache),
|
|
8999
|
-
|
|
9223
|
+
template.id,
|
|
9224
|
+
template.id
|
|
9000
9225
|
);
|
|
9001
9226
|
const entry = {
|
|
9002
9227
|
id: template.id,
|
|
@@ -9308,10 +9533,49 @@ async function refreshAntigravityOAuthModels(accessToken) {
|
|
|
9308
9533
|
}
|
|
9309
9534
|
throw new Error("Antigravity live model refresh failed \u2014 Cloud Code returned no usable models");
|
|
9310
9535
|
}
|
|
9536
|
+
function buildCopilotFreeFallback() {
|
|
9537
|
+
return normalizeCopilotModels([
|
|
9538
|
+
{
|
|
9539
|
+
id: "gpt-4.1",
|
|
9540
|
+
supported_endpoints: ["/chat/completions"],
|
|
9541
|
+
billing: { multiplier: 0 }
|
|
9542
|
+
},
|
|
9543
|
+
{
|
|
9544
|
+
id: "gpt-4o",
|
|
9545
|
+
supported_endpoints: ["/chat/completions"],
|
|
9546
|
+
billing: { multiplier: 0 }
|
|
9547
|
+
}
|
|
9548
|
+
], "free");
|
|
9549
|
+
}
|
|
9550
|
+
async function refreshGithubCopilotOAuthModels(accessToken, tier) {
|
|
9551
|
+
const result = await fetchJsonWithAuth(
|
|
9552
|
+
"https://api.githubcopilot.com/models",
|
|
9553
|
+
accessToken,
|
|
9554
|
+
1e4,
|
|
9555
|
+
{ "Editor-Version": "vscode/1.85.1" }
|
|
9556
|
+
);
|
|
9557
|
+
const body = result.body;
|
|
9558
|
+
const rows = Array.isArray(body) ? body : body && typeof body === "object" ? Array.isArray(body["data"]) ? body["data"] : Array.isArray(body["models"]) ? body["models"] : [] : [];
|
|
9559
|
+
const models = normalizeCopilotModels(rows, tier);
|
|
9560
|
+
if (models.length > 0) return { models, source: "live" };
|
|
9561
|
+
return {
|
|
9562
|
+
models: buildCopilotFreeFallback(),
|
|
9563
|
+
source: "seed",
|
|
9564
|
+
failureReason: result.error ?? "GitHub Copilot returned no usable chat models",
|
|
9565
|
+
replaceCacheOnFallback: tier !== "paid"
|
|
9566
|
+
};
|
|
9567
|
+
}
|
|
9311
9568
|
async function refreshOAuthProvider(provider, accessToken) {
|
|
9312
9569
|
const tpl = provider.templateId ?? provider.id;
|
|
9313
9570
|
if (tpl === "openai" || tpl === "openai-oauth") return refreshOpenAiOAuthModels(accessToken);
|
|
9314
9571
|
if (tpl === "xai" || tpl === "xai-oauth") return refreshXaiOAuthModels(accessToken);
|
|
9572
|
+
if (tpl === "github-copilot") {
|
|
9573
|
+
let providerData = await resolveProviderOAuthProviderData(provider.authRef);
|
|
9574
|
+
if (copilotPlanTier(providerData) === "unknown") {
|
|
9575
|
+
providerData = await enrichGithubCopilotOAuthProviderData(provider.authRef) ?? providerData;
|
|
9576
|
+
}
|
|
9577
|
+
return refreshGithubCopilotOAuthModels(accessToken, copilotPlanTier(providerData));
|
|
9578
|
+
}
|
|
9315
9579
|
if (tpl === "claude-code") return refreshClaudeCodeOAuthModels(accessToken);
|
|
9316
9580
|
if (tpl === "antigravity") return refreshAntigravityOAuthModels(accessToken);
|
|
9317
9581
|
throw new Error(`refreshOAuthProvider: unsupported template "${tpl}"`);
|
|
@@ -9369,7 +9633,7 @@ function buildDynamicOAuthModel(entry, seedById) {
|
|
|
9369
9633
|
preferWebSockets: entry.preferWebSockets
|
|
9370
9634
|
};
|
|
9371
9635
|
}
|
|
9372
|
-
async function fetchJsonWithAuth(url, accessToken, timeoutMs) {
|
|
9636
|
+
async function fetchJsonWithAuth(url, accessToken, timeoutMs, extraHeaders = {}) {
|
|
9373
9637
|
try {
|
|
9374
9638
|
const controller = new AbortController();
|
|
9375
9639
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
@@ -9377,7 +9641,8 @@ async function fetchJsonWithAuth(url, accessToken, timeoutMs) {
|
|
|
9377
9641
|
headers: {
|
|
9378
9642
|
Accept: "application/json",
|
|
9379
9643
|
Authorization: `Bearer ${accessToken}`,
|
|
9380
|
-
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
|
9644
|
+
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
9645
|
+
...extraHeaders
|
|
9381
9646
|
},
|
|
9382
9647
|
signal: controller.signal
|
|
9383
9648
|
}).finally(() => clearTimeout(timer));
|
|
@@ -9533,7 +9798,7 @@ async function refreshProviderModels(providerId, apiKey, registry = loadRegistry
|
|
|
9533
9798
|
let oauthFallbackReason;
|
|
9534
9799
|
if (source === "zen-go-api") {
|
|
9535
9800
|
models = await refreshZenGoProvider(provider);
|
|
9536
|
-
} else if (provider.authType === "oauth" && (["openai", "xai", "xai-oauth", "claude-code", "antigravity"].includes(provider.templateId ?? provider.id) || provider.id === "openai-oauth" || provider.id === "xai-oauth")) {
|
|
9801
|
+
} else if (provider.authType === "oauth" && (["openai", "xai", "xai-oauth", "github-copilot", "claude-code", "antigravity"].includes(provider.templateId ?? provider.id) || provider.id === "openai-oauth" || provider.id === "xai-oauth")) {
|
|
9537
9802
|
if (!apiKey) {
|
|
9538
9803
|
return {
|
|
9539
9804
|
id: provider.id,
|
|
@@ -9544,7 +9809,7 @@ async function refreshProviderModels(providerId, apiKey, registry = loadRegistry
|
|
|
9544
9809
|
}
|
|
9545
9810
|
const oauthResult = await refreshOAuthProvider(provider, apiKey);
|
|
9546
9811
|
const failureDetail = oauthResult.failureReason ? ` (${oauthResult.failureReason})` : "";
|
|
9547
|
-
if (oauthResult.source === "seed" && cachedModelCount(provider) > 0) {
|
|
9812
|
+
if (oauthResult.source === "seed" && cachedModelCount(provider) > 0 && !oauthResult.replaceCacheOnFallback) {
|
|
9548
9813
|
return skipWithCachedModels(
|
|
9549
9814
|
provider,
|
|
9550
9815
|
`Live model discovery failed${failureDetail} \u2014 kept your existing cached model list instead of overwriting it with relay-ai's built-in fallback list. Try refreshing again later.`
|
|
@@ -9602,10 +9867,14 @@ async function refreshProviderModels(providerId, apiKey, registry = loadRegistry
|
|
|
9602
9867
|
baseUrl = fetched.baseUrl;
|
|
9603
9868
|
}
|
|
9604
9869
|
const pricingCache = loadPricingCache();
|
|
9605
|
-
const platform = pricingPlatformForProvider(provider.templateId, provider.id);
|
|
9606
9870
|
const enriched = compatibleCachedModels(
|
|
9607
9871
|
provider,
|
|
9608
|
-
|
|
9872
|
+
enrichModelsForProviderPricing(
|
|
9873
|
+
models,
|
|
9874
|
+
buildPricingIndex(pricingCache),
|
|
9875
|
+
provider.templateId,
|
|
9876
|
+
provider.id
|
|
9877
|
+
)
|
|
9609
9878
|
);
|
|
9610
9879
|
if (provider.id === "antigravity" && enriched.length === 0) {
|
|
9611
9880
|
return {
|
|
@@ -9766,7 +10035,7 @@ var PROVIDER_DISPLAY = {
|
|
|
9766
10035
|
"xai-oauth": "xAI Grok (SuperGrok)",
|
|
9767
10036
|
openai: OPENAI_DISPLAY,
|
|
9768
10037
|
"openai-oauth": OPENAI_DISPLAY,
|
|
9769
|
-
"github-copilot": "GitHub Copilot
|
|
10038
|
+
"github-copilot": "GitHub Copilot",
|
|
9770
10039
|
"claude-code": "Claude Code (Anthropic subscription)",
|
|
9771
10040
|
antigravity: "Antigravity (Google Cloud Code Assist)"
|
|
9772
10041
|
};
|
|
@@ -10019,7 +10288,7 @@ function providerAuthHelpText() {
|
|
|
10019
10288
|
|
|
10020
10289
|
${pc6.bold("Usage:")}
|
|
10021
10290
|
relay-ai providers auth <id>
|
|
10022
|
-
relay-ai providers auth xai --native
|
|
10291
|
+
relay-ai providers auth xai-oauth --native
|
|
10023
10292
|
relay-ai providers auth openai --broker
|
|
10024
10293
|
relay-ai providers auth github-copilot
|
|
10025
10294
|
|
|
@@ -10028,9 +10297,9 @@ ${pc6.bold("Options:")}
|
|
|
10028
10297
|
--broker Delegate to OpenCode auth login
|
|
10029
10298
|
|
|
10030
10299
|
${pc6.bold("Device code (works on SSH/VPS):")}
|
|
10031
|
-
xai
|
|
10032
|
-
openai
|
|
10033
|
-
github-copilot GitHub Copilot
|
|
10300
|
+
xai-oauth SuperGrok / X Premium (device code at x.ai/device)
|
|
10301
|
+
openai-oauth ChatGPT Plus/Pro (device code at auth.openai.com/codex/device)
|
|
10302
|
+
github-copilot GitHub Copilot Free or paid (device code at github.com/login/device)`;
|
|
10034
10303
|
}
|
|
10035
10304
|
|
|
10036
10305
|
// src/codex/app-launch.ts
|
|
@@ -10618,6 +10887,7 @@ export {
|
|
|
10618
10887
|
makeRouteResolver,
|
|
10619
10888
|
buildCatalogRoutes,
|
|
10620
10889
|
cachedModelToLocal,
|
|
10890
|
+
copilotPlanTier,
|
|
10621
10891
|
fetchProviderCatalog,
|
|
10622
10892
|
providersForPicker,
|
|
10623
10893
|
resolveLocalProviderApiKey,
|
|
@@ -10665,4 +10935,4 @@ export {
|
|
|
10665
10935
|
supportsClaudeTransparentMode,
|
|
10666
10936
|
buildHttpProxyRoutes
|
|
10667
10937
|
};
|
|
10668
|
-
//# sourceMappingURL=chunk-
|
|
10938
|
+
//# sourceMappingURL=chunk-44KQK6Y5.js.map
|