// 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 ( "bytes" "context" "fmt" "os" "path/filepath" "strconv" "strings" "github.com/AlecAivazis/survey/v2" "github.com/alibaba/higress/hgctl/pkg/agent/services" "github.com/alibaba/higress/hgctl/pkg/helm" "github.com/alibaba/higress/hgctl/pkg/installer" "github.com/alibaba/higress/hgctl/pkg/kubernetes" "github.com/alibaba/higress/hgctl/pkg/util" "github.com/alibaba/higress/v2/pkg/cmd/options" "github.com/braydonk/yaml" "github.com/fatih/color" "github.com/higress-group/openapi-to-mcpserver/pkg/converter" "github.com/higress-group/openapi-to-mcpserver/pkg/models" "github.com/higress-group/openapi-to-mcpserver/pkg/parser" "github.com/manifoldco/promptui" "github.com/spf13/cobra" "github.com/spf13/viper" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" k8s "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/clientcmd" ) const ( SecretConsoleUser = "adminUsername" SecretConsolePwd = "adminPassword" ) var ( purple = color.New(color.FgMagenta, color.Bold) cyan = color.New(color.FgCyan) yellow = color.New(color.FgYellow) green = color.New(color.FgGreen) ) // ------ cmd related ------ func addHigressConsoleAuthFlag(cmd *cobra.Command, arg *HigressConsoleAuthArg) { cmd.PersistentFlags().StringVar(&arg.hgURL, HIGRESS_CONSOLE_URL, "", "The BaseURL of higress console") cmd.PersistentFlags().StringVar(&arg.hgUser, HIGRESS_CONSOLE_USER, "", "The username of higress console") cmd.PersistentFlags().StringVar(&arg.hgPassword, HIGRESS_CONSOLE_PASSWORD, "", "The password of higress console") viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_")) viper.AutomaticEnv() } func addHimarketAdminAuthFlag(cmd *cobra.Command, arg *HimarketAdminAuthArg) { cmd.PersistentFlags().StringVar(&arg.hmURL, HIMARKET_ADMIN_URL, "", "The BaseURL of himarket") cmd.PersistentFlags().StringVar(&arg.hmUser, HIMARKET_ADMIN_USER, "", "The username of himarket") cmd.PersistentFlags().StringVar(&arg.hmPassword, HIMARKET_ADMIN_PASSWORD, "", "The password of himarket") viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_")) viper.AutomaticEnv() } // ------ MCP convert utils function ------ func parseOpenapi2MCP(arg MCPAddArg) *models.MCPConfig { path := arg.spec serverName := arg.name // Create a new parser p := parser.NewParser() p.SetValidation(true) // Parse the OpenAPI specification err := p.ParseFile(path) if err != nil { fmt.Printf("Error parsing OpenAPI specification: %v\n", err) os.Exit(1) } c := converter.NewConverter(p, models.ConvertOptions{ ServerName: serverName, ToolNamePrefix: "", TemplatePath: "", }) // Convert the OpenAPI specification to an MCP configuration config, err := c.Convert() if err != nil { fmt.Printf("Error converting OpenAPI specification: %v\n", err) os.Exit(1) } return config } func convertMCPConfigToStr(cfg *models.MCPConfig) string { var data []byte var buffer bytes.Buffer encoder := yaml.NewEncoder(&buffer) encoder.SetIndent(2) if err := encoder.Encode(cfg); err != nil { fmt.Printf("Error encoding YAML: %v\n", err) os.Exit(1) } data = buffer.Bytes() str := string(data) // fmt.Println("Successfully converted OpenAPI specification to MCP Server") // fmt.Printf("Get MCP server config string: %v", str) return str // if err != nil { // fmt.Printf("Error marshaling MCP configuration: %v\n", err) // os.Exit(1) // } // err = os.WriteFile(*outputFile, data, 0644) // if err != nil { // fmt.Printf("Error writing MCP configuration: %v\n", err) // os.Exit(1) // } } func GetHigressGatewayServiceIP() (string, error) { color.Cyan("๐Ÿš€ Adding openapi MCP Server from higress to agent, checking Higress Gateway Pod status...") defaultKubeconfig := filepath.Join(os.Getenv("HOME"), ".kube", "config") config, err := clientcmd.BuildConfigFromFlags("", defaultKubeconfig) if err != nil { color.Yellow("โš ๏ธ Failed to load default kubeconfig: %v", err) return promptForServiceKubeSettingsAndRetry() } clientset, err := k8s.NewForConfig(config) if err != nil { color.Yellow("โš ๏ธ Failed to create Kubernetes client: %v", err) return promptForServiceKubeSettingsAndRetry() } namespace := "higress-system" svc, err := clientset.CoreV1().Services(namespace).Get(context.Background(), "higress-gateway", metav1.GetOptions{}) if err != nil || svc == nil { color.Yellow("โš ๏ธ Could not find Higress Gateway Service in namespace '%s'.", namespace) return promptForServiceKubeSettingsAndRetry() } ip, err := extractServiceIP(clientset, namespace, svc) if err != nil { return "", err } color.Green("โœ… Found Higress Gateway Service IP: %s (namespace: %s)", ip, namespace) return ip, nil } // higress-gateway should always be LoadBalancer func extractServiceIP(clientset *k8s.Clientset, namespace string, svc *v1.Service) (string, error) { return svc.Spec.ClusterIP, nil // // fallback to Pod IP // if len(svc.Spec.Selector) > 0 { // selector := metav1.FormatLabelSelector(&metav1.LabelSelector{MatchLabels: svc.Spec.Selector}) // pods, err := clientset.CoreV1().Pods(namespace).List(context.Background(), metav1.ListOptions{ // LabelSelector: selector, // }) // if err != nil { // return "", fmt.Errorf("failed to list pods for selector: %v", err) // } // if len(pods.Items) > 0 { // return pods.Items[0].Status.PodIP, nil // } // } } // prompt fallback for user input func promptForServiceKubeSettingsAndRetry() (string, error) { color.Cyan("Let's fix it manually ๐Ÿ‘‡") kubeconfigPrompt := promptui.Prompt{ Label: "Enter kubeconfig path", Default: filepath.Join(os.Getenv("HOME"), ".kube", "config"), } kubeconfigPath, err := kubeconfigPrompt.Run() if err != nil { return "", fmt.Errorf("aborted: %v", err) } nsPrompt := promptui.Prompt{ Label: "Enter Higress namespace", Default: "higress-system", } namespace, err := nsPrompt.Run() if err != nil { return "", err } config, err := clientcmd.BuildConfigFromFlags("", kubeconfigPath) if err != nil { return "", fmt.Errorf("failed to load kubeconfig: %v", err) } clientset, err := k8s.NewForConfig(config) if err != nil { return "", fmt.Errorf("failed to create kubernetes client: %v", err) } svc, err := clientset.CoreV1().Services(namespace).Get(context.Background(), "higress-gateway", metav1.GetOptions{}) if err != nil || svc == nil { color.Red("โŒ Higress Gateway Service not found in namespace '%s'", namespace) return "", fmt.Errorf("service not found") } ip, err := extractServiceIP(clientset, namespace, svc) if err != nil { return "", err } color.Green("โœ… Found Higress Gateway Service IP: %s (namespace: %s)", ip, namespace) return ip, nil } func getConsoleCredentials(profile *helm.Profile) (username, password string, err error) { cliClient, err := kubernetes.NewCLIClient(options.DefaultConfigFlags.ToRawKubeConfigLoader()) if err != nil { return "", "", fmt.Errorf("failed to build kubernetes client: %w", err) } secret, err := cliClient.KubernetesInterface().CoreV1().Secrets(profile.Global.Namespace).Get(context.Background(), "higress-console", metav1.GetOptions{}) if err != nil { return "", "", err } return string(secret.Data[SecretConsoleUser]), string(secret.Data[SecretConsolePwd]), nil } // This function will do following things: // 1. read the profile from local-file // 2. read the profile from k8s' configMap // 3. combine the two type profiles together and return func getAllProfiles() ([]*installer.ProfileContext, error) { profileContexts := make([]*installer.ProfileContext, 0) profileInstalledPath, err := installer.GetProfileInstalledPath() if err != nil { return profileContexts, nil } fileProfileStore, err := installer.NewFileDirProfileStore(profileInstalledPath) if err != nil { return profileContexts, nil } fileProfileContexts, err := fileProfileStore.List() if err == nil { profileContexts = append(profileContexts, fileProfileContexts...) } cliClient, err := kubernetes.NewCLIClient(options.DefaultConfigFlags.ToRawKubeConfigLoader()) if err != nil { return profileContexts, nil } configmapProfileStore, err := installer.NewConfigmapProfileStore(cliClient) if err != nil { return profileContexts, nil } configmapProfileContexts, err := configmapProfileStore.List() if err == nil { profileContexts = append(profileContexts, configmapProfileContexts...) } return profileContexts, nil } func getAgentConfig(config *AgentConfig) error { options := []string{ "create step by step", fmt.Sprintf("import existing one from current agentcore (%s)", viper.GetString(HGCTL_AGENT_CORE)), } var response string prompt := &survey.Select{ Message: "How would you like to create a agent", Options: options, } if err := survey.AskOne(prompt, &response); err != nil { fmt.Println(err) return err } switch response { case options[0]: return createAgentStepByStep(config) case options[1]: return importAgentFromCore(config) } return fmt.Errorf("Unsupport way to create a agent") } func getAgentCoreSubAgents() (map[string]string, []string, error) { home, _ := os.UserHomeDir() core, err := getCore() if err != nil { return nil, nil, fmt.Errorf("failed to get core: %s", err) } coreAgentsDir := filepath.Join(home, core.GetCoreDirName(), "agents") files, err := os.ReadDir(coreAgentsDir) if err != nil { return nil, nil, fmt.Errorf("failed to read core agents directory (%s): %w", coreAgentsDir, err) } var agentNames []string agentContentMap := make(map[string]string) for _, file := range files { if file.IsDir() { continue } filename := file.Name() if !strings.HasSuffix(filename, ".md") { continue // Only process markdown files } agentName := strings.TrimSuffix(filename, ".md") filePath := filepath.Join(coreAgentsDir, filename) contentBytes, err := os.ReadFile(filePath) if err != nil { fmt.Printf("โš ๏ธ Warning: Failed to read content of agent file %s: %v\n", filename, err) continue } agentNames = append(agentNames, agentName) agentContentMap[agentName] = string(contentBytes) } return agentContentMap, agentNames, nil } func importAgentFromCore(config *AgentConfig) error { agentContentMap, agentNames, err := getAgentCoreSubAgents() if err != nil { return err } if len(agentNames) == 0 { return fmt.Errorf("no agent files (*.md) found in the core's subagent directory") } var selectedAgentName string prompt := &survey.Select{ Message: "Select an Agent to import:", Options: agentNames, } err = survey.AskOne(prompt, &selectedAgentName, survey.WithIcons(func(icons *survey.IconSet) { icons.SelectFocus.Text = "ยป" })) if err != nil { return fmt.Errorf("agent selection failed or was interrupted: %w", err) } promptContent, ok := agentContentMap[selectedAgentName] if !ok { return fmt.Errorf("internal error: could not find prompt for selected agent: %s", selectedAgentName) } // Set the selected agent name in the config config.AgentName = selectedAgentName config.SysPromptPath = filepath.Join(util.GetHomeHgctlDir(), "agents", selectedAgentName) if err := writeAgentPromptFile(config.SysPromptPath, selectedAgentName, promptContent); err != nil { fmt.Println("โŒ failed to write prompt to target file: ", config.SysPromptPath) return err } if err := queryAgentModel(config); err != nil { return fmt.Errorf("failed to get agent's model: %s", err) } if err := queryAgentMCP(config); err != nil { return fmt.Errorf("failed to get agent's mcp servers: %s", err) } if err := queryDeploySettings(config); err != nil { return fmt.Errorf("failed to get agent's mcp servers: %s", err) } fmt.Println(" How the agent responds to user input") promptStreaming := &survey.Confirm{ Message: "Enable streaming responses?", Default: true, } if err := survey.AskOne(promptStreaming, &config.EnableStreaming); err != nil { return err } return nil } func queryAgentSysPrompt(config *AgentConfig) error { purple.Println("๐Ÿ“ System Prompt") fmt.Println(" This defines the agent's personality and behavior") options := []string{ "input directly", "use existing markdown file", "use LLM to generate", } var response string prompt := &survey.Select{ Message: "How would you like to set the agent's SysPrompt", Options: options, } if err := survey.AskOne(prompt, &response); err != nil { fmt.Println(err) return err } var finalPromptStr string switch response { case options[0]: var prompt string sysPromptDefault := fmt.Sprintf("You're a helpful assistant named %s.", config.AgentName) promptSysPrompt := &survey.Input{ Message: "What is the system prompt for this agent?", Default: sysPromptDefault, } if err := survey.AskOne(promptSysPrompt, &prompt); err != nil { return err } finalPromptStr = prompt case options[1]: var target string promptSysPrompt := &survey.Input{ Message: "Enter the target prompt file path:", } if err := survey.AskOne(promptSysPrompt, &target); err != nil { return err } content, err := os.ReadFile(target) if err != nil { fmt.Printf("โŒ Failed to read the target file (%s): %v\n", target, err) return fmt.Errorf("failed to read source file: %w", err) } finalPromptStr = string(content) case options[2]: var desc string descPrompt := &survey.Input{ Message: "Describe what this agent should do (be comprehensive for best results)", Default: "Help me write unit tests for my code...", } if err := survey.AskOne(descPrompt, &desc); err != nil { return err } fmt.Println("generating...(this may take a few minutes, depends on your model)") prompt, err := generateAgentPromptByCore(desc) fmt.Printf("Generate Prompt for agent %s:\n", config.AgentName) fmt.Println(prompt) if err != nil { fmt.Printf("failed to generate prompt use agent core: %s\n", err) return err } finalPromptStr = prompt } config.SysPromptPath = filepath.Join(util.GetHomeHgctlDir(), "agents", config.AgentName) if err := writeAgentPromptFile(config.SysPromptPath, config.AgentName, finalPromptStr); err != nil { fmt.Println("failed to write prompt to target file: ", config.SysPromptPath) return err } return nil } func queryAgentTools(config *AgentConfig) error { fmt.Println() purple.Println("๐Ÿ”ง Available Tools") fmt.Println(" Select the tools this agent can use") for _, tool := range ASAvailiableTools { yellow.Printf(" โ€ข %s\n", tool) } fmt.Println() promptTools := &survey.MultiSelect{ Message: "Which tools to enable? (Space to select, Enter to confirm)", Options: ASAvailiableTools, } if err := survey.AskOne(promptTools, &config.AvailableTools); err != nil { return err } return nil } func queryAgentModel(config *AgentConfig) error { switch config.Type { case AgentRun: return queryAgentRunModel(config) case Local: return queryLocalModel(config) default: return fmt.Errorf("unsupported deploy type") } } func queryAgentRunModel(config *AgentConfig) error { config.ChatModel = viper.GetString(AGENTRUN_MODEL_NAME) fmt.Println() purple.Println("๐Ÿค– AI Model") fmt.Println(" Enter the model name that you've already created on your agentRun dashboard") message := "Which model to use?" if config.ChatModel != "" { message = fmt.Sprintf("Detected from configuration: %s. (Enter to continue)", config.ChatModel) } promptModelName := &survey.Input{ Message: message, Default: config.ChatModel, } if err := survey.AskOne(promptModelName, &config.ChatModel); err != nil { return err } return nil } func queryLocalModel(config *AgentConfig) error { type providerSpec struct { InternalName string DefaultModel string DefaultKey string } providerMap := map[string]providerSpec{ "DashScope": {InternalName: "DashScopeChat", DefaultModel: "qwen-plus", DefaultKey: "DASHSCOPE_API_KEY"}, "OpenAI": {InternalName: "OpenAIChat", DefaultModel: "gpt-4o", DefaultKey: "OPENAI_API_KEY"}, "Anthropic": {InternalName: "AnthropicChat", DefaultModel: "claude-3-5-sonnet-latest", DefaultKey: "ANTHROPIC_API_KEY"}, "Ollama": {InternalName: "OllamaChat", DefaultModel: "llama3", DefaultKey: "OLLAMA_API_KEY"}, "Gemini": {InternalName: "GeminiChat", DefaultModel: "gemini-1.5-pro", DefaultKey: "GEMINI_API_KEY"}, "Trinity": {InternalName: "TrinityChat", DefaultModel: "trinity-model", DefaultKey: "TRINITY_API_KEY"}, } options := []string{"DashScope", "OpenAI", "Anthropic", "Ollama", "Gemini", "Trinity"} defaultProvider := options[0] if envProvider := viper.GetString(AGENT_MODEL_PROVIDER); envProvider != "" { defaultProvider = envProvider } purple.Println("๐Ÿข AI Provider") var selectedDisplayName string promptProvider := &survey.Select{ Message: fmt.Sprintf("Choose the AI provider (%s):", defaultProvider), Options: options, Default: defaultProvider, } if err := survey.AskOne(promptProvider, &selectedDisplayName); err != nil { return err } spec := providerMap[selectedDisplayName] config.Provider = spec.InternalName purple.Println("๐Ÿค– AI Model") defaultModel := spec.DefaultModel if envModel := viper.GetString(AGENT_CHAT_MODEL); envModel != "" { defaultModel = envModel } promptModelName := &survey.Input{ Message: fmt.Sprintf("Which model to use? (%s)", defaultModel), Default: defaultModel, } if err := survey.AskOne(promptModelName, &config.ChatModel); err != nil { return err } purple.Println("๐Ÿ”‘ API Key Configuration") promptAPIKey := &survey.Input{ Message: "Environment variable name for API key:", Default: spec.DefaultKey, } if err := survey.AskOne(promptAPIKey, &config.APIKeyEnvVar); err != nil { return err } return nil } func queryAgentMCP(config *AgentConfig) error { purple.Println("๐Ÿ”— MCP Server Configuration") cyan.Println(" Configure multiple MCP servers if you want to use external tools") config.MCPServers = []MCPServerConfig{} // Show Himarket's exising mcp servers existServers, names, err := getHimarketMCPServer() if err == nil && len(existServers) != 0 { yellow.Println("๐Ÿ”— Get existing MCP Servers from Himarket: ") chosedNames := []string{} hgServerPrompt := survey.MultiSelect{ Message: fmt.Sprintf("Choose MCP Server from Current Himarket(%s)", viper.GetString(HIMARKET_DEVELOPER_URL)), Options: names, } if err := survey.AskOne(&hgServerPrompt, &chosedNames); err != nil { return err } for _, name := range chosedNames { config.MCPServers = append(config.MCPServers, MCPServerConfig{ Name: name, URL: existServers[name], Transport: "streamable_http", }) } } // Show Higress's existing mcp servers existServers, names, err = getHigressMCPServers() if err == nil && len(existServers) != 0 { yellow.Println("๐Ÿ”— Get existing MCP Servers from Higress: ") chosedNames := []string{} hgServerPrompt := survey.MultiSelect{ Message: fmt.Sprintf("Choose MCP Server from Current Higress(%s)", viper.GetString(HIGRESS_CONSOLE_URL)), Options: names, } if err := survey.AskOne(&hgServerPrompt, &chosedNames); err != nil { return err } for _, name := range chosedNames { config.MCPServers = append(config.MCPServers, MCPServerConfig{ Name: name, URL: existServers[name], Transport: "streamable_http", }) } } fmt.Println() purple.Println("Add MCP Servers mannually...") for { var mcpserver MCPServerConfig promptMCPServer := &survey.Input{ Message: "MCP Server URL (or press Enter to finish):", Default: "", } if err := survey.AskOne(promptMCPServer, &mcpserver.URL); err != nil || mcpserver.URL == "" { break } promptMCPTransport := &survey.Input{ Message: "transport:", Default: "streamable_http", } if err := survey.AskOne(promptMCPTransport, &mcpserver.Transport); err != nil || mcpserver.Transport == "" { break } mcpserver.URL = strings.TrimSpace(mcpserver.URL) mcpNameDefault := fmt.Sprintf("%s-mcp-%d", config.AgentName, len(config.MCPServers)+1) promptMCPName := &survey.Input{ Message: "MCP Client Name:", Default: mcpNameDefault, } if err := survey.AskOne(promptMCPName, &mcpserver.Name); err != nil { return err } yellow.Printf("๐Ÿ“‹ HTTP Headers for '%s' (optional)\n", mcpserver.Name) cyan.Println(" Add custom headers for MCP server requests") yellow.Println(" Press Enter to finish adding headers") mcpserver.Headers = make(map[string]string) for { var headerKey, headerValue string promptKey := &survey.Input{ Message: "Header name (or press Enter to finish):", Default: "", } if err := survey.AskOne(promptKey, &headerKey); err != nil || headerKey == "" { break } promptValue := &survey.Input{ Message: fmt.Sprintf("Value for '%s':", headerKey), Default: "", } if err := survey.AskOne(promptValue, &headerValue); err != nil { return err } if headerValue != "" { mcpserver.Headers[headerKey] = headerValue } } config.MCPServers = append(config.MCPServers, mcpserver) green.Printf("โœ… Added MCP server: %s\n", mcpserver.Name) fmt.Println() } return nil } func queryDeploySettings(config *AgentConfig) error { switch config.Type { case AgentRun: return queryAgentRunDeploySettings(config) case Local: return queryLocalDeploySettings(config) default: return fmt.Errorf("unsupported deploy type") } } func queryAgentRunDeploySettings(config *AgentConfig) error { purple.Println("โ˜๏ธ AgentRun Deployment Settings") fmt.Println(" Configure the settings for deploying to AgentRun/FC") promptResourceName := &survey.Input{ Message: "Resource Name:", Default: "my-agent-resource", Help: "A unique name for the deployed resource.", } if err := survey.AskOne(promptResourceName, &config.ServerlessCfg.ResourceName); err != nil { return err } promptRegion := &survey.Select{ Message: "Region:", Options: []string{"cn-hangzhou", "cn-shanghai", "cn-beijing", "ap-southeast-1"}, Default: viper.GetString(AGENTRUN_REGION), Help: "The region where the agent will be deployed.", } if err := survey.AskOne(promptRegion, &config.ServerlessCfg.Region); err != nil { return err } promptAgentDesc := &survey.Input{ Message: "Agent Description:", Default: "My Agent Runtime created by dev", Help: "A brief description of the agent.", } if err := survey.AskOne(promptAgentDesc, &config.ServerlessCfg.AgentDesc); err != nil { return err } promptPort := &survey.Input{ Message: "Service Port:", Default: "9000", Help: "The port the agent service listens on inside the container/runtime.", } var portStr string if err := survey.AskOne(promptPort, &portStr); err != nil { return err } if portNum, err := strconv.ParseUint(portStr, 10, 32); err == nil { config.ServerlessCfg.Port = uint(portNum) } promptDiskSize := &survey.Input{ Message: "Disk Size (MB) (Optional, default 500 MB):", Default: "512", Help: "Disk size allocated to the agent runtime (MB).", } var diskSizeStr string if err := survey.AskOne(promptDiskSize, &diskSizeStr); err != nil { return err } if diskSizeNum, err := strconv.ParseUint(diskSizeStr, 10, 32); err == nil { config.ServerlessCfg.DiskSize = uint(diskSizeNum) } promptTimeout := &survey.Input{ Message: "Timeout (seconds) (Optional, default 600s):", Default: "600", Help: "The maximum request processing time (seconds).", } var timeoutStr string if err := survey.AskOne(promptTimeout, &timeoutStr); err != nil { return err } if timeoutNum, err := strconv.ParseUint(timeoutStr, 10, 32); err == nil { config.ServerlessCfg.Timeout = uint(timeoutNum) } config.ServerlessCfg.AgentName = config.AgentName return nil } func queryLocalDeploySettings(config *AgentConfig) error { purple.Println("๐ŸŒ Deployment Settings") fmt.Println(" Network configuration for the agent") promptPort := &survey.Input{ Message: "Deployment port:", Default: "8090", } var portStr string if err := survey.AskOne(promptPort, &portStr); err != nil { return err } if portNum, err := strconv.Atoi(portStr); err == nil { config.DeploymentPort = portNum } else { config.DeploymentPort = 8090 // ้ป˜่ฎคๅ€ผ } promptHost := &survey.Input{ Message: "Host binding:", Default: "0.0.0.0", } if err := survey.AskOne(promptHost, &config.HostBinding); err != nil { return err } return nil } func createAgentStepByStep(config *AgentConfig) error { name := "" namePrompt := &survey.Input{ Message: "What is the agent's name?", Default: "", } if err := survey.AskOne(namePrompt, &name); err != nil { return err } config.AgentName = name config.AppName = name cyan.Printf("๐Ÿค– Let's configure your agent '%s'\n", name) fmt.Println() purple.Println("๐Ÿ“‹ App Description") fmt.Println(" A brief description of what this agent does") promptAppDescription := &survey.Input{ Message: "What is the app description?", Default: "A helpful assistant and useful agent", } if err := survey.AskOne(promptAppDescription, &config.AppDescription); err != nil { return err } if err := queryAgentSysPrompt(config); err != nil { return fmt.Errorf("failed to get agent's sysPrompt: %s", err) } if err := queryAgentModel(config); err != nil { return fmt.Errorf("failed to get agent's model: %s", err) } if err := queryAgentTools(config); err != nil { return fmt.Errorf("failed to get agent's tools: %s", err) } if err := queryAgentMCP(config); err != nil { return fmt.Errorf("failed to get agent's mcp servers: %s", err) } if err := queryDeploySettings(config); err != nil { return fmt.Errorf("failed to get agent's mcp servers: %s", err) } fmt.Println(" How the agent responds to user input") promptStreaming := &survey.Confirm{ Message: "Enable streaming responses?", Default: true, } if err := survey.AskOne(promptStreaming, &config.EnableStreaming); err != nil { return err } showConfigSummary(config) return nil } // Write given prompt to ~/.hgctl/agents// func writeAgentPromptFile(dir, name, prompt string) error { if err := os.MkdirAll(dir, 0755); err != nil { return fmt.Errorf("failed to create agent directory %s: %w", dir, err) } filePath := filepath.Join(dir, "prompt.md") if err := os.WriteFile(filePath, []byte(prompt), 0644); err != nil { return fmt.Errorf("failed to write prompt file %s: %w", filePath, err) } return nil } func getHimarketMCPServer() (map[string]string, []string, error) { conURL := viper.GetString(HIMARKET_DEVELOPER_URL) conUser := viper.GetString(HIMARKET_DEVELOPER_USER) conPwd := viper.GetString(HIMARKET_DEVELOPER_PASSWORD) if conURL == "" || conUser == "" || conPwd == "" { return nil, nil, fmt.Errorf("empty env, can not get Himarket's MCP Servers") } client := services.NewHimarketClient( conURL, conUser, conPwd, ) resultMap, err := client.GetDevMCPServerProduct() if err != nil { return nil, nil, err } keys := make([]string, 0, len(resultMap)) for k := range resultMap { keys = append(keys, k) } return resultMap, keys, nil } func getHigressMCPServers() (map[string]string, []string, error) { conURL := viper.GetString(HIGRESS_CONSOLE_URL) conUser := viper.GetString(HIGRESS_CONSOLE_USER) conPwd := viper.GetString(HIGRESS_CONSOLE_PASSWORD) gwURL := viper.GetString(HIGRESS_GATEWAY_URL) if conURL == "" || conUser == "" || conPwd == "" || gwURL == "" { return nil, nil, fmt.Errorf("empty env, can not get Higress's MCP Servers") } client := services.NewHigressClient( conURL, conUser, conPwd, ) resultMap, err := services.GetExistingMCPServers(client) if err != nil { return nil, nil, err } for k := range resultMap { resultMap[k] = fmt.Sprintf("%s/mcp-servers/%s", gwURL, k) } keys := make([]string, 0, len(resultMap)) for k := range resultMap { keys = append(keys, k) } return resultMap, keys, nil } // Print agent config summary to user func showConfigSummary(config *AgentConfig) { summaryColor := color.New(color.FgBlue, color.Bold) summaryColor.Println("๐Ÿ“Š Agent Configuration Summary:") fmt.Printf(" ๐Ÿ“ Name: %s\n", config.AgentName) fmt.Printf(" ๐Ÿข Provider: %s\n", config.Provider) fmt.Printf(" ๐Ÿค– Model: %s\n", config.ChatModel) fmt.Printf(" ๐Ÿ”ง Tools: %d selected\n", len(config.AvailableTools)) fmt.Printf(" ๐ŸŒ Port: %d\n", config.DeploymentPort) fmt.Printf(" ๐Ÿ“ Host: %s\n", config.HostBinding) fmt.Printf(" โœจ Streaming: %t\n", config.EnableStreaming) if len(config.MCPServers) > 0 { fmt.Printf(" ๐Ÿ”— MCP Servers: %d\n", len(config.MCPServers)) for i, mcp := range config.MCPServers { fmt.Printf(" %d. %s - %s\n", i+1, mcp.Name, mcp.URL) if len(mcp.Headers) > 0 { fmt.Printf(" Headers: %d\n", len(mcp.Headers)) } } } fmt.Println() }