baa-conductor


baa-conductor / apps / conductor-daemon / src / instructions
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}