@tanstack/ai-svelte 0.7.2 → 0.8.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
@@ -52,7 +57,28 @@ export function createChat(options) {
52
57
  initialMessages: options.initialMessages,
53
58
  body: options.body,
54
59
  onResponse: options.onResponse,
55
- onChunk: options.onChunk,
60
+ onChunk: (chunk) => {
61
+ if (options.outputSchema !== undefined) {
62
+ if (chunk.type === 'RUN_STARTED') {
63
+ rawJson = '';
64
+ partial = {};
65
+ final = null;
66
+ }
67
+ else if (chunk.type === 'TEXT_MESSAGE_CONTENT' && chunk.delta) {
68
+ rawJson += chunk.delta;
69
+ const progressive = parsePartialJSON(rawJson);
70
+ if (progressive && typeof progressive === 'object') {
71
+ partial = progressive;
72
+ }
73
+ }
74
+ else if (chunk.type === 'CUSTOM' &&
75
+ chunk.name === 'structured-output.complete') {
76
+ const value = chunk.value;
77
+ final = value.object;
78
+ }
79
+ }
80
+ options.onChunk?.(chunk);
81
+ },
56
82
  onFinish: (message) => {
57
83
  options.onFinish?.(message);
58
84
  },
@@ -143,6 +169,12 @@ export function createChat(options) {
143
169
  get sessionGenerating() {
144
170
  return sessionGenerating;
145
171
  },
172
+ get partial() {
173
+ return partial;
174
+ },
175
+ get final() {
176
+ return final;
177
+ },
146
178
  sendMessage,
147
179
  append,
148
180
  reload,
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
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/ai-svelte",
3
- "version": "0.7.2",
3
+ "version": "0.8.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.9.2"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "svelte": "^5.0.0",
40
- "@tanstack/ai": "^0.16.0"
40
+ "@tanstack/ai": "^0.17.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.17.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
@@ -66,7 +86,28 @@ export function createChat<TTools extends ReadonlyArray<AnyClientTool> = any>(
66
86
  initialMessages: options.initialMessages,
67
87
  body: options.body,
68
88
  onResponse: options.onResponse,
69
- onChunk: options.onChunk,
89
+ onChunk: (chunk: StreamChunk) => {
90
+ if (options.outputSchema !== undefined) {
91
+ if (chunk.type === 'RUN_STARTED') {
92
+ rawJson = ''
93
+ partial = {} as Partial
94
+ final = null
95
+ } else if (chunk.type === 'TEXT_MESSAGE_CONTENT' && chunk.delta) {
96
+ rawJson += chunk.delta
97
+ const progressive = parsePartialJSON(rawJson)
98
+ if (progressive && typeof progressive === 'object') {
99
+ partial = progressive as Partial
100
+ }
101
+ } else if (
102
+ chunk.type === 'CUSTOM' &&
103
+ chunk.name === 'structured-output.complete'
104
+ ) {
105
+ const value = chunk.value as { object: unknown }
106
+ final = value.object as Final
107
+ }
108
+ }
109
+ options.onChunk?.(chunk)
110
+ },
70
111
  onFinish: (message) => {
71
112
  options.onFinish?.(message)
72
113
  },
@@ -178,6 +219,12 @@ export function createChat<TTools extends ReadonlyArray<AnyClientTool> = any>(
178
219
  get sessionGenerating() {
179
220
  return sessionGenerating
180
221
  },
222
+ get partial() {
223
+ return partial
224
+ },
225
+ get final() {
226
+ return final
227
+ },
181
228
  sendMessage,
182
229
  append,
183
230
  reload,
@@ -187,5 +234,5 @@ export function createChat<TTools extends ReadonlyArray<AnyClientTool> = any>(
187
234
  addToolResult,
188
235
  addToolApprovalResponse,
189
236
  updateBody,
190
- }
237
+ } as unknown as CreateChatReturn<TTools, TSchema>
191
238
  }
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
  /**