Created
January 17, 2026 07:30
-
-
Save theblazehen/1c1954d09d1a98b0a4e827bf4fb14f44 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package main | |
| import ( | |
| "bufio" | |
| "bytes" | |
| "crypto/tls" | |
| "encoding/json" | |
| "flag" | |
| "fmt" | |
| "io" | |
| "net/http" | |
| "os" | |
| "os/exec" | |
| "strings" | |
| ) | |
| const apiURL = "https://your.llm.api/v1/chat/completions" | |
| const apiKey = "sk-yourkey" | |
| const model = "model here" | |
| type Message struct { | |
| Role string `json:"role"` | |
| Content string `json:"content,omitempty"` | |
| ToolCalls []ToolCall `json:"tool_calls,omitempty"` | |
| ToolCallID string `json:"tool_call_id,omitempty"` | |
| } | |
| type ToolCall struct { | |
| ID string `json:"id"` | |
| Type string `json:"type"` | |
| Function FunctionCall `json:"function"` | |
| } | |
| type FunctionCall struct { | |
| Name string `json:"name"` | |
| Arguments string `json:"arguments"` | |
| } | |
| type Tool struct { | |
| Type string `json:"type"` | |
| Function ToolFunction `json:"function"` | |
| } | |
| type ToolFunction struct { | |
| Name string `json:"name"` | |
| Description string `json:"description"` | |
| Parameters map[string]interface{} `json:"parameters"` | |
| } | |
| type Request struct { | |
| Model string `json:"model"` | |
| Messages []Message `json:"messages"` | |
| Tools []Tool `json:"tools,omitempty"` | |
| } | |
| type Choice struct { | |
| Message Message `json:"message"` | |
| FinishReason string `json:"finish_reason"` | |
| } | |
| type Response struct { | |
| Choices []Choice `json:"choices"` | |
| } | |
| var tools = []Tool{ | |
| { | |
| Type: "function", | |
| Function: ToolFunction{ | |
| Name: "run_command", | |
| Description: "Execute an rc shell command on Plan 9 and return its output", | |
| Parameters: map[string]interface{}{ | |
| "type": "object", | |
| "properties": map[string]interface{}{ | |
| "command": map[string]interface{}{ | |
| "type": "string", | |
| "description": "The rc shell command to execute", | |
| }, | |
| }, | |
| "required": []string{"command"}, | |
| }, | |
| }, | |
| }, | |
| { | |
| Type: "function", | |
| Function: ToolFunction{ | |
| Name: "read_file", | |
| Description: "Read the contents of a file", | |
| Parameters: map[string]interface{}{ | |
| "type": "object", | |
| "properties": map[string]interface{}{ | |
| "path": map[string]interface{}{ | |
| "type": "string", | |
| "description": "The file path to read", | |
| }, | |
| }, | |
| "required": []string{"path"}, | |
| }, | |
| }, | |
| }, | |
| { | |
| Type: "function", | |
| Function: ToolFunction{ | |
| Name: "write_file", | |
| Description: "Write content to a file", | |
| Parameters: map[string]interface{}{ | |
| "type": "object", | |
| "properties": map[string]interface{}{ | |
| "path": map[string]interface{}{ | |
| "type": "string", | |
| "description": "The file path to write to", | |
| }, | |
| "content": map[string]interface{}{ | |
| "type": "string", | |
| "description": "The content to write", | |
| }, | |
| }, | |
| "required": []string{"path", "content"}, | |
| }, | |
| }, | |
| }, | |
| { | |
| Type: "function", | |
| Function: ToolFunction{ | |
| Name: "list_directory", | |
| Description: "List files in a directory", | |
| Parameters: map[string]interface{}{ | |
| "type": "object", | |
| "properties": map[string]interface{}{ | |
| "path": map[string]interface{}{ | |
| "type": "string", | |
| "description": "The directory path to list", | |
| }, | |
| }, | |
| "required": []string{"path"}, | |
| }, | |
| }, | |
| }, | |
| } | |
| var client = &http.Client{ | |
| Transport: &http.Transport{ | |
| TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, | |
| }, | |
| } | |
| func callLLM(messages []Message) (*Response, error) { | |
| req := Request{ | |
| Model: model, | |
| Messages: messages, | |
| Tools: tools, | |
| } | |
| body, _ := json.Marshal(req) | |
| httpReq, _ := http.NewRequest("POST", apiURL, bytes.NewReader(body)) | |
| httpReq.Header.Set("Authorization", "Bearer "+apiKey) | |
| httpReq.Header.Set("Content-Type", "application/json") | |
| resp, err := client.Do(httpReq) | |
| if err != nil { | |
| return nil, err | |
| } | |
| defer resp.Body.Close() | |
| respBody, _ := io.ReadAll(resp.Body) | |
| var result Response | |
| if err := json.Unmarshal(respBody, &result); err != nil { | |
| return nil, fmt.Errorf("parse error: %v\nBody: %s", err, respBody) | |
| } | |
| return &result, nil | |
| } | |
| func executeTool(name string, args string) string { | |
| var params map[string]string | |
| json.Unmarshal([]byte(args), ¶ms) | |
| switch name { | |
| case "run_command": | |
| cmd := exec.Command("rc", "-c", params["command"]) | |
| out, err := cmd.CombinedOutput() | |
| if err != nil { | |
| return fmt.Sprintf("Error: %v\nOutput: %s", err, out) | |
| } | |
| return string(out) | |
| case "read_file": | |
| data, err := os.ReadFile(params["path"]) | |
| if err != nil { | |
| return fmt.Sprintf("Error: %v", err) | |
| } | |
| return string(data) | |
| case "write_file": | |
| err := os.WriteFile(params["path"], []byte(params["content"]), 0644) | |
| if err != nil { | |
| return fmt.Sprintf("Error: %v", err) | |
| } | |
| return "File written successfully" | |
| case "list_directory": | |
| entries, err := os.ReadDir(params["path"]) | |
| if err != nil { | |
| return fmt.Sprintf("Error: %v", err) | |
| } | |
| var names []string | |
| for _, e := range entries { | |
| if e.IsDir() { | |
| names = append(names, e.Name()+"/") | |
| } else { | |
| names = append(names, e.Name()) | |
| } | |
| } | |
| return strings.Join(names, "\n") | |
| default: | |
| return fmt.Sprintf("Unknown tool: %s", name) | |
| } | |
| } | |
| // runAgentLoop runs the agent loop with the given system and user prompts | |
| // Returns the final text response | |
| func runAgentLoop(systemPrompt, userPrompt string, quiet bool) string { | |
| messages := []Message{ | |
| {Role: "system", Content: systemPrompt}, | |
| {Role: "user", Content: userPrompt}, | |
| } | |
| maxIterations := 10 | |
| for i := 0; i < maxIterations; i++ { | |
| resp, err := callLLM(messages) | |
| if err != nil { | |
| return fmt.Sprintf("Error: %v", err) | |
| } | |
| if len(resp.Choices) == 0 { | |
| return "Error: No response from LLM" | |
| } | |
| choice := resp.Choices[0] | |
| messages = append(messages, choice.Message) | |
| // If there are tool calls, execute them | |
| if len(choice.Message.ToolCalls) > 0 { | |
| for _, tc := range choice.Message.ToolCalls { | |
| if !quiet { | |
| fmt.Fprintf(os.Stderr, "[Tool: %s] %s\n", tc.Function.Name, tc.Function.Arguments) | |
| } | |
| result := executeTool(tc.Function.Name, tc.Function.Arguments) | |
| // Truncate long results | |
| if len(result) > 2000 { | |
| result = result[:2000] + "\n... (truncated)" | |
| } | |
| messages = append(messages, Message{ | |
| Role: "tool", | |
| Content: result, | |
| ToolCallID: tc.ID, | |
| }) | |
| } | |
| continue // Loop to get next response | |
| } | |
| // No tool calls, return final response | |
| return choice.Message.Content | |
| } | |
| return "Error: Max iterations reached" | |
| } | |
| // runAcmeMode handles the Acme editor integration | |
| // Reads selection from stdin, processes AI: prompt, outputs replacement text | |
| func runAcmeMode() { | |
| // Read all of stdin (the selection from Acme) | |
| var selection strings.Builder | |
| scanner := bufio.NewScanner(os.Stdin) | |
| for scanner.Scan() { | |
| selection.WriteString(scanner.Text()) | |
| selection.WriteString("\n") | |
| } | |
| selectionText := selection.String() | |
| if strings.TrimSpace(selectionText) == "" { | |
| fmt.Println("Error: No selection provided") | |
| return | |
| } | |
| systemPrompt := `You are an AI assistant integrated into the Acme editor on Plan 9 (9front). | |
| The user has selected some text that contains a line with "AI:" (case-insensitive) followed by their request. | |
| Your response will REPLACE their entire selection in the editor. | |
| Rules: | |
| - Find the line containing "AI:" and understand it as the user's request | |
| - Do NOT include the "AI: ..." line in your response - it should be removed | |
| - Match the indentation and coding style of the surrounding code/text | |
| - Be concise - return ONLY what should replace the selection | |
| - Do NOT wrap your response in markdown code fences or any other formatting | |
| - Output raw code/text only, exactly as it should appear in the file | |
| - If asked to read a file, use the read_file tool then include relevant contents | |
| - If asked to write a file, use write_file tool AND return appropriate confirmation/preview text | |
| - If asked to run a command, use run_command tool and format the output nicely | |
| You have access to tools: | |
| - run_command: Execute rc shell commands | |
| - read_file: Read file contents | |
| - write_file: Write to files | |
| - list_directory: List directory contents | |
| The user's selected text is provided below. Find the "AI:" request, understand what they want, and respond with the replacement text only. No markdown, no code fences, just the raw replacement text.` | |
| userPrompt := selectionText | |
| // Run agent loop quietly (don't print tool names to stdout - they'd end up in the file) | |
| response := runAgentLoop(systemPrompt, userPrompt, true) | |
| // Output the response (this replaces the selection in Acme) | |
| fmt.Print(response) | |
| } | |
| // runDirectMode handles direct command-line invocation | |
| func runDirectMode(prompt string) { | |
| systemPrompt := `You are a helpful AI assistant running natively on Plan 9 from Bell Labs (9front). | |
| You have access to tools to interact with the system. Use them to help the user. | |
| The shell is rc, not bash. Common differences: | |
| - Use 'cat /dev/sysname' to get hostname | |
| - Use 'ls' for listing, 'du' for disk usage | |
| - Paths: /bin, /sys, /usr, /tmp, /mnt, /n | |
| - No head command, use 'sed 5q' instead of 'head -5' | |
| Be concise and helpful.` | |
| response := runAgentLoop(systemPrompt, prompt, false) | |
| fmt.Println(response) | |
| } | |
| // runReplMode runs an interactive REPL with conversation history | |
| func runReplMode() { | |
| systemPrompt := `You are a helpful AI assistant running natively on Plan 9 from Bell Labs (9front). | |
| You have access to tools to interact with the system. Use them to help the user. | |
| The shell is rc, not bash. Common differences: | |
| - Use 'cat /dev/sysname' to get hostname | |
| - Use 'ls' for listing, 'du' for disk usage | |
| - Paths: /bin, /sys, /usr, /tmp, /mnt, /n | |
| - No head command, use 'sed 5q' instead of 'head -5' | |
| Be concise and helpful.` | |
| messages := []Message{ | |
| {Role: "system", Content: systemPrompt}, | |
| } | |
| scanner := bufio.NewScanner(os.Stdin) | |
| fmt.Println("Plan 9 AI Agent REPL") | |
| fmt.Println("Type 'quit' or 'exit' to exit, 'clear' to reset conversation") | |
| fmt.Println() | |
| for { | |
| fmt.Print("> ") | |
| if !scanner.Scan() { | |
| break | |
| } | |
| input := strings.TrimSpace(scanner.Text()) | |
| if input == "" { | |
| continue | |
| } | |
| // Handle special commands | |
| switch strings.ToLower(input) { | |
| case "quit", "exit": | |
| fmt.Println("Goodbye!") | |
| return | |
| case "clear": | |
| messages = []Message{ | |
| {Role: "system", Content: systemPrompt}, | |
| } | |
| fmt.Println("Conversation cleared.") | |
| continue | |
| } | |
| // Add user message | |
| messages = append(messages, Message{Role: "user", Content: input}) | |
| // Run agent loop with conversation history | |
| maxIterations := 10 | |
| for i := 0; i < maxIterations; i++ { | |
| resp, err := callLLM(messages) | |
| if err != nil { | |
| fmt.Fprintf(os.Stderr, "Error: %v\n", err) | |
| break | |
| } | |
| if len(resp.Choices) == 0 { | |
| fmt.Println("Error: No response from LLM") | |
| break | |
| } | |
| choice := resp.Choices[0] | |
| messages = append(messages, choice.Message) | |
| // If there are tool calls, execute them | |
| if len(choice.Message.ToolCalls) > 0 { | |
| for _, tc := range choice.Message.ToolCalls { | |
| fmt.Fprintf(os.Stderr, "[Tool: %s] %s\n", tc.Function.Name, tc.Function.Arguments) | |
| result := executeTool(tc.Function.Name, tc.Function.Arguments) | |
| // Truncate long results | |
| if len(result) > 2000 { | |
| result = result[:2000] + "\n... (truncated)" | |
| } | |
| messages = append(messages, Message{ | |
| Role: "tool", | |
| Content: result, | |
| ToolCallID: tc.ID, | |
| }) | |
| } | |
| continue // Loop to get next response | |
| } | |
| // No tool calls, print response and break | |
| if choice.Message.Content != "" { | |
| fmt.Println() | |
| fmt.Println(choice.Message.Content) | |
| fmt.Println() | |
| } | |
| break | |
| } | |
| } | |
| } | |
| func main() { | |
| acmeMode := flag.Bool("acme", false, "Acme editor integration mode (reads selection from stdin)") | |
| replMode := flag.Bool("repl", false, "Interactive REPL mode with conversation history") | |
| flag.Parse() | |
| if *acmeMode { | |
| runAcmeMode() | |
| return | |
| } | |
| if *replMode { | |
| runReplMode() | |
| return | |
| } | |
| // Direct invocation mode | |
| args := flag.Args() | |
| if len(args) < 1 { | |
| fmt.Println("Usage: agent [-acme] [-repl] <prompt>") | |
| fmt.Println(" -acme Acme editor mode (reads selection from stdin)") | |
| fmt.Println(" -repl Interactive REPL mode with conversation history") | |
| os.Exit(1) | |
| } | |
| prompt := strings.Join(args, " ") | |
| runDirectMode(prompt) | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment