Files
LocalAI/core/application/addon_policy_reader.go
T
Ettore Di Giacinto 3f5b23e3e7 feat(addons): add a host ceiling for connector files to the owner policy
The key connector_files_ceiling_bytes sets the largest file a connector
moves. Zero means the default of 100 MB. The key is always written, and
a policy document stored before it reads as the default. The connector
manager reads the ceiling from the owner policy at each resolution.

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

92 lines
3.1 KiB
Go

// SPDX-License-Identifier: MIT
package application
import (
"context"
"fmt"
"maps"
"github.com/mudler/LocalAI/core/config"
"github.com/mudler/LocalAI/core/services/addons/gateway"
"github.com/mudler/LocalAI/core/services/studio/egress"
"github.com/mudler/LocalAI/pkg/addons/contract"
)
// studioEgressSettings reads a fresh bounded owner policy on every request.
// The no-addon server retains its existing built-in settings; an enabled
// addon deployment must never substitute frontend configuration on failure.
func (a *Application) studioEgressSettings(ctx context.Context) (egress.Settings, error) {
if err := ctx.Err(); err != nil {
return egress.Settings{}, err
}
if !a.applicationConfig.Addons.Enabled {
return a.localStudioEgressSettings(), nil
}
snapshot, err := a.readAddonHostPolicy(ctx)
if err != nil {
return egress.Settings{}, err
}
p := snapshot.Policy
ports, err := config.ParseEgressPorts(p.Ports)
if err != nil {
return egress.Settings{}, fmt.Errorf("owner egress ports: %w", err)
}
extra, err := config.ParseEgressHosts(p.ExtraRegistries)
if err != nil {
return egress.Settings{}, fmt.Errorf("owner egress registries: %w", err)
}
hosts, err := config.ParseEgressAllowedHosts(p.AllowedHosts, true)
if err != nil {
return egress.Settings{}, fmt.Errorf("owner egress hosts: %w", err)
}
out := egress.Settings{Ceiling: egress.Preset(p.Ceiling), Default: egress.Preset(p.Default), AllowPrivate: p.AllowPrivate, Ports: ports, ExtraRegistries: extra}
if p.Ceiling != "" && !out.Ceiling.Valid() || p.Default != "" && !out.Default.Valid() {
return egress.Settings{}, gateway.ErrUnavailable
}
for _, h := range hosts {
out.AllowedHosts = append(out.AllowedHosts, egress.AllowedHost{Host: h.Host, Port: h.Port})
}
return out, nil
}
// studioToolPins never substitutes frontend pins when the owner is unavailable.
func (a *Application) studioToolPins(ctx context.Context) (map[string]string, error) {
if err := ctx.Err(); err != nil {
return nil, err
}
if !a.applicationConfig.Addons.Enabled {
return maps.Clone(a.applicationConfig.ChatsToolProviders), nil
}
snapshot, err := a.readAddonHostPolicy(ctx)
if err != nil {
return nil, err
}
return maps.Clone(snapshot.Policy.ToolProviders), nil
}
// studioConnectorFilesCeiling is the host ceiling of a connector file, from
// the owner policy. Without add-ons there is no owner policy and no connector
// to move a file, so the default stands.
func (a *Application) studioConnectorFilesCeiling(ctx context.Context) (int64, error) {
if err := ctx.Err(); err != nil {
return 0, err
}
if !a.applicationConfig.Addons.Enabled {
return contract.DefaultFileCeilingBytes, nil
}
snapshot, err := a.readAddonHostPolicy(ctx)
if err != nil {
return 0, err
}
return snapshot.Policy.FilesCeiling(), nil
}
// readAddonHostPolicy is shared by host consumers; the DTO keeps revision
// and policy together and excludes owner-only migration provenance.
func (a *Application) readAddonHostPolicy(ctx context.Context) (gateway.HostSettingsPolicy, error) {
if a.addonGateway == nil {
return gateway.HostSettingsPolicy{}, gateway.ErrUnavailable
}
return a.addonGateway.ReadHostSettingsInternal(ctx)
}