Commit Graph
22 Commits
Author SHA1 Message Date
infosave2007 faddf945d7 fix(config): a foreign project must not capture the config path
default_config_path treated any directory holding Cargo.toml plus a
config/ folder as a gateway checkout, so running cortiq-gateway inside
an unrelated project died with "cannot read config config/gateway.toml"
instead of using the per-user home. Now ./config/gateway.toml counts
only when the file is actually readable, the repo-checkout heuristic
requires Cargo.toml to be cortiq-gateway's own, and everything else
falls through to $CORTIQ_GATEWAY_HOME / ~/.cortiq-gateway — typing
cortiq-gateway in any directory picks up the same configuration.
2026-08-08 17:57:45 +03:00
infosave2007 19f666e7b1 feat(cmf): auto-detect tool support of managed local servers
cortiq serve >=0.5.62 advertises the loaded model's capabilities in
/healthz ({"capabilities":{"tools":true}} when the chat template has a
tools branch). The gateway now reads that handshake when the managed
server becomes healthy and treats detected tool support as equivalent
to declared caps in the candidate filter — so a low-tier chain made of
a local model no longer widens agent (tools) traffic to cloud models
once the local server can actually drive tools.

Also adds an optional manual "caps" override to [[cmf.servers]] for
older serve builds that do not advertise capabilities.
2026-08-08 15:52:20 +03:00
infosave2007 057abaa95a feat!: API keys actually gate the gateway — auth, attribution, rate limits, budgets
The office-readiness audit found the foundation missing: configured API keys
were decorative. Any bearer — or none — passed every model endpoint, per-key
rate limits existed only in the form, and all real traffic was recorded as
"anonymous", leaving the per-key spend table permanently blank.

A middleware on the model surface (/v1/*, /route, /mcp) now enforces keys:
- no keys configured → open mode as before (the console already warns);
- keys exist → Bearer or x-api-key must match (401 invalid_api_key);
- per-key rate_per_min → fixed one-minute window, 429 with Retry-After;
- NEW per-key monthly_budget_usd → 429 insufficient_quota once the account's
  month-to-date spend crosses the ceiling;
- the matched account rides the request into stats — the keys table's spend
  columns finally see real traffic.

Month-to-date spend is tracked per account in stats and persisted in the
totals sidecar with a watermark (no double counting across restarts). The
key dialog gains a budget field; the table shows "budget $N/mo" in red once
exhausted. All verified live: 401 wrong/missing key, 200 + attribution with
the right key, 429 + Retry-After over the limit, 429 insufficient_quota on
the second request past a $0.000001 budget.

Also: the dashboard shows the OpenRouter credit balance (GET
/api/v1/credits via the stored key, cached 5 min, hidden without a key, red
under $5 — live: $16.18), the server drains connections on Ctrl-C instead
of dropping them, request bodies are capped at 32 MB, and docs/DEPLOY(.ru)
cover office deployment: systemd/launchd/Windows service, nginx/caddy TLS,
security checklist, backups. 10 strings ×7 languages.
2026-08-08 14:01:16 +03:00
infosave2007 dd864eed22 fix(cache): honest settings card — clean threshold, live stats, no-embedder warning
Full audit of the semantic cache against a live instance (mock embedder,
identical requests): the mechanism works — miss at 3.1s, then hits at 2ms
with entries/hits/saved-$ counting. Two honest gaps fixed:

- the threshold round-tripped through f32 and rendered as
  0.9200000166893005 in both the TOML and the settings input — the config
  field is f64 now (writes exactly what you typed) and the input rounds
  legacy values to 2 decimals;
- the card now shows proof of life (entries / hits / hit-rate / saved $)
  and, when the cache is enabled with NO embedding model available, a
  warning that it is silently doing nothing and how to fix it — previously
  that state looked identical to a working cache.

Also audited in the same pass, no changes needed: Prometheus /metrics
(toggle on/off applies live, 404 when off), and all 7 protocol switches
(each gates its endpoint instantly both ways). 4 strings ×7 languages.
2026-08-08 13:21:43 +03:00
infosave2007 788c87c565 feat(routing): linked complexity-boundary sliders — your own low/medium/high
The tier boundaries lived on the router and were global for every customer
of a hosted router; per-gateway tuning meant hand-editing score ranges. The
Routing page now has one track with two draggable handles (the low|medium
and medium|high boundaries) over three colored zones: handles push each
other (low can't cross medium — it shoves it along, and vice versa), the
numbers ride on the handles and the legend updates live, so nobody types
ranges by hand. Unchecked = the router's own bands, untouched behavior.

Under the hood the gateway re-maps the router's CONTINUOUS complexity score
with the user's boundaries ([routing.bands] low/medium in gateway.toml,
validated 0 < low < medium < 1) before tier chains, escalation and stats —
verified live: with bands at 0.70/0.72 a score-0.27 request that used to be
medium routed low, and back after restoring the defaults.

2 new strings in all 7 languages.
2026-08-08 09:48:36 +03:00
infosave2007 06813b3e97 feat(routing)!: task rules become per-complexity model maps
The single per-task «Complexity» pin was the wrong shape — what you actually
decide is WHICH model serves a task at WHICH complexity. A task rule is now a
per-tier map, edited in the console as rows of “model ▾ + complexity ▾” pairs
(plus ×/add) inside each task:

  [routing.tasks.code]
  low  = ["cheap-coder"]
  high = ["best-coder"]
  any  = ["generic-coder"]   # leads at every tier after the tier pick

Chain order: the task's tier-specific list → (at high: the global high chain,
so escalation still beats any-tier pins) → the task's any-tier list → the
global tier chain → default as the last resort; deduplicated. Plain-array
rules keep meaning "any tier" unchanged, and the short-lived {models, tier}
form from the previous commit is normalized into the map on load.

Verified live: legacy config migrated visibly in the editor, pairs survive
save+reload, a math prompt at low ignored the math@high pair, per-tier leads
confirmed by unit tests (19 passing).
2026-08-08 08:54:27 +03:00
infosave2007 95e560dc72 feat: per-key spend accounting and per-task complexity pinning
API keys page: the table now answers "which department spends what" — per-key
requests, tokens in/out and cost for a selectable window (24h/7d/30d) plus a
cumulative all-time cost column, joined from stats by the key's account.

The stats log stays bounded while the accounting survives: once stats.jsonl
exceeds 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. alltime = folded + in-file, a
record is never counted twice, verified end-to-end on synthetic data
(1.2 MB → 301 KB, totals identical before/after compaction and restarts).

Routing: a task rule can now PIN the complexity tier —
`[routing.tasks.legal] models=[...] tier="high"` (old plain-array rules parse
unchanged). The pinned tier replaces the router's verdict before the
escalation check and reports itself as route_source "router+task-rule@high".
The Routing page grows a per-task «Complexity» selector (Auto/low/medium/
high); verified live: a code prompt with a pinned high went
router+task-rule@high → the expensive model.

13 new strings in all 7 languages; docs/ROUTING(.ru) updated.
2026-08-08 08:42:11 +03:00
infosave2007 1bba427042 feat(keys): one-paste harness setup — Claude Code, Codex, OpenCode, Continue, Cline
The "connect your app" card in the API-keys view now leads with auto-setup
scripts for popular coding harnesses, each pre-filled with this gateway's URL
and the freshly created key:

  Claude Code — env exports (the gateway speaks Anthropic /v1/messages
      natively; the protocol is now ON by default, and when a config has it
      off the tab shows a warning with a one-click Enable button)
  Codex — appends a [model_providers.cortiq] block to ~/.codex/config.toml
  OpenCode — writes the provider into opencode.json
  Continue — appends the model to ~/.continue/config.yaml
  Cline — the exact values to paste into the extension settings

The gateway also detects which harnesses are already present on its machine
(binaries across PATH + common install dirs, config files under home) via
GET /admin/api/harnesses: installed tabs get a ✓, and the status line under
the snippet says "installed" or "already configured for this gateway" when
the harness config mentions cortiq. Works on Linux/macOS/Windows.

curl / python / javascript SDK snippets stay as before. 16 new strings in
all 7 languages.
2026-08-07 19:26:54 +03:00
infosave2007 54b73f1365 fix: honest costs in streams, oracle-sized timeouts, history & log cleanup
Cost showed $0 for every streamed request: the X-Cortiq-Cost-USD header is
written before generation, when the cost cannot be known. The gateway now
ships the final number in a closing SSE event (`cortiq_cost_usd`, alongside
the token estimate when the provider sent no usage), and the console prefers
it over the header. Money renders with 4 decimals, the last two in a smaller
font — $0.0006 stays readable instead of collapsing to $0.

Timeouts, both ends: the router default was 800 ms, silently degrading every
prompt the hosted router escalates to its oracle (~10 s) — now 15 s. Provider
clients had NO timeout at all (a dead OpenRouter host hung requests forever) —
now a 10 s connect timeout, with no overall cap so long generations and
streams are never cut.

Benchmark history: per-run × and a Clear-all button. Logs view: a clear
button backed by DELETE /admin/api/logs.
2026-08-07 19:15:27 +03:00
infosave2007 2ede0ee452 feat(routing): per-task rules on top of complexity tiers; hosted router is the default
The router has always returned two signals — task label and complexity tier —
but only the tier picked models. [routing.tasks] adds `label → ordered models`:
a matching rule's models lead the candidate chain and the tier chain follows as
fallback, so pinning translations to a local model never weakens failover.
Requests whose chain was headed by a task rule report
route_source "router+task-rule".

The Routing page grows a per-task editor built from the account's LIVE
taxonomy (GET /v1/taxonomies via the new /admin/api/routing/labels proxy;
built-in label set when the router is unreachable), with localized task names,
each task's share of 7-day traffic, "by complexity (no rule)" as the explicit
empty state, and custom labels for other taxonomies. Deleting a model scrubs
it from task rules like it always did from tiers.

Also fixes the default config pointing at a placeholder localhost:8080 router
with a nonexistent taxonomy — a fresh install now targets the hosted allaigate
router (IP endpoint, verify_tls off, data-assistant, 12 s oracle timeout) and
degrades gracefully until a key is set. Verified end-to-end against the live
router: translation → task-rule model, math → tier path.

16 new strings in all 7 languages.
2026-08-07 17:39:38 +03:00
infosave2007 32ce2f7ed7 chore: satisfy CI fmt/clippy gates; make the release publish step idempotent
Docker / publish (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
`cargo fmt --all -- --check` and `cargo clippy --all-targets -- -D warnings` are
CI gates and both were failing on this release's code.

- rustfmt across the files touched by the release
- `.split('/').last()` walks the whole iterator on a DoubleEndedIterator; use
  `.next_back()` (4 sites in the setup/featured + delete handlers)
- `set_progress` had two identical branches; the intent is "take the first
  reading, then only move forward", which is one condition

release.yml published unconditionally on a version tag, so a version already on
crates.io (released by hand, or a re-run of the job) failed the workflow — a
version can never be re-uploaded. It now checks the registry and skips instead.
2026-08-06 18:18:21 +03:00
infosave2007 9ec8861464 release: v0.2.37 — zero-argument startup, first-run wizard, real download progress
Startup no longer needs arguments: the config is created on first launch, a busy
port is swapped for a free one, the console is open by default, and the browser
lands on a setup wizard that downloads a ready-made .cmf model and wires it into
routing (managed server + routing.default) when you press Finish.

Download progress was reported from curl's own meter, which prints a full
0→100% bar for the -L redirect hop before the transfer starts — so every job
showed 100% and then hung. Progress now comes from bytes on disk against the
size from the HuggingFace tree (or a HEAD request).

The UI and the gateway also disagreed about the output file name, so a finished
download never marked its card as downloaded; the gateway now returns the exact
stem and the UI sends it back verbatim for download, progress and delete.

Featured models are read live from the infosave HF account in both the wizard
and Import, and all 432 UI strings now exist in every one of the 7 languages.
2026-08-06 17:56:42 +03:00
infosave2007 1414ad1b52 release: v0.2.31 - O(1) attention knobs, reasoning toggle & admin token persistence 2026-07-22 11:12:40 +03:00
infosave2007 94c8551206 feat(release): v0.2.30 — per-model generation, O(1) attention, MTP & reasoning parameters 2026-07-22 10:40:44 +03:00
infosave2007 0c20eb34f0 chore(release): v0.2.8 — multiple managed local models
- The gateway can now run several local .cmf models at once: a [[cmf.servers]]
  list, one supervised `cortiq serve` per entry on its own port, each registered
  in the pool and independently routable/pinnable. The single legacy
  local_model/local_port still works (back-compat via effective_servers()).
- Settings → Local models is now a rows editor (id · path · port + check · threads
  · GPU) with add/remove and per-server serving status.
- Deleting a managed model stops just its server and removes its file — unless
  another model still points at the same file (shared-file guard).
2026-07-09 12:00:10 +03:00
infosave2007 7c49510c46 chore: cargo fmt — fix lint 'Format' CI job (red since v0.2.3)
Whole-repo rustfmt drift; behaviour unchanged (whitespace/layout only).
All test platforms already passed; this greens the lint job.
2026-07-08 19:32:35 +03:00
infosave2007 d48349a144 feat: fresh install starts with an empty model pool
- config.rs: validate() no longer errors on zero models — an empty pool is
  allowed (warns instead), so the admin console comes up on a fresh install and
  the user adds models in /admin -> Models (or points [cmf] at a local .cmf).
- gateway.example.toml: ships an EMPTY pool (no seed cmf-local/local-embed, empty
  routing), so `cp example -> gateway.toml && run` shows an empty list — what a
  new user expects.
2026-07-08 17:39:43 +03:00
infosave2007 6cf2608fc8 admin: surface CMF worker threads + GPU toggle in Settings
Expose the two speed knobs in the /admin Settings panel instead of TOML-only:
- [cmf].gpu (new) -> exports CMF_GPU=1 to the managed cortiq serve (Metal/wgpu).
  On Apple Silicon decode is memory-bandwidth bound so this is ~neutral for
  generation (helps prefill); off by default.
- [cmf].threads (added earlier) now has a UI number input.

- config.rs: CmfCfg.gpu bool (serde default false).
- cmf_runtime.rs: spawn_serve sets CMF_GPU=1 when gpu is on.
- admin/mod.rs: put_settings flags needs_restart on threads/gpu change (the
  server is spawned once at startup with those env vars).
- settings.js: threads input + gpu checkbox + save payload + speed hint.
- i18n en/ru: threads/gpu/speedNote keys (other locales fall back to en, as the
  whole cmf section already does there).
2026-07-08 16:18:31 +03:00
infosave2007 e6d1a630c9 cmf: parallelise the managed local server (CMF_THREADS)
The gateway spawned `cortiq serve` with no thread hint, so the runtime ran the
decode loop single-threaded (~0.25 tok/s on a 15B CPU model). Export CMF_THREADS
from a new [cmf].threads config field (default 8) in spawn_serve.

Measured: 0.25 -> 0.81 tok/s (~3.2x) on qwopus-vbit7 (15B). Scaling is sublinear
(GatedDeltaNet state recurrence is serial per token, Amdahl-bound); a real jump
needs the GPU path.
2026-07-08 16:18:31 +03:00
infosave2007 8dd77ca7ff feat(cmf): integrate CMF as a managed local model server
Run models in the CMF (.cmf) format locally, via the native `cortiq`
CLI from crates.io — no Python and no external model server.

- cmf_runtime: install/update `cortiq-cli` from crates.io, spawn and
  health-check `cortiq serve` as a managed local backend.
- import: model-import jobs shell out to the native `cortiq convert`
  (HF repo -> .cmf, quant mapped Q8_2F->q8_2f / Q8_ROW->q8 / Q4->q4);
  the Python converter is used only for advanced/exotic options.
- pipeline: local-first routing — serve from the local CMF model when
  the router is disabled/unreachable, with a "local only" hard switch.
- registry/state: register the local CMF server as an OpenAI provider
  and track local candidates.
- config: data-dir resolution + CmfCfg (auto-install/update, managed
  server, host/port/model) and relaxed validation for the managed model.
- admin + settings UI (en/ru): "Local models (CMF)" settings card.
2026-07-08 16:18:31 +03:00
infosave2007 5759de153b feat: semantic (embedding-based) response cache
- cache.rs: cosine-similarity store (threshold, TTL, ring buffer), partitioned by
  routing signature; tracks hit rate and model cost saved
- pipeline: embed routing text, look up before the model call (route_source=cache on
  hit), insert successful responses; stats record cache hits
- config [cache] + admin settings card; dashboard shows hit-rate and savings
- embedding model resolved from the pool (or first kind="embedding")
2026-06-30 17:04:53 +03:00
infosave2007 cbc385f6c7 feat: universal LLM gateway with embedded multilingual admin console
- OpenAI-compatible gateway with intelligent routing via cortiq-router (allaigate)
- Embedded build-free admin SPA at /admin: 7 languages (en/ru/de/fr/es/zh/tr),
  light/dark themes, hot config reload (no restart)
- Admin API, per-request statistics + Prometheus /metrics, secret store, playground
- English documentation (primary) with Russian translations (*.ru.md)
2026-06-30 10:51:37 +03:00