19 KiB
Developer Guide
Comprehensive onboarding guide for zai-proxy developers.
Table of Contents
- Prerequisites
- Project Structure
- Local Development Setup
- Running Tests
- Debugging
- Common Development Workflows
- CI/CD Process
- Code Style and Conventions
- Troubleshooting
Prerequisites
Required Tools
- Go 1.23+ - Proxy and dashboard backend
- Node.js 18+ and npm - Dashboard frontend
- Docker - Container builds and testing
- Git - Version control
Optional Tools
- Make - Convenience targets (if available)
- kubectl - Kubernetes access for deployment testing
- Visual Studio Code or GoLand - Recommended IDEs
Verify Installation
# Check Go version
go version
# Expected: go version go1.23.x or later
# Check Node.js version
node --version
# Expected: v18.x.x or later
# Check npm version
npm --version
# Check Docker
docker --version
Project Structure
zai-proxy/
├── proxy/ # Go reverse proxy
│ ├── cmd/ # Command-line entry points
│ ├── evaluation/ # Token counting evaluation tools
│ ├── scripts/ # Utility scripts
│ ├── tests/ # Integration test fixtures
│ ├── main.go # Proxy server implementation
│ ├── tokenizer.go # Token counting logic
│ ├── translator.go # Provider format translation
│ ├── metrics.go # Prometheus metrics
│ ├── bodyparser.go # Request/response parsing
│ ├── *_test.go # Test files
│ ├── go.mod # Go dependencies
│ ├── Dockerfile # Container image
│ └── README.md # Proxy-specific docs
│
├── dashboard/ # Monitoring dashboard
│ ├── api/ # HTTP handlers and SSE
│ ├── collector/ # Prometheus scraper
│ ├── storage/ # SQLite database layer
│ ├── logger/ # Structured logging
│ ├── model/ # Data structures
│ ├── frontend/ # React UI
│ │ ├── src/ # TypeScript source
│ │ │ ├── components/ # React components
│ │ │ ├── hooks/ # Custom React hooks
│ │ │ ├── utils/ # Utilities
│ │ │ └── main.tsx # Entry point
│ │ ├── package.json # Node dependencies
│ │ └── vite.config.ts # Vite build config
│ ├── main.go # Dashboard server
│ ├── go.mod # Go dependencies
│ ├── Dockerfile # Multi-stage build
│ └── README.md # Dashboard-specific docs
│
├── docs/ # Documentation
│ ├── plan/ # Architecture and roadmap
│ ├── notes/ # Operational guides
│ └── research/ # External research
│
├── CONTRIBUTING.md # Contribution guidelines
├── DEVELOPMENT.md # This file
└── README.md # Project overview
Local Development Setup
Quick Start (Full Stack)
# Clone repository
git clone https://git.ardenone.com/jedarden/zai-proxy.git
cd zai-proxy
# Setup proxy
cd proxy
go mod download
export ZAI_API_KEY="your-api-key-here"
# Setup dashboard (new terminal)
cd ../dashboard/frontend
npm install
# Run proxy (terminal 1)
cd ../proxy
go run .
# Run dashboard backend (terminal 2)
cd ../dashboard
export SCRAPE_TARGETS="http://localhost:8080/metrics"
go run .
# Run dashboard frontend (terminal 3)
cd frontend
npm run dev
Proxy Development
The proxy is a Go HTTP reverse proxy that fronts the Z.AI API.
Setup
cd /home/coding/zai-proxy/proxy
# Download dependencies
go mod download
# Set required environment variables
export ZAI_API_KEY="your-zai-api-key"
# Optional: Configure tokenizer model
export TOKENIZER_MODEL="glm-4" # Default
# Optional: Adjust rate limits
export RATE_LIMIT_INITIAL="10.0"
export RATE_LIMIT_MAX="50.0"
export MAX_WORKERS="50"
Run Locally
# Simple run
go run .
# Run with specific listen address
export LISTEN_ADDR=":8080"
go run .
# Run with debug logging
export LOG_LEVEL="debug"
go run .
The proxy listens on :8080 by default:
- Proxy API:
http://localhost:8080/v1/messages - Metrics:
http://localhost:8080/metrics
Test Proxy
# Make a test request
curl -X POST http://localhost:8080/v1/messages \
-H "Content-Type: application/json" \
-H "x-api-key: $ZAI_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-d '{
"model": "claude-3-sonnet-4-20250514",
"max_tokens": 100,
"messages": [{"role": "user", "content": "Hello!"}]
}'
# Check metrics
curl http://localhost:8080/metrics | grep zai_proxy
Build Binary
# Build for current platform
go build -o zai-proxy .
# Build for Linux
GOOS=linux GOARCH=amd64 go build -o zai-proxy-linux-amd64 .
# Build with version info
VERSION=$(cat VERSION) go build -ldflags="-X main.Version=$VERSION" -o zai-proxy .
Dashboard Development
The dashboard has a Go backend and React + TypeScript frontend.
Backend Setup
cd /home/coding/zai-proxy/dashboard
# Download dependencies
go mod download
# Set environment variables (optional, defaults shown)
export LISTEN_ADDR=":8081" # Dashboard API port
export SCRAPE_TARGETS="http://localhost:8080/metrics"
export SCRAPE_INTERVAL="5s"
export DB_PATH="/tmp/dashboard.db"
# Run backend
go run .
The dashboard backend listens on :8081 by default:
- Frontend:
http://localhost:8081/ - API:
http://localhost:8081/api/ - SSE:
http://localhost:8081/api/events - Health:
http://localhost:8081/healthz
Frontend Development
cd /home/coding/zai-proxy/dashboard/frontend
# Install dependencies
npm install
# Run dev server (with hot reload)
npm run dev
# Build for production
npm run build
# Preview production build
npm run preview
# Run linter
npm run lint
# Run tests
npm run test
npm run test:watch # Watch mode
The frontend dev server runs on :5173 and proxies API requests to :8081.
Frontend Project Structure
frontend/src/
├── main.tsx # React entry point
├── App.tsx # Root component
├── components/ # React components
│ ├── MetricCard.tsx # Metric display cards
│ ├── StatusHeader.tsx # Status bar
│ ├── TokenPanel.tsx # Token usage panel
│ ├── RequestPanel.tsx # Request metrics
│ ├── RateLimitPanel.tsx # Rate limiting panel
│ └── ...
├── hooks/ # Custom React hooks
│ ├── useMetrics.ts # Metrics data fetching
│ ├── useSSE.ts # SSE connection
│ └── ...
└── utils/ # Utilities
├── formatters.ts # Number/time formatting
└── constants.ts # Constants
Running Tests
Proxy Tests
cd /home/coding/zai-proxy/proxy
# Run all tests
go test -v ./...
# Run specific package tests
go test -v ./tokenizer
go test -v ./translator
# Run specific test
go test -v -run TestTokenCountingBasicRequest
# Run with coverage
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out
# Run benchmarks
go test -bench=. -benchmem
# Run regression tests (token counting accuracy)
go test -v -run Regression
Dashboard Tests
Backend Tests
cd /home/coding/zai-proxy/dashboard
# Run all backend tests
go test -v ./...
# Run specific package tests
go test -v ./collector
go test -v ./storage
go test -v ./api
# Run with coverage
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out
Frontend Tests
cd /home/coding/zai-proxy/dashboard/frontend
# Run tests once
npm run test
# Run in watch mode
npm run test:watch
# Run with coverage
npm run test -- --coverage
# Run specific test file
npm run test -- src/hooks/useMetrics.test.ts
Running All Tests
# From project root
cd /home/coding/zai-proxy
# Run all Go tests
go test -v ./proxy/... ./dashboard/...
# Run all frontend tests
cd dashboard/frontend && npm run test
Debugging
Proxy Debugging
Enable Debug Logging
export LOG_LEVEL="debug"
go run .
Use Delve (Go Debugger)
# Install delve
go install github.com/go-delve/delve/cmd/dlv@latest
# Debug with delve
dlv debug
# Common delve commands:
# (dlv) break main.go:125 # Set breakpoint
# (dlv) continue # Continue execution
# (dlv) next # Next line
# (dlv) print variableName # Print variable
# (dlv) goroutines # List goroutines
# (dlv) goroutine 5 # Switch to goroutine 5
VS Code Debug Configuration
Create .vscode/launch.json:
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Proxy",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}/proxy",
"env": {
"ZAI_API_KEY": "your-api-key",
"LOG_LEVEL": "debug"
}
},
{
"name": "Debug Dashboard Backend",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}/dashboard",
"env": {
"SCRAPE_TARGETS": "http://localhost:8080/metrics",
"LOG_LEVEL": "debug"
}
}
]
}
Dashboard Debugging
Backend Debugging
Use the same delve approach as the proxy:
cd /home/coding/zai-proxy/dashboard
dlv debug
Frontend Debugging
The Vite dev server includes hot module replacement (HMR). Use browser DevTools:
cd /home/coding/zai-proxy/dashboard/frontend
npm run dev
Chrome DevTools shortcuts:
F12orCtrl+Shift+I- Open DevToolsCtrl+Shift+J- Jump to console- React DevTools extension recommended
SSE Connection Debugging
# Test SSE endpoint directly
curl -N http://localhost:8081/api/events
# With verbose output
curl -v -N http://localhost:8081/api/events
Common Debugging Scenarios
Token Counting Issues
# Check if tokenizer initialized correctly
kubectl logs deployment/zai-proxy -n mcp | grep -i "token counting"
# Expected: "Token counting enabled (tiktoken cl100k_base encoding, model: glm-4)"
# Check if fallback is active
kubectl logs deployment/zai-proxy -n mcp | grep -i "fallback"
Dashboard Not Showing Data
# Check if collector is scraping
kubectl logs deployment/zai-proxy-dashboard -n mcp | grep "scrape"
# Check proxy metrics endpoint
curl http://zai-proxy.mcp.svc.cluster.local:8080/metrics
# Test direct connection from dashboard pod
kubectl exec -n mcp deployment/zai-proxy-dashboard -- \
wget -O- http://zai-proxy.mcp.svc.cluster.local:8080/metrics
High Memory/CPU Usage
# Check container metrics
kubectl top pod -n mcp -l app=zai-proxy
# Check Go runtime metrics
curl http://localhost:8080/metrics | grep go_
Common Development Workflows
Adding a New Feature
-
Create feature branch
git checkout -b feat/add-new-feature -
Write tests first
# Create test file touch proxy/myfeature_test.go -
Implement feature
# Edit source files vim proxy/myfeature.go -
Run tests
go test -v ./proxy npm run test # If frontend changes -
Format code
gofmt -w ./proxy ./dashboard npm run lint # Frontend -
Commit changes
git add . git commit -m "feat(proxy): add new feature" -
Push and create PR
git push origin feat/add-new-feature
Adding a New Metric
-
Define metric in
metrics.govar myNewMetric = prometheus.NewCounterVec( prometheus.CounterOpts{ Name: "zai_proxy_my_new_metric", Help: "Description of what this metric tracks", }, []string{"label1", "label2"}, ) -
Register in
MetricsRegistryvar MetricsRegistry = []*prometheus.CounterVec{ // ... existing metrics myNewMetric, } -
Use in code
myNewMetric.WithLabelValues("value1", "value2").Inc() -
Add test
func TestMyNewMetric(t *testing.T) { // Test that metric is registered // Test that metric increments correctly } -
Test locally
curl http://localhost:8080/metrics | grep my_new_metric
Adding a New Frontend Component
-
Create component file
touch dashboard/frontend/src/components/MyComponent.tsx -
Implement component
interface MyComponentProps { title: string; value: number; } export function MyComponent({ title, value }: MyComponentProps) { return ( <div className="p-4 bg-white rounded shadow"> <h3 className="text-lg font-semibold">{title}</h3> <p className="text-2xl">{value}</p> </div> ); } -
Add tests
// MyComponent.test.tsx import { describe, it, expect } from 'vitest'; import { render, screen } from '@testing-library/react'; import { MyComponent } from './MyComponent'; describe('MyComponent', () => { it('renders title and value', () => { render(<MyComponent title="Test" value={42} />); expect(screen.getByText('Test')).toBeTruthy(); expect(screen.getByText('42')).toBeTruthy(); }); }); -
Use in App
import { MyComponent } from './components/MyComponent'; // In your component <MyComponent title="My Metric" value={123} /> -
Test
npm run test npm run lint
Updating Dependencies
Go Dependencies
cd /home/coding/zai-proxy/proxy # or dashboard/
# List dependencies
go list -m all
# Update all dependencies
go get -u ./...
go mod tidy
# Update specific dependency
go get -u github.com/prometheus/client_golang@latest
go mod tidy
# Verify tests still pass
go test -v ./...
Node Dependencies
cd /home/coding/zai-proxy/dashboard/frontend
# Check for updates
npm outdated
# Update all dependencies
npm update
# Update specific dependency
npm install package@latest
# Run tests to verify
npm run test
CI/CD Process
The project uses GitHub Actions for CI/CD. See .github/workflows/ for workflow definitions.
Workflow Types
- On Push - Runs tests and builds on every push
- On PR - Runs full test suite including regression tests
- Manual - Docker image builds for specific branches
Build Process
Docker Image Build
The project uses GitHub Actions for Docker builds in devpod environments due to overlayfs limitations.
# .github/workflows/build.yml
name: Build Docker Image
on:
push:
branches: [main]
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build and push
run: |
docker build -t ghcr.io/ardenone/zai-proxy:${{ github.sha }} .
docker push ghcr.io/ardenone/zai-proxy:${{ github.sha }}
Deployment
Deployments are managed via GitOps through ArgoCD:
- Update manifests in
jedarden/declarative-configrepository - Commit and push changes
- ArgoCD automatically syncs changes to clusters
See docs/notes/DEPLOYMENT.md for detailed deployment procedures.
Running CI Locally
# Run Go tests with race detector
go test -race -v ./...
# Run with coverage (required for CI)
go test -coverprofile=coverage.out ./...
go tool cover -func=coverage.out
# Check coverage threshold (typically 80%)
go test -coverprofile=coverage.out ./...
go tool cover -func=coverage.out | grep total
Code Style and Conventions
Go Code Style
- Follow Effective Go
- Run
gofmt -w .before committing - Use meaningful variable names
- Add doc comments for exported functions
- Handle errors explicitly
- Keep functions focused and small
// Good
func countTokens(text string) (int, error) {
if text == "" {
return 0, fmt.Errorf("empty text")
}
// Implementation
return count, nil
}
// Bad
func cnt(t string) int {
// Too terse
}
TypeScript/React Style
- Use functional components with hooks
- Prefer
constoverlet - Avoid
anytype - Use TypeScript interfaces for props
- Follow existing component patterns
// Good
interface Props {
title: string;
value: number;
}
export function MetricCard({ title, value }: Props) {
return <div>{title}: {value}</div>;
}
// Bad
export function MetricCard(props: any) {
return <div>{props.title}: {props.value}</div>;
}
Commit Message Format
Follow conventional commits:
<type>(<scope>): <description>
[optional body]
[optional footer]
Types:
feat- New featurefix- Bug fixdocs- Documentation changeschore- Maintenance tasksrefactor- Code refactoringtest- Adding or updating testsperf- Performance improvements
Scopes:
proxy- Go proxy backenddashboard- Dashboard (backend + frontend)frontend- React frontend specifically
Examples:
feat(proxy): add GLM-4 tokenizer support
fix(dashboard): correct token count display
docs(proxy): update environment variables reference
chore: bump VERSION to 1.1.0
Troubleshooting
Port Already in Use
# Find process using port
lsof -i :8080
# Kill process
kill -9 <PID>
# Or use different port
export LISTEN_ADDR=":8081"
Module Download Failed
# Clear Go module cache
go clean -modcache
# Re-download dependencies
go mod download
# Verify checksums
go mod verify
Frontend Build Errors
# Clear node_modules and reinstall
rm -rf node_modules package-lock.json
npm install
# Clear Vite cache
rm -rf .vite dist
npm run build
Test Failures
# Run tests with verbose output
go test -v ./...
# Run specific test with debug output
go test -v -run TestName ./...
# Check for race conditions
go test -race ./...
Docker Build Issues
If Docker builds fail in devpod environments:
# Use GitHub Actions instead
# See .github/workflows/ for build workflows
# Or use a different environment with full Docker support
Getting Help
- Documentation: Check
docs/notes/for operational guides - Issues: File in repository
- Questions: Contact
jedarden@jedarden.com - Logs:
kubectl logs -f deployment/zai-proxy -n mcp - Metrics:
http://zai-proxy.mcp.svc.cluster.local:8080/metrics
Additional Resources
- CONTRIBUTING.md - Contribution guidelines
- proxy/README.md - Proxy-specific documentation
- dashboard/README.md - Dashboard-specific documentation
- docs/notes/ - Operational guides and procedures