Skip to content
Draft
Show file tree
Hide file tree
Changes from 2 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
16 changes: 16 additions & 0 deletions internal/engine/common/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package common

import (
"fmt"
"strings"

"github.com/Azure/InnovationEngine/internal/engine/environments"
"github.com/Azure/InnovationEngine/internal/logging"
Expand Down Expand Up @@ -42,12 +43,27 @@ func ExecuteCodeBlockAsync(codeBlock parsers.CodeBlock, env map[string]string) t
logging.GlobalLogger.Infof(
"Executing command asynchronously:\n %s", codeBlock.Content)

var accumulatedOutput strings.Builder

output, err := shells.ExecuteBashCommand(codeBlock.Content, shells.BashCommandConfiguration{
EnvironmentVariables: env,
InheritEnvironment: true,
InteractiveCommand: false,
WriteToHistory: true,
StreamOutput: true,
OutputCallback: func(output string, isStderr bool) {
// In the async case, just accumulate the output
accumulatedOutput.WriteString(output)
// Print in real-time for interactive experience
fmt.Print(output)
},
})

// Update output with accumulated content if needed
if output.StdOut == "" && accumulatedOutput.Len() > 0 {
output.StdOut = accumulatedOutput.String()
}

if err != nil {
logging.GlobalLogger.Errorf("Error executing command:\n %s", err.Error())
return FailedCommandMessage{
Expand Down
26 changes: 26 additions & 0 deletions internal/engine/execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,15 +152,41 @@ func (e *Engine) ExecuteAndRenderSteps(steps []common.Step, env map[string]strin
terminal.HideCursor()

go func(block parsers.CodeBlock) {
var accumulatedOutput strings.Builder

output, err := shells.ExecuteBashCommand(
block.Content,
shells.BashCommandConfiguration{
EnvironmentVariables: lib.CopyMap(env),
InheritEnvironment: true,
InteractiveCommand: false,
WriteToHistory: true,
StreamOutput: true,
OutputCallback: func(output string, isStderr bool) {
// Accumulate the output for final display
accumulatedOutput.WriteString(output)

// Clear current spinner line
fmt.Print("\r \r")

// Print stream output
if isStderr {
fmt.Print(ui.ErrorMessageStyle.Render(output))
} else {
fmt.Print(output)
}

// Restore spinner
fmt.Printf("\r %s", ui.SpinnerStyle.Render(string(spinnerFrames[frame])))
},
},
)

// Update commandOutput with the full output
if output.StdOut == "" && accumulatedOutput.Len() > 0 {
output.StdOut = accumulatedOutput.String()
}

logging.GlobalLogger.Infof("Command output to stdout:\n %s", output.StdOut)
logging.GlobalLogger.Infof("Command output to stderr:\n %s", output.StdErr)
commandOutput = output
Expand Down
30 changes: 30 additions & 0 deletions internal/shells/bash.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package shells
import (
"bytes"
"fmt"
"io"
"os"
"os/exec"
"strings"
Expand All @@ -12,6 +13,19 @@ import (
"github.com/Azure/InnovationEngine/internal/lib"
)

// streamWriter implements io.Writer to capture and forward command output in real-time
type streamWriter struct {
callback OutputCallback
isStderr bool
}

func (w *streamWriter) Write(p []byte) (n int, err error) {
if w.callback != nil {
w.callback(string(p), w.isStderr)
}
return len(p), nil
}

func appendToBashHistory(command string, filePath string) error {
file, err := os.OpenFile(filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600)
if err != nil {
Expand Down Expand Up @@ -40,11 +54,15 @@ type CommandOutput struct {
StdErr string
}

type OutputCallback func(string, bool)

type BashCommandConfiguration struct {
EnvironmentVariables map[string]string
InheritEnvironment bool
InteractiveCommand bool
WriteToHistory bool
StreamOutput bool
OutputCallback OutputCallback
}

var ExecuteBashCommand = executeBashCommandImpl
Expand Down Expand Up @@ -73,6 +91,18 @@ func executeBashCommandImpl(
commandToExecute.Stdout = os.Stdout
commandToExecute.Stderr = os.Stderr
commandToExecute.Stdin = os.Stdin
} else if config.StreamOutput && config.OutputCallback != nil {
// Create multi-writers to capture output both in buffer and stream it via callback
stdoutWriter := io.MultiWriter(&stdoutBuffer, &streamWriter{
callback: config.OutputCallback,
isStderr: false,
})
stderrWriter := io.MultiWriter(&stderrBuffer, &streamWriter{
callback: config.OutputCallback,
isStderr: true,
})
commandToExecute.Stdout = stdoutWriter
commandToExecute.Stderr = stderrWriter
} else {
commandToExecute.Stdout = &stdoutBuffer
commandToExecute.Stderr = &stderrBuffer
Expand Down
12 changes: 12 additions & 0 deletions test_streaming.sh
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file should be located in scenarios/testing

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've moved the test file to scenarios/testing as requested in commit 3d83840.

Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/bin/bash
echo "This is the first line"
sleep 1
echo "This is the second line"
sleep 1
echo "This is the third line"
sleep 1
echo "This is the fourth line"
sleep 1
echo "This is the fifth line"
sleep 1
echo "Done!"