CSRFMiddlewareWithCORS changed CSRF handling for every route and, with a
wildcard allowlist, disabled the Sec-Fetch-Site check. Restore the
original CSRFMiddleware: clients that authenticate with validated header
credentials, or with a single-use WebSocket ticket, are already exempt,
so the motion routes need no relaxation.
The motion WebSocket upgrade no longer reads the CORS allowlist to
decide whether a cross-origin upgrade is acceptable. It accepts one only
with a valid ticket or validated header credentials; everything else
gets the same-origin default.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
FLAG_MOTION and FLAG_UPSCALE were both declared as 1 << 26 on separate
branches, and a merge combined the two const blocks without a conflict.
Add a spec that checks every flag from GetAllModelConfigUsecases() is a
single bit that no other flag uses, so a collision fails the tests.
FLAG_MOTION itself moves to 1 << 27.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Add the GEM-X native backend with live pose and root displacement output, bounded duplex WebSocket streaming, session authorization, metrics and usage accounting. Share the embedded bidirectional RPC channel adapter across streaming backends.
Include backend build and gallery integration, API documentation, schemas and tests.
Assisted-by: Codex:gpt-6
Signed-off-by: Richard Palethorpe <io@richiejp.com>
(cherry picked from commit 016dc3d71a)
A queued import goes through the gallery installer, so it persists
recommended inference defaults and installs the backend when automatic
backend installation is on, while the synchronous write stores the body
as posted. Say so in the docs and the endpoint description, list the
reference forms that are queued (URLs, hf:// and owner/repo, artifacts),
and regenerate the swagger files.
Assisted-by: Claude:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The model editor polled a queued import forever: every status error was
swallowed and retried, and nothing stopped the loop when the editor
closed. A job the server had already dropped left the editor stuck on
"saving".
Move the job-status reading into a shared helper used by the import page
and the editor. It classifies cancelled, failed (error field or an
"error:" message) and finished jobs once, gives up after 10 consecutive
status failures or 2 hours of waiting, and stops on unmount. The editor
now shows the failure instead of looping.
Assisted-by: Claude:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Cancelled gallery jobs also set processed. Handle cancellation before
completion so imports do not announce success or open a missing model.
Assisted-by: Codex:gpt-6
Signed-off-by: Plamen K. Kosseff <p.kosseff@gmail.com>
(cherry picked from commit 0f54bdcb38)
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Review round at aec658a99:
- [P1] acquisition is decided by PrimaryArtifactSpec: managed artifacts
(hf:// repos, bare owner/repo, explicit artifacts:) enqueue with no
file entries so the worker's InstallModel runs the artifact binding
and repository materializer — flattening them into file entries routed
repositories through the single-file downloader and skipped the
materializer. Plain remote files keep the legacy file path; malformed
references fail at admission. Covered by endpoint specs and a
worker-level materializer spec.
- [P2] both ModelEditor create paths wait on the returned job before
announcing success and navigating; download failures surface as save
errors instead of after a success toast.
Assisted-by: Claude:claude-fable-5
Signed-off-by: Plamen K. Kosseff <p.kosseff@gmail.com>
(cherry picked from commit 742971c2c4)
- swagger: annotate POST /models/import (untyped body: documenting it
as config.ModelConfig would inline the whole type graph, ~5.5k
generated lines; the description spells out the dual response) and
regenerate the spec
- tests: pin the admission contract of the queued branch — job id +
status URL, job queryable before the worker runs, config not written
at admission, preload-matching filenames, checksum passthrough, and
the untouched synchronous path
- docs: the Writing YAML section explains when Create queues and what
the API answers in each case
Assisted-by: Claude:claude-fable-5
Signed-off-by: Plamen K. Kosseff <p.kosseff@gmail.com>
(cherry picked from commit aec658a998)
Manual /models/import with downloadable assets ran synchronously; it now
enqueues like a gallery install and the import UI polls the job progress.
Assisted-by: Claude:claude-fable-5
Signed-off-by: Plamen K. Kosseff <p.kosseff@gmail.com>
(cherry picked from commit 7420e2e71b)
The Qdrant client requires a newer lufia/plan9stats, so go.mod already
pins it. Remove the entries for the superseded version.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
With DOT, EUCLID or MANHATTAN the backend returns Qdrant's raw scores,
not similarity-like values. Router, face and voice stores compare
against cosine thresholds, so log a warning at load when another
metric is configured, and document the behaviour on the stores page.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
StoresSet sent the whole batch in one Upsert under a single
request_timeout_ms, so a large batch could hit the timeout even though
every point was fine. Send at most 500 points per request and apply
the timeout to each request.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
A TLS 1.3 floor rejects Qdrant endpoints that only speak TLS 1.2, such
as some managed deployments and proxies. Use TLS 1.2 as the minimum.
When Load fails after the client was created, it closed the client but
left it set, so Free closed it a second time. Clear it after closing.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Store loading picked the backend and the loader's process slot in one
function, which let an explicit `backend` argument collide with the
store's own config and made traces report a fixed "local-store".
This changes /stores/* behaviour for every store:
- An explicit `backend` that differs from the one named in the store's
model config now gets its own loader slot, `<store>@<backend>`, so it
no longer reuses (or evicts) the process of the configured backend.
Naming the configured backend, or one of its aliases, keeps the
store's own slot.
- When the config names a different backend than the explicit one, the
config's `options` are not passed on, since they are meant for the
configured backend. A config without `backend:` still passes its
options to the explicit backend.
- Backend names are compared and reported in canonical form (aliases
resolved, lower case) through the new model.CanonicalBackend, and
traces record the backend that was actually used instead of always
"local-store".
Backend loading itself uses CanonicalBackend for alias handling; type
aliases keep their existing effect.
Signed-off-by: Anush008 <mail@anush.sh>
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Titles and prepositions in front of a name are taken as well, and using
both directions on one name group swallows a following number.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
It shared order 215 with extend_to_next_word, so the settings UI had no
defined order between the two. Move it to 216 and shift the protected
terms fields. Pin that a number in front of a hit is never taken.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The mirror of extend_to_next_word. A name model often tags the surname with
high confidence and scores the first name in front of it below min_score,
so "Gast Greta Waldmeister" became "Gast Greta [REDACTED:ner:LASTNAME]".
extend_to_previous_word lists entity groups whose accepted detections also
cover the word directly before them on the same line. The preceding word
must consist of letters (plus '-' and the apostrophe): a word with digits -
an amount, a date, a room number - is never taken, nor is a protected term,
and the search never crosses a line break.
Docs: operations/middleware.md and advanced/model-configuration.md.
Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Stefan Walcz <stefan.walcz@walcz.de>
(cherry picked from commit b1845b8bed)
Protected terms apply to the NER tier only and not to a pattern
detector; extend_to_next_word also takes numbers and dates after a hit.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
A protected term that is one half of "Anna-Lena" or "O'Brien" is not a
whole word: shielding it left the term in the clear next to a masked
rest. Hyphen and apostrophe joiners between letters now count as
in-word, as they do for the word extensions.
When a detection spanned the placeholder and the name after it, the
space beside the protected term stayed inside the mask. Trim whitespace
from the surviving pieces.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Look up the compiled matcher by comparing the raw term list instead of
normalising, sorting and joining it on every call, compile outside the
lock, and evict the least recently used entry instead of the oldest.
Whitespace inside a term now also matches no-break, narrow no-break and
other Unicode spaces, so "Hotel Seeblick" written with a no-break space
is still protected. Close the term file with the repo's errcheck-clean
idiom, and note that the multi-segment path scans the joined document.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Term-file reloads retain each compiled matcher indefinitely. Limit the
cache to 32 lists and verify that evicted lists still match on reload.
Assisted-by: Codex:GPT-6
Signed-off-by: Stefan Walcz <stefan.walcz@walcz.de>
(cherry picked from commit dce0eacfcf)
A name model reads many business names as surnames. Measured with
privacy-filter-multilingual on 24 synthetic German receipts and
invoices: the supplier name ("Hotel Seeblick", "Café Lindenhof", ...)
was masked as LASTNAME (0.78-0.98) or FIRSTNAME in 9 of 12 receipts
that carried one. The supplier is exactly what the upstream model needs
to categorise a receipt, and no score floor helps at those confidences.
pii_detection.protected_terms (inline) and
pii_detection.protected_terms_files (files inside the models path, one
term per line, '#' comments, re-read when size or mtime change) list
values that must pass unchanged. Each whole-word, case-insensitive
occurrence is replaced by a neutral placeholder before the detector
runs; every detection is mapped back onto the original text with the
placeholder parts cut out, so a protected term is never masked or
blocked, the detector never sees it as context, and event offsets keep
addressing the original text. A word extension (extend_to_next_word)
is dropped rather than run into a protected term.
Terms shorter than three characters are ignored. A missing or
unreadable file is logged and skipped: fewer protected terms means more
masking, never less. Paths are verified to stay inside the models path.
Opt-in, empty by default. Covered by Ginkgo specs in
core/services/routing/pii/protected_test.go (detector-visible text,
exact restoration, straddling detections, whole-word/case/line-wrap
matching, the segment path, file loading and reload), a YAML round-trip
spec, registry entries and the middleware/model-configuration docs.
Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Stefan Walcz <stefan.walcz@walcz.de>
(cherry picked from commit 01f71772a5)
The client secret is sent to token_url, so a plain http endpoint would
expose it. Reject non-https URLs except for loopback hosts, which keeps
local identity providers and tests working. Document the restriction,
recommend client_secret_env over a literal secret, and note that rotating
a *_env secret needs a restart because sessions and cached tokens are
keyed by configuration, not by the environment value.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
JSON log handlers bypass String and serialize exported credentials.
Implement LogValue so remote server entries use the same redacted
representation in text and JSON logs. Extend the regression test
to cover both handlers.
Assisted-by: Codex:GPT-6
(cherry picked from commit f93781c307)
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
A slow failed refresh can cross the token's safety margin.
Check the current time before reusing the token and start retry
backoff when the failed fetch finishes. Apply backoff before the
first successful fetch too.
Add regression tests for these cases and document per-process caches.
Assisted-by: Codex:GPT-6
(cherry picked from commit a849e872ec)
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Remote MCP servers could only be authenticated with a static bearer
token. Deployments that put their MCP servers behind an OIDC provider
(authentik, Keycloak, Entra ID, ...) issue short-lived service tokens
instead, so LocalAI had to be given a long-lived key or could not
connect at all.
A remote server entry now accepts an optional "oauth2" block
(token_url, client_id/client_id_env, client_secret/client_secret_env,
scopes, endpoint_params). The token is fetched with the
client_credentials grant by an http.RoundTripper installed on the MCP
client's HTTP client, so streamable HTTP and SSE transports both use it.
The token is cached and refreshed at half of its lifetime; if a refresh
fails the current token is kept until 30s before expiry, after which
requests fail with an error instead of going out unauthenticated.
"token" and "oauth2" are mutually exclusive and inconsistent blocks are
rejected when the model config is loaded. Static tokens behave as
before. MCPRemoteServer now implements fmt.Stringer so the existing
debug logs that print whole server entries no longer write the token
or client secret in clear text.
Signed-off-by: Stefan Walcz <stefan.walcz@walcz.de>
Assisted-by: Claude Code:claude-opus-5-5
(cherry picked from commit 954b9d25f7)
The sd_embed branch dropped negative_prompt for every pipeline, which
discarded the user's negative prompt for Flux, where no negative embeds
are produced. Only drop it when negative_prompt_embeds is present, and
move the check to a helper that has unit tests.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Address models tend to tag a postal code with high confidence but miss
or under-score the town right after it. Measured with
privacy-filter-multilingual on 24 synthetic German invoices: the postal
code was masked every time, while the town after it stayed in the clear
in 6 of 18 cases (4 not detected at all, 2 tagged CITY at 0.43/0.47,
below the configured floor). Masking the code alone leaves the pair half
redacted: "[REDACTED:ner:ZIPCODE] Ludwigshafen".
pii_detection.extend_to_next_word lists entity groups whose accepted
detections also cover the word that directly follows them on the same
line, with the same action and group. Horizontal whitespace is skipped,
a line break ends the search, and nothing is extended when no word
follows, so a hit never grows over bare whitespace or onto the next
line (or across the segment separator on the multi-message path).
The option is opt-in and empty by default; existing detector configs
behave exactly as before. Covered by Ginkgo specs in
core/services/routing/pii/extend_test.go (fail without the change), a
YAML round-trip spec, a registry entry for the model editor, and the
middleware and model-configuration docs.
Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Stefan Walcz <stefan.walcz@walcz.de>
(cherry picked from commit c67c0782f8)
A chat completion sent to an embedding model whose config sets
use_tokenizer_template runs the thinking probe, and persistProbedReasoning
writes the probed reasoning slots back into the shared model config.
EmbedderFingerprint hashed the whole config, so that single request moved
the fingerprint and every knn router built on the embedder failed with
ErrLiveEmbeddingMismatch (HTTP 503) until LocalAI was restarted.
Reasoning settings never change the embedding space, so leave them out of
the hash.
Docs-Not-Needed: internal fingerprint computation, no user-facing config, flag or API change
Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Stefan Walcz <stefan.walcz@walcz.de>
(cherry picked from commit 46163dc1d4)
The entry installs the Qwen-Image 2.1 diffusion weights under the
llama-cpp backend as a chat model. llama.cpp cannot load these weights.
Image generation is already served by the qwen-image-2.1-q4_k-ggml and
qwen-image-2.1-q8_0-ggml entries, which use stablediffusion-ggml.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The gallery agent added a second entry named embeddinggemma-2 that
installs the Unsloth multimodal build as a chat model. The curated
embeddinggemma-2 embedding entry already exists, so the duplicate name
shadowed it. Remove the duplicate.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The checksums for four pinned Hugging Face files did not match the
x-linked-etag reported for the pinned revision, so installs of these
entries would fail verification:
- qwen3.8-27b-humanlike-chat (Q4_K_M)
- humanizer-12b-q4 (Q4_K_M)
- twil-lm3-pro-q4 (Q4_K_M)
- swift-1.5-qwen3.8-27b-q4 and -q8 (mmproj F16 at the a1614465 revision)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Complete the existing GSQ-RCO choices with the 3.50-bit build and its
vision projector. Pin downloads and verify both SHA256 values against
Hugging Face LFS metadata and download headers.
Correct the older quality description and document variant selection.
Assisted-by: Codex:gpt-6
(cherry picked from commit 70a2bd2c21, mudler/LocalAI#12651)
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Add Q4_K_M and Q8_0 builds with the F16 vision projector for llama.cpp.
Pin downloads and verify SHA256 against HF metadata and response headers.
Document variant selection and keep the embedded chat template.
Assisted-by: Codex:GPT-6
(cherry picked from commit a3a910f833, mudler/LocalAI#12650)
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Add Q4_K_M and Q8_0 builds with the vision projector and installation examples.
Assisted-by: Codex:gpt-6
(cherry picked from commit 15d6d0b6e7, mudler/LocalAI#12645)
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Add the publisher GGUF builds and installation examples for this
Gemma 4 E4B conversational fine-tune.
Assisted-by: Codex:gpt-6
(cherry picked from commit ef4c9218ad, mudler/LocalAI#12642)
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Offer intermediate quantizations of Swift 1.5 Qwen3.8 27B alongside
the existing Q4 and Q8 builds. Keep the F16 vision projector and
publisher license notice, and pin the new downloads to one revision.
Assisted-by: Codex:gpt-6
(cherry picked from commit c7a903838c, mudler/LocalAI#12640)
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Add Q4_K_M and Q8_0 GGUF builds with the matching vision projector.
Pin downloads and verify SHA256 against LFS metadata and HTTP headers.
Document installation and explicit variant selection.
Assisted-by: Codex:gpt-6
(cherry picked from commit 472cddcaea, mudler/LocalAI#12635)
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Add Q4_K_M and Q8_0 builds with the F16 vision projector.
Pin downloads and document installation and template limitations.
Assisted-by: Codex:GPT-6
(cherry picked from commit 75a91386ec, mudler/LocalAI#12631)
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Add Q4_K_M and Q8_0 builds with the F16 vision projector for llama.cpp.
Pin downloads, verify SHA256 values, and document variant selection.
Assisted-by: Codex:gpt-6
(cherry picked from commit 19c30fd2ef, mudler/LocalAI#12630)
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Add Q4_K_M and Q8_0 builds with the F16 vision projector.
Pin downloads and document installation and variant selection.
Assisted-by: Codex:gpt-6
(cherry picked from commit 411ceb7588, mudler/LocalAI#12627)
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Add official Q4 and Q8 builds with the vision projector so users can
translate images with Index-Translate-9B. Pin and verify all artifacts,
and document installation alongside the existing text-only builds.
Assisted-by: Codex:gpt-6
(cherry picked from commit dcfdb69ca1, mudler/LocalAI#12626)
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Add Q4_K_M and Q8_0 builds with the vision projector for llama.cpp.
Pin artifacts to a verified revision and document installation.
Assisted-by: Codex:gpt-6
(cherry picked from commit aed2278e2a, mudler/LocalAI#12624)
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Add the 27B Clef decision model with Q4_K_M and Q8_0 builds and a
matching vision projector. Pin artifacts and document installation.
Assisted-by: Codex:gpt-6
(cherry picked from commit ab88ee821e, mudler/LocalAI#12613)
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Add Q4_K_M and Q8_0 builds with a pinned vision projector for native
llama.cpp decisions. Document installation and variant selection.
Assisted-by: Codex:gpt-6
(cherry picked from commit 1081971697, mudler/LocalAI#12611)
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Add Q6_K and Q8_0 builds with a shared vision projector for llama.cpp.
Pin downloads and verify SHA-256 values against Hugging Face metadata
and HTTP headers. Document explicit variant installation.
Assisted-by: Codex:gpt-6
(cherry picked from commit d5cb742607, mudler/LocalAI#12608)
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Add Q4_K_M and Q8_0 builds with the Q8 vision projector for native
llama.cpp decisions. Pin artifacts and document installation.
Assisted-by: Codex:gpt-6
(cherry picked from commit caad8cae92, mudler/LocalAI#12607)
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>