Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 2 additions & 8 deletions coderd/apidoc/docs.go

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

10 changes: 2 additions & 8 deletions coderd/apidoc/swagger.json

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

48 changes: 14 additions & 34 deletions coderd/chat_routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,6 @@ const (
chatAPIPrefixExperimental
)

// injectDefaultOrganizationParam lets the legacy default-organization
// routes reuse the organization-scoped handlers.
func injectDefaultOrganizationParam(next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
chi.RouteContext(req.Context()).URLParams.Add("organization", codersdk.DefaultOrganization)
next.ServeHTTP(rw, req)
})
}

// registerChatAPIRoutes mounts the chat API surface on r, the root router
// of an API prefix. /api/v2 and /api/experimental serve the same promoted
// routes during the CODAGT-921 compatibility window. The experimental
Expand All @@ -56,30 +47,18 @@ func (api *API) registerChatAPIRoutes(r chi.Router, apiKeyMiddleware func(http.H
r.Use(api.chatFilesRateLimitMW())
r.Get("/chats/files/{file}/download", api.downloadChatFile)
})
if experimental {
// Superseded by the organization-scoped models collection and
// deliberately not promoted. Keep until the frontend uses the
// organization-scoped routes.
r.Route("/chats/model-configs", func(r chi.Router) {
r.Use(
apiKeyMiddleware,
injectDefaultOrganizationParam,
httpmw.ExtractOrganizationParam(api.Database),
)
r.Get("/", api.listDefaultOrganizationChatModelConfigs)
r.Post("/", api.createChatModelConfig)
})
}
r.Route("/chats", func(r chi.Router) {
r.Use(apiKeyMiddleware)
if experimental {
// Superseded by the organization-scoped models route and
// deliberately not promoted. Keep until the frontend uses
// the organization-scoped routes.
r.With(
injectDefaultOrganizationParam,
httpmw.ExtractOrganizationParam(api.Database),
).Get("/models", api.listChatModelConfigsByOrganization)
// Reserve removed collection paths so they return 404 instead of
// falling into the {chat} wildcard and failing UUID parsing.
for _, segment := range []string{"/models", "/model-configs"} {
r.Route(segment, func(r chi.Router) {
r.NotFound(func(rw http.ResponseWriter, _ *http.Request) {
httpapi.RouteNotFound(rw)
})
})
}
// TODO(cian): place under /api/experimental/chats/config
r.Route("/providers", func(r chi.Router) {
r.Get("/", api.listChatProviders)
Expand All @@ -97,13 +76,14 @@ func (api *API) registerChatAPIRoutes(r chi.Router, apiKeyMiddleware func(http.H
})
})
} else {
// These segments exist only under /api/experimental. Reserve
// them with empty subrouters so they return 404 instead of
// Reserve unmounted segments so they return 404 instead of
// falling into the {chat} wildcard and failing UUID parsing
// with a 400.
// TODO(CODAGT-922): drop the reservations with the
segments := []string{"/model-configs"}
// TODO(CODAGT-922): drop the provider reservations with the
// experimental mounts.
for _, segment := range []string{"/models", "/model-configs", "/providers", "/user-provider-configs"} {
segments = append(segments, "/providers", "/user-provider-configs")
for _, segment := range segments {
r.Route(segment, func(r chi.Router) {
r.NotFound(func(rw http.ResponseWriter, _ *http.Request) {
httpapi.RouteNotFound(rw)
Expand Down
5 changes: 3 additions & 2 deletions coderd/chat_routes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ func TestChatRoutesCompatibility(t *testing.T) {
for _, route := range []string{
"/api/experimental/chats",
"/api/experimental/chats/config/system-prompt",
"/api/experimental/chats/models",
"/api/v2/chats",
"/api/v2/chats/config/system-prompt",
} {
Expand All @@ -45,7 +44,9 @@ func TestChatRoutesCompatibility(t *testing.T) {
method string
path string
}{
{http.MethodGet, "/api/v2/chats/models"},
{http.MethodGet, "/api/experimental/chats/models"},
{http.MethodGet, "/api/experimental/chats/model-configs"},
{http.MethodPost, "/api/experimental/chats/model-configs"},
{http.MethodGet, "/api/v2/chats/model-configs"},
{http.MethodPost, "/api/v2/chats/model-configs"},
{http.MethodGet, "/api/v2/chats/providers"},
Expand Down
31 changes: 1 addition & 30 deletions coderd/exp_chats.go
Original file line number Diff line number Diff line change
Expand Up @@ -6898,32 +6898,6 @@ func (*API) deleteUserChatProviderKey(rw http.ResponseWriter, r *http.Request) {
writeLegacyChatProviderGone(rw, r)
}

func (api *API) listDefaultOrganizationChatModelConfigs(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
organization := httpmw.OrganizationParam(r)
apiKey := httpmw.APIKey(r)

if !chatModelConfigReadScope(apiKey.Scopes) {
httpapi.Forbidden(rw)
return
}

configs, err := api.Database.GetChatModelConfigs(ctx, organization.ID)
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Failed to list chat model configs.",
Detail: err.Error(),
})
return
}

resp := make([]codersdk.ChatModel, 0, len(configs))
for _, config := range configs {
resp = append(resp, convertChatModelConfig(config))
}
httpapi.Write(ctx, rw, http.StatusOK, resp)
}

// @Summary List AI models and provider descriptors in an organization
// @ID list-ai-models-and-provider-descriptors-in-an-organization
// @Security CoderSessionToken
Expand All @@ -6932,7 +6906,6 @@ func (api *API) listDefaultOrganizationChatModelConfigs(rw http.ResponseWriter,
// @Param organization path string true "Organization name or ID"
// @Success 200 {object} codersdk.OrganizationChatModelsResponse
// @Router /api/v2/organizations/{organization}/chats/models [get]
// @x-apidocgen {"skip": true}
func (api *API) listChatModelConfigsByOrganization(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
organization := httpmw.OrganizationParam(r)
Expand Down Expand Up @@ -7017,8 +6990,7 @@ func chatModelConfigReadScope(scopes database.APIKeyScopes) bool {
// read gate; providers are deployment-scoped and an org admin cannot read
// them directly, so the fetch runs under a narrow AsChatd context scoped to
// exactly these two reads and the result is projected to the fixed redacted
// fields (no key material, base URLs, or headers). Disclosure matches what
// /api/experimental/chats/models already shows any authenticated caller.
// fields, which exclude key material, base URLs, and headers.
func (api *API) chatModelProviderDescriptors(
ctx context.Context,
userID uuid.UUID,
Expand Down Expand Up @@ -7196,7 +7168,6 @@ func (api *API) auditChatModelConfigTransitions(
// @Param request body codersdk.CreateChatModelRequest true "Model"
// @Success 201 {object} codersdk.ChatModel
// @Router /api/v2/organizations/{organization}/chats/models [post]
// @x-apidocgen {"skip": true}
func (api *API) createChatModelConfig(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
apiKey := httpmw.APIKey(r)
Expand Down
43 changes: 0 additions & 43 deletions coderd/exp_chats_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4719,49 +4719,6 @@ func TestListChatModelConfigs(t *testing.T) {
require.True(t, found)
})

t.Run("CompatibilityCollectionRoutesUseDefaultOrganization", func(t *testing.T) {
t.Parallel()

ctx := testutil.Context(t, testutil.WaitLong)
client := newChatClient(t)
_ = coderdtest.CreateFirstUser(t, client.Client)
modelConfig := createChatModel(t, client)

res, err := client.Request(ctx, http.MethodGet, "/api/experimental/chats/model-configs", nil)
require.NoError(t, err)
defer res.Body.Close()
require.Equal(t, http.StatusOK, res.StatusCode)
var configs []codersdk.ChatModel
require.NoError(t, codersdk.ReadBodyAsJSON(res, &configs))
require.Contains(t, configs, modelConfig)

collectionRes, err := client.Request(ctx, http.MethodGet, "/api/experimental/chats/models", nil)
require.NoError(t, err)
defer collectionRes.Body.Close()
require.Equal(t, http.StatusOK, collectionRes.StatusCode)
var collection codersdk.OrganizationChatModelsResponse
require.NoError(t, codersdk.ReadBodyAsJSON(collectionRes, &collection))
require.Contains(t, collection.Models, modelConfig)

contextLimit := int64(8192)
createdRes, err := client.Request(ctx, http.MethodPost, "/api/experimental/chats/model-configs", codersdk.CreateChatModelRequest{
AIProviderID: &modelConfig.AIProviderID,
Model: "compatibility-model",
ContextLimit: &contextLimit,
})
require.NoError(t, err)
defer createdRes.Body.Close()
require.Equal(t, http.StatusCreated, createdRes.StatusCode)
var created codersdk.ChatModel
require.NoError(t, codersdk.ReadBodyAsJSON(createdRes, &created))

got, err := client.ChatModel(ctx, created.OrganizationID, created.ID)
require.NoError(t, err)
require.Equal(t, created.ID, got.ID)

require.NoError(t, client.DeleteChatModel(ctx, created.OrganizationID, created.ID))
})

t.Run("CollectionIncludesDisabledModelConfigs", func(t *testing.T) {
t.Parallel()

Expand Down
4 changes: 2 additions & 2 deletions codersdk/chats.go
Original file line number Diff line number Diff line change
Expand Up @@ -2301,8 +2301,8 @@ func (c *Client) DeleteChatModel(ctx context.Context, organizationID, modelID uu
// ChatModelProviderDescriptor is the redacted view of an AI provider carried
// on the org model collection response. It carries only the capability
// metadata the Models UI needs; key material, base URLs, and headers are
// never exposed. The fields mirror what /api/experimental/chats/models
// already discloses to any authenticated caller.
// never exposed. The fields mirror the provider descriptors returned by the
// organization-scoped chat models collection.
type ChatModelProviderDescriptor struct {
ID uuid.UUID `json:"id" format:"uuid"`
Type string `json:"type"`
Expand Down
14 changes: 14 additions & 0 deletions docs/ai-coder/agents/models.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,20 @@
Model APIs identify each configured model with a UUID.
The provider's model identifier, such as `gpt-5.3-codex`, doesn't replace this model UUID.

### Migrate model API clients

Model collection APIs require an organization name or ID in the path.
Update clients that use the removed default-organization routes as follows:

| Removed route | Replacement | Response change |
|----------------------------------------------|----------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------|
| `GET /api/experimental/chats/models` | `GET /api/v2/organizations/{organization}/chats/models` | The response remains an `OrganizationChatModelsResponse`. |
| `GET /api/experimental/chats/model-configs` | `GET /api/v2/organizations/{organization}/chats/models` | The response changes from a `ChatModel` array to an `OrganizationChatModelsResponse`. Read configured models from `models`. |
| `POST /api/experimental/chats/model-configs` | `POST /api/v2/organizations/{organization}/chats/models` | The request and `ChatModel` response shapes remain the same. |

Replace `{organization}` with the organization name or ID that owns the models.
The collection response also includes provider descriptors and unsupported provider details.

### Add a model

1. Navigate to **Admin settings** > **AI** > **Models**.
Expand Down Expand Up @@ -373,7 +387,7 @@
enabled, developers can supply personal API keys for any enabled AI provider
from the Agents settings page.

### Managing personal API keys

Check warning on line 390 in docs/ai-coder/agents/models.md

View workflow job for this annotation

GitHub Actions / lint-docs

Coder.GerundHeading

Heading starts with an -ing word ('Managing'); prefer the imperative ('Install') or the noun ('Installation'). See capitalization-and-punctuation.md#no-gerund-leading-headings.

1. Navigate to the **Agents** page in the Coder dashboard.
1. Open **Settings** and select the **API Keys** tab.
Expand All @@ -390,14 +404,14 @@
used for deployment-managed provider secrets. The dashboard never displays a
saved key, only whether one is set.

### Removing a personal key

Check warning on line 407 in docs/ai-coder/agents/models.md

View workflow job for this annotation

GitHub Actions / lint-docs

Coder.GerundHeading

Heading starts with an -ing word ('Removing'); prefer the imperative ('Install') or the noun ('Installation'). See capitalization-and-punctuation.md#no-gerund-leading-headings.

Click **Remove** on the provider card in the API Keys settings tab. Subsequent
requests use deployment-managed credentials when they are configured for that
provider. If no deployment-managed credential is available, add a new personal
key before you use models from that provider.

## Using an LLM proxy

Check warning on line 414 in docs/ai-coder/agents/models.md

View workflow job for this annotation

GitHub Actions / lint-docs

Coder.GerundHeading

Heading starts with an -ing word ('Using'); prefer the imperative ('Install') or the noun ('Installation'). See capitalization-and-punctuation.md#no-gerund-leading-headings.

Organizations that route LLM traffic through a centralized proxy, such as
LiteLLM or an internal gateway, can point a provider's **Endpoint** or **Base
Expand Down
Loading
Loading