qwen-opencode-provider 1.0.8 → 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 +28 -22
  2. package/index.js +128 -41
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,6 +1,13 @@
1
1
  # OpenCode Qwen Plugin
2
2
 
3
- OpenCode plugin for Qwen AI - auto-registers provider with 28+ models.
3
+ OpenCode plugin for Qwen AI - auto-registers provider with local proxy (no config needed!).
4
+
5
+ ## Features
6
+
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
4
11
 
5
12
  ## Installation
6
13
 
@@ -35,27 +42,26 @@ Add to `opencode.json`:
35
42
  # Choose any Qwen model
36
43
  ```
37
44
 
38
- ## Features
45
+ ## Supported Models
39
46
 
40
- - 👁️ Vision (image analysis)
41
- - 🌐 Web Search
42
- - 🧠 Thinking Mode
43
- - 🎨 Image Generation
44
- - 👨‍💻 Code Generation
47
+ | Model | Context | Output |
48
+ |-------|---------|--------|
49
+ | qwen3-max | 262K | 32K |
50
+ | qwen3-coder-plus | 1M | 65K |
51
+ | qwen3-vl-plus | 262K | 32K |
52
+ | qwen3-vl-32b | 131K | 32K |
53
+ | qwq-32b | - | - |
54
+ | qwen-deep-research | - | - |
45
55
 
46
- ## Supported Models
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
47
66
 
48
- | Model | Description |
49
- |-------|-------------|
50
- | qwen-max | Latest Qwen Max |
51
- | qwen2.5-max | Best overall + vision + web search |
52
- | qwen2.5-turbo | Fast responses |
53
- | qwen2.5-plus | Balanced performance |
54
- | qwen2.5-coder-32b | Code generation |
55
- | qwen3-coder | Code + tool calling |
56
- | qwen3-max | Best Qwen3 |
57
- | qwq-32b | Reasoning with thinking |
58
- | qwen-deep-research | Research + web search |
59
- | qwen-web-dev | Web development |
60
- | qwen-full-stack | Full-stack apps |
61
- | qwen-cogview | Image generation |
67
+ MIT
package/index.js CHANGED
@@ -1,38 +1,35 @@
1
1
  /**
2
2
  * OpenCode Qwen API Plugin
3
3
  *
4
- * Automatically registers Qwen provider with OpenCode.
5
- * No manual config needed - just add plugin to opencode.json!
4
+ * Uses local proxy approach (like Pollinations plugin) to handle auth.
5
+ * No manual config needed!
6
6
  *
7
7
  * Usage:
8
8
  * 1. Add to opencode.json: { "plugin": ["qwen-opencode-provider"] }
9
9
  * 2. Run /connect -> Select "Other" -> Enter "qwen"
10
10
  */
11
11
 
12
- const QWEN_PROVIDER_ID = 'qwen';
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';
13
18
 
14
19
  const QWEN_MODELS = {
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 } },
15
29
  'qwen-max': { name: 'Qwen Max' },
16
- 'qwen-max-latest': { name: 'Qwen Max Latest' },
17
30
  'qwen2.5-max': { name: 'Qwen2.5 Max' },
18
31
  'qwen2.5-plus': { name: 'Qwen2.5 Plus' },
19
32
  'qwen2.5-turbo': { name: 'Qwen2.5 Turbo' },
20
- 'qwen2.5-14b-instruct-1m': { name: 'Qwen2.5 14B Instruct 1M' },
21
- 'qwen2.5-72b-instruct': { name: 'Qwen2.5 72B Instruct' },
22
- 'qwen2.5-coder-32b-instruct': { name: 'Qwen2.5 Coder 32B' },
23
- 'qwen2.5-omni-7b': { name: 'Qwen2.5 Omni 7B' },
24
- 'qwen2.5-vl-32b-instruct': { name: 'Qwen2.5 VL 32B' },
25
- 'qwen3-next-80b-a3b': { name: 'Qwen3 Next 80B A3B' },
26
- 'qwen3-235b-a22b-2507': { name: 'Qwen3 235B A22B' },
27
- 'qwen3-30b-a3b-2507': { name: 'Qwen3 30B A3B' },
28
- 'qwen3-coder': { name: 'Qwen3 Coder' },
29
- 'qwen3-coder-flash': { name: 'Qwen3 Coder Flash' },
30
- 'qwen3-max': { name: 'Qwen3 Max' },
31
- 'qwen3-omni-flash': { name: 'Qwen3 Omni Flash' },
32
- 'qwen3-vl-235b-a22b': { name: 'Qwen3 VL 235B' },
33
- 'qwen3-vl-32b': { name: 'Qwen3 VL 32B' },
34
- 'qwen3-vl-30b-a3b': { name: 'Qwen3 VL 30B A3B' },
35
- 'qvq-max': { name: 'QVQ Max' },
36
33
  'qwq-32b': { name: 'QWQ 32B' },
37
34
  'qwen-deep-research': { name: 'Qwen Deep Research' },
38
35
  'qwen-web-dev': { name: 'Qwen Web Dev' },
@@ -40,38 +37,128 @@ const QWEN_MODELS = {
40
37
  'qwen-cogview': { name: 'Qwen CogView' }
41
38
  };
42
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
+
43
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
+
44
127
  return {
45
- config: async (config) => {
46
- if (!config.provider) {
47
- config.provider = {};
48
- }
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');
49
145
 
50
- if (!config.provider[QWEN_PROVIDER_ID]) {
51
- config.provider[QWEN_PROVIDER_ID] = {
52
- npm: '@ai-sdk/openai-compatible',
53
- name: 'Qwen AI',
54
- options: {
55
- baseURL: 'https://qwen.aikit.club/v1'
56
- },
57
- models: QWEN_MODELS
58
- };
146
+ if (installation?.auth?.[PROVIDER_ID]?.apiKey) {
147
+ cachedApiKey = installation.auth[PROVIDER_ID].apiKey;
148
+ log('[Hook] API Key updated');
59
149
  }
60
150
  },
61
-
151
+
62
152
  auth: {
63
- provider: QWEN_PROVIDER_ID,
153
+ provider: PROVIDER_ID,
64
154
  loader: async (auth) => {
65
155
  if (auth?.apiKey) {
66
- return { apiKey: auth.apiKey };
156
+ cachedApiKey = auth.apiKey;
67
157
  }
68
- return { apiKey: '' };
158
+ return { apiKey: cachedApiKey };
69
159
  },
70
160
  methods: [
71
- {
72
- type: 'api',
73
- label: 'API Key'
74
- }
161
+ { type: 'api', label: 'API Key' }
75
162
  ]
76
163
  }
77
164
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qwen-opencode-provider",
3
- "version": "1.0.8",
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",