Commit c98e237

mo khan <mo@mokhan.ca>
2025-06-22 02:41:18
use chat history
1 parent f5184a0
Changed files (2)
internal/del/assistant.go
@@ -10,6 +10,8 @@ import (
 	"os/exec"
 	"regexp"
 	"strings"
+
+	"github.com/ollama/ollama/api"
 )
 
 // Tool represents a tool Del can run
@@ -21,9 +23,10 @@ type Tool struct {
 
 // Del represents the assistant instance
 type Del struct {
-	aiProvider AIProvider
-	tools      map[string]*Tool
-	mcpServers map[string]string
+	aiProvider  AIProvider
+	tools       map[string]*Tool
+	mcpServers  map[string]string
+	chatHistory []api.Message // for stateful interaction
 }
 
 // NewDel creates a new assistant
@@ -99,12 +102,19 @@ func NewDel(provider AIProvider) *Del {
 		aiProvider: provider,
 		tools:      tools,
 		mcpServers: make(map[string]string),
+		chatHistory: []api.Message{
+			{
+				Role:    "system",
+				Content: "You are Del, a code assistant. For any request that involves system interaction, respond using: TOOL_USE: tool_name {json}. Available tools: run_command, read_file, write_file, list_dir. Example: TOOL_USE: list_dir {\"path\": \".\"}",
+			},
+		},
 	}
 }
 
 // StartREPL launches an interactive session
 func (d *Del) StartREPL(ctx context.Context) {
 	fmt.Printf("🎤 Del is ready with %s\n", d.aiProvider.Name())
+
 	scanner := bufio.NewScanner(os.Stdin)
 	for {
 		fmt.Print("🎤 You: ")
@@ -117,47 +127,53 @@ func (d *Del) StartREPL(ctx context.Context) {
 			return
 		}
 
-		resp, err := d.aiProvider.Generate(ctx, input)
+		d.chatHistory = append(d.chatHistory, api.Message{Role: "user", Content: input})
+
+		response, err := d.aiProvider.Chat(ctx, d.chatHistory)
 		if err != nil {
 			fmt.Println("[error]", err)
 			continue
 		}
-		output := d.handleToolCalls(ctx, resp)
+
+		d.chatHistory = append(d.chatHistory, api.Message{Role: "assistant", Content: response})
+		output := d.handleToolCalls(ctx, response)
 		fmt.Println(output)
 	}
 }
 
-// handleToolCalls parses output for TOOL_USE and runs the associated tool.
+// handleToolCalls parses and runs embedded tool requests from the model output
 func (d *Del) handleToolCalls(ctx context.Context, response string) string {
-	toolUseRE := regexp.MustCompile(`(?m)^TOOL_USE:\s*(\w+)\s*(\{.*\})`)
-	matches := toolUseRE.FindAllStringSubmatch(response, -1)
+	re := regexp.MustCompile(`(?s)TOOL_USE:\s*(\w+)\s*(\{.*?\})`)
+	matches := re.FindAllStringSubmatch(response, -1)
+	if len(matches) == 0 {
+		return response
+	}
 
-	var results []string
+	var finalOutput strings.Builder
 	for _, match := range matches {
-		name, jsonArgs := match[1], match[2]
-		tool, ok := d.tools[name]
+		toolName := match[1]
+		argsJSON := match[2]
+
+		tool, ok := d.tools[toolName]
 		if !ok {
-			results = append(results, fmt.Sprintf("[error] tool '%s' not found", name))
+			finalOutput.WriteString(fmt.Sprintf("[error] unknown tool: %s\n", toolName))
 			continue
 		}
 
 		var args map[string]interface{}
-		if err := json.Unmarshal([]byte(jsonArgs), &args); err != nil {
-			results = append(results, fmt.Sprintf("[error] invalid JSON args for tool '%s': %v", name, err))
+		if err := json.Unmarshal([]byte(argsJSON), &args); err != nil {
+			finalOutput.WriteString(fmt.Sprintf("[error] bad args: %v\n", err))
 			continue
 		}
 
 		result, err := tool.Handler(args)
 		if err != nil {
-			results = append(results, fmt.Sprintf("[error] tool '%s' failed: %v", name, err))
+			finalOutput.WriteString(fmt.Sprintf("[tool error] %v\n", err))
 			continue
 		}
-		results = append(results, fmt.Sprintf("[tool:%s] %v", name, result))
-	}
 
-	if len(results) == 0 {
-		return response
+		outBytes, _ := json.MarshalIndent(result, "", "  ")
+		finalOutput.WriteString(string(outBytes) + "\n")
 	}
-
-	return strings.TrimSpace(response) + "\n\n" + strings.Join(results, "\n")
+	return finalOutput.String()
 }
internal/del/provider.go
@@ -10,6 +10,7 @@ import (
 type AIProvider interface {
 	Generate(ctx context.Context, prompt string) (string, error)
 	Name() string
+	Chat(ctx context.Context, history []api.Message) (string, error)
 }
 
 type OllamaProvider struct {
@@ -44,3 +45,20 @@ func (o *OllamaProvider) Generate(ctx context.Context, prompt string) (string, e
 func (o *OllamaProvider) Name() string {
 	return fmt.Sprintf("Ollama (%s)", o.model)
 }
+
+func (o *OllamaProvider) Chat(ctx context.Context, history []api.Message) (string, error) {
+	var response string
+	stream := false
+	err := o.client.Chat(ctx, &api.ChatRequest{
+		Model:    o.model,
+		Messages: history,
+		Stream:   &stream,
+	}, api.ChatResponseFunc(func(resp api.ChatResponse) error {
+		response += resp.Message.Content
+		return nil
+	}))
+	if err != nil {
+		return "", err
+	}
+	return response, nil
+}