Chore: noblanks complaints
This commit is contained in:
34
bot_test.go
34
bot_test.go
@@ -1,12 +1,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"gf-lt/config"
|
||||
"gf-lt/models"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestConsolidateConsecutiveAssistantMessages(t *testing.T) {
|
||||
// Mock config for testing
|
||||
testCfg := &config.Config{
|
||||
@@ -14,7 +12,6 @@ func TestConsolidateConsecutiveAssistantMessages(t *testing.T) {
|
||||
WriteNextMsgAsCompletionAgent: "",
|
||||
}
|
||||
cfg = testCfg
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input []models.RoleMsg
|
||||
@@ -114,38 +111,31 @@ func TestConsolidateConsecutiveAssistantMessages(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := consolidateAssistantMessages(tt.input)
|
||||
|
||||
if len(result) != len(tt.expected) {
|
||||
t.Errorf("Expected %d messages, got %d", len(tt.expected), len(result))
|
||||
t.Logf("Result: %+v", result)
|
||||
t.Logf("Expected: %+v", tt.expected)
|
||||
return
|
||||
}
|
||||
|
||||
for i, expectedMsg := range tt.expected {
|
||||
if i >= len(result) {
|
||||
t.Errorf("Result has fewer messages than expected at index %d", i)
|
||||
continue
|
||||
}
|
||||
|
||||
actualMsg := result[i]
|
||||
if actualMsg.Role != expectedMsg.Role {
|
||||
t.Errorf("Message %d: expected role '%s', got '%s'", i, expectedMsg.Role, actualMsg.Role)
|
||||
}
|
||||
|
||||
if actualMsg.Content != expectedMsg.Content {
|
||||
t.Errorf("Message %d: expected content '%s', got '%s'", i, expectedMsg.Content, actualMsg.Content)
|
||||
}
|
||||
|
||||
if actualMsg.ToolCallID != expectedMsg.ToolCallID {
|
||||
t.Errorf("Message %d: expected ToolCallID '%s', got '%s'", i, expectedMsg.ToolCallID, actualMsg.ToolCallID)
|
||||
}
|
||||
}
|
||||
|
||||
// Additional check: ensure no messages were lost
|
||||
if !reflect.DeepEqual(result, tt.expected) {
|
||||
t.Errorf("Result does not match expected:\nResult: %+v\nExpected: %+v", result, tt.expected)
|
||||
@@ -153,7 +143,6 @@ func TestConsolidateConsecutiveAssistantMessages(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalFuncCall(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -213,7 +202,6 @@ func TestUnmarshalFuncCall(t *testing.T) {
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := unmarshalFuncCall(tt.jsonStr)
|
||||
@@ -238,7 +226,6 @@ func TestUnmarshalFuncCall(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertJSONToMapStringString(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -265,7 +252,6 @@ func TestConvertJSONToMapStringString(t *testing.T) {
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := convertJSONToMapStringString(tt.jsonStr)
|
||||
@@ -287,7 +273,6 @@ func TestConvertJSONToMapStringString(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseKnownToTag(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -378,7 +363,6 @@ func TestParseKnownToTag(t *testing.T) {
|
||||
wantKnownTo: []string{"Alice", "Bob", "Carl"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Set up config
|
||||
@@ -402,7 +386,6 @@ func TestParseKnownToTag(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessMessageTag(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -498,7 +481,6 @@ func TestProcessMessageTag(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
testCfg := &config.Config{
|
||||
@@ -529,7 +511,6 @@ func TestProcessMessageTag(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterMessagesForCharacter(t *testing.T) {
|
||||
messages := []models.RoleMsg{
|
||||
{Role: "system", Content: "System message", KnownTo: nil}, // visible to all
|
||||
@@ -539,7 +520,6 @@ func TestFilterMessagesForCharacter(t *testing.T) {
|
||||
{Role: "Alice", Content: "Private to Carl", KnownTo: []string{"Alice", "Carl"}},
|
||||
{Role: "Carl", Content: "Hi all", KnownTo: nil}, // visible to all
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
enabled bool
|
||||
@@ -583,7 +563,6 @@ func TestFilterMessagesForCharacter(t *testing.T) {
|
||||
wantIndices: []int{0, 1, 5},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
testCfg := &config.Config{
|
||||
@@ -591,15 +570,12 @@ func TestFilterMessagesForCharacter(t *testing.T) {
|
||||
CharSpecificContextTag: "@",
|
||||
}
|
||||
cfg = testCfg
|
||||
|
||||
got := filterMessagesForCharacter(messages, tt.character)
|
||||
|
||||
if len(got) != len(tt.wantIndices) {
|
||||
t.Errorf("filterMessagesForCharacter() returned %d messages, want %d", len(got), len(tt.wantIndices))
|
||||
t.Logf("got: %v", got)
|
||||
return
|
||||
}
|
||||
|
||||
for i, idx := range tt.wantIndices {
|
||||
if got[i].Content != messages[idx].Content {
|
||||
t.Errorf("filterMessagesForCharacter() message %d content = %q, want %q", i, got[i].Content, messages[idx].Content)
|
||||
@@ -608,7 +584,6 @@ func TestFilterMessagesForCharacter(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoleMsgCopyPreservesKnownTo(t *testing.T) {
|
||||
// Test that the Copy() method preserves the KnownTo field
|
||||
originalMsg := models.RoleMsg{
|
||||
@@ -616,9 +591,7 @@ func TestRoleMsgCopyPreservesKnownTo(t *testing.T) {
|
||||
Content: "Test message",
|
||||
KnownTo: []string{"Bob", "Charlie"},
|
||||
}
|
||||
|
||||
copiedMsg := originalMsg.Copy()
|
||||
|
||||
if copiedMsg.Role != originalMsg.Role {
|
||||
t.Errorf("Copy() failed to preserve Role: got %q, want %q", copiedMsg.Role, originalMsg.Role)
|
||||
}
|
||||
@@ -635,7 +608,6 @@ func TestRoleMsgCopyPreservesKnownTo(t *testing.T) {
|
||||
t.Errorf("Copy() failed to preserve hasContentParts flag")
|
||||
}
|
||||
}
|
||||
|
||||
func TestKnownToFieldPreservationScenario(t *testing.T) {
|
||||
// Test the specific scenario from the log where KnownTo field was getting lost
|
||||
originalMsg := models.RoleMsg{
|
||||
@@ -643,28 +615,22 @@ func TestKnownToFieldPreservationScenario(t *testing.T) {
|
||||
Content: `Alice: "Okay, Bob. The word is... **'Ephemeral'**. (ooc: @Bob@)"`,
|
||||
KnownTo: []string{"Bob"}, // This was detected in the log
|
||||
}
|
||||
|
||||
t.Logf("Original message - Role: %s, Content: %s, KnownTo: %v",
|
||||
originalMsg.Role, originalMsg.Content, originalMsg.KnownTo)
|
||||
|
||||
// Simulate what happens when the message gets copied during processing
|
||||
copiedMsg := originalMsg.Copy()
|
||||
|
||||
t.Logf("Copied message - Role: %s, Content: %s, KnownTo: %v",
|
||||
copiedMsg.Role, copiedMsg.Content, copiedMsg.KnownTo)
|
||||
|
||||
// Check if KnownTo field survived the copy
|
||||
if len(copiedMsg.KnownTo) == 0 {
|
||||
t.Error("ERROR: KnownTo field was lost during copy!")
|
||||
} else {
|
||||
t.Log("SUCCESS: KnownTo field was preserved during copy!")
|
||||
}
|
||||
|
||||
// Verify the content is the same
|
||||
if copiedMsg.Content != originalMsg.Content {
|
||||
t.Errorf("Content was changed during copy: got %s, want %s", copiedMsg.Content, originalMsg.Content)
|
||||
}
|
||||
|
||||
// Verify the KnownTo slice is properly copied
|
||||
if !reflect.DeepEqual(copiedMsg.KnownTo, originalMsg.KnownTo) {
|
||||
t.Errorf("KnownTo was not properly copied: got %v, want %v", copiedMsg.KnownTo, originalMsg.KnownTo)
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRoleMsgToTextWithImages(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -92,7 +90,6 @@ func TestRoleMsgToTextWithImages(t *testing.T) {
|
||||
expected: "[orange::i][image: /old/path/photo.jpg][-:-:-]",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := tt.msg.ToText(tt.index)
|
||||
@@ -110,12 +107,10 @@ func TestRoleMsgToTextWithImages(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractDisplayPath(t *testing.T) {
|
||||
// Save original base dir
|
||||
originalBaseDir := imageBaseDir
|
||||
defer func() { imageBaseDir = originalBaseDir }()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
baseDir string
|
||||
@@ -153,7 +148,6 @@ func TestExtractDisplayPath(t *testing.T) {
|
||||
expected: "..._that_exceeds_sixty_characters_limit_yes_it_is_very_long.jpg",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
imageBaseDir = tt.baseDir
|
||||
|
||||
@@ -62,7 +62,6 @@ func TestORModelsListModels(t *testing.T) {
|
||||
t.Errorf("expected 4 total models, got %d", len(allModels))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("integration with or_models.json", func(t *testing.T) {
|
||||
// Attempt to load the real data file from the project root
|
||||
path := filepath.Join("..", "or_models.json")
|
||||
|
||||
111
tools.go
111
tools.go
@@ -172,12 +172,10 @@ func init() {
|
||||
panic("failed to init seachagent; error: " + err.Error())
|
||||
}
|
||||
WebSearcher = sa
|
||||
|
||||
if err := rag.Init(cfg, logger, store); err != nil {
|
||||
logger.Warn("failed to init rag; rag_search tool will not be available", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
// getWebAgentClient returns a singleton AgentClient for web agents.
|
||||
func getWebAgentClient() *agent.AgentClient {
|
||||
webAgentClientOnce.Do(func() {
|
||||
@@ -197,7 +195,6 @@ func getWebAgentClient() *agent.AgentClient {
|
||||
})
|
||||
return webAgentClient
|
||||
}
|
||||
|
||||
// registerWebAgents registers WebAgentB instances for websearch and read_url tools.
|
||||
func registerWebAgents() {
|
||||
webAgentsOnce.Do(func() {
|
||||
@@ -212,7 +209,6 @@ func registerWebAgents() {
|
||||
agent.Register("summarize_chat", agent.NewWebAgentB(client, summarySysPrompt))
|
||||
})
|
||||
}
|
||||
|
||||
// web search (depends on extra server)
|
||||
func websearch(args map[string]string) []byte {
|
||||
// make http request return bytes
|
||||
@@ -246,7 +242,6 @@ func websearch(args map[string]string) []byte {
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// rag search (searches local document database)
|
||||
func ragsearch(args map[string]string) []byte {
|
||||
query, ok := args["query"]
|
||||
@@ -265,21 +260,18 @@ func ragsearch(args map[string]string) []byte {
|
||||
"limit_arg", limitS, "error", err)
|
||||
limit = 3
|
||||
}
|
||||
|
||||
ragInstance := rag.GetInstance()
|
||||
if ragInstance == nil {
|
||||
msg := "rag not initialized; rag_search tool is not available"
|
||||
logger.Error(msg)
|
||||
return []byte(msg)
|
||||
}
|
||||
|
||||
results, err := ragInstance.Search(query, limit)
|
||||
if err != nil {
|
||||
msg := "rag search failed; error: " + err.Error()
|
||||
logger.Error(msg)
|
||||
return []byte(msg)
|
||||
}
|
||||
|
||||
data, err := json.Marshal(results)
|
||||
if err != nil {
|
||||
msg := "failed to marshal rag search result; error: " + err.Error()
|
||||
@@ -288,7 +280,6 @@ func ragsearch(args map[string]string) []byte {
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// web search raw (returns raw data without processing)
|
||||
func websearchRaw(args map[string]string) []byte {
|
||||
// make http request return bytes
|
||||
@@ -317,7 +308,6 @@ func websearchRaw(args map[string]string) []byte {
|
||||
// Return raw response without any processing
|
||||
return []byte(fmt.Sprintf("%+v", resp))
|
||||
}
|
||||
|
||||
// retrieves url content (text)
|
||||
func readURL(args map[string]string) []byte {
|
||||
// make http request return bytes
|
||||
@@ -341,7 +331,6 @@ func readURL(args map[string]string) []byte {
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// retrieves url content raw (returns raw content without processing)
|
||||
func readURLRaw(args map[string]string) []byte {
|
||||
// make http request return bytes
|
||||
@@ -360,7 +349,6 @@ func readURLRaw(args map[string]string) []byte {
|
||||
// Return raw response without any processing
|
||||
return []byte(fmt.Sprintf("%+v", resp))
|
||||
}
|
||||
|
||||
/*
|
||||
consider cases:
|
||||
- append mode (treat it like a journal appendix)
|
||||
@@ -390,7 +378,6 @@ func memorise(args map[string]string) []byte {
|
||||
msg := "info saved under the topic:" + args["topic"]
|
||||
return []byte(msg)
|
||||
}
|
||||
|
||||
func recall(args map[string]string) []byte {
|
||||
agent := cfg.AssistantRole
|
||||
if len(args) < 1 {
|
||||
@@ -406,7 +393,6 @@ func recall(args map[string]string) []byte {
|
||||
answer := fmt.Sprintf("under the topic: %s is stored:\n%s", args["topic"], mind)
|
||||
return []byte(answer)
|
||||
}
|
||||
|
||||
func recallTopics(args map[string]string) []byte {
|
||||
agent := cfg.AssistantRole
|
||||
topics, err := store.RecallTopics(agent)
|
||||
@@ -417,9 +403,7 @@ func recallTopics(args map[string]string) []byte {
|
||||
joinedS := strings.Join(topics, ";")
|
||||
return []byte(joinedS)
|
||||
}
|
||||
|
||||
// File Manipulation Tools
|
||||
|
||||
func fileCreate(args map[string]string) []byte {
|
||||
path, ok := args["path"]
|
||||
if !ok || path == "" {
|
||||
@@ -427,24 +411,19 @@ func fileCreate(args map[string]string) []byte {
|
||||
logger.Error(msg)
|
||||
return []byte(msg)
|
||||
}
|
||||
|
||||
path = resolvePath(path)
|
||||
|
||||
content, ok := args["content"]
|
||||
if !ok {
|
||||
content = ""
|
||||
}
|
||||
|
||||
if err := writeStringToFile(path, content); err != nil {
|
||||
msg := "failed to create file; error: " + err.Error()
|
||||
logger.Error(msg)
|
||||
return []byte(msg)
|
||||
}
|
||||
|
||||
msg := "file created successfully at " + path
|
||||
return []byte(msg)
|
||||
}
|
||||
|
||||
func fileRead(args map[string]string) []byte {
|
||||
path, ok := args["path"]
|
||||
if !ok || path == "" {
|
||||
@@ -452,16 +431,13 @@ func fileRead(args map[string]string) []byte {
|
||||
logger.Error(msg)
|
||||
return []byte(msg)
|
||||
}
|
||||
|
||||
path = resolvePath(path)
|
||||
|
||||
content, err := readStringFromFile(path)
|
||||
if err != nil {
|
||||
msg := "failed to read file; error: " + err.Error()
|
||||
logger.Error(msg)
|
||||
return []byte(msg)
|
||||
}
|
||||
|
||||
result := map[string]string{
|
||||
"content": content,
|
||||
"path": path,
|
||||
@@ -472,10 +448,8 @@ func fileRead(args map[string]string) []byte {
|
||||
logger.Error(msg)
|
||||
return []byte(msg)
|
||||
}
|
||||
|
||||
return jsonResult
|
||||
}
|
||||
|
||||
func fileWrite(args map[string]string) []byte {
|
||||
path, ok := args["path"]
|
||||
if !ok || path == "" {
|
||||
@@ -496,7 +470,6 @@ func fileWrite(args map[string]string) []byte {
|
||||
msg := "file written successfully at " + path
|
||||
return []byte(msg)
|
||||
}
|
||||
|
||||
func fileWriteAppend(args map[string]string) []byte {
|
||||
path, ok := args["path"]
|
||||
if !ok || path == "" {
|
||||
@@ -517,7 +490,6 @@ func fileWriteAppend(args map[string]string) []byte {
|
||||
msg := "file written successfully at " + path
|
||||
return []byte(msg)
|
||||
}
|
||||
|
||||
func fileDelete(args map[string]string) []byte {
|
||||
path, ok := args["path"]
|
||||
if !ok || path == "" {
|
||||
@@ -525,19 +497,15 @@ func fileDelete(args map[string]string) []byte {
|
||||
logger.Error(msg)
|
||||
return []byte(msg)
|
||||
}
|
||||
|
||||
path = resolvePath(path)
|
||||
|
||||
if err := removeFile(path); err != nil {
|
||||
msg := "failed to delete file; error: " + err.Error()
|
||||
logger.Error(msg)
|
||||
return []byte(msg)
|
||||
}
|
||||
|
||||
msg := "file deleted successfully at " + path
|
||||
return []byte(msg)
|
||||
}
|
||||
|
||||
func fileMove(args map[string]string) []byte {
|
||||
src, ok := args["src"]
|
||||
if !ok || src == "" {
|
||||
@@ -546,7 +514,6 @@ func fileMove(args map[string]string) []byte {
|
||||
return []byte(msg)
|
||||
}
|
||||
src = resolvePath(src)
|
||||
|
||||
dst, ok := args["dst"]
|
||||
if !ok || dst == "" {
|
||||
msg := "destination path not provided to file_move tool"
|
||||
@@ -554,17 +521,14 @@ func fileMove(args map[string]string) []byte {
|
||||
return []byte(msg)
|
||||
}
|
||||
dst = resolvePath(dst)
|
||||
|
||||
if err := moveFile(src, dst); err != nil {
|
||||
msg := "failed to move file; error: " + err.Error()
|
||||
logger.Error(msg)
|
||||
return []byte(msg)
|
||||
}
|
||||
|
||||
msg := fmt.Sprintf("file moved successfully from %s to %s", src, dst)
|
||||
return []byte(msg)
|
||||
}
|
||||
|
||||
func fileCopy(args map[string]string) []byte {
|
||||
src, ok := args["src"]
|
||||
if !ok || src == "" {
|
||||
@@ -573,7 +537,6 @@ func fileCopy(args map[string]string) []byte {
|
||||
return []byte(msg)
|
||||
}
|
||||
src = resolvePath(src)
|
||||
|
||||
dst, ok := args["dst"]
|
||||
if !ok || dst == "" {
|
||||
msg := "destination path not provided to file_copy tool"
|
||||
@@ -581,32 +544,26 @@ func fileCopy(args map[string]string) []byte {
|
||||
return []byte(msg)
|
||||
}
|
||||
dst = resolvePath(dst)
|
||||
|
||||
if err := copyFile(src, dst); err != nil {
|
||||
msg := "failed to copy file; error: " + err.Error()
|
||||
logger.Error(msg)
|
||||
return []byte(msg)
|
||||
}
|
||||
|
||||
msg := fmt.Sprintf("file copied successfully from %s to %s", src, dst)
|
||||
return []byte(msg)
|
||||
}
|
||||
|
||||
func fileList(args map[string]string) []byte {
|
||||
path, ok := args["path"]
|
||||
if !ok || path == "" {
|
||||
path = "." // default to current directory
|
||||
}
|
||||
|
||||
path = resolvePath(path)
|
||||
|
||||
files, err := listDirectory(path)
|
||||
if err != nil {
|
||||
msg := "failed to list directory; error: " + err.Error()
|
||||
logger.Error(msg)
|
||||
return []byte(msg)
|
||||
}
|
||||
|
||||
result := map[string]interface{}{
|
||||
"directory": path,
|
||||
"files": files,
|
||||
@@ -617,19 +574,15 @@ func fileList(args map[string]string) []byte {
|
||||
logger.Error(msg)
|
||||
return []byte(msg)
|
||||
}
|
||||
|
||||
return jsonResult
|
||||
}
|
||||
|
||||
// Helper functions for file operations
|
||||
|
||||
func resolvePath(p string) string {
|
||||
if filepath.IsAbs(p) {
|
||||
return p
|
||||
}
|
||||
return filepath.Join(cfg.FilePickerDir, p)
|
||||
}
|
||||
|
||||
func readStringFromFile(filename string) (string, error) {
|
||||
data, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
@@ -637,26 +590,21 @@ func readStringFromFile(filename string) (string, error) {
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
func writeStringToFile(filename string, data string) error {
|
||||
return os.WriteFile(filename, []byte(data), 0644)
|
||||
}
|
||||
|
||||
func appendStringToFile(filename string, data string) error {
|
||||
file, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
_, err = file.WriteString(data)
|
||||
return err
|
||||
}
|
||||
|
||||
func removeFile(filename string) error {
|
||||
return os.Remove(filename)
|
||||
}
|
||||
|
||||
func moveFile(src, dst string) error {
|
||||
// First try with os.Rename (works within same filesystem)
|
||||
if err := os.Rename(src, dst); err == nil {
|
||||
@@ -665,24 +613,20 @@ func moveFile(src, dst string) error {
|
||||
// If that fails (e.g., cross-filesystem), copy and delete
|
||||
return copyAndRemove(src, dst)
|
||||
}
|
||||
|
||||
func copyFile(src, dst string) error {
|
||||
srcFile, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer srcFile.Close()
|
||||
|
||||
dstFile, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dstFile.Close()
|
||||
|
||||
_, err = io.Copy(dstFile, srcFile)
|
||||
return err
|
||||
}
|
||||
|
||||
func copyAndRemove(src, dst string) error {
|
||||
// Copy the file
|
||||
if err := copyFile(src, dst); err != nil {
|
||||
@@ -691,13 +635,11 @@ func copyAndRemove(src, dst string) error {
|
||||
// Remove the source file
|
||||
return os.Remove(src)
|
||||
}
|
||||
|
||||
func listDirectory(path string) ([]string, error) {
|
||||
entries, err := os.ReadDir(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var files []string
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() {
|
||||
@@ -706,12 +648,9 @@ func listDirectory(path string) ([]string, error) {
|
||||
files = append(files, entry.Name())
|
||||
}
|
||||
}
|
||||
|
||||
return files, nil
|
||||
}
|
||||
|
||||
// Command Execution Tool
|
||||
|
||||
func executeCommand(args map[string]string) []byte {
|
||||
command, ok := args["command"]
|
||||
if !ok || command == "" {
|
||||
@@ -719,7 +658,6 @@ func executeCommand(args map[string]string) []byte {
|
||||
logger.Error(msg)
|
||||
return []byte(msg)
|
||||
}
|
||||
|
||||
// Get arguments - handle both single arg and multiple args
|
||||
var cmdArgs []string
|
||||
if args["args"] != "" {
|
||||
@@ -738,52 +676,42 @@ func executeCommand(args map[string]string) []byte {
|
||||
argNum++
|
||||
}
|
||||
}
|
||||
|
||||
if !isCommandAllowed(command, cmdArgs...) {
|
||||
msg := fmt.Sprintf("command '%s' is not allowed", command)
|
||||
logger.Error(msg)
|
||||
return []byte(msg)
|
||||
}
|
||||
|
||||
// Execute with timeout for safety
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
cmd := exec.CommandContext(ctx, command, cmdArgs...)
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("command '%s' failed; error: %v; output: %s", command, err, string(output))
|
||||
logger.Error(msg)
|
||||
return []byte(msg)
|
||||
}
|
||||
|
||||
// Check if output is empty and return success message
|
||||
if len(output) == 0 {
|
||||
successMsg := fmt.Sprintf("command '%s %s' executed successfully and exited with code 0", command, strings.Join(cmdArgs, " "))
|
||||
return []byte(successMsg)
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
// Helper functions for command execution
|
||||
|
||||
// Todo structure
|
||||
type TodoItem struct {
|
||||
ID string `json:"id"`
|
||||
Task string `json:"task"`
|
||||
Status string `json:"status"` // "pending", "in_progress", "completed"
|
||||
}
|
||||
|
||||
type TodoList struct {
|
||||
Items []TodoItem `json:"items"`
|
||||
}
|
||||
|
||||
// Global todo list storage
|
||||
var globalTodoList = TodoList{
|
||||
Items: []TodoItem{},
|
||||
}
|
||||
|
||||
// Todo Management Tools
|
||||
func todoCreate(args map[string]string) []byte {
|
||||
task, ok := args["task"]
|
||||
@@ -792,35 +720,28 @@ func todoCreate(args map[string]string) []byte {
|
||||
logger.Error(msg)
|
||||
return []byte(msg)
|
||||
}
|
||||
|
||||
// Generate simple ID
|
||||
id := fmt.Sprintf("todo_%d", len(globalTodoList.Items)+1)
|
||||
|
||||
newItem := TodoItem{
|
||||
ID: id,
|
||||
Task: task,
|
||||
Status: "pending",
|
||||
}
|
||||
|
||||
globalTodoList.Items = append(globalTodoList.Items, newItem)
|
||||
|
||||
result := map[string]string{
|
||||
"message": "todo created successfully",
|
||||
"id": id,
|
||||
"task": task,
|
||||
"status": "pending",
|
||||
}
|
||||
|
||||
jsonResult, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
msg := "failed to marshal result; error: " + err.Error()
|
||||
logger.Error(msg)
|
||||
return []byte(msg)
|
||||
}
|
||||
|
||||
return jsonResult
|
||||
}
|
||||
|
||||
func todoRead(args map[string]string) []byte {
|
||||
id, ok := args["id"]
|
||||
if ok && id != "" {
|
||||
@@ -851,7 +772,6 @@ func todoRead(args map[string]string) []byte {
|
||||
}
|
||||
return jsonResult
|
||||
}
|
||||
|
||||
// Return all todos if no ID specified
|
||||
result := map[string]interface{}{
|
||||
"todos": globalTodoList.Items,
|
||||
@@ -862,10 +782,8 @@ func todoRead(args map[string]string) []byte {
|
||||
logger.Error(msg)
|
||||
return []byte(msg)
|
||||
}
|
||||
|
||||
return jsonResult
|
||||
}
|
||||
|
||||
func todoUpdate(args map[string]string) []byte {
|
||||
id, ok := args["id"]
|
||||
if !ok || id == "" {
|
||||
@@ -873,16 +791,13 @@ func todoUpdate(args map[string]string) []byte {
|
||||
logger.Error(msg)
|
||||
return []byte(msg)
|
||||
}
|
||||
|
||||
task, taskOk := args["task"]
|
||||
status, statusOk := args["status"]
|
||||
|
||||
if !taskOk && !statusOk {
|
||||
msg := "neither task nor status provided to todo_update tool"
|
||||
logger.Error(msg)
|
||||
return []byte(msg)
|
||||
}
|
||||
|
||||
// Find and update the todo
|
||||
for i, item := range globalTodoList.Items {
|
||||
if item.ID == id {
|
||||
@@ -906,23 +821,19 @@ func todoUpdate(args map[string]string) []byte {
|
||||
return jsonResult
|
||||
}
|
||||
}
|
||||
|
||||
result := map[string]string{
|
||||
"message": "todo updated successfully",
|
||||
"id": id,
|
||||
}
|
||||
|
||||
jsonResult, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
msg := "failed to marshal result; error: " + err.Error()
|
||||
logger.Error(msg)
|
||||
return []byte(msg)
|
||||
}
|
||||
|
||||
return jsonResult
|
||||
}
|
||||
}
|
||||
|
||||
// ID not found
|
||||
result := map[string]string{
|
||||
"error": fmt.Sprintf("todo with id %s not found", id),
|
||||
@@ -935,7 +846,6 @@ func todoUpdate(args map[string]string) []byte {
|
||||
}
|
||||
return jsonResult
|
||||
}
|
||||
|
||||
func todoDelete(args map[string]string) []byte {
|
||||
id, ok := args["id"]
|
||||
if !ok || id == "" {
|
||||
@@ -943,29 +853,24 @@ func todoDelete(args map[string]string) []byte {
|
||||
logger.Error(msg)
|
||||
return []byte(msg)
|
||||
}
|
||||
|
||||
// Find and remove the todo
|
||||
for i, item := range globalTodoList.Items {
|
||||
if item.ID == id {
|
||||
// Remove item from slice
|
||||
globalTodoList.Items = append(globalTodoList.Items[:i], globalTodoList.Items[i+1:]...)
|
||||
|
||||
result := map[string]string{
|
||||
"message": "todo deleted successfully",
|
||||
"id": id,
|
||||
}
|
||||
|
||||
jsonResult, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
msg := "failed to marshal result; error: " + err.Error()
|
||||
logger.Error(msg)
|
||||
return []byte(msg)
|
||||
}
|
||||
|
||||
return jsonResult
|
||||
}
|
||||
}
|
||||
|
||||
// ID not found
|
||||
result := map[string]string{
|
||||
"error": fmt.Sprintf("todo with id %s not found", id),
|
||||
@@ -978,7 +883,6 @@ func todoDelete(args map[string]string) []byte {
|
||||
}
|
||||
return jsonResult
|
||||
}
|
||||
|
||||
var gitReadSubcommands = map[string]bool{
|
||||
"status": true,
|
||||
"log": true,
|
||||
@@ -990,7 +894,6 @@ var gitReadSubcommands = map[string]bool{
|
||||
"shortlog": true,
|
||||
"describe": true,
|
||||
}
|
||||
|
||||
func isCommandAllowed(command string, args ...string) bool {
|
||||
allowedCommands := map[string]bool{
|
||||
"grep": true,
|
||||
@@ -1031,7 +934,6 @@ func isCommandAllowed(command string, args ...string) bool {
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func summarizeChat(args map[string]string) []byte {
|
||||
if len(chatBody.Messages) == 0 {
|
||||
return []byte("No chat history to summarize.")
|
||||
@@ -1040,9 +942,7 @@ func summarizeChat(args map[string]string) []byte {
|
||||
chatText := chatToText(chatBody.Messages, true) // include system and tool messages
|
||||
return []byte(chatText)
|
||||
}
|
||||
|
||||
type fnSig func(map[string]string) []byte
|
||||
|
||||
var fnMap = map[string]fnSig{
|
||||
"recall": recall,
|
||||
"recall_topics": recallTopics,
|
||||
@@ -1067,7 +967,6 @@ var fnMap = map[string]fnSig{
|
||||
"todo_delete": todoDelete,
|
||||
"summarize_chat": summarizeChat,
|
||||
}
|
||||
|
||||
// callToolWithAgent calls the tool and applies any registered agent.
|
||||
func callToolWithAgent(name string, args map[string]string) []byte {
|
||||
registerWebAgents()
|
||||
@@ -1081,7 +980,6 @@ func callToolWithAgent(name string, args map[string]string) []byte {
|
||||
}
|
||||
return raw
|
||||
}
|
||||
|
||||
// openai style def
|
||||
var baseTools = []models.Tool{
|
||||
// rag_search
|
||||
@@ -1239,7 +1137,6 @@ var baseTools = []models.Tool{
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// file_create
|
||||
models.Tool{
|
||||
Type: "function",
|
||||
@@ -1262,7 +1159,6 @@ var baseTools = []models.Tool{
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// file_read
|
||||
models.Tool{
|
||||
Type: "function",
|
||||
@@ -1281,7 +1177,6 @@ var baseTools = []models.Tool{
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// file_write
|
||||
models.Tool{
|
||||
Type: "function",
|
||||
@@ -1304,7 +1199,6 @@ var baseTools = []models.Tool{
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// file_write_append
|
||||
models.Tool{
|
||||
Type: "function",
|
||||
@@ -1327,7 +1221,6 @@ var baseTools = []models.Tool{
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// file_delete
|
||||
models.Tool{
|
||||
Type: "function",
|
||||
@@ -1346,7 +1239,6 @@ var baseTools = []models.Tool{
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// file_move
|
||||
models.Tool{
|
||||
Type: "function",
|
||||
@@ -1369,7 +1261,6 @@ var baseTools = []models.Tool{
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// file_copy
|
||||
models.Tool{
|
||||
Type: "function",
|
||||
@@ -1392,7 +1283,6 @@ var baseTools = []models.Tool{
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// file_list
|
||||
models.Tool{
|
||||
Type: "function",
|
||||
@@ -1411,7 +1301,6 @@ var baseTools = []models.Tool{
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// execute_command
|
||||
models.Tool{
|
||||
Type: "function",
|
||||
|
||||
Reference in New Issue
Block a user