Repository navigation
Releases: latere-ai/lux
Releases · latere-ai/lux
Release list
v0.18.1
Added
- With
OTEL_EXPORTER_OTLP_ENDPOINTset,luxdalso exports the Go
runtime's metrics: memory, goroutines, GOGC, GOMAXPROCS and scheduling
latency, asgo.memory.*,go.goroutine.count,go.config.gogc,
go.processor.limitandgo.schedule.duration, through
latere.ai/x/pkgv0.106.0.
v0.18.0
Added
GET /v1/requestsfilters by a request's own labels with
request_label=<name>=<value>, repeatable, every pair matched against
the record'srequestLabels, the pairs a caller sent inLux-Labels.
A harness that labels each call with the run it belongs to reads a
run's records without keeping their request ids. It is a second filter
besidelabel, which still reads the Key's labels, and it narrows what
the authorizer's filter already lets the caller read: theusage.read
resource is unchanged. A pair outside theLux-Labelssyntax, or more
names than the eight a request keeps, isinvalid_fieldat
request_label; two values for one name are an empty list.
GET /v1/usagerefuses the parameter, since a request label is never
an aggregate dimension. A cursor is bound to the pairs, and one issued
for a query without them keeps working. The archive is partitioned by a
Key label, so a query by request label alone reads every partition and
its pages may hold fewer records thanlimitwhilenext_cursoris
present.lux requests -request-label k=vsends it. For the packages:
metering.RecordQuery.RequestLabels,gateway.MaxRequestLabels,
gateway.ValidRequestLabel, andgateway.RequestLabelSyntax.- A Key can name the doors it opens with
spec.doors, a list of
openai,anthropic,gemini, andlux. A request on any other door
is refused with the new codedoor_not_allowed, 403, in that door's
error shape, on every route of the door, its model list included,
before any Model, Provider, window, or upstream is read, and recorded
as a refused request. A Key without the member opens every door, and
renders as it did before, so an authorizer or a client built against
an earlier release reads it unchanged; the authorizer's proposal
carries the list when it is set. An empty list, an unknown door, and a
door named twice areinvalid_field. A change to the list reaches each
replica at its next read of the journal.lux keys create -doors
writes it. For the packages:v1.KeySpec.Doors,
v1.KeySpec.OpensDoor, andgateway.CodeDoorNotAllowed.
v0.17.3
Fixed
- A picture a tool returns reaches the model when a request is translated
to OpenAI chat completions or Responses, as every request through the
Anthropic door to an OpenRouter model is. It was dropped and recorded as
tool_result.imageloss. The Responses door also takes a
function_call_outputwhose output is a list of text and image parts.
(latere.ai/x/pkg v0.101.0)
v0.17.2
Fixed
- The release is published. v0.17.1 was tagged, but its release workflow
stopped before publishing anything: the step that records the next
release's fixture read the control plane right after the conformance
suite had spent its rate for the minute, and the 429 failed the step.
The step now waits out the refusal. v0.17.2 carries everything v0.17.1
lists.
v0.17.0
Changed
- A Key's
spec.zeroRetentionis written only when it is true. v0.16.0
wrotezeroRetention: falseon every Key, in a read, in the store,
and in the proposal an authorizer is asked about, so an authorizer or
a client built against v0.15.0 that holds a Key to the members it
knows refused every Key, whether it used the member or not. A Key
that does not ask is now the same on the wire as in v0.15.0. Read a
Key without the member as one that does not ask; a typed client that
requires the member needs that change.false, written in a manifest,
is still accepted. The OpenAPI document no longer lists the member as
required.
v0.16.0
Added
- A Key can ask that none of its requests reach an upstream that may
keep them, withspec.zeroRetention: true, and a Provider declares
how its upstream keeps nothing of a request withspec.zeroRetention:
{}when the upstream keeps nothing of any request, or
requestFields, merged into such a request's body after
spec.requestFieldsand winning on every member both name, for an
upstream that takes the option per request, such as OpenRouter's
provider: {zdr: true}. A request on a zero-retention Key reaches
only declaring Providers and falls over only among them. A Model with
no declaring target refuses it with the new code
zero_retention_unavailable, 403, before any upstream is called;
declaring targets that cannot be tried now answer
provider_unavailable. An opaque route on such a Key is served only
toward a Provider that declares{}. A body the fields cannot be
written into isinvalid_request. Requests on other Keys are routed
and sent as before. The request record (zeroRetentionin
GET /v1/requests), the request log line (zero_retention), and each
lux.upstreamspan (lux.zero_retention) say whether a request was
on such a Key. A change to the flag reaches each replica at its next
read of the journal. The catalog'sopenrouterProvider declares
provider: {zdr: true}, anddocs/security.mdwalks the case. For
the packages:v1.KeySpec.ZeroRetention,v1.ProviderSpec.ZeroRetention
andv1.ZeroRetention,gateway.CodeZeroRetentionUnavailable,
gateway.AttrZeroRetention, andZeroRetentionongateway.Record
andmetering.Record, which a platform's ownRecordercopies across.
Changed
- Every Key renders
spec.zeroRetention,falseunless set, as it
renders its other booleans, so a Key read back after the upgrade
carries one member more than it was applied with, and the six
accepted Keys of the golden corpus change that way. A manifest without
the member keeps the default. The conformance suite's previous-release
group reads a Key from an earlier release with the member at its
default.
Fixed
- A request toward a Provider with
spec.requestFieldsis refused
invalid_requestwhen any value of itsContent-Encodingheader, or
any coding a value lists, is other thanidentity. Only the first
header line was read, so a body under a second line naminggzip
reached the upstream without the fields.
v0.15.0
Changed
- A Key can list up to six Budgets in
spec.budgets, up from four. A
platform that gives each API key it issues a Budget of its own, beside
an organization's balance, an agent's two limits, and a session's own
Budget, needs five; the sixth is spare. A Key listing five or six was
refusedinvalid_fieldatspec.budgetsand is now accepted, and a
seventh is refused as a fifth was. Every listed Budget is still a gate
of its own at the door, held from memory on each request, and
budget.drawis asked once per entry only when the Key is written.
v1.MaxKeyBudgetsis6. A Key listing more than four can be written
once every replica runs this release: a replica on an earlier one
refuses to write it, and serves one another replica wrote. In the
golden corpus the refused casekey-budgets-fiveis now
key-budgets-seven, and the accepted Keyagent-sessionlists six
Budgets, four of them new accepted cases.
Security
- Built with Go 1.27.2 and golang.org/x/net v0.60.0, which fix GO-2026-6611, GO-2026-6612, GO-2026-6613 and GO-2026-6617.
v0.14.1
Fixed
- A Model whose every price is zero is served when the Key's Budget or
spend limit is used up. A hard window lets the request that crosses
its amount run, so a spent Budget is often past its amount, and from
then on every request on the Key was refusedbudget_exhausted, or
spend_exceededfor the Key's own limit, a free one included. A
request costs nothing when the Model'sinput,output,
cachedInput, andcacheWriteprices are all zero, the same prices
the meter bills from; such a request is now admitted whatever the
window has left, counted, and recorded at a cost of0. Rate limits,
currency_mismatch, and every check before them still apply to it. A
request for a priced Model on a spent window is refused as before,
with the same code, detail, andRetry-After. An unpriced Model is
not free and is refused as before. The newmetering.Freereports
whether a pricing is free, for a plane with a Limiter of its own.
v0.14.0
Fixed
- A model added to a Key's
spec.modelsis served on the next request
through any replica. A replica that cached the Key before the change
used to refuse that model withmodel_not_alloweduntil its journal
tail orLUX_KEY_CACHEcaught up, so a call made right after the
change through another replica than the one that applied it was
refused. Before it refusesmodel_not_allowed, a door now reads the
Key from the store once and decides on what the store holds. A Key
that truly lacks the model costs one store read per refused request;
a change that takes access away still reaches every replica within
LUX_KEY_CACHE, and the door's model list still follows the cache.
The reread runs when the door'sKeyLookupalso implements the new
gateway.KeyRefresher, whichserve.KeyCachedoes; a plane that
composes the gateway with aKeyLookupof its own and without
Refreshrefuses on whatByHashanswered, as before. - A provider's refusal is readable in the log and safe in the detail.
Thelux.requestlog line gainsupstream_status, the last provider
answer's HTTP status and0when no provider answered, so a query
tells a provider's400from a refusal Lux made itself; the field was
never on the line, which is why it read empty. The caller already
received a provider's4xxreason inLux-Error-Detailas it does a
429or a5xx, the status and the first KiB of the provider's body.
That excerpt now has the Provider's credential replaced with
[redacted]and any other credential-shaped string, such as a bearer
token, a JWT, or ansk-key, replaced with***before it is cut,
so an upstream that echoes what it was sent no longer hands a
credential to the caller.
v0.13.0
Added
- A Provider takes
spec.requestFields, a JSON object the gateway
merges into every request body it sends that Provider, after any
translation, with the Provider's value winning on each member it
names: an object merges member by member, and any other value, a list
included, replaces the caller's whole. A caller can then neither leave
an upstream option out nor turn it off; for OpenRouter,
provider: {zdr: true}restricts every request to endpoints with zero
data retention. The merge reaches model routes and opaque routes alike,
so toward such a Provider an opaque body is read whole rather than
streamed, and a request whose body is sent under aContent-Encoding
is refusedinvalid_request.model,stream, andstream_options
cannot be set (reserved_prefix), a member cannot be null, and the
object is at most 16384 bytes encoded (invalid_field). The field is
not secret: every read and the authorizer's proposal carry it. A
Provider without it is sent what it was sent before, byte for byte.
docs/security.mdwalks the OpenRouter case.