mirror of
https://github.com/mudler/LocalAI.git
synced 2026-11-06 12:33:16 -05:00
The chat's network view, the administrator view and the line the model reads about the network of its commands now take their name from the capabilities.network an add-on declares, or from the title of its commands tool when it declares none. The sentences no longer say "Sandbox" or "the sandbox". The API gains a network object with the title and the preset titles next to the existing addon field. The text of the built-in bash and of the host-exec setting names an add-on that provides bash instead of one add-on. Assisted-by: Claude Code:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
104 lines
3.9 KiB
Go
104 lines
3.9 KiB
Go
package studio
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"github.com/mudler/LocalAI/core/services/studio/egress"
|
|
"github.com/mudler/LocalAI/core/services/studio/egress/audit"
|
|
"github.com/mudler/LocalAI/core/services/studio/session"
|
|
)
|
|
|
|
// The network of the commands an add-on runs. The environment has no network
|
|
// device: the only way out is the add-on's proxy, which asks this service
|
|
// about each connection through the host operations egress.check and
|
|
// egress.audit (see egresscheck.go). The limits are the administrator's
|
|
// runtime settings; the chat's preset, rules and cards live in its record.
|
|
|
|
// DefaultEgressAuditDays is how long the audit keeps a line when the setting
|
|
// chats_egress_audit_days is not set.
|
|
const DefaultEgressAuditDays = 30
|
|
|
|
// egressSettings returns the limits in force now. Without the dependency the
|
|
// ceiling is off: no command reaches the network.
|
|
func (s *Service) egressSettings(ctx context.Context) (egress.Settings, error) {
|
|
if s.d.EgressSettings == nil {
|
|
return egress.Settings{Ceiling: egress.PresetOff}, nil
|
|
}
|
|
return s.d.EgressSettings(ctx)
|
|
}
|
|
|
|
// egressAudit returns the audit store; an in-memory one when none was given.
|
|
func (s *Service) egressAudit() audit.Store { return s.egressAuditStore }
|
|
|
|
// forkEgress is the network state a fork copies: the preset and the allow
|
|
// rules. The cards and the deny rules belong to the chat that asked.
|
|
func forkEgress(src egress.ChatState) egress.ChatState {
|
|
out := egress.ChatState{Preset: src.Preset}
|
|
for _, r := range src.Rules {
|
|
if r.Allow {
|
|
out.Rules = append(out.Rules, r)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// PruneEgressAudit removes the lines of the network audit older than the
|
|
// retention (chats_egress_audit_days). In a cluster one frontend runs it, under
|
|
// an advisory lock; it is safe to run on several, since removing old lines
|
|
// twice changes nothing.
|
|
func (s *Service) PruneEgressAudit(ctx context.Context) (int64, error) {
|
|
return s.egressAudit().Prune(ctx, s.now().Add(-s.auditRetention()))
|
|
}
|
|
|
|
// auditRetention is how long the audits of the commands keep a line.
|
|
func (s *Service) auditRetention() time.Duration {
|
|
days := DefaultEgressAuditDays
|
|
if s.d.EgressAuditDays != nil {
|
|
if d := s.d.EgressAuditDays(); d >= 1 && d <= 365 {
|
|
days = d
|
|
}
|
|
}
|
|
return time.Duration(days) * 24 * time.Hour
|
|
}
|
|
|
|
// now is the clock of the network decisions.
|
|
func (s *Service) now() time.Time {
|
|
if s.d.Clock != nil {
|
|
return s.d.Clock()
|
|
}
|
|
return time.Now()
|
|
}
|
|
|
|
// egressLine is the line of the turn's note that tells the model what the
|
|
// network of its commands is: the preset that applies (the chat's, its
|
|
// parent's for a sub-agent, its agent's for an unattended run) under the
|
|
// ceiling. name is the network of the add-on that runs the commands.
|
|
func (s *Service) egressLine(ctx context.Context, rec session.ChatRecord, web bool, name string) (string, error) {
|
|
set, err := s.egressSettings(ctx)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
pol, err := s.egressPolicyOf(ctx, rec, "")
|
|
preset := egress.PresetOff
|
|
ceiling := egress.Min(set.Ceiling, egress.PresetAll)
|
|
capped := false
|
|
if err == nil {
|
|
preset = egress.Min(pol.chat.Preset, ceiling)
|
|
capped = pol.chat.Preset.Rank() > ceiling.Rank()
|
|
}
|
|
if ceiling == egress.PresetOff {
|
|
// Hint would tell the model to ask the user to turn the network on.
|
|
return egress.DescribeLimit(preset, ceiling, egress.BuiltinRegistries(), false, name), nil
|
|
}
|
|
// A card appears only in a chat a person watches (not an unattended run
|
|
// or a sub-agent), and only for a host the ceiling admits beyond the
|
|
// preset: a wider ceiling, or a private host with the private switch on.
|
|
attended := rec.Origin == "" && rec.Kind != session.KindSubAgent
|
|
asks := attended && (ceiling.Rank() > preset.Rank() || set.AllowPrivate)
|
|
if capped {
|
|
return egress.DescribeLimit(preset, ceiling, egress.BuiltinRegistries(), asks, name) + " " + egress.Hint(preset, web, name), nil
|
|
}
|
|
return egress.Describe(preset, egress.BuiltinRegistries(), asks, name) + " " + egress.Hint(preset, web, name), nil
|
|
}
|