Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
members = [
"src/crates/events",
"src/crates/ai-adapters",
"src/crates/acp",
"src/crates/core",
"src/crates/transport",
"src/crates/api-layer",
Expand Down
169 changes: 86 additions & 83 deletions scripts/test-acp.js
Original file line number Diff line number Diff line change
@@ -1,98 +1,101 @@
// Simple test client for BitFun ACP server
// Simple test client for BitFun ACP server.
// Run with: node scripts/test-acp.js

const { spawn } = require('child_process');
const path = require('path');
import { spawn } from 'node:child_process';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

// Check if bitfun-cli exists
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const cliPath = path.join(__dirname, '..', 'target', 'debug', 'bitfun-cli');
const cliReleasePath = path.join(__dirname, '..', 'target', 'release', 'bitfun-cli');
const usePath = fs.existsSync(cliPath)
? cliPath
: fs.existsSync(cliReleasePath)
? cliReleasePath
: 'bitfun-cli';

const usePath = require('fs').existsSync(cliPath) ? cliPath :
require('fs').existsSync(cliReleasePath) ? cliReleasePath :
'bitfun-cli';
const cwd = '/tmp/test-acp-node';
fs.mkdirSync(cwd, { recursive: true });

console.log('=== BitFun ACP Server Test (Node.js) ===\n');

// Test requests
const testRequests = [
{
name: 'Initialize',
request: {
jsonrpc: '2.0',
id: 1,
method: 'initialize',
params: {
protocolVersion: 1,
clientCapabilities: {
fs: { readTextFile: true, writeTextFile: true },
terminal: true
},
clientInfo: { name: 'NodeTestClient', version: '1.0' }
}
}
},
{
name: 'Create Session',
request: {
jsonrpc: '2.0',
id: 2,
method: 'session/new',
params: {
cwd: '/tmp/test-acp-node'
}
}
},
{
name: 'List Tools',
request: {
jsonrpc: '2.0',
id: 3,
method: 'tools/list'
}
}
];
const child = spawn(usePath, ['acp'], {
stdio: ['pipe', 'pipe', 'inherit'],
});

let buffer = '';
let sessionId = null;

function send(request) {
child.stdin.write(`${JSON.stringify(request)}\n`);
}

// Run individual tests
async function runTest(test) {
console.log(`Test: ${test.name}`);
console.log('Request:', JSON.stringify(test.request, null, 2));

const child = spawn(usePath, ['acp'], {
stdio: ['pipe', 'pipe', 'inherit']
});

let output = '';

child.stdout.on('data', (data) => {
output += data.toString();
});

child.stdin.write(JSON.stringify(test.request) + '\n');
function stopChild() {
child.stdin.end();

return new Promise((resolve) => {
child.on('close', (code) => {
console.log('Response:', output);
try {
const response = JSON.parse(output);
console.log('Parsed:', JSON.stringify(response, null, 2));
} catch (e) {
console.log('Parse error:', e.message);
}
console.log('\n');
resolve();
});
});
setTimeout(() => {
if (!child.killed) {
child.kill('SIGTERM');
}
}, 500);
}

// Run all tests sequentially
async function runAllTests() {
for (const test of testRequests) {
await runTest(test);
child.stdout.on('data', (data) => {
buffer += data.toString();
const lines = buffer.split(/\n/);
buffer = lines.pop();

for (const line of lines) {
if (!line.trim()) continue;
const message = JSON.parse(line);
console.log(JSON.stringify(message, null, 2));

if (message.id === 2) {
sessionId = message.result.sessionId;
send({
jsonrpc: '2.0',
id: 3,
method: 'session/list',
params: { cwd },
});
} else if (message.id === 3) {
send({
jsonrpc: '2.0',
id: 4,
method: 'session/prompt',
params: {
sessionId,
prompt: [{ type: 'text', text: '你好' }],
},
});
} else if (message.id === 4) {
stopChild();
}
}

console.log('=== Tests Complete ===');
}
});

child.on('close', (code) => {
console.log(`\n=== Tests Complete: exit ${code} ===`);
process.exit(code);
});

send({
jsonrpc: '2.0',
id: 1,
method: 'initialize',
params: {
protocolVersion: 1,
clientCapabilities: {
fs: { readTextFile: true, writeTextFile: true },
terminal: true,
},
clientInfo: { name: 'NodeTestClient', version: '1.0' },
},
});

runAllTests().catch(console.error);
send({
jsonrpc: '2.0',
id: 2,
method: 'session/new',
params: { cwd, mcpServers: [] },
});
72 changes: 50 additions & 22 deletions scripts/test-acp.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,62 @@
echo "=== BitFun ACP Server Test ==="
echo ""

# Check if bitfun-cli is built
if ! command -v bitfun-cli &> /dev/null; then
echo "Error: bitfun-cli not found in PATH"
echo "Please build the CLI first: cargo build --package bitfun-cli"
exit 1
fi
BINARY="${BITFUN_CLI:-target/debug/bitfun-cli}"
WORKSPACE="/tmp/test-acp"
PIPE_DIR="$(mktemp -d /tmp/bitfun-acp-test-sh.XXXXXX)"
ACP_IN="$PIPE_DIR/in"
ACP_OUT="$PIPE_DIR/out"
mkdir -p "$WORKSPACE"
mkfifo "$ACP_IN" "$ACP_OUT"

echo "Test 1: Initialize"
echo "Sending: initialize request"
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":1,"clientCapabilities":{"fs":{"readTextFile":true,"writeTextFile":true},"terminal":true},"clientInfo":{"name":"TestClient","version":"1.0"}}}' | bitfun-cli acp
echo ""
cleanup() {
exec 3>&- 2>/dev/null || true
exec 4<&- 2>/dev/null || true
if [[ -n "${ACP_PID:-}" ]]; then
kill "$ACP_PID" 2>/dev/null || true
wait "$ACP_PID" 2>/dev/null || true
fi
rm -rf "$PIPE_DIR"
}
trap cleanup EXIT

echo "Test 1: Initialize"
echo "Test 2: Create Session"
echo "Sending: session/new request"
echo '{"jsonrpc":"2.0","id":2,"method":"session/new","params":{"cwd":"/tmp/test-acp"}}' | bitfun-cli acp
echo ""
echo "Test 3: List Sessions"
"$BINARY" acp <"$ACP_IN" >"$ACP_OUT" &
ACP_PID="$!"
exec 3>"$ACP_IN"
exec 4<"$ACP_OUT"

echo "Test 3: List Tools"
echo "Sending: tools/list request"
echo '{"jsonrpc":"2.0","id":3,"method":"tools/list"}' | bitfun-cli acp
echo ""
printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":1,"clientCapabilities":{"fs":{"readTextFile":true,"writeTextFile":true},"terminal":true},"clientInfo":{"name":"TestClient","version":"1.0"}}}' \
>&3

responses=0
while [[ "$responses" -lt 3 ]]; do
if ! IFS= read -r -t 15 line <&4; then
echo "Timed out waiting for ACP response" >&2
exit 1
fi

echo "$line"
if [[ "$line" == *'"id":'* ]]; then
responses=$((responses + 1))
fi

echo "Test 4: List Sessions"
echo "Sending: session/list request"
echo '{"jsonrpc":"2.0","id":4,"method":"session/list"}' | bitfun-cli acp
if [[ "$line" == *'"id":1'* ]]; then
printf '%s\n' \
"{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"session/new\",\"params\":{\"cwd\":\"$WORKSPACE\",\"mcpServers\":[]}}" \
>&3
elif [[ "$line" == *'"id":2'* ]]; then
printf '%s\n' \
"{\"jsonrpc\":\"2.0\",\"id\":3,\"method\":\"session/list\",\"params\":{\"cwd\":\"$WORKSPACE\"}}" \
>&3
fi
done
exec 3>&-
echo ""

echo "=== Tests Complete ==="
echo ""
echo "Note: This is a basic test of the protocol layer."
echo "Full agentic workflow execution is not yet implemented."
echo "Note: This is a basic test of the typed ACP protocol layer."
2 changes: 1 addition & 1 deletion src/apps/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ path = "src/main.rs"
# Internal crates
bitfun-core = { path = "../../crates/core" }
bitfun-events = { path = "../../crates/events" }
bitfun-acp = { path = "../../crates/acp" }

# CLI framework
clap = { version = "4", features = ["derive"] }
Expand Down Expand Up @@ -49,4 +50,3 @@ tracing-subscriber = { workspace = true }

[features]
default = []

Loading
Loading