From 06e32b3ea9d489da52339a0f323c258d137aa5c2 Mon Sep 17 00:00:00 2001 From: trangevi Date: Fri, 12 Dec 2025 07:54:10 -0800 Subject: [PATCH 1/2] Update to use url handling from extension framework Signed-off-by: trangevi --- cli/azd/extensions/azure.ai.agents/go.mod | 6 +- cli/azd/extensions/azure.ai.agents/go.sum | 4 +- .../azure.ai.agents/internal/cmd/init.go | 92 ++----------------- 3 files changed, 13 insertions(+), 89 deletions(-) diff --git a/cli/azd/extensions/azure.ai.agents/go.mod b/cli/azd/extensions/azure.ai.agents/go.mod index 78453895d53..137136e2d98 100644 --- a/cli/azd/extensions/azure.ai.agents/go.mod +++ b/cli/azd/extensions/azure.ai.agents/go.mod @@ -10,13 +10,14 @@ 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-20251121010829-d5e0a142e813 + github.com/azure/azure-dev/cli/azd v0.0.0-20251212003342-848978091314 github.com/braydonk/yaml v0.9.0 github.com/drone/envsubst v1.0.3 github.com/fatih/color v1.18.0 github.com/google/uuid v1.6.0 github.com/mark3labs/mcp-go v0.41.1 github.com/spf13/cobra v1.10.1 + github.com/spf13/pflag v1.0.10 go.yaml.in/yaml/v3 v3.0.4 google.golang.org/protobuf v1.36.10 gopkg.in/yaml.v3 v3.0.1 @@ -75,7 +76,6 @@ require ( github.com/rivo/uniseg v0.4.7 // indirect github.com/sethvargo/go-retry v0.3.0 // indirect github.com/spf13/cast v1.10.0 // indirect - github.com/spf13/pflag v1.0.10 // indirect github.com/stretchr/testify v1.11.1 // indirect github.com/theckman/yacspin v0.13.12 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect @@ -97,3 +97,5 @@ require ( google.golang.org/genproto/googleapis/rpc v0.0.0-20251007200510-49b9836ed3ff // indirect google.golang.org/grpc v1.76.0 // indirect ) + +replace github.com/azure/azure-dev/cli/azd => ../.. diff --git a/cli/azd/extensions/azure.ai.agents/go.sum b/cli/azd/extensions/azure.ai.agents/go.sum index deddeec82e7..5045ad2c862 100644 --- a/cli/azd/extensions/azure.ai.agents/go.sum +++ b/cli/azd/extensions/azure.ai.agents/go.sum @@ -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-20251121010829-d5e0a142e813 h1:6RgPxlo9PsEc4q/IDkompYhL7U0+XdW0V4iP+1tpoKc= -github.com/azure/azure-dev/cli/azd v0.0.0-20251121010829-d5e0a142e813/go.mod h1:k86H7K6vCw8UmimYs0/gDTilxQwXUZDaikRYfDweB/U= +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/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= 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 bc371fd39a9..0307d58e053 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/cmd/init.go +++ b/cli/azd/extensions/azure.ai.agents/internal/cmd/init.go @@ -68,14 +68,6 @@ type InitAction struct { deploymentDetails []project.Deployment } -// GitHubUrlInfo holds parsed information from a GitHub URL -type GitHubUrlInfo struct { - RepoSlug string - Branch string - FilePath string - Hostname string -} - const AiAgentHost = "azure.ai.agent" const ContainerAppHost = "containerapp" @@ -788,7 +780,7 @@ func (a *InitAction) downloadAgentYaml( var content []byte var err error var isGitHubUrl bool - var urlInfo *GitHubUrlInfo + var urlInfo *azdext.ParseGitHubUrlResponse var ghCli *github.Cli var console input.Console @@ -862,7 +854,10 @@ func (a *InitAction) downloadAgentYaml( return nil, "", fmt.Errorf("creating GitHub CLI: %w", err) } - urlInfo, err = parseGitHubUrl(manifestPointer) + // urlInfo, err = parseGitHubUrl(manifestPointer) + urlInfo, err = a.azdClient.Project().ParseGitHubUrl(ctx, &azdext.ParseGitHubUrlRequest{ + Url: manifestPointer, + }) if err != nil { return nil, "", err } @@ -1223,7 +1218,7 @@ func (a *InitAction) populateContainerSettings(ctx context.Context) (*project.Co } func downloadGithubManifest( - ctx context.Context, urlInfo *GitHubUrlInfo, apiPath string, ghCli *github.Cli, console input.Console) (string, error) { + ctx context.Context, urlInfo *azdext.ParseGitHubUrlResponse, apiPath string, ghCli *github.Cli, console input.Console) (string, error) { // manifestPointer validation: // - accepts only URLs with the following format: // - https://raw.///refs/heads///.json @@ -1257,81 +1252,8 @@ func downloadGithubManifest( return content, nil } -// parseGitHubUrl extracts repository information from various GitHub URL formats -// TODO: This will fail if the branch contains a slash. Update to handle that case if needed. -func parseGitHubUrl(manifestPointer string) (*GitHubUrlInfo, error) { - parsedURL, err := url.Parse(manifestPointer) - if err != nil { - return nil, fmt.Errorf("failed to parse URL: %w", err) - } - - hostname := parsedURL.Hostname() - var repoSlug, branch, filePath string - - if strings.HasPrefix(hostname, "raw.") { - // https://raw.githubusercontent.com///refs/heads//[...path]/.yaml - pathParts := strings.Split(parsedURL.Path, "/") - if len(pathParts) < 7 { - return nil, fmt.Errorf("invalid URL format using 'raw.'. Expected the form of " + - "'https://raw.///refs/heads//[...path]/.json'") - } - if pathParts[3] != "refs" || pathParts[4] != "heads" { - return nil, fmt.Errorf("invalid raw GitHub URL format. Expected 'refs/heads' in the URL path") - } - repoSlug = fmt.Sprintf("%s/%s", pathParts[1], pathParts[2]) - branch = pathParts[5] - filePath = strings.Join(pathParts[6:], "/") - } else if strings.HasPrefix(hostname, "api.") { - // https://api.github.com/repos///contents/[...path]/.yaml - pathParts := strings.Split(parsedURL.Path, "/") - if len(pathParts) < 6 { - return nil, fmt.Errorf("invalid URL format using 'api.'. Expected the form of " + - "'https://api./repos///contents/[...path]/.json[?ref=]'") - } - repoSlug = fmt.Sprintf("%s/%s", pathParts[2], pathParts[3]) - filePath = strings.Join(pathParts[5:], "/") - // For API URLs, branch is specified in the query parameter ref - branch = parsedURL.Query().Get("ref") - if branch == "" { - branch = "main" // default branch if not specified - } - } else if strings.HasPrefix(manifestPointer, "https://") { - // https://github.com///blob//[...path]/.yaml - pathParts := strings.Split(parsedURL.Path, "/") - if len(pathParts) < 6 { - return nil, fmt.Errorf("invalid URL format. Expected the form of " + - "'https://///blob//[...path]/.json'") - } - if pathParts[3] != "blob" { - return nil, fmt.Errorf("invalid GitHub URL format. Expected 'blob' in the URL path") - } - repoSlug = fmt.Sprintf("%s/%s", pathParts[1], pathParts[2]) - branch = pathParts[4] - filePath = strings.Join(pathParts[5:], "/") - } else { - return nil, fmt.Errorf( - "invalid URL format. Expected formats are:\n" + - " - 'https://raw.///refs/heads//[...path]/.json'\n" + - " - 'https://///blob//[...path]/.json'\n" + - " - 'https://api./repos///contents/[...path]/.json[?ref=]'", - ) - } - - // Normalize hostname for API calls - if hostname == "raw.githubusercontent.com" { - hostname = "github.com" - } - - return &GitHubUrlInfo{ - RepoSlug: repoSlug, - Branch: branch, - FilePath: filePath, - Hostname: hostname, - }, nil -} - func downloadParentDirectory( - ctx context.Context, urlInfo *GitHubUrlInfo, targetDir string, ghCli *github.Cli, console input.Console) error { + ctx context.Context, urlInfo *azdext.ParseGitHubUrlResponse, targetDir string, ghCli *github.Cli, console input.Console) error { // Get parent directory by removing the filename from the file path pathParts := strings.Split(urlInfo.FilePath, "/") From 0eb189ff105882771d142b31b1bbbe751936a269 Mon Sep 17 00:00:00 2001 From: trangevi Date: Fri, 12 Dec 2025 14:23:11 -0800 Subject: [PATCH 2/2] Address PR comments Signed-off-by: trangevi --- cli/azd/extensions/azure.ai.agents/go.mod | 2 -- .../azure.ai.agents/internal/cmd/init.go | 36 +++++++++++++++---- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/cli/azd/extensions/azure.ai.agents/go.mod b/cli/azd/extensions/azure.ai.agents/go.mod index 137136e2d98..f236dc2e8b3 100644 --- a/cli/azd/extensions/azure.ai.agents/go.mod +++ b/cli/azd/extensions/azure.ai.agents/go.mod @@ -97,5 +97,3 @@ require ( google.golang.org/genproto/googleapis/rpc v0.0.0-20251007200510-49b9836ed3ff // indirect google.golang.org/grpc v1.76.0 // indirect ) - -replace github.com/azure/azure-dev/cli/azd => ../.. 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 0307d58e053..da16082d069 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/cmd/init.go +++ b/cli/azd/extensions/azure.ai.agents/internal/cmd/init.go @@ -68,6 +68,14 @@ type InitAction struct { deploymentDetails []project.Deployment } +// GitHubUrlInfo holds parsed information from a GitHub URL +type GitHubUrlInfo struct { + RepoSlug string + Branch string + FilePath string + Hostname string +} + const AiAgentHost = "azure.ai.agent" const ContainerAppHost = "containerapp" @@ -780,7 +788,7 @@ func (a *InitAction) downloadAgentYaml( var content []byte var err error var isGitHubUrl bool - var urlInfo *azdext.ParseGitHubUrlResponse + var urlInfo *GitHubUrlInfo var ghCli *github.Cli var console input.Console @@ -854,10 +862,7 @@ func (a *InitAction) downloadAgentYaml( return nil, "", fmt.Errorf("creating GitHub CLI: %w", err) } - // urlInfo, err = parseGitHubUrl(manifestPointer) - urlInfo, err = a.azdClient.Project().ParseGitHubUrl(ctx, &azdext.ParseGitHubUrlRequest{ - Url: manifestPointer, - }) + urlInfo, err = a.parseGitHubUrl(ctx, manifestPointer) if err != nil { return nil, "", err } @@ -1218,7 +1223,7 @@ func (a *InitAction) populateContainerSettings(ctx context.Context) (*project.Co } func downloadGithubManifest( - ctx context.Context, urlInfo *azdext.ParseGitHubUrlResponse, apiPath string, ghCli *github.Cli, console input.Console) (string, error) { + ctx context.Context, urlInfo *GitHubUrlInfo, apiPath string, ghCli *github.Cli, console input.Console) (string, error) { // manifestPointer validation: // - accepts only URLs with the following format: // - https://raw.///refs/heads///.json @@ -1252,8 +1257,25 @@ func downloadGithubManifest( return content, nil } +// parseGitHubUrl extracts repository information from various GitHub URL formats using extension framework +func (a *InitAction) parseGitHubUrl(ctx context.Context, manifestPointer string) (*GitHubUrlInfo, error) { + urlInfo, err := a.azdClient.Project().ParseGitHubUrl(ctx, &azdext.ParseGitHubUrlRequest{ + Url: manifestPointer, + }) + if err != nil { + return nil, err + } + + return &GitHubUrlInfo{ + RepoSlug: urlInfo.RepoSlug, + Branch: urlInfo.Branch, + FilePath: urlInfo.FilePath, + Hostname: urlInfo.Hostname, + }, nil +} + func downloadParentDirectory( - ctx context.Context, urlInfo *azdext.ParseGitHubUrlResponse, targetDir string, ghCli *github.Cli, console input.Console) error { + ctx context.Context, urlInfo *GitHubUrlInfo, targetDir string, ghCli *github.Cli, console input.Console) error { // Get parent directory by removing the filename from the file path pathParts := strings.Split(urlInfo.FilePath, "/")