mirror of
https://github.com/mudler/LocalAI.git
synced 2026-11-06 04:23:15 -05:00
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
91 lines
3.0 KiB
Go
91 lines
3.0 KiB
Go
package studio
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"path"
|
|
"slices"
|
|
"strings"
|
|
|
|
"github.com/mudler/LocalAI/core/services/studio/files"
|
|
"github.com/mudler/LocalAI/core/services/studio/session"
|
|
"github.com/mudler/LocalAI/core/services/studio/workspace"
|
|
"github.com/mudler/LocalAI/pkg/addons/contract"
|
|
)
|
|
|
|
// binaryHintTools wraps the tools that read a file as text: when one meets a
|
|
// binary file, the error says what the chat can do with it. It names the
|
|
// skill that matches the file type when the chat has one, and a script when
|
|
// the chat can run commands. The skills are resolved only when a read fails.
|
|
func (s *Service) binaryHintTools(a workspace.Actor, canRun bool, tools []session.Tool) []session.Tool {
|
|
for i, tool := range tools {
|
|
switch tool.Name {
|
|
case "read_file", "edit_file":
|
|
default:
|
|
continue
|
|
}
|
|
run := tool.Run
|
|
tools[i].Run = func(ctx context.Context, args json.RawMessage) (string, error) {
|
|
out, err := run(ctx, args)
|
|
var nt *files.NotTextError
|
|
if !errors.As(err, &nt) {
|
|
return out, err
|
|
}
|
|
return out, errors.New(s.binaryAdvice(ctx, a, canRun, nt))
|
|
}
|
|
}
|
|
return tools
|
|
}
|
|
|
|
// binaryAdvice is the error for a binary file, short and true for the tools
|
|
// the chat has.
|
|
func (s *Service) binaryAdvice(ctx context.Context, a workspace.Actor, canRun bool, nt *files.NotTextError) string {
|
|
ext := strings.ToLower(path.Ext(nt.Name))
|
|
msg := fmt.Sprintf("%s is a binary file (%d bytes), not text.", nt.Name, nt.Size)
|
|
kind, known := s.slotForExtension(ctx, a.Principal, ext)
|
|
if known {
|
|
if list, _, err := s.Skills(ctx, a.Principal, a.ChatID); err == nil {
|
|
for _, sk := range list {
|
|
name := strings.ToLower(sk.Name)
|
|
for _, w := range kind.Words {
|
|
if strings.Contains(name, w) {
|
|
return msg + fmt.Sprintf(" Use the %s skill for %s files (load_skill).", sk.Name, ext)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
switch {
|
|
case canRun && known && kind.Library != "":
|
|
return msg + fmt.Sprintf(" Run a script with the commands tool instead, for example with %s.", kind.Library)
|
|
case canRun:
|
|
return msg + " Run a script with the commands tool instead."
|
|
}
|
|
return msg + " This chat has no tool that opens it."
|
|
}
|
|
|
|
// slotForExtension finds the slot that covers files with the extension ext.
|
|
// The slots come from the skills the add-ons declare, then from the default
|
|
// slots (data in the contract); the host holds no list of formats of its own.
|
|
func (s *Service) slotForExtension(ctx context.Context, p, ext string) (contract.Slot, bool) {
|
|
var sets []AddonSkillSet
|
|
if lister, ok := s.d.AddonTools.(AddonSkillLister); ok && s.d.AddonTools != nil {
|
|
sets, _ = lister.AddonSkills(ctx, p)
|
|
}
|
|
for _, set := range sets {
|
|
for _, sl := range set.Slots {
|
|
if sl.Kind == contract.SlotKindDocuments && slices.Contains(sl.Extensions, ext) {
|
|
return sl, true
|
|
}
|
|
}
|
|
}
|
|
for _, sl := range contract.DefaultSlots() {
|
|
if sl.Kind == contract.SlotKindDocuments && slices.Contains(sl.Extensions, ext) {
|
|
return sl, true
|
|
}
|
|
}
|
|
return contract.Slot{}, false
|
|
}
|