Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
8c53106
feat(agents): add microsoft.foundry service target for a single hoste…
huimiu Jun 12, 2026
1b8a0ee
feat: resolve Foundry project from endpoint so deploy runs without pr…
huimiu Jun 15, 2026
f3ac0f7
fix(agents): expand endpoint vars and use correct error codes in foun…
huimiu Jun 15, 2026
d6b04b4
fix(agents): validate foundry project endpoint to enforce https and r…
huimiu Jun 16, 2026
4dfe0c2
fix(foundry): persist FOUNDRY_PROJECT_ENDPOINT in endpoint-only flow
huimiu Jun 17, 2026
21439bc
merge: add Foundry service-target line onto schema base
huimiu Jun 22, 2026
08fad8d
feat: split Foundry init into per-resource azure.yaml services
huimiu Jun 22, 2026
e8737f5
feat: remove unregistered microsoft.foundry service target
huimiu Jun 22, 2026
74b448f
fix: fall back to bundled agent config for pre-split azure.yaml
huimiu Jun 22, 2026
391faf7
fix: emit ai-project for siblings and detect name collisions
huimiu Jun 22, 2026
4850744
fix: drop duplicate provisionToolboxes doc comment
huimiu Jun 22, 2026
6112642
feat: register foundry resource hosts in agents extension
huimiu Jun 22, 2026
5f0e7b9
feat(agents): always emit ai-project service entry
huimiu Jun 23, 2026
ffb505f
feat: generalize $ref resolver for separate-services azure.yaml
huimiu Jun 23, 2026
89a5d3a
feat: add $ref-aware azure.yaml edit helper for Foundry services
huimiu Jun 23, 2026
6399807
test: add EntryRef edge-case tests for empty/nil/numeric \
huimiu Jun 23, 2026
8529134
test: guard substring order assertions
huimiu Jun 23, 2026
66c22a8
feat: carry foundry agent definition in azure.yaml
huimiu Jun 23, 2026
79bcd97
feat: wire foundry commands to inline agent definition
huimiu Jun 23, 2026
d66bb15
test: cover inline agent definition and migration fallback
huimiu Jun 23, 2026
d83b553
docs: document foundry legacy agent shape telemetry field
huimiu Jun 23, 2026
6f70bac
test: drop unused containsAll helper
huimiu Jun 23, 2026
54f6a75
feat: extract shared foundry ExpandEnv into core pkg/foundry
huimiu Jun 23, 2026
b660ccd
ci: allowlist example identifiers for cspell
huimiu Jun 23, 2026
27f2163
fix: clarify ref helper docs for review
huimiu Jun 23, 2026
be9e007
fix: allow inline foundry agent fields in agent schema
huimiu Jun 23, 2026
b59dd1b
fix: round-trip foundry agent image and validate inline shape
huimiu Jun 23, 2026
58cfda5
fix: bound optimize apply service path to project root
huimiu Jun 23, 2026
f2b6dae
docs: add foundry legacy agent shape to telemetry matrix
huimiu Jun 23, 2026
8a14299
feat: add azure.ai resource host schemas for Foundry services
huimiu Jun 23, 2026
560b8df
feat: write Foundry resource services at service level
huimiu Jun 23, 2026
5d54781
feat: add azure.ai.skill and azure.ai.routine service targets
huimiu Jun 23, 2026
fb0b1bf
fix: remove PR reference from TODO comment
huimiu Jun 23, 2026
6a68615
fix: honor foundry agent definition override
huimiu Jun 23, 2026
c76ed91
fix: address foundry config review feedback
huimiu Jun 23, 2026
01463ff
fix: address unresolved foundry review comments
huimiu Jun 23, 2026
7d475e4
feat: merge per-resource init and agent shape (#8675 #8779)
huimiu Jun 23, 2026
e615294
feat: merge shared $ref resolver and edit helper (#8777)
huimiu Jun 23, 2026
8f2478f
feat: move foundry $ref resolver and edit helper to pkg/foundry
huimiu Jun 23, 2026
4eacfc2
feat: add azure.ai.connection deploy-time service target
huimiu Jun 23, 2026
e4fbeb1
feat: add azure.ai.toolbox deploy-time service target
huimiu Jun 23, 2026
a427e80
feat: add azure.ai.project host service target
huimiu Jun 23, 2026
c387a84
feat: support endpoint: brownfield reuse for foundry
huimiu Jun 23, 2026
6e7d04d
feat: remove agent no-op hosts; reconcile toolboxes at deploy
huimiu Jun 23, 2026
e9d44a2
feat: expand ${VAR} in routine action input at deploy
huimiu Jun 23, 2026
c36baeb
ci: gofmt and add cspell words for foundry targets
huimiu Jun 23, 2026
3465ce9
ci: bump versions and changelogs for foundry targets
huimiu Jun 23, 2026
c525058
merge: bring base foundry networking into shape migration
huimiu Jun 24, 2026
e59e938
merge: sync shape-migration base into gap-analysis branch
huimiu Jun 24, 2026
d6fd4da
fix: restore microsoft.foundry host, brownfield warning, endpoint tri…
huimiu Jun 24, 2026
2be44d3
fix: reject path traversal in skill instructions file path
huimiu Jun 24, 2026
991adc0
fix: drop unsupported URL claim from $ref schema docs
huimiu Jun 24, 2026
6b61a1f
fix: register all foundry schemas in offline ajv check
huimiu Jun 24, 2026
e32d6c6
fix: log inline agent definition marshal failures
huimiu Jun 24, 2026
6260ede
fix: warn when foundry resource name has no service key
huimiu Jun 24, 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
9 changes: 9 additions & 0 deletions cli/azd/.vscode/cspell.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import: ../../../.vscode/cspell.global.yaml
words:
- braydonk
- osutil
- upserted
- upserting
- upserts
- yamlnode
- agentcopilot
- agentdetect
- Authenticode
Expand All @@ -9,6 +15,9 @@ words:
- lightspeed
- runewidth
- toplevel
- myacr
- myconn
- myorg
- azcloud
- azdext
- azdxignore
Expand Down
8 changes: 8 additions & 0 deletions cli/azd/cmd/telemetry_coverage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,14 @@ func TestTelemetryFieldConstants(t *testing.T) {
}
})

// Project config telemetry fields
t.Run("ProjectFields", func(t *testing.T) {
t.Parallel()
kv := fields.FoundryAgentLegacyConfigKey.Bool(true)
require.Equal(t, "foundry.agent.legacy_config_shape", string(kv.Key))
require.True(t, kv.Value.AsBool())
})

// Tool command telemetry fields
t.Run("ToolFields", func(t *testing.T) {
t.Parallel()
Expand Down
2 changes: 2 additions & 0 deletions cli/azd/extensions/azure.ai.agents/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
### Features Added

- `azd ai agent init` now writes each Foundry resource as its own `azure.yaml` service entry instead of bundling everything into the agent service. Model deployments become a single `azure.ai.project` service, each connection becomes an `azure.ai.connection` service, and each toolbox becomes an `azure.ai.toolbox` service, all wired to the agent through `uses:`. The agents extension registers the `azure.ai.project`, `azure.ai.connection`, and `azure.ai.toolbox` service-target hosts itself as no-ops (the resources are created by Bicep at provision time), so only this extension needs to be installed for `azd up`/`azd deploy` to walk the new service entries. Provisioning behavior is unchanged: the agent extension re-sources deployments, connections, and toolboxes from the sibling services when setting provisioning environment variables and creating toolsets, falling back to a pre-split `azure.yaml` that still bundles them on the agent service so existing projects keep provisioning without re-running `init`.
- The `azure.ai.project`, `azure.ai.connection`, and `azure.ai.toolbox` hosts are now owned by their sibling extensions (`azure.ai.projects`, `azure.ai.connections`, `azure.ai.toolboxes`) as real deploy-time service targets. The agents extension no longer registers them as no-op hosts, and toolboxes are reconciled at `azd deploy` by the `azure.ai.toolbox` target rather than created during `azd provision`.
- `azd provision` now connects to an existing Foundry project when the `azure.ai.project` service sets `endpoint:` (bring-your-own) instead of failing with a brownfield error, and `azd down` leaves a bring-your-own project in place because azd did not create it.

## 0.1.41-preview (2026-06-19)

Expand Down
35 changes: 35 additions & 0 deletions cli/azd/extensions/azure.ai.agents/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,41 @@ Use `--no-inspector` to run only the local agent process:
azd ai agent run --no-inspector
```

## Migrating Legacy Agent Configuration

New Foundry agent projects keep the agent definition directly on the
`azure.ai.agent` service entry in `azure.yaml`. Older projects may still have the
definition in an `agent.yaml` file or under the service's `config:` block. Those
legacy shapes continue to work during the migration window, but azd prints a
deprecation warning when it loads them.

To migrate, re-run `azd ai agent init` from the project root and keep the
generated `azure.yaml` service entry. After confirming `azd deploy` still works,
remove the old `agent.yaml` or nested `config:` definition.

Before:

```yaml
services:
my-agent:
host: azure.ai.agent
project: .
config:
kind: hosted
description: My hosted agent
```

After:

```yaml
services:
my-agent:
host: azure.ai.agent
project: .
kind: hosted
description: My hosted agent
```

## Private networking for `host: microsoft.foundry`

Foundry services can be provisioned as network-secured, VNet-bound accounts by
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,10 @@ import (
"text/tabwriter"

"azureaiagent/internal/pkg/agents/agent_api"
"azureaiagent/internal/pkg/agents/agent_yaml"
"azureaiagent/internal/pkg/paths"
"azureaiagent/internal/project"

"github.com/azure/azure-dev/cli/azd/pkg/azdext"
"github.com/spf13/cobra"
goyaml "go.yaml.in/yaml/v3"
)

type endpointShowFlags struct {
Expand Down Expand Up @@ -83,19 +81,14 @@ func runEndpointShow(
return err
}

// Read agent.yaml to get agent name.
agentYamlPath, err := paths.JoinAllowRoot(proj.Path, svc.RelativePath, "agent.yaml")
// Resolve the agent definition (inline on the service entry, or a legacy
// agent.yaml on disk) to get the agent name.
agentDef, _, source, err := project.LoadAgentDefinition(svc, proj.Path)
if err != nil {
return fmt.Errorf("invalid agent.yaml path: %w", err)
return fmt.Errorf("failed to resolve agent definition: %w", err)
}
data, err := os.ReadFile(agentYamlPath) //nolint:gosec // path validated by JoinAllowRoot
if err != nil {
return fmt.Errorf("failed to read agent.yaml: %w", err)
}

var agentDef agent_yaml.ContainerAgent
if err := goyaml.Unmarshal(data, &agentDef); err != nil {
return fmt.Errorf("failed to parse agent.yaml: %w", err)
if source.IsLegacy() {
project.WarnLegacyAgentShape(source)
}

// Resolve endpoint and create client.
Expand Down
80 changes: 37 additions & 43 deletions cli/azd/extensions/azure.ai.agents/internal/cmd/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import (

"github.com/azure/azure-dev/cli/azd/pkg/azdext"
"github.com/google/uuid"
"go.yaml.in/yaml/v3"
"golang.org/x/term"
)

Expand Down Expand Up @@ -707,6 +706,9 @@ type ServiceRunContext struct {
ServiceName string // the resolved service name (from azure.yaml)
ProjectDir string // absolute path to the service source directory
StartupCommand string // startupCommand from AdditionalProperties (may be empty)
// Definition is the resolved agent definition (from the inline azure.yaml
// entry or a legacy agent.yaml). It is nil when no definition can be resolved.
Definition *agent_yaml.ContainerAgent
}

// resolveServiceRunContext queries the azd project to find the matching azure.ai.agent
Expand All @@ -727,17 +729,23 @@ func resolveServiceRunContext(ctx context.Context, azdClient *azdext.AzdClient,
}

var startupCmd string
if svc.Config != nil {
var agentConfig projectpkg.ServiceTargetAgentConfig
if err := projectpkg.UnmarshalStruct(svc.Config, &agentConfig); err == nil {
startupCmd = agentConfig.StartupCommand
if agentConfig, cfgErr := projectpkg.LoadServiceTargetAgentConfig(svc); cfgErr == nil {
startupCmd = agentConfig.StartupCommand
}

var definition *agent_yaml.ContainerAgent
if def, _, source, defErr := projectpkg.LoadAgentDefinition(svc, project.Path); defErr == nil {
definition = &def
if source.IsLegacy() {
projectpkg.WarnLegacyAgentShape(source)
}
}

return &ServiceRunContext{
ServiceName: svc.Name,
ProjectDir: projectDir,
StartupCommand: startupCmd,
Definition: definition,
}, nil
}

Expand Down Expand Up @@ -797,7 +805,7 @@ func resolveAgentProtocol(
name string,
noPrompt bool,
) (agent_api.AgentProtocol, error) {
svc, project, err := resolveAgentService(ctx, azdClient, name, noPrompt)
svc, proj, err := resolveAgentService(ctx, azdClient, name, noPrompt)
if err != nil {
return "", exterrors.Validation(
exterrors.CodeInvalidParameter,
Expand All @@ -809,56 +817,42 @@ func resolveAgentProtocol(
)
}

agentYamlPath, err := paths.JoinAllowRoot(project.Path, svc.RelativePath, "agent.yaml")
hosted, isHosted, source, err := projectpkg.LoadAgentDefinition(svc, proj.Path)
if err != nil {
return "", exterrors.Validation(
exterrors.CodeInvalidServiceConfig,
fmt.Sprintf("invalid service path for %s: %s", svc.Name, err),
"update azure.yaml so the agent service path stays within the project directory",
exterrors.CodeInvalidParameter,
fmt.Sprintf("could not resolve the agent definition for %s: %s", svc.Name, err),
"ensure the agent definition is present in azure.yaml or run `azd ai agent init`",
)
}
return protocolFromAgentYaml(agentYamlPath)
if source.IsLegacy() {
projectpkg.WarnLegacyAgentShape(source)
}
if !isHosted {
return "", exterrors.Validation(
exterrors.CodeUnsupportedAgentKind,
fmt.Sprintf("agent service %s is not a hosted agent", svc.Name),
"only hosted agents can be invoked",
)
}

return protocolFromContainerAgent(hosted)
}

// protocolFromAgentYaml reads and parses the agent.yaml file at the given path
// and extracts the protocol to use for invocation. Returns an error with a
// contextual suggestion when the file cannot be read, parsed, or does not
// declare exactly one invocable protocol.
// protocolFromContainerAgent extracts the protocol to use for invocation from a
// resolved agent definition. Returns an error with a contextual suggestion when
// the definition does not declare exactly one invocable protocol.
//
// When multiple protocols are declared (e.g. "responses" + "a2a"), the caller
// must use --protocol to disambiguate.
func protocolFromAgentYaml(
agentYamlPath string,
func protocolFromContainerAgent(
hosted agent_yaml.ContainerAgent,
) (agent_api.AgentProtocol, error) {
data, err := os.ReadFile(agentYamlPath) //nolint:gosec // G304: path constructed from azd project root
if err != nil {
return "", exterrors.Validation(
exterrors.CodeInvalidParameter,
fmt.Sprintf(
"could not read agent.yaml at %s: %s",
agentYamlPath, err,
),
"ensure agent.yaml exists in the azd service directory",
)
}

var hosted agent_yaml.ContainerAgent
if err := yaml.Unmarshal(data, &hosted); err != nil {
return "", exterrors.Validation(
exterrors.CodeInvalidParameter,
fmt.Sprintf(
"could not parse agent.yaml at %s: %s",
agentYamlPath, err,
),
"fix the agent.yaml syntax",
)
}

if len(hosted.Protocols) == 0 {
return "", exterrors.Validation(
exterrors.CodeInvalidParameter,
"agent.yaml does not declare any protocols",
"add a protocols section to agent.yaml",
"the agent definition does not declare any protocols",
"add a protocols section to the agent definition",
)
}

Expand Down
29 changes: 6 additions & 23 deletions cli/azd/extensions/azure.ai.agents/internal/cmd/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@ import (
"strings"
"testing"

"azureaiagent/internal/pkg/agents/agent_yaml"

"github.com/azure/azure-dev/cli/azd/pkg/azdext"
"github.com/stretchr/testify/require"
goyaml "go.yaml.in/yaml/v3"
)

func TestDetectStartupCommand(t *testing.T) {
Expand Down Expand Up @@ -204,18 +207,6 @@ func TestProtocolFromAgentYaml(t *testing.T) {
yaml: "protocols:\n - protocol: invocations\n version: \"1.0\"\n",
wantProto: "invocations",
},
{
name: "no file",
noFile: true,
wantErr: true,
errContain: "could not read agent.yaml",
},
{
name: "invalid yaml",
yaml: "protocols: [[[invalid",
wantErr: true,
errContain: "could not parse agent.yaml",
},
{
name: "no protocols field",
yaml: "name: my-agent\n",
Expand Down Expand Up @@ -268,18 +259,10 @@ func TestProtocolFromAgentYaml(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

dir := t.TempDir()
yamlPath := filepath.Join(dir, "agent.yaml")

if !tt.noFile {
if err := os.WriteFile(
yamlPath, []byte(tt.yaml), 0600,
); err != nil {
t.Fatalf("failed to write agent.yaml: %v", err)
}
}
var agentDef agent_yaml.ContainerAgent
require.NoError(t, goyaml.Unmarshal([]byte(tt.yaml), &agentDef))

got, err := protocolFromAgentYaml(yamlPath)
got, err := protocolFromContainerAgent(agentDef)

if tt.wantErr {
if err == nil {
Expand Down
Loading
Loading