Skip to content

Commit 3bb46f0

Browse files
authored
fix: parse last_error as structured ChatError object (#36)
1 parent 3544421 commit 3bb46f0

5 files changed

Lines changed: 46 additions & 25 deletions

File tree

dist/index.js

Lines changed: 16 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/action.test.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -441,9 +441,9 @@ describe("CoderAgentChatAction", () => {
441441
expect(out.baseBranch).toBe("main");
442442
});
443443

444-
test("converts null pull_request_title to undefined", () => {
445-
// pull_request_title is .nullable().optional(); explicit null
446-
// from the API maps to undefined so the output is unset.
444+
test("converts empty pull_request_title to empty string", () => {
445+
// pull_request_title is always a string from the API;
446+
// an empty string passes through as-is.
447447
const inputs = createMockInputs();
448448
const action = new CoderAgentChatAction(
449449
coderClient,
@@ -454,18 +454,18 @@ describe("CoderAgentChatAction", () => {
454454
if (!diff) {
455455
throw new Error("mockChatWithDiff must have diff_status set");
456456
}
457-
const chatWithNullTitle: typeof mockChatWithDiff = {
457+
const chatWithEmptyTitle: typeof mockChatWithDiff = {
458458
...mockChatWithDiff,
459-
diff_status: { ...diff, pull_request_title: null },
459+
diff_status: { ...diff, pull_request_title: "" },
460460
};
461461

462462
const out = action.buildOutputs(
463463
mockUser.username,
464-
chatWithNullTitle,
464+
chatWithEmptyTitle,
465465
false,
466466
);
467467

468-
expect(out.pullRequestTitle).toBeUndefined();
468+
expect(out.pullRequestTitle).toBe("");
469469
});
470470

471471
test("emits zero numerics when a PR exists", () => {
@@ -570,7 +570,7 @@ describe("CoderAgentChatAction", () => {
570570
const chatWithError: typeof mockChat = {
571571
...mockChat,
572572
status: "error",
573-
last_error: "spend cap reached",
573+
last_error: { message: "spend cap reached", retryable: false },
574574
};
575575

576576
const out = action.buildOutputs(mockUser.username, chatWithError, true);
@@ -919,7 +919,7 @@ describe("CoderAgentChatAction", () => {
919919
.mockResolvedValueOnce({
920920
...mockChat,
921921
status: "error",
922-
last_error: "Anthropic 429 rate limit",
922+
last_error: { message: "Anthropic 429 rate limit", retryable: true },
923923
});
924924

925925
const inputs = createMockInputs({
@@ -1413,7 +1413,7 @@ describe("CoderAgentChatAction", () => {
14131413
.mockResolvedValueOnce({
14141414
...mockChat,
14151415
status: "error",
1416-
last_error: "agent crashed",
1416+
last_error: { message: "agent crashed", retryable: false },
14171417
});
14181418

14191419
const existingChatId = "990e8400-e29b-41d4-a716-446655440000";

src/action.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ export class CoderAgentChatAction {
218218
changedFiles: hasPR ? diff?.changed_files : undefined,
219219
headBranch: diff?.head_branch ?? undefined,
220220
baseBranch: diff?.base_branch ?? undefined,
221-
chatErrorMessage: chat.last_error ?? undefined,
221+
chatErrorMessage: chat.last_error?.message ?? undefined,
222222
};
223223
}
224224

@@ -337,7 +337,7 @@ export class CoderAgentChatAction {
337337
*/
338338
private throwOnChatError(chat: CoderChat): CoderChat {
339339
if (chat.status === "error") {
340-
const message = chat.last_error || "Chat ended in error state";
340+
const message = chat.last_error?.message || "Chat ended in error state";
341341
throw new ActionFailureError("api_error", message, chat);
342342
}
343343
return chat;

src/coder-client.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ export const ChatStatusSchema = z.enum([
199199
"paused",
200200
"completed",
201201
"error",
202+
"requires_action",
202203
]);
203204
export type ChatStatus = z.infer<typeof ChatStatusSchema>;
204205

@@ -207,7 +208,7 @@ export const ChatDiffStatusSchema = z.object({
207208
chat_id: z.uuid(),
208209
url: z.string().nullable().optional(),
209210
pull_request_state: z.string().nullable().optional(),
210-
pull_request_title: z.string().nullable().optional(),
211+
pull_request_title: z.string(),
211212
pull_request_draft: z.boolean().default(false),
212213
changes_requested: z.boolean().default(false),
213214
additions: z.number().default(0),
@@ -226,21 +227,32 @@ export const ChatDiffStatusSchema = z.object({
226227
});
227228
export type ChatDiffStatus = z.infer<typeof ChatDiffStatusSchema>;
228229

230+
// Structured error returned by the Coder API when a chat fails.
231+
export const ChatErrorSchema = z.object({
232+
message: z.string(),
233+
detail: z.string().optional(),
234+
kind: z.string().optional(),
235+
provider: z.string().optional(),
236+
retryable: z.boolean(),
237+
status_code: z.number().optional(),
238+
});
239+
export type ChatError = z.infer<typeof ChatErrorSchema>;
240+
229241
// Chat schema describes the full chat object returned by the API.
230242
export const CoderChatSchema = z.object({
231243
id: ChatIdSchema,
232244
owner_id: z.uuid(),
233245
workspace_id: z.uuid().nullable().optional(),
234246
parent_chat_id: z.uuid().nullable().optional(),
235247
root_chat_id: z.uuid().nullable().optional(),
236-
last_model_config_id: z.uuid().nullable().optional(),
248+
last_model_config_id: z.uuid(),
237249
title: z.string(),
238250
status: ChatStatusSchema,
239-
last_error: z.string().nullable().optional(),
251+
last_error: ChatErrorSchema.nullable().optional(),
240252
diff_status: ChatDiffStatusSchema.nullable().optional(),
241253
created_at: z.string(),
242254
updated_at: z.string(),
243-
archived: z.boolean().nullable().optional(),
255+
archived: z.boolean(),
244256
});
245257
export type CoderChat = z.infer<typeof CoderChatSchema>;
246258

src/schemas.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -341,8 +341,8 @@ describe("CoderChatSchema", () => {
341341
const result = CoderChatSchema.parse({
342342
...mockChat,
343343
status: "error",
344-
last_error: "spend cap reached",
344+
last_error: { message: "spend cap reached", retryable: false },
345345
});
346-
expect(result.last_error).toBe("spend cap reached");
346+
expect(result.last_error?.message).toBe("spend cap reached");
347347
});
348348
});

0 commit comments

Comments
 (0)