Skip to content

Commit fb537db

Browse files
committed
feat: add TUI wizard streaming steps and docs for memory record streaming
1 parent 98d1212 commit fb537db

File tree

6 files changed

+263
-23
lines changed

6 files changed

+263
-23
lines changed

docs/memory.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,13 @@ pushed to a delivery target in your account, enabling event-driven architectures
229229

230230
### Enabling Streaming
231231

232+
Via the interactive wizard:
233+
234+
```bash
235+
agentcore add memory
236+
# Select "Yes" when prompted for streaming, then provide the data stream ARN and content level
237+
```
238+
232239
Via CLI flags:
233240

234241
```bash

src/cli/tui/hooks/useCreateMemory.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ interface CreateMemoryConfig {
88
name: string;
99
eventExpiryDuration: number;
1010
strategies: { type: string }[];
11+
streaming?: { dataStreamArn: string; contentLevel: string };
1112
}
1213

1314
interface CreateStatus<T> {
@@ -27,6 +28,8 @@ export function useCreateMemory() {
2728
name: config.name,
2829
expiry: config.eventExpiryDuration,
2930
strategies: strategiesStr || undefined,
31+
dataStreamArn: config.streaming?.dataStreamArn,
32+
contentLevel: config.streaming?.contentLevel,
3033
});
3134
if (!addResult.success) {
3235
throw new Error(addResult.error ?? 'Failed to create memory');

src/cli/tui/screens/memory/AddMemoryScreen.tsx

Lines changed: 73 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { MemoryStrategyType } from '../../../../schema';
1+
import type { MemoryStrategyType, StreamContentLevel } from '../../../../schema';
22
import { AgentNameSchema } from '../../../../schema';
33
import {
44
ConfirmReview,
@@ -14,7 +14,7 @@ import { HELP_TEXT } from '../../constants';
1414
import { useListNavigation, useMultiSelectNavigation } from '../../hooks';
1515
import { generateUniqueName } from '../../utils';
1616
import type { AddMemoryConfig } from './types';
17-
import { EVENT_EXPIRY_OPTIONS, MEMORY_STEP_LABELS, MEMORY_STRATEGY_OPTIONS } from './types';
17+
import { CONTENT_LEVEL_OPTIONS, EVENT_EXPIRY_OPTIONS, MEMORY_STEP_LABELS, MEMORY_STRATEGY_OPTIONS } from './types';
1818
import { useAddMemoryWizard } from './useAddMemoryWizard';
1919
import React, { useMemo } from 'react';
2020

@@ -24,6 +24,11 @@ interface AddMemoryScreenProps {
2424
existingMemoryNames: string[];
2525
}
2626

27+
const STREAMING_OPTIONS: SelectableItem[] = [
28+
{ id: 'yes', title: 'Yes', description: 'Stream memory record events to a delivery target (e.g. Kinesis)' },
29+
{ id: 'no', title: 'No', description: 'No streaming' },
30+
];
31+
2732
export function AddMemoryScreen({ onComplete, onExit, existingMemoryNames }: AddMemoryScreenProps) {
2833
const wizard = useAddMemoryWizard();
2934

@@ -37,9 +42,17 @@ export function AddMemoryScreen({ onComplete, onExit, existingMemoryNames }: Add
3742
[]
3843
);
3944

45+
const contentLevelItems: SelectableItem[] = useMemo(
46+
() => CONTENT_LEVEL_OPTIONS.map(opt => ({ id: opt.id, title: opt.title, description: opt.description })),
47+
[]
48+
);
49+
4050
const isNameStep = wizard.step === 'name';
4151
const isExpiryStep = wizard.step === 'expiry';
4252
const isStrategiesStep = wizard.step === 'strategies';
53+
const isStreamingStep = wizard.step === 'streaming';
54+
const isStreamArnStep = wizard.step === 'streamArn';
55+
const isContentLevelStep = wizard.step === 'contentLevel';
4356
const isConfirmStep = wizard.step === 'confirm';
4457

4558
const expiryNav = useListNavigation({
@@ -58,6 +71,20 @@ export function AddMemoryScreen({ onComplete, onExit, existingMemoryNames }: Add
5871
requireSelection: false,
5972
});
6073

74+
const streamingNav = useListNavigation({
75+
items: STREAMING_OPTIONS,
76+
onSelect: item => wizard.setStreamingEnabled(item.id === 'yes'),
77+
onExit: () => wizard.goBack(),
78+
isActive: isStreamingStep,
79+
});
80+
81+
const contentLevelNav = useListNavigation({
82+
items: contentLevelItems,
83+
onSelect: item => wizard.setContentLevel(item.id as StreamContentLevel),
84+
onExit: () => wizard.goBack(),
85+
isActive: isContentLevelStep,
86+
});
87+
6188
useListNavigation({
6289
items: [{ id: 'confirm', title: 'Confirm' }],
6390
onSelect: () => onComplete(wizard.config),
@@ -67,14 +94,29 @@ export function AddMemoryScreen({ onComplete, onExit, existingMemoryNames }: Add
6794

6895
const helpText = isStrategiesStep
6996
? 'Space toggle · Enter confirm · Esc back'
70-
: isExpiryStep
97+
: isExpiryStep || isStreamingStep || isContentLevelStep
7198
? HELP_TEXT.NAVIGATE_SELECT
7299
: isConfirmStep
73100
? HELP_TEXT.CONFIRM_CANCEL
74101
: HELP_TEXT.TEXT_INPUT;
75102

76103
const headerContent = <StepIndicator steps={wizard.steps} currentStep={wizard.step} labels={MEMORY_STEP_LABELS} />;
77104

105+
const confirmFields = useMemo(
106+
() => [
107+
{ label: 'Name', value: wizard.config.name },
108+
{ label: 'Event Expiry', value: `${wizard.config.eventExpiryDuration} days` },
109+
{ label: 'Strategies', value: wizard.config.strategies.map(s => s.type).join(', ') || 'None' },
110+
...(wizard.config.streaming
111+
? [
112+
{ label: 'Stream ARN', value: wizard.config.streaming.dataStreamArn },
113+
{ label: 'Content Level', value: wizard.config.streaming.contentLevel },
114+
]
115+
: [{ label: 'Streaming', value: 'Disabled' }]),
116+
],
117+
[wizard.config]
118+
);
119+
78120
return (
79121
<Screen title="Add Memory" onExit={onExit} helpText={helpText} headerContent={headerContent}>
80122
<Panel>
@@ -109,15 +151,36 @@ export function AddMemoryScreen({ onComplete, onExit, existingMemoryNames }: Add
109151
/>
110152
)}
111153

112-
{isConfirmStep && (
113-
<ConfirmReview
114-
fields={[
115-
{ label: 'Name', value: wizard.config.name },
116-
{ label: 'Event Expiry', value: `${wizard.config.eventExpiryDuration} days` },
117-
{ label: 'Strategies', value: wizard.config.strategies.map(s => s.type).join(', ') || 'None' },
118-
]}
154+
{isStreamingStep && (
155+
<WizardSelect
156+
title="Enable memory record streaming?"
157+
description="Stream memory record lifecycle events to a delivery target"
158+
items={STREAMING_OPTIONS}
159+
selectedIndex={streamingNav.selectedIndex}
160+
/>
161+
)}
162+
163+
{isStreamArnStep && (
164+
<TextInput
165+
key="streamArn"
166+
prompt="Delivery target ARN (e.g. Kinesis stream)"
167+
initialValue=""
168+
onSubmit={wizard.setStreamArn}
169+
onCancel={() => wizard.goBack()}
170+
customValidation={value => value.startsWith('arn:') || 'Must be a valid ARN (starts with arn:)'}
119171
/>
120172
)}
173+
174+
{isContentLevelStep && (
175+
<WizardSelect
176+
title="Stream content level"
177+
description="What data to include in stream events"
178+
items={contentLevelItems}
179+
selectedIndex={contentLevelNav.selectedIndex}
180+
/>
181+
)}
182+
183+
{isConfirmStep && <ConfirmReview fields={confirmFields} />}
121184
</Panel>
122185
</Screen>
123186
);

src/cli/tui/screens/memory/types.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,35 @@
1-
import type { MemoryStrategyType } from '../../../../schema';
1+
import type { MemoryStrategyType, StreamContentLevel } from '../../../../schema';
22
import { MemoryStrategyTypeSchema } from '../../../../schema';
33

44
// ─────────────────────────────────────────────────────────────────────────────
55
// Memory Flow Types
66
// ─────────────────────────────────────────────────────────────────────────────
77

8-
export type AddMemoryStep = 'name' | 'expiry' | 'strategies' | 'confirm';
8+
export type AddMemoryStep = 'name' | 'expiry' | 'strategies' | 'streaming' | 'streamArn' | 'contentLevel' | 'confirm';
99

1010
export interface AddMemoryStrategyConfig {
1111
type: MemoryStrategyType;
1212
}
1313

14+
export interface AddMemoryStreamingConfig {
15+
dataStreamArn: string;
16+
contentLevel: StreamContentLevel;
17+
}
18+
1419
export interface AddMemoryConfig {
1520
name: string;
1621
eventExpiryDuration: number;
1722
strategies: AddMemoryStrategyConfig[];
23+
streaming?: AddMemoryStreamingConfig;
1824
}
1925

2026
export const MEMORY_STEP_LABELS: Record<AddMemoryStep, string> = {
2127
name: 'Name',
2228
expiry: 'Expiry',
2329
strategies: 'Strategies',
30+
streaming: 'Streaming',
31+
streamArn: 'Stream ARN',
32+
contentLevel: 'Content Level',
2433
confirm: 'Confirm',
2534
};
2635

@@ -49,6 +58,11 @@ export const EVENT_EXPIRY_OPTIONS = [
4958
{ id: 365, title: '365 days', description: 'Maximum retention' },
5059
] as const;
5160

61+
export const CONTENT_LEVEL_OPTIONS = [
62+
{ id: 'FULL_CONTENT', title: 'Full content', description: 'Include memory record text in stream events' },
63+
{ id: 'METADATA_ONLY', title: 'Metadata only', description: 'Only include metadata (IDs, timestamps, namespaces)' },
64+
] as const;
65+
5266
// ─────────────────────────────────────────────────────────────────────────────
5367
// Defaults
5468
// ─────────────────────────────────────────────────────────────────────────────

src/cli/tui/screens/memory/useAddMemoryWizard.ts

Lines changed: 68 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
import type { MemoryStrategyType } from '../../../../schema';
1+
import type { MemoryStrategyType, StreamContentLevel } from '../../../../schema';
22
import type { AddMemoryConfig, AddMemoryStep, AddMemoryStrategyConfig } from './types';
33
import { DEFAULT_EVENT_EXPIRY } from './types';
4-
import { useCallback, useState } from 'react';
4+
import { useCallback, useMemo, useState } from 'react';
55

6-
const ALL_STEPS: AddMemoryStep[] = ['name', 'expiry', 'strategies', 'confirm'];
6+
const BASE_STEPS = ['name', 'expiry', 'strategies', 'streaming'] as const;
7+
const STREAMING_STEPS = ['streamArn', 'contentLevel'] as const;
8+
const FIRST_STREAMING_STEP = STREAMING_STEPS[0];
9+
const CONFIRM_STEP = 'confirm' as const;
710

811
function getDefaultConfig(): AddMemoryConfig {
912
return {
@@ -16,18 +19,27 @@ function getDefaultConfig(): AddMemoryConfig {
1619
export function useAddMemoryWizard() {
1720
const [config, setConfig] = useState<AddMemoryConfig>(getDefaultConfig);
1821
const [step, setStep] = useState<AddMemoryStep>('name');
22+
const [enableStreaming, setEnableStreaming] = useState(false);
1923

20-
const currentIndex = ALL_STEPS.indexOf(step);
24+
const allSteps = useMemo(
25+
() => (enableStreaming ? [...BASE_STEPS, ...STREAMING_STEPS, CONFIRM_STEP] : [...BASE_STEPS, CONFIRM_STEP]),
26+
[enableStreaming]
27+
);
28+
const currentIndex = allSteps.indexOf(step);
2129

2230
const goBack = useCallback(() => {
23-
const prevStep = ALL_STEPS[currentIndex - 1];
31+
const idx = allSteps.indexOf(step);
32+
const prevStep = allSteps[idx - 1];
2433
if (prevStep) setStep(prevStep);
25-
}, [currentIndex]);
34+
}, [allSteps, step]);
2635

27-
const nextStep = useCallback((currentStep: AddMemoryStep): AddMemoryStep | undefined => {
28-
const idx = ALL_STEPS.indexOf(currentStep);
29-
return ALL_STEPS[idx + 1];
30-
}, []);
36+
const nextStep = useCallback(
37+
(currentStep: AddMemoryStep): AddMemoryStep | undefined => {
38+
const idx = allSteps.indexOf(currentStep);
39+
return allSteps[idx + 1];
40+
},
41+
[allSteps]
42+
);
3143

3244
const setName = useCallback(
3345
(name: string) => {
@@ -57,20 +69,65 @@ export function useAddMemoryWizard() {
5769
[nextStep]
5870
);
5971

72+
const setStreamingEnabled = useCallback((enabled: boolean) => {
73+
setEnableStreaming(enabled);
74+
if (enabled) {
75+
// Can't use nextStep() here — allSteps hasn't updated yet since
76+
// setEnableStreaming is queued. Hardcode the known next step.
77+
setStep(FIRST_STREAMING_STEP);
78+
} else {
79+
setConfig(c => ({ ...c, streaming: undefined }));
80+
setStep(CONFIRM_STEP);
81+
}
82+
}, []);
83+
84+
const setStreamArn = useCallback(
85+
(dataStreamArn: string) => {
86+
setConfig(c => ({
87+
...c,
88+
streaming: { dataStreamArn, contentLevel: c.streaming?.contentLevel ?? 'FULL_CONTENT' },
89+
}));
90+
const next = nextStep(FIRST_STREAMING_STEP);
91+
if (next) setStep(next);
92+
},
93+
[nextStep]
94+
);
95+
96+
const setContentLevel = useCallback(
97+
(contentLevel: StreamContentLevel) => {
98+
setConfig(c => {
99+
if (!c.streaming?.dataStreamArn) {
100+
throw new Error('Cannot set content level without a data stream ARN');
101+
}
102+
return {
103+
...c,
104+
streaming: { dataStreamArn: c.streaming.dataStreamArn, contentLevel },
105+
};
106+
});
107+
const next = nextStep('contentLevel');
108+
if (next) setStep(next);
109+
},
110+
[nextStep]
111+
);
112+
60113
const reset = useCallback(() => {
61114
setConfig(getDefaultConfig());
62115
setStep('name');
116+
setEnableStreaming(false);
63117
}, []);
64118

65119
return {
66120
config,
67121
step,
68-
steps: ALL_STEPS,
122+
steps: allSteps,
69123
currentIndex,
70124
goBack,
71125
setName,
72126
setExpiry,
73127
setStrategyTypes,
128+
setStreamingEnabled,
129+
setStreamArn,
130+
setContentLevel,
74131
reset,
75132
};
76133
}

0 commit comments

Comments
 (0)