mirror of
https://github.com/alibaba/higress.git
synced 2026-02-25 21:21:01 +08:00
268 lines
7.1 KiB
Go
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
|
|
}
|