Skip to content

Commit 206540c

Browse files
authored
feat: add Memory event TUI (#1906)
1 parent d08ecca commit 206540c

14 files changed

Lines changed: 704 additions & 51 deletions

File tree

src/components/MemoryPicker.tsx

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import type { MemorySummary } from "@aws-sdk/client-bedrock-agentcore-control";
2+
import { useNavigate } from "react-router";
3+
import type { ScreenProps } from "../handlers/types";
4+
import { coreOptsFromCtx } from "../handlers/utils";
5+
import { formatTimestamp } from "./formatTimestamp";
6+
import { PaginatedTablePicker } from "./PaginatedTablePicker";
7+
import type { DataTableColumn } from "./ui/data-table";
8+
9+
interface MemoryRow extends Record<string, unknown> {
10+
memoryId: string;
11+
status: string;
12+
updatedAt: string;
13+
}
14+
15+
export const memoryColumns = [
16+
{ key: "memoryId", header: "id", flex: true },
17+
{ key: "status", header: "status", width: 13 },
18+
{
19+
key: "updatedAt",
20+
header: "updated UTC",
21+
width: 16,
22+
render: formatTimestamp,
23+
},
24+
] satisfies DataTableColumn<MemoryRow>[];
25+
26+
function toRow(memory: MemorySummary): MemoryRow {
27+
return {
28+
memoryId: memory.id ?? "",
29+
status: memory.status ?? "-",
30+
updatedAt: memory.updatedAt?.toISOString() ?? "-",
31+
};
32+
}
33+
34+
export interface MemoryPickerProps extends ScreenProps {
35+
breadcrumb: string[];
36+
description?: string;
37+
onSelect: (memoryId: string) => void;
38+
}
39+
40+
export function MemoryPicker({ ctx, core, breadcrumb, description, onSelect }: MemoryPickerProps) {
41+
const opts = coreOptsFromCtx(ctx);
42+
const navigate = useNavigate();
43+
const goBack = () => navigate("/" + breadcrumb.slice(0, -1).join("/"));
44+
45+
return (
46+
<PaginatedTablePicker
47+
breadcrumb={breadcrumb}
48+
description={description}
49+
queryKey={["memories", opts.region]}
50+
loadPage={async (token, pageSize) => {
51+
const response = await core.memory.listMemories(token, pageSize, opts);
52+
return {
53+
items: response.memories ?? [],
54+
nextToken: response.nextToken,
55+
};
56+
}}
57+
toRow={toRow}
58+
columns={memoryColumns}
59+
getValue={(row) => row.memoryId}
60+
onSelect={onSelect}
61+
onBack={goBack}
62+
loadingMessage="Loading Memories..."
63+
errorMessage={(error) => `Error: ${error.message}`}
64+
emptyMessage="No Memories found in this Region."
65+
emptyPageMessage="No Memories on this page."
66+
/>
67+
);
68+
}

src/components/Root.tsx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ import { MemoryScreen } from "../handlers/memory/screen.tsx";
3232
import { MemoryGetJsonScreen, MemoryGetScreen } from "../handlers/memory/get/screen.tsx";
3333
import { MemoryListScreen } from "../handlers/memory/list/screen.tsx";
3434
import { RuntimeInvokeScreen } from "../handlers/runtime/invoke/screen.tsx";
35+
import { MemoryEventScreen } from "../handlers/memory/event/screen.tsx";
36+
import { MemoryEventGetScreen } from "../handlers/memory/event/get/screen.tsx";
37+
import { MemoryEventListScreen } from "../handlers/memory/event/list/screen.tsx";
3538
import { RootScreen, HelpScreen } from "../handlers/screen.tsx";
3639
import type { Context } from "../router";
3740

@@ -288,6 +291,34 @@ export function Root({ path, ctx, core, queryClient }: RootProps) {
288291
path="agentcore/runtime/invoke/:runtimeId/:qualifier"
289292
element={<RuntimeInvokeScreen ctx={ctx} core={core} />}
290293
/>
294+
<Route
295+
path="agentcore/memory/event"
296+
element={<MemoryEventScreen ctx={ctx} core={core} />}
297+
/>
298+
<Route
299+
path="agentcore/memory/event/get"
300+
element={<Navigate to="/agentcore/memory/event/list" replace />}
301+
/>
302+
<Route
303+
path="agentcore/memory/event/get/:memoryId/:actorId/:sessionId/:eventId"
304+
element={<MemoryEventGetScreen ctx={ctx} core={core} />}
305+
/>
306+
<Route
307+
path="agentcore/memory/event/list"
308+
element={<MemoryEventListScreen ctx={ctx} core={core} />}
309+
/>
310+
<Route
311+
path="agentcore/memory/event/list/:memoryId"
312+
element={<MemoryEventListScreen ctx={ctx} core={core} />}
313+
/>
314+
<Route
315+
path="agentcore/memory/event/list/:memoryId/:actorId"
316+
element={<MemoryEventListScreen ctx={ctx} core={core} />}
317+
/>
318+
<Route
319+
path="agentcore/memory/event/list/:memoryId/:actorId/:sessionId"
320+
element={<MemoryEventListScreen ctx={ctx} core={core} />}
321+
/>
291322
<Route path="*" element={<HelpScreen ctx={ctx} core={core} />} />
292323
</Routes>
293324
</MemoryRouter>

src/core/core.test.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@ import {
1010
InvokeAgentRuntimeCommand,
1111
InvokeAgentRuntimeCommandCommand,
1212
InvokeHarnessCommand,
13+
ListActorsCommand,
1314
ListEventsCommand,
1415
ListMemoryRecordsCommand,
16+
ListSessionsCommand,
1517
type BedrockAgentCoreClient,
1618
} from "@aws-sdk/client-bedrock-agentcore";
1719
import type { RuntimeInvokeRequest } from "../handlers/runtime/types";
@@ -244,6 +246,49 @@ test("listEvents sends a ListEventsCommand on the data client", async () => {
244246
expect((sent[0] as ListEventsCommand).input).toEqual(input);
245247
});
246248

249+
test("listActors sends a ListActorsCommand on the data client", async () => {
250+
const sent: unknown[] = [];
251+
const response = { actorSummaries: [], nextToken: "next" };
252+
const core = coreWithDataSend(async (command) => {
253+
sent.push(command);
254+
return response;
255+
});
256+
const input = {
257+
memoryId: "memory-123",
258+
maxResults: 25,
259+
nextToken: "current",
260+
};
261+
262+
const result = await core.memory.listActors(input, { region: "us-east-1" });
263+
264+
expect(result).toBe(response);
265+
expect(sent).toHaveLength(1);
266+
expect(sent[0]).toBeInstanceOf(ListActorsCommand);
267+
expect((sent[0] as ListActorsCommand).input).toEqual(input);
268+
});
269+
270+
test("listSessions sends a ListSessionsCommand on the data client", async () => {
271+
const sent: unknown[] = [];
272+
const response = { sessionSummaries: [], nextToken: "next" };
273+
const core = coreWithDataSend(async (command) => {
274+
sent.push(command);
275+
return response;
276+
});
277+
const input = {
278+
memoryId: "memory-123",
279+
actorId: "actor-123",
280+
maxResults: 25,
281+
nextToken: "current",
282+
};
283+
284+
const result = await core.memory.listSessions(input, { region: "us-east-1" });
285+
286+
expect(result).toBe(response);
287+
expect(sent).toHaveLength(1);
288+
expect(sent[0]).toBeInstanceOf(ListSessionsCommand);
289+
expect((sent[0] as ListSessionsCommand).input).toEqual(input);
290+
});
291+
247292
test("getMemoryRecord sends a GetMemoryRecordCommand on the data client", async () => {
248293
const sent: unknown[] = [];
249294
const response = { memoryRecord: undefined };

src/core/memory.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
11
import {
22
GetEventCommand,
33
GetMemoryRecordCommand,
4+
ListActorsCommand,
45
ListEventsCommand,
56
ListMemoryRecordsCommand,
7+
ListSessionsCommand,
68
type GetEventInput,
79
type GetEventOutput,
810
type GetMemoryRecordInput,
911
type GetMemoryRecordOutput,
12+
type ListActorsInput,
13+
type ListActorsOutput,
1014
type ListEventsInput,
1115
type ListEventsOutput,
1216
type ListMemoryRecordsInput,
1317
type ListMemoryRecordsOutput,
18+
type ListSessionsInput,
19+
type ListSessionsOutput,
1420
} from "@aws-sdk/client-bedrock-agentcore";
1521
import {
1622
GetMemoryCommand,
@@ -46,6 +52,14 @@ export class MemoryClient implements CoreMemoryClient {
4652
return this.clients.data(toClientConfig(options)).send(new GetEventCommand(input));
4753
}
4854

55+
async listActors(input: ListActorsInput, options: CoreOptions): Promise<ListActorsOutput> {
56+
return this.clients.data(toClientConfig(options)).send(new ListActorsCommand(input));
57+
}
58+
59+
async listSessions(input: ListSessionsInput, options: CoreOptions): Promise<ListSessionsOutput> {
60+
return this.clients.data(toClientConfig(options)).send(new ListSessionsCommand(input));
61+
}
62+
4963
async listEvents(input: ListEventsInput, options: CoreOptions): Promise<ListEventsOutput> {
5064
return this.clients.data(toClientConfig(options)).send(new ListEventsCommand(input));
5165
}

0 commit comments

Comments
 (0)