Files
higress/hgctl/pkg/agent/core.go
2025-12-26 13:47:32 +08:00

268 lines
7.1 KiB
Go

// Copyright (c) 2025 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package agent
import (
"encoding/base64"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/alibaba/higress/hgctl/pkg/manifests"
"github.com/alibaba/higress/hgctl/pkg/util"
"github.com/fatih/color"
"github.com/manifoldco/promptui"
"github.com/spf13/viper"
)
type AgenticCore struct {
binaryName string
}
func NewAgenticCore() *AgenticCore {
core := &AgenticCore{
binaryName: viper.GetString(HGCTL_AGENT_CORE),
}
core.Setup()
return core
}
func (c *AgenticCore) GetPromptFileName() string {
switch c.binaryName {
case string(CORE_CLAUDE):
return "CLAUDE.md"
case string(CORE_QODERCLI):
return "AGENTS.md"
}
return ""
}
func (c *AgenticCore) GetCoreDirName() string {
switch c.binaryName {
case string(CORE_CLAUDE):
return ".claude"
case string(CORE_QODERCLI):
return ".qoder"
}
return ""
}
// This will use core to test and improve created agent
func (c *AgenticCore) ImproveNewAgent(config *AgentConfig) error {
agentDir, err := util.GetSpecificAgentDir(config.AgentName)
if err != nil {
return fmt.Errorf("failed to get agent directory: %s", agentDir)
}
return c.runInTargetDir(agentDir)
}
func (c *AgenticCore) runInTargetDir(dir string, args ...string) error {
cmd := exec.Command(c.binaryName, args...)
cmd.Dir = dir
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
func (c *AgenticCore) runWithResult(args ...string) (string, error) {
cmd := exec.Command(c.binaryName, args...)
output, err := cmd.Output()
if err != nil {
if exitErr, ok := err.(*exec.ExitError); ok {
return "", fmt.Errorf("agent execution failed with exit code %d: %s\nStderr: %s",
exitErr.ExitCode(), err.Error(), exitErr.Stderr)
}
return "", fmt.Errorf("failed to run agent: %w", err)
}
return string(output), nil
}
func (c *AgenticCore) run(args ...string) error {
cmd := exec.Command(c.binaryName, args...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
// setup additional prequisite environment and plugins manifest to user's profile
// e.g. ../manifest/agent
func (c *AgenticCore) Setup() {
// Check if this is the first time, otherwise directly return (TODO: this is a simple check)
homeDir, _ := os.UserHomeDir()
targetCtlDir := filepath.Join(homeDir, ".hgctl")
if _, err := os.Stat(targetCtlDir); err == nil {
return
}
targetCoreDir := filepath.Join(homeDir, c.GetCoreDirName())
// setup subagent plugins file
embedFS := manifests.BuiltinOrDir("")
if err := manifests.ExtractEmbedFiles(embedFS, "agent", targetCtlDir); err != nil {
fmt.Println(err)
fmt.Println("failed to init plugins for agent core")
os.Exit(1)
}
// Setup predefined files like: command.md
if err := manifests.ExtractEmbedFiles(embedFS, "agent", targetCoreDir); err != nil {
fmt.Println(err)
fmt.Println("failed to init commands for agent core")
os.Exit(1)
}
// Add Predefined MCP Server
if err := c.addPredefinedMCP(); err != nil {
fmt.Printf("Warning: failed to add needed mcp server: %s\n", err)
}
if err := c.addHigressAPIMCP(); err != nil {
fmt.Println("failed to init higress-api mcp server (you may need to add it manually):", err)
fmt.Println("Details information on Higress-api MCP server refers to https://github.com/alibaba/higress/blob/main/plugins/golang-filter/mcp-server/servers/higress/higress-api/README_en.md")
return
}
// fmt.Println("Higress-api MCP server added successfully")
}
func (c *AgenticCore) addPredefinedMCP() error {
// deepwikiArg := MCPAddArg{
// name: "deepwiki",
// url: "https://mcp.deepwiki.com/mcp",
// typ: "",
// transport: STREAMABLE,
// scope: "user",
// }
// if err := c.AddMCPServer(deepwikiArg); err != nil {
// return fmt.Errorf("deepwiki")
// }
return nil
}
func (c *AgenticCore) addHigressAPIMCP() error {
arg := &HigressConsoleAuthArg{
hgURL: viper.GetString(HIGRESS_GATEWAY_URL),
hgUser: viper.GetString(HIGRESS_CONSOLE_USER),
hgPassword: viper.GetString(HIGRESS_CONSOLE_PASSWORD),
}
fmt.Println("Initializing...Add prequisite MCP server (Higress-api MCP server) automatically")
if arg.hgURL == "" {
gatewayPrompt := promptui.Prompt{
Label: "Enter higress gateway URL",
Default: "http://127.0.0.1:80",
}
gateway, err := gatewayPrompt.Run()
if err != nil {
fmt.Println("failed to run gateway prompt: ", err)
return err
}
arg.hgURL = gateway
}
if arg.hgURL == "" || arg.hgPassword == "" {
if err := tryToGetLocalCredential(arg); err != nil || arg.hgUser == "" || arg.hgPassword == "" {
// fallback: interact with user to provide password & username
color.Red("failed to get higress-console credential automatically (Requires higress installed by hgctl). Let's do it manually")
userPrompt := promptui.Prompt{
Label: "Enter higress console username",
Default: "admin",
}
username, err := userPrompt.Run()
if err != nil {
return fmt.Errorf("aborted: %v", err)
}
pwdPrompt := promptui.Prompt{
Label: "Enter higress console password",
Default: "admin",
}
pwd, err := pwdPrompt.Run()
if err != nil {
return fmt.Errorf("aborted: %v", err)
}
arg.hgUser = username
arg.hgPassword = pwd
}
}
if arg.hgUser == "" || arg.hgPassword == "" {
return fmt.Errorf("Empty higress console username and password, aborting")
}
rawByte := fmt.Appendf(nil, "%s:%s", arg.hgUser, arg.hgPassword)
resStr := base64.StdEncoding.EncodeToString(rawByte)
authHeader := fmt.Sprintf("Authorization: Basic %s", resStr)
return c.AddMCPServer(MCPAddArg{
name: "higress-api",
url: fmt.Sprintf("%s/higress-api", arg.hgURL),
transport: HTTP,
typ: HTTP,
scope: "user",
header: []string{
authHeader,
},
})
}
// ------- Initialization -------
func (c *AgenticCore) Start() error {
return c.run()
}
// ------- MCP -------
func (c *AgenticCore) AddMCPServer(arg MCPAddArg) error {
// adapt the field
if arg.transport == STREAMABLE {
arg.transport = HTTP
}
args := []string{
"mcp", "add", "--transport", arg.transport, arg.name, arg.url,
}
if arg.scope != "" {
scopeArg := []string{"--scope", arg.scope}
args = append(args, scopeArg...)
}
if len(arg.env) != 0 {
for _, e := range arg.env {
envArg := []string{"-e", e}
args = append(args, envArg...)
}
}
if len(arg.header) != 0 {
for _, h := range arg.header {
headerArg := []string{"-H", h}
args = append(args, headerArg...)
}
}
err := c.run(args...)
// Allow to add duplicate mcp server name (core will return error)
if err == nil || strings.Contains(err.Error(), "already exists") {
return nil
}
return err
}