๐๏ธ Architecture
Tresor follows a clean layered structure with three core concerns: storage, engine, and API. The codebase is written in Go with no external web framework โ it uses net/http ServeMux directly for all routing.
๐ Directory Structureโ
cmd/ # CLI entry points (Cobra)
โโโ root.go # Root command, --config flag
โโโ run.go # Daemon startup
โโโ rule.go # Rule & alias subcommands
โโโ version.go # Version display
internal/
โโโ config/ # YAML configuration loader with auto-detect
โ โโโ config.go
โ
โโโ store/ # SQLite data layer + upsert logic
โ โโโ store.go # Schema creation, migrations, busy timeout
โ โโโ rule.go # Rule CRUD + pattern matching
โ โโโ downstream.go # Downstream CRUD + model management
โ โโโ alias.go # Alias CRUD + group operations
โ โโโ write_config.go # YAML write-back on mutation
โ
โโโ engine/ # Core gateway handler + pipeline execution
โ โโโ engine.go # Gateway forwarding, model resolution, auth, auto-translation, retry on empty response
โ โโโ pipeline.go # Transformer orchestration, streaming
โ โโโ retry.go # Empty response detection + retry with exponential backoff
โ โโโ types.go # Interfaces (RequestTransformer, etc.)
โ
โโโ plugins/ # Plugin registry + built-in transformers
โ โโโ registry.go # Plugin registration and lookup
โ โโโ custom_header.go # Header injection plugin
โ โโโ openai2anthropic.go # OpenAI โ Anthropic format conversion
โ โโโ anthropic2openai.go # Anthropic โ OpenAI format conversion
โ โโโ openai2responses.go # OpenAI Chat Completions โ Responses API
โ โโโ anthropic2responses.go # Anthropic Messages โ Responses API
โ โโโ responses2openai.go # Responses API โ OpenAI Chat Completions
โ โโโ responses2anthropic.go # Responses API โ Anthropic Messages
โ โโโ anthropic_image_fix.go # Image extraction from tool results
โ โโโ anthropic_usage_fix.go # Anthropic usage block normalization (MiniMax-M3 etc.)
โ โโโ openai2gemini.go # OpenAI โ Gemini format conversion
โ โโโ anthropic2gemini.go # Anthropic โ Gemini format conversion
โ โโโ gemini2openai.go # Gemini โ OpenAI format conversion
โ โโโ gemini2anthropic.go # Gemini โ Anthropic format conversion
โ โโโ gemini2responses.go # Gemini โ Responses API conversion
โ โโโ streaming.go # SSE parsing and chunk transformation
โ โโโ streaming_test.go # Comprehensive streaming transform tests
โ
โโโ api/ # Admin REST API + embedded web UI
โ โโโ router.go # Route registration, method enforcement, auth endpoints
โ โโโ rules.go # Rule endpoints
โ โโโ downstreams.go # Downstream endpoints + plugins list + fetch-models
โ โโโ aliases.go # Alias endpoints + group operations
โ โโโ config.go # Runtime config endpoints (proxy mode, auth keys, password, default tab, retry on empty)
โ โโโ icons.go # Model icon serving (public endpoint)
โ โโโ icons_admin.go # Icon index refresh (admin endpoint)
โ โโโ embed.go # Embedded web UI via //go:embed
โ
โโโ middleware/ # Cookie-based session auth for admin API
โ โโโ auth.go
โ
โโโ icons/ # Model icon fetching, pattern matching, CDN caching
โ โโโ fetcher.go # Icon fetching and caching
โ โโโ index.go # CDN index management
โ โโโ patterns.go # Model-to-icon pattern matching
โ โโโ ratelimit.go # Rate limiting for icon fetcher
โ
โโโ inspect/ # Bounded in-memory store for captured request/response bodies
โ โโโ store.go # Inspect store for Logs inspector
โ
โโโ proxy/ # Outbound proxy mode implementation
โ โโโ proxy.go # Mode resolution (auto/env/windows/none)
โ โโโ system_windows.go # Windows registry proxy reading
โ
โโโ api/web/ # Embedded SPA
โโโ index.html
โโโ style.css
โโโ app.js
e2e/ # End-to-end integration tests
main.go # Binary entry point
๐ ๏ธ Technology Stackโ
| Concern | Choice | Reason |
|---|---|---|
| Language | Go 1.26+ | Fast compilation, cross-platform CLI, native concurrency |
| Router | net/http ServeMux | No external framework dependency |
| CLI Framework | cobra | De facto standard for Go CLI applications |
| Database | modernc.org/sqlite | Pure Go SQLite โ no CGO dependency |
| Config | YAML | Human-readable, portable, version-controllable |
| Web UI | Embedded via //go:embed | No separate frontend deployment needed |
๐ Request Flowโ
Client Request
โ
โผ
โโโโโโโโโโโโโโโโโโโโ
โ 1. Auth Check โ โ Validate proxy_api_keys if configured
โโโโโโโโโโฌโโโโโโโโโโ
โ
โโโโโโโโโโผโโโโโโโโโ
โ 2. Model Extract โ โ Parse model name from request body
โโโโโโโโโโฌโโโโโโโโโ
โ
โโโโโโโโโโผโโโโโโโโโ
โ 3. Model Resolve โ โ The forwarding gate:
โ โ โข Active exact alias? โ use alias downstream, rewrite model
โ โ โข Active regex alias? โ pattern match, use downstream, rewrite
โ โ โข No alias? โ find downstream by output_model_ids
โ โ โข Neither? โ return 404 "unknown model"
โโโโโโโโโโฌโโโโโโโโโ
โ
โโโโโโโโโโผโโโโโโโโโ
โ 4. Rule Match โ โ Collect ALL matching enabled rules (path+model)
โ โ Priority: exact path+model > exact path > wildcard *
โ โ Filter by: match_format, match_downstream_format,
โ โ match_downstreams. Rules never override downstream.
โ โ Pipelines from all matching rules are concatenated.
โโโโโโโโโโฌโโโโโโโโโ
โ
โโโโโโโโโโผโโโโโโโโโ
โ 5. Auto- โ โ If request format != downstream api_formats,
โ Translation โ automatically insert format converter (prepended/
โ โ appended around rule pipelines)
โ โ Supports: OpenAI Chat Completions, Anthropic Messages,
โ โ OpenAI Responses API
โโโโโโโโโโฌโโโโโโโโโ
โ
โโโโโโโโโโผโโโโโโโโโ
โ 6. Request โ โ Execute request transformers sequentially
โ Pipeline โ (body + headers may change)
โโโโโโโโโโฌโโโโโโโโโ
โ
โโโโโโโโโโผโโโโโโโโโ
โ 7. Forward โ โ Send transformed request to downstream server
โ to Downstream โ Strip client auth, inject downstream API key
โโโโโโโโโโฌโโโโโโโโโ
โ
โโโโโโโโโโผโโโโโโโโโ
โ 8. Retry on โ โ (if retry_on_empty is enabled)
โ Empty โ If downstream returns HTTP 200 but produces no
โ Response โ content (empty choices, thinking-only output,
โ โ etc.), retry up to 3 times with exponential
โ โ backoff. Retries are transparent to the client.
โโโโโโโโโโฌโโโโโโโโโ
โ
โโโโโโโโโโผโโโโโโโโโ
โ 9. Response โ โ Execute response transformers sequentially
โ Pipeline โ Non-streaming: full body transform
โ โ Streaming (SSE): event-by-event transform
โโโโโโโโโโฌโโโโโโโโโ
โ
โโโโโโโโโโผโโโโโโโโโ
โ 10. Return to โ โ Final response to client
โ Client โ
โโโโโโโโโโโโโโโโโโโโ
Retryโ
When retry_on_empty: true is set in the config, the gateway automatically retries downstream requests that return HTTP 200 OK but produce no useful content. This protects against scenarios where:
- The downstream LLM server accepts the request but produces no text output (transient server-side issues)
- A model generates only "thinking" or "reasoning" blocks without actual text content
- The response is garbled or empty for other reasons
How it works:
- Only retries on HTTP 200 with empty content. Non-200 responses (4xx, 5xx) are never retried โ LLM client SDKs handle their own retry logic for HTTP errors.
- Maximum 3 retries with exponential backoff (500ms, 1s, 2s base) and 0โ25% random jitter.
- Streaming responses: A
bufferedWriterholds all SSE events until content is confirmed. When the first content-producing line is detected (text, tool calls, etc.), the buffer flushes to the client. If no content arrives, the stream is discarded and retried. - Non-streaming responses: The full body is read, response transformers are applied, then the format-specific empty check runs. Empty bodies are discarded and retried.
- Empty detection per format:
- OpenAI Chat Completions: Empty choices, or no text/tool_calls/refusal in both
messageanddeltafields - Anthropic Messages: Empty content array, or blocks containing only
thinking(notext, notool_use) - OpenAI Responses: Empty output array, or message items with no
output_text - Google Gemini: Empty candidates, or parts with no
text/functionCall(parts withthought:trueexcluded)
- OpenAI Chat Completions: Empty choices, or no text/tool_calls/refusal in both
- Transparent to client: The client sees nothing during retries. Only the final successful response is delivered.
- Exhaustion: If all retries are exhausted, the gateway returns HTTP 502 with error "empty response after retries exhausted".
- Client disconnection during retry aborts the loop immediately.
๐ก Aggregated Model Listโ
Tresor exposes /v1/models and /models endpoints that aggregate all known model IDs from downstream output_model_ids and alias input/output model IDs. Regex aliases are excluded from the input model ID list since they represent patterns rather than concrete model names. The response is formatted as an OpenAI-style model list, so OpenAI-compatible clients can discover available models through Tresor.
๐พ Data Layerโ
๐๏ธ SQLite Schemaโ
The database uses WAL mode with a 5000ms busy timeout. Four tables:
downstreams โ Provider endpoints
| Column | Type | Description |
|---|---|---|
id | TEXT PRIMARY KEY | Unique identifier |
name | TEXT NOT NULL | Display name |
base_url | TEXT NOT NULL | API base URL |
api_key | TEXT NOT NULL | Authentication key |
api_formats | TEXT (JSON) | API format(s) this downstream speaks |
created_at | DATETIME | Creation timestamp |
updated_at | DATETIME | Last update timestamp |
output_model_ids โ Model-to-downstream mapping
| Column | Type | Description |
|---|---|---|
downstream_id | TEXT REFERENCES downstreams | Foreign key |
model_id | TEXT | Model identifier |
(Composite unique on both columns)
rules โ Conditional transform pipelines with format-aware matching
| Column | Type | Description |
|---|---|---|
id | TEXT PRIMARY KEY | Unique identifier |
name | TEXT NOT NULL | Display name |
pattern_path | TEXT NOT NULL | URL path to match |
pattern_model | TEXT | Optional model filter |
match_format | TEXT (JSON) | Input request formats to match |
match_downstream_format | TEXT (JSON) | Downstream API formats to match |
match_downstreams | TEXT (JSON) | Downstream IDs to match |
pipeline_config | TEXT (JSON) | Ordered plugin list |
is_enabled | INTEGER | Toggle (0/1) |
created_at | DATETIME | Creation timestamp |
updated_at | DATETIME | Last update timestamp |
aliases โ Model name mappings
| Column | Type | Description |
|---|---|---|
id | TEXT PRIMARY KEY | Unique identifier |
input_model_id | TEXT NOT NULL | Client-facing model name (group key) |
downstream_id | TEXT NOT NULL REFERENCES downstreams | Target provider |
output_model_id | TEXT NOT NULL | Actual model to forward as |
is_active | INTEGER DEFAULT 0 | Active flag (one per group) |
is_regex | INTEGER DEFAULT 0 | Treat input_model_id as a regex pattern |
group_order | INTEGER DEFAULT 0 | Display order for group reordering |
created_at | DATETIME | Creation timestamp |
updated_at | DATETIME | Last update timestamp |
sessions โ Active admin sessions (multi-session support)
| Column | Type | Description |
|---|---|---|
token | TEXT PRIMARY KEY | Session token (UUID) |
created_at | DATETIME | Session creation timestamp |
Indexes on foreign keys and query patterns ensure efficient lookups. Regex patterns are cached (via sync.Map) to avoid recompilation on every request.
๐ Upsert Semanticsโ
On startup, YAML data is merged into the database:
- Downstreams: Matched by ID โ updated if exists, inserted if new.
api_formatscarried over. - Rules: Matched by ID โ updated if exists, inserted if new.
match_format,match_downstream_format,match_downstreamsserialized as JSON arrays. - Aliases: Special logic โ runtime-active aliases not in YAML are preserved (protects hot-switched state); stale YAML aliases not matching DB are deleted; new YAML aliases are created
- Model IDs: All YAML models replace existing ones for each downstream
Rows that exist only in the database (created via web UI or API) are preserved.
๐ฅ Cascade Deleteโ
Deleting a downstream:
- Removes the downstream ID from
match_downstreamsarrays on all referencing rules - Deletes all aliases pointing to that downstream
๐ Admin APIโ
Public Endpoints (no auth required)โ
| Method | Path | Purpose |
|---|---|---|
| GET | /api/health | Health check |
| GET | /api/auth/status | Check whether auth is enabled |
| POST | /api/auth/login | Verify password, obtain session |
| GET | /api/version | Binary version and build time |
Protected Endpointsโ
All other /api/* endpoints require authentication. The admin API uses cookie-based session authentication with multi-session support: after logging in via POST /api/auth/login, a session cookie (tresor_token, Path=/api/, HttpOnly, SameSite=Lax, 365-day expiry) is issued. Multiple simultaneous sessions are supported โ logging in from a second browser doesn't invalidate the first. Logging out from one browser only clears that session; others remain active. Authenticated requests send the cookie (for browser/SSE) or use it as a Bearer header. CLI commands use the raw admin_password as Bearer token (backwards compat). Session tokens persist across daemon restarts (stored in SQLite). Login is rate-limited (5 attempts per 60s per IP). Changing the admin password clears all active sessions.
Key alias endpoints include POST /api/aliases/reorder for drag-and-drop group reordering, which accepts {"order": ["gpt-4o", "claude-sonnet"]}.
Log Endpointsโ
| Method | Path | Purpose |
|---|---|---|
| GET | /api/logs | Get recent log entries (newest first, filtered by log level) |
| GET | /api/logs/stream | SSE stream of log entries (sends initial batch + live updates) |
| GET | /api/log_level | Get current log level |
| PUT/POST | /api/log_level | Update log level |
Runtime Configuration Endpointโ
| Method | Path | Purpose |
|---|---|---|
| GET | /api/config | Get current runtime settings (proxy mode, proxy keys, password status, default tab, log level, retry on empty) |
| PUT | /api/config | Update runtime settings live (pushes changes to running engine + auth middleware, writes back to YAML) |