qwen-opencode-provider 2.0.0 → 2.0.2
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 +88 -34
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* OpenCode Qwen API Plugin
|
|
3
3
|
*
|
|
4
|
-
* Uses local proxy approach
|
|
5
|
-
* No manual config needed!
|
|
6
|
-
*
|
|
7
|
-
* Usage:
|
|
8
|
-
* 1. Add to opencode.json: { "plugin": ["qwen-opencode-provider"] }
|
|
9
|
-
* 2. Run /connect -> Select "Other" -> Enter "qwen"
|
|
4
|
+
* Uses local proxy approach to handle auth - reads API key from OpenCode auth storage.
|
|
10
5
|
*/
|
|
11
6
|
|
|
12
7
|
import http from 'http';
|
|
8
|
+
import https from 'https';
|
|
13
9
|
import fs from 'fs';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
import os from 'os';
|
|
14
12
|
|
|
15
|
-
const
|
|
13
|
+
const LOG_DIR = path.join(os.homedir(), '.cache', 'opencode', 'qwen-plugin');
|
|
14
|
+
const LOG_FILE = path.join(LOG_DIR, 'debug.log');
|
|
16
15
|
const PROVIDER_ID = 'qwen';
|
|
17
16
|
const API_BASE_URL = 'https://qwen.aikit.club/v1';
|
|
18
17
|
|
|
@@ -37,19 +36,63 @@ const QWEN_MODELS = {
|
|
|
37
36
|
'qwen-cogview': { name: 'Qwen CogView' }
|
|
38
37
|
};
|
|
39
38
|
|
|
39
|
+
function ensureLogDir() {
|
|
40
|
+
try {
|
|
41
|
+
if (!fs.existsSync(LOG_DIR)) {
|
|
42
|
+
fs.mkdirSync(LOG_DIR, { recursive: true });
|
|
43
|
+
}
|
|
44
|
+
} catch (e) { }
|
|
45
|
+
}
|
|
46
|
+
|
|
40
47
|
function log(msg) {
|
|
41
48
|
try {
|
|
49
|
+
ensureLogDir();
|
|
42
50
|
fs.appendFileSync(LOG_FILE, `[${new Date().toISOString()}] ${msg}\n`);
|
|
43
51
|
} catch (e) { }
|
|
44
52
|
}
|
|
45
53
|
|
|
46
|
-
|
|
54
|
+
function getAuthFilePath() {
|
|
55
|
+
const home = os.homedir();
|
|
56
|
+
return path.join(home, '.local', 'share', 'opencode', 'auth.json');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function readApiKeyFromAuth() {
|
|
60
|
+
try {
|
|
61
|
+
const authPath = getAuthFilePath();
|
|
62
|
+
log(`[Auth] Checking auth file: ${authPath}`);
|
|
63
|
+
|
|
64
|
+
if (fs.existsSync(authPath)) {
|
|
65
|
+
const content = fs.readFileSync(authPath, 'utf-8');
|
|
66
|
+
const auth = JSON.parse(content);
|
|
67
|
+
|
|
68
|
+
log(`[Auth] Auth file contents keys: ${Object.keys(auth).join(', ')}`);
|
|
69
|
+
|
|
70
|
+
if (auth[PROVIDER_ID]?.apiKey) {
|
|
71
|
+
const keyPreview = auth[PROVIDER_ID].apiKey.substring(0, 20) + '...';
|
|
72
|
+
log(`[Auth] Found API key for ${PROVIDER_ID}: ${keyPreview}`);
|
|
73
|
+
return auth[PROVIDER_ID].apiKey;
|
|
74
|
+
} else {
|
|
75
|
+
log(`[Auth] No apiKey found for ${PROVIDER_ID}`);
|
|
76
|
+
|
|
77
|
+
// Also check other possible keys
|
|
78
|
+
for (const key of Object.keys(auth)) {
|
|
79
|
+
log(`[Auth] Key ${key}: ${JSON.stringify(auth[key])}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
} else {
|
|
83
|
+
log(`[Auth] Auth file does not exist`);
|
|
84
|
+
}
|
|
85
|
+
} catch (e) {
|
|
86
|
+
log(`[Auth] Error reading auth: ${e.message}\n${e.stack}`);
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
let serverInstance = null;
|
|
47
92
|
|
|
48
93
|
function startProxy() {
|
|
49
94
|
return new Promise((resolve) => {
|
|
50
95
|
const server = http.createServer(async (req, res) => {
|
|
51
|
-
log(`[Proxy] Request: ${req.method} ${req.url}`);
|
|
52
|
-
|
|
53
96
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
54
97
|
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
55
98
|
res.setHeader('Access-Control-Allow-Headers', '*');
|
|
@@ -61,36 +104,49 @@ function startProxy() {
|
|
|
61
104
|
}
|
|
62
105
|
|
|
63
106
|
if (req.method === 'GET' && req.url === '/health') {
|
|
107
|
+
const apiKey = readApiKeyFromAuth();
|
|
64
108
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
65
|
-
res.end(JSON.stringify({
|
|
109
|
+
res.end(JSON.stringify({
|
|
110
|
+
status: 'ok',
|
|
111
|
+
provider: 'qwen',
|
|
112
|
+
hasKey: !!apiKey
|
|
113
|
+
}));
|
|
66
114
|
return;
|
|
67
115
|
}
|
|
68
116
|
|
|
69
117
|
if (req.url.startsWith('/v1/')) {
|
|
70
|
-
const
|
|
118
|
+
const apiKey = readApiKeyFromAuth();
|
|
71
119
|
|
|
120
|
+
log(`[Proxy] Request: ${req.method} ${req.url}`);
|
|
121
|
+
log(`[Proxy] Has API key: ${!!apiKey}`);
|
|
122
|
+
|
|
72
123
|
const options = {
|
|
73
|
-
hostname:
|
|
124
|
+
hostname: 'qwen.aikit.club',
|
|
74
125
|
port: 443,
|
|
75
126
|
path: req.url,
|
|
76
127
|
method: req.method,
|
|
77
128
|
headers: {
|
|
78
129
|
...req.headers,
|
|
79
|
-
'Host':
|
|
130
|
+
'Host': 'qwen.aikit.club',
|
|
131
|
+
'Content-Type': 'application/json'
|
|
80
132
|
}
|
|
81
133
|
};
|
|
82
134
|
|
|
83
|
-
if (
|
|
84
|
-
options.headers['Authorization'] = `Bearer ${
|
|
135
|
+
if (apiKey) {
|
|
136
|
+
options.headers['Authorization'] = `Bearer ${apiKey}`;
|
|
137
|
+
log(`[Proxy] Added Authorization: Bearer ${apiKey.substring(0, 20)}...`);
|
|
138
|
+
} else {
|
|
139
|
+
log(`[Proxy] NO API KEY - this will likely fail!`);
|
|
85
140
|
}
|
|
86
141
|
|
|
87
142
|
const proxyReq = https.request(options, (proxyRes) => {
|
|
143
|
+
log(`[Proxy] Response status: ${proxyRes.statusCode}`);
|
|
88
144
|
res.writeHead(proxyRes.statusCode, proxyRes.headers);
|
|
89
145
|
proxyRes.pipe(res, { end: true });
|
|
90
146
|
});
|
|
91
147
|
|
|
92
148
|
proxyReq.on('error', (e) => {
|
|
93
|
-
log(`[Proxy] Error: ${e}`);
|
|
149
|
+
log(`[Proxy] Error: ${e.message}`);
|
|
94
150
|
res.writeHead(500);
|
|
95
151
|
res.end(JSON.stringify({ error: String(e) }));
|
|
96
152
|
});
|
|
@@ -99,6 +155,7 @@ function startProxy() {
|
|
|
99
155
|
return;
|
|
100
156
|
}
|
|
101
157
|
|
|
158
|
+
log(`[Proxy] Unknown request: ${req.method} ${req.url}`);
|
|
102
159
|
res.writeHead(404);
|
|
103
160
|
res.end('Not Found');
|
|
104
161
|
});
|
|
@@ -106,24 +163,31 @@ function startProxy() {
|
|
|
106
163
|
server.listen(0, '127.0.0.1', () => {
|
|
107
164
|
const port = server.address().port;
|
|
108
165
|
log(`[Proxy] Started on port ${port}`);
|
|
166
|
+
serverInstance = server;
|
|
109
167
|
resolve(port);
|
|
110
168
|
});
|
|
111
169
|
|
|
112
170
|
server.on('error', (e) => {
|
|
113
|
-
log(`[Proxy] Fatal Error: ${e}`);
|
|
171
|
+
log(`[Proxy] Fatal Error: ${e.message}`);
|
|
114
172
|
resolve(0);
|
|
115
173
|
});
|
|
116
174
|
});
|
|
117
175
|
}
|
|
118
176
|
|
|
119
|
-
import https from 'https';
|
|
120
|
-
|
|
121
177
|
export const QwenPlugin = async (ctx) => {
|
|
122
|
-
log('[Plugin]
|
|
178
|
+
log('[Plugin] ====== Qwen Plugin Starting ======');
|
|
179
|
+
log(`[Plugin] Home: ${os.homedir()}`);
|
|
180
|
+
log(`[Plugin] Log file: ${LOG_FILE}`);
|
|
181
|
+
|
|
182
|
+
// Check auth immediately
|
|
183
|
+
const existingKey = readApiKeyFromAuth();
|
|
184
|
+
log(`[Plugin] Initial auth check - has key: ${!!existingKey}`);
|
|
123
185
|
|
|
124
186
|
const port = await startProxy();
|
|
125
187
|
const localBaseUrl = `http://127.0.0.1:${port}/v1`;
|
|
126
188
|
|
|
189
|
+
log(`[Plugin] Local proxy URL: ${localBaseUrl}`);
|
|
190
|
+
|
|
127
191
|
return {
|
|
128
192
|
async config(config) {
|
|
129
193
|
log('[Hook] config() called');
|
|
@@ -140,22 +204,12 @@ export const QwenPlugin = async (ctx) => {
|
|
|
140
204
|
log(`[Hook] Registered provider with ${Object.keys(QWEN_MODELS).length} models`);
|
|
141
205
|
},
|
|
142
206
|
|
|
143
|
-
async 'installation.updated'({ installation }) {
|
|
144
|
-
log('[Hook] installation.updated() called');
|
|
145
|
-
|
|
146
|
-
if (installation?.auth?.[PROVIDER_ID]?.apiKey) {
|
|
147
|
-
cachedApiKey = installation.auth[PROVIDER_ID].apiKey;
|
|
148
|
-
log('[Hook] API Key updated');
|
|
149
|
-
}
|
|
150
|
-
},
|
|
151
|
-
|
|
152
207
|
auth: {
|
|
153
208
|
provider: PROVIDER_ID,
|
|
154
209
|
loader: async (auth) => {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
158
|
-
return { apiKey: cachedApiKey };
|
|
210
|
+
const apiKey = auth?.apiKey || '';
|
|
211
|
+
log(`[Auth] loader called, hasKey: ${!!apiKey}`);
|
|
212
|
+
return { apiKey };
|
|
159
213
|
},
|
|
160
214
|
methods: [
|
|
161
215
|
{ type: 'api', label: 'API Key' }
|