fix(ai): reconnect Codex websocket on connection limit

closes #5973
This commit is contained in:
Armin Ronacher
2026-06-23 00:17:37 +02:00
parent f7d3331df6
commit d0e0b84cb9
3 changed files with 137 additions and 47 deletions
@@ -1195,6 +1195,68 @@ describe("openai-codex streaming", () => {
});
});
it("reconnects once when the websocket connection limit is reached before output starts", async () => {
const token = mockToken();
let connections = 0;
const fetchMock = vi.fn();
vi.stubGlobal("fetch", fetchMock);
class MockWebSocket extends EventTarget {
private readonly limitReached = connections++ === 0;
constructor() {
super();
queueMicrotask(() => this.dispatchEvent(new Event("open")));
}
send(): void {
const event = this.limitReached
? { type: "error", error: { code: "websocket_connection_limit_reached" } }
: {
type: "response.completed",
response: {
id: "resp_1",
status: "completed",
usage: { input_tokens: 5, output_tokens: 3, total_tokens: 8 },
},
};
queueMicrotask(() => {
this.dispatchEvent(Object.assign(new Event("message"), { data: JSON.stringify(event) }));
});
}
close(): void {}
}
vi.stubGlobal("WebSocket", MockWebSocket);
const model: Model<"openai-codex-responses"> = {
id: "gpt-5.1-codex",
name: "GPT-5.1 Codex",
api: "openai-codex-responses",
provider: "openai-codex",
baseUrl: "https://chatgpt.com/backend-api",
reasoning: true,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 400000,
maxTokens: 128000,
};
const result = await streamOpenAICodexResponses(
model,
{ systemPrompt: "", messages: [] },
{
apiKey: token,
},
).result();
expect(result.stopReason).toBe("stop");
expect(connections).toBe(2);
expect(fetchMock).not.toHaveBeenCalled();
});
it("falls back to SSE when a websocket is idle before the first event", async () => {
vi.useFakeTimers();
const token = mockToken();