Skip to content

GewoonJaap/qwen-code-cli-wrapper

Repository files navigation

πŸ€– Qwen Code CLI Wrapper

Transform Qwen Code CLI into OpenAI-compatible endpoints using Cloudflare Workers. Access advanced AI capabilities through a standardized API interface, powered by OAuth2 authentication and seamless integration with the Qwen Code ecosystem.

✨ Features

  • πŸ” OAuth2 Authentication - Uses your Qwen Code CLI credentials seamlessly
  • 🎯 OpenAI-Compatible API - Drop-in replacement for OpenAI endpoints
  • πŸ“š OpenAI SDK Support - Works with official OpenAI SDKs and libraries
  • 🌐 Third-party Integration - Compatible with Open WebUI, Cline, and more
  • πŸ›‘οΈ API Key Security - Optional authentication layer for endpoint access
  • ⚑ Cloudflare Workers - Global edge deployment with low latency
  • πŸ”„ Smart Token Management - Automatic token refresh with KV storage
  • πŸ“‘ Real-time Streaming - Server-sent events for live responses
  • πŸ—οΈ Clean Architecture - Well-structured, maintainable codebase
  • πŸ“Š Debug Logging - Comprehensive logging for troubleshooting

πŸš€ Quick Start

Prerequisites

  1. Qwen Account with Code CLI access
  2. Cloudflare Account with Workers enabled
  3. Wrangler CLI installed (npm install -g wrangler)

Step 1: Get OAuth2 Credentials

You need OAuth2 credentials from the official Qwen Code CLI.

Using Qwen Code CLI

  1. Install Qwen Code CLI:

    npm install -g @qwen-code/qwen-code@latest
  2. Start Qwen Code and authenticate:

    qwen

    Select your preferred authentication method when prompted.

  3. Locate the credentials file:

    Windows:

    C:\Users\USERNAME\.qwen\oauth_creds.json
    

    macOS/Linux:

    ~/.qwen/oauth_creds.json
    
  4. Copy the credentials: The file contains JSON in this format:

    {
      "access_token": "your_access_token_here",
      "refresh_token": "your_refresh_token_here",
      "expiry_date": 1700000000000,
      "resource_url": "https://your-endpoint.com/v1",
      "token_type": "Bearer"
    }

Step 2: Create KV Namespace

# Create a KV namespace for token caching
wrangler kv namespace create "QWEN_KV"

Note the namespace ID returned and update wrangler.toml:

kv_namespaces = [
  { binding = "QWEN_KV", id = "your-kv-namespace-id" }
]

Step 3: Environment Setup

Create a .dev.vars file:

# Required: Qwen Code CLI authentication JSON
QWEN_CLI_AUTH={"access_token":"your_access_token","refresh_token":"your_refresh_token","expiry_date":1700000000000,"resource_url":"https://your-endpoint.com/v1","token_type":"Bearer"}

# Optional: API key for client authentication (if set, users must provide it in Authorization header)
# OPENAI_API_KEY=sk-your-secret-key-here

# Optional: Default model override
# OPENAI_MODEL=qwen3-coder-plus

# Optional: Custom base URL (will use resource_url from OAuth if available)
# OPENAI_BASE_URL=https://api-inference.modelscope.cn/v1

For production, set the secrets:

wrangler secret put QWEN_CLI_AUTH
# Enter your OAuth credentials JSON

Step 4: Deploy

# Install dependencies
npm install

# Deploy to Cloudflare Workers
npm run deploy

# Or run locally for development
npm run dev

The service will be available at https://your-worker.your-subdomain.workers.dev

πŸ”§ Configuration

Environment Variables

Core Configuration

Variable Required Description
QWEN_CLI_AUTH βœ… OAuth2 credentials JSON from Qwen Code CLI
OPENAI_API_KEY ❌ API key for client authentication
OPENAI_MODEL ❌ Default model override
OPENAI_BASE_URL ❌ Custom base URL (uses OAuth resource_url if available)

Authentication Security

  • When OPENAI_API_KEY is set, all /v1/* endpoints require authentication
  • Clients must include the header: Authorization: Bearer <your-api-key>
  • Recommended format: sk- followed by a random string
  • Without this variable, endpoints are publicly accessible (not recommended for production)

OAuth Token Management

  • Automatic Refresh: Tokens are automatically refreshed when expired
  • KV Persistence: Refreshed tokens are stored in Cloudflare KV
  • Fallback Logic: KV cache β†’ environment β†’ refresh β†’ retry
  • Debug Logging: Comprehensive token source tracking

KV Namespaces

Binding Purpose
QWEN_KV OAuth token caching and refresh storage

🎯 API Endpoints

Base URL

https://your-worker.your-subdomain.workers.dev

OpenAI-Compatible Endpoints

Chat Completions

POST /v1/chat/completions
Authorization: Bearer sk-your-api-key-here (if OPENAI_API_KEY is set)
Content-Type: application/json

{
  "model": "qwen3-coder-plus",
  "messages": [
    {
      "role": "system",
      "content": "You are a helpful coding assistant."
    },
    {
      "role": "user",
      "content": "Write a Python function to calculate fibonacci numbers"
    }
  ],
  "stream": true,
  "temperature": 0.7,
  "max_tokens": 1000
}

List Models

GET /v1/models
Authorization: Bearer sk-your-api-key-here (if OPENAI_API_KEY is set)

Response:

{
  "object": "list",
  "data": [
    {
      "id": "qwen3-coder-plus",
      "object": "model",
      "created": 1700000000,
      "owned_by": "qwen"
    },
    {
      "id": "qwen3-coder-flash",
      "object": "model",
      "created": 1700000000,
      "owned_by": "qwen"
    }
  ]
}

Utility Endpoints

Health Check

GET /health

No authentication required

Response:

{
  "status": "ok",
  "uptime": 1700000000,
  "version": "qwen-worker-1.0.0"
}

πŸ’» Usage Examples

OpenAI SDK (Python)

from openai import OpenAI

# Initialize with your worker endpoint
client = OpenAI(
    base_url="https://your-worker.workers.dev/v1",
    api_key="sk-your-secret-api-key-here"  # Only if OPENAI_API_KEY is set
)

# Chat completion
response = client.chat.completions.create(
    model="qwen3-coder-plus",
    messages=[
        {"role": "system", "content": "You are a helpful coding assistant."},
        {"role": "user", "content": "Write a binary search algorithm in Python"}
    ],
    temperature=0.2,
    max_tokens=500,
    stream=True
)

for chunk in response:
    if chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="")

OpenAI SDK (JavaScript/TypeScript)

import OpenAI from 'openai';

const openai = new OpenAI({
  baseURL: 'https://your-worker.workers.dev/v1',
  apiKey: 'sk-your-secret-api-key-here', // Only if OPENAI_API_KEY is set
});

const stream = await openai.chat.completions.create({
  model: 'qwen3-coder-plus',
  messages: [
    { role: 'user', content: 'Explain async/await in JavaScript' }
  ],
  stream: true,
  temperature: 0.7,
});

for await (const chunk of stream) {
  const content = chunk.choices[0]?.delta?.content || '';
  process.stdout.write(content);
}

cURL Examples

# Chat completion (non-streaming)
curl -X POST https://your-worker.workers.dev/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer sk-your-secret-api-key-here" \
  -d '{
    "model": "qwen3-coder-plus",
    "messages": [
      {"role": "user", "content": "Hello! How are you?"}
    ],
    "temperature": 0.7
  }'

# Chat completion (streaming)
curl -N -X POST https://your-worker.workers.dev/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer sk-your-secret-api-key-here" \
  -d '{
    "model": "qwen3-coder-flash",
    "messages": [
      {"role": "user", "content": "Write a TypeScript hello world"}
    ],
    "stream": true
  }'

# List available models
curl https://your-worker.workers.dev/v1/models \
  -H "Authorization: Bearer sk-your-secret-api-key-here"

# Health check
curl https://your-worker.workers.dev/health

Open WebUI Integration

  1. Add as OpenAI-compatible endpoint:

    • Base URL: https://your-worker.workers.dev/v1
    • API Key: sk-your-secret-api-key-here (only if OPENAI_API_KEY is set)
  2. Auto-discovery: Open WebUI will automatically discover available models through the /v1/models endpoint.

πŸ—οΈ How It Works

graph TD
    A[Client Request] --> B[Cloudflare Worker]
    B --> C[API Key Validation]
    C --> D{Valid API Key?}
    D -->|No| E[401 Unauthorized]
    D -->|Yes| F{Token in KV Cache?}
    F -->|Yes| G[Use Cached Token]
    F -->|No| H[Check Environment Token]
    H --> I{Token Valid?}
    I -->|Yes| J[Cache & Use Token]
    I -->|No| K[Refresh Token via Qwen API]
    K --> L[Cache New Token]
    G --> M[Call Qwen API]
    J --> M
    L --> M
    M --> N{Streaming?}
    N -->|Yes| O[Forward SSE Stream]
    N -->|No| P[Return JSON Response]
    O --> Q[Client Receives Stream]
    P --> Q
Loading

The wrapper acts as a secure translation layer, managing OAuth2 authentication automatically while providing OpenAI-compatible responses.

🚨 Troubleshooting

Common Issues

401 Authentication Error

  • Verify your OPENAI_API_KEY is correctly set (if using API key auth)
  • Check if client is sending Authorization: Bearer <key> header
  • Ensure the API key format is valid

OAuth Token Issues

  • Check if your QWEN_CLI_AUTH credentials are valid
  • Ensure the refresh token hasn't expired
  • Verify the JSON format matches the expected structure

KV Storage Issues

  • Confirm KV namespace is correctly configured in wrangler.toml
  • Check KV namespace permissions in Cloudflare dashboard
  • Verify the binding name matches (QWEN_KV)

Streaming Problems

  • Check the worker logs for streaming-related errors
  • Ensure the upstream Qwen API supports streaming for the requested model
  • Verify the resource_url in your OAuth credentials

Debug Logging

The worker provides comprehensive debug logging:

# Run locally to see logs
npm run dev

Look for these log patterns:

=== New chat completion request ===
Environment loaded: { hasKv: true, hasCliAuth: true, ... }
Authentication passed
loadInitialCredentials called with json: present
Token validity check: { hasAccessToken: true, expiryDate: ..., tokenValid: true }
Base URL resolved: https://your-endpoint.com/v1
Making upstream request...
Captured usage in stream: {...}

Debug Endpoints

# Health check with detailed info
curl https://your-worker.workers.dev/health

# Check if KV credentials are loaded (logs will show)
curl https://your-worker.workers.dev/v1/models

🧠 Architecture

Project Structure

src/
β”œβ”€β”€ types/                 # TypeScript type definitions
β”‚   β”œβ”€β”€ bindings.ts       # Cloudflare bindings
β”‚   β”œβ”€β”€ openai.ts         # OpenAI API types
β”‚   β”œβ”€β”€ qwen.ts           # Qwen-specific types
β”‚   └── common.ts         # Shared utilities
β”œβ”€β”€ config/               # Configuration management
β”‚   β”œβ”€β”€ constants.ts      # App constants
β”‚   β”œβ”€β”€ validation.ts     # Request validation
β”‚   └── index.ts          # Config exports
β”œβ”€β”€ services/             # Business logic services
β”‚   β”œβ”€β”€ qwenOAuthKvClient.ts    # OAuth client with KV storage
β”‚   β”œβ”€β”€ qwenProxy.ts            # HTTP proxy to Qwen API
β”‚   β”œβ”€β”€ openaiMapper.ts         # Request/response mapping
β”‚   β”œβ”€β”€ auth.ts                 # API key authentication
β”‚   └── credentials.ts          # Legacy (can be removed)
β”œβ”€β”€ routes/               # HTTP route handlers
β”‚   β”œβ”€β”€ chat.ts          # Chat completions endpoint
β”‚   β”œβ”€β”€ health.ts        # Health check endpoint
β”‚   └── models.ts        # Models listing endpoint
└── index.ts             # Hono bootstrap

Key Components

  1. OAuth Client (services/qwenOAuthKvClient.ts)

    • Manages Qwen OAuth tokens with KV persistence
    • Automatic token refresh when expired
    • Bootstrap from environment credentials
  2. Request Mapping (services/openaiMapper.ts)

    • Transforms OpenAI requests to Qwen-compatible format
    • Maps sampling parameters (temperature, top_p, etc.)
    • Validates request structure
  3. HTTP Proxy (services/qwenProxy.ts)

    • Calls Qwen API with proper authentication
    • Resolves base URL from OAuth resource_url
    • Handles both streaming and non-streaming responses
  4. Authentication (services/auth.ts)

    • Optional API key validation for endpoint access
    • Bearer token format validation

🀝 Contributing

  1. Fork the repository: https://github.com/gewoonjaap/qwen-code-cli-wrapper
  2. Create a feature branch: git checkout -b feature-name
  3. Make your changes and add tests
  4. Run linting: npm run lint
  5. Test thoroughly: npm test
  6. Commit your changes: git commit -am 'Add feature'
  7. Push to the branch: git push origin feature-name
  8. Submit a pull request

Development Setup

git clone https://github.com/gewoonjaap/qwen-code-cli-wrapper.git
cd qwen-code-cli-wrapper
npm install
cp .dev.vars.example .dev.vars
# Edit .dev.vars with your Qwen OAuth credentials
npm run dev

Available Scripts

npm run dev          # Start development server
npm run deploy       # Deploy to Cloudflare Workers
npm run lint         # Run ESLint and TypeScript checks
npm run format       # Format code with Prettier
npm test             # Run test suite
npm run build        # Build the project

πŸ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.

πŸ™ Acknowledgments


⚠️ Important: This project uses Qwen's API which may have usage limits and terms of service. Please ensure compliance with Qwen's policies when using this wrapper.

Star History Chart

About

Qwen Code OpenAI Wrapper with Cloudflare Workers

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Sponsor this project

  •  

Packages

No packages published