Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
101 changes: 28 additions & 73 deletions cli/azd/extensions/azure.ai.agents/internal/cmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -1309,95 +1309,50 @@ from code-deploy ZIP packaging (uses .gitignore syntax).`,
}

switch selectedTemplate.EffectiveType() {
case TemplateTypeAzd:
// Full azd template - dispatch azd init -t <repo>
// Create project in a new subdirectory derived from the template title.
folderName := folderNameStrippingParenSuffix(selectedTemplate.Title)
// Check whether the target directory already exists so we
// only report "created" when a new directory was made.
_, statErr := os.Stat(folderName)
newlyCreated := errors.Is(statErr, fs.ErrNotExist)
initArgs := []string{"init", "-t", selectedTemplate.Source, folderName}
if flags.env != "" {
initArgs = append(initArgs, "--environment", flags.env)
} else {
base := sanitizeAgentName(folderName)
if len(base) > 59 {
base = strings.TrimRight(base[:59], "-")
}
defaultEnvName := base + "-dev"
initArgs = append(
initArgs, "--environment", defaultEnvName,
)
}

workflow := &azdext.Workflow{
Name: "init",
Steps: []*azdext.WorkflowStep{
{Command: &azdext.WorkflowCommand{Args: initArgs}},
},
}

_, err := azdClient.Workflow().Run(ctx, &azdext.RunWorkflowRequest{
Workflow: workflow,
})
if err != nil {
if exterrors.IsCancellation(err) {
return exterrors.Cancelled("initialization was cancelled")
}
case TemplateTypeAzureYaml:
Comment thread
huimiu marked this conversation as resolved.
// Unified azure.yaml template — download and adopt via
// the Foundry adoption flow (not git clone).
flags.manifestPointer = selectedTemplate.Source
content, ok := readManifestContentForInitDetection(
ctx, azdClient, flags.manifestPointer, httpClient,
)
if !ok {
return exterrors.Dependency(
exterrors.CodeProjectInitFailed,
fmt.Sprintf(
"failed to initialize project from template: %s", err,
"failed to download template source: %s",
selectedTemplate.Source,
),
"",
)
}

fmt.Printf(
"\nProject initialized from template: %s\n",
selectedTemplate.Title,
)

// Sync the extension process into the new project directory.
// The azd host already chdir'd when it processed the init command.
if err := os.Chdir(folderName); err != nil {
return fmt.Errorf(
"changing to project directory %q: %w",
folderName, err,
)
}
// Compute display path for created folder (used in nextstep).
// Only show cd hint for brand-new projects, not when adding
// a template subfolder to an existing project.
var folderDisplay string
if newlyCreated && !existingProject {
folderDisplay = filepath.ToSlash(folderName)
// Resolve the agent name BEFORE creating the project
// folder so the folder and agent identity use the same
// name. Use the azure.yaml project name as the default,
// falling back to the template title.
defaultName := foundryProjectName(content)
if defaultName == "" {
defaultName = folderNameStrippingParenSuffix(selectedTemplate.Title)
}

// Search for an agent manifest in the scaffolded project
cwd, err := os.Getwd()
resolvedName, err := resolveInitAgentName(ctx, azdClient, flags, defaultName)
if err != nil {
return fmt.Errorf("getting current directory: %w", err)
if exterrors.IsCancellation(err) {
return exterrors.Cancelled("initialization was cancelled")
}
return err
}

manifestPath, err := findAgentManifest(cwd)
if err != nil {
return fmt.Errorf("searching for agent manifest: %w", err)
if flags.src == "" && resolvedName != "" {
flags.src = sanitizeAgentName(resolvedName)
}

if manifestPath != "" {
flags.manifestPointer = manifestPath
if err := runInitFromManifest(
ctx, flags, azdClient, httpClient, ".", folderDisplay, true,
); err != nil {
if exterrors.IsCancellation(err) {
return exterrors.Cancelled("initialization was cancelled")
}
return err
if err := runInitFromAzureYaml(ctx, flags, azdClient, httpClient, content); err != nil {
if exterrors.IsCancellation(err) {
return exterrors.Cancelled("initialization was cancelled")
}
} else {
fmt.Println("No agent manifest found in the scaffolded project.")
return err
}

default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ const (
// TemplateTypeAzd is a full azd template repository.
TemplateTypeAzd = "azd"

// TemplateTypeAzureYaml is a unified azure.yaml template adopted via the Foundry flow.
TemplateTypeAzureYaml = "azure.yaml"

// templateTypeExtensionAIAgent is the discriminator value in the unified
// awesome-azd templates.json manifest that identifies an agent-init
// template. Entries with any other (or empty) templateType belong to the
Expand Down Expand Up @@ -66,8 +69,11 @@ type AgentTemplate struct {
TemplateType string `json:"templateType"`
}

// EffectiveType determines the template type by inspecting the source URL.
// EffectiveType determines the template type by inspecting the source URL
// and the template's declared templateType.
// If it ends with agent.yaml or agent.manifest.yaml, it's an agent manifest.
// If it ends with azure.yaml or azure.yml AND templateType is "extension.ai.agent",
// it's a unified azure.yaml template.
// Otherwise, it's treated as a full azd template repo.
func (t *AgentTemplate) EffectiveType() string {
lower := strings.ToLower(t.Source)
Expand All @@ -77,6 +83,13 @@ func (t *AgentTemplate) EffectiveType() string {
lower == "agent.manifest.yaml" {
return TemplateTypeAgent
}
if t.TemplateType == templateTypeExtensionAIAgent &&
(strings.HasSuffix(lower, "/azure.yaml") ||
strings.HasSuffix(lower, "/azure.yml") ||
lower == "azure.yaml" ||
lower == "azure.yml") {
return TemplateTypeAzureYaml
}
return TemplateTypeAzd
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ func TestEffectiveType(t *testing.T) {
t.Parallel()

tests := []struct {
name string
source string
expected string
name string
source string
templateType string
expected string
}{
{
name: "agent.yaml suffix",
Expand Down Expand Up @@ -72,12 +73,42 @@ func TestEffectiveType(t *testing.T) {
source: "https://github.com/org/repo/blob/main/config.yaml",
expected: TemplateTypeAzd,
},
{
name: "azure.yaml with extension.ai.agent templateType",
source: "https://github.com/org/repo/blob/main/samples/basic/azure.yaml",
templateType: "extension.ai.agent",
expected: TemplateTypeAzureYaml,
},
{
name: "azure.yml with extension.ai.agent templateType",
source: "https://github.com/org/repo/blob/main/samples/basic/azure.yml",
templateType: "extension.ai.agent",
expected: TemplateTypeAzureYaml,
},
{
name: "bare azure.yaml with extension.ai.agent templateType",
source: "azure.yaml",
templateType: "extension.ai.agent",
expected: TemplateTypeAzureYaml,
},
{
name: "azure.yaml without extension.ai.agent templateType falls back to azd",
source: "https://github.com/org/repo/blob/main/samples/basic/azure.yaml",
templateType: "",
expected: TemplateTypeAzd,
},
{
name: "azure.yaml with different templateType falls back to azd",
source: "https://github.com/org/repo/blob/main/samples/basic/azure.yaml",
templateType: "extension.something.else",
expected: TemplateTypeAzd,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
template := &AgentTemplate{Source: tt.source}
template := &AgentTemplate{Source: tt.source, TemplateType: tt.templateType}
require.Equal(t, tt.expected, template.EffectiveType())
})
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ type sampleListResponse struct {
var knownSampleListLanguages = []string{"python", "dotnetCsharp"}

// Known template type filter values.
var knownSampleListTypes = []string{TemplateTypeAgent, TemplateTypeAzd}
var knownSampleListTypes = []string{TemplateTypeAgent, TemplateTypeAzd, TemplateTypeAzureYaml}
Comment thread
huimiu marked this conversation as resolved.

// SampleListAction owns the catalog-fetch + render side of `sample list`.
//
Expand Down Expand Up @@ -293,6 +293,9 @@ func mapAgentTemplateToDTO(t AgentTemplate) TemplateListItem {
case TemplateTypeAgent:
item.ManifestURL = t.Source
item.InitCommand = fmt.Sprintf("azd ai agent init -m %q", t.Source)
case TemplateTypeAzureYaml:
item.ManifestURL = t.Source
item.InitCommand = fmt.Sprintf("azd ai agent init -m %q", t.Source)
Comment thread
huimiu marked this conversation as resolved.
case TemplateTypeAzd:
item.RepoURL = t.Source
item.InitCommand = fmt.Sprintf("azd init -t %q", t.Source)
Expand Down
Loading