Skip to content

Commit b3bb51b

Browse files
committed
fix files changed, address comment
1 parent 5f94059 commit b3bb51b

File tree

6 files changed

+238
-210
lines changed

6 files changed

+238
-210
lines changed

cli/azd/cmd/init.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -444,7 +444,7 @@ Do not stop until all tasks are complete and fully resolved.
444444
"Provide a very brief summary in markdown format that includes any files generated during this step.",
445445
}, "\n"))
446446

447-
agentOutput, err := azdAgent.SendMessage(ctx, nil, fullTaskInput)
447+
agentOutput, err := azdAgent.SendMessage(ctx, false, fullTaskInput)
448448
if err != nil {
449449
if agentOutput != "" {
450450
i.console.Message(ctx, output.WithMarkdown(agentOutput))
@@ -508,7 +508,7 @@ func (i *initAction) collectAndApplyFeedback(
508508
if userInput != "" {
509509
i.console.Message(ctx, color.MagentaString("Feedback"))
510510

511-
feedbackOutput, err := azdAgent.SendMessage(ctx, nil, userInput)
511+
feedbackOutput, err := azdAgent.SendMessage(ctx, false, userInput)
512512
if err != nil {
513513
if feedbackOutput != "" {
514514
i.console.Message(ctx, output.WithMarkdown(feedbackOutput))

cli/azd/cmd/middleware/error.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ func (e *ErrorMiddleware) Run(ctx context.Context, next NextFn) (*actions.Action
7373
"interrupt",
7474
"no project exists",
7575
}
76-
AIDisclaimer := output.WithHintFormat("The following content is AI-generated. AI responses may be incorrect.")
76+
AIDisclaimer := output.WithGrayFormat("The following content is AI-generated. AI responses may be incorrect.")
7777
agentName := "agent mode"
7878

7979
// Warn user that this is an alpha feature
@@ -113,7 +113,6 @@ func (e *ErrorMiddleware) Run(ctx context.Context, next NextFn) (*actions.Action
113113
if errors.As(originalError, &errorWithTraceId) {
114114
e.console.Message(ctx, output.WithErrorFormat("TraceID: %s", errorWithTraceId.TraceId))
115115
}
116-
117116
if errors.As(originalError, &suggestionErr) {
118117
suggestion = suggestionErr.Suggestion
119118
e.console.Message(ctx, suggestion)
@@ -134,7 +133,7 @@ func (e *ErrorMiddleware) Run(ctx context.Context, next NextFn) (*actions.Action
134133

135134
if confirm {
136135
// Provide manual steps for troubleshooting
137-
agentOutput, err := azdAgent.SendMessage(ctx, e.console, fmt.Sprintf(
136+
agentOutput, err := azdAgent.SendMessage(ctx, true, fmt.Sprintf(
138137
`Steps to follow:
139138
1. Use available tool to identify, explain and diagnose this error when running azd command and its root cause.
140139
2. Provide actionable troubleshooting steps. Do not perform any file changes.
@@ -172,7 +171,7 @@ func (e *ErrorMiddleware) Run(ctx context.Context, next NextFn) (*actions.Action
172171
}
173172

174173
previousError = originalError
175-
agentOutput, err := azdAgent.SendMessage(ctx, e.console, fmt.Sprintf(
174+
agentOutput, err := azdAgent.SendMessage(ctx, true, fmt.Sprintf(
176175
`Steps to follow:
177176
1. Use available tool to identify, explain and diagnose this error when running azd command and its root cause.
178177
2. Resolve the error by making the minimal, targeted change required to the code or configuration.
@@ -234,7 +233,7 @@ func (e *ErrorMiddleware) collectAndApplyFeedback(
234233
e.console.Message(ctx, "")
235234
e.console.Message(ctx, color.MagentaString("Feedback"))
236235

237-
feedbackOutput, err := azdAgent.SendMessage(ctx, e.console, userInput)
236+
feedbackOutput, err := azdAgent.SendMessage(ctx, true, userInput)
238237
if err != nil {
239238
if feedbackOutput != "" {
240239
e.console.Message(ctx, AIDisclaimer)

cli/azd/internal/agent/agent.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010

1111
"github.com/azure/azure-dev/cli/azd/internal/agent/logging"
1212
"github.com/azure/azure-dev/cli/azd/internal/agent/tools/common"
13-
"github.com/azure/azure-dev/cli/azd/pkg/input"
1413
"github.com/tmc/langchaingo/agents"
1514
"github.com/tmc/langchaingo/callbacks"
1615
"github.com/tmc/langchaingo/llms"
@@ -35,7 +34,7 @@ type AgentCleanup func() error
3534
// Agent represents an AI agent that can execute tools and interact with language models.
3635
type Agent interface {
3736
// SendMessage sends a message to the agent and returns the response
38-
SendMessage(ctx context.Context, console input.Console, args ...string) (string, error)
37+
SendMessage(ctx context.Context, useWatch bool, args ...string) (string, error)
3938

4039
// Stop terminates the agent and performs any necessary cleanup
4140
Stop() error

cli/azd/internal/agent/conversational_agent.go

Lines changed: 14 additions & 201 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,12 @@ import (
77
"context"
88
_ "embed"
99
"fmt"
10-
"log"
11-
"os"
12-
"path/filepath"
1310
"strings"
1411
"sync"
15-
"time"
1612

1713
"github.com/azure/azure-dev/cli/azd/internal/agent/tools/common"
18-
"github.com/azure/azure-dev/cli/azd/pkg/input"
19-
"github.com/azure/azure-dev/cli/azd/pkg/output"
20-
uxlib "github.com/azure/azure-dev/cli/azd/pkg/ux"
21-
"github.com/fatih/color"
14+
"github.com/azure/azure-dev/cli/azd/internal/agent/tools/ux"
15+
"github.com/azure/azure-dev/cli/azd/pkg/watch"
2216
"github.com/fsnotify/fsnotify"
2317
"github.com/tmc/langchaingo/agents"
2418
"github.com/tmc/langchaingo/chains"
@@ -94,225 +88,44 @@ func NewConversationalAzdAiAgent(llm llms.Model, opts ...AgentCreateOption) (Age
9488
return azdAgent, nil
9589
}
9690

97-
type FileChanges struct {
98-
Created map[string]bool
99-
Modified map[string]bool
100-
Deleted map[string]bool
101-
}
102-
10391
// SendMessage processes a single message through the agent and returns the response
104-
func (aai *ConversationalAzdAiAgent) SendMessage(ctx context.Context, console input.Console, args ...string) (string, error) {
92+
func (aai *ConversationalAzdAiAgent) SendMessage(ctx context.Context, useWatch bool, args ...string) (string, error) {
10593
thoughtsCtx, cancelCtx := context.WithCancel(ctx)
94+
10695
var watcher *fsnotify.Watcher
10796
var done chan bool
10897
var mu sync.Mutex
109-
var err error
110-
fileChanges := &FileChanges{
111-
Created: make(map[string]bool),
112-
Modified: make(map[string]bool),
113-
Deleted: make(map[string]bool),
114-
}
98+
var fileChanges *watch.FileChanges
11599

116-
if console != nil {
117-
watcher, done, err = startWatcher(ctx, fileChanges, &mu)
100+
if useWatch {
101+
var err error
102+
watcher, done, fileChanges, err = watch.StartWatcher(ctx, &mu)
118103
if err != nil {
119104
return "", fmt.Errorf("failed to start watcher: %w", err)
120105
}
121106
}
122107

123-
cleanup, err := aai.renderThoughts(thoughtsCtx)
108+
cleanup, err := ux.RenderThoughts(thoughtsCtx, aai.thoughtChan)
124109
if err != nil {
125110
cancelCtx()
126111
return "", err
127112
}
128113

129114
defer func() {
130-
if console != nil {
115+
cleanup()
116+
cancelCtx()
117+
118+
if useWatch {
119+
watch.PrintChangedFiles(ctx, fileChanges, &mu)
131120
close(done)
132121
watcher.Close()
133122
}
134-
cleanup()
135-
cancelCtx()
136123
}()
137124

138125
output, err := chains.Run(ctx, aai.executor, strings.Join(args, "\n"))
139126
if err != nil {
140127
return "", err
141128
}
142129

143-
if console != nil {
144-
printChangedFiles(ctx, console, fileChanges, &mu)
145-
}
146-
147130
return output, nil
148131
}
149-
150-
func (aai *ConversationalAzdAiAgent) renderThoughts(ctx context.Context) (func(), error) {
151-
var latestThought string
152-
153-
spinner := uxlib.NewSpinner(&uxlib.SpinnerOptions{
154-
Text: "Processing...",
155-
})
156-
157-
canvas := uxlib.NewCanvas(
158-
spinner,
159-
uxlib.NewVisualElement(func(printer uxlib.Printer) error {
160-
printer.Fprintln()
161-
printer.Fprintln()
162-
163-
if latestThought != "" {
164-
printer.Fprintln(color.HiBlackString(latestThought))
165-
printer.Fprintln()
166-
printer.Fprintln()
167-
}
168-
169-
return nil
170-
}))
171-
172-
go func() {
173-
defer canvas.Clear()
174-
175-
var latestAction string
176-
var latestActionInput string
177-
var spinnerText string
178-
179-
for {
180-
181-
select {
182-
case thought := <-aai.thoughtChan:
183-
if thought.Action != "" {
184-
latestAction = thought.Action
185-
latestActionInput = thought.ActionInput
186-
}
187-
if thought.Thought != "" {
188-
latestThought = thought.Thought
189-
}
190-
case <-ctx.Done():
191-
return
192-
case <-time.After(200 * time.Millisecond):
193-
}
194-
195-
// Update spinner text
196-
if latestAction == "" {
197-
spinnerText = "Processing..."
198-
} else {
199-
spinnerText = fmt.Sprintf("Running %s tool", color.BlueString(latestAction))
200-
if latestActionInput != "" {
201-
spinnerText += " with " + color.BlueString(latestActionInput)
202-
}
203-
204-
spinnerText += "..."
205-
}
206-
207-
spinner.UpdateText(spinnerText)
208-
canvas.Update()
209-
}
210-
}()
211-
212-
cleanup := func() {
213-
canvas.Clear()
214-
canvas.Close()
215-
}
216-
217-
return cleanup, canvas.Run()
218-
}
219-
220-
func startWatcher(ctx context.Context, fileChanges *FileChanges, mu *sync.Mutex) (*fsnotify.Watcher, chan bool, error) {
221-
watcher, err := fsnotify.NewWatcher()
222-
if err != nil {
223-
return nil, nil, fmt.Errorf("failed to create watcher: %w", err)
224-
}
225-
226-
done := make(chan bool)
227-
228-
go func() {
229-
for {
230-
select {
231-
case event := <-watcher.Events:
232-
mu.Lock()
233-
name := event.Name
234-
switch {
235-
case event.Has(fsnotify.Create):
236-
fileChanges.Created[name] = true
237-
case event.Has(fsnotify.Write) || event.Has(fsnotify.Rename):
238-
if !fileChanges.Created[name] && !fileChanges.Deleted[name] {
239-
fileChanges.Modified[name] = true
240-
}
241-
case event.Has(fsnotify.Remove):
242-
if fileChanges.Created[name] {
243-
delete(fileChanges.Created, name)
244-
} else {
245-
fileChanges.Deleted[name] = true
246-
delete(fileChanges.Modified, name)
247-
}
248-
}
249-
mu.Unlock()
250-
case err := <-watcher.Errors:
251-
log.Printf("watcher error: %v", err)
252-
case <-done:
253-
return
254-
case <-ctx.Done():
255-
return
256-
}
257-
}
258-
}()
259-
260-
cwd, err := os.Getwd()
261-
if err != nil {
262-
return nil, nil, fmt.Errorf("failed to get current working directory: %w", err)
263-
}
264-
265-
if err := watchRecursive(cwd, watcher); err != nil {
266-
return nil, nil, fmt.Errorf("watcher failed: %w", err)
267-
}
268-
269-
return watcher, done, nil
270-
}
271-
272-
func watchRecursive(root string, watcher *fsnotify.Watcher) error {
273-
return filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
274-
if err != nil {
275-
return err
276-
}
277-
if info.IsDir() {
278-
err = watcher.Add(path)
279-
if err != nil {
280-
return fmt.Errorf("failed to watch directory %s: %w", path, err)
281-
}
282-
}
283-
284-
return nil
285-
})
286-
}
287-
288-
func printChangedFiles(ctx context.Context, console input.Console, fileChanges *FileChanges, mu *sync.Mutex) {
289-
mu.Lock()
290-
defer mu.Unlock()
291-
createdFileLength := len(fileChanges.Created)
292-
modifiedFileLength := len(fileChanges.Modified)
293-
deletedFileLength := len(fileChanges.Deleted)
294-
295-
if createdFileLength == 0 && modifiedFileLength == 0 && deletedFileLength == 0 {
296-
return
297-
}
298-
299-
console.Message(ctx, output.WithHintFormat("| Files changed:"))
300-
301-
if createdFileLength > 0 {
302-
for file := range fileChanges.Created {
303-
console.Message(ctx, fmt.Sprintf("%s %s %s", output.WithHintFormat("|"), color.GreenString("+ Created"), file))
304-
}
305-
}
306-
307-
if modifiedFileLength > 0 {
308-
for file := range fileChanges.Modified {
309-
console.Message(ctx, fmt.Sprintf("%s %s %s", output.WithHintFormat("|"), color.YellowString("+/- Modified"), file))
310-
}
311-
}
312-
313-
if deletedFileLength > 0 {
314-
for file := range fileChanges.Deleted {
315-
console.Message(ctx, fmt.Sprintf("%s %s %s", output.WithHintFormat("|"), color.RedString("- Deleted"), file))
316-
}
317-
}
318-
}

0 commit comments

Comments
 (0)