Files
LocalAI/core/services/studio/addoncontext.go
T
Ettore Di Giacinto 8c5153f80f refactor(studio): name the context file tool by what it does
The tool that reads the details of a context file topic was called
memory_read. The loader is generic: any component or add-on that declares
a context file uses it, and nothing in it knows which one. Rename the tool
to context_read so the host does not name a feature of one add-on.
Behaviour and limits stay the same.

Assisted-by: Claude:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 13:57:28 +00:00

106 lines
3.6 KiB
Go

package studio
import (
"context"
"slices"
"sort"
"github.com/mudler/LocalAI/core/services/studio/contextfiles"
"github.com/mudler/LocalAI/pkg/addons/contract"
)
// Add-on context files. An add-on can declare small index files of the user's
// home (capabilities.contextFiles in its manifest). The host loads them into
// the prompt of a chat and serves their topics with context_read, with the same
// loader as any other source. The add-on is not called: it only names paths,
// and the host reads them as the user.
//
// A turn decides which add-ons contribute, as it does for their skills: a
// stopped add-on, one the chat switched off and one the chat's agent does not
// grant contribute none. The files stay where they are while the add-on is
// stopped or removed; they belong to the user.
//
// Distributed mode: the files are in the workspace store that every frontend
// reads, and which add-ons run is read per turn from the broker. Nothing is
// cached here.
// AddonContextSet is the context files one add-on declares, as the chats see
// them.
type AddonContextSet struct {
AddonID string
Files []contract.ContextFile
// Running is true when the add-on can serve now: installed, enabled and
// running.
Running bool
}
// AddonContextLister is implemented by a broker that knows the context files
// of the add-ons. It lists the installed add-ons that declare some and that
// the user can see, with whether each one runs. It is optional; a broker
// without it gives no add-on context files.
type AddonContextLister interface {
AddonContextFiles(ctx context.Context, user string) ([]AddonContextSet, error)
}
// sources turns the declaration of an add-on into loader sources. A file the
// loader would refuse is left out.
func (set AddonContextSet) sources() []contextfiles.Source {
var out []contextfiles.Source
for _, f := range set.Files {
src := contextfiles.Source{ID: set.AddonID, Scope: contextfiles.Scope(f.Scope), Path: f.Path, Cap: f.Cap}
if src.Validate() == nil {
out = append(out, src)
}
}
return out
}
func (s *Service) addonContextSets(ctx context.Context, user string) []AddonContextSet {
if s.d.AddonTools == nil {
return nil
}
lister, ok := s.d.AddonTools.(AddonContextLister)
if !ok {
return nil
}
sets, err := lister.AddonContextFiles(ctx, user)
if err != nil {
s.log.Warn("studio: add-on context files unavailable", "user", user, "error", err)
return nil
}
sort.Slice(sets, func(i, j int) bool { return sets[i].AddonID < sets[j].AddonID })
return sets
}
// addonContextSources returns the sources the add-ons bring to a turn: those
// of the add-ons that run, that the chat did not switch off and, for a chat
// with an agent, that the agent grants.
func (s *Service) addonContextSources(ctx context.Context, user string, addonsOff []string, sc *scope) []contextfiles.Source {
if !s.d.Files.Available() {
return nil
}
var out []contextfiles.Source
gate, _ := s.d.AddonTools.(AddonGate)
for _, set := range s.addonContextSets(ctx, user) {
if !set.Running || slices.Contains(addonsOff, set.AddonID) || (sc != nil && !sc.addons[set.AddonID]) {
continue
}
if gate != nil && gate.Unavailable(ctx, user, set.AddonID, true) != "" {
continue
}
out = append(out, set.sources()...)
}
return out
}
// allAddonContextSources returns the sources of every add-on the user can see,
// running or not: the files are the user's, and the Memory page lists them
// whether or not the add-on runs now.
func (s *Service) allAddonContextSources(ctx context.Context, user string) []contextfiles.Source {
var out []contextfiles.Source
for _, set := range s.addonContextSets(ctx, user) {
out = append(out, set.sources()...)
}
return out
}