Skip to content
Merged
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
2 changes: 2 additions & 0 deletions pkg/cmd/agentstudio/agentstudio.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"github.com/spf13/cobra"

"github.com/algolia/cli/pkg/cmd/agents"
"github.com/algolia/cli/pkg/cmd/conversations"
"github.com/algolia/cli/pkg/cmdutil"
)

Expand All @@ -16,5 +17,6 @@ func NewAgentStudioCmd(f *cmdutil.Factory) *cobra.Command {
}

cmd.AddCommand(agents.NewAgentsCmd(f))
cmd.AddCommand(conversations.NewConversationsCmd(f))
return cmd
}
28 changes: 28 additions & 0 deletions pkg/cmd/conversations/conversations.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package conversations

import (
"github.com/spf13/cobra"

"github.com/algolia/cli/pkg/cmd/conversations/delete"
"github.com/algolia/cli/pkg/cmd/conversations/export"
"github.com/algolia/cli/pkg/cmd/conversations/get"
"github.com/algolia/cli/pkg/cmd/conversations/list"
"github.com/algolia/cli/pkg/cmd/conversations/purge"
"github.com/algolia/cli/pkg/cmdutil"
)

// NewConversationsCmd returns the conversations command group.
func NewConversationsCmd(f *cmdutil.Factory) *cobra.Command {
cmd := &cobra.Command{
Use: "conversations",
Short: "Manage Agent Studio conversations",
Long: "List, get, export, delete, and purge Agent Studio conversations.",
}

cmd.AddCommand(list.NewListCmd(f))
cmd.AddCommand(get.NewGetCmd(f))
cmd.AddCommand(export.NewExportCmd(f))
cmd.AddCommand(delete.NewDeleteCmd(f))
cmd.AddCommand(purge.NewPurgeCmd(f))
return cmd
}
112 changes: 112 additions & 0 deletions pkg/cmd/conversations/delete/delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package delete

import (
"fmt"

"github.com/MakeNowJust/heredoc"
agentStudio "github.com/algolia/algoliasearch-client-go/v4/algolia/agent-studio"
"github.com/spf13/cobra"

"github.com/algolia/cli/pkg/cmdutil"
"github.com/algolia/cli/pkg/config"
"github.com/algolia/cli/pkg/iostreams"
"github.com/algolia/cli/pkg/prompt"
"github.com/algolia/cli/pkg/validators"
)

// DeleteOptions holds the dependencies and flags for the delete command.
type DeleteOptions struct {
Config config.IConfig
IO *iostreams.IOStreams
AgentStudioClient func() (*agentStudio.APIClient, error)
AgentID string
ConversationID string
DoConfirm bool
}

// NewDeleteCmd returns the `conversations delete` command.
func NewDeleteCmd(f *cmdutil.Factory) *cobra.Command {
opts := &DeleteOptions{
IO: f.IOStreams,
Config: f.Config,
AgentStudioClient: f.AgentStudioClient,
}

cmd := &cobra.Command{
Use: "delete <agent-id> <conversation-id>",
Short: "Delete an Agent Studio conversation",
Args: validators.ExactArgsWithMsg(
2,
"agentstudio conversations delete requires <agent-id> and <conversation-id> arguments.",
),
Annotations: map[string]string{
"acls": "logs",
},
Example: heredoc.Doc(`
# Delete a conversation (with confirmation prompt)
$ algolia agentstudio conversations delete 11111111-1111-4111-8111-111111111111 33333333-3333-4333-8333-333333333333

# Delete without confirmation
$ algolia agentstudio conversations delete 11111111-1111-4111-8111-111111111111 33333333-3333-4333-8333-333333333333 --confirm
`),
RunE: func(cmd *cobra.Command, args []string) error {
opts.AgentID = args[0]
opts.ConversationID = args[1]

if !opts.DoConfirm {
if !opts.IO.CanPrompt() {
return cmdutil.FlagErrorf(
"--confirm required when non-interactive shell is detected",
)
}
var confirmed bool
err := prompt.Confirm(
fmt.Sprintf("Delete conversation %q?", opts.ConversationID),
&confirmed,
)
if err != nil {
return fmt.Errorf("failed to prompt: %w", err)
}
if !confirmed {
return nil
}
}

return runDeleteCmd(opts)
},
}

cmd.Flags().BoolVarP(&opts.DoConfirm, "confirm", "y", false, "Skip confirmation prompt")
return cmd
}

func runDeleteCmd(opts *DeleteOptions) error {
client, err := opts.AgentStudioClient()
if err != nil {
return err
}

opts.IO.StartProgressIndicatorWithLabel("Deleting conversation")

err = client.DeleteConversation(
client.NewApiDeleteConversationRequest(opts.ConversationID, opts.AgentID),
)
if err != nil {
opts.IO.StopProgressIndicator()
return err
}

opts.IO.StopProgressIndicator()

if opts.IO.IsStdoutTTY() {
cs := opts.IO.ColorScheme()
fmt.Fprintf(
opts.IO.Out,
"%s Deleted conversation %s\n",
cs.SuccessIcon(),
opts.ConversationID,
)
}

return nil
}
53 changes: 53 additions & 0 deletions pkg/cmd/conversations/delete/delete_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package delete_test

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/algolia/cli/pkg/cmd/conversations/delete"
"github.com/algolia/cli/pkg/httpmock"
"github.com/algolia/cli/test"
)

const (
agentID = "11111111-1111-4111-8111-111111111111"
convID = "33333333-3333-4333-8333-333333333333"
deletePath = "agent-studio/1/agents/" + agentID + "/conversations/" + convID
)

func TestDeleteConversation(t *testing.T) {
r := &httpmock.Registry{}
r.Register(httpmock.REST("DELETE", deletePath), httpmock.StringResponse(""))

f, out := test.NewFactory(false, r, nil, "")
cmd := delete.NewDeleteCmd(f)
_, err := test.Execute(cmd, agentID+" "+convID+" --confirm", out)
require.NoError(t, err)
assert.Empty(t, out.String())
r.Verify(t)
}

func TestDeleteConversation_RequiresConfirmation(t *testing.T) {
r := &httpmock.Registry{}
f, out := test.NewFactory(false, r, nil, "")
cmd := delete.NewDeleteCmd(f)
_, err := test.Execute(cmd, agentID+" "+convID, out)
require.Error(t, err)
assert.Contains(t, err.Error(), "--confirm required")
assert.Empty(t, r.Requests)
}

func TestDeleteConversation_MissingArgs(t *testing.T) {
r := &httpmock.Registry{}
f, out := test.NewFactory(false, r, nil, "")
cmd := delete.NewDeleteCmd(f)
_, err := test.Execute(cmd, agentID+" --confirm", out)
require.Error(t, err)
assert.Contains(
t,
err.Error(),
"agentstudio conversations delete requires <agent-id> and <conversation-id>",
)
}
95 changes: 95 additions & 0 deletions pkg/cmd/conversations/export/export.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package export

import (
"github.com/MakeNowJust/heredoc"
agentStudio "github.com/algolia/algoliasearch-client-go/v4/algolia/agent-studio"
"github.com/spf13/cobra"

"github.com/algolia/cli/pkg/cmdutil"
"github.com/algolia/cli/pkg/config"
"github.com/algolia/cli/pkg/iostreams"
"github.com/algolia/cli/pkg/validators"
)

// ExportOptions holds the dependencies and flags for the export command.
type ExportOptions struct {
Config config.IConfig
IO *iostreams.IOStreams
AgentStudioClient func() (*agentStudio.APIClient, error)
PrintFlags *cmdutil.PrintFlags

AgentID string
StartDate string
EndDate string
}

// NewExportCmd returns the `conversations export` command.
func NewExportCmd(f *cmdutil.Factory) *cobra.Command {
opts := &ExportOptions{
IO: f.IOStreams,
Config: f.Config,
AgentStudioClient: f.AgentStudioClient,
PrintFlags: cmdutil.NewPrintFlags().WithDefaultOutput("json"),
}

cmd := &cobra.Command{
Use: "export <agent-id>",
Short: "Export Agent Studio conversations for an agent",
Args: validators.ExactArgsWithMsg(
1,
"agentstudio conversations export requires a <agent-id> argument.",
),
Annotations: map[string]string{
"acls": "logs",
},
Example: heredoc.Doc(`
# Export all conversations for an agent
$ algolia agentstudio conversations export 11111111-1111-4111-8111-111111111111

# Export a date range
$ algolia agentstudio conversations export 11111111-1111-4111-8111-111111111111 --start-date 2026-01-01 --end-date 2026-01-31
`),
RunE: func(cmd *cobra.Command, args []string) error {
opts.AgentID = args[0]
return runExportCmd(opts)
},
}

cmd.Flags().StringVar(&opts.StartDate, "start-date", "", "Created after this date (YYYY-MM-DD)")
cmd.Flags().StringVar(&opts.EndDate, "end-date", "", "Created before this date (YYYY-MM-DD)")

opts.PrintFlags.AddFlags(cmd)
return cmd
}

func runExportCmd(opts *ExportOptions) error {
client, err := opts.AgentStudioClient()
if err != nil {
return err
}

p, err := opts.PrintFlags.ToPrinter()
if err != nil {
return err
}

req := client.NewApiExportConversationsRequest(opts.AgentID)
if opts.StartDate != "" {
req = req.WithStartDate(opts.StartDate)
}
if opts.EndDate != "" {
req = req.WithEndDate(opts.EndDate)
}

opts.IO.StartProgressIndicatorWithLabel("Exporting conversations")

res, err := client.ExportConversations(req)
if err != nil {
opts.IO.StopProgressIndicator()
return err
}

opts.IO.StopProgressIndicator()

return p.Print(opts.IO, res)
}
56 changes: 56 additions & 0 deletions pkg/cmd/conversations/export/export_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package export_test

import (
"strings"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/algolia/cli/pkg/cmd/conversations/export"
"github.com/algolia/cli/pkg/httpmock"
"github.com/algolia/cli/test"
)

const (
agentID = "11111111-1111-4111-8111-111111111111"
exportPath = "agent-studio/1/agents/" + agentID + "/conversations/export"
exportBody = `[{
"id": "33333333-3333-4333-8333-333333333333",
"agentId": "11111111-1111-4111-8111-111111111111",
"createdAt": "2026-01-01T00:00:00Z",
"updatedAt": "2026-01-01T00:00:00Z",
"messages": []
}]`
)

func TestExportConversations(t *testing.T) {
r := &httpmock.Registry{}
r.Register(httpmock.REST("GET", exportPath), httpmock.StringResponse(exportBody))

f, out := test.NewFactory(false, r, nil, "")
cmd := export.NewExportCmd(f)
_, err := test.Execute(cmd, agentID+" --start-date 2026-01-01 --end-date 2026-01-31", out)
require.NoError(t, err)

assert.JSONEq(t, exportBody, strings.TrimSpace(out.String()))
r.Verify(t)

require.Len(t, r.Requests, 1)
q := r.Requests[0].URL.Query()
assert.Equal(t, "2026-01-01", q.Get("startDate"))
assert.Equal(t, "2026-01-31", q.Get("endDate"))
}

func TestExportConversations_MissingArg(t *testing.T) {
r := &httpmock.Registry{}
f, out := test.NewFactory(false, r, nil, "")
cmd := export.NewExportCmd(f)
_, err := test.Execute(cmd, "", out)
require.Error(t, err)
assert.Contains(
t,
err.Error(),
"agentstudio conversations export requires a <agent-id> argument",
)
}
Loading
Loading