diff --git a/src/__tests__/ai-job-expiry-guard.test.ts b/src/__tests__/ai-job-expiry-guard.test.ts index 7babbafe3..65197af2d 100644 --- a/src/__tests__/ai-job-expiry-guard.test.ts +++ b/src/__tests__/ai-job-expiry-guard.test.ts @@ -55,7 +55,6 @@ import { QUEUE_RUNTIME, type QueueRuntime } from "@/lib/jobs/queue-runtime"; import { COMPLETION_CEILING_MS, COMPREHENSIVE_WARM_CEILING_MS, - STATUS_RUN_CEILING_MS, } from "@/lib/jobs/model-time"; import { COMPREHENSIVE_MODEL_CALLS, @@ -147,7 +146,7 @@ function modelSecondsOf(runs: { completions?: number; }) { return ( - ((runs.status ?? 0) * STATUS_RUN_CEILING_MS + + ((runs.status ?? 0) * COMPLETION_CEILING_MS + (runs.comprehensive ?? 0) * COMPREHENSIVE_WARM_CEILING_MS + (runs.completions ?? 0) * COMPLETION_CEILING_MS) / 1000 @@ -204,7 +203,7 @@ describe("jobs that wait on the response timeout outlast it", () => { }); it("sizes the run ceilings from the largest setting and the call counts", () => { - expect(STATUS_RUN_CEILING_MS).toBe( + expect(COMPLETION_CEILING_MS).toBe( AI_RESPONSE_TIMEOUT_MAX_SECONDS * 1000 * STATUS_MODEL_CALLS, ); expect(COMPREHENSIVE_WARM_CEILING_MS).toBeGreaterThan( @@ -238,14 +237,14 @@ describe("jobs that wait on the response timeout outlast it", () => { }, "coach-memory-refresh": { runtime: "bounded", - modelSeconds: STATUS_RUN_CEILING_MS / 1000, + modelSeconds: COMPLETION_CEILING_MS / 1000, expireInSeconds: 1400, expiryVia: [], why: "planted: one run counted instead of three", }, }; expect(findings(before)).toEqual([ - `coach-memory-refresh: model time ${STATUS_RUN_CEILING_MS / 1000}s below the ${modelSecondsOf({ status: 3 })}s its runs take`, + `coach-memory-refresh: model time ${COMPLETION_CEILING_MS / 1000}s below the ${modelSecondsOf({ status: 3 })}s its runs take`, "morning-digest-refresh: declared short, but it waits on the person's response timeout", ]); }); diff --git a/src/lib/ai/coach/coach-memory-shared.ts b/src/lib/ai/coach/coach-memory-shared.ts index b5b9eb597..ad3db23ef 100644 --- a/src/lib/ai/coach/coach-memory-shared.ts +++ b/src/lib/ai/coach/coach-memory-shared.ts @@ -22,7 +22,7 @@ import { getGlobalBoss } from "@/lib/jobs/boss-instance"; import { annotate } from "@/lib/logging/context"; import { - STATUS_RUN_CEILING_MS, + COMPLETION_CEILING_MS, expireAboveModelTime, } from "@/lib/jobs/model-time"; @@ -41,7 +41,7 @@ export const COACH_MEMORY_MODEL_RUNS = 3; * beside the refresh still writing (#1274). */ export const COACH_MEMORY_REFRESH_EXPIRE_SECONDS = expireAboveModelTime( - COACH_MEMORY_MODEL_RUNS * STATUS_RUN_CEILING_MS, + COACH_MEMORY_MODEL_RUNS * COMPLETION_CEILING_MS, ); /** How long a conversation must be quiet before its memory is refreshed. */ diff --git a/src/lib/ai/local-client.ts b/src/lib/ai/local-client.ts index ee5e5b107..6af86a14c 100644 --- a/src/lib/ai/local-client.ts +++ b/src/lib/ai/local-client.ts @@ -12,7 +12,6 @@ import { } from "./openai-wire"; import { hasLearnedJsonModeDialect, - isResponseFormatRejection, jsonModeDialectFor, rememberJsonModeDialect, resetJsonModeDialectCache, @@ -66,8 +65,6 @@ type LocalJsonDialect = JsonModeDialect; /** Test hook — clears the learned per-endpoint dialect cache. */ export const resetLocalJsonDialectCache = resetJsonModeDialectCache; -export { isResponseFormatRejection }; - /** * #89 — the streaming path's absolute backstop. The real ceiling is the * per-idle-gap timer; this only stops a server that streams forever. diff --git a/src/lib/jobs/insight-status-generate-queue.ts b/src/lib/jobs/insight-status-generate-queue.ts index 1b1aa5455..cc8376a16 100644 --- a/src/lib/jobs/insight-status-generate-queue.ts +++ b/src/lib/jobs/insight-status-generate-queue.ts @@ -4,7 +4,7 @@ * worker registry and every sender can read them without loading it. */ import { - STATUS_RUN_CEILING_MS, + COMPLETION_CEILING_MS, expireAboveModelTime, } from "@/lib/jobs/model-time"; @@ -17,7 +17,7 @@ export const INSIGHT_STATUS_GENERATE_QUEUE = "insight-status-generate"; * retry beside the generation still running (#1274). */ export const INSIGHT_STATUS_GENERATE_EXPIRE_SECONDS = expireAboveModelTime( - STATUS_RUN_CEILING_MS, + COMPLETION_CEILING_MS, ); /** diff --git a/src/lib/jobs/model-time.ts b/src/lib/jobs/model-time.ts index e6df15444..a08c15580 100644 --- a/src/lib/jobs/model-time.ts +++ b/src/lib/jobs/model-time.ts @@ -63,14 +63,11 @@ export function comprehensiveWarmBudgetMs( * it: the call and the one repeat the Local and gateway clients send when a * thinking model answered with reasoning alone (`STATUS_MODEL_CALLS`), each * at the full timeout. A document read's call and its corrective retry are - * two completions. + * two completions; a status run (`runStatusCompletion`) is one. */ export const COMPLETION_CEILING_MS = AI_RESPONSE_TIMEOUT_MAX_MS * STATUS_MODEL_CALLS; -/** The longest one status run (`runStatusCompletion`, one completion) waits. */ -export const STATUS_RUN_CEILING_MS = COMPLETION_CEILING_MS; - /** * Room a job's expiry leaves on top of its model work for what the handler * does around it: reading the record, building the prompt, persisting. diff --git a/src/lib/jobs/queue-runtime.ts b/src/lib/jobs/queue-runtime.ts index 94e966bfe..28ed26810 100644 --- a/src/lib/jobs/queue-runtime.ts +++ b/src/lib/jobs/queue-runtime.ts @@ -61,7 +61,6 @@ import { import { COMPLETION_CEILING_MS, COMPREHENSIVE_WARM_CEILING_MS, - STATUS_RUN_CEILING_MS, } from "@/lib/jobs/model-time"; import { DOCUMENT_INDEX_EXPIRE_SECONDS } from "@/lib/jobs/document-index-queue"; import { DOCUMENT_SUMMARY_EXPIRE_SECONDS } from "@/lib/jobs/document-summary-queue"; @@ -199,7 +198,7 @@ export const QUEUE_RUNTIME: Readonly> = { }, "insight-status-generate": { runtime: "bounded", - modelSeconds: STATUS_RUN_CEILING_MS / 1000, + modelSeconds: COMPLETION_CEILING_MS / 1000, expireInSeconds: INSIGHT_STATUS_GENERATE_EXPIRE_SECONDS, expiryVia: ["INSIGHT_STATUS_GENERATE_EXPIRE_SECONDS"], why: "One account, one note: one status run, the call and its one reasoning repeat at the account's timeout.", @@ -208,7 +207,7 @@ export const QUEUE_RUNTIME: Readonly> = { "reaction-line-generate": short("One provider call for one reaction line."), "workout-insight-generate": { runtime: "bounded", - modelSeconds: STATUS_RUN_CEILING_MS / 1000, + modelSeconds: COMPLETION_CEILING_MS / 1000, expireInSeconds: WORKOUT_INSIGHT_GENERATE_EXPIRE_SECONDS, expiryVia: ["WORKOUT_INSIGHT_GENERATE_EXPIRE_SECONDS"], why: "One workout's line: one status run at the account's timeout.", @@ -235,7 +234,7 @@ export const QUEUE_RUNTIME: Readonly> = { }, "coach-memory-refresh": { runtime: "bounded", - modelSeconds: (COACH_MEMORY_MODEL_RUNS * STATUS_RUN_CEILING_MS) / 1000, + modelSeconds: (COACH_MEMORY_MODEL_RUNS * COMPLETION_CEILING_MS) / 1000, expireInSeconds: COACH_MEMORY_REFRESH_EXPIRE_SECONDS, expiryVia: ["COACH_MEMORY_REFRESH_EXPIRE_SECONDS"], why: "One conversation's summary, facts and plan proposals: three status runs in a row at the account's timeout.", diff --git a/src/lib/jobs/workout-insight-generate-shared.ts b/src/lib/jobs/workout-insight-generate-shared.ts index 614060950..4ea08d380 100644 --- a/src/lib/jobs/workout-insight-generate-shared.ts +++ b/src/lib/jobs/workout-insight-generate-shared.ts @@ -16,7 +16,7 @@ import { getGlobalBoss } from "@/lib/jobs/boss-instance"; import { annotate } from "@/lib/logging/context"; import { logCaught } from "@/lib/logging/signal"; import { - STATUS_RUN_CEILING_MS, + COMPLETION_CEILING_MS, expireAboveModelTime, } from "@/lib/jobs/model-time"; @@ -30,7 +30,7 @@ export const WORKOUT_INSIGHT_GENERATE_QUEUE = "workout-insight-generate"; * the attempt was recorded as a failure. */ export const WORKOUT_INSIGHT_GENERATE_EXPIRE_SECONDS = expireAboveModelTime( - STATUS_RUN_CEILING_MS, + COMPLETION_CEILING_MS, ); /** Serial — this is a provider path, kept off the request pool entirely. */