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 +62 -162
- package/dist/index.js +167 -63
- package/dist/lib/auth/auth.js +6 -6
- package/dist/lib/constants.d.ts +5 -5
- package/dist/lib/constants.js +9 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,162 +1,62 @@
|
|
|
1
|
-
# opencode-qwen-cli-auth
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
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
|
package/dist/lib/auth/auth.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
package/dist/lib/constants.d.ts
CHANGED
|
@@ -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
|
|
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:
|
|
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://
|
|
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
|
-
*
|
|
44
|
-
* Note:
|
|
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";
|
package/dist/lib/constants.js
CHANGED
|
@@ -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
|
|
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:
|
|
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
|
-
|
|
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
|
-
*
|
|
44
|
-
* Note:
|
|
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