@assistant-ui/ai-sdk 0.0.8 → 0.0.10
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/LICENSE +1 -1
- package/dist/converters/convertMessage.d.ts +2 -0
- package/dist/converters/convertMessage.d.ts.map +1 -1
- package/dist/converters/convertMessage.js +15 -4
- package/dist/converters/convertMessage.js.map +1 -1
- package/dist/converters/toCreateMessage.d.ts.map +1 -1
- package/dist/converters/toCreateMessage.js +2 -1
- package/dist/converters/toCreateMessage.js.map +1 -1
- package/dist/runtime/AISDKChat.d.ts.map +1 -1
- package/dist/runtime/AISDKChat.js.map +1 -1
- package/dist/runtime/AISDKThreads.js.map +1 -1
- package/dist/runtime/sdkIdentity.js +1 -1
- package/dist/runtime/useAISDKRuntime.d.ts +8 -1
- package/dist/runtime/useAISDKRuntime.d.ts.map +1 -1
- package/dist/runtime/useAISDKRuntime.js +212 -34
- package/dist/runtime/useAISDKRuntime.js.map +1 -1
- package/dist/runtime/useChatRuntime.js +1 -1
- package/dist/runtime/useChatRuntime.js.map +1 -1
- package/dist/runtime/useChatThread.d.ts.map +1 -1
- package/dist/runtime/useChatThread.js +40 -10
- package/dist/runtime/useChatThread.js.map +1 -1
- package/dist/runtime/useExternalHistory.d.ts.map +1 -1
- package/dist/runtime/useExternalHistory.js +2 -1
- package/dist/runtime/useExternalHistory.js.map +1 -1
- package/dist/runtime/useResourceCleanup.js.map +1 -1
- package/dist/runtime/useStreamingTiming.js.map +1 -1
- package/dist/transport/AssistantChatTransport.d.ts.map +1 -1
- package/dist/transport/AssistantChatTransport.js +9 -2
- package/dist/transport/AssistantChatTransport.js.map +1 -1
- package/dist/transport/resumable.js.map +1 -1
- package/dist/usage.js.map +1 -1
- package/package.json +15 -12
- package/src/converters/convertMessage.test.ts +99 -0
- package/src/converters/convertMessage.ts +27 -2
- package/src/converters/toCreateMessage.test.ts +13 -0
- package/src/converters/toCreateMessage.ts +1 -0
- package/src/runtime/AISDKChat.ts +0 -4
- package/src/runtime/AISDKThreads.test.ts +26 -0
- package/src/runtime/useAISDKRuntime.approval.integration.test.tsx +1588 -19
- package/src/runtime/useAISDKRuntime.approval.test.tsx +27 -0
- package/src/runtime/useAISDKRuntime.fast-refresh.test.tsx +182 -0
- package/src/runtime/useAISDKRuntime.ts +397 -28
- package/src/runtime/useChatRuntime.fast-refresh.test.tsx +94 -0
- package/src/runtime/useChatRuntime.integration.test.tsx +134 -112
- package/src/runtime/useChatRuntime.test.ts +7 -7
- package/src/runtime/useChatThread.test.ts +166 -2
- package/src/runtime/useChatThread.transport.test.tsx +5 -2
- package/src/runtime/useChatThread.ts +56 -17
- package/src/runtime/useExternalHistory.ts +12 -1
- package/src/transport/AssistantChatTransport.test.ts +164 -0
- package/src/transport/AssistantChatTransport.ts +22 -2
|
@@ -970,6 +970,69 @@ describe("AISDKMessageConverter", () => {
|
|
|
970
970
|
expect(toolCall?.argsText).toBe('{"city":"NYC');
|
|
971
971
|
});
|
|
972
972
|
|
|
973
|
+
it("strips exactly the trailing run of closing characters", () => {
|
|
974
|
+
const convertArgsText = (text: string) => {
|
|
975
|
+
stableStringifySpy.mockReturnValueOnce(text);
|
|
976
|
+
const converted = AISDKMessageConverter.toThreadMessages([
|
|
977
|
+
{
|
|
978
|
+
id: "a1",
|
|
979
|
+
role: "assistant",
|
|
980
|
+
parts: [
|
|
981
|
+
{
|
|
982
|
+
type: "tool-weather",
|
|
983
|
+
toolCallId: "tc-1",
|
|
984
|
+
state: "input-streaming",
|
|
985
|
+
input: {},
|
|
986
|
+
},
|
|
987
|
+
],
|
|
988
|
+
},
|
|
989
|
+
] as any);
|
|
990
|
+
return (converted[0]?.content[0] as any)?.argsText;
|
|
991
|
+
};
|
|
992
|
+
const alphabet = ["}", "]", '"', "a", "\\", "{", "[", "\n", " ", "😀"];
|
|
993
|
+
let seed = 7;
|
|
994
|
+
const next = () => (seed = (seed * 48271) % 0x7fffffff);
|
|
995
|
+
const texts = ["", "}", '"]}', '{"a":"x"}', '{"a":"}}x"}', "\uD800}"];
|
|
996
|
+
for (let i = 0; i < 300; i++) {
|
|
997
|
+
texts.push(
|
|
998
|
+
Array.from(
|
|
999
|
+
{ length: next() % 12 },
|
|
1000
|
+
() => alphabet[next() % alphabet.length],
|
|
1001
|
+
).join(""),
|
|
1002
|
+
);
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
for (const text of texts) {
|
|
1006
|
+
expect(convertArgsText(text)).toBe(text.replace(/[}\]"]+$/, ""));
|
|
1007
|
+
}
|
|
1008
|
+
});
|
|
1009
|
+
|
|
1010
|
+
it(
|
|
1011
|
+
"strips a long run of closing characters inside streaming input",
|
|
1012
|
+
{ timeout: 5_000 },
|
|
1013
|
+
() => {
|
|
1014
|
+
const run = "}".repeat(200_000);
|
|
1015
|
+
const converted = AISDKMessageConverter.toThreadMessages([
|
|
1016
|
+
{
|
|
1017
|
+
id: "a1",
|
|
1018
|
+
role: "assistant",
|
|
1019
|
+
parts: [
|
|
1020
|
+
{
|
|
1021
|
+
type: "tool-weather",
|
|
1022
|
+
toolCallId: "tc-1",
|
|
1023
|
+
state: "input-streaming",
|
|
1024
|
+
input: { code: `${run}x` },
|
|
1025
|
+
},
|
|
1026
|
+
],
|
|
1027
|
+
},
|
|
1028
|
+
] as any);
|
|
1029
|
+
|
|
1030
|
+
expect((converted[0]?.content[0] as any)?.argsText).toBe(
|
|
1031
|
+
`{"code":"${run}x`,
|
|
1032
|
+
);
|
|
1033
|
+
},
|
|
1034
|
+
);
|
|
1035
|
+
|
|
973
1036
|
it("attaches partial-JSON meta marking the trailing streaming field", () => {
|
|
974
1037
|
const converted = AISDKMessageConverter.toThreadMessages([
|
|
975
1038
|
{
|
|
@@ -1420,6 +1483,42 @@ describe("AISDKMessageConverter", () => {
|
|
|
1420
1483
|
expect(converted[0]?.content[2]).not.toHaveProperty("providerMetadata");
|
|
1421
1484
|
});
|
|
1422
1485
|
|
|
1486
|
+
it("keeps a step scoped reasoning block id off the part", () => {
|
|
1487
|
+
const converted = AISDKMessageConverter.toThreadMessages([
|
|
1488
|
+
{
|
|
1489
|
+
id: "a1",
|
|
1490
|
+
role: "assistant",
|
|
1491
|
+
parts: [
|
|
1492
|
+
{ type: "reasoning", id: "reasoning-0", text: "first step" },
|
|
1493
|
+
{ type: "text", text: "answer" },
|
|
1494
|
+
{ type: "reasoning", id: "reasoning-0", text: "second step" },
|
|
1495
|
+
],
|
|
1496
|
+
} as any,
|
|
1497
|
+
]);
|
|
1498
|
+
|
|
1499
|
+
expect(converted[0]?.content[0]).toMatchObject({ type: "reasoning" });
|
|
1500
|
+
expect(converted[0]?.content[0]).not.toHaveProperty("id");
|
|
1501
|
+
expect(converted[0]?.content[1]).not.toHaveProperty("id");
|
|
1502
|
+
expect(converted[0]?.content[2]).not.toHaveProperty("id");
|
|
1503
|
+
});
|
|
1504
|
+
|
|
1505
|
+
it("forwards data part ids", () => {
|
|
1506
|
+
const converted = AISDKMessageConverter.toThreadMessages([
|
|
1507
|
+
{
|
|
1508
|
+
id: "a1",
|
|
1509
|
+
role: "assistant",
|
|
1510
|
+
parts: [{ type: "data-chart", id: "data-1", data: { x: 1 } }],
|
|
1511
|
+
} as any,
|
|
1512
|
+
]);
|
|
1513
|
+
|
|
1514
|
+
expect(converted[0]?.content[0]).toMatchObject({
|
|
1515
|
+
type: "data",
|
|
1516
|
+
id: "data-1",
|
|
1517
|
+
name: "chart",
|
|
1518
|
+
data: { x: 1 },
|
|
1519
|
+
});
|
|
1520
|
+
});
|
|
1521
|
+
|
|
1423
1522
|
it("maps TextUIPart.state onto the per-part status", () => {
|
|
1424
1523
|
const converted = AISDKMessageConverter.toThreadMessages([
|
|
1425
1524
|
{
|
|
@@ -76,12 +76,16 @@ export type AISDKMessageConverterMetadata =
|
|
|
76
76
|
toolInteractions?: ReadonlyMap<string, Unstable_ToolInteractionLog>;
|
|
77
77
|
supportsRichToolApprovalResponses?: boolean;
|
|
78
78
|
toolApprovalResponses?: ReadonlyMap<string, RespondToToolApprovalOptions>;
|
|
79
|
+
cancelledToolApprovalIds?: ReadonlySet<string>;
|
|
80
|
+
cancelledStatusMessageIds?: ReadonlySet<string>;
|
|
79
81
|
/** Id of the currently-streaming message, flagged optimistic (#4037). */
|
|
80
82
|
optimisticMessageId?: string | undefined;
|
|
81
83
|
};
|
|
82
84
|
|
|
83
85
|
function stripClosingDelimiters(json: string): string {
|
|
84
|
-
|
|
86
|
+
let end = json.length;
|
|
87
|
+
while (end > 0 && '}]"'.includes(json[end - 1]!)) end--;
|
|
88
|
+
return json.slice(0, end);
|
|
85
89
|
}
|
|
86
90
|
|
|
87
91
|
const MCP_APP_METADATA_CACHE_MAX = 100;
|
|
@@ -258,15 +262,23 @@ function getToolApprovalAndInterrupt(
|
|
|
258
262
|
toolApprovalResponses:
|
|
259
263
|
| ReadonlyMap<string, RespondToToolApprovalOptions>
|
|
260
264
|
| undefined,
|
|
265
|
+
cancelledToolApprovalIds: ReadonlySet<string> | undefined,
|
|
261
266
|
): {
|
|
262
267
|
approval?: NonNullable<ToolCallMessagePart["approval"]>;
|
|
263
268
|
interrupt?: NonNullable<ToolCallMessagePart["interrupt"]>;
|
|
264
269
|
} {
|
|
265
270
|
if (part.approval) {
|
|
266
|
-
const
|
|
271
|
+
const rawApproval = {
|
|
267
272
|
...readApprovalDescriptor(part.approval.descriptor),
|
|
268
273
|
...part.approval,
|
|
269
274
|
};
|
|
275
|
+
const approval =
|
|
276
|
+
typeof rawApproval.id === "string" &&
|
|
277
|
+
rawApproval.approved === undefined &&
|
|
278
|
+
rawApproval.resolution === undefined &&
|
|
279
|
+
cancelledToolApprovalIds?.has(rawApproval.id)
|
|
280
|
+
? { ...rawApproval, resolution: "cancelled" as const }
|
|
281
|
+
: rawApproval;
|
|
270
282
|
const response =
|
|
271
283
|
typeof approval.id === "string" &&
|
|
272
284
|
approval.approved === undefined &&
|
|
@@ -523,6 +535,7 @@ function convertParts(
|
|
|
523
535
|
toolStatus,
|
|
524
536
|
metadata.supportsRichToolApprovalResponses === true,
|
|
525
537
|
metadata.toolApprovalResponses,
|
|
538
|
+
metadata.cancelledToolApprovalIds,
|
|
526
539
|
),
|
|
527
540
|
} satisfies ToolCallMessagePart;
|
|
528
541
|
}
|
|
@@ -572,6 +585,9 @@ function convertParts(
|
|
|
572
585
|
if (part.type.startsWith("data-")) {
|
|
573
586
|
return {
|
|
574
587
|
type: "data",
|
|
588
|
+
...("id" in part && typeof part.id === "string"
|
|
589
|
+
? { id: part.id }
|
|
590
|
+
: undefined),
|
|
575
591
|
name: part.type.substring(5),
|
|
576
592
|
data: (part as any).data,
|
|
577
593
|
} satisfies DataMessagePart;
|
|
@@ -661,6 +677,15 @@ export const AISDKMessageConverter = unstable_createMessageConverter(
|
|
|
661
677
|
id: message.id,
|
|
662
678
|
createdAt,
|
|
663
679
|
content,
|
|
680
|
+
...(message.role === "assistant" &&
|
|
681
|
+
metadata.cancelledStatusMessageIds?.has(message.id)
|
|
682
|
+
? {
|
|
683
|
+
status: {
|
|
684
|
+
type: "incomplete" as const,
|
|
685
|
+
reason: "cancelled" as const,
|
|
686
|
+
},
|
|
687
|
+
}
|
|
688
|
+
: undefined),
|
|
664
689
|
metadata: {
|
|
665
690
|
...toThreadMetadata(message.metadata),
|
|
666
691
|
...(timing && { timing }),
|
|
@@ -185,6 +185,19 @@ describe("toCreateMessage", () => {
|
|
|
185
185
|
]);
|
|
186
186
|
});
|
|
187
187
|
|
|
188
|
+
it("forwards a data part id", () => {
|
|
189
|
+
const message = {
|
|
190
|
+
...baseMessage,
|
|
191
|
+
content: [
|
|
192
|
+
{ type: "data", name: "workflow", id: "p1", data: { field: 1 } },
|
|
193
|
+
],
|
|
194
|
+
} as unknown as AppendMessage;
|
|
195
|
+
|
|
196
|
+
expect(toCreateMessage(message).parts).toEqual([
|
|
197
|
+
{ type: "data-workflow", id: "p1", data: { field: 1 } },
|
|
198
|
+
]);
|
|
199
|
+
});
|
|
200
|
+
|
|
188
201
|
it("converts a data part inside an attachment without throwing", () => {
|
|
189
202
|
const message = {
|
|
190
203
|
...baseMessage,
|
|
@@ -86,6 +86,7 @@ export const toCreateMessage = <UI_MESSAGE extends UIMessage = UIMessage>(
|
|
|
86
86
|
return {
|
|
87
87
|
type: `data-${part.name}`,
|
|
88
88
|
data: part.data,
|
|
89
|
+
...(part.id !== undefined && { id: part.id }),
|
|
89
90
|
};
|
|
90
91
|
default:
|
|
91
92
|
throw new Error(`Unsupported part type: ${part.type}`);
|
package/src/runtime/AISDKChat.ts
CHANGED
|
@@ -18,10 +18,6 @@ const useAISDKChat = <UI_MESSAGE extends UIMessage = UIMessage>(
|
|
|
18
18
|
options?: AISDKChatOptions<UI_MESSAGE>,
|
|
19
19
|
) => {
|
|
20
20
|
const [id] = useState(() => options?.id ?? generateId());
|
|
21
|
-
// The transport resolves the request id from the thread list item, falling
|
|
22
|
-
// back to the runtime's main item, whose id here is the external store's
|
|
23
|
-
// placeholder constant. The single thread of this entry is the chat itself,
|
|
24
|
-
// so the handed-over item initializes to the chat id.
|
|
25
21
|
const [threadListItem] = useState(() => ({
|
|
26
22
|
initialize: async () => ({ remoteId: id, externalId: undefined }),
|
|
27
23
|
}));
|
|
@@ -56,6 +56,32 @@ const chatCallbacks = () => ({
|
|
|
56
56
|
});
|
|
57
57
|
|
|
58
58
|
describe("AISDKThreads", () => {
|
|
59
|
+
it("emits the selected thread id when its composer sends", async () => {
|
|
60
|
+
const { transport } = createControlledTransport();
|
|
61
|
+
const handle = createAssistantClient(
|
|
62
|
+
AuiConfig({ threads: AISDKThreads({ transport }) }),
|
|
63
|
+
);
|
|
64
|
+
try {
|
|
65
|
+
handle.subscribe(() => {});
|
|
66
|
+
const aui = handle.getClient();
|
|
67
|
+
const sent = vi.fn();
|
|
68
|
+
aui.on({ scope: "thread", event: "composer.send" }, sent);
|
|
69
|
+
|
|
70
|
+
flushTapSync(() => aui.composer.setText("hello"));
|
|
71
|
+
flushTapSync(() => aui.composer.send());
|
|
72
|
+
|
|
73
|
+
await vi.waitFor(() => {
|
|
74
|
+
expect(sent).toHaveBeenCalledWith(
|
|
75
|
+
expect.objectContaining({
|
|
76
|
+
threadId: aui.threads.getState().mainThreadId,
|
|
77
|
+
}),
|
|
78
|
+
);
|
|
79
|
+
});
|
|
80
|
+
} finally {
|
|
81
|
+
handle.destroy();
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
59
85
|
it("runs one chat per thread and keeps histories isolated across switches", async () => {
|
|
60
86
|
const { transport, emit, close } = createControlledTransport();
|
|
61
87
|
const handle = createAssistantClient(
|