ai-retry 0.3.0 → 0.4.1
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/README.md +63 -2
- package/dist/index.d.ts +55 -1
- package/dist/index.js +2 -11
- package/dist/retryables/index.d.ts +14 -1
- package/dist/retryables/index.js +64 -2
- package/dist/{utils-lRsC105f.js → utils-Dojn0elD.js} +11 -1
- package/package.json +8 -11
package/README.md
CHANGED
|
@@ -171,6 +171,39 @@ const retryable = createRetryable({
|
|
|
171
171
|
});
|
|
172
172
|
```
|
|
173
173
|
|
|
174
|
+
#### Retry After Delay
|
|
175
|
+
|
|
176
|
+
Handle retryable errors with delays and respect `retry-after` headers from rate-limited responses. This is useful for handling 429 (Too Many Requests) and 503 (Service Unavailable) errors.
|
|
177
|
+
|
|
178
|
+
> [!NOTE]
|
|
179
|
+
> If the response contains a [`retry-after`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Retry-After) header, it will be prioritized over the configured delay.
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
import { retryAfterDelay } from 'ai-retry/retryables';
|
|
184
|
+
|
|
185
|
+
const retryableModel = createRetryable({
|
|
186
|
+
model: openai('gpt-4'), // Base model
|
|
187
|
+
retries: [
|
|
188
|
+
// Retry base model 3 times with fixed 2s delay
|
|
189
|
+
retryAfterDelay({ delay: 2000, maxAttempts: 3 }),
|
|
190
|
+
|
|
191
|
+
// Or retry with exponential backoff (2s, 4s, 8s)
|
|
192
|
+
retryAfterDelay({ delay: 2000, backoffFactor: 2, maxAttempts: 3 }),
|
|
193
|
+
|
|
194
|
+
// Or switch to a different model after delay
|
|
195
|
+
retryAfterDelay(openai('gpt-4-mini'), { delay: 1000 }),
|
|
196
|
+
],
|
|
197
|
+
});
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
**Options:**
|
|
201
|
+
- `delay` (required): Delay in milliseconds before retrying
|
|
202
|
+
- `backoffFactor` (optional): Multiplier for exponential backoff (delay × backoffFactor^attempt). If not provided, uses fixed delay.
|
|
203
|
+
- `maxAttempts` (optional): Maximum number of retry attempts for this model
|
|
204
|
+
|
|
205
|
+
By default, if a [`retry-after-ms`](https://learn.microsoft.com/en-us/azure/ai-foundry/openai/how-to/provisioned-get-started#what-should--i-do-when-i-receive-a-429-response) or `retry-after` header is present in the response, it will be prioritized over the configured delay. The delay from the header will be capped at 60 seconds for safety. If no headers are present, the configured delay or exponential backoff will be used.
|
|
206
|
+
|
|
174
207
|
#### Fallbacks
|
|
175
208
|
|
|
176
209
|
If you always want to fallback to a different model on any error, you can simply provide a list of models.
|
|
@@ -247,6 +280,8 @@ try {
|
|
|
247
280
|
}
|
|
248
281
|
```
|
|
249
282
|
|
|
283
|
+
### Options
|
|
284
|
+
|
|
250
285
|
#### Retry Delays
|
|
251
286
|
|
|
252
287
|
You can add delays before retrying to handle rate limiting or give services time to recover. The delay respects abort signals, so requests can still be cancelled during the delay period.
|
|
@@ -267,6 +302,13 @@ const retryableModel = createRetryable({
|
|
|
267
302
|
}),
|
|
268
303
|
],
|
|
269
304
|
});
|
|
305
|
+
|
|
306
|
+
const result = await generateText({
|
|
307
|
+
model: retryableModel,
|
|
308
|
+
prompt: 'Write a vegetarian lasagna recipe for 4 people.',
|
|
309
|
+
// Will be respected during delays
|
|
310
|
+
abortSignal: AbortSignal.timeout(60_000),
|
|
311
|
+
});
|
|
270
312
|
```
|
|
271
313
|
|
|
272
314
|
You can also use delays with built-in retryables:
|
|
@@ -283,6 +325,26 @@ const retryableModel = createRetryable({
|
|
|
283
325
|
});
|
|
284
326
|
```
|
|
285
327
|
|
|
328
|
+
#### Max Attempts
|
|
329
|
+
|
|
330
|
+
By default, each retryable will only attempt to retry once per model to avoid infinite loops. You can customize this behavior by returning a `maxAttempts` value from your retryable function. Note that the initial request with the base model is counted as the first attempt.
|
|
331
|
+
|
|
332
|
+
```typescript
|
|
333
|
+
const retryableModel = createRetryable({
|
|
334
|
+
model: openai('gpt-4'),
|
|
335
|
+
retries: [
|
|
336
|
+
// Try this once
|
|
337
|
+
anthropic('claude-3-haiku-20240307'),
|
|
338
|
+
// Try this one more time (initial + 1 retry)
|
|
339
|
+
() => ({ model: openai('gpt-4'), maxAttempts: 2 }),
|
|
340
|
+
// Already tried, won't be retried again
|
|
341
|
+
anthropic('claude-3-haiku-20240307')
|
|
342
|
+
],
|
|
343
|
+
});
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
The attempts are counted per unique model (provider + modelId). That means if multiple retryables return the same model, it won't be retried again once the `maxAttempts` is reached.
|
|
347
|
+
|
|
286
348
|
#### Logging
|
|
287
349
|
|
|
288
350
|
You can use the following callbacks to log retry attempts and errors:
|
|
@@ -321,11 +383,10 @@ There are several built-in retryables:
|
|
|
321
383
|
- [`contentFilterTriggered`](./src/retryables/content-filter-triggered.ts): Content filter was triggered based on the prompt or completion.
|
|
322
384
|
- [`requestTimeout`](./src/retryables/request-timeout.ts): Request timeout occurred.
|
|
323
385
|
- [`requestNotRetryable`](./src/retryables/request-not-retryable.ts): Request failed with a non-retryable error.
|
|
386
|
+
- [`retryAfterDelay`](./src/retryables/retry-after-delay.ts): Retry with exponential backoff and respect `retry-after` headers for rate limiting.
|
|
324
387
|
- [`serviceOverloaded`](./src/retryables/service-overloaded.ts): Response with status code 529 (service overloaded).
|
|
325
388
|
- Use this retryable to handle Anthropic's overloaded errors.
|
|
326
389
|
|
|
327
|
-
By default, each retryable will only attempt to retry once per model to avoid infinite loops. You can customize this behavior by returning a `maxAttempts` value from your retryable function.
|
|
328
|
-
|
|
329
390
|
### API Reference
|
|
330
391
|
|
|
331
392
|
#### `createRetryable(options: RetryableModelOptions): LanguageModelV2 | EmbeddingModelV2`
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { EmbeddingModelV2, EmbeddingModelV2CallOptions, EmbeddingModelV2Embed, LanguageModelV2, LanguageModelV2Generate, LanguageModelV2Stream, Retries, RetryAttempt, RetryContext, RetryErrorAttempt, RetryModel, RetryResultAttempt, Retryable, RetryableModelOptions } from "./types-BrJaHkFh.js";
|
|
2
|
+
import * as _ai_sdk_provider0 from "@ai-sdk/provider";
|
|
3
|
+
import { LanguageModelV2StreamPart } from "@ai-sdk/provider";
|
|
2
4
|
|
|
3
5
|
//#region src/create-retryable-model.d.ts
|
|
4
6
|
declare function createRetryable<MODEL extends LanguageModelV2>(options: RetryableModelOptions<MODEL>): LanguageModelV2;
|
|
@@ -10,4 +12,56 @@ declare function createRetryable<MODEL extends EmbeddingModelV2>(options: Retrya
|
|
|
10
12
|
*/
|
|
11
13
|
declare const getModelKey: (model: LanguageModelV2 | EmbeddingModelV2) => string;
|
|
12
14
|
//#endregion
|
|
13
|
-
|
|
15
|
+
//#region src/utils.d.ts
|
|
16
|
+
declare const isObject: (value: unknown) => value is Record<string, unknown>;
|
|
17
|
+
declare const isString: (value: unknown) => value is string;
|
|
18
|
+
declare const isStreamResult: (result: LanguageModelV2Generate | LanguageModelV2Stream) => result is LanguageModelV2Stream;
|
|
19
|
+
declare const isGenerateResult: (result: LanguageModelV2Generate | LanguageModelV2Stream) => result is LanguageModelV2Generate;
|
|
20
|
+
/**
|
|
21
|
+
* Type guard to check if a retry attempt is an error attempt
|
|
22
|
+
*/
|
|
23
|
+
declare function isErrorAttempt(attempt: RetryAttempt<any>): attempt is RetryErrorAttempt<any>;
|
|
24
|
+
/**
|
|
25
|
+
* Type guard to check if a retry attempt is a result attempt
|
|
26
|
+
*/
|
|
27
|
+
declare function isResultAttempt(attempt: RetryAttempt<any>): attempt is RetryResultAttempt;
|
|
28
|
+
/**
|
|
29
|
+
* Check if a stream part is a content part (e.g., text delta, reasoning delta, source, tool call, tool result).
|
|
30
|
+
* These types are also emitted by `onChunk` callbacks.
|
|
31
|
+
* @see https://github.com/vercel/ai/blob/1fe4bd4144bff927f5319d9d206e782a73979ccb/packages/ai/src/generate-text/stream-text.ts#L686-L697
|
|
32
|
+
*/
|
|
33
|
+
declare const isStreamContentPart: (part: LanguageModelV2StreamPart) => part is _ai_sdk_provider0.LanguageModelV2Source | _ai_sdk_provider0.LanguageModelV2ToolCall | {
|
|
34
|
+
type: "tool-result";
|
|
35
|
+
toolCallId: string;
|
|
36
|
+
toolName: string;
|
|
37
|
+
result: unknown;
|
|
38
|
+
isError?: boolean;
|
|
39
|
+
providerExecuted?: boolean;
|
|
40
|
+
providerMetadata?: _ai_sdk_provider0.SharedV2ProviderMetadata;
|
|
41
|
+
} | {
|
|
42
|
+
type: "text-delta";
|
|
43
|
+
id: string;
|
|
44
|
+
providerMetadata?: _ai_sdk_provider0.SharedV2ProviderMetadata;
|
|
45
|
+
delta: string;
|
|
46
|
+
} | {
|
|
47
|
+
type: "reasoning-delta";
|
|
48
|
+
id: string;
|
|
49
|
+
providerMetadata?: _ai_sdk_provider0.SharedV2ProviderMetadata;
|
|
50
|
+
delta: string;
|
|
51
|
+
} | {
|
|
52
|
+
type: "tool-input-start";
|
|
53
|
+
id: string;
|
|
54
|
+
toolName: string;
|
|
55
|
+
providerMetadata?: _ai_sdk_provider0.SharedV2ProviderMetadata;
|
|
56
|
+
providerExecuted?: boolean;
|
|
57
|
+
} | {
|
|
58
|
+
type: "tool-input-delta";
|
|
59
|
+
id: string;
|
|
60
|
+
delta: string;
|
|
61
|
+
providerMetadata?: _ai_sdk_provider0.SharedV2ProviderMetadata;
|
|
62
|
+
} | {
|
|
63
|
+
type: "raw";
|
|
64
|
+
rawValue: unknown;
|
|
65
|
+
};
|
|
66
|
+
//#endregion
|
|
67
|
+
export { EmbeddingModelV2, EmbeddingModelV2CallOptions, EmbeddingModelV2Embed, LanguageModelV2, LanguageModelV2Generate, LanguageModelV2Stream, Retries, RetryAttempt, RetryContext, RetryErrorAttempt, RetryModel, RetryResultAttempt, Retryable, RetryableModelOptions, createRetryable, getModelKey, isErrorAttempt, isGenerateResult, isObject, isResultAttempt, isStreamContentPart, isStreamResult, isString };
|
package/dist/index.js
CHANGED
|
@@ -1,17 +1,8 @@
|
|
|
1
|
-
import { isErrorAttempt, isGenerateResult, isResultAttempt, isStreamContentPart } from "./utils-
|
|
1
|
+
import { getModelKey, isErrorAttempt, isGenerateResult, isObject, isResultAttempt, isStreamContentPart, isStreamResult, isString } from "./utils-Dojn0elD.js";
|
|
2
2
|
import { delay } from "@ai-sdk/provider-utils";
|
|
3
3
|
import { getErrorMessage } from "@ai-sdk/provider";
|
|
4
4
|
import { RetryError } from "ai";
|
|
5
5
|
|
|
6
|
-
//#region src/get-model-key.ts
|
|
7
|
-
/**
|
|
8
|
-
* Generate a unique key for a LanguageModelV2 instance.
|
|
9
|
-
*/
|
|
10
|
-
const getModelKey = (model) => {
|
|
11
|
-
return `${model.provider}/${model.modelId}`;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
//#endregion
|
|
15
6
|
//#region src/find-retry-model.ts
|
|
16
7
|
/**
|
|
17
8
|
* Find the next model to retry with based on the retry context
|
|
@@ -415,4 +406,4 @@ function createRetryable(options) {
|
|
|
415
406
|
}
|
|
416
407
|
|
|
417
408
|
//#endregion
|
|
418
|
-
export { createRetryable, getModelKey };
|
|
409
|
+
export { createRetryable, getModelKey, isErrorAttempt, isGenerateResult, isObject, isResultAttempt, isStreamContentPart, isStreamResult, isString };
|
|
@@ -21,6 +21,19 @@ declare function requestNotRetryable<MODEL extends LanguageModelV2 | EmbeddingMo
|
|
|
21
21
|
*/
|
|
22
22
|
declare function requestTimeout<MODEL extends LanguageModelV2 | EmbeddingModelV2>(model: MODEL, options?: Omit<RetryModel<MODEL>, 'model'>): Retryable<MODEL>;
|
|
23
23
|
//#endregion
|
|
24
|
+
//#region src/retryables/retry-after-delay.d.ts
|
|
25
|
+
type RetryAfterDelayOptions<MODEL extends LanguageModelV2 | EmbeddingModelV2> = Omit<RetryModel<MODEL>, 'model' | 'delay'> & {
|
|
26
|
+
delay: number;
|
|
27
|
+
backoffFactor?: number;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Retry with the same or a different model if the error is retryable with a delay.
|
|
31
|
+
* Uses the `Retry-After` or `Retry-After-Ms` headers if present.
|
|
32
|
+
* Otherwise uses the specified `delay`, or exponential backoff if `backoffFactor` is provided.
|
|
33
|
+
*/
|
|
34
|
+
declare function retryAfterDelay<MODEL extends LanguageModelV2 | EmbeddingModelV2>(model: MODEL, options?: RetryAfterDelayOptions<MODEL>): Retryable<MODEL>;
|
|
35
|
+
declare function retryAfterDelay<MODEL extends LanguageModelV2 | EmbeddingModelV2>(options: RetryAfterDelayOptions<MODEL>): Retryable<MODEL>;
|
|
36
|
+
//#endregion
|
|
24
37
|
//#region src/retryables/service-overloaded.d.ts
|
|
25
38
|
/**
|
|
26
39
|
* Fallback to a different model if the provider returns an overloaded error.
|
|
@@ -31,4 +44,4 @@ declare function requestTimeout<MODEL extends LanguageModelV2 | EmbeddingModelV2
|
|
|
31
44
|
*/
|
|
32
45
|
declare function serviceOverloaded<MODEL extends LanguageModelV2 | EmbeddingModelV2>(model: MODEL, options?: Omit<RetryModel<MODEL>, 'model'>): Retryable<MODEL>;
|
|
33
46
|
//#endregion
|
|
34
|
-
export { contentFilterTriggered, requestNotRetryable, requestTimeout, serviceOverloaded };
|
|
47
|
+
export { contentFilterTriggered, requestNotRetryable, requestTimeout, retryAfterDelay, serviceOverloaded };
|
package/dist/retryables/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isErrorAttempt, isObject, isResultAttempt, isString } from "../utils-
|
|
1
|
+
import { getModelKey, isErrorAttempt, isObject, isResultAttempt, isString } from "../utils-Dojn0elD.js";
|
|
2
2
|
import { isAbortError } from "@ai-sdk/provider-utils";
|
|
3
3
|
import { APICallError } from "ai";
|
|
4
4
|
|
|
@@ -69,6 +69,68 @@ function requestTimeout(model, options) {
|
|
|
69
69
|
};
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region src/calculate-exponential-backoff.ts
|
|
74
|
+
/**
|
|
75
|
+
* Calculates the exponential backoff delay.
|
|
76
|
+
*/
|
|
77
|
+
function calculateExponentialBackoff(baseDelay, backoffFactor, attempts) {
|
|
78
|
+
return baseDelay * backoffFactor ** attempts;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
//#endregion
|
|
82
|
+
//#region src/parse-retry-headers.ts
|
|
83
|
+
function parseRetryHeaders(headers) {
|
|
84
|
+
if (!headers) return null;
|
|
85
|
+
const retryAfterMs = headers["retry-after-ms"];
|
|
86
|
+
if (retryAfterMs) {
|
|
87
|
+
const delayMs = Number.parseFloat(retryAfterMs);
|
|
88
|
+
if (!Number.isNaN(delayMs) && delayMs >= 0) return delayMs;
|
|
89
|
+
}
|
|
90
|
+
const retryAfter = headers["retry-after"];
|
|
91
|
+
if (retryAfter) {
|
|
92
|
+
const seconds = Number.parseFloat(retryAfter);
|
|
93
|
+
if (!Number.isNaN(seconds)) return seconds * 1e3;
|
|
94
|
+
const date = Date.parse(retryAfter);
|
|
95
|
+
if (!Number.isNaN(date)) return Math.max(0, date - Date.now());
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
//#endregion
|
|
101
|
+
//#region src/retryables/retry-after-delay.ts
|
|
102
|
+
const MAX_RETRY_AFTER_MS = 6e4;
|
|
103
|
+
function retryAfterDelay(modelOrOptions, options) {
|
|
104
|
+
const model = modelOrOptions && "delay" in modelOrOptions ? void 0 : modelOrOptions;
|
|
105
|
+
const opts = modelOrOptions && "delay" in modelOrOptions ? modelOrOptions : options;
|
|
106
|
+
if (!opts?.delay) throw new Error("retryAfterDelay: delay is required");
|
|
107
|
+
const delay$1 = opts.delay;
|
|
108
|
+
const backoffFactor = Math.max(opts.backoffFactor ?? 1, 1);
|
|
109
|
+
return (context) => {
|
|
110
|
+
const { current, attempts } = context;
|
|
111
|
+
if (isErrorAttempt(current)) {
|
|
112
|
+
const { error } = current;
|
|
113
|
+
if (APICallError.isInstance(error) && error.isRetryable === true) {
|
|
114
|
+
const targetModel = model ?? current.model;
|
|
115
|
+
const modelKey = getModelKey(targetModel);
|
|
116
|
+
const modelAttempts = attempts.filter((a) => getModelKey(a.model) === modelKey);
|
|
117
|
+
const headerDelay = parseRetryHeaders(error.responseHeaders);
|
|
118
|
+
if (headerDelay !== null) return {
|
|
119
|
+
model: targetModel,
|
|
120
|
+
delay: Math.min(headerDelay, MAX_RETRY_AFTER_MS),
|
|
121
|
+
maxAttempts: opts.maxAttempts
|
|
122
|
+
};
|
|
123
|
+
const calculatedDelay = calculateExponentialBackoff(delay$1, backoffFactor, modelAttempts.length);
|
|
124
|
+
return {
|
|
125
|
+
model: targetModel,
|
|
126
|
+
delay: calculatedDelay,
|
|
127
|
+
maxAttempts: opts.maxAttempts
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
72
134
|
//#endregion
|
|
73
135
|
//#region src/retryables/service-overloaded.ts
|
|
74
136
|
/**
|
|
@@ -100,4 +162,4 @@ function serviceOverloaded(model, options) {
|
|
|
100
162
|
}
|
|
101
163
|
|
|
102
164
|
//#endregion
|
|
103
|
-
export { contentFilterTriggered, requestNotRetryable, requestTimeout, serviceOverloaded };
|
|
165
|
+
export { contentFilterTriggered, requestNotRetryable, requestTimeout, retryAfterDelay, serviceOverloaded };
|
|
@@ -1,6 +1,16 @@
|
|
|
1
|
+
//#region src/get-model-key.ts
|
|
2
|
+
/**
|
|
3
|
+
* Generate a unique key for a LanguageModelV2 instance.
|
|
4
|
+
*/
|
|
5
|
+
const getModelKey = (model) => {
|
|
6
|
+
return `${model.provider}/${model.modelId}`;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
//#endregion
|
|
1
10
|
//#region src/utils.ts
|
|
2
11
|
const isObject = (value) => typeof value === "object" && value !== null;
|
|
3
12
|
const isString = (value) => typeof value === "string";
|
|
13
|
+
const isStreamResult = (result) => "stream" in result;
|
|
4
14
|
const isGenerateResult = (result) => "content" in result;
|
|
5
15
|
/**
|
|
6
16
|
* Type guard to check if a retry attempt is an error attempt
|
|
@@ -24,4 +34,4 @@ const isStreamContentPart = (part) => {
|
|
|
24
34
|
};
|
|
25
35
|
|
|
26
36
|
//#endregion
|
|
27
|
-
export { isErrorAttempt, isGenerateResult, isObject, isResultAttempt, isStreamContentPart, isString };
|
|
37
|
+
export { getModelKey, isErrorAttempt, isGenerateResult, isObject, isResultAttempt, isStreamContentPart, isStreamResult, isString };
|
package/package.json
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-retry",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "AI SDK Retry",
|
|
5
|
-
"packageManager": "pnpm@9.0.0",
|
|
6
5
|
"main": "./dist/index.js",
|
|
7
6
|
"module": "./dist/index.js",
|
|
8
7
|
"types": "./dist/index.d.ts",
|
|
@@ -18,14 +17,6 @@
|
|
|
18
17
|
"publishConfig": {
|
|
19
18
|
"access": "public"
|
|
20
19
|
},
|
|
21
|
-
"scripts": {
|
|
22
|
-
"prepublishOnly": "pnpm build",
|
|
23
|
-
"publish:alpha": "pnpm version prerelease --preid alpha && pnpm publish --tag alpha",
|
|
24
|
-
"build": "tsdown",
|
|
25
|
-
"test": "vitest",
|
|
26
|
-
"lint": "biome check . --write",
|
|
27
|
-
"prepare": "husky"
|
|
28
|
-
},
|
|
29
20
|
"keywords": [
|
|
30
21
|
"ai",
|
|
31
22
|
"ai-sdk",
|
|
@@ -64,5 +55,11 @@
|
|
|
64
55
|
"dependencies": {
|
|
65
56
|
"@ai-sdk/provider": "^2.0.0",
|
|
66
57
|
"@ai-sdk/provider-utils": "^3.0.9"
|
|
58
|
+
},
|
|
59
|
+
"scripts": {
|
|
60
|
+
"publish:alpha": "pnpm version prerelease --preid alpha && pnpm publish --tag alpha",
|
|
61
|
+
"build": "tsdown",
|
|
62
|
+
"test": "vitest",
|
|
63
|
+
"lint": "biome check . --write"
|
|
67
64
|
}
|
|
68
|
-
}
|
|
65
|
+
}
|