diff --git a/cli/azd/cmd/middleware/extensions.go b/cli/azd/cmd/middleware/extensions.go index 85768d9f828..ed21d5c4b68 100644 --- a/cli/azd/cmd/middleware/extensions.go +++ b/cli/azd/cmd/middleware/extensions.go @@ -132,8 +132,13 @@ func (m *ExtensionsMiddleware) Run(ctx context.Context, next NextFn) (*actions.A allEnv = append(allEnv, "FORCE_COLOR=1") } + args := []string{"listen"} + if debugEnabled, _ := m.options.Flags.GetBool("debug"); debugEnabled { + args = append(args, "--debug") + } + options := &extensions.InvokeOptions{ - Args: []string{"listen"}, + Args: args, Env: allEnv, StdIn: ext.StdIn(), StdOut: ext.StdOut(), diff --git a/cli/azd/extensions/azure.ai.agents/internal/cmd/listen.go b/cli/azd/extensions/azure.ai.agents/internal/cmd/listen.go index b579cb63a83..9c195b9c932 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/cmd/listen.go +++ b/cli/azd/extensions/azure.ai.agents/internal/cmd/listen.go @@ -9,14 +9,19 @@ import ( "fmt" "os" "path/filepath" + "strconv" "strings" + "time" "azureaiagent/internal/pkg/agents/agent_yaml" + "azureaiagent/internal/pkg/azure" "azureaiagent/internal/project" + azcorelog "github.com/Azure/azure-sdk-for-go/sdk/azcore/log" "github.com/azure/azure-dev/cli/azd/pkg/azdext" "github.com/braydonk/yaml" "github.com/spf13/cobra" + "github.com/spf13/pflag" "google.golang.org/protobuf/types/known/structpb" ) @@ -29,6 +34,20 @@ func newListenCommand() *cobra.Command { // Create a new context that includes the AZD access token. ctx := azdext.WithAccessToken(cmd.Context()) + if isDebug(cmd.Flags()) { + currentDate := time.Now().Format("2006-01-02") + logFileName := fmt.Sprintf("azd-ai-agents-%s.log", currentDate) + + 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 = azure.ConnectionStringJSONRegex.ReplaceAllString(msg, `${1}"REDACTED"`) + fmt.Fprintf(logFile, "[%s] %s: %s\n", time.Now().Format(time.RFC3339), event, msg) + }) + } + // Create a new AZD client. azdClient, err := azdext.NewAzdClient() if err != nil { @@ -315,3 +334,13 @@ func populateContainerSettings(ctx context.Context, azdClient *azdext.AzdClient, return nil } + +// isDebug checks if debug mode is enabled via --debug flag or AZD_EXT_DEBUG environment variable +func isDebug(flags *pflag.FlagSet) bool { + if debugFlag, err := flags.GetBool("debug"); err == nil && debugFlag { + return true + } + + debug, _ := strconv.ParseBool(os.Getenv("AZD_EXT_DEBUG")) + return debug +} diff --git a/cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_api/operations.go b/cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_api/operations.go index 667b141a2e2..f5763244a5b 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_api/operations.go +++ b/cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_api/operations.go @@ -11,7 +11,6 @@ import ( "io" "net/http" "net/url" - "os" "strconv" "azureaiagent/internal/version" @@ -34,6 +33,9 @@ func NewAgentClient(endpoint string, cred azcore.TokenCredential) *AgentClient { userAgent := fmt.Sprintf("azd-ext-azure-ai-agents/%s", version.Version) clientOptions := &policy.ClientOptions{ + Logging: policy.LogOptions{ + IncludeBody: true, + }, PerCallPolicies: []policy.Policy{ runtime.NewBearerTokenPolicy(cred, []string{"https://ai.azure.com/.default"}, nil), azsdk.NewMsCorrelationPolicy(), @@ -104,8 +106,6 @@ func (c *AgentClient) CreateAgent(ctx context.Context, request *CreateAgentReque return nil, fmt.Errorf("failed to set request body: %w", err) } - c.logRequest("POST", url, payload) - resp, err := c.pipeline.Do(req) if err != nil { return nil, fmt.Errorf("HTTP request failed: %w", err) @@ -126,7 +126,6 @@ func (c *AgentClient) CreateAgent(ctx context.Context, request *CreateAgentReque return nil, fmt.Errorf("failed to parse response: %w", err) } - c.logResponse(body) return &agent, nil } @@ -148,8 +147,6 @@ func (c *AgentClient) UpdateAgent(ctx context.Context, agentName string, request return nil, fmt.Errorf("failed to set request body: %w", err) } - c.logRequest("POST", url, payload) - resp, err := c.pipeline.Do(req) if err != nil { return nil, fmt.Errorf("HTTP request failed: %w", err) @@ -170,7 +167,6 @@ func (c *AgentClient) UpdateAgent(ctx context.Context, agentName string, request return nil, fmt.Errorf("failed to parse response: %w", err) } - c.logResponse(body) return &agent, nil } @@ -284,8 +280,6 @@ func (c *AgentClient) CreateAgentVersion(ctx context.Context, agentName string, return nil, fmt.Errorf("failed to set request body: %w", err) } - c.logRequest("POST", url, payload) - resp, err := c.pipeline.Do(req) if err != nil { return nil, fmt.Errorf("HTTP request failed: %w", err) @@ -306,7 +300,6 @@ func (c *AgentClient) CreateAgentVersion(ctx context.Context, agentName string, return nil, fmt.Errorf("failed to parse response: %w", err) } - c.logResponse(body) return &agentVersion, nil } @@ -579,8 +572,6 @@ func (c *AgentClient) StartAgentContainer(ctx context.Context, agentName, agentV return nil, fmt.Errorf("failed to set request body: %w", err) } - c.logRequest("POST", url, payload) - resp, err := c.pipeline.Do(req) if err != nil { return nil, fmt.Errorf("HTTP request failed: %w", err) @@ -606,7 +597,6 @@ func (c *AgentClient) StartAgentContainer(ctx context.Context, agentName, agentV Body: operation, } - c.logResponse(body) return result, nil } @@ -809,31 +799,3 @@ func (c *AgentClient) GetAgentContainerOperation(ctx context.Context, agentName, return &operation, nil } - -// Helper methods - -// logRequest logs the request details to stderr for debugging -func (c *AgentClient) logRequest(method, url string, payload []byte) { - fmt.Fprintf(os.Stderr, "%s %s\n", method, url) - if len(payload) > 0 { - var prettyPayload interface{} - if err := json.Unmarshal(payload, &prettyPayload); err == nil { - prettyJSON, _ := json.MarshalIndent(prettyPayload, "", " ") - fmt.Fprintf(os.Stderr, "Payload:\n%s\n", string(prettyJSON)) - } else { - fmt.Fprintf(os.Stderr, "Payload: %s\n", string(payload)) - } - } -} - -// logResponse logs the response body to stderr for debugging -func (c *AgentClient) logResponse(body []byte) { - fmt.Fprintln(os.Stderr, "Response:") - var jsonResponse interface{} - if err := json.Unmarshal(body, &jsonResponse); err == nil { - prettyJSON, _ := json.MarshalIndent(jsonResponse, "", " ") - fmt.Fprintln(os.Stderr, string(prettyJSON)) - } else { - fmt.Fprintln(os.Stderr, string(body)) - } -} diff --git a/cli/azd/extensions/azure.ai.agents/internal/pkg/azure/logging.go b/cli/azd/extensions/azure.ai.agents/internal/pkg/azure/logging.go new file mode 100644 index 00000000000..fdbd74e3cdb --- /dev/null +++ b/cli/azd/extensions/azure.ai.agents/internal/pkg/azure/logging.go @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package azure + +import "regexp" + +var ConnectionStringJSONRegex = regexp.MustCompile(`("[\w]*(?:CONNECTION_STRING|ConnectionString)":\s*)"[^"]*"`) diff --git a/cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go b/cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go index 2e4b77b2f93..1bc1288fa1e 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go +++ b/cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go @@ -678,7 +678,10 @@ func (p *AgentServiceTargetProvider) createAgent( azdEnv map[string]string, ) (*agent_api.AgentVersionObject, error) { // Create agent client - agentClient := agent_api.NewAgentClient(azdEnv["AZURE_AI_PROJECT_ENDPOINT"], p.credential) + agentClient := agent_api.NewAgentClient( + azdEnv["AZURE_AI_PROJECT_ENDPOINT"], + p.credential, + ) // Use constant API version const apiVersion = "2025-05-15-preview" @@ -725,7 +728,10 @@ func (p *AgentServiceTargetProvider) startAgentContainer( fmt.Fprintln(os.Stderr) // Create agent client - agentClient := agent_api.NewAgentClient(azdEnv["AZURE_AI_PROJECT_ENDPOINT"], p.credential) + agentClient := agent_api.NewAgentClient( + azdEnv["AZURE_AI_PROJECT_ENDPOINT"], + p.credential, + ) var minReplicas, maxReplicas *int32 if foundryAgentConfig.Container != nil && foundryAgentConfig.Container.Scale != nil {