@tanstack/ai-client 0.5.2 → 0.6.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/esm/generation-client.d.ts +85 -0
- package/dist/esm/generation-client.js +190 -0
- package/dist/esm/generation-client.js.map +1 -0
- package/dist/esm/generation-types.d.ts +209 -0
- package/dist/esm/generation-types.js +14 -0
- package/dist/esm/generation-types.js.map +1 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +6 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/sse-parser.d.ts +8 -0
- package/dist/esm/sse-parser.js +51 -0
- package/dist/esm/sse-parser.js.map +1 -0
- package/dist/esm/video-generation-client.d.ts +95 -0
- package/dist/esm/video-generation-client.js +238 -0
- package/dist/esm/video-generation-client.js.map +1 -0
- package/package.json +2 -2
- package/src/generation-client.ts +302 -0
- package/src/generation-types.ts +270 -0
- package/src/index.ts +20 -0
- package/src/sse-parser.ts +76 -0
- package/src/video-generation-client.ts +371 -0
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import type { StreamChunk } from '@tanstack/ai'
|
|
2
|
+
import type { ConnectionAdapter } from './connection-adapters'
|
|
3
|
+
|
|
4
|
+
// ===========================
|
|
5
|
+
// Inference Utilities
|
|
6
|
+
// ===========================
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Infers the output type from an `onResult` callback's return type.
|
|
10
|
+
*
|
|
11
|
+
* - If the callback returns a concrete type (excluding null/void/undefined), uses that type.
|
|
12
|
+
* - If the callback only returns null/void/undefined, or is not provided, falls back to TResult.
|
|
13
|
+
*
|
|
14
|
+
* @template TResult - The raw result type from the generation
|
|
15
|
+
* @template TFn - The onResult callback type (or undefined if not provided)
|
|
16
|
+
*/
|
|
17
|
+
export type InferGenerationOutput<TResult, TFn> = TFn extends (
|
|
18
|
+
result: any,
|
|
19
|
+
) => infer R
|
|
20
|
+
? [Exclude<R, null | void | undefined>] extends [never]
|
|
21
|
+
? TResult
|
|
22
|
+
: Exclude<R, null | void | undefined>
|
|
23
|
+
: TResult
|
|
24
|
+
|
|
25
|
+
// ===========================
|
|
26
|
+
// State
|
|
27
|
+
// ===========================
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* State machine for generation clients.
|
|
31
|
+
* Simpler than ChatClientState since generation is a single request/response cycle.
|
|
32
|
+
*/
|
|
33
|
+
export type GenerationClientState = 'idle' | 'generating' | 'success' | 'error'
|
|
34
|
+
|
|
35
|
+
// ===========================
|
|
36
|
+
// Event Constants
|
|
37
|
+
// ===========================
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Well-known CUSTOM event names used by generation clients.
|
|
41
|
+
* These events are emitted by the server-side streaming helpers
|
|
42
|
+
* and consumed by the client-side GenerationClient.
|
|
43
|
+
*/
|
|
44
|
+
export const GENERATION_EVENTS = {
|
|
45
|
+
/** The generation result payload */
|
|
46
|
+
RESULT: 'generation:result',
|
|
47
|
+
/** Progress update (0-100) with optional message */
|
|
48
|
+
PROGRESS: 'generation:progress',
|
|
49
|
+
/** Video job created with jobId */
|
|
50
|
+
VIDEO_JOB_CREATED: 'video:job:created',
|
|
51
|
+
/** Video job status update */
|
|
52
|
+
VIDEO_STATUS: 'video:status',
|
|
53
|
+
} as const
|
|
54
|
+
|
|
55
|
+
// ===========================
|
|
56
|
+
// Transport Types
|
|
57
|
+
// ===========================
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Options passed to a fetcher function by the generation client.
|
|
61
|
+
*/
|
|
62
|
+
export interface GenerationFetcherOptions {
|
|
63
|
+
/** AbortSignal that is triggered when the user calls `stop()` */
|
|
64
|
+
signal: AbortSignal
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* A direct async function that performs a generation request.
|
|
69
|
+
*
|
|
70
|
+
* Can return the result directly, or return a `Response` with an SSE body
|
|
71
|
+
* (e.g., from a TanStack Start server function using `toServerSentEventsResponse()`).
|
|
72
|
+
* When a `Response` is returned, the client will parse it as an SSE stream.
|
|
73
|
+
*
|
|
74
|
+
* @template TInput - The input type for the generation request
|
|
75
|
+
* @template TResult - The result type returned by the generation
|
|
76
|
+
*/
|
|
77
|
+
export type GenerationFetcher<TInput, TResult> = (
|
|
78
|
+
input: TInput,
|
|
79
|
+
options?: GenerationFetcherOptions,
|
|
80
|
+
) => Promise<TResult | Response>
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Transport configuration for generation clients.
|
|
84
|
+
* Supports either a ConnectionAdapter (streaming) or a direct fetcher function.
|
|
85
|
+
*/
|
|
86
|
+
export type GenerationTransport<TInput, TResult> =
|
|
87
|
+
| { connection: ConnectionAdapter; fetcher?: never }
|
|
88
|
+
| { fetcher: GenerationFetcher<TInput, TResult>; connection?: never }
|
|
89
|
+
|
|
90
|
+
// ===========================
|
|
91
|
+
// Client Options
|
|
92
|
+
// ===========================
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Options for the GenerationClient.
|
|
96
|
+
*
|
|
97
|
+
* @template TInput - The input type for the generation request (used by consuming code)
|
|
98
|
+
* @template TResult - The result type returned by the generation
|
|
99
|
+
* @template TOutput - The output type after optional transform (defaults to TResult)
|
|
100
|
+
*/
|
|
101
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
102
|
+
export interface GenerationClientOptions<_TInput, TResult, TOutput = TResult> {
|
|
103
|
+
/** Unique identifier for this generation client instance */
|
|
104
|
+
id?: string
|
|
105
|
+
|
|
106
|
+
/** Additional body parameters to send with ConnectionAdapter requests */
|
|
107
|
+
body?: Record<string, any>
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Callback when a result is received. Can optionally return a transformed value
|
|
111
|
+
* that replaces the stored result.
|
|
112
|
+
*
|
|
113
|
+
* - Return a non-null value to transform and store it as the result
|
|
114
|
+
* - Return `null` to keep the previous result unchanged
|
|
115
|
+
* - Return nothing (`void`) to store the raw result as-is
|
|
116
|
+
*/
|
|
117
|
+
onResult?: (result: TResult) => TOutput | null | void
|
|
118
|
+
/** Callback when an error occurs */
|
|
119
|
+
onError?: (error: Error) => void
|
|
120
|
+
/** Callback when progress is reported (0-100) */
|
|
121
|
+
onProgress?: (progress: number, message?: string) => void
|
|
122
|
+
/** Callback for each stream chunk (ConnectionAdapter mode only) */
|
|
123
|
+
onChunk?: (chunk: StreamChunk) => void
|
|
124
|
+
|
|
125
|
+
// Framework state callbacks (set by hooks, not users)
|
|
126
|
+
/** @internal Called when result changes */
|
|
127
|
+
onResultChange?: (result: TOutput | null) => void
|
|
128
|
+
/** @internal Called when loading state changes */
|
|
129
|
+
onLoadingChange?: (isLoading: boolean) => void
|
|
130
|
+
/** @internal Called when error state changes */
|
|
131
|
+
onErrorChange?: (error: Error | undefined) => void
|
|
132
|
+
/** @internal Called when generation status changes */
|
|
133
|
+
onStatusChange?: (status: GenerationClientState) => void
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ===========================
|
|
137
|
+
// Video-Specific Options
|
|
138
|
+
// ===========================
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Video status information returned during job polling.
|
|
142
|
+
*/
|
|
143
|
+
export interface VideoStatusInfo {
|
|
144
|
+
/** Job identifier */
|
|
145
|
+
jobId: string
|
|
146
|
+
/** Current status of the video generation job */
|
|
147
|
+
status: 'pending' | 'processing' | 'completed' | 'failed'
|
|
148
|
+
/** Progress percentage (0-100), if available */
|
|
149
|
+
progress?: number
|
|
150
|
+
/** URL to the generated video (when completed) */
|
|
151
|
+
url?: string
|
|
152
|
+
/** Error message if status is 'failed' */
|
|
153
|
+
error?: string
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Composite result for video generation (job completion).
|
|
158
|
+
*/
|
|
159
|
+
export interface VideoGenerateResult {
|
|
160
|
+
/** Job identifier */
|
|
161
|
+
jobId: string
|
|
162
|
+
/** Final status */
|
|
163
|
+
status: 'completed'
|
|
164
|
+
/** URL to the generated video */
|
|
165
|
+
url: string
|
|
166
|
+
/** When the URL expires, if applicable */
|
|
167
|
+
expiresAt?: Date
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Options for the VideoGenerationClient.
|
|
172
|
+
*/
|
|
173
|
+
export interface VideoGenerationClientOptions<
|
|
174
|
+
TOutput = VideoGenerateResult,
|
|
175
|
+
> extends GenerationClientOptions<
|
|
176
|
+
VideoGenerateInput,
|
|
177
|
+
VideoGenerateResult,
|
|
178
|
+
TOutput
|
|
179
|
+
> {
|
|
180
|
+
/** Callback when a video job is created */
|
|
181
|
+
onJobCreated?: (jobId: string) => void
|
|
182
|
+
/** Callback on each status update */
|
|
183
|
+
onStatusUpdate?: (status: VideoStatusInfo) => void
|
|
184
|
+
|
|
185
|
+
// Framework state callbacks
|
|
186
|
+
/** @internal Called when jobId changes */
|
|
187
|
+
onJobIdChange?: (jobId: string | null) => void
|
|
188
|
+
/** @internal Called when video status changes */
|
|
189
|
+
onVideoStatusChange?: (status: VideoStatusInfo | null) => void
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ===========================
|
|
193
|
+
// Input Types
|
|
194
|
+
// ===========================
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Input for image generation.
|
|
198
|
+
*/
|
|
199
|
+
export interface ImageGenerateInput {
|
|
200
|
+
/** Text description of the desired image(s) */
|
|
201
|
+
prompt: string
|
|
202
|
+
/** Number of images to generate (default: 1) */
|
|
203
|
+
numberOfImages?: number
|
|
204
|
+
/** Image size in WIDTHxHEIGHT format (e.g., "1024x1024") */
|
|
205
|
+
size?: string
|
|
206
|
+
/** Model-specific options */
|
|
207
|
+
modelOptions?: Record<string, any>
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Input for text-to-speech generation.
|
|
212
|
+
*/
|
|
213
|
+
export interface SpeechGenerateInput {
|
|
214
|
+
/** The text to convert to speech */
|
|
215
|
+
text: string
|
|
216
|
+
/** The voice to use for generation */
|
|
217
|
+
voice?: string
|
|
218
|
+
/** The output audio format */
|
|
219
|
+
format?: 'mp3' | 'opus' | 'aac' | 'flac' | 'wav' | 'pcm'
|
|
220
|
+
/** The speed of the generated audio (0.25 to 4.0) */
|
|
221
|
+
speed?: number
|
|
222
|
+
/** Model-specific options */
|
|
223
|
+
modelOptions?: Record<string, any>
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Input for audio transcription.
|
|
228
|
+
*/
|
|
229
|
+
export interface TranscriptionGenerateInput {
|
|
230
|
+
/** The audio data to transcribe - can be base64 string, File, or Blob */
|
|
231
|
+
audio: string | File | Blob
|
|
232
|
+
/** The language of the audio in ISO-639-1 format (e.g., 'en') */
|
|
233
|
+
language?: string
|
|
234
|
+
/** An optional prompt to guide the transcription */
|
|
235
|
+
prompt?: string
|
|
236
|
+
/** The format of the transcription output */
|
|
237
|
+
responseFormat?: 'json' | 'text' | 'srt' | 'verbose_json' | 'vtt'
|
|
238
|
+
/** Model-specific options */
|
|
239
|
+
modelOptions?: Record<string, any>
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Input for text summarization.
|
|
244
|
+
*/
|
|
245
|
+
export interface SummarizeGenerateInput {
|
|
246
|
+
/** The text to summarize */
|
|
247
|
+
text: string
|
|
248
|
+
/** Maximum length of the summary */
|
|
249
|
+
maxLength?: number
|
|
250
|
+
/** Style of the summary */
|
|
251
|
+
style?: 'bullet-points' | 'paragraph' | 'concise'
|
|
252
|
+
/** Topics to focus on */
|
|
253
|
+
focus?: Array<string>
|
|
254
|
+
/** Model-specific options */
|
|
255
|
+
modelOptions?: Record<string, any>
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Input for video generation.
|
|
260
|
+
*/
|
|
261
|
+
export interface VideoGenerateInput {
|
|
262
|
+
/** Text description of the desired video */
|
|
263
|
+
prompt: string
|
|
264
|
+
/** Video size — format depends on provider (e.g., "16:9", "1280x720") */
|
|
265
|
+
size?: string
|
|
266
|
+
/** Video duration in seconds */
|
|
267
|
+
duration?: number
|
|
268
|
+
/** Model-specific options */
|
|
269
|
+
modelOptions?: Record<string, any>
|
|
270
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { ChatClient } from './chat-client'
|
|
2
|
+
export { GenerationClient } from './generation-client'
|
|
3
|
+
export { VideoGenerationClient } from './video-generation-client'
|
|
2
4
|
export type {
|
|
3
5
|
// Core message types (re-exported from @tanstack/ai via types.ts)
|
|
4
6
|
UIMessage,
|
|
@@ -15,6 +17,24 @@ export type {
|
|
|
15
17
|
// Multimodal content input type
|
|
16
18
|
MultimodalContent,
|
|
17
19
|
} from './types'
|
|
20
|
+
// Generation client types
|
|
21
|
+
export type {
|
|
22
|
+
InferGenerationOutput,
|
|
23
|
+
GenerationClientState,
|
|
24
|
+
GenerationClientOptions,
|
|
25
|
+
GenerationFetcher,
|
|
26
|
+
GenerationFetcherOptions,
|
|
27
|
+
GenerationTransport,
|
|
28
|
+
VideoGenerationClientOptions,
|
|
29
|
+
VideoStatusInfo,
|
|
30
|
+
VideoGenerateResult,
|
|
31
|
+
ImageGenerateInput,
|
|
32
|
+
SpeechGenerateInput,
|
|
33
|
+
TranscriptionGenerateInput,
|
|
34
|
+
SummarizeGenerateInput,
|
|
35
|
+
VideoGenerateInput,
|
|
36
|
+
} from './generation-types'
|
|
37
|
+
export { GENERATION_EVENTS } from './generation-types'
|
|
18
38
|
export { clientTools, createChatClientOptions } from './types'
|
|
19
39
|
export type {
|
|
20
40
|
ExtractToolNames,
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { StreamChunk } from '@tanstack/ai'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Read lines from a stream (newline-delimited)
|
|
5
|
+
*/
|
|
6
|
+
async function* readStreamLines(
|
|
7
|
+
reader: ReadableStreamDefaultReader<Uint8Array>,
|
|
8
|
+
abortSignal?: AbortSignal,
|
|
9
|
+
): AsyncGenerator<string> {
|
|
10
|
+
try {
|
|
11
|
+
const decoder = new TextDecoder()
|
|
12
|
+
let buffer = ''
|
|
13
|
+
|
|
14
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
15
|
+
while (true) {
|
|
16
|
+
if (abortSignal?.aborted) {
|
|
17
|
+
break
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const { done, value } = await reader.read()
|
|
21
|
+
if (done) break
|
|
22
|
+
|
|
23
|
+
buffer += decoder.decode(value, { stream: true })
|
|
24
|
+
const lines = buffer.split('\n')
|
|
25
|
+
|
|
26
|
+
buffer = lines.pop() || ''
|
|
27
|
+
|
|
28
|
+
for (const line of lines) {
|
|
29
|
+
if (line.trim()) {
|
|
30
|
+
yield line
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (buffer.trim()) {
|
|
36
|
+
yield buffer
|
|
37
|
+
}
|
|
38
|
+
} finally {
|
|
39
|
+
reader.releaseLock()
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Parse a Response body as Server-Sent Events, yielding StreamChunks.
|
|
45
|
+
*
|
|
46
|
+
* Used by GenerationClient to parse SSE Responses returned from fetchers
|
|
47
|
+
* (e.g., TanStack Start server functions using `toServerSentEventsResponse()`).
|
|
48
|
+
*/
|
|
49
|
+
export async function* parseSSEResponse(
|
|
50
|
+
response: Response,
|
|
51
|
+
abortSignal?: AbortSignal,
|
|
52
|
+
): AsyncGenerator<StreamChunk> {
|
|
53
|
+
if (!response.ok) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
`HTTP error! status: ${response.status} ${response.statusText}`,
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const reader = response.body?.getReader()
|
|
60
|
+
if (!reader) {
|
|
61
|
+
throw new Error('Response body is not readable')
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
for await (const line of readStreamLines(reader, abortSignal)) {
|
|
65
|
+
const data = line.startsWith('data: ') ? line.slice(6) : line
|
|
66
|
+
|
|
67
|
+
if (data === '[DONE]') continue
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
const parsed: StreamChunk = JSON.parse(data)
|
|
71
|
+
yield parsed
|
|
72
|
+
} catch (parseError) {
|
|
73
|
+
console.warn('Failed to parse SSE chunk:', data)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|