opencode-qwen-oauth 2.2.0 → 2.3.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
@@ -15,14 +15,15 @@ Qwen OAuth authentication plugin for [OpenCode](https://opencode.ai) - authentic
15
15
 
16
16
  - 🔐 **OAuth Device Flow** - PKCE-secured authentication, works in headless/CI environments
17
17
  - 🔄 **Automatic Token Refresh** - Tokens are refreshed before expiry
18
+ - 💾 **Persistent Credentials** - Tokens saved to `~/.qwen/oauth_creds.json` for persistence across sessions
18
19
  - 🌐 **Auto Browser Open** - Automatically opens browser for authentication
19
20
  - 📝 **File Logging** - All OAuth activity logged to `~/.config/opencode/logs/qwen-oauth.log`
20
- - 🐛 **Debug Mode** - Enable verbose output with `QWEN_OAUTH_DEBUG=true`
21
21
  - 🚀 **Easy Install** - One-command installation with CLI tool
22
- - 🎯 **Custom Headers** - Automatically adds Qwen-specific headers to API requests
22
+ - 🎯 **Custom Headers** - Automatically adds Qwen-specific headers (X-DashScope-*) to API requests
23
23
  - ⚙️ **Optimized Parameters** - Pre-configured temperature and topP settings for Qwen models
24
24
  - 🌍 **Environment Variables** - Exposes Qwen credentials to shell environments
25
25
  - 📊 **Event Monitoring** - Tracks authentication and session events for debugging
26
+ - ⏱️ **Request Throttling** - Built-in rate limiting to avoid 429 errors
26
27
 
27
28
  ## Quick Start
28
29
 
@@ -58,15 +59,15 @@ Select **"Qwen Code (qwen.ai OAuth)"** and follow the device flow instructions.
58
59
  ### Use Qwen Models
59
60
 
60
61
  ```
61
- /model qwen/qwen3-coder-plus
62
+ /model qwen/coder-model
62
63
  ```
63
64
 
64
65
  ## Models
65
66
 
66
- | Model | Context | Features |
67
- |-------|---------|----------|
68
- | `qwen3-coder-plus` | 1M tokens | Optimized for coding |
69
- | `qwen3-vl-plus` | 256K tokens | Vision + language |
67
+ | Model | Context | Output | Features |
68
+ |-------|---------|--------|----------|
69
+ | `coder-model` | 1M tokens | 64K | Optimized for coding |
70
+ | `vision-model` | 128K tokens | 32K | Vision + language |
70
71
 
71
72
  ## Configuration
72
73
 
@@ -106,13 +107,17 @@ If you prefer manual setup, add to `.opencode/opencode.json`:
106
107
  "baseURL": "https://portal.qwen.ai/v1"
107
108
  },
108
109
  "models": {
109
- "qwen3-coder-plus": {
110
- "id": "qwen3-coder-plus",
111
- "name": "Qwen3 Coder Plus"
110
+ "coder-model": {
111
+ "id": "coder-model",
112
+ "name": "Qwen Coder",
113
+ "limit": { "context": 1048576, "output": 65536 },
114
+ "modalities": { "input": ["text"], "output": ["text"] }
112
115
  },
113
- "qwen3-vl-plus": {
114
- "id": "qwen3-vl-plus",
115
- "name": "Qwen3 VL Plus",
116
+ "vision-model": {
117
+ "id": "vision-model",
118
+ "name": "Qwen Vision",
119
+ "limit": { "context": 131072, "output": 32768 },
120
+ "modalities": { "input": ["text", "image"], "output": ["text"] },
116
121
  "attachment": true
117
122
  }
118
123
  }
@@ -134,14 +139,43 @@ npx opencode-qwen-oauth uninstall
134
139
  npx opencode-qwen-oauth --help
135
140
  ```
136
141
 
142
+ ## Diagnostics
143
+
144
+ Test if the Qwen OAuth endpoints are accessible:
145
+
146
+ ```bash
147
+ npm run diagnose
148
+ ```
149
+
150
+ This will check:
151
+ - ✓ OAuth base URL accessibility
152
+ - ✓ Device code endpoint functionality
153
+ - ✓ API endpoint availability
154
+
155
+ Example output:
156
+ ```
157
+ [Base URL] https://chat.qwen.ai
158
+ Status: ✓ 200
159
+
160
+ [Device Code] https://chat.qwen.ai/api/v1/oauth2/device/code
161
+ Status: ✓ 200
162
+
163
+ [API Endpoints] Testing /chat/completions...
164
+ ⚠ https://portal.qwen.ai/v1
165
+ Status: 401 (endpoint exists, requires auth)
166
+ ```
167
+
137
168
  ## Troubleshooting
138
169
 
139
170
  ### "Device code expired"
140
- Complete the browser login within 5 minutes of starting `/connect`.
171
+ Complete the browser login within 15 minutes of starting `/connect`.
141
172
 
142
173
  ### "invalid_grant" error
143
174
  Your refresh token has expired. Run `/connect` to re-authenticate.
144
175
 
176
+ ### "Quota exceeded" error
177
+ Your free tier limit has been reached. Wait for quota reset or upgrade your account at https://chat.qwen.ai
178
+
145
179
  ### Provider not showing in /connect
146
180
  Use the CLI directly:
147
181
  ```bash
@@ -167,6 +201,14 @@ sudo dnf install xdg-utils
167
201
  sudo pacman -S xdg-utils
168
202
  ```
169
203
 
204
+ ### Credentials File
205
+ Credentials are saved to `~/.qwen/oauth_creds.json` (compatible with qwen-code CLI):
206
+ ```
207
+ ~/.qwen/oauth_creds.json
208
+ ```
209
+
210
+ This allows sharing authentication between OpenCode plugin and qwen-code CLI.
211
+
170
212
  ### Check logs
171
213
  ```bash
172
214
  cat ~/.config/opencode/logs/qwen-oauth.log
@@ -182,12 +224,31 @@ This plugin implements OAuth 2.0 Device Flow (RFC 8628) with PKCE:
182
224
  4. **Token Storage** - Tokens are stored in OpenCode's auth system
183
225
  5. **Auto Refresh** - Access tokens are refreshed before expiry
184
226
 
185
- ## Security
227
+ ## Security & Important Notes
228
+
229
+ ### Security Features
230
+ - ✅ Uses PKCE (RFC 7636) for enhanced security
231
+ - ✅ No client secret required (safer for public clients)
232
+ - ✅ Tokens stored in OpenCode's auth system and `~/.qwen/oauth_creds.json` for persistence
233
+ - ✅ All OAuth activity logged for auditing
234
+ - ✅ Sensitive data sanitized in logs
235
+
236
+ ### Implementation Notes
237
+
238
+ ⚠️ **Important**: This plugin uses OAuth endpoints that appear to be part of Qwen's web interface (`chat.qwen.ai`). While the implementation follows standard OAuth 2.0 specifications (RFC 8628 Device Flow + RFC 7636 PKCE), these endpoints are not officially documented in Qwen's public API documentation.
239
+
240
+ **What this means:**
241
+ - The OAuth flow works correctly and follows industry standards
242
+ - Endpoints are actively maintained and functional
243
+ - Future changes to Qwen's authentication system may require plugin updates
244
+
245
+ **Verified Working:**
246
+ - ✅ OAuth Device Flow: `https://chat.qwen.ai/api/v1/oauth2/*`
247
+ - ✅ API Endpoint: `https://portal.qwen.ai/v1/chat/completions`
248
+ - ✅ Token Refresh: Automatic refresh before expiration
249
+ - ✅ OpenAI-Compatible: Uses standard OpenAI API format
186
250
 
187
- - Uses PKCE (RFC 7636) for enhanced security
188
- - No client secret required
189
- - Tokens stored in OpenCode's secure auth storage
190
- - All OAuth activity logged for auditing
251
+ Run `npm run diagnose` to verify endpoint availability at any time.
191
252
 
192
253
  ## Development
193
254
 
@@ -226,8 +287,10 @@ Monitors session events (creation, errors) for debugging and logging.
226
287
 
227
288
  #### `chat.headers` Hook
228
289
  Injects custom headers for Qwen API requests:
229
- - `X-Qwen-Client: OpenCode`
230
- - `X-Qwen-Plugin-Version: 1.1.0`
290
+ - `User-Agent: QwenCode/0.10.3 (platform)`
291
+ - `X-DashScope-CacheControl: enable`
292
+ - `X-DashScope-UserAgent: QwenCode/0.10.3 (platform)`
293
+ - `X-DashScope-AuthType: qwen-oauth`
231
294
 
232
295
  #### `chat.params` Hook
233
296
  Optimizes model parameters for Qwen:
@@ -246,9 +309,12 @@ opencode-qwen-oauth/
246
309
  ├── src/ # TypeScript source files
247
310
  │ ├── index.ts # Main plugin with hooks
248
311
  │ ├── oauth.ts # OAuth device flow logic
312
+ │ ├── credentials.ts # Credential storage (~/.qwen/)
249
313
  │ ├── pkce.ts # PKCE implementation
250
314
  │ ├── browser.ts # Browser opening utility
251
315
  │ ├── logger.ts # Logging utilities
316
+ │ ├── request-queue.ts # Rate limiting
317
+ │ ├── diagnostic.ts # Diagnostics CLI
252
318
  │ └── constants.ts # API constants
253
319
  ├── bin/ # CLI scripts
254
320
  │ └── install.js # Installer script
package/bin/install.js CHANGED
@@ -105,13 +105,17 @@ function install() {
105
105
  baseURL: "https://portal.qwen.ai/v1",
106
106
  },
107
107
  models: {
108
- "qwen3-coder-plus": {
109
- id: "qwen3-coder-plus",
110
- name: "Qwen3 Coder Plus",
108
+ "coder-model": {
109
+ id: "coder-model",
110
+ name: "Qwen Coder",
111
+ limit: { context: 1048576, output: 65536 },
112
+ modalities: { input: ["text"], output: ["text"] },
111
113
  },
112
- "qwen3-vl-plus": {
113
- id: "qwen3-vl-plus",
114
- name: "Qwen3 VL Plus",
114
+ "vision-model": {
115
+ id: "vision-model",
116
+ name: "Qwen Vision",
117
+ limit: { context: 131072, output: 32768 },
118
+ modalities: { input: ["text", "image"], output: ["text"] },
115
119
  attachment: true,
116
120
  },
117
121
  },
@@ -144,7 +148,7 @@ function install() {
144
148
 
145
149
  opencodePackage.dependencies = opencodePackage.dependencies || {};
146
150
  if (!opencodePackage.dependencies["opencode-qwen-oauth"]) {
147
- opencodePackage.dependencies["opencode-qwen-oauth"] = "^1.0.0";
151
+ opencodePackage.dependencies["opencode-qwen-oauth"] = "^2.3.1";
148
152
  log("Added 'opencode-qwen-oauth' to .opencode/package.json dependencies");
149
153
  }
150
154
 
@@ -159,12 +163,19 @@ function install() {
159
163
  process.exit(1);
160
164
  }
161
165
 
162
- log("Installation complete!");
166
+ log("Installation complete!");
167
+ log("");
163
168
  log("Next steps:");
164
- log("1. Run: opencode");
165
- log("2. Connect: /connect (select 'Qwen Code (qwen.ai OAuth)')");
166
- log("3. Use model: /model qwen/qwen3-coder-plus");
167
- log("Debug mode: QWEN_OAUTH_DEBUG=true opencode");
169
+ log(" 1. Run: opencode");
170
+ log(" 2. Connect: /connect (select 'Qwen Code (qwen.ai OAuth)')");
171
+ log(" 3. Use model: /model qwen/coder-model");
172
+ log(" Vision model: /model qwen/vision-model");
173
+ log("");
174
+ log("Advanced:");
175
+ log(" • Run diagnostics: npm run diagnose");
176
+ log(" • View logs: tail -f ~/.config/opencode/logs/qwen-oauth.log");
177
+ log(" • Credentials saved to: ~/.qwen/oauth_creds.json");
178
+ log("");
168
179
  }
169
180
 
170
181
  // ============================================
@@ -239,9 +250,14 @@ Usage:
239
250
  After installation:
240
251
  1. Run: opencode
241
252
  2. Connect: /connect (select 'Qwen Code (qwen.ai OAuth)')
242
- 3. Use model: /model qwen/qwen3-coder-plus
253
+ 3. Use model: /model qwen/coder-model
243
254
 
244
- Debug mode: QWEN_OAUTH_DEBUG=true opencode
255
+ Models:
256
+ - coder-model: Qwen Coder (1M context, 64K output)
257
+ - vision-model: Qwen Vision (128K context, 32K output, supports images)
258
+
259
+ Logs:
260
+ - tail -f ~/.config/opencode/logs/qwen-oauth.log
245
261
  `);
246
262
  } else {
247
263
  error(`Unknown command: ${command}`);
@@ -0,0 +1,20 @@
1
+ /**
2
+ * API Key Exchange for Qwen
3
+ * Attempts to exchange OAuth token for API key
4
+ */
5
+ interface ApiKeyResponse {
6
+ success: boolean;
7
+ api_key?: string;
8
+ error?: string;
9
+ }
10
+ /**
11
+ * Try to get API key from OAuth token
12
+ * This is speculative - Qwen may require OAuth token → API key exchange
13
+ */
14
+ export declare function tryGetApiKey(oauthToken: string): Promise<ApiKeyResponse>;
15
+ /**
16
+ * Check if we need to use OAuth token directly or exchange for API key
17
+ */
18
+ export declare function validateTokenWithApi(token: string, apiBaseUrl: string): Promise<boolean>;
19
+ export {};
20
+ //# sourceMappingURL=api-key-exchange.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-key-exchange.d.ts","sourceRoot":"","sources":["../src/api-key-exchange.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,UAAU,cAAc;IACtB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAaD;;;GAGG;AACH,wBAAsB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAiD9E;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CA2B9F"}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * API Key Exchange for Qwen
3
+ * Attempts to exchange OAuth token for API key
4
+ */
5
+ import { debugLog, warnLog, infoLog } from "./logger.js";
6
+ import { QWEN_OAUTH_BASE_URL } from "./constants.js";
7
+ /**
8
+ * Potential endpoints for API key retrieval
9
+ */
10
+ const API_KEY_ENDPOINTS = [
11
+ "/api/v1/user/api-key",
12
+ "/api/v1/user/token",
13
+ "/api/v1/user/info",
14
+ "/api/v1/auth/api-key",
15
+ "/api/v1/oauth2/api-key",
16
+ ];
17
+ /**
18
+ * Try to get API key from OAuth token
19
+ * This is speculative - Qwen may require OAuth token → API key exchange
20
+ */
21
+ export async function tryGetApiKey(oauthToken) {
22
+ debugLog("Attempting to exchange OAuth token for API key");
23
+ for (const endpoint of API_KEY_ENDPOINTS) {
24
+ const url = `${QWEN_OAUTH_BASE_URL}${endpoint}`;
25
+ try {
26
+ debugLog(`Trying endpoint: ${url}`);
27
+ const response = await fetch(url, {
28
+ method: "GET",
29
+ headers: {
30
+ "Authorization": `Bearer ${oauthToken}`,
31
+ "Content-Type": "application/json",
32
+ },
33
+ });
34
+ debugLog(`Endpoint ${endpoint} responded with ${response.status}`);
35
+ if (response.ok) {
36
+ const data = await response.json();
37
+ // Look for API key in various possible fields
38
+ const apiKey = data.api_key || data.apiKey || data.key || data.token;
39
+ if (apiKey && typeof apiKey === "string") {
40
+ infoLog(`Found API key via endpoint: ${endpoint}`);
41
+ return {
42
+ success: true,
43
+ api_key: apiKey,
44
+ };
45
+ }
46
+ debugLog(`Endpoint ${endpoint} returned data but no API key found`, {
47
+ fields: Object.keys(data),
48
+ });
49
+ }
50
+ }
51
+ catch (error) {
52
+ debugLog(`Error trying endpoint ${endpoint}:`, {
53
+ error: String(error),
54
+ });
55
+ }
56
+ }
57
+ warnLog("Could not find API key exchange endpoint");
58
+ return {
59
+ success: false,
60
+ error: "No API key exchange endpoint found",
61
+ };
62
+ }
63
+ /**
64
+ * Check if we need to use OAuth token directly or exchange for API key
65
+ */
66
+ export async function validateTokenWithApi(token, apiBaseUrl) {
67
+ try {
68
+ debugLog("Validating token with API endpoint");
69
+ // Try a simple API call to see if token works
70
+ const response = await fetch(`${apiBaseUrl}/models`, {
71
+ method: "GET",
72
+ headers: {
73
+ "Authorization": `Bearer ${token}`,
74
+ },
75
+ });
76
+ if (response.ok) {
77
+ infoLog("Token is valid for API endpoint");
78
+ return true;
79
+ }
80
+ debugLog("Token validation failed", {
81
+ status: response.status,
82
+ statusText: response.statusText,
83
+ });
84
+ return false;
85
+ }
86
+ catch (error) {
87
+ debugLog("Error validating token", { error: String(error) });
88
+ return false;
89
+ }
90
+ }
91
+ //# sourceMappingURL=api-key-exchange.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-key-exchange.js","sourceRoot":"","sources":["../src/api-key-exchange.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Credential storage for Qwen OAuth
3
+ * Saves/loads credentials from ~/.qwen/oauth_creds.json
4
+ */
5
+ export interface QwenCredentials {
6
+ accessToken: string;
7
+ refreshToken?: string;
8
+ expiryDate?: number;
9
+ tokenType?: string;
10
+ resourceUrl?: string;
11
+ scope?: string;
12
+ }
13
+ export declare function saveCredentials(credentials: QwenCredentials): void;
14
+ export declare function loadCredentials(): QwenCredentials | null;
15
+ export declare function deleteCredentials(): void;
16
+ //# sourceMappingURL=credentials.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAqBD,wBAAgB,eAAe,CAAC,WAAW,EAAE,eAAe,GAAG,IAAI,CAmBlE;AAED,wBAAgB,eAAe,IAAI,eAAe,GAAG,IAAI,CA2BxD;AAED,wBAAgB,iBAAiB,IAAI,IAAI,CAWxC"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Credential storage for Qwen OAuth
3
+ * Saves/loads credentials from ~/.qwen/oauth_creds.json
4
+ */
5
+ import { writeFileSync, mkdirSync, existsSync, readFileSync } from "node:fs";
6
+ import { join } from "node:path";
7
+ import { homedir } from "node:os";
8
+ const CREDENTIALS_DIR = join(homedir(), ".qwen");
9
+ const CREDENTIALS_FILE = join(CREDENTIALS_DIR, "oauth_creds.json");
10
+ function ensureCredentialsDir() {
11
+ if (!existsSync(CREDENTIALS_DIR)) {
12
+ mkdirSync(CREDENTIALS_DIR, { recursive: true, mode: 0o700 });
13
+ }
14
+ }
15
+ export function saveCredentials(credentials) {
16
+ try {
17
+ ensureCredentialsDir();
18
+ // Save in snake_case format (matches OAuth response)
19
+ const data = {
20
+ access_token: credentials.accessToken,
21
+ refresh_token: credentials.refreshToken,
22
+ expiry_date: credentials.expiryDate,
23
+ token_type: credentials.tokenType,
24
+ resource_url: credentials.resourceUrl,
25
+ scope: credentials.scope,
26
+ };
27
+ writeFileSync(CREDENTIALS_FILE, JSON.stringify(data, null, 2), {
28
+ encoding: "utf-8",
29
+ mode: 0o600,
30
+ });
31
+ }
32
+ catch (error) {
33
+ console.error("Failed to save credentials:", error);
34
+ }
35
+ }
36
+ export function loadCredentials() {
37
+ try {
38
+ if (!existsSync(CREDENTIALS_FILE)) {
39
+ return null;
40
+ }
41
+ const data = readFileSync(CREDENTIALS_FILE, "utf-8");
42
+ const fileCreds = JSON.parse(data);
43
+ // Convert snake_case to camelCase
44
+ const credentials = {
45
+ accessToken: fileCreds.access_token,
46
+ refreshToken: fileCreds.refresh_token,
47
+ expiryDate: fileCreds.expiry_date,
48
+ tokenType: fileCreds.token_type,
49
+ resourceUrl: fileCreds.resource_url,
50
+ scope: fileCreds.scope,
51
+ };
52
+ // Check if token is expired
53
+ if (credentials.expiryDate && Date.now() > credentials.expiryDate) {
54
+ return null;
55
+ }
56
+ return credentials;
57
+ }
58
+ catch {
59
+ return null;
60
+ }
61
+ }
62
+ export function deleteCredentials() {
63
+ try {
64
+ if (existsSync(CREDENTIALS_FILE)) {
65
+ writeFileSync(CREDENTIALS_FILE, JSON.stringify({}, null, 2), {
66
+ encoding: "utf-8",
67
+ mode: 0o600,
68
+ });
69
+ }
70
+ }
71
+ catch {
72
+ // Ignore errors
73
+ }
74
+ }
75
+ //# sourceMappingURL=credentials.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credentials.js","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAqBlC,MAAM,eAAe,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,OAAO,CAAC,CAAC;AACjD,MAAM,gBAAgB,GAAG,IAAI,CAAC,eAAe,EAAE,kBAAkB,CAAC,CAAC;AAEnE,SAAS,oBAAoB;IAC3B,IAAI,CAAC,UAAU,CAAC,eAAe,CAAC,EAAE,CAAC;QACjC,SAAS,CAAC,eAAe,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC/D,CAAC;AACH,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,WAA4B;IAC1D,IAAI,CAAC;QACH,oBAAoB,EAAE,CAAC;QACvB,qDAAqD;QACrD,MAAM,IAAI,GAAwB;YAChC,YAAY,EAAE,WAAW,CAAC,WAAW;YACrC,aAAa,EAAE,WAAW,CAAC,YAAY;YACvC,WAAW,EAAE,WAAW,CAAC,UAAU;YACnC,UAAU,EAAE,WAAW,CAAC,SAAS;YACjC,YAAY,EAAE,WAAW,CAAC,WAAW;YACrC,KAAK,EAAE,WAAW,CAAC,KAAK;SACzB,CAAC;QACF,aAAa,CAAC,gBAAgB,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE;YAC7D,QAAQ,EAAE,OAAO;YACjB,IAAI,EAAE,KAAK;SACZ,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,6BAA6B,EAAE,KAAK,CAAC,CAAC;IACtD,CAAC;AACH,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,IAAI,CAAC;QACH,IAAI,CAAC,UAAU,CAAC,gBAAgB,CAAC,EAAE,CAAC;YAClC,OAAO,IAAI,CAAC;QACd,CAAC;QACD,MAAM,IAAI,GAAG,YAAY,CAAC,gBAAgB,EAAE,OAAO,CAAC,CAAC;QACrD,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAwB,CAAC;QAE1D,kCAAkC;QAClC,MAAM,WAAW,GAAoB;YACnC,WAAW,EAAE,SAAS,CAAC,YAAY;YACnC,YAAY,EAAE,SAAS,CAAC,aAAa;YACrC,UAAU,EAAE,SAAS,CAAC,WAAW;YACjC,SAAS,EAAE,SAAS,CAAC,UAAU;YAC/B,WAAW,EAAE,SAAS,CAAC,YAAY;YACnC,KAAK,EAAE,SAAS,CAAC,KAAK;SACvB,CAAC;QAEF,4BAA4B;QAC5B,IAAI,WAAW,CAAC,UAAU,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,WAAW,CAAC,UAAU,EAAE,CAAC;YAClE,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,WAAW,CAAC;IACrB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,UAAU,iBAAiB;IAC/B,IAAI,CAAC;QACH,IAAI,UAAU,CAAC,gBAAgB,CAAC,EAAE,CAAC;YACjC,aAAa,CAAC,gBAAgB,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE;gBAC3D,QAAQ,EAAE,OAAO;gBACjB,IAAI,EAAE,KAAK;aACZ,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,gBAAgB;IAClB,CAAC;AACH,CAAC"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Diagnostic utilities for testing Qwen OAuth endpoints
3
+ */
4
+ export interface DiagnosticResult {
5
+ success: boolean;
6
+ endpoint: string;
7
+ status?: number;
8
+ statusText?: string;
9
+ error?: string;
10
+ data?: any;
11
+ responseTime?: number;
12
+ }
13
+ /**
14
+ * Test if Qwen OAuth device code endpoint is accessible
15
+ */
16
+ export declare function testDeviceCodeEndpoint(): Promise<DiagnosticResult>;
17
+ /**
18
+ * Test if Qwen API endpoint is accessible
19
+ * Tests the /chat/completions endpoint (OpenAI-compatible)
20
+ */
21
+ export declare function testAPIEndpoint(baseURL: string, token?: string): Promise<DiagnosticResult>;
22
+ /**
23
+ * Test if the base OAuth URL is accessible
24
+ */
25
+ export declare function testBaseURL(): Promise<DiagnosticResult>;
26
+ /**
27
+ * Run all diagnostic tests
28
+ */
29
+ export declare function runDiagnostics(token?: string): Promise<{
30
+ baseURL: DiagnosticResult;
31
+ deviceCode: DiagnosticResult;
32
+ apis: Record<string, DiagnosticResult>;
33
+ }>;
34
+ /**
35
+ * Run the OAuth device flow to get a token
36
+ */
37
+ export declare function runOAuthFlow(): Promise<{
38
+ access_token: string;
39
+ refresh_token: string;
40
+ expires_in: number;
41
+ } | null>;
42
+ /**
43
+ * Refresh an access token
44
+ */
45
+ export declare function refreshToken(refreshToken: string): Promise<{
46
+ access_token: string;
47
+ expires_in: number;
48
+ } | null>;
49
+ //# sourceMappingURL=diagnostic.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diagnostic.d.ts","sourceRoot":"","sources":["../src/diagnostic.ts"],"names":[],"mappings":"AAAA;;GAEG;AAgBH,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,gBAAgB,CAAC,CAuCxE;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAmDhG;AAED;;GAEG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC,gBAAgB,CAAC,CA2B7D;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC5D,OAAO,EAAE,gBAAgB,CAAC;IAC1B,UAAU,EAAE,gBAAgB,CAAC;IAC7B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;CACxC,CAAC,CAgDD;AAED;;GAEG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC;IAC5C,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,IAAI,CAAC,CA6HR;AAED;;GAEG;AACH,wBAAsB,YAAY,CAChC,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CA8B9D"}