@ai-sdk/vue 3.0.45 → 3.0.46

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/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # @ai-sdk/vue
2
2
 
3
+ ## 3.0.46
4
+
5
+ ### Patch Changes
6
+
7
+ - 8dc54db: chore: add src folders to package bundle
8
+ - ai@6.0.46
9
+
3
10
  ## 3.0.45
4
11
 
5
12
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/vue",
3
- "version": "3.0.45",
3
+ "version": "3.0.46",
4
4
  "license": "Apache-2.0",
5
5
  "sideEffects": false,
6
6
  "main": "./dist/index.js",
@@ -16,13 +16,14 @@
16
16
  },
17
17
  "files": [
18
18
  "dist/**/*",
19
+ "src",
19
20
  "CHANGELOG.md",
20
21
  "README.md"
21
22
  ],
22
23
  "dependencies": {
23
24
  "swrv": "^1.0.4",
24
25
  "@ai-sdk/provider-utils": "4.0.8",
25
- "ai": "6.0.45"
26
+ "ai": "6.0.46"
26
27
  },
27
28
  "devDependencies": {
28
29
  "@testing-library/jest-dom": "^6.6.3",
@@ -37,7 +38,7 @@
37
38
  "typescript": "5.8.3",
38
39
  "vitest": "2.1.4",
39
40
  "zod": "3.25.76",
40
- "@ai-sdk/test-server": "1.0.1",
41
+ "@ai-sdk/test-server": "1.0.2",
41
42
  "@vercel/ai-tsconfig": "0.0.0",
42
43
  "eslint-config-vercel-ai": "0.0.0"
43
44
  },
@@ -0,0 +1,37 @@
1
+ <script setup lang="ts">
2
+ import { generateId } from 'ai';
3
+ import { mockId } from 'ai/test';
4
+ import { Chat } from './chat.vue';
5
+
6
+ const chat = new Chat({
7
+ id: generateId(),
8
+ generateId: mockId(),
9
+ });
10
+ </script>
11
+
12
+ <template>
13
+ <div>
14
+ <div data-testid="messages">
15
+ {{ JSON.stringify(chat.messages, null, 2) }}
16
+ </div>
17
+
18
+ <button
19
+ data-testid="do-append"
20
+ @click="
21
+ chat.sendMessage({
22
+ parts: [
23
+ {
24
+ type: 'file',
25
+ url: 'https://example.com/image.png',
26
+ mediaType: 'image/png',
27
+ },
28
+ {
29
+ type: 'text',
30
+ text: 'Message with image attachment',
31
+ },
32
+ ],
33
+ })
34
+ "
35
+ />
36
+ </div>
37
+ </template>
@@ -0,0 +1,56 @@
1
+ <script setup lang="ts">
2
+ import { generateId } from 'ai';
3
+ import { mockId } from 'ai/test';
4
+ import { computed, ref } from 'vue';
5
+ import { Chat } from './chat.vue';
6
+
7
+ const chat = new Chat({
8
+ id: generateId(),
9
+ generateId: mockId(),
10
+ });
11
+ const files = ref<FileList>();
12
+ const fileInputRef = ref<HTMLInputElement | null>(null);
13
+ const isLoading = computed(() => chat.status !== 'ready');
14
+ const input = ref('');
15
+ </script>
16
+
17
+ <template>
18
+ <div>
19
+ <div data-testid="messages">
20
+ {{ JSON.stringify(chat.messages, null, 2) }}
21
+ </div>
22
+
23
+ <form
24
+ @submit="
25
+ event => {
26
+ chat.sendMessage({ text: input, files });
27
+ files = undefined;
28
+ if (fileInputRef) {
29
+ fileInputRef.value = '';
30
+ }
31
+ }
32
+ "
33
+ data-testid="chat-form"
34
+ >
35
+ <input
36
+ type="file"
37
+ @change="
38
+ event => {
39
+ if (event.target != null && 'files' in event.target) {
40
+ files = event.target.files as FileList;
41
+ }
42
+ }
43
+ "
44
+ multiple
45
+ ref="fileInputRef"
46
+ data-testid="file-input"
47
+ />
48
+ <input
49
+ v-model="input"
50
+ :disabled="isLoading"
51
+ data-testid="message-input"
52
+ />
53
+ <button type="submit" data-testid="submit-button">Send</button>
54
+ </form>
55
+ </div>
56
+ </template>
@@ -0,0 +1,33 @@
1
+ <script setup lang="ts">
2
+ import { UIMessage } from 'ai';
3
+ import { reactive } from 'vue';
4
+ import { Chat } from './chat.vue';
5
+
6
+ const onFinishCalls: Array<{ message: UIMessage }> = reactive([]);
7
+
8
+ const chat = new Chat({
9
+ onFinish: options => {
10
+ onFinishCalls.push(options);
11
+ },
12
+ });
13
+ </script>
14
+
15
+ <template>
16
+ <div>
17
+ <div data-testid="status">{{ chat.status }}</div>
18
+ <div data-testid="error">{{ chat.error?.toString() }}</div>
19
+ <div
20
+ v-for="(m, idx) in chat.messages"
21
+ key="m.id"
22
+ :data-testid="`message-${idx}`"
23
+ >
24
+ {{ m.role === 'user' ? 'User: ' : 'AI: ' }}
25
+ {{
26
+ m.parts.map(part => (part.type === 'text' ? part.text : '')).join('')
27
+ }}
28
+ </div>
29
+
30
+ <button data-testid="do-append" @click="chat.sendMessage({ text: 'hi' })" />
31
+ <div data-testid="on-finish-calls">{{ JSON.stringify(onFinishCalls) }}</div>
32
+ </div>
33
+ </template>
@@ -0,0 +1,33 @@
1
+ <script setup lang="ts">
2
+ import { UIMessage } from 'ai';
3
+ import { Chat } from './chat.vue';
4
+ import { ref } from 'vue';
5
+
6
+ const messages = ref<UIMessage[]>([
7
+ { id: 'message-0', role: 'user', parts: [{ type: 'text', text: 'Greetings.' }] },
8
+ { id: 'message-1', role: 'assistant', parts: [{ type: 'text', text: 'Hello.' }] },
9
+ ]);
10
+
11
+ const chat = new Chat({
12
+ messages: messages.value,
13
+ });
14
+ </script>
15
+
16
+ <template>
17
+ <div>
18
+ <div data-testid="status">{{ chat.status }}</div>
19
+ <div data-testid="error">{{ chat.error?.toString() }}</div>
20
+ <div
21
+ v-for="(m, idx) in chat.messages"
22
+ key="m.id"
23
+ :data-testid="`message-${idx}`"
24
+ >
25
+ {{ m.role === 'user' ? 'User: ' : 'AI: ' }}
26
+ {{
27
+ m.parts.map(part => (part.type === 'text' ? part.text : '')).join('')
28
+ }}
29
+ </div>
30
+
31
+ <button data-testid="do-append" @click="chat.sendMessage({ text: 'Hi.' })" />
32
+ </div>
33
+ </template>
@@ -0,0 +1,59 @@
1
+ <script setup lang="ts">
2
+ import { DefaultChatTransport } from 'ai';
3
+ import { computed, ref } from 'vue';
4
+ import { Chat } from './chat.vue';
5
+
6
+ const options = ref<any>();
7
+
8
+ const chat = new Chat({
9
+ transport: new DefaultChatTransport({
10
+ api: '/api/chat',
11
+ prepareSendMessagesRequest(optionsArg) {
12
+ options.value = JSON.parse(JSON.stringify(optionsArg));
13
+ return {
14
+ body: { 'body-key': 'body-value' },
15
+ headers: { 'header-key': 'header-value' },
16
+ };
17
+ },
18
+ }),
19
+ });
20
+
21
+ const isLoading = computed(() => chat.status !== 'ready');
22
+ </script>
23
+
24
+ <template>
25
+ <div>
26
+ <div data-testid="loading">{{ isLoading?.toString() }}</div>
27
+ <div
28
+ v-for="(m, idx) in chat.messages"
29
+ :key="m.id"
30
+ :data-testid="`message-${idx}`"
31
+ >
32
+ {{ m.role === 'user' ? 'User: ' : 'AI: ' }}
33
+ {{
34
+ m.parts.map(part => (part.type === 'text' ? part.text : '')).join('')
35
+ }}
36
+ </div>
37
+
38
+ <button
39
+ data-testid="do-append"
40
+ @click="
41
+ chat.sendMessage(
42
+ {
43
+ role: 'user',
44
+ parts: [{ text: 'hi', type: 'text' }],
45
+ },
46
+ {
47
+ body: { 'request-body-key': 'request-body-value' },
48
+ headers: { 'request-header-key': 'request-header-value' },
49
+ metadata: { 'request-metadata-key': 'request-metadata-value' },
50
+ },
51
+ )
52
+ "
53
+ />
54
+
55
+ <div v-if="options" data-testid="on-options">
56
+ {{ options }}
57
+ </div>
58
+ </div>
59
+ </template>
@@ -0,0 +1,37 @@
1
+ <script setup lang="ts">
2
+ import { generateId } from 'ai';
3
+ import { mockId } from 'ai/test';
4
+ import { Chat } from './chat.vue';
5
+
6
+ const chat = new Chat({
7
+ id: generateId(),
8
+ generateId: mockId(),
9
+ });
10
+ </script>
11
+
12
+ <template>
13
+ <div>
14
+ <div
15
+ v-for="(m, idx) in chat.messages"
16
+ key="m.id"
17
+ :data-testid="`message-${idx}`"
18
+ >
19
+ {{ m.role === 'user' ? 'User: ' : 'AI: ' }}
20
+ {{
21
+ m.parts.map(part => (part.type === 'text' ? part.text : '')).join('')
22
+ }}
23
+ </div>
24
+
25
+ <button data-testid="do-append" @click="chat.sendMessage({ text: 'hi' })" />
26
+
27
+ <button
28
+ data-testid="do-regenerate"
29
+ @click="
30
+ chat.regenerate({
31
+ body: { 'request-body-key': 'request-body-value' },
32
+ headers: { 'header-key': 'header-value' },
33
+ })
34
+ "
35
+ />
36
+ </div>
37
+ </template>
@@ -0,0 +1,43 @@
1
+ <script setup lang="ts">
2
+ import { reactive } from 'vue';
3
+ import { TextStreamChatTransport } from 'ai';
4
+ import { Chat } from './chat.vue';
5
+ import { UIMessage } from 'ai';
6
+
7
+ const onFinishCalls: Array<{ message: UIMessage }> = reactive([]);
8
+
9
+ const chat = new Chat({
10
+ onFinish: options => {
11
+ onFinishCalls.push(options);
12
+ },
13
+ transport: new TextStreamChatTransport({
14
+ api: '/api/chat',
15
+ }),
16
+ });
17
+ </script>
18
+
19
+ <template>
20
+ <div>
21
+ <div
22
+ v-for="(m, idx) in chat.messages"
23
+ key="m.id"
24
+ :data-testid="`message-${idx}`"
25
+ >
26
+ {{ m.role === 'user' ? 'User: ' : 'AI: ' }}
27
+ {{
28
+ m.parts.map(part => (part.type === 'text' ? part.text : '')).join('')
29
+ }}
30
+ </div>
31
+
32
+ <button
33
+ data-testid="do-append"
34
+ @click="
35
+ chat.sendMessage({
36
+ text: 'hi',
37
+ })
38
+ "
39
+ />
40
+
41
+ <div data-testid="on-finish-calls">{{ JSON.stringify(onFinishCalls) }}</div>
42
+ </div>
43
+ </template>
@@ -0,0 +1,48 @@
1
+ <script setup lang="ts">
2
+ import { isStaticToolUIPart } from 'ai';
3
+ import { Chat } from './chat.vue';
4
+
5
+ const chat = new Chat({});
6
+ </script>
7
+
8
+ <template>
9
+ <div>
10
+ <div
11
+ v-for="(m, idx) in chat.messages"
12
+ :key="m.id"
13
+ :data-testid="`message-${idx}`"
14
+ >
15
+ <div
16
+ v-for="(toolPart, toolIdx) in m.parts.filter(isStaticToolUIPart)"
17
+ :key="toolPart.toolCallId"
18
+ >
19
+ {{ JSON.stringify(toolPart) }}
20
+ <button
21
+ v-if="toolPart.state === 'input-available'"
22
+ :data-testid="`add-result-${toolIdx}`"
23
+ @click="
24
+ chat.addToolOutput({
25
+ toolCallId: toolPart.toolCallId,
26
+ tool: 'test-tool',
27
+ output: 'test-result',
28
+ })
29
+ "
30
+ />
31
+ </div>
32
+ <div :data-testid="`text-${idx}`">
33
+ {{
34
+ m.parts.map(part => (part.type === 'text' ? part.text : '')).join('')
35
+ }}
36
+ </div>
37
+ </div>
38
+
39
+ <button
40
+ data-testid="do-append"
41
+ @click="
42
+ chat.sendMessage({
43
+ text: 'hi',
44
+ })
45
+ "
46
+ />
47
+ </div>
48
+ </template>
@@ -0,0 +1,46 @@
1
+ <script setup lang="ts">
2
+ import { generateId } from 'ai';
3
+ import { mockId } from 'ai/test';
4
+ import { computed, ref } from 'vue';
5
+ import { Chat } from './chat.vue';
6
+
7
+ const chat = new Chat({
8
+ id: generateId(),
9
+ generateId: mockId(),
10
+ });
11
+ const isLoading = computed(() => chat.status !== 'ready');
12
+ const input = ref('');
13
+ </script>
14
+
15
+ <template>
16
+ <div>
17
+ <div data-testid="messages">
18
+ {{ JSON.stringify(chat.messages, null, 2) }}
19
+ </div>
20
+
21
+ <form
22
+ @submit="
23
+ event => {
24
+ chat.sendMessage({
25
+ text: input,
26
+ files: [
27
+ {
28
+ type: 'file',
29
+ mediaType: 'image/png',
30
+ url: 'https://example.com/image.png',
31
+ },
32
+ ],
33
+ });
34
+ }
35
+ "
36
+ data-testid="chat-form"
37
+ >
38
+ <input
39
+ v-model="input"
40
+ :disabled="isLoading"
41
+ data-testid="message-input"
42
+ />
43
+ <button type="submit" data-testid="submit-button">Send</button>
44
+ </form>
45
+ </div>
46
+ </template>
@@ -0,0 +1,17 @@
1
+ <script setup lang="ts">
2
+ import { useCompletion } from './use-completion';
3
+
4
+ const { completion, handleSubmit, input, isLoading, error } = useCompletion();
5
+ </script>
6
+
7
+ <template>
8
+ <div>
9
+ <div data-testid="loading">{{ isLoading?.toString() }}</div>
10
+ <div data-testid="error">{{ error?.toString() }}</div>
11
+ <div data-testid="completion">{{ completion }}</div>
12
+
13
+ <form @submit="handleSubmit">
14
+ <input data-testid="input" v-model="input" />
15
+ </form>
16
+ </div>
17
+ </template>
@@ -0,0 +1,19 @@
1
+ <script setup lang="ts">
2
+ import { useCompletion } from './use-completion';
3
+
4
+ const { completion, handleSubmit, input, isLoading, error } = useCompletion({
5
+ streamProtocol: 'text',
6
+ });
7
+ </script>
8
+
9
+ <template>
10
+ <div>
11
+ <div data-testid="loading">{{ isLoading?.toString() }}</div>
12
+ <div data-testid="error">{{ error?.toString() }}</div>
13
+ <div data-testid="completion">{{ completion }}</div>
14
+
15
+ <form @submit="handleSubmit">
16
+ <input data-testid="input" v-model="input" />
17
+ </form>
18
+ </div>
19
+ </template>
@@ -0,0 +1,39 @@
1
+ <script setup lang="ts">
2
+ import { experimental_useObject } from './use-object';
3
+ import { z } from 'zod/v4';
4
+ import { ref, reactive } from 'vue';
5
+
6
+ const onFinishCalls: Array<{
7
+ object: { content: string } | undefined;
8
+ error: Error | undefined;
9
+ }> = reactive([]);
10
+
11
+ const onErrorResult: Error | undefined = ref(undefined);
12
+
13
+ const { object, error, submit, isLoading, stop, clear } =
14
+ experimental_useObject({
15
+ api: '/api/use-object',
16
+ schema: z.object({ content: z.string() }),
17
+ onError(error) {
18
+ onErrorResult.value = error;
19
+ },
20
+ onFinish(event) {
21
+ onFinishCalls.push(event);
22
+ },
23
+ });
24
+ </script>
25
+
26
+ <template>
27
+ <div>
28
+ <div data-testid="loading">{{ isLoading.toString() }}</div>
29
+ <div data-testid="object">{{ JSON.stringify(object) }}</div>
30
+ <div data-testid="error">{{ error?.toString() }}</div>
31
+ <button data-testid="submit-button" @click="submit('test-input')">
32
+ Generate
33
+ </button>
34
+ <button data-testid="stop-button" @click="stop">Stop</button>
35
+ <button data-testid="clear-button" @click="clear">Clear</button>
36
+ <div data-testid="on-error-result">{{ onErrorResult?.toString() }}</div>
37
+ <div data-testid="on-finish-calls">{{ JSON.stringify(onFinishCalls) }}</div>
38
+ </div>
39
+ </template>
@@ -0,0 +1,29 @@
1
+ <script setup lang="ts">
2
+ import { experimental_useObject } from './use-object';
3
+ import { z } from 'zod/v4';
4
+ import { ref, reactive } from 'vue';
5
+
6
+ const { object, error, submit, isLoading, stop, clear } =
7
+ experimental_useObject({
8
+ api: '/api/use-object',
9
+ schema: z.object({ content: z.string() }),
10
+ headers: {
11
+ Authorization: 'Bearer TEST_TOKEN',
12
+ 'X-Custom-Header': 'CustomValue',
13
+ },
14
+ credentials: 'include',
15
+ });
16
+ </script>
17
+
18
+ <template>
19
+ <div>
20
+ <div data-testid="loading">{{ isLoading.toString() }}</div>
21
+ <div data-testid="object">{{ JSON.stringify(object) }}</div>
22
+ <div data-testid="error">{{ error?.toString() }}</div>
23
+ <button data-testid="submit-button" @click="submit('test-input')">
24
+ Generate
25
+ </button>
26
+ <button data-testid="stop-button" @click="stop">Stop</button>
27
+ <button data-testid="clear-button" @click="clear">Clear</button>
28
+ </div>
29
+ </template>
@@ -0,0 +1,70 @@
1
+ import {
2
+ AbstractChat,
3
+ ChatInit as BaseChatInit,
4
+ ChatState,
5
+ ChatStatus,
6
+ UIMessage,
7
+ } from 'ai';
8
+ import { Ref, ref } from 'vue';
9
+
10
+ class VueChatState<UI_MESSAGE extends UIMessage>
11
+ implements ChatState<UI_MESSAGE>
12
+ {
13
+ private messagesRef: Ref<UI_MESSAGE[]>;
14
+ private statusRef = ref<ChatStatus>('ready');
15
+ private errorRef = ref<Error | undefined>(undefined);
16
+
17
+ constructor(messages?: UI_MESSAGE[]) {
18
+ this.messagesRef = ref(messages ?? []) as Ref<UI_MESSAGE[]>;
19
+ }
20
+
21
+ get messages(): UI_MESSAGE[] {
22
+ return this.messagesRef.value;
23
+ }
24
+
25
+ set messages(messages: UI_MESSAGE[]) {
26
+ this.messagesRef.value = messages;
27
+ }
28
+
29
+ get status(): ChatStatus {
30
+ return this.statusRef.value;
31
+ }
32
+
33
+ set status(status: ChatStatus) {
34
+ this.statusRef.value = status;
35
+ }
36
+
37
+ get error(): Error | undefined {
38
+ return this.errorRef.value;
39
+ }
40
+
41
+ set error(error: Error | undefined) {
42
+ this.errorRef.value = error;
43
+ }
44
+
45
+ pushMessage = (message: UI_MESSAGE) => {
46
+ this.messagesRef.value = [...this.messagesRef.value, message];
47
+ };
48
+
49
+ popMessage = () => {
50
+ this.messagesRef.value = this.messagesRef.value.slice(0, -1);
51
+ };
52
+
53
+ replaceMessage = (index: number, message: UI_MESSAGE) => {
54
+ // message is cloned here because vue's deep reactivity shows unexpected behavior, particularly when updating tool invocation parts
55
+ this.messagesRef.value[index] = { ...message };
56
+ };
57
+
58
+ snapshot = <T>(value: T): T => value;
59
+ }
60
+
61
+ export class Chat<
62
+ UI_MESSAGE extends UIMessage,
63
+ > extends AbstractChat<UI_MESSAGE> {
64
+ constructor({ messages, ...init }: BaseChatInit<UI_MESSAGE>) {
65
+ super({
66
+ ...init,
67
+ state: new VueChatState(messages),
68
+ });
69
+ }
70
+ }