@tanstack/ai-svelte 0.9.0 → 0.10.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/dist/create-chat.svelte.js +35 -24
- package/dist/types.d.ts +12 -9
- package/package.json +5 -5
- package/src/create-chat.svelte.ts +47 -27
- package/src/types.ts +15 -8
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { ChatClient } from '@tanstack/ai-client';
|
|
2
|
-
import { parsePartialJSON } from '@tanstack/ai';
|
|
3
2
|
/**
|
|
4
3
|
* Creates a reactive chat instance for Svelte 5.
|
|
5
4
|
*
|
|
@@ -42,10 +41,6 @@ export function createChat(options) {
|
|
|
42
41
|
let isSubscribed = $state(false);
|
|
43
42
|
let connectionStatus = $state('disconnected');
|
|
44
43
|
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 = '';
|
|
49
44
|
// Create ChatClient instance.
|
|
50
45
|
// Note: Svelte's createChat runs once per instance and `options` is captured
|
|
51
46
|
// by reference. Callbacks are therefore frozen to whatever the caller passed
|
|
@@ -59,25 +54,6 @@ export function createChat(options) {
|
|
|
59
54
|
forwardedProps: options.forwardedProps,
|
|
60
55
|
onResponse: options.onResponse,
|
|
61
56
|
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
57
|
options.onChunk?.(chunk);
|
|
82
58
|
},
|
|
83
59
|
onFinish: (message) => {
|
|
@@ -153,6 +129,41 @@ export function createChat(options) {
|
|
|
153
129
|
const updateForwardedProps = (newForwardedProps) => {
|
|
154
130
|
client.updateOptions({ forwardedProps: newForwardedProps });
|
|
155
131
|
};
|
|
132
|
+
// The "active" structured-output part is the one on the assistant message
|
|
133
|
+
// after the latest user message. When no user message exists yet (e.g.
|
|
134
|
+
// `initialMessages` carries only a stale assistant turn), we return null
|
|
135
|
+
// rather than scanning history — otherwise a `final` from a previous
|
|
136
|
+
// session would leak in on first render. Uses `$derived.by` so the
|
|
137
|
+
// multi-line scan re-runs whenever `messages` changes.
|
|
138
|
+
const activeStructuredPart = $derived.by(() => {
|
|
139
|
+
let lastUserIndex = -1;
|
|
140
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
141
|
+
if (messages[i].role === 'user') {
|
|
142
|
+
lastUserIndex = i;
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (lastUserIndex === -1)
|
|
147
|
+
return null;
|
|
148
|
+
for (let i = messages.length - 1; i > lastUserIndex; i--) {
|
|
149
|
+
const m = messages[i];
|
|
150
|
+
if (m.role !== 'assistant')
|
|
151
|
+
continue;
|
|
152
|
+
const part = m.parts.find((p) => p.type === 'structured-output');
|
|
153
|
+
if (part)
|
|
154
|
+
return part;
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
});
|
|
158
|
+
const partial = $derived.by(() => {
|
|
159
|
+
if (!activeStructuredPart)
|
|
160
|
+
return {};
|
|
161
|
+
const v = activeStructuredPart.partial ?? activeStructuredPart.data;
|
|
162
|
+
return (v ?? {});
|
|
163
|
+
});
|
|
164
|
+
const final = $derived(activeStructuredPart && activeStructuredPart.status === 'complete'
|
|
165
|
+
? activeStructuredPart.data
|
|
166
|
+
: null);
|
|
156
167
|
// Return the chat interface with reactive getters
|
|
157
168
|
// Using getters allows Svelte to track reactivity without needing $ prefix
|
|
158
169
|
return {
|
package/dist/types.d.ts
CHANGED
|
@@ -43,24 +43,27 @@ export type CreateChatOptions<TTools extends ReadonlyArray<AnyClientTool> = any,
|
|
|
43
43
|
* typed `partial` / `final` reactive getters; otherwise the return is
|
|
44
44
|
* unchanged.
|
|
45
45
|
*/
|
|
46
|
-
export type CreateChatReturn<TTools extends ReadonlyArray<AnyClientTool> = any, TSchema extends SchemaInput | undefined = undefined> = BaseCreateChatReturn<TTools> & (TSchema extends SchemaInput ? {
|
|
46
|
+
export type CreateChatReturn<TTools extends ReadonlyArray<AnyClientTool> = any, TSchema extends SchemaInput | undefined = undefined> = BaseCreateChatReturn<TTools, TSchema extends SchemaInput ? InferSchemaType<TSchema> : unknown> & (TSchema extends SchemaInput ? {
|
|
47
47
|
/**
|
|
48
48
|
* Live progressively-parsed structured output (reactive getter).
|
|
49
|
-
*
|
|
49
|
+
* Derived from the latest assistant message's structured-output part.
|
|
50
50
|
*/
|
|
51
51
|
readonly partial: DeepPartial<InferSchemaType<TSchema>>;
|
|
52
52
|
/**
|
|
53
53
|
* Final, schema-validated structured output (reactive getter). `null`
|
|
54
|
-
* until the
|
|
55
|
-
*
|
|
54
|
+
* until the latest assistant turn's structured-output part transitions
|
|
55
|
+
* to `complete`.
|
|
56
56
|
*/
|
|
57
57
|
readonly final: InferSchemaType<TSchema> | null;
|
|
58
58
|
} : Record<never, never>);
|
|
59
|
-
interface BaseCreateChatReturn<TTools extends ReadonlyArray<AnyClientTool> = any> {
|
|
59
|
+
interface BaseCreateChatReturn<TTools extends ReadonlyArray<AnyClientTool> = any, TData = unknown> {
|
|
60
60
|
/**
|
|
61
|
-
* Current messages in the conversation (reactive getter)
|
|
61
|
+
* Current messages in the conversation (reactive getter). When
|
|
62
|
+
* `outputSchema` is supplied, `messages[i].parts.find(p => p.type ===
|
|
63
|
+
* 'structured-output')` is typed by the schema — `data: T`,
|
|
64
|
+
* `partial: DeepPartial<T>`.
|
|
62
65
|
*/
|
|
63
|
-
readonly messages: Array<UIMessage<TTools>>;
|
|
66
|
+
readonly messages: Array<UIMessage<TTools, TData>>;
|
|
64
67
|
/**
|
|
65
68
|
* Send a message and get a response.
|
|
66
69
|
* Can be a simple string or multimodal content with images, audio, etc.
|
|
@@ -69,7 +72,7 @@ interface BaseCreateChatReturn<TTools extends ReadonlyArray<AnyClientTool> = any
|
|
|
69
72
|
/**
|
|
70
73
|
* Append a message to the conversation
|
|
71
74
|
*/
|
|
72
|
-
append: (message: ModelMessage | UIMessage<TTools>) => Promise<void>;
|
|
75
|
+
append: (message: ModelMessage | UIMessage<TTools, TData>) => Promise<void>;
|
|
73
76
|
/**
|
|
74
77
|
* Add the result of a client-side tool execution
|
|
75
78
|
*/
|
|
@@ -106,7 +109,7 @@ interface BaseCreateChatReturn<TTools extends ReadonlyArray<AnyClientTool> = any
|
|
|
106
109
|
/**
|
|
107
110
|
* Set messages manually
|
|
108
111
|
*/
|
|
109
|
-
setMessages: (messages: Array<UIMessage<TTools>>) => void;
|
|
112
|
+
setMessages: (messages: Array<UIMessage<TTools, TData>>) => void;
|
|
110
113
|
/**
|
|
111
114
|
* Clear all messages
|
|
112
115
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/ai-svelte",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Svelte bindings for TanStack AI",
|
|
5
5
|
"author": "",
|
|
6
6
|
"license": "MIT",
|
|
@@ -33,11 +33,11 @@
|
|
|
33
33
|
"streaming"
|
|
34
34
|
],
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@tanstack/ai-client": "0.
|
|
36
|
+
"@tanstack/ai-client": "0.11.0"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"svelte": "^5.0.0",
|
|
40
|
-
"@tanstack/ai": "^0.
|
|
40
|
+
"@tanstack/ai": "^0.19.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@standard-schema/spec": "^1.1.0",
|
|
@@ -49,8 +49,8 @@
|
|
|
49
49
|
"svelte": "^5.20.0",
|
|
50
50
|
"svelte-check": "^4.2.0",
|
|
51
51
|
"typescript": "5.9.3",
|
|
52
|
-
"vite": "^7.
|
|
53
|
-
"@tanstack/ai": "0.
|
|
52
|
+
"vite": "^7.3.3",
|
|
53
|
+
"@tanstack/ai": "0.19.0"
|
|
54
54
|
},
|
|
55
55
|
"scripts": {
|
|
56
56
|
"clean": "rm -rf ./dist",
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { ChatClient } from '@tanstack/ai-client'
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import type {
|
|
3
|
+
ChatClientState,
|
|
4
|
+
ConnectionStatus,
|
|
5
|
+
StructuredOutputPart,
|
|
6
|
+
} from '@tanstack/ai-client'
|
|
4
7
|
import type {
|
|
5
8
|
AnyClientTool,
|
|
6
9
|
InferSchemaType,
|
|
@@ -66,14 +69,12 @@ export function createChat<
|
|
|
66
69
|
let connectionStatus = $state<ConnectionStatus>('disconnected')
|
|
67
70
|
let sessionGenerating = $state(false)
|
|
68
71
|
|
|
69
|
-
// Structured-output
|
|
70
|
-
//
|
|
72
|
+
// Structured-output `partial` / `final` are derived from `messages` —
|
|
73
|
+
// specifically from the structured-output part on the latest assistant
|
|
74
|
+
// message (the one after the most recent user message). Per-turn parts
|
|
75
|
+
// keep history coherent without a separate reset signal.
|
|
71
76
|
type Partial = DeepPartial<InferSchemaType<NonNullable<TSchema>>>
|
|
72
77
|
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
78
|
|
|
78
79
|
// Create ChatClient instance.
|
|
79
80
|
// Note: Svelte's createChat runs once per instance and `options` is captured
|
|
@@ -88,25 +89,6 @@ export function createChat<
|
|
|
88
89
|
forwardedProps: options.forwardedProps,
|
|
89
90
|
onResponse: options.onResponse,
|
|
90
91
|
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
92
|
options.onChunk?.(chunk)
|
|
111
93
|
},
|
|
112
94
|
onFinish: (message) => {
|
|
@@ -204,6 +186,44 @@ export function createChat<
|
|
|
204
186
|
client.updateOptions({ forwardedProps: newForwardedProps })
|
|
205
187
|
}
|
|
206
188
|
|
|
189
|
+
// The "active" structured-output part is the one on the assistant message
|
|
190
|
+
// after the latest user message. When no user message exists yet (e.g.
|
|
191
|
+
// `initialMessages` carries only a stale assistant turn), we return null
|
|
192
|
+
// rather than scanning history — otherwise a `final` from a previous
|
|
193
|
+
// session would leak in on first render. Uses `$derived.by` so the
|
|
194
|
+
// multi-line scan re-runs whenever `messages` changes.
|
|
195
|
+
const activeStructuredPart: StructuredOutputPart | null = $derived.by(() => {
|
|
196
|
+
let lastUserIndex = -1
|
|
197
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
198
|
+
if (messages[i]!.role === 'user') {
|
|
199
|
+
lastUserIndex = i
|
|
200
|
+
break
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (lastUserIndex === -1) return null
|
|
204
|
+
for (let i = messages.length - 1; i > lastUserIndex; i--) {
|
|
205
|
+
const m = messages[i]!
|
|
206
|
+
if (m.role !== 'assistant') continue
|
|
207
|
+
const part = m.parts.find(
|
|
208
|
+
(p): p is StructuredOutputPart => p.type === 'structured-output',
|
|
209
|
+
)
|
|
210
|
+
if (part) return part
|
|
211
|
+
}
|
|
212
|
+
return null
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
const partial: Partial = $derived.by(() => {
|
|
216
|
+
if (!activeStructuredPart) return {} as Partial
|
|
217
|
+
const v = activeStructuredPart.partial ?? activeStructuredPart.data
|
|
218
|
+
return (v ?? {}) as Partial
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
const final: Final | null = $derived(
|
|
222
|
+
activeStructuredPart && activeStructuredPart.status === 'complete'
|
|
223
|
+
? (activeStructuredPart.data as Final)
|
|
224
|
+
: null,
|
|
225
|
+
)
|
|
226
|
+
|
|
207
227
|
// Return the chat interface with reactive getters
|
|
208
228
|
// Using getters allows Svelte to track reactivity without needing $ prefix
|
|
209
229
|
return {
|
package/src/types.ts
CHANGED
|
@@ -78,18 +78,21 @@ export type CreateChatOptions<
|
|
|
78
78
|
export type CreateChatReturn<
|
|
79
79
|
TTools extends ReadonlyArray<AnyClientTool> = any,
|
|
80
80
|
TSchema extends SchemaInput | undefined = undefined,
|
|
81
|
-
> = BaseCreateChatReturn<
|
|
81
|
+
> = BaseCreateChatReturn<
|
|
82
|
+
TTools,
|
|
83
|
+
TSchema extends SchemaInput ? InferSchemaType<TSchema> : unknown
|
|
84
|
+
> &
|
|
82
85
|
(TSchema extends SchemaInput
|
|
83
86
|
? {
|
|
84
87
|
/**
|
|
85
88
|
* Live progressively-parsed structured output (reactive getter).
|
|
86
|
-
*
|
|
89
|
+
* Derived from the latest assistant message's structured-output part.
|
|
87
90
|
*/
|
|
88
91
|
readonly partial: DeepPartial<InferSchemaType<TSchema>>
|
|
89
92
|
/**
|
|
90
93
|
* Final, schema-validated structured output (reactive getter). `null`
|
|
91
|
-
* until the
|
|
92
|
-
*
|
|
94
|
+
* until the latest assistant turn's structured-output part transitions
|
|
95
|
+
* to `complete`.
|
|
93
96
|
*/
|
|
94
97
|
readonly final: InferSchemaType<TSchema> | null
|
|
95
98
|
}
|
|
@@ -97,11 +100,15 @@ export type CreateChatReturn<
|
|
|
97
100
|
|
|
98
101
|
interface BaseCreateChatReturn<
|
|
99
102
|
TTools extends ReadonlyArray<AnyClientTool> = any,
|
|
103
|
+
TData = unknown,
|
|
100
104
|
> {
|
|
101
105
|
/**
|
|
102
|
-
* Current messages in the conversation (reactive getter)
|
|
106
|
+
* Current messages in the conversation (reactive getter). When
|
|
107
|
+
* `outputSchema` is supplied, `messages[i].parts.find(p => p.type ===
|
|
108
|
+
* 'structured-output')` is typed by the schema — `data: T`,
|
|
109
|
+
* `partial: DeepPartial<T>`.
|
|
103
110
|
*/
|
|
104
|
-
readonly messages: Array<UIMessage<TTools>>
|
|
111
|
+
readonly messages: Array<UIMessage<TTools, TData>>
|
|
105
112
|
|
|
106
113
|
/**
|
|
107
114
|
* Send a message and get a response.
|
|
@@ -112,7 +119,7 @@ interface BaseCreateChatReturn<
|
|
|
112
119
|
/**
|
|
113
120
|
* Append a message to the conversation
|
|
114
121
|
*/
|
|
115
|
-
append: (message: ModelMessage | UIMessage<TTools>) => Promise<void>
|
|
122
|
+
append: (message: ModelMessage | UIMessage<TTools, TData>) => Promise<void>
|
|
116
123
|
|
|
117
124
|
/**
|
|
118
125
|
* Add the result of a client-side tool execution
|
|
@@ -156,7 +163,7 @@ interface BaseCreateChatReturn<
|
|
|
156
163
|
/**
|
|
157
164
|
* Set messages manually
|
|
158
165
|
*/
|
|
159
|
-
setMessages: (messages: Array<UIMessage<TTools>>) => void
|
|
166
|
+
setMessages: (messages: Array<UIMessage<TTools, TData>>) => void
|
|
160
167
|
|
|
161
168
|
/**
|
|
162
169
|
* Clear all messages
|