// SQLite Durable Object for the free MCP tier (see free-tier.js). One instance ("free-tier") per Worker.
// Table u(day, k, n): k = sha256(day|client) prefix, '*' = the tool-wide total. Days before today are pruned.
// take() is synchronous SQL inside one DO, so concurrent calls can't overshoot either cap.
import { DurableObject } from 'cloudflare:workers';
export class FreeTierCounter extends DurableObject {
constructor(ctx, env) {
super(ctx, env);
this.sql = ctx.storage.sql;
this.sql.exec('CREATE TABLE IF NOT EXISTS u (day TEXT NOT NULL, k TEXT NOT NULL, n INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (day, k))');
this.day = null;
}
n(day, k) { const r = this.sql.exec('SELECT n FROM u WHERE day = ? AND k = ?', day, k).toArray(); return r.length ? Number(r[0].n) : 0; }
take(day, k, perClient, globalDaily) {
if (!/^\d{4}-\d{2}-\d{2}$/.test(day) || !/^[0-9a-f]{8,64}$/.test(k)) return { ok: false, reason: 'client', used: 0, global_used: 0 };
if (day !== this.day) { this.sql.exec('DELETE FROM u WHERE day < ?', day); this.day = day; }
const n = this.n(day, k); const g = this.n(day, '*');
if (n >= perClient) return { ok: false, reason: 'client', used: n, global_used: g };
if (g >= globalDaily) return { ok: false, reason: 'global', used: n, global_used: g };
const up = 'INSERT INTO u (day, k, n) VALUES (?, ?, 1) ON CONFLICT (day, k) DO UPDATE SET n = n + 1';
this.sql.exec(up, day, k); this.sql.exec(up, day, '*');
return { ok: true, used: n + 1, global_used: g + 1 };
}
refund(day, k) {
if (!/^[0-9a-f]{8,64}$/.test(k)) return false;
this.sql.exec('UPDATE u SET n = n - 1 WHERE day = ? AND k IN (?, ?) AND n > 0', day, k, '*');
return true;
}
}
agentgate-source: code mirror on Cloudflare Pages. All repos