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
24 changes: 6 additions & 18 deletions cli/azd/extensions/azure.ai.agents/internal/cmd/files.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,16 @@ func newFilesCommand() *cobra.Command {
Upload, download, list, and remove files in the session-scoped filesystem
of a hosted agent. This is useful for debugging, seeding data, and agent setup.

Agent details (name, version, endpoint) are automatically resolved from the
Agent details (name, endpoint) are automatically resolved from the
azd environment. Use --agent-name to select a specific agent when the project
has multiple azure.ai.agent services. The session ID is automatically resolved
from the last invoke session, or can be overridden with --session.`,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
// Chain with parent's PersistentPreRunE (root sets NoPrompt)
if parent := cmd.Parent(); parent != nil && parent.PersistentPreRunE != nil {
if err := parent.PersistentPreRunE(cmd, args); err != nil {
// Chain with root's PersistentPreRunE (root sets NoPrompt).
// Note: cmd.Parent() would return the "files" command itself when
// a subcommand runs, causing infinite recursion.
if root := cmd.Root(); root != nil && root.PersistentPreRunE != nil {
if err := root.PersistentPreRunE(cmd, args); err != nil {
return err
}
}
Expand Down Expand Up @@ -138,13 +140,6 @@ func resolveFilesContext(ctx context.Context, flags *filesFlags) (*filesContext,
info.ServiceName,
)
}
if info.Version == "" {
return nil, fmt.Errorf(
"agent version not found in azd environment for service %q\n\n"+
"Run 'azd deploy' to deploy the agent, or check that the service is configured in azure.yaml",
info.ServiceName,
)
}

endpoint, err := resolveAgentEndpoint(ctx, "", "")
if err != nil {
Expand All @@ -160,7 +155,6 @@ func resolveFilesContext(ctx context.Context, flags *filesFlags) (*filesContext,
AgentContext: &AgentContext{
ProjectEndpoint: endpoint,
Name: info.AgentName,
Version: info.Version,
},
sessionID: sessionID,
}, nil
Expand Down Expand Up @@ -251,7 +245,6 @@ func (a *FilesUploadAction) Run(ctx context.Context) error {
err = agentClient.UploadSessionFile(
ctx,
a.Name,
a.Version,
a.sessionID,
remotePath,
DefaultVNextAgentAPIVersion,
Expand Down Expand Up @@ -338,7 +331,6 @@ func (a *FilesDownloadAction) Run(ctx context.Context) error {
body, err := agentClient.DownloadSessionFile(
ctx,
a.Name,
a.Version,
a.sessionID,
a.flags.file,
DefaultVNextAgentAPIVersion,
Expand Down Expand Up @@ -448,7 +440,6 @@ func (a *FilesListAction) Run(ctx context.Context) error {
fileList, err := agentClient.ListSessionFiles(
ctx,
a.Name,
a.Version,
a.sessionID,
a.remotePath,
DefaultVNextAgentAPIVersion,
Expand Down Expand Up @@ -568,7 +559,6 @@ func (a *FilesRemoveAction) Run(ctx context.Context) error {
err = agentClient.RemoveSessionFile(
ctx,
a.Name,
a.Version,
a.sessionID,
a.remotePath,
a.flags.recursive,
Expand Down Expand Up @@ -645,7 +635,6 @@ func (a *FilesMkdirAction) Run(ctx context.Context) error {
err = agentClient.MkdirSessionFile(
ctx,
a.Name,
a.Version,
a.sessionID,
a.remotePath,
DefaultVNextAgentAPIVersion,
Expand Down Expand Up @@ -729,7 +718,6 @@ func (a *FilesStatAction) Run(ctx context.Context) error {
fileInfo, err := agentClient.StatSessionFile(
ctx,
a.Name,
a.Version,
a.sessionID,
a.remotePath,
DefaultVNextAgentAPIVersion,
Expand Down
19 changes: 19 additions & 0 deletions cli/azd/extensions/azure.ai.agents/internal/cmd/files_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,29 @@ import (

"azureaiagent/internal/pkg/agents/agent_api"

"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestFilesCommand_PersistentPreRunE_NoRecursion(t *testing.T) {
// Regression: PersistentPreRunE previously used cmd.Parent() which returned
// the "files" command itself when a subcommand ran, causing infinite recursion
// (stack overflow). The fix uses cmd.Root() to chain to the root command.
root := NewRootCommand()

// Override the list subcommand's RunE so it doesn't need a real environment.
filesCmd, _, _ := root.Find([]string{"files"})
require.NotNil(t, filesCmd)
for _, sub := range filesCmd.Commands() {
sub.RunE = func(cmd *cobra.Command, args []string) error { return nil }
}

root.SetArgs([]string{"files", "list"})
// If the bug is present this will stack-overflow instead of returning.
_ = root.Execute()
}

func TestFilesCommand_HasSubcommands(t *testing.T) {
cmd := newFilesCommand()

Expand Down
14 changes: 6 additions & 8 deletions cli/azd/extensions/azure.ai.agents/internal/cmd/monitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,13 +93,6 @@ configuration and the current azd environment. Optionally specify the service na
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 {
Expand Down Expand Up @@ -152,14 +145,19 @@ func (a *MonitorAction) Run(ctx context.Context) error {
body, err = agentClient.GetAgentSessionLogStream(
ctx,
a.Name,
a.Version,
a.flags.sessionID,
DefaultVNextAgentAPIVersion,
a.flags.logType,
a.flags.tail,
a.flags.follow,
)
} else {
if a.Version == "" {
return fmt.Errorf(
"agent version is required for container log streaming\n\n" +
"Run 'azd deploy' first to deploy the agent, or check your azd environment values",
)
}
body, err = agentClient.GetAgentContainerLogStream(
ctx,
a.Name,
Expand Down
59 changes: 53 additions & 6 deletions cli/azd/extensions/azure.ai.agents/internal/cmd/show.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"fmt"
"os"
"text/tabwriter"
"time"

"azureaiagent/internal/pkg/agents/agent_api"

Expand All @@ -24,19 +25,17 @@ type showFlags struct {
// ShowAction handles the execution of the show command.
type ShowAction struct {
*AgentContext
flags *showFlags
flags *showFlags
azdClient *azdext.AzdClient
}

func newShowCommand() *cobra.Command {
flags := &showFlags{}

cmd := &cobra.Command{
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.
Short: "Show the status of a hosted agent.",
Long: `Show the status of a hosted agent.

The agent name and version are resolved automatically from the azure.yaml service
configuration and the current azd environment. Optionally specify the service name
Expand Down Expand Up @@ -91,6 +90,7 @@ configuration and the current azd environment. Optionally specify the service na
action := &ShowAction{
AgentContext: agentContext,
flags: flags,
azdClient: azdClient,
}

return action.Run(ctx)
Expand All @@ -109,6 +109,22 @@ func (a *ShowAction) Run(ctx context.Context) error {
return err
}

if isVNextEnabled(ctx, a.azdClient) {
version, err := agentClient.GetAgentVersion(
ctx, a.Name, a.Version, DefaultAgentAPIVersion,
)
if err != nil {
return fmt.Errorf("failed to get agent version: %w", err)
}

switch a.flags.output {
case "table":
return printAgentVersionTable(version)
default:
return printAgentVersionJSON(version)
}
}

container, err := agentClient.GetAgentContainer(ctx, a.Name, a.Version, DefaultAgentAPIVersion)
if err != nil {
return fmt.Errorf("failed to get agent container status: %w", err)
Expand Down Expand Up @@ -167,3 +183,34 @@ func printStatusTable(container *agent_api.AgentContainerObject) error {

return w.Flush()
}

func printAgentVersionJSON(version *agent_api.AgentVersionObject) error {
jsonBytes, err := json.MarshalIndent(version, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal agent version to JSON: %w", err)
}
fmt.Println(string(jsonBytes))
return nil
}

func printAgentVersionTable(version *agent_api.AgentVersionObject) error {
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintln(w, "FIELD\tVALUE")
fmt.Fprintln(w, "-----\t-----")

fmt.Fprintf(w, "ID\t%s\n", version.ID)
fmt.Fprintf(w, "Name\t%s\n", version.Name)
fmt.Fprintf(w, "Version\t%s\n", version.Version)
if version.Description != nil {
fmt.Fprintf(w, "Description\t%s\n", *version.Description)
}
if version.CreatedAt != 0 {
ts := time.Unix(version.CreatedAt, 0).UTC().Format(time.RFC3339)
fmt.Fprintf(w, "Created At\t%s\n", ts)
}
for k, v := range version.Metadata {
fmt.Fprintf(w, "Metadata[%s]\t%s\n", k, v)
}

return w.Flush()
}
69 changes: 69 additions & 0 deletions cli/azd/extensions/azure.ai.agents/internal/cmd/show_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,3 +175,72 @@ func TestShowCommand_DefaultOutputFlag(t *testing.T) {
output, _ := cmd.Flags().GetString("output")
assert.Equal(t, "json", output)
}

func TestPrintAgentVersionJSON(t *testing.T) {
version := &agent_api.AgentVersionObject{
Object: "agent.version",
ID: "ver-123",
Name: "my-agent",
Version: "1",
CreatedAt: 1735689600, // 2025-01-01T00:00:00Z
}

err := printAgentVersionJSON(version)
require.NoError(t, err)
}

func TestPrintAgentVersionJSON_Format(t *testing.T) {
desc := "A test agent"
version := &agent_api.AgentVersionObject{
Object: "agent.version",
ID: "ver-456",
Name: "test-agent",
Version: "2",
Description: &desc,
Metadata: map[string]string{"env": "prod"},
CreatedAt: 1735689600,
}

jsonBytes, err := json.MarshalIndent(version, "", " ")
require.NoError(t, err)

var result map[string]any
err = json.Unmarshal(jsonBytes, &result)
require.NoError(t, err)

assert.Equal(t, "agent.version", result["object"])
assert.Equal(t, "ver-456", result["id"])
assert.Equal(t, "test-agent", result["name"])
assert.Equal(t, "2", result["version"])
assert.Equal(t, "A test agent", result["description"])
metadata := result["metadata"].(map[string]any)
assert.Equal(t, "prod", metadata["env"])
}

func TestPrintAgentVersionTable(t *testing.T) {
desc := "A test agent"
version := &agent_api.AgentVersionObject{
Object: "agent.version",
ID: "ver-789",
Name: "my-agent",
Version: "3",
Description: &desc,
Metadata: map[string]string{"env": "staging"},
CreatedAt: 1735689600,
}

err := printAgentVersionTable(version)
require.NoError(t, err)
}

func TestPrintAgentVersionTable_MinimalFields(t *testing.T) {
version := &agent_api.AgentVersionObject{
Object: "agent.version",
ID: "ver-min",
Name: "minimal-agent",
Version: "1",
}

err := printAgentVersionTable(version)
require.NoError(t, err)
}
Loading
Loading