Skip to content

Commit 8423a42

Browse files
committed
Add CI workflows, pre-commit tests, and setup script
- pre-commit.sh: Now runs full test suite (Swift build + server tests) - .github/workflows/ci.yml: Basic validation on Linux (bash syntax, file checks) - .github/workflows/afm-tests.yml: Full AFM tests (disabled until macOS 26+ runners) - setup-localcode.sh: One-command installation script
1 parent e2ba50a commit 8423a42

4 files changed

Lines changed: 245 additions & 1 deletion

File tree

.github/workflows/afm-tests.yml

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
name: AFM Tests
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
afm-tests:
11+
# Only run on macOS since AFM requires Apple Silicon + macOS 26+
12+
runs-on: macos-latest
13+
if: false # Disable until macOS 26+ runners available
14+
15+
steps:
16+
- uses: actions/checkout@v4
17+
18+
- name: Check macOS version
19+
run: sw_vers
20+
21+
- name: Check Apple Silicon
22+
run: sysctl -n machdep.cpu.brand_string
23+
24+
- name: Install Bun
25+
run: brew install bun
26+
27+
- name: Build Swift AFM helper
28+
run: |
29+
cd LocalCode/Sources/afmhelper
30+
swiftc -o afmhelper main.swift -framework FoundationModels -target arm64-apple-macosx26.0
31+
32+
- name: Start AFM server
33+
run: |
34+
./start-afm-server.sh &
35+
sleep 3
36+
37+
- name: Test /v1/models
38+
run: curl -s http://localhost:8080/v1/models | grep -q "afm"
39+
40+
- name: Test non-streaming tool call
41+
run: |
42+
curl -s -X POST http://localhost:8080/v1/chat/completions \
43+
-H "Content-Type: application/json" \
44+
-d '{"model":"afm","messages":[{"role":"user","content":"test"}],"stream":false}' \
45+
| grep -q "tool_calls"
46+
47+
- name: Test streaming tool call
48+
run: |
49+
curl -s -X POST http://localhost:8080/v1/chat/completions \
50+
-H "Content-Type: application/json" \
51+
-d '{"model":"afm","messages":[{"role":"user","content":"test"}],"stream":true}' \
52+
| grep -q "chat.completion.chunk"

.github/workflows/ci.yml

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
validate:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
15+
- name: Validate bash scripts
16+
run: |
17+
for script in start-afm-server.sh setup-localcode.sh pre-commit.sh; do
18+
if [ -f "$script" ]; then
19+
bash -n "$script" && echo "✓ $script is valid" || exit 1
20+
fi
21+
done
22+
23+
- name: Check Swift files exist
24+
run: |
25+
if [ -f "LocalCode/Sources/afmhelper/main.swift" ]; then
26+
echo "✓ Swift source exists"
27+
else
28+
echo "✗ Swift source not found"
29+
exit 1
30+
fi
31+
32+
docs:
33+
runs-on: ubuntu-latest
34+
steps:
35+
- uses: actions/checkout@v4
36+
37+
- name: Check README exists
38+
run: |
39+
if [ -f "README.md" ]; then
40+
echo "✓ README.md exists"
41+
wc -l README.md
42+
else
43+
echo "✗ README.md not found"
44+
exit 1
45+
fi
46+
47+
- name: Check QA-TESTING.md exists
48+
run: |
49+
if [ -f "QA-TESTING.md" ]; then
50+
echo "✓ QA-TESTING.md exists"
51+
else
52+
echo "✗ QA-TESTING.md not found"
53+
exit 1
54+
fi

pre-commit.sh

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,66 @@ if ! swiftc -o afmhelper main.swift -framework FoundationModels -target arm64-ap
1414
exit 1
1515
fi
1616
echo "Swift build: OK"
17-
cd ../..
17+
cd "$SCRIPT_DIR"
18+
19+
# Kill any existing server
20+
pkill -f "bun.*8080" 2>/dev/null || true
21+
sleep 1
22+
23+
# Start AFM server for testing in background
24+
./start-afm-server.sh &
25+
AFM_PID=$!
26+
sleep 3
27+
28+
# Cleanup function
29+
cleanup() {
30+
kill $AFM_PID 2>/dev/null || true
31+
pkill -f "bun.*8080" 2>/dev/null || true
32+
}
33+
trap cleanup EXIT
34+
35+
# Test server health
36+
echo ""
37+
echo "Testing /v1/models endpoint..."
38+
MODELS_RESPONSE=$(curl -s http://localhost:8080/v1/models 2>/dev/null)
39+
if ! echo "$MODELS_RESPONSE" | grep -q "afm"; then
40+
echo "FAILED: /v1/models endpoint not returning AFM model"
41+
echo "Response: $MODELS_RESPONSE"
42+
exit 1
43+
fi
44+
echo "Models endpoint: OK"
45+
46+
# Test non-streaming tool call
47+
echo "Testing non-streaming tool call..."
48+
TOOL_CALL_RESPONSE=$(curl -s -X POST http://localhost:8080/v1/chat/completions \
49+
-H "Content-Type: application/json" \
50+
-d '{"model":"afm","messages":[{"role":"user","content":"test"}],"stream":false}' 2>/dev/null)
51+
52+
if ! echo "$TOOL_CALL_RESPONSE" | grep -q "tool_calls"; then
53+
echo "FAILED: Non-streaming response missing tool_calls"
54+
echo "Response: $TOOL_CALL_RESPONSE"
55+
exit 1
56+
fi
57+
58+
if ! echo "$TOOL_CALL_RESPONSE" | grep -q "description"; then
59+
echo "FAILED: Tool call missing description field"
60+
echo "Response: $TOOL_CALL_RESPONSE"
61+
exit 1
62+
fi
63+
echo "Non-streaming tool call: OK"
64+
65+
# Test streaming tool call
66+
echo "Testing streaming tool call..."
67+
STREAM_RESPONSE=$(curl -s -X POST http://localhost:8080/v1/chat/completions \
68+
-H "Content-Type: application/json" \
69+
-d '{"model":"afm","messages":[{"role":"user","content":"test"}],"stream":true}' 2>/dev/null)
70+
71+
if ! echo "$STREAM_RESPONSE" | grep -q "chat.completion.chunk"; then
72+
echo "FAILED: Streaming response missing chunk format"
73+
echo "Response: $STREAM_RESPONSE"
74+
exit 1
75+
fi
76+
echo "Streaming tool call: OK"
1877

1978
echo ""
2079
echo "All checks passed!"

setup-localcode.sh

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
#!/bin/bash
2+
set -e
3+
4+
INSTALL_DIR="${HOME}/.local/bin"
5+
CONFIG_FILE="${HOME}/.config/opencode/opencode.json"
6+
7+
echo "LocalCode AFM Setup"
8+
echo "==================="
9+
echo ""
10+
11+
if [ ! -d "$INSTALL_DIR" ]; then
12+
mkdir -p "$INSTALL_DIR"
13+
fi
14+
15+
echo "Installing start-afm-server.sh to $INSTALL_DIR..."
16+
cp "$(dirname "$0")/start-afm-server.sh" "$INSTALL_DIR/localcode-afm"
17+
chmod +x "$INSTALL_DIR/localcode-afm"
18+
19+
echo "Configuring OpenCode provider..."
20+
21+
if [ -f "$CONFIG_FILE" ]; then
22+
if grep -q "localcode-afm" "$CONFIG_FILE"; then
23+
echo "Provider already configured in $CONFIG_FILE"
24+
else
25+
echo "Adding provider to existing config..."
26+
node -e "
27+
const fs = require('fs');
28+
const config = JSON.parse(fs.readFileSync('$CONFIG_FILE', 'utf8'));
29+
config.provider = config.provider || {};
30+
config.provider['localcode-afm'] = {
31+
npm: '@ai-sdk/openai-compatible',
32+
name: 'LocalCode AFM',
33+
options: {
34+
baseURL: 'http://localhost:8080/v1',
35+
stream: false
36+
},
37+
models: {
38+
afm: { name: 'Apple Foundation Models' }
39+
}
40+
};
41+
fs.writeFileSync('$CONFIG_FILE', JSON.stringify(config, null, 2));
42+
"
43+
fi
44+
else
45+
echo "Creating new config with provider..."
46+
mkdir -p "$(dirname "$CONFIG_FILE")"
47+
cat > "$CONFIG_FILE" << 'EOF'
48+
{
49+
"$schema": "https://opencode.ai/config.json",
50+
"mcp": {},
51+
"provider": {
52+
"localcode-afm": {
53+
"npm": "@ai-sdk/openai-compatible",
54+
"name": "LocalCode AFM",
55+
"options": {
56+
"baseURL": "http://localhost:8080/v1",
57+
"stream": false
58+
},
59+
"models": {
60+
"afm": {
61+
"name": "Apple Foundation Models"
62+
}
63+
}
64+
}
65+
}
66+
}
67+
EOF
68+
fi
69+
70+
echo ""
71+
echo "Setup complete!"
72+
echo ""
73+
echo "To start LocalCode AFM:"
74+
echo " $INSTALL_DIR/localcode-afm &"
75+
echo ""
76+
echo "Then run opencode and select 'LocalCode AFM' provider"
77+
echo ""
78+
echo "Quick test:"
79+
echo " curl http://localhost:8080/v1/models"

0 commit comments

Comments
 (0)