ai 6.0.219 → 6.0.221

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.
@@ -152,7 +152,7 @@ function detectMediaType({
152
152
  var import_provider_utils2 = require("@ai-sdk/provider-utils");
153
153
 
154
154
  // src/version.ts
155
- var VERSION = true ? "6.0.219" : "0.0.0-test";
155
+ var VERSION = true ? "6.0.221" : "0.0.0-test";
156
156
 
157
157
  // src/util/download/download.ts
158
158
  var download = async ({
@@ -132,7 +132,7 @@ import {
132
132
  } from "@ai-sdk/provider-utils";
133
133
 
134
134
  // src/version.ts
135
- var VERSION = true ? "6.0.219" : "0.0.0-test";
135
+ var VERSION = true ? "6.0.221" : "0.0.0-test";
136
136
 
137
137
  // src/util/download/download.ts
138
138
  var download = async ({
@@ -82,7 +82,7 @@ The open-source community has created the following providers:
82
82
  - [Browser AI Provider](/providers/community-providers/browser-ai) (`browser-ai`)
83
83
  - [Gemini CLI Provider](/providers/community-providers/gemini-cli) (`ai-sdk-provider-gemini-cli`)
84
84
  - [A2A Provider](/providers/community-providers/a2a) (`a2a-ai-provider`)
85
- - [SAP-AI Provider](/providers/community-providers/sap-ai) (`@mymediset/sap-ai-provider`)
85
+ - [SAP AI Core Provider](/providers/community-providers/sap-ai) (`@jerome-benoit/sap-ai-provider`)
86
86
  - [AI/ML API Provider](/providers/community-providers/aimlapi) (`@ai-ml.api/aimlapi-vercel-ai`)
87
87
  - [MCP Sampling Provider](/providers/community-providers/mcp-sampling) (`@mcpc-tech/mcp-sampling-ai-provider`)
88
88
  - [ACP Provider](/providers/community-providers/acp) (`@mcpc-tech/acp-ai-provider`)
@@ -207,6 +207,55 @@ The `bankId` identifies the memory store and is typically a user ID. In multi-us
207
207
 
208
208
  See the [Hindsight provider documentation](/providers/community-providers/hindsight) for full setup and configuration.
209
209
 
210
+ ### MongoDB
211
+
212
+ [`@mongodb-developer/vercel-ai-memory`](https://www.npmjs.com/package/@mongodb-developer/vercel-ai-memory) provides MongoDB Atlas-backed persistent memory with five structured tiers: **Session**, **Semantic**, **Procedural**, **Episodic**, and **Scratchpad**. Retrieval is powered by Atlas Vector Search using any AI SDK embedding model, with automatic index creation and per-type retention policies.
213
+
214
+ ```bash
215
+ pnpm add @mongodb-developer/vercel-ai-memory
216
+ ```
217
+
218
+ This integration targets AI SDK v6 because it relies on v6 APIs such as `ModelMessage`, `ToolLoopAgent`, and `isLoopFinished()`:
219
+
220
+ ```json
221
+ {
222
+ "peerDependencies": {
223
+ "ai": "^6.0.0",
224
+ "mongodb": "^6.0.0",
225
+ "zod": "^3.0.0"
226
+ }
227
+ }
228
+ ```
229
+
230
+ ```ts
231
+ import { createMongoDBMemory } from '@mongodb-developer/vercel-ai-memory';
232
+ import { openai } from '@ai-sdk/openai';
233
+ import { ToolLoopAgent, isLoopFinished } from 'ai';
234
+
235
+ // Create the memory instance once at module/server level
236
+ const mongodbMemory = createMongoDBMemory({
237
+ uri: process.env.MONGODB_URI!,
238
+ embedder: openai.embedding('text-embedding-3-small'),
239
+ });
240
+
241
+ // Scope to a user and session per request
242
+ const agent = new ToolLoopAgent({
243
+ model: openai('gpt-4.1'),
244
+ tools: mongodbMemory({ userId: 'alice', sessionId: 'sess-001' }),
245
+ stopWhen: isLoopFinished(),
246
+ });
247
+
248
+ const result = await agent.generate({
249
+ prompt: 'My name is Alice and I love hiking. Remember that.',
250
+ });
251
+ ```
252
+
253
+ `isLoopFinished()` lets the agent keep running until the tool loop naturally finishes, which is useful when memory tools need to read and write before the final response.
254
+
255
+ Session memory supports two modes: **tool-driven** (the LLM decides when to read/write — good for prototypes) and **hook-driven** (the runtime persists every turn via `prepareCall` + `onFinish` hooks — recommended for production). The other memory tiers (semantic, procedural, episodic, scratchpad) are always LLM-controlled and selective by design.
256
+
257
+ MongoDB memory works with any AI SDK model and embedding provider, with no vendor lock-in beyond MongoDB Atlas.
258
+
210
259
  **When to use memory providers**: these providers are a good fit when you want memory without building any storage infrastructure. The tradeoff is that the provider controls memory behavior, so you have less visibility into what gets stored and how it is retrieved. You also take on a dependency on an external service.
211
260
 
212
261
  ## Custom Tool
@@ -307,9 +307,13 @@ export const yourLogMiddleware: LanguageModelV3Middleware = {
307
307
  console.log(`params: ${JSON.stringify(params, null, 2)}`);
308
308
 
309
309
  const result = await doGenerate();
310
+ const generatedText = result.content
311
+ .filter(part => part.type === 'text')
312
+ .map(part => part.text)
313
+ .join('');
310
314
 
311
315
  console.log('doGenerate finished');
312
- console.log(`generated text: ${result.text}`);
316
+ console.log(`generated text: ${generatedText}`);
313
317
 
314
318
  return result;
315
319
  },
@@ -437,12 +441,16 @@ import type { LanguageModelV3Middleware } from '@ai-sdk/provider';
437
441
 
438
442
  export const yourGuardrailMiddleware: LanguageModelV3Middleware = {
439
443
  wrapGenerate: async ({ doGenerate }) => {
440
- const { text, ...rest } = await doGenerate();
444
+ const result = await doGenerate();
441
445
 
442
446
  // filtering approach, e.g. for PII or other sensitive information:
443
- const cleanedText = text?.replace(/badword/g, '<REDACTED>');
447
+ const content = result.content.map(part =>
448
+ part.type === 'text'
449
+ ? { ...part, text: part.text.replace(/badword/g, '<REDACTED>') }
450
+ : part,
451
+ );
444
452
 
445
- return { text: cleanedText, ...rest };
453
+ return { ...result, content };
446
454
  },
447
455
 
448
456
  // here you would implement the guardrail logic for streaming
@@ -345,11 +345,20 @@ By default, message IDs are generated client-side:
345
345
  - AI response message IDs are generated by `streamText` on the server
346
346
 
347
347
  For applications without persistence, client-side ID generation works perfectly.
348
- However, **for persistence, you need server-side generated IDs** to ensure consistency across sessions and prevent ID conflicts when messages are stored and retrieved.
348
+ However, **for persistence, you should use IDs that are stable before messages
349
+ are stored** to ensure consistency across sessions and prevent ID conflicts when
350
+ messages are restored.
351
+
352
+ The server-side options below control IDs for generated assistant response
353
+ messages. User message IDs are created by `useChat` before the request is sent
354
+ to your API route, so keep those client-generated IDs when saving incoming
355
+ request messages, or generate and persist your own user message IDs before
356
+ sending/storing them.
349
357
 
350
358
  ### Setting Up Server-side ID Generation
351
359
 
352
- When implementing persistence, you have two options for generating server-side IDs:
360
+ When implementing persistence, you have two options for generating server-side
361
+ IDs for assistant response messages:
353
362
 
354
363
  1. **Using `generateMessageId` in `toUIMessageStreamResponse`**
355
364
  2. **Setting IDs in your start message part with `createUIMessageStream`**
@@ -40,7 +40,7 @@ You can create a GitHub repository from within your terminal, or on [github.com]
40
40
 
41
41
  To create your GitHub repository:
42
42
 
43
- 1. Navigate to [github.com](http://github.com/)
43
+ 1. Navigate to [github.com](https://github.com/)
44
44
  2. In the top right corner, click the "plus" icon and select "New repository"
45
45
  3. Pick a name for your repository (this can be anything)
46
46
  4. Click "Create repository"
@@ -139,7 +139,7 @@ export const weatherTool = tool({
139
139
  isOptional: true,
140
140
  type: '({toolCallId: string; input: INPUT; output: OUTPUT}) => ToolResultOutput | PromiseLike<ToolResultOutput>',
141
141
  description:
142
- 'Optional conversion function that maps the tool result to an output that can be used by the language model. If not provided, the tool result will be sent as a JSON object.',
142
+ 'Optional conversion function that maps the tool result to an output that can be used by the language model. If not provided, the tool result will be sent as a JSON object. This function is invoked on the server by "convertToModelMessages", so ensure that you pass the same "tools" (ToolSet) to both "convertToModelMessages" and "streamText" (or other generation APIs).',
143
143
  },
144
144
  {
145
145
  name: 'onInputStart',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai",
3
- "version": "6.0.219",
3
+ "version": "6.0.221",
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,9 +45,9 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "@opentelemetry/api": "^1.9.0",
48
- "@ai-sdk/gateway": "3.0.143",
48
+ "@ai-sdk/gateway": "3.0.145",
49
49
  "@ai-sdk/provider": "3.0.13",
50
- "@ai-sdk/provider-utils": "4.0.35"
50
+ "@ai-sdk/provider-utils": "4.0.37"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@edge-runtime/vm": "^5.0.0",
@@ -151,6 +151,16 @@ export type ToolLoopAgentSettings<
151
151
  * Prepare the parameters for the generateText or streamText call.
152
152
  *
153
153
  * You can use this to have templates based on call options.
154
+ *
155
+ * The design requires you to pass call parameters as follows to
156
+ * allow for the removal of parameters from the original settings
157
+ * by setting them to `undefined`:
158
+ *
159
+ * ```
160
+ * prepareCall: ({ options, ...rest }) => ({
161
+ * ...rest,
162
+ * }),
163
+ * ```
154
164
  */
155
165
  prepareCall?: (
156
166
  options: Omit<
@@ -52,6 +52,7 @@ import { asArray } from '../util/as-array';
52
52
  import type { DownloadFunction } from '../util/download/download-function';
53
53
  import { mergeObjects } from '../util/merge-objects';
54
54
  import { prepareRetries } from '../util/prepare-retries';
55
+ import { setAbortTimeout } from '../util/set-abort-timeout';
55
56
  import { VERSION } from '../version';
56
57
  import type {
57
58
  OnFinishEvent,
@@ -684,10 +685,11 @@ export async function generateText<
684
685
 
685
686
  do {
686
687
  // Set up step timeout if configured
687
- const stepTimeoutId =
688
- stepTimeoutMs != null
689
- ? setTimeout(() => stepAbortController!.abort(), stepTimeoutMs)
690
- : undefined;
688
+ const stepTimeoutId = setAbortTimeout({
689
+ abortController: stepAbortController,
690
+ label: 'Step',
691
+ timeoutMs: stepTimeoutMs,
692
+ });
691
693
 
692
694
  try {
693
695
  const stepInputMessages = [...initialMessages, ...responseMessages];
@@ -278,6 +278,7 @@ export const array = <ELEMENT>({
278
278
  });
279
279
  }
280
280
 
281
+ const validatedElements: Array<ELEMENT> = [];
281
282
  for (const element of outerValue.elements) {
282
283
  const validationResult = await safeValidateTypes({
283
284
  value: element,
@@ -294,9 +295,11 @@ export const array = <ELEMENT>({
294
295
  finishReason: context.finishReason,
295
296
  });
296
297
  }
298
+
299
+ validatedElements.push(validationResult.value);
297
300
  }
298
301
 
299
- return outerValue.elements as Array<ELEMENT>;
302
+ return validatedElements;
300
303
  },
301
304
 
302
305
  async parsePartialOutput({ text }: { text: string }) {
@@ -171,10 +171,8 @@ export function runToolsTransformation<TOOLS extends ToolSet>({
171
171
  // keep track of outstanding tool results for stream closing:
172
172
  const outstandingToolResults = new Set<string>();
173
173
 
174
- // keep track of tool inputs for provider-side tool results
175
- const toolInputs = new Map<string, unknown>();
176
-
177
174
  // keep track of parsed tool calls so provider-emitted approval requests can reference them
175
+ // keep track of tool inputs for provider-side tool results
178
176
  const toolCallsByToolCallId = new Map<string, TypedToolCall<TOOLS>>();
179
177
 
180
178
  let canClose = false;
@@ -349,8 +347,6 @@ export function runToolsTransformation<TOOLS extends ToolSet>({
349
347
  break;
350
348
  }
351
349
 
352
- toolInputs.set(toolCall.toolCallId, toolCall.input);
353
-
354
350
  // Only execute tools that are not provider-executed:
355
351
  if (tool.execute != null && toolCall.providerExecuted !== true) {
356
352
  const toolExecutionId = generateId(); // use our own id to guarantee uniqueness
@@ -405,7 +401,7 @@ export function runToolsTransformation<TOOLS extends ToolSet>({
405
401
  type: 'tool-error',
406
402
  toolCallId: chunk.toolCallId,
407
403
  toolName,
408
- input: toolInputs.get(chunk.toolCallId),
404
+ input: toolCall?.input,
409
405
  providerExecuted: true,
410
406
  error: chunk.result,
411
407
  dynamic: chunk.dynamic,
@@ -421,7 +417,7 @@ export function runToolsTransformation<TOOLS extends ToolSet>({
421
417
  type: 'tool-result',
422
418
  toolCallId: chunk.toolCallId,
423
419
  toolName,
424
- input: toolInputs.get(chunk.toolCallId),
420
+ input: toolCall?.input,
425
421
  output: chunk.result,
426
422
  providerExecuted: true,
427
423
  dynamic: chunk.dynamic,
@@ -82,6 +82,7 @@ import { mergeAbortSignals } from '../util/merge-abort-signals';
82
82
  import { mergeObjects } from '../util/merge-objects';
83
83
  import { now as originalNow } from '../util/now';
84
84
  import { prepareRetries } from '../util/prepare-retries';
85
+ import { setAbortTimeout } from '../util/set-abort-timeout';
85
86
  import { collectToolApprovals } from './collect-tool-approvals';
86
87
  import { validateApprovedToolApprovals } from './validate-tool-approvals';
87
88
  import type {
@@ -1567,25 +1568,25 @@ class DefaultStreamTextResult<
1567
1568
  const includeRawChunks = self.includeRawChunks;
1568
1569
 
1569
1570
  // Set up step timeout if configured
1570
- const stepTimeoutId =
1571
- stepTimeoutMs != null
1572
- ? setTimeout(() => stepAbortController!.abort(), stepTimeoutMs)
1573
- : undefined;
1571
+ const stepTimeoutId = setAbortTimeout({
1572
+ abortController: stepAbortController,
1573
+ label: 'Step',
1574
+ timeoutMs: stepTimeoutMs,
1575
+ });
1574
1576
 
1575
1577
  // Set up chunk timeout tracking (will be reset on each chunk)
1576
1578
  let chunkTimeoutId: ReturnType<typeof setTimeout> | undefined =
1577
1579
  undefined;
1578
1580
 
1579
1581
  function resetChunkTimeout() {
1580
- if (chunkTimeoutMs != null) {
1581
- if (chunkTimeoutId != null) {
1582
- clearTimeout(chunkTimeoutId);
1583
- }
1584
- chunkTimeoutId = setTimeout(
1585
- () => chunkAbortController!.abort(),
1586
- chunkTimeoutMs,
1587
- );
1582
+ if (chunkTimeoutId != null) {
1583
+ clearTimeout(chunkTimeoutId);
1588
1584
  }
1585
+ chunkTimeoutId = setAbortTimeout({
1586
+ abortController: chunkAbortController,
1587
+ label: 'Chunk',
1588
+ timeoutMs: chunkTimeoutMs,
1589
+ });
1589
1590
  }
1590
1591
 
1591
1592
  function clearChunkTimeout() {
@@ -1601,6 +1602,13 @@ class DefaultStreamTextResult<
1601
1602
  }
1602
1603
  }
1603
1604
 
1605
+ // The step's stream is registered lazily and consumed long after this
1606
+ // function returns, so the step timer must stay armed past setup. When
1607
+ // the merged abort signal fires (any step/chunk/total timeout or caller
1608
+ // abort), drop both step-scoped timers so neither outlives the step.
1609
+ abortSignal?.addEventListener('abort', clearStepTimeout);
1610
+ abortSignal?.addEventListener('abort', clearChunkTimeout);
1611
+
1604
1612
  try {
1605
1613
  stepFinish = new DelayedPromise<void>();
1606
1614
 
@@ -2244,9 +2252,12 @@ class DefaultStreamTextResult<
2244
2252
  }),
2245
2253
  ),
2246
2254
  );
2247
- } finally {
2255
+ } catch (error) {
2256
+ // Setup failed before the stream was registered, so neither the
2257
+ // stream's flush nor an abort will clear the timers — clear them here.
2248
2258
  clearStepTimeout();
2249
2259
  clearChunkTimeout();
2260
+ throw error;
2250
2261
  }
2251
2262
  }
2252
2263
 
@@ -19,6 +19,7 @@ export async function toResponseMessages<TOOLS extends ToolSet>({
19
19
  tools: TOOLS | undefined;
20
20
  }): Promise<Array<AssistantModelMessage | ToolModelMessage>> {
21
21
  const responseMessages: Array<AssistantModelMessage | ToolModelMessage> = [];
22
+ const toolCallOrder = new Map<string, number>();
22
23
 
23
24
  const content: AssistantContent = [];
24
25
  for (const part of inputContent) {
@@ -64,6 +65,9 @@ export async function toResponseMessages<TOOLS extends ToolSet>({
64
65
  });
65
66
  break;
66
67
  case 'tool-call':
68
+ if (!toolCallOrder.has(part.toolCallId)) {
69
+ toolCallOrder.set(part.toolCallId, toolCallOrder.size);
70
+ }
67
71
  content.push({
68
72
  type: 'tool-call',
69
73
  toolCallId: part.toolCallId,
@@ -157,9 +161,49 @@ export async function toResponseMessages<TOOLS extends ToolSet>({
157
161
  if (toolResultContent.length > 0) {
158
162
  responseMessages.push({
159
163
  role: 'tool',
160
- content: toolResultContent,
164
+ content: sortToolResultContentByToolCallOrder({
165
+ toolResultContent,
166
+ toolCallOrder,
167
+ }),
161
168
  });
162
169
  }
163
170
 
164
171
  return responseMessages;
165
172
  }
173
+
174
+ function sortToolResultContentByToolCallOrder({
175
+ toolResultContent,
176
+ toolCallOrder,
177
+ }: {
178
+ toolResultContent: ToolContent;
179
+ toolCallOrder: Map<string, number>;
180
+ }): ToolContent {
181
+ const sortedToolResults = toolResultContent
182
+ .filter(part => part.type === 'tool-result')
183
+ .map((part, index) => ({ part, index }))
184
+ .sort((a, b) => {
185
+ const aOrder = toolCallOrder.get(a.part.toolCallId);
186
+ const bOrder = toolCallOrder.get(b.part.toolCallId);
187
+
188
+ if (aOrder == null && bOrder == null) {
189
+ return a.index - b.index;
190
+ }
191
+
192
+ if (aOrder == null) {
193
+ return 1;
194
+ }
195
+
196
+ if (bOrder == null) {
197
+ return -1;
198
+ }
199
+
200
+ return aOrder - bOrder || a.index - b.index;
201
+ })
202
+ .map(({ part }) => part);
203
+
204
+ let toolResultIndex = 0;
205
+
206
+ return toolResultContent.map(part =>
207
+ part.type === 'tool-result' ? sortedToolResults[toolResultIndex++] : part,
208
+ );
209
+ }
@@ -15,6 +15,10 @@ function defaultTransform(text: string): string {
15
15
  .trim();
16
16
  }
17
17
 
18
+ function stripMarkdownCodeFenceSuffix(text: string): string {
19
+ return text.replace(/\n?```\s*$/, '').trimEnd();
20
+ }
21
+
18
22
  /**
19
23
  * Middleware that extracts JSON from text content by stripping
20
24
  * markdown code fences and other formatting.
@@ -169,10 +173,15 @@ export function extractJsonMiddleware(options?: {
169
173
  remaining = transform(remaining);
170
174
  } else if (block.prefixStripped) {
171
175
  // strip suffix since prefix already handled
172
- remaining = remaining.replace(/\n?```\s*$/, '').trimEnd();
173
- } else {
174
- // Apply full transform (handles both prefix and suffix)
176
+ remaining = stripMarkdownCodeFenceSuffix(remaining);
177
+ } else if (block.phase === 'prefix') {
178
+ // No text has streamed yet, so the full transform is safe.
175
179
  remaining = transform(remaining);
180
+ } else {
181
+ // Only strip the suffix. Since earlier text may already have
182
+ // streamed, trimming the remaining suffix would remove valid
183
+ // leading whitespace at the stream boundary.
184
+ remaining = stripMarkdownCodeFenceSuffix(remaining);
176
185
  }
177
186
 
178
187
  if (remaining.length > 0) {
package/src/ui/chat.ts CHANGED
@@ -494,7 +494,7 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
494
494
  ? {
495
495
  ...part,
496
496
  state: 'approval-responded',
497
- approval: { id, approved, reason },
497
+ approval: { ...part.approval, id, approved, reason },
498
498
  }
499
499
  : part;
500
500
 
@@ -246,10 +246,12 @@ export async function convertToModelMessages<UI_MESSAGE extends UIMessage>(
246
246
  }
247
247
  }
248
248
 
249
- modelMessages.push({
250
- role: 'assistant',
251
- content,
252
- });
249
+ if (content.length > 0) {
250
+ modelMessages.push({
251
+ role: 'assistant',
252
+ content,
253
+ });
254
+ }
253
255
 
254
256
  // check if there are tool invocations with results in the block
255
257
  // Include non-provider-executed tools, OR provider-executed tools with approval responses
@@ -297,7 +299,7 @@ export async function convertToModelMessages<UI_MESSAGE extends UIMessage>(
297
299
  type: 'error-text' as const,
298
300
  value:
299
301
  toolPart.approval?.reason ??
300
- 'Tool execution denied.',
302
+ 'Tool call execution denied.',
301
303
  },
302
304
  ...(toolPart.callProviderMetadata != null
303
305
  ? { providerOptions: toolPart.callProviderMetadata }
package/src/ui/index.ts CHANGED
@@ -37,6 +37,7 @@ export {
37
37
  getToolName,
38
38
  getToolOrDynamicToolName,
39
39
  isDataUIPart,
40
+ isDynamicToolUIPart,
40
41
  isFileUIPart,
41
42
  isReasoningUIPart,
42
43
  isStaticToolUIPart,
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Schedules a timeout that aborts the given controller with a `TimeoutError`
3
+ * `DOMException`, matching the reason produced by `AbortSignal.timeout(ms)`.
4
+ *
5
+ * @param abortController - The controller to abort when the timeout elapses.
6
+ * If undefined, no timeout is scheduled.
7
+ * @param label - Human-readable label included in the error message
8
+ * (e.g. "Step", "Chunk").
9
+ * @param timeoutMs - Duration in milliseconds before the controller is aborted.
10
+ * If undefined, no timeout is scheduled.
11
+ * @returns The timeout id (suitable for passing to `clearTimeout`), or
12
+ * `undefined` if no timeout was scheduled.
13
+ */
14
+ export function setAbortTimeout({
15
+ abortController,
16
+ label,
17
+ timeoutMs,
18
+ }: {
19
+ abortController: AbortController | undefined;
20
+ label: string;
21
+ timeoutMs: number | undefined;
22
+ }): ReturnType<typeof setTimeout> | undefined {
23
+ if (abortController == null || timeoutMs == null) {
24
+ return undefined;
25
+ }
26
+
27
+ return setTimeout(
28
+ () =>
29
+ abortController.abort(
30
+ new DOMException(
31
+ `${label} timeout of ${timeoutMs}ms exceeded`,
32
+ 'TimeoutError',
33
+ ),
34
+ ),
35
+ timeoutMs,
36
+ );
37
+ }