@animalabs/membrane 0.5.24 → 0.5.26
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/membrane.d.ts +37 -0
- package/dist/membrane.d.ts.map +1 -1
- package/dist/membrane.js +590 -1
- package/dist/membrane.js.map +1 -1
- package/dist/providers/gemini.d.ts.map +1 -1
- package/dist/providers/gemini.js +9 -2
- package/dist/providers/gemini.js.map +1 -1
- package/dist/providers/mock.d.ts +8 -0
- package/dist/providers/mock.d.ts.map +1 -1
- package/dist/providers/mock.js +39 -2
- package/dist/providers/mock.js.map +1 -1
- package/dist/providers/openai-compatible.d.ts.map +1 -1
- package/dist/providers/openai-compatible.js +5 -1
- package/dist/providers/openai-compatible.js.map +1 -1
- package/dist/providers/openai.d.ts.map +1 -1
- package/dist/providers/openai.js +5 -1
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/openrouter.d.ts.map +1 -1
- package/dist/providers/openrouter.js +5 -1
- package/dist/providers/openrouter.js.map +1 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/yielding-stream.d.ts +167 -0
- package/dist/types/yielding-stream.d.ts.map +1 -0
- package/dist/types/yielding-stream.js +34 -0
- package/dist/types/yielding-stream.js.map +1 -0
- package/dist/yielding-stream.d.ts +60 -0
- package/dist/yielding-stream.d.ts.map +1 -0
- package/dist/yielding-stream.js +204 -0
- package/dist/yielding-stream.js.map +1 -0
- package/package.json +1 -1
- package/src/membrane.ts +729 -2
- package/src/providers/gemini.ts +11 -2
- package/src/providers/mock.ts +47 -2
- package/src/providers/openai-compatible.ts +8 -3
- package/src/providers/openai.ts +8 -3
- package/src/providers/openrouter.ts +8 -3
- package/src/types/index.ts +23 -0
- package/src/types/yielding-stream.ts +228 -0
- package/src/yielding-stream.ts +271 -0
package/src/providers/gemini.ts
CHANGED
|
@@ -54,6 +54,7 @@ interface GeminiRequest {
|
|
|
54
54
|
topP?: number;
|
|
55
55
|
topK?: number;
|
|
56
56
|
stopSequences?: string[];
|
|
57
|
+
responseModalities?: string[];
|
|
57
58
|
};
|
|
58
59
|
tools?: { functionDeclarations: GeminiFunctionDeclaration[] }[];
|
|
59
60
|
}
|
|
@@ -350,6 +351,11 @@ export class GeminiAdapter implements ProviderAdapter {
|
|
|
350
351
|
geminiRequest.generationConfig.stopSequences = request.stopSequences.slice(0, 5);
|
|
351
352
|
}
|
|
352
353
|
|
|
354
|
+
// Auto-detect image generation models by name
|
|
355
|
+
if (request.model?.includes('image')) {
|
|
356
|
+
geminiRequest.generationConfig.responseModalities = ['TEXT', 'IMAGE'];
|
|
357
|
+
}
|
|
358
|
+
|
|
353
359
|
// Tools
|
|
354
360
|
if (request.tools && request.tools.length > 0) {
|
|
355
361
|
geminiRequest.tools = [{
|
|
@@ -357,10 +363,13 @@ export class GeminiAdapter implements ProviderAdapter {
|
|
|
357
363
|
}];
|
|
358
364
|
}
|
|
359
365
|
|
|
360
|
-
// Extra params
|
|
366
|
+
// Extra params — deep-merge generationConfig to preserve auto-detected settings
|
|
361
367
|
if (request.extra) {
|
|
362
|
-
const { normalizedMessages, prompt, ...rest } = request.extra as Record<string, unknown>;
|
|
368
|
+
const { normalizedMessages, prompt, generationConfig: extraGenConfig, ...rest } = request.extra as Record<string, unknown>;
|
|
363
369
|
Object.assign(geminiRequest, rest);
|
|
370
|
+
if (extraGenConfig && typeof extraGenConfig === 'object') {
|
|
371
|
+
Object.assign(geminiRequest.generationConfig, extraGenConfig);
|
|
372
|
+
}
|
|
364
373
|
}
|
|
365
374
|
|
|
366
375
|
return geminiRequest;
|
package/src/providers/mock.ts
CHANGED
|
@@ -12,6 +12,7 @@ import type {
|
|
|
12
12
|
ProviderResponse,
|
|
13
13
|
StreamCallbacks,
|
|
14
14
|
} from '../types/provider.js';
|
|
15
|
+
import { abortError } from '../types/errors.js';
|
|
15
16
|
|
|
16
17
|
export interface MockAdapterConfig {
|
|
17
18
|
/** Default response text when no specific response is configured */
|
|
@@ -157,12 +158,15 @@ export class MockAdapter implements ProviderAdapter {
|
|
|
157
158
|
request: ProviderRequest,
|
|
158
159
|
options?: ProviderRequestOptions
|
|
159
160
|
): Promise<ProviderResponse> {
|
|
161
|
+
// Check for abort before starting
|
|
162
|
+
this.checkAbort(options?.signal);
|
|
163
|
+
|
|
160
164
|
// Call onRequest callback if provided
|
|
161
165
|
options?.onRequest?.(request);
|
|
162
166
|
|
|
163
167
|
// Simulate processing delay
|
|
164
168
|
if (this.config.completeDelayMs > 0) {
|
|
165
|
-
await this.
|
|
169
|
+
await this.abortableSleep(this.config.completeDelayMs, options?.signal);
|
|
166
170
|
}
|
|
167
171
|
|
|
168
172
|
const responseText = this.getResponse(request);
|
|
@@ -185,20 +189,28 @@ export class MockAdapter implements ProviderAdapter {
|
|
|
185
189
|
callbacks: StreamCallbacks,
|
|
186
190
|
options?: ProviderRequestOptions
|
|
187
191
|
): Promise<ProviderResponse> {
|
|
192
|
+
// Check for abort before starting
|
|
193
|
+
this.checkAbort(options?.signal);
|
|
194
|
+
|
|
188
195
|
// Call onRequest callback if provided
|
|
189
196
|
options?.onRequest?.(request);
|
|
190
197
|
|
|
191
198
|
const responseText = this.getResponse(request);
|
|
199
|
+
let streamedText = '';
|
|
192
200
|
|
|
193
201
|
// Stream the response in chunks
|
|
194
202
|
let offset = 0;
|
|
195
203
|
while (offset < responseText.length) {
|
|
204
|
+
// Check for abort before each chunk
|
|
205
|
+
this.checkAbort(options?.signal);
|
|
206
|
+
|
|
196
207
|
const chunk = responseText.slice(offset, offset + this.config.streamChunkSize);
|
|
197
208
|
callbacks.onChunk(chunk);
|
|
209
|
+
streamedText += chunk;
|
|
198
210
|
offset += this.config.streamChunkSize;
|
|
199
211
|
|
|
200
212
|
if (offset < responseText.length && this.config.streamChunkDelayMs > 0) {
|
|
201
|
-
await this.
|
|
213
|
+
await this.abortableSleep(this.config.streamChunkDelayMs, options?.signal);
|
|
202
214
|
}
|
|
203
215
|
}
|
|
204
216
|
|
|
@@ -215,6 +227,39 @@ export class MockAdapter implements ProviderAdapter {
|
|
|
215
227
|
};
|
|
216
228
|
}
|
|
217
229
|
|
|
230
|
+
/**
|
|
231
|
+
* Check if the abort signal is set and throw if so.
|
|
232
|
+
*/
|
|
233
|
+
private checkAbort(signal?: AbortSignal): void {
|
|
234
|
+
if (signal?.aborted) {
|
|
235
|
+
throw abortError('Request aborted');
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Sleep that can be interrupted by an abort signal.
|
|
241
|
+
*/
|
|
242
|
+
private abortableSleep(ms: number, signal?: AbortSignal): Promise<void> {
|
|
243
|
+
return new Promise((resolve, reject) => {
|
|
244
|
+
if (signal?.aborted) {
|
|
245
|
+
reject(abortError('Request aborted'));
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const onAbort = () => {
|
|
250
|
+
clearTimeout(timeout);
|
|
251
|
+
reject(abortError('Request aborted'));
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
const timeout = setTimeout(() => {
|
|
255
|
+
signal?.removeEventListener('abort', onAbort);
|
|
256
|
+
resolve();
|
|
257
|
+
}, ms);
|
|
258
|
+
|
|
259
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
218
263
|
private sleep(ms: number): Promise<void> {
|
|
219
264
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
220
265
|
}
|
|
@@ -350,16 +350,21 @@ export class OpenAICompatibleAdapter implements ProviderAdapter {
|
|
|
350
350
|
return toolResults;
|
|
351
351
|
}
|
|
352
352
|
|
|
353
|
+
// Skip messages with no usable content (image-only, embed-only messages)
|
|
354
|
+
if (textParts.length === 0 && toolCalls.length === 0) {
|
|
355
|
+
return [];
|
|
356
|
+
}
|
|
357
|
+
|
|
353
358
|
// Otherwise build normal message
|
|
354
359
|
const result: OpenAIMessage = {
|
|
355
360
|
role: msg.role,
|
|
356
|
-
content: textParts.join('\n')
|
|
361
|
+
content: textParts.length > 0 ? textParts.join('\n') : null,
|
|
357
362
|
};
|
|
358
|
-
|
|
363
|
+
|
|
359
364
|
if (toolCalls.length > 0) {
|
|
360
365
|
result.tool_calls = toolCalls;
|
|
361
366
|
}
|
|
362
|
-
|
|
367
|
+
|
|
363
368
|
return [result];
|
|
364
369
|
}
|
|
365
370
|
|
package/src/providers/openai.ts
CHANGED
|
@@ -449,16 +449,21 @@ export class OpenAIAdapter implements ProviderAdapter {
|
|
|
449
449
|
return toolResults;
|
|
450
450
|
}
|
|
451
451
|
|
|
452
|
+
// Skip messages with no usable content (image-only, embed-only messages)
|
|
453
|
+
if (textParts.length === 0 && toolCalls.length === 0) {
|
|
454
|
+
return [];
|
|
455
|
+
}
|
|
456
|
+
|
|
452
457
|
// Otherwise build normal message
|
|
453
458
|
const result: OpenAIMessage = {
|
|
454
459
|
role: msg.role,
|
|
455
|
-
content: textParts.join('\n')
|
|
460
|
+
content: textParts.length > 0 ? textParts.join('\n') : null,
|
|
456
461
|
};
|
|
457
|
-
|
|
462
|
+
|
|
458
463
|
if (toolCalls.length > 0) {
|
|
459
464
|
result.tool_calls = toolCalls;
|
|
460
465
|
}
|
|
461
|
-
|
|
466
|
+
|
|
462
467
|
return [result];
|
|
463
468
|
}
|
|
464
469
|
|
|
@@ -407,17 +407,22 @@ export class OpenRouterAdapter implements ProviderAdapter {
|
|
|
407
407
|
return toolResults;
|
|
408
408
|
}
|
|
409
409
|
|
|
410
|
+
// Skip messages with no usable content (image-only, embed-only messages)
|
|
411
|
+
if (textParts.length === 0 && toolCalls.length === 0) {
|
|
412
|
+
return [];
|
|
413
|
+
}
|
|
414
|
+
|
|
410
415
|
// Otherwise build normal message
|
|
411
416
|
const result: OpenRouterMessage = {
|
|
412
417
|
role: msg.role,
|
|
413
418
|
// Use content blocks array if caching is in use, otherwise concatenate text
|
|
414
|
-
content: hasCache ? contentBlocks : (textParts.join('\n')
|
|
419
|
+
content: hasCache ? contentBlocks : (textParts.length > 0 ? textParts.join('\n') : null),
|
|
415
420
|
};
|
|
416
|
-
|
|
421
|
+
|
|
417
422
|
if (toolCalls.length > 0) {
|
|
418
423
|
result.tool_calls = toolCalls;
|
|
419
424
|
}
|
|
420
|
-
|
|
425
|
+
|
|
421
426
|
return [result];
|
|
422
427
|
}
|
|
423
428
|
|
package/src/types/index.ts
CHANGED
|
@@ -120,6 +120,29 @@ export type {
|
|
|
120
120
|
BlockDelta,
|
|
121
121
|
} from './streaming.js';
|
|
122
122
|
|
|
123
|
+
// Yielding Stream
|
|
124
|
+
export type {
|
|
125
|
+
TokensEvent,
|
|
126
|
+
StreamBlockEvent,
|
|
127
|
+
ToolCallsEvent,
|
|
128
|
+
UsageEvent,
|
|
129
|
+
CompleteEvent,
|
|
130
|
+
ErrorEvent,
|
|
131
|
+
AbortedEvent,
|
|
132
|
+
StreamEvent,
|
|
133
|
+
YieldingStream,
|
|
134
|
+
YieldingStreamOptions,
|
|
135
|
+
} from './yielding-stream.js';
|
|
136
|
+
|
|
137
|
+
export {
|
|
138
|
+
isTokensEvent,
|
|
139
|
+
isToolCallsEvent,
|
|
140
|
+
isCompleteEvent,
|
|
141
|
+
isErrorEvent,
|
|
142
|
+
isAbortedEvent,
|
|
143
|
+
isTerminalEvent,
|
|
144
|
+
} from './yielding-stream.js';
|
|
145
|
+
|
|
123
146
|
// Errors
|
|
124
147
|
export type {
|
|
125
148
|
MembraneErrorType,
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Yielding stream types for membrane
|
|
3
|
+
*
|
|
4
|
+
* This module defines the interface for a streaming API that yields control
|
|
5
|
+
* back to the caller when tool calls are detected, rather than handling them
|
|
6
|
+
* internally via callbacks.
|
|
7
|
+
*
|
|
8
|
+
* @see agent-framework/docs/yielding-stream-architecture.md
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { ContentBlock } from './content.js';
|
|
12
|
+
import type { ToolCall, ToolResult, ToolContext } from './tools.js';
|
|
13
|
+
import type { BasicUsage, NormalizedResponse, StopReason } from './response.js';
|
|
14
|
+
import type { ChunkMeta, BlockEvent } from './streaming.js';
|
|
15
|
+
|
|
16
|
+
// ============================================================================
|
|
17
|
+
// Stream Events
|
|
18
|
+
// ============================================================================
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Token/chunk event - raw text as it arrives from the LLM.
|
|
22
|
+
*/
|
|
23
|
+
export interface TokensEvent {
|
|
24
|
+
type: 'tokens';
|
|
25
|
+
content: string;
|
|
26
|
+
meta: ChunkMeta;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Block event - structural block start/complete notifications.
|
|
31
|
+
*/
|
|
32
|
+
export interface StreamBlockEvent {
|
|
33
|
+
type: 'block';
|
|
34
|
+
event: BlockEvent;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Tool calls event - LLM has requested tool execution.
|
|
39
|
+
* The stream pauses here until results are provided via provideToolResults().
|
|
40
|
+
*/
|
|
41
|
+
export interface ToolCallsEvent {
|
|
42
|
+
type: 'tool-calls';
|
|
43
|
+
calls: ToolCall[];
|
|
44
|
+
context: ToolContext;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Usage update event - token counts updated.
|
|
49
|
+
*/
|
|
50
|
+
export interface UsageEvent {
|
|
51
|
+
type: 'usage';
|
|
52
|
+
usage: BasicUsage;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Complete event - inference cycle finished successfully.
|
|
57
|
+
*/
|
|
58
|
+
export interface CompleteEvent {
|
|
59
|
+
type: 'complete';
|
|
60
|
+
response: NormalizedResponse;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Error event - something went wrong.
|
|
65
|
+
*/
|
|
66
|
+
export interface ErrorEvent {
|
|
67
|
+
type: 'error';
|
|
68
|
+
error: Error;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Aborted event - stream was cancelled.
|
|
73
|
+
*/
|
|
74
|
+
export interface AbortedEvent {
|
|
75
|
+
type: 'aborted';
|
|
76
|
+
reason: 'user' | 'timeout' | 'error';
|
|
77
|
+
partialContent?: ContentBlock[];
|
|
78
|
+
rawAssistantText?: string;
|
|
79
|
+
toolCalls?: ToolCall[];
|
|
80
|
+
toolResults?: ToolResult[];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Union of all stream events.
|
|
85
|
+
*/
|
|
86
|
+
export type StreamEvent =
|
|
87
|
+
| TokensEvent
|
|
88
|
+
| StreamBlockEvent
|
|
89
|
+
| ToolCallsEvent
|
|
90
|
+
| UsageEvent
|
|
91
|
+
| CompleteEvent
|
|
92
|
+
| ErrorEvent
|
|
93
|
+
| AbortedEvent;
|
|
94
|
+
|
|
95
|
+
// ============================================================================
|
|
96
|
+
// Yielding Stream Interface
|
|
97
|
+
// ============================================================================
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* A streaming inference that yields control to the caller for tool execution.
|
|
101
|
+
*
|
|
102
|
+
* Usage:
|
|
103
|
+
* ```typescript
|
|
104
|
+
* const stream = membrane.streamYielding(request, options);
|
|
105
|
+
*
|
|
106
|
+
* for await (const event of stream) {
|
|
107
|
+
* switch (event.type) {
|
|
108
|
+
* case 'tokens':
|
|
109
|
+
* process.stdout.write(event.content);
|
|
110
|
+
* break;
|
|
111
|
+
* case 'tool-calls':
|
|
112
|
+
* const results = await executeTools(event.calls);
|
|
113
|
+
* stream.provideToolResults(results);
|
|
114
|
+
* break;
|
|
115
|
+
* case 'complete':
|
|
116
|
+
* console.log('Done:', event.response);
|
|
117
|
+
* break;
|
|
118
|
+
* case 'error':
|
|
119
|
+
* console.error('Error:', event.error);
|
|
120
|
+
* break;
|
|
121
|
+
* }
|
|
122
|
+
* }
|
|
123
|
+
* ```
|
|
124
|
+
*/
|
|
125
|
+
export interface YieldingStream extends AsyncIterable<StreamEvent> {
|
|
126
|
+
/**
|
|
127
|
+
* Provide tool results after receiving a 'tool-calls' event.
|
|
128
|
+
* The stream will resume and continue generating.
|
|
129
|
+
*
|
|
130
|
+
* @param results - Results for the tool calls (must match call IDs)
|
|
131
|
+
* @throws Error if called when not waiting for tool results
|
|
132
|
+
*/
|
|
133
|
+
provideToolResults(results: ToolResult[]): void;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Cancel the stream. Any in-flight requests will be aborted.
|
|
137
|
+
* The iterator will yield an 'aborted' event and then complete.
|
|
138
|
+
*/
|
|
139
|
+
cancel(): void;
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Check if the stream is currently waiting for tool results.
|
|
143
|
+
*/
|
|
144
|
+
readonly isWaitingForTools: boolean;
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Get the IDs of tool calls we're waiting for results for.
|
|
148
|
+
* Empty if not waiting for tools.
|
|
149
|
+
*/
|
|
150
|
+
readonly pendingToolCallIds: string[];
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Current tool execution depth (0 = first inference, 1 = after first tool round, etc.)
|
|
154
|
+
*/
|
|
155
|
+
readonly toolDepth: number;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ============================================================================
|
|
159
|
+
// Yielding Stream Options
|
|
160
|
+
// ============================================================================
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Options for streamYielding().
|
|
164
|
+
* Simpler than StreamOptions since tool execution is handled externally.
|
|
165
|
+
*/
|
|
166
|
+
export interface YieldingStreamOptions {
|
|
167
|
+
/** Abort signal for cancellation */
|
|
168
|
+
signal?: AbortSignal;
|
|
169
|
+
|
|
170
|
+
/** Request timeout (per API call, not total) */
|
|
171
|
+
timeoutMs?: number;
|
|
172
|
+
|
|
173
|
+
/** Request ID for correlation/logging */
|
|
174
|
+
requestId?: string;
|
|
175
|
+
|
|
176
|
+
/** Maximum tool execution depth (default: 10) */
|
|
177
|
+
maxToolDepth?: number;
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Whether to emit 'tokens' events.
|
|
181
|
+
* Set to false if you only care about tool calls and final response.
|
|
182
|
+
* Default: true
|
|
183
|
+
*/
|
|
184
|
+
emitTokens?: boolean;
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Whether to emit 'block' events.
|
|
188
|
+
* Default: true
|
|
189
|
+
*/
|
|
190
|
+
emitBlocks?: boolean;
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Whether to emit 'usage' events.
|
|
194
|
+
* Default: true
|
|
195
|
+
*/
|
|
196
|
+
emitUsage?: boolean;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// ============================================================================
|
|
200
|
+
// Type Guards
|
|
201
|
+
// ============================================================================
|
|
202
|
+
|
|
203
|
+
export function isTokensEvent(event: StreamEvent): event is TokensEvent {
|
|
204
|
+
return event.type === 'tokens';
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export function isToolCallsEvent(event: StreamEvent): event is ToolCallsEvent {
|
|
208
|
+
return event.type === 'tool-calls';
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function isCompleteEvent(event: StreamEvent): event is CompleteEvent {
|
|
212
|
+
return event.type === 'complete';
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export function isErrorEvent(event: StreamEvent): event is ErrorEvent {
|
|
216
|
+
return event.type === 'error';
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export function isAbortedEvent(event: StreamEvent): event is AbortedEvent {
|
|
220
|
+
return event.type === 'aborted';
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Check if the stream has terminated (complete, error, or aborted).
|
|
225
|
+
*/
|
|
226
|
+
export function isTerminalEvent(event: StreamEvent): event is CompleteEvent | ErrorEvent | AbortedEvent {
|
|
227
|
+
return event.type === 'complete' || event.type === 'error' || event.type === 'aborted';
|
|
228
|
+
}
|