Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -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 —
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -770,6 +783,10 @@ func appendInvokeLocalSecondary(
if len(state.Services) == 1 {
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)
if readmeHint != nil {
out = append(out, *readmeHint)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 '<payload>'`, out[2].Command)
})
}

func TestFindService(t *testing.T) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down