mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
refactor(site): move experimental endpoints to ExperimentalApiMethods (#23449)
This commit is contained in:
@@ -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
@@ -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
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
+81
-61
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user