opencode-qwencode-auth 1.1.0 β†’ 1.2.1

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 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 Qwen3-Coder models with **2,000 free requests per day** - no API key or credit card required!
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 │────▢│ Qwen3-Coder β”‚
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 Qwen3-Coder com **2.000 requisiΓ§Γ΅es gratuitas por dia** - sem API key ou cartΓ£o de crΓ©dito!
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 │────▢│ Qwen3-Coder β”‚
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.1.0",
4
- "description": "Qwen OAuth authentication plugin for OpenCode - Access Qwen3-Coder models with your qwen.ai account",
3
+ "version": "1.2.1",
4
+ "description": "Qwen OAuth authentication plugin for OpenCode - Access Qwen AI models (Coder, Vision) with your qwen.ai account",
5
5
  "module": "index.ts",
6
6
  "type": "module",
7
7
  "scripts": {
@@ -14,6 +14,8 @@
14
14
  "qwen",
15
15
  "qwen-code",
16
16
  "qwen3-coder",
17
+ "qwen3-vl-plus",
18
+ "vision-model",
17
19
  "oauth",
18
20
  "authentication",
19
21
  "ai",
package/src/constants.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  * Based on qwen-code implementation
4
4
  */
5
5
 
6
- // Provider ID - cria provider separado para OAuth
6
+ // Provider ID
7
7
  export const QWEN_PROVIDER_ID = 'qwen-code';
8
8
 
9
9
  // OAuth Device Flow Endpoints (descobertos do qwen-code)
@@ -34,15 +34,17 @@ export const QWEN_API_CONFIG = {
34
34
  // OAuth callback port (para futuro Device Flow no plugin)
35
35
  export const CALLBACK_PORT = 14561;
36
36
 
37
- // Available Qwen models through OAuth
38
- // Baseado nos modelos disponΓ­veis no qwen-code
37
+ // Available Qwen models through OAuth (portal.qwen.ai)
38
+ // Testados e confirmados funcionando via token OAuth
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,27 @@ 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
+ // --- Alias Models (portal mapeia internamente) ---
60
+ 'coder-model': {
61
+ id: 'coder-model',
62
+ name: 'Qwen Coder (auto)',
63
+ contextWindow: 1048576,
64
+ maxOutput: 65536,
65
+ description: 'Auto-routed coding model (maps to qwen3-coder-plus)',
66
+ reasoning: false,
67
+ cost: { input: 0, output: 0 },
68
+ },
69
+ // --- Vision Model ---
70
+ 'vision-model': {
71
+ id: 'vision-model',
72
+ name: 'Qwen VL Plus (vision)',
73
+ contextWindow: 131072, // 128K tokens
74
+ maxOutput: 32768, // 32K tokens
75
+ description: 'Vision-language model (maps to qwen3-vl-plus), supports image input',
76
+ reasoning: false,
54
77
  cost: { input: 0, output: 0 },
55
78
  },
56
79
  } as const;
package/src/index.ts CHANGED
@@ -3,6 +3,9 @@
3
3
  *
4
4
  * Plugin de autenticaΓ§Γ£o OAuth para Qwen, baseado no qwen-code.
5
5
  * Implementa Device Flow (RFC 8628) para autenticaΓ§Γ£o.
6
+ *
7
+ * Provider ΓΊnico: qwen-code β†’ portal.qwen.ai/v1
8
+ * Modelos confirmados: qwen3-coder-plus, qwen3-coder-flash, coder-model, vision-model
6
9
  */
7
10
 
8
11
  import { existsSync } from 'node:fs';
@@ -31,14 +34,6 @@ export type { AuthErrorKind } from './errors.js';
31
34
  // Helpers
32
35
  // ============================================
33
36
 
34
- function getBaseUrl(resourceUrl?: string): string {
35
- if (!resourceUrl) return QWEN_API_CONFIG.baseUrl;
36
- if (resourceUrl.startsWith('http')) {
37
- return resourceUrl.endsWith('/v1') ? resourceUrl : `${resourceUrl}/v1`;
38
- }
39
- return `https://${resourceUrl}/v1`;
40
- }
41
-
42
37
  function openBrowser(url: string): void {
43
38
  try {
44
39
  const platform = process.platform;
@@ -51,6 +46,7 @@ function openBrowser(url: string): void {
51
46
  }
52
47
  }
53
48
 
49
+ /** Verifica se existem credenciais vΓ‘lidas em ~/.qwen/oauth_creds.json */
54
50
  export function checkExistingCredentials(): QwenCredentials | null {
55
51
  const credPath = getCredentialsPath();
56
52
  if (existsSync(credPath)) {
@@ -62,6 +58,50 @@ export function checkExistingCredentials(): QwenCredentials | null {
62
58
  return null;
63
59
  }
64
60
 
61
+ /** ObtΓ©m um access token vΓ‘lido (com refresh se necessΓ‘rio) */
62
+ async function getValidAccessToken(
63
+ getAuth: () => Promise<{ type: string; access?: string; refresh?: string; expires?: number }>,
64
+ ): Promise<string | null> {
65
+ const auth = await getAuth();
66
+
67
+ // Se nΓ£o Γ© OAuth, tentar carregar credenciais locais do qwen-code
68
+ if (!auth || auth.type !== 'oauth') {
69
+ const creds = checkExistingCredentials();
70
+ return creds?.accessToken ?? null;
71
+ }
72
+
73
+ let accessToken = auth.access;
74
+
75
+ // Refresh se expirado (com margem de 30s)
76
+ if (accessToken && auth.expires && Date.now() > auth.expires - 30000 && auth.refresh) {
77
+ try {
78
+ const refreshed = await refreshAccessToken(auth.refresh);
79
+ accessToken = refreshed.accessToken;
80
+ saveCredentials(refreshed);
81
+ } catch (e) {
82
+ const detail = e instanceof Error ? e.message : String(e);
83
+ logTechnicalDetail(`Token refresh falhou: ${detail}`);
84
+ accessToken = undefined;
85
+ }
86
+ }
87
+
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
+
102
+ return accessToken ?? null;
103
+ }
104
+
65
105
  // ============================================
66
106
  // Plugin Principal
67
107
  // ============================================
@@ -73,22 +113,8 @@ export const QwenAuthPlugin = async (_input: unknown) => {
73
113
 
74
114
  loader: async (
75
115
  getAuth: () => Promise<{ type: string; access?: string; refresh?: string; expires?: number }>,
76
- provider: { models?: Record<string, { cost?: { input: number; output: number } }> }
116
+ provider: { models?: Record<string, { cost?: { input: number; output: number } }> },
77
117
  ) => {
78
- const auth = await getAuth();
79
-
80
- // Se nΓ£o Γ© OAuth, tentar carregar credenciais do qwen-code
81
- if (!auth || auth.type !== 'oauth') {
82
- const creds = checkExistingCredentials();
83
- if (creds) {
84
- return {
85
- apiKey: creds.accessToken,
86
- baseURL: getBaseUrl(creds.resourceUrl),
87
- };
88
- }
89
- return null;
90
- }
91
-
92
118
  // Zerar custo dos modelos (gratuito via OAuth)
93
119
  if (provider?.models) {
94
120
  for (const model of Object.values(provider.models)) {
@@ -96,48 +122,18 @@ export const QwenAuthPlugin = async (_input: unknown) => {
96
122
  }
97
123
  }
98
124
 
99
- let accessToken = auth.access;
100
-
101
- // Refresh se expirado
102
- if (accessToken && auth.expires && Date.now() > auth.expires - 30000 && auth.refresh) {
103
- try {
104
- const refreshed = await refreshAccessToken(auth.refresh);
105
- accessToken = refreshed.accessToken;
106
- saveCredentials(refreshed);
107
- } catch (e) {
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;
112
- }
113
- }
114
-
115
- // Fallback para credenciais do qwen-code
116
- if (!accessToken) {
117
- const creds = checkExistingCredentials();
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
- }
127
- }
128
-
125
+ const accessToken = await getValidAccessToken(getAuth);
129
126
  if (!accessToken) return null;
130
127
 
131
- const creds = loadCredentials();
132
128
  return {
133
129
  apiKey: accessToken,
134
- baseURL: getBaseUrl(creds?.resourceUrl),
130
+ baseURL: QWEN_API_CONFIG.baseUrl,
135
131
  };
136
132
  },
137
133
 
138
134
  methods: [
139
135
  {
140
- type: 'oauth',
136
+ type: 'oauth' as const,
141
137
  label: 'Qwen Code (qwen.ai OAuth)',
142
138
  authorize: async () => {
143
139
  const { verifier, challenge } = generatePKCE();
@@ -214,7 +210,7 @@ export const QwenAuthPlugin = async (_input: unknown) => {
214
210
  {
215
211
  id: m.id,
216
212
  name: m.name,
217
- reasoning: false,
213
+ reasoning: m.reasoning,
218
214
  limit: { context: m.contextWindow, output: m.maxOutput },
219
215
  cost: m.cost,
220
216
  modalities: { input: ['text'], output: ['text'] },
@@ -30,7 +30,6 @@ export class QwenClient {
30
30
  */
31
31
  private getBaseUrl(): string {
32
32
  if (this.credentials?.resourceUrl) {
33
- // resourceUrl from qwen-code is just the host, need to add protocol and path
34
33
  const resourceUrl = this.credentials.resourceUrl;
35
34
  if (resourceUrl.startsWith('http')) {
36
35
  return resourceUrl.endsWith('/v1') ? resourceUrl : `${resourceUrl}/v1`;