Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
cdef10b
MHA azd cli
kshitij-microsoft Jun 25, 2026
f694d8d
Merge branch 'main' of https://github.com/Azure/azure-dev into kchawl…
kshitij-microsoft Jun 25, 2026
1be4f1c
0.1.42-preview dev release
kshitij-microsoft Jun 25, 2026
b740281
MHA with tools etc
kshitij-microsoft Jul 1, 2026
a00e569
MHA with skills and toolboxes
kshitij-microsoft Jul 10, 2026
7449984
Remove obsolete demo, scratch agent project, and managed-harness specs
kshitij-microsoft Jul 10, 2026
e991e5f
Managed Harness Agents PrPr
kshitij-microsoft Jul 15, 2026
1ee803f
Managed Harness Agents PrPr
kshitij-microsoft Jul 15, 2026
c5398d6
Managed Harness Agents PrPr
kshitij-microsoft Jul 15, 2026
cfea4e8
MHA bug fixes: 5446971, 5444617
kshitij-microsoft Jul 17, 2026
d6f9fb5
removing files subfolder from project scaffold
kshitij-microsoft Jul 17, 2026
1217b85
removing files subfolder from project scaffold
kshitij-microsoft Jul 17, 2026
9d92a95
prpr 1.45-preview release
kshitij-microsoft Jul 17, 2026
b97ede5
prpr 1.46-preview release
kshitij-microsoft Jul 20, 2026
2bb6450
Merge branch 'main' of https://github.com/Azure/azure-dev into kchawl…
kshitij-microsoft Jul 27, 2026
7150098
merge fixes
kshitij-microsoft Jul 27, 2026
7b51fb0
prompt and managed agents
kshitij-microsoft Aug 21, 2026
363b1f1
0.1.50-preview for demo
kshitij-microsoft Aug 21, 2026
57d20cb
feat(ai-agents): agent definition ref, harness gate, and manifest upd…
kshitij-microsoft Aug 26, 2026
028eab8
docs(ai-agents): update Agent schema, changelog, and cspell words
kshitij-microsoft Aug 26, 2026
ff410f9
Merge branch 'main' of https://github.com/Azure/azure-dev into kchawl…
kshitij-microsoft Aug 26, 2026
101be13
feat(ai-agents): RAI policy discovery with fallback, and prompt agent…
kshitij-microsoft Aug 26, 2026
70561f5
docs(ai-agents): record RAI policy binding and sibling-service changes
kshitij-microsoft Aug 26, 2026
b59d590
fix(ai-agents): repair typed-nil credential fallback and clear lint debt
kshitij-microsoft Aug 26, 2026
c5980a9
fix(ai-agents): reject silent typos in authored agent.yaml blocks
kshitij-microsoft Aug 26, 2026
ffe8a60
chore(ai-agents): keep new spelling terms in the extension cspell config
kshitij-microsoft Aug 27, 2026
1789ef0
feat(ai-agents): author prompt agents inline in azure.yaml
kshitij-microsoft Aug 27, 2026
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
40 changes: 40 additions & 0 deletions cli/azd/extensions/azure.ai.agents/.agentignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Files excluded from agent code deployment packaging.
# Uses .gitignore syntax.
# Note: only the root .agentignore is read; subdirectory files are not supported.
#
# To include a file that is excluded by default, use negation: !filename

# azd tooling files
agent.yaml
agent.manifest.yaml
azure.yaml
.agentignore

# Security / secrets
.env
.env.*
.azure/
.git/

# Python
__pycache__/
.venv/
venv/
*.pyc
*.pyo
.mypy_cache/
.pytest_cache/

# .NET
bin/
obj/
*.user
*.suo
.vs/

# Node
node_modules/

# Docker (not used in code deploy)
Dockerfile
.dockerignore
65 changes: 65 additions & 0 deletions cli/azd/extensions/azure.ai.agents/CHANGELOG.md

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion cli/azd/extensions/azure.ai.agents/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,9 @@ Details:
`Microsoft.DefaultV2` still need the full ID, with the account that hosts them
in the path.
- Create or list policies on the Foundry account first — azd does not create the
policy, it only associates the agent with an existing one.
policy, it only associates the agent with an existing one. For prompt and
managed agents, `azd ai agent init` lists the policies on the selected account
and can bind one for you; see `--rai-policy`.

> **Note:** In the deprecated on-disk `agent.yaml` shape the key is snake_case
> (`rai_policy_name`). In `azure.yaml` it is camelCase (`raiPolicyName`), like
Expand Down
22 changes: 22 additions & 0 deletions cli/azd/extensions/azure.ai.agents/cspell.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,25 @@ words:
- upsert
- m365agentstoolkit
- atk
# Managed agent (Foundry PES / vienna harness) terms
- vienna
- azureml
- cognitiveservices
- fdp
- PES
# Prompt agent (graph, skills, connections, policies) terms
- chdirs
- dedupe
- frontmatter
- gerr
- ghcp
- pasteable
- pctx
- raipolicies
- retarget
- sandboxed
- stringifying
- subfolders
- ufeff
- warnf
- workspacenotfound
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,10 @@ func parseAgentEndpoint(rawURL string) (*parsedAgentEndpoint, error) {
)
}

if !strings.EqualFold(u.Scheme, "https") {
bypass := foundryEndpointValidationBypassed()

if !strings.EqualFold(u.Scheme, "https") &&
!(bypass && strings.EqualFold(u.Scheme, "http")) {
return nil, exterrors.Validation(
exterrors.CodeInvalidParameter,
"--agent-endpoint must use https",
Expand All @@ -82,7 +85,14 @@ func parseAgentEndpoint(rawURL string) (*parsedAgentEndpoint, error) {
}

host := strings.ToLower(u.Hostname())
if host == "" || !isFoundryHost(host) {
if host == "" {
return nil, exterrors.Validation(
exterrors.CodeInvalidParameter,
"--agent-endpoint host must not be empty",
agentEndpointHint,
)
}
if !bypass && !isFoundryHost(host) {
return nil, exterrors.Validation(
exterrors.CodeInvalidParameter,
fmt.Sprintf("--agent-endpoint host %q is not a Foundry host (*%s)", u.Hostname(), agentEndpointHostHint),
Expand All @@ -92,7 +102,8 @@ func parseAgentEndpoint(rawURL string) (*parsedAgentEndpoint, error) {

// Reject explicit ports — Foundry endpoints always use the default HTTPS port,
// and silently dropping a non-default port would route requests to a different origin.
if u.Port() != "" {
// The override path allows ports (e.g. http://localhost:5000) for local backends.
if !bypass && u.Port() != "" {
return nil, exterrors.Validation(
exterrors.CodeInvalidParameter,
fmt.Sprintf("--agent-endpoint host %q must not include a port", u.Host),
Expand Down Expand Up @@ -156,7 +167,16 @@ func parseAgentEndpoint(rawURL string) (*parsedAgentEndpoint, error) {
apiVersion = values[0]
}

projectEndpoint := fmt.Sprintf("https://%s/api/projects/%s", host, projectSegment)
// Rebuild the project-scoped endpoint. On the override path preserve the
// caller's scheme and host:port verbatim: forcing https and dropping the port
// would rewrite an `http://localhost:5000` override to `https://localhost/...`
// and never reach the local backend the override exists to target. This
// mirrors what validateProjectEndpoint does in project_endpoint.go.
scheme, authority := "https", host
if bypass {
scheme, authority = strings.ToLower(u.Scheme), u.Host
}
projectEndpoint := fmt.Sprintf("%s://%s/api/projects/%s", scheme, authority, projectSegment)

return &parsedAgentEndpoint{
ProjectEndpoint: projectEndpoint,
Expand Down
110 changes: 108 additions & 2 deletions cli/azd/extensions/azure.ai.agents/internal/cmd/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ func newDeleteCommand(extCtx *azdext.ExtensionContext) *cobra.Command {

cmd := &cobra.Command{
Use: "delete [name]",
Short: "Delete a hosted agent.",
Long: `Delete a hosted agent and all of its versions.
Short: "Delete an agent.",
Long: `Delete an agent and all of its versions.

If --version is specified, only that version is deleted (the agent itself remains).

Expand Down Expand Up @@ -103,6 +103,20 @@ func (a *DeleteAction) Run(ctx context.Context) error {
}
defer azdClient.Close()

// Prompt (kind=managed) agents are azd services on the harness. They are
// torn down with the rest of the project via `azd down`, so redirect
// rather than calling the Foundry agent-delete path that would fail.
if pctx, isPrompt, pErr := resolvePromptAgentService(
ctx, azdClient, a.flags.name, a.flags.noPrompt,
); pErr != nil {
// Match `show`: a failure to resolve is a real error. Falling through to
// the hosted path would run the Foundry agent-delete flow against a
// prompt agent and report a misleading "agent not found".
return pErr
} else if isPrompt {
return a.runPromptDelete(ctx, azdClient, pctx)
}

info, err := resolveAgentServiceFromProject(ctx, azdClient, a.flags.name, a.flags.noPrompt)
if err != nil {
return err
Expand Down Expand Up @@ -236,6 +250,7 @@ func (a *DeleteAction) cleanupEnvVars(
fmt.Sprintf("AGENT_%s_NAME", serviceKey),
fmt.Sprintf("AGENT_%s_VERSION", serviceKey),
fmt.Sprintf("AGENT_%s_ENDPOINT", serviceKey),
fmt.Sprintf("AGENT_%s_VECTOR_STORE_ID", serviceKey),
envkey.AgentProjectEndpoint(serviceName),
}
for _, protocol := range project.DisplayableProtocolEnvSuffixes() {
Expand Down Expand Up @@ -388,3 +403,94 @@ func classifyDeleteError(err error, agentName string) error {
}
return exterrors.ServiceFromAzure(err, exterrors.OpDeleteAgent)
}

// runPromptDelete deletes a prompt (kind=managed) agent from the harness. It
// is dispatched from Run() when the resolved azure.ai.agent service carries a
// promptAgent config block. The agent is removed from the harness directly;
// to tear down the whole project (infra included) use `azd down`.
//
// Versioning is not supported for prompt agents today — the backend does not
// expose a per-version delete on the v2.0 surface — so --version is rejected
// with a typed validation error rather than silently ignored.
func (a *DeleteAction) runPromptDelete(
ctx context.Context,
azdClient *azdext.AzdClient,
pctx *promptServiceContext,
) error {
if a.flags.version != "" {
return exterrors.Validation(
exterrors.CodeInvalidParameter,
"--version is not supported for prompt agents",
"prompt agents do not expose per-version delete; omit --version to delete the agent",
)
}

agentName := pctx.AgentName()
if agentName == "" {
return exterrors.Validation(
exterrors.CodeInvalidAgentName,
"agent name is required but could not be resolved",
"set 'name' in agent.yaml or pass the agent name as a positional argument",
)
}

// Confirmation prompt (skip in --no-prompt mode).
if !a.flags.noPrompt {
message := fmt.Sprintf("Delete prompt agent %q from the harness?", agentName)
if a.flags.force {
message = fmt.Sprintf(
"Force-delete prompt agent %q? This will terminate all active sessions.",
agentName,
)
}
defaultValue := false
resp, promptErr := azdClient.Prompt().Confirm(ctx, &azdext.ConfirmRequest{
Options: &azdext.ConfirmOptions{
Message: message,
DefaultValue: &defaultValue,
},
})
if promptErr != nil {
if exterrors.IsCancellation(promptErr) {
return exterrors.Cancelled("delete cancelled")
}
return fmt.Errorf("prompting for confirmation: %w", promptErr)
}
if resp.Value == nil || !*resp.Value {
return exterrors.Cancelled("delete cancelled by user")
}
}

client, err := pctx.newClient()
if err != nil {
return err
}

result, err := client.DeleteAgent(ctx, agentName, pctx.Settings.EffectiveAPIVersion(), a.flags.force)
if err != nil {
return classifyDeleteError(err, agentName)
}

// Same post-delete cleanup as the hosted path: without it the stale
// AGENT_{KEY}_* values keep `show`/`invoke` pointed at an agent that no
// longer exists. Session state must be cleared first since it reads
// AGENT_{KEY}_ENDPOINT.
if envResp, envErr := azdClient.Environment().GetCurrent(ctx, &azdext.EmptyRequest{}); envErr == nil {
cleanupAgentSessionState(ctx, azdClient, envResp.Environment.Name, pctx.ServiceName)
}
a.cleanupEnvVars(ctx, azdClient, pctx.ServiceName, pctx.Settings.ProjectEndpoint)

switch a.flags.output {
case "json":
data, jsonErr := json.MarshalIndent(result, "", " ")
if jsonErr != nil {
return fmt.Errorf("failed to marshal response: %w", jsonErr)
}
fmt.Println(string(data))
default:
fmt.Printf("Prompt agent %q deleted from the harness.\n", agentName)
fmt.Println("To also tear down the project infrastructure, run `azd down`.")
}

return nil
}
59 changes: 59 additions & 0 deletions cli/azd/extensions/azure.ai.agents/internal/cmd/deploy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package cmd

import (
"context"
"fmt"

"azureaiagent/internal/exterrors"

"github.com/azure/azure-dev/cli/azd/pkg/azdext"
"github.com/spf13/cobra"
)

// newDeployCommand creates `azd ai agent deploy`, which now exists only to
// redirect users to the standard azd lifecycle.
//
// Prompt agents are first-class azd services (host: azure.ai.agent) created on
// the harness by the service-target provider during `azd up` / `azd deploy`,
// exactly like hosted agents. The previous standalone harness-deploy behavior
// has been removed in favor of that unified flow.
//
// The extension context is accepted for symmetry with the other command
// constructors in root.go but is unused: the command only returns an error
// pointing at the standard lifecycle.
func newDeployCommand(_ *azdext.ExtensionContext) *cobra.Command {
cmd := &cobra.Command{
Use: "deploy [name]",
Short: "Deprecated: use `azd up` or `azd deploy`.",
Hidden: true,
Long: `Deprecated. Prompt and hosted agents both deploy through the standard azd
lifecycle now.

Run 'azd up' to provision infrastructure and create the agent, or 'azd deploy'
to (re)deploy the agent once infrastructure exists.`,
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := azdext.WithAccessToken(cmd.Context())
return (&DeployAction{}).Run(ctx)
},
}

return cmd
}

// DeployAction implements the deprecated deploy redirect.
type DeployAction struct{}

func (a *DeployAction) Run(_ context.Context) error {
return exterrors.Validation(
exterrors.CodeInvalidParameter,
"`azd ai agent deploy` has been replaced by the standard azd lifecycle",
fmt.Sprintf(
"run %q to provision and deploy, or %q to (re)deploy an existing project",
"azd up", "azd deploy",
),
)
}
5 changes: 5 additions & 0 deletions cli/azd/extensions/azure.ai.agents/internal/cmd/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,11 @@ type AgentServiceInfo struct {
Version string // deployed agent version from env
AgentEndpoint string // full AGENT_{SVC}_ENDPOINT URL (includes name + version)
ProjectEndpoint string // adopted project endpoint used by a verified brownfield fallback
// ServiceDir is the absolute path to the service's source directory
// (project.Path joined with svc.RelativePath). It points at the folder
// that contains the service's agent.yaml, when one was scaffolded by
// `azd ai agent init`. May be empty if the resolver could not compute it.
ServiceDir string
}

// promptForAgentService prompts the user to select one of multiple azure.ai.agent services.
Expand Down
Loading
Loading