Files
coder/enterprise/cli/aibridge.go
T
Paweł Banaszewski 90c11f3386 feat: add client column to aibridge_interceptions table (#21839)
Adds `client` column to `aibridge_interceptions` table. It is set accordingly to what is passed from AI Bridge in `RecordInterception`.
Adds interception filtering by `client` value.

Depends on: https://github.com/coder/aibridge/pull/158
Updates aibridge library to include this change.

Fixes: https://github.com/coder/aibridge/issues/31
2026-02-17 15:43:02 +01:00

174 lines
4.6 KiB
Go

package cli
import (
"encoding/json"
"fmt"
"time"
"github.com/google/uuid"
"golang.org/x/xerrors"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/serpent"
)
const maxInterceptionsLimit = 1000
func (r *RootCmd) aibridge() *serpent.Command {
cmd := &serpent.Command{
Use: "aibridge",
Short: "Manage AI Bridge.",
Handler: func(inv *serpent.Invocation) error {
return inv.Command.HelpHandler(inv)
},
Children: []*serpent.Command{
r.aibridgeInterceptions(),
},
}
return cmd
}
func (r *RootCmd) aibridgeInterceptions() *serpent.Command {
cmd := &serpent.Command{
Use: "interceptions",
Short: "Manage AI Bridge interceptions.",
Handler: func(inv *serpent.Invocation) error {
return inv.Command.HelpHandler(inv)
},
Children: []*serpent.Command{
r.aibridgeInterceptionsList(),
},
}
return cmd
}
func (r *RootCmd) aibridgeInterceptionsList() *serpent.Command {
var (
initiator string
startedBeforeRaw string
startedAfterRaw string
provider string
model string
client string
afterIDRaw string
limit int64
)
return &serpent.Command{
Use: "list",
Short: "List AI Bridge interceptions as JSON.",
Options: serpent.OptionSet{
{
Flag: "initiator",
Description: `Only return interceptions initiated by this user. Accepts a user ID, username, or "me".`,
Default: "",
Value: serpent.StringOf(&initiator),
},
{
Flag: "started-before",
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),
Default: "",
Value: serpent.StringOf(&startedBeforeRaw),
},
{
Flag: "started-after",
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),
Default: "",
Value: serpent.StringOf(&startedAfterRaw),
},
{
Flag: "provider",
Description: `Only return interceptions from this provider.`,
Default: "",
Value: serpent.StringOf(&provider),
},
{
Flag: "model",
Description: `Only return interceptions from this model.`,
Default: "",
Value: serpent.StringOf(&model),
},
{
Flag: "client",
Description: `Only return interceptions from this client.`,
Default: "",
Value: serpent.StringOf(&client),
},
{
Flag: "after-id",
Description: "The ID of the last result on the previous page to use as a pagination cursor.",
Default: "",
Value: serpent.StringOf(&afterIDRaw),
},
{
Flag: "limit",
Description: fmt.Sprintf(`The limit of results to return. Must be between 1 and %d.`, maxInterceptionsLimit),
Default: "100",
Value: serpent.Int64Of(&limit),
},
},
Handler: func(inv *serpent.Invocation) error {
serpetClient, err := r.InitClient(inv)
if err != nil {
return err
}
startedBefore := time.Time{}
if startedBeforeRaw != "" {
startedBefore, err = time.Parse(time.RFC3339, startedBeforeRaw)
if err != nil {
return xerrors.Errorf("parse started before filter value %q: %w", startedBeforeRaw, err)
}
}
startedAfter := time.Time{}
if startedAfterRaw != "" {
startedAfter, err = time.Parse(time.RFC3339, startedAfterRaw)
if err != nil {
return xerrors.Errorf("parse started after filter value %q: %w", startedAfterRaw, err)
}
}
afterID := uuid.Nil
if afterIDRaw != "" {
afterID, err = uuid.Parse(afterIDRaw)
if err != nil {
return xerrors.Errorf("parse after_id filter value %q: %w", afterIDRaw, err)
}
}
if limit < 1 || limit > maxInterceptionsLimit {
return xerrors.Errorf("limit value must be between 1 and %d", maxInterceptionsLimit)
}
resp, err := serpetClient.AIBridgeListInterceptions(inv.Context(), codersdk.AIBridgeListInterceptionsFilter{
Pagination: codersdk.Pagination{
AfterID: afterID,
// #nosec G115 - Checked above.
Limit: int(limit),
},
Client: client,
Initiator: initiator,
StartedBefore: startedBefore,
StartedAfter: startedAfter,
Provider: provider,
Model: model,
})
if err != nil {
return xerrors.Errorf("list interceptions: %w", err)
}
// We currently only support JSON output, so we don't use a
// formatter.
enc := json.NewEncoder(inv.Stdout)
enc.SetIndent("", " ")
err = enc.Encode(resp.Results)
if err != nil {
return err
}
return err
},
}
}