feat: idempotency su POST /v1/memories (Idempotency-Key, replay → stessa risposta, payload diverso → 409)

- gateway: tabella in-memory con TTL 24h, hash canonico del payload, scoped per API key
- estensione: crypto.randomUUID() per operazione (store e correct), riusata su retry
- from __future__ import annotations (forward-reference _payload_hash)
This commit is contained in:
Matteo Benedetto
2026-08-16 19:10:32 +02:00
parent 9cb3008f4d
commit a7d4bbb4a8
2 changed files with 54 additions and 6 deletions
+13 -4
View File
@@ -60,14 +60,17 @@ async function gatewayRequest(
route: string,
body?: unknown,
signal?: AbortSignal,
idempotencyKey?: string,
): Promise<{ ok: boolean; status: number; data: any }> {
const headers: Record<string, string> = {
"Content-Type": "application/json",
"X-API-Key": cfg.apiKey,
};
if (idempotencyKey) headers["Idempotency-Key"] = idempotencyKey;
const res = await fetch(`${cfg.url}${route}`, {
method,
signal,
headers: {
"Content-Type": "application/json",
"X-API-Key": cfg.apiKey,
},
headers,
body: body ? JSON.stringify(body) : undefined,
});
const data = await res.json().catch(() => ({}));
@@ -164,6 +167,8 @@ export default function qmemExtension(pi: ExtensionAPI) {
}
const p = params as any;
onUpdate?.({ content: [{ type: "text", text: "qmem: salvataggio record..." }] });
// Idempotency: stessa key per tutta l'operazione (e per eventuali retry)
const idemKey = crypto.randomUUID();
const { ok, status, data } = await gatewayRequest(
cfg,
"POST",
@@ -180,6 +185,7 @@ export default function qmemExtension(pi: ExtensionAPI) {
supersede_reason: p.supersede_reason,
},
signal,
idemKey,
);
if (!ok) {
return {
@@ -376,6 +382,8 @@ export default function qmemExtension(pi: ExtensionAPI) {
}
onUpdate?.({ content: [{ type: "text", text: `qmem: supersede di ${memoryId}...` }] });
// Idempotency: stessa key per tutta l'operazione (e per eventuali retry)
const idemKey = crypto.randomUUID();
const { ok, status, data } = await gatewayRequest(
cfg,
"POST",
@@ -391,6 +399,7 @@ export default function qmemExtension(pi: ExtensionAPI) {
supersede_reason: p.reason,
},
signal,
idemKey,
);
if (!ok) {
return {