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
22 changes: 22 additions & 0 deletions cli/azd/extensions/azure.ai.agents/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,28 @@ When bumping the extension version for a patch release, update **only** these fi

**Do NOT update `cli/azd/extensions/registry.json`.** The registry entry (checksums, artifact URLs) is generated automatically by CI after the release build produces the binaries. Editing it manually by hand will result in wrong or placeholder checksums that break installation.

## Output: `log` vs `fmt`

Extensions write directly to stdout/stderr — there is no `Console` abstraction from azd core.

- **`fmt.Print*`** — user-facing output (stdout). Pair with `output.With*Format` helpers for styled text.
- **`log.Print*`** — developer diagnostics (stderr). Hidden unless `--debug` is set. Never use `log` for anything the user needs to see.
- Do not use `log.Fatal` or `log.Panic` for expected failures — return a structured error via `exterrors` instead.

```go
// ✅ log — internal detail the user doesn't need to see
log.Printf("ARM response: status=%d, id=%s", resp.StatusCode, resourceId)

// ✅ fmt — user-facing status and results
fmt.Println(output.WithSuccessFormat("Agent deployed to %s", endpoint))

// ❌ fmt used for debug noise — user sees internal details they can't act on
fmt.Printf("Parsed resource ID: sub=%s, rg=%s\n", subId, rg) // use log.Printf

// ❌ log used for user-facing info — user never sees it without --debug
log.Printf("No Foundry projects found in subscription") // use fmt + output helper
```

## Other extension conventions

- Use modern Go 1.26 patterns where they help readability
Expand Down
6 changes: 3 additions & 3 deletions cli/azd/extensions/azure.ai.agents/internal/cmd/banner.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"azureaiagent/internal/version"

"github.com/azure/azure-dev/cli/azd/pkg/output"
"github.com/fatih/color"
)

Expand All @@ -27,16 +28,15 @@ const bannerArt = `███████╗ ██████╗ ██╗

func printBanner(w io.Writer) {
purple := color.RGB(109, 53, 255).Add(color.Bold)
dim := color.New(color.Faint)
fmt.Fprintln(w)

for line := range strings.SplitSeq(bannerArt, "\n") {
purple.Fprintln(w, line) //nolint:gosec // G104 - banner output errors are non-critical
}

dim.Fprintf(w, "v%s", version.Version) //nolint:gosec // G104 - banner output errors are non-critical
fmt.Fprint(w, output.WithGrayFormat("v%s", version.Version)) //nolint:gosec // G104 - banner output errors are non-critical
fmt.Fprint(w, " ")
fmt.Fprintln(w)
dim.Fprintln(w, "Visit the docs at https://aka.ms/azd-ai-agent-docs") //nolint:gosec // G104 - banner output errors are non-critical
fmt.Fprintln(w, output.WithGrayFormat("Visit the docs at https://aka.ms/azd-ai-agent-docs")) //nolint:gosec // G104 - banner output errors are non-critical
fmt.Fprintln(w)
}
56 changes: 41 additions & 15 deletions cli/azd/extensions/azure.ai.agents/internal/cmd/debug.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ package cmd

import (
"fmt"
"io"
"log"
"os"
"regexp"
"strconv"
Expand All @@ -16,21 +18,45 @@ import (

var connectionStringJSONRegex = regexp.MustCompile(`("[\w]*(?:CONNECTION_STRING|ConnectionString)":\s*)"[^"]*"`)

// setupDebugLogging configures the Azure SDK logger if debug mode is enabled.
func setupDebugLogging(flags *pflag.FlagSet) {
if isDebug(flags) {
currentDate := time.Now().Format("2006-01-02")
logFileName := fmt.Sprintf("azd-ai-agents-%s.log", currentDate)

//nolint:gosec // log file name is generated locally from date and not user-controlled
logFile, err := os.OpenFile(logFileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
logFile = os.Stderr
}
azcorelog.SetListener(func(event azcorelog.Event, msg string) {
msg = connectionStringJSONRegex.ReplaceAllString(msg, `${1}"REDACTED"`)
fmt.Fprintf(logFile, "[%s] %s: %s\n", time.Now().Format(time.RFC3339), event, msg)
})
// setupDebugLogging configures debug logging for the extension.
// By default Go's standard log package writes to stderr, which causes internal
// messages (e.g. from the command runner and GitHub CLI wrapper) to appear as
// noisy user-facing output. This function silences those logs unless debug mode
// is enabled, and additionally configures the Azure SDK logger when debugging.
// Returns a cleanup function that should be deferred by the caller.
func setupDebugLogging(flags *pflag.FlagSet) func() {
if !isDebug(flags) {
log.SetOutput(io.Discard)
azcorelog.SetListener(nil)
return func() {}
}

currentDate := time.Now().Format("2006-01-02")
logFileName := fmt.Sprintf("azd-ai-agents-%s.log", currentDate)

//nolint:gosec // log file name is generated locally from date and not user-controlled
logFile, err := os.OpenFile(logFileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)

var w io.Writer
var closeFile func()
if err != nil {
w = os.Stderr
closeFile = func() {}
} else {
w = logFile
closeFile = func() { logFile.Close() } //nolint:gosec // best-effort cleanup of debug log file
}

log.SetOutput(w)
azcorelog.SetListener(func(event azcorelog.Event, msg string) {
msg = connectionStringJSONRegex.ReplaceAllString(msg, `${1}"REDACTED"`)
fmt.Fprintf(w, "[%s] %s: %s\n", time.Now().Format(time.RFC3339), event, msg)
})

return func() {
log.SetOutput(io.Discard)
azcorelog.SetListener(nil)
closeFile()
}
}

Expand Down
18 changes: 12 additions & 6 deletions cli/azd/extensions/azure.ai.agents/internal/cmd/files.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,8 @@ Agent details are automatically resolved from the azd environment.`,
azd ai agent files upload --file ./input.csv --agent-name my-agent --session <session-id>`,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := azdext.WithAccessToken(cmd.Context())
setupDebugLogging(cmd.Flags())
logCleanup := setupDebugLogging(cmd.Flags())
defer logCleanup()

fc, err := resolveFilesContext(ctx, &flags.filesFlags)
if err != nil {
Expand Down Expand Up @@ -296,7 +297,8 @@ Agent details are automatically resolved from the azd environment.`,
azd ai agent files download --file /data/output.csv --session <session-id>`,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := azdext.WithAccessToken(cmd.Context())
setupDebugLogging(cmd.Flags())
logCleanup := setupDebugLogging(cmd.Flags())
defer logCleanup()

fc, err := resolveFilesContext(ctx, &flags.filesFlags)
if err != nil {
Expand Down Expand Up @@ -401,7 +403,8 @@ Agent details are automatically resolved from the azd environment.`,
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := azdext.WithAccessToken(cmd.Context())
setupDebugLogging(cmd.Flags())
logCleanup := setupDebugLogging(cmd.Flags())
defer logCleanup()

fc, err := resolveFilesContext(ctx, &flags.filesFlags)
if err != nil {
Expand Down Expand Up @@ -523,7 +526,8 @@ Agent details are automatically resolved from the azd environment.`,
azd ai agent files remove --file /data/old-file.csv --session <session-id>`,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := azdext.WithAccessToken(cmd.Context())
setupDebugLogging(cmd.Flags())
logCleanup := setupDebugLogging(cmd.Flags())
defer logCleanup()

fc, err := resolveFilesContext(ctx, &flags.filesFlags)
if err != nil {
Expand Down Expand Up @@ -601,7 +605,8 @@ Agent details are automatically resolved from the azd environment.`,
azd ai agent files mkdir --dir /data/output --session <session-id>`,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := azdext.WithAccessToken(cmd.Context())
setupDebugLogging(cmd.Flags())
logCleanup := setupDebugLogging(cmd.Flags())
defer logCleanup()

fc, err := resolveFilesContext(ctx, flags)
if err != nil {
Expand Down Expand Up @@ -684,7 +689,8 @@ Agent details are automatically resolved from the azd environment.`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := azdext.WithAccessToken(cmd.Context())
setupDebugLogging(cmd.Flags())
logCleanup := setupDebugLogging(cmd.Flags())
defer logCleanup()

fc, err := resolveFilesContext(ctx, &flags.filesFlags)
if err != nil {
Expand Down
Loading
Loading