diff --git a/daiapi.service b/daiapi.service new file mode 100644 index 0000000..4836a8a --- /dev/null +++ b/daiapi.service @@ -0,0 +1,23 @@ +[Unit] +Description=DAIAPI HTTP service +After=network.target + +[Service] +Type=simple +User=igor +Group=igor +WorkingDirectory=/home/igor/prog/daiapi +ExecStart=/home/igor/prog/daiapi/daiapi +Restart=on-failure +RestartSec=3 +Environment=PORT=8000 +Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/home/igor/.local/bin +EnvironmentFile=-/etc/daiapi/daiapi.env +NoNewPrivileges=true +KillSignal=SIGINT +TimeoutStopSec=30 +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=multi-user.target diff --git a/main.go b/main.go new file mode 100644 index 0000000..f2553eb --- /dev/null +++ b/main.go @@ -0,0 +1,114 @@ +package main + +import ( + "bufio" + "bytes" + "encoding/json" + "log" + "net/http" + "os" + "os/exec" +) + +type runRequest struct { + Prompt string `json:"prompt"` +} + +type runResponse struct { + Success bool `json:"success"` + Answer *string `json:"answer"` + Usage interface{} `json:"usage"` + Stderr string `json:"stderr"` +} + +type event struct { + Type string `json:"type"` + Item map[string]interface{} `json:"item"` + Usage interface{} `json:"usage"` +} + +func main() { + mux := http.NewServeMux() + mux.HandleFunc("/run", runHandler) + + addr := ":8000" + if port := os.Getenv("PORT"); port != "" { + addr = ":" + port + } + + log.Printf("listening on %s", addr) + if err := http.ListenAndServe(addr, mux); err != nil { + log.Fatal(err) + } +} + +func runHandler(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) + return + } + + var req runRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, "invalid json body", http.StatusBadRequest) + return + } + + cmd := exec.Command( + "codex", + "exec", + "--skip-git-repo-check", + "--full-auto", + "--add-dir", "/tmp", + "--json", + req.Prompt, + ) + + var stdout bytes.Buffer + var stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + + err := cmd.Run() + + var finalText *string + var usage interface{} + + scanner := bufio.NewScanner(bytes.NewReader(stdout.Bytes())) + for scanner.Scan() { + line := bytes.TrimSpace(scanner.Bytes()) + if len(line) == 0 { + continue + } + + var evt event + if unmarshalErr := json.Unmarshal(line, &evt); unmarshalErr != nil { + continue + } + + if evt.Type == "item.completed" { + itemType, _ := evt.Item["type"].(string) + itemText, _ := evt.Item["text"].(string) + if (itemType == "agent_message" || itemType == "message") && itemText != "" { + textCopy := itemText + finalText = &textCopy + } + } + + if evt.Type == "turn.completed" { + usage = evt.Usage + } + } + + resp := runResponse{ + Success: err == nil, + Answer: finalText, + Usage: usage, + Stderr: stderr.String(), + } + + w.Header().Set("Content-Type", "application/json") + if encodeErr := json.NewEncoder(w).Encode(resp); encodeErr != nil { + http.Error(w, encodeErr.Error(), http.StatusInternalServerError) + } +}