@xsai/text-messages 0.0.0-stage → 1.0.0-beta.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/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Moeru AI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,28 @@
1
- # Temporary Holding Version
1
+ # @xsai/text-messages
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ <!-- automd:file src="/docs/snippets/text-messages.md" -->
4
+
5
+ ```sh
6
+ pnpm add @xsai/text @xsai/text-messages
7
+ ```
8
+
9
+ ```ts
10
+ import { generateText } from '@xsai/text'
11
+ import { messages } from '@xsai/text-messages'
12
+
13
+ const model = messages({
14
+ apiKey: process.env.ANTHROPIC_API_KEY,
15
+ baseURL: 'https://api.anthropic.com/v1/',
16
+ model: 'claude-haiku-5.5',
17
+ })
18
+
19
+ const { text } = await generateText(model, {
20
+ input: 'Say hello.',
21
+ maxOutputTokens: 128,
22
+ })
23
+ console.log(text)
24
+ ```
25
+
26
+ <!-- /automd -->
27
+
28
+ Read the [documentation](https://xsai.js.org/text/adapters).
@@ -0,0 +1,59 @@
1
+ import { HttpOptions } from '@xsai/shared';
2
+ import { LanguageModel } from '@xsai/text';
3
+
4
+ interface MessagesCitation {
5
+ [key: string]: unknown;
6
+ type: string;
7
+ }
8
+ interface MessagesPartMetadata {
9
+ citations?: MessagesCitation[];
10
+ signature?: string;
11
+ }
12
+
13
+ interface MessagesCacheControl {
14
+ ttl?: '1h' | '5m';
15
+ type: 'ephemeral';
16
+ }
17
+ interface MessagesMcpServer {
18
+ authorization_token?: string;
19
+ name: string;
20
+ type: 'url';
21
+ url: string;
22
+ }
23
+ interface MessagesProviderOptions {
24
+ betas?: readonly string[];
25
+ /** Automatic caching at the last cacheable part in the request. */
26
+ cacheControl?: MessagesCacheControl;
27
+ mcpServers?: readonly MessagesMcpServer[];
28
+ stopSequences?: readonly string[];
29
+ /** Thinking mode and display. Effort stays in `reasoningEffort`. */
30
+ thinking?: MessagesThinkingConfig;
31
+ tools?: readonly MessagesProviderTool[];
32
+ }
33
+ interface MessagesProviderTool {
34
+ [key: string]: unknown;
35
+ type: string;
36
+ }
37
+ type MessagesThinkingConfig = {
38
+ budget_tokens: number;
39
+ display?: 'omitted' | 'summarized';
40
+ type: 'enabled';
41
+ } | {
42
+ display?: 'omitted' | 'summarized';
43
+ type: 'adaptive';
44
+ } | {
45
+ type: 'between_tools' | 'disabled';
46
+ };
47
+
48
+ declare module '@xsai/text' {
49
+ interface LanguageModelProviderOptions {
50
+ messages?: MessagesProviderOptions;
51
+ }
52
+ interface PartProviderMetadata {
53
+ messages?: MessagesPartMetadata;
54
+ }
55
+ }
56
+ declare const messages: (options: HttpOptions) => LanguageModel;
57
+
58
+ export { messages };
59
+ export type { MessagesCacheControl, MessagesCitation, MessagesMcpServer, MessagesPartMetadata, MessagesProviderOptions, MessagesProviderTool, MessagesThinkingConfig };
package/dist/index.js ADDED
@@ -0,0 +1,412 @@
1
+ import { XSAIError, requestURL } from '@xsai/shared';
2
+ import { WireEventStream, resolveSchema, wireRequest } from '@xsai/text/internal';
3
+
4
+ const mapStop = (FinishReason) => {
5
+ switch (FinishReason) {
6
+ case "content_filter":
7
+ return { reason: "content-filter", status: "incomplete" };
8
+ case "end_turn":
9
+ case "stop_sequence":
10
+ return { reason: "stop", status: "completed" };
11
+ case "max_output_tokens":
12
+ case "max_tokens":
13
+ return { reason: "length", status: "incomplete" };
14
+ case null:
15
+ case void 0:
16
+ return { status: "completed" };
17
+ case "refusal":
18
+ return { reason: "refusal", status: "completed" };
19
+ case "tool_use":
20
+ return { reason: "tool-calls", status: "completed" };
21
+ default:
22
+ return { reason: FinishReason, status: "completed" };
23
+ }
24
+ };
25
+ const mergeUsage = (start, delta) => {
26
+ const inputTokens = delta?.input_tokens ?? start?.input_tokens;
27
+ const outputTokens = delta?.output_tokens ?? start?.output_tokens;
28
+ if (inputTokens == null && outputTokens == null)
29
+ return void 0;
30
+ const input = inputTokens ?? 0;
31
+ const output = outputTokens ?? 0;
32
+ const cacheRead = start?.cache_read_input_tokens ?? delta?.cache_read_input_tokens;
33
+ const cacheCreation = start?.cache_creation_input_tokens ?? delta?.cache_creation_input_tokens;
34
+ return {
35
+ cacheCreationInputTokens: cacheCreation,
36
+ cacheReadInputTokens: cacheRead,
37
+ inputTokens: input,
38
+ outputTokens: output,
39
+ reasoningTokens: delta?.output_tokens_details?.thinking_tokens,
40
+ // Anthropic reports no total and counts cache buckets separately from input_tokens.
41
+ totalTokens: input + output + (cacheRead ?? 0) + (cacheCreation ?? 0)
42
+ };
43
+ };
44
+ class MessagesEventStream extends WireEventStream {
45
+ citations = /* @__PURE__ */ new Map();
46
+ deltaUsage;
47
+ FinishReason;
48
+ providerBlocks = /* @__PURE__ */ new Map();
49
+ signatures = /* @__PURE__ */ new Map();
50
+ source;
51
+ startUsage;
52
+ constructor(source, includeRawEvents = false) {
53
+ super((event, builder) => {
54
+ switch (event.type) {
55
+ case "content_block_delta":
56
+ this.onDelta(builder, event);
57
+ break;
58
+ case "content_block_start":
59
+ this.onStart(builder, event);
60
+ break;
61
+ case "content_block_stop":
62
+ this.onStop(builder, event);
63
+ break;
64
+ case "error":
65
+ builder.fail(new XSAIError("model-error", event.error.message, {
66
+ cause: event.error
67
+ }));
68
+ break;
69
+ case "message_delta":
70
+ this.FinishReason = event.delta.stop_reason;
71
+ this.deltaUsage = event.usage;
72
+ break;
73
+ case "message_start":
74
+ builder.meta({ messageId: event.message.id });
75
+ this.startUsage = event.message.usage;
76
+ break;
77
+ case "message_stop": {
78
+ const usage = mergeUsage(this.startUsage, this.deltaUsage);
79
+ if (usage != null)
80
+ builder.meta({ usage });
81
+ const finish = mapStop(this.FinishReason);
82
+ builder.done(finish.status, finish.reason);
83
+ break;
84
+ }
85
+ }
86
+ }, includeRawEvents);
87
+ this.source = source;
88
+ }
89
+ onDelta(builder, event) {
90
+ switch (event.delta.type) {
91
+ case "citations_delta": {
92
+ const citations = this.citations.get(event.index);
93
+ if (citations == null)
94
+ this.citations.set(event.index, [event.delta.citation]);
95
+ else
96
+ citations.push(event.delta.citation);
97
+ break;
98
+ }
99
+ case "input_json_delta": {
100
+ const provider = this.providerBlocks.get(event.index);
101
+ if (provider == null)
102
+ builder.delta(event.index, event.delta.partial_json);
103
+ else
104
+ provider.input += event.delta.partial_json;
105
+ break;
106
+ }
107
+ case "signature_delta":
108
+ this.signatures.set(event.index, (this.signatures.get(event.index) ?? "") + event.delta.signature);
109
+ break;
110
+ case "text_delta":
111
+ builder.delta(event.index, event.delta.text);
112
+ break;
113
+ case "thinking_delta":
114
+ builder.delta(event.index, event.delta.thinking);
115
+ break;
116
+ }
117
+ }
118
+ onStart(builder, event) {
119
+ const block = event.content_block;
120
+ switch (block.type) {
121
+ case "document":
122
+ case "image":
123
+ case "tool_result":
124
+ break;
125
+ case "redacted_thinking":
126
+ builder.start(event.index, "reasoning");
127
+ builder.end(event.index, {
128
+ content: { content: [{ text: block.data, type: "redacted" }], type: "reasoning" }
129
+ });
130
+ break;
131
+ case "text":
132
+ builder.start(event.index, "text");
133
+ if (block.citations != null && block.citations.length > 0)
134
+ this.citations.set(event.index, [...block.citations]);
135
+ if (block.text !== "")
136
+ builder.delta(event.index, block.text);
137
+ break;
138
+ case "thinking":
139
+ builder.start(event.index, "reasoning");
140
+ if (block.thinking !== "")
141
+ builder.delta(event.index, block.thinking);
142
+ if (block.signature != null)
143
+ this.signatures.set(event.index, block.signature);
144
+ break;
145
+ case "tool_use":
146
+ builder.start(event.index, "tool-call", { callId: block.id, id: block.id, name: block.name });
147
+ if (typeof block.input === "object" && block.input !== null && Object.keys(block.input).length > 0)
148
+ builder.delta(event.index, JSON.stringify(block.input));
149
+ break;
150
+ default:
151
+ builder.start(event.index, "provider", { source: this.source });
152
+ this.providerBlocks.set(event.index, { block, input: "" });
153
+ }
154
+ }
155
+ onStop(builder, event) {
156
+ const provider = this.providerBlocks.get(event.index);
157
+ this.providerBlocks.delete(event.index);
158
+ if (provider != null) {
159
+ const value = provider.input === "" ? provider.block : { ...provider.block, input: JSON.parse(provider.input) };
160
+ builder.end(event.index, { content: { source: this.source, type: "provider", value } });
161
+ return;
162
+ }
163
+ const signature = this.signatures.get(event.index);
164
+ this.signatures.delete(event.index);
165
+ const citations = this.citations.get(event.index);
166
+ this.citations.delete(event.index);
167
+ builder.end(event.index, signature == null && citations == null ? {} : { providerMetadata: { messages: {
168
+ ...citations == null ? {} : { citations },
169
+ ...signature == null ? {} : { signature }
170
+ } } });
171
+ }
172
+ }
173
+
174
+ const SUPPORTED_FORMATS = /* @__PURE__ */ new Set([
175
+ "date",
176
+ "date-time",
177
+ "duration",
178
+ "email",
179
+ "hostname",
180
+ "ipv4",
181
+ "ipv6",
182
+ "time",
183
+ "uri",
184
+ "uuid"
185
+ ]);
186
+ const normalizeSchema = (schema) => {
187
+ const normalized = { ...schema };
188
+ const normalize = (schema2) => typeof schema2 === "boolean" ? schema2 : normalizeSchema(schema2);
189
+ if (normalized.type === "object" || normalized.properties != null) {
190
+ normalized.properties ??= {};
191
+ normalized.properties = Object.fromEntries(Object.entries(normalized.properties).map(([name, property]) => [name, normalize(property)]));
192
+ normalized.additionalProperties = false;
193
+ }
194
+ if (normalized.$defs != null)
195
+ normalized.$defs = Object.fromEntries(Object.entries(normalized.$defs).map(([name, definition]) => [name, normalize(definition)]));
196
+ if (normalized.definitions != null)
197
+ normalized.definitions = Object.fromEntries(Object.entries(normalized.definitions).map(([name, definition]) => [name, normalize(definition)]));
198
+ if (normalized.items != null) {
199
+ normalized.items = Array.isArray(normalized.items) ? normalized.items.map(normalize) : normalize(normalized.items);
200
+ }
201
+ if (normalized.oneOf != null) {
202
+ const oneOf = normalized.oneOf;
203
+ delete normalized.oneOf;
204
+ normalized.anyOf = normalized.anyOf == null ? oneOf : normalized.anyOf.concat(oneOf);
205
+ }
206
+ if (normalized.anyOf != null)
207
+ normalized.anyOf = normalized.anyOf.map(normalize);
208
+ if (normalized.allOf != null)
209
+ normalized.allOf = normalized.allOf.map(normalize);
210
+ for (const key of ["exclusiveMaximum", "exclusiveMinimum", "maximum", "minimum", "multipleOf", "minLength", "maxLength", "maxItems"])
211
+ delete normalized[key];
212
+ if ((normalized.minItems ?? 0) > 1)
213
+ delete normalized.minItems;
214
+ if (normalized.format != null && !SUPPORTED_FORMATS.has(normalized.format))
215
+ delete normalized.format;
216
+ return normalized;
217
+ };
218
+
219
+ const normalizeFormat = (outputFormat) => outputFormat == null ? void 0 : {
220
+ schema: normalizeSchema(resolveSchema(outputFormat).schema),
221
+ type: "json_schema"
222
+ };
223
+
224
+ const DATA_URL_PATTERN = /^data:([^;,]+);base64,(.*)$/s;
225
+ const parseDataUrl = (data) => {
226
+ const match = DATA_URL_PATTERN.exec(data);
227
+ return match === null ? void 0 : { data: match[2], mediaType: match[1] };
228
+ };
229
+ const normalizeInputTextPart = (part) => ({
230
+ // TODO: Move citations to a first-class TextPart field.
231
+ citations: part.providerMetadata?.messages?.citations,
232
+ text: part.text,
233
+ type: "text"
234
+ });
235
+ const normalizeImagePart = (part) => {
236
+ const data = part.data.toString();
237
+ const dataUrl = parseDataUrl(data);
238
+ if (dataUrl != null)
239
+ return { source: { data: dataUrl.data, media_type: dataUrl.mediaType, type: "base64" }, type: "image" };
240
+ if (URL.canParse(data))
241
+ return { source: { type: "url", url: data }, type: "image" };
242
+ throw new XSAIError("invalid-input", "Image part data must be a URL or a base64 data: URL");
243
+ };
244
+ const normalizeFilePart = (part) => {
245
+ const data = part.data.toString();
246
+ const dataUrl = parseDataUrl(data);
247
+ if (dataUrl != null) {
248
+ if (dataUrl.mediaType !== "application/pdf")
249
+ throw new XSAIError("invalid-input", `Anthropic documents only support PDF or plain text, got: ${dataUrl.mediaType}`);
250
+ return { source: { data: dataUrl.data, media_type: "application/pdf", type: "base64" }, type: "document" };
251
+ }
252
+ if (URL.canParse(data))
253
+ return { source: { type: "url", url: data }, type: "document" };
254
+ return { source: { data, media_type: "text/plain", type: "text" }, type: "document" };
255
+ };
256
+ const normalizeToolResultContent = (content) => {
257
+ switch (content.type) {
258
+ case "image":
259
+ return normalizeImagePart(content);
260
+ case "text":
261
+ return normalizeInputTextPart(content);
262
+ }
263
+ };
264
+ const normalizeToolResultPart = (part) => ({
265
+ content: typeof part.output === "string" ? part.output : part.output.map(normalizeToolResultContent),
266
+ is_error: part.isError,
267
+ tool_use_id: part.callId,
268
+ type: "tool_result"
269
+ });
270
+ const normalizeReasoningPartContent = (content) => {
271
+ switch (content.type) {
272
+ case "encrypted":
273
+ case "redacted":
274
+ return [{ data: content.text, type: "redacted_thinking" }];
275
+ case "summary":
276
+ return [];
277
+ case "text":
278
+ return [{ thinking: content.text, type: "thinking" }];
279
+ }
280
+ };
281
+ const normalizeReasoningPart = (part) => {
282
+ const blocks = part.content.flatMap(normalizeReasoningPartContent);
283
+ const signature = part.providerMetadata?.messages?.signature;
284
+ if (signature == null)
285
+ return blocks;
286
+ const block = blocks.findLast((block2) => block2.type === "thinking");
287
+ if (block?.type === "thinking")
288
+ block.signature = signature;
289
+ return blocks;
290
+ };
291
+ const normalizeToolCallPart = (part) => ({
292
+ id: part.callId,
293
+ input: part.arguments === "" ? {} : JSON.parse(part.arguments),
294
+ name: part.name,
295
+ type: "tool_use"
296
+ });
297
+ const normalizeUserPart = (part) => {
298
+ switch (part.type) {
299
+ case "file":
300
+ return normalizeFilePart(part);
301
+ case "image":
302
+ return normalizeImagePart(part);
303
+ case "text":
304
+ return normalizeInputTextPart(part);
305
+ case "tool-result":
306
+ return normalizeToolResultPart(part);
307
+ }
308
+ };
309
+ const normalizeAssistantPart = (part, source) => {
310
+ switch (part.type) {
311
+ case "provider":
312
+ return part.source === source ? [part.value] : [];
313
+ case "reasoning":
314
+ return normalizeReasoningPart(part);
315
+ case "refusal":
316
+ throw new XSAIError("invalid-input", "Refusal parts cannot be replayed on the Messages API");
317
+ case "text":
318
+ return [normalizeInputTextPart(part)];
319
+ case "tool-call":
320
+ return [normalizeToolCallPart(part)];
321
+ }
322
+ };
323
+ const normalizeUserMessage = (message) => ({
324
+ content: typeof message.content === "string" ? [{ text: message.content, type: "text" }] : message.content.map(normalizeUserPart),
325
+ role: "user"
326
+ });
327
+ const normalizeAssistantMessage = (message, source) => {
328
+ const content = typeof message.content === "string" ? [{ text: message.content, type: "text" }] : message.content.flatMap((part) => normalizeAssistantPart(part, source));
329
+ return content.length === 0 ? void 0 : { content, role: "assistant" };
330
+ };
331
+ const systemText = (content) => typeof content === "string" ? [{ text: content, type: "text" }] : content.map(normalizeInputTextPart);
332
+ const normalizeInput = (options, source) => {
333
+ const system = [];
334
+ const messages = [];
335
+ if (options.instructions != null)
336
+ system.push({ text: options.instructions, type: "text" });
337
+ for (const message of typeof options.input === "string" ? [{ content: options.input, role: "user" }] : options.input) {
338
+ switch (message.role) {
339
+ case "assistant":
340
+ {
341
+ const assistant = normalizeAssistantMessage(message, source);
342
+ if (assistant != null)
343
+ messages.push(assistant);
344
+ }
345
+ break;
346
+ case "developer":
347
+ case "system":
348
+ system.push(...systemText(message.content));
349
+ break;
350
+ case "user":
351
+ messages.push(normalizeUserMessage(message));
352
+ break;
353
+ }
354
+ }
355
+ return {
356
+ messages,
357
+ system: system.length === 0 ? void 0 : system
358
+ };
359
+ };
360
+
361
+ const normalizeTools = (tools) => tools?.map((tool) => ({
362
+ description: tool.description,
363
+ input_schema: normalizeSchema(tool.inputSchema.schema),
364
+ name: tool.name
365
+ }));
366
+ const mergeTools = (providerTools, tools) => {
367
+ const merged = [
368
+ ...providerTools ?? [],
369
+ ...normalizeTools(tools) ?? []
370
+ ];
371
+ return merged.length === 0 ? void 0 : merged;
372
+ };
373
+ const normalizeToolChoice = (toolChoice) => toolChoice == null ? void 0 : typeof toolChoice === "string" ? { type: toolChoice === "required" ? "any" : toolChoice } : { name: toolChoice.name, type: "tool" };
374
+
375
+ const ANTHROPIC_VERSION = "2023-06-01";
376
+ const messages = (options) => async (modelOptions) => {
377
+ const source = requestURL("messages", options.baseURL).toString();
378
+ const { messages: inputMessages, system } = normalizeInput(modelOptions, source);
379
+ const maxTokens = modelOptions.maxOutputTokens;
380
+ const outputFormat = normalizeFormat(modelOptions.outputFormat);
381
+ const providerOptions = modelOptions.providerOptions?.messages;
382
+ if (typeof maxTokens !== "number")
383
+ throw new XSAIError("invalid-input", "maxOutputTokens is required for the Messages API");
384
+ return wireRequest({
385
+ body: {
386
+ cache_control: providerOptions?.cacheControl,
387
+ max_tokens: maxTokens,
388
+ mcp_servers: providerOptions?.mcpServers,
389
+ messages: inputMessages,
390
+ model: options.model,
391
+ output_config: modelOptions.reasoningEffort == null && outputFormat == null ? void 0 : { effort: modelOptions.reasoningEffort, format: outputFormat },
392
+ stop_sequences: providerOptions?.stopSequences,
393
+ stream: true,
394
+ system,
395
+ temperature: modelOptions.temperature,
396
+ thinking: providerOptions?.thinking,
397
+ tool_choice: normalizeToolChoice(modelOptions.toolChoice),
398
+ tools: mergeTools(providerOptions?.tools, modelOptions.tools),
399
+ top_p: modelOptions.topP
400
+ },
401
+ headers: {
402
+ ...options.headers,
403
+ "anthropic-beta": providerOptions?.betas !== void 0 && providerOptions.betas.length > 0 ? providerOptions.betas.join(",") : options.headers?.["anthropic-beta"],
404
+ "anthropic-version": ANTHROPIC_VERSION,
405
+ "Content-Type": "application/json",
406
+ "x-api-key": options.apiKey ?? options.headers?.["x-api-key"]
407
+ },
408
+ path: source
409
+ }, options, modelOptions, new MessagesEventStream(source, modelOptions.includeRawEvents));
410
+ };
411
+
412
+ export { messages };
package/package.json CHANGED
@@ -1,6 +1,49 @@
1
1
  {
2
2
  "name": "@xsai/text-messages",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "type": "module",
4
+ "version": "1.0.0-beta.1",
5
+ "description": "extra-small AI SDK: Anthropic Messages API model for @xsai/text.",
6
+ "author": "Moeru AI",
7
+ "license": "MIT",
8
+ "homepage": "https://xsai.js.org",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/moeru-ai/xsai.git",
12
+ "directory": "packages/text-messages"
13
+ },
14
+ "bugs": "https://github.com/moeru-ai/xsai/issues",
15
+ "keywords": [
16
+ "xsai",
17
+ "ai",
18
+ "anthropic",
19
+ "messages",
20
+ "claude"
21
+ ],
22
+ "sideEffects": false,
23
+ "exports": {
24
+ ".": {
25
+ "types": "./dist/index.d.ts",
26
+ "default": "./dist/index.js"
27
+ },
28
+ "./package.json": "./package.json"
29
+ },
30
+ "files": [
31
+ "dist"
32
+ ],
33
+ "peerDependencies": {
34
+ "@xsai/text": "1.0.0-beta.1"
35
+ },
36
+ "dependencies": {
37
+ "@xsai/shared": "1.0.0-beta.1"
38
+ },
39
+ "devDependencies": {
40
+ "@xsai/text": "1.0.0-beta.1"
41
+ },
42
+ "scripts": {
43
+ "build": "pkgroll --clean-dist",
44
+ "test": "vitest run --exclude e2e/**",
45
+ "test:watch": "vitest"
46
+ },
47
+ "main": "./dist/index.js",
48
+ "types": "./dist/index.d.ts"
6
49
  }