mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-17 23:51:30 +00:00
feat(ui): add Memory page to view/manage /v1/memory entries
Adds a new "Memory" sidebar item under Tools so users can see what their agents have stored. Lists all memories visible to the caller (scoped by the backend), with a key-search filter, preview column, scope tags, and view/edit/delete actions. Create modal accepts optional JSON metadata. - networking.tsx: fetchMemoryList / createMemory / updateMemory / deleteMemory wired to the /v1/memory CRUD endpoints. - MemoryView + MemoryEditModal: new antd-based components (per CLAUDE.md: use antd for new UI, not tremor). - page.tsx + leftnav.tsx: wire the "memory" route + sidebar entry. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
bd9e1ba0a0
commit
b0b1ea3740
6 changed files with 671 additions and 0 deletions
|
|
@ -39,6 +39,7 @@ import { AccessGroupsPage } from "@/components/AccessGroups/AccessGroupsPage";
|
|||
import { ProjectsPage } from "@/components/Projects/ProjectsPage";
|
||||
import VectorStoreManagement from "@/components/vector_store_management";
|
||||
import ToolPoliciesView from "@/components/ToolPoliciesView";
|
||||
import { MemoryView } from "@/components/MemoryView";
|
||||
import SpendLogsTable from "@/components/view_logs";
|
||||
import ViewUserDashboard from "@/components/view_users";
|
||||
import { ThemeProvider } from "@/contexts/ThemeContext";
|
||||
|
|
@ -630,6 +631,12 @@ function CreateKeyPageContent() {
|
|||
<VectorStoreManagement accessToken={accessToken} userRole={userRole} userID={userID} />
|
||||
) : page == "tool-policies" ? (
|
||||
<ToolPoliciesView accessToken={accessToken} userRole={userRole} />
|
||||
) : page == "memory" ? (
|
||||
<MemoryView
|
||||
accessToken={accessToken}
|
||||
userID={userID}
|
||||
userRole={userRole}
|
||||
/>
|
||||
) : page == "guardrails-monitor" ? (
|
||||
<GuardrailsMonitorView accessToken={accessToken} />
|
||||
) : page == "new_usage" ? (
|
||||
|
|
|
|||
|
|
@ -0,0 +1,121 @@
|
|||
"use client";
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Form, Input, Modal, Typography } from "antd";
|
||||
import type { MemoryRow } from "../networking";
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
interface MemoryEditModalProps {
|
||||
open: boolean;
|
||||
mode: "create" | "edit";
|
||||
initialRow?: MemoryRow;
|
||||
onClose: () => void;
|
||||
onSave: (
|
||||
key: string,
|
||||
value: string,
|
||||
metadataText: string,
|
||||
isCreate: boolean,
|
||||
) => Promise<boolean>;
|
||||
}
|
||||
|
||||
export const MemoryEditModal: React.FC<MemoryEditModalProps> = ({
|
||||
open,
|
||||
mode,
|
||||
initialRow,
|
||||
onClose,
|
||||
onSave,
|
||||
}) => {
|
||||
const [form] = Form.useForm();
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
if (mode === "edit" && initialRow) {
|
||||
form.setFieldsValue({
|
||||
key: initialRow.key,
|
||||
value: initialRow.value,
|
||||
metadata:
|
||||
initialRow.metadata != null
|
||||
? JSON.stringify(initialRow.metadata, null, 2)
|
||||
: "",
|
||||
});
|
||||
} else {
|
||||
form.resetFields();
|
||||
}
|
||||
}, [open, mode, initialRow, form]);
|
||||
|
||||
const handleOk = async () => {
|
||||
const values = await form.validateFields();
|
||||
setSubmitting(true);
|
||||
const ok = await onSave(
|
||||
values.key.trim(),
|
||||
values.value ?? "",
|
||||
values.metadata ?? "",
|
||||
mode === "create",
|
||||
);
|
||||
setSubmitting(false);
|
||||
if (ok) {
|
||||
form.resetFields();
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={open}
|
||||
title={mode === "create" ? "Create memory" : `Edit ${initialRow?.key ?? ""}`}
|
||||
onCancel={() => {
|
||||
form.resetFields();
|
||||
onClose();
|
||||
}}
|
||||
onOk={handleOk}
|
||||
okText={mode === "create" ? "Create" : "Save"}
|
||||
confirmLoading={submitting}
|
||||
width={640}
|
||||
destroyOnClose
|
||||
>
|
||||
<Form form={form} layout="vertical">
|
||||
<Form.Item
|
||||
label="Key"
|
||||
name="key"
|
||||
rules={[{ required: true, message: "Key is required" }]}
|
||||
tooltip="Unique per user/team scope. Acts as the namespace in /v1/memory/{key}."
|
||||
>
|
||||
<Input
|
||||
placeholder="e.g. user_role"
|
||||
disabled={mode === "edit"}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Value"
|
||||
name="value"
|
||||
rules={[{ required: true, message: "Value is required" }]}
|
||||
tooltip="Markdown/text injected into LLM context. Plain strings are fine."
|
||||
>
|
||||
<Input.TextArea
|
||||
rows={8}
|
||||
placeholder="What the agent should remember…"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={
|
||||
<span>
|
||||
Metadata <Text type="secondary">(optional JSON)</Text>
|
||||
</span>
|
||||
}
|
||||
name="metadata"
|
||||
tooltip="Optional structured metadata — must be valid JSON if provided."
|
||||
>
|
||||
<Input.TextArea
|
||||
rows={4}
|
||||
placeholder='{"tags": ["example"]}'
|
||||
style={{ fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace" }}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default MemoryEditModal;
|
||||
419
ui/litellm-dashboard/src/components/MemoryView/MemoryView.tsx
Normal file
419
ui/litellm-dashboard/src/components/MemoryView/MemoryView.tsx
Normal file
|
|
@ -0,0 +1,419 @@
|
|||
"use client";
|
||||
|
||||
import React, { useMemo, useState } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Drawer,
|
||||
Empty,
|
||||
Input,
|
||||
Modal,
|
||||
Space,
|
||||
Table,
|
||||
Tag,
|
||||
Typography,
|
||||
message,
|
||||
} from "antd";
|
||||
import type { ColumnsType } from "antd/es/table";
|
||||
import {
|
||||
DeleteOutlined,
|
||||
EditOutlined,
|
||||
EyeOutlined,
|
||||
PlusOutlined,
|
||||
ReloadOutlined,
|
||||
SearchOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import {
|
||||
MemoryRow,
|
||||
createMemory,
|
||||
deleteMemory,
|
||||
fetchMemoryList,
|
||||
updateMemory,
|
||||
} from "../networking";
|
||||
import { MemoryEditModal } from "./MemoryEditModal";
|
||||
|
||||
const { Text, Paragraph, Title } = Typography;
|
||||
|
||||
interface MemoryViewProps {
|
||||
accessToken: string | null;
|
||||
userID: string | null;
|
||||
userRole: string | null;
|
||||
}
|
||||
|
||||
function previewValue(value: string, max = 120): string {
|
||||
if (!value) return "";
|
||||
const trimmed = value.trim();
|
||||
if (trimmed.length <= max) return trimmed;
|
||||
return `${trimmed.slice(0, max)}…`;
|
||||
}
|
||||
|
||||
function formatTimestamp(ts?: string): string {
|
||||
if (!ts) return "—";
|
||||
try {
|
||||
const d = new Date(ts);
|
||||
return d.toLocaleString();
|
||||
} catch {
|
||||
return ts;
|
||||
}
|
||||
}
|
||||
|
||||
export const MemoryView: React.FC<MemoryViewProps> = ({ accessToken }) => {
|
||||
const [searchInput, setSearchInput] = useState("");
|
||||
const [appliedSearch, setAppliedSearch] = useState("");
|
||||
const [detailRow, setDetailRow] = useState<MemoryRow | null>(null);
|
||||
const [editRow, setEditRow] = useState<MemoryRow | null>(null);
|
||||
const [isCreateOpen, setIsCreateOpen] = useState(false);
|
||||
|
||||
const {
|
||||
data,
|
||||
isLoading,
|
||||
isFetching,
|
||||
refetch,
|
||||
} = useQuery({
|
||||
queryKey: ["memoryList", appliedSearch],
|
||||
queryFn: () => {
|
||||
if (!accessToken) throw new Error("Access token required");
|
||||
// Current API supports exact key match; treat empty search as list-all.
|
||||
return fetchMemoryList(accessToken, {
|
||||
keyPrefix: appliedSearch || undefined,
|
||||
pageSize: 200,
|
||||
});
|
||||
},
|
||||
enabled: !!accessToken,
|
||||
});
|
||||
|
||||
const rows = useMemo(() => data?.memories ?? [], [data]);
|
||||
|
||||
const handleDelete = async (row: MemoryRow) => {
|
||||
Modal.confirm({
|
||||
title: "Delete memory",
|
||||
content: (
|
||||
<span>
|
||||
Delete memory key <Text code>{row.key}</Text>? This cannot be undone.
|
||||
</span>
|
||||
),
|
||||
okText: "Delete",
|
||||
okType: "danger",
|
||||
cancelText: "Cancel",
|
||||
onOk: async () => {
|
||||
if (!accessToken) return;
|
||||
try {
|
||||
await deleteMemory(accessToken, row.key);
|
||||
message.success(`Deleted ${row.key}`);
|
||||
refetch();
|
||||
} catch (err: any) {
|
||||
message.error(`Delete failed: ${err?.message ?? err}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleSave = async (
|
||||
key: string,
|
||||
value: string,
|
||||
metadataText: string,
|
||||
isCreate: boolean,
|
||||
): Promise<boolean> => {
|
||||
if (!accessToken) return false;
|
||||
let metadataParsed: unknown | undefined;
|
||||
if (metadataText.trim()) {
|
||||
try {
|
||||
metadataParsed = JSON.parse(metadataText);
|
||||
} catch {
|
||||
message.error("Metadata must be valid JSON (or leave empty).");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
try {
|
||||
if (isCreate) {
|
||||
await createMemory(accessToken, {
|
||||
key,
|
||||
value,
|
||||
metadata: metadataParsed,
|
||||
});
|
||||
message.success(`Created ${key}`);
|
||||
} else {
|
||||
await updateMemory(accessToken, key, {
|
||||
value,
|
||||
metadata: metadataParsed,
|
||||
});
|
||||
message.success(`Updated ${key}`);
|
||||
}
|
||||
refetch();
|
||||
return true;
|
||||
} catch (err: any) {
|
||||
message.error(`Save failed: ${err?.message ?? err}`);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const columns: ColumnsType<MemoryRow> = [
|
||||
{
|
||||
title: "Key",
|
||||
dataIndex: "key",
|
||||
key: "key",
|
||||
render: (k: string) => <Text code>{k}</Text>,
|
||||
sorter: (a, b) => a.key.localeCompare(b.key),
|
||||
width: 240,
|
||||
},
|
||||
{
|
||||
title: "Preview",
|
||||
dataIndex: "value",
|
||||
key: "value",
|
||||
render: (v: string) => (
|
||||
<Text type="secondary" style={{ whiteSpace: "pre-wrap" }}>
|
||||
{previewValue(v)}
|
||||
</Text>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Scope",
|
||||
key: "scope",
|
||||
width: 220,
|
||||
render: (_: unknown, r: MemoryRow) => (
|
||||
<Space size={4} wrap>
|
||||
{r.user_id && <Tag color="blue">user: {r.user_id}</Tag>}
|
||||
{r.team_id && <Tag color="purple">team: {r.team_id}</Tag>}
|
||||
{!r.user_id && !r.team_id && <Tag>global</Tag>}
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Updated",
|
||||
dataIndex: "updated_at",
|
||||
key: "updated_at",
|
||||
width: 180,
|
||||
render: (ts?: string) => (
|
||||
<Text type="secondary">{formatTimestamp(ts)}</Text>
|
||||
),
|
||||
sorter: (a, b) =>
|
||||
new Date(a.updated_at ?? 0).getTime() -
|
||||
new Date(b.updated_at ?? 0).getTime(),
|
||||
defaultSortOrder: "descend",
|
||||
},
|
||||
{
|
||||
title: "",
|
||||
key: "actions",
|
||||
width: 140,
|
||||
render: (_: unknown, r: MemoryRow) => (
|
||||
<Space size={4}>
|
||||
<Button
|
||||
size="small"
|
||||
type="text"
|
||||
icon={<EyeOutlined />}
|
||||
onClick={() => setDetailRow(r)}
|
||||
aria-label="View"
|
||||
/>
|
||||
<Button
|
||||
size="small"
|
||||
type="text"
|
||||
icon={<EditOutlined />}
|
||||
onClick={() => setEditRow(r)}
|
||||
aria-label="Edit"
|
||||
/>
|
||||
<Button
|
||||
size="small"
|
||||
type="text"
|
||||
danger
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={() => handleDelete(r)}
|
||||
aria-label="Delete"
|
||||
/>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div style={{ padding: 24 }}>
|
||||
<Space
|
||||
direction="vertical"
|
||||
size="large"
|
||||
style={{ width: "100%" }}
|
||||
>
|
||||
<div>
|
||||
<Title level={3} style={{ marginBottom: 4 }}>
|
||||
Memory
|
||||
</Title>
|
||||
<Paragraph type="secondary" style={{ marginBottom: 0 }}>
|
||||
Inspect what your agents have stored under <Text code>/v1/memory</Text>.
|
||||
Scoped to memories visible to your user / team (admins see all).
|
||||
</Paragraph>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<Space
|
||||
style={{
|
||||
width: "100%",
|
||||
justifyContent: "space-between",
|
||||
marginBottom: 16,
|
||||
}}
|
||||
wrap
|
||||
>
|
||||
<Space>
|
||||
<Input
|
||||
allowClear
|
||||
placeholder="Filter by exact key"
|
||||
prefix={<SearchOutlined />}
|
||||
value={searchInput}
|
||||
onChange={(e) => setSearchInput(e.target.value)}
|
||||
onPressEnter={() => setAppliedSearch(searchInput.trim())}
|
||||
onClear={() => {
|
||||
setSearchInput("");
|
||||
setAppliedSearch("");
|
||||
}}
|
||||
style={{ width: 280 }}
|
||||
/>
|
||||
<Button
|
||||
type="primary"
|
||||
ghost
|
||||
onClick={() => setAppliedSearch(searchInput.trim())}
|
||||
>
|
||||
Search
|
||||
</Button>
|
||||
<Button
|
||||
icon={<ReloadOutlined />}
|
||||
onClick={() => refetch()}
|
||||
loading={isFetching && !isLoading}
|
||||
>
|
||||
Refresh
|
||||
</Button>
|
||||
</Space>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => setIsCreateOpen(true)}
|
||||
>
|
||||
New memory
|
||||
</Button>
|
||||
</Space>
|
||||
|
||||
<Table
|
||||
rowKey="memory_id"
|
||||
loading={isLoading}
|
||||
dataSource={rows}
|
||||
columns={columns}
|
||||
pagination={{ pageSize: 20, showSizeChanger: true }}
|
||||
locale={{
|
||||
emptyText: (
|
||||
<Empty
|
||||
description={
|
||||
appliedSearch
|
||||
? `No memories found for key "${appliedSearch}"`
|
||||
: "No memories stored yet"
|
||||
}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
{data?.total !== undefined && (
|
||||
<Text type="secondary">
|
||||
Showing {rows.length} of {data.total}
|
||||
</Text>
|
||||
)}
|
||||
</Card>
|
||||
</Space>
|
||||
|
||||
{/* Detail drawer */}
|
||||
<Drawer
|
||||
open={!!detailRow}
|
||||
onClose={() => setDetailRow(null)}
|
||||
title={
|
||||
detailRow ? (
|
||||
<Space>
|
||||
<Text code>{detailRow.key}</Text>
|
||||
</Space>
|
||||
) : (
|
||||
"Memory"
|
||||
)
|
||||
}
|
||||
width={720}
|
||||
destroyOnClose
|
||||
>
|
||||
{detailRow && (
|
||||
<Space direction="vertical" size="middle" style={{ width: "100%" }}>
|
||||
<div>
|
||||
<Text strong>Scope</Text>
|
||||
<div>
|
||||
<Space size={4} wrap>
|
||||
{detailRow.user_id && (
|
||||
<Tag color="blue">user: {detailRow.user_id}</Tag>
|
||||
)}
|
||||
{detailRow.team_id && (
|
||||
<Tag color="purple">team: {detailRow.team_id}</Tag>
|
||||
)}
|
||||
{!detailRow.user_id && !detailRow.team_id && <Tag>global</Tag>}
|
||||
</Space>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Text strong>Value</Text>
|
||||
<Paragraph
|
||||
style={{
|
||||
background: "#fafafa",
|
||||
padding: 12,
|
||||
borderRadius: 6,
|
||||
whiteSpace: "pre-wrap",
|
||||
fontFamily:
|
||||
"ui-monospace, SFMono-Regular, Menlo, monospace",
|
||||
fontSize: 13,
|
||||
}}
|
||||
>
|
||||
{detailRow.value}
|
||||
</Paragraph>
|
||||
</div>
|
||||
{detailRow.metadata !== undefined &&
|
||||
detailRow.metadata !== null && (
|
||||
<div>
|
||||
<Text strong>Metadata</Text>
|
||||
<Paragraph
|
||||
style={{
|
||||
background: "#fafafa",
|
||||
padding: 12,
|
||||
borderRadius: 6,
|
||||
whiteSpace: "pre-wrap",
|
||||
fontFamily:
|
||||
"ui-monospace, SFMono-Regular, Menlo, monospace",
|
||||
fontSize: 12,
|
||||
}}
|
||||
>
|
||||
{JSON.stringify(detailRow.metadata, null, 2)}
|
||||
</Paragraph>
|
||||
</div>
|
||||
)}
|
||||
<Space
|
||||
split={<Text type="secondary">·</Text>}
|
||||
wrap
|
||||
size="small"
|
||||
style={{ color: "rgba(0,0,0,0.45)" }}
|
||||
>
|
||||
<Text type="secondary">
|
||||
Created {formatTimestamp(detailRow.created_at)}
|
||||
{detailRow.created_by ? ` by ${detailRow.created_by}` : ""}
|
||||
</Text>
|
||||
<Text type="secondary">
|
||||
Updated {formatTimestamp(detailRow.updated_at)}
|
||||
{detailRow.updated_by ? ` by ${detailRow.updated_by}` : ""}
|
||||
</Text>
|
||||
</Space>
|
||||
</Space>
|
||||
)}
|
||||
</Drawer>
|
||||
|
||||
{/* Create / edit modal */}
|
||||
<MemoryEditModal
|
||||
open={isCreateOpen || !!editRow}
|
||||
mode={editRow ? "edit" : "create"}
|
||||
initialRow={editRow ?? undefined}
|
||||
onClose={() => {
|
||||
setIsCreateOpen(false);
|
||||
setEditRow(null);
|
||||
}}
|
||||
onSave={handleSave}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MemoryView;
|
||||
1
ui/litellm-dashboard/src/components/MemoryView/index.tsx
Normal file
1
ui/litellm-dashboard/src/components/MemoryView/index.tsx
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { MemoryView, default } from "./MemoryView";
|
||||
|
|
@ -180,6 +180,12 @@ const menuGroups: MenuGroup[] = [
|
|||
label: "Tool Policies",
|
||||
icon: <SafetyOutlined />,
|
||||
},
|
||||
{
|
||||
key: "memory",
|
||||
page: "memory",
|
||||
label: "Memory",
|
||||
icon: <BookOutlined />,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
|
|
|||
|
|
@ -9986,3 +9986,120 @@ export const listMCPUserCredentials = async (
|
|||
if (!response.ok) return [];
|
||||
return response.json();
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// Memory management (/v1/memory)
|
||||
// ============================================================
|
||||
|
||||
export interface MemoryRow {
|
||||
memory_id: string;
|
||||
key: string;
|
||||
value: string;
|
||||
metadata?: unknown;
|
||||
user_id?: string | null;
|
||||
team_id?: string | null;
|
||||
created_at?: string;
|
||||
created_by?: string | null;
|
||||
updated_at?: string;
|
||||
updated_by?: string | null;
|
||||
}
|
||||
|
||||
export interface MemoryListResponse {
|
||||
memories: MemoryRow[];
|
||||
total: number;
|
||||
}
|
||||
|
||||
export const fetchMemoryList = async (
|
||||
accessToken: string,
|
||||
options: { keyPrefix?: string; page?: number; pageSize?: number } = {},
|
||||
): Promise<MemoryListResponse> => {
|
||||
const base = proxyBaseUrl ? `${proxyBaseUrl}/v1/memory` : `/v1/memory`;
|
||||
const params = new URLSearchParams();
|
||||
if (options.keyPrefix) params.append("key", options.keyPrefix);
|
||||
if (options.page != null) params.append("page", String(options.page));
|
||||
if (options.pageSize != null)
|
||||
params.append("page_size", String(options.pageSize));
|
||||
const url = params.toString() ? `${base}?${params.toString()}` : base;
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
if (!response.ok) {
|
||||
const errorData = await response.text();
|
||||
throw new Error(errorData);
|
||||
}
|
||||
return response.json();
|
||||
};
|
||||
|
||||
export const createMemory = async (
|
||||
accessToken: string,
|
||||
payload: { key: string; value: string; metadata?: unknown },
|
||||
): Promise<MemoryRow> => {
|
||||
const url = proxyBaseUrl ? `${proxyBaseUrl}/v1/memory` : `/v1/memory`;
|
||||
const body: Record<string, unknown> = {
|
||||
key: payload.key,
|
||||
value: payload.value,
|
||||
};
|
||||
if (payload.metadata !== undefined) body.metadata = payload.metadata;
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
if (!response.ok) {
|
||||
const errorData = await response.text();
|
||||
throw new Error(errorData);
|
||||
}
|
||||
return response.json();
|
||||
};
|
||||
|
||||
export const updateMemory = async (
|
||||
accessToken: string,
|
||||
key: string,
|
||||
payload: { value?: string; metadata?: unknown },
|
||||
): Promise<MemoryRow> => {
|
||||
const encoded = encodeURIComponent(key);
|
||||
const url = proxyBaseUrl
|
||||
? `${proxyBaseUrl}/v1/memory/${encoded}`
|
||||
: `/v1/memory/${encoded}`;
|
||||
const response = await fetch(url, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
if (!response.ok) {
|
||||
const errorData = await response.text();
|
||||
throw new Error(errorData);
|
||||
}
|
||||
return response.json();
|
||||
};
|
||||
|
||||
export const deleteMemory = async (
|
||||
accessToken: string,
|
||||
key: string,
|
||||
): Promise<void> => {
|
||||
const encoded = encodeURIComponent(key);
|
||||
const url = proxyBaseUrl
|
||||
? `${proxyBaseUrl}/v1/memory/${encoded}`
|
||||
: `/v1/memory/${encoded}`;
|
||||
const response = await fetch(url, {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
if (!response.ok) {
|
||||
const errorData = await response.text();
|
||||
throw new Error(errorData);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue