diff --git a/go/cli/internal/cli/get.go b/go/cli/internal/cli/get.go index 54bcec421..6ed8acef4 100644 --- a/go/cli/internal/cli/get.go +++ b/go/cli/internal/cli/get.go @@ -105,13 +105,15 @@ func printTools(tools []database.Tool) error { func printAgents(agents []api.AgentResponse) error { // Prepare table data - headers := []string{"#", "NAME", "CREATED"} + headers := []string{"#", "NAME", "CREATED", "DEPLOYMENT_READY", "ACCEPTED"} rows := make([][]string, len(agents)) for i, agent := range agents { rows[i] = []string{ strconv.Itoa(i + 1), utils.GetObjectRef(agent.Agent), agent.Agent.CreationTimestamp.Format(time.RFC3339), + strconv.FormatBool(agent.DeploymentReady), + strconv.FormatBool(agent.Accepted), } } diff --git a/go/internal/httpserver/handlers/agents.go b/go/internal/httpserver/handlers/agents.go index 829c23890..8cc37b2bd 100644 --- a/go/internal/httpserver/handlers/agents.go +++ b/go/internal/httpserver/handlers/agents.go @@ -48,11 +48,9 @@ func (h *AgentsHandler) HandleListAgents(w ErrorResponseWriter, r *http.Request) agentRef := common.GetObjectRef(&agent) log.V(1).Info("Processing Agent", "agentRef", agentRef) - agentResponse, err := h.getAgentResponse(r.Context(), log, &agent) - if err != nil { - w.RespondWithError(err) - return - } + // When listing agents, we don't want a failure when a single agent has an issue, so we ignore the error. + // The getAgentResponse should return its reconciliation status in the agentResponse. + agentResponse, _ := h.getAgentResponse(r.Context(), log, &agent) agentsWithID = append(agentsWithID, agentResponse) } @@ -75,10 +73,20 @@ func (h *AgentsHandler) getAgentResponse(ctx context.Context, log logr.Logger, a } } + accepted := false + for _, condition := range agent.Status.Conditions { + // The exact reason is not important (although "AgentReconciled" is the current one), as long as the agent is accepted + if condition.Type == "Accepted" && condition.Status == "True" { + accepted = true + break + } + } + response := api.AgentResponse{ ID: common.ConvertToPythonIdentifier(agentRef), Agent: agent, DeploymentReady: deploymentReady, + Accepted: accepted, } if agent.Spec.Type == v1alpha2.AgentType_Declarative { diff --git a/go/internal/httpserver/handlers/agents_test.go b/go/internal/httpserver/handlers/agents_test.go index fc92cda2a..1f06ed9fa 100644 --- a/go/internal/httpserver/handlers/agents_test.go +++ b/go/internal/httpserver/handlers/agents_test.go @@ -118,7 +118,7 @@ func TestHandleGetAgent(t *testing.T) { require.False(t, response.Data.DeploymentReady) // No status conditions, should be false }) - t.Run("gets agent with DeploymentReady=true", func(t *testing.T) { + t.Run("gets agent with DeploymentReady=true, Accepted=true", func(t *testing.T) { modelConfig := createTestModelConfig() conditions := []metav1.Condition{ { @@ -150,6 +150,7 @@ func TestHandleGetAgent(t *testing.T) { err := json.Unmarshal(w.Body.Bytes(), &response) require.NoError(t, err) require.True(t, response.Data.DeploymentReady) + require.True(t, response.Data.Accepted) }) t.Run("gets agent with DeploymentReady=false when Ready status is False", func(t *testing.T) { @@ -235,6 +236,11 @@ func TestHandleListAgents(t *testing.T) { Status: "True", Reason: "DeploymentReady", }, + { + Type: "Accepted", + Status: "True", + Reason: "AgentReconciled", + }, } readyAgent := createTestAgentWithStatus("ready-agent", modelConfig, readyConditions) @@ -269,6 +275,63 @@ func TestHandleListAgents(t *testing.T) { require.Equal(t, v1alpha2.ModelProviderOpenAI, response.Data[1].ModelProvider) require.Equal(t, true, response.Data[1].DeploymentReady) }) + + t.Run("lists expected agent conditions", func(t *testing.T) { + modelConfig := createTestModelConfig() + + // Agent with DeploymentReady=true + readyConditions := []metav1.Condition{ + { + Type: "Ready", + Status: "True", + Reason: "DeploymentReady", + }, + { + Type: "Accepted", + Status: "True", + Reason: "AgentReconciled", + }, + } + invalidConditions := []metav1.Condition{ // an agent's deployment can be ready although it's configuration is invalid + { + Type: "Accepted", + Status: "False", + Reason: "AgentReconcileFailed", + }, + { + Type: "Ready", + Status: "True", + Reason: "DeploymentReady", + }, + } + readyAgent := createTestAgentWithStatus("ready-agent", modelConfig, readyConditions) + invalidAgent := createTestAgentWithStatus("invalid-agent", modelConfig, invalidConditions) + + handler, _ := setupTestHandler(readyAgent, invalidAgent, modelConfig) + createAgent(handler.DatabaseService, readyAgent) + createAgent(handler.DatabaseService, invalidAgent) + + req := httptest.NewRequest("GET", "/api/agents", nil) + req = setUser(req, "test-user") + + w := httptest.NewRecorder() + + handler.HandleListAgents(&testErrorResponseWriter{w}, req) + + require.Equal(t, http.StatusOK, w.Code) + + // both agents are returned with their statuses + var response api.StandardResponse[[]api.AgentResponse] + err := json.Unmarshal(w.Body.Bytes(), &response) + require.NoError(t, err) + require.Len(t, response.Data, 2) + require.Equal(t, "ready-agent", response.Data[1].Agent.Name) + require.Equal(t, true, response.Data[1].Accepted) + require.Equal(t, true, response.Data[1].DeploymentReady) + require.Equal(t, "invalid-agent", response.Data[0].Agent.Name) + require.Equal(t, false, response.Data[0].Accepted) + require.Equal(t, true, response.Data[0].DeploymentReady) + }) } func TestHandleUpdateAgent(t *testing.T) { diff --git a/go/pkg/client/api/types.go b/go/pkg/client/api/types.go index 03dd3d3d3..88fa2c1b0 100644 --- a/go/pkg/client/api/types.go +++ b/go/pkg/client/api/types.go @@ -92,6 +92,7 @@ type AgentResponse struct { MemoryRefs []string `json:"memoryRefs"` Tools []*v1alpha2.Tool `json:"tools"` DeploymentReady bool `json:"deploymentReady"` + Accepted bool `json:"accepted"` } // Session types diff --git a/ui/src/components/AgentCard.tsx b/ui/src/components/AgentCard.tsx index 5762ffe28..5aa3d529b 100644 --- a/ui/src/components/AgentCard.tsx +++ b/ui/src/components/AgentCard.tsx @@ -13,7 +13,7 @@ interface AgentCardProps { id: number; } -export function AgentCard({ id, agentResponse: { agent, model, modelProvider, deploymentReady } }: AgentCardProps) { +export function AgentCard({ id, agentResponse: { agent, model, modelProvider, deploymentReady, accepted } }: AgentCardProps) { const router = useRouter(); const agentRef = k8sRefUtils.toRef( agent.metadata.namespace || '', @@ -29,7 +29,7 @@ export function AgentCard({ id, agentResponse: { agent, model, modelProvider, de const cardContent = ( @@ -37,26 +37,31 @@ export function AgentCard({ id, agentResponse: { agent, model, modelProvider, de {agentRef} + {!accepted && ( + + Not Accepted + + )} {!deploymentReady && ( Not Ready )} -
+
@@ -73,7 +78,7 @@ export function AgentCard({ id, agentResponse: { agent, model, modelProvider, de ); - return deploymentReady ? ( + return deploymentReady && accepted ? ( {cardContent} diff --git a/ui/src/components/sidebars/AgentSwitcher.tsx b/ui/src/components/sidebars/AgentSwitcher.tsx index a9b089840..92bbc668b 100644 --- a/ui/src/components/sidebars/AgentSwitcher.tsx +++ b/ui/src/components/sidebars/AgentSwitcher.tsx @@ -19,18 +19,21 @@ export function AgentSwitcher({ currentAgent, allAgents }: AgentSwitcherProps) { const router = useRouter(); const { isMobile } = useSidebar(); - const selectedTeam = currentAgent; + const selectedAgent = currentAgent; const agentResponses = allAgents; - if (!selectedTeam) { + if (!selectedAgent) { return null; } - const selectedTeamRef = k8sRefUtils.toRef( - selectedTeam.agent.metadata.namespace || "", - selectedTeam.agent.metadata.name + const selectedAgentRef = k8sRefUtils.toRef( + selectedAgent.agent.metadata.namespace || "", + selectedAgent.agent.metadata.name ); + // We don't want to show agents that are not ready or accepted + const filteredAgentResponses = agentResponses.filter(({ deploymentReady, accepted }) => deploymentReady && accepted); + return ( @@ -41,15 +44,15 @@ export function AgentSwitcher({ currentAgent, allAgents }: AgentSwitcherProps) {
- {selectedTeamRef} - {selectedTeam.modelProvider} ({selectedTeam.model}) + {selectedAgentRef} + {selectedAgent.modelProvider} ({selectedAgent.model})
Agents - {agentResponses.map(({ id, agent}, index) => { + {filteredAgentResponses.map(({ id, agent }, index) => { const agentRef = k8sRefUtils.toRef(agent.metadata.namespace || "", agent.metadata.name) return (