@jacobbd/relay-ai 0.7.6 → 0.8.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 +8 -4
- package/dist/{chunk-GHSURQOK.js → chunk-5DDQJSTU.js} +535 -104
- package/dist/chunk-5DDQJSTU.js.map +1 -0
- package/dist/{chunk-HXGZ4CTV.js → chunk-Q2FTCICO.js} +19 -3
- package/dist/chunk-Q2FTCICO.js.map +1 -0
- package/dist/cli.js +156 -49
- package/dist/cli.js.map +1 -1
- package/dist/core/index.js +628 -7
- package/dist/core/index.js.map +1 -1
- package/dist/{provider-templates-4H3C4DRL.js → provider-templates-XKNRKAQU.js} +2 -2
- package/dist/ui/public/app.js +106 -0
- package/dist/{ui-command-27X4WJKC.js → ui-command-RYWL36VR.js} +44 -7
- package/dist/ui-command-RYWL36VR.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-GHSURQOK.js.map +0 -1
- package/dist/chunk-HXGZ4CTV.js.map +0 -1
- package/dist/ui-command-27X4WJKC.js.map +0 -1
- /package/dist/{provider-templates-4H3C4DRL.js.map → provider-templates-XKNRKAQU.js.map} +0 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
getTemplateById,
|
|
4
4
|
init_provider_templates
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-Q2FTCICO.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.8.0",
|
|
15
15
|
publishConfig: {
|
|
16
16
|
access: "public"
|
|
17
17
|
},
|
|
@@ -610,6 +610,44 @@ function injectClaudeIdentity(body, providerData, seed) {
|
|
|
610
610
|
return { sessionId, userId };
|
|
611
611
|
}
|
|
612
612
|
|
|
613
|
+
// src/cline-pass.ts
|
|
614
|
+
var CLINE_PASS_HOST = "https://api.cline.bot";
|
|
615
|
+
var CLINE_PASS_SDK_BASE_URL = `${CLINE_PASS_HOST}/api/v1`;
|
|
616
|
+
var CLINE_PASS_CATALOG_URL = `${CLINE_PASS_HOST}/api/v1/ai/cline/recommended-models`;
|
|
617
|
+
var CLINE_PASS_VALIDATION_URL = `${CLINE_PASS_HOST}/api/v1/users/me`;
|
|
618
|
+
var CLINE_PASS_REGISTER_URL = `${CLINE_PASS_HOST}/api/v1/auth/register`;
|
|
619
|
+
var CLINE_PASS_REFRESH_URL = `${CLINE_PASS_HOST}/api/v1/auth/refresh`;
|
|
620
|
+
var CLINE_PASS_LEGACY_DEFAULT_CONTEXT_WINDOW = 131072;
|
|
621
|
+
var CLINE_PASS_WORKOS_PREFIX = "workos:";
|
|
622
|
+
function isClinePassOAuth(providerId, authType) {
|
|
623
|
+
return providerId === "cline-pass" && authType === "oauth";
|
|
624
|
+
}
|
|
625
|
+
function formatClineRuntimeCredential(providerId, authType, key) {
|
|
626
|
+
if (!isClinePassOAuth(providerId, authType)) return key;
|
|
627
|
+
return key.toLowerCase().startsWith(CLINE_PASS_WORKOS_PREFIX) ? key : `${CLINE_PASS_WORKOS_PREFIX}${key}`;
|
|
628
|
+
}
|
|
629
|
+
function createClinePassOAuthFetch(initialRuntimeCredential, refreshToken, onTokenRefreshed, fetchImpl = globalThis.fetch) {
|
|
630
|
+
let currentRuntimeCredential = initialRuntimeCredential;
|
|
631
|
+
return async (input, init) => {
|
|
632
|
+
const request = new Request(input, init);
|
|
633
|
+
const send = (runtimeCredential) => {
|
|
634
|
+
const headers = new Headers(request.headers);
|
|
635
|
+
headers.set("Authorization", `Bearer ${runtimeCredential}`);
|
|
636
|
+
return fetchImpl(request.clone(), { headers });
|
|
637
|
+
};
|
|
638
|
+
const response = await send(currentRuntimeCredential);
|
|
639
|
+
if (response.status !== 401) return response;
|
|
640
|
+
const refreshedRawToken = await refreshToken().catch(() => null);
|
|
641
|
+
const refreshedRuntimeCredential = refreshedRawToken ? formatClineRuntimeCredential("cline-pass", "oauth", refreshedRawToken) : null;
|
|
642
|
+
if (!refreshedRawToken || !refreshedRuntimeCredential || refreshedRuntimeCredential === currentRuntimeCredential) {
|
|
643
|
+
return response;
|
|
644
|
+
}
|
|
645
|
+
currentRuntimeCredential = refreshedRuntimeCredential;
|
|
646
|
+
onTokenRefreshed?.(refreshedRawToken);
|
|
647
|
+
return send(currentRuntimeCredential);
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
|
|
613
651
|
// src/provider-factory.ts
|
|
614
652
|
var RESPONSES_ONLY_PREFIXES = [
|
|
615
653
|
"gpt-5-codex",
|
|
@@ -744,11 +782,19 @@ async function createLanguageModel(spec) {
|
|
|
744
782
|
let model;
|
|
745
783
|
if (npm === "@ai-sdk/openai-compatible") {
|
|
746
784
|
const { createOpenAICompatible } = await import("@ai-sdk/openai-compatible");
|
|
785
|
+
const runtimeApiKey = formatClineRuntimeCredential(spec.providerId, spec.authType, apiKey);
|
|
747
786
|
const options = {
|
|
748
787
|
name: spec.providerId ?? "openai-compatible",
|
|
749
788
|
baseURL: baseURL ?? "",
|
|
750
|
-
...
|
|
751
|
-
...spec.headers ? { headers: spec.headers } : {}
|
|
789
|
+
...runtimeApiKey.trim() ? { apiKey: runtimeApiKey } : {},
|
|
790
|
+
...spec.headers ? { headers: spec.headers } : {},
|
|
791
|
+
...isClinePassOAuth(spec.providerId, spec.authType) && spec.refreshToken ? {
|
|
792
|
+
fetch: createClinePassOAuthFetch(
|
|
793
|
+
runtimeApiKey,
|
|
794
|
+
spec.refreshToken,
|
|
795
|
+
spec.onTokenRefreshed
|
|
796
|
+
)
|
|
797
|
+
} : {}
|
|
752
798
|
};
|
|
753
799
|
model = createOpenAICompatible({
|
|
754
800
|
...options
|
|
@@ -1502,9 +1548,9 @@ function modelSelectOption(model, hint) {
|
|
|
1502
1548
|
hint: defaultHint && ctxSuffix ? `${defaultHint} \xB7 ${ctxSuffix}` : defaultHint || ctxSuffix
|
|
1503
1549
|
};
|
|
1504
1550
|
}
|
|
1505
|
-
function fmtContextWindow(
|
|
1506
|
-
if (!
|
|
1507
|
-
const k =
|
|
1551
|
+
function fmtContextWindow(contextWindow2) {
|
|
1552
|
+
if (!contextWindow2) return "";
|
|
1553
|
+
const k = contextWindow2 >= 1e3 ? `${Math.round(contextWindow2 / 1e3)}k` : String(contextWindow2);
|
|
1508
1554
|
return pc.dim(`${k} ctx`);
|
|
1509
1555
|
}
|
|
1510
1556
|
function navOption(value, label, hint = "") {
|
|
@@ -2146,9 +2192,9 @@ var ONE_M_CONTEXT_SUFFIX = "[1m]";
|
|
|
2146
2192
|
function stripOneMContextSuffix(modelId) {
|
|
2147
2193
|
return modelId.replace(/\[1m\]$/i, "");
|
|
2148
2194
|
}
|
|
2149
|
-
function claudeCodeClientModelId(modelId,
|
|
2195
|
+
function claudeCodeClientModelId(modelId, contextWindow2) {
|
|
2150
2196
|
const bare = stripOneMContextSuffix(modelId);
|
|
2151
|
-
const window = resolveContextWindow(bare,
|
|
2197
|
+
const window = resolveContextWindow(bare, contextWindow2);
|
|
2152
2198
|
if (window > DEFAULT_CONTEXT_WINDOW) {
|
|
2153
2199
|
return `${bare}${ONE_M_CONTEXT_SUFFIX}`;
|
|
2154
2200
|
}
|
|
@@ -2509,16 +2555,16 @@ function readOpencodeAuthFile(env = process.env) {
|
|
|
2509
2555
|
} catch {
|
|
2510
2556
|
return { path, entries: {}, permissionWarning: authFilePermissionWarning(path) };
|
|
2511
2557
|
}
|
|
2512
|
-
const
|
|
2558
|
+
const entries2 = {};
|
|
2513
2559
|
if (parsed && typeof parsed === "object") {
|
|
2514
2560
|
for (const [providerId, value] of Object.entries(parsed)) {
|
|
2515
2561
|
const entry = decodeAuthEntry(value);
|
|
2516
|
-
if (entry)
|
|
2562
|
+
if (entry) entries2[providerId] = entry;
|
|
2517
2563
|
}
|
|
2518
2564
|
}
|
|
2519
2565
|
return {
|
|
2520
2566
|
path,
|
|
2521
|
-
entries,
|
|
2567
|
+
entries: entries2,
|
|
2522
2568
|
permissionWarning: authFilePermissionWarning(path)
|
|
2523
2569
|
};
|
|
2524
2570
|
}
|
|
@@ -2570,7 +2616,7 @@ function accessTokenIsExpiring(token, skewMs = OAUTH_REFRESH_SKEW_MS) {
|
|
|
2570
2616
|
return false;
|
|
2571
2617
|
}
|
|
2572
2618
|
}
|
|
2573
|
-
var NATIVE_OAUTH_PROVIDER_IDS = ["xai", "xai-oauth", "openai", "openai-oauth", "github-copilot", "claude-code", "antigravity"];
|
|
2619
|
+
var NATIVE_OAUTH_PROVIDER_IDS = ["xai", "xai-oauth", "openai", "openai-oauth", "github-copilot", "claude-code", "antigravity", "cline-pass"];
|
|
2574
2620
|
function supportsNativeOAuth(providerId) {
|
|
2575
2621
|
return NATIVE_OAUTH_PROVIDER_IDS.includes(providerId);
|
|
2576
2622
|
}
|
|
@@ -2981,21 +3027,165 @@ async function fetchClaudeCodeModels(accessToken) {
|
|
|
2981
3027
|
throw new Error(`Claude Code model discovery failed (HTTP ${res.status}): ${await res.text().catch(() => "")}`);
|
|
2982
3028
|
}
|
|
2983
3029
|
const body = await res.json();
|
|
2984
|
-
const
|
|
3030
|
+
const entries2 = (body.data ?? []).filter((m) => typeof m.id === "string" && m.id.length > 0).map((m) => ({
|
|
2985
3031
|
id: m.id,
|
|
2986
3032
|
displayName: typeof m.display_name === "string" ? m.display_name : m.id,
|
|
2987
3033
|
maxInputTokens: typeof m.max_input_tokens === "number" ? m.max_input_tokens : void 0,
|
|
2988
3034
|
maxTokens: typeof m.max_tokens === "number" ? m.max_tokens : void 0
|
|
2989
3035
|
}));
|
|
2990
|
-
if (
|
|
3036
|
+
if (entries2.length === 0) {
|
|
2991
3037
|
throw new Error("Claude Code model discovery returned no models");
|
|
2992
3038
|
}
|
|
2993
|
-
return
|
|
3039
|
+
return entries2;
|
|
2994
3040
|
}
|
|
2995
3041
|
function guiCallbackRedirectUri(host) {
|
|
2996
3042
|
return `http://${host}/oauth/callback`;
|
|
2997
3043
|
}
|
|
2998
3044
|
|
|
3045
|
+
// src/oauth/cline-pass.ts
|
|
3046
|
+
var WORKOS_CLIENT_ID = "client_01K3A541FN8TA3EPPHTD2325AR";
|
|
3047
|
+
var WORKOS_DEVICE_URL = "https://api.workos.com/user_management/authorize/device";
|
|
3048
|
+
var WORKOS_TOKEN_URL = "https://api.workos.com/user_management/authenticate";
|
|
3049
|
+
var DEVICE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
|
|
3050
|
+
var DEFAULT_INTERVAL_MS = 5e3;
|
|
3051
|
+
var SLOW_DOWN_INCREMENT_MS = 1e3;
|
|
3052
|
+
var DEFAULT_EXPIRES_MS = 10 * 60 * 1e3;
|
|
3053
|
+
function formHeaders() {
|
|
3054
|
+
return {
|
|
3055
|
+
Accept: "application/json",
|
|
3056
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
3057
|
+
};
|
|
3058
|
+
}
|
|
3059
|
+
function jsonHeaders() {
|
|
3060
|
+
return {
|
|
3061
|
+
Accept: "application/json",
|
|
3062
|
+
"Content-Type": "application/json"
|
|
3063
|
+
};
|
|
3064
|
+
}
|
|
3065
|
+
function expiresInFromIso(expiresAt) {
|
|
3066
|
+
if (typeof expiresAt !== "string") throw new Error("ClinePass response is missing a valid expiresAt");
|
|
3067
|
+
const timestamp = Date.parse(expiresAt);
|
|
3068
|
+
if (!Number.isFinite(timestamp)) throw new Error("ClinePass response is missing a valid expiresAt");
|
|
3069
|
+
return Math.max(1, Math.floor((timestamp - Date.now()) / 1e3));
|
|
3070
|
+
}
|
|
3071
|
+
function toOAuthResult(data) {
|
|
3072
|
+
if (typeof data.accessToken !== "string" || !data.accessToken) {
|
|
3073
|
+
throw new Error("ClinePass response is missing accessToken");
|
|
3074
|
+
}
|
|
3075
|
+
const userInfo = data.userInfo && typeof data.userInfo === "object" && !Array.isArray(data.userInfo) ? data.userInfo : void 0;
|
|
3076
|
+
const accountId = typeof userInfo?.clineUserId === "string" ? userInfo.clineUserId : void 0;
|
|
3077
|
+
return {
|
|
3078
|
+
tokens: {
|
|
3079
|
+
access_token: data.accessToken,
|
|
3080
|
+
...typeof data.refreshToken === "string" ? { refresh_token: data.refreshToken } : {},
|
|
3081
|
+
expires_in: expiresInFromIso(data.expiresAt),
|
|
3082
|
+
...userInfo ? { providerData: userInfo } : {}
|
|
3083
|
+
},
|
|
3084
|
+
...accountId ? { accountId } : {},
|
|
3085
|
+
...userInfo ? { providerData: userInfo } : {}
|
|
3086
|
+
};
|
|
3087
|
+
}
|
|
3088
|
+
async function readError(response) {
|
|
3089
|
+
const text4 = await response.text().catch(() => "");
|
|
3090
|
+
if (!text4) return `HTTP ${response.status}`;
|
|
3091
|
+
try {
|
|
3092
|
+
const parsed = JSON.parse(text4);
|
|
3093
|
+
const detail = typeof parsed.error === "string" ? parsed.error : typeof parsed.message === "string" ? parsed.message : "";
|
|
3094
|
+
return detail || `HTTP ${response.status}`;
|
|
3095
|
+
} catch {
|
|
3096
|
+
return text4.slice(0, 120);
|
|
3097
|
+
}
|
|
3098
|
+
}
|
|
3099
|
+
async function requestClinePassDeviceCode() {
|
|
3100
|
+
const response = await fetch(WORKOS_DEVICE_URL, {
|
|
3101
|
+
method: "POST",
|
|
3102
|
+
headers: formHeaders(),
|
|
3103
|
+
body: new URLSearchParams({ client_id: WORKOS_CLIENT_ID }).toString()
|
|
3104
|
+
});
|
|
3105
|
+
if (!response.ok) throw new Error(`ClinePass device code request failed (${response.status})`);
|
|
3106
|
+
const json = await response.json();
|
|
3107
|
+
if (!json.device_code || !json.user_code || !json.verification_uri) {
|
|
3108
|
+
throw new Error("ClinePass device code response is missing required fields");
|
|
3109
|
+
}
|
|
3110
|
+
return json;
|
|
3111
|
+
}
|
|
3112
|
+
async function registerClinePassTokens(accessToken, refreshToken) {
|
|
3113
|
+
const response = await fetch(CLINE_PASS_REGISTER_URL, {
|
|
3114
|
+
method: "POST",
|
|
3115
|
+
headers: jsonHeaders(),
|
|
3116
|
+
body: JSON.stringify({ accessToken, refreshToken })
|
|
3117
|
+
});
|
|
3118
|
+
if (!response.ok) throw new Error(`ClinePass registration failed (${response.status})`);
|
|
3119
|
+
const body = await response.json();
|
|
3120
|
+
if (body.success !== true || !body.data) {
|
|
3121
|
+
const detail = typeof body.error === "string" ? body.error : typeof body.message === "string" ? body.message : "unsuccessful response";
|
|
3122
|
+
throw new Error(`ClinePass registration failed: ${detail}`);
|
|
3123
|
+
}
|
|
3124
|
+
return toOAuthResult(body.data);
|
|
3125
|
+
}
|
|
3126
|
+
async function pollClinePassDeviceCode(device, opts) {
|
|
3127
|
+
const sleep3 = opts?.sleep ?? sleepMs;
|
|
3128
|
+
const now = opts?.now ?? (() => Date.now());
|
|
3129
|
+
const deadline = now() + positiveSecondsToMs(device.expires_in, DEFAULT_EXPIRES_MS);
|
|
3130
|
+
let intervalMs = Math.max(positiveSecondsToMs(device.interval, DEFAULT_INTERVAL_MS), 1e3);
|
|
3131
|
+
while (now() < deadline) {
|
|
3132
|
+
const response = await fetch(WORKOS_TOKEN_URL, {
|
|
3133
|
+
method: "POST",
|
|
3134
|
+
headers: formHeaders(),
|
|
3135
|
+
body: new URLSearchParams({
|
|
3136
|
+
grant_type: DEVICE_GRANT_TYPE,
|
|
3137
|
+
client_id: WORKOS_CLIENT_ID,
|
|
3138
|
+
device_code: device.device_code
|
|
3139
|
+
}).toString()
|
|
3140
|
+
});
|
|
3141
|
+
if (response.ok) {
|
|
3142
|
+
const workos = await response.json();
|
|
3143
|
+
if (!workos.access_token || !workos.refresh_token) {
|
|
3144
|
+
throw new Error("ClinePass WorkOS response is missing required tokens");
|
|
3145
|
+
}
|
|
3146
|
+
return registerClinePassTokens(workos.access_token, workos.refresh_token);
|
|
3147
|
+
}
|
|
3148
|
+
const body = await response.json().catch(() => ({}));
|
|
3149
|
+
const remaining = Math.max(0, deadline - now());
|
|
3150
|
+
if (body.error === "authorization_pending") {
|
|
3151
|
+
await sleep3(Math.min(intervalMs, remaining));
|
|
3152
|
+
continue;
|
|
3153
|
+
}
|
|
3154
|
+
if (body.error === "slow_down") {
|
|
3155
|
+
intervalMs += SLOW_DOWN_INCREMENT_MS;
|
|
3156
|
+
await sleep3(Math.min(intervalMs, remaining));
|
|
3157
|
+
continue;
|
|
3158
|
+
}
|
|
3159
|
+
throw new Error(`ClinePass device authorization failed${body.error ? `: ${body.error}` : ""}`);
|
|
3160
|
+
}
|
|
3161
|
+
throw new Error("ClinePass device authorization timed out");
|
|
3162
|
+
}
|
|
3163
|
+
async function runClinePassDeviceCodeFlow(onDeviceCode, opts) {
|
|
3164
|
+
const device = await requestClinePassDeviceCode();
|
|
3165
|
+
onDeviceCode({
|
|
3166
|
+
url: device.verification_uri_complete ?? device.verification_uri,
|
|
3167
|
+
userCode: device.user_code
|
|
3168
|
+
});
|
|
3169
|
+
return pollClinePassDeviceCode(device, opts);
|
|
3170
|
+
}
|
|
3171
|
+
async function refreshClinePassAccessToken(refreshToken) {
|
|
3172
|
+
const response = await fetch(CLINE_PASS_REFRESH_URL, {
|
|
3173
|
+
method: "POST",
|
|
3174
|
+
headers: jsonHeaders(),
|
|
3175
|
+
body: JSON.stringify({ refreshToken, grantType: "refresh_token" })
|
|
3176
|
+
});
|
|
3177
|
+
if (!response.ok) {
|
|
3178
|
+
const detail = await readError(response);
|
|
3179
|
+
throw new Error(`ClinePass token refresh failed (${response.status}): ${detail}`);
|
|
3180
|
+
}
|
|
3181
|
+
const body = await response.json();
|
|
3182
|
+
if (body.success !== true || !body.data) {
|
|
3183
|
+
const detail = typeof body.error === "string" ? body.error : typeof body.message === "string" ? body.message : "unsuccessful response";
|
|
3184
|
+
throw new Error(`ClinePass token refresh failed: ${detail}`);
|
|
3185
|
+
}
|
|
3186
|
+
return toOAuthResult(body.data).tokens;
|
|
3187
|
+
}
|
|
3188
|
+
|
|
2999
3189
|
// src/oauth/refresh.ts
|
|
3000
3190
|
function oauthCredentialShouldRefresh(cred, providerId) {
|
|
3001
3191
|
if (oauthCredentialNeedsRefresh(cred)) return true;
|
|
@@ -3017,10 +3207,13 @@ async function refreshStoredOAuthCredential(providerId, cred) {
|
|
|
3017
3207
|
tokens = await refreshClaudeCodeToken(cred.refresh);
|
|
3018
3208
|
} else if (providerId === "antigravity") {
|
|
3019
3209
|
tokens = await refreshAntigravityToken(cred.refresh);
|
|
3210
|
+
} else if (providerId === "cline-pass") {
|
|
3211
|
+
tokens = await refreshClinePassAccessToken(cred.refresh);
|
|
3020
3212
|
} else {
|
|
3021
3213
|
throw new Error(`OAuth refresh not implemented for provider "${providerId}"`);
|
|
3022
3214
|
}
|
|
3023
|
-
|
|
3215
|
+
const accountId = providerId === "cline-pass" && typeof tokens.providerData?.clineUserId === "string" ? tokens.providerData.clineUserId : cred.accountId;
|
|
3216
|
+
return tokensToStoredCredential(tokens, cred.refresh, accountId, cred.providerData);
|
|
3024
3217
|
}
|
|
3025
3218
|
|
|
3026
3219
|
// src/secrets-file.ts
|
|
@@ -3102,7 +3295,7 @@ function applyClaudeCodeThirdPartyCompat(env) {
|
|
|
3102
3295
|
env["ENABLE_TOOL_SEARCH"] = "true";
|
|
3103
3296
|
env["CLAUDE_CODE_SIMPLE_SYSTEM_PROMPT"] = "0";
|
|
3104
3297
|
}
|
|
3105
|
-
function buildChildEnv(baseUrl, model, apiKey, proxyPort,
|
|
3298
|
+
function buildChildEnv(baseUrl, model, apiKey, proxyPort, contextWindow2, enableGatewayDiscovery) {
|
|
3106
3299
|
const env = { ...process.env };
|
|
3107
3300
|
for (const name of CONFLICTING_ENV_VARS) {
|
|
3108
3301
|
delete env[name];
|
|
@@ -3113,8 +3306,8 @@ function buildChildEnv(baseUrl, model, apiKey, proxyPort, contextWindow, enableG
|
|
|
3113
3306
|
env["ANTHROPIC_BASE_URL"] = proxyPort ? `http://127.0.0.1:${proxyPort}` : baseUrl;
|
|
3114
3307
|
env["ANTHROPIC_API_KEY"] = apiKey;
|
|
3115
3308
|
const bareModel = stripOneMContextSuffix(model);
|
|
3116
|
-
env["ANTHROPIC_MODEL"] = claudeCodeClientModelId(model,
|
|
3117
|
-
env["CLAUDE_CODE_MAX_CONTEXT_TOKENS"] = String(resolveContextWindow(bareModel,
|
|
3309
|
+
env["ANTHROPIC_MODEL"] = claudeCodeClientModelId(model, contextWindow2);
|
|
3310
|
+
env["CLAUDE_CODE_MAX_CONTEXT_TOKENS"] = String(resolveContextWindow(bareModel, contextWindow2));
|
|
3118
3311
|
if (enableGatewayDiscovery) {
|
|
3119
3312
|
env["CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY"] = "1";
|
|
3120
3313
|
}
|
|
@@ -3311,6 +3504,18 @@ async function resolveProviderCredential(providerId, authRef, diag) {
|
|
|
3311
3504
|
}
|
|
3312
3505
|
return readProviderSecret(parsed.account, diag);
|
|
3313
3506
|
}
|
|
3507
|
+
async function forceRefreshProviderCredential(providerId, authRef, diag) {
|
|
3508
|
+
const namespaced = readEnvCredential(relayAiKeyEnvVar(providerId));
|
|
3509
|
+
if (namespaced) return namespaced;
|
|
3510
|
+
const parsed = parseAuthRef(authRef);
|
|
3511
|
+
if (!parsed || parsed.kind !== "keyring") {
|
|
3512
|
+
return resolveProviderCredential(providerId, authRef, diag);
|
|
3513
|
+
}
|
|
3514
|
+
const oauthProviderId = oauthProviderIdFromAccount(parsed.account);
|
|
3515
|
+
const raw = await readKeyringAccount(parsed.account, diag);
|
|
3516
|
+
if (!raw || !oauthProviderId) return decodeProviderSecret(raw);
|
|
3517
|
+
return refreshOAuthKeyringAccount(parsed.account, oauthProviderId, raw, diag, true);
|
|
3518
|
+
}
|
|
3314
3519
|
async function resolveProviderOAuthAccountId(authRef, diag) {
|
|
3315
3520
|
const parsed = parseAuthRef(authRef);
|
|
3316
3521
|
if (!parsed || parsed.kind !== "keyring" || !oauthProviderIdFromAccount(parsed.account)) return void 0;
|
|
@@ -3359,12 +3564,12 @@ function decodeProviderSecret(raw) {
|
|
|
3359
3564
|
}
|
|
3360
3565
|
return trimmed;
|
|
3361
3566
|
}
|
|
3362
|
-
async function refreshOAuthKeyringAccount(account, providerId, raw, diag) {
|
|
3567
|
+
async function refreshOAuthKeyringAccount(account, providerId, raw, diag, force = false) {
|
|
3363
3568
|
const existing = oauthRefreshInflight.get(account);
|
|
3364
3569
|
if (existing) return existing;
|
|
3365
3570
|
const work = (async () => {
|
|
3366
3571
|
const cred = parseStoredOAuthCredential(raw);
|
|
3367
|
-
if (!cred || !oauthCredentialShouldRefresh(cred, providerId)) {
|
|
3572
|
+
if (!cred || !force && !oauthCredentialShouldRefresh(cred, providerId)) {
|
|
3368
3573
|
return decodeProviderSecret(raw);
|
|
3369
3574
|
}
|
|
3370
3575
|
try {
|
|
@@ -4180,6 +4385,16 @@ function getAntigravityDebugLogPath(tracePrefix) {
|
|
|
4180
4385
|
const surface = tracePrefix === "antigravity" ? "app" : tracePrefix;
|
|
4181
4386
|
return join8(ensureLogsDir(), `antigravity-${surface}-debug.log`);
|
|
4182
4387
|
}
|
|
4388
|
+
function prepareProviderTraceLog() {
|
|
4389
|
+
const path = getProviderDebugLogPath();
|
|
4390
|
+
resetTraceLog(path);
|
|
4391
|
+
try {
|
|
4392
|
+
writeFileSync5(path, "", { mode: FILE_MODE5 });
|
|
4393
|
+
chmodSync5(path, FILE_MODE5);
|
|
4394
|
+
} catch {
|
|
4395
|
+
}
|
|
4396
|
+
return path;
|
|
4397
|
+
}
|
|
4183
4398
|
function makeTraceLogger(logPath) {
|
|
4184
4399
|
resetTraceLog(logPath);
|
|
4185
4400
|
return (message) => writeSecureLogLine(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${message}`);
|
|
@@ -4552,8 +4767,8 @@ function augmentClaudeAgentTool(tool4, routing) {
|
|
|
4552
4767
|
// src/server/models.ts
|
|
4553
4768
|
var CREATED_AT_ISO = "2025-01-01T00:00:00Z";
|
|
4554
4769
|
var CREATED_AT_UNIX = 1735689600;
|
|
4555
|
-
function formatAnthropicModelEntry(id, displayName,
|
|
4556
|
-
const maxInput = resolveContextWindow(id,
|
|
4770
|
+
function formatAnthropicModelEntry(id, displayName, contextWindow2, options) {
|
|
4771
|
+
const maxInput = resolveContextWindow(id, contextWindow2);
|
|
4557
4772
|
return {
|
|
4558
4773
|
id,
|
|
4559
4774
|
type: "model",
|
|
@@ -4564,17 +4779,17 @@ function formatAnthropicModelEntry(id, displayName, contextWindow, options) {
|
|
|
4564
4779
|
...options?.supportsOneM !== void 0 ? { supports_1m: options.supportsOneM } : {}
|
|
4565
4780
|
};
|
|
4566
4781
|
}
|
|
4567
|
-
function formatAnthropicModelList(
|
|
4782
|
+
function formatAnthropicModelList(entries2) {
|
|
4568
4783
|
return {
|
|
4569
|
-
data:
|
|
4784
|
+
data: entries2.map((entry) => formatAnthropicModelEntry(
|
|
4570
4785
|
entry.id,
|
|
4571
4786
|
entry.name,
|
|
4572
4787
|
entry.contextWindow,
|
|
4573
4788
|
{ supportsOneM: entry.supportsOneM }
|
|
4574
4789
|
)),
|
|
4575
4790
|
has_more: false,
|
|
4576
|
-
first_id:
|
|
4577
|
-
last_id:
|
|
4791
|
+
first_id: entries2[0]?.id ?? null,
|
|
4792
|
+
last_id: entries2.at(-1)?.id ?? null
|
|
4578
4793
|
};
|
|
4579
4794
|
}
|
|
4580
4795
|
function gatewayProviderLabel(model) {
|
|
@@ -6561,6 +6776,10 @@ function startProxyCatalog(routes, defaultAliasId, debug = false) {
|
|
|
6561
6776
|
oauthAccountId: route.oauthAccountId,
|
|
6562
6777
|
providerData: route.providerData,
|
|
6563
6778
|
headers: route.headers,
|
|
6779
|
+
refreshToken: route.refreshToken,
|
|
6780
|
+
onTokenRefreshed: (refreshed) => {
|
|
6781
|
+
route.apiKey = refreshed;
|
|
6782
|
+
},
|
|
6564
6783
|
useResponsesLite: route.useResponsesLite,
|
|
6565
6784
|
preferWebSockets: route.preferWebSockets,
|
|
6566
6785
|
onDebug: (msg) => plog(() => msg)
|
|
@@ -6681,9 +6900,9 @@ data: ${JSON.stringify({ type: "error", error: { type: errorType, message } })}
|
|
|
6681
6900
|
});
|
|
6682
6901
|
});
|
|
6683
6902
|
}
|
|
6684
|
-
function startProxy(completionsUrl, modelId, debug = false,
|
|
6903
|
+
function startProxy(completionsUrl, modelId, debug = false, contextWindow2, sdk, apiKey) {
|
|
6685
6904
|
const bareModelId = stripOneMContextSuffix(modelId);
|
|
6686
|
-
const clientModelId = claudeCodeClientModelId(modelId,
|
|
6905
|
+
const clientModelId = claudeCodeClientModelId(modelId, contextWindow2);
|
|
6687
6906
|
return startProxyCatalog([{
|
|
6688
6907
|
aliasId: clientModelId,
|
|
6689
6908
|
realModelId: sdk?.upstreamModelId ?? bareModelId,
|
|
@@ -6691,13 +6910,15 @@ function startProxy(completionsUrl, modelId, debug = false, contextWindow, sdk,
|
|
|
6691
6910
|
upstreamUrl: completionsUrl,
|
|
6692
6911
|
apiKey: apiKey ?? "",
|
|
6693
6912
|
modelFormat: sdk?.modelFormat ?? "openai",
|
|
6694
|
-
contextWindow,
|
|
6913
|
+
contextWindow: contextWindow2,
|
|
6695
6914
|
npm: sdk?.npm,
|
|
6696
6915
|
baseURL: sdk?.baseURL,
|
|
6697
6916
|
providerId: sdk?.providerId,
|
|
6698
6917
|
authType: sdk?.authType,
|
|
6699
6918
|
oauthAccountId: sdk?.oauthAccountId,
|
|
6700
6919
|
providerData: sdk?.providerData,
|
|
6920
|
+
refreshToken: sdk?.refreshToken,
|
|
6921
|
+
headers: sdk?.headers,
|
|
6701
6922
|
supportedParameters: sdk?.supportedParameters,
|
|
6702
6923
|
reasoning: sdk?.reasoning,
|
|
6703
6924
|
interleavedReasoningField: sdk?.interleavedReasoningField,
|
|
@@ -6706,54 +6927,6 @@ function startProxy(completionsUrl, modelId, debug = false, contextWindow, sdk,
|
|
|
6706
6927
|
}], clientModelId, debug);
|
|
6707
6928
|
}
|
|
6708
6929
|
|
|
6709
|
-
// src/catalog.ts
|
|
6710
|
-
function localModelToRoute(lp, model) {
|
|
6711
|
-
if (model.modelFormat === "anthropic" && !model.baseUrl) return null;
|
|
6712
|
-
if (model.modelFormat === "openai" && !isSdkMigratedNpm(model.npm) && !model.completionsUrl) return null;
|
|
6713
|
-
const upstreamUrl = model.modelFormat === "cloud-code" ? model.baseUrl ?? ANTIGRAVITY_BASE_URLS[0] : model.modelFormat === "anthropic" ? model.baseUrl : model.completionsUrl;
|
|
6714
|
-
return {
|
|
6715
|
-
aliasId: claudeCodeClientModelId(aliasModelId(model.id, lp.id), model.contextWindow),
|
|
6716
|
-
realModelId: model.upstreamModelId,
|
|
6717
|
-
displayName: `${model.name || model.id} (${lp.name})`,
|
|
6718
|
-
upstreamUrl: upstreamUrl ?? "",
|
|
6719
|
-
apiKey: lp.apiKey,
|
|
6720
|
-
modelFormat: model.modelFormat,
|
|
6721
|
-
contextWindow: model.contextWindow,
|
|
6722
|
-
npm: model.npm,
|
|
6723
|
-
baseURL: model.apiBaseUrl,
|
|
6724
|
-
providerId: lp.id,
|
|
6725
|
-
authType: lp.authType,
|
|
6726
|
-
oauthAccountId: lp.oauthAccountId,
|
|
6727
|
-
providerData: lp.providerData,
|
|
6728
|
-
headers: lp.headers,
|
|
6729
|
-
supportedParameters: model.supportedParameters,
|
|
6730
|
-
reasoning: model.reasoning,
|
|
6731
|
-
interleavedReasoningField: model.interleavedReasoningField,
|
|
6732
|
-
useResponsesLite: model.useResponsesLite,
|
|
6733
|
-
preferWebSockets: model.preferWebSockets
|
|
6734
|
-
};
|
|
6735
|
-
}
|
|
6736
|
-
function makeRouteResolver(localProviders) {
|
|
6737
|
-
return (providerId, modelId) => {
|
|
6738
|
-
const provider = localProviders?.find((lp) => lp.id === providerId);
|
|
6739
|
-
const model = provider?.models.find((m) => m.id === modelId);
|
|
6740
|
-
return provider && model ? localModelToRoute(provider, model) ?? void 0 : void 0;
|
|
6741
|
-
};
|
|
6742
|
-
}
|
|
6743
|
-
function buildCatalogRoutes(startingRoute, favorites, resolveRoute, max = MAX_MODEL_CATALOG) {
|
|
6744
|
-
const droppedFavorites = [];
|
|
6745
|
-
const tail = favorites.map((fav) => {
|
|
6746
|
-
const route = resolveRoute(fav.providerId, fav.modelId);
|
|
6747
|
-
if (!route) droppedFavorites.push(fav);
|
|
6748
|
-
return route;
|
|
6749
|
-
}).filter((route) => route !== void 0);
|
|
6750
|
-
const routes = [
|
|
6751
|
-
startingRoute,
|
|
6752
|
-
...tail.filter((route) => route.aliasId !== startingRoute.aliasId)
|
|
6753
|
-
].slice(0, max);
|
|
6754
|
-
return { routes, droppedFavorites };
|
|
6755
|
-
}
|
|
6756
|
-
|
|
6757
6930
|
// src/data/model-incompatible.json
|
|
6758
6931
|
var model_incompatible_default = {
|
|
6759
6932
|
schema_version: "1",
|
|
@@ -7561,6 +7734,61 @@ function listCredentialSkippedProviders(raw, authEntries, importedIds, alreadyRe
|
|
|
7561
7734
|
return skipped;
|
|
7562
7735
|
}
|
|
7563
7736
|
|
|
7737
|
+
// src/provider-runtime.ts
|
|
7738
|
+
function providerRefreshToken(providerId, authType, authRef) {
|
|
7739
|
+
if (authType !== "oauth" || !providerId) return void 0;
|
|
7740
|
+
return () => forceRefreshProviderCredential(providerId, authRef ?? oauthAuthRef(providerId));
|
|
7741
|
+
}
|
|
7742
|
+
|
|
7743
|
+
// src/catalog.ts
|
|
7744
|
+
function localModelToRoute(lp, model) {
|
|
7745
|
+
if (model.modelFormat === "anthropic" && !model.baseUrl) return null;
|
|
7746
|
+
if (model.modelFormat === "openai" && !isSdkMigratedNpm(model.npm) && !model.completionsUrl) return null;
|
|
7747
|
+
const upstreamUrl = model.modelFormat === "cloud-code" ? model.baseUrl ?? ANTIGRAVITY_BASE_URLS[0] : model.modelFormat === "anthropic" ? model.baseUrl : model.completionsUrl;
|
|
7748
|
+
return {
|
|
7749
|
+
aliasId: claudeCodeClientModelId(aliasModelId(model.id, lp.id), model.contextWindow),
|
|
7750
|
+
realModelId: model.upstreamModelId,
|
|
7751
|
+
displayName: `${model.name || model.id} (${lp.name})`,
|
|
7752
|
+
upstreamUrl: upstreamUrl ?? "",
|
|
7753
|
+
apiKey: lp.apiKey,
|
|
7754
|
+
modelFormat: model.modelFormat,
|
|
7755
|
+
contextWindow: model.contextWindow,
|
|
7756
|
+
npm: model.npm,
|
|
7757
|
+
baseURL: model.apiBaseUrl,
|
|
7758
|
+
providerId: lp.id,
|
|
7759
|
+
authType: lp.authType,
|
|
7760
|
+
oauthAccountId: lp.oauthAccountId,
|
|
7761
|
+
providerData: lp.providerData,
|
|
7762
|
+
refreshToken: providerRefreshToken(lp.id, lp.authType, lp.authRef),
|
|
7763
|
+
headers: lp.headers,
|
|
7764
|
+
supportedParameters: model.supportedParameters,
|
|
7765
|
+
reasoning: model.reasoning,
|
|
7766
|
+
interleavedReasoningField: model.interleavedReasoningField,
|
|
7767
|
+
useResponsesLite: model.useResponsesLite,
|
|
7768
|
+
preferWebSockets: model.preferWebSockets
|
|
7769
|
+
};
|
|
7770
|
+
}
|
|
7771
|
+
function makeRouteResolver(localProviders) {
|
|
7772
|
+
return (providerId, modelId) => {
|
|
7773
|
+
const provider = localProviders?.find((lp) => lp.id === providerId);
|
|
7774
|
+
const model = provider?.models.find((m) => m.id === modelId);
|
|
7775
|
+
return provider && model ? localModelToRoute(provider, model) ?? void 0 : void 0;
|
|
7776
|
+
};
|
|
7777
|
+
}
|
|
7778
|
+
function buildCatalogRoutes(startingRoute, favorites, resolveRoute, max = MAX_MODEL_CATALOG) {
|
|
7779
|
+
const droppedFavorites = [];
|
|
7780
|
+
const tail = favorites.map((fav) => {
|
|
7781
|
+
const route = resolveRoute(fav.providerId, fav.modelId);
|
|
7782
|
+
if (!route) droppedFavorites.push(fav);
|
|
7783
|
+
return route;
|
|
7784
|
+
}).filter((route) => route !== void 0);
|
|
7785
|
+
const routes = [
|
|
7786
|
+
startingRoute,
|
|
7787
|
+
...tail.filter((route) => route.aliasId !== startingRoute.aliasId)
|
|
7788
|
+
].slice(0, max);
|
|
7789
|
+
return { routes, droppedFavorites };
|
|
7790
|
+
}
|
|
7791
|
+
|
|
7564
7792
|
// src/registry/materialize.ts
|
|
7565
7793
|
init_provider_templates();
|
|
7566
7794
|
function cachedModelToLocal(cached, provider) {
|
|
@@ -7607,7 +7835,12 @@ function cachedModelToLocal(cached, provider) {
|
|
|
7607
7835
|
cost: cached.cost,
|
|
7608
7836
|
isFree: isFreeStatus(freeStatus),
|
|
7609
7837
|
freeStatus,
|
|
7610
|
-
|
|
7838
|
+
// ClinePass's public catalog does not currently report per-model context
|
|
7839
|
+
// limits. Preserve that unknown state for the picker instead of displaying
|
|
7840
|
+
// a heuristic as if it were provider metadata. Launch-time callers still
|
|
7841
|
+
// resolve their required safety fallback when they build the child env or
|
|
7842
|
+
// proxy catalog.
|
|
7843
|
+
contextWindow: provider.id === "cline-pass" && cached.contextWindow === CLINE_PASS_LEGACY_DEFAULT_CONTEXT_WINDOW && cached.contextWindowSource !== "provider" ? void 0 : cached.contextWindow ?? (provider.id === "cline-pass" ? void 0 : resolveContextWindow(id)),
|
|
7611
7844
|
supportedParameters: cached.supportedParameters,
|
|
7612
7845
|
reasoning: cached.reasoning ?? modelsDev?.reasoning,
|
|
7613
7846
|
interleavedReasoningField: cached.interleavedReasoningField ?? modelsDev?.interleaved?.field,
|
|
@@ -7644,6 +7877,7 @@ function materializeOne(provider, resolveCredential, agent) {
|
|
|
7644
7877
|
id: provider.id,
|
|
7645
7878
|
name: provider.name,
|
|
7646
7879
|
apiKey,
|
|
7880
|
+
authRef: provider.authRef,
|
|
7647
7881
|
authType: provider.authType,
|
|
7648
7882
|
headers: provider.api.headers,
|
|
7649
7883
|
models
|
|
@@ -7687,14 +7921,14 @@ function normalizeCopilotModels(rows, tier) {
|
|
|
7687
7921
|
const lowerId = id.toLowerCase();
|
|
7688
7922
|
const isFree = tier !== "paid" || copilotModelIsIncluded(row);
|
|
7689
7923
|
const family = lowerId.split(/[-/:]/)[0] ?? lowerId;
|
|
7690
|
-
const
|
|
7924
|
+
const contextWindow2 = numericValue(row["context_length"]) ?? numericValue(row["contextWindow"]) ?? numericValue(row["context_window"]) ?? resolveContextWindow(id);
|
|
7691
7925
|
models.push({
|
|
7692
7926
|
id,
|
|
7693
7927
|
name: `${id} [Copilot]`,
|
|
7694
7928
|
upstreamModelId: id,
|
|
7695
7929
|
family,
|
|
7696
7930
|
brand: deriveBrand(family),
|
|
7697
|
-
contextWindow,
|
|
7931
|
+
contextWindow: contextWindow2,
|
|
7698
7932
|
isFree,
|
|
7699
7933
|
freeStatus: isFree ? "verified_free" : "unknown",
|
|
7700
7934
|
modelFormat: "openai",
|
|
@@ -7852,9 +8086,9 @@ function formatRegistryAuthLabel(provider) {
|
|
|
7852
8086
|
}
|
|
7853
8087
|
async function resolveProvidersForDisplay() {
|
|
7854
8088
|
const reg = loadRegistry();
|
|
7855
|
-
const
|
|
8089
|
+
const entries2 = [];
|
|
7856
8090
|
for (const provider of reg.providers) {
|
|
7857
|
-
|
|
8091
|
+
entries2.push({
|
|
7858
8092
|
id: provider.id,
|
|
7859
8093
|
name: provider.name,
|
|
7860
8094
|
modelCount: provider.modelsCache?.models.length ?? 0,
|
|
@@ -7863,7 +8097,7 @@ async function resolveProvidersForDisplay() {
|
|
|
7863
8097
|
inRegistry: true
|
|
7864
8098
|
});
|
|
7865
8099
|
}
|
|
7866
|
-
return
|
|
8100
|
+
return entries2.sort((a, b) => a.name.localeCompare(b.name));
|
|
7867
8101
|
}
|
|
7868
8102
|
function localProvidersToServerModels(localProviders) {
|
|
7869
8103
|
return localProviders.flatMap(
|
|
@@ -8360,7 +8594,7 @@ function buildAntigravityRoutes(resolvedFavorites, maxRoutes = MAX_MODEL_CATALOG
|
|
|
8360
8594
|
const npm = favModel.npm || "@ai-sdk/openai-compatible";
|
|
8361
8595
|
const upstreamModelId2 = favModel.upstreamModelId || modelId;
|
|
8362
8596
|
const baseURL = favModel.apiBaseUrl || favModel.completionsUrl || void 0;
|
|
8363
|
-
const
|
|
8597
|
+
const contextWindow2 = favModel.contextWindow;
|
|
8364
8598
|
const modelFormat = favModel.modelFormat;
|
|
8365
8599
|
routes.push({
|
|
8366
8600
|
catalogId,
|
|
@@ -8375,8 +8609,10 @@ function buildAntigravityRoutes(resolvedFavorites, maxRoutes = MAX_MODEL_CATALOG
|
|
|
8375
8609
|
...fav.authType ? { authType: fav.authType } : {},
|
|
8376
8610
|
...fav.oauthAccountId ? { oauthAccountId: fav.oauthAccountId } : {},
|
|
8377
8611
|
...fav.providerData ? { providerData: fav.providerData } : {},
|
|
8612
|
+
...fav.headers ? { headers: fav.headers } : {},
|
|
8613
|
+
...fav.refreshToken ? { refreshToken: fav.refreshToken } : {},
|
|
8378
8614
|
baseURL,
|
|
8379
|
-
contextWindow
|
|
8615
|
+
contextWindow: contextWindow2
|
|
8380
8616
|
});
|
|
8381
8617
|
}
|
|
8382
8618
|
return applyUniqueAntigravityRouteLabels(routes);
|
|
@@ -8592,8 +8828,8 @@ function contextFloorForTarget(target) {
|
|
|
8592
8828
|
if (target === "server") return 0;
|
|
8593
8829
|
return MIN_CONTEXT_WINDOW;
|
|
8594
8830
|
}
|
|
8595
|
-
function meetsContextFloor(target,
|
|
8596
|
-
return
|
|
8831
|
+
function meetsContextFloor(target, contextWindow2) {
|
|
8832
|
+
return contextWindow2 === void 0 || contextWindow2 >= contextFloorForTarget(target);
|
|
8597
8833
|
}
|
|
8598
8834
|
function isTargetCompatibleModel(ctx) {
|
|
8599
8835
|
const blacklistAgent = blacklistAgentForTarget(ctx.target);
|
|
@@ -8769,14 +9005,14 @@ function parseModelList(body, npm) {
|
|
|
8769
9005
|
// daily Neuron allowance, so free access is a provider rule, not a price.
|
|
8770
9006
|
freeAccess: isFreeFromProps === true
|
|
8771
9007
|
});
|
|
8772
|
-
const
|
|
9008
|
+
const contextWindow2 = contextWindowFromProps ?? row.context_length ?? row.contextWindow ?? row.context_window ?? resolveContextWindow(id);
|
|
8773
9009
|
models.push({
|
|
8774
9010
|
id,
|
|
8775
9011
|
name: normalizeGoogleDisplayName(row.name, id),
|
|
8776
9012
|
upstreamModelId: upstreamModelId2,
|
|
8777
9013
|
family,
|
|
8778
9014
|
brand: deriveBrand(family),
|
|
8779
|
-
contextWindow,
|
|
9015
|
+
contextWindow: contextWindow2,
|
|
8780
9016
|
cost,
|
|
8781
9017
|
isFree: isFreeStatus(freeStatus),
|
|
8782
9018
|
freeStatus,
|
|
@@ -9178,6 +9414,125 @@ async function addCustomEndpointProvider(input) {
|
|
|
9178
9414
|
return { added: true, provider: entry, modelCount: fetched.models.length };
|
|
9179
9415
|
}
|
|
9180
9416
|
|
|
9417
|
+
// src/registry/fetch-cline-pass-models.ts
|
|
9418
|
+
var REQUEST_TIMEOUT_MS = 1e4;
|
|
9419
|
+
function trace(message) {
|
|
9420
|
+
if (process.env.RELAY_AI_TRACE !== "1") return;
|
|
9421
|
+
writeSecureLogLine(
|
|
9422
|
+
getProviderDebugLogPath(),
|
|
9423
|
+
`${(/* @__PURE__ */ new Date()).toISOString()} ${message}`
|
|
9424
|
+
);
|
|
9425
|
+
}
|
|
9426
|
+
async function responseBodyPreview(response) {
|
|
9427
|
+
try {
|
|
9428
|
+
const clone = typeof response.clone === "function" ? response.clone() : response;
|
|
9429
|
+
if (typeof clone.text !== "function") return "";
|
|
9430
|
+
return (await clone.text()).slice(0, 500).trim();
|
|
9431
|
+
} catch {
|
|
9432
|
+
return "";
|
|
9433
|
+
}
|
|
9434
|
+
}
|
|
9435
|
+
function entries(value) {
|
|
9436
|
+
if (!Array.isArray(value)) return [];
|
|
9437
|
+
return value.filter((entry) => Boolean(entry && typeof entry === "object"));
|
|
9438
|
+
}
|
|
9439
|
+
function positiveNumber(value) {
|
|
9440
|
+
const number = typeof value === "number" ? value : typeof value === "string" && value.trim() ? Number(value) : void 0;
|
|
9441
|
+
return typeof number === "number" && Number.isFinite(number) && number > 0 ? number : void 0;
|
|
9442
|
+
}
|
|
9443
|
+
function contextWindow(entry) {
|
|
9444
|
+
return [
|
|
9445
|
+
entry.context_window,
|
|
9446
|
+
entry.contextWindow,
|
|
9447
|
+
entry.context_length,
|
|
9448
|
+
entry.max_input_tokens,
|
|
9449
|
+
entry.limit?.context
|
|
9450
|
+
].map(positiveNumber).find((value) => value !== void 0);
|
|
9451
|
+
}
|
|
9452
|
+
function toCachedModel(entry, isFree) {
|
|
9453
|
+
const id = typeof entry.id === "string" ? entry.id.trim() : "";
|
|
9454
|
+
if (!id) return null;
|
|
9455
|
+
const name = typeof entry.name === "string" && entry.name.trim() ? entry.name.trim() : id;
|
|
9456
|
+
const reportedContextWindow = contextWindow(entry);
|
|
9457
|
+
const cost = isFree ? { input: 0, output: 0 } : void 0;
|
|
9458
|
+
const freeStatus = classifyFreeStatus({ model: { cost, isFree } });
|
|
9459
|
+
const family = id.split("/").pop()?.split(/[-:]/)[0] ?? id;
|
|
9460
|
+
return {
|
|
9461
|
+
id,
|
|
9462
|
+
name,
|
|
9463
|
+
upstreamModelId: id,
|
|
9464
|
+
family,
|
|
9465
|
+
brand: deriveBrand(family),
|
|
9466
|
+
contextWindow: reportedContextWindow,
|
|
9467
|
+
contextWindowSource: reportedContextWindow === void 0 ? void 0 : "provider",
|
|
9468
|
+
cost,
|
|
9469
|
+
isFree: isFreeStatus(freeStatus),
|
|
9470
|
+
freeStatus,
|
|
9471
|
+
modelFormat: "openai",
|
|
9472
|
+
npm: "@ai-sdk/openai-compatible"
|
|
9473
|
+
};
|
|
9474
|
+
}
|
|
9475
|
+
function parseClinePassModels(payload) {
|
|
9476
|
+
if (!payload || typeof payload !== "object") return [];
|
|
9477
|
+
const body = payload;
|
|
9478
|
+
const byId = /* @__PURE__ */ new Map();
|
|
9479
|
+
for (const entry of entries(body.clinePass)) {
|
|
9480
|
+
const model = toCachedModel(entry, false);
|
|
9481
|
+
if (model) byId.set(model.id, model);
|
|
9482
|
+
}
|
|
9483
|
+
for (const entry of entries(body.free)) {
|
|
9484
|
+
const model = toCachedModel(entry, true);
|
|
9485
|
+
if (model && !byId.has(model.id)) byId.set(model.id, model);
|
|
9486
|
+
}
|
|
9487
|
+
return [...byId.values()];
|
|
9488
|
+
}
|
|
9489
|
+
async function fetchJson(url, headers) {
|
|
9490
|
+
const controller = new AbortController();
|
|
9491
|
+
const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
9492
|
+
trace(`ClinePass GET ${url} authorization=${headers?.Authorization ? "present" : "absent"}`);
|
|
9493
|
+
try {
|
|
9494
|
+
const response = await fetch(url, {
|
|
9495
|
+
method: "GET",
|
|
9496
|
+
headers: { Accept: "application/json", ...headers },
|
|
9497
|
+
redirect: "manual",
|
|
9498
|
+
signal: controller.signal
|
|
9499
|
+
});
|
|
9500
|
+
trace(`ClinePass response status=${response.status} url=${url}`);
|
|
9501
|
+
return response;
|
|
9502
|
+
} catch (err) {
|
|
9503
|
+
trace(`ClinePass request failed url=${url} error=${err instanceof Error ? err.message : String(err)}`);
|
|
9504
|
+
throw err;
|
|
9505
|
+
} finally {
|
|
9506
|
+
clearTimeout(timer);
|
|
9507
|
+
}
|
|
9508
|
+
}
|
|
9509
|
+
async function fetchClinePassModels() {
|
|
9510
|
+
const response = await fetchJson(CLINE_PASS_CATALOG_URL);
|
|
9511
|
+
if (!response.ok) {
|
|
9512
|
+
const body = await responseBodyPreview(response);
|
|
9513
|
+
if (body) trace(`ClinePass catalog body=${body}`);
|
|
9514
|
+
throw new Error(`ClinePass catalog returned HTTP ${response.status}.`);
|
|
9515
|
+
}
|
|
9516
|
+
const payload = await response.json().catch(() => null);
|
|
9517
|
+
const models = parseClinePassModels(payload);
|
|
9518
|
+
trace(`ClinePass catalog parsed models=${models.length}`);
|
|
9519
|
+
if (models.length === 0) throw new Error("ClinePass catalog returned no usable models.");
|
|
9520
|
+
return models;
|
|
9521
|
+
}
|
|
9522
|
+
async function validateClinePassApiKey(apiKey) {
|
|
9523
|
+
const response = await fetchJson(CLINE_PASS_VALIDATION_URL, {
|
|
9524
|
+
Authorization: `Bearer ${apiKey.trim()}`
|
|
9525
|
+
});
|
|
9526
|
+
const body = await responseBodyPreview(response);
|
|
9527
|
+
if (body) trace(`ClinePass validation body=${body}`);
|
|
9528
|
+
if (response.status === 401 || response.status === 403) {
|
|
9529
|
+
throw new Error("API key was rejected.");
|
|
9530
|
+
}
|
|
9531
|
+
if (!response.ok) {
|
|
9532
|
+
throw new Error(`ClinePass API key validation returned HTTP ${response.status}.`);
|
|
9533
|
+
}
|
|
9534
|
+
}
|
|
9535
|
+
|
|
9181
9536
|
// src/registry/model-source.ts
|
|
9182
9537
|
init_provider_templates();
|
|
9183
9538
|
|
|
@@ -9347,8 +9702,8 @@ async function refreshZenGoProvider(provider) {
|
|
|
9347
9702
|
});
|
|
9348
9703
|
}
|
|
9349
9704
|
async function refreshClaudeCodeOAuthModels(accessToken) {
|
|
9350
|
-
const
|
|
9351
|
-
const models =
|
|
9705
|
+
const entries2 = await fetchClaudeCodeModels(accessToken);
|
|
9706
|
+
const models = entries2.map((entry) => ({
|
|
9352
9707
|
id: entry.id,
|
|
9353
9708
|
name: entry.displayName,
|
|
9354
9709
|
upstreamModelId: entry.id,
|
|
@@ -9529,7 +9884,7 @@ async function fetchJsonWithAuth(url, accessToken, timeoutMs, extraHeaders = {})
|
|
|
9529
9884
|
async function refreshOpenAiOAuthModels(accessToken) {
|
|
9530
9885
|
const TIMEOUT_MS = 1e4;
|
|
9531
9886
|
const seedById = new Map(buildOpenAiOAuthModels().map((m) => [m.id, m]));
|
|
9532
|
-
const toModels = (
|
|
9887
|
+
const toModels = (entries2) => entries2.map((entry) => buildDynamicOAuthModel(entry, seedById));
|
|
9533
9888
|
const claudeVersion = getInstalledClaudeVersion();
|
|
9534
9889
|
const codexResult = await fetchJsonWithAuth(
|
|
9535
9890
|
`https://chatgpt.com/backend-api/codex/models?client_version=${claudeVersion}`,
|
|
@@ -9560,9 +9915,9 @@ async function refreshXaiOAuthModels(accessToken) {
|
|
|
9560
9915
|
const seedById = new Map(seed.map((m) => [m.id, m]));
|
|
9561
9916
|
const result = await fetchJsonWithAuth("https://api.x.ai/v1/models", accessToken, 8e3);
|
|
9562
9917
|
if (result.body) {
|
|
9563
|
-
const
|
|
9564
|
-
if (
|
|
9565
|
-
const live =
|
|
9918
|
+
const entries2 = (result.body.data ?? []).filter((m) => !!m.id);
|
|
9919
|
+
if (entries2.length > 0) {
|
|
9920
|
+
const live = entries2.map(({ id, context_length }) => {
|
|
9566
9921
|
const cached = seedById.get(id);
|
|
9567
9922
|
if (cached) return cached;
|
|
9568
9923
|
const prefix = id.split("-")[0] ?? id;
|
|
@@ -9669,6 +10024,19 @@ async function refreshProviderModels(providerId, apiKey, registry = loadRegistry
|
|
|
9669
10024
|
let oauthFallbackReason;
|
|
9670
10025
|
if (source === "zen-go-api") {
|
|
9671
10026
|
models = await refreshZenGoProvider(provider);
|
|
10027
|
+
} else if (source === "cline-recommended") {
|
|
10028
|
+
try {
|
|
10029
|
+
models = await fetchClinePassModels();
|
|
10030
|
+
baseUrl = provider.api.url ?? "https://api.cline.bot/api/v1";
|
|
10031
|
+
} catch (err) {
|
|
10032
|
+
if (cachedModelCount(provider) > 0) {
|
|
10033
|
+
return skipWithCachedModels(
|
|
10034
|
+
provider,
|
|
10035
|
+
`ClinePass catalog refresh failed: ${err instanceof Error ? err.message : String(err)} Kept the existing cached model list; try again later.`
|
|
10036
|
+
);
|
|
10037
|
+
}
|
|
10038
|
+
throw err;
|
|
10039
|
+
}
|
|
9672
10040
|
} 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")) {
|
|
9673
10041
|
if (!apiKey) {
|
|
9674
10042
|
return {
|
|
@@ -10453,7 +10821,15 @@ async function handleAnthropicMessages(req, res, options, modelCache, plog, suba
|
|
|
10453
10821
|
return;
|
|
10454
10822
|
}
|
|
10455
10823
|
const apiKey = model.apiKey ?? options.apiKey;
|
|
10456
|
-
const languageModel = await getOrInitLanguageModel(
|
|
10824
|
+
const languageModel = await getOrInitLanguageModel(
|
|
10825
|
+
modelCache,
|
|
10826
|
+
model,
|
|
10827
|
+
model.npm,
|
|
10828
|
+
model.apiBaseUrl,
|
|
10829
|
+
apiKey,
|
|
10830
|
+
options.vertex,
|
|
10831
|
+
providerRefreshToken(model.providerId, model.authType)
|
|
10832
|
+
);
|
|
10457
10833
|
const npmMaxTools = maxToolsForNpm(model.npm);
|
|
10458
10834
|
const toolCount = Array.isArray(body.tools) ? body.tools.length : 0;
|
|
10459
10835
|
if (npmMaxTools !== void 0 && toolCount > npmMaxTools) {
|
|
@@ -10569,7 +10945,15 @@ async function handleOpenAIChatCompletions(req, res, options, modelCache, plog)
|
|
|
10569
10945
|
}
|
|
10570
10946
|
const apiKey = model.apiKey ?? options.apiKey;
|
|
10571
10947
|
const baseURL = model.modelFormat === "anthropic" ? model.baseUrl : model.apiBaseUrl;
|
|
10572
|
-
const languageModel = await getOrInitLanguageModel(
|
|
10948
|
+
const languageModel = await getOrInitLanguageModel(
|
|
10949
|
+
modelCache,
|
|
10950
|
+
model,
|
|
10951
|
+
npm,
|
|
10952
|
+
baseURL,
|
|
10953
|
+
apiKey,
|
|
10954
|
+
options.vertex,
|
|
10955
|
+
providerRefreshToken(model.providerId, model.authType)
|
|
10956
|
+
);
|
|
10573
10957
|
const params = translateOpenAiRequest(body);
|
|
10574
10958
|
const clientWantsStream = Boolean(body.stream);
|
|
10575
10959
|
const responseModelId = getResponseModelId(body.model, model, options);
|
|
@@ -10616,7 +11000,7 @@ function backendFor(options, model) {
|
|
|
10616
11000
|
if (model.sourceBackend === "go") return options.backends.go;
|
|
10617
11001
|
throw new Error(`Provider ${model.sourceBackend} is not a cloud backend \u2014 model must set baseUrl/completionsUrl`);
|
|
10618
11002
|
}
|
|
10619
|
-
async function getOrInitLanguageModel(modelCache, model, npm, baseURL, apiKey, vertex) {
|
|
11003
|
+
async function getOrInitLanguageModel(modelCache, model, npm, baseURL, apiKey, vertex, refreshToken) {
|
|
10620
11004
|
const cacheKey = [
|
|
10621
11005
|
model.providerId ?? model.sourceBackend,
|
|
10622
11006
|
model.id,
|
|
@@ -10636,6 +11020,10 @@ async function getOrInitLanguageModel(modelCache, model, npm, baseURL, apiKey, v
|
|
|
10636
11020
|
oauthAccountId: model.oauthAccountId,
|
|
10637
11021
|
vertex,
|
|
10638
11022
|
headers: model.headers,
|
|
11023
|
+
refreshToken,
|
|
11024
|
+
onTokenRefreshed: (refreshed) => {
|
|
11025
|
+
model.apiKey = refreshed;
|
|
11026
|
+
},
|
|
10639
11027
|
useResponsesLite: model.useResponsesLite,
|
|
10640
11028
|
preferWebSockets: model.preferWebSockets
|
|
10641
11029
|
});
|
|
@@ -11579,7 +11967,24 @@ async function addProviderFromTemplate(template, apiKey, opts) {
|
|
|
11579
11967
|
hint: `Remove it first with: relay-ai providers remove ${template.id}`
|
|
11580
11968
|
};
|
|
11581
11969
|
}
|
|
11582
|
-
|
|
11970
|
+
let fetched;
|
|
11971
|
+
if (template.modelSource === "cline-recommended") {
|
|
11972
|
+
try {
|
|
11973
|
+
await validateClinePassApiKey(trimmedKey);
|
|
11974
|
+
fetched = {
|
|
11975
|
+
models: await fetchClinePassModels(),
|
|
11976
|
+
baseUrl: template.defaultBaseUrl ?? ""
|
|
11977
|
+
};
|
|
11978
|
+
} catch (err) {
|
|
11979
|
+
return {
|
|
11980
|
+
added: false,
|
|
11981
|
+
error: err instanceof Error ? err.message : String(err),
|
|
11982
|
+
hint: template.signupUrl ? `Verify your key at ${template.signupUrl}` : void 0
|
|
11983
|
+
};
|
|
11984
|
+
}
|
|
11985
|
+
} else {
|
|
11986
|
+
fetched = await fetchTemplateModels(template, trimmedKey, opts?.baseUrl);
|
|
11987
|
+
}
|
|
11583
11988
|
if (fetched.error || fetched.models.length === 0) {
|
|
11584
11989
|
return {
|
|
11585
11990
|
added: false,
|
|
@@ -11621,7 +12026,8 @@ async function addProviderFromTemplate(template, apiKey, opts) {
|
|
|
11621
12026
|
authType: template.authType,
|
|
11622
12027
|
api: {
|
|
11623
12028
|
npm: template.npm,
|
|
11624
|
-
url: fetched.baseUrl
|
|
12029
|
+
url: fetched.baseUrl,
|
|
12030
|
+
...template.headers ? { headers: template.headers } : {}
|
|
11625
12031
|
},
|
|
11626
12032
|
addedAt: existing?.addedAt ?? now,
|
|
11627
12033
|
refreshedAt: now,
|
|
@@ -11637,6 +12043,9 @@ async function addProviderFromTemplate(template, apiKey, opts) {
|
|
|
11637
12043
|
registry.providers.push(entry);
|
|
11638
12044
|
}
|
|
11639
12045
|
saveRegistry(registry);
|
|
12046
|
+
if (existing?.authRef && existing.authRef !== authRef) {
|
|
12047
|
+
await deleteProviderCredential(existing.authRef);
|
|
12048
|
+
}
|
|
11640
12049
|
enrichPricingAsync();
|
|
11641
12050
|
return { added: true, provider: entry, modelCount: pricedModels.length };
|
|
11642
12051
|
}
|
|
@@ -11654,7 +12063,8 @@ var PROVIDER_DISPLAY = {
|
|
|
11654
12063
|
"openai-oauth": OPENAI_DISPLAY,
|
|
11655
12064
|
"github-copilot": "GitHub Copilot",
|
|
11656
12065
|
"claude-code": "Claude Code (Anthropic subscription)",
|
|
11657
|
-
antigravity: "Antigravity (Google Cloud Code Assist)"
|
|
12066
|
+
antigravity: "Antigravity (Google Cloud Code Assist)",
|
|
12067
|
+
"cline-pass": "ClinePass"
|
|
11658
12068
|
};
|
|
11659
12069
|
function openBrowser(url) {
|
|
11660
12070
|
open3(url).catch(() => {
|
|
@@ -11688,6 +12098,17 @@ async function runNativeDeviceCode(providerId) {
|
|
|
11688
12098
|
spinner3.stop(pc6.green("Signed in to GitHub Copilot"));
|
|
11689
12099
|
return tokensToStoredCredential(tokens2);
|
|
11690
12100
|
}
|
|
12101
|
+
if (providerId === "cline-pass") {
|
|
12102
|
+
const result = await runClinePassDeviceCodeFlow(({ url, userCode }) => {
|
|
12103
|
+
spinner3.stop("");
|
|
12104
|
+
p5.log.info(`Visit: ${pc6.cyan(url)}`);
|
|
12105
|
+
p5.log.info(`Enter code: ${pc6.bold(userCode)}`);
|
|
12106
|
+
openBrowser(url);
|
|
12107
|
+
spinner3.start("Waiting for authorization...");
|
|
12108
|
+
});
|
|
12109
|
+
spinner3.stop(pc6.green("Signed in to ClinePass"));
|
|
12110
|
+
return tokensToStoredCredential(result.tokens, void 0, result.accountId, result.providerData);
|
|
12111
|
+
}
|
|
11691
12112
|
const { tokens, accountId } = await runOpenAiDeviceCodeFlow(({ url, userCode }) => {
|
|
11692
12113
|
spinner3.stop("");
|
|
11693
12114
|
p5.log.info(`Visit: ${pc6.cyan(url)}`);
|
|
@@ -11781,6 +12202,7 @@ async function upsertOAuthProvider(providerId, cred) {
|
|
|
11781
12202
|
const authRef = oauthAuthRef(registryId);
|
|
11782
12203
|
const template = getTemplateById(templateId) ?? getTemplateById(registryId);
|
|
11783
12204
|
let entry = registry.providers.find((pr) => pr.id === registryId);
|
|
12205
|
+
const previousAuthRef = entry?.authRef;
|
|
11784
12206
|
if (!entry) {
|
|
11785
12207
|
if (!template) {
|
|
11786
12208
|
throw new Error(`Provider "${providerId}" is not in your registry and has no template`);
|
|
@@ -11807,6 +12229,9 @@ async function upsertOAuthProvider(providerId, cred) {
|
|
|
11807
12229
|
if (idx >= 0) registry.providers[idx] = entry;
|
|
11808
12230
|
else registry.providers.push(entry);
|
|
11809
12231
|
saveRegistry(registry);
|
|
12232
|
+
if (previousAuthRef && previousAuthRef !== authRef) {
|
|
12233
|
+
await deleteProviderCredential(previousAuthRef);
|
|
12234
|
+
}
|
|
11810
12235
|
return entry;
|
|
11811
12236
|
}
|
|
11812
12237
|
async function authenticateProvider(providerId, options = {}) {
|
|
@@ -11852,11 +12277,13 @@ ${pc6.bold("Usage:")}
|
|
|
11852
12277
|
relay-ai providers auth xai-oauth
|
|
11853
12278
|
relay-ai providers auth openai-oauth
|
|
11854
12279
|
relay-ai providers auth github-copilot
|
|
12280
|
+
relay-ai providers auth cline-pass
|
|
11855
12281
|
|
|
11856
12282
|
${pc6.bold("Device code (works on SSH/VPS):")}
|
|
11857
12283
|
xai-oauth SuperGrok / X Premium (device code at x.ai/device)
|
|
11858
12284
|
openai-oauth ChatGPT Plus/Pro (device code at auth.openai.com/codex/device)
|
|
11859
12285
|
github-copilot GitHub Copilot Free or paid (device code at github.com/login/device)
|
|
12286
|
+
cline-pass ClinePass account (device code at app.cline.bot)
|
|
11860
12287
|
|
|
11861
12288
|
${pc6.dim("OpenCode CLI configs: use")} relay-ai providers import${pc6.dim(" (optional one-time migration).")}`;
|
|
11862
12289
|
}
|
|
@@ -12452,6 +12879,8 @@ export {
|
|
|
12452
12879
|
ANTIGRAVITY_BASE_URLS,
|
|
12453
12880
|
buildAntigravityAuthUrl,
|
|
12454
12881
|
completeAntigravityExchange,
|
|
12882
|
+
requestClinePassDeviceCode,
|
|
12883
|
+
pollClinePassDeviceCode,
|
|
12455
12884
|
detectConflicts,
|
|
12456
12885
|
resolveApiKey,
|
|
12457
12886
|
buildChildEnv,
|
|
@@ -12489,6 +12918,7 @@ export {
|
|
|
12489
12918
|
getUiDebugLogPath,
|
|
12490
12919
|
getServerDebugLogPath,
|
|
12491
12920
|
getAntigravityDebugLogPath,
|
|
12921
|
+
prepareProviderTraceLog,
|
|
12492
12922
|
makeTraceLogger,
|
|
12493
12923
|
writeSecureLogLine,
|
|
12494
12924
|
printTraceLog,
|
|
@@ -12530,6 +12960,7 @@ export {
|
|
|
12530
12960
|
aliasModelId,
|
|
12531
12961
|
startProxyCatalog,
|
|
12532
12962
|
startProxy,
|
|
12963
|
+
providerRefreshToken,
|
|
12533
12964
|
makeRouteResolver,
|
|
12534
12965
|
buildCatalogRoutes,
|
|
12535
12966
|
hostFromHeader,
|
|
@@ -12591,4 +13022,4 @@ export {
|
|
|
12591
13022
|
supportsClaudeTransparentMode,
|
|
12592
13023
|
buildHttpProxyRoutes
|
|
12593
13024
|
};
|
|
12594
|
-
//# sourceMappingURL=chunk-
|
|
13025
|
+
//# sourceMappingURL=chunk-5DDQJSTU.js.map
|