Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
641a866
feat(codersdk): add AI provider chat APIs
ibetitsmike May 16, 2026
1895592
feat(coderd): add AI provider chat routes
ibetitsmike May 16, 2026
0b819aa
Merge branch 'mike/ai-providers/schema-expand' into mike/ai-providers…
ibetitsmike May 16, 2026
663d3d5
Merge branch 'mike/ai-providers/schema-expand' into mike/ai-providers…
ibetitsmike May 16, 2026
e82b66f
test(coderd): cover AI provider chat APIs
ibetitsmike May 16, 2026
7c54e12
Merge branch 'mike/ai-providers/schema-expand' into mike/ai-providers…
ibetitsmike May 16, 2026
f088432
Merge branch 'mike/ai-providers/schema-expand' into mike/ai-providers…
ibetitsmike May 16, 2026
72a5df4
Merge branch 'mike/ai-providers/schema-expand' into mike/ai-providers…
ibetitsmike May 16, 2026
ab08cf6
Merge branch 'mike/ai-providers/schema-expand' into mike/ai-providers…
ibetitsmike May 16, 2026
a735065
fix(coderd): harden AI provider API validation
ibetitsmike May 16, 2026
b345f39
Merge branch 'mike/ai-providers/schema-expand' into mike/ai-providers…
ibetitsmike May 16, 2026
c077e86
Merge branch 'mike/ai-providers/schema-expand' into mike/ai-providers…
ibetitsmike May 16, 2026
6539df3
Merge branch 'mike/ai-providers/schema-expand' into mike/ai-providers…
ibetitsmike May 16, 2026
74a5622
test(coderd/database/dbauthz): cover AI provider row lock authz
ibetitsmike May 16, 2026
a22b9cf
feat: add AI provider chat routes
ibetitsmike May 16, 2026
b8b1cb6
fix(coderd): include AI providers in enabled model queries
ibetitsmike May 16, 2026
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
21 changes: 21 additions & 0 deletions coderd/coderd.go
Original file line number Diff line number Diff line change
Expand Up @@ -1250,6 +1250,20 @@ func New(options *Options) *API {
r.Delete("/", api.deleteChatProvider)
})
})
r.Route("/ai-providers", func(r chi.Router) {
r.Get("/", api.listAIProviders)
r.Post("/", api.createAIProvider)
r.Route("/{aiProvider}", func(r chi.Router) {
r.Get("/", api.readAIProvider)
r.Patch("/", api.updateAIProvider)
r.Delete("/", api.deleteAIProvider)
r.Route("/keys", func(r chi.Router) {
r.Get("/", api.listAIProviderKeys)
r.Post("/", api.createAIProviderKey)
r.Delete("/{aiProviderKey}", api.deleteAIProviderKey)
})
})
})
// TODO(cian): place under /api/experimental/chats/config
r.Route("/model-configs", func(r chi.Router) {
r.Get("/", api.listChatModelConfigs)
Expand Down Expand Up @@ -1279,6 +1293,13 @@ func New(options *Options) *API {
r.Delete("/", api.deleteUserChatProviderKey)
})
})
r.Route("/user-ai-provider-keys", func(r chi.Router) {
r.Get("/", api.listUserAIProviderKeyConfigs)
r.Route("/{aiProvider}", func(r chi.Router) {
r.Put("/", api.upsertUserAIProviderKey)
r.Delete("/", api.deleteUserAIProviderKey)
})
})
r.Route("/{chat}", func(r chi.Router) {
r.Use(httpmw.ExtractChatParam(options.Database))
r.Get("/", api.getChat)
Expand Down
7 changes: 7 additions & 0 deletions coderd/database/dbauthz/dbauthz.go
Original file line number Diff line number Diff line change
Expand Up @@ -2532,6 +2532,13 @@ func (q *querier) GetAIProviderByID(ctx context.Context, id uuid.UUID) (database
return q.db.GetAIProviderByID(ctx, id)
}

func (q *querier) GetAIProviderByIDForUpdate(ctx context.Context, id uuid.UUID) (database.AIProvider, error) {
Comment thread
ibetitsmike marked this conversation as resolved.
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceAIProvider); err != nil {
return database.AIProvider{}, err
}
return q.db.GetAIProviderByIDForUpdate(ctx, id)
}

func (q *querier) GetAIProviderByName(ctx context.Context, name string) (database.AIProvider, error) {
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceAIProvider); err != nil {
return database.AIProvider{}, err
Expand Down
6 changes: 6 additions & 0 deletions coderd/database/dbauthz/dbauthz_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6277,6 +6277,11 @@ func (s *MethodTestSuite) TestAIBridge() {
dbm.EXPECT().GetAIProviderByID(gomock.Any(), provider.ID).Return(provider, nil).AnyTimes()
check.Args(provider.ID).Asserts(rbac.ResourceAIProvider, policy.ActionRead).Returns(provider)
}))
s.Run("GetAIProviderByIDForUpdate", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
provider := testutil.Fake(s.T(), faker, database.AIProvider{})
dbm.EXPECT().GetAIProviderByIDForUpdate(gomock.Any(), provider.ID).Return(provider, nil).AnyTimes()
check.Args(provider.ID).Asserts(rbac.ResourceAIProvider, policy.ActionUpdate).Returns(provider)
}))
s.Run("GetAIProviderByName", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
provider := testutil.Fake(s.T(), faker, database.AIProvider{})
dbm.EXPECT().GetAIProviderByName(gomock.Any(), provider.Name).Return(provider, nil).AnyTimes()
Expand Down Expand Up @@ -6305,6 +6310,7 @@ func (s *MethodTestSuite) TestAIBridge() {
provider := testutil.Fake(s.T(), faker, database.AIProvider{})
arg := database.UpdateAIProviderParams{
ID: provider.ID,
Name: provider.Name,
Enabled: true,
BaseUrl: "https://api.example.com/",
}
Expand Down
8 changes: 8 additions & 0 deletions coderd/database/dbmetrics/querymetrics.go

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

15 changes: 15 additions & 0 deletions coderd/database/dbmock/dbmock.go

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

1 change: 1 addition & 0 deletions coderd/database/querier.go

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

62 changes: 52 additions & 10 deletions coderd/database/queries.sql.go

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

10 changes: 10 additions & 0 deletions coderd/database/queries/ai_providers.sql
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ FROM
WHERE
id = @id::uuid AND deleted = FALSE;

-- name: GetAIProviderByIDForUpdate :one
SELECT
*
FROM
ai_providers
WHERE
id = @id::uuid AND deleted = FALSE
FOR UPDATE;

-- name: GetAIProviderByName :one
SELECT
*
Expand Down Expand Up @@ -54,6 +63,7 @@ RETURNING
UPDATE
ai_providers
SET
name = @name::text,
display_name = sqlc.narg('display_name')::text,
enabled = @enabled::boolean,
base_url = @base_url::text,
Expand Down
18 changes: 14 additions & 4 deletions coderd/database/queries/chatmodelconfigs.sql
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,17 @@ SELECT
cmc.*
FROM
chat_model_configs cmc
JOIN
LEFT JOIN
ai_providers ap ON ap.id = cmc.ai_provider_id AND ap.deleted = FALSE
LEFT JOIN
chat_providers cp ON cp.provider = cmc.provider
WHERE
cmc.enabled = TRUE
AND cmc.deleted = FALSE
AND cp.enabled = TRUE
AND (
(cmc.ai_provider_id IS NOT NULL AND ap.enabled = TRUE)
OR (cmc.ai_provider_id IS NULL AND cp.enabled = TRUE)
)
ORDER BY
cmc.provider ASC,
cmc.model ASC,
Expand All @@ -53,13 +58,18 @@ FROM
chat_model_configs cmc
-- Providers can be disabled independently of their model configs.
-- Check both to ensure the selected config is actually usable.
JOIN
LEFT JOIN
ai_providers ap ON ap.id = cmc.ai_provider_id AND ap.deleted = FALSE
LEFT JOIN
chat_providers cp ON cp.provider = cmc.provider
WHERE
cmc.id = @id::uuid
AND cmc.deleted = FALSE
AND cmc.enabled = TRUE
AND cp.enabled = TRUE;
AND (
(cmc.ai_provider_id IS NOT NULL AND ap.enabled = TRUE)
OR (cmc.ai_provider_id IS NULL AND cp.enabled = TRUE)
);

-- name: InsertChatModelConfig :one
INSERT INTO chat_model_configs (
Expand Down
Loading
Loading