import { VellumPlatformClient } from "../platform/client.js"; import type { BackgroundWakeIntent } from "./next-wake.js"; /** * Upper bound for a single background-wake platform request. * * Every call here is a lightweight control-plane request (publish/clear the * wake intent, renew/complete a lease), so it must settle quickly. The bound * exists because the publisher serializes refreshes behind a single in-flight * promise: a request that hangs (half-open socket during a platform reconnect) * would wedge that promise and silence every future refresh. The timeout * guarantees each request rejects instead of hanging, so the publisher always * makes forward progress. */ const PLATFORM_REQUEST_TIMEOUT_MS = 15_000; export type BackgroundWakeIntentClientResult = { status: "published" | "cleared" | "skipped"; httpStatus?: number; reason?: "missing_platform_client" | "missing_platform_assistant_id"; }; export type BackgroundWakeLeaseClientResult = { status: "renewed" | "completed" | "skipped"; httpStatus?: number; reason?: "missing_platform_client" | "missing_platform_assistant_id"; }; type BackgroundWakeIntentSnapshot = Pick< BackgroundWakeIntent, "sourceGeneration" | "computedAt" > | null; export async function publishBackgroundWakeIntent( intent: BackgroundWakeIntent, ): Promise { const client = await VellumPlatformClient.create(); if (!client) { return { status: "skipped", reason: "missing_platform_client" }; } if (!client.platformAssistantId) { return { status: "skipped", reason: "missing_platform_assistant_id" }; } const response = await client.fetch(intentPath(client.platformAssistantId), { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ reason: intent.reason, source_generation: intent.sourceGeneration, computed_at: toIsoString(intent.computedAt), next_wake_at: toIsoString(intent.nextWakeAt), actual_next_due_at: toIsoString(intent.actualNextDueAt), source_payload: intent.sourcePayload, }), signal: AbortSignal.timeout(PLATFORM_REQUEST_TIMEOUT_MS), }); await throwIfNotOk(response, "publish background wake intent"); return { status: "published", httpStatus: response.status }; } export async function clearBackgroundWakeIntent( intentSnapshot: BackgroundWakeIntentSnapshot = null, ): Promise { const client = await VellumPlatformClient.create(); if (!client) { return { status: "skipped", reason: "missing_platform_client" }; } if (!client.platformAssistantId) { return { status: "skipped", reason: "missing_platform_assistant_id" }; } const body: Record = {}; if (intentSnapshot) { body.source_generation = intentSnapshot.sourceGeneration; body.computed_at = toIsoString(intentSnapshot.computedAt); } const response = await client.fetch(intentPath(client.platformAssistantId), { method: "DELETE", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), signal: AbortSignal.timeout(PLATFORM_REQUEST_TIMEOUT_MS), }); await throwIfNotOk(response, "clear background wake intent"); return { status: "cleared", httpStatus: response.status }; } export async function renewBackgroundWakeLease( leaseId: string, ): Promise { const client = await VellumPlatformClient.create(); if (!client) { return { status: "skipped", reason: "missing_platform_client" }; } if (!client.platformAssistantId) { return { status: "skipped", reason: "missing_platform_assistant_id" }; } const response = await client.fetch( leasePath(client.platformAssistantId, leaseId, "renew"), { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({}), signal: AbortSignal.timeout(PLATFORM_REQUEST_TIMEOUT_MS), }, ); await throwIfNotOk(response, "renew background wake lease"); return { status: "renewed", httpStatus: response.status }; } export async function completeBackgroundWakeLease(args: { leaseId: string; status: "completed" | "failed" | "expired"; error?: string; nextIntent?: BackgroundWakeIntent | null; }): Promise { const client = await VellumPlatformClient.create(); if (!client) { return { status: "skipped", reason: "missing_platform_client" }; } if (!client.platformAssistantId) { return { status: "skipped", reason: "missing_platform_assistant_id" }; } const body: Record = { status: args.status, }; if (args.error) { body.error = args.error; } if ("nextIntent" in args) { body.next_intent = args.nextIntent ? { reason: args.nextIntent.reason, source_generation: args.nextIntent.sourceGeneration, computed_at: toIsoString(args.nextIntent.computedAt), next_wake_at: toIsoString(args.nextIntent.nextWakeAt), actual_next_due_at: toIsoString(args.nextIntent.actualNextDueAt), source_payload: args.nextIntent.sourcePayload, } : null; } const response = await client.fetch( leasePath(client.platformAssistantId, args.leaseId, "complete"), { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), signal: AbortSignal.timeout(PLATFORM_REQUEST_TIMEOUT_MS), }, ); await throwIfNotOk(response, "complete background wake lease"); return { status: "completed", httpStatus: response.status }; } function intentPath(assistantId: string): string { const encodedAssistantId = encodeURIComponent(assistantId); return `/v1/assistants/${encodedAssistantId}/background-wake-intent/`; } function leasePath( assistantId: string, leaseId: string, action: "renew" | "complete", ): string { const encodedAssistantId = encodeURIComponent(assistantId); const encodedLeaseId = encodeURIComponent(leaseId); return `/v1/assistants/${encodedAssistantId}/background-wake-leases/${encodedLeaseId}/${action}/`; } function toIsoString(timestampMs: number): string { return new Date(timestampMs).toISOString(); } async function throwIfNotOk(response: Response, action: string): Promise { if (response.ok) { return; } let body = ""; try { body = await response.text(); } catch { body = ""; } const detail = body ? `: ${body}` : ""; throw new Error(`Failed to ${action}: HTTP ${response.status}${detail}`); }