qwen-opencode-provider 2.0.3 → 2.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.
Files changed (3) hide show
  1. package/README.md +40 -24
  2. package/index.js +14 -190
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,32 +1,43 @@
1
1
  # OpenCode Qwen Plugin
2
2
 
3
- OpenCode plugin for Qwen AI - auto-registers provider with local proxy (no config needed!).
3
+ OpenCode plugin for Qwen API - auto-configures models.
4
4
 
5
- ## Features
5
+ ## Important
6
+
7
+ ⚠️ **Plugin requires manual provider config** - OpenCode doesn't support auto-registering providers via plugins yet.
6
8
 
7
- - **No manual config** - Just add plugin!
8
- - ✅ **Local proxy** - Handles auth automatically
9
- - ✅ **28+ Models** - All Qwen models pre-configured
10
- - ✅ **Native auth** - Uses OpenCode's /connect
9
+ Plugin only auto-configures the model list.
11
10
 
12
11
  ## Installation
13
12
 
14
- Add to `opencode.json`:
13
+ ### Step 1: Add plugin to opencode.json
14
+
15
15
  ```json
16
16
  {
17
17
  "plugin": ["qwen-opencode-provider"]
18
18
  }
19
19
  ```
20
20
 
21
- ## Usage
21
+ ### Step 2: Add Provider Config
22
22
 
23
- ### Connect
23
+ Add this to your `opencode.json`:
24
24
 
25
- ```bash
26
- /connect
27
- # Select: Other
28
- # Enter: qwen
29
- # Enter your Qwen token
25
+ ```json
26
+ {
27
+ "$schema": "https://opencode.ai/config.json",
28
+ "plugin": ["qwen-opencode-provider"],
29
+ "provider": {
30
+ "qwen": {
31
+ "npm": "@ai-sdk/openai-compatible",
32
+ "name": "Qwen",
33
+ "options": {
34
+ "baseURL": "https://qwen.aikit.club/v1",
35
+ "apiKey": "YOUR_QWEN_TOKEN"
36
+ },
37
+ "models": {}
38
+ }
39
+ }
40
+ }
30
41
  ```
31
42
 
32
43
  ### Get Token
@@ -35,32 +46,37 @@ Add to `opencode.json`:
35
46
  2. Open Developer Console (F12)
36
47
  3. Run: `localStorage.getItem('token')`
37
48
 
38
- ### Select Model
49
+ ## Usage
39
50
 
40
51
  ```bash
52
+ /connect
53
+ # Select: Other
54
+ # Enter: qwen
55
+
41
56
  /models
42
- # Choose any Qwen model
57
+ # Select Qwen model
43
58
  ```
44
59
 
45
60
  ## Supported Models
46
61
 
62
+ Plugin auto-configures these models:
63
+
47
64
  | Model | Context | Output |
48
65
  |-------|---------|--------|
49
66
  | qwen3-max | 262K | 32K |
50
- | qwen3-coder-plus | 1M | 65K |
51
67
  | qwen3-vl-plus | 262K | 32K |
68
+ | qwen3-coder-plus | 1M | 65K |
52
69
  | qwen3-vl-32b | 131K | 32K |
70
+ | qwen3-coder-flash | 262K | 65K |
53
71
  | qwq-32b | - | - |
54
72
  | qwen-deep-research | - | - |
55
73
 
56
- ## How It Works
57
-
58
- This plugin uses a **local proxy server** (like Pollinations plugin):
74
+ ## Features
59
75
 
60
- 1. Plugin starts a local HTTP proxy on random port
61
- 2. Registers provider pointing to `localhost:PORT`
62
- 3. Proxy adds Bearer token and forwards to Qwen API
63
- 4. No manual config needed!
76
+ - 👁️ Vision (image analysis)
77
+ - 🌐 Web Search
78
+ - 🧠 Thinking Mode
79
+ - 👨‍💻 Code Generation
64
80
 
65
81
  ## License
66
82
 
package/index.js CHANGED
@@ -1,20 +1,13 @@
1
1
  /**
2
2
  * OpenCode Qwen API Plugin
3
3
  *
4
- * Uses local proxy approach to handle auth - reads API key from OpenCode auth storage.
4
+ * Auto-configures Qwen provider models - requires manual provider config.
5
+ *
6
+ * Usage:
7
+ * 1. Add plugin to opencode.json
8
+ * 2. Add provider config manually (see README)
5
9
  */
6
10
 
7
- import http from 'http';
8
- import https from 'https';
9
- import fs from 'fs';
10
- import path from 'path';
11
- import os from 'os';
12
-
13
- const LOG_DIR = path.join(os.homedir(), '.cache', 'opencode', 'qwen-plugin');
14
- const LOG_FILE = path.join(LOG_DIR, 'debug.log');
15
- const PROVIDER_ID = 'qwen';
16
- const API_BASE_URL = 'https://qwen.aikit.club/v1';
17
-
18
11
  const QWEN_MODELS = {
19
12
  'qwen3-max': { name: 'Qwen3 Max', limit: { context: 262144, output: 32768 } },
20
13
  'qwen3-max-2026-01-23': { name: 'Qwen3 Max', limit: { context: 262144, output: 32768 } },
@@ -36,186 +29,17 @@ const QWEN_MODELS = {
36
29
  'qwen-cogview': { name: 'Qwen CogView' }
37
30
  };
38
31
 
39
- function ensureLogDir() {
40
- try {
41
- if (!fs.existsSync(LOG_DIR)) {
42
- fs.mkdirSync(LOG_DIR, { recursive: true });
43
- }
44
- } catch (e) { }
45
- }
46
-
47
- function log(msg) {
48
- try {
49
- ensureLogDir();
50
- fs.appendFileSync(LOG_FILE, `[${new Date().toISOString()}] ${msg}\n`);
51
- } catch (e) { }
52
- }
53
-
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
- // Try both apiKey and key fields
71
- const providerAuth = auth[PROVIDER_ID];
72
- if (providerAuth) {
73
- const apiKey = providerAuth.apiKey || providerAuth.key || providerAuth.token || null;
74
- if (apiKey) {
75
- const keyPreview = apiKey.substring(0, 20) + '...';
76
- log(`[Auth] Found API key for ${PROVIDER_ID}: ${keyPreview}`);
77
- return apiKey;
78
- } else {
79
- log(`[Auth] Provider exists but no key field: ${JSON.stringify(providerAuth)}`);
80
- }
81
- } else {
82
- log(`[Auth] No provider found for ${PROVIDER_ID}`);
83
- }
84
- } else {
85
- log(`[Auth] Auth file does not exist`);
86
- }
87
- } catch (e) {
88
- log(`[Auth] Error reading auth: ${e.message}\n${e.stack}`);
89
- }
90
- return null;
91
- }
92
-
93
- let serverInstance = null;
94
-
95
- function startProxy() {
96
- return new Promise((resolve) => {
97
- const server = http.createServer(async (req, res) => {
98
- res.setHeader('Access-Control-Allow-Origin', '*');
99
- res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
100
- res.setHeader('Access-Control-Allow-Headers', '*');
101
-
102
- if (req.method === 'OPTIONS') {
103
- res.writeHead(204);
104
- res.end();
105
- return;
106
- }
107
-
108
- if (req.method === 'GET' && req.url === '/health') {
109
- const apiKey = readApiKeyFromAuth();
110
- res.writeHead(200, { 'Content-Type': 'application/json' });
111
- res.end(JSON.stringify({
112
- status: 'ok',
113
- provider: 'qwen',
114
- hasKey: !!apiKey
115
- }));
116
- return;
117
- }
118
-
119
- if (req.url.startsWith('/v1/')) {
120
- const apiKey = readApiKeyFromAuth();
121
-
122
- log(`[Proxy] Request: ${req.method} ${req.url}`);
123
- log(`[Proxy] Has API key: ${!!apiKey}`);
124
-
125
- const options = {
126
- hostname: 'qwen.aikit.club',
127
- port: 443,
128
- path: req.url,
129
- method: req.method,
130
- headers: {
131
- ...req.headers,
132
- 'Host': 'qwen.aikit.club',
133
- 'Content-Type': 'application/json'
134
- }
135
- };
136
-
137
- if (apiKey) {
138
- options.headers['Authorization'] = `Bearer ${apiKey}`;
139
- log(`[Proxy] Added Authorization: Bearer ${apiKey.substring(0, 20)}...`);
140
- } else {
141
- log(`[Proxy] NO API KEY - this will likely fail!`);
142
- }
143
-
144
- const proxyReq = https.request(options, (proxyRes) => {
145
- log(`[Proxy] Response status: ${proxyRes.statusCode}`);
146
- res.writeHead(proxyRes.statusCode, proxyRes.headers);
147
- proxyRes.pipe(res, { end: true });
148
- });
149
-
150
- proxyReq.on('error', (e) => {
151
- log(`[Proxy] Error: ${e.message}`);
152
- res.writeHead(500);
153
- res.end(JSON.stringify({ error: String(e) }));
154
- });
155
-
156
- req.pipe(proxyReq, { end: true });
157
- return;
158
- }
159
-
160
- log(`[Proxy] Unknown request: ${req.method} ${req.url}`);
161
- res.writeHead(404);
162
- res.end('Not Found');
163
- });
164
-
165
- server.listen(0, '127.0.0.1', () => {
166
- const port = server.address().port;
167
- log(`[Proxy] Started on port ${port}`);
168
- serverInstance = server;
169
- resolve(port);
170
- });
171
-
172
- server.on('error', (e) => {
173
- log(`[Proxy] Fatal Error: ${e.message}`);
174
- resolve(0);
175
- });
176
- });
177
- }
178
-
179
32
  export const QwenPlugin = async (ctx) => {
180
- log('[Plugin] ====== Qwen Plugin Starting ======');
181
- log(`[Plugin] Home: ${os.homedir()}`);
182
- log(`[Plugin] Log file: ${LOG_FILE}`);
183
-
184
- // Check auth immediately
185
- const existingKey = readApiKeyFromAuth();
186
- log(`[Plugin] Initial auth check - has key: ${!!existingKey}`);
187
-
188
- const port = await startProxy();
189
- const localBaseUrl = `http://127.0.0.1:${port}/v1`;
190
-
191
- log(`[Plugin] Local proxy URL: ${localBaseUrl}`);
192
-
193
33
  return {
194
- async config(config) {
195
- log('[Hook] config() called');
196
-
197
- if (!config.provider) config.provider = {};
198
-
199
- config.provider[PROVIDER_ID] = {
200
- id: PROVIDER_ID,
201
- name: 'Qwen AI',
202
- options: { baseURL: localBaseUrl },
203
- models: QWEN_MODELS
204
- };
205
-
206
- log(`[Hook] Registered provider with ${Object.keys(QWEN_MODELS).length} models`);
207
- },
208
-
209
- auth: {
210
- provider: PROVIDER_ID,
211
- loader: async (auth) => {
212
- const apiKey = auth?.apiKey || '';
213
- log(`[Auth] loader called, hasKey: ${!!apiKey}`);
214
- return { apiKey };
215
- },
216
- methods: [
217
- { type: 'api', label: 'API Key' }
218
- ]
34
+ config: async (config) => {
35
+ if (!config.provider) {
36
+ config.provider = {};
37
+ }
38
+
39
+ // If provider already exists, just merge models
40
+ if (config.provider.qwen) {
41
+ config.provider.qwen.models = { ...QWEN_MODELS, ...config.provider.qwen.models };
42
+ }
219
43
  }
220
44
  };
221
45
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qwen-opencode-provider",
3
- "version": "2.0.3",
3
+ "version": "2.1.0",
4
4
  "description": "OpenCode plugin for Qwen API - auto adds provider with 28+ models",
5
5
  "main": "index.js",
6
6
  "type": "module",