opencode-qwen-cli-auth 2.2.8 → 2.2.9

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
@@ -1,162 +1,62 @@
1
- # opencode-qwen-cli-auth
2
- OAuth plugin for OpenCode that lets you use Qwen models with your Qwen account, without managing a DashScope API key directly.
3
-
4
- ## Scope
5
-
6
- - Uses OAuth Device Authorization Grant (RFC 8628) for sign-in.
7
- - Best suited for personal/dev workflows.
8
- - For production or commercial workloads, use DashScope API key auth instead:
9
- https://dashscope.console.aliyun.com/
10
-
11
- ## Features
12
-
13
- - OAuth login through `opencode auth login`.
14
- - Automatic token refresh before expiration.
15
- - Dynamic API base URL from token `resource_url` with safe fallback.
16
- - Model normalization to `coder-model` for Qwen Portal API.
17
- - Optional prompt bridge behavior via `QWEN_MODE`.
18
- - Optional debug and request logging via environment variables.
19
-
20
- ## Requirements
21
-
22
- - Qwen account
23
- - OpenCode
24
- - Node.js `>=20` (only required when building/testing from source)
25
-
26
- ## Quick start
27
-
28
- Add the plugin to your OpenCode config:
29
-
30
- ```json
31
- {
32
- "$schema": "https://opencode.ai/config.json",
33
- "plugin": ["opencode-qwen-cli-auth"],
34
- "model": "qwen-code/coder-model"
35
- }
36
- ```
37
-
38
- Then sign in:
39
-
40
- ```bash
41
- opencode auth login
42
- ```
43
-
44
- Choose `Qwen Code` -> `Qwen Code (qwen.ai OAuth)`.
45
-
46
- ## Usage
47
-
48
- ```bash
49
- opencode run "create a hello world file" --model=qwen-code/coder-model
50
- opencode chat --model=qwen-code/coder-model
51
- ```
52
-
53
- Always keep the provider prefix `qwen-code/` in model configuration.
54
-
55
- ## Configuration
56
-
57
- ### `QWEN_MODE`
58
-
59
- Resolution order:
60
-
61
- 1. Environment variable `QWEN_MODE`
62
- 2. File `~/.opencode/qwen/auth-config.json`
63
- 3. Default value: `true`
64
-
65
- Example `~/.opencode/qwen/auth-config.json`:
66
-
67
- ```json
68
- {
69
- "qwenMode": true
70
- }
71
- ```
72
-
73
- Supported env values:
74
-
75
- - Enable: `QWEN_MODE=1` or `QWEN_MODE=true`
76
- - Disable: `QWEN_MODE=0` or `QWEN_MODE=false`
77
-
78
- ## Logging and debug
79
-
80
- - Enable debug logs:
81
-
82
- ```bash
83
- DEBUG_QWEN_PLUGIN=1 opencode run "your prompt"
84
- ```
85
-
86
- - Enable request logging to files:
87
-
88
- ```bash
89
- ENABLE_PLUGIN_REQUEST_LOGGING=1 opencode run "your prompt"
90
- ```
91
-
92
- Log path: `~/.opencode/logs/qwen-plugin/`
93
-
94
- ## Local plugin data
95
-
96
- - OAuth token: `~/.opencode/qwen/oauth_token.json`
97
- - Plugin config: `~/.opencode/qwen/auth-config.json`
98
- - Prompt cache: `~/.opencode/cache/`
99
-
100
- ## Troubleshooting
101
-
102
- ### `Authentication required. Please run: opencode auth login`
103
-
104
- Token is missing or refresh failed. Re-authenticate:
105
-
106
- ```bash
107
- opencode auth login
108
- ```
109
-
110
- ### Device authorization timed out
111
-
112
- The device code expired or was not confirmed in time. Run `opencode auth login` again and confirm in the browser sooner.
113
-
114
- ### `429` rate limit
115
-
116
- The server is throttling requests. Reduce request frequency and retry later.
117
-
118
- ### Wrong model behavior
119
-
120
- Ensure your model is set correctly in OpenCode:
121
-
122
- ```yaml
123
- model: qwen-code/coder-model
124
- ```
125
-
126
- ## Clear auth state
127
-
128
- - macOS/Linux:
129
-
130
- ```bash
131
- rm -rf ~/.opencode/qwen/
132
- ```
133
-
134
- - PowerShell:
135
-
136
- ```powershell
137
- Remove-Item -Recurse -Force "$HOME/.opencode/qwen"
138
- ```
139
-
140
- Then log in again with `opencode auth login`.
141
-
142
- ## Development
143
-
144
- ```bash
145
- npm run build
146
- npm run typecheck
147
- npm run test
148
- npm run lint
149
- ```
150
-
151
- ## Policy and links
152
-
153
- - Terms of Service: https://qwen.ai/termsservice
154
- - Privacy Policy: https://qwen.ai/privacypolicy
155
- - Usage Policy: https://qwen.ai/usagepolicy
156
- - NPM: https://www.npmjs.com/package/opencode-qwen-cli-auth
157
- - Repository: https://github.com/TVD-00/opencode-qwen-cli-auth
158
- - Issues: https://github.com/TVD-00/opencode-qwen-cli-auth/issues
159
-
160
- ## License
161
-
162
- MIT
1
+ # opencode-qwen-cli-auth (local fork)
2
+
3
+ Plugin OAuth cho **OpenCode** để dùng Qwen theo cơ chế giống **qwen-code CLI** (free tier bằng Qwen account), không cần DashScope API key.
4
+
5
+ ## Cấu hình nhanh
6
+
7
+ `opencode.json`:
8
+
9
+ ```json
10
+ {
11
+ "$schema": "https://opencode.ai/config.json",
12
+ "plugin": ["opencode-qwen-cli-auth"],
13
+ "model": "qwen-code/coder-model"
14
+ }
15
+ ```
16
+
17
+ Đăng nhập:
18
+
19
+ ```bash
20
+ opencode auth login
21
+ ```
22
+
23
+ Chọn provider **Qwen Code (qwen.ai OAuth)**.
24
+
25
+ ## Vì sao plugin trước bị `insufficient_quota`?
26
+
27
+ Từ việc đối chiếu với **qwen-code** (gốc), request free-tier cần:
28
+
29
+ - Base URL đúng (DashScope OpenAI-compatible):
30
+ - mặc định: `https://dashscope.aliyuncs.com/compatible-mode/v1`
31
+ - có thể thay đổi theo `resource_url` trong `~/.qwen/oauth_creds.json`
32
+ - Headers DashScope đặc thù:
33
+ - `X-DashScope-AuthType: qwen-oauth`
34
+ - `X-DashScope-CacheControl: enable`
35
+ - `User-Agent` + `X-DashScope-UserAgent`
36
+ - Giới hạn output token theo model (qwen-code):
37
+ - `coder-model`: 65536
38
+ - `vision-model`: 8192
39
+
40
+ Fork này đã **inject headers ở tầng fetch** để vẫn hoạt động ngay cả khi OpenCode không gọi hook `chat.headers`.
41
+
42
+ ## Debug / logging
43
+
44
+ ```bash
45
+ DEBUG_QWEN_PLUGIN=1 opencode run "hello" --model=qwen-code/coder-model
46
+ ENABLE_PLUGIN_REQUEST_LOGGING=1 opencode run "hello" --model=qwen-code/coder-model
47
+ ```
48
+
49
+ Log path: `~/.opencode/logs/qwen-plugin/`
50
+
51
+ ## Clear auth
52
+
53
+ PowerShell:
54
+
55
+ ```powershell
56
+ Remove-Item -Recurse -Force "$HOME/.opencode/qwen"
57
+ Remove-Item -Recurse -Force "$HOME/.qwen" # nếu muốn xoá token qwen-code luôn
58
+ ```
59
+
60
+ ## Ghi chú build
61
+
62
+ Repo này chỉ chứa output `dist/` (không có `src/`/`tsconfig.json`), nên `npm run build/typecheck` sẽ không compile lại TS.
package/dist/index.js CHANGED
@@ -15,7 +15,11 @@ import { PROVIDER_ID, AUTH_LABELS, DEVICE_FLOW, PORTAL_HEADERS } from "./lib/con
15
15
  import { logError, logInfo, logWarn, LOGGING_ENABLED } from "./lib/logger.js";
16
16
  const CHAT_REQUEST_TIMEOUT_MS = 30000;
17
17
  const CHAT_MAX_RETRIES = 0;
18
- const CHAT_MAX_TOKENS_CAP = 2048;
18
+ // Output token caps should match what qwen-code uses for DashScope.
19
+ // - coder-model: 64K output
20
+ // - vision-model: 8K output
21
+ // We still keep a default for safety.
22
+ const CHAT_MAX_TOKENS_CAP = 65536;
19
23
  const CHAT_DEFAULT_MAX_TOKENS = 2048;
20
24
  const MAX_CONSECUTIVE_POLL_FAILURES = 3;
21
25
  const QUOTA_DEGRADE_MAX_TOKENS = 1024;
@@ -23,6 +27,58 @@ const CLI_FALLBACK_TIMEOUT_MS = 8000;
23
27
  const CLI_FALLBACK_MAX_BUFFER_CHARS = 1024 * 1024;
24
28
  const ENABLE_CLI_FALLBACK = process.env.OPENCODE_QWEN_ENABLE_CLI_FALLBACK === "1";
25
29
  const PLUGIN_USER_AGENT = "opencode-qwen-cli-auth/2.2.1";
30
+ // Match qwen-code output limits for DashScope OAuth.
31
+ const DASH_SCOPE_OUTPUT_LIMITS = {
32
+ "coder-model": 65536,
33
+ "vision-model": 8192,
34
+ };
35
+ function capPayloadMaxTokens(payload) {
36
+ if (!payload || typeof payload !== "object") {
37
+ return payload;
38
+ }
39
+ const model = typeof payload.model === "string" ? payload.model : "";
40
+ const normalizedModel = model.trim().toLowerCase();
41
+ const limit = DASH_SCOPE_OUTPUT_LIMITS[normalizedModel];
42
+ if (!limit) {
43
+ return payload;
44
+ }
45
+ const next = { ...payload };
46
+ let changed = false;
47
+ if (typeof next.max_tokens === "number" && next.max_tokens > limit) {
48
+ next.max_tokens = limit;
49
+ changed = true;
50
+ }
51
+ if (typeof next.max_completion_tokens === "number" && next.max_completion_tokens > limit) {
52
+ next.max_completion_tokens = limit;
53
+ changed = true;
54
+ }
55
+ // Some clients use camelCase.
56
+ if (typeof next.maxTokens === "number" && next.maxTokens > limit) {
57
+ next.maxTokens = limit;
58
+ changed = true;
59
+ }
60
+ if (next.options && typeof next.options === "object") {
61
+ const options = { ...next.options };
62
+ let optionsChanged = false;
63
+ if (typeof options.max_tokens === "number" && options.max_tokens > limit) {
64
+ options.max_tokens = limit;
65
+ optionsChanged = true;
66
+ }
67
+ if (typeof options.max_completion_tokens === "number" && options.max_completion_tokens > limit) {
68
+ options.max_completion_tokens = limit;
69
+ optionsChanged = true;
70
+ }
71
+ if (typeof options.maxTokens === "number" && options.maxTokens > limit) {
72
+ options.maxTokens = limit;
73
+ optionsChanged = true;
74
+ }
75
+ if (optionsChanged) {
76
+ next.options = options;
77
+ changed = true;
78
+ }
79
+ }
80
+ return changed ? next : payload;
81
+ }
26
82
  const CLIENT_ONLY_BODY_FIELDS = new Set([
27
83
  "providerID",
28
84
  "provider",
@@ -581,15 +637,63 @@ async function sendWithTimeout(input, requestInit) {
581
637
  composed.cleanup();
582
638
  }
583
639
  }
640
+ function applyDashScopeHeaders(requestInit) {
641
+ // Ensure required DashScope OAuth headers are always present.
642
+ // This mirrors qwen-code (DashScopeOpenAICompatibleProvider.buildHeaders) behavior.
643
+ // NOTE: We intentionally do this in the fetch layer so it works even when
644
+ // OpenCode does not call the `chat.headers` hook (older versions / API mismatch).
645
+ const headersToApply = {
646
+ "X-DashScope-AuthType": PORTAL_HEADERS.AUTH_TYPE_VALUE,
647
+ "X-DashScope-CacheControl": "enable",
648
+ "User-Agent": PLUGIN_USER_AGENT,
649
+ "X-DashScope-UserAgent": PLUGIN_USER_AGENT,
650
+ };
651
+ if (!requestInit.headers) {
652
+ requestInit.headers = { ...headersToApply };
653
+ return;
654
+ }
655
+ if (requestInit.headers instanceof Headers) {
656
+ for (const [key, value] of Object.entries(headersToApply)) {
657
+ if (!requestInit.headers.has(key)) {
658
+ requestInit.headers.set(key, value);
659
+ }
660
+ }
661
+ return;
662
+ }
663
+ if (Array.isArray(requestInit.headers)) {
664
+ const existing = new Set(requestInit.headers.map(([name]) => String(name).toLowerCase()));
665
+ for (const [key, value] of Object.entries(headersToApply)) {
666
+ if (!existing.has(key.toLowerCase())) {
667
+ requestInit.headers.push([key, value]);
668
+ }
669
+ }
670
+ return;
671
+ }
672
+ // Plain object
673
+ for (const [key, value] of Object.entries(headersToApply)) {
674
+ if (!(key in requestInit.headers)) {
675
+ requestInit.headers[key] = value;
676
+ }
677
+ }
678
+ }
584
679
  async function failFastFetch(input, init) {
585
680
  const normalized = await normalizeFetchInvocation(input, init);
586
681
  const requestInput = normalized.requestInput;
587
682
  const requestInit = normalized.requestInit;
683
+ // Always inject DashScope OAuth headers at the fetch layer.
684
+ // This ensures compatibility across OpenCode versions.
685
+ applyDashScopeHeaders(requestInit);
588
686
  const sourceSignal = requestInit.signal;
589
687
  const rawPayload = parseJsonRequestBody(requestInit);
590
688
  const sessionID = typeof rawPayload?.sessionID === "string" ? rawPayload.sessionID : undefined;
591
689
  let payload = rawPayload;
592
690
  if (payload) {
691
+ // Ensure we never exceed DashScope model output limits.
692
+ const capped = capPayloadMaxTokens(payload);
693
+ if (capped !== payload) {
694
+ payload = capped;
695
+ applyJsonRequestBody(requestInit, payload);
696
+ }
593
697
  const sanitized = sanitizeOutgoingPayload(payload);
594
698
  if (sanitized !== payload) {
595
699
  payload = sanitized;
@@ -762,7 +866,7 @@ async function getValidAccessToken(getAuth) {
762
866
  }
763
867
  /**
764
868
  * Get base URL from token stored on disk (resource_url).
765
- * Falls back to portal.qwen.ai/v1 if not available.
869
+ * Falls back to DashScope compatible-mode if not available.
766
870
  */
767
871
  function getBaseUrl() {
768
872
  try {
@@ -776,31 +880,31 @@ function getBaseUrl() {
776
880
  }
777
881
  return getApiBaseUrl();
778
882
  }
779
- /**
780
- * Alibaba Qwen OAuth authentication plugin for opencode
781
- *
782
- * @example
783
- * ```json
784
- * {
785
- * "plugin": ["opencode-alibaba-qwen-cli-auth"],
786
- * "model": "qwen-code/coder-model"
787
- * }
788
- * ```
789
- */
790
- export const QwenAuthPlugin = async (_input) => {
791
- return {
792
- auth: {
793
- provider: PROVIDER_ID,
883
+ /**
884
+ * Alibaba Qwen OAuth authentication plugin for opencode
885
+ *
886
+ * @example
887
+ * ```json
888
+ * {
889
+ * "plugin": ["opencode-alibaba-qwen-cli-auth"],
890
+ * "model": "qwen-code/coder-model"
891
+ * }
892
+ * ```
893
+ */
894
+ export const QwenAuthPlugin = async (_input) => {
895
+ return {
896
+ auth: {
897
+ provider: PROVIDER_ID,
794
898
  /**
795
899
  * Loader: get token + base URL, return to SDK.
796
900
  * Pattern similar to opencode-qwencode-auth reference plugin.
797
901
  */
798
- async loader(getAuth, provider) {
902
+ async loader(getAuth, provider) {
799
903
  // Zero cost for OAuth models (free)
800
904
  if (provider?.models) {
801
- for (const model of Object.values(provider.models)) {
802
- if (model) model.cost = { input: 0, output: 0 };
803
- }
905
+ for (const model of Object.values(provider.models)) {
906
+ if (model) model.cost = { input: 0, output: 0 };
907
+ }
804
908
  }
805
909
  const accessToken = await getValidAccessToken(getAuth);
806
910
  if (!accessToken) return null;
@@ -817,32 +921,32 @@ export const QwenAuthPlugin = async (_input) => {
817
921
  };
818
922
  },
819
923
  methods: [
820
- {
821
- label: AUTH_LABELS.OAUTH,
822
- type: "oauth",
823
- /**
824
- * Device Authorization Grant OAuth flow (RFC 8628)
825
- */
826
- authorize: async () => {
827
- // Generate PKCE
828
- const pkce = await createPKCE();
829
- // Request device code
830
- const deviceAuth = await requestDeviceCode(pkce);
831
- if (!deviceAuth) {
832
- throw new Error("Failed to request device code");
833
- }
924
+ {
925
+ label: AUTH_LABELS.OAUTH,
926
+ type: "oauth",
927
+ /**
928
+ * Device Authorization Grant OAuth flow (RFC 8628)
929
+ */
930
+ authorize: async () => {
931
+ // Generate PKCE
932
+ const pkce = await createPKCE();
933
+ // Request device code
934
+ const deviceAuth = await requestDeviceCode(pkce);
935
+ if (!deviceAuth) {
936
+ throw new Error("Failed to request device code");
937
+ }
834
938
  // Display user code
835
939
  console.log(`\nPlease visit: ${deviceAuth.verification_uri}`);
836
940
  console.log(`And enter code: ${deviceAuth.user_code}\n`);
837
941
  // Verification URL - SDK will open browser automatically when method=auto
838
- const verificationUrl = deviceAuth.verification_uri_complete || deviceAuth.verification_uri;
839
- return {
840
- url: verificationUrl,
841
- method: "auto",
842
- instructions: AUTH_LABELS.INSTRUCTIONS,
843
- callback: async () => {
844
- // Poll for token
845
- let pollInterval = (deviceAuth.interval || 5) * 1000;
942
+ const verificationUrl = deviceAuth.verification_uri_complete || deviceAuth.verification_uri;
943
+ return {
944
+ url: verificationUrl,
945
+ method: "auto",
946
+ instructions: AUTH_LABELS.INSTRUCTIONS,
947
+ callback: async () => {
948
+ // Poll for token
949
+ let pollInterval = (deviceAuth.interval || 5) * 1000;
846
950
  const POLLING_MARGIN_MS = 3000;
847
951
  const maxInterval = DEVICE_FLOW.MAX_POLL_INTERVAL;
848
952
  const startTime = Date.now();
@@ -855,9 +959,9 @@ export const QwenAuthPlugin = async (_input) => {
855
959
  saveToken(result);
856
960
  // Return to SDK to save auth state
857
961
  return {
858
- type: "success",
859
- access: result.access,
860
- refresh: result.refresh,
962
+ type: "success",
963
+ access: result.access,
964
+ refresh: result.refresh,
861
965
  expires: result.expires,
862
966
  };
863
967
  }
@@ -900,19 +1004,19 @@ export const QwenAuthPlugin = async (_input) => {
900
1004
  console.error("[qwen-oauth-plugin] Device authorization timed out");
901
1005
  return { type: "failed" };
902
1006
  },
903
- };
904
- },
905
- },
906
- ],
907
- },
1007
+ };
1008
+ },
1009
+ },
1010
+ ],
1011
+ },
908
1012
  /**
909
1013
  * Register qwen-code provider with model list.
910
1014
  * Only register models that Portal API (OAuth) accepts:
911
1015
  * coder-model and vision-model (according to QWEN_OAUTH_ALLOWED_MODELS from original CLI)
912
1016
  */
913
- config: async (config) => {
914
- const providers = config.provider || {};
915
- providers[PROVIDER_ID] = {
1017
+ config: async (config) => {
1018
+ const providers = config.provider || {};
1019
+ providers[PROVIDER_ID] = {
916
1020
  npm: "@ai-sdk/openai-compatible",
917
1021
  name: "Qwen Code",
918
1022
  options: {
@@ -928,18 +1032,18 @@ export const QwenAuthPlugin = async (_input) => {
928
1032
  // Thinking is always enabled by default on server side (qwen3.5-plus)
929
1033
  reasoning: false,
930
1034
  limit: { context: 1048576, output: CHAT_MAX_TOKENS_CAP },
931
- cost: { input: 0, output: 0 },
932
- modalities: { input: ["text"], output: ["text"] },
933
- },
1035
+ cost: { input: 0, output: 0 },
1036
+ modalities: { input: ["text"], output: ["text"] },
1037
+ },
934
1038
  "vision-model": {
935
1039
  id: "vision-model",
936
1040
  name: "Qwen VL Plus (vision)",
937
1041
  reasoning: false,
938
- limit: { context: 131072, output: CHAT_MAX_TOKENS_CAP },
939
- cost: { input: 0, output: 0 },
940
- modalities: { input: ["text"], output: ["text"] },
941
- },
942
- },
1042
+ limit: { context: 131072, output: DASH_SCOPE_OUTPUT_LIMITS["vision-model"] },
1043
+ cost: { input: 0, output: 0 },
1044
+ modalities: { input: ["text"], output: ["text"] },
1045
+ },
1046
+ },
943
1047
  };
944
1048
  config.provider = providers;
945
1049
  },
@@ -1013,5 +1117,5 @@ export const QwenAuthPlugin = async (_input) => {
1013
1117
  },
1014
1118
  };
1015
1119
  };
1016
- export default QwenAuthPlugin;
1120
+ export default QwenAuthPlugin;
1017
1121
  //# sourceMappingURL=index.js.map
@@ -556,12 +556,12 @@ export function getApiBaseUrl(resourceUrl) {
556
556
  try {
557
557
  const normalizedResourceUrl = normalizeResourceUrl(resourceUrl);
558
558
  if (!normalizedResourceUrl) {
559
- logWarn("Invalid resource_url, using default Portal API URL");
559
+ logWarn("Invalid resource_url, using default DashScope endpoint");
560
560
  return DEFAULT_QWEN_BASE_URL;
561
561
  }
562
562
  const url = new URL(normalizedResourceUrl);
563
563
  if (!url.protocol.startsWith("http")) {
564
- logWarn("Invalid resource_url protocol, using default Portal API URL");
564
+ logWarn("Invalid resource_url protocol, using default DashScope endpoint");
565
565
  return DEFAULT_QWEN_BASE_URL;
566
566
  }
567
567
  let baseUrl = normalizedResourceUrl.replace(/\/$/, "");
@@ -570,18 +570,18 @@ export function getApiBaseUrl(resourceUrl) {
570
570
  baseUrl = `${baseUrl}${suffix}`;
571
571
  }
572
572
  if (LOGGING_ENABLED) {
573
- logInfo("Constructed Portal API base URL from resource_url:", baseUrl);
573
+ logInfo("Constructed DashScope base URL from resource_url:", baseUrl);
574
574
  }
575
575
  return baseUrl;
576
576
  }
577
577
  catch (error) {
578
- logWarn("Invalid resource_url format, using default Portal API URL:", error);
578
+ logWarn("Invalid resource_url format, using default DashScope endpoint:", error);
579
579
  return DEFAULT_QWEN_BASE_URL;
580
580
  }
581
581
  }
582
582
  if (LOGGING_ENABLED) {
583
- logInfo("No resource_url provided, using default Portal API URL");
583
+ logInfo("No resource_url provided, using default DashScope endpoint");
584
584
  }
585
585
  return DEFAULT_QWEN_BASE_URL;
586
586
  }
587
- //# sourceMappingURL=auth.js.map
587
+ //# sourceMappingURL=auth.js.map
@@ -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";
@@ -8,15 +8,19 @@ export const PROVIDER_ID = "qwen-code";
8
8
  /** Dummy API key (actual auth via OAuth) */
9
9
  export 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 const DEFAULT_QWEN_BASE_URL = "https://portal.qwen.ai/v1";
19
+ // NOTE:
20
+ // qwen-code (official CLI) defaults to DashScope OpenAI-compatible endpoint when
21
+ // `resource_url` is missing. This is required for the free OAuth flow to behave
22
+ // the same as the CLI.
23
+ export const DEFAULT_QWEN_BASE_URL = "https://dashscope.aliyuncs.com/compatible-mode/v1";
20
24
  /** Qwen OAuth endpoints and configuration */
21
25
  export const QWEN_OAUTH = {
22
26
  DEVICE_CODE_URL: "https://chat.qwen.ai/api/v1/oauth2/device/code",
@@ -40,8 +44,8 @@ export const HTTP_STATUS = {
40
44
  TOO_MANY_REQUESTS: 429,
41
45
  };
42
46
  /**
43
- * Portal API headers
44
- * Note: Portal API (OAuth) requires special header to indicate OAuth authentication
47
+ * DashScope headers
48
+ * Note: OAuth requires X-DashScope-AuthType to indicate qwen-oauth authentication
45
49
  */
46
50
  export const PORTAL_HEADERS = {
47
51
  AUTH_TYPE: "X-DashScope-AuthType",
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.2.9",
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",