Skip to content

Commit a19e53a

Browse files
authored
🤖 Fix web server error handling to preserve structured errors (#369)
1 parent 88e9504 commit a19e53a

File tree

3 files changed

+90
-9
lines changed

3 files changed

+90
-9
lines changed

src/browser/api.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const WS_BASE = API_BASE.replace("http://", "ws://").replace("https://", "wss://
1010
interface InvokeResponse<T> {
1111
success: boolean;
1212
data?: T;
13-
error?: string;
13+
error?: unknown; // Can be string or structured error object
1414
}
1515

1616
// Helper function to invoke IPC handlers via HTTP
@@ -30,9 +30,16 @@ async function invokeIPC<T>(channel: string, ...args: unknown[]): Promise<T> {
3030
const result = (await response.json()) as InvokeResponse<T>;
3131

3232
if (!result.success) {
33-
throw new Error(result.error ?? "Unknown error");
33+
// Failed response - check if it's a structured error or simple string
34+
if (typeof result.error === "object" && result.error !== null) {
35+
// Structured error (e.g., SendMessageError) - return as Result<T, E> for caller to handle
36+
return result as T;
37+
}
38+
// Simple string error - throw it
39+
throw new Error(typeof result.error === "string" ? result.error : "Unknown error");
3440
}
3541

42+
// Success - unwrap and return the data
3643
return result.data as T;
3744
}
3845

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { describe, expect, test } from "@jest/globals";
2+
import { createErrorToast } from "./ChatInputToasts";
3+
import type { SendMessageError } from "@/types/errors";
4+
5+
describe("ChatInputToasts", () => {
6+
describe("createErrorToast", () => {
7+
test("should create toast for api_key_not_found error", () => {
8+
const error: SendMessageError = {
9+
type: "api_key_not_found",
10+
provider: "openai",
11+
};
12+
13+
const toast = createErrorToast(error);
14+
15+
expect(toast.type).toBe("error");
16+
expect(toast.title).toBe("API Key Not Found");
17+
expect(toast.message).toContain("openai");
18+
expect(toast.message).toContain("API key");
19+
});
20+
21+
test("should create toast for provider_not_supported error", () => {
22+
const error: SendMessageError = {
23+
type: "provider_not_supported",
24+
provider: "custom-provider",
25+
};
26+
27+
const toast = createErrorToast(error);
28+
29+
expect(toast.type).toBe("error");
30+
expect(toast.title).toBe("Provider Not Supported");
31+
expect(toast.message).toContain("custom-provider");
32+
});
33+
34+
test("should create toast for invalid_model_string error", () => {
35+
const error: SendMessageError = {
36+
type: "invalid_model_string",
37+
message: "Invalid format: expected provider:model",
38+
};
39+
40+
const toast = createErrorToast(error);
41+
42+
expect(toast.type).toBe("error");
43+
expect(toast.title).toBe("Invalid Model Format");
44+
expect(toast.message).toBe("Invalid format: expected provider:model");
45+
});
46+
47+
test("should create toast for unknown error with message", () => {
48+
const error: SendMessageError = {
49+
type: "unknown",
50+
raw: "Network connection failed",
51+
};
52+
53+
const toast = createErrorToast(error);
54+
55+
expect(toast.type).toBe("error");
56+
expect(toast.title).toBe("Message Send Failed");
57+
expect(toast.message).toBe("Network connection failed");
58+
});
59+
60+
test("should create toast for unknown error without message", () => {
61+
const error: SendMessageError = {
62+
type: "unknown",
63+
raw: "",
64+
};
65+
66+
const toast = createErrorToast(error);
67+
68+
expect(toast.type).toBe("error");
69+
expect(toast.title).toBe("Message Send Failed");
70+
expect(toast.message).toContain("unexpected error");
71+
});
72+
});
73+
});

src/main-server.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,21 +30,22 @@ class HttpIpcMainAdapter {
3030
const args: unknown[] = body.args ?? [];
3131
const result = await handler(null, ...args);
3232

33-
// If handler returns an error result object, unwrap it and send as error response
34-
// This ensures webApi.ts will throw with the proper error message
33+
// If handler returns a failed Result type, pass through the error
34+
// This preserves structured error types like SendMessageError
3535
if (
3636
result &&
3737
typeof result === "object" &&
3838
"success" in result &&
39-
result.success === false
39+
result.success === false &&
40+
"error" in result
4041
) {
41-
const errorMessage =
42-
"error" in result && typeof result.error === "string" ? result.error : "Unknown error";
43-
// Return 200 with error structure so webApi can throw with the detailed message
44-
res.json({ success: false, error: errorMessage });
42+
// Pass through failed Result to preserve error structure
43+
res.json(result);
4544
return;
4645
}
4746

47+
// For all other return values (including successful Results), wrap in success response
48+
// The browser API will unwrap the data field
4849
res.json({ success: true, data: result });
4950
} catch (error) {
5051
const message = error instanceof Error ? error.message : String(error);

0 commit comments

Comments
 (0)