Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions cli/azd/extensions/azure.ai.agents/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## 0.1.32-preview (2026-05-18)

- [[#8223]](https://github.com/Azure/azure-dev/pull/8223) Add `.agentignore` support for controlling which files are excluded from agent code-deploy ZIP packaging. Uses `.gitignore` syntax with sensible defaults generated during `azd ai agent init`.
- [[#8222]](https://github.com/Azure/azure-dev/pull/8222) Add post-init validation to check .NET runtime compatibility with project TargetFramework and show guidance when mismatched.
- [[#7865]](https://github.com/Azure/azure-dev/pull/7865) Improve `azd ai agent invoke` trace ID handling for consistent responses, including deduping comma-folded request IDs.
- [[#8184]](https://github.com/Azure/azure-dev/pull/8184) Default `azd ai agent show` output to table format.
Expand Down
1 change: 1 addition & 0 deletions cli/azd/extensions/azure.ai.agents/cspell.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ words:
- westeurope
# Project terms
- ABAC
- agentignore
- ADLS
- agentserver
- aiservices
Expand Down
3 changes: 3 additions & 0 deletions cli/azd/extensions/azure.ai.agents/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ require (
gopkg.in/yaml.v3 v3.0.1
)

require github.com/denormal/go-gitignore v0.0.0-20180930084346-ae8ad1d07817

require (
dario.cat/mergo v1.0.2 // indirect
github.com/AlecAivazis/survey/v2 v2.3.7 // indirect
Expand Down Expand Up @@ -58,6 +60,7 @@ require (
github.com/clipperhouse/displaywidth v0.9.0 // indirect
github.com/clipperhouse/stringish v0.1.1 // indirect
github.com/clipperhouse/uax29/v2 v2.5.0 // indirect
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dlclark/regexp2 v1.11.5 // indirect
Comment thread
v1212 marked this conversation as resolved.
github.com/go-logr/logr v1.4.3 // indirect
Expand Down
6 changes: 4 additions & 2 deletions cli/azd/extensions/azure.ai.agents/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthoriza
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v2 v2.2.0/go.mod h1:/pz8dyNQe+Ey3yBp/XuYz7oqX8YDNWVpPB0hH3XWfbc=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v3 v3.0.0-beta.2 h1:qiir/pptnHqp6hV8QwV+IExYIf6cPsXBfUDUXQ27t2Y=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v3 v3.0.0-beta.2/go.mod h1:jVRrRDLCOuif95HDYC23ADTMlvahB7tMdl519m9Iyjc=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/cognitiveservices/armcognitiveservices v1.8.0 h1:ZMGAqCZov8+7iFUPWKVcTaLgNXUeTlz20sIuWkQWNfg=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/cognitiveservices/armcognitiveservices v1.8.0/go.mod h1:BElPQ/GZtrdQ2i5uDZw3OKLE1we75W0AEWyeBR1TWQA=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/cognitiveservices/armcognitiveservices/v2 v2.0.0 h1:pxphC/uRZKNHNPbZ0duDDgKkefju2F03OkG5xF6byHQ=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/cognitiveservices/armcognitiveservices/v2 v2.0.0/go.mod h1:twcwRey+l1znKBL5TEzYiZMtiVkWfM7Pq8a9vY04xYc=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerregistry/armcontainerregistry v1.3.0-beta.3 h1:4qfc7os3wRQcl+ImfeH9z0abWJzuV9IGcN1B9olmPTU=
Expand Down Expand Up @@ -106,10 +104,14 @@ github.com/clipperhouse/uax29/v2 v2.5.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsV
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI=
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ=
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denormal/go-gitignore v0.0.0-20180930084346-ae8ad1d07817 h1:0nsrg//Dc7xC74H/TZ5sYR8uk4UQRNjsw8zejqH5a4Q=
github.com/denormal/go-gitignore v0.0.0-20180930084346-ae8ad1d07817/go.mod h1:C/+sI4IFnEpCn6VQ3GIPEp+FrQnQw+YQP3+n+GdGq7o=
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/drone/envsubst v1.0.3 h1:PCIBwNDYjs50AsLZPYdfhSATKaRg/FJmDc2D6+C2x8g=
Expand Down
14 changes: 13 additions & 1 deletion cli/azd/extensions/azure.ai.agents/internal/cmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -586,7 +586,10 @@ agents are unique by name within a project, so deploying with an existing name
creates a new version of that existing agent instead of a separate agent.

Use --agent-name to choose a unique Foundry agent name when initializing from
a reusable sample or manifest.`,
a reusable sample or manifest.

A default .agentignore file is generated to control which files are excluded
from code-deploy ZIP packaging (uses .gitignore syntax).`,
Example: ` # Initialize from an agent manifest
azd ai agent init -m ./agent.manifest.yaml

Expand Down Expand Up @@ -1835,6 +1838,15 @@ func writeAgentDefinitionFile(targetDir string, agentManifest *agent_yaml.AgentM
}

fmt.Println(output.WithGrayFormat("Processed agent.yaml at %s", filePath))

// Generate .agentignore if it doesn't already exist
agentIgnorePath := filepath.Join(targetDir, ".agentignore")
if _, err := os.Stat(agentIgnorePath); os.IsNotExist(err) {
if err := os.WriteFile(agentIgnorePath, []byte(project.DefaultAgentIgnoreContent()), osutil.PermissionFile); err != nil {
return fmt.Errorf("writing .agentignore: %w", err)
}
}

return nil
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (

"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/azure/azure-dev/cli/azd/pkg/azdext"
"github.com/azure/azure-dev/cli/azd/pkg/osutil"
"github.com/azure/azure-dev/cli/azd/pkg/ux"
"github.com/fatih/color"
"google.golang.org/protobuf/types/known/structpb"
Expand Down Expand Up @@ -819,6 +820,14 @@ func (a *InitFromCodeAction) writeDefinitionToSrcDir(definition *agent_yaml.Cont
return "", fmt.Errorf("writing definition to file: %w", err)
}

// Generate .agentignore if it doesn't already exist
agentIgnorePath := filepath.Join(srcDir, ".agentignore")
if _, err := os.Stat(agentIgnorePath); os.IsNotExist(err) {
if err := os.WriteFile(agentIgnorePath, []byte(project.DefaultAgentIgnoreContent()), osutil.PermissionFile); err != nil {
return "", fmt.Errorf("writing .agentignore: %w", err)
}
}

return definitionPath, nil
}

Expand Down
158 changes: 158 additions & 0 deletions cli/azd/extensions/azure.ai.agents/internal/project/agentignore.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package project

import (
"bytes"
"context"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"strings"

gitignore "github.com/denormal/go-gitignore"
)

const (
agentIgnoreFileName = ".agentignore"
agentIgnoreMaxSize = 1 << 20 // 1 MB
)

// defaultExclusionsContent is used as the matcher when no .agentignore file exists.
// Generated from DefaultAgentIgnoreContent() to maintain a single source of truth.
var defaultExclusionsContent = DefaultAgentIgnoreContent()

// utf8BOM is the byte order mark that some Windows editors prepend to UTF-8 files.
var utf8BOM = []byte{0xEF, 0xBB, 0xBF}

// agentIgnoreMatcher provides path matching for agent code deploy packaging.
type agentIgnoreMatcher struct {
ignore gitignore.GitIgnore // from .agentignore file or defaults
hasUserIgnore bool
}

// newAgentIgnoreMatcher creates a matcher by reading .agentignore from srcDir.
// If no .agentignore exists, defaults are used.
func newAgentIgnoreMatcher(ctx context.Context, srcDir string) (*agentIgnoreMatcher, error) {
_ = ctx // reserved for future cancellation support
m := &agentIgnoreMatcher{}

// Try to load user's .agentignore
ig, err := loadAgentIgnore(ctx, srcDir)
if err != nil {
return nil, err
}

if ig != nil {
m.ignore = ig
m.hasUserIgnore = true
} else {
// No .agentignore file — use defaults
m.ignore = gitignore.New(
strings.NewReader(defaultExclusionsContent),
srcDir,
nil,
)
}

return m, nil
}

// ShouldExclude returns true if the given path should be excluded from the ZIP.
// relPath is the path relative to srcDir using forward slashes.
// isDir indicates whether the path is a directory.
func (m *agentIgnoreMatcher) ShouldExclude(relPath string, isDir bool) bool {
match := m.ignore.Relative(relPath, isDir)
if match != nil && match.Ignore() {
return true
}
return false
}

// loadAgentIgnore reads an .agentignore file from srcDir.
// Returns nil, nil if no file exists.
func loadAgentIgnore(ctx context.Context, srcDir string) (gitignore.GitIgnore, error) {
_ = ctx // reserved for future cancellation support
path := filepath.Join(srcDir, agentIgnoreFileName)
info, err := os.Lstat(path)
if errors.Is(err, os.ErrNotExist) {
return nil, nil
}
if err != nil {
return nil, fmt.Errorf("reading %s: %w", agentIgnoreFileName, err)
}
if !info.Mode().IsRegular() {
return nil, fmt.Errorf("%s must be a regular file", agentIgnoreFileName)
}
if info.Size() > agentIgnoreMaxSize {
return nil, fmt.Errorf("%s exceeds maximum size (%d bytes)", agentIgnoreFileName, agentIgnoreMaxSize)
}

f, err := os.Open(path) //nolint:gosec // path is constructed from a known directory + constant filename
if err != nil {
return nil, fmt.Errorf("reading %s: %w", agentIgnoreFileName, err)
}
defer f.Close()

data, err := io.ReadAll(io.LimitReader(f, agentIgnoreMaxSize+1))
if err != nil {
return nil, fmt.Errorf("reading %s: %w", agentIgnoreFileName, err)
}
if int64(len(data)) > agentIgnoreMaxSize {
return nil, fmt.Errorf("%s exceeds maximum size (%d bytes)", agentIgnoreFileName, agentIgnoreMaxSize)
}

// Strip UTF-8 BOM
data = bytes.TrimPrefix(data, utf8BOM)

return gitignore.New(bytes.NewReader(data), srcDir, nil), nil
}

// DefaultAgentIgnoreContent returns the default .agentignore file content
// that should be generated during `azd ai agent init`.
func DefaultAgentIgnoreContent() string {
Comment thread
trangevi marked this conversation as resolved.
return `# 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
`
}
Loading
Loading