@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.
- package/README.md +8 -4
- package/dist/{chunk-HEMJFOXG.js → chunk-5DDQJSTU.js} +1029 -169
- package/dist/chunk-5DDQJSTU.js.map +1 -0
- package/dist/{chunk-HXGZ4CTV.js → chunk-Q2FTCICO.js} +19 -3
- package/dist/chunk-Q2FTCICO.js.map +1 -0
- package/dist/cli.js +206 -50
- package/dist/cli.js.map +1 -1
- package/dist/core/index.js +628 -7
- package/dist/core/index.js.map +1 -1
- package/dist/{provider-templates-4H3C4DRL.js → provider-templates-XKNRKAQU.js} +2 -2
- package/dist/ui/public/app.js +106 -0
- package/dist/{ui-command-26QQUWTQ.js → ui-command-RYWL36VR.js} +44 -7
- package/dist/ui-command-RYWL36VR.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-HEMJFOXG.js.map +0 -1
- package/dist/chunk-HXGZ4CTV.js.map +0 -1
- package/dist/ui-command-26QQUWTQ.js.map +0 -1
- /package/dist/{provider-templates-4H3C4DRL.js.map → provider-templates-XKNRKAQU.js.map} +0 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
getTemplateById,
|
|
4
4
|
init_provider_templates
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-Q2FTCICO.js";
|
|
6
6
|
|
|
7
7
|
// src/constants.ts
|
|
8
8
|
import { homedir } from "os";
|
|
@@ -11,7 +11,7 @@ import { join } from "path";
|
|
|
11
11
|
// package.json
|
|
12
12
|
var package_default = {
|
|
13
13
|
name: "@jacobbd/relay-ai",
|
|
14
|
-
version: "0.
|
|
14
|
+
version: "0.8.0",
|
|
15
15
|
publishConfig: {
|
|
16
16
|
access: "public"
|
|
17
17
|
},
|
|
@@ -610,6 +610,44 @@ function injectClaudeIdentity(body, providerData, seed) {
|
|
|
610
610
|
return { sessionId, userId };
|
|
611
611
|
}
|
|
612
612
|
|
|
613
|
+
// src/cline-pass.ts
|
|
614
|
+
var CLINE_PASS_HOST = "https://api.cline.bot";
|
|
615
|
+
var CLINE_PASS_SDK_BASE_URL = `${CLINE_PASS_HOST}/api/v1`;
|
|
616
|
+
var CLINE_PASS_CATALOG_URL = `${CLINE_PASS_HOST}/api/v1/ai/cline/recommended-models`;
|
|
617
|
+
var CLINE_PASS_VALIDATION_URL = `${CLINE_PASS_HOST}/api/v1/users/me`;
|
|
618
|
+
var CLINE_PASS_REGISTER_URL = `${CLINE_PASS_HOST}/api/v1/auth/register`;
|
|
619
|
+
var CLINE_PASS_REFRESH_URL = `${CLINE_PASS_HOST}/api/v1/auth/refresh`;
|
|
620
|
+
var CLINE_PASS_LEGACY_DEFAULT_CONTEXT_WINDOW = 131072;
|
|
621
|
+
var CLINE_PASS_WORKOS_PREFIX = "workos:";
|
|
622
|
+
function isClinePassOAuth(providerId, authType) {
|
|
623
|
+
return providerId === "cline-pass" && authType === "oauth";
|
|
624
|
+
}
|
|
625
|
+
function formatClineRuntimeCredential(providerId, authType, key) {
|
|
626
|
+
if (!isClinePassOAuth(providerId, authType)) return key;
|
|
627
|
+
return key.toLowerCase().startsWith(CLINE_PASS_WORKOS_PREFIX) ? key : `${CLINE_PASS_WORKOS_PREFIX}${key}`;
|
|
628
|
+
}
|
|
629
|
+
function createClinePassOAuthFetch(initialRuntimeCredential, refreshToken, onTokenRefreshed, fetchImpl = globalThis.fetch) {
|
|
630
|
+
let currentRuntimeCredential = initialRuntimeCredential;
|
|
631
|
+
return async (input, init) => {
|
|
632
|
+
const request = new Request(input, init);
|
|
633
|
+
const send = (runtimeCredential) => {
|
|
634
|
+
const headers = new Headers(request.headers);
|
|
635
|
+
headers.set("Authorization", `Bearer ${runtimeCredential}`);
|
|
636
|
+
return fetchImpl(request.clone(), { headers });
|
|
637
|
+
};
|
|
638
|
+
const response = await send(currentRuntimeCredential);
|
|
639
|
+
if (response.status !== 401) return response;
|
|
640
|
+
const refreshedRawToken = await refreshToken().catch(() => null);
|
|
641
|
+
const refreshedRuntimeCredential = refreshedRawToken ? formatClineRuntimeCredential("cline-pass", "oauth", refreshedRawToken) : null;
|
|
642
|
+
if (!refreshedRawToken || !refreshedRuntimeCredential || refreshedRuntimeCredential === currentRuntimeCredential) {
|
|
643
|
+
return response;
|
|
644
|
+
}
|
|
645
|
+
currentRuntimeCredential = refreshedRuntimeCredential;
|
|
646
|
+
onTokenRefreshed?.(refreshedRawToken);
|
|
647
|
+
return send(currentRuntimeCredential);
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
|
|
613
651
|
// src/provider-factory.ts
|
|
614
652
|
var RESPONSES_ONLY_PREFIXES = [
|
|
615
653
|
"gpt-5-codex",
|
|
@@ -744,11 +782,19 @@ async function createLanguageModel(spec) {
|
|
|
744
782
|
let model;
|
|
745
783
|
if (npm === "@ai-sdk/openai-compatible") {
|
|
746
784
|
const { createOpenAICompatible } = await import("@ai-sdk/openai-compatible");
|
|
785
|
+
const runtimeApiKey = formatClineRuntimeCredential(spec.providerId, spec.authType, apiKey);
|
|
747
786
|
const options = {
|
|
748
787
|
name: spec.providerId ?? "openai-compatible",
|
|
749
788
|
baseURL: baseURL ?? "",
|
|
750
|
-
...
|
|
751
|
-
...spec.headers ? { headers: spec.headers } : {}
|
|
789
|
+
...runtimeApiKey.trim() ? { apiKey: runtimeApiKey } : {},
|
|
790
|
+
...spec.headers ? { headers: spec.headers } : {},
|
|
791
|
+
...isClinePassOAuth(spec.providerId, spec.authType) && spec.refreshToken ? {
|
|
792
|
+
fetch: createClinePassOAuthFetch(
|
|
793
|
+
runtimeApiKey,
|
|
794
|
+
spec.refreshToken,
|
|
795
|
+
spec.onTokenRefreshed
|
|
796
|
+
)
|
|
797
|
+
} : {}
|
|
752
798
|
};
|
|
753
799
|
model = createOpenAICompatible({
|
|
754
800
|
...options
|
|
@@ -1502,9 +1548,9 @@ function modelSelectOption(model, hint) {
|
|
|
1502
1548
|
hint: defaultHint && ctxSuffix ? `${defaultHint} \xB7 ${ctxSuffix}` : defaultHint || ctxSuffix
|
|
1503
1549
|
};
|
|
1504
1550
|
}
|
|
1505
|
-
function fmtContextWindow(
|
|
1506
|
-
if (!
|
|
1507
|
-
const k =
|
|
1551
|
+
function fmtContextWindow(contextWindow2) {
|
|
1552
|
+
if (!contextWindow2) return "";
|
|
1553
|
+
const k = contextWindow2 >= 1e3 ? `${Math.round(contextWindow2 / 1e3)}k` : String(contextWindow2);
|
|
1508
1554
|
return pc.dim(`${k} ctx`);
|
|
1509
1555
|
}
|
|
1510
1556
|
function navOption(value, label, hint = "") {
|
|
@@ -2146,9 +2192,9 @@ var ONE_M_CONTEXT_SUFFIX = "[1m]";
|
|
|
2146
2192
|
function stripOneMContextSuffix(modelId) {
|
|
2147
2193
|
return modelId.replace(/\[1m\]$/i, "");
|
|
2148
2194
|
}
|
|
2149
|
-
function claudeCodeClientModelId(modelId,
|
|
2195
|
+
function claudeCodeClientModelId(modelId, contextWindow2) {
|
|
2150
2196
|
const bare = stripOneMContextSuffix(modelId);
|
|
2151
|
-
const window = resolveContextWindow(bare,
|
|
2197
|
+
const window = resolveContextWindow(bare, contextWindow2);
|
|
2152
2198
|
if (window > DEFAULT_CONTEXT_WINDOW) {
|
|
2153
2199
|
return `${bare}${ONE_M_CONTEXT_SUFFIX}`;
|
|
2154
2200
|
}
|
|
@@ -2509,16 +2555,16 @@ function readOpencodeAuthFile(env = process.env) {
|
|
|
2509
2555
|
} catch {
|
|
2510
2556
|
return { path, entries: {}, permissionWarning: authFilePermissionWarning(path) };
|
|
2511
2557
|
}
|
|
2512
|
-
const
|
|
2558
|
+
const entries2 = {};
|
|
2513
2559
|
if (parsed && typeof parsed === "object") {
|
|
2514
2560
|
for (const [providerId, value] of Object.entries(parsed)) {
|
|
2515
2561
|
const entry = decodeAuthEntry(value);
|
|
2516
|
-
if (entry)
|
|
2562
|
+
if (entry) entries2[providerId] = entry;
|
|
2517
2563
|
}
|
|
2518
2564
|
}
|
|
2519
2565
|
return {
|
|
2520
2566
|
path,
|
|
2521
|
-
entries,
|
|
2567
|
+
entries: entries2,
|
|
2522
2568
|
permissionWarning: authFilePermissionWarning(path)
|
|
2523
2569
|
};
|
|
2524
2570
|
}
|
|
@@ -2570,7 +2616,7 @@ function accessTokenIsExpiring(token, skewMs = OAUTH_REFRESH_SKEW_MS) {
|
|
|
2570
2616
|
return false;
|
|
2571
2617
|
}
|
|
2572
2618
|
}
|
|
2573
|
-
var NATIVE_OAUTH_PROVIDER_IDS = ["xai", "xai-oauth", "openai", "openai-oauth", "github-copilot", "claude-code", "antigravity"];
|
|
2619
|
+
var NATIVE_OAUTH_PROVIDER_IDS = ["xai", "xai-oauth", "openai", "openai-oauth", "github-copilot", "claude-code", "antigravity", "cline-pass"];
|
|
2574
2620
|
function supportsNativeOAuth(providerId) {
|
|
2575
2621
|
return NATIVE_OAUTH_PROVIDER_IDS.includes(providerId);
|
|
2576
2622
|
}
|
|
@@ -2981,21 +3027,165 @@ async function fetchClaudeCodeModels(accessToken) {
|
|
|
2981
3027
|
throw new Error(`Claude Code model discovery failed (HTTP ${res.status}): ${await res.text().catch(() => "")}`);
|
|
2982
3028
|
}
|
|
2983
3029
|
const body = await res.json();
|
|
2984
|
-
const
|
|
3030
|
+
const entries2 = (body.data ?? []).filter((m) => typeof m.id === "string" && m.id.length > 0).map((m) => ({
|
|
2985
3031
|
id: m.id,
|
|
2986
3032
|
displayName: typeof m.display_name === "string" ? m.display_name : m.id,
|
|
2987
3033
|
maxInputTokens: typeof m.max_input_tokens === "number" ? m.max_input_tokens : void 0,
|
|
2988
3034
|
maxTokens: typeof m.max_tokens === "number" ? m.max_tokens : void 0
|
|
2989
3035
|
}));
|
|
2990
|
-
if (
|
|
3036
|
+
if (entries2.length === 0) {
|
|
2991
3037
|
throw new Error("Claude Code model discovery returned no models");
|
|
2992
3038
|
}
|
|
2993
|
-
return
|
|
3039
|
+
return entries2;
|
|
2994
3040
|
}
|
|
2995
3041
|
function guiCallbackRedirectUri(host) {
|
|
2996
3042
|
return `http://${host}/oauth/callback`;
|
|
2997
3043
|
}
|
|
2998
3044
|
|
|
3045
|
+
// src/oauth/cline-pass.ts
|
|
3046
|
+
var WORKOS_CLIENT_ID = "client_01K3A541FN8TA3EPPHTD2325AR";
|
|
3047
|
+
var WORKOS_DEVICE_URL = "https://api.workos.com/user_management/authorize/device";
|
|
3048
|
+
var WORKOS_TOKEN_URL = "https://api.workos.com/user_management/authenticate";
|
|
3049
|
+
var DEVICE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
|
|
3050
|
+
var DEFAULT_INTERVAL_MS = 5e3;
|
|
3051
|
+
var SLOW_DOWN_INCREMENT_MS = 1e3;
|
|
3052
|
+
var DEFAULT_EXPIRES_MS = 10 * 60 * 1e3;
|
|
3053
|
+
function formHeaders() {
|
|
3054
|
+
return {
|
|
3055
|
+
Accept: "application/json",
|
|
3056
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
3057
|
+
};
|
|
3058
|
+
}
|
|
3059
|
+
function jsonHeaders() {
|
|
3060
|
+
return {
|
|
3061
|
+
Accept: "application/json",
|
|
3062
|
+
"Content-Type": "application/json"
|
|
3063
|
+
};
|
|
3064
|
+
}
|
|
3065
|
+
function expiresInFromIso(expiresAt) {
|
|
3066
|
+
if (typeof expiresAt !== "string") throw new Error("ClinePass response is missing a valid expiresAt");
|
|
3067
|
+
const timestamp = Date.parse(expiresAt);
|
|
3068
|
+
if (!Number.isFinite(timestamp)) throw new Error("ClinePass response is missing a valid expiresAt");
|
|
3069
|
+
return Math.max(1, Math.floor((timestamp - Date.now()) / 1e3));
|
|
3070
|
+
}
|
|
3071
|
+
function toOAuthResult(data) {
|
|
3072
|
+
if (typeof data.accessToken !== "string" || !data.accessToken) {
|
|
3073
|
+
throw new Error("ClinePass response is missing accessToken");
|
|
3074
|
+
}
|
|
3075
|
+
const userInfo = data.userInfo && typeof data.userInfo === "object" && !Array.isArray(data.userInfo) ? data.userInfo : void 0;
|
|
3076
|
+
const accountId = typeof userInfo?.clineUserId === "string" ? userInfo.clineUserId : void 0;
|
|
3077
|
+
return {
|
|
3078
|
+
tokens: {
|
|
3079
|
+
access_token: data.accessToken,
|
|
3080
|
+
...typeof data.refreshToken === "string" ? { refresh_token: data.refreshToken } : {},
|
|
3081
|
+
expires_in: expiresInFromIso(data.expiresAt),
|
|
3082
|
+
...userInfo ? { providerData: userInfo } : {}
|
|
3083
|
+
},
|
|
3084
|
+
...accountId ? { accountId } : {},
|
|
3085
|
+
...userInfo ? { providerData: userInfo } : {}
|
|
3086
|
+
};
|
|
3087
|
+
}
|
|
3088
|
+
async function readError(response) {
|
|
3089
|
+
const text4 = await response.text().catch(() => "");
|
|
3090
|
+
if (!text4) return `HTTP ${response.status}`;
|
|
3091
|
+
try {
|
|
3092
|
+
const parsed = JSON.parse(text4);
|
|
3093
|
+
const detail = typeof parsed.error === "string" ? parsed.error : typeof parsed.message === "string" ? parsed.message : "";
|
|
3094
|
+
return detail || `HTTP ${response.status}`;
|
|
3095
|
+
} catch {
|
|
3096
|
+
return text4.slice(0, 120);
|
|
3097
|
+
}
|
|
3098
|
+
}
|
|
3099
|
+
async function requestClinePassDeviceCode() {
|
|
3100
|
+
const response = await fetch(WORKOS_DEVICE_URL, {
|
|
3101
|
+
method: "POST",
|
|
3102
|
+
headers: formHeaders(),
|
|
3103
|
+
body: new URLSearchParams({ client_id: WORKOS_CLIENT_ID }).toString()
|
|
3104
|
+
});
|
|
3105
|
+
if (!response.ok) throw new Error(`ClinePass device code request failed (${response.status})`);
|
|
3106
|
+
const json = await response.json();
|
|
3107
|
+
if (!json.device_code || !json.user_code || !json.verification_uri) {
|
|
3108
|
+
throw new Error("ClinePass device code response is missing required fields");
|
|
3109
|
+
}
|
|
3110
|
+
return json;
|
|
3111
|
+
}
|
|
3112
|
+
async function registerClinePassTokens(accessToken, refreshToken) {
|
|
3113
|
+
const response = await fetch(CLINE_PASS_REGISTER_URL, {
|
|
3114
|
+
method: "POST",
|
|
3115
|
+
headers: jsonHeaders(),
|
|
3116
|
+
body: JSON.stringify({ accessToken, refreshToken })
|
|
3117
|
+
});
|
|
3118
|
+
if (!response.ok) throw new Error(`ClinePass registration failed (${response.status})`);
|
|
3119
|
+
const body = await response.json();
|
|
3120
|
+
if (body.success !== true || !body.data) {
|
|
3121
|
+
const detail = typeof body.error === "string" ? body.error : typeof body.message === "string" ? body.message : "unsuccessful response";
|
|
3122
|
+
throw new Error(`ClinePass registration failed: ${detail}`);
|
|
3123
|
+
}
|
|
3124
|
+
return toOAuthResult(body.data);
|
|
3125
|
+
}
|
|
3126
|
+
async function pollClinePassDeviceCode(device, opts) {
|
|
3127
|
+
const sleep3 = opts?.sleep ?? sleepMs;
|
|
3128
|
+
const now = opts?.now ?? (() => Date.now());
|
|
3129
|
+
const deadline = now() + positiveSecondsToMs(device.expires_in, DEFAULT_EXPIRES_MS);
|
|
3130
|
+
let intervalMs = Math.max(positiveSecondsToMs(device.interval, DEFAULT_INTERVAL_MS), 1e3);
|
|
3131
|
+
while (now() < deadline) {
|
|
3132
|
+
const response = await fetch(WORKOS_TOKEN_URL, {
|
|
3133
|
+
method: "POST",
|
|
3134
|
+
headers: formHeaders(),
|
|
3135
|
+
body: new URLSearchParams({
|
|
3136
|
+
grant_type: DEVICE_GRANT_TYPE,
|
|
3137
|
+
client_id: WORKOS_CLIENT_ID,
|
|
3138
|
+
device_code: device.device_code
|
|
3139
|
+
}).toString()
|
|
3140
|
+
});
|
|
3141
|
+
if (response.ok) {
|
|
3142
|
+
const workos = await response.json();
|
|
3143
|
+
if (!workos.access_token || !workos.refresh_token) {
|
|
3144
|
+
throw new Error("ClinePass WorkOS response is missing required tokens");
|
|
3145
|
+
}
|
|
3146
|
+
return registerClinePassTokens(workos.access_token, workos.refresh_token);
|
|
3147
|
+
}
|
|
3148
|
+
const body = await response.json().catch(() => ({}));
|
|
3149
|
+
const remaining = Math.max(0, deadline - now());
|
|
3150
|
+
if (body.error === "authorization_pending") {
|
|
3151
|
+
await sleep3(Math.min(intervalMs, remaining));
|
|
3152
|
+
continue;
|
|
3153
|
+
}
|
|
3154
|
+
if (body.error === "slow_down") {
|
|
3155
|
+
intervalMs += SLOW_DOWN_INCREMENT_MS;
|
|
3156
|
+
await sleep3(Math.min(intervalMs, remaining));
|
|
3157
|
+
continue;
|
|
3158
|
+
}
|
|
3159
|
+
throw new Error(`ClinePass device authorization failed${body.error ? `: ${body.error}` : ""}`);
|
|
3160
|
+
}
|
|
3161
|
+
throw new Error("ClinePass device authorization timed out");
|
|
3162
|
+
}
|
|
3163
|
+
async function runClinePassDeviceCodeFlow(onDeviceCode, opts) {
|
|
3164
|
+
const device = await requestClinePassDeviceCode();
|
|
3165
|
+
onDeviceCode({
|
|
3166
|
+
url: device.verification_uri_complete ?? device.verification_uri,
|
|
3167
|
+
userCode: device.user_code
|
|
3168
|
+
});
|
|
3169
|
+
return pollClinePassDeviceCode(device, opts);
|
|
3170
|
+
}
|
|
3171
|
+
async function refreshClinePassAccessToken(refreshToken) {
|
|
3172
|
+
const response = await fetch(CLINE_PASS_REFRESH_URL, {
|
|
3173
|
+
method: "POST",
|
|
3174
|
+
headers: jsonHeaders(),
|
|
3175
|
+
body: JSON.stringify({ refreshToken, grantType: "refresh_token" })
|
|
3176
|
+
});
|
|
3177
|
+
if (!response.ok) {
|
|
3178
|
+
const detail = await readError(response);
|
|
3179
|
+
throw new Error(`ClinePass token refresh failed (${response.status}): ${detail}`);
|
|
3180
|
+
}
|
|
3181
|
+
const body = await response.json();
|
|
3182
|
+
if (body.success !== true || !body.data) {
|
|
3183
|
+
const detail = typeof body.error === "string" ? body.error : typeof body.message === "string" ? body.message : "unsuccessful response";
|
|
3184
|
+
throw new Error(`ClinePass token refresh failed: ${detail}`);
|
|
3185
|
+
}
|
|
3186
|
+
return toOAuthResult(body.data).tokens;
|
|
3187
|
+
}
|
|
3188
|
+
|
|
2999
3189
|
// src/oauth/refresh.ts
|
|
3000
3190
|
function oauthCredentialShouldRefresh(cred, providerId) {
|
|
3001
3191
|
if (oauthCredentialNeedsRefresh(cred)) return true;
|
|
@@ -3017,10 +3207,13 @@ async function refreshStoredOAuthCredential(providerId, cred) {
|
|
|
3017
3207
|
tokens = await refreshClaudeCodeToken(cred.refresh);
|
|
3018
3208
|
} else if (providerId === "antigravity") {
|
|
3019
3209
|
tokens = await refreshAntigravityToken(cred.refresh);
|
|
3210
|
+
} else if (providerId === "cline-pass") {
|
|
3211
|
+
tokens = await refreshClinePassAccessToken(cred.refresh);
|
|
3020
3212
|
} else {
|
|
3021
3213
|
throw new Error(`OAuth refresh not implemented for provider "${providerId}"`);
|
|
3022
3214
|
}
|
|
3023
|
-
|
|
3215
|
+
const accountId = providerId === "cline-pass" && typeof tokens.providerData?.clineUserId === "string" ? tokens.providerData.clineUserId : cred.accountId;
|
|
3216
|
+
return tokensToStoredCredential(tokens, cred.refresh, accountId, cred.providerData);
|
|
3024
3217
|
}
|
|
3025
3218
|
|
|
3026
3219
|
// src/secrets-file.ts
|
|
@@ -3102,7 +3295,7 @@ function applyClaudeCodeThirdPartyCompat(env) {
|
|
|
3102
3295
|
env["ENABLE_TOOL_SEARCH"] = "true";
|
|
3103
3296
|
env["CLAUDE_CODE_SIMPLE_SYSTEM_PROMPT"] = "0";
|
|
3104
3297
|
}
|
|
3105
|
-
function buildChildEnv(baseUrl, model, apiKey, proxyPort,
|
|
3298
|
+
function buildChildEnv(baseUrl, model, apiKey, proxyPort, contextWindow2, enableGatewayDiscovery) {
|
|
3106
3299
|
const env = { ...process.env };
|
|
3107
3300
|
for (const name of CONFLICTING_ENV_VARS) {
|
|
3108
3301
|
delete env[name];
|
|
@@ -3113,8 +3306,8 @@ function buildChildEnv(baseUrl, model, apiKey, proxyPort, contextWindow, enableG
|
|
|
3113
3306
|
env["ANTHROPIC_BASE_URL"] = proxyPort ? `http://127.0.0.1:${proxyPort}` : baseUrl;
|
|
3114
3307
|
env["ANTHROPIC_API_KEY"] = apiKey;
|
|
3115
3308
|
const bareModel = stripOneMContextSuffix(model);
|
|
3116
|
-
env["ANTHROPIC_MODEL"] = claudeCodeClientModelId(model,
|
|
3117
|
-
env["CLAUDE_CODE_MAX_CONTEXT_TOKENS"] = String(resolveContextWindow(bareModel,
|
|
3309
|
+
env["ANTHROPIC_MODEL"] = claudeCodeClientModelId(model, contextWindow2);
|
|
3310
|
+
env["CLAUDE_CODE_MAX_CONTEXT_TOKENS"] = String(resolveContextWindow(bareModel, contextWindow2));
|
|
3118
3311
|
if (enableGatewayDiscovery) {
|
|
3119
3312
|
env["CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY"] = "1";
|
|
3120
3313
|
}
|
|
@@ -3311,6 +3504,18 @@ async function resolveProviderCredential(providerId, authRef, diag) {
|
|
|
3311
3504
|
}
|
|
3312
3505
|
return readProviderSecret(parsed.account, diag);
|
|
3313
3506
|
}
|
|
3507
|
+
async function forceRefreshProviderCredential(providerId, authRef, diag) {
|
|
3508
|
+
const namespaced = readEnvCredential(relayAiKeyEnvVar(providerId));
|
|
3509
|
+
if (namespaced) return namespaced;
|
|
3510
|
+
const parsed = parseAuthRef(authRef);
|
|
3511
|
+
if (!parsed || parsed.kind !== "keyring") {
|
|
3512
|
+
return resolveProviderCredential(providerId, authRef, diag);
|
|
3513
|
+
}
|
|
3514
|
+
const oauthProviderId = oauthProviderIdFromAccount(parsed.account);
|
|
3515
|
+
const raw = await readKeyringAccount(parsed.account, diag);
|
|
3516
|
+
if (!raw || !oauthProviderId) return decodeProviderSecret(raw);
|
|
3517
|
+
return refreshOAuthKeyringAccount(parsed.account, oauthProviderId, raw, diag, true);
|
|
3518
|
+
}
|
|
3314
3519
|
async function resolveProviderOAuthAccountId(authRef, diag) {
|
|
3315
3520
|
const parsed = parseAuthRef(authRef);
|
|
3316
3521
|
if (!parsed || parsed.kind !== "keyring" || !oauthProviderIdFromAccount(parsed.account)) return void 0;
|
|
@@ -3359,12 +3564,12 @@ function decodeProviderSecret(raw) {
|
|
|
3359
3564
|
}
|
|
3360
3565
|
return trimmed;
|
|
3361
3566
|
}
|
|
3362
|
-
async function refreshOAuthKeyringAccount(account, providerId, raw, diag) {
|
|
3567
|
+
async function refreshOAuthKeyringAccount(account, providerId, raw, diag, force = false) {
|
|
3363
3568
|
const existing = oauthRefreshInflight.get(account);
|
|
3364
3569
|
if (existing) return existing;
|
|
3365
3570
|
const work = (async () => {
|
|
3366
3571
|
const cred = parseStoredOAuthCredential(raw);
|
|
3367
|
-
if (!cred || !oauthCredentialShouldRefresh(cred, providerId)) {
|
|
3572
|
+
if (!cred || !force && !oauthCredentialShouldRefresh(cred, providerId)) {
|
|
3368
3573
|
return decodeProviderSecret(raw);
|
|
3369
3574
|
}
|
|
3370
3575
|
try {
|
|
@@ -4180,6 +4385,16 @@ function getAntigravityDebugLogPath(tracePrefix) {
|
|
|
4180
4385
|
const surface = tracePrefix === "antigravity" ? "app" : tracePrefix;
|
|
4181
4386
|
return join8(ensureLogsDir(), `antigravity-${surface}-debug.log`);
|
|
4182
4387
|
}
|
|
4388
|
+
function prepareProviderTraceLog() {
|
|
4389
|
+
const path = getProviderDebugLogPath();
|
|
4390
|
+
resetTraceLog(path);
|
|
4391
|
+
try {
|
|
4392
|
+
writeFileSync5(path, "", { mode: FILE_MODE5 });
|
|
4393
|
+
chmodSync5(path, FILE_MODE5);
|
|
4394
|
+
} catch {
|
|
4395
|
+
}
|
|
4396
|
+
return path;
|
|
4397
|
+
}
|
|
4183
4398
|
function makeTraceLogger(logPath) {
|
|
4184
4399
|
resetTraceLog(logPath);
|
|
4185
4400
|
return (message) => writeSecureLogLine(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${message}`);
|
|
@@ -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,
|
|
4322
|
-
const maxInput = resolveContextWindow(id,
|
|
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(
|
|
4782
|
+
function formatAnthropicModelList(entries2) {
|
|
4334
4783
|
return {
|
|
4335
|
-
data:
|
|
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:
|
|
4343
|
-
last_id:
|
|
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
|
-
|
|
4393
|
-
const
|
|
4394
|
-
|
|
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
|
|
5088
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
4615
5089
|
|
|
4616
5090
|
// src/antigravity/request-adapter.ts
|
|
4617
|
-
import { randomUUID as
|
|
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
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
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_" +
|
|
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_" +
|
|
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:
|
|
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
|
|
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_${
|
|
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_${
|
|
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_${
|
|
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_${
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
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, ...
|
|
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, ...
|
|
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
|
-
|
|
5961
|
-
|
|
5962
|
-
|
|
5963
|
-
|
|
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 =
|
|
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
|
|
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,
|
|
6903
|
+
function startProxy(completionsUrl, modelId, debug = false, contextWindow2, sdk, apiKey) {
|
|
6285
6904
|
const bareModelId = stripOneMContextSuffix(modelId);
|
|
6286
|
-
const clientModelId = claudeCodeClientModelId(modelId,
|
|
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
|
-
|
|
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
|
|
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
|
|
8089
|
+
const entries2 = [];
|
|
7456
8090
|
for (const provider of reg.providers) {
|
|
7457
|
-
|
|
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
|
|
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
|
|
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,
|
|
8188
|
-
return
|
|
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
|
|
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
|
|
8943
|
-
const models =
|
|
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 = (
|
|
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
|
|
9156
|
-
if (
|
|
9157
|
-
const live =
|
|
9918
|
+
const entries2 = (result.body.data ?? []).filter((m) => !!m.id);
|
|
9919
|
+
if (entries2.length > 0) {
|
|
9920
|
+
const live = entries2.map(({ id, context_length }) => {
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
10877
|
+
plog,
|
|
10080
10878
|
estimateAnthropicInputTokens(body)
|
|
10081
10879
|
);
|
|
10082
10880
|
res.end();
|
|
10083
10881
|
} else {
|
|
10084
|
-
const anthropicResponse = await generateAnthropicResponse(
|
|
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(
|
|
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
|
-
|
|
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-
|
|
13025
|
+
//# sourceMappingURL=chunk-5DDQJSTU.js.map
|