Skip to content

Commit 0d45d1d

Browse files
committed
feat(agents): add streamlined lk agent init command
1 parent e0e8548 commit 0d45d1d

File tree

7 files changed

+274
-144
lines changed

7 files changed

+274
-144
lines changed

cmd/lk/agent.go

Lines changed: 140 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,14 @@ import (
3232

3333
livekitcli "github.com/livekit/livekit-cli/v2"
3434
"github.com/livekit/livekit-cli/v2/pkg/agentfs"
35+
"github.com/livekit/livekit-cli/v2/pkg/bootstrap"
3536
"github.com/livekit/livekit-cli/v2/pkg/config"
3637
"github.com/livekit/livekit-cli/v2/pkg/util"
3738
lkproto "github.com/livekit/protocol/livekit"
3839
"github.com/livekit/protocol/logger"
3940
lksdk "github.com/livekit/server-sdk-go/v2"
4041
)
4142

42-
const (
43-
cloudAgentsBetaSignupURL = "https://forms.gle/GkGNNTiMt2qyfnu78"
44-
)
45-
4643
var (
4744
idFlag = func(required bool) *cli.StringFlag {
4845
return &cli.StringFlag{
@@ -97,6 +94,41 @@ var (
9794
Aliases: []string{"a"},
9895
Usage: "Manage LiveKit Cloud Agents",
9996
Commands: []*cli.Command{
97+
{
98+
Name: "init",
99+
Usage: "Initialize a new LiveKit Cloud agent project",
100+
Before: createAgentClient,
101+
Action: initAgent,
102+
MutuallyExclusiveFlags: []cli.MutuallyExclusiveFlags{{
103+
Flags: [][]cli.Flag{{
104+
&cli.StringFlag{
105+
Name: "lang",
106+
Usage: "`LANGUAGE` of the project, one of \"node\", \"python\"",
107+
Action: func(ctx context.Context, cmd *cli.Command, l string) error {
108+
if l == "" {
109+
return nil
110+
}
111+
if !slices.Contains([]string{"node", "python"}, l) {
112+
return fmt.Errorf("unsupported language: %s", l)
113+
}
114+
return nil
115+
},
116+
Hidden: true,
117+
},
118+
&cli.BoolFlag{
119+
Name: "deploy",
120+
Usage: "If set, automatically deploys the agent to LiveKit Cloud after initialization.",
121+
Value: false,
122+
},
123+
templateFlag,
124+
templateURLFlag,
125+
sandboxFlag,
126+
}},
127+
}},
128+
Flags: []cli.Flag{},
129+
ArgsUsage: "[AGENT-NAME]",
130+
DisableSliceFlagSeparator: true,
131+
},
100132
{
101133
Name: "create",
102134
Usage: "Create a new LiveKit Cloud Agent",
@@ -330,6 +362,82 @@ func createAgentClient(ctx context.Context, cmd *cli.Command) (context.Context,
330362
return ctx, nil
331363
}
332364

365+
func initAgent(ctx context.Context, cmd *cli.Command) error {
366+
if !(cmd.IsSet("lang") || cmd.IsSet("template") || cmd.IsSet("template-url")) {
367+
var lang string
368+
// Prompt for language
369+
if err := huh.NewSelect[string]().
370+
Title("Select the language for your agent project").
371+
Options(
372+
huh.NewOption("Python", "python"),
373+
huh.NewOption("Node.js", "node"),
374+
).
375+
Value(&lang).
376+
WithTheme(util.Theme).
377+
Run(); err != nil {
378+
return err
379+
}
380+
381+
switch lang {
382+
case "node":
383+
return fmt.Errorf("this language is not yet supported")
384+
case "python":
385+
templateURL = "https://github.com/livekit-examples/agent-starter-python-gateway"
386+
break
387+
default:
388+
return fmt.Errorf("unsupported language: %s", lang)
389+
}
390+
}
391+
392+
logger.Debugw("Initializing agent project", "working-dir", workingDir)
393+
394+
// Create sandbox if one is not provided
395+
if sandboxID == "" {
396+
fmt.Println("Creating Sandbox app...")
397+
token, err := requireToken(ctx, cmd)
398+
if err != nil {
399+
return err
400+
}
401+
402+
appName = cmd.Args().First()
403+
if appName == "" {
404+
appName = project.Name
405+
}
406+
// We set agent name in env for use in template tasks
407+
os.Setenv("LIVEKIT_AGENT_NAME", appName)
408+
409+
// NOTE: for now, we assume that agent-starter-react is the desired template.
410+
sandboxID, err = bootstrap.CreateSandbox(
411+
ctx,
412+
appName,
413+
"https://github.com/livekit-examples/agent-starter-react",
414+
token,
415+
serverURL,
416+
)
417+
if err != nil {
418+
return fmt.Errorf("failed to create sandbox: %w", err)
419+
}
420+
}
421+
422+
// Run template bootstrap
423+
shouldDeploy := cmd.Bool("deploy")
424+
if shouldDeploy {
425+
cmd.Set("install", "true")
426+
}
427+
if err := setupTemplate(ctx, cmd); err != nil {
428+
return err
429+
}
430+
// Deploy if requested
431+
if shouldDeploy {
432+
fmt.Println("Deploying agent...")
433+
if err := createAgent(ctx, cmd); err != nil {
434+
return fmt.Errorf("failed to deploy agent: %w", err)
435+
}
436+
}
437+
438+
return nil
439+
}
440+
333441
func createAgent(ctx context.Context, cmd *cli.Command) error {
334442
subdomainMatches := subdomainPattern.FindStringSubmatch(project.URL)
335443
if len(subdomainMatches) < 2 {
@@ -424,11 +532,9 @@ func createAgent(ctx context.Context, cmd *cli.Command) error {
424532
resp, err := agentsClient.CreateAgent(ctx, req)
425533
if err != nil {
426534
if twerr, ok := err.(twirp.Error); ok {
427-
if twerr.Code() == twirp.PermissionDenied {
428-
return fmt.Errorf("agent hosting is disabled for this project -- join the beta program here [%s]", cloudAgentsBetaSignupURL)
429-
}
535+
return fmt.Errorf("unable to create agent: %s", twerr.Msg())
430536
}
431-
return err
537+
return fmt.Errorf("unable to create agent: %w", err)
432538
}
433539

434540
lkConfig.Agent.ID = resp.AgentId
@@ -512,11 +618,9 @@ func createAgentConfig(ctx context.Context, cmd *cli.Command) error {
512618
})
513619
if err != nil {
514620
if twerr, ok := err.(twirp.Error); ok {
515-
if twerr.Code() == twirp.PermissionDenied {
516-
return fmt.Errorf("agent hosting is disabled for this project -- join the beta program here [%s]", cloudAgentsBetaSignupURL)
517-
}
621+
return fmt.Errorf("unable to list agents: %s", twerr.Msg())
518622
}
519-
return err
623+
return fmt.Errorf("unable to list agents: %w", err)
520624
}
521625
if len(response.Agents) == 0 {
522626
return fmt.Errorf("agent not found")
@@ -581,11 +685,9 @@ func deployAgent(ctx context.Context, cmd *cli.Command) error {
581685
resp, err := agentsClient.DeployAgent(ctx, req)
582686
if err != nil {
583687
if twerr, ok := err.(twirp.Error); ok {
584-
if twerr.Code() == twirp.PermissionDenied {
585-
return fmt.Errorf("agent hosting is disabled for this project -- join the beta program here [%s]", cloudAgentsBetaSignupURL)
586-
}
688+
return fmt.Errorf("unable to deploy agent: %s", twerr.Msg())
587689
}
588-
return err
690+
return fmt.Errorf("unable to deploy agent: %w", err)
589691
}
590692

591693
if !resp.Success {
@@ -619,11 +721,9 @@ func getAgentStatus(ctx context.Context, cmd *cli.Command) error {
619721
})
620722
if err != nil {
621723
if twerr, ok := err.(twirp.Error); ok {
622-
if twerr.Code() == twirp.PermissionDenied {
623-
return fmt.Errorf("agent hosting is disabled for this project -- join the beta program here [%s]", cloudAgentsBetaSignupURL)
624-
}
724+
return fmt.Errorf("unable to list agents: %s", twerr.Msg())
625725
}
626-
return err
726+
return fmt.Errorf("unable to list agents: %w", err)
627727
}
628728

629729
if len(res.Agents) == 0 {
@@ -721,11 +821,9 @@ func updateAgent(ctx context.Context, cmd *cli.Command) error {
721821
})
722822
if err != nil {
723823
if twerr, ok := err.(twirp.Error); ok {
724-
if twerr.Code() == twirp.PermissionDenied {
725-
return fmt.Errorf("agent hosting is disabled for this project -- join the beta program here [%s]", cloudAgentsBetaSignupURL)
726-
}
824+
return fmt.Errorf("unable to update agent: %s", twerr.Msg())
727825
}
728-
return err
826+
return fmt.Errorf("unable to update agent: %w", err)
729827
}
730828

731829
if resp.Success {
@@ -755,11 +853,9 @@ func rollbackAgent(ctx context.Context, cmd *cli.Command) error {
755853

756854
if err != nil {
757855
if twerr, ok := err.(twirp.Error); ok {
758-
if twerr.Code() == twirp.PermissionDenied {
759-
return fmt.Errorf("agent hosting is disabled for this project -- join the beta program here [%s]", cloudAgentsBetaSignupURL)
760-
}
856+
return fmt.Errorf("unable to rollback agent: %s", twerr.Msg())
761857
}
762-
return err
858+
return fmt.Errorf("unable to rollback agent: %w", err)
763859
}
764860

765861
if !resp.Success {
@@ -818,11 +914,9 @@ func deleteAgent(ctx context.Context, cmd *cli.Command) error {
818914

819915
if err != nil {
820916
if twerr, ok := err.(twirp.Error); ok {
821-
if twerr.Code() == twirp.PermissionDenied {
822-
return fmt.Errorf("agent hosting is disabled for this project -- join the beta program here [%s]", cloudAgentsBetaSignupURL)
823-
}
917+
return fmt.Errorf("unable to delete agent: %s", twerr.Msg())
824918
}
825-
return err
919+
return fmt.Errorf("unable to delete agent: %w", err)
826920
}
827921

828922
if !res.Success {
@@ -846,11 +940,9 @@ func listAgentVersions(ctx context.Context, cmd *cli.Command) error {
846940
versions, err := agentsClient.ListAgentVersions(ctx, req)
847941
if err != nil {
848942
if twerr, ok := err.(twirp.Error); ok {
849-
if twerr.Code() == twirp.PermissionDenied {
850-
return fmt.Errorf("agent hosting is disabled for this project -- join the beta program here [%s]", cloudAgentsBetaSignupURL)
851-
}
943+
return fmt.Errorf("unable to list agent versions: %s", twerr.Msg())
852944
}
853-
return err
945+
return fmt.Errorf("unable to list agent versions: %w", err)
854946
}
855947

856948
table := util.CreateTable().
@@ -885,23 +977,19 @@ func listAgents(ctx context.Context, cmd *cli.Command) error {
885977
})
886978
if err != nil {
887979
if twerr, ok := err.(twirp.Error); ok {
888-
if twerr.Code() == twirp.PermissionDenied {
889-
return fmt.Errorf("agent hosting is disabled for this project -- join the beta program here [%s]", cloudAgentsBetaSignupURL)
890-
}
980+
return fmt.Errorf("unable to list agents: %s", twerr.Msg())
891981
}
892-
return err
982+
return fmt.Errorf("unable to list agents: %w", err)
893983
}
894984
items = append(items, res.Agents...)
895985
}
896986
} else {
897987
agents, err := agentsClient.ListAgents(ctx, &lkproto.ListAgentsRequest{})
898988
if err != nil {
899989
if twerr, ok := err.(twirp.Error); ok {
900-
if twerr.Code() == twirp.PermissionDenied {
901-
return fmt.Errorf("agent hosting is disabled for this project -- join the beta program here [%s]", cloudAgentsBetaSignupURL)
902-
}
990+
return fmt.Errorf("unable to list agents: %s", twerr.Msg())
903991
}
904-
return err
992+
return fmt.Errorf("unable to list agents: %w", err)
905993
}
906994
items = agents.Agents
907995
}
@@ -950,11 +1038,9 @@ func listAgentSecrets(ctx context.Context, cmd *cli.Command) error {
9501038
secrets, err := agentsClient.ListAgentSecrets(ctx, req)
9511039
if err != nil {
9521040
if twerr, ok := err.(twirp.Error); ok {
953-
if twerr.Code() == twirp.PermissionDenied {
954-
return fmt.Errorf("agent hosting is disabled for this project -- join the beta program here [%s]", cloudAgentsBetaSignupURL)
955-
}
1041+
return fmt.Errorf("unable to list agent secrets: %s", twerr.Msg())
9561042
}
957-
return err
1043+
return fmt.Errorf("unable to list agent secrets: %w", err)
9581044
}
9591045

9601046
table := util.CreateTable().
@@ -1011,11 +1097,9 @@ func updateAgentSecrets(ctx context.Context, cmd *cli.Command) error {
10111097
resp, err := agentsClient.UpdateAgentSecrets(ctx, req)
10121098
if err != nil {
10131099
if twerr, ok := err.(twirp.Error); ok {
1014-
if twerr.Code() == twirp.PermissionDenied {
1015-
return fmt.Errorf("agent hosting is disabled for this project -- join the beta program here [%s]", cloudAgentsBetaSignupURL)
1016-
}
1100+
return fmt.Errorf("unable to update agent secrets: %s", twerr.Msg())
10171101
}
1018-
return err
1102+
return fmt.Errorf("unable to update agent secrets: %w", err)
10191103
}
10201104

10211105
if resp.Success {
@@ -1067,11 +1151,9 @@ func selectAgent(ctx context.Context, _ *cli.Command, excludeEmptyVersion bool)
10671151
})
10681152
if err != nil {
10691153
if twerr, ok := err.(twirp.Error); ok {
1070-
if twerr.Code() == twirp.PermissionDenied {
1071-
return "", fmt.Errorf("agent hosting is disabled for this project -- join the beta program here [%s]", cloudAgentsBetaSignupURL)
1072-
}
1154+
return "", fmt.Errorf("unable to list agents: %s", twerr.Msg())
10731155
}
1074-
return "", err
1156+
return "", fmt.Errorf("unable to list agents: %w", err)
10751157
}
10761158

10771159
if len(agents.Agents) == 0 {
@@ -1213,11 +1295,9 @@ func getClientSettings(ctx context.Context, silent bool) (map[string]string, err
12131295

12141296
if err != nil {
12151297
if twerr, ok := err.(twirp.Error); ok {
1216-
if twerr.Code() == twirp.PermissionDenied {
1217-
return nil, fmt.Errorf("agent hosting is disabled for this project -- join the beta program here [%s]", cloudAgentsBetaSignupURL)
1218-
}
1298+
return nil, fmt.Errorf("unable to get client settings: %s", twerr.Msg())
12191299
}
1220-
return nil, err
1300+
return nil, fmt.Errorf("unable to get client settings: %w", err)
12211301
}
12221302

12231303
if clientSettingsResponse == nil {

0 commit comments

Comments
 (0)