Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat: add AI Bridge request logs model filter#21340

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Draft
jakehwll wants to merge35 commits intomain
base:main
Choose a base branch
Loading
fromjakehwll/ai-bridge-request-logs-model
Draft
Show file tree
Hide file tree
Changes fromall commits
Commits
Show all changes
35 commits
Select commitHold shift + click to select a range
417aac7
feat: update header content
jakehwllDec 11, 2025
0eeb490
fix: resolve table layout
jakehwllDec 11, 2025
efbf178
feat: add provider iconography
jakehwllDec 11, 2025
2a85911
feat: extend icons into `<ProviderFilter />`
jakehwllDec 11, 2025
803ea8e
fix: resolve pagination amount position
jakehwllDec 12, 2025
ac38fc6
fix: revert `<PageHeaderTitle/>` and `<PageHeaderSubtitle />`
jakehwllDec 13, 2025
e6b20c7
feat: rename `OpenAiIcon` to `OpenAIIcon`
jakehwllDec 13, 2025
0186385
feat: return all the models from the db
jakehwllDec 13, 2025
b912173
feat: implement filtering by `model`
jakehwllDec 13, 2025
1399a6c
feat: implement `q`, `limit` and `offset`
jakehwllDec 13, 2025
63dd692
feat: add missing docs
jakehwllDec 13, 2025
5254cf5
fix: resolve `dbmock.go`
jakehwllDec 13, 2025
12a013e
chore: regenerate `dbmock.go`
jakehwllDec 13, 2025
d2050bc
fix: remove `dbmock.go` from root
jakehwllDec 13, 2025
edbe5c8
chore: commit bump (ci)
jakehwllDec 13, 2025
c544671
fix: remove unused `db`
jakehwllDec 15, 2025
2e307e5
feat: add `dbauthz_test.go`
jakehwllDec 15, 2025
887c1d1
fix: change `ListAuthorizedAIBridgeModels` to `ListAIBridgeModels` in…
jakehwllDec 15, 2025
1750466
chore: remove `.db` to remove panic error
jakehwllDec 15, 2025
2db553a
chore: rename `prepared` to `_` as its unused
jakehwllDec 15, 2025
1399fea
fix: resolve missing key in story
jakehwllDec 15, 2025
772a703
chore: commit bump (ci)
jakehwllDec 15, 2025
a858686
fix: use `ILIKE` instead of `=` on search
jakehwllDec 15, 2025
20dc29b
chore: commit bump (ci)
jakehwllDec 15, 2025
975106c
chore: commit bump (ci)
jakehwllDec 15, 2025
4df9017
fix: add comment describing `@authorize_filter` usecase
jakehwllDec 15, 2025
02cbcd4
fix: describe the default in `AIBridgeModels()`
jakehwllDec 15, 2025
ff120fe
fix: add missing comment in `queries.sql.go`
jakehwllDec 15, 2025
9dd08eb
chore!: promote AIBridge from `ExperimentalHandler` (#21278)
jakehwllDec 17, 2025
366cca9
fix: resolve icons
jakehwllDec 16, 2025
be933ce
fix: add `anthropic.svg` to `icons.json`
jakehwllDec 16, 2025
fb14167
fix: rename `anthropic.svg` to `claude-device.svg`
jakehwllDec 17, 2025
3ea068f
fix: refactor to `<AIBridgeProviderIcon />`
jakehwllDec 17, 2025
175261d
fix: reorder `"claude-device.svg"` and `"claude.svg"`
jakehwllDec 17, 2025
379b418
fix: replace `claude.svg` with `claude-device.svg` (better contrast)
jakehwllDec 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletionscoderd/apidoc/docs.go
View file
Open in desktop

Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.

15 changes: 15 additions & 0 deletionscoderd/database/dbauthz/dbauthz.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -4674,6 +4674,14 @@ func (q *querier) ListAIBridgeInterceptionsTelemetrySummaries(ctx context.Contex
return q.db.ListAIBridgeInterceptionsTelemetrySummaries(ctx, arg)
}

func (q *querier) ListAIBridgeModels(ctx context.Context, arg database.ListAIBridgeModelsParams) ([]string, 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)
}
return q.db.ListAuthorizedAIBridgeModels(ctx, arg, prep)
}

func (q *querier) ListAIBridgeTokenUsagesByInterceptionIDs(ctx context.Context, interceptionIDs []uuid.UUID) ([]database.AIBridgeTokenUsage, error) {
// This function is a system function until we implement a join for aibridge interceptions.
// Matches the behavior of the workspaces listing endpoint.
Expand DownExpand Up@@ -6183,3 +6191,10 @@ func (q *querier) CountAuthorizedAIBridgeInterceptions(ctx context.Context, arg
// database.Store interface, so dbauthz needs to implement it.
return q.CountAIBridgeInterceptions(ctx, arg)
}

func (q *querier) ListAuthorizedAIBridgeModels(ctx context.Context, arg database.ListAIBridgeModelsParams, _ rbac.PreparedAuthorized) ([]string, error) {
// TODO: Delete this function, all ListAIBridgeModels should be authorized. For now just call ListAIBridgeModels 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.
return q.ListAIBridgeModels(ctx, arg)
}
14 changes: 14 additions & 0 deletionscoderd/database/dbauthz/dbauthz_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -4662,6 +4662,20 @@ func (s *MethodTestSuite) TestAIBridge() {
check.Args(params, emptyPreparedAuthorized{}).Asserts()
}))

s.Run("ListAIBridgeModels", s.Mocked(func(db *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
params := database.ListAIBridgeModelsParams{}
db.EXPECT().ListAuthorizedAIBridgeModels(gomock.Any(), params, gomock.Any()).Return([]string{}, nil).AnyTimes()
// No asserts here because SQLFilter.
check.Args(params).Asserts()
}))

s.Run("ListAuthorizedAIBridgeModels", s.Mocked(func(db *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
params := database.ListAIBridgeModelsParams{}
db.EXPECT().ListAuthorizedAIBridgeModels(gomock.Any(), params, gomock.Any()).Return([]string{}, nil).AnyTimes()
// No asserts here because SQLFilter.
check.Args(params, emptyPreparedAuthorized{}).Asserts()
}))

s.Run("CountAIBridgeInterceptions", s.Mocked(func(db *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
params := database.CountAIBridgeInterceptionsParams{}
db.EXPECT().CountAuthorizedAIBridgeInterceptions(gomock.Any(), params, gomock.Any()).Return(int64(0), nil).AnyTimes()
Expand Down
14 changes: 14 additions & 0 deletionscoderd/database/dbmetrics/querymetrics.go
View file
Open in desktop

Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.

30 changes: 30 additions & 0 deletionscoderd/database/dbmock/dbmock.go
View file
Open in desktop

Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.

30 changes: 30 additions & 0 deletionscoderd/database/modelqueries.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -768,6 +768,7 @@ func (q *sqlQuerier) CountAuthorizedConnectionLogs(ctx context.Context, arg Coun
type aibridgeQuerier interface {
ListAuthorizedAIBridgeInterceptions(ctx context.Context, arg ListAIBridgeInterceptionsParams, prepared rbac.PreparedAuthorized) ([]ListAIBridgeInterceptionsRow, error)
CountAuthorizedAIBridgeInterceptions(ctx context.Context, arg CountAIBridgeInterceptionsParams, prepared rbac.PreparedAuthorized) (int64, error)
ListAuthorizedAIBridgeModels(ctx context.Context, arg ListAIBridgeModelsParams, prepared rbac.PreparedAuthorized) ([]string, error)
}

func (q *sqlQuerier) ListAuthorizedAIBridgeInterceptions(ctx context.Context, arg ListAIBridgeInterceptionsParams, prepared rbac.PreparedAuthorized) ([]ListAIBridgeInterceptionsRow, error) {
Expand DownExpand Up@@ -866,6 +867,35 @@ func (q *sqlQuerier) CountAuthorizedAIBridgeInterceptions(ctx context.Context, a
return count, nil
}

func (q *sqlQuerier) ListAuthorizedAIBridgeModels(ctx context.Context, arg ListAIBridgeModelsParams, prepared rbac.PreparedAuthorized) ([]string, error) {
authorizedFilter, err := prepared.CompileToSQL(ctx, regosql.ConvertConfig{
VariableConverter: regosql.AIBridgeInterceptionConverter(),
})
if err != nil {
return nil, xerrors.Errorf("compile authorized filter: %w", err)
}
filtered, err := insertAuthorizedFilter(listAIBridgeModels, fmt.Sprintf(" AND %s", authorizedFilter))
if err != nil {
return nil, xerrors.Errorf("insert authorized filter: %w", err)
}

query := fmt.Sprintf("-- name: ListAIBridgeModels :many\n%s", filtered)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

nit:-- name: ListAuthorizedAIBridgeModels

rows, err := q.db.QueryContext(ctx, query, arg.Model, arg.Offset, arg.Limit)
if err != nil {
return nil, err
}
defer rows.Close()
var items []string
for rows.Next() {
var model string
if err := rows.Scan(&model); err != nil {
return nil, err
}
items = append(items, model)
}
return items, nil
}

func insertAuthorizedFilter(query string, replaceWith string) (string, error) {
if !strings.Contains(query, authorizedQueryPlaceholder) {
return "", xerrors.Errorf("query does not contain authorized replace string, this is not an authorized query")
Expand Down
1 change: 1 addition & 0 deletionscoderd/database/querier.go
View file
Open in desktop

Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.

51 changes: 51 additions & 0 deletionscoderd/database/queries.sql.go
View file
Open in desktop

Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.

22 changes: 22 additions & 0 deletionscoderd/database/queries/aibridge.sql
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -366,3 +366,25 @@ SELECT (
(SELECT COUNT(*) FROM user_prompts) +
(SELECT COUNT(*) FROM interceptions)
)::bigint as total_deleted;

-- name: ListAIBridgeModels :many
SELECT
model
FROM
aibridge_interceptions
WHERE
ended_at IS NOT NULL
-- Filter model
AND CASE
WHEN @model::text != '' THEN aibridge_interceptions.model ILIKE '%' || @model::text || '%'
Copy link
Contributor

@pawbanapawbanaDec 19, 2025
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I'm a bit confused why we would want to filter model by model name? There shouldn't be that many unique models? Could we return all models and filter in the frontend (in doogfood there are distinct 28 models now)?

Problem with filtering by text in backend is efficiency,aibridge_interceptions table can be quite large.
If I understand correctly, current code will do full table scan.
To use existing B-tree index onmodel column filtering should be case sensitive prefix matching:aibridge_interceptions.model LIKE @model::text || '%'

or different index needs to be created:https://www.postgresql.org/docs/current/pgtrgm.html#PGTRGM-INDEX

Also if I understand correctly authorization will add some WHERE initiator_id = "..." clause. There is no composite index but I don't think it matters that much in this case as there are single column indexes on both initiator_id and model columns.

ELSE true
END
-- We use an `@authorize_filter` as we are attempting to list models that are relevant
-- to the user and what they are allowed to see.
-- Authorize Filter clause will be injected below in ListAIBridgeModelsAuthorized
-- @authorize_filter
GROUP BY
model
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

nit: SELECT DISTINCT model could be used instead of GROUP BY model
for me it is a bit easier to read.

LIMIT COALESCE(NULLIF(@limit_::integer, 0), 100)
OFFSET @offset_
;
29 changes: 29 additions & 0 deletionscoderd/searchquery/search.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -391,6 +391,35 @@ func AIBridgeInterceptions(ctx context.Context, db database.Store, query string,
return filter, parser.Errors
}

func AIBridgeModels(query string, page codersdk.Pagination) (database.ListAIBridgeModelsParams, []codersdk.ValidationError) {
// nolint:exhaustruct // Empty values just means "don't filter by that field".
filter := database.ListAIBridgeModelsParams{
// #nosec G115 - Safe conversion for pagination offset which is expected to be within int32 range
Offset: int32(page.Offset),
// #nosec G115 - Safe conversion for pagination limit which is expected to be within int32 range
Limit: int32(page.Limit),
}

if query == "" {
return filter, nil
}

values, errors := searchTerms(query, func(term string, values url.Values) error {
// Defaults to the `model` if no `key:value` pair is provided.
values.Add("model", term)
return nil
})
if len(errors) > 0 {
return filter, errors
}

parser := httpapi.NewQueryParamParser()
filter.Model = parser.String(values, "", "model")

parser.ErrorExcessParams(values)
return filter, parser.Errors
}

// Tasks parses a search query for tasks.
//
// Supported query parameters:
Expand Down
33 changes: 33 additions & 0 deletionsdocs/reference/api/aibridge.md
View file
Open in desktop

Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.

Loading
Loading

[8]ページ先頭

©2009-2025 Movatter.jp