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
4 changes: 4 additions & 0 deletions cli/azd/extensions/azure.ai.agents/cspell.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import: ../../.vscode/cspell.yaml
overrides:
- filename: ["internal/helpformat/*.go", "internal/cmd/root.go"]
words:
- helpformat
words:
# Connection commands
- tavily
Expand Down
1 change: 1 addition & 0 deletions cli/azd/extensions/azure.ai.agents/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ require (
github.com/aymerick/douceur v0.2.0 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/blang/semver/v4 v4.0.0 // indirect
github.com/bradleyjkemp/cupaloy/v2 v2.8.0 // indirect
github.com/buger/goterm v1.0.4 // indirect
github.com/buger/jsonparser v1.1.2 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
Expand Down
1 change: 1 addition & 0 deletions cli/azd/extensions/azure.ai.agents/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
Expand Down
11 changes: 10 additions & 1 deletion cli/azd/extensions/azure.ai.agents/internal/cmd/add.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ func newAgentDependencyCommand(
cmd := &cobra.Command{
Use: dependencyType + " <command>",
Short: fmt.Sprintf("Manage %s service dependencies for an agent.", dependencyType),
Example: fmt.Sprintf(` # Attach an existing %s service to an agent
azd ai agent %s add <service> --agent <agent-service>`, dependencyType, dependencyType),
}
cmd.AddCommand(newAgentAddDependencyCommand(extCtx, dependencyType, expectedHost))
return cmd
Expand All @@ -45,7 +47,14 @@ func newAgentAddDependencyCommand(
cmd := &cobra.Command{
Use: "add <service>",
Short: fmt.Sprintf("Add a %s service dependency to an agent service.", dependencyType),
Args: cobra.ExactArgs(1),
Long: fmt.Sprintf(`Add a %s service dependency to an agent service.

Both services must already be declared in azure.yaml. This adds a uses entry
to the agent service; it does not create the dependency resource.
Use --agent to identify the agent service to update.`, dependencyType),
Example: fmt.Sprintf(` # Attach an existing %s service to an agent
azd ai agent %s add <service> --agent <agent-service>`, dependencyType, dependencyType),
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
agentServiceName, dependencyServiceName := normalizeAgentDependencyNames(flags.agent, args[0])
azdClient, err := azdext.NewAzdClient()
Expand Down
2 changes: 2 additions & 0 deletions cli/azd/extensions/azure.ai.agents/internal/cmd/code.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ func newCodeCommand(extCtx *azdext.ExtensionContext) *cobra.Command {
Use: "code",
Short: "Manage agent source code. (Preview)",
Long: `Commands for managing the source code of code-based hosted agents.`,
Example: ` # Download the deployed agent source code
azd ai agent code download`,
}

cmd.AddCommand(newCodeDownloadCommand(extCtx))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ reporting on each one and (when all checks pass) suggesting the next
command to run. Use this when you have lost terminal context or hit a
confusing error and want a complete picture of the project's state.

Exit codes:
Exit Codes:
0 — at least one check passed and no checks failed
1 — any check failed
2 — all checks were skipped (e.g. preconditions unmet)`,
Expand Down
11 changes: 5 additions & 6 deletions cli/azd/extensions/azure.ai.agents/internal/cmd/eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,12 +121,11 @@ func newEvalCommand(extCtx *azdext.ExtensionContext) *cobra.Command {
Short: "Create and run quick evals for an agent.",
Long: `Create and run quick evals for an agent.

Subcommands:
generate Generate an eval config and dataset from a hosted agent
run Execute an evaluation run from eval.yaml
update Update an existing eval configuration
list List evaluations for the current project
show Show details of an evaluation run`,
Generate an eval config and dataset, run evaluations, and inspect the results.
Use 'azd ai agent eval update' to upload changes to local evaluators and datasets.`,
Example: ` # Generate an eval suite and run it
azd ai agent eval generate
azd ai agent eval run`,
}

cmd.AddCommand(newEvalGenerateCommand(extCtx))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ func newEvalListCommand(extCtx *azdext.ExtensionContext) *cobra.Command {
cmd := &cobra.Command{
Use: "list",
Short: "List evaluations for the current project.",
Args: cobra.NoArgs,
Example: ` # List the five most recent evaluations
azd ai agent eval list --limit 5`,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := azdext.WithAccessToken(cmd.Context())
logCleanup := setupDebugLogging(cmd.Flags())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,12 @@ func newEvalRunCommand(extCtx *azdext.ExtensionContext) *cobra.Command {
cmd := &cobra.Command{
Use: "run",
Short: "Execute an evaluation run from eval.yaml.",
Args: cobra.NoArgs,
Example: ` # Run the evaluation configured in eval.yaml
azd ai agent eval run

# Start a run without waiting for results
azd ai agent eval run --no-wait`,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := azdext.WithAccessToken(cmd.Context())
logCleanup := setupDebugLogging(cmd.Flags())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ func newEvalShowCommand(extCtx *azdext.ExtensionContext) *cobra.Command {
Long: `Show an eval definition, run history, or run details.

If eval-id is omitted, the most recent eval from the current environment is used.`,
Example: ` # Show the most recent evaluation in the current environment
azd ai agent eval show

# Show a specific evaluation
azd ai agent eval show <eval-id>`,
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := azdext.WithAccessToken(cmd.Context())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ The version fields in the config are updated after successful uploads.

In interactive mode, you will be prompted for each asset type that has
local changes. Use --dataset-only or --evaluator-only to skip prompts.`,
Example: ` # Upload changes to the local dataset
azd ai agent eval update --dataset-only`,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := azdext.WithAccessToken(cmd.Context())
Expand Down
2 changes: 2 additions & 0 deletions cli/azd/extensions/azure.ai.agents/internal/cmd/files.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ from the last invoke session, or can be overridden with --session-id.

For agents configured with header-based isolation, pass --user-identity
on each file operation.`,
Example: ` # List files in the current hosted agent session
azd ai agent files list`,
}

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

package cmd

const agentHelpFooter = `Environments & Environment Variables:
An azd environment is a named deployment configuration, such as dev or prod.
Its values are stored locally in .azure/<environment>/.env.
Use 'azd env new <name>' to create one, 'azd env select <name>' to change the
default, or --environment <name> (-e) to select one for a command.

Use 'azd env set <name> <value>' to save a value and 'azd env get-values' to
inspect the selected environment. Values can include secrets; do not share
the output or commit .azure to source control.

FOUNDRY_PROJECT_ENDPOINT identifies the Foundry project. Init records it
when you select an existing project; generated azure.yaml files reference
it as ${FOUNDRY_PROJECT_ENDPOINT} to keep configuration portable.
Deploy records AGENT_<SERVICE>_NAME, AGENT_<SERVICE>_VERSION and
AGENT_<SERVICE>_PROJECT_ENDPOINT so commands can resolve deployed agents.
<SERVICE> is the service name in uppercase with spaces and hyphens replaced
by underscores (for example, my-agent becomes MY_AGENT).

For hosted agent runtime settings, use the service-level env mapping in
azure.yaml. Reference environment values with ${VARIABLE_NAME}; setting an
azd environment value alone does not forward every value to the agent.
Use 'azd ai agent run' for local development and 'azd deploy' to apply changes
to the deployed agent.

Learn More:
Agent documentation: https://aka.ms/azd-ai-agent-docs
Environment commands: 'azd env --help'`
104 changes: 104 additions & 0 deletions cli/azd/extensions/azure.ai.agents/internal/cmd/help_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package cmd

import (
"bytes"
"strings"
"testing"

"github.com/azure/azure-dev/cli/azd/pkg/output"
"github.com/azure/azure-dev/cli/azd/test/snapshot"
"github.com/fatih/color"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/stretchr/testify/require"
)

func TestAgentHelp(t *testing.T) {
t.Setenv("NO_COLOR", "1")
previous := color.NoColor
color.NoColor = true
t.Cleanup(func() { color.NoColor = previous })

var walk func(*cobra.Command, []string)
walk = func(command *cobra.Command, path []string) {
name := strings.Join(path, "_")
if name == "" {
name = "root"
}
t.Run(name, func(t *testing.T) {
root := NewRootCommand()
var buf bytes.Buffer
root.SetOut(&buf)
root.SetErr(&buf)
root.SetArgs(append(append([]string{}, path...), "--help"))
require.NoError(t, root.Execute())
text := buf.String()
require.NotContains(t, text, "\x1b")
require.Contains(t, text, "\nUsage\n azd ai agent")
require.Contains(t, text, "\nGlobal Flags\n")
require.Contains(t, text, "\nExamples\n")
require.Equal(t, 1, strings.Count(text, "\nExamples\n"))
require.NotContains(t, text, "\nUsage:")
require.NotContains(t, text, "\nFlags:")
require.NotContains(t, text, "\nSubcommands:")
if len(path) == 0 {
require.Contains(t, text, "Environments & Environment Variables\n")
require.Contains(t, text, ".azure/<environment>/.env")
require.Contains(t, text, "${FOUNDRY_PROJECT_ENDPOINT}")
require.Contains(t, text, "AGENT_<SERVICE>_NAME")
require.Contains(t, text, "AGENT_<SERVICE>_VERSION")
require.Contains(t, text, "AGENT_<SERVICE>_PROJECT_ENDPOINT")
} else {
require.NotContains(t, text, "Environments & Environment Variables")
require.NotContains(t, text, bannerArt)
}
command.Flags().VisitAll(func(flag *pflag.Flag) {
if !flag.Hidden && flag.Deprecated == "" {
require.Contains(t, text, "--"+flag.Name)
}
})
// Ignore trailing terminal padding (including the banner), but keep
// indentation, internal whitespace and line breaks in snapshots.
lines := strings.Split(text, "\n")
for i, line := range lines {
lines[i] = strings.TrimRight(line, " \t\r")
}
snapshot.SnapshotT(t, strings.TrimRight(strings.Join(lines, "\n"), "\n"))
})
for _, child := range command.Commands() {
if child.IsAvailableCommand() {
walk(child, append(append([]string{}, path...), child.Name()))
}
}
}
walk(NewRootCommand(), nil)
}

func TestAgentHelpColor(t *testing.T) {
previous := color.NoColor
color.NoColor = false
t.Cleanup(func() { color.NoColor = previous })

for _, path := range [][]string{nil, {"init"}, {"invoke"}, {"files", "list"}, {"optimize"}, {"eval", "show"}} {
t.Run(strings.Join(path, "_"), func(t *testing.T) {
root := NewRootCommand()
var buf bytes.Buffer
root.SetOut(&buf)
root.SetErr(&buf)
root.SetArgs(append(append([]string{}, path...), "--help"))
require.NoError(t, root.Execute())
text := buf.String()
require.Contains(t, text, output.WithBold("%s", output.WithUnderline("Usage")))
require.Contains(t, text, output.WithBold("%s", output.WithUnderline("Examples")))
require.Contains(t, text, output.WithHighLightFormat("%s", "--environment"))
require.Contains(t, text, "\x1b[94m")
if len(path) == 0 {
require.Contains(t, text,
output.WithBold("%s", output.WithUnderline("Environments & Environment Variables")))
}
})
}
}
4 changes: 4 additions & 0 deletions cli/azd/extensions/azure.ai.agents/internal/cmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -1251,6 +1251,7 @@ func newInitCommand(extCtx *azdext.ExtensionContext) *cobra.Command {
Short: fmt.Sprintf("Initialize a new prompt, hosted, or voice agent project. %s", color.YellowString("(Preview)")),
Long: `Initialize a new prompt, hosted, or voice agent project.

Manifests:
When -m points at a sample's unified azure.yaml (a project manifest that
declares services with host: azure.ai.project / azure.ai.agent / ...), that
azure.yaml is adopted as the project manifest and its referenced files are
Expand All @@ -1259,6 +1260,7 @@ project's azure.yaml is generated from it. An agent manifest that declares
kind: prompt scaffolds a prompt agent (or a managed agent when it also declares
a harness), carrying over its model, instructions, skills, and tools.

Voice Agents:
Use --kind prompt-voice to initialize a managed prompt voice agent without
source code or container scaffolding.
The managed model defaults to gpt-realtime and does not require a model deployment.
Expand All @@ -1283,13 +1285,15 @@ Configure advanced settings in azure.yaml.
Run 'azd provision' and 'azd deploy' to deploy voice services, then connect to
the voice WebSocket endpoint with a Voice Live client.

Agent Names:
The agent name written to agent.yaml is the Foundry agent identity. Foundry
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.

File Exclusions:
A default .agentignore file is generated to control which files are excluded
from code-deploy ZIP packaging (uses .gitignore syntax).`,
Example: ` # Adopt a sample's unified azure.yaml as the project manifest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ and service results depend on the protocol; unsupported operations are rejected.

When --id is omitted, use the latest ID saved for the selected agent and
protocol. Explicit IDs do not change the saved selection.`,
Example: ` # Show the current invocation result
azd ai agent invocations show`,
}
cmd.AddCommand(newInvocationsShowCommand(extCtx))
cmd.AddCommand(newInvocationsFollowCommand(extCtx))
Expand Down
12 changes: 10 additions & 2 deletions cli/azd/extensions/azure.ai.agents/internal/cmd/invoke.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ func newInvokeCommand(extCtx *azdext.ExtensionContext) *cobra.Command {
Short: "Send a message to your prompt or hosted agent.",
Long: `Send a message to your prompt or hosted agent.

Invocation Target:
By default the agent is invoked remotely on Foundry. When a single
argument is provided it is treated as the message and the agent name
is auto-detected from azure.yaml. With two arguments the first is the
Expand All @@ -104,21 +105,25 @@ redeploying or bypassed with --protocol. Otherwise the agent definition is
used. If neither identifies exactly one invocable protocol, pass --protocol
explicitly.

Voice Agents:
For prompt voice agents and hosted voice wrappers, open your agent in the
Microsoft Foundry portal at https://ai.azure.com to try it.
For programmatic voice access, use the voice WebSocket endpoint shown by 'azd show' or
'azd ai agent show' with a Voice Live client. Text invoke is for HTTP-based
hosted agent protocols such as responses, invocations, and a2a.

Request Body:
Use --input-file/-f to send the contents of a file as the request body
instead of a positional message argument. This is useful for structured
or large payloads with the invocations protocol, or for sending a complete
JSON-RPC request with the a2a protocol.

Local Development:
Use --local to target a locally running agent (started via 'azd ai agent run')
instead of Foundry. The a2a protocol is remote-only and cannot be used with
--local.

Sessions & Identity:
Sessions are persisted per-agent — consecutive invokes reuse the same
session automatically. Pass --new-session to force a reset.

Expand All @@ -139,12 +144,14 @@ x-client-* header family to the agent; other header names are rejected, and
the flag is not supported with the a2a protocol (which does not propagate
x-client-* headers). For identity headers use --user-identity or --call-id.

Response Output:
Use --output raw (or -o raw) to dump the unmodified server response (status
line, headers, and body verbatim) to stdout. Useful for debugging server
behavior and inspecting response headers (for example, the agent version
header). Friendly summary lines like "Session:" and "Invocation:" are
suppressed in raw mode.

Platform Latency:
Remote Hosted Agent Responses and Invocations requests include platform latency diagnostics
by default. A compact summary is shown after a successful invocation when the
service returns timing headers. Use --debug-latency=false to disable collection
Expand All @@ -154,6 +161,7 @@ Explicit --debug-latency=true is rejected for these routes; omit the flag
or use --debug-latency=false.
Raw output includes the returned headers without a formatted latency summary.

Long-running Invocations:
Use --long-running with the Responses protocol to start work that continues running in
the service if this command disconnects. The command remains attached until the work
finishes. Add --no-wait to return after azd receives the Response ID.
Expand Down Expand Up @@ -208,8 +216,8 @@ This option does not provide crash recovery or automatic reconnection.`,

# Invoke a deployed agent from any directory using the endpoint URL shown by 'azd ai agent show'
azd ai agent invoke \
--agent-endpoint https://<acct>.services.ai.azure.com/api/projects/<proj>/agents/<name>/endpoint/protocols/openai/responses?api-version=v1 \
"Hello!"`,
--agent-endpoint https://<acct>.services.ai.azure.com/api/projects/<proj>/agents/<name>/endpoint/protocols/openai/responses?api-version=v1 \
"Hello!"`,
Args: cobra.RangeArgs(0, 2),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := azdext.WithAccessToken(cmd.Context())
Expand Down
Loading
Loading