qwen-opencode-provider 1.0.9 → 2.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.
Files changed (3) hide show
  1. package/README.md +29 -37
  2. package/index.js +143 -27
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,43 +1,32 @@
1
1
  # OpenCode Qwen Plugin
2
2
 
3
- OpenCode plugin for Qwen API - auto-configures models.
3
+ OpenCode plugin for Qwen AI - auto-registers provider with local proxy (no config needed!).
4
4
 
5
- ## Important Limitation
6
-
7
- ⚠️ **Plugin cannot auto-register provider** - You must add provider config manually to `opencode.json`.
5
+ ## Features
8
6
 
9
- Plugin only auto-configures the model list.
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
10
11
 
11
12
  ## Installation
12
13
 
13
- ### Step 1: Add to opencode.json
14
-
14
+ Add to `opencode.json`:
15
15
  ```json
16
16
  {
17
17
  "plugin": ["qwen-opencode-provider"]
18
18
  }
19
19
  ```
20
20
 
21
- ### Step 2: Add Provider Config
21
+ ## Usage
22
22
 
23
- You must manually add this provider config to your `opencode.json`:
23
+ ### Connect
24
24
 
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_HERE"
36
- },
37
- "models": {}
38
- }
39
- }
40
- }
25
+ ```bash
26
+ /connect
27
+ # Select: Other
28
+ # Enter: qwen
29
+ # Enter your Qwen token
41
30
  ```
42
31
 
43
32
  ### Get Token
@@ -46,30 +35,33 @@ You must manually add this provider config to your `opencode.json`:
46
35
  2. Open Developer Console (F12)
47
36
  3. Run: `localStorage.getItem('token')`
48
37
 
49
- ## Usage
38
+ ### Select Model
50
39
 
51
40
  ```bash
52
41
  /models
53
- # Select Qwen model
42
+ # Choose any Qwen model
54
43
  ```
55
44
 
56
45
  ## Supported Models
57
46
 
58
- Plugin auto-configures these models:
59
-
60
47
  | Model | Context | Output |
61
48
  |-------|---------|--------|
62
- | qwen3-max-2026-01-23 | 262K | 32K |
63
- | qwen3-vl-plus | 262K | 32K |
49
+ | qwen3-max | 262K | 32K |
64
50
  | qwen3-coder-plus | 1M | 65K |
51
+ | qwen3-vl-plus | 262K | 32K |
65
52
  | qwen3-vl-32b | 131K | 32K |
66
- | qwen3-coder-flash | 262K | 65K |
67
53
  | qwq-32b | - | - |
68
54
  | qwen-deep-research | - | - |
69
55
 
70
- ## Features
56
+ ## How It Works
57
+
58
+ This plugin uses a **local proxy server** (like Pollinations plugin):
59
+
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!
64
+
65
+ ## License
71
66
 
72
- - 👁️ Vision (image analysis)
73
- - 🌐 Web Search
74
- - 🧠 Thinking Mode
75
- - 👨‍💻 Code Generation
67
+ MIT
package/index.js CHANGED
@@ -1,49 +1,165 @@
1
1
  /**
2
2
  * OpenCode Qwen API Plugin
3
3
  *
4
- * Auto-configures Qwen provider models - but requires manual provider config.
4
+ * Uses local proxy approach (like Pollinations plugin) to handle auth.
5
+ * No manual config needed!
5
6
  *
6
7
  * Usage:
7
- * 1. Add plugin to opencode.json
8
- * 2. Add provider config (see below)
8
+ * 1. Add to opencode.json: { "plugin": ["qwen-opencode-provider"] }
9
+ * 2. Run /connect -> Select "Other" -> Enter "qwen"
9
10
  */
10
11
 
12
+ import http from 'http';
13
+ import fs from 'fs';
14
+
15
+ const LOG_FILE = '/tmp/opencode_qwen_plugin.log';
16
+ const PROVIDER_ID = 'qwen';
17
+ const API_BASE_URL = 'https://qwen.aikit.club/v1';
18
+
11
19
  const QWEN_MODELS = {
12
- 'qwen3-max-2026-01-23': { name: 'Qwen3-Max', limit: { context: 262144, output: 32768 } },
13
- 'qwen3-vl-plus': { name: 'Qwen3-VL-235B-A22B', limit: { context: 262144, output: 32768 } },
14
- 'qwen3-coder-plus': { name: 'Qwen3-Coder', limit: { context: 1048576, output: 65536 } },
15
- 'qwen3-vl-32b': { name: 'Qwen3-VL-32B', limit: { context: 131072, output: 32768 } },
16
- 'qwen3-vl-30b-a3b': { name: 'Qwen3-VL-30B-A3B', limit: { context: 131072, output: 32768 } },
17
- 'qwen3-omni-flash-2025-12-01': { name: 'Qwen3-Omni-Flash', limit: { context: 65536, output: 13684 } },
18
- 'qwen3-30b-a3b': { name: 'Qwen3-30B-A3B', limit: { context: 131072, output: 32768 } },
19
- 'qwen3-coder-30b-a3b-instruct': { name: 'Qwen3-Coder-Flash', limit: { context: 262144, output: 65536 } },
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 } },
20
29
  'qwen-max': { name: 'Qwen Max' },
21
30
  'qwen2.5-max': { name: 'Qwen2.5 Max' },
22
31
  'qwen2.5-plus': { name: 'Qwen2.5 Plus' },
23
32
  'qwen2.5-turbo': { name: 'Qwen2.5 Turbo' },
24
33
  'qwq-32b': { name: 'QWQ 32B' },
25
- 'qwen-deep-research': { name: 'Qwen Deep Research' }
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' }
26
38
  };
27
39
 
40
+ function log(msg) {
41
+ try {
42
+ fs.appendFileSync(LOG_FILE, `[${new Date().toISOString()}] ${msg}\n`);
43
+ } catch (e) { }
44
+ }
45
+
46
+ let cachedApiKey = '';
47
+
48
+ function startProxy() {
49
+ return new Promise((resolve) => {
50
+ const server = http.createServer(async (req, res) => {
51
+ log(`[Proxy] Request: ${req.method} ${req.url}`);
52
+
53
+ res.setHeader('Access-Control-Allow-Origin', '*');
54
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
55
+ res.setHeader('Access-Control-Allow-Headers', '*');
56
+
57
+ if (req.method === 'OPTIONS') {
58
+ res.writeHead(204);
59
+ res.end();
60
+ return;
61
+ }
62
+
63
+ if (req.method === 'GET' && req.url === '/health') {
64
+ res.writeHead(200, { 'Content-Type': 'application/json' });
65
+ res.end(JSON.stringify({ status: 'ok', provider: 'qwen' }));
66
+ return;
67
+ }
68
+
69
+ if (req.url.startsWith('/v1/')) {
70
+ const targetUrl = API_BASE_URL + req.url;
71
+
72
+ const options = {
73
+ hostname: new URL(API_BASE_URL).hostname,
74
+ port: 443,
75
+ path: req.url,
76
+ method: req.method,
77
+ headers: {
78
+ ...req.headers,
79
+ 'Host': new URL(API_BASE_URL).host
80
+ }
81
+ };
82
+
83
+ if (cachedApiKey) {
84
+ options.headers['Authorization'] = `Bearer ${cachedApiKey}`;
85
+ }
86
+
87
+ const proxyReq = https.request(options, (proxyRes) => {
88
+ res.writeHead(proxyRes.statusCode, proxyRes.headers);
89
+ proxyRes.pipe(res, { end: true });
90
+ });
91
+
92
+ proxyReq.on('error', (e) => {
93
+ log(`[Proxy] Error: ${e}`);
94
+ res.writeHead(500);
95
+ res.end(JSON.stringify({ error: String(e) }));
96
+ });
97
+
98
+ req.pipe(proxyReq, { end: true });
99
+ return;
100
+ }
101
+
102
+ res.writeHead(404);
103
+ res.end('Not Found');
104
+ });
105
+
106
+ server.listen(0, '127.0.0.1', () => {
107
+ const port = server.address().port;
108
+ log(`[Proxy] Started on port ${port}`);
109
+ resolve(port);
110
+ });
111
+
112
+ server.on('error', (e) => {
113
+ log(`[Proxy] Fatal Error: ${e}`);
114
+ resolve(0);
115
+ });
116
+ });
117
+ }
118
+
119
+ import https from 'https';
120
+
28
121
  export const QwenPlugin = async (ctx) => {
122
+ log('[Plugin] Initializing Qwen Plugin...');
123
+
124
+ const port = await startProxy();
125
+ const localBaseUrl = `http://127.0.0.1:${port}/v1`;
126
+
29
127
  return {
30
- config: async (config) => {
31
- if (!config.provider) {
32
- config.provider = {};
33
- }
128
+ async config(config) {
129
+ log('[Hook] config() called');
130
+
131
+ if (!config.provider) config.provider = {};
132
+
133
+ config.provider[PROVIDER_ID] = {
134
+ id: PROVIDER_ID,
135
+ name: 'Qwen AI',
136
+ options: { baseURL: localBaseUrl },
137
+ models: QWEN_MODELS
138
+ };
139
+
140
+ log(`[Hook] Registered provider with ${Object.keys(QWEN_MODELS).length} models`);
141
+ },
142
+
143
+ async 'installation.updated'({ installation }) {
144
+ log('[Hook] installation.updated() called');
34
145
 
35
- if (!config.provider.qwen) {
36
- config.provider.qwen = {
37
- npm: '@ai-sdk/openai-compatible',
38
- name: 'Qwen',
39
- options: {
40
- baseURL: 'https://qwen.aikit.club/v1'
41
- },
42
- models: QWEN_MODELS
43
- };
44
- } else if (config.provider.qwen?.models) {
45
- config.provider.qwen.models = { ...QWEN_MODELS, ...config.provider.qwen.models };
146
+ if (installation?.auth?.[PROVIDER_ID]?.apiKey) {
147
+ cachedApiKey = installation.auth[PROVIDER_ID].apiKey;
148
+ log('[Hook] API Key updated');
46
149
  }
150
+ },
151
+
152
+ auth: {
153
+ provider: PROVIDER_ID,
154
+ loader: async (auth) => {
155
+ if (auth?.apiKey) {
156
+ cachedApiKey = auth.apiKey;
157
+ }
158
+ return { apiKey: cachedApiKey };
159
+ },
160
+ methods: [
161
+ { type: 'api', label: 'API Key' }
162
+ ]
47
163
  }
48
164
  };
49
165
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qwen-opencode-provider",
3
- "version": "1.0.9",
3
+ "version": "2.0.0",
4
4
  "description": "OpenCode plugin for Qwen API - auto adds provider with 28+ models",
5
5
  "main": "index.js",
6
6
  "type": "module",