tokenmix 0.4.13 → 0.4.15
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/dist/i18n/messages.js +12 -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 {
|
package/dist/i18n/messages.js
CHANGED
|
@@ -15,11 +15,11 @@ export const en = {
|
|
|
15
15
|
'welcome.loggedIn': "You're logged in — launch an agent:",
|
|
16
16
|
'welcome.more': 'More: `tokenmix --help` for all commands · `tokenmix balance` for your balance',
|
|
17
17
|
// login
|
|
18
|
-
'login.keyMustStart': 'API key should start with sk-tm
|
|
18
|
+
'login.keyMustStart': 'API key should start with sk-tm-. Get one at https://tokenmix.ai/dashboard/keys',
|
|
19
19
|
'login.verifying': 'Verifying API key against {baseUrl} ...',
|
|
20
20
|
'login.verifyFailed': 'API key verification failed. Double-check at https://tokenmix.ai/dashboard/keys',
|
|
21
21
|
'login.loggedInHint': 'Logged in. Try `tokenmix opencode` to launch your first agent.',
|
|
22
|
-
'login.noKey': 'No API key provided.',
|
|
22
|
+
'login.noKey': 'No API key provided. Get one at https://tokenmix.ai/dashboard/keys',
|
|
23
23
|
'login.requesting': 'Requesting device authorization ...',
|
|
24
24
|
'login.couldNotStart': 'Could not start device authorization: {msg}',
|
|
25
25
|
'login.fallbackPaste': 'Falling back to manual paste. Get an API key at https://tokenmix.ai/dashboard/keys',
|
|
@@ -191,11 +191,11 @@ export const zh = {
|
|
|
191
191
|
'welcome.loggedIn': '你已登录 —— 启动一个 agent:',
|
|
192
192
|
'welcome.more': '更多:`tokenmix --help` 查看全部命令 · `tokenmix balance` 查看余额',
|
|
193
193
|
// login
|
|
194
|
-
'login.keyMustStart': 'API 密钥应以 sk-tm-
|
|
194
|
+
'login.keyMustStart': 'API 密钥应以 sk-tm- 开头。可在 https://tokenmix.ai/dashboard/keys 获取',
|
|
195
195
|
'login.verifying': '正在通过 {baseUrl} 校验 API 密钥 ...',
|
|
196
196
|
'login.verifyFailed': 'API 密钥校验失败。请到 https://tokenmix.ai/dashboard/keys 核对',
|
|
197
197
|
'login.loggedInHint': '登录成功。试试 `tokenmix opencode` 启动你的第一个 agent。',
|
|
198
|
-
'login.noKey': '未提供 API
|
|
198
|
+
'login.noKey': '未提供 API 密钥。可在 https://tokenmix.ai/dashboard/keys 获取',
|
|
199
199
|
'login.requesting': '正在请求设备授权 ...',
|
|
200
200
|
'login.couldNotStart': '无法发起设备授权:{msg}',
|
|
201
201
|
'login.fallbackPaste': '改用手动粘贴。可在 https://tokenmix.ai/dashboard/keys 获取 API 密钥',
|
|
@@ -359,11 +359,11 @@ export const ja = {
|
|
|
359
359
|
'welcome.s3': '設定して起動',
|
|
360
360
|
'welcome.loggedIn': 'ログイン済みです —— agent を起動:',
|
|
361
361
|
'welcome.more': 'その他:`tokenmix --help` で全コマンド · `tokenmix balance` で残高',
|
|
362
|
-
'login.keyMustStart': 'API キーは sk-tm-
|
|
362
|
+
'login.keyMustStart': 'API キーは sk-tm- で始まる必要があります。https://tokenmix.ai/dashboard/keys で取得できます',
|
|
363
363
|
'login.verifying': '{baseUrl} で API キーを検証しています ...',
|
|
364
364
|
'login.verifyFailed': 'API キーの検証に失敗しました。https://tokenmix.ai/dashboard/keys で確認してください',
|
|
365
365
|
'login.loggedInHint': 'ログインしました。`tokenmix opencode` で最初の agent を起動してみましょう。',
|
|
366
|
-
'login.noKey': 'API キーが入力されていません。',
|
|
366
|
+
'login.noKey': 'API キーが入力されていません。https://tokenmix.ai/dashboard/keys で取得できます',
|
|
367
367
|
'login.requesting': 'デバイス認証をリクエストしています ...',
|
|
368
368
|
'login.couldNotStart': 'デバイス認証を開始できませんでした:{msg}',
|
|
369
369
|
'login.fallbackPaste': '手動貼り付けに切り替えます。API キーは https://tokenmix.ai/dashboard/keys で取得できます',
|
|
@@ -513,11 +513,11 @@ export const ko = {
|
|
|
513
513
|
'welcome.s3': '설정하고 실행',
|
|
514
514
|
'welcome.loggedIn': '로그인되었습니다 —— agent를 실행하세요:',
|
|
515
515
|
'welcome.more': '더 보기: `tokenmix --help` 모든 명령 · `tokenmix balance` 잔액',
|
|
516
|
-
'login.keyMustStart': 'API 키는 sk-tm- 로 시작해야
|
|
516
|
+
'login.keyMustStart': 'API 키는 sk-tm- 로 시작해야 합니다. https://tokenmix.ai/dashboard/keys 에서 발급받으세요',
|
|
517
517
|
'login.verifying': '{baseUrl} 에서 API 키를 확인하는 중 ...',
|
|
518
518
|
'login.verifyFailed': 'API 키 확인에 실패했습니다. https://tokenmix.ai/dashboard/keys 에서 확인하세요',
|
|
519
519
|
'login.loggedInHint': '로그인되었습니다. `tokenmix opencode` 로 첫 agent를 실행해 보세요.',
|
|
520
|
-
'login.noKey': 'API 키가 입력되지 않았습니다.',
|
|
520
|
+
'login.noKey': 'API 키가 입력되지 않았습니다. https://tokenmix.ai/dashboard/keys 에서 발급받으세요',
|
|
521
521
|
'login.requesting': '기기 인증을 요청하는 중 ...',
|
|
522
522
|
'login.couldNotStart': '기기 인증을 시작할 수 없습니다: {msg}',
|
|
523
523
|
'login.fallbackPaste': '수동 붙여넣기로 전환합니다. API 키는 https://tokenmix.ai/dashboard/keys 에서 발급받으세요',
|
|
@@ -667,11 +667,11 @@ export const es = {
|
|
|
667
667
|
'welcome.s3': 'configura y lanza uno',
|
|
668
668
|
'welcome.loggedIn': 'Has iniciado sesión: lanza un agente:',
|
|
669
669
|
'welcome.more': 'Más: `tokenmix --help` para todos los comandos · `tokenmix balance` para tu saldo',
|
|
670
|
-
'login.keyMustStart': 'La clave de API debe empezar por sk-tm
|
|
670
|
+
'login.keyMustStart': 'La clave de API debe empezar por sk-tm-. Consíguela en https://tokenmix.ai/dashboard/keys',
|
|
671
671
|
'login.verifying': 'Verificando la clave de API en {baseUrl} ...',
|
|
672
672
|
'login.verifyFailed': 'Falló la verificación de la clave de API. Compruébala en https://tokenmix.ai/dashboard/keys',
|
|
673
673
|
'login.loggedInHint': 'Sesión iniciada. Prueba `tokenmix opencode` para lanzar tu primer agent.',
|
|
674
|
-
'login.noKey': 'No se proporcionó ninguna clave de API.',
|
|
674
|
+
'login.noKey': 'No se proporcionó ninguna clave de API. Consíguela en https://tokenmix.ai/dashboard/keys',
|
|
675
675
|
'login.requesting': 'Solicitando autorización del dispositivo ...',
|
|
676
676
|
'login.couldNotStart': 'No se pudo iniciar la autorización del dispositivo: {msg}',
|
|
677
677
|
'login.fallbackPaste': 'Cambiando a pegado manual. Consigue una clave de API en https://tokenmix.ai/dashboard/keys',
|
|
@@ -821,11 +821,11 @@ export const fr = {
|
|
|
821
821
|
'welcome.s3': 'configurer et lancer un agent',
|
|
822
822
|
'welcome.loggedIn': 'Vous êtes connecté — lancez un agent :',
|
|
823
823
|
'welcome.more': 'Plus : `tokenmix --help` pour toutes les commandes · `tokenmix balance` pour votre solde',
|
|
824
|
-
'login.keyMustStart': 'La clé API doit commencer par sk-tm-',
|
|
824
|
+
'login.keyMustStart': 'La clé API doit commencer par sk-tm-. Obtenez-en une sur https://tokenmix.ai/dashboard/keys',
|
|
825
825
|
'login.verifying': 'Vérification de la clé API sur {baseUrl} ...',
|
|
826
826
|
'login.verifyFailed': 'Échec de la vérification de la clé API. Vérifiez sur https://tokenmix.ai/dashboard/keys',
|
|
827
827
|
'login.loggedInHint': 'Connecté. Essayez `tokenmix opencode` pour lancer votre premier agent.',
|
|
828
|
-
'login.noKey': 'Aucune clé API fournie.',
|
|
828
|
+
'login.noKey': 'Aucune clé API fournie. Obtenez-en une sur https://tokenmix.ai/dashboard/keys',
|
|
829
829
|
'login.requesting': 'Demande d’autorisation de l’appareil ...',
|
|
830
830
|
'login.couldNotStart': 'Impossible de démarrer l’autorisation de l’appareil : {msg}',
|
|
831
831
|
'login.fallbackPaste': 'Passage au collage manuel. Obtenez une clé API sur https://tokenmix.ai/dashboard/keys',
|