@jacobbd/relay-ai 0.4.7 → 0.4.9
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-XQCHSU6L.js → chunk-XTBJCRT3.js} +253 -16
- package/dist/chunk-XTBJCRT3.js.map +1 -0
- package/dist/cli.js +5 -5
- package/dist/cli.js.map +1 -1
- package/dist/ui/public/app.js +231 -210
- package/dist/ui/public/oauth-device.js +30 -0
- package/dist/ui/public/provider-logo.js +59 -0
- package/dist/ui/public/style.css +158 -1
- package/dist/{ui-command-W2W6P6VJ.js → ui-command-GATWK5JC.js} +21 -8
- package/dist/ui-command-GATWK5JC.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-XQCHSU6L.js.map +0 -1
- package/dist/ui-command-W2W6P6VJ.js.map +0 -1
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
|
|
@@ -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.4.
|
|
14
|
+
version: "0.4.9",
|
|
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();
|
|
@@ -6781,6 +6861,106 @@ function materializeRegistry(registry, resolveCredential, opts) {
|
|
|
6781
6861
|
return result;
|
|
6782
6862
|
}
|
|
6783
6863
|
|
|
6864
|
+
// src/registry/copilot-models.ts
|
|
6865
|
+
var FREE_MODEL_IDS = /* @__PURE__ */ new Set([
|
|
6866
|
+
"gpt-4.1",
|
|
6867
|
+
"gpt-4o",
|
|
6868
|
+
"gpt-4o-mini",
|
|
6869
|
+
"raptor-mini",
|
|
6870
|
+
"goldeneye"
|
|
6871
|
+
]);
|
|
6872
|
+
var FREE_CHAT_BLOCKLIST = /* @__PURE__ */ new Set(["gpt-5-mini"]);
|
|
6873
|
+
function copilotPlanTier(providerData) {
|
|
6874
|
+
const copilot = providerData?.["copilot"];
|
|
6875
|
+
if (!copilot || typeof copilot !== "object" || Array.isArray(copilot)) return "unknown";
|
|
6876
|
+
const summary = copilot;
|
|
6877
|
+
if (summary["lookup_status"] === "unknown") return "unknown";
|
|
6878
|
+
if (summary["is_free_plan"] === true) return "free";
|
|
6879
|
+
if (summary["is_free_plan"] === false) return "paid";
|
|
6880
|
+
return "unknown";
|
|
6881
|
+
}
|
|
6882
|
+
function normalizeCopilotModels(rows, tier) {
|
|
6883
|
+
const models = [];
|
|
6884
|
+
for (const value of rows) {
|
|
6885
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) continue;
|
|
6886
|
+
const row = value;
|
|
6887
|
+
const id = typeof row["id"] === "string" ? row["id"].trim() : "";
|
|
6888
|
+
if (!id || !copilotModelAllowed(row, tier)) continue;
|
|
6889
|
+
const lowerId = id.toLowerCase();
|
|
6890
|
+
const isFree = tier !== "paid" || copilotModelIsIncluded(row);
|
|
6891
|
+
const family = lowerId.split(/[-/:]/)[0] ?? lowerId;
|
|
6892
|
+
const contextWindow = numericValue(row["context_length"]) ?? numericValue(row["contextWindow"]) ?? numericValue(row["context_window"]) ?? resolveContextWindow(id);
|
|
6893
|
+
models.push({
|
|
6894
|
+
id,
|
|
6895
|
+
name: `${id} [Copilot]`,
|
|
6896
|
+
upstreamModelId: id,
|
|
6897
|
+
family,
|
|
6898
|
+
brand: deriveBrand(family),
|
|
6899
|
+
contextWindow,
|
|
6900
|
+
isFree,
|
|
6901
|
+
freeStatus: isFree ? "verified_free" : "unknown",
|
|
6902
|
+
modelFormat: "openai",
|
|
6903
|
+
npm: "@ai-sdk/openai-compatible",
|
|
6904
|
+
apiUrl: "https://api.githubcopilot.com"
|
|
6905
|
+
});
|
|
6906
|
+
}
|
|
6907
|
+
return models;
|
|
6908
|
+
}
|
|
6909
|
+
function filterCachedCopilotModels(models, tier) {
|
|
6910
|
+
return models.flatMap((model) => {
|
|
6911
|
+
const id = model.id.toLowerCase();
|
|
6912
|
+
if (!copilotIdIsCallable(id)) return [];
|
|
6913
|
+
if (tier !== "paid" && (!FREE_MODEL_IDS.has(id) || FREE_CHAT_BLOCKLIST.has(id))) return [];
|
|
6914
|
+
if (tier === "paid") return [model];
|
|
6915
|
+
return [{ ...model, isFree: true, freeStatus: "verified_free" }];
|
|
6916
|
+
});
|
|
6917
|
+
}
|
|
6918
|
+
function copilotModelAllowed(row, tier) {
|
|
6919
|
+
const id = String(row["id"] ?? "").toLowerCase();
|
|
6920
|
+
if (!copilotIdIsCallable(id)) return false;
|
|
6921
|
+
if (row["model_picker_enabled"] === false) return false;
|
|
6922
|
+
const policy = row["policy"];
|
|
6923
|
+
if (policy && typeof policy === "object" && !Array.isArray(policy)) {
|
|
6924
|
+
if (String(policy["state"] ?? "").toLowerCase() === "disabled") return false;
|
|
6925
|
+
}
|
|
6926
|
+
const capabilities = row["capabilities"];
|
|
6927
|
+
if (capabilities && typeof capabilities === "object" && !Array.isArray(capabilities)) {
|
|
6928
|
+
const family = String(capabilities["family"] ?? "").toLowerCase();
|
|
6929
|
+
if (family.includes("embedding")) return false;
|
|
6930
|
+
}
|
|
6931
|
+
const endpoints = row["supported_endpoints"];
|
|
6932
|
+
if (Array.isArray(endpoints) && endpoints.length > 0) {
|
|
6933
|
+
const supportsChat = endpoints.some((endpoint) => {
|
|
6934
|
+
const normalized = String(endpoint).toLowerCase().replace(/\/$/, "");
|
|
6935
|
+
return normalized.endsWith("/chat/completions") || normalized === "chat/completions";
|
|
6936
|
+
});
|
|
6937
|
+
if (!supportsChat) return false;
|
|
6938
|
+
}
|
|
6939
|
+
if (tier !== "paid" && (!FREE_MODEL_IDS.has(id) || FREE_CHAT_BLOCKLIST.has(id))) return false;
|
|
6940
|
+
return true;
|
|
6941
|
+
}
|
|
6942
|
+
function copilotIdIsCallable(id) {
|
|
6943
|
+
return id !== "auto" && !id.endsWith("-auto") && !id.includes("embedding");
|
|
6944
|
+
}
|
|
6945
|
+
function copilotModelIsIncluded(row) {
|
|
6946
|
+
const id = String(row["id"] ?? "").toLowerCase();
|
|
6947
|
+
if (FREE_CHAT_BLOCKLIST.has(id) || !copilotIdIsCallable(id)) return false;
|
|
6948
|
+
const billing = row["billing"];
|
|
6949
|
+
if (billing && typeof billing === "object" && !Array.isArray(billing)) {
|
|
6950
|
+
const multiplier = numericValue(billing["multiplier"]);
|
|
6951
|
+
if (multiplier !== void 0) return multiplier === 0;
|
|
6952
|
+
}
|
|
6953
|
+
return FREE_MODEL_IDS.has(id);
|
|
6954
|
+
}
|
|
6955
|
+
function numericValue(value) {
|
|
6956
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
6957
|
+
if (typeof value === "string" && value.trim()) {
|
|
6958
|
+
const number = Number(value);
|
|
6959
|
+
if (Number.isFinite(number)) return number;
|
|
6960
|
+
}
|
|
6961
|
+
return void 0;
|
|
6962
|
+
}
|
|
6963
|
+
|
|
6784
6964
|
// src/registry/load.ts
|
|
6785
6965
|
async function loadRegistryProviders(diag, opts) {
|
|
6786
6966
|
const registry = loadRegistry();
|
|
@@ -6804,7 +6984,23 @@ async function loadRegistryProviders(diag, opts) {
|
|
|
6804
6984
|
}
|
|
6805
6985
|
}
|
|
6806
6986
|
}));
|
|
6807
|
-
|
|
6987
|
+
const runtimeRegistry = {
|
|
6988
|
+
...registry,
|
|
6989
|
+
providers: registry.providers.map((provider) => {
|
|
6990
|
+
if (provider.id !== "github-copilot" || !provider.modelsCache) return provider;
|
|
6991
|
+
return {
|
|
6992
|
+
...provider,
|
|
6993
|
+
modelsCache: {
|
|
6994
|
+
...provider.modelsCache,
|
|
6995
|
+
models: filterCachedCopilotModels(
|
|
6996
|
+
provider.modelsCache.models,
|
|
6997
|
+
copilotPlanTier(oauthProviderData.get(provider.id))
|
|
6998
|
+
)
|
|
6999
|
+
}
|
|
7000
|
+
};
|
|
7001
|
+
})
|
|
7002
|
+
};
|
|
7003
|
+
return materializeRegistry(runtimeRegistry, (provider) => keys.get(provider.id) ?? null, opts).map((provider) => ({
|
|
6808
7004
|
...provider,
|
|
6809
7005
|
oauthAccountId: oauthAccountIds.get(provider.id),
|
|
6810
7006
|
providerData: oauthProviderData.get(provider.id)
|
|
@@ -9308,10 +9504,49 @@ async function refreshAntigravityOAuthModels(accessToken) {
|
|
|
9308
9504
|
}
|
|
9309
9505
|
throw new Error("Antigravity live model refresh failed \u2014 Cloud Code returned no usable models");
|
|
9310
9506
|
}
|
|
9507
|
+
function buildCopilotFreeFallback() {
|
|
9508
|
+
return normalizeCopilotModels([
|
|
9509
|
+
{
|
|
9510
|
+
id: "gpt-4.1",
|
|
9511
|
+
supported_endpoints: ["/chat/completions"],
|
|
9512
|
+
billing: { multiplier: 0 }
|
|
9513
|
+
},
|
|
9514
|
+
{
|
|
9515
|
+
id: "gpt-4o",
|
|
9516
|
+
supported_endpoints: ["/chat/completions"],
|
|
9517
|
+
billing: { multiplier: 0 }
|
|
9518
|
+
}
|
|
9519
|
+
], "free");
|
|
9520
|
+
}
|
|
9521
|
+
async function refreshGithubCopilotOAuthModels(accessToken, tier) {
|
|
9522
|
+
const result = await fetchJsonWithAuth(
|
|
9523
|
+
"https://api.githubcopilot.com/models",
|
|
9524
|
+
accessToken,
|
|
9525
|
+
1e4,
|
|
9526
|
+
{ "Editor-Version": "vscode/1.85.1" }
|
|
9527
|
+
);
|
|
9528
|
+
const body = result.body;
|
|
9529
|
+
const rows = Array.isArray(body) ? body : body && typeof body === "object" ? Array.isArray(body["data"]) ? body["data"] : Array.isArray(body["models"]) ? body["models"] : [] : [];
|
|
9530
|
+
const models = normalizeCopilotModels(rows, tier);
|
|
9531
|
+
if (models.length > 0) return { models, source: "live" };
|
|
9532
|
+
return {
|
|
9533
|
+
models: buildCopilotFreeFallback(),
|
|
9534
|
+
source: "seed",
|
|
9535
|
+
failureReason: result.error ?? "GitHub Copilot returned no usable chat models",
|
|
9536
|
+
replaceCacheOnFallback: tier !== "paid"
|
|
9537
|
+
};
|
|
9538
|
+
}
|
|
9311
9539
|
async function refreshOAuthProvider(provider, accessToken) {
|
|
9312
9540
|
const tpl = provider.templateId ?? provider.id;
|
|
9313
9541
|
if (tpl === "openai" || tpl === "openai-oauth") return refreshOpenAiOAuthModels(accessToken);
|
|
9314
9542
|
if (tpl === "xai" || tpl === "xai-oauth") return refreshXaiOAuthModels(accessToken);
|
|
9543
|
+
if (tpl === "github-copilot") {
|
|
9544
|
+
let providerData = await resolveProviderOAuthProviderData(provider.authRef);
|
|
9545
|
+
if (copilotPlanTier(providerData) === "unknown") {
|
|
9546
|
+
providerData = await enrichGithubCopilotOAuthProviderData(provider.authRef) ?? providerData;
|
|
9547
|
+
}
|
|
9548
|
+
return refreshGithubCopilotOAuthModels(accessToken, copilotPlanTier(providerData));
|
|
9549
|
+
}
|
|
9315
9550
|
if (tpl === "claude-code") return refreshClaudeCodeOAuthModels(accessToken);
|
|
9316
9551
|
if (tpl === "antigravity") return refreshAntigravityOAuthModels(accessToken);
|
|
9317
9552
|
throw new Error(`refreshOAuthProvider: unsupported template "${tpl}"`);
|
|
@@ -9369,7 +9604,7 @@ function buildDynamicOAuthModel(entry, seedById) {
|
|
|
9369
9604
|
preferWebSockets: entry.preferWebSockets
|
|
9370
9605
|
};
|
|
9371
9606
|
}
|
|
9372
|
-
async function fetchJsonWithAuth(url, accessToken, timeoutMs) {
|
|
9607
|
+
async function fetchJsonWithAuth(url, accessToken, timeoutMs, extraHeaders = {}) {
|
|
9373
9608
|
try {
|
|
9374
9609
|
const controller = new AbortController();
|
|
9375
9610
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
@@ -9377,7 +9612,8 @@ async function fetchJsonWithAuth(url, accessToken, timeoutMs) {
|
|
|
9377
9612
|
headers: {
|
|
9378
9613
|
Accept: "application/json",
|
|
9379
9614
|
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"
|
|
9615
|
+
"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",
|
|
9616
|
+
...extraHeaders
|
|
9381
9617
|
},
|
|
9382
9618
|
signal: controller.signal
|
|
9383
9619
|
}).finally(() => clearTimeout(timer));
|
|
@@ -9533,7 +9769,7 @@ async function refreshProviderModels(providerId, apiKey, registry = loadRegistry
|
|
|
9533
9769
|
let oauthFallbackReason;
|
|
9534
9770
|
if (source === "zen-go-api") {
|
|
9535
9771
|
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")) {
|
|
9772
|
+
} 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
9773
|
if (!apiKey) {
|
|
9538
9774
|
return {
|
|
9539
9775
|
id: provider.id,
|
|
@@ -9544,7 +9780,7 @@ async function refreshProviderModels(providerId, apiKey, registry = loadRegistry
|
|
|
9544
9780
|
}
|
|
9545
9781
|
const oauthResult = await refreshOAuthProvider(provider, apiKey);
|
|
9546
9782
|
const failureDetail = oauthResult.failureReason ? ` (${oauthResult.failureReason})` : "";
|
|
9547
|
-
if (oauthResult.source === "seed" && cachedModelCount(provider) > 0) {
|
|
9783
|
+
if (oauthResult.source === "seed" && cachedModelCount(provider) > 0 && !oauthResult.replaceCacheOnFallback) {
|
|
9548
9784
|
return skipWithCachedModels(
|
|
9549
9785
|
provider,
|
|
9550
9786
|
`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.`
|
|
@@ -9766,7 +10002,7 @@ var PROVIDER_DISPLAY = {
|
|
|
9766
10002
|
"xai-oauth": "xAI Grok (SuperGrok)",
|
|
9767
10003
|
openai: OPENAI_DISPLAY,
|
|
9768
10004
|
"openai-oauth": OPENAI_DISPLAY,
|
|
9769
|
-
"github-copilot": "GitHub Copilot
|
|
10005
|
+
"github-copilot": "GitHub Copilot",
|
|
9770
10006
|
"claude-code": "Claude Code (Anthropic subscription)",
|
|
9771
10007
|
antigravity: "Antigravity (Google Cloud Code Assist)"
|
|
9772
10008
|
};
|
|
@@ -10019,7 +10255,7 @@ function providerAuthHelpText() {
|
|
|
10019
10255
|
|
|
10020
10256
|
${pc6.bold("Usage:")}
|
|
10021
10257
|
relay-ai providers auth <id>
|
|
10022
|
-
relay-ai providers auth xai --native
|
|
10258
|
+
relay-ai providers auth xai-oauth --native
|
|
10023
10259
|
relay-ai providers auth openai --broker
|
|
10024
10260
|
relay-ai providers auth github-copilot
|
|
10025
10261
|
|
|
@@ -10028,9 +10264,9 @@ ${pc6.bold("Options:")}
|
|
|
10028
10264
|
--broker Delegate to OpenCode auth login
|
|
10029
10265
|
|
|
10030
10266
|
${pc6.bold("Device code (works on SSH/VPS):")}
|
|
10031
|
-
xai
|
|
10032
|
-
openai
|
|
10033
|
-
github-copilot GitHub Copilot
|
|
10267
|
+
xai-oauth SuperGrok / X Premium (device code at x.ai/device)
|
|
10268
|
+
openai-oauth ChatGPT Plus/Pro (device code at auth.openai.com/codex/device)
|
|
10269
|
+
github-copilot GitHub Copilot Free or paid (device code at github.com/login/device)`;
|
|
10034
10270
|
}
|
|
10035
10271
|
|
|
10036
10272
|
// src/codex/app-launch.ts
|
|
@@ -10618,6 +10854,7 @@ export {
|
|
|
10618
10854
|
makeRouteResolver,
|
|
10619
10855
|
buildCatalogRoutes,
|
|
10620
10856
|
cachedModelToLocal,
|
|
10857
|
+
copilotPlanTier,
|
|
10621
10858
|
fetchProviderCatalog,
|
|
10622
10859
|
providersForPicker,
|
|
10623
10860
|
resolveLocalProviderApiKey,
|
|
@@ -10665,4 +10902,4 @@ export {
|
|
|
10665
10902
|
supportsClaudeTransparentMode,
|
|
10666
10903
|
buildHttpProxyRoutes
|
|
10667
10904
|
};
|
|
10668
|
-
//# sourceMappingURL=chunk-
|
|
10905
|
+
//# sourceMappingURL=chunk-XTBJCRT3.js.map
|