Qwen OAuth authentication plugin for OpenCode with multi-account rotation, proactive token refresh, and automatic API translation.
💝 Donate (Crypto addresses)
bc1q76tzf55t2mkwhpg3w7lfnvzmhmmfvwkw4uphfs
0x902B403852b632be0F8d2175C9d86bCF77B2319A
Use the same Ethereum address above:
0x902B403852b632be0F8d2175C9d86bCF77B2319A
Note for donors in China: Crypto via Binance P2P is recommended (Great Firewall blocks Ko-fi).
- Device Flow OAuth - PKCE-secured authentication, works in headless/CI environments
- Multi-Account Support - Store and rotate between multiple Qwen accounts
- Hybrid Account Rotation - Smart selection using health scores, token bucket, and LRU
- Proactive Token Refresh - Automatically refresh tokens before expiry
- Rate Limit Handling - Detects 429 responses, rotates accounts, respects retry-after
- API Translation - Bridges OpenAI Responses API ↔ Chat Completions API
- Streaming Support - Full SSE transformation for real-time responses
Paste this into any LLM agent (Claude Code, OpenCode, Cursor, etc.):
Install the opencode-qwen-auth plugin by following: https://raw.githubusercontent.com/foxswat/opencode-qwen-auth/main/README.md
Run one command to automatically configure OpenCode:
bunx opencode-qwen-auth install
# or
npx opencode-qwen-auth installThis adds the plugin and Qwen provider configuration to your opencode.json.
If you prefer manual setup:
# Using Bun
bun add opencode-qwen-auth
# Using npm
npm install opencode-qwen-authThen add to your opencode.json:
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["opencode-qwen-auth"],
"provider": {
"qwen": {
"npm": "@ai-sdk/openai",
"options": {
"baseURL": "https://portal.qwen.ai/v1",
"compatibility": "strict"
},
"models": {
"qwen3-coder-plus": {},
"qwen3-vl-plus": { "attachment": true }
}
}
}
}-
Start OpenCode in your project directory:
opencode
-
Authenticate with Qwen:
/connectSelect Qwen OAuth and follow the device flow instructions.
-
Start coding with Qwen models:
/model qwen/qwen3-coder-plus
No configuration required. The plugin works out of the box with sensible defaults.
To customize behavior, create .opencode/qwen.json (project) or ~/.config/opencode/qwen.json (user-level) with only the options you want to override:
| Option | Default | Description |
|---|---|---|
base_url |
https://portal.qwen.ai/v1 |
API endpoint for Qwen requests |
client_id |
(built-in) | OAuth client ID |
oauth_base_url |
https://chat.qwen.ai |
OAuth server URL |
rotation_strategy |
hybrid |
Account rotation: hybrid, round-robin, or sequential |
pid_offset_enabled |
false |
Distribute parallel sessions across accounts using PID offset |
proactive_refresh |
true |
Refresh tokens before expiry |
refresh_window_seconds |
300 |
Seconds before expiry to trigger refresh |
max_rate_limit_wait_seconds |
300 |
Maximum wait time when rate limited |
quiet_mode |
false |
Suppress informational messages |
All options can be overridden via environment variables:
QWEN_API_BASE_URLQWEN_OAUTH_CLIENT_IDQWEN_OAUTH_BASE_URLQWEN_ROTATION_STRATEGYQWEN_PID_OFFSET_ENABLEDQWEN_PROACTIVE_REFRESHQWEN_REFRESH_WINDOW_SECONDSQWEN_MAX_RATE_LIMIT_WAIT_SECONDSQWEN_QUIET_MODE
| Model | Context Window | Features |
|---|---|---|
qwen3-coder-plus |
1M tokens | Optimized for coding tasks |
qwen3-vl-plus |
256K tokens | Vision + language multimodal |
Add multiple accounts for higher throughput:
- Run
/connectand complete the first login - Run
/connectagain to add additional accounts - The plugin automatically rotates between accounts
- hybrid (default): Smart selection combining health scores, token bucket rate limiting, and LRU. Accounts recover health passively over time.
- round-robin: Cycles through accounts on each request
- sequential: Uses one account until rate limited, then switches
The hybrid strategy uses a weighted scoring algorithm:
- Health Score (0-100): Tracks account wellness. Success rewards (+1), rate limits penalize (-10), failures penalize more (-20). Accounts passively recover +2 points/hour when rested.
- Token Bucket: Client-side rate limiting (50 tokens max, regenerates 6/minute) to prevent hitting server 429s.
- LRU Freshness: Prefers accounts that haven't been used recently.
Score formula: (health × 2) + (tokens × 5) + (freshness × 0.1)
Enable pid_offset_enabled: true when running multiple parallel sessions (e.g., oh-my-opencode) to distribute load across accounts.
This plugin bridges OpenCode's Responses API format with Qwen's Chat Completions API:
OpenCode → [Responses API] → Plugin → [Chat Completions] → Qwen
↓
OpenCode ← [Responses API] ← Plugin ← [Chat Completions] ← Qwen
| Responses API | Chat Completions API |
|---|---|
input |
messages |
input_text |
text content type |
input_image |
image_url content type |
instructions |
System message |
max_output_tokens |
max_tokens |
Converts SSE events from Chat Completions to Responses API format:
response.createdresponse.output_item.addedresponse.content_part.addedresponse.output_text.deltaresponse.completed
| Data | Location |
|---|---|
| User config | ~/.config/opencode/qwen.json |
| Project config | .opencode/qwen.json |
| Account tokens | ~/.config/opencode/qwen-auth-accounts.json |
Security Note: Tokens are stored with restricted permissions (0600). Ensure appropriate filesystem security.
"invalid_grant" error
- Your refresh token has expired. Run
/connectto re-authenticate.
Device code expired
- Complete the browser login within 5 minutes of starting
/connect.
Frequent 429 errors
- Add more accounts with
/connect - Increase
max_rate_limit_wait_secondsin config
To start fresh, delete the accounts file:
rm ~/.config/opencode/qwen-auth-accounts.jsonThis project uses Bun for development.
- Bun 1.0+ (recommended)
- Node.js 20+ (for npm compatibility)
# Install dependencies
bun install
# Build
bun run build
# Run tests
bun test
# Run tests in watch mode
bun test --watch
# Run e2e test (requires authenticated Qwen account)
bun run test:e2e
# Link for local testing
bun linkThe project also works with npm:
npm install
npm run build
npm test- Audio input (
input_audio) is not supported by Qwen and is converted to placeholder text
Apache-2.0
Planned features and improvements for future releases:
| Feature | Description | Status |
|---|---|---|
| Rate Limit Deduplication | Ignore duplicate 429s within 2s window to prevent backoff cascades | Planned |
| Exponential Backoff with Jitter | Add randomness to retry delays to prevent thundering herd | Planned |
| Schema Cleaning | Remove unsupported JSON Schema keys (const, $ref, $defs) that cause API rejections |
Planned |
| Feature | Description | Status |
|---|---|---|
| Circuit Breaker | Temporarily stop requests to failing accounts after consecutive failures | Planned |
| Proactive Health Checks | Validate tokens before use, not just after failures | Planned |
| CLI: Status Command | bunx opencode-qwen-auth status to show account health and token info |
Planned |
| Feature | Description | Status |
|---|---|---|
| Session Recovery | Handle tool_result_missing errors from interrupted conversations |
Research |
| CLI: Uninstall Command | Clean removal from opencode.json | Planned |
| Configurable Retry Strategies | User-selectable aggressive/conservative retry modes | Research |
| Feature | Description | Status |
|---|---|---|
| Dual Quota System | Track separate quotas per API endpoint if Qwen supports | Research |
| OAuth Server Fallback | Try backup auth servers when primary fails | Research |
| Rate Limit Prediction | Use historical patterns to predict when limits will hit | Research |
| Feature | Version | Description |
|---|---|---|
| CLI Installer Safety | v0.3.4 | Preview, backup, --yes flag for CI automation |
| Hybrid Account Rotation | v0.3.0 | Health scores, token bucket, LRU freshness |
| PID Offset | v0.3.0 | Multi-session load distribution |
Want to contribute? See AGENTS.md for development guidelines.
{ // API endpoint (default: https://portal.qwen.ai/v1) "base_url": "https://portal.qwen.ai/v1", // OAuth client ID (default: built-in) "client_id": "your-client-id", // OAuth server URL (default: https://chat.qwen.ai) "oauth_base_url": "https://chat.qwen.ai", // Account rotation: "hybrid", "round-robin", or "sequential" (default: hybrid) "rotation_strategy": "hybrid", // Enable PID-based offset for multi-session load distribution (default: false) "pid_offset_enabled": false, // Refresh tokens before expiry (default: true) "proactive_refresh": true, // Seconds before expiry to trigger refresh (default: 300) "refresh_window_seconds": 300, // Maximum wait time when rate limited (default: 300) "max_rate_limit_wait_seconds": 300, // Suppress informational messages (default: false) "quiet_mode": true }