新增 memory 和 skill 存储,实现 Agent 持续学习,并增加工具支持;增加 LLM progress detail 输出
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
||||
import { dirname, join } from "node:path";
|
||||
|
||||
type JsonRecord = Record<string, unknown>;
|
||||
|
||||
const isErrnoException = (error: unknown): error is NodeJS.ErrnoException =>
|
||||
error instanceof Error && "code" in error;
|
||||
|
||||
export const ensureDirectory = async (path: string) => {
|
||||
await mkdir(path, { recursive: true });
|
||||
};
|
||||
|
||||
export const atomicWriteFile = async (path: string, content: string) => {
|
||||
await ensureDirectory(dirname(path));
|
||||
const tempPath = `${path}.${process.pid}.${Date.now().toString(36)}.tmp`;
|
||||
await writeFile(tempPath, content, "utf8");
|
||||
await rename(tempPath, path);
|
||||
};
|
||||
|
||||
export const atomicWriteJson = async (path: string, value: JsonRecord | unknown[]) => {
|
||||
await atomicWriteFile(path, JSON.stringify(value, null, 2));
|
||||
};
|
||||
|
||||
export const readJsonFile = async <T>(path: string): Promise<T | null> => {
|
||||
try {
|
||||
const content = await readFile(path, "utf8");
|
||||
return JSON.parse(content) as T;
|
||||
} catch (error) {
|
||||
if (isErrnoException(error) && error.code === "ENOENT") {
|
||||
return null;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const readTextFile = async (path: string): Promise<string | null> => {
|
||||
try {
|
||||
return await readFile(path, "utf8");
|
||||
} catch (error) {
|
||||
if (isErrnoException(error) && error.code === "ENOENT") {
|
||||
return null;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const listJsonFiles = async (path: string) => {
|
||||
try {
|
||||
const names = await readdir(path);
|
||||
return names.filter((name) => name.endsWith(".json")).map((name) => join(path, name));
|
||||
} catch (error) {
|
||||
if (isErrnoException(error) && error.code === "ENOENT") {
|
||||
return [];
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const listFiles = async (path: string) => {
|
||||
try {
|
||||
const names = await readdir(path);
|
||||
return names.map((name) => join(path, name));
|
||||
} catch (error) {
|
||||
if (isErrnoException(error) && error.code === "ENOENT") {
|
||||
return [];
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const removeFileIfExists = async (path: string) => {
|
||||
try {
|
||||
await rm(path, { force: true });
|
||||
} catch (error) {
|
||||
if (isErrnoException(error) && error.code === "ENOENT") {
|
||||
return;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const getFileStat = async (path: string) => {
|
||||
try {
|
||||
return await stat(path);
|
||||
} catch (error) {
|
||||
if (isErrnoException(error) && error.code === "ENOENT") {
|
||||
return null;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const toScopedKey = (prefix: string, value?: string) => {
|
||||
const normalized = value?.trim() || `${prefix}-default`;
|
||||
return `${prefix}-${createHash("sha256").update(normalized).digest("hex").slice(0, 16)}`;
|
||||
};
|
||||
|
||||
export const toActorKey = (userId?: string) => toScopedKey("actor", userId);
|
||||
|
||||
export const toProjectKey = (projectId?: string) => toScopedKey("project", projectId);
|
||||
|
||||
export const toStableId = (...parts: string[]) =>
|
||||
createHash("sha256").update(parts.join("|")).digest("hex").slice(0, 24);
|
||||
|
||||
export const slugify = (value: string) =>
|
||||
value
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9._-]+/g, "-")
|
||||
.replace(/^-+|-+$/g, "")
|
||||
.slice(0, 64) || "entry";
|
||||
Reference in New Issue
Block a user