mirror of
https://github.com/cheikh2shift/godex.git
synced 2026-10-22 02:53:24 -05:00
remove dead code
This commit is contained in:
1 parent
f4b6175d25
commit
fc74de2b53
15 files changed
-1041
No files matched your search
@@ -23,7 +23,3 @@ func readClipboardText() []byte {
|
||||
func readClipboardImage() []byte {
|
||||
return clipboard.Read(clipboard.FmtImage)
|
||||
}
|
||||
|
||||
func readClipboardImagePath() string {
|
||||
return ""
|
||||
}
|
||||
@@ -1225,10 +1225,6 @@ func (m *mcpLog) Printf(format string, args ...interface{}) {
|
||||
m.lines = append(m.lines, fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func (m *mcpLog) Print(args ...interface{}) {
|
||||
m.lines = append(m.lines, fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
func (m *mcpLog) Println(args ...interface{}) {
|
||||
m.lines = append(m.lines, fmt.Sprint(args...))
|
||||
}
|
||||
@@ -2241,68 +2237,6 @@ func formatElapsed(totalSeconds int) string {
|
||||
return fmt.Sprintf("%ds", totalSeconds)
|
||||
}
|
||||
|
||||
func parseToolCall(text string) (string, string, map[string]interface{}, bool) {
|
||||
text = strings.TrimSpace(text)
|
||||
|
||||
if strings.HasPrefix(text, "{") {
|
||||
var toolData map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(text), &toolData); err == nil {
|
||||
if name, ok := toolData["name"].(string); ok {
|
||||
args := make(map[string]interface{})
|
||||
if toolArgs, ok := toolData["arguments"].(map[string]interface{}); ok {
|
||||
args = toolArgs
|
||||
} else if toolArgs, ok := toolData["args"].(map[string]interface{}); ok {
|
||||
args = toolArgs
|
||||
}
|
||||
return name, name, args, true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lines := strings.Split(text, "\n")
|
||||
for _, line := range lines {
|
||||
trimmed := strings.TrimSpace(line)
|
||||
lower := strings.ToLower(trimmed)
|
||||
if strings.HasPrefix(lower, "call:") || strings.HasPrefix(lower, "tool_call:") || strings.HasPrefix(lower, "execute:") {
|
||||
parts := strings.SplitN(trimmed, ":", 2)
|
||||
if len(parts) < 2 {
|
||||
continue
|
||||
}
|
||||
toolPart := strings.TrimSpace(parts[1])
|
||||
name := ""
|
||||
args := make(map[string]interface{})
|
||||
if idx := strings.Index(toolPart, "("); idx > 0 {
|
||||
name = strings.TrimSpace(toolPart[:idx])
|
||||
argsStr := strings.Trim(toolPart[idx:], "()")
|
||||
args = parseArgs(argsStr)
|
||||
}
|
||||
if name != "" {
|
||||
return name, name, args, true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", "", nil, false
|
||||
}
|
||||
|
||||
func parseArgs(argsStr string) map[string]interface{} {
|
||||
args := make(map[string]interface{})
|
||||
if argsStr == "" {
|
||||
return args
|
||||
}
|
||||
parts := strings.Split(argsStr, ",")
|
||||
for _, part := range parts {
|
||||
part = strings.TrimSpace(part)
|
||||
if idx := strings.Index(part, "="); idx > 0 {
|
||||
key := strings.TrimSpace(part[:idx])
|
||||
value := strings.TrimSpace(part[idx+1:])
|
||||
value = strings.Trim(value, "\"'")
|
||||
args[key] = value
|
||||
}
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
func callTool(ctx context.Context, servers []MCPServer, name string, args map[string]interface{}, timeoutSecs int, autoDenyRestrictedPaths bool) (string, error) {
|
||||
|
||||
//log.Println("args", args)
|
||||
|
||||
@@ -635,13 +635,6 @@ func promptTruncateRunes(s string, max int) string {
|
||||
return string(runes[:max-3]) + "..."
|
||||
}
|
||||
|
||||
func promptFormatCommitDate(t time.Time) string {
|
||||
if t.IsZero() {
|
||||
return ""
|
||||
}
|
||||
return t.Format("2006-01-02 15:04")
|
||||
}
|
||||
|
||||
func commonPrefix(values []string) string {
|
||||
if len(values) == 0 {
|
||||
return ""
|
||||
|
||||
@@ -2,12 +2,10 @@ package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/cheikh2shift/godex/internal/config"
|
||||
"github.com/cheikh2shift/godex/internal/mcp"
|
||||
@@ -32,14 +30,6 @@ type ToolCallResult struct {
|
||||
Arguments string
|
||||
}
|
||||
|
||||
func (t *ToolCallResult) ParseArguments() (map[string]interface{}, error) {
|
||||
var result map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(t.Arguments), &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// SendPrompt dispatches the prompt to the configured provider via the registry.
|
||||
func SendPrompt(ctx context.Context, provider *config.Provider, prompt string) (string, error) {
|
||||
if provider == nil {
|
||||
@@ -58,26 +48,6 @@ func SendPrompt(ctx context.Context, provider *config.Provider, prompt string) (
|
||||
return strings.TrimSpace(resp), nil
|
||||
}
|
||||
|
||||
// SendPromptWithThink dispatches the prompt with a thinking callback.
|
||||
func SendPromptWithThink(ctx context.Context, provider *config.Provider, prompt string, onThink func(string)) (string, error) {
|
||||
if provider == nil {
|
||||
return "", fmt.Errorf("no provider provided")
|
||||
}
|
||||
|
||||
p, err := GetProvider(provider)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
p.SetThinkCallback(onThink)
|
||||
defer p.SetThinkCallback(nil)
|
||||
|
||||
resp, err := p.Send(ctx, prompt)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.TrimSpace(resp), nil
|
||||
}
|
||||
|
||||
func CancelPrompt(provider *config.Provider) {
|
||||
if provider == nil {
|
||||
@@ -101,33 +71,6 @@ func CancelPrompt(provider *config.Provider) {
|
||||
mcpExecutorsMu.Unlock()
|
||||
}
|
||||
|
||||
// CallTool executes an MCP tool call.
|
||||
func CallTool(ctx context.Context, provider *config.Provider, toolName string, args map[string]interface{}) (string, error) {
|
||||
if provider == nil {
|
||||
return "", fmt.Errorf("no provider provided")
|
||||
}
|
||||
|
||||
p, err := GetProvider(provider)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return p.CallTool(ctx, toolName, args)
|
||||
}
|
||||
|
||||
// GetTools returns the available MCP tools for a provider.
|
||||
func GetTools(provider *config.Provider) []providers.Tool {
|
||||
if provider == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
p, err := GetProvider(provider)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return p.Tools()
|
||||
}
|
||||
|
||||
func SetProviderTools(provider *config.Provider, tools []providers.Tool) {
|
||||
p, err := GetProvider(provider)
|
||||
@@ -139,15 +82,6 @@ func SetProviderTools(provider *config.Provider, tools []providers.Tool) {
|
||||
}
|
||||
}
|
||||
|
||||
func SubmitProviderToolResult(provider *config.Provider, toolCallID, result string) {
|
||||
p, err := GetProvider(provider)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if pt, ok := p.(interface{ SubmitToolResult(string, string) }); ok {
|
||||
pt.SubmitToolResult(toolCallID, result)
|
||||
}
|
||||
}
|
||||
|
||||
func RegisterMCPExecutor(provider *config.Provider, executor *mcp.MCPToolExecutor) {
|
||||
if provider == nil || executor == nil {
|
||||
@@ -213,171 +147,3 @@ func GetProvider(cfg *config.Provider) (providers.Provider, error) {
|
||||
func cacheKey(cfg *config.Provider) string {
|
||||
return fmt.Sprintf("%s|%s|%s|%s", cfg.Type, cfg.Name, cfg.Model, cfg.Endpoint)
|
||||
}
|
||||
|
||||
type ToolCall struct {
|
||||
ID string
|
||||
Name string
|
||||
Arguments map[string]interface{}
|
||||
}
|
||||
|
||||
type ToolCallResponse struct {
|
||||
ID string
|
||||
Result string
|
||||
Err error
|
||||
}
|
||||
|
||||
func LaunchToolsInParallel(ctx context.Context, provider *config.Provider, toolCalls []ToolCall) []ToolCallResponse {
|
||||
if len(toolCalls) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
providerName := "unknown"
|
||||
if provider != nil {
|
||||
providerName = provider.Name
|
||||
}
|
||||
|
||||
fmt.Printf("\n")
|
||||
fmt.Printf(" %s %s %s\n", "╔═══════════════════════════════════════════════════╗", "\033[1;36m🚀 LAUNCHING TOOLS IN PARALLEL\033[0m", "")
|
||||
fmt.Printf(" %s %s %s\n", "║", fmt.Sprintf("\033[1;33mProvider:\033[0m %s", providerName), " ║")
|
||||
fmt.Printf(" %s %s %s\n", "║", fmt.Sprintf("\033[1;33mTotal:\033[0m %d tools", len(toolCalls)), " ║")
|
||||
fmt.Printf(" %s %s %s\n", "╚═══════════════════════════════════════════════════╝", " ", "")
|
||||
|
||||
cyan := "\033[1;36m"
|
||||
green := "\033[1;32m"
|
||||
yellow := "\033[1;33m"
|
||||
reset := "\033[0m"
|
||||
|
||||
for i, tc := range toolCalls {
|
||||
arrow := "➤"
|
||||
argsJSON, _ := json.Marshal(tc.Arguments)
|
||||
fmt.Printf(" %s %s[%d]%s %s%s%s %s%s%s\n",
|
||||
arrow, cyan, i+1, reset,
|
||||
green, tc.Name, reset,
|
||||
yellow, string(argsJSON), reset)
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
responses := make([]ToolCallResponse, len(toolCalls))
|
||||
resultCh := make(chan struct {
|
||||
index int
|
||||
response ToolCallResponse
|
||||
}, len(toolCalls))
|
||||
|
||||
startTime := time.Now()
|
||||
|
||||
for i, tc := range toolCalls {
|
||||
wg.Add(1)
|
||||
go func(index int, call ToolCall) {
|
||||
defer wg.Done()
|
||||
|
||||
select {
|
||||
case resultCh <- struct {
|
||||
index int
|
||||
response ToolCallResponse
|
||||
}{index, ToolCallResponse{ID: call.ID, Result: "⏳ running..."}}:
|
||||
default:
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
resultCh <- struct {
|
||||
index int
|
||||
response ToolCallResponse
|
||||
}{index, ToolCallResponse{ID: call.ID, Err: ctx.Err()}}
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
var result string
|
||||
var err error
|
||||
|
||||
if provider != nil {
|
||||
result, err = CallTool(ctx, provider, call.Name, call.Arguments)
|
||||
} else {
|
||||
err = fmt.Errorf("no provider configured")
|
||||
}
|
||||
|
||||
response := ToolCallResponse{
|
||||
ID: call.ID,
|
||||
Result: result,
|
||||
Err: err,
|
||||
}
|
||||
|
||||
select {
|
||||
case resultCh <- struct {
|
||||
index int
|
||||
response ToolCallResponse
|
||||
}{index, response}:
|
||||
default:
|
||||
}
|
||||
}(i, tc)
|
||||
}
|
||||
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(resultCh)
|
||||
}()
|
||||
|
||||
for result := range resultCh {
|
||||
responses[result.index] = result.response
|
||||
}
|
||||
|
||||
elapsed := time.Since(startTime)
|
||||
|
||||
fmt.Printf("\n")
|
||||
fmt.Printf(" %s %s %s\n", "╔═══════════════════════════════════════════════════╗", "\033[1;32m✓ TOOLS COMPLETED\033[0m", "")
|
||||
fmt.Printf(" %s %s %s\n", "║", fmt.Sprintf("\033[1;33mTime:\033[0m %v", elapsed), " ║")
|
||||
fmt.Printf(" %s %s %s\n", "╚═══════════════════════════════════════════════════╝", " ", "")
|
||||
|
||||
for i, resp := range responses {
|
||||
statusIcon := "✓"
|
||||
if resp.Err != nil {
|
||||
statusIcon = "✗"
|
||||
}
|
||||
statusColor := green
|
||||
if resp.Err != nil {
|
||||
statusColor = "\033[1;31m"
|
||||
}
|
||||
|
||||
resultPreview := resp.Result
|
||||
toolName := toolCalls[i].Name
|
||||
|
||||
if toolName == "read_image" {
|
||||
// Full output for read_image
|
||||
resultPreview = resp.Result
|
||||
} else if toolName == "read_pdf" {
|
||||
// Truncate read_pdf but with higher limit
|
||||
if len(resultPreview) > 5000 {
|
||||
resultPreview = resultPreview[:5000] + "\n... (truncated)"
|
||||
}
|
||||
} else {
|
||||
// Truncate other tools
|
||||
if len(resultPreview) > 2500 {
|
||||
resultPreview = resultPreview[:2500] + "\n... (truncated)"
|
||||
}
|
||||
}
|
||||
|
||||
if len(resultPreview) > 60 {
|
||||
resultPreview = resultPreview[:60] + "..."
|
||||
}
|
||||
|
||||
name := toolCalls[i].Name
|
||||
if len(name) > 25 {
|
||||
name = name[:25] + "..."
|
||||
}
|
||||
|
||||
fmt.Printf(" %s %s[%d]%s %s%-25s%s %s%s%s\n",
|
||||
statusIcon,
|
||||
cyan, i+1, reset,
|
||||
statusColor, name, reset,
|
||||
yellow, resultPreview, reset)
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
if ctx.Err() != nil {
|
||||
fmt.Printf(" ⚠️ %s\n", ctx.Err())
|
||||
}
|
||||
|
||||
return responses
|
||||
}
|
||||
@@ -1,266 +0,0 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
projectctx "github.com/cheikh2shift/godex/internal/context"
|
||||
)
|
||||
|
||||
const (
|
||||
toolIntroFmt = `You have access to MCP tools. Use them to accomplish tasks.
|
||||
You can call multiple tools in a single response to be more efficient.
|
||||
To call tools, you MUST respond with one or more JSON objects, each in its own markdown code block.
|
||||
|
||||
Example:
|
||||
` + "```" + `json
|
||||
{
|
||||
"name": "read_file",
|
||||
"arguments": {
|
||||
"path": "/absolute/path/to/file"
|
||||
}
|
||||
}
|
||||
` + "```" + `
|
||||
|
||||
IMPORTANT: Execute tools FIRST, then provide the final answer. Do NOT include any final answer, summary, or "FINAL_ANSWER:" until AFTER you have executed all necessary tools and received their results. If you need to run commands/tests to verify something, run them first before answering.
|
||||
If you need to start a server/service or any long-running process, you MUST use background: true and continue.
|
||||
|
||||
Project tree:
|
||||
%s
|
||||
|
||||
User prompt:
|
||||
%s`
|
||||
toolFollowupFmt = "Tool results:\n%s\n\nPlease proceed or provide the FINAL_ANSWER if complete."
|
||||
)
|
||||
|
||||
type ToolRequest struct {
|
||||
Reads []string
|
||||
Greps []projectctx.GrepRequest
|
||||
Tree bool
|
||||
Description string
|
||||
}
|
||||
|
||||
func ToolIntroPrompt(prompt, tree string) string {
|
||||
if strings.TrimSpace(tree) == "" {
|
||||
tree = "(tree unavailable)"
|
||||
}
|
||||
return fmt.Sprintf(toolIntroFmt, tree, prompt)
|
||||
}
|
||||
|
||||
func ToolFollowupPrompt(excerpts string) string {
|
||||
return fmt.Sprintf(toolFollowupFmt, excerpts)
|
||||
}
|
||||
|
||||
func ParseToolRequests(text string) (ToolRequest, bool) {
|
||||
lines := strings.Split(text, "\n")
|
||||
req := ToolRequest{}
|
||||
for _, line := range lines {
|
||||
trimmed := strings.TrimSpace(line)
|
||||
lower := strings.ToLower(trimmed)
|
||||
switch {
|
||||
case lower == "tree":
|
||||
req.Tree = true
|
||||
case strings.HasPrefix(lower, "read:") || strings.HasPrefix(lower, "request:"):
|
||||
parts := strings.SplitN(trimmed, ":", 2)
|
||||
if len(parts) < 2 {
|
||||
continue
|
||||
}
|
||||
req.Reads = append(req.Reads, splitList(parts[1])...)
|
||||
case strings.HasPrefix(lower, "grep:"):
|
||||
parts := strings.SplitN(trimmed, ":", 2)
|
||||
if len(parts) < 2 {
|
||||
continue
|
||||
}
|
||||
spec := strings.TrimSpace(parts[1])
|
||||
if pattern, paths, ok := parseGrepSpec(spec); ok {
|
||||
req.Greps = append(req.Greps, projectctx.GrepRequest{Pattern: pattern, Paths: paths})
|
||||
continue
|
||||
}
|
||||
if looksLikePath(spec) {
|
||||
req.Reads = append(req.Reads, spec)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(req.Reads) == 0 && len(req.Greps) == 0 && !req.Tree {
|
||||
return ToolRequest{}, false
|
||||
}
|
||||
|
||||
var descParts []string
|
||||
if req.Tree {
|
||||
descParts = append(descParts, "TREE")
|
||||
}
|
||||
if len(req.Reads) > 0 {
|
||||
descParts = append(descParts, fmt.Sprintf("READ: %s", strings.Join(req.Reads, ", ")))
|
||||
}
|
||||
for _, grep := range req.Greps {
|
||||
descParts = append(descParts, fmt.Sprintf("GREP: %s | %s", grep.Pattern, strings.Join(grep.Paths, ", ")))
|
||||
}
|
||||
req.Description = strings.Join(descParts, " | ")
|
||||
return req, true
|
||||
}
|
||||
|
||||
func parseGrepSpec(spec string) (string, []string, bool) {
|
||||
pieces := strings.SplitN(spec, "|", 2)
|
||||
if len(pieces) < 2 {
|
||||
return "", nil, false
|
||||
}
|
||||
left := strings.TrimSpace(pieces[0])
|
||||
right := strings.TrimSpace(pieces[1])
|
||||
if left == "" || right == "" {
|
||||
return "", nil, false
|
||||
}
|
||||
|
||||
if looksLikePath(left) && looksLikeGrepCommand(right) {
|
||||
if extracted := extractGrepPattern(right); extracted != "" {
|
||||
return extracted, []string{left}, true
|
||||
}
|
||||
}
|
||||
|
||||
pattern := left
|
||||
paths := filterPaths(splitList(right))
|
||||
if pattern == "" || len(paths) == 0 {
|
||||
return "", nil, false
|
||||
}
|
||||
if looksLikeGrepCommand(pattern) {
|
||||
return "", nil, false
|
||||
}
|
||||
return pattern, paths, true
|
||||
}
|
||||
|
||||
func looksLikePath(value string) bool {
|
||||
if strings.ContainsAny(value, " \t") {
|
||||
return false
|
||||
}
|
||||
return strings.Contains(value, "/") || strings.Contains(value, ".")
|
||||
}
|
||||
|
||||
func looksLikeGrepCommand(value string) bool {
|
||||
lower := strings.ToLower(value)
|
||||
return strings.Contains(lower, "grep") || strings.Contains(lower, "rg ") || strings.Contains(lower, "ripgrep")
|
||||
}
|
||||
|
||||
func extractGrepPattern(value string) string {
|
||||
if extracted := extractQuoted(value); extracted != "" {
|
||||
return extracted
|
||||
}
|
||||
lower := strings.ToLower(value)
|
||||
if idx := strings.Index(lower, "grep"); idx >= 0 {
|
||||
rest := strings.TrimSpace(value[idx+4:])
|
||||
rest = strings.TrimLeft(rest, "-n ")
|
||||
if rest != "" {
|
||||
return strings.Fields(rest)[0]
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func extractQuoted(value string) string {
|
||||
for _, quote := range []string{"'", `"`} {
|
||||
if start := strings.Index(value, quote); start >= 0 {
|
||||
if end := strings.Index(value[start+1:], quote); end >= 0 {
|
||||
return value[start+1 : start+1+end]
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func filterPaths(paths []string) []string {
|
||||
var out []string
|
||||
for _, p := range paths {
|
||||
if p == "" {
|
||||
continue
|
||||
}
|
||||
if looksLikeGrepCommand(p) || strings.Contains(p, "|") {
|
||||
continue
|
||||
}
|
||||
out = append(out, p)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func ExtractCommandsFromResponse(text string) []string {
|
||||
return extractCommandBlocks(text)
|
||||
}
|
||||
|
||||
func ExtractCommandFromResponse(text string) (string, bool) {
|
||||
commands := extractCommandBlocks(text)
|
||||
if len(commands) == 0 {
|
||||
return "", false
|
||||
}
|
||||
return commands[0], true
|
||||
}
|
||||
|
||||
func extractCommandBlocks(text string) []string {
|
||||
lines := strings.Split(text, "\n")
|
||||
var commands []string
|
||||
for i := 0; i < len(lines); i++ {
|
||||
trimmed := strings.TrimSpace(lines[i])
|
||||
lower := strings.ToLower(trimmed)
|
||||
prefix := ""
|
||||
for _, p := range []string{"run:", "command:", "execute:"} {
|
||||
if strings.HasPrefix(lower, p) {
|
||||
prefix = p
|
||||
break
|
||||
}
|
||||
}
|
||||
if prefix == "" {
|
||||
continue
|
||||
}
|
||||
command := strings.TrimSpace(trimmed[len(prefix):])
|
||||
if command == "" {
|
||||
continue
|
||||
}
|
||||
marker := heredocMarker(command)
|
||||
if marker == "" {
|
||||
commands = append(commands, command)
|
||||
continue
|
||||
}
|
||||
var block strings.Builder
|
||||
block.WriteString(command)
|
||||
block.WriteString("\n")
|
||||
for j := i + 1; j < len(lines); j++ {
|
||||
block.WriteString(lines[j])
|
||||
block.WriteString("\n")
|
||||
if strings.TrimSpace(lines[j]) == marker {
|
||||
i = j
|
||||
break
|
||||
}
|
||||
}
|
||||
commands = append(commands, strings.TrimRight(block.String(), "\n"))
|
||||
}
|
||||
return commands
|
||||
}
|
||||
|
||||
func heredocMarker(command string) string {
|
||||
idx := strings.Index(command, "<<")
|
||||
if idx == -1 {
|
||||
return ""
|
||||
}
|
||||
rest := strings.TrimSpace(command[idx+2:])
|
||||
if rest == "" {
|
||||
return ""
|
||||
}
|
||||
fields := strings.Fields(rest)
|
||||
if len(fields) == 0 {
|
||||
return ""
|
||||
}
|
||||
marker := strings.Trim(fields[0], "'\"")
|
||||
if marker == "" {
|
||||
return ""
|
||||
}
|
||||
return marker
|
||||
}
|
||||
|
||||
func splitList(raw string) []string {
|
||||
parts := strings.Split(raw, ",")
|
||||
var out []string
|
||||
for _, part := range parts {
|
||||
item := strings.TrimSpace(part)
|
||||
if item != "" {
|
||||
out = append(out, item)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -13,11 +11,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
maxFileSizeBytes = 200 * 1024
|
||||
maxTreeLines = 500
|
||||
maxSnippetBytes = 8 * 1024
|
||||
maxTotalBytes = 50 * 1024
|
||||
maxGrepHits = 200
|
||||
treeCacheName = ".gtree"
|
||||
)
|
||||
|
||||
@@ -87,146 +81,6 @@ func BuildTree(root string) (string, error) {
|
||||
return tree, nil
|
||||
}
|
||||
|
||||
// ResolvePaths expands requested paths/globs against the project root.
|
||||
func ResolvePaths(root string, requested []string) ([]string, error) {
|
||||
ig := loadGitignore(root)
|
||||
wanted := map[string]struct{}{}
|
||||
patterns := make([]string, 0, len(requested))
|
||||
for _, entry := range requested {
|
||||
entry = strings.TrimSpace(entry)
|
||||
if entry == "" {
|
||||
continue
|
||||
}
|
||||
patterns = append(patterns, filepath.ToSlash(entry))
|
||||
}
|
||||
|
||||
err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rel, relErr := filepath.Rel(root, path)
|
||||
if relErr != nil {
|
||||
return relErr
|
||||
}
|
||||
if rel == "." {
|
||||
return nil
|
||||
}
|
||||
slashRel := filepath.ToSlash(rel)
|
||||
if shouldIgnorePath(ig, slashRel) {
|
||||
if d.IsDir() {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, pattern := range patterns {
|
||||
if pattern == slashRel {
|
||||
wanted[slashRel] = struct{}{}
|
||||
continue
|
||||
}
|
||||
if strings.ContainsAny(pattern, "*?[") {
|
||||
if ok, _ := filepath.Match(pattern, slashRel); ok {
|
||||
wanted[slashRel] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
results := make([]string, 0, len(wanted))
|
||||
for path := range wanted {
|
||||
results = append(results, path)
|
||||
}
|
||||
sort.Strings(results)
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// ReadFiles returns file excerpts for the requested paths.
|
||||
func ReadFiles(root string, paths []string) (string, error) {
|
||||
var buf strings.Builder
|
||||
total := 0
|
||||
for _, rel := range paths {
|
||||
abs := filepath.Join(root, filepath.FromSlash(rel))
|
||||
info, err := os.Stat(abs)
|
||||
if err != nil || info.IsDir() {
|
||||
continue
|
||||
}
|
||||
if info.Size() > maxFileSizeBytes {
|
||||
continue
|
||||
}
|
||||
data, err := os.ReadFile(abs)
|
||||
if err != nil || isBinary(data) {
|
||||
continue
|
||||
}
|
||||
content := string(data)
|
||||
if len(content) > maxSnippetBytes {
|
||||
content = content[:maxSnippetBytes]
|
||||
}
|
||||
entry := fmt.Sprintf("FILE: %s\n%s\n\n", rel, content)
|
||||
if total+len(entry) > maxTotalBytes {
|
||||
break
|
||||
}
|
||||
buf.WriteString(entry)
|
||||
total += len(entry)
|
||||
}
|
||||
return strings.TrimSpace(buf.String()), nil
|
||||
}
|
||||
|
||||
// GrepFiles searches for patterns in requested files.
|
||||
func GrepFiles(root string, req GrepRequest) (string, error) {
|
||||
pattern := strings.TrimSpace(req.Pattern)
|
||||
if pattern == "" {
|
||||
return "", nil
|
||||
}
|
||||
paths := req.Paths
|
||||
if len(paths) == 0 {
|
||||
lower := strings.ToLower(pattern)
|
||||
if strings.Contains(lower, "grep") || strings.Contains(lower, "rg ") || strings.Contains(pattern, "|") {
|
||||
return "", nil
|
||||
}
|
||||
return grepAll(root, pattern)
|
||||
}
|
||||
var buf strings.Builder
|
||||
count := 0
|
||||
for _, rel := range paths {
|
||||
abs := filepath.Join(root, filepath.FromSlash(rel))
|
||||
info, err := os.Stat(abs)
|
||||
if err != nil || info.IsDir() {
|
||||
continue
|
||||
}
|
||||
if info.Size() > maxFileSizeBytes {
|
||||
continue
|
||||
}
|
||||
file, err := os.Open(abs)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
scanner := bufio.NewScanner(file)
|
||||
lineNo := 0
|
||||
for scanner.Scan() {
|
||||
lineNo++
|
||||
line := scanner.Text()
|
||||
if strings.Contains(line, pattern) {
|
||||
buf.WriteString(fmt.Sprintf("GREP: %s:%d: %s\n", rel, lineNo, line))
|
||||
count++
|
||||
if count >= maxGrepHits {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
_ = file.Close()
|
||||
if count >= maxGrepHits {
|
||||
break
|
||||
}
|
||||
}
|
||||
return strings.TrimSpace(buf.String()), nil
|
||||
}
|
||||
|
||||
func loadGitignore(root string) *ignore.GitIgnore {
|
||||
path := filepath.Join(root, ".gitignore")
|
||||
@@ -274,74 +128,3 @@ func writeTreeCache(root, tree string) error {
|
||||
path := filepath.Join(root, treeCacheName)
|
||||
return os.WriteFile(path, []byte(tree), 0o644)
|
||||
}
|
||||
|
||||
func isBinary(data []byte) bool {
|
||||
for _, b := range data {
|
||||
if b == 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func grepAll(root, pattern string) (string, error) {
|
||||
ig := loadGitignore(root)
|
||||
var buf strings.Builder
|
||||
count := 0
|
||||
|
||||
err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rel, relErr := filepath.Rel(root, path)
|
||||
if relErr != nil {
|
||||
return relErr
|
||||
}
|
||||
if rel == "." {
|
||||
return nil
|
||||
}
|
||||
slashRel := filepath.ToSlash(rel)
|
||||
if shouldIgnorePath(ig, slashRel) {
|
||||
if d.IsDir() {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
info, infoErr := d.Info()
|
||||
if infoErr != nil || info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
if info.Size() > maxFileSizeBytes {
|
||||
return nil
|
||||
}
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
scanner := bufio.NewScanner(file)
|
||||
lineNo := 0
|
||||
for scanner.Scan() {
|
||||
lineNo++
|
||||
line := scanner.Text()
|
||||
if strings.Contains(line, pattern) {
|
||||
buf.WriteString(fmt.Sprintf("GREP: %s:%d: %s\n", slashRel, lineNo, line))
|
||||
count++
|
||||
if count >= maxGrepHits {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
_ = file.Close()
|
||||
if count >= maxGrepHits {
|
||||
return fs.SkipAll
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil && err != fs.SkipAll {
|
||||
return "", err
|
||||
}
|
||||
return strings.TrimSpace(buf.String()), nil
|
||||
}
|
||||
@@ -17,10 +17,6 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func GetWorkingDir() (string, error) {
|
||||
return os.Getwd()
|
||||
}
|
||||
|
||||
var runCmdMu sync.Mutex
|
||||
|
||||
// Job represents a running background job managed by a goroutine
|
||||
|
||||
@@ -137,13 +137,6 @@ func (m *MCPToolExecutor) buildArgs() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MCPToolExecutor) buildTransport() string {
|
||||
if m.server.Transport == "stdio" {
|
||||
return "stdio"
|
||||
}
|
||||
return "stdio"
|
||||
}
|
||||
|
||||
func listTools(ctx context.Context, mcpClient *client.Client) ([]Tool, error) {
|
||||
result, err := mcpClient.ListTools(ctx, mcp.ListToolsRequest{})
|
||||
if err != nil {
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -194,18 +193,6 @@ func StartVisionServer(ctx context.Context) error {
|
||||
return EnsureOCR(ctx)
|
||||
}
|
||||
|
||||
func checkServerHealth(url string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func getGodexBinDir() (string, error) {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filepath.Join(home, ".godex", "bin"), nil
|
||||
}
|
||||
|
||||
func DownloadVisionServer(ctx context.Context) error {
|
||||
visionDebug("Attempting to install tesseract...")
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
@@ -96,43 +95,6 @@ type DownloadProgress struct {
|
||||
Filename string
|
||||
}
|
||||
|
||||
func DownloadProgressCallback(progress chan<- DownloadProgress) func(bytesRead int64, contentLength int64, done bool) {
|
||||
return func(bytesRead int64, contentLength int64, done bool) {
|
||||
if progress != nil {
|
||||
progress <- DownloadProgress{
|
||||
Downloaded: bytesRead,
|
||||
Total: contentLength,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func GetGGUFQuantizations(ctx context.Context, modelID string) ([]string, error) {
|
||||
files, err := getGGUFFiles(ctx, modelID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
quants := make(map[string]string)
|
||||
quantRe := regexp.MustCompile(`-([A-Za-z0-9_]+)\.gguf$`)
|
||||
|
||||
for _, f := range files {
|
||||
matches := quantRe.FindStringSubmatch(f)
|
||||
if len(matches) >= 2 {
|
||||
quant := matches[1]
|
||||
if _, exists := quants[quant]; !exists {
|
||||
quants[quant] = f
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result := make([]string, 0, len(quants))
|
||||
for q := range quants {
|
||||
result = append(result, q)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
var quantOrder = map[string]int{
|
||||
"Q2_K": 1,
|
||||
"Q3_K_S": 2,
|
||||
|
||||
@@ -63,24 +63,6 @@ func TestGetDownloadURL(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetGGUFQuantizations(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
quants, err := GetGGUFQuantizations(ctx, "Qwen/Qwen2.5-3B-Instruct-GGUF")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get quantizations: %v", err)
|
||||
}
|
||||
|
||||
if len(quants) == 0 {
|
||||
t.Fatal("Expected quantizations, got none")
|
||||
}
|
||||
|
||||
t.Logf("Found %d quantizations:", len(quants))
|
||||
for _, q := range quants {
|
||||
t.Logf(" - %s", q)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveModelPathWithDownload(t *testing.T) {
|
||||
t.Skip("Skipping download test - requires user input for quantization selection")
|
||||
}
|
||||
|
||||
@@ -1565,45 +1565,3 @@ func (p *llamaProvider) SetModel(model string, contextLimit int) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetLlamaContextLimitFromModel(model string) (int, error) {
|
||||
return 0, fmt.Errorf("context limit detection from model name not implemented for llama.cpp")
|
||||
}
|
||||
|
||||
func parseModelContext(model string) int {
|
||||
re := regexp.MustCompile(`-(\d+)k[_-]?ctx`)
|
||||
matches := re.FindStringSubmatch(strings.ToLower(model))
|
||||
if len(matches) >= 2 {
|
||||
if val, err := strconv.Atoi(matches[1]); err == nil {
|
||||
return val * 1000
|
||||
}
|
||||
}
|
||||
|
||||
knownModels := map[string]int{
|
||||
"qwen2.5-coder": 8192,
|
||||
"qwen2.5": 8192,
|
||||
"qwen2": 8192,
|
||||
"llama3": 8192,
|
||||
"llama3.1": 128 * 1000,
|
||||
"llama3.2": 128 * 1000,
|
||||
"codellama": 16384,
|
||||
"mistral": 32768,
|
||||
"mixtral": 32768,
|
||||
"phi": 4096,
|
||||
"phi3": 4096,
|
||||
"gemma": 8192,
|
||||
"gemma2": 8192,
|
||||
"stablelm": 8192,
|
||||
"deepseek-coder": 16384,
|
||||
"deepseek": 16384,
|
||||
}
|
||||
|
||||
modelLower := strings.ToLower(model)
|
||||
for prefix, ctx := range knownModels {
|
||||
if strings.Contains(modelLower, prefix) {
|
||||
return ctx
|
||||
}
|
||||
}
|
||||
|
||||
return 4096
|
||||
}
|
||||
@@ -13,33 +13,6 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestParseModelContextFromName(t *testing.T) {
|
||||
tests := []struct {
|
||||
model string
|
||||
expected int
|
||||
}{
|
||||
{"qwen2.5-coder:3b", 8192},
|
||||
{"qwen2.5:7b", 8192},
|
||||
{"llama3:8b", 8192},
|
||||
{"mistral:7b", 32768},
|
||||
{"mixtral:8x7b", 32768},
|
||||
{"codellama:34b", 16384},
|
||||
{"phi3:14b", 4096},
|
||||
{"gemma:7b", 8192},
|
||||
{"deepseek-coder:33b", 16384},
|
||||
{"unknown-model", 4096},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.model, func(t *testing.T) {
|
||||
result := parseModelContext(tt.model)
|
||||
if result != tt.expected {
|
||||
t.Errorf("parseModelContext(%q) = %d, want %d", tt.model, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveModelPathLocal(t *testing.T) {
|
||||
tests := []struct {
|
||||
model string
|
||||
@@ -225,29 +198,6 @@ func TestGetQuantizationDescription(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestModelDownloadURLIntegration(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping integration test in short mode")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
quants, err := GetGGUFQuantizations(ctx, "Qwen/Qwen2.5-3B-Instruct-GGUF")
|
||||
if err != nil {
|
||||
t.Skipf("could not fetch quantizations: %v", err)
|
||||
}
|
||||
|
||||
if len(quants) == 0 {
|
||||
t.Fatal("expected at least one quantization")
|
||||
}
|
||||
|
||||
t.Logf("Found %d quantizations for Qwen/Qwen2.5-3B-Instruct-GGUF:", len(quants))
|
||||
for _, q := range quants[:min(5, len(quants))] {
|
||||
t.Logf(" - %s", q)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLlamaGetGGUFFilesIntegration(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping integration test in short mode")
|
||||
|
||||
@@ -367,50 +367,3 @@ func TestGetOllamaContextLimitNonexistent(t *testing.T) {
|
||||
t.Error("expected error for nonexistent model, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetGGUFQuantizationsIntegration(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping integration test in short mode")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
||||
defer cancel()
|
||||
|
||||
tests := []struct {
|
||||
modelID string
|
||||
wantQuants []string
|
||||
}{
|
||||
{"Qwen/Qwen2.5-3B-Instruct-GGUF", []string{"Q4_K_M", "Q4_K_S", "Q5_K_M"}},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.modelID, func(t *testing.T) {
|
||||
quants, err := GetGGUFQuantizations(ctx, tt.modelID)
|
||||
if err != nil {
|
||||
t.Skipf("failed to fetch quantizations for %s: %v", tt.modelID, err)
|
||||
}
|
||||
|
||||
if len(quants) == 0 {
|
||||
t.Error("expected at least one quantization")
|
||||
}
|
||||
|
||||
t.Logf("Model %s: found %d quantizations", tt.modelID, len(quants))
|
||||
for _, q := range quants {
|
||||
t.Logf(" - %s", q)
|
||||
}
|
||||
|
||||
for _, want := range tt.wantQuants {
|
||||
found := false
|
||||
for _, q := range quants {
|
||||
if q == want {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Logf("expected quantization %s not found (may be acceptable)", want)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -334,21 +334,6 @@ func (p *openRouterProvider) Send(ctx context.Context, prompt string) (string, e
|
||||
return "", lastErr
|
||||
}
|
||||
|
||||
// extractUserInput extracts the actual user input from the full prompt
|
||||
// by finding "User request:" or "User asked:" and returning everything after it
|
||||
func extractUserInput(prompt string) string {
|
||||
// Try to find "User request:" first (new format)
|
||||
if idx := strings.Index(prompt, "User request:"); idx != -1 {
|
||||
return strings.TrimSpace(prompt[idx+len("User request:"):])
|
||||
}
|
||||
// Fall back to "User asked:" (follow-up format)
|
||||
if idx := strings.Index(prompt, "User asked:"); idx != -1 {
|
||||
return strings.TrimSpace(prompt[idx+len("User asked:"):])
|
||||
}
|
||||
// Fall back to returning the whole prompt if markers not found
|
||||
return prompt
|
||||
}
|
||||
|
||||
func buildResponsesInput(messages []map[string]interface{}) []map[string]interface{} {
|
||||
input := make([]map[string]interface{}, 0, len(messages))
|
||||
for _, msg := range messages {
|
||||
@@ -573,19 +558,6 @@ func (p *openRouterProvider) SetStatusChannel(ch chan<- string) {
|
||||
p.statusCh = ch
|
||||
}
|
||||
|
||||
func (p *openRouterProvider) sendStatus(msg string) {
|
||||
p.mu.Lock()
|
||||
ch := p.statusCh
|
||||
p.mu.Unlock()
|
||||
if ch == nil {
|
||||
return
|
||||
}
|
||||
select {
|
||||
case ch <- msg:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func pickMessageContent(content, reasoning string, details []reasoningDetail) string {
|
||||
candidates := []string{
|
||||
content,
|
||||
|
||||
Reference in new issue
Block a user