Files
LocalAI/core/services/studio/noaddonid_test.go
T
Ettore Di Giacinto 337a034601 feat(addons): declare document slots and legacy arguments in the manifest
The four document skill names were code in the contract, the hint for
binary files kept its own list of formats and libraries, and the chat
boundary rewrote the old arguments of one add-on by its ID.

A skill that shadows a name can now declare the slot it serves: kind,
file extensions, name words and a library hint. A tool can declare
legacyArgs, the arguments it refuses now, each with a hint. Both need
manifest api 2. The four default slots move to a data file that the
contract reads, so a manifest that declares nothing keeps working.

The host takes the replaceable names, the files a skill covers and the
hint for a script from the skills the add-ons list. It compares no
format or add-on ID. A guard spec fails when it does again. The Office
manifest declares its slots and legacy arguments and accepts api 2.

Assisted-by: Claude:claude-sonnet-5-5 go gofmt
2026-11-03 14:45:43 +00:00

86 lines
2.8 KiB
Go

// SPDX-License-Identifier: MIT
package studio_test
import (
"go/ast"
"go/parser"
"go/token"
"os"
"path/filepath"
"slices"
"strconv"
"strings"
"github.com/mudler/LocalAI/pkg/addons/contract"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
// Core decides what an add-on may do from what the add-on declares and what it
// reports at run time. These specs fail when a file that sets the rules for
// command execution names one add-on again. Tests, comments, documentation and
// the add-on's own data are the places that may.
var _ = Describe("the command execution code of the host", func() {
// literals returns the string literals of the non-test Go files of dir.
literals := func(dir string, only ...string) map[string][]string {
out := map[string][]string{}
entries, err := os.ReadDir(dir)
Expect(err).NotTo(HaveOccurred())
for _, e := range entries {
name := e.Name()
if e.IsDir() || !strings.HasSuffix(name, ".go") || strings.HasSuffix(name, "_test.go") {
continue
}
if len(only) > 0 && !slices.Contains(only, name) {
continue
}
path := filepath.Join(dir, name)
file, err := parser.ParseFile(token.NewFileSet(), path, nil, 0)
Expect(err).NotTo(HaveOccurred())
ast.Inspect(file, func(n ast.Node) bool {
if lit, ok := n.(*ast.BasicLit); ok && lit.Kind == token.STRING {
v, err := strconv.Unquote(lit.Value)
Expect(err).NotTo(HaveOccurred())
out[path] = append(out[path], v)
}
return true
})
}
return out
}
It("compares no string with the ID of the add-on that runs commands", func() {
// Other add-on IDs join this list as their special cases are removed.
ids := []string{"sandbox", "office"}
for _, dir := range []string{".", "../addons/tools"} {
for path, values := range literals(dir) {
for _, v := range values {
Expect(ids).NotTo(ContainElement(v), "%s compares with the ID of an add-on", path)
}
}
}
})
It("names no add-on in the text it builds for the commands of a chat", func() {
for path, values := range literals(".", "capabilities.go", "addonskills.go", "loadskill.go", "shadow.go") {
for _, v := range values {
Expect(strings.ToLower(v)).NotTo(ContainSubstring("sandbox"), "%s writes a provider name into a sentence; take it from the manifest", path)
}
}
})
It("holds no list of document formats: slots come from the add-ons and from data", func() {
var formats []string
for _, sl := range contract.DefaultSlots() {
formats = append(formats, sl.Name, sl.Library)
formats = append(formats, sl.Extensions...)
formats = append(formats, sl.Words...)
}
for path, values := range literals(".", "binaryhint.go", "addonskills.go", "loadskill.go", "toolsadmin.go") {
for _, v := range values {
Expect(formats).NotTo(ContainElement(v), "%s names a document format; read it from the slot", path)
}
}
})
})