@tanstack/ai-svelte 0.8.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 +44 -24
- package/dist/types.d.ts +19 -10
- package/package.json +5 -5
- package/src/create-chat.svelte.ts +57 -27
- package/src/types.ts +22 -9
|
@@ -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
|
|
@@ -56,27 +51,9 @@ export function createChat(options) {
|
|
|
56
51
|
id: clientId,
|
|
57
52
|
initialMessages: options.initialMessages,
|
|
58
53
|
body: options.body,
|
|
54
|
+
forwardedProps: options.forwardedProps,
|
|
59
55
|
onResponse: options.onResponse,
|
|
60
56
|
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
57
|
options.onChunk?.(chunk);
|
|
81
58
|
},
|
|
82
59
|
onFinish: (message) => {
|
|
@@ -142,9 +119,51 @@ export function createChat(options) {
|
|
|
142
119
|
const addToolApprovalResponse = async (response) => {
|
|
143
120
|
await client.addToolApprovalResponse(response);
|
|
144
121
|
};
|
|
122
|
+
/**
|
|
123
|
+
* @deprecated Use `updateForwardedProps` instead.
|
|
124
|
+
* Both populate the same wire payload.
|
|
125
|
+
*/
|
|
145
126
|
const updateBody = (newBody) => {
|
|
146
127
|
client.updateOptions({ body: newBody });
|
|
147
128
|
};
|
|
129
|
+
const updateForwardedProps = (newForwardedProps) => {
|
|
130
|
+
client.updateOptions({ forwardedProps: newForwardedProps });
|
|
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);
|
|
148
167
|
// Return the chat interface with reactive getters
|
|
149
168
|
// Using getters allows Svelte to track reactivity without needing $ prefix
|
|
150
169
|
return {
|
|
@@ -184,5 +203,6 @@ export function createChat(options) {
|
|
|
184
203
|
addToolResult,
|
|
185
204
|
addToolApprovalResponse,
|
|
186
205
|
updateBody,
|
|
206
|
+
updateForwardedProps,
|
|
187
207
|
};
|
|
188
208
|
}
|
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
|
*/
|
|
@@ -131,7 +134,13 @@ interface BaseCreateChatReturn<TTools extends ReadonlyArray<AnyClientTool> = any
|
|
|
131
134
|
*/
|
|
132
135
|
readonly sessionGenerating: boolean;
|
|
133
136
|
/**
|
|
134
|
-
*
|
|
137
|
+
* @deprecated Use `updateForwardedProps` instead. Both populate the
|
|
138
|
+
* same wire payload; `updateBody` is retained for backward compatibility.
|
|
135
139
|
*/
|
|
136
140
|
updateBody: (body: Record<string, any>) => void;
|
|
141
|
+
/**
|
|
142
|
+
* Update the AG-UI `forwardedProps` sent with requests (e.g., for
|
|
143
|
+
* changing model selection or other client-driven options).
|
|
144
|
+
*/
|
|
145
|
+
updateForwardedProps: (forwardedProps: Record<string, any>) => void;
|
|
137
146
|
}
|
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
|
|
@@ -85,27 +86,9 @@ export function createChat<
|
|
|
85
86
|
id: clientId,
|
|
86
87
|
initialMessages: options.initialMessages,
|
|
87
88
|
body: options.body,
|
|
89
|
+
forwardedProps: options.forwardedProps,
|
|
88
90
|
onResponse: options.onResponse,
|
|
89
91
|
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
92
|
options.onChunk?.(chunk)
|
|
110
93
|
},
|
|
111
94
|
onFinish: (message) => {
|
|
@@ -191,10 +174,56 @@ export function createChat<
|
|
|
191
174
|
await client.addToolApprovalResponse(response)
|
|
192
175
|
}
|
|
193
176
|
|
|
177
|
+
/**
|
|
178
|
+
* @deprecated Use `updateForwardedProps` instead.
|
|
179
|
+
* Both populate the same wire payload.
|
|
180
|
+
*/
|
|
194
181
|
const updateBody = (newBody: Record<string, any>) => {
|
|
195
182
|
client.updateOptions({ body: newBody })
|
|
196
183
|
}
|
|
197
184
|
|
|
185
|
+
const updateForwardedProps = (newForwardedProps: Record<string, any>) => {
|
|
186
|
+
client.updateOptions({ forwardedProps: newForwardedProps })
|
|
187
|
+
}
|
|
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
|
+
|
|
198
227
|
// Return the chat interface with reactive getters
|
|
199
228
|
// Using getters allows Svelte to track reactivity without needing $ prefix
|
|
200
229
|
return {
|
|
@@ -234,5 +263,6 @@ export function createChat<
|
|
|
234
263
|
addToolResult,
|
|
235
264
|
addToolApprovalResponse,
|
|
236
265
|
updateBody,
|
|
266
|
+
updateForwardedProps,
|
|
237
267
|
} as unknown as CreateChatReturn<TTools, TSchema>
|
|
238
268
|
}
|
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
|
|
@@ -183,9 +190,15 @@ interface BaseCreateChatReturn<
|
|
|
183
190
|
*/
|
|
184
191
|
readonly sessionGenerating: boolean
|
|
185
192
|
/**
|
|
186
|
-
*
|
|
193
|
+
* @deprecated Use `updateForwardedProps` instead. Both populate the
|
|
194
|
+
* same wire payload; `updateBody` is retained for backward compatibility.
|
|
187
195
|
*/
|
|
188
196
|
updateBody: (body: Record<string, any>) => void
|
|
197
|
+
/**
|
|
198
|
+
* Update the AG-UI `forwardedProps` sent with requests (e.g., for
|
|
199
|
+
* changing model selection or other client-driven options).
|
|
200
|
+
*/
|
|
201
|
+
updateForwardedProps: (forwardedProps: Record<string, any>) => void
|
|
189
202
|
}
|
|
190
203
|
|
|
191
204
|
// Note: createChatClientOptions and InferChatMessages are now in @tanstack/ai-client
|