diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go
index 43f6b8bbfb0fc..4cc16522e1a3a 100644
--- a/coderd/apidoc/docs.go
+++ b/coderd/apidoc/docs.go
@@ -136,6 +136,34 @@ const docTemplate = `{
}
}
},
+ "/aibridge/models": {
+ "get": {
+ "security": [
+ {
+ "CoderSessionToken": []
+ }
+ ],
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "AI Bridge"
+ ],
+ "summary": "List AI Bridge models",
+ "operationId": "list-ai-bridge-models",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "schema": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ },
"/appearance": {
"get": {
"security": [
diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json
index 702c79ef022cd..65169579e7670 100644
--- a/coderd/apidoc/swagger.json
+++ b/coderd/apidoc/swagger.json
@@ -112,6 +112,30 @@
}
}
},
+ "/aibridge/models": {
+ "get": {
+ "security": [
+ {
+ "CoderSessionToken": []
+ }
+ ],
+ "produces": ["application/json"],
+ "tags": ["AI Bridge"],
+ "summary": "List AI Bridge models",
+ "operationId": "list-ai-bridge-models",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "schema": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ },
"/appearance": {
"get": {
"security": [
diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go
index 4962949f7fd49..001365a121bd0 100644
--- a/coderd/database/dbauthz/dbauthz.go
+++ b/coderd/database/dbauthz/dbauthz.go
@@ -4647,6 +4647,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.
@@ -6156,3 +6164,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)
+}
diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go
index 11909b1a65e93..45dba5ea0aee5 100644
--- a/coderd/database/dbauthz/dbauthz_test.go
+++ b/coderd/database/dbauthz/dbauthz_test.go
@@ -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()
diff --git a/coderd/database/dbmetrics/querymetrics.go b/coderd/database/dbmetrics/querymetrics.go
index 6a018f41905f1..7965f65d5a8b0 100644
--- a/coderd/database/dbmetrics/querymetrics.go
+++ b/coderd/database/dbmetrics/querymetrics.go
@@ -2811,6 +2811,13 @@ func (m queryMetricsStore) ListAIBridgeInterceptionsTelemetrySummaries(ctx conte
return r0, r1
}
+func (m queryMetricsStore) ListAIBridgeModels(ctx context.Context, arg database.ListAIBridgeModelsParams) ([]string, error) {
+ start := time.Now()
+ r0, r1 := m.s.ListAIBridgeModels(ctx, arg)
+ m.queryLatencies.WithLabelValues("ListAIBridgeModels").Observe(time.Since(start).Seconds())
+ return r0, r1
+}
+
func (m queryMetricsStore) ListAIBridgeTokenUsagesByInterceptionIDs(ctx context.Context, interceptionIds []uuid.UUID) ([]database.AIBridgeTokenUsage, error) {
start := time.Now()
r0, r1 := m.s.ListAIBridgeTokenUsagesByInterceptionIDs(ctx, interceptionIds)
@@ -3867,3 +3874,10 @@ func (m queryMetricsStore) CountAuthorizedAIBridgeInterceptions(ctx context.Cont
m.queryLatencies.WithLabelValues("CountAuthorizedAIBridgeInterceptions").Observe(time.Since(start).Seconds())
return r0, r1
}
+
+func (m queryMetricsStore) ListAuthorizedAIBridgeModels(ctx context.Context, arg database.ListAIBridgeModelsParams, prepared rbac.PreparedAuthorized) ([]string, error) {
+ start := time.Now()
+ r0, r1 := m.s.ListAuthorizedAIBridgeModels(ctx, arg, prepared)
+ m.queryLatencies.WithLabelValues("ListAuthorizedAIBridgeModels").Observe(time.Since(start).Seconds())
+ return r0, r1
+}
diff --git a/coderd/database/dbmock/dbmock.go b/coderd/database/dbmock/dbmock.go
index f25e91e90c249..a675d00b82ca5 100644
--- a/coderd/database/dbmock/dbmock.go
+++ b/coderd/database/dbmock/dbmock.go
@@ -6011,6 +6011,21 @@ func (mr *MockStoreMockRecorder) ListAIBridgeInterceptionsTelemetrySummaries(ctx
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListAIBridgeInterceptionsTelemetrySummaries", reflect.TypeOf((*MockStore)(nil).ListAIBridgeInterceptionsTelemetrySummaries), ctx, arg)
}
+// ListAIBridgeModels mocks base method.
+func (m *MockStore) ListAIBridgeModels(ctx context.Context, arg database.ListAIBridgeModelsParams) ([]string, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "ListAIBridgeModels", ctx, arg)
+ ret0, _ := ret[0].([]string)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// ListAIBridgeModels indicates an expected call of ListAIBridgeModels.
+func (mr *MockStoreMockRecorder) ListAIBridgeModels(ctx, arg any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListAIBridgeModels", reflect.TypeOf((*MockStore)(nil).ListAIBridgeModels), ctx, arg)
+}
+
// ListAIBridgeTokenUsagesByInterceptionIDs mocks base method.
func (m *MockStore) ListAIBridgeTokenUsagesByInterceptionIDs(ctx context.Context, interceptionIds []uuid.UUID) ([]database.AIBridgeTokenUsage, error) {
m.ctrl.T.Helper()
@@ -6071,6 +6086,21 @@ func (mr *MockStoreMockRecorder) ListAuthorizedAIBridgeInterceptions(ctx, arg, p
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListAuthorizedAIBridgeInterceptions", reflect.TypeOf((*MockStore)(nil).ListAuthorizedAIBridgeInterceptions), ctx, arg, prepared)
}
+// ListAuthorizedAIBridgeModels mocks base method.
+func (m *MockStore) ListAuthorizedAIBridgeModels(ctx context.Context, arg database.ListAIBridgeModelsParams, prepared rbac.PreparedAuthorized) ([]string, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "ListAuthorizedAIBridgeModels", ctx, arg, prepared)
+ ret0, _ := ret[0].([]string)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// ListAuthorizedAIBridgeModels indicates an expected call of ListAuthorizedAIBridgeModels.
+func (mr *MockStoreMockRecorder) ListAuthorizedAIBridgeModels(ctx, arg, prepared any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListAuthorizedAIBridgeModels", reflect.TypeOf((*MockStore)(nil).ListAuthorizedAIBridgeModels), ctx, arg, prepared)
+}
+
// ListProvisionerKeysByOrganization mocks base method.
func (m *MockStore) ListProvisionerKeysByOrganization(ctx context.Context, organizationID uuid.UUID) ([]database.ProvisionerKey, error) {
m.ctrl.T.Helper()
diff --git a/coderd/database/modelqueries.go b/coderd/database/modelqueries.go
index fae0f3eca4fa4..d12adfed41b8b 100644
--- a/coderd/database/modelqueries.go
+++ b/coderd/database/modelqueries.go
@@ -766,6 +766,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) {
@@ -864,6 +865,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)
+ 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")
diff --git a/coderd/database/querier.go b/coderd/database/querier.go
index 7202d22f3d142..0a698f968f5bf 100644
--- a/coderd/database/querier.go
+++ b/coderd/database/querier.go
@@ -622,6 +622,7 @@ type sqlcQuerier interface {
// Finds all unique AI Bridge interception telemetry summaries combinations
// (provider, model, client) in the given timeframe for telemetry reporting.
ListAIBridgeInterceptionsTelemetrySummaries(ctx context.Context, arg ListAIBridgeInterceptionsTelemetrySummariesParams) ([]ListAIBridgeInterceptionsTelemetrySummariesRow, error)
+ ListAIBridgeModels(ctx context.Context, arg ListAIBridgeModelsParams) ([]string, 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)
diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go
index 4c81d43441cd0..6fabd7e7b43c7 100644
--- a/coderd/database/queries.sql.go
+++ b/coderd/database/queries.sql.go
@@ -910,6 +910,57 @@ func (q *sqlQuerier) ListAIBridgeInterceptionsTelemetrySummaries(ctx context.Con
return items, nil
}
+const listAIBridgeModels = `-- name: ListAIBridgeModels :many
+SELECT
+ model
+FROM
+ aibridge_interceptions
+WHERE
+ ended_at IS NOT NULL
+ -- Filter model
+ AND CASE
+ WHEN $1::text != '' THEN aibridge_interceptions.model ILIKE '%' || $1::text || '%'
+ 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
+LIMIT COALESCE(NULLIF($3::integer, 0), 100)
+OFFSET $2
+`
+
+type ListAIBridgeModelsParams struct {
+ Model string `db:"model" json:"model"`
+ Offset int32 `db:"offset_" json:"offset_"`
+ Limit int32 `db:"limit_" json:"limit_"`
+}
+
+func (q *sqlQuerier) ListAIBridgeModels(ctx context.Context, arg ListAIBridgeModelsParams) ([]string, error) {
+ rows, err := q.db.QueryContext(ctx, listAIBridgeModels, 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)
+ }
+ if err := rows.Close(); err != nil {
+ return nil, err
+ }
+ if err := rows.Err(); err != nil {
+ return nil, err
+ }
+ return items, nil
+}
+
const listAIBridgeTokenUsagesByInterceptionIDs = `-- name: ListAIBridgeTokenUsagesByInterceptionIDs :many
SELECT
id, interception_id, provider_response_id, input_tokens, output_tokens, metadata, created_at
diff --git a/coderd/database/queries/aibridge.sql b/coderd/database/queries/aibridge.sql
index 960fe18ec07ca..9463bff4d3790 100644
--- a/coderd/database/queries/aibridge.sql
+++ b/coderd/database/queries/aibridge.sql
@@ -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 || '%'
+ 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
+LIMIT COALESCE(NULLIF(@limit_::integer, 0), 100)
+OFFSET @offset_
+;
diff --git a/coderd/searchquery/search.go b/coderd/searchquery/search.go
index 59ec3e04923ff..6728fbfe54227 100644
--- a/coderd/searchquery/search.go
+++ b/coderd/searchquery/search.go
@@ -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:
diff --git a/docs/reference/api/aibridge.md b/docs/reference/api/aibridge.md
index 9969a51d4adc7..e0b0187885e2b 100644
--- a/docs/reference/api/aibridge.md
+++ b/docs/reference/api/aibridge.md
@@ -103,3 +103,36 @@ curl -X GET http://coder-server:8080/api/v2/aibridge/interceptions \
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.AIBridgeListInterceptionsResponse](schemas.md#codersdkaibridgelistinterceptionsresponse) |
To perform this operation, you must be authenticated. [Learn more](authentication.md).
+
+## List AI Bridge models
+
+### Code samples
+
+```shell
+# Example request using curl
+curl -X GET http://coder-server:8080/api/v2/aibridge/models \
+ -H 'Accept: application/json' \
+ -H 'Coder-Session-Token: API_KEY'
+```
+
+`GET /aibridge/models`
+
+### Example responses
+
+> 200 Response
+
+```json
+[
+ "string"
+]
+```
+
+### Responses
+
+| Status | Meaning | Description | Schema |
+|--------|---------------------------------------------------------|-------------|-----------------|
+| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | array of string |
+
+
Response Schema
+
+To perform this operation, you must be authenticated. [Learn more](authentication.md).
diff --git a/enterprise/coderd/aibridge.go b/enterprise/coderd/aibridge.go
index d1d12d7b027c9..1549a14cfcb2b 100644
--- a/enterprise/coderd/aibridge.go
+++ b/enterprise/coderd/aibridge.go
@@ -23,7 +23,9 @@ import (
const (
maxListInterceptionsLimit = 1000
+ maxListModelsLimit = 1000
defaultListInterceptionsLimit = 100
+ defaultListModelsLimit = 100
// aiBridgeRateLimitWindow is the fixed duration for rate limiting AI Bridge
// requests. This is hardcoded to keep configuration simple.
aiBridgeRateLimitWindow = time.Second
@@ -42,6 +44,7 @@ func aibridgeHandler(api *API, middlewares ...func(http.Handler) http.Handler) f
r.Group(func(r chi.Router) {
r.Use(middlewares...)
r.Get("/interceptions", api.aiBridgeListInterceptions)
+ r.Get("/models", api.aiBridgeListModels)
})
// Apply overload protection middleware to the aibridged handler.
@@ -230,3 +233,55 @@ func populatedAndConvertAIBridgeInterceptions(ctx context.Context, db database.S
return items, nil
}
+
+// aiBridgeListModels returns all AI Bridge models a user can see.
+//
+// @Summary List AI Bridge models
+// @ID list-ai-bridge-models
+// @Security CoderSessionToken
+// @Produce json
+// @Tags AI Bridge
+// @Success 200 {array} string
+// @Router /aibridge/models [get]
+func (api *API) aiBridgeListModels(rw http.ResponseWriter, r *http.Request) {
+ ctx := r.Context()
+
+ page, ok := coderd.ParsePagination(rw, r)
+ if !ok {
+ return
+ }
+
+ if page.Limit == 0 {
+ page.Limit = defaultListModelsLimit
+ }
+
+ if page.Limit > maxListModelsLimit || page.Limit < 1 {
+ httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
+ Message: "Invalid pagination limit value.",
+ Detail: fmt.Sprintf("Pagination limit must be in range (0, %d]", maxListModelsLimit),
+ })
+ return
+ }
+
+ queryStr := r.URL.Query().Get("q")
+ filter, errs := searchquery.AIBridgeModels(queryStr, page)
+
+ if len(errs) > 0 {
+ httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
+ Message: "Invalid AI Bridge models search query.",
+ Validations: errs,
+ })
+ return
+ }
+
+ models, err := api.Database.ListAIBridgeModels(ctx, filter)
+ if err != nil {
+ httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
+ Message: "Internal error getting AI Bridge models.",
+ Detail: err.Error(),
+ })
+ return
+ }
+
+ httpapi.Write(ctx, rw, http.StatusOK, models)
+}
diff --git a/enterprise/coderd/aibridge_test.go b/enterprise/coderd/aibridge_test.go
index d35b166402f21..db8fc4b7c26b4 100644
--- a/enterprise/coderd/aibridge_test.go
+++ b/enterprise/coderd/aibridge_test.go
@@ -674,11 +674,6 @@ func TestAIBridgeRouting(t *testing.T) {
path: "/api/v2/aibridge/openai/v1/chat/completions",
expectedPath: "/openai/v1/chat/completions",
},
- {
- name: "ExperimentalPrefix",
- path: "/api/experimental/aibridge/openai/v1/chat/completions",
- expectedPath: "/openai/v1/chat/completions",
- },
}
for _, tc := range cases {
diff --git a/enterprise/coderd/coderd.go b/enterprise/coderd/coderd.go
index bf1a5acf531ee..fe9bf589b9af2 100644
--- a/enterprise/coderd/coderd.go
+++ b/enterprise/coderd/coderd.go
@@ -226,10 +226,8 @@ func New(ctx context.Context, options *Options) (_ *API, err error) {
return api.refreshEntitlements(ctx)
}
- api.AGPL.ExperimentalHandler.Group(func(r chi.Router) {
- // Deprecated.
- // TODO: remove with Beta release.
- r.Route("/aibridge", aibridgeHandler(api, apiKeyMiddleware))
+ api.AGPL.ExperimentalHandler.Group(func(_ chi.Router) {
+ // Add enterprise-only experimental routes here
})
api.AGPL.APIHandler.Group(func(r chi.Router) {
diff --git a/site/src/api/api.ts b/site/src/api/api.ts
index 656d7137ad2d4..cff9a7867e013 100644
--- a/site/src/api/api.ts
+++ b/site/src/api/api.ts
@@ -32,6 +32,8 @@ import type {
} from "./typesGenerated";
import * as TypesGen from "./typesGenerated";
+const API_PREFIX = "/api/v2";
+
const getMissingParameters = (
oldBuildParameters: TypesGen.WorkspaceBuildParameter[],
newBuildParameters: TypesGen.WorkspaceBuildParameter[],
@@ -123,7 +125,7 @@ export const watchAgentMetadata = (
agentId: string,
): OneWayWebSocket => {
return new OneWayWebSocket({
- apiRoute: `/api/v2/workspaceagents/${agentId}/watch-metadata-ws`,
+ apiRoute: `${API_PREFIX}/workspaceagents/${agentId}/watch-metadata-ws`,
});
};
@@ -134,7 +136,7 @@ export const watchWorkspace = (
workspaceId: string,
): OneWayWebSocket => {
return new OneWayWebSocket({
- apiRoute: `/api/v2/workspaces/${workspaceId}/watch-ws`,
+ apiRoute: `${API_PREFIX}/workspaces/${workspaceId}/watch-ws`,
});
};
@@ -142,7 +144,7 @@ export const watchAgentContainers = (
agentId: string,
): OneWayWebSocket => {
return new OneWayWebSocket({
- apiRoute: `/api/v2/workspaceagents/${agentId}/containers/watch`,
+ apiRoute: `${API_PREFIX}/workspaceagents/${agentId}/containers/watch`,
});
};
@@ -154,7 +156,7 @@ export function watchInboxNotifications(
params?: WatchInboxNotificationsParams,
): OneWayWebSocket {
return new OneWayWebSocket({
- apiRoute: "/api/v2/notifications/inbox/watch",
+ apiRoute: `${API_PREFIX}/notifications/inbox/watch`,
searchParams: params,
});
}
@@ -220,7 +222,7 @@ export const watchBuildLogsByTemplateVersionId = (
}
const socket = createWebSocket(
- `/api/v2/templateversions/${versionId}/logs`,
+ `${API_PREFIX}/templateversions/${versionId}/logs`,
searchParams,
);
@@ -260,7 +262,7 @@ export const watchWorkspaceAgentLogs = (
}
return new OneWayWebSocket({
- apiRoute: `/api/v2/workspaceagents/${agentId}/logs`,
+ apiRoute: `${API_PREFIX}/workspaceagents/${agentId}/logs`,
searchParams,
});
};
@@ -285,7 +287,7 @@ export const watchBuildLogsByBuildId = (
}
const socket = createWebSocket(
- `/api/v2/workspacebuilds/${buildId}/logs`,
+ `${API_PREFIX}/workspacebuilds/${buildId}/logs`,
searchParams,
);
@@ -453,7 +455,7 @@ class ApiMethods {
): Promise => {
const payload = JSON.stringify({ email, password });
const response = await this.axios.post(
- "/api/v2/users/login",
+ `${API_PREFIX}/users/login`,
payload,
{ headers: { ...BASE_CONTENT_TYPE_JSON } },
);
@@ -463,7 +465,7 @@ class ApiMethods {
convertToOAUTH = async (request: TypesGen.ConvertLoginRequest) => {
const response = await this.axios.post(
- "/api/v2/users/me/convert-login",
+ `${API_PREFIX}/users/me/convert-login`,
request,
);
@@ -471,17 +473,19 @@ class ApiMethods {
};
logout = async (): Promise => {
- return this.axios.post("/api/v2/users/logout");
+ return this.axios.post(`${API_PREFIX}/users/logout`);
};
getAuthenticatedUser = async () => {
- const response = await this.axios.get("/api/v2/users/me");
+ const response = await this.axios.get(
+ `${API_PREFIX}/users/me`,
+ );
return response.data;
};
getUserParameters = async (templateID: string) => {
const response = await this.axios.get(
- `/api/v2/users/me/autofill-parameters?template_id=${templateID}`,
+ `${API_PREFIX}/users/me/autofill-parameters?template_id=${templateID}`,
);
return response.data;
@@ -489,7 +493,7 @@ class ApiMethods {
getAuthMethods = async (): Promise => {
const response = await this.axios.get(
- "/api/v2/users/authmethods",
+ `${API_PREFIX}/users/authmethods`,
);
return response.data;
@@ -497,7 +501,7 @@ class ApiMethods {
getUserLoginType = async (): Promise => {
const response = await this.axios.get(
- "/api/v2/users/me/login-type",
+ `${API_PREFIX}/users/me/login-type`,
);
return response.data;
@@ -507,7 +511,7 @@ class ApiMethods {
params: TypesGen.AuthorizationRequest,
) => {
const response = await this.axios.post(
- "/api/v2/authcheck",
+ `${API_PREFIX}/authcheck`,
params,
);
@@ -516,7 +520,7 @@ class ApiMethods {
getApiKey = async (): Promise => {
const response = await this.axios.post(
- "/api/v2/users/me/keys",
+ `${API_PREFIX}/users/me/keys`,
);
return response.data;
@@ -526,7 +530,7 @@ class ApiMethods {
params: TypesGen.TokensFilter,
): Promise => {
const response = await this.axios.get(
- "/api/v2/users/me/keys/tokens",
+ `${API_PREFIX}/users/me/keys/tokens`,
{ params },
);
@@ -534,14 +538,14 @@ class ApiMethods {
};
deleteToken = async (keyId: string): Promise => {
- await this.axios.delete(`/api/v2/users/me/keys/${keyId}`);
+ await this.axios.delete(`${API_PREFIX}/users/me/keys/${keyId}`);
};
createToken = async (
params: TypesGen.CreateTokenRequest,
): Promise => {
const response = await this.axios.post(
- "/api/v2/users/me/keys/tokens",
+ `${API_PREFIX}/users/me/keys/tokens`,
params,
);
@@ -550,7 +554,7 @@ class ApiMethods {
getTokenConfig = async (): Promise => {
const response = await this.axios.get(
- "/api/v2/users/me/keys/tokens/tokenconfig",
+ `${API_PREFIX}/users/me/keys/tokens/tokenconfig`,
);
return response.data;
@@ -560,7 +564,7 @@ class ApiMethods {
options: TypesGen.UsersRequest,
signal?: AbortSignal,
): Promise => {
- const url = getURLWithSearchParams("/api/v2/users", options);
+ const url = getURLWithSearchParams(`${API_PREFIX}/users`, options);
const response = await this.axios.get(
url.toString(),
{ signal },
@@ -571,7 +575,7 @@ class ApiMethods {
createOrganization = async (params: TypesGen.CreateOrganizationRequest) => {
const response = await this.axios.post(
- "/api/v2/organizations",
+ `${API_PREFIX}/organizations`,
params,
);
return response.data;
@@ -585,7 +589,7 @@ class ApiMethods {
params: TypesGen.UpdateOrganizationRequest,
) => {
const response = await this.axios.patch(
- `/api/v2/organizations/${organization}`,
+ `${API_PREFIX}/organizations/${organization}`,
params,
);
return response.data;
@@ -596,7 +600,7 @@ class ApiMethods {
*/
deleteOrganization = async (organization: string) => {
await this.axios.delete(
- `/api/v2/organizations/${organization}`,
+ `${API_PREFIX}/organizations/${organization}`,
);
};
@@ -607,7 +611,7 @@ class ApiMethods {
organization: string,
): Promise => {
const response = await this.axios.get(
- `/api/v2/organizations/${organization}`,
+ `${API_PREFIX}/organizations/${organization}`,
);
return response.data;
@@ -619,7 +623,7 @@ class ApiMethods {
getOrganizationMembers = async (organization: string) => {
const response = await this.axios.get<
TypesGen.OrganizationMemberWithUserData[]
- >(`/api/v2/organizations/${organization}/members`);
+ >(`${API_PREFIX}/organizations/${organization}/members`);
return response.data;
};
@@ -633,7 +637,7 @@ class ApiMethods {
options?: TypesGen.Pagination,
) => {
const url = getURLWithSearchParams(
- `/api/v2/organizations/${organization}/paginated-members`,
+ `${API_PREFIX}/organizations/${organization}/paginated-members`,
options,
);
const response =
@@ -647,7 +651,7 @@ class ApiMethods {
*/
getOrganizationRoles = async (organization: string) => {
const response = await this.axios.get(
- `/api/v2/organizations/${organization}/members/roles`,
+ `${API_PREFIX}/organizations/${organization}/members/roles`,
);
return response.data;
@@ -662,7 +666,7 @@ class ApiMethods {
roles: TypesGen.SlimRole["name"][],
): Promise => {
const response = await this.axios.put(
- `/api/v2/organizations/${organization}/members/${userId}/roles`,
+ `${API_PREFIX}/organizations/${organization}/members/${userId}/roles`,
{ roles },
);
@@ -677,7 +681,7 @@ class ApiMethods {
role: TypesGen.Role,
): Promise => {
const response = await this.axios.post(
- `/api/v2/organizations/${organization}/members/roles`,
+ `${API_PREFIX}/organizations/${organization}/members/roles`,
role,
);
@@ -692,7 +696,7 @@ class ApiMethods {
role: TypesGen.Role,
): Promise => {
const response = await this.axios.put(
- `/api/v2/organizations/${organization}/members/roles`,
+ `${API_PREFIX}/organizations/${organization}/members/roles`,
role,
);
@@ -704,7 +708,7 @@ class ApiMethods {
*/
deleteOrganizationRole = async (organization: string, roleName: string) => {
await this.axios.delete(
- `/api/v2/organizations/${organization}/members/roles/${roleName}`,
+ `${API_PREFIX}/organizations/${organization}/members/roles/${roleName}`,
);
};
@@ -713,7 +717,7 @@ class ApiMethods {
*/
addOrganizationMember = async (organization: string, userId: string) => {
const response = await this.axios.post(
- `/api/v2/organizations/${organization}/members/${userId}`,
+ `${API_PREFIX}/organizations/${organization}/members/${userId}`,
);
return response.data;
@@ -724,20 +728,20 @@ class ApiMethods {
*/
removeOrganizationMember = async (organization: string, userId: string) => {
await this.axios.delete(
- `/api/v2/organizations/${organization}/members/${userId}`,
+ `${API_PREFIX}/organizations/${organization}/members/${userId}`,
);
};
getOrganizations = async (): Promise => {
const response = await this.axios.get(
- "/api/v2/organizations",
+ `${API_PREFIX}/organizations`,
);
return response.data;
};
getMyOrganizations = async (): Promise => {
const response = await this.axios.get(
- "/api/v2/users/me/organizations",
+ `${API_PREFIX}/users/me/organizations`,
);
return response.data;
};
@@ -747,7 +751,7 @@ class ApiMethods {
params?: GetProvisionerDaemonsParams,
): Promise => {
const response = await this.axios.get(
- `/api/v2/organizations/${organization}/provisionerdaemons`,
+ `${API_PREFIX}/organizations/${organization}/provisionerdaemons`,
{ params },
);
return response.data;
@@ -760,7 +764,7 @@ class ApiMethods {
organization: string,
): Promise => {
const response = await this.axios.get(
- `/api/v2/organizations/${organization}/provisionerkeys/daemons`,
+ `${API_PREFIX}/organizations/${organization}/provisionerkeys/daemons`,
);
return response.data;
};
@@ -768,7 +772,7 @@ class ApiMethods {
getOrganizationIdpSyncSettings =
async (): Promise => {
const response = await this.axios.get(
- "/api/v2/settings/idpsync/organization",
+ `${API_PREFIX}/settings/idpsync/organization`,
);
return response.data;
};
@@ -777,7 +781,7 @@ class ApiMethods {
data: TypesGen.OrganizationSyncSettings,
) => {
const response = await this.axios.patch(
- "/api/v2/settings/idpsync/organization",
+ `${API_PREFIX}/settings/idpsync/organization`,
data,
);
return response.data;
@@ -792,7 +796,7 @@ class ApiMethods {
organization: string,
) => {
const response = await this.axios.patch(
- `/api/v2/organizations/${organization}/settings/idpsync/groups`,
+ `${API_PREFIX}/organizations/${organization}/settings/idpsync/groups`,
data,
);
return response.data;
@@ -807,7 +811,7 @@ class ApiMethods {
organization: string,
) => {
const response = await this.axios.patch(
- `/api/v2/organizations/${organization}/settings/idpsync/roles`,
+ `${API_PREFIX}/organizations/${organization}/settings/idpsync/roles`,
data,
);
return response.data;
@@ -820,7 +824,7 @@ class ApiMethods {
organization: string,
): Promise => {
const response = await this.axios.get(
- `/api/v2/organizations/${organization}/settings/idpsync/groups`,
+ `${API_PREFIX}/organizations/${organization}/settings/idpsync/groups`,
);
return response.data;
};
@@ -832,7 +836,7 @@ class ApiMethods {
organization: string,
): Promise => {
const response = await this.axios.get(
- `/api/v2/organizations/${organization}/settings/idpsync/roles`,
+ `${API_PREFIX}/organizations/${organization}/settings/idpsync/roles`,
);
return response.data;
};
@@ -843,7 +847,7 @@ class ApiMethods {
const params = new URLSearchParams();
params.set("claimField", field);
const response = await this.axios.get(
- `/api/v2/settings/idpsync/field-values?${params}`,
+ `${API_PREFIX}/settings/idpsync/field-values?${params}`,
);
return response.data;
};
@@ -855,14 +859,14 @@ class ApiMethods {
const params = new URLSearchParams();
params.set("claimField", field);
const response = await this.axios.get(
- `/api/v2/organizations/${organization}/settings/idpsync/field-values?${params}`,
+ `${API_PREFIX}/organizations/${organization}/settings/idpsync/field-values?${params}`,
);
return response.data;
};
getTemplate = async (templateId: string): Promise => {
const response = await this.axios.get(
- `/api/v2/templates/${templateId}`,
+ `${API_PREFIX}/templates/${templateId}`,
);
return response.data;
@@ -873,7 +877,7 @@ class ApiMethods {
): Promise => {
const params = normalizeGetTemplatesOptions(options);
const response = await this.axios.get(
- "/api/v2/templates",
+ `${API_PREFIX}/templates`,
{ params },
);
@@ -889,7 +893,7 @@ class ApiMethods {
): Promise => {
const params = normalizeGetTemplatesOptions(options);
const response = await this.axios.get(
- `/api/v2/organizations/${organization}/templates`,
+ `${API_PREFIX}/organizations/${organization}/templates`,
{ params },
);
@@ -904,7 +908,7 @@ class ApiMethods {
name: string,
): Promise => {
const response = await this.axios.get(
- `/api/v2/organizations/${organization}/templates/${name}`,
+ `${API_PREFIX}/organizations/${organization}/templates/${name}`,
);
return response.data;
@@ -914,7 +918,7 @@ class ApiMethods {
versionId: string,
): Promise => {
const response = await this.axios.get(
- `/api/v2/templateversions/${versionId}`,
+ `${API_PREFIX}/templateversions/${versionId}`,
);
return response.data;
@@ -924,7 +928,7 @@ class ApiMethods {
versionId: string,
): Promise => {
const response = await this.axios.get(
- `/api/v2/templateversions/${versionId}/resources`,
+ `${API_PREFIX}/templateversions/${versionId}/resources`,
);
return response.data;
@@ -938,7 +942,7 @@ class ApiMethods {
type VerArray = TypesGen.TemplateVersionVariable[];
const response = await this.axios.get(
- `/api/v2/templateversions/${versionId}/variables`,
+ `${API_PREFIX}/templateversions/${versionId}/variables`,
);
return response.data;
@@ -948,7 +952,7 @@ class ApiMethods {
templateId: string,
): Promise => {
const response = await this.axios.get(
- `/api/v2/templates/${templateId}/versions`,
+ `${API_PREFIX}/templates/${templateId}/versions`,
);
return response.data;
};
@@ -962,7 +966,7 @@ class ApiMethods {
versionName: string,
): Promise => {
const response = await this.axios.get(
- `/api/v2/organizations/${organization}/templates/${templateName}/versions/${versionName}`,
+ `${API_PREFIX}/organizations/${organization}/templates/${templateName}/versions/${versionName}`,
);
return response.data;
@@ -978,7 +982,7 @@ class ApiMethods {
) => {
try {
const response = await this.axios.get(
- `/api/v2/organizations/${organization}/templates/${templateName}/versions/${versionName}/previous`,
+ `${API_PREFIX}/organizations/${organization}/templates/${templateName}/versions/${versionName}/previous`,
);
return response.data;
@@ -1005,7 +1009,7 @@ class ApiMethods {
data: TypesGen.CreateTemplateVersionRequest,
): Promise => {
const response = await this.axios.post(
- `/api/v2/organizations/${organization}/templateversions`,
+ `${API_PREFIX}/organizations/${organization}/templateversions`,
data,
);
@@ -1016,7 +1020,7 @@ class ApiMethods {
versionId: string,
): Promise => {
const response = await this.axios.get(
- `/api/v2/templateversions/${versionId}/external-auth`,
+ `${API_PREFIX}/templateversions/${versionId}/external-auth`,
);
return response.data;
@@ -1027,7 +1031,7 @@ class ApiMethods {
data: TypesGen.DynamicParametersRequest,
): Promise => {
const response = await this.axios.post(
- `/api/v2/templateversions/${versionId}/dynamic-parameters/evaluate`,
+ `${API_PREFIX}/templateversions/${versionId}/dynamic-parameters/evaluate`,
data,
);
return response.data;
@@ -1037,7 +1041,7 @@ class ApiMethods {
versionId: string,
): Promise => {
const response = await this.axios.get(
- `/api/v2/templateversions/${versionId}/rich-parameters`,
+ `${API_PREFIX}/templateversions/${versionId}/rich-parameters`,
);
return response.data;
};
@@ -1056,7 +1060,7 @@ class ApiMethods {
},
): WebSocket => {
const socket = createWebSocket(
- `/api/v2/templateversions/${versionId}/dynamic-parameters`,
+ `${API_PREFIX}/templateversions/${versionId}/dynamic-parameters`,
new URLSearchParams({ user_id: userId }),
);
@@ -1084,7 +1088,7 @@ class ApiMethods {
data: TypesGen.CreateTemplateRequest,
): Promise => {
const response = await this.axios.post(
- `/api/v2/organizations/${organization}/templates`,
+ `${API_PREFIX}/organizations/${organization}/templates`,
data,
);
@@ -1096,7 +1100,7 @@ class ApiMethods {
data: TypesGen.UpdateActiveTemplateVersion,
) => {
const response = await this.axios.patch(
- `/api/v2/templates/${templateId}/versions`,
+ `${API_PREFIX}/templates/${templateId}/versions`,
data,
);
return response.data;
@@ -1107,7 +1111,7 @@ class ApiMethods {
data: TypesGen.PatchTemplateVersionRequest,
) => {
const response = await this.axios.patch(
- `/api/v2/templateversions/${templateVersionId}`,
+ `${API_PREFIX}/templateversions/${templateVersionId}`,
data,
);
@@ -1116,7 +1120,7 @@ class ApiMethods {
archiveTemplateVersion = async (templateVersionId: string) => {
const response = await this.axios.post(
- `/api/v2/templateversions/${templateVersionId}/archive`,
+ `${API_PREFIX}/templateversions/${templateVersionId}/archive`,
);
return response.data;
@@ -1124,7 +1128,7 @@ class ApiMethods {
unarchiveTemplateVersion = async (templateVersionId: string) => {
const response = await this.axios.post(
- `/api/v2/templateversions/${templateVersionId}/unarchive`,
+ `${API_PREFIX}/templateversions/${templateVersionId}/unarchive`,
);
return response.data;
};
@@ -1145,7 +1149,7 @@ class ApiMethods {
}
const response = await this.axios.get(
- `/api/v2/files/${fileId}?${params.toString()}`,
+ `${API_PREFIX}/files/${fileId}?${params.toString()}`,
{
responseType: "blob",
},
@@ -1159,7 +1163,7 @@ class ApiMethods {
data: TypesGen.UpdateTemplateMeta,
): Promise => {
const response = await this.axios.patch(
- `/api/v2/templates/${templateId}`,
+ `${API_PREFIX}/templates/${templateId}`,
data,
);
@@ -1173,7 +1177,7 @@ class ApiMethods {
deleteTemplate = async (templateId: string): Promise => {
const response = await this.axios.delete(
- `/api/v2/templates/${templateId}`,
+ `${API_PREFIX}/templates/${templateId}`,
);
return response.data;
@@ -1183,7 +1187,7 @@ class ApiMethods {
templateId: string,
): Promise => {
const response = await this.axios.post(
- `/api/v2/templates/${templateId}/prebuilds/invalidate`,
+ `${API_PREFIX}/templates/${templateId}/prebuilds/invalidate`,
);
return response.data;
};
@@ -1193,7 +1197,7 @@ class ApiMethods {
params?: TypesGen.WorkspaceOptions,
): Promise => {
const response = await this.axios.get(
- `/api/v2/workspaces/${workspaceId}`,
+ `${API_PREFIX}/workspaces/${workspaceId}`,
{ params },
);
@@ -1203,7 +1207,7 @@ class ApiMethods {
getWorkspaces = async (
req: TypesGen.WorkspacesRequest,
): Promise => {
- const url = getURLWithSearchParams("/api/v2/workspaces", req);
+ const url = getURLWithSearchParams(`${API_PREFIX}/workspaces`, req);
const response = await this.axios.get(url);
return response.data;
};
@@ -1214,7 +1218,7 @@ class ApiMethods {
params?: TypesGen.WorkspaceOptions,
): Promise => {
const response = await this.axios.get(
- `/api/v2/users/${username}/workspace/${workspaceName}`,
+ `${API_PREFIX}/users/${username}/workspace/${workspaceName}`,
{ params },
);
@@ -1227,7 +1231,7 @@ class ApiMethods {
buildNumber: number,
): Promise => {
const response = await this.axios.get(
- `/api/v2/users/${username}/workspace/${workspaceName}/builds/${buildNumber}`,
+ `${API_PREFIX}/users/${username}/workspace/${workspaceName}/builds/${buildNumber}`,
);
return response.data;
@@ -1267,7 +1271,7 @@ class ApiMethods {
data: TypesGen.CreateWorkspaceBuildRequest,
): Promise => {
const response = await this.axios.post(
- `/api/v2/workspaces/${workspaceId}/builds`,
+ `${API_PREFIX}/workspaces/${workspaceId}/builds`,
data,
);
return response.data;
@@ -1277,7 +1281,7 @@ class ApiMethods {
templateVersionId: string,
): Promise => {
const response = await this.axios.get(
- `/api/v2/templateversions/${templateVersionId}/presets`,
+ `${API_PREFIX}/templateversions/${templateVersionId}/presets`,
);
return response.data;
};
@@ -1319,7 +1323,7 @@ class ApiMethods {
params?: TypesGen.CancelWorkspaceBuildParams,
): Promise => {
const response = await this.axios.patch(
- `/api/v2/workspacebuilds/${workspaceBuildId}/cancel`,
+ `${API_PREFIX}/workspacebuilds/${workspaceBuildId}/cancel`,
null,
{ params },
);
@@ -1333,7 +1337,7 @@ class ApiMethods {
): Promise => {
const data: TypesGen.UpdateWorkspaceDormancy = { dormant };
const response = await this.axios.put(
- `/api/v2/workspaces/${workspaceId}/dormant`,
+ `${API_PREFIX}/workspaces/${workspaceId}/dormant`,
data,
);
@@ -1349,7 +1353,7 @@ class ApiMethods {
};
const response = await this.axios.put(
- `/api/v2/workspaces/${workspaceId}/autoupdates`,
+ `${API_PREFIX}/workspaces/${workspaceId}/autoupdates`,
req,
);
@@ -1382,7 +1386,7 @@ class ApiMethods {
templateVersionId: string,
): Promise => {
const response = await this.axios.patch(
- `/api/v2/templateversions/${templateVersionId}/cancel`,
+ `${API_PREFIX}/templateversions/${templateVersionId}/cancel`,
);
return response.data;
@@ -1393,7 +1397,7 @@ class ApiMethods {
jobId: string,
): Promise => {
const response = await this.axios.patch(
- `/api/v2/templateversions/${templateVersionId}/dry-run/${jobId}/cancel`,
+ `${API_PREFIX}/templateversions/${templateVersionId}/dry-run/${jobId}/cancel`,
);
return response.data;
@@ -1403,7 +1407,7 @@ class ApiMethods {
user: TypesGen.CreateUserRequestWithOrgs,
): Promise => {
const response = await this.axios.post(
- "/api/v2/users",
+ `${API_PREFIX}/users`,
user,
);
@@ -1415,7 +1419,7 @@ class ApiMethods {
workspace: TypesGen.CreateWorkspaceRequest,
): Promise => {
const response = await this.axios.post(
- `/api/v2/users/${userId}/workspaces`,
+ `${API_PREFIX}/users/${userId}/workspaces`,
workspace,
);
@@ -1426,16 +1430,16 @@ class ApiMethods {
workspaceId: string,
data: TypesGen.UpdateWorkspaceRequest,
): Promise => {
- await this.axios.patch(`/api/v2/workspaces/${workspaceId}`, data);
+ await this.axios.patch(`${API_PREFIX}/workspaces/${workspaceId}`, data);
};
getBuildInfo = async (): Promise => {
- const response = await this.axios.get("/api/v2/buildinfo");
+ const response = await this.axios.get(`${API_PREFIX}/buildinfo`);
return response.data;
};
getUpdateCheck = async (): Promise => {
- const response = await this.axios.get("/api/v2/updatecheck");
+ const response = await this.axios.get(`${API_PREFIX}/updatecheck`);
return response.data;
};
@@ -1445,7 +1449,7 @@ class ApiMethods {
): Promise => {
const payload = JSON.stringify(autostart);
await this.axios.put(
- `/api/v2/workspaces/${workspaceID}/autostart`,
+ `${API_PREFIX}/workspaces/${workspaceID}/autostart`,
payload,
{ headers: { ...BASE_CONTENT_TYPE_JSON } },
);
@@ -1456,9 +1460,13 @@ class ApiMethods {
ttl: TypesGen.UpdateWorkspaceTTLRequest,
): Promise => {
const payload = JSON.stringify(ttl);
- await this.axios.put(`/api/v2/workspaces/${workspaceID}/ttl`, payload, {
- headers: { ...BASE_CONTENT_TYPE_JSON },
- });
+ await this.axios.put(
+ `${API_PREFIX}/workspaces/${workspaceID}/ttl`,
+ payload,
+ {
+ headers: { ...BASE_CONTENT_TYPE_JSON },
+ },
+ );
};
updateProfile = async (
@@ -1466,7 +1474,7 @@ class ApiMethods {
data: TypesGen.UpdateUserProfileRequest,
): Promise => {
const response = await this.axios.put(
- `/api/v2/users/${userId}/profile`,
+ `${API_PREFIX}/users/${userId}/profile`,
data,
);
return response.data;
@@ -1474,27 +1482,37 @@ class ApiMethods {
getAppearanceSettings =
async (): Promise => {
- const response = await this.axios.get("/api/v2/users/me/appearance");
+ const response = await this.axios.get(
+ `${API_PREFIX}/users/me/appearance`,
+ );
return response.data;
};
updateAppearanceSettings = async (
data: TypesGen.UpdateUserAppearanceSettingsRequest,
): Promise => {
- const response = await this.axios.put("/api/v2/users/me/appearance", data);
+ const response = await this.axios.put(
+ `${API_PREFIX}/users/me/appearance`,
+ data,
+ );
return response.data;
};
getUserPreferenceSettings =
async (): Promise => {
- const response = await this.axios.get("/api/v2/users/me/preferences");
+ const response = await this.axios.get(
+ `${API_PREFIX}/users/me/preferences`,
+ );
return response.data;
};
updateUserPreferenceSettings = async (
req: TypesGen.UpdateUserPreferenceSettingsRequest,
): Promise => {
- const response = await this.axios.put("/api/v2/users/me/preferences", req);
+ const response = await this.axios.put(
+ `${API_PREFIX}/users/me/preferences`,
+ req,
+ );
return response.data;
};
@@ -1502,7 +1520,7 @@ class ApiMethods {
userId: TypesGen.User["id"],
): Promise => {
const response = await this.axios.get(
- `/api/v2/users/${userId}/quiet-hours`,
+ `${API_PREFIX}/users/${userId}/quiet-hours`,
);
return response.data;
};
@@ -1512,7 +1530,7 @@ class ApiMethods {
data: TypesGen.UpdateUserQuietHoursScheduleRequest,
): Promise => {
const response = await this.axios.put(
- `/api/v2/users/${userId}/quiet-hours`,
+ `${API_PREFIX}/users/${userId}/quiet-hours`,
data,
);
@@ -1523,21 +1541,21 @@ class ApiMethods {
userId: TypesGen.User["id"],
): Promise => {
const response = await this.axios.put(
- `/api/v2/users/${userId}/status/activate`,
+ `${API_PREFIX}/users/${userId}/status/activate`,
);
return response.data;
};
suspendUser = async (userId: TypesGen.User["id"]): Promise => {
const response = await this.axios.put(
- `/api/v2/users/${userId}/status/suspend`,
+ `${API_PREFIX}/users/${userId}/status/suspend`,
);
return response.data;
};
deleteUser = async (userId: TypesGen.User["id"]): Promise => {
- await this.axios.delete(`/api/v2/users/${userId}`);
+ await this.axios.delete(`${API_PREFIX}/users/${userId}`);
};
// API definition:
@@ -1545,7 +1563,7 @@ class ApiMethods {
hasFirstUser = async (): Promise => {
try {
// If it is success, it is true
- await this.axios.get("/api/v2/users/first");
+ await this.axios.get(`${API_PREFIX}/users/first`);
return true;
} catch (error) {
// If it returns a 404, it is false
@@ -1560,7 +1578,7 @@ class ApiMethods {
createFirstUser = async (
req: TypesGen.CreateFirstUserRequest,
): Promise => {
- const response = await this.axios.post("/api/v2/users/first", req);
+ const response = await this.axios.post(`${API_PREFIX}/users/first`, req);
return response.data;
};
@@ -1568,21 +1586,27 @@ class ApiMethods {
userId: TypesGen.User["id"],
updatePassword: TypesGen.UpdateUserPasswordRequest,
): Promise => {
- await this.axios.put(`/api/v2/users/${userId}/password`, updatePassword);
+ await this.axios.put(
+ `${API_PREFIX}/users/${userId}/password`,
+ updatePassword,
+ );
};
validateUserPassword = async (
password: string,
): Promise => {
- const response = await this.axios.post("/api/v2/users/validate-password", {
- password,
- });
+ const response = await this.axios.post(
+ `${API_PREFIX}/users/validate-password`,
+ {
+ password,
+ },
+ );
return response.data;
};
getRoles = async (): Promise> => {
const response = await this.axios.get(
- "/api/v2/users/roles",
+ `${API_PREFIX}/users/roles`,
);
return response.data;
@@ -1593,7 +1617,7 @@ class ApiMethods {
userId: TypesGen.User["id"],
): Promise => {
const response = await this.axios.put(
- `/api/v2/users/${userId}/roles`,
+ `${API_PREFIX}/users/${userId}/roles`,
{ roles },
);
@@ -1602,7 +1626,7 @@ class ApiMethods {
getUserSSHKey = async (userId = "me"): Promise => {
const response = await this.axios.get(
- `/api/v2/users/${userId}/gitsshkey`,
+ `${API_PREFIX}/users/${userId}/gitsshkey`,
);
return response.data;
@@ -1610,7 +1634,7 @@ class ApiMethods {
regenerateUserSSHKey = async (userId = "me"): Promise => {
const response = await this.axios.put(
- `/api/v2/users/${userId}/gitsshkey`,
+ `${API_PREFIX}/users/${userId}/gitsshkey`,
);
return response.data;
@@ -1621,7 +1645,10 @@ class ApiMethods {
req?: TypesGen.WorkspaceBuildsRequest,
) => {
const response = await this.axios.get(
- getURLWithSearchParams(`/api/v2/workspaces/${workspaceId}/builds`, req),
+ getURLWithSearchParams(
+ `${API_PREFIX}/workspaces/${workspaceId}/builds`,
+ req,
+ ),
);
return response.data;
@@ -1631,7 +1658,7 @@ class ApiMethods {
buildId: string,
): Promise => {
const response = await this.axios.get(
- `/api/v2/workspacebuilds/${buildId}/logs`,
+ `${API_PREFIX}/workspacebuilds/${buildId}/logs`,
);
return response.data;
@@ -1641,7 +1668,7 @@ class ApiMethods {
agentID: string,
): Promise => {
const response = await this.axios.get(
- `/api/v2/workspaceagents/${agentID}/logs`,
+ `${API_PREFIX}/workspaceagents/${agentID}/logs`,
);
return response.data;
@@ -1651,19 +1678,19 @@ class ApiMethods {
workspaceId: string,
newDeadline: dayjs.Dayjs,
): Promise => {
- await this.axios.put(`/api/v2/workspaces/${workspaceId}/extend`, {
+ await this.axios.put(`${API_PREFIX}/workspaces/${workspaceId}/extend`, {
deadline: newDeadline,
});
};
refreshEntitlements = async (): Promise => {
- await this.axios.post("/api/v2/licenses/refresh-entitlements");
+ await this.axios.post(`${API_PREFIX}/licenses/refresh-entitlements`);
};
getEntitlements = async (): Promise => {
try {
const response = await this.axios.get(
- "/api/v2/entitlements",
+ `${API_PREFIX}/entitlements`,
);
return response.data;
@@ -1686,7 +1713,7 @@ class ApiMethods {
getExperiments = async (): Promise => {
try {
const response = await this.axios.get(
- "/api/v2/experiments",
+ `${API_PREFIX}/experiments`,
);
return response.data;
@@ -1702,7 +1729,9 @@ class ApiMethods {
getAvailableExperiments =
async (): Promise => {
try {
- const response = await this.axios.get("/api/v2/experiments/available");
+ const response = await this.axios.get(
+ `${API_PREFIX}/experiments/available`,
+ );
return response.data;
} catch (error) {
@@ -1716,7 +1745,7 @@ class ApiMethods {
getExternalAuthProvider = async (
provider: string,
): Promise => {
- const res = await this.axios.get(`/api/v2/external-auth/${provider}`);
+ const res = await this.axios.get(`${API_PREFIX}/external-auth/${provider}`);
return res.data;
};
@@ -1724,7 +1753,7 @@ class ApiMethods {
provider: string,
): Promise => {
const resp = await this.axios.get(
- `/api/v2/external-auth/${provider}/device`,
+ `${API_PREFIX}/external-auth/${provider}/device`,
);
return resp.data;
};
@@ -1734,7 +1763,7 @@ class ApiMethods {
req: TypesGen.ExternalAuthDeviceExchange,
): Promise => {
const resp = await this.axios.post(
- `/api/v2/external-auth/${provider}/device`,
+ `${API_PREFIX}/external-auth/${provider}/device`,
req,
);
@@ -1743,14 +1772,16 @@ class ApiMethods {
getUserExternalAuthProviders =
async (): Promise => {
- const resp = await this.axios.get("/api/v2/external-auth");
+ const resp = await this.axios.get(`${API_PREFIX}/external-auth`);
return resp.data;
};
unlinkExternalAuthProvider = async (
provider: string,
): Promise => {
- const resp = await this.axios.delete(`/api/v2/external-auth/${provider}`);
+ const resp = await this.axios.delete(
+ `${API_PREFIX}/external-auth/${provider}`,
+ );
return resp.data;
};
@@ -1759,7 +1790,7 @@ class ApiMethods {
state: string,
): Promise => {
const resp = await this.axios.get(
- `/api/v2/users/oauth2/github/callback?code=${code}&state=${state}`,
+ `${API_PREFIX}/users/oauth2/github/callback?code=${code}&state=${state}`,
);
// sanity check
if (
@@ -1773,7 +1804,9 @@ class ApiMethods {
};
getOAuth2GitHubDevice = async (): Promise => {
- const resp = await this.axios.get("/api/v2/users/oauth2/github/device");
+ const resp = await this.axios.get(
+ `${API_PREFIX}/users/oauth2/github/device`,
+ );
return resp.data;
};
@@ -1784,14 +1817,18 @@ class ApiMethods {
? new URLSearchParams({ user_id: filter.user_id }).toString()
: "";
- const resp = await this.axios.get(`/api/v2/oauth2-provider/apps?${params}`);
+ const resp = await this.axios.get(
+ `${API_PREFIX}/oauth2-provider/apps?${params}`,
+ );
return resp.data;
};
getOAuth2ProviderApp = async (
id: string,
): Promise => {
- const resp = await this.axios.get(`/api/v2/oauth2-provider/apps/${id}`);
+ const resp = await this.axios.get(
+ `${API_PREFIX}/oauth2-provider/apps/${id}`,
+ );
return resp.data;
};
@@ -1799,7 +1836,7 @@ class ApiMethods {
data: TypesGen.PostOAuth2ProviderAppRequest,
): Promise => {
const response = await this.axios.post(
- "/api/v2/oauth2-provider/apps",
+ `${API_PREFIX}/oauth2-provider/apps`,
data,
);
return response.data;
@@ -1810,21 +1847,21 @@ class ApiMethods {
data: TypesGen.PutOAuth2ProviderAppRequest,
): Promise => {
const response = await this.axios.put(
- `/api/v2/oauth2-provider/apps/${id}`,
+ `${API_PREFIX}/oauth2-provider/apps/${id}`,
data,
);
return response.data;
};
deleteOAuth2ProviderApp = async (id: string): Promise => {
- await this.axios.delete(`/api/v2/oauth2-provider/apps/${id}`);
+ await this.axios.delete(`${API_PREFIX}/oauth2-provider/apps/${id}`);
};
getOAuth2ProviderAppSecrets = async (
id: string,
): Promise => {
const resp = await this.axios.get(
- `/api/v2/oauth2-provider/apps/${id}/secrets`,
+ `${API_PREFIX}/oauth2-provider/apps/${id}/secrets`,
);
return resp.data;
};
@@ -1833,7 +1870,7 @@ class ApiMethods {
id: string,
): Promise => {
const resp = await this.axios.post(
- `/api/v2/oauth2-provider/apps/${id}/secrets`,
+ `${API_PREFIX}/oauth2-provider/apps/${id}/secrets`,
);
return resp.data;
};
@@ -1843,7 +1880,7 @@ class ApiMethods {
secretId: string,
): Promise => {
await this.axios.delete(
- `/api/v2/oauth2-provider/apps/${appId}/secrets/${secretId}`,
+ `${API_PREFIX}/oauth2-provider/apps/${appId}/secrets/${secretId}`,
);
};
@@ -1854,7 +1891,7 @@ class ApiMethods {
getAuditLogs = async (
options: TypesGen.AuditLogsRequest,
): Promise => {
- const url = getURLWithSearchParams("/api/v2/audit", options);
+ const url = getURLWithSearchParams(`${API_PREFIX}/audit`, options);
const response = await this.axios.get(url);
return response.data;
};
@@ -1862,7 +1899,7 @@ class ApiMethods {
getConnectionLogs = async (
options: TypesGen.ConnectionLogsRequest,
): Promise => {
- const url = getURLWithSearchParams("/api/v2/connectionlog", options);
+ const url = getURLWithSearchParams(`${API_PREFIX}/connectionlog`, options);
const response = await this.axios.get(url);
return response.data;
};
@@ -1871,7 +1908,7 @@ class ApiMethods {
templateId: string,
): Promise => {
const response = await this.axios.get(
- `/api/v2/templates/${templateId}/daus`,
+ `${API_PREFIX}/templates/${templateId}/daus`,
);
return response.data;
@@ -1884,7 +1921,7 @@ class ApiMethods {
offset = Math.trunc(new Date().getTimezoneOffset() / 60),
): Promise => {
const response = await this.axios.get(
- `/api/v2/insights/daus?tz_offset=${offset}`,
+ `${API_PREFIX}/insights/daus?tz_offset=${offset}`,
);
return response.data;
@@ -1895,7 +1932,7 @@ class ApiMethods {
options: TypesGen.UsersRequest,
): Promise => {
const url = getURLWithSearchParams(
- `/api/v2/templates/${templateId}/acl/available`,
+ `${API_PREFIX}/templates/${templateId}/acl/available`,
options,
).toString();
@@ -1907,7 +1944,7 @@ class ApiMethods {
templateId: string,
): Promise => {
const response = await this.axios.get(
- `/api/v2/templates/${templateId}/acl`,
+ `${API_PREFIX}/templates/${templateId}/acl`,
);
return response.data;
@@ -1918,7 +1955,7 @@ class ApiMethods {
data: TypesGen.UpdateTemplateACL,
): Promise<{ message: string }> => {
const response = await this.axios.patch(
- `/api/v2/templates/${templateId}/acl`,
+ `${API_PREFIX}/templates/${templateId}/acl`,
data,
);
@@ -1929,7 +1966,7 @@ class ApiMethods {
workspaceId: string,
): Promise => {
const response = await this.axios.get(
- `/api/v2/workspaces/${workspaceId}/acl`,
+ `${API_PREFIX}/workspaces/${workspaceId}/acl`,
);
return response.data;
@@ -1939,11 +1976,11 @@ class ApiMethods {
workspaceId: string,
data: TypesGen.UpdateWorkspaceACL,
): Promise => {
- await this.axios.patch(`/api/v2/workspaces/${workspaceId}/acl`, data);
+ await this.axios.patch(`${API_PREFIX}/workspaces/${workspaceId}/acl`, data);
};
getApplicationsHost = async (): Promise => {
- const response = await this.axios.get("/api/v2/applications/host");
+ const response = await this.axios.get(`${API_PREFIX}/applications/host`);
return response.data;
};
@@ -1955,7 +1992,7 @@ class ApiMethods {
params.has_member = options.userId;
}
- const response = await this.axios.get("/api/v2/groups", { params });
+ const response = await this.axios.get(`${API_PREFIX}/groups`, { params });
return response.data;
};
@@ -1966,7 +2003,7 @@ class ApiMethods {
organization: string,
): Promise => {
const response = await this.axios.get(
- `/api/v2/organizations/${organization}/groups`,
+ `${API_PREFIX}/organizations/${organization}/groups`,
);
return response.data;
};
@@ -1979,7 +2016,7 @@ class ApiMethods {
data: TypesGen.CreateGroupRequest,
): Promise => {
const response = await this.axios.post(
- `/api/v2/organizations/${organization}/groups`,
+ `${API_PREFIX}/organizations/${organization}/groups`,
data,
);
return response.data;
@@ -1993,7 +2030,7 @@ class ApiMethods {
groupName: string,
): Promise => {
const response = await this.axios.get(
- `/api/v2/organizations/${organization}/groups/${groupName}`,
+ `${API_PREFIX}/organizations/${organization}/groups/${groupName}`,
);
return response.data;
};
@@ -2002,7 +2039,10 @@ class ApiMethods {
groupId: string,
data: TypesGen.PatchGroupRequest,
): Promise => {
- const response = await this.axios.patch(`/api/v2/groups/${groupId}`, data);
+ const response = await this.axios.patch(
+ `${API_PREFIX}/groups/${groupId}`,
+ data,
+ );
return response.data;
};
@@ -2029,7 +2069,7 @@ class ApiMethods {
};
deleteGroup = async (groupId: string): Promise => {
- await this.axios.delete(`/api/v2/groups/${groupId}`);
+ await this.axios.delete(`${API_PREFIX}/groups/${groupId}`);
};
getWorkspaceQuota = async (
@@ -2037,7 +2077,7 @@ class ApiMethods {
username: string,
): Promise => {
const response = await this.axios.get(
- `/api/v2/organizations/${encodeURIComponent(organizationName)}/members/${encodeURIComponent(username)}/workspace-quota`,
+ `${API_PREFIX}/organizations/${encodeURIComponent(organizationName)}/members/${encodeURIComponent(username)}/workspace-quota`,
);
return response.data;
@@ -2047,7 +2087,7 @@ class ApiMethods {
agentID: string,
): Promise => {
const response = await this.axios.get(
- `/api/v2/workspaceagents/${agentID}/listening-ports`,
+ `${API_PREFIX}/workspaceagents/${agentID}/listening-ports`,
);
return response.data;
};
@@ -2056,7 +2096,7 @@ class ApiMethods {
workspaceID: string,
): Promise => {
const response = await this.axios.get(
- `/api/v2/workspaces/${workspaceID}/port-share`,
+ `${API_PREFIX}/workspaces/${workspaceID}/port-share`,
);
return response.data;
};
@@ -2066,7 +2106,7 @@ class ApiMethods {
agentName: string,
): Promise => {
const response = await this.axios.get(
- `/api/v2/workspaces/${workspaceID}/external-agent/${agentName}/credentials`,
+ `${API_PREFIX}/workspaces/${workspaceID}/external-agent/${agentName}/credentials`,
);
return response.data;
};
@@ -2076,7 +2116,7 @@ class ApiMethods {
req: TypesGen.UpsertWorkspaceAgentPortShareRequest,
): Promise => {
const response = await this.axios.post(
- `/api/v2/workspaces/${workspaceID}/port-share`,
+ `${API_PREFIX}/workspaces/${workspaceID}/port-share`,
req,
);
return response.data;
@@ -2087,7 +2127,7 @@ class ApiMethods {
req: TypesGen.DeleteWorkspaceAgentPortShareRequest,
): Promise => {
const response = await this.axios.delete(
- `/api/v2/workspaces/${workspaceID}/port-share`,
+ `${API_PREFIX}/workspaces/${workspaceID}/port-share`,
{ data: req },
);
@@ -2096,28 +2136,28 @@ class ApiMethods {
// getDeploymentSSHConfig is used by the VSCode-Extension.
getDeploymentSSHConfig = async (): Promise => {
- const response = await this.axios.get("/api/v2/deployment/ssh");
+ const response = await this.axios.get(`${API_PREFIX}/deployment/ssh`);
return response.data;
};
getDeploymentConfig = async (): Promise => {
- const response = await this.axios.get("/api/v2/deployment/config");
+ const response = await this.axios.get(`${API_PREFIX}/deployment/config`);
return response.data;
};
getDeploymentStats = async (): Promise => {
- const response = await this.axios.get("/api/v2/deployment/stats");
+ const response = await this.axios.get(`${API_PREFIX}/deployment/stats`);
return response.data;
};
getReplicas = async (): Promise => {
- const response = await this.axios.get("/api/v2/replicas");
+ const response = await this.axios.get(`${API_PREFIX}/replicas`);
return response.data;
};
getFile = async (fileId: string): Promise => {
const response = await this.axios.get(
- `/api/v2/files/${fileId}`,
+ `${API_PREFIX}/files/${fileId}`,
{ responseType: "arraybuffer" },
);
@@ -2127,10 +2167,9 @@ class ApiMethods {
getWorkspaceProxyRegions = async (): Promise<
TypesGen.RegionsResponse
> => {
- const response =
- await this.axios.get>(
- "/api/v2/regions",
- );
+ const response = await this.axios.get<
+ TypesGen.RegionsResponse
+ >(`${API_PREFIX}/regions`);
return response.data;
};
@@ -2140,7 +2179,7 @@ class ApiMethods {
> => {
const response = await this.axios.get<
TypesGen.RegionsResponse
- >("/api/v2/workspaceproxies");
+ >(`${API_PREFIX}/workspaceproxies`);
return response.data;
};
@@ -2148,13 +2187,13 @@ class ApiMethods {
createWorkspaceProxy = async (
b: TypesGen.CreateWorkspaceProxyRequest,
): Promise => {
- const response = await this.axios.post("/api/v2/workspaceproxies", b);
+ const response = await this.axios.post(`${API_PREFIX}/workspaceproxies`, b);
return response.data;
};
getAppearance = async (): Promise => {
try {
- const response = await this.axios.get("/api/v2/appearance");
+ const response = await this.axios.get(`${API_PREFIX}/appearance`);
return response.data || {};
} catch (ex) {
if (isAxiosError(ex) && ex.response?.status === 404) {
@@ -2176,7 +2215,7 @@ class ApiMethods {
updateAppearance = async (
b: TypesGen.AppearanceConfig,
): Promise => {
- const response = await this.axios.put("/api/v2/appearance", b);
+ const response = await this.axios.put(`${API_PREFIX}/appearance`, b);
return response.data;
};
@@ -2184,13 +2223,13 @@ class ApiMethods {
* @param organization Can be the organization's ID or name
*/
getTemplateExamples = async (): Promise => {
- const response = await this.axios.get("/api/v2/templates/examples");
+ const response = await this.axios.get(`${API_PREFIX}/templates/examples`);
return response.data;
};
uploadFile = async (file: File): Promise => {
- const response = await this.axios.post("/api/v2/files", file, {
+ const response = await this.axios.post(`${API_PREFIX}/files`, file, {
headers: { "Content-Type": file.type },
});
@@ -2201,7 +2240,7 @@ class ApiMethods {
versionId: string,
): Promise => {
const response = await this.axios.get(
- `/api/v2/templateversions/${versionId}/logs`,
+ `${API_PREFIX}/templateversions/${versionId}/logs`,
);
return response.data;
};
@@ -2217,26 +2256,26 @@ class ApiMethods {
workspaceBuildId: TypesGen.WorkspaceBuild["id"],
): Promise => {
const response = await this.axios.get(
- `/api/v2/workspacebuilds/${workspaceBuildId}/parameters`,
+ `${API_PREFIX}/workspacebuilds/${workspaceBuildId}/parameters`,
);
return response.data;
};
getLicenses = async (): Promise => {
- const response = await this.axios.get("/api/v2/licenses");
+ const response = await this.axios.get(`${API_PREFIX}/licenses`);
return response.data;
};
createLicense = async (
data: TypesGen.AddLicenseRequest,
): Promise => {
- const response = await this.axios.post("/api/v2/licenses", data);
+ const response = await this.axios.post(`${API_PREFIX}/licenses`, data);
return response.data;
};
removeLicense = async (licenseId: number): Promise => {
- await this.axios.delete(`/api/v2/licenses/${licenseId}`);
+ await this.axios.delete(`${API_PREFIX}/licenses/${licenseId}`);
};
getDynamicParameters = async (
@@ -2403,7 +2442,7 @@ class ApiMethods {
workspaceId: string,
): Promise => {
const response = await this.axios.get(
- `/api/v2/workspaces/${workspaceId}/resolve-autostart`,
+ `${API_PREFIX}/workspaces/${workspaceId}/resolve-autostart`,
);
return response.data;
};
@@ -2412,7 +2451,7 @@ class ApiMethods {
params: TypesGen.IssueReconnectingPTYSignedTokenRequest,
): Promise => {
const response = await this.axios.post(
- "/api/v2/applications/reconnecting-pty-signed-token",
+ `${API_PREFIX}/applications/reconnecting-pty-signed-token`,
params,
);
@@ -2424,7 +2463,7 @@ class ApiMethods {
): Promise => {
const params = new URLSearchParams(filters);
const response = await this.axios.get(
- `/api/v2/insights/user-latency?${params}`,
+ `${API_PREFIX}/insights/user-latency?${params}`,
);
return response.data;
@@ -2435,7 +2474,7 @@ class ApiMethods {
): Promise => {
const params = new URLSearchParams(filters);
const response = await this.axios.get(
- `/api/v2/insights/user-activity?${params}`,
+ `${API_PREFIX}/insights/user-activity?${params}`,
);
return response.data;
@@ -2448,7 +2487,7 @@ class ApiMethods {
tz_offset: offset.toString(),
});
const response = await this.axios.get(
- `/api/v2/insights/user-status-counts?${searchParams}`,
+ `${API_PREFIX}/insights/user-status-counts?${searchParams}`,
);
return response.data;
@@ -2459,7 +2498,7 @@ class ApiMethods {
): Promise => {
const searchParams = new URLSearchParams(params);
const response = await this.axios.get(
- `/api/v2/insights/templates?${searchParams}`,
+ `${API_PREFIX}/insights/templates?${searchParams}`,
);
return response.data;
@@ -2468,14 +2507,14 @@ class ApiMethods {
getHealth = async (force = false) => {
const params = new URLSearchParams({ force: force.toString() });
const response = await this.axios.get(
- `/api/v2/debug/health?${params}`,
+ `${API_PREFIX}/debug/health?${params}`,
);
return response.data;
};
getHealthSettings = async (): Promise => {
const res = await this.axios.get(
- "/api/v2/debug/health/settings",
+ `${API_PREFIX}/debug/health/settings`,
);
return res.data;
@@ -2483,7 +2522,7 @@ class ApiMethods {
updateHealthSettings = async (data: TypesGen.UpdateHealthSettings) => {
const response = await this.axios.put(
- "/api/v2/debug/health/settings",
+ `${API_PREFIX}/debug/health/settings`,
data,
);
@@ -2491,11 +2530,11 @@ class ApiMethods {
};
putFavoriteWorkspace = async (workspaceID: string) => {
- await this.axios.put(`/api/v2/workspaces/${workspaceID}/favorite`);
+ await this.axios.put(`${API_PREFIX}/workspaces/${workspaceID}/favorite`);
};
deleteFavoriteWorkspace = async (workspaceID: string) => {
- await this.axios.delete(`/api/v2/workspaces/${workspaceID}/favorite`);
+ await this.axios.delete(`${API_PREFIX}/workspaces/${workspaceID}/favorite`);
};
postWorkspaceUsage = async (
@@ -2503,7 +2542,7 @@ class ApiMethods {
options: PostWorkspaceUsageRequest,
) => {
const response = await this.axios.post(
- `/api/v2/workspaces/${workspaceID}/usage`,
+ `${API_PREFIX}/workspaces/${workspaceID}/usage`,
options,
);
@@ -2512,7 +2551,7 @@ class ApiMethods {
getUserNotificationPreferences = async (userId: string) => {
const res = await this.axios.get(
- `/api/v2/users/${userId}/notifications/preferences`,
+ `${API_PREFIX}/users/${userId}/notifications/preferences`,
);
return res.data ?? [];
};
@@ -2522,7 +2561,7 @@ class ApiMethods {
req: TypesGen.UpdateUserNotificationPreferences,
) => {
const res = await this.axios.put(
- `/api/v2/users/${userId}/notifications/preferences`,
+ `${API_PREFIX}/users/${userId}/notifications/preferences`,
req,
);
return res.data;
@@ -2530,21 +2569,21 @@ class ApiMethods {
getSystemNotificationTemplates = async () => {
const res = await this.axios.get(
- "/api/v2/notifications/templates/system",
+ `${API_PREFIX}/notifications/templates/system`,
);
return res.data;
};
getCustomNotificationTemplates = async () => {
const res = await this.axios.get(
- "/api/v2/notifications/templates/custom",
+ `${API_PREFIX}/notifications/templates/custom`,
);
return res.data;
};
getNotificationDispatchMethods = async () => {
const res = await this.axios.get(
- "/api/v2/notifications/dispatch-methods",
+ `${API_PREFIX}/notifications/dispatch-methods`,
);
return res.data;
};
@@ -2554,14 +2593,14 @@ class ApiMethods {
req: TypesGen.UpdateNotificationTemplateMethod,
) => {
const res = await this.axios.put(
- `/api/v2/notifications/templates/${templateId}/method`,
+ `${API_PREFIX}/notifications/templates/${templateId}/method`,
req,
);
return res.data;
};
postTestNotification = async () => {
- await this.axios.post("/api/v2/notifications/test");
+ await this.axios.post(`${API_PREFIX}/notifications/test`);
};
createWebPushSubscription = async (
@@ -2569,7 +2608,7 @@ class ApiMethods {
req: TypesGen.WebpushSubscription,
) => {
await this.axios.post(
- `/api/v2/users/${userId}/webpush/subscription`,
+ `${API_PREFIX}/users/${userId}/webpush/subscription`,
req,
);
};
@@ -2579,7 +2618,7 @@ class ApiMethods {
req: TypesGen.DeleteWebpushSubscription,
) => {
await this.axios.delete(
- `/api/v2/users/${userId}/webpush/subscription`,
+ `${API_PREFIX}/users/${userId}/webpush/subscription`,
{
data: req,
},
@@ -2589,18 +2628,18 @@ class ApiMethods {
requestOneTimePassword = async (
req: TypesGen.RequestOneTimePasscodeRequest,
) => {
- await this.axios.post("/api/v2/users/otp/request", req);
+ await this.axios.post(`${API_PREFIX}/users/otp/request`, req);
};
changePasswordWithOTP = async (
req: TypesGen.ChangePasswordWithOneTimePasscodeRequest,
) => {
- await this.axios.post("/api/v2/users/otp/change-password", req);
+ await this.axios.post(`${API_PREFIX}/users/otp/change-password`, req);
};
workspaceBuildTimings = async (workspaceBuildId: string) => {
const res = await this.axios.get(
- `/api/v2/workspacebuilds/${workspaceBuildId}/timings`,
+ `${API_PREFIX}/workspacebuilds/${workspaceBuildId}/timings`,
);
return res.data;
};
@@ -2610,7 +2649,7 @@ class ApiMethods {
params: GetProvisionerJobsParams = {},
) => {
const res = await this.axios.get(
- `/api/v2/organizations/${orgId}/provisionerjobs`,
+ `${API_PREFIX}/organizations/${orgId}/provisionerjobs`,
{ params },
);
return res.data;
@@ -2647,7 +2686,7 @@ class ApiMethods {
);
const res =
await this.axios.get(
- `/api/v2/workspaceagents/${agentId}/containers?${params.toString()}`,
+ `${API_PREFIX}/workspaceagents/${agentId}/containers?${params.toString()}`,
);
return res.data;
};
@@ -2658,7 +2697,7 @@ class ApiMethods {
params.append("starting_before", startingBeforeId);
}
const res = await this.axios.get(
- `/api/v2/notifications/inbox?${params.toString()}`,
+ `${API_PREFIX}/notifications/inbox?${params.toString()}`,
);
return res.data;
};
@@ -2669,14 +2708,16 @@ class ApiMethods {
) => {
const res =
await this.axios.put(
- `/api/v2/notifications/inbox/${notificationId}/read-status`,
+ `${API_PREFIX}/notifications/inbox/${notificationId}/read-status`,
req,
);
return res.data;
};
markAllInboxNotificationsAsRead = async () => {
- await this.axios.put("/api/v2/notifications/inbox/mark-all-as-read");
+ await this.axios.put(
+ `${API_PREFIX}/notifications/inbox/mark-all-as-read`,
+ );
};
createTask = async (
@@ -2684,7 +2725,7 @@ class ApiMethods {
req: TypesGen.CreateTaskRequest,
): Promise => {
const response = await this.axios.post(
- `/api/v2/tasks/${user}`,
+ `${API_PREFIX}/tasks/${user}`,
req,
);
@@ -2703,7 +2744,7 @@ class ApiMethods {
}
const res = await this.axios.get(
- "/api/v2/tasks",
+ `${API_PREFIX}/tasks`,
{
params: {
q: query.join(", "),
@@ -2716,14 +2757,14 @@ class ApiMethods {
getTask = async (user: string, id: string): Promise => {
const response = await this.axios.get(
- `/api/v2/tasks/${user}/${id}`,
+ `${API_PREFIX}/tasks/${user}/${id}`,
);
return response.data;
};
deleteTask = async (user: string, id: string): Promise => {
- await this.axios.delete(`/api/v2/tasks/${user}/${id}`);
+ await this.axios.delete(`${API_PREFIX}/tasks/${user}/${id}`);
};
updateTaskInput = async (
@@ -2731,7 +2772,7 @@ class ApiMethods {
id: string,
input: string,
): Promise => {
- await this.axios.patch(`/api/v2/tasks/${user}/${id}/input`, {
+ await this.axios.patch(`${API_PREFIX}/tasks/${user}/${id}/input`, {
input,
} satisfies TypesGen.UpdateTaskInputRequest);
};
@@ -2744,6 +2785,26 @@ class ApiMethods {
setTimeout(() => res(), 500);
});
};
+
+ getAIBridgeInterceptions = async (options: SearchParamOptions) => {
+ const url = getURLWithSearchParams(
+ `${API_PREFIX}/aibridge/interceptions`,
+ options,
+ );
+ const response =
+ await this.axios.get(url);
+ return response.data;
+ };
+
+ getAIBridgeModels = async (options: SearchParamOptions) => {
+ const url = getURLWithSearchParams(
+ `${API_PREFIX}/aibridge/models`,
+ options,
+ );
+
+ const response = await this.axios.get(url);
+ return response.data;
+ };
}
export type TaskFeedbackRating = "good" | "okay" | "bad";
@@ -2760,16 +2821,6 @@ export type CreateTaskFeedbackRequest = {
// above the ApiMethods class for a full explanation.
class ExperimentalApiMethods {
constructor(protected readonly axios: AxiosInstance) {}
-
- getAIBridgeInterceptions = async (options: SearchParamOptions) => {
- const url = getURLWithSearchParams(
- "/api/experimental/aibridge/interceptions",
- options,
- );
- const response =
- await this.axios.get(url);
- return response.data;
- };
}
// This is a hard coded CSRF token/cookie pair for local development. In prod,
diff --git a/site/src/api/queries/aiBridge.ts b/site/src/api/queries/aiBridge.ts
index 1e385bc464564..987555aabcffd 100644
--- a/site/src/api/queries/aiBridge.ts
+++ b/site/src/api/queries/aiBridge.ts
@@ -13,7 +13,7 @@ export const paginatedInterceptions = (
return ["aiBridgeInterceptions", payload, pageNumber] as const;
},
queryFn: ({ limit, offset, payload }) =>
- API.experimental.getAIBridgeInterceptions({
+ API.getAIBridgeInterceptions({
offset,
limit,
q: payload,
diff --git a/site/src/pages/AIBridgePage/RequestLogsPage/RequestLogsFilter/ModelFilter.tsx b/site/src/pages/AIBridgePage/RequestLogsPage/RequestLogsFilter/ModelFilter.tsx
new file mode 100644
index 0000000000000..cdebd924ef635
--- /dev/null
+++ b/site/src/pages/AIBridgePage/RequestLogsPage/RequestLogsFilter/ModelFilter.tsx
@@ -0,0 +1,76 @@
+import { API } from "api/api";
+import {
+ type UseFilterMenuOptions,
+ useFilterMenu,
+} from "components/Filter/menu";
+import {
+ SelectFilter,
+ SelectFilterSearch,
+} from "components/Filter/SelectFilter";
+import type { FC } from "react";
+
+export const useModelFilterMenu = ({
+ value,
+ onChange,
+ enabled,
+}: Pick) => {
+ return useFilterMenu({
+ id: "model",
+ getSelectedOption: async () => {
+ const modelsRes = await API.getAIBridgeModels({
+ q: value,
+ limit: 1,
+ });
+ const firstModel = modelsRes.at(0);
+
+ if (firstModel) {
+ return {
+ label: firstModel,
+ value: firstModel,
+ };
+ }
+
+ return null;
+ },
+ getOptions: async (query) => {
+ const modelsRes = await API.getAIBridgeModels({
+ q: query,
+ limit: 25,
+ });
+ return modelsRes.map((model) => ({
+ label: model,
+ value: model,
+ }));
+ },
+ value,
+ onChange,
+ enabled,
+ });
+};
+
+export type ModelFilterMenu = ReturnType;
+
+interface ModelFilterProps {
+ menu: ModelFilterMenu;
+}
+
+export const ModelFilter: FC = ({ menu }) => {
+ return (
+ menu.selectOption(option)}
+ selectedOption={menu.selectedOption ?? undefined}
+ selectFilterSearch={
+
+ }
+ />
+ );
+};
diff --git a/site/src/pages/AIBridgePage/RequestLogsPage/filter/filter.tsx b/site/src/pages/AIBridgePage/RequestLogsPage/RequestLogsFilter/ProviderFilter.tsx
similarity index 100%
rename from site/src/pages/AIBridgePage/RequestLogsPage/filter/filter.tsx
rename to site/src/pages/AIBridgePage/RequestLogsPage/RequestLogsFilter/ProviderFilter.tsx
diff --git a/site/src/pages/AIBridgePage/RequestLogsPage/filter/RequestLogsFilter.tsx b/site/src/pages/AIBridgePage/RequestLogsPage/RequestLogsFilter/RequestLogsFilter.tsx
similarity index 80%
rename from site/src/pages/AIBridgePage/RequestLogsPage/filter/RequestLogsFilter.tsx
rename to site/src/pages/AIBridgePage/RequestLogsPage/RequestLogsFilter/RequestLogsFilter.tsx
index 34efa48a38aa4..5ae101704b895 100644
--- a/site/src/pages/AIBridgePage/RequestLogsPage/filter/RequestLogsFilter.tsx
+++ b/site/src/pages/AIBridgePage/RequestLogsPage/RequestLogsFilter/RequestLogsFilter.tsx
@@ -1,7 +1,8 @@
import { Filter, MenuSkeleton, type useFilter } from "components/Filter/Filter";
import { type UserFilterMenu, UserMenu } from "components/Filter/UserFilter";
import type { FC } from "react";
-import { ProviderFilter, type ProviderFilterMenu } from "./filter";
+import { ModelFilter, type ModelFilterMenu } from "./ModelFilter";
+import { ProviderFilter, type ProviderFilterMenu } from "./ProviderFilter";
interface RequestLogsFilterProps {
filter: ReturnType;
@@ -9,6 +10,7 @@ interface RequestLogsFilterProps {
menus: {
user: UserFilterMenu;
provider: ProviderFilterMenu;
+ model: ModelFilterMenu;
};
}
@@ -37,6 +39,7 @@ export const RequestLogsFilter: FC