200 lines
5.9 KiB
TypeScript
200 lines
5.9 KiB
TypeScript
// Memory V2 API 客户端。对应后端 /api/memory/v2 系列端点。
|
|
|
|
import { apiFetch } from "../api";
|
|
import { getBackendBaseURL } from "../config";
|
|
import { OptType, writeYLog } from "../log/y-log";
|
|
|
|
import type {
|
|
MemorySearchMode,
|
|
MemorySearchResult,
|
|
MemoryTarget,
|
|
MemoryV2ConfigResponse,
|
|
MemoryV2Data,
|
|
} from "./types";
|
|
|
|
/** 从响应里提取后端的错误详情文本。 */
|
|
function formatErrorDetail(detail: unknown): string | null {
|
|
if (typeof detail === "string") {
|
|
return detail;
|
|
}
|
|
if (Array.isArray(detail)) {
|
|
const parts = detail
|
|
.map((item) => {
|
|
if (typeof item === "string") return item;
|
|
if (item && typeof item === "object") {
|
|
const record = item as Record<string, unknown>;
|
|
if (typeof record.msg === "string") return record.msg;
|
|
}
|
|
return null;
|
|
})
|
|
.filter(Boolean);
|
|
return parts.length > 0 ? parts.join("; ") : null;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
async function readJson<T>(response: Response, fallbackMessage: string): Promise<T> {
|
|
if (!response.ok) {
|
|
const errorData = (await response.json().catch(() => ({}))) as {
|
|
detail?: unknown;
|
|
};
|
|
const detailMessage = formatErrorDetail(errorData.detail);
|
|
throw new Error(detailMessage ?? `${fallbackMessage}: ${response.statusText}`);
|
|
}
|
|
return response.json() as Promise<T>;
|
|
}
|
|
|
|
/** 拼接可选的 ?agent= 查询串。 */
|
|
function agentQuery(agent?: string): string {
|
|
return agent ? `?agent=${encodeURIComponent(agent)}` : "";
|
|
}
|
|
|
|
// --- 本地记忆数据 (USER.md / MEMORY.md) ---
|
|
|
|
export async function loadMemoryV2(agent?: string): Promise<MemoryV2Data> {
|
|
const response = await apiFetch(
|
|
`${getBackendBaseURL()}/api/memory/v2${agentQuery(agent)}`,
|
|
);
|
|
const data = await readJson<MemoryV2Data>(response, "Failed to fetch memory");
|
|
writeYLog({
|
|
optType: OptType.view,
|
|
optDetail: "CM助手-全量-记忆-查看",
|
|
url: `/api/memory/v2${agentQuery(agent)}`,
|
|
reqParam: { agent },
|
|
returnParam: data,
|
|
});
|
|
return data;
|
|
}
|
|
|
|
export async function addMemoryEntry(
|
|
target: MemoryTarget,
|
|
content: string,
|
|
agent?: string,
|
|
): Promise<MemoryV2Data> {
|
|
const response = await apiFetch(
|
|
`${getBackendBaseURL()}/api/memory/v2/entries${agentQuery(agent)}`,
|
|
{
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ target, content }),
|
|
},
|
|
);
|
|
const data = await readJson<MemoryV2Data>(response, "Failed to add memory entry");
|
|
writeYLog({
|
|
optType: OptType.create,
|
|
optDetail: "CM助手-全量-记忆-新增",
|
|
url: `/api/memory/v2/entries${agentQuery(agent)}`,
|
|
reqParam: { target, content, agent },
|
|
returnParam: data,
|
|
});
|
|
return data;
|
|
}
|
|
|
|
export async function replaceMemoryEntry(
|
|
target: MemoryTarget,
|
|
oldText: string,
|
|
content: string,
|
|
agent?: string,
|
|
): Promise<MemoryV2Data> {
|
|
const response = await apiFetch(
|
|
`${getBackendBaseURL()}/api/memory/v2/entries${agentQuery(agent)}`,
|
|
{
|
|
method: "PUT",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ target, old_text: oldText, content }),
|
|
},
|
|
);
|
|
const data = await readJson<MemoryV2Data>(response, "Failed to replace memory entry");
|
|
writeYLog({
|
|
optType: OptType.edit,
|
|
optDetail: "CM助手-全量-记忆-编辑",
|
|
url: `/api/memory/v2/entries${agentQuery(agent)}`,
|
|
reqParam: { target, old_text: oldText, content, agent },
|
|
returnParam: data,
|
|
});
|
|
return data;
|
|
}
|
|
|
|
export async function deleteMemoryEntry(
|
|
target: MemoryTarget,
|
|
oldText: string,
|
|
agent?: string,
|
|
): Promise<MemoryV2Data> {
|
|
const params = new URLSearchParams({ target, old_text: oldText });
|
|
if (agent) {
|
|
params.set("agent", agent);
|
|
}
|
|
const response = await apiFetch(
|
|
`${getBackendBaseURL()}/api/memory/v2/entries?${params.toString()}`,
|
|
{ method: "DELETE" },
|
|
);
|
|
const data = await readJson<MemoryV2Data>(response, "Failed to delete memory entry");
|
|
writeYLog({
|
|
optType: OptType.delete,
|
|
optDetail: "CM助手-全量-记忆-删除",
|
|
url: `/api/memory/v2/entries?${params.toString()}`,
|
|
reqParam: { target, old_text: oldText, agent },
|
|
returnParam: data,
|
|
});
|
|
return data;
|
|
}
|
|
|
|
// --- 配置 ---
|
|
|
|
export async function loadMemoryConfig(): Promise<MemoryV2ConfigResponse> {
|
|
const response = await apiFetch(`${getBackendBaseURL()}/api/memory/v2/config`);
|
|
const data = await readJson<MemoryV2ConfigResponse>(response, "Failed to fetch memory config");
|
|
writeYLog({
|
|
optType: OptType.view,
|
|
optDetail: "CM助手-全量-记忆-配置查看",
|
|
url: "/api/memory/v2/config",
|
|
returnParam: data,
|
|
});
|
|
return data;
|
|
}
|
|
|
|
export async function patchMemoryConfig(
|
|
overrides: Record<string, unknown>,
|
|
): Promise<MemoryV2ConfigResponse> {
|
|
const response = await apiFetch(`${getBackendBaseURL()}/api/memory/v2/config`, {
|
|
method: "PATCH",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ overrides }),
|
|
});
|
|
const data = await readJson<MemoryV2ConfigResponse>(response, "Failed to update memory config");
|
|
writeYLog({
|
|
optType: OptType.edit,
|
|
optDetail: "CM助手-全量-记忆-配置编辑",
|
|
url: "/api/memory/v2/config",
|
|
reqParam: { overrides },
|
|
returnParam: data,
|
|
});
|
|
return data;
|
|
}
|
|
|
|
// --- Hindsight 检索 ---
|
|
|
|
export async function searchMemory(
|
|
query: string,
|
|
mode: MemorySearchMode = "recall",
|
|
agent?: string,
|
|
): Promise<MemorySearchResult> {
|
|
const response = await apiFetch(
|
|
`${getBackendBaseURL()}/api/memory/v2/search${agentQuery(agent)}`,
|
|
{
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ query, mode }),
|
|
},
|
|
);
|
|
const data = await readJson<MemorySearchResult>(response, "Failed to search memory");
|
|
writeYLog({
|
|
optType: OptType.search,
|
|
optDetail: "CM助手-全量-记忆-检索",
|
|
url: `/api/memory/v2/search${agentQuery(agent)}`,
|
|
reqParam: { query, mode, agent },
|
|
returnParam: data,
|
|
});
|
|
return data;
|
|
}
|