im_wower
·
2026-03-28
dedupe.ts
1import { createHash } from "node:crypto";
2
3import type {
4 BaaJsonValue,
5 BaaInstructionDedupeBasis,
6 BaaInstructionEnvelope,
7 BaaInstructionSourceMessage,
8 BaaParsedInstruction
9} from "./types.js";
10import { sortBaaJsonValue, stableStringifyBaaJson } from "./types.js";
11
12const DEFAULT_IN_MEMORY_BAA_INSTRUCTION_DEDUPER_MAX_SIZE = 10_000;
13
14function normalizeInMemoryDeduperMaxSize(maxSize: number | null | undefined): number {
15 if (typeof maxSize !== "number" || !Number.isFinite(maxSize)) {
16 return DEFAULT_IN_MEMORY_BAA_INSTRUCTION_DEDUPER_MAX_SIZE;
17 }
18
19 const normalized = Math.trunc(maxSize);
20 return normalized > 0 ? normalized : DEFAULT_IN_MEMORY_BAA_INSTRUCTION_DEDUPER_MAX_SIZE;
21}
22
23export interface BaaInstructionDeduper {
24 add(instruction: BaaInstructionEnvelope): Promise<void> | void;
25 has(dedupeKey: string): Promise<boolean> | boolean;
26}
27
28export interface InMemoryBaaInstructionDeduperOptions {
29 maxSize?: number;
30}
31
32export class InMemoryBaaInstructionDeduper implements BaaInstructionDeduper {
33 private readonly keys = new Set<string>();
34 private readonly maxSize: number;
35
36 constructor(options: InMemoryBaaInstructionDeduperOptions = {}) {
37 this.maxSize = normalizeInMemoryDeduperMaxSize(options.maxSize);
38 }
39
40 add(instruction: BaaInstructionEnvelope): void {
41 this.keys.delete(instruction.dedupeKey);
42 this.keys.add(instruction.dedupeKey);
43 this.evictOverflow();
44 }
45
46 clear(): void {
47 this.keys.clear();
48 }
49
50 has(dedupeKey: string): boolean {
51 return this.keys.has(dedupeKey);
52 }
53
54 private evictOverflow(): void {
55 while (this.keys.size > this.maxSize) {
56 const oldestKey = this.keys.values().next().value;
57
58 if (typeof oldestKey !== "string") {
59 return;
60 }
61
62 this.keys.delete(oldestKey);
63 }
64 }
65}
66
67export function buildBaaInstructionDedupeBasis(
68 source: BaaInstructionSourceMessage,
69 instruction: BaaParsedInstruction
70): BaaInstructionDedupeBasis {
71 return {
72 assistant_message_id: source.assistantMessageId,
73 block_index: instruction.blockIndex,
74 conversation_id: source.conversationId,
75 params: sortBaaJsonValue(instruction.params),
76 platform: source.platform,
77 target: instruction.target,
78 tool: instruction.tool,
79 version: "baa.v1"
80 };
81}
82
83export function buildBaaInstructionDedupeKey(basis: BaaInstructionDedupeBasis): string {
84 return `sha256:${createHash("sha256").update(stableStringifyBaaJson(sortBaaJsonValue(basis as unknown as BaaJsonValue))).digest("hex")}`;
85}
86
87export function buildBaaInstructionId(dedupeKey: string): string {
88 return `inst_${dedupeKey.replace(/^sha256:/u, "").slice(0, 16)}`;
89}