Files
aethera/backend/AGENTS.md

96 lines
4.1 KiB
Markdown

# Backend Agent Instructions
## Stack
- **Go 1.25.5** (`reichard.io/aethera`)
- **cobra** (CLI framework)
- **logrus** (structured logging)
- **openai-go/v3** (OpenAI API client)
- **jsonschema-go** (structured output schema generation)
- **testify** (testing assertions)
- **golangci-lint** (linting)
## Commands
```bash
go build -o ./dist/aethera ./cmd # Build binary
golangci-lint run # Lint
go test ./... # Run all tests
```
## Architecture
Frontend assets are embedded at compile time via `web/embed.go` (`//go:embed static/*`). The `make frontend` target copies built frontend output into `backend/web/static/` before the Go build.
### Directory Layout
```
cmd/ # CLI entry (main.go, config.go)
internal/
api/ # HTTP handlers, request/response types, streaming generation
client/ # OpenAI-compatible API client (chat, images, structured output)
server/ # HTTP server setup, routing, logging middleware
store/ # Store interface + FileStore (JSON files) & InMemoryStore
storage/ # Standalone file utilities (e.g. ListImages)
types/ # Shared domain types (MessageStats)
pkg/
ptr/ # Generic pointer helpers (Of, DerefOrZero)
slices/ # Generic slice helpers (Map, First, Last, FindFirst)
values/ # Generic value helpers (FirstNonZero, CountNonZero)
web/ # Embedded static assets (embed.go)
```
### Key Patterns
- **Store interface** (`internal/store/interface.go`): abstraction over settings + chat persistence. Two implementations:
- `FileStore` — JSON files on disk (settings.json + `chats/*.json`). Used in production.
- `InMemoryStore` — map-based, thread-safe with `sync.RWMutex`. Used in tests.
- **DI via constructors**: `api.New(store, dataDir, logger)`, `client.NewClient(baseURL)`, `store.NewFileStore(path)`
- **Streaming**: `generationManager` coordinates concurrent chat completions. One active generation per chat. Subscribers receive `MessageChunk` updates via channels over NDJSON (`application/x-ndjson`).
- **Client lazy init**: `api.getClient()` creates the OpenAI client on first use from stored settings, invalidated on settings change.
- **Env vars**: all prefixed `AETHERA_` (e.g. `AETHERA_PORT`, `AETHERA_DATA_DIR`). CLI flags override env.
### API Routes
| Method | Path | Handler |
|----------|-------------------------------|--------------------|
| GET | `/api/settings` | GetSettings |
| POST | `/api/settings` | PostSettings |
| GET | `/api/models` | GetModels |
| GET | `/api/images` | GetImages |
| POST | `/api/images` | PostImage |
| DELETE | `/api/images/{filename}` | DeleteImage |
| GET | `/api/chats` | GetChats |
| POST | `/api/chats` | PostChat |
| GET | `/api/chats/{chatId}` | GetChat |
| POST | `/api/chats/{chatId}` | PostChatMessage |
| DELETE | `/api/chats/{chatId}` | DeleteChat |
| GET | `/api/chats/{chatId}/stream` | GetChatStream |
| POST | `/api/chats/{chatId}/stop` | StopChatGeneration |
## Testing
- Use `testify` (`require` for fatal, `assert` for non-fatal)
- Test both success and error paths
- Table-driven tests where appropriate
- File-based storage tests use temp directories
- Concurrency safety is verified
## Non-Negotiables
- ❌ No unhandled errors — always check `err`
- ❌ No ignored linter warnings
- ❌ No sensitive data in logs
- ❌ No hardcoded paths — use `path.Join`
- ❌ No unsafe file access — use `filepath.Base`
- ❌ Don't skip tests or linting
## Code Style
- Tab indentation, PascalCase exports, camelCase internal
- Error wrapping with context: `fmt.Errorf("...: %w", err)`
- Sentinel errors for domain cases (`ErrChatNotFound`, `ErrNilChatID`)
- Struct tags for JSON with `omitempty`
- Log with context: `log.WithField("key", val)`
- Clean up resources with `defer`