Skip to content

[WIP] feat: add agent inspector into azd cli#8125

Draft
XiaofuHuang wants to merge 12 commits into
Azure:mainfrom
XiaofuHuang:xiaofua/azd-inspector-pr-review
Draft

[WIP] feat: add agent inspector into azd cli#8125
XiaofuHuang wants to merge 12 commits into
Azure:mainfrom
XiaofuHuang:xiaofua/azd-inspector-pr-review

Conversation

@XiaofuHuang
Copy link
Copy Markdown

Spec: https://github.com/coreai-microsoft/foundrysdk_specs/pull/169

This pull request introduces the Agent Inspector UI to the Azure AI Agents extension, allowing users to launch a browser-based inspector for local agents without any Python or Node.js runtime dependencies. The main changes add a new --inspector mode to the azd ai agent invoke command, which serves a standalone Single Page Application (SPA) built from the VS Code extension and hosts it with an embedded Go HTTP/WebSocket server. This enables local agent inspection and debugging in a user-friendly way.

The most important changes are:

Agent Inspector UI integration:

  • Adds a new --inspector flag (with --inspector-port) to the azd ai agent invoke command. When enabled (and combined with --local), this launches a browser-based inspector UI connected to the local agent, instead of streaming responses to the terminal. Input validation ensures this flag is only used in supported scenarios. (invoke.go, invoke_inspector.go) [1] [2] [3] [4] [5] [6]
  • Implements the inspector backend in Go, embedding the SPA assets and serving them via an in-process HTTP server. The backend reimplements the Python rpc_handler.py logic, supporting the required WebSocket JSON-RPC protocol for the UI. (internal/inspector/assets.go, internal/inspector/proxy_fetch.go) [1] [2]
  • Includes the frontend SPA assets (HTML, JS, CSS, highlight.js themes) in the repository and sets up a PowerShell script to build and stage these assets for embedding. (internal/inspector/assets/assets/*, internal/inspector/assets/index.html, build-frontend.ps1) [1] [2] [3] [4]

Dependency and build system updates:

  • Updates go.mod to add direct dependencies on github.com/cli/browser (for browser launching) and github.com/gorilla/websocket (for WebSocket support). (go.mod) [1] [2]
  • Documents the build and embedding process for the inspector SPA, clarifying how to integrate the UI into the extension binary. (build-frontend.ps1, comments in code) [1] [2]

Testing and validation:

  • Adds unit tests for localhost assertion logic, ensuring the inspector only proxies requests to local endpoints for security. (internal/inspector/localhost_test.go)

Overall, these changes provide a seamless, cross-platform way to inspect and debug local agents via a modern browser UI, improving developer experience and removing the need for extra runtime dependencies.

@microsoft-github-policy-service microsoft-github-policy-service Bot added the customer-reported identify a customer issue label May 11, 2026
@microsoft-github-policy-service
Copy link
Copy Markdown
Contributor

Thank you for your contribution @XiaofuHuang! We will review the pull request and get back to you soon.

Adds a `--inspector` flag to `azd ai agent invoke` that launches the
Agent Inspector SPA in the browser and proxies localhost HTTP/SSE/RPC
to the agent running via `azd ai agent run --local`. The SPA is the
same one shipped by the VS Code extension built in `--mode azd`; its
assets are checked in under `internal/inspector/assets/` and embedded
via `go:embed`.

Includes table-driven tests for `assertLocalhost` and a server smoke
test that binds an ephemeral port and verifies SPA fallback.

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
@XiaofuHuang XiaofuHuang force-pushed the xiaofua/azd-inspector-pr-review branch from 22ae049 to e95eb9d Compare May 11, 2026 07:17
XiaofuHuang and others added 11 commits May 11, 2026 15:38
- Drop assertLocalhost and its test; remove dead compile-time check.
- Extract validateInspectorFlags; add DefaultInspectorPort constant.
- Server.Start signals readiness so browser launches after bind.
- sendError uses err.Error() and JSON-RPC -32603; log unhandled methods.
- SSE loop uses bufio.Reader.ReadString to avoid per-iteration alloc.
- Tests use strconv.Itoa and the ready channel.
- Simplify comments throughout.

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
- rpc.go: handle the new "inspector/fixRequested" SPA notification by
  logging "there is an error and user want AI to fix (source=...): ..."
  to the inspector logger. The standalone host has no Copilot UI, so
  this turns the SPA's Fix buttons into a usable signal during preview.
- Re-embed the rebuilt SPA bundle (azd flavor) carrying the matching
  frontend changes: Fix buttons now route through this notification,
  and Events/Tools/Output empty states say "Run the agent" instead of
  "Run the workflow" under VITE_HOST=azd.
handleFixRequested now writes the "user wants AI to fix" line directly
to os.Stderr in addition to the regular logger. The root command
redirects log.Default() to io.Discard (no --debug) or a daily file
(with --debug), so the existing s.logger.Printf was invisible at the
terminal. Other inspector logs stay on the existing path — only this
specific user-facing signal is teed to stderr.
- rpc.go handleFixRequested: prefix output with "[inspector]
  [fix-with-ai] ", drop the "(source=...)" tag, and indent
  continuation lines so multi-line summaries align under the prefix.
  Empty summaries fall back to "(no error details provided)".
- Re-embed the rebuilt SPA bundle (azd flavor) carrying the matching
  generateFixPrompt change: the "Fix this failed workflow step:
  <executorId>." prefix line is gone, leaving only the fields that
  actually carry data (Failed step / Error Code / Error Message /
  Traceback).
…Default)

Drop the URL-based "/responses" gate in proxyInvoke and the hardcoded
logRaw=false in proxyFetchSSE. Both now log unconditionally through
s.logger; --debug controls visibility through the existing
log.Default() routing (file with --debug, io.Discard without).

- proxyInvoke: always log POST body and response (full body for
  /responses, byte count for others to keep diagnostics readable).
- proxyInvoke streaming: pass logRaw=true so SSE chunks are logged for
  every streaming response, not just /responses.
- proxyFetchSSE: pass logRaw=true so SPA-driven SSE channels (e.g.
  /agentdev/v1/diagnostics that powers the Events tab) are also
  captured. Previously these were silently dropped even with --debug.
proxyInvoke now logs the full buffered response body for every URL,
not just /responses. Symmetry with the streaming SSE path which
already logs every chunk regardless of endpoint. --debug still
controls visibility through log.Default().
Add an SSESink callback on inspector.Config so the CLI receives the
proxied SSE bytes; when the inspector webview chats, the terminal now
echoes user input and streams the agent response (or error) using
the same printAgentResponse formatter as `azd ai agent invoke --local`.

Adapter layer (injectSSEEvents in invoke_inspector.go) synthesises
event: lines from the local agentserver's typed data: chunks so the
existing readSSEStream parser can be reused unchanged.

Also collapse the fix-with-ai stderr line to a single line.
Resolves the per-agent session_id and conversation.id via the existing
resolveStoredID helper and pipes them through inspector.Config and the
navigateToStep boot payload so the SPA continues whatever conversation
the CLI was using instead of minting a fresh UUID per browser mount.

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
Replace the proxyInvoke pump's split close paths (deferred resp.Body.Close
plus a goroutine close-on-cancel) with a single cancel()-driven close, so
Body is closed exactly once whether the stream ends normally or the
session is torn down mid-read. Add a test that asserts upstream sees its
request context cancelled within ~2s of the inspector WebSocket closing.

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

customer-reported identify a customer issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant