fix(core,docs): window-derived output caps and compaction threshold, slower retry ladder, retry-all-but-auth (#235)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1910,14 +1910,15 @@ describe("ContextEngine LLM timeout / network interruption (PRN-012)", () => {
|
||||
expect((abort!.payload as { reason?: string }).reason).toContain("llm request error");
|
||||
});
|
||||
|
||||
it("a quota-403 (classified timeout) retries within the default cap and succeeds", async () => {
|
||||
it("a bare 403 (classified failed) retries within the default cap and succeeds", async () => {
|
||||
let calls = 0;
|
||||
const llm: LLMInterface = {
|
||||
async *streamGenerate() {
|
||||
calls += 1;
|
||||
// Two quota rejections (GenerativeModel classifies them as timeout), then success —
|
||||
// attempt 3 is within the default cap of 5.
|
||||
if (calls <= 2) return { status: "timeout" };
|
||||
// Two provider 403 rejections (GenerativeModel labels them failed — there is no
|
||||
// quota/message heuristic anymore), then success: every non-auth failure rides
|
||||
// the ladder, and attempt 3 is within the default cap of 5.
|
||||
if (calls <= 2) return { status: "failed", errorMessage: "403 forbidden" };
|
||||
yield assistantText("recovered");
|
||||
yield tokenUsage(emptyTokenCounts(), {
|
||||
cache_read: 0,
|
||||
@@ -2020,15 +2021,16 @@ describe("ContextEngine LLM timeout / network interruption (PRN-012)", () => {
|
||||
expect(engine.skipReconnectWait()).toBe(false);
|
||||
});
|
||||
|
||||
it("reconnectDelayMs: exponential-with-ceiling ladder (defaults: 250ms base, 30s cap)", () => {
|
||||
// The default cap (5) walks the first five steps — 250+500+1000+2000+4000 ≈ 7.75s of
|
||||
// total patience; the formula keeps climbing to the 30s ceiling for larger caps.
|
||||
const ladder = [1, 2, 3, 4, 5].map((n) => reconnectDelayMs(250, 30_000, n));
|
||||
expect(ladder).toEqual([250, 500, 1000, 2000, 4000]);
|
||||
expect(ladder.reduce((a, b) => a + b, 0)).toBe(7750);
|
||||
expect([6, 7, 8].map((n) => reconnectDelayMs(250, 30_000, n))).toEqual([8000, 16000, 30000]);
|
||||
it("reconnectDelayMs: exponential-with-ceiling ladder (defaults: 2s base, 30s cap)", () => {
|
||||
// The default cap (5) walks 2s/4s/8s/16s and hits the 30s ceiling on the fifth wait —
|
||||
// ≈ 60s of total patience (issue #218: the old 250ms base burned the whole ladder in
|
||||
// ~7.75s, faster than a provider restart or rate-limit window can recover). Every wait
|
||||
// sits at or above the hosts' 2s countdown floor, so each retry is visible.
|
||||
const ladder = [1, 2, 3, 4, 5].map((n) => reconnectDelayMs(2000, 30_000, n));
|
||||
expect(ladder).toEqual([2000, 4000, 8000, 16000, 30000]);
|
||||
expect(ladder.reduce((a, b) => a + b, 0)).toBe(60_000);
|
||||
// Past the ceiling the delay stays pinned (no overflow, no further growth).
|
||||
expect(reconnectDelayMs(250, 30_000, 12)).toBe(30_000);
|
||||
expect([6, 7].map((n) => reconnectDelayMs(2000, 30_000, n))).toEqual([30_000, 30_000]);
|
||||
// The cap also applies when the base itself exceeds it.
|
||||
expect(reconnectDelayMs(50_000, 30_000, 1)).toBe(30_000);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user