@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.
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  getTemplateById,
4
4
  init_provider_templates
5
- } from "./chunk-HXGZ4CTV.js";
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.7.6",
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
- ...apiKey.trim() ? { apiKey } : {},
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(contextWindow) {
1506
- if (!contextWindow) return "";
1507
- const k = contextWindow >= 1e3 ? `${Math.round(contextWindow / 1e3)}k` : String(contextWindow);
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, contextWindow) {
2195
+ function claudeCodeClientModelId(modelId, contextWindow2) {
2150
2196
  const bare = stripOneMContextSuffix(modelId);
2151
- const window = resolveContextWindow(bare, contextWindow);
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 entries = {};
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) entries[providerId] = 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 entries = (body.data ?? []).filter((m) => typeof m.id === "string" && m.id.length > 0).map((m) => ({
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 (entries.length === 0) {
3036
+ if (entries2.length === 0) {
2991
3037
  throw new Error("Claude Code model discovery returned no models");
2992
3038
  }
2993
- return entries;
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
- return tokensToStoredCredential(tokens, cred.refresh, cred.accountId, cred.providerData);
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, contextWindow, enableGatewayDiscovery) {
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, contextWindow);
3117
- env["CLAUDE_CODE_MAX_CONTEXT_TOKENS"] = String(resolveContextWindow(bareModel, contextWindow));
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, contextWindow, options) {
4556
- const maxInput = resolveContextWindow(id, contextWindow);
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(entries) {
4782
+ function formatAnthropicModelList(entries2) {
4568
4783
  return {
4569
- data: entries.map((entry) => formatAnthropicModelEntry(
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: entries[0]?.id ?? null,
4577
- last_id: entries.at(-1)?.id ?? null
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, contextWindow, sdk, apiKey) {
6903
+ function startProxy(completionsUrl, modelId, debug = false, contextWindow2, sdk, apiKey) {
6685
6904
  const bareModelId = stripOneMContextSuffix(modelId);
6686
- const clientModelId = claudeCodeClientModelId(modelId, contextWindow);
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
- contextWindow: cached.contextWindow ?? resolveContextWindow(id),
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 contextWindow = numericValue(row["context_length"]) ?? numericValue(row["contextWindow"]) ?? numericValue(row["context_window"]) ?? resolveContextWindow(id);
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 entries = [];
8089
+ const entries2 = [];
7856
8090
  for (const provider of reg.providers) {
7857
- entries.push({
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 entries.sort((a, b) => a.name.localeCompare(b.name));
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 contextWindow = favModel.contextWindow;
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, contextWindow) {
8596
- return contextWindow === void 0 || contextWindow >= contextFloorForTarget(target);
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 contextWindow = contextWindowFromProps ?? row.context_length ?? row.contextWindow ?? row.context_window ?? resolveContextWindow(id);
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 entries = await fetchClaudeCodeModels(accessToken);
9351
- const models = entries.map((entry) => ({
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 = (entries) => entries.map((entry) => buildDynamicOAuthModel(entry, seedById));
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 entries = (result.body.data ?? []).filter((m) => !!m.id);
9564
- if (entries.length > 0) {
9565
- const live = entries.map(({ id, context_length }) => {
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(modelCache, model, model.npm, model.apiBaseUrl, apiKey, options.vertex);
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(modelCache, model, npm, baseURL, apiKey, options.vertex);
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
- const fetched = await fetchTemplateModels(template, trimmedKey, opts?.baseUrl);
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-GHSURQOK.js.map
13025
+ //# sourceMappingURL=chunk-5DDQJSTU.js.map