From 79f9c984a6bf4ccd481b0a3973f137448a9873c9 Mon Sep 17 00:00:00 2001 From: trangevi Date: Thu, 2 Jul 2026 16:39:31 -0700 Subject: [PATCH] feat: route azure.yaml templates through Foundry adoption flow Add TemplateTypeAzureYaml constant for unified azure.yaml templates that carry templateType=extension.ai.agent. Update EffectiveType() to detect these by checking both the source URL suffix and templateType. Replace the TemplateTypeAzd init case (which attempted git clone) with the Foundry adopt flow: download the azure.yaml content, resolve the agent name interactively, derive the project folder, and call runInitFromAzureYaml. Add TemplateTypeAzureYaml to sample_list known types and DTO mapping. Update unit tests with new EffectiveType cases. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../azure.ai.agents/internal/cmd/init.go | 101 +++++------------- .../cmd/init_from_templates_helpers.go | 15 ++- .../cmd/init_from_templates_helpers_test.go | 39 ++++++- .../internal/cmd/sample_list.go | 5 +- 4 files changed, 81 insertions(+), 79 deletions(-) diff --git a/cli/azd/extensions/azure.ai.agents/internal/cmd/init.go b/cli/azd/extensions/azure.ai.agents/internal/cmd/init.go index 4cfbfa9d06c..82cab43c338 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/cmd/init.go +++ b/cli/azd/extensions/azure.ai.agents/internal/cmd/init.go @@ -1309,95 +1309,50 @@ from code-deploy ZIP packaging (uses .gitignore syntax).`, } switch selectedTemplate.EffectiveType() { - case TemplateTypeAzd: - // Full azd template - dispatch azd init -t - // 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: + // 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: diff --git a/cli/azd/extensions/azure.ai.agents/internal/cmd/init_from_templates_helpers.go b/cli/azd/extensions/azure.ai.agents/internal/cmd/init_from_templates_helpers.go index 5ca25256dea..ce7712b4f1f 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/cmd/init_from_templates_helpers.go +++ b/cli/azd/extensions/azure.ai.agents/internal/cmd/init_from_templates_helpers.go @@ -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 @@ -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) @@ -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 } diff --git a/cli/azd/extensions/azure.ai.agents/internal/cmd/init_from_templates_helpers_test.go b/cli/azd/extensions/azure.ai.agents/internal/cmd/init_from_templates_helpers_test.go index d2a5ffd4a90..7c7faf0b43f 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/cmd/init_from_templates_helpers_test.go +++ b/cli/azd/extensions/azure.ai.agents/internal/cmd/init_from_templates_helpers_test.go @@ -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", @@ -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()) }) } diff --git a/cli/azd/extensions/azure.ai.agents/internal/cmd/sample_list.go b/cli/azd/extensions/azure.ai.agents/internal/cmd/sample_list.go index 229dd4ff012..93799ab50af 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/cmd/sample_list.go +++ b/cli/azd/extensions/azure.ai.agents/internal/cmd/sample_list.go @@ -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} // SampleListAction owns the catalog-fetch + render side of `sample list`. // @@ -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) case TemplateTypeAzd: item.RepoURL = t.Source item.InitCommand = fmt.Sprintf("azd init -t %q", t.Source)