337 lines
9.5 KiB
Go
337 lines
9.5 KiB
Go
// SPDX-License-Identifier: EUPL-1.2
|
|
|
|
package brain
|
|
|
|
import (
|
|
"context"
|
|
|
|
core "dappco.re/go/core"
|
|
)
|
|
|
|
type DirectOptions struct{}
|
|
|
|
// subsystem := brain.NewDirect()
|
|
// _ = subsystem.OnStartup(context.Background())
|
|
func (s *DirectSubsystem) OnStartup(_ context.Context) core.Result {
|
|
if s.ServiceRuntime == nil || s.Core() == nil {
|
|
return core.Result{OK: true}
|
|
}
|
|
|
|
c := s.Core()
|
|
c.Action("brain.remember", s.handleRemember).Description = "Store knowledge in OpenBrain"
|
|
c.Action("brain.recall", s.handleRecall).Description = "Recall knowledge from OpenBrain"
|
|
c.Action("brain.forget", s.handleForget).Description = "Forget knowledge in OpenBrain"
|
|
c.Action("brain.list", s.handleList).Description = "List knowledge in OpenBrain"
|
|
c.Action("message.send", s.handleSend).Description = "Send a direct message to another agent"
|
|
c.Action("message.inbox", s.handleInbox).Description = "Read direct messages for an agent"
|
|
c.Action("message.conversation", s.handleConversation).Description = "Read the conversation thread with another agent"
|
|
c.Action("agent.send", s.handleSend).Description = "Send a direct message to another agent"
|
|
c.Action("agent.inbox", s.handleInbox).Description = "Read direct messages for an agent"
|
|
c.Action("agent.conversation", s.handleConversation).Description = "Read the conversation thread with another agent"
|
|
return core.Result{OK: true}
|
|
}
|
|
|
|
// result := c.Action("brain.remember").Run(ctx, core.NewOptions(
|
|
//
|
|
// core.Option{Key: "content", Value: "Use OpenBrain for cross-agent context"},
|
|
// core.Option{Key: "type", Value: "architecture"},
|
|
//
|
|
// ))
|
|
func (s *DirectSubsystem) handleRemember(ctx context.Context, options core.Options) core.Result {
|
|
input := RememberInput{
|
|
Content: actionStringValue(options, "content"),
|
|
Type: actionStringValue(options, "type"),
|
|
Tags: actionStringSliceValue(options, "tags"),
|
|
Project: actionStringValue(options, "project"),
|
|
Confidence: actionFloatValue(options, "confidence"),
|
|
Supersedes: actionStringValue(options, "supersedes"),
|
|
ExpiresIn: actionIntValue(options, "expires_in", "expiresIn"),
|
|
}
|
|
_, output, err := s.remember(ctx, nil, input)
|
|
if err != nil {
|
|
return core.Result{Value: err, OK: false}
|
|
}
|
|
return core.Result{Value: output, OK: true}
|
|
}
|
|
|
|
// result := c.Action("brain.recall").Run(ctx, core.NewOptions(
|
|
//
|
|
// core.Option{Key: "query", Value: "OpenBrain architecture"},
|
|
// core.Option{Key: "top_k", Value: 5},
|
|
//
|
|
// ))
|
|
func (s *DirectSubsystem) handleRecall(ctx context.Context, options core.Options) core.Result {
|
|
input := RecallInput{
|
|
Query: actionStringValue(options, "query"),
|
|
TopK: actionIntValue(options, "top_k", "topK"),
|
|
Filter: recallFilterFromOptions(options),
|
|
}
|
|
_, output, err := s.recall(ctx, nil, input)
|
|
if err != nil {
|
|
return core.Result{Value: err, OK: false}
|
|
}
|
|
return core.Result{Value: output, OK: true}
|
|
}
|
|
|
|
// result := c.Action("brain.forget").Run(ctx, core.NewOptions(
|
|
//
|
|
// core.Option{Key: "id", Value: "mem-123"},
|
|
//
|
|
// ))
|
|
func (s *DirectSubsystem) handleForget(ctx context.Context, options core.Options) core.Result {
|
|
input := ForgetInput{
|
|
ID: actionStringValue(options, "id"),
|
|
Reason: actionStringValue(options, "reason"),
|
|
}
|
|
_, output, err := s.forget(ctx, nil, input)
|
|
if err != nil {
|
|
return core.Result{Value: err, OK: false}
|
|
}
|
|
return core.Result{Value: output, OK: true}
|
|
}
|
|
|
|
// result := c.Action("brain.list").Run(ctx, core.NewOptions(
|
|
//
|
|
// core.Option{Key: "project", Value: "agent"},
|
|
// core.Option{Key: "limit", Value: 10},
|
|
//
|
|
// ))
|
|
func (s *DirectSubsystem) handleList(ctx context.Context, options core.Options) core.Result {
|
|
input := ListInput{
|
|
Project: actionStringValue(options, "project"),
|
|
Type: actionStringValue(options, "type"),
|
|
AgentID: actionStringValue(options, "agent_id", "agent"),
|
|
Limit: actionIntValue(options, "limit"),
|
|
}
|
|
_, output, err := s.list(ctx, nil, input)
|
|
if err != nil {
|
|
return core.Result{Value: err, OK: false}
|
|
}
|
|
return core.Result{Value: output, OK: true}
|
|
}
|
|
|
|
// result := c.Action("message.send").Run(ctx, core.NewOptions(
|
|
//
|
|
// core.Option{Key: "to", Value: "charon"},
|
|
// core.Option{Key: "content", Value: "Deploy complete"},
|
|
//
|
|
// ))
|
|
func (s *DirectSubsystem) handleSend(ctx context.Context, options core.Options) core.Result {
|
|
input := SendInput{
|
|
To: actionStringValue(options, "to"),
|
|
Content: actionStringValue(options, "content"),
|
|
Subject: actionStringValue(options, "subject"),
|
|
}
|
|
_, output, err := s.sendMessage(ctx, nil, input)
|
|
if err != nil {
|
|
return core.Result{Value: err, OK: false}
|
|
}
|
|
return core.Result{Value: output, OK: true}
|
|
}
|
|
|
|
// result := c.Action("message.inbox").Run(ctx, core.NewOptions(
|
|
//
|
|
// core.Option{Key: "agent", Value: "cladius"},
|
|
//
|
|
// ))
|
|
func (s *DirectSubsystem) handleInbox(ctx context.Context, options core.Options) core.Result {
|
|
input := InboxInput{
|
|
Agent: actionStringValue(options, "agent"),
|
|
}
|
|
_, output, err := s.inbox(ctx, nil, input)
|
|
if err != nil {
|
|
return core.Result{Value: err, OK: false}
|
|
}
|
|
return core.Result{Value: output, OK: true}
|
|
}
|
|
|
|
// result := c.Action("message.conversation").Run(ctx, core.NewOptions(
|
|
//
|
|
// core.Option{Key: "agent", Value: "charon"},
|
|
//
|
|
// ))
|
|
func (s *DirectSubsystem) handleConversation(ctx context.Context, options core.Options) core.Result {
|
|
input := ConversationInput{
|
|
Agent: actionStringValue(options, "agent"),
|
|
}
|
|
_, output, err := s.conversation(ctx, nil, input)
|
|
if err != nil {
|
|
return core.Result{Value: err, OK: false}
|
|
}
|
|
return core.Result{Value: output, OK: true}
|
|
}
|
|
|
|
func recallFilterFromOptions(options core.Options) RecallFilter {
|
|
filter := recallFilterValue(actionOptionValue(options, "filter"))
|
|
if filter.Project == "" {
|
|
filter.Project = actionStringValue(options, "project")
|
|
}
|
|
if filter.Type == nil {
|
|
filter.Type = actionOptionValue(options, "type")
|
|
}
|
|
if filter.AgentID == "" {
|
|
filter.AgentID = actionStringValue(options, "agent_id", "agent")
|
|
}
|
|
if filter.MinConfidence == 0 {
|
|
filter.MinConfidence = actionFloatValue(options, "min_confidence", "minConfidence")
|
|
}
|
|
return filter
|
|
}
|
|
|
|
func recallFilterValue(value any) RecallFilter {
|
|
switch typed := value.(type) {
|
|
case RecallFilter:
|
|
return typed
|
|
case map[string]any:
|
|
return RecallFilter{
|
|
Project: actionStringFromAny(typed["project"]),
|
|
Type: typed["type"],
|
|
AgentID: actionStringFromAny(typed["agent_id"]),
|
|
MinConfidence: actionFloatFromAny(typed["min_confidence"]),
|
|
}
|
|
case map[string]string:
|
|
return RecallFilter{
|
|
Project: actionStringFromAny(typed["project"]),
|
|
Type: typed["type"],
|
|
AgentID: actionStringFromAny(typed["agent_id"]),
|
|
}
|
|
default:
|
|
if text := actionStringFromAny(value); text != "" {
|
|
return RecallFilter{Type: text}
|
|
}
|
|
}
|
|
return RecallFilter{}
|
|
}
|
|
|
|
func actionOptionValue(options core.Options, keys ...string) any {
|
|
for _, key := range keys {
|
|
result := options.Get(key)
|
|
if result.OK {
|
|
return result.Value
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func actionStringValue(options core.Options, keys ...string) string {
|
|
return actionStringFromAny(actionOptionValue(options, keys...))
|
|
}
|
|
|
|
func actionIntValue(options core.Options, keys ...string) int {
|
|
return actionIntFromAny(actionOptionValue(options, keys...))
|
|
}
|
|
|
|
func actionFloatValue(options core.Options, keys ...string) float64 {
|
|
return actionFloatFromAny(actionOptionValue(options, keys...))
|
|
}
|
|
|
|
func actionStringSliceValue(options core.Options, keys ...string) []string {
|
|
return actionStringSliceFromAny(actionOptionValue(options, keys...))
|
|
}
|
|
|
|
func actionStringFromAny(value any) string {
|
|
switch typed := value.(type) {
|
|
case string:
|
|
return core.Trim(typed)
|
|
case int:
|
|
return core.Sprint(typed)
|
|
case int64:
|
|
return core.Sprint(typed)
|
|
case float64:
|
|
return core.Sprint(int(typed))
|
|
case bool:
|
|
return core.Sprint(typed)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func actionIntFromAny(value any) int {
|
|
switch typed := value.(type) {
|
|
case int:
|
|
return typed
|
|
case int64:
|
|
return int(typed)
|
|
case float64:
|
|
return int(typed)
|
|
case string:
|
|
trimmed := core.Trim(typed)
|
|
if trimmed == "" {
|
|
return 0
|
|
}
|
|
var parsed int
|
|
if result := core.JSONUnmarshalString(core.Concat("{\"n\":", trimmed, "}"), &struct {
|
|
N *int `json:"n"`
|
|
}{N: &parsed}); result.OK {
|
|
return parsed
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func actionFloatFromAny(value any) float64 {
|
|
switch typed := value.(type) {
|
|
case float64:
|
|
return typed
|
|
case float32:
|
|
return float64(typed)
|
|
case int:
|
|
return float64(typed)
|
|
case int64:
|
|
return float64(typed)
|
|
case string:
|
|
trimmed := core.Trim(typed)
|
|
if trimmed == "" {
|
|
return 0
|
|
}
|
|
var parsed float64
|
|
if result := core.JSONUnmarshalString(core.Concat("{\"n\":", trimmed, "}"), &struct {
|
|
N *float64 `json:"n"`
|
|
}{N: &parsed}); result.OK {
|
|
return parsed
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func actionStringSliceFromAny(value any) []string {
|
|
switch typed := value.(type) {
|
|
case []string:
|
|
return cleanActionStrings(typed)
|
|
case []any:
|
|
var values []string
|
|
for _, item := range typed {
|
|
if text := actionStringFromAny(item); text != "" {
|
|
values = append(values, text)
|
|
}
|
|
}
|
|
return cleanActionStrings(values)
|
|
case string:
|
|
trimmed := core.Trim(typed)
|
|
if trimmed == "" {
|
|
return nil
|
|
}
|
|
if core.HasPrefix(trimmed, "[") {
|
|
var values []string
|
|
if result := core.JSONUnmarshalString(trimmed, &values); result.OK {
|
|
return cleanActionStrings(values)
|
|
}
|
|
}
|
|
return cleanActionStrings(core.Split(trimmed, ","))
|
|
default:
|
|
if text := actionStringFromAny(value); text != "" {
|
|
return []string{text}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func cleanActionStrings(values []string) []string {
|
|
var cleaned []string
|
|
for _, value := range values {
|
|
trimmed := core.Trim(value)
|
|
if trimmed != "" {
|
|
cleaned = append(cleaned, trimmed)
|
|
}
|
|
}
|
|
return cleaned
|
|
}
|