Skip to content
Merged
231 changes: 231 additions & 0 deletions .github/workflows/e2e-qwen.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
name: E2E Tests - Qwen Code CLI

on:
workflow_dispatch:
inputs:
test_prompt:
description: 'Test prompt to send to Qwen Code'
required: false
default: 'Hello, respond with a single word: working'
type: string
working_directory:
description: 'Working directory for the test'
required: false
default: '/tmp/qwen-e2e-test'
type: string
model:
description: 'Model to use for testing'
required: false
default: 'qwen3-coder'
type: choice
options:
- qwen3-coder
- coder
- gpt-4o

concurrency:
group: e2e-qwen-${{ github.ref }}
cancel-in-progress: true

jobs:
e2e-qwen-test:
name: Qwen Code E2E Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20.x'

- name: Install dependencies
working-directory: js
run: npm install

- name: Install Qwen Code CLI
run: |
npm install -g @qwen-code/qwen-code@latest
echo "Qwen Code CLI version:"
qwen --version || echo "Version command not available"

- name: Create test working directory
run: |
mkdir -p ${{ github.event.inputs.working_directory || '/tmp/qwen-e2e-test' }}
echo "# Test Project" > "${{ github.event.inputs.working_directory || '/tmp/qwen-e2e-test' }}/README.md"
echo "This is a test project for Qwen Code E2E testing." >> "${{ github.event.inputs.working_directory || '/tmp/qwen-e2e-test' }}/README.md"

- name: Test Qwen Code CLI directly
id: direct_test
continue-on-error: true
run: |
echo "Testing Qwen Code CLI directly..."
cd "${{ github.event.inputs.working_directory || '/tmp/qwen-e2e-test' }}"

# Test with headless mode (-p flag)
# Note: This requires authentication. In CI, we test that the CLI is installed
# and responds correctly to basic commands.

# Test help command
echo "=== Testing help command ==="
qwen --help || echo "Help command failed"

# Test version
echo "=== Testing version ==="
qwen --version || echo "Version command not available"

echo "direct_test_completed=true" >> $GITHUB_OUTPUT

- name: Test Qwen tool configuration
run: |
echo "Testing Qwen tool configuration..."
cd js

# Create a test script to verify the tool configuration
cat > test-qwen-config.mjs << 'EOF'
import { getTool, listTools, isToolSupported } from './src/tools/index.mjs';

console.log('=== Qwen Tool Configuration Test ===\n');

// Test 1: List tools includes qwen
const tools = listTools();
console.log('Available tools:', tools);
if (!tools.includes('qwen')) {
console.error('FAIL: qwen not in tool list');
process.exit(1);
}
console.log('PASS: qwen is in tool list\n');

// Test 2: isToolSupported returns true for qwen
if (!isToolSupported({ toolName: 'qwen' })) {
console.error('FAIL: qwen not supported');
process.exit(1);
}
console.log('PASS: qwen is supported\n');

// Test 3: getTool returns qwen configuration
const qwenTool = getTool({ toolName: 'qwen' });
console.log('Qwen tool config:', {
name: qwenTool.name,
displayName: qwenTool.displayName,
executable: qwenTool.executable,
defaultModel: qwenTool.defaultModel,
supportsJsonOutput: qwenTool.supportsJsonOutput,
supportsYolo: qwenTool.supportsYolo,
});

// Test 4: Model mapping works
console.log('\nModel mapping tests:');
const testModels = ['qwen3-coder', 'coder', 'gpt-4o', 'custom-model'];
for (const model of testModels) {
const mapped = qwenTool.mapModelToId({ model });
console.log(` ${model} -> ${mapped}`);
}

// Test 5: buildArgs produces correct arguments
console.log('\nbuildArgs test:');
const args = qwenTool.buildArgs({
prompt: 'Test prompt',
model: 'qwen3-coder',
yolo: true,
streamJson: true,
});
console.log(' Args:', args);

if (!args.includes('-p') || !args.includes('Test prompt')) {
console.error('FAIL: prompt argument missing');
process.exit(1);
}
if (!args.includes('--yolo')) {
console.error('FAIL: --yolo flag missing');
process.exit(1);
}
if (!args.includes('--output-format') || !args.includes('stream-json')) {
console.error('FAIL: stream-json output format missing');
process.exit(1);
}
console.log('PASS: buildArgs produces correct arguments\n');

// Test 6: buildCommand produces correct command
console.log('buildCommand test:');
const cmd = qwenTool.buildCommand({
workingDirectory: '/tmp/test',
prompt: 'Review code',
model: 'coder',
});
console.log(' Command:', cmd);

if (!cmd.includes('qwen')) {
console.error('FAIL: command does not include qwen');
process.exit(1);
}
console.log('PASS: buildCommand produces correct command\n');

// Test 7: parseOutput handles NDJSON
console.log('parseOutput test:');
const testOutput = '{"type":"message","content":"Hello"}\n{"type":"done"}';
const messages = qwenTool.parseOutput({ output: testOutput });
console.log(' Parsed messages:', messages);

if (messages.length !== 2) {
console.error('FAIL: expected 2 messages, got', messages.length);
process.exit(1);
}
console.log('PASS: parseOutput handles NDJSON correctly\n');

// Test 8: extractSessionId works
console.log('extractSessionId test:');
const sessionOutput = '{"session_id":"test-123"}\n{"type":"done"}';
const sessionId = qwenTool.extractSessionId({ output: sessionOutput });
console.log(' Session ID:', sessionId);

if (sessionId !== 'test-123') {
console.error('FAIL: expected test-123, got', sessionId);
process.exit(1);
}
console.log('PASS: extractSessionId works correctly\n');

// Test 9: detectErrors finds errors
console.log('detectErrors test:');
const errorOutput = '{"type":"error","message":"Test error"}';
const errorResult = qwenTool.detectErrors({ output: errorOutput });
console.log(' Error result:', errorResult);

if (!errorResult.hasError) {
console.error('FAIL: expected error to be detected');
process.exit(1);
}
console.log('PASS: detectErrors works correctly\n');

console.log('=== All Qwen Tool Configuration Tests Passed ===');
EOF

node test-qwen-config.mjs
rm test-qwen-config.mjs

- name: Test agent-commander with Qwen (dry-run)
run: |
echo "Testing agent-commander with Qwen tool (dry-run mode)..."
cd js

# Test the CLI in dry-run mode
node bin/start-agent.mjs \
--tool qwen \
--working-directory "${{ github.event.inputs.working_directory || '/tmp/qwen-e2e-test' }}" \
--prompt "${{ github.event.inputs.test_prompt || 'Hello, respond with a single word: working' }}" \
--model "${{ github.event.inputs.model || 'qwen3-coder' }}" \
--dry-run

- name: E2E Test Summary
run: |
echo "=== E2E Test Summary ==="
echo ""
echo "✅ Qwen Code CLI installed successfully"
echo "✅ Qwen tool configuration tests passed"
echo "✅ agent-commander dry-run with Qwen completed"
echo ""
echo "Note: Full E2E tests with actual API calls require authentication."
echo "To run authenticated tests locally:"
echo " 1. Install Qwen Code: npm install -g @qwen-code/qwen-code@latest"
echo " 2. Authenticate: qwen then /auth"
echo " 3. Run: start-agent --tool qwen --working-directory ./project --prompt 'Your prompt'"
35 changes: 31 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# agent-commander

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

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.

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

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

### Qwen-specific Features

The [Qwen Code CLI](https://github.com/QwenLM/qwen-code) supports additional features:

- **Stream JSON format**: Uses `--output-format stream-json` for real-time NDJSON streaming
- **Auto-approval mode**: Use `--yolo` flag for automatic action approval (enabled by default)
- **Session management**: Support for `--resume <sessionId>` and `--continue` for most recent session
- **Context options**: Use `--all-files` to include all files, `--include-directories` for specific dirs
- **Partial messages**: Use `--include-partial-messages` with stream-json for real-time UI updates
- **Model flexibility**: Supports Qwen3-Coder models plus OpenAI-compatible models via API

## CLI Usage

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

#### Options

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

**Using Qwen Code**
```bash
start-agent --tool qwen --working-directory "/tmp/dir" --prompt "Review this code" --model qwen3-coder
```

**With model fallback (Claude)**
```bash
start-agent --tool claude --working-directory "/tmp/dir" \
Expand Down Expand Up @@ -233,6 +251,14 @@ const linkAgent = agent({
prompt: 'Implement feature',
model: 'grok',
});

// Using Qwen Code
const qwenAgent = agent({
tool: 'qwen',
workingDirectory: '/tmp/project',
prompt: 'Review this code',
model: 'qwen3-coder',
});
```

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

// List all available tools
console.log(listTools()); // ['claude', 'codex', 'opencode', 'agent']
console.log(listTools()); // ['claude', 'codex', 'opencode', 'agent', 'qwen']

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

**Parameters:**
- `options.tool` (string, required) - CLI tool to use ('claude', 'codex', 'opencode', 'agent')
- `options.tool` (string, required) - CLI tool to use ('claude', 'codex', 'opencode', 'qwen', 'agent')
- `options.workingDirectory` (string, required) - Working directory
- `options.prompt` (string, optional) - Prompt for the agent
- `options.systemPrompt` (string, optional) - System prompt
Expand Down Expand Up @@ -535,6 +561,7 @@ agent-commander/
│ │ ├── claude.mjs # Claude Code CLI config
│ │ ├── codex.mjs # Codex CLI config
│ │ ├── opencode.mjs # OpenCode CLI config
│ │ ├── qwen.mjs # Qwen Code CLI config
│ │ └── agent.mjs # @link-assistant/agent config
│ ├── streaming/ # JSON streaming utilities
│ │ ├── index.mjs # Stream exports
Expand Down
14 changes: 14 additions & 0 deletions js/.changeset/add-qwen-coder-support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
'agent-commander': minor
---

Add Qwen Code CLI support

- Added new `qwen` tool configuration for Qwen Code CLI (Alibaba's AI coding agent)
- Supports stream-json output format for real-time NDJSON streaming
- Supports auto-approval mode with `--yolo` flag (enabled by default)
- Supports session management with `--resume` and `--continue` options
- Supports context options like `--all-files` and `--include-directories`
- Supports `--include-partial-messages` for real-time UI updates
- Added model aliases: `qwen3-coder`, `coder`, `gpt-4o`
- Added comprehensive tests for the new Qwen tool
Loading
Loading