@letta-ai/letta-react 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +117 -0
  2. package/dist/hooks/useAgentMessages/useAgentMessages.d.ts +27 -0
  3. package/dist/hooks/useAgentMessages/useAgentMessages.js +217 -0
  4. package/dist/hooks/useAgentState/useAgentState.d.ts +13 -0
  5. package/dist/hooks/useAgentState/useAgentState.js +46 -0
  6. package/dist/hooks/useCachedState/useCachedState.d.ts +2 -0
  7. package/dist/hooks/useCachedState/useCachedState.js +19 -0
  8. package/dist/hooks/useGlobalLettaConfig/useGlobalLettaConfig.d.ts +17 -0
  9. package/dist/hooks/useGlobalLettaConfig/useGlobalLettaConfig.js +22 -0
  10. package/dist/hooks/useLettaClient/useLettaClient.d.ts +2 -0
  11. package/dist/hooks/useLettaClient/useLettaClient.js +8 -0
  12. package/dist/index.d.ts +3 -0
  13. package/dist/index.js +3 -0
  14. package/dist/types.d.ts +5 -0
  15. package/dist/types.js +1 -0
  16. package/eslint.config.mjs +3 -0
  17. package/examples/view-and-send-messages/package-lock.json +1020 -0
  18. package/examples/view-and-send-messages/package.json +15 -0
  19. package/examples/view-and-send-messages/src/App.css +24 -0
  20. package/examples/view-and-send-messages/src/App.tsx +92 -0
  21. package/examples/view-and-send-messages/src/client.tsx +34 -0
  22. package/examples/view-and-send-messages/src/index.html +13 -0
  23. package/examples/view-and-send-messages/tsconfig.json +21 -0
  24. package/examples/view-and-send-messages/vite.config.mts +6 -0
  25. package/hooks/useAgentMessages/useAgentMessages.d.ts +27 -0
  26. package/hooks/useAgentMessages/useAgentMessages.js +217 -0
  27. package/hooks/useAgentState/useAgentState.d.ts +13 -0
  28. package/hooks/useAgentState/useAgentState.js +46 -0
  29. package/hooks/useCachedState/useCachedState.d.ts +2 -0
  30. package/hooks/useCachedState/useCachedState.js +19 -0
  31. package/hooks/useGlobalLettaConfig/useGlobalLettaConfig.d.ts +17 -0
  32. package/hooks/useGlobalLettaConfig/useGlobalLettaConfig.js +22 -0
  33. package/hooks/useLettaClient/useLettaClient.d.ts +2 -0
  34. package/hooks/useLettaClient/useLettaClient.js +8 -0
  35. package/index.d.ts +3 -0
  36. package/index.js +3 -0
  37. package/jest.config.ts +10 -0
  38. package/package.json +39 -0
  39. package/project.json +20 -0
  40. package/src/hooks/useAgentMessages/useAgentMessages.ts +329 -0
  41. package/src/hooks/useAgentState/useAgentState.ts +55 -0
  42. package/src/hooks/useCachedState/useCachedState.ts +30 -0
  43. package/src/hooks/useGlobalLettaConfig/useGlobalLettaConfig.tsx +49 -0
  44. package/src/hooks/useLettaClient/useLettaClient.ts +17 -0
  45. package/src/index.ts +3 -0
  46. package/src/types.ts +6 -0
  47. package/tsconfig.app.json +10 -0
  48. package/tsconfig.json +16 -0
  49. package/tsconfig.spec.json +15 -0
  50. package/types.d.ts +5 -0
  51. package/types.js +1 -0
package/project.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "letta-react",
3
+ "$schema": "../../node_modules/nx/schemas/project-schema.json",
4
+ "sourceRoot": "packages/letta-react/src",
5
+ "projectType": "application",
6
+ "tags": [],
7
+ "targets": {
8
+ "typecheck": {
9
+ "executor": "nx:run-commands",
10
+ "options": {
11
+ "command": "tsc --noEmit -p packages/letta-react/tsconfig.app.json"
12
+ }
13
+ },
14
+ "test": {
15
+ "options": {
16
+ "passWithNoTests": true
17
+ }
18
+ }
19
+ }
20
+ }
@@ -0,0 +1,329 @@
1
+ import { LettaClient, type Letta } from '@letta-ai/letta-client';
2
+ import { useCallback, useEffect, useRef, useState } from 'react';
3
+ import type { LettaRequest } from '@letta-ai/letta-client/api/types/LettaRequest';
4
+ import type { MessagesListRequest } from '@letta-ai/letta-client/api/resources/agents';
5
+ import { useGlobalLettaConfig } from '../useGlobalLettaConfig/useGlobalLettaConfig';
6
+ import type { LocalMessagesState } from '../../types';
7
+ import { useCachedState } from '../useCachedState/useCachedState';
8
+ import { useLettaClient } from '../useLettaClient/useLettaClient';
9
+
10
+ interface UseAgentOptions {
11
+ client?: LettaClient.Options;
12
+ agentId: string;
13
+ limit?: number;
14
+ messageOptions?: Omit<MessagesListRequest, 'after' | 'before' | 'limit'>;
15
+ method?: 'stream' | 'basic';
16
+ }
17
+
18
+ function extendContent(
19
+ content: string | Letta.TextContent[],
20
+ nextContent: string | Letta.TextContent[]
21
+ ): string | Letta.TextContent[] {
22
+ if (typeof content === 'string') {
23
+ if (typeof nextContent === 'string') {
24
+ return `${content}${nextContent}`;
25
+ } else {
26
+ return `${content}${nextContent.join('')}`;
27
+ }
28
+ } else {
29
+ if (typeof nextContent === 'string') {
30
+ return [...content, { text: nextContent, type: 'text' }];
31
+ } else {
32
+ return [...content, ...nextContent];
33
+ }
34
+ }
35
+ }
36
+
37
+ interface SendMessagePayload {
38
+ messages: LettaRequest['messages'];
39
+ }
40
+
41
+ export function useAgentMessages(options: UseAgentOptions) {
42
+ const {
43
+ client = {},
44
+ method = 'stream',
45
+ messageOptions = {},
46
+ limit = 20,
47
+ agentId,
48
+ } = options;
49
+ const localClient = useLettaClient(client);
50
+
51
+ const [localMessages, setLocalMessages] = useCachedState<LocalMessagesState>(
52
+ `messages-${agentId}`,
53
+ {
54
+ messages: [],
55
+ }
56
+ );
57
+
58
+ const hasInitialLoaded = useRef<boolean>(false);
59
+
60
+ const [isLoading, setIsLoading] = useState(true);
61
+ const [isFetching, setIsFetching] = useState(false);
62
+ const [loadingError, setLoadingError] = useState<unknown | null>(null);
63
+
64
+ const [isSending, setIsSending] = useState(false);
65
+ const [sendingError, setSendingError] = useState<unknown | null>(null);
66
+
67
+ const sendNonStreamedMessage = useCallback(
68
+ async function sendNonStreamedMessage(
69
+ sendMessagePayload: SendMessagePayload
70
+ ) {
71
+ try {
72
+ setIsSending(true);
73
+ setSendingError(null);
74
+
75
+ const response = await localClient.agents.messages.create(agentId, {
76
+ ...sendMessagePayload,
77
+ ...messageOptions,
78
+ });
79
+
80
+ setLocalMessages((prevState) => ({
81
+ ...prevState,
82
+ messages: [...response.messages, ...prevState.messages],
83
+ }));
84
+ } catch (e) {
85
+ setSendingError(e);
86
+ } finally {
87
+ setIsSending(false);
88
+ }
89
+ },
90
+ [localClient]
91
+ );
92
+
93
+ const sendStreamedMessage = useCallback(
94
+ async function sendStreamedMessage(sendMessagePayload: SendMessagePayload) {
95
+ try {
96
+ setIsSending(true);
97
+ setSendingError(null);
98
+
99
+ const response = await localClient.agents.messages.createStream(
100
+ agentId,
101
+ {
102
+ ...sendMessagePayload,
103
+ ...messageOptions,
104
+ streamTokens: true,
105
+ }
106
+ );
107
+
108
+ for await (const nextMessage of response) {
109
+ if (nextMessage.messageType === 'usage_statistics') {
110
+ return;
111
+ }
112
+ // @ts-expect-error - this is a correct lookup, id does exist
113
+ if (!nextMessage?.id) {
114
+ return;
115
+ }
116
+
117
+ setLocalMessages((prevState) => {
118
+ let hasExistingMessage = false;
119
+ const nextMessages = prevState.messages.map((prevMessage) => {
120
+ if (nextMessage.messageType === 'usage_statistics') {
121
+ return prevMessage;
122
+ }
123
+
124
+ // @ts-expect-error - this is a correct lookup, id does exist
125
+ if (!prevMessage?.id || !nextMessage?.id) {
126
+ return prevMessage;
127
+ }
128
+
129
+ // @ts-expect-error - this is a correct lookup, id does exist
130
+ if (prevMessage.id !== nextMessage.id) {
131
+ return prevMessage;
132
+ }
133
+
134
+ if (
135
+ nextMessage.messageType === 'reasoning_message' &&
136
+ prevMessage.messageType === 'reasoning_message'
137
+ ) {
138
+ hasExistingMessage = true;
139
+
140
+ return {
141
+ ...prevMessage,
142
+ ...nextMessage,
143
+ reasoning: `${prevMessage.reasoning}${nextMessage.reasoning}`,
144
+ };
145
+ }
146
+
147
+ if (
148
+ nextMessage.messageType === 'system_message' &&
149
+ prevMessage.messageType === 'system_message'
150
+ ) {
151
+ hasExistingMessage = true;
152
+
153
+ return {
154
+ ...prevMessage,
155
+ ...nextMessage,
156
+ content: extendContent(
157
+ prevMessage.content,
158
+ nextMessage.content
159
+ ),
160
+ };
161
+ }
162
+
163
+ if (
164
+ nextMessage.messageType === 'user_message' &&
165
+ prevMessage.messageType === 'user_message'
166
+ ) {
167
+ hasExistingMessage = true;
168
+
169
+ return {
170
+ ...prevMessage,
171
+ ...nextMessage,
172
+ content: extendContent(
173
+ prevMessage.content,
174
+ nextMessage.content
175
+ ),
176
+ };
177
+ }
178
+
179
+ if (
180
+ nextMessage.messageType === 'assistant_message' &&
181
+ prevMessage.messageType === 'assistant_message'
182
+ ) {
183
+ hasExistingMessage = true;
184
+
185
+ return {
186
+ ...prevMessage,
187
+ ...nextMessage,
188
+ content: extendContent(
189
+ prevMessage.content,
190
+ nextMessage.content
191
+ ),
192
+ };
193
+ }
194
+
195
+ if (
196
+ nextMessage.messageType === 'tool_call_message' &&
197
+ prevMessage.messageType === 'tool_call_message'
198
+ ) {
199
+ hasExistingMessage = true;
200
+
201
+ return {
202
+ ...prevMessage,
203
+ ...nextMessage,
204
+ };
205
+ }
206
+
207
+ if (
208
+ nextMessage.messageType === 'tool_return_message' &&
209
+ prevMessage.messageType === 'tool_return_message'
210
+ ) {
211
+ hasExistingMessage = true;
212
+ return {
213
+ ...prevMessage,
214
+ ...nextMessage,
215
+ };
216
+ }
217
+
218
+ return prevMessage;
219
+ });
220
+
221
+ return {
222
+ ...prevState,
223
+ messages: [
224
+ ...nextMessages,
225
+ ...(!hasExistingMessage
226
+ ? [
227
+ {
228
+ ...nextMessage,
229
+ },
230
+ ]
231
+ : []),
232
+ ] as LocalMessagesState['messages'],
233
+ };
234
+ });
235
+ }
236
+ } catch (e) {
237
+ setSendingError(e);
238
+ } finally {
239
+ setIsSending(false);
240
+ }
241
+ },
242
+ [localClient]
243
+ );
244
+
245
+ const sendMessage = useCallback(
246
+ function sendMessage(payload: SendMessagePayload) {
247
+ if (isSending) {
248
+ return;
249
+ }
250
+
251
+ if (method === 'stream') {
252
+ return sendStreamedMessage(payload);
253
+ }
254
+
255
+ return sendNonStreamedMessage(payload);
256
+ },
257
+ [method, isSending, sendStreamedMessage, sendNonStreamedMessage]
258
+ );
259
+
260
+ const getMessages = useCallback(
261
+ async function getMessages(before?: string) {
262
+ try {
263
+ if (isFetching) {
264
+ return;
265
+ }
266
+
267
+ setLoadingError(null);
268
+ setIsFetching(true);
269
+
270
+ const messages = await localClient.agents.messages.list(agentId, {
271
+ before,
272
+ limit: limit + 1,
273
+ ...messageOptions,
274
+ });
275
+
276
+ const messagesToAdd = messages.slice(1, messages.length);
277
+ const nextCursor = messages.length > limit ? messages[0] : undefined;
278
+
279
+ setLocalMessages((prevState) => ({
280
+ ...prevState,
281
+ messages: [...messagesToAdd, ...prevState.messages],
282
+ nextCursor: nextCursor?.id,
283
+ }));
284
+ } catch (e) {
285
+ setLoadingError(e);
286
+ } finally {
287
+ setIsFetching(false);
288
+ setIsLoading(false);
289
+ }
290
+ },
291
+ [localClient, isFetching]
292
+ );
293
+
294
+ const fetchOlderMessages = useCallback(async () => {
295
+ const nextCursor = localMessages.nextCursor;
296
+
297
+ if (!nextCursor) {
298
+ return;
299
+ }
300
+
301
+ return getMessages(nextCursor);
302
+ }, [getMessages]);
303
+
304
+ useEffect(() => {
305
+ if (hasInitialLoaded.current) {
306
+ return;
307
+ }
308
+
309
+ hasInitialLoaded.current = true;
310
+
311
+ setIsLoading(true);
312
+
313
+ getMessages();
314
+ }, []);
315
+
316
+ return {
317
+ messages: localMessages.messages,
318
+ isLoading,
319
+ isFetching,
320
+ isLoadingError: !!loadingError,
321
+ loadingError,
322
+ sendMessage,
323
+ isSending,
324
+ sendingError,
325
+ isSendingError: !!sendingError,
326
+ fetchOlderMessages,
327
+ hasOlderMessages: localMessages.nextCursor,
328
+ };
329
+ }
@@ -0,0 +1,55 @@
1
+ import { useLettaClient } from '../useLettaClient/useLettaClient';
2
+ import type { LettaClient } from '@letta-ai/letta-client';
3
+ import { useCachedState } from '../useCachedState/useCachedState';
4
+ import type { AgentState } from '@letta-ai/letta-client/api';
5
+ import { useCallback, useEffect, useRef, useState } from 'react';
6
+
7
+ interface UseAgentStateOptions {
8
+ client?: LettaClient.Options;
9
+ agentId: string;
10
+ }
11
+
12
+ export function useAgentState(options: UseAgentStateOptions) {
13
+ const { client, agentId } = options;
14
+ const localClient = useLettaClient(client);
15
+
16
+ const [localState, setLocalState] = useCachedState<AgentState | undefined>(
17
+ `agent-state-${agentId}`,
18
+ undefined
19
+ );
20
+
21
+ const [isLoading, setIsLoading] = useState(true);
22
+ const [loadingError, setLoadingError] = useState<unknown | null>(null);
23
+ const hasInitialLoaded = useRef<boolean>(false);
24
+
25
+ const getAgentState = useCallback(async () => {
26
+ try {
27
+ const state = await localClient.agents.retrieve(agentId);
28
+
29
+ setLocalState(state);
30
+ } catch (error) {
31
+ setLoadingError(error);
32
+ } finally {
33
+ setIsLoading(false);
34
+ }
35
+ }, [agentId, localClient, setLocalState]);
36
+
37
+ useEffect(() => {
38
+ if (hasInitialLoaded.current) {
39
+ return;
40
+ }
41
+
42
+ hasInitialLoaded.current = true;
43
+
44
+ setIsLoading(true);
45
+
46
+ getAgentState();
47
+ }, []);
48
+
49
+ return {
50
+ isLoading,
51
+ error: loadingError,
52
+ agentState: localState,
53
+ refresh: getAgentState,
54
+ };
55
+ }
@@ -0,0 +1,30 @@
1
+ import { useGlobalLettaConfig } from '../useGlobalLettaConfig/useGlobalLettaConfig';
2
+ import { Dispatch, SetStateAction, useState } from 'react';
3
+
4
+ export function useCachedState<T>(
5
+ key: string,
6
+ defaultValue: T
7
+ ): [T, Dispatch<SetStateAction<T>>] {
8
+ const { isProviderSet, cachedData, updateCache } = useGlobalLettaConfig();
9
+
10
+ const [localState, setLocalState] = useState<T>(defaultValue);
11
+
12
+ if (!isProviderSet) {
13
+ return [localState, setLocalState];
14
+ }
15
+
16
+ return [
17
+ cachedData?.[key] || defaultValue,
18
+ (value: T) => {
19
+ updateCache((prevState) => {
20
+ return {
21
+ ...prevState,
22
+ [key]:
23
+ typeof value === 'function'
24
+ ? value(prevState?.[key] || defaultValue)
25
+ : value,
26
+ };
27
+ });
28
+ },
29
+ ] as [T, Dispatch<SetStateAction<T>>];
30
+ }
@@ -0,0 +1,49 @@
1
+ import React, { Dispatch, SetStateAction, useState } from 'react';
2
+ import { LettaClient } from '@letta-ai/letta-client';
3
+ import { createContext, useMemo, type ReactNode, useContext } from 'react';
4
+
5
+ type CachedData = Record<string, any>;
6
+
7
+ interface LettaProviderState {
8
+ isProviderSet?: boolean;
9
+ state: LettaClient.Options;
10
+ cachedData: CachedData;
11
+ updateCache: Dispatch<SetStateAction<CachedData>>;
12
+ }
13
+
14
+ const LettaContext = createContext<LettaProviderState>({
15
+ state: {},
16
+ cachedData: {},
17
+ updateCache: () => {
18
+ return;
19
+ },
20
+ });
21
+
22
+ interface LettaProviderProps {
23
+ options: LettaClient.Options;
24
+ children: ReactNode;
25
+ }
26
+
27
+ export function LettaProvider(props: LettaProviderProps) {
28
+ const [cachedData, updateCache] = useState({});
29
+
30
+ const state = useMemo(
31
+ () => ({
32
+ state: props.options,
33
+ cachedData,
34
+ updateCache,
35
+ isProviderSet: true,
36
+ }),
37
+ [props.options, updateCache, cachedData]
38
+ );
39
+
40
+ return (
41
+ <LettaContext.Provider value={state}>
42
+ {props.children}
43
+ </LettaContext.Provider>
44
+ );
45
+ }
46
+
47
+ export function useGlobalLettaConfig() {
48
+ return useContext(LettaContext);
49
+ }
@@ -0,0 +1,17 @@
1
+ import { useState } from 'react';
2
+ import { LettaClient } from '@letta-ai/letta-client';
3
+ import { useGlobalLettaConfig } from '../useGlobalLettaConfig/useGlobalLettaConfig';
4
+
5
+ export function useLettaClient(localOptions: LettaClient.Options = {}) {
6
+ const globalClient = useGlobalLettaConfig();
7
+
8
+ const [localClient] = useState(
9
+ () =>
10
+ new LettaClient({
11
+ ...globalClient.state,
12
+ ...localOptions,
13
+ })
14
+ );
15
+
16
+ return localClient;
17
+ }
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export { LettaProvider } from './hooks/useGlobalLettaConfig/useGlobalLettaConfig';
2
+ export { useAgentMessages } from './hooks/useAgentMessages/useAgentMessages';
3
+ export { useAgentState } from './hooks/useAgentState/useAgentState';
package/src/types.ts ADDED
@@ -0,0 +1,6 @@
1
+ import type { Letta } from '@letta-ai/letta-client';
2
+
3
+ export interface LocalMessagesState {
4
+ messages: Letta.LettaMessageUnion[];
5
+ nextCursor?: string;
6
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "../../dist/out-tsc",
5
+ "module": "commonjs",
6
+ "types": ["node"]
7
+ },
8
+ "include": ["src/**/*.ts"],
9
+ "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"]
10
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "include": ["src"],
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "rootDir": "src",
6
+ "baseUrl": "src",
7
+ "jsx": "react",
8
+ "extendedDiagnostics": true,
9
+ "strict": true,
10
+ "target": "ES6",
11
+ "moduleResolution": "node",
12
+ "skipLibCheck": true,
13
+ "declaration": true,
14
+ "esModuleInterop": true
15
+ }
16
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "../../dist/out-tsc",
5
+ "module": "commonjs",
6
+ "moduleResolution": "node10",
7
+ "types": ["jest", "node"]
8
+ },
9
+ "include": [
10
+ "jest.config.ts",
11
+ "src/**/*.test.ts",
12
+ "src/**/*.spec.ts",
13
+ "src/**/*.d.ts"
14
+ ]
15
+ }
package/types.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ import type { Letta } from '@letta-ai/letta-client';
2
+ export interface LocalMessagesState {
3
+ messages: Letta.LettaMessageUnion[];
4
+ nextCursor?: string;
5
+ }
package/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};