From b111ba08d8394147982e5a3ae3e0859cca530cdf Mon Sep 17 00:00:00 2001 From: owell andry Date: Tue, 24 Jun 2025 16:08:36 -0500 Subject: [PATCH] Add OpenRouter integration and CLI --- README.md | 8 ++++ go/cli/main.go | 69 ++++++++++++++++++++++++++++++++- go/openrouter/client.go | 84 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 159 insertions(+), 2 deletions(-) create mode 100644 go/openrouter/client.go diff --git a/README.md b/README.md index 206ba17..3cbff94 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,14 @@ Runix esta implementado completamente en Go. Para compilar las herramientas ejec ```bash go build ./... ``` +### Uso de la IA en consola + +Runix puede interactuar con [OpenRouter](https://openrouter.ai) para generar respuestas mediante IA. Define la variable de entorno `OPENROUTER_API_KEY` y utiliza el comando `chat`: + +```bash + go run ./go/cli chat --context "Hola" "¿Cómo estás?" +``` + --- diff --git a/go/cli/main.go b/go/cli/main.go index c75a09a..7afe27d 100644 --- a/go/cli/main.go +++ b/go/cli/main.go @@ -1,7 +1,72 @@ package main -import "fmt" +import ( + "flag" + "fmt" + "os" + + "runix/go/openrouter" +) func main() { - fmt.Println("¡Bienvenido a Runix en Go!") + if len(os.Args) < 2 { + usage() + return + } + + switch os.Args[1] { + case "chat": + chatCmd(os.Args[2:]) + case "create": + createCmd(os.Args[2:]) + default: + fmt.Println("Unknown command:", os.Args[1]) + usage() + } +} + +func usage() { + fmt.Println("Runix CLI") + fmt.Println("Usage:") + fmt.Println(" runix chat [options] ") + fmt.Println(" runix create ") +} + +func chatCmd(args []string) { + fs := flag.NewFlagSet("chat", flag.ExitOnError) + context := fs.String("context", "", "context prompt") + model := fs.String("model", "mistralai/mistral-small-3.2-24b-instruct:free", "model") + fs.Parse(args) + + if fs.NArg() < 1 { + fmt.Println("message is required") + return + } + + apiKey := os.Getenv("OPENROUTER_API_KEY") + if apiKey == "" { + fmt.Println("OPENROUTER_API_KEY environment variable not set") + return + } + + client := openrouter.NewClient(apiKey) + reply, err := client.Chat(*model, *context, fs.Arg(0)) + if err != nil { + fmt.Println("error:", err) + return + } + fmt.Println(reply) +} + +func createCmd(args []string) { + if len(args) < 1 { + fmt.Println("directory path is required") + return + } + path := args[0] + if err := os.MkdirAll(path, 0755); err != nil { + fmt.Println("error creating directory:", err) + return + } + fmt.Println("created directory", path) } diff --git a/go/openrouter/client.go b/go/openrouter/client.go new file mode 100644 index 0000000..3ecc6d7 --- /dev/null +++ b/go/openrouter/client.go @@ -0,0 +1,84 @@ +package openrouter + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" +) + +type Message struct { + Role string `json:"role"` + Content string `json:"content"` +} + +type chatRequest struct { + Model string `json:"model"` + Messages []Message `json:"messages"` +} + +type chatResponse struct { + Choices []struct { + Message Message `json:"message"` + } `json:"choices"` +} + +type Client struct { + apiKey string + baseURL string + httpClient *http.Client +} + +// NewClient returns an OpenRouter API client. +func NewClient(apiKey string) *Client { + return &Client{ + apiKey: apiKey, + baseURL: "https://openrouter.ai/api/v1", + httpClient: &http.Client{}, + } +} + +// Chat sends a message with an optional context prompt to the given model. +func (c *Client) Chat(model, context, message string) (string, error) { + reqBody := chatRequest{ + Model: model, + Messages: []Message{ + {Role: "system", Content: context}, + {Role: "user", Content: message}, + }, + } + data, err := json.Marshal(reqBody) + if err != nil { + return "", err + } + + req, err := http.NewRequest("POST", c.baseURL+"/chat/completions", bytes.NewReader(data)) + if err != nil { + return "", err + } + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+c.apiKey) + req.Header.Set("HTTP-Referer", "https://github.com/cordlang/runix") + req.Header.Set("User-Agent", "runix-cli") + + resp, err := c.httpClient.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + body, _ := io.ReadAll(resp.Body) + return "", fmt.Errorf("openrouter error: %s", string(body)) + } + + var r chatResponse + if err := json.NewDecoder(resp.Body).Decode(&r); err != nil { + return "", err + } + if len(r.Choices) == 0 { + return "", fmt.Errorf("no choices returned") + } + return r.Choices[0].Message.Content, nil +}