Skip to content

feat(ai-agents): add --agent-endpoint flag to invoke command#8028

Merged
trangevi merged 11 commits into
Azure:mainfrom
antriksh30:antriksh30/invoke-agent-endpoint-url-fix
May 6, 2026
Merged

feat(ai-agents): add --agent-endpoint flag to invoke command#8028
trangevi merged 11 commits into
Azure:mainfrom
antriksh30:antriksh30/invoke-agent-endpoint-url-fix

Conversation

@antriksh30
Copy link
Copy Markdown
Contributor

@antriksh30 antriksh30 commented May 4, 2026

Summary

Adds --agent-endpoint to azd ai agent invoke so users can invoke a deployed Foundry agent from any directory without needing an azd project, azure.yaml, or environment.

azd ai agent invoke \
    --agent-endpoint <endpoint-url> \
    'Hello!'

The endpoint is the same URL printed by azd ai agent show for a deployed agent.

Behavior

  • Agent name and protocol are parsed from the URL no positional name or --protocol needed.
  • Auth uses AzureDeveloperCLICredential (run azd auth login first), consistent with the rest of the extension.
  • Session and conversation IDs are persisted to the per-user azd config, keyed by the parsed endpoint. Subsequent invokes against the same endpoint auto-resume the session/conversation; pass --new-session or --new-conversation to reset.
  • After every successful invoke a one-line tip is printed pointing users at --help for the reset flags. The tip is protocol-aware:
    • Responses protocol: (tip: pass --new-session or --new-conversation to reset; see azd ai agent invoke --help)
    • Invocations protocol: (tip: pass --new-session to reset; see azd ai agent invoke --help) multi-turn memory on this protocol is bound to the session, so --new-conversation is not advertised.
  • If the user passes --new-conversation while invoking an invocations endpoint, a note: is printed to stderr explaining that the flag has no effect for this protocol and pointing at --new-session instead. The flag is still accepted (no error) so existing scripts keep working.
  • The invocation ID (x-agent-invocation-id) returned by the invocations protocol is printed for trace correlation, matching the local-invoke behavior.
  • The local OpenAPI-spec cache is skipped there's no project to write it into.

Changes

  • New --agent-endpoint flag on azd ai agent invoke. A single regex parses /api/projects/<proj>/agents/<name>/endpoint/protocols/(invocations|openai/responses) and derives projectEndpoint, agent name, protocol, and apiVersion (from the api-version query parameter) in one pass.
  • Rejects encoded path separators (%2F, %5C) inside the project segment so the parsed project name is stable.
  • Validates that flags which conflict with ephemeral mode are not also supplied: --local, a positional agent name, --port, --protocol. Each is rejected with a targeted suggestion. --new-session / --new-conversation are accepted (they reset the persisted IDs).
  • Unit tests for endpoint URL parsing, flag-conflict validation, and stability of the persistence key across URL variants.

Example

with responses endpoint:
image

with invocations endpoint:
image

when --new-conversation flag is added with invocations endpoint, a warning/note is shown:
image

Copilot AI review requested due to automatic review settings May 4, 2026 09:24
@antriksh30 antriksh30 changed the title ai agents: accept full Foundry endpoint URL for --agent-endpoint on invoke feat(ai-agents): add --agent-endpoint flag to invoke command May 4, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR updates azd ai agent invoke so --agent-endpoint can accept a full Foundry agent endpoint URL and derive the remote invocation settings directly from it, including support for running outside an azd project.

Changes:

  • Added parsing and validation for full Foundry agent endpoint URLs, plus a new --agent-endpoint flag.
  • Refactored remote invocation setup to support an “ephemeral” mode that skips azd project resolution and uses values derived from the endpoint URL.
  • Added tests for endpoint parsing and upfront flag-conflict validation.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.

File Description
cli/azd/extensions/azure.ai.agents/internal/cmd/invoke.go Wires in --agent-endpoint, adds remote-context resolution, and updates remote request construction.
cli/azd/extensions/azure.ai.agents/internal/cmd/invoke_test.go Adds validation tests for --agent-endpoint conflict handling.
cli/azd/extensions/azure.ai.agents/internal/cmd/agent_endpoint.go Implements endpoint URL parsing/validation and ephemeral session hint output.
cli/azd/extensions/azure.ai.agents/internal/cmd/agent_endpoint_test.go Adds tests for endpoint parsing, agent-name validation, and session hint smoke coverage.

Comment thread cli/azd/extensions/azure.ai.agents/internal/cmd/agent_endpoint.go
Comment thread cli/azd/extensions/azure.ai.agents/internal/cmd/agent_endpoint.go Outdated
Comment thread cli/azd/extensions/azure.ai.agents/internal/cmd/invoke.go Outdated
Comment thread cli/azd/extensions/azure.ai.agents/internal/cmd/invoke.go Outdated
Comment thread cli/azd/extensions/azure.ai.agents/internal/cmd/agent_endpoint.go Outdated
Comment thread cli/azd/extensions/azure.ai.agents/internal/cmd/invoke.go Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

Comment thread cli/azd/extensions/azure.ai.agents/internal/cmd/invoke.go Outdated
Comment thread cli/azd/extensions/azure.ai.agents/internal/cmd/invoke.go
Comment thread cli/azd/extensions/azure.ai.agents/internal/cmd/agent_endpoint_test.go Outdated
Comment thread cli/azd/extensions/azure.ai.agents/internal/cmd/agent_endpoint.go Outdated
Copy link
Copy Markdown
Member

@jongio jongio left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolveRemoteContext cleanly centralizes name/endpoint/api-version resolution. Deferring acquireBearerToken until after body validation avoids unnecessary token round-trips on input errors. URL parser is thorough (scheme, host suffix, port rejection, path structure, agent-name charset, empty api-version).

One gap: ephemeral responses mode creates a conversation via createConversation but doesn't print a --conversation-id continuation hint. See thread on printEphemeralSessionHint.

Copy link
Copy Markdown
Contributor

@wbreza wbreza left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Summary

This PR adds an --agent-endpoint flag to azd ai agent invoke, enabling users to invoke Foundry agents from any directory without needing an azd project. The code is well-structured with good URL parsing/validation in a dedicated agent_endpoint.go file. Most prior feedback has been addressed across 6 iterative commits.

Should Fix

1. Empty API version produces malformed request URL
When a user provides an endpoint URL without a ?api-version= query parameter, ApiVersion is set to in the parsed struct.buildResponsesURL()/buildInvocationsURL()then produce?api-version=(key present, empty value). The existing fix rejects explicit?api-version=with no value, but not the absence of the parameter entirely. Consider falling back toDefaultAgentAPIVersionwhenapiVersion` is empty, or rejecting URLs missing the parameter.

2. Ephemeral conversation hint missing from invocationsRemote()
responsesRemote() prints both printEphemeralSessionHint and printEphemeralConversationHint, but invocationsRemote() only prints the session hint. Users invoking with --protocol invocations in ephemeral mode won't see how to continue their conversation — inconsistent UX between the two protocols.

3. Missing test coverage for resolveRemoteContext() and ephemeral integration flow
resolveRemoteContext() is a critical new function that branches between ephemeral and project modes, handles auth, and manages resource cleanup. It currently has no tests. The parsing helpers have good coverage, but the orchestration layer is untested.

4. Tests use raw t.Errorf/t.Fatalf instead of testify assertions
Repo convention uses testify (require/assert). The new agent_endpoint_test.go uses raw Go testing patterns. Should use require.NoError(t, err), require.Equal(t, expected, actual), etc.

5. Credential error messages may expose auth infrastructure details
ephemeralAuthError() uses fmt.Sprintf("failed to get auth token: %v", err) which may surface OAuth endpoint URLs or credential manager internals via %v formatting.

Nitpick

6. Several test functions in agent_endpoint_test.go are missing t.Parallel().

7. Variable url shadows the net/url package import — consider renaming to respURL.

Copy link
Copy Markdown
Member

@jongio jongio left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addresses my previous feedback - the conversation-id continuation hint is clean and well-tested. The guard logic (skip when user already supplied --conversation-id, skip when no conversation was created) covers the right cases. LGTM.

Comment thread cli/azd/extensions/azure.ai.agents/internal/cmd/agent_endpoint.go Outdated
Comment thread cli/azd/extensions/azure.ai.agents/internal/cmd/agent_endpoint.go Outdated
Comment thread cli/azd/extensions/azure.ai.agents/internal/cmd/invoke.go
@antriksh30 antriksh30 force-pushed the antriksh30/invoke-agent-endpoint-url-fix branch from 7d2fc12 to 5948c96 Compare May 5, 2026 05:42
@antriksh30 antriksh30 requested a review from Copilot May 5, 2026 05:46
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

Comment thread cli/azd/extensions/azure.ai.agents/internal/cmd/invoke.go Outdated
Comment thread cli/azd/extensions/azure.ai.agents/internal/cmd/invoke.go Outdated
Comment thread cli/azd/extensions/azure.ai.agents/internal/cmd/invoke.go
@antriksh30 antriksh30 requested a review from Copilot May 5, 2026 06:54
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Comment thread cli/azd/extensions/azure.ai.agents/internal/cmd/invoke.go Outdated
Comment thread cli/azd/extensions/azure.ai.agents/internal/cmd/invoke.go Outdated
Comment thread cli/azd/extensions/azure.ai.agents/internal/cmd/invoke.go Outdated
Comment thread cli/azd/extensions/azure.ai.agents/internal/cmd/agent_endpoint.go
Comment thread cli/azd/extensions/azure.ai.agents/internal/cmd/config_store.go Outdated
Comment thread cli/azd/extensions/azure.ai.agents/internal/cmd/invoke.go Outdated
Comment thread cli/azd/extensions/azure.ai.agents/internal/cmd/invoke.go Outdated
Copy link
Copy Markdown
Member

@jongio jongio left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-approving after the rebase and rework. The config persistence layer (config_store.go) is clean - proper validation, key normalization, legacy fallback with rewrite. The stderr fix for warnIneffectiveResetFlags, delegation to agent_yaml.ValidateAgentName, and the tip messages are all good. All prior findings addressed.

@JeffreyCA JeffreyCA added the ext-agents azure.ai.agents extension label May 5, 2026
Copy link
Copy Markdown
Contributor

@wbreza wbreza left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-Review (post-rebase) — Approve

The PR was rebased with 7 new commits. 5 of 7 original findings are resolved. New commits fix real bugs: broken saveContextValue(..., "invocations"), suppressed log.Printf warnings, and loose agent name validation now delegated to �gent_yaml.ValidateAgentName.

Previous Findings — Status

Finding Status
Empty API version → malformed URL ✅ Resolved
Conversation hint missing from invocationsRemote() ✅ Resolved
Credential error exposure ✅ Resolved
Missing .Parallel() ✅ Resolved
Variable shadowing ✅ Resolved
Missing
esolveRemoteContext() tests ⚠️ Still open
Raw .Errorf vs testify ⚠️ Still open

New Findings

🟡 Should Fix

1.
esolveRemoteContext() still has no dedicated tests

This function branches between ephemeral and project modes, manages �zdClient lifecycle, and handles auth. It's the critical orchestration point but remains untested in isolation. Parsing helpers have good coverage; the integration layer does not.

2. warnIneffectiveResetFlags() doesn't account for protocol
--new-conversation is always a no-op for invocations protocol (memory is session-bound), but the warning only fires when there's no parent daemon. A user with a daemon running invocations + --new-conversation gets no warning.

🟢 Nitpick

3. �gent_endpoint_test.go assertion style inconsistency
config_store_test.go correctly uses �ssert.Equal()/�ssert.NotEqual(), but �gent_endpoint_test.go still uses .Fatalf/ .Errorf. Standardizing would improve consistency.

4. No test for URL-encoded invalid agent names
TestParseAgentEndpoint_RejectsInvalidAgentNames tests underscores, length, and hyphens — but not URL-encoded characters like �gent%20name. Likely works via ValidateAgentName() but worth a test case.

@antriksh30 antriksh30 linked an issue May 5, 2026 that may be closed by this pull request
@antriksh30 antriksh30 requested a review from trangevi May 5, 2026 20:54
@antriksh30
Copy link
Copy Markdown
Contributor Author

antriksh30 commented May 6, 2026

Thanks for the re-review @wbreza. Status on the remaining items:

New #1 resolveRemoteContext() tests Addressed in 854e901.
Added TestResolveRemoteContext_EphemeralMode covering the ephemeral branch end-to-end: api-version default fallback (URL omits ?api-version=), explicit override, and name/projectEndpoint/agentKey propagation. The project-mode branch depends on the azd gRPC client and is exercised by the live tests in this PR's verification rather than unit tests.

New #2warnIneffectiveResetFlags() protocol awareness Function no longer exists.
It was removed in 9ed62d9d during the rebase because the warning path was unreachable: runRemote() always reaches the per-protocol branches, and both responsesRemote() and invocationsRemote() print their own protocol-specific notes when reset flags don't apply (e.g., note: --new-conversation has no effect for the invocations protocol (memory is bound to the session; use --new-session to reset) at invoke.go:736-739). The exact gap you describe (daemon + invocations + --new-conversation) is now covered by that protocol-aware note.

Antriksh Jain and others added 11 commits May 6, 2026 13:56
Adds a new --agent-endpoint flag to 'azd ai agent invoke' that accepts
the full Foundry agent invocation URL printed by 'azd up' / 'azd deploy'
and lets the user invoke a deployed agent from any directory without an
azd project on disk.

* Parses the URL strictly: requires https, the *.services.ai.azure.com
  Foundry host, the canonical /api/projects/.../agents/.../endpoint/
  protocols/<protocol>[?api-version=...] path, no explicit port, and a
  non-empty api-version when present.
* Derives the protocol (invocations or openai/responses) from the URL
  and rejects any flags that have no meaning in ephemeral mode (--local,
  positional name, --port, --protocol, --new-session, --new-conversation).
* Body validation runs before bearer-token acquisition so local input
  errors surface ahead of any auth round-trip.
* Prints continuation hints for both server-assigned --session-id and
  auto-created --conversation-id so users can preserve multi-turn state
  on the next invoke.
* Adds buildResponsesURL / buildInvocationsURL helpers and unit tests
  covering api-version propagation and URL-encoding of session ids.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- agent_endpoint.go: replace ad-hoc segment-by-segment path validation
  with a single regex match (matches existing projectResourceIdRegex
  style elsewhere in the package).
- agent_endpoint.go: introduce agentEndpointHint constant and use it
  everywhere the previous 'pass the agent endpoint printed by azd up
  or azd deploy' message appeared. Now points users at
  'azd ai agent show', which persistently prints the endpoint URL.
- invoke.go: collapse validateAgentEndpointFlags' six-case switch into
  a generic table-driven loop over disallowed flags.
- agent_endpoint_test.go: update unknown_protocol_tail expectation to
  match the unified regex error message.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- invoke.go: restore the `Invocation:` print line that was lost during
  the rebase. Previously, `invocationsRemote` always printed the
  `x-agent-invocation-id` header so users could correlate the call
  for tracing. The rebased version only persisted it (and only in
  project mode), so `--agent-endpoint` callers and project-mode
  callers both lost the visible handle. Restore the print and keep
  the persist as an extra step in project mode.

- agent_endpoint.go: reject `%2F` (or other encoded path separators)
  inside the project segment of `--agent-endpoint`. The regex captures
  `[^/]+` against the escaped path, so an encoded slash slipped through
  validation and `url.PathUnescape` then materialized a literal `/`
  in the project name. Add a `ContainsAny(name, "/\\")` check to
  match the strictness already applied to the agent segment. Add a
  test case covering `proj%2Fother`.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
After PR Azure#8034 the session/conversation store is keyed by an endpoint-derived agentKey in global UserConfig (env-independent). This wires --agent-endpoint into that store so ephemeral invokes auto-resume across calls.

Changes:

- Add buildEphemeralAgentKey: a stable, query-string-free key derived from the parsed projectEndpoint+agentName. Distinct '/ephemeral' suffix so it never collides with project-mode '/remote' keys.

- resolveRemoteContext (ephemeral): best-effort attach the parent azd daemon and set rc.agentKey. Standalone runs (no daemon) silently fall back to no-persistence.

- responsesRemote / invocationsRemote: drop the local agentKey computation; use rc.agentKey. Tighten OpenAPI-spec fetch to project mode only (no on-disk side effect for ephemeral).

- Drop --new-session / --new-conversation from validateAgentEndpointFlags so users can reset stored IDs in ephemeral mode. Add warnIneffectiveResetFlags to log a no-op warning when standalone.

- Continuation hints now require resp.StatusCode<400 so failed invokes don't tell users to continue a never-created conversation (review feedback).

- Hint gate widened to (agentKey == '' || azdClient == nil) so it fires whenever persistence is genuinely unavailable, not just when the daemon is missing.

Tests: TestBuildEphemeralAgentKey covers URL-variant stability (canonical, trailing-slash, mixed case host) and the project-mode key-collision contract. The two dropped --new-* validation cases removed from TestAgentEndpointFlagValidation.

Live-verified against the deployed responses agent: invokes 1+2 share session+conversation IDs and the agent recalls prior turns; invoke 3 with --new-session --new-conversation produces fresh IDs; invoke 4 with a URL variant (no ?api-version) hits the same persisted entry.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The standalone-mode hint code (printEphemeralSessionHint /
printEphemeralConversationHint) only fired when the parent azd daemon
was unreachable -- a path that does not occur in normal user flow
(�zd ai agent invoke ... always spawns the daemon). With persistence
working under Azure#8034 those hints were essentially dead code.

Remove both helpers (and their tests / unused captureStdout helper /
net/http import) and replace with a single concise tip printed after a
successful invoke when persistence is active in both responsesRemote
and invocationsRemote:

  (tip: pass --new-session or --new-conversation to reset; see
   `azd ai agent invoke --help`)

Tests + lint clean. Live verified end-of-output ordering against
hello-world-python-responses.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The post-invoke tip and reset-flag handling now match each protocol's
actual memory model:

- Responses protocol keeps the existing tip mentioning both
  --new-session and --new-conversation (it uses the Foundry
  Conversations API for multi-turn memory).
- Invocations protocol prints a tip that only mentions --new-session,
  since memory is bound to the session and --new-conversation has no
  observable effect on this path.
- If the user does pass --new-conversation while invoking an
  invocations endpoint, a stderr note explains that the flag is a
  no-op for this protocol and points to --new-session instead.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- agent_endpoint.go: agent-name validation now delegates to
  agent_yaml.ValidateAgentName so --agent-endpoint enforces the same
  deployable-name format as the rest of the extension. Previously
  underscores and unbounded lengths slipped through local validation
  only to fail later as 404s. The bespoke isValidAgentNameSegment
  helper and its unit test are removed; a new
  TestParseAgentEndpoint_RejectsInvalidAgentNames covers underscore,
  length>63, and leading/trailing hyphen rejection.

- invoke.go (warnIneffectiveResetFlags): switched from log.Printf to
  fmt.Fprintln(os.Stderr, ...). The extension silences the standard
  logger unless debug mode is enabled (setupDebugLogging redirects to
  io.Discard), so the previous warning never reached users in
  standalone --agent-endpoint mode.

- invoke.go (invocationsRemote): removed the saveContextValue(...,
  "invocations") call. validateStoreField only allows "sessions" and
  "conversations", so the persistence call always failed silently. The
  invocation ID is still printed for trace correlation; we just no
  longer pretend to persist it.

- config_store.go: rewrote a doc comment to use "scopes" instead of
  "lifecycles" so the cspell pipeline passes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Per PR review (trangevi): unify ephemeral and project-mode key builders.
buildAgentKey(ep, name, '', false) yields the same canonical shape used
elsewhere  '<ep>/agents/<name>/versions/latest/remote'  and inherits
the segment validation logic for free.

- Remove buildEphemeralAgentKey helper (config_store.go)
- Update sole call site in invocations setup (invoke.go)
- Drop now-redundant ephemeral-specific tests; URL-variant stability
  remains covered by TestNormalizeEndpoint_StripScheme and
  TestBuildRemoteAgentKeyFromEndpoint

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Per PR review (trangevi): the example used the /protocols/invocations URL
paired with a plain "Hello!" body, but most invocations samples expect a
JSON request body. Switch the example to the responses-protocol URL
(/protocols/openai/responses) which matches the plain-string body shape
and aligns with the other "Hello!" examples in this help block.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The warning only fired when running the extension binary standalone with --agent-endpoint and a reset flag (no parent azd daemon). End-user invokes always go through the host and persist via gRPC, so the warn was effectively dead for the supported flow.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Rename url locals to respURL/convURL to avoid shadowing the net/url
  package import (invoke.go: responsesRemote and createConversation).
- Add t.Parallel() to the 4 functions in agent_endpoint_test.go.
- Add TestResolveRemoteContext_EphemeralMode covering the api-version
  default fallback (when URL omits ?api-version=) and explicit override,
  plus name/projectEndpoint/agentKey propagation. Pins the existing safe
  behavior end-to-end.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@antriksh30 antriksh30 force-pushed the antriksh30/invoke-agent-endpoint-url-fix branch from 854e901 to 20d23e4 Compare May 6, 2026 08:28
@antriksh30 antriksh30 requested review from trangevi and wbreza May 6, 2026 08:33
@trangevi
Copy link
Copy Markdown
Member

trangevi commented May 6, 2026

/check-enforcer override

@trangevi trangevi merged commit df29300 into Azure:main May 6, 2026
21 of 23 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ext-agents azure.ai.agents extension

Projects

None yet

Development

Successfully merging this pull request may close these issues.

invoke agent using the agent endpoint

7 participants