@assistant-ui/react 0.12.12 → 0.12.15
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/legacy-runtime/cloud/AssistantCloudThreadHistoryAdapter.d.ts.map +1 -1
- package/dist/legacy-runtime/cloud/AssistantCloudThreadHistoryAdapter.js +135 -27
- package/dist/legacy-runtime/cloud/AssistantCloudThreadHistoryAdapter.js.map +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.d.ts.map +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.js +143 -38
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.js.map +1 -1
- package/dist/legacy-runtime/runtime-cores/external-store/external-message-converter.d.ts.map +1 -1
- package/dist/legacy-runtime/runtime-cores/external-store/external-message-converter.js +21 -9
- package/dist/legacy-runtime/runtime-cores/external-store/external-message-converter.js.map +1 -1
- package/dist/primitives/actionBar/ActionBarInteractionContext.d.ts +6 -0
- package/dist/primitives/actionBar/ActionBarInteractionContext.d.ts.map +1 -0
- package/dist/primitives/actionBar/ActionBarInteractionContext.js +5 -0
- package/dist/primitives/actionBar/ActionBarInteractionContext.js.map +1 -0
- package/dist/primitives/actionBar/ActionBarRoot.d.ts.map +1 -1
- package/dist/primitives/actionBar/ActionBarRoot.js +18 -4
- package/dist/primitives/actionBar/ActionBarRoot.js.map +1 -1
- package/dist/primitives/actionBar/useActionBarFloatStatus.d.ts +2 -1
- package/dist/primitives/actionBar/useActionBarFloatStatus.d.ts.map +1 -1
- package/dist/primitives/actionBar/useActionBarFloatStatus.js +3 -2
- package/dist/primitives/actionBar/useActionBarFloatStatus.js.map +1 -1
- package/dist/primitives/actionBarMore/ActionBarMoreRoot.d.ts.map +1 -1
- package/dist/primitives/actionBarMore/ActionBarMoreRoot.js +35 -2
- package/dist/primitives/actionBarMore/ActionBarMoreRoot.js.map +1 -1
- package/dist/utils/json/is-json-equal.d.ts +2 -0
- package/dist/utils/json/is-json-equal.d.ts.map +1 -0
- package/dist/utils/json/is-json-equal.js +31 -0
- package/dist/utils/json/is-json-equal.js.map +1 -0
- package/dist/utils/json/is-json.d.ts +1 -0
- package/dist/utils/json/is-json.d.ts.map +1 -1
- package/dist/utils/json/is-json.js +5 -3
- package/dist/utils/json/is-json.js.map +1 -1
- package/package.json +7 -7
- package/src/legacy-runtime/cloud/AssistantCloudThreadHistoryAdapter.ts +179 -40
- package/src/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.test.ts +230 -4
- package/src/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.ts +191 -50
- package/src/legacy-runtime/runtime-cores/external-store/external-message-converter.ts +28 -10
- package/src/primitives/actionBar/ActionBarInteractionContext.ts +13 -0
- package/src/primitives/actionBar/ActionBarRoot.tsx +38 -8
- package/src/primitives/actionBar/useActionBarFloatStatus.ts +4 -1
- package/src/primitives/actionBarMore/ActionBarMoreRoot.tsx +52 -2
- package/src/tests/external-message-converter.test.ts +80 -0
- package/src/utils/json/is-json-equal.ts +48 -0
- package/src/utils/json/is-json.ts +6 -3
|
@@ -64,10 +64,12 @@ class AssistantCloudThreadHistoryAdapter implements ThreadHistoryAdapter {
|
|
|
64
64
|
stepTimestamps?: StepTimestamp[];
|
|
65
65
|
},
|
|
66
66
|
) {
|
|
67
|
-
const
|
|
68
|
-
|
|
67
|
+
const encodedRunMessages = items.map((item) =>
|
|
68
|
+
formatAdapter.encode(item),
|
|
69
|
+
);
|
|
70
|
+
adapter._reportRunTelemetry(
|
|
69
71
|
formatAdapter.format,
|
|
70
|
-
|
|
72
|
+
encodedRunMessages,
|
|
71
73
|
options,
|
|
72
74
|
);
|
|
73
75
|
},
|
|
@@ -109,9 +111,9 @@ class AssistantCloudThreadHistoryAdapter implements ThreadHistoryAdapter {
|
|
|
109
111
|
};
|
|
110
112
|
}
|
|
111
113
|
|
|
112
|
-
private
|
|
114
|
+
private _reportRunTelemetry<T>(
|
|
113
115
|
format: string,
|
|
114
|
-
|
|
116
|
+
runMessages: T[],
|
|
115
117
|
options?: {
|
|
116
118
|
durationMs?: number;
|
|
117
119
|
stepTimestamps?: StepTimestamp[];
|
|
@@ -122,7 +124,7 @@ class AssistantCloudThreadHistoryAdapter implements ThreadHistoryAdapter {
|
|
|
122
124
|
const remoteId = this.aui.threadListItem().getState().remoteId;
|
|
123
125
|
if (!remoteId) return;
|
|
124
126
|
|
|
125
|
-
const extracted =
|
|
127
|
+
const extracted = extractRunTelemetry(format, runMessages);
|
|
126
128
|
if (!extracted) return;
|
|
127
129
|
|
|
128
130
|
this._sendReport(
|
|
@@ -147,6 +149,8 @@ class AssistantCloudThreadHistoryAdapter implements ThreadHistoryAdapter {
|
|
|
147
149
|
stepTimestamps?: StepTimestamp[],
|
|
148
150
|
) {
|
|
149
151
|
const mergedSteps = mergeStepTimestamps(data.steps, stepTimestamps);
|
|
152
|
+
// Keep in sync with assistant-cloud createRunSchema
|
|
153
|
+
// (apps/aui-cloud-api/src/endpoints/runs/create.ts).
|
|
150
154
|
const initial: Parameters<typeof this.cloudRef.current.runs.report>[0] = {
|
|
151
155
|
thread_id: remoteId,
|
|
152
156
|
status: data.status,
|
|
@@ -161,6 +165,12 @@ class AssistantCloudThreadHistoryAdapter implements ThreadHistoryAdapter {
|
|
|
161
165
|
...(data.outputTokens != null
|
|
162
166
|
? { output_tokens: data.outputTokens }
|
|
163
167
|
: undefined),
|
|
168
|
+
...(data.reasoningTokens != null
|
|
169
|
+
? { reasoning_tokens: data.reasoningTokens }
|
|
170
|
+
: undefined),
|
|
171
|
+
...(data.cachedInputTokens != null
|
|
172
|
+
? { cached_input_tokens: data.cachedInputTokens }
|
|
173
|
+
: undefined),
|
|
164
174
|
...(durationMs != null ? { duration_ms: durationMs } : undefined),
|
|
165
175
|
...(data.outputText != null
|
|
166
176
|
? { output_text: data.outputText }
|
|
@@ -253,6 +263,8 @@ function buildToolCall(
|
|
|
253
263
|
type TelemetryStepData = {
|
|
254
264
|
input_tokens?: number;
|
|
255
265
|
output_tokens?: number;
|
|
266
|
+
reasoning_tokens?: number;
|
|
267
|
+
cached_input_tokens?: number;
|
|
256
268
|
tool_calls?: TelemetryToolCall[];
|
|
257
269
|
start_ms?: number;
|
|
258
270
|
end_ms?: number;
|
|
@@ -280,6 +292,8 @@ type TelemetryData = {
|
|
|
280
292
|
totalSteps?: number;
|
|
281
293
|
inputTokens?: number;
|
|
282
294
|
outputTokens?: number;
|
|
295
|
+
reasoningTokens?: number;
|
|
296
|
+
cachedInputTokens?: number;
|
|
283
297
|
outputText?: string;
|
|
284
298
|
metadata?: Record<string, unknown>;
|
|
285
299
|
steps?: TelemetryStepData[];
|
|
@@ -297,15 +311,15 @@ function extractTelemetry<T>(format: string, content: T): TelemetryData | null {
|
|
|
297
311
|
}
|
|
298
312
|
}
|
|
299
313
|
|
|
300
|
-
function
|
|
314
|
+
function extractRunTelemetry<T>(
|
|
301
315
|
format: string,
|
|
302
|
-
|
|
316
|
+
runMessages: T[],
|
|
303
317
|
): TelemetryData | null {
|
|
304
318
|
if (format === "ai-sdk/v6") {
|
|
305
|
-
return
|
|
319
|
+
return aggregateAiSdkV6RunSteps(runMessages);
|
|
306
320
|
}
|
|
307
|
-
for (let i =
|
|
308
|
-
const result = extractTelemetry(format,
|
|
321
|
+
for (let i = runMessages.length - 1; i >= 0; i--) {
|
|
322
|
+
const result = extractTelemetry(format, runMessages[i]!);
|
|
309
323
|
if (result) return result;
|
|
310
324
|
}
|
|
311
325
|
return null;
|
|
@@ -332,7 +346,12 @@ function extractAuiV0<T>(content: T): TelemetryData | null {
|
|
|
332
346
|
metadata?: {
|
|
333
347
|
modelId?: string;
|
|
334
348
|
steps?: readonly {
|
|
335
|
-
usage?: {
|
|
349
|
+
usage?: {
|
|
350
|
+
inputTokens?: number;
|
|
351
|
+
outputTokens?: number;
|
|
352
|
+
reasoningTokens?: number;
|
|
353
|
+
cachedInputTokens?: number;
|
|
354
|
+
};
|
|
336
355
|
}[];
|
|
337
356
|
custom?: Record<string, unknown> & { modelId?: string };
|
|
338
357
|
};
|
|
@@ -355,13 +374,39 @@ function extractAuiV0<T>(content: T): TelemetryData | null {
|
|
|
355
374
|
const steps = msg.metadata?.steps;
|
|
356
375
|
let inputTokens: number | undefined;
|
|
357
376
|
let outputTokens: number | undefined;
|
|
377
|
+
let reasoningTokens: number | undefined;
|
|
378
|
+
let cachedInputTokens: number | undefined;
|
|
358
379
|
if (steps && steps.length > 0) {
|
|
359
|
-
|
|
360
|
-
|
|
380
|
+
let totalInput = 0;
|
|
381
|
+
let totalOutput = 0;
|
|
382
|
+
let totalReasoning = 0;
|
|
383
|
+
let totalCachedInput = 0;
|
|
384
|
+
let hasInput = false;
|
|
385
|
+
let hasOutput = false;
|
|
386
|
+
let hasReasoning = false;
|
|
387
|
+
let hasCachedInput = false;
|
|
361
388
|
for (const step of steps) {
|
|
362
|
-
|
|
363
|
-
|
|
389
|
+
if (step.usage?.inputTokens != null) {
|
|
390
|
+
totalInput += step.usage.inputTokens;
|
|
391
|
+
hasInput = true;
|
|
392
|
+
}
|
|
393
|
+
if (step.usage?.outputTokens != null) {
|
|
394
|
+
totalOutput += step.usage.outputTokens;
|
|
395
|
+
hasOutput = true;
|
|
396
|
+
}
|
|
397
|
+
if (step.usage?.reasoningTokens != null) {
|
|
398
|
+
totalReasoning += step.usage.reasoningTokens;
|
|
399
|
+
hasReasoning = true;
|
|
400
|
+
}
|
|
401
|
+
if (step.usage?.cachedInputTokens != null) {
|
|
402
|
+
totalCachedInput += step.usage.cachedInputTokens;
|
|
403
|
+
hasCachedInput = true;
|
|
404
|
+
}
|
|
364
405
|
}
|
|
406
|
+
inputTokens = hasInput ? totalInput : undefined;
|
|
407
|
+
outputTokens = hasOutput ? totalOutput : undefined;
|
|
408
|
+
reasoningTokens = hasReasoning ? totalReasoning : undefined;
|
|
409
|
+
cachedInputTokens = hasCachedInput ? totalCachedInput : undefined;
|
|
365
410
|
}
|
|
366
411
|
|
|
367
412
|
const statusType = msg.status?.type;
|
|
@@ -384,6 +429,12 @@ function extractAuiV0<T>(content: T): TelemetryData | null {
|
|
|
384
429
|
...(s.usage?.outputTokens != null
|
|
385
430
|
? { output_tokens: s.usage.outputTokens }
|
|
386
431
|
: undefined),
|
|
432
|
+
...(s.usage?.reasoningTokens != null
|
|
433
|
+
? { reasoning_tokens: s.usage.reasoningTokens }
|
|
434
|
+
: undefined),
|
|
435
|
+
...(s.usage?.cachedInputTokens != null
|
|
436
|
+
? { cached_input_tokens: s.usage.cachedInputTokens }
|
|
437
|
+
: undefined),
|
|
387
438
|
}))
|
|
388
439
|
: undefined;
|
|
389
440
|
|
|
@@ -393,6 +444,8 @@ function extractAuiV0<T>(content: T): TelemetryData | null {
|
|
|
393
444
|
...(steps?.length ? { totalSteps: steps.length } : undefined),
|
|
394
445
|
...(inputTokens != null ? { inputTokens } : undefined),
|
|
395
446
|
...(outputTokens != null ? { outputTokens } : undefined),
|
|
447
|
+
...(reasoningTokens != null ? { reasoningTokens } : undefined),
|
|
448
|
+
...(cachedInputTokens != null ? { cachedInputTokens } : undefined),
|
|
396
449
|
...(outputText != null ? { outputText } : undefined),
|
|
397
450
|
...(metadata ? { metadata } : undefined),
|
|
398
451
|
...(telemetrySteps ? { steps: telemetrySteps } : undefined),
|
|
@@ -491,7 +544,12 @@ function buildAiSdkV6Result(
|
|
|
491
544
|
totalSteps: number,
|
|
492
545
|
metadata?: Record<string, unknown>,
|
|
493
546
|
stepsData?: { tool_calls: TelemetryToolCall[] }[],
|
|
494
|
-
usage?: {
|
|
547
|
+
usage?: {
|
|
548
|
+
inputTokens?: number;
|
|
549
|
+
outputTokens?: number;
|
|
550
|
+
reasoningTokens?: number;
|
|
551
|
+
cachedInputTokens?: number;
|
|
552
|
+
},
|
|
495
553
|
): TelemetryData {
|
|
496
554
|
const hasText = textParts.length > 0;
|
|
497
555
|
const outputText = hasText ? truncateStr(textParts.join("")) : undefined;
|
|
@@ -516,6 +574,12 @@ function buildAiSdkV6Result(
|
|
|
516
574
|
...(usage?.outputTokens != null
|
|
517
575
|
? { outputTokens: usage.outputTokens }
|
|
518
576
|
: undefined),
|
|
577
|
+
...(usage?.reasoningTokens != null
|
|
578
|
+
? { reasoningTokens: usage.reasoningTokens }
|
|
579
|
+
: undefined),
|
|
580
|
+
...(usage?.cachedInputTokens != null
|
|
581
|
+
? { cachedInputTokens: usage.cachedInputTokens }
|
|
582
|
+
: undefined),
|
|
519
583
|
...(outputText != null ? { outputText } : undefined),
|
|
520
584
|
...(metadata ? { metadata } : undefined),
|
|
521
585
|
...(steps ? { steps } : undefined),
|
|
@@ -528,23 +592,49 @@ type UsageFields = {
|
|
|
528
592
|
outputTokens?: number;
|
|
529
593
|
promptTokens?: number;
|
|
530
594
|
completionTokens?: number;
|
|
595
|
+
reasoningTokens?: number;
|
|
596
|
+
cachedInputTokens?: number;
|
|
531
597
|
};
|
|
532
598
|
|
|
533
|
-
function normalizeUsage(
|
|
534
|
-
|
|
535
|
-
|
|
599
|
+
function normalizeUsage(u: UsageFields):
|
|
600
|
+
| {
|
|
601
|
+
inputTokens?: number;
|
|
602
|
+
outputTokens?: number;
|
|
603
|
+
reasoningTokens?: number;
|
|
604
|
+
cachedInputTokens?: number;
|
|
605
|
+
}
|
|
606
|
+
| undefined {
|
|
536
607
|
const input = u.inputTokens ?? u.promptTokens;
|
|
537
608
|
const output = u.outputTokens ?? u.completionTokens;
|
|
538
|
-
if (
|
|
609
|
+
if (
|
|
610
|
+
input == null &&
|
|
611
|
+
output == null &&
|
|
612
|
+
u.reasoningTokens == null &&
|
|
613
|
+
u.cachedInputTokens == null
|
|
614
|
+
) {
|
|
615
|
+
return undefined;
|
|
616
|
+
}
|
|
617
|
+
|
|
539
618
|
return {
|
|
540
|
-
inputTokens: input
|
|
541
|
-
outputTokens: output
|
|
619
|
+
...(input != null ? { inputTokens: input } : undefined),
|
|
620
|
+
...(output != null ? { outputTokens: output } : undefined),
|
|
621
|
+
...(u.reasoningTokens != null
|
|
622
|
+
? { reasoningTokens: u.reasoningTokens }
|
|
623
|
+
: undefined),
|
|
624
|
+
...(u.cachedInputTokens != null
|
|
625
|
+
? { cachedInputTokens: u.cachedInputTokens }
|
|
626
|
+
: undefined),
|
|
542
627
|
};
|
|
543
628
|
}
|
|
544
629
|
|
|
545
|
-
function extractAiSdkV6Usage(
|
|
546
|
-
|
|
547
|
-
|
|
630
|
+
function extractAiSdkV6Usage(metadata?: Record<string, unknown>):
|
|
631
|
+
| {
|
|
632
|
+
inputTokens?: number;
|
|
633
|
+
outputTokens?: number;
|
|
634
|
+
reasoningTokens?: number;
|
|
635
|
+
cachedInputTokens?: number;
|
|
636
|
+
}
|
|
637
|
+
| undefined {
|
|
548
638
|
// Try top-level metadata.usage
|
|
549
639
|
const usage = metadata?.usage as UsageFields | undefined;
|
|
550
640
|
if (usage) {
|
|
@@ -559,17 +649,44 @@ function extractAiSdkV6Usage(
|
|
|
559
649
|
if (steps && steps.length > 0) {
|
|
560
650
|
let inputTokens = 0;
|
|
561
651
|
let outputTokens = 0;
|
|
652
|
+
let reasoningTokens = 0;
|
|
653
|
+
let cachedInputTokens = 0;
|
|
654
|
+
let hasInput = false;
|
|
655
|
+
let hasOutput = false;
|
|
656
|
+
let hasReasoning = false;
|
|
657
|
+
let hasCachedInput = false;
|
|
562
658
|
let hasAny = false;
|
|
563
659
|
for (const s of steps) {
|
|
564
660
|
if (!s.usage) continue;
|
|
565
661
|
const n = normalizeUsage(s.usage);
|
|
566
662
|
if (n) {
|
|
567
|
-
|
|
568
|
-
|
|
663
|
+
if (n.inputTokens != null) {
|
|
664
|
+
inputTokens += n.inputTokens;
|
|
665
|
+
hasInput = true;
|
|
666
|
+
}
|
|
667
|
+
if (n.outputTokens != null) {
|
|
668
|
+
outputTokens += n.outputTokens;
|
|
669
|
+
hasOutput = true;
|
|
670
|
+
}
|
|
671
|
+
if (n.reasoningTokens != null) {
|
|
672
|
+
reasoningTokens += n.reasoningTokens;
|
|
673
|
+
hasReasoning = true;
|
|
674
|
+
}
|
|
675
|
+
if (n.cachedInputTokens != null) {
|
|
676
|
+
cachedInputTokens += n.cachedInputTokens;
|
|
677
|
+
hasCachedInput = true;
|
|
678
|
+
}
|
|
569
679
|
hasAny = true;
|
|
570
680
|
}
|
|
571
681
|
}
|
|
572
|
-
if (hasAny)
|
|
682
|
+
if (hasAny) {
|
|
683
|
+
return {
|
|
684
|
+
...(hasInput ? { inputTokens } : undefined),
|
|
685
|
+
...(hasOutput ? { outputTokens } : undefined),
|
|
686
|
+
...(hasReasoning ? { reasoningTokens } : undefined),
|
|
687
|
+
...(hasCachedInput ? { cachedInputTokens } : undefined),
|
|
688
|
+
};
|
|
689
|
+
}
|
|
573
690
|
}
|
|
574
691
|
|
|
575
692
|
return undefined;
|
|
@@ -592,17 +709,22 @@ function extractAiSdkV6<T>(content: T): TelemetryData | null {
|
|
|
592
709
|
);
|
|
593
710
|
}
|
|
594
711
|
|
|
595
|
-
function
|
|
712
|
+
function aggregateAiSdkV6RunSteps<T>(stepMessages: T[]): TelemetryData | null {
|
|
596
713
|
const allTextParts: string[] = [];
|
|
597
714
|
const allToolCalls: TelemetryToolCall[] = [];
|
|
598
715
|
const allStepsData: { tool_calls: TelemetryToolCall[] }[] = [];
|
|
599
716
|
let hasAssistant = false;
|
|
600
717
|
let metadata: Record<string, unknown> | undefined;
|
|
601
|
-
let
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
718
|
+
let inputTokens = 0;
|
|
719
|
+
let outputTokens = 0;
|
|
720
|
+
let reasoningTokens = 0;
|
|
721
|
+
let cachedInputTokens = 0;
|
|
722
|
+
let hasInput = false;
|
|
723
|
+
let hasOutput = false;
|
|
724
|
+
let hasReasoning = false;
|
|
725
|
+
let hasCachedInput = false;
|
|
726
|
+
|
|
727
|
+
for (const content of stepMessages) {
|
|
606
728
|
const msg = content as AiSdkV6Message;
|
|
607
729
|
if (msg.role !== "assistant") continue;
|
|
608
730
|
hasAssistant = true;
|
|
@@ -617,10 +739,22 @@ function extractAiSdkV6Batch<T>(contents: T[]): TelemetryData | null {
|
|
|
617
739
|
|
|
618
740
|
const usage = extractAiSdkV6Usage(msg.metadata);
|
|
619
741
|
if (usage) {
|
|
620
|
-
if (
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
742
|
+
if (usage.inputTokens != null) {
|
|
743
|
+
inputTokens += usage.inputTokens;
|
|
744
|
+
hasInput = true;
|
|
745
|
+
}
|
|
746
|
+
if (usage.outputTokens != null) {
|
|
747
|
+
outputTokens += usage.outputTokens;
|
|
748
|
+
hasOutput = true;
|
|
749
|
+
}
|
|
750
|
+
if (usage.reasoningTokens != null) {
|
|
751
|
+
reasoningTokens += usage.reasoningTokens;
|
|
752
|
+
hasReasoning = true;
|
|
753
|
+
}
|
|
754
|
+
if (usage.cachedInputTokens != null) {
|
|
755
|
+
cachedInputTokens += usage.cachedInputTokens;
|
|
756
|
+
hasCachedInput = true;
|
|
757
|
+
}
|
|
624
758
|
}
|
|
625
759
|
}
|
|
626
760
|
|
|
@@ -631,7 +765,12 @@ function extractAiSdkV6Batch<T>(contents: T[]): TelemetryData | null {
|
|
|
631
765
|
allStepsData.length,
|
|
632
766
|
metadata,
|
|
633
767
|
allStepsData,
|
|
634
|
-
|
|
768
|
+
{
|
|
769
|
+
...(hasInput ? { inputTokens } : undefined),
|
|
770
|
+
...(hasOutput ? { outputTokens } : undefined),
|
|
771
|
+
...(hasReasoning ? { reasoningTokens } : undefined),
|
|
772
|
+
...(hasCachedInput ? { cachedInputTokens } : undefined),
|
|
773
|
+
},
|
|
635
774
|
);
|
|
636
775
|
}
|
|
637
776
|
|
|
@@ -4,19 +4,22 @@ import type { ThreadAssistantMessage } from "@assistant-ui/core";
|
|
|
4
4
|
import type { Tool } from "assistant-stream";
|
|
5
5
|
import { act, renderHook, waitFor } from "@testing-library/react";
|
|
6
6
|
import { describe, expect, it, vi } from "vitest";
|
|
7
|
-
import type { AssistantTransportState
|
|
8
|
-
import { useToolInvocations } from "./useToolInvocations";
|
|
7
|
+
import type { AssistantTransportState } from "./types";
|
|
8
|
+
import { ToolExecutionStatus, useToolInvocations } from "./useToolInvocations";
|
|
9
|
+
import { ReadonlyJSONObject, ReadonlyJSONValue } from "assistant-stream/utils";
|
|
9
10
|
|
|
10
11
|
const createState = (
|
|
11
12
|
messages: ThreadAssistantMessage[],
|
|
13
|
+
isRunning: boolean = true,
|
|
12
14
|
): AssistantTransportState => ({
|
|
13
15
|
messages,
|
|
14
|
-
isRunning
|
|
16
|
+
isRunning,
|
|
15
17
|
});
|
|
16
18
|
|
|
17
19
|
const createAssistantMessage = (
|
|
18
20
|
argsText: string,
|
|
19
21
|
args: Record<string, unknown>,
|
|
22
|
+
options?: { result?: ReadonlyJSONValue; isError?: boolean },
|
|
20
23
|
): ThreadAssistantMessage => ({
|
|
21
24
|
id: "m-1",
|
|
22
25
|
role: "assistant",
|
|
@@ -34,8 +37,10 @@ const createAssistantMessage = (
|
|
|
34
37
|
type: "tool-call",
|
|
35
38
|
toolCallId: "tool-1",
|
|
36
39
|
toolName: "weatherSearch",
|
|
37
|
-
args,
|
|
40
|
+
args: args as ReadonlyJSONObject,
|
|
38
41
|
argsText,
|
|
42
|
+
...(options?.result !== undefined && { result: options.result }),
|
|
43
|
+
...(options?.isError !== undefined && { isError: options.isError }),
|
|
39
44
|
},
|
|
40
45
|
],
|
|
41
46
|
});
|
|
@@ -45,6 +50,7 @@ describe("useToolInvocations", () => {
|
|
|
45
50
|
const execute = vi.fn(async () => ({ forecast: "ok" }));
|
|
46
51
|
const getTools = () => ({
|
|
47
52
|
weatherSearch: {
|
|
53
|
+
parameters: { type: "object", properties: {} },
|
|
48
54
|
execute,
|
|
49
55
|
} satisfies Tool,
|
|
50
56
|
});
|
|
@@ -137,6 +143,7 @@ describe("useToolInvocations", () => {
|
|
|
137
143
|
);
|
|
138
144
|
const getTools = () => ({
|
|
139
145
|
weatherSearch: {
|
|
146
|
+
parameters: { type: "object", properties: {} },
|
|
140
147
|
execute,
|
|
141
148
|
} satisfies Tool,
|
|
142
149
|
});
|
|
@@ -220,4 +227,223 @@ describe("useToolInvocations", () => {
|
|
|
220
227
|
});
|
|
221
228
|
expect(Object.keys(statuses)).not.toContain("tool-1:rewrite:0");
|
|
222
229
|
});
|
|
230
|
+
|
|
231
|
+
it("does not close args stream early for non-executable tool snapshots", () => {
|
|
232
|
+
const getTools = () => ({
|
|
233
|
+
weatherSearch: {
|
|
234
|
+
parameters: { type: "object", properties: {} },
|
|
235
|
+
} satisfies Tool,
|
|
236
|
+
});
|
|
237
|
+
const onResult = vi.fn();
|
|
238
|
+
const setToolStatuses = vi.fn();
|
|
239
|
+
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
240
|
+
|
|
241
|
+
try {
|
|
242
|
+
const { rerender } = renderHook(
|
|
243
|
+
({ state }: { state: AssistantTransportState }) =>
|
|
244
|
+
useToolInvocations({
|
|
245
|
+
state,
|
|
246
|
+
getTools,
|
|
247
|
+
onResult,
|
|
248
|
+
setToolStatuses,
|
|
249
|
+
}),
|
|
250
|
+
{
|
|
251
|
+
initialProps: {
|
|
252
|
+
state: createState([]),
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
act(() => {
|
|
258
|
+
rerender({
|
|
259
|
+
state: createState([createAssistantMessage("{}", {})]),
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
act(() => {
|
|
264
|
+
rerender({
|
|
265
|
+
state: createState([
|
|
266
|
+
createAssistantMessage('{"title":"Weekly"', {
|
|
267
|
+
title: "Weekly",
|
|
268
|
+
}),
|
|
269
|
+
]),
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
act(() => {
|
|
274
|
+
rerender({
|
|
275
|
+
state: createState([
|
|
276
|
+
createAssistantMessage('{"title":"Weekly","columns":["name"]}', {
|
|
277
|
+
title: "Weekly",
|
|
278
|
+
columns: ["name"],
|
|
279
|
+
}),
|
|
280
|
+
]),
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
expect(warnSpy).not.toHaveBeenCalledWith(
|
|
285
|
+
"argsText updated after controller was closed:",
|
|
286
|
+
expect.anything(),
|
|
287
|
+
);
|
|
288
|
+
expect(warnSpy).not.toHaveBeenCalledWith(
|
|
289
|
+
"argsText updated after controller was closed, restarting tool args stream:",
|
|
290
|
+
expect.anything(),
|
|
291
|
+
);
|
|
292
|
+
expect(onResult).not.toHaveBeenCalled();
|
|
293
|
+
} finally {
|
|
294
|
+
warnSpy.mockRestore();
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it("closes non-executable complete args stream after run settles", () => {
|
|
299
|
+
const getTools = () => ({
|
|
300
|
+
weatherSearch: {
|
|
301
|
+
parameters: { type: "object", properties: {} },
|
|
302
|
+
} satisfies Tool,
|
|
303
|
+
});
|
|
304
|
+
const onResult = vi.fn();
|
|
305
|
+
const setToolStatuses = vi.fn();
|
|
306
|
+
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
307
|
+
|
|
308
|
+
try {
|
|
309
|
+
const { rerender } = renderHook(
|
|
310
|
+
({ state }: { state: AssistantTransportState }) =>
|
|
311
|
+
useToolInvocations({
|
|
312
|
+
state,
|
|
313
|
+
getTools,
|
|
314
|
+
onResult,
|
|
315
|
+
setToolStatuses,
|
|
316
|
+
}),
|
|
317
|
+
{
|
|
318
|
+
initialProps: {
|
|
319
|
+
state: createState([]),
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
);
|
|
323
|
+
|
|
324
|
+
act(() => {
|
|
325
|
+
rerender({
|
|
326
|
+
state: createState(
|
|
327
|
+
[
|
|
328
|
+
createAssistantMessage('{"title":"Weekly"}', {
|
|
329
|
+
title: "Weekly",
|
|
330
|
+
}),
|
|
331
|
+
],
|
|
332
|
+
true,
|
|
333
|
+
),
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
act(() => {
|
|
338
|
+
rerender({
|
|
339
|
+
state: createState(
|
|
340
|
+
[
|
|
341
|
+
createAssistantMessage('{"title":"Weekly"}', {
|
|
342
|
+
title: "Weekly",
|
|
343
|
+
}),
|
|
344
|
+
],
|
|
345
|
+
false,
|
|
346
|
+
),
|
|
347
|
+
});
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
act(() => {
|
|
351
|
+
rerender({
|
|
352
|
+
state: createState(
|
|
353
|
+
[
|
|
354
|
+
createAssistantMessage('{"title":"Weekly","columns":["name"]}', {
|
|
355
|
+
title: "Weekly",
|
|
356
|
+
columns: ["name"],
|
|
357
|
+
}),
|
|
358
|
+
],
|
|
359
|
+
false,
|
|
360
|
+
),
|
|
361
|
+
});
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
365
|
+
"argsText updated after controller was closed, restarting tool args stream:",
|
|
366
|
+
expect.objectContaining({
|
|
367
|
+
previous: '{"title":"Weekly"}',
|
|
368
|
+
next: '{"title":"Weekly","columns":["name"]}',
|
|
369
|
+
}),
|
|
370
|
+
);
|
|
371
|
+
expect(onResult).not.toHaveBeenCalled();
|
|
372
|
+
} finally {
|
|
373
|
+
warnSpy.mockRestore();
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
it("handles backend result when equivalent complete argsText reorders keys", async () => {
|
|
378
|
+
let resolveExecute: ((value: unknown) => void) | undefined;
|
|
379
|
+
const execute = vi.fn(
|
|
380
|
+
() =>
|
|
381
|
+
new Promise<unknown>((resolve) => {
|
|
382
|
+
resolveExecute = resolve;
|
|
383
|
+
}),
|
|
384
|
+
);
|
|
385
|
+
const getTools = () => ({
|
|
386
|
+
weatherSearch: {
|
|
387
|
+
parameters: { type: "object", properties: {} },
|
|
388
|
+
execute,
|
|
389
|
+
} satisfies Tool,
|
|
390
|
+
});
|
|
391
|
+
const onResult = vi.fn();
|
|
392
|
+
const setToolStatuses = vi.fn();
|
|
393
|
+
|
|
394
|
+
const { rerender } = renderHook(
|
|
395
|
+
({ state }: { state: AssistantTransportState }) =>
|
|
396
|
+
useToolInvocations({
|
|
397
|
+
state,
|
|
398
|
+
getTools,
|
|
399
|
+
onResult,
|
|
400
|
+
setToolStatuses,
|
|
401
|
+
}),
|
|
402
|
+
{
|
|
403
|
+
initialProps: {
|
|
404
|
+
state: createState([]),
|
|
405
|
+
},
|
|
406
|
+
},
|
|
407
|
+
);
|
|
408
|
+
|
|
409
|
+
act(() => {
|
|
410
|
+
rerender({
|
|
411
|
+
state: createState([
|
|
412
|
+
createAssistantMessage('{"a":1,"b":2}', {
|
|
413
|
+
a: 1,
|
|
414
|
+
b: 2,
|
|
415
|
+
}),
|
|
416
|
+
]),
|
|
417
|
+
});
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
await waitFor(() => {
|
|
421
|
+
expect(execute).toHaveBeenCalledTimes(1);
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
act(() => {
|
|
425
|
+
rerender({
|
|
426
|
+
state: createState([
|
|
427
|
+
createAssistantMessage(
|
|
428
|
+
'{"b":2,"a":1}',
|
|
429
|
+
{
|
|
430
|
+
a: 1,
|
|
431
|
+
b: 2,
|
|
432
|
+
},
|
|
433
|
+
{
|
|
434
|
+
result: { source: "backend" },
|
|
435
|
+
},
|
|
436
|
+
),
|
|
437
|
+
]),
|
|
438
|
+
});
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
await act(async () => {
|
|
442
|
+
resolveExecute?.({ source: "client" });
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
await waitFor(() => {
|
|
446
|
+
expect(onResult).not.toHaveBeenCalled();
|
|
447
|
+
});
|
|
448
|
+
});
|
|
223
449
|
});
|