mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
refactor: consolidate experimental chats API types (#23143)
## Summary
Consolidates three areas of type duplication in the experimental chats
API:
### 1. Merge archive/unarchive into `PATCH /{chat}`
- **Before:** `POST /{chat}/archive` + `POST /{chat}/unarchive` (two
endpoints, two handlers with mirrored logic)
- **After:** `PATCH /{chat}` accepting `{ "archived": true/false }` via
`UpdateChatRequest`
- Removes one endpoint and ~30 lines of duplicated handler code
### 2. Collapse identical request/response prompt types
- `ChatSystemPromptResponse` + `UpdateChatSystemPromptRequest` →
`ChatSystemPrompt`
- `UserChatCustomPromptResponse` + `UpdateUserChatCustomPromptRequest` →
`UserChatCustomPrompt`
- These pairs were field-for-field identical (single string field)
### 3. Merge duplicate reasoning options types
- `ChatModelOpenRouterReasoningOptions` +
`ChatModelVercelReasoningOptions` → `ChatModelReasoningOptions`
- Same 4 fields, same types — only field ordering and enum value sets
differed
- Unified type uses the superset of enum values
### Files changed
- `codersdk/chats.go` — SDK types and client methods
- `coderd/chats.go` — Handler consolidation
- `coderd/coderd.go` — Route change
- `coderd/chats_test.go` — Test updates
- `site/src/api/api.ts` — Frontend API client
- `site/src/api/queries/chats.ts` — Query mutations
- `site/src/api/queries/chats.test.ts` — Test mocks
- `site/src/pages/AgentsPage/AgentsPage.tsx` — Call site
- Generated files (`typesGenerated.ts`,
`chatModelOptionsGenerated.json`)
### Testing
- All Go tests pass (`TestArchiveChat`, `TestUnarchiveChat`,
`TestChatSystemPrompt`)
- All frontend tests pass (31/31 in `chats.test.ts`)
This commit is contained in:
@@ -82,7 +82,7 @@ func TestMergeMissingProviderOptions_OpenRouterNested(t *testing.T) {
|
||||
|
||||
options := &codersdk.ChatModelProviderOptions{
|
||||
OpenRouter: &codersdk.ChatModelOpenRouterProviderOptions{
|
||||
Reasoning: &codersdk.ChatModelOpenRouterReasoningOptions{
|
||||
Reasoning: &codersdk.ChatModelReasoningOptions{
|
||||
Enabled: boolPtr(true),
|
||||
},
|
||||
Provider: &codersdk.ChatModelOpenRouterProvider{
|
||||
@@ -92,7 +92,7 @@ func TestMergeMissingProviderOptions_OpenRouterNested(t *testing.T) {
|
||||
}
|
||||
defaults := &codersdk.ChatModelProviderOptions{
|
||||
OpenRouter: &codersdk.ChatModelOpenRouterProviderOptions{
|
||||
Reasoning: &codersdk.ChatModelOpenRouterReasoningOptions{
|
||||
Reasoning: &codersdk.ChatModelReasoningOptions{
|
||||
Enabled: boolPtr(false),
|
||||
Exclude: boolPtr(true),
|
||||
MaxTokens: int64Ptr(123),
|
||||
|
||||
+50
-56
@@ -1369,64 +1369,58 @@ func (api *API) watchChatDesktop(rw http.ResponseWriter, r *http.Request) {
|
||||
logger.Debug(ctx, "desktop Bicopy finished")
|
||||
}
|
||||
|
||||
// EXPERIMENTAL: this endpoint is experimental and is subject to change.
|
||||
func (api *API) archiveChat(rw http.ResponseWriter, r *http.Request) {
|
||||
// patchChat updates a chat resource. Currently supports toggling the
|
||||
// archived state via the Archived field.
|
||||
func (api *API) patchChat(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
chat := httpmw.ChatParam(r)
|
||||
|
||||
if chat.Archived {
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "Chat is already archived.",
|
||||
})
|
||||
var req codersdk.UpdateChatRequest
|
||||
if !httpapi.Read(ctx, rw, r, &req) {
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
// Use chatDaemon when available so it can notify
|
||||
// active subscribers. Fall back to direct DB for the
|
||||
// simple archive flag — no streaming state is involved.
|
||||
if api.chatDaemon != nil {
|
||||
err = api.chatDaemon.ArchiveChat(ctx, chat)
|
||||
} else {
|
||||
err = api.Database.ArchiveChatByID(ctx, chat.ID)
|
||||
}
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Failed to archive chat.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
if req.Archived != nil {
|
||||
archived := *req.Archived
|
||||
if archived == chat.Archived {
|
||||
state := "archived"
|
||||
if !archived {
|
||||
state = "not archived"
|
||||
}
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: fmt.Sprintf("Chat is already %s.", state),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
rw.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func (api *API) unarchiveChat(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
chat := httpmw.ChatParam(r)
|
||||
|
||||
if !chat.Archived {
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "Chat is not archived.",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
// Use chatDaemon when available so it can notify
|
||||
// active subscribers. Fall back to direct DB for the
|
||||
// simple unarchive flag — no streaming state is involved.
|
||||
if api.chatDaemon != nil {
|
||||
err = api.chatDaemon.UnarchiveChat(ctx, chat)
|
||||
} else {
|
||||
err = api.Database.UnarchiveChatByID(ctx, chat.ID)
|
||||
}
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Failed to unarchive chat.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
var err error
|
||||
// Use chatDaemon when available so it can notify active
|
||||
// subscribers. Fall back to direct DB for the simple
|
||||
// archive flag — no streaming state is involved.
|
||||
if archived {
|
||||
if api.chatDaemon != nil {
|
||||
err = api.chatDaemon.ArchiveChat(ctx, chat)
|
||||
} else {
|
||||
err = api.Database.ArchiveChatByID(ctx, chat.ID)
|
||||
}
|
||||
} else {
|
||||
if api.chatDaemon != nil {
|
||||
err = api.chatDaemon.UnarchiveChat(ctx, chat)
|
||||
} else {
|
||||
err = api.Database.UnarchiveChatByID(ctx, chat.ID)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
action := "archive"
|
||||
if !archived {
|
||||
action = "unarchive"
|
||||
}
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: fmt.Sprintf("Failed to %s chat.", action),
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
rw.WriteHeader(http.StatusNoContent)
|
||||
@@ -2525,14 +2519,14 @@ func (api *API) getChatSystemPrompt(rw http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
return
|
||||
}
|
||||
httpapi.Write(ctx, rw, http.StatusOK, codersdk.ChatSystemPromptResponse{
|
||||
httpapi.Write(ctx, rw, http.StatusOK, codersdk.ChatSystemPrompt{
|
||||
SystemPrompt: prompt,
|
||||
})
|
||||
}
|
||||
|
||||
func (api *API) putChatSystemPrompt(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
var req codersdk.UpdateChatSystemPromptRequest
|
||||
var req codersdk.ChatSystemPrompt
|
||||
if !httpapi.Read(ctx, rw, r, &req) {
|
||||
return
|
||||
}
|
||||
@@ -2582,7 +2576,7 @@ func (api *API) getUserChatCustomPrompt(rw http.ResponseWriter, r *http.Request)
|
||||
customPrompt = ""
|
||||
}
|
||||
|
||||
httpapi.Write(ctx, rw, http.StatusOK, codersdk.UserChatCustomPromptResponse{
|
||||
httpapi.Write(ctx, rw, http.StatusOK, codersdk.UserChatCustomPrompt{
|
||||
CustomPrompt: customPrompt,
|
||||
})
|
||||
}
|
||||
@@ -2594,7 +2588,7 @@ func (api *API) putUserChatCustomPrompt(rw http.ResponseWriter, r *http.Request)
|
||||
apiKey = httpmw.APIKey(r)
|
||||
)
|
||||
|
||||
var params codersdk.UpdateUserChatCustomPromptRequest
|
||||
var params codersdk.UserChatCustomPrompt
|
||||
if !httpapi.Read(ctx, rw, r, ¶ms) {
|
||||
return
|
||||
}
|
||||
@@ -2621,7 +2615,7 @@ func (api *API) putUserChatCustomPrompt(rw http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
httpapi.Write(ctx, rw, http.StatusOK, codersdk.UserChatCustomPromptResponse{
|
||||
httpapi.Write(ctx, rw, http.StatusOK, codersdk.UserChatCustomPrompt{
|
||||
CustomPrompt: updatedConfig.Value,
|
||||
})
|
||||
}
|
||||
|
||||
+12
-12
@@ -29,6 +29,7 @@ import (
|
||||
"github.com/coder/coder/v2/coderd/externalauth"
|
||||
coderdpubsub "github.com/coder/coder/v2/coderd/pubsub"
|
||||
"github.com/coder/coder/v2/coderd/rbac"
|
||||
"github.com/coder/coder/v2/coderd/util/ptr"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
"github.com/coder/coder/v2/testutil"
|
||||
"github.com/coder/websocket"
|
||||
@@ -1687,7 +1688,7 @@ func TestArchiveChat(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Len(t, chatsBeforeArchive, 2)
|
||||
|
||||
err = client.ArchiveChat(ctx, chatToArchive.ID)
|
||||
err = client.UpdateChat(ctx, chatToArchive.ID, codersdk.UpdateChatRequest{Archived: ptr.Ref(true)})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Default (no filter) returns only non-archived chats.
|
||||
@@ -1721,7 +1722,7 @@ func TestArchiveChat(t *testing.T) {
|
||||
client := newChatClient(t)
|
||||
_ = coderdtest.CreateFirstUser(t, client)
|
||||
|
||||
err := client.ArchiveChat(ctx, uuid.New())
|
||||
err := client.UpdateChat(ctx, uuid.New(), codersdk.UpdateChatRequest{Archived: ptr.Ref(true)})
|
||||
requireSDKError(t, err, http.StatusNotFound)
|
||||
})
|
||||
|
||||
@@ -1764,7 +1765,7 @@ func TestArchiveChat(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
// Archive the parent via the API.
|
||||
err = client.ArchiveChat(ctx, parentChat.ID)
|
||||
err = client.UpdateChat(ctx, parentChat.ID, codersdk.UpdateChatRequest{Archived: ptr.Ref(true)})
|
||||
require.NoError(t, err)
|
||||
|
||||
// archived:false should exclude the entire archived family.
|
||||
@@ -1811,7 +1812,7 @@ func TestUnarchiveChat(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
// Archive the chat first.
|
||||
err = client.ArchiveChat(ctx, chat.ID)
|
||||
err = client.UpdateChat(ctx, chat.ID, codersdk.UpdateChatRequest{Archived: ptr.Ref(true)})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify it's archived.
|
||||
@@ -1822,7 +1823,7 @@ func TestUnarchiveChat(t *testing.T) {
|
||||
require.Len(t, archivedChats, 1)
|
||||
require.True(t, archivedChats[0].Archived)
|
||||
// Unarchive the chat.
|
||||
err = client.UnarchiveChat(ctx, chat.ID)
|
||||
err = client.UpdateChat(ctx, chat.ID, codersdk.UpdateChatRequest{Archived: ptr.Ref(false)})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify it's no longer archived.
|
||||
@@ -1861,10 +1862,9 @@ func TestUnarchiveChat(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
// Trying to unarchive a non-archived chat should fail.
|
||||
err = client.UnarchiveChat(ctx, chat.ID)
|
||||
err = client.UpdateChat(ctx, chat.ID, codersdk.UpdateChatRequest{Archived: ptr.Ref(false)})
|
||||
requireSDKError(t, err, http.StatusBadRequest)
|
||||
})
|
||||
|
||||
t.Run("NotFound", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -1872,7 +1872,7 @@ func TestUnarchiveChat(t *testing.T) {
|
||||
client := newChatClient(t)
|
||||
_ = coderdtest.CreateFirstUser(t, client)
|
||||
|
||||
err := client.UnarchiveChat(ctx, uuid.New())
|
||||
err := client.UpdateChat(ctx, uuid.New(), codersdk.UpdateChatRequest{Archived: ptr.Ref(false)})
|
||||
requireSDKError(t, err, http.StatusNotFound)
|
||||
})
|
||||
}
|
||||
@@ -4512,7 +4512,7 @@ func TestChatSystemPrompt(t *testing.T) {
|
||||
t.Run("AdminCanSet", func(t *testing.T) {
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
|
||||
err := adminClient.UpdateChatSystemPrompt(ctx, codersdk.UpdateChatSystemPromptRequest{
|
||||
err := adminClient.UpdateChatSystemPrompt(ctx, codersdk.ChatSystemPrompt{
|
||||
SystemPrompt: "You are a helpful coding assistant.",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
@@ -4526,7 +4526,7 @@ func TestChatSystemPrompt(t *testing.T) {
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
|
||||
// Unset by sending an empty string.
|
||||
err := adminClient.UpdateChatSystemPrompt(ctx, codersdk.UpdateChatSystemPromptRequest{
|
||||
err := adminClient.UpdateChatSystemPrompt(ctx, codersdk.ChatSystemPrompt{
|
||||
SystemPrompt: "",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
@@ -4539,7 +4539,7 @@ func TestChatSystemPrompt(t *testing.T) {
|
||||
t.Run("NonAdminFails", func(t *testing.T) {
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
|
||||
err := memberClient.UpdateChatSystemPrompt(ctx, codersdk.UpdateChatSystemPromptRequest{
|
||||
err := memberClient.UpdateChatSystemPrompt(ctx, codersdk.ChatSystemPrompt{
|
||||
SystemPrompt: "This should fail.",
|
||||
})
|
||||
requireSDKError(t, err, http.StatusNotFound)
|
||||
@@ -4560,7 +4560,7 @@ func TestChatSystemPrompt(t *testing.T) {
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
|
||||
tooLong := strings.Repeat("a", 131073)
|
||||
err := adminClient.UpdateChatSystemPrompt(ctx, codersdk.UpdateChatSystemPromptRequest{
|
||||
err := adminClient.UpdateChatSystemPrompt(ctx, codersdk.ChatSystemPrompt{
|
||||
SystemPrompt: tooLong,
|
||||
})
|
||||
sdkErr := requireSDKError(t, err, http.StatusBadRequest)
|
||||
|
||||
+1
-2
@@ -1196,8 +1196,7 @@ func New(options *Options) *API {
|
||||
r.Get("/", api.getChat)
|
||||
r.Get("/git/watch", api.watchChatGit)
|
||||
r.Get("/desktop", api.watchChatDesktop)
|
||||
r.Post("/archive", api.archiveChat)
|
||||
r.Post("/unarchive", api.unarchiveChat)
|
||||
r.Patch("/", api.patchChat)
|
||||
r.Get("/messages", api.getChatMessages)
|
||||
r.Post("/messages", api.postChatMessages)
|
||||
r.Patch("/messages/{message}", api.patchChatMessage)
|
||||
|
||||
+37
-64
@@ -245,7 +245,8 @@ type CreateChatRequest struct {
|
||||
|
||||
// UpdateChatRequest is the request to update a chat.
|
||||
type UpdateChatRequest struct {
|
||||
Title string `json:"title"`
|
||||
Title *string `json:"title,omitempty"`
|
||||
Archived *bool `json:"archived,omitempty"`
|
||||
}
|
||||
|
||||
// CreateChatMessageRequest is the request to add a message to a chat.
|
||||
@@ -307,25 +308,15 @@ type ChatModelsResponse struct {
|
||||
Providers []ChatModelProvider `json:"providers"`
|
||||
}
|
||||
|
||||
// ChatSystemPromptResponse is the response for getting the chat system prompt.
|
||||
type ChatSystemPromptResponse struct {
|
||||
// ChatSystemPrompt is the request and response body for the chat
|
||||
// system prompt configuration endpoint.
|
||||
type ChatSystemPrompt struct {
|
||||
SystemPrompt string `json:"system_prompt"`
|
||||
}
|
||||
|
||||
// UpdateChatSystemPromptRequest is the request to update the chat system prompt.
|
||||
type UpdateChatSystemPromptRequest struct {
|
||||
SystemPrompt string `json:"system_prompt"`
|
||||
}
|
||||
|
||||
// UserChatCustomPromptResponse is the response for getting a user's
|
||||
// custom chat prompt.
|
||||
type UserChatCustomPromptResponse struct {
|
||||
CustomPrompt string `json:"custom_prompt"`
|
||||
}
|
||||
|
||||
// UpdateUserChatCustomPromptRequest is the request to update a user's
|
||||
// custom chat prompt.
|
||||
type UpdateUserChatCustomPromptRequest struct {
|
||||
// UserChatCustomPrompt is the request and response body for the
|
||||
// user chat custom prompt configuration endpoint.
|
||||
type UserChatCustomPrompt struct {
|
||||
CustomPrompt string `json:"custom_prompt"`
|
||||
}
|
||||
|
||||
@@ -466,12 +457,13 @@ type ChatModelOpenAICompatProviderOptions struct {
|
||||
ReasoningEffort *string `json:"reasoning_effort,omitempty" description:"Controls the level of reasoning effort" enum:"none,minimal,low,medium,high,xhigh"`
|
||||
}
|
||||
|
||||
// ChatModelOpenRouterReasoningOptions configures OpenRouter reasoning behavior.
|
||||
type ChatModelOpenRouterReasoningOptions struct {
|
||||
// ChatModelReasoningOptions configures reasoning behavior for model
|
||||
// providers that support it.
|
||||
type ChatModelReasoningOptions struct {
|
||||
Enabled *bool `json:"enabled,omitempty" description:"Whether reasoning is enabled"`
|
||||
Exclude *bool `json:"exclude,omitempty" description:"Whether to exclude reasoning content from the response"`
|
||||
MaxTokens *int64 `json:"max_tokens,omitempty" description:"Maximum number of tokens for reasoning output"`
|
||||
Effort *string `json:"effort,omitempty" description:"Controls the level of reasoning effort" enum:"low,medium,high"`
|
||||
Effort *string `json:"effort,omitempty" description:"Controls the level of reasoning effort" enum:"none,minimal,low,medium,high,xhigh"`
|
||||
}
|
||||
|
||||
// ChatModelOpenRouterProvider configures OpenRouter routing preferences.
|
||||
@@ -488,22 +480,14 @@ type ChatModelOpenRouterProvider struct {
|
||||
|
||||
// ChatModelOpenRouterProviderOptions configures OpenRouter provider behavior.
|
||||
type ChatModelOpenRouterProviderOptions struct {
|
||||
Reasoning *ChatModelOpenRouterReasoningOptions `json:"reasoning,omitempty" description:"Configuration for reasoning behavior"`
|
||||
ExtraBody map[string]any `json:"extra_body,omitempty" description:"Additional fields to include in the request body" hidden:"true"`
|
||||
IncludeUsage *bool `json:"include_usage,omitempty" description:"Whether to include token usage information in the response" hidden:"true"`
|
||||
LogitBias map[string]int64 `json:"logit_bias,omitempty" description:"Token IDs mapped to bias values from -100 to 100" hidden:"true"`
|
||||
LogProbs *bool `json:"log_probs,omitempty" description:"Whether to return log probabilities of output tokens" hidden:"true"`
|
||||
ParallelToolCalls *bool `json:"parallel_tool_calls,omitempty" description:"Whether the model may make multiple tool calls in parallel"`
|
||||
User *string `json:"user,omitempty" description:"Unique identifier for the end user for abuse monitoring" hidden:"true"`
|
||||
Provider *ChatModelOpenRouterProvider `json:"provider,omitempty" description:"Routing preferences for provider selection" hidden:"true"`
|
||||
}
|
||||
|
||||
// ChatModelVercelReasoningOptions configures Vercel reasoning behavior.
|
||||
type ChatModelVercelReasoningOptions struct {
|
||||
Enabled *bool `json:"enabled,omitempty" description:"Whether reasoning is enabled"`
|
||||
MaxTokens *int64 `json:"max_tokens,omitempty" description:"Maximum number of tokens for reasoning output"`
|
||||
Effort *string `json:"effort,omitempty" description:"Controls the level of reasoning effort" enum:"none,minimal,low,medium,high,xhigh"`
|
||||
Exclude *bool `json:"exclude,omitempty" description:"Whether to exclude reasoning content from the response"`
|
||||
Reasoning *ChatModelReasoningOptions `json:"reasoning,omitempty" description:"Configuration for reasoning behavior"`
|
||||
ExtraBody map[string]any `json:"extra_body,omitempty" description:"Additional fields to include in the request body" hidden:"true"`
|
||||
IncludeUsage *bool `json:"include_usage,omitempty" description:"Whether to include token usage information in the response" hidden:"true"`
|
||||
LogitBias map[string]int64 `json:"logit_bias,omitempty" description:"Token IDs mapped to bias values from -100 to 100" hidden:"true"`
|
||||
LogProbs *bool `json:"log_probs,omitempty" description:"Whether to return log probabilities of output tokens" hidden:"true"`
|
||||
ParallelToolCalls *bool `json:"parallel_tool_calls,omitempty" description:"Whether the model may make multiple tool calls in parallel"`
|
||||
User *string `json:"user,omitempty" description:"Unique identifier for the end user for abuse monitoring" hidden:"true"`
|
||||
Provider *ChatModelOpenRouterProvider `json:"provider,omitempty" description:"Routing preferences for provider selection" hidden:"true"`
|
||||
}
|
||||
|
||||
// ChatModelVercelGatewayProviderOptions configures Vercel routing behavior.
|
||||
@@ -514,7 +498,7 @@ type ChatModelVercelGatewayProviderOptions struct {
|
||||
|
||||
// ChatModelVercelProviderOptions configures Vercel provider behavior.
|
||||
type ChatModelVercelProviderOptions struct {
|
||||
Reasoning *ChatModelVercelReasoningOptions `json:"reasoning,omitempty" description:"Configuration for reasoning behavior"`
|
||||
Reasoning *ChatModelReasoningOptions `json:"reasoning,omitempty" description:"Configuration for reasoning behavior"`
|
||||
ProviderOptions *ChatModelVercelGatewayProviderOptions `json:"providerOptions,omitempty" description:"Gateway routing options for provider selection" hidden:"true"`
|
||||
User *string `json:"user,omitempty" description:"Unique identifier for the end user for abuse monitoring" hidden:"true"`
|
||||
LogitBias map[string]int64 `json:"logit_bias,omitempty" description:"Token IDs mapped to bias values from -100 to 100" hidden:"true"`
|
||||
@@ -1246,21 +1230,21 @@ func (c *Client) GetChatCostUsers(ctx context.Context, opts ChatCostUsersOptions
|
||||
}
|
||||
|
||||
// GetChatSystemPrompt returns the deployment-wide chat system prompt.
|
||||
func (c *Client) GetChatSystemPrompt(ctx context.Context) (ChatSystemPromptResponse, error) {
|
||||
func (c *Client) GetChatSystemPrompt(ctx context.Context) (ChatSystemPrompt, error) {
|
||||
res, err := c.Request(ctx, http.MethodGet, "/api/experimental/chats/config/system-prompt", nil)
|
||||
if err != nil {
|
||||
return ChatSystemPromptResponse{}, err
|
||||
return ChatSystemPrompt{}, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return ChatSystemPromptResponse{}, ReadBodyAsError(res)
|
||||
return ChatSystemPrompt{}, ReadBodyAsError(res)
|
||||
}
|
||||
var resp ChatSystemPromptResponse
|
||||
var resp ChatSystemPrompt
|
||||
return resp, json.NewDecoder(res.Body).Decode(&resp)
|
||||
}
|
||||
|
||||
// UpdateChatSystemPrompt updates the deployment-wide chat system prompt.
|
||||
func (c *Client) UpdateChatSystemPrompt(ctx context.Context, req UpdateChatSystemPromptRequest) error {
|
||||
func (c *Client) UpdateChatSystemPrompt(ctx context.Context, req ChatSystemPrompt) error {
|
||||
res, err := c.Request(ctx, http.MethodPut, "/api/experimental/chats/config/system-prompt", req)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -1273,30 +1257,30 @@ func (c *Client) UpdateChatSystemPrompt(ctx context.Context, req UpdateChatSyste
|
||||
}
|
||||
|
||||
// GetUserChatCustomPrompt fetches the user's custom chat prompt.
|
||||
func (c *Client) GetUserChatCustomPrompt(ctx context.Context) (UserChatCustomPromptResponse, error) {
|
||||
func (c *Client) GetUserChatCustomPrompt(ctx context.Context) (UserChatCustomPrompt, error) {
|
||||
res, err := c.Request(ctx, http.MethodGet, "/api/experimental/chats/config/user-prompt", nil)
|
||||
if err != nil {
|
||||
return UserChatCustomPromptResponse{}, err
|
||||
return UserChatCustomPrompt{}, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return UserChatCustomPromptResponse{}, ReadBodyAsError(res)
|
||||
return UserChatCustomPrompt{}, ReadBodyAsError(res)
|
||||
}
|
||||
var resp UserChatCustomPromptResponse
|
||||
var resp UserChatCustomPrompt
|
||||
return resp, json.NewDecoder(res.Body).Decode(&resp)
|
||||
}
|
||||
|
||||
// UpdateUserChatCustomPrompt updates the user's custom chat prompt.
|
||||
func (c *Client) UpdateUserChatCustomPrompt(ctx context.Context, req UpdateUserChatCustomPromptRequest) (UserChatCustomPromptResponse, error) {
|
||||
func (c *Client) UpdateUserChatCustomPrompt(ctx context.Context, req UserChatCustomPrompt) (UserChatCustomPrompt, error) {
|
||||
res, err := c.Request(ctx, http.MethodPut, "/api/experimental/chats/config/user-prompt", req)
|
||||
if err != nil {
|
||||
return UserChatCustomPromptResponse{}, err
|
||||
return UserChatCustomPrompt{}, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return UserChatCustomPromptResponse{}, ReadBodyAsError(res)
|
||||
return UserChatCustomPrompt{}, ReadBodyAsError(res)
|
||||
}
|
||||
var resp UserChatCustomPromptResponse
|
||||
var resp UserChatCustomPrompt
|
||||
return resp, json.NewDecoder(res.Body).Decode(&resp)
|
||||
}
|
||||
|
||||
@@ -1499,20 +1483,9 @@ func (c *Client) GetChatMessages(ctx context.Context, chatID uuid.UUID, opts *Ch
|
||||
return resp, json.NewDecoder(res.Body).Decode(&resp)
|
||||
}
|
||||
|
||||
func (c *Client) ArchiveChat(ctx context.Context, chatID uuid.UUID) error {
|
||||
res, err := c.Request(ctx, http.MethodPost, fmt.Sprintf("/api/experimental/chats/%s/archive", chatID), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusNoContent {
|
||||
return ReadBodyAsError(res)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) UnarchiveChat(ctx context.Context, chatID uuid.UUID) error {
|
||||
res, err := c.Request(ctx, http.MethodPost, fmt.Sprintf("/api/experimental/chats/%s/unarchive", chatID), nil)
|
||||
// UpdateChat patches a chat resource.
|
||||
func (c *Client) UpdateChat(ctx context.Context, chatID uuid.UUID, req UpdateChatRequest) error {
|
||||
res, err := c.Request(ctx, http.MethodPatch, fmt.Sprintf("/api/experimental/chats/%s", chatID), req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+22
-27
@@ -2985,12 +2985,11 @@ class ApiMethods {
|
||||
return response.data;
|
||||
};
|
||||
|
||||
archiveChat = async (chatId: string): Promise<void> => {
|
||||
await this.axios.post(`/api/experimental/chats/${chatId}/archive`);
|
||||
};
|
||||
|
||||
unarchiveChat = async (chatId: string): Promise<void> => {
|
||||
await this.axios.post(`/api/experimental/chats/${chatId}/unarchive`);
|
||||
updateChat = async (
|
||||
chatId: string,
|
||||
req: TypesGen.UpdateChatRequest,
|
||||
): Promise<void> => {
|
||||
await this.axios.patch(`/api/experimental/chats/${chatId}`, req);
|
||||
};
|
||||
|
||||
createChatMessage = async (
|
||||
@@ -3067,37 +3066,33 @@ class ApiMethods {
|
||||
return response.data;
|
||||
};
|
||||
|
||||
getChatSystemPrompt =
|
||||
async (): Promise<TypesGen.ChatSystemPromptResponse> => {
|
||||
const response = await this.axios.get<TypesGen.ChatSystemPromptResponse>(
|
||||
"/api/experimental/chats/config/system-prompt",
|
||||
);
|
||||
return response.data;
|
||||
};
|
||||
getChatSystemPrompt = async (): Promise<TypesGen.ChatSystemPrompt> => {
|
||||
const response = await this.axios.get<TypesGen.ChatSystemPrompt>(
|
||||
"/api/experimental/chats/config/system-prompt",
|
||||
);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
updateChatSystemPrompt = async (
|
||||
req: TypesGen.UpdateChatSystemPromptRequest,
|
||||
req: TypesGen.ChatSystemPrompt,
|
||||
): Promise<void> => {
|
||||
await this.axios.put("/api/experimental/chats/config/system-prompt", req);
|
||||
};
|
||||
|
||||
getUserChatCustomPrompt =
|
||||
async (): Promise<TypesGen.UserChatCustomPromptResponse> => {
|
||||
const response =
|
||||
await this.axios.get<TypesGen.UserChatCustomPromptResponse>(
|
||||
"/api/experimental/chats/config/user-prompt",
|
||||
);
|
||||
async (): Promise<TypesGen.UserChatCustomPrompt> => {
|
||||
const response = await this.axios.get<TypesGen.UserChatCustomPrompt>(
|
||||
"/api/experimental/chats/config/user-prompt",
|
||||
);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
updateUserChatCustomPrompt = async (
|
||||
req: TypesGen.UpdateUserChatCustomPromptRequest,
|
||||
): Promise<TypesGen.UserChatCustomPromptResponse> => {
|
||||
const response =
|
||||
await this.axios.put<TypesGen.UserChatCustomPromptResponse>(
|
||||
"/api/experimental/chats/config/user-prompt",
|
||||
req,
|
||||
);
|
||||
req: TypesGen.UserChatCustomPrompt,
|
||||
): Promise<TypesGen.UserChatCustomPrompt> => {
|
||||
const response = await this.axios.put<TypesGen.UserChatCustomPrompt>(
|
||||
"/api/experimental/chats/config/user-prompt",
|
||||
req,
|
||||
);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
|
||||
@@ -457,7 +457,7 @@
|
||||
"type": "string",
|
||||
"description": "Controls the level of reasoning effort",
|
||||
"required": false,
|
||||
"enum": ["low", "medium", "high"],
|
||||
"enum": ["none", "minimal", "low", "medium", "high", "xhigh"],
|
||||
"input_type": "select"
|
||||
},
|
||||
{
|
||||
@@ -534,6 +534,14 @@
|
||||
"required": false,
|
||||
"input_type": "select"
|
||||
},
|
||||
{
|
||||
"json_name": "reasoning.exclude",
|
||||
"go_name": "Reasoning.Exclude",
|
||||
"type": "boolean",
|
||||
"description": "Whether to exclude reasoning content from the response",
|
||||
"required": false,
|
||||
"input_type": "select"
|
||||
},
|
||||
{
|
||||
"json_name": "reasoning.max_tokens",
|
||||
"go_name": "Reasoning.MaxTokens",
|
||||
@@ -551,14 +559,6 @@
|
||||
"enum": ["none", "minimal", "low", "medium", "high", "xhigh"],
|
||||
"input_type": "select"
|
||||
},
|
||||
{
|
||||
"json_name": "reasoning.exclude",
|
||||
"go_name": "Reasoning.Exclude",
|
||||
"type": "boolean",
|
||||
"description": "Whether to exclude reasoning content from the response",
|
||||
"required": false,
|
||||
"input_type": "select"
|
||||
},
|
||||
{
|
||||
"json_name": "providerOptions",
|
||||
"go_name": "ProviderOptions",
|
||||
|
||||
@@ -25,10 +25,9 @@ import {
|
||||
|
||||
vi.mock("api/api", () => ({
|
||||
API: {
|
||||
archiveChat: vi.fn(),
|
||||
updateChat: vi.fn(),
|
||||
createChat: vi.fn(),
|
||||
deleteChatQueuedMessage: vi.fn(),
|
||||
unarchiveChat: vi.fn(),
|
||||
getChats: vi.fn(),
|
||||
getChatCostSummary: vi.fn(),
|
||||
getChatCostUsers: vi.fn(),
|
||||
@@ -207,7 +206,7 @@ describe("archiveChat optimistic update", () => {
|
||||
const initialChats = [makeChat(chatId), makeChat("chat-2")];
|
||||
seedInfiniteChats(queryClient, initialChats);
|
||||
|
||||
vi.mocked(API.archiveChat).mockResolvedValue();
|
||||
vi.mocked(API.updateChat).mockResolvedValue();
|
||||
|
||||
const mutation = archiveChat(queryClient);
|
||||
await mutation.onMutate(chatId);
|
||||
@@ -225,7 +224,7 @@ describe("archiveChat optimistic update", () => {
|
||||
seedInfiniteChats(queryClient, [makeChat(chatId)]);
|
||||
queryClient.setQueryData(chatKey(chatId), makeChat(chatId));
|
||||
|
||||
vi.mocked(API.archiveChat).mockResolvedValue();
|
||||
vi.mocked(API.updateChat).mockResolvedValue();
|
||||
|
||||
const mutation = archiveChat(queryClient);
|
||||
await mutation.onMutate(chatId);
|
||||
|
||||
@@ -175,7 +175,7 @@ export const chatMessagesForInfiniteScroll = (chatId: string) => ({
|
||||
});
|
||||
|
||||
export const archiveChat = (queryClient: QueryClient) => ({
|
||||
mutationFn: (chatId: string) => API.archiveChat(chatId),
|
||||
mutationFn: (chatId: string) => API.updateChat(chatId, { archived: true }),
|
||||
onMutate: async (chatId: string) => {
|
||||
await queryClient.cancelQueries({
|
||||
queryKey: chatsKey,
|
||||
@@ -234,7 +234,7 @@ export const archiveChat = (queryClient: QueryClient) => ({
|
||||
});
|
||||
|
||||
export const unarchiveChat = (queryClient: QueryClient) => ({
|
||||
mutationFn: (chatId: string) => API.unarchiveChat(chatId),
|
||||
mutationFn: (chatId: string) => API.updateChat(chatId, { archived: false }),
|
||||
onMutate: async (chatId: string) => {
|
||||
await queryClient.cancelQueries({
|
||||
queryKey: chatsKey,
|
||||
|
||||
Generated
+22
-47
@@ -1538,7 +1538,7 @@ export interface ChatModelOpenRouterProvider {
|
||||
* ChatModelOpenRouterProviderOptions configures OpenRouter provider behavior.
|
||||
*/
|
||||
export interface ChatModelOpenRouterProviderOptions {
|
||||
readonly reasoning?: ChatModelOpenRouterReasoningOptions;
|
||||
readonly reasoning?: ChatModelReasoningOptions;
|
||||
// empty interface{} type, falling back to unknown
|
||||
readonly extra_body?: Record<string, unknown>;
|
||||
readonly include_usage?: boolean;
|
||||
@@ -1549,17 +1549,6 @@ export interface ChatModelOpenRouterProviderOptions {
|
||||
readonly provider?: ChatModelOpenRouterProvider;
|
||||
}
|
||||
|
||||
// From codersdk/chats.go
|
||||
/**
|
||||
* ChatModelOpenRouterReasoningOptions configures OpenRouter reasoning behavior.
|
||||
*/
|
||||
export interface ChatModelOpenRouterReasoningOptions {
|
||||
readonly enabled?: boolean;
|
||||
readonly exclude?: boolean;
|
||||
readonly max_tokens?: number;
|
||||
readonly effort?: string;
|
||||
}
|
||||
|
||||
// From codersdk/chats.go
|
||||
/**
|
||||
* ChatModelProvider represents provider availability and model results.
|
||||
@@ -1595,6 +1584,18 @@ export type ChatModelProviderUnavailableReason =
|
||||
export const ChatModelProviderUnavailableReasons: ChatModelProviderUnavailableReason[] =
|
||||
["fetch_failed", "missing_api_key"];
|
||||
|
||||
// From codersdk/chats.go
|
||||
/**
|
||||
* ChatModelReasoningOptions configures reasoning behavior for model
|
||||
* providers that support it.
|
||||
*/
|
||||
export interface ChatModelReasoningOptions {
|
||||
readonly enabled?: boolean;
|
||||
readonly exclude?: boolean;
|
||||
readonly max_tokens?: number;
|
||||
readonly effort?: string;
|
||||
}
|
||||
|
||||
// From codersdk/chats.go
|
||||
/**
|
||||
* ChatModelVercelGatewayProviderOptions configures Vercel routing behavior.
|
||||
@@ -1609,7 +1610,7 @@ export interface ChatModelVercelGatewayProviderOptions {
|
||||
* ChatModelVercelProviderOptions configures Vercel provider behavior.
|
||||
*/
|
||||
export interface ChatModelVercelProviderOptions {
|
||||
readonly reasoning?: ChatModelVercelReasoningOptions;
|
||||
readonly reasoning?: ChatModelReasoningOptions;
|
||||
readonly providerOptions?: ChatModelVercelGatewayProviderOptions;
|
||||
readonly user?: string;
|
||||
readonly logit_bias?: Record<string, number>;
|
||||
@@ -1620,17 +1621,6 @@ export interface ChatModelVercelProviderOptions {
|
||||
readonly extra_body?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
// From codersdk/chats.go
|
||||
/**
|
||||
* ChatModelVercelReasoningOptions configures Vercel reasoning behavior.
|
||||
*/
|
||||
export interface ChatModelVercelReasoningOptions {
|
||||
readonly enabled?: boolean;
|
||||
readonly max_tokens?: number;
|
||||
readonly effort?: string;
|
||||
readonly exclude?: boolean;
|
||||
}
|
||||
|
||||
// From codersdk/chats.go
|
||||
/**
|
||||
* ChatModelsResponse is the catalog returned from chat model discovery.
|
||||
@@ -1777,9 +1767,10 @@ export interface ChatStreamStatus {
|
||||
|
||||
// From codersdk/chats.go
|
||||
/**
|
||||
* ChatSystemPromptResponse is the response for getting the chat system prompt.
|
||||
* ChatSystemPrompt is the request and response body for the chat
|
||||
* system prompt configuration endpoint.
|
||||
*/
|
||||
export interface ChatSystemPromptResponse {
|
||||
export interface ChatSystemPrompt {
|
||||
readonly system_prompt: string;
|
||||
}
|
||||
|
||||
@@ -6608,15 +6599,8 @@ export interface UpdateChatProviderConfigRequest {
|
||||
* UpdateChatRequest is the request to update a chat.
|
||||
*/
|
||||
export interface UpdateChatRequest {
|
||||
readonly title: string;
|
||||
}
|
||||
|
||||
// From codersdk/chats.go
|
||||
/**
|
||||
* UpdateChatSystemPromptRequest is the request to update the chat system prompt.
|
||||
*/
|
||||
export interface UpdateChatSystemPromptRequest {
|
||||
readonly system_prompt: string;
|
||||
readonly title?: string;
|
||||
readonly archived?: boolean;
|
||||
}
|
||||
|
||||
// From codersdk/chats.go
|
||||
@@ -6795,15 +6779,6 @@ export interface UpdateUserAppearanceSettingsRequest {
|
||||
readonly terminal_font: TerminalFontName;
|
||||
}
|
||||
|
||||
// From codersdk/chats.go
|
||||
/**
|
||||
* UpdateUserChatCustomPromptRequest is the request to update a user's
|
||||
* custom chat prompt.
|
||||
*/
|
||||
export interface UpdateUserChatCustomPromptRequest {
|
||||
readonly custom_prompt: string;
|
||||
}
|
||||
|
||||
// From codersdk/notifications.go
|
||||
export interface UpdateUserNotificationPreferences {
|
||||
readonly template_disabled_map: Record<string, boolean>;
|
||||
@@ -7048,10 +7023,10 @@ export interface UserAppearanceSettings {
|
||||
|
||||
// From codersdk/chats.go
|
||||
/**
|
||||
* UserChatCustomPromptResponse is the response for getting a user's
|
||||
* custom chat prompt.
|
||||
* UserChatCustomPrompt is the request and response body for the
|
||||
* user chat custom prompt configuration endpoint.
|
||||
*/
|
||||
export interface UserChatCustomPromptResponse {
|
||||
export interface UserChatCustomPrompt {
|
||||
readonly custom_prompt: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -148,7 +148,7 @@ const AgentsPage: FC = () => {
|
||||
chatId: string;
|
||||
workspaceId: string;
|
||||
}) => {
|
||||
await API.archiveChat(chatId);
|
||||
await API.updateChat(chatId, { archived: true });
|
||||
await API.deleteWorkspace(workspaceId);
|
||||
return { chatId, workspaceId };
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user