mirror of
https://github.com/mudler/LocalAI.git
synced 2026-11-06 04:23:15 -05:00
A connector chat can now propose a trigger, the person answers it in the chat, and the run reports to the chat. The trigger tools propose_trigger and list_triggers are offered in a connector chat when a capability gate allows triggers.propose for the binding. The gate is a small interface on the service with a deny-all default, so nothing changes until the application sets one. Change and delete stay in Studio. A proposal from a chat stores the binding, chat and thread, a four character code and a 24 hour expiry. The host writes the proposal and the code in that chat. "confirm CODE" or "cancel CODE", as a whole message in the same chat and thread, is handled before any model turn and needs proposals.confirm. A binding may make 3 proposals an hour. Proposals that lapse are marked expired, and the proposal card shows that state. A trigger carries a delivery: the Inbox (default) or the chat of the proposal, with the modes always, on_change and on_problem. The host writes the last message of the run, cut and with broadcast mentions broken; a message of exactly [SILENT] writes nothing; three failures in a row raise one alert per 6 hours. A failed delivery falls back to the Inbox. Host messages of a binding go through an outbox of 20 a minute that queues the rest. While triggers.run is off, runs of such a trigger are skipped, not replayed, and the record is unchanged. The audit records each step with a hash and no text. Assisted-by: Claude:claude-sonnet-5-5 go test ginkgo eslint Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
59 lines
2.2 KiB
Go
59 lines
2.2 KiB
Go
package studio
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
"github.com/mudler/LocalAI/core/services/studio/connectors"
|
|
"github.com/mudler/LocalAI/pkg/addons/contract"
|
|
)
|
|
|
|
// The place of a connector chat is where the platform shows it: the chat and
|
|
// the thread of the last message the bot answered. A tool that runs in the
|
|
// turn, and the host when a run reports back, need it to write to the same
|
|
// place. A connector chat is made for a sender or a thread, not for one
|
|
// platform chat, so the host keeps the place of each chat next to the chat
|
|
// mapping, under a key that cannot be the ID of a sender. Nothing in the
|
|
// store changes: the chat column holds "chat\x1fthread".
|
|
const (
|
|
placeKeyPrefix = "place:"
|
|
placeSep = "\x1f"
|
|
// maxPlaceValue is the width of the chat column of the stores.
|
|
maxPlaceValue = 128
|
|
)
|
|
|
|
func placeKey(chatID string) string { return placeKeyPrefix + chatID }
|
|
|
|
func encodePlace(chat, thread string) string { return chat + placeSep + thread }
|
|
|
|
func decodePlace(v string) (chat, thread string, ok bool) {
|
|
chat, thread, ok = strings.Cut(v, placeSep)
|
|
return chat, thread, ok && chat != ""
|
|
}
|
|
|
|
// rememberPlace keeps where the message m was written, for the chat chatID of
|
|
// binding b. A place that does not fit the store is not kept: the chat then
|
|
// has no place, and what needs one falls back to the Inbox.
|
|
func (c *Connectors) rememberPlace(ctx context.Context, b connectors.Binding, chatID string, m contract.ConnectorMessage) {
|
|
key, val := placeKey(chatID), encodePlace(m.Chat, m.Thread)
|
|
if len(key) > contract.MaxConnectorSender || len(val) > maxPlaceValue {
|
|
return
|
|
}
|
|
if cur, ok, err := c.o.Store.ChatOf(ctx, b.ID, key); err == nil && ok && cur == val {
|
|
return
|
|
}
|
|
if err := c.o.Store.SetChat(ctx, b.ID, key, val); err != nil {
|
|
c.log.Warn("studio: could not keep the place of a connector chat", "binding", b.ID, "error", err)
|
|
}
|
|
}
|
|
|
|
// PlaceOf returns the platform chat and thread where the connector chat chatID
|
|
// of the binding was last written to. It is false when the chat has none.
|
|
func (c *Connectors) PlaceOf(ctx context.Context, bindingID, chatID string) (chat, thread string, ok bool) {
|
|
v, found, err := c.o.Store.ChatOf(ctx, bindingID, placeKey(chatID))
|
|
if err != nil || !found {
|
|
return "", "", false
|
|
}
|
|
return decodePlace(v)
|
|
}
|