Skip to content
Open
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
110 changes: 95 additions & 15 deletions commands/genai_agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package commands

import (
"fmt"
"strings"
"time"

"github.com/digitalocean/doctl"
"github.com/digitalocean/doctl/commands/displayers"
Expand Down Expand Up @@ -70,9 +72,34 @@ func AgentCmd() *Command {
aliasOpt("ls"),
displayerType(&displayers.Agent{}),
)

// Existing filters
AddStringFlag(cmdAgentList, doctl.ArgAgentRegion, "", "", "Retrieves a list of Agents in a specified region")
AddStringFlag(cmdAgentList, doctl.ArgTag, "", "", "Retrieves a list of Agents with a specified tag")
cmdAgentList.Example = `The following example retrieves a list of all Agent in the ` + "`" + `tor1` + "`" + ` region: doctl genai agent list --region tor1`

// New filtering options
AddStringFlag(cmdAgentList, doctl.ArgAgentName, "", "", "Filter agents by name (partial match)")
AddStringFlag(cmdAgentList, doctl.ArgModelId, "", "", "Filter agents by model ID")
AddStringFlag(cmdAgentList, doctl.ArgProjectID, "", "", "Filter agents by project ID")
AddStringFlag(cmdAgentList, "created-after", "", "", "Filter agents created after specified date (YYYY-MM-DD format)")
AddStringFlag(cmdAgentList, "created-before", "", "", "Filter agents created before specified date (YYYY-MM-DD format)")

cmdAgentList.Example = `The following examples show various filtering options:

# List all agents in tor1 region
doctl genai agent list --region tor1

# List agents with specific tag
doctl genai agent list --tag production

# List agents by name (partial match)
doctl genai agent list --name "chatbot"

# List agents in a specific project
doctl genai agent list --project-id "12345678-1234-1234-1234-123456789012"

# List agents created after 2024-01-01
doctl genai agent list --created-after "2024-01-01"`

cmdAgentGet := CmdBuilder(
cmd,
Expand Down Expand Up @@ -165,11 +192,16 @@ func AgentCmd() *Command {
return cmd
}

// RunAgentList lists all agents.
// RunAgentList lists all agents with enhanced filtering capabilities.
func RunAgentList(c *CmdConfig) error {
// Get filter parameters
region, _ := c.Doit.GetString(c.NS, "region")
projectId, _ := c.Doit.GetString(c.NS, "project-id")
tag, _ := c.Doit.GetString(c.NS, "tag")
name, _ := c.Doit.GetString(c.NS, "name")
modelId, _ := c.Doit.GetString(c.NS, "model-id")
createdAfter, _ := c.Doit.GetString(c.NS, "created-after")
createdBefore, _ := c.Doit.GetString(c.NS, "created-before")

agents, err := c.GenAI().ListAgents()
if err != nil {
Expand All @@ -178,27 +210,75 @@ func RunAgentList(c *CmdConfig) error {

filtered := make(do.Agents, 0, len(agents))
for _, agent := range agents {
if region != "" && agent.Agent.Region != region {
// Apply filters
if !matchesFilters(agent, region, projectId, tag, name, modelId, createdAfter, createdBefore) {
continue
}
if projectId != "" && agent.Agent.ProjectId != projectId {
continue
filtered = append(filtered, agent)
}

return c.Display(&displayers.Agent{Agents: filtered})
}

// matchesFilters applies all filtering logic to determine if an agent should be included
func matchesFilters(agent do.Agent, region, projectId, tag, name, modelId, createdAfter, createdBefore string) bool {
// Region filter
if region != "" && agent.Agent.Region != region {
return false
}

// Project ID filter
if projectId != "" && agent.Agent.ProjectId != projectId {
return false
}

// Tag filter (exact match)
if tag != "" {
found := false
for _, t := range agent.Agent.Tags {
if t == tag {
found = true
break
}
}
if !found {
return false
}
if tag != "" {
found := false
for _, t := range agent.Agent.Tags {
if t == tag {
found = true
break
}

// Name filter (partial match, case-insensitive)
if name != "" && !strings.Contains(strings.ToLower(agent.Agent.Name), strings.ToLower(name)) {
return false
}

// Model ID filter
if modelId != "" && agent.Agent.Model != nil && agent.Agent.Model.Uuid != modelId {
return false
}

// Note: Status and Visibility filters are not available in current godo.Agent struct

// Date filters
if createdAfter != "" || createdBefore != "" {
createdAt := agent.Agent.CreatedAt
if createdAt != nil && !createdAt.Time.IsZero() {
// Parse the input dates
if createdAfter != "" {
afterDate, err := time.Parse("2006-01-02", createdAfter)
if err == nil && createdAt.Time.Before(afterDate) {
return false
}
}
if !found {
continue
if createdBefore != "" {
beforeDate, err := time.Parse("2006-01-02", createdBefore)
if err == nil && createdAt.Time.After(beforeDate) {
return false
}
}
}
filtered = append(filtered, agent)
}
return c.Display(&displayers.Agent{Agents: filtered})

return true
}

// RunAgentCreate creates a new agent.
Expand Down
226 changes: 226 additions & 0 deletions commands/genai_agent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ package commands

import (
"testing"
"time"

"github.com/digitalocean/godo"

Expand Down Expand Up @@ -91,6 +92,231 @@ func TestRunAgentList(t *testing.T) {
assert.NoError(t, err)
})
}

func TestRunAgentListWithFilters(t *testing.T) {
// Create timestamps for testing
createdAt1 := &godo.Timestamp{Time: time.Date(2024, 1, 15, 10, 0, 0, 0, time.UTC)}
createdAt2 := &godo.Timestamp{Time: time.Date(2024, 2, 1, 15, 30, 0, 0, time.UTC)}

testAgents := do.Agents{
{
Agent: &godo.Agent{
Uuid: "00000000-0000-4000-8000-000000000001",
Name: "ChatBot Agent",
Region: "tor1",
ProjectId: "00000000-0000-4000-8000-000000000000",
Tags: []string{"production", "chatbot"},
Model: &godo.Model{
Uuid: "00000000-0000-4000-8000-000000000000",
},
CreatedAt: createdAt1,
},
},
{
Agent: &godo.Agent{
Uuid: "00000000-0000-4000-8000-000000000002",
Name: "Support Agent",
Region: "nyc1",
ProjectId: "00000000-0000-4000-8000-000000000001",
Tags: []string{"support", "internal"},
Model: &godo.Model{
Uuid: "00000000-0000-4000-8000-000000000001",
},
CreatedAt: createdAt2,
},
},
}

tests := []struct {
name string
filters map[string]string
expectedCount int
description string
}{
{
name: "Filter by region",
filters: map[string]string{"region": "tor1"},
expectedCount: 1,
description: "Should return only agents in tor1 region",
},
{
name: "Filter by project ID",
filters: map[string]string{"project-id": "00000000-0000-4000-8000-000000000000"},
expectedCount: 1,
description: "Should return only agents in specific project",
},
{
name: "Filter by name (partial match)",
filters: map[string]string{"name": "Chat"},
expectedCount: 1,
description: "Should return agents with name containing 'Chat'",
},
{
name: "Filter by tag",
filters: map[string]string{"tag": "production"},
expectedCount: 1,
description: "Should return agents with 'production' tag",
},
// Note: Status and Visibility filters are commented out as these fields
// may not exist in the current godo.Agent struct
// {
// name: "Filter by status",
// filters: map[string]string{"status": "active"},
// expectedCount: 1,
// description: "Should return only active agents",
// },
// {
// name: "Filter by visibility",
// filters: map[string]string{"visibility": "VISIBILITY_PUBLIC"},
// expectedCount: 1,
// description: "Should return only public agents",
// },
{
name: "Filter by model ID",
filters: map[string]string{"model-id": "00000000-0000-4000-8000-000000000000"},
expectedCount: 1,
description: "Should return agents using specific model",
},
{
name: "Filter by created after date",
filters: map[string]string{"created-after": "2024-01-20"},
expectedCount: 1,
description: "Should return agents created after 2024-01-20",
},
{
name: "Filter by created before date",
filters: map[string]string{"created-before": "2024-01-20"},
expectedCount: 1,
description: "Should return agents created before 2024-01-20",
},
{
name: "Multiple filters",
filters: map[string]string{"region": "tor1", "tag": "production"},
expectedCount: 1,
description: "Should return agents matching multiple filters",
},
{
name: "No matching filters",
filters: map[string]string{"region": "sfo1"},
expectedCount: 0,
description: "Should return no agents when no matches",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
withTestClient(t, func(config *CmdConfig, tm *tcMocks) {
// Set up filters
for key, value := range tt.filters {
config.Doit.Set(config.NS, key, value)
}

tm.genAI.EXPECT().ListAgents().Return(testAgents, nil)

err := RunAgentList(config)
assert.NoError(t, err)
})
})
}
}

func TestMatchesFilters(t *testing.T) {
createdAt := &godo.Timestamp{Time: time.Date(2024, 1, 15, 10, 0, 0, 0, time.UTC)}

agent := do.Agent{
Agent: &godo.Agent{
Uuid: "00000000-0000-4000-8000-000000000001",
Name: "Test Agent",
Region: "tor1",
ProjectId: "00000000-0000-4000-8000-000000000000",
Tags: []string{"test", "production"},
Model: &godo.Model{
Uuid: "00000000-0000-4000-8000-000000000000",
},
CreatedAt: createdAt,
},
}

tests := []struct {
name string
filters map[string]string
expected bool
}{
{
name: "Match region",
filters: map[string]string{"region": "tor1"},
expected: true,
},
{
name: "No match region",
filters: map[string]string{"region": "nyc1"},
expected: false,
},
{
name: "Match name (partial)",
filters: map[string]string{"name": "Test"},
expected: true,
},
{
name: "Match name (case insensitive)",
filters: map[string]string{"name": "test"},
expected: true,
},
{
name: "No match name",
filters: map[string]string{"name": "NonExistent"},
expected: false,
},
{
name: "Match tag",
filters: map[string]string{"tag": "test"},
expected: true,
},
{
name: "No match tag",
filters: map[string]string{"tag": "nonexistent"},
expected: false,
},
// Note: Status and Visibility filters are commented out as these fields
// may not exist in the current godo.Agent struct
// {
// name: "Match status",
// filters: map[string]string{"status": "active"},
// expected: true,
// },
// {
// name: "Match visibility",
// filters: map[string]string{"visibility": "VISIBILITY_PUBLIC"},
// expected: true,
// },
{
name: "Match created after",
filters: map[string]string{"created-after": "2024-01-01"},
expected: true,
},
{
name: "Match created before",
filters: map[string]string{"created-before": "2024-02-01"},
expected: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := matchesFilters(
agent,
tt.filters["region"],
tt.filters["project-id"],
tt.filters["tag"],
tt.filters["name"],
tt.filters["model-id"],
tt.filters["created-after"],
tt.filters["created-before"],
)
assert.Equal(t, tt.expected, result)
})
}
}
func TestRunAgentUpdate(t *testing.T) {
withTestClient(t, func(config *CmdConfig, tm *tcMocks) {
agentID := "00000000-0000-4000-8000-000000000000"
Expand Down
Binary file added doctl
Binary file not shown.