@botpress/zai 1.1.0 → 1.2.0
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 +1 -1
- package/build.ts +9 -0
- package/dist/adapters/botpress-table.js +21 -21
- package/dist/index.d.ts +22 -521
- package/dist/operations/check.js +15 -3
- package/dist/operations/extract.js +28 -8
- package/dist/operations/filter.js +15 -3
- package/dist/operations/label.js +15 -3
- package/dist/operations/rewrite.js +18 -6
- package/dist/operations/summarize.js +6 -5
- package/dist/operations/text.js +4 -3
- package/dist/utils.js +0 -6
- package/dist/zai.js +28 -68
- package/e2e/data/cache.jsonl +107 -0
- package/{src/operations/__tests/index.ts → e2e/utils.ts} +18 -16
- package/package.json +23 -21
- package/src/adapters/adapter.ts +2 -2
- package/src/adapters/botpress-table.ts +36 -36
- package/src/adapters/memory.ts +3 -3
- package/src/operations/check.ts +31 -17
- package/src/operations/errors.ts +1 -1
- package/src/operations/extract.ts +49 -31
- package/src/operations/filter.ts +36 -23
- package/src/operations/label.ts +32 -19
- package/src/operations/rewrite.ts +28 -15
- package/src/operations/summarize.ts +11 -9
- package/src/operations/text.ts +7 -5
- package/src/utils.ts +5 -14
- package/src/zai.ts +45 -91
- package/tsconfig.json +2 -22
- package/dist/models.js +0 -387
- package/src/models.ts +0 -394
- package/src/operations/__tests/cache.jsonl +0 -101
- package/src/sdk-interfaces/llm/generateContent.ts +0 -127
- package/src/sdk-interfaces/llm/listLanguageModels.ts +0 -19
- /package/{src/operations/__tests → e2e/data}/botpress_docs.txt +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// eslint-disable consistent-type-definitions
|
|
1
2
|
import { z } from '@bpinternal/zui'
|
|
2
3
|
|
|
3
4
|
import { chunk } from 'lodash-es'
|
|
@@ -27,10 +28,10 @@ const Options = z.object({
|
|
|
27
28
|
sliding: z
|
|
28
29
|
.object({
|
|
29
30
|
window: z.number().min(10).max(100_000),
|
|
30
|
-
overlap: z.number().min(0).max(100_000)
|
|
31
|
+
overlap: z.number().min(0).max(100_000),
|
|
31
32
|
})
|
|
32
33
|
.describe('Sliding window options')
|
|
33
|
-
.default({ window: 50_000, overlap: 250 })
|
|
34
|
+
.default({ window: 50_000, overlap: 250 }),
|
|
34
35
|
})
|
|
35
36
|
|
|
36
37
|
declare module '@botpress/zai' {
|
|
@@ -46,20 +47,21 @@ const END = '■END■'
|
|
|
46
47
|
Zai.prototype.summarize = async function (this: Zai, original, _options) {
|
|
47
48
|
const options = Options.parse(_options ?? {})
|
|
48
49
|
const tokenizer = await this.getTokenizer()
|
|
50
|
+
await this.fetchModelDetails()
|
|
49
51
|
|
|
50
|
-
const INPUT_COMPONENT_SIZE = Math.max(100, (this.
|
|
52
|
+
const INPUT_COMPONENT_SIZE = Math.max(100, (this.ModelDetails.input.maxTokens - PROMPT_INPUT_BUFFER) / 4)
|
|
51
53
|
options.prompt = tokenizer.truncate(options.prompt, INPUT_COMPONENT_SIZE)
|
|
52
54
|
options.format = tokenizer.truncate(options.format, INPUT_COMPONENT_SIZE)
|
|
53
55
|
|
|
54
|
-
const maxOutputSize = this.
|
|
56
|
+
const maxOutputSize = this.ModelDetails.output.maxTokens - PROMPT_OUTPUT_BUFFER
|
|
55
57
|
if (options.length > maxOutputSize) {
|
|
56
58
|
throw new Error(
|
|
57
|
-
`The desired output length is ${maxOutputSize} tokens long, which is more than the maximum of ${this.
|
|
59
|
+
`The desired output length is ${maxOutputSize} tokens long, which is more than the maximum of ${this.ModelDetails.output.maxTokens} tokens for this model (${this.ModelDetails.name})`
|
|
58
60
|
)
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
// Ensure the sliding window is not bigger than the model input size
|
|
62
|
-
options.sliding.window = Math.min(options.sliding.window, this.
|
|
64
|
+
options.sliding.window = Math.min(options.sliding.window, this.ModelDetails.input.maxTokens - PROMPT_INPUT_BUFFER)
|
|
63
65
|
|
|
64
66
|
// Ensure the overlap is not bigger than the window
|
|
65
67
|
// Most extreme case possible (all 3 same size)
|
|
@@ -117,7 +119,7 @@ ${newText}
|
|
|
117
119
|
'Summarize the text and make sure that the main points are included.',
|
|
118
120
|
'Ignore any unnecessary details and focus on the main points.',
|
|
119
121
|
'Use short and concise sentences to increase readability and information density.',
|
|
120
|
-
'When looking at the new information, focus on: ' + options.prompt
|
|
122
|
+
'When looking at the new information, focus on: ' + options.prompt,
|
|
121
123
|
]
|
|
122
124
|
|
|
123
125
|
if (isFirst) {
|
|
@@ -157,7 +159,7 @@ ${newText}
|
|
|
157
159
|
}
|
|
158
160
|
}
|
|
159
161
|
|
|
160
|
-
const output = await this.callModel({
|
|
162
|
+
const { output } = await this.callModel({
|
|
161
163
|
systemPrompt: `
|
|
162
164
|
You are summarizing a text. The text is split into ${parts} parts, and you are currently working on part ${iteration}.
|
|
163
165
|
At every step, you will receive the current summary and a new part of the text. You need to amend the summary to include the new information (if needed).
|
|
@@ -171,7 +173,7 @@ ${options.format}
|
|
|
171
173
|
`.trim(),
|
|
172
174
|
messages: [{ type: 'text', content: format(currentSummary, slice), role: 'user' }],
|
|
173
175
|
maxTokens: generationLength,
|
|
174
|
-
stopSequences: [END]
|
|
176
|
+
stopSequences: [END],
|
|
175
177
|
})
|
|
176
178
|
|
|
177
179
|
let result = output?.choices[0]?.content as string
|
package/src/operations/text.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// eslint-disable consistent-type-definitions
|
|
1
2
|
import { z } from '@bpinternal/zui'
|
|
2
3
|
|
|
3
4
|
import { clamp } from 'lodash-es'
|
|
@@ -6,7 +7,7 @@ import { PROMPT_INPUT_BUFFER, PROMPT_OUTPUT_BUFFER } from './constants'
|
|
|
6
7
|
|
|
7
8
|
export type Options = z.input<typeof Options>
|
|
8
9
|
const Options = z.object({
|
|
9
|
-
length: z.number().min(1).max(100_000).optional().describe('The maximum number of tokens to generate')
|
|
10
|
+
length: z.number().min(1).max(100_000).optional().describe('The maximum number of tokens to generate'),
|
|
10
11
|
})
|
|
11
12
|
|
|
12
13
|
declare module '@botpress/zai' {
|
|
@@ -19,11 +20,12 @@ declare module '@botpress/zai' {
|
|
|
19
20
|
Zai.prototype.text = async function (this: Zai, prompt, _options) {
|
|
20
21
|
const options = Options.parse(_options ?? {})
|
|
21
22
|
const tokenizer = await this.getTokenizer()
|
|
23
|
+
await this.fetchModelDetails()
|
|
22
24
|
|
|
23
|
-
prompt = tokenizer.truncate(prompt, Math.max(this.
|
|
25
|
+
prompt = tokenizer.truncate(prompt, Math.max(this.ModelDetails.input.maxTokens - PROMPT_INPUT_BUFFER, 100))
|
|
24
26
|
|
|
25
27
|
if (options.length) {
|
|
26
|
-
options.length = Math.min(this.
|
|
28
|
+
options.length = Math.min(this.ModelDetails.output.maxTokens - PROMPT_OUTPUT_BUFFER, options.length)
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
const instructions: string[] = []
|
|
@@ -49,7 +51,7 @@ Zai.prototype.text = async function (this: Zai, prompt, _options) {
|
|
|
49
51
|
| 300-500 tokens| A long paragraph (200-300 words) |`.trim()
|
|
50
52
|
}
|
|
51
53
|
|
|
52
|
-
const output = await this.callModel({
|
|
54
|
+
const { output } = await this.callModel({
|
|
53
55
|
systemPrompt: `
|
|
54
56
|
Generate a text that fulfills the user prompt below. Answer directly to the prompt, without any acknowledgements or fluff. Also, make sure the text is standalone and complete.
|
|
55
57
|
${instructions.map((x) => `- ${x}`).join('\n')}
|
|
@@ -57,7 +59,7 @@ ${chart}
|
|
|
57
59
|
`.trim(),
|
|
58
60
|
temperature: 0.7,
|
|
59
61
|
messages: [{ type: 'text', content: prompt, role: 'user' }],
|
|
60
|
-
maxTokens: options.length
|
|
62
|
+
maxTokens: options.length,
|
|
61
63
|
})
|
|
62
64
|
return output?.choices?.[0]?.content! as string
|
|
63
65
|
}
|
package/src/utils.ts
CHANGED
|
@@ -1,22 +1,13 @@
|
|
|
1
|
-
import type { Client } from '@botpress/client'
|
|
2
1
|
import { z } from '@bpinternal/zui'
|
|
3
2
|
|
|
4
3
|
export const stringify = (input: unknown, beautify = true) => {
|
|
5
4
|
return typeof input === 'string' && !!input.length
|
|
6
5
|
? input
|
|
7
6
|
: input
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
? JSON.stringify(input, beautify ? null : undefined, beautify ? 2 : undefined)
|
|
8
|
+
: '<input is null, false, undefined or empty>'
|
|
10
9
|
}
|
|
11
10
|
|
|
12
|
-
export const BotpressClient = z.custom<Client | any>(
|
|
13
|
-
(value) =>
|
|
14
|
-
typeof value === 'object' && value !== null && 'callAction' in value && typeof value.callAction === 'function',
|
|
15
|
-
{
|
|
16
|
-
message: 'Invalid Botpress Client. Make sure to pass an instance of @botpress/client'
|
|
17
|
-
}
|
|
18
|
-
)
|
|
19
|
-
|
|
20
11
|
export function fastHash(str: string): string {
|
|
21
12
|
let hash = 0
|
|
22
13
|
for (let i = 0; i < str.length; i++) {
|
|
@@ -48,14 +39,14 @@ export const GenerationMetadata = z.object({
|
|
|
48
39
|
cost: z
|
|
49
40
|
.object({
|
|
50
41
|
input: z.number(),
|
|
51
|
-
output: z.number()
|
|
42
|
+
output: z.number(),
|
|
52
43
|
})
|
|
53
44
|
.describe('Cost in $USD'),
|
|
54
45
|
latency: z.number().describe('Latency in milliseconds'),
|
|
55
46
|
tokens: z
|
|
56
47
|
.object({
|
|
57
48
|
input: z.number(),
|
|
58
|
-
output: z.number()
|
|
49
|
+
output: z.number(),
|
|
59
50
|
})
|
|
60
|
-
.describe('Number of tokens used')
|
|
51
|
+
.describe('Number of tokens used'),
|
|
61
52
|
})
|
package/src/zai.ts
CHANGED
|
@@ -1,16 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BotpressClientLike, Cognitive, Model } from '@botpress/cognitive'
|
|
2
|
+
|
|
3
|
+
import { type TextTokenizer, getWasmTokenizer } from '@bpinternal/thicktoken'
|
|
2
4
|
import { z } from '@bpinternal/zui'
|
|
3
|
-
import { type TextTokenizer, getWasmTokenizer } from '@botpress/wasm'
|
|
4
5
|
|
|
5
6
|
import { Adapter } from './adapters/adapter'
|
|
6
7
|
import { TableAdapter } from './adapters/botpress-table'
|
|
7
8
|
import { MemoryAdapter } from './adapters/memory'
|
|
8
|
-
import { Models } from './models'
|
|
9
|
-
import { llm } from './sdk-interfaces/llm/generateContent'
|
|
10
|
-
|
|
11
|
-
import { BotpressClient, GenerationMetadata } from './utils'
|
|
12
9
|
|
|
13
|
-
type ModelId =
|
|
10
|
+
type ModelId = Required<Parameters<Cognitive['generateContent']>[0]['model']>
|
|
14
11
|
|
|
15
12
|
type ActiveLearning = z.input<typeof ActiveLearning>
|
|
16
13
|
const ActiveLearning = z.object({
|
|
@@ -30,29 +27,32 @@ const ActiveLearning = z.object({
|
|
|
30
27
|
'Namespace must be alphanumeric and contain only letters, numbers, underscores, hyphens and slashes'
|
|
31
28
|
)
|
|
32
29
|
.describe('The ID of the task')
|
|
33
|
-
.default('default')
|
|
30
|
+
.default('default'),
|
|
34
31
|
})
|
|
35
32
|
|
|
36
33
|
type ZaiConfig = z.input<typeof ZaiConfig>
|
|
37
34
|
const ZaiConfig = z.object({
|
|
38
|
-
client:
|
|
35
|
+
client: z.custom<BotpressClientLike | Cognitive>(),
|
|
39
36
|
userId: z.string().describe('The ID of the user consuming the API').optional(),
|
|
40
|
-
retry: z.object({ maxRetries: z.number().min(0).max(100) }).default({ maxRetries: 3 }),
|
|
41
37
|
modelId: z
|
|
42
38
|
.custom<ModelId | string>(
|
|
43
39
|
(value) => {
|
|
44
|
-
if (typeof value !== 'string'
|
|
40
|
+
if (typeof value !== 'string') {
|
|
41
|
+
return false
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (value !== 'best' && value !== 'fast' && !value.includes(':')) {
|
|
45
45
|
return false
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
return true
|
|
49
49
|
},
|
|
50
50
|
{
|
|
51
|
-
message: 'Invalid model ID'
|
|
51
|
+
message: 'Invalid model ID',
|
|
52
52
|
}
|
|
53
53
|
)
|
|
54
54
|
.describe('The ID of the model you want to use')
|
|
55
|
-
.default('
|
|
55
|
+
.default('best' satisfies ModelId),
|
|
56
56
|
activeLearning: ActiveLearning.default({ enable: false }),
|
|
57
57
|
namespace: z
|
|
58
58
|
.string()
|
|
@@ -60,102 +60,50 @@ const ZaiConfig = z.object({
|
|
|
60
60
|
/^[A-Za-z0-9_/-]{1,100}$/,
|
|
61
61
|
'Namespace must be alphanumeric and contain only letters, numbers, underscores, hyphens and slashes'
|
|
62
62
|
)
|
|
63
|
-
.default('zai')
|
|
63
|
+
.default('zai'),
|
|
64
64
|
})
|
|
65
65
|
|
|
66
66
|
export class Zai {
|
|
67
67
|
protected static tokenizer: TextTokenizer = null!
|
|
68
|
-
protected client:
|
|
68
|
+
protected client: Cognitive
|
|
69
69
|
|
|
70
|
-
private
|
|
70
|
+
private _originalConfig: ZaiConfig
|
|
71
71
|
|
|
72
|
-
private
|
|
73
|
-
private integration: string
|
|
74
|
-
private model: string
|
|
75
|
-
private retry: { maxRetries: number }
|
|
72
|
+
private _userId: string | undefined
|
|
76
73
|
|
|
77
|
-
protected Model:
|
|
74
|
+
protected Model: ModelId
|
|
75
|
+
protected ModelDetails: Model
|
|
78
76
|
protected namespace: string
|
|
79
77
|
protected adapter: Adapter
|
|
80
78
|
protected activeLearning: ActiveLearning
|
|
81
79
|
|
|
82
|
-
constructor(config: ZaiConfig) {
|
|
83
|
-
this.
|
|
80
|
+
public constructor(config: ZaiConfig) {
|
|
81
|
+
this._originalConfig = config
|
|
84
82
|
const parsed = ZaiConfig.parse(config)
|
|
85
83
|
|
|
86
|
-
this.client = parsed.client
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if (!integration?.length || !modelId?.length) {
|
|
90
|
-
throw new Error(`Invalid model ID: ${parsed.modelId}. Expected format: <integration>__<modelId>`)
|
|
91
|
-
}
|
|
84
|
+
this.client = Cognitive.isCognitiveClient(parsed.client)
|
|
85
|
+
? (parsed.client as unknown as Cognitive)
|
|
86
|
+
: new Cognitive({ client: parsed.client })
|
|
92
87
|
|
|
93
|
-
this.integration = integration!
|
|
94
|
-
this.model = modelId!
|
|
95
88
|
this.namespace = parsed.namespace
|
|
96
|
-
this.
|
|
97
|
-
this.
|
|
98
|
-
this.Model = Models.find((m) => m.id === parsed.modelId)!
|
|
89
|
+
this._userId = parsed.userId
|
|
90
|
+
this.Model = parsed.modelId as ModelId
|
|
99
91
|
this.activeLearning = parsed.activeLearning
|
|
100
92
|
|
|
101
93
|
this.adapter = parsed.activeLearning?.enable
|
|
102
|
-
? new TableAdapter({ client: this.client, tableName: parsed.activeLearning.tableName })
|
|
94
|
+
? new TableAdapter({ client: this.client.client, tableName: parsed.activeLearning.tableName })
|
|
103
95
|
: new MemoryAdapter([])
|
|
104
96
|
}
|
|
105
97
|
|
|
106
98
|
/** @internal */
|
|
107
99
|
protected async callModel(
|
|
108
|
-
props:
|
|
109
|
-
):
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
if (retries >= 0) {
|
|
116
|
-
await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
117
|
-
} else {
|
|
118
|
-
throw new Error('Failed to call model after multiple retries')
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
throw new Error('Failed to call model after multiple retries')
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/** @internal */
|
|
127
|
-
private async _callModel(
|
|
128
|
-
props: Partial<llm.generateContent.Input>
|
|
129
|
-
): Promise<llm.generateContent.Output & { metadata: GenerationMetadata }> {
|
|
130
|
-
let retries = this.retry.maxRetries
|
|
131
|
-
do {
|
|
132
|
-
const start = Date.now()
|
|
133
|
-
const input: llm.generateContent.Input = {
|
|
134
|
-
messages: [],
|
|
135
|
-
temperature: 0.0,
|
|
136
|
-
topP: 1,
|
|
137
|
-
model: { id: this.model },
|
|
138
|
-
userId: this.userId,
|
|
139
|
-
...props
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
const { output } = (await this.client.callAction({
|
|
143
|
-
type: `${this.integration}:generateContent`,
|
|
144
|
-
input
|
|
145
|
-
})) as unknown as { output: llm.generateContent.Output }
|
|
146
|
-
|
|
147
|
-
const latency = Date.now() - start
|
|
148
|
-
|
|
149
|
-
return {
|
|
150
|
-
...output,
|
|
151
|
-
metadata: {
|
|
152
|
-
model: this.model,
|
|
153
|
-
latency,
|
|
154
|
-
cost: { input: output.usage.inputCost, output: output.usage.outputCost },
|
|
155
|
-
tokens: { input: output.usage.inputTokens, output: output.usage.outputTokens }
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
} while (--retries > 0)
|
|
100
|
+
props: Parameters<Cognitive['generateContent']>[0]
|
|
101
|
+
): ReturnType<Cognitive['generateContent']> {
|
|
102
|
+
return this.client.generateContent({
|
|
103
|
+
...props,
|
|
104
|
+
model: this.Model,
|
|
105
|
+
userId: this._userId,
|
|
106
|
+
})
|
|
159
107
|
}
|
|
160
108
|
|
|
161
109
|
protected async getTokenizer() {
|
|
@@ -164,11 +112,17 @@ export class Zai {
|
|
|
164
112
|
// there's an issue with wasm, it doesn't load immediately
|
|
165
113
|
await new Promise((resolve) => setTimeout(resolve, 25))
|
|
166
114
|
}
|
|
167
|
-
return getWasmTokenizer()
|
|
115
|
+
return getWasmTokenizer() as TextTokenizer
|
|
168
116
|
})()
|
|
169
117
|
return Zai.tokenizer
|
|
170
118
|
}
|
|
171
119
|
|
|
120
|
+
protected async fetchModelDetails(): Promise<void> {
|
|
121
|
+
if (!this.ModelDetails) {
|
|
122
|
+
this.ModelDetails = await this.client.getModelDetails(this.Model)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
172
126
|
protected get taskId() {
|
|
173
127
|
if (!this.activeLearning.enable) {
|
|
174
128
|
return undefined
|
|
@@ -179,15 +133,15 @@ export class Zai {
|
|
|
179
133
|
|
|
180
134
|
public with(options: Partial<ZaiConfig>): Zai {
|
|
181
135
|
return new Zai({
|
|
182
|
-
...this.
|
|
183
|
-
...options
|
|
136
|
+
...this._originalConfig,
|
|
137
|
+
...options,
|
|
184
138
|
})
|
|
185
139
|
}
|
|
186
140
|
|
|
187
141
|
public learn(taskId: string) {
|
|
188
142
|
return new Zai({
|
|
189
|
-
...this.
|
|
190
|
-
activeLearning: { ...this.activeLearning, taskId, enable: true }
|
|
143
|
+
...this._originalConfig,
|
|
144
|
+
activeLearning: { ...this.activeLearning, taskId, enable: true },
|
|
191
145
|
})
|
|
192
146
|
}
|
|
193
147
|
}
|
package/tsconfig.json
CHANGED
|
@@ -1,32 +1,12 @@
|
|
|
1
1
|
{
|
|
2
|
+
"extends": "../../tsconfig.json",
|
|
2
3
|
"compilerOptions": {
|
|
3
|
-
"target": "ESNext",
|
|
4
|
-
"module": "ESNext",
|
|
5
|
-
"moduleResolution": "bundler",
|
|
6
4
|
"outDir": "dist",
|
|
7
|
-
"allowJs": true,
|
|
8
|
-
"skipLibCheck": true,
|
|
9
|
-
"esModuleInterop": true,
|
|
10
|
-
"allowSyntheticDefaultImports": true,
|
|
11
|
-
"forceConsistentCasingInFileNames": true,
|
|
12
|
-
"disableReferencedProjectLoad": true,
|
|
13
|
-
"resolveJsonModule": true,
|
|
14
|
-
"isolatedModules": true,
|
|
15
5
|
"strict": false,
|
|
16
|
-
"noUnusedLocals": true,
|
|
17
|
-
"noUnusedParameters": true,
|
|
18
|
-
"noUncheckedIndexedAccess": true,
|
|
19
|
-
"lib": ["dom", "ESNext", "dom.iterable"],
|
|
20
|
-
"declaration": true,
|
|
21
|
-
"noEmit": false,
|
|
22
6
|
"paths": {
|
|
23
7
|
"@botpress/zai": ["./src/zai.ts"]
|
|
24
8
|
}
|
|
25
9
|
},
|
|
26
10
|
"exclude": ["node_modules", "dist"],
|
|
27
|
-
"include": ["src/**/*", "vitest.d.ts"]
|
|
28
|
-
"ts-node": {
|
|
29
|
-
"esm": true,
|
|
30
|
-
"require": ["dotenv/config", "./ensure-env.cjs"]
|
|
31
|
-
}
|
|
11
|
+
"include": ["src/**/*", "vitest.d.ts", "e2e/**/*"]
|
|
32
12
|
}
|