feat: update chat, settings, prompts and system info modules
This commit is contained in:
179
backend/internal/handlers/agenttools.go
Normal file
179
backend/internal/handlers/agenttools.go
Normal file
@@ -0,0 +1,179 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// AgentToolInfo holds version info for an AI coding agent tool.
|
||||
type AgentToolInfo struct {
|
||||
Name string `json:"name"`
|
||||
Found bool `json:"found"`
|
||||
Version string `json:"version,omitempty"`
|
||||
Path string `json:"path,omitempty"`
|
||||
}
|
||||
|
||||
// RuntimeInfo holds version info for a programming language runtime or compiler.
|
||||
type RuntimeInfo struct {
|
||||
Name string `json:"name"`
|
||||
Found bool `json:"found"`
|
||||
Version string `json:"version,omitempty"`
|
||||
Path string `json:"path,omitempty"`
|
||||
}
|
||||
|
||||
type agentToolDef struct {
|
||||
Name string
|
||||
Binaries []string
|
||||
Args []string
|
||||
}
|
||||
|
||||
type runtimeDef struct {
|
||||
Name string
|
||||
Binaries []string // try in order
|
||||
Args []string
|
||||
}
|
||||
|
||||
var agentToolDefs = []agentToolDef{
|
||||
{Name: "Codex", Binaries: []string{"codex"}, Args: []string{"--version"}},
|
||||
{Name: "Claude Code", Binaries: []string{"claude"}, Args: []string{"--version"}},
|
||||
{Name: "OpenCode", Binaries: []string{"opencode"}, Args: []string{"--version"}},
|
||||
}
|
||||
|
||||
var runtimeDefs = []runtimeDef{
|
||||
// python3 first; fall back to python on Windows where python3 may not exist
|
||||
{Name: "Python", Binaries: []string{"python3", "python"}, Args: []string{"--version"}},
|
||||
{Name: "Node.js", Binaries: []string{"node"}, Args: []string{"--version"}},
|
||||
{Name: "Bun", Binaries: []string{"bun"}, Args: []string{"--version"}},
|
||||
{Name: "Go", Binaries: []string{"go"}, Args: []string{"version"}},
|
||||
// Java outputs version to stderr — runVersionOutput handles this
|
||||
{Name: "Java", Binaries: []string{"java"}, Args: []string{"-version"}},
|
||||
{Name: "Rust", Binaries: []string{"rustc"}, Args: []string{"--version"}},
|
||||
// C compilers: detect both, show whichever is present
|
||||
{Name: "GCC", Binaries: []string{"gcc"}, Args: []string{"--version"}},
|
||||
{Name: "Clang", Binaries: []string{"clang"}, Args: []string{"--version"}},
|
||||
}
|
||||
|
||||
// extraSearchPaths returns common installation directories not always in PATH.
|
||||
func extraSearchPaths() []string {
|
||||
home, _ := os.UserHomeDir()
|
||||
paths := []string{
|
||||
filepath.Join(home, "bin"),
|
||||
filepath.Join(home, ".local", "bin"),
|
||||
filepath.Join(home, ".bun", "bin"),
|
||||
filepath.Join(home, "go", "bin"),
|
||||
filepath.Join(home, ".cargo", "bin"),
|
||||
"/usr/local/go/bin",
|
||||
"/usr/local/bin",
|
||||
"/usr/bin",
|
||||
}
|
||||
if runtime.GOOS == "windows" {
|
||||
appdata := os.Getenv("APPDATA")
|
||||
paths = append(paths,
|
||||
filepath.Join(appdata, "npm"),
|
||||
filepath.Join(home, "AppData", "Local", "Programs"),
|
||||
filepath.Join(home, "AppData", "Roaming", "npm"),
|
||||
)
|
||||
}
|
||||
return paths
|
||||
}
|
||||
|
||||
// lookupBinary searches PATH then extra directories for the binary.
|
||||
func lookupBinary(name string) string {
|
||||
if p, err := exec.LookPath(name); err == nil {
|
||||
return p
|
||||
}
|
||||
for _, dir := range extraSearchPaths() {
|
||||
candidate := filepath.Join(dir, name)
|
||||
if runtime.GOOS == "windows" {
|
||||
candidate += ".exe"
|
||||
}
|
||||
if _, err := os.Stat(candidate); err == nil {
|
||||
return candidate
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// runVersionOutput runs the binary and returns the first non-empty line from stdout
|
||||
// or stderr (some tools like Java output version to stderr).
|
||||
func runVersionOutput(binaryPath string, args []string) string {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 4*time.Second)
|
||||
defer cancel()
|
||||
cmd := exec.CommandContext(ctx, binaryPath, args...)
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
_ = cmd.Run()
|
||||
out := strings.TrimSpace(stdout.String())
|
||||
if out == "" {
|
||||
out = strings.TrimSpace(stderr.String())
|
||||
}
|
||||
if idx := strings.IndexByte(out, '\n'); idx >= 0 {
|
||||
out = strings.TrimSpace(out[:idx])
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// runVersion is kept for backward compatibility.
|
||||
func runVersion(binaryPath string, args []string) string {
|
||||
return runVersionOutput(binaryPath, args)
|
||||
}
|
||||
|
||||
// detectAgentTools detects all AI agent tools in parallel.
|
||||
func detectAgentTools() []AgentToolInfo {
|
||||
results := make([]AgentToolInfo, len(agentToolDefs))
|
||||
var wg sync.WaitGroup
|
||||
for i, def := range agentToolDefs {
|
||||
wg.Add(1)
|
||||
go func(idx int, d agentToolDef) {
|
||||
defer wg.Done()
|
||||
info := AgentToolInfo{Name: d.Name}
|
||||
for _, bin := range d.Binaries {
|
||||
p := lookupBinary(bin)
|
||||
if p == "" {
|
||||
continue
|
||||
}
|
||||
info.Found = true
|
||||
info.Path = p
|
||||
info.Version = runVersionOutput(p, d.Args)
|
||||
break
|
||||
}
|
||||
results[idx] = info
|
||||
}(i, def)
|
||||
}
|
||||
wg.Wait()
|
||||
return results
|
||||
}
|
||||
|
||||
// detectRuntimes detects all language runtimes in parallel.
|
||||
func detectRuntimes() []RuntimeInfo {
|
||||
results := make([]RuntimeInfo, len(runtimeDefs))
|
||||
var wg sync.WaitGroup
|
||||
for i, def := range runtimeDefs {
|
||||
wg.Add(1)
|
||||
go func(idx int, d runtimeDef) {
|
||||
defer wg.Done()
|
||||
info := RuntimeInfo{Name: d.Name}
|
||||
for _, bin := range d.Binaries {
|
||||
p := lookupBinary(bin)
|
||||
if p == "" {
|
||||
continue
|
||||
}
|
||||
info.Found = true
|
||||
info.Path = p
|
||||
info.Version = runVersionOutput(p, d.Args)
|
||||
break
|
||||
}
|
||||
results[idx] = info
|
||||
}(i, def)
|
||||
}
|
||||
wg.Wait()
|
||||
return results
|
||||
}
|
||||
Reference in New Issue
Block a user