Skip to content

Commit 65d7eb9

Browse files
fix: remover dependencia qwen-code e corrigir bugs criticos (#5)
Corrige autenticacao que falhava sem qwen-code instalado. O plugin ja possui seu proprio OAuth Device Flow e nunca precisou do qwen-code - o problema eram bugs e codigo morto. Bugs corrigidos: - slow_down retornava null em vez de lancar erro (RFC 8628) - Bun.sleep nao funciona em Node.js (substituido por setTimeout) - refreshToken || '' tratava string vazia como falsy (trocado por ??) - REAUTH_HINT referenciava qwen-code auth login inexistente Codigo morto removido: - Fallback para ~/.qwen/oauth_creds.json (checkExistingCredentials) - loadCredentials, getValidCredentials, getOpenCodeAuthPath - generateState (nunca usada) - Tipos nao usados (QwenOAuthState, QwenModelId, ChatMessage, etc) - Arquivos mortos: plugin/utils.ts, plugin/client.ts - Dependencia open (nao importada por nenhum codigo ativo)
1 parent 539388b commit 65d7eb9

File tree

10 files changed

+33
-492
lines changed

10 files changed

+33
-492
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ node_modules/
22
dist/
33
*.log
44
.DS_Store
5+
package-lock.json

bun.lock

Lines changed: 0 additions & 26 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "opencode-qwencode-auth",
3-
"version": "1.2.1",
3+
"version": "1.3.0",
44
"description": "Qwen OAuth authentication plugin for OpenCode - Access Qwen AI models (Coder, Vision) with your qwen.ai account",
55
"module": "index.ts",
66
"type": "module",
@@ -32,9 +32,6 @@
3232
"bugs": {
3333
"url": "https://github.com/gustavodiasdev/opencode-qwencode-auth/issues"
3434
},
35-
"dependencies": {
36-
"open": "^10.1.0"
37-
},
3835
"devDependencies": {
3936
"@opencode-ai/plugin": "^1.1.48",
4037
"@types/node": "^22.0.0",

src/errors.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
*/
77

88
const REAUTH_HINT =
9-
'Execute "npx opencode-qwencode-auth" ou "qwen-code auth login" para re-autenticar.';
9+
'Execute "opencode auth login" e selecione "Qwen Code (qwen.ai OAuth)" para autenticar.';
1010

1111
// ============================================
1212
// Erro de Autenticação

src/index.ts

Lines changed: 15 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,27 @@
11
/**
22
* OpenCode Qwen Auth Plugin
33
*
4-
* Plugin de autenticação OAuth para Qwen, baseado no qwen-code.
5-
* Implementa Device Flow (RFC 8628) para autenticação.
4+
* Plugin de autenticacao OAuth para Qwen, baseado no qwen-code.
5+
* Implementa Device Flow (RFC 8628) para autenticacao.
66
*
7-
* Provider único: qwen-code portal.qwen.ai/v1
8-
* Modelos confirmados: qwen3-coder-plus, qwen3-coder-flash, coder-model, vision-model
7+
* Provider: qwen-code -> portal.qwen.ai/v1
8+
* Modelos: qwen3-coder-plus, qwen3-coder-flash, coder-model, vision-model
99
*/
1010

11-
import { existsSync } from 'node:fs';
1211
import { spawn } from 'node:child_process';
1312

1413
import { QWEN_PROVIDER_ID, QWEN_API_CONFIG, QWEN_MODELS } from './constants.js';
1514
import type { QwenCredentials } from './types.js';
16-
import {
17-
loadCredentials,
18-
saveCredentials,
19-
getCredentialsPath,
20-
isCredentialsExpired,
21-
} from './plugin/auth.js';
15+
import { saveCredentials } from './plugin/auth.js';
2216
import {
2317
generatePKCE,
2418
requestDeviceAuthorization,
2519
pollDeviceToken,
2620
tokenResponseToCredentials,
2721
refreshAccessToken,
22+
SlowDownError,
2823
} from './qwen/oauth.js';
2924
import { logTechnicalDetail } from './errors.js';
30-
export { QwenAuthError, QwenApiError } from './errors.js';
31-
export type { AuthErrorKind } from './errors.js';
3225

3326
// ============================================
3427
// Helpers
@@ -46,34 +39,20 @@ function openBrowser(url: string): void {
4639
}
4740
}
4841

49-
/** Verifica se existem credenciais válidas em ~/.qwen/oauth_creds.json */
50-
export function checkExistingCredentials(): QwenCredentials | null {
51-
const credPath = getCredentialsPath();
52-
if (existsSync(credPath)) {
53-
const creds = loadCredentials();
54-
if (creds && !isCredentialsExpired(creds)) {
55-
return creds;
56-
}
57-
}
58-
return null;
59-
}
60-
61-
/** Obtém um access token válido (com refresh se necessário) */
42+
/** Obtem um access token valido (com refresh se necessario) */
6243
async function getValidAccessToken(
6344
getAuth: () => Promise<{ type: string; access?: string; refresh?: string; expires?: number }>,
6445
): Promise<string | null> {
6546
const auth = await getAuth();
6647

67-
// Se não é OAuth, tentar carregar credenciais locais do qwen-code
6848
if (!auth || auth.type !== 'oauth') {
69-
const creds = checkExistingCredentials();
70-
return creds?.accessToken ?? null;
49+
return null;
7150
}
7251

7352
let accessToken = auth.access;
7453

75-
// Refresh se expirado (com margem de 30s)
76-
if (accessToken && auth.expires && Date.now() > auth.expires - 30000 && auth.refresh) {
54+
// Refresh se expirado (com margem de 60s)
55+
if (accessToken && auth.expires && Date.now() > auth.expires - 60_000 && auth.refresh) {
7756
try {
7857
const refreshed = await refreshAccessToken(auth.refresh);
7958
accessToken = refreshed.accessToken;
@@ -85,20 +64,6 @@ async function getValidAccessToken(
8564
}
8665
}
8766

88-
// Fallback para credenciais locais do qwen-code
89-
if (!accessToken) {
90-
const creds = checkExistingCredentials();
91-
if (creds) {
92-
accessToken = creds.accessToken;
93-
} else {
94-
console.warn(
95-
'[Qwen] Token expirado e sem credenciais alternativas. ' +
96-
'Execute "npx opencode-qwencode-auth" ou "qwen-code auth login" para re-autenticar.'
97-
);
98-
return null;
99-
}
100-
}
101-
10267
return accessToken ?? null;
10368
}
10469

@@ -146,15 +111,15 @@ export const QwenAuthPlugin = async (_input: unknown) => {
146111

147112
return {
148113
url: deviceAuth.verification_uri_complete,
149-
instructions: `Código: ${deviceAuth.user_code}`,
114+
instructions: `Codigo: ${deviceAuth.user_code}`,
150115
method: 'auto' as const,
151116
callback: async () => {
152117
const startTime = Date.now();
153118
const timeoutMs = deviceAuth.expires_in * 1000;
154119
let interval = 5000;
155120

156121
while (Date.now() - startTime < timeoutMs) {
157-
await Bun.sleep(interval + POLLING_MARGIN_MS);
122+
await new Promise(resolve => setTimeout(resolve, interval + POLLING_MARGIN_MS));
158123

159124
try {
160125
const tokenResponse = await pollDeviceToken(deviceAuth.device_code, verifier);
@@ -166,15 +131,14 @@ export const QwenAuthPlugin = async (_input: unknown) => {
166131
return {
167132
type: 'success' as const,
168133
access: credentials.accessToken,
169-
refresh: credentials.refreshToken || '',
134+
refresh: credentials.refreshToken ?? '',
170135
expires: credentials.expiryDate || Date.now() + 3600000,
171136
};
172137
}
173138
} catch (e) {
174-
const msg = e instanceof Error ? e.message : '';
175-
if (msg.includes('slow_down')) {
139+
if (e instanceof SlowDownError) {
176140
interval = Math.min(interval + 5000, 15000);
177-
} else if (!msg.includes('authorization_pending')) {
141+
} else if (!(e instanceof Error) || !e.message.includes('authorization_pending')) {
178142
return { type: 'failed' as const };
179143
}
180144
}

src/plugin/auth.ts

Lines changed: 2 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,23 @@
11
/**
22
* Qwen Credentials Management
33
*
4-
* Handles loading, saving, and validating credentials
4+
* Handles saving credentials to ~/.qwen/oauth_creds.json
55
*/
66

77
import { homedir } from 'node:os';
88
import { join } from 'node:path';
9-
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
9+
import { existsSync, writeFileSync, mkdirSync } from 'node:fs';
1010

1111
import type { QwenCredentials } from '../types.js';
12-
import { refreshAccessToken, isCredentialsExpired } from '../qwen/oauth.js';
13-
import { logTechnicalDetail } from '../errors.js';
1412

1513
/**
1614
* Get the path to the credentials file
17-
* Uses the same location as qwen-code for compatibility
1815
*/
1916
export function getCredentialsPath(): string {
2017
const homeDir = homedir();
2118
return join(homeDir, '.qwen', 'oauth_creds.json');
2219
}
2320

24-
/**
25-
* Get the OpenCode auth store path
26-
*/
27-
export function getOpenCodeAuthPath(): string {
28-
const homeDir = homedir();
29-
return join(homeDir, '.local', 'share', 'opencode', 'auth.json');
30-
}
31-
32-
/**
33-
* Load existing Qwen credentials if available
34-
* Supports qwen-code format with expiry_date and resource_url
35-
*/
36-
export function loadCredentials(): QwenCredentials | null {
37-
const credPath = getCredentialsPath();
38-
39-
if (!existsSync(credPath)) {
40-
return null;
41-
}
42-
43-
try {
44-
const data = readFileSync(credPath, 'utf-8');
45-
const parsed = JSON.parse(data);
46-
47-
// Handle qwen-code format and variations
48-
if (parsed.access_token || parsed.accessToken) {
49-
return {
50-
accessToken: parsed.access_token || parsed.accessToken,
51-
tokenType: parsed.token_type || parsed.tokenType || 'Bearer',
52-
refreshToken: parsed.refresh_token || parsed.refreshToken,
53-
resourceUrl: parsed.resource_url || parsed.resourceUrl,
54-
// qwen-code uses expiry_date, fallback to expires_at for compatibility
55-
expiryDate: parsed.expiry_date || parsed.expiresAt || parsed.expires_at,
56-
scope: parsed.scope,
57-
};
58-
}
59-
60-
return null;
61-
} catch (error) {
62-
logTechnicalDetail(`Erro ao carregar credenciais: ${error}`);
63-
return null;
64-
}
65-
}
66-
6721
/**
6822
* Save credentials to file in qwen-code compatible format
6923
*/
@@ -87,29 +41,3 @@ export function saveCredentials(credentials: QwenCredentials): void {
8741

8842
writeFileSync(credPath, JSON.stringify(data, null, 2));
8943
}
90-
91-
/**
92-
* Get valid credentials, refreshing if necessary
93-
*/
94-
export async function getValidCredentials(): Promise<QwenCredentials | null> {
95-
let credentials = loadCredentials();
96-
97-
if (!credentials) {
98-
return null;
99-
}
100-
101-
if (isCredentialsExpired(credentials) && credentials.refreshToken) {
102-
try {
103-
credentials = await refreshAccessToken(credentials.refreshToken);
104-
saveCredentials(credentials);
105-
} catch (error) {
106-
logTechnicalDetail(`Falha no refresh: ${error}`);
107-
return null;
108-
}
109-
}
110-
111-
return credentials;
112-
}
113-
114-
// Re-export isCredentialsExpired for convenience
115-
export { isCredentialsExpired } from '../qwen/oauth.js';

0 commit comments

Comments
 (0)