The confirm header names what the administrator acted on: addonId@digest
for an install, the add-on ID for an edit or a revoke, "blanket" and "deny"
for the two switches. The operation ID is optional on every write but the
install; the receiving host makes one when the caller sent none.
Preview and page statuses are allowed, not_allowed, blocked or unsupported
in both. A preview names what an upgrade adds, using the identity of the
installed package. The page carries the library, the new optional access and
the required operations that keep an add-on from running. "keep" relabels
named blanket grants as the administrator's own. The blanket switch ignores
scope options when it is off.
Assisted-by: Claude:claude-sonnet-5-5 go gofmt ginkgo swag
Add the host access routes to the /api/instructions text of the add-ons
group. Add a spec that no MCP tool maps to them and that nothing under
pkg/mcp names them, so an agent cannot reach them through a tool.
Assisted-by: Claude:claude-sonnet-5-5 go gofmt ginkgo
Seven routes: read the host access state, preview an install, install or
upgrade with an approval, edit or revoke the grants of an add-on, change the
blanket approval and change the deny rules (with a dry run).
Reads take an administrator session or API key. Every write takes only the
administrator's own browser session: same-origin fetch and the confirm
header equal to the operation ID. An API key, bearer token, MCP session or
tool call gets 403 not_human_surface. The dry run of a deny change is a read.
The first path segment host-grants is reserved as an add-on ID, and the
routes are listed in the feature registry.
Assisted-by: Claude:claude-sonnet-5-5 go gofmt ginkgo
The owner now serves the host access calls: read the grants, preview what an
install would ask for, install or upgrade with an approval, edit or revoke
the grants of one add-on, change the blanket approval and the deny rules.
Every write needs the human surface mark and is refused when the deployment
locks the store. Typed refusals (revision_conflict, approval_stale,
blocked_by_policy, locked_by_deployment and others) cross the carrier
unchanged, so every frontend gives the answer of the owner.
A frontend carries the mark as a signed field of the forwarded request, and
only for a host access call. The owner honors it and nothing else: a
frontend with its own flags or store still forwards, and an owner without the
calls is reported as unavailable with its reason.
The preview issues a single-use nonce bound to the credential, the add-on,
the package digest and the grants revision. The install must present it.
Assisted-by: Claude:claude-sonnet-5-5 go gofmt ginkgo
StripPathPrefix and the redirect from / and /browse still read
X-Forwarded-Prefix from any peer, after BaseURL and BasePathPrefix
were gated on --trusted-proxies. A client could make the server strip
a path prefix or add one to a redirect.
Gate both on the same trusted-proxy resolver. The resolver is now
stamped into the request before StripPathPrefix runs, and the check is
exported as ForwardedHeadersTrusted. The explorer has no
--trusted-proxies option and keeps its old behavior by trusting every
peer for this one header. The docs say that the prefix needs a trusted
proxy.
Assisted-by: Claude:claude-sonnet-5-5 go-test
A chat on a model whose config has no context_size got a window of zero,
so the harness fell back to its own default for compaction and for the
context meter, while the backend actually runs with its default window.
The meter and the compaction trigger then disagreed with the model.
Report the window that a request can use, the same value that
/v1/models/capabilities gives (backend.EffectiveRequestContextSize). A
model with no local config, such as a remote one, still gives zero.
Assisted-by: Claude:claude-sonnet-5-5 go-test
go test -race on core/http/endpoints/localai reported data races
between POST /api/settings and the goroutines that read the result:
ApplyRuntimeSettings wrote chats_egress_audit_days and the
chats_loop_* limits as plain fields while the audit prune and the loop
runner read them through the Effective getters.
Guard those six fields with one RWMutex, taken by the getters and by
the registry setters. A new spec applies the settings while four
goroutines read them and fails under -race without the lock.
Assisted-by: Claude:claude-sonnet-5-5 go-test
The handle leaves the table before the audit hook runs, so a loaded
machine could read the list too early. Wait for the outcome.
Assisted-by: Claude:claude-sonnet-5-5 go
An administrator can now grant host operations when an add-on is
installed or upgraded. The manager checks the approval against the
package it actually stages: the digest must match, and only operations
the verified manifest declares and the host registers are granted, never
a denied one. A required operation that is neither held nor approved
refuses the install with approval_required, or blocked_by_policy when a
deny rule forbids it. An upgrade that needs more required operations is
held and the old version keeps running. Optional operations are never
granted without approval, and a manifest that asks for fewer operations
shrinks nothing.
Grants are written to a GrantStore before the journal commits the
operation and removed if it fails, and they count only once the
operation completed. Uninstall deletes them after the journal. A start
reconciles the store with the journal. The automatic upgrade never
approves; only the blanket hook of the store can. HostGrantsChanged
stops an add-on that lost a required operation and retries one that was
refused.
Assisted-by: Claude:claude-sonnet-5-5 go
The install dialog and the host access page need a title, one sentence
and a risk word for each operation an add-on may ask for. Add
DescribeOperation next to the supported list, and a test that fails when
an operation has no description.
Assisted-by: Claude:claude-sonnet-5-5 go
Show the host operations an add-on asks for in the install dialog, hold
an upgrade that asks for more until an administrator approves it, and add
a Host access tab with the grants of each add-on, the deny rules and the
blanket switch. All strings are in every locale.
Assisted-by: Claude:claude-sonnet-5-5 playwright eslint
Shape the answers of the host-grants routes for display, build the
approval that an administrator sends with an install or an upgrade, and
let the install flow carry it. A typed refusal keeps its type and its
details so the pages can say what happened.
Assisted-by: Claude:claude-sonnet-5-5 playwright eslint
Token help now works: a button opens GitHub's fine-grained token page,
the steps are open while there is no connection, and a table lists the
permissions for read only and for write modes. The page says it does not
answer mentions yet.
Errors sit under their own field. The repository list splits on lines and
commas only, so "not a repo" is one entry and is named in the message.
The connection name defaults to the first repository.
The write mode cards each carry one consequence line, and "Allowed" is
now "Acts without asking". The Test result is drawn per repository in the
row, with a tone, a busy line, and a plain text when GitHub refuses the
token. The unit tests and a browser spec cover the page.
Assisted-by: Claude:claude-sonnet-5-5 node playwright eslint
An application a spec starts on its own, such as the isolated ones of
the router specs, took the shared test transport over and pointed its
file transfers at a table with no blob store. Which specs ran before
decided if the file specs of Telegram and Slack passed, and they failed
in CI with "addon owner unavailable". The transport now keeps the table
of the first application.
Assisted-by: Claude:claude-sonnet-5-5 go ginkgo
An expiry ended a file handle while a put created the pipe and its
cancel function, which the race detector reports. The expiry must not
wait for a put that holds the handle lock, so guard the two fields with
a small lock of their own.
Assisted-by: Claude:claude-sonnet-5-5 go
The broker copied the allowed prefixes when an add-on started, so a
change of grants needed a restart of the add-on. Take an Authority
instead and ask it on each call: a revoke or a deny applies to the next
call, and a call that already runs finishes. A refused call answers the
same text whatever the reason; the reason goes to the log.
The supervisor opens the host endpoint when the add-on holds an
operation or its manifest declares one, so an add-on with an optional
operation and no grant starts degraded and a later grant works at once.
The broker now exists whenever the add-on manager does. The manager,
the apps list and the tool filters read the same Authority, which is the
startup policy until another one is set. Admission also tells a denied
operation from a missing grant.
Assisted-by: Claude:claude-sonnet-5-5 go
The audit hook runs on the goroutine that closes a transfer while the
spec reads the list, which the race detector reports. Read the outcomes
through a locked copy.
Assisted-by: Claude:claude-sonnet-5-5 go
A connector kind can declare agentOptional. A binding of such a kind may
have no agent: it holds the token, the resource list and the write mode
for the tools of a chat, and never receives or answers a message. The
GitHub kind declares it, so a person no longer makes a throwaway agent
only to store a token.
Without an agent the host refuses every setting that needs one:
listening, accepting senders, an allowlist, a notification chat,
talk-back channels, permissions, pairing and trigger approval. The
places that read the agent of a binding handle the empty value:
inbound routing, the polling loop, notifications, pairing, the capability
check (deny by default) and the grant switch. A kind that does not
declare the key still needs an agent on every binding.
The binding page makes the agent field optional for such a kind with
help text, and the list shows "Credentials only".
Assisted-by: Claude:claude-sonnet-5-5 go gofmt ginkgo node protoc swag
A poll that began before the add-on stopped could still be counted, so
the spec for a binding without its add-on failed at times. Take the
count after a short quiet and require that it does not grow.
Assisted-by: Claude:claude-sonnet-5-5 go ginkgo
The Tools list of the agent form gained six grant rows, so the count is
14 and the Triggers switch needs an exact name. Two folder specs also
went to the edit page, which has no facts panel, instead of the agent
page.
Assisted-by: Claude:claude-sonnet-5-5 playwright
A binding needs a running add-on that serves its kind, so the real
server of the UI tests, which runs none, answers 400 instead of
storing an inactive binding. The spec now checks that answer and that
nothing leaks. Switching and listing stay covered by the add-on specs.
Assisted-by: Claude:claude-sonnet-5-5 playwright
The server renamed the group title, but the English locale kept "Web"
and the Tools menu reads the locale first. The menu, the home composer
and the agent form showed a name that the specs and the server did not.
Assisted-by: Claude:claude-sonnet-5-5 playwright
NewAuthority reads the store on every call through an immutable view, so
a UI grant, revoke or deny applies to the next host call without a
restart. The file and flag deny rules do not depend on the store.
Assisted-by: Claude:claude-sonnet-5-5 go
The hostgrants package holds the owner document of host operation grants
made in the UI, the UI deny rules, the blanket switch and the hints used
to pre-tick a reinstall. Writes use compare-and-set on a revision and
operation receipts, with atomic persist like the host settings store.
The package also holds the pure policy logic: deny (file, flag, UI) over
file prefix grants over exact UI grants, with identity and pending-entry
checks, the grant plan (approved, declared, registered, not denied),
blanket evaluation, startup reconciliation and an fsynced audit file.
Assisted-by: Claude:claude-sonnet-5-5 go
A policy file may now carry a deny object. Deny rules name an add-on ID
or "*" and list prefixes, exact operation names or the single token "*".
Schema 1 files parse as before; a deny key in a schema 1 file, or a
version above 2, is an error that names the problem.
The selected file is now always opened, even when a flat
--addon-host-ops list replaces its grants, because a deny must not
vanish when someone adds a flag. An invalid selected file still stops
startup.
Add --addon-host-deny (LOCALAI_ADDON_HOST_DENY), unioned with the file,
and the --addon-host-blanket and --addon-host-ui-grants flags with their
config options.
Assisted-by: Claude:claude-sonnet-5-5 go
Bindings saved before permission enforcement have an empty capability
list, so file upload, triggers, memory and talk-back stay denied until
the owner sets them. Give those bindings the set that "Allow the usual"
grants, one time.
An empty list is stored the same whether the owner never chose or
cleared every switch, so the store keeps a migration record instead of
a flag on each binding. The record is claimed atomically with a lease,
so several frontends starting together run it once, and a finished
record is never claimed again: later empty lists are the owner's choice.
Each binding is written only while it still has no capabilities, so a
save made at the same moment is not overwritten.
Only bindings whose kind is running and lists permissions are changed;
sensitive capabilities are never granted. One log line per binding and
one summary. LOCALAI_SKIP_BINDING_CAPS_MIGRATION skips it.
Assisted-by: Claude:claude-sonnet-5-5 go gofmt protoc
The broker, the supervisor and the manager each copy the grant map at
start. An Authority answers what an add-on may call right now, so a
later change can apply to the next call without a restart.
Add the interface, the Reason type and EffectiveSet, with a static
implementation over today's prefix map. Nothing uses it yet and
behavior does not change.
Assisted-by: Claude:claude-sonnet-5-5 go
The Indonesian chat namespace was completed, so the ratchet of the
completeness gate failed the UI unit tests until the baseline went down.
Assisted-by: Claude:claude-sonnet-5-5 node
The trigger form has a Permissions section that lists the act.*
grants like the grants of an agent: a plain-words line on each,
sensitive ones marked, and a grant the chosen agent does not hold
cannot be ticked. It says in a callout when a run only reads and why:
no acts, the agent holds none of them, the proposal said read only,
or a read only connection made it. The details page shows the same,
the trigger list marks a read only trigger, and the proposal card has
a Permissions row, so the person sees the acts before pressing Create.
The editor always sends the acts it loaded, so saving a form never
drops them silently. New strings are in every locale.
Assisted-by: Claude:claude-sonnet-5-5 node playwright
A trigger run with nobody present only reads unless its trigger holds
act.* grants, and until now only the REST API could set them. An agent
that proposes a trigger can now ask for acts too:
- propose_trigger takes an optional acts list. The names must be
act.* grants the agent of the chat holds; anything else is refused
and nothing is stored. A read_only proposal cannot carry acts.
- The proposal stores the list. Confirming it, in Studio, by a plain
yes or by confirm CODE, grants exactly that list. No tool changes
the acts of a trigger afterwards. Two proposals that differ only in
their acts are kept apart.
- The text of a proposal in a messaging app now has a Permissions
line, in plain words with sensitive classes marked: read only, or
what a run may do. The confirmation repeats it. A proposal that asks
for acts is never set up without a yes, whatever the approval mode.
- The trigger REST object gains acts_held, read_only and
read_only_why, computed with the same rules as the run, so a client
can show why a run only reads.
Assisted-by: Claude:claude-sonnet-5-5 go ginkgo
A new chat with the sandbox switched off in the composer showed no
"switched off" notice. GET /api/chats/draft/addons took only the model
and the agent, so it always reported the default commands state.
Accept the draft's switches as comma-separated query parameters
(addons_off, builtins_off, builtins_on), check them as the create request
does, and apply them to the draft record before the commands state is
worked out. The composer sends its choices each time an add-on or a
built-in group is switched, so the notice follows the switch.
Assisted-by: Claude:claude-sonnet-5-5 go-test playwright
An agent with no grant list keeps its non-sensitive connector features
(trigger tools, talk-back, files from senders). Granting any one built-in
makes the list the whole answer, so the others stop working. The editor
had no grants list at all, so nobody could see this.
Add a Permissions section to the agent editor with the declared grants.
When the first grant is ticked on a saved agent that had none, a notice
names the connector grants that would turn off and a button adds them.
Also fix the files.upload text. The gate enforces it on files a sender
attaches to a message, not on the agent sending files out, so the grant
is now "Receive files".
Assisted-by: Claude:claude-sonnet-5-5 go-test playwright
BaseURL and BasePathPrefix read X-Forwarded-Proto, X-Forwarded-Host,
X-Forwarded-Prefix and Forwarded from any caller when LOCALAI_BASE_URL
is not set. A client could pick the scheme and host that end up in the
SPA <base href>, generated links and redirects.
Gate them on the trusted-proxy resolver (--trusted-proxies): the headers
count only when the TCP peer is trusted. An empty list trusts nobody, so
the request's own scheme and host are used. LOCALAI_BASE_URL still wins
over everything for the origin.
This changes behavior for setups behind a proxy that rely on these
headers without setting LOCALAI_BASE_URL: they must now list the proxy
in --trusted-proxies. The docs say so.
Assisted-by: Claude:claude-sonnet-5-5 go-test
The speech-to-speech path labels its transcription WAV with the session
rate for client commits, so resampling first handed it 16 kHz audio under
a 24 kHz header. Resample only in the chained pipeline, and say so in the
docs.
Assisted-by: Claude:claude-sonnet-5-5 go-test
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
(cherry picked from commit 82cc411af54cf884e4a44a3e96292a323ba5611c)
The committed-utterance WAV is written with a fixed 16 kHz header. The VAD
commit path stores audio that is already resampled to 16 kHz (vadCommit),
but the client-commit path (input_audio_buffer.commit) passed the raw
buffer at the session input rate. For a 24 kHz OpenAI Realtime session the
backend decoded 24 kHz audio as 16 kHz (1.5x speed/pitch): garbage or empty
transcripts, then input_not_committed on the follow-up response.
commitUtterance now resamples from session.InputSampleRate to
localSampleRate, so both paths hand 16 kHz audio to the 16 kHz header.
This replaces the earlier approach of this PR (header at the session rate),
which mislabelled the already-resampled VAD audio (review feedback).
Tests go through the real paths with a 24 kHz session: commitUtterance and
a server_vad commit via vadTick; both must produce a 16 kHz WAV of the
resampled length. The client-commit spec fails without the fix.
Fixes#12747
Signed-off-by: nexxtmobile.de <git@nexxtmobile.de>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 05ba891da6)
(cherry picked from commit 56e2b6d0cc5cbf1944ef50735f7b499d998eb25c)
Connecting a connector must reach both MCP transports. Adds specs that resolve a
Streamable HTTP server promptly and a legacy SSE server, through the same
mcptransport.Connect path that agent runs use.
Signed-off-by: rossg <ross@golder.org>
(cherry picked from commit 4feaf9db39)
(cherry picked from commit fab3cfa21c13747e03115d7cc39f2466962f16e8)
Clients written for llama-server, vLLM or SGLang switch template variables
per request with a top-level chat_template_kwargs object, most often
{"enable_thinking": false}. OpenAIRequest had no such field, so it was
dropped: with a Qwen3-style template thinking stayed on, the reply landed
in message.reasoning and content came back empty.
Fold the field's scalar values into the request metadata, so it takes the
path metadata already takes (gRPCPredictOpts, ResolveChatTemplateKwargs).
Booleans become "true"/"false", strings pass through, numbers become their
decimal string; nested values are skipped. An explicit metadata key wins.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Cristi An <c4xp@msn.com>
(cherry picked from commit 2cd7425e87)
(cherry picked from commit 5e86ad6bd5286cc8db398d278adaa32e562223ca)
qwen3's chat template wraps <think> in {% if enable_thinking %}, but the
prefill detectors scan the raw template without evaluating the condition.
With streaming and no tools, the synthetic prefill made the Go-side extractor
classify the whole answer as reasoning whenever the client sent
metadata.enable_thinking=false.
Skip prefill detection when the request disables thinking. Real
<think>...</think> blocks that a model emits anyway are still extracted from
the content itself.
Fixes#12939
Assisted-by: ZCode:deepseek-flash
Signed-off-by: xianshujun <xianshupang@gmail.com>
(cherry picked from commit cee7ed4b32)
(cherry picked from commit 29618866d07a05624934bf9f595c95a68c938461)
Signed-off-by: Dedy F. Setyawan <dedyfajars@gmail.com>
(cherry picked from commit 2413f19b7d)
Conflict resolved against current master: keep the strings master already
has for chat.json and add the translations this change supplies for keys
master lacks. Keys added to master after the PR was opened are still
untranslated and fall back to English.
Assisted-by: Claude:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
(cherry picked from commit 8d4799d91b33b2af204e7cc5386d8b984d09f04b)
A model can cite a plausible deep link it never opened, written from
memory. The client cannot tell it from a link a search tool returned,
because server-side MCP tool results never reach it.
evidence_check.urls compares every http(s) link of the final answer,
bare or inside a Markdown link, with the URLs of the request (system
prompt, system, user and tool messages) and of the request's successful
MCP tool results. Links are compared without scheme, fragment, "www."
and trailing slash; a link counts as retrieved when it equals an evidence
URL or is a shorter form of one at a path or query boundary. Unbacked
links (at most five) are listed in an appended note; urls_note replaces
the default note, with {urls} as the placeholder for the list.
Assistant messages are not evidence, so an invented link is marked again
when the client sends it back on the next turn.
Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Stefan Walcz <stefan.walcz@walcz.de>
(cherry picked from commit 523e8cd3c4)
ChatEndpoint gained a compressor parameter after the spec was written, so
the openai test package no longer compiled.
Assisted-by: Claude:claude-sonnet-5-5 go golangci-lint
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The comment in sourceURLs named ExecuteMCPToolCall, which the tool
consumer matrix test (core/services/addons/tools) reads as a marker of a
file that discovers or executes tools. evidence.go only reads tool
output, so reword the comment instead of classifying the file.
Docs-Not-Needed: comment-only change, no behaviour change
Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Stefan Walcz <stefan.walcz@walcz.de>
(cherry picked from commit 1043d3cbc4)
Keep balanced parentheses in source URLs and decode MCP text arrays before
extracting links. This preserves Wikipedia paths and escaped query
separators instead of appending truncated citations.
Assisted-by: Codex:gpt-6
Signed-off-by: Stefan Walcz <stefan.walcz@walcz.de>
(cherry picked from commit 9b81cb20ab)
A system prompt that tells a model to look things up first does not stop
it from answering from its own knowledge, for example when the question
comes with a picture.
profile.require_tool makes the first model call of a chat request that
offers the profile's server-side MCP tools use tool_choice "required".
Every follow-up call of the server-side tool loop is made with the
client's tool_choice again: a "required" that stayed in place would make
the model call a tool after every tool result and never answer until the
iteration limit.
With a tokenizer template the backend enforces "required" with a grammar
built from the chat template's tool-call format. Where LocalAI builds the
tool-call grammar itself, the first call's grammar leaves out the
no-action function instead.
The first call is not forced when no server-side tool was offered, when
the client set tool_choice or function_call itself, or when the request
does not end with a user message, so a request returning a client tool's
result cannot bounce tool calls between client and server.
Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Stefan Walcz <stefan.walcz@walcz.de>
(cherry picked from commit 2801938aac)
A research profile asks the model in its system prompt to cite its
sources, but a model can answer from a search result without naming a
single link, and the client never sees the server-side tool results that
held them.
evidence_check.sources appends one line with the distinct http(s) URLs
returned by the request's successful MCP tool calls (at most five, in
order of appearance) when the final answer names no URL itself. An answer
that already links something is left unchanged, and nothing is added when
no tool result holds a URL, so a link is never made up. The label is
configurable through evidence_check.sources_label (default "Sources") for
profiles that answer in another language.
It runs in the existing evidence gate, on the same streaming and
non-streaming paths as the number note.
Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Stefan Walcz <stefan.walcz@walcz.de>
(cherry picked from commit 4ee6ff1395)
Four recent changes added English strings without the other locales, so
the locale completeness gate failed in the UI unit tests. Add the keys
to the eight locales. The baseline stays as it is.
Assisted-by: Claude:claude-sonnet-5-5 node
The package already registers a suite in p2p_suite_test.go. A second
RunSpecs call made the whole package test binary fail with "RunSpecs
called more than once" even though every spec passed.
Assisted-by: Claude:claude-sonnet-5-5 go golangci-lint
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Reserve capacity when selecting a worker and release it when forwarding
ends. Completed connections no longer bias routing toward new workers.
Keep active counts through offline periods and choose randomly on ties.
Add regression tests and document per-instance TCP connection counts.
Fixes#12992
Assisted-by: Codex:gpt-6
(cherry picked from commit 66cf106490)
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>