mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 06:06:10 +00:00
The comprehensive insight that feeds both the daily briefing and the insights trend narrative is a single reasoning-heavy generation over the full feature snapshot. On a large account it runs past the provider client's fixed 60 s timeout and is aborted mid-stream, so the chain reports an all-failed outcome, no cached block is written, and the surface falls back to its static placeholder on every visit. The warm paths bounded the same generation tighter still (45 s) and counted the failure with no observable signal, so the blank state was permanent and silent. Make the per-request upstream timeout configurable (CompletionParams.timeoutMs, default 60 s for every existing caller) and raise it to 120 s for the comprehensive surface only, across all three generation paths: the on-demand regenerate, the on-demand warm, and the nightly pre-generation. Raise the warm-pass budget to 130 s so the provider's own timeout governs a genuine completion rather than clipping it early. Annotate the comprehensive failure in both the generator and the nightly loop so a generation that keeps failing is queryable instead of leaving the surface empty without a trace.
77 lines
2.3 KiB
TypeScript
77 lines
2.3 KiB
TypeScript
/**
|
|
* v1.21.5 — the Codex client honours the caller's per-request timeout
|
|
* override (`CompletionParams.timeoutMs`), falling back to the shared 60 s
|
|
* default when unset. The comprehensive briefing is the one caller that
|
|
* overrides it; the 60 s default was aborting its reasoning-heavy generation
|
|
* mid-stream on large accounts, leaving the briefing blank.
|
|
*/
|
|
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
|
|
const safeFetch = vi.fn();
|
|
vi.mock("@/lib/safe-fetch", () => ({
|
|
safeFetch: (...a: unknown[]) => safeFetch(...a),
|
|
}));
|
|
|
|
import { CodexClient } from "../codex-client";
|
|
import { singleUserTurn } from "../types";
|
|
|
|
function sseResponse(): Response {
|
|
const encoder = new TextEncoder();
|
|
const events = [
|
|
`event: response.output_item.done\ndata: ${JSON.stringify({
|
|
type: "response.output_item.done",
|
|
item: {
|
|
type: "message",
|
|
role: "assistant",
|
|
content: [{ type: "output_text", text: '{"summary":"ok"}' }],
|
|
},
|
|
})}\n\n`,
|
|
`event: response.completed\ndata: ${JSON.stringify({
|
|
type: "response.completed",
|
|
response: { id: "r1", usage: { total_tokens: 10 } },
|
|
})}\n\n`,
|
|
];
|
|
const stream = new ReadableStream<Uint8Array>({
|
|
start(controller) {
|
|
for (const ev of events) controller.enqueue(encoder.encode(ev));
|
|
controller.close();
|
|
},
|
|
});
|
|
return new Response(stream, {
|
|
status: 200,
|
|
headers: { "content-type": "text/event-stream" },
|
|
});
|
|
}
|
|
|
|
function makeClient() {
|
|
return new CodexClient({
|
|
accessToken: "t",
|
|
accountId: "acct",
|
|
onTokenRefresh: vi
|
|
.fn()
|
|
.mockResolvedValue({ accessToken: "t", accountId: "acct" }),
|
|
slugChain: ["gpt-5.5"],
|
|
});
|
|
}
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
safeFetch.mockResolvedValue(sseResponse());
|
|
});
|
|
|
|
describe("CodexClient timeout override", () => {
|
|
it("forwards an explicit timeoutMs to safeFetch", async () => {
|
|
await makeClient().generateCompletion(
|
|
singleUserTurn({ system: "s", user: "u", timeoutMs: 120_000 }),
|
|
);
|
|
expect(safeFetch.mock.calls[0][2].timeoutMs).toBe(120_000);
|
|
});
|
|
|
|
it("falls back to the 60 s default when no override is set", async () => {
|
|
await makeClient().generateCompletion(
|
|
singleUserTurn({ system: "s", user: "u" }),
|
|
);
|
|
expect(safeFetch.mock.calls[0][2].timeoutMs).toBe(60_000);
|
|
});
|
|
});
|