From 1e88cc7c9555acec6f71e236526805f782ba2c16 Mon Sep 17 00:00:00 2001 From: Rajesh Kamal Date: Mon, 2 Feb 2026 19:38:38 -0800 Subject: [PATCH 1/3] Remove clickable hyperlink from agent endpoint and add documentation note - Update artifact.go to only use hyperlinks for endpoints with LocationKind=Remote - Add support for 'note' metadata field in artifact rendering - Set agent endpoint LocationKind to UNSPECIFIED to make it non-clickable - Add note pointing to aka.ms/azd-agents-invoke for agent invocation docs Fixes #6659 --- .../internal/project/service_target_agent.go | 3 ++- cli/azd/pkg/project/artifact.go | 17 +++++++++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go b/cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go index 05d49738db1..5ffd4d0f0ce 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go +++ b/cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go @@ -627,11 +627,12 @@ func (p *AgentServiceTargetProvider) deployArtifacts( artifacts = append(artifacts, &azdext.Artifact{ Kind: azdext.ArtifactKind_ARTIFACT_KIND_ENDPOINT, Location: agentEndpoint, - LocationKind: azdext.LocationKind_LOCATION_KIND_REMOTE, + LocationKind: azdext.LocationKind_LOCATION_KIND_UNSPECIFIED, Metadata: map[string]string{ "agentName": agentName, "agentVersion": agentVersion, "label": "Agent endpoint", + "note": "For consuming agent endpoint, refer to https://aka.ms/azd-agents-invoke", }, }) } diff --git a/cli/azd/pkg/project/artifact.go b/cli/azd/pkg/project/artifact.go index 6e971a985e9..5aa8018db62 100644 --- a/cli/azd/pkg/project/artifact.go +++ b/cli/azd/pkg/project/artifact.go @@ -95,14 +95,27 @@ func (a *Artifact) ToString(currentIndentation string) string { discriminator = customDiscriminator } - return fmt.Sprintf( + // Only make the endpoint clickable if it's a remote location + displayLocation := location + if a.LocationKind == LocationKindRemote { + displayLocation = output.WithHyperlink(location, location) + } + + result := fmt.Sprintf( "%s- %s: %s %s", currentIndentation, label, - output.WithHyperlink(location, location), + displayLocation, discriminator, ) + // Append note if present + if note, has := a.Metadata["note"]; has && note != "" { + result += fmt.Sprintf("\n%s • %s", currentIndentation, note) + } + + return result + case ArtifactKindContainer: if a.LocationKind == LocationKindRemote { return fmt.Sprintf("%s- Remote Image: %s", currentIndentation, output.WithLinkFormat(location)) From dbb05a2ea234ebbef26877c9c7457a424ae10617 Mon Sep 17 00:00:00 2001 From: Jeffrey Chen Date: Tue, 3 Feb 2026 21:54:06 +0000 Subject: [PATCH 2/3] Fix clickable logic to use metadata instead of LocationKind --- .../internal/project/service_target_agent.go | 5 +- cli/azd/pkg/project/artifact.go | 23 +- cli/azd/pkg/project/artifact_test.go | 206 ++++++++++++++++++ 3 files changed, 228 insertions(+), 6 deletions(-) create mode 100644 cli/azd/pkg/project/artifact_test.go diff --git a/cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go b/cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go index 5ffd4d0f0ce..008d269b7f8 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go +++ b/cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go @@ -627,12 +627,13 @@ func (p *AgentServiceTargetProvider) deployArtifacts( artifacts = append(artifacts, &azdext.Artifact{ Kind: azdext.ArtifactKind_ARTIFACT_KIND_ENDPOINT, Location: agentEndpoint, - LocationKind: azdext.LocationKind_LOCATION_KIND_UNSPECIFIED, + LocationKind: azdext.LocationKind_LOCATION_KIND_REMOTE, Metadata: map[string]string{ "agentName": agentName, "agentVersion": agentVersion, "label": "Agent endpoint", - "note": "For consuming agent endpoint, refer to https://aka.ms/azd-agents-invoke", + "clickable": "false", + "note": "For information on invoking the agent, see https://aka.ms/azd-agents-invoke", }, }) } diff --git a/cli/azd/pkg/project/artifact.go b/cli/azd/pkg/project/artifact.go index 5aa8018db62..a365c629296 100644 --- a/cli/azd/pkg/project/artifact.go +++ b/cli/azd/pkg/project/artifact.go @@ -14,6 +14,16 @@ import ( "github.com/azure/azure-dev/cli/azd/pkg/output" ) +// Metadata key constants for artifact configuration +const ( + // MetadataKeyClickable controls whether an endpoint should be rendered as a clickable hyperlink. + // Set to "false" to display the URL as plain text (users can still Ctrl+Click in supported terminals). + MetadataKeyClickable = "clickable" + + // MetadataKeyNote adds a note line below the artifact output. + MetadataKeyNote = "note" +) + // ArtifactKind represents well-known artifact types in the Azure Developer CLI type ArtifactKind string @@ -95,9 +105,14 @@ func (a *Artifact) ToString(currentIndentation string) string { discriminator = customDiscriminator } - // Only make the endpoint clickable if it's a remote location + // Endpoints are clickable (hyperlinked) by default, but can be disabled via metadata + clickable := true + if val, has := a.Metadata[MetadataKeyClickable]; has && strings.EqualFold(val, "false") { + clickable = false + } + displayLocation := location - if a.LocationKind == LocationKindRemote { + if clickable { displayLocation = output.WithHyperlink(location, location) } @@ -110,8 +125,8 @@ func (a *Artifact) ToString(currentIndentation string) string { ) // Append note if present - if note, has := a.Metadata["note"]; has && note != "" { - result += fmt.Sprintf("\n%s • %s", currentIndentation, note) + if note, has := a.Metadata[MetadataKeyNote]; has && note != "" { + result += fmt.Sprintf("\n%s %s", currentIndentation, note) } return result diff --git a/cli/azd/pkg/project/artifact_test.go b/cli/azd/pkg/project/artifact_test.go new file mode 100644 index 00000000000..a1d00e87330 --- /dev/null +++ b/cli/azd/pkg/project/artifact_test.go @@ -0,0 +1,206 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package project + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +// hyperlinkPrefix is the OSC 8 escape sequence prefix used for terminal hyperlinks. +// The actual WithHyperlink function may not emit this in non-terminal environments, +// so we check for its absence to verify non-clickable behavior. +const hyperlinkPrefix = "\x1b]8;;" + +func TestArtifactToString_Endpoint(t *testing.T) { + tests := []struct { + name string + artifact *Artifact + contains []string + shouldBeClickable bool + }{ + { + name: "remote endpoint is clickable by default", + artifact: &Artifact{ + Kind: ArtifactKindEndpoint, + Location: "https://example.com/api", + LocationKind: LocationKindRemote, + }, + contains: []string{ + "- Endpoint:", + "https://example.com/api", + }, + shouldBeClickable: true, + }, + { + name: "remote endpoint with clickable=false is not hyperlinked", + artifact: &Artifact{ + Kind: ArtifactKindEndpoint, + Location: "https://example.com/agents/myagent", + LocationKind: LocationKindRemote, + Metadata: map[string]string{ + MetadataKeyClickable: "false", + }, + }, + contains: []string{ + "- Endpoint:", + "https://example.com/agents/myagent", + }, + shouldBeClickable: false, + }, + { + name: "agent endpoint with custom label and note", + artifact: &Artifact{ + Kind: ArtifactKindEndpoint, + Location: "https://example.com/agents/myagent/versions/1", + LocationKind: LocationKindRemote, + Metadata: map[string]string{ + "label": "Agent endpoint", + MetadataKeyClickable: "false", + MetadataKeyNote: "For information on invoking the agent, see https://aka.ms/azd-agents-invoke", + }, + }, + contains: []string{ + "- Agent endpoint:", + "https://example.com/agents/myagent/versions/1", + "For information on invoking the agent, see https://aka.ms/azd-agents-invoke", + }, + shouldBeClickable: false, + }, + { + name: "local endpoint is clickable by default", + artifact: &Artifact{ + Kind: ArtifactKindEndpoint, + Location: "http://localhost:8080", + LocationKind: LocationKindLocal, + }, + contains: []string{ + "- Endpoint:", + "http://localhost:8080", + }, + shouldBeClickable: true, + }, + { + name: "endpoint with discriminator", + artifact: &Artifact{ + Kind: ArtifactKindEndpoint, + Location: "https://example.com/api", + LocationKind: LocationKindRemote, + Metadata: map[string]string{ + "discriminator": "(primary)", + }, + }, + contains: []string{ + "- Endpoint:", + "https://example.com/api", + "(primary)", + }, + shouldBeClickable: true, + }, + { + name: "clickable=FALSE is case insensitive", + artifact: &Artifact{ + Kind: ArtifactKindEndpoint, + Location: "https://example.com/api", + LocationKind: LocationKindRemote, + Metadata: map[string]string{ + MetadataKeyClickable: "FALSE", + }, + }, + contains: []string{ + "- Endpoint:", + "https://example.com/api", + }, + shouldBeClickable: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := tt.artifact.ToString("") + + for _, expected := range tt.contains { + require.True(t, strings.Contains(result, expected), + "Expected output to contain %q, got: %s", expected, result) + } + + // Check clickability by looking for hyperlink escape sequence + hasHyperlink := strings.Contains(result, hyperlinkPrefix) + if tt.shouldBeClickable { + // In terminal environments, should have hyperlink; in non-terminal, won't have it + // We can't directly test this without mocking terminal, so we just verify the URL is present + require.Contains(t, result, tt.artifact.Location) + } else { + // Should NOT have hyperlink escape sequence + require.False(t, hasHyperlink, + "Expected output NOT to contain hyperlink escape sequence for non-clickable endpoint, got: %q", result) + } + }) + } +} + +func TestArtifactToString_OtherKinds(t *testing.T) { + tests := []struct { + name string + artifact *Artifact + contains string + }{ + { + name: "container remote", + artifact: &Artifact{ + Kind: ArtifactKindContainer, + Location: "myregistry.azurecr.io/myimage:latest", + LocationKind: LocationKindRemote, + }, + contains: "- Remote Image:", + }, + { + name: "container local", + artifact: &Artifact{ + Kind: ArtifactKindContainer, + Location: "myimage:latest", + LocationKind: LocationKindLocal, + }, + contains: "- Container:", + }, + { + name: "archive", + artifact: &Artifact{ + Kind: ArtifactKindArchive, + Location: "/path/to/output.zip", + LocationKind: LocationKindLocal, + }, + contains: "- Package Output:", + }, + { + name: "directory", + artifact: &Artifact{ + Kind: ArtifactKindDirectory, + Location: "/path/to/build", + LocationKind: LocationKindLocal, + }, + contains: "- Build Output:", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := tt.artifact.ToString("") + require.Contains(t, result, tt.contains) + }) + } +} + +func TestArtifactToString_EmptyLocation(t *testing.T) { + artifact := &Artifact{ + Kind: ArtifactKindEndpoint, + Location: "", + LocationKind: LocationKindRemote, + } + + result := artifact.ToString("") + require.Empty(t, result) +} From 7fe9ac63dc74fa071877977438c46ba4afb3f28d Mon Sep 17 00:00:00 2001 From: Jeffrey Chen Date: Tue, 3 Feb 2026 22:07:28 +0000 Subject: [PATCH 3/3] Format aka.ms link --- .../azure.ai.agents/internal/project/service_target_agent.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go b/cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go index 008d269b7f8..23500705af9 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go +++ b/cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go @@ -21,6 +21,7 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azidentity" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/cognitiveservices/armcognitiveservices" "github.com/azure/azure-dev/cli/azd/pkg/azdext" + "github.com/azure/azure-dev/cli/azd/pkg/output" "github.com/braydonk/yaml" "github.com/drone/envsubst" "github.com/fatih/color" @@ -633,7 +634,7 @@ func (p *AgentServiceTargetProvider) deployArtifacts( "agentVersion": agentVersion, "label": "Agent endpoint", "clickable": "false", - "note": "For information on invoking the agent, see https://aka.ms/azd-agents-invoke", + "note": "For information on invoking the agent, see " + output.WithLinkFormat("https://aka.ms/azd-agents-invoke"), }, }) }