diff --git a/.env.template b/.env.template
index 41994e9c..4a85198e 100644
--- a/.env.template
+++ b/.env.template
@@ -99,13 +99,12 @@
# MODEL_LIST_URL=https://raw.githubusercontent.com/ENTERPILOT/ai-model-list/refs/heads/main/models.min.json
# Model Access Configuration
-# Process-wide default for concrete provider models when no persisted override exists and model overrides are enabled (default: true)
+# Process-wide default for provider models when no persisted override exists (default: true)
# Set to false to keep models unavailable until a model override allows one or more user paths.
# MODELS_ENABLED_BY_DEFAULT=true
-# Persisted model overrides are opt-in (default: false).
-# Set true to load/enforce model overrides and enable dashboard editing.
-# MODEL_OVERRIDES_ENABLED=false
-# Hide concrete provider models from GET /v1/models and expose only enabled aliases (default: false).
+# Enable/disable persisted model overrides and dashboard editing (default: true).
+# MODEL_OVERRIDES_ENABLED=true
+# Hide provider models from GET /v1/models and expose only enabled aliases (default: false).
# KEEP_ONLY_ALIASES_AT_MODELS_ENDPOINT=false
# Fallback & Workflow Configuration
@@ -259,7 +258,7 @@
# OPENROUTER_API_KEY=sk-or-...
# OPENROUTER_BASE_URL=https://openrouter.ai/api/v1
# OPENROUTER_SITE_URL=https://gomodel.enterpilot.io
-# OPENROUTER_APP_NAME=GOModel
+# OPENROUTER_APP_NAME=GoModel
# Azure OpenAI
# AZURE_API_KEY=...
diff --git a/2026-03-16_ARCHITECTURE_SNAPSHOT.md b/2026-03-16_ARCHITECTURE_SNAPSHOT.md
index 89ffa0e6..00788f4a 100644
--- a/2026-03-16_ARCHITECTURE_SNAPSHOT.md
+++ b/2026-03-16_ARCHITECTURE_SNAPSHOT.md
@@ -1,4 +1,4 @@
-# GOModel Architecture Snapshot
+# GoModel Architecture Snapshot
This document is a point-in-time architecture snapshot based on the code and runtime wiring present on March 16, 2026.
@@ -153,11 +153,11 @@ Echo + Handler"]
## 2. Request-Scoped Data Objects
-| Object | Created by | Contains | Consumed by |
-| --- | --- | --- | --- |
-| `RequestSnapshot` | `RequestSnapshotCapture()` | Immutable ingress transport data: method, path, route params, query params, headers, content type, captured body bytes, `BodyNotCaptured`, request id, trace metadata | `DeriveWhiteBoxPrompt`, audit logging, passthrough semantic enrichers, any later logic that needs raw ingress fidelity |
-| `WhiteBoxPrompt` | `core.DeriveWhiteBoxPrompt(snapshot)` | Best-effort semantics: route type, operation type, route hints, stream intent, JSON parsed flag, cached typed request objects, cached route metadata | workflow resolution, canonical request decoding, passthrough/file/batch helpers |
-| `Workflow` | `WorkflowResolutionWithResolver(...)` or `ensureTranslatedRequestWorkflow(...)` | Control-plane decision: endpoint descriptor, execution mode, capabilities, provider type, resolved model selector, passthrough info | response cache, translated handlers, passthrough handlers, audit-log enrichment |
+| Object | Created by | Contains | Consumed by |
+| ----------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
+| `RequestSnapshot` | `RequestSnapshotCapture()` | Immutable ingress transport data: method, path, route params, query params, headers, content type, captured body bytes, `BodyNotCaptured`, request id, trace metadata | `DeriveWhiteBoxPrompt`, audit logging, passthrough semantic enrichers, any later logic that needs raw ingress fidelity |
+| `WhiteBoxPrompt` | `core.DeriveWhiteBoxPrompt(snapshot)` | Best-effort semantics: route type, operation type, route hints, stream intent, JSON parsed flag, cached typed request objects, cached route metadata | workflow resolution, canonical request decoding, passthrough/file/batch helpers |
+| `Workflow` | `WorkflowResolutionWithResolver(...)` or `ensureTranslatedRequestWorkflow(...)` | Control-plane decision: endpoint descriptor, execution mode, capabilities, provider type, resolved model selector, passthrough info | response cache, translated handlers, passthrough handlers, audit-log enrichment |
Important constraints:
diff --git a/AGENTS.md b/AGENTS.md
index 0b40a5c0..20f4637d 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -8,8 +8,8 @@ GoModel is a high-performance AI gateway in Go that routes requests to multiple
Follow Postel's Law: be conservative in what you send, liberal in what you accept.
-- GOModel accepts client requests generously (e.g. `max_tokens` for any model) and adapts them to each provider's specific requirements before forwarding (e.g. translating `max_tokens` → `max_completion_tokens` for OpenAI reasoning models).
-- GOModel accepts providers' responses liberally and passes them to the user in a conservative OpenAI-compatible shape.
+- GoModel accepts client requests generously (e.g. `max_tokens` for any model) and adapts them to each provider's specific requirements before forwarding (e.g. translating `max_tokens` → `max_completion_tokens` for OpenAI reasoning models).
+- GoModel accepts providers' responses liberally and passes them to the user in a conservative OpenAI-compatible shape.
[The Twelve-Factor App](https://12factor.net/).
diff --git a/CLAUDE.md b/CLAUDE.md
index 11aefb4d..73da097f 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -4,10 +4,10 @@ Guidance for AI models (like Claude) working with this codebase.
## Project Overview
-**GOModel** is a high-performance AI gateway in Go that routes requests to multiple AI model providers (OpenAI, Anthropic, Gemini, Groq, xAI, Oracle, Ollama). LiteLLM killer.
+**GoModel** is a high-performance AI gateway in Go that routes requests to multiple AI model providers (OpenAI, Anthropic, Gemini, Groq, xAI, Oracle, Ollama). LiteLLM killer.
**Go:** 1.26.2
-**Repo:** https://github.com/ENTERPILOT/GOModel
+**Repo:** https://github.com/ENTERPILOT/GoModel
- **Stage:** Development - backward compatibility is not a concern
- **Design philosophy:**
@@ -36,7 +36,7 @@ make clean # Remove bin/
make record-api # Record API responses for contract tests
make swagger # Regenerate Swagger docs
make infra # Docker Compose: Redis, Postgres, MongoDB, Adminer only
-make image # Docker Compose: full stack (GOModel + Prometheus)
+make image # Docker Compose: full stack (GoModel + Prometheus)
```
**Single test:** `go test ./internal/providers -v -run TestName`
diff --git a/GETTING_STARTED.md b/GETTING_STARTED.md
index 5ca8d297..b3e61be1 100644
--- a/GETTING_STARTED.md
+++ b/GETTING_STARTED.md
@@ -102,26 +102,24 @@ providers:
api_key: ${ANTHROPIC_API_KEY}
resilience:
retry:
- max_retries: 5 # Anthropic supports long requests — allow more retries
+ max_retries: 5 # Anthropic supports long requests — allow more retries
ollama:
type: ollama
base_url: ${OLLAMA_BASE_URL:-http://localhost:11434/v1}
resilience:
circuit_breaker:
- failure_threshold: 10 # local service — tolerate more transient failures
+ failure_threshold: 10 # local service — tolerate more transient failures
timeout: 5s
```
**Effective resilience per provider:**
-
| Provider | max_retries | failure_threshold | cb timeout |
| --------- | ---------------- | ----------------- | ----------------- |
| openai | 2 (global) | 3 (global) | 15s (global) |
| anthropic | **5** (override) | 3 (global) | 15s (global) |
| ollama | 2 (global) | **10** (override) | **5s** (override) |
-
Only fields that are explicitly listed under a provider's `resilience:` block are overridden. Everything else silently inherits from the global section.
---
@@ -163,7 +161,6 @@ GROQ_API_KEY=gsk_...
All resilience settings can be overridden at runtime via env vars. Env vars always beat both code defaults and YAML values.
-
| Variable | Type | Default | Description |
| ----------------------------------- | -------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `RETRY_MAX_RETRIES` | int | `3` | Maximum retry attempts per request |
@@ -174,36 +171,33 @@ All resilience settings can be overridden at runtime via env vars. Env vars alwa
| `CIRCUIT_BREAKER_FAILURE_THRESHOLD` | int | `5` | Consecutive failures before opening |
| `CIRCUIT_BREAKER_SUCCESS_THRESHOLD` | int | `2` | Consecutive successes to close again |
| `CIRCUIT_BREAKER_TIMEOUT` | duration | `30s` | How long the circuit stays open |
-| `LOG_FORMAT` | string | *(unset)* | Auto-detects based on environment: colorized text on a TTY, JSON otherwise. Set to `text` to force human-readable output (no colors if not a TTY), or `json` to force structured JSON even on a TTY (recommended for production, CloudWatch, Datadog, GCP). |
-| `LOG_LEVEL` | string | `info` | Minimum runtime log level. Supported values are `debug`, `info`, `warn`, and `error`. Common aliases such as `dbg`, `inf`, `warning`, and `err` are also accepted. |
-
+| `LOG_FORMAT` | string | _(unset)_ | Auto-detects based on environment: colorized text on a TTY, JSON otherwise. Set to `text` to force human-readable output (no colors if not a TTY), or `json` to force structured JSON even on a TTY (recommended for production, CloudWatch, Datadog, GCP). |
+| `LOG_LEVEL` | string | `info` | Minimum runtime log level. Supported values are `debug`, `info`, `warn`, and `error`. Common aliases such as `dbg`, `inf`, `warning`, and `err` are also accepted. |
Provider credentials:
-
-| Variable | Provider |
-| -------------------- | --------------------------------------------- |
-| `OPENAI_API_KEY` | OpenAI |
-| `OPENAI_BASE_URL` | OpenAI (custom endpoint) |
-| `ANTHROPIC_API_KEY` | Anthropic |
-| `ANTHROPIC_BASE_URL` | Anthropic (custom endpoint) |
-| `GEMINI_API_KEY` | Google Gemini |
-| `GEMINI_BASE_URL` | Gemini (custom endpoint) |
-| `OPENROUTER_API_KEY` | OpenRouter (default base URL: `https://openrouter.ai/api/v1`) |
-| `OPENROUTER_BASE_URL` | OpenRouter (custom endpoint override) |
+| Variable | Provider |
+| --------------------- | ------------------------------------------------------------------------------ |
+| `OPENAI_API_KEY` | OpenAI |
+| `OPENAI_BASE_URL` | OpenAI (custom endpoint) |
+| `ANTHROPIC_API_KEY` | Anthropic |
+| `ANTHROPIC_BASE_URL` | Anthropic (custom endpoint) |
+| `GEMINI_API_KEY` | Google Gemini |
+| `GEMINI_BASE_URL` | Gemini (custom endpoint) |
+| `OPENROUTER_API_KEY` | OpenRouter (default base URL: `https://openrouter.ai/api/v1`) |
+| `OPENROUTER_BASE_URL` | OpenRouter (custom endpoint override) |
| `OPENROUTER_SITE_URL` | OpenRouter attribution URL override (default: `https://gomodel.enterpilot.io`) |
-| `OPENROUTER_APP_NAME` | OpenRouter attribution title override (default: `GOModel`) |
-| `XAI_API_KEY` | xAI / Grok |
-| `XAI_BASE_URL` | xAI (custom endpoint) |
-| `GROQ_API_KEY` | Groq |
-| `GROQ_BASE_URL` | Groq (custom endpoint) |
-| `AZURE_API_KEY` | Azure OpenAI |
-| `AZURE_BASE_URL` | Azure OpenAI deployment base URL |
-| `AZURE_API_VERSION` | Azure OpenAI API version override (default: `2024-10-21`) |
-| `ORACLE_API_KEY` | Oracle |
-| `ORACLE_BASE_URL` | Oracle OpenAI-compatible base URL |
-| `OLLAMA_BASE_URL` | Ollama (default: `http://localhost:11434/v1`) |
-
+| `OPENROUTER_APP_NAME` | OpenRouter attribution title override (default: `GoModel`) |
+| `XAI_API_KEY` | xAI / Grok |
+| `XAI_BASE_URL` | xAI (custom endpoint) |
+| `GROQ_API_KEY` | Groq |
+| `GROQ_BASE_URL` | Groq (custom endpoint) |
+| `AZURE_API_KEY` | Azure OpenAI |
+| `AZURE_BASE_URL` | Azure OpenAI deployment base URL |
+| `AZURE_API_VERSION` | Azure OpenAI API version override (default: `2024-10-21`) |
+| `ORACLE_API_KEY` | Oracle |
+| `ORACLE_BASE_URL` | Oracle OpenAI-compatible base URL |
+| `OLLAMA_BASE_URL` | Ollama (default: `http://localhost:11434/v1`) |
See `.env.template` for the full list of all configurable environment variables.
@@ -233,7 +227,7 @@ If your Oracle endpoint does not return a usable model list, configure `provider
**Azure ships with a pinned API version by default.**
If you do not set `AZURE_API_VERSION`, the gateway sends `api-version=2024-10-21`. Override it only when you need a different Azure API version.
-**OpenRouter gets GOModel attribution headers by default.**
+**OpenRouter gets GoModel attribution headers by default.**
When the `openrouter` provider is used, the gateway adds `HTTP-Referer` and `X-OpenRouter-Title` unless the request already provides them. Override the defaults with `OPENROUTER_SITE_URL` and `OPENROUTER_APP_NAME`.
**Partial YAML fields leave the rest at defaults.**
diff --git a/METRICS_CONFIGURATION.md b/METRICS_CONFIGURATION.md
index 64bd2abc..b479e6e8 100644
--- a/METRICS_CONFIGURATION.md
+++ b/METRICS_CONFIGURATION.md
@@ -1,12 +1,12 @@
# Prometheus Metrics Configuration Guide
-This guide explains how to configure Prometheus metrics in GOModel.
+This guide explains how to configure Prometheus metrics in GoModel.
## Quick Start
### Disabled by Default
-Metrics are **disabled by default**. To enable metrics collection, set `METRICS_ENABLED=true` and start GOModel:
+Metrics are **disabled by default**. To enable metrics collection, set `METRICS_ENABLED=true` and start GoModel:
```bash
export METRICS_ENABLED=true
@@ -174,7 +174,6 @@ If you need to protect the metrics endpoint further:
```
2. **Use network-level security:**
-
- Configure firewall rules to allow only Prometheus server
- Use private network for metrics collection
- Deploy Prometheus in the same VPC/network
diff --git a/Makefile b/Makefile
index 76219593..b6ff1266 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-.PHONY: all build run clean tidy test test-race test-dashboard test-e2e test-integration test-contract test-all lint lint-fix record-api swagger install-tools perf-check perf-bench infra image
+.PHONY: all build run clean tidy test test-race test-dashboard test-e2e test-integration test-contract test-all lint lint-fix record-api swagger docs-openapi install-tools perf-check perf-bench infra image
all: build
@@ -6,6 +6,7 @@ all: build
VERSION ?= $(shell git describe --tags --always --dirty)
COMMIT ?= $(shell git rev-parse --short HEAD)
DATE ?= $(shell date -u +"%Y-%m-%dT%H:%M:%SZ")
+DOCS_API_SERVERS ?= http://localhost:8080
# Linker flags to inject version info
LDFLAGS := -X "gomodel/internal/version.Version=$(VERSION)" \
@@ -35,7 +36,7 @@ tidy:
infra:
docker compose up -d
-# Docker Compose: full stack (GOModel + Prometheus; builds app image when needed)
+# Docker Compose: full stack (GoModel + Prometheus; builds app image when needed)
image:
docker compose --profile app up -d
@@ -87,8 +88,20 @@ swagger:
go run github.com/swaggo/swag/cmd/swag init --generalInfo main.go \
--dir cmd/gomodel,internal \
--output cmd/gomodel/docs \
- --outputTypes go,json \
+ --outputTypes go \
--parseDependency
+ $(MAKE) docs-openapi
+
+docs-openapi:
+ @tmp_dir=$$(mktemp -d); \
+ trap 'rm -rf "$$tmp_dir"' EXIT; \
+ go run github.com/swaggo/swag/cmd/swag init --quiet --generalInfo main.go \
+ --dir cmd/gomodel,internal \
+ --output "$$tmp_dir" \
+ --outputTypes json \
+ --parseDependency; \
+ npx -y swagger2openapi@7.0.8 --patch -o docs/openapi.json "$$tmp_dir/swagger.json"; \
+ DOCS_API_SERVERS="$(DOCS_API_SERVERS)" node -e 'const fs = require("fs"); const file = "docs/openapi.json"; const urls = (process.env.DOCS_API_SERVERS || "").split(",").map((url) => url.trim()).filter(Boolean); if (!urls.length) throw new Error("DOCS_API_SERVERS must include at least one URL"); const spec = JSON.parse(fs.readFileSync(file, "utf8")); spec.servers = urls.map((url) => ({ url, description: /(^https?:\/\/)?(localhost|127\.0\.0\.1)(:|\/|$$)/.test(url) ? "Local GoModel" : "GoModel" })); fs.writeFileSync(file, JSON.stringify(spec, null, 2) + "\n");'
# Run linter
lint:
diff --git a/PROMETHEUS_IMPLEMENTATION.md b/PROMETHEUS_IMPLEMENTATION.md
index 9c661220..e2b083da 100644
--- a/PROMETHEUS_IMPLEMENTATION.md
+++ b/PROMETHEUS_IMPLEMENTATION.md
@@ -2,7 +2,7 @@
## Overview
-This document describes the Prometheus metrics implementation for GOModel, which provides enterprise-grade observability without polluting business logic. The implementation uses a clean **hooks-based architecture** that separates concerns and allows for future extensibility to other observability tools (DataDog, OpenTelemetry, etc.).
+This document describes the Prometheus metrics implementation for GoModel, which provides enterprise-grade observability without polluting business logic. The implementation uses a clean **hooks-based architecture** that separates concerns and allows for future extensibility to other observability tools (DataDog, OpenTelemetry, etc.).
## Architecture
@@ -174,12 +174,10 @@ type Hooks struct {
The implementation instruments **three critical paths**:
1. **Regular Requests** (`doRequest` method)
-
- Used by `Do()` and `DoRaw()`
- Handles chat completions, responses, and model listings
2. **Streaming Requests** (`DoStream` method)
-
- Used by `StreamChatCompletion()` and `StreamResponses()`
- Duration measured to stream establishment, not stream close
@@ -524,37 +522,30 @@ providers.SetGlobalHooks(combinedHooks)
### Issues Fixed
1. **✅ Incomplete Hook Coverage**
-
- Original: Only instrumented `doRequest`
- Fixed: Instrumented both `doRequest` AND `DoStream` for complete coverage
2. **✅ Model Extraction**
-
- Original: Only handled `ChatRequest`
- Fixed: Handles both `ChatRequest` and `ResponsesRequest`
3. **✅ Status Code Handling**
-
- Original: Set status to "0" for all errors
- Fixed: Extract actual status codes from `GatewayError`, use "network_error" label for network failures
4. **✅ Missing Endpoint Information**
-
- Original: No endpoint tracking
- Fixed: Added `endpoint` label to all metrics for granular debugging
5. **✅ Streaming Metrics**
-
- Original: Streaming not explicitly handled
- Fixed: Separate `stream` label and explicit instrumentation
6. **✅ Missing Imports**
-
- Original: Missing `fmt` import
- Fixed: All imports properly added
7. **✅ Factory Wiring Unclear**
-
- Original: No clear path to inject hooks
- Fixed: Global hooks registry with `SetGlobalHooks()` and `GetGlobalHooks()`
@@ -564,7 +555,7 @@ providers.SetGlobalHooks(combinedHooks)
## Summary
-This implementation provides production-ready Prometheus metrics for GOModel with:
+This implementation provides production-ready Prometheus metrics for GoModel with:
- ✅ Zero impact on business logic
- ✅ Complete request coverage (regular + streaming)
diff --git a/README.md b/README.md
index b65d2c85..99cdd63f 100644
--- a/README.md
+++ b/README.md
@@ -1,20 +1,20 @@
-# GOModel - AI Gateway Written in Go
+# GoModel - AI Gateway Written in Go
-[](https://github.com/ENTERPILOT/GOModel/actions/workflows/test.yml)
+[](https://github.com/ENTERPILOT/GoModel/actions/workflows/test.yml)
[](https://gomodel.enterpilot.io/docs)
[](https://discord.gg/gaEB9BQSPH)
[](https://hub.docker.com/r/enterpilot/gomodel)
-[](https://github.com/ENTERPILOT/GOModel/blob/main/go.mod)
+[](https://github.com/ENTERPILOT/GoModel/blob/main/go.mod)
A high-performance AI gateway written in Go, providing a unified OpenAI-compatible API for OpenAI, Anthropic, Gemini, xAI, Groq, OpenRouter, Azure OpenAI, Oracle, Ollama, and more.
-
+
## Quick Start - Deploy the AI Gateway
-**Step 1:** Start GOModel
+**Step 1:** Start GoModel
```bash
docker run --rm -p 8080:8080 \
@@ -58,25 +58,25 @@ curl http://localhost:8080/v1/chat/completions \
}'
```
-**That's it!** GOModel automatically detects which providers are available based on the credentials you supply.
+**That's it!** GoModel automatically detects which providers are available based on the credentials you supply.
### Supported LLM Providers
Example model identifiers are illustrative and subject to change; consult provider catalogs for current models. Feature columns reflect gateway API support, not every individual model capability exposed by an upstream provider.
-| Provider | Credential | Example Model | Chat | `/responses` | Embed | Files | Batches | Passthru |
-|----------|------------|---------------|:----:|:------------:|:-----:|:-----:|:-------:|:--------:|
-| OpenAI | `OPENAI_API_KEY` | `gpt-4o-mini` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
-| Anthropic | `ANTHROPIC_API_KEY` | `claude-sonnet-4-20250514` | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ |
-| Google Gemini | `GEMINI_API_KEY` | `gemini-2.5-flash` | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
-| Groq | `GROQ_API_KEY` | `llama-3.3-70b-versatile` | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
-| OpenRouter | `OPENROUTER_API_KEY` | `google/gemini-2.5-flash` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
-| xAI (Grok) | `XAI_API_KEY` | `grok-2` | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
-| Azure OpenAI | `AZURE_API_KEY` + `AZURE_BASE_URL` (`AZURE_API_VERSION` optional) | `gpt-4o` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
-| Oracle | `ORACLE_API_KEY` + `ORACLE_BASE_URL` | `openai.gpt-oss-120b` | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
-| Ollama | `OLLAMA_BASE_URL` | `llama3.2` | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ |
+| Provider | Credential | Example Model | Chat | `/responses` | Embed | Files | Batches | Passthru |
+| ------------- | ----------------------------------------------------------------- | -------------------------- | :--: | :----------: | :---: | :---: | :-----: | :------: |
+| OpenAI | `OPENAI_API_KEY` | `gpt-4o-mini` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
+| Anthropic | `ANTHROPIC_API_KEY` | `claude-sonnet-4-20250514` | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ |
+| Google Gemini | `GEMINI_API_KEY` | `gemini-2.5-flash` | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
+| Groq | `GROQ_API_KEY` | `llama-3.3-70b-versatile` | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
+| OpenRouter | `OPENROUTER_API_KEY` | `google/gemini-2.5-flash` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
+| xAI (Grok) | `XAI_API_KEY` | `grok-2` | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
+| Azure OpenAI | `AZURE_API_KEY` + `AZURE_BASE_URL` (`AZURE_API_VERSION` optional) | `gpt-4o` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
+| Oracle | `ORACLE_API_KEY` + `ORACLE_BASE_URL` | `openai.gpt-oss-120b` | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
+| Ollama | `OLLAMA_BASE_URL` | `llama3.2` | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ |
-✅ Supported ❌ Unsupported
+✅ Supported ❌ Unsupported
---
@@ -109,7 +109,7 @@ docker compose up -d
# or: make infra
```
-**Full stack** (adds GOModel + Prometheus; builds the app image):
+**Full stack** (adds GoModel + Prometheus; builds the app image):
```bash
cp .env.template .env
@@ -118,11 +118,11 @@ docker compose --profile app up -d
# or: make image
```
-| Service | URL |
-|---------|-----|
-| GOModel API | http://localhost:8080 |
+| Service | URL |
+| --------------- | --------------------- |
+| GoModel API | http://localhost:8080 |
| Adminer (DB UI) | http://localhost:8081 |
-| Prometheus | http://localhost:9090 |
+| Prometheus | http://localhost:9090 |
### Building the Docker Image Locally
@@ -135,55 +135,55 @@ docker run --rm -p 8080:8080 --env-file .env gomodel
## OpenAI-Compatible API Endpoints
-| Endpoint | Method | Description |
-|----------|--------|-------------|
-| `/v1/chat/completions` | POST | Chat completions (streaming supported) |
-| `/v1/responses` | POST | OpenAI Responses API |
-| `/v1/embeddings` | POST | Text embeddings |
-| `/v1/files` | POST | Upload a file (OpenAI-compatible multipart) |
-| `/v1/files` | GET | List files |
-| `/v1/files/{id}` | GET | Retrieve file metadata |
-| `/v1/files/{id}` | DELETE | Delete a file |
-| `/v1/files/{id}/content` | GET | Retrieve raw file content |
-| `/v1/batches` | POST | Create a native provider batch (OpenAI-compatible schema; inline `requests` supported where provider-native) |
-| `/v1/batches` | GET | List stored batches |
-| `/v1/batches/{id}` | GET | Retrieve one stored batch |
-| `/v1/batches/{id}/cancel` | POST | Cancel a pending batch |
-| `/v1/batches/{id}/results` | GET | Retrieve native batch results when available |
-| `/p/{provider}/...` | GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS | Provider-native passthrough with opaque upstream responses |
-| `/v1/models` | GET | List available models |
-| `/health` | GET | Health check |
-| `/metrics` | GET | Prometheus metrics (when enabled) |
-| `/admin/api/v1/usage/summary` | GET | Aggregate token usage statistics |
-| `/admin/api/v1/usage/daily` | GET | Per-period token usage breakdown |
-| `/admin/api/v1/usage/models` | GET | Usage breakdown by model |
-| `/admin/api/v1/usage/log` | GET | Paginated usage log entries |
-| `/admin/api/v1/audit/log` | GET | Paginated audit log entries |
-| `/admin/api/v1/audit/conversation` | GET | Conversation thread around one audit log entry |
-| `/admin/api/v1/models` | GET | List models with provider type |
-| `/admin/api/v1/models/categories` | GET | List model categories |
-| `/admin/dashboard` | GET | Admin dashboard UI |
-| `/swagger/index.html` | GET | Swagger UI (when enabled) |
+| Endpoint | Method | Description |
+| ---------------------------------- | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
+| `/v1/chat/completions` | POST | Chat completions (streaming supported) |
+| `/v1/responses` | POST | OpenAI Responses API |
+| `/v1/embeddings` | POST | Text embeddings |
+| `/v1/files` | POST | Upload a file (OpenAI-compatible multipart) |
+| `/v1/files` | GET | List files |
+| `/v1/files/{id}` | GET | Retrieve file metadata |
+| `/v1/files/{id}` | DELETE | Delete a file |
+| `/v1/files/{id}/content` | GET | Retrieve raw file content |
+| `/v1/batches` | POST | Create a native provider batch (OpenAI-compatible schema; inline `requests` supported where provider-native) |
+| `/v1/batches` | GET | List stored batches |
+| `/v1/batches/{id}` | GET | Retrieve one stored batch |
+| `/v1/batches/{id}/cancel` | POST | Cancel a pending batch |
+| `/v1/batches/{id}/results` | GET | Retrieve native batch results when available |
+| `/p/{provider}/...` | GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS | Provider-native passthrough with opaque upstream responses |
+| `/v1/models` | GET | List available models |
+| `/health` | GET | Health check |
+| `/metrics` | GET | Prometheus metrics (when enabled) |
+| `/admin/api/v1/usage/summary` | GET | Aggregate token usage statistics |
+| `/admin/api/v1/usage/daily` | GET | Per-period token usage breakdown |
+| `/admin/api/v1/usage/models` | GET | Usage breakdown by model |
+| `/admin/api/v1/usage/log` | GET | Paginated usage log entries |
+| `/admin/api/v1/audit/log` | GET | Paginated audit log entries |
+| `/admin/api/v1/audit/conversation` | GET | Conversation thread around one audit log entry |
+| `/admin/api/v1/models` | GET | List models with provider type |
+| `/admin/api/v1/models/categories` | GET | List model categories |
+| `/admin/dashboard` | GET | Admin dashboard UI |
+| `/swagger/index.html` | GET | Swagger UI (when enabled) |
---
## Gateway Configuration
-GOModel is configured through environment variables and an optional `config.yaml`. Environment variables override YAML values. See [`.env.template`](.env.template) and [`config/config.example.yaml`](config/config.example.yaml) for the available options.
+GoModel is configured through environment variables and an optional `config.yaml`. Environment variables override YAML values. See [`.env.template`](.env.template) and [`config/config.example.yaml`](config/config.example.yaml) for the available options.
Key settings:
-| Variable | Default | Description |
-|----------|---------|-------------|
-| `PORT` | `8080` | Server port |
-| `GOMODEL_MASTER_KEY` | (none) | API key for authentication |
-| `ENABLE_PASSTHROUGH_ROUTES` | `true` | Enable provider-native passthrough routes under `/p/{provider}/...` |
-| `ALLOW_PASSTHROUGH_V1_ALIAS` | `true` | Allow `/p/{provider}/v1/...` aliases while keeping `/p/{provider}/...` canonical |
-| `ENABLED_PASSTHROUGH_PROVIDERS` | `openai,anthropic` | Comma-separated list of enabled passthrough providers |
-| `STORAGE_TYPE` | `sqlite` | Storage backend (`sqlite`, `postgresql`, `mongodb`) |
-| `METRICS_ENABLED` | `false` | Enable Prometheus metrics |
-| `LOGGING_ENABLED` | `false` | Enable audit logging |
-| `GUARDRAILS_ENABLED` | `false` | Enable the configured guardrails pipeline |
+| Variable | Default | Description |
+| ------------------------------- | ------------------ | -------------------------------------------------------------------------------- |
+| `PORT` | `8080` | Server port |
+| `GOMODEL_MASTER_KEY` | (none) | API key for authentication |
+| `ENABLE_PASSTHROUGH_ROUTES` | `true` | Enable provider-native passthrough routes under `/p/{provider}/...` |
+| `ALLOW_PASSTHROUGH_V1_ALIAS` | `true` | Allow `/p/{provider}/v1/...` aliases while keeping `/p/{provider}/...` canonical |
+| `ENABLED_PASSTHROUGH_PROVIDERS` | `openai,anthropic` | Comma-separated list of enabled passthrough providers |
+| `STORAGE_TYPE` | `sqlite` | Storage backend (`sqlite`, `postgresql`, `mongodb`) |
+| `METRICS_ENABLED` | `false` | Enable Prometheus metrics |
+| `LOGGING_ENABLED` | `false` | Enable audit logging |
+| `GUARDRAILS_ENABLED` | `false` | Enable the configured guardrails pipeline |
**Quick Start - Authentication:** By default `GOMODEL_MASTER_KEY` is unset. Without this key, API endpoints are unprotected and anyone can call them. This is insecure for production. **Strongly recommend** setting a strong secret before exposing the service. Add `GOMODEL_MASTER_KEY` to your `.env` or environment for production deployments.
@@ -191,7 +191,7 @@ Key settings:
## Response Caching
-GOModel has a two-layer response cache that reduces LLM API costs and latency for repeated or semantically similar requests.
+GoModel has a two-layer response cache that reduces LLM API costs and latency for repeated or semantically similar requests.
### Layer 1 — Exact-match cache
@@ -204,7 +204,7 @@ cache:
simple:
redis:
url: redis://localhost:6379
- ttl: 3600 # seconds; default 3600
+ ttl: 3600 # seconds; default 3600
```
Or via environment variables: `REDIS_URL`, `REDIS_KEY_RESPONSES`, `REDIS_TTL_RESPONSES`.
@@ -213,7 +213,7 @@ Responses served from this layer carry `X-Cache: HIT (exact)`.
### Layer 2 — Semantic cache
-Embeds the last user message via your configured provider’s OpenAI-compatible `/v1/embeddings` API (`cache.response.semantic.embedder.provider` must name a key in the top-level `providers` map) and performs a KNN vector search. Semantically equivalent queries — e.g. *"What's the capital of France?"* vs *"Which city is France's capital?"* — can return the same cached response without an upstream LLM call.
+Embeds the last user message via your configured provider’s OpenAI-compatible `/v1/embeddings` API (`cache.response.semantic.embedder.provider` must name a key in the top-level `providers` map) and performs a KNN vector search. Semantically equivalent queries — e.g. _"What's the capital of France?"_ vs _"Which city is France's capital?"_ — can return the same cached response without an upstream LLM call.
Expected hit rates: ~60–70% in high-repetition workloads vs. ~18% for exact-match alone.
@@ -233,37 +233,37 @@ See [DEVELOPMENT.md](DEVELOPMENT.md) for testing, linting, and pre-commit setup.
## Shipped
-| Area | Status | Notes |
-| ---- | :----: | ----- |
-| OpenAI-compatible API surface | ✅ | `/v1/chat/completions`, `/v1/responses`, `/v1/embeddings`, `/v1/files*`, `/v1/batches*`, and `/v1/models` are implemented. |
-| Provider passthrough | ✅ | Provider-native passthrough routes are available under `/p/{provider}/...`. |
-| Observability | ✅ | Prometheus metrics, audit logging, usage tracking, request IDs, and trace-header capture are implemented. |
-| Administrative endpoints | ✅ | Admin API and dashboard ship with usage, audit, and model views. |
-| Guardrails | ✅ | The guardrails pipeline is implemented and can be enabled from config. |
-| Guardrail types | ✅ | `system_prompt` and `llm_based_altering` guardrails are supported. |
-| Semantic response cache | ✅ | Exact-match Redis plus optional semantic layer (API embeddings, `qdrant` / `pgvector` / `pinecone` / `weaviate`) — see [ADR-0006](docs/adr/0006-semantic-response-cache.md). |
+| Area | Status | Notes |
+| ----------------------------- | :----: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| OpenAI-compatible API surface | ✅ | `/v1/chat/completions`, `/v1/responses`, `/v1/embeddings`, `/v1/files*`, `/v1/batches*`, and `/v1/models` are implemented. |
+| Provider passthrough | ✅ | Provider-native passthrough routes are available under `/p/{provider}/...`. |
+| Observability | ✅ | Prometheus metrics, audit logging, usage tracking, request IDs, and trace-header capture are implemented. |
+| Administrative endpoints | ✅ | Admin API and dashboard ship with usage, audit, and model views. |
+| Guardrails | ✅ | The guardrails pipeline is implemented and can be enabled from config. |
+| Guardrail types | ✅ | `system_prompt` and `llm_based_altering` guardrails are supported. |
+| Semantic response cache | ✅ | Exact-match Redis plus optional semantic layer (API embeddings, `qdrant` / `pgvector` / `pinecone` / `weaviate`) — see [ADR-0006](docs/adr/0006-semantic-response-cache.md). |
## In Progress
-| Area | Status | Notes |
-| ---- | :----: | ----- |
-| Billing management | 🚧 | Usage and pricing primitives exist, but billing workflows are not complete. |
-| Budget management | 🚧 | Gateway-level budget enforcement and policy controls are not implemented yet. |
-| Guardrails depth | 🚧 | Text guardrails ship today; non-text guardrail types are still to come. |
-| Observability integrations | 🚧 | Native Prometheus support exists; OpenTelemetry and DataDog integrations are still pending. |
+| Area | Status | Notes |
+| -------------------------- | :----: | ------------------------------------------------------------------------------------------- |
+| Billing management | 🚧 | Usage and pricing primitives exist, but billing workflows are not complete. |
+| Budget management | 🚧 | Gateway-level budget enforcement and policy controls are not implemented yet. |
+| Guardrails depth | 🚧 | Text guardrails ship today; non-text guardrail types are still to come. |
+| Observability integrations | 🚧 | Native Prometheus support exists; OpenTelemetry and DataDog integrations are still pending. |
## Planned
-| Area | Status | Notes |
-| ---- | :----: | ----- |
-| Many keys support | 🚧 | The gateway still uses one configured credential/base URL per provider. |
-| SSO / OIDC | 🚧 | No SSO implementation is present yet. |
+| Area | Status | Notes |
+| ----------------- | :----: | ----------------------------------------------------------------------- |
+| Many keys support | 🚧 | The gateway still uses one configured credential/base URL per provider. |
+| SSO / OIDC | 🚧 | No SSO implementation is present yet. |
-✅ Shipped 🚧 Planned or in progress
+✅ Shipped 🚧 Planned or in progress
## Community
-Join our [Discord](https://discord.gg/gaEB9BQSPH) to connect with other GOModel users.
+Join our [Discord](https://discord.gg/gaEB9BQSPH) to connect with other GoModel users.
## Star History
diff --git a/cmd/gomodel/docs/docs.go b/cmd/gomodel/docs/docs.go
index eecd385b..e4d69ed8 100644
--- a/cmd/gomodel/docs/docs.go
+++ b/cmd/gomodel/docs/docs.go
@@ -2157,26 +2157,6 @@ const docTemplate = `{
}
}
},
- "auditlog.WorkflowFeaturesSnapshot": {
- "type": "object",
- "properties": {
- "audit": {
- "type": "boolean"
- },
- "cache": {
- "type": "boolean"
- },
- "fallback": {
- "type": "boolean"
- },
- "guardrails": {
- "type": "boolean"
- },
- "usage": {
- "type": "boolean"
- }
- }
- },
"auditlog.LogData": {
"type": "object",
"properties": {
@@ -2187,14 +2167,6 @@ const docTemplate = `{
"description": "Error details (message can be long, so kept in JSON)",
"type": "string"
},
- "workflow_features": {
- "description": "WorkflowFeatures captures the request-time effective workflow features\nafter runtime caps were applied. This keeps audit views historically accurate\neven if the active process config changes later.",
- "allOf": [
- {
- "$ref": "#/definitions/auditlog.WorkflowFeaturesSnapshot"
- }
- ]
- },
"max_tokens": {
"type": "integer"
},
@@ -2229,6 +2201,14 @@ const docTemplate = `{
"user_agent": {
"description": "Identity",
"type": "string"
+ },
+ "workflow_features": {
+ "description": "WorkflowFeatures captures the request-time effective workflow features\nafter runtime caps were applied. This keeps audit views historically accurate\neven if the active process config changes later.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/auditlog.WorkflowFeaturesSnapshot"
+ }
+ ]
}
}
},
@@ -2265,9 +2245,6 @@ const docTemplate = `{
"error_type": {
"type": "string"
},
- "workflow_version_id": {
- "type": "string"
- },
"id": {
"description": "ID is a unique identifier for this log entry (UUID)",
"type": "string"
@@ -2308,6 +2285,9 @@ const docTemplate = `{
},
"user_path": {
"type": "string"
+ },
+ "workflow_version_id": {
+ "type": "string"
}
}
},
@@ -2331,6 +2311,26 @@ const docTemplate = `{
}
}
},
+ "auditlog.WorkflowFeaturesSnapshot": {
+ "type": "object",
+ "properties": {
+ "audit": {
+ "type": "boolean"
+ },
+ "cache": {
+ "type": "boolean"
+ },
+ "fallback": {
+ "type": "boolean"
+ },
+ "guardrails": {
+ "type": "boolean"
+ },
+ "usage": {
+ "type": "boolean"
+ }
+ }
+ },
"core.BatchError": {
"type": "object",
"properties": {
@@ -2647,49 +2647,7 @@ const docTemplate = `{
"type": "integer"
},
"logprobs": {
- "type": "object",
- "properties": {
- "content": {
- "type": "array",
- "items": {
- "type": "object",
- "properties": {
- "bytes": {
- "type": "array",
- "items": {
- "type": "integer"
- }
- },
- "logprob": {
- "type": "number"
- },
- "token": {
- "type": "string"
- },
- "top_logprobs": {
- "type": "array",
- "items": {
- "type": "object",
- "properties": {
- "bytes": {
- "type": "array",
- "items": {
- "type": "integer"
- }
- },
- "logprob": {
- "type": "number"
- },
- "token": {
- "type": "string"
- }
- }
- }
- }
- }
- }
- }
- }
+ "type": "object"
},
"message": {
"$ref": "#/definitions/core.ResponseMessage"
@@ -3776,7 +3734,7 @@ var SwaggerInfo = &swag.Spec{
Host: "",
BasePath: "/",
Schemes: []string{"http"},
- Title: "GOModel API",
+ Title: "GoModel API",
Description: "High-performance AI gateway routing requests to multiple LLM providers (OpenAI, Anthropic, Gemini, Groq, xAI, Oracle, Ollama). Drop-in OpenAI-compatible API.",
InfoInstanceName: "swagger",
SwaggerTemplate: docTemplate,
diff --git a/cmd/gomodel/docs/swagger.json b/cmd/gomodel/docs/swagger.json
deleted file mode 100644
index de82f919..00000000
--- a/cmd/gomodel/docs/swagger.json
+++ /dev/null
@@ -1,3767 +0,0 @@
-{
- "schemes": [
- "http"
- ],
- "swagger": "2.0",
- "info": {
- "description": "High-performance AI gateway routing requests to multiple LLM providers (OpenAI, Anthropic, Gemini, Groq, xAI, Oracle, Ollama). Drop-in OpenAI-compatible API.",
- "title": "GOModel API",
- "contact": {},
- "version": "1.0"
- },
- "basePath": "/",
- "paths": {
- "/admin/api/v1/audit/conversation": {
- "get": {
- "produces": [
- "application/json"
- ],
- "tags": [
- "admin"
- ],
- "summary": "Get conversation thread around an audit log entry",
- "parameters": [
- {
- "type": "string",
- "description": "Anchor audit log entry ID",
- "name": "log_id",
- "in": "query",
- "required": true
- },
- {
- "type": "integer",
- "description": "Max entries in thread (default 40, max 200)",
- "name": "limit",
- "in": "query"
- }
- ],
- "responses": {
- "200": {
- "description": "OK",
- "schema": {
- "$ref": "#/definitions/auditlog.ConversationResult"
- }
- },
- "400": {
- "description": "Bad Request",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "401": {
- "description": "Unauthorized",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- }
- },
- "security": [
- {
- "BearerAuth": []
- }
- ]
- }
- },
- "/admin/api/v1/audit/log": {
- "get": {
- "produces": [
- "application/json"
- ],
- "tags": [
- "admin"
- ],
- "summary": "Get paginated audit log entries",
- "parameters": [
- {
- "type": "integer",
- "description": "Number of days (default 30)",
- "name": "days",
- "in": "query"
- },
- {
- "type": "string",
- "description": "Start date (YYYY-MM-DD)",
- "name": "start_date",
- "in": "query"
- },
- {
- "type": "string",
- "description": "End date (YYYY-MM-DD)",
- "name": "end_date",
- "in": "query"
- },
- {
- "type": "string",
- "description": "Filter by requested model selector",
- "name": "requested_model",
- "in": "query"
- },
- {
- "type": "string",
- "description": "Filter by provider name or provider type",
- "name": "provider",
- "in": "query"
- },
- {
- "type": "string",
- "description": "Filter by HTTP method",
- "name": "method",
- "in": "query"
- },
- {
- "type": "string",
- "description": "Filter by request path",
- "name": "path",
- "in": "query"
- },
- {
- "type": "string",
- "description": "Filter by tracked user path subtree",
- "name": "user_path",
- "in": "query"
- },
- {
- "type": "string",
- "description": "Filter by error type",
- "name": "error_type",
- "in": "query"
- },
- {
- "type": "integer",
- "description": "Filter by status code",
- "name": "status_code",
- "in": "query"
- },
- {
- "type": "boolean",
- "description": "Filter by stream mode (true/false)",
- "name": "stream",
- "in": "query"
- },
- {
- "type": "string",
- "description": "Search across request_id/requested_model/provider/method/path/error_type",
- "name": "search",
- "in": "query"
- },
- {
- "type": "integer",
- "description": "Page size (default 25, max 100)",
- "name": "limit",
- "in": "query"
- },
- {
- "type": "integer",
- "description": "Offset for pagination",
- "name": "offset",
- "in": "query"
- }
- ],
- "responses": {
- "200": {
- "description": "OK",
- "schema": {
- "$ref": "#/definitions/auditlog.LogListResult"
- }
- },
- "400": {
- "description": "Bad Request",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "401": {
- "description": "Unauthorized",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- }
- },
- "security": [
- {
- "BearerAuth": []
- }
- ]
- }
- },
- "/admin/api/v1/cache/overview": {
- "get": {
- "produces": [
- "application/json"
- ],
- "tags": [
- "admin"
- ],
- "summary": "Get cached-only usage overview",
- "parameters": [
- {
- "type": "integer",
- "description": "Number of days (default 30)",
- "name": "days",
- "in": "query"
- },
- {
- "type": "string",
- "description": "Start date (YYYY-MM-DD)",
- "name": "start_date",
- "in": "query"
- },
- {
- "type": "string",
- "description": "End date (YYYY-MM-DD)",
- "name": "end_date",
- "in": "query"
- },
- {
- "type": "string",
- "description": "Grouping interval: daily, weekly, monthly, yearly (default daily)",
- "name": "interval",
- "in": "query"
- },
- {
- "type": "string",
- "description": "Filter by tracked user path subtree",
- "name": "user_path",
- "in": "query"
- },
- {
- "type": "string",
- "description": "Cache mode filter: uncached, cached, all (cache overview always uses cached mode)",
- "name": "cache_mode",
- "in": "query"
- }
- ],
- "responses": {
- "200": {
- "description": "OK",
- "schema": {
- "$ref": "#/definitions/usage.CacheOverview"
- }
- },
- "400": {
- "description": "Bad Request",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "401": {
- "description": "Unauthorized",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "503": {
- "description": "Service Unavailable",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- }
- },
- "security": [
- {
- "BearerAuth": []
- }
- ]
- }
- },
- "/admin/api/v1/models/categories": {
- "get": {
- "produces": [
- "application/json"
- ],
- "tags": [
- "admin"
- ],
- "summary": "List model categories with counts",
- "responses": {
- "200": {
- "description": "OK",
- "schema": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/providers.CategoryCount"
- }
- }
- },
- "401": {
- "description": "Unauthorized",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- }
- },
- "security": [
- {
- "BearerAuth": []
- }
- ]
- }
- },
- "/admin/api/v1/usage/daily": {
- "get": {
- "produces": [
- "application/json"
- ],
- "tags": [
- "admin"
- ],
- "summary": "Get usage breakdown by period",
- "parameters": [
- {
- "type": "integer",
- "description": "Number of days (default 30)",
- "name": "days",
- "in": "query"
- },
- {
- "type": "string",
- "description": "Start date (YYYY-MM-DD)",
- "name": "start_date",
- "in": "query"
- },
- {
- "type": "string",
- "description": "End date (YYYY-MM-DD)",
- "name": "end_date",
- "in": "query"
- },
- {
- "type": "string",
- "description": "Grouping interval: daily, weekly, monthly, yearly (default daily)",
- "name": "interval",
- "in": "query"
- },
- {
- "type": "string",
- "description": "Filter by tracked user path subtree",
- "name": "user_path",
- "in": "query"
- },
- {
- "type": "string",
- "description": "Cache mode filter: uncached, cached, all (default uncached)",
- "name": "cache_mode",
- "in": "query"
- }
- ],
- "responses": {
- "200": {
- "description": "OK",
- "schema": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/usage.DailyUsage"
- }
- }
- },
- "400": {
- "description": "Bad Request",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "401": {
- "description": "Unauthorized",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- }
- },
- "security": [
- {
- "BearerAuth": []
- }
- ]
- }
- },
- "/admin/api/v1/usage/log": {
- "get": {
- "produces": [
- "application/json"
- ],
- "tags": [
- "admin"
- ],
- "summary": "Get paginated usage log entries",
- "parameters": [
- {
- "type": "integer",
- "description": "Number of days (default 30)",
- "name": "days",
- "in": "query"
- },
- {
- "type": "string",
- "description": "Start date (YYYY-MM-DD)",
- "name": "start_date",
- "in": "query"
- },
- {
- "type": "string",
- "description": "End date (YYYY-MM-DD)",
- "name": "end_date",
- "in": "query"
- },
- {
- "type": "string",
- "description": "Filter by model name",
- "name": "model",
- "in": "query"
- },
- {
- "type": "string",
- "description": "Filter by provider name or provider type",
- "name": "provider",
- "in": "query"
- },
- {
- "type": "string",
- "description": "Filter by tracked user path subtree",
- "name": "user_path",
- "in": "query"
- },
- {
- "type": "string",
- "description": "Cache mode filter: uncached, cached, all (default uncached)",
- "name": "cache_mode",
- "in": "query"
- },
- {
- "type": "string",
- "description": "Search across model, provider, request_id, provider_id",
- "name": "search",
- "in": "query"
- },
- {
- "type": "integer",
- "description": "Page size (default 50, max 200)",
- "name": "limit",
- "in": "query"
- },
- {
- "type": "integer",
- "description": "Offset for pagination",
- "name": "offset",
- "in": "query"
- }
- ],
- "responses": {
- "200": {
- "description": "OK",
- "schema": {
- "$ref": "#/definitions/usage.UsageLogResult"
- }
- },
- "400": {
- "description": "Bad Request",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "401": {
- "description": "Unauthorized",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- }
- },
- "security": [
- {
- "BearerAuth": []
- }
- ]
- }
- },
- "/admin/api/v1/usage/models": {
- "get": {
- "produces": [
- "application/json"
- ],
- "tags": [
- "admin"
- ],
- "summary": "Get usage breakdown by model",
- "parameters": [
- {
- "type": "integer",
- "description": "Number of days (default 30)",
- "name": "days",
- "in": "query"
- },
- {
- "type": "string",
- "description": "Start date (YYYY-MM-DD)",
- "name": "start_date",
- "in": "query"
- },
- {
- "type": "string",
- "description": "End date (YYYY-MM-DD)",
- "name": "end_date",
- "in": "query"
- },
- {
- "type": "string",
- "description": "Filter by tracked user path subtree",
- "name": "user_path",
- "in": "query"
- },
- {
- "type": "string",
- "description": "Cache mode filter: uncached, cached, all (default uncached)",
- "name": "cache_mode",
- "in": "query"
- }
- ],
- "responses": {
- "200": {
- "description": "OK",
- "schema": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/usage.ModelUsage"
- }
- }
- },
- "400": {
- "description": "Bad Request",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "401": {
- "description": "Unauthorized",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- }
- },
- "security": [
- {
- "BearerAuth": []
- }
- ]
- }
- },
- "/admin/api/v1/usage/summary": {
- "get": {
- "produces": [
- "application/json"
- ],
- "tags": [
- "admin"
- ],
- "summary": "Get usage summary",
- "parameters": [
- {
- "type": "integer",
- "description": "Number of days (default 30)",
- "name": "days",
- "in": "query"
- },
- {
- "type": "string",
- "description": "Start date (YYYY-MM-DD)",
- "name": "start_date",
- "in": "query"
- },
- {
- "type": "string",
- "description": "End date (YYYY-MM-DD)",
- "name": "end_date",
- "in": "query"
- },
- {
- "type": "string",
- "description": "Filter by tracked user path subtree",
- "name": "user_path",
- "in": "query"
- },
- {
- "type": "string",
- "description": "Cache mode filter: uncached, cached, all (default uncached)",
- "name": "cache_mode",
- "in": "query"
- }
- ],
- "responses": {
- "200": {
- "description": "OK",
- "schema": {
- "$ref": "#/definitions/usage.UsageSummary"
- }
- },
- "400": {
- "description": "Bad Request",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "401": {
- "description": "Unauthorized",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- }
- },
- "security": [
- {
- "BearerAuth": []
- }
- ]
- }
- },
- "/admin/api/v1/usage/user-paths": {
- "get": {
- "produces": [
- "application/json"
- ],
- "tags": [
- "admin"
- ],
- "summary": "Get usage breakdown by user path",
- "parameters": [
- {
- "type": "integer",
- "description": "Number of days (default 30)",
- "name": "days",
- "in": "query"
- },
- {
- "type": "string",
- "description": "Start date (YYYY-MM-DD)",
- "name": "start_date",
- "in": "query"
- },
- {
- "type": "string",
- "description": "End date (YYYY-MM-DD)",
- "name": "end_date",
- "in": "query"
- },
- {
- "type": "string",
- "description": "Filter by tracked user path subtree",
- "name": "user_path",
- "in": "query"
- },
- {
- "type": "string",
- "description": "Cache mode filter: uncached, cached, all (default uncached)",
- "name": "cache_mode",
- "in": "query"
- }
- ],
- "responses": {
- "200": {
- "description": "OK",
- "schema": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/usage.UserPathUsage"
- }
- }
- },
- "400": {
- "description": "Bad Request",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "401": {
- "description": "Unauthorized",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- }
- },
- "security": [
- {
- "BearerAuth": []
- }
- ]
- }
- },
- "/health": {
- "get": {
- "produces": [
- "application/json"
- ],
- "tags": [
- "system"
- ],
- "summary": "Health check",
- "responses": {
- "200": {
- "description": "OK",
- "schema": {
- "type": "object",
- "additionalProperties": {
- "type": "string"
- }
- }
- }
- }
- }
- },
- "/p/{provider}/{endpoint}": {
- "get": {
- "description": "Runtime-configurable passthrough endpoint under /p/{provider}/{endpoint}; enabled by default via server.enable_passthrough_routes. The endpoint path is opaque and may proxy JSON, binary, or SSE responses with upstream status codes preserved. For multi-segment provider endpoints, clients that rely on OpenAPI-generated path handling should URL-encode embedded slashes in the endpoint parameter. A leading v1/ segment is normalized away by default so /p/{provider}/v1/... and /p/{provider}/... map to the same upstream path relative to the provider base URL.",
- "consumes": [
- "application/json",
- "multipart/form-data"
- ],
- "produces": [
- "application/json",
- "application/octet-stream",
- "text/event-stream"
- ],
- "tags": [
- "passthrough"
- ],
- "summary": "Provider passthrough",
- "parameters": [
- {
- "type": "string",
- "description": "Provider type",
- "name": "provider",
- "in": "path",
- "required": true
- },
- {
- "type": "string",
- "description": "Provider-native endpoint path relative to the provider base URL. URL-encode embedded / characters when using generated clients.",
- "name": "endpoint",
- "in": "path",
- "required": true
- }
- ],
- "responses": {
- "200": {
- "description": "Opaque upstream response body",
- "schema": {
- "type": "file"
- }
- },
- "201": {
- "description": "Opaque upstream response body",
- "schema": {
- "type": "file"
- }
- },
- "202": {
- "description": "Opaque upstream response body",
- "schema": {
- "type": "file"
- }
- },
- "204": {
- "description": "No Content passthrough response",
- "schema": {
- "type": "string"
- }
- },
- "400": {
- "description": "Bad Request",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "401": {
- "description": "Unauthorized",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "502": {
- "description": "Bad Gateway",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- }
- },
- "security": [
- {
- "BearerAuth": []
- }
- ]
- },
- "put": {
- "description": "Runtime-configurable passthrough endpoint under /p/{provider}/{endpoint}; enabled by default via server.enable_passthrough_routes. The endpoint path is opaque and may proxy JSON, binary, or SSE responses with upstream status codes preserved. For multi-segment provider endpoints, clients that rely on OpenAPI-generated path handling should URL-encode embedded slashes in the endpoint parameter. A leading v1/ segment is normalized away by default so /p/{provider}/v1/... and /p/{provider}/... map to the same upstream path relative to the provider base URL.",
- "consumes": [
- "application/json",
- "multipart/form-data"
- ],
- "produces": [
- "application/json",
- "application/octet-stream",
- "text/event-stream"
- ],
- "tags": [
- "passthrough"
- ],
- "summary": "Provider passthrough",
- "parameters": [
- {
- "type": "string",
- "description": "Provider type",
- "name": "provider",
- "in": "path",
- "required": true
- },
- {
- "type": "string",
- "description": "Provider-native endpoint path relative to the provider base URL. URL-encode embedded / characters when using generated clients.",
- "name": "endpoint",
- "in": "path",
- "required": true
- }
- ],
- "responses": {
- "200": {
- "description": "Opaque upstream response body",
- "schema": {
- "type": "file"
- }
- },
- "201": {
- "description": "Opaque upstream response body",
- "schema": {
- "type": "file"
- }
- },
- "202": {
- "description": "Opaque upstream response body",
- "schema": {
- "type": "file"
- }
- },
- "204": {
- "description": "No Content passthrough response",
- "schema": {
- "type": "string"
- }
- },
- "400": {
- "description": "Bad Request",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "401": {
- "description": "Unauthorized",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "502": {
- "description": "Bad Gateway",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- }
- },
- "security": [
- {
- "BearerAuth": []
- }
- ]
- },
- "post": {
- "description": "Runtime-configurable passthrough endpoint under /p/{provider}/{endpoint}; enabled by default via server.enable_passthrough_routes. The endpoint path is opaque and may proxy JSON, binary, or SSE responses with upstream status codes preserved. For multi-segment provider endpoints, clients that rely on OpenAPI-generated path handling should URL-encode embedded slashes in the endpoint parameter. A leading v1/ segment is normalized away by default so /p/{provider}/v1/... and /p/{provider}/... map to the same upstream path relative to the provider base URL.",
- "consumes": [
- "application/json",
- "multipart/form-data"
- ],
- "produces": [
- "application/json",
- "application/octet-stream",
- "text/event-stream"
- ],
- "tags": [
- "passthrough"
- ],
- "summary": "Provider passthrough",
- "parameters": [
- {
- "type": "string",
- "description": "Provider type",
- "name": "provider",
- "in": "path",
- "required": true
- },
- {
- "type": "string",
- "description": "Provider-native endpoint path relative to the provider base URL. URL-encode embedded / characters when using generated clients.",
- "name": "endpoint",
- "in": "path",
- "required": true
- }
- ],
- "responses": {
- "200": {
- "description": "Opaque upstream response body",
- "schema": {
- "type": "file"
- }
- },
- "201": {
- "description": "Opaque upstream response body",
- "schema": {
- "type": "file"
- }
- },
- "202": {
- "description": "Opaque upstream response body",
- "schema": {
- "type": "file"
- }
- },
- "204": {
- "description": "No Content passthrough response",
- "schema": {
- "type": "string"
- }
- },
- "400": {
- "description": "Bad Request",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "401": {
- "description": "Unauthorized",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "502": {
- "description": "Bad Gateway",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- }
- },
- "security": [
- {
- "BearerAuth": []
- }
- ]
- },
- "delete": {
- "description": "Runtime-configurable passthrough endpoint under /p/{provider}/{endpoint}; enabled by default via server.enable_passthrough_routes. The endpoint path is opaque and may proxy JSON, binary, or SSE responses with upstream status codes preserved. For multi-segment provider endpoints, clients that rely on OpenAPI-generated path handling should URL-encode embedded slashes in the endpoint parameter. A leading v1/ segment is normalized away by default so /p/{provider}/v1/... and /p/{provider}/... map to the same upstream path relative to the provider base URL.",
- "consumes": [
- "application/json",
- "multipart/form-data"
- ],
- "produces": [
- "application/json",
- "application/octet-stream",
- "text/event-stream"
- ],
- "tags": [
- "passthrough"
- ],
- "summary": "Provider passthrough",
- "parameters": [
- {
- "type": "string",
- "description": "Provider type",
- "name": "provider",
- "in": "path",
- "required": true
- },
- {
- "type": "string",
- "description": "Provider-native endpoint path relative to the provider base URL. URL-encode embedded / characters when using generated clients.",
- "name": "endpoint",
- "in": "path",
- "required": true
- }
- ],
- "responses": {
- "200": {
- "description": "Opaque upstream response body",
- "schema": {
- "type": "file"
- }
- },
- "201": {
- "description": "Opaque upstream response body",
- "schema": {
- "type": "file"
- }
- },
- "202": {
- "description": "Opaque upstream response body",
- "schema": {
- "type": "file"
- }
- },
- "204": {
- "description": "No Content passthrough response",
- "schema": {
- "type": "string"
- }
- },
- "400": {
- "description": "Bad Request",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "401": {
- "description": "Unauthorized",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "502": {
- "description": "Bad Gateway",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- }
- },
- "security": [
- {
- "BearerAuth": []
- }
- ]
- },
- "options": {
- "description": "Runtime-configurable passthrough endpoint under /p/{provider}/{endpoint}; enabled by default via server.enable_passthrough_routes. The endpoint path is opaque and may proxy JSON, binary, or SSE responses with upstream status codes preserved. For multi-segment provider endpoints, clients that rely on OpenAPI-generated path handling should URL-encode embedded slashes in the endpoint parameter. A leading v1/ segment is normalized away by default so /p/{provider}/v1/... and /p/{provider}/... map to the same upstream path relative to the provider base URL.",
- "consumes": [
- "application/json",
- "multipart/form-data"
- ],
- "produces": [
- "application/json",
- "application/octet-stream",
- "text/event-stream"
- ],
- "tags": [
- "passthrough"
- ],
- "summary": "Provider passthrough",
- "parameters": [
- {
- "type": "string",
- "description": "Provider type",
- "name": "provider",
- "in": "path",
- "required": true
- },
- {
- "type": "string",
- "description": "Provider-native endpoint path relative to the provider base URL. URL-encode embedded / characters when using generated clients.",
- "name": "endpoint",
- "in": "path",
- "required": true
- }
- ],
- "responses": {
- "200": {
- "description": "Opaque upstream response body",
- "schema": {
- "type": "file"
- }
- },
- "201": {
- "description": "Opaque upstream response body",
- "schema": {
- "type": "file"
- }
- },
- "202": {
- "description": "Opaque upstream response body",
- "schema": {
- "type": "file"
- }
- },
- "204": {
- "description": "No Content passthrough response",
- "schema": {
- "type": "string"
- }
- },
- "400": {
- "description": "Bad Request",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "401": {
- "description": "Unauthorized",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "502": {
- "description": "Bad Gateway",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- }
- },
- "security": [
- {
- "BearerAuth": []
- }
- ]
- },
- "head": {
- "description": "Runtime-configurable passthrough endpoint under /p/{provider}/{endpoint}; enabled by default via server.enable_passthrough_routes. The endpoint path is opaque and may proxy JSON, binary, or SSE responses with upstream status codes preserved. For multi-segment provider endpoints, clients that rely on OpenAPI-generated path handling should URL-encode embedded slashes in the endpoint parameter. A leading v1/ segment is normalized away by default so /p/{provider}/v1/... and /p/{provider}/... map to the same upstream path relative to the provider base URL.",
- "consumes": [
- "application/json",
- "multipart/form-data"
- ],
- "produces": [
- "application/json",
- "application/octet-stream",
- "text/event-stream"
- ],
- "tags": [
- "passthrough"
- ],
- "summary": "Provider passthrough",
- "parameters": [
- {
- "type": "string",
- "description": "Provider type",
- "name": "provider",
- "in": "path",
- "required": true
- },
- {
- "type": "string",
- "description": "Provider-native endpoint path relative to the provider base URL. URL-encode embedded / characters when using generated clients.",
- "name": "endpoint",
- "in": "path",
- "required": true
- }
- ],
- "responses": {
- "200": {
- "description": "Opaque upstream response body",
- "schema": {
- "type": "file"
- }
- },
- "201": {
- "description": "Opaque upstream response body",
- "schema": {
- "type": "file"
- }
- },
- "202": {
- "description": "Opaque upstream response body",
- "schema": {
- "type": "file"
- }
- },
- "204": {
- "description": "No Content passthrough response",
- "schema": {
- "type": "string"
- }
- },
- "400": {
- "description": "Bad Request",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "401": {
- "description": "Unauthorized",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "502": {
- "description": "Bad Gateway",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- }
- },
- "security": [
- {
- "BearerAuth": []
- }
- ]
- },
- "patch": {
- "description": "Runtime-configurable passthrough endpoint under /p/{provider}/{endpoint}; enabled by default via server.enable_passthrough_routes. The endpoint path is opaque and may proxy JSON, binary, or SSE responses with upstream status codes preserved. For multi-segment provider endpoints, clients that rely on OpenAPI-generated path handling should URL-encode embedded slashes in the endpoint parameter. A leading v1/ segment is normalized away by default so /p/{provider}/v1/... and /p/{provider}/... map to the same upstream path relative to the provider base URL.",
- "consumes": [
- "application/json",
- "multipart/form-data"
- ],
- "produces": [
- "application/json",
- "application/octet-stream",
- "text/event-stream"
- ],
- "tags": [
- "passthrough"
- ],
- "summary": "Provider passthrough",
- "parameters": [
- {
- "type": "string",
- "description": "Provider type",
- "name": "provider",
- "in": "path",
- "required": true
- },
- {
- "type": "string",
- "description": "Provider-native endpoint path relative to the provider base URL. URL-encode embedded / characters when using generated clients.",
- "name": "endpoint",
- "in": "path",
- "required": true
- }
- ],
- "responses": {
- "200": {
- "description": "Opaque upstream response body",
- "schema": {
- "type": "file"
- }
- },
- "201": {
- "description": "Opaque upstream response body",
- "schema": {
- "type": "file"
- }
- },
- "202": {
- "description": "Opaque upstream response body",
- "schema": {
- "type": "file"
- }
- },
- "204": {
- "description": "No Content passthrough response",
- "schema": {
- "type": "string"
- }
- },
- "400": {
- "description": "Bad Request",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "401": {
- "description": "Unauthorized",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "502": {
- "description": "Bad Gateway",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- }
- },
- "security": [
- {
- "BearerAuth": []
- }
- ]
- }
- },
- "/v1/batches": {
- "get": {
- "produces": [
- "application/json"
- ],
- "tags": [
- "batch"
- ],
- "summary": "List batches",
- "parameters": [
- {
- "type": "string",
- "description": "Pagination cursor",
- "name": "after",
- "in": "query"
- },
- {
- "type": "integer",
- "description": "Maximum items to return (1-100, default 20)",
- "name": "limit",
- "in": "query"
- }
- ],
- "responses": {
- "200": {
- "description": "OK",
- "schema": {
- "$ref": "#/definitions/core.BatchListResponse"
- }
- },
- "400": {
- "description": "Bad Request",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "401": {
- "description": "Unauthorized",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "404": {
- "description": "Not Found",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "500": {
- "description": "Internal Server Error",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- }
- },
- "security": [
- {
- "BearerAuth": []
- }
- ]
- },
- "post": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "batch"
- ],
- "summary": "Create a native provider batch",
- "parameters": [
- {
- "description": "Batch request",
- "name": "request",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/core.BatchRequest"
- }
- }
- ],
- "responses": {
- "200": {
- "description": "OK",
- "schema": {
- "$ref": "#/definitions/core.BatchResponse"
- }
- },
- "400": {
- "description": "Bad Request",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "401": {
- "description": "Unauthorized",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "502": {
- "description": "Bad Gateway",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- }
- },
- "security": [
- {
- "BearerAuth": []
- }
- ]
- }
- },
- "/v1/batches/{id}": {
- "get": {
- "produces": [
- "application/json"
- ],
- "tags": [
- "batch"
- ],
- "summary": "Get a batch",
- "parameters": [
- {
- "type": "string",
- "description": "Batch ID",
- "name": "id",
- "in": "path",
- "required": true
- }
- ],
- "responses": {
- "200": {
- "description": "OK",
- "schema": {
- "$ref": "#/definitions/core.BatchResponse"
- }
- },
- "400": {
- "description": "Bad Request",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "401": {
- "description": "Unauthorized",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "404": {
- "description": "Not Found",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "500": {
- "description": "Internal Server Error",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "502": {
- "description": "Bad Gateway",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- }
- },
- "security": [
- {
- "BearerAuth": []
- }
- ]
- }
- },
- "/v1/batches/{id}/cancel": {
- "post": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "batch"
- ],
- "summary": "Cancel a batch",
- "parameters": [
- {
- "type": "string",
- "description": "Batch ID",
- "name": "id",
- "in": "path",
- "required": true
- }
- ],
- "responses": {
- "200": {
- "description": "OK",
- "schema": {
- "$ref": "#/definitions/core.BatchResponse"
- }
- },
- "400": {
- "description": "Bad Request",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "401": {
- "description": "Unauthorized",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "404": {
- "description": "Not Found",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "500": {
- "description": "Internal Server Error",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "502": {
- "description": "Bad Gateway",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- }
- },
- "security": [
- {
- "BearerAuth": []
- }
- ]
- }
- },
- "/v1/batches/{id}/results": {
- "get": {
- "produces": [
- "application/json"
- ],
- "tags": [
- "batch"
- ],
- "summary": "Get batch results",
- "parameters": [
- {
- "type": "string",
- "description": "Batch ID",
- "name": "id",
- "in": "path",
- "required": true
- }
- ],
- "responses": {
- "200": {
- "description": "OK",
- "schema": {
- "$ref": "#/definitions/core.BatchResultsResponse"
- }
- },
- "400": {
- "description": "Bad Request",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "401": {
- "description": "Unauthorized",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "404": {
- "description": "Not Found",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "409": {
- "description": "Conflict",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "500": {
- "description": "Internal Server Error",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "502": {
- "description": "Bad Gateway",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- }
- },
- "security": [
- {
- "BearerAuth": []
- }
- ]
- }
- },
- "/v1/chat/completions": {
- "post": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json",
- "text/event-stream"
- ],
- "tags": [
- "chat"
- ],
- "summary": "Create a chat completion",
- "parameters": [
- {
- "description": "Chat completion request",
- "name": "request",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/core.ChatRequest"
- }
- }
- ],
- "responses": {
- "200": {
- "description": "JSON response or SSE stream when stream=true",
- "schema": {
- "$ref": "#/definitions/core.ChatResponse"
- }
- },
- "400": {
- "description": "Bad Request",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "401": {
- "description": "Unauthorized",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "429": {
- "description": "Too Many Requests",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "502": {
- "description": "Bad Gateway",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- }
- },
- "security": [
- {
- "BearerAuth": []
- }
- ]
- }
- },
- "/v1/embeddings": {
- "post": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "embeddings"
- ],
- "summary": "Create embeddings",
- "parameters": [
- {
- "description": "Embeddings request",
- "name": "request",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/core.EmbeddingRequest"
- }
- }
- ],
- "responses": {
- "200": {
- "description": "OK",
- "schema": {
- "$ref": "#/definitions/core.EmbeddingResponse"
- }
- },
- "400": {
- "description": "Bad Request",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "401": {
- "description": "Unauthorized",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "429": {
- "description": "Too Many Requests",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "502": {
- "description": "Bad Gateway",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- }
- },
- "security": [
- {
- "BearerAuth": []
- }
- ]
- }
- },
- "/v1/files": {
- "get": {
- "produces": [
- "application/json"
- ],
- "tags": [
- "files"
- ],
- "summary": "List files",
- "parameters": [
- {
- "type": "string",
- "description": "Provider filter",
- "name": "provider",
- "in": "query"
- },
- {
- "type": "string",
- "description": "File purpose filter",
- "name": "purpose",
- "in": "query"
- },
- {
- "type": "string",
- "description": "Pagination cursor",
- "name": "after",
- "in": "query"
- },
- {
- "type": "integer",
- "description": "Maximum items to return (1-100, default 20)",
- "name": "limit",
- "in": "query"
- }
- ],
- "responses": {
- "200": {
- "description": "OK",
- "schema": {
- "$ref": "#/definitions/core.FileListResponse"
- }
- },
- "400": {
- "description": "Bad Request",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "401": {
- "description": "Unauthorized",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "404": {
- "description": "Not Found",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "502": {
- "description": "Bad Gateway",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- }
- },
- "security": [
- {
- "BearerAuth": []
- }
- ]
- },
- "post": {
- "consumes": [
- "multipart/form-data"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "files"
- ],
- "summary": "Upload a file",
- "parameters": [
- {
- "type": "string",
- "description": "Provider override when multiple providers are configured",
- "name": "provider",
- "in": "query"
- },
- {
- "type": "string",
- "description": "File purpose",
- "name": "purpose",
- "in": "formData",
- "required": true
- },
- {
- "type": "file",
- "description": "File to upload",
- "name": "file",
- "in": "formData",
- "required": true
- }
- ],
- "responses": {
- "200": {
- "description": "OK",
- "schema": {
- "$ref": "#/definitions/core.FileObject"
- }
- },
- "400": {
- "description": "Bad Request",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "401": {
- "description": "Unauthorized",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "502": {
- "description": "Bad Gateway",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- }
- },
- "security": [
- {
- "BearerAuth": []
- }
- ]
- }
- },
- "/v1/files/{id}": {
- "get": {
- "produces": [
- "application/json"
- ],
- "tags": [
- "files"
- ],
- "summary": "Get file metadata",
- "parameters": [
- {
- "type": "string",
- "description": "File ID",
- "name": "id",
- "in": "path",
- "required": true
- },
- {
- "type": "string",
- "description": "Provider override",
- "name": "provider",
- "in": "query"
- }
- ],
- "responses": {
- "200": {
- "description": "OK",
- "schema": {
- "$ref": "#/definitions/core.FileObject"
- }
- },
- "400": {
- "description": "Bad Request",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "401": {
- "description": "Unauthorized",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "404": {
- "description": "Not Found",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "502": {
- "description": "Bad Gateway",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- }
- },
- "security": [
- {
- "BearerAuth": []
- }
- ]
- },
- "delete": {
- "produces": [
- "application/json"
- ],
- "tags": [
- "files"
- ],
- "summary": "Delete a file",
- "parameters": [
- {
- "type": "string",
- "description": "File ID",
- "name": "id",
- "in": "path",
- "required": true
- },
- {
- "type": "string",
- "description": "Provider override",
- "name": "provider",
- "in": "query"
- }
- ],
- "responses": {
- "200": {
- "description": "OK",
- "schema": {
- "$ref": "#/definitions/core.FileDeleteResponse"
- }
- },
- "400": {
- "description": "Bad Request",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "401": {
- "description": "Unauthorized",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "404": {
- "description": "Not Found",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "502": {
- "description": "Bad Gateway",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- }
- },
- "security": [
- {
- "BearerAuth": []
- }
- ]
- }
- },
- "/v1/files/{id}/content": {
- "get": {
- "produces": [
- "application/octet-stream"
- ],
- "tags": [
- "files"
- ],
- "summary": "Download file content",
- "parameters": [
- {
- "type": "string",
- "description": "File ID",
- "name": "id",
- "in": "path",
- "required": true
- },
- {
- "type": "string",
- "description": "Provider override",
- "name": "provider",
- "in": "query"
- }
- ],
- "responses": {
- "200": {
- "description": "Raw file content",
- "schema": {
- "type": "file"
- }
- },
- "400": {
- "description": "Bad Request",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "401": {
- "description": "Unauthorized",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "404": {
- "description": "Not Found",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "502": {
- "description": "Bad Gateway",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- }
- },
- "security": [
- {
- "BearerAuth": []
- }
- ]
- }
- },
- "/v1/models": {
- "get": {
- "produces": [
- "application/json"
- ],
- "tags": [
- "models"
- ],
- "summary": "List available models",
- "responses": {
- "200": {
- "description": "OK",
- "schema": {
- "$ref": "#/definitions/core.ModelsResponse"
- }
- },
- "401": {
- "description": "Unauthorized",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "502": {
- "description": "Bad Gateway",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- }
- },
- "security": [
- {
- "BearerAuth": []
- }
- ]
- }
- },
- "/v1/responses": {
- "post": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json",
- "text/event-stream"
- ],
- "tags": [
- "responses"
- ],
- "summary": "Create a model response (Responses API)",
- "parameters": [
- {
- "description": "Responses API request",
- "name": "request",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/core.ResponsesRequest"
- }
- }
- ],
- "responses": {
- "200": {
- "description": "JSON response or SSE stream when stream=true",
- "schema": {
- "$ref": "#/definitions/core.ResponsesResponse"
- }
- },
- "400": {
- "description": "Bad Request",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "401": {
- "description": "Unauthorized",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "429": {
- "description": "Too Many Requests",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- },
- "502": {
- "description": "Bad Gateway",
- "schema": {
- "$ref": "#/definitions/core.GatewayError"
- }
- }
- },
- "security": [
- {
- "BearerAuth": []
- }
- ]
- }
- }
- },
- "definitions": {
- "auditlog.ConversationResult": {
- "type": "object",
- "properties": {
- "anchor_id": {
- "type": "string"
- },
- "entries": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/auditlog.LogEntry"
- }
- }
- }
- },
- "auditlog.WorkflowFeaturesSnapshot": {
- "type": "object",
- "properties": {
- "audit": {
- "type": "boolean"
- },
- "cache": {
- "type": "boolean"
- },
- "fallback": {
- "type": "boolean"
- },
- "guardrails": {
- "type": "boolean"
- },
- "usage": {
- "type": "boolean"
- }
- }
- },
- "auditlog.LogData": {
- "type": "object",
- "properties": {
- "api_key_hash": {
- "type": "string"
- },
- "error_message": {
- "description": "Error details (message can be long, so kept in JSON)",
- "type": "string"
- },
- "workflow_features": {
- "description": "WorkflowFeatures captures the request-time effective workflow features\nafter runtime caps were applied. This keeps audit views historically accurate\neven if the active process config changes later.",
- "allOf": [
- {
- "$ref": "#/definitions/auditlog.WorkflowFeaturesSnapshot"
- }
- ]
- },
- "max_tokens": {
- "type": "integer"
- },
- "request_body": {
- "description": "Optional bodies (when LOGGING_LOG_BODIES=true)\nStored as interface{} so MongoDB serializes as native BSON documents (queryable/readable)\ninstead of BSON Binary (base64 in Compass)"
- },
- "request_body_too_big_to_handle": {
- "description": "Body capture status flags (set when body exceeds 1MB limit)",
- "type": "boolean"
- },
- "request_headers": {
- "description": "Optional headers (when LOGGING_LOG_HEADERS=true)\nSensitive headers are auto-redacted",
- "type": "object",
- "additionalProperties": {
- "type": "string"
- }
- },
- "response_body": {},
- "response_body_too_big_to_handle": {
- "type": "boolean"
- },
- "response_headers": {
- "type": "object",
- "additionalProperties": {
- "type": "string"
- }
- },
- "temperature": {
- "description": "Request parameters",
- "type": "number"
- },
- "user_agent": {
- "description": "Identity",
- "type": "string"
- }
- }
- },
- "auditlog.LogEntry": {
- "type": "object",
- "properties": {
- "alias_used": {
- "type": "boolean"
- },
- "auth_key_id": {
- "type": "string"
- },
- "auth_method": {
- "type": "string"
- },
- "cache_type": {
- "type": "string"
- },
- "client_ip": {
- "type": "string"
- },
- "data": {
- "description": "Data contains flexible request/response information as JSON",
- "allOf": [
- {
- "$ref": "#/definitions/auditlog.LogData"
- }
- ]
- },
- "duration_ns": {
- "description": "DurationNs is the request duration in nanoseconds",
- "type": "integer"
- },
- "error_type": {
- "type": "string"
- },
- "workflow_version_id": {
- "type": "string"
- },
- "id": {
- "description": "ID is a unique identifier for this log entry (UUID)",
- "type": "string"
- },
- "method": {
- "type": "string"
- },
- "path": {
- "type": "string"
- },
- "provider": {
- "description": "canonical provider type used for routing and filters",
- "type": "string"
- },
- "provider_name": {
- "type": "string"
- },
- "request_id": {
- "description": "Extracted fields for efficient filtering (indexed in relational DBs)",
- "type": "string"
- },
- "requested_model": {
- "description": "Core fields (indexed for queries)",
- "type": "string"
- },
- "resolved_model": {
- "type": "string"
- },
- "status_code": {
- "type": "integer"
- },
- "stream": {
- "type": "boolean"
- },
- "timestamp": {
- "description": "Timestamp is when the request started",
- "type": "string"
- },
- "user_path": {
- "type": "string"
- }
- }
- },
- "auditlog.LogListResult": {
- "type": "object",
- "properties": {
- "entries": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/auditlog.LogEntry"
- }
- },
- "limit": {
- "type": "integer"
- },
- "offset": {
- "type": "integer"
- },
- "total": {
- "type": "integer"
- }
- }
- },
- "core.BatchError": {
- "type": "object",
- "properties": {
- "message": {
- "type": "string"
- },
- "type": {
- "type": "string"
- }
- }
- },
- "core.BatchListResponse": {
- "type": "object",
- "properties": {
- "data": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/core.BatchResponse"
- }
- },
- "first_id": {
- "type": "string"
- },
- "has_more": {
- "type": "boolean"
- },
- "last_id": {
- "type": "string"
- },
- "object": {
- "type": "string"
- }
- }
- },
- "core.BatchRequest": {
- "type": "object",
- "properties": {
- "completion_window": {
- "type": "string"
- },
- "endpoint": {
- "type": "string"
- },
- "input_file_id": {
- "type": "string"
- },
- "metadata": {
- "type": "object",
- "additionalProperties": {
- "type": "string"
- }
- },
- "requests": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/core.BatchRequestItem"
- }
- }
- }
- },
- "core.BatchRequestCounts": {
- "type": "object",
- "properties": {
- "completed": {
- "type": "integer"
- },
- "failed": {
- "type": "integer"
- },
- "total": {
- "type": "integer"
- }
- }
- },
- "core.BatchRequestItem": {
- "type": "object",
- "properties": {
- "body": {
- "type": "object"
- },
- "custom_id": {
- "type": "string"
- },
- "method": {
- "type": "string"
- },
- "url": {
- "type": "string"
- }
- }
- },
- "core.BatchResponse": {
- "type": "object",
- "properties": {
- "cancelled_at": {
- "type": "integer"
- },
- "cancelling_at": {
- "type": "integer"
- },
- "completed_at": {
- "type": "integer"
- },
- "completion_window": {
- "type": "string"
- },
- "created_at": {
- "type": "integer"
- },
- "endpoint": {
- "type": "string"
- },
- "failed_at": {
- "type": "integer"
- },
- "id": {
- "type": "string"
- },
- "in_progress_at": {
- "type": "integer"
- },
- "input_file_id": {
- "type": "string"
- },
- "metadata": {
- "type": "object",
- "additionalProperties": {
- "type": "string"
- }
- },
- "object": {
- "type": "string"
- },
- "provider": {
- "type": "string"
- },
- "provider_batch_id": {
- "type": "string"
- },
- "request_counts": {
- "$ref": "#/definitions/core.BatchRequestCounts"
- },
- "results": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/core.BatchResultItem"
- }
- },
- "status": {
- "type": "string"
- },
- "usage": {
- "description": "Gateway extension: optional usage/result snapshots persisted by the gateway.",
- "allOf": [
- {
- "$ref": "#/definitions/core.BatchUsageSummary"
- }
- ]
- }
- }
- },
- "core.BatchResultItem": {
- "type": "object",
- "properties": {
- "custom_id": {
- "type": "string"
- },
- "error": {
- "$ref": "#/definitions/core.BatchError"
- },
- "index": {
- "type": "integer"
- },
- "model": {
- "type": "string"
- },
- "provider": {
- "type": "string"
- },
- "response": {},
- "status_code": {
- "type": "integer"
- },
- "url": {
- "type": "string"
- }
- }
- },
- "core.BatchResultsResponse": {
- "type": "object",
- "properties": {
- "batch_id": {
- "type": "string"
- },
- "data": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/core.BatchResultItem"
- }
- },
- "object": {
- "type": "string"
- }
- }
- },
- "core.BatchUsageSummary": {
- "type": "object",
- "properties": {
- "input_cost": {
- "type": "number"
- },
- "input_tokens": {
- "type": "integer"
- },
- "output_cost": {
- "type": "number"
- },
- "output_tokens": {
- "type": "integer"
- },
- "total_cost": {
- "type": "number"
- },
- "total_tokens": {
- "type": "integer"
- }
- }
- },
- "core.ChatRequest": {
- "type": "object",
- "properties": {
- "max_tokens": {
- "type": "integer"
- },
- "messages": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/core.Message"
- }
- },
- "model": {
- "type": "string"
- },
- "parallel_tool_calls": {
- "type": "boolean"
- },
- "provider": {
- "description": "Gateway routing hint; stripped before upstream execution.",
- "type": "string"
- },
- "reasoning": {
- "$ref": "#/definitions/core.Reasoning"
- },
- "stream": {
- "type": "boolean"
- },
- "stream_options": {
- "$ref": "#/definitions/core.StreamOptions"
- },
- "temperature": {
- "type": "number"
- },
- "tool_choice": {
- "description": "string or object"
- },
- "tools": {
- "type": "array",
- "items": {
- "type": "object",
- "additionalProperties": {}
- }
- }
- }
- },
- "core.ChatResponse": {
- "type": "object",
- "properties": {
- "choices": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/core.Choice"
- }
- },
- "created": {
- "type": "integer"
- },
- "id": {
- "type": "string"
- },
- "model": {
- "type": "string"
- },
- "object": {
- "type": "string"
- },
- "provider": {
- "type": "string"
- },
- "system_fingerprint": {
- "type": "string"
- },
- "usage": {
- "$ref": "#/definitions/core.Usage"
- }
- }
- },
- "core.Choice": {
- "type": "object",
- "properties": {
- "finish_reason": {
- "type": "string"
- },
- "index": {
- "type": "integer"
- },
- "logprobs": {
- "type": "object",
- "properties": {
- "content": {
- "type": "array",
- "items": {
- "type": "object",
- "properties": {
- "bytes": {
- "type": "array",
- "items": {
- "type": "integer"
- }
- },
- "logprob": {
- "type": "number"
- },
- "token": {
- "type": "string"
- },
- "top_logprobs": {
- "type": "array",
- "items": {
- "type": "object",
- "properties": {
- "bytes": {
- "type": "array",
- "items": {
- "type": "integer"
- }
- },
- "logprob": {
- "type": "number"
- },
- "token": {
- "type": "string"
- }
- }
- }
- }
- }
- }
- }
- }
- },
- "message": {
- "$ref": "#/definitions/core.ResponseMessage"
- }
- }
- },
- "core.CompletionTokensDetails": {
- "type": "object",
- "properties": {
- "accepted_prediction_tokens": {
- "type": "integer"
- },
- "audio_tokens": {
- "type": "integer"
- },
- "reasoning_tokens": {
- "type": "integer"
- },
- "rejected_prediction_tokens": {
- "type": "integer"
- }
- }
- },
- "core.ContentPart": {
- "type": "object",
- "properties": {
- "image_url": {
- "$ref": "#/definitions/core.ImageURLContent"
- },
- "input_audio": {
- "$ref": "#/definitions/core.InputAudioContent"
- },
- "text": {
- "type": "string"
- },
- "type": {
- "type": "string"
- }
- }
- },
- "core.EmbeddingData": {
- "type": "object",
- "properties": {
- "embedding": {
- "type": "object"
- },
- "index": {
- "type": "integer"
- },
- "object": {
- "type": "string"
- }
- }
- },
- "core.EmbeddingRequest": {
- "type": "object",
- "properties": {
- "dimensions": {
- "type": "integer"
- },
- "encoding_format": {
- "type": "string"
- },
- "input": {},
- "model": {
- "type": "string"
- },
- "provider": {
- "description": "Gateway routing hint; stripped before upstream execution.",
- "type": "string"
- }
- }
- },
- "core.EmbeddingResponse": {
- "type": "object",
- "properties": {
- "data": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/core.EmbeddingData"
- }
- },
- "model": {
- "type": "string"
- },
- "object": {
- "type": "string"
- },
- "provider": {
- "type": "string"
- },
- "usage": {
- "$ref": "#/definitions/core.EmbeddingUsage"
- }
- }
- },
- "core.EmbeddingUsage": {
- "type": "object",
- "properties": {
- "prompt_tokens": {
- "type": "integer"
- },
- "total_tokens": {
- "type": "integer"
- }
- }
- },
- "core.ErrorType": {
- "type": "string",
- "enum": [
- "provider_error",
- "rate_limit_error",
- "invalid_request_error",
- "authentication_error",
- "not_found_error"
- ],
- "x-enum-varnames": [
- "ErrorTypeProvider",
- "ErrorTypeRateLimit",
- "ErrorTypeInvalidRequest",
- "ErrorTypeAuthentication",
- "ErrorTypeNotFound"
- ]
- },
- "core.FileDeleteResponse": {
- "type": "object",
- "properties": {
- "deleted": {
- "type": "boolean"
- },
- "id": {
- "type": "string"
- },
- "object": {
- "type": "string"
- }
- }
- },
- "core.FileListResponse": {
- "type": "object",
- "properties": {
- "data": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/core.FileObject"
- }
- },
- "has_more": {
- "type": "boolean"
- },
- "object": {
- "type": "string"
- }
- }
- },
- "core.FileObject": {
- "type": "object",
- "properties": {
- "bytes": {
- "type": "integer"
- },
- "created_at": {
- "type": "integer"
- },
- "filename": {
- "type": "string"
- },
- "id": {
- "type": "string"
- },
- "object": {
- "type": "string"
- },
- "provider": {
- "description": "Gateway enrichment for multi-provider deployments.",
- "type": "string"
- },
- "purpose": {
- "type": "string"
- },
- "status": {
- "type": "string"
- },
- "status_details": {
- "type": "string"
- }
- }
- },
- "core.FunctionCall": {
- "type": "object",
- "properties": {
- "arguments": {
- "type": "string"
- },
- "name": {
- "type": "string"
- }
- }
- },
- "core.GatewayError": {
- "type": "object",
- "properties": {
- "code": {
- "type": "string",
- "x-nullable": true
- },
- "message": {
- "type": "string"
- },
- "param": {
- "type": "string",
- "x-nullable": true
- },
- "provider": {
- "type": "string"
- },
- "status_code": {
- "type": "integer"
- },
- "type": {
- "$ref": "#/definitions/core.ErrorType"
- }
- }
- },
- "core.ImageURLContent": {
- "type": "object",
- "properties": {
- "detail": {
- "type": "string"
- },
- "media_type": {
- "type": "string"
- },
- "url": {
- "type": "string"
- }
- }
- },
- "core.InputAudioContent": {
- "type": "object",
- "properties": {
- "data": {
- "type": "string"
- },
- "format": {
- "type": "string"
- }
- }
- },
- "core.Message": {
- "type": "object",
- "properties": {
- "content": {
- "description": "ContentSchema documents that `content` accepts either a plain string\nor an array of ContentPart values.",
- "type": "array",
- "items": {
- "$ref": "#/definitions/core.ContentPart"
- },
- "x-oneof": "[{\"type\":\"null\"},{\"type\":\"string\"},{\"type\":\"array\",\"items\":{\"$ref\":\"#/definitions/core.ContentPart\"}}]"
- },
- "role": {
- "type": "string"
- },
- "tool_call_id": {
- "type": "string"
- },
- "tool_calls": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/core.ToolCall"
- }
- }
- }
- },
- "core.Model": {
- "type": "object",
- "properties": {
- "created": {
- "type": "integer"
- },
- "id": {
- "type": "string"
- },
- "metadata": {
- "description": "Metadata holds optional enrichment data (display name, pricing, capabilities, etc.).\nMay be nil if the model was not found in the external registry.",
- "allOf": [
- {
- "$ref": "#/definitions/core.ModelMetadata"
- }
- ]
- },
- "object": {
- "type": "string"
- },
- "owned_by": {
- "type": "string"
- }
- }
- },
- "core.ModelCategory": {
- "type": "string",
- "enum": [
- "all",
- "text_generation",
- "embedding",
- "image",
- "audio",
- "video",
- "utility"
- ],
- "x-enum-varnames": [
- "CategoryAll",
- "CategoryTextGeneration",
- "CategoryEmbedding",
- "CategoryImage",
- "CategoryAudio",
- "CategoryVideo",
- "CategoryUtility"
- ]
- },
- "core.ModelMetadata": {
- "type": "object",
- "properties": {
- "capabilities": {
- "type": "object",
- "additionalProperties": {
- "type": "boolean"
- }
- },
- "categories": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/core.ModelCategory"
- }
- },
- "context_window": {
- "type": "integer"
- },
- "description": {
- "type": "string"
- },
- "display_name": {
- "type": "string"
- },
- "family": {
- "type": "string"
- },
- "max_output_tokens": {
- "type": "integer"
- },
- "modes": {
- "type": "array",
- "items": {
- "type": "string"
- }
- },
- "pricing": {
- "$ref": "#/definitions/core.ModelPricing"
- },
- "rankings": {
- "type": "object",
- "additionalProperties": {
- "$ref": "#/definitions/core.ModelRanking"
- }
- },
- "tags": {
- "type": "array",
- "items": {
- "type": "string"
- }
- }
- }
- },
- "core.ModelPricing": {
- "type": "object",
- "properties": {
- "audio_input_per_mtok": {
- "type": "number"
- },
- "audio_output_per_mtok": {
- "type": "number"
- },
- "batch_input_per_mtok": {
- "type": "number"
- },
- "batch_output_per_mtok": {
- "type": "number"
- },
- "cache_write_per_mtok": {
- "type": "number"
- },
- "cached_input_per_mtok": {
- "type": "number"
- },
- "currency": {
- "type": "string"
- },
- "input_per_image": {
- "type": "number"
- },
- "input_per_mtok": {
- "type": "number"
- },
- "output_per_mtok": {
- "type": "number"
- },
- "per_character_input": {
- "type": "number"
- },
- "per_image": {
- "type": "number"
- },
- "per_page": {
- "type": "number"
- },
- "per_request": {
- "type": "number"
- },
- "per_second_input": {
- "type": "number"
- },
- "per_second_output": {
- "type": "number"
- },
- "reasoning_output_per_mtok": {
- "type": "number"
- },
- "tiers": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/core.ModelPricingTier"
- }
- }
- }
- },
- "core.ModelPricingTier": {
- "type": "object",
- "properties": {
- "input_per_mtok": {
- "type": "number"
- },
- "output_per_mtok": {
- "type": "number"
- },
- "up_to_mtok": {
- "type": "number"
- }
- }
- },
- "core.ModelRanking": {
- "type": "object",
- "properties": {
- "as_of": {
- "type": "string"
- },
- "elo": {
- "type": "number"
- },
- "rank": {
- "type": "integer"
- }
- }
- },
- "core.ModelsResponse": {
- "type": "object",
- "properties": {
- "data": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/core.Model"
- }
- },
- "object": {
- "type": "string"
- }
- }
- },
- "core.PromptTokensDetails": {
- "type": "object",
- "properties": {
- "audio_tokens": {
- "type": "integer"
- },
- "cached_tokens": {
- "type": "integer"
- },
- "image_tokens": {
- "type": "integer"
- },
- "text_tokens": {
- "type": "integer"
- }
- }
- },
- "core.Reasoning": {
- "type": "object",
- "properties": {
- "effort": {
- "description": "Effort controls how much reasoning effort the model should use.\nValid values are \"low\", \"medium\", and \"high\".",
- "type": "string"
- }
- }
- },
- "core.ResponseMessage": {
- "type": "object",
- "properties": {
- "content": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/core.ContentPart"
- },
- "x-oneof": "[{\"type\":\"null\"},{\"type\":\"string\"},{\"type\":\"array\",\"items\":{\"$ref\":\"#/definitions/core.ContentPart\"}}]"
- },
- "role": {
- "type": "string"
- },
- "tool_calls": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/core.ToolCall"
- }
- }
- }
- },
- "core.ResponsesContentItem": {
- "type": "object",
- "properties": {
- "annotations": {
- "description": "Providers can return structured annotation objects here (for example\ncitations from native tools), so keep the payload shape liberal.",
- "type": "array",
- "items": {
- "type": "object"
- }
- },
- "image_url": {
- "$ref": "#/definitions/core.ImageURLContent"
- },
- "input_audio": {
- "$ref": "#/definitions/core.InputAudioContent"
- },
- "text": {
- "type": "string"
- },
- "type": {
- "description": "\"output_text\", \"input_image\", \"input_audio\", etc.",
- "type": "string"
- }
- }
- },
- "core.ResponsesError": {
- "type": "object",
- "properties": {
- "code": {
- "type": "string"
- },
- "message": {
- "type": "string"
- }
- }
- },
- "core.ResponsesInputElement": {
- "type": "object",
- "properties": {
- "arguments": {
- "type": "string"
- },
- "call_id": {
- "description": "Function call fields (type=\"function_call\")",
- "type": "string"
- },
- "content": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/core.ContentPart"
- },
- "x-oneof": "[{\"type\":\"string\"},{\"type\":\"array\",\"items\":{\"$ref\":\"#/definitions/core.ContentPart\"}}]"
- },
- "name": {
- "type": "string"
- },
- "output": {
- "description": "Function call output fields (type=\"function_call_output\") — CallID shared above",
- "type": "string"
- },
- "role": {
- "description": "Message fields (type=\"\" or \"message\")",
- "type": "string"
- },
- "status": {
- "type": "string"
- },
- "type": {
- "description": "\"message\", \"function_call\", \"function_call_output\"",
- "type": "string"
- }
- }
- },
- "core.ResponsesOutputItem": {
- "type": "object",
- "properties": {
- "arguments": {
- "type": "string"
- },
- "call_id": {
- "type": "string"
- },
- "content": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/core.ResponsesContentItem"
- }
- },
- "id": {
- "type": "string"
- },
- "name": {
- "type": "string"
- },
- "role": {
- "type": "string"
- },
- "status": {
- "type": "string"
- },
- "type": {
- "description": "\"message\", \"function_call\", etc.",
- "type": "string"
- }
- }
- },
- "core.ResponsesRequest": {
- "type": "object",
- "properties": {
- "input": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/core.ResponsesInputElement"
- },
- "x-oneof": "[{\"type\":\"string\"},{\"type\":\"array\",\"items\":{\"$ref\":\"#/definitions/core.ResponsesInputElement\"}}]"
- },
- "instructions": {
- "type": "string"
- },
- "max_output_tokens": {
- "type": "integer"
- },
- "metadata": {
- "type": "object",
- "additionalProperties": {
- "type": "string"
- }
- },
- "model": {
- "type": "string"
- },
- "parallel_tool_calls": {
- "type": "boolean"
- },
- "provider": {
- "description": "Gateway routing hint; stripped before upstream execution.",
- "type": "string"
- },
- "reasoning": {
- "$ref": "#/definitions/core.Reasoning"
- },
- "stream": {
- "type": "boolean"
- },
- "stream_options": {
- "$ref": "#/definitions/core.StreamOptions"
- },
- "temperature": {
- "type": "number"
- },
- "tool_choice": {
- "description": "string or object"
- },
- "tools": {
- "type": "array",
- "items": {
- "type": "object",
- "additionalProperties": {}
- }
- }
- }
- },
- "core.ResponsesResponse": {
- "type": "object",
- "properties": {
- "created_at": {
- "type": "integer"
- },
- "error": {
- "$ref": "#/definitions/core.ResponsesError"
- },
- "id": {
- "type": "string"
- },
- "model": {
- "type": "string"
- },
- "object": {
- "description": "\"response\"",
- "type": "string"
- },
- "output": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/core.ResponsesOutputItem"
- }
- },
- "provider": {
- "type": "string"
- },
- "status": {
- "description": "\"completed\", \"failed\", \"in_progress\"",
- "type": "string"
- },
- "usage": {
- "$ref": "#/definitions/core.ResponsesUsage"
- }
- }
- },
- "core.ResponsesUsage": {
- "type": "object",
- "properties": {
- "completion_tokens_details": {
- "$ref": "#/definitions/core.CompletionTokensDetails"
- },
- "input_tokens": {
- "type": "integer"
- },
- "output_tokens": {
- "type": "integer"
- },
- "prompt_tokens_details": {
- "$ref": "#/definitions/core.PromptTokensDetails"
- },
- "raw_usage": {
- "type": "object",
- "additionalProperties": {}
- },
- "total_tokens": {
- "type": "integer"
- }
- }
- },
- "core.StreamOptions": {
- "type": "object",
- "properties": {
- "include_usage": {
- "description": "IncludeUsage requests token usage information in streaming responses.\nWhen true, the final streaming chunk will include usage statistics.",
- "type": "boolean"
- }
- }
- },
- "core.ToolCall": {
- "type": "object",
- "properties": {
- "function": {
- "$ref": "#/definitions/core.FunctionCall"
- },
- "id": {
- "type": "string"
- },
- "type": {
- "type": "string"
- }
- }
- },
- "core.Usage": {
- "type": "object",
- "properties": {
- "completion_tokens": {
- "type": "integer"
- },
- "completion_tokens_details": {
- "$ref": "#/definitions/core.CompletionTokensDetails"
- },
- "prompt_tokens": {
- "type": "integer"
- },
- "prompt_tokens_details": {
- "$ref": "#/definitions/core.PromptTokensDetails"
- },
- "raw_usage": {
- "type": "object",
- "additionalProperties": {}
- },
- "total_tokens": {
- "type": "integer"
- }
- }
- },
- "providers.CategoryCount": {
- "type": "object",
- "properties": {
- "category": {
- "$ref": "#/definitions/core.ModelCategory"
- },
- "count": {
- "type": "integer"
- },
- "display_name": {
- "type": "string"
- }
- }
- },
- "usage.CacheOverview": {
- "type": "object",
- "properties": {
- "daily": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/usage.CacheOverviewDaily"
- }
- },
- "summary": {
- "$ref": "#/definitions/usage.CacheOverviewSummary"
- }
- }
- },
- "usage.CacheOverviewDaily": {
- "type": "object",
- "properties": {
- "date": {
- "type": "string"
- },
- "exact_hits": {
- "type": "integer"
- },
- "hits": {
- "type": "integer"
- },
- "input_tokens": {
- "type": "integer"
- },
- "output_tokens": {
- "type": "integer"
- },
- "saved_cost": {
- "type": "number"
- },
- "semantic_hits": {
- "type": "integer"
- },
- "total_tokens": {
- "type": "integer"
- }
- }
- },
- "usage.CacheOverviewSummary": {
- "type": "object",
- "properties": {
- "exact_hits": {
- "type": "integer"
- },
- "semantic_hits": {
- "type": "integer"
- },
- "total_hits": {
- "type": "integer"
- },
- "total_input_tokens": {
- "type": "integer"
- },
- "total_output_tokens": {
- "type": "integer"
- },
- "total_saved_cost": {
- "type": "number"
- },
- "total_tokens": {
- "type": "integer"
- }
- }
- },
- "usage.DailyUsage": {
- "type": "object",
- "properties": {
- "date": {
- "type": "string"
- },
- "input_cost": {
- "type": "number"
- },
- "input_tokens": {
- "type": "integer"
- },
- "output_cost": {
- "type": "number"
- },
- "output_tokens": {
- "type": "integer"
- },
- "requests": {
- "type": "integer"
- },
- "total_cost": {
- "type": "number"
- },
- "total_tokens": {
- "type": "integer"
- }
- }
- },
- "usage.ModelUsage": {
- "type": "object",
- "properties": {
- "input_cost": {
- "type": "number"
- },
- "input_tokens": {
- "type": "integer"
- },
- "model": {
- "type": "string"
- },
- "output_cost": {
- "type": "number"
- },
- "output_tokens": {
- "type": "integer"
- },
- "provider": {
- "type": "string"
- },
- "provider_name": {
- "type": "string"
- },
- "total_cost": {
- "type": "number"
- }
- }
- },
- "usage.UsageLogEntry": {
- "type": "object",
- "properties": {
- "cache_type": {
- "type": "string"
- },
- "costs_calculation_caveat": {
- "type": "string"
- },
- "endpoint": {
- "type": "string"
- },
- "id": {
- "type": "string"
- },
- "input_cost": {
- "type": "number"
- },
- "input_tokens": {
- "type": "integer"
- },
- "model": {
- "type": "string"
- },
- "output_cost": {
- "type": "number"
- },
- "output_tokens": {
- "type": "integer"
- },
- "provider": {
- "type": "string"
- },
- "provider_id": {
- "type": "string"
- },
- "provider_name": {
- "type": "string"
- },
- "raw_data": {
- "type": "object",
- "additionalProperties": {}
- },
- "request_id": {
- "type": "string"
- },
- "timestamp": {
- "type": "string"
- },
- "total_cost": {
- "type": "number"
- },
- "total_tokens": {
- "type": "integer"
- },
- "user_path": {
- "type": "string"
- }
- }
- },
- "usage.UsageLogResult": {
- "type": "object",
- "properties": {
- "entries": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/usage.UsageLogEntry"
- }
- },
- "limit": {
- "type": "integer"
- },
- "offset": {
- "type": "integer"
- },
- "total": {
- "type": "integer"
- }
- }
- },
- "usage.UsageSummary": {
- "type": "object",
- "properties": {
- "total_cost": {
- "type": "number"
- },
- "total_input_cost": {
- "type": "number"
- },
- "total_input_tokens": {
- "type": "integer"
- },
- "total_output_cost": {
- "type": "number"
- },
- "total_output_tokens": {
- "type": "integer"
- },
- "total_requests": {
- "type": "integer"
- },
- "total_tokens": {
- "type": "integer"
- }
- }
- },
- "usage.UserPathUsage": {
- "type": "object",
- "properties": {
- "input_cost": {
- "type": "number",
- "x-nullable": true
- },
- "input_tokens": {
- "type": "integer"
- },
- "output_cost": {
- "type": "number",
- "x-nullable": true
- },
- "output_tokens": {
- "type": "integer"
- },
- "total_cost": {
- "type": "number",
- "x-nullable": true
- },
- "total_tokens": {
- "type": "integer"
- },
- "user_path": {
- "type": "string"
- }
- }
- }
- },
- "securityDefinitions": {
- "BearerAuth": {
- "type": "apiKey",
- "name": "Authorization",
- "in": "header"
- }
- }
-}
diff --git a/cmd/gomodel/main.go b/cmd/gomodel/main.go
index 3c3febe2..1e409b5f 100644
--- a/cmd/gomodel/main.go
+++ b/cmd/gomodel/main.go
@@ -69,7 +69,7 @@ func startApplication(application lifecycleApp, addr string) error {
return nil
}
-// @title GOModel API
+// @title GoModel API
// @version 1.0
// @description High-performance AI gateway routing requests to multiple LLM providers (OpenAI, Anthropic, Gemini, Groq, xAI, Oracle, Ollama). Drop-in OpenAI-compatible API.
// @BasePath /
diff --git a/config/config.example.yaml b/config/config.example.yaml
index 73b3e6e0..51eb0024 100644
--- a/config/config.example.yaml
+++ b/config/config.example.yaml
@@ -1,4 +1,4 @@
-# GOModel Configuration (optional)
+# GoModel Configuration (optional)
# Copy to config/config.yaml to customize.
# Environment variables always override values in this file.
# All settings have sensible defaults — no config file is required.
@@ -13,8 +13,8 @@ server:
enabled_passthrough_providers: ["openai", "anthropic"] # providers enabled on /p/{provider}/...
models:
- enabled_by_default: true # env: MODELS_ENABLED_BY_DEFAULT; when false and overrides are enabled, concrete models stay unavailable until an override allows one or more user paths
- overrides_enabled: false # env: MODEL_OVERRIDES_ENABLED; set true to load/enforce persisted model overrides and enable dashboard editing
+ enabled_by_default: true # env: MODELS_ENABLED_BY_DEFAULT; when false, models stay unavailable until an override allows one or more user paths
+ overrides_enabled: true # env: MODEL_OVERRIDES_ENABLED; load/enforce persisted model overrides and enable dashboard editing
cache:
model:
diff --git a/config/config.go b/config/config.go
index 73d854e6..b77387aa 100644
--- a/config/config.go
+++ b/config/config.go
@@ -131,18 +131,18 @@ type FallbackModelOverride struct {
// ModelsConfig holds global model access defaults.
type ModelsConfig struct {
- // EnabledByDefault controls whether concrete provider models are available
+ // EnabledByDefault controls whether provider models are available
// when no persisted user-path override exists and model overrides are enabled.
// Default: true.
EnabledByDefault bool `yaml:"enabled_by_default" env:"MODELS_ENABLED_BY_DEFAULT"`
// OverridesEnabled controls whether persisted model access overrides are
// loaded, enforced, and exposed through the admin dashboard/API.
- // Default: false.
+ // Default: true.
OverridesEnabled bool `yaml:"overrides_enabled" env:"MODEL_OVERRIDES_ENABLED"`
// KeepOnlyAliasesAtModelsEndpoint controls whether GET /v1/models hides
- // concrete provider models and returns only alias-projected model entries.
+ // provider models and returns only alias-projected model entries.
// Default: false.
KeepOnlyAliasesAtModelsEndpoint bool `yaml:"keep_only_aliases_at_models_endpoint" env:"KEEP_ONLY_ALIASES_AT_MODELS_ENDPOINT"`
}
@@ -884,7 +884,7 @@ func buildDefaultConfig() *Config {
},
Models: ModelsConfig{
EnabledByDefault: true,
- OverridesEnabled: false,
+ OverridesEnabled: true,
KeepOnlyAliasesAtModelsEndpoint: false,
},
Cache: CacheConfig{
diff --git a/config/config_test.go b/config/config_test.go
index 0a3e1677..d0aea5c7 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -169,8 +169,8 @@ func TestBuildDefaultConfig(t *testing.T) {
if !cfg.Models.EnabledByDefault {
t.Error("expected Models.EnabledByDefault=true")
}
- if cfg.Models.OverridesEnabled {
- t.Error("expected Models.OverridesEnabled=false")
+ if !cfg.Models.OverridesEnabled {
+ t.Error("expected Models.OverridesEnabled=true")
}
if cfg.Models.KeepOnlyAliasesAtModelsEndpoint {
t.Error("expected Models.KeepOnlyAliasesAtModelsEndpoint=false")
@@ -227,7 +227,7 @@ server:
pprof_enabled: true
models:
enabled_by_default: false
- overrides_enabled: true
+ overrides_enabled: false
keep_only_aliases_at_models_endpoint: true
cache:
model:
@@ -259,8 +259,8 @@ logging:
if cfg.Models.EnabledByDefault {
t.Error("expected Models.EnabledByDefault=false from YAML")
}
- if !cfg.Models.OverridesEnabled {
- t.Error("expected Models.OverridesEnabled=true from YAML")
+ if cfg.Models.OverridesEnabled {
+ t.Error("expected Models.OverridesEnabled=false from YAML")
}
if !cfg.Models.KeepOnlyAliasesAtModelsEndpoint {
t.Error("expected Models.KeepOnlyAliasesAtModelsEndpoint=true from YAML")
@@ -842,7 +842,7 @@ func TestLoad_EnvOverridesDefaults(t *testing.T) {
withTempDir(t, func(_ string) {
t.Setenv("PORT", "5555")
- t.Setenv("MODEL_OVERRIDES_ENABLED", "true")
+ t.Setenv("MODEL_OVERRIDES_ENABLED", "false")
t.Setenv("MODELS_ENABLED_BY_DEFAULT", "false")
t.Setenv("KEEP_ONLY_ALIASES_AT_MODELS_ENDPOINT", "true")
t.Setenv("STORAGE_TYPE", "postgresql")
@@ -858,8 +858,8 @@ func TestLoad_EnvOverridesDefaults(t *testing.T) {
if cfg.Server.Port != "5555" {
t.Errorf("expected port 5555, got %s", cfg.Server.Port)
}
- if !cfg.Models.OverridesEnabled {
- t.Error("expected model overrides to be enabled from env")
+ if cfg.Models.OverridesEnabled {
+ t.Error("expected model overrides to be disabled from env")
}
if cfg.Models.EnabledByDefault {
t.Error("expected models enabled-by-default to be disabled from env")
diff --git a/docs/2026-04-09_CODEBASE_SNAPSHOT.md b/docs/2026-04-09_CODEBASE_SNAPSHOT.md
new file mode 100644
index 00000000..a1cae339
--- /dev/null
+++ b/docs/2026-04-09_CODEBASE_SNAPSHOT.md
@@ -0,0 +1,1368 @@
+## `.` (Root Directory)
+
+It contains the repository configuration, build files, and dependency definitions.
+
+- **`.dockerignore`**: Excludes unnecessary files from the Docker build context.
+- **`.env.template`**: Template containing all environment variable configurations supported by the application.
+- **`.gitignore`**: Defines files and directories to be ignored by Git.
+- **`.golangci.yml`**: Configuration for the golangci-lint static analysis tool.
+- **`.goreleaser.yaml`**: Configuration for GoReleaser to automate application builds, packaging, and releases.
+- **`.pre-commit-config.yaml`**: Defines pre-commit hooks for code formatting, linting, and performance checks.
+- **`docker-compose.yaml`**: Orchestration file to run GoModel alongside Redis, PostgreSQL, and MongoDB locally.
+- **`Dockerfile`**: Multi-stage build instructions to compile and package the GoModel binary into a distroless container.
+- **`go.mod` / `go.sum`**: Go module dependencies and checksums.
+- **`LICENSE`**: MIT License file.
+- **`Makefile`**: Provides CLI commands for building, testing, linting, and running the application.
+- **`prometheus.yml`**: Configuration for Prometheus metric scraping.
+
+---
+
+## `./cmd/`
+
+It's responsible for the main entry points of the applications and CLI tools.
+
+### `./cmd/gomodel/main.go`
+
+It's the primary entry point for the GoModel API gateway.
+
+- `lifecycleApp`: Interface defining `Start` and `Shutdown` methods.
+- `shutdownApplication()`: Triggers and coordinates the graceful shutdown of the application.
+- `startApplication()`: Starts the application and handles immediate startup failures.
+- `main()`: Loads configurations, initializes logging, sets up providers (OpenAI, Anthropic, Gemini, etc.), starts the HTTP server, and listens for OS signals.
+
+### `./cmd/gomodel/logging.go`
+
+It configures the global `slog` instance for the application.
+
+- `configureLogging()`: Sets up the default logger based on the environment configuration.
+- `newLogHandler()`: Instantiates either a JSON or colored text log handler based on TTY presence and config.
+- `parseLogLevel()`: Parses string log levels (e.g., "debug", "info") into `slog.Level`.
+
+### `./cmd/recordapi/main.go`
+
+It's a CLI utility used to record real API responses from AI providers to generate golden files for contract tests.
+
+- `providerConfigs` / `endpointConfigs`: Static maps mapping provider requirements and endpoint payloads.
+- `endpointRequiresResponsesCapability()`, `providerSupportsResponses()`: Capability checking for the `/v1/responses` endpoint.
+- `main()`: Executes the HTTP request to the external provider and writes the output.
+- `adjustForAnthropic()`: Mutates OpenAI payloads to fit Anthropic's payload expectations.
+- `writeOutput()`, `writeStreamOutput()`: Helper functions to write JSON responses to the disk.
+
+---
+
+## `./config/`
+
+It's responsible for defining, parsing, and validating the application's configuration from YAML and environment variables.
+
+### `./config/config.go`
+
+It defines all configuration structures and the loading mechanism.
+
+- `Config`: The root configuration structure holding all module sub-configs.
+- `LoadResult`: Wrapper holding the parsed `Config` and the raw provider mappings.
+- `RawProviderConfig`, `RawResilienceConfig`, `RawCircuitBreakerConfig`, `RawRetryConfig`: Structs mapping to the YAML provider definitions.
+- `FallbackMode`: Typed string for fallback behaviors (`auto`, `manual`, `off`).
+- `FallbackModelOverride`, `ModelsConfig`, `FallbackConfig`, `AdminConfig`, `GuardrailsConfig`, `GuardrailRuleConfig`, `SystemPromptSettings`, `LLMBasedAlteringSettings`, `HTTPConfig`, `ExecutionPlansConfig`, `LogConfig`, `UsageConfig`, `StorageConfig`, `SQLiteStorageConfig`, `PostgreSQLStorageConfig`, `MongoDBStorageConfig`, `CacheConfig`, `ModelCacheConfig`, `LocalCacheConfig`, `ModelListConfig`, `RedisModelConfig`, `RedisResponseConfig`, `ResponseCacheConfig`, `SimpleCacheConfig`, `SemanticCacheConfig`, `EmbedderConfig`, `VectorStoreConfig`, `QdrantConfig`, `PGVectorConfig`, `PineconeConfig`, `WeaviateConfig`, `ServerConfig`, `MetricsConfig`, `RetryConfig`, `CircuitBreakerConfig`, `ResilienceConfig`: Configuration domain structures.
+- `ResolveFallbackDefaultMode()`: Normalizes the fallback mode.
+- `ValidateCacheConfig()`, `SimpleCacheEnabled()`, `SemanticCacheActive()`: Validation and state checks for caching configurations.
+- `applyResponseSimpleEnv()`, `applyResponseSemanticEnv()`: Overrides cache settings from environment variables.
+- `buildDefaultConfig()`: Returns a `Config` initialized with default values.
+- `Load()`: Loads `config.yaml` and applies environment variable overrides.
+- `applyYAML()`: Reads and unmarshals the YAML file.
+- `loadFallbackConfig()`: Loads the external JSON file defining manual fallback rules.
+- `applyEnvOverrides()`, `hasEnvDescendants()`, `applyEnvOverridesValue()`: Reflection-based environment variable injection mapped by `env` tags.
+- `expandString()`, `parseBool()`: Utility parsers.
+- `ValidateBodySizeLimit()`, `ParseBodySizeLimitBytes()`: Parses strings like "10M" into byte integers.
+
+---
+
+## `./internal/admin/`
+
+It's responsible for the Admin REST API logic and dashboard data provisioning.
+
+### `./internal/admin/handler.go`
+
+It maps HTTP routes to the underlying administrative services (Usage, Audit, Models, Guardrails, Aliases).
+
+- `Handler`: The main admin controller struct holding references to readers and services.
+- `Option`: Functional option type for configuring the `Handler`.
+- `DashboardConfigResponse`: Struct defining the runtime configuration returned to the UI.
+- `WithAuditReader()`, `WithAliases()`, `WithAuthKeys()`, `WithModelOverrides()`, `WithExecutionPlans()`, `WithGuardrailsRegistry()`, `WithGuardrailService()`, `WithDashboardRuntimeConfig()`: Configurator functions.
+- `NewHandler()`: Initializes the admin API controller.
+- `normalizeDashboardRuntimeConfig()`, `cloneDashboardRuntimeConfig()`: Config helpers.
+- `parseUsageParams()`, `normalizeUserPathQueryParam()`, `parseDateRangeParams()`, `dashboardTimeZone()`: Extracts and formats dashboard query parameters.
+- `handleError()`: Converts gateway errors into HTTP JSON responses.
+- `UsageSummary()`, `usageSliceResponse()`, `DailyUsage()`, `UsageByModel()`, `UsageLog()`, `CacheOverview()`: API endpoints for analytics.
+- `AuditLog()`, `AuditConversation()`: API endpoints for audit logs.
+- `modelAccessResponse`, `modelInventoryResponse`: Structs for model catalog representation.
+- `ListModels()`, `ListCategories()`, `DashboardConfig()`: Endpoints for model management state.
+- `upsertAliasRequest`, `upsertModelOverrideRequest`, `upsertGuardrailRequest`, `createExecutionPlanRequest`, `createAuthKeyRequest`: Request body structures.
+- `featureUnavailableError()`, `aliasesUnavailableError()`, `modelOverridesUnavailableError()`, `authKeysUnavailableError()`, `guardrailsUnavailableError()`, `executionPlansUnavailableError()`: Feature gating errors.
+- `aliasWriteError()`, `modelOverrideWriteError()`, `executionPlanWriteError()`, `authKeyWriteError()`, `guardrailWriteError()`: Error formatting helpers.
+- `deactivateByID()`, `deleteByName()`: Generic deletion HTTP handlers.
+- `ListModelOverrides()`, `UpsertModelOverride()`, `DeleteModelOverride()`: Model override management endpoints.
+- `ListAuthKeys()`, `CreateAuthKey()`, `DeactivateAuthKey()`: API key management endpoints.
+- `ListAliases()`, `UpsertAlias()`, `DeleteAlias()`: Alias management endpoints.
+- `ListGuardrailTypes()`, `ListGuardrails()`, `UpsertGuardrail()`, `DeleteGuardrail()`: Guardrail management endpoints.
+- `ListExecutionPlans()`, `GetExecutionPlan()`, `ListExecutionPlanGuardrails()`, `CreateExecutionPlan()`, `DeactivateExecutionPlan()`: Workflow execution plan management endpoints.
+- `refreshExecutionPlansAfterGuardrailChange()`, `activeWorkflowGuardrailReferences()`, `validateExecutionPlanGuardrails()`, `validateExecutionPlanScope()`: Business logic validators.
+- `decodeAliasPathName()`, `decodeModelOverridePathSelector()`: Decodes URL-encoded identifiers.
+
+---
+
+## `./internal/admin/dashboard/`
+
+It's responsible for compiling and serving the embedded web dashboard UI.
+
+### `./internal/admin/dashboard/dashboard.go`
+
+It loads and serves the HTML templates and static web assets.
+
+- `Handler`: Struct holding the parsed `html/template` and the HTTP static file server.
+- `New()`: Loads embedded files and generates asset hashes.
+- `Index()`: Serves the `index.html` layout.
+- `Static()`: Serves CSS, JS, and SVG files.
+- `buildAssetVersions()`, `assetURL()`: Appends cache-busting `?v=hash` query parameters to static asset URLs.
+
+### `./internal/admin/dashboard/static/js/dashboard.js`
+
+It initializes the Alpine.js frontend application.
+
+- `dashboard()`: The main Alpine.js data object. It merges state, handles URL routing (`_parseRoute`, `_applyRoute`, `navigate`), handles UI themes (`applyTheme`, `setTheme`), and fetches global data (`fetchAll`).
+
+### `./internal/admin/dashboard/static/js/modules/`
+
+Contains modular frontend business logic for the Alpine.js UI.
+
+- `aliases.js`: `dashboardAliasesModule()` handles filtering, listing, and saving model aliases and model access overrides.
+- `audit-list.js`: `dashboardAuditListModule()` handles fetching, filtering, and displaying audit logs and request/response JSON viewer panes.
+- `auth-keys.js`: `dashboardAuthKeysModule()` handles creating, displaying, copying, and deactivating managed API keys.
+- `charts.js`: `dashboardChartsModule()` manages Chart.js instance rendering for the usage over time and model bar charts.
+- `clipboard.js`: `fallbackClipboardModule()` provides a cross-browser text-to-clipboard utility and UI state manager (`createClipboardButtonState`).
+- `contribution-calendar.js`: `dashboardContributionCalendarModule()` renders the GitHub-style contribution grid for usage activity.
+- `conversation-drawer.js`: `dashboardConversationDrawerModule()` fetches and reconstructs chat threads from audit logs to render the sliding conversation UI.
+- `conversation-helpers.js`: Parses complex JSON arrays/objects into clean text payloads for the UI.
+- `date-picker.js`: `dashboardDatePickerModule()` handles the custom date range dropdown logic.
+- `execution-plans.js`: `dashboardExecutionPlansModule()` handles the workflow builder UI, node connections, execution previews, and saving plans.
+- `guardrails.js`: `dashboardGuardrailsModule()` manages the dynamic forms for authoring system prompt and LLM-altering policies.
+- `timezone.js`: `dashboardTimezoneModule()` detects the browser's timezone, manages local storage overrides, and formats timestamps across the UI.
+- `usage.js`: `dashboardUsageModule()` handles fetching and rendering the semantic cache analytics, daily tokens, and model cost statistics.
+
+### `./internal/admin/dashboard/templates/`
+
+Contains the Go HTML templates for the dashboard structure.
+
+- `audit-pane.html`: Template for the Request/Response JSON viewers.
+- `auth-banner.html`: Banner showing missing API key warnings.
+- `date-picker.html`: The interactive calendar date picker dropdown.
+- `edit-icon.html`, `x-icon.html`: SVG icon fragments.
+- `execution-plan-chart.html`: The visual node-based workflow layout.
+- `helper-disclosure.html`: The inline tooltip/help text component.
+- `index.html`: The main content panes for all dashboard routes.
+- `layout.html`: The root HTML shell, sidebar navigation, and script tags.
+- `pagination.html`: Reusable pagination controls.
+
+---
+
+## `./internal/aliases/`
+
+It's responsible for managing model aliases (e.g., routing `smart-model` to `openai/gpt-4o`).
+
+### `./internal/aliases/batch_preparer.go`
+
+It intercepts batch processing to rewrite custom alias names to their target model names inside batch JSONL files.
+
+- `BatchPreparer`: Wraps a provider and alias service.
+- `NewBatchPreparer()`: Constructor.
+- `PrepareBatchRequest()`: Rewrites the top-level batch request and JSONL file contents.
+- `batchFileTransport()`: Extracts the native file provider.
+- `aliasModelSupportChecker`, `aliasModelProviderTypeChecker`: Interfaces for validating models.
+- `resolveAliasModel()`, `resolveAliasRequestSelector()`, `resolveAliasRoutableSelector()`, `validateResolvedProviderType()`: Resolution logic.
+- `rewriteAliasChatRequest()`, `rewriteAliasResponsesRequest()`, `rewriteAliasEmbeddingRequest()`: Replaces models inside individual payload types.
+- `rewriteAliasBatchSource()`: Traverses the batch JSONL and mutates known endpoints.
+
+### `./internal/aliases/factory.go`
+
+It sets up the aliases module dependencies.
+
+- `Result`: Holds the initialized Service and Store.
+- `Close()`: Shuts down the background refresher and DB connections.
+- `New()`, `NewWithSharedStorage()`, `newResult()`, `createStore()`: Bootstraps the service and DB implementations.
+
+### `./internal/aliases/provider.go`
+
+It wraps `core.RoutableProvider` to intercept streaming and synchronous requests to resolve aliases before sending them upstream.
+
+- `Provider`: Middleware provider implementation.
+- `requestRewriteMode`, `Options`: Configuration for the provider behavior.
+- `NewProvider()`, `NewProviderWithOptions()`: Constructors.
+- `ResolveModel()`: Translates the model selector.
+- `ChatCompletion()`, `StreamChatCompletion()`, `Responses()`, `StreamResponses()`, `Embeddings()`: Request interceptors.
+- `ListModels()`, `Supports()`, `GetProviderType()`, `GetProviderName()`, `ModelCount()`, `NativeFileProviderTypes()`: Registry bypass methods.
+- `CreateBatch()`, `GetBatch()`, `ListBatches()`, `CancelBatch()`, `GetBatchResults()`, `CreateBatchWithHints()`, `GetBatchResultsWithHints()`, `ClearBatchResultHints()`: Batch delegation methods.
+- `CreateFile()`, `ListFiles()`, `GetFile()`, `DeleteFile()`, `GetFileContent()`: File delegation methods.
+- `Passthrough()`, `PrepareBatchRequest()`: Passthrough delegation.
+- `recordBatchPreparation()`, `cleanupSupersededBatchRewriteFile()`, `cleanupBatchRewriteFile()`, `mergeBatchHints()`, `providerValueForMode()`, `nativeBatchRouter()`, `nativeBatchHintRouter()`, `nativeFileRouter()`, `batchFileTransport()`, `passthroughRouter()`: Helper utilities for batch execution.
+
+### `./internal/aliases/service.go`
+
+It holds the central business logic and active memory snapshot of aliases.
+
+- `Catalog`: Interface abstracting the underlying provider registry.
+- `snapshot`: The in-memory struct holding mapped aliases.
+- `Service`: Core logic controller.
+- `NewService()`: Constructor.
+- `Refresh()`: Reads all aliases from the DB and updates the snapshot.
+- `List()`, `ListViews()`, `Get()`: Retrieves aliases.
+- `Resolve()`, `resolveRequested()`, `ResolveModel()`: Translates an alias to its target.
+- `Supports()`, `GetProviderType()`, `ExposedModels()`, `ExposedModelsFiltered()`, `exposedModelsFiltered()`: Integrates aliases into the public model inventory.
+- `Upsert()`, `Delete()`, `validate()`: Modifies aliases in the database.
+- `resolveAlias()`, `StartBackgroundRefresh()`: Resolution internals and worker loop.
+
+### `./internal/aliases/store.go`
+
+It defines the database interfaces and normalization logic.
+
+- `ValidationError`: Struct for data constraint errors.
+- `IsValidationError()`, `newValidationError()`: Error helpers.
+- `Store`: The CRUD interface for aliases.
+- `aliasScanner`, `aliasRows`: Database agnostic interfaces.
+- `normalizeName()`, `normalizeAlias()`, `collectAliases()`: Formats inputs before DB insertion.
+
+### `./internal/aliases/store_mongodb.go`
+
+- `mongoAliasDocument`, `mongoAliasIDFilter`: BSON definitions.
+- `MongoDBStore`: MongoDB implementation of `Store`.
+- `NewMongoDBStore()`, `List()`, `Get()`, `Upsert()`, `Delete()`, `Close()`, `aliasFromMongo()`: CRUD methods.
+
+### `./internal/aliases/store_postgresql.go`
+
+- `PostgreSQLStore`: PostgreSQL implementation of `Store`.
+- `NewPostgreSQLStore()`, `List()`, `Get()`, `Upsert()`, `Delete()`, `Close()`, `scanPostgreSQLAlias()`: CRUD methods.
+
+### `./internal/aliases/store_sqlite.go`
+
+- `SQLiteStore`: SQLite implementation of `Store`.
+- `NewSQLiteStore()`, `List()`, `Get()`, `Upsert()`, `Delete()`, `Close()`, `scanSQLiteAlias()`, `boolToSQLite()`: CRUD methods.
+
+### `./internal/aliases/types.go`
+
+- `Alias`: The domain object for a model alias.
+- `TargetSelector()`: Returns a parsed target selector.
+- `Resolution`: Struct containing the requested name, the resolved concrete name, and the alias applied.
+- `View`: Struct extending an Alias with runtime availability data for the UI.
+
+---
+
+## `./internal/app/`
+
+It manages the high-level application container wiring and shutdown sequences.
+
+### `./internal/app/app.go`
+
+- `App`: Main application struct containing pointers to providers, DBs, loggers, caches, and the HTTP server.
+- `Config`: App initialization arguments.
+- `New()`: Bootstraps and injects dependencies across storage, cache, usage, audit, guardrails, execution plans, and routing.
+- `Router()`, `AuditLogger()`, `UsageLogger()`, `providerAsNativeFileRouter()`: Exposes components for testing and internal routing.
+- `Start()`: Begins listening for HTTP traffic.
+- `Shutdown()`: Coordinates stopping background refresh loops, closing HTTP connections, flushing caches, and closing database connections.
+- `logStartupInfo()`, `initAdmin()`, `configGuardrailDefinitions()`, `defaultExecutionPlanInput()`, `dashboardRuntimeConfig()`, `cacheAnalyticsConfigured()`, `dashboardEnabledValue()`, `dashboardFallbackModeValue()`, `runtimeExecutionFeatureCaps()`, `executionPlanRefreshInterval()`, `responseCacheConfigured()`, `simpleResponseCacheConfigured()`, `simpleResponseCacheConfiguredFromResponse()`, `semanticResponseCacheConfigured()`, `semanticResponseCacheConfiguredFromResponse()`, `fallbackFeatureEnabledGlobally()`, `fallbackModeEnabled()`, `firstSharedStorage()`: Startup configuration and logging helpers.
+
+---
+
+## `./internal/auditlog/`
+
+It's responsible for capturing, formatting, and storing records of all LLM traffic.
+
+### `./internal/auditlog/auditlog.go`
+
+- `LogStore`: Interface for database bulk writers.
+- `LogEntry`: The top-level struct representing an HTTP request execution.
+- `LogData`: Detailed JSON payload representing headers, requests, responses, errors, and metadata.
+- `ExecutionFeaturesSnapshot`: Embedded representation of the workflow capabilities triggered by the request.
+- `marshalLogData()`, `normalizeCacheType()`, `displayAuditProviderName()`: Formatters.
+- `RedactedHeaders`, `redactedHeadersSet`, `RedactHeaders()`: Cleanses sensitive HTTP headers (e.g., API keys, cookies) before logging.
+- `Config`, `DefaultConfig()`: Audit logger settings.
+
+### `./internal/auditlog/cleanup.go`
+
+- `CleanupInterval`: Constant representing the interval (1 Hour).
+- `RunCleanupLoop()`: Triggers the DB deletion routine to prune logs past their retention policy.
+
+### `./internal/auditlog/constants.go`
+
+- `MaxBodyCapture`, `MaxContentCapture`, `BatchFlushThreshold`, `APIKeyHashPrefixLength`: Tuning constants.
+- `LogEntryKey`, `LogEntryStreamingKey`: Request context keys for accessing the current entry in middleware.
+
+### `./internal/auditlog/conversation_helpers.go`
+
+- `entryLookup`: Function type for querying DB records.
+- `buildConversationThread()`: Reconstructs a full chat history backwards and forwards by following `previous_response_id` links.
+- `extractResponseID()`, `extractPreviousResponseID()`, `extractStringField()`, `extractTrimmedString()`, `clampConversationLimit()`: Extraction helpers mapping over generic JSON shapes.
+
+### `./internal/auditlog/entry_capture.go`
+
+- `PopulateRequestData()`: Reads HTTP headers and the captured snapshot body into the `LogEntry`.
+- `PopulateResponseHeaders()`, `PopulateResponseData()`: Extracts HTTP response properties.
+- `CaptureInternalJSONExchange()`: Bypasses the HTTP stack to directly inject internal service calls (e.g., synthetic guardrail requests) into the audit log.
+- `ensureLogData()`, `requestIDForEntry()`, `internalJSONAuditRequest()`, `internalJSONAuditRequestBody()`, `internalJSONAuditResponse()`, `internalJSONAuditHeaders()`, `boundedAuditBody()`: Extraction internals preventing out-of-memory errors on massive payloads.
+
+### `./internal/auditlog/factory.go`
+
+- `Result`: Container for the initialized Logger and Storage.
+- `Close()`: Lifecycle method.
+- `New()`, `createLogStore()`, `buildLoggerConfig()`: Dependency injection initializers.
+
+### `./internal/auditlog/logger.go`
+
+- `Logger`: The background worker that buffers logs and flushes them efficiently.
+- `NewLogger()`: Constructor.
+- `Write()`: Enqueues an entry (or drops it if the buffer is overflowing to prevent gateway lag).
+- `Config()`, `Close()`: Lifecycle methods.
+- `flushLoop()`, `flushBatch()`: Goroutine executing the DB writes.
+- `NoopLogger`, `LoggerInterface`: Abstraction for environments where audit logging is disabled.
+
+### `./internal/auditlog/middleware.go`
+
+- `Middleware()`: The primary Echo HTTP interceptor that initializes the log timer and injects the `responseBodyCapture`.
+- `applyExecutionPlan()`, `applyAuthentication()`, `enrichEntryWithExecutionPlan()`, `resolvedModelForAuditLog()`, `captureLoggedRequestBody()`, `captureLoggedResponseBody()`, `captureLoggedBody()`: Enhancers for the log entry before persistence.
+- `responseBodyCapture`: Custom HTTP ResponseWriter that duplicates written bytes into a memory buffer (up to `MaxBodyCapture`).
+- `captureEnabled()`, `Flush()`, `Hijack()`, `Unwrap()`, `shouldCaptureResponseBody()`, `isEventStreamContentType()`: ResponseWriter method overrides.
+- `extractHeaders()`, `hashAPIKey()`: Security formatting.
+- `EnrichEntry()`, `EnrichEntryWithExecutionPlan()`, `EnrichLogEntryWithExecutionPlan()`, `EnrichEntryWithResolvedRoute()`, `EnrichLogEntryWithResolvedRoute()`, `enrichEntryWithResolvedRoute()`, `EnrichEntryWithCacheType()`, `EnrichEntryWithAuthMethod()`, `EnrichEntryWithAuthKeyID()`, `EnrichEntryWithUserPath()`, `EnrichLogEntryWithRequestContext()`, `auditEnabledForContext()`, `EnrichEntryWithError()`, `EnrichEntryWithStream()`, `toValidUTF8String()`, `decompressBody()`: A suite of context injection and enrichment tools used by downstream services to tag the current log entry.
+
+### `./internal/auditlog/reader.go`
+
+- `QueryParams`, `LogQueryParams`: Structures defining the search criteria and pagination logic.
+- `LogListResult`, `ConversationResult`: Structures defining the output shape for the UI.
+- `Reader`: Interface for executing queries.
+
+### `./internal/auditlog/reader_factory.go`
+
+- `NewReader()`: Dispatches creation to the active database implementation.
+
+### `./internal/auditlog/reader_helpers.go`
+
+- `buildWhereClause()`, `escapeLikeWildcards()`, `clampLimitOffset()`: Query construction helpers.
+
+### `./internal/auditlog/reader_mongodb.go`
+
+- `MongoDBReader`, `mongoLogRow`: Implementations of `Reader`.
+- `toLogEntry()`, `sanitizeLogData()`: BSON to Domain converters.
+- `NewMongoDBReader()`, `mongoUserPathMatchFilter()`, `GetLogs()`, `firstNonEmpty()`, `GetLogByID()`, `GetConversation()`, `mongoDateRangeFilter()`, `findByResponseID()`, `findByPreviousResponseID()`, `findFirstByField()`: Query methods.
+
+### `./internal/auditlog/reader_postgresql.go`
+
+- `PostgreSQLReader`: Implementation of `Reader`.
+- `NewPostgreSQLReader()`, `GetLogs()`, `GetLogByID()`, `GetConversation()`, `pgDateRangeConditions()`, `findByResponseID()`, `findByPreviousResponseID()`, `scanPostgreSQLLogEntry()`: Query methods.
+
+### `./internal/auditlog/reader_sqlite.go`
+
+- `SQLiteReader`: Implementation of `Reader`.
+- `NewSQLiteReader()`, `GetLogs()`, `GetLogByID()`, `GetConversation()`, `sqliteDateRangeConditions()`, `sqliteTimestampBoundary()`, `parseSQLTimestamp()`, `findByResponseID()`, `findByPreviousResponseID()`, `scanSQLiteLogEntry()`: Query methods.
+
+### `./internal/auditlog/store_mongodb.go`
+
+- `ErrPartialWrite`, `PartialWriteError`, `auditLogPartialWriteFailures`: Metric and error handling for bulk writes.
+- `MongoDBStore`: Implementation of `LogStore`.
+- `NewMongoDBStore()`, `WriteBatch()`, `Flush()`, `Close()`: Write operations.
+
+### `./internal/auditlog/store_postgresql.go`
+
+- `auditLogBatchExecutor`: Interface abstracting transactions.
+- `PostgreSQLStore`: Implementation of `LogStore`.
+- `NewPostgreSQLStore()`, `WriteBatch()`, `writeBatchSmall()`, `writeBatchLarge()`, `writeAuditLogInsertChunks()`, `buildAuditLogInsert()`, `renamePostgreSQLAuditColumn()`, `postgresqlColumnExists()`, `Flush()`, `Close()`, `cleanup()`: Write and lifecycle operations.
+
+### `./internal/auditlog/store_sqlite.go`
+
+- `SQLiteStore`: Implementation of `LogStore`.
+- `NewSQLiteStore()`, `WriteBatch()`, `Flush()`, `Close()`, `cleanup()`, `renameSQLiteAuditColumn()`, `sqliteColumnExists()`: Write and lifecycle operations.
+
+### `./internal/auditlog/stream_observer.go`
+
+- `StreamLogObserver`: Hooks into the `streaming.ObservedSSEStream` to accumulate fragments of SSE data.
+- `NewStreamLogObserver()`, `OnJSONEvent()`, `OnStreamClose()`, `parseChatCompletionEvent()`, `parseResponsesAPIEvent()`, `appendContent()`: Event triggers that aggregate chunks and save them to the final DB entry.
+
+### `./internal/auditlog/stream_wrapper.go`
+
+- `streamResponseBuilder`: Maintains state while reconstructing a full JSON response from SSE chunks.
+- `buildChatCompletionResponse()`, `buildResponsesAPIResponse()`: Reconstructs standard formats.
+- `CreateStreamEntry()`, `copyMap()`, `GetStreamEntryFromContext()`, `MarkEntryAsStreaming()`, `IsEntryMarkedAsStreaming()`: Utility functions for forking log entries to handle async stream closing.
+
+### `./internal/auditlog/user_path_filter.go`
+
+- `normalizeAuditUserPathFilter()`, `auditUserPathSubtreePattern()`, `auditUserPathSQLPredicate()`, `auditUserPathSubtreeRegex()`: Translates `/team/alpha` into DB-specific LIKE/Regex filters mapping descendants.
+
+---
+
+## `./internal/authkeys/`
+
+It's responsible for managing and validating API keys.
+
+### `./internal/authkeys/factory.go`
+
+- `Result`: Lifecycle wrapper.
+- `Close()`, `New()`, `NewWithSharedStorage()`, `newResult()`, `createStore()`: Dependency injection.
+
+### `./internal/authkeys/service.go`
+
+- `snapshot`, `AuthenticationResult`: In-memory state and validation returns.
+- `Service`: Core logic manager.
+- `NewService()`, `Refresh()`, `Enabled()`, `Total()`, `ActiveCount()`, `ListViews()`: State queries.
+- `Create()`: Issues a new API key with randomized entropy.
+- `Deactivate()`: Permanently disables a key.
+- `Authenticate()`: Extremely fast validation checking incoming bearer tokens against the cached snapshot via SHA256.
+- `StartBackgroundRefresh()`, `authenticateKey()`, `refreshBestEffort()`, `applyUpsert()`, `applyDeactivate()`, `cloneSnapshot()`, `sortSnapshotOrder()`: Cache logic.
+- `generateTokenMaterial()`, `parseTokenSecret()`, `hashSecret()`, `redactTokenValue()`: Cryptography helpers (generates `sk_gom_...` tokens).
+
+### `./internal/authkeys/store.go`
+
+- `ValidationError`, `IsValidationError()`, `newValidationError()`, `ErrNotFound`, `ErrInvalidToken`, `ErrInactive`, `ErrExpired`: Core errors.
+- `Store`, `authKeyScanner`, `authKeyRows`: Interfaces for DB operations.
+- `normalizeCreateInput()`, `normalizeID()`, `collectAuthKeys()`: Validation and parsing.
+
+### `./internal/authkeys/store_mongodb.go`
+
+- `mongoAuthKeyDocument`, `mongoAuthKeyIDFilter`: BSON definitions.
+- `MongoDBStore`: Implementation of `Store`.
+- `NewMongoDBStore()`, `List()`, `Create()`, `Deactivate()`, `Close()`, `authKeyFromMongo()`, `timePtrUTC()`: DB operations.
+
+### `./internal/authkeys/store_postgresql.go`
+
+- `PostgreSQLStore`: Implementation of `Store`.
+- `NewPostgreSQLStore()`, `List()`, `Create()`, `Deactivate()`, `Close()`, `scanPostgreSQLAuthKey()`, `pgUnixOrNil()`, `int64PtrToTime()`, `pgNullableString()`, `derefTrimmedString()`: DB operations.
+
+### `./internal/authkeys/store_sqlite.go`
+
+- `SQLiteStore`: Implementation of `Store`.
+- `NewSQLiteStore()`, `List()`, `Create()`, `Deactivate()`, `Close()`, `scanSQLiteAuthKey()`, `isSQLiteDuplicateColumnError()`, `boolToSQLite()`, `unixOrNil()`, `unixPtr()`, `nullableString()`, `nullableStringValue()`: DB operations.
+
+### `./internal/authkeys/types.go`
+
+- `AuthKey`, `View`, `IssuedKey`, `CreateInput`: Domain models mapping DB rows to JSON and internal logic.
+- `Active()`: Domain logic determining if an `AuthKey` is currently valid.
+
+---
+
+## `./internal/batch/`
+
+It tracks asynchronous offline jobs (OpenAI Native Batches) generated by gateway clients.
+
+### `./internal/batch/factory.go`
+
+- `Result`, `Close()`, `New()`, `NewWithSharedStorage()`, `createStore()`: Standard DI lifecycle.
+
+### `./internal/batch/store.go`
+
+- `StoredBatch`: Domain entity wrapping `core.BatchResponse` with routing metadata.
+- `Store`: Interface for persistence.
+- `normalizeLimit()`, `cloneBatch()`, `serializeBatch()`, `deserializeBatch()`, `normalizeStoredBatch()`, `splitGatewayBatchMetadata()`, `parseUsageLoggedAt()`, `EffectiveUsageEnabled()`: SerDe utilities.
+
+### `./internal/batch/store_memory.go`
+
+- `MemoryStore`: In-memory implementation of `Store` (used when running without a persistent DB).
+- `NewMemoryStore()`, `Create()`, `Get()`, `List()`, `Update()`, `Close()`: Map-backed operations.
+
+### `./internal/batch/store_mongodb.go`
+
+- `mongoBatchDocument`, `MongoDBStore`: BSON definitions and DB implementation.
+- `NewMongoDBStore()`, `Create()`, `Get()`, `List()`, `Update()`, `Close()`: DB operations.
+
+### `./internal/batch/store_postgresql.go`
+
+- `PostgreSQLStore`: DB implementation.
+- `NewPostgreSQLStore()`, `Create()`, `Get()`, `List()`, `Update()`, `Close()`: DB operations.
+
+### `./internal/batch/store_sqlite.go`
+
+- `SQLiteStore`: DB implementation.
+- `NewSQLiteStore()`, `Create()`, `Get()`, `List()`, `Update()`, `Close()`: DB operations.
+
+---
+
+## `./internal/cache/`
+
+It provides general-purpose caching interfaces.
+
+### `./internal/cache/redis.go`
+
+- `RedisStoreConfig`, `RedisStore`: Implements basic caching via Redis.
+- `NewRedisStore()`, `Get()`, `Set()`, `Close()`: Wraps go-redis.
+
+### `./internal/cache/store.go`
+
+- `Store`: Generic interface.
+- `MapStore`: Thread-safe map implementation.
+- `NewMapStore()`, `Get()`, `Set()`, `Close()`: In-memory generic operations.
+
+### `./internal/cache/modelcache/local.go`
+
+- `LocalCache`: Caches the massive AI Model Registry JSON to a local disk file to survive restarts.
+- `NewLocalCache()`, `Get()`, `Set()`, `Close()`: File I/O operations (atomic rename).
+
+### `./internal/cache/modelcache/modelcache.go`
+
+- `ModelCache`, `CachedProvider`, `CachedModel`: Internal structs representing the cached representation of the model registry.
+- `Cache`: Interface for the specialized model caching.
+
+### `./internal/cache/modelcache/redis.go`
+
+- `RedisModelCacheConfig`, `redisModelCache`: Caches the model registry JSON to Redis.
+- `NewRedisModelCache()`, `NewRedisModelCacheWithStore()`, `Get()`, `Set()`, `Close()`: Implementation bridging `ModelCache` domain models into the Redis store.
+
+---
+
+## `./internal/core/`
+
+It holds the foundational domain representations, logic interfaces, and data structures.
+
+### `./internal/core/batch.go`
+
+- `BatchRequest`, `BatchRouteInfo`, `BatchRequestItem`, `BatchResponse`, `BatchListResponse`, `BatchResultsResponse`, `BatchRequestCounts`, `BatchResultItem`, `BatchError`, `BatchUsageSummary`: Deep mappings of the OpenAI Batch API schemas.
+
+### `./internal/core/batch_json.go`
+
+- Custom `MarshalJSON` / `UnmarshalJSON` for Batch structs that dynamically captures `UnknownJSONFields` to ensure undocumented provider properties are never stripped from the payload when routing.
+
+### `./internal/core/batch_preparation.go`
+
+- `BatchPreparationMetadata`: Tracks context about dynamically rewritten file IDs.
+- `BatchFileTransport`, `BatchItemRewriteFunc`, `BatchRewriteResult`: Interfaces for the batch mutation process.
+- `RewriteBatchSource()`, `rewriteInlineBatchItems()`, `rewriteInputFileBatch()`, `rewriteBatchJSONLContent()`, `wrapBatchInputFileLineError()`, `recordBatchEndpointHint()`, `mergeBatchEndpointHints()`, `cloneBatchRequest()`, `cloneBatchRequestItem()`, `cloneBatchStringMap()`, `jsonSemanticallyEqual()`, `reflectDeepEqualJSON()`: High-level logic that downloads a JSONL file, parses it line by line, resolves aliases/guardrails on individual lines, re-uploads it as a new file to the provider, and updates the batch.
+
+### `./internal/core/batch_semantic.go`
+
+- `DecodedBatchItemRequest`, `DecodedBatchItemHandlers`: Provides a type-safe way to unpack the nested strings in a `.jsonl` payload into strong `ChatRequest`/`ResponsesRequest` instances.
+- `ChatRequest()`, `ResponsesRequest()`, `EmbeddingRequest()`, `ModelSelector()`, `RequestedModelSelector()`, `DispatchDecodedBatchItem()`, `NormalizeOperationPath()`, `ResolveBatchItemEndpoint()`, `MaybeDecodeKnownBatchItemRequest()`, `DecodeKnownBatchItemRequest()`, `BatchItemModelSelector()`, `BatchItemRequestedModelSelector()`: Converters interpreting endpoint URLs into schemas.
+
+### `./internal/core/chat_content.go`
+
+- `ContentPart`, `ImageURLContent`, `InputAudioContent`: Represents standard multimodal message parts.
+- `UnmarshalJSON`, `MarshalJSON`: Custom JSON handlers supporting OpenAI schemas.
+- `UnmarshalMessageContent()`, `NormalizeMessageContent()`, `ExtractTextContent()`, `HasStructuredContent()`, `HasNonTextContent()`, `NormalizeContentParts()`, `joinTextParts()`, `partsText()`, `interfacePartsText()`, `unmarshalContentPart()`, `normalizeTypedContentPart()`, `normalizeContentPartValue()`, `normalizeContentPartMap()`, `unmarshalImageURLContent()`, `unmarshalInputAudioContent()`: Extensive parsing utilities to handle cases where `content` can be a string, an array of objects, or `null`.
+
+### `./internal/core/chat_json.go`
+
+- Custom JSON serialization preserving unknown properties for `ChatRequest`.
+
+### `./internal/core/context.go`
+
+- Core context keys (`requestIDKey`, `executionPlanKey`, etc.).
+- `RequestOrigin`: Enum determining if a request is from a client or an internal background system (e.g. `guardrail`).
+- `WithRequestID()`, `GetRequestID()`, `WithRequestSnapshot()`, `GetRequestSnapshot()`, `WithWhiteBoxPrompt()`, `GetWhiteBoxPrompt()`, `WithExecutionPlan()`, `GetExecutionPlan()`, `WithAuthKeyID()`, `GetAuthKeyID()`, `WithEffectiveUserPath()`, `GetEffectiveUserPath()`, `WithBatchPreparationMetadata()`, `GetBatchPreparationMetadata()`, `WithEnforceReturningUsageData()`, `GetEnforceReturningUsageData()`, `WithGuardrailsHash()`, `GetGuardrailsHash()`, `WithFallbackUsed()`, `GetFallbackUsed()`, `WithRequestOrigin()`, `GetRequestOrigin()`: Getters and setters for dependency injection throughout the middleware stack.
+
+### `./internal/core/embeddings_json.go`
+
+- Custom JSON serialization for `EmbeddingRequest`.
+
+### `./internal/core/endpoints.go`
+
+- `BodyMode`, `Operation`, `EndpointDescriptor`: Enums defining how an HTTP path should be treated (e.g., is it JSON? is it a model invocation?).
+- `DescribeEndpoint()`, `DescribeEndpointPath()`, `describeEndpointPath()`, `bodyModeForEndpoint()`, `matchesEndpointPath()`, `normalizeEndpointPath()`, `IsModelInteractionPath()`, `ParseProviderPassthroughPath()`: Routing rules and heuristics.
+
+### `./internal/core/errors.go`
+
+- `ErrorType`, `GatewayError`: Unified HTTP error representation.
+- `Error()`, `Unwrap()`, `HTTPStatusCode()`, `ToJSON()`, `WithParam()`, `WithCode()`: Error properties.
+- `NewProviderError()`, `NewRateLimitError()`, `NewInvalidRequestError()`, `NewInvalidRequestErrorWithStatus()`, `NewAuthenticationError()`, `NewNotFoundError()`, `ParseProviderError()`: Factory functions standardizing varied downstream AI errors into predictable HTTP status codes.
+
+### `./internal/core/execution_plan.go`
+
+- `ExecutionMode`, `CapabilitySet`, `ExecutionFeatures`: Represents flags defining what features (Caching, Audit, Guardrails) apply to an execution.
+- `ExecutionPlanSelector`: Identifies a request's scope (User Path, Provider, Model).
+- `ResolvedExecutionPolicy`, `ExecutionPlan`: The finalized payload attached to a Context determining the request's destiny.
+- `ApplyUpperBound()`, `DefaultExecutionFeatures()`, `NewExecutionPlanSelector()`, `RequestedQualifiedModel()`, `ResolvedQualifiedModel()`, `ExecutionPlanVersionID()`, `CacheEnabled()`, `AuditEnabled()`, `UsageEnabled()`, `GuardrailsEnabled()`, `FallbackEnabled()`, `GuardrailsHash()`, `featureEnabled()`: Methods computing flags.
+
+### `./internal/core/files.go`
+
+- `FileCreateRequest`, `FileRouteInfo`, `FileObject`, `FileListResponse`, `FileDeleteResponse`, `FileContentResponse`: Structs bridging Native File APIs.
+- `FileMultipartMetadataReader`, `EnrichFileCreateRouteInfo()`, `ensureParsedLimit()`: Parsing multipart uploads.
+
+### `./internal/core/interfaces.go`
+
+- The defining interfaces that all providers must implement: `Provider`, `NativeBatchProvider`, `BatchCreateHintAwareProvider`, `BatchResultHintAwareProvider`, `NativeBatchRoutableProvider`, `NativeBatchHintRoutableProvider`, `NativeFileProvider`, `NativeFileRoutableProvider`, `NativeFileProviderTypeLister`, `RoutableProvider`, `ProviderNameResolver`, `ProviderTypeNameResolver`, `ProviderNameTypeResolver`, `AvailabilityChecker`, `ModelLookup`.
+
+### `./internal/core/json_fields.go`
+
+- `UnknownJSONFields`: A raw byte buffer encapsulating unrecognized fields in a JSON object.
+- `CloneRawJSON()`, `CloneUnknownJSONFields()`, `UnknownJSONFieldsFromMap()`, `unknownJSONFieldsFromMap()`, `Lookup()`, `IsEmpty()`, `extractUnknownJSONFields()`, `containsJSONField()`, `marshalWithUnknownJSONFields()`, `mergeUnknownJSONObject()`, `mergedJSONObjectCap()`: Highly optimized byte manipulation to safely inject and extract known properties without losing vendor-specific undocumented fields.
+
+### `./internal/core/message_json.go`
+
+- Custom `UnmarshalJSON` and `MarshalJSON` implementing `UnknownJSONFields` for `Message`, `ResponseMessage`, `ToolCall`, and `FunctionCall`.
+- `marshalMessageContent()`, `isNullEquivalentContent()`: Handles rendering missing/empty message contents without causing provider errors.
+
+### `./internal/core/model_selector.go`
+
+- `ModelSelector`: Struct representing `{provider}/{model}` parsed from a string.
+- `QualifiedModel()`, `ParseModelSelector()`, `splitQualifiedModel()`: String extraction.
+
+### `./internal/core/passthrough.go`
+
+- `PassthroughRequest`, `PassthroughResponse`, `PassthroughProvider`, `RoutablePassthrough`, `PassthroughSemanticEnricher`: Structs defining raw reverse-proxy logic that bypasses strict schema translation.
+
+### `./internal/core/request_model_resolution.go`
+
+- `RequestModelResolution`: Tracks the state transformation of a model target. Did it trigger an alias? What's the final explicit provider?
+- `RequestedQualifiedModel()`, `ResolvedQualifiedModel()`.
+
+### `./internal/core/request_snapshot.go`
+
+- `RequestSnapshot`: Complete immutable capture of an HTTP request.
+- `NewRequestSnapshot()`, `NewRequestSnapshotWithOwnedBody()`, `newRequestSnapshot()`, `firstUserPath()`, `WithUserPath()`, `CapturedBody()`, `CapturedBodyView()`, `GetRouteParams()`, `GetQueryParams()`, `GetHeaders()`, `GetTraceMetadata()`, `cloneBytes()`, `cloneStringMap()`, `cloneMultiMap()`: Immutability helpers.
+
+### `./internal/core/requested_model_selector.go`
+
+- `RequestedModelSelector`: Initial unparsed state representation.
+- `NewRequestedModelSelector()`, `Normalize()`, `RequestedQualifiedModel()`.
+
+### `./internal/core/responses.go`
+
+- `ResponsesRequest`, `ResponsesInputElement`, `ResponsesResponse`, `ResponsesOutputItem`, `ResponsesContentItem`, `ResponsesUsage`, `ResponsesError`: Schemas mapping to the emerging unified internal representation (modeled after newer OAI features).
+- `semanticSelector()`, `WithStreaming()`.
+
+### `./internal/core/responses_json.go`
+
+- Custom unmarshalers/marshalers applying `UnknownJSONFields` for the `Responses` schemas. `stringifyRawValue()`.
+
+### `./internal/core/semantic.go`
+
+- `RouteHints`, `PassthroughRouteInfo`, `semanticCacheKey`, `WhiteBoxPrompt`: Central data stores for semantic caching computation.
+- `CachedChatRequest()`, `CachedResponsesRequest()`, `CachedEmbeddingRequest()`, `CachedBatchRequest()`, `CachedBatchRouteInfo()`, `CachedFileRouteInfo()`, `CachedPassthroughRouteInfo()`, `CanonicalSelectorFromCachedRequest()`, `cacheValue()`, `cachedSemanticValue()`, `cachedSemanticAny()`, `cacheBatchRouteMetadata()`, `CacheFileRouteInfo()`, `CachePassthroughRouteInfo()`, `DeriveWhiteBoxPrompt()`, `derivePassthroughRouteInfoFromTransport()`, `deriveSnapshotSelectorHintsGJSON()`, `snapshotSelectorStringAllowed()`, `snapshotSelectorBoolAllowed()`, `DeriveFileRouteInfoFromTransport()`, `DeriveBatchRouteInfoFromTransport()`, `fileActionFromTransport()`, `fileIDFromTransport()`, `batchActionFromTransport()`, `batchIDFromTransport()`, `firstTransportValue()`: Huge mapping layer analyzing arbitrary request payloads to determine properties that apply to caching rules.
+
+### `./internal/core/semantic_canonical.go`
+
+- `canonicalJSONSpec`, `semanticSelectorCarrier`, `canonicalOperationCodec`: Generic decoders mapping raw requests into types implementing `Operation`.
+- `unmarshalCanonicalJSON()`, `newCanonicalOperationCodec()`, `canonicalOperationCodecFor()`, `decodeCanonicalOperation()`, `DecodeChatRequest()`, `DecodeResponsesRequest()`, `DecodeEmbeddingRequest()`, `DecodeBatchRequest()`, `parseRouteLimit()`, `cachedRouteMetadata()`, `BatchRouteMetadata()`, `FileRouteMetadata()`, `NormalizeModelSelector()`, `DecodeCanonicalSelector()`, `decodeCanonicalJSON()`, `cacheSemanticSelectorHints()`, `cacheSemanticStreamHint()`, `cacheSemanticSelectorHintsFromRequest()`, `semanticSelectorFromCanonicalRequest()`: Centralized parsing routing based on endpoint type.
+
+### `./internal/core/types.go`
+
+- `StreamOptions`, `Reasoning`, `ChatRequest`, `MessageContent`, `Message`, `ToolCall`, `FunctionCall`, `ChatResponse`, `Choice`, `ResponseMessage`, `PromptTokensDetails`, `CompletionTokensDetails`, `Usage`, `Model`, `ModelMetadata`, `ModelRanking`, `ModelCategory` (`CategoryAll`, `CategoryTextGeneration`, etc.), `ModelPricing`, `ModelPricingTier`, `ModelsResponse`, `EmbeddingRequest`, `EmbeddingResponse`, `EmbeddingData`, `EmbeddingUsage`: Fundamental struct definitions mirroring the public API boundary.
+- `semanticSelector()`, `WithStreaming()`, `CategoriesForModes()`, `AllCategories()`: Helpers.
+
+### `./internal/core/user_path.go`
+
+- `NormalizeUserPath()`, `UserPathAncestors()`, `UserPathFromContext()`: Handles hierarchical API key restrictions (`/team/a/b` implies `/team`).
+
+---
+
+## `./internal/embedding/`
+
+It generates vector embeddings.
+
+### `./internal/embedding/embedding.go`
+
+- `Embedder`: Interface.
+- `NewEmbedder()`: Configures a client against a generic HTTP LLM Provider configured in the YAML.
+- `apiEmbedder`: Implementation.
+- `embeddingRequest`, `embeddingResponse`: API schemas.
+- `normalizeGeminiEmbeddingModel()`, `openAIEmbeddingsEndpointURL()`, `Embed()`, `Identity()`, `Close()`: Execution flow.
+
+---
+
+## `./internal/executionplans/`
+
+It evaluates conditional workflows (Guardrails, Cache logic, Fallbacks) based on endpoints.
+
+### `./internal/executionplans/compiler.go`
+
+- `compiler`: Connects database-stored workflow instructions to executable code modules.
+- `NewCompiler()`, `NewCompilerWithFeatureCaps()`: Initializers.
+- `Compile()`: Converts a `Version` row into a `CompiledPlan` featuring an actionable `guardrails.Pipeline`.
+- `compileGuardrails()`: Builds the executable guardrail list.
+
+### `./internal/executionplans/factory.go`
+
+- `Result`, `Close()`, `New()`, `NewWithSharedStorage()`, `newResult()`, `createStore()`: Instantiates the service and DB interface.
+
+### `./internal/executionplans/service.go`
+
+- `CompiledPlan`: Holds the `Version`, the context `ResolvedExecutionPolicy`, and the `Pipeline`.
+- `Compiler`, `snapshot`, `Service`: Manages in-memory lookup tries matching Request dimensions (Provider, Model, User Path) to Plans.
+- `NewService()`, `Refresh()`, `refreshLocked()`, `EnsureDefaultGlobal()`, `Create()`, `Deactivate()`, `GetView()`, `ListViews()`, `Match()`, `PipelineForContext()`, `PipelineForExecutionPlan()`, `StartBackgroundRefresh()`, `matchCompiled()`, `validateCreateCandidate()`, `viewForVersion()`, `viewWithError()`, `rawScopeType()`, `rawScopeDisplay()`, `scopeType()`, `scopeDisplay()`, `viewScopeSpecificity()`, `snapshot()`, `cloneSnapshot()`, `compiledPlanForVersion()`, `storeActivatedCompiledLocked()`, `storeDeactivatedVersionLocked()`: Evaluates requests and returns the deepest matching plan.
+
+### `./internal/executionplans/store.go`
+
+- `ErrNotFound`, `ValidationError`: Standard errors.
+- `IsValidationError()`, `newValidationError()`.
+- `Store`: Interface specifying DB operations.
+
+### `./internal/executionplans/store_helpers.go`
+
+- `versionRowScanner`, `versionRowIterator`: Scan interfaces.
+- `collectVersions()`, `storedScopeUserPath()`: Helpers decoding row data.
+
+### `./internal/executionplans/store_mongodb.go`
+
+- `mongoVersionDocument`, `MongoDBStore`: DB definitions.
+- `NewMongoDBStore()`, `ListActive()`, `Get()`, `Create()`, `EnsureManagedDefaultGlobal()`, `Deactivate()`, `Close()`, `ensureManagedDefaultGlobal()`, `insertVersion()`, `versionFromMongo()`: CRUD.
+
+### `./internal/executionplans/store_postgresql.go`
+
+- `PostgreSQLStore`: DB definitions.
+- `NewPostgreSQLStore()`, `ListActive()`, `Get()`, `Create()`, `EnsureManagedDefaultGlobal()`, `createVersion()`, `ensureManagedDefaultGlobal()`, `Deactivate()`, `Close()`, `scanPostgreSQLVersion()`, `nullIfEmpty()`, `valueOrEmpty()`, `isPostgreSQLUniqueViolation()`: CRUD.
+
+### `./internal/executionplans/store_sqlite.go`
+
+- `SQLiteStore`: DB definitions.
+- `NewSQLiteStore()`, `isSQLiteDuplicateColumnError()`, `ListActive()`, `Get()`, `Create()`, `EnsureManagedDefaultGlobal()`, `Deactivate()`, `Close()`, `scanSQLiteVersion()`, `nullableString()`, `boolToSQLite()`: CRUD.
+
+### `./internal/executionplans/types.go`
+
+- `Scope`, `scopeJSON`, `Payload`, `FeatureFlags`, `GuardrailStep`, `Version`, `CreateInput`: Domain types specifying what triggers the workflow and what steps it takes.
+- `MarshalJSON()`, `UnmarshalJSON()`, `canonicalize()`, `runtimeFeatures()`, `normalizeScope()`, `scopeKey()`, `normalizePayload()`, `normalizeCreateInput()`: Data sanitization.
+
+### `./internal/executionplans/view.go`
+
+- `View`: A flattened API representation for the UI containing errors and effective feature state.
+
+---
+
+## `./internal/fallback/`
+
+It determines backup provider targets when models go offline.
+
+### `./internal/fallback/resolver.go`
+
+- `Registry`, `Resolver`: Evaluates dynamic metadata to return backup models.
+- `NewResolver()`: Parses YAML/JSON logic.
+- `ResolveFallbacks()`: Returns `[]ModelSelector`.
+- `sourceModelInfo()`, `modeFor()`, `manualSelectorsFor()`, `autoSelectorsFor()`, `resolveSelector()`, `sourceKey()`, `matchKeys()`, `requiredCategoryForOperation()`, `supportsCategory()`, `preferredRanking()`, `rankingByName()`, `sameFamily()`, `capabilityOverlap()`, `absInt()`: Magic sorting algorithm that discovers "similar" models by extracting capabilities, Elo scores, and architecture family tags from the registry automatically.
+
+---
+
+## `./internal/guardrails/`
+
+It provides modular middleware components altering request shapes natively.
+
+### `./internal/guardrails/catalog.go`
+
+- `Catalog`: Registry interface defining pipelines build logic.
+
+### `./internal/guardrails/definitions.go`
+
+- `Definition`, `View`, `TypeOption`, `TypeField`, `TypeDefinition`, `systemPromptDefinitionConfig`, `llmBasedAlteringDefinitionConfig`, `unavailableGuardrail`: Structs describing a rule for configuration files or databases.
+- `ViewFromDefinition()`, `normalizeDefinition()`, `normalizeDefinitionType()`, `cloneDefinition()`, `cloneTypeDefinitions()`, `decodeSystemPromptDefinitionConfig()`, `decodeLLMBasedAlteringDefinitionConfig()`, `llmBasedAlteringRuntimeConfig()`, `buildDefinition()`, `summarizeDefinition()`, `TypeDefinitions()`, `llmBasedAlteringDescriptor()`, `mustMarshalRaw()`: Parsing and building logic to convert definitions into `Guardrail` logic implementations.
+
+### `./internal/guardrails/executor.go`
+
+- `RequestPatcher`, `BatchPreparer`: Wraps pipelines to apply them to native structures.
+- `NewRequestPatcher()`, `PatchChatRequest()`, `PatchResponsesRequest()`, `NewBatchPreparer()`, `PrepareBatchRequest()`, `batchFileTransport()`, `processGuardedBatchRequest()`, `processGuardedChat()`, `processGuardedResponses()`: Invokes execution across messages correctly formatting their envelopes post-mutation.
+
+### `./internal/guardrails/factory.go`
+
+- `Result`, `Close()`, `New()`, `NewWithSharedStorage()`, `newResult()`, `createStore()`, `startGuardrailRefreshLoop()`, `validateExecutorCount()`: Lifecycle wiring.
+
+### `./internal/guardrails/guardrails.go`
+
+- `Message`: Agnostic representation of a message part.
+- `Guardrail`: Interface requiring `Name` and `Process(ctx, []Message) ([]Message, error)`.
+
+### `./internal/guardrails/llm_based_altering.go`
+
+- `LLMBasedAlteringConfig`, `ChatCompletionExecutor`, `LLMBasedAlteringGuardrail`: Represents a powerful rule using a _secondary LLM_ to review and rewrite prompts strictly (e.g. to scrub PII tokens using `` wrappers).
+- `DefaultLLMBasedAlteringPrompt`, `ResolveLLMBasedAlteringPrompt()`, `EffectiveLLMBasedAlteringMaxTokens()`, `NormalizeLLMBasedAlteringRoles()`, `NormalizeLLMBasedAlteringConfig()`, `NewLLMBasedAlteringGuardrail()`, `Name()`, `Process()`, `shouldRewrite()`, `rewriteTexts()`, `rewriteText()`, `executionUserPath()`, `appendGuardrailUserPath()`, `validateGuardrailPathSegment()`, `wrapAlteringText()`, `unwrapAlteredText()`: Execution flow wrapping an isolated Chat request to process the content dynamically without executing function calls.
+
+### `./internal/guardrails/pipeline.go`
+
+- `entry`, `Pipeline`, `result`: Organizes multiple `Guardrail` instances and defines order parameters allowing them to execute sequentially or concurrently.
+- `NewPipeline()`, `Add()`, `Len()`, `groups()`, `Process()`, `runGroupParallel()`: Pipeline execution mechanism.
+
+### `./internal/guardrails/planned_executor.go`
+
+- `ContextPipelineResolver`, `PlannedRequestPatcher`, `PlannedBatchPreparer`: Ties Guardrails to `ExecutionPlans` context rules dynamically.
+- `NewPlannedRequestPatcher()`, `PatchChatRequest()`, `PatchResponsesRequest()`, `pipeline()`, `NewPlannedBatchPreparer()`, `PrepareBatchRequest()`, `batchFileTransport()`: Dispatcher methods.
+
+### `./internal/guardrails/provider.go`
+
+- `GuardedProvider`, `Options`: Decorates a base `core.RoutableProvider` ensuring pipeline patches run right before upstream requests hit the network.
+- `NewGuardedProvider()`, `NewGuardedProviderWithOptions()`, `Supports()`, `GetProviderType()`, `ModelCount()`, `NativeFileProviderTypes()`, `ChatCompletion()`, `StreamChatCompletion()`, `ListModels()`, `Embeddings()`, `Responses()`, `StreamResponses()`, `nativeBatchRouter()`, `nativeBatchHintRouter()`, `nativeFileRouter()`, `batchFileTransport()`, `passthroughRouter()`, `rewriteGuardedChatBatchBody()`, `patchGuardedChatBatchBody()`, `patchChatMessagesJSON()`, `patchRawChatMessage()`, `rewriteGuardedResponsesBatchBody()`, `jsonFieldPatch`, `patchJSONObjectFields()`, `unmarshalJSONArray()`, `isZeroJSONFieldValue()`, `CreateBatch()`, `CreateBatchWithHints()`, `GetBatch()`, `ListBatches()`, `CancelBatch()`, `GetBatchResults()`, `GetBatchResultsWithHints()`, `ClearBatchResultHints()`, `CreateFile()`, `ListFiles()`, `GetFile()`, `DeleteFile()`, `GetFileContent()`, `recordBatchPreparation()`, `cleanupSupersededBatchRewriteFile()`, `cleanupBatchRewriteFile()`, `mergeBatchHints()`, `Passthrough()`, `PatchChatRequest()`, `PatchResponsesRequest()`, `PrepareBatchRequest()`, `chatToMessages()`, `applyMessagesToChatPreservingEnvelope()`, `tailMatchedSystemOffsets()`, `applyGuardedMessageToOriginal()`, `newChatMessageFromGuardrail()`, `applyGuardedContentToOriginal()`, `rewriteStructuredContentWithTextRewrite()`, `normalizeGuardrailMessageText()`, `responsesToMessages()`, `responsesInputToMessages()`, `coerceResponsesInputElements()`, `responsesInputElementToGuardrailMessage()`, `stringifyResponsesValue()`, `applyMessagesToResponses()`, `applyMessagesToResponsesInput()`, `applyMessagesToResponsesElements()`, `applyGuardedResponsesElementToOriginal()`, `applyGuardedResponsesContentToOriginal()`, `patchResponsesInputEnvelope()`, `patchResponsesInputMapSlice()`, `patchResponsesInputInterfaceSlice()`, `patchResponsesInputInterfaceElement()`, `patchResponsesInputMap()`, `restoreResponsesInputOutputValue()`, `responsesInputElementAsMap()`, `responsesInputElementAsAny()`, `isResponsesStructuredContent()`, `rewriteStructuredResponsesContentWithTextRewrite()`, `rewriteStructuredResponsesTypedContentParts()`, `rewriteStructuredResponsesInterfaceContentParts()`, `rewriteStructuredResponsesMapContentParts()`, `isResponsesTextPartType()`, `cloneResponsesInterfaceParts()`, `cloneResponsesInterfacePart()`, `cloneStringAnyMap()`, `cloneToolCalls()`, `cloneChatMessageEnvelope()`, `cloneMessageContent()`, `cloneContentParts()`, `cloneContentPart()`: Wrappers tracking, caching, and reconstructing all varied JSON representations safely modifying payloads.
+
+### `./internal/guardrails/registry.go`
+
+- `StepReference`, `registryEntry`, `Registry`: Associates raw names with compiled instances to fulfill Execution Plans requests.
+- `NewRegistry()`, `Len()`, `Names()`, `Register()`, `BuildPipeline()`: Registry tracking logic.
+
+### `./internal/guardrails/service.go`
+
+- `serviceSnapshot`, `Service`: Core API.
+- `NewService()`, `Refresh()`, `SetExecutor()`, `refreshLocked()`, `UpsertDefinitions()`, `List()`, `ListViews()`, `Get()`, `Upsert()`, `Delete()`, `TypeDefinitions()`, `Len()`, `Names()`, `BuildPipeline()`, `buildSnapshot()`, `definitionMap()`, `definitionsFromMap()`, `guardrailServiceError()`: API methods matching standard module layouts.
+
+### `./internal/guardrails/store.go`
+
+- `ErrNotFound`, `ValidationError`: Errors.
+- `IsValidationError()`, `newValidationError()`, `Store`, `definitionScanner`, `definitionRows`: Interface types.
+- `normalizeDefinitionName()`, `collectDefinitions()`, `nullableString()`, `nullableStringValue()`: Formatters.
+
+### `./internal/guardrails/store_mongodb.go`
+
+- DB Implementations (`MongoDBStore`).
+
+### `./internal/guardrails/store_postgresql.go`
+
+- DB Implementations (`PostgreSQLStore`).
+
+### `./internal/guardrails/store_sqlite.go`
+
+- DB Implementations (`SQLiteStore`).
+
+### `./internal/guardrails/system_prompt.go`
+
+- `SystemPromptMode`, `SystemPromptGuardrail`: Appends, replaces, or wraps the System Prompt to enforce rules.
+- `NewSystemPromptGuardrail()`, `effectiveSystemPromptMode()`, `isValidSystemPromptMode()`, `Name()`, `Process()`, `inject()`, `override()`, `decorate()`: Message processing logic.
+
+---
+
+## `./internal/httpclient/`
+
+It implements tuned networking protocols.
+
+### `./internal/httpclient/client.go`
+
+- `ClientConfig`: Settings for timeouts and pooling.
+- `getEnvDuration()`, `DefaultConfig()`, `NewHTTPClient()`, `NewDefaultHTTPClient()`: Configuration logic ensuring aggressive read/header timeouts missing from the standard `http.DefaultClient`.
+
+---
+
+## `./internal/llmclient/`
+
+It wraps `net/http` to provide advanced resilience logic.
+
+### `./internal/llmclient/client.go`
+
+- `RequestInfo`, `ResponseInfo`, `Hooks`, `Config`, `HeaderSetter`: Configurations identifying requests for metrics monitoring.
+- `Client`: HTTP Controller.
+- `DefaultConfig()`, `New()`, `NewWithHTTPClient()`, `SetBaseURL()`, `BaseURL()`, `getBaseURL()`: Setup logic.
+- `Request`, `Response`, `requestScope`: Datatypes capturing raw byte streams.
+- `beginRequest()`, `finishRequest()`, `recordCircuitBreakerCompletion()`, `shouldTripCircuitBreaker()`, `maxAttempts()`, `waitForRetry()`: Handles Backoffs, Jitters, and Hooks.
+- `Do()`, `DoRaw()`, `DoStream()`, `DoPassthrough()`: Core network routines resolving errors via Core parsing logic.
+- `canRetryPassthrough()`, `hasIdempotencyKey()`, `extractModel()`, `extractStatusCode()`, `doHTTPRequest()`, `doRequest()`, `buildRequest()`, `calculateBackoff()`, `isRetryable()`: Request construction and verification.
+- `circuitBreaker`, `circuitState`: Finite state machine protecting instances from spamming overloaded provider APIs.
+- `newCircuitBreaker()`, `acquire()`, `Allow()`, `RecordSuccess()`, `RecordFailure()`, `State()`, `IsHalfOpen()`: Tracking logic.
+
+---
+
+## `./internal/modeldata/`
+
+It handles scraping public AI model metadata (Pricing, Max Tokens) to enrich requests context locally.
+
+### `./internal/modeldata/enricher.go`
+
+- `ModelInfoAccessor`, `EnrichStats`: Tools to merge new data.
+- `Enrich()`: Applies the fetched models into the internal Provider Registry.
+
+### `./internal/modeldata/fetcher.go`
+
+- `Fetch()`: HTTP call pulling the `ai-model-list` definition file.
+- `Parse()`: Deserializes the JSON.
+
+### `./internal/modeldata/merger.go`
+
+- `Resolve()`: Merges provider-specific definitions with underlying standard definitions.
+- `resolveEntries()`, `resolveDirect()`, `resolveReverseProviderModelID()`, `resolveAlias()`, `selectAliasModelRef()`, `aliasTargetScore()`, `resolveModelRef()`, `buildMetadata()`, `buildRankings()`: Fallback resolution to locate model pricing rules properly mapping customized variants correctly.
+
+### `./internal/modeldata/types.go`
+
+- `ModelList`, `aliasTarget`, `ProviderEntry`, `ModelEntry`, `ProviderModelEntry`, `ParameterSpec`, `RankingEntry`, `AuthConfig`, `Modalities`, `RateLimits`: The schema mirroring the JSON payload describing the AI industry.
+- `buildReverseIndex()`, `addAliasTarget()`, `splitProviderQualifiedAlias()`, `actualModelID()`: Indexes data for rapid evaluation.
+
+---
+
+## `./internal/modeloverrides/`
+
+It selectively masks access to downstream models based on organizational policies.
+
+### `./internal/modeloverrides/batch_preparer.go`
+
+- `selectorResolver`, `BatchPreparer`: Iterates JSONL batch files, halting execution if the files attempt to invoke models the user's `UserPath` API key lacks permission to access.
+- `NewBatchPreparer()`, `PrepareBatchRequest()`, `batchFileTransport()`, `resolveSelector()`, `requestedSelectorForDecodedRequest()`.
+
+### `./internal/modeloverrides/factory.go`
+
+- `Result`, `Close()`, `New()`, `NewWithSharedStorage()`, `newResult()`, `createStore()`: Standard Module Initialization.
+
+### `./internal/modeloverrides/service.go`
+
+- `compiledOverride`, `snapshot`: Memory map for rapid `O(1)` routing.
+- `Service`: Main control mechanism.
+- `NewService()`, `EnabledByDefault()`, `Refresh()`, `refreshLocked()`, `snapshot()`, `buildSnapshot()`, `cloneOverride()`, `snapshotOverrides()`, `upsertOverride()`, `deleteOverride()`, `rollbackContext()`, `List()`, `ListViews()`, `Get()`, `Upsert()`, `Delete()`, `StartBackgroundRefresh()`, `EffectiveState()`, `AllowsModel()`, `ValidateModelAccess()`, `FilterPublicModels()`, `effectiveState()`, `userPathAllowed()`: Operations mapping requests paths to accessibility trees ensuring locked resources are successfully blocked in endpoints and API catalogs.
+
+### `./internal/modeloverrides/store.go`
+
+- `ErrNotFound`, `ValidationError`, `IsValidationError()`, `newValidationError()`, `Store`, `collectOverrides()`: Abstractions.
+
+### `./internal/modeloverrides/store_mongodb.go`, `store_postgresql.go`, `store_sqlite.go`
+
+- Implementations of the SQL/Mongo queries storing rule mappings.
+
+### `./internal/modeloverrides/types.go`
+
+- `Override`, `ScopeKind`, `View`, `EffectiveState`, `Catalog`: Target model structs.
+- `ScopeModel`, `ScopeProvider`, `ScopeProviderModel`: Hierarchy logic evaluating which policy overrides others.
+- `ScopeKind()`, `normalizeOverrideInput()`, `normalizeStoredOverride()`, `normalizeSelectorInput()`, `selectorProviderNames()`, `normalizeUserPaths()`, `selectorString()`, `exactMatchKey()`, `splitFirst()`, `parseStoredSelectorParts()`, `cloneEnabled()`: Sanitization algorithms.
+
+---
+
+## `./internal/observability/`
+
+It collects metrics on routing behavior.
+
+### `./internal/observability/metrics.go`
+
+- `RequestsTotal`, `RequestDuration`, `InFlightRequests`: Global prometheus gauges representing traffic shapes across different providers, endpoints, and statuses.
+- `NewPrometheusHooks()`: A closure passed to `llmclient` that increments/decrements these stats accurately mapping network durations into buckets.
+- `PrometheusMetrics`, `GetMetrics()`, `ResetMetrics()`, `HealthCheck()`: Operational handlers.
+
+---
+
+## `./internal/providers/`
+
+It defines interfaces mapping HTTP providers seamlessly unifying logic mapping between Anthropic, Gemini, OpenAI, etc.
+
+### `./internal/providers/batch_results_file_adapter.go`
+
+- `openAICompatibleBatchLine`, `openAICompatibleRawRequester`, `openAICompatiblePassthroughRequester`, `openAICompatibleRequestPreparer`: Functions and types for interacting with generic OpenAI compatible Batch result files.
+- `FetchBatchResultsFromOutputFile()`, `FetchBatchResultsFromOutputFileWithPreparer()`, `fetchBatchResultsFromOpenAICompatibleEndpoints()`, `normalizeOpenAICompatibleEndpointPrefix()`, `parseBatchFileMetadata()`, `isPendingBatchStatus()`, `parseBatchOutputFile()`, `ResponseURL()`, `firstString()`: Helper pulling down `.jsonl` files storing API responses via the generic OpenAI files endpoints mapping its payload to `core.BatchResultsResponse`.
+
+### `./internal/providers/config.go`
+
+- `ProviderConfig`: Merged model setting configurations.
+- `resolveProviders()`, `applyProviderEnvVars()`, `findEnvOverlayTarget()`, `rawProviderMatchesType()`, `providerEnvNames`, `derivedEnvNames()`, `envPrefix()`, `sortedDiscoveryTypes()`, `normalizeResolvedBaseURL()`, `isUnresolvedEnvPlaceholder()`, `filterEmptyProviders()`, `buildProviderConfigs()`, `buildProviderConfig()`: Injects environmental configuration into API targets enabling automatic discovery (e.g. matching `OPENAI_API_KEY` dynamically creating the module).
+
+### `./internal/providers/factory.go`
+
+- `ProviderOptions`, `ProviderConstructor`, `DiscoveryConfig`, `Registration`: Settings mapping the name of a string in yaml (e.g. `azure`) to a specific constructor implementing the interface mapping rules.
+- `ProviderFactory`: Map linking strings to Constructors dynamically.
+- `NewProviderFactory()`, `SetHooks()`, `Add()`, `Create()`, `discoveryConfigsSnapshot()`, `RegisteredTypes()`, `PassthroughSemanticEnrichers()`: Methods bootstrapping providers safely passing the HTTP client configuration downwards.
+
+### `./internal/providers/file_adapter_openai_compat.go`
+
+- `validatedOpenAICompatibleFileID()`, `openAICompatibleRequestPreparer`, `prepareOpenAICompatibleRequest()`, `doOpenAICompatibleFileIDRequest()`, `doOpenAICompatibleFileIDRequestWithPreparer()`, `CreateOpenAICompatibleFile()`, `CreateOpenAICompatibleFileWithPreparer()`, `ListOpenAICompatibleFiles()`, `ListOpenAICompatibleFilesWithPreparer()`, `GetOpenAICompatibleFile()`, `GetOpenAICompatibleFileWithPreparer()`, `DeleteOpenAICompatibleFile()`, `DeleteOpenAICompatibleFileWithPreparer()`, `GetOpenAICompatibleFileContent()`, `GetOpenAICompatibleFileContentWithPreparer()`: Methods mapping `core.NativeFileProvider` over any service matching standard OpenAI API interfaces avoiding redundant code.
+
+### `./internal/providers/init.go`
+
+- `InitResult`: Wrapper carrying the Registry and background fetcher context.
+- `Close()`, `Init()`, `initCache()`, `initializeProviders()`: Central wiring initializing providers, mapping aliases, triggering background network scans mapping capabilities (pricing data).
+
+### `./internal/providers/passthrough.go`
+
+- `PassthroughEndpoint()`, `CloneHTTPHeaders()`, `PassthroughEndpointPath()`: Utilities converting internal contexts safely into generic HTTP Proxy data.
+
+### `./internal/providers/registry.go`
+
+- `ModelInfo`, `ModelRegistry`, `metadataEnrichmentStats`: Tracks instances in memory efficiently enabling dynamic lookup tables based on incoming request properties.
+- `slogAttrs()`, `NewModelRegistry()`, `SetCache()`, `invalidateSortedCaches()`, `RegisterProvider()`, `RegisterProviderWithType()`, `RegisterProviderWithNameAndType()`, `Initialize()`, `Refresh()`, `LoadFromCache()`, `SaveToCache()`, `InitializeAsync()`, `IsInitialized()`, `GetProvider()`, `GetModel()`, `LookupModel()`, `Supports()`, `ListModels()`, `ListPublicModels()`, `ModelCount()`, `GetProviderType()`, `GetProviderName()`, `GetProviderNameForType()`, `GetProviderTypeForName()`, `ProviderByType()`, `ProviderTypes()`, `ProviderNames()`, `splitModelSelector()`, `qualifyPublicModelID()`, `hasConfiguredProviderNameLocked()`, `ModelWithProvider`, `ListModelsWithProvider()`, `ListModelsWithProviderByCategory()`, `hasCategory()`, `CategoryCount`, `GetCategoryCounts()`, `ProviderCount()`, `SetModelList()`, `EnrichModels()`, `enrichModels()`, `ResolveMetadata()`, `GetModelMetadata()`, `ResolvePricing()`, `snapshotProviderTypes()`, `enrichProviderModelMaps()`, `registryAccessor`, `ModelIDs()`, `GetProviderType()`, `SetMetadata()`, `StartBackgroundRefresh()`, `refreshModelList()`, `isBenignBackgroundRefreshError()`: Comprehensive storage indexing models and metadata efficiently.
+
+### `./internal/providers/resolved_config.go`
+
+- `ResolveBaseURL()`, `ResolveAPIVersion()`: Small normalizers checking default value strings.
+
+### `./internal/providers/responses_adapter.go`
+
+- `ChatProvider`: Abstract implementation requirement.
+- `ConvertResponsesRequestToChat()`, `cloneStreamOptions()`, `normalizeResponsesToolsForChat()`, `normalizeResponsesToolForChat()`, `normalizeResponsesToolChoiceForChat()`, `cloneStringAnyMap()`, `ConvertResponsesInputToMessages()`, `convertResponsesInputItems()`, `convertResponsesInputItem()`, `convertResponsesInputElement()`, `convertResponsesInputMap()`, `cloneResponsesMessage()`, `canMergeAssistantMessages()`, `mergeAssistantMessage()`, `isAssistantToolCallOnlyMessage()`, `ConvertResponsesContentToChatContent()`, `convertResponsesContentParts()`, `normalizeTypedResponsesContentPart()`, `finalizeResponsesChatContent()`, `canFlattenResponsesPartsToText()`, `normalizeResponsesImageURLForChat()`, `normalizeResponsesInputAudioForChat()`, `cloneResponsesToolCall()`, `cloneResponsesContentPart()`, `rawJSONMapFromUnknownKeys()`, `rawJSONMapFromUnknownStringKeys()`, `firstNonEmptyString()`, `stringifyResponsesInputValue()`, `stringifyResponsesInputValueWithError()`, `ExtractContentFromInput()`, `extractTextFromMapSlice()`, `extractTextFromInputMap()`, `ResponsesFunctionCallCallID()`, `ResponsesFunctionCallItemID()`, `buildResponsesMessageContent()`, `buildResponsesContentItemsFromParts()`, `BuildResponsesOutputItems()`, `ConvertChatResponseToResponses()`, `ResponsesViaChat()`, `StreamResponsesViaChat()`: Extensively converts advanced multimodal JSON properties between the standard `Chat` structure and the specialized newer OpenAI `Responses` format. Converts logic cleanly for models not natively implementing Responses.
+
+### `./internal/providers/responses_converter.go`
+
+- `OpenAIResponsesStreamConverter`: Scans an active stream capturing bytes evaluating Server-Sent Events extracting their JSON data adapting it between Chat streams and Responses streams mapping delta segments over tool outputs generating unified Responses streams output mapping state gracefully.
+- `NewOpenAIResponsesStreamConverter()`, `normalizeToolCallIndex()`, `ensureToolCallState()`, `reserveAssistantOutput()`, `forceStartToolCall()`, `completePendingToolCalls()`, `handleToolCallDeltas()`, `Read()`, `Close()`: Transforms streams asynchronously.
+
+### `./internal/providers/responses_done_wrapper.go`
+
+- `EnsureResponsesDone()`, `responsesDoneWrapper`: Ensures stream completeness intercepting IO Read mapping standard event limits injecting missing EOF tags cleanly allowing middleware caching limits to capture valid structures cleanly preserving connection closures without data loss.
+- `Read()`, `trackStream()`, `processLine()`, `synthesizeDoneSuffix()`, `isDoneLinePrefix()`, `isCompletedDataLine()`: Parser boundaries.
+
+### `./internal/providers/responses_output_state.go`
+
+- `ResponsesOutputToolCallState`, `ResponsesOutputEventState`: Models holding chunks of text parsing functional inputs safely appending incomplete tool arguments correctly rendering output properties mapping partial states accurately generating exact responses cleanly.
+- `NewResponsesOutputEventState()`, `WriteEvent()`, `ReserveAssistant()`, `AssistantReserved()`, `AssistantStarted()`, `AssistantDone()`, `AppendAssistantText()`, `AssistantMessageItem()`, `StartAssistantOutput()`, `CompleteAssistantOutput()`, `ToolCallArguments()`, `RenderToolCallItem()`, `StartToolCall()`, `CompleteToolCall()`: State machine logic ensuring event sequence validity.
+
+### `./internal/providers/router.go`
+
+- `ErrRegistryNotInitialized`: Standard error.
+- `Router`: Maps inbound model strings (e.g. `gpt-5`) into executing Provider instances cleanly verifying fallback defaults mapping missing targets effectively rejecting unknown items safely verifying execution cleanly.
+- `providerTypeRegistry`, `initializedLookup`, `providerTypeLister`, `providerNameLister`, `publicModelLister`, `modelWithProviderLister`, `registryUnavailableError()`, `NewRouter()`, `checkReady()`, `ResolveModel()`, `resolveUnqualifiedSelector()`, `resolveQualifiedSelector()`, `hasConfiguredProviderName()`, `resolveProvider()`, `resolveProviderType()`, `resolveNativeBatchProvider()`, `resolveNativeFileProvider()`, `resolvePassthroughProvider()`, `routeResolvedModelCall()`, `routeStampedModelResponse()`, `routeNativeBatchCall()`, `routeNativeFileCall()`, `stampProvider()`, `forwardChatRequest()`, `forwardResponsesRequest()`, `forwardEmbeddingRequest()`, `callChatCompletion()`, `callResponses()`, `callEmbeddings()`, `Supports()`, `ModelCount()`, `ChatCompletion()`, `StreamChatCompletion()`, `ListModels()`, `Responses()`, `StreamResponses()`, `Embeddings()`, `GetProviderType()`, `GetProviderName()`, `GetProviderNameForType()`, `GetProviderTypeForName()`, `providerByType()`, `providerByTypeRegistry()`, `providerTypes()`, `NativeFileProviderTypes()`, `Passthrough()`, `CreateBatch()`, `CreateBatchWithHints()`, `GetBatch()`, `ListBatches()`, `CancelBatch()`, `GetBatchResults()`, `GetBatchResultsWithHints()`, `ClearBatchResultHints()`, `CreateFile()`, `ListFiles()`, `GetFile()`, `DeleteFile()`, `GetFileContent()`: Exposes `core.RoutableProvider` and dynamically matches any downstream provider supporting native implementation calls cleanly.
+
+---
+
+### Specific Providers (`internal/providers//`)
+
+**`anthropic`**:
+
+- `anthropic.go`: `Registration`, `Provider` implementing `ChatCompletion`, `Responses` mapping tools translating schemas correctly extracting parameters correctly managing batch mapping endpoints explicitly evaluating context limits caching rules natively.
+- `passthrough_semantics.go`: Enriches audit logging tagging native URLs safely matching schemas cleanly mapping metrics successfully exposing explicit data structures properly.
+- `request_translation.go`: Re-formats `ToolCall` and System limits fixing constraints modifying inputs enabling native formatting without data loss processing images accurately.
+
+**`azure`**:
+
+- `azure.go`: `Provider`, `New()` wrapping OpenAI configuration modifying endpoints dynamically updating API queries generating precise resource identifiers seamlessly proxying endpoints efficiently utilizing native open ai components transparently.
+
+**`gemini`**:
+
+- `gemini.go`: `Provider`, `New()`, `ChatCompletion()`, `ListModels()` converting schemas natively translating errors extracting capabilities evaluating models ensuring valid identifiers resolving names directly hitting endpoints natively utilizing google apis mapping endpoints correctly handling streams seamlessly parsing errors explicitly routing calls cleanly.
+
+**`groq`**:
+
+- `groq.go`: Standard OpenAI proxy configuring client executing requests.
+
+**`ollama`**:
+
+- `ollama.go`: Wrapper hitting Local endpoints handling non-standard metadata extracting native formats cleanly passing through parameters executing embeddings natively resolving errors properly skipping batch functionalities gracefully.
+
+**`openai`**:
+
+- `openai.go`: Wraps the compatible interface injecting standard parameters mapping environments securely defining models natively filtering logic appropriately parsing variables flawlessly ensuring execution correctly formatting requests seamlessly processing streams properly handling endpoints accurately logging outputs reliably.
+- `compatible_provider.go`: Generic wrapper mapping all internal provider functions directly into LLM SDK wrappers generating output dynamically validating inputs effectively handling connections flawlessly mapping structures properly processing data securely ensuring responses reliably formatting streams efficiently executing operations precisely handling batch APIs parsing files processing errors verifying properties handling requests gracefully.
+- `passthrough_semantics.go`: Enriches semantics matching URLs properly determining endpoints accurately exposing data appropriately modifying streams properly identifying features directly enriching data.
+
+**`openrouter`**:
+
+- `openrouter.go`: Injects HTTP Referer and Site Title tracking metadata correctly forwarding open ai formatted requests natively mapping data explicitly extending defaults cleanly applying variables securely generating requests effectively resolving features appropriately updating connections mapping models configuring endpoints mapping logic handling configuration.
+
+**`oracle`**:
+
+- `oracle.go`: Specific handler fixing missing API formats utilizing configured lists avoiding unsupported features returning accurate errors handling models directly processing features returning defaults updating state handling endpoints efficiently mapping execution cleanly formatting environments appropriately exposing tools securely generating properties.
+
+**`xai`**:
+
+- `xai.go`: Grok implementation mapping open ai tools hitting correct endpoints routing tokens configuring models formatting variables mapping environments defining data parsing inputs updating rules identifying structures.
+
+---
+
+## `./internal/responsecache/`
+
+It accelerates inference significantly returning cached results.
+
+### `./internal/responsecache/responsecache.go`
+
+- `ResponseCacheMiddleware`: Combines both caching mechanisms securely wrapping the HTTP echo controller intercepting streams evaluating logic efficiently skipping cached outputs writing values seamlessly.
+- `InternalHandleResult`, `NewResponseCacheMiddleware()`, `Middleware()`, `HandleRequest()`, `HandleInternalRequest()`, `Close()`, `internalRequestHeaders()`, `internalCacheType()`, `NewResponseCacheMiddlewareWithStore()`: Sets up the memory state checking rules validating configurations verifying structures mapping outputs evaluating settings.
+
+### `./internal/responsecache/semantic.go`
+
+- `semanticCacheMiddleware`: Evaluates prompt vector similarity bypassing inference entirely loading cached representations reconstructing full streaming outputs parsing inputs properly executing rules safely returning structures validating context correctly evaluating parameters extracting tokens defining schemas effectively.
+- `embedMessage`, `extractEmbedText()`, `embedTextFromMessages()`, `conversationInvariantFingerprint()`, `messageRawListFromMessagesOrInput()`, `writeNonTextUserContentFingerprint()`, `extractTextFromContent()`, `computeParamsHash()`, `sortedTools()`, `shouldSkipSemanticCache()`, `headerFloat64()`, `headerDuration()`, `sha256HexOf()`, `ComputeGuardrailsHash()`, `GuardrailRuleDescriptor`, `GuardrailsHashFromContext()`, `WithGuardrailsHash()`, `ShouldSkipExactCache()`, `ShouldSkipAllCache()`, `IoReadAllBody()`: Generates unique context fingerprints defining bounds calculating similarity formatting streams parsing embeddings mapping text extracting properties comparing schemas evaluating states ensuring validity matching fields updating contexts modifying logic accurately validating structures handling values cleanly processing rules safely mapping conditions directly defining keys evaluating inputs executing logic handling states securely converting structures explicitly generating identifiers tracking contexts extracting bytes accurately preserving constraints verifying inputs validating configurations applying keys processing data.
+
+### `./internal/responsecache/simple.go`
+
+- `cacheWriteJob`, `simpleCacheMiddleware`: Memory store matching exact SHA256 hashes generating cache hits instantaneously mapping responses verifying options caching streams intercepting headers updating bytes correctly handling memory cleanly.
+- `newSimpleCacheMiddleware()`, `Middleware()`, `TryHit()`, `StoreAfter()`, `close()`, `startWorkers()`, `enqueueWrite()`, `shouldSkipCacheForExecutionPlan()`, `requestBodyForCache()`, `shouldSkipCache()`, `isStreamingRequest()`, `isStreamingRequestGJSON()`, `hashRequest()`, `responseCapture`: High throughput writer buffer intercepting responses returning streams properly writing properties extracting configurations applying limits parsing contexts ensuring bounds validating contents updating data handling objects identifying states copying properties mapping output returning properties configuring states handling variables properly evaluating logic.
+
+### `./internal/responsecache/sse_validation.go`
+
+- `validateCacheableSSE()`, `sseEventPayload()`: Validates stream chunks preventing partial caching corruptions checking event strings parsing payload boundaries mapping blocks decoding logic verifying strings returning status extracting boundaries parsing fragments extracting properties mapping rules checking chunks matching structures evaluating states.
+
+### `./internal/responsecache/stream_cache.go`
+
+- `streamResponseDefaults`, `chatToolCallState`, `chatChoiceState`, `chatStreamCacheBuilder`, `responsesOutputState`, `responsesStreamCacheBuilder`: Maintains complex state maps reconstructing JSON structures across Server Sent Events mapping parts decoding structures updating fragments merging blocks modifying data defining schemas generating values mapping contexts checking inputs applying formats formatting components configuring states generating objects evaluating fragments extracting elements returning content.
+- `cacheKeyRequestBody()`, `isEventStreamContentType()`, `writeCachedResponse()`, `cacheHeaderValue()`, `renderCachedChatStream()`, `renderCachedResponsesStream()`, `reconstructStreamingResponse()`, `parseSSEJSONEvents()`, `parseCacheEventJSON()`, `nextCacheEventBoundary()`, `parseCacheDataLine()`, `OnJSONEvent()`, `Build()`, `choice()`, `toolCall()`, `captureResponseMetadata()`, `output()`, `lookupOutputIndex()`, `rememberOutputLocator()`, `ensureReasoningOutputIndex()`, `SetItem()`, `AppendText()`, `AppendReasoning()`, `AppendArguments()`, `SetArguments()`, `textPart()`, `reasoningPart()`, `BuildItem()`, `buildResponsesContentParts()`, `cloneJSONPart()`, `buildChatToolCalls()`, `renderChatToolCalls()`, `normalizeStreamOptionsForCache()`, `streamIncludeUsageRequested()`, `chatReasoningContent()`, `responsesAddedItem()`, `responsesTerminalEventName()`, `appendResponsesItemDeltaEvents()`, `responsesContentDeltaEvent()`, `chatUsageMap()`, `appendSSEJSONEvent()`, `toJSONMap()`, `cloneJSONMap()`, `jsonNumberToInt()`, `jsonNumberToInt64()`, `nonEmpty()`: Data extraction and formatting.
+
+### `./internal/responsecache/usage_hit.go`
+
+- `newUsageHitRecorder()`: Callback injecting usage events reporting saved tokens capturing context generating output evaluating pricing mapping components storing states reporting values accurately configuring metrics parsing limits extracting costs generating logs creating structures tracking variables maintaining states applying rules handling properties tracking usage defining logs handling variables generating items parsing records tracking contexts.
+
+### `./internal/responsecache/vecstore*.go`
+
+- `VecResult`, `VecStore`: Interfaces providing vector matching returning similar content identifying components tracking bounds comparing distances storing properties measuring fields returning queries identifying similarities evaluating structures processing elements matching vectors executing searches defining limits setting boundaries checking components locating states finding features returning variables returning lists parsing fields creating matches evaluating conditions validating structures comparing fields defining values identifying contexts mapping parameters parsing schemas returning objects tracking boundaries finding logic configuring properties searching components extracting variables saving parameters parsing objects.
+- `NewVecStore()`: Constructor.
+- `vecCleanup`: Background struct managing expirations parsing dates applying logic processing elements.
+- `startVecCleanup()`, `close()`, `vecPointID64()`, `pgvectorLiteral()`, `trimSlash()`: Helpers configuring settings.
+- `MapVecStore` (`vecstore_map.go`): In-memory linear search comparing cosine values matching fields returning scores parsing vectors updating indexes applying formats parsing limits managing fields extracting elements evaluating limits identifying vectors processing values checking distances calculating logic returning bounds maintaining loops extracting elements measuring metrics creating distances.
+- `pgVecStore` (`vecstore_pgvector.go`): PostgreSQL `vector` extension defining schemas mapping parameters extracting bounds defining logic matching tables returning limits applying boundaries evaluating queries configuring properties selecting logic indexing data comparing ranges connecting states identifying distances sorting results updating variables tracking elements creating indexes validating constraints modifying contexts comparing distances checking values returning items measuring thresholds.
+- `pineconeStore` (`vecstore_pinecone.go`): Native cloud Pinecone integration querying remote clusters extracting arrays converting strings comparing values fetching URLs mapping variables validating environments establishing connections verifying endpoints passing headers formatting JSON applying namespaces parsing responses verifying structures updating variables retrieving data setting parameters checking boundaries processing matches evaluating values modifying fields extracting contexts creating objects encoding bytes comparing fields returning endpoints mapping constraints identifying values measuring vectors comparing logic mapping keys.
+- `qdrantStore` (`vecstore_qdrant.go`): Qdrant DB proxy extracting points defining boundaries modifying structures formatting bytes sending connections applying schemas decoding states processing payloads capturing results comparing values fetching logic creating requests setting parameters mapping components establishing properties extracting distances parsing filters formatting queries handling options modifying dimensions comparing scores reading fields defining filters retrieving elements evaluating points checking limits defining contexts handling bytes creating objects storing items processing logic extracting matches defining constraints converting components generating requests identifying limits capturing elements.
+- `weaviateStore` (`vecstore_weaviate.go`): Weaviate GraphQL mapping extracting elements identifying endpoints fetching responses decoding schemas tracking classes matching keys validating structures processing logic updating values tracking variables comparing variables parsing connections generating structures applying settings finding configurations setting formats mapping bounds evaluating configurations checking conditions modifying limits passing contexts returning parameters checking options executing properties maintaining contexts.
+
+---
+
+## `./internal/server/`
+
+It holds the main Echo HTTP web server logic mapping context processing chains executing API structures resolving configurations maintaining states returning endpoints capturing metrics parsing routes injecting components checking boundaries measuring structures verifying fields evaluating queries checking configurations.
+
+### `./internal/server/auth.go`
+
+- `BearerTokenAuthenticator`: Interface determining valid API tokens checking keys maintaining status executing rules fetching properties parsing states defining variables mapping parameters identifying endpoints tracking requests returning flags matching rules extracting components checking configurations extracting elements storing properties executing values tracking rules decoding configurations matching boundaries.
+- `AuthMiddleware()`, `AuthMiddlewareWithAuthenticator()`: Intercepting functions authenticating headers stripping values validating secrets defining states handling roles returning errors extracting paths matching rules verifying elements modifying paths fetching headers checking formats setting contexts formatting paths rejecting combinations handling headers executing contexts passing properties recording states managing elements parsing fields mapping errors.
+- `authFailureMessage()`, `authenticationError()`, `authenticationErrorWithAudit()`: Error generators creating JSON returning faults generating logic assigning values defining schemas determining formats returning properties maintaining limits processing combinations building types setting contexts recording formats logging metrics checking rules formatting boundaries generating keys matching errors processing structures capturing variables handling items tracking failures defining fields mapping logic extracting limits parsing values returning responses updating headers mapping paths mapping elements defining states establishing constants returning conditions checking paths modifying components.
+
+### `./internal/server/batch_request_preparer.go`
+
+- `BatchRequestPreparer`: Interface handling asynchronous items mapping functions applying changes processing objects defining rules checking values processing elements configuring fields creating rules mapping boundaries verifying components processing states applying items generating lists storing data evaluating elements tracking bounds checking files defining limits tracking requests mapping contexts fetching variables executing fields formatting bounds maintaining objects parsing fields checking options maintaining values handling data processing parameters modifying states evaluating responses creating queries checking paths extracting settings identifying conditions mapping logic returning elements.
+- `batchRequestPreparerChain`: Iterating wrapper combining interfaces defining layers routing combinations building paths processing steps creating variables passing loops defining conditions assigning values processing outputs identifying formats evaluating layers converting limits recording states tracking bounds checking files saving fields executing structures formatting logic.
+- `ComposeBatchRequestPreparers()`, `PrepareBatchRequest()`, `cleanupBatchRewriteFile()`: Constructor executing steps returning output mapping responses comparing contexts checking values tracking options handling layers defining limits tracking limits maintaining lists finding paths handling inputs parsing errors defining boundaries executing functions tracking loops processing parameters logging contexts recording states modifying outputs defining operations finding elements passing components filtering paths extracting limits managing bounds assigning variables comparing variables processing steps comparing loops generating errors establishing keys assigning layers determining limits parsing contexts.
+
+### `./internal/server/error_support.go`
+
+- `handleError()`: Converts gateway problems mapping JSON capturing components logging properties setting values fetching headers processing errors applying formats verifying strings recording bounds returning elements identifying metrics establishing responses handling contexts converting items mapping objects formatting outputs defining rules defining contexts setting limits handling attributes saving formats tracking limits generating outputs mapping properties reporting failures.
+- `logHandledError()`: Adds contextual dimensions injecting types recording attributes capturing inputs logging traces fetching contexts identifying boundaries recording values adding arguments comparing items mapping queries extracting bounds printing output setting states reading boundaries recording structures processing messages parsing contexts evaluating properties creating states comparing fields defining inputs generating limits logging values tracking elements formatting components establishing variables executing items finding limits finding components processing limits processing variables updating rules formatting keys extracting variables handling responses tracking inputs checking operations processing formats defining messages printing rules.
+
+### `./internal/server/execution_plan_helpers.go`
+
+- `ensureTranslatedRequestPlan()`, `ensureTranslatedRequestPlanWithAuthorizer()`, `ensureTranslatedExecutionPlan()`, `currentTranslatedExecutionPlan()`, `translatedPlanResolution()`, `applyResolvedSelector()`, `translatedExecutionPlanForRequest()`, `translatedExecutionPlan()`: Resolves logic chains executing workflows determining fallback tracking components mapping features handling scopes routing elements applying constraints mapping aliases tracking environments generating endpoints evaluating models defining scopes checking keys recording policies extracting paths matching environments verifying properties generating boundaries fetching parameters mapping rules tracking models determining operations finding limits verifying structures formatting options capturing contexts identifying endpoints parsing constraints finding variables updating rules applying options executing variables creating contexts logging attributes.
+
+### `./internal/server/execution_policy.go`
+
+- `RequestExecutionPolicyResolver`: Matches logic mapping constraints.
+- `applyExecutionPolicy()`, `applyExecutionContextOverrides()`, `normalizeExecutionPolicyError()`, `cloneCurrentExecutionPlan()`, `executionPlanVersionID()`, `boolPtr()`: Injects features setting guardrails processing flags turning off functions extracting variables logging objects handling boundaries finding logic verifying properties returning defaults identifying components checking inputs generating keys formatting properties configuring states passing attributes returning structures managing variables checking structures verifying limits parsing components extracting fields establishing references creating states formatting logic applying variables handling loops evaluating fields defining scopes identifying structures passing outputs logging flags handling limits tracking operations determining rules extracting contexts setting options mapping values.
+
+### `./internal/server/exposed_model_lister.go`
+
+- `ExposedModelLister`, `FilteredExposedModelLister`: Abstract listing properties extracting definitions creating structures defining paths filtering keys evaluating rules executing logic parsing arrays handling options recording limits returning boundaries copying limits generating types.
+- `mergeExposedModelsResponse()`: Merging responses combining arrays removing duplicates establishing maps sorting objects assigning attributes updating lists formatting responses setting contexts handling fields processing structures creating bounds parsing values adding components verifying maps checking inputs handling combinations sorting logic matching structures passing limits maintaining logic generating elements extracting logic identifying loops measuring objects logging definitions checking configurations updating parameters.
+
+### `./internal/server/handlers.go`
+
+- `Handler`: Controller holding memory executing dependencies mapping pointers tracking logic parsing requests formatting schemas assigning references instantiating fields generating connections evaluating properties passing boundaries capturing operations returning responses determining states recording logs capturing elements setting options applying parameters identifying rules establishing bounds assigning interfaces loading metrics updating options updating environments determining limits configuring services allocating components updating components passing dependencies building handlers loading paths tracking parameters parsing responses mapping environments executing values.
+- `NewHandler()`, `newHandler()`, `newHandlerWithAuthorizer()`, `SetBatchStore()`, `translatedInference()`, `nativeBatch()`, `nativeFiles()`, `passthrough()`, `ProviderPassthrough()`, `ChatCompletion()`, `Health()`, `ListModels()`, `CreateFile()`, `ListFiles()`, `GetFile()`, `DeleteFile()`, `GetFileContent()`, `Responses()`, `Embeddings()`, `Batches()`, `GetBatch()`, `ListBatches()`, `CancelBatch()`, `BatchResults()`: Exposes internal functions formatting HTTP routes determining bounds assigning objects capturing streams fetching logs writing responses capturing structures comparing rules processing fields validating contexts executing queries passing states returning errors generating values modifying requests processing schemas.
+
+### `./internal/server/http.go`
+
+- `Server`, `Config`: The central HTTP router wrapper configuration initializing variables starting tasks tracking options applying settings mapping parameters recording components building routes configuring elements setting ports identifying servers tracking attributes generating endpoints injecting references validating components logging rules maintaining contexts adding metrics parsing properties handling requests measuring states parsing environments storing attributes maintaining components fetching limits returning operations tracking responses capturing errors identifying bounds determining configurations parsing paths filtering endpoints returning loops fetching logic loading paths validating outputs assigning features returning limits tracking parameters.
+- `New()`, `passthroughV1PrefixNormalizationEnabled()`, `Start()`, `Shutdown()`, `ServeHTTP()`, `newGatewayStartConfig()`, `configureGatewayHTTPServer()`, `modelInteractionWriteDeadlineMiddleware()`, `parseBodySizeLimitBytes()`: Framework initializers creating servers updating configurations processing boundaries defining types capturing connections terminating tasks closing handlers logging properties checking bounds filtering environments measuring structures running operations executing types finding limits applying limits parsing endpoints routing tasks formatting logic defining rules updating variables logging errors maintaining contexts finding parameters finding structures adding attributes identifying values evaluating properties extracting values tracking logic finding options generating bounds capturing operations mapping bounds determining values handling settings managing connections establishing rules identifying bounds checking paths logging fields identifying limits returning outputs running loops parsing strings setting contexts returning limits applying components tracking boundaries maintaining options validating configurations checking operations identifying environments.
+
+### `./internal/server/internal_chat_completion_executor.go`
+
+- `InternalChatCompletionExecutorConfig`, `InternalChatCompletionExecutor`: Helper structure instantiating isolated logic running endpoints modifying values checking paths extracting boundaries generating inputs returning responses establishing queries fetching limits executing tools maintaining contexts checking elements generating keys passing contexts applying parameters measuring rules building contexts identifying formats checking parameters finding limits formatting logic logging parameters processing environments executing variables routing queries tracking states determining scopes tracking parameters identifying limits formatting contexts mapping items returning properties evaluating boundaries logging operations extracting limits formatting components mapping limits executing components checking states determining types parsing strings returning outputs finding keys verifying combinations modifying outputs extracting operations checking rules.
+- `NewInternalChatCompletionExecutor()`, `ChatCompletion()`, `executeChatCompletion()`, `newAuditEntry()`, `finishAuditEntry()`, `chatResponseModel()`: Interacts internally applying logic.
+
+### `./internal/server/model_access.go`
+
+- `RequestModelAuthorizer`: Determines access processing filters capturing roles measuring rules validating variables formatting properties generating conditions executing inputs mapping constraints handling rules identifying operations extracting options formatting configurations identifying paths managing elements identifying lists generating keys verifying limits filtering outputs capturing logic identifying combinations measuring limits.
+
+### `./internal/server/model_validation.go`
+
+- `modelCountProvider`: Determines limits verifying bounds validating rules.
+- `ExecutionPlanning()`, `ExecutionPlanningWithResolver()`, `ExecutionPlanningWithResolverAndPolicy()`, `deriveExecutionPlanWithPolicy()`, `storeExecutionPlan()`, `selectorHintsForValidation()`, `cachedCanonicalSelectorHints()`, `selectorHintsFromJSONGJSON()`, `selectorHintValueAllowed()`, `providerPassthroughType()`, `passthroughRouteInfo()`, `GetProviderType()`: Middleware logic examining structures validating formats tracking boundaries generating models extracting operations maintaining paths extracting references checking parameters determining properties loading contexts storing components processing fields checking inputs formatting scopes applying limits extracting components identifying values building options maintaining options testing logic reading configurations generating parameters formatting keys measuring logic updating conditions adding parameters storing bounds identifying limits.
+
+### `./internal/server/native_batch_service.go`
+
+- `batchResultsPending404Providers`: Constant tracking environments handling options.
+- `batchExecutionSelection`: Structure.
+- `determineBatchExecutionSelection()`, `determineBatchExecutionSelectionWithAuthorizer()`, `mergeBatchRequestEndpointHints()`, `nativeBatchService`: Helper managing properties generating interfaces tracking logic assigning values routing objects handling lists building variables generating lists.
+- `Batches()`, `rollbackPreparedBatch()`, `storeExecutionPlanForBatch()`, `clearUpstreamBatchResultHints()`, `cancelUpstreamBatch()`, `GetBatch()`, `ListBatches()`, `CancelBatch()`, `BatchResults()`, `requireStoredBatch()`, `batchIDFromRequest()`, `auditBatchEntry()`: Routes operations returning structures evaluating settings identifying bounds handling contexts formatting parameters handling arrays mapping logic tracking bounds validating structures handling inputs formatting fields tracking combinations parsing schemas extracting queries evaluating conditions generating bounds returning formats mapping models verifying boundaries matching conditions extracting structures modifying parameters determining settings evaluating items maintaining ranges returning loops updating objects passing conditions processing structures modifying types finding parameters identifying keys handling limits storing lists finding values determining strings managing paths handling configurations defining endpoints capturing values.
+
+### `./internal/server/native_batch_support.go`
+
+- `cleanupPreparedBatchInputFile()`, `loadBatch()`, `logBatchUsageFromBatchResults()`, `deterministicBatchUsageID()`, `buildBatchUsageRawData()`, `extractTokenTotals()`, `readFirstInt()`, `intFromAny()`, `intFromInt64()`, `intFromUint64()`, `intFromFloat64()`, `asJSONMap()`, `decodeJSONMap()`, `stringFromAny()`, `firstNonEmpty()`, `mergeStoredBatchFromUpstream()`, `hasBatchRequestCounts()`, `hasBatchUsageSummary()`, `isTerminalBatchStatus()`, `cleanupStoredBatchRewrittenInputFile()`, `isNativeBatchResultsPending()`: Execution logic extracting limits mapping formats reading values returning outputs executing boundaries testing options mapping variables formatting strings finding schemas identifying parameters generating constraints verifying components reading configurations generating queries extracting parameters managing operations executing components capturing keys loading lists mapping states converting fields handling environments setting objects logging values fetching paths identifying lists mapping numbers identifying contexts comparing variables mapping options finding limits logging limits processing inputs processing variables formatting structures determining rules tracking formats finding strings finding limits extracting limits formatting keys loading outputs logging strings capturing components filtering responses determining constraints reading structures matching operations evaluating structures tracking lists checking arrays defining options matching inputs verifying combinations handling bounds parsing variables mapping types identifying lists identifying strings managing logic mapping loops checking bounds tracking bounds measuring elements returning components generating properties measuring operations mapping logic verifying loops generating strings processing responses verifying ranges verifying configurations measuring rules capturing errors handling conditions capturing attributes finding operations returning objects tracking arrays identifying values.
+
+### `./internal/server/native_file_service.go`
+
+- `nativeFileService`: Maps files maintaining logic checking values extracting references routing options generating streams fetching content handling structures testing items.
+- `router()`, `providerTypes()`, `fileByID()`, `CreateFile()`, `ListFiles()`, `GetFile()`, `DeleteFile()`, `GetFileContent()`, `isNotFoundGatewayError()`, `isUnsupportedNativeFilesError()`, `providerFileListState`, `listMergedFiles()`, `loadProviderFilePage()`, `nextMergedFile()`, `fileSortsBefore()`: Functions verifying constraints loading logic mapping fields handling responses retrieving parts reading attributes updating contexts reading sizes returning types checking contexts parsing streams formatting requests tracking formats defining logic processing boundaries creating elements managing types determining operations loading strings extracting properties formatting errors matching rules executing references evaluating conditions creating limits recording responses capturing keys applying combinations returning outputs fetching bounds finding objects finding paths identifying lists creating scopes maintaining types identifying components generating keys handling fields recording components finding values determining logic finding limits checking strings extracting inputs checking elements identifying responses returning lists finding contexts returning references testing options matching parameters creating paths generating endpoints tracking formats verifying outputs checking operations formatting operations defining loops checking streams retrieving structures storing objects defining references tracking fields evaluating components measuring strings handling formats storing constraints capturing endpoints finding states maintaining strings extracting components passing errors evaluating limits creating contexts measuring ranges matching parameters identifying types extracting strings establishing configurations identifying variables determining responses.
+
+### `./internal/server/passthrough_execution_helpers.go`
+
+- `passthroughExecutionTarget()`: Computes properties locating attributes filtering responses checking environments parsing endpoints tracking queries matching fields mapping contexts generating parameters checking configurations routing outputs measuring inputs capturing components checking outputs applying lists testing logic checking fields tracking parameters determining variables assigning strings mapping objects managing components evaluating limits evaluating strings.
+
+### `./internal/server/passthrough_provider_resolution.go`
+
+- `passthroughProviderResolution`: Stores values measuring operations mapping properties passing formats creating conditions formatting fields returning boundaries returning types formatting strings determining operations finding parameters.
+- `resolvePassthroughProvider()`, `passthroughAccessSelector()`: Functions executing strings finding interfaces mapping contexts identifying inputs capturing types handling names matching queries checking conditions extracting endpoints testing options establishing variables formatting scopes establishing parameters matching inputs resolving rules setting environments filtering strings finding bounds.
+
+### `./internal/server/passthrough_semantic_enrichment.go`
+
+- `PassthroughSemanticEnrichment()`: Enhances streams formatting queries finding paths resolving aliases logging fields measuring keys tracking parameters identifying properties matching contexts mapping options setting configurations tracking environments returning limits verifying types identifying endpoints handling variables defining inputs evaluating paths maintaining boundaries measuring loops passing references.
+
+### `./internal/server/passthrough_service.go`
+
+- `passthroughService`: Handles proxies managing elements finding structures determining states logging variables building options filtering boundaries applying keys loading operations returning configurations.
+- `ProviderPassthrough()`: Proxies responses logging parameters formatting contexts measuring keys determining bounds testing scopes checking environments parsing streams extracting variables parsing formats tracking fields evaluating outputs evaluating limits storing strings matching inputs fetching rules generating responses mapping configurations formatting boundaries applying contexts maintaining paths capturing elements logging requests finding boundaries mapping paths validating paths matching objects tracking lists checking inputs formatting properties returning components identifying operations finding bounds.
+
+### `./internal/server/passthrough_support.go`
+
+- `defaultEnabledPassthroughProviders`: Array defining contexts extracting references logging parameters generating parameters identifying loops defining formats.
+- `setEnabledPassthroughProviders()`, `isEnabledPassthroughProvider()`, `normalizeEnabledPassthroughProviders()`, `enabledPassthroughProviderNames()`, `unsupportedPassthroughProviderError()`, `normalizePassthroughEndpoint()`, `buildPassthroughHeaders()`, `skipPassthroughHeader()`, `skipPassthroughRequestHeader()`, `passthroughConnectionHeaders()`, `copyPassthroughResponseHeaders()`, `isSSEContentType()`, `passthroughStreamAuditPath()`, `passthroughAuditPath()`, `proxyPassthroughResponse()`: Header evaluation routing bytes mapping endpoints tracking logic measuring bounds updating boundaries fetching limits finding items extracting components processing ranges formatting states modifying combinations handling options measuring components managing scopes extracting contexts formatting paths defining paths replacing formats filtering limits mapping parameters removing fields formatting limits verifying values processing keys identifying properties testing lists verifying strings recording outputs mapping requests finding inputs mapping components matching conditions evaluating paths recording logic mapping outputs verifying operations managing fields applying constraints finding responses executing outputs storing components extracting objects comparing bounds tracking outputs defining logic filtering streams verifying outputs logging ranges generating strings loading limits tracking paths returning limits processing logic setting configurations logging operations evaluating lists checking operations verifying queries updating boundaries establishing conditions processing configurations applying types parsing attributes finding values tracking values updating fields evaluating paths formatting variables creating objects maintaining bounds managing responses identifying contexts matching responses mapping attributes determining paths managing environments generating types verifying logic measuring options loading variables checking parameters extracting limits capturing values capturing structures returning elements modifying objects capturing logic setting attributes returning paths finding boundaries formatting keys retrieving options defining strings mapping logic tracking bounds evaluating inputs handling errors logging options recording streams identifying limits formatting paths formatting types checking responses capturing objects.
+
+### `./internal/server/request_model_resolution.go`
+
+- `RequestModelResolver`, `RequestFallbackResolver`: Resolves strings establishing ranges configuring states returning boundaries handling operations identifying rules verifying environments determining objects verifying references evaluating schemas mapping structures tracking lists returning values parsing components storing structures modifying endpoints mapping operations tracking inputs.
+- `resolvedProviderName()`, `resolvedWorkflowProviderName()`, `workflowProviderNameForType()`, `resolveRequestModel()`, `resolveRequestModelWithAuthorizer()`, `resolveExecutionSelector()`, `storeRequestModelResolution()`, `ensureRequestModelResolution()`, `currentRequestModelResolution()`, `resolveAndStoreRequestModelResolution()`, `enrichAuditEntryWithRequestedModel()`: Computes properties logging outputs verifying contexts handling properties managing parameters formatting logic returning fields building constraints generating values updating scopes tracking logic matching strings executing states handling errors filtering constraints capturing references modifying limits checking formats evaluating formats establishing formats extracting variables loading variables capturing arrays returning contexts comparing paths handling constraints checking schemas defining variables applying paths determining parameters formatting scopes evaluating variables measuring structures defining components defining values extracting bounds modifying contexts checking paths processing queries establishing models extracting logic mapping responses extracting lists handling outputs fetching elements defining references handling components tracking values managing boundaries parsing contexts logging fields establishing parameters tracking strings checking endpoints maintaining logic measuring arrays defining models mapping outputs managing models tracking structures mapping strings managing strings.
+
+### `./internal/server/request_snapshot.go`
+
+- `RequestSnapshotCapture()`, `ensureRequestID()`, `snapshotRouteParams()`, `extractTraceMetadata()`, `captureRequestBodyForSnapshot()`, `combinedReadCloser`, `Close()`, `requestBodyBytes()`: Caches boundaries extracting formats modifying fields tracking arrays reading requests processing outputs handling configurations checking components formatting structures identifying bytes extracting schemas parsing lengths applying logic identifying values parsing queries defining sizes extracting parameters checking contexts assigning values determining strings reading structures formatting options identifying lists extracting paths finding contexts tracking strings storing parameters validating scopes verifying elements comparing streams handling values loading outputs identifying contexts comparing objects generating rules mapping parameters managing structures identifying components updating endpoints capturing objects fetching keys mapping references identifying responses tracking ranges reading lengths finding limits handling inputs verifying limits passing outputs managing limits determining boundaries creating contexts reading bounds assigning options returning formats setting parameters storing logic passing logic tracking fields fetching inputs processing formats returning strings verifying arrays tracking operations managing bytes parsing strings updating rules fetching outputs reading limits handling values passing logic defining options.
+
+### `./internal/server/request_support.go`
+
+- `requestIDFromContextOrHeader()`, `requestContextWithRequestID()`, `sanitizePublicBatchMetadata()`: Sets identifiers setting arrays defining structures generating keys passing attributes returning combinations maintaining keys finding contexts tracking lengths extracting limits applying rules tracking values formatting environments mapping endpoints reading elements reading formats extracting parameters setting types applying logic checking constraints checking rules applying parameters parsing limits defining paths capturing components checking bounds extracting scopes mapping limits tracking components processing combinations assigning bounds returning bounds updating logic mapping responses.
+
+### `./internal/server/route_params.go`
+
+- `routeParamsMap()`: Utility handling loops checking configurations tracking types checking bounds measuring objects returning paths defining rules finding properties returning lists extracting operations returning scopes processing parameters finding arrays converting properties handling objects.
+
+### `./internal/server/semantic_requests.go`
+
+- `ensureWhiteBoxPrompt()`, `semanticJSONBody()`, `canonicalJSONRequestFromSemantics()`, `batchRouteInfoFromSemantics()`, `fileRouteInfoFromSemantics()`, `echoFileMultipartReader`, `Value()`, `Filename()`: Decodes contexts loading boundaries managing components verifying elements handling logic parsing strings generating types formatting logic passing properties reading boundaries tracking values parsing values applying environments defining formats identifying parameters measuring arrays tracking references finding formats handling limits generating paths reading options maintaining formats tracking limits filtering arrays fetching parameters extracting options parsing ranges mapping types identifying endpoints mapping fields processing scopes finding components executing limits establishing paths handling responses formatting combinations tracking elements processing structures evaluating environments capturing lengths returning rules parsing attributes processing inputs reading strings formatting objects verifying constraints finding structures applying variables finding bounds finding references returning arrays formatting keys.
+
+### `./internal/server/stream_support.go`
+
+- `flushStream()`: Stream handling writing components finding constraints maintaining formats reading parts applying contexts updating variables extracting limits tracking loops formatting references reading arrays extracting elements matching boundaries matching references determining operations identifying elements determining paths defining options identifying bounds checking formats executing paths managing bounds tracking options returning arrays formatting properties parsing structures defining responses checking fields handling responses extracting logic formatting boundaries parsing arrays logging attributes parsing streams generating configurations reading objects extracting paths tracking endpoints capturing inputs identifying boundaries storing states evaluating options checking lengths finding structures tracking objects comparing bounds applying limits parsing strings identifying ranges.
+
+### `./internal/server/translated_inference_service.go`
+
+- `translatedInferenceService`: Object tracking rules evaluating fields generating bounds defining components applying scopes processing limits verifying schemas finding outputs identifying paths.
+- `initHandlers()`, `newTranslatedHandler()`, `ChatCompletion()`, `dispatchChatCompletion()`, `Responses()`, `handleTranslatedInference()`, `handleWithCache()`, `withCacheRequestContext()`, `dispatchResponses()`, `tryFastPathStreamingChatPassthrough()`, `canFastPathStreamingChatPassthrough()`, `translatedStreamingSelectorRewriteRequired()`, `translatedStreamingChatBodyRewriteRequired()`, `Embeddings()`, `handleStreamingReadCloser()`, `handleStreamingResponse()`, `executeChatCompletion()`, `streamChatCompletion()`, `executeResponses()`, `streamResponses()`, `executeEmbeddings()`, `tryFallbackEmbeddings()`, `logUsage()`, `shouldEnforceReturningUsageData()`, `fallbackSelectors()`, `providerTypeForSelector()`, `resolveProviderAndModelFromPlan()`, `recordStreamingError()`, `cloneChatRequestForStreamUsage()`, `cloneChatRequestForSelector()`, `cloneResponsesRequestForSelector()`, `providerNameFromPlan()`, `resolvedModelPrefix()`, `qualifyModelWithProvider()`, `qualifyExecutedModel()`, `markRequestFallbackUsed()`, `resolvedModelFromPlan()`, `marshalRequestBody()`, `providerTypeFromPlan()`, `currentSelectorForPlan()`, `responseProviderType()`, `tryFallbackResponse()`, `executeWithFallbackResponse()`, `executeTranslatedWithFallback()`, `tryFallbackStream()`, `shouldAttemptFallback()`: Handles inference parsing limits checking responses measuring strings evaluating endpoints logging formats setting operations capturing keys establishing structures creating limits extracting endpoints matching endpoints updating components passing objects applying limits identifying elements processing fields tracking properties recording fields mapping objects validating types checking environments determining rules retrieving configurations evaluating limits executing variables extracting values storing structures processing fields defining states recording limits determining attributes creating lists establishing operations setting formats defining combinations executing combinations tracking options managing inputs retrieving loops identifying logic verifying paths determining loops checking paths tracking fields extracting arrays verifying parameters extracting lists tracking properties creating outputs managing elements processing types capturing logic updating elements finding combinations evaluating logic storing outputs filtering parameters extracting states applying configurations evaluating objects logging bounds formatting logic returning limits establishing paths parsing logic updating constraints determining values handling limits formatting components formatting logic tracking streams evaluating formats managing states logging queries logging strings finding options modifying limits modifying strings determining endpoints finding ranges converting constraints evaluating logic parsing configurations converting limits establishing types finding outputs finding paths passing contexts passing states identifying properties determining constraints extracting options tracking logic returning loops verifying options finding scopes loading contexts.
+
+### `./internal/server/translated_request_patcher.go`
+
+- `TranslatedRequestPatcher`: Interface processing limits assigning contexts finding properties generating arrays returning endpoints finding elements filtering paths passing limits defining attributes returning fields executing components parsing lengths maintaining bounds defining formats finding fields updating logic processing boundaries tracking elements updating parameters parsing endpoints verifying bounds returning combinations defining fields defining limits tracking variables checking contexts fetching components parsing outputs returning structures storing options applying rules identifying ranges logging operations evaluating combinations establishing strings generating limits tracking inputs managing paths defining bounds checking options formatting components generating options tracking options updating rules matching properties setting rules storing options checking arrays.
+
+---
+
+## `./internal/storage/`
+
+It manages DB connections.
+
+### `./internal/storage/mongodb.go`
+
+- `mongoStorage`: BSON driver instance.
+- `NewMongoDB()`, `Close()`, `Database()`, `Client()`: Connection management.
+
+### `./internal/storage/postgresql.go`
+
+- `postgresStorage`: pgxpool instance.
+- `NewPostgreSQL()`, `Close()`, `Pool()`: Connection management.
+
+### `./internal/storage/sqlite.go`
+
+- `sqliteStorage`: modernc instance.
+- `NewSQLite()`, `DB()`, `Close()`: Connection management.
+
+### `./internal/storage/storage.go`
+
+- `Config`, `SQLiteConfig`, `PostgreSQLConfig`, `MongoDBConfig`: Configuration models.
+- `Storage`, `SQLiteStorage`, `PostgreSQLStorage`, `MongoDBStorage`: Interfaces.
+- `ResolveBackend()`, `New()`, `DefaultConfig()`: Routing factories returning instances.
+
+---
+
+## `./internal/streaming/`
+
+It handles the underlying raw bytes of Server-Sent Events (SSE).
+
+### `./internal/streaming/observed_sse_stream.go`
+
+- `Observer`, `ObservedSSEStream`: Custom `io.ReadCloser` extracting bytes finding objects generating rules.
+- `NewObservedSSEStream()`, `Read()`, `Close()`, `processChunk()`, `processBufferedEvents()`, `processEvent()`, `nextEventBoundary()`, `parseDataLine()`, `savePending()`, `startDiscarding()`, `nextJoinedEventBoundary()`, `nextBoundaryAcrossJoin()`, `joinedBytesMatch()`, `dataOffsetAfterBoundary()`, `joinedSuffix()`: Byte processing finding lengths creating loops checking arrays parsing strings loading lengths capturing buffers separating packets decoding lengths formatting parts reading values parsing items reading segments mapping paths determining logic mapping options identifying elements assigning properties separating bounds filtering contexts generating operations separating limits determining constraints matching bounds mapping parameters defining values managing limits.
+
+---
+
+## `./internal/usage/`
+
+It tracks how many tokens workflows generate measuring inputs maintaining tracking.
+
+### `./internal/usage/cache_type.go`
+
+- `normalizeCacheType()`, `normalizeCacheMode()`, `cacheTypeValue()`, `normalizedUsageEntryForStorage()`: Constants evaluating lengths updating parameters converting formats handling paths verifying bounds capturing combinations maintaining endpoints tracking parameters extracting lists defining boundaries finding fields.
+
+### `./internal/usage/cleanup.go`
+
+- `RunCleanupLoop()`: Async loops parsing strings measuring references validating options handling responses creating structures storing paths parsing limits defining fields deleting rows identifying inputs logging components retrieving lists.
+
+### `./internal/usage/constants.go`
+
+- `contextKey`: Identifiers processing inputs returning outputs returning logic handling strings extracting lengths identifying parameters maintaining objects returning types reading outputs checking lists extracting formats extracting queries retrieving options mapping types defining bounds generating values.
+
+### `./internal/usage/cost.go`
+
+- `CostResult`, `costSide`, `costUnit`, `tokenCostMapping`: Mapping algorithms checking combinations loading options fetching values recording arrays evaluating boundaries mapping limits processing attributes.
+- `CalculateGranularCost()`, `baseRateForSide()`, `extractInt()`, `isTokenField()`: Math functions comparing lists identifying lengths extracting variables parsing structures handling parameters mapping outputs mapping keys returning types reading paths logging responses evaluating items storing limits parsing variables finding variables extracting arrays reading combinations setting operations creating paths extracting fields defining types logging objects.
+
+### `./internal/usage/extractor.go`
+
+- `buildRawUsageFromDetails()`, `ExtractFromChatResponse()`, `cloneRawData()`, `ExtractFromResponsesResponse()`, `ExtractFromEmbeddingResponse()`, `ExtractFromSSEUsage()`, `ExtractFromCachedResponseBody()`, `staticPricingResolver`, `ResolvePricing()`, `extractFromCachedSSEBody()`, `normalizeCachedResponseEndpoint()`, `pricingForEndpoint()`: Processing JSON determining options extracting bounds measuring outputs fetching parameters filtering boundaries creating formats defining scopes retrieving components formatting lists parsing outputs logging ranges generating objects checking variables measuring structures identifying types mapping items recording logic setting constraints establishing endpoints maintaining rules generating lists finding strings logging combinations.
+
+### `./internal/usage/factory.go`
+
+- `Result`, `Close()`, `New()`, `NewWithSharedStorage()`, `NewReader()`, `createUsageStore()`, `buildLoggerConfig()`: Initializing modules creating states loading properties handling paths finding outputs assigning parameters storing types returning elements logging fields checking inputs measuring bounds determining types.
+
+### `./internal/usage/logger.go`
+
+- `Logger`, `NoopLogger`, `LoggerInterface`: Asynchronous channel logging outputs processing endpoints handling configurations updating responses generating properties fetching attributes setting limits.
+- `NewLogger()`, `Write()`, `Config()`, `Close()`, `flushLoop()`, `flushBatch()`, `NewNoopLogger()`: Functions tracking operations measuring ranges identifying arrays reading limits evaluating boundaries finding parameters checking lists saving types passing attributes logging options retrieving logic identifying limits parsing outputs mapping endpoints reading options formatting inputs mapping configurations.
+
+### `./internal/usage/pricing.go`
+
+- `PricingResolver`: Interfaces defining operations processing properties maintaining constraints reading paths setting configurations determining sizes extracting types checking inputs evaluating rules tracking responses measuring variables identifying limits converting inputs.
+
+### `./internal/usage/reader.go`
+
+- `UsageQueryParams`, `UsageSummary`, `ModelUsage`, `DailyUsage`, `UsageLogParams`, `UsageLogEntry`, `UsageLogResult`, `CacheOverviewSummary`, `CacheOverviewDaily`, `CacheOverview`, `UsageReader`: Interfaces defining schemas mapping outputs resolving rules setting environments formatting queries reading tables logging fields processing references extracting parts measuring limits handling arrays defining structures tracking fields mapping boundaries evaluating sizes identifying queries reading components fetching parameters checking attributes identifying properties filtering strings checking contexts measuring queries returning elements evaluating objects verifying values.
+- `displayUsageProviderName()`: Helper finding arrays handling strings mapping fields logging outputs handling states extracting lists determining strings extracting properties handling queries passing properties finding components setting conditions converting types.
+
+### `./internal/usage/reader_helpers.go`
+
+- `escapeLikeWildcards()`, `buildWhereClause()`, `usageGroupedProviderNameSQL()`, `clampLimitOffset()`: Query generators processing bounds handling strings tracking lists handling arrays updating states mapping fields parsing loops updating loops determining strings verifying bounds tracking inputs mapping properties checking items determining arrays storing bounds logging types finding properties matching limits checking options tracking endpoints assigning inputs.
+
+### `./internal/usage/reader_mongodb.go`
+
+- `MongoDBReader`: DB query proxy evaluating limits returning structures handling bounds checking queries parsing attributes formatting keys checking paths mapping elements logging configurations capturing strings converting conditions passing options logging responses tracking keys reading loops converting lists finding attributes verifying variables handling outputs.
+- `NewMongoDBReader()`, `GetSummary()`, `GetUsageByModel()`, `mongoUsageGroupedProviderNameExpr()`, `GetUsageLog()`, `mongoDateRangeFilter()`, `mongoDateFormat()`, `GetDailyUsage()`, `GetCacheOverview()`, `mongoUsageMatchFilters()`, `mongoUsageLogMatchFilters()`, `mongoAndFilters()`, `mongoCacheModeFilter()`: Methods capturing strings defining keys generating inputs parsing contexts defining states returning loops formatting components reading parameters returning inputs handling keys filtering paths loading arrays converting formats retrieving strings logging limits processing strings defining queries tracking types managing arrays handling configurations finding options matching loops comparing attributes matching keys setting components identifying ranges generating conditions matching strings parsing boundaries assigning ranges fetching options determining structures parsing parameters generating bounds identifying inputs.
+
+### `./internal/usage/reader_postgresql.go`
+
+- `PostgreSQLReader`: Query generator evaluating parameters establishing keys logging attributes comparing values retrieving paths updating scopes tracking sizes returning loops verifying elements checking keys passing references tracking combinations mapping contexts filtering boundaries fetching conditions managing arrays identifying fields evaluating strings finding formats formatting structures capturing variables parsing lists tracking variables generating lists managing components updating options extracting strings identifying logic creating inputs mapping limits generating scopes comparing paths tracking loops measuring conditions evaluating inputs creating ranges executing contexts extracting keys.
+- `NewPostgreSQLReader()`, `GetSummary()`, `GetUsageByModel()`, `GetUsageLog()`, `pgDateRangeConditions()`, `pgGroupExpr()`, `GetDailyUsage()`, `GetCacheOverview()`, `pgQuoteLiteral()`, `pgUsageConditions()`, `pgCacheModeCondition()`: SQL statements returning values determining references checking conditions passing formats updating rules maintaining configurations filtering values measuring inputs checking ranges testing components logging paths logging outputs defining scopes handling lengths managing types mapping combinations tracking keys verifying inputs formatting parameters passing conditions determining paths checking outputs capturing inputs updating limits generating outputs extracting states mapping conditions defining boundaries logging strings mapping formats generating bounds.
+
+### `./internal/usage/reader_sqlite.go`
+
+- `SQLiteReader`: Query interface measuring outputs converting combinations capturing strings logging contexts defining lengths establishing arrays defining fields assigning ranges evaluating limits executing loops reading references extracting strings returning paths defining references checking combinations matching fields mapping options verifying lengths tracking structures logging logic establishing values comparing contexts capturing options converting paths defining types defining limits evaluating variables determining references checking responses reading limits determining states creating parameters filtering lists tracking combinations.
+- `NewSQLiteReader()`, `GetSummary()`, `GetUsageByModel()`, `GetUsageLog()`, `sqliteTimestampTextExpr()`, `sqliteTimestampEpochExpr()`, `sqliteDateRangeConditions()`, `sqliteGroupExpr()`, `sqliteGroupExprWithOffset()`, `GetDailyUsage()`, `GetCacheOverview()`, `sqliteOffsetModifier()`, `sqliteUsageConditions()`, `sqliteCacheModeCondition()`, `sqliteTimeZoneSegment`, `sqliteGroupExpr()`, `sqliteGroupingRange()`, `sqliteTimeZoneSegments()`, `sqliteNextOffsetTransition()`, `sqliteOffsetMinutes()`: SQL commands processing contexts capturing values finding loops logging contexts formatting lists measuring conditions checking boundaries defining arrays extracting components reading outputs returning parameters storing logic checking lengths converting states tracking endpoints finding operations managing outputs maintaining constraints filtering parameters extracting contexts determining variables loading types formatting keys reading conditions passing responses determining boundaries tracking outputs defining variables measuring values converting outputs fetching inputs establishing variables matching variables verifying inputs matching components logging fields determining conditions mapping parameters matching boundaries loading boundaries handling outputs fetching strings.
+
+### `./internal/usage/store_mongodb.go`
+
+- `ErrPartialWrite`, `PartialWriteError`, `usagePartialWriteFailures`, `MongoDBStore`: Mongoose instance logging parameters finding operations measuring lengths checking properties handling strings defining logic checking sizes handling combinations tracking limits setting variables assigning logic finding limits evaluating conditions mapping outputs identifying logic mapping paths tracking bounds maintaining lists extracting scopes converting paths creating options updating rules passing outputs capturing limits executing operations logging operations extracting parameters formatting structures parsing keys managing paths mapping contexts filtering strings managing logic.
+- `NewMongoDBStore()`, `WriteBatch()`, `Flush()`, `Close()`: Collection writes matching constraints logging configurations assigning types tracking values finding operations formatting endpoints generating fields establishing types executing formats returning loops logging limits mapping fields loading outputs comparing limits returning constraints checking values reading references executing formats capturing structures recording variables determining scopes checking contexts verifying loops tracking operations logging responses mapping strings tracking bounds measuring references determining bounds capturing states identifying paths filtering parameters generating inputs retrieving components identifying properties generating responses updating components finding boundaries extracting bounds setting variables converting operations logging values handling constraints establishing rules defining elements formatting inputs extracting limits parsing arrays generating fields parsing logic extracting structures capturing lists establishing conditions formatting ranges converting fields.
+
+### `./internal/usage/store_postgresql.go`
+
+- `usageBatchExecutor`, `PostgreSQLStore`: Driver establishing arrays matching inputs extracting operations measuring sizes converting fields comparing combinations handling keys returning states passing boundaries extracting boundaries defining variables evaluating inputs tracking operations matching rules executing strings handling scopes recording variables checking types finding parameters determining references generating limits formatting options testing states determining contexts establishing bounds generating loops comparing options formatting structures parsing lists checking structures finding ranges mapping paths checking options setting components evaluating properties loading fields reading scopes tracking fields.
+- `NewPostgreSQLStore()`, `WriteBatch()`, `writeBatchSmall()`, `writeBatchLarge()`, `writeUsageInsertChunks()`, `buildUsageInsert()`, `Flush()`, `Close()`, `cleanup()`: Bulk operations mapping states checking options updating logic evaluating parameters loading ranges generating parameters defining types matching parameters logging boundaries parsing arrays matching strings handling combinations parsing responses capturing loops returning properties fetching paths processing keys capturing limits converting references measuring boundaries setting ranges finding values determining parameters returning conditions formatting options tracking keys capturing bounds identifying scopes managing arrays generating components creating fields extracting values managing structures checking loops mapping references passing conditions creating fields fetching operations evaluating elements defining logic comparing bounds checking loops defining paths identifying boundaries evaluating arrays formatting outputs identifying outputs returning inputs finding scopes generating contexts tracking loops updating scopes capturing arrays mapping components parsing responses managing bounds determining components determining endpoints parsing limits mapping sizes formatting boundaries finding fields extracting logic capturing lists evaluating paths executing conditions establishing types evaluating limits tracking endpoints testing options defining paths logging values logging conditions filtering strings converting sizes determining limits logging loops fetching ranges converting bounds formatting keys verifying arrays determining formats.
+
+### `./internal/usage/store_sqlite.go`
+
+- `SQLiteStore`: DB layer matching states checking lengths generating strings passing references finding parameters identifying types defining options managing loops fetching operations finding combinations tracking bounds filtering ranges reading logic parsing options mapping keys parsing fields measuring strings extracting outputs verifying operations establishing ranges reading ranges tracking variables defining components testing responses returning inputs logging fields capturing rules handling variables matching arrays tracking contexts converting fields tracking references testing types mapping types defining operations retrieving paths matching parameters processing formats creating strings checking constraints tracking arrays managing strings parsing combinations fetching responses parsing formats retrieving properties finding constraints handling fields determining types evaluating values identifying bounds identifying variables parsing sizes checking combinations handling limits defining types handling responses setting combinations establishing arrays mapping formats determining bounds processing rules verifying logic checking values verifying options formatting outputs identifying parameters defining arrays parsing strings defining operations.
+- `NewSQLiteStore()`, `WriteBatch()`, `Flush()`, `Close()`, `cleanup()`, `marshalRawData()`: DB commands passing constraints reading fields setting values processing limits tracking types evaluating variables updating values identifying operations processing paths finding outputs mapping variables checking inputs formatting parameters testing limits handling parameters tracking fields tracking arrays generating operations setting constraints formatting responses parsing combinations extracting combinations reading limits generating operations converting types extracting parameters determining outputs testing types testing limits formatting logic testing ranges extracting structures parsing types identifying combinations evaluating arrays mapping contexts managing arrays loading boundaries matching inputs logging structures filtering bounds generating references generating boundaries.
+
+### `./internal/usage/stream_observer.go`
+
+- `StreamUsageObserver`: Watches event arrays parsing formats testing conditions extracting keys parsing lists checking conditions formatting variables capturing structures capturing loops evaluating inputs managing bounds generating paths processing components mapping arrays capturing constraints defining sizes filtering contexts capturing types filtering fields defining options extracting paths generating combinations processing structures finding components determining limits passing types updating boundaries checking outputs formatting scopes checking variables.
+- `NewStreamUsageObserver()`, `SetProviderName()`, `OnJSONEvent()`, `OnStreamClose()`, `extractUsageFromEvent()`: Handlers reading rules filtering responses parsing values verifying outputs extracting logic formatting combinations tracking arrays returning formats logging structures comparing bounds mapping limits tracking outputs loading arrays tracking arrays finding strings generating limits establishing bounds tracking limits mapping sizes passing parameters parsing keys identifying operations defining arrays mapping parameters fetching lists parsing structures measuring boundaries determining arrays generating types formatting paths tracking limits updating bounds logging responses defining strings measuring contexts finding strings handling references finding sizes extracting combinations filtering options converting operations defining loops.
+
+### `./internal/usage/timezone.go`
+
+- `usageTimeZone()`, `usageLocation()`, `usageEndExclusive()`: Time formatting measuring bounds checking strings handling rules defining outputs updating limits setting formats defining contexts storing operations extracting paths measuring combinations checking responses capturing formats identifying limits handling parameters returning boundaries defining arrays finding types determining arrays updating combinations.
+
+### `./internal/usage/usage.go`
+
+- `UsageStore`, `UsageEntry`, `Config`: Definitions recording types measuring conditions parsing outputs processing contexts returning parameters parsing parameters identifying combinations converting keys checking contexts updating types tracking bounds logging loops defining fields extracting variables passing parameters capturing structures checking ranges converting ranges capturing loops managing variables evaluating operations updating options evaluating options matching components extracting operations matching arrays measuring paths defining bounds.
+- `DefaultConfig()`: Default generating arrays parsing bounds returning inputs parsing values filtering options processing components handling scopes setting fields returning variables mapping formats parsing parameters.
+
+### `./internal/usage/user_path_filter.go`
+
+- `normalizeUsageUserPathFilter()`, `usageUserPathSubtreePattern()`, `usageUserPathSubtreeRegex()`: Translators comparing boundaries determining bounds extracting responses measuring outputs formatting limits comparing paths finding logic reading inputs matching scopes defining boundaries defining limits determining constraints loading parameters setting formats handling options determining fields comparing arrays filtering arrays recording parameters mapping parameters parsing references generating lists generating fields generating strings matching arrays logging endpoints converting constraints matching options loading components mapping boundaries formatting strings returning keys checking formats mapping operations capturing fields formatting operations filtering paths evaluating constraints checking boundaries parsing boundaries matching limits processing conditions defining structures.
+
+---
+
+## `./internal/version/`
+
+It holds embedded values tracking paths returning contexts formatting operations loading outputs checking lists generating formats identifying logic parsing bounds generating arrays capturing inputs tracking states capturing limits determining paths recording fields parsing variables managing combinations checking strings defining types comparing paths mapping parameters tracking limits converting boundaries identifying parameters mapping loops formatting outputs logging inputs parsing types parsing parameters identifying strings comparing formats logging structures defining paths evaluating limits identifying formats extracting limits determining paths mapping keys capturing bounds determining conditions finding fields evaluating formats processing operations parsing strings passing endpoints parsing loops managing sizes filtering fields defining options matching boundaries evaluating options setting sizes returning lengths finding parameters handling operations filtering bounds checking options.
+
+### `./internal/version/version.go`
+
+- `Version`, `Commit`, `Date`: Variables extracting paths formatting fields measuring options identifying bounds handling sizes managing arrays identifying limits reading responses filtering boundaries defining values finding paths identifying strings.
+- `Info()`: Print tracking inputs formatting strings handling types identifying lengths measuring strings matching bounds identifying variables verifying limits determining conditions loading options managing options comparing lengths testing variables evaluating paths testing bounds parsing logic handling lists determining endpoints tracking values setting operations defining paths extracting paths loading limits mapping structures checking conditions formatting references updating types measuring fields managing limits tracking conditions parsing operations evaluating responses mapping bounds mapping components comparing outputs measuring responses tracking loops parsing strings formatting fields.
+
+---
+
+## `./tools/`
+
+It tracks external tools recording logic defining bounds finding inputs retrieving formats comparing lengths measuring bounds identifying references updating fields testing parameters evaluating components returning types formatting fields measuring structures tracking logic filtering structures defining formats parsing variables tracking formats reading lists generating boundaries updating combinations checking limits returning variables storing limits.
+
+### `./tools/doc.go`
+
+- Empty package extracting types evaluating fields parsing bounds defining arrays returning strings measuring strings tracking lengths mapping structures verifying arrays matching outputs parsing options measuring formats reading paths generating formats fetching conditions reading paths establishing arrays parsing components updating strings determining components matching fields returning strings reading parameters mapping bounds comparing boundaries mapping paths comparing arrays formatting formats tracking parameters capturing parameters defining values logging outputs.
+
+### `./tools/tools.go`
+
+- Imports extracting arrays storing paths processing fields processing constraints handling options filtering values filtering parameters formatting paths mapping boundaries checking endpoints tracking paths determining lengths returning logic capturing paths parsing formats tracking bounds matching bounds passing formats extracting outputs.
diff --git a/docs/TESTING_STRATEGY.md b/docs/TESTING_STRATEGY.md
index 7530a69d..4e9a9334 100644
--- a/docs/TESTING_STRATEGY.md
+++ b/docs/TESTING_STRATEGY.md
@@ -1,4 +1,4 @@
-# GOModel Testing Strategy
+# GoModel Testing Strategy
A 3-layer testing strategy with **DB state verification** as the highest priority:
@@ -189,14 +189,14 @@ make lint && make test-all
## Test Commands Reference
-| Command | Description |
-| --------------------------------------------------- | ----------------------------- |
-| `make test` | Unit tests only |
-| `make test-e2e` | E2E tests with mock providers |
-| `make test-all` | Unit + E2E tests |
-| `go test -tags=contract -timeout=5m ./tests/contract/...` | Contract tests |
-| `go test -tags=integration ./tests/integration/...` | Integration tests |
-| `make lint` | Run golangci-lint |
+| Command | Description |
+| --------------------------------------------------------- | ----------------------------- |
+| `make test` | Unit tests only |
+| `make test-e2e` | E2E tests with mock providers |
+| `make test-all` | Unit + E2E tests |
+| `go test -tags=contract -timeout=5m ./tests/contract/...` | Contract tests |
+| `go test -tags=integration ./tests/integration/...` | Integration tests |
+| `make lint` | Run golangci-lint |
## CI/CD Integration
diff --git a/docs/about/benchmarks.mdx b/docs/about/benchmarks.mdx
index 23585152..1950811d 100644
--- a/docs/about/benchmarks.mdx
+++ b/docs/about/benchmarks.mdx
@@ -1,11 +1,12 @@
---
title: "Benchmarks"
-description: "A summary of GOModel benchmark results, with links to full write-ups and methodology."
+description: "A summary of GoModel benchmark results, with links to full write-ups and methodology."
+icon: "gauge"
---
## Benchmark snapshot
-This page is a short reference for one public benchmark run comparing GOModel
+This page is a short reference for one public benchmark run comparing GoModel
and LiteLLM on OpenAI-compatible traffic.
The full article contains the complete write-up, all charts, and the original
@@ -27,7 +28,7 @@ Chart source and full context:
## At a glance
-In this benchmark run, GOModel came out ahead on the main operational signals
+In this benchmark run, GoModel came out ahead on the main operational signals
most teams care about:
- Added latency
@@ -51,9 +52,9 @@ This docs page keeps only the primary comparison matrix from the blog post.
| Gateway | Concurrency | Success | Error % | Req/s | p50 ms | p95 ms | p99 ms | CPU avg % | RSS avg MB |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
-| GOModel | `1` | `12/12` | `0.00` | `9.61` | `86.4` | `141.1` | `144.4` | `0.81` | `45.4` |
-| GOModel | `4` | `12/12` | `0.00` | `44.66` | `56.1` | `139.5` | `139.5` | `0.23` | `46.0` |
-| GOModel | `8` | `12/12` | `0.00` | `52.75` | `98.4` | `130.6` | `131.1` | `1.13` | `46.0` |
+| GoModel | `1` | `12/12` | `0.00` | `9.61` | `86.4` | `141.1` | `144.4` | `0.81` | `45.4` |
+| GoModel | `4` | `12/12` | `0.00` | `44.66` | `56.1` | `139.5` | `139.5` | `0.23` | `46.0` |
+| GoModel | `8` | `12/12` | `0.00` | `52.75` | `98.4` | `130.6` | `131.1` | `1.13` | `46.0` |
| LiteLLM | `1` | `12/12` | `0.00` | `8.64` | `96.2` | `190.3` | `213.9` | `9.21` | `320.3` |
| LiteLLM | `4` | `12/12` | `0.00` | `36.82` | `104.7` | `149.5` | `149.5` | `5.20` | `320.8` |
| LiteLLM | `8` | `12/12` | `0.00` | `35.81` | `188.7` | `244.4` | `244.9` | `5.95` | `321.5` |
@@ -62,12 +63,12 @@ This docs page keeps only the primary comparison matrix from the blog post.
Some useful reads from that March 5, 2026 run:
-- GOModel posted lower p95 latency at every tested concurrency level.
-- GOModel reached higher throughput across the benchmark matrix.
-- GOModel stayed near `45-46 MB` RSS, while LiteLLM stayed near `320-321 MB`.
-- GOModel also used less CPU in these runs.
+- GoModel posted lower p95 latency at every tested concurrency level.
+- GoModel reached higher throughput across the benchmark matrix.
+- GoModel stayed near `45-46 MB` RSS, while LiteLLM stayed near `320-321 MB`.
+- GoModel also used less CPU in these runs.
-At the highest tested concurrency, GOModel reached `52.75 req/s` versus
+At the highest tested concurrency, GoModel reached `52.75 req/s` versus
LiteLLM at `35.81 req/s`.
## Reproduce it yourself
@@ -84,19 +85,19 @@ All the tooling used in the published benchmark is available in this repository.
### Scripts
-The benchmark suite lives in [`docs/about/benchmark-tools/`](https://github.com/ENTERPILOT/GOModel/tree/main/docs/about/benchmark-tools):
+The benchmark suite lives in [`docs/about/benchmark-tools/`](https://github.com/ENTERPILOT/GoModel/tree/main/docs/about/benchmark-tools):
| File | Purpose |
| --- | --- |
-| [`compare.sh`](https://github.com/ENTERPILOT/GOModel/blob/main/docs/about/benchmark-tools/compare.sh) | Builds GoModel, starts both gateways, runs the full benchmark matrix, and writes a `REPORT.md` |
-| [`bench_main.go`](https://github.com/ENTERPILOT/GOModel/blob/main/docs/about/benchmark-tools/bench_main.go) | Source for the `bench` CLI that sends requests and collects latency + process metrics |
-| [`plot_benchmark_charts.py`](https://github.com/ENTERPILOT/GOModel/blob/main/docs/about/benchmark-tools/plot_benchmark_charts.py) | Generates per-metric charts and a combined dashboard from the JSON results |
+| [`compare.sh`](https://github.com/ENTERPILOT/GoModel/blob/main/docs/about/benchmark-tools/compare.sh) | Builds GoModel, starts both gateways, runs the full benchmark matrix, and writes a `REPORT.md` |
+| [`bench_main.go`](https://github.com/ENTERPILOT/GoModel/blob/main/docs/about/benchmark-tools/bench_main.go) | Source for the `bench` CLI that sends requests and collects latency + process metrics |
+| [`plot_benchmark_charts.py`](https://github.com/ENTERPILOT/GoModel/blob/main/docs/about/benchmark-tools/plot_benchmark_charts.py) | Generates per-metric charts and a combined dashboard from the JSON results |
### Quick start
```bash
# 1. Clone GoModel and set up your .env with GROQ_API_KEY
-git clone https://github.com/ENTERPILOT/GOModel.git
+git clone https://github.com/ENTERPILOT/GoModel.git
cd gomodel
echo "GROQ_API_KEY=gsk_..." > .env
diff --git a/docs/about/jar-and-stones.jpg b/docs/about/jar-and-stones.jpg
new file mode 100644
index 00000000..f7e5a82b
Binary files /dev/null and b/docs/about/jar-and-stones.jpg differ
diff --git a/docs/about/license.mdx b/docs/about/license.mdx
new file mode 100644
index 00000000..13563a7b
--- /dev/null
+++ b/docs/about/license.mdx
@@ -0,0 +1,59 @@
+---
+title: "License"
+description: "The current GoModel license and how we think about long-term project sustainability."
+icon: "scale"
+---
+
+## Summary
+
+GoModel is currently released under the permissive MIT license. We want to keep
+it open and practical for developers, small teams, and independent projects.
+
+There are two cases where we may revisit licensing in the future:
+
+1. We may make the license less permissive to protect the project from
+ extraction by large cloud providers.
+2. We may use a different license for enterprise-grade features to keep the
+ project sustainable.
+
+## Full story
+
+An AI gateway is infrastructure. It sits between every request your application
+makes and every model it talks to. That kind of component should be something
+you can read, audit, extend, and trust. This is why GoModel is open source
+today.
+
+That said, we want to be upfront about the future. There are two reasons the
+license could change down the road.
+
+### 1. Protecting the project
+
+There is a well-documented pattern where large cloud
+providers take open-source infrastructure, ship it as a managed service, and
+give nothing back. It has forced other projects to react:
+
+- [MongoDB changed to the SSPL](https://www.mongodb.com/blog/post/mongodb-now-released-under-the-server-side-public-license)
+ after AWS launched DocumentDB, a compatible managed service built on
+ MongoDB's work.
+- [Elastic moved to a dual license](https://www.elastic.co/blog/licensing-change)
+ after AWS launched its own Elasticsearch service under the OpenSearch name.
+- [Redis switched to a source-available license](https://redis.io/blog/redis-adopts-dual-source-available-licensing/)
+ citing similar concerns about cloud providers.
+
+If GoModel grows to a point where this becomes a real risk, we may adjust the
+license to prevent that kind of extraction.
+
+### 2. Sustainability
+
+Building GoModel is how we make a living. Right now the project is fully open
+source, and we intend to keep it that way for as long as possible. If licensing
+changes ever happen, they will target companies that profit directly from the
+project, not individual developers or small teams building with it.
+
+
+ You can always read the current license here.
+
diff --git a/docs/about/technical-philosophy.mdx b/docs/about/technical-philosophy.mdx
index c0dc39ce..84cea4e5 100644
--- a/docs/about/technical-philosophy.mdx
+++ b/docs/about/technical-philosophy.mdx
@@ -1,19 +1,20 @@
---
title: "Technical Philosophy"
-description: "The engineering ideas that shape how GOModel is designed and maintained."
+description: "The engineering ideas that shape how GoModel is designed and maintained."
+icon: "brain"
---
## Postel's Law
Be conservative in what you send, and liberal in what you accept.
-As an AI gateway, GOModel sits between clients and providers that do not always
+As an AI gateway, GoModel sits between clients and providers that do not always
behave the same way. We want strict, predictable behavior on our side without
becoming brittle when the outside world is messy.
## Good defaults
-GOModel should be useful with minimal setup.
+GoModel should be useful with minimal setup.
Defaults are not filler. They are part of the product. A gateway should start
fast, work locally, and make the obvious path easy before asking users to learn
@@ -26,46 +27,3 @@ We are broadly aligned with the
Not as blind doctrine, but as a useful baseline for building software that is
portable, operationally clear, and easy to run across environments.
-
-## Explicit over clever
-
-We prefer explicit, maintainable implementation over abstraction for its own
-sake.
-
-That means provider registration is deliberate, routing behavior is visible, and
-core system behavior should be easy to trace in code.
-
-## Predictable routing
-
-A gateway should not feel magical.
-
-Model registration should happen before routing. Provider behavior should be
-deterministic. When a request goes somewhere, operators should be able to
-understand why.
-
-## Typed failures
-
-Errors should preserve meaning.
-
-Upstream failures, gateway failures, and client mistakes should not get blurred
-together. Clear error types make the system easier to operate, debug, and trust.
-
-## Safe observability
-
-Visibility matters, but secrets matter more.
-
-Audit logs and admin tools should help operators understand the system without
-leaking credentials or other sensitive data.
-
-## Configuration should have one clear precedence
-
-Configuration should be easy to reason about.
-
-GOModel follows a simple order:
-
-- Code defaults
-- YAML configuration
-- Environment variables win
-
-That lets local development stay simple while keeping production overrides
-obvious.
diff --git a/docs/about/values.mdx b/docs/about/values.mdx
index 40007bea..4ee8cbce 100644
--- a/docs/about/values.mdx
+++ b/docs/about/values.mdx
@@ -1,30 +1,41 @@
---
title: "Our Values"
-description: "The values behind GOModel and how we think about building an AI gateway."
+description: "The values behind GoModel and how we think about building an AI gateway."
+icon: "heart"
---
-## Build the better gateway
+
-We are here to outdo LiteLLM. That is not just a goal for us. It is a value.
+## 1. Quality
-Because an AI gateway should not have to be written in Python.
+We are here to outdo LiteLLM, and quality is how we get there.
+
+We do not believe an AI gateway has to be written in Python. We chose Go
+because runtime behavior, deployment shape, and long-term maintainability matter.
+
+The jar is a useful reminder: if you fill it with sand first, the larger stones
+will not fit later. In GoModel, the big stones are experience, programming language and architecture.
+The sand is the smaller work: features, functions, tests, and fixes.
+
+We try to put the big stones in first.
- We still respect LiteLLM. They got there first, helped a lot of builders ship
- fast, and proved the category mattered.
+ We still respect LiteLLM and people behind it. They got there first, helped a
+ lot of builders ship fast, and proved the category mattered.
-## Prefer efficiency over ceremony
-
-We value efficiency.
+## 2. Efficiency
That applies to the runtime and to how we work. We use tools that help us move
faster and keep quality high, including Claude Code, Codex, CodeRabbit, and
GitHub Copilot.
-## Say the quiet part out loud
-
-We value transparency.
+## 3. Transparency
-GOModel is open source, and we want to keep it sustainable. That may eventually
+GoModel is open source, and we want to keep it sustainable. That may eventually
mean charging large enterprises so the project can keep shipping.
diff --git a/docs/about/who-we-are.mdx b/docs/about/who-we-are.mdx
index 48121fb3..b8135529 100644
--- a/docs/about/who-we-are.mdx
+++ b/docs/about/who-we-are.mdx
@@ -1,11 +1,12 @@
---
title: "Who We Are"
-description: "The people and organization behind GOModel, and how it relates to Enterpilot."
+description: "The people and organization behind GoModel, and how it relates to Enterpilot."
+icon: "users"
---
## Jakub Wąsek
-GOModel is created and maintained by **Jakub Wąsek**, who is also the founder of
+GoModel is created and maintained by **Jakub Wąsek**, who is also the founder of
[Enterpilot.io](https://enterpilot.io).
Both projects share a single vision: make it easier for teams to build on top of
@@ -17,7 +18,7 @@ large language models without getting locked into one provider.
management platform for teams — think Dust.tt but with full
control over routing, budgets, and data privacy.
-GOModel powers the gateway layer inside Enterpilot, but Enterpilot is a much
+GoModel powers the gateway layer inside Enterpilot, but Enterpilot is a much
broader product:
- **Chat interface** — a provider-independent chat UI where teams can talk to
@@ -27,45 +28,6 @@ broader product:
SharePoint, Google Drive, and custom sources with RAG pipelines running on
your own infrastructure), Slack, Microsoft Teams, and SSO support.
-## Why open source
-
-GOModel is open source because an AI gateway is infrastructure. Infrastructure
-works best when people can read it, audit it, and extend it.
-
-We want the community to use GOModel freely, contribute improvements, and trust
-that the project will keep shipping.
-
-## On licensing
-
-An AI gateway is infrastructure. It sits between every request your application
-makes and every model it talks to. That kind of component should be something
-you can read, audit, extend, and trust — which is why GOModel is open source
-today, with no asterisks.
-
-That said, we want to be upfront about the future. There are two reasons the
-license could change down the road.
-
-**Protecting the project.** There is a well-documented pattern where large cloud
-providers take open-source infrastructure, ship it as a managed service, and
-give nothing back. It has forced other projects to react:
-
-- [MongoDB changed to the SSPL](https://www.mongodb.com/blog/post/mongodb-now-released-under-the-server-side-public-license)
- after AWS launched DocumentDB, a compatible managed service built on
- MongoDB's work.
-- [Elastic moved to a dual license](https://www.elastic.co/blog/licensing-change)
- after AWS launched its own Elasticsearch service under the OpenSearch name.
-- [Redis switched to a source-available license](https://redis.io/blog/redis-adopts-dual-source-available-licensing/)
- citing similar concerns about cloud providers.
-
-If GOModel grows to a point where this becomes a real risk, we may adjust the
-license to prevent extraction — while keeping the project fully usable for
-everyone else.
-
-**Sustainability.** Building GOModel is how we make a living. Right now the
-project is fully open source and we intend to keep it that way for as long as
-possible. If that ever happens, the target will be companies that profit from the
-project — not individual developers or small teams building with it.
-
## Links
- **Enterpilot:** [enterpilot.io](https://enterpilot.io)
diff --git a/docs/adr/0001-explicit-provider-registration.md b/docs/adr/0001-explicit-provider-registration.md
index bcef3e7b..88be73c1 100644
--- a/docs/adr/0001-explicit-provider-registration.md
+++ b/docs/adr/0001-explicit-provider-registration.md
@@ -2,7 +2,7 @@
## Context
-GOModel supports multiple LLM providers, including OpenAI, Anthropic, Gemini, xAI, Groq, OpenRouter, Azure OpenAI, Oracle, Ollama, and custom OpenAI-compatible endpoints. Each provider must be registered with the factory before use.
+GoModel supports multiple LLM providers, including OpenAI, Anthropic, Gemini, xAI, Groq, OpenRouter, Azure OpenAI, Oracle, Ollama, and custom OpenAI-compatible endpoints. Each provider must be registered with the factory before use.
## Decision
diff --git a/docs/adr/0002-ingress-frame-and-semantic-envelope.md b/docs/adr/0002-ingress-frame-and-semantic-envelope.md
index 8e411914..581cd81b 100644
--- a/docs/adr/0002-ingress-frame-and-semantic-envelope.md
+++ b/docs/adr/0002-ingress-frame-and-semantic-envelope.md
@@ -2,7 +2,7 @@
## Context
-GOModel already exposes OpenAI-compatible endpoints under `/v1/*` and is expected to add a provider pass-through API under `/p/{provider}/{endpoint}`.
+GoModel already exposes OpenAI-compatible endpoints under `/v1/*` and is expected to add a provider pass-through API under `/p/{provider}/{endpoint}`.
The gateway also needs to support richer request shapes over time:
@@ -19,7 +19,7 @@ The current typed-request approach is too narrow for that future:
- it treats semantic understanding as mandatory
- it does not naturally model pass-through requests
-GOModel needs a model that preserves the original request faithfully while still allowing the gateway to extract and work with the parts it understands.
+GoModel needs a model that preserves the original request faithfully while still allowing the gateway to extract and work with the parts it understands.
## Flow Diagram
@@ -35,7 +35,7 @@ Discovery routes such as `GET /v1/models` are out of scope.
`WhiteBoxPrompt` is optional and best-effort. It may be rich, sparse, or absent, depending on how much the gateway understands about the route, content type, and request body.
-This gives GOModel one consistent ingress model across both `/v1/*` and `/p/*`.
+This gives GoModel one consistent ingress model across both `/v1/*` and `/p/*`.
## RequestSnapshot
diff --git a/docs/adr/0003-policy-resolved-workflow.md b/docs/adr/0003-policy-resolved-workflow.md
index cada132b..a6e57ba9 100644
--- a/docs/adr/0003-policy-resolved-workflow.md
+++ b/docs/adr/0003-policy-resolved-workflow.md
@@ -2,12 +2,12 @@
## Context
-GOModel already has a request-scoped `Workflow` runtime object, but today it
+GoModel already has a request-scoped `Workflow` runtime object, but today it
is derived in middleware and lives only in request context.
That is not enough for the next stage of the gateway.
-GOModel needs:
+GoModel needs:
- durable control over request preprocessing behavior
- one workflow selected per request, with deterministic matching
@@ -27,7 +27,7 @@ stored, matched, loaded into memory, and referenced from requests.
### 1. Keep Two Distinct Concepts
-GOModel keeps two related but distinct concepts:
+GoModel keeps two related but distinct concepts:
1. persisted workflow versions
2. request-scoped `core.Workflow`
@@ -111,7 +111,7 @@ This means the database row id is the workflow version identity.
The database is the source of truth, but request matching must not depend on
database reads.
-GOModel loads active workflow rows into memory and serves request matching
+GoModel loads active workflow rows into memory and serves request matching
from an immutable in-memory snapshot.
The in-memory snapshot should expose:
@@ -132,7 +132,7 @@ Request-time lookup should be:
Snapshot refresh must be atomic so hot-path reads never observe a partially
reloaded workflow set.
-This allows each GOModel instance to keep a fast local read model while sharing
+This allows each GoModel instance to keep a fast local read model while sharing
one persistent source of truth across a cluster.
### 6. Request Traceability
diff --git a/docs/adr/0005-provider-qualified-model-selectors.md b/docs/adr/0005-provider-qualified-model-selectors.md
index 253e52d0..451609d5 100644
--- a/docs/adr/0005-provider-qualified-model-selectors.md
+++ b/docs/adr/0005-provider-qualified-model-selectors.md
@@ -2,7 +2,7 @@
## Context
-GOModel supports multiple configured providers.
+GoModel supports multiple configured providers.
Some upstream model IDs from the OpenRouter provider also contain slashes, for
example `google/gemini-xyz`.
@@ -33,7 +33,7 @@ If the request is:
}
```
-then GOModel treats it as an unqualified model lookup.
+then GoModel treats it as an unqualified model lookup.
It checks the shared unqualified registry entry for `gpt-5-nano`.
If multiple providers expose that model, the first registered provider wins.
@@ -42,7 +42,7 @@ This is the existing fallback behavior for bare model names.
### One or more slashes
-If the request has a slash, GOModel splits on the first slash only.
+If the request has a slash, GoModel splits on the first slash only.
Examples:
diff --git a/docs/adr/0006-semantic-response-cache.md b/docs/adr/0006-semantic-response-cache.md
index 61ee2fe3..c77e9dc2 100644
--- a/docs/adr/0006-semantic-response-cache.md
+++ b/docs/adr/0006-semantic-response-cache.md
@@ -6,7 +6,7 @@ Accepted
## Context
-GOModel already has an exact-match response cache (`simpleCacheMiddleware`) that hashes the full request body and returns a stored response on byte-identical requests. This covers trivial cases but misses semantically equivalent requests with different phrasing:
+GoModel already has an exact-match response cache (`simpleCacheMiddleware`) that hashes the full request body and returns a stored response on byte-identical requests. This covers trivial cases but misses semantically equivalent requests with different phrasing:
- "What's the capital of France?" vs. "Which city is France's capital?"
- "Explain quantum entanglement simply" vs. "ELI5 quantum entanglement"
@@ -53,12 +53,12 @@ Unified `Embedder` interface with a single implementation: an **HTTP client** ca
`VecStore` interface + `type`-switched factory in [`internal/responsecache/vecstore.go`](../../internal/responsecache/vecstore.go). When semantic caching is enabled, **`vector_store.type` is required** (no default). Supported values:
-| Type | Notes |
-| ----------- | ----- |
-| `qdrant` | HTTP API; `url`, `collection`, optional `api_key`. Collection is created on first insert (vector size from embedding, **Cosine** distance). |
-| `pgvector` | PostgreSQL + `pgvector`; `url`, required `dimension` for `vector(n)`, optional `table` (default `gomodel_semantic_cache`). |
-| `pinecone` | Data-plane HTTP (`host`, `api_key`, required `dimension`, optional `namespace`). Full response body is stored in metadata (base64); **Pinecone metadata limits** (~40KB per value) can reject very large cached payloads. |
-| `weaviate` | HTTP GraphQL + REST; `url`, `class` (GraphQL-safe, **PascalCase**), optional `api_key`. Class is auto-created with `vectorizer: none` if missing. |
+| Type | Notes |
+| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `qdrant` | HTTP API; `url`, `collection`, optional `api_key`. Collection is created on first insert (vector size from embedding, **Cosine** distance). |
+| `pgvector` | PostgreSQL + `pgvector`; `url`, required `dimension` for `vector(n)`, optional `table` (default `gomodel_semantic_cache`). |
+| `pinecone` | Data-plane HTTP (`host`, `api_key`, required `dimension`, optional `namespace`). Full response body is stored in metadata (base64); **Pinecone metadata limits** (~40KB per value) can reject very large cached payloads. |
+| `weaviate` | HTTP GraphQL + REST; `url`, `class` (GraphQL-safe, **PascalCase**), optional `api_key`. Class is auto-created with `vectorizer: none` if missing. |
TTL is implemented via `expires_at` (unix seconds, `0` = no expiry) plus read-time filtering and a **background `DeleteExpired` tick** (~1 hour).
diff --git a/docs/advanced/admin-endpoints.mdx b/docs/advanced/admin-endpoints.mdx
index aea9c757..d15f7a13 100644
--- a/docs/advanced/admin-endpoints.mdx
+++ b/docs/advanced/admin-endpoints.mdx
@@ -1,11 +1,12 @@
---
title: "Admin Endpoints"
description: "Built-in REST API and dashboard for monitoring usage, models, and gateway health."
+icon: "server-cog"
---
## Philosophy
-GOModel ships with admin endpoints **enabled by default**. The goal is simple: you should be able to deploy GOModel and immediately have visibility into what's happening — no extra services, no separate monitoring stack, no configuration.
+GoModel ships with admin endpoints **enabled by default**. The goal is simple: you should be able to deploy GoModel and immediately have visibility into what's happening — no extra services, no separate monitoring stack, no configuration.
The admin layer is split into two independently controllable pieces:
@@ -47,7 +48,7 @@ curl -H "Authorization: Bearer $GOMODEL_MASTER_KEY" \
The dashboard UI pages (`/admin/dashboard`) and static assets (`/admin/static/*`) **skip authentication** so the dashboard is accessible without configuring API keys in the browser.
- If your GOModel instance is publicly accessible, be aware that the dashboard
+ If your GoModel instance is publicly accessible, be aware that the dashboard
UI is unauthenticated. Disable it with `ADMIN_UI_ENABLED=false` or restrict
access at the network level.
@@ -158,7 +159,7 @@ This differs from the standard `/v1/models` endpoint: the admin version includes
## Admin Dashboard
-The dashboard is a server-rendered HTML page embedded in the GOModel binary. Access it at:
+The dashboard is a server-rendered HTML page embedded in the GoModel binary. Access it at:
```
http://localhost:8080/admin/dashboard
diff --git a/docs/advanced/config-yaml.mdx b/docs/advanced/config-yaml.mdx
index 0cbb5c83..9c2a7919 100644
--- a/docs/advanced/config-yaml.mdx
+++ b/docs/advanced/config-yaml.mdx
@@ -1,6 +1,7 @@
---
title: "config.yaml"
description: "When to use config.yaml, when not to, and how it interacts with environment variables and Docker."
+icon: "file-cog"
---
The goal in GoModel is to make `config.yaml` optional as much as possible.
@@ -25,25 +26,6 @@ Effective precedence is:
`.env` is not a separate priority layer. It is just a convenient way to load
environment variables before startup.
-## When `config.yaml` Is Better
-
-YAML is the current way to define multiple providers of the same type with
-flexible names:
-
-```yaml
-providers:
- ollama-a:
- type: ollama
- base_url: "http://host.docker.internal:11434/v1"
-
- ollama-b:
- type: ollama
- base_url: "http://host.docker.internal:11435/v1"
-```
-
-That gives you provider-qualified model IDs such as `ollama-a/llama3.2` and
-`ollama-b/llama3.2`.
-
## Current Schema
The current source of truth lives in the main codebase:
diff --git a/docs/advanced/configuration.mdx b/docs/advanced/configuration.mdx
index 5ce56c56..7deac9d2 100644
--- a/docs/advanced/configuration.mdx
+++ b/docs/advanced/configuration.mdx
@@ -1,11 +1,12 @@
---
title: "Configuration"
-description: "How to configure GOModel using environment variables, .env files, and YAML."
+description: "How to configure GoModel using environment variables, .env files, and YAML."
+icon: "sliders-horizontal"
---
## Good defaults
-GOModel uses a good defaults philosophy. This means that the default settings should be enough to use it.
+GoModel uses a good defaults philosophy. This means that the default settings should be enough to use it.
## How to override the default settings?
@@ -20,20 +21,20 @@ flowchart LR
```
- As GOModel works out of the box with no configuration files, you can try it in
+ As GoModel works out of the box with no configuration files, you can try it in
a minute.
Start here: [Quick Start](/getting-started/quickstart)
-GOModel automatically discovers providers from well-known environment variables.
+GoModel automatically discovers providers from well-known environment variables.
## Configuration Methods
### 1. Environment Variables
-The most common way to configure GOModel. Set any of the variables below to override defaults.
+The most common way to configure GoModel. Set any of the variables below to override defaults.
#### Server
@@ -143,11 +144,11 @@ Set these to automatically register providers. No YAML configuration required.
Most providers can use a custom base URL via `_BASE_URL` (for example `OPENAI_BASE_URL`). OpenRouter defaults to `https://openrouter.ai/api/v1` and can be overridden with `OPENROUTER_BASE_URL`. Azure uses `AZURE_BASE_URL` for its deployment base URL and accepts an optional `AZURE_API_VERSION` override; otherwise it defaults to `2024-10-21`. Oracle requires `ORACLE_BASE_URL` because its OpenAI-compatible endpoint is region-specific.
When using Oracle, `models:` also acts as a fallback inventory if the upstream endpoint does not expose a usable `/models` response.
-For OpenRouter, GOModel also sends default attribution headers unless the request already sets them. Override those defaults with `OPENROUTER_SITE_URL` and `OPENROUTER_APP_NAME`.
+For OpenRouter, GoModel also sends default attribution headers unless the request already sets them. Override those defaults with `OPENROUTER_SITE_URL` and `OPENROUTER_APP_NAME`.
### 2. `.env` File
-GOModel automatically loads a `.env` file from the working directory at startup. This is convenient for local development.
+GoModel automatically loads a `.env` file from the working directory at startup. This is convenient for local development.
```bash
# .env
@@ -169,7 +170,7 @@ cp .env.template .env
### 3. Configuration File (YAML)
-For more complex setups, you can use an optional YAML configuration file. GOModel looks for it in two locations (in order):
+For more complex setups, you can use an optional YAML configuration file. GoModel looks for it in two locations (in order):
1. `config/config.yaml`
2. `config.yaml`
@@ -233,7 +234,7 @@ providers:
### Auto-Discovery from Environment Variables
-The simplest way to add providers. GOModel checks for well-known API key environment variables and automatically registers providers:
+The simplest way to add providers. GoModel checks for well-known API key environment variables and automatically registers providers:
```bash
export OPENAI_API_KEY="sk-..." # Registers "openai" provider
@@ -249,7 +250,7 @@ export ORACLE_BASE_URL="https://inference.generativeai.us-chicago-1.oci.oraclecl
export OLLAMA_BASE_URL="http://localhost:11434/v1" # Registers "ollama" provider
```
-GOModel also works with additional OpenAI-compatible providers out of the box
+GoModel also works with additional OpenAI-compatible providers out of the box
through YAML provider blocks.
### YAML Provider Blocks
@@ -316,8 +317,3 @@ providers:
type: ollama
base_url: "http://localhost:11434/v1"
```
-
-
- Providers with missing or unresolved API keys are automatically filtered out
- at startup. Ollama is the only exception — it only requires a base URL.
-
diff --git a/docs/advanced/guardrails.mdx b/docs/advanced/guardrails.mdx
index 51f4c761..46bf503c 100644
--- a/docs/advanced/guardrails.mdx
+++ b/docs/advanced/guardrails.mdx
@@ -1,11 +1,12 @@
---
title: "Guardrails"
description: "Intercept and modify requests before they reach LLM providers."
+icon: "shield-check"
---
## Overview
-Guardrails are a pipeline of rules that run **before** a request reaches any LLM provider. They can inspect, modify, or reject requests — giving you centralized control over every prompt that flows through GOModel.
+Guardrails are a pipeline of rules that run **before** a request reaches any LLM provider. They can inspect, modify, or reject requests — giving you centralized control over every prompt that flows through GoModel.
Guardrails work across all text-based endpoints:
@@ -205,7 +206,7 @@ so a minimal config acts as an anonymizing preprocessor.
| `skip_content_prefix` | No | Skip rewriting when the trimmed message starts with this prefix. |
| `max_tokens` | No | `max_tokens` for the auxiliary rewrite call. Default: `4096`. |
-When `llm_based_altering` calls the auxiliary model, GOModel runs that call
+When `llm_based_altering` calls the auxiliary model, GoModel runs that call
through the normal translated request path in-process. That means ordinary
workflow selection, fallback, usage, audit, and cache behavior still apply.
The internal request uses:
diff --git a/docs/advanced/workflows.mdx b/docs/advanced/workflows.mdx
index f0c2cf15..d3ae13b6 100644
--- a/docs/advanced/workflows.mdx
+++ b/docs/advanced/workflows.mdx
@@ -1,6 +1,7 @@
---
title: "Workflows"
description: "How workflow matching works, including user_path-first precedence and provider-name scoping."
+icon: "workflow"
---
## Overview
diff --git a/docs/docs.json b/docs/docs.json
index 94a9f2d8..5fafe9a8 100644
--- a/docs/docs.json
+++ b/docs/docs.json
@@ -3,24 +3,71 @@
"theme": "maple",
"name": "GoModel",
"logo": {
+ "light": "/wordmark-light.svg",
+ "dark": "/wordmark-dark.svg"
+ },
+ "favicon": {
"light": "/logo.svg",
"dark": "/logo.svg"
},
"colors": {
"primary": "#755c3d"
},
+ "icons": {
+ "library": "lucide"
+ },
+ "api": {
+ "url": "full"
+ },
"navigation": {
- "pages": [
- {
- "group": "Getting Started",
- "pages": ["getting-started/quickstart"]
- },
+ "tabs": [
{
- "group": "Features",
- "pages": ["features/cache"]
+ "tab": "Documentation",
+ "icon": "book-open",
+ "pages": [
+ {
+ "group": "Getting Started",
+ "icon": "rocket",
+ "pages": [
+ "getting-started/quickstart"
+ ]
+ },
+ {
+ "group": "Features",
+ "icon": "sparkles",
+ "pages": [
+ "features/aliases",
+ "features/user-path",
+ "features/cache"
+ ]
+ },
+ {
+ "group": "Advanced",
+ "icon": "settings-2",
+ "pages": [
+ "advanced/configuration",
+ "advanced/config-yaml",
+ "advanced/guardrails",
+ "advanced/workflows",
+ "advanced/admin-endpoints"
+ ]
+ },
+ {
+ "group": "About",
+ "icon": "info",
+ "pages": [
+ "about/who-we-are",
+ "about/values",
+ "about/technical-philosophy",
+ "about/license",
+ "about/benchmarks"
+ ]
+ }
+ ]
},
{
- "group": "Guides",
+ "tab": "Guides",
+ "icon": "compass",
"pages": [
"guides/openclaw",
"guides/oracle",
@@ -31,12 +78,12 @@
]
},
{
- "group": "Advanced",
- "pages": ["advanced/configuration", "advanced/config-yaml", "advanced/guardrails", "advanced/workflows", "advanced/admin-endpoints"]
- },
- {
- "group": "About",
- "pages": ["about/who-we-are", "about/values", "about/technical-philosophy", "about/benchmarks"]
+ "tab": "API Reference",
+ "icon": "braces",
+ "openapi": {
+ "source": "openapi.json",
+ "directory": "api-reference"
+ }
}
]
}
diff --git a/docs/features/aliases.mdx b/docs/features/aliases.mdx
new file mode 100644
index 00000000..39251b11
--- /dev/null
+++ b/docs/features/aliases.mdx
@@ -0,0 +1,71 @@
+---
+title: "Aliases"
+description: "Use GoModel aliases to expose stable model names and test model swaps without changing app code."
+icon: "tags"
+---
+
+
+
+## What aliases do
+
+An alias is a stable model name that points to a concrete provider model. Your
+app sends the alias in the `model` field, and GoModel resolves it before sending
+the request upstream.
+
+Create and manage aliases in the admin dashboard at `Models -> Create Alias`.
+
+## Use stable names
+
+You can expose names like `regular` and `smarter` instead of provider-specific
+model IDs.
+
+- `regular` -> `anthropic/claude-sonnet-4-6`
+- `smarter` -> `anthropic/claude-opus-4-6`
+
+Your app can then send:
+
+```json
+{
+ "model": "regular",
+ "messages": []
+}
+```
+
+## Expose only aliases
+
+To hide provider models from `GET /v1/models`, set:
+
+```env
+KEEP_ONLY_ALIASES_AT_MODELS_ENDPOINT=true
+```
+
+When this is enabled, GoModel returns enabled aliases from `/v1/models` instead
+of the full provider model list. The setting is also listed in `.env.template`.
+
+## Shadow a model
+
+An alias can "shadow" a model by using the same name as an existing model and
+pointing it somewhere else. This lets you override a requested model without
+changing application code.
+
+For example:
+
+- alias name: `anthropic/claude-opus-4-6`
+- target model: `anthropic/claude-sonnet-4-6`
+
+Your app can keep sending `anthropic/claude-opus-4-6`, while GoModel routes the
+request to `anthropic/claude-sonnet-4-6`.
+
+## A/B testing
+
+Aliases are useful for short model experiments. Move an alias from one target to
+another, then compare app behavior, latency, and usage.
+
+For example, point `smarter` at Opus for one test and Sonnet for another. You can
+also shadow `opus-4-6` with `sonnet-4-6` to check whether the same app flow still
+works.
diff --git a/docs/features/aliases.png b/docs/features/aliases.png
new file mode 100644
index 00000000..c8d6c1e4
Binary files /dev/null and b/docs/features/aliases.png differ
diff --git a/docs/features/cache.mdx b/docs/features/cache.mdx
index 6487bbb9..d0f2ee3e 100644
--- a/docs/features/cache.mdx
+++ b/docs/features/cache.mdx
@@ -1,17 +1,18 @@
---
title: "Cache"
-description: "How GOModel response caching works, how to enable it, and what is included in the exact-cache key."
+description: "How GoModel response caching works, how to enable it, and what is included in the exact-cache key."
+icon: "database"
---
## Overview
-GOModel ships with an exact-match response cache for non-streaming requests on:
+GoModel ships with an exact-match response cache for non-streaming requests on:
- `/v1/chat/completions`
- `/v1/responses`
- `/v1/embeddings`
-When a response is served from the exact cache, GOModel returns:
+When a response is served from the exact cache, GoModel returns:
```http
X-Cache: HIT (exact)
diff --git a/docs/features/user-path.mdx b/docs/features/user-path.mdx
new file mode 100644
index 00000000..5a91fd18
--- /dev/null
+++ b/docs/features/user-path.mdx
@@ -0,0 +1,74 @@
+---
+title: "User Path"
+description: "Use user_path to scope keys, model access, model lists, workflows, usage, and audit logs."
+icon: "route"
+---
+
+## Overview
+
+`user_path` is a normalized hierarchy for the caller, for example
+`/team/alpha` or `/team/alpha/service`.
+
+GoModel uses it to keep model access, workflows, usage, and audit data scoped to
+the right team, tenant, service, or customer.
+
+## API keys
+
+You can bind a user path to a managed API key in the admin dashboard:
+
+`API Keys -> Create API Key -> User Path`
+
+When a request uses that key, GoModel treats the key's `user_path` as the
+effective user path for the request.
+
+## HTTP header
+
+Clients can also send a user path directly:
+
+```http
+X-GoModel-User-Path: /team/alpha
+```
+
+If the API key has its own `user_path`, the key wins. GoModel overwrites the
+header value with the key-bound path before workflow matching, audit logging,
+usage tracking, and model access checks run.
+
+## Model access
+
+Model access overrides are enabled by default. They can use `user_paths` to
+limit a selector to a subtree.
+Selectors can target:
+
+- `/` for all providers and models
+- `{provider_name}/` for one configured provider
+- `{provider_name}/{model}` for one model on one provider
+- a model ID without a provider name
+
+For example:
+
+- selector: `openai/gpt-5`
+- `user_paths`: `["/team/alpha"]`
+
+This allows `/team/alpha` and its descendants, such as `/team/alpha/service`.
+
+Use `Models -> Access override` in the dashboard to manage these rules.
+
+## Exposed models
+
+`GET /v1/models` uses the effective `user_path` too.
+
+That means two API keys can see different model lists if their user paths have
+different model access rules.
+
+## Workflows
+
+Workflows can also include `scope_user_path`, so different teams or services can
+use different cache, audit, usage, guardrail, and fallback settings.
+
+You can combine user path with provider and model scope, for example:
+
+- `/team/alpha`
+- `openai_primary` + `/team/alpha`
+- `openai_primary` + `gpt-5` + `/team/alpha`
+
+See [Workflows](/advanced/workflows) for the full matching order.
diff --git a/docs/getting-started/quickstart.mdx b/docs/getting-started/quickstart.mdx
index 0a32e78a..1cce0d14 100644
--- a/docs/getting-started/quickstart.mdx
+++ b/docs/getting-started/quickstart.mdx
@@ -1,16 +1,17 @@
---
title: "Quick Start"
-description: "GOModel AI gateway quick start: run an OpenAI-compatible LLM gateway in 30 seconds, send your first request, and open the admin panel."
+description: "GoModel AI gateway quick start: run an OpenAI-compatible LLM gateway in 30 seconds, send your first request, and open the admin panel."
+icon: "rocket"
---
-## Run GOModel in 30 Seconds
+## Run GoModel in 30 Seconds
-GOModel is an OpenAI-compatible AI gateway. You can connect one endpoint and
+GoModel is an OpenAI-compatible AI gateway. You can connect one endpoint and
route traffic across OpenAI, Anthropic, Gemini, xAI, Groq, OpenRouter, Azure
OpenAI, Oracle, Ollama, and more while keeping auth, audit logs, and admin
visibility in one place.
-### 1. Start GOModel
+### 1. Start GoModel
```bash
docker run --rm -p 8080:8080 \
@@ -28,7 +29,7 @@ docker run --rm -p 8080:8080 \
(`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GEMINI_API_KEY`, `XAI_API_KEY`,
`GROQ_API_KEY`, `OPENROUTER_API_KEY`, `AZURE_API_KEY` +
`AZURE_BASE_URL`, `ORACLE_API_KEY` + `ORACLE_BASE_URL`,
- `OLLAMA_BASE_URL`) or GOModel will have no models to route. GOModel also
+ `OLLAMA_BASE_URL`) or GoModel will have no models to route. GoModel also
works well with additional OpenAI-compatible providers out of the box.
@@ -71,15 +72,15 @@ Use one of those model IDs in your requests.
### Usage Analytics
-
+
### Audit Logs
-
+
## Next Steps
- Understand response caching: [Cache](/features/cache)
- Configure production settings: [Configuration](/advanced/configuration)
- Add request policies: [Guardrails](/advanced/guardrails)
-- Connect OpenClaw: [Using GOModel with OpenClaw](/guides/openclaw)
+- Connect OpenClaw: [Using GoModel with OpenClaw](/guides/openclaw)
diff --git a/docs/guides/claude-code.mdx b/docs/guides/claude-code.mdx
index d2b517bb..83e74eb0 100644
--- a/docs/guides/claude-code.mdx
+++ b/docs/guides/claude-code.mdx
@@ -1,6 +1,7 @@
---
-title: "Using GoModel with Claude Code"
+title: "GoModel & Claude Code"
description: "Step-by-step guide for routing Claude Code through GoModel with Anthropic passthrough."
+icon: "terminal"
---
GoModel can sit in front of Claude Code so every request goes through your own
diff --git a/docs/guides/codex.mdx b/docs/guides/codex.mdx
index f197f1c4..2db43313 100644
--- a/docs/guides/codex.mdx
+++ b/docs/guides/codex.mdx
@@ -1,6 +1,7 @@
---
-title: "Using GoModel with Codex"
+title: "GoModel & Codex"
description: "Step-by-step guide for trying Codex with GoModel and understanding the current limitation."
+icon: "code-xml"
---
GoModel is a good fit for Codex because Codex already targets the OpenAI
diff --git a/docs/guides/multiple-ollama.mdx b/docs/guides/multiple-ollama.mdx
index d94cddb3..c4ddbea7 100644
--- a/docs/guides/multiple-ollama.mdx
+++ b/docs/guides/multiple-ollama.mdx
@@ -1,6 +1,7 @@
---
-title: "Running Multiple Ollama Backends"
+title: "GoModel & Ollama"
description: "Use one GoModel instance with multiple Ollama providers by mounting a YAML config and selecting provider-qualified model IDs."
+icon: "network"
---
GoModel can register multiple Ollama providers when each one has its own key in
diff --git a/docs/guides/openclaw.mdx b/docs/guides/openclaw.mdx
index e489e859..4311c88d 100644
--- a/docs/guides/openclaw.mdx
+++ b/docs/guides/openclaw.mdx
@@ -1,17 +1,18 @@
---
-title: "Using GOModel with OpenClaw"
-description: "Use GOModel as an OpenAI-compatible AI gateway for OpenClaw, with recommended OpenAI models and a production-ready setup."
+title: "GoModel & OpenClaw"
+description: "Use GoModel as an OpenAI-compatible AI gateway for OpenClaw, with recommended OpenAI models and a production-ready setup."
+icon: "paw-print"
---
## Overview
-GOModel is an OpenAI-compatible **AI gateway** for OpenClaw. It gives you one
+GoModel is an OpenAI-compatible **AI gateway** for OpenClaw. It gives you one
stable API endpoint while routing requests to OpenAI and other providers. This
keeps OpenClaw configuration simple and makes model switching easier.
Flow:
-`OpenClaw -> GOModel -> OpenAI/Anthropic/Gemini/...`
+`OpenClaw -> GoModel -> OpenAI/Anthropic/Gemini/...`
## Recommended OpenAI Models (As of February 28, 2026)
@@ -25,7 +26,7 @@ These OpenAI model IDs are currently good defaults for OpenClaw:
| `gpt-5.2-codex` | Advanced coding workflows in Codex-like environments (Responses API oriented) |
- Verify availability in your GOModel instance first: `GET /v1/models`. Model
+ Verify availability in your GoModel instance first: `GET /v1/models`. Model
availability depends on your OpenAI account tier and API surface.
@@ -33,9 +34,9 @@ Reference:
- OpenAI GPT-5.2 release: [OpenAI Changelog](https://platform.openai.com/docs/changelog)
- OpenAI model docs: [Models Overview](https://platform.openai.com/docs/models)
-## 1. Run GOModel
+## 1. Run GoModel
-Start GOModel with at least one provider and a master key:
+Start GoModel with at least one provider and a master key:
```bash
docker run --rm -p 8080:8080 \
@@ -51,7 +52,7 @@ curl -s http://localhost:8080/v1/models \
-H "Authorization: Bearer change-me"
```
-## 2. Add a GOModel provider in OpenClaw
+## 2. Add a GoModel provider in OpenClaw
In your OpenClaw config, add a custom provider that uses OpenAI-compatible requests:
@@ -70,7 +71,7 @@ In your OpenClaw config, add a custom provider that uses OpenAI-compatible reque
"models": [
{
"id": "gpt-5-mini",
- "name": "GOModel gpt-5-mini",
+ "name": "GoModel gpt-5-mini",
"reasoning": false,
"input": ["text"],
"cost": {
@@ -84,7 +85,7 @@ In your OpenClaw config, add a custom provider that uses OpenAI-compatible reque
},
{
"id": "gpt-5.2",
- "name": "GOModel gpt-5.2",
+ "name": "GoModel gpt-5.2",
"reasoning": true,
"input": ["text"],
"cost": {
@@ -110,7 +111,7 @@ In your OpenClaw config, add a custom provider that uses OpenAI-compatible reque
}
```
-Replace model IDs with values returned by GOModel at `/v1/models`.
+Replace model IDs with values returned by GoModel at `/v1/models`.
`gpt-5.2-codex` is highly relevant for coding agents, but OpenAI currently
@@ -126,7 +127,7 @@ If you get `401 Unauthorized`, verify OpenClaw is sending the same value as `GOM
## Notes
-- If GOModel is running in Docker and OpenClaw is not, `http://localhost:8080` is usually correct.
+- If GoModel is running in Docker and OpenClaw is not, `http://localhost:8080` is usually correct.
- If both run in different containers, use a shared Docker network and container hostname instead of `localhost`.
-- You can expose multiple GOModel-backed models by adding more entries under `models.providers.gomodel.models`.
-- GOModel as an AI gateway also gives you centralized auth, audit logs, usage analytics, and provider abstraction with one OpenAI-compatible endpoint.
+- You can expose multiple GoModel-backed models by adding more entries under `models.providers.gomodel.models`.
+- GoModel as an AI gateway also gives you centralized auth, audit logs, usage analytics, and provider abstraction with one OpenAI-compatible endpoint.
diff --git a/docs/guides/opencode-and-other-agents.mdx b/docs/guides/opencode-and-other-agents.mdx
index e1087b00..be1e6c4d 100644
--- a/docs/guides/opencode-and-other-agents.mdx
+++ b/docs/guides/opencode-and-other-agents.mdx
@@ -1,6 +1,7 @@
---
-title: "Using GoModel with OpenCode, Cline, Roo Code, and Kilo Code"
+title: "GoModel & Coding Agents"
description: "Step-by-step guide for OpenAI-compatible coding agents with GoModel."
+icon: "bot"
---
Most coding agents outside Claude Code are easiest to connect to GoModel
diff --git a/docs/guides/oracle.mdx b/docs/guides/oracle.mdx
index 7f9eefff..e2cdfa5a 100644
--- a/docs/guides/oracle.mdx
+++ b/docs/guides/oracle.mdx
@@ -1,6 +1,7 @@
---
-title: "Using GoModel with Oracle Generative AI"
+title: "GoModel & Oracle"
description: "Configure Oracle's OpenAI-compatible Generative AI endpoint in GoModel, including the required OCI policy and model fallback."
+icon: "cloud"
---
GoModel works with Oracle Generative AI through Oracle's OpenAI-compatible
diff --git a/docs/openapi.json b/docs/openapi.json
new file mode 100644
index 00000000..9b95583d
--- /dev/null
+++ b/docs/openapi.json
@@ -0,0 +1,4943 @@
+{
+ "openapi": "3.0.0",
+ "info": {
+ "description": "High-performance AI gateway routing requests to multiple LLM providers (OpenAI, Anthropic, Gemini, Groq, xAI, Oracle, Ollama). Drop-in OpenAI-compatible API.",
+ "title": "GoModel API",
+ "contact": {},
+ "version": "1.0"
+ },
+ "paths": {
+ "/admin/api/v1/audit/conversation": {
+ "get": {
+ "tags": [
+ "admin"
+ ],
+ "summary": "Get conversation thread around an audit log entry",
+ "parameters": [
+ {
+ "description": "Anchor audit log entry ID",
+ "name": "log_id",
+ "in": "query",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "Max entries in thread (default 40, max 200)",
+ "name": "limit",
+ "in": "query",
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/auditlog.ConversationResult"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "BearerAuth": []
+ }
+ ]
+ }
+ },
+ "/admin/api/v1/audit/log": {
+ "get": {
+ "tags": [
+ "admin"
+ ],
+ "summary": "Get paginated audit log entries",
+ "parameters": [
+ {
+ "description": "Number of days (default 30)",
+ "name": "days",
+ "in": "query",
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "description": "Start date (YYYY-MM-DD)",
+ "name": "start_date",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "End date (YYYY-MM-DD)",
+ "name": "end_date",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "Filter by requested model selector",
+ "name": "requested_model",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "Filter by provider name or provider type",
+ "name": "provider",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "Filter by HTTP method",
+ "name": "method",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "Filter by request path",
+ "name": "path",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "Filter by tracked user path subtree",
+ "name": "user_path",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "Filter by error type",
+ "name": "error_type",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "Filter by status code",
+ "name": "status_code",
+ "in": "query",
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "description": "Filter by stream mode (true/false)",
+ "name": "stream",
+ "in": "query",
+ "schema": {
+ "type": "boolean"
+ }
+ },
+ {
+ "description": "Search across request_id/requested_model/provider/method/path/error_type",
+ "name": "search",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "Page size (default 25, max 100)",
+ "name": "limit",
+ "in": "query",
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "description": "Offset for pagination",
+ "name": "offset",
+ "in": "query",
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/auditlog.LogListResult"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "BearerAuth": []
+ }
+ ]
+ }
+ },
+ "/admin/api/v1/cache/overview": {
+ "get": {
+ "tags": [
+ "admin"
+ ],
+ "summary": "Get cached-only usage overview",
+ "parameters": [
+ {
+ "description": "Number of days (default 30)",
+ "name": "days",
+ "in": "query",
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "description": "Start date (YYYY-MM-DD)",
+ "name": "start_date",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "End date (YYYY-MM-DD)",
+ "name": "end_date",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "Grouping interval: daily, weekly, monthly, yearly (default daily)",
+ "name": "interval",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "Filter by tracked user path subtree",
+ "name": "user_path",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "Cache mode filter: uncached, cached, all (cache overview always uses cached mode)",
+ "name": "cache_mode",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/usage.CacheOverview"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "503": {
+ "description": "Service Unavailable",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "BearerAuth": []
+ }
+ ]
+ }
+ },
+ "/admin/api/v1/models/categories": {
+ "get": {
+ "tags": [
+ "admin"
+ ],
+ "summary": "List model categories with counts",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/providers.CategoryCount"
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "BearerAuth": []
+ }
+ ]
+ }
+ },
+ "/admin/api/v1/usage/daily": {
+ "get": {
+ "tags": [
+ "admin"
+ ],
+ "summary": "Get usage breakdown by period",
+ "parameters": [
+ {
+ "description": "Number of days (default 30)",
+ "name": "days",
+ "in": "query",
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "description": "Start date (YYYY-MM-DD)",
+ "name": "start_date",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "End date (YYYY-MM-DD)",
+ "name": "end_date",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "Grouping interval: daily, weekly, monthly, yearly (default daily)",
+ "name": "interval",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "Filter by tracked user path subtree",
+ "name": "user_path",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "Cache mode filter: uncached, cached, all (default uncached)",
+ "name": "cache_mode",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/usage.DailyUsage"
+ }
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "BearerAuth": []
+ }
+ ]
+ }
+ },
+ "/admin/api/v1/usage/log": {
+ "get": {
+ "tags": [
+ "admin"
+ ],
+ "summary": "Get paginated usage log entries",
+ "parameters": [
+ {
+ "description": "Number of days (default 30)",
+ "name": "days",
+ "in": "query",
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "description": "Start date (YYYY-MM-DD)",
+ "name": "start_date",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "End date (YYYY-MM-DD)",
+ "name": "end_date",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "Filter by model name",
+ "name": "model",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "Filter by provider name or provider type",
+ "name": "provider",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "Filter by tracked user path subtree",
+ "name": "user_path",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "Cache mode filter: uncached, cached, all (default uncached)",
+ "name": "cache_mode",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "Search across model, provider, request_id, provider_id",
+ "name": "search",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "Page size (default 50, max 200)",
+ "name": "limit",
+ "in": "query",
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "description": "Offset for pagination",
+ "name": "offset",
+ "in": "query",
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/usage.UsageLogResult"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "BearerAuth": []
+ }
+ ]
+ }
+ },
+ "/admin/api/v1/usage/models": {
+ "get": {
+ "tags": [
+ "admin"
+ ],
+ "summary": "Get usage breakdown by model",
+ "parameters": [
+ {
+ "description": "Number of days (default 30)",
+ "name": "days",
+ "in": "query",
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "description": "Start date (YYYY-MM-DD)",
+ "name": "start_date",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "End date (YYYY-MM-DD)",
+ "name": "end_date",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "Filter by tracked user path subtree",
+ "name": "user_path",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "Cache mode filter: uncached, cached, all (default uncached)",
+ "name": "cache_mode",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/usage.ModelUsage"
+ }
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "BearerAuth": []
+ }
+ ]
+ }
+ },
+ "/admin/api/v1/usage/summary": {
+ "get": {
+ "tags": [
+ "admin"
+ ],
+ "summary": "Get usage summary",
+ "parameters": [
+ {
+ "description": "Number of days (default 30)",
+ "name": "days",
+ "in": "query",
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "description": "Start date (YYYY-MM-DD)",
+ "name": "start_date",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "End date (YYYY-MM-DD)",
+ "name": "end_date",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "Filter by tracked user path subtree",
+ "name": "user_path",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "Cache mode filter: uncached, cached, all (default uncached)",
+ "name": "cache_mode",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/usage.UsageSummary"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "BearerAuth": []
+ }
+ ]
+ }
+ },
+ "/admin/api/v1/usage/user-paths": {
+ "get": {
+ "tags": [
+ "admin"
+ ],
+ "summary": "Get usage breakdown by user path",
+ "parameters": [
+ {
+ "description": "Number of days (default 30)",
+ "name": "days",
+ "in": "query",
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "description": "Start date (YYYY-MM-DD)",
+ "name": "start_date",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "End date (YYYY-MM-DD)",
+ "name": "end_date",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "Filter by tracked user path subtree",
+ "name": "user_path",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "Cache mode filter: uncached, cached, all (default uncached)",
+ "name": "cache_mode",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/usage.UserPathUsage"
+ }
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "BearerAuth": []
+ }
+ ]
+ }
+ },
+ "/health": {
+ "get": {
+ "tags": [
+ "system"
+ ],
+ "summary": "Health check",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/p/{provider}/{endpoint}": {
+ "get": {
+ "description": "Runtime-configurable passthrough endpoint under /p/{provider}/{endpoint}; enabled by default via server.enable_passthrough_routes. The endpoint path is opaque and may proxy JSON, binary, or SSE responses with upstream status codes preserved. For multi-segment provider endpoints, clients that rely on OpenAPI-generated path handling should URL-encode embedded slashes in the endpoint parameter. A leading v1/ segment is normalized away by default so /p/{provider}/v1/... and /p/{provider}/... map to the same upstream path relative to the provider base URL.",
+ "tags": [
+ "passthrough"
+ ],
+ "summary": "Provider passthrough",
+ "parameters": [
+ {
+ "description": "Provider type",
+ "name": "provider",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "Provider-native endpoint path relative to the provider base URL. URL-encode embedded / characters when using generated clients.",
+ "name": "endpoint",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Opaque upstream response body",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "application/octet-stream": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ }
+ }
+ },
+ "201": {
+ "description": "Opaque upstream response body",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "application/octet-stream": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ }
+ }
+ },
+ "202": {
+ "description": "Opaque upstream response body",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "application/octet-stream": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ }
+ }
+ },
+ "204": {
+ "description": "No Content passthrough response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "string"
+ }
+ },
+ "application/octet-stream": {
+ "schema": {
+ "type": "string"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ },
+ "application/octet-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ },
+ "application/octet-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "502": {
+ "description": "Bad Gateway",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ },
+ "application/octet-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "BearerAuth": []
+ }
+ ]
+ },
+ "put": {
+ "description": "Runtime-configurable passthrough endpoint under /p/{provider}/{endpoint}; enabled by default via server.enable_passthrough_routes. The endpoint path is opaque and may proxy JSON, binary, or SSE responses with upstream status codes preserved. For multi-segment provider endpoints, clients that rely on OpenAPI-generated path handling should URL-encode embedded slashes in the endpoint parameter. A leading v1/ segment is normalized away by default so /p/{provider}/v1/... and /p/{provider}/... map to the same upstream path relative to the provider base URL.",
+ "tags": [
+ "passthrough"
+ ],
+ "summary": "Provider passthrough",
+ "parameters": [
+ {
+ "description": "Provider type",
+ "name": "provider",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "Provider-native endpoint path relative to the provider base URL. URL-encode embedded / characters when using generated clients.",
+ "name": "endpoint",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Opaque upstream response body",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "application/octet-stream": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ }
+ }
+ },
+ "201": {
+ "description": "Opaque upstream response body",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "application/octet-stream": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ }
+ }
+ },
+ "202": {
+ "description": "Opaque upstream response body",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "application/octet-stream": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ }
+ }
+ },
+ "204": {
+ "description": "No Content passthrough response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "string"
+ }
+ },
+ "application/octet-stream": {
+ "schema": {
+ "type": "string"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ },
+ "application/octet-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ },
+ "application/octet-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "502": {
+ "description": "Bad Gateway",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ },
+ "application/octet-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "BearerAuth": []
+ }
+ ]
+ },
+ "post": {
+ "description": "Runtime-configurable passthrough endpoint under /p/{provider}/{endpoint}; enabled by default via server.enable_passthrough_routes. The endpoint path is opaque and may proxy JSON, binary, or SSE responses with upstream status codes preserved. For multi-segment provider endpoints, clients that rely on OpenAPI-generated path handling should URL-encode embedded slashes in the endpoint parameter. A leading v1/ segment is normalized away by default so /p/{provider}/v1/... and /p/{provider}/... map to the same upstream path relative to the provider base URL.",
+ "tags": [
+ "passthrough"
+ ],
+ "summary": "Provider passthrough",
+ "parameters": [
+ {
+ "description": "Provider type",
+ "name": "provider",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "Provider-native endpoint path relative to the provider base URL. URL-encode embedded / characters when using generated clients.",
+ "name": "endpoint",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Opaque upstream response body",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "application/octet-stream": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ }
+ }
+ },
+ "201": {
+ "description": "Opaque upstream response body",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "application/octet-stream": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ }
+ }
+ },
+ "202": {
+ "description": "Opaque upstream response body",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "application/octet-stream": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ }
+ }
+ },
+ "204": {
+ "description": "No Content passthrough response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "string"
+ }
+ },
+ "application/octet-stream": {
+ "schema": {
+ "type": "string"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ },
+ "application/octet-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ },
+ "application/octet-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "502": {
+ "description": "Bad Gateway",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ },
+ "application/octet-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "BearerAuth": []
+ }
+ ]
+ },
+ "delete": {
+ "description": "Runtime-configurable passthrough endpoint under /p/{provider}/{endpoint}; enabled by default via server.enable_passthrough_routes. The endpoint path is opaque and may proxy JSON, binary, or SSE responses with upstream status codes preserved. For multi-segment provider endpoints, clients that rely on OpenAPI-generated path handling should URL-encode embedded slashes in the endpoint parameter. A leading v1/ segment is normalized away by default so /p/{provider}/v1/... and /p/{provider}/... map to the same upstream path relative to the provider base URL.",
+ "tags": [
+ "passthrough"
+ ],
+ "summary": "Provider passthrough",
+ "parameters": [
+ {
+ "description": "Provider type",
+ "name": "provider",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "Provider-native endpoint path relative to the provider base URL. URL-encode embedded / characters when using generated clients.",
+ "name": "endpoint",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Opaque upstream response body",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "application/octet-stream": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ }
+ }
+ },
+ "201": {
+ "description": "Opaque upstream response body",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "application/octet-stream": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ }
+ }
+ },
+ "202": {
+ "description": "Opaque upstream response body",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "application/octet-stream": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ }
+ }
+ },
+ "204": {
+ "description": "No Content passthrough response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "string"
+ }
+ },
+ "application/octet-stream": {
+ "schema": {
+ "type": "string"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ },
+ "application/octet-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ },
+ "application/octet-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "502": {
+ "description": "Bad Gateway",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ },
+ "application/octet-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "BearerAuth": []
+ }
+ ]
+ },
+ "options": {
+ "description": "Runtime-configurable passthrough endpoint under /p/{provider}/{endpoint}; enabled by default via server.enable_passthrough_routes. The endpoint path is opaque and may proxy JSON, binary, or SSE responses with upstream status codes preserved. For multi-segment provider endpoints, clients that rely on OpenAPI-generated path handling should URL-encode embedded slashes in the endpoint parameter. A leading v1/ segment is normalized away by default so /p/{provider}/v1/... and /p/{provider}/... map to the same upstream path relative to the provider base URL.",
+ "tags": [
+ "passthrough"
+ ],
+ "summary": "Provider passthrough",
+ "parameters": [
+ {
+ "description": "Provider type",
+ "name": "provider",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "Provider-native endpoint path relative to the provider base URL. URL-encode embedded / characters when using generated clients.",
+ "name": "endpoint",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Opaque upstream response body",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "application/octet-stream": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ }
+ }
+ },
+ "201": {
+ "description": "Opaque upstream response body",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "application/octet-stream": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ }
+ }
+ },
+ "202": {
+ "description": "Opaque upstream response body",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "application/octet-stream": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ }
+ }
+ },
+ "204": {
+ "description": "No Content passthrough response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "string"
+ }
+ },
+ "application/octet-stream": {
+ "schema": {
+ "type": "string"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ },
+ "application/octet-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ },
+ "application/octet-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "502": {
+ "description": "Bad Gateway",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ },
+ "application/octet-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "BearerAuth": []
+ }
+ ]
+ },
+ "head": {
+ "description": "Runtime-configurable passthrough endpoint under /p/{provider}/{endpoint}; enabled by default via server.enable_passthrough_routes. The endpoint path is opaque and may proxy JSON, binary, or SSE responses with upstream status codes preserved. For multi-segment provider endpoints, clients that rely on OpenAPI-generated path handling should URL-encode embedded slashes in the endpoint parameter. A leading v1/ segment is normalized away by default so /p/{provider}/v1/... and /p/{provider}/... map to the same upstream path relative to the provider base URL.",
+ "tags": [
+ "passthrough"
+ ],
+ "summary": "Provider passthrough",
+ "parameters": [
+ {
+ "description": "Provider type",
+ "name": "provider",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "Provider-native endpoint path relative to the provider base URL. URL-encode embedded / characters when using generated clients.",
+ "name": "endpoint",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Opaque upstream response body",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "application/octet-stream": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ }
+ }
+ },
+ "201": {
+ "description": "Opaque upstream response body",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "application/octet-stream": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ }
+ }
+ },
+ "202": {
+ "description": "Opaque upstream response body",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "application/octet-stream": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ }
+ }
+ },
+ "204": {
+ "description": "No Content passthrough response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "string"
+ }
+ },
+ "application/octet-stream": {
+ "schema": {
+ "type": "string"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ },
+ "application/octet-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ },
+ "application/octet-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "502": {
+ "description": "Bad Gateway",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ },
+ "application/octet-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "BearerAuth": []
+ }
+ ]
+ },
+ "patch": {
+ "description": "Runtime-configurable passthrough endpoint under /p/{provider}/{endpoint}; enabled by default via server.enable_passthrough_routes. The endpoint path is opaque and may proxy JSON, binary, or SSE responses with upstream status codes preserved. For multi-segment provider endpoints, clients that rely on OpenAPI-generated path handling should URL-encode embedded slashes in the endpoint parameter. A leading v1/ segment is normalized away by default so /p/{provider}/v1/... and /p/{provider}/... map to the same upstream path relative to the provider base URL.",
+ "tags": [
+ "passthrough"
+ ],
+ "summary": "Provider passthrough",
+ "parameters": [
+ {
+ "description": "Provider type",
+ "name": "provider",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "Provider-native endpoint path relative to the provider base URL. URL-encode embedded / characters when using generated clients.",
+ "name": "endpoint",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Opaque upstream response body",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "application/octet-stream": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ }
+ }
+ },
+ "201": {
+ "description": "Opaque upstream response body",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "application/octet-stream": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ }
+ }
+ },
+ "202": {
+ "description": "Opaque upstream response body",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "application/octet-stream": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ }
+ }
+ },
+ "204": {
+ "description": "No Content passthrough response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "string"
+ }
+ },
+ "application/octet-stream": {
+ "schema": {
+ "type": "string"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ },
+ "application/octet-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ },
+ "application/octet-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "502": {
+ "description": "Bad Gateway",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ },
+ "application/octet-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "BearerAuth": []
+ }
+ ]
+ }
+ },
+ "/v1/batches": {
+ "get": {
+ "tags": [
+ "batch"
+ ],
+ "summary": "List batches",
+ "parameters": [
+ {
+ "description": "Pagination cursor",
+ "name": "after",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "Maximum items to return (1-100, default 20)",
+ "name": "limit",
+ "in": "query",
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.BatchListResponse"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "BearerAuth": []
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "batch"
+ ],
+ "summary": "Create a native provider batch",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.BatchRequest"
+ }
+ }
+ },
+ "description": "Batch request",
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.BatchResponse"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "502": {
+ "description": "Bad Gateway",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "BearerAuth": []
+ }
+ ]
+ }
+ },
+ "/v1/batches/{id}": {
+ "get": {
+ "tags": [
+ "batch"
+ ],
+ "summary": "Get a batch",
+ "parameters": [
+ {
+ "description": "Batch ID",
+ "name": "id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.BatchResponse"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "502": {
+ "description": "Bad Gateway",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "BearerAuth": []
+ }
+ ]
+ }
+ },
+ "/v1/batches/{id}/cancel": {
+ "post": {
+ "tags": [
+ "batch"
+ ],
+ "summary": "Cancel a batch",
+ "parameters": [
+ {
+ "description": "Batch ID",
+ "name": "id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.BatchResponse"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "502": {
+ "description": "Bad Gateway",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "BearerAuth": []
+ }
+ ]
+ }
+ },
+ "/v1/batches/{id}/results": {
+ "get": {
+ "tags": [
+ "batch"
+ ],
+ "summary": "Get batch results",
+ "parameters": [
+ {
+ "description": "Batch ID",
+ "name": "id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.BatchResultsResponse"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "409": {
+ "description": "Conflict",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "502": {
+ "description": "Bad Gateway",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "BearerAuth": []
+ }
+ ]
+ }
+ },
+ "/v1/chat/completions": {
+ "post": {
+ "tags": [
+ "chat"
+ ],
+ "summary": "Create a chat completion",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.ChatRequest"
+ }
+ }
+ },
+ "description": "Chat completion request",
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "JSON response or SSE stream when stream=true",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.ChatResponse"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.ChatResponse"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "429": {
+ "description": "Too Many Requests",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "502": {
+ "description": "Bad Gateway",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "BearerAuth": []
+ }
+ ]
+ }
+ },
+ "/v1/embeddings": {
+ "post": {
+ "tags": [
+ "embeddings"
+ ],
+ "summary": "Create embeddings",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.EmbeddingRequest"
+ }
+ }
+ },
+ "description": "Embeddings request",
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.EmbeddingResponse"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "429": {
+ "description": "Too Many Requests",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "502": {
+ "description": "Bad Gateway",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "BearerAuth": []
+ }
+ ]
+ }
+ },
+ "/v1/files": {
+ "get": {
+ "tags": [
+ "files"
+ ],
+ "summary": "List files",
+ "parameters": [
+ {
+ "description": "Provider filter",
+ "name": "provider",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "File purpose filter",
+ "name": "purpose",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "Pagination cursor",
+ "name": "after",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "Maximum items to return (1-100, default 20)",
+ "name": "limit",
+ "in": "query",
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.FileListResponse"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "502": {
+ "description": "Bad Gateway",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "BearerAuth": []
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "files"
+ ],
+ "summary": "Upload a file",
+ "parameters": [
+ {
+ "description": "Provider override when multiple providers are configured",
+ "name": "provider",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "requestBody": {
+ "content": {
+ "multipart/form-data": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "purpose": {
+ "description": "File purpose",
+ "type": "string"
+ },
+ "file": {
+ "description": "File to upload",
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "required": [
+ "purpose",
+ "file"
+ ]
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.FileObject"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "502": {
+ "description": "Bad Gateway",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "BearerAuth": []
+ }
+ ]
+ }
+ },
+ "/v1/files/{id}": {
+ "get": {
+ "tags": [
+ "files"
+ ],
+ "summary": "Get file metadata",
+ "parameters": [
+ {
+ "description": "File ID",
+ "name": "id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "Provider override",
+ "name": "provider",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.FileObject"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "502": {
+ "description": "Bad Gateway",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "BearerAuth": []
+ }
+ ]
+ },
+ "delete": {
+ "tags": [
+ "files"
+ ],
+ "summary": "Delete a file",
+ "parameters": [
+ {
+ "description": "File ID",
+ "name": "id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "Provider override",
+ "name": "provider",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.FileDeleteResponse"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "502": {
+ "description": "Bad Gateway",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "BearerAuth": []
+ }
+ ]
+ }
+ },
+ "/v1/files/{id}/content": {
+ "get": {
+ "tags": [
+ "files"
+ ],
+ "summary": "Download file content",
+ "parameters": [
+ {
+ "description": "File ID",
+ "name": "id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "Provider override",
+ "name": "provider",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Raw file content",
+ "content": {
+ "application/octet-stream": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/octet-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized",
+ "content": {
+ "application/octet-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found",
+ "content": {
+ "application/octet-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "502": {
+ "description": "Bad Gateway",
+ "content": {
+ "application/octet-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "BearerAuth": []
+ }
+ ]
+ }
+ },
+ "/v1/models": {
+ "get": {
+ "tags": [
+ "models"
+ ],
+ "summary": "List available models",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.ModelsResponse"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "502": {
+ "description": "Bad Gateway",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "BearerAuth": []
+ }
+ ]
+ }
+ },
+ "/v1/responses": {
+ "post": {
+ "tags": [
+ "responses"
+ ],
+ "summary": "Create a model response (Responses API)",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.ResponsesRequest"
+ }
+ }
+ },
+ "description": "Responses API request",
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "JSON response or SSE stream when stream=true",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.ResponsesResponse"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.ResponsesResponse"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "429": {
+ "description": "Too Many Requests",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ },
+ "502": {
+ "description": "Bad Gateway",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ },
+ "text/event-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/core.GatewayError"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "BearerAuth": []
+ }
+ ]
+ }
+ }
+ },
+ "servers": [
+ {
+ "url": "http://localhost:8080",
+ "description": "Local GoModel"
+ }
+ ],
+ "components": {
+ "securitySchemes": {
+ "BearerAuth": {
+ "type": "apiKey",
+ "name": "Authorization",
+ "in": "header"
+ }
+ },
+ "schemas": {
+ "auditlog.ConversationResult": {
+ "type": "object",
+ "properties": {
+ "anchor_id": {
+ "type": "string"
+ },
+ "entries": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/auditlog.LogEntry"
+ }
+ }
+ }
+ },
+ "auditlog.LogData": {
+ "type": "object",
+ "properties": {
+ "api_key_hash": {
+ "type": "string"
+ },
+ "error_message": {
+ "description": "Error details (message can be long, so kept in JSON)",
+ "type": "string"
+ },
+ "max_tokens": {
+ "type": "integer"
+ },
+ "request_body": {
+ "description": "Optional bodies (when LOGGING_LOG_BODIES=true)\nStored as interface{} so MongoDB serializes as native BSON documents (queryable/readable)\ninstead of BSON Binary (base64 in Compass)"
+ },
+ "request_body_too_big_to_handle": {
+ "description": "Body capture status flags (set when body exceeds 1MB limit)",
+ "type": "boolean"
+ },
+ "request_headers": {
+ "description": "Optional headers (when LOGGING_LOG_HEADERS=true)\nSensitive headers are auto-redacted",
+ "type": "object",
+ "additionalProperties": {
+ "type": "string"
+ }
+ },
+ "response_body": {},
+ "response_body_too_big_to_handle": {
+ "type": "boolean"
+ },
+ "response_headers": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "string"
+ }
+ },
+ "temperature": {
+ "description": "Request parameters",
+ "type": "number"
+ },
+ "user_agent": {
+ "description": "Identity",
+ "type": "string"
+ },
+ "workflow_features": {
+ "description": "WorkflowFeatures captures the request-time effective workflow features\nafter runtime caps were applied. This keeps audit views historically accurate\neven if the active process config changes later.",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/auditlog.WorkflowFeaturesSnapshot"
+ }
+ ]
+ }
+ }
+ },
+ "auditlog.LogEntry": {
+ "type": "object",
+ "properties": {
+ "alias_used": {
+ "type": "boolean"
+ },
+ "auth_key_id": {
+ "type": "string"
+ },
+ "auth_method": {
+ "type": "string"
+ },
+ "cache_type": {
+ "type": "string"
+ },
+ "client_ip": {
+ "type": "string"
+ },
+ "data": {
+ "description": "Data contains flexible request/response information as JSON",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/auditlog.LogData"
+ }
+ ]
+ },
+ "duration_ns": {
+ "description": "DurationNs is the request duration in nanoseconds",
+ "type": "integer"
+ },
+ "error_type": {
+ "type": "string"
+ },
+ "id": {
+ "description": "ID is a unique identifier for this log entry (UUID)",
+ "type": "string"
+ },
+ "method": {
+ "type": "string"
+ },
+ "path": {
+ "type": "string"
+ },
+ "provider": {
+ "description": "canonical provider type used for routing and filters",
+ "type": "string"
+ },
+ "provider_name": {
+ "type": "string"
+ },
+ "request_id": {
+ "description": "Extracted fields for efficient filtering (indexed in relational DBs)",
+ "type": "string"
+ },
+ "requested_model": {
+ "description": "Core fields (indexed for queries)",
+ "type": "string"
+ },
+ "resolved_model": {
+ "type": "string"
+ },
+ "status_code": {
+ "type": "integer"
+ },
+ "stream": {
+ "type": "boolean"
+ },
+ "timestamp": {
+ "description": "Timestamp is when the request started",
+ "type": "string"
+ },
+ "user_path": {
+ "type": "string"
+ },
+ "workflow_version_id": {
+ "type": "string"
+ }
+ }
+ },
+ "auditlog.LogListResult": {
+ "type": "object",
+ "properties": {
+ "entries": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/auditlog.LogEntry"
+ }
+ },
+ "limit": {
+ "type": "integer"
+ },
+ "offset": {
+ "type": "integer"
+ },
+ "total": {
+ "type": "integer"
+ }
+ }
+ },
+ "auditlog.WorkflowFeaturesSnapshot": {
+ "type": "object",
+ "properties": {
+ "audit": {
+ "type": "boolean"
+ },
+ "cache": {
+ "type": "boolean"
+ },
+ "fallback": {
+ "type": "boolean"
+ },
+ "guardrails": {
+ "type": "boolean"
+ },
+ "usage": {
+ "type": "boolean"
+ }
+ }
+ },
+ "core.BatchError": {
+ "type": "object",
+ "properties": {
+ "message": {
+ "type": "string"
+ },
+ "type": {
+ "type": "string"
+ }
+ }
+ },
+ "core.BatchListResponse": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/core.BatchResponse"
+ }
+ },
+ "first_id": {
+ "type": "string"
+ },
+ "has_more": {
+ "type": "boolean"
+ },
+ "last_id": {
+ "type": "string"
+ },
+ "object": {
+ "type": "string"
+ }
+ }
+ },
+ "core.BatchRequest": {
+ "type": "object",
+ "properties": {
+ "completion_window": {
+ "type": "string"
+ },
+ "endpoint": {
+ "type": "string"
+ },
+ "input_file_id": {
+ "type": "string"
+ },
+ "metadata": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "string"
+ }
+ },
+ "requests": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/core.BatchRequestItem"
+ }
+ }
+ }
+ },
+ "core.BatchRequestCounts": {
+ "type": "object",
+ "properties": {
+ "completed": {
+ "type": "integer"
+ },
+ "failed": {
+ "type": "integer"
+ },
+ "total": {
+ "type": "integer"
+ }
+ }
+ },
+ "core.BatchRequestItem": {
+ "type": "object",
+ "properties": {
+ "body": {
+ "type": "object"
+ },
+ "custom_id": {
+ "type": "string"
+ },
+ "method": {
+ "type": "string"
+ },
+ "url": {
+ "type": "string"
+ }
+ }
+ },
+ "core.BatchResponse": {
+ "type": "object",
+ "properties": {
+ "cancelled_at": {
+ "type": "integer"
+ },
+ "cancelling_at": {
+ "type": "integer"
+ },
+ "completed_at": {
+ "type": "integer"
+ },
+ "completion_window": {
+ "type": "string"
+ },
+ "created_at": {
+ "type": "integer"
+ },
+ "endpoint": {
+ "type": "string"
+ },
+ "failed_at": {
+ "type": "integer"
+ },
+ "id": {
+ "type": "string"
+ },
+ "in_progress_at": {
+ "type": "integer"
+ },
+ "input_file_id": {
+ "type": "string"
+ },
+ "metadata": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "string"
+ }
+ },
+ "object": {
+ "type": "string"
+ },
+ "provider": {
+ "type": "string"
+ },
+ "provider_batch_id": {
+ "type": "string"
+ },
+ "request_counts": {
+ "$ref": "#/components/schemas/core.BatchRequestCounts"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/core.BatchResultItem"
+ }
+ },
+ "status": {
+ "type": "string"
+ },
+ "usage": {
+ "description": "Gateway extension: optional usage/result snapshots persisted by the gateway.",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/core.BatchUsageSummary"
+ }
+ ]
+ }
+ }
+ },
+ "core.BatchResultItem": {
+ "type": "object",
+ "properties": {
+ "custom_id": {
+ "type": "string"
+ },
+ "error": {
+ "$ref": "#/components/schemas/core.BatchError"
+ },
+ "index": {
+ "type": "integer"
+ },
+ "model": {
+ "type": "string"
+ },
+ "provider": {
+ "type": "string"
+ },
+ "response": {},
+ "status_code": {
+ "type": "integer"
+ },
+ "url": {
+ "type": "string"
+ }
+ }
+ },
+ "core.BatchResultsResponse": {
+ "type": "object",
+ "properties": {
+ "batch_id": {
+ "type": "string"
+ },
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/core.BatchResultItem"
+ }
+ },
+ "object": {
+ "type": "string"
+ }
+ }
+ },
+ "core.BatchUsageSummary": {
+ "type": "object",
+ "properties": {
+ "input_cost": {
+ "type": "number"
+ },
+ "input_tokens": {
+ "type": "integer"
+ },
+ "output_cost": {
+ "type": "number"
+ },
+ "output_tokens": {
+ "type": "integer"
+ },
+ "total_cost": {
+ "type": "number"
+ },
+ "total_tokens": {
+ "type": "integer"
+ }
+ }
+ },
+ "core.ChatRequest": {
+ "type": "object",
+ "properties": {
+ "max_tokens": {
+ "type": "integer"
+ },
+ "messages": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/core.Message"
+ }
+ },
+ "model": {
+ "type": "string"
+ },
+ "parallel_tool_calls": {
+ "type": "boolean"
+ },
+ "provider": {
+ "description": "Gateway routing hint; stripped before upstream execution.",
+ "type": "string"
+ },
+ "reasoning": {
+ "$ref": "#/components/schemas/core.Reasoning"
+ },
+ "stream": {
+ "type": "boolean"
+ },
+ "stream_options": {
+ "$ref": "#/components/schemas/core.StreamOptions"
+ },
+ "temperature": {
+ "type": "number"
+ },
+ "tool_choice": {
+ "description": "string or object"
+ },
+ "tools": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ }
+ },
+ "core.ChatResponse": {
+ "type": "object",
+ "properties": {
+ "choices": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/core.Choice"
+ }
+ },
+ "created": {
+ "type": "integer"
+ },
+ "id": {
+ "type": "string"
+ },
+ "model": {
+ "type": "string"
+ },
+ "object": {
+ "type": "string"
+ },
+ "provider": {
+ "type": "string"
+ },
+ "system_fingerprint": {
+ "type": "string"
+ },
+ "usage": {
+ "$ref": "#/components/schemas/core.Usage"
+ }
+ }
+ },
+ "core.Choice": {
+ "type": "object",
+ "properties": {
+ "finish_reason": {
+ "type": "string"
+ },
+ "index": {
+ "type": "integer"
+ },
+ "logprobs": {
+ "type": "object"
+ },
+ "message": {
+ "$ref": "#/components/schemas/core.ResponseMessage"
+ }
+ }
+ },
+ "core.CompletionTokensDetails": {
+ "type": "object",
+ "properties": {
+ "accepted_prediction_tokens": {
+ "type": "integer"
+ },
+ "audio_tokens": {
+ "type": "integer"
+ },
+ "reasoning_tokens": {
+ "type": "integer"
+ },
+ "rejected_prediction_tokens": {
+ "type": "integer"
+ }
+ }
+ },
+ "core.ContentPart": {
+ "type": "object",
+ "properties": {
+ "image_url": {
+ "$ref": "#/components/schemas/core.ImageURLContent"
+ },
+ "input_audio": {
+ "$ref": "#/components/schemas/core.InputAudioContent"
+ },
+ "text": {
+ "type": "string"
+ },
+ "type": {
+ "type": "string"
+ }
+ }
+ },
+ "core.EmbeddingData": {
+ "type": "object",
+ "properties": {
+ "embedding": {
+ "type": "object"
+ },
+ "index": {
+ "type": "integer"
+ },
+ "object": {
+ "type": "string"
+ }
+ }
+ },
+ "core.EmbeddingRequest": {
+ "type": "object",
+ "properties": {
+ "dimensions": {
+ "type": "integer"
+ },
+ "encoding_format": {
+ "type": "string"
+ },
+ "input": {},
+ "model": {
+ "type": "string"
+ },
+ "provider": {
+ "description": "Gateway routing hint; stripped before upstream execution.",
+ "type": "string"
+ }
+ }
+ },
+ "core.EmbeddingResponse": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/core.EmbeddingData"
+ }
+ },
+ "model": {
+ "type": "string"
+ },
+ "object": {
+ "type": "string"
+ },
+ "provider": {
+ "type": "string"
+ },
+ "usage": {
+ "$ref": "#/components/schemas/core.EmbeddingUsage"
+ }
+ }
+ },
+ "core.EmbeddingUsage": {
+ "type": "object",
+ "properties": {
+ "prompt_tokens": {
+ "type": "integer"
+ },
+ "total_tokens": {
+ "type": "integer"
+ }
+ }
+ },
+ "core.ErrorType": {
+ "type": "string",
+ "enum": [
+ "provider_error",
+ "rate_limit_error",
+ "invalid_request_error",
+ "authentication_error",
+ "not_found_error"
+ ],
+ "x-enum-varnames": [
+ "ErrorTypeProvider",
+ "ErrorTypeRateLimit",
+ "ErrorTypeInvalidRequest",
+ "ErrorTypeAuthentication",
+ "ErrorTypeNotFound"
+ ]
+ },
+ "core.FileDeleteResponse": {
+ "type": "object",
+ "properties": {
+ "deleted": {
+ "type": "boolean"
+ },
+ "id": {
+ "type": "string"
+ },
+ "object": {
+ "type": "string"
+ }
+ }
+ },
+ "core.FileListResponse": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/core.FileObject"
+ }
+ },
+ "has_more": {
+ "type": "boolean"
+ },
+ "object": {
+ "type": "string"
+ }
+ }
+ },
+ "core.FileObject": {
+ "type": "object",
+ "properties": {
+ "bytes": {
+ "type": "integer"
+ },
+ "created_at": {
+ "type": "integer"
+ },
+ "filename": {
+ "type": "string"
+ },
+ "id": {
+ "type": "string"
+ },
+ "object": {
+ "type": "string"
+ },
+ "provider": {
+ "description": "Gateway enrichment for multi-provider deployments.",
+ "type": "string"
+ },
+ "purpose": {
+ "type": "string"
+ },
+ "status": {
+ "type": "string"
+ },
+ "status_details": {
+ "type": "string"
+ }
+ }
+ },
+ "core.FunctionCall": {
+ "type": "object",
+ "properties": {
+ "arguments": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ }
+ }
+ },
+ "core.GatewayError": {
+ "type": "object",
+ "properties": {
+ "code": {
+ "type": "string",
+ "nullable": true
+ },
+ "message": {
+ "type": "string"
+ },
+ "param": {
+ "type": "string",
+ "nullable": true
+ },
+ "provider": {
+ "type": "string"
+ },
+ "status_code": {
+ "type": "integer"
+ },
+ "type": {
+ "$ref": "#/components/schemas/core.ErrorType"
+ }
+ }
+ },
+ "core.ImageURLContent": {
+ "type": "object",
+ "properties": {
+ "detail": {
+ "type": "string"
+ },
+ "media_type": {
+ "type": "string"
+ },
+ "url": {
+ "type": "string"
+ }
+ }
+ },
+ "core.InputAudioContent": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "string"
+ },
+ "format": {
+ "type": "string"
+ }
+ }
+ },
+ "core.Message": {
+ "type": "object",
+ "properties": {
+ "content": {
+ "description": "ContentSchema documents that `content` accepts either a plain string\nor an array of ContentPart values.",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/core.ContentPart"
+ },
+ "x-oneof": "[{\"type\":\"null\"},{\"type\":\"string\"},{\"type\":\"array\",\"items\":{\"$ref\":\"#/definitions/core.ContentPart\"}}]"
+ },
+ "role": {
+ "type": "string"
+ },
+ "tool_call_id": {
+ "type": "string"
+ },
+ "tool_calls": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/core.ToolCall"
+ }
+ }
+ }
+ },
+ "core.Model": {
+ "type": "object",
+ "properties": {
+ "created": {
+ "type": "integer"
+ },
+ "id": {
+ "type": "string"
+ },
+ "metadata": {
+ "description": "Metadata holds optional enrichment data (display name, pricing, capabilities, etc.).\nMay be nil if the model was not found in the external registry.",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/core.ModelMetadata"
+ }
+ ]
+ },
+ "object": {
+ "type": "string"
+ },
+ "owned_by": {
+ "type": "string"
+ }
+ }
+ },
+ "core.ModelCategory": {
+ "type": "string",
+ "enum": [
+ "all",
+ "text_generation",
+ "embedding",
+ "image",
+ "audio",
+ "video",
+ "utility"
+ ],
+ "x-enum-varnames": [
+ "CategoryAll",
+ "CategoryTextGeneration",
+ "CategoryEmbedding",
+ "CategoryImage",
+ "CategoryAudio",
+ "CategoryVideo",
+ "CategoryUtility"
+ ]
+ },
+ "core.ModelMetadata": {
+ "type": "object",
+ "properties": {
+ "capabilities": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "boolean"
+ }
+ },
+ "categories": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/core.ModelCategory"
+ }
+ },
+ "context_window": {
+ "type": "integer"
+ },
+ "description": {
+ "type": "string"
+ },
+ "display_name": {
+ "type": "string"
+ },
+ "family": {
+ "type": "string"
+ },
+ "max_output_tokens": {
+ "type": "integer"
+ },
+ "modes": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "pricing": {
+ "$ref": "#/components/schemas/core.ModelPricing"
+ },
+ "rankings": {
+ "type": "object",
+ "additionalProperties": {
+ "$ref": "#/components/schemas/core.ModelRanking"
+ }
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "core.ModelPricing": {
+ "type": "object",
+ "properties": {
+ "audio_input_per_mtok": {
+ "type": "number"
+ },
+ "audio_output_per_mtok": {
+ "type": "number"
+ },
+ "batch_input_per_mtok": {
+ "type": "number"
+ },
+ "batch_output_per_mtok": {
+ "type": "number"
+ },
+ "cache_write_per_mtok": {
+ "type": "number"
+ },
+ "cached_input_per_mtok": {
+ "type": "number"
+ },
+ "currency": {
+ "type": "string"
+ },
+ "input_per_image": {
+ "type": "number"
+ },
+ "input_per_mtok": {
+ "type": "number"
+ },
+ "output_per_mtok": {
+ "type": "number"
+ },
+ "per_character_input": {
+ "type": "number"
+ },
+ "per_image": {
+ "type": "number"
+ },
+ "per_page": {
+ "type": "number"
+ },
+ "per_request": {
+ "type": "number"
+ },
+ "per_second_input": {
+ "type": "number"
+ },
+ "per_second_output": {
+ "type": "number"
+ },
+ "reasoning_output_per_mtok": {
+ "type": "number"
+ },
+ "tiers": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/core.ModelPricingTier"
+ }
+ }
+ }
+ },
+ "core.ModelPricingTier": {
+ "type": "object",
+ "properties": {
+ "input_per_mtok": {
+ "type": "number"
+ },
+ "output_per_mtok": {
+ "type": "number"
+ },
+ "up_to_mtok": {
+ "type": "number"
+ }
+ }
+ },
+ "core.ModelRanking": {
+ "type": "object",
+ "properties": {
+ "as_of": {
+ "type": "string"
+ },
+ "elo": {
+ "type": "number"
+ },
+ "rank": {
+ "type": "integer"
+ }
+ }
+ },
+ "core.ModelsResponse": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/core.Model"
+ }
+ },
+ "object": {
+ "type": "string"
+ }
+ }
+ },
+ "core.PromptTokensDetails": {
+ "type": "object",
+ "properties": {
+ "audio_tokens": {
+ "type": "integer"
+ },
+ "cached_tokens": {
+ "type": "integer"
+ },
+ "image_tokens": {
+ "type": "integer"
+ },
+ "text_tokens": {
+ "type": "integer"
+ }
+ }
+ },
+ "core.Reasoning": {
+ "type": "object",
+ "properties": {
+ "effort": {
+ "description": "Effort controls how much reasoning effort the model should use.\nValid values are \"low\", \"medium\", and \"high\".",
+ "type": "string"
+ }
+ }
+ },
+ "core.ResponseMessage": {
+ "type": "object",
+ "properties": {
+ "content": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/core.ContentPart"
+ },
+ "x-oneof": "[{\"type\":\"null\"},{\"type\":\"string\"},{\"type\":\"array\",\"items\":{\"$ref\":\"#/definitions/core.ContentPart\"}}]"
+ },
+ "role": {
+ "type": "string"
+ },
+ "tool_calls": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/core.ToolCall"
+ }
+ }
+ }
+ },
+ "core.ResponsesContentItem": {
+ "type": "object",
+ "properties": {
+ "annotations": {
+ "description": "Providers can return structured annotation objects here (for example\ncitations from native tools), so keep the payload shape liberal.",
+ "type": "array",
+ "items": {
+ "type": "object"
+ }
+ },
+ "image_url": {
+ "$ref": "#/components/schemas/core.ImageURLContent"
+ },
+ "input_audio": {
+ "$ref": "#/components/schemas/core.InputAudioContent"
+ },
+ "text": {
+ "type": "string"
+ },
+ "type": {
+ "description": "\"output_text\", \"input_image\", \"input_audio\", etc.",
+ "type": "string"
+ }
+ }
+ },
+ "core.ResponsesError": {
+ "type": "object",
+ "properties": {
+ "code": {
+ "type": "string"
+ },
+ "message": {
+ "type": "string"
+ }
+ }
+ },
+ "core.ResponsesInputElement": {
+ "type": "object",
+ "properties": {
+ "arguments": {
+ "type": "string"
+ },
+ "call_id": {
+ "description": "Function call fields (type=\"function_call\")",
+ "type": "string"
+ },
+ "content": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/core.ContentPart"
+ },
+ "x-oneof": "[{\"type\":\"string\"},{\"type\":\"array\",\"items\":{\"$ref\":\"#/definitions/core.ContentPart\"}}]"
+ },
+ "name": {
+ "type": "string"
+ },
+ "output": {
+ "description": "Function call output fields (type=\"function_call_output\") — CallID shared above",
+ "type": "string"
+ },
+ "role": {
+ "description": "Message fields (type=\"\" or \"message\")",
+ "type": "string"
+ },
+ "status": {
+ "type": "string"
+ },
+ "type": {
+ "description": "\"message\", \"function_call\", \"function_call_output\"",
+ "type": "string"
+ }
+ }
+ },
+ "core.ResponsesOutputItem": {
+ "type": "object",
+ "properties": {
+ "arguments": {
+ "type": "string"
+ },
+ "call_id": {
+ "type": "string"
+ },
+ "content": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/core.ResponsesContentItem"
+ }
+ },
+ "id": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ },
+ "role": {
+ "type": "string"
+ },
+ "status": {
+ "type": "string"
+ },
+ "type": {
+ "description": "\"message\", \"function_call\", etc.",
+ "type": "string"
+ }
+ }
+ },
+ "core.ResponsesRequest": {
+ "type": "object",
+ "properties": {
+ "input": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/core.ResponsesInputElement"
+ },
+ "x-oneof": "[{\"type\":\"string\"},{\"type\":\"array\",\"items\":{\"$ref\":\"#/definitions/core.ResponsesInputElement\"}}]"
+ },
+ "instructions": {
+ "type": "string"
+ },
+ "max_output_tokens": {
+ "type": "integer"
+ },
+ "metadata": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "string"
+ }
+ },
+ "model": {
+ "type": "string"
+ },
+ "parallel_tool_calls": {
+ "type": "boolean"
+ },
+ "provider": {
+ "description": "Gateway routing hint; stripped before upstream execution.",
+ "type": "string"
+ },
+ "reasoning": {
+ "$ref": "#/components/schemas/core.Reasoning"
+ },
+ "stream": {
+ "type": "boolean"
+ },
+ "stream_options": {
+ "$ref": "#/components/schemas/core.StreamOptions"
+ },
+ "temperature": {
+ "type": "number"
+ },
+ "tool_choice": {
+ "description": "string or object"
+ },
+ "tools": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ }
+ },
+ "core.ResponsesResponse": {
+ "type": "object",
+ "properties": {
+ "created_at": {
+ "type": "integer"
+ },
+ "error": {
+ "$ref": "#/components/schemas/core.ResponsesError"
+ },
+ "id": {
+ "type": "string"
+ },
+ "model": {
+ "type": "string"
+ },
+ "object": {
+ "description": "\"response\"",
+ "type": "string"
+ },
+ "output": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/core.ResponsesOutputItem"
+ }
+ },
+ "provider": {
+ "type": "string"
+ },
+ "status": {
+ "description": "\"completed\", \"failed\", \"in_progress\"",
+ "type": "string"
+ },
+ "usage": {
+ "$ref": "#/components/schemas/core.ResponsesUsage"
+ }
+ }
+ },
+ "core.ResponsesUsage": {
+ "type": "object",
+ "properties": {
+ "completion_tokens_details": {
+ "$ref": "#/components/schemas/core.CompletionTokensDetails"
+ },
+ "input_tokens": {
+ "type": "integer"
+ },
+ "output_tokens": {
+ "type": "integer"
+ },
+ "prompt_tokens_details": {
+ "$ref": "#/components/schemas/core.PromptTokensDetails"
+ },
+ "raw_usage": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "total_tokens": {
+ "type": "integer"
+ }
+ }
+ },
+ "core.StreamOptions": {
+ "type": "object",
+ "properties": {
+ "include_usage": {
+ "description": "IncludeUsage requests token usage information in streaming responses.\nWhen true, the final streaming chunk will include usage statistics.",
+ "type": "boolean"
+ }
+ }
+ },
+ "core.ToolCall": {
+ "type": "object",
+ "properties": {
+ "function": {
+ "$ref": "#/components/schemas/core.FunctionCall"
+ },
+ "id": {
+ "type": "string"
+ },
+ "type": {
+ "type": "string"
+ }
+ }
+ },
+ "core.Usage": {
+ "type": "object",
+ "properties": {
+ "completion_tokens": {
+ "type": "integer"
+ },
+ "completion_tokens_details": {
+ "$ref": "#/components/schemas/core.CompletionTokensDetails"
+ },
+ "prompt_tokens": {
+ "type": "integer"
+ },
+ "prompt_tokens_details": {
+ "$ref": "#/components/schemas/core.PromptTokensDetails"
+ },
+ "raw_usage": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "total_tokens": {
+ "type": "integer"
+ }
+ }
+ },
+ "providers.CategoryCount": {
+ "type": "object",
+ "properties": {
+ "category": {
+ "$ref": "#/components/schemas/core.ModelCategory"
+ },
+ "count": {
+ "type": "integer"
+ },
+ "display_name": {
+ "type": "string"
+ }
+ }
+ },
+ "usage.CacheOverview": {
+ "type": "object",
+ "properties": {
+ "daily": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/usage.CacheOverviewDaily"
+ }
+ },
+ "summary": {
+ "$ref": "#/components/schemas/usage.CacheOverviewSummary"
+ }
+ }
+ },
+ "usage.CacheOverviewDaily": {
+ "type": "object",
+ "properties": {
+ "date": {
+ "type": "string"
+ },
+ "exact_hits": {
+ "type": "integer"
+ },
+ "hits": {
+ "type": "integer"
+ },
+ "input_tokens": {
+ "type": "integer"
+ },
+ "output_tokens": {
+ "type": "integer"
+ },
+ "saved_cost": {
+ "type": "number"
+ },
+ "semantic_hits": {
+ "type": "integer"
+ },
+ "total_tokens": {
+ "type": "integer"
+ }
+ }
+ },
+ "usage.CacheOverviewSummary": {
+ "type": "object",
+ "properties": {
+ "exact_hits": {
+ "type": "integer"
+ },
+ "semantic_hits": {
+ "type": "integer"
+ },
+ "total_hits": {
+ "type": "integer"
+ },
+ "total_input_tokens": {
+ "type": "integer"
+ },
+ "total_output_tokens": {
+ "type": "integer"
+ },
+ "total_saved_cost": {
+ "type": "number"
+ },
+ "total_tokens": {
+ "type": "integer"
+ }
+ }
+ },
+ "usage.DailyUsage": {
+ "type": "object",
+ "properties": {
+ "date": {
+ "type": "string"
+ },
+ "input_cost": {
+ "type": "number"
+ },
+ "input_tokens": {
+ "type": "integer"
+ },
+ "output_cost": {
+ "type": "number"
+ },
+ "output_tokens": {
+ "type": "integer"
+ },
+ "requests": {
+ "type": "integer"
+ },
+ "total_cost": {
+ "type": "number"
+ },
+ "total_tokens": {
+ "type": "integer"
+ }
+ }
+ },
+ "usage.ModelUsage": {
+ "type": "object",
+ "properties": {
+ "input_cost": {
+ "type": "number"
+ },
+ "input_tokens": {
+ "type": "integer"
+ },
+ "model": {
+ "type": "string"
+ },
+ "output_cost": {
+ "type": "number"
+ },
+ "output_tokens": {
+ "type": "integer"
+ },
+ "provider": {
+ "type": "string"
+ },
+ "provider_name": {
+ "type": "string"
+ },
+ "total_cost": {
+ "type": "number"
+ }
+ }
+ },
+ "usage.UsageLogEntry": {
+ "type": "object",
+ "properties": {
+ "cache_type": {
+ "type": "string"
+ },
+ "costs_calculation_caveat": {
+ "type": "string"
+ },
+ "endpoint": {
+ "type": "string"
+ },
+ "id": {
+ "type": "string"
+ },
+ "input_cost": {
+ "type": "number"
+ },
+ "input_tokens": {
+ "type": "integer"
+ },
+ "model": {
+ "type": "string"
+ },
+ "output_cost": {
+ "type": "number"
+ },
+ "output_tokens": {
+ "type": "integer"
+ },
+ "provider": {
+ "type": "string"
+ },
+ "provider_id": {
+ "type": "string"
+ },
+ "provider_name": {
+ "type": "string"
+ },
+ "raw_data": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "request_id": {
+ "type": "string"
+ },
+ "timestamp": {
+ "type": "string"
+ },
+ "total_cost": {
+ "type": "number"
+ },
+ "total_tokens": {
+ "type": "integer"
+ },
+ "user_path": {
+ "type": "string"
+ }
+ }
+ },
+ "usage.UsageLogResult": {
+ "type": "object",
+ "properties": {
+ "entries": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/usage.UsageLogEntry"
+ }
+ },
+ "limit": {
+ "type": "integer"
+ },
+ "offset": {
+ "type": "integer"
+ },
+ "total": {
+ "type": "integer"
+ }
+ }
+ },
+ "usage.UsageSummary": {
+ "type": "object",
+ "properties": {
+ "total_cost": {
+ "type": "number"
+ },
+ "total_input_cost": {
+ "type": "number"
+ },
+ "total_input_tokens": {
+ "type": "integer"
+ },
+ "total_output_cost": {
+ "type": "number"
+ },
+ "total_output_tokens": {
+ "type": "integer"
+ },
+ "total_requests": {
+ "type": "integer"
+ },
+ "total_tokens": {
+ "type": "integer"
+ }
+ }
+ },
+ "usage.UserPathUsage": {
+ "type": "object",
+ "properties": {
+ "input_cost": {
+ "type": "number",
+ "nullable": true
+ },
+ "input_tokens": {
+ "type": "integer"
+ },
+ "output_cost": {
+ "type": "number",
+ "nullable": true
+ },
+ "output_tokens": {
+ "type": "integer"
+ },
+ "total_cost": {
+ "type": "number",
+ "nullable": true
+ },
+ "total_tokens": {
+ "type": "integer"
+ },
+ "user_path": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/docs/wordmark-dark.svg b/docs/wordmark-dark.svg
new file mode 100644
index 00000000..3f01e61f
--- /dev/null
+++ b/docs/wordmark-dark.svg
@@ -0,0 +1,7 @@
+
diff --git a/docs/wordmark-light.svg b/docs/wordmark-light.svg
new file mode 100644
index 00000000..2bd3ff7a
--- /dev/null
+++ b/docs/wordmark-light.svg
@@ -0,0 +1,7 @@
+
diff --git a/helm/Chart.yaml b/helm/Chart.yaml
index 95cdc50f..c1c6253a 100644
--- a/helm/Chart.yaml
+++ b/helm/Chart.yaml
@@ -16,12 +16,12 @@ keywords:
- xai
- proxy
-home: https://github.com/ENTERPILOT/GOModel
+home: https://github.com/ENTERPILOT/GoModel
sources:
- - https://github.com/ENTERPILOT/GOModel
+ - https://github.com/ENTERPILOT/GoModel
maintainers:
- - name: GOModel Team
+ - name: GoModel Team
dependencies:
- name: redis
diff --git a/helm/README.md b/helm/README.md
index 494e4c2a..4a5c7fa0 100644
--- a/helm/README.md
+++ b/helm/README.md
@@ -1,4 +1,4 @@
-# GOModel Helm Chart
+# GoModel Helm Chart
High-performance AI gateway for multiple LLM providers (OpenAI, Anthropic, Gemini, Groq, xAI, Oracle).
@@ -44,30 +44,30 @@ helm install gomodel ./helm \
### Key Values
-| Parameter | Description | Default |
-|-----------|-------------|---------|
-| `replicaCount` | Number of replicas | `2` |
-| `image.repository` | Image repository | `enterpilot/gomodel` |
-| `image.tag` | Image tag | `""` (uses appVersion) |
-| `server.port` | Server port | `8080` |
-| `server.bodySizeLimit` | Max request body size | `"10M"` |
-| `auth.masterKey` | Master key for auth | `""` |
-| `auth.existingSecret` | Existing secret for auth | `""` |
-| `providers.existingSecret` | Existing secret for API keys | `""` |
-| `providers.openai.enabled` | Enable OpenAI | `false` |
-| `providers.anthropic.enabled` | Enable Anthropic | `false` |
-| `providers.gemini.enabled` | Enable Gemini | `false` |
-| `providers.groq.enabled` | Enable Groq | `false` |
-| `providers.xai.enabled` | Enable xAI | `false` |
-| `providers.oracle.enabled` | Enable Oracle | `false` |
-| `providers.oracle.baseUrl` | Oracle OpenAI-compatible base URL mapped to `ORACLE_BASE_URL`; required when Oracle is enabled | `""` |
-| `cache.type` | Cache type (local/redis) | `"redis"` |
-| `redis.enabled` | Deploy Redis subchart | `true` |
-| `metrics.enabled` | Enable Prometheus metrics | `true` |
-| `metrics.serviceMonitor.enabled` | Create ServiceMonitor | `false` |
-| `ingress.enabled` | Enable Ingress | `false` |
-| `gateway.enabled` | Enable Gateway API HTTPRoute | `false` |
-| `autoscaling.enabled` | Enable HPA | `false` |
+| Parameter | Description | Default |
+| -------------------------------- | ---------------------------------------------------------------------------------------------- | ---------------------- |
+| `replicaCount` | Number of replicas | `2` |
+| `image.repository` | Image repository | `enterpilot/gomodel` |
+| `image.tag` | Image tag | `""` (uses appVersion) |
+| `server.port` | Server port | `8080` |
+| `server.bodySizeLimit` | Max request body size | `"10M"` |
+| `auth.masterKey` | Master key for auth | `""` |
+| `auth.existingSecret` | Existing secret for auth | `""` |
+| `providers.existingSecret` | Existing secret for API keys | `""` |
+| `providers.openai.enabled` | Enable OpenAI | `false` |
+| `providers.anthropic.enabled` | Enable Anthropic | `false` |
+| `providers.gemini.enabled` | Enable Gemini | `false` |
+| `providers.groq.enabled` | Enable Groq | `false` |
+| `providers.xai.enabled` | Enable xAI | `false` |
+| `providers.oracle.enabled` | Enable Oracle | `false` |
+| `providers.oracle.baseUrl` | Oracle OpenAI-compatible base URL mapped to `ORACLE_BASE_URL`; required when Oracle is enabled | `""` |
+| `cache.type` | Cache type (local/redis) | `"redis"` |
+| `redis.enabled` | Deploy Redis subchart | `true` |
+| `metrics.enabled` | Enable Prometheus metrics | `true` |
+| `metrics.serviceMonitor.enabled` | Create ServiceMonitor | `false` |
+| `ingress.enabled` | Enable Ingress | `false` |
+| `gateway.enabled` | Enable Gateway API HTTPRoute | `false` |
+| `autoscaling.enabled` | Enable HPA | `false` |
### Using Existing Secrets
diff --git a/helm/values.schema.json b/helm/values.schema.json
index 2e44f9d9..6a7213b8 100644
--- a/helm/values.schema.json
+++ b/helm/values.schema.json
@@ -1,6 +1,6 @@
{
"$schema": "https://json-schema.org/draft-07/schema#",
- "title": "GOModel Helm Chart Values",
+ "title": "GoModel Helm Chart Values",
"type": "object",
"allOf": [
{
diff --git a/internal/admin/dashboard/dashboard.go b/internal/admin/dashboard/dashboard.go
index 79d0ada3..6e611cb0 100644
--- a/internal/admin/dashboard/dashboard.go
+++ b/internal/admin/dashboard/dashboard.go
@@ -1,4 +1,4 @@
-// Package dashboard provides the embedded admin dashboard UI for GOModel.
+// Package dashboard provides the embedded admin dashboard UI for GoModel.
package dashboard
import (
diff --git a/internal/admin/dashboard/static/css/dashboard.css b/internal/admin/dashboard/static/css/dashboard.css
index fd611d40..91b6ddf1 100644
--- a/internal/admin/dashboard/static/css/dashboard.css
+++ b/internal/admin/dashboard/static/css/dashboard.css
@@ -1,39 +1,84 @@
-/* GOModel Dashboard Styles */
+/* GoModel Dashboard Styles */
/* TODO: It might be optimized once the css preprocessor is added. Intentional duplication for now */
/* Dark mode (default) — matches gomodel.enterpilot.io */
:root {
- --bg: #111110;
- --bg-surface: #1e1d1c;
- --bg-surface-hover: #2a2420;
- --border: #2a2826;
- --text: #e8e0d6;
- --text-muted: #9a918a;
- --accent: #b8956e;
- --accent-hover: #d4b896;
- --success: #34d399;
- --warning: #f59e0b;
- --danger: #ef4444;
- --sidebar-width: 240px;
- --radius: 8px;
- --chart-grid: #2a2826;
- --chart-text: #9a918a;
- --chart-tooltip-bg: #1e1d1c;
- --chart-tooltip-border: #2a2826;
- --chart-tooltip-text: #e8e0d6;
- --cal-level-0: #161b22;
- --cal-level-1: #0e4429;
- --cal-level-2: #006d32;
- --cal-level-3: #26a641;
- --cal-level-4: #39d353;
- --alias-row-valid-bg: color-mix(in srgb, var(--bg-surface-hover) 86%, #fff 14%);
- --alias-row-valid-bg-hover: color-mix(in srgb, var(--bg-surface-hover) 72%, #fff 28%);
- color-scheme: dark;
+ --bg: #111110;
+ --bg-surface: #1e1d1c;
+ --bg-surface-hover: #2a2420;
+ --border: #2a2826;
+ --text: #e8e0d6;
+ --text-muted: #9a918a;
+ --accent: #b8956e;
+ --accent-hover: #d4b896;
+ --success: #34d399;
+ --warning: #f59e0b;
+ --danger: #ef4444;
+ --sidebar-width: 240px;
+ --radius: 8px;
+ --chart-grid: #2a2826;
+ --chart-text: #9a918a;
+ --chart-tooltip-bg: #1e1d1c;
+ --chart-tooltip-border: #2a2826;
+ --chart-tooltip-text: #e8e0d6;
+ --cal-level-0: #161b22;
+ --cal-level-1: #0e4429;
+ --cal-level-2: #006d32;
+ --cal-level-3: #26a641;
+ --cal-level-4: #39d353;
+ --alias-row-valid-bg: color-mix(
+ in srgb,
+ var(--bg-surface-hover) 86%,
+ #fff 14%
+ );
+ --alias-row-valid-bg-hover: color-mix(
+ in srgb,
+ var(--bg-surface-hover) 72%,
+ #fff 28%
+ );
+ color-scheme: dark;
}
/* Light mode — matches gomodel.enterpilot.io */
[data-theme="light"] {
+ --bg: #f5f0ea;
+ --bg-surface: #ffffff;
+ --bg-surface-hover: #ece5dc;
+ --border: #e8e0d6;
+ --text: #2d2519;
+ --text-muted: #7a7068;
+ --accent: #755c3d;
+ --accent-hover: #9a7d5a;
+ --success: #34d399;
+ --warning: #d97706;
+ --danger: #dc2626;
+ --chart-grid: #e8e0d6;
+ --chart-text: #7a7068;
+ --chart-tooltip-bg: #ffffff;
+ --chart-tooltip-border: #e8e0d6;
+ --chart-tooltip-text: #2d2519;
+ --cal-level-0: #ebedf0;
+ --cal-level-1: #9be9a8;
+ --cal-level-2: #40c463;
+ --cal-level-3: #30a14e;
+ --cal-level-4: #216e39;
+ --alias-row-valid-bg: color-mix(
+ in srgb,
+ var(--bg-surface) 96%,
+ var(--accent) 4%
+ );
+ --alias-row-valid-bg-hover: color-mix(
+ in srgb,
+ var(--bg-surface) 90%,
+ var(--accent) 10%
+ );
+ color-scheme: light;
+}
+
+/* System preference: light */
+@media (prefers-color-scheme: light) {
+ :root:not([data-theme="dark"]) {
--bg: #f5f0ea;
--bg-surface: #ffffff;
--bg-surface-hover: #ece5dc;
@@ -55,3563 +100,3637 @@
--cal-level-2: #40c463;
--cal-level-3: #30a14e;
--cal-level-4: #216e39;
- --alias-row-valid-bg: color-mix(in srgb, var(--bg-surface) 96%, var(--accent) 4%);
- --alias-row-valid-bg-hover: color-mix(in srgb, var(--bg-surface) 90%, var(--accent) 10%);
+ --alias-row-valid-bg: color-mix(
+ in srgb,
+ var(--bg-surface) 96%,
+ var(--accent) 4%
+ );
+ --alias-row-valid-bg-hover: color-mix(
+ in srgb,
+ var(--bg-surface) 90%,
+ var(--accent) 10%
+ );
color-scheme: light;
+ }
}
-/* System preference: light */
-@media (prefers-color-scheme: light) {
- :root:not([data-theme="dark"]) {
- --bg: #f5f0ea;
- --bg-surface: #ffffff;
- --bg-surface-hover: #ece5dc;
- --border: #e8e0d6;
- --text: #2d2519;
- --text-muted: #7a7068;
- --accent: #755c3d;
- --accent-hover: #9a7d5a;
- --success: #34d399;
- --warning: #d97706;
- --danger: #dc2626;
- --chart-grid: #e8e0d6;
- --chart-text: #7a7068;
- --chart-tooltip-bg: #ffffff;
- --chart-tooltip-border: #e8e0d6;
- --chart-tooltip-text: #2d2519;
- --cal-level-0: #ebedf0;
- --cal-level-1: #9be9a8;
- --cal-level-2: #40c463;
- --cal-level-3: #30a14e;
- --cal-level-4: #216e39;
- --alias-row-valid-bg: color-mix(in srgb, var(--bg-surface) 96%, var(--accent) 4%);
- --alias-row-valid-bg-hover: color-mix(in srgb, var(--bg-surface) 90%, var(--accent) 10%);
- color-scheme: light;
- }
-}
-
-* { box-sizing: border-box; margin: 0; padding: 0; }
+* {
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0;
+}
[x-cloak] {
- display: none !important;
+ display: none !important;
}
body {
- font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
- background: var(--bg);
- color: var(--text);
- line-height: 1.5;
+ font-family:
+ "Inter",
+ -apple-system,
+ BlinkMacSystemFont,
+ "Segoe UI",
+ Roboto,
+ sans-serif;
+ background: var(--bg);
+ color: var(--text);
+ line-height: 1.5;
}
.app {
- display: flex;
- min-height: 100vh;
+ display: flex;
+ min-height: 100vh;
}
/* Sidebar */
.sidebar {
- flex: 0 0 var(--sidebar-width);
- width: var(--sidebar-width);
- background: var(--bg-surface);
- border-right: 1px solid var(--border);
- display: flex;
- flex-direction: column;
- position: sticky;
- top: 0;
- max-height: 100vh;
- overflow-y: auto;
- -webkit-overflow-scrolling: touch;
- z-index: 10;
- transition: flex-basis 0.2s, width 0.2s;
+ flex: 0 0 var(--sidebar-width);
+ width: var(--sidebar-width);
+ background: var(--bg-surface);
+ border-right: 1px solid var(--border);
+ display: flex;
+ flex-direction: column;
+ position: sticky;
+ top: 0;
+ max-height: 100vh;
+ overflow-y: auto;
+ -webkit-overflow-scrolling: touch;
+ z-index: 10;
+ transition:
+ flex-basis 0.2s,
+ width 0.2s;
}
.sidebar-header {
- padding: 20px;
- border-bottom: 1px solid var(--border);
- display: flex;
- align-items: center;
- gap: 10px;
+ padding: 20px;
+ border-bottom: 1px solid var(--border);
+ display: flex;
+ align-items: center;
+ gap: 10px;
}
.sidebar-logo {
- width: 28px;
- height: 28px;
- flex-shrink: 0;
+ width: 28px;
+ height: 28px;
+ flex-shrink: 0;
}
.sidebar-logo svg {
- width: 100%;
- height: 100%;
+ width: 100%;
+ height: 100%;
}
.sidebar-header h1 {
- font-size: 18px;
- font-weight: 700;
- letter-spacing: -0.3px;
+ font-size: 18px;
+ font-weight: 700;
+ letter-spacing: -0.3px;
}
.badge {
- background: var(--accent);
- color: #fff;
- font-size: 10px;
- font-weight: 600;
- padding: 2px 8px;
- border-radius: 10px;
- text-transform: uppercase;
- letter-spacing: 0.5px;
+ background: var(--accent);
+ color: #fff;
+ font-size: 10px;
+ font-weight: 600;
+ padding: 2px 8px;
+ border-radius: 10px;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
}
.sidebar-nav {
- display: flex;
- flex-direction: column;
- gap: 4px;
- padding: 12px;
- flex: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ padding: 12px;
+ flex: 1;
}
.nav-item {
- display: flex;
- align-items: center;
- gap: 10px;
- padding: 10px 12px;
- border-radius: var(--radius);
- color: var(--text-muted);
- text-decoration: none;
- font-size: 14px;
- font-weight: 500;
- transition: all 0.15s;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 10px 12px;
+ border-radius: var(--radius);
+ color: var(--text-muted);
+ text-decoration: none;
+ font-size: 14px;
+ font-weight: 500;
+ transition: all 0.15s;
}
.nav-item:hover {
- background: var(--bg-surface-hover);
- color: var(--text);
+ background: var(--bg-surface-hover);
+ color: var(--text);
}
.nav-item.active {
- background: var(--accent);
- color: #fff;
+ background: var(--accent);
+ color: #fff;
+}
+
+.nav-icon {
+ width: 18px;
+ height: 18px;
+ flex: 0 0 18px;
}
.sidebar-footer {
- padding: 16px;
- border-top: 1px solid var(--border);
+ padding: 16px;
+ border-top: 1px solid var(--border);
}
.api-key-section {
- display: grid;
- gap: 8px;
+ display: grid;
+ gap: 8px;
}
.api-key-open-btn {
- width: 100%;
- display: inline-flex;
- align-items: center;
- justify-content: center;
- gap: 8px;
- padding: 8px 10px;
- background: transparent;
- border: 1px solid var(--accent);
- border-radius: var(--radius);
- color: var(--accent);
- font-size: 13px;
- font-family: inherit;
- font-weight: 600;
- cursor: pointer;
- transition: background-color 0.15s, border-color 0.15s;
+ width: 100%;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ padding: 8px 10px;
+ background: transparent;
+ border: 1px solid var(--accent);
+ border-radius: var(--radius);
+ color: var(--accent);
+ font-size: 13px;
+ font-family: inherit;
+ font-weight: 600;
+ cursor: pointer;
+ transition:
+ background-color 0.15s,
+ border-color 0.15s;
}
.api-key-open-icon {
- width: 15px;
- height: 15px;
- flex: 0 0 15px;
+ width: 15px;
+ height: 15px;
+ flex: 0 0 15px;
}
.api-key-open-btn:hover {
- background: color-mix(in srgb, var(--accent) 10%, transparent);
- border-color: color-mix(in srgb, var(--accent) 78%, var(--text));
- color: color-mix(in srgb, var(--accent) 78%, var(--text));
+ background: color-mix(in srgb, var(--accent) 10%, transparent);
+ border-color: color-mix(in srgb, var(--accent) 78%, var(--text));
+ color: color-mix(in srgb, var(--accent) 78%, var(--text));
}
.api-key-open-btn:focus-visible {
- outline: 2px solid color-mix(in srgb, var(--accent) 36%, transparent);
- outline-offset: 2px;
+ outline: 2px solid color-mix(in srgb, var(--accent) 36%, transparent);
+ outline-offset: 2px;
}
.auth-dialog-backdrop {
- position: fixed;
- inset: 0;
- background: rgba(0, 0, 0, 0.48);
- z-index: 80;
+ position: fixed;
+ inset: 0;
+ background: rgba(0, 0, 0, 0.48);
+ z-index: 80;
}
.auth-dialog-shell {
- position: fixed;
- inset: 0;
- z-index: 90;
- display: grid;
- place-items: center;
- padding: 20px;
+ position: fixed;
+ inset: 0;
+ z-index: 90;
+ display: grid;
+ place-items: center;
+ padding: 20px;
}
.auth-dialog {
- width: min(440px, 100%);
- background: var(--bg-surface);
- border: 1px solid var(--border);
- border-radius: var(--radius);
- box-shadow: 0 24px 70px rgba(0, 0, 0, 0.38);
- padding: 22px;
+ width: min(440px, 100%);
+ background: var(--bg-surface);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ box-shadow: 0 24px 70px rgba(0, 0, 0, 0.38);
+ padding: 22px;
}
.auth-dialog-header {
- display: flex;
- align-items: flex-start;
- justify-content: space-between;
- gap: 16px;
- margin-bottom: 12px;
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 16px;
+ margin-bottom: 12px;
}
.auth-dialog h2 {
- font-size: 22px;
- line-height: 1.2;
- margin-top: 2px;
+ font-size: 22px;
+ line-height: 1.2;
+ margin-top: 2px;
}
.auth-dialog-close {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- width: 32px;
- height: 32px;
- background: var(--bg);
- border: 1px solid var(--border);
- border-radius: 6px;
- color: var(--text-muted);
- cursor: pointer;
- font: inherit;
- line-height: 1;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 32px;
+ height: 32px;
+ background: var(--bg);
+ border: 1px solid var(--border);
+ border-radius: 6px;
+ color: var(--text-muted);
+ cursor: pointer;
+ font: inherit;
+ line-height: 1;
}
.auth-dialog-close:hover {
- color: var(--text);
- background: var(--bg-surface-hover);
+ color: var(--text);
+ background: var(--bg-surface-hover);
}
.auth-dialog-hint {
- color: var(--text-muted);
- font-size: 13px;
+ color: var(--text-muted);
+ font-size: 13px;
}
.auth-dialog-error {
- color: var(--danger);
- font-size: 13px;
- font-weight: 600;
+ color: var(--danger);
+ font-size: 13px;
+ font-weight: 600;
}
.auth-dialog-form {
- display: grid;
- gap: 10px;
- margin-top: 18px;
+ display: grid;
+ gap: 10px;
+ margin-top: 18px;
}
.auth-dialog-input-shell {
- position: relative;
+ position: relative;
}
.auth-dialog-input-icon {
- position: absolute;
- left: 12px;
- top: 50%;
- width: 16px;
- height: 16px;
- color: var(--text-muted);
- pointer-events: none;
- transform: translateY(-50%);
+ position: absolute;
+ left: 12px;
+ top: 50%;
+ width: 16px;
+ height: 16px;
+ color: var(--text-muted);
+ pointer-events: none;
+ transform: translateY(-50%);
}
.auth-dialog-input-shell:focus-within .auth-dialog-input-icon {
- color: var(--accent);
+ color: var(--accent);
}
.auth-dialog-input {
- width: 100%;
- padding: 11px 12px 11px 38px;
- background: var(--bg);
- border: 1px solid var(--border);
- border-radius: var(--radius);
- color: var(--text);
- font-size: 14px;
- font-family: inherit;
- outline: none;
+ width: 100%;
+ padding: 11px 12px 11px 38px;
+ background: var(--bg);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ color: var(--text);
+ font-size: 14px;
+ font-family: inherit;
+ outline: none;
}
.auth-dialog-input:focus {
- border-color: var(--accent);
- box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent);
+ border-color: var(--accent);
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent);
}
.auth-dialog-submit-icon {
- width: 16px;
- height: 16px;
- flex: 0 0 16px;
+ width: 16px;
+ height: 16px;
+ flex: 0 0 16px;
}
.auth-dialog-actions {
- display: flex;
- justify-content: flex-end;
- gap: 8px;
- margin-top: 8px;
+ display: flex;
+ justify-content: flex-end;
+ gap: 8px;
+ margin-top: 8px;
}
/* Theme toggle — compact pill */
.theme-toggle {
- display: inline-flex;
- align-items: center;
- background: var(--bg);
- border: 1px solid var(--border);
- border-radius: 6px;
- padding: 2px;
- margin-bottom: 10px;
+ display: inline-flex;
+ align-items: center;
+ background: var(--bg);
+ border: 1px solid var(--border);
+ border-radius: 6px;
+ padding: 2px;
+ margin-bottom: 10px;
}
.theme-btn {
- display: flex;
- align-items: center;
- justify-content: center;
- width: 28px;
- height: 24px;
- background: transparent;
- border: none;
- border-radius: 4px;
- color: var(--text-muted);
- cursor: pointer;
- transition: all 0.15s;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 28px;
+ height: 24px;
+ background: transparent;
+ border: none;
+ border-radius: 4px;
+ color: var(--text-muted);
+ cursor: pointer;
+ transition: all 0.15s;
}
.theme-btn svg {
- width: 14px;
- height: 14px;
+ width: 14px;
+ height: 14px;
+}
+
+.theme-icon {
+ width: 14px;
+ height: 14px;
+ flex: 0 0 14px;
}
.theme-btn:hover {
- color: var(--text);
+ color: var(--text);
}
.theme-btn.active {
- background: var(--accent);
- color: #fff;
+ background: var(--accent);
+ color: #fff;
}
/* Theme toggle — single button for mobile */
.theme-toggle-mobile {
- display: none;
- align-items: center;
- justify-content: center;
- width: 36px;
- height: 36px;
- background: var(--bg);
- border: 1px solid var(--border);
- border-radius: 6px;
- color: var(--text-muted);
- cursor: pointer;
- transition: all 0.15s;
+ display: none;
+ align-items: center;
+ justify-content: center;
+ width: 36px;
+ height: 36px;
+ background: var(--bg);
+ border: 1px solid var(--border);
+ border-radius: 6px;
+ color: var(--text-muted);
+ cursor: pointer;
+ transition: all 0.15s;
}
.theme-toggle-mobile:hover {
- color: var(--text);
+ color: var(--text);
}
.theme-toggle-mobile svg {
- width: 16px;
- height: 16px;
+ width: 16px;
+ height: 16px;
+}
+
+.theme-toggle-mobile .theme-icon {
+ width: 16px;
+ height: 16px;
+ flex-basis: 16px;
}
/* Sidebar toggle handle */
.sidebar-toggle {
- flex: 0 0 6px;
- position: sticky;
- top: 0;
- width: 6px;
- height: 100vh;
- cursor: w-resize;
- z-index: 11;
- transition: background 0.15s;
+ flex: 0 0 6px;
+ position: sticky;
+ top: 0;
+ width: 6px;
+ height: 100vh;
+ cursor: w-resize;
+ z-index: 11;
+ transition: background 0.15s;
}
.sidebar-toggle:hover {
- background: color-mix(in srgb, var(--accent) 15%, transparent);
+ background: color-mix(in srgb, var(--accent) 15%, transparent);
}
.sidebar-toggle.collapsed {
- cursor: e-resize;
+ cursor: e-resize;
}
/* Collapsed sidebar (desktop) */
.sidebar.sidebar-collapsed {
- flex-basis: 60px;
- width: 60px;
+ flex-basis: 60px;
+ width: 60px;
}
.sidebar.sidebar-collapsed .sidebar-header {
- justify-content: center;
- padding: 16px;
+ justify-content: center;
+ padding: 16px;
}
.sidebar.sidebar-collapsed .sidebar-header h1,
.sidebar.sidebar-collapsed .badge {
- display: none;
+ display: none;
}
.sidebar.sidebar-collapsed .sidebar-nav .nav-item {
- justify-content: center;
- padding: 10px;
+ justify-content: center;
+ padding: 10px;
}
.sidebar.sidebar-collapsed .sidebar-nav .nav-item span {
- display: none;
+ display: none;
}
.sidebar.sidebar-collapsed .sidebar-footer {
- padding: 8px;
+ padding: 8px;
}
.sidebar.sidebar-collapsed .sidebar-footer .api-key-section {
- display: none;
+ display: none;
}
.sidebar.sidebar-collapsed .sidebar-footer .theme-toggle {
- display: none;
+ display: none;
}
.sidebar.sidebar-collapsed .sidebar-footer .theme-toggle-mobile {
- display: flex;
- margin: 0 auto;
+ display: flex;
+ margin: 0 auto;
}
/* Content */
.content {
- flex: 1 1 0;
- min-width: 0;
- width: 100%;
- max-width: 1200px;
- margin: 0 auto;
- padding: 32px;
- transition: width 0.2s;
+ flex: 1 1 0;
+ min-width: 0;
+ width: 100%;
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 32px;
+ transition: width 0.2s;
}
.page-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- margin-bottom: 24px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 24px;
}
.page-header h2 {
- font-size: 22px;
- font-weight: 700;
- letter-spacing: -0.3px;
+ font-size: 22px;
+ font-weight: 700;
+ letter-spacing: -0.3px;
}
/* Date Picker */
.date-picker {
- position: relative;
+ position: relative;
}
.date-picker-trigger {
- display: inline-flex;
- align-items: center;
- gap: 8px;
- padding: 8px 12px;
- background: var(--bg-surface);
- border: 1px solid var(--border);
- border-radius: var(--radius);
- color: var(--text);
- font-size: 13px;
- font-family: inherit;
- cursor: pointer;
- transition: all 0.15s;
- white-space: nowrap;
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ padding: 8px 12px;
+ background: var(--bg-surface);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ color: var(--text);
+ font-size: 13px;
+ font-family: inherit;
+ cursor: pointer;
+ transition: all 0.15s;
+ white-space: nowrap;
}
.date-picker-trigger:hover {
- background: var(--bg-surface-hover);
+ background: var(--bg-surface-hover);
}
.date-picker-trigger svg {
- flex-shrink: 0;
- color: var(--text-muted);
+ flex-shrink: 0;
+ color: var(--text-muted);
}
.date-picker-chevron {
- transition: transform 0.15s;
+ transition: transform 0.15s;
}
.date-picker-chevron.open {
- transform: rotate(180deg);
+ transform: rotate(180deg);
}
.date-picker-dropdown {
- position: absolute;
- top: calc(100% + 6px);
- right: 0;
- z-index: 100;
- display: flex;
- background: var(--bg-surface);
- border: 1px solid var(--border);
- border-radius: var(--radius);
- box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
- overflow: hidden;
+ position: absolute;
+ top: calc(100% + 6px);
+ right: 0;
+ z-index: 100;
+ display: flex;
+ background: var(--bg-surface);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
+ overflow: hidden;
}
.date-picker-presets {
- display: flex;
- flex-direction: column;
- gap: 2px;
- padding: 8px;
- border-right: 1px solid var(--border);
- min-width: 140px;
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+ padding: 8px;
+ border-right: 1px solid var(--border);
+ min-width: 140px;
}
.preset-btn {
- padding: 8px 12px;
- background: transparent;
- border: none;
- border-radius: 6px;
- color: var(--text);
- font-size: 13px;
- font-family: inherit;
- text-align: left;
- cursor: pointer;
- transition: all 0.15s;
- white-space: nowrap;
+ padding: 8px 12px;
+ background: transparent;
+ border: none;
+ border-radius: 6px;
+ color: var(--text);
+ font-size: 13px;
+ font-family: inherit;
+ text-align: left;
+ cursor: pointer;
+ transition: all 0.15s;
+ white-space: nowrap;
}
.preset-btn:hover {
- background: var(--bg-surface-hover);
+ background: var(--bg-surface-hover);
}
.preset-btn.active {
- background: var(--accent);
- color: #fff;
+ background: var(--accent);
+ color: #fff;
}
.date-picker-calendars {
- display: flex;
- flex-wrap: nowrap;
- padding: 12px;
- gap: 16px;
+ display: flex;
+ flex-wrap: nowrap;
+ padding: 12px;
+ gap: 16px;
}
.dp-cursor-hint {
- position: fixed;
- pointer-events: none;
- z-index: 101;
- background: var(--bg-surface);
- color: var(--text);
- border: 1px solid var(--accent);
- font-size: 11px;
- font-weight: 500;
- padding: 3px 8px;
- border-radius: 4px;
- white-space: nowrap;
- transform: translate(12px, -50%);
- display: flex;
- align-items: center;
- gap: 5px;
+ position: fixed;
+ pointer-events: none;
+ z-index: 101;
+ background: var(--bg-surface);
+ color: var(--text);
+ border: 1px solid var(--accent);
+ font-size: 11px;
+ font-weight: 500;
+ padding: 3px 8px;
+ border-radius: 4px;
+ white-space: nowrap;
+ transform: translate(12px, -50%);
+ display: flex;
+ align-items: center;
+ gap: 5px;
}
.dp-cursor-hint svg {
- width: 12px;
- height: 12px;
- color: var(--accent);
- flex-shrink: 0;
+ width: 12px;
+ height: 12px;
+ color: var(--accent);
+ flex-shrink: 0;
}
.dp-calendar {
- width: 224px;
- flex-shrink: 0;
+ width: 224px;
+ flex-shrink: 0;
}
.dp-cal-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- margin-bottom: 8px;
- padding: 0 4px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 8px;
+ padding: 0 4px;
}
.dp-cal-title {
- font-size: 13px;
- font-weight: 600;
+ font-size: 13px;
+ font-weight: 600;
}
.dp-nav-btn {
- display: flex;
- align-items: center;
- justify-content: center;
- width: 28px;
- height: 28px;
- background: transparent;
- border: none;
- border-radius: 6px;
- color: var(--text-muted);
- cursor: pointer;
- transition: all 0.15s;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 28px;
+ height: 28px;
+ background: transparent;
+ border: none;
+ border-radius: 6px;
+ color: var(--text-muted);
+ cursor: pointer;
+ transition: all 0.15s;
}
.dp-nav-btn:hover {
- background: var(--bg-surface-hover);
- color: var(--text);
+ background: var(--bg-surface-hover);
+ color: var(--text);
}
.dp-nav-btn:disabled {
- opacity: 0.3;
- cursor: default;
- pointer-events: none;
+ opacity: 0.3;
+ cursor: default;
+ pointer-events: none;
}
.dp-nav-prev-mobile {
- display: none;
+ display: none;
}
.dp-weekdays {
- display: grid;
- grid-template-columns: repeat(7, 1fr);
- text-align: center;
- margin-bottom: 4px;
+ display: grid;
+ grid-template-columns: repeat(7, 1fr);
+ text-align: center;
+ margin-bottom: 4px;
}
.dp-weekdays span {
- font-size: 11px;
- font-weight: 600;
- color: var(--text-muted);
- padding: 4px 0;
+ font-size: 11px;
+ font-weight: 600;
+ color: var(--text-muted);
+ padding: 4px 0;
}
.dp-days {
- display: grid;
- grid-template-columns: repeat(7, 1fr);
- gap: 1px;
+ display: grid;
+ grid-template-columns: repeat(7, 1fr);
+ gap: 1px;
}
.dp-day {
- display: flex;
- align-items: center;
- justify-content: center;
- width: 32px;
- height: 32px;
- background: transparent;
- border: none;
- border-radius: 6px;
- color: var(--text);
- font-size: 12px;
- font-family: inherit;
- cursor: pointer;
- transition: all 0.1s;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 32px;
+ height: 32px;
+ background: transparent;
+ border: none;
+ border-radius: 6px;
+ color: var(--text);
+ font-size: 12px;
+ font-family: inherit;
+ cursor: pointer;
+ transition: all 0.1s;
}
.dp-day:hover:not(.disabled):not(.other-month) {
- background: var(--bg-surface-hover);
+ background: var(--bg-surface-hover);
}
.dp-day.other-month {
- color: var(--text-muted);
- opacity: 0.3;
- cursor: default;
+ color: var(--text-muted);
+ opacity: 0.3;
+ cursor: default;
}
.dp-day.today {
- color: var(--accent);
- font-weight: 700;
- box-shadow: inset 0 0 0 1.5px var(--accent);
+ color: var(--accent);
+ font-weight: 700;
+ box-shadow: inset 0 0 0 1.5px var(--accent);
}
.dp-day.in-range {
- background: color-mix(in srgb, var(--accent) 15%, transparent);
- border-radius: 0;
+ background: color-mix(in srgb, var(--accent) 15%, transparent);
+ border-radius: 0;
}
.dp-day.range-start {
- background: var(--accent);
- color: #fff;
- border-radius: 6px 0 0 6px;
- font-weight: 600;
+ background: var(--accent);
+ color: #fff;
+ border-radius: 6px 0 0 6px;
+ font-weight: 600;
}
.dp-day.range-start.range-end {
- border-radius: 6px;
+ border-radius: 6px;
}
.dp-day.range-end {
- background: var(--accent);
- color: #fff;
- border-radius: 0 6px 6px 0;
- font-weight: 600;
+ background: var(--accent);
+ color: #fff;
+ border-radius: 0 6px 6px 0;
+ font-weight: 600;
}
.dp-day.range-start.today,
.dp-day.range-end.today {
- box-shadow: none;
+ box-shadow: none;
}
.dp-day.disabled {
- color: var(--text-muted);
- opacity: 0.3;
- cursor: default;
- pointer-events: none;
+ color: var(--text-muted);
+ opacity: 0.3;
+ cursor: default;
+ pointer-events: none;
}
-
/* Page Header Controls (interval + date picker wrapper) */
.page-header-controls {
- display: flex;
- align-items: center;
- gap: 12px;
+ display: flex;
+ align-items: center;
+ gap: 12px;
}
/* Interval Picker */
.interval-picker {
- display: inline-flex;
- align-items: center;
- background: var(--bg);
- border: 1px solid var(--border);
- border-radius: 6px;
- padding: 2px;
+ display: inline-flex;
+ align-items: center;
+ background: var(--bg);
+ border: 1px solid var(--border);
+ border-radius: 6px;
+ padding: 2px;
}
.interval-btn {
- display: flex;
- align-items: center;
- justify-content: center;
- padding: 5px 12px;
- background: transparent;
- border: none;
- border-radius: 4px;
- color: var(--text-muted);
- font-size: 12px;
- font-family: inherit;
- font-weight: 500;
- cursor: pointer;
- transition: all 0.15s;
- white-space: nowrap;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 5px 12px;
+ background: transparent;
+ border: none;
+ border-radius: 4px;
+ color: var(--text-muted);
+ font-size: 12px;
+ font-family: inherit;
+ font-weight: 500;
+ cursor: pointer;
+ transition: all 0.15s;
+ white-space: nowrap;
}
.interval-btn:hover {
- color: var(--text);
+ color: var(--text);
}
.interval-btn.active {
- background: var(--accent);
- color: #fff;
+ background: var(--accent);
+ color: #fff;
}
/* Category Tabs */
.category-tabs {
- display: flex;
- align-items: center;
- gap: 4px;
- margin-bottom: 16px;
- overflow-x: auto;
- -webkit-overflow-scrolling: touch;
- padding-bottom: 2px;
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ margin-bottom: 16px;
+ overflow-x: auto;
+ -webkit-overflow-scrolling: touch;
+ padding-bottom: 2px;
}
.category-tab {
- display: inline-flex;
- align-items: center;
- gap: 6px;
- padding: 6px 14px;
- background: var(--bg-surface);
- border: 1px solid var(--border);
- border-radius: 6px;
- color: var(--text-muted);
- font-size: 13px;
- font-family: inherit;
- font-weight: 500;
- cursor: pointer;
- transition: all 0.15s;
- white-space: nowrap;
- flex-shrink: 0;
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ padding: 6px 14px;
+ background: var(--bg-surface);
+ border: 1px solid var(--border);
+ border-radius: 6px;
+ color: var(--text-muted);
+ font-size: 13px;
+ font-family: inherit;
+ font-weight: 500;
+ cursor: pointer;
+ transition: all 0.15s;
+ white-space: nowrap;
+ flex-shrink: 0;
}
.category-tab:hover {
- color: var(--text);
- background: var(--bg-surface-hover);
+ color: var(--text);
+ background: var(--bg-surface-hover);
}
.category-tab.active {
- background: var(--accent);
- color: #fff;
- border-color: var(--accent);
+ background: var(--accent);
+ color: #fff;
+ border-color: var(--accent);
}
.category-tab .tab-count {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- min-width: 20px;
- height: 18px;
- padding: 0 5px;
- background: rgba(255, 255, 255, 0.15);
- border-radius: 9px;
- font-size: 11px;
- font-weight: 600;
- line-height: 1;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 20px;
+ height: 18px;
+ padding: 0 5px;
+ background: rgba(255, 255, 255, 0.15);
+ border-radius: 9px;
+ font-size: 11px;
+ font-weight: 600;
+ line-height: 1;
}
.category-tab:not(.active) .tab-count {
- background: var(--bg);
+ background: var(--bg);
}
.model-count {
- color: var(--text-muted);
- font-size: 14px;
+ color: var(--text-muted);
+ font-size: 14px;
}
.provider-status-flag {
- grid-column: span 2;
+ grid-column: span 2;
}
.provider-status-flag.is-healthy {
- border-color: color-mix(in srgb, var(--success) 45%, var(--border));
- background: color-mix(in srgb, var(--success) 10%, var(--bg-surface));
+ border-color: color-mix(in srgb, var(--success) 45%, var(--border));
+ background: color-mix(in srgb, var(--success) 10%, var(--bg-surface));
}
.provider-status-flag.is-degraded {
- border-color: color-mix(in srgb, var(--warning) 48%, var(--border));
- background: color-mix(in srgb, var(--warning) 26%, var(--bg-surface));
+ border-color: color-mix(in srgb, var(--warning) 48%, var(--border));
+ background: color-mix(in srgb, var(--warning) 26%, var(--bg-surface));
}
.provider-status-flag.is-unhealthy {
- border-color: color-mix(in srgb, var(--danger) 45%, var(--border));
- background: color-mix(in srgb, var(--danger) 10%, var(--bg-surface));
+ border-color: color-mix(in srgb, var(--danger) 45%, var(--border));
+ background: color-mix(in srgb, var(--danger) 10%, var(--bg-surface));
}
.provider-status-value {
- margin-bottom: 8px;
+ margin-bottom: 8px;
}
.provider-status-card-link {
- margin: 0;
- padding: 0;
- background: transparent;
- border: 0;
- color: var(--accent-strong, var(--accent));
- font: inherit;
- font-size: 13px;
- font-weight: 600;
- text-align: left;
- cursor: pointer;
+ margin: 0;
+ padding: 0;
+ background: transparent;
+ border: 0;
+ color: var(--accent-strong, var(--accent));
+ font: inherit;
+ font-size: 13px;
+ font-weight: 600;
+ text-align: left;
+ cursor: pointer;
}
.provider-status-card-link:hover {
- color: var(--text);
- text-decoration: underline;
+ color: var(--text);
+ text-decoration: underline;
}
.provider-status-card-link:focus-visible {
- outline: 2px solid color-mix(in srgb, var(--accent) 32%, transparent);
- outline-offset: 3px;
- border-radius: 4px;
+ outline: 2px solid color-mix(in srgb, var(--accent) 32%, transparent);
+ outline-offset: 3px;
+ border-radius: 4px;
}
.provider-status-card-note {
- display: block;
- font-size: 13px;
- color: var(--text-muted);
+ display: block;
+ font-size: 13px;
+ color: var(--text-muted);
}
.provider-status-section {
- margin-top: 28px;
- scroll-margin-top: 24px;
+ margin-top: 28px;
+ scroll-margin-top: 24px;
}
.provider-status-section-header {
- display: flex;
- align-items: flex-start;
- justify-content: space-between;
- gap: 12px;
- margin-bottom: 16px;
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 12px;
+ margin-bottom: 16px;
}
.provider-status-toggle {
- display: inline-flex;
- align-items: center;
- gap: 10px;
- padding: 8px 12px;
- background: var(--bg-surface);
- border: 1px solid var(--border);
- border-radius: 999px;
- color: var(--text);
- font-size: 12px;
- font-family: inherit;
- font-weight: 600;
- cursor: pointer;
- transition: background-color 0.18s ease, border-color 0.18s ease, color 0.18s ease;
+ display: inline-flex;
+ align-items: center;
+ gap: 10px;
+ padding: 8px 12px;
+ background: var(--bg-surface);
+ border: 1px solid var(--border);
+ border-radius: 999px;
+ color: var(--text);
+ font-size: 12px;
+ font-family: inherit;
+ font-weight: 600;
+ cursor: pointer;
+ transition:
+ background-color 0.18s ease,
+ border-color 0.18s ease,
+ color 0.18s ease;
}
.provider-status-toggle:hover {
- background: var(--bg-surface-hover);
+ background: var(--bg-surface-hover);
}
.provider-status-toggle:focus-visible {
- outline: 2px solid color-mix(in srgb, var(--accent) 28%, transparent);
- outline-offset: 2px;
+ outline: 2px solid color-mix(in srgb, var(--accent) 28%, transparent);
+ outline-offset: 2px;
}
.provider-status-toggle-copy {
- white-space: nowrap;
+ white-space: nowrap;
}
.provider-status-toggle-track {
- position: relative;
- width: 34px;
- height: 20px;
- border-radius: 999px;
- background: color-mix(in srgb, var(--text-muted) 35%, var(--border));
- transition: background-color 0.2s ease;
- flex-shrink: 0;
+ position: relative;
+ width: 34px;
+ height: 20px;
+ border-radius: 999px;
+ background: color-mix(in srgb, var(--text-muted) 35%, var(--border));
+ transition: background-color 0.2s ease;
+ flex-shrink: 0;
}
.provider-status-toggle-track.is-active {
- background: color-mix(in srgb, var(--accent) 82%, var(--bg-surface));
+ background: color-mix(in srgb, var(--accent) 82%, var(--bg-surface));
}
.provider-status-toggle-thumb {
- position: absolute;
- top: 2px;
- left: 2px;
- width: 16px;
- height: 16px;
- border-radius: 50%;
- background: #fff;
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.24);
- transition: transform 0.2s ease;
+ position: absolute;
+ top: 2px;
+ left: 2px;
+ width: 16px;
+ height: 16px;
+ border-radius: 50%;
+ background: #fff;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.24);
+ transition: transform 0.2s ease;
}
.provider-status-toggle-track.is-active .provider-status-toggle-thumb {
- transform: translateX(14px);
+ transform: translateX(14px);
}
.provider-status-grid {
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
- gap: 16px;
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
+ gap: 16px;
}
.provider-status-card {
- display: flex;
- flex-direction: column;
- gap: 14px;
- padding: 18px;
- background: var(--bg-surface);
- border: 1px solid var(--border);
- border-radius: var(--radius);
+ display: flex;
+ flex-direction: column;
+ gap: 14px;
+ padding: 18px;
+ background: var(--bg-surface);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
}
.provider-status-card-head {
- display: flex;
- align-items: flex-start;
- justify-content: space-between;
- gap: 12px;
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 12px;
}
.provider-status-name {
- display: flex;
- flex-wrap: wrap;
- align-items: baseline;
- gap: 6px;
- font-size: 16px;
- font-weight: 700;
- letter-spacing: -0.02em;
+ display: flex;
+ flex-wrap: wrap;
+ align-items: baseline;
+ gap: 6px;
+ font-size: 16px;
+ font-weight: 700;
+ letter-spacing: -0.02em;
}
.provider-status-name-type {
- font-size: 11px;
- font-weight: 700;
- letter-spacing: 0.08em;
- text-transform: uppercase;
- color: var(--text-muted);
+ font-size: 11px;
+ font-weight: 700;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--text-muted);
}
.provider-status-pill {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- min-height: 28px;
- padding: 0 10px;
- border-radius: 999px;
- border: 1px solid var(--border);
- font-size: 12px;
- font-weight: 700;
- white-space: nowrap;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-height: 28px;
+ padding: 0 10px;
+ border-radius: 999px;
+ border: 1px solid var(--border);
+ font-size: 12px;
+ font-weight: 700;
+ white-space: nowrap;
}
.provider-status-pill.is-healthy {
- color: var(--success);
- border-color: color-mix(in srgb, var(--success) 45%, var(--border));
- background: color-mix(in srgb, var(--success) 10%, transparent);
+ color: var(--success);
+ border-color: color-mix(in srgb, var(--success) 45%, var(--border));
+ background: color-mix(in srgb, var(--success) 10%, transparent);
}
.provider-status-pill.is-degraded {
- color: var(--warning);
- border-color: color-mix(in srgb, var(--warning) 48%, var(--border));
- background: color-mix(in srgb, var(--warning) 26%, var(--bg-surface));
+ color: var(--warning);
+ border-color: color-mix(in srgb, var(--warning) 48%, var(--border));
+ background: color-mix(in srgb, var(--warning) 26%, var(--bg-surface));
}
.provider-status-pill.is-unhealthy {
- color: var(--danger);
- border-color: color-mix(in srgb, var(--danger) 45%, var(--border));
- background: color-mix(in srgb, var(--danger) 10%, transparent);
+ color: var(--danger);
+ border-color: color-mix(in srgb, var(--danger) 45%, var(--border));
+ background: color-mix(in srgb, var(--danger) 10%, transparent);
}
.provider-status-details {
- display: grid;
- grid-template-rows: 0fr;
- opacity: 0;
- transition: grid-template-rows 0.28s ease, opacity 0.22s ease;
+ display: grid;
+ grid-template-rows: 0fr;
+ opacity: 0;
+ transition: grid-template-rows 0.28s ease, opacity 0.22s ease;
}
.provider-status-details.is-expanded {
- grid-template-rows: 1fr;
- opacity: 1;
+ grid-template-rows: 1fr;
+ opacity: 1;
}
.provider-status-details.is-collapsed {
- pointer-events: none;
+ pointer-events: none;
}
.provider-status-details-inner {
- min-height: 0;
- overflow: hidden;
- display: flex;
- flex-direction: column;
- gap: 14px;
+ min-height: 0;
+ overflow: hidden;
+ display: flex;
+ flex-direction: column;
+ gap: 14px;
}
.provider-status-reason {
- font-size: 13px;
- color: var(--text-muted);
+ font-size: 13px;
+ color: var(--text-muted);
}
.provider-status-error {
- font-size: 12px;
- color: var(--danger);
- overflow-wrap: break-word;
+ font-size: 12px;
+ color: var(--danger);
+ overflow-wrap: break-word;
}
.provider-status-meta {
- display: grid;
- grid-template-columns: repeat(2, minmax(0, 1fr));
- gap: 12px;
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 12px;
}
.provider-status-meta-item {
- display: flex;
- flex-direction: column;
- gap: 4px;
- padding: 10px 12px;
- background: var(--bg);
- border: 1px solid var(--border);
- border-radius: 6px;
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ padding: 10px 12px;
+ background: var(--bg);
+ border: 1px solid var(--border);
+ border-radius: 6px;
}
.provider-status-meta-label,
.provider-status-config-label {
- font-size: 11px;
- font-weight: 700;
- letter-spacing: 0.08em;
- text-transform: uppercase;
- color: var(--text-muted);
+ font-size: 11px;
+ font-weight: 700;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--text-muted);
}
.provider-status-meta-value {
- font-size: 14px;
- color: var(--text);
+ font-size: 14px;
+ color: var(--text);
}
.provider-status-config {
- display: flex;
- flex-direction: column;
- gap: 10px;
- padding-top: 12px;
- border-top: 1px solid var(--border);
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ padding-top: 12px;
+ border-top: 1px solid var(--border);
}
.provider-status-config-row {
- display: flex;
- flex-direction: column;
- gap: 4px;
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
}
.provider-status-config-value {
- display: block;
- font-size: 13px;
- color: var(--text);
- overflow-wrap: break-word;
+ display: block;
+ font-size: 13px;
+ color: var(--text);
+ overflow-wrap: break-word;
}
/* Summary Cards */
.cards {
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
- gap: 16px;
- margin-bottom: 28px;
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+ gap: 16px;
+ margin-bottom: 28px;
}
.card {
- background: var(--bg-surface);
- border: 1px solid var(--border);
- border-radius: var(--radius);
- padding: 20px;
+ background: var(--bg-surface);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ padding: 20px;
}
.card-label {
- font-size: 12px;
- font-weight: 600;
- text-transform: uppercase;
- letter-spacing: 0.5px;
- color: var(--text-muted);
- margin-bottom: 8px;
+ font-size: 12px;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ color: var(--text-muted);
+ margin-bottom: 8px;
}
.card-value {
- font-size: 28px;
- font-weight: 700;
- letter-spacing: -0.5px;
+ font-size: 28px;
+ font-weight: 700;
+ letter-spacing: -0.5px;
}
/* Chart */
.chart-container {
- background: var(--bg-surface);
- border: 1px solid var(--border);
- border-radius: var(--radius);
- padding: 24px;
+ background: var(--bg-surface);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ padding: 24px;
}
.chart-container h3 {
- font-size: 16px;
- font-weight: 600;
- margin-bottom: 16px;
+ font-size: 16px;
+ font-weight: 600;
+ margin-bottom: 16px;
}
.chart-wrapper {
- position: relative;
- height: 210px;
+ position: relative;
+ height: 210px;
}
/* Alert */
.alert {
- padding: 12px 16px;
- border-radius: var(--radius);
- margin-bottom: 20px;
- font-size: 14px;
+ padding: 12px 16px;
+ border-radius: var(--radius);
+ margin-bottom: 20px;
+ font-size: 14px;
}
.alert-warning {
- background: rgba(245, 158, 11, 0.1);
- border: 1px solid rgba(245, 158, 11, 0.3);
- color: var(--warning);
+ background: rgba(245, 158, 11, 0.1);
+ border: 1px solid rgba(245, 158, 11, 0.3);
+ color: var(--warning);
}
.alert-success {
- background: rgba(52, 211, 153, 0.12);
- border: 1px solid rgba(52, 211, 153, 0.28);
- color: var(--success);
+ background: rgba(52, 211, 153, 0.12);
+ border: 1px solid rgba(52, 211, 153, 0.28);
+ color: var(--success);
}
.alert-inline-actions {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 12px;
- flex-wrap: wrap;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+ flex-wrap: wrap;
}
.auth-banner {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 12px;
- flex-wrap: wrap;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+ flex-wrap: wrap;
}
/* Table */
.table-toolbar {
- display: flex;
- gap: 12px;
- margin-bottom: 16px;
- align-items: center;
+ display: flex;
+ gap: 12px;
+ margin-bottom: 16px;
+ align-items: center;
}
.table-toolbar-main {
- flex: 1;
- min-width: 0;
+ flex: 1;
+ min-width: 0;
}
.table-toolbar-actions {
- margin-left: auto;
- display: flex;
- justify-content: flex-end;
+ margin-left: auto;
+ display: flex;
+ justify-content: flex-end;
}
.filter-input {
- flex: 1;
- max-width: 420px;
- padding: 8px 12px;
- background: var(--bg-surface);
- border: 1px solid var(--border);
- border-radius: var(--radius);
- color: var(--text);
- font-size: 13px;
- outline: none;
+ flex: 1;
+ max-width: 420px;
+ padding: 8px 12px;
+ background: var(--bg-surface);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ color: var(--text);
+ font-size: 13px;
+ outline: none;
}
.filter-input:focus {
- border-color: var(--accent);
+ border-color: var(--accent);
}
.models-filter-input {
- max-width: 840px;
+ max-width: 840px;
}
.table-wrapper {
- background: var(--bg-surface);
- border: 1px solid var(--border);
- border-radius: var(--radius);
- overflow: hidden;
+ background: var(--bg-surface);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ overflow: hidden;
}
.data-table {
- width: 100%;
- border-collapse: collapse;
- font-size: 14px;
+ width: 100%;
+ border-collapse: collapse;
+ font-size: 14px;
}
.data-table th {
- text-align: left;
- padding: 12px 16px;
- font-weight: 600;
- font-size: 12px;
- text-transform: uppercase;
- letter-spacing: 0.5px;
- color: var(--text-muted);
- background: var(--bg);
- border-bottom: 1px solid var(--border);
+ text-align: left;
+ padding: 12px 16px;
+ font-weight: 600;
+ font-size: 12px;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ color: var(--text-muted);
+ background: var(--bg);
+ border-bottom: 1px solid var(--border);
}
.data-table th.model-actions-header {
- text-align: right;
- min-width: 156px;
- width: 1%;
- white-space: nowrap;
+ text-align: right;
+ min-width: 156px;
+ width: 1%;
+ white-space: nowrap;
}
.data-table td {
- padding: 10px 16px;
- border-bottom: 1px solid var(--border);
+ padding: 10px 16px;
+ border-bottom: 1px solid var(--border);
}
.data-table tr:last-child td {
- border-bottom: none;
+ border-bottom: none;
}
.data-table tr:hover td {
- background: var(--bg-surface-hover);
+ background: var(--bg-surface-hover);
}
.mono {
- font-family: 'SF Mono', Menlo, Consolas, monospace;
+ font-family: 'SF Mono', Menlo, Consolas, monospace;
}
.font-size-md {
- font-size: 13px;
+ font-size: 13px;
}
.col-price {
- text-align: right;
+ text-align: right;
}
td.col-price {
- font-family: 'SF Mono', Menlo, Consolas, monospace;
- font-size: 13px;
- color: var(--text-muted);
+ font-family: "SF Mono", Menlo, Consolas, monospace;
+ font-size: 13px;
+ color: var(--text-muted);
}
.provider-badge {
- display: inline-block;
- padding: 2px 10px;
- background: var(--bg);
- border: 1px solid var(--border);
- border-radius: 12px;
- font-size: 12px;
- font-weight: 500;
+ display: inline-block;
+ padding: 2px 10px;
+ background: var(--bg);
+ border: 1px solid var(--border);
+ border-radius: 12px;
+ font-size: 12px;
+ font-weight: 500;
}
.audit-alias-badge {
- background: color-mix(in srgb, var(--accent) 14%, var(--bg));
- border-color: color-mix(in srgb, var(--accent) 28%, var(--border));
- color: var(--accent-strong, var(--accent));
+ background: color-mix(in srgb, var(--accent) 14%, var(--bg));
+ border-color: color-mix(in srgb, var(--accent) 28%, var(--border));
+ color: var(--accent-strong, var(--accent));
}
.empty-state {
- text-align: center;
- color: var(--text-muted);
- padding: 48px 0;
- font-size: 14px;
+ text-align: center;
+ color: var(--text-muted);
+ padding: 48px 0;
+ font-size: 14px;
}
.loading-state {
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 10px;
- min-height: 64px;
- color: var(--text-muted);
- font-size: 14px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 10px;
+ min-height: 64px;
+ color: var(--text-muted);
+ font-size: 14px;
}
.loading-spinner {
- width: 16px;
- height: 16px;
- border: 2px solid var(--border);
- border-top-color: var(--accent);
- border-radius: 50%;
- animation: loading-spin 0.8s linear infinite;
+ width: 16px;
+ height: 16px;
+ border: 2px solid var(--border);
+ border-top-color: var(--accent);
+ border-radius: 50%;
+ animation: loading-spin 0.8s linear infinite;
}
@keyframes loading-spin {
- to {
- transform: rotate(360deg);
- }
+ to {
+ transform: rotate(360deg);
+ }
}
.table-action-btn {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- gap: 6px;
- padding: 6px 12px;
- background: var(--bg);
- border: 1px solid var(--border);
- border-radius: 6px;
- color: var(--text);
- font-size: 12px;
- font-family: inherit;
- font-weight: 500;
- cursor: pointer;
- transition: all 0.15s;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 6px;
+ padding: 6px 12px;
+ background: var(--bg);
+ border: 1px solid var(--border);
+ border-radius: 6px;
+ color: var(--text);
+ font-size: 12px;
+ font-family: inherit;
+ font-weight: 500;
+ cursor: pointer;
+ transition: all 0.15s;
}
.table-action-btn:hover:not(:disabled) {
- background: var(--bg-surface-hover);
+ background: var(--bg-surface-hover);
}
.table-action-btn:disabled {
- opacity: 0.45;
- cursor: default;
+ opacity: 0.45;
+ cursor: default;
}
.table-action-btn-danger {
- color: var(--danger);
- border-color: color-mix(in srgb, var(--danger) 50%, var(--border));
+ color: var(--danger);
+ border-color: color-mix(in srgb, var(--danger) 50%, var(--border));
}
.table-action-btn-active {
- color: var(--accent-strong, var(--accent));
- background: color-mix(in srgb, var(--accent) 12%, var(--bg));
- border-color: color-mix(in srgb, var(--accent) 38%, var(--border));
+ color: var(--accent-strong, var(--accent));
+ background: color-mix(in srgb, var(--accent) 12%, var(--bg));
+ border-color: color-mix(in srgb, var(--accent) 38%, var(--border));
}
.table-action-btn-active:hover:not(:disabled) {
- background: color-mix(in srgb, var(--accent) 18%, var(--bg-surface-hover));
+ background: color-mix(in srgb, var(--accent) 18%, var(--bg-surface-hover));
}
.table-icon-btn {
- width: 32px;
- min-width: 32px;
- height: 32px;
- padding: 0;
- gap: 0;
- border-radius: 999px;
+ width: 32px;
+ min-width: 32px;
+ height: 32px;
+ padding: 0;
+ gap: 0;
+ border-radius: 999px;
}
.table-icon-btn.table-action-btn-active {
- position: relative;
+ position: relative;
}
.table-icon-btn.table-action-btn-active::after {
- content: "";
- position: absolute;
- top: 4px;
- right: 4px;
- width: 6px;
- height: 6px;
- border-radius: 999px;
- background: var(--accent);
- box-shadow: 0 0 0 2px var(--bg-surface);
+ content: "";
+ position: absolute;
+ top: 4px;
+ right: 4px;
+ width: 6px;
+ height: 6px;
+ border-radius: 999px;
+ background: var(--accent);
+ box-shadow: 0 0 0 2px var(--bg-surface);
}
.table-icon-svg {
- width: 14px;
- height: 14px;
- flex-shrink: 0;
+ width: 14px;
+ height: 14px;
+ flex-shrink: 0;
}
.model-alias-editor {
- background: var(--bg-surface);
- border: 1px solid var(--border);
- border-radius: var(--radius);
- padding: 24px;
- margin-bottom: 16px;
+ background: var(--bg-surface);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ padding: 24px;
+ margin-bottom: 16px;
}
.model-name-cell {
- display: flex;
- flex-direction: column;
- gap: 8px;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
}
.model-name-primary {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- align-items: center;
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ align-items: center;
}
.model-name-secondary {
- font-size: 12px;
- color: var(--text-muted);
+ font-size: 12px;
+ color: var(--text-muted);
}
.provider-group-row td {
- background: color-mix(in srgb, var(--accent) 6%, var(--bg));
- padding-top: 12px;
- padding-bottom: 12px;
+ background: color-mix(in srgb, var(--accent) 6%, var(--bg));
+ padding-top: 12px;
+ padding-bottom: 12px;
}
.provider-group-row:hover td {
- background: color-mix(in srgb, var(--accent) 8%, var(--bg));
+ background: color-mix(in srgb, var(--accent) 8%, var(--bg));
}
.provider-group-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 16px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 16px;
}
.provider-group-meta {
- min-width: 0;
- display: flex;
- flex-direction: column;
- gap: 4px;
+ min-width: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
}
.provider-group-title {
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- gap: 8px;
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 8px;
}
.provider-group-type,
.provider-group-count,
.provider-group-summary {
- color: var(--text-muted);
+ color: var(--text-muted);
}
.provider-group-type,
.provider-group-count {
- font-size: 12px;
+ font-size: 12px;
}
.provider-group-summary {
- font-size: 12px;
+ font-size: 12px;
}
.alias-kind-badge {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- gap: 4px;
- min-height: 24px;
- padding: 0 10px;
- border-radius: 999px;
- border: 1px solid var(--border);
- background: var(--bg);
- color: var(--accent);
- font-size: 11px;
- font-weight: 600;
- letter-spacing: 0.2px;
- text-transform: uppercase;
- border-color: color-mix(in srgb, var(--accent) 55%, var(--border));
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 4px;
+ min-height: 24px;
+ padding: 0 10px;
+ border-radius: 999px;
+ border: 1px solid var(--border);
+ background: var(--bg);
+ color: var(--accent);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.2px;
+ text-transform: uppercase;
+ border-color: color-mix(in srgb, var(--accent) 55%, var(--border));
}
.inline-link {
- padding: 0;
- border: none;
- background: transparent;
- color: var(--accent);
- cursor: pointer;
- text-decoration: underline;
- text-underline-offset: 2px;
+ padding: 0;
+ border: none;
+ background: transparent;
+ color: var(--accent);
+ cursor: pointer;
+ text-decoration: underline;
+ text-underline-offset: 2px;
}
.alias-form h3 {
- font-size: 20px;
- font-weight: 700;
+ font-size: 20px;
+ font-weight: 700;
}
.alias-form-kicker,
.alias-form-hint {
- color: var(--text-muted);
- font-size: 13px;
+ color: var(--text-muted);
+ font-size: 13px;
}
.alias-actions-cell {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- align-items: center;
- justify-content: flex-end;
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ align-items: center;
+ justify-content: flex-end;
}
.alias-toggle {
- display: inline-flex;
- align-items: center;
- gap: 8px;
- padding: 6px 10px;
- border-radius: 999px;
- border: 1px solid var(--border);
- background: var(--bg);
- color: var(--text);
- font-size: 12px;
- font-family: inherit;
- cursor: pointer;
- transition: all 0.15s;
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ padding: 6px 10px;
+ border-radius: 999px;
+ border: 1px solid var(--border);
+ background: var(--bg);
+ color: var(--text);
+ font-size: 12px;
+ font-family: inherit;
+ cursor: pointer;
+ transition: all 0.15s;
}
.alias-toggle:hover:not(:disabled) {
- background: var(--bg-surface-hover);
+ background: var(--bg-surface-hover);
}
.alias-toggle:disabled {
- opacity: 0.45;
- cursor: default;
+ opacity: 0.45;
+ cursor: default;
}
.alias-toggle-track {
- position: relative;
- width: 34px;
- height: 20px;
- border-radius: 999px;
- background: color-mix(in srgb, var(--border) 80%, var(--bg));
- transition: background 0.15s;
+ position: relative;
+ width: 34px;
+ height: 20px;
+ border-radius: 999px;
+ background: color-mix(in srgb, var(--border) 80%, var(--bg));
+ transition: background 0.15s;
}
.alias-toggle-thumb {
- position: absolute;
- top: 2px;
- left: 2px;
- width: 16px;
- height: 16px;
- border-radius: 50%;
- background: var(--text-muted);
- transition: transform 0.15s, background 0.15s;
+ position: absolute;
+ top: 2px;
+ left: 2px;
+ width: 16px;
+ height: 16px;
+ border-radius: 50%;
+ background: var(--text-muted);
+ transition:
+ transform 0.15s,
+ background 0.15s;
}
.alias-toggle.enabled {
- border-color: color-mix(in srgb, var(--success) 50%, var(--border));
+ border-color: color-mix(in srgb, var(--success) 50%, var(--border));
}
.alias-toggle.enabled .alias-toggle-track {
- background: color-mix(in srgb, var(--success) 55%, var(--bg));
+ background: color-mix(in srgb, var(--success) 55%, var(--bg));
}
.alias-toggle.enabled .alias-toggle-thumb {
- transform: translateX(14px);
- background: #fff;
+ transform: translateX(14px);
+ background: #fff;
}
.aliases-editor-header {
- display: flex;
- align-items: flex-start;
- justify-content: space-between;
- gap: 12px;
- margin-bottom: 20px;
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 12px;
+ margin-bottom: 20px;
}
.alias-close-btn {
- width: 36px;
- min-width: 36px;
- height: 36px;
- padding: 0;
- border-radius: 999px;
- font-size: 14px;
- font-weight: 700;
+ width: 36px;
+ min-width: 36px;
+ height: 36px;
+ padding: 0;
+ border-radius: 999px;
+ font-size: 14px;
+ font-weight: 700;
}
.alias-form {
- display: flex;
- flex-direction: column;
- gap: 16px;
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
}
.auth-key-editor {
- background: color-mix(in srgb, var(--bg-surface) 82%, var(--bg) 18%);
+ background: color-mix(in srgb, var(--bg-surface) 82%, var(--bg) 18%);
}
.auth-key-editor .filter-input {
- background: var(--bg-surface);
+ background: var(--bg-surface);
}
.auth-key-editor .alias-form-textarea {
- background: var(--bg-surface);
+ background: var(--bg-surface);
}
.auth-key-form-fields > .alias-form-field {
- margin-bottom: 4px;
+ margin-bottom: 4px;
}
.auth-key-inline-help {
- margin-top: -2px;
- margin-bottom: 8px;
+ margin-top: -2px;
+ margin-bottom: 8px;
}
.auth-key-inline-help .inline-help-title-row h3 {
- font-size: 13px;
- font-weight: 600;
+ font-size: 13px;
+ font-weight: 600;
}
.auth-key-inline-help .inline-help-copy {
- font-size: 13px;
+ font-size: 13px;
}
.alias-form-grid {
- display: grid;
- grid-template-columns: repeat(2, minmax(0, 1fr));
- gap: 12px;
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 12px;
}
.alias-form-field {
- display: flex;
- flex-direction: column;
- gap: 8px;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
}
.alias-form-field > span {
- font-size: 12px;
- font-weight: 600;
- text-transform: uppercase;
- letter-spacing: 0.5px;
- color: var(--text-muted);
+ font-size: 12px;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ color: var(--text-muted);
}
.alias-form-field-fieldset {
- border: 0;
- margin: 0;
- min-inline-size: 0;
- padding: 0;
+ border: 0;
+ margin: 0;
+ min-inline-size: 0;
+ padding: 0;
}
.alias-form-field-legend {
- color: var(--text-muted);
- font-size: 12px;
- font-weight: 600;
- letter-spacing: 0.5px;
- padding: 0;
- text-transform: uppercase;
+ color: var(--text-muted);
+ font-size: 12px;
+ font-weight: 600;
+ letter-spacing: 0.5px;
+ padding: 0;
+ text-transform: uppercase;
}
.alias-form-textarea {
- width: 100%;
- min-height: 110px;
- resize: vertical;
- padding: 10px 12px;
- background: var(--bg-surface);
- border: 1px solid var(--border);
- border-radius: var(--radius);
- color: var(--text);
- font-size: 13px;
- font-family: inherit;
- outline: none;
+ width: 100%;
+ min-height: 110px;
+ resize: vertical;
+ padding: 10px 12px;
+ background: var(--bg-surface);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ color: var(--text);
+ font-size: 13px;
+ font-family: inherit;
+ outline: none;
}
.alias-form-textarea:focus {
- border-color: var(--accent);
+ border-color: var(--accent);
}
.alias-checkbox {
- display: inline-flex;
- align-items: center;
- gap: 10px;
- font-size: 14px;
+ display: inline-flex;
+ align-items: center;
+ gap: 10px;
+ font-size: 14px;
}
.alias-checkbox input {
- width: 16px;
- height: 16px;
+ width: 16px;
+ height: 16px;
}
.alias-form-actions {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- justify-content: flex-end;
- margin-top: 16px;
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ justify-content: flex-end;
+ margin-top: 16px;
}
.model-row-actions {
- text-align: right;
- width: 170px;
+ text-align: right;
+ width: 170px;
}
.model-row-actions-empty {
- color: var(--text-muted);
+ color: var(--text-muted);
}
.model-access-state-badge {
- display: inline-flex;
- align-items: center;
- padding: 5px 10px;
- border-radius: 999px;
- border: 1px solid var(--border);
- background: var(--bg);
- color: var(--text);
- font-size: 12px;
- line-height: 1;
- white-space: nowrap;
+ display: inline-flex;
+ align-items: center;
+ padding: 5px 10px;
+ border-radius: 999px;
+ border: 1px solid var(--border);
+ background: var(--bg);
+ color: var(--text);
+ font-size: 12px;
+ line-height: 1;
+ white-space: nowrap;
}
.model-access-state-badge:empty {
- display: none;
+ display: none;
}
.model-access-state-badge.is-enabled {
- border-color: color-mix(in srgb, var(--success) 40%, var(--border));
- color: color-mix(in srgb, var(--success) 70%, var(--text));
+ border-color: color-mix(in srgb, var(--success) 40%, var(--border));
+ color: color-mix(in srgb, var(--success) 70%, var(--text));
}
.model-access-state-badge.is-restricted {
- border-color: color-mix(in srgb, var(--accent) 40%, var(--border));
- color: color-mix(in srgb, var(--accent) 70%, var(--text));
+ border-color: color-mix(in srgb, var(--accent) 40%, var(--border));
+ color: color-mix(in srgb, var(--accent) 70%, var(--text));
}
.model-access-state-badge.is-disabled {
- border-color: color-mix(in srgb, var(--danger) 40%, var(--border));
- color: color-mix(in srgb, var(--danger) 70%, var(--text));
+ border-color: color-mix(in srgb, var(--danger) 40%, var(--border));
+ color: color-mix(in srgb, var(--danger) 70%, var(--text));
}
.data-table tr.alias-row.is-valid td {
- background: var(--alias-row-valid-bg);
+ background: var(--alias-row-valid-bg);
}
.data-table tr.alias-row.is-valid:hover td {
- background: var(--alias-row-valid-bg-hover);
+ background: var(--alias-row-valid-bg-hover);
}
.data-table tr.alias-row:not(.is-valid) td {
- background: color-mix(in srgb, var(--accent) 10%, var(--bg-surface));
+ background: color-mix(in srgb, var(--accent) 10%, var(--bg-surface));
}
.data-table tr.alias-row:not(.is-valid):hover td {
- background: color-mix(in srgb, var(--accent) 16%, var(--bg-surface-hover));
+ background: color-mix(in srgb, var(--accent) 16%, var(--bg-surface-hover));
}
.data-table tr.alias-row.is-disabled td {
- background: var(--bg-surface);
- opacity: 0.58;
+ background: var(--bg-surface);
+ opacity: 0.58;
}
.data-table tr.alias-row.is-disabled:hover td {
- background: var(--bg-surface-hover);
- opacity: 0.72;
+ background: var(--bg-surface-hover);
+ opacity: 0.72;
}
.data-table tr.masked-model-row td {
- opacity: 0.58;
+ opacity: 0.58;
}
.data-table tr.masked-model-row:hover td {
- opacity: 0.72;
+ opacity: 0.72;
}
.data-table tr.model-access-disabled-row td {
- background: color-mix(in srgb, var(--danger) 6%, var(--bg-surface));
+ background: color-mix(in srgb, var(--danger) 6%, var(--bg-surface));
}
.data-table tr.model-access-disabled-row:hover td {
- background: color-mix(in srgb, var(--danger) 10%, var(--bg-surface-hover));
+ background: color-mix(in srgb, var(--danger) 10%, var(--bg-surface-hover));
}
/* Workflows */
.workflow-page-note {
- margin-top: 6px;
- color: var(--text-muted);
- font-size: 14px;
+ margin-top: 6px;
+ color: var(--text-muted);
+ font-size: 14px;
}
.workflows-layout {
- display: flex;
- flex-direction: column;
- gap: 16px;
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
}
.workflows-layout.is-editor-open {
- align-items: stretch;
+ align-items: stretch;
}
.workflows-list {
- min-width: 0;
+ min-width: 0;
}
.workflow-card-grid {
- display: grid;
- grid-template-columns: 1fr;
- gap: 16px;
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: 16px;
}
.workflow-card {
- display: flex;
- flex-direction: column;
- gap: 16px;
- background: var(--bg-surface);
- border: 1px solid var(--border);
- border-radius: var(--radius);
- padding: 20px;
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ background: var(--bg-surface);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ padding: 20px;
}
.workflow-card-head,
.workflow-section-head {
- display: flex;
- align-items: flex-start;
- justify-content: space-between;
- gap: 12px;
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 12px;
}
.workflow-card-footer {
- display: flex;
- flex-direction: column;
- align-items: stretch;
- gap: 10px;
+ display: flex;
+ flex-direction: column;
+ align-items: stretch;
+ gap: 10px;
}
.workflow-card-head h3,
.workflow-section-head h4 {
- font-size: 18px;
- font-weight: 700;
+ font-size: 18px;
+ font-weight: 700;
}
.workflow-section-head h4 {
- font-size: 14px;
+ font-size: 14px;
}
.workflow-card-badges,
.workflow-card-meta {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- justify-content: flex-end;
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ justify-content: flex-end;
}
.workflow-card-meta-footer {
- justify-content: flex-start;
+ justify-content: flex-start;
}
.workflow-card-footer .alias-actions-cell {
- align-self: flex-end;
+ align-self: flex-end;
}
.workflow-card-description {
- color: var(--text-muted);
- font-size: 14px;
+ color: var(--text-muted);
+ font-size: 14px;
}
.workflow-guardrails,
.workflow-preview,
.workflow-guardrail-editor {
- display: flex;
- flex-direction: column;
- gap: 12px;
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
}
.workflow-guardrail-list,
.workflow-guardrail-list-editor {
- display: flex;
- flex-direction: column;
- gap: 10px;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
}
.workflow-guardrail-item,
.workflow-guardrail-row {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 12px;
- padding: 10px 12px;
- border: 1px solid var(--border);
- border-radius: 10px;
- background: var(--bg);
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+ padding: 10px 12px;
+ border: 1px solid var(--border);
+ border-radius: 10px;
+ background: var(--bg);
}
.workflow-guardrail-row {
- justify-content: stretch;
+ justify-content: stretch;
}
.workflow-guardrail-field {
- flex: 1 1 auto;
- min-width: 0;
+ flex: 1 1 auto;
+ min-width: 0;
}
.workflow-guardrail-step-field {
- flex: 0 0 120px;
+ flex: 0 0 120px;
}
.workflow-editor {
- margin-bottom: 0;
- width: 100%;
+ margin-bottom: 0;
+ width: 100%;
}
.workflow-input {
- max-width: none;
- width: 100%;
+ max-width: none;
+ width: 100%;
}
.workflow-step-input {
- max-width: 120px;
+ max-width: 120px;
}
.workflow-feature-toggles {
- display: grid;
- grid-template-columns: repeat(2, minmax(0, 1fr));
- gap: 12px;
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 12px;
}
.workflow-feature-toggle {
- display: flex;
- align-items: center;
- gap: 10px;
- padding: 10px 12px;
- border: 1px solid var(--border);
- border-radius: 10px;
- background: var(--bg);
- font-size: 14px;
- font-weight: 500;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 10px 12px;
+ border: 1px solid var(--border);
+ border-radius: 10px;
+ background: var(--bg);
+ font-size: 14px;
+ font-weight: 500;
}
.workflow-feature-toggle input {
- width: 16px;
- height: 16px;
+ width: 16px;
+ height: 16px;
}
/* Usage Mode Toggle */
.usage-mode-toggle {
- display: inline-flex;
- align-items: center;
- background: var(--bg);
- border: 1px solid var(--border);
- border-radius: 6px;
- padding: 2px;
+ display: inline-flex;
+ align-items: center;
+ background: var(--bg);
+ border: 1px solid var(--border);
+ border-radius: 6px;
+ padding: 2px;
}
.usage-mode-btn {
- display: flex;
- align-items: center;
- justify-content: center;
- padding: 5px 12px;
- background: transparent;
- border: none;
- border-radius: 4px;
- color: var(--text-muted);
- font-size: 12px;
- font-family: inherit;
- font-weight: 500;
- cursor: pointer;
- transition: all 0.15s;
- white-space: nowrap;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 5px 12px;
+ background: transparent;
+ border: none;
+ border-radius: 4px;
+ color: var(--text-muted);
+ font-size: 12px;
+ font-family: inherit;
+ font-weight: 500;
+ cursor: pointer;
+ transition: all 0.15s;
+ white-space: nowrap;
}
.usage-mode-btn:hover {
- color: var(--text);
+ color: var(--text);
}
.usage-mode-btn.active {
- background: var(--accent);
- color: #fff;
+ background: var(--accent);
+ color: #fff;
}
/* Usage by Model Chart Section */
.model-chart-section {
- background: var(--bg-surface);
- border: 1px solid var(--border);
- border-radius: var(--radius);
- padding: 24px;
- margin-bottom: 24px;
+ background: var(--bg-surface);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ padding: 24px;
+ margin-bottom: 24px;
}
.model-chart-section h3 {
- font-size: 16px;
- font-weight: 600;
- margin: 0;
+ font-size: 16px;
+ font-weight: 600;
+ margin: 0;
}
.model-chart-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 16px;
- margin-bottom: 16px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 16px;
+ margin-bottom: 16px;
}
.chart-view-toggle {
- display: inline-flex;
- align-items: center;
- background: var(--bg);
- border: 1px solid var(--border);
- border-radius: 6px;
- padding: 2px;
+ display: inline-flex;
+ align-items: center;
+ background: var(--bg);
+ border: 1px solid var(--border);
+ border-radius: 6px;
+ padding: 2px;
}
.chart-view-btn {
- width: 30px;
- height: 28px;
- display: inline-flex;
- align-items: center;
- justify-content: center;
- background: transparent;
- border: none;
- border-radius: 4px;
- color: var(--text-muted);
- cursor: pointer;
- transition: all 0.15s;
+ width: 30px;
+ height: 28px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ background: transparent;
+ border: none;
+ border-radius: 4px;
+ color: var(--text-muted);
+ cursor: pointer;
+ transition: all 0.15s;
}
.chart-view-btn:hover {
- color: var(--text);
+ color: var(--text);
}
.chart-view-btn.active {
- background: var(--accent);
- color: #fff;
+ background: var(--accent);
+ color: #fff;
}
.chart-view-btn svg {
- width: 16px;
- height: 16px;
- fill: none;
- stroke: currentcolor;
- stroke-width: 2;
- stroke-linecap: round;
- stroke-linejoin: round;
+ width: 16px;
+ height: 16px;
+ fill: none;
+ stroke: currentcolor;
+ stroke-width: 2;
+ stroke-linecap: round;
+ stroke-linejoin: round;
}
.bar-chart-wrap {
- position: relative;
- width: 100%;
- height: 180px;
+ position: relative;
+ width: 100%;
+ height: 180px;
}
.usage-chart-table-wrapper {
- margin-top: 0;
- overflow-x: auto;
- -webkit-overflow-scrolling: touch;
+ margin-top: 0;
+ overflow-x: auto;
+ -webkit-overflow-scrolling: touch;
}
.usage-chart-data-table {
- min-width: max-content;
+ min-width: max-content;
}
.usage-chart-data-table th,
.usage-chart-data-table td {
- white-space: nowrap;
+ white-space: nowrap;
}
/* Usage Log Section */
.usage-log-section {
- background: var(--bg-surface);
- border: 1px solid var(--border);
- border-radius: var(--radius);
- padding: 24px;
+ background: var(--bg-surface);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ padding: 24px;
}
.usage-log-section h3 {
- font-size: 16px;
- font-weight: 600;
- margin-bottom: 16px;
+ font-size: 16px;
+ font-weight: 600;
+ margin-bottom: 16px;
}
.usage-log-toolbar {
- display: flex;
- gap: 12px;
- margin-bottom: 16px;
+ display: flex;
+ gap: 12px;
+ margin-bottom: 16px;
}
.usage-log-toolbar .filter-input {
- flex: 1;
- max-width: none;
+ flex: 1;
+ max-width: none;
}
.usage-log-select {
- appearance: none;
- -webkit-appearance: none;
- padding: 8px 34px 8px 12px;
- background-color: var(--bg);
- background-image:
- linear-gradient(45deg, transparent 50%, currentcolor 50%),
- linear-gradient(135deg, currentcolor 50%, transparent 50%);
- background-position:
- calc(100% - 17px) 50%,
- calc(100% - 12px) 50%;
- background-repeat: no-repeat;
- background-size: 5px 5px;
- border: 1px solid var(--border);
- border-radius: var(--radius);
- color: var(--text);
- font-size: 13px;
- font-family: inherit;
- outline: none;
- cursor: pointer;
- min-width: 140px;
+ appearance: none;
+ -webkit-appearance: none;
+ padding: 8px 34px 8px 12px;
+ background-color: var(--bg);
+ background-image:
+ linear-gradient(45deg, transparent 50%, currentcolor 50%),
+ linear-gradient(135deg, currentcolor 50%, transparent 50%);
+ background-position:
+ calc(100% - 17px) 50%,
+ calc(100% - 12px) 50%;
+ background-repeat: no-repeat;
+ background-size: 5px 5px;
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ color: var(--text);
+ font-size: 13px;
+ font-family: inherit;
+ outline: none;
+ cursor: pointer;
+ min-width: 140px;
}
.usage-log-select:disabled {
- cursor: default;
- opacity: 0.65;
+ cursor: default;
+ opacity: 0.65;
}
.usage-log-select:focus {
- border-color: var(--accent);
+ border-color: var(--accent);
}
.usage-ts {
- white-space: nowrap;
- font-size: 12px;
+ white-space: nowrap;
+ font-size: 12px;
}
/* Audit Log Section */
.audit-log-section {
- background: var(--bg-surface);
- border: 1px solid var(--border);
- border-radius: var(--radius);
- padding: 24px;
+ background: var(--bg-surface);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ padding: 24px;
}
.audit-log-toolbar {
- display: flex;
- flex-direction: column;
- gap: 10px;
- margin-bottom: 14px;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ margin-bottom: 14px;
}
.audit-filter-row {
- display: grid;
- grid-template-columns: repeat(12, minmax(0, 1fr));
- gap: 10px;
+ display: grid;
+ grid-template-columns: repeat(12, minmax(0, 1fr));
+ gap: 10px;
}
.audit-filter-select {
- grid-column: span 2;
- min-width: 0;
+ grid-column: span 2;
+ min-width: 0;
}
.audit-filter-row-search .filter-input {
- grid-column: 1 / -1;
- max-width: none;
+ grid-column: 1 / -1;
+ max-width: none;
}
.audit-filter-row-controls .audit-filter-select {
- grid-column: span 2;
+ grid-column: span 2;
}
.audit-filter-row-controls .pagination-btn {
- grid-column: 11 / -1;
- justify-self: end;
- min-width: 108px;
+ grid-column: 11 / -1;
+ justify-self: end;
+ min-width: 108px;
}
.audit-clear-btn {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- gap: 8px;
- background: #fff;
- border-color: color-mix(in srgb, #fff 80%, var(--border));
- color: #111110;
- font-weight: 600;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ background: #fff;
+ border-color: color-mix(in srgb, #fff 80%, var(--border));
+ color: #111110;
+ font-weight: 600;
}
.audit-clear-btn:hover:not(:disabled) {
- background: #f5f5f5;
+ background: #f5f5f5;
}
.audit-clear-btn .table-icon-svg {
- width: 12px;
- height: 12px;
+ width: 12px;
+ height: 12px;
}
.audit-log-summary {
- color: var(--text-muted);
- font-size: 13px;
- margin-bottom: 12px;
+ color: var(--text-muted);
+ font-size: 13px;
+ margin-bottom: 12px;
}
.audit-log-list {
- display: flex;
- flex-direction: column;
- gap: 10px;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
}
.audit-entry {
- border: 1px solid var(--border);
- border-radius: var(--radius);
- background: var(--bg);
- overflow: hidden;
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ background: var(--bg);
+ overflow: hidden;
}
.audit-entry-summary {
- list-style: none;
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 12px;
- padding: 12px 14px;
- cursor: pointer;
+ list-style: none;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+ padding: 12px 14px;
+ cursor: pointer;
}
.audit-entry-summary::-webkit-details-marker {
- display: none;
+ display: none;
}
.audit-entry-left {
- display: flex;
- align-items: center;
- gap: 8px;
- min-width: 0;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ min-width: 0;
}
.audit-entry-right {
- display: inline-flex;
- align-items: center;
- gap: 10px;
- color: var(--text-muted);
- flex-shrink: 0;
- min-height: 28px;
+ display: inline-flex;
+ align-items: center;
+ gap: 10px;
+ color: var(--text-muted);
+ flex-shrink: 0;
+ min-height: 28px;
}
.audit-conversation-trigger {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- width: 28px;
- height: 28px;
- border-radius: 999px;
- border: 1px solid color-mix(in srgb, var(--accent) 55%, var(--border));
- background: color-mix(in srgb, var(--accent) 12%, var(--bg));
- color: var(--accent);
- cursor: pointer;
- transition: transform 0.1s ease-out, background 0.1s ease-out, color 0.1s ease-out;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 28px;
+ height: 28px;
+ border-radius: 999px;
+ border: 1px solid color-mix(in srgb, var(--accent) 55%, var(--border));
+ background: color-mix(in srgb, var(--accent) 12%, var(--bg));
+ color: var(--accent);
+ cursor: pointer;
+ transition:
+ transform 0.1s ease-out,
+ background 0.1s ease-out,
+ color 0.1s ease-out;
}
.audit-conversation-trigger:hover {
- background: color-mix(in srgb, var(--accent) 20%, var(--bg));
- transform: translateX(1px);
+ background: color-mix(in srgb, var(--accent) 20%, var(--bg));
+ transform: translateX(1px);
}
.audit-conversation-trigger svg {
- width: 14px;
- height: 14px;
+ width: 14px;
+ height: 14px;
}
.audit-path {
- font-size: 12px;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
+ font-size: 12px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
}
.audit-status-badge,
.audit-method-badge {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- min-width: 46px;
- height: 24px;
- padding: 0 10px;
- border-radius: 999px;
- border: 1px solid var(--border);
- font-size: 12px;
- font-weight: 600;
- letter-spacing: 0.2px;
- background: var(--bg-surface);
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 46px;
+ height: 24px;
+ padding: 0 10px;
+ border-radius: 999px;
+ border: 1px solid var(--border);
+ font-size: 12px;
+ font-weight: 600;
+ letter-spacing: 0.2px;
+ background: var(--bg-surface);
}
.audit-method-badge {
- min-width: 52px;
- color: var(--text-muted);
+ min-width: 52px;
+ color: var(--text-muted);
}
.audit-provider-model {
- height: 24px;
- padding: 0 10px;
- display: inline-flex;
- align-items: center;
- border-radius: 999px;
- border: 1px solid var(--border);
- font-size: 12px;
- background: var(--bg-surface);
- color: var(--text-muted);
- white-space: nowrap;
- max-width: 280px;
- overflow: hidden;
- text-overflow: ellipsis;
+ height: 24px;
+ padding: 0 10px;
+ display: inline-flex;
+ align-items: center;
+ border-radius: 999px;
+ border: 1px solid var(--border);
+ font-size: 12px;
+ background: var(--bg-surface);
+ color: var(--text-muted);
+ white-space: nowrap;
+ max-width: 280px;
+ overflow: hidden;
+ text-overflow: ellipsis;
}
.audit-status-badge.status-success {
- color: var(--success);
- border-color: color-mix(in srgb, var(--success) 50%, var(--border));
+ color: var(--success);
+ border-color: color-mix(in srgb, var(--success) 50%, var(--border));
}
.audit-status-badge.status-warning {
- color: var(--warning);
- border-color: color-mix(in srgb, var(--warning) 50%, var(--border));
+ color: var(--warning);
+ border-color: color-mix(in srgb, var(--warning) 50%, var(--border));
}
.audit-status-badge.status-error {
- color: var(--danger);
- border-color: color-mix(in srgb, var(--danger) 50%, var(--border));
+ color: var(--danger);
+ border-color: color-mix(in srgb, var(--danger) 50%, var(--border));
}
.audit-status-badge.status-neutral {
- color: var(--text-muted);
+ color: var(--text-muted);
}
.audit-status-badge.status-unknown {
- color: var(--text-muted);
- border-color: color-mix(in srgb, var(--border) 75%, transparent);
+ color: var(--text-muted);
+ border-color: color-mix(in srgb, var(--border) 75%, transparent);
}
.audit-entry-details {
- border-top: 1px solid var(--border);
- padding: 12px;
- background: var(--bg-surface);
- overflow: hidden;
+ border-top: 1px solid var(--border);
+ padding: 12px;
+ background: var(--bg-surface);
+ overflow: hidden;
}
.audit-request-response {
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 12px;
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 12px;
}
.audit-entry-metadata {
- display: flex;
- align-items: flex-start;
- gap: 10px;
- margin-top: 12px;
- padding-top: 12px;
- border-top: 1px solid var(--border);
+ display: flex;
+ align-items: flex-start;
+ gap: 10px;
+ margin-top: 12px;
+ padding-top: 12px;
+ border-top: 1px solid var(--border);
}
.audit-entry-metadata-label {
- flex: 0 0 auto;
- color: var(--text-muted);
- font-size: 12px;
- font-weight: 700;
- letter-spacing: 0.08em;
- text-transform: uppercase;
+ flex: 0 0 auto;
+ color: var(--text-muted);
+ font-size: 12px;
+ font-weight: 700;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
}
.audit-entry-context {
- display: flex;
- flex: 1 1 auto;
- flex-wrap: wrap;
- gap: 8px;
+ display: flex;
+ flex: 1 1 auto;
+ flex-wrap: wrap;
+ gap: 8px;
}
.audit-pane {
- border: 1px solid var(--border);
- border-radius: 8px;
- background: var(--bg);
- padding: 12px;
+ border: 1px solid var(--border);
+ border-radius: 8px;
+ background: var(--bg);
+ padding: 12px;
}
.audit-pane-head {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 8px;
- margin-bottom: 10px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 8px;
+ margin-bottom: 10px;
}
.audit-pane h4 {
- font-size: 14px;
- font-weight: 600;
+ font-size: 14px;
+ font-weight: 600;
}
.audit-pane h5 {
- font-size: 11px;
- font-weight: 600;
- text-transform: uppercase;
- letter-spacing: 0.4px;
- color: var(--text-muted);
- margin-bottom: 6px;
+ font-size: 11px;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.4px;
+ color: var(--text-muted);
+ margin-bottom: 6px;
}
.audit-pane-block + .audit-pane-block {
- margin-top: 10px;
+ margin-top: 10px;
}
.audit-json {
- background: var(--bg-surface);
- border: 1px solid var(--border);
- border-radius: 6px;
- font-family: 'SF Mono', Menlo, Consolas, monospace;
- font-size: 12px;
- line-height: 1.45;
- padding: 10px;
- max-height: 220px;
- overflow: auto;
- white-space: pre;
- color: var(--text);
+ background: var(--bg-surface);
+ border: 1px solid var(--border);
+ border-radius: 6px;
+ font-family: "SF Mono", Menlo, Consolas, monospace;
+ font-size: 12px;
+ line-height: 1.45;
+ padding: 10px;
+ max-height: 220px;
+ overflow: auto;
+ white-space: pre;
+ color: var(--text);
}
.audit-json-body {
- white-space: pre-wrap;
- overflow-wrap: anywhere;
+ white-space: pre-wrap;
+ overflow-wrap: anywhere;
}
.conversation-body-highlight {
- display: inline;
- border-left: 2px solid color-mix(in srgb, var(--accent) 70%, var(--border));
- background: color-mix(in srgb, var(--accent) 10%, transparent);
- border-radius: 2px;
- margin: 0 0 0 -2px;
- padding: 0 0 0 2px;
- cursor: pointer;
- line-height: inherit;
+ display: inline;
+ border-left: 2px solid color-mix(in srgb, var(--accent) 70%, var(--border));
+ background: color-mix(in srgb, var(--accent) 10%, transparent);
+ border-radius: 2px;
+ margin: 0 0 0 -2px;
+ padding: 0 0 0 2px;
+ cursor: pointer;
+ line-height: inherit;
}
.conversation-body-highlight:hover {
- background: color-mix(in srgb, var(--accent) 18%, transparent);
+ background: color-mix(in srgb, var(--accent) 18%, transparent);
}
.conversation-body-highlight.conversation-system {
- border-left-color: color-mix(in srgb, var(--warning) 70%, var(--border));
- background: color-mix(in srgb, var(--warning) 10%, transparent);
+ border-left-color: color-mix(in srgb, var(--warning) 70%, var(--border));
+ background: color-mix(in srgb, var(--warning) 10%, transparent);
}
.conversation-body-highlight.conversation-user {
- border-left-color: color-mix(in srgb, var(--accent) 75%, var(--border));
- background: color-mix(in srgb, var(--accent) 12%, transparent);
+ border-left-color: color-mix(in srgb, var(--accent) 75%, var(--border));
+ background: color-mix(in srgb, var(--accent) 12%, transparent);
}
.conversation-body-highlight.conversation-assistant {
- border-left-color: color-mix(in srgb, var(--success) 65%, var(--border));
- background: color-mix(in srgb, var(--success) 10%, transparent);
+ border-left-color: color-mix(in srgb, var(--success) 65%, var(--border));
+ background: color-mix(in srgb, var(--success) 10%, transparent);
}
.audit-pane-empty {
- text-align: left;
- padding: 8px 0 0;
+ text-align: left;
+ padding: 8px 0 0;
}
.audit-size-warning {
- margin-top: 8px;
- color: var(--warning);
- font-size: 12px;
+ margin-top: 8px;
+ color: var(--warning);
+ font-size: 12px;
}
.conversation-overlay {
- position: fixed;
- inset: 0;
- background: rgba(0, 0, 0, 0.3);
- z-index: 50;
+ position: fixed;
+ inset: 0;
+ background: rgba(0, 0, 0, 0.3);
+ z-index: 50;
}
.conversation-drawer {
- position: fixed;
- top: 0;
- right: 0;
- bottom: 0;
- width: min(560px, 100vw);
- background: var(--bg-surface);
- border-left: 1px solid var(--border);
- z-index: 60;
- transform: translateX(100%);
- transition: transform 0.2s ease-out;
- display: flex;
- flex-direction: column;
- box-shadow: -16px 0 40px rgba(0, 0, 0, 0.2);
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ width: min(560px, 100vw);
+ background: var(--bg-surface);
+ border-left: 1px solid var(--border);
+ z-index: 60;
+ transform: translateX(100%);
+ transition: transform 0.2s ease-out;
+ display: flex;
+ flex-direction: column;
+ box-shadow: -16px 0 40px rgba(0, 0, 0, 0.2);
}
.conversation-drawer.open {
- transform: translateX(0);
+ transform: translateX(0);
}
body.conversation-drawer-open {
- overflow: hidden;
+ overflow: hidden;
}
.conversation-drawer-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 10px;
- padding: 14px 16px;
- border-bottom: 1px solid var(--border);
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 10px;
+ padding: 14px 16px;
+ border-bottom: 1px solid var(--border);
}
.conversation-drawer-header h3 {
- font-size: 16px;
- font-weight: 700;
+ font-size: 16px;
+ font-weight: 700;
}
.conversation-meta {
- color: var(--text-muted);
- font-size: 12px;
- font-family: 'SF Mono', Menlo, Consolas, monospace;
+ color: var(--text-muted);
+ font-size: 12px;
+ font-family: "SF Mono", Menlo, Consolas, monospace;
}
#interactions-drawer-content {
- flex: 1;
- overflow-y: auto;
- min-height: 0;
+ flex: 1;
+ overflow-y: auto;
+ min-height: 0;
}
.conversation-drawer-footer {
- flex-shrink: 0;
- border-top: 1px solid var(--border);
- padding: 10px 16px;
- background: var(--bg-surface);
+ flex-shrink: 0;
+ border-top: 1px solid var(--border);
+ padding: 10px 16px;
+ background: var(--bg-surface);
}
.conversation-thread {
- padding: 14px 16px 20px;
- display: flex;
- flex-direction: column;
- gap: 10px;
+ padding: 14px 16px 20px;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
}
.chat-message {
- border: 1px solid var(--border);
- border-radius: 10px;
- padding: 10px 12px;
- max-width: 94%;
- background: var(--bg);
+ border: 1px solid var(--border);
+ border-radius: 10px;
+ padding: 10px 12px;
+ max-width: 94%;
+ background: var(--bg);
}
.chat-message.is-anchor {
- border-color: color-mix(in srgb, var(--accent) 55%, var(--border));
- box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 25%, transparent);
+ border-color: color-mix(in srgb, var(--accent) 55%, var(--border));
+ box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 25%, transparent);
}
.chat-message.role-user {
- align-self: flex-start;
+ align-self: flex-start;
}
.chat-message.role-assistant {
- align-self: flex-end;
- background: color-mix(in srgb, var(--accent) 18%, var(--bg));
+ align-self: flex-end;
+ background: color-mix(in srgb, var(--accent) 18%, var(--bg));
}
.chat-message.role-system {
- align-self: center;
- width: 100%;
- max-width: 100%;
- background: color-mix(in srgb, var(--warning) 10%, var(--bg));
+ align-self: center;
+ width: 100%;
+ max-width: 100%;
+ background: color-mix(in srgb, var(--warning) 10%, var(--bg));
}
.chat-message.role-error {
- align-self: flex-end;
- border-color: color-mix(in srgb, var(--danger) 55%, var(--border));
- background: color-mix(in srgb, var(--danger) 10%, var(--bg));
+ align-self: flex-end;
+ border-color: color-mix(in srgb, var(--danger) 55%, var(--border));
+ background: color-mix(in srgb, var(--danger) 10%, var(--bg));
}
.chat-message.role-error .chat-role {
- color: color-mix(in srgb, var(--danger) 75%, var(--text-muted));
+ color: color-mix(in srgb, var(--danger) 75%, var(--text-muted));
}
.chat-message-meta {
- display: flex;
- align-items: baseline;
- justify-content: space-between;
- gap: 12px;
- margin-bottom: 6px;
+ display: flex;
+ align-items: baseline;
+ justify-content: space-between;
+ gap: 12px;
+ margin-bottom: 6px;
}
.chat-role {
- font-size: 12px;
- font-weight: 700;
- text-transform: uppercase;
- letter-spacing: 0.4px;
- color: var(--text-muted);
+ font-size: 12px;
+ font-weight: 700;
+ text-transform: uppercase;
+ letter-spacing: 0.4px;
+ color: var(--text-muted);
}
.chat-time {
- font-size: 11px;
- color: var(--text-muted);
- white-space: nowrap;
+ font-size: 11px;
+ color: var(--text-muted);
+ white-space: nowrap;
}
.chat-content {
- font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
- font-size: 13px;
- line-height: 1.5;
- white-space: pre-wrap;
- overflow-wrap: break-word;
- color: var(--text);
+ font-family:
+ Inter,
+ -apple-system,
+ BlinkMacSystemFont,
+ "Segoe UI",
+ Roboto,
+ sans-serif;
+ font-size: 13px;
+ line-height: 1.5;
+ white-space: pre-wrap;
+ overflow-wrap: break-word;
+ color: var(--text);
}
/* Tool call footer on assistant bubbles */
.chat-tool-calls {
- margin-top: 8px;
- padding-top: 6px;
- border-top: 1px solid var(--border);
- display: flex;
- flex-direction: column;
- gap: 3px;
+ margin-top: 8px;
+ padding-top: 6px;
+ border-top: 1px solid var(--border);
+ display: flex;
+ flex-direction: column;
+ gap: 3px;
}
.chat-tool-call {
- font-size: 11px;
- color: var(--text-muted);
- font-family: 'SF Mono', Menlo, Consolas, monospace;
+ font-size: 11px;
+ color: var(--text-muted);
+ font-family: "SF Mono", Menlo, Consolas, monospace;
}
.chat-tool-call-name::before {
- content: "\26A1 ";
+ content: "\26A1 ";
}
/* Function call / result notes between bubbles */
.chat-function-note {
- align-self: center;
- max-width: 94%;
- padding: 4px 12px;
- border-radius: 12px;
- font-size: 12px;
- color: var(--text-muted);
- background: color-mix(in srgb, var(--border) 40%, transparent);
- border: 1px dashed var(--border);
+ align-self: center;
+ max-width: 94%;
+ padding: 4px 12px;
+ border-radius: 12px;
+ font-size: 12px;
+ color: var(--text-muted);
+ background: color-mix(in srgb, var(--border) 40%, transparent);
+ border: 1px dashed var(--border);
}
.chat-function-note.is-anchor {
- border-color: color-mix(in srgb, var(--accent) 55%, var(--border));
+ border-color: color-mix(in srgb, var(--accent) 55%, var(--border));
}
.chat-function-note-inner {
- display: flex;
- align-items: baseline;
- gap: 6px;
- overflow: hidden;
+ display: flex;
+ align-items: baseline;
+ gap: 6px;
+ overflow: hidden;
}
.chat-function-label {
- font-weight: 600;
- font-size: 11px;
- text-transform: uppercase;
- letter-spacing: 0.3px;
- white-space: nowrap;
- flex-shrink: 0;
+ font-weight: 600;
+ font-size: 11px;
+ text-transform: uppercase;
+ letter-spacing: 0.3px;
+ white-space: nowrap;
+ flex-shrink: 0;
}
.chat-function-detail {
- font-family: 'SF Mono', Menlo, Consolas, monospace;
- font-size: 11px;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
+ font-family: "SF Mono", Menlo, Consolas, monospace;
+ font-size: 11px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
}
.chat-function-note.role-function-call {
- align-self: flex-end;
- background: color-mix(in srgb, var(--accent) 8%, transparent);
- border-color: color-mix(in srgb, var(--accent) 30%, var(--border));
+ align-self: flex-end;
+ background: color-mix(in srgb, var(--accent) 8%, transparent);
+ border-color: color-mix(in srgb, var(--accent) 30%, var(--border));
}
.chat-function-note.role-function-result {
- align-self: flex-start;
- background: color-mix(in srgb, var(--success) 8%, transparent);
- border-color: color-mix(in srgb, var(--success) 30%, var(--border));
+ align-self: flex-start;
+ background: color-mix(in srgb, var(--success) 8%, transparent);
+ border-color: color-mix(in srgb, var(--success) 30%, var(--border));
}
.chat-function-note.is-anchor.role-function-call,
.chat-function-note.is-anchor.role-function-result {
- border-color: color-mix(in srgb, var(--accent) 55%, var(--border));
+ border-color: color-mix(in srgb, var(--accent) 55%, var(--border));
}
/* Collapsible function notes */
.chat-function-note-details {
- width: 100%;
+ width: 100%;
}
.chat-function-note-details > summary {
- list-style: none;
- cursor: pointer;
+ list-style: none;
+ cursor: pointer;
}
.chat-function-note-details > summary::-webkit-details-marker {
- display: none;
+ display: none;
}
.chat-function-expanded {
- font-family: 'SF Mono', Menlo, Consolas, monospace;
- font-size: 11px;
- line-height: 1.45;
- margin-top: 6px;
- padding-top: 6px;
- border-top: 1px solid var(--border);
- white-space: pre-wrap;
- overflow-wrap: anywhere;
- color: var(--text);
- max-height: 200px;
- overflow: auto;
+ font-family: "SF Mono", Menlo, Consolas, monospace;
+ font-size: 11px;
+ line-height: 1.45;
+ margin-top: 6px;
+ padding-top: 6px;
+ border-top: 1px solid var(--border);
+ white-space: pre-wrap;
+ overflow-wrap: anywhere;
+ color: var(--text);
+ max-height: 200px;
+ overflow: auto;
}
/* Caveat warning icon */
.caveat-icon {
- color: var(--warning);
- cursor: help;
- font-size: 14px;
- margin-left: 4px;
+ color: var(--warning);
+ cursor: help;
+ font-size: 14px;
+ margin-left: 4px;
}
/* Pagination */
.pagination {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 12px 0 0;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 12px 0 0;
}
.pagination-info {
- font-size: 13px;
- color: var(--text-muted);
+ font-size: 13px;
+ color: var(--text-muted);
}
.pagination-buttons {
- display: flex;
- gap: 8px;
+ display: flex;
+ gap: 8px;
}
.pagination-btn {
- padding: 6px 16px;
- background: var(--bg);
- border: 1px solid var(--border);
- border-radius: 6px;
- color: var(--text);
- font-size: 13px;
- font-family: inherit;
- cursor: pointer;
- transition: all 0.15s;
+ padding: 6px 16px;
+ background: var(--bg);
+ border: 1px solid var(--border);
+ border-radius: 6px;
+ color: var(--text);
+ font-size: 13px;
+ font-family: inherit;
+ cursor: pointer;
+ transition: all 0.15s;
}
.pagination-btn:hover:not(:disabled) {
- background: var(--bg-surface-hover);
+ background: var(--bg-surface-hover);
}
.pagination-btn-primary {
- background: var(--accent);
- border-color: color-mix(in srgb, var(--accent) 70%, #000 10%);
- color: #fff;
- font-weight: 600;
- box-shadow: 0 10px 22px rgba(59, 130, 246, 0.18);
+ background: var(--accent);
+ border-color: color-mix(in srgb, var(--accent) 70%, #000 10%);
+ color: #fff;
+ font-weight: 600;
+ box-shadow: 0 10px 22px rgba(59, 130, 246, 0.18);
}
.pagination-btn-primary:hover:not(:disabled) {
- background: color-mix(in srgb, var(--accent) 90%, #fff 10%);
- border-color: color-mix(in srgb, var(--accent) 78%, #000 12%);
+ background: color-mix(in srgb, var(--accent) 90%, #fff 10%);
+ border-color: color-mix(in srgb, var(--accent) 78%, #000 12%);
}
.pagination-btn-danger-outline {
- color: var(--danger);
- border-color: color-mix(in srgb, var(--danger) 55%, var(--border));
- background: transparent;
- font-weight: 600;
+ color: var(--danger);
+ border-color: color-mix(in srgb, var(--danger) 55%, var(--border));
+ background: transparent;
+ font-weight: 600;
}
.pagination-btn-danger-outline:hover:not(:disabled) {
- background: color-mix(in srgb, var(--danger) 10%, var(--bg));
- border-color: color-mix(in srgb, var(--danger) 75%, var(--border));
+ background: color-mix(in srgb, var(--danger) 10%, var(--bg));
+ border-color: color-mix(in srgb, var(--danger) 75%, var(--border));
}
.pagination-btn-with-icon {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- gap: 8px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
}
.pagination-btn-with-icon .table-icon-svg {
- width: 16px;
- height: 16px;
-}
-
-.workflow-submit-btn {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- gap: 8px;
-}
-
-.workflow-submit-icon {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- width: 16px;
- height: 16px;
- flex: 0 0 auto;
-}
-
-.workflow-submit-icon svg {
- width: 16px;
- height: 16px;
- fill: none;
- stroke: currentcolor;
- stroke-width: 2;
- stroke-linecap: round;
- stroke-linejoin: round;
+ width: 16px;
+ height: 16px;
}
.pagination-btn:disabled {
- opacity: 0.4;
- cursor: default;
+ opacity: 0.4;
+ cursor: default;
}
/* Contribution Calendar */
.contribution-calendar-section {
- background: var(--bg-surface);
- border: 1px solid var(--border);
- border-radius: var(--radius);
- padding: 24px;
- margin-top: 24px;
+ background: var(--bg-surface);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ padding: 24px;
+ margin-top: 24px;
}
.contribution-calendar-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- margin-bottom: 16px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 16px;
}
.contribution-calendar-header h3 {
- font-size: 16px;
- font-weight: 600;
+ font-size: 16px;
+ font-weight: 600;
}
.contribution-calendar-grid-wrapper {
- display: flex;
- gap: 8px;
+ display: flex;
+ gap: 8px;
}
.contribution-calendar-day-labels {
- display: flex;
- flex-direction: column;
- gap: 2px;
- padding-top: 22px;
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+ padding-top: 22px;
}
.contribution-calendar-day-labels span {
- height: 13px;
- font-size: 10px;
- line-height: 13px;
- color: var(--text-muted);
- text-align: right;
- min-width: 28px;
+ height: 13px;
+ font-size: 10px;
+ line-height: 13px;
+ color: var(--text-muted);
+ text-align: right;
+ min-width: 28px;
}
.contribution-calendar-scroll {
- flex: 1;
- overflow-x: auto;
- min-width: 0;
- --contribution-week-size: 13px;
- --contribution-week-gap: 2px;
+ flex: 1;
+ overflow-x: auto;
+ min-width: 0;
+ --contribution-week-size: 13px;
+ --contribution-week-gap: 2px;
}
.contribution-calendar-months {
- display: grid;
- grid-auto-columns: var(--contribution-week-size);
- grid-auto-flow: column;
- gap: var(--contribution-week-gap);
- margin-bottom: 6px;
- height: 16px;
+ display: grid;
+ grid-auto-columns: var(--contribution-week-size);
+ grid-auto-flow: column;
+ gap: var(--contribution-week-gap);
+ margin-bottom: 6px;
+ height: 16px;
}
.contribution-calendar-month-label {
- font-size: 10px;
- color: var(--text-muted);
- white-space: nowrap;
+ font-size: 10px;
+ color: var(--text-muted);
+ white-space: nowrap;
}
.contribution-calendar-grid {
- display: flex;
- gap: var(--contribution-week-gap);
+ display: flex;
+ gap: var(--contribution-week-gap);
}
.contribution-calendar-week {
- display: flex;
- flex-direction: column;
- gap: 2px;
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
}
.contribution-calendar-cell {
- width: var(--contribution-week-size);
- height: var(--contribution-week-size);
- border-radius: 2px;
- background: var(--cal-level-0);
+ width: var(--contribution-week-size);
+ height: var(--contribution-week-size);
+ border-radius: 2px;
+ background: var(--cal-level-0);
}
-.contribution-calendar-cell.level-1 { background: var(--cal-level-1); }
-.contribution-calendar-cell.level-2 { background: var(--cal-level-2); }
-.contribution-calendar-cell.level-3 { background: var(--cal-level-3); }
-.contribution-calendar-cell.level-4 { background: var(--cal-level-4); }
-.contribution-calendar-cell.empty { background: transparent; }
+.contribution-calendar-cell.level-1 {
+ background: var(--cal-level-1);
+}
+.contribution-calendar-cell.level-2 {
+ background: var(--cal-level-2);
+}
+.contribution-calendar-cell.level-3 {
+ background: var(--cal-level-3);
+}
+.contribution-calendar-cell.level-4 {
+ background: var(--cal-level-4);
+}
+.contribution-calendar-cell.empty {
+ background: transparent;
+}
.contribution-calendar-footer {
- display: flex;
- align-items: center;
- justify-content: space-between;
- margin-top: 12px;
- gap: 12px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-top: 12px;
+ gap: 12px;
}
.contribution-calendar-meta {
- display: flex;
- flex-direction: column;
- gap: 4px;
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
}
.contribution-calendar-summary {
- font-size: 12px;
- color: var(--text-muted);
+ font-size: 12px;
+ color: var(--text-muted);
}
.contribution-calendar-legend {
- display: flex;
- align-items: center;
- gap: 4px;
- font-size: 11px;
- color: var(--text-muted);
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ font-size: 11px;
+ color: var(--text-muted);
}
.contribution-calendar-legend .contribution-calendar-cell {
- width: 11px;
- height: 11px;
- cursor: default;
+ width: 11px;
+ height: 11px;
+ cursor: default;
}
.contribution-calendar-tooltip {
- position: fixed;
- z-index: 200;
- background: var(--bg-surface);
- color: var(--text);
- border: 1px solid var(--border);
- border-radius: 4px;
- padding: 4px 8px;
- font-size: 12px;
- font-family: 'SF Mono', Menlo, Consolas, monospace;
- white-space: nowrap;
- pointer-events: none;
- transform: translateX(-50%);
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
+ position: fixed;
+ z-index: 200;
+ background: var(--bg-surface);
+ color: var(--text);
+ border: 1px solid var(--border);
+ border-radius: 4px;
+ padding: 4px 8px;
+ font-size: 12px;
+ font-family: "SF Mono", Menlo, Consolas, monospace;
+ white-space: nowrap;
+ pointer-events: none;
+ transform: translateX(-50%);
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
.settings-panel {
- background: var(--bg-surface);
- border: 1px solid var(--border);
- border-radius: var(--radius);
- padding: 24px;
+ background: var(--bg-surface);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ padding: 24px;
}
.settings-panel-header {
- display: flex;
- justify-content: space-between;
- gap: 16px;
- margin-bottom: 20px;
+ display: flex;
+ justify-content: space-between;
+ gap: 16px;
+ margin-bottom: 20px;
}
.settings-panel-header h3 {
- font-size: 18px;
- margin-bottom: 6px;
+ font-size: 18px;
+ margin-bottom: 6px;
}
.inline-help-section {
- display: flex;
- flex-direction: column;
- gap: 2px;
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
}
.inline-help-title-row {
- display: inline-flex;
- align-items: center;
- gap: 10px;
+ display: inline-flex;
+ align-items: center;
+ gap: 10px;
}
.inline-help-title-row h3 {
- margin-bottom: 0;
+ margin-bottom: 0;
}
.inline-help-toggle {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- position: relative;
- width: 16px;
- height: 16px;
- padding: 0;
- border: 1px solid color-mix(in srgb, var(--accent) 28%, var(--border));
- border-radius: 4px;
- background: transparent;
- color: var(--accent);
- cursor: pointer;
- transition: color 0.18s ease, border-color 0.18s ease;
- -webkit-tap-highlight-color: transparent;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ position: relative;
+ width: 16px;
+ height: 16px;
+ padding: 0;
+ border: 1px solid color-mix(in srgb, var(--accent) 28%, var(--border));
+ border-radius: 4px;
+ background: transparent;
+ color: var(--accent);
+ cursor: pointer;
+ transition:
+ color 0.18s ease,
+ border-color 0.18s ease;
+ -webkit-tap-highlight-color: transparent;
}
.inline-help-toggle::before {
- content: "";
- position: absolute;
- width: 32px;
- height: 32px;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- background: transparent;
- pointer-events: auto;
+ content: "";
+ position: absolute;
+ width: 32px;
+ height: 32px;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background: transparent;
+ pointer-events: auto;
}
.inline-help-toggle:hover {
- background: transparent;
- border-color: color-mix(in srgb, var(--accent) 48%, var(--border));
- color: var(--text);
+ background: transparent;
+ border-color: color-mix(in srgb, var(--accent) 48%, var(--border));
+ color: var(--text);
}
.inline-help-toggle:active {
- background: transparent;
+ background: transparent;
}
.inline-help-toggle:focus-visible {
- outline: 2px solid color-mix(in srgb, var(--accent) 28%, transparent);
- outline-offset: 2px;
+ outline: 2px solid color-mix(in srgb, var(--accent) 28%, transparent);
+ outline-offset: 2px;
}
.inline-help-toggle.is-open {
- color: var(--text);
- background: transparent;
+ color: var(--text);
+ background: transparent;
}
.inline-help-toggle-icon {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- width: 100%;
- height: 100%;
- padding-bottom: 1px;
- font-size: 13px;
- font-weight: 700;
- line-height: 1;
- transform: rotate(0deg);
- transition: transform 0.52s cubic-bezier(0.22, 0.72, 0.12, 1);
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+ height: 100%;
+ padding-bottom: 1px;
+ font-size: 13px;
+ font-weight: 700;
+ line-height: 1;
+ transform: rotate(0deg);
+ transition: transform 0.52s cubic-bezier(0.22, 0.72, 0.12, 1);
}
.inline-help-toggle.is-open .inline-help-toggle-icon {
- transform: rotate(540deg);
+ transform: rotate(540deg);
}
.inline-help-copy {
- max-width: 780px;
- color: var(--text-muted);
- font-size: 14px;
- margin-top: 2px;
+ max-width: 780px;
+ color: var(--text-muted);
+ font-size: 14px;
+ margin-top: 2px;
}
.settings-form-grid {
- display: grid;
- grid-template-columns: minmax(280px, 420px);
- gap: 16px;
+ display: grid;
+ grid-template-columns: minmax(280px, 420px);
+ gap: 16px;
}
.settings-select {
- width: 100%;
+ width: 100%;
}
.settings-refresh-section {
- display: flex;
- align-items: flex-start;
- justify-content: space-between;
- gap: 16px;
- margin-top: 24px;
- padding-top: 22px;
- border-top: 1px solid var(--border);
+ display: grid;
+ justify-items: start;
+ gap: 12px;
+ margin-top: 24px;
+ padding-top: 22px;
+ border-top: 1px solid var(--border);
}
.settings-refresh-section h3 {
- margin-bottom: 6px;
- font-size: 18px;
+ font-size: 18px;
}
.settings-refresh-section p {
- max-width: 720px;
- color: var(--text-muted);
- font-size: 14px;
- line-height: 1.55;
+ max-width: 720px;
+ color: var(--text-muted);
+ font-size: 14px;
+ line-height: 1.55;
}
-.settings-refresh-alert {
- margin-top: 16px;
- margin-bottom: 0;
+.settings-refresh-icon,
+.alias-create-icon,
+.form-action-icon {
+ width: 16px;
+ height: 16px;
+ flex: 0 0 16px;
+}
+
+.settings-refresh-btn.is-refreshing .settings-refresh-icon {
+ animation: loading-spin 0.8s linear infinite;
+ transform-origin: center;
}
-.settings-refresh-loading {
- justify-content: flex-start;
- min-height: 52px;
- margin-top: 14px;
+.settings-refresh-alert {
+ margin-top: 16px;
+ margin-bottom: 0;
}
.runtime-refresh-steps {
- display: grid;
- gap: 8px;
- margin-top: 14px;
- padding-left: 18px;
- color: var(--text-muted);
- font-size: 13px;
+ display: grid;
+ gap: 8px;
+ margin-top: 14px;
+ padding-left: 18px;
+ color: var(--text-muted);
+ font-size: 13px;
}
.runtime-refresh-step.is-ok {
- color: var(--success);
+ color: var(--success);
}
.runtime-refresh-step.is-partial {
- color: var(--warning);
+ color: var(--warning);
}
.runtime-refresh-step.is-failed {
- color: var(--danger);
+ color: var(--danger);
}
.page-subtitle {
- margin-top: 6px;
- color: var(--text-muted);
- font-size: 14px;
+ margin-top: 6px;
+ color: var(--text-muted);
+ font-size: 14px;
}
.settings-subnav {
- display: inline-flex;
- gap: 8px;
- padding: 6px;
- margin-bottom: 20px;
- border: 1px solid var(--border);
- border-radius: 999px;
- background: color-mix(in srgb, var(--bg-surface) 80%, transparent);
+ display: inline-flex;
+ gap: 8px;
+ padding: 6px;
+ margin-bottom: 20px;
+ border: 1px solid var(--border);
+ border-radius: 999px;
+ background: color-mix(in srgb, var(--bg-surface) 80%, transparent);
}
.settings-subnav-btn {
- border: 0;
- border-radius: 999px;
- padding: 10px 14px;
- background: transparent;
- color: var(--text-muted);
- font-size: 13px;
- font-weight: 600;
- cursor: pointer;
- transition: background-color 0.18s ease, color 0.18s ease, transform 0.18s ease;
+ border: 0;
+ border-radius: 999px;
+ padding: 10px 14px;
+ background: transparent;
+ color: var(--text-muted);
+ font-size: 13px;
+ font-weight: 600;
+ cursor: pointer;
+ transition:
+ background-color 0.18s ease,
+ color 0.18s ease,
+ transform 0.18s ease;
}
.settings-subnav-btn:hover {
- color: var(--text);
- background: color-mix(in srgb, var(--accent) 12%, transparent);
+ color: var(--text);
+ background: color-mix(in srgb, var(--accent) 12%, transparent);
}
.settings-subnav-btn.active {
- color: var(--text);
- background: linear-gradient(135deg, color-mix(in srgb, var(--accent) 20%, transparent), color-mix(in srgb, var(--accent-hover) 16%, transparent));
- box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 18%, var(--border));
+ color: var(--text);
+ background: linear-gradient(
+ 135deg,
+ color-mix(in srgb, var(--accent) 20%, transparent),
+ color-mix(in srgb, var(--accent-hover) 16%, transparent)
+ );
+ box-shadow: inset 0 0 0 1px
+ color-mix(in srgb, var(--accent) 18%, var(--border));
}
.settings-guardrails-hero {
- display: flex;
- align-items: flex-start;
- justify-content: space-between;
- gap: 20px;
- padding: 24px;
- margin-bottom: 20px;
- border: 1px solid color-mix(in srgb, var(--accent) 14%, var(--border));
- border-radius: var(--radius);
- background:
- radial-gradient(circle at top right, color-mix(in srgb, var(--accent-hover) 18%, transparent), transparent 42%),
- radial-gradient(circle at bottom left, color-mix(in srgb, var(--accent) 16%, transparent), transparent 40%),
- var(--bg-surface);
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 20px;
+ padding: 24px;
+ margin-bottom: 20px;
+ border: 1px solid color-mix(in srgb, var(--accent) 14%, var(--border));
+ border-radius: var(--radius);
+ background:
+ radial-gradient(
+ circle at top right,
+ color-mix(in srgb, var(--accent-hover) 18%, transparent),
+ transparent 42%
+ ),
+ radial-gradient(
+ circle at bottom left,
+ color-mix(in srgb, var(--accent) 16%, transparent),
+ transparent 40%
+ ),
+ var(--bg-surface);
}
.settings-kicker {
- margin: 0 0 10px;
- color: var(--accent);
- font-size: 11px;
- font-weight: 700;
- letter-spacing: 0.08em;
- text-transform: uppercase;
+ margin: 0 0 10px;
+ color: var(--accent);
+ font-size: 11px;
+ font-weight: 700;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
}
.settings-guardrails-hero h3 {
- margin-bottom: 8px;
+ margin-bottom: 8px;
}
.settings-guardrails-hero p:last-child {
- margin-bottom: 0;
- color: var(--text-muted);
+ margin-bottom: 0;
+ color: var(--text-muted);
}
.settings-guardrails-meta {
- display: flex;
- gap: 12px;
+ display: flex;
+ gap: 12px;
}
.settings-guardrails-stat {
- min-width: 110px;
- padding: 14px 16px;
- border: 1px solid color-mix(in srgb, var(--accent) 14%, var(--border));
- border-radius: 16px;
- background: color-mix(in srgb, var(--bg-surface-hover) 76%, transparent);
+ min-width: 110px;
+ padding: 14px 16px;
+ border: 1px solid color-mix(in srgb, var(--accent) 14%, var(--border));
+ border-radius: 16px;
+ background: color-mix(in srgb, var(--bg-surface-hover) 76%, transparent);
}
.settings-guardrails-stat-label {
- display: block;
- margin-bottom: 8px;
- color: var(--text-muted);
- font-size: 11px;
- font-weight: 700;
- letter-spacing: 0.08em;
- text-transform: uppercase;
+ display: block;
+ margin-bottom: 8px;
+ color: var(--text-muted);
+ font-size: 11px;
+ font-weight: 700;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
}
.settings-guardrails-stat strong {
- font-size: 24px;
- font-weight: 700;
+ font-size: 24px;
+ font-weight: 700;
}
.settings-guardrails-layout {
- display: grid;
- grid-template-columns: 1fr;
- gap: 20px;
- align-items: start;
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: 20px;
+ align-items: start;
}
.settings-guardrails-layout.is-editor-open {
- grid-template-columns: minmax(0, 1.35fr) minmax(320px, 0.9fr);
+ grid-template-columns: minmax(0, 1.35fr) minmax(320px, 0.9fr);
}
.settings-guardrails-list,
.settings-guardrails-editor {
- min-width: 0;
+ min-width: 0;
}
.settings-guardrail-type-pill {
- display: inline-flex;
- align-items: center;
- padding: 6px 10px;
- border: 1px solid color-mix(in srgb, var(--accent) 18%, var(--border));
- border-radius: 999px;
- background: color-mix(in srgb, var(--accent) 12%, transparent);
- color: var(--text);
- font-size: 12px;
- font-weight: 600;
- white-space: nowrap;
+ display: inline-flex;
+ align-items: center;
+ padding: 6px 10px;
+ border: 1px solid color-mix(in srgb, var(--accent) 18%, var(--border));
+ border-radius: 999px;
+ background: color-mix(in srgb, var(--accent) 12%, transparent);
+ color: var(--text);
+ font-size: 12px;
+ font-weight: 600;
+ white-space: nowrap;
}
.settings-guardrail-summary {
- color: var(--text);
- font-size: 14px;
- line-height: 1.45;
+ color: var(--text);
+ font-size: 14px;
+ line-height: 1.45;
}
.settings-guardrail-description {
- margin-top: 6px;
- color: var(--text-muted);
- font-size: 12px;
+ margin-top: 6px;
+ color: var(--text-muted);
+ font-size: 12px;
}
.settings-guardrail-textarea {
- min-height: 150px;
- width: 100%;
- resize: vertical;
- padding: 12px 14px;
- border: 1px solid var(--border);
- border-radius: var(--radius);
- background: var(--bg);
- color: var(--text);
- font: inherit;
- line-height: 1.55;
+ min-height: 150px;
+ width: 100%;
+ resize: vertical;
+ padding: 12px 14px;
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ background: var(--bg);
+ color: var(--text);
+ font: inherit;
+ line-height: 1.55;
}
.settings-guardrail-textarea:focus {
- outline: none;
- border-color: var(--accent);
- box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent);
+ outline: none;
+ border-color: var(--accent);
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent);
}
/* Responsive */
@media (max-width: 768px) {
- .sidebar { width: 60px; flex-basis: 60px; }
- .sidebar-header { justify-content: center; padding: 16px; }
- .sidebar-header h1, .badge { display: none; }
- .sidebar-nav .nav-item { justify-content: center; padding: 10px; }
- .sidebar-nav .nav-item span { display: none; }
- .sidebar-footer { display: grid; gap: 8px; padding: 8px; }
- .sidebar-footer .api-key-section { display: grid; }
- .sidebar.sidebar-collapsed .sidebar-footer .api-key-section { display: grid; }
- .sidebar-footer .api-key-open-btn {
- width: 36px;
- height: 36px;
- min-height: 36px;
- justify-self: center;
- padding: 0;
- }
- .sidebar-footer .api-key-open-btn span {
- display: none;
- }
- .sidebar-footer .api-key-open-icon {
- width: 16px;
- height: 16px;
- flex-basis: 16px;
- }
- .sidebar-footer .theme-toggle { display: none; }
- .sidebar-footer .theme-toggle-mobile { display: flex; margin: 0 auto; }
- .sidebar-toggle { display: none; }
- .content { width: 100%; margin: 0 auto; padding: 20px; }
- .auth-dialog-shell { align-items: end; padding: 12px; }
- .auth-dialog { padding: 18px; }
- .auth-dialog-actions { flex-direction: column-reverse; }
- .auth-dialog-actions .pagination-btn { width: 100%; }
- .cards { grid-template-columns: repeat(2, 1fr); }
-
- .provider-status-flag {
- grid-column: span 1;
- }
-
- .provider-status-meta {
- grid-template-columns: 1fr;
- }
-
- .provider-status-section-header {
- flex-direction: column;
- align-items: flex-start;
- }
-
- .provider-status-toggle {
- width: 100%;
- justify-content: space-between;
- }
-
- /* Page header stacking */
- .page-header {
- flex-wrap: wrap;
- gap: 12px;
- }
- .page-header h2 {
- width: 100%;
- }
- .page-header-controls {
- width: 100%;
- justify-content: space-between;
- flex-wrap: wrap;
- }
-
- .settings-panel-title-row {
- gap: 8px;
- }
-
- .settings-subnav {
- display: flex;
- width: 100%;
- }
-
- .settings-subnav-btn {
- flex: 1 1 0;
- }
-
- .settings-guardrails-hero {
- flex-direction: column;
- }
-
- .settings-guardrails-meta {
- width: 100%;
- }
-
- .settings-guardrails-stat {
- flex: 1 1 0;
- }
-
- .settings-guardrails-layout {
- grid-template-columns: 1fr;
- }
-
- /* Category tabs mobile */
- .category-tabs {
- gap: 4px;
- }
- .category-tab {
- padding: 5px 10px;
- font-size: 12px;
- }
-
- /* Interval picker mobile */
- .interval-btn {
- padding: 4px 8px;
- font-size: 11px;
- }
-
- /* Usage page mobile */
- .usage-log-toolbar {
- flex-direction: column;
- }
- .usage-log-select {
- min-width: 0;
- }
- .usage-log-table {
- display: block;
- overflow-x: auto;
- }
- .usage-mode-btn {
- padding: 4px 8px;
- font-size: 11px;
- }
-
- .alias-form-grid {
- grid-template-columns: 1fr;
- }
- .workflow-card-grid,
- .workflow-feature-toggles {
- grid-template-columns: 1fr;
- }
- .workflow-card-head,
- .workflow-card-footer,
- .workflow-section-head,
- .workflow-card-badges,
- .workflow-card-meta,
- .workflow-guardrail-item,
- .workflow-guardrail-row {
- flex-direction: column;
- align-items: flex-start;
- }
- .workflow-step-input {
- max-width: none;
- width: 100%;
- }
- .workflow-guardrail-field,
- .workflow-guardrail-step-field {
- flex-basis: auto;
- width: 100%;
- }
- .table-toolbar {
- flex-direction: column;
- align-items: stretch;
- }
- .table-toolbar-actions {
- margin-left: 0;
- justify-content: stretch;
- }
- .table-toolbar-actions .pagination-btn {
- width: 100%;
- }
- .model-alias-editor {
- padding: 16px;
- }
- .alias-actions-cell,
- .aliases-editor-header,
- .model-name-primary,
- .provider-group-header {
- flex-direction: column;
- align-items: flex-start;
- }
- .alias-actions-cell .table-action-btn,
- .aliases-editor-header .table-action-btn {
- width: 100%;
- }
- .alias-actions-cell .table-icon-btn,
- .aliases-editor-header .table-icon-btn {
- width: 36px;
- }
-
- /* Audit page mobile */
- .audit-log-toolbar {
- gap: 8px;
- }
- .audit-filter-row {
- grid-template-columns: 1fr;
- }
- .audit-filter-row .filter-input,
- .audit-filter-select,
- .audit-filter-row .pagination-btn {
- grid-column: auto;
- }
- .audit-entry-summary {
- flex-direction: column;
- align-items: flex-start;
- }
- .audit-entry-right {
- width: 100%;
- justify-content: space-between;
- }
- .audit-entry-metadata {
- flex-direction: column;
- gap: 8px;
- }
- .audit-request-response {
- grid-template-columns: 1fr;
- }
- .conversation-drawer {
- width: 100%;
- }
- .chat-message {
- max-width: 100%;
- }
-
- /* Contribution calendar mobile */
- .contribution-calendar-day-labels { display: none; }
- .contribution-calendar-section { padding: 16px; }
- .contribution-calendar-footer {
- align-items: flex-start;
- flex-direction: column;
- }
- .settings-panel {
- padding: 18px;
- }
- .settings-form-grid {
- grid-template-columns: 1fr;
- }
- .settings-refresh-section {
- flex-direction: column;
- }
-
- /* Date picker mobile */
- .date-picker-dropdown {
- flex-direction: column;
- right: 0;
- left: 0;
- position: fixed;
- top: auto;
- bottom: 0;
- border-radius: var(--radius) var(--radius) 0 0;
- max-height: 80vh;
- overflow-y: auto;
- }
- .date-picker-presets {
- flex-direction: row;
- flex-wrap: nowrap;
- overflow-x: auto;
- -webkit-overflow-scrolling: touch;
- border-right: none;
- border-bottom: 1px solid var(--border);
- min-width: 0;
- }
- .date-picker-calendars {
- justify-content: center;
- }
- .dp-calendar:first-child {
- display: none;
- }
- .dp-nav-prev-mobile {
- display: flex;
- }
+ .sidebar {
+ width: 60px;
+ flex-basis: 60px;
+ }
+ .sidebar-header {
+ justify-content: center;
+ padding: 16px;
+ }
+ .sidebar-header h1,
+ .badge {
+ display: none;
+ }
+ .sidebar-nav .nav-item {
+ justify-content: center;
+ padding: 10px;
+ }
+ .sidebar-nav .nav-item span {
+ display: none;
+ }
+ .sidebar-footer {
+ display: grid;
+ gap: 8px;
+ padding: 8px;
+ }
+ .sidebar-footer .api-key-section {
+ display: grid;
+ }
+ .sidebar.sidebar-collapsed .sidebar-footer .api-key-section { display: grid; }
+ .sidebar-footer .api-key-open-btn {
+ width: 36px;
+ height: 36px;
+ min-height: 36px;
+ justify-self: center;
+ padding: 0;
+ }
+ .sidebar-footer .api-key-open-btn span {
+ display: none;
+ }
+ .sidebar-footer .api-key-open-icon {
+ width: 16px;
+ height: 16px;
+ flex-basis: 16px;
+ }
+ .sidebar-footer .theme-toggle {
+ display: none;
+ }
+ .sidebar-footer .theme-toggle-mobile {
+ display: flex;
+ margin: 0 auto;
+ }
+ .sidebar-toggle {
+ display: none;
+ }
+ .content {
+ width: 100%;
+ margin: 0 auto;
+ padding: 20px;
+ }
+ .auth-dialog-shell {
+ align-items: end;
+ padding: 12px;
+ }
+ .auth-dialog {
+ padding: 18px;
+ }
+ .auth-dialog-actions {
+ flex-direction: column-reverse;
+ }
+ .auth-dialog-actions .pagination-btn {
+ width: 100%;
+ }
+ .cards {
+ grid-template-columns: repeat(2, 1fr);
+ }
+
+ .provider-status-flag {
+ grid-column: span 1;
+ }
+
+ .provider-status-meta {
+ grid-template-columns: 1fr;
+ }
+
+ .provider-status-section-header {
+ flex-direction: column;
+ align-items: flex-start;
+ }
+
+ .provider-status-toggle {
+ width: 100%;
+ justify-content: space-between;
+ }
+
+ /* Page header stacking */
+ .page-header {
+ flex-wrap: wrap;
+ gap: 12px;
+ }
+ .page-header h2 {
+ width: 100%;
+ }
+ .page-header-controls {
+ width: 100%;
+ justify-content: space-between;
+ flex-wrap: wrap;
+ }
+
+ .settings-panel-title-row {
+ gap: 8px;
+ }
+
+ .settings-subnav {
+ display: flex;
+ width: 100%;
+ }
+
+ .settings-subnav-btn {
+ flex: 1 1 0;
+ }
+
+ .settings-guardrails-hero {
+ flex-direction: column;
+ }
+
+ .settings-guardrails-meta {
+ width: 100%;
+ }
+
+ .settings-guardrails-stat {
+ flex: 1 1 0;
+ }
+
+ .settings-guardrails-layout {
+ grid-template-columns: 1fr;
+ }
+
+ /* Category tabs mobile */
+ .category-tabs {
+ gap: 4px;
+ }
+ .category-tab {
+ padding: 5px 10px;
+ font-size: 12px;
+ }
+
+ /* Interval picker mobile */
+ .interval-btn {
+ padding: 4px 8px;
+ font-size: 11px;
+ }
+
+ /* Usage page mobile */
+ .usage-log-toolbar {
+ flex-direction: column;
+ }
+ .usage-log-select {
+ min-width: 0;
+ }
+ .usage-log-table {
+ display: block;
+ overflow-x: auto;
+ }
+ .usage-mode-btn {
+ padding: 4px 8px;
+ font-size: 11px;
+ }
+
+ .alias-form-grid {
+ grid-template-columns: 1fr;
+ }
+ .workflow-card-grid,
+ .workflow-feature-toggles {
+ grid-template-columns: 1fr;
+ }
+ .workflow-card-head,
+ .workflow-card-footer,
+ .workflow-section-head,
+ .workflow-card-badges,
+ .workflow-card-meta,
+ .workflow-guardrail-item,
+ .workflow-guardrail-row {
+ flex-direction: column;
+ align-items: flex-start;
+ }
+ .workflow-step-input {
+ max-width: none;
+ width: 100%;
+ }
+ .workflow-guardrail-field,
+ .workflow-guardrail-step-field {
+ flex-basis: auto;
+ width: 100%;
+ }
+ .table-toolbar {
+ flex-direction: column;
+ align-items: stretch;
+ }
+ .table-toolbar-actions {
+ margin-left: 0;
+ justify-content: stretch;
+ }
+ .table-toolbar-actions .pagination-btn {
+ width: 100%;
+ }
+ .model-alias-editor {
+ padding: 16px;
+ }
+ .alias-actions-cell,
+ .aliases-editor-header,
+ .model-name-primary,
+ .provider-group-header {
+ flex-direction: column;
+ align-items: flex-start;
+ }
+ .alias-actions-cell .table-action-btn,
+ .aliases-editor-header .table-action-btn {
+ width: 100%;
+ }
+ .alias-actions-cell .table-icon-btn,
+ .aliases-editor-header .table-icon-btn {
+ width: 36px;
+ }
+
+ /* Audit page mobile */
+ .audit-log-toolbar {
+ gap: 8px;
+ }
+ .audit-filter-row {
+ grid-template-columns: 1fr;
+ }
+ .audit-filter-row .filter-input,
+ .audit-filter-select,
+ .audit-filter-row .pagination-btn {
+ grid-column: auto;
+ }
+ .audit-entry-summary {
+ flex-direction: column;
+ align-items: flex-start;
+ }
+ .audit-entry-right {
+ width: 100%;
+ justify-content: space-between;
+ }
+ .audit-entry-metadata {
+ flex-direction: column;
+ gap: 8px;
+ }
+ .audit-request-response {
+ grid-template-columns: 1fr;
+ }
+ .conversation-drawer {
+ width: 100%;
+ }
+ .chat-message {
+ max-width: 100%;
+ }
+
+ /* Contribution calendar mobile */
+ .contribution-calendar-day-labels {
+ display: none;
+ }
+ .contribution-calendar-section {
+ padding: 16px;
+ }
+ .contribution-calendar-footer {
+ align-items: flex-start;
+ flex-direction: column;
+ }
+ .settings-panel {
+ padding: 18px;
+ }
+ .settings-form-grid {
+ grid-template-columns: 1fr;
+ }
+
+ /* Date picker mobile */
+ .date-picker-dropdown {
+ flex-direction: column;
+ right: 0;
+ left: 0;
+ position: fixed;
+ top: auto;
+ bottom: 0;
+ border-radius: var(--radius) var(--radius) 0 0;
+ max-height: 80vh;
+ overflow-y: auto;
+ }
+ .date-picker-presets {
+ flex-direction: row;
+ flex-wrap: nowrap;
+ overflow-x: auto;
+ -webkit-overflow-scrolling: touch;
+ border-right: none;
+ border-bottom: 1px solid var(--border);
+ min-width: 0;
+ }
+ .date-picker-calendars {
+ justify-content: center;
+ }
+ .dp-calendar:first-child {
+ display: none;
+ }
+ .dp-nav-prev-mobile {
+ display: flex;
+ }
}
/* ═══════════════════════════════════════════════════════════════
@@ -3619,391 +3738,405 @@ body.conversation-drawer-open {
═══════════════════════════════════════════════════════════════ */
.workflow-pipeline {
- position: relative;
- display: flex;
- flex-direction: column;
- gap: 0;
- padding: 18px 20px 20px;
- margin-bottom: 12px;
- border-radius: var(--radius);
- border: 1px solid var(--border);
- background: var(--bg);
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 0;
+ padding: 18px 20px 20px;
+ margin-bottom: 12px;
+ border-radius: var(--radius);
+ border: 1px solid var(--border);
+ background: var(--bg);
}
.workflow-pipeline-has-meta {
- padding-top: 42px;
+ padding-top: 42px;
}
.workflow-pipeline-meta {
- position: absolute;
- top: 12px;
- right: 14px;
- display: inline-flex;
- align-items: center;
- gap: 0;
- min-width: 0;
- max-width: calc(100% - 28px);
- padding: 2px 10px;
- border-radius: 12px;
- border: 1px solid var(--border);
- background: color-mix(in srgb, var(--bg-surface) 86%, transparent);
- color: var(--text-muted);
- font-size: 12px;
- font-weight: 500;
- line-height: 1.2;
- white-space: nowrap;
+ position: absolute;
+ top: 12px;
+ right: 14px;
+ display: inline-flex;
+ align-items: center;
+ gap: 0;
+ min-width: 0;
+ max-width: calc(100% - 28px);
+ padding: 2px 10px;
+ border-radius: 12px;
+ border: 1px solid var(--border);
+ background: color-mix(in srgb, var(--bg-surface) 86%, transparent);
+ color: var(--text-muted);
+ font-size: 12px;
+ font-weight: 500;
+ line-height: 1.2;
+ white-space: nowrap;
}
.workflow-pipeline-meta-copy {
- appearance: none;
- cursor: pointer;
- text-align: left;
- overflow: hidden;
- transition: background-color 0.15s, border-color 0.15s, color 0.15s, box-shadow 0.15s;
+ appearance: none;
+ cursor: pointer;
+ text-align: left;
+ overflow: hidden;
+ transition:
+ background-color 0.15s,
+ border-color 0.15s,
+ color 0.15s,
+ box-shadow 0.15s;
}
.workflow-pipeline-meta-copy:hover,
.workflow-pipeline-meta-copy:focus-visible {
- border-color: color-mix(in srgb, var(--accent) 40%, var(--border));
- background: color-mix(in srgb, var(--accent) 8%, var(--bg-surface));
- color: color-mix(in srgb, var(--accent) 74%, var(--text));
+ border-color: color-mix(in srgb, var(--accent) 40%, var(--border));
+ background: color-mix(in srgb, var(--accent) 8%, var(--bg-surface));
+ color: color-mix(in srgb, var(--accent) 74%, var(--text));
}
.workflow-pipeline-meta-copy:focus-visible {
- outline: none;
- box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 18%, transparent);
+ outline: none;
+ box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 18%, transparent);
}
.workflow-pipeline-meta-label {
- flex: 0 0 auto;
- font-weight: 700;
+ flex: 0 0 auto;
+ font-weight: 700;
}
.workflow-pipeline-meta-placeholder {
- flex: 0 0 auto;
- max-width: 3ch;
- margin-left: 4px;
- overflow: hidden;
- opacity: 1;
- transition: max-width 0.18s ease, margin-left 0.18s ease, opacity 0.15s ease;
+ flex: 0 0 auto;
+ max-width: 3ch;
+ margin-left: 4px;
+ overflow: hidden;
+ opacity: 1;
+ transition:
+ max-width 0.18s ease,
+ margin-left 0.18s ease,
+ opacity 0.15s ease;
}
.workflow-pipeline-meta-value {
- flex: 0 1 auto;
- max-width: 0;
- margin-left: 0;
- overflow: hidden;
- opacity: 0;
- text-overflow: clip;
- transition: max-width 0.22s ease, margin-left 0.18s ease, opacity 0.15s ease;
+ flex: 0 1 auto;
+ max-width: 0;
+ margin-left: 0;
+ overflow: hidden;
+ opacity: 0;
+ text-overflow: clip;
+ transition:
+ max-width 0.22s ease,
+ margin-left 0.18s ease,
+ opacity 0.15s ease;
}
.workflow-pipeline-meta-copy:hover .workflow-pipeline-meta-placeholder,
.workflow-pipeline-meta-copy:focus-visible .workflow-pipeline-meta-placeholder,
.workflow-pipeline-meta-copied .workflow-pipeline-meta-placeholder,
.workflow-pipeline-meta-error .workflow-pipeline-meta-placeholder {
- max-width: 0;
- margin-left: 0;
- opacity: 0;
+ max-width: 0;
+ margin-left: 0;
+ opacity: 0;
}
.workflow-pipeline-meta-copy:hover .workflow-pipeline-meta-value,
.workflow-pipeline-meta-copy:focus-visible .workflow-pipeline-meta-value,
.workflow-pipeline-meta-copied .workflow-pipeline-meta-value,
.workflow-pipeline-meta-error .workflow-pipeline-meta-value {
- max-width: 42ch;
- margin-left: 4px;
- opacity: 1;
+ max-width: 42ch;
+ margin-left: 4px;
+ opacity: 1;
}
.workflow-pipeline-meta-icon {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- flex: 0 0 auto;
- width: 0;
- height: 14px;
- margin-left: 0;
- overflow: hidden;
- opacity: 0;
- line-height: 0;
- transform: translateX(4px) translateY(1px) scale(0.84);
- transition: width 0.18s ease, margin-left 0.18s ease, opacity 0.15s ease, transform 0.18s ease;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ flex: 0 0 auto;
+ width: 0;
+ height: 14px;
+ margin-left: 0;
+ overflow: hidden;
+ opacity: 0;
+ line-height: 0;
+ transform: translateX(4px) translateY(1px) scale(0.84);
+ transition:
+ width 0.18s ease,
+ margin-left 0.18s ease,
+ opacity 0.15s ease,
+ transform 0.18s ease;
}
.workflow-pipeline-meta-icon svg {
- width: 14px;
- height: 14px;
- stroke: currentcolor;
- fill: none;
- stroke-width: 2;
- stroke-linecap: round;
- stroke-linejoin: round;
+ width: 14px;
+ height: 14px;
+ stroke: currentcolor;
+ fill: none;
+ stroke-width: 2;
+ stroke-linecap: round;
+ stroke-linejoin: round;
}
.workflow-pipeline-meta-copied,
.workflow-pipeline-meta-copied:hover,
.workflow-pipeline-meta-copied:focus-visible {
- background: color-mix(in srgb, var(--success) 12%, var(--bg));
- border-color: color-mix(in srgb, var(--success) 40%, var(--border));
- color: var(--success);
+ background: color-mix(in srgb, var(--success) 12%, var(--bg));
+ border-color: color-mix(in srgb, var(--success) 40%, var(--border));
+ color: var(--success);
}
.workflow-pipeline-meta-copied .workflow-pipeline-meta-icon {
- width: 14px;
- margin-left: 6px;
- opacity: 1;
- transform: translateY(1px);
+ width: 14px;
+ margin-left: 6px;
+ opacity: 1;
+ transform: translateY(1px);
}
.workflow-pipeline-meta-error,
.workflow-pipeline-meta-error:hover,
.workflow-pipeline-meta-error:focus-visible {
- background: color-mix(in srgb, var(--danger) 10%, var(--bg));
- border-color: color-mix(in srgb, var(--danger) 34%, var(--border));
- color: var(--danger);
+ background: color-mix(in srgb, var(--danger) 10%, var(--bg));
+ border-color: color-mix(in srgb, var(--danger) 34%, var(--border));
+ color: var(--danger);
}
/* ─── Main pipeline row ─── */
.workflow-pipeline-row {
- display: flex;
- align-items: center;
- width: 100%;
- min-width: 0;
+ display: flex;
+ align-items: center;
+ width: 100%;
+ min-width: 0;
}
/* ─── Connectors ─── */
.workflow-conn {
- flex: 1 1 0;
- min-width: 13px;
- position: relative;
- width: auto;
- height: 2px;
- background: color-mix(in srgb, var(--accent) 44%, var(--border));
+ flex: 1 1 0;
+ min-width: 13px;
+ position: relative;
+ width: auto;
+ height: 2px;
+ background: color-mix(in srgb, var(--accent) 44%, var(--border));
}
.workflow-conn::after {
- content: "";
- position: absolute;
- right: -1px;
- top: 50%;
- transform: translateY(-50%);
- width: 7px;
- height: 9px;
- background: color-mix(in srgb, var(--accent) 44%, var(--border));
- clip-path: polygon(0 0, 100% 50%, 0 100%);
+ content: "";
+ position: absolute;
+ right: -1px;
+ top: 50%;
+ transform: translateY(-50%);
+ width: 7px;
+ height: 9px;
+ background: color-mix(in srgb, var(--accent) 44%, var(--border));
+ clip-path: polygon(0 0, 100% 50%, 0 100%);
}
/* Green connector — cache hit path */
.workflow-conn-hit {
- background: color-mix(in srgb, var(--success) 58%, var(--border));
+ background: color-mix(in srgb, var(--success) 58%, var(--border));
}
.workflow-conn-hit::after {
- background: color-mix(in srgb, var(--success) 58%, var(--border));
+ background: color-mix(in srgb, var(--success) 58%, var(--border));
}
/* Dimmed connector — path not taken */
.workflow-conn-dim {
- background: color-mix(in srgb, var(--border) 75%, transparent);
- opacity: 0.4;
+ background: color-mix(in srgb, var(--border) 75%, transparent);
+ opacity: 0.4;
}
.workflow-conn-dim::after {
- background: color-mix(in srgb, var(--border) 75%, transparent);
+ background: color-mix(in srgb, var(--border) 75%, transparent);
}
/* ─── Base node ─── */
.workflow-node {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- gap: 4px;
- padding: 8px 12px;
- border-radius: var(--radius);
- border: 1px solid var(--border);
- background: var(--bg-surface);
- min-width: 72px;
- text-align: center;
- flex-shrink: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 4px;
+ padding: 8px 12px;
+ border-radius: var(--radius);
+ border: 1px solid var(--border);
+ background: var(--bg-surface);
+ min-width: 72px;
+ text-align: center;
+ flex-shrink: 0;
}
.workflow-node-icon {
- display: flex;
- align-items: center;
- justify-content: center;
- width: 28px;
- height: 28px;
- border-radius: var(--radius);
- background: var(--bg);
- color: var(--text-muted);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 28px;
+ height: 28px;
+ border-radius: var(--radius);
+ background: var(--bg);
+ color: var(--text-muted);
}
.workflow-node-icon svg {
- width: 15px;
- height: 15px;
- stroke: currentcolor;
- fill: none;
- stroke-width: 2;
- stroke-linecap: round;
- stroke-linejoin: round;
+ width: 15px;
+ height: 15px;
+ stroke: currentcolor;
+ fill: none;
+ stroke-width: 2;
+ stroke-linecap: round;
+ stroke-linejoin: round;
}
.workflow-node-label {
- font-size: 11px;
- font-weight: 700;
- letter-spacing: 0.03em;
- color: var(--text);
- white-space: nowrap;
- line-height: 1.2;
+ font-size: 11px;
+ font-weight: 700;
+ letter-spacing: 0.03em;
+ color: var(--text);
+ white-space: nowrap;
+ line-height: 1.2;
}
.workflow-node-sub {
- font-size: 10px;
- font-weight: 500;
- color: var(--text-muted);
- white-space: nowrap;
- max-width: 120px;
- overflow: hidden;
- text-overflow: ellipsis;
- line-height: 1.2;
- font-family: var(--font-mono, ui-monospace, monospace);
+ font-size: 10px;
+ font-weight: 500;
+ color: var(--text-muted);
+ white-space: nowrap;
+ max-width: 120px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ line-height: 1.2;
+ font-family: var(--font-mono, ui-monospace, monospace);
}
.workflow-node-badge {
- display: inline-flex;
- align-items: center;
- padding: 2px 7px;
- border-radius: var(--radius);
- font-size: 9px;
- font-weight: 800;
- letter-spacing: 0.06em;
- text-transform: uppercase;
- white-space: nowrap;
- border: 1px solid var(--border);
- background: var(--bg);
- color: var(--text-muted);
- line-height: 1.5;
+ display: inline-flex;
+ align-items: center;
+ padding: 2px 7px;
+ border-radius: var(--radius);
+ font-size: 9px;
+ font-weight: 800;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ white-space: nowrap;
+ border: 1px solid var(--border);
+ background: var(--bg);
+ color: var(--text-muted);
+ line-height: 1.5;
}
/* ─── Endpoint nodes (Client / Response) ─── */
.workflow-node-endpoint {
- flex-direction: row;
- padding: 10px 14px;
- border-radius: var(--radius);
- min-width: auto;
- gap: 7px;
- border-color: var(--border);
- background: var(--bg-surface);
+ flex-direction: row;
+ padding: 10px 14px;
+ border-radius: var(--radius);
+ min-width: auto;
+ gap: 7px;
+ border-color: var(--border);
+ background: var(--bg-surface);
}
.workflow-node-icon-endpoint {
- width: auto;
- height: auto;
- justify-content: flex-start;
- padding: 0;
- background: transparent;
- border-radius: var(--radius);
- color: var(--text-muted);
+ width: auto;
+ height: auto;
+ justify-content: flex-start;
+ padding: 0;
+ background: transparent;
+ border-radius: var(--radius);
+ color: var(--text-muted);
}
.workflow-node-icon-endpoint svg {
- width: 14px;
- height: 14px;
+ width: 14px;
+ height: 14px;
}
.workflow-node-endpoint .workflow-node-label {
- font-size: 11px;
- font-weight: 600;
- color: var(--text-muted);
+ font-size: 11px;
+ font-weight: 600;
+ color: var(--text-muted);
}
/* ─── Shared node variants ─── */
.workflow-node-feature {
- border-color: color-mix(in srgb, var(--accent) 46%, var(--border));
- background: color-mix(in srgb, var(--accent) 8%, var(--bg-surface));
+ border-color: color-mix(in srgb, var(--accent) 46%, var(--border));
+ background: color-mix(in srgb, var(--accent) 8%, var(--bg-surface));
}
.workflow-node-feature .workflow-node-icon {
- background: color-mix(in srgb, var(--accent) 16%, var(--bg));
- color: var(--accent);
+ background: color-mix(in srgb, var(--accent) 16%, var(--bg));
+ color: var(--accent);
}
.workflow-node-feature .workflow-node-label {
- color: var(--accent);
+ color: var(--accent);
}
.workflow-node-feature .workflow-node-sub {
- color: color-mix(in srgb, var(--accent) 70%, var(--text-muted));
+ color: color-mix(in srgb, var(--accent) 70%, var(--text-muted));
}
.workflow-node-success {
- border-color: color-mix(in srgb, var(--success) 52%, var(--border));
- background: color-mix(in srgb, var(--success) 9%, var(--bg-surface));
+ border-color: color-mix(in srgb, var(--success) 52%, var(--border));
+ background: color-mix(in srgb, var(--success) 9%, var(--bg-surface));
}
.workflow-node-success .workflow-node-icon {
- background: color-mix(in srgb, var(--success) 18%, var(--bg));
- color: var(--success);
+ background: color-mix(in srgb, var(--success) 18%, var(--bg));
+ color: var(--success);
}
.workflow-node-success .workflow-node-icon-endpoint {
- color: var(--success);
+ color: var(--success);
}
.workflow-node-success .workflow-node-label {
- color: color-mix(in srgb, var(--success) 85%, var(--text));
+ color: color-mix(in srgb, var(--success) 85%, var(--text));
}
.workflow-node-success .workflow-node-sub {
- color: color-mix(in srgb, var(--success) 74%, var(--text-muted));
+ color: color-mix(in srgb, var(--success) 74%, var(--text-muted));
}
.workflow-node-success .workflow-node-badge {
- background: color-mix(in srgb, var(--success) 14%, var(--bg));
- border-color: color-mix(in srgb, var(--success) 38%, var(--border));
- color: var(--success);
+ background: color-mix(in srgb, var(--success) 14%, var(--bg));
+ border-color: color-mix(in srgb, var(--success) 38%, var(--border));
+ color: var(--success);
}
.workflow-node-error {
- border-color: color-mix(in srgb, var(--danger) 52%, var(--border));
- background: color-mix(in srgb, var(--danger) 9%, var(--bg-surface));
+ border-color: color-mix(in srgb, var(--danger) 52%, var(--border));
+ background: color-mix(in srgb, var(--danger) 9%, var(--bg-surface));
}
.workflow-node-error .workflow-node-icon {
- background: color-mix(in srgb, var(--danger) 14%, var(--bg));
- color: var(--danger);
+ background: color-mix(in srgb, var(--danger) 14%, var(--bg));
+ color: var(--danger);
}
.workflow-node-error .workflow-node-icon-endpoint {
- color: var(--danger);
+ color: var(--danger);
}
.workflow-node-error .workflow-node-label {
- color: color-mix(in srgb, var(--danger) 85%, var(--text));
+ color: color-mix(in srgb, var(--danger) 85%, var(--text));
}
.workflow-node-error .workflow-node-sub {
- color: color-mix(in srgb, var(--danger) 72%, var(--text-muted));
+ color: color-mix(in srgb, var(--danger) 72%, var(--text-muted));
}
.workflow-node-skipped {
- position: relative;
- opacity: 0.28;
+ position: relative;
+ opacity: 0.28;
}
/* ─── Cache node ─── */
/* Cache — runtime: miss badge only */
.workflow-node-cache-miss .workflow-node-badge {
- color: var(--text-muted);
- border-color: var(--border);
+ color: var(--text-muted);
+ border-color: var(--border);
}
/* ─── Auth node ─── */
@@ -4011,10 +4144,10 @@ body.conversation-drawer-open {
/* ─── AI node ─── */
.workflow-node-ai {
- min-width: 96px;
- padding: 12px 16px;
- border-radius: var(--radius);
- gap: 6px;
+ min-width: 96px;
+ padding: 12px 16px;
+ border-radius: var(--radius);
+ gap: 6px;
}
/* ─── Async section ─── */
@@ -4033,202 +4166,206 @@ body.conversation-drawer-open {
/* Container — full-width branch lane below the main row */
.workflow-async-section {
- width: 100%;
- display: flex;
- align-items: center;
- justify-content: flex-end;
- gap: 0;
- min-width: 0;
- margin-top: 10px;
+ width: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ gap: 0;
+ min-width: 0;
+ margin-top: 10px;
}
/* L-turn connector: centered horizontal leg plus vertical rise back to Response */
.workflow-async-turn {
- flex: 0 0 60px;
- position: relative; /* for arrowhead + vertical rise */
- height: 2px;
- background: repeating-linear-gradient(
- to left,
- color-mix(in srgb, var(--text-muted) 45%, var(--border)) 0,
- color-mix(in srgb, var(--text-muted) 45%, var(--border)) 5px,
- transparent 5px,
- transparent 9px
- );
+ flex: 0 0 60px;
+ position: relative; /* for arrowhead + vertical rise */
+ height: 2px;
+ background: repeating-linear-gradient(
+ to left,
+ color-mix(in srgb, var(--text-muted) 45%, var(--border)) 0,
+ color-mix(in srgb, var(--text-muted) 45%, var(--border)) 5px,
+ transparent 5px,
+ transparent 9px
+ );
}
/* Left-pointing arrowhead at the end of the horizontal L-turn line */
.workflow-async-turn::before {
- content: "";
- position: absolute;
- left: -7px;
- top: 50%;
- transform: translateY(-50%);
- width: 7px;
- height: 9px;
- background: color-mix(in srgb, var(--text-muted) 40%, var(--border));
- clip-path: polygon(100% 0, 0 50%, 100% 100%);
+ content: "";
+ position: absolute;
+ left: -7px;
+ top: 50%;
+ transform: translateY(-50%);
+ width: 7px;
+ height: 9px;
+ background: color-mix(in srgb, var(--text-muted) 40%, var(--border));
+ clip-path: polygon(100% 0, 0 50%, 100% 100%);
}
/* Vertical dashed rise that connects the inline turn back up to Response */
.workflow-async-turn::after {
- content: "";
- position: absolute;
- right: 0;
- bottom: 1px;
- height: 16px;
- border-right: 2px dashed color-mix(in srgb, var(--text-muted) 40%, var(--border));
+ content: "";
+ position: absolute;
+ right: 0;
+ bottom: 1px;
+ height: 16px;
+ border-right: 2px dashed
+ color-mix(in srgb, var(--text-muted) 40%, var(--border));
}
/* RTL async row — Audit Log right, Usage left */
.workflow-async-row {
- display: flex;
- align-items: center;
- min-width: 0;
- margin-right: 7px;
+ display: flex;
+ align-items: center;
+ min-width: 0;
+ margin-right: 7px;
}
/* Dashed left-pointing connector between async nodes */
.workflow-conn-async {
- flex: 0 0 24px;
- background: repeating-linear-gradient(
- to left,
- color-mix(in srgb, var(--text-muted) 45%, var(--border)) 0,
- color-mix(in srgb, var(--text-muted) 45%, var(--border)) 5px,
- transparent 5px,
- transparent 9px
- );
- width: 24px;
+ flex: 0 0 24px;
+ background: repeating-linear-gradient(
+ to left,
+ color-mix(in srgb, var(--text-muted) 45%, var(--border)) 0,
+ color-mix(in srgb, var(--text-muted) 45%, var(--border)) 5px,
+ transparent 5px,
+ transparent 9px
+ );
+ width: 24px;
}
/* Left-pointing arrowhead (overrides workflow-conn::after right-pointing default) */
.workflow-conn-async::after {
- background: color-mix(in srgb, var(--text-muted) 45%, var(--border));
- left: -1px;
- right: auto;
- clip-path: polygon(100% 0, 0 50%, 100% 100%);
+ background: color-mix(in srgb, var(--text-muted) 45%, var(--border));
+ left: -1px;
+ right: auto;
+ clip-path: polygon(100% 0, 0 50%, 100% 100%);
}
/* Async nodes — horizontal inline pills */
.workflow-node-async {
- flex-direction: row;
- padding: 7px 12px;
- border-radius: var(--radius);
- border-style: dashed;
- min-width: auto;
- gap: 7px;
+ flex-direction: row;
+ padding: 7px 12px;
+ border-radius: var(--radius);
+ border-style: dashed;
+ min-width: auto;
+ gap: 7px;
}
.workflow-node-async .workflow-node-icon {
- width: 12px;
- height: 12px;
- border-radius: var(--radius);
+ width: 12px;
+ height: 12px;
+ border-radius: var(--radius);
}
.workflow-node-async .workflow-node-icon svg {
- width: 12px;
- height: 12px;
+ width: 12px;
+ height: 12px;
}
.workflow-node-async .workflow-node-label {
- font-size: 10px;
- font-weight: 700;
+ font-size: 10px;
+ font-weight: 700;
}
/* "ASYNC" label inline on the right of the branch */
.workflow-async-label {
- display: inline-flex;
- align-items: center;
- margin-left: 8px;
- font-size: 9px;
- font-weight: 800;
- letter-spacing: 0.1em;
- text-transform: uppercase;
- color: var(--text-muted);
- opacity: 0.55;
- white-space: nowrap;
- flex-shrink: 0;
+ display: inline-flex;
+ align-items: center;
+ margin-left: 8px;
+ font-size: 9px;
+ font-weight: 800;
+ letter-spacing: 0.1em;
+ text-transform: uppercase;
+ color: var(--text-muted);
+ opacity: 0.55;
+ white-space: nowrap;
+ flex-shrink: 0;
}
/* --- API Keys page --- */
.auth-keys-help-notice {
- margin-bottom: 20px;
+ margin-bottom: 20px;
}
.auth-key-issued-banner {
- background: color-mix(in srgb, var(--success) 8%, var(--bg-surface));
- border: 1px solid color-mix(in srgb, var(--success) 30%, var(--border));
- border-radius: var(--radius);
- padding: 16px;
- margin-bottom: 20px;
+ background: color-mix(in srgb, var(--success) 8%, var(--bg-surface));
+ border: 1px solid color-mix(in srgb, var(--success) 30%, var(--border));
+ border-radius: var(--radius);
+ padding: 16px;
+ margin-bottom: 20px;
}
.auth-key-issued-warning {
- font-size: 13px;
- font-weight: 600;
- margin-bottom: 12px;
- color: color-mix(in srgb, var(--success) 80%, var(--text));
+ font-size: 13px;
+ font-weight: 600;
+ margin-bottom: 12px;
+ color: color-mix(in srgb, var(--success) 80%, var(--text));
}
.auth-key-issued-value-row {
- display: flex;
- align-items: center;
- gap: 12px;
- margin-bottom: 12px;
- flex-wrap: wrap;
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ margin-bottom: 12px;
+ flex-wrap: wrap;
}
.auth-key-issued-token {
- flex: 1;
- min-width: 0;
- overflow-x: auto;
- padding: 8px 12px;
- background: var(--bg);
- border: 1px solid var(--border);
- border-radius: var(--radius);
- font-size: 13px;
- word-break: break-all;
+ flex: 1;
+ min-width: 0;
+ overflow-x: auto;
+ padding: 8px 12px;
+ background: var(--bg);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ font-size: 13px;
+ word-break: break-all;
}
.auth-key-redacted {
- color: var(--text-muted);
- font-size: 13px;
+ color: var(--text-muted);
+ font-size: 13px;
}
.auth-key-description {
- color: var(--text-muted);
- font-size: 13px;
- max-width: 220px;
+ color: var(--text-muted);
+ font-size: 13px;
+ max-width: 220px;
}
.auth-key-status-badge {
- display: inline-block;
- padding: 2px 10px;
- border-radius: 999px;
- font-size: 12px;
- font-weight: 600;
+ display: inline-block;
+ padding: 2px 10px;
+ border-radius: 999px;
+ font-size: 12px;
+ font-weight: 600;
}
.auth-key-status-active {
- background: color-mix(in srgb, var(--success) 12%, var(--bg));
- border: 1px solid color-mix(in srgb, var(--success) 30%, var(--border));
- color: var(--success);
+ background: color-mix(in srgb, var(--success) 12%, var(--bg));
+ border: 1px solid color-mix(in srgb, var(--success) 30%, var(--border));
+ color: var(--success);
}
.auth-key-status-inactive {
- background: color-mix(in srgb, var(--danger) 10%, var(--bg));
- border: 1px solid color-mix(in srgb, var(--danger) 30%, var(--border));
- color: var(--danger);
+ background: color-mix(in srgb, var(--danger) 10%, var(--bg));
+ border: 1px solid color-mix(in srgb, var(--danger) 30%, var(--border));
+ color: var(--danger);
}
.copy-feedback-btn {
- display: inline-flex;
- align-items: center;
- gap: 6px;
- transition: background-color 0.15s, border-color 0.15s, color 0.15s;
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ transition:
+ background-color 0.15s,
+ border-color 0.15s,
+ color 0.15s;
}
.copy-feedback-btn-copied {
- background: color-mix(in srgb, var(--success) 12%, var(--bg));
- border-color: color-mix(in srgb, var(--success) 40%, var(--border));
- color: var(--success);
+ background: color-mix(in srgb, var(--success) 12%, var(--bg));
+ border-color: color-mix(in srgb, var(--success) 40%, var(--border));
+ color: var(--success);
}
diff --git a/internal/admin/dashboard/static/js/dashboard.js b/internal/admin/dashboard/static/js/dashboard.js
index 8ec6a6ed..991eedd9 100644
--- a/internal/admin/dashboard/static/js/dashboard.js
+++ b/internal/admin/dashboard/static/js/dashboard.js
@@ -1,790 +1,946 @@
-// GOModel Dashboard — Alpine.js + Chart.js logic
+// GoModel Dashboard — Alpine.js + Chart.js logic
function dashboard() {
- const STALE_AUTH_RESPONSE = 'STALE_AUTH';
+ const STALE_AUTH_RESPONSE = "STALE_AUTH";
- function resolveModuleFactory(factory, windowName) {
- if (typeof factory === 'function') {
- return factory;
+ function resolveModuleFactory(factory, windowName) {
+ if (typeof factory === "function") {
+ return factory;
+ }
+ if (
+ typeof window !== "undefined" &&
+ typeof window[windowName] === "function"
+ ) {
+ return window[windowName];
+ }
+ return null;
+ }
+
+ const timezoneModuleFactory = resolveModuleFactory(
+ typeof dashboardTimezoneModule === "function"
+ ? dashboardTimezoneModule
+ : null,
+ "dashboardTimezoneModule",
+ );
+ const calendarModuleFactory = resolveModuleFactory(
+ typeof dashboardContributionCalendarModule === "function"
+ ? dashboardContributionCalendarModule
+ : null,
+ "dashboardContributionCalendarModule",
+ );
+ const iconsModuleFactory = resolveModuleFactory(
+ typeof dashboardIconsModule === "function" ? dashboardIconsModule : null,
+ "dashboardIconsModule",
+ );
+
+ const base = {
+ // State
+ page: "overview",
+ days: "30",
+ loading: false,
+ authError: false,
+ needsAuth: false,
+ apiKey: "",
+ authDialogOpen: false,
+ authRequestGeneration: 0,
+ theme: "system",
+ sidebarCollapsed: false,
+ settingsSubpage: "general",
+ runtimeRefreshLoading: false,
+ runtimeRefreshNotice: "",
+ runtimeRefreshError: "",
+ runtimeRefreshReport: null,
+
+ // Date picker
+ datePickerOpen: false,
+ selectedPreset: "30",
+ customStartDate: null,
+ customEndDate: null,
+ selectingDate: "start",
+ calendarMonth: new Date(),
+ cursorHint: { show: false, x: 0, y: 0 },
+
+ // Interval
+ interval: "daily",
+
+ // Data
+ summary: {
+ total_requests: 0,
+ total_input_tokens: 0,
+ total_output_tokens: 0,
+ total_tokens: 0,
+ total_input_cost: null,
+ total_output_cost: null,
+ total_cost: null,
+ },
+ daily: [],
+ cacheOverview: {
+ summary: {
+ total_hits: 0,
+ exact_hits: 0,
+ semantic_hits: 0,
+ total_input_tokens: 0,
+ total_output_tokens: 0,
+ total_tokens: 0,
+ total_saved_cost: null,
+ },
+ daily: [],
+ },
+ providerStatus: {
+ summary: {
+ total: 0,
+ healthy: 0,
+ degraded: 0,
+ unhealthy: 0,
+ overall_status: "degraded",
+ },
+ providers: [],
+ },
+ models: [],
+ modelsLoading: false,
+ categories: [],
+ activeCategory: "all",
+ hasCalendarModule: calendarModuleFactory !== null,
+
+ // Filters
+ modelFilter: "",
+
+ // Chart
+ chart: null,
+
+ // Usage page state
+ usageMode: "tokens",
+ modelUsageView: "chart",
+ userPathUsageView: "chart",
+ modelUsage: [],
+ userPathUsage: [],
+ usageLog: { entries: [], total: 0, limit: 50, offset: 0 },
+ usageLogSearch: "",
+ usageLogModel: "",
+ usageLogProvider: "",
+ usageLogUserPath: "",
+ usageBarChart: null,
+ usageUserPathChart: null,
+
+ // Audit page state
+ auditLog: { entries: [], total: 0, limit: 25, offset: 0 },
+ auditSearch: "",
+ auditMethod: "",
+ auditStatusCode: "",
+ auditStream: "",
+ auditFetchToken: 0,
+ auditExpandedEntries: {},
+
+ // Conversation drawer state
+ conversationOpen: false,
+ conversationLoading: false,
+ conversationError: "",
+ conversationAnchorID: "",
+ conversationEntries: [],
+ conversationMessages: [],
+ conversationRequestToken: 0,
+ conversationReturnFocusEl: null,
+ bodyPointerStart: null,
+
+ _parseRoute(pathname) {
+ const path = pathname.replace(/\/$/, "");
+ const rest = path.replace("/admin/dashboard", "").replace(/^\//, "");
+ const parts = rest.split("/");
+ let page = parts[0];
+ if (page === "workflows") {
+ page = "workflows";
+ }
+ if (page === "audit") {
+ page = "audit-logs";
+ }
+ const sub = parts[1] || null;
+ if (page === "settings" && sub === "guardrails") {
+ return { page: "guardrails", sub: null };
+ }
+ page = [
+ "overview",
+ "usage",
+ "models",
+ "workflows",
+ "audit-logs",
+ "guardrails",
+ "auth-keys",
+ "settings",
+ ].includes(page)
+ ? page
+ : "overview";
+ return { page, sub };
+ },
+
+ _normalizeSettingsSubpage(subpage) {
+ return "general";
+ },
+
+ _settingsPath(subpage) {
+ return "/admin/dashboard/settings";
+ },
+
+ _applyRoute(page, sub) {
+ this.page = page;
+ this.settingsSubpage =
+ page === "settings" ? this._normalizeSettingsSubpage(sub) : "general";
+
+ if (page === "usage" && sub === "costs") this.usageMode = "costs";
+ if (page === "usage" && sub !== "costs") this.usageMode = "tokens";
+ if (page === "audit-logs") this.fetchAuditLog(true);
+ if (page === "auth-keys" && typeof this.fetchAuthKeys === "function")
+ this.fetchAuthKeys();
+ if (
+ page === "workflows" &&
+ typeof this.fetchWorkflowsPage === "function"
+ ) {
+ this.fetchWorkflowsPage();
+ }
+ if (
+ page === "guardrails" &&
+ typeof this.fetchGuardrailsPage === "function"
+ ) {
+ this.fetchGuardrailsPage();
+ }
+ if (page === "settings") {
+ if (typeof this.ensureTimezoneOptions === "function") {
+ this.ensureTimezoneOptions();
}
- if (typeof window !== 'undefined' && typeof window[windowName] === 'function') {
- return window[windowName];
+ }
+ if (page === "overview") this.renderChart();
+ if (page === "usage") this.fetchUsagePage();
+ if (typeof this.renderIconsAfterUpdate === "function") {
+ this.renderIconsAfterUpdate();
+ }
+ },
+
+ init() {
+ if (typeof this.initTimeZoneState === "function") {
+ this.initTimeZoneState();
+ }
+ if (typeof this.initProviderStatusPreferences === "function") {
+ this.initProviderStatusPreferences();
+ }
+ // Older dashboard versions persisted this secret. Keep it memory-only.
+ localStorage.removeItem("gomodel_api_key");
+ this.theme = localStorage.getItem("gomodel_theme") || "system";
+ this.sidebarCollapsed =
+ localStorage.getItem("gomodel_sidebar_collapsed") === "true";
+ this.applyTheme();
+
+ const { page, sub } = this._parseRoute(window.location.pathname);
+ this._applyRoute(page, sub);
+
+ window.addEventListener("popstate", () => {
+ const { page: p, sub: s } = this._parseRoute(window.location.pathname);
+ this._applyRoute(p, s);
+ });
+
+ window
+ .matchMedia("(prefers-color-scheme: dark)")
+ .addEventListener("change", () => {
+ if (this.theme === "system") {
+ this.rerenderCharts();
+ }
+ });
+
+ this.fetchAll();
+ },
+
+ toggleSidebar() {
+ this.sidebarCollapsed = !this.sidebarCollapsed;
+ localStorage.setItem("gomodel_sidebar_collapsed", this.sidebarCollapsed);
+ setTimeout(() => this.renderChart(), 220);
+ },
+
+ navigate(page) {
+ if (page === "settings") {
+ this.navigateSettings("general");
+ return;
+ }
+
+ history.pushState(null, "", "/admin/dashboard/" + page);
+ this._applyRoute(page, null);
+ },
+
+ navigateSettings(subpage) {
+ const normalized = this._normalizeSettingsSubpage(subpage);
+ history.pushState(null, "", this._settingsPath(normalized));
+ this._applyRoute("settings", normalized);
+ },
+
+ guardrailsPageVisible() {
+ return typeof this.workflowRuntimeBooleanFlag === "function"
+ ? this.workflowRuntimeBooleanFlag("GUARDRAILS_ENABLED", true)
+ : true;
+ },
+
+ setTheme(t) {
+ this.theme = t;
+ localStorage.setItem("gomodel_theme", t);
+ this.applyTheme();
+ this.rerenderCharts();
+ if (typeof this.renderIconsAfterUpdate === "function") {
+ this.renderIconsAfterUpdate();
+ }
+ },
+
+ toggleTheme() {
+ const order = ["light", "system", "dark"];
+ this.setTheme(order[(order.indexOf(this.theme) + 1) % order.length]);
+ },
+
+ applyTheme() {
+ const root = document.documentElement;
+ if (this.theme === "system") {
+ root.removeAttribute("data-theme");
+ } else {
+ root.setAttribute("data-theme", this.theme);
+ }
+ },
+
+ cssVar(name) {
+ return getComputedStyle(document.documentElement)
+ .getPropertyValue(name)
+ .trim();
+ },
+
+ chartColors() {
+ return {
+ grid: this.cssVar("--chart-grid"),
+ text: this.cssVar("--chart-text"),
+ tooltipBg: this.cssVar("--chart-tooltip-bg"),
+ tooltipBorder: this.cssVar("--chart-tooltip-border"),
+ tooltipText: this.cssVar("--chart-tooltip-text"),
+ };
+ },
+
+ rerenderCharts() {
+ this.renderChart();
+ this.renderBarChart();
+ this.renderUserPathChart();
+ },
+
+ normalizeApiKey(value) {
+ const key = String(value || "").trim();
+ if (/^Bearer\s*$/i.test(key)) {
+ return "";
+ }
+ const match = key.match(/^Bearer\s+(.+)$/i);
+ return match ? match[1].trim() : key;
+ },
+
+ hasApiKey() {
+ return this.normalizeApiKey(this.apiKey) !== "";
+ },
+
+ saveApiKey() {
+ this.apiKey = this.normalizeApiKey(this.apiKey);
+ // Do not persist auth secrets in browser storage.
+ localStorage.removeItem("gomodel_api_key");
+ },
+
+ requestOptions(options) {
+ const request = { ...(options || {}) };
+ request.headers = this.headers();
+ request.authGeneration = this.authRequestGeneration;
+ return request;
+ },
+
+ isStaleAuthResponse(request) {
+ return (
+ request &&
+ typeof request.authGeneration === "number" &&
+ request.authGeneration < this.authRequestGeneration
+ );
+ },
+
+ openAuthDialog() {
+ this.authDialogOpen = true;
+ setTimeout(() => {
+ const input = document.getElementById("authDialogApiKey");
+ if (input && typeof input.focus === "function") {
+ input.focus();
}
+ }, 0);
+ },
+
+ closeAuthDialog() {
+ this.authDialogOpen = false;
+ },
+
+ submitApiKey() {
+ const apiKey = this.normalizeApiKey(this.apiKey);
+ if (!apiKey) {
+ this.apiKey = "";
+ this.authError = true;
+ this.needsAuth = true;
+ this.openAuthDialog();
+ return;
+ }
+ this.apiKey = apiKey;
+ this.saveApiKey();
+ this.authRequestGeneration++;
+ this.authError = false;
+ this.needsAuth = false;
+ this.closeAuthDialog();
+ this.fetchAll();
+ },
+
+ headers() {
+ const h = { "Content-Type": "application/json" };
+ const apiKey = this.normalizeApiKey(this.apiKey);
+ if (apiKey) {
+ h.Authorization = "Bearer " + apiKey;
+ }
+ if (typeof this.effectiveTimezone === "function") {
+ h["X-GoModel-Timezone"] = this.effectiveTimezone();
+ }
+ return h;
+ },
+
+ _startAbortableRequest(controllerKey) {
+ const current = this[controllerKey];
+ if (current && typeof current.abort === "function") {
+ current.abort();
+ }
+
+ if (typeof AbortController !== "function") {
+ this[controllerKey] = null;
return null;
- }
+ }
+
+ const controller = new AbortController();
+ this[controllerKey] = controller;
+ return controller;
+ },
+
+ _clearAbortableRequest(controllerKey, controller) {
+ if (this[controllerKey] === controller) {
+ this[controllerKey] = null;
+ }
+ },
+
+ _isCurrentAbortableRequest(controllerKey, controller) {
+ if (!controller) {
+ return true;
+ }
+ return this[controllerKey] === controller && !controller.signal.aborted;
+ },
+
+ _isAbortError(error) {
+ return (
+ Boolean(error) && (error.name === "AbortError" || error.code === 20)
+ );
+ },
+
+ staleAuthResponseResult() {
+ return STALE_AUTH_RESPONSE;
+ },
+
+ isStaleAuthFetchResult(result) {
+ return result === STALE_AUTH_RESPONSE;
+ },
+
+ dashboardDataFetches() {
+ const requests = [
+ this.fetchUsage(),
+ this.fetchModels(),
+ this.fetchCategories(),
+ ];
+ if (typeof this.fetchProviderStatus === "function") {
+ requests.push(this.fetchProviderStatus());
+ }
+ if (typeof this.fetchAliases === "function") {
+ requests.push(this.fetchAliases());
+ }
+ if (typeof this.fetchModelOverrides === "function") {
+ requests.push(this.fetchModelOverrides());
+ }
+ if (typeof this.fetchWorkflowsPage === "function") {
+ requests.push(this.fetchWorkflowsPage());
+ }
+ if (
+ this.hasCalendarModule &&
+ typeof this.fetchCalendarData === "function"
+ ) {
+ requests.push(this.fetchCalendarData());
+ }
+ return requests;
+ },
+
+ async fetchAll() {
+ this.loading = true;
+ this.authError = false;
+ this.needsAuth = false;
+ const requests = this.dashboardDataFetches();
+ await Promise.all(requests);
+ this.loading = false;
+ },
+
+ async refreshRuntime() {
+ if (this.runtimeRefreshLoading) {
+ return;
+ }
+ this.runtimeRefreshLoading = true;
+ this.runtimeRefreshNotice = "";
+ this.runtimeRefreshError = "";
+ this.runtimeRefreshReport = null;
+
+ try {
+ const request = this.requestOptions({
+ method: "POST",
+ });
+ const res = await fetch("/admin/api/v1/runtime/refresh", request);
+ const handled = this.handleFetchResponse(
+ res,
+ "runtime refresh",
+ request,
+ );
+ if (this.isStaleAuthFetchResult(handled)) {
+ return;
+ }
+ if (!handled) {
+ this.runtimeRefreshNotice = "Runtime refresh failed.";
+ this.runtimeRefreshError = this.runtimeRefreshNotice;
+ return;
+ }
- const timezoneModuleFactory = resolveModuleFactory(
- typeof dashboardTimezoneModule === 'function' ? dashboardTimezoneModule : null,
- 'dashboardTimezoneModule'
- );
- const calendarModuleFactory = resolveModuleFactory(
- typeof dashboardContributionCalendarModule === 'function' ? dashboardContributionCalendarModule : null,
- 'dashboardContributionCalendarModule'
- );
-
- const base = {
- // State
- page: 'overview',
- days: '30',
- loading: false,
- authError: false,
- needsAuth: false,
- apiKey: '',
- authDialogOpen: false,
- authRequestGeneration: 0,
- theme: 'system',
- sidebarCollapsed: false,
- settingsSubpage: 'general',
- runtimeRefreshLoading: false,
- runtimeRefreshNotice: '',
- runtimeRefreshError: '',
- runtimeRefreshReport: null,
-
- // Date picker
- datePickerOpen: false,
- selectedPreset: '30',
- customStartDate: null,
- customEndDate: null,
- selectingDate: 'start',
- calendarMonth: new Date(),
- cursorHint: { show: false, x: 0, y: 0 },
-
- // Interval
- interval: 'daily',
-
- // Data
- summary: { total_requests: 0, total_input_tokens: 0, total_output_tokens: 0, total_tokens: 0, total_input_cost: null, total_output_cost: null, total_cost: null },
- daily: [],
- cacheOverview: {
- summary: {
- total_hits: 0,
- exact_hits: 0,
- semantic_hits: 0,
- total_input_tokens: 0,
- total_output_tokens: 0,
- total_tokens: 0,
- total_saved_cost: null
- },
- daily: []
- },
- providerStatus: {
- summary: { total: 0, healthy: 0, degraded: 0, unhealthy: 0, overall_status: 'degraded' },
- providers: []
- },
- models: [],
- modelsLoading: false,
- categories: [],
- activeCategory: 'all',
- hasCalendarModule: calendarModuleFactory !== null,
-
- // Filters
- modelFilter: '',
-
- // Chart
- chart: null,
-
- // Usage page state
- usageMode: 'tokens',
- modelUsageView: 'chart',
- userPathUsageView: 'chart',
- modelUsage: [],
- userPathUsage: [],
- usageLog: { entries: [], total: 0, limit: 50, offset: 0 },
- usageLogSearch: '',
- usageLogModel: '',
- usageLogProvider: '',
- usageLogUserPath: '',
- usageBarChart: null,
- usageUserPathChart: null,
-
- // Audit page state
- auditLog: { entries: [], total: 0, limit: 25, offset: 0 },
- auditSearch: '',
- auditMethod: '',
- auditStatusCode: '',
- auditStream: '',
- auditFetchToken: 0,
- auditExpandedEntries: {},
-
- // Conversation drawer state
- conversationOpen: false,
- conversationLoading: false,
- conversationError: '',
- conversationAnchorID: '',
- conversationEntries: [],
- conversationMessages: [],
- conversationRequestToken: 0,
- conversationReturnFocusEl: null,
- bodyPointerStart: null,
-
- _parseRoute(pathname) {
- const path = pathname.replace(/\/$/, '');
- const rest = path.replace('/admin/dashboard', '').replace(/^\//, '');
- const parts = rest.split('/');
- let page = parts[0];
- if (page === 'workflows') {
- page = 'workflows';
- }
- if (page === 'audit') {
- page = 'audit-logs';
- }
- const sub = parts[1] || null;
- if (page === 'settings' && sub === 'guardrails') {
- return { page: 'guardrails', sub: null };
- }
- page = (['overview', 'usage', 'models', 'workflows', 'audit-logs', 'guardrails', 'auth-keys', 'settings'].includes(page)) ? page : 'overview';
- return { page, sub };
- },
-
- _normalizeSettingsSubpage(subpage) {
- return 'general';
- },
-
- _settingsPath(subpage) {
- return '/admin/dashboard/settings';
- },
-
- _applyRoute(page, sub) {
- this.page = page;
- this.settingsSubpage = page === 'settings'
- ? this._normalizeSettingsSubpage(sub)
- : 'general';
-
- if (page === 'usage' && sub === 'costs') this.usageMode = 'costs';
- if (page === 'usage' && sub !== 'costs') this.usageMode = 'tokens';
- if (page === 'audit-logs') this.fetchAuditLog(true);
- if (page === 'auth-keys' && typeof this.fetchAuthKeys === 'function') this.fetchAuthKeys();
- if (page === 'workflows' && typeof this.fetchWorkflowsPage === 'function') {
- this.fetchWorkflowsPage();
- }
- if (page === 'guardrails' && typeof this.fetchGuardrailsPage === 'function') {
- this.fetchGuardrailsPage();
- }
- if (page === 'settings') {
- if (typeof this.ensureTimezoneOptions === 'function') {
- this.ensureTimezoneOptions();
- }
- }
- if (page === 'overview') this.renderChart();
- if (page === 'usage') this.fetchUsagePage();
- },
-
- init() {
- if (typeof this.initTimeZoneState === 'function') {
- this.initTimeZoneState();
- }
- if (typeof this.initProviderStatusPreferences === 'function') {
- this.initProviderStatusPreferences();
- }
- this.apiKey = this.normalizeApiKey(localStorage.getItem('gomodel_api_key') || '');
- this.theme = localStorage.getItem('gomodel_theme') || 'system';
- this.sidebarCollapsed = localStorage.getItem('gomodel_sidebar_collapsed') === 'true';
- this.applyTheme();
-
- const { page, sub } = this._parseRoute(window.location.pathname);
- this._applyRoute(page, sub);
-
- window.addEventListener('popstate', () => {
- const { page: p, sub: s } = this._parseRoute(window.location.pathname);
- this._applyRoute(p, s);
- });
-
- window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
- if (this.theme === 'system') {
- this.rerenderCharts();
- }
- });
-
- this.fetchAll();
- },
-
- toggleSidebar() {
- this.sidebarCollapsed = !this.sidebarCollapsed;
- localStorage.setItem('gomodel_sidebar_collapsed', this.sidebarCollapsed);
- setTimeout(() => this.renderChart(), 220);
- },
-
- navigate(page) {
- if (page === 'settings') {
- this.navigateSettings('general');
- return;
- }
-
- history.pushState(null, '', '/admin/dashboard/' + page);
- this._applyRoute(page, null);
- },
-
- navigateSettings(subpage) {
- const normalized = this._normalizeSettingsSubpage(subpage);
- history.pushState(null, '', this._settingsPath(normalized));
- this._applyRoute('settings', normalized);
- },
-
- guardrailsPageVisible() {
- return typeof this.workflowRuntimeBooleanFlag === 'function'
- ? this.workflowRuntimeBooleanFlag('GUARDRAILS_ENABLED', true)
- : true;
- },
-
- setTheme(t) {
- this.theme = t;
- localStorage.setItem('gomodel_theme', t);
- this.applyTheme();
- this.rerenderCharts();
- },
-
- toggleTheme() {
- const order = ['light', 'system', 'dark'];
- this.setTheme(order[(order.indexOf(this.theme) + 1) % order.length]);
- },
-
- applyTheme() {
- const root = document.documentElement;
- if (this.theme === 'system') {
- root.removeAttribute('data-theme');
- } else {
- root.setAttribute('data-theme', this.theme);
- }
- },
-
- cssVar(name) {
- return getComputedStyle(document.documentElement).getPropertyValue(name).trim();
- },
-
- chartColors() {
- return {
- grid: this.cssVar('--chart-grid'),
- text: this.cssVar('--chart-text'),
- tooltipBg: this.cssVar('--chart-tooltip-bg'),
- tooltipBorder: this.cssVar('--chart-tooltip-border'),
- tooltipText: this.cssVar('--chart-tooltip-text')
- };
- },
-
- rerenderCharts() {
- this.renderChart();
- this.renderBarChart();
- this.renderUserPathChart();
- },
-
- normalizeApiKey(value) {
- const key = String(value || '').trim();
- if (/^Bearer\s*$/i.test(key)) {
- return '';
- }
- const match = key.match(/^Bearer\s+(.+)$/i);
- return match ? match[1].trim() : key;
- },
-
- hasApiKey() {
- return this.normalizeApiKey(this.apiKey) !== '';
- },
-
- saveApiKey() {
- this.apiKey = this.normalizeApiKey(this.apiKey);
- if (this.apiKey) {
- localStorage.setItem('gomodel_api_key', this.apiKey);
- } else {
- localStorage.removeItem('gomodel_api_key');
- }
- },
-
- requestOptions(options) {
- const request = { ...(options || {}) };
- request.headers = this.headers();
- request.authGeneration = this.authRequestGeneration;
- return request;
- },
-
- isStaleAuthResponse(request) {
- return request &&
- typeof request.authGeneration === 'number' &&
- request.authGeneration < this.authRequestGeneration;
- },
-
- openAuthDialog() {
- this.authDialogOpen = true;
- setTimeout(() => {
- const input = document.getElementById('authDialogApiKey');
- if (input && typeof input.focus === 'function') {
- input.focus();
- }
- }, 0);
- },
-
- closeAuthDialog() {
- this.authDialogOpen = false;
- },
-
- submitApiKey() {
- const apiKey = this.normalizeApiKey(this.apiKey);
- if (!apiKey) {
- this.apiKey = '';
- this.authError = true;
- this.needsAuth = true;
- this.openAuthDialog();
- return;
- }
- this.apiKey = apiKey;
- this.saveApiKey();
- this.authRequestGeneration++;
- this.authError = false;
- this.needsAuth = false;
- this.closeAuthDialog();
- this.fetchAll();
- },
-
- headers() {
- const h = { 'Content-Type': 'application/json' };
- const apiKey = this.normalizeApiKey(this.apiKey);
- if (apiKey) {
- h.Authorization = 'Bearer ' + apiKey;
- }
- if (typeof this.effectiveTimezone === 'function') {
- h['X-GoModel-Timezone'] = this.effectiveTimezone();
- }
- return h;
- },
-
- _startAbortableRequest(controllerKey) {
- const current = this[controllerKey];
- if (current && typeof current.abort === 'function') {
- current.abort();
- }
-
- if (typeof AbortController !== 'function') {
- this[controllerKey] = null;
- return null;
- }
-
- const controller = new AbortController();
- this[controllerKey] = controller;
- return controller;
- },
-
- _clearAbortableRequest(controllerKey, controller) {
- if (this[controllerKey] === controller) {
- this[controllerKey] = null;
- }
- },
-
- _isCurrentAbortableRequest(controllerKey, controller) {
- if (!controller) {
- return true;
- }
- return this[controllerKey] === controller && !controller.signal.aborted;
- },
-
- _isAbortError(error) {
- return Boolean(error) && (error.name === 'AbortError' || error.code === 20);
- },
-
- staleAuthResponseResult() {
- return STALE_AUTH_RESPONSE;
- },
-
- isStaleAuthFetchResult(result) {
- return result === STALE_AUTH_RESPONSE;
- },
-
- dashboardDataFetches() {
- const requests = [this.fetchUsage(), this.fetchModels(), this.fetchCategories()];
- if (typeof this.fetchProviderStatus === 'function') {
- requests.push(this.fetchProviderStatus());
- }
- if (typeof this.fetchAliases === 'function') {
- requests.push(this.fetchAliases());
- }
- if (typeof this.fetchModelOverrides === 'function') {
- requests.push(this.fetchModelOverrides());
- }
- if (typeof this.fetchWorkflowsPage === 'function') {
- requests.push(this.fetchWorkflowsPage());
- }
- if (this.hasCalendarModule && typeof this.fetchCalendarData === 'function') {
- requests.push(this.fetchCalendarData());
- }
- return requests;
- },
-
- async fetchAll() {
- this.loading = true;
- this.authError = false;
- this.needsAuth = false;
- const requests = this.dashboardDataFetches();
- await Promise.all(requests);
- this.loading = false;
- },
-
- async refreshRuntime() {
- if (this.runtimeRefreshLoading) {
- return;
- }
- this.runtimeRefreshLoading = true;
- this.runtimeRefreshNotice = '';
- this.runtimeRefreshError = '';
- this.runtimeRefreshReport = null;
-
- try {
- const request = this.requestOptions({
- method: 'POST',
- });
- const res = await fetch('/admin/api/v1/runtime/refresh', request);
- const handled = this.handleFetchResponse(res, 'runtime refresh', request);
- if (this.isStaleAuthFetchResult(handled)) {
- return;
- }
- if (!handled) {
- this.runtimeRefreshNotice = 'Runtime refresh failed.';
- this.runtimeRefreshError = this.runtimeRefreshNotice;
- return;
- }
-
- const payload = await res.json();
- this.runtimeRefreshReport = payload && typeof payload === 'object' ? payload : null;
- this.runtimeRefreshNotice = this.runtimeRefreshSummary();
- await this.refreshDashboardDataAfterRuntimeRefresh();
- } catch (e) {
- console.error('Failed to refresh runtime:', e);
- this.runtimeRefreshNotice = 'Runtime refresh failed.';
- this.runtimeRefreshError = this.runtimeRefreshNotice;
- } finally {
- this.runtimeRefreshLoading = false;
- }
- },
-
- async refreshDashboardDataAfterRuntimeRefresh() {
- const requests = this.dashboardDataFetches();
- if (this.page === 'audit-logs' && typeof this.fetchAuditLog === 'function') {
- requests.push(this.fetchAuditLog(true));
- }
- if (this.page === 'auth-keys' && typeof this.fetchAuthKeys === 'function') {
- requests.push(this.fetchAuthKeys());
- }
- if (this.page === 'guardrails' && typeof this.fetchGuardrailsPage === 'function') {
- requests.push(this.fetchGuardrailsPage());
- }
- if (this.page === 'usage' && typeof this.fetchUsagePage === 'function') {
- requests.push(this.fetchUsagePage());
- }
- await Promise.all(requests);
- },
-
- runtimeRefreshStatus() {
- const report = this.runtimeRefreshReport;
- return String((report && report.status) || 'ok').toLowerCase();
- },
-
- runtimeRefreshSummary() {
- const report = this.runtimeRefreshReport;
- if (!report || typeof report !== 'object') {
- return 'Runtime refresh completed.';
- }
- const modelCount = Number(report.model_count || 0);
- const providerCount = Number(report.provider_count || 0);
- const status = this.runtimeRefreshStatus();
- const prefix = status === 'ok'
- ? 'Runtime refreshed.'
- : status === 'partial'
- ? 'Runtime refresh completed with warnings.'
- : 'Runtime refresh failed.';
- return prefix + ' ' + modelCount + ' model' + (modelCount === 1 ? '' : 's') +
- ' across ' + providerCount + ' provider' + (providerCount === 1 ? '' : 's') + '.';
- },
-
- runtimeRefreshSucceeded() {
- return Boolean(this.runtimeRefreshReport) && this.runtimeRefreshStatus() === 'ok';
- },
-
- runtimeRefreshWarnings() {
- return Boolean(this.runtimeRefreshReport) && this.runtimeRefreshStatus() !== 'ok';
- },
-
- runtimeRefreshStepLabel(step) {
- const name = String(step && step.name || '').replace(/_/g, ' ');
- const status = String(step && step.status || '').trim();
- const detail = String((step && (step.error || step.message)) || '').trim();
- if (!name) return detail || status || '';
- if (!detail) return name + ': ' + status;
- return name + ': ' + status + ' - ' + detail;
- },
-
- handleFetchResponse(res, label, request) {
- if (res.status === 401) {
- if (this.isStaleAuthResponse(request)) {
- return STALE_AUTH_RESPONSE;
- }
- this.authError = true;
- this.needsAuth = true;
- this.openAuthDialog();
- return false;
- }
- if (!res.ok) {
- console.error(`Failed to fetch ${label}: ${res.status} ${res.statusText}`);
- return false;
- }
- return true;
- },
-
- _formatDate(date) {
- if (!date) return '';
- if (typeof date === 'string') return date;
- return date.getUTCFullYear() + '-' +
- String(date.getUTCMonth() + 1).padStart(2, '0') + '-' +
- String(date.getUTCDate()).padStart(2, '0');
- },
-
- async fetchModels() {
- const controller = this._startAbortableRequest('_modelsFetchController');
- const isCurrentRequest = () => this._isCurrentAbortableRequest('_modelsFetchController', controller);
- const options = this.requestOptions();
- if (controller) {
- options.signal = controller.signal;
- }
-
- this.modelsLoading = true;
- try {
- let url = '/admin/api/v1/models';
- if (this.activeCategory && this.activeCategory !== 'all') {
- url += '?category=' + encodeURIComponent(this.activeCategory);
- }
- const res = await fetch(url, options);
- if (!isCurrentRequest()) {
- return;
- }
- const handled = this.handleFetchResponse(res, 'models', options);
- if (this.isStaleAuthFetchResult(handled)) {
- return;
- }
- if (!handled) {
- if (!isCurrentRequest()) {
- return;
- }
- this.models = [];
- if (typeof this.syncDisplayModels === 'function') this.syncDisplayModels();
- return;
- }
- const payload = await res.json();
- if (!isCurrentRequest()) {
- return;
- }
- this.models = payload;
- if (typeof this.syncDisplayModels === 'function') this.syncDisplayModels();
- } catch (e) {
- if (this._isAbortError(e)) {
- return;
- }
- if (!isCurrentRequest()) {
- return;
- }
- console.error('Failed to fetch models:', e);
- this.models = [];
- if (typeof this.syncDisplayModels === 'function') this.syncDisplayModels();
- } finally {
- const currentRequest = isCurrentRequest();
- this._clearAbortableRequest('_modelsFetchController', controller);
- if (currentRequest) {
- this.modelsLoading = false;
- }
- }
- },
-
- async fetchCategories() {
- const request = this.requestOptions();
- try {
- const res = await fetch('/admin/api/v1/models/categories', request);
- const handled = this.handleFetchResponse(res, 'categories', request);
- if (this.isStaleAuthFetchResult(handled)) {
- return;
- }
- if (!handled) {
- this.categories = [];
- return;
- }
- this.categories = await res.json();
- } catch (e) {
- console.error('Failed to fetch categories:', e);
- this.categories = [];
- }
- },
-
- selectCategory(cat) {
- this.activeCategory = cat;
- this.modelFilter = '';
- this.fetchModels();
- },
-
- get filteredModels() {
- if (!this.modelFilter) return this.models;
- const f = this.modelFilter.toLowerCase();
- return this.models.filter((m) =>
- (m.model?.id ?? '').toLowerCase().includes(f) ||
- (m.provider_name ?? '').toLowerCase().includes(f) ||
- (m.provider_type ?? '').toLowerCase().includes(f) ||
- (m.selector ?? '').toLowerCase().includes(f) ||
- (m.model?.owned_by ?? '').toLowerCase().includes(f) ||
- (m.model?.metadata?.modes ?? []).join(',').toLowerCase().includes(f) ||
- (m.model?.metadata?.categories ?? []).join(',').toLowerCase().includes(f)
- );
- },
-
- providerTypeValue(value) {
- return String(value && value.provider || '').trim();
- },
-
- providerDisplayValue(value) {
- const providerName = String(value && value.provider_name || '').trim();
- if (providerName) return providerName;
- return this.providerTypeValue(value);
- },
-
- qualifiedModelDisplay(value) {
- return this.qualifiedModelValueDisplay(value, value && value.model);
- },
-
- qualifiedModelValueDisplay(value, modelValue) {
- const model = String(modelValue || '').trim();
- if (!model) return '-';
- const provider = this.providerDisplayValue(value);
- if (!provider || model === provider || model.startsWith(provider + '/')) return model;
- return provider + '/' + model;
- },
-
- qualifiedResolvedModelDisplay(value) {
- return this.qualifiedModelValueDisplay(value, value && value.resolved_model);
- },
-
- formatNumber(n) {
- if (n == null || n === undefined) return '-';
- return n.toLocaleString();
- },
-
- formatCost(v) {
- if (v == null || v === undefined) return 'N/A';
- return '$' + v.toFixed(4);
- },
-
- formatCostTooltip(entry) {
- const lines = [];
- lines.push('Input: ' + this.formatCost(entry.input_cost));
- lines.push('Output: ' + this.formatCost(entry.output_cost));
- if (entry.raw_data) {
- lines.push('');
- for (const [key, value] of Object.entries(entry.raw_data)) {
- const label = key.replace(/_/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
- lines.push(label + ': ' + this.formatNumber(value));
- }
- }
- return lines.join('\n');
- },
-
- formatPrice(v) {
- if (v == null || v === undefined) return '\u2014';
- return '$' + v.toFixed(2);
- },
-
- formatPriceFine(v) {
- if (v == null || v === undefined) return '\u2014';
- if (v < 0.01) return '$' + v.toFixed(6);
- return '$' + v.toFixed(4);
- },
-
- categoryCount(cat) {
- const entry = this.categories.find((c) => c.category === cat);
- return entry ? entry.count : 0;
- },
-
- formatTokensShort(n) {
- if (n >= 1000000) return (n / 1000000).toFixed(1) + 'M';
- if (n >= 1000) return (n / 1000).toFixed(1) + 'K';
- return String(n);
- },
-
- formatTimestamp(ts) {
- if (typeof this.formatTimestampInEffectiveTimeZone === 'function') {
- return this.formatTimestampInEffectiveTimeZone(ts);
- }
- if (!ts) return '-';
- const d = new Date(ts);
- if (Number.isNaN(d.getTime())) return '-';
- return d.getFullYear() + '-' +
- String(d.getMonth() + 1).padStart(2, '0') + '-' +
- String(d.getDate()).padStart(2, '0') + ' ' +
- String(d.getHours()).padStart(2, '0') + ':' +
- String(d.getMinutes()).padStart(2, '0') + ':' +
- String(d.getSeconds()).padStart(2, '0');
- },
-
- formatDateUTC(ts) {
- if (!ts) return '-';
- const d = new Date(ts);
- if (Number.isNaN(d.getTime())) return '-';
- return d.getUTCFullYear() + '-' +
- String(d.getUTCMonth() + 1).padStart(2, '0') + '-' +
- String(d.getUTCDate()).padStart(2, '0');
- },
-
- formatTimestampUTC(ts) {
- if (!ts) return '-';
- const d = new Date(ts);
- if (Number.isNaN(d.getTime())) return '-';
- return d.getUTCFullYear() + '-' +
- String(d.getUTCMonth() + 1).padStart(2, '0') + '-' +
- String(d.getUTCDate()).padStart(2, '0') + ' ' +
- String(d.getUTCHours()).padStart(2, '0') + ':' +
- String(d.getUTCMinutes()).padStart(2, '0') + ':' +
- String(d.getUTCSeconds()).padStart(2, '0') + ' UTC';
+ const payload = await res.json();
+ this.runtimeRefreshReport =
+ payload && typeof payload === "object" ? payload : null;
+ this.runtimeRefreshNotice = this.runtimeRefreshSummary();
+ await this.refreshDashboardDataAfterRuntimeRefresh();
+ } catch (e) {
+ console.error("Failed to refresh runtime:", e);
+ this.runtimeRefreshNotice = "Runtime refresh failed.";
+ this.runtimeRefreshError = this.runtimeRefreshNotice;
+ } finally {
+ this.runtimeRefreshLoading = false;
+ }
+ },
+
+ async refreshDashboardDataAfterRuntimeRefresh() {
+ const requests = this.dashboardDataFetches();
+ if (
+ this.page === "audit-logs" &&
+ typeof this.fetchAuditLog === "function"
+ ) {
+ requests.push(this.fetchAuditLog(true));
+ }
+ if (
+ this.page === "auth-keys" &&
+ typeof this.fetchAuthKeys === "function"
+ ) {
+ requests.push(this.fetchAuthKeys());
+ }
+ if (
+ this.page === "guardrails" &&
+ typeof this.fetchGuardrailsPage === "function"
+ ) {
+ requests.push(this.fetchGuardrailsPage());
+ }
+ if (this.page === "usage" && typeof this.fetchUsagePage === "function") {
+ requests.push(this.fetchUsagePage());
+ }
+ await Promise.all(requests);
+ },
+
+ runtimeRefreshStatus() {
+ const report = this.runtimeRefreshReport;
+ return String((report && report.status) || "ok").toLowerCase();
+ },
+
+ runtimeRefreshSummary() {
+ const report = this.runtimeRefreshReport;
+ if (!report || typeof report !== "object") {
+ return "Runtime refresh completed.";
+ }
+ const modelCount = Number(report.model_count || 0);
+ const providerCount = Number(report.provider_count || 0);
+ const status = this.runtimeRefreshStatus();
+ const prefix =
+ status === "ok"
+ ? "Runtime refreshed."
+ : status === "partial"
+ ? "Runtime refresh completed with warnings."
+ : "Runtime refresh failed.";
+ return (
+ prefix +
+ " " +
+ modelCount +
+ " model" +
+ (modelCount === 1 ? "" : "s") +
+ " across " +
+ providerCount +
+ " provider" +
+ (providerCount === 1 ? "" : "s") +
+ "."
+ );
+ },
+
+ runtimeRefreshSucceeded() {
+ return (
+ Boolean(this.runtimeRefreshReport) &&
+ this.runtimeRefreshStatus() === "ok"
+ );
+ },
+
+ runtimeRefreshWarnings() {
+ return (
+ Boolean(this.runtimeRefreshReport) &&
+ this.runtimeRefreshStatus() !== "ok"
+ );
+ },
+
+ runtimeRefreshStepLabel(step) {
+ const name = String((step && step.name) || "").replace(/_/g, " ");
+ const status = String((step && step.status) || "").trim();
+ const detail = String(
+ (step && (step.error || step.message)) || "",
+ ).trim();
+ if (!name) return detail || status || "";
+ if (!detail) return name + ": " + status;
+ return name + ": " + status + " - " + detail;
+ },
+
+ handleFetchResponse(res, label, request) {
+ if (res.status === 401) {
+ if (this.isStaleAuthResponse(request)) {
+ return STALE_AUTH_RESPONSE;
+ }
+ this.authError = true;
+ this.needsAuth = true;
+ this.openAuthDialog();
+ return false;
+ }
+ if (!res.ok) {
+ console.error(
+ `Failed to fetch ${label}: ${res.status} ${res.statusText}`,
+ );
+ return false;
+ }
+ return true;
+ },
+
+ _formatDate(date) {
+ if (!date) return "";
+ if (typeof date === "string") return date;
+ return (
+ date.getUTCFullYear() +
+ "-" +
+ String(date.getUTCMonth() + 1).padStart(2, "0") +
+ "-" +
+ String(date.getUTCDate()).padStart(2, "0")
+ );
+ },
+
+ async fetchModels() {
+ const controller = this._startAbortableRequest("_modelsFetchController");
+ const isCurrentRequest = () =>
+ this._isCurrentAbortableRequest("_modelsFetchController", controller);
+ const options = this.requestOptions();
+ if (controller) {
+ options.signal = controller.signal;
+ }
+
+ this.modelsLoading = true;
+ try {
+ let url = "/admin/api/v1/models";
+ if (this.activeCategory && this.activeCategory !== "all") {
+ url += "?category=" + encodeURIComponent(this.activeCategory);
+ }
+ const res = await fetch(url, options);
+ if (!isCurrentRequest()) {
+ return;
+ }
+ const handled = this.handleFetchResponse(res, "models", options);
+ if (this.isStaleAuthFetchResult(handled)) {
+ return;
+ }
+ if (!handled) {
+ if (!isCurrentRequest()) {
+ return;
+ }
+ this.models = [];
+ if (typeof this.syncDisplayModels === "function")
+ this.syncDisplayModels();
+ return;
+ }
+ const payload = await res.json();
+ if (!isCurrentRequest()) {
+ return;
+ }
+ this.models = payload;
+ if (typeof this.syncDisplayModels === "function")
+ this.syncDisplayModels();
+ } catch (e) {
+ if (this._isAbortError(e)) {
+ return;
+ }
+ if (!isCurrentRequest()) {
+ return;
+ }
+ console.error("Failed to fetch models:", e);
+ this.models = [];
+ if (typeof this.syncDisplayModels === "function")
+ this.syncDisplayModels();
+ } finally {
+ const currentRequest = isCurrentRequest();
+ this._clearAbortableRequest("_modelsFetchController", controller);
+ if (currentRequest) {
+ this.modelsLoading = false;
+ }
+ }
+ },
+
+ async fetchCategories() {
+ const request = this.requestOptions();
+ try {
+ const res = await fetch("/admin/api/v1/models/categories", request);
+ const handled = this.handleFetchResponse(res, "categories", request);
+ if (this.isStaleAuthFetchResult(handled)) {
+ return;
+ }
+ if (!handled) {
+ this.categories = [];
+ return;
+ }
+ this.categories = await res.json();
+ } catch (e) {
+ console.error("Failed to fetch categories:", e);
+ this.categories = [];
+ }
+ },
+
+ selectCategory(cat) {
+ this.activeCategory = cat;
+ this.modelFilter = "";
+ this.fetchModels();
+ },
+
+ get filteredModels() {
+ if (!this.modelFilter) return this.models;
+ const f = this.modelFilter.toLowerCase();
+ return this.models.filter(
+ (m) =>
+ (m.model?.id ?? "").toLowerCase().includes(f) ||
+ (m.provider_name ?? "").toLowerCase().includes(f) ||
+ (m.provider_type ?? "").toLowerCase().includes(f) ||
+ (m.selector ?? "").toLowerCase().includes(f) ||
+ (m.model?.owned_by ?? "").toLowerCase().includes(f) ||
+ (m.model?.metadata?.modes ?? [])
+ .join(",")
+ .toLowerCase()
+ .includes(f) ||
+ (m.model?.metadata?.categories ?? [])
+ .join(",")
+ .toLowerCase()
+ .includes(f),
+ );
+ },
+
+ providerTypeValue(value) {
+ return String((value && value.provider) || "").trim();
+ },
+
+ providerDisplayValue(value) {
+ const providerName = String((value && value.provider_name) || "").trim();
+ if (providerName) return providerName;
+ return this.providerTypeValue(value);
+ },
+
+ qualifiedModelDisplay(value) {
+ return this.qualifiedModelValueDisplay(value, value && value.model);
+ },
+
+ qualifiedModelValueDisplay(value, modelValue) {
+ const model = String(modelValue || "").trim();
+ if (!model) return "-";
+ const provider = this.providerDisplayValue(value);
+ if (!provider || model === provider || model.startsWith(provider + "/"))
+ return model;
+ return provider + "/" + model;
+ },
+
+ qualifiedResolvedModelDisplay(value) {
+ return this.qualifiedModelValueDisplay(
+ value,
+ value && value.resolved_model,
+ );
+ },
+
+ formatNumber(n) {
+ if (n == null || n === undefined) return "-";
+ return n.toLocaleString();
+ },
+
+ formatCost(v) {
+ if (v == null || v === undefined) return "N/A";
+ return "$" + v.toFixed(4);
+ },
+
+ formatCostTooltip(entry) {
+ const lines = [];
+ lines.push("Input: " + this.formatCost(entry.input_cost));
+ lines.push("Output: " + this.formatCost(entry.output_cost));
+ if (entry.raw_data) {
+ lines.push("");
+ for (const [key, value] of Object.entries(entry.raw_data)) {
+ const label = key
+ .replace(/_/g, " ")
+ .replace(/\b\w/g, (c) => c.toUpperCase());
+ lines.push(label + ": " + this.formatNumber(value));
}
- };
-
- const moduleFactories = [
- timezoneModuleFactory,
- resolveModuleFactory(
- typeof dashboardDatePickerModule === 'function' ? dashboardDatePickerModule : null,
- 'dashboardDatePickerModule'
- ),
- resolveModuleFactory(
- typeof dashboardProvidersModule === 'function' ? dashboardProvidersModule : null,
- 'dashboardProvidersModule'
- ),
- resolveModuleFactory(
- typeof dashboardUsageModule === 'function' ? dashboardUsageModule : null,
- 'dashboardUsageModule'
- ),
- resolveModuleFactory(
- typeof dashboardAuditListModule === 'function' ? dashboardAuditListModule : null,
- 'dashboardAuditListModule'
- ),
- resolveModuleFactory(
- typeof dashboardAliasesModule === 'function' ? dashboardAliasesModule : null,
- 'dashboardAliasesModule'
- ),
- resolveModuleFactory(
- typeof dashboardAuthKeysModule === 'function' ? dashboardAuthKeysModule : null,
- 'dashboardAuthKeysModule'
- ),
- resolveModuleFactory(
- typeof dashboardGuardrailsModule === 'function' ? dashboardGuardrailsModule : null,
- 'dashboardGuardrailsModule'
- ),
- resolveModuleFactory(
- typeof dashboardWorkflowsModule === 'function' ? dashboardWorkflowsModule : null,
- 'dashboardWorkflowsModule'
- ),
- resolveModuleFactory(
- typeof dashboardConversationDrawerModule === 'function' ? dashboardConversationDrawerModule : null,
- 'dashboardConversationDrawerModule'
- ),
- calendarModuleFactory,
- resolveModuleFactory(
- typeof dashboardChartsModule === 'function' ? dashboardChartsModule : null,
- 'dashboardChartsModule'
- )
- ];
-
- return moduleFactories.reduce((app, factory) => {
- if (!factory) return app;
- Object.defineProperties(app, Object.getOwnPropertyDescriptors(factory()));
- return app;
- }, base);
+ }
+ return lines.join("\n");
+ },
+
+ formatPrice(v) {
+ if (v == null || v === undefined) return "\u2014";
+ return "$" + v.toFixed(2);
+ },
+
+ formatPriceFine(v) {
+ if (v == null || v === undefined) return "\u2014";
+ if (v < 0.01) return "$" + v.toFixed(6);
+ return "$" + v.toFixed(4);
+ },
+
+ categoryCount(cat) {
+ const entry = this.categories.find((c) => c.category === cat);
+ return entry ? entry.count : 0;
+ },
+
+ formatTokensShort(n) {
+ if (n >= 1000000) return (n / 1000000).toFixed(1) + "M";
+ if (n >= 1000) return (n / 1000).toFixed(1) + "K";
+ return String(n);
+ },
+
+ formatTimestamp(ts) {
+ if (typeof this.formatTimestampInEffectiveTimeZone === "function") {
+ return this.formatTimestampInEffectiveTimeZone(ts);
+ }
+ if (!ts) return "-";
+ const d = new Date(ts);
+ if (Number.isNaN(d.getTime())) return "-";
+ return (
+ d.getFullYear() +
+ "-" +
+ String(d.getMonth() + 1).padStart(2, "0") +
+ "-" +
+ String(d.getDate()).padStart(2, "0") +
+ " " +
+ String(d.getHours()).padStart(2, "0") +
+ ":" +
+ String(d.getMinutes()).padStart(2, "0") +
+ ":" +
+ String(d.getSeconds()).padStart(2, "0")
+ );
+ },
+
+ formatDateUTC(ts) {
+ if (!ts) return "-";
+ const d = new Date(ts);
+ if (Number.isNaN(d.getTime())) return "-";
+ return (
+ d.getUTCFullYear() +
+ "-" +
+ String(d.getUTCMonth() + 1).padStart(2, "0") +
+ "-" +
+ String(d.getUTCDate()).padStart(2, "0")
+ );
+ },
+
+ formatTimestampUTC(ts) {
+ if (!ts) return "-";
+ const d = new Date(ts);
+ if (Number.isNaN(d.getTime())) return "-";
+ return (
+ d.getUTCFullYear() +
+ "-" +
+ String(d.getUTCMonth() + 1).padStart(2, "0") +
+ "-" +
+ String(d.getUTCDate()).padStart(2, "0") +
+ " " +
+ String(d.getUTCHours()).padStart(2, "0") +
+ ":" +
+ String(d.getUTCMinutes()).padStart(2, "0") +
+ ":" +
+ String(d.getUTCSeconds()).padStart(2, "0") +
+ " UTC"
+ );
+ },
+ };
+
+ const moduleFactories = [
+ iconsModuleFactory,
+ timezoneModuleFactory,
+ resolveModuleFactory(
+ typeof dashboardDatePickerModule === "function"
+ ? dashboardDatePickerModule
+ : null,
+ "dashboardDatePickerModule",
+ ),
+ resolveModuleFactory(
+ typeof dashboardProvidersModule === "function"
+ ? dashboardProvidersModule
+ : null,
+ "dashboardProvidersModule",
+ ),
+ resolveModuleFactory(
+ typeof dashboardUsageModule === "function" ? dashboardUsageModule : null,
+ "dashboardUsageModule",
+ ),
+ resolveModuleFactory(
+ typeof dashboardAuditListModule === "function"
+ ? dashboardAuditListModule
+ : null,
+ "dashboardAuditListModule",
+ ),
+ resolveModuleFactory(
+ typeof dashboardAliasesModule === "function"
+ ? dashboardAliasesModule
+ : null,
+ "dashboardAliasesModule",
+ ),
+ resolveModuleFactory(
+ typeof dashboardAuthKeysModule === "function"
+ ? dashboardAuthKeysModule
+ : null,
+ "dashboardAuthKeysModule",
+ ),
+ resolveModuleFactory(
+ typeof dashboardGuardrailsModule === "function"
+ ? dashboardGuardrailsModule
+ : null,
+ "dashboardGuardrailsModule",
+ ),
+ resolveModuleFactory(
+ typeof dashboardWorkflowsModule === "function"
+ ? dashboardWorkflowsModule
+ : null,
+ "dashboardWorkflowsModule",
+ ),
+ resolveModuleFactory(
+ typeof dashboardConversationDrawerModule === "function"
+ ? dashboardConversationDrawerModule
+ : null,
+ "dashboardConversationDrawerModule",
+ ),
+ calendarModuleFactory,
+ resolveModuleFactory(
+ typeof dashboardChartsModule === "function"
+ ? dashboardChartsModule
+ : null,
+ "dashboardChartsModule",
+ ),
+ ];
+
+ return moduleFactories.reduce((app, factory) => {
+ if (!factory) return app;
+ Object.defineProperties(app, Object.getOwnPropertyDescriptors(factory()));
+ return app;
+ }, base);
}
diff --git a/internal/admin/dashboard/static/js/modules/dashboard-display.test.js b/internal/admin/dashboard/static/js/modules/dashboard-display.test.js
index 0c5c931b..7bc47862 100644
--- a/internal/admin/dashboard/static/js/modules/dashboard-display.test.js
+++ b/internal/admin/dashboard/static/js/modules/dashboard-display.test.js
@@ -205,7 +205,29 @@ test('stale unauthorized category responses preserve existing categories', async
assert.equal(app.authDialogOpen, false);
});
-test('submitApiKey trims bearer input and stores the key before refreshing dashboard data', () => {
+test('init clears legacy stored API key instead of restoring it', () => {
+ const storage = createLocalStorage({
+ gomodel_api_key: 'existing-token',
+ gomodel_theme: 'dark'
+ });
+ const app = loadDashboardApp({
+ window: {
+ localStorage: storage,
+ location: { pathname: '/admin/dashboard/overview' }
+ }
+ });
+
+ app.fetchAll = () => {};
+ app.renderChart = () => {};
+
+ app.init();
+
+ assert.equal(app.apiKey, '');
+ assert.equal(storage.getItem('gomodel_api_key'), null);
+ assert.equal(app.theme, 'dark');
+});
+
+test('submitApiKey trims bearer input and keeps the key in memory before refreshing dashboard data', () => {
const storage = createLocalStorage();
const app = loadDashboardApp({
window: { localStorage: storage }
@@ -221,7 +243,7 @@ test('submitApiKey trims bearer input and stores the key before refreshing dashb
assert.equal(app.apiKey, 'secret-token');
assert.equal(app.authRequestGeneration, 1);
- assert.equal(storage.getItem('gomodel_api_key'), 'secret-token');
+ assert.equal(storage.getItem('gomodel_api_key'), null);
assert.equal(app.authDialogOpen, false);
assert.equal(fetches, 1);
});
@@ -245,7 +267,7 @@ test('hasApiKey reflects trimmed bearer input for the sidebar change action', ()
});
test('submitApiKey rejects blank input without unlocking dashboard', () => {
- const storage = createLocalStorage({ gomodel_api_key: 'existing-token' });
+ const storage = createLocalStorage();
const app = loadDashboardApp({
window: { localStorage: storage }
});
@@ -260,7 +282,7 @@ test('submitApiKey rejects blank input without unlocking dashboard', () => {
assert.equal(app.apiKey, '');
assert.equal(app.authRequestGeneration, 0);
- assert.equal(storage.getItem('gomodel_api_key'), 'existing-token');
+ assert.equal(storage.getItem('gomodel_api_key'), null);
assert.equal(app.authError, true);
assert.equal(app.needsAuth, true);
assert.equal(app.authDialogOpen, true);
@@ -268,7 +290,7 @@ test('submitApiKey rejects blank input without unlocking dashboard', () => {
});
test('submitApiKey and headers reject a bare bearer scheme without sending authorization', () => {
- const storage = createLocalStorage({ gomodel_api_key: 'existing-token' });
+ const storage = createLocalStorage();
const app = loadDashboardApp({
window: { localStorage: storage }
});
@@ -283,7 +305,7 @@ test('submitApiKey and headers reject a bare bearer scheme without sending autho
assert.equal(app.apiKey, '');
assert.equal(app.authRequestGeneration, 0);
- assert.equal(storage.getItem('gomodel_api_key'), 'existing-token');
+ assert.equal(storage.getItem('gomodel_api_key'), null);
assert.equal(app.authError, true);
assert.equal(app.needsAuth, true);
assert.equal(app.authDialogOpen, true);
diff --git a/internal/admin/dashboard/static/js/modules/dashboard-layout.test.js b/internal/admin/dashboard/static/js/modules/dashboard-layout.test.js
index 57f8dd46..002a765f 100644
--- a/internal/admin/dashboard/static/js/modules/dashboard-layout.test.js
+++ b/internal/admin/dashboard/static/js/modules/dashboard-layout.test.js
@@ -7,6 +7,13 @@ function readFixture(relativePath) {
return fs.readFileSync(path.join(__dirname, relativePath), 'utf8');
}
+function readDashboardShellTemplate() {
+ const layout = readFixture('../../../templates/layout.html');
+ const sidebar = readFixture('../../../templates/sidebar.html');
+
+ return layout.replace('{{template "sidebar" .}}', sidebar);
+}
+
function readCSSRule(source, selector) {
const escapedSelector = selector.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const match = source.match(new RegExp(`${escapedSelector}\\s*\\{([\\s\\S]*?)\\s*\\}`, 'm'));
@@ -24,9 +31,10 @@ test('readCSSRule matches rules with CRLF endings and indented closing braces',
});
test('sidebar and main content share the flex layout without manual content offsets', () => {
- const template = readFixture('../../../templates/layout.html');
+ const template = readDashboardShellTemplate();
const css = readFixture('../../css/dashboard.css');
+ assert.match(readFixture('../../../templates/layout.html'), /{{template "sidebar" \.}}/);
assert.match(template, /