mirror of
https://github.com/cheikh2shift/godex.git
synced 2026-10-23 11:22:18 -05:00
Merge branch 'main' into interface-any
This commit is contained in:
14 files changed
+100
-142
No files matched your search
@@ -47,7 +47,7 @@ The server exposes a subset of GoDex’s filesystem tools, including:
|
||||
- `create_directory`, `delete_file`
|
||||
- `search_files`, `search_in_file`, `search_directory_text`, `search_file_text`
|
||||
- `get_file_info`, `read_file_line_range`
|
||||
- `replace_first_in_file`, `replace_line_range`, `insert_at_line`
|
||||
- `replace_first_in_file`, `insert_at_line`
|
||||
- `list_allowed_paths`
|
||||
|
||||
## Add to OpenCode
|
||||
|
||||
@@ -9,11 +9,6 @@ Orchestration and parallel tasks? open another terminal tab and start a new inst
|
||||
|
||||

|
||||
|
||||
## Requirements
|
||||
|
||||
- **Go 1.25.7+** - Build from source
|
||||
- **Ollama** - For the default LLM backend (or use Gemini, OpenRouter, or llama.cpp)
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Requirements](#requirements)
|
||||
|
||||
+10
-10
@@ -15,7 +15,6 @@ import (
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -35,6 +34,7 @@ import (
|
||||
"github.com/cheikh2shift/godex/internal/mcp"
|
||||
"github.com/cheikh2shift/godex/internal/ml"
|
||||
"github.com/cheikh2shift/godex/internal/providers"
|
||||
"github.com/cheikh2shift/godex/internal/rxcache"
|
||||
"github.com/cheikh2shift/godex/internal/scheduler"
|
||||
"github.com/cheikh2shift/godex/internal/wizard"
|
||||
"github.com/cheikh2shift/godex/modelquery"
|
||||
@@ -1445,8 +1445,8 @@ func handleMCPSubcommand(args []string, configPath string) error {
|
||||
if err := config.Save(configPath, cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Added MCP server %q to provider %q\n", serverName, provider.Name)
|
||||
return nil
|
||||
fmt.Printf("Added MCP server %q to provider %q\n", serverName, provider.Name)
|
||||
return nil
|
||||
case "serve":
|
||||
return handleMCPServeSubcommand(args[1:])
|
||||
case "remove":
|
||||
@@ -2852,11 +2852,11 @@ func extractAllToolCalls(text string) []map[string]any {
|
||||
|
||||
// Safety fallback for the specific [TOOL_CALL] format
|
||||
if len(results) == 0 {
|
||||
toolCallRe := regexp.MustCompile(`(?s)\[TOOL_CALL\]\s*(.*?)\s*\[/TOOL_CALL\]`)
|
||||
toolCallRe := rxcache.MustCompile(`(?s)\[TOOL_CALL\]\s*(.*?)\s*\[/TOOL_CALL\]`)
|
||||
tcMatches := toolCallRe.FindAllStringSubmatch(candidate, -1)
|
||||
for _, m := range tcMatches {
|
||||
if len(m) > 1 {
|
||||
nameRe := regexp.MustCompile(`tool\s*=>\s*"([^"]+)"`)
|
||||
nameRe := rxcache.MustCompile(`tool\s*=>\s*"([^"]+)"`)
|
||||
if nameMatch := nameRe.FindStringSubmatch(m[1]); len(nameMatch) > 1 {
|
||||
results = append(results, map[string]any{"name": nameMatch[1], "arguments": map[string]any{}})
|
||||
}
|
||||
@@ -2922,7 +2922,7 @@ func extractToolCallsFromJSONArray(text string) []map[string]any {
|
||||
|
||||
func extractNativeToolCalls(text string) [][2]string {
|
||||
var results [][2]string
|
||||
re := regexp.MustCompile(`\[TOOL_CALL:\s*([^\s|]+)\s*\|`)
|
||||
re := rxcache.MustCompile(`\[TOOL_CALL:\s*([^\s|]+)\s*\|`)
|
||||
matches := re.FindAllStringSubmatchIndex(text, -1)
|
||||
for _, m := range matches {
|
||||
if len(m) < 4 {
|
||||
@@ -2977,8 +2977,8 @@ func normalizeToolCallText(text string) string {
|
||||
|
||||
nonEmpty := 0
|
||||
withMargin := 0
|
||||
marginRe := regexp.MustCompile(`^\s*│`)
|
||||
stripRe := regexp.MustCompile(`^\s*│\s?`)
|
||||
marginRe := rxcache.MustCompile(`^\s*│`)
|
||||
stripRe := rxcache.MustCompile(`^\s*│\s?`)
|
||||
|
||||
for _, line := range lines {
|
||||
if strings.TrimSpace(line) == "" {
|
||||
@@ -3004,7 +3004,7 @@ func normalizeToolCallText(text string) string {
|
||||
// Only replaces the first occurrence at the start of the command
|
||||
func fixDriveLetterPath(cmd string) string {
|
||||
// Match :/<path> at start and convert to cd /<path>
|
||||
re := regexp.MustCompile(`(?i)^:\/(.+)`)
|
||||
re := rxcache.MustCompile(`(?i)^:\/(.+)`)
|
||||
match := re.FindStringSubmatch(cmd)
|
||||
if match == nil {
|
||||
return cmd
|
||||
@@ -3128,7 +3128,7 @@ func extractJsonObjects(text string) []map[string]any {
|
||||
}
|
||||
|
||||
func extractThinkingText(text string) string {
|
||||
codeBlockRe := regexp.MustCompile("(?s)```(?:json)?\\n.*?\\n```")
|
||||
codeBlockRe := rxcache.MustCompile("(?s)```(?:json)?\\n.*?\\n```")
|
||||
thinking := codeBlockRe.ReplaceAllString(text, "")
|
||||
thinking = strings.TrimSpace(thinking)
|
||||
return thinking
|
||||
|
||||
@@ -172,16 +172,6 @@ func serveFilesystemMCP(baseAllowedPaths []string, autoConfirm bool, useRoots bo
|
||||
},
|
||||
Required: []string{"path", "find", "replace"},
|
||||
})
|
||||
addProxyTool("replace_line_range", "Replace a range of lines in a file.", mcplib.ToolInputSchema{
|
||||
Type: "object",
|
||||
Properties: map[string]any{
|
||||
"path": map[string]any{"type": "string", "description": "File path."},
|
||||
"start": map[string]any{"type": "number", "description": "Start line number (1-indexed)."},
|
||||
"end": map[string]any{"type": "number", "description": "End line number (inclusive)."},
|
||||
"new_content": map[string]any{"type": "string", "description": "New content to insert at the deleted location."},
|
||||
},
|
||||
Required: []string{"path", "start", "end", "new_content"},
|
||||
})
|
||||
addProxyTool("insert_at_line", "Insert content at a specific line number.", mcplib.ToolInputSchema{
|
||||
Type: "object",
|
||||
Properties: map[string]any{
|
||||
|
||||
@@ -89,15 +89,6 @@ DYNO_TOOL_CALL_BATCHES = [
|
||||
"content": "inserted line",
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "replace_line_range",
|
||||
"arguments": {
|
||||
"path": "/dyno/workspace/example_dir/example.txt",
|
||||
"start_line": 2,
|
||||
"end_line": 2,
|
||||
"new_content": "replaced second line",
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "search_directory_text",
|
||||
"arguments": {"path": "/dyno/workspace", "query": "replaced second line"},
|
||||
@@ -324,23 +315,6 @@ SYSTEM_TOOLS = [
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "replace_line_range",
|
||||
"description": "Replace a range of lines in a file",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"path": {"type": "string", "description": "File path"},
|
||||
"start": {"type": "integer", "description": "Start line"},
|
||||
"end": {"type": "integer", "description": "End line"},
|
||||
"new": {"type": "string", "description": "New content"}
|
||||
},
|
||||
"required": ["path", "start", "end", "new"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
|
||||
@@ -10,11 +10,12 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/cheikh2shift/godex/internal/rxcache"
|
||||
)
|
||||
|
||||
var runCmdMu sync.Mutex
|
||||
@@ -178,9 +179,9 @@ type BashServer struct {
|
||||
}
|
||||
|
||||
var (
|
||||
cdRegex = regexp.MustCompile(`(?i)^cd\s+(\S+)`)
|
||||
interpreterRegex = regexp.MustCompile(`(?i)(^|\s)(python[0-9.]*|pypy|ruby|perl|python|bash|sh|zsh|fish|r|lua|lua5|tcl|expect)(\s|$)`)
|
||||
pathRegex = regexp.MustCompile(`(?:^|\s)([/\\]+[^\s/\\]+(?:/[^\s/\\]*)*|[\.]{1,2}[/\\]|(?:~|\$(?:HOME|\w+))[/\\][^\s/\\]+)`)
|
||||
cdRegex = rxcache.MustCompile(`(?i)^cd\s+(\S+)`)
|
||||
interpreterRegex = rxcache.MustCompile(`(?i)(^|\s)(python[0-9.]*|pypy|ruby|perl|python|bash|sh|zsh|fish|r|lua|lua5|tcl|expect)(\s|$)`)
|
||||
pathRegex = rxcache.MustCompile(`(?:^|\s)([/\\]+[^\s/\\]+(?:/[^\s/\\]*)*|[\.]{1,2}[/\\]|(?:~|\$(?:HOME|\w+))[/\\][^\s/\\]+)`)
|
||||
)
|
||||
|
||||
func extractPathsFromCommand(command string) []string {
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/cheikh2shift/godex/internal/ml"
|
||||
"github.com/cheikh2shift/godex/internal/rxcache"
|
||||
)
|
||||
|
||||
func getIntArg(args map[string]any, key, alias string) (float64, bool) {
|
||||
@@ -106,11 +107,6 @@ func NewFileSystemServer(allowedPaths []string, autoConfirm bool) *FileSystemSer
|
||||
Description: "Replace the first occurrence of a text string in a file",
|
||||
InputSchema: json.RawMessage(`{"type":"object","properties":{"path":{"type":"string","description":"Absolute path to the file"},"find":{"type":"string","description":"Text to find (plain string, not regex)"},"replace":{"type":"string","description":"Replacement text"}},"required":["path","find","replace"]}`),
|
||||
},
|
||||
{
|
||||
Name: "replace_line_range",
|
||||
Description: "Delete a range of lines and insert new content at that location in a file",
|
||||
InputSchema: json.RawMessage(`{"type":"object","properties":{"path":{"type":"string","description":"Absolute path to the file"},"start":{"type":"integer","description":"Start line number (1-indexed)"},"end":{"type":"integer","description":"End line number (inclusive)"},"new_content":{"type":"string","description":"New content to insert at the deleted location"},"start_line":{"type":"integer","description":"Alias for start"},"end_line":{"type":"integer","description":"Alias for end"}},"required":["path","start","end","new_content"]}`),
|
||||
},
|
||||
{
|
||||
Name: "insert_at_line",
|
||||
Description: "Insert content at a specific line number",
|
||||
@@ -171,8 +167,6 @@ func (s *FileSystemServer) CallTool(ctx context.Context, name string, arguments
|
||||
return s.readFileLineRange(arguments)
|
||||
case "replace_first_in_file":
|
||||
return s.replaceFirstInFile(arguments)
|
||||
case "replace_line_range":
|
||||
return s.replaceLineRange(arguments)
|
||||
case "insert_at_line":
|
||||
return s.insertAtLine(arguments)
|
||||
case "search_file_text":
|
||||
@@ -547,60 +541,6 @@ func (s *FileSystemServer) replaceFirstInFile(args map[string]any) (string, erro
|
||||
return fmt.Sprintf("Replaced first occurrence of %s in %s", find, path), nil
|
||||
}
|
||||
|
||||
func (s *FileSystemServer) replaceLineRange(args map[string]any) (string, error) {
|
||||
path, ok := args["path"].(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("path is required")
|
||||
}
|
||||
|
||||
if err := s.checkAndMaybeAddPath(path); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
content, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read file: %w", err)
|
||||
}
|
||||
|
||||
lines := strings.Split(string(content), "\n")
|
||||
start, ok := getIntArg(args, "start", "start_line")
|
||||
if !ok {
|
||||
return "", fmt.Errorf("start is required")
|
||||
}
|
||||
end, ok := getIntArg(args, "end", "end_line")
|
||||
if !ok {
|
||||
return "", fmt.Errorf("end is required")
|
||||
}
|
||||
newContent, ok := args["new_content"].(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("new_content is required")
|
||||
}
|
||||
|
||||
startIdx := int(start) - 1
|
||||
endIdx := int(end)
|
||||
|
||||
if startIdx < 0 || startIdx >= len(lines) {
|
||||
return "", fmt.Errorf("start line out of range")
|
||||
}
|
||||
if endIdx < startIdx {
|
||||
return "", fmt.Errorf("end line must be greater than or equal to start line")
|
||||
}
|
||||
if endIdx > len(lines) {
|
||||
endIdx = len(lines)
|
||||
}
|
||||
|
||||
deletedLines := endIdx - startIdx
|
||||
newLines := strings.Split(newContent, "\n")
|
||||
|
||||
result := append(lines[:startIdx], append(newLines, lines[endIdx:]...)...)
|
||||
|
||||
if err := os.WriteFile(path, []byte(strings.Join(result, "\n")), 0644); err != nil {
|
||||
return "", fmt.Errorf("failed to write file: %w", err)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("Replaced lines %d-%d (%d lines deleted) with %d lines in %s", int(start), int(end), deletedLines, len(newLines), path), nil
|
||||
}
|
||||
|
||||
func (s *FileSystemServer) insertAtLine(args map[string]any) (string, error) {
|
||||
path, ok := args["path"].(string)
|
||||
if !ok {
|
||||
@@ -664,7 +604,7 @@ func (s *FileSystemServer) searchFileText(args map[string]any) (string, error) {
|
||||
pattern = "(?i)" + pattern
|
||||
}
|
||||
var err error
|
||||
regex, err = regexp.Compile(pattern)
|
||||
regex, err = rxcache.Compile(pattern)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid regex pattern: %w", err)
|
||||
}
|
||||
@@ -755,7 +695,7 @@ func (s *FileSystemServer) searchInFile(args map[string]any) (string, error) {
|
||||
if !caseSensitive {
|
||||
compiledPattern = "(?i)" + compiledPattern
|
||||
}
|
||||
regex, err = regexp.Compile(compiledPattern)
|
||||
regex, err = rxcache.Compile(compiledPattern)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid regex pattern: %w", err)
|
||||
}
|
||||
@@ -819,7 +759,7 @@ func (s *FileSystemServer) searchDirectoryText(args map[string]any) (string, err
|
||||
compiledPattern = "(?i)" + compiledPattern
|
||||
}
|
||||
var err error
|
||||
regex, err = regexp.Compile(compiledPattern)
|
||||
regex, err = rxcache.Compile(compiledPattern)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid regex pattern: %w", err)
|
||||
}
|
||||
|
||||
@@ -6,11 +6,11 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/cheikh2shift/godex/internal/rxcache"
|
||||
"github.com/chromedp/chromedp"
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
@@ -204,7 +204,7 @@ func (s *WebScraperServer) processContent(htmlContent, grepPattern string, start
|
||||
}
|
||||
|
||||
func (s *WebScraperServer) applyGrep(htmlContent, pattern string) (string, error) {
|
||||
re, err := regexp.Compile(pattern)
|
||||
re, err := rxcache.Compile(pattern)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid grep pattern: %v", err)
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -22,6 +21,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/cheikh2shift/godex/internal/config"
|
||||
"github.com/cheikh2shift/godex/internal/rxcache"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -294,7 +294,7 @@ func resolveModelPathWithDownload(ctx context.Context, model string, downloadPro
|
||||
}
|
||||
|
||||
quantToFile := make(map[string]string)
|
||||
quantRe := regexp.MustCompile(`-([A-Za-z0-9_]+)\.gguf$`)
|
||||
quantRe := rxcache.MustCompile(`-([A-Za-z0-9_]+)\.gguf$`)
|
||||
for _, f := range ggufFiles {
|
||||
matches := quantRe.FindStringSubmatch(f)
|
||||
if len(matches) >= 2 {
|
||||
|
||||
@@ -6,11 +6,12 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/cheikh2shift/godex/internal/rxcache"
|
||||
)
|
||||
|
||||
func TestResolveModelPathLocal(t *testing.T) {
|
||||
@@ -36,7 +37,7 @@ func TestResolveModelPathLocal(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestQuantizationRegexParsing(t *testing.T) {
|
||||
re := regexp.MustCompile(`-([A-Za-z0-9_]+)\.gguf$`)
|
||||
re := rxcache.MustCompile(`-([A-Za-z0-9_]+)\.gguf$`)
|
||||
|
||||
tests := []struct {
|
||||
filename string
|
||||
@@ -513,7 +514,7 @@ func min(a, b int) int {
|
||||
}
|
||||
|
||||
func TestParseContextValueFromHTML(t *testing.T) {
|
||||
re := regexp.MustCompile(`(\d+)[Kk]\s*context\s*window`)
|
||||
re := rxcache.MustCompile(`(\d+)[Kk]\s*context\s*window`)
|
||||
|
||||
tests := []struct {
|
||||
html string
|
||||
|
||||
@@ -7,13 +7,13 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/cheikh2shift/godex/internal/config"
|
||||
"github.com/cheikh2shift/godex/internal/rxcache"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -169,7 +169,7 @@ func (o *ollamaProvider) fetchOllamaLibraryContext() error {
|
||||
return err
|
||||
}
|
||||
|
||||
re := regexp.MustCompile(`(\d+)[Kk]\s*context\s*window`)
|
||||
re := rxcache.MustCompile(`(\d+)[Kk]\s*context\s*window`)
|
||||
matches := re.FindStringSubmatch(string(body))
|
||||
if len(matches) >= 2 {
|
||||
contextStr := matches[1]
|
||||
@@ -203,7 +203,7 @@ func GetOllamaContextLimit(model string) (int, error) {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
re := regexp.MustCompile(`(\d+)[Kk]\s*context\s*window`)
|
||||
re := rxcache.MustCompile(`(\d+)[Kk]\s*context\s*window`)
|
||||
matches := re.FindStringSubmatch(string(body))
|
||||
if len(matches) >= 2 {
|
||||
contextStr := matches[1]
|
||||
|
||||
@@ -5,11 +5,12 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/cheikh2shift/godex/internal/rxcache"
|
||||
)
|
||||
|
||||
func TestParseOllamaLibraryContextRegex(t *testing.T) {
|
||||
@@ -69,7 +70,7 @@ func TestParseOllamaLibraryContextRegex(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
re := regexp.MustCompile(`(\d+)[Kk]\s*context\s*window`)
|
||||
re := rxcache.MustCompile(`(\d+)[Kk]\s*context\s*window`)
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
@@ -99,7 +100,7 @@ func TestParseOllamaLibraryContextRegex(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestExtractContextFromOllamaHTML(t *testing.T) {
|
||||
re := regexp.MustCompile(`(\d+)[Kk]\s*context\s*window`)
|
||||
re := rxcache.MustCompile(`(\d+)[Kk]\s*context\s*window`)
|
||||
|
||||
realisticHTML := `
|
||||
<!DOCTYPE html>
|
||||
@@ -176,7 +177,7 @@ func TestExtractContextFromOllamaHTMLVariations(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
re := regexp.MustCompile(`(\d+)[Kk]\s*context\s*window`)
|
||||
re := rxcache.MustCompile(`(\d+)[Kk]\s*context\s*window`)
|
||||
|
||||
for _, v := range variations {
|
||||
t.Run(v.name, func(t *testing.T) {
|
||||
@@ -205,7 +206,7 @@ func TestExtractContextNoMatch(t *testing.T) {
|
||||
`<div>just numbers 128K here</div>`,
|
||||
}
|
||||
|
||||
re := regexp.MustCompile(`(\d+)[Kk]\s*context\s*window`)
|
||||
re := rxcache.MustCompile(`(\d+)[Kk]\s*context\s*window`)
|
||||
|
||||
for _, html := range noMatchCases {
|
||||
matches := re.FindStringSubmatch(html)
|
||||
@@ -285,7 +286,7 @@ func TestFetchOllamaLibraryContextIntegration(t *testing.T) {
|
||||
}
|
||||
|
||||
client := &http.Client{Timeout: 30 * time.Second}
|
||||
re := regexp.MustCompile(`(\d+)[Kk]\s*context\s*window`)
|
||||
re := rxcache.MustCompile(`(\d+)[Kk]\s*context\s*window`)
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.model, func(t *testing.T) {
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package rxcache
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"sync"
|
||||
)
|
||||
|
||||
func MustCompile(pattern string) *regexp.Regexp { return globalCache.MustCompile(pattern) }
|
||||
func Compile(pattern string) (*regexp.Regexp, error) { return globalCache.Compile(pattern) }
|
||||
|
||||
const globalCacheLimit = 1000
|
||||
|
||||
var globalCache = RegexpCache{m: make(map[string]*regexp.Regexp, globalCacheLimit), limit: globalCacheLimit}
|
||||
|
||||
type RegexpCache struct {
|
||||
mu sync.RWMutex
|
||||
m map[string]*regexp.Regexp
|
||||
limit int
|
||||
}
|
||||
|
||||
func (c *RegexpCache) MustCompile(pattern string) *regexp.Regexp {
|
||||
r, err := c.Compile(pattern)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (c *RegexpCache) Compile(pattern string) (*regexp.Regexp, error) {
|
||||
c.mu.RLock()
|
||||
r := c.m[pattern]
|
||||
c.mu.RUnlock()
|
||||
if r != nil {
|
||||
return r, nil
|
||||
}
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
if r = c.m[pattern]; r != nil {
|
||||
return r, nil
|
||||
}
|
||||
if n := len(c.m) - c.limit; c.limit > 0 && n > 0 {
|
||||
for k := range c.m {
|
||||
delete(c.m, k)
|
||||
n--
|
||||
if n <= 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
r, err := regexp.Compile(pattern)
|
||||
if r != nil {
|
||||
c.m[pattern] = r
|
||||
}
|
||||
return r, err
|
||||
}
|
||||
@@ -2,8 +2,9 @@ package toolcalls
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/cheikh2shift/godex/internal/rxcache"
|
||||
)
|
||||
|
||||
// ExtractAllToolCalls extracts tool calls from a model response in a variety of common formats.
|
||||
@@ -69,11 +70,11 @@ func ExtractAllToolCalls(text string) []map[string]any {
|
||||
|
||||
// Safety fallback for the specific [TOOL_CALL] format
|
||||
if len(results) == 0 {
|
||||
toolCallRe := regexp.MustCompile(`(?s)\[TOOL_CALL\]\s*(.*?)\s*\[/TOOL_CALL\]`)
|
||||
toolCallRe := rxcache.MustCompile(`(?s)\[TOOL_CALL\]\s*(.*?)\s*\[/TOOL_CALL\]`)
|
||||
tcMatches := toolCallRe.FindAllStringSubmatch(candidate, -1)
|
||||
for _, m := range tcMatches {
|
||||
if len(m) > 1 {
|
||||
nameRe := regexp.MustCompile(`tool\s*=>\s*"([^"]+)"`)
|
||||
nameRe := rxcache.MustCompile(`tool\s*=>\s*"([^"]+)"`)
|
||||
if nameMatch := nameRe.FindStringSubmatch(m[1]); len(nameMatch) > 1 {
|
||||
results = append(results, map[string]any{"name": nameMatch[1], "arguments": map[string]any{}})
|
||||
}
|
||||
@@ -139,7 +140,7 @@ func extractToolCallsFromJSONArray(text string) []map[string]any {
|
||||
|
||||
func extractNativeToolCalls(text string) [][2]string {
|
||||
var results [][2]string
|
||||
re := regexp.MustCompile(`\[TOOL_CALL:\s*([^\s|]+)\s*\|`)
|
||||
re := rxcache.MustCompile(`\[TOOL_CALL:\s*([^\s|]+)\s*\|`)
|
||||
matches := re.FindAllStringSubmatchIndex(text, -1)
|
||||
for _, m := range matches {
|
||||
if len(m) < 4 {
|
||||
@@ -194,8 +195,8 @@ func normalizeToolCallText(text string) string {
|
||||
|
||||
nonEmpty := 0
|
||||
withMargin := 0
|
||||
marginRe := regexp.MustCompile(`^\s*│`)
|
||||
stripRe := regexp.MustCompile(`^\s*│\s?`)
|
||||
marginRe := rxcache.MustCompile(`^\s*│`)
|
||||
stripRe := rxcache.MustCompile(`^\s*│\s?`)
|
||||
|
||||
for _, line := range lines {
|
||||
if strings.TrimSpace(line) == "" {
|
||||
|
||||
Reference in new issue
Block a user