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.
Files changed (2) hide show
  1. package/index.js +88 -34
  2. 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 (like Pollinations plugin) to handle auth.
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 LOG_FILE = '/tmp/opencode_qwen_plugin.log';
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
- let cachedApiKey = '';
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({ status: 'ok', provider: 'qwen' }));
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 targetUrl = API_BASE_URL + req.url;
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: new URL(API_BASE_URL).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': new URL(API_BASE_URL).host
130
+ 'Host': 'qwen.aikit.club',
131
+ 'Content-Type': 'application/json'
80
132
  }
81
133
  };
82
134
 
83
- if (cachedApiKey) {
84
- options.headers['Authorization'] = `Bearer ${cachedApiKey}`;
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] Initializing Qwen 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
- if (auth?.apiKey) {
156
- cachedApiKey = auth.apiKey;
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' }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qwen-opencode-provider",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "OpenCode plugin for Qwen API - auto adds provider with 28+ models",
5
5
  "main": "index.js",
6
6
  "type": "module",