mirror of
https://github.com/coder/coder.git
synced 2026-10-16 14:48:56 +08:00
feat!(coderd): remove the experimental chat API mounts after the transition window
This commit is contained in:
@@ -320,20 +320,20 @@ func TestIsReplicaRelayRequest(t *testing.T) {
|
||||
|
||||
t.Run("WithHeader", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
r, _ := http.NewRequestWithContext(context.Background(), "GET", "/api/experimental/chats/abc/stream", nil)
|
||||
r, _ := http.NewRequestWithContext(context.Background(), "GET", "/api/v2/chats/abc/stream", nil)
|
||||
r.Header.Set("X-Coder-Relay-Source-Replica", "some-uuid")
|
||||
require.True(t, isReplicaRelayRequest(r))
|
||||
})
|
||||
|
||||
t.Run("WithoutHeader", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
r, _ := http.NewRequestWithContext(context.Background(), "GET", "/api/experimental/chats/abc/stream", nil)
|
||||
r, _ := http.NewRequestWithContext(context.Background(), "GET", "/api/v2/chats/abc/stream", nil)
|
||||
require.False(t, isReplicaRelayRequest(r))
|
||||
})
|
||||
|
||||
t.Run("EmptyHeader", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
r, _ := http.NewRequestWithContext(context.Background(), "GET", "/api/experimental/chats/abc/stream", nil)
|
||||
r, _ := http.NewRequestWithContext(context.Background(), "GET", "/api/v2/chats/abc/stream", nil)
|
||||
r.Header.Set("X-Coder-Relay-Source-Replica", "")
|
||||
require.False(t, isReplicaRelayRequest(r))
|
||||
})
|
||||
|
||||
+41
-112
@@ -12,91 +12,31 @@ import (
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
)
|
||||
|
||||
// chatFilesRateLimitMW returns the middleware enforcing FilesRateLimit
|
||||
// on chat file routes. Both API prefixes mount the same instance, and
|
||||
// the limiter keys on a prefix-stripped endpoint, so alternating
|
||||
// prefixes cannot double the budget.
|
||||
func (api *API) chatFilesRateLimitMW() func(http.Handler) http.Handler {
|
||||
api.chatFilesRateLimitOnce.Do(func() {
|
||||
api.chatFilesRateLimit = httpmw.RateLimitByAPICompatibilityEndpoint(api.FilesRateLimit, time.Minute)
|
||||
})
|
||||
return api.chatFilesRateLimit
|
||||
}
|
||||
|
||||
// chatAPIPrefix identifies which API prefix a chat route mount serves.
|
||||
type chatAPIPrefix int
|
||||
|
||||
const (
|
||||
chatAPIPrefixV2 chatAPIPrefix = iota
|
||||
chatAPIPrefixExperimental
|
||||
)
|
||||
|
||||
// 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
|
||||
// mount also serves the routes that were not promoted, while /api/v2
|
||||
// reserves their path segments so they return 404 instead of falling into
|
||||
// the {chat} wildcard.
|
||||
// TODO(CODAGT-921): unmount from /api/experimental after the transition
|
||||
// window (tracked in CODAGT-922).
|
||||
func (api *API) registerChatAPIRoutes(r chi.Router, apiKeyMiddleware func(http.Handler) http.Handler, prefix chatAPIPrefix) {
|
||||
experimental := prefix == chatAPIPrefixExperimental
|
||||
// registerChatAPIRoutes mounts the chat API surface on r, the /api/v2 root
|
||||
// router.
|
||||
func (api *API) registerChatAPIRoutes(r chi.Router, apiKeyMiddleware func(http.Handler) http.Handler) {
|
||||
// Signed URL tokens authenticate downloads, so the route stays
|
||||
// outside the API key middleware.
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(api.chatFilesRateLimitMW())
|
||||
r.Use(httpmw.RateLimit(api.FilesRateLimit, time.Minute))
|
||||
r.Get("/chats/files/{file}/download", api.downloadChatFile)
|
||||
})
|
||||
r.Route("/chats", func(r chi.Router) {
|
||||
r.Use(apiKeyMiddleware)
|
||||
if experimental {
|
||||
// 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)
|
||||
r.Post("/", api.createChatProvider)
|
||||
r.Route("/{providerConfig}", func(r chi.Router) {
|
||||
r.Patch("/", api.updateChatProvider)
|
||||
r.Delete("/", api.deleteChatProvider)
|
||||
})
|
||||
// Reserve the unmounted segment so it returns 404 instead of
|
||||
// falling into the {chat} wildcard and failing UUID parsing
|
||||
// with a 400.
|
||||
r.Route("/model-configs", func(r chi.Router) {
|
||||
r.NotFound(func(rw http.ResponseWriter, _ *http.Request) {
|
||||
httpapi.RouteNotFound(rw)
|
||||
})
|
||||
r.Route("/user-provider-configs", func(r chi.Router) {
|
||||
r.Get("/", api.listUserChatProviderConfigs)
|
||||
r.Route("/{providerConfig}", func(r chi.Router) {
|
||||
r.Put("/", api.upsertUserChatProviderKey)
|
||||
r.Delete("/", api.deleteUserChatProviderKey)
|
||||
})
|
||||
})
|
||||
} else {
|
||||
// Reserve unmounted segments so they return 404 instead of
|
||||
// falling into the {chat} wildcard and failing UUID parsing
|
||||
// with a 400.
|
||||
segments := []string{"/model-configs"}
|
||||
// TODO(CODAGT-922): drop the provider reservations with the
|
||||
// experimental mounts.
|
||||
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)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
r.Get("/by-workspace", api.chatsByWorkspace)
|
||||
r.Get("/", api.listChats)
|
||||
r.Post("/", api.postChats)
|
||||
r.Get("/watch", api.watchChats)
|
||||
r.Route("/files", func(r chi.Router) {
|
||||
r.Use(api.chatFilesRateLimitMW())
|
||||
r.Use(httpmw.RateLimit(api.FilesRateLimit, time.Minute))
|
||||
r.Post("/", api.postChatFile)
|
||||
r.Post("/{file}/download-url", api.postChatFileDownloadURL)
|
||||
r.Get("/{file}", api.chatFileByID)
|
||||
@@ -125,18 +65,6 @@ func (api *API) registerChatAPIRoutes(r chi.Router, apiKeyMiddleware func(http.H
|
||||
r.Put("/debug-retention-days", api.putChatDebugRetentionDays)
|
||||
r.Get("/auto-archive-days", api.getChatAutoArchiveDays)
|
||||
r.Put("/auto-archive-days", api.putChatAutoArchiveDays)
|
||||
if experimental {
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(httpmw.RequireExperimentWithDevBypass(api.Experiments, codersdk.ExperimentChatVirtualDesktop))
|
||||
r.Get("/computer-use-provider", api.getChatComputerUseProvider)
|
||||
r.Put("/computer-use-provider", api.putChatComputerUseProvider)
|
||||
})
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(httpmw.RequireExperimentWithDevBypass(api.Experiments, codersdk.ExperimentChatAdvisor))
|
||||
r.Get("/advisor", api.getChatAdvisorConfig)
|
||||
r.Put("/advisor", api.putChatAdvisorConfig)
|
||||
})
|
||||
}
|
||||
})
|
||||
r.Route("/{chat}", func(r chi.Router) {
|
||||
r.Use(httpmw.ExtractChatParam(api.Database))
|
||||
@@ -167,34 +95,37 @@ func (api *API) registerChatAPIRoutes(r chi.Router, apiKeyMiddleware func(http.H
|
||||
r.Get("/", api.streamChat)
|
||||
r.Get("/parts", api.streamChatParts)
|
||||
r.Get("/git", api.watchChatGit)
|
||||
if experimental {
|
||||
r.Get("/desktop", api.watchChatDesktop)
|
||||
}
|
||||
})
|
||||
if experimental {
|
||||
r.Route("/debug", func(r chi.Router) {
|
||||
r.Get("/runs", api.getChatDebugRuns)
|
||||
r.Get("/runs/{debugRun}", api.getChatDebugRun)
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// registerMCPServerOAuth2Routes mounts the user-scoped MCP server OAuth2
|
||||
// routes shared by both API prefixes.
|
||||
func (api *API) registerMCPServerOAuth2Routes(r chi.Router, prefix chatAPIPrefix) {
|
||||
if prefix == chatAPIPrefixExperimental {
|
||||
// Providers pin the redirect URI when a session is established,
|
||||
// so the callback URL cannot change for existing sessions without
|
||||
// breaking token refresh and forcing a re-auth.
|
||||
// TODO(CODAGT-922): promote once existing sessions can be
|
||||
// re-authenticated against a /api/v2 callback.
|
||||
r.Get("/servers/{mcpServer}/oauth2/callback", api.mcpServerOAuth2Callback)
|
||||
}
|
||||
// Disconnect stays outside organization routes so former organization
|
||||
// members can delete their stored token after losing config read access.
|
||||
r.Delete("/servers/{mcpServer}/oauth2/disconnect", api.mcpServerOAuth2Disconnect)
|
||||
// registerExperimentalChatRoutes mounts the chat routes that were not
|
||||
// promoted to /api/v2 on r, the /api/experimental root router.
|
||||
func (api *API) registerExperimentalChatRoutes(r chi.Router, apiKeyMiddleware func(http.Handler) http.Handler) {
|
||||
r.Route("/chats", func(r chi.Router) {
|
||||
r.Use(apiKeyMiddleware)
|
||||
r.Route("/config", func(r chi.Router) {
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(httpmw.RequireExperimentWithDevBypass(api.Experiments, codersdk.ExperimentChatVirtualDesktop))
|
||||
r.Get("/computer-use-provider", api.getChatComputerUseProvider)
|
||||
r.Put("/computer-use-provider", api.putChatComputerUseProvider)
|
||||
})
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(httpmw.RequireExperimentWithDevBypass(api.Experiments, codersdk.ExperimentChatAdvisor))
|
||||
r.Get("/advisor", api.getChatAdvisorConfig)
|
||||
r.Put("/advisor", api.putChatAdvisorConfig)
|
||||
})
|
||||
})
|
||||
r.Route("/{chat}", func(r chi.Router) {
|
||||
r.Use(httpmw.ExtractChatParam(api.Database))
|
||||
r.Get("/stream/desktop", api.watchChatDesktop)
|
||||
r.Route("/debug", func(r chi.Router) {
|
||||
r.Get("/runs", api.getChatDebugRuns)
|
||||
r.Get("/runs/{debugRun}", api.getChatDebugRun)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func (api *API) registerUserAIProviderKeyRoutes(r chi.Router) {
|
||||
@@ -208,7 +139,7 @@ func (api *API) registerUserAIProviderKeyRoutes(r chi.Router) {
|
||||
// registerOrganizationChatRoutes mounts the organization-scoped chat and
|
||||
// MCP server configuration routes; r must already extract the
|
||||
// organization parameter.
|
||||
func (api *API) registerOrganizationChatRoutes(r chi.Router, prefix chatAPIPrefix) {
|
||||
func (api *API) registerOrganizationChatRoutes(r chi.Router) {
|
||||
r.Route("/mcp-servers", func(r chi.Router) {
|
||||
r.Get("/", api.listMCPServerConfigs)
|
||||
r.Post("/", api.createMCPServerConfig)
|
||||
@@ -223,10 +154,8 @@ func (api *API) registerOrganizationChatRoutes(r chi.Router, prefix chatAPIPrefi
|
||||
policy.ActionShare)).Get("/acl", api.mcpServerConfigACL)
|
||||
r.With(httpmw.ExtractMCPServerConfigParam(api.Database, api.HTTPAuth.Authorize,
|
||||
policy.ActionShare)).Patch("/acl", api.patchMCPServerConfigACL)
|
||||
if prefix == chatAPIPrefixV2 {
|
||||
r.With(httpmw.ExtractMCPServerConfigParam(api.Database, api.HTTPAuth.Authorize,
|
||||
policy.ActionShare)).Get("/acl/available", api.mcpServerConfigACLAvailable)
|
||||
}
|
||||
r.With(httpmw.ExtractMCPServerConfigParam(api.Database, api.HTTPAuth.Authorize,
|
||||
policy.ActionShare)).Get("/acl/available", api.mcpServerConfigACLAvailable)
|
||||
r.With(httpmw.ExtractMCPServerConfigParam(api.Database, api.HTTPAuth.Authorize,
|
||||
policy.ActionRead)).Get("/oauth2/connect", api.mcpServerOAuth2Connect)
|
||||
})
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
package coderd
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestChatFilesRateLimitMWCompatibilityAliases(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
api := &API{Options: &Options{FilesRateLimit: 1}}
|
||||
handler := api.chatFilesRateLimitMW()(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
|
||||
for i, requestPath := range []string{
|
||||
"/api/v2/chats/files/00000000-0000-0000-0000-000000000000",
|
||||
"/api/experimental/chats/files/00000000-0000-0000-0000-000000000000",
|
||||
} {
|
||||
req := httptest.NewRequest(http.MethodGet, requestPath, nil)
|
||||
req.RemoteAddr = "192.0.2.1:1234"
|
||||
rec := httptest.NewRecorder()
|
||||
handler.ServeHTTP(rec, req)
|
||||
resp := rec.Result()
|
||||
_ = resp.Body.Close()
|
||||
|
||||
expectedStatus := http.StatusOK
|
||||
if i > 0 {
|
||||
expectedStatus = http.StatusTooManyRequests
|
||||
}
|
||||
require.Equal(t, expectedStatus, resp.StatusCode, requestPath)
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/coderdtest"
|
||||
@@ -13,7 +14,7 @@ import (
|
||||
"github.com/coder/coder/v2/testutil"
|
||||
)
|
||||
|
||||
func TestChatRoutesCompatibility(t *testing.T) {
|
||||
func TestChatRouteMounts(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
@@ -29,10 +30,9 @@ func TestChatRoutesCompatibility(t *testing.T) {
|
||||
})
|
||||
|
||||
for _, route := range []string{
|
||||
"/api/experimental/chats",
|
||||
"/api/experimental/chats/config/system-prompt",
|
||||
"/api/v2/chats",
|
||||
"/api/v2/chats/config/system-prompt",
|
||||
fmt.Sprintf("/api/experimental/chats/%s/debug/runs", chat.ID),
|
||||
} {
|
||||
res, err := client.Request(ctx, http.MethodGet, route, nil)
|
||||
require.NoError(t, err)
|
||||
@@ -44,18 +44,20 @@ func TestChatRoutesCompatibility(t *testing.T) {
|
||||
method string
|
||||
path string
|
||||
}{
|
||||
{http.MethodGet, "/api/experimental/chats/models"},
|
||||
{http.MethodGet, "/api/experimental/chats/model-configs"},
|
||||
{http.MethodPost, "/api/experimental/chats/model-configs"},
|
||||
// Promoted routes no longer answer on /api/experimental.
|
||||
{http.MethodGet, "/api/experimental/chats"},
|
||||
{http.MethodGet, "/api/experimental/chats/config/system-prompt"},
|
||||
{http.MethodGet, fmt.Sprintf("/api/experimental/organizations/%s/chats/models", firstUser.OrganizationID)},
|
||||
{http.MethodGet, fmt.Sprintf("/api/experimental/organizations/%s/mcp-servers", firstUser.OrganizationID)},
|
||||
{http.MethodDelete, fmt.Sprintf("/api/experimental/mcp/servers/%s/oauth2/disconnect", uuid.New())},
|
||||
{http.MethodGet, "/api/experimental/users/me/ai-provider-keys"},
|
||||
// Never-promoted routes are absent from /api/v2.
|
||||
{http.MethodGet, "/api/v2/chats/model-configs"},
|
||||
{http.MethodPost, "/api/v2/chats/model-configs"},
|
||||
{http.MethodGet, "/api/v2/chats/providers"},
|
||||
{http.MethodGet, "/api/v2/chats/user-provider-configs"},
|
||||
{http.MethodGet, "/api/v2/chats/config/computer-use-provider"},
|
||||
{http.MethodGet, "/api/v2/chats/config/advisor"},
|
||||
{http.MethodGet, fmt.Sprintf("/api/v2/chats/%s/debug/runs", chat.ID)},
|
||||
{http.MethodGet, fmt.Sprintf("/api/v2/chats/%s/stream/desktop", chat.ID)},
|
||||
{http.MethodGet, fmt.Sprintf("/api/experimental/organizations/%s/mcp-servers/not-a-uuid/acl/available", firstUser.OrganizationID)},
|
||||
{http.MethodGet, "/api/v2/mcp/servers/not-a-uuid/oauth2/callback"},
|
||||
{http.MethodPost, "/api/v2/mcp/http/server"},
|
||||
} {
|
||||
|
||||
+10
-32
@@ -1351,32 +1351,14 @@ func New(options *Options) *API {
|
||||
r.Delete("/", api.deleteUserSkill)
|
||||
})
|
||||
})
|
||||
// Chat routes are promoted to /api/v2. CODAGT-921 decided a compatibility
|
||||
// window, so these experimental duplicates must remain for one release.
|
||||
// TODO(CODAGT-921): remove after the transition window (tracked in CODAGT-922).
|
||||
r.Route("/users/{user}/ai-provider-keys", func(r chi.Router) {
|
||||
r.Use(
|
||||
apiKeyMiddleware,
|
||||
httpmw.ExtractUserParam(options.Database),
|
||||
)
|
||||
api.registerUserAIProviderKeyRoutes(r)
|
||||
})
|
||||
r.Route("/organizations", func(r chi.Router) {
|
||||
r.Use(apiKeyMiddleware)
|
||||
r.Route("/{organization}", func(r chi.Router) {
|
||||
r.Use(httpmw.ExtractOrganizationParam(options.Database))
|
||||
api.registerOrganizationChatRoutes(r, chatAPIPrefixExperimental)
|
||||
r.Route("/members/{user}", func(r chi.Router) {
|
||||
r.Use(httpmw.ExtractOrganizationMemberParam(options.Database))
|
||||
api.registerOrganizationMemberChatRoutes(r)
|
||||
})
|
||||
})
|
||||
})
|
||||
api.registerChatAPIRoutes(r, apiKeyMiddleware, chatAPIPrefixExperimental)
|
||||
api.registerExperimentalChatRoutes(r, apiKeyMiddleware)
|
||||
|
||||
r.Route("/mcp", func(r chi.Router) {
|
||||
r.Use(apiKeyMiddleware)
|
||||
api.registerMCPServerOAuth2Routes(r, chatAPIPrefixExperimental)
|
||||
// Providers pin the redirect URI when a session is established,
|
||||
// so the callback URL cannot change for existing sessions without
|
||||
// breaking token refresh and forcing a re-auth.
|
||||
r.Get("/servers/{mcpServer}/oauth2/callback", api.mcpServerOAuth2Callback)
|
||||
// MCP HTTP transport endpoint with mandatory authentication.
|
||||
r.Route("/http", func(r chi.Router) {
|
||||
r.Use(
|
||||
@@ -1432,10 +1414,12 @@ func New(options *Options) *API {
|
||||
r.Get("/available", handleExperimentsAvailable)
|
||||
r.Get("/", api.handleExperimentsGet)
|
||||
})
|
||||
api.registerChatAPIRoutes(r, apiKeyMiddleware, chatAPIPrefixV2)
|
||||
api.registerChatAPIRoutes(r, apiKeyMiddleware)
|
||||
r.Route("/mcp", func(r chi.Router) {
|
||||
r.Use(apiKeyMiddleware)
|
||||
api.registerMCPServerOAuth2Routes(r, chatAPIPrefixV2)
|
||||
// Disconnect stays outside organization routes so former organization
|
||||
// members can delete their stored token after losing config read access.
|
||||
r.Delete("/servers/{mcpServer}/oauth2/disconnect", api.mcpServerOAuth2Disconnect)
|
||||
})
|
||||
|
||||
r.Get("/updatecheck", api.updateCheck)
|
||||
@@ -1500,7 +1484,7 @@ func New(options *Options) *API {
|
||||
r.Use(
|
||||
httpmw.ExtractOrganizationParam(options.Database),
|
||||
)
|
||||
api.registerOrganizationChatRoutes(r, chatAPIPrefixV2)
|
||||
api.registerOrganizationChatRoutes(r)
|
||||
r.Get("/", api.organization)
|
||||
r.Post("/templateversions", api.postTemplateVersionsByOrganization)
|
||||
r.Route("/templates", func(r chi.Router) {
|
||||
@@ -2175,12 +2159,6 @@ type API struct {
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
|
||||
// chatFilesRateLimit is shared by the /api/experimental and /api/v2
|
||||
// chat file mounts so the compatibility window does not double the
|
||||
// FilesRateLimit budget.
|
||||
chatFilesRateLimitOnce sync.Once
|
||||
chatFilesRateLimit func(http.Handler) http.Handler
|
||||
|
||||
// DeploymentID is loaded from the database on startup.
|
||||
DeploymentID string
|
||||
|
||||
|
||||
+2
-38
@@ -5522,7 +5522,7 @@ func (api *API) putChatAdvisorConfig(rw http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
if req.DeprecatedModelConfigID != nil || req.DeprecatedReasoningEffort != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "Advisor model settings moved to PUT /api/experimental/organizations/{organization}/chats/model-overrides/advisor.",
|
||||
Message: "Advisor model settings moved to PUT /api/v2/organizations/{organization}/chats/model-overrides/advisor.",
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -6376,8 +6376,7 @@ func (api *API) postChatFileDownloadURL(rw http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO(CODAGT-922): flip to /api/v2 when experimental mounts are removed.
|
||||
downloadURL := api.AccessURL.JoinPath("api", "experimental", "chats", "files", fileID.String(), "download")
|
||||
downloadURL := api.AccessURL.JoinPath("api", "v2", "chats", "files", fileID.String(), "download")
|
||||
downloadURL.RawQuery = url.Values{"token": {token}}.Encode()
|
||||
digest := sha256.Sum256(chatFile.Data)
|
||||
httpapi.Write(ctx, rw, http.StatusOK, codersdk.ChatFileDownloadURLResponse{
|
||||
@@ -6942,41 +6941,6 @@ func (api *API) configuredProviderFromAIProviderKeys(provider database.AIProvide
|
||||
}
|
||||
}
|
||||
|
||||
func writeLegacyChatProviderGone(rw http.ResponseWriter, r *http.Request) {
|
||||
httpapi.Write(r.Context(), rw, http.StatusGone, codersdk.Response{
|
||||
Message: "Legacy chat provider APIs were removed. Use AI provider APIs instead.",
|
||||
Detail: "See https://coder.com/docs/ai-coder/agents/models#providers for AI provider configuration.",
|
||||
})
|
||||
}
|
||||
|
||||
func (*API) listChatProviders(rw http.ResponseWriter, r *http.Request) {
|
||||
writeLegacyChatProviderGone(rw, r)
|
||||
}
|
||||
|
||||
func (*API) createChatProvider(rw http.ResponseWriter, r *http.Request) {
|
||||
writeLegacyChatProviderGone(rw, r)
|
||||
}
|
||||
|
||||
func (*API) updateChatProvider(rw http.ResponseWriter, r *http.Request) {
|
||||
writeLegacyChatProviderGone(rw, r)
|
||||
}
|
||||
|
||||
func (*API) deleteChatProvider(rw http.ResponseWriter, r *http.Request) {
|
||||
writeLegacyChatProviderGone(rw, r)
|
||||
}
|
||||
|
||||
func (*API) listUserChatProviderConfigs(rw http.ResponseWriter, r *http.Request) {
|
||||
writeLegacyChatProviderGone(rw, r)
|
||||
}
|
||||
|
||||
func (*API) upsertUserChatProviderKey(rw http.ResponseWriter, r *http.Request) {
|
||||
writeLegacyChatProviderGone(rw, r)
|
||||
}
|
||||
|
||||
func (*API) deleteUserChatProviderKey(rw http.ResponseWriter, r *http.Request) {
|
||||
writeLegacyChatProviderGone(rw, r)
|
||||
}
|
||||
|
||||
// @Summary List AI models and provider descriptors in an organization
|
||||
// @ID list-ai-models-and-provider-descriptors-in-an-organization
|
||||
// @Security CoderSessionToken
|
||||
|
||||
@@ -237,7 +237,7 @@ func TestDeleteChatQueuedMessageMissingReturns404(t *testing.T) {
|
||||
res, err := client.Request(
|
||||
ctx,
|
||||
http.MethodDelete,
|
||||
fmt.Sprintf("/api/experimental/chats/%s/queue/99999999", chat.ID),
|
||||
fmt.Sprintf("/api/v2/chats/%s/queue/99999999", chat.ID),
|
||||
nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
@@ -264,7 +264,7 @@ func TestDeleteChatQueuedMessageEmptyQueueReturnsConflict(t *testing.T) {
|
||||
res, err := client.Request(
|
||||
ctx,
|
||||
http.MethodDelete,
|
||||
fmt.Sprintf("/api/experimental/chats/%s/queue/99999999", chat.ID),
|
||||
fmt.Sprintf("/api/v2/chats/%s/queue/99999999", chat.ID),
|
||||
nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
@@ -299,7 +299,7 @@ func TestPromoteChatQueuedMessageMissingReturns404(t *testing.T) {
|
||||
res, err := client.Request(
|
||||
ctx,
|
||||
http.MethodPost,
|
||||
fmt.Sprintf("/api/experimental/chats/%s/queue/99999999/promote", chat.ID),
|
||||
fmt.Sprintf("/api/v2/chats/%s/queue/99999999/promote", chat.ID),
|
||||
nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
@@ -326,7 +326,7 @@ func TestPromoteChatQueuedMessageEmptyQueueReturnsConflict(t *testing.T) {
|
||||
res, err := client.Request(
|
||||
ctx,
|
||||
http.MethodPost,
|
||||
fmt.Sprintf("/api/experimental/chats/%s/queue/99999999/promote", chat.ID),
|
||||
fmt.Sprintf("/api/v2/chats/%s/queue/99999999/promote", chat.ID),
|
||||
nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
@@ -436,7 +436,7 @@ func TestSubmitToolResultsRequiresActionSucceeds(t *testing.T) {
|
||||
require.True(t, foundToolResult, "tool result message must be visible in chat history")
|
||||
}
|
||||
|
||||
// TestPatchChatArchiveChildRejected verifies that PATCH /api/experimental/chats/{child}
|
||||
// TestPatchChatArchiveChildRejected verifies that PATCH /api/v2/chats/{child}
|
||||
// with archived=true returns the root-only error regardless of the
|
||||
// child's current archived value, and does not change archive state on
|
||||
// any family member.
|
||||
@@ -485,7 +485,7 @@ func TestPatchChatArchiveChildRejected(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestPatchChatUnarchiveChildRejected verifies that PATCH /api/experimental/chats/{child}
|
||||
// TestPatchChatUnarchiveChildRejected verifies that PATCH /api/v2/chats/{child}
|
||||
// with archived=false on an archived family is rejected with the
|
||||
// root-only error and leaves every family member archived. The child
|
||||
// already matches the requested value? No, the family is archived;
|
||||
|
||||
@@ -78,7 +78,7 @@ func TestPostChatsInitialPromptHookErrors(t *testing.T) {
|
||||
model := createAdditionalChatModel(t, client, "openai", "gpt-4.1")
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
|
||||
res, err := client.Request(ctx, http.MethodPost, "/api/experimental/chats", codersdk.CreateChatRequest{
|
||||
res, err := client.Request(ctx, http.MethodPost, "/api/v2/chats", codersdk.CreateChatRequest{
|
||||
OrganizationID: user.OrganizationID,
|
||||
ModelConfigID: &model.ID,
|
||||
Content: []codersdk.ChatInputPart{{
|
||||
|
||||
@@ -448,7 +448,7 @@ func TestChatModelACLSparseUpdate(t *testing.T) {
|
||||
require.Equal(t, expectedGroupRoles, chatModelACLGroupRoles(modelACL))
|
||||
|
||||
path := fmt.Sprintf(
|
||||
"/api/experimental/organizations/%s/chats/models/%s/acl",
|
||||
"/api/v2/organizations/%s/chats/models/%s/acl",
|
||||
firstUser.OrganizationID,
|
||||
model.ID,
|
||||
)
|
||||
@@ -596,7 +596,7 @@ func TestChatModelACLValidationIsAtomic(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
res, err := adminClient.Request(ctx, http.MethodPatch, fmt.Sprintf(
|
||||
"/api/experimental/organizations/%s/chats/models/%s/acl",
|
||||
"/api/v2/organizations/%s/chats/models/%s/acl",
|
||||
firstUser.OrganizationID,
|
||||
model.ID,
|
||||
), test.body)
|
||||
@@ -783,7 +783,7 @@ func TestCreateChatModelRejectsACLKeys(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
res, err := client.Request(ctx, http.MethodPost, fmt.Sprintf(
|
||||
"/api/experimental/organizations/%s/chats/models",
|
||||
"/api/v2/organizations/%s/chats/models",
|
||||
firstUser.OrganizationID,
|
||||
), map[string]any{test.key: test.value})
|
||||
require.NoError(t, err)
|
||||
|
||||
+45
-45
@@ -3102,7 +3102,7 @@ func TestWatchChats(t *testing.T) {
|
||||
firstUser := coderdtest.CreateFirstUser(t, client.Client)
|
||||
_ = createChatModel(t, client)
|
||||
|
||||
conn, err := client.Dial(ctx, "/api/experimental/chats/watch", nil)
|
||||
conn, err := client.Dial(ctx, "/api/v2/chats/watch", nil)
|
||||
require.NoError(t, err)
|
||||
defer conn.Close(websocket.StatusNormalClosure, "done")
|
||||
|
||||
@@ -3136,7 +3136,7 @@ func TestWatchChats(t *testing.T) {
|
||||
firstUser := coderdtest.CreateFirstUser(t, client.Client)
|
||||
modelConfig := createChatModel(t, client)
|
||||
|
||||
conn, err := client.Dial(ctx, "/api/experimental/chats/watch", nil)
|
||||
conn, err := client.Dial(ctx, "/api/v2/chats/watch", nil)
|
||||
require.NoError(t, err)
|
||||
defer conn.Close(websocket.StatusNormalClosure, "done")
|
||||
|
||||
@@ -3228,7 +3228,7 @@ func TestWatchChats(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
// Open the watch WebSocket.
|
||||
conn, err := client.Dial(ctx, "/api/experimental/chats/watch", nil)
|
||||
conn, err := client.Dial(ctx, "/api/v2/chats/watch", nil)
|
||||
require.NoError(t, err)
|
||||
defer conn.Close(websocket.StatusNormalClosure, "done")
|
||||
|
||||
@@ -3300,7 +3300,7 @@ func TestWatchChats(t *testing.T) {
|
||||
RootChatID: uuid.NullUUID{UUID: parentChat.ID, Valid: true},
|
||||
})
|
||||
|
||||
conn, err := client.Dial(ctx, "/api/experimental/chats/watch", nil)
|
||||
conn, err := client.Dial(ctx, "/api/v2/chats/watch", nil)
|
||||
require.NoError(t, err)
|
||||
defer conn.Close(websocket.StatusNormalClosure, "done")
|
||||
|
||||
@@ -3353,7 +3353,7 @@ func TestWatchChats(t *testing.T) {
|
||||
res, err := unauthenticatedClient.Request(
|
||||
ctx,
|
||||
http.MethodGet,
|
||||
"/api/experimental/chats/watch",
|
||||
"/api/v2/chats/watch",
|
||||
nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
@@ -3715,7 +3715,7 @@ func TestGetChatModel(t *testing.T) {
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
res, err := client.Request(ctx, tc.method, fmt.Sprintf(
|
||||
"/api/experimental/organizations/%s/chats/models/%s",
|
||||
"/api/v2/organizations/%s/chats/models/%s",
|
||||
wrongOrganization.ID,
|
||||
modelConfig.ID,
|
||||
), tc.body)
|
||||
@@ -4989,7 +4989,7 @@ func TestUpdateChatModel(t *testing.T) {
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
res, err := client.Request(ctx, http.MethodPatch, fmt.Sprintf(
|
||||
"/api/experimental/organizations/%s/chats/models/%s",
|
||||
"/api/v2/organizations/%s/chats/models/%s",
|
||||
modelConfig.OrganizationID,
|
||||
modelConfig.ID,
|
||||
), map[string]any{tc.key: tc.value})
|
||||
@@ -5065,7 +5065,7 @@ func TestUpdateChatModel(t *testing.T) {
|
||||
res, err := client.Request(
|
||||
ctx,
|
||||
http.MethodPatch,
|
||||
fmt.Sprintf("/api/experimental/organizations/%s/chats/models/not-a-uuid", firstUser.OrganizationID),
|
||||
fmt.Sprintf("/api/v2/organizations/%s/chats/models/not-a-uuid", firstUser.OrganizationID),
|
||||
codersdk.UpdateChatModelRequest{DisplayName: "ignored"},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
@@ -5223,7 +5223,7 @@ func TestDeleteChatModelConfig(t *testing.T) {
|
||||
res, err := client.Request(
|
||||
ctx,
|
||||
http.MethodDelete,
|
||||
fmt.Sprintf("/api/experimental/organizations/%s/chats/models/not-a-uuid", firstUser.OrganizationID),
|
||||
fmt.Sprintf("/api/v2/organizations/%s/chats/models/not-a-uuid", firstUser.OrganizationID),
|
||||
nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
@@ -6459,7 +6459,7 @@ func TestPatchChat(t *testing.T) {
|
||||
|
||||
coderdtest.WaitForChatSettled(ctx, t, api, chat.ID)
|
||||
|
||||
conn, err := client.Dial(ctx, "/api/experimental/chats/watch", nil)
|
||||
conn, err := client.Dial(ctx, "/api/v2/chats/watch", nil)
|
||||
require.NoError(t, err)
|
||||
defer conn.Close(websocket.StatusNormalClosure, "done")
|
||||
|
||||
@@ -7872,7 +7872,7 @@ func TestWatchChatsStatusChangeCarriesUpdatedLastModelConfigID(t *testing.T) {
|
||||
Title: "watch direct model switch",
|
||||
})
|
||||
|
||||
conn, err := client.Dial(ctx, "/api/experimental/chats/watch", nil)
|
||||
conn, err := client.Dial(ctx, "/api/v2/chats/watch", nil)
|
||||
require.NoError(t, err)
|
||||
defer conn.Close(websocket.StatusNormalClosure, "done")
|
||||
|
||||
@@ -7937,14 +7937,14 @@ func TestWatchChatsStatusChangeCarriesUpdatedLastModelConfigID(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
conn, err := client.Dial(ctx, "/api/experimental/chats/watch", nil)
|
||||
conn, err := client.Dial(ctx, "/api/v2/chats/watch", nil)
|
||||
require.NoError(t, err)
|
||||
defer conn.Close(websocket.StatusNormalClosure, "done")
|
||||
|
||||
promoteRes, err := client.Request(
|
||||
ctx,
|
||||
http.MethodPost,
|
||||
fmt.Sprintf("/api/experimental/chats/%s/queue/%d/promote", chat.ID, queuedResp.QueuedMessage.ID),
|
||||
fmt.Sprintf("/api/v2/chats/%s/queue/%d/promote", chat.ID, queuedResp.QueuedMessage.ID),
|
||||
nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
@@ -9333,7 +9333,7 @@ func TestPatchChatMessage(t *testing.T) {
|
||||
res, err := client.Request(
|
||||
ctx,
|
||||
http.MethodPatch,
|
||||
fmt.Sprintf("/api/experimental/chats/%s/messages/not-an-int", chat.ID),
|
||||
fmt.Sprintf("/api/v2/chats/%s/messages/not-an-int", chat.ID),
|
||||
codersdk.EditChatMessageRequest{
|
||||
Content: []codersdk.ChatInputPart{
|
||||
{
|
||||
@@ -9787,7 +9787,7 @@ func TestStreamChat(t *testing.T) {
|
||||
res, err := unauthenticatedClient.Request(
|
||||
ctx,
|
||||
http.MethodGet,
|
||||
fmt.Sprintf("/api/experimental/chats/%s/stream", uuid.New()),
|
||||
fmt.Sprintf("/api/v2/chats/%s/stream", uuid.New()),
|
||||
nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
@@ -11016,7 +11016,7 @@ func TestDeleteChatQueuedMessage(t *testing.T) {
|
||||
res, err := client.Request(
|
||||
ctx,
|
||||
http.MethodDelete,
|
||||
fmt.Sprintf("/api/experimental/chats/%s/queue/%d", chat.ID, queuedMessage.ID),
|
||||
fmt.Sprintf("/api/v2/chats/%s/queue/%d", chat.ID, queuedMessage.ID),
|
||||
nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
@@ -11054,7 +11054,7 @@ func TestDeleteChatQueuedMessage(t *testing.T) {
|
||||
invalidRes, err := client.Request(
|
||||
ctx,
|
||||
http.MethodDelete,
|
||||
fmt.Sprintf("/api/experimental/chats/%s/queue/not-an-int", chat.ID),
|
||||
fmt.Sprintf("/api/v2/chats/%s/queue/not-an-int", chat.ID),
|
||||
nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
@@ -11097,7 +11097,7 @@ func TestPromoteChatQueuedMessage(t *testing.T) {
|
||||
promoteRes, err := client.Request(
|
||||
ctx,
|
||||
http.MethodPost,
|
||||
fmt.Sprintf("/api/experimental/chats/%s/queue/%d/promote", chat.ID, queuedMessage.ID),
|
||||
fmt.Sprintf("/api/v2/chats/%s/queue/%d/promote", chat.ID, queuedMessage.ID),
|
||||
nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
@@ -11161,7 +11161,7 @@ func TestPromoteChatQueuedMessage(t *testing.T) {
|
||||
promoteRes, err := memberClient.Request(
|
||||
ctx,
|
||||
http.MethodPost,
|
||||
fmt.Sprintf("/api/experimental/chats/%s/queue/%d/promote", chat.ID, queuedMessage.ID),
|
||||
fmt.Sprintf("/api/v2/chats/%s/queue/%d/promote", chat.ID, queuedMessage.ID),
|
||||
nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
@@ -11198,7 +11198,7 @@ func TestPromoteChatQueuedMessage(t *testing.T) {
|
||||
invalidRes, err := client.Request(
|
||||
ctx,
|
||||
http.MethodPost,
|
||||
fmt.Sprintf("/api/experimental/chats/%s/queue/not-an-int/promote", chat.ID),
|
||||
fmt.Sprintf("/api/v2/chats/%s/queue/not-an-int/promote", chat.ID),
|
||||
nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
@@ -11237,7 +11237,7 @@ func TestPromoteChatQueuedMessage(t *testing.T) {
|
||||
promoteRes, err := memberClient.Request(
|
||||
ctx,
|
||||
http.MethodPost,
|
||||
fmt.Sprintf("/api/experimental/chats/%s/queue/%d/promote", chat.ID, queuedMessage.ID),
|
||||
fmt.Sprintf("/api/v2/chats/%s/queue/%d/promote", chat.ID, queuedMessage.ID),
|
||||
nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
@@ -11273,7 +11273,7 @@ func TestPromoteChatQueuedMessage(t *testing.T) {
|
||||
promoteRes, err := client.Request(
|
||||
ctx,
|
||||
http.MethodPost,
|
||||
fmt.Sprintf("/api/experimental/chats/%s/queue/%d/promote", chat.ID, queuedMessage.ID),
|
||||
fmt.Sprintf("/api/v2/chats/%s/queue/%d/promote", chat.ID, queuedMessage.ID),
|
||||
nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
@@ -11358,7 +11358,7 @@ func TestPromoteChatQueuedMessage(t *testing.T) {
|
||||
promoteRes, err := client.Request(
|
||||
ctx,
|
||||
http.MethodPost,
|
||||
fmt.Sprintf("/api/experimental/chats/%s/queue/%d/promote", chat.ID, queuedMessage.ID),
|
||||
fmt.Sprintf("/api/v2/chats/%s/queue/%d/promote", chat.ID, queuedMessage.ID),
|
||||
nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
@@ -11451,7 +11451,7 @@ func TestPromoteChatQueuedMessage(t *testing.T) {
|
||||
promoteRes, err := client.Request(
|
||||
ctx,
|
||||
http.MethodPost,
|
||||
fmt.Sprintf("/api/experimental/chats/%s/queue/%d/promote", chat.ID, queuedMessage.ID),
|
||||
fmt.Sprintf("/api/v2/chats/%s/queue/%d/promote", chat.ID, queuedMessage.ID),
|
||||
nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
@@ -11695,7 +11695,7 @@ This arrived as octet-stream.
|
||||
coderdtest.CreateFirstUser(t, client.Client)
|
||||
|
||||
data := append([]byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}, make([]byte, 64)...)
|
||||
res, err := client.Request(ctx, http.MethodPost, "/api/experimental/chats/files", bytes.NewReader(data), func(r *http.Request) {
|
||||
res, err := client.Request(ctx, http.MethodPost, "/api/v2/chats/files", bytes.NewReader(data), func(r *http.Request) {
|
||||
r.Header.Set("Content-Type", "image/png")
|
||||
})
|
||||
|
||||
@@ -11713,7 +11713,7 @@ This arrived as octet-stream.
|
||||
coderdtest.CreateFirstUser(t, client.Client)
|
||||
|
||||
data := append([]byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}, make([]byte, 64)...)
|
||||
res, err := client.Request(ctx, http.MethodPost, "/api/experimental/chats/files?organization=not-a-uuid", bytes.NewReader(data), func(r *http.Request) {
|
||||
res, err := client.Request(ctx, http.MethodPost, "/api/v2/chats/files?organization=not-a-uuid", bytes.NewReader(data), func(r *http.Request) {
|
||||
r.Header.Set("Content-Type", "image/png")
|
||||
})
|
||||
require.NoError(t, err)
|
||||
@@ -11799,7 +11799,7 @@ func TestGetChatFile(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
res, err := client.Request(ctx, http.MethodGet,
|
||||
fmt.Sprintf("/api/experimental/chats/files/%s", uploaded.ID), nil)
|
||||
fmt.Sprintf("/api/v2/chats/files/%s", uploaded.ID), nil)
|
||||
require.NoError(t, err)
|
||||
defer res.Body.Close()
|
||||
require.Equal(t, http.StatusOK, res.StatusCode)
|
||||
@@ -11819,7 +11819,7 @@ func TestGetChatFile(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
res, err := client.Request(ctx, http.MethodGet,
|
||||
fmt.Sprintf("/api/experimental/chats/files/%s", uploaded.ID), nil)
|
||||
fmt.Sprintf("/api/v2/chats/files/%s", uploaded.ID), nil)
|
||||
require.NoError(t, err)
|
||||
defer res.Body.Close()
|
||||
require.Equal(t, http.StatusOK, res.StatusCode)
|
||||
@@ -11842,7 +11842,7 @@ func TestGetChatFile(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
res, err := client.Request(ctx, http.MethodGet,
|
||||
fmt.Sprintf("/api/experimental/chats/files/%s", uploaded.ID), nil)
|
||||
fmt.Sprintf("/api/v2/chats/files/%s", uploaded.ID), nil)
|
||||
require.NoError(t, err)
|
||||
defer res.Body.Close()
|
||||
require.Equal(t, http.StatusOK, res.StatusCode)
|
||||
@@ -11874,7 +11874,7 @@ func TestGetChatFile(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
res, err := client.Request(ctx, http.MethodGet,
|
||||
fmt.Sprintf("/api/experimental/chats/files/%s", row.ID), nil)
|
||||
fmt.Sprintf("/api/v2/chats/files/%s", row.ID), nil)
|
||||
require.NoError(t, err)
|
||||
defer res.Body.Close()
|
||||
require.Equal(t, http.StatusOK, res.StatusCode)
|
||||
@@ -11903,7 +11903,7 @@ func TestGetChatFile(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
res, err := client.Request(ctx, http.MethodGet,
|
||||
fmt.Sprintf("/api/experimental/chats/files/%s", uploaded.ID), nil)
|
||||
fmt.Sprintf("/api/v2/chats/files/%s", uploaded.ID), nil)
|
||||
require.NoError(t, err)
|
||||
defer res.Body.Close()
|
||||
require.Equal(t, http.StatusOK, res.StatusCode)
|
||||
@@ -11927,7 +11927,7 @@ func TestGetChatFile(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
res, err := client.Request(ctx, http.MethodGet,
|
||||
fmt.Sprintf("/api/experimental/chats/files/%s", uploaded.ID), nil)
|
||||
fmt.Sprintf("/api/v2/chats/files/%s", uploaded.ID), nil)
|
||||
require.NoError(t, err)
|
||||
defer res.Body.Close()
|
||||
require.Equal(t, http.StatusOK, res.StatusCode)
|
||||
@@ -11955,7 +11955,7 @@ func TestGetChatFile(t *testing.T) {
|
||||
coderdtest.CreateFirstUser(t, client.Client)
|
||||
|
||||
res, err := client.Request(ctx, http.MethodGet,
|
||||
"/api/experimental/chats/files/not-a-uuid", nil)
|
||||
"/api/v2/chats/files/not-a-uuid", nil)
|
||||
require.NoError(t, err)
|
||||
defer res.Body.Close()
|
||||
err = codersdk.ReadBodyAsError(res)
|
||||
@@ -12049,7 +12049,7 @@ func TestChatFileDownloadURL(t *testing.T) {
|
||||
UserID: firstUser.UserID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
downloadURL := client.URL.JoinPath("api", "experimental", "chats", "files", uploaded.ID.String(), "download")
|
||||
downloadURL := client.URL.JoinPath("api", "v2", "chats", "files", uploaded.ID.String(), "download")
|
||||
query := downloadURL.Query()
|
||||
query.Set("token", token)
|
||||
downloadURL.RawQuery = query.Encode()
|
||||
@@ -12095,7 +12095,7 @@ func TestChatFileDownloadURL(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
downloadURL, err := url.Parse(download.URL)
|
||||
require.NoError(t, err)
|
||||
downloadURL.Path = fmt.Sprintf("/api/experimental/chats/files/%s/download", fileB.ID)
|
||||
downloadURL.Path = fmt.Sprintf("/api/v2/chats/files/%s/download", fileB.ID)
|
||||
|
||||
res := get(t, ctx, downloadURL.String())
|
||||
defer res.Body.Close()
|
||||
@@ -12720,7 +12720,7 @@ func TestWatchChatGitAuthz(t *testing.T) {
|
||||
res, err := adminClient.Request(
|
||||
ctx,
|
||||
http.MethodGet,
|
||||
fmt.Sprintf("/api/experimental/chats/%s/stream/git", chat.ID),
|
||||
fmt.Sprintf("/api/v2/chats/%s/stream/git", chat.ID),
|
||||
nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
@@ -14466,7 +14466,7 @@ func TestChatModelOverrides(t *testing.T) {
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
adminClient := newChatClient(t)
|
||||
coderdtest.CreateFirstUser(t, adminClient.Client)
|
||||
res, err := adminClient.Request(ctx, http.MethodGet, "/api/experimental/chats/config/model-override/general", nil)
|
||||
res, err := adminClient.Request(ctx, http.MethodGet, "/api/v2/chats/config/model-override/general", nil)
|
||||
require.NoError(t, err)
|
||||
defer res.Body.Close()
|
||||
require.Equal(t, http.StatusNotFound, res.StatusCode)
|
||||
@@ -14742,7 +14742,7 @@ func TestUserChatPersonalModelOverrides(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("LegacyRouteRemoved", func(t *testing.T) {
|
||||
res, err := memberClient.Request(ctx, http.MethodGet, "/api/experimental/chats/config/user-personal-model-overrides", nil)
|
||||
res, err := memberClient.Request(ctx, http.MethodGet, "/api/v2/chats/config/user-personal-model-overrides", nil)
|
||||
require.NoError(t, err)
|
||||
defer res.Body.Close()
|
||||
require.Equal(t, http.StatusNotFound, res.StatusCode)
|
||||
@@ -15626,7 +15626,7 @@ func TestChatAdvisorConfig_StaleModelWriteRejected(t *testing.T) {
|
||||
} {
|
||||
err := adminClient.UpdateChatAdvisorConfig(ctx, req)
|
||||
sdkErr := requireSDKError(t, err, http.StatusBadRequest)
|
||||
require.Equal(t, "Advisor model settings moved to PUT /api/experimental/organizations/{organization}/chats/model-overrides/advisor.", sdkErr.Message)
|
||||
require.Equal(t, "Advisor model settings moved to PUT /api/v2/organizations/{organization}/chats/model-overrides/advisor.", sdkErr.Message)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16647,7 +16647,7 @@ func TestSubmitToolResults(t *testing.T) {
|
||||
// request lets the invalid payload reach the server so we
|
||||
// can verify server-side validation.
|
||||
rawBody := `{"results":[{"tool_call_id":"call_json","output":not-json,"is_error":false}]}`
|
||||
url := client.URL.JoinPath(fmt.Sprintf("/api/experimental/chats/%s/tool-results", chat.ID)).String()
|
||||
url := client.URL.JoinPath(fmt.Sprintf("/api/v2/chats/%s/tool-results", chat.ID)).String()
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBufferString(rawBody))
|
||||
require.NoError(t, err)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
@@ -17155,7 +17155,7 @@ func TestGetChatMessages_Pagination(t *testing.T) {
|
||||
res, err := client.Request(
|
||||
ctx,
|
||||
http.MethodGet,
|
||||
fmt.Sprintf("/api/experimental/chats/%s/messages?after_id=-1", chat.ID),
|
||||
fmt.Sprintf("/api/v2/chats/%s/messages?after_id=-1", chat.ID),
|
||||
nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
@@ -17186,7 +17186,7 @@ func TestGetChatMessages_Pagination(t *testing.T) {
|
||||
res, err := client.Request(
|
||||
ctx,
|
||||
http.MethodGet,
|
||||
fmt.Sprintf("/api/experimental/chats/%s/messages?after_id=abc", chat.ID),
|
||||
fmt.Sprintf("/api/v2/chats/%s/messages?after_id=abc", chat.ID),
|
||||
nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
@@ -17461,7 +17461,7 @@ func TestChatReadOnlySharedWriteHandlers(t *testing.T) {
|
||||
res, err := sharedClient.Request(
|
||||
ctx,
|
||||
http.MethodPost,
|
||||
fmt.Sprintf("/api/experimental/chats/%s/queue/%d/promote", chat.ID, queuedMessage.ID),
|
||||
fmt.Sprintf("/api/v2/chats/%s/queue/%d/promote", chat.ID, queuedMessage.ID),
|
||||
nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
@@ -17496,7 +17496,7 @@ func TestChatReadOnlySharedWriteHandlers(t *testing.T) {
|
||||
res, err := sharedClient.Request(
|
||||
ctx,
|
||||
http.MethodDelete,
|
||||
fmt.Sprintf("/api/experimental/chats/%s/queue/%d", chat.ID, queuedMessage.ID),
|
||||
fmt.Sprintf("/api/v2/chats/%s/queue/%d", chat.ID, queuedMessage.ID),
|
||||
nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
@@ -17650,7 +17650,7 @@ func TestChatOwnerOnlyWriteHandlers(t *testing.T) {
|
||||
promoteRes, err := adminClient.Request(
|
||||
ctx,
|
||||
http.MethodPost,
|
||||
fmt.Sprintf("/api/experimental/chats/%s/queue/%d/promote", chat.ID, queuedMessage.ID),
|
||||
fmt.Sprintf("/api/v2/chats/%s/queue/%d/promote", chat.ID, queuedMessage.ID),
|
||||
nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"net/http"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
@@ -23,16 +22,6 @@ import (
|
||||
// RateLimit returns a handler that limits requests per-minute based
|
||||
// on IP, endpoint, and user ID (if available).
|
||||
func RateLimit(count int, window time.Duration) func(http.Handler) http.Handler {
|
||||
return rateLimitWithEndpointKey(count, window, keyByNormalizedEndpoint)
|
||||
}
|
||||
|
||||
// RateLimitByAPICompatibilityEndpoint shares rate-limit buckets for matching
|
||||
// endpoints under the /api/v2 and /api/experimental compatibility prefixes.
|
||||
func RateLimitByAPICompatibilityEndpoint(count int, window time.Duration) func(http.Handler) http.Handler {
|
||||
return rateLimitWithEndpointKey(count, window, keyByAPICompatibilityEndpoint)
|
||||
}
|
||||
|
||||
func rateLimitWithEndpointKey(count int, window time.Duration, endpointKey func(*http.Request) (string, error)) func(http.Handler) http.Handler {
|
||||
// -1 is no rate limit
|
||||
if count <= 0 {
|
||||
return func(handler http.Handler) http.Handler {
|
||||
@@ -97,7 +86,7 @@ func rateLimitWithEndpointKey(count int, window time.Duration, endpointKey func(
|
||||
"%q provided but user is not %v",
|
||||
codersdk.BypassRatelimitHeader, rbac.RoleOwner(),
|
||||
)
|
||||
}, endpointKey),
|
||||
}, keyByNormalizedEndpoint),
|
||||
httprate.WithLimitHandler(func(w http.ResponseWriter, r *http.Request) {
|
||||
httpapi.Write(r.Context(), w, http.StatusTooManyRequests, codersdk.Response{
|
||||
Message: fmt.Sprintf("You've been rate limited for sending more than %v requests in %v.", count, window),
|
||||
@@ -121,22 +110,6 @@ func keyByNormalizedEndpoint(r *http.Request) (string, error) {
|
||||
return path.Clean(p), nil
|
||||
}
|
||||
|
||||
func keyByAPICompatibilityEndpoint(r *http.Request) (string, error) {
|
||||
p, err := keyByNormalizedEndpoint(r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for _, prefix := range []string{"/api/v2", "/api/experimental"} {
|
||||
if p == prefix {
|
||||
return "/", nil
|
||||
}
|
||||
if strings.HasPrefix(p, prefix+"/") {
|
||||
return strings.TrimPrefix(p, prefix), nil
|
||||
}
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// RateLimitByAuthToken returns a handler that limits requests based on the
|
||||
// authentication token in the request.
|
||||
//
|
||||
|
||||
@@ -201,30 +201,6 @@ func TestRateLimit(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestRateLimitByAPICompatibilityEndpoint(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
rtr := chi.NewRouter()
|
||||
rtr.Use(httpmw.RateLimitByAPICompatibilityEndpoint(1, time.Second))
|
||||
rtr.Get("/*", func(rw http.ResponseWriter, r *http.Request) {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
remoteAddr := randRemoteAddr()
|
||||
for i, p := range []string{
|
||||
"/api/v2/chats/files/00000000-0000-0000-0000-000000000000",
|
||||
"/api/experimental/chats/files/00000000-0000-0000-0000-000000000000",
|
||||
} {
|
||||
req := httptest.NewRequest("GET", p, nil)
|
||||
req.RemoteAddr = remoteAddr
|
||||
rec := httptest.NewRecorder()
|
||||
rtr.ServeHTTP(rec, req)
|
||||
resp := rec.Result()
|
||||
_ = resp.Body.Close()
|
||||
require.Equal(t, i != 0, resp.StatusCode == http.StatusTooManyRequests, p)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRateLimitByAuthToken(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -1567,8 +1567,6 @@ func (api *API) markMCPTokenRefreshFailure(
|
||||
// external authorization servers, so it must not change when other MCP
|
||||
// routes move. The route registration in coderd.go and the OAuth cookie
|
||||
// Path values must stay aligned with it.
|
||||
// TODO(CODAGT-922): define a migration story before moving registered
|
||||
// redirect URIs to /api/v2.
|
||||
func mcpServerOAuth2CallbackPath(configID uuid.UUID) string {
|
||||
return fmt.Sprintf("/api/experimental/mcp/servers/%s/oauth2/callback", configID)
|
||||
}
|
||||
|
||||
@@ -575,7 +575,7 @@ func Test_Middleware(t *testing.T) {
|
||||
)
|
||||
|
||||
rw := &tracing.StatusWriter{ResponseWriter: httptest.NewRecorder()}
|
||||
r := httptest.NewRequest("GET", "/api/experimental/chats/files/abc/download?token="+token, nil)
|
||||
r := httptest.NewRequest("GET", "/api/v2/chats/files/abc/download?token="+token, nil)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
@@ -427,6 +427,8 @@ This section maps the public endpoints that mutate chat state to the transitions
|
||||
|
||||
Chat routes are registered once by `registerChatAPIRoutes` and mounted under both `/api/experimental` and `/api/v2` during a compatibility window. Paths in this document are written with one prefix or the other, but every promoted route answers on both. The routes that were not promoted answer only on `/api/experimental`; at the time of writing these are the `providers` and `user-provider-configs` collections under `/chats`, the `computer-use-provider` and `advisor` routes under `/chats/config`, `GET /chats/{chat}/stream/desktop`, and `GET /chats/{chat}/debug/runs` with `GET /chats/{chat}/debug/runs/{debugRun}`. Each mount reserves the top-level `/chats/<segment>` collection paths it does not serve (`model-configs`, `providers`, and `user-provider-configs` on `/api/v2`; `models` and `model-configs` on `/api/experimental`) so they return 404 instead of matching the `{chat}` wildcard and failing UUID parsing.
|
||||
|
||||
TODO: CODAGT-922 removed the `/api/experimental` duplicates, the tombstone `providers` and `user-provider-configs` routes, and the reservations on the experimental mount; promoted routes now answer only on `/api/v2`. Update the paragraph above and the `/api/experimental` route headings in this section.
|
||||
|
||||
### Organization-scoped model discovery
|
||||
|
||||
Clients discover models through `GET /api/v2/organizations/{organization}/chats/models`. The handler requires either full API token scope or chat model configuration read scope, then queries only configs in the requested organization that pass the caller's RBAC filter.
|
||||
|
||||
@@ -1227,31 +1227,6 @@ type ChatProviderConfig struct {
|
||||
UpdatedAt time.Time `json:"updated_at,omitempty" format:"date-time"`
|
||||
}
|
||||
|
||||
// CreateChatProviderConfigRequest creates a chat provider config.
|
||||
type CreateChatProviderConfigRequest struct {
|
||||
Provider string `json:"provider"`
|
||||
DisplayName string `json:"display_name,omitempty"`
|
||||
Icon string `json:"icon,omitempty"`
|
||||
APIKey string `json:"api_key,omitempty"`
|
||||
BaseURL string `json:"base_url,omitempty"`
|
||||
Enabled *bool `json:"enabled,omitempty"`
|
||||
CentralAPIKeyEnabled *bool `json:"central_api_key_enabled,omitempty"`
|
||||
AllowUserAPIKey *bool `json:"allow_user_api_key,omitempty"`
|
||||
AllowCentralAPIKeyFallback *bool `json:"allow_central_api_key_fallback,omitempty"`
|
||||
}
|
||||
|
||||
// UpdateChatProviderConfigRequest updates a chat provider config.
|
||||
type UpdateChatProviderConfigRequest struct {
|
||||
DisplayName string `json:"display_name,omitempty"`
|
||||
Icon string `json:"icon,omitempty"`
|
||||
APIKey *string `json:"api_key,omitempty"`
|
||||
BaseURL *string `json:"base_url,omitempty"`
|
||||
Enabled *bool `json:"enabled,omitempty"`
|
||||
CentralAPIKeyEnabled *bool `json:"central_api_key_enabled,omitempty"`
|
||||
AllowUserAPIKey *bool `json:"allow_user_api_key,omitempty"`
|
||||
AllowCentralAPIKeyFallback *bool `json:"allow_central_api_key_fallback,omitempty"`
|
||||
}
|
||||
|
||||
// AIProviderSummary is provider metadata embedded in other API responses.
|
||||
type AIProviderSummary struct {
|
||||
ID uuid.UUID `json:"id" format:"uuid"`
|
||||
@@ -2027,64 +2002,6 @@ func (c *Client) ListChats(ctx context.Context, opts *ListChatsOptions) ([]Chat,
|
||||
return chats, ReadBodyAsJSON(res, &chats)
|
||||
}
|
||||
|
||||
// ListChatProviders returns admin-managed chat provider configs.
|
||||
func (c *ExperimentalClient) ListChatProviders(ctx context.Context) ([]ChatProviderConfig, error) {
|
||||
res, err := c.Request(ctx, http.MethodGet, "/api/experimental/chats/providers", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return nil, ReadBodyAsError(res)
|
||||
}
|
||||
|
||||
var providers []ChatProviderConfig
|
||||
return providers, ReadBodyAsJSON(res, &providers)
|
||||
}
|
||||
|
||||
// CreateChatProvider creates an admin-managed chat provider config.
|
||||
func (c *ExperimentalClient) CreateChatProvider(ctx context.Context, req CreateChatProviderConfigRequest) (ChatProviderConfig, error) {
|
||||
res, err := c.Request(ctx, http.MethodPost, "/api/experimental/chats/providers", req)
|
||||
if err != nil {
|
||||
return ChatProviderConfig{}, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusCreated {
|
||||
return ChatProviderConfig{}, ReadBodyAsError(res)
|
||||
}
|
||||
|
||||
var provider ChatProviderConfig
|
||||
return provider, ReadBodyAsJSON(res, &provider)
|
||||
}
|
||||
|
||||
// UpdateChatProvider updates an admin-managed chat provider config.
|
||||
func (c *ExperimentalClient) UpdateChatProvider(ctx context.Context, providerID uuid.UUID, req UpdateChatProviderConfigRequest) (ChatProviderConfig, error) {
|
||||
res, err := c.Request(ctx, http.MethodPatch, fmt.Sprintf("/api/experimental/chats/providers/%s", providerID), req)
|
||||
if err != nil {
|
||||
return ChatProviderConfig{}, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return ChatProviderConfig{}, ReadBodyAsError(res)
|
||||
}
|
||||
|
||||
var provider ChatProviderConfig
|
||||
return provider, ReadBodyAsJSON(res, &provider)
|
||||
}
|
||||
|
||||
// DeleteChatProvider deletes an admin-managed chat provider config.
|
||||
func (c *ExperimentalClient) DeleteChatProvider(ctx context.Context, providerID uuid.UUID) error {
|
||||
res, err := c.Request(ctx, http.MethodDelete, fmt.Sprintf("/api/experimental/chats/providers/%s", providerID), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusNoContent {
|
||||
return ReadBodyAsError(res)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListUserAIProviderKeyConfigs returns user-scoped AI provider key configs.
|
||||
func (c *Client) ListUserAIProviderKeyConfigs(ctx context.Context, user string) ([]UserAIProviderKeyConfig, error) {
|
||||
res, err := c.Request(ctx, http.MethodGet, userAIProviderKeysPath(user), nil)
|
||||
@@ -2130,47 +2047,6 @@ func userAIProviderKeysPath(user string) string {
|
||||
return fmt.Sprintf("/api/v2/users/%s/ai-provider-keys", url.PathEscape(user))
|
||||
}
|
||||
|
||||
// ListUserChatProviderConfigs returns user-scoped chat provider configs.
|
||||
func (c *ExperimentalClient) ListUserChatProviderConfigs(ctx context.Context) ([]UserChatProviderConfig, error) {
|
||||
res, err := c.Request(ctx, http.MethodGet, "/api/experimental/chats/user-provider-configs", nil)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("list user chat provider configs: %w", err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return nil, ReadBodyAsError(res)
|
||||
}
|
||||
var configs []UserChatProviderConfig
|
||||
return configs, ReadBodyAsJSON(res, &configs)
|
||||
}
|
||||
|
||||
// UpsertUserChatProviderKey creates or replaces a user API key for a provider.
|
||||
func (c *ExperimentalClient) UpsertUserChatProviderKey(ctx context.Context, providerID uuid.UUID, req CreateUserChatProviderKeyRequest) (UserChatProviderConfig, error) {
|
||||
res, err := c.Request(ctx, http.MethodPut, fmt.Sprintf("/api/experimental/chats/user-provider-configs/%s", providerID), req)
|
||||
if err != nil {
|
||||
return UserChatProviderConfig{}, xerrors.Errorf("upsert user chat provider key: %w", err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return UserChatProviderConfig{}, ReadBodyAsError(res)
|
||||
}
|
||||
var config UserChatProviderConfig
|
||||
return config, ReadBodyAsJSON(res, &config)
|
||||
}
|
||||
|
||||
// DeleteUserChatProviderKey deletes a user API key for a provider.
|
||||
func (c *ExperimentalClient) DeleteUserChatProviderKey(ctx context.Context, providerID uuid.UUID) error {
|
||||
res, err := c.Request(ctx, http.MethodDelete, fmt.Sprintf("/api/experimental/chats/user-provider-configs/%s", providerID), nil)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("delete user chat provider key: %w", err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusNoContent {
|
||||
return ReadBodyAsError(res)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ChatModels returns the chat model configs the caller can read in one
|
||||
// organization, plus the redacted provider descriptors the authoring page
|
||||
// needs, for org-scoped management and picker surfaces.
|
||||
|
||||
@@ -50,4 +50,4 @@ Failed advisor invocations refund the per-turn budget.
|
||||
The same configuration is available through the API: runtime limits at
|
||||
`GET`/`PUT` `/api/experimental/chats/config/advisor`, and the advisor
|
||||
model override at
|
||||
`PUT /api/experimental/organizations/{organization}/chats/model-overrides/advisor`.
|
||||
`PUT /api/v2/organizations/{organization}/chats/model-overrides/advisor`.
|
||||
|
||||
@@ -123,7 +123,7 @@ func TestMCPServerConfigUpdateOnlyRoleReachesACLExcludedConfigs(t *testing.T) {
|
||||
require.Equal(t, disabled.URL, fetched.URL)
|
||||
|
||||
requireMCPServerConfigRequestStatus(t, updateOnly, http.MethodGet,
|
||||
"/api/experimental/organizations/"+firstUser.OrganizationID.String()+"/mcp-servers/"+disabled.ID.String()+"/oauth2/connect",
|
||||
"/api/v2/organizations/"+firstUser.OrganizationID.String()+"/mcp-servers/"+disabled.ID.String()+"/oauth2/connect",
|
||||
nil, http.StatusNotFound)
|
||||
|
||||
updatedName := "updated-hidden-mcp"
|
||||
@@ -187,7 +187,7 @@ func TestMCPServerConfigDeleteOnlyRoleReachesDisabled(t *testing.T) {
|
||||
require.Empty(t, fetched.URL)
|
||||
|
||||
requireMCPServerConfigRequestStatus(t, deleteOnly, http.MethodGet,
|
||||
"/api/experimental/organizations/"+firstUser.OrganizationID.String()+"/mcp-servers/"+disabled.ID.String()+"/oauth2/connect",
|
||||
"/api/v2/organizations/"+firstUser.OrganizationID.String()+"/mcp-servers/"+disabled.ID.String()+"/oauth2/connect",
|
||||
nil, http.StatusNotFound)
|
||||
|
||||
err = deleteOnly.DeleteMCPServerConfig(ctx, firstUser.OrganizationID, disabled.ID)
|
||||
@@ -270,7 +270,7 @@ func TestMCPServerConfigShareOnlyRoleRoutes(t *testing.T) {
|
||||
err = shareOnly.UpdateMCPServerConfigACL(ctx, firstUser.OrganizationID, config.ID, codersdk.UpdateMCPServerConfigACLRequest{})
|
||||
require.NoError(t, err)
|
||||
|
||||
configPath := "/api/experimental/organizations/" + firstUser.OrganizationID.String() + "/mcp-servers/" + config.ID.String()
|
||||
configPath := "/api/v2/organizations/" + firstUser.OrganizationID.String() + "/mcp-servers/" + config.ID.String()
|
||||
requireMCPServerConfigRequestStatus(t, shareOnly, http.MethodGet, configPath, nil, http.StatusNotFound)
|
||||
requireMCPServerConfigRequestStatus(t, shareOnly, http.MethodGet, configPath+"/oauth2/connect", nil, http.StatusNotFound)
|
||||
requireMCPServerConfigRequestStatus(t, shareOnly, http.MethodPatch, configPath,
|
||||
@@ -329,7 +329,7 @@ func TestMCPServerConfigReadOnlyRoleCanConnect(t *testing.T) {
|
||||
}
|
||||
|
||||
requireMCPServerConfigRequestStatus(t, readOnly, http.MethodGet,
|
||||
"/api/experimental/organizations/"+firstUser.OrganizationID.String()+"/mcp-servers/"+config.ID.String()+"/oauth2/connect",
|
||||
"/api/v2/organizations/"+firstUser.OrganizationID.String()+"/mcp-servers/"+config.ID.String()+"/oauth2/connect",
|
||||
nil, http.StatusTemporaryRedirect)
|
||||
}
|
||||
|
||||
@@ -380,7 +380,7 @@ func TestMCPServerConfigItemCrossOrganizationConcealment(t *testing.T) {
|
||||
secondOrg := coderdenttest.CreateOrganization(t, client, coderdenttest.CreateOrganizationOptions{})
|
||||
otherClient, _ := coderdtest.CreateAnotherUser(t, client, secondOrg.ID)
|
||||
config := createMCPServerConfigForOrganization(t, client, firstUser.OrganizationID, "private-org-one-mcp")
|
||||
organizationPath := "/api/experimental/organizations/" + secondOrg.ID.String() + "/mcp-servers/" + config.ID.String()
|
||||
organizationPath := "/api/v2/organizations/" + secondOrg.ID.String() + "/mcp-servers/" + config.ID.String()
|
||||
frozenPath := "/api/experimental/mcp/servers/" + config.ID.String()
|
||||
mAudit.ResetLogs()
|
||||
|
||||
@@ -399,7 +399,7 @@ func TestMCPServerConfigItemCrossOrganizationConcealment(t *testing.T) {
|
||||
// Disconnect returns 200 for every caller without a token,
|
||||
// including nonexistent config IDs, so the response does not
|
||||
// reveal whether the config exists.
|
||||
{name: "OAuthDisconnect", method: http.MethodDelete, path: frozenPath + "/oauth2/disconnect", wantStatus: http.StatusOK},
|
||||
{name: "OAuthDisconnect", method: http.MethodDelete, path: "/api/v2/mcp/servers/" + config.ID.String() + "/oauth2/disconnect", wantStatus: http.StatusOK},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
@@ -426,7 +426,7 @@ func TestMCPServerConfigItemCrossOrganizationConcealment(t *testing.T) {
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
rawDisconnect := func(id uuid.UUID) (int, string) {
|
||||
res, err := otherClient.Request(ctx, http.MethodDelete,
|
||||
"/api/experimental/mcp/servers/"+id.String()+"/oauth2/disconnect", nil)
|
||||
"/api/v2/mcp/servers/"+id.String()+"/oauth2/disconnect", nil)
|
||||
require.NoError(t, err)
|
||||
defer res.Body.Close()
|
||||
body, err := io.ReadAll(res.Body)
|
||||
|
||||
Generated
-31
@@ -3861,22 +3861,6 @@ export interface CreateChatModelRequest {
|
||||
readonly model_config?: ChatModelCallConfig;
|
||||
}
|
||||
|
||||
// From codersdk/chats.go
|
||||
/**
|
||||
* CreateChatProviderConfigRequest creates a chat provider config.
|
||||
*/
|
||||
export interface CreateChatProviderConfigRequest {
|
||||
readonly provider: string;
|
||||
readonly display_name?: string;
|
||||
readonly icon?: string;
|
||||
readonly api_key?: string;
|
||||
readonly base_url?: string;
|
||||
readonly enabled?: boolean;
|
||||
readonly central_api_key_enabled?: boolean;
|
||||
readonly allow_user_api_key?: boolean;
|
||||
readonly allow_central_api_key_fallback?: boolean;
|
||||
}
|
||||
|
||||
// From codersdk/chats.go
|
||||
/**
|
||||
* CreateChatRequest is the request to create a new chat.
|
||||
@@ -9820,21 +9804,6 @@ export interface UpdateChatPlanModeInstructionsRequest {
|
||||
readonly plan_mode_instructions: string;
|
||||
}
|
||||
|
||||
// From codersdk/chats.go
|
||||
/**
|
||||
* UpdateChatProviderConfigRequest updates a chat provider config.
|
||||
*/
|
||||
export interface UpdateChatProviderConfigRequest {
|
||||
readonly display_name?: string;
|
||||
readonly icon?: string;
|
||||
readonly api_key?: string;
|
||||
readonly base_url?: string;
|
||||
readonly enabled?: boolean;
|
||||
readonly central_api_key_enabled?: boolean;
|
||||
readonly allow_user_api_key?: boolean;
|
||||
readonly allow_central_api_key_fallback?: boolean;
|
||||
}
|
||||
|
||||
// From codersdk/chats.go
|
||||
/**
|
||||
* UpdateChatRequest is the request to update a chat.
|
||||
|
||||
Reference in New Issue
Block a user