@tanstack/ai-svelte 0.7.2 → 0.9.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.
@@ -1,4 +1,4 @@
1
- import type { AnyClientTool } from '@tanstack/ai';
1
+ import type { AnyClientTool, SchemaInput } from '@tanstack/ai';
2
2
  import type { CreateChatOptions, CreateChatReturn } from './types';
3
3
  /**
4
4
  * Creates a reactive chat instance for Svelte 5.
@@ -30,4 +30,4 @@ import type { CreateChatOptions, CreateChatReturn } from './types';
30
30
  * </div>
31
31
  * ```
32
32
  */
33
- export declare function createChat<TTools extends ReadonlyArray<AnyClientTool> = any>(options: CreateChatOptions<TTools>): CreateChatReturn<TTools>;
33
+ export declare function createChat<TTools extends ReadonlyArray<AnyClientTool> = any, TSchema extends SchemaInput | undefined = undefined>(options: CreateChatOptions<TTools, TSchema>): CreateChatReturn<TTools, TSchema>;
@@ -1,4 +1,5 @@
1
1
  import { ChatClient } from '@tanstack/ai-client';
2
+ import { parsePartialJSON } from '@tanstack/ai';
2
3
  /**
3
4
  * Creates a reactive chat instance for Svelte 5.
4
5
  *
@@ -41,6 +42,10 @@ export function createChat(options) {
41
42
  let isSubscribed = $state(false);
42
43
  let connectionStatus = $state('disconnected');
43
44
  let sessionGenerating = $state(false);
45
+ let partial = $state({});
46
+ let final = $state(null);
47
+ // Raw JSON accumulator — not reactive, just a local buffer.
48
+ let rawJson = '';
44
49
  // Create ChatClient instance.
45
50
  // Note: Svelte's createChat runs once per instance and `options` is captured
46
51
  // by reference. Callbacks are therefore frozen to whatever the caller passed
@@ -51,8 +56,30 @@ export function createChat(options) {
51
56
  id: clientId,
52
57
  initialMessages: options.initialMessages,
53
58
  body: options.body,
59
+ forwardedProps: options.forwardedProps,
54
60
  onResponse: options.onResponse,
55
- onChunk: options.onChunk,
61
+ onChunk: (chunk) => {
62
+ if (options.outputSchema !== undefined) {
63
+ if (chunk.type === 'RUN_STARTED') {
64
+ rawJson = '';
65
+ partial = {};
66
+ final = null;
67
+ }
68
+ else if (chunk.type === 'TEXT_MESSAGE_CONTENT' && chunk.delta) {
69
+ rawJson += chunk.delta;
70
+ const progressive = parsePartialJSON(rawJson);
71
+ if (progressive && typeof progressive === 'object') {
72
+ partial = progressive;
73
+ }
74
+ }
75
+ else if (chunk.type === 'CUSTOM' &&
76
+ chunk.name === 'structured-output.complete') {
77
+ const value = chunk.value;
78
+ final = value.object;
79
+ }
80
+ }
81
+ options.onChunk?.(chunk);
82
+ },
56
83
  onFinish: (message) => {
57
84
  options.onFinish?.(message);
58
85
  },
@@ -116,9 +143,16 @@ export function createChat(options) {
116
143
  const addToolApprovalResponse = async (response) => {
117
144
  await client.addToolApprovalResponse(response);
118
145
  };
146
+ /**
147
+ * @deprecated Use `updateForwardedProps` instead.
148
+ * Both populate the same wire payload.
149
+ */
119
150
  const updateBody = (newBody) => {
120
151
  client.updateOptions({ body: newBody });
121
152
  };
153
+ const updateForwardedProps = (newForwardedProps) => {
154
+ client.updateOptions({ forwardedProps: newForwardedProps });
155
+ };
122
156
  // Return the chat interface with reactive getters
123
157
  // Using getters allows Svelte to track reactivity without needing $ prefix
124
158
  return {
@@ -143,6 +177,12 @@ export function createChat(options) {
143
177
  get sessionGenerating() {
144
178
  return sessionGenerating;
145
179
  },
180
+ get partial() {
181
+ return partial;
182
+ },
183
+ get final() {
184
+ return final;
185
+ },
146
186
  sendMessage,
147
187
  append,
148
188
  reload,
@@ -152,5 +192,6 @@ export function createChat(options) {
152
192
  addToolResult,
153
193
  addToolApprovalResponse,
154
194
  updateBody,
195
+ updateForwardedProps,
155
196
  };
156
197
  }
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { createChat } from './create-chat.svelte';
2
- export type { CreateChatOptions, CreateChatReturn, UIMessage, ChatRequestBody, } from './types';
2
+ export type { CreateChatOptions, CreateChatReturn, DeepPartial, UIMessage, ChatRequestBody, } from './types';
3
3
  export { createGeneration } from './create-generation.svelte';
4
4
  export type { CreateGenerationOptions, CreateGenerationReturn, } from './create-generation.svelte';
5
5
  export { createGenerateImage } from './create-generate-image.svelte';
package/dist/types.d.ts CHANGED
@@ -1,6 +1,14 @@
1
- import type { AnyClientTool, ModelMessage } from '@tanstack/ai';
1
+ import type { AnyClientTool, InferSchemaType, ModelMessage, SchemaInput } from '@tanstack/ai';
2
2
  import type { ChatClientOptions, ChatClientState, ChatRequestBody, ConnectionStatus, MultimodalContent, UIMessage } from '@tanstack/ai-client';
3
3
  export type { ChatRequestBody, MultimodalContent, UIMessage };
4
+ /**
5
+ * Recursive partial — every property and every nested array element is
6
+ * optional. Used to type the in-flight `partial` getter while a structured-
7
+ * output stream is still arriving.
8
+ */
9
+ export type DeepPartial<T> = T extends ReadonlyArray<infer U> ? Array<DeepPartial<U>> : T extends object ? {
10
+ [K in keyof T]?: DeepPartial<T[K]>;
11
+ } : T;
4
12
  /**
5
13
  * Options for the createChat function.
6
14
  *
@@ -14,13 +22,41 @@ export type { ChatRequestBody, MultimodalContent, UIMessage };
14
22
  * All other callbacks (onResponse, onChunk, onFinish, onError) are
15
23
  * passed through to the underlying ChatClient and can be used for side effects.
16
24
  *
25
+ * When `outputSchema` is supplied, the return adds typed `partial` and `final`
26
+ * reactive getters. The schema is used purely for type inference; server-side
27
+ * validation still runs against the schema passed to `chat({ outputSchema })`
28
+ * on the server route.
29
+ *
17
30
  * Note: Connection and body changes will recreate the ChatClient instance.
18
31
  * To update these options, remount the component or use a key prop.
19
32
  */
20
- export type CreateChatOptions<TTools extends ReadonlyArray<AnyClientTool> = any> = Omit<ChatClientOptions<TTools>, 'onMessagesChange' | 'onLoadingChange' | 'onErrorChange' | 'onStatusChange' | 'onSubscriptionChange' | 'onConnectionStatusChange' | 'onSessionGeneratingChange'> & {
33
+ export type CreateChatOptions<TTools extends ReadonlyArray<AnyClientTool> = any, TSchema extends SchemaInput | undefined = undefined> = Omit<ChatClientOptions<TTools>, 'onMessagesChange' | 'onLoadingChange' | 'onErrorChange' | 'onStatusChange' | 'onSubscriptionChange' | 'onConnectionStatusChange' | 'onSessionGeneratingChange'> & {
21
34
  live?: boolean;
35
+ /**
36
+ * Standard-schema-compatible schema (Zod, Valibot, ArkType, or plain JSON
37
+ * Schema). Used to infer the shape of `partial` and `final`.
38
+ */
39
+ outputSchema?: TSchema;
22
40
  };
23
- export interface CreateChatReturn<TTools extends ReadonlyArray<AnyClientTool> = any> {
41
+ /**
42
+ * Discriminated return shape: when `outputSchema` is supplied, the return adds
43
+ * typed `partial` / `final` reactive getters; otherwise the return is
44
+ * unchanged.
45
+ */
46
+ export type CreateChatReturn<TTools extends ReadonlyArray<AnyClientTool> = any, TSchema extends SchemaInput | undefined = undefined> = BaseCreateChatReturn<TTools> & (TSchema extends SchemaInput ? {
47
+ /**
48
+ * Live progressively-parsed structured output (reactive getter).
49
+ * Resets on every new run.
50
+ */
51
+ readonly partial: DeepPartial<InferSchemaType<TSchema>>;
52
+ /**
53
+ * Final, schema-validated structured output (reactive getter). `null`
54
+ * until the terminal `structured-output.complete` event arrives.
55
+ * Resets on every new run.
56
+ */
57
+ readonly final: InferSchemaType<TSchema> | null;
58
+ } : Record<never, never>);
59
+ interface BaseCreateChatReturn<TTools extends ReadonlyArray<AnyClientTool> = any> {
24
60
  /**
25
61
  * Current messages in the conversation (reactive getter)
26
62
  */
@@ -95,7 +131,13 @@ export interface CreateChatReturn<TTools extends ReadonlyArray<AnyClientTool> =
95
131
  */
96
132
  readonly sessionGenerating: boolean;
97
133
  /**
98
- * Update the body sent with requests (e.g., for changing model selection)
134
+ * @deprecated Use `updateForwardedProps` instead. Both populate the
135
+ * same wire payload; `updateBody` is retained for backward compatibility.
99
136
  */
100
137
  updateBody: (body: Record<string, any>) => void;
138
+ /**
139
+ * Update the AG-UI `forwardedProps` sent with requests (e.g., for
140
+ * changing model selection or other client-driven options).
141
+ */
142
+ updateForwardedProps: (forwardedProps: Record<string, any>) => void;
101
143
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/ai-svelte",
3
- "version": "0.7.2",
3
+ "version": "0.9.0",
4
4
  "description": "Svelte bindings for TanStack AI",
5
5
  "author": "",
6
6
  "license": "MIT",
@@ -33,13 +33,14 @@
33
33
  "streaming"
34
34
  ],
35
35
  "dependencies": {
36
- "@tanstack/ai-client": "0.9.1"
36
+ "@tanstack/ai-client": "0.10.0"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "svelte": "^5.0.0",
40
- "@tanstack/ai": "^0.16.0"
40
+ "@tanstack/ai": "^0.18.0"
41
41
  },
42
42
  "devDependencies": {
43
+ "@standard-schema/spec": "^1.1.0",
43
44
  "@sveltejs/package": "^2.3.10",
44
45
  "@sveltejs/vite-plugin-svelte": "^5.1.1",
45
46
  "@types/node": "^24.10.1",
@@ -49,7 +50,7 @@
49
50
  "svelte-check": "^4.2.0",
50
51
  "typescript": "5.9.3",
51
52
  "vite": "^7.2.7",
52
- "@tanstack/ai": "0.16.0"
53
+ "@tanstack/ai": "0.18.0"
53
54
  },
54
55
  "scripts": {
55
56
  "clean": "rm -rf ./dist",
@@ -1,9 +1,17 @@
1
1
  import { ChatClient } from '@tanstack/ai-client'
2
+ import { parsePartialJSON } from '@tanstack/ai'
2
3
  import type { ChatClientState, ConnectionStatus } from '@tanstack/ai-client'
3
- import type { AnyClientTool, ModelMessage } from '@tanstack/ai'
4
+ import type {
5
+ AnyClientTool,
6
+ InferSchemaType,
7
+ ModelMessage,
8
+ SchemaInput,
9
+ StreamChunk,
10
+ } from '@tanstack/ai'
4
11
  import type {
5
12
  CreateChatOptions,
6
13
  CreateChatReturn,
14
+ DeepPartial,
7
15
  MultimodalContent,
8
16
  UIMessage,
9
17
  } from './types'
@@ -38,9 +46,12 @@ import type {
38
46
  * </div>
39
47
  * ```
40
48
  */
41
- export function createChat<TTools extends ReadonlyArray<AnyClientTool> = any>(
42
- options: CreateChatOptions<TTools>,
43
- ): CreateChatReturn<TTools> {
49
+ export function createChat<
50
+ TTools extends ReadonlyArray<AnyClientTool> = any,
51
+ TSchema extends SchemaInput | undefined = undefined,
52
+ >(
53
+ options: CreateChatOptions<TTools, TSchema>,
54
+ ): CreateChatReturn<TTools, TSchema> {
44
55
  // Generate a unique ID for this chat instance
45
56
  const clientId =
46
57
  options.id ||
@@ -55,6 +66,15 @@ export function createChat<TTools extends ReadonlyArray<AnyClientTool> = any>(
55
66
  let connectionStatus = $state<ConnectionStatus>('disconnected')
56
67
  let sessionGenerating = $state(false)
57
68
 
69
+ // Structured-output state. Runtime always tracks them — the conditional
70
+ // return type hides them when no `outputSchema` is supplied.
71
+ type Partial = DeepPartial<InferSchemaType<NonNullable<TSchema>>>
72
+ type Final = InferSchemaType<NonNullable<TSchema>>
73
+ let partial = $state<Partial>({} as Partial)
74
+ let final = $state<Final | null>(null)
75
+ // Raw JSON accumulator — not reactive, just a local buffer.
76
+ let rawJson = ''
77
+
58
78
  // Create ChatClient instance.
59
79
  // Note: Svelte's createChat runs once per instance and `options` is captured
60
80
  // by reference. Callbacks are therefore frozen to whatever the caller passed
@@ -65,8 +85,30 @@ export function createChat<TTools extends ReadonlyArray<AnyClientTool> = any>(
65
85
  id: clientId,
66
86
  initialMessages: options.initialMessages,
67
87
  body: options.body,
88
+ forwardedProps: options.forwardedProps,
68
89
  onResponse: options.onResponse,
69
- onChunk: options.onChunk,
90
+ onChunk: (chunk: StreamChunk) => {
91
+ if (options.outputSchema !== undefined) {
92
+ if (chunk.type === 'RUN_STARTED') {
93
+ rawJson = ''
94
+ partial = {} as Partial
95
+ final = null
96
+ } else if (chunk.type === 'TEXT_MESSAGE_CONTENT' && chunk.delta) {
97
+ rawJson += chunk.delta
98
+ const progressive = parsePartialJSON(rawJson)
99
+ if (progressive && typeof progressive === 'object') {
100
+ partial = progressive as Partial
101
+ }
102
+ } else if (
103
+ chunk.type === 'CUSTOM' &&
104
+ chunk.name === 'structured-output.complete'
105
+ ) {
106
+ const value = chunk.value as { object: unknown }
107
+ final = value.object as Final
108
+ }
109
+ }
110
+ options.onChunk?.(chunk)
111
+ },
70
112
  onFinish: (message) => {
71
113
  options.onFinish?.(message)
72
114
  },
@@ -150,10 +192,18 @@ export function createChat<TTools extends ReadonlyArray<AnyClientTool> = any>(
150
192
  await client.addToolApprovalResponse(response)
151
193
  }
152
194
 
195
+ /**
196
+ * @deprecated Use `updateForwardedProps` instead.
197
+ * Both populate the same wire payload.
198
+ */
153
199
  const updateBody = (newBody: Record<string, any>) => {
154
200
  client.updateOptions({ body: newBody })
155
201
  }
156
202
 
203
+ const updateForwardedProps = (newForwardedProps: Record<string, any>) => {
204
+ client.updateOptions({ forwardedProps: newForwardedProps })
205
+ }
206
+
157
207
  // Return the chat interface with reactive getters
158
208
  // Using getters allows Svelte to track reactivity without needing $ prefix
159
209
  return {
@@ -178,6 +228,12 @@ export function createChat<TTools extends ReadonlyArray<AnyClientTool> = any>(
178
228
  get sessionGenerating() {
179
229
  return sessionGenerating
180
230
  },
231
+ get partial() {
232
+ return partial
233
+ },
234
+ get final() {
235
+ return final
236
+ },
181
237
  sendMessage,
182
238
  append,
183
239
  reload,
@@ -187,5 +243,6 @@ export function createChat<TTools extends ReadonlyArray<AnyClientTool> = any>(
187
243
  addToolResult,
188
244
  addToolApprovalResponse,
189
245
  updateBody,
190
- }
246
+ updateForwardedProps,
247
+ } as unknown as CreateChatReturn<TTools, TSchema>
191
248
  }
package/src/index.ts CHANGED
@@ -2,6 +2,7 @@ export { createChat } from './create-chat.svelte'
2
2
  export type {
3
3
  CreateChatOptions,
4
4
  CreateChatReturn,
5
+ DeepPartial,
5
6
  UIMessage,
6
7
  ChatRequestBody,
7
8
  } from './types'
package/src/types.ts CHANGED
@@ -1,4 +1,9 @@
1
- import type { AnyClientTool, ModelMessage } from '@tanstack/ai'
1
+ import type {
2
+ AnyClientTool,
3
+ InferSchemaType,
4
+ ModelMessage,
5
+ SchemaInput,
6
+ } from '@tanstack/ai'
2
7
  import type {
3
8
  ChatClientOptions,
4
9
  ChatClientState,
@@ -11,6 +16,18 @@ import type {
11
16
  // Re-export types from ai-client
12
17
  export type { ChatRequestBody, MultimodalContent, UIMessage }
13
18
 
19
+ /**
20
+ * Recursive partial — every property and every nested array element is
21
+ * optional. Used to type the in-flight `partial` getter while a structured-
22
+ * output stream is still arriving.
23
+ */
24
+ export type DeepPartial<T> =
25
+ T extends ReadonlyArray<infer U>
26
+ ? Array<DeepPartial<U>>
27
+ : T extends object
28
+ ? { [K in keyof T]?: DeepPartial<T[K]> }
29
+ : T
30
+
14
31
  /**
15
32
  * Options for the createChat function.
16
33
  *
@@ -24,11 +41,17 @@ export type { ChatRequestBody, MultimodalContent, UIMessage }
24
41
  * All other callbacks (onResponse, onChunk, onFinish, onError) are
25
42
  * passed through to the underlying ChatClient and can be used for side effects.
26
43
  *
44
+ * When `outputSchema` is supplied, the return adds typed `partial` and `final`
45
+ * reactive getters. The schema is used purely for type inference; server-side
46
+ * validation still runs against the schema passed to `chat({ outputSchema })`
47
+ * on the server route.
48
+ *
27
49
  * Note: Connection and body changes will recreate the ChatClient instance.
28
50
  * To update these options, remount the component or use a key prop.
29
51
  */
30
52
  export type CreateChatOptions<
31
53
  TTools extends ReadonlyArray<AnyClientTool> = any,
54
+ TSchema extends SchemaInput | undefined = undefined,
32
55
  > = Omit<
33
56
  ChatClientOptions<TTools>,
34
57
  | 'onMessagesChange'
@@ -40,9 +63,39 @@ export type CreateChatOptions<
40
63
  | 'onSessionGeneratingChange'
41
64
  > & {
42
65
  live?: boolean
66
+ /**
67
+ * Standard-schema-compatible schema (Zod, Valibot, ArkType, or plain JSON
68
+ * Schema). Used to infer the shape of `partial` and `final`.
69
+ */
70
+ outputSchema?: TSchema
43
71
  }
44
72
 
45
- export interface CreateChatReturn<
73
+ /**
74
+ * Discriminated return shape: when `outputSchema` is supplied, the return adds
75
+ * typed `partial` / `final` reactive getters; otherwise the return is
76
+ * unchanged.
77
+ */
78
+ export type CreateChatReturn<
79
+ TTools extends ReadonlyArray<AnyClientTool> = any,
80
+ TSchema extends SchemaInput | undefined = undefined,
81
+ > = BaseCreateChatReturn<TTools> &
82
+ (TSchema extends SchemaInput
83
+ ? {
84
+ /**
85
+ * Live progressively-parsed structured output (reactive getter).
86
+ * Resets on every new run.
87
+ */
88
+ readonly partial: DeepPartial<InferSchemaType<TSchema>>
89
+ /**
90
+ * Final, schema-validated structured output (reactive getter). `null`
91
+ * until the terminal `structured-output.complete` event arrives.
92
+ * Resets on every new run.
93
+ */
94
+ readonly final: InferSchemaType<TSchema> | null
95
+ }
96
+ : Record<never, never>)
97
+
98
+ interface BaseCreateChatReturn<
46
99
  TTools extends ReadonlyArray<AnyClientTool> = any,
47
100
  > {
48
101
  /**
@@ -130,9 +183,15 @@ export interface CreateChatReturn<
130
183
  */
131
184
  readonly sessionGenerating: boolean
132
185
  /**
133
- * Update the body sent with requests (e.g., for changing model selection)
186
+ * @deprecated Use `updateForwardedProps` instead. Both populate the
187
+ * same wire payload; `updateBody` is retained for backward compatibility.
134
188
  */
135
189
  updateBody: (body: Record<string, any>) => void
190
+ /**
191
+ * Update the AG-UI `forwardedProps` sent with requests (e.g., for
192
+ * changing model selection or other client-driven options).
193
+ */
194
+ updateForwardedProps: (forwardedProps: Record<string, any>) => void
136
195
  }
137
196
 
138
197
  // Note: createChatClientOptions and InferChatMessages are now in @tanstack/ai-client