From dd57b1a6908faa54c59b3e35cd9083a667bbc227 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Jul 2026 08:46:56 +0000 Subject: [PATCH 1/3] Initial plan From 059c8bf58e359590ac2ae6c45bca50749bbc60ec Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Jul 2026 08:55:05 +0000 Subject: [PATCH 2/3] Fix: suppress invoke hints for ProtocolInvocationsWS in all resolver paths - Add ProtocolInvocationsWS constant ("invocations_ws") - Guard in appendInvokeLocalSecondary: skip invoke hint for WS agents - Guard in ResolveAfterRun: return nil for WS agents (not invocable) - Guard in ResolveAfterDeploy pass 2: skip invoke line for WS services - Add ProtocolInvocationsWS case in loadServiceProtocol (state.go) - Add regression tests for run/deploy paths with ProtocolInvocationsWS Co-authored-by: v1212 <49907914+v1212@users.noreply.github.com> --- .../internal/cmd/nextstep/resolver.go | 16 +++++++ .../internal/cmd/nextstep/resolver_test.go | 44 +++++++++++++++++++ .../internal/cmd/nextstep/state.go | 2 + .../internal/cmd/nextstep/state_test.go | 9 ++++ 4 files changed, 71 insertions(+) diff --git a/cli/azd/extensions/azure.ai.agents/internal/cmd/nextstep/resolver.go b/cli/azd/extensions/azure.ai.agents/internal/cmd/nextstep/resolver.go index 4cb658aaea9..45db0c2aa74 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/cmd/nextstep/resolver.go +++ b/cli/azd/extensions/azure.ai.agents/internal/cmd/nextstep/resolver.go @@ -17,6 +17,9 @@ const ( // ProtocolResponses is the value of `agent.yaml#protocol` for plain // text /responses agents. ProtocolResponses = "responses" + // ProtocolInvocationsWS is the value of `agent.yaml#protocol` for + // bidirectional WebSocket /invocations_ws agents. + ProtocolInvocationsWS = "invocations_ws" // placeholderPayload is the single-quoted literal the resolver // emits as the body argument when no concrete payload is known — @@ -331,6 +334,11 @@ func ResolveAfterRun(state *State, serviceName string, readmeExists func(relativ svc := findService(state, serviceName) + // WebSocket agents are not invocable via azd ai agent invoke --local. + if svc != nil && svc.Protocol == ProtocolInvocationsWS { + return nil + } + cachedPayload := "" if state.HasOpenAPI && state.OpenAPIPayload != "" { cachedPayload = state.OpenAPIPayload @@ -642,6 +650,11 @@ func ResolveAfterDeploy( for i := range state.Services { svc := &state.Services[i] + // WebSocket agents are not invocable; skip the invoke line. + if svc.Protocol == ProtocolInvocationsWS { + continue + } + cached := "" if cachedPayload != nil { cached = cachedPayload(svc.Name) @@ -770,6 +783,9 @@ func appendInvokeLocalSecondary( if len(state.Services) == 1 { svc = &state.Services[0] } + if svc != nil && svc.Protocol == ProtocolInvocationsWS { + return out, priority + } invokeArg, readmeHint := resolveInvokeArg(svc, "", readmeExists, priority) if readmeHint != nil { out = append(out, *readmeHint) diff --git a/cli/azd/extensions/azure.ai.agents/internal/cmd/nextstep/resolver_test.go b/cli/azd/extensions/azure.ai.agents/internal/cmd/nextstep/resolver_test.go index 231a60eb8cd..9ea0201c233 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/cmd/nextstep/resolver_test.go +++ b/cli/azd/extensions/azure.ai.agents/internal/cmd/nextstep/resolver_test.go @@ -804,6 +804,14 @@ func TestResolveAfterRun(t *testing.T) { `azd ai agent invoke --local '{"q":"don'\''t"}'`, }, }, + { + name: "invocations_ws protocol → no invoke suggestion (not invocable)", + state: &State{ + Services: []ServiceState{{Name: "ws", Protocol: ProtocolInvocationsWS}}, + }, + serviceName: "ws", + want: nil, + }, } for _, tt := range tests { @@ -1174,6 +1182,42 @@ func TestResolveAfterDeploy(t *testing.T) { require.Len(t, out, 2) assert.Equal(t, "azd ai agent show echo", out[0].Command) }) + + t.Run("single invocations_ws agent → show only, no invoke (not invocable)", func(t *testing.T) { + t.Parallel() + state := &State{Services: []ServiceState{ + {Name: "ws", Protocol: ProtocolInvocationsWS}, + }} + out := ResolveAfterDeploy(state, nil, nil) + require.Len(t, out, 1) + assert.Equal(t, "azd ai agent show ws", out[0].Command) + }) + + t.Run("all invocations_ws multi-service → shows only, no invokes", func(t *testing.T) { + t.Parallel() + state := &State{Services: []ServiceState{ + {Name: "ws1", Protocol: ProtocolInvocationsWS}, + {Name: "ws2", Protocol: ProtocolInvocationsWS}, + }} + out := ResolveAfterDeploy(state, nil, nil) + require.Len(t, out, 2) + assert.Equal(t, "azd ai agent show ws1", out[0].Command) + assert.Equal(t, "azd ai agent show ws2", out[1].Command) + }) + + t.Run("mixed protocols → show all, invoke only non-WS", func(t *testing.T) { + t.Parallel() + state := &State{Services: []ServiceState{ + {Name: "ws", Protocol: ProtocolInvocationsWS}, + {Name: "regular", Protocol: ProtocolInvocations}, + }} + out := ResolveAfterDeploy(state, nil, nil) + // 2 shows + 1 invoke (ws is skipped) + require.Len(t, out, 3) + assert.Equal(t, "azd ai agent show ws", out[0].Command) + assert.Equal(t, "azd ai agent show regular", out[1].Command) + assert.Equal(t, `azd ai agent invoke regular ''`, out[2].Command) + }) } func TestFindService(t *testing.T) { diff --git a/cli/azd/extensions/azure.ai.agents/internal/cmd/nextstep/state.go b/cli/azd/extensions/azure.ai.agents/internal/cmd/nextstep/state.go index bf8bc5078c0..83e0036940b 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/cmd/nextstep/state.go +++ b/cli/azd/extensions/azure.ai.agents/internal/cmd/nextstep/state.go @@ -482,6 +482,8 @@ func loadServiceProtocol(projectPath, relativePath string) string { switch strings.TrimSpace(p.Protocol) { case ProtocolResponses: return ProtocolResponses + case ProtocolInvocationsWS: + return ProtocolInvocationsWS case ProtocolInvocations: sawInvocations = true } diff --git a/cli/azd/extensions/azure.ai.agents/internal/cmd/nextstep/state_test.go b/cli/azd/extensions/azure.ai.agents/internal/cmd/nextstep/state_test.go index 4663ad937f2..b74cec0a04f 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/cmd/nextstep/state_test.go +++ b/cli/azd/extensions/azure.ai.agents/internal/cmd/nextstep/state_test.go @@ -596,6 +596,15 @@ protocols: `, want: ProtocolInvocations, }, + { + name: "single invocations_ws protocol", + manifest: `kind: hostedAgent +protocols: + - protocol: invocations_ws + version: "2.0.0" +`, + want: ProtocolInvocationsWS, + }, { name: "responses wins when both declared", manifest: `kind: hostedAgent From 3c6098cee531905f198afb7c911b941e0dc452a4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Jul 2026 08:55:52 +0000 Subject: [PATCH 3/3] Add consistent comment to appendInvokeLocalSecondary WS guard Co-authored-by: v1212 <49907914+v1212@users.noreply.github.com> --- .../extensions/azure.ai.agents/internal/cmd/nextstep/resolver.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cli/azd/extensions/azure.ai.agents/internal/cmd/nextstep/resolver.go b/cli/azd/extensions/azure.ai.agents/internal/cmd/nextstep/resolver.go index 45db0c2aa74..e6bae18a022 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/cmd/nextstep/resolver.go +++ b/cli/azd/extensions/azure.ai.agents/internal/cmd/nextstep/resolver.go @@ -784,6 +784,7 @@ func appendInvokeLocalSecondary( svc = &state.Services[0] } if svc != nil && svc.Protocol == ProtocolInvocationsWS { + // WebSocket agents are not invocable; skip the invoke hint. return out, priority } invokeArg, readmeHint := resolveInvokeArg(svc, "", readmeExists, priority)