qwen-opencode-provider 3.0.0 → 3.1.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.
- package/index.js +82 -34
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* OpenCode Qwen API Plugin
|
|
3
3
|
*
|
|
4
4
|
* Uses local proxy (like Pollinations) to handle auth automatically.
|
|
5
|
-
*
|
|
5
|
+
* Auto-fetches all models from Qwen API.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import http from 'http';
|
|
@@ -16,27 +16,6 @@ const LOG_FILE = path.join(LOG_DIR, 'debug.log');
|
|
|
16
16
|
const PROVIDER_ID = 'qwen';
|
|
17
17
|
const API_BASE_URL = 'https://qwen.aikit.club/v1';
|
|
18
18
|
|
|
19
|
-
const QWEN_MODELS = {
|
|
20
|
-
'qwen3-max': { name: 'Qwen3 Max', limit: { context: 262144, output: 32768 } },
|
|
21
|
-
'qwen3-max-2026-01-23': { name: 'Qwen3 Max', limit: { context: 262144, output: 32768 } },
|
|
22
|
-
'qwen3-vl-plus': { name: 'Qwen3 VL Plus', limit: { context: 262144, output: 32768 } },
|
|
23
|
-
'qwen3-coder-plus': { name: 'Qwen3 Coder Plus', limit: { context: 1048576, output: 65536 } },
|
|
24
|
-
'qwen3-vl-32b': { name: 'Qwen3 VL 32B', limit: { context: 131072, output: 32768 } },
|
|
25
|
-
'qwen3-vl-30b-a3b': { name: 'Qwen3 VL 30B A3B', limit: { context: 131072, output: 32768 } },
|
|
26
|
-
'qwen3-omni-flash': { name: 'Qwen3 Omni Flash', limit: { context: 65536, output: 13684 } },
|
|
27
|
-
'qwen3-30b-a3b': { name: 'Qwen3 30B A3B', limit: { context: 131072, output: 32768 } },
|
|
28
|
-
'qwen3-coder-30b-a3b-instruct': { name: 'Qwen3 Coder Flash', limit: { context: 262144, output: 65536 } },
|
|
29
|
-
'qwen-max': { name: 'Qwen Max' },
|
|
30
|
-
'qwen2.5-max': { name: 'Qwen2.5 Max' },
|
|
31
|
-
'qwen2.5-plus': { name: 'Qwen2.5 Plus' },
|
|
32
|
-
'qwen2.5-turbo': { name: 'Qwen2.5 Turbo' },
|
|
33
|
-
'qwq-32b': { name: 'QWQ 32B' },
|
|
34
|
-
'qwen-deep-research': { name: 'Qwen Deep Research' },
|
|
35
|
-
'qwen-web-dev': { name: 'Qwen Web Dev' },
|
|
36
|
-
'qwen-full-stack': { name: 'Qwen Full Stack' },
|
|
37
|
-
'qwen-cogview': { name: 'Qwen CogView' }
|
|
38
|
-
};
|
|
39
|
-
|
|
40
19
|
function ensureLogDir() {
|
|
41
20
|
try {
|
|
42
21
|
if (!fs.existsSync(LOG_DIR)) {
|
|
@@ -67,11 +46,8 @@ function readApiKeyFromAuth() {
|
|
|
67
46
|
|
|
68
47
|
const providerAuth = auth[PROVIDER_ID];
|
|
69
48
|
if (providerAuth) {
|
|
70
|
-
// Read BOTH key and apiKey fields - OpenCode can use either
|
|
71
49
|
const apiKey = providerAuth.key || providerAuth.apiKey || providerAuth.token || null;
|
|
72
|
-
|
|
73
|
-
return apiKey;
|
|
74
|
-
}
|
|
50
|
+
return apiKey;
|
|
75
51
|
}
|
|
76
52
|
}
|
|
77
53
|
} catch (e) {
|
|
@@ -80,6 +56,72 @@ function readApiKeyFromAuth() {
|
|
|
80
56
|
return null;
|
|
81
57
|
}
|
|
82
58
|
|
|
59
|
+
async function fetchModels() {
|
|
60
|
+
return new Promise((resolve) => {
|
|
61
|
+
const apiKey = readApiKeyFromAuth();
|
|
62
|
+
|
|
63
|
+
const options = {
|
|
64
|
+
hostname: 'qwen.aikit.club',
|
|
65
|
+
port: 443,
|
|
66
|
+
path: '/v1/models',
|
|
67
|
+
method: 'GET',
|
|
68
|
+
headers: {
|
|
69
|
+
'Host': 'qwen.aikit.club',
|
|
70
|
+
'Content-Type': 'application/json'
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
if (apiKey) {
|
|
75
|
+
options.headers['Authorization'] = `Bearer ${apiKey}`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const req = https.request(options, (res) => {
|
|
79
|
+
let data = '';
|
|
80
|
+
res.on('data', chunk => data += chunk);
|
|
81
|
+
res.on('end', () => {
|
|
82
|
+
try {
|
|
83
|
+
const json = JSON.parse(data);
|
|
84
|
+
if (json.data && Array.isArray(json.data)) {
|
|
85
|
+
const models = {};
|
|
86
|
+
json.data.forEach(model => {
|
|
87
|
+
models[model.id] = { name: model.id };
|
|
88
|
+
});
|
|
89
|
+
log(`[Models] Fetched ${Object.keys(models).length} models`);
|
|
90
|
+
resolve(models);
|
|
91
|
+
} else {
|
|
92
|
+
log(`[Models] Unexpected response: ${data.substring(0, 200)}`);
|
|
93
|
+
resolve(getDefaultModels());
|
|
94
|
+
}
|
|
95
|
+
} catch (e) {
|
|
96
|
+
log(`[Models] Parse error: ${e.message}`);
|
|
97
|
+
resolve(getDefaultModels());
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
req.on('error', (e) => {
|
|
103
|
+
log(`[Models] Error: ${e.message}`);
|
|
104
|
+
resolve(getDefaultModels());
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
req.end();
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function getDefaultModels() {
|
|
112
|
+
return {
|
|
113
|
+
'qwen-max': { name: 'Qwen Max' },
|
|
114
|
+
'qwen2.5-max': { name: 'Qwen2.5 Max' },
|
|
115
|
+
'qwen2.5-plus': { name: 'Qwen2.5 Plus' },
|
|
116
|
+
'qwen2.5-turbo': { name: 'Qwen2.5 Turbo' },
|
|
117
|
+
'qwq-32b': { name: 'QWQ 32B' },
|
|
118
|
+
'qwen-deep-research': { name: 'Qwen Deep Research' }
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
let cachedModels = null;
|
|
123
|
+
let proxyPort = 0;
|
|
124
|
+
|
|
83
125
|
function startProxy() {
|
|
84
126
|
return new Promise((resolve) => {
|
|
85
127
|
const server = http.createServer(async (req, res) => {
|
|
@@ -99,7 +141,8 @@ function startProxy() {
|
|
|
99
141
|
res.end(JSON.stringify({
|
|
100
142
|
status: 'ok',
|
|
101
143
|
provider: 'qwen',
|
|
102
|
-
hasKey: !!apiKey
|
|
144
|
+
hasKey: !!apiKey,
|
|
145
|
+
modelsCount: cachedModels ? Object.keys(cachedModels).length : 0
|
|
103
146
|
}));
|
|
104
147
|
return;
|
|
105
148
|
}
|
|
@@ -142,9 +185,9 @@ function startProxy() {
|
|
|
142
185
|
});
|
|
143
186
|
|
|
144
187
|
server.listen(0, '127.0.0.1', () => {
|
|
145
|
-
|
|
146
|
-
log(`[Proxy] Started on port ${
|
|
147
|
-
resolve(
|
|
188
|
+
proxyPort = server.address().port;
|
|
189
|
+
log(`[Proxy] Started on port ${proxyPort}`);
|
|
190
|
+
resolve(proxyPort);
|
|
148
191
|
});
|
|
149
192
|
|
|
150
193
|
server.on('error', (e) => {
|
|
@@ -157,8 +200,13 @@ function startProxy() {
|
|
|
157
200
|
export const QwenPlugin = async (ctx) => {
|
|
158
201
|
log('[Plugin] Starting Qwen Plugin...');
|
|
159
202
|
|
|
160
|
-
|
|
161
|
-
|
|
203
|
+
await startProxy();
|
|
204
|
+
|
|
205
|
+
// Fetch models from API
|
|
206
|
+
cachedModels = await fetchModels();
|
|
207
|
+
log(`[Plugin] Loaded ${Object.keys(cachedModels).length} models`);
|
|
208
|
+
|
|
209
|
+
const localBaseUrl = `http://127.0.0.1:${proxyPort}/v1`;
|
|
162
210
|
|
|
163
211
|
return {
|
|
164
212
|
config: async (config) => {
|
|
@@ -170,10 +218,10 @@ export const QwenPlugin = async (ctx) => {
|
|
|
170
218
|
id: PROVIDER_ID,
|
|
171
219
|
name: 'Qwen AI',
|
|
172
220
|
options: { baseURL: localBaseUrl },
|
|
173
|
-
models:
|
|
221
|
+
models: cachedModels
|
|
174
222
|
};
|
|
175
223
|
|
|
176
|
-
log(`[Hook] Registered provider with ${Object.keys(
|
|
224
|
+
log(`[Hook] Registered provider with ${Object.keys(cachedModels).length} models`);
|
|
177
225
|
}
|
|
178
226
|
};
|
|
179
227
|
};
|