Files
wehub-resource-sync e071084ebe
govulncheck / govulncheck (push) Has been cancelled
Lint / golangci-lint (push) Has been cancelled
Run Tests / Unit Tests (push) Has been cancelled
Run Tests / Etcd Integration Tests (push) Has been cancelled
Harness (E2E) / Harnesses (mock LLM) (push) Has been cancelled
Harness (E2E) / Provider harnesses (live LLM conformance) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:40:33 +08:00

164 lines
6.0 KiB
Go

package agent
import (
"fmt"
"io"
"net"
"os"
"os/exec"
"strings"
"go-micro.dev/v6/cmd"
)
type preflightCheck struct {
Name string
OK bool
Detail string
Fix string
Next string
}
type preflightDeps struct {
lookPath func(string) (string, error)
commandOutput func(string, ...string) ([]byte, error)
executable func() (string, error)
version func() string
getenv func(string) string
listen func(string, string) (net.Listener, error)
}
func defaultPreflightDeps() preflightDeps {
return preflightDeps{
lookPath: exec.LookPath,
commandOutput: func(name string, args ...string) ([]byte, error) { return exec.Command(name, args...).CombinedOutput() },
executable: os.Executable,
version: func() string { return cmd.App().Version },
getenv: os.Getenv,
listen: net.Listen,
}
}
func runAgentPreflight(w io.Writer, deps preflightDeps) error {
checks := agentPreflightChecks(deps)
failures := 0
fmt.Fprintln(w, "First-agent preflight")
for _, check := range checks {
mark := "✓"
if !check.OK {
mark = "✗"
failures++
}
fmt.Fprintf(w, " %s %s — %s\n", mark, check.Name, check.Detail)
if !check.OK && check.Fix != "" {
fmt.Fprintf(w, " Fix: %s\n", check.Fix)
}
if !check.OK && check.Next != "" {
fmt.Fprintf(w, " Next: %s\n", check.Next)
}
}
if failures > 0 {
return fmt.Errorf("first-agent preflight failed: %d check(s) need attention", failures)
}
fmt.Fprintln(w, "\nReady for the first-agent walkthrough: micro run, then open http://localhost:8080/agent or use micro chat.")
return nil
}
func agentPreflightChecks(deps preflightDeps) []preflightCheck {
if deps.lookPath == nil {
deps.lookPath = exec.LookPath
}
if deps.commandOutput == nil {
deps.commandOutput = func(name string, args ...string) ([]byte, error) { return exec.Command(name, args...).CombinedOutput() }
}
if deps.executable == nil {
deps.executable = os.Executable
}
if deps.version == nil {
deps.version = func() string { return cmd.App().Version }
}
if deps.getenv == nil {
deps.getenv = os.Getenv
}
if deps.listen == nil {
deps.listen = net.Listen
}
checks := []preflightCheck{checkGoToolchain(deps), checkMicroBinary(deps), checkProviderKey(deps), checkPortAvailable(deps, ":8080", "micro run gateway and /agent playground")}
return checks
}
func checkGoToolchain(deps preflightDeps) preflightCheck {
path, err := deps.lookPath("go")
if err != nil {
return preflightCheck{Name: "Go toolchain", Detail: "go was not found on PATH", Fix: "Install Go 1.24 or newer from https://go.dev/doc/install and ensure go is on PATH.", Next: "After installing Go, rerun micro agent preflight, then continue with docs/guides/your-first-agent.html."}
}
out, err := deps.commandOutput("go", "version")
if err != nil {
return preflightCheck{Name: "Go toolchain", Detail: strings.TrimSpace(string(out)), Fix: "Ensure the go command runs successfully (try `go version`) before starting the agent walkthrough.", Next: "Use docs/guides/debugging-agents.html after the toolchain check passes if an agent run still fails."}
}
version := firstLine(out)
if !goVersionAtLeast(version, 1, 24) {
return preflightCheck{Name: "Go toolchain", Detail: fmt.Sprintf("%s (%s)", version, path), Fix: "Upgrade to Go 1.24 or newer before running generated services.", Next: "Rerun micro agent preflight, then continue with docs/guides/your-first-agent.html."}
}
return preflightCheck{Name: "Go toolchain", OK: true, Detail: fmt.Sprintf("%s (%s)", version, path)}
}
func checkMicroBinary(deps preflightDeps) preflightCheck {
exe, err := deps.executable()
if err != nil || exe == "" {
return preflightCheck{Name: "micro binary", Detail: "micro executable path is unavailable", Fix: "Install the micro CLI or run this check through `go run ./cmd/micro agent preflight` from the repository.", Next: "Then follow docs/getting-started.html for the scaffold -> run path."}
}
version := deps.version()
if version == "" {
version = "version unavailable"
}
return preflightCheck{Name: "micro binary", OK: true, Detail: fmt.Sprintf("%s (%s)", version, exe)}
}
func checkProviderKey(deps preflightDeps) preflightCheck {
keys := []string{"MICRO_AI_API_KEY", "ANTHROPIC_API_KEY", "OPENAI_API_KEY", "GEMINI_API_KEY", "GROQ_API_KEY", "MISTRAL_API_KEY", "TOGETHER_API_KEY", "ATLASCLOUD_API_KEY"}
var found []string
for _, k := range keys {
if deps.getenv(k) != "" {
found = append(found, k)
}
}
if len(found) == 0 {
return preflightCheck{Name: "provider API key", Detail: "no supported provider key found", Fix: "Export MICRO_AI_API_KEY or a provider key such as ANTHROPIC_API_KEY before running provider-backed agents.", Next: "For a no-secret path, run the mock-model walkthrough in docs/guides/no-secret-first-agent.html; for real providers, see docs/guides/debugging-agents.html#provider-failures."}
}
return preflightCheck{Name: "provider API key", OK: true, Detail: "found " + strings.Join(found, ", ")}
}
func checkPortAvailable(deps preflightDeps, addr, use string) preflightCheck {
ln, err := deps.listen("tcp", addr)
if err != nil {
return preflightCheck{Name: "local port " + addr, Detail: "busy or unavailable for " + use, Fix: "Stop the process using " + addr + " (for example, `lsof -i :8080`) or run `micro run --address` with a free port.", Next: "Once the gateway starts, open http://localhost:8080/agent or continue with docs/guides/your-first-agent.html#chat-with-your-agent."}
}
_ = ln.Close()
return preflightCheck{Name: "local port " + addr, OK: true, Detail: "available for " + use}
}
func firstLine(b []byte) string {
s := strings.TrimSpace(string(b))
if i := strings.IndexByte(s, '\n'); i >= 0 {
return s[:i]
}
return s
}
func goVersionAtLeast(line string, wantMajor, wantMinor int) bool {
idx := strings.Index(line, "go1.")
if idx < 0 {
return false
}
var major, minor int
if _, err := fmt.Sscanf(line[idx:], "go%d.%d", &major, &minor); err != nil {
return false
}
if major != wantMajor {
return major > wantMajor
}
return minor >= wantMinor
}