Skip to content

Commit b3acd4c

Browse files
feat: add Qwen Code support via OpenRouter authentication
Add Qwen Code (Qwen3) as a supported ACP-compatible agent backend, using OpenRouter as the authentication provider instead of the discontinued Qwen OAuth free tier or the expensive Coding Plan subscription. - Add Dockerfile.qwen with @qwen-code/qwen-code@0.15.6 (pinned) - Add qwen agent config block to config.toml.example - Add docs/qwen.md with OpenRouter setup guide - Update README.md: backends table, description, features - Add Qwen auth instructions to Helm NOTES.txt - Add commented-out qwen agent example to values.yaml - Add Dockerfile.qwen to CI smoke-test matrix - Add Helm unit tests for extraInitContainers/extraVolumes/extraVolumeMounts Closes #55
1 parent 4f050e5 commit b3acd4c

11 files changed

Lines changed: 388 additions & 3 deletions

File tree

.github/workflows/build.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ jobs:
7272
- { suffix: "-copilot", dockerfile: "Dockerfile.copilot", artifact: "copilot" }
7373
- { suffix: "-opencode", dockerfile: "Dockerfile.opencode", artifact: "opencode" }
7474
- { suffix: "-cursor", dockerfile: "Dockerfile.cursor", artifact: "cursor" }
75+
- { suffix: "-qwen", dockerfile: "Dockerfile.qwen", artifact: "qwen" }
7576
platform:
7677
- { os: linux/amd64, runner: ubuntu-latest }
7778
- { os: linux/arm64, runner: ubuntu-24.04-arm }
@@ -135,6 +136,7 @@ jobs:
135136
- { suffix: "-copilot", artifact: "copilot" }
136137
- { suffix: "-opencode", artifact: "opencode" }
137138
- { suffix: "-cursor", artifact: "cursor" }
139+
- { suffix: "-qwen", artifact: "qwen" }
138140
runs-on: ubuntu-latest
139141
permissions:
140142
contents: read
@@ -185,6 +187,7 @@ jobs:
185187
- { suffix: "-copilot" }
186188
- { suffix: "-opencode" }
187189
- { suffix: "-cursor" }
190+
- { suffix: "-qwen" }
188191
runs-on: ubuntu-latest
189192
permissions:
190193
contents: read

.github/workflows/docker-smoke-test.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ jobs:
2020
- { dockerfile: Dockerfile.copilot, suffix: "-copilot", agent: "copilot", agent_args: "--acp" }
2121
- { dockerfile: Dockerfile.opencode, suffix: "-opencode", agent: "opencode", agent_args: "acp" }
2222
- { dockerfile: Dockerfile.cursor, suffix: "-cursor", agent: "cursor-agent", agent_args: "acp" }
23+
- { dockerfile: Dockerfile.qwen, suffix: "-qwen", agent: "qwen", agent_args: "--acp" }
2324
runs-on: ubuntu-latest
2425
steps:
2526
- uses: actions/checkout@v6

Dockerfile.qwen

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# --- Build stage ---
2+
FROM rust:1-bookworm AS builder
3+
WORKDIR /build
4+
COPY Cargo.toml Cargo.lock ./
5+
RUN mkdir src && echo 'fn main() {}' > src/main.rs && cargo build --release && rm -rf src
6+
COPY src/ src/
7+
RUN touch src/main.rs && cargo build --release
8+
9+
# --- Runtime stage ---
10+
FROM node:22-bookworm-slim
11+
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates curl procps ripgrep tini && rm -rf /var/lib/apt/lists/*
12+
13+
# Install Qwen Code CLI (ACP support via --acp flag)
14+
# --ignore-scripts: defense-in-depth; package has no postinstall scripts at time of pinning
15+
ARG QWEN_CODE_VERSION=0.15.6
16+
RUN npm install -g @qwen-code/qwen-code@${QWEN_CODE_VERSION} --ignore-scripts --retry 3
17+
18+
# Install gh CLI
19+
RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
20+
-o /usr/share/keyrings/githubcli-archive-keyring.gpg && \
21+
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
22+
> /etc/apt/sources.list.d/github-cli.list && \
23+
apt-get update && apt-get install -y --no-install-recommends gh && \
24+
rm -rf /var/lib/apt/lists/*
25+
26+
ENV HOME=/home/node
27+
WORKDIR /home/node
28+
29+
# Note: In Helm deployments, ~/.qwen/ is provided by an init container + emptyDir volume.
30+
# This line is only needed for standalone `docker run` usage.
31+
RUN mkdir -p /home/node/.qwen && chown -R node:node /home/node/.qwen
32+
33+
COPY --from=builder --chown=node:node /build/target/release/openab /usr/local/bin/openab
34+
35+
USER node
36+
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
37+
CMD pgrep -x openab || exit 1
38+
ENTRYPOINT ["tini", "--"]
39+
CMD ["openab", "run", "-c", "/etc/openab/config.toml"]

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
![OpenAB banner](images/banner.jpg)
66

7-
A lightweight, secure, cloud-native ACP harness that bridges **Discord, Slack**, and any [Agent Client Protocol](https://github.com/anthropics/agent-protocol)-compatible coding CLI (Kiro CLI, Claude Code, Codex, Gemini, OpenCode, Copilot CLI, etc.) over stdio JSON-RPC — delivering the next-generation development experience. **Telegram, LINE, Feishu/Lark, Google Chat**, and other webhook-based platforms are supported via the standalone [Custom Gateway](gateway/).
7+
A lightweight, secure, cloud-native ACP harness that bridges **Discord, Slack**, and any [Agent Client Protocol](https://github.com/anthropics/agent-protocol)-compatible coding CLI (Kiro CLI, Claude Code, Codex, Gemini, Qwen Code, OpenCode, Copilot CLI, etc.) over stdio JSON-RPC — delivering the next-generation development experience. **Telegram, LINE, Feishu/Lark, Google Chat**, and other webhook-based platforms are supported via the standalone [Custom Gateway](gateway/).
88

99
🪼 **Join our community!** Come say hi on Discord — we'd love to have you: **[🪼 OpenAB — Official](https://discord.gg/DmbhfDZjQS)** 🎉
1010

@@ -38,7 +38,7 @@ A lightweight, secure, cloud-native ACP harness that bridges **Discord, Slack**,
3838

3939
- **Multi-platform** — supports Discord and Slack, run one or both simultaneously
4040
- **Custom Gateway** — extend to Telegram, LINE, Feishu/Lark, Google Chat, MS Teams via standalone [gateway](gateway/)
41-
- **Pluggable agent backend** — swap between Kiro CLI, Claude Code, Codex, Gemini, OpenCode, Copilot CLI via config
41+
- **Pluggable agent backend** — swap between Kiro CLI, Claude Code, Codex, Gemini, Qwen Code, OpenCode, Copilot CLI via config
4242
- **@mention trigger** — mention the bot in an allowed channel to start a conversation
4343
- **Thread-based multi-turn** — auto-creates threads; no @mention needed for follow-ups
4444
- **Multi-agent collaboration** — bot-to-bot messaging for coordinated workflows ([docs/multi-agent.md](docs/multi-agent.md))
@@ -156,6 +156,7 @@ The bot creates a thread. After that, just type in the thread — no @mention ne
156156
| Codex | `codex-acp` | [@zed-industries/codex-acp](https://github.com/zed-industries/codex-acp) | [docs/codex.md](docs/codex.md) |
157157
| Gemini | `gemini --acp` | Native | [docs/gemini.md](docs/gemini.md) |
158158
| OpenCode | `opencode acp` | Native | [docs/opencode.md](docs/opencode.md) |
159+
| Qwen Code | `qwen --acp` | Native | [docs/qwen.md](docs/qwen.md) |
159160
| Copilot CLI ⚠️ | `copilot --acp --stdio` | Native | [docs/copilot.md](docs/copilot.md) |
160161
| Cursor | `cursor-agent acp` | Native | [docs/cursor.md](docs/cursor.md) |
161162

charts/openab/templates/NOTES.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,13 @@ Agents deployed:
4242
{{- else if eq (toString $cfg.command) "cursor-agent" }}
4343
Authenticate:
4444
kubectl exec -it deployment/{{ include "openab.agentFullname" (dict "ctx" $ "agent" $name) }} -- cursor-agent login
45+
{{- else if eq (toString $cfg.command) "qwen" }}
46+
Authenticate (OpenRouter):
47+
1. Set OPENROUTER_API_KEY via secretEnv in Helm values
48+
2. Write settings.json to the PVC (persists across restarts):
49+
kubectl exec -it deployment/{{ include "openab.agentFullname" (dict "ctx" $ "agent" $name) }} -- sh -c 'mkdir -p ~/.qwen && cat > ~/.qwen/settings.json << EOF
50+
{"modelProviders":{"openai":[{"id":"qwen/qwen3-coder","baseUrl":"https://openrouter.ai/api/v1","envKey":"OPENROUTER_API_KEY"}]},"security":{"auth":{"selectedType":"openai"}},"model":{"name":"qwen/qwen3-coder"}}
51+
EOF'
4552
{{- end }}
4653

4754
Restart after auth:
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
suite: pod extensibility — extraInitContainers / extraVolumes / extraVolumeMounts
2+
templates:
3+
- templates/deployment.yaml
4+
5+
tests:
6+
- it: renders extraInitContainers as initContainers on the pod
7+
set:
8+
agents.kiro.extraInitContainers:
9+
- name: copy-qwen-settings
10+
image: busybox:1.37
11+
command: ["sh", "-c", "cp /src/settings.json /dst/settings.json"]
12+
volumeMounts:
13+
- name: qwen-config
14+
mountPath: /src
15+
readOnly: true
16+
- name: qwen-data
17+
mountPath: /dst
18+
asserts:
19+
- contains:
20+
path: spec.template.spec.initContainers
21+
content:
22+
name: copy-qwen-settings
23+
image: busybox:1.37
24+
command: ["sh", "-c", "cp /src/settings.json /dst/settings.json"]
25+
volumeMounts:
26+
- name: qwen-config
27+
mountPath: /src
28+
readOnly: true
29+
- name: qwen-data
30+
mountPath: /dst
31+
32+
- it: renders extraVolumeMounts inside the agent container
33+
set:
34+
agents.kiro.extraVolumeMounts:
35+
- name: qwen-data
36+
mountPath: /home/node/.qwen
37+
asserts:
38+
- contains:
39+
path: spec.template.spec.containers[0].volumeMounts
40+
content:
41+
name: qwen-data
42+
mountPath: /home/node/.qwen
43+
44+
- it: renders extraVolumes in the pod volumes list
45+
set:
46+
agents.kiro.extraVolumes:
47+
- name: qwen-config
48+
configMap:
49+
name: qwen-settings
50+
- name: qwen-data
51+
emptyDir: {}
52+
asserts:
53+
- contains:
54+
path: spec.template.spec.volumes
55+
content:
56+
name: qwen-config
57+
configMap:
58+
name: qwen-settings
59+
- contains:
60+
path: spec.template.spec.volumes
61+
content:
62+
name: qwen-data
63+
emptyDir: {}
64+
65+
- it: renders all three together (init container pattern for settings.json injection)
66+
set:
67+
agents.kiro.extraInitContainers:
68+
- name: copy-qwen-settings
69+
image: busybox:1.37
70+
command: ["sh", "-c", "cp /qwen-config/settings.json /home/node/.qwen/settings.json"]
71+
volumeMounts:
72+
- name: qwen-config
73+
mountPath: /qwen-config
74+
readOnly: true
75+
- name: qwen-data
76+
mountPath: /home/node/.qwen
77+
agents.kiro.extraVolumes:
78+
- name: qwen-config
79+
configMap:
80+
name: qwen-settings
81+
- name: qwen-data
82+
emptyDir: {}
83+
agents.kiro.extraVolumeMounts:
84+
- name: qwen-data
85+
mountPath: /home/node/.qwen
86+
asserts:
87+
- contains:
88+
path: spec.template.spec.initContainers
89+
content:
90+
name: copy-qwen-settings
91+
image: busybox:1.37
92+
command: ["sh", "-c", "cp /qwen-config/settings.json /home/node/.qwen/settings.json"]
93+
volumeMounts:
94+
- name: qwen-config
95+
mountPath: /qwen-config
96+
readOnly: true
97+
- name: qwen-data
98+
mountPath: /home/node/.qwen
99+
- contains:
100+
path: spec.template.spec.volumes
101+
content:
102+
name: qwen-config
103+
configMap:
104+
name: qwen-settings
105+
- contains:
106+
path: spec.template.spec.containers[0].volumeMounts
107+
content:
108+
name: qwen-data
109+
mountPath: /home/node/.qwen

charts/openab/values.yaml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,39 @@ agents:
130130
# storageClass: ""
131131
# size: 1Gi
132132
# image: "ghcr.io/openabdev/openab-cursor:latest"
133+
# qwen:
134+
# command: qwen
135+
# args:
136+
# - --acp
137+
# - --yolo
138+
# discord:
139+
# enabled: true
140+
# allowedChannels:
141+
# - "YOUR_CHANNEL_ID"
142+
# allowedUsers: []
143+
# allowBotMessages: "off"
144+
# trustedBotIds: []
145+
# workingDir: /home/node
146+
# # Auth via OpenRouter — set OPENROUTER_API_KEY and mount ~/.qwen/settings.json
147+
# # See docs/qwen.md for the full settings.json template.
148+
# env: {}
149+
# envFrom: []
150+
# secretEnv:
151+
# - name: OPENROUTER_API_KEY
152+
# secretName: qwen-secrets
153+
# secretKey: OPENROUTER_API_KEY
154+
# pool:
155+
# maxSessions: 10
156+
# sessionTtlHours: 24
157+
# reactions:
158+
# enabled: true
159+
# removeAfterReply: false
160+
# persistence:
161+
# enabled: true
162+
# existingClaim: ""
163+
# storageClass: ""
164+
# size: 1Gi
165+
# image: "ghcr.io/openabdev/openab-qwen:latest"
133166
image: ""
134167
command: kiro-cli
135168
args:

config.toml.example

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,18 @@ working_dir = "/home/agent"
101101
# working_dir = "/home/agent"
102102
# env = {} # Auth via: kubectl exec -it <pod> -- cursor-agent login
103103

104+
# [agent]
105+
# command = "qwen"
106+
# args = ["--acp", "--yolo"]
107+
# working_dir = "/home/node"
108+
# # Qwen Code uses ~/.qwen/settings.json for provider config.
109+
# # Auth via OpenRouter: set OPENROUTER_API_KEY env var and mount settings.json
110+
# # with baseUrl = "https://openrouter.ai/api/v1" and envKey = "OPENROUTER_API_KEY".
111+
# # See docs/qwen.md for the full settings.json template.
112+
# # ⚠️ Passing API keys via env is for LOCAL DEV ONLY. In K8s, use secretEnv
113+
# # (valueFrom.secretKeyRef) to avoid storing keys in plaintext config.
114+
# env = { OPENROUTER_API_KEY = "${OPENROUTER_API_KEY}" }
115+
104116
[pool]
105117
max_sessions = 10
106118
session_ttl_hours = 24

docs/config-reference.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ The AI agent subprocess that OpenAB spawns to handle messages via ACP.
8686

8787
| Key | Type | Default | Description |
8888
|-----|------|---------|-------------|
89-
| `command` | string | *required* | Agent binary (e.g. `kiro-cli`, `claude`, `codex`, `gemini`, `copilot`, `opencode`, `cursor-agent`). |
89+
| `command` | string | *required* | Agent binary (e.g. `kiro-cli`, `claude`, `codex`, `gemini`, `qwen`, `copilot`, `opencode`, `cursor-agent`). |
9090
| `args` | string[] | `[]` | CLI arguments passed to the agent. |
9191
| `working_dir` | string | `"/tmp"` | Working directory for the agent process. |
9292
| `env` | map | `{}` | Extra environment variables (e.g. `{ ANTHROPIC_API_KEY = "${ANTHROPIC_API_KEY}" }`). |

docs/multi-agent.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ See individual agent docs for authentication steps:
5151
- [Claude Code](claude-code.md)
5252
- [Codex](codex.md)
5353
- [Gemini](gemini.md)
54+
- [Qwen Code](qwen.md)
5455

5556
## Bot-to-Bot Communication
5657

0 commit comments

Comments
 (0)