Files
LocalAI/core/services/studio/egress.go
T
Ettore Di Giacinto fc6abfca8c refactor(studio): name the network from the add-on's declaration
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>
2026-11-03 10:58:46 +00:00

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
}