Skip to content

Commit 2d2675a

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

File tree

7 files changed

+273
-144
lines changed

7 files changed

+273
-144
lines changed

cmd/lk/agent.go

Lines changed: 139 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,81 @@ 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+
default:
387+
return fmt.Errorf("unsupported language: %s", lang)
388+
}
389+
}
390+
391+
logger.Debugw("Initializing agent project", "working-dir", workingDir)
392+
393+
// Create sandbox if one is not provided
394+
if sandboxID == "" {
395+
fmt.Println("Creating Sandbox app...")
396+
token, err := requireToken(ctx, cmd)
397+
if err != nil {
398+
return err
399+
}
400+
401+
appName = cmd.Args().First()
402+
if appName == "" {
403+
appName = project.Name
404+
}
405+
// We set agent name in env for use in template tasks
406+
os.Setenv("LIVEKIT_AGENT_NAME", appName)
407+
408+
// NOTE: for now, we assume that agent-starter-react is the desired template.
409+
sandboxID, err = bootstrap.CreateSandbox(
410+
ctx,
411+
appName,
412+
"https://github.com/livekit-examples/agent-starter-react",
413+
token,
414+
serverURL,
415+
)
416+
if err != nil {
417+
return fmt.Errorf("failed to create sandbox: %w", err)
418+
}
419+
}
420+
421+
// Run template bootstrap
422+
shouldDeploy := cmd.Bool("deploy")
423+
if shouldDeploy {
424+
cmd.Set("install", "true")
425+
}
426+
if err := setupTemplate(ctx, cmd); err != nil {
427+
return err
428+
}
429+
// Deploy if requested
430+
if shouldDeploy {
431+
fmt.Println("Deploying agent...")
432+
if err := createAgent(ctx, cmd); err != nil {
433+
return fmt.Errorf("failed to deploy agent: %w", err)
434+
}
435+
}
436+
437+
return nil
438+
}
439+
333440
func createAgent(ctx context.Context, cmd *cli.Command) error {
334441
subdomainMatches := subdomainPattern.FindStringSubmatch(project.URL)
335442
if len(subdomainMatches) < 2 {
@@ -424,11 +531,9 @@ func createAgent(ctx context.Context, cmd *cli.Command) error {
424531
resp, err := agentsClient.CreateAgent(ctx, req)
425532
if err != nil {
426533
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-
}
534+
return fmt.Errorf("unable to create agent: %s", twerr.Msg())
430535
}
431-
return err
536+
return fmt.Errorf("unable to create agent: %w", err)
432537
}
433538

434539
lkConfig.Agent.ID = resp.AgentId
@@ -512,11 +617,9 @@ func createAgentConfig(ctx context.Context, cmd *cli.Command) error {
512617
})
513618
if err != nil {
514619
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-
}
620+
return fmt.Errorf("unable to list agents: %s", twerr.Msg())
518621
}
519-
return err
622+
return fmt.Errorf("unable to list agents: %w", err)
520623
}
521624
if len(response.Agents) == 0 {
522625
return fmt.Errorf("agent not found")
@@ -581,11 +684,9 @@ func deployAgent(ctx context.Context, cmd *cli.Command) error {
581684
resp, err := agentsClient.DeployAgent(ctx, req)
582685
if err != nil {
583686
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-
}
687+
return fmt.Errorf("unable to deploy agent: %s", twerr.Msg())
587688
}
588-
return err
689+
return fmt.Errorf("unable to deploy agent: %w", err)
589690
}
590691

591692
if !resp.Success {
@@ -619,11 +720,9 @@ func getAgentStatus(ctx context.Context, cmd *cli.Command) error {
619720
})
620721
if err != nil {
621722
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-
}
723+
return fmt.Errorf("unable to list agents: %s", twerr.Msg())
625724
}
626-
return err
725+
return fmt.Errorf("unable to list agents: %w", err)
627726
}
628727

629728
if len(res.Agents) == 0 {
@@ -721,11 +820,9 @@ func updateAgent(ctx context.Context, cmd *cli.Command) error {
721820
})
722821
if err != nil {
723822
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-
}
823+
return fmt.Errorf("unable to update agent: %s", twerr.Msg())
727824
}
728-
return err
825+
return fmt.Errorf("unable to update agent: %w", err)
729826
}
730827

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

756853
if err != nil {
757854
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-
}
855+
return fmt.Errorf("unable to rollback agent: %s", twerr.Msg())
761856
}
762-
return err
857+
return fmt.Errorf("unable to rollback agent: %w", err)
763858
}
764859

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

819914
if err != nil {
820915
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-
}
916+
return fmt.Errorf("unable to delete agent: %s", twerr.Msg())
824917
}
825-
return err
918+
return fmt.Errorf("unable to delete agent: %w", err)
826919
}
827920

828921
if !res.Success {
@@ -846,11 +939,9 @@ func listAgentVersions(ctx context.Context, cmd *cli.Command) error {
846939
versions, err := agentsClient.ListAgentVersions(ctx, req)
847940
if err != nil {
848941
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-
}
942+
return fmt.Errorf("unable to list agent versions: %s", twerr.Msg())
852943
}
853-
return err
944+
return fmt.Errorf("unable to list agent versions: %w", err)
854945
}
855946

856947
table := util.CreateTable().
@@ -885,23 +976,19 @@ func listAgents(ctx context.Context, cmd *cli.Command) error {
885976
})
886977
if err != nil {
887978
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-
}
979+
return fmt.Errorf("unable to list agents: %s", twerr.Msg())
891980
}
892-
return err
981+
return fmt.Errorf("unable to list agents: %w", err)
893982
}
894983
items = append(items, res.Agents...)
895984
}
896985
} else {
897986
agents, err := agentsClient.ListAgents(ctx, &lkproto.ListAgentsRequest{})
898987
if err != nil {
899988
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-
}
989+
return fmt.Errorf("unable to list agents: %s", twerr.Msg())
903990
}
904-
return err
991+
return fmt.Errorf("unable to list agents: %w", err)
905992
}
906993
items = agents.Agents
907994
}
@@ -950,11 +1037,9 @@ func listAgentSecrets(ctx context.Context, cmd *cli.Command) error {
9501037
secrets, err := agentsClient.ListAgentSecrets(ctx, req)
9511038
if err != nil {
9521039
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-
}
1040+
return fmt.Errorf("unable to list agent secrets: %s", twerr.Msg())
9561041
}
957-
return err
1042+
return fmt.Errorf("unable to list agent secrets: %w", err)
9581043
}
9591044

9601045
table := util.CreateTable().
@@ -1011,11 +1096,9 @@ func updateAgentSecrets(ctx context.Context, cmd *cli.Command) error {
10111096
resp, err := agentsClient.UpdateAgentSecrets(ctx, req)
10121097
if err != nil {
10131098
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-
}
1099+
return fmt.Errorf("unable to update agent secrets: %s", twerr.Msg())
10171100
}
1018-
return err
1101+
return fmt.Errorf("unable to update agent secrets: %w", err)
10191102
}
10201103

10211104
if resp.Success {
@@ -1067,11 +1150,9 @@ func selectAgent(ctx context.Context, _ *cli.Command, excludeEmptyVersion bool)
10671150
})
10681151
if err != nil {
10691152
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-
}
1153+
return "", fmt.Errorf("unable to list agents: %s", twerr.Msg())
10731154
}
1074-
return "", err
1155+
return "", fmt.Errorf("unable to list agents: %w", err)
10751156
}
10761157

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

12141295
if err != nil {
12151296
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-
}
1297+
return nil, fmt.Errorf("unable to get client settings: %s", twerr.Msg())
12191298
}
1220-
return nil, err
1299+
return nil, fmt.Errorf("unable to get client settings: %w", err)
12211300
}
12221301

12231302
if clientSettingsResponse == nil {

0 commit comments

Comments
 (0)