Skip to content

Commit 833bf86

Browse files
committed
feat(site): add chat debug API layer and panel utilities
Add API client methods, React Query builders, and unit tests for the chat debug endpoints. Add debugPanelUtils with coercion helpers that transform raw debug step data into structured display models for the Debug panel, and wire debug run streaming into the chat store. Signed-off-by: Thomas Kosiewski <tk@coder.com>
1 parent 7a26ad4 commit 833bf86

7 files changed

Lines changed: 1461 additions & 3 deletions

File tree

site/.knip.jsonc

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22
"$schema": "https://unpkg.com/knip@5/schema.json",
33
"entry": ["./src/index.tsx", "./src/serviceWorker.ts"],
44
"project": ["./src/**/*.ts", "./src/**/*.tsx", "./e2e/**/*.ts"],
5-
"ignore": ["**/*Generated.ts", "src/api/chatModelOptions.ts"],
5+
"ignore": [
6+
"**/*Generated.ts",
7+
"src/api/chatModelOptions.ts",
8+
"src/pages/AgentsPage/components/RightPanel/DebugPanel/debugPanelUtils.ts"
9+
],
610
"ignoreBinaries": ["protoc"],
711
"ignoreDependencies": [
812
"@babel/plugin-syntax-typescript",

site/src/api/api.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3241,6 +3241,54 @@ class ExperimentalApiMethods {
32413241
await this.axios.put("/api/experimental/chats/config/system-prompt", req);
32423242
};
32433243

3244+
getChatDebugLogging = async (): Promise<TypesGen.ChatDebugSettings> => {
3245+
const response = await this.axios.get<TypesGen.ChatDebugSettings>(
3246+
"/api/experimental/chats/config/debug-logging",
3247+
);
3248+
return response.data;
3249+
};
3250+
3251+
updateChatDebugLogging = async (
3252+
req: TypesGen.UpdateChatDebugLoggingRequest,
3253+
): Promise<void> => {
3254+
await this.axios.put("/api/experimental/chats/config/debug-logging", req);
3255+
};
3256+
3257+
getChatUserDebugLogging = async (): Promise<TypesGen.ChatDebugSettings> => {
3258+
const response = await this.axios.get<TypesGen.ChatDebugSettings>(
3259+
"/api/experimental/chats/config/user-debug-logging",
3260+
);
3261+
return response.data;
3262+
};
3263+
3264+
updateChatUserDebugLogging = async (
3265+
req: TypesGen.UpdateChatDebugLoggingRequest,
3266+
): Promise<void> => {
3267+
await this.axios.put(
3268+
"/api/experimental/chats/config/user-debug-logging",
3269+
req,
3270+
);
3271+
};
3272+
3273+
getChatDebugRuns = async (
3274+
chatId: string,
3275+
): Promise<TypesGen.ChatDebugRunSummary[]> => {
3276+
const response = await this.axios.get<TypesGen.ChatDebugRunSummary[]>(
3277+
`/api/experimental/chats/${chatId}/debug/runs`,
3278+
);
3279+
return response.data;
3280+
};
3281+
3282+
getChatDebugRun = async (
3283+
chatId: string,
3284+
runId: string,
3285+
): Promise<TypesGen.ChatDebugRun> => {
3286+
const response = await this.axios.get<TypesGen.ChatDebugRun>(
3287+
`/api/experimental/chats/${chatId}/debug/runs/${runId}`,
3288+
);
3289+
return response.data;
3290+
};
3291+
32443292
getChatDesktopEnabled =
32453293
async (): Promise<TypesGen.ChatDesktopEnabledResponse> => {
32463294
const response =

site/src/api/queries/chats.test.ts

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,14 @@ import {
99
chatCostSummaryKey,
1010
chatCostUsers,
1111
chatCostUsersKey,
12+
chatDebugLogging,
13+
chatDebugRun,
14+
chatDebugRuns,
1215
chatDiffContentsKey,
1316
chatKey,
1417
chatMessagesKey,
1518
chatsKey,
19+
chatUserDebugLogging,
1620
createChat,
1721
createChatMessage,
1822
deleteChatQueuedMessage,
@@ -26,6 +30,8 @@ import {
2630
reorderPinnedChat,
2731
unarchiveChat,
2832
unpinChat,
33+
updateChatDebugLogging,
34+
updateChatUserDebugLogging,
2935
updateInfiniteChatsCache,
3036
} from "./chats";
3137

@@ -38,6 +44,12 @@ vi.mock("#/api/api", () => ({
3844
getChats: vi.fn(),
3945
getChatCostSummary: vi.fn(),
4046
getChatCostUsers: vi.fn(),
47+
getChatDebugLogging: vi.fn(),
48+
updateChatDebugLogging: vi.fn(),
49+
getChatUserDebugLogging: vi.fn(),
50+
updateChatUserDebugLogging: vi.fn(),
51+
getChatDebugRuns: vi.fn(),
52+
getChatDebugRun: vi.fn(),
4153
createChatMessage: vi.fn(),
4254
editChatMessage: vi.fn(),
4355
interruptChat: vi.fn(),
@@ -107,6 +119,116 @@ const createTestQueryClient = (): QueryClient =>
107119
},
108120
});
109121

122+
describe("chat debug queries", () => {
123+
it("builds the expected chat debug logging query", async () => {
124+
const settings = {
125+
debug_logging_enabled: true,
126+
} satisfies TypesGen.ChatDebugSettings;
127+
vi.mocked(API.experimental.getChatDebugLogging).mockResolvedValue(settings);
128+
129+
const query = chatDebugLogging();
130+
131+
expect(query.queryKey).toEqual(["chatDebugLogging"]);
132+
await expect(query.queryFn()).resolves.toEqual(settings);
133+
});
134+
135+
it("invalidates chat debug logging after updates", async () => {
136+
const queryClient = createTestQueryClient();
137+
const invalidateSpy = vi.spyOn(queryClient, "invalidateQueries");
138+
const req = {
139+
debug_logging_enabled: true,
140+
} satisfies TypesGen.UpdateChatDebugLoggingRequest;
141+
vi.mocked(API.experimental.updateChatDebugLogging).mockResolvedValue();
142+
143+
const mutation = updateChatDebugLogging(queryClient);
144+
await expect(mutation.mutationFn(req)).resolves.toBeUndefined();
145+
await mutation.onSuccess();
146+
147+
expect(API.experimental.updateChatDebugLogging).toHaveBeenCalledWith(req);
148+
expect(invalidateSpy).toHaveBeenCalledWith({
149+
queryKey: ["chatDebugLogging"],
150+
});
151+
});
152+
153+
it("builds the expected chat user debug logging query", async () => {
154+
const settings = {
155+
debug_logging_enabled: false,
156+
} satisfies TypesGen.ChatDebugSettings;
157+
vi.mocked(API.experimental.getChatUserDebugLogging).mockResolvedValue(
158+
settings,
159+
);
160+
161+
const query = chatUserDebugLogging();
162+
163+
expect(query.queryKey).toEqual(["chatUserDebugLogging"]);
164+
await expect(query.queryFn()).resolves.toEqual(settings);
165+
});
166+
167+
it("invalidates chat user debug logging after updates", async () => {
168+
const queryClient = createTestQueryClient();
169+
const invalidateSpy = vi.spyOn(queryClient, "invalidateQueries");
170+
const req = {
171+
debug_logging_enabled: false,
172+
} satisfies TypesGen.UpdateChatDebugLoggingRequest;
173+
vi.mocked(API.experimental.updateChatUserDebugLogging).mockResolvedValue();
174+
175+
const mutation = updateChatUserDebugLogging(queryClient);
176+
await expect(mutation.mutationFn(req)).resolves.toBeUndefined();
177+
await mutation.onSuccess();
178+
179+
expect(API.experimental.updateChatUserDebugLogging).toHaveBeenCalledWith(
180+
req,
181+
);
182+
expect(invalidateSpy).toHaveBeenCalledWith({
183+
queryKey: ["chatUserDebugLogging"],
184+
});
185+
});
186+
187+
it("builds the expected chat debug runs query", async () => {
188+
const chatId = "chat-1";
189+
const runs = [
190+
{
191+
id: "run-1",
192+
chat_id: chatId,
193+
kind: "message",
194+
status: "running",
195+
summary: {},
196+
started_at: "2025-01-01T00:00:00.000Z",
197+
updated_at: "2025-01-01T00:00:00.000Z",
198+
},
199+
] satisfies TypesGen.ChatDebugRunSummary[];
200+
vi.mocked(API.experimental.getChatDebugRuns).mockResolvedValue(runs);
201+
202+
const query = chatDebugRuns(chatId);
203+
204+
expect(query.queryKey).toEqual(["chats", chatId, "debug-runs"]);
205+
expect(query.refetchInterval).toBe(5_000);
206+
expect(query.refetchIntervalInBackground).toBe(false);
207+
await expect(query.queryFn()).resolves.toEqual(runs);
208+
});
209+
210+
it("builds the expected chat debug run query", async () => {
211+
const chatId = "chat-1";
212+
const runId = "run-1";
213+
const run = {
214+
id: runId,
215+
chat_id: chatId,
216+
kind: "message",
217+
status: "running",
218+
summary: {},
219+
started_at: "2025-01-01T00:00:00.000Z",
220+
updated_at: "2025-01-01T00:00:00.000Z",
221+
steps: [],
222+
} satisfies TypesGen.ChatDebugRun;
223+
vi.mocked(API.experimental.getChatDebugRun).mockResolvedValue(run);
224+
225+
const query = chatDebugRun(chatId, runId);
226+
227+
expect(query.queryKey).toEqual(["chats", chatId, "debug-runs", runId]);
228+
await expect(query.queryFn()).resolves.toEqual(run);
229+
});
230+
});
231+
110232
describe("invalidateChatListQueries", () => {
111233
it("invalidates flat and infinite chat list queries", async () => {
112234
const queryClient = createTestQueryClient();

site/src/api/queries/chats.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -680,6 +680,56 @@ export const updateChatSystemPrompt = (queryClient: QueryClient) => ({
680680
},
681681
});
682682

683+
const chatDebugLoggingKey = ["chatDebugLogging"] as const;
684+
685+
export const chatDebugLogging = () => ({
686+
queryKey: chatDebugLoggingKey,
687+
queryFn: () => API.experimental.getChatDebugLogging(),
688+
});
689+
690+
export const updateChatDebugLogging = (queryClient: QueryClient) => ({
691+
mutationFn: API.experimental.updateChatDebugLogging,
692+
onSuccess: async () => {
693+
await queryClient.invalidateQueries({
694+
queryKey: chatDebugLoggingKey,
695+
});
696+
},
697+
});
698+
699+
const chatUserDebugLoggingKey = ["chatUserDebugLogging"] as const;
700+
701+
export const chatUserDebugLogging = () => ({
702+
queryKey: chatUserDebugLoggingKey,
703+
queryFn: () => API.experimental.getChatUserDebugLogging(),
704+
});
705+
706+
export const updateChatUserDebugLogging = (queryClient: QueryClient) => ({
707+
mutationFn: API.experimental.updateChatUserDebugLogging,
708+
onSuccess: async () => {
709+
await queryClient.invalidateQueries({
710+
queryKey: chatUserDebugLoggingKey,
711+
});
712+
},
713+
});
714+
715+
export const chatDebugRunsKey = (chatId: string) =>
716+
["chats", chatId, "debug-runs"] as const;
717+
718+
export const chatDebugRuns = (chatId: string) => ({
719+
queryKey: chatDebugRunsKey(chatId),
720+
queryFn: () => API.experimental.getChatDebugRuns(chatId),
721+
refetchInterval: 5_000,
722+
refetchIntervalInBackground: false,
723+
});
724+
725+
const chatDebugRunKey = (chatId: string, runId: string) =>
726+
["chats", chatId, "debug-runs", runId] as const;
727+
728+
export const chatDebugRun = (chatId: string, runId: string) => ({
729+
queryKey: chatDebugRunKey(chatId, runId),
730+
queryFn: () => API.experimental.getChatDebugRun(chatId, runId),
731+
});
732+
683733
const chatDesktopEnabledKey = ["chat-desktop-enabled"] as const;
684734

685735
export const chatDesktopEnabled = () => ({

0 commit comments

Comments
 (0)