Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions cli/azd/extensions/azure.ai.agents/internal/cmd/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
package cmd

import (
"cmp"
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
"slices"
"strings"

"azureaiagent/internal/exterrors"
Expand Down Expand Up @@ -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 {
Expand Down
85 changes: 57 additions & 28 deletions cli/azd/extensions/azure.ai.agents/internal/cmd/monitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -39,41 +36,79 @@ 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 <session-id>
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 <session-id> --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 <session-id>

# 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 <session-id> --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
}

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(
Expand All @@ -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
}
Expand Down
20 changes: 10 additions & 10 deletions cli/azd/extensions/azure.ai.agents/internal/cmd/monitor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
64 changes: 44 additions & 20 deletions cli/azd/extensions/azure.ai.agents/internal/cmd/show.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
}
Expand All @@ -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
}

Expand Down
21 changes: 10 additions & 11 deletions cli/azd/extensions/azure.ai.agents/internal/cmd/show_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Loading