Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
2 changes: 1 addition & 1 deletion cli/azd/extensions/azure.ai.agents/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ require (
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/cognitiveservices/armcognitiveservices v1.8.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armsubscriptions v1.3.0
github.com/azure/azure-dev/cli/azd v0.0.0-20251212003342-848978091314
github.com/azure/azure-dev/cli/azd v0.0.0-20260109002911-7e0ee49fe5ac
github.com/braydonk/yaml v0.9.0
github.com/drone/envsubst v1.0.3
github.com/fatih/color v1.18.0
Expand Down
4 changes: 2 additions & 2 deletions cli/azd/extensions/azure.ai.agents/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWp
github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/azure/azure-dev/cli/azd v0.0.0-20251212003342-848978091314 h1:2COt/tcJlZauO+Vd47SGD//isdVqSj2K1DhMfa3J3Vo=
github.com/azure/azure-dev/cli/azd v0.0.0-20251212003342-848978091314/go.mod h1:9+M/plQRg5MGyLdTOm8MMxgKohlUdBF04pzZrIugmPs=
github.com/azure/azure-dev/cli/azd v0.0.0-20260109002911-7e0ee49fe5ac h1:ow5lGEF+3dB4Jps7EVWvQKb46/sXK4GzDexumF1bjuI=
github.com/azure/azure-dev/cli/azd v0.0.0-20260109002911-7e0ee49fe5ac/go.mod h1:j+bdvNwQPdYtSfFe/xbfWqYr8Guw9hiP1JOVpIBERj0=
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o=
Expand Down
143 changes: 61 additions & 82 deletions cli/azd/extensions/azure.ai.agents/internal/cmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,6 @@ func newInitCommand(rootFlags rootFlagsDefinition) *cobra.Command {
return fmt.Errorf("failed to ground into a project context: %w", err)
}

// getComposedResourcesResponse, err := azdClient.Compose().ListResources(ctx, &azdext.EmptyRequest{})
// if err != nil {
// return fmt.Errorf("failed to get composed resources: %w", err)
// }

credential, err := azidentity.NewAzureDeveloperCLICredential(&azidentity.AzureDeveloperCLICredentialOptions{
TenantID: azureContext.Scope.TenantId,
AdditionallyAllowedTenants: []string{"*"},
Expand Down Expand Up @@ -893,24 +888,32 @@ func (a *InitAction) downloadAgentYaml(
name = ""
}

// Check if the manifest file is under current directory + "src"
currentDir, _ := os.Getwd()
srcDir := filepath.Join(currentDir, "src", name)
absManifestPath, _ := filepath.Abs(manifestPointer)

// Check if manifest is under src directory
if strings.HasPrefix(absManifestPath, srcDir) {
confirmResponse, err := a.azdClient.Prompt().Confirm(ctx, &azdext.ConfirmRequest{
Options: &azdext.ConfirmOptions{
Message: "This operation will overwrite the provided manifest file. Do you want to continue?",
DefaultValue: to.Ptr(false),
},
})
if name != "" {
// Check if the manifest file is under current directory + "src/<name>"
currentDir, err := os.Getwd()
if err != nil {
return nil, "", fmt.Errorf("prompting for confirmation: %w", err)
return nil, "", fmt.Errorf("getting current directory: %w", err)
}
if !*confirmResponse.Value {
return nil, "", fmt.Errorf("operation cancelled by user")
srcDir := filepath.Join(currentDir, "src", name)
absManifestPath, err := filepath.Abs(manifestPointer)
if err != nil {
return nil, "", fmt.Errorf("getting absolute path for manifest %s: %w", manifestPointer, err)
}

// Check if manifest is under src directory
if isSubpath(absManifestPath, srcDir) {
confirmResponse, err := a.azdClient.Prompt().Confirm(ctx, &azdext.ConfirmRequest{
Options: &azdext.ConfirmOptions{
Message: "This operation will overwrite the provided manifest file. Continue?",
DefaultValue: to.Ptr(false),
},
})
if err != nil {
return nil, "", fmt.Errorf("prompting for confirmation: %w", err)
}
if !*confirmResponse.Value {
return nil, "", fmt.Errorf("operation cancelled by user")
}
}
}
} else if a.isGitHubUrl(manifestPointer) {
Expand Down Expand Up @@ -1016,6 +1019,12 @@ func (a *InitAction) downloadAgentYaml(
return nil, "", fmt.Errorf("marshaling agent manifest to YAML: %w", err)
}
content = manifestBytes
} else {
// If we reach here, the manifest pointer didn't match any known type
return nil, "", fmt.Errorf(
"manifest pointer '%s' is not a valid local file path, GitHub URL, or registry URL",
manifestPointer,
)
}

// Parse and validate the YAML content against AgentManifest structure
Expand All @@ -1026,6 +1035,22 @@ func (a *InitAction) downloadAgentYaml(

fmt.Println("✓ YAML content successfully validated against AgentManifest format")

agentId := agentManifest.Name

// Use targetDir if provided, otherwise default to "src/{agentId}"
if targetDir == "" {
targetDir = filepath.Join("src", agentId)
}

// Safety checks for local container-based agents should happen before prompting for model SKU, etc.
if a.isLocalFilePath(manifestPointer) {
if _, isContainerAgent := agentManifest.Template.(agent_yaml.ContainerAgent); isContainerAgent {
if err := a.validateLocalContainerAgentCopy(ctx, manifestPointer, targetDir); err != nil {
return nil, "", err
}
}
}

agentManifest, err = registry_api.ProcessManifestParameters(ctx, agentManifest, a.azdClient, a.flags.NoPrompt)
if err != nil {
return nil, "", fmt.Errorf("failed to process manifest parameters: %w", err)
Expand All @@ -1046,13 +1071,6 @@ func (a *InitAction) downloadAgentYaml(
}
}

agentId := agentManifest.Name

// Use targetDir if provided or set to local file pointer, otherwise default to "src/{agentId}"
if targetDir == "" {
targetDir = filepath.Join("src", agentId)
}

// Create target directory if it doesn't exist
if err := os.MkdirAll(targetDir, 0755); err != nil {
return nil, "", fmt.Errorf("creating target directory %s: %w", targetDir, err)
Expand All @@ -1063,12 +1081,23 @@ func (a *InitAction) downloadAgentYaml(
_, isHostedContainer := agentManifest.Template.(agent_yaml.ContainerAgent)

if isHostedContainer {
// For container agents, copy the entire parent directory
fmt.Println("Copying full directory for container agent")
// For container agents, copy the entire parent directory.
// If the manifest already lives in the target directory (re-init), skip the copy.
manifestDir := filepath.Dir(manifestPointer)
err := copyDirectory(manifestDir, targetDir)
srcAbs, err := filepath.Abs(manifestDir)
if err != nil {
return nil, "", fmt.Errorf("resolving manifest directory %s: %w", manifestDir, err)
}
dstAbs, err := filepath.Abs(targetDir)
if err != nil {
return nil, "", fmt.Errorf("copying parent directory: %w", err)
return nil, "", fmt.Errorf("resolving target directory %s: %w", targetDir, err)
}
if !isSamePath(srcAbs, dstAbs) {
fmt.Println("Copying full directory for container agent")
err := copyDirectory(manifestDir, targetDir)
if err != nil {
return nil, "", fmt.Errorf("copying parent directory: %w", err)
}
}
}
} else if isGitHubUrl {
Expand Down Expand Up @@ -2019,53 +2048,3 @@ func (a *InitAction) ProcessModels(ctx context.Context, manifest *agent_yaml.Age

return updatedManifest, deploymentDetails, nil
}

// copyDirectory recursively copies all files and directories from src to dst
func copyDirectory(src, dst string) error {
return filepath.WalkDir(src, func(path string, d os.DirEntry, err error) error {
if err != nil {
return err
}

// Calculate the destination path
relPath, err := filepath.Rel(src, path)
if err != nil {
return err
}
dstPath := filepath.Join(dst, relPath)

if d.IsDir() {
// Create directory and continue processing its contents
return os.MkdirAll(dstPath, 0755)
} else {
// Copy file
return copyFile(path, dstPath)
}
})
}

// copyFile copies a single file from src to dst
func copyFile(src, dst string) error {
// Create the destination directory if it doesn't exist
if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
return err
}

// Open source file
srcFile, err := os.Open(src)
if err != nil {
return err
}
defer srcFile.Close()

// Create destination file
dstFile, err := os.Create(dst)
if err != nil {
return err
}
defer dstFile.Close()

// Copy file contents
_, err = srcFile.WriteTo(dstFile)
return err
}
Loading
Loading