Repo iniciado
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
package decision
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Rule maps a condition to a set of actions.
|
||||
type Rule struct {
|
||||
Name string
|
||||
Match MatchFunc
|
||||
Actions []Action
|
||||
}
|
||||
|
||||
// MatchFunc is a pure predicate over a MessageContext.
|
||||
type MatchFunc func(ctx MessageContext) bool
|
||||
|
||||
// Evaluate runs all rules against the context and returns the matching actions. Pure.
|
||||
func Evaluate(ctx MessageContext, rules []Rule) []Action {
|
||||
var actions []Action
|
||||
for _, rule := range rules {
|
||||
if rule.Match(ctx) {
|
||||
actions = append(actions, rule.Actions...)
|
||||
}
|
||||
}
|
||||
return actions
|
||||
}
|
||||
|
||||
// MatchCommand returns a MatchFunc that matches when the command equals cmd.
|
||||
func MatchCommand(cmd string) MatchFunc {
|
||||
return func(ctx MessageContext) bool {
|
||||
return strings.EqualFold(ctx.Command, cmd)
|
||||
}
|
||||
}
|
||||
|
||||
// MatchPrefix returns a MatchFunc that matches when content starts with prefix.
|
||||
func MatchPrefix(prefix string) MatchFunc {
|
||||
return func(ctx MessageContext) bool {
|
||||
return strings.HasPrefix(ctx.Content, prefix)
|
||||
}
|
||||
}
|
||||
|
||||
// MatchAny returns a MatchFunc that matches any message.
|
||||
func MatchAny() MatchFunc {
|
||||
return func(_ MessageContext) bool { return true }
|
||||
}
|
||||
|
||||
// MatchMinPowerLevel returns a MatchFunc that requires a minimum Matrix power level.
|
||||
func MatchMinPowerLevel(level int) MatchFunc {
|
||||
return func(ctx MessageContext) bool {
|
||||
return ctx.PowerLevel >= level
|
||||
}
|
||||
}
|
||||
|
||||
// And composes multiple MatchFuncs with logical AND.
|
||||
func And(fns ...MatchFunc) MatchFunc {
|
||||
return func(ctx MessageContext) bool {
|
||||
for _, fn := range fns {
|
||||
if !fn(ctx) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Or composes multiple MatchFuncs with logical OR.
|
||||
func Or(fns ...MatchFunc) MatchFunc {
|
||||
return func(ctx MessageContext) bool {
|
||||
for _, fn := range fns {
|
||||
if fn(ctx) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
// Package decision implements the pure decision engine.
|
||||
// Input: MessageContext. Output: []Action. Zero side effects.
|
||||
package decision
|
||||
|
||||
import "github.com/enmanuel/agents/pkg/tools"
|
||||
|
||||
// MessageContext holds all the information about an incoming message.
|
||||
type MessageContext struct {
|
||||
SenderID string
|
||||
SenderName string
|
||||
RoomID string
|
||||
Content string
|
||||
Command string // parsed command name, e.g. "deploy"
|
||||
Args []string // parsed arguments
|
||||
PowerLevel int
|
||||
IsDirectMsg bool
|
||||
IsMention bool
|
||||
ThreadID string
|
||||
}
|
||||
|
||||
// ActionKind is the type of action to perform.
|
||||
type ActionKind string
|
||||
|
||||
const (
|
||||
ActionKindReply ActionKind = "reply"
|
||||
ActionKindSSH ActionKind = "ssh"
|
||||
ActionKindHTTP ActionKind = "http"
|
||||
ActionKindScript ActionKind = "script"
|
||||
ActionKindFileOps ActionKind = "file_ops"
|
||||
ActionKindMCP ActionKind = "mcp"
|
||||
ActionKindLLM ActionKind = "llm"
|
||||
ActionKindDelegate ActionKind = "delegate"
|
||||
)
|
||||
|
||||
// Action is a pure description of what the shell should do.
|
||||
// It is a tagged union — only the field matching Kind is set.
|
||||
type Action struct {
|
||||
Kind ActionKind
|
||||
Reply *ReplyAction
|
||||
SSH *tools.SSHCommandSpec
|
||||
HTTP *tools.HTTPCallSpec
|
||||
Script *tools.ScriptSpec
|
||||
FileOps *tools.FileOpsSpec
|
||||
MCP *tools.MCPCallSpec
|
||||
LLM *LLMAction
|
||||
Delegate *DelegateAction
|
||||
}
|
||||
|
||||
type ReplyAction struct {
|
||||
Content string
|
||||
ThreadID string // empty = new thread
|
||||
Reaction string // optional Matrix reaction
|
||||
}
|
||||
|
||||
type LLMAction struct {
|
||||
ContextKey string // key to look up conversation history
|
||||
ExtraTools []string // additional tool names to make available
|
||||
}
|
||||
|
||||
type DelegateAction struct {
|
||||
TargetAgentID string
|
||||
Task string
|
||||
Context map[string]string
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package llm
|
||||
|
||||
import "strings"
|
||||
|
||||
// Route maps a model string to its provider. Pure function.
|
||||
func Route(model string) ProviderID {
|
||||
switch {
|
||||
case strings.HasPrefix(model, "claude"):
|
||||
return ProviderAnthropic
|
||||
case strings.HasPrefix(model, "gpt"), strings.HasPrefix(model, "o1"), strings.HasPrefix(model, "o3"):
|
||||
return ProviderOpenAI
|
||||
case strings.HasPrefix(model, "ollama/"):
|
||||
return ProviderOllama
|
||||
default:
|
||||
return ProviderOpenAI
|
||||
}
|
||||
}
|
||||
|
||||
// ModelName strips the provider prefix from a model string.
|
||||
func ModelName(model string) string {
|
||||
if after, ok := strings.CutPrefix(model, "ollama/"); ok {
|
||||
return after
|
||||
}
|
||||
return model
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
// Package llm defines pure types for LLM provider communication.
|
||||
// No side effects — only data and transformations.
|
||||
package llm
|
||||
|
||||
import "context"
|
||||
|
||||
type Role string
|
||||
|
||||
const (
|
||||
RoleSystem Role = "system"
|
||||
RoleUser Role = "user"
|
||||
RoleAssistant Role = "assistant"
|
||||
RoleTool Role = "tool"
|
||||
)
|
||||
|
||||
type ProviderID string
|
||||
|
||||
const (
|
||||
ProviderAnthropic ProviderID = "anthropic"
|
||||
ProviderOpenAI ProviderID = "openai"
|
||||
ProviderOllama ProviderID = "ollama"
|
||||
)
|
||||
|
||||
type Message struct {
|
||||
Role Role
|
||||
Content string
|
||||
ToolCallID string
|
||||
ToolCalls []ToolCall
|
||||
}
|
||||
|
||||
type ToolCall struct {
|
||||
ID string
|
||||
Name string
|
||||
Arguments string // JSON-encoded
|
||||
}
|
||||
|
||||
type ToolSpec struct {
|
||||
Name string
|
||||
Description string
|
||||
InputSchema map[string]any
|
||||
}
|
||||
|
||||
type CompletionRequest struct {
|
||||
Model string
|
||||
Messages []Message
|
||||
Tools []ToolSpec
|
||||
MaxTokens int
|
||||
Temperature float64
|
||||
Stream bool
|
||||
SystemPrompt string
|
||||
}
|
||||
|
||||
type TokenUsage struct {
|
||||
InputTokens int
|
||||
OutputTokens int
|
||||
TotalTokens int
|
||||
}
|
||||
|
||||
type CompletionResponse struct {
|
||||
Content string
|
||||
ToolCalls []ToolCall
|
||||
Usage TokenUsage
|
||||
FinishReason string
|
||||
}
|
||||
|
||||
// CompleteFunc is the single contract for LLM providers.
|
||||
// Implementations live in shell/llm/.
|
||||
type CompleteFunc func(ctx context.Context, req CompletionRequest) (CompletionResponse, error)
|
||||
@@ -0,0 +1,28 @@
|
||||
package message
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
// Render executes a Go template string with the given data. Pure.
|
||||
func Render(tmpl string, data any) (string, error) {
|
||||
t, err := template.New("").Parse(tmpl)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if err := t.Execute(&buf, data); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
// MustRender is like Render but panics on error. Use only in tests.
|
||||
func MustRender(tmpl string, data any) string {
|
||||
s, err := Render(tmpl, data)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return s
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// Package message provides pure parsing and formatting for Matrix messages.
|
||||
package message
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/enmanuel/agents/pkg/decision"
|
||||
)
|
||||
|
||||
// ParseOptions configures how messages are parsed.
|
||||
type ParseOptions struct {
|
||||
CommandPrefix string // e.g. "!"
|
||||
BotUserID string // for mention detection
|
||||
}
|
||||
|
||||
// Parse converts a raw Matrix message body into a structured MessageContext. Pure.
|
||||
func Parse(body, senderID, roomID string, powerLevel int, isDM bool, opts ParseOptions) decision.MessageContext {
|
||||
ctx := decision.MessageContext{
|
||||
SenderID: senderID,
|
||||
RoomID: roomID,
|
||||
Content: body,
|
||||
PowerLevel: powerLevel,
|
||||
IsDirectMsg: isDM,
|
||||
}
|
||||
|
||||
// Detect mention
|
||||
if opts.BotUserID != "" && strings.Contains(body, opts.BotUserID) {
|
||||
ctx.IsMention = true
|
||||
body = strings.ReplaceAll(body, opts.BotUserID, "")
|
||||
body = strings.TrimSpace(body)
|
||||
}
|
||||
|
||||
// Parse command
|
||||
if opts.CommandPrefix != "" && strings.HasPrefix(body, opts.CommandPrefix) {
|
||||
parts := strings.Fields(strings.TrimPrefix(body, opts.CommandPrefix))
|
||||
if len(parts) > 0 {
|
||||
ctx.Command = strings.ToLower(parts[0])
|
||||
ctx.Args = parts[1:]
|
||||
}
|
||||
}
|
||||
|
||||
return ctx
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
// Package personality defines pure types for agent personality and behavior.
|
||||
package personality
|
||||
|
||||
type Tone string
|
||||
|
||||
const (
|
||||
ToneDirect Tone = "direct"
|
||||
ToneFriendly Tone = "friendly"
|
||||
ToneFormal Tone = "formal"
|
||||
ToneCasual Tone = "casual"
|
||||
ToneTechnical Tone = "technical"
|
||||
)
|
||||
|
||||
type Verbosity string
|
||||
|
||||
const (
|
||||
VerbosityMinimal Verbosity = "minimal"
|
||||
VerbosityConcise Verbosity = "concise"
|
||||
VerbosityDetailed Verbosity = "detailed"
|
||||
VerbosityVerbose Verbosity = "verbose"
|
||||
)
|
||||
|
||||
type EmojiStyle string
|
||||
|
||||
const (
|
||||
EmojiNone EmojiStyle = "none"
|
||||
EmojiMinimal EmojiStyle = "minimal"
|
||||
EmojiModerate EmojiStyle = "moderate"
|
||||
EmojiHeavy EmojiStyle = "heavy"
|
||||
)
|
||||
|
||||
type ErrorStyle string
|
||||
|
||||
const (
|
||||
ErrorTerse ErrorStyle = "terse"
|
||||
ErrorHelpful ErrorStyle = "helpful"
|
||||
ErrorDetailed ErrorStyle = "detailed"
|
||||
)
|
||||
|
||||
type Templates struct {
|
||||
Greeting string
|
||||
UnknownCommand string
|
||||
PermissionDenied string
|
||||
Error string
|
||||
Success string
|
||||
Busy string
|
||||
}
|
||||
|
||||
type Behavior struct {
|
||||
Proactive bool
|
||||
AskConfirmation bool
|
||||
ShowReasoning bool
|
||||
ThreadReplies bool
|
||||
TypingIndicator bool
|
||||
AcknowledgeReceipt bool
|
||||
}
|
||||
|
||||
type Personality struct {
|
||||
Tone Tone
|
||||
Verbosity Verbosity
|
||||
Language string
|
||||
LanguagesSupported []string
|
||||
EmojiStyle EmojiStyle
|
||||
Prefix string
|
||||
ErrorStyle ErrorStyle
|
||||
Templates Templates
|
||||
Behavior Behavior
|
||||
}
|
||||
|
||||
// DefaultPersonality returns a sensible baseline.
|
||||
func DefaultPersonality() Personality {
|
||||
return Personality{
|
||||
Tone: ToneFriendly,
|
||||
Verbosity: VerbosityConcise,
|
||||
Language: "en",
|
||||
EmojiStyle: EmojiMinimal,
|
||||
ErrorStyle: ErrorHelpful,
|
||||
Templates: Templates{
|
||||
Greeting: "Ready. What do you need?",
|
||||
UnknownCommand: "Unknown command. Use `!help` for available commands.",
|
||||
PermissionDenied: "You don't have permission for that.",
|
||||
Error: "Something failed: {{.Error}}",
|
||||
Success: "Done. {{.Summary}}",
|
||||
Busy: "I'm busy with another task. Wait or use `!queue`.",
|
||||
},
|
||||
Behavior: Behavior{
|
||||
AskConfirmation: true,
|
||||
ThreadReplies: true,
|
||||
TypingIndicator: true,
|
||||
AcknowledgeReceipt: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// Package tools defines pure, declarative tool specifications.
|
||||
// No execution happens here — only data describing what tools exist and their contracts.
|
||||
package tools
|
||||
|
||||
// ToolKind identifies the category of a tool.
|
||||
type ToolKind string
|
||||
|
||||
const (
|
||||
ToolKindSSH ToolKind = "ssh"
|
||||
ToolKindHTTP ToolKind = "http"
|
||||
ToolKindScript ToolKind = "script"
|
||||
ToolKindFileOps ToolKind = "file_ops"
|
||||
ToolKindMCP ToolKind = "mcp"
|
||||
)
|
||||
|
||||
// ToolSpec is a pure description of a tool — what it does and what it accepts.
|
||||
// The actual execution lives in shell/effects/.
|
||||
type ToolSpec struct {
|
||||
Name string
|
||||
Kind ToolKind
|
||||
Description string
|
||||
Parameters []ParameterSpec
|
||||
}
|
||||
|
||||
type ParameterSpec struct {
|
||||
Name string
|
||||
Type string
|
||||
Description string
|
||||
Required bool
|
||||
}
|
||||
|
||||
// Registry is a map of available tools, keyed by name.
|
||||
type Registry map[string]ToolSpec
|
||||
|
||||
// Add returns a new Registry with the given spec added.
|
||||
func (r Registry) Add(spec ToolSpec) Registry {
|
||||
out := make(Registry, len(r)+1)
|
||||
for k, v := range r {
|
||||
out[k] = v
|
||||
}
|
||||
out[spec.Name] = spec
|
||||
return out
|
||||
}
|
||||
|
||||
// Get looks up a tool spec by name.
|
||||
func (r Registry) Get(name string) (ToolSpec, bool) {
|
||||
spec, ok := r[name]
|
||||
return spec, ok
|
||||
}
|
||||
|
||||
// Names returns all registered tool names.
|
||||
func (r Registry) Names() []string {
|
||||
names := make([]string, 0, len(r))
|
||||
for k := range r {
|
||||
names = append(names, k)
|
||||
}
|
||||
return names
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package tools
|
||||
|
||||
// SSHCommandSpec describes an SSH command to execute. Pure data — no execution.
|
||||
type SSHCommandSpec struct {
|
||||
Target string // references a named target in ssh config
|
||||
Command string
|
||||
Timeout string
|
||||
}
|
||||
|
||||
// HTTPCallSpec describes an HTTP call to make. Pure data.
|
||||
type HTTPCallSpec struct {
|
||||
Method string
|
||||
URL string
|
||||
Headers map[string]string
|
||||
Body string
|
||||
Timeout string
|
||||
}
|
||||
|
||||
// ScriptSpec describes a script to run. Pure data.
|
||||
type ScriptSpec struct {
|
||||
Name string
|
||||
Args []string
|
||||
Timeout string
|
||||
}
|
||||
|
||||
// FileOpsSpec describes a file operation. Pure data.
|
||||
type FileOpsSpec struct {
|
||||
Op string // read | write | list | delete
|
||||
Path string
|
||||
}
|
||||
|
||||
// MCPCallSpec describes a call to an MCP server. Pure data.
|
||||
type MCPCallSpec struct {
|
||||
ServerName string
|
||||
ToolName string
|
||||
Arguments map[string]any
|
||||
}
|
||||
Reference in New Issue
Block a user