Files
infosave2007 0b071279ac
CI / lint (push) Canceled after 0s
CI / test (macos-14) (push) Canceled after 0s
CI / test (ubuntu-latest) (push) Canceled after 0s
CI / test (windows-latest) (push) Canceled after 0s
Release / crates-io (push) Canceled after 0s
Release / release (macos-14, aarch64-apple-darwin) (push) Canceled after 0s
Release / release (ubuntu-latest, x86_64-unknown-linux-gnu) (push) Canceled after 0s
Release / release (windows-latest, x86_64-pc-windows-msvc) (push) Canceled after 0s
Docker / publish (push) Canceled after 0s
fix(stream): no choice-less chunks unless the client asks for usage
Every stream used to carry two chunks with an empty choices array: usage,
which the upstream emits BEFORE the stop reason, and a cortiq_cost_usd chunk
the gateway added itself. OpenAI ships the usage chunk only when the request
sets stream_options: {"include_usage": true}, and chat UIs do not set it -
which is why the same tool, in the same Open WebUI, sees neither chunk from
Groq and two from us.

Honour the flag. With no stream_options a stream is now exactly
role -> content... -> tool_calls -> finish_reason -> [DONE], with no
choice-less chunks at all. When usage is requested it ships once, after the
stop reason, with cortiq_cost_usd folded into the same chunk rather than
trailing behind it.

Token accounting, cost and the X-Cortiq-* headers are untouched: those numbers
were never read back off the wire.

Verified: the default stream matches the reference shape byte for byte, the
opt-in stream matches OpenAI's, the official OpenAI SDK reassembles the
streamed tool call, and the full Open WebUI 0.11.0 tool loop still runs the
tool and answers from its result.
2026-08-21 21:24:11 +03:00

516 lines
34 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Changelog
All notable changes to this project are documented here.
The format is based on [Keep a Changelog](https://keepachangelog.com/).
## [0.2.49] - 2026-08-21
### Fixed
- **The SSE stream now has the same shape every hosted provider sends.** Two choice-less
chunks used to ride along on every stream: `usage` (emitted by the upstream *before* the
stop reason) and a `cortiq_cost_usd` chunk of the gateway's own. OpenAI ships the usage
chunk only when the client sets `stream_options: {"include_usage": true}` — and chat UIs
do not set it, which is why a Groq stream carries neither. The gateway now honours that
flag: with no `stream_options`, a stream is exactly
`role → content… → tool_calls → finish_reason → [DONE]` with no choice-less chunks at
all. When usage *is* requested it ships once, **after** the stop reason, with
`cortiq_cost_usd` folded into the same chunk instead of trailing behind it.
Token accounting, cost and the `X-Cortiq-*` headers are unchanged — the numbers were
never taken from those chunks.
## [0.2.48] - 2026-08-21
Verified end to end: Open WebUI 0.11.0 → gateway → managed CMF/Qwen, native
function calling, tool executed and its result fed back into the reply.
### Fixed
- **A tool call is never delivered twice.** Qwen-family templates write the call into the
generated text as `<tool_call>{…}</tool_call>`; some runtimes parse it into structured
`tool_calls` *and* still stream the raw markup as `content`, so the client sees the call
once as prose it must not show and once as the real thing. The gateway now strips the
markup from `content` — including when the marker is split across SSE chunks — while
passing the structured call through untouched. Ordinary text containing `<` is not
affected, and an unterminated block is handed back as text rather than swallowed.
- **`finish_reason` agrees with the reply.** A response carrying tool calls now always ends
with `finish_reason: "tool_calls"`, whatever the upstream called it — clients branch on
that field to decide whether to run the tool.
- **Streamed `tool_calls` are brought to the OpenAI shape**: `index` on every entry, `id`
and `type` exactly once per call (continuation deltas that carry only an argument
fragment are left alone, as the spec requires), and `function.arguments` always a string
even when the runtime sends an object.
### Added
- **Unparsed `<tool_call>` markup is promoted into a real call.** When the upstream never
parsed it, the gateway turns it into a proper `tool_calls` entry with a generated
`call_…` id, so the call is executable instead of decorative. Markup that is not a JSON
object with a `name` is ignored — a stray marker must not become a bogus call.
## [0.2.47] - 2026-08-21
### Changed
- **The hosted allaigate router is addressed by hostname:
`https://router.allaigate.com`** is now the default `[router].url`, and
`verify_tls` defaults to `true`. The endpoint used to be reachable only by bare IP,
whose certificate is issued for the hostname — so every hosted setup had to run with
certificate verification switched off. It no longer does.
- **Existing configs are migrated on load.** A `[router].url` still pointing at the old
`138.226.222.209` (with or without a scheme or trailing slash) is rewritten to the
hostname, `verify_tls` is turned back on, and the file is saved — the endpoint is the
same machine, and this is the difference between a verified connection and a blind one.
A self-hosted router URL is never touched.
- README, `docs/ROUTER.md` (both languages), `config/gateway.example.toml` and
`bench/accuracy.py` updated to the hostname.
## [0.2.46] - 2026-08-21
Verified end to end against a real Open WebUI container talking to the gateway.
### Fixed
- **Offered `tools` no longer make a local model unroutable.** Open WebUI attaches its ~26
built-in tools (knowledge bases, notes, calendar, …) to *every* message, and the gateway
treated that offer as a hard capability requirement — so a plain "hello" against a pool of
tools-less local models failed with «No healthy model available that satisfies required
capabilities». In the OpenAI contract `tools` is an offer: only `tool_choice: "required"`
(or a named function) makes the call impossible without them. The gateway now still
prefers a tool-capable model, but falls back to serving the request with the tools
stripped (along with `tool_choice`/`parallel_tool_calls`, which a provider rejects on
their own). `tool_choice: "required"` still fails loudly, as it must.
- **A pinned model that merely lacks tool support is honoured** instead of being reported as
«not available — it is not in the pool (deleted?)». An unknown pin still fails loudly.
### Added
- `X-Cortiq-Tools-Dropped: true` (and `cortiq.tools_dropped` in the echo block) whenever the
gateway answers without the tools the client offered, so the downgrade is never silent.
## [0.2.45] - 2026-08-21
### Fixed
- **Open WebUI (and every strict OpenAI client) can finally chat with the gateway.**
`POST /v1/chat/completions` rejected perfectly ordinary payloads with a bare HTTP 422 —
a plain-text body no OpenAI client can parse, which is why the chat window just spun with
no answer and no error. The request parser now accepts every shape real clients send:
`content` as a string, `null`, or an array of content parts (text parts concatenated,
images skipped), `tools: null`, `stream: null`, a string `reasoning_effort`
(`low`/`medium`/`high`) as well as a numeric budget, `max_completion_tokens` as an alias
for `max_tokens`, `messages[].name` and `messages[].tool_call_id` proxied through, and a
missing `Content-Type`. A body that genuinely cannot be read now returns an OpenAI error
envelope with HTTP 400.
- **Strict OpenAI response JSON, no optional `null`s.** Absent tool calls no longer emit
`"tool_calls": null` (the key is omitted entirely); `finish_reason` is mapped to the
spec's closed set, so `cortiq serve`'s `max_tokens` reaches clients as `length`; the
response always carries a non-empty `id`.
- **`model` echoes the id the client requested** (`cortiq-auto` stays `cortiq-auto`, not the
upstream's `cortiq`), matching OpenAI's own behaviour. The model that actually answered
stays in `cortiq.selected_model` and `X-Cortiq-Selected-Model`. Applies to
`/v1/chat/completions`, `/v1/completions` and `/v1/messages`.
- **Streaming is re-normalised instead of forwarded verbatim.** Every chunk is reissued as a
strict `chat.completion.chunk`: the stream opens with a `role` delta, `message`-shaped
chunks are converted to `delta`, empty deltas are dropped, and it always closes with one
terminal `finish_reason` and `data: [DONE]` — even when the upstream stops early or dies
mid-flight, in which case the failure arrives as an SSE `error` event rather than a client
waiting forever. SSE responses also carry `X-Accel-Buffering: no` so nginx/Traefik cannot
buffer the stream into a hang.
- **Client bookkeeping keys are no longer proxied upstream.** `chat_id`, `session_id`,
`metadata`, `features`, `background_tasks` and friends (plus a non-string `user`) are
stripped before the provider call, so strict backends stop rejecting the request.
### Added
- **`GET /v1/models` lists managed local CMF models.** Every `[[cmf.servers]]` entry now
appears alongside `cortiq-auto` and the `[[models]]` pool, with live `running`/`healthy`
state in a non-standard `cortiq` field, so Open WebUI and other clients can pin a local
`.cmf` directly. Entries also carry `created`.
### Changed
- Dropped leftover `[to_wire]` debug prints from the OpenAI provider — they wrote to stderr
on every single request.
## [0.2.44] - 2026-08-08
### Added
- **Power controls in Settings**: «Stop everything & power off» stops every managed local
model server and exits the gateway (the panel shows a clear "gateway is off" screen);
«Restart everything» (relabeled) re-execs the gateway with its local servers. Both behind
confirm dialogs, translated in all seven languages.
### Fixed
- `cortiq-gateway` typed in any directory picks up your configuration. Resolution order:
`--config` → a readable `./config/gateway.toml` → `$CORTIQ_GATEWAY_HOME` →
`~/.cortiq-gateway/gateway.toml`. An unrelated project that merely contains `Cargo.toml`
and a `config/` folder no longer hijacks the config path ("cannot read config
config/gateway.toml").
## [0.2.43] - 2026-08-08
### Added
- **API keys actually gate the gateway.** Configured `[[api_keys]]` now enforce auth on
`/v1`, `/route` and `/mcp` (Bearer or `x-api-key`), attribute every request to the key's
account in stats, apply per-key rate limits (`rate_per_min` → 429 with `Retry-After`) and
per-key monthly budgets (`monthly_budget_usd` → 429 `insufficient_quota`). No keys
configured = open mode, as before.
- **Auto-detected tool support for managed local servers**: `cortiq serve` ≥ 0.5.63
advertises the loaded model's capabilities in `/healthz`; the gateway reads the handshake
when the server becomes healthy, so a local model whose chat template can drive tool
calling serves agent (tools) traffic with zero manual configuration. Manual override:
`caps = ["tools"]` on the `[[cmf.servers]]` entry (also the fallback for older serves).
- **OpenRouter balance on the dashboard** (when an OpenRouter key is configured): the
remaining credit is a KPI card, red under $5.
- **Harness snippets for every OS** on the API-keys page: Linux/macOS and Windows variants
per harness, an install command chip when the harness is missing, and a paste-your-key
field that substitutes the real key into every snippet.
- Deploy guide (`docs/DEPLOY.md`, Russian mirror): systemd/launchd/Windows service, nginx and
Caddy TLS fronting, security checklist, backup list.
### Fixed
- Capability filtering widens the tier chain instead of killing the request: when an AUTO
route's chain has no model with the required capabilities (tools), the gateway escalates
through medium→high chains and then the rest of the pool instead of returning
"No healthy model available". Explicit pins still fail loudly.
- Semantic-cache threshold no longer renders as `0.9200000166893005` (f32→f64), the cache
settings card shows live entries/hits/saved-$ and warns when no embedding model exists.
- Key-page snippets survive being pasted into a real interactive zsh (no `#` comment lines,
no history-expansion `!`), and the install chips are readable in the light theme.
- Stats log growth is bounded: `[stats] max_file_mb` (default 16) folds old JSONL records
into the sidecar totals file — per-account all-time and month spend survive compaction
and restarts.
### Breaking
- If your config already lists `[[api_keys]]` but your clients never send keys, requests
will now be rejected with 401 — either remove the keys from the config or start sending
`Authorization: Bearer <key>`.
## [0.2.42] - 2026-08-08
### Added
- **The continuous complexity score is visible**: the benchmark's routing table and the
dashboard's recent-requests list show the router's raw 0..1 score next to the tier badge —
see what different prompts actually weigh and tune the boundary sliders from real traffic.
### Changed
- Dashboard tier badges render translated tier names; the Russian locale calls the dashboard
«Рабочий стол».
## [0.2.41] - 2026-08-08
### Added
- **Linked complexity-boundary sliders** on the Routing page: one track, two draggable handles
(the low|medium and medium|high boundaries) over green/amber/red zones. Handles push each
other, the numbers ride on the handles and the legend updates live — no hand-typed score
ranges. The gateway re-maps the router's continuous complexity score with YOUR boundaries
(`[routing.bands]`, validated `0 < low < medium < 1`) before tier chains, escalation and
stats; unchecked = the router's own bands, untouched behavior.
### Fixed
- Complexity tier names are translated everywhere they appear (bands legend, tier badges, pair
selects, benchmark column, Playground decision): лёгкий/средний/сложный, leicht/mittel/schwer,
facile/moyen/difficile, fácil/medio/difícil, 简单/中等/困难, kolay/orta/zor.
- Benchmark route-judged retries re-ask the SAME prompt after a pause before reaching for a
fresh template — difficulty is judged in the background, so the second occurrence is the
honest steady-state answer.
## [0.2.40] - 2026-08-08
### Added
- **Per-key spend accounting** on the API-keys page: requests, tokens in/out and cost per key
for a selectable window (24h / 7d / 30d) plus a cumulative all-time cost column — "which
department spends what" at a glance.
- **Bounded stats on disk**: once `stats.jsonl` outgrows `stats.max_file_mb` (default 16, 0
disables), records older than the retention window fold into cumulative per-account totals in
a small sidecar file (tmp+rename on both) and leave the log. All-time accounting survives
compaction and restarts; nothing is ever counted twice.
- **Per-complexity models inside a task rule**: each task is edited as rows of
"model + complexity" pairs — `[routing.tasks.code] low=["cheap"] high=["best"] any=[...]`.
The tier-specific pick leads its own tier, `any` (and legacy plain arrays) leads everywhere,
the global tier chain and `default` always remain as fallback, and high verdicts still
escalate over any-tier pins.
### Changed
- Benchmark hard probes are generated from wider interdisciplinary pools (6 families each) and
retry once with a fresh template — the router's novelty decay no longer flips them red.
### Fixed
- The `task-rule` badge is honest: it appears only when the rule actually chose the head of the
candidate chain, not whenever a rule merely exists for the task.
## [0.2.39] - 2026-08-07
### Added
- **Benchmark, redesigned into the console** (⏱ sidebar entry): two explicit modes. **Smart
routing** (default) sends every probe through `cortiq-auto` and judges the ROUTER — task label
vs expected (✓/✗), complexity tier, chosen model with per-request cost, task-rule badge —
with KPIs for answer quality, classification accuracy, TTFT, tok/s and money spent, plus a
"requests went to" per-model distribution. **Individual models** compares pinned models
head-to-head. Results render live row-by-row, runs land in a browser-side history (per-run ×
and Clear-all), custom tests supported, ⚡ quick subset (7) vs full suite (17).
- **Hard probes that exercise the high tier**: two route-judged tests (science / ops pools)
generated fresh each run — interdisciplinary prompts rate high reliably while fixed wordings
decay with the router's novelty signal. Pass = the router said `high` and the request escalated;
the tiny token budget keeps them at cents even on the priciest model.
- **One-paste harness setup** on the API-keys page: Claude Code (native Anthropic `/v1/messages`,
now on by default, one-click enable for older configs), Codex, OpenCode, Continue and Cline —
each pre-filled with the gateway URL and the freshly created key. The gateway detects harnesses
already installed on its machine (✓ on the tab, "already configured for this gateway" status).
- **Stale-tab detection**: open consoles notice a gateway restart via the meta start marker and
show a reload banner instead of silently running an old interface.
- Log viewer Clear button (`DELETE /admin/api/logs`).
### Changed
- **High complexity escalates over task rules**: at tier `high` the tier chain leads and the task
rule demotes to fallback — a `code → cheap-model` rule no longer pins the hardest prompts to
the cheap model. Low/medium keep task-rule-first semantics (documented in docs/ROUTING).
- **Honest stream costs**: the final cost rides a closing SSE event (`cortiq_cost_usd`) since the
initial header is written before generation; money renders with 4 decimals (last two smaller)
and grows precision below $0.0001 instead of collapsing to $0.0000.
- **Oracle-sized timeouts**: router default 800 ms → 15 s (hosted escalations take ~10 s);
provider clients gained a 10 s connect timeout (dead upstreams fail over fast) with no overall
cap so long generations and streams are never cut.
- Benchmark fairness: +256-token reasoning headroom (thinking models aren't cut mid-`<think>`),
router degradation shown as "⚠ router degraded" and excluded from accuracy, "budget spent on
reasoning" instead of a misleading "empty answer", creative-writing budget 96 → 512.
- New-key dialog simplified to account / rate limit / optional custom value.
### Fixed
- `cortiq bench` results showed "error" with newer cortiq releases that pretty-print `--json`:
the parser now finds the last balanced JSON object anywhere in the output, and tok/s prefers
steady decode speed (the old heuristic could report `allocs_per_token` as tok/s).
## [0.2.38] - 2026-08-07
### Added
- **Image & video generation in the Playground**: three tabs now — Chat, Image, Video. Image runs
`cortiq imagine` over a local text-to-image `.cmf` (Lumina) with width/height/steps/CFG/seed,
live step progress and an honest ETA; the P6 PPM result is decoded to a canvas in the browser
with one-click PNG export. Video runs `cortiq animate` (MiniMax-H3) with frames/steps/quality
and optional first/last keyframes (rasterized to PPM client-side); the MJPEG+PCM AVI is decoded
in JS and played on a canvas synced to the wav — no ffmpeg on either side (an mp4 remux happens
opportunistically when ffmpeg exists). Local `.cmf` files are classified chat/image/video via
`cortiq info`; media packs are never registered as chat servers. Requires cortiq ≥ 0.5.56.
- **Task-based routing**: `[routing.tasks]` maps a router task label to an ordered model list.
A matching rule's models lead the candidate chain and the complexity chain follows as fallback,
so a task pin never weakens failover. The Routing page builds the editor from the account's
live taxonomy (`GET /v1/taxonomies`), shows each task's share of 7-day traffic, and supports
custom labels. Responses whose chain was headed by a rule report `route_source "router+task-rule"`.
- **Generation gallery** under the media tabs (lazy PPM thumbnails, reopen with seed/steps, delete),
and generations **queue up** (with position shown) instead of being rejected.
- **Chat is a real dialogue**: history is resent, bubbles, Clear, Cmd/Ctrl+Enter — plus live
metrics: token counts, tok/s and time-to-first-token.
- **Exact stream token counts** when the model server provides usage; the gateway fallback counts
content chunks (exactly 1/token on cortiq serve) instead of chars/4, flagged as an estimate.
- **Jobs survive restarts**: import and media job lists persist to `<data>/jobs-*.json` and are
reconciled on start — the Register button no longer vanishes because the gateway restarted.
- **Admin console, ops batch**: Logs viewer in the sidebar (500-line ring, filter/level/pause/copy);
a System panel on the dashboard (CPU sparkline + load, RAM incl. the gateway's RSS, free disk at
the models dir); "not enough disk / may not fit in RAM" badges on model cards before a download;
a Restart-gateway button; an update-available notice (crates.io checked twice a day); config
export as the real `gateway.toml` and TOML import (validated, hot-reloaded); "Connect your app"
snippets (curl/Python/JS) on the keys page; one-click `cortiq bench --json` on the Models page.
- **Docker ships the cortiq binary** (built in the builder stage, plus `libvulkan1`): local `.cmf`
chat models and media generation now work in containers, where auto-install never could.
- **Security**: an open admin console listening on 0.0.0.0 outside Docker warns in the log and
shows a dismissible banner.
### Fixed
- The default config pointed at a placeholder `localhost:8080` router with a nonexistent taxonomy,
leaving every fresh install with a permanently red "Router: offline" badge. A fresh install now
targets the hosted allaigate router and degrades gracefully until a key is set.
- The wizard's Finish lands on the Playground instead of the Models list.
## [0.2.37] - 2026-08-06
### Added
- **Zero-argument startup**: `cortiq-gateway` now runs with no flags. The config is created on
first launch (repo `config/gateway.toml` when run from a checkout, otherwise
`~/.cortiq-gateway/gateway.toml`, or `$CORTIQ_GATEWAY_HOME`), the browser opens by itself,
and a busy port is swapped for the next free one. `--no-browser` / `--no-port-search` opt out;
`--config` still takes an explicit path.
- **No admin token required by default**: the console opens without auth. Set `--admin-token`,
`[admin].token` or `[admin].token_env` to protect it.
- **First-run wizard** (`/admin#/onboarding`): pick a language, download a ready-made `.cmf`
model, and finish — Finish connects every completed download as a local model, starts its
`cortiq serve` and points `routing.default` at it. Nothing to edit by hand.
- **Ready-made models are read live** from the `infosave` HuggingFace account in both the wizard
and Import, with per-variant quantizations, real sizes, an "already downloaded" mark and a
delete button. Newly published models appear without a gateway update.
- **Windows-friendly data dir**: `%APPDATA%` / `%LOCALAPPDATA%` / `%USERPROFILE%` are used before
falling back, so running the exe from `Downloads` never writes into `Program Files`.
- **Full localization**: all 432 UI strings now exist in every language (en, ru, de, fr, es, zh, tr).
Import, Settings → Local models, the model form and the whole first-run wizard were English-only
in de/fr/es/zh/tr before this release.
### Fixed
- **Download progress jumped straight to 100% and then hung**: with `-L`, curl prints a complete
`0→100%` progress bar for the redirect hop before the transfer starts, and that bar was being
parsed as the download's progress. Progress is now derived from bytes on disk against the size
reported by the HuggingFace tree (or a `HEAD` request), so the bar and the reported
`done / total` bytes track the real transfer.
- **Re-downloading over an existing file reported 100% instantly**: the previous copy is now
removed before the transfer starts, so the size poller cannot read stale bytes.
- **Downloaded models were not recognised**: the admin UI derived the output file name
independently of the gateway, so a finished download did not mark its card as downloaded and the
delete button never appeared. The gateway now returns the exact output stem (`local_name`) and
the UI sends it back verbatim for download, progress and delete.
- **Registering a local model produced an unusable pool entry**: with `manage_server = false` the
model was added to the static pool pointing at `cortiq_server_url`, where nothing listens —
every call failed with `upstream unavailable`. Registration now always creates a managed server
the gateway runs itself, and repairs a stale static entry for the same file.
- **Cancel did nothing during a sharded download**: cancellation was only checked between part
files, so a multi-GB part had to finish first. The running transfer is now aborted immediately
and the partial output removed.
- **Deleting a model left the config broken**: removing a `.cmf` now also unregisters the model
and stops its managed server instead of leaving a pool entry pointing at a missing file.
- **Two variants of one repo collapsed into a single job row**; jobs are now keyed by repo + variant.
- Curl's stderr is captured into the job log, so a failed download explains itself instead of
showing a bare exit code.
## [0.2.36] - 2026-07-25
### Fixed
- **Docker image failed to start when built fresh (`GLIBC_2.39 not found`)**: the floating `rust:1-slim` builder had drifted to Debian trixie while the runtime stayed on bookworm, so a locally built container crashed on startup. Builder and runtime are now pinned to the same Debian release (trixie).
- **Docker model downloads (curl exit code 23)**: the runtime image was missing `curl`, and the default models dir (`$HOME/.cortiq-gateway/models`) was not writable by the container user, so importing a ready `.cmf` model failed with a write error. The image now bundles `curl`, keeps all gateway state under `/app/data` (`CORTIQ_GATEWAY_HOME`), and runs as root so bind-mounted host folders (Docker Desktop on Windows/macOS) are writable; pass `--user 10001` to harden when not using host bind mounts.
- **Docker Compose persistence**: added a `cortiq-data` volume at `/app/data` so downloaded models and stats survive `docker compose up --force-recreate`.
- **Clear import errors**: a models dir that cannot be created now fails the import immediately with the path and reason instead of a cryptic curl exit code; curl exits 23 (write error) and 22 (HTTP error) are annotated with a human-readable hint in the job log.
## [0.2.35] - 2026-07-23
### Fixed
- **Streaming Pipeline System Prompt Merging**: Fixed system prompt injection in `run_stream` so configured model system prompts and reasoning suppression directives are properly passed into streaming requests.
- **CMF Reasoning Mode Compatibility**: Fully integrated with `cortiq-engine` v0.5.17 for sampler-level logit bias reasoning token suppression and zero-token thinking bypass.
## [0.2.34] - 2026-07-23
### Added
- **Responsive mobile admin console**: Added a hamburger navigation drawer, touch-friendly controls, phone-sized forms and cards, scrollable tables, compact toolbars, safe-area support, and mobile bottom-sheet dialogs across the admin interface.
- **Accessible mobile interactions**: Navigation and dialogs now support backdrop dismissal, Escape-key closing, reduced-motion preferences, and explicit accessibility attributes.
### Fixed
- **Add model dialog**: Restored the missing `opt` helper import that prevented the Add model button from opening its form.
- **Local model settings on phones**: Reflowed managed CMF server controls to avoid clipped fields and actions on narrow screens.
## [0.2.31] - 2026-07-22
### Added
- **Structured O(1) Nyström Attention & Reasoning Control**: Enhanced settings drawer with structured parameter sections for O(1) Nyström Streaming Attention (`o1_m`, `o1_window`, `o1_sink`), Generation (Temp, Top-P, MaxTokens, System Prompt), Reasoning ("Disable reasoning" toggle mapping to `think_budget = 0` for Qwen3/3.5 models), and Runtime (`skip_mtp` via `CMF_MTP=0`).
- **Persistent Admin Token Configuration**: Support for explicit `token` field in `config/gateway.toml` under `[admin]` block to prevent authorization invalidation across restarts.
### Fixed
- **JavaScript UI Helper Restoration**: Restored `opt(...)` helper in `web/js/ui.js` and `web/js/views/import.js` to ensure HF model import modal options render smoothly.
- **Engine Command Flag Accuracy**: Fixed `CMF_MTP=0` environment flag pass-through for `cortiq serve` processes.
## [0.2.30] - 2026-07-22
### Added
- **Per-Model Generation & Reasoning Parameters**: Full support in Web UI and Gateway core for per-model generation defaults: Temperature, Top-P, Max Tokens, Think Budget (Reasoning mode), O(1) Constant-Memory Streaming Attention (`o1`), MTP (Multi-Token Prediction toggle `skip_mtp`), and default System Prompt.
### Fixed
- **Duplicate Local Model Registration**: Fixed `duplicate managed model id` conflict in `register_import` by cleanly routing managed CMF models directly into `[[cmf.servers]]` with automatic free port allocation without pushing duplicate entries to `[[models]]`.
### Fixed
- **Quantization Badge & Title for Pre-Converted Models**: Automatically detect quantization (`Q1`, `Q1T`) for ready-to-run `.cmf` models (e.g. `infosave/Bonsai-27Bcmf`) from repo name and downloaded filename, resolving default `Q8_2F` badge fallback. Updated section title to "Загрузки / Конвертации" ("Downloads & Conversions").
### Fixed
- **Automatic CMF Local Server Lifecycle**: Registering an imported `.cmf` model automatically adds it to `[[cmf.servers]]` and reloads the runtime (`cmf_runtime::manage`), automatically spawning `cortiq serve` on port 8090 so chat completion requests route seamlessly without `upstream unavailable` errors.
### Fixed
- **Multi-GB Ready CMF Download Stream**: Replaced HTTP stream decoder with a streaming process downloader (`curl`) for pre-converted `.cmf` models to support multi-gigabyte Hugging Face LFS repositories without 30s/60s socket timeouts.
- **Ready CMF Quantization & UI Labels**: Set correct quantization badges (`Q1`, `Q1T`) for featured ready-to-run `.cmf` models and removed misleading "converting" log messages for direct downloads.
### Fixed
- **Direct Ready CMF Download**: Importing pre-converted `.cmf` model repositories (e.g. `infosave/Bonsai-8B_2bit_cmf`) directly streams the ready `.cmf` file from Hugging Face instead of attempting a safetensors conversion without `config.json`.
### Added
- **New Quantization Schemes**: Added support for `Q4_TILED` (q4t), `Q1T` (1.58-bit ternary), `Q1S` (1-bit sparse), `Q1P` (1-bit PTQ error diffusion), and `Q1` (pure binary) in converter backend and web admin UI.
- **Featured Ready-to-Run .cmf Models**: Added 1-tap download cards for pre-converted `.cmf` models (`infosave/Bonsai-1.7Bcmf`, `infosave/Bonsai-8B_2bit_cmf`, `infosave/Bonsai-27Bcmf`).
## [Unreleased]
### Added
- **Docker Compose** quickstart (`docker-compose.yml` + `config/gateway.compose.toml`):
gateway + a local Ollama model in one command. GHCR badge in the READMEs.
## [0.2.2] - 2026-07-01
### Added
- **Latency benchmark** vs LiteLLM and Portkey: a reproducible harness (`bench/`,
instant mock backend + Apache Bench) and `BENCHMARKS.md`. Measured gateway overhead:
Cortiq ~57k req/s / p99 1 ms vs Portkey ~5.8k / p99 9 ms vs LiteLLM ~1.2k / p99 59 ms.
- **Accuracy benchmark**: task-type routing on natural-language prompts (`bench/accuracy.py`,
`bench/tasks.jsonl`) — allaigate semantic router 100% (37/37) vs a keyword heuristic 32%
(competitors have no semantic task router).
- **Router docs** (`docs/ROUTER.md` + `.ru.md`): what the allaigate/cortiq-router does, how
to point the gateway at it, API contract, pricing, and the current `LAUNCH1` $1 promo.
- **Docker image**: multi-stage `Dockerfile` (non-root, ~160 MB) + `docker.yml` workflow
publishing to `ghcr.io/infosave2007/cortiq-gateway` (`:latest`, `:X.Y.Z`, `:edge`;
linux/amd64 + arm64 on release tags). `docker run` works out of the box.
## [0.2.1] - 2026-06-30
### Added
- **OpenAI Models** (`GET /v1/models`): lists the pool + the virtual `cortiq-auto`.
- **OpenAI Completions (legacy)** (`POST /v1/completions`): non-streaming and streaming
(`text_completion`), routed through the same pipeline.
- **Native passthrough** (`POST /route`): returns the routing decision (task, complexity,
candidate models) without calling a model.
All inbound protocol adapters are now implemented.
## [0.2.0] - 2026-06-30
### Added
- **Streaming (SSE)** end-to-end: `POST /v1/chat/completions` with `"stream": true`
forwards provider SSE chunks verbatim, with `X-Cortiq-*` routing headers and token
usage tapped from the final chunk for statistics. Failover applies before the first
byte. Live streaming output in the admin Playground (`/admin/api/test/stream`).
- Cross-platform Windows binaries in releases (`x86_64-pc-windows-msvc`) and a
cross-platform CI test matrix (ubuntu/windows/macos); secure token RNG via `getrandom`.
- **Anthropic provider** (outbound): `POST /v1/messages` with `system` extraction,
`x-api-key` + `anthropic-version` headers, and streaming that translates Anthropic
SSE events into the unified OpenAI wire format.
- **Anthropic inbound** adapter: `POST /v1/messages` (non-streaming JSON and streaming
SSE), so Claude-native clients can target the gateway.
- **Embeddings**: `POST /v1/embeddings` inbound + provider `embed()`; embedding models
are now resolvable from the pool.
- **Semantic cache**: embedding-based cache that returns a stored answer for prompts
semantically near a previous one (cosine threshold, TTL, ring buffer), skipping the
model call. Partitioned by routing signature; hit-rate and cost saved are tracked and
shown on the dashboard; configurable from Settings (`[cache]`).
- **MCP server** (`POST /mcp`, JSON-RPC 2.0): exposes routing to MCP-native orchestrators
via two tools — `cortiq_route` (classify → decision) and `cortiq_chat` (route → answer).
## [0.1.0] - 2026-06-30
### Added
- **Embedded admin web console** at `/admin` — a build-free single-page app compiled
into the binary via `rust-embed`. Manage models, routing, protocols, virtual keys
and secrets from the browser; changes are hot-reloaded (no restart).
- **Multilingual UI** in 7 languages (en, ru, de, fr, es, zh, tr) with browser
auto-detection, plus light/dark themes.
- **Admin API** under `/admin/api/*` (Bearer admin token): config, models (CRUD +
probe), routing, protocols, settings, keys, secrets, stats, requests, and a
playground `test` endpoint.
- **Hot config reload** — config-derived state lives behind `arc-swap`; writes
validate → atomically rewrite the TOML → swap, with no restart.
- **Request statistics** — in-memory aggregates, per-minute time buckets, and a ring
buffer of recent requests, appended to a JSONL file and replayed on startup.
- **Prometheus `GET /metrics`** endpoint.
- **Secret store** (`config/secrets.toml`, mode `0600`) — provider keys can be set
from the UI without writing them into `gateway.toml`; values are never returned by
the API.
- English documentation as the primary language, with Russian translations
(`README.ru.md`, `docs/*.ru.md`).
### Changed
- The Anthropic provider now returns a clear error instead of panicking when called
(the adapter is still planned for v0.2).
## [0.0.1]
- Initial skeleton: OpenAI Chat Completions inbound adapter, OpenAI-compatible
outbound provider, routing table with fallback and graceful degradation, and
cost/token accounting.