mirror of
https://github.com/mudler/LocalAI.git
synced 2026-11-07 13:03:20 -05:00
A chat now enables a set of connections for an add-on whose tools run with
a credential, and the model names one of them in each call. This applies to
every add-on that declares a credential in its manifest.
- The Tools menu row of the add-on lists the connections with a checkbox
each. The saved agent names a set too (an empty set means each chat
chooses). A chat that a connector binding started keeps that binding
alone. With one connection, it is the set. With none enabled, the tools
are not offered.
- With one connection enabled the tools are unchanged. With two or more,
the host adds a required `connection` argument to every tool of the
add-on in the definitions it gives the model: an enum of the connection
names (the display name folded to lower case, digits and hyphens; the
description keeps the display name). The add-on never sees the argument.
- The name is checked against the set the chat has now, not against all the
connections of the person. A missing or unknown name returns a sentence
that lists the allowed names, and nothing is sent. A write tool never runs
without the name. Text in a tool result cannot widen the set, and the
owner checks the chosen binding against the set again.
- The resource list of the named connection is the only one that applies.
Results, the approval card and the text for a pending change name the
connection; the audit line has the binding ID.
- A connection name must be unique per person and add-on among the
credential-only connections, compared by name fold. Older duplicates get
a numeric suffix in the model's list.
- The stored chat, draft and agent values move from one binding ID per
add-on to a list of IDs. A single string is read as a set of one in the
stores and in every request, so older data and clients keep working.
PUT /api/chats/{id}/connections takes `connections`; the older
`connection` field still works.
Assisted-by: Claude:claude-sonnet-5-5 go gofmt ginkgo node eslint playwright swag
104 lines
3.8 KiB
Go
104 lines
3.8 KiB
Go
package studio
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/mudler/LocalAI/core/services/studio/connsets"
|
|
"github.com/mudler/LocalAI/core/services/studio/session"
|
|
)
|
|
|
|
// The composer of a new chat shows the Tools and Skills menus before the chat
|
|
// exists. It lists what a chat on the chosen model (and saved agent) could
|
|
// switch, keeps the person's choices as a draft and sends them with the
|
|
// create request (CreateInput.Switches), so the chat is stored with them.
|
|
|
|
// DraftTools lists what a new chat on model, with the saved agent agentID when
|
|
// it is set, could use, every switch in its default state. The agent's model,
|
|
// when it has one, replaces model, as Create does.
|
|
func (s *Service) DraftTools(ctx context.Context, user, model, agentID string) (ToolsPanel, error) {
|
|
rec, err := s.draftRecord(ctx, user, model, agentID)
|
|
if err != nil {
|
|
return ToolsPanel{}, err
|
|
}
|
|
// The add-on gate reads the execution status. A draft has no chat, so the
|
|
// scope has no conversation and the status says "draft".
|
|
ctx, closeScope := s.executionScope(ctx, principal(user), rec.ID)
|
|
defer closeScope()
|
|
return s.toolsFor(ctx, user, rec)
|
|
}
|
|
|
|
// DraftAddons lists the add-ons of a new chat as DraftTools does, and whether
|
|
// it could run commands at its first turn. sw are the switches the person made
|
|
// in the composer so far (only the add-ons, the built-in groups and the opt-in
|
|
// groups count): the chat would start with them, so the commands state and the
|
|
// add-on switches answer for them, for example "switched off" when the
|
|
// sandbox group is off. They are checked as Create checks them.
|
|
func (s *Service) DraftAddons(ctx context.Context, user, model, agentID string, sw Switches) ([]AddonInfo, Commands, error) {
|
|
rec, err := s.draftRecord(ctx, user, model, agentID)
|
|
if err != nil {
|
|
return nil, Commands{}, err
|
|
}
|
|
clean, err := s.cleanSwitches(Switches{AddonsOff: sw.AddonsOff, BuiltinsOff: sw.BuiltinsOff, BuiltinsOn: sw.BuiltinsOn})
|
|
if err != nil {
|
|
return nil, Commands{}, err
|
|
}
|
|
rec.AddonsOff = orNil(clean.AddonsOff)
|
|
rec.BuiltinsOff = orNil(clean.BuiltinsOff)
|
|
rec.BuiltinsOn = mergeGroups(rec.BuiltinsOn, clean.BuiltinsOn)
|
|
rec.Connections = s.knownConnections(ctx, user, sw.Connections)
|
|
ctx, closeScope := s.executionScope(ctx, principal(user), rec.ID)
|
|
defer closeScope()
|
|
list, err := s.addonsFor(ctx, user, rec)
|
|
if err != nil {
|
|
return nil, Commands{}, err
|
|
}
|
|
commands, err := s.commandsFor(ctx, principal(user), rec)
|
|
if err != nil {
|
|
return nil, Commands{}, err
|
|
}
|
|
return list, commands, nil
|
|
}
|
|
|
|
// draftRecord is the record a new chat would have, with no ID and no switch.
|
|
func (s *Service) draftRecord(ctx context.Context, user, model, agentID string) (session.ChatRecord, error) {
|
|
rec := session.ChatRecord{Principal: principal(user), Kind: session.KindChat, Model: model, AgentID: agentID}
|
|
if agentID == "" {
|
|
return rec, nil
|
|
}
|
|
if s.d.Agents == nil {
|
|
return rec, ErrAgentsOff
|
|
}
|
|
ag, err := s.d.Agents.Get(ctx, principal(user), agentID)
|
|
if err != nil {
|
|
return rec, err
|
|
}
|
|
if ag.Model != "" {
|
|
rec.Model = ag.Model
|
|
}
|
|
// The opt-in groups the agent starts with are on in the draft.
|
|
rec.BuiltinsOn = mergeGroups(rec.BuiltinsOn, ag.BuiltinsOn)
|
|
return rec, nil
|
|
}
|
|
|
|
// knownConnections keeps the entries of a draft's connection sets that name
|
|
// connections the user has now. A draft that outlived a deleted connection
|
|
// then shows none selected instead of failing.
|
|
func (s *Service) knownConnections(ctx context.Context, user string, m connsets.Sets) connsets.Sets {
|
|
var out connsets.Sets
|
|
for addon, ids := range m {
|
|
var known []string
|
|
for _, id := range connsets.Dedup(ids) {
|
|
if s.checkConnections(ctx, user, connsets.Sets{addon: {id}}) == nil {
|
|
known = append(known, id)
|
|
}
|
|
}
|
|
if len(known) > 0 {
|
|
if out == nil {
|
|
out = connsets.Sets{}
|
|
}
|
|
out[addon] = known
|
|
}
|
|
}
|
|
return out
|
|
}
|