opencode-qwencode-auth 1.0.1 β 1.2.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 +16 -3
- package/README.pt-BR.md +16 -3
- package/package.json +5 -2
- package/src/constants.ts +41 -1
- package/src/errors.ts +75 -0
- package/src/index.ts +17 -3
- package/src/plugin/auth.ts +3 -2
- package/src/plugin/client.ts +6 -5
- package/src/qwen/oauth.ts +5 -4
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<img src="assets/screenshot.png" alt="OpenCode with Qwen Code" width="800">
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
|
-
**Authenticate OpenCode CLI with your qwen.ai account.** This plugin enables you to use
|
|
11
|
+
**Authenticate OpenCode CLI with your qwen.ai account.** This plugin enables you to use Qwen models (Coder, Max, Plus and more) with **2,000 free requests per day** - no API key or credit card required!
|
|
12
12
|
|
|
13
13
|
[π§π· Leia em PortuguΓͺs](./README.pt-BR.md)
|
|
14
14
|
|
|
@@ -69,22 +69,35 @@ Select **"Qwen Code (qwen.ai OAuth)"**
|
|
|
69
69
|
|
|
70
70
|
## π― Available Models
|
|
71
71
|
|
|
72
|
+
### Coding Models
|
|
73
|
+
|
|
72
74
|
| Model | Context | Max Output | Best For |
|
|
73
75
|
|-------|---------|------------|----------|
|
|
74
76
|
| `qwen3-coder-plus` | 1M tokens | 64K tokens | Complex coding tasks |
|
|
75
|
-
| `qwen3-coder-flash` | 1M tokens | 64K tokens | Fast responses |
|
|
77
|
+
| `qwen3-coder-flash` | 1M tokens | 64K tokens | Fast coding responses |
|
|
78
|
+
|
|
79
|
+
### General Purpose Models
|
|
80
|
+
|
|
81
|
+
| Model | Context | Max Output | Reasoning | Best For |
|
|
82
|
+
|-------|---------|------------|-----------|----------|
|
|
83
|
+
| `qwen3-max` | 256K tokens | 64K tokens | No | Flagship model, complex reasoning and tool use |
|
|
84
|
+
| `qwen-plus-latest` | 128K tokens | 16K tokens | Yes | Balanced quality-speed with thinking mode |
|
|
85
|
+
| `qwen3-235b-a22b` | 128K tokens | 32K tokens | Yes | Largest open-weight MoE with thinking mode |
|
|
86
|
+
| `qwen-flash` | 1M tokens | 8K tokens | No | Ultra-fast, low-cost simple tasks |
|
|
76
87
|
|
|
77
88
|
### Using a specific model
|
|
78
89
|
|
|
79
90
|
```bash
|
|
80
91
|
opencode --provider qwen-code --model qwen3-coder-plus
|
|
92
|
+
opencode --provider qwen-code --model qwen3-max
|
|
93
|
+
opencode --provider qwen-code --model qwen-plus-latest
|
|
81
94
|
```
|
|
82
95
|
|
|
83
96
|
## βοΈ How It Works
|
|
84
97
|
|
|
85
98
|
```
|
|
86
99
|
βββββββββββββββββββ ββββββββββββββββββββ βββββββββββββββββββ
|
|
87
|
-
β OpenCode CLI ββββββΆβ qwen.ai OAuth ββββββΆβ
|
|
100
|
+
β OpenCode CLI ββββββΆβ qwen.ai OAuth ββββββΆβ Qwen Models β
|
|
88
101
|
β βββββββ (Device Flow) βββββββ API β
|
|
89
102
|
βββββββββββββββββββ ββββββββββββββββββββ βββββββββββββββββββ
|
|
90
103
|
```
|
package/README.pt-BR.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<img src="assets/screenshot.png" alt="OpenCode com Qwen Code" width="800">
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
|
-
**Autentique o OpenCode CLI com sua conta qwen.ai.** Este plugin permite usar modelos
|
|
11
|
+
**Autentique o OpenCode CLI com sua conta qwen.ai.** Este plugin permite usar modelos Qwen (Coder, Max, Plus e mais) com **2.000 requisiΓ§Γ΅es gratuitas por dia** - sem API key ou cartΓ£o de crΓ©dito!
|
|
12
12
|
|
|
13
13
|
[πΊπΈ Read in English](./README.md)
|
|
14
14
|
|
|
@@ -69,22 +69,35 @@ Selecione **"Qwen Code (qwen.ai OAuth)"**
|
|
|
69
69
|
|
|
70
70
|
## π― Modelos DisponΓveis
|
|
71
71
|
|
|
72
|
+
### Modelos de CΓ³digo
|
|
73
|
+
|
|
72
74
|
| Modelo | Contexto | Max Output | Melhor Para |
|
|
73
75
|
|--------|----------|------------|-------------|
|
|
74
76
|
| `qwen3-coder-plus` | 1M tokens | 64K tokens | Tarefas complexas de cΓ³digo |
|
|
75
|
-
| `qwen3-coder-flash` | 1M tokens | 64K tokens | Respostas rΓ‘pidas |
|
|
77
|
+
| `qwen3-coder-flash` | 1M tokens | 64K tokens | Respostas rΓ‘pidas de cΓ³digo |
|
|
78
|
+
|
|
79
|
+
### Modelos de PropΓ³sito Geral
|
|
80
|
+
|
|
81
|
+
| Modelo | Contexto | Max Output | Reasoning | Melhor Para |
|
|
82
|
+
|--------|----------|------------|-----------|-------------|
|
|
83
|
+
| `qwen3-max` | 256K tokens | 64K tokens | NΓ£o | Modelo flagship, raciocΓnio complexo e tool use |
|
|
84
|
+
| `qwen-plus-latest` | 128K tokens | 16K tokens | Sim | EquilΓbrio qualidade-velocidade com thinking mode |
|
|
85
|
+
| `qwen3-235b-a22b` | 128K tokens | 32K tokens | Sim | Maior modelo open-weight MoE com thinking mode |
|
|
86
|
+
| `qwen-flash` | 1M tokens | 8K tokens | NΓ£o | Ultra-rΓ‘pido, baixo custo para tarefas simples |
|
|
76
87
|
|
|
77
88
|
### Usando um modelo especΓfico
|
|
78
89
|
|
|
79
90
|
```bash
|
|
80
91
|
opencode --provider qwen-code --model qwen3-coder-plus
|
|
92
|
+
opencode --provider qwen-code --model qwen3-max
|
|
93
|
+
opencode --provider qwen-code --model qwen-plus-latest
|
|
81
94
|
```
|
|
82
95
|
|
|
83
96
|
## βοΈ Como Funciona
|
|
84
97
|
|
|
85
98
|
```
|
|
86
99
|
βββββββββββββββββββ ββββββββββββββββββββ βββββββββββββββββββ
|
|
87
|
-
β OpenCode CLI ββββββΆβ qwen.ai OAuth ββββββΆβ
|
|
100
|
+
β OpenCode CLI ββββββΆβ qwen.ai OAuth ββββββΆβ Qwen Models β
|
|
88
101
|
β βββββββ (Device Flow) βββββββ API β
|
|
89
102
|
βββββββββββββββββββ ββββββββββββββββββββ βββββββββββββββββββ
|
|
90
103
|
```
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-qwencode-auth",
|
|
3
|
-
"version": "1.0
|
|
4
|
-
"description": "Qwen OAuth authentication plugin for OpenCode - Access
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "Qwen OAuth authentication plugin for OpenCode - Access Qwen AI models (Coder, Max, Plus and more) with your qwen.ai account",
|
|
5
5
|
"module": "index.ts",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"scripts": {
|
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
"qwen",
|
|
15
15
|
"qwen-code",
|
|
16
16
|
"qwen3-coder",
|
|
17
|
+
"qwen3-max",
|
|
18
|
+
"qwen-plus",
|
|
19
|
+
"qwen-flash",
|
|
17
20
|
"oauth",
|
|
18
21
|
"authentication",
|
|
19
22
|
"ai",
|
package/src/constants.ts
CHANGED
|
@@ -35,14 +35,16 @@ export const QWEN_API_CONFIG = {
|
|
|
35
35
|
export const CALLBACK_PORT = 14561;
|
|
36
36
|
|
|
37
37
|
// Available Qwen models through OAuth
|
|
38
|
-
// Baseado nos modelos disponΓveis no qwen-code
|
|
38
|
+
// Baseado nos modelos disponΓveis no qwen-code + modelos gerais via portal.qwen.ai
|
|
39
39
|
export const QWEN_MODELS = {
|
|
40
|
+
// --- Coding Models ---
|
|
40
41
|
'qwen3-coder-plus': {
|
|
41
42
|
id: 'qwen3-coder-plus',
|
|
42
43
|
name: 'Qwen3 Coder Plus',
|
|
43
44
|
contextWindow: 1048576, // 1M tokens
|
|
44
45
|
maxOutput: 65536, // 64K tokens
|
|
45
46
|
description: 'Most capable Qwen coding model with 1M context window',
|
|
47
|
+
reasoning: false,
|
|
46
48
|
cost: { input: 0, output: 0 }, // Free via OAuth
|
|
47
49
|
},
|
|
48
50
|
'qwen3-coder-flash': {
|
|
@@ -51,6 +53,44 @@ export const QWEN_MODELS = {
|
|
|
51
53
|
contextWindow: 1048576,
|
|
52
54
|
maxOutput: 65536,
|
|
53
55
|
description: 'Faster Qwen coding model for quick responses',
|
|
56
|
+
reasoning: false,
|
|
57
|
+
cost: { input: 0, output: 0 },
|
|
58
|
+
},
|
|
59
|
+
// --- General Purpose Models ---
|
|
60
|
+
'qwen3-max': {
|
|
61
|
+
id: 'qwen3-max',
|
|
62
|
+
name: 'Qwen3 Max',
|
|
63
|
+
contextWindow: 262144, // 256K tokens
|
|
64
|
+
maxOutput: 65536, // 64K tokens
|
|
65
|
+
description: 'Flagship ~1T parameter MoE model, best for complex reasoning and tool use',
|
|
66
|
+
reasoning: false,
|
|
67
|
+
cost: { input: 0, output: 0 },
|
|
68
|
+
},
|
|
69
|
+
'qwen-plus-latest': {
|
|
70
|
+
id: 'qwen-plus-latest',
|
|
71
|
+
name: 'Qwen Plus',
|
|
72
|
+
contextWindow: 131072, // 128K tokens
|
|
73
|
+
maxOutput: 16384, // 16K tokens
|
|
74
|
+
description: 'Balanced model with thinking mode, good quality-speed tradeoff',
|
|
75
|
+
reasoning: true,
|
|
76
|
+
cost: { input: 0, output: 0 },
|
|
77
|
+
},
|
|
78
|
+
'qwen3-235b-a22b': {
|
|
79
|
+
id: 'qwen3-235b-a22b',
|
|
80
|
+
name: 'Qwen3 235B-A22B',
|
|
81
|
+
contextWindow: 131072, // 128K tokens
|
|
82
|
+
maxOutput: 32768, // 32K tokens
|
|
83
|
+
description: 'Largest open-weight Qwen3 MoE model with thinking mode',
|
|
84
|
+
reasoning: true,
|
|
85
|
+
cost: { input: 0, output: 0 },
|
|
86
|
+
},
|
|
87
|
+
'qwen-flash': {
|
|
88
|
+
id: 'qwen-flash',
|
|
89
|
+
name: 'Qwen Flash',
|
|
90
|
+
contextWindow: 1048576, // 1M tokens
|
|
91
|
+
maxOutput: 8192, // 8K tokens
|
|
92
|
+
description: 'Ultra-fast and low-cost model for simple tasks',
|
|
93
|
+
reasoning: false,
|
|
54
94
|
cost: { input: 0, output: 0 },
|
|
55
95
|
},
|
|
56
96
|
} as const;
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Erros customizados do plugin Qwen Auth
|
|
3
|
+
*
|
|
4
|
+
* Fornece mensagens amigΓ‘veis para o usuΓ‘rio em vez de JSON bruto da API.
|
|
5
|
+
* Detalhes tΓ©cnicos sΓ³ aparecem com OPENCODE_QWEN_DEBUG=1.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const REAUTH_HINT =
|
|
9
|
+
'Execute "npx opencode-qwencode-auth" ou "qwen-code auth login" para re-autenticar.';
|
|
10
|
+
|
|
11
|
+
// ============================================
|
|
12
|
+
// Erro de AutenticaΓ§Γ£o
|
|
13
|
+
// ============================================
|
|
14
|
+
|
|
15
|
+
export type AuthErrorKind = 'token_expired' | 'refresh_failed' | 'auth_required';
|
|
16
|
+
|
|
17
|
+
const AUTH_MESSAGES: Record<AuthErrorKind, string> = {
|
|
18
|
+
token_expired: `[Qwen] Token expirado. ${REAUTH_HINT}`,
|
|
19
|
+
refresh_failed: `[Qwen] Falha ao renovar token. ${REAUTH_HINT}`,
|
|
20
|
+
auth_required: `[Qwen] Autenticacao necessaria. ${REAUTH_HINT}`,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export class QwenAuthError extends Error {
|
|
24
|
+
public readonly kind: AuthErrorKind;
|
|
25
|
+
public readonly technicalDetail?: string;
|
|
26
|
+
|
|
27
|
+
constructor(kind: AuthErrorKind, technicalDetail?: string) {
|
|
28
|
+
super(AUTH_MESSAGES[kind]);
|
|
29
|
+
this.name = 'QwenAuthError';
|
|
30
|
+
this.kind = kind;
|
|
31
|
+
this.technicalDetail = technicalDetail;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ============================================
|
|
36
|
+
// Erro de API
|
|
37
|
+
// ============================================
|
|
38
|
+
|
|
39
|
+
function classifyApiStatus(statusCode: number): string {
|
|
40
|
+
if (statusCode === 401 || statusCode === 403) {
|
|
41
|
+
return `[Qwen] Token invalido ou expirado. ${REAUTH_HINT}`;
|
|
42
|
+
}
|
|
43
|
+
if (statusCode === 429) {
|
|
44
|
+
return '[Qwen] Limite de requisicoes atingido. Aguarde alguns minutos antes de tentar novamente.';
|
|
45
|
+
}
|
|
46
|
+
if (statusCode >= 500) {
|
|
47
|
+
return `[Qwen] Servidor Qwen indisponivel (erro ${statusCode}). Tente novamente em alguns minutos.`;
|
|
48
|
+
}
|
|
49
|
+
return `[Qwen] Erro na API Qwen (${statusCode}). Verifique sua conexao e tente novamente.`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export class QwenApiError extends Error {
|
|
53
|
+
public readonly statusCode: number;
|
|
54
|
+
public readonly technicalDetail?: string;
|
|
55
|
+
|
|
56
|
+
constructor(statusCode: number, technicalDetail?: string) {
|
|
57
|
+
super(classifyApiStatus(statusCode));
|
|
58
|
+
this.name = 'QwenApiError';
|
|
59
|
+
this.statusCode = statusCode;
|
|
60
|
+
this.technicalDetail = technicalDetail;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ============================================
|
|
65
|
+
// Helper de log condicional
|
|
66
|
+
// ============================================
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Loga detalhes tΓ©cnicos apenas quando debug estΓ‘ ativo.
|
|
70
|
+
*/
|
|
71
|
+
export function logTechnicalDetail(detail: string): void {
|
|
72
|
+
if (process.env.OPENCODE_QWEN_DEBUG === '1') {
|
|
73
|
+
console.debug('[Qwen Debug]', detail);
|
|
74
|
+
}
|
|
75
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -23,6 +23,9 @@ import {
|
|
|
23
23
|
tokenResponseToCredentials,
|
|
24
24
|
refreshAccessToken,
|
|
25
25
|
} from './qwen/oauth.js';
|
|
26
|
+
import { logTechnicalDetail } from './errors.js';
|
|
27
|
+
export { QwenAuthError, QwenApiError } from './errors.js';
|
|
28
|
+
export type { AuthErrorKind } from './errors.js';
|
|
26
29
|
|
|
27
30
|
// ============================================
|
|
28
31
|
// Helpers
|
|
@@ -102,14 +105,25 @@ export const QwenAuthPlugin = async (_input: unknown) => {
|
|
|
102
105
|
accessToken = refreshed.accessToken;
|
|
103
106
|
saveCredentials(refreshed);
|
|
104
107
|
} catch (e) {
|
|
105
|
-
|
|
108
|
+
const detail = e instanceof Error ? e.message : String(e);
|
|
109
|
+
logTechnicalDetail(`Token refresh falhou: ${detail}`);
|
|
110
|
+
// NΓ£o continuar com token expirado - tentar fallback
|
|
111
|
+
accessToken = undefined;
|
|
106
112
|
}
|
|
107
113
|
}
|
|
108
114
|
|
|
109
115
|
// Fallback para credenciais do qwen-code
|
|
110
116
|
if (!accessToken) {
|
|
111
117
|
const creds = checkExistingCredentials();
|
|
112
|
-
if (creds)
|
|
118
|
+
if (creds) {
|
|
119
|
+
accessToken = creds.accessToken;
|
|
120
|
+
} else {
|
|
121
|
+
console.warn(
|
|
122
|
+
'[Qwen] Token expirado e sem credenciais alternativas. ' +
|
|
123
|
+
'Execute "npx opencode-qwencode-auth" ou "qwen-code auth login" para re-autenticar.'
|
|
124
|
+
);
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
113
127
|
}
|
|
114
128
|
|
|
115
129
|
if (!accessToken) return null;
|
|
@@ -200,7 +214,7 @@ export const QwenAuthPlugin = async (_input: unknown) => {
|
|
|
200
214
|
{
|
|
201
215
|
id: m.id,
|
|
202
216
|
name: m.name,
|
|
203
|
-
reasoning:
|
|
217
|
+
reasoning: m.reasoning,
|
|
204
218
|
limit: { context: m.contextWindow, output: m.maxOutput },
|
|
205
219
|
cost: m.cost,
|
|
206
220
|
modalities: { input: ['text'], output: ['text'] },
|
package/src/plugin/auth.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
|
10
10
|
|
|
11
11
|
import type { QwenCredentials } from '../types.js';
|
|
12
12
|
import { refreshAccessToken, isCredentialsExpired } from '../qwen/oauth.js';
|
|
13
|
+
import { logTechnicalDetail } from '../errors.js';
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Get the path to the credentials file
|
|
@@ -58,7 +59,7 @@ export function loadCredentials(): QwenCredentials | null {
|
|
|
58
59
|
|
|
59
60
|
return null;
|
|
60
61
|
} catch (error) {
|
|
61
|
-
|
|
62
|
+
logTechnicalDetail(`Erro ao carregar credenciais: ${error}`);
|
|
62
63
|
return null;
|
|
63
64
|
}
|
|
64
65
|
}
|
|
@@ -102,7 +103,7 @@ export async function getValidCredentials(): Promise<QwenCredentials | null> {
|
|
|
102
103
|
credentials = await refreshAccessToken(credentials.refreshToken);
|
|
103
104
|
saveCredentials(credentials);
|
|
104
105
|
} catch (error) {
|
|
105
|
-
|
|
106
|
+
logTechnicalDetail(`Falha no refresh: ${error}`);
|
|
106
107
|
return null;
|
|
107
108
|
}
|
|
108
109
|
}
|
package/src/plugin/client.ts
CHANGED
|
@@ -12,6 +12,7 @@ import type {
|
|
|
12
12
|
StreamChunk
|
|
13
13
|
} from '../types.js';
|
|
14
14
|
import { getValidCredentials, loadCredentials, isCredentialsExpired } from './auth.js';
|
|
15
|
+
import { QwenAuthError, QwenApiError } from '../errors.js';
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* QwenClient - Makes authenticated API calls to Qwen
|
|
@@ -66,7 +67,7 @@ export class QwenClient {
|
|
|
66
67
|
*/
|
|
67
68
|
private getAuthHeader(): string {
|
|
68
69
|
if (!this.credentials) {
|
|
69
|
-
throw new
|
|
70
|
+
throw new QwenAuthError('auth_required');
|
|
70
71
|
}
|
|
71
72
|
return `Bearer ${this.credentials.accessToken}`;
|
|
72
73
|
}
|
|
@@ -87,7 +88,7 @@ export class QwenClient {
|
|
|
87
88
|
if (!this.credentials) {
|
|
88
89
|
const initialized = await this.initialize();
|
|
89
90
|
if (!initialized) {
|
|
90
|
-
throw new
|
|
91
|
+
throw new QwenAuthError('auth_required');
|
|
91
92
|
}
|
|
92
93
|
}
|
|
93
94
|
|
|
@@ -106,7 +107,7 @@ export class QwenClient {
|
|
|
106
107
|
if (!response.ok) {
|
|
107
108
|
const errorText = await response.text();
|
|
108
109
|
this.log('API Error:', response.status, errorText);
|
|
109
|
-
throw new
|
|
110
|
+
throw new QwenApiError(response.status, errorText);
|
|
110
111
|
}
|
|
111
112
|
|
|
112
113
|
const data = await response.json();
|
|
@@ -122,7 +123,7 @@ export class QwenClient {
|
|
|
122
123
|
if (!this.credentials) {
|
|
123
124
|
const initialized = await this.initialize();
|
|
124
125
|
if (!initialized) {
|
|
125
|
-
throw new
|
|
126
|
+
throw new QwenAuthError('auth_required');
|
|
126
127
|
}
|
|
127
128
|
}
|
|
128
129
|
|
|
@@ -142,7 +143,7 @@ export class QwenClient {
|
|
|
142
143
|
if (!response.ok) {
|
|
143
144
|
const errorText = await response.text();
|
|
144
145
|
this.log('API Error:', response.status, errorText);
|
|
145
|
-
throw new
|
|
146
|
+
throw new QwenApiError(response.status, errorText);
|
|
146
147
|
}
|
|
147
148
|
|
|
148
149
|
const reader = response.body?.getReader();
|
package/src/qwen/oauth.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { randomBytes, createHash, randomUUID } from 'node:crypto';
|
|
|
9
9
|
|
|
10
10
|
import { QWEN_OAUTH_CONFIG } from '../constants.js';
|
|
11
11
|
import type { QwenCredentials } from '../types.js';
|
|
12
|
+
import { QwenAuthError, logTechnicalDetail } from '../errors.js';
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Device authorization response from Qwen OAuth
|
|
@@ -87,9 +88,8 @@ export async function requestDeviceAuthorization(
|
|
|
87
88
|
|
|
88
89
|
if (!response.ok) {
|
|
89
90
|
const errorData = await response.text();
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
);
|
|
91
|
+
logTechnicalDetail(`Device auth HTTP ${response.status}: ${errorData}`);
|
|
92
|
+
throw new QwenAuthError('auth_required', `HTTP ${response.status}: ${errorData}`);
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
const result = await response.json() as DeviceAuthorizationResponse;
|
|
@@ -193,7 +193,8 @@ export async function refreshAccessToken(refreshToken: string): Promise<QwenCred
|
|
|
193
193
|
|
|
194
194
|
if (!response.ok) {
|
|
195
195
|
const errorText = await response.text();
|
|
196
|
-
|
|
196
|
+
logTechnicalDetail(`Token refresh HTTP ${response.status}: ${errorText}`);
|
|
197
|
+
throw new QwenAuthError('refresh_failed', `HTTP ${response.status}: ${errorText}`);
|
|
197
198
|
}
|
|
198
199
|
|
|
199
200
|
const data = await response.json() as TokenResponse;
|