133 lines
3.9 KiB
Go
133 lines
3.9 KiB
Go
package backend
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"ollie/paths"
|
|
)
|
|
|
|
// loadEnvFile reads KEY=VALUE pairs from path and sets any key that is not
|
|
// already present in the environment. Lines beginning with # and blank lines
|
|
// are ignored. Errors opening the file are silently ignored (file is optional).
|
|
func loadEnvFile(path string) {
|
|
f, err := os.Open(path)
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer f.Close()
|
|
|
|
sc := bufio.NewScanner(f)
|
|
for sc.Scan() {
|
|
line := strings.TrimSpace(sc.Text())
|
|
if line == "" || strings.HasPrefix(line, "#") {
|
|
continue
|
|
}
|
|
k, v, ok := strings.Cut(line, "=")
|
|
if !ok {
|
|
continue
|
|
}
|
|
k = strings.TrimSpace(k)
|
|
if i := strings.Index(v, " #"); i >= 0 {
|
|
v = v[:i]
|
|
}
|
|
v = strings.TrimSpace(v)
|
|
if len(v) >= 2 && (v[0] == '"' && v[len(v)-1] == '"' || v[0] == '\'' && v[len(v)-1] == '\'') {
|
|
v = v[1 : len(v)-1]
|
|
}
|
|
if k != "" && os.Getenv(k) == "" {
|
|
os.Setenv(k, v)
|
|
}
|
|
}
|
|
}
|
|
|
|
// New constructs a Backend from environment variables.
|
|
// Values are loaded from ~/.config/ollie/env before consulting the environment,
|
|
// so the file acts as a default; variables already set in the environment take
|
|
// precedence.
|
|
//
|
|
// OLLIE_OPENAI_KEY API key (required for openai/openrouter/gemini)
|
|
// OLLIE_ANTHROPIC_KEY API key (required for anthropic)
|
|
// OLLIE_GEMINI_KEY Google AI Studio API key (required for gemini; falls back to OLLIE_OPENAI_KEY)
|
|
// OLLIE_COPILOT_TOKEN bearer token (required for copilot)
|
|
// OLLIE_KIRO_TOKEN bearer token or sqlite:// URL for kiro/codewhisperer
|
|
// (default: sqlite path auto-detected from Kiro CLI data dir)
|
|
func New() (Backend, error) {
|
|
return newFromEnv(paths.CfgDir() + "/env")
|
|
}
|
|
|
|
// NewWithName constructs a Backend for the given backend name, loading
|
|
// env-file defaults for API keys/URLs but ignoring OLLIE_BACKEND.
|
|
// If name is empty, falls back to ~/.config/ollie/backend; errors if neither is set.
|
|
func NewWithName(name string) (Backend, error) {
|
|
loadEnvFile(paths.CfgDir() + "/env")
|
|
if name == "" {
|
|
name = readDefaultBackend()
|
|
if name == "" {
|
|
return nil, fmt.Errorf("no backend specified: set backend= in agent config or create ~/.config/ollie/backend")
|
|
}
|
|
}
|
|
return newBackend(name)
|
|
}
|
|
|
|
// readDefaultBackend reads the default backend from ~/.config/ollie/backend.
|
|
// Returns "" if the file doesn't exist or is empty.
|
|
func readDefaultBackend() string {
|
|
data, err := os.ReadFile(paths.CfgDir() + "/backend")
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return strings.TrimSpace(string(data))
|
|
}
|
|
|
|
func newFromEnv(envFile string) (Backend, error) {
|
|
loadEnvFile(envFile)
|
|
|
|
which := os.Getenv("OLLIE_BACKEND")
|
|
if which == "" {
|
|
return nil, fmt.Errorf("OLLIE_BACKEND is not set")
|
|
}
|
|
|
|
return newBackend(which)
|
|
}
|
|
|
|
func newBackend(which string) (Backend, error) {
|
|
switch which {
|
|
case "ollama":
|
|
return NewOllama(os.Getenv("OLLAMA_HOST"))
|
|
case "openai":
|
|
return NewOpenAI("openai", "https://api.openai.com/v1", os.Getenv("OLLIE_OPENAI_KEY"))
|
|
case "openrouter":
|
|
return NewOpenAI("openrouter", "https://openrouter.ai/api/v1", os.Getenv("OLLIE_OPENAI_KEY"))
|
|
case "anthropic":
|
|
key := os.Getenv("OLLIE_ANTHROPIC_KEY")
|
|
if key == "" {
|
|
return nil, fmt.Errorf("OLLIE_ANTHROPIC_KEY is required for anthropic backend")
|
|
}
|
|
return NewAnthropic(key)
|
|
case "copilot":
|
|
token := os.Getenv("OLLIE_COPILOT_TOKEN")
|
|
if token == "" {
|
|
return nil, fmt.Errorf("OLLIE_COPILOT_TOKEN is required for copilot backend")
|
|
}
|
|
return NewCopilot(token)
|
|
case "kiro", "codewhisperer":
|
|
return NewCodeWhisperer(os.Getenv("OLLIE_KIRO_TOKEN"))
|
|
case "gemini":
|
|
key := geminiKey()
|
|
if key == "" {
|
|
return nil, fmt.Errorf("OLLIE_GEMINI_KEY is required for gemini backend")
|
|
}
|
|
return NewGemini(key)
|
|
default:
|
|
return nil, fmt.Errorf("unknown OLLIE_BACKEND %q (supported: ollama, openai, openrouter, anthropic, copilot, kiro, gemini)", which)
|
|
}
|
|
}
|
|
|
|
// Backends returns the list of supported backend names.
|
|
func Backends() []string {
|
|
return []string{"ollama", "openai", "openrouter", "anthropic", "copilot", "kiro", "gemini"}
|
|
}
|