@jacobbd/relay-ai 0.7.5 → 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.5",
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}`);
@@ -4315,11 +4530,245 @@ function maskGatewayModelId(aliasId) {
4315
4530
  return `anthropic-${reverseSegment(providerSlug)}__${reverseSegment(modelSuffix)}`;
4316
4531
  }
4317
4532
 
4533
+ // src/subagent-route-registry.ts
4534
+ import { randomUUID as randomUUID2 } from "crypto";
4535
+ var DEFAULT_TTL_MS = 5 * 6e4;
4536
+ var DEFAULT_MAX_ENTRIES = 1024;
4537
+ var ROUTE_MARKER_PATTERN = /(?:\n\n)?<relay-ai-subagent-route token="([0-9a-f-]{36})"\s*\/>/gi;
4538
+ function firstHeader(value) {
4539
+ const first = Array.isArray(value) ? value[0] : value;
4540
+ return typeof first === "string" && first.trim() ? first.trim() : void 0;
4541
+ }
4542
+ function extractClaudeSessionId(headers, body) {
4543
+ const headerSession = firstHeader(headers["x-claude-code-session-id"]);
4544
+ if (headerSession) return headerSession;
4545
+ const userId = body?.metadata?.user_id;
4546
+ if (typeof userId !== "string") return void 0;
4547
+ try {
4548
+ const parsed = JSON.parse(userId);
4549
+ return typeof parsed.session_id === "string" && parsed.session_id.trim() ? parsed.session_id.trim() : void 0;
4550
+ } catch {
4551
+ return void 0;
4552
+ }
4553
+ }
4554
+ function appendSubagentRouteMarker(prompt, token) {
4555
+ return `${prompt}
4556
+
4557
+ <relay-ai-subagent-route token="${token}"/>`;
4558
+ }
4559
+ function findMarkersInUserMessages(body) {
4560
+ if (!Array.isArray(body.messages)) return void 0;
4561
+ const tokens = [];
4562
+ const messages = [...body.messages];
4563
+ for (let messageIndex = 0; messageIndex < messages.length; messageIndex++) {
4564
+ const message = body.messages[messageIndex];
4565
+ if (!message || message.role !== "user") continue;
4566
+ if (typeof message.content === "string") {
4567
+ const matches = [...message.content.matchAll(ROUTE_MARKER_PATTERN)];
4568
+ if (matches.length === 0) continue;
4569
+ tokens.push(...matches.flatMap((match) => match[1] ? [match[1]] : []));
4570
+ messages[messageIndex] = {
4571
+ ...message,
4572
+ content: message.content.replace(ROUTE_MARKER_PATTERN, "").trimEnd()
4573
+ };
4574
+ continue;
4575
+ }
4576
+ if (!Array.isArray(message.content)) continue;
4577
+ const content = [...message.content];
4578
+ for (let partIndex = 0; partIndex < message.content.length; partIndex++) {
4579
+ const part = message.content[partIndex];
4580
+ if (!part || part.type !== "text" || typeof part.text !== "string") continue;
4581
+ const matches = [...part.text.matchAll(ROUTE_MARKER_PATTERN)];
4582
+ if (matches.length === 0) continue;
4583
+ tokens.push(...matches.flatMap((match) => match[1] ? [match[1]] : []));
4584
+ content[partIndex] = {
4585
+ ...part,
4586
+ text: part.text.replace(ROUTE_MARKER_PATTERN, "").trimEnd()
4587
+ };
4588
+ }
4589
+ messages[messageIndex] = { ...message, content };
4590
+ }
4591
+ return tokens.length > 0 ? { tokens, body: { ...body, messages } } : void 0;
4592
+ }
4593
+ var SubagentRouteRegistry = class {
4594
+ entries = /* @__PURE__ */ new Map();
4595
+ ttlMs;
4596
+ maxEntries;
4597
+ now;
4598
+ constructor(options = {}) {
4599
+ this.ttlMs = options.ttlMs ?? DEFAULT_TTL_MS;
4600
+ this.maxEntries = options.maxEntries ?? DEFAULT_MAX_ENTRIES;
4601
+ this.now = options.now ?? Date.now;
4602
+ }
4603
+ register(sessionId, modelId) {
4604
+ this.cleanup();
4605
+ while (this.entries.size >= this.maxEntries) {
4606
+ const oldest = this.entries.keys().next().value;
4607
+ if (!oldest) break;
4608
+ this.entries.delete(oldest);
4609
+ }
4610
+ const token = randomUUID2();
4611
+ this.entries.set(token, { sessionId, modelId, createdAt: this.now() });
4612
+ return token;
4613
+ }
4614
+ consume(headers, body) {
4615
+ this.cleanup();
4616
+ if (!firstHeader(headers["x-claude-code-agent-id"])) return void 0;
4617
+ const sessionId = extractClaudeSessionId(headers, body);
4618
+ if (!sessionId) return void 0;
4619
+ const marked = findMarkersInUserMessages(body);
4620
+ if (!marked) return void 0;
4621
+ const token = [...marked.tokens].reverse().find((candidate) => this.entries.get(candidate)?.sessionId === sessionId);
4622
+ if (!token) return void 0;
4623
+ const entry = this.entries.get(token);
4624
+ this.entries.delete(token);
4625
+ return { modelId: entry.modelId, body: marked.body };
4626
+ }
4627
+ cleanup() {
4628
+ const cutoff = this.now() - this.ttlMs;
4629
+ for (const [token, entry] of this.entries) {
4630
+ if (entry.createdAt < cutoff) this.entries.delete(token);
4631
+ }
4632
+ }
4633
+ };
4634
+
4635
+ // src/subagent-model-routing.ts
4636
+ var CLAUDE_MODEL_FAMILIES = ["sonnet", "opus", "haiku", "fable"];
4637
+ var CLAUDE_MODEL_FAMILY_SET = new Set(CLAUDE_MODEL_FAMILIES);
4638
+ function isRecord(value) {
4639
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4640
+ }
4641
+ function claudeModelFamily(modelId) {
4642
+ const normalized = modelId.toLowerCase();
4643
+ if (!normalized.startsWith("claude-")) return void 0;
4644
+ return CLAUDE_MODEL_FAMILIES.find((family) => normalized.includes(family));
4645
+ }
4646
+ function isClaudeAgentTool(tool4) {
4647
+ if (tool4.name !== "Agent" || !isRecord(tool4.input_schema)) return false;
4648
+ const properties = tool4.input_schema.properties;
4649
+ if (!isRecord(properties)) return false;
4650
+ return ["description", "prompt", "subagent_type"].every((name) => isRecord(properties[name]));
4651
+ }
4652
+ var UnavailableSubagentModelError = class extends Error {
4653
+ constructor(selector, routing) {
4654
+ const visible = routing.models.slice(0, MAX_MODEL_CATALOG).map((model) => model.id);
4655
+ const omitted = routing.models.length - visible.length;
4656
+ const suffix = omitted > 0 ? `, and ${omitted} more` : "";
4657
+ super(
4658
+ `Subagent model "${selector}" is unavailable in this Relay AI session. Available model ids: ${visible.join(", ")}${suffix}.`
4659
+ );
4660
+ this.selector = selector;
4661
+ this.name = "UnavailableSubagentModelError";
4662
+ }
4663
+ selector;
4664
+ statusCode = 400;
4665
+ };
4666
+ function normalizeClaudeAgentInput(input, routing) {
4667
+ const source = isRecord(input) ? input : {};
4668
+ const normalized = { ...source };
4669
+ if (source.subagent_type === "fork") {
4670
+ return { input: normalized, decision: { kind: "fork" } };
4671
+ }
4672
+ const rawModel = source.model;
4673
+ if (rawModel == null) {
4674
+ normalized.model = routing.parentModelId;
4675
+ return {
4676
+ input: normalized,
4677
+ decision: { kind: "inherit", resolvedModelId: routing.parentModelId }
4678
+ };
4679
+ }
4680
+ if (typeof rawModel !== "string") {
4681
+ throw new UnavailableSubagentModelError(String(rawModel), routing);
4682
+ }
4683
+ const selector = rawModel.trim();
4684
+ if (selector === "" || selector === "inherit") {
4685
+ normalized.model = routing.parentModelId;
4686
+ return {
4687
+ input: normalized,
4688
+ decision: { kind: "inherit", resolvedModelId: routing.parentModelId }
4689
+ };
4690
+ }
4691
+ const exposed = routing.models.find((model) => model.id === selector);
4692
+ if (exposed) {
4693
+ normalized.model = exposed.id;
4694
+ return {
4695
+ input: normalized,
4696
+ decision: { kind: "explicit", resolvedModelId: exposed.id }
4697
+ };
4698
+ }
4699
+ const compatible = routing.models.find((model) => model.compatibilityIds.includes(selector));
4700
+ if (compatible) {
4701
+ normalized.model = compatible.id;
4702
+ return {
4703
+ input: normalized,
4704
+ decision: {
4705
+ kind: "compatibility",
4706
+ requestedModelId: selector,
4707
+ resolvedModelId: compatible.id
4708
+ }
4709
+ };
4710
+ }
4711
+ if (CLAUDE_MODEL_FAMILY_SET.has(selector)) {
4712
+ const family = selector;
4713
+ const nativeModel = routing.models.find((model) => model.family === family);
4714
+ const resolvedModelId = nativeModel?.id ?? routing.parentModelId;
4715
+ normalized.model = resolvedModelId;
4716
+ return {
4717
+ input: normalized,
4718
+ decision: nativeModel ? { kind: "family", requestedModelId: family, resolvedModelId } : { kind: "family-fallback", requestedModelId: family, resolvedModelId }
4719
+ };
4720
+ }
4721
+ throw new UnavailableSubagentModelError(selector, routing);
4722
+ }
4723
+ function prepareClaudeAgentInput(input, routing) {
4724
+ const normalized = normalizeClaudeAgentInput(input, routing);
4725
+ const decision = normalized.decision;
4726
+ if (decision.kind === "fork") return normalized;
4727
+ if (!routing.registerSubagentRoute) return normalized;
4728
+ const prompt = normalized.input.prompt;
4729
+ if (typeof prompt !== "string") return normalized;
4730
+ const token = routing.registerSubagentRoute(decision.resolvedModelId);
4731
+ const clientInput = { ...normalized.input };
4732
+ const target = routing.models.find((model) => model.id === decision.resolvedModelId);
4733
+ if (target?.family) clientInput.model = target.family;
4734
+ else delete clientInput.model;
4735
+ clientInput.prompt = appendSubagentRouteMarker(prompt, token);
4736
+ return { input: clientInput, decision };
4737
+ }
4738
+ function augmentClaudeAgentTool(tool4, routing) {
4739
+ const inputSchema = isRecord(tool4.input_schema) ? tool4.input_schema : {};
4740
+ const properties = isRecord(inputSchema.properties) ? inputSchema.properties : {};
4741
+ const originalModel = isRecord(properties.model) ? properties.model : {};
4742
+ const smallCatalog = routing.models.length <= MAX_MODEL_CATALOG;
4743
+ const modelProperty = {
4744
+ ...originalModel,
4745
+ type: "string"
4746
+ };
4747
+ if (smallCatalog) {
4748
+ const originalEnum = Array.isArray(originalModel.enum) ? originalModel.enum.filter((value) => typeof value === "string") : CLAUDE_MODEL_FAMILIES;
4749
+ modelProperty.enum = [.../* @__PURE__ */ new Set([...originalEnum, ...routing.models.map((model) => model.id)])];
4750
+ } else {
4751
+ delete modelProperty.enum;
4752
+ }
4753
+ const guidance = smallCatalog ? `Relay AI subagent model routing (default: ${routing.parentModelId}). ` + routing.models.map((model) => `${model.displayName}: ${model.id}`).join("; ") : `Relay AI subagent model routing (default: ${routing.parentModelId}). Other explicit model values must be exact ids from the current session catalog.`;
4754
+ return {
4755
+ ...tool4,
4756
+ description: [tool4.description?.trim(), guidance].filter(Boolean).join("\n\n"),
4757
+ input_schema: {
4758
+ ...inputSchema,
4759
+ properties: {
4760
+ ...properties,
4761
+ model: modelProperty
4762
+ }
4763
+ }
4764
+ };
4765
+ }
4766
+
4318
4767
  // src/server/models.ts
4319
4768
  var CREATED_AT_ISO = "2025-01-01T00:00:00Z";
4320
4769
  var CREATED_AT_UNIX = 1735689600;
4321
- function formatAnthropicModelEntry(id, displayName, contextWindow, options) {
4322
- const maxInput = resolveContextWindow(id, contextWindow);
4770
+ function formatAnthropicModelEntry(id, displayName, contextWindow2, options) {
4771
+ const maxInput = resolveContextWindow(id, contextWindow2);
4323
4772
  return {
4324
4773
  id,
4325
4774
  type: "model",
@@ -4330,17 +4779,17 @@ function formatAnthropicModelEntry(id, displayName, contextWindow, options) {
4330
4779
  ...options?.supportsOneM !== void 0 ? { supports_1m: options.supportsOneM } : {}
4331
4780
  };
4332
4781
  }
4333
- function formatAnthropicModelList(entries) {
4782
+ function formatAnthropicModelList(entries2) {
4334
4783
  return {
4335
- data: entries.map((entry) => formatAnthropicModelEntry(
4784
+ data: entries2.map((entry) => formatAnthropicModelEntry(
4336
4785
  entry.id,
4337
4786
  entry.name,
4338
4787
  entry.contextWindow,
4339
4788
  { supportsOneM: entry.supportsOneM }
4340
4789
  )),
4341
4790
  has_more: false,
4342
- first_id: entries[0]?.id ?? null,
4343
- last_id: entries.at(-1)?.id ?? null
4791
+ first_id: entries2[0]?.id ?? null,
4792
+ last_id: entries2.at(-1)?.id ?? null
4344
4793
  };
4345
4794
  }
4346
4795
  function gatewayProviderLabel(model) {
@@ -4368,6 +4817,52 @@ function exposedGatewayAliasId(model, opts) {
4368
4817
  const exposed = opts?.maskGatewayIds ? maskGatewayModelId(alias) : alias;
4369
4818
  return singleOneM ? `${stripOneMContextSuffix(exposed)}[1m]` : exposed;
4370
4819
  }
4820
+ function gatewayModelIdentity(model, models, opts) {
4821
+ const collisions = openAiIdCollisions(models);
4822
+ const ids = [];
4823
+ const modelIndex = models.indexOf(model);
4824
+ const firstBareIndex = models.findIndex((candidate) => candidate.id === model.id);
4825
+ if (modelIndex === firstBareIndex || modelIndex < 0) ids.push(model.id);
4826
+ const scopedId = openAiExposedId(model, collisions);
4827
+ if (scopedId !== model.id) ids.push(scopedId);
4828
+ const publicId = exposedGatewayAliasId(model, opts);
4829
+ if (publicId !== model.id) ids.push(publicId);
4830
+ const singleOneM = usesSingleOneMEntry(model, opts);
4831
+ if (singleOneM) {
4832
+ const bareModel = { ...model, id: stripOneMContextSuffix(model.id) };
4833
+ const rawBareAlias = gatewayAliasId(bareModel);
4834
+ const exposedBareAlias = opts?.maskGatewayIds ? maskGatewayModelId(rawBareAlias) : rawBareAlias;
4835
+ ids.push(
4836
+ stripOneMContextSuffix(model.id),
4837
+ rawBareAlias,
4838
+ `${rawBareAlias}[1m]`,
4839
+ exposedBareAlias,
4840
+ `${exposedBareAlias}[1m]`
4841
+ );
4842
+ }
4843
+ if (opts?.maskGatewayIds) {
4844
+ const rawAlias = gatewayAliasId(singleOneM ? { ...model, id: stripOneMContextSuffix(model.id) } : model);
4845
+ if (rawAlias !== publicId) ids.push(rawAlias);
4846
+ }
4847
+ return {
4848
+ publicId,
4849
+ compatibilityIds: [...new Set(ids)]
4850
+ };
4851
+ }
4852
+ function buildServerSubagentModelRouting(models, parentModel, opts) {
4853
+ const identities = models.map((model) => gatewayModelIdentity(model, models, opts));
4854
+ const parentIndex = models.indexOf(parentModel);
4855
+ const parentModelId = parentIndex >= 0 ? identities[parentIndex].publicId : exposedGatewayAliasId(parentModel, opts);
4856
+ return {
4857
+ parentModelId,
4858
+ models: models.map((model, index) => ({
4859
+ id: identities[index].publicId,
4860
+ compatibilityIds: identities[index].compatibilityIds,
4861
+ displayName: gatewayDisplayName(model, opts),
4862
+ family: model.modelFormat === "anthropic" ? claudeModelFamily(model.upstreamModelId ?? model.id) : void 0
4863
+ }))
4864
+ };
4865
+ }
4371
4866
  function gatewayDisplayName(model, opts) {
4372
4867
  const name = opts?.maskGatewayIds ? `${model.name} (${gatewayProviderLabel(model)})` : model.name;
4373
4868
  return usesSingleOneMEntry(model, opts) && !/\b1m$/i.test(name) ? `${name} 1M` : name;
@@ -4387,31 +4882,10 @@ function usesSingleOneMEntry(model, opts) {
4387
4882
  }
4388
4883
  function createGatewayModelCatalog(models, opts) {
4389
4884
  const byId = /* @__PURE__ */ new Map();
4390
- const collisions = openAiIdCollisions(models);
4391
4885
  for (const model of models) {
4392
- if (!byId.has(model.id)) byId.set(model.id, model);
4393
- const scopedId = openAiExposedId(model, collisions);
4394
- if (scopedId !== model.id) byId.set(scopedId, model);
4395
- const alias = exposedGatewayAliasId(model, opts);
4396
- if (alias !== model.id) byId.set(alias, model);
4397
- const singleOneM = usesSingleOneMEntry(model, opts);
4398
- if (singleOneM) {
4399
- const bareModel = { ...model, id: stripOneMContextSuffix(model.id) };
4400
- const rawBareAlias = gatewayAliasId(bareModel);
4401
- const exposedBareAlias = opts?.maskGatewayIds ? maskGatewayModelId(rawBareAlias) : rawBareAlias;
4402
- for (const compatibleId of [
4403
- stripOneMContextSuffix(model.id),
4404
- rawBareAlias,
4405
- `${rawBareAlias}[1m]`,
4406
- exposedBareAlias,
4407
- `${exposedBareAlias}[1m]`
4408
- ]) {
4409
- byId.set(compatibleId, model);
4410
- }
4411
- }
4412
- if (opts?.maskGatewayIds) {
4413
- const rawAlias = gatewayAliasId(singleOneM ? { ...model, id: stripOneMContextSuffix(model.id) } : model);
4414
- if (rawAlias !== alias) byId.set(rawAlias, model);
4886
+ const identity = gatewayModelIdentity(model, models, opts);
4887
+ for (const compatibleId of identity.compatibilityIds) {
4888
+ byId.set(compatibleId, model);
4415
4889
  }
4416
4890
  }
4417
4891
  return {
@@ -4611,10 +5085,10 @@ async function relayAnthropicMessages(res, messagesUrl, body, apiKey, clientWant
4611
5085
  }
4612
5086
 
4613
5087
  // src/antigravity/anthropic-to-cloudcode.ts
4614
- import { randomUUID as randomUUID3 } from "crypto";
5088
+ import { randomUUID as randomUUID4 } from "crypto";
4615
5089
 
4616
5090
  // src/antigravity/request-adapter.ts
4617
- import { randomUUID as randomUUID2 } from "crypto";
5091
+ import { randomUUID as randomUUID3 } from "crypto";
4618
5092
  import { tool, jsonSchema } from "ai";
4619
5093
 
4620
5094
  // src/proxy-shared.ts
@@ -4724,6 +5198,43 @@ function serializeToolResultContent(content) {
4724
5198
  }
4725
5199
 
4726
5200
  // src/antigravity/request-adapter.ts
5201
+ var UNSUPPORTED_VOICE_MESSAGE = "Voice transcription isn\u2019t supported by Relay AI yet. Please type your message. Your coding session remains active.";
5202
+ var OMITTED_VOICE_TEXT = "[Voice recording omitted because transcription is not supported by Relay AI.]";
5203
+ function isSupportedImage(part) {
5204
+ return part.inlineData?.mimeType.toLowerCase().startsWith("image/") ?? false;
5205
+ }
5206
+ function isUnsupportedInlineData(part) {
5207
+ return !!part.inlineData && !isSupportedImage(part);
5208
+ }
5209
+ function sanitizeUnsupportedInlineData(ccReq) {
5210
+ const contents = ccReq.request?.contents ?? [];
5211
+ let latestUserIndex = -1;
5212
+ for (let i = contents.length - 1; i >= 0; i--) {
5213
+ if (contents[i].role === "user") {
5214
+ latestUserIndex = i;
5215
+ break;
5216
+ }
5217
+ }
5218
+ let latestUserTurnHasUnsupportedMedia = false;
5219
+ const sanitizedContents = contents.map((message, index) => ({
5220
+ ...message,
5221
+ parts: message.parts.map((part) => {
5222
+ if (!isUnsupportedInlineData(part)) return part;
5223
+ if (index === latestUserIndex) latestUserTurnHasUnsupportedMedia = true;
5224
+ return { text: OMITTED_VOICE_TEXT };
5225
+ })
5226
+ }));
5227
+ return {
5228
+ request: {
5229
+ ...ccReq,
5230
+ request: {
5231
+ ...ccReq.request,
5232
+ contents: sanitizedContents
5233
+ }
5234
+ },
5235
+ latestUserTurnHasUnsupportedMedia
5236
+ };
5237
+ }
4727
5238
  function tracePartChars(part) {
4728
5239
  if (typeof part.text === "string") return part.text.length;
4729
5240
  if (part.type !== "tool-result") return void 0;
@@ -4883,13 +5394,17 @@ function translateRequest(ccReq, options = {}) {
4883
5394
  }
4884
5395
  }
4885
5396
  } else if (part.inlineData) {
4886
- contentParts.push({
4887
- type: "image",
4888
- image: part.inlineData.data,
4889
- mimeType: part.inlineData.mimeType
4890
- });
5397
+ if (isSupportedImage(part)) {
5398
+ contentParts.push({
5399
+ type: "image",
5400
+ image: part.inlineData.data,
5401
+ mimeType: part.inlineData.mimeType
5402
+ });
5403
+ } else {
5404
+ contentParts.push({ type: "text", text: OMITTED_VOICE_TEXT });
5405
+ }
4891
5406
  } else if (part.functionCall) {
4892
- const id = "call_" + randomUUID2().replace(/-/g, "");
5407
+ const id = "call_" + randomUUID3().replace(/-/g, "");
4893
5408
  const name = part.functionCall.name;
4894
5409
  if (!nameToIdList.has(name)) nameToIdList.set(name, []);
4895
5410
  nameToIdList.get(name).push(id);
@@ -4902,7 +5417,7 @@ function translateRequest(ccReq, options = {}) {
4902
5417
  } else if (part.functionResponse) {
4903
5418
  const name = part.functionResponse.name;
4904
5419
  const idList = nameToIdList.get(name) || [];
4905
- const id = idList.shift() || "call_" + randomUUID2().replace(/-/g, "");
5420
+ const id = idList.shift() || "call_" + randomUUID3().replace(/-/g, "");
4906
5421
  toolResults.push({
4907
5422
  type: "tool-result",
4908
5423
  toolCallId: id,
@@ -5111,7 +5626,7 @@ function anthropicToCloudCode(body, realModelId, projectId) {
5111
5626
  }
5112
5627
  return {
5113
5628
  project: projectId,
5114
- requestId: randomUUID3(),
5629
+ requestId: randomUUID4(),
5115
5630
  model: realModelId,
5116
5631
  userAgent: ANTIGRAVITY_USER_AGENT2,
5117
5632
  requestType: "agent",
@@ -5121,7 +5636,7 @@ function anthropicToCloudCode(body, realModelId, projectId) {
5121
5636
  }
5122
5637
 
5123
5638
  // src/antigravity/cloudcode-to-anthropic.ts
5124
- import { randomUUID as randomUUID4 } from "crypto";
5639
+ import { randomUUID as randomUUID5 } from "crypto";
5125
5640
  function writeEvent(res, event, data) {
5126
5641
  res.write(`event: ${event}
5127
5642
  data: ${JSON.stringify(data)}
@@ -5211,7 +5726,7 @@ function closeBlock(res, state) {
5211
5726
  }
5212
5727
  async function streamCloudCodeToAnthropic(res, upstreamRes, model, log7) {
5213
5728
  const state = {
5214
- messageId: `msg_${randomUUID4().replace(/-/g, "").slice(0, 24)}`,
5729
+ messageId: `msg_${randomUUID5().replace(/-/g, "").slice(0, 24)}`,
5215
5730
  model,
5216
5731
  blockIdx: 0,
5217
5732
  textBlockOpen: false,
@@ -5300,7 +5815,7 @@ async function streamCloudCodeToAnthropic(res, upstreamRes, model, log7) {
5300
5815
  closeBlock(res, state);
5301
5816
  }
5302
5817
  for (const tc of state.toolCalls) {
5303
- const rawToolId = `toolu_${randomUUID4().replace(/-/g, "").slice(0, 16)}`;
5818
+ const rawToolId = `toolu_${randomUUID5().replace(/-/g, "").slice(0, 16)}`;
5304
5819
  const toolId = encodeToolUseId(rawToolId, tc.signature);
5305
5820
  writeEvent(res, "content_block_start", {
5306
5821
  type: "content_block_start",
@@ -5351,7 +5866,7 @@ async function streamCloudCodeToAnthropic(res, upstreamRes, model, log7) {
5351
5866
  }
5352
5867
  async function collectCloudCodeToAnthropic(upstreamRes, model, log7) {
5353
5868
  const text4 = await upstreamRes.text();
5354
- const messageId = `msg_${randomUUID4().replace(/-/g, "").slice(0, 24)}`;
5869
+ const messageId = `msg_${randomUUID5().replace(/-/g, "").slice(0, 24)}`;
5355
5870
  const content = [];
5356
5871
  let stopReason = "end_turn";
5357
5872
  let inputTokens = 0;
@@ -5380,7 +5895,7 @@ async function collectCloudCodeToAnthropic(upstreamRes, model, log7) {
5380
5895
  else content.push({ type: "text", text: part.text });
5381
5896
  } else if (part.functionCall && typeof part.functionCall === "object") {
5382
5897
  const fc = part.functionCall;
5383
- const rawToolId = `toolu_${randomUUID4().replace(/-/g, "").slice(0, 16)}`;
5898
+ const rawToolId = `toolu_${randomUUID5().replace(/-/g, "").slice(0, 16)}`;
5384
5899
  content.push({
5385
5900
  type: "tool_use",
5386
5901
  id: encodeToolUseId(rawToolId, signature ?? pendingThoughtSignature),
@@ -5413,7 +5928,7 @@ async function collectCloudCodeToAnthropic(upstreamRes, model, log7) {
5413
5928
  }
5414
5929
 
5415
5930
  // src/proxy.ts
5416
- import { randomUUID as randomUUID5 } from "crypto";
5931
+ import { randomUUID as randomUUID6 } from "crypto";
5417
5932
 
5418
5933
  // src/sdk-adapter.ts
5419
5934
  import { streamText, generateText, tool as tool2, jsonSchema as jsonSchema2 } from "ai";
@@ -5746,6 +6261,17 @@ function translateRequest2(body, npm, options) {
5746
6261
  if (options?.maxTools !== void 0 && upstreamTools.length > options.maxTools) {
5747
6262
  upstreamTools = upstreamTools.slice(0, options.maxTools);
5748
6263
  }
6264
+ let responseSubagentRouting;
6265
+ if (options?.subagentRouting) {
6266
+ const agentIndex = upstreamTools.findIndex((toolDefinition) => isClaudeAgentTool(toolDefinition));
6267
+ if (agentIndex >= 0) {
6268
+ upstreamTools = upstreamTools.map((toolDefinition, index) => index === agentIndex ? augmentClaudeAgentTool(
6269
+ toolDefinition,
6270
+ options.subagentRouting
6271
+ ) : toolDefinition);
6272
+ responseSubagentRouting = options.subagentRouting;
6273
+ }
6274
+ }
5749
6275
  const effort = anthropicEffortFromRequest(body) ?? options?.defaultEffort;
5750
6276
  let providerOptions = deepMergeProviderOptions(
5751
6277
  thinkingProviderOptions(npm),
@@ -5763,16 +6289,22 @@ function translateRequest2(body, npm, options) {
5763
6289
  toolChoice: translateToolChoice(body.tool_choice),
5764
6290
  maxOutputTokens: options?.openAiOAuth ? void 0 : body.max_tokens,
5765
6291
  temperature: body.temperature,
5766
- providerOptions
6292
+ providerOptions,
6293
+ subagentRouting: responseSubagentRouting
5767
6294
  };
5768
6295
  }
5769
- async function writeAnthropicStream(fullStream, modelId, write, log7, estimatedInputTokens = 0) {
6296
+ function logSubagentDecision(log7, decision) {
6297
+ if (!log7 || decision.kind === "fork" || decision.kind === "explicit") return;
6298
+ log7(() => decision.kind === "family-fallback" ? `sdk Agent model "${decision.requestedModelId}" unavailable; using parent ${decision.resolvedModelId}` : `sdk Agent model ${decision.kind}: ${"requestedModelId" in decision ? `${decision.requestedModelId} -> ` : ""}${decision.resolvedModelId}`);
6299
+ }
6300
+ async function writeAnthropicStream(fullStream, modelId, write, log7, estimatedInputTokens = 0, subagentRouting) {
5770
6301
  const messageId = "msg_" + Date.now();
5771
6302
  let blockIndex = -1;
5772
6303
  let started = false;
5773
6304
  let openType = null;
5774
6305
  let pendingThinkingSig;
5775
6306
  const idToBlock = /* @__PURE__ */ new Map();
6307
+ const bufferedAgentCalls = /* @__PURE__ */ new Map();
5776
6308
  let finishReason = "end_turn";
5777
6309
  let usage = { input_tokens: estimatedInputTokens, output_tokens: 0 };
5778
6310
  const emit = (event, data) => write(sseChunk(event, data));
@@ -5855,9 +6387,13 @@ async function writeAnthropicStream(fullStream, modelId, write, log7, estimatedI
5855
6387
  input: {}
5856
6388
  });
5857
6389
  idToBlock.set(part.id ?? "", blockIndex);
6390
+ if (subagentRouting && part.toolName === "Agent") {
6391
+ bufferedAgentCalls.set(part.id ?? "", { blockIndex });
6392
+ }
5858
6393
  break;
5859
6394
  }
5860
6395
  case "tool-input-delta":
6396
+ if (bufferedAgentCalls.has(part.id ?? "")) break;
5861
6397
  emit("content_block_delta", {
5862
6398
  type: "content_block_delta",
5863
6399
  index: idToBlock.get(part.id ?? "") ?? blockIndex,
@@ -5868,11 +6404,53 @@ async function writeAnthropicStream(fullStream, modelId, write, log7, estimatedI
5868
6404
  break;
5869
6405
  case "tool-call": {
5870
6406
  finishReason = "tool_use";
5871
- if (!idToBlock.has(part.toolCallId ?? "") && openType !== "tool") {
6407
+ const toolCallId = part.toolCallId ?? "";
6408
+ if (subagentRouting && part.toolName === "Agent") {
6409
+ try {
6410
+ const normalized = prepareClaudeAgentInput(part.input, subagentRouting);
6411
+ logSubagentDecision(log7, normalized.decision);
6412
+ const buffered = bufferedAgentCalls.get(toolCallId);
6413
+ if (!buffered && !idToBlock.has(toolCallId)) {
6414
+ const sig = grabRoundTripSignature(part);
6415
+ openBlock("tool", {
6416
+ type: "tool_use",
6417
+ id: encodeToolUseId(toolCallId, sig),
6418
+ name: part.toolName,
6419
+ input: {}
6420
+ });
6421
+ idToBlock.set(toolCallId, blockIndex);
6422
+ }
6423
+ emit("content_block_delta", {
6424
+ type: "content_block_delta",
6425
+ index: buffered?.blockIndex ?? idToBlock.get(toolCallId) ?? blockIndex,
6426
+ delta: {
6427
+ type: "input_json_delta",
6428
+ partial_json: JSON.stringify(stripNullInputs(normalized.input))
6429
+ }
6430
+ });
6431
+ bufferedAgentCalls.delete(toolCallId);
6432
+ } catch (error) {
6433
+ if (error instanceof UnavailableSubagentModelError) {
6434
+ bufferedAgentCalls.delete(toolCallId);
6435
+ closeOpen();
6436
+ emit("error", {
6437
+ type: "error",
6438
+ error: {
6439
+ type: anthropicErrorType(error.statusCode),
6440
+ message: error.message
6441
+ }
6442
+ });
6443
+ return;
6444
+ }
6445
+ throw error;
6446
+ }
6447
+ break;
6448
+ }
6449
+ if (!idToBlock.has(toolCallId) && openType !== "tool") {
5872
6450
  const sig = grabRoundTripSignature(part);
5873
6451
  openBlock("tool", {
5874
6452
  type: "tool_use",
5875
- id: encodeToolUseId(part.toolCallId ?? "", sig),
6453
+ id: encodeToolUseId(toolCallId, sig),
5876
6454
  name: part.toolName,
5877
6455
  input: {}
5878
6456
  });
@@ -5900,6 +6478,7 @@ async function writeAnthropicStream(fullStream, modelId, write, log7, estimatedI
5900
6478
  const errMsg = e?.message || (typeof part.error === "string" ? part.error : JSON.stringify(e?.data ?? part.error));
5901
6479
  const errorType = anthropicErrorType(upstreamHttpStatus(part.error, errMsg));
5902
6480
  log7?.(() => `sdk stream error (${errorType}): ${errMsg}`);
6481
+ bufferedAgentCalls.clear();
5903
6482
  closeOpen();
5904
6483
  emit("error", { type: "error", error: { type: errorType, message: errMsg } });
5905
6484
  return;
@@ -5914,7 +6493,8 @@ async function writeAnthropicStream(fullStream, modelId, write, log7, estimatedI
5914
6493
  emit("message_stop", { type: "message_stop" });
5915
6494
  }
5916
6495
  async function streamAnthropicResponse(model, params, modelId, write, log7, estimatedInputTokens = 0) {
5917
- const result = streamText({ model, ...params, onError: () => {
6496
+ const { subagentRouting, ...providerParams } = params;
6497
+ const result = streamText({ model, ...providerParams, onError: () => {
5918
6498
  } });
5919
6499
  Promise.resolve(result.text).catch(() => {
5920
6500
  });
@@ -5931,22 +6511,24 @@ async function streamAnthropicResponse(model, params, modelId, write, log7, esti
5931
6511
  modelId,
5932
6512
  write,
5933
6513
  log7,
5934
- estimatedInputTokens
6514
+ estimatedInputTokens,
6515
+ subagentRouting
5935
6516
  );
5936
6517
  }
5937
6518
  async function generateAnthropicResponse(model, params, modelId, options) {
6519
+ const { subagentRouting, ...providerParams } = params;
5938
6520
  let text4;
5939
6521
  let toolCalls;
5940
6522
  let finishReason;
5941
6523
  let usage;
5942
6524
  if (options?.forceStream) {
5943
- const r = streamText({ model, ...params, onError: () => {
6525
+ const r = streamText({ model, ...providerParams, onError: () => {
5944
6526
  } });
5945
6527
  Promise.resolve(r.toolResults).catch(() => {
5946
6528
  });
5947
6529
  [text4, toolCalls, finishReason, usage] = await Promise.all([r.text, r.toolCalls, r.finishReason, r.usage]);
5948
6530
  } else {
5949
- const r = await generateText({ model, ...params });
6531
+ const r = await generateText({ model, ...providerParams });
5950
6532
  ({ text: text4, toolCalls, finishReason, usage } = r);
5951
6533
  }
5952
6534
  return {
@@ -5956,12 +6538,20 @@ async function generateAnthropicResponse(model, params, modelId, options) {
5956
6538
  model: modelId,
5957
6539
  content: [
5958
6540
  ...text4 ? [{ type: "text", text: text4 }] : [],
5959
- ...toolCalls.map((tc) => ({
5960
- type: "tool_use",
5961
- id: encodeToolUseId(tc.toolCallId, grabRoundTripSignature(tc)),
5962
- name: tc.toolName,
5963
- input: stripNullInputs(tc.input)
5964
- }))
6541
+ ...toolCalls.map((tc) => {
6542
+ let input = tc.input;
6543
+ if (subagentRouting && tc.toolName === "Agent") {
6544
+ const normalized = prepareClaudeAgentInput(tc.input, subagentRouting);
6545
+ logSubagentDecision(options?.log, normalized.decision);
6546
+ input = normalized.input;
6547
+ }
6548
+ return {
6549
+ type: "tool_use",
6550
+ id: encodeToolUseId(tc.toolCallId, grabRoundTripSignature(tc)),
6551
+ name: tc.toolName,
6552
+ input: stripNullInputs(input)
6553
+ };
6554
+ })
5965
6555
  ],
5966
6556
  stop_reason: finishReason === "tool-calls" ? "tool_use" : "end_turn",
5967
6557
  usage: { input_tokens: usage?.inputTokens ?? 0, output_tokens: usage?.outputTokens ?? 0 }
@@ -6008,6 +6598,21 @@ function aliasModelId(realId, providerId) {
6008
6598
  const sanitized = providerId.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
6009
6599
  return `anthropic-${sanitized}__${realId}`;
6010
6600
  }
6601
+ function buildProxySubagentModelRouting(routes, parentRoute) {
6602
+ const publicId = (route) => route.gatewayAliasId ?? route.aliasId;
6603
+ return {
6604
+ parentModelId: publicId(parentRoute),
6605
+ models: routes.map((route) => ({
6606
+ id: publicId(route),
6607
+ compatibilityIds: [.../* @__PURE__ */ new Set([
6608
+ ...routeLookupIds(route.aliasId),
6609
+ ...route.gatewayAliasId ? routeLookupIds(route.gatewayAliasId) : []
6610
+ ])],
6611
+ displayName: route.displayName,
6612
+ family: route.modelFormat === "anthropic" ? claudeModelFamily(route.realModelId) : void 0
6613
+ }))
6614
+ };
6615
+ }
6011
6616
  function lookupRoute(byAlias, id) {
6012
6617
  for (const key of routeLookupIds(id)) {
6013
6618
  const route = byAlias.get(key);
@@ -6016,13 +6621,14 @@ function lookupRoute(byAlias, id) {
6016
6621
  return void 0;
6017
6622
  }
6018
6623
  function startProxyCatalog(routes, defaultAliasId, debug = false) {
6019
- const proxyToken = randomUUID5();
6624
+ const proxyToken = randomUUID6();
6020
6625
  silenceSdkWarnings();
6021
6626
  if (routes.length === 0) {
6022
6627
  return Promise.reject(new Error("Proxy catalog requires at least one route"));
6023
6628
  }
6024
6629
  const byAlias = new Map(routes.map((r) => [r.aliasId, r]));
6025
6630
  const defaultRoute = byAlias.get(defaultAliasId) ?? routes[0];
6631
+ const subagentRouteRegistry = new SubagentRouteRegistry();
6026
6632
  const plog = makeProxyLog(debug);
6027
6633
  const onRejection = (reason) => {
6028
6634
  plog(() => `Unhandled Rejection: ${reason instanceof Error ? reason.stack || reason.message : String(reason)}`);
@@ -6076,9 +6682,12 @@ function startProxyCatalog(routes, defaultAliasId, debug = false) {
6076
6682
  anthropicError(res, 400, "Invalid JSON body");
6077
6683
  return;
6078
6684
  }
6685
+ const correlatedSubagent = subagentRouteRegistry.consume(req.headers, anthropicBody);
6686
+ if (correlatedSubagent) anthropicBody = correlatedSubagent.body;
6079
6687
  const originalModel = anthropicBody.model;
6080
6688
  const clientWantsStream = Boolean(anthropicBody.stream);
6081
- const route = lookupRoute(byAlias, originalModel) ?? defaultRoute;
6689
+ const correlatedRoute = correlatedSubagent ? routes.find((candidate) => (candidate.gatewayAliasId ?? candidate.aliasId) === correlatedSubagent.modelId) : void 0;
6690
+ const route = correlatedRoute ?? lookupRoute(byAlias, originalModel) ?? defaultRoute;
6082
6691
  const apiKey = route.apiKey;
6083
6692
  const upstreamUrl = route.upstreamUrl;
6084
6693
  plog(
@@ -6134,10 +6743,16 @@ function startProxyCatalog(routes, defaultAliasId, debug = false) {
6134
6743
  }
6135
6744
  if (usesSdkAdapter) {
6136
6745
  const openAiOAuth = route.npm === "@ai-sdk/openai" && route.authType === "oauth";
6746
+ const subagentRouting = buildProxySubagentModelRouting(routes, route);
6747
+ const sessionId = extractClaudeSessionId(req.headers, anthropicBody);
6748
+ if (sessionId) {
6749
+ subagentRouting.registerSubagentRoute = (modelId) => subagentRouteRegistry.register(sessionId, modelId);
6750
+ }
6137
6751
  const params = translateRequest2(anthropicBody, route.npm, {
6138
6752
  openAiOAuth,
6139
6753
  maxTools: maxToolsForNpm(route.npm),
6140
6754
  onDebug: (msg) => plog(() => msg),
6755
+ subagentRouting,
6141
6756
  reasoningMetadata: {
6142
6757
  providerId: route.providerId,
6143
6758
  apiBaseUrl: route.baseURL,
@@ -6161,6 +6776,10 @@ function startProxyCatalog(routes, defaultAliasId, debug = false) {
6161
6776
  oauthAccountId: route.oauthAccountId,
6162
6777
  providerData: route.providerData,
6163
6778
  headers: route.headers,
6779
+ refreshToken: route.refreshToken,
6780
+ onTokenRefreshed: (refreshed) => {
6781
+ route.apiKey = refreshed;
6782
+ },
6164
6783
  useResponsesLite: route.useResponsesLite,
6165
6784
  preferWebSockets: route.preferWebSockets,
6166
6785
  onDebug: (msg) => plog(() => msg)
@@ -6185,7 +6804,7 @@ function startProxyCatalog(routes, defaultAliasId, debug = false) {
6185
6804
  model,
6186
6805
  params,
6187
6806
  originalModel,
6188
- { forceStream: openAiOAuth }
6807
+ { forceStream: openAiOAuth, log: plog }
6189
6808
  );
6190
6809
  sendJson(res, 200, anthropicResponse);
6191
6810
  }
@@ -6281,9 +6900,9 @@ data: ${JSON.stringify({ type: "error", error: { type: errorType, message } })}
6281
6900
  });
6282
6901
  });
6283
6902
  }
6284
- function startProxy(completionsUrl, modelId, debug = false, contextWindow, sdk, apiKey) {
6903
+ function startProxy(completionsUrl, modelId, debug = false, contextWindow2, sdk, apiKey) {
6285
6904
  const bareModelId = stripOneMContextSuffix(modelId);
6286
- const clientModelId = claudeCodeClientModelId(modelId, contextWindow);
6905
+ const clientModelId = claudeCodeClientModelId(modelId, contextWindow2);
6287
6906
  return startProxyCatalog([{
6288
6907
  aliasId: clientModelId,
6289
6908
  realModelId: sdk?.upstreamModelId ?? bareModelId,
@@ -6291,13 +6910,15 @@ function startProxy(completionsUrl, modelId, debug = false, contextWindow, sdk,
6291
6910
  upstreamUrl: completionsUrl,
6292
6911
  apiKey: apiKey ?? "",
6293
6912
  modelFormat: sdk?.modelFormat ?? "openai",
6294
- contextWindow,
6913
+ contextWindow: contextWindow2,
6295
6914
  npm: sdk?.npm,
6296
6915
  baseURL: sdk?.baseURL,
6297
6916
  providerId: sdk?.providerId,
6298
6917
  authType: sdk?.authType,
6299
6918
  oauthAccountId: sdk?.oauthAccountId,
6300
6919
  providerData: sdk?.providerData,
6920
+ refreshToken: sdk?.refreshToken,
6921
+ headers: sdk?.headers,
6301
6922
  supportedParameters: sdk?.supportedParameters,
6302
6923
  reasoning: sdk?.reasoning,
6303
6924
  interleavedReasoningField: sdk?.interleavedReasoningField,
@@ -6306,54 +6927,6 @@ function startProxy(completionsUrl, modelId, debug = false, contextWindow, sdk,
6306
6927
  }], clientModelId, debug);
6307
6928
  }
6308
6929
 
6309
- // src/catalog.ts
6310
- function localModelToRoute(lp, model) {
6311
- if (model.modelFormat === "anthropic" && !model.baseUrl) return null;
6312
- if (model.modelFormat === "openai" && !isSdkMigratedNpm(model.npm) && !model.completionsUrl) return null;
6313
- const upstreamUrl = model.modelFormat === "cloud-code" ? model.baseUrl ?? ANTIGRAVITY_BASE_URLS[0] : model.modelFormat === "anthropic" ? model.baseUrl : model.completionsUrl;
6314
- return {
6315
- aliasId: claudeCodeClientModelId(aliasModelId(model.id, lp.id), model.contextWindow),
6316
- realModelId: model.upstreamModelId,
6317
- displayName: `${model.name || model.id} (${lp.name})`,
6318
- upstreamUrl: upstreamUrl ?? "",
6319
- apiKey: lp.apiKey,
6320
- modelFormat: model.modelFormat,
6321
- contextWindow: model.contextWindow,
6322
- npm: model.npm,
6323
- baseURL: model.apiBaseUrl,
6324
- providerId: lp.id,
6325
- authType: lp.authType,
6326
- oauthAccountId: lp.oauthAccountId,
6327
- providerData: lp.providerData,
6328
- headers: lp.headers,
6329
- supportedParameters: model.supportedParameters,
6330
- reasoning: model.reasoning,
6331
- interleavedReasoningField: model.interleavedReasoningField,
6332
- useResponsesLite: model.useResponsesLite,
6333
- preferWebSockets: model.preferWebSockets
6334
- };
6335
- }
6336
- function makeRouteResolver(localProviders) {
6337
- return (providerId, modelId) => {
6338
- const provider = localProviders?.find((lp) => lp.id === providerId);
6339
- const model = provider?.models.find((m) => m.id === modelId);
6340
- return provider && model ? localModelToRoute(provider, model) ?? void 0 : void 0;
6341
- };
6342
- }
6343
- function buildCatalogRoutes(startingRoute, favorites, resolveRoute, max = MAX_MODEL_CATALOG) {
6344
- const droppedFavorites = [];
6345
- const tail = favorites.map((fav) => {
6346
- const route = resolveRoute(fav.providerId, fav.modelId);
6347
- if (!route) droppedFavorites.push(fav);
6348
- return route;
6349
- }).filter((route) => route !== void 0);
6350
- const routes = [
6351
- startingRoute,
6352
- ...tail.filter((route) => route.aliasId !== startingRoute.aliasId)
6353
- ].slice(0, max);
6354
- return { routes, droppedFavorites };
6355
- }
6356
-
6357
6930
  // src/data/model-incompatible.json
6358
6931
  var model_incompatible_default = {
6359
6932
  schema_version: "1",
@@ -7161,6 +7734,61 @@ function listCredentialSkippedProviders(raw, authEntries, importedIds, alreadyRe
7161
7734
  return skipped;
7162
7735
  }
7163
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
+
7164
7792
  // src/registry/materialize.ts
7165
7793
  init_provider_templates();
7166
7794
  function cachedModelToLocal(cached, provider) {
@@ -7207,7 +7835,12 @@ function cachedModelToLocal(cached, provider) {
7207
7835
  cost: cached.cost,
7208
7836
  isFree: isFreeStatus(freeStatus),
7209
7837
  freeStatus,
7210
- 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)),
7211
7844
  supportedParameters: cached.supportedParameters,
7212
7845
  reasoning: cached.reasoning ?? modelsDev?.reasoning,
7213
7846
  interleavedReasoningField: cached.interleavedReasoningField ?? modelsDev?.interleaved?.field,
@@ -7244,6 +7877,7 @@ function materializeOne(provider, resolveCredential, agent) {
7244
7877
  id: provider.id,
7245
7878
  name: provider.name,
7246
7879
  apiKey,
7880
+ authRef: provider.authRef,
7247
7881
  authType: provider.authType,
7248
7882
  headers: provider.api.headers,
7249
7883
  models
@@ -7287,14 +7921,14 @@ function normalizeCopilotModels(rows, tier) {
7287
7921
  const lowerId = id.toLowerCase();
7288
7922
  const isFree = tier !== "paid" || copilotModelIsIncluded(row);
7289
7923
  const family = lowerId.split(/[-/:]/)[0] ?? lowerId;
7290
- 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);
7291
7925
  models.push({
7292
7926
  id,
7293
7927
  name: `${id} [Copilot]`,
7294
7928
  upstreamModelId: id,
7295
7929
  family,
7296
7930
  brand: deriveBrand(family),
7297
- contextWindow,
7931
+ contextWindow: contextWindow2,
7298
7932
  isFree,
7299
7933
  freeStatus: isFree ? "verified_free" : "unknown",
7300
7934
  modelFormat: "openai",
@@ -7452,9 +8086,9 @@ function formatRegistryAuthLabel(provider) {
7452
8086
  }
7453
8087
  async function resolveProvidersForDisplay() {
7454
8088
  const reg = loadRegistry();
7455
- const entries = [];
8089
+ const entries2 = [];
7456
8090
  for (const provider of reg.providers) {
7457
- entries.push({
8091
+ entries2.push({
7458
8092
  id: provider.id,
7459
8093
  name: provider.name,
7460
8094
  modelCount: provider.modelsCache?.models.length ?? 0,
@@ -7463,7 +8097,7 @@ async function resolveProvidersForDisplay() {
7463
8097
  inRegistry: true
7464
8098
  });
7465
8099
  }
7466
- return entries.sort((a, b) => a.name.localeCompare(b.name));
8100
+ return entries2.sort((a, b) => a.name.localeCompare(b.name));
7467
8101
  }
7468
8102
  function localProvidersToServerModels(localProviders) {
7469
8103
  return localProviders.flatMap(
@@ -7926,6 +8560,14 @@ function injectRelayModels(fixture, routes, templateKey) {
7926
8560
  }]
7927
8561
  }
7928
8562
  ];
8563
+ for (const entry of Object.values(result.models)) {
8564
+ const mimeTypes = entry.supportedMimeTypes;
8565
+ if (!mimeTypes || typeof mimeTypes !== "object" || Array.isArray(mimeTypes)) continue;
8566
+ entry.supportedMimeTypes = Object.fromEntries(
8567
+ Object.entries(mimeTypes).filter(([mime]) => !mime.toLowerCase().includes("audio/"))
8568
+ );
8569
+ }
8570
+ result.audioTranscriptionModelIds = [];
7929
8571
  return result;
7930
8572
  }
7931
8573
  if (!result.agentModelSorts?.[0]?.groups?.[0]) {
@@ -7952,7 +8594,7 @@ function buildAntigravityRoutes(resolvedFavorites, maxRoutes = MAX_MODEL_CATALOG
7952
8594
  const npm = favModel.npm || "@ai-sdk/openai-compatible";
7953
8595
  const upstreamModelId2 = favModel.upstreamModelId || modelId;
7954
8596
  const baseURL = favModel.apiBaseUrl || favModel.completionsUrl || void 0;
7955
- const contextWindow = favModel.contextWindow;
8597
+ const contextWindow2 = favModel.contextWindow;
7956
8598
  const modelFormat = favModel.modelFormat;
7957
8599
  routes.push({
7958
8600
  catalogId,
@@ -7967,8 +8609,10 @@ function buildAntigravityRoutes(resolvedFavorites, maxRoutes = MAX_MODEL_CATALOG
7967
8609
  ...fav.authType ? { authType: fav.authType } : {},
7968
8610
  ...fav.oauthAccountId ? { oauthAccountId: fav.oauthAccountId } : {},
7969
8611
  ...fav.providerData ? { providerData: fav.providerData } : {},
8612
+ ...fav.headers ? { headers: fav.headers } : {},
8613
+ ...fav.refreshToken ? { refreshToken: fav.refreshToken } : {},
7970
8614
  baseURL,
7971
- contextWindow
8615
+ contextWindow: contextWindow2
7972
8616
  });
7973
8617
  }
7974
8618
  return applyUniqueAntigravityRouteLabels(routes);
@@ -8184,8 +8828,8 @@ function contextFloorForTarget(target) {
8184
8828
  if (target === "server") return 0;
8185
8829
  return MIN_CONTEXT_WINDOW;
8186
8830
  }
8187
- function meetsContextFloor(target, contextWindow) {
8188
- return contextWindow === void 0 || contextWindow >= contextFloorForTarget(target);
8831
+ function meetsContextFloor(target, contextWindow2) {
8832
+ return contextWindow2 === void 0 || contextWindow2 >= contextFloorForTarget(target);
8189
8833
  }
8190
8834
  function isTargetCompatibleModel(ctx) {
8191
8835
  const blacklistAgent = blacklistAgentForTarget(ctx.target);
@@ -8361,14 +9005,14 @@ function parseModelList(body, npm) {
8361
9005
  // daily Neuron allowance, so free access is a provider rule, not a price.
8362
9006
  freeAccess: isFreeFromProps === true
8363
9007
  });
8364
- 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);
8365
9009
  models.push({
8366
9010
  id,
8367
9011
  name: normalizeGoogleDisplayName(row.name, id),
8368
9012
  upstreamModelId: upstreamModelId2,
8369
9013
  family,
8370
9014
  brand: deriveBrand(family),
8371
- contextWindow,
9015
+ contextWindow: contextWindow2,
8372
9016
  cost,
8373
9017
  isFree: isFreeStatus(freeStatus),
8374
9018
  freeStatus,
@@ -8770,6 +9414,125 @@ async function addCustomEndpointProvider(input) {
8770
9414
  return { added: true, provider: entry, modelCount: fetched.models.length };
8771
9415
  }
8772
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
+
8773
9536
  // src/registry/model-source.ts
8774
9537
  init_provider_templates();
8775
9538
 
@@ -8939,8 +9702,8 @@ async function refreshZenGoProvider(provider) {
8939
9702
  });
8940
9703
  }
8941
9704
  async function refreshClaudeCodeOAuthModels(accessToken) {
8942
- const entries = await fetchClaudeCodeModels(accessToken);
8943
- const models = entries.map((entry) => ({
9705
+ const entries2 = await fetchClaudeCodeModels(accessToken);
9706
+ const models = entries2.map((entry) => ({
8944
9707
  id: entry.id,
8945
9708
  name: entry.displayName,
8946
9709
  upstreamModelId: entry.id,
@@ -9121,7 +9884,7 @@ async function fetchJsonWithAuth(url, accessToken, timeoutMs, extraHeaders = {})
9121
9884
  async function refreshOpenAiOAuthModels(accessToken) {
9122
9885
  const TIMEOUT_MS = 1e4;
9123
9886
  const seedById = new Map(buildOpenAiOAuthModels().map((m) => [m.id, m]));
9124
- const toModels = (entries) => entries.map((entry) => buildDynamicOAuthModel(entry, seedById));
9887
+ const toModels = (entries2) => entries2.map((entry) => buildDynamicOAuthModel(entry, seedById));
9125
9888
  const claudeVersion = getInstalledClaudeVersion();
9126
9889
  const codexResult = await fetchJsonWithAuth(
9127
9890
  `https://chatgpt.com/backend-api/codex/models?client_version=${claudeVersion}`,
@@ -9152,9 +9915,9 @@ async function refreshXaiOAuthModels(accessToken) {
9152
9915
  const seedById = new Map(seed.map((m) => [m.id, m]));
9153
9916
  const result = await fetchJsonWithAuth("https://api.x.ai/v1/models", accessToken, 8e3);
9154
9917
  if (result.body) {
9155
- const entries = (result.body.data ?? []).filter((m) => !!m.id);
9156
- if (entries.length > 0) {
9157
- 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 }) => {
9158
9921
  const cached = seedById.get(id);
9159
9922
  if (cached) return cached;
9160
9923
  const prefix = id.split("-")[0] ?? id;
@@ -9261,6 +10024,19 @@ async function refreshProviderModels(providerId, apiKey, registry = loadRegistry
9261
10024
  let oauthFallbackReason;
9262
10025
  if (source === "zen-go-api") {
9263
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
+ }
9264
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")) {
9265
10041
  if (!apiKey) {
9266
10042
  return {
@@ -9921,8 +10697,9 @@ async function startServer(options) {
9921
10697
  silenceSdkWarnings();
9922
10698
  const languageModelCache = /* @__PURE__ */ new Map();
9923
10699
  const plog = makeServerLog(options.debugLogPath);
10700
+ const subagentRouteRegistry = new SubagentRouteRegistry();
9924
10701
  const server = createServer2((req, res) => {
9925
- void routeRequest(req, res, options, languageModelCache, plog);
10702
+ void routeRequest(req, res, options, languageModelCache, plog, subagentRouteRegistry);
9926
10703
  });
9927
10704
  await new Promise((resolve, reject) => {
9928
10705
  server.once("error", reject);
@@ -9945,7 +10722,7 @@ async function startServer(options) {
9945
10722
  })
9946
10723
  };
9947
10724
  }
9948
- async function routeRequest(req, res, options, modelCache, plog) {
10725
+ async function routeRequest(req, res, options, modelCache, plog, subagentRouteRegistry) {
9949
10726
  try {
9950
10727
  const pathname = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`).pathname;
9951
10728
  plog(`${req.method} ${pathname}`);
@@ -9970,7 +10747,7 @@ async function routeRequest(req, res, options, modelCache, plog) {
9970
10747
  return;
9971
10748
  }
9972
10749
  if (req.method === "POST" && pathname === "/anthropic/v1/messages") {
9973
- await handleAnthropicMessages(req, res, options, modelCache, plog);
10750
+ await handleAnthropicMessages(req, res, options, modelCache, plog, subagentRouteRegistry);
9974
10751
  return;
9975
10752
  }
9976
10753
  if (req.method === "POST" && pathname === "/openai/v1/chat/completions") {
@@ -9982,13 +10759,16 @@ async function routeRequest(req, res, options, modelCache, plog) {
9982
10759
  sendJson(res, 500, { error: { message: err instanceof Error ? err.message : String(err) } });
9983
10760
  }
9984
10761
  }
9985
- async function handleAnthropicMessages(req, res, options, modelCache, plog) {
9986
- const body = await readJson(req);
10762
+ async function handleAnthropicMessages(req, res, options, modelCache, plog, subagentRouteRegistry) {
10763
+ let body = await readJson(req);
9987
10764
  if (!body) {
9988
10765
  sendJson(res, 400, { error: { message: "Invalid JSON body" } });
9989
10766
  return;
9990
10767
  }
9991
- const model = lookupModel(res, options.catalog, body.model);
10768
+ const correlatedSubagent = subagentRouteRegistry.consume(req.headers, body);
10769
+ if (correlatedSubagent) body = correlatedSubagent.body;
10770
+ const requestedModelId = correlatedSubagent?.modelId ?? body.model;
10771
+ const model = lookupModel(res, options.catalog, requestedModelId);
9992
10772
  if (!model) {
9993
10773
  plog(`model not found: ${body.model}`);
9994
10774
  return;
@@ -10041,16 +10821,34 @@ async function handleAnthropicMessages(req, res, options, modelCache, plog) {
10041
10821
  return;
10042
10822
  }
10043
10823
  const apiKey = model.apiKey ?? options.apiKey;
10044
- 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
+ );
10045
10833
  const npmMaxTools = maxToolsForNpm(model.npm);
10046
10834
  const toolCount = Array.isArray(body.tools) ? body.tools.length : 0;
10047
10835
  if (npmMaxTools !== void 0 && toolCount > npmMaxTools) {
10048
10836
  plog(`tools truncated: ${toolCount} \u2192 ${npmMaxTools} (provider limit)`);
10049
10837
  }
10838
+ const subagentRouting = buildServerSubagentModelRouting(
10839
+ options.catalog.list(),
10840
+ model,
10841
+ options.gateway
10842
+ );
10843
+ const sessionId = extractClaudeSessionId(req.headers, body);
10844
+ if (sessionId) {
10845
+ subagentRouting.registerSubagentRoute = (modelId) => subagentRouteRegistry.register(sessionId, modelId);
10846
+ }
10050
10847
  const params = translateRequest2(body, model.npm, {
10051
10848
  defaultEffort: anthropicEffortFromRequest(body) ? void 0 : model.defaultEffort,
10052
10849
  openAiOAuth: model.npm === "@ai-sdk/openai" && model.authType === "oauth",
10053
10850
  onDebug: plog,
10851
+ subagentRouting,
10054
10852
  reasoningMetadata: {
10055
10853
  providerId: model.providerId,
10056
10854
  apiBaseUrl: model.apiBaseUrl,
@@ -10076,12 +10874,17 @@ async function handleAnthropicMessages(req, res, options, modelCache, plog) {
10076
10874
  params,
10077
10875
  responseModelId,
10078
10876
  (chunk) => res.write(chunk),
10079
- void 0,
10877
+ plog,
10080
10878
  estimateAnthropicInputTokens(body)
10081
10879
  );
10082
10880
  res.end();
10083
10881
  } else {
10084
- const anthropicResponse = await generateAnthropicResponse(languageModel, params, responseModelId);
10882
+ const anthropicResponse = await generateAnthropicResponse(
10883
+ languageModel,
10884
+ params,
10885
+ responseModelId,
10886
+ { log: plog }
10887
+ );
10085
10888
  sendJson(res, 200, anthropicResponse);
10086
10889
  }
10087
10890
  } catch (err) {
@@ -10142,7 +10945,15 @@ async function handleOpenAIChatCompletions(req, res, options, modelCache, plog)
10142
10945
  }
10143
10946
  const apiKey = model.apiKey ?? options.apiKey;
10144
10947
  const baseURL = model.modelFormat === "anthropic" ? model.baseUrl : model.apiBaseUrl;
10145
- 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
+ );
10146
10957
  const params = translateOpenAiRequest(body);
10147
10958
  const clientWantsStream = Boolean(body.stream);
10148
10959
  const responseModelId = getResponseModelId(body.model, model, options);
@@ -10189,7 +11000,7 @@ function backendFor(options, model) {
10189
11000
  if (model.sourceBackend === "go") return options.backends.go;
10190
11001
  throw new Error(`Provider ${model.sourceBackend} is not a cloud backend \u2014 model must set baseUrl/completionsUrl`);
10191
11002
  }
10192
- async function getOrInitLanguageModel(modelCache, model, npm, baseURL, apiKey, vertex) {
11003
+ async function getOrInitLanguageModel(modelCache, model, npm, baseURL, apiKey, vertex, refreshToken) {
10193
11004
  const cacheKey = [
10194
11005
  model.providerId ?? model.sourceBackend,
10195
11006
  model.id,
@@ -10209,6 +11020,10 @@ async function getOrInitLanguageModel(modelCache, model, npm, baseURL, apiKey, v
10209
11020
  oauthAccountId: model.oauthAccountId,
10210
11021
  vertex,
10211
11022
  headers: model.headers,
11023
+ refreshToken,
11024
+ onTokenRefreshed: (refreshed) => {
11025
+ model.apiKey = refreshed;
11026
+ },
10212
11027
  useResponsesLite: model.useResponsesLite,
10213
11028
  preferWebSockets: model.preferWebSockets
10214
11029
  });
@@ -11152,7 +11967,24 @@ async function addProviderFromTemplate(template, apiKey, opts) {
11152
11967
  hint: `Remove it first with: relay-ai providers remove ${template.id}`
11153
11968
  };
11154
11969
  }
11155
- 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
+ }
11156
11988
  if (fetched.error || fetched.models.length === 0) {
11157
11989
  return {
11158
11990
  added: false,
@@ -11194,7 +12026,8 @@ async function addProviderFromTemplate(template, apiKey, opts) {
11194
12026
  authType: template.authType,
11195
12027
  api: {
11196
12028
  npm: template.npm,
11197
- url: fetched.baseUrl
12029
+ url: fetched.baseUrl,
12030
+ ...template.headers ? { headers: template.headers } : {}
11198
12031
  },
11199
12032
  addedAt: existing?.addedAt ?? now,
11200
12033
  refreshedAt: now,
@@ -11210,6 +12043,9 @@ async function addProviderFromTemplate(template, apiKey, opts) {
11210
12043
  registry.providers.push(entry);
11211
12044
  }
11212
12045
  saveRegistry(registry);
12046
+ if (existing?.authRef && existing.authRef !== authRef) {
12047
+ await deleteProviderCredential(existing.authRef);
12048
+ }
11213
12049
  enrichPricingAsync();
11214
12050
  return { added: true, provider: entry, modelCount: pricedModels.length };
11215
12051
  }
@@ -11227,7 +12063,8 @@ var PROVIDER_DISPLAY = {
11227
12063
  "openai-oauth": OPENAI_DISPLAY,
11228
12064
  "github-copilot": "GitHub Copilot",
11229
12065
  "claude-code": "Claude Code (Anthropic subscription)",
11230
- antigravity: "Antigravity (Google Cloud Code Assist)"
12066
+ antigravity: "Antigravity (Google Cloud Code Assist)",
12067
+ "cline-pass": "ClinePass"
11231
12068
  };
11232
12069
  function openBrowser(url) {
11233
12070
  open3(url).catch(() => {
@@ -11261,6 +12098,17 @@ async function runNativeDeviceCode(providerId) {
11261
12098
  spinner3.stop(pc6.green("Signed in to GitHub Copilot"));
11262
12099
  return tokensToStoredCredential(tokens2);
11263
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
+ }
11264
12112
  const { tokens, accountId } = await runOpenAiDeviceCodeFlow(({ url, userCode }) => {
11265
12113
  spinner3.stop("");
11266
12114
  p5.log.info(`Visit: ${pc6.cyan(url)}`);
@@ -11354,6 +12202,7 @@ async function upsertOAuthProvider(providerId, cred) {
11354
12202
  const authRef = oauthAuthRef(registryId);
11355
12203
  const template = getTemplateById(templateId) ?? getTemplateById(registryId);
11356
12204
  let entry = registry.providers.find((pr) => pr.id === registryId);
12205
+ const previousAuthRef = entry?.authRef;
11357
12206
  if (!entry) {
11358
12207
  if (!template) {
11359
12208
  throw new Error(`Provider "${providerId}" is not in your registry and has no template`);
@@ -11380,6 +12229,9 @@ async function upsertOAuthProvider(providerId, cred) {
11380
12229
  if (idx >= 0) registry.providers[idx] = entry;
11381
12230
  else registry.providers.push(entry);
11382
12231
  saveRegistry(registry);
12232
+ if (previousAuthRef && previousAuthRef !== authRef) {
12233
+ await deleteProviderCredential(previousAuthRef);
12234
+ }
11383
12235
  return entry;
11384
12236
  }
11385
12237
  async function authenticateProvider(providerId, options = {}) {
@@ -11425,11 +12277,13 @@ ${pc6.bold("Usage:")}
11425
12277
  relay-ai providers auth xai-oauth
11426
12278
  relay-ai providers auth openai-oauth
11427
12279
  relay-ai providers auth github-copilot
12280
+ relay-ai providers auth cline-pass
11428
12281
 
11429
12282
  ${pc6.bold("Device code (works on SSH/VPS):")}
11430
12283
  xai-oauth SuperGrok / X Premium (device code at x.ai/device)
11431
12284
  openai-oauth ChatGPT Plus/Pro (device code at auth.openai.com/codex/device)
11432
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)
11433
12287
 
11434
12288
  ${pc6.dim("OpenCode CLI configs: use")} relay-ai providers import${pc6.dim(" (optional one-time migration).")}`;
11435
12289
  }
@@ -12025,6 +12879,8 @@ export {
12025
12879
  ANTIGRAVITY_BASE_URLS,
12026
12880
  buildAntigravityAuthUrl,
12027
12881
  completeAntigravityExchange,
12882
+ requestClinePassDeviceCode,
12883
+ pollClinePassDeviceCode,
12028
12884
  detectConflicts,
12029
12885
  resolveApiKey,
12030
12886
  buildChildEnv,
@@ -12062,6 +12918,7 @@ export {
12062
12918
  getUiDebugLogPath,
12063
12919
  getServerDebugLogPath,
12064
12920
  getAntigravityDebugLogPath,
12921
+ prepareProviderTraceLog,
12065
12922
  makeTraceLogger,
12066
12923
  writeSecureLogLine,
12067
12924
  printTraceLog,
@@ -12093,6 +12950,8 @@ export {
12093
12950
  splitToolUseId,
12094
12951
  encodeToolUseId,
12095
12952
  serializeToolResultContent,
12953
+ UNSUPPORTED_VOICE_MESSAGE,
12954
+ sanitizeUnsupportedInlineData,
12096
12955
  summarizeSdkRequestForTrace,
12097
12956
  translateRequest,
12098
12957
  formatUpstreamErrorTrace,
@@ -12101,6 +12960,7 @@ export {
12101
12960
  aliasModelId,
12102
12961
  startProxyCatalog,
12103
12962
  startProxy,
12963
+ providerRefreshToken,
12104
12964
  makeRouteResolver,
12105
12965
  buildCatalogRoutes,
12106
12966
  hostFromHeader,
@@ -12162,4 +13022,4 @@ export {
12162
13022
  supportsClaudeTransparentMode,
12163
13023
  buildHttpProxyRoutes
12164
13024
  };
12165
- //# sourceMappingURL=chunk-HEMJFOXG.js.map
13025
+ //# sourceMappingURL=chunk-5DDQJSTU.js.map