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:
Kyle Carberry
2026-03-17 14:31:11 +00:00
committed by GitHub
parent fdb1205bdf
commit 075dfecd12
11 changed files with 161 additions and 226 deletions
+22 -27
View File
@@ -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;
};
+9 -9
View File
@@ -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",
+3 -4
View File
@@ -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);
+2 -2
View File
@@ -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,
+22 -47
View File
@@ -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;
}
+1 -1
View File
@@ -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 };
},