refactor(site): move experimental endpoints to ExperimentalApiMethods (#23449)

This commit is contained in:
Danielle Maywood
2026-03-23 16:29:07 +00:00
committed by GitHub
parent 975373704f
commit ed19a3a08e
12 changed files with 346 additions and 284 deletions
+12 -6
View File
@@ -285,19 +285,19 @@ describe("api.ts", () => {
it.each<[string, () => Promise<unknown>, unknown]>([
[
"/api/experimental/chats/models",
() => API.getChatModels(),
() => API.experimental.getChatModels(),
{
providers: [],
},
],
[
"/api/experimental/chats/providers",
() => API.getChatProviderConfigs(),
() => API.experimental.getChatProviderConfigs(),
[],
],
[
"/api/experimental/chats/model-configs",
() => API.getChatModelConfigs(),
() => API.experimental.getChatModelConfigs(),
[],
],
])("returns response data for %s", async (path, request, responseData) => {
@@ -312,11 +312,17 @@ describe("api.ts", () => {
});
it.each<[string, () => Promise<unknown>]>([
["/api/experimental/chats/models", () => API.getChatModels()],
["/api/experimental/chats/providers", () => API.getChatProviderConfigs()],
[
"/api/experimental/chats/models",
() => API.experimental.getChatModels(),
],
[
"/api/experimental/chats/providers",
() => API.experimental.getChatProviderConfigs(),
],
[
"/api/experimental/chats/model-configs",
() => API.getChatModelConfigs(),
() => API.experimental.getChatModelConfigs(),
],
])("rethrows axios errors for %s", async (path, request) => {
const expectedError = new Error("request failed");
+44 -44
View File
@@ -2369,27 +2369,6 @@ class ApiMethods {
return response.data;
};
uploadChatFile = async (
file: File,
organizationId: string,
): Promise<TypesGen.UploadChatFileResponse> => {
const response = await this.axios.post(
`/api/experimental/chats/files?organization=${organizationId}`,
file,
{
headers: {
"Content-Type": file.type || "application/octet-stream",
// Use RFC 5987 encoding for the filename to support
// non-ASCII characters. Placing the raw name directly in
// the header causes XMLHttpRequest to throw because HTTP
// headers only allow ISO-8859-1 code points.
"Content-Disposition": `attachment; filename="file"; filename*=UTF-8''${encodeURIComponent(file.name)}`,
},
},
);
return response.data;
};
getTemplateVersionLogs = async (
versionId: string,
): Promise<TypesGen.ProvisionerJobLog[]> => {
@@ -3013,6 +2992,50 @@ class ApiMethods {
return response.data;
};
getAIBridgeModels = async (options: SearchParamOptions) => {
const url = getURLWithSearchParams("/api/v2/aibridge/models", options);
const response = await this.axios.get<string[]>(url);
return response.data;
};
}
export type TaskFeedbackRating = "good" | "okay" | "bad";
export type CreateTaskFeedbackRequest = {
rate: TaskFeedbackRating;
comment?: string;
};
// Experimental API methods call endpoints under the /api/experimental/ prefix.
// These endpoints are not stable and may change or be removed at any time.
//
// All methods must be defined with arrow function syntax. See the docstring
// above the ApiMethods class for a full explanation.
class ExperimentalApiMethods {
constructor(protected readonly axios: AxiosInstance) {}
uploadChatFile = async (
file: File,
organizationId: string,
): Promise<TypesGen.UploadChatFileResponse> => {
const response = await this.axios.post(
`/api/experimental/chats/files?organization=${organizationId}`,
file,
{
headers: {
"Content-Type": file.type || "application/octet-stream",
// Use RFC 5987 encoding for the filename to support
// non-ASCII characters. Placing the raw name directly in
// the header causes XMLHttpRequest to throw because HTTP
// headers only allow ISO-8859-1 code points.
"Content-Disposition": `attachment; filename="file"; filename*=UTF-8''${encodeURIComponent(file.name)}`,
},
},
);
return response.data;
};
// Chat API methods
getChats = async (req?: {
after_id?: string;
@@ -3300,13 +3323,6 @@ class ApiMethods {
);
};
getAIBridgeModels = async (options: SearchParamOptions) => {
const url = getURLWithSearchParams("/api/v2/aibridge/models", options);
const response = await this.axios.get<string[]>(url);
return response.data;
};
getChatCostSummary = async (
user = "me",
params?: ChatCostDateParams,
@@ -3408,22 +3424,6 @@ class ApiMethods {
};
}
export type TaskFeedbackRating = "good" | "okay" | "bad";
export type CreateTaskFeedbackRequest = {
rate: TaskFeedbackRating;
comment?: string;
};
// Experimental API methods call endpoints under the /api/experimental/ prefix.
// These endpoints are not stable and may change or be removed at any time.
//
// All methods must be defined with arrow function syntax. See the docstring
// above the ApiMethods class for a full explanation.
class ExperimentalApiMethods {
constructor(protected readonly axios: AxiosInstance) {}
}
// This is a hard coded CSRF token/cookie pair for local development. In prod,
// the GoLang webserver generates a random cookie with a new token for each
// document request. For local development, we don't use the Go webserver for
+28 -23
View File
@@ -25,16 +25,18 @@ import {
vi.mock("api/api", () => ({
API: {
updateChat: vi.fn(),
createChat: vi.fn(),
deleteChatQueuedMessage: vi.fn(),
getChats: vi.fn(),
getChatCostSummary: vi.fn(),
getChatCostUsers: vi.fn(),
createChatMessage: vi.fn(),
editChatMessage: vi.fn(),
interruptChat: vi.fn(),
promoteChatQueuedMessage: vi.fn(),
experimental: {
updateChat: vi.fn(),
createChat: vi.fn(),
deleteChatQueuedMessage: vi.fn(),
getChats: vi.fn(),
getChatCostSummary: vi.fn(),
getChatCostUsers: vi.fn(),
createChatMessage: vi.fn(),
editChatMessage: vi.fn(),
interruptChat: vi.fn(),
promoteChatQueuedMessage: vi.fn(),
},
},
}));
@@ -207,7 +209,7 @@ describe("archiveChat optimistic update", () => {
const initialChats = [makeChat(chatId), makeChat("chat-2")];
seedInfiniteChats(queryClient, initialChats);
vi.mocked(API.updateChat).mockResolvedValue();
vi.mocked(API.experimental.updateChat).mockResolvedValue();
const mutation = archiveChat(queryClient);
await mutation.onMutate(chatId);
@@ -225,7 +227,7 @@ describe("archiveChat optimistic update", () => {
seedInfiniteChats(queryClient, [makeChat(chatId)]);
queryClient.setQueryData(chatKey(chatId), makeChat(chatId));
vi.mocked(API.updateChat).mockResolvedValue();
vi.mocked(API.experimental.updateChat).mockResolvedValue();
const mutation = archiveChat(queryClient);
await mutation.onMutate(chatId);
@@ -414,7 +416,7 @@ describe("chat cost query factories", () => {
start_date: "2025-01-01",
end_date: "2025-01-31",
};
vi.mocked(API.getChatCostSummary).mockResolvedValue(
vi.mocked(API.experimental.getChatCostSummary).mockResolvedValue(
{} as TypesGen.ChatCostSummary,
);
@@ -428,7 +430,10 @@ describe("chat cost query factories", () => {
]);
expect(query.queryKey).toEqual(["chats", "costSummary", user, params]);
await query.queryFn();
expect(API.getChatCostSummary).toHaveBeenCalledWith(user, params);
expect(API.experimental.getChatCostSummary).toHaveBeenCalledWith(
user,
params,
);
});
it("builds a distinct users query key and forwards snake_case params", async () => {
@@ -439,7 +444,7 @@ describe("chat cost query factories", () => {
limit: 10,
offset: 20,
};
vi.mocked(API.getChatCostUsers).mockResolvedValue(
vi.mocked(API.experimental.getChatCostUsers).mockResolvedValue(
{} as TypesGen.ChatCostUsersResponse,
);
@@ -449,7 +454,7 @@ describe("chat cost query factories", () => {
expect(query.queryKey).toEqual(["chats", "costUsers", params]);
expect(query.queryKey).not.toEqual(chatCostSummaryKey("me", params));
await query.queryFn();
expect(API.getChatCostUsers).toHaveBeenCalledWith(params);
expect(API.experimental.getChatCostUsers).toHaveBeenCalledWith(params);
});
});
@@ -710,37 +715,37 @@ describe("infiniteChats", () => {
describe("queryFn", () => {
it("computes offset 0 for pageParam 0", async () => {
vi.mocked(API.getChats).mockResolvedValue([]);
vi.mocked(API.experimental.getChats).mockResolvedValue([]);
const { queryFn } = infiniteChats();
await queryFn({ pageParam: 0 });
expect(API.getChats).toHaveBeenCalledWith({
expect(API.experimental.getChats).toHaveBeenCalledWith({
limit: PAGE_LIMIT,
offset: 0,
});
});
it("computes offset 0 for pageParam <= 0", async () => {
vi.mocked(API.getChats).mockResolvedValue([]);
vi.mocked(API.experimental.getChats).mockResolvedValue([]);
const { queryFn } = infiniteChats();
await queryFn({ pageParam: -1 });
expect(API.getChats).toHaveBeenCalledWith({
expect(API.experimental.getChats).toHaveBeenCalledWith({
limit: PAGE_LIMIT,
offset: 0,
});
});
it("computes correct offset for subsequent pages", async () => {
vi.mocked(API.getChats).mockResolvedValue([]);
vi.mocked(API.experimental.getChats).mockResolvedValue([]);
const { queryFn } = infiniteChats();
await queryFn({ pageParam: 2 });
expect(API.getChats).toHaveBeenCalledWith({
expect(API.experimental.getChats).toHaveBeenCalledWith({
limit: PAGE_LIMIT,
offset: PAGE_LIMIT,
});
await queryFn({ pageParam: 3 });
expect(API.getChats).toHaveBeenCalledWith({
expect(API.experimental.getChats).toHaveBeenCalledWith({
limit: PAGE_LIMIT,
offset: PAGE_LIMIT * 2,
});
+50 -43
View File
@@ -132,7 +132,7 @@ export const infiniteChats = (opts?: { q?: string; archived?: boolean }) => {
if (typeof pageParam !== "number") {
throw new Error("pageParam must be a number");
}
return API.getChats({
return API.experimental.getChats({
limit,
offset: pageParam <= 0 ? 0 : (pageParam - 1) * limit,
q,
@@ -145,7 +145,7 @@ export const infiniteChats = (opts?: { q?: string; archived?: boolean }) => {
export const chat = (chatId: string) => ({
queryKey: chatKey(chatId),
queryFn: () => API.getChat(chatId),
queryFn: () => API.experimental.getChat(chatId),
});
const MESSAGES_PAGE_SIZE = 50;
@@ -154,7 +154,7 @@ export const chatMessagesForInfiniteScroll = (chatId: string) => ({
queryKey: chatMessagesKey(chatId),
initialPageParam: undefined as number | undefined,
queryFn: ({ pageParam }: { pageParam: number | undefined }) =>
API.getChatMessages(chatId, {
API.experimental.getChatMessages(chatId, {
before_id: pageParam,
limit: MESSAGES_PAGE_SIZE,
}),
@@ -170,7 +170,8 @@ export const chatMessagesForInfiniteScroll = (chatId: string) => ({
});
export const archiveChat = (queryClient: QueryClient) => ({
mutationFn: (chatId: string) => API.updateChat(chatId, { archived: true }),
mutationFn: (chatId: string) =>
API.experimental.updateChat(chatId, { archived: true }),
onMutate: async (chatId: string) => {
await queryClient.cancelQueries({
queryKey: chatsKey,
@@ -229,7 +230,8 @@ export const archiveChat = (queryClient: QueryClient) => ({
});
export const unarchiveChat = (queryClient: QueryClient) => ({
mutationFn: (chatId: string) => API.updateChat(chatId, { archived: false }),
mutationFn: (chatId: string) =>
API.experimental.updateChat(chatId, { archived: false }),
onMutate: async (chatId: string) => {
await queryClient.cancelQueries({
queryKey: chatsKey,
@@ -288,7 +290,8 @@ export const unarchiveChat = (queryClient: QueryClient) => ({
});
export const createChat = (queryClient: QueryClient) => ({
mutationFn: (req: TypesGen.CreateChatRequest) => API.createChat(req),
mutationFn: (req: TypesGen.CreateChatRequest) =>
API.experimental.createChat(req),
onSuccess: () => {
void invalidateChatListQueries(queryClient);
},
@@ -299,7 +302,7 @@ export const createChatMessage = (
chatId: string,
) => ({
mutationFn: (req: TypesGen.CreateChatMessageRequest) =>
API.createChatMessage(chatId, req),
API.experimental.createChatMessage(chatId, req),
// No onSuccess invalidation needed: the per-chat WebSocket delivers
// the response message via upsertDurableMessage, and the global
// watchChats() WebSocket updates the sidebar sort order.
@@ -312,7 +315,7 @@ type EditChatMessageMutationArgs = {
export const editChatMessage = (queryClient: QueryClient, chatId: string) => ({
mutationFn: ({ messageId, req }: EditChatMessageMutationArgs) =>
API.editChatMessage(chatId, messageId, req),
API.experimental.editChatMessage(chatId, messageId, req),
onSuccess: () => {
// Editing truncates all messages after the edited one on the
// server. The WebSocket can insert/update messages but cannot
@@ -331,7 +334,7 @@ export const editChatMessage = (queryClient: QueryClient, chatId: string) => ({
});
export const interruptChat = (_queryClient: QueryClient, chatId: string) => ({
mutationFn: () => API.interruptChat(chatId),
mutationFn: () => API.experimental.interruptChat(chatId),
// No onSuccess invalidation needed: the per-chat WebSocket
// delivers the status change via setChatStatus, and the global
// watchChats() WebSocket updates the sidebar.
@@ -342,7 +345,7 @@ export const deleteChatQueuedMessage = (
chatId: string,
) => ({
mutationFn: (queuedMessageId: number) =>
API.deleteChatQueuedMessage(chatId, queuedMessageId),
API.experimental.deleteChatQueuedMessage(chatId, queuedMessageId),
onSuccess: async () => {
await queryClient.invalidateQueries({
queryKey: chatKey(chatId),
@@ -360,7 +363,7 @@ export const promoteChatQueuedMessage = (
chatId: string,
) => ({
mutationFn: (queuedMessageId: number) =>
API.promoteChatQueuedMessage(chatId, queuedMessageId),
API.experimental.promoteChatQueuedMessage(chatId, queuedMessageId),
// No onSuccess invalidation needed: the caller upserts the
// promoted message from the response, and the per-chat
// WebSocket delivers queue and status updates in real-time.
@@ -371,18 +374,18 @@ export const chatDiffContentsKey = (chatId: string) =>
export const chatDiffContents = (chatId: string) => ({
queryKey: chatDiffContentsKey(chatId),
queryFn: () => API.getChatDiffContents(chatId),
queryFn: () => API.experimental.getChatDiffContents(chatId),
});
const chatSystemPromptKey = ["chat-system-prompt"] as const;
export const chatSystemPrompt = () => ({
queryKey: chatSystemPromptKey,
queryFn: () => API.getChatSystemPrompt(),
queryFn: () => API.experimental.getChatSystemPrompt(),
});
export const updateChatSystemPrompt = (queryClient: QueryClient) => ({
mutationFn: API.updateChatSystemPrompt,
mutationFn: API.experimental.updateChatSystemPrompt,
onSuccess: async () => {
await queryClient.invalidateQueries({
queryKey: chatSystemPromptKey,
@@ -394,11 +397,11 @@ const chatDesktopEnabledKey = ["chat-desktop-enabled"] as const;
export const chatDesktopEnabled = () => ({
queryKey: chatDesktopEnabledKey,
queryFn: () => API.getChatDesktopEnabled(),
queryFn: () => API.experimental.getChatDesktopEnabled(),
});
export const updateChatDesktopEnabled = (queryClient: QueryClient) => ({
mutationFn: API.updateChatDesktopEnabled,
mutationFn: API.experimental.updateChatDesktopEnabled,
onSuccess: async () => {
await queryClient.invalidateQueries({
queryKey: chatDesktopEnabledKey,
@@ -410,11 +413,11 @@ const chatWorkspaceTTLKey = ["chat-workspace-ttl"] as const;
export const chatWorkspaceTTL = () => ({
queryKey: chatWorkspaceTTLKey,
queryFn: () => API.getChatWorkspaceTTL(),
queryFn: () => API.experimental.getChatWorkspaceTTL(),
});
export const updateChatWorkspaceTTL = (queryClient: QueryClient) => ({
mutationFn: API.updateChatWorkspaceTTL,
mutationFn: API.experimental.updateChatWorkspaceTTL,
onSuccess: async () => {
await queryClient.invalidateQueries({
queryKey: chatWorkspaceTTLKey,
@@ -426,11 +429,11 @@ const chatUserCustomPromptKey = ["chat-user-custom-prompt"] as const;
export const chatUserCustomPrompt = () => ({
queryKey: chatUserCustomPromptKey,
queryFn: () => API.getUserChatCustomPrompt(),
queryFn: () => API.experimental.getUserChatCustomPrompt(),
});
export const updateUserChatCustomPrompt = (queryClient: QueryClient) => ({
mutationFn: API.updateUserChatCustomPrompt,
mutationFn: API.experimental.updateUserChatCustomPrompt,
onSuccess: async () => {
await queryClient.invalidateQueries({
queryKey: chatUserCustomPromptKey,
@@ -442,7 +445,8 @@ export const chatModelsKey = ["chat-models"] as const;
export const chatModels = () => ({
queryKey: chatModelsKey,
queryFn: (): Promise<TypesGen.ChatModelsResponse> => API.getChatModels(),
queryFn: (): Promise<TypesGen.ChatModelsResponse> =>
API.experimental.getChatModels(),
});
const chatProviderConfigsKey = ["chat-provider-configs"] as const;
@@ -450,14 +454,15 @@ const chatProviderConfigsKey = ["chat-provider-configs"] as const;
export const chatProviderConfigs = () => ({
queryKey: chatProviderConfigsKey,
queryFn: (): Promise<TypesGen.ChatProviderConfig[]> =>
API.getChatProviderConfigs(),
API.experimental.getChatProviderConfigs(),
});
const chatModelConfigsKey = ["chat-model-configs"] as const;
export const chatModelConfigs = () => ({
queryKey: chatModelConfigsKey,
queryFn: (): Promise<TypesGen.ChatModelConfig[]> => API.getChatModelConfigs(),
queryFn: (): Promise<TypesGen.ChatModelConfig[]> =>
API.experimental.getChatModelConfigs(),
});
const invalidateChatConfigurationQueries = async (queryClient: QueryClient) => {
@@ -470,7 +475,7 @@ const invalidateChatConfigurationQueries = async (queryClient: QueryClient) => {
export const createChatProviderConfig = (queryClient: QueryClient) => ({
mutationFn: (req: TypesGen.CreateChatProviderConfigRequest) =>
API.createChatProviderConfig(req),
API.experimental.createChatProviderConfig(req),
onSuccess: async () => {
await invalidateChatConfigurationQueries(queryClient);
},
@@ -486,7 +491,7 @@ export const updateChatProviderConfig = (queryClient: QueryClient) => ({
providerConfigId,
req,
}: UpdateChatProviderConfigMutationArgs) =>
API.updateChatProviderConfig(providerConfigId, req),
API.experimental.updateChatProviderConfig(providerConfigId, req),
onSuccess: async () => {
await invalidateChatConfigurationQueries(queryClient);
},
@@ -494,7 +499,7 @@ export const updateChatProviderConfig = (queryClient: QueryClient) => ({
export const deleteChatProviderConfig = (queryClient: QueryClient) => ({
mutationFn: (providerConfigId: string) =>
API.deleteChatProviderConfig(providerConfigId),
API.experimental.deleteChatProviderConfig(providerConfigId),
onSuccess: async () => {
await invalidateChatConfigurationQueries(queryClient);
},
@@ -502,7 +507,7 @@ export const deleteChatProviderConfig = (queryClient: QueryClient) => ({
export const createChatModelConfig = (queryClient: QueryClient) => ({
mutationFn: (req: TypesGen.CreateChatModelConfigRequest) =>
API.createChatModelConfig(req),
API.experimental.createChatModelConfig(req),
onSuccess: async () => {
await invalidateChatConfigurationQueries(queryClient);
},
@@ -515,7 +520,7 @@ type UpdateChatModelConfigMutationArgs = {
export const updateChatModelConfig = (queryClient: QueryClient) => ({
mutationFn: ({ modelConfigId, req }: UpdateChatModelConfigMutationArgs) =>
API.updateChatModelConfig(modelConfigId, req),
API.experimental.updateChatModelConfig(modelConfigId, req),
onSuccess: async () => {
await invalidateChatConfigurationQueries(queryClient);
},
@@ -523,7 +528,7 @@ export const updateChatModelConfig = (queryClient: QueryClient) => ({
export const deleteChatModelConfig = (queryClient: QueryClient) => ({
mutationFn: (modelConfigId: string) =>
API.deleteChatModelConfig(modelConfigId),
API.experimental.deleteChatModelConfig(modelConfigId),
onSuccess: async () => {
await invalidateChatConfigurationQueries(queryClient);
},
@@ -545,7 +550,7 @@ export const chatCostSummaryKey = (user = "me", params?: ChatCostDateParams) =>
export const chatCostSummary = (user = "me", params?: ChatCostDateParams) => ({
queryKey: chatCostSummaryKey(user, params),
queryFn: () => API.getChatCostSummary(user, params),
queryFn: () => API.experimental.getChatCostSummary(user, params),
staleTime: 60_000,
});
@@ -554,7 +559,7 @@ export const chatCostUsersKey = (params?: ChatCostUsersParams) =>
export const chatCostUsers = (params?: ChatCostUsersParams) => ({
queryKey: chatCostUsersKey(params),
queryFn: () => API.getChatCostUsers(params),
queryFn: () => API.experimental.getChatCostUsers(params),
staleTime: 60_000,
});
@@ -566,7 +571,7 @@ export const prInsights = (params?: {
end_date?: string;
}) => ({
queryKey: prInsightsKey(params),
queryFn: () => API.getPRInsights(params),
queryFn: () => API.experimental.getPRInsights(params),
staleTime: 60_000,
});
@@ -577,7 +582,7 @@ export const chatUsageLimitStatusKey = [
export const chatUsageLimitStatus = () => ({
queryKey: chatUsageLimitStatusKey,
queryFn: () => API.getChatUsageLimitStatus(),
queryFn: () => API.experimental.getChatUsageLimitStatus(),
refetchInterval: 60_000,
});
@@ -585,12 +590,12 @@ const chatUsageLimitConfigKey = [...chatsKey, "usageLimitConfig"] as const;
export const chatUsageLimitConfig = () => ({
queryKey: chatUsageLimitConfigKey,
queryFn: () => API.getChatUsageLimitConfig(),
queryFn: () => API.experimental.getChatUsageLimitConfig(),
});
export const updateChatUsageLimitConfig = (queryClient: QueryClient) => ({
mutationFn: (req: TypesGen.ChatUsageLimitConfig) =>
API.updateChatUsageLimitConfig(req),
API.experimental.updateChatUsageLimitConfig(req),
onSuccess: async () => {
await queryClient.invalidateQueries({
queryKey: chatUsageLimitConfigKey,
@@ -605,7 +610,7 @@ type UpsertChatUsageLimitOverrideMutationArgs = {
export const upsertChatUsageLimitOverride = (queryClient: QueryClient) => ({
mutationFn: ({ userID, req }: UpsertChatUsageLimitOverrideMutationArgs) =>
API.upsertChatUsageLimitOverride(userID, req),
API.experimental.upsertChatUsageLimitOverride(userID, req),
onSuccess: async () => {
await queryClient.invalidateQueries({
queryKey: chatUsageLimitConfigKey,
@@ -614,7 +619,8 @@ export const upsertChatUsageLimitOverride = (queryClient: QueryClient) => ({
});
export const deleteChatUsageLimitOverride = (queryClient: QueryClient) => ({
mutationFn: (userID: string) => API.deleteChatUsageLimitOverride(userID),
mutationFn: (userID: string) =>
API.experimental.deleteChatUsageLimitOverride(userID),
onSuccess: async () => {
await queryClient.invalidateQueries({
queryKey: chatUsageLimitConfigKey,
@@ -634,7 +640,7 @@ export const upsertChatUsageLimitGroupOverride = (
groupID,
req,
}: UpsertChatUsageLimitGroupOverrideMutationArgs) =>
API.upsertChatUsageLimitGroupOverride(groupID, req),
API.experimental.upsertChatUsageLimitGroupOverride(groupID, req),
onSuccess: async () => {
await queryClient.invalidateQueries({
queryKey: chatUsageLimitConfigKey,
@@ -646,7 +652,7 @@ export const deleteChatUsageLimitGroupOverride = (
queryClient: QueryClient,
) => ({
mutationFn: (groupID: string) =>
API.deleteChatUsageLimitGroupOverride(groupID),
API.experimental.deleteChatUsageLimitGroupOverride(groupID),
onSuccess: async () => {
await queryClient.invalidateQueries({
queryKey: chatUsageLimitConfigKey,
@@ -660,7 +666,8 @@ const mcpServerConfigsKey = ["mcp-server-configs"] as const;
export const mcpServerConfigs = () => ({
queryKey: mcpServerConfigsKey,
queryFn: (): Promise<TypesGen.MCPServerConfig[]> => API.getMCPServerConfigs(),
queryFn: (): Promise<TypesGen.MCPServerConfig[]> =>
API.experimental.getMCPServerConfigs(),
});
const invalidateMCPServerConfigQueries = async (queryClient: QueryClient) => {
@@ -669,7 +676,7 @@ const invalidateMCPServerConfigQueries = async (queryClient: QueryClient) => {
export const createMCPServerConfig = (queryClient: QueryClient) => ({
mutationFn: (req: TypesGen.CreateMCPServerConfigRequest) =>
API.createMCPServerConfig(req),
API.experimental.createMCPServerConfig(req),
onSuccess: async () => {
await invalidateMCPServerConfigQueries(queryClient);
},
@@ -682,14 +689,14 @@ type UpdateMCPServerConfigMutationArgs = {
export const updateMCPServerConfig = (queryClient: QueryClient) => ({
mutationFn: ({ id, req }: UpdateMCPServerConfigMutationArgs) =>
API.updateMCPServerConfig(id, req),
API.experimental.updateMCPServerConfig(id, req),
onSuccess: async () => {
await invalidateMCPServerConfigQueries(queryClient);
},
});
export const deleteMCPServerConfig = (queryClient: QueryClient) => ({
mutationFn: (id: string) => API.deleteMCPServerConfig(id),
mutationFn: (id: string) => API.experimental.deleteMCPServerConfig(id),
onSuccess: async () => {
await invalidateMCPServerConfigQueries(queryClient);
},
@@ -107,18 +107,21 @@ const mockCostSummary: TypesGen.ChatCostSummary = {
const setupUsageSpies = (opts?: {
usersResponse?: TypesGen.ChatCostUsersResponse;
}) => {
spyOn(API, "getChatCostUsers").mockResolvedValue(
spyOn(API.experimental, "getChatCostUsers").mockResolvedValue(
opts?.usersResponse ?? mockUsersResponse,
);
spyOn(API, "getUser").mockResolvedValue(mockUserProfile);
spyOn(API, "getChatCostSummary").mockResolvedValue(mockCostSummary);
spyOn(API.experimental, "getChatCostSummary").mockResolvedValue(
mockCostSummary,
);
};
const getChatCostUsersCalls = () =>
(
API.getChatCostUsers as typeof API.getChatCostUsers & {
API.experimental
.getChatCostUsers as typeof API.experimental.getChatCostUsers & {
mock: {
calls: Array<[Parameters<typeof API.getChatCostUsers>[0]]>;
calls: Array<[Parameters<typeof API.experimental.getChatCostUsers>[0]]>;
};
}
).mock.calls;
@@ -142,24 +145,24 @@ const meta = {
layout: "fullscreen",
},
beforeEach: () => {
spyOn(API, "getChatSystemPrompt").mockResolvedValue({
spyOn(API.experimental, "getChatSystemPrompt").mockResolvedValue({
system_prompt: "",
});
spyOn(API, "updateChatSystemPrompt").mockResolvedValue();
spyOn(API, "getChatDesktopEnabled").mockResolvedValue({
spyOn(API.experimental, "updateChatSystemPrompt").mockResolvedValue();
spyOn(API.experimental, "getChatDesktopEnabled").mockResolvedValue({
enable_desktop: false,
});
spyOn(API, "updateChatDesktopEnabled").mockResolvedValue();
spyOn(API, "getUserChatCustomPrompt").mockResolvedValue({
spyOn(API.experimental, "updateChatDesktopEnabled").mockResolvedValue();
spyOn(API.experimental, "getUserChatCustomPrompt").mockResolvedValue({
custom_prompt: "",
});
spyOn(API, "updateUserChatCustomPrompt").mockResolvedValue({
spyOn(API.experimental, "updateUserChatCustomPrompt").mockResolvedValue({
custom_prompt: "",
});
spyOn(API, "getChatWorkspaceTTL").mockResolvedValue({
spyOn(API.experimental, "getChatWorkspaceTTL").mockResolvedValue({
workspace_ttl_ms: 0,
});
spyOn(API, "updateChatWorkspaceTTL").mockResolvedValue();
spyOn(API.experimental, "updateChatWorkspaceTTL").mockResolvedValue();
},
} satisfies Meta<typeof AgentSettingsPageView>;
@@ -189,7 +192,7 @@ export const TogglesDesktop: Story = {
await userEvent.click(toggle);
await waitFor(() => {
expect(API.updateChatDesktopEnabled).toHaveBeenCalledWith({
expect(API.experimental.updateChatDesktopEnabled).toHaveBeenCalledWith({
enable_desktop: true,
});
});
@@ -220,7 +223,7 @@ export const DefaultAutostopDefault: Story = {
export const DefaultAutostopCustomValue: Story = {
beforeEach: () => {
// 2h = 2 hours exactly, shows cleanly in DurationField.
spyOn(API, "getChatWorkspaceTTL").mockResolvedValue({
spyOn(API.experimental, "getChatWorkspaceTTL").mockResolvedValue({
workspace_ttl_ms: 7_200_000,
});
},
@@ -256,7 +259,7 @@ export const DefaultAutostopSave: Story = {
await userEvent.click(saveButton);
await waitFor(() => {
expect(API.updateChatWorkspaceTTL).toHaveBeenCalledWith({
expect(API.experimental.updateChatWorkspaceTTL).toHaveBeenCalledWith({
workspace_ttl_ms: 10_800_000,
});
});
@@ -349,7 +352,7 @@ export const UsageDateFilter: Story = {
const defaultEndLabel = fixedNow.format("MMM D, YYYY");
await waitFor(() => {
expect(API.getChatCostUsers).toHaveBeenCalled();
expect(API.experimental.getChatCostUsers).toHaveBeenCalled();
});
const initialCallCount = getChatCostUsersCalls().length;
@@ -396,7 +399,7 @@ export const UsageDateFilterRefetchOverlay: Story = {
},
);
spyOn(API, "getChatCostUsers").mockImplementation(async () => {
spyOn(API.experimental, "getChatCostUsers").mockImplementation(async () => {
requestCount += 1;
if (requestCount === 1) {
return mockUsersResponse;
@@ -405,7 +408,9 @@ export const UsageDateFilterRefetchOverlay: Story = {
return refetchPromise;
});
spyOn(API, "getUser").mockResolvedValue(mockUserProfile);
spyOn(API, "getChatCostSummary").mockResolvedValue(mockCostSummary);
spyOn(API.experimental, "getChatCostSummary").mockResolvedValue(
mockCostSummary,
);
return () => {
resolveRefetch?.({
@@ -496,7 +501,7 @@ export const UsageUserDrillIn: Story = {
// The cost summary should have been fetched.
await waitFor(() => {
expect(API.getChatCostSummary).toHaveBeenCalled();
expect(API.experimental.getChatCostSummary).toHaveBeenCalled();
});
// The Back button should be visible.
+1 -1
View File
@@ -163,7 +163,7 @@ const AgentsPage: FC = () => {
chatId: string;
workspaceId: string;
}) => {
await API.updateChat(chatId, { archived: true });
await API.experimental.updateChat(chatId, { archived: true });
await API.deleteWorkspace(workspaceId);
return { chatId, workspaceId };
},
@@ -191,20 +191,24 @@ const meta: Meta<typeof AgentsPageView> = {
workspaces: [],
count: 0,
});
spyOn(API, "getChatCostSummary").mockResolvedValue(mockAnalyticsSummary);
spyOn(API, "getChatCostUsers").mockResolvedValue(mockUsageUsers);
spyOn(API, "getChatSystemPrompt").mockResolvedValue({
spyOn(API.experimental, "getChatCostSummary").mockResolvedValue(
mockAnalyticsSummary,
);
spyOn(API.experimental, "getChatCostUsers").mockResolvedValue(
mockUsageUsers,
);
spyOn(API.experimental, "getChatSystemPrompt").mockResolvedValue({
system_prompt: "",
});
spyOn(API, "updateChatSystemPrompt").mockResolvedValue();
spyOn(API, "getUserChatCustomPrompt").mockResolvedValue({
spyOn(API.experimental, "updateChatSystemPrompt").mockResolvedValue();
spyOn(API.experimental, "getUserChatCustomPrompt").mockResolvedValue({
custom_prompt: "",
});
spyOn(API, "updateUserChatCustomPrompt").mockResolvedValue({
spyOn(API.experimental, "updateUserChatCustomPrompt").mockResolvedValue({
custom_prompt: "",
});
// Mocks for child route pages that fetch their own data.
spyOn(API, "getChatModels").mockResolvedValue({
spyOn(API.experimental, "getChatModels").mockResolvedValue({
providers: [
{
provider: "openai",
@@ -220,7 +224,7 @@ const meta: Meta<typeof AgentsPageView> = {
},
],
});
spyOn(API, "getChatModelConfigs").mockResolvedValue([
spyOn(API.experimental, "getChatModelConfigs").mockResolvedValue([
{
id: "config-openai-gpt-4o",
provider: "openai",
@@ -234,13 +238,13 @@ const meta: Meta<typeof AgentsPageView> = {
updated_at: "2026-02-18T00:00:00.000Z",
},
]);
spyOn(API, "getChatDesktopEnabled").mockResolvedValue({
spyOn(API.experimental, "getChatDesktopEnabled").mockResolvedValue({
enable_desktop: false,
});
spyOn(API, "getChatWorkspaceTTL").mockResolvedValue({
spyOn(API.experimental, "getChatWorkspaceTTL").mockResolvedValue({
workspace_ttl_ms: 0,
});
spyOn(API, "updateChatWorkspaceTTL").mockResolvedValue();
spyOn(API.experimental, "updateChatWorkspaceTTL").mockResolvedValue();
},
};
@@ -207,7 +207,7 @@ export const WithSidebarPanel: Story = {
} satisfies ChatDiffStatus,
},
beforeEach: () => {
spyOn(API, "getChatDiffContents").mockResolvedValue({
spyOn(API.experimental, "getChatDiffContents").mockResolvedValue({
chat_id: AGENT_ID,
diff: `diff --git a/src/main.ts b/src/main.ts
index abc1234..def5678 100644
@@ -54,33 +54,39 @@ const setupChatSpies = (state: {
modelConfigs: TypesGen.ChatModelConfig[];
modelCatalog: TypesGen.ChatModelsResponse;
}) => {
spyOn(API, "getChatProviderConfigs").mockImplementation(async () => {
return state.providerConfigs;
});
spyOn(API, "getChatModelConfigs").mockImplementation(async () => {
return state.modelConfigs;
});
spyOn(API, "getChatModels").mockImplementation(async () => {
spyOn(API.experimental, "getChatProviderConfigs").mockImplementation(
async () => {
return state.providerConfigs;
},
);
spyOn(API.experimental, "getChatModelConfigs").mockImplementation(
async () => {
return state.modelConfigs;
},
);
spyOn(API.experimental, "getChatModels").mockImplementation(async () => {
return state.modelCatalog;
});
spyOn(API, "createChatProviderConfig").mockImplementation(async (req) => {
const created = createProviderConfig({
id: `provider-${Date.now()}`,
provider: req.provider,
display_name: req.display_name ?? "",
has_api_key: (req.api_key ?? "").trim().length > 0,
base_url: req.base_url ?? "",
source: "database",
});
state.providerConfigs = [
...state.providerConfigs.filter((p) => p.provider !== req.provider),
created,
];
return created;
});
spyOn(API.experimental, "createChatProviderConfig").mockImplementation(
async (req) => {
const created = createProviderConfig({
id: `provider-${Date.now()}`,
provider: req.provider,
display_name: req.display_name ?? "",
has_api_key: (req.api_key ?? "").trim().length > 0,
base_url: req.base_url ?? "",
source: "database",
});
state.providerConfigs = [
...state.providerConfigs.filter((p) => p.provider !== req.provider),
created,
];
return created;
},
);
spyOn(API, "updateChatProviderConfig").mockImplementation(
spyOn(API.experimental, "updateChatProviderConfig").mockImplementation(
async (providerConfigId, req) => {
const idx = state.providerConfigs.findIndex(
(p) => p.id === providerConfigId,
@@ -110,29 +116,31 @@ const setupChatSpies = (state: {
},
);
spyOn(API, "createChatModelConfig").mockImplementation(async (req) => {
const created = createModelConfig({
id: `model-${state.modelConfigs.length + 1}`,
provider: req.provider,
model: req.model,
display_name: req.display_name || req.model,
context_limit:
typeof req.context_limit === "number" &&
Number.isFinite(req.context_limit)
? req.context_limit
: 200000,
compression_threshold:
typeof req.compression_threshold === "number" &&
Number.isFinite(req.compression_threshold)
? req.compression_threshold
: 70,
model_config: req.model_config,
});
state.modelConfigs = [...state.modelConfigs, created];
return created;
});
spyOn(API.experimental, "createChatModelConfig").mockImplementation(
async (req) => {
const created = createModelConfig({
id: `model-${state.modelConfigs.length + 1}`,
provider: req.provider,
model: req.model,
display_name: req.display_name || req.model,
context_limit:
typeof req.context_limit === "number" &&
Number.isFinite(req.context_limit)
? req.context_limit
: 200000,
compression_threshold:
typeof req.compression_threshold === "number" &&
Number.isFinite(req.compression_threshold)
? req.compression_threshold
: 70,
model_config: req.model_config,
});
state.modelConfigs = [...state.modelConfigs, created];
return created;
},
);
spyOn(API, "deleteChatModelConfig").mockImplementation(
spyOn(API.experimental, "deleteChatModelConfig").mockImplementation(
async (modelConfigId) => {
state.modelConfigs = state.modelConfigs.filter(
(m) => m.id !== modelConfigId,
@@ -141,8 +149,10 @@ const setupChatSpies = (state: {
);
// Unused but mock to avoid errors.
spyOn(API, "deleteChatProviderConfig").mockResolvedValue(undefined);
spyOn(API, "updateChatModelConfig").mockResolvedValue(
spyOn(API.experimental, "deleteChatProviderConfig").mockResolvedValue(
undefined,
);
spyOn(API.experimental, "updateChatModelConfig").mockResolvedValue(
createModelConfig({
id: "stub",
provider: "stub",
@@ -311,9 +321,11 @@ export const CreateAndUpdateProvider: Story = {
// The create spy should have been called.
await waitFor(() => {
expect(API.createChatProviderConfig).toHaveBeenCalledTimes(1);
expect(API.experimental.createChatProviderConfig).toHaveBeenCalledTimes(
1,
);
});
expect(API.createChatProviderConfig).toHaveBeenCalledWith(
expect(API.experimental.createChatProviderConfig).toHaveBeenCalledWith(
expect.objectContaining({
provider: "openai",
api_key: "sk-provider-key",
@@ -342,9 +354,11 @@ export const CreateAndUpdateProvider: Story = {
await userEvent.click(body.getByRole("button", { name: "Save changes" }));
await waitFor(() => {
expect(API.updateChatProviderConfig).toHaveBeenCalledTimes(1);
expect(API.experimental.updateChatProviderConfig).toHaveBeenCalledTimes(
1,
);
});
expect(API.updateChatProviderConfig).toHaveBeenCalledWith(
expect(API.experimental.updateChatProviderConfig).toHaveBeenCalledWith(
expect.any(String),
expect.objectContaining({
api_key: "sk-updated-provider-key",
@@ -413,9 +427,9 @@ export const NoModelConfigByDefault: Story = {
// The submit button in ModelForm also says "Add model".
await userEvent.click(body.getByRole("button", { name: "Add model" }));
await waitFor(() => {
expect(API.createChatModelConfig).toHaveBeenCalledTimes(1);
expect(API.experimental.createChatModelConfig).toHaveBeenCalledTimes(1);
});
expect(API.createChatModelConfig).toHaveBeenCalledWith(
expect(API.experimental.createChatModelConfig).toHaveBeenCalledWith(
expect.objectContaining({
provider: "openai",
model: "gpt-5-pro",
@@ -423,7 +437,9 @@ export const NoModelConfigByDefault: Story = {
);
// Blank pricing fields should remain unset in the payload.
const callArgs = (
API.createChatModelConfig as unknown as ReturnType<typeof spyOn>
API.experimental.createChatModelConfig as unknown as ReturnType<
typeof spyOn
>
).mock.calls[0][0] as Record<string, unknown>;
expect(callArgs).not.toHaveProperty("model_config");
},
@@ -472,9 +488,9 @@ export const SubmitModelConfigExplicitly: Story = {
await userEvent.click(body.getByRole("button", { name: "Add model" }));
await waitFor(() => {
expect(API.createChatModelConfig).toHaveBeenCalledTimes(1);
expect(API.experimental.createChatModelConfig).toHaveBeenCalledTimes(1);
});
expect(API.createChatModelConfig).toHaveBeenCalledWith(
expect(API.experimental.createChatModelConfig).toHaveBeenCalledWith(
expect.objectContaining({
provider: "openai",
model: "gpt-5-pro-custom",
@@ -786,9 +802,11 @@ export const ModelDeleteConfirmed: Story = {
// The delete API should have been called.
await waitFor(() => {
expect(API.deleteChatModelConfig).toHaveBeenCalledTimes(1);
expect(API.experimental.deleteChatModelConfig).toHaveBeenCalledTimes(1);
});
expect(API.deleteChatModelConfig).toHaveBeenCalledWith("model-1");
expect(API.experimental.deleteChatModelConfig).toHaveBeenCalledWith(
"model-1",
);
},
};
@@ -898,9 +916,11 @@ export const ProviderDeleteConfirmed: Story = {
// The delete API should have been called.
await waitFor(() => {
expect(API.deleteChatProviderConfig).toHaveBeenCalledTimes(1);
expect(API.experimental.deleteChatProviderConfig).toHaveBeenCalledTimes(
1,
);
});
expect(API.deleteChatProviderConfig).toHaveBeenCalledWith(
expect(API.experimental.deleteChatProviderConfig).toHaveBeenCalledWith(
"provider-openai",
);
},
@@ -940,6 +960,6 @@ export const ValidatesModelConfigFields: Story = {
expect(body.getByRole("button", { name: "Add model" })).toBeDisabled();
});
// No API call should have been made.
expect(API.createChatModelConfig).not.toHaveBeenCalled();
expect(API.experimental.createChatModelConfig).not.toHaveBeenCalled();
},
};
@@ -111,7 +111,9 @@ const meta: Meta<typeof GitPanel> = {
),
],
beforeEach: () => {
spyOn(API, "getChatDiffContents").mockResolvedValue(defaultDiffContents);
spyOn(API.experimental, "getChatDiffContents").mockResolvedValue(
defaultDiffContents,
);
},
};
@@ -130,7 +132,7 @@ export const PullRequestAndWorkingChanges: Story = {
repositories: new Map([["/home/coder/coder", makeRepo()]]),
},
beforeEach: () => {
spyOn(API, "getChatDiffContents").mockResolvedValue({
spyOn(API.experimental, "getChatDiffContents").mockResolvedValue({
...defaultDiffContents,
diff: sampleDiff,
});
@@ -155,7 +157,7 @@ export const DraftPullRequest: Story = {
]),
},
beforeEach: () => {
spyOn(API, "getChatDiffContents").mockResolvedValue({
spyOn(API.experimental, "getChatDiffContents").mockResolvedValue({
...defaultDiffContents,
diff: sampleDiff,
});
@@ -177,7 +179,7 @@ export const MergedPullRequest: Story = {
}),
},
beforeEach: () => {
spyOn(API, "getChatDiffContents").mockResolvedValue({
spyOn(API.experimental, "getChatDiffContents").mockResolvedValue({
...defaultDiffContents,
diff: sampleDiff,
});
@@ -199,7 +201,7 @@ export const ClosedPullRequest: Story = {
}),
},
beforeEach: () => {
spyOn(API, "getChatDiffContents").mockResolvedValue({
spyOn(API.experimental, "getChatDiffContents").mockResolvedValue({
...defaultDiffContents,
diff: sampleDiff,
});
@@ -251,7 +253,7 @@ export const MultipleRepos: Story = {
]),
},
beforeEach: () => {
spyOn(API, "getChatDiffContents").mockResolvedValue({
spyOn(API.experimental, "getChatDiffContents").mockResolvedValue({
...defaultDiffContents,
diff: sampleDiff,
});
@@ -283,7 +285,7 @@ export const InlineCommentInput: Story = {
),
],
beforeEach: () => {
spyOn(API, "getChatDiffContents").mockResolvedValue({
spyOn(API.experimental, "getChatDiffContents").mockResolvedValue({
...defaultDiffContents,
diff: sampleDiff,
});
@@ -44,59 +44,67 @@ const createServerConfig = (
* on refetch, mimicking a real server round-trip.
*/
const setupMCPSpies = (state: { servers: TypesGen.MCPServerConfig[] }) => {
spyOn(API, "getMCPServerConfigs").mockImplementation(async () => {
return state.servers;
});
spyOn(API.experimental, "getMCPServerConfigs").mockImplementation(
async () => {
return state.servers;
},
);
spyOn(API, "createMCPServerConfig").mockImplementation(async (req) => {
const created = createServerConfig({
id: `mcp-${Date.now()}`,
display_name: req.display_name,
slug: req.slug,
description: req.description,
icon_url: req.icon_url,
transport: req.transport,
url: req.url,
auth_type: req.auth_type,
availability: req.availability,
enabled: req.enabled,
has_oauth2_secret: (req.oauth2_client_secret ?? "").length > 0,
has_api_key: (req.api_key_value ?? "").length > 0,
has_custom_headers:
req.custom_headers != null &&
Object.keys(req.custom_headers).length > 0,
tool_allow_list: req.tool_allow_list ?? [],
tool_deny_list: req.tool_deny_list ?? [],
});
state.servers = [...state.servers, created];
return created;
});
spyOn(API.experimental, "createMCPServerConfig").mockImplementation(
async (req) => {
const created = createServerConfig({
id: `mcp-${Date.now()}`,
display_name: req.display_name,
slug: req.slug,
description: req.description,
icon_url: req.icon_url,
transport: req.transport,
url: req.url,
auth_type: req.auth_type,
availability: req.availability,
enabled: req.enabled,
has_oauth2_secret: (req.oauth2_client_secret ?? "").length > 0,
has_api_key: (req.api_key_value ?? "").length > 0,
has_custom_headers:
req.custom_headers != null &&
Object.keys(req.custom_headers).length > 0,
tool_allow_list: req.tool_allow_list ?? [],
tool_deny_list: req.tool_deny_list ?? [],
});
state.servers = [...state.servers, created];
return created;
},
);
spyOn(API, "updateMCPServerConfig").mockImplementation(async (id, req) => {
const idx = state.servers.findIndex((s) => s.id === id);
if (idx < 0) {
throw new Error("MCP server config not found.");
}
const current = state.servers[idx];
const updated: TypesGen.MCPServerConfig = {
...current,
display_name: req.display_name ?? current.display_name,
slug: req.slug ?? current.slug,
description: req.description ?? current.description,
url: req.url ?? current.url,
transport: req.transport ?? current.transport,
auth_type: req.auth_type ?? current.auth_type,
availability: req.availability ?? current.availability,
enabled: req.enabled ?? current.enabled,
updated_at: now,
};
state.servers = state.servers.map((s, i) => (i === idx ? updated : s));
return updated;
});
spyOn(API.experimental, "updateMCPServerConfig").mockImplementation(
async (id, req) => {
const idx = state.servers.findIndex((s) => s.id === id);
if (idx < 0) {
throw new Error("MCP server config not found.");
}
const current = state.servers[idx];
const updated: TypesGen.MCPServerConfig = {
...current,
display_name: req.display_name ?? current.display_name,
slug: req.slug ?? current.slug,
description: req.description ?? current.description,
url: req.url ?? current.url,
transport: req.transport ?? current.transport,
auth_type: req.auth_type ?? current.auth_type,
availability: req.availability ?? current.availability,
enabled: req.enabled ?? current.enabled,
updated_at: now,
};
state.servers = state.servers.map((s, i) => (i === idx ? updated : s));
return updated;
},
);
spyOn(API, "deleteMCPServerConfig").mockImplementation(async (id) => {
state.servers = state.servers.filter((s) => s.id !== id);
});
spyOn(API.experimental, "deleteMCPServerConfig").mockImplementation(
async (id) => {
state.servers = state.servers.filter((s) => s.id !== id);
},
);
};
// ── Meta ───────────────────────────────────────────────────────
@@ -217,9 +225,9 @@ export const CreateServer: Story = {
await userEvent.click(body.getByRole("button", { name: /Create server/i }));
await waitFor(() => {
expect(API.createMCPServerConfig).toHaveBeenCalledTimes(1);
expect(API.experimental.createMCPServerConfig).toHaveBeenCalledTimes(1);
});
expect(API.createMCPServerConfig).toHaveBeenCalledWith(
expect(API.experimental.createMCPServerConfig).toHaveBeenCalledWith(
expect.objectContaining({
display_name: "Sentry",
slug: "sentry",
@@ -268,9 +276,9 @@ export const CreateServerOAuth2: Story = {
await userEvent.click(body.getByRole("button", { name: /Create server/i }));
await waitFor(() => {
expect(API.createMCPServerConfig).toHaveBeenCalledTimes(1);
expect(API.experimental.createMCPServerConfig).toHaveBeenCalledTimes(1);
});
expect(API.createMCPServerConfig).toHaveBeenCalledWith(
expect(API.experimental.createMCPServerConfig).toHaveBeenCalledWith(
expect.objectContaining({
auth_type: "oauth2",
oauth2_client_id: "my-client-id",
@@ -316,9 +324,9 @@ export const CreateServerAPIKey: Story = {
await userEvent.click(body.getByRole("button", { name: /Create server/i }));
await waitFor(() => {
expect(API.createMCPServerConfig).toHaveBeenCalledTimes(1);
expect(API.experimental.createMCPServerConfig).toHaveBeenCalledTimes(1);
});
expect(API.createMCPServerConfig).toHaveBeenCalledWith(
expect(API.experimental.createMCPServerConfig).toHaveBeenCalledWith(
expect.objectContaining({
auth_type: "api_key",
api_key_header: "Authorization",
@@ -369,9 +377,9 @@ export const EditServer: Story = {
await userEvent.click(body.getByRole("button", { name: /Save changes/i }));
await waitFor(() => {
expect(API.updateMCPServerConfig).toHaveBeenCalledTimes(1);
expect(API.experimental.updateMCPServerConfig).toHaveBeenCalledTimes(1);
});
expect(API.updateMCPServerConfig).toHaveBeenCalledWith(
expect(API.experimental.updateMCPServerConfig).toHaveBeenCalledWith(
"mcp-sentry",
expect.objectContaining({
description: "Sentry error tracking integration",
@@ -460,9 +468,9 @@ export const EditServerWithCustomHeaders: Story = {
await userEvent.click(body.getByRole("button", { name: /Save changes/i }));
await waitFor(() => {
expect(API.updateMCPServerConfig).toHaveBeenCalledTimes(1);
expect(API.experimental.updateMCPServerConfig).toHaveBeenCalledTimes(1);
});
expect(API.updateMCPServerConfig).toHaveBeenCalledWith(
expect(API.experimental.updateMCPServerConfig).toHaveBeenCalledWith(
"mcp-custom",
expect.objectContaining({
custom_headers: { Authorization: "Bearer tok_abc" },
@@ -552,9 +560,11 @@ export const DeleteServerConfirmed: Story = {
await userEvent.click(body.getByRole("button", { name: /Delete server/i }));
await waitFor(() => {
expect(API.deleteMCPServerConfig).toHaveBeenCalledTimes(1);
expect(API.experimental.deleteMCPServerConfig).toHaveBeenCalledTimes(1);
});
expect(API.deleteMCPServerConfig).toHaveBeenCalledWith("mcp-sentry");
expect(API.experimental.deleteMCPServerConfig).toHaveBeenCalledWith(
"mcp-sentry",
);
},
};
@@ -586,7 +596,7 @@ export const BackToList: Story = {
await body.findByRole("button", { name: /Sentry/ }),
).toBeInTheDocument();
expect(API.createMCPServerConfig).not.toHaveBeenCalled();
expect(API.experimental.createMCPServerConfig).not.toHaveBeenCalled();
},
};
@@ -623,9 +633,9 @@ export const CreateServerWithToolGovernance: Story = {
await userEvent.click(body.getByRole("button", { name: /Create server/i }));
await waitFor(() => {
expect(API.createMCPServerConfig).toHaveBeenCalledTimes(1);
expect(API.experimental.createMCPServerConfig).toHaveBeenCalledTimes(1);
});
expect(API.createMCPServerConfig).toHaveBeenCalledWith(
expect(API.experimental.createMCPServerConfig).toHaveBeenCalledWith(
expect.objectContaining({
tool_allow_list: ["search", "read_file"],
tool_deny_list: ["delete_file", "execute"],
@@ -676,9 +686,9 @@ export const CustomHeadersAuthType: Story = {
await userEvent.click(body.getByRole("button", { name: /Create server/i }));
await waitFor(() => {
expect(API.createMCPServerConfig).toHaveBeenCalledTimes(1);
expect(API.experimental.createMCPServerConfig).toHaveBeenCalledTimes(1);
});
expect(API.createMCPServerConfig).toHaveBeenCalledWith(
expect(API.experimental.createMCPServerConfig).toHaveBeenCalledWith(
expect.objectContaining({
auth_type: "custom_headers",
custom_headers: { "X-Api-Token": "secret-token-123" },
@@ -57,7 +57,10 @@ export function useFileAttachments(
setUploadStates((prev) => new Map(prev).set(file, { status: "uploading" }));
void (async () => {
try {
const result = await API.uploadChatFile(file, organizationId);
const result = await API.experimental.uploadChatFile(
file,
organizationId,
);
setUploadStates((prev) =>
new Map(prev).set(file, {
status: "uploaded",