razeri-plugin-api 0.23.0

Extension traits and registries for Razeri plugins
Documentation
# Changelog — razeri-plugin-api

What changed in this crate's own surface, newest first. The rendering traits a
dialect is written against have their own changelog, in
`razeri-plugin-contract`. Every entry says whether a plugin that compiled
against the version before it still compiles.

While the crate is below `1.0`, a minor version is the breaking one: Cargo
reads `0.19` and `0.20` as incompatible.

## 0.23.0 — published 2026-11-10

**This crate follows `razeri-plugin-contract` 0.21.0**, which adds
`SqlRenderer::render_shared_relation` with a default and turns `XACT_ABORT` on
in the T-SQL procedure form, so a plugin that compiled against 0.22.0 compiles
unchanged once it is rebuilt against 0.23.0. The conformance report lists the
new method among the answers a dialect left to the contract. The loader
compares versions first, so a plugin built against 0.22.0 is refused by name
until it is rebuilt.

**No event sink registers itself any more.** A registry built
`with_auto_discovered()` used to hold a `jsonl` sink and a `webhook` sink that
each dropped every event, and `EventRegistry::sink_names()` listed them beside
the real ones. Both are gone. A registry now holds a sink only when a program
registers it, or a plugin linked into the program submits one.

`WebhookSink` is removed, with `EventSinkConfig::url` and the `webhook` type of
`create_sink`: it serialised each event and sent it nowhere. `create_sink` now
refuses any type but `stdout` and `jsonl`, and says which types there are.
`JsonlSink` stays, and writes its file as before; open it with
`JsonlSink::open` or build it with `create_sink` from `{ type: jsonl, path }`.

A program that named `WebhookSink` or set `EventSinkConfig::url` no longer
compiles; nothing else in this crate's surface changed. `EventRegistry` says in
its documentation how it delivers: one sink after another, in registration
order, a refusal stopping none of the sinks after it.

## 0.22.0 — published 2026-11-07

**This crate follows `razeri-plugin-contract` 0.20.0**, which adds
`SqlRenderer::parquet_file_relation`, `ExpressionRenderer::render_decimal_fit`
and `ProcedureRenderer::render_procedure_table`, each with a default, so a plugin
that compiled against 0.21.0 compiles unchanged once it is rebuilt against
0.22.0. The conformance report lists the last two among the answers a dialect left to
the contract. The loader compares versions first, so a plugin built against
0.21.0 is refused by name until it is rebuilt.

The conformance report and the loader's messages are plain ASCII: `razeri plugin
conform`'s obligations, the world-writable plugin directory's warning and a name
two platforms claim use a colon, a comma or `...` where they used an em dash or
an ellipsis character. No signature changed.

The contract's 0.20.0 also adds `SqlRenderer::render_comment`, with a default
that writes a block comment, and the conformance report lists it among the
answers a dialect left to the contract.

## 0.21.0 — published 2026-11-06

**This crate follows `razeri-plugin-contract` 0.19.0**, which adds
`ReplicationRenderer::session_prerequisites_for` with a default, so a plugin
that compiled against 0.20.3 compiles unchanged once it is rebuilt against
0.21.0. The loader compares versions first, so a plugin built against 0.20.3 is
refused by name until it is rebuilt. Nothing else in this crate's own surface
changed.

## 0.20.3 — published 2026-11-04

**A dialect that says its engine cannot compute razeri's surrogate key has
answered the key.** Where `ReplicationRenderer::sid_prerequisite_kind` answers
`SidPrerequisiteKind::Refused` and `sid_prerequisites` says why, the
conformance report lists the refusal, with the first sentence of the reason,
as worth a look, and no longer owes a surrogate-key spelling, so the dialect can
be ready. A refusal with no reason is a finding to fix. The new
`conformance::key_refusal_reason` returns the reason as prose.

A plugin that compiled against 0.20.2 still compiles and changes nothing. One
that refused the key now reads as having answered it. The loader compares
versions exactly, so a plugin built against 0.20.2 is rebuilt against 0.20.3 to
load.

## 0.20.2 — published 2026-11-02

**The loader's module documentation says what runs before a library is
refused.** Opening a library runs its initialisers, and reading its build
declaration calls the C function that returns it; a refused library runs
nothing else. The documentation used to say a refused library had none of its
code run. Nothing else changed.

A plugin that compiled against 0.20.1 still compiles and changes nothing. The
loader compares versions exactly, so a plugin built against 0.20.1 is rebuilt
against 0.20.2 to load.

## 0.20.1 — published 2026-11-01

**A request can say which part of it will be sent again.** `PromptReuse` has
three values: `Conversation` (the default), `SystemPrompt` and `Nothing`. The
`LlmClient` trait gains `chat_stream_with_reuse`, which takes one. A provider
with a prompt cache marks only what the caller says will come back, because a
cache write costs more than fresh input and pays back only when read again.

A plugin that compiled against 0.20.0 still compiles and changes nothing: the
new method has a default that ignores the hint and calls `chat_stream`. The
loader compares versions exactly, so a plugin built against 0.20.0 is rebuilt
against 0.20.1 to load.

## 0.20.0 — published 2026-10-31

**The token-usage record says what a Claude API call cost.** `TokenUsage`,
the record every LLM provider fills in a reply, gains two fields:

- `cache_write_tokens: Option<u32>` — input the provider wrote to its prompt
  cache. Written input is priced apart from fresh input and from cache reads,
  and it is part of `prompt_tokens`.
- `advisor: Option<AdvisorTokenUsage>` — what a second model, offered to the
  answering one as an advisor, spent on the request: its model, how often it
  was consulted, and its input, cache-read, cache-write and output tokens. The
  advisor is billed at its own rates, so none of these are in the answering
  model's counts.

A provider that reports neither leaves both `None`, and a record written
without them still reads back, because both fields default and are left out of
the JSON when empty.

### What a plugin that compiled against 0.19 changes

- **An LLM plugin that builds a `TokenUsage` by naming its fields adds two
  lines**, `cache_write_tokens: None` and `advisor: None`. Left out, the build
  stops: *error[E0063]: missing fields `advisor` and `cache_write_tokens`*.
  A plugin that only reads a `TokenUsage` changes nothing.
- **A dialect, storage or language plugin changes nothing.**
- A plugin that fills `cache_write_tokens` keeps those tokens inside
  `prompt_tokens`, the way `cached_input_tokens` already sit inside it.

### Loading a plugin built against 0.19

The loader compares versions before anything else, so a razeri built against
0.20.0 refuses a plugin built against 0.19.0 by name and says what to rebuild
it with. Rebuilt against 0.20.0, with the change above, it loads.