Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
d39479d
feat: implement session management commands for hosted agents
therealjohn Apr 6, 2026
5a3997d
feat: enhance session management with agent version handling and erro…
therealjohn Apr 7, 2026
1b8592a
feat: rename session command to sessions and update related documenta…
therealjohn Apr 8, 2026
5f6c57b
fix: use correct endpoint for files
therealjohn Apr 8, 2026
6ae0e9f
fix: default target path is relative to home, not full local path
therealjohn Apr 8, 2026
d0edf46
feat: implement error handling for session retrieval and deletion, in…
therealjohn Apr 8, 2026
cd26efa
feat: files remove -> files delete to be consistent with SDK and sess…
therealjohn Apr 8, 2026
1c342a4
feat: add positional args for file commands
therealjohn Apr 8, 2026
29fc917
feat: rename --session flag to --session-id across commands. Fixes #7…
therealjohn Apr 8, 2026
05e7200
feat: update command documentation for session and file operations, e…
therealjohn Apr 8, 2026
6c9ec38
fix: remove unused body field from fakeTransport struct in operations…
therealjohn Apr 8, 2026
76b01d7
fix: replace context.Background() with t.Context() in GetSession test
therealjohn Apr 8, 2026
857d4ee
feat: update documentation to replace --session with --session-id for…
therealjohn Apr 8, 2026
d4057b6
feat: enhance session deletion message and add tests for session crea…
therealjohn Apr 9, 2026
e342bdb
Update cli/azd/extensions/azure.ai.agents/internal/cmd/session.go
therealjohn Apr 9, 2026
d06a926
feat: improve error handling for session deletion and add tests for 4…
therealjohn Apr 9, 2026
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
129 changes: 85 additions & 44 deletions cli/azd/extensions/azure.ai.agents/internal/cmd/files.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,13 @@ func newFilesCommand() *cobra.Command {
Hidden: !isVNextEnabled(context.Background()),
Long: `Manage files in a hosted agent session.

Upload, download, list, and remove files in the session-scoped filesystem
Upload, download, list, and delete files in the session-scoped filesystem
of a hosted agent. This is useful for debugging, seeding data, and agent setup.

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.`,
from the last invoke session, or can be overridden with --session-id.`,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
// Chain with root's PersistentPreRunE (root sets NoPrompt).
// Note: cmd.Parent() would return the "files" command itself when
Expand Down Expand Up @@ -111,7 +111,7 @@ from the last invoke session, or can be overridden with --session.`,
// addFilesFlags registers the common flags on a cobra command.
func addFilesFlags(cmd *cobra.Command, flags *filesFlags) {
cmd.Flags().StringVarP(&flags.agentName, "agent-name", "n", "", "Agent name (matches azure.yaml service name; auto-detected when only one exists)")
cmd.Flags().StringVarP(&flags.session, "session", "s", "", "Session ID override (defaults to last invoke session)")
cmd.Flags().StringVarP(&flags.session, "session-id", "s", "", "Session ID override (defaults to last invoke session)")
}

// filesContext holds the resolved agent context and session for file operations.
Expand Down Expand Up @@ -179,27 +179,38 @@ func newFilesUploadCommand() *cobra.Command {
flags := &filesUploadFlags{}

cmd := &cobra.Command{
Use: "upload",
Use: "upload [file]",
Short: "Upload a file to a hosted agent session.",
Long: `Upload a file to a hosted agent session.

Reads a local file and uploads it to the specified remote path
in the session's filesystem. If --target-path is not provided,
the remote path defaults to the local file path.
the remote path defaults to the local filename.

Agent details are automatically resolved from the azd environment.`,
Example: ` # Upload a file (remote path defaults to local path)
azd ai agent files upload --file ./data/input.csv
Example: ` # Upload a file (remote path defaults to filename)
azd ai agent files upload ./data/input.csv

# Upload to a specific remote path
azd ai agent files upload --file ./input.csv --target-path /data/input.csv
azd ai agent files upload ./input.csv --target-path /data/input.csv

# Upload with explicit agent name and session
azd ai agent files upload --file ./input.csv --agent-name my-agent --session <session-id>`,
# Upload with flags
azd ai agent files upload --file ./input.csv --agent-name my-agent`,
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := azdext.WithAccessToken(cmd.Context())
setupDebugLogging(cmd.Flags())

if len(args) > 0 && flags.file == "" {
flags.file = args[0]
}
if flags.file == "" {
return fmt.Errorf(
"file path is required as a positional argument " +
"or via --file",
)
}

fc, err := resolveFilesContext(ctx, &flags.filesFlags)
if err != nil {
return err
Expand All @@ -216,9 +227,8 @@ Agent details are automatically resolved from the azd environment.`,
}

addFilesFlags(cmd, &flags.filesFlags)
cmd.Flags().StringVarP(&flags.file, "file", "f", "", "Local file path to upload (required)")
cmd.Flags().StringVarP(&flags.targetPath, "target-path", "t", "", "Remote destination path (defaults to local file path)")
_ = cmd.MarkFlagRequired("file")
cmd.Flags().StringVarP(&flags.file, "file", "f", "", "Local file path to upload")
cmd.Flags().StringVarP(&flags.targetPath, "target-path", "t", "", "Remote destination path (defaults to local filename)")

return cmd
}
Expand All @@ -227,7 +237,7 @@ Agent details are automatically resolved from the azd environment.`,
func (a *FilesUploadAction) Run(ctx context.Context) error {
remotePath := a.flags.targetPath
if remotePath == "" {
remotePath = a.flags.file
remotePath = filepath.Base(a.flags.file)
}

//nolint:gosec // G304: file path is provided by the user via CLI flag
Expand All @@ -254,7 +264,7 @@ func (a *FilesUploadAction) Run(ctx context.Context) error {
return fmt.Errorf("failed to upload file: %w", err)
}

fmt.Printf("Uploaded %s %s\n", a.flags.file, remotePath)
fmt.Printf("Uploaded %s -> %s\n", a.flags.file, remotePath)
return nil
}

Expand All @@ -277,7 +287,7 @@ func newFilesDownloadCommand() *cobra.Command {
flags := &filesDownloadFlags{}

cmd := &cobra.Command{
Use: "download",
Use: "download [file]",
Short: "Download a file from a hosted agent session.",
Long: `Download a file from a hosted agent session.

Expand All @@ -287,17 +297,28 @@ the local path defaults to the basename of the remote file.

Agent details are automatically resolved from the azd environment.`,
Example: ` # Download a file (local path defaults to remote filename)
azd ai agent files download --file /data/output.csv
azd ai agent files download /data/output.csv

# Download to a specific local path
azd ai agent files download --file /data/output.csv --target-path ./output.csv
azd ai agent files download /data/output.csv --target-path ./output.csv

# Download with explicit session
azd ai agent files download --file /data/output.csv --session <session-id>`,
# Download with flags
azd ai agent files download --file /data/output.csv --session-id <session-id>`,
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := azdext.WithAccessToken(cmd.Context())
setupDebugLogging(cmd.Flags())

if len(args) > 0 && flags.file == "" {
flags.file = args[0]
}
if flags.file == "" {
return fmt.Errorf(
"file path is required as a positional argument " +
"or via --file",
)
}

fc, err := resolveFilesContext(ctx, &flags.filesFlags)
if err != nil {
return err
Expand All @@ -314,9 +335,8 @@ Agent details are automatically resolved from the azd environment.`,
}

addFilesFlags(cmd, &flags.filesFlags)
cmd.Flags().StringVarP(&flags.file, "file", "f", "", "Remote file path to download (required)")
cmd.Flags().StringVarP(&flags.file, "file", "f", "", "Remote file path to download")
cmd.Flags().StringVarP(&flags.targetPath, "target-path", "t", "", "Local destination path (defaults to remote filename)")
_ = cmd.MarkFlagRequired("file")

return cmd
}
Expand Down Expand Up @@ -356,7 +376,7 @@ func (a *FilesDownloadAction) Run(ctx context.Context) error {
return fmt.Errorf("failed to write file: %w", err)
}

fmt.Printf("Downloaded %s %s\n", a.flags.file, targetPath)
fmt.Printf("Downloaded %s -> %s\n", a.flags.file, targetPath)
return nil
}

Expand Down Expand Up @@ -397,7 +417,7 @@ Agent details are automatically resolved from the azd environment.`,
azd ai agent files list /data --output table

# List with explicit session
azd ai agent files list --session <session-id>`,
azd ai agent files list --session-id <session-id>`,
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := azdext.WithAccessToken(cmd.Context())
Expand Down Expand Up @@ -505,26 +525,38 @@ func newFilesRemoveCommand() *cobra.Command {
var filePath string

cmd := &cobra.Command{
Use: "remove",
Short: "Remove a file or directory from a hosted agent session.",
Long: `Remove a file or directory from a hosted agent session.
Use: "delete [file]",
Aliases: []string{"remove"},
Short: "Delete a file or directory from a hosted agent session.",
Long: `Delete a file or directory from a hosted agent session.

Removes the specified file or directory from the session's filesystem.
Use --recursive to remove directories and their contents.
Deletes the specified file or directory from the session's filesystem.
Use --recursive to delete directories and their contents.

Agent details are automatically resolved from the azd environment.`,
Example: ` # Remove a file (agent auto-detected)
azd ai agent files remove --file /data/old-file.csv
Example: ` # Delete a file (agent auto-detected)
azd ai agent files delete /data/old-file.csv

# Remove a directory recursively
azd ai agent files remove --file /data/temp --recursive
# Delete a directory recursively
azd ai agent files delete /data/temp --recursive

# Remove with explicit session
azd ai agent files remove --file /data/old-file.csv --session <session-id>`,
# Delete with flags
azd ai agent files delete --file /data/old-file.csv --session-id <session-id>`,
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := azdext.WithAccessToken(cmd.Context())
setupDebugLogging(cmd.Flags())

if len(args) > 0 && filePath == "" {
filePath = args[0]
}
if filePath == "" {
return fmt.Errorf(
"file path is required as a positional argument " +
"or via --file",
)
}

fc, err := resolveFilesContext(ctx, &flags.filesFlags)
if err != nil {
return err
Expand All @@ -542,9 +574,8 @@ Agent details are automatically resolved from the azd environment.`,
}

addFilesFlags(cmd, &flags.filesFlags)
cmd.Flags().StringVarP(&filePath, "file", "f", "", "Remote file or directory path to remove")
_ = cmd.MarkFlagRequired("file")
cmd.Flags().BoolVar(&flags.recursive, "recursive", false, "Recursively remove directories and their contents")
cmd.Flags().StringVarP(&filePath, "file", "f", "", "Remote file or directory path to delete")
cmd.Flags().BoolVar(&flags.recursive, "recursive", false, "Recursively delete directories and their contents")

return cmd
}
Expand Down Expand Up @@ -586,7 +617,7 @@ func newFilesMkdirCommand() *cobra.Command {
var dirPath string

cmd := &cobra.Command{
Use: "mkdir",
Use: "mkdir [dir]",
Short: "Create a directory in a hosted agent session.",
Long: `Create a directory in a hosted agent session.

Expand All @@ -595,14 +626,25 @@ Parent directories are created as needed.

Agent details are automatically resolved from the azd environment.`,
Example: ` # Create a directory (agent auto-detected)
azd ai agent files mkdir --dir /data/output
azd ai agent files mkdir /data/output

# Create with explicit session
azd ai agent files mkdir --dir /data/output --session <session-id>`,
# Create with flags
azd ai agent files mkdir --dir /data/output --session-id <session-id>`,
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := azdext.WithAccessToken(cmd.Context())
setupDebugLogging(cmd.Flags())

if len(args) > 0 && dirPath == "" {
dirPath = args[0]
}
if dirPath == "" {
return fmt.Errorf(
"directory path is required as a positional " +
"argument or via --dir",
)
}

fc, err := resolveFilesContext(ctx, flags)
if err != nil {
return err
Expand All @@ -620,7 +662,6 @@ Agent details are automatically resolved from the azd environment.`,

addFilesFlags(cmd, flags)
cmd.Flags().StringVarP(&dirPath, "dir", "d", "", "Remote directory path to create")
_ = cmd.MarkFlagRequired("dir")

return cmd
}
Expand Down Expand Up @@ -680,7 +721,7 @@ Agent details are automatically resolved from the azd environment.`,
azd ai agent files stat /data/output.csv --output table

# Get metadata with explicit session
azd ai agent files stat /data/output.csv --session <session-id>`,
azd ai agent files stat /data/output.csv --session-id <session-id>`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := azdext.WithAccessToken(cmd.Context())
Expand Down
14 changes: 7 additions & 7 deletions cli/azd/extensions/azure.ai.agents/internal/cmd/files_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func TestFilesCommand_HasSubcommands(t *testing.T) {
assert.Contains(t, names, "upload")
assert.Contains(t, names, "download")
assert.Contains(t, names, "list")
assert.Contains(t, names, "remove")
assert.Contains(t, names, "delete")
}

func TestFilesUploadCommand_MissingFile(t *testing.T) {
Expand All @@ -59,7 +59,7 @@ func TestFilesUploadCommand_MissingFile(t *testing.T) {
func TestFilesUploadCommand_HasFlags(t *testing.T) {
cmd := newFilesUploadCommand()

for _, name := range []string{"file", "target-path", "agent-name", "session"} {
for _, name := range []string{"file", "target-path", "agent-name", "session-id"} {
f := cmd.Flags().Lookup(name)
require.NotNil(t, f, "expected flag %q", name)
assert.Equal(t, "", f.DefValue)
Expand All @@ -79,7 +79,7 @@ func TestFilesDownloadCommand_MissingFile(t *testing.T) {
func TestFilesDownloadCommand_HasFlags(t *testing.T) {
cmd := newFilesDownloadCommand()

for _, name := range []string{"file", "target-path", "agent-name", "session"} {
for _, name := range []string{"file", "target-path", "agent-name", "session-id"} {
f := cmd.Flags().Lookup(name)
require.NotNil(t, f, "expected flag %q", name)
assert.Equal(t, "", f.DefValue)
Expand All @@ -100,7 +100,7 @@ func TestFilesListCommand_OptionalRemotePath(t *testing.T) {
assert.NotNil(t, cmd.Args)
}

func TestFilesRemoveCommand_MissingFile(t *testing.T) {
func TestFilesDeleteCommand_MissingFile(t *testing.T) {
cmd := newFilesRemoveCommand()

// Missing required --file flag
Expand All @@ -110,10 +110,10 @@ func TestFilesRemoveCommand_MissingFile(t *testing.T) {
assert.Contains(t, err.Error(), "file")
}

func TestFilesRemoveCommand_HasFlags(t *testing.T) {
func TestFilesDeleteCommand_HasFlags(t *testing.T) {
cmd := newFilesRemoveCommand()

for _, name := range []string{"file", "recursive", "agent-name", "session"} {
for _, name := range []string{"file", "recursive", "agent-name", "session-id"} {
f := cmd.Flags().Lookup(name)
require.NotNil(t, f, "expected flag %q", name)
}
Expand All @@ -135,7 +135,7 @@ func TestFilesMkdirCommand_MissingDir(t *testing.T) {
func TestFilesMkdirCommand_HasFlags(t *testing.T) {
cmd := newFilesMkdirCommand()

for _, name := range []string{"dir", "agent-name", "session"} {
for _, name := range []string{"dir", "agent-name", "session-id"} {
f := cmd.Flags().Lookup(name)
require.NotNil(t, f, "expected flag %q", name)
assert.Equal(t, "", f.DefValue)
Expand Down
10 changes: 5 additions & 5 deletions cli/azd/extensions/azure.ai.agents/internal/cmd/monitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func newMonitorCommand() *cobra.Command {
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 --session-id 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.

Expand All @@ -55,10 +55,10 @@ configuration and the current azd environment. Optionally specify the service na
azd ai agent monitor my-agent

# Stream session logs
azd ai agent monitor --session <session-id>
azd ai agent monitor --session-id <session-id>

# Stream session logs in real-time
azd ai agent monitor --session <session-id> --follow
azd ai agent monitor --session-id <session-id> --follow

# Fetch system event logs from container
azd ai agent monitor --type system`,
Expand Down Expand Up @@ -107,7 +107,7 @@ configuration and the current azd environment. Optionally specify the service na
return exterrors.Validation(
exterrors.CodeInvalidSessionId,
"VNext agents are currently enabled and require a session ID for log streaming.",
"Specify the session ID using --session, or run `azd ai agent invoke` first to create one",
"Specify the session ID using --session-id, or run `azd ai agent invoke` first to create one",
)
}
flags.sessionID = sessionID
Expand All @@ -123,7 +123,7 @@ configuration and the current azd environment. Optionally specify the service na
},
}

cmd.Flags().StringVarP(&flags.sessionID, "session", "s", "", "Session ID to stream logs for")
cmd.Flags().StringVarP(&flags.sessionID, "session-id", "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",
Expand Down
Loading
Loading