{"id":30655993,"url":"https://github.com/gewoonjaap/qwen-code-cli-wrapper","last_synced_at":"2025-09-01T11:02:17.961Z","repository":{"id":311641601,"uuid":"1044397856","full_name":"GewoonJaap/qwen-code-cli-wrapper","owner":"GewoonJaap","description":"Qwen Code OpenAI Wrapper with Cloudflare Workers","archived":false,"fork":false,"pushed_at":"2025-08-25T16:48:59.000Z","size":136,"stargazers_count":4,"open_issues_count":0,"forks_count":2,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-08-25T18:32:49.629Z","etag":null,"topics":["cloudflare-workers","docker","openai","qwen","qwen-code"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/GewoonJaap.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":["GewoonJaap"],"patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"lfx_crowdfunding":null,"polar":null,"buy_me_a_coffee":"mrproper","thanks_dev":null,"custom":null}},"created_at":"2025-08-25T16:11:16.000Z","updated_at":"2025-08-25T17:59:48.000Z","dependencies_parsed_at":"2025-08-25T18:42:55.993Z","dependency_job_id":null,"html_url":"https://github.com/GewoonJaap/qwen-code-cli-wrapper","commit_stats":null,"previous_names":["gewoonjaap/qwen-code-cli-wrapper"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/GewoonJaap/qwen-code-cli-wrapper","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GewoonJaap%2Fqwen-code-cli-wrapper","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GewoonJaap%2Fqwen-code-cli-wrapper/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GewoonJaap%2Fqwen-code-cli-wrapper/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GewoonJaap%2Fqwen-code-cli-wrapper/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/GewoonJaap","download_url":"https://codeload.github.com/GewoonJaap/qwen-code-cli-wrapper/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GewoonJaap%2Fqwen-code-cli-wrapper/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":272965497,"owners_count":25023071,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-31T02:00:09.071Z","response_time":79,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cloudflare-workers","docker","openai","qwen","qwen-code"],"created_at":"2025-08-31T10:02:57.923Z","updated_at":"2025-08-31T10:04:24.927Z","avatar_url":"https://github.com/GewoonJaap.png","language":"TypeScript","funding_links":["https://github.com/sponsors/GewoonJaap","https://buymeacoffee.com/mrproper"],"categories":[],"sub_categories":[],"readme":"# 🤖 Qwen Code CLI Wrapper\n\nTransform 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.\n\n## ✨ Features\n\n- 🔐 **OAuth2 Authentication** - Uses your Qwen Code CLI credentials seamlessly\n- 🎯 **OpenAI-Compatible API** - Drop-in replacement for OpenAI endpoints\n- 📚 **OpenAI SDK Support** - Works with official OpenAI SDKs and libraries\n- 🌐 **Third-party Integration** - Compatible with Open WebUI, Cline, and more\n- 🛡️ **API Key Security** - Optional authentication layer for endpoint access\n- ⚡ **Cloudflare Workers** - Global edge deployment with low latency\n- 🔄 **Smart Token Management** - Automatic token refresh with KV storage\n- 📡 **Real-time Streaming** - Server-sent events for live responses\n- 🏗️ **Clean Architecture** - Well-structured, maintainable codebase\n- 📊 **Debug Logging** - Comprehensive logging for troubleshooting\n\n## 🚀 Quick Start\n\n### Prerequisites\n\n1. **Qwen Account** with Code CLI access\n2. **Cloudflare Account** with Workers enabled\n3. **Wrangler CLI** installed (`npm install -g wrangler`)\n\n### Step 1: Get OAuth2 Credentials\n\nYou need OAuth2 credentials from the official Qwen Code CLI.\n\n#### Using Qwen Code CLI\n\n1. **Install Qwen Code CLI**:\n   ```bash\n   npm install -g @qwen-code/qwen-code@latest\n   ```\n\n2. **Start Qwen Code and authenticate**:\n   ```bash\n   qwen\n   ```\n\n   Select your preferred authentication method when prompted.\n\n3. **Locate the credentials file**:\n\n   **Windows:**\n   ```\n   C:\\Users\\USERNAME\\.qwen\\oauth_creds.json\n   ```\n\n   **macOS/Linux:**\n   ```\n   ~/.qwen/oauth_creds.json\n   ```\n\n4. **Copy the credentials**:\n   The file contains JSON in this format:\n   ```json\n   {\n     \"access_token\": \"your_access_token_here\",\n     \"refresh_token\": \"your_refresh_token_here\",\n     \"expiry_date\": 1700000000000,\n     \"resource_url\": \"https://your-endpoint.com/v1\",\n     \"token_type\": \"Bearer\"\n   }\n   ```\n\n### Step 2: Create KV Namespace\n\n```bash\n# Create a KV namespace for token caching\nwrangler kv namespace create \"QWEN_KV\"\n```\n\nNote the namespace ID returned and update `wrangler.toml`:\n```toml\nkv_namespaces = [\n  { binding = \"QWEN_KV\", id = \"your-kv-namespace-id\" }\n]\n```\n\n### Step 3: Environment Setup\n\nCreate a `.dev.vars` file:\n```bash\n# Required: Qwen Code CLI authentication JSON\nQWEN_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\"}\n\n# Optional: API key for client authentication (if set, users must provide it in Authorization header)\n# OPENAI_API_KEY=sk-your-secret-key-here\n\n# Optional: Default model override\n# OPENAI_MODEL=qwen3-coder-plus\n\n# Optional: Custom base URL (will use resource_url from OAuth if available)\n# OPENAI_BASE_URL=https://api-inference.modelscope.cn/v1\n```\n\nFor production, set the secrets:\n```bash\nwrangler secret put QWEN_CLI_AUTH\n# Enter your OAuth credentials JSON\n```\n\n### Step 4: Deploy\n\n```bash\n# Install dependencies\nnpm install\n\n# Deploy to Cloudflare Workers\nnpm run deploy\n\n# Or run locally for development\nnpm run dev\n```\n\nThe service will be available at `https://your-worker.your-subdomain.workers.dev`\n\n## 🔧 Configuration\n\n### Environment Variables\n\n#### Core Configuration\n\n| Variable | Required | Description |\n|----------|----------|-------------|\n| `QWEN_CLI_AUTH` | ✅ | OAuth2 credentials JSON from Qwen Code CLI |\n| `OPENAI_API_KEY` | ❌ | API key for client authentication |\n| `OPENAI_MODEL` | ❌ | Default model override |\n| `OPENAI_BASE_URL` | ❌ | Custom base URL (uses OAuth resource_url if available) |\n\n#### Authentication Security\n\n- When `OPENAI_API_KEY` is set, all `/v1/*` endpoints require authentication\n- Clients must include the header: `Authorization: Bearer \u003cyour-api-key\u003e`\n- Recommended format: `sk-` followed by a random string\n- Without this variable, endpoints are publicly accessible (not recommended for production)\n\n#### OAuth Token Management\n\n- **Automatic Refresh**: Tokens are automatically refreshed when expired\n- **KV Persistence**: Refreshed tokens are stored in Cloudflare KV\n- **Fallback Logic**: KV cache → environment → refresh → retry\n- **Debug Logging**: Comprehensive token source tracking\n\n### KV Namespaces\n\n| Binding | Purpose |\n|---------|---------|\n| `QWEN_KV` | OAuth token caching and refresh storage |\n\n## 🎯 API Endpoints\n\n### Base URL\n```\nhttps://your-worker.your-subdomain.workers.dev\n```\n\n### OpenAI-Compatible Endpoints\n\n#### Chat Completions\n```http\nPOST /v1/chat/completions\nAuthorization: Bearer sk-your-api-key-here (if OPENAI_API_KEY is set)\nContent-Type: application/json\n\n{\n  \"model\": \"qwen3-coder-plus\",\n  \"messages\": [\n    {\n      \"role\": \"system\",\n      \"content\": \"You are a helpful coding assistant.\"\n    },\n    {\n      \"role\": \"user\",\n      \"content\": \"Write a Python function to calculate fibonacci numbers\"\n    }\n  ],\n  \"stream\": true,\n  \"temperature\": 0.7,\n  \"max_tokens\": 1000\n}\n```\n\n#### List Models\n```http\nGET /v1/models\nAuthorization: Bearer sk-your-api-key-here (if OPENAI_API_KEY is set)\n```\n\n**Response:**\n```json\n{\n  \"object\": \"list\",\n  \"data\": [\n    {\n      \"id\": \"qwen3-coder-plus\",\n      \"object\": \"model\",\n      \"created\": 1700000000,\n      \"owned_by\": \"qwen\"\n    },\n    {\n      \"id\": \"qwen3-coder-flash\",\n      \"object\": \"model\",\n      \"created\": 1700000000,\n      \"owned_by\": \"qwen\"\n    }\n  ]\n}\n```\n\n### Utility Endpoints\n\n#### Health Check\n```http\nGET /health\n```\n*No authentication required*\n\n**Response:**\n```json\n{\n  \"status\": \"ok\",\n  \"uptime\": 1700000000,\n  \"version\": \"qwen-worker-1.0.0\"\n}\n```\n\n## 💻 Usage Examples\n\n### OpenAI SDK (Python)\n```python\nfrom openai import OpenAI\n\n# Initialize with your worker endpoint\nclient = OpenAI(\n    base_url=\"https://your-worker.workers.dev/v1\",\n    api_key=\"sk-your-secret-api-key-here\"  # Only if OPENAI_API_KEY is set\n)\n\n# Chat completion\nresponse = client.chat.completions.create(\n    model=\"qwen3-coder-plus\",\n    messages=[\n        {\"role\": \"system\", \"content\": \"You are a helpful coding assistant.\"},\n        {\"role\": \"user\", \"content\": \"Write a binary search algorithm in Python\"}\n    ],\n    temperature=0.2,\n    max_tokens=500,\n    stream=True\n)\n\nfor chunk in response:\n    if chunk.choices[0].delta.content:\n        print(chunk.choices[0].delta.content, end=\"\")\n```\n\n### OpenAI SDK (JavaScript/TypeScript)\n```typescript\nimport OpenAI from 'openai';\n\nconst openai = new OpenAI({\n  baseURL: 'https://your-worker.workers.dev/v1',\n  apiKey: 'sk-your-secret-api-key-here', // Only if OPENAI_API_KEY is set\n});\n\nconst stream = await openai.chat.completions.create({\n  model: 'qwen3-coder-plus',\n  messages: [\n    { role: 'user', content: 'Explain async/await in JavaScript' }\n  ],\n  stream: true,\n  temperature: 0.7,\n});\n\nfor await (const chunk of stream) {\n  const content = chunk.choices[0]?.delta?.content || '';\n  process.stdout.write(content);\n}\n```\n\n### cURL Examples\n```bash\n# Chat completion (non-streaming)\ncurl -X POST https://your-worker.workers.dev/v1/chat/completions \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Authorization: Bearer sk-your-secret-api-key-here\" \\\n  -d '{\n    \"model\": \"qwen3-coder-plus\",\n    \"messages\": [\n      {\"role\": \"user\", \"content\": \"Hello! How are you?\"}\n    ],\n    \"temperature\": 0.7\n  }'\n\n# Chat completion (streaming)\ncurl -N -X POST https://your-worker.workers.dev/v1/chat/completions \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Authorization: Bearer sk-your-secret-api-key-here\" \\\n  -d '{\n    \"model\": \"qwen3-coder-flash\",\n    \"messages\": [\n      {\"role\": \"user\", \"content\": \"Write a TypeScript hello world\"}\n    ],\n    \"stream\": true\n  }'\n\n# List available models\ncurl https://your-worker.workers.dev/v1/models \\\n  -H \"Authorization: Bearer sk-your-secret-api-key-here\"\n\n# Health check\ncurl https://your-worker.workers.dev/health\n```\n\n### Open WebUI Integration\n\n1. **Add as OpenAI-compatible endpoint**:\n   - Base URL: `https://your-worker.workers.dev/v1`\n   - API Key: `sk-your-secret-api-key-here` (only if `OPENAI_API_KEY` is set)\n\n2. **Auto-discovery**:\n   Open WebUI will automatically discover available models through the `/v1/models` endpoint.\n\n## 🏗️ How It Works\n\n```mermaid\ngraph TD\n    A[Client Request] --\u003e B[Cloudflare Worker]\n    B --\u003e C[API Key Validation]\n    C --\u003e D{Valid API Key?}\n    D --\u003e|No| E[401 Unauthorized]\n    D --\u003e|Yes| F{Token in KV Cache?}\n    F --\u003e|Yes| G[Use Cached Token]\n    F --\u003e|No| H[Check Environment Token]\n    H --\u003e I{Token Valid?}\n    I --\u003e|Yes| J[Cache \u0026 Use Token]\n    I --\u003e|No| K[Refresh Token via Qwen API]\n    K --\u003e L[Cache New Token]\n    G --\u003e M[Call Qwen API]\n    J --\u003e M\n    L --\u003e M\n    M --\u003e N{Streaming?}\n    N --\u003e|Yes| O[Forward SSE Stream]\n    N --\u003e|No| P[Return JSON Response]\n    O --\u003e Q[Client Receives Stream]\n    P --\u003e Q\n```\n\nThe wrapper acts as a secure translation layer, managing OAuth2 authentication automatically while providing OpenAI-compatible responses.\n\n## 🚨 Troubleshooting\n\n### Common Issues\n\n**401 Authentication Error**\n- Verify your `OPENAI_API_KEY` is correctly set (if using API key auth)\n- Check if client is sending `Authorization: Bearer \u003ckey\u003e` header\n- Ensure the API key format is valid\n\n**OAuth Token Issues**\n- Check if your `QWEN_CLI_AUTH` credentials are valid\n- Ensure the refresh token hasn't expired\n- Verify the JSON format matches the expected structure\n\n**KV Storage Issues**\n- Confirm KV namespace is correctly configured in `wrangler.toml`\n- Check KV namespace permissions in Cloudflare dashboard\n- Verify the binding name matches (`QWEN_KV`)\n\n**Streaming Problems**\n- Check the worker logs for streaming-related errors\n- Ensure the upstream Qwen API supports streaming for the requested model\n- Verify the resource_url in your OAuth credentials\n\n### Debug Logging\n\nThe worker provides comprehensive debug logging:\n\n```bash\n# Run locally to see logs\nnpm run dev\n```\n\nLook for these log patterns:\n```\n=== New chat completion request ===\nEnvironment loaded: { hasKv: true, hasCliAuth: true, ... }\nAuthentication passed\nloadInitialCredentials called with json: present\nToken validity check: { hasAccessToken: true, expiryDate: ..., tokenValid: true }\nBase URL resolved: https://your-endpoint.com/v1\nMaking upstream request...\nCaptured usage in stream: {...}\n```\n\n### Debug Endpoints\n\n```bash\n# Health check with detailed info\ncurl https://your-worker.workers.dev/health\n\n# Check if KV credentials are loaded (logs will show)\ncurl https://your-worker.workers.dev/v1/models\n```\n\n## 🧠 Architecture\n\n### Project Structure\n```\nsrc/\n├── types/                 # TypeScript type definitions\n│   ├── bindings.ts       # Cloudflare bindings\n│   ├── openai.ts         # OpenAI API types\n│   ├── qwen.ts           # Qwen-specific types\n│   └── common.ts         # Shared utilities\n├── config/               # Configuration management\n│   ├── constants.ts      # App constants\n│   ├── validation.ts     # Request validation\n│   └── index.ts          # Config exports\n├── services/             # Business logic services\n│   ├── qwenOAuthKvClient.ts    # OAuth client with KV storage\n│   ├── qwenProxy.ts            # HTTP proxy to Qwen API\n│   ├── openaiMapper.ts         # Request/response mapping\n│   ├── auth.ts                 # API key authentication\n│   └── credentials.ts          # Legacy (can be removed)\n├── routes/               # HTTP route handlers\n│   ├── chat.ts          # Chat completions endpoint\n│   ├── health.ts        # Health check endpoint\n│   └── models.ts        # Models listing endpoint\n└── index.ts             # Hono bootstrap\n```\n\n### Key Components\n\n1. **OAuth Client** (`services/qwenOAuthKvClient.ts`)\n   - Manages Qwen OAuth tokens with KV persistence\n   - Automatic token refresh when expired\n   - Bootstrap from environment credentials\n\n2. **Request Mapping** (`services/openaiMapper.ts`)\n   - Transforms OpenAI requests to Qwen-compatible format\n   - Maps sampling parameters (temperature, top_p, etc.)\n   - Validates request structure\n\n3. **HTTP Proxy** (`services/qwenProxy.ts`)\n   - Calls Qwen API with proper authentication\n   - Resolves base URL from OAuth resource_url\n   - Handles both streaming and non-streaming responses\n\n4. **Authentication** (`services/auth.ts`)\n   - Optional API key validation for endpoint access\n   - Bearer token format validation\n\n## 🤝 Contributing\n\n1. Fork the repository: `https://github.com/gewoonjaap/qwen-code-cli-wrapper`\n2. Create a feature branch: `git checkout -b feature-name`\n3. Make your changes and add tests\n4. Run linting: `npm run lint`\n5. Test thoroughly: `npm test`\n6. Commit your changes: `git commit -am 'Add feature'`\n7. Push to the branch: `git push origin feature-name`\n8. Submit a pull request\n\n### Development Setup\n\n```bash\ngit clone https://github.com/gewoonjaap/qwen-code-cli-wrapper.git\ncd qwen-code-cli-wrapper\nnpm install\ncp .dev.vars.example .dev.vars\n# Edit .dev.vars with your Qwen OAuth credentials\nnpm run dev\n```\n\n### Available Scripts\n\n```bash\nnpm run dev          # Start development server\nnpm run deploy       # Deploy to Cloudflare Workers\nnpm run lint         # Run ESLint and TypeScript checks\nnpm run format       # Format code with Prettier\nnpm test             # Run test suite\nnpm run build        # Build the project\n```\n\n## 📄 License\n\nThis codebase is provided for personal use and self-hosting only.\n\nRedistribution of the codebase, whether in original or modified form, is not permitted without prior written consent from the author.\n\nYou may fork and modify the repository solely for the purpose of running and self-hosting your own instance.\n\nAny other form of distribution, sublicensing, or commercial use is strictly prohibited unless explicitly authorized.\n\n## 🙏 Acknowledgments\n\n- Built on [Cloudflare Workers](https://workers.cloudflare.com/)\n- Uses [Hono](https://hono.dev/) web framework\n- Inspired by the official [Qwen Code CLI](https://github.com/QwenLM/qwen-code)\n- OAuth patterns adapted from Qwen Code CLI implementation\n\n---\n\n**⚠️ 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.\n\n[![Star History Chart](https://api.star-history.com/svg?repos=gewoonjaap/qwen-code-cli-wrapper\u0026type=Date)](https://www.star-history.com/#gewoonjaap/qwen-code-cli-wrapper\u0026Date)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgewoonjaap%2Fqwen-code-cli-wrapper","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgewoonjaap%2Fqwen-code-cli-wrapper","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgewoonjaap%2Fqwen-code-cli-wrapper/lists"}