opencode-qwen 1.0.2 → 1.0.5
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/README.md +81 -104
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +172 -0
- package/package.json +17 -11
- package/.github/workflows/main.yml +0 -30
- package/auth.ts +0 -165
- package/fix_summary.md +0 -28
- package/index.ts +0 -222
- package/opencode.example.json +0 -34
- package/qwen-auth.ts +0 -353
- package/qwen-auth.ts.backup +0 -337
- package/test_fix.md +0 -69
package/README.md
CHANGED
|
@@ -1,142 +1,119 @@
|
|
|
1
|
-
# Qwen
|
|
1
|
+
# Qwen Provider for OpenCode
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Qwen AI provider integration for OpenCode with automatic config setup.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Quick Start
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
- **Secure Credential Handling**: Integrates with environment variables
|
|
9
|
-
- **Token Refresh Support**: Handles access token renewal using refresh tokens
|
|
10
|
-
- **Custom Tools**: Provides dedicated tools for Qwen API interactions
|
|
11
|
-
- **Logging & Monitoring**: Comprehensive logging for debugging and monitoring
|
|
12
|
-
- **Type Safety**: Full TypeScript support with Zod validation
|
|
7
|
+
### Option 1: Use `/connect` Command (Recommended)
|
|
13
8
|
|
|
14
|
-
|
|
9
|
+
1. Copy the content of `opencode.json` file to your OpenCode config location:
|
|
10
|
+
```
|
|
11
|
+
~/.config/opencode/opencode.json
|
|
12
|
+
```
|
|
13
|
+
Or on Windows:
|
|
14
|
+
```
|
|
15
|
+
%APPDATA%\opencode\opencode.json
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
2. Restart OpenCode
|
|
15
19
|
|
|
16
|
-
|
|
20
|
+
3. Run `/connect` command in OpenCode TUI:
|
|
21
|
+
- Select "Other" as provider type
|
|
22
|
+
- Enter provider ID: `qwen`
|
|
23
|
+
- Enter your Qwen API key
|
|
24
|
+
|
|
25
|
+
4. Run `/models` to see Qwen models
|
|
26
|
+
|
|
27
|
+
### Option 2: Manual Installation
|
|
28
|
+
|
|
29
|
+
1. Place plugin files in your OpenCode plugins directory:
|
|
17
30
|
```
|
|
18
31
|
.opencode/
|
|
19
32
|
├── plugins/
|
|
20
|
-
│ └──
|
|
33
|
+
│ └── dist/
|
|
34
|
+
│ └── index.js
|
|
35
|
+
├── index.ts
|
|
36
|
+
├── opencode.json <-- Copy this file
|
|
21
37
|
└── package.json
|
|
22
38
|
```
|
|
23
39
|
|
|
24
40
|
2. Install dependencies:
|
|
25
41
|
```bash
|
|
26
|
-
|
|
42
|
+
bun install
|
|
27
43
|
```
|
|
28
44
|
|
|
29
|
-
|
|
45
|
+
3. Set your Qwen API key as environment variable:
|
|
46
|
+
```bash
|
|
47
|
+
export QWEN_API_KEY=your_access_token_here
|
|
48
|
+
```
|
|
30
49
|
|
|
31
|
-
|
|
50
|
+
## Getting Your API Key
|
|
32
51
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
52
|
+
1. Visit [chat.qwen.ai](https://chat.qwen.ai) and log in
|
|
53
|
+
2. Open browser console (F12 → Console tab)
|
|
54
|
+
3. Paste this JavaScript code:
|
|
55
|
+
```javascript
|
|
56
|
+
(function(){if(window.location.hostname!=="chat.qwen.ai"){alert("🚀 This code is for chat.qwen.ai");window.open("https://chat.qwen.ai","_blank");return;}
|
|
57
|
+
function getApiKeyData(){const token=localStorage.getItem("token");if(!token){alert("❌ qwen access_token not found !!!");return null;}
|
|
58
|
+
return token;}
|
|
59
|
+
async function copyToClipboard(text){try{await navigator.clipboard.writeText(text);return true;}catch(err){console.error("❌ Failed to copy to clipboard:",err);const textarea=document.createElement("textarea");textarea.value=text;textarea.style.position="fixed";textarea.style.opacity="0";document.body.appendChild(textarea);textarea.focus();textarea.select();const success=document.execCommand("copy");document.body.removeChild(textarea);return success;}}
|
|
60
|
+
const apiKeyData=getApiKeyData();if(!apiKeyData)return;copyToClipboard(apiKeyData).then((success)=>{if(success){alert("🔑 Qwen access_token copied to clipboard !!! 🎉");}else{prompt("🔰 Qwen access_token:",apiKeyData);}});})();
|
|
61
|
+
```
|
|
62
|
+
4. Copy the API key and set it as `QWEN_API_KEY`
|
|
36
63
|
|
|
37
|
-
|
|
38
|
-
QWEN_REFRESH_TOKEN=your_refresh_token_here
|
|
64
|
+
## Configuration Details
|
|
39
65
|
|
|
40
|
-
|
|
41
|
-
QWEN_AUTO_REFRESH=true
|
|
66
|
+
The `opencode.json` file configures:
|
|
42
67
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
68
|
+
- **Qwen Provider**: Points to the opencode-qwen plugin
|
|
69
|
+
- **Models**: 10 pre-configured Qwen models
|
|
70
|
+
- **Environment Variables**: Uses `QWEN_API_KEY` for authentication
|
|
46
71
|
|
|
47
|
-
##
|
|
72
|
+
## Available Models
|
|
48
73
|
|
|
49
|
-
|
|
74
|
+
| Model ID | Name | Capabilities |
|
|
75
|
+
|-----------|------|--------------|
|
|
76
|
+
| `qwen-turbo` | Qwen Turbo | Fast, efficient |
|
|
77
|
+
| `qwen-plus` | Qwen Plus | Complex reasoning, tool calling |
|
|
78
|
+
| `qwen-max` | Qwen Max | Most capable, advanced tasks |
|
|
79
|
+
| `qwen-coder` | Qwen Coder | Coding specialized |
|
|
80
|
+
| `qwen-vl` | Qwen VL | Multimodal, supports images |
|
|
81
|
+
| `qwen-deep-research` | Qwen Deep Research | Web search enabled |
|
|
82
|
+
| `qwen-web-dev` | Qwen Web Dev | Web development |
|
|
83
|
+
| `qwen-full-stack` | Qwen Full Stack | Full-stack apps |
|
|
84
|
+
| `qwen-omni` | Qwen Omni | Extensive multimodal |
|
|
50
85
|
|
|
51
|
-
|
|
86
|
+
## For AI Agents
|
|
52
87
|
|
|
53
|
-
|
|
54
|
-
Validate your Qwen API access token.
|
|
88
|
+
When using this with an AI agent, simply copy the contents of `opencode.json` from this repository to your OpenCode config file location:
|
|
55
89
|
|
|
56
|
-
```javascript
|
|
57
|
-
// Example usage
|
|
58
|
-
const result = await qwen.validate-token({
|
|
59
|
-
token: "optional_token_to_validate" // Uses current token if not provided
|
|
60
|
-
})
|
|
61
90
|
```
|
|
62
|
-
|
|
63
|
-
#### `qwen.refresh-token`
|
|
64
|
-
Refresh your access token using the refresh token.
|
|
65
|
-
|
|
66
|
-
```javascript
|
|
67
|
-
// Example usage
|
|
68
|
-
const result = await qwen.refresh-token({
|
|
69
|
-
refreshToken: "optional_refresh_token" // Uses stored refresh token if not provided
|
|
70
|
-
})
|
|
91
|
+
~/.config/opencode/opencode.json
|
|
71
92
|
```
|
|
72
93
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
```javascript
|
|
77
|
-
// Example usage
|
|
78
|
-
const models = await qwen.list-models({})
|
|
79
|
-
console.log(`Available models: ${models.data.length}`)
|
|
94
|
+
Or on Windows:
|
|
95
|
+
```
|
|
96
|
+
%APPDATA%\opencode\opencode.json
|
|
80
97
|
```
|
|
81
98
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
The plugin automatically handles:
|
|
85
|
-
|
|
86
|
-
- **Session Validation**: Validates tokens when a new session starts
|
|
87
|
-
- **Periodic Refresh**: Checks token validity during idle periods
|
|
88
|
-
- **API Call Interception**: Ensures valid tokens before Qwen API calls
|
|
89
|
-
- **Error Recovery**: Attempts token refresh on validation failures
|
|
90
|
-
|
|
91
|
-
## API Endpoints
|
|
92
|
-
|
|
93
|
-
The plugin integrates with the following Qwen API endpoints:
|
|
94
|
-
|
|
95
|
-
- `GET /validate` - Token validation
|
|
96
|
-
- `POST /refresh` - Token refresh
|
|
97
|
-
- `GET /models` - List available models
|
|
98
|
-
|
|
99
|
-
## Security Considerations
|
|
100
|
-
|
|
101
|
-
- Store API keys and refresh tokens in environment variables, not in code
|
|
102
|
-
- The plugin never logs sensitive credential information
|
|
103
|
-
- Token validation is performed over secure HTTPS connections
|
|
104
|
-
- Consider rotating API keys regularly for enhanced security
|
|
105
|
-
|
|
106
|
-
## Troubleshooting
|
|
107
|
-
|
|
108
|
-
### Common Issues
|
|
109
|
-
|
|
110
|
-
1. **"No valid Qwen authentication token available"**
|
|
111
|
-
- Ensure `QWEN_API_KEY` is set correctly
|
|
112
|
-
- Check if the token has expired and refresh if needed
|
|
113
|
-
|
|
114
|
-
2. **"No refresh token available"**
|
|
115
|
-
- Set `QWEN_REFRESH_TOKEN` environment variable
|
|
116
|
-
- Obtain a refresh token from your Qwen API dashboard
|
|
117
|
-
|
|
118
|
-
3. **"Failed to fetch models"**
|
|
119
|
-
- Validate your token first using `qwen.validate-token`
|
|
120
|
-
- Check network connectivity to `qwen.aikit.club`
|
|
121
|
-
|
|
122
|
-
### Debug Logging
|
|
123
|
-
|
|
124
|
-
Enable debug logging to troubleshoot issues:
|
|
125
|
-
|
|
99
|
+
Then set the environment variable:
|
|
126
100
|
```bash
|
|
127
|
-
|
|
128
|
-
export DEBUG=qwen-auth:*
|
|
101
|
+
export QWEN_API_KEY=your_key_here
|
|
129
102
|
```
|
|
130
103
|
|
|
131
|
-
##
|
|
104
|
+
## Troubleshooting
|
|
132
105
|
|
|
133
|
-
|
|
106
|
+
### Qwen provider not showing up?
|
|
107
|
+
1. Ensure `QWEN_API_KEY` environment variable is set
|
|
108
|
+
2. Restart OpenCode after setting the variable
|
|
109
|
+
3. Check that `opencode-qwen` is in the `plugin` array of your config
|
|
134
110
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
-
|
|
138
|
-
-
|
|
111
|
+
### Models not appearing?
|
|
112
|
+
The plugin uses dynamic model loading. Models are fetched from the API when:
|
|
113
|
+
- Your `QWEN_API_KEY` is valid
|
|
114
|
+
- Network connection to `qwen.aikit.club` works
|
|
115
|
+
- The plugin is properly loaded
|
|
139
116
|
|
|
140
117
|
## License
|
|
141
118
|
|
|
142
|
-
|
|
119
|
+
MIT License. Created by OpenCode Community.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Qwen Provider for OpenCode
|
|
3
|
+
*
|
|
4
|
+
* This package provides Qwen API integration for OpenCode with dynamic model loading.
|
|
5
|
+
* Models are fetched dynamically from the Qwen API.
|
|
6
|
+
*/
|
|
7
|
+
export interface QwenApiModel {
|
|
8
|
+
id: string;
|
|
9
|
+
object: string;
|
|
10
|
+
created: number;
|
|
11
|
+
owned_by: string;
|
|
12
|
+
permission?: any[];
|
|
13
|
+
root?: string;
|
|
14
|
+
parent?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface QwenModelsResponse {
|
|
17
|
+
object: string;
|
|
18
|
+
data: QwenApiModel[];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Fetch models from Qwen API
|
|
22
|
+
*/
|
|
23
|
+
export declare function fetchQwenModels(apiKey: string, baseURL?: string): Promise<Record<string, any>>;
|
|
24
|
+
/**
|
|
25
|
+
* Validate Qwen API key
|
|
26
|
+
*/
|
|
27
|
+
export declare function validateQwenApiKey(apiKey: string): Promise<boolean>;
|
|
28
|
+
/**
|
|
29
|
+
* Qwen Plugin for OpenCode
|
|
30
|
+
*/
|
|
31
|
+
export default function QwenPlugin({ project, client, $, directory, worktree }: any): Promise<{
|
|
32
|
+
config(config: any): Promise<void>;
|
|
33
|
+
}>;
|
|
34
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAQH,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,CAAC,EAAE,GAAG,EAAE,CAAA;IAClB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,YAAY,EAAE,CAAA;CACrB;AA0CD;;GAEG;AACH,wBAAsB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,MAAsB,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAwDnH;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAczE;AAED;;GAEG;AACH,wBAA8B,UAAU,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,GAAG;mBAkBhE,GAAG;GAoC3B"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Qwen Provider for OpenCode
|
|
4
|
+
*
|
|
5
|
+
* This package provides Qwen API integration for OpenCode with dynamic model loading.
|
|
6
|
+
* Models are fetched dynamically from the Qwen API.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.fetchQwenModels = fetchQwenModels;
|
|
10
|
+
exports.validateQwenApiKey = validateQwenApiKey;
|
|
11
|
+
exports.default = QwenPlugin;
|
|
12
|
+
/// <reference types="node"/>
|
|
13
|
+
// Qwen API base URL
|
|
14
|
+
const QWEN_BASE_URL = 'https://qwen.aikit.club/v1';
|
|
15
|
+
// Model cache to avoid repeated API calls
|
|
16
|
+
let modelCache = null;
|
|
17
|
+
let modelCacheTime = 0;
|
|
18
|
+
const CACHE_DURATION = 5 * 60 * 1000; // 5 minutes
|
|
19
|
+
/**
|
|
20
|
+
* Convert Qwen API model to OpenCode Model format
|
|
21
|
+
*/
|
|
22
|
+
function convertApiModelToModel(apiModel) {
|
|
23
|
+
const modelId = apiModel.id;
|
|
24
|
+
// Extract capabilities from model name
|
|
25
|
+
const isVisionModel = modelId.includes('vl') || modelId.includes('vision') || modelId.includes('qvq');
|
|
26
|
+
const isCoderModel = modelId.includes('coder') || modelId.includes('code');
|
|
27
|
+
const isTurboModel = modelId.includes('turbo') || modelId.includes('flash');
|
|
28
|
+
const isMaxModel = modelId.includes('max');
|
|
29
|
+
const isPlusModel = modelId.includes('plus');
|
|
30
|
+
const isDeepResearch = modelId.includes('deep-research');
|
|
31
|
+
const isWebDev = modelId.includes('web-dev');
|
|
32
|
+
const isFullStack = modelId.includes('full-stack');
|
|
33
|
+
const isOmni = modelId.includes('omni');
|
|
34
|
+
// Determine capabilities
|
|
35
|
+
const attachment = isVisionModel;
|
|
36
|
+
const reasoning = modelId.includes('thinking') || modelId.includes('max') || modelId.includes('deep-research');
|
|
37
|
+
const tool_call = isCoderModel || modelId.includes('tools') || modelId.includes('plus') || modelId.includes('max');
|
|
38
|
+
// Generate name
|
|
39
|
+
let name = modelId.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
|
|
40
|
+
return {
|
|
41
|
+
id: modelId,
|
|
42
|
+
name,
|
|
43
|
+
attachment,
|
|
44
|
+
reasoning,
|
|
45
|
+
tool_call,
|
|
46
|
+
temperature: true
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Fetch models from Qwen API
|
|
51
|
+
*/
|
|
52
|
+
async function fetchQwenModels(apiKey, baseURL = QWEN_BASE_URL) {
|
|
53
|
+
const now = Date.now();
|
|
54
|
+
// Return cached models if still valid
|
|
55
|
+
if (modelCache && (now - modelCacheTime) < CACHE_DURATION) {
|
|
56
|
+
return modelCache;
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
const response = await fetch(`${baseURL}/models`, {
|
|
60
|
+
method: 'GET',
|
|
61
|
+
headers: {
|
|
62
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
63
|
+
'Content-Type': 'application/json',
|
|
64
|
+
'User-Agent': 'OpenCode-Qwen-Provider/1.0.5'
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
if (!response.ok) {
|
|
68
|
+
throw new Error(`Failed to fetch models: ${response.status} ${response.statusText}`);
|
|
69
|
+
}
|
|
70
|
+
const data = await response.json();
|
|
71
|
+
// Convert API models to OpenCode format
|
|
72
|
+
const models = {};
|
|
73
|
+
for (const apiModel of data.data) {
|
|
74
|
+
models[apiModel.id] = convertApiModelToModel(apiModel);
|
|
75
|
+
}
|
|
76
|
+
// Cache the results
|
|
77
|
+
modelCache = models;
|
|
78
|
+
modelCacheTime = now;
|
|
79
|
+
return models;
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
console.error('Failed to fetch Qwen models:', error);
|
|
83
|
+
// Return fallback models if cache exists but is expired
|
|
84
|
+
if (modelCache) {
|
|
85
|
+
console.warn('Using cached models due to API error');
|
|
86
|
+
return modelCache;
|
|
87
|
+
}
|
|
88
|
+
// Return minimal fallback models
|
|
89
|
+
return {
|
|
90
|
+
'qwen-turbo': {
|
|
91
|
+
id: 'qwen-turbo',
|
|
92
|
+
name: 'Qwen Turbo',
|
|
93
|
+
attachment: false,
|
|
94
|
+
reasoning: false,
|
|
95
|
+
tool_call: false,
|
|
96
|
+
temperature: true
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Validate Qwen API key
|
|
103
|
+
*/
|
|
104
|
+
async function validateQwenApiKey(apiKey) {
|
|
105
|
+
try {
|
|
106
|
+
const response = await fetch(`${QWEN_BASE_URL}/models`, {
|
|
107
|
+
method: 'GET',
|
|
108
|
+
headers: {
|
|
109
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
110
|
+
'Content-Type': 'application/json'
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
return response.ok;
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Qwen Plugin for OpenCode
|
|
121
|
+
*/
|
|
122
|
+
async function QwenPlugin({ project, client, $, directory, worktree }) {
|
|
123
|
+
const apiKey = process.env.QWEN_API_KEY;
|
|
124
|
+
const baseURL = process.env.QWEN_BASE_URL || QWEN_BASE_URL;
|
|
125
|
+
const LOG_FILE = '/tmp/opencode_qwen_plugin.log';
|
|
126
|
+
const log = (level, message) => {
|
|
127
|
+
try {
|
|
128
|
+
const timestamp = new Date().toISOString();
|
|
129
|
+
const logMsg = `[${timestamp}] [${level.toUpperCase()}] ${message}\n`;
|
|
130
|
+
const fs = require('fs');
|
|
131
|
+
fs.appendFileSync(LOG_FILE, logMsg);
|
|
132
|
+
}
|
|
133
|
+
catch (e) {
|
|
134
|
+
// Silent fail for logging
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
return {
|
|
138
|
+
async config(config) {
|
|
139
|
+
log('info', 'Qwen plugin initializing...');
|
|
140
|
+
if (!config.provider) {
|
|
141
|
+
config.provider = {};
|
|
142
|
+
}
|
|
143
|
+
let models = {};
|
|
144
|
+
if (apiKey) {
|
|
145
|
+
try {
|
|
146
|
+
const isValid = await validateQwenApiKey(apiKey);
|
|
147
|
+
if (isValid) {
|
|
148
|
+
models = await fetchQwenModels(apiKey, baseURL);
|
|
149
|
+
log('info', `Loaded ${Object.keys(models).length} models`);
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
log('warn', 'Invalid API key, using fallback models');
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
log('error', `Failed to fetch models: ${error instanceof Error ? error.message : String(error)}`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
log('warn', 'No QWEN_API_KEY found, using fallback models');
|
|
161
|
+
}
|
|
162
|
+
config.provider['qwen'] = {
|
|
163
|
+
api: QWEN_BASE_URL,
|
|
164
|
+
id: 'qwen',
|
|
165
|
+
name: 'Qwen',
|
|
166
|
+
env: ['QWEN_API_KEY'],
|
|
167
|
+
models
|
|
168
|
+
};
|
|
169
|
+
log('info', 'Qwen provider registered');
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
}
|
package/package.json
CHANGED
|
@@ -1,23 +1,29 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-qwen",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "Qwen provider for OpenCode with
|
|
5
|
-
"main": "index.
|
|
3
|
+
"version": "1.0.5",
|
|
4
|
+
"description": "Qwen provider for OpenCode with dynamic model loading",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
6
7
|
"exports": {
|
|
7
|
-
".":
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
}
|
|
8
12
|
},
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"prepare": "npm run build",
|
|
16
|
+
"publish": "npm publish --access=public"
|
|
13
17
|
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist"
|
|
20
|
+
],
|
|
14
21
|
"devDependencies": {
|
|
15
|
-
"
|
|
16
|
-
"
|
|
22
|
+
"@types/node": "^20.19.30",
|
|
23
|
+
"typescript": "^5.0.0"
|
|
17
24
|
},
|
|
18
25
|
"keywords": [
|
|
19
26
|
"opencode",
|
|
20
|
-
"plugin",
|
|
21
27
|
"provider",
|
|
22
28
|
"qwen",
|
|
23
29
|
"ai"
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
|
|
2
|
-
# For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages
|
|
3
|
-
|
|
4
|
-
name: Node.js Package
|
|
5
|
-
|
|
6
|
-
on:
|
|
7
|
-
workflow_dispatch:
|
|
8
|
-
|
|
9
|
-
jobs:
|
|
10
|
-
build:
|
|
11
|
-
runs-on: ubuntu-latest
|
|
12
|
-
steps:
|
|
13
|
-
- uses: actions/checkout@v4
|
|
14
|
-
- uses: actions/setup-node@v4
|
|
15
|
-
with:
|
|
16
|
-
node-version: 20
|
|
17
|
-
- run: npm install
|
|
18
|
-
|
|
19
|
-
publish-npm:
|
|
20
|
-
needs: build
|
|
21
|
-
runs-on: ubuntu-latest
|
|
22
|
-
steps:
|
|
23
|
-
- uses: actions/checkout@v4
|
|
24
|
-
- uses: actions/setup-node@v4
|
|
25
|
-
with:
|
|
26
|
-
node-version: 20
|
|
27
|
-
registry-url: https://registry.npmjs.org/
|
|
28
|
-
- run: npm publish
|
|
29
|
-
env:
|
|
30
|
-
NODE_AUTH_TOKEN: ${{secrets.npm_token}}
|
package/auth.ts
DELETED
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Qwen Provider Authentication Handler for OpenCode
|
|
3
|
-
*
|
|
4
|
-
* This module handles authentication integration with OpenCode's /connect command
|
|
5
|
-
* and provides secure credential management.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { z } from 'zod'
|
|
9
|
-
import type { AuthHandler, Credential } from '@opencode-ai/plugin'
|
|
10
|
-
import { validateQwenApiKey, createQwenProvider, fetchQwenModels } from './index'
|
|
11
|
-
|
|
12
|
-
// Credential validation schema
|
|
13
|
-
const QwenCredentialSchema = z.object({
|
|
14
|
-
apiKey: z.string().min(1, 'API key is required'),
|
|
15
|
-
name: z.string().optional()
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
export type QwenCredential = z.infer<typeof QwenCredentialSchema>
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Qwen authentication handler
|
|
22
|
-
*/
|
|
23
|
-
export const QwenAuthHandler: AuthHandler = {
|
|
24
|
-
// Provider metadata
|
|
25
|
-
provider: {
|
|
26
|
-
id: 'qwen',
|
|
27
|
-
name: 'Qwen',
|
|
28
|
-
description: 'Qwen AI models - high-quality large language models for coding, reasoning, and creative tasks',
|
|
29
|
-
website: 'https://qwen.aikit.club',
|
|
30
|
-
icon: '🤖',
|
|
31
|
-
category: 'ai'
|
|
32
|
-
},
|
|
33
|
-
|
|
34
|
-
// Credential validation
|
|
35
|
-
validateCredential: async (credential: Credential): Promise<boolean> => {
|
|
36
|
-
try {
|
|
37
|
-
const qwenCred = QwenCredentialSchema.parse(credential)
|
|
38
|
-
return await validateQwenApiKey(qwenCred.apiKey)
|
|
39
|
-
} catch (error) {
|
|
40
|
-
console.error('Credential validation error:', error)
|
|
41
|
-
return false
|
|
42
|
-
}
|
|
43
|
-
},
|
|
44
|
-
|
|
45
|
-
// Provider creation
|
|
46
|
-
createProvider: async (credential: Credential) => {
|
|
47
|
-
try {
|
|
48
|
-
const qwenCred = QwenCredentialSchema.parse(credential)
|
|
49
|
-
const baseURL = 'https://qwen.aikit.club/v1'
|
|
50
|
-
|
|
51
|
-
// Validate and fetch models
|
|
52
|
-
const isValid = await validateQwenApiKey(qwenCred.apiKey)
|
|
53
|
-
if (!isValid) {
|
|
54
|
-
throw new Error('Invalid Qwen API key')
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const models = await fetchQwenModels(qwenCred.apiKey, baseURL)
|
|
58
|
-
|
|
59
|
-
return createQwenProvider({
|
|
60
|
-
apiKey: qwenCred.apiKey,
|
|
61
|
-
baseURL
|
|
62
|
-
})
|
|
63
|
-
} catch (error) {
|
|
64
|
-
throw new Error(`Failed to create Qwen provider: ${error instanceof Error ? error.message : String(error)}`)
|
|
65
|
-
}
|
|
66
|
-
},
|
|
67
|
-
|
|
68
|
-
// Credential templates for user guidance
|
|
69
|
-
credentialTemplates: [
|
|
70
|
-
{
|
|
71
|
-
name: 'API Key',
|
|
72
|
-
fields: [
|
|
73
|
-
{
|
|
74
|
-
key: 'apiKey',
|
|
75
|
-
label: 'API Key',
|
|
76
|
-
type: 'password',
|
|
77
|
-
placeholder: 'sk-...',
|
|
78
|
-
required: true,
|
|
79
|
-
description: 'Your Qwen API key from https://qwen.aikit.club'
|
|
80
|
-
}
|
|
81
|
-
]
|
|
82
|
-
}
|
|
83
|
-
],
|
|
84
|
-
|
|
85
|
-
// Additional configuration options
|
|
86
|
-
configOptions: {
|
|
87
|
-
autoRefresh: {
|
|
88
|
-
type: 'boolean',
|
|
89
|
-
default: true,
|
|
90
|
-
description: 'Automatically refresh tokens when they expire'
|
|
91
|
-
},
|
|
92
|
-
timeout: {
|
|
93
|
-
type: 'number',
|
|
94
|
-
default: 30000,
|
|
95
|
-
description: 'Request timeout in milliseconds'
|
|
96
|
-
}
|
|
97
|
-
},
|
|
98
|
-
|
|
99
|
-
// Help and documentation
|
|
100
|
-
help: {
|
|
101
|
-
title: 'Qwen AI Integration',
|
|
102
|
-
description: `
|
|
103
|
-
Qwen provides state-of-the-art language models with dynamic model loading:
|
|
104
|
-
|
|
105
|
-
• **Code Generation**: Qwen Coder, Qwen3-Coder for development tasks
|
|
106
|
-
• **Complex Reasoning**: Qwen Max, Qwen-Deep-Research for advanced problem-solving
|
|
107
|
-
• **Speed & Efficiency**: Qwen Turbo, Qwen-Flash for quick responses
|
|
108
|
-
• **Multimodal**: Qwen VL, Qwen3-Omni for image and text understanding
|
|
109
|
-
• **Web Development**: Qwen-Web-Dev for frontend development
|
|
110
|
-
• **Full-Stack**: Qwen-Full-Stack for complete application development
|
|
111
|
-
• **Video Generation**: Text-to-video capabilities
|
|
112
|
-
• **Image Generation**: Advanced image creation and editing
|
|
113
|
-
|
|
114
|
-
**Getting Started:**
|
|
115
|
-
|
|
116
|
-
1. Visit [chat.qwen.ai](https://chat.qwen.ai) and log in
|
|
117
|
-
2. Extract your access token using the browser console script from [qwen-api repo](https://github.com/tanu1337/qwen-api)
|
|
118
|
-
3. Use \`/connect\` command and select "Qwen"
|
|
119
|
-
4. Paste your access token when prompted
|
|
120
|
-
5. Models will be loaded dynamically based on your account access
|
|
121
|
-
|
|
122
|
-
**Dynamic Model Loading:**
|
|
123
|
-
Models are fetched directly from the API, ensuring you always have access to the latest available models. Common models include:
|
|
124
|
-
- \`qwen-turbo\`: Fast, efficient for general tasks
|
|
125
|
-
- \`qwen-plus\`: Balanced performance for complex tasks
|
|
126
|
-
- \`qwen-max\`: Most capable for advanced reasoning
|
|
127
|
-
- \`qwen-coder\`: Specialized for coding and development
|
|
128
|
-
- \`qwen-vl\`: Multimodal, supports images and text
|
|
129
|
-
- \`qwen-deep-research\`: Comprehensive research with web search
|
|
130
|
-
- \`qwen-web-dev\`: Frontend web development
|
|
131
|
-
- \`qwen-full-stack\`: Complete application development
|
|
132
|
-
|
|
133
|
-
**Getting Your Token:**
|
|
134
|
-
1. Go to [chat.qwen.ai](https://chat.qwen.ai) and login
|
|
135
|
-
2. Open browser console (F12 → Console tab)
|
|
136
|
-
3. Paste this JavaScript code:
|
|
137
|
-
\`\`\`javascript
|
|
138
|
-
(function(){if(window.location.hostname!=="chat.qwen.ai"){alert("🚀 This code is for chat.qwen.ai");window.open("https://chat.qwen.ai","_blank");return;}
|
|
139
|
-
function getApiKeyData(){const token=localStorage.getItem("token");if(!token){alert("❌ qwen access_token not found !!!");return null;}
|
|
140
|
-
return token;}
|
|
141
|
-
async function copyToClipboard(text){try{await navigator.clipboard.writeText(text);return true;}catch(err){console.error("❌ Failed to copy to clipboard:",err);const textarea=document.createElement("textarea");textarea.value=text;textarea.style.position="fixed";textarea.style.opacity="0";document.body.appendChild(textarea);textarea.focus();textarea.select();const success=document.execCommand("copy");document.body.removeChild(textarea);return success;}}
|
|
142
|
-
const apiKeyData=getApiKeyData();if(!apiKeyData)return;copyToClipboard(apiKeyData).then((success)=>{if(success){alert("🔑 Qwen access_token copied to clipboard !!! 🎉");}else{prompt("🔰 Qwen access_token:",apiKeyData);}});})();
|
|
143
|
-
\`\`\`
|
|
144
|
-
|
|
145
|
-
For more information, visit the [Qwen API repository](https://github.com/tanu1337/qwen-api).
|
|
146
|
-
`.trim(),
|
|
147
|
-
links: [
|
|
148
|
-
{
|
|
149
|
-
label: 'Get API Key',
|
|
150
|
-
url: 'https://qwen.aikit.club/api-keys'
|
|
151
|
-
},
|
|
152
|
-
{
|
|
153
|
-
label: 'Documentation',
|
|
154
|
-
url: 'https://qwen.aikit.club/docs'
|
|
155
|
-
},
|
|
156
|
-
{
|
|
157
|
-
label: 'Pricing',
|
|
158
|
-
url: 'https://qwen.aikit.club/pricing'
|
|
159
|
-
}
|
|
160
|
-
]
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Export for direct usage
|
|
165
|
-
export { QwenCredentialSchema, type QwenCredential }
|