diff --git a/cli/azd/extensions/azure.ai.agents/internal/cmd/helpers.go b/cli/azd/extensions/azure.ai.agents/internal/cmd/helpers.go index adc01c60dcf..6d034fac74d 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/cmd/helpers.go +++ b/cli/azd/extensions/azure.ai.agents/internal/cmd/helpers.go @@ -4,11 +4,13 @@ package cmd import ( + "cmp" "context" "encoding/json" "fmt" "os" "path/filepath" + "slices" "strings" "azureaiagent/internal/exterrors" @@ -212,6 +214,10 @@ func promptForAgentService( services []*azdext.ServiceConfig, noPrompt bool, ) (*azdext.ServiceConfig, error) { + slices.SortFunc(services, func(a, b *azdext.ServiceConfig) int { + return cmp.Compare(a.Name, b.Name) + }) + if noPrompt { names := make([]string, len(services)) for i, s := range services { diff --git a/cli/azd/extensions/azure.ai.agents/internal/cmd/monitor.go b/cli/azd/extensions/azure.ai.agents/internal/cmd/monitor.go index 20de466b5d7..ad2ede2a555 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/cmd/monitor.go +++ b/cli/azd/extensions/azure.ai.agents/internal/cmd/monitor.go @@ -19,14 +19,11 @@ import ( ) type monitorFlags struct { - accountName string - projectName string - name string - version string - sessionID string - follow bool - tail int - logType string + name string + sessionID string + follow bool + tail int + logType string } // MonitorAction handles the execution of the monitor command. @@ -39,26 +36,38 @@ func newMonitorCommand() *cobra.Command { flags := &monitorFlags{} cmd := &cobra.Command{ - Use: "monitor", + Use: "monitor [name]", Short: "Monitor logs from a hosted agent.", Long: `Monitor logs from a hosted agent. Streams console output (stdout/stderr) or system events from an agent session or container. Use --session to stream logs for a specific session, or omit it to use the container logstream. Use --follow to stream logs in real-time, or omit it to fetch recent logs and exit. -This is useful for troubleshooting agent startup issues or monitoring agent behavior.`, - Example: ` # Stream session logs - azd ai agent monitor --name my-agent --version 1 --session +This is useful for troubleshooting agent startup issues or monitoring agent behavior. - # Stream session logs in real-time - azd ai agent monitor --name my-agent --version 1 --session --follow +The agent name and version are resolved automatically from the azure.yaml service +configuration and the current azd environment. Optionally specify the service name +(from azure.yaml) as a positional argument when multiple agent services exist.`, + Example: ` # Monitor logs (auto-resolves from azure.yaml) + azd ai agent monitor + + # Monitor logs for a specific agent service + azd ai agent monitor my-agent - # Fetch container console logs (legacy) - azd ai agent monitor --name my-agent --version 1 + # Stream session logs + azd ai agent monitor --session - # Fetch system event logs from container (legacy) - azd ai agent monitor --name my-agent --version 1 --type system`, + # Stream session logs in real-time + azd ai agent monitor --session --follow + + # Fetch system event logs from container + azd ai agent monitor --type system`, + Args: cobra.MaximumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { + if len(args) > 0 { + flags.name = args[0] + } + if err := validateMonitorFlags(flags); err != nil { return err } @@ -66,14 +75,40 @@ This is useful for troubleshooting agent startup issues or monitoring agent beha ctx := azdext.WithAccessToken(cmd.Context()) setupDebugLogging(cmd.Flags()) - agentContext, err := newAgentContext(ctx, flags.accountName, flags.projectName, flags.name, flags.version) + azdClient, err := azdext.NewAzdClient() + if err != nil { + return fmt.Errorf("failed to create azd client: %w", err) + } + defer azdClient.Close() + + info, err := resolveAgentServiceFromProject(ctx, azdClient, flags.name, rootFlags.NoPrompt) + if err != nil { + return err + } + + if info.AgentName == "" { + return fmt.Errorf( + "agent name could not be resolved from azd environment for service '%s'\n\n"+ + "Run 'azd deploy' first to deploy the agent, or check your azd environment values", + info.ServiceName, + ) + } + if info.Version == "" { + return fmt.Errorf( + "agent version could not be resolved from azd environment for service '%s'\n\n"+ + "Run 'azd deploy' first to deploy the agent, or check your azd environment values", + info.ServiceName, + ) + } + + agentContext, err := newAgentContext(ctx, "", "", info.AgentName, info.Version) if err != nil { return err } // When vnext is enabled, resolve session ID for session-based logstream. if flags.sessionID == "" { - sessionID, vnext := resolveMonitorSession(ctx, flags.name) + sessionID, vnext := resolveMonitorSession(ctx, info.AgentName) if vnext { if sessionID == "" { return exterrors.Validation( @@ -95,17 +130,11 @@ This is useful for troubleshooting agent startup issues or monitoring agent beha }, } - cmd.Flags().StringVarP(&flags.accountName, "account-name", "a", "", "Cognitive Services account name") - cmd.Flags().StringVarP(&flags.projectName, "project-name", "p", "", "AI Foundry project name") - cmd.Flags().StringVarP(&flags.name, "name", "n", "", "Name of the hosted agent (required)") - cmd.Flags().StringVarP(&flags.version, "version", "v", "", "Version of the hosted agent (required)") cmd.Flags().StringVarP(&flags.sessionID, "session", "s", "", "Session ID to stream logs for") cmd.Flags().BoolVarP(&flags.follow, "follow", "f", false, "Stream logs in real-time") cmd.Flags().IntVarP(&flags.tail, "tail", "l", 50, "Number of trailing log lines to fetch (1-300)") - cmd.Flags().StringVarP(&flags.logType, "type", "t", "console", "Type of logs: 'console' (stdout/stderr) or 'system' (container events)") - - _ = cmd.MarkFlagRequired("name") - _ = cmd.MarkFlagRequired("version") + cmd.Flags().StringVarP(&flags.logType, "type", "t", "console", + "Type of logs: 'console' (stdout/stderr) or 'system' (container events)") return cmd } diff --git a/cli/azd/extensions/azure.ai.agents/internal/cmd/monitor_test.go b/cli/azd/extensions/azure.ai.agents/internal/cmd/monitor_test.go index c3f5b464c1f..93e7d1d4418 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/cmd/monitor_test.go +++ b/cli/azd/extensions/azure.ai.agents/internal/cmd/monitor_test.go @@ -13,22 +13,22 @@ import ( "github.com/stretchr/testify/require" ) -func TestMonitorCommand_RequiredFlags(t *testing.T) { +func TestMonitorCommand_AcceptsPositionalArg(t *testing.T) { cmd := newMonitorCommand() - - cmd.SetArgs([]string{}) - err := cmd.Execute() - assert.Error(t, err) - assert.Contains(t, err.Error(), "name") + err := cmd.Args(cmd, []string{"my-agent"}) + assert.NoError(t, err) } -func TestMonitorCommand_MissingVersionFlag(t *testing.T) { +func TestMonitorCommand_AcceptsNoArgs(t *testing.T) { cmd := newMonitorCommand() + err := cmd.Args(cmd, []string{}) + assert.NoError(t, err) +} - cmd.SetArgs([]string{"--name", "test-agent"}) - err := cmd.Execute() +func TestMonitorCommand_RejectsMultipleArgs(t *testing.T) { + cmd := newMonitorCommand() + err := cmd.Args(cmd, []string{"svc1", "svc2"}) assert.Error(t, err) - assert.Contains(t, err.Error(), "version") } func TestValidateMonitorFlags_Valid(t *testing.T) { diff --git a/cli/azd/extensions/azure.ai.agents/internal/cmd/show.go b/cli/azd/extensions/azure.ai.agents/internal/cmd/show.go index 3a4c4e649f5..195967c3b1e 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/cmd/show.go +++ b/cli/azd/extensions/azure.ai.agents/internal/cmd/show.go @@ -17,11 +17,8 @@ import ( ) type showFlags struct { - accountName string - projectName string - name string - version string - output string + name string + output string } // ShowAction handles the execution of the show command. @@ -34,25 +31,59 @@ func newShowCommand() *cobra.Command { flags := &showFlags{} cmd := &cobra.Command{ - Use: "show", + Use: "show [name]", Short: "Show the status of a hosted agent deployment.", Long: `Show the status of a hosted agent deployment. Retrieves the runtime status of a hosted agent container, including its current state, -replica configuration, and any error messages.`, - Example: ` # Show status using azd environment configuration - azd ai agent show --name my-agent --version 1 +replica configuration, and any error messages. - # Show status with explicit account and project - azd ai agent show --name my-agent --version 1 --account-name myAccount --project-name myProject +The agent name and version are resolved automatically from the azure.yaml service +configuration and the current azd environment. Optionally specify the service name +(from azure.yaml) as a positional argument when multiple agent services exist.`, + Example: ` # Show status (auto-resolves from azure.yaml) + azd ai agent show + + # Show status for a specific agent service + azd ai agent show my-agent # Show status in table format - azd ai agent show --name my-agent --version 1 --output table`, + azd ai agent show --output table`, + Args: cobra.MaximumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { + if len(args) > 0 { + flags.name = args[0] + } ctx := azdext.WithAccessToken(cmd.Context()) setupDebugLogging(cmd.Flags()) - agentContext, err := newAgentContext(ctx, flags.accountName, flags.projectName, flags.name, flags.version) + azdClient, err := azdext.NewAzdClient() + if err != nil { + return fmt.Errorf("failed to create azd client: %w", err) + } + defer azdClient.Close() + + info, err := resolveAgentServiceFromProject(ctx, azdClient, flags.name, rootFlags.NoPrompt) + if err != nil { + return err + } + + if info.AgentName == "" { + return fmt.Errorf( + "agent name could not be resolved from azd environment for service '%s'\n\n"+ + "Run 'azd deploy' first to deploy the agent, or check your azd environment values", + info.ServiceName, + ) + } + if info.Version == "" { + return fmt.Errorf( + "agent version could not be resolved from azd environment for service '%s'\n\n"+ + "Run 'azd deploy' first to deploy the agent, or check your azd environment values", + info.ServiceName, + ) + } + + agentContext, err := newAgentContext(ctx, "", "", info.AgentName, info.Version) if err != nil { return err } @@ -66,15 +97,8 @@ replica configuration, and any error messages.`, }, } - cmd.Flags().StringVarP(&flags.accountName, "account-name", "a", "", "Cognitive Services account name") - cmd.Flags().StringVarP(&flags.projectName, "project-name", "p", "", "AI Foundry project name") - cmd.Flags().StringVarP(&flags.name, "name", "n", "", "Name of the hosted agent (required)") - cmd.Flags().StringVarP(&flags.version, "version", "v", "", "Version of the hosted agent (required)") cmd.Flags().StringVarP(&flags.output, "output", "o", "json", "Output format (json or table)") - _ = cmd.MarkFlagRequired("name") - _ = cmd.MarkFlagRequired("version") - return cmd } diff --git a/cli/azd/extensions/azure.ai.agents/internal/cmd/show_test.go b/cli/azd/extensions/azure.ai.agents/internal/cmd/show_test.go index ba759e9f46d..0e5af4cd7f9 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/cmd/show_test.go +++ b/cli/azd/extensions/azure.ai.agents/internal/cmd/show_test.go @@ -13,23 +13,22 @@ import ( "github.com/stretchr/testify/require" ) -func TestShowCommand_RequiredFlags(t *testing.T) { +func TestShowCommand_AcceptsPositionalArg(t *testing.T) { cmd := newShowCommand() - - // Execute with no flags should fail (missing required flags) - cmd.SetArgs([]string{}) - err := cmd.Execute() - assert.Error(t, err) - assert.Contains(t, err.Error(), "name") + err := cmd.Args(cmd, []string{"my-agent"}) + assert.NoError(t, err) } -func TestShowCommand_MissingVersionFlag(t *testing.T) { +func TestShowCommand_AcceptsNoArgs(t *testing.T) { cmd := newShowCommand() + err := cmd.Args(cmd, []string{}) + assert.NoError(t, err) +} - cmd.SetArgs([]string{"--name", "test-agent"}) - err := cmd.Execute() +func TestShowCommand_RejectsMultipleArgs(t *testing.T) { + cmd := newShowCommand() + err := cmd.Args(cmd, []string{"svc1", "svc2"}) assert.Error(t, err) - assert.Contains(t, err.Error(), "version") } func TestPrintStatusJSON(t *testing.T) {