Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 22 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,13 @@ agentcore # interactive TUI
│ └── list # list a Runtime's endpoints
├── memory # inspect AgentCore Memories
│ ├── get # fetch a Memory by id
│ └── list # list Memories (server-side paginated)
│ ├── list # list Memories (server-side paginated)
│ ├── event
│ │ ├── get # get an Event from a Memory session
│ │ └── list # list Events from a Memory session
│ └── record
│ ├── get # get a long-term Memory record
│ └── list # list long-term Memory records
├── gateway # inspect AgentCore Gateways
│ ├── get # get a Gateway by id
│ ├── list # list Gateways (server-side paginated)
Expand Down Expand Up @@ -136,6 +142,10 @@ agentcore runtime endpoint list --id <runtimeId> --max-results 20
agentcore memory get --id <memoryId>
agentcore memory get --id <memoryId> --view without_decryption
agentcore memory list --max-results 20
agentcore memory event get --memory <memoryId> --actor-id <actorId> --session-id <sessionId> --event-id <eventId>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The examples include these commands, but the command tree still shows Memory with only get and list. We should add the event and record groups there too.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interesting, I thought it did, let me check

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be lines 68-70 i believe

agentcore memory event list --memory <memoryId> --actor-id <actorId> --session-id <sessionId> --max-results 20
agentcore memory record get --memory <memoryId> --record-id <recordId>
agentcore memory record list --memory <memoryId> --namespace <namespace> --max-results 20

# Inspect Gateway resources without project configuration or deployment
agentcore gateway get --id <gatewayId>
Expand Down Expand Up @@ -279,10 +289,14 @@ accept ARNs, `--version`, `--interactive`, cross-account targets, or custom
request paths. All requests use the Runtime `/invocations` route, including MCP
Runtimes.

Bare Runtime and Memory branches and leaves require a TTY on stdin and stdout.
Bare Runtime branches and leaves, plus `memory`, `memory get`, and `memory list`,
require a TTY on stdin and stdout.
For Runtime Invoke, supplying a payload or headless-only request or output flags
runs headlessly; `--session-id` can instead seed the persistent console.
`--json` always suppresses TUI rendering.
Supplying Memory operation flags runs those commands headlessly, and `--json`
always suppresses TUI rendering. The `memory event` and `memory record` groups
are headless: invoking a group without a leaf prints help, and their leaves
require resource selectors.

```bash
agentcore runtime
Expand All @@ -293,6 +307,8 @@ agentcore runtime endpoint list
agentcore memory
agentcore memory list
agentcore memory get
agentcore memory event
agentcore memory record
```

---
Expand Down Expand Up @@ -704,8 +720,9 @@ A Husky pre-commit hook runs Prettier (via lint-staged) on staged files automati

- **Cover more AgentCore resources.** The harness surface (CRUD, versions,
endpoints, invoke, exec) is fully implemented in both the CLI and the TUI;
the same patterns extend naturally to gateways, Memory mutations and
data-plane operations, browser profiles, and the other AgentCore resources.
the same patterns extend naturally to gateways, the remaining read-only
Memory data-plane operations, browser profiles, and the other AgentCore
resources.
- **Implement `config`.** The `config` command is currently a stub — it should
read/write real global settings (telemetry, log level, ...) through an
injected config accessor.
93 changes: 93 additions & 0 deletions src/core/core.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@ import {
} from "@aws-sdk/client-bedrock-agentcore-control";
import type { IAMClient } from "@aws-sdk/client-iam";
import {
GetEventCommand,
GetMemoryRecordCommand,
InvokeAgentRuntimeCommand,
InvokeAgentRuntimeCommandCommand,
InvokeHarnessCommand,
ListEventsCommand,
ListMemoryRecordsCommand,
type BedrockAgentCoreClient,
} from "@aws-sdk/client-bedrock-agentcore";
import type { RuntimeInvokeRequest } from "../handlers/runtime/types";
Expand Down Expand Up @@ -194,6 +198,95 @@ test("exposes feature sub-clients", () => {
expect(core.gateway).toBeDefined();
});

test("getEvent sends a GetEventCommand on the data client", async () => {
const sent: unknown[] = [];
const response = { event: undefined };
const core = coreWithDataSend(async (command) => {
sent.push(command);
return response;
});
const input = {
memoryId: "memory-123",
actorId: "actor-123",
sessionId: "session-123",
eventId: "event-123",
};

const result = await core.memory.getEvent(input, { region: "us-east-1" });

expect(result).toBe(response);
expect(sent).toHaveLength(1);
expect(sent[0]).toBeInstanceOf(GetEventCommand);
expect((sent[0] as GetEventCommand).input).toEqual(input);
});

test("listEvents sends a ListEventsCommand on the data client", async () => {
const sent: unknown[] = [];
const response = { events: [], nextToken: "next" };
const core = coreWithDataSend(async (command) => {
sent.push(command);
return response;
});
const input = {
memoryId: "memory-123",
actorId: "actor-123",
sessionId: "session-123",
includePayloads: true,
maxResults: 25,
nextToken: "current",
};

const result = await core.memory.listEvents(input, { region: "us-east-1" });

expect(result).toBe(response);
expect(sent).toHaveLength(1);
expect(sent[0]).toBeInstanceOf(ListEventsCommand);
expect((sent[0] as ListEventsCommand).input).toEqual(input);
});

test("getMemoryRecord sends a GetMemoryRecordCommand on the data client", async () => {
const sent: unknown[] = [];
const response = { memoryRecord: undefined };
const core = coreWithDataSend(async (command) => {
sent.push(command);
return response;
});
const input = {
memoryId: "memory-123",
memoryRecordId: "record-123",
};

const result = await core.memory.getMemoryRecord(input, { region: "us-east-1" });

expect(result).toBe(response);
expect(sent).toHaveLength(1);
expect(sent[0]).toBeInstanceOf(GetMemoryRecordCommand);
expect((sent[0] as GetMemoryRecordCommand).input).toEqual(input);
});

test("listMemoryRecords sends a ListMemoryRecordsCommand on the data client", async () => {
const sent: unknown[] = [];
const response = { memoryRecordSummaries: [], nextToken: "next" };
const core = coreWithDataSend(async (command) => {
sent.push(command);
return response;
});
const input = {
memoryId: "memory-123",
namespace: "/customers/acme",
memoryStrategyId: "strategy-123",
maxResults: 25,
nextToken: "current",
};

const result = await core.memory.listMemoryRecords(input, { region: "us-east-1" });

expect(result).toBe(response);
expect(sent).toHaveLength(1);
expect(sent[0]).toBeInstanceOf(ListMemoryRecordsCommand);
expect((sent[0] as ListMemoryRecordsCommand).input).toEqual(input);
});

test("getRuntime sends the abort signal to the control client", async () => {
const sent: { command: unknown; options: unknown }[] = [];
const core = new CoreClient({
Expand Down
36 changes: 36 additions & 0 deletions src/core/memory.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
import {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT - can this be a TS file since it doesn't contain any JSX

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah yes, I was just following the convention in the other files. I can get a follow up PR that fixes it for everything

GetEventCommand,
GetMemoryRecordCommand,
ListEventsCommand,
ListMemoryRecordsCommand,
type GetEventInput,
type GetEventOutput,
type GetMemoryRecordInput,
type GetMemoryRecordOutput,
type ListEventsInput,
type ListEventsOutput,
type ListMemoryRecordsInput,
type ListMemoryRecordsOutput,
} from "@aws-sdk/client-bedrock-agentcore";
import {
GetMemoryCommand,
ListMemoriesCommand,
Expand Down Expand Up @@ -27,4 +41,26 @@ export class MemoryClient implements CoreMemoryClient {
.control(toClientConfig(options))
.send(new ListMemoriesCommand({ nextToken, maxResults }));
}

async getEvent(input: GetEventInput, options: CoreOptions): Promise<GetEventOutput> {
return this.clients.data(toClientConfig(options)).send(new GetEventCommand(input));
}

async listEvents(input: ListEventsInput, options: CoreOptions): Promise<ListEventsOutput> {
return this.clients.data(toClientConfig(options)).send(new ListEventsCommand(input));
}

async getMemoryRecord(
input: GetMemoryRecordInput,
options: CoreOptions,
): Promise<GetMemoryRecordOutput> {
return this.clients.data(toClientConfig(options)).send(new GetMemoryRecordCommand(input));
}

async listMemoryRecords(
input: ListMemoryRecordsInput,
options: CoreOptions,
): Promise<ListMemoryRecordsOutput> {
return this.clients.data(toClientConfig(options)).send(new ListMemoryRecordsCommand(input));
}
}
44 changes: 44 additions & 0 deletions src/handlers/memory/event/get/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import z from "zod";
import { InputValidationError } from "../../../../errors";
import { createHandler, flag } from "../../../../router";
import type { Core } from "../../../types";
import { coreOptsFromCtx } from "../../../utils";
import { JsonRendererKey } from "../../../../tui";

export const createGetMemoryEventHandler = (core: Core) =>
createHandler({
name: "get",
description: "get an AgentCore Memory Event",
flags: [
flag("memory", "the ID of the Memory", z.string().optional()),
flag("actor-id", "the ID of the actor", z.string().optional()),
flag("event-id", "the event ID", z.string().optional()),
flag("session-id", "the session ID", z.string().optional()),
],
handle: async (ctx, flags) => {
if (!flags.memory) {
throw new InputValidationError("required option '--memory <memory>' not specified");
}
if (!flags["actor-id"]) {
throw new InputValidationError("required option '--actor-id <actor-id>' not specified");
}
if (!flags["session-id"]) {
throw new InputValidationError("required option '--session-id <session-id>' not specified");
}
if (!flags["event-id"]) {
throw new InputValidationError("required option '--event-id <event-id>' not specified");
}

const response = await core.memory.getEvent(
{
memoryId: flags.memory,
actorId: flags["actor-id"],
sessionId: flags["session-id"],
eventId: flags["event-id"],
},
coreOptsFromCtx(ctx),
);

ctx.require(JsonRendererKey).renderJson(response);
},
});
10 changes: 10 additions & 0 deletions src/handlers/memory/event/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Router } from "../../../router";
import type { Core } from "../../types";
import { createGetMemoryEventHandler } from "./get";
import { createListMemoryEventsHandler } from "./list";

export function createMemoryEventHandler(core: Core): Router {
return new Router("event", "inspect AgentCore Memory events")
.handler(createGetMemoryEventHandler(core))
.handler(createListMemoryEventsHandler(core));
}
73 changes: 73 additions & 0 deletions src/handlers/memory/event/list/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import z from "zod";
import { InputValidationError } from "../../../../errors";
import { createHandler, flag } from "../../../../router";
import type { Core } from "../../../types";
import { coreOptsFromCtx } from "../../../utils";
import { JsonRendererKey } from "../../../../tui";
import { parseEventMetadataFilters } from "../../metadataFilters";

export const createListMemoryEventsHandler = (core: Core) =>
createHandler({
name: "list",
description: "list AgentCore Memory events",
flags: [
flag("memory", "the ID of the Memory", z.string().optional()),
flag("actor-id", "the ID of the actor", z.string().optional()),
flag("session-id", "the session ID", z.string().optional()),
flag("include-payloads", "includes event payloads in the response", z.boolean().optional()),
flag("branch", "filter events by branch name", z.string().optional()),
flag(
"include-parent-branches",
"includes parent branches when filtering by branch",
z.boolean().optional(),
),
flag("metadata-filters", "event metadata filters as JSON", z.string().optional()),
flag("max-results", "maximum number of events to return; default 20", z.number().optional()),
flag("next-token", "pagination token returned by a previous request", z.string().optional()),
],

handle: async (ctx, flags) => {
if (!flags.memory) {
throw new InputValidationError("required option '--memory <memory>' not specified");
}
if (!flags["actor-id"]) {
throw new InputValidationError("required option '--actor-id <actor-id>' not specified");
}
if (!flags["session-id"]) {
throw new InputValidationError("required option '--session-id <session-id>' not specified");
}

if (flags["include-parent-branches"] && !flags.branch) {
throw new InputValidationError("'--include-parent-branches' requires '--branch'");
}

const eventMetadata = parseEventMetadataFilters(flags["metadata-filters"]);
const filter =
flags.branch || eventMetadata
? {
branch: flags.branch
? {
name: flags.branch,
includeParentBranches: flags["include-parent-branches"],
}
: undefined,
eventMetadata,
}
: undefined;

const response = await core.memory.listEvents(
{
memoryId: flags.memory,
actorId: flags["actor-id"],
sessionId: flags["session-id"],
includePayloads: flags["include-payloads"],
filter,
maxResults: flags["max-results"],
nextToken: flags["next-token"],
},
coreOptsFromCtx(ctx),
);

ctx.require(JsonRendererKey).renderJson(response);
},
});
6 changes: 5 additions & 1 deletion src/handlers/memory/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@ import { Router } from "../../router";
import { renderTui } from "../../tui";
import type { AppIO } from "../../io";
import type { Core } from "../types";
import { createMemoryEventHandler } from "./event";
import { createGetMemoryHandler } from "./get";
import { createListMemoriesHandler } from "./list";
import { createMemoryRecordHandler } from "./record";

export function createMemoryHandler(core: Core, io: AppIO): Router {
return new Router("memory", "manage AgentCore Memories")
.use(withTuiOnEmptyFlagsAndArgs(core, io))
.default(renderTui(core, io))
.handler(createGetMemoryHandler(core))
.handler(createListMemoriesHandler(core));
.handler(createListMemoriesHandler(core))
.handler(createMemoryEventHandler(core))
.handler(createMemoryRecordHandler(core));
}
Loading
Loading