@assistant-ui/ai-sdk 0.0.6 → 0.0.8
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.
- package/README.md +1 -1
- package/dist/adapters/aiSDKFormatAdapter.d.ts +2 -2
- package/dist/adapters/aiSDKFormatAdapter.d.ts.map +1 -0
- package/dist/adapters/vercelAttachmentAdapter.d.ts +2 -5
- package/dist/adapters/vercelAttachmentAdapter.d.ts.map +1 -1
- package/dist/adapters/vercelAttachmentAdapter.js +3 -3
- package/dist/adapters/vercelAttachmentAdapter.js.map +1 -1
- package/dist/aiSDKExtras.d.ts +6 -9
- package/dist/aiSDKExtras.d.ts.map +1 -1
- package/dist/converters/convertMessage.d.ts +34 -25
- package/dist/converters/convertMessage.d.ts.map +1 -1
- package/dist/converters/convertMessage.js +78 -8
- package/dist/converters/convertMessage.js.map +1 -1
- package/dist/converters/modelContentEnvelope.d.ts +10 -12
- package/dist/converters/modelContentEnvelope.d.ts.map +1 -1
- package/dist/converters/toCreateMessage.d.ts +3 -6
- package/dist/converters/toCreateMessage.d.ts.map +1 -1
- package/dist/converters/toolOutputConversion.d.ts +22 -25
- package/dist/converters/toolOutputConversion.d.ts.map +1 -1
- package/dist/hooks.d.ts +4 -7
- package/dist/hooks.d.ts.map +1 -1
- package/dist/index.d.ts +17 -14
- package/dist/index.d.ts.map +1 -0
- package/dist/index.native.d.ts +15 -12
- package/dist/index.native.d.ts.map +1 -0
- package/dist/model-context/injectInteractableContext.d.ts +3 -6
- package/dist/model-context/injectInteractableContext.d.ts.map +1 -1
- package/dist/model-context/injectQuoteContext.d.ts +2 -5
- package/dist/model-context/injectQuoteContext.d.ts.map +1 -1
- package/dist/runtime/AISDKChat.d.ts +4 -7
- package/dist/runtime/AISDKChat.d.ts.map +1 -1
- package/dist/runtime/AISDKChat.js +1 -1
- package/dist/runtime/AISDKThreads.d.ts +29 -32
- package/dist/runtime/AISDKThreads.d.ts.map +1 -1
- package/dist/runtime/AISDKThreads.js +26 -33
- package/dist/runtime/AISDKThreads.js.map +1 -1
- package/dist/runtime/sdkIdentity.d.ts +2 -5
- package/dist/runtime/sdkIdentity.d.ts.map +1 -1
- package/dist/runtime/sdkIdentity.js +1 -1
- package/dist/runtime/useAISDKRuntime.d.ts +72 -61
- package/dist/runtime/useAISDKRuntime.d.ts.map +1 -1
- package/dist/runtime/useAISDKRuntime.js +170 -56
- package/dist/runtime/useAISDKRuntime.js.map +1 -1
- package/dist/runtime/useChatRuntime.d.ts +8 -11
- package/dist/runtime/useChatRuntime.d.ts.map +1 -1
- package/dist/runtime/useChatRuntime.js +14 -10
- package/dist/runtime/useChatRuntime.js.map +1 -1
- package/dist/runtime/useChatThread.d.ts +100 -81
- package/dist/runtime/useChatThread.d.ts.map +1 -1
- package/dist/runtime/useChatThread.js +33 -13
- package/dist/runtime/useChatThread.js.map +1 -1
- package/dist/runtime/useExternalHistory.d.ts +8 -9
- package/dist/runtime/useExternalHistory.d.ts.map +1 -1
- package/dist/runtime/useExternalHistory.js +236 -58
- package/dist/runtime/useExternalHistory.js.map +1 -1
- package/dist/runtime/useResourceCleanup.d.ts +1 -4
- package/dist/runtime/useResourceCleanup.d.ts.map +1 -1
- package/dist/runtime/useResourceCleanup.js +38 -14
- package/dist/runtime/useResourceCleanup.js.map +1 -1
- package/dist/runtime/useStreamingTiming.d.ts +5 -8
- package/dist/runtime/useStreamingTiming.d.ts.map +1 -1
- package/dist/runtime/useStreamingTiming.js +1 -1
- package/dist/tools/frontendTools.d.ts +24 -27
- package/dist/tools/frontendTools.d.ts.map +1 -1
- package/dist/tools/generativeTools.d.ts +29 -32
- package/dist/tools/generativeTools.d.ts.map +1 -1
- package/dist/tools/mcp-stdio.node.d.ts +2 -2
- package/dist/tools/mcp-stdio.node.d.ts.map +1 -0
- package/dist/tools/mcp-stdio.unsupported.d.ts +2 -5
- package/dist/tools/mcp-stdio.unsupported.d.ts.map +1 -1
- package/dist/transport/AssistantChatTransport.d.ts +17 -20
- package/dist/transport/AssistantChatTransport.d.ts.map +1 -1
- package/dist/transport/resumable.d.ts +29 -32
- package/dist/transport/resumable.d.ts.map +1 -1
- package/dist/usage.d.ts +12 -15
- package/dist/usage.d.ts.map +1 -1
- package/dist/usage.js.map +1 -1
- package/dist/utils/getVercelAIMessages.d.ts +3 -6
- package/dist/utils/getVercelAIMessages.d.ts.map +1 -1
- package/dist/utils/sliceMessagesUntil.d.ts +2 -5
- package/dist/utils/sliceMessagesUntil.d.ts.map +1 -1
- package/package.json +16 -14
- package/src/adapters/vercelAttachmentAdapter.test.ts +23 -0
- package/src/adapters/vercelAttachmentAdapter.ts +2 -2
- package/src/converters/convertMessage.test.ts +616 -3
- package/src/converters/convertMessage.ts +187 -12
- package/src/index.native.ts +1 -1
- package/src/index.ts +1 -1
- package/src/runtime/AISDKChat.test.ts +4 -5
- package/src/runtime/AISDKThreads.cloud.test.ts +60 -2
- package/src/runtime/AISDKThreads.test.ts +88 -51
- package/src/runtime/AISDKThreads.ts +4 -13
- package/src/runtime/useAISDKRuntime.approval.integration.test.tsx +338 -0
- package/src/runtime/useAISDKRuntime.approval.test.tsx +476 -4
- package/src/runtime/useAISDKRuntime.test.ts +282 -8
- package/src/runtime/useAISDKRuntime.ts +274 -46
- package/src/runtime/useAISDKRuntime.voice.test.tsx +319 -0
- package/src/runtime/useChatRuntime.integration.test.tsx +56 -0
- package/src/runtime/useChatRuntime.test.ts +77 -5
- package/src/runtime/useChatRuntime.ts +6 -2
- package/src/runtime/useChatThread.transport.test.tsx +149 -0
- package/src/runtime/useChatThread.ts +77 -10
- package/src/runtime/useExternalHistory.test.ts +989 -3
- package/src/runtime/useExternalHistory.ts +390 -24
- package/src/runtime/useResourceCleanup.ts +41 -15
- package/src/tools/generativeTools.test.ts +7 -1
- package/src/usage.ts +1 -1
|
@@ -8,9 +8,14 @@ import type {
|
|
|
8
8
|
MessageFormatItem,
|
|
9
9
|
MessageFormatRepository,
|
|
10
10
|
ExportedMessageRepository,
|
|
11
|
+
RespondToToolApprovalOptions,
|
|
12
|
+
Unstable_ToolInteractionLog,
|
|
11
13
|
} from "@assistant-ui/core";
|
|
12
14
|
import { getExternalStoreMessages } from "@assistant-ui/core";
|
|
13
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
MessageRepository,
|
|
17
|
+
readToolInteractionLog,
|
|
18
|
+
} from "@assistant-ui/core/internal";
|
|
14
19
|
import { useAui } from "@assistant-ui/store";
|
|
15
20
|
import {
|
|
16
21
|
useRef,
|
|
@@ -50,6 +55,211 @@ const isAwaitingToolApproval = (message: ThreadMessage) =>
|
|
|
50
55
|
message.status?.type === "requires-action" &&
|
|
51
56
|
message.status.reason === "tool-calls";
|
|
52
57
|
|
|
58
|
+
const isTerminalMessage = (message: ThreadMessage) =>
|
|
59
|
+
message.status === undefined ||
|
|
60
|
+
message.status.type === "complete" ||
|
|
61
|
+
message.status.type === "incomplete";
|
|
62
|
+
|
|
63
|
+
const TOOL_ARTIFACTS_METADATA_KEY = "__aui_toolArtifacts";
|
|
64
|
+
const TOOL_INTERACTIONS_METADATA_KEY = "__aui_toolInteractions";
|
|
65
|
+
const TOOL_APPROVAL_RESPONSES_METADATA_KEY = "__aui_toolApprovalResponses";
|
|
66
|
+
|
|
67
|
+
type StoredToolApprovalResponse = Omit<
|
|
68
|
+
RespondToToolApprovalOptions,
|
|
69
|
+
"approvalId"
|
|
70
|
+
>;
|
|
71
|
+
|
|
72
|
+
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
73
|
+
value !== null && typeof value === "object" && !Array.isArray(value);
|
|
74
|
+
|
|
75
|
+
const collectToolArtifacts = (
|
|
76
|
+
message: ThreadMessage,
|
|
77
|
+
toolArtifacts: ReadonlyMap<string, unknown> | undefined,
|
|
78
|
+
) => {
|
|
79
|
+
if (!toolArtifacts) return undefined;
|
|
80
|
+
const entries = message.content.flatMap((part) => {
|
|
81
|
+
if (part.type !== "tool-call") return [];
|
|
82
|
+
const artifact = toolArtifacts.get(part.toolCallId);
|
|
83
|
+
return artifact === undefined ? [] : [[part.toolCallId, artifact] as const];
|
|
84
|
+
});
|
|
85
|
+
return entries.length > 0 ? Object.fromEntries(entries) : undefined;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const collectToolInteractions = (
|
|
89
|
+
message: ThreadMessage,
|
|
90
|
+
toolInteractions:
|
|
91
|
+
| ReadonlyMap<string, Unstable_ToolInteractionLog>
|
|
92
|
+
| undefined,
|
|
93
|
+
) => {
|
|
94
|
+
if (!toolInteractions) return undefined;
|
|
95
|
+
const entries = message.content.flatMap((part) => {
|
|
96
|
+
if (part.type !== "tool-call") return [];
|
|
97
|
+
const interactions = toolInteractions.get(part.toolCallId);
|
|
98
|
+
return interactions === undefined
|
|
99
|
+
? []
|
|
100
|
+
: [[part.toolCallId, interactions] as const];
|
|
101
|
+
});
|
|
102
|
+
return entries.length > 0 ? Object.fromEntries(entries) : undefined;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const collectToolApprovalResponses = (
|
|
106
|
+
message: ThreadMessage,
|
|
107
|
+
toolApprovalResponses:
|
|
108
|
+
| ReadonlyMap<string, RespondToToolApprovalOptions>
|
|
109
|
+
| undefined,
|
|
110
|
+
) => {
|
|
111
|
+
if (!toolApprovalResponses) return undefined;
|
|
112
|
+
const entries = message.content.flatMap((part) => {
|
|
113
|
+
if (part.type !== "tool-call" || !part.approval) return [];
|
|
114
|
+
const response = toolApprovalResponses.get(part.approval.id);
|
|
115
|
+
if (!response) return [];
|
|
116
|
+
return [
|
|
117
|
+
[
|
|
118
|
+
part.approval.id,
|
|
119
|
+
{
|
|
120
|
+
approved: response.approved,
|
|
121
|
+
...(response.optionId != null && { optionId: response.optionId }),
|
|
122
|
+
...(response.text != null && { text: response.text }),
|
|
123
|
+
...(response.reason != null && { reason: response.reason }),
|
|
124
|
+
},
|
|
125
|
+
] as const,
|
|
126
|
+
];
|
|
127
|
+
});
|
|
128
|
+
return entries.length > 0 ? Object.fromEntries(entries) : undefined;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const addToolData = <TMessage>(
|
|
132
|
+
message: TMessage,
|
|
133
|
+
toolArtifacts: Record<string, unknown> | undefined,
|
|
134
|
+
toolInteractions: Record<string, Unstable_ToolInteractionLog> | undefined,
|
|
135
|
+
toolApprovalResponses: Record<string, StoredToolApprovalResponse> | undefined,
|
|
136
|
+
): TMessage => {
|
|
137
|
+
if (
|
|
138
|
+
(!toolArtifacts && !toolInteractions && !toolApprovalResponses) ||
|
|
139
|
+
!isRecord(message) ||
|
|
140
|
+
!Array.isArray(message.parts)
|
|
141
|
+
)
|
|
142
|
+
return message;
|
|
143
|
+
const toolCallIds = message.parts.flatMap((part) => {
|
|
144
|
+
if (!isRecord(part) || typeof part.toolCallId !== "string") return [];
|
|
145
|
+
return [part.toolCallId];
|
|
146
|
+
});
|
|
147
|
+
const artifacts = toolArtifacts
|
|
148
|
+
? Object.fromEntries(
|
|
149
|
+
toolCallIds.flatMap((toolCallId) =>
|
|
150
|
+
Object.hasOwn(toolArtifacts, toolCallId)
|
|
151
|
+
? [[toolCallId, toolArtifacts[toolCallId]] as const]
|
|
152
|
+
: [],
|
|
153
|
+
),
|
|
154
|
+
)
|
|
155
|
+
: undefined;
|
|
156
|
+
const interactions = toolInteractions
|
|
157
|
+
? Object.fromEntries(
|
|
158
|
+
toolCallIds.flatMap((toolCallId) =>
|
|
159
|
+
Object.hasOwn(toolInteractions, toolCallId)
|
|
160
|
+
? [[toolCallId, toolInteractions[toolCallId]] as const]
|
|
161
|
+
: [],
|
|
162
|
+
),
|
|
163
|
+
)
|
|
164
|
+
: undefined;
|
|
165
|
+
const approvalIds = message.parts.flatMap((part) => {
|
|
166
|
+
if (!isRecord(part) || !isRecord(part.approval)) return [];
|
|
167
|
+
const approvalId = part.approval.id;
|
|
168
|
+
return typeof approvalId === "string" ? [approvalId] : [];
|
|
169
|
+
});
|
|
170
|
+
const approvalResponses = toolApprovalResponses
|
|
171
|
+
? Object.fromEntries(
|
|
172
|
+
approvalIds.flatMap((approvalId) =>
|
|
173
|
+
Object.hasOwn(toolApprovalResponses, approvalId)
|
|
174
|
+
? [[approvalId, toolApprovalResponses[approvalId]] as const]
|
|
175
|
+
: [],
|
|
176
|
+
),
|
|
177
|
+
)
|
|
178
|
+
: undefined;
|
|
179
|
+
const hasArtifacts = !!artifacts && Object.keys(artifacts).length > 0;
|
|
180
|
+
const hasInteractions =
|
|
181
|
+
!!interactions && Object.keys(interactions).length > 0;
|
|
182
|
+
const hasApprovalResponses =
|
|
183
|
+
!!approvalResponses && Object.keys(approvalResponses).length > 0;
|
|
184
|
+
if (!hasArtifacts && !hasInteractions && !hasApprovalResponses)
|
|
185
|
+
return message;
|
|
186
|
+
const metadata = isRecord(message.metadata) ? message.metadata : {};
|
|
187
|
+
return {
|
|
188
|
+
...message,
|
|
189
|
+
metadata: {
|
|
190
|
+
...metadata,
|
|
191
|
+
...(hasArtifacts && { [TOOL_ARTIFACTS_METADATA_KEY]: artifacts }),
|
|
192
|
+
...(hasInteractions && {
|
|
193
|
+
[TOOL_INTERACTIONS_METADATA_KEY]: interactions,
|
|
194
|
+
}),
|
|
195
|
+
...(hasApprovalResponses && {
|
|
196
|
+
[TOOL_APPROVAL_RESPONSES_METADATA_KEY]: approvalResponses,
|
|
197
|
+
}),
|
|
198
|
+
},
|
|
199
|
+
} as TMessage;
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const restoreToolData = <TMessage>(
|
|
203
|
+
message: TMessage,
|
|
204
|
+
toolArtifacts: Map<string, unknown> | undefined,
|
|
205
|
+
toolInteractions: Map<string, Unstable_ToolInteractionLog> | undefined,
|
|
206
|
+
toolApprovalResponses: Map<string, RespondToToolApprovalOptions> | undefined,
|
|
207
|
+
): TMessage => {
|
|
208
|
+
if (!isRecord(message) || !isRecord(message.metadata)) return message;
|
|
209
|
+
const metadata = message.metadata;
|
|
210
|
+
const hasArtifacts = Object.hasOwn(metadata, TOOL_ARTIFACTS_METADATA_KEY);
|
|
211
|
+
const hasInteractions = Object.hasOwn(
|
|
212
|
+
metadata,
|
|
213
|
+
TOOL_INTERACTIONS_METADATA_KEY,
|
|
214
|
+
);
|
|
215
|
+
const hasApprovalResponses = Object.hasOwn(
|
|
216
|
+
metadata,
|
|
217
|
+
TOOL_APPROVAL_RESPONSES_METADATA_KEY,
|
|
218
|
+
);
|
|
219
|
+
if (!hasArtifacts && !hasInteractions && !hasApprovalResponses)
|
|
220
|
+
return message;
|
|
221
|
+
const artifacts = metadata[TOOL_ARTIFACTS_METADATA_KEY];
|
|
222
|
+
if (toolArtifacts && isRecord(artifacts)) {
|
|
223
|
+
for (const [toolCallId, artifact] of Object.entries(artifacts)) {
|
|
224
|
+
toolArtifacts.set(toolCallId, artifact);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
const interactions = metadata[TOOL_INTERACTIONS_METADATA_KEY];
|
|
228
|
+
if (toolInteractions && isRecord(interactions)) {
|
|
229
|
+
for (const [toolCallId, value] of Object.entries(interactions)) {
|
|
230
|
+
const log = readToolInteractionLog(value);
|
|
231
|
+
if (log) toolInteractions.set(toolCallId, log);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
const approvalResponses = metadata[TOOL_APPROVAL_RESPONSES_METADATA_KEY];
|
|
235
|
+
if (toolApprovalResponses && isRecord(approvalResponses)) {
|
|
236
|
+
for (const [approvalId, value] of Object.entries(approvalResponses)) {
|
|
237
|
+
if (!isRecord(value) || typeof value.approved !== "boolean") continue;
|
|
238
|
+
toolApprovalResponses.set(approvalId, {
|
|
239
|
+
approvalId,
|
|
240
|
+
approved: value.approved,
|
|
241
|
+
...(typeof value.optionId === "string" && {
|
|
242
|
+
optionId: value.optionId,
|
|
243
|
+
}),
|
|
244
|
+
...(typeof value.text === "string" && { text: value.text }),
|
|
245
|
+
...(typeof value.reason === "string" && { reason: value.reason }),
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
const {
|
|
250
|
+
[TOOL_ARTIFACTS_METADATA_KEY]: _,
|
|
251
|
+
[TOOL_INTERACTIONS_METADATA_KEY]: __,
|
|
252
|
+
[TOOL_APPROVAL_RESPONSES_METADATA_KEY]: ___,
|
|
253
|
+
...restMetadata
|
|
254
|
+
} = metadata;
|
|
255
|
+
const { metadata: _metadata, ...restMessage } = message;
|
|
256
|
+
return (
|
|
257
|
+
Object.keys(restMetadata).length === 0
|
|
258
|
+
? restMessage
|
|
259
|
+
: { ...restMessage, metadata: restMetadata }
|
|
260
|
+
) as TMessage;
|
|
261
|
+
};
|
|
262
|
+
|
|
53
263
|
const encodeContent = <TMessage>(
|
|
54
264
|
storageFormatAdapter: MessageFormatAdapter<TMessage, any>,
|
|
55
265
|
item: MessageFormatItem<TMessage>,
|
|
@@ -61,6 +271,12 @@ export const useExternalHistory = <TMessage>(
|
|
|
61
271
|
toThreadMessages: (messages: TMessage[]) => ThreadMessage[],
|
|
62
272
|
storageFormatAdapter: MessageFormatAdapter<TMessage, any>,
|
|
63
273
|
onSetMessages: (messages: TMessage[]) => void,
|
|
274
|
+
toolArtifacts?: Map<string, unknown>,
|
|
275
|
+
onToolArtifactsRestored?: () => void,
|
|
276
|
+
toolInteractions?: Map<string, Unstable_ToolInteractionLog>,
|
|
277
|
+
onToolInteractionsRestored?: () => void,
|
|
278
|
+
toolApprovalResponses?: Map<string, RespondToToolApprovalOptions>,
|
|
279
|
+
onToolApprovalResponsesRestored?: () => void,
|
|
64
280
|
) => {
|
|
65
281
|
const loadedRef = useRef(false);
|
|
66
282
|
const [itemEpoch, setItemEpoch] = useState(0);
|
|
@@ -103,17 +319,37 @@ export const useExternalHistory = <TMessage>(
|
|
|
103
319
|
const loadHistory = async () => {
|
|
104
320
|
try {
|
|
105
321
|
const repo = await formatAdapter.load();
|
|
106
|
-
|
|
107
|
-
|
|
322
|
+
toolArtifacts?.clear();
|
|
323
|
+
toolInteractions?.clear();
|
|
324
|
+
toolApprovalResponses?.clear();
|
|
325
|
+
const restoredMessages =
|
|
326
|
+
repo?.messages.map((item) => ({
|
|
327
|
+
...item,
|
|
328
|
+
message: restoreToolData(
|
|
329
|
+
item.message,
|
|
330
|
+
toolArtifacts,
|
|
331
|
+
toolInteractions,
|
|
332
|
+
toolApprovalResponses,
|
|
333
|
+
),
|
|
334
|
+
})) ?? [];
|
|
335
|
+
onToolArtifactsRestored?.();
|
|
336
|
+
onToolInteractionsRestored?.();
|
|
337
|
+
onToolApprovalResponsesRestored?.();
|
|
338
|
+
if (repo && restoredMessages.length > 0) {
|
|
339
|
+
const restoredRepo = { ...repo, messages: restoredMessages };
|
|
340
|
+
for (const [index, m] of repo.messages.entries()) {
|
|
108
341
|
persistedInnerMessages.current.set(
|
|
109
342
|
storageFormatAdapter.getId(m.message),
|
|
110
343
|
{
|
|
111
|
-
source:
|
|
344
|
+
source: restoredMessages[index]!.message,
|
|
112
345
|
content: encodeContent(storageFormatAdapter, m),
|
|
113
346
|
},
|
|
114
347
|
);
|
|
115
348
|
}
|
|
116
|
-
const converted = toExportedMessageRepository(
|
|
349
|
+
const converted = toExportedMessageRepository(
|
|
350
|
+
toThreadMessages,
|
|
351
|
+
restoredRepo,
|
|
352
|
+
);
|
|
117
353
|
runtimeRef.current.thread.import(converted);
|
|
118
354
|
|
|
119
355
|
const tempRepo = new MessageRepository();
|
|
@@ -172,6 +408,12 @@ export const useExternalHistory = <TMessage>(
|
|
|
172
408
|
aui,
|
|
173
409
|
itemEpoch,
|
|
174
410
|
storageFormatAdapter,
|
|
411
|
+
toolArtifacts,
|
|
412
|
+
onToolArtifactsRestored,
|
|
413
|
+
toolInteractions,
|
|
414
|
+
onToolInteractionsRestored,
|
|
415
|
+
toolApprovalResponses,
|
|
416
|
+
onToolApprovalResponsesRestored,
|
|
175
417
|
]);
|
|
176
418
|
|
|
177
419
|
const runStartRef = useRef<number | null>(null);
|
|
@@ -181,10 +423,96 @@ export const useExternalHistory = <TMessage>(
|
|
|
181
423
|
const wasRunningRef = useRef(false);
|
|
182
424
|
const toolCallCountRef = useRef(0);
|
|
183
425
|
|
|
426
|
+
const persistToolInteractions = useCallback(
|
|
427
|
+
(messageId: string) => {
|
|
428
|
+
const persistence = persistInFlightRef.current.then(async () => {
|
|
429
|
+
if (!formatAdapter?.update) return;
|
|
430
|
+
const messages = runtimeRef.current.thread.getState().messages;
|
|
431
|
+
const message = messages.find(
|
|
432
|
+
(item) =>
|
|
433
|
+
item.id === messageId ||
|
|
434
|
+
getExternalStoreMessages<TMessage>(item).some(
|
|
435
|
+
(innerMessage) =>
|
|
436
|
+
storageFormatAdapter.getId(innerMessage) === messageId,
|
|
437
|
+
),
|
|
438
|
+
);
|
|
439
|
+
if (!message) return;
|
|
440
|
+
|
|
441
|
+
const previousMessages = messages
|
|
442
|
+
.slice(0, messages.indexOf(message))
|
|
443
|
+
.flatMap(getExternalStoreMessages<TMessage>);
|
|
444
|
+
let parentId = previousMessages.at(-1)
|
|
445
|
+
? storageFormatAdapter.getId(previousMessages.at(-1)!)
|
|
446
|
+
: null;
|
|
447
|
+
const storedToolArtifacts = collectToolArtifacts(
|
|
448
|
+
message,
|
|
449
|
+
toolArtifacts,
|
|
450
|
+
);
|
|
451
|
+
const storedToolInteractions = collectToolInteractions(
|
|
452
|
+
message,
|
|
453
|
+
toolInteractions,
|
|
454
|
+
);
|
|
455
|
+
const storedToolApprovalResponses = collectToolApprovalResponses(
|
|
456
|
+
message,
|
|
457
|
+
toolApprovalResponses,
|
|
458
|
+
);
|
|
459
|
+
|
|
460
|
+
for (const innerMessage of getExternalStoreMessages<TMessage>(
|
|
461
|
+
message,
|
|
462
|
+
)) {
|
|
463
|
+
const item = {
|
|
464
|
+
parentId,
|
|
465
|
+
message: addToolData(
|
|
466
|
+
innerMessage,
|
|
467
|
+
storedToolArtifacts,
|
|
468
|
+
storedToolInteractions,
|
|
469
|
+
storedToolApprovalResponses,
|
|
470
|
+
),
|
|
471
|
+
};
|
|
472
|
+
const innerId = storageFormatAdapter.getId(item.message);
|
|
473
|
+
const persisted = persistedInnerMessages.current.get(innerId);
|
|
474
|
+
if (persisted) {
|
|
475
|
+
const content = encodeContent(storageFormatAdapter, item);
|
|
476
|
+
if (content === persisted.content) {
|
|
477
|
+
persisted.source = item.message;
|
|
478
|
+
} else {
|
|
479
|
+
await formatAdapter.update(item, innerId);
|
|
480
|
+
persistedInnerMessages.current.set(innerId, {
|
|
481
|
+
source: item.message,
|
|
482
|
+
content,
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
parentId = innerId;
|
|
487
|
+
}
|
|
488
|
+
});
|
|
489
|
+
persistInFlightRef.current = persistence.catch(() => {});
|
|
490
|
+
return persistence.catch((error) => {
|
|
491
|
+
console.error("Failed to persist tool data:", error);
|
|
492
|
+
});
|
|
493
|
+
},
|
|
494
|
+
[
|
|
495
|
+
formatAdapter,
|
|
496
|
+
runtimeRef,
|
|
497
|
+
storageFormatAdapter,
|
|
498
|
+
toolArtifacts,
|
|
499
|
+
toolInteractions,
|
|
500
|
+
toolApprovalResponses,
|
|
501
|
+
],
|
|
502
|
+
);
|
|
503
|
+
|
|
184
504
|
useEffect(() => {
|
|
185
505
|
if (!formatAdapter) return;
|
|
186
506
|
const adapter = formatAdapter;
|
|
187
507
|
|
|
508
|
+
const idleScheduledInnerIds = new Set<string>();
|
|
509
|
+
const unpersistedInnerIds = (message: ThreadMessage) =>
|
|
510
|
+
isTerminalMessage(message)
|
|
511
|
+
? getExternalStoreMessages<TMessage>(message)
|
|
512
|
+
.map((innerMessage) => storageFormatAdapter.getId(innerMessage))
|
|
513
|
+
.filter((innerId) => !persistedInnerMessages.current.has(innerId))
|
|
514
|
+
: [];
|
|
515
|
+
|
|
188
516
|
const unsubscribe = runtimeRef.current.thread.subscribe(() => {
|
|
189
517
|
const threadState = runtimeRef.current.thread.getState();
|
|
190
518
|
const { isRunning } = threadState;
|
|
@@ -220,12 +548,16 @@ export const useExternalHistory = <TMessage>(
|
|
|
220
548
|
return;
|
|
221
549
|
}
|
|
222
550
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
551
|
+
if (wasRunning) {
|
|
552
|
+
// Record step boundary offset (synchronous for accuracy)
|
|
553
|
+
if (runStartRef.current != null) {
|
|
554
|
+
stepBoundariesRef.current.push(Date.now() - runStartRef.current);
|
|
555
|
+
}
|
|
556
|
+
} else {
|
|
557
|
+
const pending = threadState.messages.flatMap(unpersistedInnerIds);
|
|
558
|
+
if (pending.every((innerId) => idleScheduledInnerIds.has(innerId)))
|
|
559
|
+
return;
|
|
560
|
+
for (const innerId of pending) idleScheduledInnerIds.add(innerId);
|
|
229
561
|
}
|
|
230
562
|
|
|
231
563
|
// Debounce: wait one macrotask so agentic step flickers are absorbed
|
|
@@ -301,22 +633,33 @@ export const useExternalHistory = <TMessage>(
|
|
|
301
633
|
const getLastInnerId = (msgs: TMessage[]): string | null =>
|
|
302
634
|
msgs.length > 0 ? storageFormatAdapter.getId(msgs.at(-1)!) : null;
|
|
303
635
|
|
|
304
|
-
const toBatchItems = (
|
|
636
|
+
const toBatchItems = (
|
|
637
|
+
msgs: TMessage[],
|
|
638
|
+
toolArtifacts: Record<string, unknown> | undefined,
|
|
639
|
+
toolInteractions:
|
|
640
|
+
| Record<string, Unstable_ToolInteractionLog>
|
|
641
|
+
| undefined,
|
|
642
|
+
toolApprovalResponses:
|
|
643
|
+
| Record<string, StoredToolApprovalResponse>
|
|
644
|
+
| undefined,
|
|
645
|
+
) =>
|
|
305
646
|
msgs.map((msg, idx) => ({
|
|
306
647
|
parentId:
|
|
307
648
|
idx === 0
|
|
308
649
|
? lastInnerMessageId
|
|
309
650
|
: storageFormatAdapter.getId(msgs[idx - 1]!),
|
|
310
|
-
message:
|
|
651
|
+
message: addToolData(
|
|
652
|
+
msg,
|
|
653
|
+
toolArtifacts,
|
|
654
|
+
toolInteractions,
|
|
655
|
+
toolApprovalResponses,
|
|
656
|
+
),
|
|
311
657
|
}));
|
|
312
658
|
|
|
313
659
|
for (const message of messages) {
|
|
314
660
|
const innerMessages = getExternalStoreMessages<TMessage>(message);
|
|
315
661
|
|
|
316
|
-
const isTerminal =
|
|
317
|
-
message.status === undefined ||
|
|
318
|
-
message.status.type === "complete" ||
|
|
319
|
-
message.status.type === "incomplete";
|
|
662
|
+
const isTerminal = isTerminalMessage(message);
|
|
320
663
|
const isAwaitingToolCalls = isAwaitingToolApproval(message);
|
|
321
664
|
// A paused message's later content can only reach storage via update, so it is persisted early only when the adapter supports update.
|
|
322
665
|
const isReady =
|
|
@@ -334,7 +677,12 @@ export const useExternalHistory = <TMessage>(
|
|
|
334
677
|
deferredTelemetryIds.current.add(message.id);
|
|
335
678
|
}
|
|
336
679
|
|
|
337
|
-
const batchItems = toBatchItems(
|
|
680
|
+
const batchItems = toBatchItems(
|
|
681
|
+
innerMessages,
|
|
682
|
+
collectToolArtifacts(message, toolArtifacts),
|
|
683
|
+
collectToolInteractions(message, toolInteractions),
|
|
684
|
+
collectToolApprovalResponses(message, toolApprovalResponses),
|
|
685
|
+
);
|
|
338
686
|
for (const item of batchItems) {
|
|
339
687
|
const innerId = storageFormatAdapter.getId(item.message);
|
|
340
688
|
const persisted = persistedInnerMessages.current.get(innerId);
|
|
@@ -371,10 +719,16 @@ export const useExternalHistory = <TMessage>(
|
|
|
371
719
|
|
|
372
720
|
if (deferredTelemetryIds.current.has(message.id) && isTerminal) {
|
|
373
721
|
deferredTelemetryIds.current.delete(message.id);
|
|
374
|
-
adapter.reportTelemetry?.(
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
722
|
+
adapter.reportTelemetry?.(
|
|
723
|
+
batchItems.map((item, index) => ({
|
|
724
|
+
...item,
|
|
725
|
+
message: innerMessages[index]!,
|
|
726
|
+
})),
|
|
727
|
+
{
|
|
728
|
+
...telemetryOptions,
|
|
729
|
+
message,
|
|
730
|
+
},
|
|
731
|
+
);
|
|
378
732
|
}
|
|
379
733
|
}
|
|
380
734
|
})
|
|
@@ -391,7 +745,14 @@ export const useExternalHistory = <TMessage>(
|
|
|
391
745
|
persistSettled(false);
|
|
392
746
|
}
|
|
393
747
|
};
|
|
394
|
-
}, [
|
|
748
|
+
}, [
|
|
749
|
+
formatAdapter,
|
|
750
|
+
storageFormatAdapter,
|
|
751
|
+
runtimeRef,
|
|
752
|
+
toolArtifacts,
|
|
753
|
+
toolInteractions,
|
|
754
|
+
toolApprovalResponses,
|
|
755
|
+
]);
|
|
395
756
|
|
|
396
757
|
const deleteMessage = useCallback(
|
|
397
758
|
async (messageId: string) => {
|
|
@@ -434,5 +795,10 @@ export const useExternalHistory = <TMessage>(
|
|
|
434
795
|
[formatAdapter, runtimeRef, storageFormatAdapter],
|
|
435
796
|
);
|
|
436
797
|
|
|
437
|
-
return {
|
|
798
|
+
return {
|
|
799
|
+
isLoading,
|
|
800
|
+
deleteMessage,
|
|
801
|
+
persistToolInteractions,
|
|
802
|
+
persistToolApprovalResponses: persistToolInteractions,
|
|
803
|
+
};
|
|
438
804
|
};
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import { useEffect, useRef } from "react";
|
|
2
2
|
import { useAssistantClientDestroySignal } from "@assistant-ui/store/internal";
|
|
3
3
|
|
|
4
|
-
export const useResourceCleanup = (
|
|
4
|
+
export const useResourceCleanup = (
|
|
5
|
+
enabled: boolean,
|
|
6
|
+
cleanup: () => void,
|
|
7
|
+
hostDestroySignal?: AbortSignal,
|
|
8
|
+
) => {
|
|
5
9
|
const destroySignal = useAssistantClientDestroySignal();
|
|
6
10
|
const cleanupRef = useRef(cleanup);
|
|
7
11
|
const enabledRef = useRef(enabled);
|
|
8
|
-
const
|
|
12
|
+
const registrationRef = useRef<{
|
|
13
|
+
destroySignal: AbortSignal | undefined;
|
|
14
|
+
hostDestroySignal: AbortSignal | undefined;
|
|
15
|
+
controller: AbortController;
|
|
16
|
+
} | null>(null);
|
|
9
17
|
|
|
10
18
|
useEffect(() => {
|
|
11
19
|
cleanupRef.current = cleanup;
|
|
@@ -13,20 +21,38 @@ export const useResourceCleanup = (enabled: boolean, cleanup: () => void) => {
|
|
|
13
21
|
});
|
|
14
22
|
|
|
15
23
|
useEffect(() => {
|
|
16
|
-
if (!enabled || !destroySignal) return undefined;
|
|
17
|
-
|
|
24
|
+
if (!enabled || (!destroySignal && !hostDestroySignal)) return undefined;
|
|
25
|
+
const current = registrationRef.current;
|
|
26
|
+
if (
|
|
27
|
+
current !== null &&
|
|
28
|
+
current.destroySignal === destroySignal &&
|
|
29
|
+
current.hostDestroySignal === hostDestroySignal
|
|
30
|
+
) {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
current?.controller.abort();
|
|
18
34
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
35
|
+
const registration = new AbortController();
|
|
36
|
+
registrationRef.current = {
|
|
37
|
+
destroySignal,
|
|
38
|
+
hostDestroySignal,
|
|
39
|
+
controller: registration,
|
|
40
|
+
};
|
|
41
|
+
const run = () => {
|
|
42
|
+
if (registration.signal.aborted) return;
|
|
43
|
+
registration.abort();
|
|
44
|
+
if (enabledRef.current) cleanupRef.current();
|
|
45
|
+
};
|
|
46
|
+
if (destroySignal?.aborted || hostDestroySignal?.aborted) {
|
|
47
|
+
run();
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
const options = { once: true, signal: registration.signal };
|
|
51
|
+
destroySignal?.addEventListener("abort", run, options);
|
|
52
|
+
hostDestroySignal?.addEventListener("abort", run, options);
|
|
27
53
|
|
|
28
|
-
// The
|
|
29
|
-
//
|
|
54
|
+
// The listeners must survive standalone soft unmounts so a later
|
|
55
|
+
// permanent destroy still cleans up the retained resource state.
|
|
30
56
|
return undefined;
|
|
31
|
-
}, [destroySignal, enabled]);
|
|
57
|
+
}, [destroySignal, enabled, hostDestroySignal]);
|
|
32
58
|
};
|
|
@@ -623,8 +623,14 @@ describe("AISDKToolkit", () => {
|
|
|
623
623
|
toolCallId: "call-docs-search",
|
|
624
624
|
messages: [],
|
|
625
625
|
};
|
|
626
|
+
const execute = toolSet.docs_search?.execute as
|
|
627
|
+
| ((
|
|
628
|
+
args: { query: string },
|
|
629
|
+
options: typeof executeOptions,
|
|
630
|
+
) => Promise<string>)
|
|
631
|
+
| undefined;
|
|
626
632
|
await expect(
|
|
627
|
-
|
|
633
|
+
execute?.({ query: "assistant-ui" }, executeOptions),
|
|
628
634
|
).resolves.toBe("docs result");
|
|
629
635
|
expect(docsExecute).toHaveBeenCalledWith(
|
|
630
636
|
{ query: "assistant-ui" },
|
package/src/usage.ts
CHANGED