Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
130 commits
Select commit Hold shift + click to select a range
7347ee6
WIP
vhvb1989 Jun 21, 2025
0ffff37
wip
vhvb1989 Jun 24, 2025
8900565
Ading llm package wrapping langchain lib
vhvb1989 Jun 26, 2025
60b75ed
Merge branch 'main' of https://github.com/azure/azure-dev into use-llm
vhvb1989 Jun 26, 2025
ed0d5fa
Merge branch 'main' of https://github.com/azure/azure-dev into use-llm
vhvb1989 Jun 26, 2025
fe55644
revert
vhvb1989 Jun 27, 2025
50018b8
Merge branch 'main' of https://github.com/azure/azure-dev into use-llm
vhvb1989 Jun 27, 2025
5e48f4a
copyright
vhvb1989 Jun 30, 2025
f6e1dbc
WIP
vhvb1989 Jul 1, 2025
f23545b
WIP
vhvb1989 Jul 2, 2025
75e5ca4
Merge branch 'main' of https://github.com/azure/azure-dev into hooks-…
vhvb1989 Jul 2, 2025
036f690
wip
vhvb1989 Jul 2, 2025
3ffce68
wip
vhvb1989 Jul 2, 2025
593af36
wip
vhvb1989 Jul 3, 2025
8d593a3
Merge branch 'main' of https://github.com/azure/azure-dev into hooks-…
vhvb1989 Jul 7, 2025
43db53a
Merge branch 'main' of https://github.com/azure/azure-dev into hooks-…
vhvb1989 Jul 9, 2025
514d4e2
wip
vhvb1989 Jul 11, 2025
c6c3ee4
Merge branch 'main' of https://github.com/azure/azure-dev into hooks-…
vhvb1989 Jul 15, 2025
62246e7
mcp sampling
vhvb1989 Jul 15, 2025
e592c65
separate from victor's pr
hemarina Jul 23, 2025
802a203
move part of hooks new code here and tweak for agentRunner
hemarina Jul 23, 2025
38a5694
AI error suggestion
hemarina Jul 23, 2025
8866ab0
clean from hooks new
hemarina Jul 23, 2025
7a8d66f
WIP: azd ai chat
wbreza Jul 26, 2025
ee6c099
azd agent
wbreza Jul 29, 2025
c148d11
Upadates agent and tools
wbreza Jul 29, 2025
00befc3
Adds MCP tool support
wbreza Jul 30, 2025
08d9e32
UX updates, tool JSON payloads
wbreza Jul 30, 2025
95feb33
Adds c2c similar tools
wbreza Jul 31, 2025
9a36c51
Wire up noop sampling handler
wbreza Jul 31, 2025
8960505
Adds sampling handler
wbreza Jul 31, 2025
de487a4
Fixed sampling
wbreza Aug 1, 2025
9cffc50
Adds azd helper tools
wbreza Aug 1, 2025
bf7f39f
Moved files around
wbreza Aug 1, 2025
972dcb2
WIP: Initial integration of agent mode for init
wbreza Aug 2, 2025
bfd5d13
Updates io tools to remove callback handler
wbreza Aug 2, 2025
a001e39
Adds feature flag usage
wbreza Aug 7, 2025
99f195f
Updates final prompts and output
wbreza Aug 7, 2025
c7b41de
Fixes all linter & spelling issues
wbreza Aug 7, 2025
56a68ea
Fixes more spell linter issues
wbreza Aug 7, 2025
10638e3
Moves azd commands to MCP server tools
wbreza Aug 8, 2025
ed8df26
Fixes lint issues
wbreza Aug 8, 2025
718112b
Adds github copilot instructions
wbreza Aug 8, 2025
36d888b
Updates inline go docs
wbreza Aug 8, 2025
339fe59
Merge branch 'azd-ai-agent-int' into error
hemarina Aug 14, 2025
4e23251
Merge branch 'main' of https://github.com/Azure/azure-dev into error
hemarina Aug 18, 2025
6e1ad1b
WIP: azd ai chat
wbreza Jul 26, 2025
add633c
azd agent
wbreza Jul 29, 2025
2090338
Upadates agent and tools
wbreza Jul 29, 2025
f8c1b20
Adds MCP tool support
wbreza Jul 30, 2025
1761578
UX updates, tool JSON payloads
wbreza Jul 30, 2025
9167aa0
Adds c2c similar tools
wbreza Jul 31, 2025
e04ed74
Wire up noop sampling handler
wbreza Jul 31, 2025
0c5e3db
Adds sampling handler
wbreza Jul 31, 2025
8377ecb
Fixed sampling
wbreza Aug 1, 2025
0637809
Adds azd helper tools
wbreza Aug 1, 2025
4f5b28e
Moved files around
wbreza Aug 1, 2025
42e1324
WIP: Initial integration of agent mode for init
wbreza Aug 2, 2025
70170af
Updates io tools to remove callback handler
wbreza Aug 2, 2025
1cbdb1e
Adds feature flag usage
wbreza Aug 7, 2025
1c19950
Updates final prompts and output
wbreza Aug 7, 2025
d45f608
Fixes all linter & spelling issues
wbreza Aug 7, 2025
21a6559
Fixes more spell linter issues
wbreza Aug 7, 2025
8bc28bc
Moves azd commands to MCP server tools
wbreza Aug 8, 2025
889cd2b
Fixes lint issues
wbreza Aug 8, 2025
af2cf70
Adds github copilot instructions
wbreza Aug 8, 2025
459fc3c
Updates inline go docs
wbreza Aug 8, 2025
88e2ced
WIP: user consent
wbreza Aug 9, 2025
b17be7c
Adds more consent validation
wbreza Aug 11, 2025
cac0351
Adds unit tests for reading files, LLM multi-partial write scenarios.
wbreza Aug 12, 2025
b24e242
Updates write tool description about partial writes
wbreza Aug 12, 2025
79c0485
Fixes MCP tools
wbreza Aug 12, 2025
f498b4c
Adds consent system for sampling requests
wbreza Aug 12, 2025
106c55d
Revision of consent system
wbreza Aug 13, 2025
31805e3
Revises tool prompts and adds annotations to azd tools
wbreza Aug 13, 2025
d5dfe2b
Moves flag parsing to consent package
wbreza Aug 13, 2025
73aab29
Made updates to consent filtering APIs
wbreza Aug 13, 2025
22476af
Renamed 'clear' to 'revoke'
wbreza Aug 13, 2025
59c5694
Addresses linter issues
wbreza Aug 15, 2025
e324121
Update azure yaml validation tool
wbreza Aug 16, 2025
4724432
Updates prompts and markdown output
wbreza Aug 18, 2025
5ee0b02
Updates MCP command registration after rebase
wbreza Aug 18, 2025
584ae77
Updates tool prompts
wbreza Aug 18, 2025
6f98217
add azd error troubleshooting tool md
hemarina Aug 18, 2025
fddea1b
Merge branch 'azd-ai-agent-int' into error
hemarina Aug 18, 2025
8c0f631
wip
wbreza Aug 19, 2025
4574d49
visual render orchestration
wbreza Aug 19, 2025
74e3159
Updates agent output for init
wbreza Aug 20, 2025
eecef9f
Updates read/write file tests
wbreza Aug 20, 2025
c8ef23e
Merge branch 'azd-ai-agent-int' into error
hemarina Aug 20, 2025
e0871ef
debug
hemarina Aug 21, 2025
762bffd
Fixes issue where agent hangs on blocking thought channel
wbreza Aug 22, 2025
22d23b2
Merge branch 'azd-ai-agent-int' into error
hemarina Aug 22, 2025
644117f
fix error middleware workflow bug
hemarina Aug 28, 2025
f19d828
Merge branch 'main' of https://github.com/Azure/azure-dev into error
hemarina Aug 28, 2025
a69bb37
clean up merge
hemarina Aug 28, 2025
b026808
clean up
hemarina Aug 28, 2025
adb754b
minor fix
hemarina Aug 28, 2025
8e0df2d
add user confirmation prompt
hemarina Aug 30, 2025
590015b
lll
hemarina Aug 30, 2025
5f48532
fix the bug
hemarina Aug 30, 2025
5937df3
revert this ux fix, cause other bug
hemarina Aug 30, 2025
80effbc
lll
hemarina Aug 31, 2025
0e3905b
minor fix on comment and skipAnalyzingErrors
hemarina Sep 3, 2025
c778dcd
remove comment
hemarina Sep 3, 2025
e5fe1b4
fix provision print twice ux in up bug
hemarina Sep 4, 2025
ef17fd0
make sure validation file changes is removed
hemarina Sep 4, 2025
c9fe908
print out file changes
hemarina Sep 4, 2025
9ad6fd6
add prompt consent for error handling, update tool color, add ai disc…
hemarina Sep 9, 2025
4de0c1e
separate error handling consent from consent system
hemarina Sep 9, 2025
3f3a8af
clean up and lll
hemarina Sep 9, 2025
df6ff52
fix bug for prompt consent
hemarina Sep 9, 2025
aea8215
minor description update
hemarina Sep 9, 2025
545bf7c
fileschange printed fix
hemarina Sep 10, 2025
2c01342
fix azd.exe multiple calling at background bug
hemarina Sep 10, 2025
3f29682
nit
hemarina Sep 10, 2025
5f94059
remove debugger
hemarina Sep 10, 2025
f0b927e
fix files changed, address comment
hemarina Sep 10, 2025
e23a0cd
fix golangci-lint warning
hemarina Sep 10, 2025
0efbe50
minor UX fix
hemarina Sep 10, 2025
5221d60
lll
hemarina Sep 10, 2025
d8e9f61
fix spinner UX bug for "Comparing deployment state" running forever
hemarina Sep 10, 2025
a431fd4
UX update on troubleshoot steps
hemarina Sep 10, 2025
a5f21f1
minor ux update
hemarina Sep 10, 2025
59fc732
Merge branch 'main' of https://github.com/Azure/azure-dev into error
hemarina Sep 10, 2025
d6be32e
minor ux updates
hemarina Sep 11, 2025
bbc1769
address feedback 1
hemarina Sep 11, 2025
c55c7cd
address feedback 2
hemarina Sep 11, 2025
ae9ecf7
address feedback
hemarina Sep 11, 2025
758d12f
address feedback
hemarina Sep 12, 2025
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
1 change: 1 addition & 0 deletions cli/azd/cmd/mcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ func (a *mcpStartAction) Run(ctx context.Context) (*actions.ActionResult, error)
tools.NewAzdProjectValidationTool(),
tools.NewAzdYamlSchemaTool(),
tools.NewSamplingTool(),
tools.NewAzdErrorTroubleShootingTool(),
}

s.AddTools(allTools...)
Expand Down
340 changes: 340 additions & 0 deletions cli/azd/cmd/middleware/error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,340 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package middleware

import (
"context"
"errors"
"fmt"
"strings"

"github.com/azure/azure-dev/cli/azd/cmd/actions"
"github.com/azure/azure-dev/cli/azd/internal"
"github.com/azure/azure-dev/cli/azd/internal/agent"
"github.com/azure/azure-dev/cli/azd/pkg/alpha"
"github.com/azure/azure-dev/cli/azd/pkg/config"
"github.com/azure/azure-dev/cli/azd/pkg/input"
"github.com/azure/azure-dev/cli/azd/pkg/llm"
"github.com/azure/azure-dev/cli/azd/pkg/output"
"github.com/azure/azure-dev/cli/azd/pkg/tools"
uxlib "github.com/azure/azure-dev/cli/azd/pkg/ux"
"github.com/fatih/color"
)

type ErrorMiddleware struct {
options *Options
console input.Console
agentFactory *agent.AgentFactory
global *internal.GlobalCommandOptions
featuresManager *alpha.FeatureManager
userConfigManager config.UserConfigManager
}

func NewErrorMiddleware(
options *Options, console input.Console,
agentFactory *agent.AgentFactory,
global *internal.GlobalCommandOptions,
featuresManager *alpha.FeatureManager,
userConfigManager config.UserConfigManager,
) Middleware {
return &ErrorMiddleware{
options: options,
console: console,
agentFactory: agentFactory,
global: global,
featuresManager: featuresManager,
userConfigManager: userConfigManager,
}
}

func (e *ErrorMiddleware) Run(ctx context.Context, next NextFn) (*actions.ActionResult, error) {
actionResult, err := next(ctx)
if !e.featuresManager.IsEnabled(llm.FeatureLlm) {
return actionResult, err
}

// Stop the spinner always to un-hide cursor
e.console.StopSpinner(ctx, "", input.Step)
if err == nil || e.options.IsChildAction(ctx) {
return actionResult, err
}

// Error already has a suggestion, no need for AI
var suggestionErr *internal.ErrorWithSuggestion
if errors.As(err, &suggestionErr) {
e.console.Message(ctx, suggestionErr.Suggestion)
return actionResult, err
}

// Skip certain errors, no need for AI
skipAnalyzingErrors := []string{
"environment already initialized",
"interrupt",
"no project exists",
}
for _, s := range skipAnalyzingErrors {
if strings.Contains(err.Error(), s) {
return actionResult, err
}
}

// Warn user that this is an alpha feature
e.console.WarnForFeature(ctx, llm.FeatureLlm)

originalError := err
azdAgent, err := e.agentFactory.Create(
agent.WithDebug(e.global.EnableDebugLogging),
agent.WithFileWatching(true),
)
if err != nil {
return nil, err
}

defer azdAgent.Stop()

attempt := 0
var previousError error
var errorWithTraceId *internal.ErrorWithTraceId
AIDisclaimer := output.WithGrayFormat("The following content is AI-generated. AI responses may be incorrect.")
agentName := "agent mode"

for {
if originalError == nil {
break
}

e.console.Message(ctx, output.WithErrorFormat("\nERROR: %s", originalError.Error()))

if previousError != nil && errors.Is(originalError, previousError) {
attempt++
if attempt >= 3 {
e.console.Message(ctx, fmt.Sprintf("Please review the error and fix it manually, "+
"%s was unable to resolve the error after multiple attempts.", agentName))
return actionResult, originalError
}
}

if errors.As(originalError, &errorWithTraceId) {
e.console.Message(ctx, output.WithErrorFormat("TraceID: %s", errorWithTraceId.TraceId))
}

errorInput := originalError.Error()

e.console.Message(ctx, "")
confirm, err := e.checkErrorHandlingConsent(
ctx,
"mcp.errorHandling.troubleshooting",
fmt.Sprintf("Generate troubleshooting steps using %s?", agentName),
fmt.Sprintf("This action will run AI tools to generate troubleshooting steps."+
" Edit permissions for AI tools anytime by running %s.",
output.WithHighLightFormat("azd mcp consent")),
true,
)
if err != nil {
return nil, fmt.Errorf("prompting to provide troubleshooting steps: %w", err)
}

if confirm {
// Provide manual steps for troubleshooting
agentOutput, err := azdAgent.SendMessage(ctx, fmt.Sprintf(
`Steps to follow:
1. Use available tool including azd_error_troubleshooting tool to identify and explain the error.
Diagnose its root cause when running azd command.
2. Provide actionable troubleshooting steps. Do not perform any file changes.
Error details: %s`, errorInput))

if err != nil {
if agentOutput != "" {
e.console.Message(ctx, AIDisclaimer)
e.console.Message(ctx, output.WithMarkdown(agentOutput))
}

return nil, err
}

e.console.Message(ctx, AIDisclaimer)
e.console.Message(ctx, "")
e.console.Message(ctx, fmt.Sprintf("%s:", output.AzdAgentLabel()))
e.console.Message(ctx, output.WithMarkdown(agentOutput))
e.console.Message(ctx, "")
}

// Ask user if they want to let AI fix the
confirm, err = e.checkErrorHandlingConsent(
ctx,
"mcp.errorHandling.fix",
fmt.Sprintf("Fix this error using %s?", agentName),
fmt.Sprintf("This action will run AI tools to help fix the error."+
" Edit permissions for AI tools anytime by running %s.",
output.WithHighLightFormat("azd mcp consent")),
false,
)
if err != nil {
return nil, fmt.Errorf("prompting to fix error using %s: %w", agentName, err)
}

if !confirm {
return actionResult, err
}

previousError = originalError
agentOutput, err := azdAgent.SendMessage(ctx, fmt.Sprintf(
`Steps to follow:
1. Use available tool to identify, explain and diagnose this error when running azd command and its root cause.
2. Resolve the error by making the minimal, targeted change required to the code or configuration.
Avoid unnecessary modifications and focus only on what is essential to restore correct functionality.
3. Remove any changes that were created solely for validation and are not part of the actual error fix.
Error details: %s`, errorInput))

if err != nil {
if agentOutput != "" {
e.console.Message(ctx, AIDisclaimer)
e.console.Message(ctx, output.WithMarkdown(agentOutput))
}

return nil, err
}

// Ask the user to add feedback
if err := e.collectAndApplyFeedback(ctx, azdAgent, AIDisclaimer); err != nil {
return nil, err
}

// Clear check cache to prevent skip of tool related error
ctx = tools.WithInstalledCheckCache(ctx)

actionResult, err = next(ctx)
originalError = err
}

return actionResult, err
}

// collectAndApplyFeedback prompts for user feedback and applies it using the agent
func (e *ErrorMiddleware) collectAndApplyFeedback(
ctx context.Context,
azdAgent agent.Agent,
AIDisclaimer string,
) error {
userInputPrompt := uxlib.NewPrompt(&uxlib.PromptOptions{
Message: "Any changes you'd like to make?",
Hint: "Describe your changes or press enter to skip.",
Required: false,
})

userInput, err := userInputPrompt.Ask(ctx)
if err != nil {
return fmt.Errorf("failed to collect feedback for user input: %w", err)
}

if userInput == "" {
e.console.Message(ctx, "")
return nil
}

e.console.Message(ctx, "")
e.console.Message(ctx, color.MagentaString("Feedback"))

feedbackOutput, err := azdAgent.SendMessage(ctx, userInput)
if err != nil {
if feedbackOutput != "" {
e.console.Message(ctx, AIDisclaimer)
e.console.Message(ctx, output.WithMarkdown(feedbackOutput))
}
return err
}

e.console.Message(ctx, AIDisclaimer)
e.console.Message(ctx, "")
e.console.Message(ctx, fmt.Sprintf("%s:", output.AzdAgentLabel()))
e.console.Message(ctx, output.WithMarkdown(feedbackOutput))
e.console.Message(ctx, "")

return nil
}

func (e *ErrorMiddleware) checkErrorHandlingConsent(
ctx context.Context,
promptName string,
message string,
helpMessage string,
skip bool,
) (bool, error) {
userConfig, err := e.userConfigManager.Load()
if err != nil {
return false, fmt.Errorf("failed to load user config: %w", err)
}

if exists, ok := userConfig.GetString(promptName); !ok && exists == "" {
choice, err := promptForErrorHandlingConsent(ctx, message, helpMessage, skip)
if err != nil {
return false, fmt.Errorf("prompting for error handling consent: %w", err)
}

if choice == "skip" || choice == "deny" {
return false, nil
}

if choice == "always" {
if err := userConfig.Set(promptName, "allow"); err != nil {
return false, fmt.Errorf("failed to set consent config: %w", err)
}

if err := e.userConfigManager.Save(userConfig); err != nil {
return false, err
}
}
}

return true, nil
}

func promptForErrorHandlingConsent(
ctx context.Context,
message string,
helpMessage string,
skip bool,
) (string, error) {
choices := []*uxlib.SelectChoice{
{
Value: "once",
Label: "Yes, allow once",
},
{
Value: "always",
Label: "Yes, allow always",
},
}

if skip {
choices = append(choices, &uxlib.SelectChoice{
Value: "skip",
Label: "No, skip to next step",
})
} else {
choices = append(choices, &uxlib.SelectChoice{
Value: "deny",
Label: "No, cancel this interaction (esc)",
})
}

selector := uxlib.NewSelect(&uxlib.SelectOptions{
Message: message,
HelpMessage: helpMessage,
Choices: choices,
EnableFiltering: uxlib.Ptr(false),
DisplayCount: 5,
})

choiceIndex, err := selector.Ask(ctx)
if err != nil {
return "", err
}

if choiceIndex == nil || *choiceIndex < 0 || *choiceIndex >= len(choices) {
return "", fmt.Errorf("invalid choice selected")
}

return choices[*choiceIndex].Value, nil
}
16 changes: 10 additions & 6 deletions cli/azd/cmd/middleware/ux.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,24 @@ import (

"github.com/azure/azure-dev/cli/azd/cmd/actions"
"github.com/azure/azure-dev/cli/azd/internal"
"github.com/azure/azure-dev/cli/azd/pkg/alpha"
"github.com/azure/azure-dev/cli/azd/pkg/input"
"github.com/azure/azure-dev/cli/azd/pkg/llm"
"github.com/azure/azure-dev/cli/azd/pkg/output"
"github.com/azure/azure-dev/cli/azd/pkg/output/ux"
)

type UxMiddleware struct {
options *Options
console input.Console
options *Options
console input.Console
featuresManager *alpha.FeatureManager
}

func NewUxMiddleware(options *Options, console input.Console) Middleware {
func NewUxMiddleware(options *Options, console input.Console, featuresManager *alpha.FeatureManager) Middleware {
return &UxMiddleware{
options: options,
console: console,
options: options,
console: console,
featuresManager: featuresManager,
}
}

Expand All @@ -37,7 +41,7 @@ func (m *UxMiddleware) Run(ctx context.Context, next NextFn) (*actions.ActionRes
// Stop the spinner always to un-hide cursor
m.console.StopSpinner(ctx, "", input.Step)

if err != nil {
if err != nil && !m.featuresManager.IsEnabled(llm.FeatureLlm) {
var suggestionErr *internal.ErrorWithSuggestion
var errorWithTraceId *internal.ErrorWithTraceId
m.console.Message(ctx, output.WithErrorFormat("\nERROR: %s", err.Error()))
Expand Down
1 change: 1 addition & 0 deletions cli/azd/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,7 @@ func NewRootCmd(
root.
UseMiddleware("debug", middleware.NewDebugMiddleware).
UseMiddleware("ux", middleware.NewUxMiddleware).
UseMiddleware("error", middleware.NewErrorMiddleware).
UseMiddlewareWhen("telemetry", middleware.NewTelemetryMiddleware, func(descriptor *actions.ActionDescriptor) bool {
return !descriptor.Options.DisableTelemetry
}).
Expand Down
Loading
Loading