Developer Guide
This guide provides everything you need to know about developing Osaurus, from building the project to contributing code.
ποΈ Architecture Overviewβ
Osaurus is built with a clean, modular architecture:
βββββββββββββββββββ βββββββββββββββββββ
β SwiftUI App ββββββΆβ Menu Bar UI β
ββββββββββ¬βββββββββ βββββββββββββββββββ
β
βΌ
βββββββββββββββββββ βββββββββββββββββββ
β ServerControllerββββββΆβ SwiftNIO HTTP β
ββββββββββ¬βββββββββ ββββββββββ¬βββββββββ
β β
βΌ βΌ
βββββββββββββββββββ βββββββββββββββββββ
β Model Manager β β API Handler β
ββββββββββ¬βββββββββ ββββββββββ¬βββββββββ
β β
βββββββββ¬ββββββββββββββββ
βΌ
βββββββββββββββββββ
β MLX Service β
βββββββββββββββββββ
π Project Structureβ
osaurus/
βββ Core/ # App lifecycle
β βββ AppDelegate.swift # macOS app delegate
β βββ osaurusApp.swift # SwiftUI app entry point
β
βββ Controllers/ # Business logic controllers
β βββ ServerController.swift # HTTP server lifecycle management
β βββ ModelManager.swift # Model discovery & downloads
β βββ HotKeyManager.swift # Global hotkey registration
β
βββ Models/ # Data models
β βββ InternalMessage.swift # Internal message types
β βββ MLXModel.swift # MLX model representation
β βββ OpenAIAPI.swift # OpenAI-compatible DTOs
β βββ ResponseWriters.swift # SSE and NDJSON writers
β βββ ServerConfiguration.swift # Server config model
β βββ ChatConfiguration.swift # Chat config model
β βββ ServerHealth.swift # Health check model
β
βββ Networking/ # Network layer
β βββ HTTPHandler.swift # HTTP request handling
β βββ Router.swift # URL routing with normalization
β βββ AsyncHTTPHandler.swift # Async request processing
β
βββ Services/ # Core services
β βββ DirectoryPickerService.swift # File system access
β βββ FoundationModelService.swift # Apple Intelligence integration
β βββ HuggingFaceService.swift # Model repository access
β βββ LoginItemService.swift # Auto-start functionality
β βββ MLXService.swift # MLX inference engine
β βββ ModelService.swift # Model management
β βββ PromptBuilder.swift # Chat template formatting
β βββ SearchService.swift # Model search
β βββ SharedConfigurationService.swift # Inter-app communication
β βββ SystemMonitorService.swift # CPU/RAM monitoring
β βββ UpdaterService.swift # App updates
β
βββ Theme/ # UI theming
β βββ Theme.swift # Colors, fonts, styling
β
βββ Views/ # SwiftUI views
β βββ Components/ # Reusable UI components
β βββ ContentView.swift # Main app view
β βββ ModelDownloadView.swift # Model browser
β βββ ChatView.swift # Chat interface
β βββ SettingsView.swift # Configuration UI
β
βββ Resources/ # Assets and resources
β βββ Assets.xcassets/ # Images and colors
β βββ Info.plist # App metadata
β
βββ CLI/ # Command-line interface
βββ main.swift # CLI entry point
π οΈ Development Setupβ
Prerequisitesβ
- macOS 15.5+ on Apple Silicon
- Xcode 16.4+ with Command Line Tools
- Swift 6.0+
- Git
Clone and Buildβ
# Clone the repository
git clone https://github.com/dinoki-ai/osaurus.git
cd osaurus
# Open in Xcode
open osaurus.xcodeproj
# Or build from command line
xcodebuild -scheme osaurus -configuration Debug
Running in Developmentβ
-
In Xcode:
- Select the
osaurusscheme - Choose your Mac as the run destination
- Press βR to build and run
- Select the
-
Debug Console:
- View logs in Xcode's console
- Set breakpoints for debugging
- Use lldb commands for inspection
-
SwiftUI Previews:
- Available for most views
- Press ββ₯P to refresh previews
π§ Key Componentsβ
ServerControllerβ
Manages the HTTP server lifecycle:
class ServerController: ObservableObject {
@Published var isRunning = false
@Published var port = 1337
@Published var isExposed = false
func start() async throws {
// Initialize SwiftNIO event loop
// Bind to configured address
// Start accepting connections
}
func stop() async {
// Gracefully shutdown server
// Clean up resources
}
}
MLXServiceβ
Handles model loading and inference:
class MLXService {
private var loadedModel: MLXModel?
private var sessionCache: [String: InferenceSession] = [:]
func loadModel(_ name: String) async throws -> MLXModel {
// Check cache first
// Load model from disk
// Initialize tokenizer
// Warm up model
}
func generate(
prompt: String,
maxTokens: Int,
temperature: Float
) -> AsyncStream<String> {
// Tokenize input
// Run inference
// Stream tokens
// Handle stop sequences
}
}
Routerβ
Handles API path normalization:
struct Router {
static func normalize(_ path: String) -> String {
// Remove common prefixes (/v1, /api, etc.)
// Standardize paths
// Return canonical route
}
static func route(_ request: HTTPRequest) -> Handler? {
let path = normalize(request.uri)
switch path {
case "/models": return ModelsHandler()
case "/chat/completions": return ChatHandler()
// ... more routes
default: return nil
}
}
}
π§ͺ Testingβ
Unit Testsβ
# Run all tests
xcodebuild test -scheme osaurus
# Run specific test
xcodebuild test -scheme osaurus -only-testing:osaurusTests/MLXServiceTests
Integration Testsβ
# test_integration.py
import pytest
import requests
def test_chat_completion():
response = requests.post(
"http://localhost:1337/v1/chat/completions",
json={
"model": "llama-3.2-3b-instruct-4bit",
"messages": [{"role": "user", "content": "Hello"}]
}
)
assert response.status_code == 200
assert "choices" in response.json()
Performance Testingβ
# Run benchmark suite
./scripts/run_bench.sh
# Profile with Instruments
xcrun xctrace record --template "Time Profiler" --launch osaurus
π¨ UI Developmentβ
SwiftUI Best Practicesβ
// Use @StateObject for view models
struct ChatView: View {
@StateObject private var viewModel = ChatViewModel()
var body: some View {
// Use ViewBuilder for complex layouts
// Prefer computed properties
// Extract reusable components
}
}
// Create reusable components
struct MessageBubble: View {
let message: ChatMessage
var body: some View {
Text(message.content)
.padding()
.background(bubbleColor)
.cornerRadius(12)
}
private var bubbleColor: Color {
message.role == .user ? .blue : .gray
}
}
Theme Systemβ
extension Color {
static let osaurusPrimary = Color("AccentColor")
static let osaurusBackground = Color("BackgroundColor")
}
extension Font {
static let osaurusTitle = Font.system(.largeTitle, design: .rounded)
static let osaurusBody = Font.system(.body)
}
π Adding New Featuresβ
1. New Model Supportβ
// 1. Add model definition
struct NewModel: MLXModel {
let architecture = "new_arch"
let tokenizer = "new_tokenizer"
}
// 2. Update model registry
ModelRegistry.register(NewModel.self, for: "new_arch")
// 3. Add to UI
ModelManager.suggestedModels.append(
SuggestedModel(name: "New Model", repo: "org/model")
)
2. New API Endpointβ
// 1. Create handler
class CustomHandler: HTTPHandler {
func handle(_ request: HTTPRequest) async -> HTTPResponse {
// Process request
// Return response
}
}
// 2. Add route
Router.addRoute("/custom", handler: CustomHandler())
// 3. Document in OpenAPI
3. New Settingsβ
// 1. Add to configuration model
struct ServerConfiguration {
var newSetting: Bool = false
}
// 2. Add UI control
Toggle("New Feature", isOn: $config.newSetting)
// 3. Persist to UserDefaults
@AppStorage("newSetting") var newSetting = false
π¦ Dependenciesβ
Core Dependenciesβ
- SwiftNIO - High-performance HTTP server
- MLX-Swift - ML framework for Apple Silicon
- MLXLLM - LLM-specific MLX extensions
Development Dependenciesβ
- SwiftLint - Code linting
- XCTest - Unit testing
- Instruments - Performance profiling
Package Managementβ
// Package.swift
dependencies: [
.package(url: "https://github.com/ml-explore/mlx-swift", from: "0.10.0"),
.package(url: "https://github.com/apple/swift-nio.git", from: "2.50.0"),
]
π Debuggingβ
Common Issuesβ
Model Loading Failures
// Check model path
print(modelPath.path)
// Verify files exist
let requiredFiles = ["config.json", "model.safetensors"]
for file in requiredFiles {
assert(FileManager.default.fileExists(atPath: modelPath.appendingPathComponent(file).path))
}
Memory Issues
// Monitor memory usage
let memory = ProcessInfo.processInfo.physicalMemory
print("Available memory: \(memory / 1024 / 1024 / 1024)GB")
// Force model unload
MLXService.shared.unloadModel()
Network Errors
// Enable verbose logging
HTTPHandler.logLevel = .trace
// Check port binding
netstat -an | grep 1337
Debug Helpersβ
// Add debug commands
#if DEBUG
extension ChatView {
func debugMenu() -> some View {
Menu("Debug") {
Button("Clear Cache") { MLXService.shared.clearCache() }
Button("Print Stats") { printDebugStats() }
Button("Force Crash") { fatalError("Debug crash") }
}
}
}
#endif
π Performance Optimizationβ
Model Loadingβ
- Cache loaded models in memory
- Implement lazy loading
- Use memory mapping for weights
Inferenceβ
- Batch similar requests
- Implement request queuing
- Use streaming for long outputs
UI Responsivenessβ
- Offload heavy work to background queues
- Use SwiftUI's task modifier
- Implement progressive loading
π€ Contributingβ
Code Styleβ
Follow Swift style guide:
- Use descriptive names
- Document public APIs
- Keep functions focused
- Prefer immutability
Pull Request Processβ
- Fork and create feature branch
- Write tests for new features
- Update documentation
- Run SwiftLint
- Submit PR with description
Commit Messagesβ
feat: Add support for new model architecture
fix: Resolve memory leak in streaming responses
docs: Update API documentation
test: Add integration tests for tool calling
refactor: Simplify router implementation
π Resourcesβ
Documentationβ
Toolsβ
Communityβ
- Discord - Get help and discuss
- GitHub Issues - Report bugs
- GitHub Discussions - Ideas and questions
π Securityβ
Best Practicesβ
- Validate all inputs
- Sanitize file paths
- Use secure random for IDs
- Implement rate limiting
- Log security events
Reporting Issuesβ
See SECURITY.md for reporting vulnerabilities.
Ready to contribute?
Check out good first issues to get started!