Skip to content

Commit 65f2895

Browse files
chore: add CLI command to list aibridge interceptions (#19935)
Co-authored-by: Dean Sheather <dean@deansheather.com>
1 parent 0a6ba5d commit 65f2895

File tree

6 files changed

+422
-28
lines changed

6 files changed

+422
-28
lines changed

cli/exp.go

Lines changed: 0 additions & 23 deletions
This file was deleted.

cli/root.go

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,6 @@ func (r *RootCmd) CoreSubcommands() []*serpent.Command {
128128

129129
// Hidden
130130
r.connectCmd(),
131-
r.expCmd(),
132131
gitssh(),
133132
r.support(),
134133
r.vpnDaemon(),
@@ -137,15 +136,45 @@ func (r *RootCmd) CoreSubcommands() []*serpent.Command {
137136
}
138137
}
139138

139+
// AGPLExperimental returns all AGPL experimental subcommands.
140+
func (r *RootCmd) AGPLExperimental() []*serpent.Command {
141+
return []*serpent.Command{
142+
r.scaletestCmd(),
143+
r.errorExample(),
144+
r.mcpCommand(),
145+
r.promptExample(),
146+
r.rptyCommand(),
147+
r.tasksCommand(),
148+
}
149+
}
150+
151+
// AGPL returns all AGPL commands including any non-core commands that are
152+
// duplicated in the Enterprise CLI.
140153
func (r *RootCmd) AGPL() []*serpent.Command {
141154
all := append(
142155
r.CoreSubcommands(),
143156
r.Server( /* Do not import coderd here. */ nil),
144157
r.Provisioners(),
158+
ExperimentalCommand(r.AGPLExperimental()),
145159
)
146160
return all
147161
}
148162

163+
// ExperimentalCommand creates an experimental command that is hidden and has
164+
// the given subcommands.
165+
func ExperimentalCommand(subcommands []*serpent.Command) *serpent.Command {
166+
cmd := &serpent.Command{
167+
Use: "exp",
168+
Short: "Internal commands for testing and experimentation. These are prone to breaking changes with no notice.",
169+
Handler: func(i *serpent.Invocation) error {
170+
return i.Command.HelpHandler(i)
171+
},
172+
Hidden: true,
173+
Children: subcommands,
174+
}
175+
return cmd
176+
}
177+
149178
// RunWithSubcommands runs the root command with the given subcommands.
150179
// It is abstracted to enable the Enterprise code to add commands.
151180
func (r *RootCmd) RunWithSubcommands(subcommands []*serpent.Command) {

docs/reference/cli/index.md

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

enterprise/cli/exp_aibridge.go

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
package cli
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"time"
7+
8+
"github.com/google/uuid"
9+
"golang.org/x/xerrors"
10+
11+
"github.com/coder/coder/v2/codersdk"
12+
"github.com/coder/serpent"
13+
)
14+
15+
const maxInterceptionsLimit = 1000
16+
17+
func (r *RootCmd) aibridge() *serpent.Command {
18+
cmd := &serpent.Command{
19+
Use: "aibridge",
20+
Short: "Manage AIBridge.",
21+
Handler: func(inv *serpent.Invocation) error {
22+
return inv.Command.HelpHandler(inv)
23+
},
24+
Children: []*serpent.Command{
25+
r.aibridgeInterceptions(),
26+
},
27+
}
28+
return cmd
29+
}
30+
31+
func (r *RootCmd) aibridgeInterceptions() *serpent.Command {
32+
cmd := &serpent.Command{
33+
Use: "interceptions",
34+
Short: "Manage AIBridge interceptions.",
35+
Handler: func(inv *serpent.Invocation) error {
36+
return inv.Command.HelpHandler(inv)
37+
},
38+
Children: []*serpent.Command{
39+
r.aibridgeInterceptionsList(),
40+
},
41+
}
42+
return cmd
43+
}
44+
45+
func (r *RootCmd) aibridgeInterceptionsList() *serpent.Command {
46+
var (
47+
initiator string
48+
startedBeforeRaw string
49+
startedAfterRaw string
50+
provider string
51+
model string
52+
afterIDRaw string
53+
limit int64
54+
)
55+
56+
return &serpent.Command{
57+
Use: "list",
58+
Short: "List AIBridge interceptions as JSON.",
59+
Options: serpent.OptionSet{
60+
{
61+
Flag: "initiator",
62+
Description: `Only return interceptions initiated by this user. Accepts a user ID, username, or "me".`,
63+
Default: "",
64+
Value: serpent.StringOf(&initiator),
65+
},
66+
{
67+
Flag: "started-before",
68+
Description: fmt.Sprintf("Only return interceptions started before this time. Must be after 'started-after' if set. Accepts a time in the RFC 3339 format, e.g. %q.", time.RFC3339),
69+
Default: "",
70+
Value: serpent.StringOf(&startedBeforeRaw),
71+
},
72+
{
73+
Flag: "started-after",
74+
Description: fmt.Sprintf("Only return interceptions started after this time. Must be before 'started-before' if set. Accepts a time in the RFC 3339 format, e.g. %q.", time.RFC3339),
75+
Default: "",
76+
Value: serpent.StringOf(&startedAfterRaw),
77+
},
78+
{
79+
Flag: "provider",
80+
Description: `Only return interceptions from this provider.`,
81+
Default: "",
82+
Value: serpent.StringOf(&provider),
83+
},
84+
{
85+
Flag: "model",
86+
Description: `Only return interceptions from this model.`,
87+
Default: "",
88+
Value: serpent.StringOf(&model),
89+
},
90+
{
91+
Flag: "after-id",
92+
Description: "The ID of the last result on the previous page to use as a pagination cursor.",
93+
Default: "",
94+
Value: serpent.StringOf(&afterIDRaw),
95+
},
96+
{
97+
Flag: "limit",
98+
Description: fmt.Sprintf(`The limit of results to return. Must be between 1 and %d.`, maxInterceptionsLimit),
99+
Default: "100",
100+
Value: serpent.Int64Of(&limit),
101+
},
102+
},
103+
Handler: func(inv *serpent.Invocation) error {
104+
client, err := r.InitClient(inv)
105+
if err != nil {
106+
return err
107+
}
108+
109+
startedBefore := time.Time{}
110+
if startedBeforeRaw != "" {
111+
startedBefore, err = time.Parse(time.RFC3339, startedBeforeRaw)
112+
if err != nil {
113+
return xerrors.Errorf("parse started before filter value %q: %w", startedBeforeRaw, err)
114+
}
115+
}
116+
117+
startedAfter := time.Time{}
118+
if startedAfterRaw != "" {
119+
startedAfter, err = time.Parse(time.RFC3339, startedAfterRaw)
120+
if err != nil {
121+
return xerrors.Errorf("parse started after filter value %q: %w", startedAfterRaw, err)
122+
}
123+
}
124+
125+
afterID := uuid.Nil
126+
if afterIDRaw != "" {
127+
afterID, err = uuid.Parse(afterIDRaw)
128+
if err != nil {
129+
return xerrors.Errorf("parse after_id filter value %q: %w", afterIDRaw, err)
130+
}
131+
}
132+
133+
if limit < 1 || limit > maxInterceptionsLimit {
134+
return xerrors.Errorf("limit value must be between 1 and %d", maxInterceptionsLimit)
135+
}
136+
137+
expCli := codersdk.NewExperimentalClient(client)
138+
resp, err := expCli.AIBridgeListInterceptions(inv.Context(), codersdk.AIBridgeListInterceptionsFilter{
139+
Pagination: codersdk.Pagination{
140+
AfterID: afterID,
141+
// #nosec G115 - Checked above.
142+
Limit: int(limit),
143+
},
144+
Initiator: initiator,
145+
StartedBefore: startedBefore,
146+
StartedAfter: startedAfter,
147+
Provider: provider,
148+
Model: model,
149+
})
150+
if err != nil {
151+
return xerrors.Errorf("list interceptions: %w", err)
152+
}
153+
154+
// We currently only support JSON output, so we don't use a
155+
// formatter.
156+
enc := json.NewEncoder(inv.Stdout)
157+
enc.SetIndent("", " ")
158+
err = enc.Encode(resp.Results)
159+
if err != nil {
160+
return err
161+
}
162+
163+
return err
164+
},
165+
}
166+
}

0 commit comments

Comments
 (0)