ai 7.0.83 → 7.0.85

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.
@@ -1,5 +1,4 @@
1
1
  import type {
2
- LanguageModelV4Content,
3
2
  LanguageModelV4GenerateResult,
4
3
  LanguageModelV4ToolCall,
5
4
  } from '@ai-sdk/provider';
@@ -17,7 +16,6 @@ import {
17
16
  type ToolSet,
18
17
  } from '@ai-sdk/provider-utils';
19
18
  import { NoOutputGeneratedError } from '../error';
20
- import { ToolCallNotFoundForApprovalError } from '../error/tool-call-not-found-for-approval-error';
21
19
  import { logWarnings } from '../logger/log-warnings';
22
20
  import { resolveLanguageModel } from '../model/resolve-model';
23
21
  import type { ModelMessage } from '../prompt';
@@ -64,7 +62,7 @@ import { VERSION } from '../version';
64
62
  import type { ActiveTools } from './active-tools';
65
63
  import { calculateTokensPerSecond } from './calculate-tokens-per-second';
66
64
  import { collectToolApprovals } from './collect-tool-approvals';
67
- import type { ContentPart } from './content-part';
65
+ import { convertLanguageModelContent } from './convert-language-model-content';
68
66
  import { executeToolCall } from './execute-tool-call';
69
67
  import {
70
68
  filterActiveTools,
@@ -78,7 +76,6 @@ import type {
78
76
  GenerateTextOnStepStartCallback,
79
77
  } from './generate-text-events';
80
78
  import type { GenerateTextResult } from './generate-text-result';
81
- import { DefaultGeneratedFile } from './generated-file';
82
79
  import { isToolExecutionAllowedFinishReason } from './is-tool-execution-allowed-finish-reason';
83
80
  import type {
84
81
  OnLanguageModelCallEndCallback,
@@ -115,7 +112,6 @@ import {
115
112
  } from './tool-caller-configuration';
116
113
  import type { TypedToolCall } from './tool-call';
117
114
  import type { ToolCallRepairFunction } from './tool-call-repair-function';
118
- import type { TypedToolError } from './tool-error';
119
115
  import type {
120
116
  OnToolExecutionEndCallback,
121
117
  OnToolExecutionStartCallback,
@@ -123,7 +119,6 @@ import type {
123
119
  import type { ToolInputRefinement } from './tool-input-refinement';
124
120
  import type { ToolOrder } from './tool-order';
125
121
  import type { ToolOutput } from './tool-output';
126
- import type { TypedToolResult } from './tool-result';
127
122
  import type { ToolsContextParameter } from './tools-context-parameter';
128
123
  import { maybeSignApproval } from './tool-approval-signature';
129
124
  import { validateApprovedToolApprovals } from './validate-tool-approvals';
@@ -1088,7 +1083,7 @@ export async function generateText<
1088
1083
  > = {};
1089
1084
  const blockedToolCallIds = new Set<string>();
1090
1085
 
1091
- const modelCallContent = asContent({
1086
+ const modelCallContent = convertLanguageModelContent({
1092
1087
  content: currentModelResponse.content,
1093
1088
  toolCalls: stepToolCalls,
1094
1089
  toolOutputs: [],
@@ -1381,7 +1376,7 @@ export async function generateText<
1381
1376
  }
1382
1377
 
1383
1378
  // content:
1384
- const stepContent = asContent({
1379
+ const stepContent = convertLanguageModelContent({
1385
1380
  content: currentModelResponse.content,
1386
1381
  toolCalls: stepToolCalls,
1387
1382
  toolOutputs: clientToolOutputs,
@@ -1755,191 +1750,3 @@ class DefaultGenerateTextResult<
1755
1750
  return this._output;
1756
1751
  }
1757
1752
  }
1758
-
1759
- function asContent<TOOLS extends ToolSet>({
1760
- content,
1761
- toolCalls,
1762
- toolOutputs,
1763
- toolApprovalRequests,
1764
- toolApprovalResponses,
1765
- tools,
1766
- }: {
1767
- content: Array<LanguageModelV4Content>;
1768
- toolCalls: Array<TypedToolCall<TOOLS>>;
1769
- toolOutputs: Array<ToolOutput<TOOLS>>;
1770
- toolApprovalRequests: Array<ToolApprovalRequestOutput<TOOLS>>;
1771
- toolApprovalResponses: Array<ToolApprovalResponseOutput<TOOLS>>;
1772
- tools: TOOLS | undefined;
1773
- }): Array<ContentPart<TOOLS>> {
1774
- const contentParts: Array<ContentPart<TOOLS>> = [];
1775
- const toolOutputsWithApprovalResponses: Array<ToolOutput<TOOLS>> = [];
1776
- const toolOutputsWithoutApprovalResponses: Array<ToolOutput<TOOLS>> = [];
1777
- const toolCallIdsWithApprovalResponses = new Set(
1778
- toolApprovalResponses.map(
1779
- toolApprovalResponse => toolApprovalResponse.toolCall.toolCallId,
1780
- ),
1781
- );
1782
-
1783
- for (const part of content) {
1784
- switch (part.type) {
1785
- case 'text':
1786
- case 'reasoning':
1787
- case 'custom':
1788
- case 'source':
1789
- contentParts.push(part);
1790
- break;
1791
-
1792
- case 'file':
1793
- case 'reasoning-file': {
1794
- contentParts.push({
1795
- type: part.type as 'file' | 'reasoning-file',
1796
- file: new DefaultGeneratedFile({
1797
- data:
1798
- part.data.type === 'data'
1799
- ? part.data.data
1800
- : part.data.url.toString(),
1801
- mediaType: part.mediaType,
1802
- }),
1803
- ...(part.providerMetadata != null
1804
- ? { providerMetadata: part.providerMetadata }
1805
- : {}),
1806
- });
1807
- break;
1808
- }
1809
-
1810
- case 'tool-call': {
1811
- contentParts.push(
1812
- toolCalls.find(toolCall => toolCall.toolCallId === part.toolCallId)!,
1813
- );
1814
- break;
1815
- }
1816
-
1817
- case 'tool-result': {
1818
- const toolCall = toolCalls.find(
1819
- toolCall => toolCall.toolCallId === part.toolCallId,
1820
- );
1821
-
1822
- // Handle deferred results for provider-executed tools (e.g., programmatic tool calling).
1823
- // When a server tool (like code_execution) triggers a client tool, the server tool's
1824
- // result may be deferred to a later turn. In this case, there's no matching tool-call
1825
- // in the current response.
1826
- if (toolCall == null) {
1827
- const tool = getOwn(tools, part.toolName);
1828
- const supportsDeferredResults =
1829
- tool?.type === 'provider' && tool.supportsDeferredResults;
1830
-
1831
- if (!supportsDeferredResults) {
1832
- throw new Error(`Tool call ${part.toolCallId} not found.`);
1833
- }
1834
-
1835
- // Create tool result without tool call input (deferred result)
1836
- if (part.isError) {
1837
- contentParts.push({
1838
- type: 'tool-error' as const,
1839
- toolCallId: part.toolCallId,
1840
- toolName: part.toolName as keyof TOOLS & string,
1841
- input: undefined,
1842
- error: part.result,
1843
- providerExecuted: true,
1844
- dynamic: part.dynamic,
1845
- ...(part.providerMetadata != null
1846
- ? { providerMetadata: part.providerMetadata }
1847
- : {}),
1848
- ...(tool?.metadata != null
1849
- ? { toolMetadata: tool.metadata }
1850
- : {}),
1851
- } as TypedToolError<TOOLS>);
1852
- } else {
1853
- contentParts.push({
1854
- type: 'tool-result' as const,
1855
- toolCallId: part.toolCallId,
1856
- toolName: part.toolName as keyof TOOLS & string,
1857
- input: undefined,
1858
- output: part.result,
1859
- providerExecuted: true,
1860
- dynamic: part.dynamic,
1861
- ...(part.providerMetadata != null
1862
- ? { providerMetadata: part.providerMetadata }
1863
- : {}),
1864
- ...(tool?.metadata != null
1865
- ? { toolMetadata: tool.metadata }
1866
- : {}),
1867
- } as TypedToolResult<TOOLS>);
1868
- }
1869
- break;
1870
- }
1871
-
1872
- if (part.isError) {
1873
- contentParts.push({
1874
- type: 'tool-error' as const,
1875
- toolCallId: part.toolCallId,
1876
- toolName: part.toolName as keyof TOOLS & string,
1877
- input: toolCall.input,
1878
- error: part.result,
1879
- providerExecuted: true,
1880
- dynamic: toolCall.dynamic,
1881
- ...(part.providerMetadata != null
1882
- ? { providerMetadata: part.providerMetadata }
1883
- : {}),
1884
- ...(toolCall.toolMetadata != null
1885
- ? { toolMetadata: toolCall.toolMetadata }
1886
- : {}),
1887
- } as TypedToolError<TOOLS>);
1888
- } else {
1889
- contentParts.push({
1890
- type: 'tool-result' as const,
1891
- toolCallId: part.toolCallId,
1892
- toolName: part.toolName as keyof TOOLS & string,
1893
- input: toolCall.input,
1894
- output: part.result,
1895
- providerExecuted: true,
1896
- dynamic: toolCall.dynamic,
1897
- ...(part.providerMetadata != null
1898
- ? { providerMetadata: part.providerMetadata }
1899
- : {}),
1900
- ...(toolCall.toolMetadata != null
1901
- ? { toolMetadata: toolCall.toolMetadata }
1902
- : {}),
1903
- } as TypedToolResult<TOOLS>);
1904
- }
1905
- break;
1906
- }
1907
-
1908
- case 'tool-approval-request': {
1909
- const toolCall = toolCalls.find(
1910
- toolCall => toolCall.toolCallId === part.toolCallId,
1911
- );
1912
-
1913
- if (toolCall == null) {
1914
- throw new ToolCallNotFoundForApprovalError({
1915
- toolCallId: part.toolCallId,
1916
- approvalId: part.approvalId,
1917
- });
1918
- }
1919
-
1920
- contentParts.push({
1921
- type: 'tool-approval-request' as const,
1922
- approvalId: part.approvalId,
1923
- toolCall,
1924
- });
1925
- break;
1926
- }
1927
- }
1928
- }
1929
-
1930
- for (const toolOutput of toolOutputs) {
1931
- if (toolCallIdsWithApprovalResponses.has(toolOutput.toolCallId)) {
1932
- toolOutputsWithApprovalResponses.push(toolOutput);
1933
- } else {
1934
- toolOutputsWithoutApprovalResponses.push(toolOutput);
1935
- }
1936
- }
1937
-
1938
- return [
1939
- ...contentParts,
1940
- ...toolOutputsWithoutApprovalResponses,
1941
- ...toolApprovalRequests,
1942
- ...toolApprovalResponses,
1943
- ...toolOutputsWithApprovalResponses,
1944
- ];
1945
- }
@@ -2,6 +2,7 @@ import {
2
2
  convertBase64ToUint8Array,
3
3
  convertUint8ArrayToBase64,
4
4
  } from '@ai-sdk/provider-utils';
5
+ import type { JSONObject } from '@ai-sdk/provider';
5
6
 
6
7
  /**
7
8
  * A generated file.
@@ -23,6 +24,11 @@ export interface GeneratedFile {
23
24
  * @see https://www.iana.org/assignments/media-types/media-types.xhtml
24
25
  */
25
26
  readonly mediaType: string;
27
+
28
+ /**
29
+ * Provider-specific metadata for this file.
30
+ */
31
+ readonly providerMetadata?: Record<string, JSONObject>;
26
32
  }
27
33
 
28
34
  /**
@@ -35,18 +41,22 @@ export class DefaultGeneratedFile implements GeneratedFile {
35
41
  private uint8ArrayData: Uint8Array | undefined;
36
42
 
37
43
  readonly mediaType: string;
44
+ readonly providerMetadata?: Record<string, JSONObject>;
38
45
 
39
46
  constructor({
40
47
  data,
41
48
  mediaType,
49
+ providerMetadata,
42
50
  }: {
43
51
  data: string | Uint8Array;
44
52
  mediaType: string;
53
+ providerMetadata?: Record<string, JSONObject>;
45
54
  }) {
46
55
  const isUint8Array = data instanceof Uint8Array;
47
56
  this.base64Data = isUint8Array ? undefined : data;
48
57
  this.uint8ArrayData = isUint8Array ? data : undefined;
49
58
  this.mediaType = mediaType;
59
+ this.providerMetadata = providerMetadata;
50
60
  }
51
61
 
52
62
  // lazy conversion with caching to avoid unnecessary conversion overhead:
@@ -69,7 +79,11 @@ export class DefaultGeneratedFile implements GeneratedFile {
69
79
  export class DefaultGeneratedFileWithType extends DefaultGeneratedFile {
70
80
  readonly type = 'file';
71
81
 
72
- constructor(options: { data: string | Uint8Array; mediaType: string }) {
82
+ constructor(options: {
83
+ data: string | Uint8Array;
84
+ mediaType: string;
85
+ providerMetadata?: Record<string, JSONObject>;
86
+ }) {
73
87
  super(options);
74
88
  }
75
89
  }
@@ -65,8 +65,10 @@ export {
65
65
  } from './stream-language-model-call';
66
66
  export {
67
67
  streamText,
68
+ type StreamTextEndEvent,
68
69
  type StreamTextInclude,
69
70
  type StreamTextOnChunkCallback,
71
+ type StreamTextOnEndCallback,
70
72
  type StreamTextOnErrorCallback,
71
73
  type StreamTextTransform,
72
74
  } from './stream-text';
@@ -23,7 +23,7 @@ export type ChunkDetector = (buffer: string) => string | undefined | null;
23
23
  * Smooths text and reasoning streaming output.
24
24
  *
25
25
  * @param delayInMs - The delay in milliseconds between each chunk. Defaults to 10ms. Can be set to `null` to skip the delay.
26
- * @param chunking - Controls how the text is chunked for streaming. Use "word" to stream word by word (default), "line" to stream line by line, provide a custom RegExp pattern for custom chunking, provide an Intl.Segmenter for locale-aware word segmentation (recommended for CJK languages), or provide a custom ChunkDetector function.
26
+ * @param chunking - Controls how the text is chunked for streaming. Use "word" to stream word by word (default), "line" to stream line by line, provide a custom RegExp pattern that does not match the empty string for custom chunking, provide an Intl.Segmenter for locale-aware word segmentation (recommended for CJK languages), or provide a custom ChunkDetector function.
27
27
  *
28
28
  * @returns A transform stream that smooths text streaming output.
29
29
  */
@@ -95,13 +95,25 @@ export function smoothStream<TOOLS extends ToolSet>({
95
95
  }
96
96
 
97
97
  detectChunk = buffer => {
98
- const match = chunkingRegex.exec(buffer);
98
+ const lastIndex = chunkingRegex.lastIndex;
99
+ chunkingRegex.lastIndex = 0;
100
+
101
+ let match: RegExpExecArray | null;
102
+ try {
103
+ match = chunkingRegex.exec(buffer);
104
+ } finally {
105
+ chunkingRegex.lastIndex = lastIndex;
106
+ }
99
107
 
100
108
  if (!match) {
101
109
  return null;
102
110
  }
103
111
 
104
- return buffer.slice(0, match.index) + match?.[0];
112
+ if (!match[0].length) {
113
+ throw new Error(`Chunking RegExp must not match an empty string.`);
114
+ }
115
+
116
+ return buffer.slice(0, match.index) + match[0];
105
117
  };
106
118
  }
107
119
 
@@ -95,7 +95,7 @@ import {
95
95
  type ActiveToolSubset,
96
96
  } from './filter-active-tools';
97
97
  import type {
98
- GenerateTextOnEndCallback,
98
+ GenerateTextEndEvent,
99
99
  GenerateTextOnStartCallback,
100
100
  GenerateTextOnStepEndCallback,
101
101
  GenerateTextOnStepFinishCallback,
@@ -275,6 +275,24 @@ export type StreamTextOnChunkCallback<TOOLS extends ToolSet> = (event: {
275
275
  chunk: TextStreamPart<TOOLS>;
276
276
  }) => PromiseLike<void> | void;
277
277
 
278
+ export type StreamTextEndEvent<
279
+ TOOLS extends ToolSet = ToolSet,
280
+ RUNTIME_CONTEXT extends Context = Context,
281
+ OUTPUT extends Output = Output,
282
+ > = GenerateTextEndEvent<TOOLS, RUNTIME_CONTEXT> & {
283
+ /**
284
+ * The parsed output when an output setting was provided and parsing
285
+ * succeeded.
286
+ */
287
+ readonly output?: InferCompleteOutput<OUTPUT>;
288
+ };
289
+
290
+ export type StreamTextOnEndCallback<
291
+ TOOLS extends ToolSet = ToolSet,
292
+ RUNTIME_CONTEXT extends Context = Context,
293
+ OUTPUT extends Output = Output,
294
+ > = Callback<StreamTextEndEvent<TOOLS, RUNTIME_CONTEXT, OUTPUT>>;
295
+
278
296
  /**
279
297
  * Callback that is set using the `onAbort` option.
280
298
  *
@@ -589,7 +607,11 @@ export function streamText<
589
607
  *
590
608
  * The usage is the combined usage of all steps.
591
609
  */
592
- onEnd?: GenerateTextOnEndCallback<NoInfer<TOOLS>, NoInfer<RUNTIME_CONTEXT>>;
610
+ onEnd?: StreamTextOnEndCallback<
611
+ NoInfer<TOOLS>,
612
+ NoInfer<RUNTIME_CONTEXT>,
613
+ NoInfer<OUTPUT>
614
+ >;
593
615
 
594
616
  /**
595
617
  * Callback that is called when the LLM response and all request tool executions
@@ -599,9 +621,10 @@ export function streamText<
599
621
  *
600
622
  * @deprecated Use `onEnd` instead.
601
623
  */
602
- onFinish?: GenerateTextOnEndCallback<
624
+ onFinish?: StreamTextOnEndCallback<
603
625
  NoInfer<TOOLS>,
604
- NoInfer<RUNTIME_CONTEXT>
626
+ NoInfer<RUNTIME_CONTEXT>,
627
+ NoInfer<OUTPUT>
605
628
  >;
606
629
 
607
630
  onAbort?: StreamTextOnAbortCallback<
@@ -974,6 +997,8 @@ class DefaultStreamTextResult<
974
997
  Array<ResponseMessage>
975
998
  >();
976
999
 
1000
+ private outputPromise: Promise<InferCompleteOutput<OUTPUT>> | undefined;
1001
+
977
1002
  private readonly addStream: (
978
1003
  stream: ReadableStream<TextStreamPart<TOOLS>>,
979
1004
  callbacks?: {
@@ -1095,7 +1120,11 @@ class DefaultStreamTextResult<
1095
1120
  onError: StreamTextOnErrorCallback;
1096
1121
  onEnd:
1097
1122
  | undefined
1098
- | GenerateTextOnEndCallback<NoInfer<TOOLS>, NoInfer<RUNTIME_CONTEXT>>;
1123
+ | StreamTextOnEndCallback<
1124
+ NoInfer<TOOLS>,
1125
+ NoInfer<RUNTIME_CONTEXT>,
1126
+ NoInfer<OUTPUT>
1127
+ >;
1099
1128
  onAbort:
1100
1129
  | undefined
1101
1130
  | StreamTextOnAbortCallback<NoInfer<TOOLS>, NoInfer<RUNTIME_CONTEXT>>;
@@ -1486,43 +1515,65 @@ class DefaultStreamTextResult<
1486
1515
  step => step.dynamicToolResults,
1487
1516
  );
1488
1517
  const warnings = recordedSteps.flatMap(step => step.warnings ?? []);
1518
+ const onEndWithOutput =
1519
+ onEnd == null
1520
+ ? undefined
1521
+ : async (event: GenerateTextEndEvent<TOOLS, RUNTIME_CONTEXT>) => {
1522
+ const parsedOutput =
1523
+ output == null
1524
+ ? undefined
1525
+ : await self.getOutputPromise().catch(() => undefined);
1526
+
1527
+ await onEnd({
1528
+ ...event,
1529
+ ...(output != null ? { output: parsedOutput } : {}),
1530
+ });
1531
+ };
1489
1532
 
1490
- await notify({
1491
- event: {
1492
- callId,
1493
- toolsContext: finalStep.toolsContext,
1494
- stepNumber: finalStep.stepNumber,
1495
- model: finalStep.model,
1496
- runtimeContext: finalStep.runtimeContext,
1497
- finishReason: finalStep.finishReason,
1498
- rawFinishReason: finalStep.rawFinishReason,
1499
- usage: totalUsage,
1500
- totalUsage,
1501
- content,
1502
- text: finalStep.text,
1503
- reasoning: finalStep.reasoning,
1504
- reasoningText: finalStep.reasoningText,
1505
- files,
1506
- sources,
1507
- toolCalls,
1508
- staticToolCalls,
1509
- dynamicToolCalls,
1510
- toolResults,
1511
- staticToolResults,
1512
- dynamicToolResults,
1513
- responseMessages: [
1514
- ...initialResponseMessages,
1515
- ...recordedSteps.flatMap(step => step.response.messages),
1516
- ],
1517
- warnings,
1518
- request: finalStep.request,
1519
- response: finalStep.response,
1520
- providerMetadata: finalStep.providerMetadata,
1521
- steps: recordedSteps,
1522
- finalStep,
1523
- },
1524
- callbacks: [onEnd, telemetryDispatcher.onEnd],
1525
- });
1533
+ const onEndEvent = {
1534
+ callId,
1535
+ toolsContext: finalStep.toolsContext,
1536
+ stepNumber: finalStep.stepNumber,
1537
+ model: finalStep.model,
1538
+ runtimeContext: finalStep.runtimeContext,
1539
+ finishReason: finalStep.finishReason,
1540
+ rawFinishReason: finalStep.rawFinishReason,
1541
+ usage: totalUsage,
1542
+ totalUsage,
1543
+ content,
1544
+ text: finalStep.text,
1545
+ reasoning: finalStep.reasoning,
1546
+ reasoningText: finalStep.reasoningText,
1547
+ files,
1548
+ sources,
1549
+ toolCalls,
1550
+ staticToolCalls,
1551
+ dynamicToolCalls,
1552
+ toolResults,
1553
+ staticToolResults,
1554
+ dynamicToolResults,
1555
+ responseMessages: [
1556
+ ...initialResponseMessages,
1557
+ ...recordedSteps.flatMap(step => step.response.messages),
1558
+ ],
1559
+ warnings,
1560
+ request: finalStep.request,
1561
+ response: finalStep.response,
1562
+ providerMetadata: finalStep.providerMetadata,
1563
+ steps: recordedSteps,
1564
+ finalStep,
1565
+ };
1566
+
1567
+ await Promise.all([
1568
+ notify({
1569
+ event: onEndEvent,
1570
+ callbacks: onEndWithOutput,
1571
+ }),
1572
+ notify({
1573
+ event: onEndEvent,
1574
+ callbacks: telemetryDispatcher.onEnd,
1575
+ }),
1576
+ ]);
1526
1577
  } catch (error) {
1527
1578
  controller.error(error);
1528
1579
  }
@@ -2853,18 +2904,26 @@ class DefaultStreamTextResult<
2853
2904
  return createAsyncIterableStream(this.teeStream().pipeThrough(transform));
2854
2905
  }
2855
2906
 
2907
+ private getOutputPromise(): Promise<InferCompleteOutput<OUTPUT>> {
2908
+ if (this.outputPromise == null) {
2909
+ this.outputPromise = this.finalStep.then(step => {
2910
+ const output = this.outputSpecification ?? text();
2911
+ return output.parseCompleteOutput(
2912
+ { text: step.text },
2913
+ {
2914
+ response: step.response,
2915
+ usage: step.usage,
2916
+ finishReason: step.finishReason,
2917
+ },
2918
+ );
2919
+ });
2920
+ }
2921
+
2922
+ return this.outputPromise;
2923
+ }
2924
+
2856
2925
  get output(): Promise<InferCompleteOutput<OUTPUT>> {
2857
- return this.finalStep.then(step => {
2858
- const output = this.outputSpecification ?? text();
2859
- return output.parseCompleteOutput(
2860
- { text: step.text },
2861
- {
2862
- response: step.response,
2863
- usage: step.usage,
2864
- finishReason: step.finishReason,
2865
- },
2866
- );
2867
- });
2926
+ return this.getOutputPromise();
2868
2927
  }
2869
2928
 
2870
2929
  toUIMessageStream<UI_MESSAGE extends UIMessage>({
@@ -15,7 +15,9 @@ export function canonicalJSON(value: unknown): string {
15
15
  return JSON.stringify(value);
16
16
  }
17
17
  if (Array.isArray(value)) {
18
- return `[${value.map(canonicalJSON).join(',')}]`;
18
+ return `[${value
19
+ .map(element => (element === undefined ? 'null' : canonicalJSON(element)))
20
+ .join(',')}]`;
19
21
  }
20
22
  const keys = Object.keys(value as Record<string, unknown>).sort();
21
23
  const entries = keys.map(