69 lines
2.2 KiB
JavaScript
69 lines
2.2 KiB
JavaScript
// Job-id deduplication. Recap mints a UUID per summarize job (the
|
|
// transcribe + analyze pair) and sends it in X-Recap-Job-Id on every
|
|
// relay call. The first call with a given (install_id, job_id) tuple
|
|
// reserves a credit; subsequent calls with the same tuple are free
|
|
// until the job_id expires (1 hour).
|
|
//
|
|
// Stored in-memory only — not persisted across restarts because (a)
|
|
// a restart breaks all in-flight Recap streams anyway and (b) the
|
|
// worst-case outcome of a "lost reservation" is the user being
|
|
// charged for a single retry, which is acceptable.
|
|
|
|
const JOB_TTL_MS = 60 * 60 * 1000; // 1 hour
|
|
|
|
// Map<install_id|job_id, { backend, tier, charged_at, refunded }>
|
|
const jobs = new Map();
|
|
|
|
function key(installId, jobId) {
|
|
return `${installId}|${jobId}`;
|
|
}
|
|
|
|
// On a new request: returns { charged: true } if this is the first call
|
|
// for the job (caller must commit a credit), or { charged: false,
|
|
// backend, tier } if it's a retry/follow-up.
|
|
export function lookupJob(installId, jobId) {
|
|
if (!installId || !jobId) return null;
|
|
pruneExpired();
|
|
const k = key(installId, jobId);
|
|
const existing = jobs.get(k);
|
|
if (existing && !existing.refunded) return existing;
|
|
return null;
|
|
}
|
|
|
|
// Mark a job as having been charged. Idempotent — second call for the
|
|
// same (install_id, job_id) is a no-op.
|
|
export function markJobCharged(installId, jobId, { backend, tier }) {
|
|
if (!installId || !jobId) return;
|
|
pruneExpired();
|
|
const k = key(installId, jobId);
|
|
if (jobs.has(k) && !jobs.get(k).refunded) return;
|
|
jobs.set(k, {
|
|
backend,
|
|
tier,
|
|
charged_at: Date.now(),
|
|
refunded: false,
|
|
});
|
|
}
|
|
|
|
// Refund a previously charged credit for a failed job. Future calls
|
|
// with the same job_id will be treated as new (since the reservation
|
|
// is no longer valid).
|
|
export function refundJob(installId, jobId) {
|
|
if (!installId || !jobId) return;
|
|
const k = key(installId, jobId);
|
|
const existing = jobs.get(k);
|
|
if (existing) existing.refunded = true;
|
|
}
|
|
|
|
function pruneExpired() {
|
|
const cutoff = Date.now() - JOB_TTL_MS;
|
|
for (const [k, v] of jobs) {
|
|
if (v.charged_at < cutoff) jobs.delete(k);
|
|
}
|
|
}
|
|
|
|
export function snapshotJobs() {
|
|
pruneExpired();
|
|
return Array.from(jobs.entries()).map(([k, v]) => ({ key: k, ...v }));
|
|
}
|