@tanstack/ai-client 0.6.0 → 0.7.2
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/README.md +6 -6
- package/dist/esm/chat-client.d.ts +62 -3
- package/dist/esm/chat-client.js +264 -54
- package/dist/esm/chat-client.js.map +1 -1
- package/dist/esm/connection-adapters.d.ts +28 -12
- package/dist/esm/connection-adapters.js +93 -0
- package/dist/esm/connection-adapters.js.map +1 -1
- package/dist/esm/events.js +1 -1
- package/dist/esm/events.js.map +1 -1
- package/dist/esm/generation-client.d.ts +5 -5
- package/dist/esm/generation-client.js +1 -1
- package/dist/esm/generation-client.js.map +1 -1
- package/dist/esm/generation-types.d.ts +5 -5
- package/dist/esm/generation-types.js.map +1 -1
- package/dist/esm/index.d.ts +4 -2
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/realtime-client.d.ts +98 -0
- package/dist/esm/realtime-client.js +384 -0
- package/dist/esm/realtime-client.js.map +1 -0
- package/dist/esm/realtime-types.d.ts +118 -0
- package/dist/esm/types.d.ts +23 -2
- package/dist/esm/types.js.map +1 -1
- package/dist/esm/video-generation-client.d.ts +5 -5
- package/dist/esm/video-generation-client.js +1 -1
- package/dist/esm/video-generation-client.js.map +1 -1
- package/package.json +3 -2
- package/src/chat-client.ts +355 -86
- package/src/connection-adapters.ts +147 -12
- package/src/events.ts +1 -1
- package/src/generation-client.ts +7 -7
- package/src/generation-types.ts +5 -5
- package/src/index.ts +11 -0
- package/src/realtime-client.ts +526 -0
- package/src/realtime-types.ts +180 -0
- package/src/types.ts +31 -2
- package/src/video-generation-client.ts +6 -6
|
@@ -62,15 +62,9 @@ async function* readStreamLines(
|
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
* Connection adapter interface - converts a connection into a stream of chunks
|
|
67
|
-
*/
|
|
68
|
-
export interface ConnectionAdapter {
|
|
65
|
+
export interface ConnectConnectionAdapter {
|
|
69
66
|
/**
|
|
70
|
-
* Connect and return an async iterable of StreamChunks
|
|
71
|
-
* @param messages - The messages to send (UIMessages or ModelMessages)
|
|
72
|
-
* @param data - Additional data to send
|
|
73
|
-
* @param abortSignal - Optional abort signal for request cancellation
|
|
67
|
+
* Connect and return an async iterable of StreamChunks.
|
|
74
68
|
*/
|
|
75
69
|
connect: (
|
|
76
70
|
messages: Array<UIMessage> | Array<ModelMessage>,
|
|
@@ -79,6 +73,147 @@ export interface ConnectionAdapter {
|
|
|
79
73
|
) => AsyncIterable<StreamChunk>
|
|
80
74
|
}
|
|
81
75
|
|
|
76
|
+
export interface SubscribeConnectionAdapter {
|
|
77
|
+
/**
|
|
78
|
+
* Subscribe to stream chunks.
|
|
79
|
+
*/
|
|
80
|
+
subscribe: (abortSignal?: AbortSignal) => AsyncIterable<StreamChunk>
|
|
81
|
+
/**
|
|
82
|
+
* Send a request; chunks arrive through subscribe().
|
|
83
|
+
*/
|
|
84
|
+
send: (
|
|
85
|
+
messages: Array<UIMessage> | Array<ModelMessage>,
|
|
86
|
+
data?: Record<string, any>,
|
|
87
|
+
abortSignal?: AbortSignal,
|
|
88
|
+
) => Promise<void>
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Connection adapter union.
|
|
93
|
+
* Provide either `connect`, or `subscribe` + `send`.
|
|
94
|
+
*/
|
|
95
|
+
export type ConnectionAdapter =
|
|
96
|
+
| ConnectConnectionAdapter
|
|
97
|
+
| SubscribeConnectionAdapter
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Normalize a ConnectionAdapter to subscribe/send operations.
|
|
101
|
+
*
|
|
102
|
+
* If a connection provides native subscribe/send, that mode is used.
|
|
103
|
+
* Otherwise, connect() is wrapped using an async queue.
|
|
104
|
+
*/
|
|
105
|
+
export function normalizeConnectionAdapter(
|
|
106
|
+
connection: ConnectionAdapter | undefined,
|
|
107
|
+
): SubscribeConnectionAdapter {
|
|
108
|
+
if (!connection) {
|
|
109
|
+
throw new Error('Connection adapter is required')
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const hasConnect = 'connect' in connection
|
|
113
|
+
const hasSubscribe = 'subscribe' in connection
|
|
114
|
+
const hasSend = 'send' in connection
|
|
115
|
+
|
|
116
|
+
if (hasConnect && (hasSubscribe || hasSend)) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
'Connection adapter must provide either connect or both subscribe and send, not both modes',
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (hasSubscribe && hasSend) {
|
|
123
|
+
return {
|
|
124
|
+
subscribe: connection.subscribe.bind(connection),
|
|
125
|
+
send: connection.send.bind(connection),
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (!hasConnect) {
|
|
130
|
+
throw new Error(
|
|
131
|
+
'Connection adapter must provide either connect or both subscribe and send',
|
|
132
|
+
)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Legacy connect() wrapper
|
|
136
|
+
let activeBuffer: Array<StreamChunk> = []
|
|
137
|
+
let activeWaiters: Array<(chunk: StreamChunk | null) => void> = []
|
|
138
|
+
|
|
139
|
+
function push(chunk: StreamChunk): void {
|
|
140
|
+
const waiter = activeWaiters.shift()
|
|
141
|
+
if (waiter) {
|
|
142
|
+
waiter(chunk)
|
|
143
|
+
} else {
|
|
144
|
+
activeBuffer.push(chunk)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
subscribe(abortSignal?: AbortSignal): AsyncIterable<StreamChunk> {
|
|
150
|
+
// Transfer ownership to the latest subscriber so only one active
|
|
151
|
+
// subscribe() call receives chunks from the shared connect-wrapper queue.
|
|
152
|
+
const myBuffer: Array<StreamChunk> = activeBuffer.splice(0)
|
|
153
|
+
const myWaiters: Array<(chunk: StreamChunk | null) => void> = []
|
|
154
|
+
activeBuffer = myBuffer
|
|
155
|
+
activeWaiters = myWaiters
|
|
156
|
+
|
|
157
|
+
return (async function* () {
|
|
158
|
+
while (!abortSignal?.aborted) {
|
|
159
|
+
let chunk: StreamChunk | null
|
|
160
|
+
if (myBuffer.length > 0) {
|
|
161
|
+
chunk = myBuffer.shift()!
|
|
162
|
+
} else {
|
|
163
|
+
chunk = await new Promise<StreamChunk | null>((resolve) => {
|
|
164
|
+
const onAbort = () => resolve(null)
|
|
165
|
+
myWaiters.push((c) => {
|
|
166
|
+
abortSignal?.removeEventListener('abort', onAbort)
|
|
167
|
+
resolve(c)
|
|
168
|
+
})
|
|
169
|
+
abortSignal?.addEventListener('abort', onAbort, { once: true })
|
|
170
|
+
})
|
|
171
|
+
}
|
|
172
|
+
if (chunk !== null) yield chunk
|
|
173
|
+
}
|
|
174
|
+
})()
|
|
175
|
+
},
|
|
176
|
+
async send(messages, data, abortSignal) {
|
|
177
|
+
let hasTerminalEvent = false
|
|
178
|
+
try {
|
|
179
|
+
const stream = connection.connect(messages, data, abortSignal)
|
|
180
|
+
for await (const chunk of stream) {
|
|
181
|
+
if (chunk.type === 'RUN_FINISHED' || chunk.type === 'RUN_ERROR') {
|
|
182
|
+
hasTerminalEvent = true
|
|
183
|
+
}
|
|
184
|
+
push(chunk)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// If the connect stream ended cleanly without a terminal event,
|
|
188
|
+
// synthesize RUN_FINISHED so request-scoped consumers can complete.
|
|
189
|
+
if (!abortSignal?.aborted && !hasTerminalEvent) {
|
|
190
|
+
push({
|
|
191
|
+
type: 'RUN_FINISHED',
|
|
192
|
+
runId: `run-${Date.now()}`,
|
|
193
|
+
model: 'connect-wrapper',
|
|
194
|
+
timestamp: Date.now(),
|
|
195
|
+
finishReason: 'stop',
|
|
196
|
+
})
|
|
197
|
+
}
|
|
198
|
+
} catch (err) {
|
|
199
|
+
if (!abortSignal?.aborted && !hasTerminalEvent) {
|
|
200
|
+
push({
|
|
201
|
+
type: 'RUN_ERROR',
|
|
202
|
+
timestamp: Date.now(),
|
|
203
|
+
error: {
|
|
204
|
+
message:
|
|
205
|
+
err instanceof Error
|
|
206
|
+
? err.message
|
|
207
|
+
: 'Unknown error in connect()',
|
|
208
|
+
},
|
|
209
|
+
})
|
|
210
|
+
}
|
|
211
|
+
throw err
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
82
217
|
/**
|
|
83
218
|
* Options for fetch-based connection adapters
|
|
84
219
|
*/
|
|
@@ -129,7 +264,7 @@ export function fetchServerSentEvents(
|
|
|
129
264
|
options:
|
|
130
265
|
| FetchConnectionOptions
|
|
131
266
|
| (() => FetchConnectionOptions | Promise<FetchConnectionOptions>) = {},
|
|
132
|
-
):
|
|
267
|
+
): ConnectConnectionAdapter {
|
|
133
268
|
return {
|
|
134
269
|
async *connect(messages, data, abortSignal) {
|
|
135
270
|
// Resolve URL and options if they are functions
|
|
@@ -228,7 +363,7 @@ export function fetchHttpStream(
|
|
|
228
363
|
options:
|
|
229
364
|
| FetchConnectionOptions
|
|
230
365
|
| (() => FetchConnectionOptions | Promise<FetchConnectionOptions>) = {},
|
|
231
|
-
):
|
|
366
|
+
): ConnectConnectionAdapter {
|
|
232
367
|
return {
|
|
233
368
|
async *connect(messages, data, abortSignal) {
|
|
234
369
|
// Resolve URL and options if they are functions
|
|
@@ -301,7 +436,7 @@ export function stream(
|
|
|
301
436
|
messages: Array<UIMessage> | Array<ModelMessage>,
|
|
302
437
|
data?: Record<string, any>,
|
|
303
438
|
) => AsyncIterable<StreamChunk>,
|
|
304
|
-
):
|
|
439
|
+
): ConnectConnectionAdapter {
|
|
305
440
|
return {
|
|
306
441
|
async *connect(messages, data) {
|
|
307
442
|
// Pass messages as-is (UIMessages with parts preserved)
|
|
@@ -332,7 +467,7 @@ export function rpcStream(
|
|
|
332
467
|
messages: Array<UIMessage> | Array<ModelMessage>,
|
|
333
468
|
data?: Record<string, any>,
|
|
334
469
|
) => AsyncIterable<StreamChunk>,
|
|
335
|
-
):
|
|
470
|
+
): ConnectConnectionAdapter {
|
|
336
471
|
return {
|
|
337
472
|
async *connect(messages, data) {
|
|
338
473
|
// Pass messages as-is (UIMessages with parts preserved)
|
package/src/events.ts
CHANGED
package/src/generation-client.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { GENERATION_EVENTS } from './generation-types'
|
|
2
2
|
import { parseSSEResponse } from './sse-parser'
|
|
3
3
|
import type { StreamChunk } from '@tanstack/ai'
|
|
4
|
-
import type {
|
|
4
|
+
import type { ConnectConnectionAdapter } from './connection-adapters'
|
|
5
5
|
import type {
|
|
6
6
|
GenerationClientOptions,
|
|
7
7
|
GenerationClientState,
|
|
@@ -27,7 +27,7 @@ interface GenerationCallbacks<TResult, TOutput> {
|
|
|
27
27
|
* (image, speech, transcription, summarize).
|
|
28
28
|
*
|
|
29
29
|
* Supports two transport modes:
|
|
30
|
-
* - **
|
|
30
|
+
* - **ConnectConnectionAdapter** — Streaming transport (SSE, HTTP stream, custom).
|
|
31
31
|
* Server wraps results in StreamChunk events with CUSTOM event names.
|
|
32
32
|
* - **Fetcher** — Direct async function call. No streaming protocol needed.
|
|
33
33
|
*
|
|
@@ -36,7 +36,7 @@ interface GenerationCallbacks<TResult, TOutput> {
|
|
|
36
36
|
*
|
|
37
37
|
* @example
|
|
38
38
|
* ```typescript
|
|
39
|
-
* // With
|
|
39
|
+
* // With streaming connection adapter
|
|
40
40
|
* const client = new GenerationClient<ImageGenerateInput, ImageGenerationResult>({
|
|
41
41
|
* connection: fetchServerSentEvents('/api/generate/image'),
|
|
42
42
|
* onResultChange: setResult,
|
|
@@ -62,7 +62,7 @@ export class GenerationClient<
|
|
|
62
62
|
TResult,
|
|
63
63
|
TOutput = TResult,
|
|
64
64
|
> {
|
|
65
|
-
private connection:
|
|
65
|
+
private connection: ConnectConnectionAdapter | undefined
|
|
66
66
|
private fetcher: GenerationFetcher<TInput, TResult> | undefined
|
|
67
67
|
private body: Record<string, any>
|
|
68
68
|
private result: TOutput | null = null
|
|
@@ -75,7 +75,7 @@ export class GenerationClient<
|
|
|
75
75
|
constructor(
|
|
76
76
|
options: GenerationClientOptions<TInput, TResult, TOutput> &
|
|
77
77
|
(
|
|
78
|
-
| { connection:
|
|
78
|
+
| { connection: ConnectConnectionAdapter; fetcher?: never }
|
|
79
79
|
| {
|
|
80
80
|
fetcher: GenerationFetcher<TInput, TResult>
|
|
81
81
|
connection?: never
|
|
@@ -127,7 +127,7 @@ export class GenerationClient<
|
|
|
127
127
|
this.setStatus('success')
|
|
128
128
|
}
|
|
129
129
|
} else if (this.connection) {
|
|
130
|
-
//
|
|
130
|
+
// Streaming adapter path
|
|
131
131
|
const mergedData = { ...this.body, ...input }
|
|
132
132
|
const stream = this.connection.connect([], mergedData, signal)
|
|
133
133
|
await this.processStream(stream)
|
|
@@ -149,7 +149,7 @@ export class GenerationClient<
|
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
/**
|
|
152
|
-
* Process a stream of AG-UI events from the
|
|
152
|
+
* Process a stream of AG-UI events from the streaming connection adapter.
|
|
153
153
|
*/
|
|
154
154
|
private async processStream(
|
|
155
155
|
source: AsyncIterable<StreamChunk>,
|
package/src/generation-types.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { StreamChunk } from '@tanstack/ai'
|
|
2
|
-
import type {
|
|
2
|
+
import type { ConnectConnectionAdapter } from './connection-adapters'
|
|
3
3
|
|
|
4
4
|
// ===========================
|
|
5
5
|
// Inference Utilities
|
|
@@ -81,10 +81,10 @@ export type GenerationFetcher<TInput, TResult> = (
|
|
|
81
81
|
|
|
82
82
|
/**
|
|
83
83
|
* Transport configuration for generation clients.
|
|
84
|
-
* Supports either a
|
|
84
|
+
* Supports either a connect-based streaming adapter or a direct fetcher function.
|
|
85
85
|
*/
|
|
86
86
|
export type GenerationTransport<TInput, TResult> =
|
|
87
|
-
| { connection:
|
|
87
|
+
| { connection: ConnectConnectionAdapter; fetcher?: never }
|
|
88
88
|
| { fetcher: GenerationFetcher<TInput, TResult>; connection?: never }
|
|
89
89
|
|
|
90
90
|
// ===========================
|
|
@@ -103,7 +103,7 @@ export interface GenerationClientOptions<_TInput, TResult, TOutput = TResult> {
|
|
|
103
103
|
/** Unique identifier for this generation client instance */
|
|
104
104
|
id?: string
|
|
105
105
|
|
|
106
|
-
/** Additional body parameters to send with
|
|
106
|
+
/** Additional body parameters to send with connect-based adapter requests */
|
|
107
107
|
body?: Record<string, any>
|
|
108
108
|
|
|
109
109
|
/**
|
|
@@ -119,7 +119,7 @@ export interface GenerationClientOptions<_TInput, TResult, TOutput = TResult> {
|
|
|
119
119
|
onError?: (error: Error) => void
|
|
120
120
|
/** Callback when progress is reported (0-100) */
|
|
121
121
|
onProgress?: (progress: number, message?: string) => void
|
|
122
|
-
/** Callback for each stream chunk (
|
|
122
|
+
/** Callback for each stream chunk (connect-based adapter mode only) */
|
|
123
123
|
onChunk?: (chunk: StreamChunk) => void
|
|
124
124
|
|
|
125
125
|
// Framework state callbacks (set by hooks, not users)
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { ChatClient } from './chat-client'
|
|
2
|
+
export { RealtimeClient } from './realtime-client'
|
|
2
3
|
export { GenerationClient } from './generation-client'
|
|
3
4
|
export { VideoGenerationClient } from './video-generation-client'
|
|
4
5
|
export type {
|
|
@@ -14,6 +15,7 @@ export type {
|
|
|
14
15
|
ChatRequestBody,
|
|
15
16
|
InferChatMessages,
|
|
16
17
|
ChatClientState,
|
|
18
|
+
ConnectionStatus,
|
|
17
19
|
// Multimodal content input type
|
|
18
20
|
MultimodalContent,
|
|
19
21
|
} from './types'
|
|
@@ -42,13 +44,22 @@ export type {
|
|
|
42
44
|
ExtractToolOutput,
|
|
43
45
|
} from './tool-types'
|
|
44
46
|
export type { AnyClientTool } from '@tanstack/ai'
|
|
47
|
+
export type {
|
|
48
|
+
RealtimeAdapter,
|
|
49
|
+
RealtimeConnection,
|
|
50
|
+
RealtimeClientOptions,
|
|
51
|
+
RealtimeClientState,
|
|
52
|
+
RealtimeStateChangeCallback,
|
|
53
|
+
} from './realtime-types'
|
|
45
54
|
export {
|
|
46
55
|
fetchServerSentEvents,
|
|
47
56
|
fetchHttpStream,
|
|
48
57
|
stream,
|
|
49
58
|
rpcStream,
|
|
59
|
+
type ConnectConnectionAdapter,
|
|
50
60
|
type ConnectionAdapter,
|
|
51
61
|
type FetchConnectionOptions,
|
|
62
|
+
type SubscribeConnectionAdapter,
|
|
52
63
|
} from './connection-adapters'
|
|
53
64
|
|
|
54
65
|
// Re-export message converters from @tanstack/ai
|