summaryrefslogtreecommitdiff
path: root/internal/aitool/dispatch.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/aitool/dispatch.go')
-rw-r--r--internal/aitool/dispatch.go76
1 files changed, 76 insertions, 0 deletions
diff --git a/internal/aitool/dispatch.go b/internal/aitool/dispatch.go
new file mode 100644
index 0000000..0924c8b
--- /dev/null
+++ b/internal/aitool/dispatch.go
@@ -0,0 +1,76 @@
+package aitool
+
+import "os/exec"
+
+type Tool string
+
+const (
+ ToolOpencode Tool = "opencode"
+ ToolHexAI Tool = "hexai"
+ ToolClaude Tool = "claude"
+ ToolAmp Tool = "amp"
+)
+
+type LookPathFunc func(file string) (string, error)
+
+func Chain(preferred string) []Tool {
+ switch preferred {
+ case "", string(ToolOpencode):
+ return []Tool{ToolOpencode, ToolHexAI, ToolClaude, ToolAmp}
+ case string(ToolHexAI):
+ return []Tool{ToolHexAI, ToolClaude, ToolAmp}
+ case string(ToolClaude), "claude-code":
+ return []Tool{ToolClaude, ToolAmp}
+ case string(ToolAmp):
+ return []Tool{ToolAmp}
+ default:
+ return nil
+ }
+}
+
+func FirstAvailable(preferred string, lookPath LookPathFunc) Tool {
+ for _, tool := range Chain(preferred) {
+ if IsAvailable(tool, lookPath) {
+ return tool
+ }
+ }
+
+ return ""
+}
+
+func AvailableChain(preferred string, lookPath LookPathFunc) []Tool {
+ chain := Chain(preferred)
+ available := make([]Tool, 0, len(chain))
+ for _, tool := range chain {
+ if IsAvailable(tool, lookPath) {
+ available = append(available, tool)
+ }
+ }
+
+ return available
+}
+
+func IsAvailable(tool Tool, lookPath LookPathFunc) bool {
+ if lookPath == nil {
+ lookPath = exec.LookPath
+ }
+
+ binary, ok := availabilityBinary(tool)
+ if !ok {
+ return false
+ }
+
+ _, err := lookPath(binary)
+ return err == nil
+}
+
+func availabilityBinary(tool Tool) (string, bool) {
+ switch tool {
+ case ToolOpencode:
+ return "ollama", true
+ case ToolHexAI, ToolClaude, ToolAmp:
+ return string(tool), true
+ default:
+ return "", false
+ }
+}