lemma-sdk 0.2.28 → 0.2.31

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 (122) hide show
  1. package/README.md +241 -201
  2. package/bin/lemma-sdk.js +108 -0
  3. package/dist/browser/lemma-client.js +125 -4
  4. package/dist/client.d.ts +2 -0
  5. package/dist/client.js +3 -0
  6. package/dist/config.d.ts +2 -2
  7. package/dist/config.js +2 -2
  8. package/dist/datastore-query.d.ts +54 -0
  9. package/dist/datastore-query.js +157 -0
  10. package/dist/index.d.ts +7 -0
  11. package/dist/index.js +3 -0
  12. package/dist/namespaces/datastore.d.ts +9 -0
  13. package/dist/namespaces/datastore.js +13 -0
  14. package/dist/namespaces/records.d.ts +1 -1
  15. package/dist/openapi_client/index.d.ts +4 -0
  16. package/dist/openapi_client/index.js +1 -0
  17. package/dist/openapi_client/models/ConvertedArtifactResponse.d.ts +6 -0
  18. package/dist/openapi_client/models/ConvertedFileResponse.d.ts +10 -0
  19. package/dist/openapi_client/models/ConvertedFileResponse.js +1 -0
  20. package/dist/openapi_client/models/CreateFolderRequest.d.ts +3 -1
  21. package/dist/openapi_client/models/FlowRunEntity.d.ts +1 -0
  22. package/dist/openapi_client/models/ScheduledFlowStart.d.ts +3 -6
  23. package/dist/openapi_client/models/ScheduledFlowStartType.d.ts +4 -0
  24. package/dist/openapi_client/models/ScheduledFlowStartType.js +9 -0
  25. package/dist/openapi_client/models/WorkflowInstallRequest.d.ts +5 -0
  26. package/dist/openapi_client/models/WorkflowTimeInstallConfig.d.ts +19 -0
  27. package/dist/openapi_client/models/WorkflowTimeInstallConfig.js +1 -0
  28. package/dist/openapi_client/services/FilesService.d.ts +27 -1
  29. package/dist/openapi_client/services/FilesService.js +69 -1
  30. package/dist/openapi_client/services/WorkflowsService.d.ts +1 -1
  31. package/dist/openapi_client/services/WorkflowsService.js +1 -1
  32. package/dist/react/assistant-output.d.ts +6 -0
  33. package/dist/react/assistant-output.js +90 -0
  34. package/dist/react/index.d.ts +62 -8
  35. package/dist/react/index.js +31 -4
  36. package/dist/react/useAgentInputSchema.d.ts +19 -0
  37. package/dist/react/useAgentInputSchema.js +73 -0
  38. package/dist/react/useAgentRun.d.ts +17 -0
  39. package/dist/react/useAgentRun.js +56 -0
  40. package/dist/react/useAgentRuns.d.ts +33 -0
  41. package/dist/react/useAgentRuns.js +149 -0
  42. package/dist/react/useAssistantRun.d.ts +9 -0
  43. package/dist/react/useAssistantRun.js +28 -17
  44. package/dist/react/useAssistantSession.d.ts +5 -0
  45. package/dist/react/useAssistantSession.js +135 -86
  46. package/dist/react/useBulkRecords.d.ts +20 -0
  47. package/dist/react/useBulkRecords.js +65 -0
  48. package/dist/react/useConversation.d.ts +18 -0
  49. package/dist/react/useConversation.js +75 -0
  50. package/dist/react/useConversationMessages.d.ts +59 -0
  51. package/dist/react/useConversationMessages.js +167 -0
  52. package/dist/react/useConversations.d.ts +52 -0
  53. package/dist/react/useConversations.js +228 -0
  54. package/dist/react/useCreateRecord.d.ts +18 -0
  55. package/dist/react/useCreateRecord.js +51 -0
  56. package/dist/react/useCurrentUser.d.ts +14 -0
  57. package/dist/react/useCurrentUser.js +68 -0
  58. package/dist/react/useDeleteRecord.d.ts +21 -0
  59. package/dist/react/useDeleteRecord.js +52 -0
  60. package/dist/react/useFlowRunHistory.js +1 -5
  61. package/dist/react/useFlowSession.js +41 -33
  62. package/dist/react/useForeignKeyOptions.d.ts +31 -0
  63. package/dist/react/useForeignKeyOptions.js +161 -0
  64. package/dist/react/useFunctionRun.d.ts +19 -0
  65. package/dist/react/useFunctionRun.js +30 -0
  66. package/dist/react/useFunctionRuns.d.ts +33 -0
  67. package/dist/react/useFunctionRuns.js +149 -0
  68. package/dist/react/useFunctionSession.js +37 -29
  69. package/dist/react/useJoinedRecords.d.ts +18 -0
  70. package/dist/react/useJoinedRecords.js +80 -0
  71. package/dist/react/useMembers.d.ts +26 -0
  72. package/dist/react/useMembers.js +98 -0
  73. package/dist/react/useOrganizationMembers.d.ts +26 -0
  74. package/dist/react/useOrganizationMembers.js +113 -0
  75. package/dist/react/usePodAccess.d.ts +22 -0
  76. package/dist/react/usePodAccess.js +128 -0
  77. package/dist/react/useRecord.d.ts +18 -0
  78. package/dist/react/useRecord.js +75 -0
  79. package/dist/react/useRecordForm.d.ts +42 -0
  80. package/dist/react/useRecordForm.js +221 -0
  81. package/dist/react/useRecordSchema.d.ts +20 -0
  82. package/dist/react/useRecordSchema.js +24 -0
  83. package/dist/react/useRecords.d.ts +20 -0
  84. package/dist/react/useRecords.js +146 -0
  85. package/dist/react/useRelatedRecords.d.ts +43 -0
  86. package/dist/react/useRelatedRecords.js +239 -0
  87. package/dist/react/useReverseRelatedRecords.d.ts +47 -0
  88. package/dist/react/useReverseRelatedRecords.js +235 -0
  89. package/dist/react/useSchemaForm.d.ts +24 -0
  90. package/dist/react/useSchemaForm.js +104 -0
  91. package/dist/react/useTable.d.ts +16 -0
  92. package/dist/react/useTable.js +70 -0
  93. package/dist/react/useTables.d.ts +26 -0
  94. package/dist/react/useTables.js +113 -0
  95. package/dist/react/useTaskSession.js +11 -22
  96. package/dist/react/useUpdateRecord.d.ts +21 -0
  97. package/dist/react/useUpdateRecord.js +55 -0
  98. package/dist/react/useWorkflowResume.d.ts +18 -0
  99. package/dist/react/useWorkflowResume.js +45 -0
  100. package/dist/react/useWorkflowRun.d.ts +21 -0
  101. package/dist/react/useWorkflowRun.js +49 -0
  102. package/dist/react/useWorkflowRuns.d.ts +33 -0
  103. package/dist/react/useWorkflowRuns.js +149 -0
  104. package/dist/react/useWorkflowStart.d.ts +33 -0
  105. package/dist/react/useWorkflowStart.js +148 -0
  106. package/dist/react/utils.d.ts +5 -0
  107. package/dist/react/utils.js +25 -0
  108. package/dist/record-form.d.ts +30 -0
  109. package/dist/record-form.js +199 -0
  110. package/dist/schema-form.d.ts +41 -0
  111. package/dist/schema-form.js +200 -0
  112. package/dist/types.d.ts +6 -1
  113. package/package.json +11 -8
  114. package/dist/react/components/AssistantChrome.d.ts +0 -86
  115. package/dist/react/components/AssistantChrome.js +0 -48
  116. package/dist/react/components/AssistantEmbedded.d.ts +0 -10
  117. package/dist/react/components/AssistantEmbedded.js +0 -15
  118. package/dist/react/components/AssistantExperience.d.ts +0 -96
  119. package/dist/react/components/AssistantExperience.js +0 -1294
  120. package/dist/react/components/assistant-types.d.ts +0 -80
  121. package/dist/react/styles.css +0 -2407
  122. /package/dist/{react/components/assistant-types.js → openapi_client/models/ConvertedArtifactResponse.js} +0 -0
@@ -0,0 +1,149 @@
1
+ import { useCallback, useEffect, useMemo, useState } from "react";
2
+ import { normalizeError, resolvePodClient } from "./utils.js";
3
+ export function useAgentRuns({ client, podId, agentName, enabled = true, autoLoad = true, limit = 100, pageToken, initialTaskId = null, }) {
4
+ const [runs, setRuns] = useState([]);
5
+ const [total, setTotal] = useState(0);
6
+ const [nextPageToken, setNextPageToken] = useState(null);
7
+ const [selectedTaskId, setSelectedTaskId] = useState(initialTaskId);
8
+ const [isLoading, setIsLoading] = useState(false);
9
+ const [isLoadingMore, setIsLoadingMore] = useState(false);
10
+ const [error, setError] = useState(null);
11
+ const refresh = useCallback(async (overrides = {}, signal) => {
12
+ if (!enabled) {
13
+ setRuns([]);
14
+ setTotal(0);
15
+ setNextPageToken(null);
16
+ setError(null);
17
+ setIsLoading(false);
18
+ return [];
19
+ }
20
+ setIsLoading(true);
21
+ setError(null);
22
+ try {
23
+ const scopedClient = resolvePodClient(client, podId);
24
+ const response = await scopedClient.tasks.list({
25
+ agent_name: agentName,
26
+ limit: overrides.limit ?? limit,
27
+ page_token: overrides.pageToken ?? pageToken,
28
+ });
29
+ if (signal?.aborted)
30
+ return [];
31
+ const nextRuns = response.items ?? [];
32
+ setRuns(nextRuns);
33
+ setTotal(response.total ?? nextRuns.length);
34
+ setNextPageToken(response.next_page_token ?? null);
35
+ setSelectedTaskId((current) => (current && nextRuns.some((run) => run.id === current) ? current : initialTaskId));
36
+ return nextRuns;
37
+ }
38
+ catch (refreshError) {
39
+ if (signal?.aborted)
40
+ return [];
41
+ const normalized = normalizeError(refreshError, "Failed to load agent runs.");
42
+ setError(normalized);
43
+ return [];
44
+ }
45
+ finally {
46
+ if (!signal?.aborted)
47
+ setIsLoading(false);
48
+ }
49
+ }, [agentName, client, enabled, initialTaskId, limit, pageToken, podId]);
50
+ const loadMore = useCallback(async (overrides = {}) => {
51
+ if (!enabled || !nextPageToken || isLoading || isLoadingMore) {
52
+ return [];
53
+ }
54
+ setIsLoadingMore(true);
55
+ setError(null);
56
+ try {
57
+ const scopedClient = resolvePodClient(client, podId);
58
+ const response = await scopedClient.tasks.list({
59
+ agent_name: agentName,
60
+ limit: overrides.limit ?? limit,
61
+ page_token: nextPageToken,
62
+ });
63
+ const moreRuns = response.items ?? [];
64
+ setRuns((previous) => [...previous, ...moreRuns]);
65
+ setTotal(response.total ?? runs.length + moreRuns.length);
66
+ setNextPageToken(response.next_page_token ?? null);
67
+ return moreRuns;
68
+ }
69
+ catch (loadError) {
70
+ const normalized = normalizeError(loadError, "Failed to load more agent runs.");
71
+ setError(normalized);
72
+ return [];
73
+ }
74
+ finally {
75
+ setIsLoadingMore(false);
76
+ }
77
+ }, [agentName, client, enabled, isLoading, isLoadingMore, limit, nextPageToken, podId]);
78
+ useEffect(() => {
79
+ setSelectedTaskId(initialTaskId);
80
+ }, [initialTaskId]);
81
+ useEffect(() => {
82
+ if (!enabled) {
83
+ setRuns([]);
84
+ setTotal(0);
85
+ setNextPageToken(null);
86
+ setError(null);
87
+ setIsLoading(false);
88
+ setIsLoadingMore(false);
89
+ return;
90
+ }
91
+ if (!autoLoad)
92
+ return;
93
+ const controller = new AbortController();
94
+ let cancelled = false;
95
+ (async () => {
96
+ try {
97
+ await refresh({}, controller.signal);
98
+ }
99
+ catch {
100
+ if (!cancelled) {
101
+ setError(normalizeError(new Error("Failed to load agent runs."), "Failed to load agent runs."));
102
+ }
103
+ }
104
+ })();
105
+ return () => {
106
+ cancelled = true;
107
+ controller.abort();
108
+ };
109
+ }, [autoLoad, enabled, refresh]);
110
+ const selectRun = useCallback((taskId) => {
111
+ setSelectedTaskId(taskId);
112
+ }, []);
113
+ const clearSelection = useCallback(() => {
114
+ setSelectedTaskId(null);
115
+ }, []);
116
+ return useMemo(() => {
117
+ const effectiveSelectedTaskId = selectedTaskId ?? runs[0]?.id ?? null;
118
+ const selectedRun = effectiveSelectedTaskId
119
+ ? runs.find((run) => run.id === effectiveSelectedTaskId) ?? null
120
+ : null;
121
+ return {
122
+ runs,
123
+ total,
124
+ nextPageToken,
125
+ selectedTaskId,
126
+ effectiveSelectedTaskId,
127
+ selectedRun,
128
+ isLoading,
129
+ isLoadingMore,
130
+ error,
131
+ selectRun,
132
+ clearSelection,
133
+ refresh,
134
+ loadMore,
135
+ };
136
+ }, [
137
+ clearSelection,
138
+ error,
139
+ isLoading,
140
+ isLoadingMore,
141
+ loadMore,
142
+ nextPageToken,
143
+ refresh,
144
+ runs,
145
+ selectRun,
146
+ selectedTaskId,
147
+ total,
148
+ ]);
149
+ }
@@ -1,5 +1,6 @@
1
1
  import type { LemmaClient } from "../client.js";
2
2
  import type { SseRawEvent } from "../streams.js";
3
+ import type { ConversationMessage } from "../types.js";
3
4
  export interface UseAssistantRunOptions {
4
5
  client: LemmaClient;
5
6
  podId?: string;
@@ -10,6 +11,14 @@ export interface UseAssistantRunOptions {
10
11
  export interface UseAssistantRunResult {
11
12
  isStreaming: boolean;
12
13
  error: Error | null;
14
+ status?: string;
15
+ messages: ConversationMessage[];
16
+ output: ConversationMessage["content"] | null;
17
+ outputText: string;
18
+ finalOutput: ConversationMessage["content"] | null;
19
+ finalOutputText: string;
20
+ latestAssistantMessage: ConversationMessage | null;
21
+ refresh: () => Promise<ConversationMessage[]>;
13
22
  sendMessage: (content: string) => Promise<void>;
14
23
  resume: () => Promise<void>;
15
24
  stop: () => Promise<void>;
@@ -1,4 +1,5 @@
1
- import { useAssistantSession } from "./useAssistantSession.js";
1
+ import { useCallback, useMemo } from "react";
2
+ import { useConversationMessages } from "./useConversationMessages.js";
2
3
  function requireConversationId(conversationId) {
3
4
  if (!conversationId) {
4
5
  throw new Error("conversationId is required.");
@@ -6,31 +7,41 @@ function requireConversationId(conversationId) {
6
7
  return conversationId;
7
8
  }
8
9
  export function useAssistantRun({ client, podId, conversationId, onEvent, onError, }) {
9
- const session = useAssistantSession({
10
+ const messages = useConversationMessages({
10
11
  client,
11
12
  podId,
12
13
  conversationId,
14
+ autoLoad: true,
15
+ autoResume: false,
13
16
  onEvent,
14
17
  onError,
15
18
  });
16
- const sendMessage = async (content) => {
17
- await session.sendMessage(content, {
18
- conversationId: requireConversationId(conversationId ?? session.conversationId),
19
+ const sendMessage = useCallback(async (content) => {
20
+ await messages.sendMessage(content, {
21
+ conversationId: requireConversationId(conversationId ?? messages.conversationId),
19
22
  createIfMissing: false,
20
23
  });
21
- };
22
- const resume = async () => {
23
- await session.resume(requireConversationId(conversationId ?? session.conversationId));
24
- };
25
- const stop = async () => {
26
- await session.stop(requireConversationId(conversationId ?? session.conversationId));
27
- };
28
- return {
29
- isStreaming: session.isStreaming,
30
- error: session.error,
24
+ }, [conversationId, messages]);
25
+ const resume = useCallback(async () => {
26
+ await messages.resume(requireConversationId(conversationId ?? messages.conversationId));
27
+ }, [conversationId, messages]);
28
+ const stop = useCallback(async () => {
29
+ await messages.stop(requireConversationId(conversationId ?? messages.conversationId));
30
+ }, [conversationId, messages]);
31
+ return useMemo(() => ({
32
+ isStreaming: messages.isStreaming,
33
+ error: messages.error,
34
+ status: messages.status,
35
+ messages: messages.messages,
36
+ output: messages.output,
37
+ outputText: messages.outputText,
38
+ finalOutput: messages.finalOutput,
39
+ finalOutputText: messages.finalOutputText,
40
+ latestAssistantMessage: messages.latestAssistantMessage,
41
+ refresh: messages.refresh,
31
42
  sendMessage,
32
43
  resume,
33
44
  stop,
34
- cancel: session.cancel,
35
- };
45
+ cancel: messages.cancel,
46
+ }), [messages, sendMessage, resume, stop]);
36
47
  }
@@ -59,6 +59,11 @@ export interface UseAssistantSessionResult {
59
59
  conversation: Conversation | null;
60
60
  status?: string;
61
61
  messages: ConversationMessage[];
62
+ latestAssistantMessage: ConversationMessage | null;
63
+ output: ConversationMessage["content"] | null;
64
+ outputText: string;
65
+ finalOutput: ConversationMessage["content"] | null;
66
+ finalOutputText: string;
62
67
  streamingText: string;
63
68
  isStreaming: boolean;
64
69
  error: Error | null;
@@ -1,15 +1,14 @@
1
1
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
2
  import { parseSSEJson, readSSE } from "../streams.js";
3
3
  import { parseAssistantStreamEvent, upsertConversationMessage } from "../assistant-events.js";
4
- function resolveOptionalPodId(client, podId) {
5
- return podId ?? client.podId;
6
- }
4
+ import { extractConversationMessageText, getLatestAssistantMessage, } from "./assistant-output.js";
5
+ import { normalizeError } from "./utils.js";
7
6
  function applyPodScope(client, podId) {
8
- const resolvedPodId = resolveOptionalPodId(client, podId);
9
- if (resolvedPodId) {
10
- client.setPodId(resolvedPodId);
7
+ const resolvedPodId = podId ?? client.podId;
8
+ if (resolvedPodId && resolvedPodId !== client.podId) {
9
+ return client.withPod(resolvedPodId);
11
10
  }
12
- return resolvedPodId;
11
+ return client;
13
12
  }
14
13
  function requireConversationId(conversationId) {
15
14
  if (!conversationId) {
@@ -17,11 +16,6 @@ function requireConversationId(conversationId) {
17
16
  }
18
17
  return conversationId;
19
18
  }
20
- function normalizeError(error, fallback) {
21
- if (error instanceof Error)
22
- return error;
23
- return new Error(fallback);
24
- }
25
19
  function normalizeScope(client, defaults, override) {
26
20
  const resolvedAssistantName = override?.assistantName
27
21
  ?? override?.assistantId
@@ -140,10 +134,11 @@ export function useAssistantSession(options) {
140
134
  organizationId: defaultOrganizationId ?? null,
141
135
  }), [defaultAssistantId, defaultAssistantName, defaultOrganizationId, defaultPodId]);
142
136
  const listConversations = useCallback(async (input = {}) => {
137
+ setError(null);
143
138
  try {
144
139
  const scope = normalizeScope(client, defaultScope, input.scope);
145
- applyPodScope(client, scope.podId);
146
- const response = await client.conversations.list({
140
+ const scopedClient = applyPodScope(client, scope.podId);
141
+ const response = await scopedClient.conversations.list({
147
142
  pod_id: scope.podId ?? undefined,
148
143
  assistant_name: scope.assistantName ?? scope.assistantId ?? undefined,
149
144
  organization_id: scope.organizationId ?? undefined,
@@ -154,6 +149,7 @@ export function useAssistantSession(options) {
154
149
  items: response.items ?? [],
155
150
  limit: response.limit ?? input.limit ?? 20,
156
151
  next_page_token: response.next_page_token,
152
+ total: response.total,
157
153
  };
158
154
  }
159
155
  catch (listError) {
@@ -168,30 +164,39 @@ export function useAssistantSession(options) {
168
164
  }
169
165
  }, [client, defaultScope]);
170
166
  const createConversation = useCallback(async (input = {}) => {
171
- applyPodScope(client, input.podId ?? defaultPodId ?? null);
172
- const payload = {
173
- title: input.title ?? undefined,
174
- pod_id: input.podId ?? defaultPodId ?? client.podId ?? undefined,
175
- assistant_name: input.assistantName
176
- ?? input.assistantId
177
- ?? defaultAssistantName
178
- ?? defaultAssistantId
179
- ?? undefined,
180
- organization_id: input.organizationId ?? defaultOrganizationId ?? undefined,
181
- model: typeof input.model === "undefined"
182
- ? undefined
183
- : input.model,
184
- };
185
- const created = await client.conversations.create(payload);
186
- if (input.setActive !== false) {
187
- setConversationIdState(created.id);
188
- setConversation(created);
189
- setConversationStatus(created.status);
190
- setMessages([]);
191
- clearStreamingText();
192
- autoResumedKeyRef.current = null;
167
+ setError(null);
168
+ try {
169
+ const scopedClient = applyPodScope(client, input.podId ?? defaultPodId ?? null);
170
+ const payload = {
171
+ title: input.title ?? undefined,
172
+ pod_id: input.podId ?? defaultPodId ?? scopedClient.podId ?? undefined,
173
+ assistant_name: input.assistantName
174
+ ?? input.assistantId
175
+ ?? defaultAssistantName
176
+ ?? defaultAssistantId
177
+ ?? undefined,
178
+ organization_id: input.organizationId ?? defaultOrganizationId ?? undefined,
179
+ model: typeof input.model === "undefined"
180
+ ? undefined
181
+ : input.model,
182
+ };
183
+ const created = await scopedClient.conversations.create(payload);
184
+ if (input.setActive !== false) {
185
+ setConversationIdState(created.id);
186
+ setConversation(created);
187
+ setConversationStatus(created.status);
188
+ setMessages([]);
189
+ clearStreamingText();
190
+ autoResumedKeyRef.current = null;
191
+ }
192
+ return created;
193
+ }
194
+ catch (createError) {
195
+ const normalized = normalizeError(createError, "Failed to create conversation.");
196
+ setError(normalized);
197
+ onErrorRef.current?.(createError);
198
+ throw normalized;
193
199
  }
194
- return created;
195
200
  }, [
196
201
  clearStreamingText,
197
202
  client,
@@ -205,10 +210,11 @@ export function useAssistantSession(options) {
205
210
  const id = explicitConversationId ?? conversationId;
206
211
  if (!id)
207
212
  return null;
213
+ setError(null);
208
214
  try {
209
215
  const scope = normalizeScope(client, defaultScope);
210
- applyPodScope(client, scope.podId);
211
- const nextConversation = await client.conversations.get(id, {
216
+ const scopedClient = applyPodScope(client, scope.podId);
217
+ const nextConversation = await scopedClient.conversations.get(id, {
212
218
  pod_id: scope.podId ?? undefined,
213
219
  });
214
220
  setConversation(nextConversation);
@@ -230,6 +236,7 @@ export function useAssistantSession(options) {
230
236
  if (!id) {
231
237
  return { items: [], limit: input.limit ?? 20, next_page_token: null };
232
238
  }
239
+ setError(null);
233
240
  try {
234
241
  const response = await client.conversations.messages.list(id, {
235
242
  limit: input.limit,
@@ -351,48 +358,66 @@ export function useAssistantSession(options) {
351
358
  });
352
359
  }, [conversation, conversationId, createConversation, refreshConversation]);
353
360
  const sendMessage = useCallback(async (content, input = {}) => {
354
- const resolvedConversation = await ensureConversation(input.conversationId, input);
355
- const resolvedConversationId = requireConversationId(resolvedConversation.id);
356
- cancel();
357
- const controller = new AbortController();
358
- abortRef.current = controller;
359
- const scope = normalizeScope(client, defaultScope, input.createConversation);
360
- applyPodScope(client, scope.podId);
361
- const stream = await client.conversations.sendMessageStream(resolvedConversationId, { content }, {
362
- pod_id: scope.podId ?? undefined,
363
- signal: controller.signal,
364
- });
365
- setConversationStatus("RUNNING");
366
- await consume({
367
- stream,
368
- controller,
369
- streamConversationId: resolvedConversationId,
370
- syncAfterStream: input.syncOnTurnEnd,
371
- });
372
- return resolvedConversation;
361
+ setError(null);
362
+ try {
363
+ const resolvedConversation = await ensureConversation(input.conversationId, input);
364
+ const resolvedConversationId = requireConversationId(resolvedConversation.id);
365
+ cancel();
366
+ const controller = new AbortController();
367
+ abortRef.current = controller;
368
+ const scope = normalizeScope(client, defaultScope, input.createConversation);
369
+ const scopedClient = applyPodScope(client, scope.podId);
370
+ const stream = await scopedClient.conversations.sendMessageStream(resolvedConversationId, { content }, {
371
+ pod_id: scope.podId ?? undefined,
372
+ signal: controller.signal,
373
+ });
374
+ setConversationStatus("RUNNING");
375
+ await consume({
376
+ stream,
377
+ controller,
378
+ streamConversationId: resolvedConversationId,
379
+ syncAfterStream: input.syncOnTurnEnd,
380
+ });
381
+ return resolvedConversation;
382
+ }
383
+ catch (sendError) {
384
+ const normalized = normalizeError(sendError, "Failed to send assistant message.");
385
+ setError(normalized);
386
+ onErrorRef.current?.(sendError);
387
+ throw normalized;
388
+ }
373
389
  }, [cancel, client, consume, defaultScope, ensureConversation, setConversationStatus]);
374
390
  const resume = useCallback(async (input) => {
375
- const resumeInput = resolveResumeInput(input);
376
- const id = requireConversationId(resumeInput.conversationId ?? conversationId);
377
- if (resumeInput.onlyIfRunning && !isConversationRunningStatus(statusRef.current)) {
378
- return;
391
+ setError(null);
392
+ try {
393
+ const resumeInput = resolveResumeInput(input);
394
+ const id = requireConversationId(resumeInput.conversationId ?? conversationId);
395
+ if (resumeInput.onlyIfRunning && !isConversationRunningStatus(statusRef.current)) {
396
+ return;
397
+ }
398
+ cancel();
399
+ const controller = new AbortController();
400
+ abortRef.current = controller;
401
+ const scope = normalizeScope(client, defaultScope);
402
+ const scopedClient = applyPodScope(client, scope.podId);
403
+ const stream = await scopedClient.conversations.resumeStream(id, {
404
+ pod_id: scope.podId ?? undefined,
405
+ signal: controller.signal,
406
+ });
407
+ setConversationStatus("RUNNING");
408
+ await consume({
409
+ stream,
410
+ controller,
411
+ streamConversationId: id,
412
+ syncAfterStream: resumeInput.syncOnTurnEnd,
413
+ });
414
+ }
415
+ catch (resumeError) {
416
+ const normalized = normalizeError(resumeError, "Failed to resume assistant run.");
417
+ setError(normalized);
418
+ onErrorRef.current?.(resumeError);
419
+ throw normalized;
379
420
  }
380
- cancel();
381
- const controller = new AbortController();
382
- abortRef.current = controller;
383
- const scope = normalizeScope(client, defaultScope);
384
- applyPodScope(client, scope.podId);
385
- const stream = await client.conversations.resumeStream(id, {
386
- pod_id: scope.podId ?? undefined,
387
- signal: controller.signal,
388
- });
389
- setConversationStatus("RUNNING");
390
- await consume({
391
- stream,
392
- controller,
393
- streamConversationId: id,
394
- syncAfterStream: resumeInput.syncOnTurnEnd,
395
- });
396
421
  }, [cancel, client, consume, conversationId, defaultScope, setConversationStatus]);
397
422
  const resumeIfRunning = useCallback(async (explicitConversationId) => {
398
423
  const id = explicitConversationId ?? conversationId;
@@ -429,14 +454,23 @@ export function useAssistantSession(options) {
429
454
  }
430
455
  }, [conversationId, isStreaming, refreshConversation, resume]);
431
456
  const stop = useCallback(async (explicitConversationId) => {
432
- const id = requireConversationId(explicitConversationId ?? conversationId);
433
- const scope = normalizeScope(client, defaultScope);
434
- applyPodScope(client, scope.podId);
435
- await client.conversations.stopRun(id, {
436
- pod_id: scope.podId ?? undefined,
437
- });
438
- setConversationStatus("WAITING");
439
- clearStreamingText();
457
+ setError(null);
458
+ try {
459
+ const id = requireConversationId(explicitConversationId ?? conversationId);
460
+ const scope = normalizeScope(client, defaultScope);
461
+ const scopedClient = applyPodScope(client, scope.podId);
462
+ await scopedClient.conversations.stopRun(id, {
463
+ pod_id: scope.podId ?? undefined,
464
+ });
465
+ setConversationStatus("WAITING");
466
+ clearStreamingText();
467
+ }
468
+ catch (stopError) {
469
+ const normalized = normalizeError(stopError, "Failed to stop assistant run.");
470
+ setError(normalized);
471
+ onErrorRef.current?.(stopError);
472
+ throw normalized;
473
+ }
440
474
  }, [client, conversationId, defaultScope]);
441
475
  const clearMessages = useCallback(() => {
442
476
  setMessages([]);
@@ -453,6 +487,7 @@ export function useAssistantSession(options) {
453
487
  if (!autoLoad || !conversationId) {
454
488
  return;
455
489
  }
490
+ const controller = new AbortController();
456
491
  let cancelled = false;
457
492
  const bootstrapConversation = async () => {
458
493
  const latestConversation = await refreshConversation(conversationId);
@@ -471,13 +506,27 @@ export function useAssistantSession(options) {
471
506
  void bootstrapConversation();
472
507
  return () => {
473
508
  cancelled = true;
509
+ controller.abort();
474
510
  };
475
511
  }, [autoLoad, autoResume, conversationId, loadMessages, refreshConversation, resumeIfRunning]);
512
+ const latestAssistantMessage = useMemo(() => getLatestAssistantMessage(messages), [messages]);
513
+ const output = latestAssistantMessage?.content ?? null;
514
+ const latestAssistantText = latestAssistantMessage
515
+ ? extractConversationMessageText(latestAssistantMessage.content)
516
+ : "";
517
+ const outputText = streamingText.trim() || latestAssistantText;
518
+ const finalOutput = !isStreaming && !isConversationRunningStatus(status) ? output : null;
519
+ const finalOutputText = !isStreaming && !isConversationRunningStatus(status) ? latestAssistantText : "";
476
520
  return {
477
521
  conversationId,
478
522
  conversation,
479
523
  status,
480
524
  messages,
525
+ latestAssistantMessage,
526
+ output,
527
+ outputText,
528
+ finalOutput,
529
+ finalOutputText,
481
530
  streamingText,
482
531
  isStreaming,
483
532
  error,
@@ -0,0 +1,20 @@
1
+ import type { LemmaClient } from "../client.js";
2
+ import type { DatastoreMessageResponse } from "../types.js";
3
+ export interface UseBulkRecordsOptions {
4
+ client: LemmaClient;
5
+ podId?: string;
6
+ tableName: string;
7
+ enabled?: boolean;
8
+ onSuccess?: (response: DatastoreMessageResponse) => void;
9
+ onError?: (error: unknown) => void;
10
+ }
11
+ export interface UseBulkRecordsResult {
12
+ isSubmitting: boolean;
13
+ error: Error | null;
14
+ lastMessage: string | null;
15
+ createMany: (records: Record<string, unknown>[]) => Promise<DatastoreMessageResponse | null>;
16
+ updateMany: (records: Record<string, unknown>[]) => Promise<DatastoreMessageResponse | null>;
17
+ deleteMany: (recordIds: Array<string | number>) => Promise<DatastoreMessageResponse | null>;
18
+ reset: () => void;
19
+ }
20
+ export declare function useBulkRecords({ client, podId, tableName, enabled, onSuccess, onError, }: UseBulkRecordsOptions): UseBulkRecordsResult;
@@ -0,0 +1,65 @@
1
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
+ import { normalizeError, resolvePodClient } from "./utils.js";
3
+ export function useBulkRecords({ client, podId, tableName, enabled = true, onSuccess, onError, }) {
4
+ const [isSubmitting, setIsSubmitting] = useState(false);
5
+ const [error, setError] = useState(null);
6
+ const [lastMessage, setLastMessage] = useState(null);
7
+ const onSuccessRef = useRef(onSuccess);
8
+ const onErrorRef = useRef(onError);
9
+ useEffect(() => { onSuccessRef.current = onSuccess; }, [onSuccess]);
10
+ useEffect(() => { onErrorRef.current = onError; }, [onError]);
11
+ const trimmedTableName = tableName.trim();
12
+ const isEnabled = enabled && trimmedTableName.length > 0;
13
+ const runBulkOperation = useCallback(async (action, fallbackError) => {
14
+ if (!isEnabled) {
15
+ return null;
16
+ }
17
+ setIsSubmitting(true);
18
+ setError(null);
19
+ try {
20
+ const scopedClient = resolvePodClient(client, podId);
21
+ const response = await action(scopedClient);
22
+ setLastMessage(response.message ?? null);
23
+ onSuccessRef.current?.(response);
24
+ return response;
25
+ }
26
+ catch (mutationError) {
27
+ const normalized = normalizeError(mutationError, fallbackError);
28
+ setError(normalized);
29
+ onErrorRef.current?.(mutationError);
30
+ return null;
31
+ }
32
+ finally {
33
+ setIsSubmitting(false);
34
+ }
35
+ }, [client, isEnabled, podId]);
36
+ const createMany = useCallback(async (records) => {
37
+ if (records.length === 0)
38
+ return null;
39
+ return runBulkOperation((scopedClient) => scopedClient.records.bulk.create(trimmedTableName, records), "Failed to bulk create records.");
40
+ }, [runBulkOperation, trimmedTableName]);
41
+ const updateMany = useCallback(async (records) => {
42
+ if (records.length === 0)
43
+ return null;
44
+ return runBulkOperation((scopedClient) => scopedClient.records.bulk.update(trimmedTableName, records), "Failed to bulk update records.");
45
+ }, [runBulkOperation, trimmedTableName]);
46
+ const deleteMany = useCallback(async (recordIds) => {
47
+ if (recordIds.length === 0)
48
+ return null;
49
+ return runBulkOperation((scopedClient) => scopedClient.records.bulk.delete(trimmedTableName, recordIds), "Failed to bulk delete records.");
50
+ }, [runBulkOperation, trimmedTableName]);
51
+ const reset = useCallback(() => {
52
+ setError(null);
53
+ setIsSubmitting(false);
54
+ setLastMessage(null);
55
+ }, []);
56
+ return useMemo(() => ({
57
+ isSubmitting,
58
+ error,
59
+ lastMessage,
60
+ createMany,
61
+ updateMany,
62
+ deleteMany,
63
+ reset,
64
+ }), [createMany, deleteMany, error, isSubmitting, lastMessage, reset, updateMany]);
65
+ }
@@ -0,0 +1,18 @@
1
+ import type { LemmaClient } from "../client.js";
2
+ import type { Conversation } from "../types.js";
3
+ export interface UseConversationOptions {
4
+ client: LemmaClient;
5
+ podId?: string;
6
+ conversationId?: string | null;
7
+ enabled?: boolean;
8
+ autoLoad?: boolean;
9
+ }
10
+ export interface UseConversationResult {
11
+ conversation: Conversation | null;
12
+ isLoading: boolean;
13
+ error: Error | null;
14
+ refresh: (overrides?: {
15
+ conversationId?: string | null;
16
+ }) => Promise<Conversation | null>;
17
+ }
18
+ export declare function useConversation({ client, podId, conversationId, enabled, autoLoad, }: UseConversationOptions): UseConversationResult;