Skip to content

Commit 3fe9021

Browse files
authored
Merge pull request #12 from link-assistant/issue-11-eb21e33818d4
Add Qwen Code CLI support
2 parents ac40b85 + 29bf13e commit 3fe9021

File tree

10 files changed

+1720
-12
lines changed

10 files changed

+1720
-12
lines changed

.github/workflows/e2e-qwen.yml

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
name: E2E Tests - Qwen Code CLI
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
test_prompt:
7+
description: 'Test prompt to send to Qwen Code'
8+
required: false
9+
default: 'Hello, respond with a single word: working'
10+
type: string
11+
working_directory:
12+
description: 'Working directory for the test'
13+
required: false
14+
default: '/tmp/qwen-e2e-test'
15+
type: string
16+
model:
17+
description: 'Model to use for testing'
18+
required: false
19+
default: 'qwen3-coder'
20+
type: choice
21+
options:
22+
- qwen3-coder
23+
- coder
24+
- gpt-4o
25+
26+
concurrency:
27+
group: e2e-qwen-${{ github.ref }}
28+
cancel-in-progress: true
29+
30+
jobs:
31+
e2e-qwen-test:
32+
name: Qwen Code E2E Test
33+
runs-on: ubuntu-latest
34+
steps:
35+
- uses: actions/checkout@v4
36+
37+
- name: Setup Node.js
38+
uses: actions/setup-node@v4
39+
with:
40+
node-version: '20.x'
41+
42+
- name: Install dependencies
43+
working-directory: js
44+
run: npm install
45+
46+
- name: Install Qwen Code CLI
47+
run: |
48+
npm install -g @qwen-code/qwen-code@latest
49+
echo "Qwen Code CLI version:"
50+
qwen --version || echo "Version command not available"
51+
52+
- name: Create test working directory
53+
run: |
54+
mkdir -p ${{ github.event.inputs.working_directory || '/tmp/qwen-e2e-test' }}
55+
echo "# Test Project" > "${{ github.event.inputs.working_directory || '/tmp/qwen-e2e-test' }}/README.md"
56+
echo "This is a test project for Qwen Code E2E testing." >> "${{ github.event.inputs.working_directory || '/tmp/qwen-e2e-test' }}/README.md"
57+
58+
- name: Test Qwen Code CLI directly
59+
id: direct_test
60+
continue-on-error: true
61+
run: |
62+
echo "Testing Qwen Code CLI directly..."
63+
cd "${{ github.event.inputs.working_directory || '/tmp/qwen-e2e-test' }}"
64+
65+
# Test with headless mode (-p flag)
66+
# Note: This requires authentication. In CI, we test that the CLI is installed
67+
# and responds correctly to basic commands.
68+
69+
# Test help command
70+
echo "=== Testing help command ==="
71+
qwen --help || echo "Help command failed"
72+
73+
# Test version
74+
echo "=== Testing version ==="
75+
qwen --version || echo "Version command not available"
76+
77+
echo "direct_test_completed=true" >> $GITHUB_OUTPUT
78+
79+
- name: Test Qwen tool configuration
80+
run: |
81+
echo "Testing Qwen tool configuration..."
82+
cd js
83+
84+
# Create a test script to verify the tool configuration
85+
cat > test-qwen-config.mjs << 'EOF'
86+
import { getTool, listTools, isToolSupported } from './src/tools/index.mjs';
87+
88+
console.log('=== Qwen Tool Configuration Test ===\n');
89+
90+
// Test 1: List tools includes qwen
91+
const tools = listTools();
92+
console.log('Available tools:', tools);
93+
if (!tools.includes('qwen')) {
94+
console.error('FAIL: qwen not in tool list');
95+
process.exit(1);
96+
}
97+
console.log('PASS: qwen is in tool list\n');
98+
99+
// Test 2: isToolSupported returns true for qwen
100+
if (!isToolSupported({ toolName: 'qwen' })) {
101+
console.error('FAIL: qwen not supported');
102+
process.exit(1);
103+
}
104+
console.log('PASS: qwen is supported\n');
105+
106+
// Test 3: getTool returns qwen configuration
107+
const qwenTool = getTool({ toolName: 'qwen' });
108+
console.log('Qwen tool config:', {
109+
name: qwenTool.name,
110+
displayName: qwenTool.displayName,
111+
executable: qwenTool.executable,
112+
defaultModel: qwenTool.defaultModel,
113+
supportsJsonOutput: qwenTool.supportsJsonOutput,
114+
supportsYolo: qwenTool.supportsYolo,
115+
});
116+
117+
// Test 4: Model mapping works
118+
console.log('\nModel mapping tests:');
119+
const testModels = ['qwen3-coder', 'coder', 'gpt-4o', 'custom-model'];
120+
for (const model of testModels) {
121+
const mapped = qwenTool.mapModelToId({ model });
122+
console.log(` ${model} -> ${mapped}`);
123+
}
124+
125+
// Test 5: buildArgs produces correct arguments
126+
console.log('\nbuildArgs test:');
127+
const args = qwenTool.buildArgs({
128+
prompt: 'Test prompt',
129+
model: 'qwen3-coder',
130+
yolo: true,
131+
streamJson: true,
132+
});
133+
console.log(' Args:', args);
134+
135+
if (!args.includes('-p') || !args.includes('Test prompt')) {
136+
console.error('FAIL: prompt argument missing');
137+
process.exit(1);
138+
}
139+
if (!args.includes('--yolo')) {
140+
console.error('FAIL: --yolo flag missing');
141+
process.exit(1);
142+
}
143+
if (!args.includes('--output-format') || !args.includes('stream-json')) {
144+
console.error('FAIL: stream-json output format missing');
145+
process.exit(1);
146+
}
147+
console.log('PASS: buildArgs produces correct arguments\n');
148+
149+
// Test 6: buildCommand produces correct command
150+
console.log('buildCommand test:');
151+
const cmd = qwenTool.buildCommand({
152+
workingDirectory: '/tmp/test',
153+
prompt: 'Review code',
154+
model: 'coder',
155+
});
156+
console.log(' Command:', cmd);
157+
158+
if (!cmd.includes('qwen')) {
159+
console.error('FAIL: command does not include qwen');
160+
process.exit(1);
161+
}
162+
console.log('PASS: buildCommand produces correct command\n');
163+
164+
// Test 7: parseOutput handles NDJSON
165+
console.log('parseOutput test:');
166+
const testOutput = '{"type":"message","content":"Hello"}\n{"type":"done"}';
167+
const messages = qwenTool.parseOutput({ output: testOutput });
168+
console.log(' Parsed messages:', messages);
169+
170+
if (messages.length !== 2) {
171+
console.error('FAIL: expected 2 messages, got', messages.length);
172+
process.exit(1);
173+
}
174+
console.log('PASS: parseOutput handles NDJSON correctly\n');
175+
176+
// Test 8: extractSessionId works
177+
console.log('extractSessionId test:');
178+
const sessionOutput = '{"session_id":"test-123"}\n{"type":"done"}';
179+
const sessionId = qwenTool.extractSessionId({ output: sessionOutput });
180+
console.log(' Session ID:', sessionId);
181+
182+
if (sessionId !== 'test-123') {
183+
console.error('FAIL: expected test-123, got', sessionId);
184+
process.exit(1);
185+
}
186+
console.log('PASS: extractSessionId works correctly\n');
187+
188+
// Test 9: detectErrors finds errors
189+
console.log('detectErrors test:');
190+
const errorOutput = '{"type":"error","message":"Test error"}';
191+
const errorResult = qwenTool.detectErrors({ output: errorOutput });
192+
console.log(' Error result:', errorResult);
193+
194+
if (!errorResult.hasError) {
195+
console.error('FAIL: expected error to be detected');
196+
process.exit(1);
197+
}
198+
console.log('PASS: detectErrors works correctly\n');
199+
200+
console.log('=== All Qwen Tool Configuration Tests Passed ===');
201+
EOF
202+
203+
node test-qwen-config.mjs
204+
rm test-qwen-config.mjs
205+
206+
- name: Test agent-commander with Qwen (dry-run)
207+
run: |
208+
echo "Testing agent-commander with Qwen tool (dry-run mode)..."
209+
cd js
210+
211+
# Test the CLI in dry-run mode
212+
node bin/start-agent.mjs \
213+
--tool qwen \
214+
--working-directory "${{ github.event.inputs.working_directory || '/tmp/qwen-e2e-test' }}" \
215+
--prompt "${{ github.event.inputs.test_prompt || 'Hello, respond with a single word: working' }}" \
216+
--model "${{ github.event.inputs.model || 'qwen3-coder' }}" \
217+
--dry-run
218+
219+
- name: E2E Test Summary
220+
run: |
221+
echo "=== E2E Test Summary ==="
222+
echo ""
223+
echo "✅ Qwen Code CLI installed successfully"
224+
echo "✅ Qwen tool configuration tests passed"
225+
echo "✅ agent-commander dry-run with Qwen completed"
226+
echo ""
227+
echo "Note: Full E2E tests with actual API calls require authentication."
228+
echo "To run authenticated tests locally:"
229+
echo " 1. Install Qwen Code: npm install -g @qwen-code/qwen-code@latest"
230+
echo " 2. Authenticate: qwen then /auth"
231+
echo " 3. Run: start-agent --tool qwen --working-directory ./project --prompt 'Your prompt'"

README.md

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# agent-commander
22

3-
A JavaScript library to control agents enclosed in CLI commands like Anthropic Claude Code CLI, OpenAI Codex, OpenCode, and @link-assistant/agent.
3+
A JavaScript library to control agents enclosed in CLI commands like Anthropic Claude Code CLI, OpenAI Codex, OpenCode, Qwen Code, and @link-assistant/agent.
44

55
Built on the success of [hive-mind](https://github.com/link-assistant/hive-mind), `agent-commander` provides a flexible JavaScript interface and CLI tools for managing agent processes with various isolation levels.
66

@@ -11,6 +11,7 @@ Built on the success of [hive-mind](https://github.com/link-assistant/hive-mind)
1111
- `claude` - Anthropic Claude Code CLI
1212
- `codex` - OpenAI Codex CLI
1313
- `opencode` - OpenCode CLI
14+
- `qwen` - Qwen Code CLI (Alibaba's AI coding agent)
1415
- `agent` - @link-assistant/agent (unrestricted OpenCode fork)
1516
- **Multiple Isolation Modes**:
1617
- No isolation (direct execution)
@@ -56,6 +57,7 @@ bun add agent-commander
5657
| `claude` | Anthropic Claude Code CLI | ✅ (stream-json) | ✅ (stream-json) | `sonnet`, `opus`, `haiku` |
5758
| `codex` | OpenAI Codex CLI ||| `gpt5`, `o3`, `gpt4o` |
5859
| `opencode` | OpenCode CLI ||| `grok`, `gemini`, `sonnet` |
60+
| `qwen` | Qwen Code CLI | ✅ (stream-json) | ✅ (stream-json) | `qwen3-coder`, `coder`, `gpt-4o` |
5961
| `agent` | @link-assistant/agent ||| `grok`, `sonnet`, `haiku` |
6062

6163
### Claude-specific Features
@@ -70,6 +72,17 @@ The Claude Code CLI supports additional features:
7072
- **Verbose mode**: Enable with `--verbose` for detailed output
7173
- **User message replay**: Use `--replay-user-messages` for streaming acknowledgment
7274

75+
### Qwen-specific Features
76+
77+
The [Qwen Code CLI](https://github.com/QwenLM/qwen-code) supports additional features:
78+
79+
- **Stream JSON format**: Uses `--output-format stream-json` for real-time NDJSON streaming
80+
- **Auto-approval mode**: Use `--yolo` flag for automatic action approval (enabled by default)
81+
- **Session management**: Support for `--resume <sessionId>` and `--continue` for most recent session
82+
- **Context options**: Use `--all-files` to include all files, `--include-directories` for specific dirs
83+
- **Partial messages**: Use `--include-partial-messages` with stream-json for real-time UI updates
84+
- **Model flexibility**: Supports Qwen3-Coder models plus OpenAI-compatible models via API
85+
7386
## CLI Usage
7487

7588
### start-agent
@@ -82,7 +95,7 @@ start-agent --tool claude --working-directory "/tmp/dir" --prompt "Solve the iss
8295

8396
#### Options
8497

85-
- `--tool <name>` - CLI tool to use (e.g., 'claude', 'codex', 'opencode', 'agent') [required]
98+
- `--tool <name>` - CLI tool to use (e.g., 'claude', 'codex', 'opencode', 'qwen', 'agent') [required]
8699
- `--working-directory <path>` - Working directory for the agent [required]
87100
- `--prompt <text>` - Prompt for the agent
88101
- `--system-prompt <text>` - System prompt for the agent
@@ -118,6 +131,11 @@ start-agent --tool codex --working-directory "/tmp/dir" --prompt "Fix the bug" -
118131
start-agent --tool agent --working-directory "/tmp/dir" --prompt "Analyze code" --model grok
119132
```
120133

134+
**Using Qwen Code**
135+
```bash
136+
start-agent --tool qwen --working-directory "/tmp/dir" --prompt "Review this code" --model qwen3-coder
137+
```
138+
121139
**With model fallback (Claude)**
122140
```bash
123141
start-agent --tool claude --working-directory "/tmp/dir" \
@@ -233,6 +251,14 @@ const linkAgent = agent({
233251
prompt: 'Implement feature',
234252
model: 'grok',
235253
});
254+
255+
// Using Qwen Code
256+
const qwenAgent = agent({
257+
tool: 'qwen',
258+
workingDirectory: '/tmp/project',
259+
prompt: 'Review this code',
260+
model: 'qwen3-coder',
261+
});
236262
```
237263

238264
### Streaming JSON Messages
@@ -347,7 +373,7 @@ await myAgent.start({ dryRun: true });
347373
import { getTool, listTools, isToolSupported } from 'agent-commander';
348374

349375
// List all available tools
350-
console.log(listTools()); // ['claude', 'codex', 'opencode', 'agent']
376+
console.log(listTools()); // ['claude', 'codex', 'opencode', 'agent', 'qwen']
351377

352378
// Check if a tool is supported
353379
console.log(isToolSupported({ toolName: 'claude' })); // true
@@ -368,7 +394,7 @@ console.log(fullId); // 'claude-opus-4-5-20251101'
368394
Creates an agent controller.
369395

370396
**Parameters:**
371-
- `options.tool` (string, required) - CLI tool to use ('claude', 'codex', 'opencode', 'agent')
397+
- `options.tool` (string, required) - CLI tool to use ('claude', 'codex', 'opencode', 'qwen', 'agent')
372398
- `options.workingDirectory` (string, required) - Working directory
373399
- `options.prompt` (string, optional) - Prompt for the agent
374400
- `options.systemPrompt` (string, optional) - System prompt
@@ -535,6 +561,7 @@ agent-commander/
535561
│ │ ├── claude.mjs # Claude Code CLI config
536562
│ │ ├── codex.mjs # Codex CLI config
537563
│ │ ├── opencode.mjs # OpenCode CLI config
564+
│ │ ├── qwen.mjs # Qwen Code CLI config
538565
│ │ └── agent.mjs # @link-assistant/agent config
539566
│ ├── streaming/ # JSON streaming utilities
540567
│ │ ├── index.mjs # Stream exports
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
'agent-commander': minor
3+
---
4+
5+
Add Qwen Code CLI support
6+
7+
- Added new `qwen` tool configuration for Qwen Code CLI (Alibaba's AI coding agent)
8+
- Supports stream-json output format for real-time NDJSON streaming
9+
- Supports auto-approval mode with `--yolo` flag (enabled by default)
10+
- Supports session management with `--resume` and `--continue` options
11+
- Supports context options like `--all-files` and `--include-directories`
12+
- Supports `--include-partial-messages` for real-time UI updates
13+
- Added model aliases: `qwen3-coder`, `coder`, `gpt-4o`
14+
- Added comprehensive tests for the new Qwen tool

0 commit comments

Comments
 (0)