Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
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
2 changes: 2 additions & 0 deletions cli/azd/.vscode/cspell.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ words:
- protoimpl
- protojson
- protoreflect
- protowire
Comment thread
trangevi marked this conversation as resolved.
Outdated
- projectconfig
- SNAPPROCESS
- structpb
- subtest
Expand Down
64 changes: 63 additions & 1 deletion cli/azd/extensions/azure.ai.agents/internal/cmd/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"fmt"
"io"
"log"
"maps"
"net/http"
"net/url"
"os"
Expand All @@ -23,6 +24,7 @@ import (
"azureaiagent/internal/pkg/agents/agent_api"
"azureaiagent/internal/pkg/agents/agent_yaml"
"azureaiagent/internal/pkg/paths"
"azureaiagent/internal/pkg/projectconfig"
projectpkg "azureaiagent/internal/project"

"github.com/azure/azure-dev/cli/azd/pkg/azdext"
Expand Down Expand Up @@ -871,6 +873,7 @@ 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)
Environment map[string]string
// 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
Expand All @@ -883,6 +886,20 @@ func resolveServiceRunContext(ctx context.Context, azdClient *azdext.AzdClient,
if err != nil {
return nil, err
}
if err := projectpkg.ResolveServiceConfigInPlace(
svc,
project.Path,
); err != nil {
return nil, exterrors.Validation(
exterrors.CodeInvalidServiceConfig,
fmt.Sprintf(
"failed to resolve agent service %s: %s",
svc.Name,
err,
),
"fix the agent service configuration in azure.yaml",
)
}

projectDir, err := paths.JoinAllowRoot(project.Path, svc.RelativePath)
if err != nil {
Expand All @@ -894,8 +911,28 @@ func resolveServiceRunContext(ctx context.Context, azdClient *azdext.AzdClient,
}

var startupCmd string
if agentConfig, cfgErr := projectpkg.LoadServiceTargetAgentConfig(svc); cfgErr == nil {
serviceEnv := map[string]string{}
if agentConfig, cfgErr := projectpkg.LoadServiceTargetAgentConfig(
svc,
); cfgErr == nil {
startupCmd = agentConfig.StartupCommand
maps.Copy(serviceEnv, agentConfig.Environment)
}
serviceEnv, err = loadServiceRunEnvironment(
project.Path,
svc,
serviceEnv,
)
if err != nil {
return nil, exterrors.Validation(
exterrors.CodeInvalidServiceConfig,
fmt.Sprintf(
"failed to load environment for %s: %s",
svc.Name,
err,
),
"fix the service env configuration in azure.yaml",
)
}

var definition *agent_yaml.ContainerAgent
Expand All @@ -910,10 +947,35 @@ func resolveServiceRunContext(ctx context.Context, azdClient *azdext.AzdClient,
ServiceName: svc.Name,
ProjectDir: projectDir,
StartupCommand: startupCmd,
Environment: serviceEnv,
Definition: definition,
}, nil
}

func loadServiceRunEnvironment(
projectRoot string,
svc *azdext.ServiceConfig,
base map[string]string,
) (map[string]string, error) {
env := maps.Clone(base)
if env == nil {
env = map[string]string{}
}
raw, err := projectconfig.LoadServiceEnvironment(
projectRoot,
svc.GetName(),
)
if err != nil {
return nil, err
}
if raw == nil {
maps.Copy(env, svc.GetEnvironment())
} else {
maps.Copy(env, raw)
}
return env, nil
}

// toServiceKey converts a service name into the env var key format (uppercase, underscores).
func toServiceKey(serviceName string) string {
key := strings.ReplaceAll(serviceName, " ", "_")
Expand Down
38 changes: 38 additions & 0 deletions cli/azd/extensions/azure.ai.agents/internal/cmd/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -838,3 +838,41 @@ func TestResolveAgentProtocol_MultipleServicesPromptsOnce(t *testing.T) {
require.Equal(t, int32(1), promptServer.selectCalls.Load(),
"resolveAgentProtocol should trigger exactly one prompt")
}

func TestLoadServiceRunEnvironmentUsesRawValues(t *testing.T) {
t.Parallel()

root := t.TempDir()
require.NoError(t, os.WriteFile(
filepath.Join(root, "azure.yaml"),
[]byte(`services:
agent:
host: azure.ai.agent
env:
PROJECT: ${{project.endpoint}}
ENABLED: true
SHARED: direct
`),
0o600,
))
svc := &azdext.ServiceConfig{
Name: "agent",
Environment: map[string]string{
"PROJECT": "",
"ENABLED": "",
"SHARED": "expanded",
},
}

env, err := loadServiceRunEnvironment(
root,
svc,
map[string]string{"CONFIG_ONLY": "config", "SHARED": "config"},
)

require.NoError(t, err)
require.Equal(t, "${{project.endpoint}}", env["PROJECT"])
require.Equal(t, "true", env["ENABLED"])
require.Equal(t, "direct", env["SHARED"])
require.Equal(t, "config", env["CONFIG_ONLY"])
}
87 changes: 69 additions & 18 deletions cli/azd/extensions/azure.ai.agents/internal/cmd/listen.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,22 +54,36 @@ func configureExtensionHost(host *azdext.ExtensionHost) {
}

func preprovisionHandler(ctx context.Context, azdClient *azdext.AzdClient, args *azdext.ProjectEventArgs) error {
<<<<<<< HEAD
Comment thread
huimiu marked this conversation as resolved.
Outdated
deployments, err := collectProjectDeployments(
args.Project.Services,
args.Project.Path,
)
if err != nil {
=======
if err := updateLegacyProjectDeployments(
ctx,
azdClient,
args.Project.Services,
); err != nil {
>>>>>>> origin/main
return err
}
connections, err := collectConnections(args.Project.Services)
connections, err := collectConnections(
args.Project.Services,
args.Project.Path,
)
if err != nil {
return err
}

for _, svc := range args.Project.Services {
switch svc.Host {
case AiAgentHost:
if err := populateContainerSettings(ctx, azdClient, svc); err != nil {
if err := prepareContainerSettings(
svc,
args.Project.Path,
); err != nil {
Comment thread
huimiu marked this conversation as resolved.
return fmt.Errorf("failed to populate container settings for service %q: %w", svc.Name, err)
}
if err := envUpdate(
Expand Down Expand Up @@ -217,19 +231,33 @@ func predeployHandler(ctx context.Context, azdClient *azdext.AzdClient, args *az
warnDuplicateAgentNames(args.Project)
})

<<<<<<< HEAD
deployments, err := collectProjectDeployments(
args.Project.Services,
args.Project.Path,
)
if err != nil {
=======
if err := updateLegacyProjectDeployments(
ctx,
azdClient,
args.Project.Services,
); err != nil {
>>>>>>> origin/main
return err
}
connections, err := collectConnections(args.Project.Services)
connections, err := collectConnections(
args.Project.Services,
args.Project.Path,
)
if err != nil {
return err
}

if err := populateContainerSettings(ctx, azdClient, svc); err != nil {
if err := prepareContainerSettings(
svc,
args.Project.Path,
); err != nil {
return fmt.Errorf("failed to populate container settings for service %q: %w", svc.Name, err)
}
if err := envUpdate(
Expand Down Expand Up @@ -738,10 +766,37 @@ func setEnvVar(ctx context.Context, azdClient *azdext.AzdClient, envName string,
return nil
}

func populateContainerSettings(ctx context.Context, azdClient *azdext.AzdClient, svc *azdext.ServiceConfig) error {
func prepareContainerSettings(
svc *azdext.ServiceConfig,
projectRoot string,
) error {
rawAdditional := svc.GetAdditionalProperties()
rawConfig := svc.GetConfig()
hasRootFileRef := rawAdditional != nil &&
rawAdditional.GetFields()["$ref"] != nil ||
rawConfig != nil && rawConfig.GetFields()["$ref"] != nil
if hasRootFileRef {
if err := project.ResolveServiceConfigInPlace(
svc,
projectRoot,
); err != nil {
return fmt.Errorf(
"failed to resolve agent config: %w",
err,
)
}
} else if err := project.NormalizeServiceConfigInPlace(svc); err != nil {
Comment thread
trangevi marked this conversation as resolved.
return fmt.Errorf(
"failed to normalize agent config: %w",
err,
)
}
Comment thread
huimiu marked this conversation as resolved.
foundryAgentConfig, err := project.LoadServiceTargetAgentConfig(svc)
if err != nil {
return fmt.Errorf("failed to parse foundry agent config: %w", err)
return fmt.Errorf(
"failed to parse foundry agent config: %w",
err,
)
}

// Resolve the container resources, applying defaults when unset.
Expand All @@ -760,19 +815,15 @@ func populateContainerSettings(ctx context.Context, azdClient *azdext.AzdClient,
result.Cpu = project.DefaultCpu
}

// Persist the resolved container settings back onto the service's inline
// properties, preserving the agent definition and other config keys.
if err := project.SetAgentContainerSettings(svc, &project.ContainerSettings{Resources: result}); err != nil {
return fmt.Errorf("failed to update agent container settings: %w", err)
}

// Need to add the service config back to the project for use further down the pipeline
req := &azdext.AddServiceRequest{Service: svc}

if _, err := azdClient.Project().AddService(ctx, req); err != nil {
Comment thread
trangevi marked this conversation as resolved.
return fmt.Errorf("adding agent service to project: %w", err)
if err := project.SetAgentContainerSettings(
svc,
&project.ContainerSettings{Resources: result},
); err != nil {
return fmt.Errorf(
"failed to update agent container settings: %w",
err,
)
}

return nil
}

Expand Down
Loading
Loading