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:
+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