diff --git a/src/providers/codex.ts b/src/providers/codex.ts index 75cb8ff4..1ed5e30c 100644 --- a/src/providers/codex.ts +++ b/src/providers/codex.ts @@ -676,14 +676,21 @@ async function appServerRequestResult(input: { } if (input.method === "account/chatgptAuthTokens/refresh") { const bridge = requireCodexHostAuthBridge(); - const refreshed = await bridge.refreshAuthTokens({ - previousAccountId: typeof input.params.previousAccountId === "string" - ? input.params.previousAccountId - : null, - reason: typeof input.params.reason === "string" && input.params.reason - ? input.params.reason - : "account/chatgptAuthTokens/refresh", - }); + let refreshed: CodexChatgptAuthTokens; + try { + refreshed = await bridge.refreshAuthTokens({ + previousAccountId: typeof input.params.previousAccountId === "string" + ? input.params.previousAccountId + : null, + reason: typeof input.params.reason === "string" && input.params.reason + ? input.params.reason + : "account/chatgptAuthTokens/refresh", + }); + } catch (error) { + return { + error: appServerHostRequestFailure(error), + }; + } return { result: { accessToken: refreshed.accessToken, @@ -701,6 +708,18 @@ async function appServerRequestResult(input: { }; } +function appServerHostRequestFailure(error: unknown): { + code: number; + message: string; +} { + return { + code: -32000, + message: error instanceof Error && error.message + ? error.message + : String(error), + }; +} + async function bootstrapCodexExternalAuth(input: { request: ( method: string, @@ -1257,7 +1276,14 @@ async function defaultAppServerTurnRunner( method, params: safeJsonObjectFromRecord(params), }); - const response = await appServerRequestResult({ method, params }); + let response: Awaited>; + try { + response = await appServerRequestResult({ method, params }); + } catch (error) { + response = { + error: appServerHostRequestFailure(error), + }; + } logCodexAppServerDebug("message:host_response", { method, requestId, @@ -2561,6 +2587,19 @@ export function safeJsonForTest(text: string): Record { return safeJsonObject(text); } +export async function appServerRequestResultForTest(input: { + method: string; + params: Record; +}): Promise<{ + result?: Record; + error?: { + code: number; + message: string; + }; +}> { + return await appServerRequestResult(input); +} + export function sanitizeCodexSpawnArgsForTest( args: Array, ): Array { diff --git a/src/providers/codex_app_server.test.ts b/src/providers/codex_app_server.test.ts new file mode 100644 index 00000000..ee3eadcf --- /dev/null +++ b/src/providers/codex_app_server.test.ts @@ -0,0 +1,35 @@ +import { assertEquals } from "@std/assert"; +import { + appServerRequestResultForTest, + setCodexHostAuthBridgeForTests, +} from "./codex.ts"; + +Deno.test("codex app-server refresh host failures are returned as RPC errors", async () => { + setCodexHostAuthBridgeForTests({ + readAuthTokens: () => { + throw new Error("not expected"); + }, + refreshAuthTokens: () => { + throw new SyntaxError("Unexpected end of JSON input"); + }, + }); + + try { + const response = await appServerRequestResultForTest({ + method: "account/chatgptAuthTokens/refresh", + params: { + previousAccountId: "acct-test", + reason: "account/chatgptAuthTokens/refresh", + }, + }); + + assertEquals(response, { + error: { + code: -32000, + message: "Unexpected end of JSON input", + }, + }); + } finally { + setCodexHostAuthBridgeForTests(null); + } +});