qwen-opencode-provider 2.1.0 → 3.0.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 +147 -13
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* OpenCode Qwen API Plugin
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* Usage:
|
|
7
|
-
* 1. Add plugin to opencode.json
|
|
8
|
-
* 2. Add provider config manually (see README)
|
|
4
|
+
* Uses local proxy (like Pollinations) to handle auth automatically.
|
|
5
|
+
* No manual config needed!
|
|
9
6
|
*/
|
|
10
7
|
|
|
8
|
+
import http from 'http';
|
|
9
|
+
import https from 'https';
|
|
10
|
+
import fs from 'fs';
|
|
11
|
+
import path from 'path';
|
|
12
|
+
import os from 'os';
|
|
13
|
+
|
|
14
|
+
const LOG_DIR = path.join(os.homedir(), '.cache', 'opencode', 'qwen-plugin');
|
|
15
|
+
const LOG_FILE = path.join(LOG_DIR, 'debug.log');
|
|
16
|
+
const PROVIDER_ID = 'qwen';
|
|
17
|
+
const API_BASE_URL = 'https://qwen.aikit.club/v1';
|
|
18
|
+
|
|
11
19
|
const QWEN_MODELS = {
|
|
12
20
|
'qwen3-max': { name: 'Qwen3 Max', limit: { context: 262144, output: 32768 } },
|
|
13
21
|
'qwen3-max-2026-01-23': { name: 'Qwen3 Max', limit: { context: 262144, output: 32768 } },
|
|
@@ -29,17 +37,143 @@ const QWEN_MODELS = {
|
|
|
29
37
|
'qwen-cogview': { name: 'Qwen CogView' }
|
|
30
38
|
};
|
|
31
39
|
|
|
40
|
+
function ensureLogDir() {
|
|
41
|
+
try {
|
|
42
|
+
if (!fs.existsSync(LOG_DIR)) {
|
|
43
|
+
fs.mkdirSync(LOG_DIR, { recursive: true });
|
|
44
|
+
}
|
|
45
|
+
} catch (e) { }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function log(msg) {
|
|
49
|
+
try {
|
|
50
|
+
ensureLogDir();
|
|
51
|
+
fs.appendFileSync(LOG_FILE, `[${new Date().toISOString()}] ${msg}\n`);
|
|
52
|
+
} catch (e) { }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function getAuthFilePath() {
|
|
56
|
+
const home = os.homedir();
|
|
57
|
+
return path.join(home, '.local', 'share', 'opencode', 'auth.json');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function readApiKeyFromAuth() {
|
|
61
|
+
try {
|
|
62
|
+
const authPath = getAuthFilePath();
|
|
63
|
+
|
|
64
|
+
if (fs.existsSync(authPath)) {
|
|
65
|
+
const content = fs.readFileSync(authPath, 'utf-8');
|
|
66
|
+
const auth = JSON.parse(content);
|
|
67
|
+
|
|
68
|
+
const providerAuth = auth[PROVIDER_ID];
|
|
69
|
+
if (providerAuth) {
|
|
70
|
+
// Read BOTH key and apiKey fields - OpenCode can use either
|
|
71
|
+
const apiKey = providerAuth.key || providerAuth.apiKey || providerAuth.token || null;
|
|
72
|
+
if (apiKey) {
|
|
73
|
+
return apiKey;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
} catch (e) {
|
|
78
|
+
log(`[Auth] Error: ${e.message}`);
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function startProxy() {
|
|
84
|
+
return new Promise((resolve) => {
|
|
85
|
+
const server = http.createServer(async (req, res) => {
|
|
86
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
87
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
88
|
+
res.setHeader('Access-Control-Allow-Headers', '*');
|
|
89
|
+
|
|
90
|
+
if (req.method === 'OPTIONS') {
|
|
91
|
+
res.writeHead(204);
|
|
92
|
+
res.end();
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (req.method === 'GET' && req.url === '/health') {
|
|
97
|
+
const apiKey = readApiKeyFromAuth();
|
|
98
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
99
|
+
res.end(JSON.stringify({
|
|
100
|
+
status: 'ok',
|
|
101
|
+
provider: 'qwen',
|
|
102
|
+
hasKey: !!apiKey
|
|
103
|
+
}));
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (req.url.startsWith('/v1/')) {
|
|
108
|
+
const apiKey = readApiKeyFromAuth();
|
|
109
|
+
|
|
110
|
+
const options = {
|
|
111
|
+
hostname: 'qwen.aikit.club',
|
|
112
|
+
port: 443,
|
|
113
|
+
path: req.url,
|
|
114
|
+
method: req.method,
|
|
115
|
+
headers: {
|
|
116
|
+
...req.headers,
|
|
117
|
+
'Host': 'qwen.aikit.club',
|
|
118
|
+
'Content-Type': 'application/json'
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
if (apiKey) {
|
|
123
|
+
options.headers['Authorization'] = `Bearer ${apiKey}`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const proxyReq = https.request(options, (proxyRes) => {
|
|
127
|
+
res.writeHead(proxyRes.statusCode, proxyRes.headers);
|
|
128
|
+
proxyRes.pipe(res, { end: true });
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
proxyReq.on('error', (e) => {
|
|
132
|
+
res.writeHead(500);
|
|
133
|
+
res.end(JSON.stringify({ error: String(e) }));
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
req.pipe(proxyReq, { end: true });
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
res.writeHead(404);
|
|
141
|
+
res.end('Not Found');
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
server.listen(0, '127.0.0.1', () => {
|
|
145
|
+
const port = server.address().port;
|
|
146
|
+
log(`[Proxy] Started on port ${port}`);
|
|
147
|
+
resolve(port);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
server.on('error', (e) => {
|
|
151
|
+
log(`[Proxy] Error: ${e.message}`);
|
|
152
|
+
resolve(0);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
32
157
|
export const QwenPlugin = async (ctx) => {
|
|
158
|
+
log('[Plugin] Starting Qwen Plugin...');
|
|
159
|
+
|
|
160
|
+
const port = await startProxy();
|
|
161
|
+
const localBaseUrl = `http://127.0.0.1:${port}/v1`;
|
|
162
|
+
|
|
33
163
|
return {
|
|
34
164
|
config: async (config) => {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
165
|
+
log('[Hook] config() called');
|
|
166
|
+
|
|
167
|
+
if (!config.provider) config.provider = {};
|
|
168
|
+
|
|
169
|
+
config.provider[PROVIDER_ID] = {
|
|
170
|
+
id: PROVIDER_ID,
|
|
171
|
+
name: 'Qwen AI',
|
|
172
|
+
options: { baseURL: localBaseUrl },
|
|
173
|
+
models: QWEN_MODELS
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
log(`[Hook] Registered provider with ${Object.keys(QWEN_MODELS).length} models`);
|
|
43
177
|
}
|
|
44
178
|
};
|
|
45
179
|
};
|