@assistant-ui/react 0.14.28 → 0.14.29
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/dist/client/ExternalThread.d.ts +11 -1
- package/dist/client/ExternalThread.d.ts.map +1 -1
- package/dist/client/ExternalThread.js +758 -513
- package/dist/client/ExternalThread.js.map +1 -1
- package/dist/legacy-runtime/cloud/auiV0.d.ts +12 -1
- package/dist/legacy-runtime/cloud/auiV0.d.ts.map +1 -1
- package/dist/legacy-runtime/cloud/auiV0.js +2 -1
- package/dist/legacy-runtime/cloud/auiV0.js.map +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.d.ts +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.js +7 -6
- package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.js.map +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/runManager.d.ts.map +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/runManager.js +30 -15
- package/dist/legacy-runtime/runtime-cores/assistant-transport/runManager.js.map +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.d.ts.map +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js +8 -4
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js.map +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.js +1 -0
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.js.map +1 -1
- package/dist/primitives/actionBar/ActionBarRoot.js +3 -3
- package/dist/primitives/actionBar/ActionBarRoot.js.map +1 -1
- package/dist/primitives/actionBar/useActionBarFloatStatus.d.ts +7 -6
- package/dist/primitives/actionBar/useActionBarFloatStatus.d.ts.map +1 -1
- package/dist/primitives/actionBar/useActionBarFloatStatus.js +10 -11
- package/dist/primitives/actionBar/useActionBarFloatStatus.js.map +1 -1
- package/dist/primitives/attachment/AttachmentRemove.js +1 -0
- package/dist/primitives/attachment/AttachmentRemove.js.map +1 -1
- package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.js +1 -0
- package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.js.map +1 -1
- package/dist/primitives/composer/ComposerAddAttachment.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerAddAttachment.js +8 -2
- package/dist/primitives/composer/ComposerAddAttachment.js.map +1 -1
- package/dist/primitives/composer/ComposerAttachmentDropzone.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerAttachmentDropzone.js +1 -3
- package/dist/primitives/composer/ComposerAttachmentDropzone.js.map +1 -1
- package/dist/primitives/composer/ComposerInput.js +6 -4
- package/dist/primitives/composer/ComposerInput.js.map +1 -1
- package/dist/primitives/composer/ComposerSend.js +1 -0
- package/dist/primitives/composer/ComposerSend.js.map +1 -1
- package/dist/primitives/composer/ComposerStopDictation.js +1 -0
- package/dist/primitives/composer/ComposerStopDictation.js.map +1 -1
- package/dist/primitives/queueItem/QueueItemRemove.js +1 -0
- package/dist/primitives/queueItem/QueueItemRemove.js.map +1 -1
- package/dist/primitives/queueItem/QueueItemSteer.js +1 -0
- package/dist/primitives/queueItem/QueueItemSteer.js.map +1 -1
- package/dist/primitives/suggestion/SuggestionTrigger.js +1 -0
- package/dist/primitives/suggestion/SuggestionTrigger.js.map +1 -1
- package/dist/primitives/thread/ThreadScrollToBottom.js +1 -0
- package/dist/primitives/thread/ThreadScrollToBottom.js.map +1 -1
- package/dist/utils/hooks/useOnResizeContent.js +1 -0
- package/dist/utils/hooks/useOnResizeContent.js.map +1 -1
- package/dist/utils/hooks/useSizeHandle.js +1 -0
- package/dist/utils/hooks/useSizeHandle.js.map +1 -1
- package/dist/utils/smooth/useSmooth.d.ts.map +1 -1
- package/dist/utils/smooth/useSmooth.js +2 -2
- package/dist/utils/smooth/useSmooth.js.map +1 -1
- package/package.json +9 -9
- package/src/client/ExternalThread.ts +340 -53
- package/src/legacy-runtime/cloud/auiV0.ts +14 -0
- package/src/legacy-runtime/runtime-cores/assistant-transport/commandQueue.ts +4 -1
- package/src/legacy-runtime/runtime-cores/assistant-transport/runManager.ts +37 -10
- package/src/legacy-runtime/runtime-cores/assistant-transport/transport-delivery.test.tsx +79 -0
- package/src/legacy-runtime/runtime-cores/assistant-transport/transport-parentid.test.tsx +97 -0
- package/src/legacy-runtime/runtime-cores/assistant-transport/transport-scheduling.test.ts +113 -2
- package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransport.spec.md +10 -2
- package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.test.tsx +189 -0
- package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.ts +20 -4
- package/src/primitives/actionBar/useActionBarFloatStatus.ts +7 -5
- package/src/primitives/composer/ComposerAddAttachment.test.tsx +108 -0
- package/src/primitives/composer/ComposerAddAttachment.ts +10 -4
- package/src/primitives/composer/ComposerAttachmentDropzone.test.tsx +3 -2
- package/src/primitives/composer/ComposerAttachmentDropzone.tsx +3 -2
- package/src/primitives/composer/ComposerInput.test.tsx +98 -1
- package/src/primitives/composer/ComposerInput.tsx +10 -8
- package/src/tests/DataRenderers.test.tsx +212 -0
- package/src/tests/RemoteThreadListRuntime.adapterProvider.test.tsx +1 -11
- package/src/tests/external-thread-attachments.test.tsx +292 -1
- package/src/tests/external-thread-parity.test.tsx +232 -0
- package/src/tests/generative-ui.test.tsx +1 -46
- package/src/tests/messagePartSwitchRace.test.tsx +25 -11
- package/src/tests/messagePartTypeMismatch.test.tsx +2 -2
- package/src/tests/threadMessageById.test.tsx +0 -7
- package/src/utils/smooth/useSmooth.ts +7 -4
- package/src/tests/BaseComposerRuntimeCore.test.ts +0 -621
- package/src/tests/BaseSubscribable.test.ts +0 -54
- package/src/tests/DataRenderers.test.ts +0 -355
- package/src/tests/ExternalStoreThreadRuntimeCore.test.ts +0 -685
- package/src/tests/LocalThreadRuntimeCore.test.ts +0 -324
- package/src/tests/auiV0Encode.test.ts +0 -188
- package/src/tests/external-message-converter.test.ts +0 -346
- package/src/tests/shouldContinue.test.ts +0 -122
|
@@ -1,346 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { convertExternalMessages } from "../legacy-runtime/runtime-cores/external-store/external-message-converter";
|
|
3
|
-
import type { useExternalMessageConverter } from "../legacy-runtime/runtime-cores/external-store/external-message-converter";
|
|
4
|
-
import { isErrorMessageId } from "@assistant-ui/core/internal";
|
|
5
|
-
|
|
6
|
-
describe("convertExternalMessages", () => {
|
|
7
|
-
describe("reasoning part merging", () => {
|
|
8
|
-
it("should merge reasoning parts with the same parentId", () => {
|
|
9
|
-
const messages = [
|
|
10
|
-
{
|
|
11
|
-
id: "msg1",
|
|
12
|
-
role: "assistant" as const,
|
|
13
|
-
content: [
|
|
14
|
-
{
|
|
15
|
-
type: "reasoning" as const,
|
|
16
|
-
text: "First reasoning",
|
|
17
|
-
parentId: "parent1",
|
|
18
|
-
},
|
|
19
|
-
],
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
id: "msg2",
|
|
23
|
-
role: "assistant" as const,
|
|
24
|
-
content: [
|
|
25
|
-
{
|
|
26
|
-
type: "reasoning" as const,
|
|
27
|
-
text: "Second reasoning",
|
|
28
|
-
parentId: "parent1",
|
|
29
|
-
},
|
|
30
|
-
],
|
|
31
|
-
},
|
|
32
|
-
];
|
|
33
|
-
|
|
34
|
-
const callback: useExternalMessageConverter.Callback<
|
|
35
|
-
(typeof messages)[number]
|
|
36
|
-
> = (msg) => msg;
|
|
37
|
-
|
|
38
|
-
const result = convertExternalMessages(messages, callback, false, {});
|
|
39
|
-
|
|
40
|
-
expect(result).toHaveLength(1);
|
|
41
|
-
expect(result[0]!.role).toBe("assistant");
|
|
42
|
-
|
|
43
|
-
const reasoningParts = result[0]!.content.filter(
|
|
44
|
-
(p) => p.type === "reasoning",
|
|
45
|
-
);
|
|
46
|
-
expect(reasoningParts).toHaveLength(1);
|
|
47
|
-
expect((reasoningParts[0] as any).text).toBe(
|
|
48
|
-
"First reasoning\n\nSecond reasoning",
|
|
49
|
-
);
|
|
50
|
-
expect((reasoningParts[0] as any).parentId).toBe("parent1");
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it("should keep reasoning parts without parentId separate", () => {
|
|
54
|
-
const messages = [
|
|
55
|
-
{
|
|
56
|
-
id: "msg1",
|
|
57
|
-
role: "assistant" as const,
|
|
58
|
-
content: [{ type: "reasoning" as const, text: "First reasoning" }],
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
id: "msg2",
|
|
62
|
-
role: "assistant" as const,
|
|
63
|
-
content: [{ type: "reasoning" as const, text: "Second reasoning" }],
|
|
64
|
-
},
|
|
65
|
-
];
|
|
66
|
-
|
|
67
|
-
const callback: useExternalMessageConverter.Callback<
|
|
68
|
-
(typeof messages)[number]
|
|
69
|
-
> = (msg) => msg;
|
|
70
|
-
|
|
71
|
-
const result = convertExternalMessages(messages, callback, false, {});
|
|
72
|
-
|
|
73
|
-
expect(result).toHaveLength(1);
|
|
74
|
-
|
|
75
|
-
const reasoningParts = result[0]!.content.filter(
|
|
76
|
-
(p) => p.type === "reasoning",
|
|
77
|
-
);
|
|
78
|
-
expect(reasoningParts).toHaveLength(2);
|
|
79
|
-
expect((reasoningParts[0] as any).text).toBe("First reasoning");
|
|
80
|
-
expect((reasoningParts[1] as any).text).toBe("Second reasoning");
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it("should keep reasoning parts with different parentIds separate", () => {
|
|
84
|
-
const messages = [
|
|
85
|
-
{
|
|
86
|
-
id: "msg1",
|
|
87
|
-
role: "assistant" as const,
|
|
88
|
-
content: [
|
|
89
|
-
{
|
|
90
|
-
type: "reasoning" as const,
|
|
91
|
-
text: "Reasoning for parent1",
|
|
92
|
-
parentId: "parent1",
|
|
93
|
-
},
|
|
94
|
-
],
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
id: "msg2",
|
|
98
|
-
role: "assistant" as const,
|
|
99
|
-
content: [
|
|
100
|
-
{
|
|
101
|
-
type: "reasoning" as const,
|
|
102
|
-
text: "Reasoning for parent2",
|
|
103
|
-
parentId: "parent2",
|
|
104
|
-
},
|
|
105
|
-
],
|
|
106
|
-
},
|
|
107
|
-
];
|
|
108
|
-
|
|
109
|
-
const callback: useExternalMessageConverter.Callback<
|
|
110
|
-
(typeof messages)[number]
|
|
111
|
-
> = (msg) => msg;
|
|
112
|
-
|
|
113
|
-
const result = convertExternalMessages(messages, callback, false, {});
|
|
114
|
-
|
|
115
|
-
expect(result).toHaveLength(1);
|
|
116
|
-
|
|
117
|
-
const reasoningParts = result[0]!.content.filter(
|
|
118
|
-
(p) => p.type === "reasoning",
|
|
119
|
-
);
|
|
120
|
-
expect(reasoningParts).toHaveLength(2);
|
|
121
|
-
expect((reasoningParts[0] as any).parentId).toBe("parent1");
|
|
122
|
-
expect((reasoningParts[1] as any).parentId).toBe("parent2");
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
it("should still merge tool results with matching tool calls", () => {
|
|
126
|
-
const messages = [
|
|
127
|
-
{
|
|
128
|
-
id: "msg1",
|
|
129
|
-
role: "assistant" as const,
|
|
130
|
-
content: [
|
|
131
|
-
{
|
|
132
|
-
type: "tool-call" as const,
|
|
133
|
-
toolCallId: "tc1",
|
|
134
|
-
toolName: "search",
|
|
135
|
-
args: { query: "test" },
|
|
136
|
-
argsText: '{"query":"test"}',
|
|
137
|
-
},
|
|
138
|
-
],
|
|
139
|
-
},
|
|
140
|
-
{
|
|
141
|
-
role: "tool" as const,
|
|
142
|
-
toolCallId: "tc1",
|
|
143
|
-
result: { data: "result" },
|
|
144
|
-
},
|
|
145
|
-
];
|
|
146
|
-
|
|
147
|
-
const callback: useExternalMessageConverter.Callback<
|
|
148
|
-
(typeof messages)[number]
|
|
149
|
-
> = (msg) => msg;
|
|
150
|
-
|
|
151
|
-
const result = convertExternalMessages(messages, callback, false, {});
|
|
152
|
-
|
|
153
|
-
expect(result).toHaveLength(1);
|
|
154
|
-
|
|
155
|
-
const toolCallParts = result[0]!.content.filter(
|
|
156
|
-
(p) => p.type === "tool-call",
|
|
157
|
-
);
|
|
158
|
-
expect(toolCallParts).toHaveLength(1);
|
|
159
|
-
expect((toolCallParts[0] as any).result).toEqual({ data: "result" });
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
it("should merge duplicate tool calls by toolCallId across assistant messages", () => {
|
|
163
|
-
const messages = [
|
|
164
|
-
{
|
|
165
|
-
id: "msg1",
|
|
166
|
-
role: "assistant" as const,
|
|
167
|
-
content: [
|
|
168
|
-
{
|
|
169
|
-
type: "tool-call" as const,
|
|
170
|
-
toolCallId: "tc1",
|
|
171
|
-
toolName: "search",
|
|
172
|
-
args: { query: "old" },
|
|
173
|
-
argsText: '{"query":"old"',
|
|
174
|
-
},
|
|
175
|
-
],
|
|
176
|
-
},
|
|
177
|
-
{
|
|
178
|
-
id: "msg2",
|
|
179
|
-
role: "assistant" as const,
|
|
180
|
-
content: [
|
|
181
|
-
{
|
|
182
|
-
type: "tool-call" as const,
|
|
183
|
-
toolCallId: "tc1",
|
|
184
|
-
toolName: "search",
|
|
185
|
-
args: { query: "new" },
|
|
186
|
-
argsText: '{"query":"new"}',
|
|
187
|
-
},
|
|
188
|
-
],
|
|
189
|
-
},
|
|
190
|
-
];
|
|
191
|
-
|
|
192
|
-
const callback: useExternalMessageConverter.Callback<
|
|
193
|
-
(typeof messages)[number]
|
|
194
|
-
> = (msg) => msg;
|
|
195
|
-
|
|
196
|
-
const result = convertExternalMessages(messages, callback, false, {});
|
|
197
|
-
|
|
198
|
-
expect(result).toHaveLength(1);
|
|
199
|
-
expect(result[0]!.role).toBe("assistant");
|
|
200
|
-
const toolCallParts = result[0]!.content.filter(
|
|
201
|
-
(p) => p.type === "tool-call",
|
|
202
|
-
);
|
|
203
|
-
expect(toolCallParts).toHaveLength(1);
|
|
204
|
-
expect((toolCallParts[0] as any).args).toEqual({ query: "new" });
|
|
205
|
-
expect((toolCallParts[0] as any).argsText).toBe('{"query":"new"}');
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
it("should ignore orphaned tool results without throwing", () => {
|
|
209
|
-
const messages = [
|
|
210
|
-
{
|
|
211
|
-
id: "msg1",
|
|
212
|
-
role: "assistant" as const,
|
|
213
|
-
content: "First response",
|
|
214
|
-
},
|
|
215
|
-
{
|
|
216
|
-
role: "tool" as const,
|
|
217
|
-
toolCallId: "missing-tool-call",
|
|
218
|
-
toolName: "search",
|
|
219
|
-
result: { data: "orphan result" },
|
|
220
|
-
},
|
|
221
|
-
{
|
|
222
|
-
id: "msg2",
|
|
223
|
-
role: "assistant" as const,
|
|
224
|
-
content: "Second response",
|
|
225
|
-
},
|
|
226
|
-
];
|
|
227
|
-
|
|
228
|
-
const callback: useExternalMessageConverter.Callback<
|
|
229
|
-
(typeof messages)[number]
|
|
230
|
-
> = (msg) => msg;
|
|
231
|
-
|
|
232
|
-
const result = convertExternalMessages(messages, callback, false, {});
|
|
233
|
-
expect(result).toHaveLength(1);
|
|
234
|
-
expect(result[0]!.role).toBe("assistant");
|
|
235
|
-
|
|
236
|
-
const textParts = result[0]!.content.filter((p) => p.type === "text");
|
|
237
|
-
expect(textParts).toHaveLength(2);
|
|
238
|
-
expect((textParts[0] as any).text).toBe("First response");
|
|
239
|
-
expect((textParts[1] as any).text).toBe("Second response");
|
|
240
|
-
});
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
describe("synthetic error message", () => {
|
|
244
|
-
it("should create synthetic error message when error exists and no messages", () => {
|
|
245
|
-
const messages: never[] = [];
|
|
246
|
-
const callback: useExternalMessageConverter.Callback<never> = (msg) =>
|
|
247
|
-
msg;
|
|
248
|
-
|
|
249
|
-
const result = convertExternalMessages(messages, callback, false, {
|
|
250
|
-
error: "API key is missing",
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
expect(result).toHaveLength(1);
|
|
254
|
-
expect(result[0]!.role).toBe("assistant");
|
|
255
|
-
expect(result[0]!.content).toHaveLength(0);
|
|
256
|
-
expect(result[0]!.status).toEqual({
|
|
257
|
-
type: "incomplete",
|
|
258
|
-
reason: "error",
|
|
259
|
-
error: "API key is missing",
|
|
260
|
-
});
|
|
261
|
-
expect(isErrorMessageId(result[0]!.id)).toBe(true);
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
it("should create synthetic error message when error exists and last message is user", () => {
|
|
265
|
-
const messages = [
|
|
266
|
-
{
|
|
267
|
-
id: "user1",
|
|
268
|
-
role: "user" as const,
|
|
269
|
-
content: "Hello",
|
|
270
|
-
},
|
|
271
|
-
];
|
|
272
|
-
|
|
273
|
-
const callback: useExternalMessageConverter.Callback<
|
|
274
|
-
(typeof messages)[number]
|
|
275
|
-
> = (msg) => msg;
|
|
276
|
-
|
|
277
|
-
const result = convertExternalMessages(messages, callback, false, {
|
|
278
|
-
error: { message: "Invalid API key" },
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
expect(result).toHaveLength(2);
|
|
282
|
-
expect(result[0]!.role).toBe("user");
|
|
283
|
-
expect(result[1]!.role).toBe("assistant");
|
|
284
|
-
expect(result[1]!.content).toHaveLength(0);
|
|
285
|
-
expect(result[1]!.status).toEqual({
|
|
286
|
-
type: "incomplete",
|
|
287
|
-
reason: "error",
|
|
288
|
-
error: { message: "Invalid API key" },
|
|
289
|
-
});
|
|
290
|
-
expect(isErrorMessageId(result[1]!.id)).toBe(true);
|
|
291
|
-
});
|
|
292
|
-
|
|
293
|
-
it("should not create synthetic error message when last message is assistant", () => {
|
|
294
|
-
const messages = [
|
|
295
|
-
{
|
|
296
|
-
id: "user1",
|
|
297
|
-
role: "user" as const,
|
|
298
|
-
content: "Hello",
|
|
299
|
-
},
|
|
300
|
-
{
|
|
301
|
-
id: "assistant1",
|
|
302
|
-
role: "assistant" as const,
|
|
303
|
-
content: "Hi there",
|
|
304
|
-
},
|
|
305
|
-
];
|
|
306
|
-
|
|
307
|
-
const callback: useExternalMessageConverter.Callback<
|
|
308
|
-
(typeof messages)[number]
|
|
309
|
-
> = (msg) => msg;
|
|
310
|
-
|
|
311
|
-
const result = convertExternalMessages(messages, callback, false, {
|
|
312
|
-
error: "Connection error",
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
expect(result).toHaveLength(2);
|
|
316
|
-
expect(result[0]!.role).toBe("user");
|
|
317
|
-
expect(result[1]!.role).toBe("assistant");
|
|
318
|
-
expect(result[1]!.id).toBe("assistant1");
|
|
319
|
-
expect(result[1]!.status).toMatchObject({
|
|
320
|
-
type: "incomplete",
|
|
321
|
-
reason: "error",
|
|
322
|
-
error: "Connection error",
|
|
323
|
-
});
|
|
324
|
-
expect(isErrorMessageId(result[1]!.id)).toBe(false);
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
it("should not create synthetic message when no error", () => {
|
|
328
|
-
const messages = [
|
|
329
|
-
{
|
|
330
|
-
id: "user1",
|
|
331
|
-
role: "user" as const,
|
|
332
|
-
content: "Hello",
|
|
333
|
-
},
|
|
334
|
-
];
|
|
335
|
-
|
|
336
|
-
const callback: useExternalMessageConverter.Callback<
|
|
337
|
-
(typeof messages)[number]
|
|
338
|
-
> = (msg) => msg;
|
|
339
|
-
|
|
340
|
-
const result = convertExternalMessages(messages, callback, false, {});
|
|
341
|
-
|
|
342
|
-
expect(result).toHaveLength(1);
|
|
343
|
-
expect(result[0]!.role).toBe("user");
|
|
344
|
-
});
|
|
345
|
-
});
|
|
346
|
-
});
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
import type { ThreadAssistantMessage } from "@assistant-ui/core";
|
|
2
|
-
import { describe, expect, it } from "vitest";
|
|
3
|
-
import { shouldContinue } from "../legacy-runtime/runtime-cores/local/shouldContinue";
|
|
4
|
-
|
|
5
|
-
const makeMessage = (
|
|
6
|
-
overrides: Partial<ThreadAssistantMessage>,
|
|
7
|
-
): ThreadAssistantMessage => ({
|
|
8
|
-
id: "msg-1",
|
|
9
|
-
role: "assistant",
|
|
10
|
-
createdAt: new Date(),
|
|
11
|
-
content: [],
|
|
12
|
-
status: { type: "complete", reason: "stop" },
|
|
13
|
-
metadata: {
|
|
14
|
-
unstable_state: null,
|
|
15
|
-
unstable_annotations: [],
|
|
16
|
-
unstable_data: [],
|
|
17
|
-
steps: [],
|
|
18
|
-
custom: {},
|
|
19
|
-
},
|
|
20
|
-
...overrides,
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
const toolCall = (toolName: string, result?: unknown) => ({
|
|
24
|
-
type: "tool-call" as const,
|
|
25
|
-
toolCallId: `call-${toolName}`,
|
|
26
|
-
toolName,
|
|
27
|
-
args: {},
|
|
28
|
-
argsText: "{}",
|
|
29
|
-
...(result !== undefined ? { result } : {}),
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
describe("shouldContinue", () => {
|
|
33
|
-
it("returns false when status is not requires-action", () => {
|
|
34
|
-
const msg = makeMessage({
|
|
35
|
-
status: { type: "complete", reason: "stop" },
|
|
36
|
-
});
|
|
37
|
-
expect(shouldContinue(msg, undefined)).toBe(false);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it("returns false when reason is not tool-calls", () => {
|
|
41
|
-
const msg = makeMessage({
|
|
42
|
-
status: { type: "requires-action", reason: "interrupt" },
|
|
43
|
-
});
|
|
44
|
-
expect(shouldContinue(msg, undefined)).toBe(false);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it("returns true when all tool calls have results (no humanToolNames)", () => {
|
|
48
|
-
const msg = makeMessage({
|
|
49
|
-
status: { type: "requires-action", reason: "tool-calls" },
|
|
50
|
-
content: [toolCall("search", "found it"), toolCall("calculate", 42)],
|
|
51
|
-
});
|
|
52
|
-
expect(shouldContinue(msg, undefined)).toBe(true);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it("returns false when some tool calls have no result (no humanToolNames)", () => {
|
|
56
|
-
const msg = makeMessage({
|
|
57
|
-
status: { type: "requires-action", reason: "tool-calls" },
|
|
58
|
-
content: [toolCall("search", "found it"), toolCall("calculate")],
|
|
59
|
-
});
|
|
60
|
-
expect(shouldContinue(msg, undefined)).toBe(false);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it("returns true when unresolved tool call is not a human tool", () => {
|
|
64
|
-
const msg = makeMessage({
|
|
65
|
-
status: { type: "requires-action", reason: "tool-calls" },
|
|
66
|
-
content: [toolCall("auto-tool")],
|
|
67
|
-
});
|
|
68
|
-
expect(shouldContinue(msg, ["human-approval"])).toBe(true);
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it("returns false when unresolved tool call IS a human tool", () => {
|
|
72
|
-
const msg = makeMessage({
|
|
73
|
-
status: { type: "requires-action", reason: "tool-calls" },
|
|
74
|
-
content: [toolCall("human-approval")],
|
|
75
|
-
});
|
|
76
|
-
expect(shouldContinue(msg, ["human-approval"])).toBe(false);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it("returns true for non-tool-call content parts", () => {
|
|
80
|
-
const msg = makeMessage({
|
|
81
|
-
status: { type: "requires-action", reason: "tool-calls" },
|
|
82
|
-
content: [
|
|
83
|
-
{ type: "text", text: "Hello" } as any,
|
|
84
|
-
toolCall("search", "done"),
|
|
85
|
-
],
|
|
86
|
-
});
|
|
87
|
-
expect(shouldContinue(msg, undefined)).toBe(true);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it("returns false while a tool call has a pending approval", () => {
|
|
91
|
-
const msg = makeMessage({
|
|
92
|
-
status: { type: "requires-action", reason: "tool-calls" },
|
|
93
|
-
content: [{ ...toolCall("deploy"), approval: { id: "a1" } }],
|
|
94
|
-
});
|
|
95
|
-
expect(shouldContinue(msg, undefined)).toBe(false);
|
|
96
|
-
expect(shouldContinue(msg, ["human-approval"])).toBe(false);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it("returns true when a decided approval has no result", () => {
|
|
100
|
-
const msg = makeMessage({
|
|
101
|
-
status: { type: "requires-action", reason: "tool-calls" },
|
|
102
|
-
content: [
|
|
103
|
-
{ ...toolCall("deploy"), approval: { id: "a1", approved: true } },
|
|
104
|
-
],
|
|
105
|
-
});
|
|
106
|
-
expect(shouldContinue(msg, undefined)).toBe(true);
|
|
107
|
-
expect(shouldContinue(msg, ["human-approval"])).toBe(true);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it("exempts approval-gated tool calls from the human tool result requirement", () => {
|
|
111
|
-
const msg = makeMessage({
|
|
112
|
-
status: { type: "requires-action", reason: "tool-calls" },
|
|
113
|
-
content: [
|
|
114
|
-
{
|
|
115
|
-
...toolCall("human-approval"),
|
|
116
|
-
approval: { id: "a1", approved: true },
|
|
117
|
-
},
|
|
118
|
-
],
|
|
119
|
-
});
|
|
120
|
-
expect(shouldContinue(msg, ["human-approval"])).toBe(true);
|
|
121
|
-
});
|
|
122
|
-
});
|