Files
LocalAI/pkg/addons/contract/slots.go
T
Ettore Di Giacinto 2a491cb2cf refactor(addons): remove the shims for older add-on data
No release carried add-ons, so nothing reads older add-on data or
manifests. Delete the code that did.

- Connectors: the kind and the add-on of the Telegram connectors that
  core once carried are gone (LegacyAddon, KindTelegram, the stored
  binding normalization, the platform titles). A binding needs a kind,
  and its add-on is the one that serves the kind. The conversion of the
  old agent pool lists every connector as dropped instead of making a
  Telegram binding, and its notes say so.
- The library index reads only the id field of an entry, not addonId.
- A host control that has a capability block (the network policy) is
  attached by that block alone, not by a tool that replaces bash.
- The Office create tools no longer answer for the argument names of an
  earlier schema, and the identity file note about old addresses is
  gone.
- The known debt of the provider-name guard drops the entries of the
  conversion and of the resource listing shim, and its ceiling goes
  from 13 to 5.

Assisted-by: Claude:claude-sonnet-5-5 go test
2026-11-03 16:56:49 +00:00

119 lines
3.5 KiB
Go

// SPDX-License-Identifier: MIT
package contract
import (
_ "embed"
"encoding/json"
"fmt"
"slices"
"strings"
"sync"
)
// SlotKindDocuments is the kind of a slot for a document format: the skill
// that serves it needs a commands provider that reports isolated execution.
const SlotKindDocuments = SkillKindDocuments
// Bounds of a slot declaration.
const (
MaxSlotExtensions = 8
MaxSlotWords = 8
MaxSlotWordChars = 32
MaxSlotLibChars = 64
)
// SlotDefinition describes the slot a skill serves. The add-on that provides
// the skill declares it, so the host needs no list of formats. It carries no
// authority: it only tells the host which files the skill covers and what to
// say when the host meets one.
type SlotDefinition struct {
// Kind is SlotKindDocuments.
Kind string `json:"kind"`
// Extensions are the file extensions the skill covers, lower case with the
// dot, such as ".docx".
Extensions []string `json:"extensions,omitempty"`
// Words are lower-case words that a skill of this slot has in its name.
Words []string `json:"words,omitempty"`
// Library names what a script can use to read these files, shown as a hint.
Library string `json:"library,omitempty"`
}
// Slot is a named slot: the name of the skill that serves it and its
// definition.
type Slot struct {
Name string `json:"name"`
SlotDefinition
}
//go:embed slots.json
var defaultSlotsJSON []byte
var defaultSlots = sync.OnceValue(func() []Slot {
var out []Slot
if err := json.Unmarshal(defaultSlotsJSON, &out); err != nil {
panic("contract: slots.json: " + err.Error())
}
return out
})
// DefaultSlots are the slots the host knows without any declaration, read
// from data. A skill that shadows one of them and declares no slot of its own
// is served by these.
func DefaultSlots() []Slot { return slices.Clone(defaultSlots()) }
// DefaultSlot returns the default slot called name.
func DefaultSlot(name string) (Slot, bool) {
i := slices.IndexFunc(defaultSlots(), func(s Slot) bool { return s.Name == name })
if i < 0 {
return Slot{}, false
}
return defaultSlots()[i], true
}
// SlotsOf lists the slots the skill definitions serve, in order: the slot a
// skill declares, or else the default slot of the name it shadows.
func SlotsOf(defs []SkillDefinition) []Slot {
var out []Slot
for _, d := range defs {
if s, ok := d.slot(); ok {
out = append(out, s)
}
}
return out
}
// slot returns the slot this skill serves.
func (d SkillDefinition) slot() (Slot, bool) {
if d.Shadows == "" || d.Shadows != d.Name {
return Slot{}, false
}
if d.Slot != nil {
return Slot{Name: d.Name, SlotDefinition: *d.Slot}, true
}
return DefaultSlot(d.Name)
}
// validate checks a slot declaration of the skill name.
func (s *SlotDefinition) validate(name string) error {
if s.Kind != SlotKindDocuments {
return fmt.Errorf("skill %s: unknown slot kind %q", name, s.Kind)
}
if len(s.Extensions) > MaxSlotExtensions || len(s.Words) > MaxSlotWords {
return fmt.Errorf("skill %s: too many slot extensions or words", name)
}
for _, e := range s.Extensions {
if len(e) < 2 || len(e) > 16 || e[0] != '.' || strings.ToLower(e) != e || !plainText(e, 16) || strings.ContainsAny(e[1:], "./\\ ") {
return fmt.Errorf("skill %s: invalid slot extension %q", name, e)
}
}
for _, w := range s.Words {
if !plainText(w, MaxSlotWordChars) || strings.ToLower(w) != w {
return fmt.Errorf("skill %s: invalid slot word", name)
}
}
if s.Library != "" && !plainText(s.Library, MaxSlotLibChars) {
return fmt.Errorf("skill %s: invalid slot library", name)
}
return nil
}