tokenmix 0.4.13 → 0.4.14
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 -0
- package/dist/api/client.js +33 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -102,6 +102,14 @@ TOKENMIX_DEFAULT_MODEL=qwen-flash npx tokenmix aider
|
|
|
102
102
|
|
|
103
103
|
(`tokenmix claude` and `tokenmix codex` speak the Anthropic / Responses protocols, so they need a Claude-family model; the other agents accept any chat model.)
|
|
104
104
|
|
|
105
|
+
## Slow or restricted networks
|
|
106
|
+
|
|
107
|
+
Requests auto-retry transient network failures (so a single hiccup won't fail a command) and time out after 20s. On a slow, proxied, or firewalled connection, raise the timeout with `TOKENMIX_TIMEOUT_MS`:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
TOKENMIX_TIMEOUT_MS=60000 npx tokenmix login
|
|
111
|
+
```
|
|
112
|
+
|
|
105
113
|
## Configuration Location
|
|
106
114
|
|
|
107
115
|
Your TokenMix credentials are stored locally at:
|
package/dist/api/client.js
CHANGED
|
@@ -22,7 +22,28 @@ function handleAxios(err) {
|
|
|
22
22
|
e.message;
|
|
23
23
|
throw new ApiError(e.response.status, msg);
|
|
24
24
|
}
|
|
25
|
-
throw new ApiError(0, `Could not reach the TokenMix API (${e.message || 'network error'}). Check your internet connection.`);
|
|
25
|
+
throw new ApiError(0, `Could not reach the TokenMix API (${e.message || 'network error'}). Check your internet connection or proxy.`);
|
|
26
|
+
}
|
|
27
|
+
// Network resilience for flaky / slow / GFW'd connections: a generous default
|
|
28
|
+
// timeout (override with TOKENMIX_TIMEOUT_MS) and automatic retry of transient
|
|
29
|
+
// TRANSPORT failures (no HTTP response) with exponential backoff. HTTP errors
|
|
30
|
+
// (4xx/5xx) are real answers and are NEVER retried.
|
|
31
|
+
export const REQUEST_TIMEOUT_MS = Number(process.env.TOKENMIX_TIMEOUT_MS) || 20000;
|
|
32
|
+
const MAX_RETRIES = 2;
|
|
33
|
+
export async function withRetry(fn) {
|
|
34
|
+
let lastErr;
|
|
35
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
36
|
+
try {
|
|
37
|
+
return await fn();
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
if (err.response || attempt === MAX_RETRIES)
|
|
41
|
+
throw err;
|
|
42
|
+
lastErr = err;
|
|
43
|
+
await new Promise((r) => setTimeout(r, 300 * 2 ** attempt));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
throw lastErr;
|
|
26
47
|
}
|
|
27
48
|
// Public endpoint, no auth required.
|
|
28
49
|
// Note: backend pagination uses `per_page` (NOT `page_size`); max 500 (anything >500 falls back to 20).
|
|
@@ -30,10 +51,10 @@ function handleAxios(err) {
|
|
|
30
51
|
export async function listPublicModels(cfg) {
|
|
31
52
|
const c = cfg || (await readConfig());
|
|
32
53
|
try {
|
|
33
|
-
const r = await axios.get(`${apiBaseUrl(c)}/api/models`, {
|
|
54
|
+
const r = await withRetry(() => axios.get(`${apiBaseUrl(c)}/api/models`, {
|
|
34
55
|
params: { per_page: 500 },
|
|
35
|
-
timeout:
|
|
36
|
-
});
|
|
56
|
+
timeout: REQUEST_TIMEOUT_MS,
|
|
57
|
+
}));
|
|
37
58
|
const list = unwrap(r);
|
|
38
59
|
return Array.isArray(list) ? list : list?.list ?? [];
|
|
39
60
|
}
|
|
@@ -49,11 +70,11 @@ export async function verifyApiKey(apiKey, baseUrl) {
|
|
|
49
70
|
// then raises a clear "could not reach the API" ApiError, letting callers tell a
|
|
50
71
|
// network problem apart from a genuinely bad key.
|
|
51
72
|
try {
|
|
52
|
-
const r = await axios.get(`${baseUrl || DEFAULT_API_BASE}/v1/models`, {
|
|
73
|
+
const r = await withRetry(() => axios.get(`${baseUrl || DEFAULT_API_BASE}/v1/models`, {
|
|
53
74
|
headers: { Authorization: `Bearer ${apiKey}` },
|
|
54
|
-
timeout:
|
|
75
|
+
timeout: REQUEST_TIMEOUT_MS,
|
|
55
76
|
validateStatus: () => true,
|
|
56
|
-
});
|
|
77
|
+
}));
|
|
57
78
|
return r.status === 200;
|
|
58
79
|
}
|
|
59
80
|
catch (err) {
|
|
@@ -62,10 +83,10 @@ export async function verifyApiKey(apiKey, baseUrl) {
|
|
|
62
83
|
}
|
|
63
84
|
export async function fetchWallet(apiKey, baseUrl) {
|
|
64
85
|
try {
|
|
65
|
-
const r = await axios.get(`${baseUrl || DEFAULT_API_BASE}/v1/wallet`, {
|
|
86
|
+
const r = await withRetry(() => axios.get(`${baseUrl || DEFAULT_API_BASE}/v1/wallet`, {
|
|
66
87
|
headers: { Authorization: `Bearer ${apiKey}` },
|
|
67
|
-
timeout:
|
|
68
|
-
});
|
|
88
|
+
timeout: REQUEST_TIMEOUT_MS,
|
|
89
|
+
}));
|
|
69
90
|
return unwrap(r);
|
|
70
91
|
}
|
|
71
92
|
catch (err) {
|
|
@@ -81,7 +102,7 @@ export class DeviceFlowError extends Error {
|
|
|
81
102
|
}
|
|
82
103
|
export async function startDeviceAuthorization(baseUrl, clientName = 'tokenmix-cli') {
|
|
83
104
|
try {
|
|
84
|
-
const r = await axios.post(`${baseUrl}/api/auth/device/code`, { client_name: clientName }, { timeout:
|
|
105
|
+
const r = await withRetry(() => axios.post(`${baseUrl}/api/auth/device/code`, { client_name: clientName }, { timeout: REQUEST_TIMEOUT_MS }));
|
|
85
106
|
return unwrap(r);
|
|
86
107
|
}
|
|
87
108
|
catch (err) {
|
|
@@ -106,7 +127,7 @@ export async function pollDeviceToken(baseUrl, auth, onTick) {
|
|
|
106
127
|
onTick(Math.max(0, Math.round((deadline - Date.now()) / 1000)));
|
|
107
128
|
}
|
|
108
129
|
try {
|
|
109
|
-
const r = await axios.post(`${baseUrl}/api/auth/device/token`, { device_code: auth.device_code }, { timeout:
|
|
130
|
+
const r = await axios.post(`${baseUrl}/api/auth/device/token`, { device_code: auth.device_code }, { timeout: REQUEST_TIMEOUT_MS, validateStatus: () => true });
|
|
110
131
|
if (r.status === 200 && r.data?.code === 0) {
|
|
111
132
|
const body = r.data.data;
|
|
112
133
|
return {
|