Files
LocalAI/core/http/endpoints/studio/addons.go
T
Ettore Di Giacinto 9fafea8fdf fix(chats): show the network switcher and say why execution status is unknown
The Network row of the Tools menu could never show the Off, Registries and
All switcher: the host stopped setting `applies` because isolation alone does
not prove network-policy enforcement, and nothing else set it.

Let the provider report enforcement in its typed execution status
(`networkPolicyEnforced`). The host sets `applies` only when that report is
true and the status is available and isolated. The Sandbox reports it for the
gVisor and KVM backends, whose guests reach the network only through the proxy
that asks the host. Providers that do not report it keep the "not enforced"
text.

When the host cannot read the status, give the unknown state a reason
(`unknown_reason`) in the API and the Tools menu: owner unreachable, not
reported, stale, invalid, not selected, no answer or reported unknown. Log the
class of each failure, rate limited. After a failure that a restart of the
add-on owner can cause, let a long turn bind and read again, at most once every
five seconds, instead of keeping the first failure for the whole turn.

The Sandbox now reports "unavailable" with a reason when it started without
settings, and explains a backend it does not know.

When the Sandbox document skills are hidden because the Sandbox is not known to
be isolated, say so in the skill list, in the turn note and in the
`load_skill` error, instead of "This chat has no skills now".

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 08:39:30 +00:00

67 lines
3.5 KiB
Go

package studio
import (
"net/http"
"github.com/labstack/echo/v4"
"github.com/mudler/LocalAI/core/schema"
studiosvc "github.com/mudler/LocalAI/core/services/studio"
)
func addonList(c echo.Context, h *Handlers) error {
list, err := h.svc.Addons(c.Request().Context(), userID(c), c.Param("id"))
if err != nil {
return failFor(c, err)
}
cmds, err := h.svc.CommandRunner(c.Request().Context(), userID(c), c.Param("id"))
if err != nil {
return failFor(c, err)
}
return writeAddonList(c, list, cmds)
}
func writeAddonList(c echo.Context, list []studiosvc.AddonInfo, cmds studiosvc.Commands) error {
out := schema.ChatsAddonList{Addons: make([]schema.ChatsAddon, 0, len(list)), Commands: string(cmds.State), CommandsReason: cmds.Reason,
CommandsIsolation: cmds.Isolation, CommandsProvider: cmds.Provider, CommandsConsent: cmds.Consent,
CommandsFallback: cmds.Fallback, CommandsFallbackReason: cmds.FallbackReason}
if v := cmds.ExecutionStatus; v != nil {
out.ExecutionStatus = &schema.ChatsExecutionStatus{Readiness: v.Readiness, Isolation: v.Isolation, Explanation: v.Explanation, UnknownReason: v.UnknownReason, Generation: v.Generation, Incarnation: v.Incarnation}
}
for _, a := range list {
out.Addons = append(out.Addons, schema.ChatsAddon{ID: a.ID, Tools: a.Tools, Off: a.Off})
}
return c.JSON(http.StatusOK, out)
}
// ListChatAddons lists the add-ons a chat can use.
// @Summary List the add-ons a chat can use.
// @Description The add-ons whose tools the signed-in user may use now, each with the chat's switch, and whether the chat can run commands at its next turn (commands: available, off, unavailable, none, or switched_off when the chat switched the built-in group shell off). When commands is available, commands_provider says who serves bash (builtin: directly on this machine with no isolation and with its network, or the add-on's ID) and commands_isolation says isolated or host; commands_consent is ask when each command waits for a yes in the chat, and commands_fallback names an add-on that would serve but does not (commands_fallback_reason: off, or why it cannot run). Without the add-on host the list is empty.
// @Tags chats
// @Param id path string true "conversation id"
// @Success 200 {object} schema.ChatsAddonList "The add-ons and their switches"
// @Failure 400 {object} schema.ErrorResponse "The request is not valid"
// @Failure 404 {object} schema.ErrorResponse "No such conversation, or it is another user's"
// @Router /api/chats/{id}/addons [get]
func (h *Handlers) ListChatAddons(c echo.Context) error { return addonList(c, h) }
// SetChatAddons sets the add-ons a chat has switched off.
// @Summary Switch the tools of add-ons on and off for a chat.
// @Description The list replaces the earlier one. The next turn of the chat gets the tools of the add-ons that are on.
// @Tags chats
// @Param id path string true "conversation id"
// @Param request body schema.ChatsAddonSwitches true "the IDs of the add-ons that are off"
// @Success 200 {object} schema.ChatsAddonList "The add-ons and their switches now in force"
// @Failure 400 {object} schema.ErrorResponse "The request is not valid"
// @Failure 404 {object} schema.ErrorResponse "No such conversation, or it is another user's"
// @Router /api/chats/{id}/addons [put]
func (h *Handlers) SetChatAddons(c echo.Context) error {
req, ok := bind[schema.ChatsAddonSwitches](c, "off is required")
if !ok {
return nil
}
if err := h.svc.SetAddonsOff(c.Request().Context(), userID(c), c.Param("id"), req.Off); err != nil {
return failFor(c, err)
}
return addonList(c, h)
}