Any model can name another model as its backup. The gateway tries the
backup when the model is offline, rate limited, or its upstream fails
before the client has received anything. The response reports the
requested model, so the client sees no switch. The request log records
each failed attempt and the address that served the request, and the
cost uses the backup's prices.
InfluxDB 3 creates a column on its first write. A dashboard query that
names ttft_ms or error failed until a streamed or failed request had
been exported. Write 0 and empty strings instead of omitting the field.
Add an optional exporter that writes one point per proxied request to
the InfluxDB 3 write endpoint. Points are batched in memory and flushed
by size or interval. The exporter is off until GATEWAY_INFLUX_URL is set.
The sync took meta.n_ctx_train from the model listing, which is the
training context, not the -c the server runs with. The llama.cpp provider
now asks the server for the live value: /props on a single server, the
router's /models status and /props?model=, and /upstream/<model>/props for
models llama-swap has loaded. Stopped models are never probed, because
that would start them.
Admins can also pin the context window per model from the Models page.
The override wins over the discovered value and survives syncs.
The go:embed pattern needs at least one file under internal/web/dist.
The placeholder was ignored instead of tracked, so CI failed at go vet
before the dashboard build step ran.
Add a golangci-lint config and fix its findings: the OIDC state cookie
gets the Secure flag on https deployments, and pool sizes are clamped
before the int32 conversion.
Add a RequestLogSink interface so the handlers can be tested without
Postgres. The tests run the image, chat, stream, embeddings and model
routes end to end against a fake upstream.
Translate OpenAI image requests to OpenRouter chat completions with image
output and back, as the LiteLLM proxy does. Only models whose provider
reports image output accept the call; the catalog sync now stores the
reported output modalities in the capabilities column.
Model catalog is a table keyed by the exact provider/slug address with
a copy button. The chat model picker filters by address and name.
API Keys shows the base URL and a curl example, also right after a
key is created. /v1/models entries gain created, display_name and
context_length.
Shared tokens and components, responsive layout, real health badge,
request volume chart with hover, log detail with errors and cost,
provider enable and edit, override clearing, OIDC re-login that
returns to the current page.
Record upstream and gateway errors in request logs, including stream
interruptions. Translate Anthropic streamed tool calls, count cache
creation tokens, and drop top_p when temperature is set. Coalesce the
stats summary so an empty window no longer returns 500. Validate
provider and setting writes, allow clearing model overrides, add
provider update endpoint, and purge logs at startup.
extractDelta and extractCompletion read only reasoning_content, so
reasoning from OpenRouter (reasoning, reasoning_details) and Anthropic
(thinking_delta blocks) was dropped from logged completions. The
streaming path now accumulates reasoning and content separately and
wraps reasoning in think tags, matching the non-streaming format.
The dashboard Test Chat page and the Anthropic stream/translate paths
also recognize the OpenRouter and Anthropic reasoning fields.
OpenWebUI was failing with 'NoneType' errors because it expects a
'created' field on all chat and embedding responses. OpenAI always
includes this unix-timestamp field in both streaming chunks and
non-streaming responses.
- Inject 'created' into non-streaming responses if the upstream
didn't provide one (chat + embeddings)
- Inject 'created' into every streaming SSE chunk if missing
- Add 'created' to Anthropic synthetic streaming chunks and
non-streaming translated responses
Cost was always estimated locally from catalog per-token prices, which
drifts from what OpenRouter actually charges (dynamic routing, BYOK,
promos, provider-specific caching). Request OpenRouter's usage.include
extension and use the returned usage.cost as the authoritative figure
when present, falling back to the per-token estimate for providers that
don't report it.
Admin logs list no longer joins prompt/completion content for every row —
content is fetched per-row only when a log is expanded in the dashboard,
since a table of chat-length blobs was slow to serialize even at low row
counts.
request_logs.user_id (previously unpopulated despite an index existing on
it) is now set from the owning key on every insert, including synthetic
dashboard keys, and log queries filter on it directly instead of a
virtual_keys subquery. Pagination switches from OFFSET to a (created_at, id)
keyset cursor so paging deep into the log stays cheap. Adds indexes on
provider_name/key_prefix used by the admin filters.
- Migration 000003: request_log_content table (messages jsonb, completion text)
- InsertRequestLog now returns the new row's ID so content can be linked
- Chat handler captures prompt messages and completion text (streaming accumulates deltas)
- ListRequestLogs left-joins content; logs API passes key filter through
- KeyStats query aggregates requests/tokens/cost/errors per key prefix
- GET /api/admin/stats/keys endpoint
- Logs page: expandable rows show prompt + completion inline
- Analytics page: per-key usage table below the chart
Add /auth/login and /auth/callback handlers. The backend holds the
client secret and exchanges the authorization code server-side;
the browser never sees the secret. Redirect URI and post-login
fragment destination are derived solely from GATEWAY_SERVER_PUBLIC_URL
(required when OIDC is set) to prevent host-header injection.
Tailwind v4 dark design (DM Sans/Mono, violet accent). Pages: Overview,
Logs, API Keys, Providers, Models, Analytics, Routing, Settings.
Auth gate uses localStorage token validated against /api/admin/providers.
Vite builds to internal/web/dist which is embedded into the binary.
main.go connects all packages: config → pool → migrate → cipher → store
→ providermgr → catalog → optional OIDC → rate limiter → API server.
WriteTimeout is zero to support SSE streaming; no middleware.Timeout.
Config validation now allows OIDC to be optional (static-token fallback).
chi v5 router with virtual-key auth middleware, admin-auth middleware
(OIDC-first with static-token fallback), chat and embeddings proxy
handlers with RPM/TPM rate limiting, admin CRUD endpoints for
providers/keys/models/settings, request log handler, and embedded
SPA handler.