theclawbay 0.3.45 → 0.3.46
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/setup.js +46 -9
- package/dist/lib/device-session-auth.js +64 -13
- package/package.json +1 -1
package/dist/commands/setup.js
CHANGED
|
@@ -913,6 +913,14 @@ async function resolveDeviceLabel(params) {
|
|
|
913
913
|
rl.close();
|
|
914
914
|
}
|
|
915
915
|
}
|
|
916
|
+
function summarizeModelFetchFailure(detail) {
|
|
917
|
+
const normalized = detail.replace(/\s+/g, " ").trim();
|
|
918
|
+
if (!normalized)
|
|
919
|
+
return "unknown error";
|
|
920
|
+
if (normalized.length <= 140)
|
|
921
|
+
return normalized;
|
|
922
|
+
return `${normalized.slice(0, 137).trimEnd()}...`;
|
|
923
|
+
}
|
|
916
924
|
async function fetchBackendModelIds(backendUrl, apiKey) {
|
|
917
925
|
const url = `${trimTrailingSlash(backendUrl)}/api/codex-auth/v1/proxy/v1/models`;
|
|
918
926
|
try {
|
|
@@ -922,18 +930,45 @@ async function fetchBackendModelIds(backendUrl, apiKey) {
|
|
|
922
930
|
Accept: "application/json",
|
|
923
931
|
"User-Agent": CLI_HTTP_USER_AGENT,
|
|
924
932
|
},
|
|
925
|
-
signal: AbortSignal.timeout(
|
|
933
|
+
signal: AbortSignal.timeout(10000),
|
|
926
934
|
});
|
|
927
|
-
if (!response.ok)
|
|
928
|
-
|
|
935
|
+
if (!response.ok) {
|
|
936
|
+
const responseText = await response.text().catch(() => "");
|
|
937
|
+
let detail = responseText.trim();
|
|
938
|
+
try {
|
|
939
|
+
const parsed = JSON.parse(responseText);
|
|
940
|
+
if (typeof parsed.error === "string" && parsed.error.trim()) {
|
|
941
|
+
detail = parsed.error.trim();
|
|
942
|
+
}
|
|
943
|
+
else if (typeof parsed.code === "string" && parsed.code.trim()) {
|
|
944
|
+
detail = parsed.code.trim();
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
catch {
|
|
948
|
+
// Keep the raw response text when the body is not JSON.
|
|
949
|
+
}
|
|
950
|
+
return {
|
|
951
|
+
ids: null,
|
|
952
|
+
failure: `HTTP ${response.status}${detail ? `: ${summarizeModelFetchFailure(detail)}` : ""}`,
|
|
953
|
+
};
|
|
954
|
+
}
|
|
929
955
|
const body = (await response.json());
|
|
930
956
|
const ids = (body.data ?? [])
|
|
931
957
|
.map((entry) => (typeof entry.id === "string" ? entry.id.trim() : ""))
|
|
932
958
|
.filter((id) => id.length > 0);
|
|
933
|
-
|
|
959
|
+
if (!ids.length) {
|
|
960
|
+
return {
|
|
961
|
+
ids: null,
|
|
962
|
+
failure: "backend returned an empty model list",
|
|
963
|
+
};
|
|
964
|
+
}
|
|
965
|
+
return { ids };
|
|
934
966
|
}
|
|
935
|
-
catch {
|
|
936
|
-
return
|
|
967
|
+
catch (error) {
|
|
968
|
+
return {
|
|
969
|
+
ids: null,
|
|
970
|
+
failure: summarizeModelFetchFailure(error instanceof Error ? error.message : String(error)),
|
|
971
|
+
};
|
|
937
972
|
}
|
|
938
973
|
}
|
|
939
974
|
function kiloStorageCandidates() {
|
|
@@ -1042,7 +1077,7 @@ async function detectZoClient() {
|
|
|
1042
1077
|
return false;
|
|
1043
1078
|
}
|
|
1044
1079
|
async function resolveModels(backendUrl, apiKey) {
|
|
1045
|
-
const ids = await fetchBackendModelIds(backendUrl, apiKey);
|
|
1080
|
+
const { ids, failure } = await fetchBackendModelIds(backendUrl, apiKey);
|
|
1046
1081
|
const available = new Set(ids ?? []);
|
|
1047
1082
|
let selected = DEFAULT_CODEX_MODEL;
|
|
1048
1083
|
let note;
|
|
@@ -1057,7 +1092,9 @@ async function resolveModels(backendUrl, apiKey) {
|
|
|
1057
1092
|
note = "No preferred Codex model advertised by backend; selected first available model.";
|
|
1058
1093
|
}
|
|
1059
1094
|
else if (!ids) {
|
|
1060
|
-
note =
|
|
1095
|
+
note = failure
|
|
1096
|
+
? `Unable to query backend model list (${failure}); defaulted to ${DEFAULT_CODEX_MODEL}.`
|
|
1097
|
+
: `Unable to query backend model list; defaulted to ${DEFAULT_CODEX_MODEL}.`;
|
|
1061
1098
|
}
|
|
1062
1099
|
const unique = [];
|
|
1063
1100
|
const pushUnique = (modelId) => {
|
|
@@ -1094,9 +1131,9 @@ async function writeCodexConfig(params) {
|
|
|
1094
1131
|
next = removeProviderTable(next, DEFAULT_PROVIDER_ID);
|
|
1095
1132
|
next = upsertFirstKeyLine(next, "model_provider", `"${DEFAULT_PROVIDER_ID}"`);
|
|
1096
1133
|
next = upsertFirstKeyLine(next, "model", `"${params.model}"`);
|
|
1134
|
+
next = upsertFirstKeyLine(next, "chatgpt_base_url", `"${proxyRoot}"`);
|
|
1097
1135
|
const managedBlock = appendManagedBlock("", [
|
|
1098
1136
|
MANAGED_START,
|
|
1099
|
-
`chatgpt_base_url = "${proxyRoot}"`,
|
|
1100
1137
|
`[model_providers.${DEFAULT_PROVIDER_ID}]`,
|
|
1101
1138
|
'name = "OpenAI"',
|
|
1102
1139
|
`base_url = "${proxyRoot}/backend-api/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