opencode-qwen-cli-auth 2.2.8 → 2.3.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.
@@ -1,30 +1,46 @@
1
+ /**
2
+ * @fileoverview Configuration utilities for Qwen OAuth Plugin
3
+ * Manages paths for configuration, tokens, and cache directories
4
+ * @license MIT
5
+ */
6
+
1
7
  import { homedir } from "os";
2
8
  import { join } from "path";
3
9
  import { readFileSync, existsSync } from "fs";
10
+
4
11
  /**
5
12
  * Get plugin configuration directory
13
+ * @returns {string} Path to ~/.opencode/qwen/
6
14
  */
7
15
  export function getConfigDir() {
8
16
  return join(homedir(), ".opencode", "qwen");
9
17
  }
18
+
10
19
  /**
11
20
  * Get Qwen CLI credential directory (~/.qwen)
21
+ * This directory is shared with the official qwen-code CLI for token storage
22
+ * @returns {string} Path to ~/.qwen/
12
23
  */
13
24
  export function getQwenDir() {
14
25
  return join(homedir(), ".qwen");
15
26
  }
27
+
16
28
  /**
17
29
  * Get plugin configuration file path
30
+ * @returns {string} Path to ~/.opencode/qwen/auth-config.json
18
31
  */
19
32
  export function getConfigPath() {
20
33
  return join(getConfigDir(), "auth-config.json");
21
34
  }
35
+
22
36
  /**
23
37
  * Load plugin configuration from ~/.opencode/qwen/auth-config.json
24
38
  * Returns default config if file doesn't exist
39
+ * @returns {{ qwenMode: boolean }} Configuration object with qwenMode flag
25
40
  */
26
41
  export function loadPluginConfig() {
27
42
  const configPath = getConfigPath();
43
+ // Return default config if config file doesn't exist
28
44
  if (!existsSync(configPath)) {
29
45
  return { qwenMode: true }; // Default: QWEN_MODE enabled
30
46
  }
@@ -33,15 +49,20 @@ export function loadPluginConfig() {
33
49
  return JSON.parse(content);
34
50
  }
35
51
  catch (error) {
52
+ // Log warning and return default config on parse error
36
53
  console.warn(`[qwen-oauth-plugin] Failed to load config from ${configPath}:`, error);
37
54
  return { qwenMode: true };
38
55
  }
39
56
  }
57
+
40
58
  /**
41
59
  * Get QWEN_MODE setting
42
60
  * Priority: QWEN_MODE env var > config file > default (true)
61
+ * @param {{ qwenMode?: boolean|string|null }} config - Configuration object from file
62
+ * @returns {boolean} True if QWEN_MODE is enabled, false otherwise
43
63
  */
44
64
  export function getQwenMode(config) {
65
+ // Environment variable takes highest priority
45
66
  const envValue = process.env.QWEN_MODE;
46
67
  if (envValue !== undefined) {
47
68
  return envValue === "1" || envValue.toLowerCase() === "true";
@@ -49,31 +70,44 @@ export function getQwenMode(config) {
49
70
  // Ensure boolean type, avoid string "false" being truthy
50
71
  const val = config.qwenMode;
51
72
  if (val === undefined || val === null) return true; // default: enabled
73
+ // Handle string values from config file
52
74
  if (typeof val === "string") {
53
75
  return val === "1" || val.toLowerCase() === "true";
54
76
  }
77
+ // Convert to boolean for actual boolean values
55
78
  return !!val;
56
79
  }
80
+
57
81
  /**
58
82
  * Get token storage path
83
+ * Token file contains OAuth credentials: access_token, refresh_token, expiry_date, resource_url
84
+ * @returns {string} Path to ~/.qwen/oauth_creds.json
59
85
  */
60
86
  export function getTokenPath() {
61
87
  return join(getQwenDir(), "oauth_creds.json");
62
88
  }
89
+
63
90
  /**
64
91
  * Get token lock path for multi-process refresh coordination
92
+ * Prevents concurrent token refresh operations across multiple processes
93
+ * @returns {string} Path to ~/.qwen/oauth_creds.lock
65
94
  */
66
95
  export function getTokenLockPath() {
67
96
  return join(getQwenDir(), "oauth_creds.lock");
68
97
  }
98
+
69
99
  /**
70
100
  * Get legacy token storage path used by old plugin versions
101
+ * Used for backward compatibility and token migration
102
+ * @returns {string} Path to ~/.opencode/qwen/oauth_token.json
71
103
  */
72
104
  export function getLegacyTokenPath() {
73
105
  return join(getConfigDir(), "oauth_token.json");
74
106
  }
107
+
75
108
  /**
76
109
  * Get cache directory for prompts
110
+ * @returns {string} Path to ~/.opencode/cache/
77
111
  */
78
112
  export function getCacheDir() {
79
113
  return join(homedir(), ".opencode", "cache");
@@ -8,15 +8,15 @@ export declare const PROVIDER_ID = "qwen-code";
8
8
  /** Dummy API key (actual auth via OAuth) */
9
9
  export declare const DUMMY_API_KEY = "qwen-oauth";
10
10
  /**
11
- * Default Qwen Portal API base URL (fallback if resource_url is missing)
11
+ * Default Qwen DashScope base URL (fallback if resource_url is missing)
12
12
  * Note: This plugin is for OAuth authentication only. For API key authentication,
13
13
  * use OpenCode's built-in DashScope support.
14
14
  *
15
- * IMPORTANT: Portal API uses /v1 path (not /api/v1)
15
+ * IMPORTANT: OAuth endpoints use /api/v1, DashScope OpenAI-compatible uses /compatible-mode/v1
16
16
  * - OAuth endpoints: /api/v1/oauth2/ (for authentication)
17
17
  * - Chat API: /v1/ (for completions)
18
18
  */
19
- export declare const DEFAULT_QWEN_BASE_URL = "https://portal.qwen.ai/v1";
19
+ export declare const DEFAULT_QWEN_BASE_URL = "https://dashscope.aliyuncs.com/compatible-mode/v1";
20
20
  /** Qwen OAuth endpoints and configuration */
21
21
  export declare const QWEN_OAUTH: {
22
22
  readonly DEVICE_CODE_URL: "https://chat.qwen.ai/api/v1/oauth2/device/code";
@@ -40,8 +40,8 @@ export declare const HTTP_STATUS: {
40
40
  readonly TOO_MANY_REQUESTS: 429;
41
41
  };
42
42
  /**
43
- * Portal API headers
44
- * Note: Portal API (OAuth) requires special header to indicate OAuth authentication
43
+ * DashScope headers
44
+ * Note: OAuth requires X-DashScope-AuthType to indicate qwen-oauth authentication
45
45
  */
46
46
  export declare const PORTAL_HEADERS: {
47
47
  readonly AUTH_TYPE: "X-DashScope-AuthType";
@@ -1,37 +1,69 @@
1
1
  /**
2
- * Constants for Qwen OAuth Plugin
2
+ * @fileoverview Constants for Qwen OAuth Plugin
3
+ * Centralized configuration for OAuth endpoints, headers, error codes, and other constants
4
+ * @license MIT
3
5
  */
4
- /** Plugin identifier */
6
+
7
+ /** Plugin identifier for logging and debugging */
5
8
  export const PLUGIN_NAME = "qwen-oauth-plugin";
6
- /** Provider ID for opencode configuration (used in model references like qwen-code/coder-model) */
9
+
10
+ /**
11
+ * Provider ID for opencode configuration
12
+ * Used in model references like qwen-code/coder-model
13
+ */
7
14
  export const PROVIDER_ID = "qwen-code";
8
- /** Dummy API key (actual auth via OAuth) */
15
+
16
+ /**
17
+ * Dummy API key placeholder
18
+ * Actual authentication is handled via OAuth flow, not API key
19
+ */
9
20
  export const DUMMY_API_KEY = "qwen-oauth";
21
+
10
22
  /**
11
- * Default Qwen Portal API base URL (fallback if resource_url is missing)
23
+ * Default Qwen DashScope base URL (fallback if resource_url is missing)
12
24
  * Note: This plugin is for OAuth authentication only. For API key authentication,
13
25
  * use OpenCode's built-in DashScope support.
14
26
  *
15
- * IMPORTANT: Portal API uses /v1 path (not /api/v1)
27
+ * IMPORTANT: OAuth endpoints use /api/v1, DashScope OpenAI-compatible uses /compatible-mode/v1
16
28
  * - OAuth endpoints: /api/v1/oauth2/ (for authentication)
17
29
  * - Chat API: /v1/ (for completions)
30
+ *
31
+ * @constant {string}
32
+ */
33
+ // NOTE:
34
+ // qwen-code (official CLI) defaults to DashScope OpenAI-compatible endpoint when
35
+ // `resource_url` is missing. This is required for the free OAuth flow to behave
36
+ // the same as the CLI.
37
+ export const DEFAULT_QWEN_BASE_URL = "https://dashscope.aliyuncs.com/compatible-mode/v1";
38
+
39
+ /**
40
+ * Qwen OAuth endpoints and configuration
41
+ * Source: Qwen Code CLI (https://github.com/QwenLM/qwen-code)
42
+ * @namespace
18
43
  */
19
- export const DEFAULT_QWEN_BASE_URL = "https://portal.qwen.ai/v1";
20
- /** Qwen OAuth endpoints and configuration */
21
44
  export const QWEN_OAUTH = {
45
+ /** OAuth 2.0 Device Code endpoint */
22
46
  DEVICE_CODE_URL: "https://chat.qwen.ai/api/v1/oauth2/device/code",
47
+ /** OAuth 2.0 Token endpoint */
23
48
  TOKEN_URL: "https://chat.qwen.ai/api/v1/oauth2/token",
24
49
  /**
25
50
  * Qwen OAuth Client ID
26
- * Source: Qwen Code CLI (https://github.com/QwenLM/qwen-code)
27
51
  * This is a public client ID used for OAuth Device Authorization Grant flow (RFC 8628)
52
+ * @constant {string}
28
53
  */
29
54
  CLIENT_ID: "f0304373b74a44d2b584a3fb70ca9e56",
55
+ /** OAuth scopes requested: openid, profile, email, and model completion access */
30
56
  SCOPE: "openid profile email model.completion",
57
+ /** OAuth 2.0 Device Code grant type (RFC 8628) */
31
58
  GRANT_TYPE_DEVICE: "urn:ietf:params:oauth:grant-type:device_code",
59
+ /** OAuth 2.0 Refresh Token grant type */
32
60
  GRANT_TYPE_REFRESH: "refresh_token",
33
61
  };
34
- /** HTTP Status Codes */
62
+
63
+ /**
64
+ * HTTP Status Codes for error handling
65
+ * @namespace
66
+ */
35
67
  export const HTTP_STATUS = {
36
68
  OK: 200,
37
69
  BAD_REQUEST: 400,
@@ -39,21 +71,37 @@ export const HTTP_STATUS = {
39
71
  FORBIDDEN: 403,
40
72
  TOO_MANY_REQUESTS: 429,
41
73
  };
74
+
42
75
  /**
43
- * Portal API headers
44
- * Note: Portal API (OAuth) requires special header to indicate OAuth authentication
76
+ * DashScope headers for OAuth authentication
77
+ * Note: OAuth requires X-DashScope-AuthType to indicate qwen-oauth authentication
78
+ * @namespace
45
79
  */
46
80
  export const PORTAL_HEADERS = {
81
+ /** Header name for auth type specification */
47
82
  AUTH_TYPE: "X-DashScope-AuthType",
83
+ /** Header value for qwen-oauth authentication */
48
84
  AUTH_TYPE_VALUE: "qwen-oauth",
49
85
  };
50
- /** Device flow polling configuration */
86
+
87
+ /**
88
+ * Device flow polling configuration
89
+ * Controls backoff strategy for OAuth token polling
90
+ * @namespace
91
+ */
51
92
  export const DEVICE_FLOW = {
93
+ /** Initial polling interval in milliseconds */
52
94
  INITIAL_POLL_INTERVAL: 2000, // 2 seconds
95
+ /** Maximum polling interval in milliseconds */
53
96
  MAX_POLL_INTERVAL: 10000, // 10 seconds
97
+ /** Backoff multiplier for exponential backoff */
54
98
  BACKOFF_MULTIPLIER: 1.5,
55
99
  };
56
- /** Error messages */
100
+
101
+ /**
102
+ * Error messages for user-facing errors
103
+ * @namespace
104
+ */
57
105
  export const ERROR_MESSAGES = {
58
106
  TOKEN_REFRESH_FAILED: "Failed to refresh token, authentication required",
59
107
  DEVICE_AUTH_TIMEOUT: "Device authorization timed out",
@@ -61,14 +109,27 @@ export const ERROR_MESSAGES = {
61
109
  REQUEST_PARSE_ERROR: "Error parsing request",
62
110
  NO_RESOURCE_URL: "No resource_url in token response, using default",
63
111
  };
64
- /** OAuth error codes */
112
+
113
+ /**
114
+ * OAuth error codes from RFC 8628 Device Flow
115
+ * @namespace
116
+ */
65
117
  export const OAUTH_ERRORS = {
118
+ /** User has not yet authorized the device code */
66
119
  AUTHORIZATION_PENDING: "authorization_pending",
120
+ /** Server requests slower polling (slow_down error) */
67
121
  SLOW_DOWN: "slow_down",
122
+ /** User denied the authorization request */
68
123
  ACCESS_DENIED: "access_denied",
124
+ /** Device code has expired */
69
125
  EXPIRED_TOKEN: "expired_token",
70
126
  };
71
- /** Log stages for request logging */
127
+
128
+ /**
129
+ * Log stages for request logging
130
+ * Used for debugging and tracing request lifecycle
131
+ * @namespace
132
+ */
72
133
  export const LOG_STAGES = {
73
134
  BEFORE_TRANSFORM: "before-transform",
74
135
  AFTER_TRANSFORM: "after-transform",
@@ -77,29 +138,53 @@ export const LOG_STAGES = {
77
138
  DEVICE_CODE_REQUEST: "device-code-request",
78
139
  TOKEN_POLL: "token-poll",
79
140
  };
80
- /** Platform-specific browser opener commands */
141
+
142
+ /**
143
+ * Platform-specific browser opener commands
144
+ * Used for opening OAuth verification URL in default browser
145
+ * @namespace
146
+ */
81
147
  export const PLATFORM_OPENERS = {
82
148
  darwin: "open",
83
149
  win32: "start",
84
150
  linux: "xdg-open",
85
151
  };
86
- /** OAuth authorization labels */
152
+
153
+ /**
154
+ * OAuth authorization labels for UI display
155
+ * @namespace
156
+ */
87
157
  export const AUTH_LABELS = {
158
+ /** Label shown in OpenCode auth provider selection */
88
159
  OAUTH: "Qwen Code (qwen.ai OAuth)",
160
+ /** Instructions shown to user during OAuth flow */
89
161
  INSTRUCTIONS: "Visit the URL shown in your browser to complete authentication.",
90
162
  };
91
- /** OAuth verification URI parameters */
163
+
164
+ /**
165
+ * OAuth verification URI parameters
166
+ * Used to construct complete verification URL with client identification
167
+ * @namespace
168
+ */
92
169
  export const VERIFICATION_URI = {
93
170
  /** Query parameter key for client identification */
94
171
  CLIENT_PARAM_KEY: "client=",
95
172
  /** Full query parameter for Qwen Code client */
96
173
  CLIENT_PARAM_VALUE: "client=qwen-code",
97
174
  };
98
- /** Token refresh buffer (refresh 30 seconds before expiry) */
175
+
176
+ /**
177
+ * Token refresh buffer in milliseconds
178
+ * Tokens are refreshed 30 seconds before expiry to avoid race conditions
179
+ * @constant {number}
180
+ */
99
181
  export const TOKEN_REFRESH_BUFFER_MS = 30 * 1000; // 30 seconds
100
- /** Stream processing configuration */
182
+
183
+ /**
184
+ * Stream processing configuration
185
+ * @namespace
186
+ */
101
187
  export const STREAM_CONFIG = {
102
188
  /** Maximum buffer size for SSE pass-through mode (1MB) */
103
189
  MAX_BUFFER_SIZE: 1024 * 1024,
104
190
  };
105
- //# sourceMappingURL=constants.js.map
@@ -1,10 +1,34 @@
1
+ /**
2
+ * @fileoverview Logging utilities for Qwen OAuth Plugin
3
+ * Provides configurable logging for debugging and request tracing
4
+ * @license MIT
5
+ */
6
+
1
7
  import { writeFileSync, mkdirSync, existsSync } from "node:fs";
2
8
  import { join } from "node:path";
3
9
  import { homedir } from "node:os";
4
- // Logging configuration
10
+
11
+ /**
12
+ * Flag to enable request logging to file
13
+ * Controlled by ENABLE_PLUGIN_REQUEST_LOGGING environment variable
14
+ * @constant {boolean}
15
+ */
5
16
  export const LOGGING_ENABLED = process.env.ENABLE_PLUGIN_REQUEST_LOGGING === "1";
17
+
18
+ /**
19
+ * Flag to enable debug logging to console
20
+ * Controlled by DEBUG_QWEN_PLUGIN or ENABLE_PLUGIN_REQUEST_LOGGING environment variables
21
+ * @constant {boolean}
22
+ */
6
23
  export const DEBUG_ENABLED = process.env.DEBUG_QWEN_PLUGIN === "1" || LOGGING_ENABLED;
24
+
25
+ /**
26
+ * Directory path for log files
27
+ * Logs are stored in ~/.opencode/logs/qwen-plugin/
28
+ * @constant {string}
29
+ */
7
30
  const LOG_DIR = join(homedir(), ".opencode", "logs", "qwen-plugin");
31
+
8
32
  // Log startup message about logging state
9
33
  if (LOGGING_ENABLED) {
10
34
  console.log("[qwen-oauth-plugin] Request logging ENABLED - logs will be saved to:", LOG_DIR);
@@ -12,23 +36,34 @@ if (LOGGING_ENABLED) {
12
36
  if (DEBUG_ENABLED && !LOGGING_ENABLED) {
13
37
  console.log("[qwen-oauth-plugin] Debug logging ENABLED");
14
38
  }
39
+
40
+ /**
41
+ * Request counter for generating unique request IDs in logs
42
+ * @type {number}
43
+ */
15
44
  let requestCounter = 0;
45
+
16
46
  /**
17
47
  * Log request data to file (only when LOGGING_ENABLED is true)
18
- * @param stage - The stage of the request (e.g., "before-transform", "after-transform")
19
- * @param data - The data to log
48
+ * Creates JSON files with request/response data for debugging
49
+ * @param {string} stage - The stage of the request (e.g., "before-transform", "after-transform", "response")
50
+ * @param {Object} data - The data to log (request/response objects, metadata, etc.)
51
+ * @returns {void}
20
52
  */
21
53
  export function logRequest(stage, data) {
22
54
  // Only log if explicitly enabled via environment variable
23
55
  if (!LOGGING_ENABLED)
24
56
  return;
57
+
25
58
  // Ensure log directory exists on first log
26
59
  if (!existsSync(LOG_DIR)) {
27
60
  mkdirSync(LOG_DIR, { recursive: true });
28
61
  }
62
+
29
63
  const timestamp = new Date().toISOString();
30
64
  const requestId = ++requestCounter;
31
65
  const filename = join(LOG_DIR, `request-${requestId}-${stage}.json`);
66
+
32
67
  try {
33
68
  writeFileSync(filename, JSON.stringify({
34
69
  timestamp,
@@ -43,10 +78,13 @@ export function logRequest(stage, data) {
43
78
  console.error("[qwen-oauth-plugin] Failed to write log:", error.message);
44
79
  }
45
80
  }
81
+
46
82
  /**
47
83
  * Log debug information (only when DEBUG_ENABLED is true)
48
- * @param message - Debug message
49
- * @param data - Optional data to log
84
+ * Used for detailed debugging during development
85
+ * @param {string} message - Debug message describing the context
86
+ * @param {*} [data] - Optional data to log (objects, values, etc.)
87
+ * @returns {void}
50
88
  */
51
89
  export function logDebug(message, data) {
52
90
  if (!DEBUG_ENABLED)
@@ -58,10 +96,13 @@ export function logDebug(message, data) {
58
96
  console.log(`[qwen-oauth-plugin] ${message}`);
59
97
  }
60
98
  }
99
+
61
100
  /**
62
101
  * Log error (always enabled for important issues)
63
- * @param message - Error message
64
- * @param data - Optional data to log
102
+ * Used for critical errors that need attention
103
+ * @param {string} message - Error message describing what went wrong
104
+ * @param {*} [data] - Optional data to log (error objects, context, etc.)
105
+ * @returns {void}
65
106
  */
66
107
  export function logError(message, data) {
67
108
  if (data !== undefined) {
@@ -71,10 +112,13 @@ export function logError(message, data) {
71
112
  console.error(`[qwen-oauth-plugin] ${message}`);
72
113
  }
73
114
  }
115
+
74
116
  /**
75
117
  * Log warning (always enabled for important issues)
76
- * @param message - Warning message
77
- * @param data - Optional data to log
118
+ * Used for non-critical issues that may need attention
119
+ * @param {string} message - Warning message describing the issue
120
+ * @param {*} [data] - Optional data to log (context, values, etc.)
121
+ * @returns {void}
78
122
  */
79
123
  export function logWarn(message, data) {
80
124
  if (data !== undefined) {
@@ -84,10 +128,13 @@ export function logWarn(message, data) {
84
128
  console.warn(`[qwen-oauth-plugin] ${message}`);
85
129
  }
86
130
  }
131
+
87
132
  /**
88
133
  * Log info message (always enabled)
89
- * @param message - Info message
90
- * @param data - Optional data to log
134
+ * Used for general informational messages
135
+ * @param {string} message - Info message describing the event
136
+ * @param {*} [data] - Optional data to log (context, values, etc.)
137
+ * @returns {void}
91
138
  */
92
139
  export function logInfo(message, data) {
93
140
  if (data !== undefined) {
@@ -97,4 +144,3 @@ export function logInfo(message, data) {
97
144
  console.log(`[qwen-oauth-plugin] ${message}`);
98
145
  }
99
146
  }
100
- //# sourceMappingURL=logger.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-qwen-cli-auth",
3
- "version": "2.2.8",
3
+ "version": "2.3.0",
4
4
  "description": "Qwen OAuth authentication plugin for opencode - use your Qwen account instead of API keys",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",