# Qwen (DashScope) — VS Code Custom Endpoint Setup Guide

> **TL;DR:** `qwen3.8-max` (vision) works both direct and via the local proxy. OpenRouter's live rankings card shows unversioned Qwen3.8 Max at **#2 with 53.4**, while the exact `qwen/qwen3.8-max-0902` API metadata row is **45.4**; the public API no longer exposes the unversioned ID. The proxy forwards the upstream `model` value unchanged, so use the exact model ID supported by the endpoint you choose. The proxy gives you dynamic thinking suppression: reasoning stays ON in plain chat but turns OFF automatically when tools are invoked. The direct path is simpler if you don't need reasoning in chat.

## At a Glance

| Field                  | Value                                                                            |
| ---------------------- | -------------------------------------------------------------------------------- |
| Mode                   | **Proxy** (local on `:3458`) **or** **Direct** (static `enable_thinking: false`) |
| Billing                | **Pay-as-You-Go only** — 1M-token free quota for new users                       |
| Vision                 | ✅ Yes (`qwen3.8-max`)                                                           |
| Tool calling           | ✅ Yes                                                                           |
| Context                | 1M                                                                               |
| Max output             | 131K                                                                             |
| Endpoint               | `https://dashscope-intl.aliyuncs.com/compatible-mode/v1/chat/completions`        |
| Proxy endpoint         | `http://127.0.0.1:3458/v1/chat/completions`                                      |
| Auth                   | `Authorization: Bearer $DASHSCOPE_API_KEY`                                       |
| `requestBody` (direct) | `enable_thinking: false`                                                         |
| `requestBody` (proxy)  | _none — proxy injects based on tool activity_                                    |

### Models

| Model         | Vision | Context | Max output | Notes                                                   |
| ------------- | ------ | ------- | ---------- | ------------------------------------------------------- |
| `qwen3.8-max` | ✅ Yes | 1M      | 131K       | Frontier multimodal model; reasoning enabled by default |

> The live `chatLanguageModels.json` points Qwen at the local proxy by default; the direct DashScope URL is shown below for users who prefer a static `enable_thinking: false` setup.

### OpenRouter snapshot records

These are separate OpenRouter catalog entries, not two picker labels for one model:

| OpenRouter slug         | Snapshot | AA Intelligence Index | Notes                                                                         |
| ----------------------- | -------- | --------------------- | ----------------------------------------------------------------------------- |
| `qwen/qwen3.8-max`      | 0803     | **53.4 (#2)**         | Current ranking-card result; exact unversioned API entry is no longer exposed |
| `qwen/qwen3.8-max-0902` | 0902     | **45.4**              | September snapshot; current exact-ID API metadata                             |

The DashScope custom-endpoint snippets below use the provider model ID `qwen3.8-max`. Do not replace it with the OpenRouter `qwen/qwen3.8-max-0902` slug unless you also change the upstream URL to OpenRouter and have verified that path independently.

### Optional OpenRouter snapshot proxy

The proxy forwards the `model` field without mapping it, so it can also be pointed at OpenRouter for the two catalog snapshots. This is an **optional, unvalidated OpenRouter path**; it is separate from the validated DashScope setup above and requires an OpenRouter API key.

1. Add this to the repo-root `.env` file:

   ```dotenv
   QWEN_UPSTREAM_URL=https://openrouter.ai/api/v1/chat/completions
   ```

2. Start the existing Qwen proxy:

   ```bash
   npm run proxy:qwen
   ```

3. Add a separate provider group using the OpenRouter key and these model entries:

   ```json
   {
     "name": "Qwen (OpenRouter snapshots)",
     "vendor": "customendpoint",
     "apiKey": "",
     "apiType": "chat-completions",
     "models": [
       {
         "id": "qwen/qwen3.8-max",
         "name": "Qwen 3.8 Max (0803)",
         "url": "http://127.0.0.1:3458/v1/chat/completions",
         "toolCalling": true,
         "vision": true,
         "streaming": true,
         "maxInputTokens": 991000,
         "maxOutputTokens": 131072
       },
       {
         "id": "qwen/qwen3.8-max-0902",
         "name": "Qwen 3.8 Max (0902)",
         "url": "http://127.0.0.1:3458/v1/chat/completions",
         "toolCalling": true,
         "vision": true,
         "streaming": true,
         "maxInputTokens": 991000,
         "maxOutputTokens": 131072
       }
     ]
   }
   ```

Set the API key through **Chat: Manage Language Models**, then verify the proxy with `curl http://127.0.0.1:3458/healthz`. Do not run this route at the same time as the default DashScope route unless each process uses a different `QWEN_PROXY_PORT` and log path.

## Quick Start

### With Proxy (recommended)

1. **Start the proxy:** `npm run proxy:qwen` (or `npx copilot-custom-endpoint qwen`).
2. **Use the proxy-path JSON snippet** below.
3. **Set your DashScope API key** via Command Palette → **Chat: Manage Language Models**.
4. **Configure the Utility Small Model** — Open Settings → search **"Chat: Utility Small Model"** → pick your fastest model (e.g., DeepSeek V4 Flash or MiMo V2.5). [Why?](../../README.md#4-configure-the-utility-small-model)
5. **Restart VS Code.** Reasoning will be visible in plain chat and suppressed on tool turns.

### Direct (no proxy)

1. **Use the direct-path JSON snippet** below.
2. **Set your `DASHSCOPE_API_KEY`** via Command Palette → **Chat: Manage Language Models**.
3. **Configure the Utility Small Model** — Open Settings → search **"Chat: Utility Small Model"** → pick your fastest model (e.g., DeepSeek V4 Flash or MiMo V2.5). [Why?](../../README.md#4-configure-the-utility-small-model)
4. **Restart VS Code** and pick "Qwen 3.8 Max".

## Setup

### Regional endpoints

DashScope is region-specific — your API key only works on the endpoint it was created for:

| Region        | Endpoint                                                                  |
| ------------- | ------------------------------------------------------------------------- |
| Singapore     | `https://dashscope-intl.aliyuncs.com/compatible-mode/v1/chat/completions` |
| China         | `https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions`      |
| US (Virginia) | `https://dashscope-us.aliyuncs.com/compatible-mode/v1/chat/completions`   |

### 1. VS Code configuration

| OS      | Path                                                              |
| ------- | ----------------------------------------------------------------- |
| Windows | `%APPDATA%\Code\User\chatLanguageModels.json`                     |
| macOS   | `~/Library/Application Support/Code/User/chatLanguageModels.json` |
| Linux   | `~/.config/Code/User/chatLanguageModels.json`                     |

#### Direct path

```json
{
  "name": "Qwen",
  "vendor": "customendpoint",
  "apiKey": "",
  "apiType": "chat-completions",
  "models": [
    {
      "id": "qwen3.8-max",
      "name": "Qwen 3.8 Max (vision)",
      "url": "https://dashscope-intl.aliyuncs.com/compatible-mode/v1/chat/completions",
      "toolCalling": true,
      "vision": true,
      "streaming": true,
      "maxInputTokens": 991000,
      "maxOutputTokens": 131072,
      "requestBody": { "enable_thinking": false }
    }
  ]
}
```

> The live `chatLanguageModels.json` points Qwen at the local proxy with no `requestBody` override; use the snippet above only if you're not running the proxy.

#### Proxy path

1. Start the proxy:

   ```bash
   node proxy/qwen-proxy.mjs
   ```

   Verify:

   ```bash
   curl http://127.0.0.1:3458/healthz
   ```

2. Use this snippet (URLs point at the proxy; no `requestBody` override):

```json
{
  "name": "Qwen",
  "vendor": "customendpoint",
  "apiKey": "",
  "apiType": "chat-completions",
  "models": [
    {
      "id": "qwen3.8-max",
      "name": "Qwen 3.8 Max (vision)",
      "url": "http://127.0.0.1:3458/v1/chat/completions",
      "toolCalling": true,
      "vision": true,
      "streaming": true,
      "maxInputTokens": 991000,
      "maxOutputTokens": 131072
    }
  ]
}
```

> **Keep the proxy terminal open** while using Qwen via proxy. Configuration knobs are documented in [Local Proxy](#local-proxy) below.

### 2. API key

DashScope is **Pay-as-You-Go only** — but new Model Studio users get **1M input + 1M output tokens per model free for 90 days**. Prepaid token packages are also available.

1. Open the Command Palette (`Ctrl+Shift+P`).
2. Run **Chat: Manage Language Models**.
3. Find the **Qwen** group → **Update API Key**.
4. Paste your DashScope API key.

> VS Code replaces `"apiKey": ""` with a `${input:chat.lm.secret.<id>}` reference. Keys are region-specific.

## Local Proxy

The `proxy/qwen-proxy.mjs` adds dynamic thinking suppression on top of the direct integration. Reasoning stays ON in plain chat and turns OFF automatically only when tools are invoked. If you don't need reasoning visibility, the direct path (`### Direct (no proxy)` above) is simpler.

| Setting      | Value                                                 |
| ------------ | ----------------------------------------------------- |
| Script       | `proxy/qwen-proxy.mjs`                                |
| Listen URL   | `http://127.0.0.1:3458/v1/chat/completions`           |
| Health check | `curl http://127.0.0.1:3458/healthz`                  |
| Start        | `npm run proxy:qwen` (or `node proxy/qwen-proxy.mjs`) |
| Help         | `node proxy/qwen-proxy.mjs --help`                    |

### Environment variables

Set in `.env` at the repo root (the proxy `import 'dotenv/config'` automatically).

| Variable                                 | Default                                                                   | Purpose                                            |
| ---------------------------------------- | ------------------------------------------------------------------------- | -------------------------------------------------- |
| `QWEN_PROXY_PORT`                        | `3458`                                                                    | Local listen port                                  |
| `QWEN_UPSTREAM_URL`                      | `https://dashscope-intl.aliyuncs.com/compatible-mode/v1/chat/completions` | Upstream DashScope endpoint                        |
| `QWEN_PROXY_LOG`                         | `debug_log/qwen-proxy.ndjson`                                             | Redacted NDJSON log path                           |
| `QWEN_PROXY_DISABLE_THINKING_WITH_TOOLS` | `1`                                                                       | Set to `0` to skip tool-aware thinking suppression |

### What the proxy does

The proxy detects active tool use by examining the conversation state (a `"tool"`-role message in history or a non-default `tool_choice`), not just the presence of a `tools` array. This correctly handles tool-enabled conversations even when the client sends `tools` in an earlier request but omits it from subsequent turns.

| Condition                                                     | Action                                |
| ------------------------------------------------------------- | ------------------------------------- |
| Tool-role message in history **or** non-default `tool_choice` | Inject `enable_thinking: false`       |
| Plain chat (no tool activity)                                 | Delete `enable_thinking` (default ON) |

### Forwards header & streaming

- Forwards your `Authorization` header upstream unchanged.
- Preserves streaming responses (SSE).
- Writes redacted request summaries to `debug_log/qwen-proxy.ndjson`.

> **Keep the proxy terminal open** while using Qwen via proxy.

## Notes

- **Vision (`qwen3.8-max`)** uses OpenAI-compatible `content` array format. Base64 data URIs work reliably; external image URLs may fail if DashScope can't reach them. If a drag-and-drop image fails to load, providing the absolute file path (e.g. `c:\path\to\image.png`) in the prompt is a reliable workaround.
- **Qwen 3.8 reasoning:** `qwen3.8-max` enables reasoning by default and supports `reasoning_effort` values `low`, `medium`, and `xhigh` (default). The direct snippet disables reasoning for stable VS Code tool loops; the proxy leaves it on for plain chat and suppresses it when tool activity is detected. The OpenRouter ranking card and 0902 API metadata are separate source records; the benchmark difference does not mean the DashScope config should silently switch IDs.
- **Thinking trade-off:** Direct = thinking always off (loops stable, no reasoning visible). Proxy = thinking on in plain chat, off in tool turns.
- **`tool_choice` only supports `auto`** — don't override it (VS Code's default is `auto`).

## Troubleshooting

| Symptom                                         | Likely cause                                 | Fix                                                                                                          |
| ----------------------------------------------- | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| "Connection refused" (proxy mode)               | Proxy not running                            | `npm run proxy:qwen`                                                                                         |
| Tool loops fail with `reasoning_content` errors | Direct path missing `enable_thinking: false` | Add `enable_thinking: false` to `requestBody`                                                                |
| Tool loops still fail with proxy                | Proxy not rewriting                          | Check `debug_log/qwen-proxy.ndjson` — verify `hasTools: true` requests have `rewrittenEnableThinking: false` |
| Vision fails with external image URL            | DashScope couldn't reach the URL             | Use a base64 data URI instead                                                                                |
| 401 Unauthorized                                | API key region mismatch                      | Match your key to the regional endpoint                                                                      |
| Want to switch back to direct                   | Proxy mode active                            | Revert `url` to DashScope endpoint and restore `requestBody.enable_thinking: false`                          |

## Pricing

For the cross-provider comparison, see [docs/pricing.md](../pricing.md). DashScope (international) rates for **non-thinking** mode:

| Model         | Input (≤ 256K tokens) | Input (> 256K tokens) | Output (≤ 256K tokens) | Output (> 256K tokens) |
| ------------- | --------------------- | --------------------- | ---------------------- | ---------------------- |
| `qwen3.8-max` | $2.00                 | —                     | $6.00                  | —                      |

> **Billing options:** Qwen Cloud now offers a separate Token Plan subscription with access to Qwen3.8 Max. The validated VS Code snippets in this document use the PAYG DashScope-compatible endpoint and API key; do not assume the Token Plan base URL or credentials are interchangeable without following Qwen Cloud's current integration instructions.

> Free quota: 1M input + 1M output tokens per model, valid 90 days after activating Model Studio.
