CoolFace
Datasetpublic

basant307/AI_Governance_Project

sourceHugging Faceapache-2.0updated 2mo agoView on Hugging Face
0likes48downloads
channel-memory.ts218 linesDownload Raw Back to memory
1/**2 * @license3 * Copyright 2025 Google LLC4 * SPDX-License-Identifier: Apache-2.05 */6 7import { createHash } from 'node:crypto';8import * as fs from 'node:fs/promises';9import * as path from 'node:path';10import lockfile from 'proper-lockfile';11import { Storage } from '../config/storage.js';12 13export interface ChannelMemoryTarget {14  channelName: string;15  chatId: string;16  threadId?: string;17}18 19export interface ChannelMemoryWriteResult {20  changed: boolean;21  filePath: string;22}23 24export const CHANNEL_MEMORY_FILE_NAME = 'CHANNEL.md';25export const MAX_CHANNEL_MEMORY_BYTES = 1024 * 1024;26const pendingAppends = new Map<string, Promise<void>>();27const LOCK_OPTIONS: lockfile.LockOptions = {28  realpath: false,29  retries: {30    retries: 12,31    minTimeout: 50,32    maxTimeout: 1000,33    factor: 2,34    randomize: true,35  },36  stale: 5000,37};38 39function isMissingFile(error: unknown): boolean {40  return (error as NodeJS.ErrnoException).code === 'ENOENT';41}42 43async function releaseLock(release: () => Promise<void>): Promise<void> {44  try {45    await release();46  } catch {47    // The write/delete already completed; stale-lock cleanup is non-fatal.48  }49}50 51function safeChannelName(channelName: string): string {52  const slug = channelName.replace(/[^a-zA-Z0-9._-]/g, '_').slice(0, 20) || '_';53  const hash = createHash('sha256')54    .update(channelName)55    .digest('hex')56    .slice(0, 16);57  return `${slug}-${hash}`;58}59 60function hashedThreadPath(target: ChannelMemoryTarget): string {61  return createHash('sha256')62    .update(target.chatId)63    .update('\0')64    .update(target.threadId ?? '')65    .digest('hex')66    .slice(0, 32);67}68 69export function getChannelMemoryFilePath(target: ChannelMemoryTarget): string {70  return path.join(71    Storage.getGlobalQwenDir(),72    'channels',73    'memory',74    safeChannelName(target.channelName),75    hashedThreadPath(target),76    CHANNEL_MEMORY_FILE_NAME,77  );78}79 80async function serializeAppend<T>(81  filePath: string,82  task: () => Promise<T>,83): Promise<T> {84  const previous = pendingAppends.get(filePath) ?? Promise.resolve();85  let release: () => void = () => {};86  const current = new Promise<void>((resolve) => {87    release = resolve;88  });89  const queued = previous.then(90    () => current,91    () => current,92  );93  pendingAppends.set(filePath, queued);94 95  await previous.catch(() => {});96  try {97    return await task();98  } finally {99    release();100    if (pendingAppends.get(filePath) === queued) {101      pendingAppends.delete(filePath);102    }103  }104}105 106export async function readChannelMemory(107  target: ChannelMemoryTarget,108): Promise<string> {109  const filePath = getChannelMemoryFilePath(target);110  return serializeAppend(filePath, async () => {111    let size: number;112    try {113      size = (await fs.stat(filePath)).size;114    } catch (error) {115      if (isMissingFile(error)) {116        return '';117      }118      throw error;119    }120    if (size > MAX_CHANNEL_MEMORY_BYTES) {121      process.stderr.write(122        `[channel-memory] ${filePath} is ${size} bytes, exceeding ${MAX_CHANNEL_MEMORY_BYTES}; treating as empty\n`,123      );124      return '';125    }126    try {127      return await fs.readFile(filePath, 'utf8');128    } catch (error) {129      if (isMissingFile(error)) {130        return '';131      }132      throw error;133    }134  });135}136 137export async function appendChannelMemory(138  target: ChannelMemoryTarget,139  text: string,140): Promise<ChannelMemoryWriteResult> {141  const filePath = getChannelMemoryFilePath(target);142  const entry = text.trim();143  if (!entry) {144    return { changed: false, filePath };145  }146 147  return serializeAppend(filePath, async () => {148    const appendBytes = Buffer.byteLength(`${entry}\n`, 'utf8');149    await fs.mkdir(path.dirname(filePath), { recursive: true });150    // proper-lockfile requires the target file to exist before locking it.151    const initialHandle = await fs.open(filePath, 'a+');152    await initialHandle.close();153    let release: () => Promise<void>;154    try {155      release = await lockfile.lock(filePath, LOCK_OPTIONS);156    } catch (error) {157      if (!isMissingFile(error)) {158        throw error;159      }160      const retryHandle = await fs.open(filePath, 'a+');161      await retryHandle.close();162      release = await lockfile.lock(filePath, LOCK_OPTIONS);163    }164    try {165      const handle = await fs.open(filePath, 'a+');166      try {167        const existingSize = (await handle.stat()).size;168        if (existingSize + appendBytes > MAX_CHANNEL_MEMORY_BYTES) {169          throw new Error('Channel memory exceeds maximum size');170        }171        await handle.appendFile(`${entry}\n`, 'utf8');172      } finally {173        await handle.close();174      }175    } finally {176      await releaseLock(release);177    }178    return { changed: true, filePath };179  });180}181 182export async function clearChannelMemory(183  target: ChannelMemoryTarget,184): Promise<ChannelMemoryWriteResult> {185  const filePath = getChannelMemoryFilePath(target);186  return serializeAppend(filePath, async () => {187    try {188      await fs.access(filePath);189    } catch (error) {190      if (isMissingFile(error)) {191        return { changed: false, filePath };192      }193      throw error;194    }195 196    let release: () => Promise<void>;197    try {198      release = await lockfile.lock(filePath, LOCK_OPTIONS);199    } catch (error) {200      if (isMissingFile(error)) {201        return { changed: false, filePath };202      }203      throw error;204    }205    try {206      await fs.unlink(filePath);207      return { changed: true, filePath };208    } catch (error) {209      if (isMissingFile(error)) {210        return { changed: false, filePath };211      }212      throw error;213    } finally {214      await releaseLock(release);215    }216  });217}218 
basant307/AI_Governance_Project · CoolFace