mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
chore: add user details to aibridge interception list endpoint (#20397)
- Adds FK from `aibridge_interceptions.initiator_id` to `users.id`
- This is enforced by deleting any rows that don't have any users. Since
this is an experimental feature AND coder never deletes user rows I
think this is acceptable.
- Adds `name` as a property on `codersdk.MinimalUser`
- This matches the `visible_users` view in the database. I'm unsure why
`name` wasn't already included given that `username` is.
- Adds a new `initiator` field to `codersdk.AIBridgeInterception` which
contains `codersdk.MinimalUser` (ID, username, name, avatar URL)
- Removes `initiator_id` from `codersdk.AIBridgeInterception`
- Should be fine since we're still in early access
This commit is contained in:
@@ -54,6 +54,7 @@ func TestSharingShare(t *testing.T) {
|
||||
MinimalUser: codersdk.MinimalUser{
|
||||
ID: toShareWithUser.ID,
|
||||
Username: toShareWithUser.Username,
|
||||
Name: toShareWithUser.Name,
|
||||
AvatarURL: toShareWithUser.AvatarURL,
|
||||
},
|
||||
Role: codersdk.WorkspaceRole("use"),
|
||||
@@ -103,6 +104,7 @@ func TestSharingShare(t *testing.T) {
|
||||
MinimalUser: codersdk.MinimalUser{
|
||||
ID: toShareWithUser1.ID,
|
||||
Username: toShareWithUser1.Username,
|
||||
Name: toShareWithUser1.Name,
|
||||
AvatarURL: toShareWithUser1.AvatarURL,
|
||||
},
|
||||
Role: codersdk.WorkspaceRoleUse,
|
||||
@@ -111,6 +113,7 @@ func TestSharingShare(t *testing.T) {
|
||||
MinimalUser: codersdk.MinimalUser{
|
||||
ID: toShareWithUser2.ID,
|
||||
Username: toShareWithUser2.Username,
|
||||
Name: toShareWithUser2.Name,
|
||||
AvatarURL: toShareWithUser2.AvatarURL,
|
||||
},
|
||||
Role: codersdk.WorkspaceRoleUse,
|
||||
@@ -155,6 +158,7 @@ func TestSharingShare(t *testing.T) {
|
||||
MinimalUser: codersdk.MinimalUser{
|
||||
ID: toShareWithUser.ID,
|
||||
Username: toShareWithUser.Username,
|
||||
Name: toShareWithUser.Name,
|
||||
AvatarURL: toShareWithUser.AvatarURL,
|
||||
},
|
||||
Role: codersdk.WorkspaceRoleAdmin,
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ USAGE:
|
||||
Aliases: ls
|
||||
|
||||
OPTIONS:
|
||||
-c, --column [id|username|email|created at|updated at|status] (default: username,email,created at,status)
|
||||
-c, --column [id|username|name|email|created at|updated at|status] (default: username,email,created at,status)
|
||||
Columns to display in table output.
|
||||
|
||||
--github-user-id int
|
||||
|
||||
Generated
+8
-3
@@ -11687,9 +11687,8 @@ const docTemplate = `{
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"initiator_id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
"initiator": {
|
||||
"$ref": "#/definitions/codersdk.MinimalUser"
|
||||
},
|
||||
"metadata": {
|
||||
"type": "object",
|
||||
@@ -15071,6 +15070,9 @@ const docTemplate = `{
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"username": {
|
||||
"type": "string"
|
||||
}
|
||||
@@ -20895,6 +20897,9 @@ const docTemplate = `{
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"role": {
|
||||
"enum": [
|
||||
"admin",
|
||||
|
||||
Generated
+8
-3
@@ -10387,9 +10387,8 @@
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"initiator_id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
"initiator": {
|
||||
"$ref": "#/definitions/codersdk.MinimalUser"
|
||||
},
|
||||
"metadata": {
|
||||
"type": "object",
|
||||
@@ -13633,6 +13632,9 @@
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"username": {
|
||||
"type": "string"
|
||||
}
|
||||
@@ -19211,6 +19213,9 @@
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"role": {
|
||||
"enum": ["admin", "use"],
|
||||
"allOf": [
|
||||
|
||||
@@ -189,6 +189,16 @@ func MinimalUser(user database.User) codersdk.MinimalUser {
|
||||
return codersdk.MinimalUser{
|
||||
ID: user.ID,
|
||||
Username: user.Username,
|
||||
Name: user.Name,
|
||||
AvatarURL: user.AvatarURL,
|
||||
}
|
||||
}
|
||||
|
||||
func MinimalUserFromVisibleUser(user database.VisibleUser) codersdk.MinimalUser {
|
||||
return codersdk.MinimalUser{
|
||||
ID: user.ID,
|
||||
Username: user.Username,
|
||||
Name: user.Name,
|
||||
AvatarURL: user.AvatarURL,
|
||||
}
|
||||
}
|
||||
@@ -197,7 +207,6 @@ func ReducedUser(user database.User) codersdk.ReducedUser {
|
||||
return codersdk.ReducedUser{
|
||||
MinimalUser: MinimalUser(user),
|
||||
Email: user.Email,
|
||||
Name: user.Name,
|
||||
CreatedAt: user.CreatedAt,
|
||||
UpdatedAt: user.UpdatedAt,
|
||||
LastSeenAt: user.LastSeenAt,
|
||||
@@ -927,7 +936,7 @@ func PreviewParameterValidation(v *previewtypes.ParameterValidation) codersdk.Pr
|
||||
}
|
||||
}
|
||||
|
||||
func AIBridgeInterception(interception database.AIBridgeInterception, tokenUsages []database.AIBridgeTokenUsage, userPrompts []database.AIBridgeUserPrompt, toolUsages []database.AIBridgeToolUsage) codersdk.AIBridgeInterception {
|
||||
func AIBridgeInterception(interception database.AIBridgeInterception, initiator database.VisibleUser, tokenUsages []database.AIBridgeTokenUsage, userPrompts []database.AIBridgeUserPrompt, toolUsages []database.AIBridgeToolUsage) codersdk.AIBridgeInterception {
|
||||
sdkTokenUsages := List(tokenUsages, AIBridgeTokenUsage)
|
||||
sort.Slice(sdkTokenUsages, func(i, j int) bool {
|
||||
// created_at ASC
|
||||
@@ -945,7 +954,7 @@ func AIBridgeInterception(interception database.AIBridgeInterception, tokenUsage
|
||||
})
|
||||
return codersdk.AIBridgeInterception{
|
||||
ID: interception.ID,
|
||||
InitiatorID: interception.InitiatorID,
|
||||
Initiator: MinimalUserFromVisibleUser(initiator),
|
||||
Provider: interception.Provider,
|
||||
Model: interception.Model,
|
||||
Metadata: jsonOrEmptyMap(interception.Metadata),
|
||||
|
||||
@@ -4461,7 +4461,7 @@ func (q *querier) InsertWorkspaceResourceMetadata(ctx context.Context, arg datab
|
||||
return q.db.InsertWorkspaceResourceMetadata(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) ListAIBridgeInterceptions(ctx context.Context, arg database.ListAIBridgeInterceptionsParams) ([]database.AIBridgeInterception, error) {
|
||||
func (q *querier) ListAIBridgeInterceptions(ctx context.Context, arg database.ListAIBridgeInterceptionsParams) ([]database.ListAIBridgeInterceptionsRow, error) {
|
||||
prep, err := prepareSQLFilter(ctx, q.auth, policy.ActionRead, rbac.ResourceAibridgeInterception.Type)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("(dev error) prepare sql filter: %w", err)
|
||||
@@ -5870,7 +5870,7 @@ func (q *querier) CountAuthorizedConnectionLogs(ctx context.Context, arg databas
|
||||
return q.CountConnectionLogs(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) ListAuthorizedAIBridgeInterceptions(ctx context.Context, arg database.ListAIBridgeInterceptionsParams, _ rbac.PreparedAuthorized) ([]database.AIBridgeInterception, error) {
|
||||
func (q *querier) ListAuthorizedAIBridgeInterceptions(ctx context.Context, arg database.ListAIBridgeInterceptionsParams, _ rbac.PreparedAuthorized) ([]database.ListAIBridgeInterceptionsRow, error) {
|
||||
// TODO: Delete this function, all ListAIBridgeInterceptions should be authorized. For now just call ListAIBridgeInterceptions on the authz querier.
|
||||
// This cannot be deleted for now because it's included in the
|
||||
// database.Store interface, so dbauthz needs to implement it.
|
||||
|
||||
@@ -4537,14 +4537,14 @@ func (s *MethodTestSuite) TestAIBridge() {
|
||||
|
||||
s.Run("ListAIBridgeInterceptions", s.Mocked(func(db *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
params := database.ListAIBridgeInterceptionsParams{}
|
||||
db.EXPECT().ListAuthorizedAIBridgeInterceptions(gomock.Any(), params, gomock.Any()).Return([]database.AIBridgeInterception{}, nil).AnyTimes()
|
||||
db.EXPECT().ListAuthorizedAIBridgeInterceptions(gomock.Any(), params, gomock.Any()).Return([]database.ListAIBridgeInterceptionsRow{}, nil).AnyTimes()
|
||||
// No asserts here because SQLFilter.
|
||||
check.Args(params).Asserts()
|
||||
}))
|
||||
|
||||
s.Run("ListAuthorizedAIBridgeInterceptions", s.Mocked(func(db *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
params := database.ListAIBridgeInterceptionsParams{}
|
||||
db.EXPECT().ListAuthorizedAIBridgeInterceptions(gomock.Any(), params, gomock.Any()).Return([]database.AIBridgeInterception{}, nil).AnyTimes()
|
||||
db.EXPECT().ListAuthorizedAIBridgeInterceptions(gomock.Any(), params, gomock.Any()).Return([]database.ListAIBridgeInterceptionsRow{}, nil).AnyTimes()
|
||||
// No asserts here because SQLFilter.
|
||||
check.Args(params, emptyPreparedAuthorized{}).Asserts()
|
||||
}))
|
||||
|
||||
@@ -2714,7 +2714,7 @@ func (m queryMetricsStore) InsertWorkspaceResourceMetadata(ctx context.Context,
|
||||
return metadata, err
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) ListAIBridgeInterceptions(ctx context.Context, arg database.ListAIBridgeInterceptionsParams) ([]database.AIBridgeInterception, error) {
|
||||
func (m queryMetricsStore) ListAIBridgeInterceptions(ctx context.Context, arg database.ListAIBridgeInterceptionsParams) ([]database.ListAIBridgeInterceptionsRow, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.ListAIBridgeInterceptions(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("ListAIBridgeInterceptions").Observe(time.Since(start).Seconds())
|
||||
@@ -3722,7 +3722,7 @@ func (m queryMetricsStore) CountAuthorizedConnectionLogs(ctx context.Context, ar
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) ListAuthorizedAIBridgeInterceptions(ctx context.Context, arg database.ListAIBridgeInterceptionsParams, prepared rbac.PreparedAuthorized) ([]database.AIBridgeInterception, error) {
|
||||
func (m queryMetricsStore) ListAuthorizedAIBridgeInterceptions(ctx context.Context, arg database.ListAIBridgeInterceptionsParams, prepared rbac.PreparedAuthorized) ([]database.ListAIBridgeInterceptionsRow, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.ListAuthorizedAIBridgeInterceptions(ctx, arg, prepared)
|
||||
m.queryLatencies.WithLabelValues("ListAuthorizedAIBridgeInterceptions").Observe(time.Since(start).Seconds())
|
||||
|
||||
@@ -5804,10 +5804,10 @@ func (mr *MockStoreMockRecorder) InsertWorkspaceResourceMetadata(ctx, arg any) *
|
||||
}
|
||||
|
||||
// ListAIBridgeInterceptions mocks base method.
|
||||
func (m *MockStore) ListAIBridgeInterceptions(ctx context.Context, arg database.ListAIBridgeInterceptionsParams) ([]database.AIBridgeInterception, error) {
|
||||
func (m *MockStore) ListAIBridgeInterceptions(ctx context.Context, arg database.ListAIBridgeInterceptionsParams) ([]database.ListAIBridgeInterceptionsRow, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ListAIBridgeInterceptions", ctx, arg)
|
||||
ret0, _ := ret[0].([]database.AIBridgeInterception)
|
||||
ret0, _ := ret[0].([]database.ListAIBridgeInterceptionsRow)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
@@ -5864,10 +5864,10 @@ func (mr *MockStoreMockRecorder) ListAIBridgeUserPromptsByInterceptionIDs(ctx, i
|
||||
}
|
||||
|
||||
// ListAuthorizedAIBridgeInterceptions mocks base method.
|
||||
func (m *MockStore) ListAuthorizedAIBridgeInterceptions(ctx context.Context, arg database.ListAIBridgeInterceptionsParams, prepared rbac.PreparedAuthorized) ([]database.AIBridgeInterception, error) {
|
||||
func (m *MockStore) ListAuthorizedAIBridgeInterceptions(ctx context.Context, arg database.ListAIBridgeInterceptionsParams, prepared rbac.PreparedAuthorized) ([]database.ListAIBridgeInterceptionsRow, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ListAuthorizedAIBridgeInterceptions", ctx, arg, prepared)
|
||||
ret0, _ := ret[0].([]database.AIBridgeInterception)
|
||||
ret0, _ := ret[0].([]database.ListAIBridgeInterceptionsRow)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
Generated
+3
@@ -3511,6 +3511,9 @@ COMMENT ON TRIGGER workspace_agent_name_unique_trigger ON workspace_agents IS 'U
|
||||
the uniqueness requirement. A trigger allows us to enforce uniqueness going
|
||||
forward without requiring a migration to clean up historical data.';
|
||||
|
||||
ALTER TABLE ONLY aibridge_interceptions
|
||||
ADD CONSTRAINT aibridge_interceptions_initiator_id_fkey FOREIGN KEY (initiator_id) REFERENCES users(id);
|
||||
|
||||
ALTER TABLE ONLY api_keys
|
||||
ADD CONSTRAINT api_keys_user_id_uuid_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ type ForeignKeyConstraint string
|
||||
|
||||
// ForeignKeyConstraint enums.
|
||||
const (
|
||||
ForeignKeyAibridgeInterceptionsInitiatorID ForeignKeyConstraint = "aibridge_interceptions_initiator_id_fkey" // ALTER TABLE ONLY aibridge_interceptions ADD CONSTRAINT aibridge_interceptions_initiator_id_fkey FOREIGN KEY (initiator_id) REFERENCES users(id);
|
||||
ForeignKeyAPIKeysUserIDUUID ForeignKeyConstraint = "api_keys_user_id_uuid_fkey" // ALTER TABLE ONLY api_keys ADD CONSTRAINT api_keys_user_id_uuid_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
ForeignKeyConnectionLogsOrganizationID ForeignKeyConstraint = "connection_logs_organization_id_fkey" // ALTER TABLE ONLY connection_logs ADD CONSTRAINT connection_logs_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE;
|
||||
ForeignKeyConnectionLogsWorkspaceID ForeignKeyConstraint = "connection_logs_workspace_id_fkey" // ALTER TABLE ONLY connection_logs ADD CONSTRAINT connection_logs_workspace_id_fkey FOREIGN KEY (workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE;
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
-- We didn't add an FK as a premature optimization when the aibridge tables were
|
||||
-- added, but for the initiator_id it's pretty annoying not having a strong
|
||||
-- reference.
|
||||
--
|
||||
-- Since the aibridge feature is still in early access, we're going to add the
|
||||
-- FK and drop any rows that violate it (which should be none). This isn't a
|
||||
-- very efficient migration, but since the feature is behind an experimental
|
||||
-- flag, it shouldn't have any impact on deployments that aren't using the
|
||||
-- feature.
|
||||
|
||||
-- Step 1: Add FK without validating it
|
||||
ALTER TABLE aibridge_interceptions
|
||||
ADD CONSTRAINT aibridge_interceptions_initiator_id_fkey
|
||||
FOREIGN KEY (initiator_id)
|
||||
REFERENCES users(id)
|
||||
-- We can't:
|
||||
-- - Cascade delete because this is an auditing feature, and it also
|
||||
-- wouldn't delete related aibridge rows since we don't FK them.
|
||||
-- - Set null because you can't correlate to the original user ID if the
|
||||
-- user somehow gets deleted.
|
||||
--
|
||||
-- So we just use the default and don't do anything. This will result in a
|
||||
-- deferred constraint violation error when the user is deleted.
|
||||
--
|
||||
-- In Coder, we don't delete user rows ever, so this should never happen
|
||||
-- unless an admin manually deletes a user with SQL.
|
||||
ON DELETE NO ACTION
|
||||
-- Delay validation of existing data until after we've dropped rows that
|
||||
-- violate the FK.
|
||||
NOT VALID;
|
||||
|
||||
-- Step 2: Drop existing interceptions that violate the FK.
|
||||
DELETE FROM aibridge_interceptions
|
||||
WHERE initiator_id NOT IN (SELECT id FROM users);
|
||||
|
||||
-- Step 3: Drop existing rows from other tables that no longer have a valid
|
||||
-- interception in the database.
|
||||
DELETE FROM aibridge_token_usages
|
||||
WHERE interception_id NOT IN (SELECT id FROM aibridge_interceptions);
|
||||
|
||||
DELETE FROM aibridge_user_prompts
|
||||
WHERE interception_id NOT IN (SELECT id FROM aibridge_interceptions);
|
||||
|
||||
DELETE FROM aibridge_tool_usages
|
||||
WHERE interception_id NOT IN (SELECT id FROM aibridge_interceptions);
|
||||
|
||||
-- Step 4: Validate the FK
|
||||
ALTER TABLE aibridge_interceptions
|
||||
VALIDATE CONSTRAINT aibridge_interceptions_initiator_id_fkey;
|
||||
@@ -8,7 +8,7 @@ INSERT INTO
|
||||
)
|
||||
VALUES (
|
||||
'be003e1e-b38f-43bf-847d-928074dd0aa8',
|
||||
'30095c71-380b-457a-8995-97b8ee6e5307',
|
||||
'30095c71-380b-457a-8995-97b8ee6e5307', -- admin@coder.com, from 000022_initial_v0.6.6.up.sql
|
||||
'openai',
|
||||
'gpt-5',
|
||||
'2025-09-15 12:45:13.921148+00'
|
||||
@@ -77,3 +77,82 @@ VALUES (
|
||||
'{}',
|
||||
'2025-09-15 12:45:21.674335+00'
|
||||
);
|
||||
|
||||
-- For a later migration, we'll add an invalid interception without a valid
|
||||
-- initiator_id.
|
||||
INSERT INTO
|
||||
aibridge_interceptions (
|
||||
id,
|
||||
initiator_id,
|
||||
provider,
|
||||
model,
|
||||
started_at
|
||||
)
|
||||
VALUES (
|
||||
'c6d29c6e-26a3-4137-bb2e-9dfeef3c1c26',
|
||||
'cab8d56a-8922-4999-81a9-046b43ac1312', -- user does not exist
|
||||
'openai',
|
||||
'gpt-5',
|
||||
'2025-09-15 12:45:13.921148+00'
|
||||
);
|
||||
INSERT INTO
|
||||
aibridge_token_usages (
|
||||
id,
|
||||
interception_id,
|
||||
provider_response_id,
|
||||
input_tokens,
|
||||
output_tokens,
|
||||
metadata,
|
||||
created_at
|
||||
)
|
||||
VALUES (
|
||||
'5650db6c-0b7c-49e3-bb26-9b2ba0107e11',
|
||||
'c6d29c6e-26a3-4137-bb2e-9dfeef3c1c26',
|
||||
'chatcmpl-CG2s28QlpKIoooUtXuLTmGbdtyS1k',
|
||||
10950,
|
||||
118,
|
||||
'{}',
|
||||
'2025-09-15 12:45:21.674413+00'
|
||||
);
|
||||
INSERT INTO
|
||||
aibridge_user_prompts (
|
||||
id,
|
||||
interception_id,
|
||||
provider_response_id,
|
||||
prompt,
|
||||
metadata,
|
||||
created_at
|
||||
)
|
||||
VALUES (
|
||||
'1e76cb5b-7c34-4160-b604-a4256f856169',
|
||||
'c6d29c6e-26a3-4137-bb2e-9dfeef3c1c26',
|
||||
'chatcmpl-CG2s28QlpKIoooUtXuLTmGbdtyS1k',
|
||||
'how many workspaces do i have',
|
||||
'{}',
|
||||
'2025-09-15 12:45:21.674335+00'
|
||||
);
|
||||
INSERT INTO
|
||||
aibridge_tool_usages (
|
||||
id,
|
||||
interception_id,
|
||||
provider_response_id,
|
||||
tool,
|
||||
server_url,
|
||||
input,
|
||||
injected,
|
||||
invocation_error,
|
||||
metadata,
|
||||
created_at
|
||||
)
|
||||
VALUES (
|
||||
'351b440f-d605-4f37-8ceb-011f0377b695',
|
||||
'c6d29c6e-26a3-4137-bb2e-9dfeef3c1c26',
|
||||
'chatcmpl-CG2s28QlpKIoooUtXuLTmGbdtyS1k',
|
||||
'coder_list_workspaces',
|
||||
'http://localhost:3000/api/experimental/mcp/http',
|
||||
'{}',
|
||||
true,
|
||||
NULL,
|
||||
'{}',
|
||||
'2025-09-15 12:45:21.674413+00'
|
||||
);
|
||||
|
||||
@@ -763,11 +763,11 @@ func (q *sqlQuerier) CountAuthorizedConnectionLogs(ctx context.Context, arg Coun
|
||||
}
|
||||
|
||||
type aibridgeQuerier interface {
|
||||
ListAuthorizedAIBridgeInterceptions(ctx context.Context, arg ListAIBridgeInterceptionsParams, prepared rbac.PreparedAuthorized) ([]AIBridgeInterception, error)
|
||||
ListAuthorizedAIBridgeInterceptions(ctx context.Context, arg ListAIBridgeInterceptionsParams, prepared rbac.PreparedAuthorized) ([]ListAIBridgeInterceptionsRow, error)
|
||||
CountAuthorizedAIBridgeInterceptions(ctx context.Context, arg CountAIBridgeInterceptionsParams, prepared rbac.PreparedAuthorized) (int64, error)
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) ListAuthorizedAIBridgeInterceptions(ctx context.Context, arg ListAIBridgeInterceptionsParams, prepared rbac.PreparedAuthorized) ([]AIBridgeInterception, error) {
|
||||
func (q *sqlQuerier) ListAuthorizedAIBridgeInterceptions(ctx context.Context, arg ListAIBridgeInterceptionsParams, prepared rbac.PreparedAuthorized) ([]ListAIBridgeInterceptionsRow, error) {
|
||||
authorizedFilter, err := prepared.CompileToSQL(ctx, regosql.ConvertConfig{
|
||||
VariableConverter: regosql.AIBridgeInterceptionConverter(),
|
||||
})
|
||||
@@ -794,16 +794,20 @@ func (q *sqlQuerier) ListAuthorizedAIBridgeInterceptions(ctx context.Context, ar
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []AIBridgeInterception
|
||||
var items []ListAIBridgeInterceptionsRow
|
||||
for rows.Next() {
|
||||
var i AIBridgeInterception
|
||||
var i ListAIBridgeInterceptionsRow
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.InitiatorID,
|
||||
&i.Provider,
|
||||
&i.Model,
|
||||
&i.StartedAt,
|
||||
&i.Metadata,
|
||||
&i.AIBridgeInterception.ID,
|
||||
&i.AIBridgeInterception.InitiatorID,
|
||||
&i.AIBridgeInterception.Provider,
|
||||
&i.AIBridgeInterception.Model,
|
||||
&i.AIBridgeInterception.StartedAt,
|
||||
&i.AIBridgeInterception.Metadata,
|
||||
&i.VisibleUser.ID,
|
||||
&i.VisibleUser.Username,
|
||||
&i.VisibleUser.Name,
|
||||
&i.VisibleUser.AvatarURL,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -592,7 +592,7 @@ type sqlcQuerier interface {
|
||||
InsertWorkspaceProxy(ctx context.Context, arg InsertWorkspaceProxyParams) (WorkspaceProxy, error)
|
||||
InsertWorkspaceResource(ctx context.Context, arg InsertWorkspaceResourceParams) (WorkspaceResource, error)
|
||||
InsertWorkspaceResourceMetadata(ctx context.Context, arg InsertWorkspaceResourceMetadataParams) ([]WorkspaceResourceMetadatum, error)
|
||||
ListAIBridgeInterceptions(ctx context.Context, arg ListAIBridgeInterceptionsParams) ([]AIBridgeInterception, error)
|
||||
ListAIBridgeInterceptions(ctx context.Context, arg ListAIBridgeInterceptionsParams) ([]ListAIBridgeInterceptionsRow, error)
|
||||
ListAIBridgeTokenUsagesByInterceptionIDs(ctx context.Context, interceptionIds []uuid.UUID) ([]AIBridgeTokenUsage, error)
|
||||
ListAIBridgeToolUsagesByInterceptionIDs(ctx context.Context, interceptionIds []uuid.UUID) ([]AIBridgeToolUsage, error)
|
||||
ListAIBridgeUserPromptsByInterceptionIDs(ctx context.Context, interceptionIds []uuid.UUID) ([]AIBridgeUserPrompt, error)
|
||||
|
||||
@@ -528,9 +528,12 @@ func (q *sqlQuerier) InsertAIBridgeUserPrompt(ctx context.Context, arg InsertAIB
|
||||
|
||||
const listAIBridgeInterceptions = `-- name: ListAIBridgeInterceptions :many
|
||||
SELECT
|
||||
id, initiator_id, provider, model, started_at, metadata
|
||||
aibridge_interceptions.id, aibridge_interceptions.initiator_id, aibridge_interceptions.provider, aibridge_interceptions.model, aibridge_interceptions.started_at, aibridge_interceptions.metadata,
|
||||
visible_users.id, visible_users.username, visible_users.name, visible_users.avatar_url
|
||||
FROM
|
||||
aibridge_interceptions
|
||||
JOIN
|
||||
visible_users ON visible_users.id = aibridge_interceptions.initiator_id
|
||||
WHERE
|
||||
-- Filter by time frame
|
||||
CASE
|
||||
@@ -592,7 +595,12 @@ type ListAIBridgeInterceptionsParams struct {
|
||||
Limit int32 `db:"limit_" json:"limit_"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) ListAIBridgeInterceptions(ctx context.Context, arg ListAIBridgeInterceptionsParams) ([]AIBridgeInterception, error) {
|
||||
type ListAIBridgeInterceptionsRow struct {
|
||||
AIBridgeInterception AIBridgeInterception `db:"aibridge_interception" json:"aibridge_interception"`
|
||||
VisibleUser VisibleUser `db:"visible_user" json:"visible_user"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) ListAIBridgeInterceptions(ctx context.Context, arg ListAIBridgeInterceptionsParams) ([]ListAIBridgeInterceptionsRow, error) {
|
||||
rows, err := q.db.QueryContext(ctx, listAIBridgeInterceptions,
|
||||
arg.StartedAfter,
|
||||
arg.StartedBefore,
|
||||
@@ -607,16 +615,20 @@ func (q *sqlQuerier) ListAIBridgeInterceptions(ctx context.Context, arg ListAIBr
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []AIBridgeInterception
|
||||
var items []ListAIBridgeInterceptionsRow
|
||||
for rows.Next() {
|
||||
var i AIBridgeInterception
|
||||
var i ListAIBridgeInterceptionsRow
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.InitiatorID,
|
||||
&i.Provider,
|
||||
&i.Model,
|
||||
&i.StartedAt,
|
||||
&i.Metadata,
|
||||
&i.AIBridgeInterception.ID,
|
||||
&i.AIBridgeInterception.InitiatorID,
|
||||
&i.AIBridgeInterception.Provider,
|
||||
&i.AIBridgeInterception.Model,
|
||||
&i.AIBridgeInterception.StartedAt,
|
||||
&i.AIBridgeInterception.Metadata,
|
||||
&i.VisibleUser.ID,
|
||||
&i.VisibleUser.Username,
|
||||
&i.VisibleUser.Name,
|
||||
&i.VisibleUser.AvatarURL,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -111,9 +111,12 @@ WHERE
|
||||
|
||||
-- name: ListAIBridgeInterceptions :many
|
||||
SELECT
|
||||
*
|
||||
sqlc.embed(aibridge_interceptions),
|
||||
sqlc.embed(visible_users)
|
||||
FROM
|
||||
aibridge_interceptions
|
||||
JOIN
|
||||
visible_users ON visible_users.id = aibridge_interceptions.initiator_id
|
||||
WHERE
|
||||
-- Filter by time frame
|
||||
CASE
|
||||
|
||||
@@ -1958,6 +1958,7 @@ func convertTemplateVersion(version database.TemplateVersion, job codersdk.Provi
|
||||
CreatedBy: codersdk.MinimalUser{
|
||||
ID: version.CreatedBy,
|
||||
Username: version.CreatedByUsername,
|
||||
Name: version.CreatedByName,
|
||||
AvatarURL: version.CreatedByAvatarURL,
|
||||
},
|
||||
Archived: version.Archived,
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
|
||||
type AIBridgeInterception struct {
|
||||
ID uuid.UUID `json:"id" format:"uuid"`
|
||||
InitiatorID uuid.UUID `json:"initiator_id" format:"uuid"`
|
||||
Initiator MinimalUser `json:"initiator"`
|
||||
Provider string `json:"provider"`
|
||||
Model string `json:"model"`
|
||||
Metadata map[string]any `json:"metadata"`
|
||||
|
||||
+1
-1
@@ -41,6 +41,7 @@ type UsersRequest struct {
|
||||
type MinimalUser struct {
|
||||
ID uuid.UUID `json:"id" validate:"required" table:"id" format:"uuid"`
|
||||
Username string `json:"username" validate:"required" table:"username,default_sort"`
|
||||
Name string `json:"name,omitempty" table:"name"`
|
||||
AvatarURL string `json:"avatar_url,omitempty" format:"uri"`
|
||||
}
|
||||
|
||||
@@ -50,7 +51,6 @@ type MinimalUser struct {
|
||||
// required by the frontend.
|
||||
type ReducedUser struct {
|
||||
MinimalUser `table:"m,recursive_inline"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Email string `json:"email" validate:"required" table:"email" format:"email"`
|
||||
CreatedAt time.Time `json:"created_at" validate:"required" table:"created at" format:"date-time"`
|
||||
UpdatedAt time.Time `json:"updated_at" table:"updated at" format:"date-time"`
|
||||
|
||||
Generated
+6
-1
@@ -31,7 +31,12 @@ curl -X GET http://coder-server:8080/api/v2/api/experimental/aibridge/intercepti
|
||||
"results": [
|
||||
{
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"initiator_id": "06588898-9a84-4b35-ba8f-f9cbd64946f3",
|
||||
"initiator": {
|
||||
"avatar_url": "http://example.com",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"name": "string",
|
||||
"username": "string"
|
||||
},
|
||||
"metadata": {
|
||||
"property1": null,
|
||||
"property2": null
|
||||
|
||||
Generated
+19
-3
@@ -436,7 +436,12 @@
|
||||
```json
|
||||
{
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"initiator_id": "06588898-9a84-4b35-ba8f-f9cbd64946f3",
|
||||
"initiator": {
|
||||
"avatar_url": "http://example.com",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"name": "string",
|
||||
"username": "string"
|
||||
},
|
||||
"metadata": {
|
||||
"property1": null,
|
||||
"property2": null
|
||||
@@ -496,7 +501,7 @@
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
|--------------------|---------------------------------------------------------------------|----------|--------------|-------------|
|
||||
| `id` | string | false | | |
|
||||
| `initiator_id` | string | false | | |
|
||||
| `initiator` | [codersdk.MinimalUser](#codersdkminimaluser) | false | | |
|
||||
| `metadata` | object | false | | |
|
||||
| » `[any property]` | any | false | | |
|
||||
| `model` | string | false | | |
|
||||
@@ -513,7 +518,12 @@
|
||||
"results": [
|
||||
{
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"initiator_id": "06588898-9a84-4b35-ba8f-f9cbd64946f3",
|
||||
"initiator": {
|
||||
"avatar_url": "http://example.com",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"name": "string",
|
||||
"username": "string"
|
||||
},
|
||||
"metadata": {
|
||||
"property1": null,
|
||||
"property2": null
|
||||
@@ -4947,6 +4957,7 @@ Only certain features set these fields: - FeatureManagedAgentLimit|
|
||||
{
|
||||
"avatar_url": "http://example.com",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"name": "string",
|
||||
"username": "string"
|
||||
}
|
||||
```
|
||||
@@ -4957,6 +4968,7 @@ Only certain features set these fields: - FeatureManagedAgentLimit|
|
||||
|--------------|--------|----------|--------------|-------------|
|
||||
| `avatar_url` | string | false | | |
|
||||
| `id` | string | true | | |
|
||||
| `name` | string | false | | |
|
||||
| `username` | string | true | | |
|
||||
|
||||
## codersdk.NotificationMethodsResponse
|
||||
@@ -8558,6 +8570,7 @@ Restarts will only happen on weekdays in this list on weeks which line up with W
|
||||
"created_by": {
|
||||
"avatar_url": "http://example.com",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"name": "string",
|
||||
"username": "string"
|
||||
},
|
||||
"has_external_agent": true,
|
||||
@@ -10129,6 +10142,7 @@ If the schedule is empty, the user will be updated to use the default schedule.|
|
||||
{
|
||||
"avatar_url": "http://example.com",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"name": "string",
|
||||
"role": "admin",
|
||||
"username": "string"
|
||||
}
|
||||
@@ -11769,6 +11783,7 @@ If the schedule is empty, the user will be updated to use the default schedule.|
|
||||
{
|
||||
"avatar_url": "http://example.com",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"name": "string",
|
||||
"role": "admin",
|
||||
"username": "string"
|
||||
}
|
||||
@@ -11780,6 +11795,7 @@ If the schedule is empty, the user will be updated to use the default schedule.|
|
||||
|--------------|--------------------------------------------------|----------|--------------|-------------|
|
||||
| `avatar_url` | string | false | | |
|
||||
| `id` | string | true | | |
|
||||
| `name` | string | false | | |
|
||||
| `role` | [codersdk.WorkspaceRole](#codersdkworkspacerole) | false | | |
|
||||
| `username` | string | true | | |
|
||||
|
||||
|
||||
Generated
+9
@@ -460,6 +460,7 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/templat
|
||||
"created_by": {
|
||||
"avatar_url": "http://example.com",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"name": "string",
|
||||
"username": "string"
|
||||
},
|
||||
"has_external_agent": true,
|
||||
@@ -561,6 +562,7 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/templat
|
||||
"created_by": {
|
||||
"avatar_url": "http://example.com",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"name": "string",
|
||||
"username": "string"
|
||||
},
|
||||
"has_external_agent": true,
|
||||
@@ -686,6 +688,7 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/templa
|
||||
"created_by": {
|
||||
"avatar_url": "http://example.com",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"name": "string",
|
||||
"username": "string"
|
||||
},
|
||||
"has_external_agent": true,
|
||||
@@ -1294,6 +1297,7 @@ curl -X GET http://coder-server:8080/api/v2/templates/{template}/versions \
|
||||
"created_by": {
|
||||
"avatar_url": "http://example.com",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"name": "string",
|
||||
"username": "string"
|
||||
},
|
||||
"has_external_agent": true,
|
||||
@@ -1374,6 +1378,7 @@ Status Code **200**
|
||||
| `» created_by` | [codersdk.MinimalUser](schemas.md#codersdkminimaluser) | false | | |
|
||||
| `»» avatar_url` | string(uri) | false | | |
|
||||
| `»» id` | string(uuid) | true | | |
|
||||
| `»» name` | string | false | | |
|
||||
| `»» username` | string | true | | |
|
||||
| `» has_external_agent` | boolean | false | | |
|
||||
| `» id` | string(uuid) | false | | |
|
||||
@@ -1579,6 +1584,7 @@ curl -X GET http://coder-server:8080/api/v2/templates/{template}/versions/{templ
|
||||
"created_by": {
|
||||
"avatar_url": "http://example.com",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"name": "string",
|
||||
"username": "string"
|
||||
},
|
||||
"has_external_agent": true,
|
||||
@@ -1659,6 +1665,7 @@ Status Code **200**
|
||||
| `» created_by` | [codersdk.MinimalUser](schemas.md#codersdkminimaluser) | false | | |
|
||||
| `»» avatar_url` | string(uri) | false | | |
|
||||
| `»» id` | string(uuid) | true | | |
|
||||
| `»» name` | string | false | | |
|
||||
| `»» username` | string | true | | |
|
||||
| `» has_external_agent` | boolean | false | | |
|
||||
| `» id` | string(uuid) | false | | |
|
||||
@@ -1754,6 +1761,7 @@ curl -X GET http://coder-server:8080/api/v2/templateversions/{templateversion} \
|
||||
"created_by": {
|
||||
"avatar_url": "http://example.com",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"name": "string",
|
||||
"username": "string"
|
||||
},
|
||||
"has_external_agent": true,
|
||||
@@ -1864,6 +1872,7 @@ curl -X PATCH http://coder-server:8080/api/v2/templateversions/{templateversion}
|
||||
"created_by": {
|
||||
"avatar_url": "http://example.com",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"name": "string",
|
||||
"username": "string"
|
||||
},
|
||||
"has_external_agent": true,
|
||||
|
||||
Generated
+1
@@ -1588,6 +1588,7 @@ curl -X GET http://coder-server:8080/api/v2/workspaces/{workspace}/acl \
|
||||
{
|
||||
"avatar_url": "http://example.com",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"name": "string",
|
||||
"role": "admin",
|
||||
"username": "string"
|
||||
}
|
||||
|
||||
Generated
+4
-4
@@ -25,10 +25,10 @@ Filter users by their GitHub user ID.
|
||||
|
||||
### -c, --column
|
||||
|
||||
| | |
|
||||
|---------|--------------------------------------------------------------------|
|
||||
| Type | <code>[id\|username\|email\|created at\|updated at\|status]</code> |
|
||||
| Default | <code>username,email,created at,status</code> |
|
||||
| | |
|
||||
|---------|--------------------------------------------------------------------------|
|
||||
| Type | <code>[id\|username\|name\|email\|created at\|updated at\|status]</code> |
|
||||
| Default | <code>username,email,created at,status</code> |
|
||||
|
||||
Columns to display in table output.
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ func (api *API) aiBridgeListInterceptions(rw http.ResponseWriter, r *http.Reques
|
||||
|
||||
var (
|
||||
count int64
|
||||
rows []database.AIBridgeInterception
|
||||
rows []database.ListAIBridgeInterceptionsRow
|
||||
)
|
||||
err := api.Database.InTx(func(db database.Store) error {
|
||||
// Ensure the after_id interception exists and is visible to the user.
|
||||
@@ -132,10 +132,10 @@ func (api *API) aiBridgeListInterceptions(rw http.ResponseWriter, r *http.Reques
|
||||
})
|
||||
}
|
||||
|
||||
func populatedAndConvertAIBridgeInterceptions(ctx context.Context, db database.Store, dbInterceptions []database.AIBridgeInterception) ([]codersdk.AIBridgeInterception, error) {
|
||||
func populatedAndConvertAIBridgeInterceptions(ctx context.Context, db database.Store, dbInterceptions []database.ListAIBridgeInterceptionsRow) ([]codersdk.AIBridgeInterception, error) {
|
||||
ids := make([]uuid.UUID, len(dbInterceptions))
|
||||
for i, row := range dbInterceptions {
|
||||
ids[i] = row.ID
|
||||
ids[i] = row.AIBridgeInterception.ID
|
||||
}
|
||||
|
||||
//nolint:gocritic // This is a system function until we implement a join for aibridge interceptions. AIBridge interception subresources use the same authorization call as their parent.
|
||||
@@ -170,7 +170,13 @@ func populatedAndConvertAIBridgeInterceptions(ctx context.Context, db database.S
|
||||
|
||||
items := make([]codersdk.AIBridgeInterception, len(dbInterceptions))
|
||||
for i, row := range dbInterceptions {
|
||||
items[i] = db2sdk.AIBridgeInterception(row, tokenUsagesMap[row.ID], userPromptsMap[row.ID], toolUsagesMap[row.ID])
|
||||
items[i] = db2sdk.AIBridgeInterception(
|
||||
row.AIBridgeInterception,
|
||||
row.VisibleUser,
|
||||
tokenUsagesMap[row.AIBridgeInterception.ID],
|
||||
userPromptsMap[row.AIBridgeInterception.ID],
|
||||
toolUsagesMap[row.AIBridgeInterception.ID],
|
||||
)
|
||||
}
|
||||
|
||||
return items, nil
|
||||
|
||||
@@ -72,7 +72,7 @@ func TestAIBridgeListInterceptions(t *testing.T) {
|
||||
t.Parallel()
|
||||
dv := coderdtest.DeploymentValues(t)
|
||||
dv.Experiments = []string{string(codersdk.ExperimentAIBridge)}
|
||||
client, db, _ := coderdenttest.NewWithDatabase(t, &coderdenttest.Options{
|
||||
client, db, firstUser := coderdenttest.NewWithDatabase(t, &coderdenttest.Options{
|
||||
Options: &coderdtest.Options{
|
||||
DeploymentValues: dv,
|
||||
},
|
||||
@@ -85,10 +85,28 @@ func TestAIBridgeListInterceptions(t *testing.T) {
|
||||
experimentalClient := codersdk.NewExperimentalClient(client)
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
|
||||
user1, err := client.User(ctx, codersdk.Me)
|
||||
require.NoError(t, err)
|
||||
user1Visible := database.VisibleUser{
|
||||
ID: user1.ID,
|
||||
Username: user1.Username,
|
||||
Name: user1.Name,
|
||||
AvatarURL: user1.AvatarURL,
|
||||
}
|
||||
|
||||
_, user2 := coderdtest.CreateAnotherUser(t, client, firstUser.OrganizationID)
|
||||
user2Visible := database.VisibleUser{
|
||||
ID: user2.ID,
|
||||
Username: user2.Username,
|
||||
Name: user2.Name,
|
||||
AvatarURL: user2.AvatarURL,
|
||||
}
|
||||
|
||||
// Insert a bunch of test data.
|
||||
now := dbtime.Now()
|
||||
i1 := dbgen.AIBridgeInterception(t, db, database.InsertAIBridgeInterceptionParams{
|
||||
StartedAt: now.Add(-time.Hour),
|
||||
InitiatorID: user1.ID,
|
||||
StartedAt: now.Add(-time.Hour),
|
||||
})
|
||||
i1tok1 := dbgen.AIBridgeTokenUsage(t, db, database.InsertAIBridgeTokenUsageParams{
|
||||
InterceptionID: i1.ID,
|
||||
@@ -115,14 +133,15 @@ func TestAIBridgeListInterceptions(t *testing.T) {
|
||||
CreatedAt: now.Add(-time.Minute),
|
||||
})
|
||||
i2 := dbgen.AIBridgeInterception(t, db, database.InsertAIBridgeInterceptionParams{
|
||||
StartedAt: now,
|
||||
InitiatorID: user2.ID,
|
||||
StartedAt: now,
|
||||
})
|
||||
|
||||
// Convert to SDK types for response comparison.
|
||||
// You may notice that the ordering of the inner arrays are ASC, this is
|
||||
// intentional.
|
||||
i1SDK := db2sdk.AIBridgeInterception(i1, []database.AIBridgeTokenUsage{i1tok2, i1tok1}, []database.AIBridgeUserPrompt{i1up2, i1up1}, []database.AIBridgeToolUsage{i1tool2, i1tool1})
|
||||
i2SDK := db2sdk.AIBridgeInterception(i2, nil, nil, nil)
|
||||
i1SDK := db2sdk.AIBridgeInterception(i1, user1Visible, []database.AIBridgeTokenUsage{i1tok2, i1tok1}, []database.AIBridgeUserPrompt{i1up2, i1up1}, []database.AIBridgeToolUsage{i1tool2, i1tool1})
|
||||
i2SDK := db2sdk.AIBridgeInterception(i2, user2Visible, nil, nil, nil)
|
||||
|
||||
res, err := experimentalClient.AIBridgeListInterceptions(ctx, codersdk.AIBridgeListInterceptionsFilter{})
|
||||
require.NoError(t, err)
|
||||
@@ -158,7 +177,7 @@ func TestAIBridgeListInterceptions(t *testing.T) {
|
||||
|
||||
dv := coderdtest.DeploymentValues(t)
|
||||
dv.Experiments = []string{string(codersdk.ExperimentAIBridge)}
|
||||
client, db, _ := coderdenttest.NewWithDatabase(t, &coderdenttest.Options{
|
||||
client, db, firstUser := coderdenttest.NewWithDatabase(t, &coderdenttest.Options{
|
||||
Options: &coderdtest.Options{
|
||||
DeploymentValues: dv,
|
||||
},
|
||||
@@ -178,8 +197,9 @@ func TestAIBridgeListInterceptions(t *testing.T) {
|
||||
now := dbtime.Now()
|
||||
for i := range 10 {
|
||||
interception := dbgen.AIBridgeInterception(t, db, database.InsertAIBridgeInterceptionParams{
|
||||
ID: uuid.UUID{byte(i)},
|
||||
StartedAt: now,
|
||||
ID: uuid.UUID{byte(i)},
|
||||
InitiatorID: firstUser.UserID,
|
||||
StartedAt: now,
|
||||
})
|
||||
allInterceptionIDs = append(allInterceptionIDs, interception.ID)
|
||||
}
|
||||
@@ -190,8 +210,9 @@ func TestAIBridgeListInterceptions(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
randomOffsetDur := time.Duration(randomOffset) * time.Second
|
||||
interception := dbgen.AIBridgeInterception(t, db, database.InsertAIBridgeInterceptionParams{
|
||||
ID: uuid.UUID{byte(i + 10)},
|
||||
StartedAt: now.Add(randomOffsetDur),
|
||||
ID: uuid.UUID{byte(i + 10)},
|
||||
InitiatorID: firstUser.UserID,
|
||||
StartedAt: now.Add(randomOffsetDur),
|
||||
})
|
||||
allInterceptionIDs = append(allInterceptionIDs, interception.ID)
|
||||
}
|
||||
@@ -329,27 +350,44 @@ func TestAIBridgeListInterceptions(t *testing.T) {
|
||||
},
|
||||
})
|
||||
experimentalClient := codersdk.NewExperimentalClient(client)
|
||||
_, secondUser := coderdtest.CreateAnotherUser(t, client, firstUser.OrganizationID)
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
|
||||
user1, err := client.User(ctx, codersdk.Me)
|
||||
require.NoError(t, err)
|
||||
user1Visible := database.VisibleUser{
|
||||
ID: user1.ID,
|
||||
Username: user1.Username,
|
||||
Name: user1.Name,
|
||||
AvatarURL: user1.AvatarURL,
|
||||
}
|
||||
|
||||
_, user2 := coderdtest.CreateAnotherUser(t, client, firstUser.OrganizationID)
|
||||
user2Visible := database.VisibleUser{
|
||||
ID: user2.ID,
|
||||
Username: user2.Username,
|
||||
Name: user2.Name,
|
||||
AvatarURL: user2.AvatarURL,
|
||||
}
|
||||
|
||||
// Insert a bunch of test data with varying filterable fields.
|
||||
now := dbtime.Now()
|
||||
i1 := dbgen.AIBridgeInterception(t, db, database.InsertAIBridgeInterceptionParams{
|
||||
ID: uuid.MustParse("00000000-0000-0000-0000-000000000001"),
|
||||
InitiatorID: firstUser.UserID,
|
||||
InitiatorID: user1.ID,
|
||||
Provider: "one",
|
||||
Model: "one",
|
||||
StartedAt: now,
|
||||
})
|
||||
i2 := dbgen.AIBridgeInterception(t, db, database.InsertAIBridgeInterceptionParams{
|
||||
ID: uuid.MustParse("00000000-0000-0000-0000-000000000002"),
|
||||
InitiatorID: firstUser.UserID,
|
||||
InitiatorID: user1.ID,
|
||||
Provider: "two",
|
||||
Model: "two",
|
||||
StartedAt: now.Add(-time.Hour),
|
||||
})
|
||||
i3 := dbgen.AIBridgeInterception(t, db, database.InsertAIBridgeInterceptionParams{
|
||||
ID: uuid.MustParse("00000000-0000-0000-0000-000000000003"),
|
||||
InitiatorID: secondUser.ID,
|
||||
InitiatorID: user2.ID,
|
||||
Provider: "three",
|
||||
Model: "three",
|
||||
StartedAt: now.Add(-2 * time.Hour),
|
||||
@@ -357,9 +395,9 @@ func TestAIBridgeListInterceptions(t *testing.T) {
|
||||
|
||||
// Convert to SDK types for response comparison. We don't care about the
|
||||
// inner arrays for this test.
|
||||
i1SDK := db2sdk.AIBridgeInterception(i1, nil, nil, nil)
|
||||
i2SDK := db2sdk.AIBridgeInterception(i2, nil, nil, nil)
|
||||
i3SDK := db2sdk.AIBridgeInterception(i3, nil, nil, nil)
|
||||
i1SDK := db2sdk.AIBridgeInterception(i1, user1Visible, nil, nil, nil)
|
||||
i2SDK := db2sdk.AIBridgeInterception(i2, user1Visible, nil, nil, nil)
|
||||
i3SDK := db2sdk.AIBridgeInterception(i3, user2Visible, nil, nil, nil)
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
@@ -383,12 +421,12 @@ func TestAIBridgeListInterceptions(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "Initiator/UserID",
|
||||
filter: codersdk.AIBridgeListInterceptionsFilter{Initiator: secondUser.ID.String()},
|
||||
filter: codersdk.AIBridgeListInterceptionsFilter{Initiator: user2.ID.String()},
|
||||
want: []codersdk.AIBridgeInterception{i3SDK},
|
||||
},
|
||||
{
|
||||
name: "Initiator/Username",
|
||||
filter: codersdk.AIBridgeListInterceptionsFilter{Initiator: secondUser.Username},
|
||||
filter: codersdk.AIBridgeListInterceptionsFilter{Initiator: user2.Username},
|
||||
want: []codersdk.AIBridgeInterception{i3SDK},
|
||||
},
|
||||
{
|
||||
|
||||
Generated
+2
-2
@@ -26,7 +26,7 @@ export interface AIBridgeConfig {
|
||||
// From codersdk/aibridge.go
|
||||
export interface AIBridgeInterception {
|
||||
readonly id: string;
|
||||
readonly initiator_id: string;
|
||||
readonly initiator: MinimalUser;
|
||||
readonly provider: string;
|
||||
readonly model: string;
|
||||
// empty interface{} type, falling back to unknown
|
||||
@@ -2638,6 +2638,7 @@ export interface MinimalOrganization {
|
||||
export interface MinimalUser {
|
||||
readonly id: string;
|
||||
readonly username: string;
|
||||
readonly name?: string;
|
||||
readonly avatar_url?: string;
|
||||
}
|
||||
|
||||
@@ -3951,7 +3952,6 @@ export interface RateLimitConfig {
|
||||
* required by the frontend.
|
||||
*/
|
||||
export interface ReducedUser extends MinimalUser {
|
||||
readonly name?: string;
|
||||
readonly email: string;
|
||||
readonly created_at: string;
|
||||
readonly updated_at: string;
|
||||
|
||||
@@ -102,6 +102,7 @@ func TestClient_WorkspaceUpdates(t *testing.T) {
|
||||
MinimalUser: codersdk.MinimalUser{
|
||||
ID: userID,
|
||||
Username: "rootbeer",
|
||||
Name: "Root Beer",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user