ai 6.0.302 → 6.0.303
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/CHANGELOG.md +11 -0
- package/dist/index.d.mts +19 -4
- package/dist/index.d.ts +19 -4
- package/dist/index.js +115 -39
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +115 -39
- package/dist/index.mjs.map +1 -1
- package/dist/internal/index.js +1 -1
- package/dist/internal/index.mjs +1 -1
- package/docs/04-ai-sdk-ui/21-error-handling.mdx +34 -0
- package/package.json +2 -2
- package/src/global.ts +23 -4
- package/src/ui/chat.ts +111 -33
- package/src/ui/process-ui-message-stream.ts +21 -0
- package/src/ui-message-stream/read-ui-message-stream.ts +1 -0
- package/src/util/serial-job-executor.ts +22 -5
package/dist/internal/index.js
CHANGED
|
@@ -164,7 +164,7 @@ function detectMediaType({
|
|
|
164
164
|
var import_provider_utils2 = require("@ai-sdk/provider-utils");
|
|
165
165
|
|
|
166
166
|
// src/version.ts
|
|
167
|
-
var VERSION = true ? "6.0.
|
|
167
|
+
var VERSION = true ? "6.0.303" : "0.0.0-test";
|
|
168
168
|
|
|
169
169
|
// src/util/download/download.ts
|
|
170
170
|
var download = async ({
|
package/dist/internal/index.mjs
CHANGED
|
@@ -49,6 +49,40 @@ globalThis.AI_SDK_LOG_WARNINGS = ({ warnings, provider, model }) => {
|
|
|
49
49
|
};
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
+
If your application loads multiple AI SDK versions in the same runtime, they all
|
|
53
|
+
share `globalThis.AI_SDK_LOG_WARNINGS`. Each version calls the logger with its own
|
|
54
|
+
arguments and warning types. The merged global type declarations prevent
|
|
55
|
+
TypeScript declaration conflicts, but do not convert these arguments at runtime.
|
|
56
|
+
A handler typed for only one version may therefore fail when another version
|
|
57
|
+
calls it.
|
|
58
|
+
|
|
59
|
+
Import `LogWarningsFunction` from every installed version that can call the logger
|
|
60
|
+
and accept a union of their parameter types. For example, with AI SDK 7 installed
|
|
61
|
+
as `ai` and AI SDK 6 installed under the package alias `ai-v6` (`npm:ai@6`):
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import type { LogWarningsFunction as LogWarningsFunctionV7 } from 'ai';
|
|
65
|
+
import type { LogWarningsFunction as LogWarningsFunctionV6 } from 'ai-v6';
|
|
66
|
+
|
|
67
|
+
type LoggerOptions =
|
|
68
|
+
| Parameters<LogWarningsFunctionV7>[0]
|
|
69
|
+
| Parameters<LogWarningsFunctionV6>[0];
|
|
70
|
+
|
|
71
|
+
const logger = ({ warnings, provider, model }: LoggerOptions) => {
|
|
72
|
+
for (const warning of warnings) {
|
|
73
|
+
console.warn('AI SDK Warning:', warning, { provider, model });
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
globalThis.AI_SDK_LOG_WARNINGS = logger;
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Check the argument shape and warning type before accessing version-specific
|
|
81
|
+
fields. If AI SDK 5 is also installed, include its
|
|
82
|
+
`Experimental_LogWarningsFunction` parameter type and use `Array.isArray` to
|
|
83
|
+
handle its warning array before accessing the options object used by AI SDK 6
|
|
84
|
+
and 7.
|
|
85
|
+
|
|
52
86
|
## Error Handling
|
|
53
87
|
|
|
54
88
|
### Error Helper Object
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai",
|
|
3
|
-
"version": "6.0.
|
|
3
|
+
"version": "6.0.303",
|
|
4
4
|
"description": "AI SDK by Vercel - build apps like ChatGPT, Claude, Gemini, and more with a single interface for any model using the Vercel AI Gateway or go direct to OpenAI, Anthropic, Google, or any other model provider.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@opentelemetry/api": "^1.9.0",
|
|
48
|
-
"@ai-sdk/gateway": "3.0.
|
|
48
|
+
"@ai-sdk/gateway": "3.0.212",
|
|
49
49
|
"@ai-sdk/provider": "3.0.18",
|
|
50
50
|
"@ai-sdk/provider-utils": "4.0.57"
|
|
51
51
|
},
|
package/src/global.ts
CHANGED
|
@@ -2,8 +2,20 @@ import type { ProviderV3 } from '@ai-sdk/provider';
|
|
|
2
2
|
import type { LogWarningsFunction } from './logger/log-warnings';
|
|
3
3
|
import type { TelemetryIntegration } from './telemetry/telemetry-integration';
|
|
4
4
|
|
|
5
|
-
//
|
|
5
|
+
// Each installed copy contributes its own types without conflicting with other
|
|
6
|
+
// versions. This is a type-only key; no symbol is created at runtime.
|
|
7
|
+
declare const aiSdkGlobalsKey: unique symbol;
|
|
8
|
+
|
|
6
9
|
declare global {
|
|
10
|
+
interface AISDKGlobalTypes {
|
|
11
|
+
[aiSdkGlobalsKey]: {
|
|
12
|
+
provider: ProviderV3;
|
|
13
|
+
logWarnings: LogWarningsFunction;
|
|
14
|
+
telemetry: TelemetryIntegration;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Keep these registry-based declarations identical across major versions.
|
|
7
19
|
/**
|
|
8
20
|
* The default provider to use for the AI SDK.
|
|
9
21
|
* String model ids are resolved to the default provider and model id.
|
|
@@ -12,7 +24,9 @@ declare global {
|
|
|
12
24
|
*
|
|
13
25
|
* @see https://ai-sdk.dev/docs/ai-sdk-core/provider-management#global-provider-configuration
|
|
14
26
|
*/
|
|
15
|
-
var AI_SDK_DEFAULT_PROVIDER:
|
|
27
|
+
var AI_SDK_DEFAULT_PROVIDER:
|
|
28
|
+
| AISDKGlobalTypes[keyof AISDKGlobalTypes]['provider']
|
|
29
|
+
| undefined;
|
|
16
30
|
|
|
17
31
|
/**
|
|
18
32
|
* The warning logger to use for the AI SDK.
|
|
@@ -21,7 +35,10 @@ declare global {
|
|
|
21
35
|
*
|
|
22
36
|
* If set to false, no warnings are logged.
|
|
23
37
|
*/
|
|
24
|
-
var AI_SDK_LOG_WARNINGS:
|
|
38
|
+
var AI_SDK_LOG_WARNINGS:
|
|
39
|
+
| AISDKGlobalTypes[keyof AISDKGlobalTypes]['logWarnings']
|
|
40
|
+
| undefined
|
|
41
|
+
| false;
|
|
25
42
|
|
|
26
43
|
/**
|
|
27
44
|
* Globally registered telemetry integrations for the AI SDK.
|
|
@@ -32,5 +49,7 @@ declare global {
|
|
|
32
49
|
* Prefer using `registerTelemetryIntegration()` from `'ai'` instead of
|
|
33
50
|
* assigning this directly.
|
|
34
51
|
*/
|
|
35
|
-
var AI_SDK_TELEMETRY_INTEGRATIONS:
|
|
52
|
+
var AI_SDK_TELEMETRY_INTEGRATIONS:
|
|
53
|
+
| Array<AISDKGlobalTypes[keyof AISDKGlobalTypes]['telemetry']>
|
|
54
|
+
| undefined;
|
|
36
55
|
}
|
package/src/ui/chat.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
import type { FinishReason } from '../types/language-model';
|
|
8
8
|
import type { UIMessageChunk } from '../ui-message-stream/ui-message-chunks';
|
|
9
9
|
import { consumeStream } from '../util/consume-stream';
|
|
10
|
+
import { createResolvablePromise } from '../util/create-resolvable-promise';
|
|
10
11
|
import { SerialJobExecutor } from '../util/serial-job-executor';
|
|
11
12
|
import type { ChatTransport } from './chat-transport';
|
|
12
13
|
import { convertFileListToFileUIParts } from './convert-file-list-to-file-ui-parts';
|
|
@@ -135,12 +136,19 @@ export type ChatStatus = 'submitted' | 'streaming' | 'ready' | 'error';
|
|
|
135
136
|
type ActiveResponse<UI_MESSAGE extends UIMessage> = {
|
|
136
137
|
state: StreamingUIMessageState<UI_MESSAGE>;
|
|
137
138
|
abortController: AbortController;
|
|
139
|
+
completionPromise: Promise<void>;
|
|
138
140
|
};
|
|
139
141
|
|
|
140
142
|
type ActiveResumeRequest = {
|
|
141
143
|
abortController: AbortController;
|
|
144
|
+
completionPromise: Promise<void>;
|
|
142
145
|
};
|
|
143
146
|
|
|
147
|
+
type MakeRequestOptions = {
|
|
148
|
+
trigger: 'submit-message' | 'resume-stream' | 'regenerate-message';
|
|
149
|
+
messageId?: string;
|
|
150
|
+
} & ChatRequestOptions;
|
|
151
|
+
|
|
144
152
|
export interface ChatState<UI_MESSAGE extends UIMessage> {
|
|
145
153
|
status: ChatStatus;
|
|
146
154
|
|
|
@@ -266,6 +274,8 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
|
|
|
266
274
|
| StreamingUIMessageState<UI_MESSAGE>
|
|
267
275
|
| undefined = undefined;
|
|
268
276
|
private jobExecutor = new SerialJobExecutor();
|
|
277
|
+
private activeStopCount = 0;
|
|
278
|
+
private stopGeneration = 0;
|
|
269
279
|
|
|
270
280
|
constructor({
|
|
271
281
|
generateId = generateIdFunc,
|
|
@@ -572,21 +582,23 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
|
|
|
572
582
|
this.status !== 'submitted' &&
|
|
573
583
|
this.sendAutomaticallyWhen
|
|
574
584
|
) {
|
|
575
|
-
this.shouldSendAutomatically()
|
|
576
|
-
|
|
577
|
-
|
|
585
|
+
const shouldSend = await this.shouldSendAutomatically();
|
|
586
|
+
|
|
587
|
+
if (shouldSend) {
|
|
588
|
+
// no await to avoid deadlocking
|
|
589
|
+
void this.runAutomaticRequest(() => {
|
|
578
590
|
const messageId =
|
|
579
591
|
messageIndex === -1
|
|
580
592
|
? this.lastMessage?.id
|
|
581
593
|
: messages[messageIndex].id;
|
|
582
594
|
|
|
583
|
-
this.makeRequestForToolApproval({
|
|
595
|
+
return this.makeRequestForToolApproval({
|
|
584
596
|
messageId,
|
|
585
597
|
messageIndex,
|
|
586
598
|
...options,
|
|
587
599
|
});
|
|
588
|
-
}
|
|
589
|
-
}
|
|
600
|
+
}, shouldSend);
|
|
601
|
+
}
|
|
590
602
|
}
|
|
591
603
|
});
|
|
592
604
|
|
|
@@ -626,16 +638,20 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
|
|
|
626
638
|
this.status !== 'submitted' &&
|
|
627
639
|
this.sendAutomaticallyWhen
|
|
628
640
|
) {
|
|
629
|
-
this.shouldSendAutomatically()
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
641
|
+
const shouldSend = await this.shouldSendAutomatically();
|
|
642
|
+
|
|
643
|
+
if (shouldSend) {
|
|
644
|
+
// no await to avoid deadlocking
|
|
645
|
+
void this.runAutomaticRequest(
|
|
646
|
+
() =>
|
|
647
|
+
this.makeRequest({
|
|
648
|
+
trigger: 'submit-message',
|
|
649
|
+
messageId: this.lastMessage?.id,
|
|
650
|
+
...options,
|
|
651
|
+
}),
|
|
652
|
+
shouldSend,
|
|
653
|
+
);
|
|
654
|
+
}
|
|
639
655
|
}
|
|
640
656
|
});
|
|
641
657
|
|
|
@@ -643,11 +659,31 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
|
|
|
643
659
|
addToolResult = this.addToolOutput;
|
|
644
660
|
|
|
645
661
|
/**
|
|
646
|
-
* Abort the current request
|
|
662
|
+
* Abort the current request, keep the generated tokens if any, and wait for
|
|
663
|
+
* the request pipeline to finish.
|
|
647
664
|
*/
|
|
648
665
|
stop = async () => {
|
|
649
|
-
this.
|
|
650
|
-
this.
|
|
666
|
+
this.activeStopCount++;
|
|
667
|
+
this.stopGeneration++;
|
|
668
|
+
|
|
669
|
+
try {
|
|
670
|
+
const activeResumeRequest = this.activeResumeRequest;
|
|
671
|
+
const activeResponse = this.activeResponse;
|
|
672
|
+
|
|
673
|
+
activeResumeRequest?.abortController.abort();
|
|
674
|
+
activeResponse?.abortController.abort();
|
|
675
|
+
|
|
676
|
+
await Promise.all([
|
|
677
|
+
activeResumeRequest?.completionPromise,
|
|
678
|
+
activeResponse?.completionPromise,
|
|
679
|
+
]);
|
|
680
|
+
|
|
681
|
+
// Stream cancellation can complete while a processing job is still
|
|
682
|
+
// blocked in onToolCall. Drain that job and any message update it queued.
|
|
683
|
+
await this.jobExecutor.waitForIdle();
|
|
684
|
+
} finally {
|
|
685
|
+
this.activeStopCount--;
|
|
686
|
+
}
|
|
651
687
|
};
|
|
652
688
|
|
|
653
689
|
private async shouldSendAutomatically(): Promise<boolean> {
|
|
@@ -665,6 +701,30 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
|
|
|
665
701
|
return result as boolean;
|
|
666
702
|
}
|
|
667
703
|
|
|
704
|
+
private async runAutomaticRequest(
|
|
705
|
+
request: () => Promise<void>,
|
|
706
|
+
shouldSend?: boolean,
|
|
707
|
+
): Promise<void> {
|
|
708
|
+
const stopGeneration = this.stopGeneration;
|
|
709
|
+
const startedWhileStopping = this.activeStopCount > 0;
|
|
710
|
+
|
|
711
|
+
if (!(shouldSend ?? (await this.shouldSendAutomatically()))) {
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
// A stop invalidates automatic requests that were pending when it started,
|
|
716
|
+
// including requests queued by callback-driven tool updates.
|
|
717
|
+
if (
|
|
718
|
+
startedWhileStopping ||
|
|
719
|
+
stopGeneration !== this.stopGeneration ||
|
|
720
|
+
this.activeStopCount > 0
|
|
721
|
+
) {
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
await request();
|
|
726
|
+
}
|
|
727
|
+
|
|
668
728
|
private async makeRequestForToolApproval({
|
|
669
729
|
messageId,
|
|
670
730
|
messageIndex,
|
|
@@ -695,23 +755,38 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
|
|
|
695
755
|
}
|
|
696
756
|
}
|
|
697
757
|
|
|
698
|
-
private async makeRequest({
|
|
758
|
+
private async makeRequest(options: MakeRequestOptions) {
|
|
759
|
+
const completion = createResolvablePromise<void>();
|
|
760
|
+
|
|
761
|
+
try {
|
|
762
|
+
await this.makeRequestImpl({
|
|
763
|
+
...options,
|
|
764
|
+
completionPromise: completion.promise,
|
|
765
|
+
});
|
|
766
|
+
} finally {
|
|
767
|
+
completion.resolve();
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
private async makeRequestImpl({
|
|
699
772
|
trigger,
|
|
700
773
|
metadata,
|
|
701
774
|
headers,
|
|
702
775
|
body,
|
|
703
776
|
messageId,
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
}
|
|
777
|
+
completionPromise,
|
|
778
|
+
}: MakeRequestOptions & {
|
|
779
|
+
completionPromise: Promise<void>;
|
|
780
|
+
}) {
|
|
708
781
|
if (trigger !== 'resume-stream') {
|
|
709
782
|
this.resumableStreamState = undefined;
|
|
710
783
|
}
|
|
711
784
|
|
|
712
785
|
const abortController = new AbortController();
|
|
713
786
|
const activeResumeRequest =
|
|
714
|
-
trigger === 'resume-stream'
|
|
787
|
+
trigger === 'resume-stream'
|
|
788
|
+
? { abortController, completionPromise }
|
|
789
|
+
: undefined;
|
|
715
790
|
|
|
716
791
|
if (activeResumeRequest) {
|
|
717
792
|
this.activeResumeRequest?.abortController.abort();
|
|
@@ -830,6 +905,7 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
|
|
|
830
905
|
messageId: this.generateId(),
|
|
831
906
|
}),
|
|
832
907
|
abortController,
|
|
908
|
+
completionPromise,
|
|
833
909
|
} as ActiveResponse<UI_MESSAGE>;
|
|
834
910
|
|
|
835
911
|
activeResponse = response;
|
|
@@ -992,14 +1068,16 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
|
|
|
992
1068
|
}
|
|
993
1069
|
|
|
994
1070
|
// automatically send the message if the sendAutomaticallyWhen function returns true
|
|
995
|
-
if (!
|
|
996
|
-
await this.
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1071
|
+
if (!isAbort && !isError) {
|
|
1072
|
+
await this.runAutomaticRequest(() =>
|
|
1073
|
+
this.makeRequest({
|
|
1074
|
+
trigger: 'submit-message',
|
|
1075
|
+
messageId: this.lastMessage?.id,
|
|
1076
|
+
metadata,
|
|
1077
|
+
headers,
|
|
1078
|
+
body,
|
|
1079
|
+
}),
|
|
1080
|
+
);
|
|
1003
1081
|
}
|
|
1004
1082
|
}
|
|
1005
1083
|
}
|
|
@@ -120,6 +120,7 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
120
120
|
onError,
|
|
121
121
|
onToolCall,
|
|
122
122
|
onData,
|
|
123
|
+
resetStateOnMessageIdChange = false,
|
|
123
124
|
}: {
|
|
124
125
|
// input stream is not fully typed yet:
|
|
125
126
|
stream: ReadableStream<UIMessageChunk>;
|
|
@@ -136,6 +137,8 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
136
137
|
}) => Promise<void>,
|
|
137
138
|
) => Promise<void>;
|
|
138
139
|
onError: ErrorHandler;
|
|
140
|
+
// During resume, a different message ID identifies a separate response.
|
|
141
|
+
resetStateOnMessageIdChange?: boolean;
|
|
139
142
|
}): ReadableStream<InferUIMessageChunk<UI_MESSAGE>> {
|
|
140
143
|
return stream.pipeThrough(
|
|
141
144
|
new TransformStream<UIMessageChunk, InferUIMessageChunk<UI_MESSAGE>>({
|
|
@@ -878,6 +881,24 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
878
881
|
}
|
|
879
882
|
|
|
880
883
|
case 'start': {
|
|
884
|
+
if (
|
|
885
|
+
resetStateOnMessageIdChange &&
|
|
886
|
+
chunk.messageId != null &&
|
|
887
|
+
chunk.messageId !== state.message.id
|
|
888
|
+
) {
|
|
889
|
+
// Start the separate response with empty parts and metadata.
|
|
890
|
+
// Reset the active-part maps too, so earlier chunks stay with
|
|
891
|
+
// the previous response. Keep the state object for its callers.
|
|
892
|
+
Object.assign(
|
|
893
|
+
state,
|
|
894
|
+
createStreamingUIMessageState({
|
|
895
|
+
lastMessage: undefined,
|
|
896
|
+
messageId: chunk.messageId,
|
|
897
|
+
}),
|
|
898
|
+
{ finishReason: undefined },
|
|
899
|
+
);
|
|
900
|
+
}
|
|
901
|
+
|
|
881
902
|
if (chunk.messageId != null) {
|
|
882
903
|
state.message.id = chunk.messageId;
|
|
883
904
|
}
|
|
@@ -95,6 +95,7 @@ export function readUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
95
95
|
consumeStream({
|
|
96
96
|
stream: processUIMessageStream({
|
|
97
97
|
stream,
|
|
98
|
+
resetStateOnMessageIdChange: true,
|
|
98
99
|
runUpdateMessageJob(
|
|
99
100
|
job: (options: {
|
|
100
101
|
state: StreamingUIMessageState<UI_MESSAGE>;
|
|
@@ -3,6 +3,7 @@ import type { Job } from './job';
|
|
|
3
3
|
export class SerialJobExecutor {
|
|
4
4
|
private queue: Array<Job> = [];
|
|
5
5
|
private isProcessing = false;
|
|
6
|
+
private idleWaiters: Array<() => void> = [];
|
|
6
7
|
|
|
7
8
|
private async processQueue() {
|
|
8
9
|
if (this.isProcessing) {
|
|
@@ -11,12 +12,18 @@ export class SerialJobExecutor {
|
|
|
11
12
|
|
|
12
13
|
this.isProcessing = true;
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
try {
|
|
16
|
+
while (this.queue.length > 0) {
|
|
17
|
+
await this.queue[0]();
|
|
18
|
+
this.queue.shift();
|
|
19
|
+
}
|
|
20
|
+
} finally {
|
|
21
|
+
this.isProcessing = false;
|
|
18
22
|
|
|
19
|
-
|
|
23
|
+
for (const resolve of this.idleWaiters.splice(0)) {
|
|
24
|
+
resolve();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
20
27
|
}
|
|
21
28
|
|
|
22
29
|
async run(job: Job): Promise<void> {
|
|
@@ -33,4 +40,14 @@ export class SerialJobExecutor {
|
|
|
33
40
|
void this.processQueue();
|
|
34
41
|
});
|
|
35
42
|
}
|
|
43
|
+
|
|
44
|
+
async waitForIdle(): Promise<void> {
|
|
45
|
+
if (!this.isProcessing && this.queue.length === 0) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
await new Promise<void>(resolve => {
|
|
50
|
+
this.idleWaiters.push(resolve);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
36
53
|
}
|