theclawbay 0.3.45 → 0.3.47
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/dist/commands/logout.js +26 -2
- package/dist/commands/setup.js +204 -26
- package/dist/lib/device-session-auth.js +64 -13
- package/package.json +1 -1
package/dist/commands/logout.js
CHANGED
|
@@ -43,7 +43,10 @@ const HISTORY_PROVIDER_NEUTRALIZE_SOURCES = new Set([
|
|
|
43
43
|
DEFAULT_PROVIDER_ID,
|
|
44
44
|
]);
|
|
45
45
|
const HISTORY_PROVIDER_DB_MIGRATE_SOURCES = [DEFAULT_PROVIDER_ID, WAN_PROVIDER_ID];
|
|
46
|
-
const
|
|
46
|
+
const LEGACY_THECLAWBAY_OPENAI_PROXY_SUFFIX = "/api/codex-auth/v1/proxy/v1";
|
|
47
|
+
const CANONICAL_THECLAWBAY_OPENAI_PROXY_SUFFIX = "/v1";
|
|
48
|
+
const THECLAWBAY_CANONICAL_API_HOST = "api.theclawbay.com";
|
|
49
|
+
const THECLAWBAY_WEBSITE_HOSTS = new Set(["theclawbay.com", "www.theclawbay.com"]);
|
|
47
50
|
const SUPPORTED_MODEL_IDS = new Set((0, supported_models_1.getSupportedModelIds)());
|
|
48
51
|
const CONTINUE_MODEL_NAME = "The Claw Bay";
|
|
49
52
|
const TRAE_PATCH_MARKER = "theclawbay-trae-patch";
|
|
@@ -166,7 +169,28 @@ function objectRecordOr(value, fallback) {
|
|
|
166
169
|
return fallback;
|
|
167
170
|
}
|
|
168
171
|
function isTheClawBayOpenAiCompatibleBaseUrl(value) {
|
|
169
|
-
|
|
172
|
+
if (typeof value !== "string")
|
|
173
|
+
return false;
|
|
174
|
+
const normalized = value.trim();
|
|
175
|
+
if (!normalized)
|
|
176
|
+
return false;
|
|
177
|
+
try {
|
|
178
|
+
const parsed = new URL(normalized);
|
|
179
|
+
const hostname = parsed.hostname.toLowerCase();
|
|
180
|
+
const pathname = parsed.pathname.replace(/\/+$/g, "");
|
|
181
|
+
if (pathname === LEGACY_THECLAWBAY_OPENAI_PROXY_SUFFIX &&
|
|
182
|
+
(THECLAWBAY_WEBSITE_HOSTS.has(hostname) ||
|
|
183
|
+
hostname === "localhost" ||
|
|
184
|
+
hostname === "127.0.0.1" ||
|
|
185
|
+
hostname === "::1")) {
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
return (hostname === THECLAWBAY_CANONICAL_API_HOST &&
|
|
189
|
+
pathname === CANONICAL_THECLAWBAY_OPENAI_PROXY_SUFFIX);
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
170
194
|
}
|
|
171
195
|
function uniqueStrings(values) {
|
|
172
196
|
const output = [];
|
package/dist/commands/setup.js
CHANGED
|
@@ -39,6 +39,8 @@ const DEFAULT_ZO_MODEL = DEFAULT_CODEX_MODEL;
|
|
|
39
39
|
const DEFAULT_MODELS = [...SUPPORTED_MODEL_IDS];
|
|
40
40
|
const PREFERRED_MODELS = [...SUPPORTED_MODEL_IDS];
|
|
41
41
|
const ENV_KEY_NAME = "THECLAWBAY_API_KEY";
|
|
42
|
+
const CLAUDE_ENV_API_KEY_NAME = "ANTHROPIC_API_KEY";
|
|
43
|
+
const CLAUDE_ENV_BASE_URL_NAME = "ANTHROPIC_BASE_URL";
|
|
42
44
|
const ENV_FILE = node_path_1.default.join(paths_1.theclawbayConfigDir, "env");
|
|
43
45
|
const CONTINUE_CONFIG_PATH = node_path_1.default.join(node_os_1.default.homedir(), ".continue", "config.yaml");
|
|
44
46
|
const CLINE_GLOBAL_STATE_PATH = node_path_1.default.join(node_os_1.default.homedir(), ".cline", "data", "globalState.json");
|
|
@@ -60,7 +62,11 @@ const OPENCLAW_PROVIDER_ID = DEFAULT_PROVIDER_ID;
|
|
|
60
62
|
const HISTORY_PROVIDER_NEUTRALIZE_SOURCES = new Set(["openai", "theclawbay-wan", DEFAULT_PROVIDER_ID]);
|
|
61
63
|
const HISTORY_PROVIDER_DB_MIGRATE_SOURCES = ["openai", "theclawbay-wan"];
|
|
62
64
|
const SETUP_CLIENT_IDS = ["codex", "continue", "cline", "openclaw", "opencode", "kilo", "roo", "trae", "aider", "zo"];
|
|
63
|
-
const
|
|
65
|
+
const LEGACY_THECLAWBAY_OPENAI_PROXY_SUFFIX = "/api/codex-auth/v1/proxy/v1";
|
|
66
|
+
const CANONICAL_THECLAWBAY_OPENAI_PROXY_SUFFIX = "/v1";
|
|
67
|
+
const CANONICAL_CODEX_NATIVE_PROXY_SUFFIX = "/backend-api/codex";
|
|
68
|
+
const THECLAWBAY_CANONICAL_API_HOST = "api.theclawbay.com";
|
|
69
|
+
const THECLAWBAY_WEBSITE_HOSTS = new Set(["theclawbay.com", "www.theclawbay.com"]);
|
|
64
70
|
const CONTINUE_MODEL_NAME = "The Claw Bay";
|
|
65
71
|
const ROO_PROFILE_NAME = "The Claw Bay";
|
|
66
72
|
const ROO_PROFILE_ID = "theclawbay-openai-compatible";
|
|
@@ -303,7 +309,7 @@ async function configureZoByok(params) {
|
|
|
303
309
|
throw new Error("Zo was detected, but no active Zo desktop session token could be read. Open Zo, sign in, and rerun setup.");
|
|
304
310
|
}
|
|
305
311
|
const desiredName = `${ZO_CONFIG_NAME_PREFIX} ${modelDisplayName(params.model)}`;
|
|
306
|
-
const desiredBaseUrl =
|
|
312
|
+
const desiredBaseUrl = openAiCompatibleProxyUrl(params.backendUrl);
|
|
307
313
|
const body = {
|
|
308
314
|
provider: "openai-style",
|
|
309
315
|
format: "openai",
|
|
@@ -449,11 +455,51 @@ function shellQuote(value) {
|
|
|
449
455
|
function modelDisplayName(modelId) {
|
|
450
456
|
return MODEL_DISPLAY_NAMES[modelId] ?? modelId;
|
|
451
457
|
}
|
|
458
|
+
function publicApiOriginForBackendUrl(backendUrl) {
|
|
459
|
+
const trimmed = trimTrailingSlash(backendUrl);
|
|
460
|
+
try {
|
|
461
|
+
const parsed = new URL(trimmed);
|
|
462
|
+
parsed.hash = "";
|
|
463
|
+
parsed.search = "";
|
|
464
|
+
parsed.pathname = "";
|
|
465
|
+
if (THECLAWBAY_WEBSITE_HOSTS.has(parsed.hostname.toLowerCase())) {
|
|
466
|
+
parsed.hostname = THECLAWBAY_CANONICAL_API_HOST;
|
|
467
|
+
}
|
|
468
|
+
return trimTrailingSlash(parsed.toString());
|
|
469
|
+
}
|
|
470
|
+
catch {
|
|
471
|
+
return trimmed;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
452
474
|
function openAiCompatibleProxyUrl(backendUrl) {
|
|
453
|
-
return `${
|
|
475
|
+
return `${publicApiOriginForBackendUrl(backendUrl)}${CANONICAL_THECLAWBAY_OPENAI_PROXY_SUFFIX}`;
|
|
476
|
+
}
|
|
477
|
+
function anthropicCompatibleProxyUrl(backendUrl) {
|
|
478
|
+
return `${publicApiOriginForBackendUrl(backendUrl)}/anthropic`;
|
|
454
479
|
}
|
|
455
480
|
function isTheClawBayOpenAiCompatibleBaseUrl(value) {
|
|
456
|
-
|
|
481
|
+
if (typeof value !== "string")
|
|
482
|
+
return false;
|
|
483
|
+
const normalized = value.trim();
|
|
484
|
+
if (!normalized)
|
|
485
|
+
return false;
|
|
486
|
+
try {
|
|
487
|
+
const parsed = new URL(normalized);
|
|
488
|
+
const hostname = parsed.hostname.toLowerCase();
|
|
489
|
+
const pathname = trimTrailingSlash(parsed.pathname);
|
|
490
|
+
if (pathname === LEGACY_THECLAWBAY_OPENAI_PROXY_SUFFIX &&
|
|
491
|
+
(THECLAWBAY_WEBSITE_HOSTS.has(hostname) ||
|
|
492
|
+
hostname === "localhost" ||
|
|
493
|
+
hostname === "127.0.0.1" ||
|
|
494
|
+
hostname === "::1")) {
|
|
495
|
+
return true;
|
|
496
|
+
}
|
|
497
|
+
return (hostname === THECLAWBAY_CANONICAL_API_HOST &&
|
|
498
|
+
pathname === CANONICAL_THECLAWBAY_OPENAI_PROXY_SUFFIX);
|
|
499
|
+
}
|
|
500
|
+
catch {
|
|
501
|
+
return false;
|
|
502
|
+
}
|
|
457
503
|
}
|
|
458
504
|
function uniqueStrings(values) {
|
|
459
505
|
const output = [];
|
|
@@ -913,8 +959,16 @@ async function resolveDeviceLabel(params) {
|
|
|
913
959
|
rl.close();
|
|
914
960
|
}
|
|
915
961
|
}
|
|
962
|
+
function summarizeModelFetchFailure(detail) {
|
|
963
|
+
const normalized = detail.replace(/\s+/g, " ").trim();
|
|
964
|
+
if (!normalized)
|
|
965
|
+
return "unknown error";
|
|
966
|
+
if (normalized.length <= 140)
|
|
967
|
+
return normalized;
|
|
968
|
+
return `${normalized.slice(0, 137).trimEnd()}...`;
|
|
969
|
+
}
|
|
916
970
|
async function fetchBackendModelIds(backendUrl, apiKey) {
|
|
917
|
-
const url = `${
|
|
971
|
+
const url = `${openAiCompatibleProxyUrl(backendUrl)}/models`;
|
|
918
972
|
try {
|
|
919
973
|
const response = await fetch(url, {
|
|
920
974
|
headers: {
|
|
@@ -922,18 +976,95 @@ async function fetchBackendModelIds(backendUrl, apiKey) {
|
|
|
922
976
|
Accept: "application/json",
|
|
923
977
|
"User-Agent": CLI_HTTP_USER_AGENT,
|
|
924
978
|
},
|
|
925
|
-
signal: AbortSignal.timeout(
|
|
979
|
+
signal: AbortSignal.timeout(10000),
|
|
926
980
|
});
|
|
927
|
-
if (!response.ok)
|
|
928
|
-
|
|
981
|
+
if (!response.ok) {
|
|
982
|
+
const responseText = await response.text().catch(() => "");
|
|
983
|
+
let detail = responseText.trim();
|
|
984
|
+
try {
|
|
985
|
+
const parsed = JSON.parse(responseText);
|
|
986
|
+
if (typeof parsed.error === "string" && parsed.error.trim()) {
|
|
987
|
+
detail = parsed.error.trim();
|
|
988
|
+
}
|
|
989
|
+
else if (typeof parsed.code === "string" && parsed.code.trim()) {
|
|
990
|
+
detail = parsed.code.trim();
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
catch {
|
|
994
|
+
// Keep the raw response text when the body is not JSON.
|
|
995
|
+
}
|
|
996
|
+
return {
|
|
997
|
+
ids: null,
|
|
998
|
+
failure: `HTTP ${response.status}${detail ? `: ${summarizeModelFetchFailure(detail)}` : ""}`,
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
929
1001
|
const body = (await response.json());
|
|
930
1002
|
const ids = (body.data ?? [])
|
|
931
1003
|
.map((entry) => (typeof entry.id === "string" ? entry.id.trim() : ""))
|
|
932
1004
|
.filter((id) => id.length > 0);
|
|
933
|
-
|
|
1005
|
+
if (!ids.length) {
|
|
1006
|
+
return {
|
|
1007
|
+
ids: null,
|
|
1008
|
+
failure: "backend returned an empty model list",
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
return { ids };
|
|
934
1012
|
}
|
|
935
|
-
catch {
|
|
936
|
-
return
|
|
1013
|
+
catch (error) {
|
|
1014
|
+
return {
|
|
1015
|
+
ids: null,
|
|
1016
|
+
failure: summarizeModelFetchFailure(error instanceof Error ? error.message : String(error)),
|
|
1017
|
+
};
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
async function fetchClaudeModelIds(backendUrl, apiKey) {
|
|
1021
|
+
const url = `${anthropicCompatibleProxyUrl(backendUrl)}/v1/models`;
|
|
1022
|
+
try {
|
|
1023
|
+
const response = await fetch(url, {
|
|
1024
|
+
headers: {
|
|
1025
|
+
"x-api-key": apiKey,
|
|
1026
|
+
Accept: "application/json",
|
|
1027
|
+
"User-Agent": CLI_HTTP_USER_AGENT,
|
|
1028
|
+
},
|
|
1029
|
+
signal: AbortSignal.timeout(10000),
|
|
1030
|
+
});
|
|
1031
|
+
if (!response.ok) {
|
|
1032
|
+
const responseText = await response.text().catch(() => "");
|
|
1033
|
+
let detail = responseText.trim();
|
|
1034
|
+
try {
|
|
1035
|
+
const parsed = JSON.parse(responseText);
|
|
1036
|
+
if (typeof parsed.error === "string" && parsed.error.trim()) {
|
|
1037
|
+
detail = parsed.error.trim();
|
|
1038
|
+
}
|
|
1039
|
+
else if (typeof parsed.code === "string" && parsed.code.trim()) {
|
|
1040
|
+
detail = parsed.code.trim();
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
catch {
|
|
1044
|
+
// Keep the raw response text when the body is not JSON.
|
|
1045
|
+
}
|
|
1046
|
+
return {
|
|
1047
|
+
ids: null,
|
|
1048
|
+
failure: `HTTP ${response.status}${detail ? `: ${summarizeModelFetchFailure(detail)}` : ""}`,
|
|
1049
|
+
};
|
|
1050
|
+
}
|
|
1051
|
+
const body = (await response.json());
|
|
1052
|
+
const ids = (body.data ?? [])
|
|
1053
|
+
.map((entry) => (typeof entry.id === "string" ? entry.id.trim() : ""))
|
|
1054
|
+
.filter((id) => id.length > 0);
|
|
1055
|
+
if (!ids.length) {
|
|
1056
|
+
return {
|
|
1057
|
+
ids: null,
|
|
1058
|
+
failure: "backend returned an empty Claude model list",
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
return { ids };
|
|
1062
|
+
}
|
|
1063
|
+
catch (error) {
|
|
1064
|
+
return {
|
|
1065
|
+
ids: null,
|
|
1066
|
+
failure: summarizeModelFetchFailure(error instanceof Error ? error.message : String(error)),
|
|
1067
|
+
};
|
|
937
1068
|
}
|
|
938
1069
|
}
|
|
939
1070
|
function kiloStorageCandidates() {
|
|
@@ -1042,7 +1173,7 @@ async function detectZoClient() {
|
|
|
1042
1173
|
return false;
|
|
1043
1174
|
}
|
|
1044
1175
|
async function resolveModels(backendUrl, apiKey) {
|
|
1045
|
-
const ids = await fetchBackendModelIds(backendUrl, apiKey);
|
|
1176
|
+
const { ids, failure } = await fetchBackendModelIds(backendUrl, apiKey);
|
|
1046
1177
|
const available = new Set(ids ?? []);
|
|
1047
1178
|
let selected = DEFAULT_CODEX_MODEL;
|
|
1048
1179
|
let note;
|
|
@@ -1057,7 +1188,9 @@ async function resolveModels(backendUrl, apiKey) {
|
|
|
1057
1188
|
note = "No preferred Codex model advertised by backend; selected first available model.";
|
|
1058
1189
|
}
|
|
1059
1190
|
else if (!ids) {
|
|
1060
|
-
note =
|
|
1191
|
+
note = failure
|
|
1192
|
+
? `Unable to query backend model list (${failure}); defaulted to ${DEFAULT_CODEX_MODEL}.`
|
|
1193
|
+
: `Unable to query backend model list; defaulted to ${DEFAULT_CODEX_MODEL}.`;
|
|
1061
1194
|
}
|
|
1062
1195
|
const unique = [];
|
|
1063
1196
|
const pushUnique = (modelId) => {
|
|
@@ -1076,6 +1209,20 @@ async function resolveModels(backendUrl, apiKey) {
|
|
|
1076
1209
|
note,
|
|
1077
1210
|
};
|
|
1078
1211
|
}
|
|
1212
|
+
async function resolveClaudeAccess(backendUrl, apiKey) {
|
|
1213
|
+
const { ids, failure } = await fetchClaudeModelIds(backendUrl, apiKey);
|
|
1214
|
+
if (!ids?.length) {
|
|
1215
|
+
return {
|
|
1216
|
+
enabled: false,
|
|
1217
|
+
models: [],
|
|
1218
|
+
note: failure ? `Claude Code auto-setup skipped (${failure}).` : undefined,
|
|
1219
|
+
};
|
|
1220
|
+
}
|
|
1221
|
+
return {
|
|
1222
|
+
enabled: true,
|
|
1223
|
+
models: ids,
|
|
1224
|
+
};
|
|
1225
|
+
}
|
|
1079
1226
|
async function writeCodexConfig(params) {
|
|
1080
1227
|
const configPath = node_path_1.default.join(paths_1.codexDir, "config.toml");
|
|
1081
1228
|
await promises_1.default.mkdir(paths_1.codexDir, { recursive: true });
|
|
@@ -1088,18 +1235,19 @@ async function writeCodexConfig(params) {
|
|
|
1088
1235
|
if (err.code !== "ENOENT")
|
|
1089
1236
|
throw error;
|
|
1090
1237
|
}
|
|
1091
|
-
const proxyRoot =
|
|
1238
|
+
const proxyRoot = publicApiOriginForBackendUrl(params.backendUrl);
|
|
1239
|
+
const nativeCodexBaseUrl = `${proxyRoot}${CANONICAL_CODEX_NATIVE_PROXY_SUFFIX}`;
|
|
1092
1240
|
let next = existing;
|
|
1093
1241
|
next = removeManagedBlock(next, MANAGED_START, MANAGED_END);
|
|
1094
1242
|
next = removeProviderTable(next, DEFAULT_PROVIDER_ID);
|
|
1095
1243
|
next = upsertFirstKeyLine(next, "model_provider", `"${DEFAULT_PROVIDER_ID}"`);
|
|
1096
1244
|
next = upsertFirstKeyLine(next, "model", `"${params.model}"`);
|
|
1245
|
+
next = upsertFirstKeyLine(next, "chatgpt_base_url", `"${proxyRoot}"`);
|
|
1097
1246
|
const managedBlock = appendManagedBlock("", [
|
|
1098
1247
|
MANAGED_START,
|
|
1099
|
-
`chatgpt_base_url = "${proxyRoot}"`,
|
|
1100
1248
|
`[model_providers.${DEFAULT_PROVIDER_ID}]`,
|
|
1101
1249
|
'name = "OpenAI"',
|
|
1102
|
-
`base_url = "${
|
|
1250
|
+
`base_url = "${nativeCodexBaseUrl}"`,
|
|
1103
1251
|
'wire_api = "responses"',
|
|
1104
1252
|
"requires_openai_auth = true",
|
|
1105
1253
|
"supports_websockets = true",
|
|
@@ -1250,14 +1398,17 @@ async function writeAiderConfig(params) {
|
|
|
1250
1398
|
await promises_1.default.writeFile(AIDER_CONFIG_PATH, next, "utf8");
|
|
1251
1399
|
return AIDER_CONFIG_PATH;
|
|
1252
1400
|
}
|
|
1253
|
-
async function persistApiKeyEnv(
|
|
1401
|
+
async function persistApiKeyEnv(params) {
|
|
1254
1402
|
const envDir = node_path_1.default.dirname(ENV_FILE);
|
|
1255
1403
|
await promises_1.default.mkdir(envDir, { recursive: true });
|
|
1256
|
-
const
|
|
1404
|
+
const envLines = [
|
|
1257
1405
|
"# Generated by theclawbay setup",
|
|
1258
|
-
`export ${ENV_KEY_NAME}=${shellQuote(apiKey)}`,
|
|
1259
|
-
|
|
1260
|
-
|
|
1406
|
+
`export ${ENV_KEY_NAME}=${shellQuote(params.apiKey)}`,
|
|
1407
|
+
];
|
|
1408
|
+
if (params.claudeEnabled) {
|
|
1409
|
+
envLines.push("", "# Official Claude Code CLI", `export ${CLAUDE_ENV_API_KEY_NAME}=${shellQuote(params.apiKey)}`, `export ${CLAUDE_ENV_BASE_URL_NAME}=${shellQuote(anthropicCompatibleProxyUrl(params.backendUrl))}`);
|
|
1410
|
+
}
|
|
1411
|
+
const envContents = `${envLines.join("\n")}\n`;
|
|
1261
1412
|
await promises_1.default.writeFile(ENV_FILE, envContents, "utf8");
|
|
1262
1413
|
await promises_1.default.chmod(ENV_FILE, 0o600);
|
|
1263
1414
|
const sourceLine = `[ -f "$HOME/.config/theclawbay/env" ] && . "$HOME/.config/theclawbay/env"`;
|
|
@@ -1278,7 +1429,15 @@ async function persistApiKeyEnv(apiKey) {
|
|
|
1278
1429
|
await promises_1.default.writeFile(rcPath, withBlock, "utf8");
|
|
1279
1430
|
updated.push(rcPath);
|
|
1280
1431
|
}
|
|
1281
|
-
process.env[ENV_KEY_NAME] = apiKey;
|
|
1432
|
+
process.env[ENV_KEY_NAME] = params.apiKey;
|
|
1433
|
+
if (params.claudeEnabled) {
|
|
1434
|
+
process.env[CLAUDE_ENV_API_KEY_NAME] = params.apiKey;
|
|
1435
|
+
process.env[CLAUDE_ENV_BASE_URL_NAME] = anthropicCompatibleProxyUrl(params.backendUrl);
|
|
1436
|
+
}
|
|
1437
|
+
else {
|
|
1438
|
+
delete process.env[CLAUDE_ENV_API_KEY_NAME];
|
|
1439
|
+
delete process.env[CLAUDE_ENV_BASE_URL_NAME];
|
|
1440
|
+
}
|
|
1282
1441
|
return updated;
|
|
1283
1442
|
}
|
|
1284
1443
|
async function persistVsCodeServerEnvSource() {
|
|
@@ -1334,7 +1493,7 @@ function resolveOpenClawPrimaryModel(params) {
|
|
|
1334
1493
|
}
|
|
1335
1494
|
async function patchOpenClawConfigFile(params) {
|
|
1336
1495
|
const provider = {
|
|
1337
|
-
baseUrl:
|
|
1496
|
+
baseUrl: openAiCompatibleProxyUrl(params.backendUrl),
|
|
1338
1497
|
apiKey: params.apiKey,
|
|
1339
1498
|
api: "openai-responses",
|
|
1340
1499
|
models: buildOpenClawModels(params.models),
|
|
@@ -1647,7 +1806,7 @@ async function writeOpenCodeFamilyConfig(params) {
|
|
|
1647
1806
|
}
|
|
1648
1807
|
const managedOpenAiProvider = objectRecordOr(providerRoot[OPENAI_PROVIDER_ID], {});
|
|
1649
1808
|
const optionsRoot = objectRecordOr(managedOpenAiProvider.options, {});
|
|
1650
|
-
optionsRoot.baseURL =
|
|
1809
|
+
optionsRoot.baseURL = openAiCompatibleProxyUrl(params.backendUrl);
|
|
1651
1810
|
optionsRoot.apiKey = params.apiKey;
|
|
1652
1811
|
managedOpenAiProvider.options = optionsRoot;
|
|
1653
1812
|
managedOpenAiProvider.models = buildOpenCodeModelsObject(params.models);
|
|
@@ -1689,7 +1848,7 @@ function traePatchSnippet(params) {
|
|
|
1689
1848
|
const patchedModels = params.models.length
|
|
1690
1849
|
? params.models
|
|
1691
1850
|
: [{ id: params.model.trim() || DEFAULT_TRAE_MODEL, name: modelDisplayName(params.model.trim() || DEFAULT_TRAE_MODEL) }];
|
|
1692
|
-
const proxyBaseUrl =
|
|
1851
|
+
const proxyBaseUrl = openAiCompatibleProxyUrl(params.backendUrl);
|
|
1693
1852
|
return `async setOriginModelListMapAndCache(e,t=!0){let{userProfile:r}=this.credentialStore.getState(),i=r?.scope!==v9.BYTEDANCE,n={},o=${JSON.stringify(patchedModels)};Object.keys(e).forEach(t=>{let r=[...(e[t]||[])],s=r[0]&&"object"==typeof r[0]?r[0]:null;if([Mh.Builder,Mh.SoloCoder].includes(t)&&s){let a=o.map((e,l)=>{let o={...s};return o.id=${JSON.stringify(TRAE_PATCH_MARKER)}+"-"+t+"-"+e.id,o.name=e.id,o.display_name=e.name,o.provider="theclawbay",o.icon={dark:"https://theclawbay.com/favicon.ico",light:"https://theclawbay.com/favicon.ico"},o.ak=${JSON.stringify(params.apiKey)},o.base_url=${JSON.stringify(proxyBaseUrl)},o.is_custom_base_url=!0,o.custom_model_id=${JSON.stringify(TRAE_PATCH_MARKER)}+"-"+e.id,o.selectable=!0,o.status=!0,o.is_default=0===l,o.config_source=MK.Personal,o.model_type=s.model_type??"chat_model",o.builder=s.builder??null,o.client_connect=!0,o.multimodal=!1!==s.multimodal,o.tags=Array.isArray(s.tags)?s.tags:[],o.auth_type="number"==typeof s.auth_type?s.auth_type:0,o.region=s.region??null,o.max_turns=s.max_turns??{default:50,max:50},o.context_window_size=s.context_window_size??{max:[128000],default:64000},o.features=s.features??{memory:{enable:!1},cost:{enable:!1,data:{manual_usage:0}},multimodal:{enable:!0},context_windows:{enable:!0,data:{dev_context:64000,max_context:128000,max_context_list:[128000],dev_turns:50,max_turns:50}}},o.commercial_info=s.commercial_info??{manual_usage:0,info:""},o.saas_usage=s.saas_usage??{max:0,default:null},o});n[t]=a;return}!i&&[Mh.SoloCoder,Mh.UIBuilder].includes(t)?n[t]=r?.filter(e=>e.config_source!==MK.Personal)||[]:n[t]=r}),this._modelStore.actions.setOriginModelListMap(n),t&&this._modelStorageService.storeModelListMap(n),this._logService.info("[ModelService.setOriginModelListMapAndCache] officialModels",Object.entries(n).map(([e,t])=>({scene:e,models:(t||[]).map(e=>e.display_name||e.name)})))}`;
|
|
1694
1853
|
}
|
|
1695
1854
|
async function patchTraeBundle(params) {
|
|
@@ -1895,6 +2054,7 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
1895
2054
|
}
|
|
1896
2055
|
const progress = this.createProgressHandle(!debugOutput);
|
|
1897
2056
|
let resolved = null;
|
|
2057
|
+
let claudeAccess = null;
|
|
1898
2058
|
let updatedShellFiles = [];
|
|
1899
2059
|
let codexConfigPath = null;
|
|
1900
2060
|
let updatedVsCodeEnvFiles = [];
|
|
@@ -1918,6 +2078,8 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
1918
2078
|
progress.update("Resolving supported models");
|
|
1919
2079
|
resolved = await resolveModels(backendUrl, authCredential);
|
|
1920
2080
|
}
|
|
2081
|
+
progress.update("Checking Claude access");
|
|
2082
|
+
claudeAccess = await resolveClaudeAccess(backendUrl, authCredential);
|
|
1921
2083
|
progress.update("Saving shared machine config");
|
|
1922
2084
|
await (0, config_1.writeManagedConfig)({
|
|
1923
2085
|
backendUrl,
|
|
@@ -1926,7 +2088,11 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
1926
2088
|
deviceSessionId,
|
|
1927
2089
|
deviceLabel,
|
|
1928
2090
|
});
|
|
1929
|
-
updatedShellFiles = await persistApiKeyEnv(
|
|
2091
|
+
updatedShellFiles = await persistApiKeyEnv({
|
|
2092
|
+
apiKey: authCredential,
|
|
2093
|
+
backendUrl,
|
|
2094
|
+
claudeEnabled: claudeAccess.enabled,
|
|
2095
|
+
});
|
|
1930
2096
|
if (selectedSetupClients.has("codex")) {
|
|
1931
2097
|
progress.update("Configuring Codex");
|
|
1932
2098
|
codexConfigPath = await writeCodexConfig({
|
|
@@ -2052,6 +2218,12 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
2052
2218
|
const summaryNotes = new Set();
|
|
2053
2219
|
if (resolved?.note)
|
|
2054
2220
|
summaryNotes.add(resolved.note);
|
|
2221
|
+
if (claudeAccess?.enabled) {
|
|
2222
|
+
summaryNotes.add("Claude Code: exported ANTHROPIC_BASE_URL and ANTHROPIC_API_KEY for the official client.");
|
|
2223
|
+
}
|
|
2224
|
+
else if (claudeAccess?.note) {
|
|
2225
|
+
summaryNotes.add(claudeAccess.note);
|
|
2226
|
+
}
|
|
2055
2227
|
if (selectedSetupClients.has("trae")) {
|
|
2056
2228
|
summaryNotes.add("Trae uses an experimental patch. Restart Trae and pick a The Claw Bay model to test it.");
|
|
2057
2229
|
}
|
|
@@ -2101,6 +2273,12 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
2101
2273
|
}
|
|
2102
2274
|
if (resolved?.note)
|
|
2103
2275
|
this.log(resolved.note);
|
|
2276
|
+
if (claudeAccess?.enabled) {
|
|
2277
|
+
this.log(`- Claude Code base URL: ${anthropicCompatibleProxyUrl(backendUrl)}`);
|
|
2278
|
+
}
|
|
2279
|
+
else if (claudeAccess?.note) {
|
|
2280
|
+
this.log(`- Claude Code: ${claudeAccess.note}`);
|
|
2281
|
+
}
|
|
2104
2282
|
this.log(`- Local credential env: ${ENV_FILE}`);
|
|
2105
2283
|
this.log(`- Shell profiles updated: ${updatedShellFiles.join(", ")}`);
|
|
2106
2284
|
if (selectedSetupClients.has("codex")) {
|
|
@@ -152,8 +152,53 @@ async function exchangeBrowserSetupSession(params) {
|
|
|
152
152
|
deviceLabel: exchangeBody.deviceLabel,
|
|
153
153
|
};
|
|
154
154
|
}
|
|
155
|
-
|
|
156
|
-
|
|
155
|
+
function createSetupAbortError() {
|
|
156
|
+
const error = new Error("browser setup cancelled");
|
|
157
|
+
error.name = "AbortError";
|
|
158
|
+
return error;
|
|
159
|
+
}
|
|
160
|
+
async function delay(ms, signal) {
|
|
161
|
+
if (signal?.aborted)
|
|
162
|
+
throw createSetupAbortError();
|
|
163
|
+
await new Promise((resolve, reject) => {
|
|
164
|
+
const timer = setTimeout(() => {
|
|
165
|
+
cleanup();
|
|
166
|
+
resolve();
|
|
167
|
+
}, ms);
|
|
168
|
+
const onAbort = () => {
|
|
169
|
+
clearTimeout(timer);
|
|
170
|
+
cleanup();
|
|
171
|
+
reject(createSetupAbortError());
|
|
172
|
+
};
|
|
173
|
+
const cleanup = () => signal?.removeEventListener("abort", onAbort);
|
|
174
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
async function withTimeout(promise, timeoutMs, signal) {
|
|
178
|
+
if (signal?.aborted)
|
|
179
|
+
throw createSetupAbortError();
|
|
180
|
+
return await new Promise((resolve, reject) => {
|
|
181
|
+
const timer = setTimeout(() => {
|
|
182
|
+
cleanup();
|
|
183
|
+
reject(new Error("browser setup timed out"));
|
|
184
|
+
}, timeoutMs);
|
|
185
|
+
const onAbort = () => {
|
|
186
|
+
clearTimeout(timer);
|
|
187
|
+
cleanup();
|
|
188
|
+
reject(createSetupAbortError());
|
|
189
|
+
};
|
|
190
|
+
const cleanup = () => signal?.removeEventListener("abort", onAbort);
|
|
191
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
192
|
+
promise.then((value) => {
|
|
193
|
+
clearTimeout(timer);
|
|
194
|
+
cleanup();
|
|
195
|
+
resolve(value);
|
|
196
|
+
}, (error) => {
|
|
197
|
+
clearTimeout(timer);
|
|
198
|
+
cleanup();
|
|
199
|
+
reject(error);
|
|
200
|
+
});
|
|
201
|
+
});
|
|
157
202
|
}
|
|
158
203
|
async function waitForFirstSuccessfulExchange(promises) {
|
|
159
204
|
return await new Promise((resolve, reject) => {
|
|
@@ -190,6 +235,8 @@ async function createBrowserLinkedDeviceSession(params) {
|
|
|
190
235
|
const label = (params.deviceLabel ?? "").trim() || node_os_1.default.hostname() || "This device";
|
|
191
236
|
const state = node_crypto_1.default.randomUUID();
|
|
192
237
|
const callbackServer = await createLocalCallbackServer(state);
|
|
238
|
+
const exchangeAbortController = new AbortController();
|
|
239
|
+
const exchangeSignal = exchangeAbortController.signal;
|
|
193
240
|
try {
|
|
194
241
|
const startResponse = await fetch(`${params.backendUrl}/api/setup/device-session/start`, {
|
|
195
242
|
method: "POST",
|
|
@@ -215,12 +262,7 @@ async function createBrowserLinkedDeviceSession(params) {
|
|
|
215
262
|
let fallbackNoticeShown = false;
|
|
216
263
|
const timeoutError = new Error("browser setup timed out");
|
|
217
264
|
const waitForLocalCallbackExchange = async () => {
|
|
218
|
-
const callback = await
|
|
219
|
-
callbackServer.waitForCallback(),
|
|
220
|
-
new Promise((_, reject) => {
|
|
221
|
-
setTimeout(() => reject(timeoutError), BROWSER_SETUP_TIMEOUT_MS);
|
|
222
|
-
}),
|
|
223
|
-
]);
|
|
265
|
+
const callback = await withTimeout(callbackServer.waitForCallback(), BROWSER_SETUP_TIMEOUT_MS, exchangeSignal);
|
|
224
266
|
return exchangeBrowserSetupSession({
|
|
225
267
|
backendUrl: params.backendUrl,
|
|
226
268
|
sessionId: callback.sessionId,
|
|
@@ -230,6 +272,8 @@ async function createBrowserLinkedDeviceSession(params) {
|
|
|
230
272
|
};
|
|
231
273
|
const waitForPolledExchange = async () => {
|
|
232
274
|
while (Date.now() < deadlineMs) {
|
|
275
|
+
if (exchangeSignal.aborted)
|
|
276
|
+
throw createSetupAbortError();
|
|
233
277
|
try {
|
|
234
278
|
return await exchangeBrowserSetupSession({
|
|
235
279
|
backendUrl: params.backendUrl,
|
|
@@ -247,14 +291,21 @@ async function createBrowserLinkedDeviceSession(params) {
|
|
|
247
291
|
params.log("Waiting for browser approval. If the browser cannot reach localhost, setup will still finish here after you connect the device.");
|
|
248
292
|
fallbackNoticeShown = true;
|
|
249
293
|
}
|
|
250
|
-
await delay(BROWSER_SETUP_POLL_INTERVAL_MS);
|
|
294
|
+
await delay(BROWSER_SETUP_POLL_INTERVAL_MS, exchangeSignal);
|
|
251
295
|
}
|
|
252
296
|
throw timeoutError;
|
|
253
297
|
};
|
|
254
|
-
const linked = await
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
298
|
+
const linked = await (async () => {
|
|
299
|
+
try {
|
|
300
|
+
return await waitForFirstSuccessfulExchange([
|
|
301
|
+
waitForLocalCallbackExchange(),
|
|
302
|
+
waitForPolledExchange(),
|
|
303
|
+
]);
|
|
304
|
+
}
|
|
305
|
+
finally {
|
|
306
|
+
exchangeAbortController.abort();
|
|
307
|
+
}
|
|
308
|
+
})();
|
|
258
309
|
return {
|
|
259
310
|
credential: linked.credential,
|
|
260
311
|
authType: linked.authType,
|
package/package.json
CHANGED