@tanstack/ai-react 0.19.3 → 0.21.1
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 +14 -5
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +2 -2
- package/dist/esm/types.d.ts +9 -9
- package/dist/esm/use-chat.d.ts +2 -2
- package/dist/esm/use-chat.js +92 -55
- package/dist/esm/use-chat.js.map +1 -1
- package/dist/esm/use-generate-audio.d.ts +3 -7
- package/dist/esm/use-generate-audio.js.map +1 -1
- package/dist/esm/use-generate-image.d.ts +3 -7
- package/dist/esm/use-generate-image.js.map +1 -1
- package/dist/esm/use-generate-speech.d.ts +3 -7
- package/dist/esm/use-generate-speech.js.map +1 -1
- package/dist/esm/use-generate-video.d.ts +3 -7
- package/dist/esm/use-generate-video.js +7 -3
- package/dist/esm/use-generate-video.js.map +1 -1
- package/dist/esm/use-generation.d.ts +3 -7
- package/dist/esm/use-generation.js +7 -3
- package/dist/esm/use-generation.js.map +1 -1
- package/dist/esm/use-summarize.d.ts +3 -7
- package/dist/esm/use-summarize.js.map +1 -1
- package/dist/esm/use-transcription.d.ts +3 -7
- package/dist/esm/use-transcription.js.map +1 -1
- package/package.json +4 -4
- package/src/index.ts +4 -0
- package/src/types.ts +19 -11
- package/src/use-chat.ts +132 -72
- package/src/use-generate-audio.ts +3 -7
- package/src/use-generate-image.ts +3 -7
- package/src/use-generate-speech.ts +3 -7
- package/src/use-generate-video.ts +22 -15
- package/src/use-generation.ts +21 -15
- package/src/use-summarize.ts +3 -7
- package/src/use-transcription.ts +3 -7
package/README.md
CHANGED
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
<picture>
|
|
3
3
|
<source
|
|
4
4
|
media="(prefers-color-scheme: dark)"
|
|
5
|
-
srcset="https://tanstack.com/api/readme/ai.png?
|
|
5
|
+
srcset="https://tanstack.com/api/readme/ai.png?theme=dark"
|
|
6
6
|
/>
|
|
7
7
|
<source
|
|
8
8
|
media="(prefers-color-scheme: light)"
|
|
9
|
-
srcset="https://tanstack.com/api/readme/ai.png
|
|
9
|
+
srcset="https://tanstack.com/api/readme/ai.png"
|
|
10
10
|
/>
|
|
11
11
|
<img
|
|
12
|
-
src="https://tanstack.com/api/readme/ai.png
|
|
13
|
-
alt="TanStack
|
|
12
|
+
src="https://tanstack.com/api/readme/ai.png"
|
|
13
|
+
alt="TanStack AI"
|
|
14
14
|
width="900"
|
|
15
15
|
/>
|
|
16
16
|
</picture>
|
|
@@ -36,6 +36,14 @@
|
|
|
36
36
|
</a>
|
|
37
37
|
</div>
|
|
38
38
|
|
|
39
|
+
<br />
|
|
40
|
+
|
|
41
|
+
<div align="center">
|
|
42
|
+
<a href="https://tanstack.com/blog/tanstack-open-source-awards-2026">
|
|
43
|
+
<img src="https://raw.githubusercontent.com/TanStack/ai/16826d81cade868956df240d6239a671e689193c/media/js-open-source-award-2026-ai-project-of-the-year.svg" alt="Winner of the 2026 JavaScript Open Source Award for AI Project of the Year" width="250" />
|
|
44
|
+
</a>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
39
47
|
# TanStack AI
|
|
40
48
|
|
|
41
49
|
Type-safe, provider-agnostic TypeScript SDK for building streaming chat,
|
|
@@ -191,7 +199,7 @@ Learn more in the
|
|
|
191
199
|
build low-latency realtime voice experiences.
|
|
192
200
|
- [Code Mode](https://tanstack.com/ai/latest/docs/code-mode/code-mode) - let
|
|
193
201
|
models write and execute TypeScript inside a secure isolate.
|
|
194
|
-
- [Code Mode with
|
|
202
|
+
- [Code Mode with Snippets](https://tanstack.com/ai/latest/docs/code-mode/code-mode-with-snippets) -
|
|
195
203
|
give Code Mode reusable runtime capabilities.
|
|
196
204
|
|
|
197
205
|
## Providers
|
|
@@ -208,6 +216,7 @@ Official adapters include:
|
|
|
208
216
|
| [`@tanstack/ai-grok`](https://tanstack.com/ai/latest/docs/adapters/grok) | xAI Grok chat, images, and realtime |
|
|
209
217
|
| [`@tanstack/ai-groq`](https://tanstack.com/ai/latest/docs/adapters/groq) | Groq low-latency inference |
|
|
210
218
|
| [`@tanstack/ai-elevenlabs`](https://tanstack.com/ai/latest/docs/adapters/elevenlabs) | ElevenLabs realtime voice, speech, transcription, music, and sound effects |
|
|
219
|
+
| [`@tanstack/ai-byteplus`](https://tanstack.com/ai/latest/docs/adapters/byteplus) | BytePlus Seed chat, Seedance video, Seedream image, and Seed Speech TTS/ASR |
|
|
211
220
|
| [`@tanstack/ai-fal`](https://tanstack.com/ai/latest/docs/adapters/fal) | fal.ai image, video, audio, speech, and transcription models |
|
|
212
221
|
|
|
213
222
|
The adapter system is tree-shakeable by activity. Import `openaiText` for chat,
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -20,4 +20,4 @@ export { useGenerateVideo } from './use-generate-video.js';
|
|
|
20
20
|
export type { UseGenerateVideoOptions, UseGenerateVideoReturn, } from './use-generate-video.js';
|
|
21
21
|
export { useAudioRecorder } from './use-audio-recorder.js';
|
|
22
22
|
export type { UseAudioRecorderOptions, UseAudioRecorderReturn, } from './use-audio-recorder.js';
|
|
23
|
-
export { fetchServerSentEvents, localStoragePersistence, sessionStoragePersistence, indexedDBPersistence, StorageUnavailableError, type ChatClientPersistence, type ChatPersistedState, type ChatPersistenceOption, type ChatStorageAdapter, type WebStoragePersistenceOptions, type IndexedDBPersistenceOptions, fetchHttpStream, xhrServerSentEvents, xhrHttpStream, stream, rpcStream, createChatClientOptions, createMcpAppBridge, type McpAppBridge, type CreateMcpAppBridgeOptions, type ChatFetcher, type ChatFetcherInput, type ChatFetcherOptions, type ConnectionAdapter, type ConnectConnectionAdapter, type SubscribeConnectionAdapter, type RunAgentInputContext, type FetchConnectionOptions, type XhrConnectionOptions, type InferChatMessages, type GenerationClientState, type ImageGenerateInput, type AudioGenerateInput, type SpeechGenerateInput, type TranscriptionGenerateInput, type SummarizeGenerateInput, type VideoGenerateInput, type VideoGenerateResult, type VideoStatusInfo, } from '@tanstack/ai-client';
|
|
23
|
+
export { fetchServerSentEvents, localStoragePersistence, sessionStoragePersistence, indexedDBPersistence, StorageUnavailableError, type ChatClientPersistence, type ChatPersistedState, type ChatPersistenceOption, type ChatStorageAdapter, type WebStoragePersistenceOptions, type IndexedDBPersistenceOptions, fetchHttpStream, xhrServerSentEvents, xhrHttpStream, stream, rpcStream, webSocket, createChatClientOptions, createMcpAppBridge, type McpAppBridge, type CreateMcpAppBridgeOptions, type ChatFetcher, type ChatFetcherInput, type ChatFetcherOptions, type ConnectionAdapter, type ConnectConnectionAdapter, type SubscribeConnectionAdapter, type RunAgentInputContext, type FetchConnectionOptions, type XhrConnectionOptions, type WebSocketConnectionOptions, type InferChatMessages, type GenericInterrupt, type RegisteredGenericInterrupt, type GenerationClientState, type ImageGenerateInput, type AudioGenerateInput, type SpeechGenerateInput, type TranscriptionGenerateInput, type SummarizeGenerateInput, type VideoGenerateInput, type VideoGenerateResult, type VideoStatusInfo, } from '@tanstack/ai-client';
|
package/dist/esm/index.js
CHANGED
|
@@ -9,5 +9,5 @@ import { useTranscription } from "./use-transcription.js";
|
|
|
9
9
|
import { useSummarize } from "./use-summarize.js";
|
|
10
10
|
import { useGenerateVideo } from "./use-generate-video.js";
|
|
11
11
|
import { useAudioRecorder } from "./use-audio-recorder.js";
|
|
12
|
-
import { StorageUnavailableError, createChatClientOptions, createMcpAppBridge, fetchHttpStream, fetchServerSentEvents, indexedDBPersistence, localStoragePersistence, rpcStream, sessionStoragePersistence, stream, xhrHttpStream, xhrServerSentEvents } from "@tanstack/ai-client";
|
|
13
|
-
export { StorageUnavailableError, createChatClientOptions, createMcpAppBridge, fetchHttpStream, fetchServerSentEvents, indexedDBPersistence, localStoragePersistence, rpcStream, sessionStoragePersistence, stream, useAudioRecorder, useChat, useGenerateAudio, useGenerateImage, useGenerateSpeech, useGenerateVideo, useGeneration, useMcpAppBridge, useRealtimeChat, useSummarize, useTranscription, xhrHttpStream, xhrServerSentEvents };
|
|
12
|
+
import { StorageUnavailableError, createChatClientOptions, createMcpAppBridge, fetchHttpStream, fetchServerSentEvents, indexedDBPersistence, localStoragePersistence, rpcStream, sessionStoragePersistence, stream, webSocket, xhrHttpStream, xhrServerSentEvents } from "@tanstack/ai-client";
|
|
13
|
+
export { StorageUnavailableError, createChatClientOptions, createMcpAppBridge, fetchHttpStream, fetchServerSentEvents, indexedDBPersistence, localStoragePersistence, rpcStream, sessionStoragePersistence, stream, useAudioRecorder, useChat, useGenerateAudio, useGenerateImage, useGenerateSpeech, useGenerateVideo, useGeneration, useMcpAppBridge, useRealtimeChat, useSummarize, useTranscription, webSocket, xhrHttpStream, xhrServerSentEvents };
|
package/dist/esm/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { AnyClientTool, InferSchemaType, ModelMessage, RunAgentResumeItem, SchemaInput } from '@tanstack/ai/client';
|
|
2
|
-
import { AIDevtoolsDisplayOptions, BoundInterrupts, ChatClientOptions, ChatClientState,
|
|
1
|
+
import { AnyClientTool, InterruptDefinition, InferSchemaType, ModelMessage, RunAgentResumeItem, SchemaInput } from '@tanstack/ai/client';
|
|
2
|
+
import { AIDevtoolsDisplayOptions, BoundInterrupts, ChatClientOptions, ChatClientState, ResolvableChatInterrupt, ChatInterruptState, ChatRequestBody, ChatResumeState, ClientContextOptionFromTools, ConnectionStatus, DistributedOmit, InferredClientContext, MultimodalContent, QueueConfig, QueueOption, QueueStrategy, QueuedMessage, SendMessageOptions, UIMessage, WhenBusy } from '@tanstack/ai-client';
|
|
3
3
|
export type { ChatRequestBody, MultimodalContent, QueueConfig, QueuedMessage, QueueOption, QueueStrategy, SendMessageOptions, UIMessage, WhenBusy, };
|
|
4
4
|
/**
|
|
5
5
|
* Recursive partial — every property and every nested array element is optional.
|
|
@@ -35,7 +35,7 @@ export type DeepPartial<T> = T extends ReadonlyArray<infer U> ? Array<DeepPartia
|
|
|
35
35
|
* Note: Connection and body changes will recreate the ChatClient instance.
|
|
36
36
|
* To update these options, remount the component or use a key prop.
|
|
37
37
|
*/
|
|
38
|
-
export type UseChatOptions<TTools extends ReadonlyArray<AnyClientTool> = any, TSchema extends SchemaInput | undefined = undefined, TContext = InferredClientContext<TTools>> = DistributedOmit<ChatClientOptions<TTools, TContext>, 'onMessagesChange' | 'onLoadingChange' | 'onErrorChange' | 'onStatusChange' | 'onSubscriptionChange' | 'onConnectionStatusChange' | 'onSessionGeneratingChange' | 'onQueueChange' | 'onResumeStateChange' | 'onRunIdChange' | 'context' | 'devtools'
|
|
38
|
+
export type UseChatOptions<TTools extends ReadonlyArray<AnyClientTool> = any, TSchema extends SchemaInput | undefined = undefined, TContext = InferredClientContext<TTools>, TInterrupts extends ReadonlyArray<InterruptDefinition<any, any, any, any>> = readonly []> = DistributedOmit<ChatClientOptions<TTools, TContext, TInterrupts>, 'onMessagesChange' | 'onLoadingChange' | 'onErrorChange' | 'onStatusChange' | 'onSubscriptionChange' | 'onConnectionStatusChange' | 'onSessionGeneratingChange' | 'onQueueChange' | 'onResumeStateChange' | 'onRunIdChange' | 'context' | 'devtools'> & {
|
|
39
39
|
/** Display options for TanStack AI Devtools. */
|
|
40
40
|
devtools?: AIDevtoolsDisplayOptions;
|
|
41
41
|
/**
|
|
@@ -56,7 +56,7 @@ export type UseChatOptions<TTools extends ReadonlyArray<AnyClientTool> = any, TS
|
|
|
56
56
|
* typed `partial` / `final` fields; when it is omitted (default), the return
|
|
57
57
|
* is unchanged.
|
|
58
58
|
*/
|
|
59
|
-
export type UseChatReturn<TTools extends ReadonlyArray<AnyClientTool> = any, TSchema extends SchemaInput | undefined = undefined> = BaseUseChatReturn<TTools, TSchema extends SchemaInput ? InferSchemaType<TSchema> : unknown> & (TSchema extends SchemaInput ? {
|
|
59
|
+
export type UseChatReturn<TTools extends ReadonlyArray<AnyClientTool> = any, TSchema extends SchemaInput | undefined = undefined, TInterrupts extends ReadonlyArray<InterruptDefinition<any, any, any, any>> = readonly []> = BaseUseChatReturn<TTools, TSchema extends SchemaInput ? InferSchemaType<TSchema> : unknown, TInterrupts> & (TSchema extends SchemaInput ? {
|
|
60
60
|
/**
|
|
61
61
|
* Live, progressively-parsed structured output. Updated from
|
|
62
62
|
* `TEXT_MESSAGE_CONTENT` deltas via `parsePartialJSON` while the stream
|
|
@@ -71,7 +71,7 @@ export type UseChatReturn<TTools extends ReadonlyArray<AnyClientTool> = any, TSc
|
|
|
71
71
|
*/
|
|
72
72
|
final: InferSchemaType<TSchema> | null;
|
|
73
73
|
} : Record<never, never>);
|
|
74
|
-
interface BaseUseChatReturn<TTools extends ReadonlyArray<AnyClientTool> = any, TData = unknown> {
|
|
74
|
+
interface BaseUseChatReturn<TTools extends ReadonlyArray<AnyClientTool> = any, TData = unknown, TInterrupts extends ReadonlyArray<InterruptDefinition<any, any, any, any>> = readonly []> {
|
|
75
75
|
/**
|
|
76
76
|
* Current messages in the conversation. When `outputSchema` is supplied,
|
|
77
77
|
* `messages[i].parts.find(p => p.type === 'structured-output')` is typed
|
|
@@ -128,14 +128,14 @@ interface BaseUseChatReturn<TTools extends ReadonlyArray<AnyClientTool> = any, T
|
|
|
128
128
|
* it, correlate a log line).
|
|
129
129
|
*/
|
|
130
130
|
runId: string | null;
|
|
131
|
-
interrupts: BoundInterrupts<TTools>;
|
|
131
|
+
interrupts: BoundInterrupts<TTools, TInterrupts>;
|
|
132
132
|
/** @deprecated Use `interrupts`. */
|
|
133
|
-
pendingInterrupts: BoundInterrupts<TTools>;
|
|
134
|
-
interruptErrors: ChatInterruptState<TTools>['interruptErrors'];
|
|
133
|
+
pendingInterrupts: BoundInterrupts<TTools, TInterrupts>;
|
|
134
|
+
interruptErrors: ChatInterruptState<TTools, TInterrupts>['interruptErrors'];
|
|
135
135
|
resuming: boolean;
|
|
136
136
|
resolveInterrupts: {
|
|
137
137
|
(approved: boolean): void;
|
|
138
|
-
(resolver: (interrupt:
|
|
138
|
+
(resolver: (interrupt: ResolvableChatInterrupt<TTools, TInterrupts>) => undefined): void;
|
|
139
139
|
};
|
|
140
140
|
cancelInterrupts: () => void;
|
|
141
141
|
retryInterrupts: () => void;
|
package/dist/esm/use-chat.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AnyClientTool, SchemaInput } from '@tanstack/ai/client';
|
|
1
|
+
import { AnyClientTool, InterruptDefinition, SchemaInput } from '@tanstack/ai/client';
|
|
2
2
|
import { InferredClientContext } from '@tanstack/ai-client';
|
|
3
3
|
import { UseChatOptions, UseChatReturn } from './types.js';
|
|
4
|
-
export declare function useChat<const TTools extends ReadonlyArray<AnyClientTool> = any, TSchema extends SchemaInput | undefined = undefined, TContext = InferredClientContext<TTools>>(options: UseChatOptions<TTools, TSchema, TContext>): UseChatReturn<TTools, TSchema>;
|
|
4
|
+
export declare function useChat<const TTools extends ReadonlyArray<AnyClientTool> = any, TSchema extends SchemaInput | undefined = undefined, TContext = InferredClientContext<TTools>, const TInterrupts extends ReadonlyArray<InterruptDefinition<any, any, any, any>> = readonly []>(options: UseChatOptions<TTools, TSchema, TContext, TInterrupts>): UseChatReturn<TTools, TSchema, TInterrupts>;
|
package/dist/esm/use-chat.js
CHANGED
|
@@ -36,7 +36,7 @@ function useChat(options) {
|
|
|
36
36
|
setRunId(target.getCurrentRunId());
|
|
37
37
|
setInterruptState(target.getInterruptState());
|
|
38
38
|
}, []);
|
|
39
|
-
const client = useMemo(() => {
|
|
39
|
+
const { client, initialization } = useMemo(() => {
|
|
40
40
|
const messagesToUse = options.initialMessages || [];
|
|
41
41
|
isFirstMountRef.current = false;
|
|
42
42
|
const initialOptions = optionsRef.current;
|
|
@@ -47,15 +47,29 @@ function useChat(options) {
|
|
|
47
47
|
if (!currentInstance || activeClientRef.current !== currentInstance) return;
|
|
48
48
|
return currentInstance;
|
|
49
49
|
};
|
|
50
|
-
const
|
|
50
|
+
const initializationState = {
|
|
51
|
+
ready: false,
|
|
52
|
+
callbacks: []
|
|
53
|
+
};
|
|
54
|
+
const runOrQueueForActiveInstance = (callback) => {
|
|
55
|
+
if (!initializationState.ready) {
|
|
56
|
+
initializationState.callbacks.push(callback);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const currentInstance = instanceHolder.current;
|
|
60
|
+
if (!currentInstance || activeClientRef.current !== currentInstance) return;
|
|
61
|
+
callback();
|
|
62
|
+
};
|
|
51
63
|
const instance = new ChatClient({
|
|
52
64
|
devtoolsBridgeFactory: createChatDevtoolsBridge,
|
|
53
65
|
...transport,
|
|
54
66
|
initialMessages: messagesToUse,
|
|
67
|
+
...typeof initialOptions.threadId === "string" && initialOptions.persistence ? {
|
|
68
|
+
persistence: initialOptions.persistence,
|
|
69
|
+
threadId: initialOptions.threadId
|
|
70
|
+
} : { ...initialOptions.threadId !== void 0 && { threadId: initialOptions.threadId } },
|
|
55
71
|
...initialOptions.body !== void 0 && { body: initialOptions.body },
|
|
56
|
-
...initialOptions.threadId !== void 0 && { threadId: initialOptions.threadId },
|
|
57
72
|
...initialOptions.forwardedProps !== void 0 && { forwardedProps: initialOptions.forwardedProps },
|
|
58
|
-
...initialOptions.persistence !== void 0 && { persistence: initialOptions.persistence },
|
|
59
73
|
...initialOptions.initialResumeSnapshot !== void 0 && { initialResumeSnapshot: initialOptions.initialResumeSnapshot },
|
|
60
74
|
...initialOptions.context !== void 0 && { context: initialOptions.context },
|
|
61
75
|
devtools: {
|
|
@@ -66,92 +80,116 @@ function useChat(options) {
|
|
|
66
80
|
},
|
|
67
81
|
onResponse: (response) => {
|
|
68
82
|
if (!getActiveInstance()) return;
|
|
69
|
-
optionsRef.current.onResponse?.(response);
|
|
83
|
+
return optionsRef.current.onResponse?.(response);
|
|
70
84
|
},
|
|
71
85
|
onChunk: (chunk) => {
|
|
72
|
-
|
|
73
|
-
|
|
86
|
+
runOrQueueForActiveInstance(() => {
|
|
87
|
+
optionsRef.current.onChunk?.(chunk);
|
|
88
|
+
});
|
|
74
89
|
},
|
|
75
90
|
onFinish: (message) => {
|
|
76
|
-
|
|
77
|
-
|
|
91
|
+
runOrQueueForActiveInstance(() => {
|
|
92
|
+
optionsRef.current.onFinish?.(message);
|
|
93
|
+
});
|
|
78
94
|
},
|
|
79
95
|
onError: (error) => {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
if (activeClientRef.current !== currentInstance) return;
|
|
86
|
-
optionsRef.current.onError?.(error);
|
|
96
|
+
runOrQueueForActiveInstance(() => {
|
|
97
|
+
optionsRef.current.onError?.(error);
|
|
98
|
+
});
|
|
87
99
|
},
|
|
88
100
|
...initialOptions.tools !== void 0 && { tools: initialOptions.tools },
|
|
101
|
+
...initialOptions.interrupts !== void 0 && { interrupts: initialOptions.interrupts },
|
|
89
102
|
onCustomEvent: (eventType, data, context) => {
|
|
90
|
-
|
|
91
|
-
|
|
103
|
+
runOrQueueForActiveInstance(() => {
|
|
104
|
+
optionsRef.current.onCustomEvent?.(eventType, data, context);
|
|
105
|
+
});
|
|
92
106
|
},
|
|
93
107
|
...options.streamProcessor !== void 0 && { streamProcessor: options.streamProcessor },
|
|
94
108
|
onMessagesChange: (newMessages) => {
|
|
95
|
-
|
|
96
|
-
|
|
109
|
+
runOrQueueForActiveInstance(() => {
|
|
110
|
+
setMessages(newMessages);
|
|
111
|
+
});
|
|
97
112
|
},
|
|
98
113
|
onLoadingChange: (newIsLoading) => {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
114
|
+
runOrQueueForActiveInstance(() => {
|
|
115
|
+
const currentInstance = getActiveInstance();
|
|
116
|
+
if (!currentInstance) return;
|
|
117
|
+
setIsLoading(newIsLoading);
|
|
118
|
+
syncResumeState(currentInstance);
|
|
119
|
+
});
|
|
103
120
|
},
|
|
104
121
|
onErrorChange: (newError) => {
|
|
105
|
-
|
|
106
|
-
|
|
122
|
+
runOrQueueForActiveInstance(() => {
|
|
123
|
+
setError(newError);
|
|
124
|
+
});
|
|
107
125
|
},
|
|
108
126
|
onStatusChange: (status) => {
|
|
109
|
-
|
|
110
|
-
|
|
127
|
+
runOrQueueForActiveInstance(() => {
|
|
128
|
+
setStatus(status);
|
|
129
|
+
});
|
|
111
130
|
},
|
|
112
131
|
onSubscriptionChange: (nextIsSubscribed) => {
|
|
113
|
-
|
|
114
|
-
|
|
132
|
+
runOrQueueForActiveInstance(() => {
|
|
133
|
+
setIsSubscribed(nextIsSubscribed);
|
|
134
|
+
});
|
|
115
135
|
},
|
|
116
136
|
onConnectionStatusChange: (nextStatus) => {
|
|
117
|
-
|
|
118
|
-
|
|
137
|
+
runOrQueueForActiveInstance(() => {
|
|
138
|
+
setConnectionStatus(nextStatus);
|
|
139
|
+
});
|
|
119
140
|
},
|
|
120
141
|
onSessionGeneratingChange: (isGenerating) => {
|
|
121
|
-
|
|
122
|
-
|
|
142
|
+
runOrQueueForActiveInstance(() => {
|
|
143
|
+
setSessionGenerating(isGenerating);
|
|
144
|
+
});
|
|
123
145
|
},
|
|
124
146
|
...optionsRef.current.queue !== void 0 && { queue: optionsRef.current.queue },
|
|
125
147
|
onQueueChange: (nextQueue) => {
|
|
126
|
-
|
|
127
|
-
|
|
148
|
+
runOrQueueForActiveInstance(() => {
|
|
149
|
+
setQueue(nextQueue);
|
|
150
|
+
});
|
|
128
151
|
},
|
|
129
152
|
onRunIdChange: (nextRunId) => {
|
|
130
|
-
|
|
131
|
-
|
|
153
|
+
runOrQueueForActiveInstance(() => {
|
|
154
|
+
setRunId(nextRunId);
|
|
155
|
+
});
|
|
132
156
|
},
|
|
133
157
|
onResumeStateChange: (_nextResumeState, nextPendingInterrupts) => {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
158
|
+
runOrQueueForActiveInstance(() => {
|
|
159
|
+
setInterruptState((current) => ({
|
|
160
|
+
...current,
|
|
161
|
+
interrupts: nextPendingInterrupts,
|
|
162
|
+
pendingInterrupts: nextPendingInterrupts
|
|
163
|
+
}));
|
|
164
|
+
});
|
|
140
165
|
},
|
|
141
|
-
onInterruptStateChange: (nextInterruptState) => {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
166
|
+
onInterruptStateChange: (nextInterruptState, context) => {
|
|
167
|
+
runOrQueueForActiveInstance(() => {
|
|
168
|
+
setInterruptState(nextInterruptState);
|
|
169
|
+
optionsRef.current.onInterruptStateChange?.(nextInterruptState, context);
|
|
170
|
+
});
|
|
145
171
|
}
|
|
146
172
|
});
|
|
147
173
|
instanceHolder.current = instance;
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
return instance;
|
|
174
|
+
return {
|
|
175
|
+
client: instance,
|
|
176
|
+
initialization: initializationState
|
|
177
|
+
};
|
|
154
178
|
}, [clientId, syncResumeState]);
|
|
179
|
+
useEffect(() => {
|
|
180
|
+
activeClientRef.current = client;
|
|
181
|
+
try {
|
|
182
|
+
while (initialization.callbacks.length > 0) {
|
|
183
|
+
if (activeClientRef.current !== client) {
|
|
184
|
+
initialization.callbacks.length = 0;
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
initialization.callbacks.shift()?.();
|
|
188
|
+
}
|
|
189
|
+
} finally {
|
|
190
|
+
initialization.ready = true;
|
|
191
|
+
}
|
|
192
|
+
}, [client, initialization]);
|
|
155
193
|
useEffect(() => {
|
|
156
194
|
const clientMessages = client.getMessages();
|
|
157
195
|
if (clientMessages !== messagesRef.current) setMessages(clientMessages);
|
|
@@ -195,7 +233,6 @@ function useChat(options) {
|
|
|
195
233
|
clearTimeout(cleanupInvalidationRef.current);
|
|
196
234
|
cleanupInvalidationRef.current = null;
|
|
197
235
|
}
|
|
198
|
-
activeClientRef.current = client;
|
|
199
236
|
client.mountDevtools();
|
|
200
237
|
syncResumeState(client);
|
|
201
238
|
return () => {
|
package/dist/esm/use-chat.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-chat.js","names":[],"sources":["../../src/use-chat.ts"],"sourcesContent":["import { ChatClient } from '@tanstack/ai-client'\nimport { createChatDevtoolsBridge } from '@tanstack/ai-client/devtools'\nimport { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react'\nimport type {\n AnyClientTool,\n InferSchemaType,\n ModelMessage,\n RunAgentResumeItem,\n SchemaInput,\n StreamChunk,\n} from '@tanstack/ai/client'\nimport type {\n ChatClientState,\n ChatInterrupt,\n ChatInterruptState,\n ChatResumeState,\n ConnectionStatus,\n InferredClientContext,\n QueuedMessage,\n SendMessageOptions,\n StructuredOutputPart,\n} from '@tanstack/ai-client'\n\nimport type {\n DeepPartial,\n MultimodalContent,\n UIMessage,\n UseChatOptions,\n UseChatReturn,\n} from './types'\n\nconst EMPTY_INTERRUPTS = Object.freeze([])\nconst EMPTY_INTERRUPT_ERRORS = Object.freeze([])\n\nexport function useChat<\n const TTools extends ReadonlyArray<AnyClientTool> = any,\n TSchema extends SchemaInput | undefined = undefined,\n TContext = InferredClientContext<TTools>,\n>(\n options: UseChatOptions<TTools, TSchema, TContext>,\n): UseChatReturn<TTools, TSchema> {\n // The hook's identity is its `threadId` — also the persistence key, so a\n // reload with the same `threadId` restores the same conversation. `hookId` is\n // only a stable fallback for React's client-recreation keying when no\n // `threadId` is given (an ephemeral chat), never a persistence key.\n const hookId = useId()\n const clientId = options.threadId ?? hookId\n\n const [messages, setMessages] = useState<Array<UIMessage<TTools>>>(\n options.initialMessages || [],\n )\n const [isLoading, setIsLoading] = useState(false)\n const [error, setError] = useState<Error | undefined>(undefined)\n const [status, setStatus] = useState<ChatClientState>('ready')\n const [isSubscribed, setIsSubscribed] = useState(false)\n const [connectionStatus, setConnectionStatus] =\n useState<ConnectionStatus>('disconnected')\n const [sessionGenerating, setSessionGenerating] = useState(false)\n const [queue, setQueue] = useState<Array<QueuedMessage>>([])\n const [runId, setRunId] = useState<string | null>(null)\n const [interruptState, setInterruptState] = useState<\n ChatInterruptState<TTools>\n >(() => ({\n interrupts: EMPTY_INTERRUPTS,\n pendingInterrupts: EMPTY_INTERRUPTS,\n interruptErrors: EMPTY_INTERRUPT_ERRORS,\n resuming: false,\n }))\n\n type Partial = DeepPartial<InferSchemaType<NonNullable<TSchema>>>\n type Final = InferSchemaType<NonNullable<TSchema>>\n\n // Track current messages in a ref to preserve them when client is recreated\n const messagesRef = useRef<Array<UIMessage<TTools>>>(\n options.initialMessages || [],\n )\n const isFirstMountRef = useRef(true)\n const subscribedRef = useRef(false)\n const activeClientRef = useRef<ChatClient | null>(null)\n const cleanupInvalidationRef = useRef<ReturnType<typeof setTimeout> | null>(\n null,\n )\n const cleanupDisposalRef = useRef<{\n client: ChatClient\n timeout: ReturnType<typeof setTimeout>\n } | null>(null)\n\n // Update ref synchronously during render so it's always current when useMemo runs.\n messagesRef.current = messages\n\n // Track current options in a ref to avoid recreating client when options change\n const optionsRef = useRef<UseChatOptions<TTools, TSchema, TContext>>(options)\n optionsRef.current = options\n\n const syncResumeState = useCallback((target: ChatClient | null) => {\n if (!target) return\n setRunId(target.getCurrentRunId())\n setInterruptState(target.getInterruptState())\n }, [])\n\n // Create ChatClient instance with callbacks to sync state\n const client = useMemo(() => {\n const messagesToUse = options.initialMessages || []\n isFirstMountRef.current = false\n\n // Build options with conditional spreads for fields whose source\n // type is `T | undefined` but the ChatClient target uses a strict\n // optional (`field?: T`) — `exactOptionalPropertyTypes` rejects\n // assigning `undefined` to those, so we omit the key when absent.\n const initialOptions = optionsRef.current\n const transport = initialOptions.connection\n ? { connection: initialOptions.connection }\n : { fetcher: initialOptions.fetcher }\n\n const instanceHolder: {\n current: ChatClient<TTools, TContext> | undefined\n } = { current: undefined }\n const getActiveInstance = () => {\n const currentInstance = instanceHolder.current\n if (!currentInstance || activeClientRef.current !== currentInstance) {\n return undefined\n }\n return currentInstance\n }\n const pendingInitializationErrors: Array<Error> = []\n const instance = new ChatClient<TTools, TContext>({\n devtoolsBridgeFactory: createChatDevtoolsBridge,\n ...transport,\n initialMessages: messagesToUse,\n ...(initialOptions.body !== undefined && { body: initialOptions.body }),\n ...(initialOptions.threadId !== undefined && {\n threadId: initialOptions.threadId,\n }),\n ...(initialOptions.forwardedProps !== undefined && {\n forwardedProps: initialOptions.forwardedProps,\n }),\n ...(initialOptions.persistence !== undefined && {\n persistence: initialOptions.persistence,\n }),\n ...(initialOptions.initialResumeSnapshot !== undefined && {\n initialResumeSnapshot: initialOptions.initialResumeSnapshot,\n }),\n ...(initialOptions.context !== undefined && {\n context: initialOptions.context,\n }),\n devtools: {\n ...initialOptions.devtools,\n framework: 'react',\n hookName: 'useChat',\n outputKind: initialOptions.outputSchema ? 'structured' : 'chat',\n },\n onResponse: (response) => {\n if (!getActiveInstance()) return\n void optionsRef.current.onResponse?.(response)\n },\n onChunk: (chunk: StreamChunk) => {\n if (!getActiveInstance()) return\n optionsRef.current.onChunk?.(chunk)\n },\n onFinish: (message: UIMessage<TTools>) => {\n if (!getActiveInstance()) return\n optionsRef.current.onFinish?.(message)\n },\n onError: (error: Error) => {\n const currentInstance = instanceHolder.current\n if (!currentInstance) {\n pendingInitializationErrors.push(error)\n return\n }\n if (activeClientRef.current !== currentInstance) return\n optionsRef.current.onError?.(error)\n },\n ...(initialOptions.tools !== undefined && {\n tools: initialOptions.tools,\n }),\n onCustomEvent: (eventType, data, context) => {\n if (!getActiveInstance()) return\n optionsRef.current.onCustomEvent?.(eventType, data, context)\n },\n ...(options.streamProcessor !== undefined && {\n streamProcessor: options.streamProcessor,\n }),\n onMessagesChange: (newMessages: Array<UIMessage<TTools>>) => {\n if (!getActiveInstance()) return\n setMessages(newMessages)\n },\n onLoadingChange: (newIsLoading: boolean) => {\n const currentInstance = getActiveInstance()\n if (!currentInstance) return\n setIsLoading(newIsLoading)\n syncResumeState(currentInstance)\n },\n onErrorChange: (newError: Error | undefined) => {\n if (!getActiveInstance()) return\n setError(newError)\n },\n onStatusChange: (status: ChatClientState) => {\n if (!getActiveInstance()) return\n setStatus(status)\n },\n onSubscriptionChange: (nextIsSubscribed: boolean) => {\n if (!getActiveInstance()) return\n setIsSubscribed(nextIsSubscribed)\n },\n onConnectionStatusChange: (nextStatus: ConnectionStatus) => {\n if (!getActiveInstance()) return\n setConnectionStatus(nextStatus)\n },\n onSessionGeneratingChange: (isGenerating: boolean) => {\n if (!getActiveInstance()) return\n setSessionGenerating(isGenerating)\n },\n ...(optionsRef.current.queue !== undefined && {\n queue: optionsRef.current.queue,\n }),\n onQueueChange: (nextQueue: Array<QueuedMessage>) => {\n if (activeClientRef.current !== instance) return\n setQueue(nextQueue)\n },\n onRunIdChange: (nextRunId) => {\n if (!getActiveInstance()) return\n setRunId(nextRunId)\n },\n onResumeStateChange: (_nextResumeState, nextPendingInterrupts) => {\n if (!getActiveInstance()) return\n setInterruptState((current) => ({\n ...current,\n interrupts: nextPendingInterrupts,\n pendingInterrupts: nextPendingInterrupts,\n }))\n },\n onInterruptStateChange: (nextInterruptState) => {\n if (!getActiveInstance()) return\n setInterruptState(nextInterruptState)\n optionsRef.current.onInterruptStateChange?.(nextInterruptState)\n },\n })\n instanceHolder.current = instance\n activeClientRef.current = instance\n for (const error of pendingInitializationErrors) {\n if (activeClientRef.current !== instance) break\n optionsRef.current.onError?.(error)\n }\n return instance\n }, [clientId, syncResumeState])\n\n useEffect(() => {\n const clientMessages = client.getMessages()\n if (clientMessages !== messagesRef.current) {\n setMessages(clientMessages)\n }\n }, [client])\n\n // Sync each wire-payload slot in its own effect so an unrelated option\n // changing doesn't re-run the others. `updateOptions` declares strict-optional\n // fields and rejects explicit `undefined` under EOPT, so guard the optional\n // slots before passing them.\n useEffect(() => {\n client.updateOptions({ body: options.body })\n }, [client, options.body])\n\n useEffect(() => {\n if (options.forwardedProps !== undefined) {\n client.updateOptions({ forwardedProps: options.forwardedProps })\n }\n }, [client, options.forwardedProps])\n\n useEffect(() => {\n if (options.tools !== undefined) {\n client.updateOptions({ tools: options.tools })\n }\n }, [client, options.tools])\n\n useEffect(() => {\n client.updateOptions({ context: options.context })\n }, [client, options.context])\n\n useEffect(() => {\n if (options.queue !== undefined) {\n client.updateOptions({ queue: options.queue })\n }\n }, [client, options.queue])\n\n useEffect(() => {\n if (options.live) {\n client.subscribe()\n subscribedRef.current = true\n } else if (subscribedRef.current) {\n // Only tear down a subscription we actually started. Calling\n // `unsubscribe()` on initial mount (when `live` was never enabled) would\n // abort an in-flight delivery resume — `resumeInFlightRun` is kicked off\n // in the client constructor, and `unsubscribe()` cancels the shared\n // in-flight stream — so a mid-stream reload would drop its rejoin before\n // it delivers a single chunk. This is exactly why a reload froze instead\n // of continuing.\n client.unsubscribe()\n subscribedRef.current = false\n }\n }, [client, options.live])\n\n // ONLY THE VIEW ON SCREEN HOLDS A STREAM.\n //\n // A page can own many chats — 40 sandboxes, 40 conversations — and a browser\n // allows only ~6 connections per origin. One long-lived stream per chat reaches\n // that ceiling after a handful of views, and every request after it QUEUES:\n // measured, an in-page fetch took over two minutes while the same request from\n // outside the browser took 17ms. So the connection follows the view.\n //\n // Immediate, not deferred: the deferred teardown below can be skipped when the\n // same client remounts, which is right for disposal but useless for a\n // connection. `attach` is idempotent and `detach` keeps the transcript and the\n // resume pointer, so a Strict Mode remount is just detach-then-attach and the\n // run is picked straight back up from the durable log.\n useEffect(() => {\n client.attach()\n return () => {\n client.detach()\n }\n }, [client])\n\n useEffect(() => {\n if (cleanupDisposalRef.current?.client === client) {\n clearTimeout(cleanupDisposalRef.current.timeout)\n cleanupDisposalRef.current = null\n }\n if (cleanupInvalidationRef.current) {\n clearTimeout(cleanupInvalidationRef.current)\n cleanupInvalidationRef.current = null\n }\n activeClientRef.current = client\n client.mountDevtools()\n // Delivery-durability resume is transparent: the resumable SSE connection\n // adapter re-attaches via the browser's native Last-Event-ID on reconnect.\n // We only seed interrupt (state) resume from the client here.\n syncResumeState(client)\n\n return () => {\n cleanupInvalidationRef.current = setTimeout(() => {\n if (activeClientRef.current === client) {\n activeClientRef.current = null\n }\n cleanupInvalidationRef.current = null\n }, 0)\n // Soft cleanup only: do NOT stop/unsubscribe here. React Strict Mode\n // remounts fire this cleanup then re-attach the same client one tick\n // later; calling `stop()` would abort a constructor rejoin\n // (`resumeInFlightRun`) and can wipe the durable resume pointer before\n // the first chunk. Real teardown lives in the deferred dispose path\n // below, which only runs when the client is not remounted.\n // Subscribe/unsubscribe on `options.live` is still owned by the\n // dedicated effect above for live toggles.\n const disposal = {\n client,\n timeout: setTimeout(() => {\n if (optionsRef.current.live) {\n client.unsubscribe()\n } else {\n client.stop()\n }\n client.dispose()\n if (cleanupDisposalRef.current === disposal) {\n cleanupDisposalRef.current = null\n }\n }, 0),\n }\n cleanupDisposalRef.current = disposal\n }\n }, [client, syncResumeState])\n\n const sendMessage = useCallback(\n async (\n content: string | MultimodalContent,\n sendOptions?: SendMessageOptions,\n ) => {\n try {\n await client.sendMessage(content, undefined, sendOptions)\n } finally {\n syncResumeState(client)\n }\n },\n [client, syncResumeState],\n )\n\n const cancelQueued = useCallback(\n (id: string) => {\n client.cancelQueued(id)\n },\n [client, syncResumeState],\n )\n\n const append = useCallback(\n async (message: ModelMessage | UIMessage) => {\n try {\n await client.append(message)\n } finally {\n syncResumeState(client)\n }\n },\n [client, syncResumeState],\n )\n\n const reload = useCallback(async () => {\n try {\n await client.reload()\n } finally {\n syncResumeState(client)\n }\n }, [client, syncResumeState])\n\n const stop = useCallback(() => {\n client.stop()\n }, [client])\n\n const clear = useCallback(() => {\n client.clear()\n syncResumeState(client)\n }, [client, syncResumeState])\n\n const setMessagesManually = useCallback(\n (newMessages: Array<UIMessage<TTools>>) => {\n client.setMessagesManually(newMessages)\n },\n [client],\n )\n\n const addToolResult = useCallback(\n async (result: {\n toolCallId: string\n tool: string\n output: any\n state?: 'output-available' | 'output-error'\n errorText?: string\n }) => {\n await client.addToolResult(result)\n },\n [client],\n )\n\n const addToolApprovalResponse = useCallback(\n async (response: { id: string; approved: boolean }) => {\n await client.addToolApprovalResponse(response)\n syncResumeState(client)\n },\n [client, syncResumeState],\n )\n\n const resumeInterrupts = useCallback(\n async (resumeItems: Array<RunAgentResumeItem>, state?: ChatResumeState) => {\n const result = await client.resumeInterrupts(\n resumeItems,\n state ?? undefined,\n )\n syncResumeState(client)\n return result\n },\n [client, syncResumeState],\n )\n\n const resolveInterrupts = useCallback(\n (\n resolution: boolean | ((interrupt: ChatInterrupt<TTools>) => undefined),\n ) => {\n if (typeof resolution === 'boolean') {\n client.resolveInterrupts(resolution)\n } else {\n client.resolveInterrupts(resolution)\n }\n },\n [client],\n )\n\n const cancelInterrupts = useCallback(() => {\n client.cancelInterrupts()\n }, [client])\n\n const retryInterrupts = useCallback(() => {\n client.retryInterrupts()\n }, [client])\n\n const resumeInterruptsUnsafe = useCallback(\n (resumeItems: Array<RunAgentResumeItem>, state?: ChatResumeState) =>\n client.resumeInterruptsUnsafe(resumeItems, state),\n [client],\n )\n\n // The \"active\" structured-output part is the one on the assistant message\n // that follows the latest user message. No such message exists between\n // sendMessage() and the first chunk, so partial/final naturally read as\n // cleared. Historical parts on earlier assistant messages remain available\n // via `messages` directly.\n //\n // When there is NO user message yet (e.g. `initialMessages` contains only\n // a stale assistant turn or a system prompt) we deliberately return null\n // rather than scanning historical assistants — otherwise a `final` from a\n // previous session would leak into the hook value on first render.\n const renderedMessages = client.getMessages()\n\n const activeStructuredPart = useMemo<StructuredOutputPart | null>(() => {\n let lastUserIndex = -1\n for (let i = renderedMessages.length - 1; i >= 0; i--) {\n if (renderedMessages[i]?.role === 'user') {\n lastUserIndex = i\n break\n }\n }\n if (lastUserIndex === -1) return null\n for (let i = renderedMessages.length - 1; i > lastUserIndex; i--) {\n const m = renderedMessages[i]\n if (m?.role !== 'assistant') continue\n const part = m.parts.find(\n (p): p is StructuredOutputPart => p.type === 'structured-output',\n )\n if (part) return part\n }\n return null\n }, [renderedMessages])\n\n const partial = useMemo<Partial>(() => {\n if (!activeStructuredPart) return {} as Partial\n const v = activeStructuredPart.partial ?? activeStructuredPart.data\n return (v ?? {}) as Partial\n }, [activeStructuredPart])\n\n const final = useMemo<Final | null>(() => {\n if (!activeStructuredPart || activeStructuredPart.status !== 'complete') {\n return null\n }\n return activeStructuredPart.data as Final\n }, [activeStructuredPart])\n\n // The runtime shape unconditionally exposes partial/final; the public\n // return type hides them when no outputSchema was supplied. TS can't\n // structurally narrow across that conditional, so the `as` is the seam.\n // oxlint-disable-next-line eslint-js/no-restricted-syntax -- hook return shape diverges from generic UseChatReturn<TTools, TSchema> due to conditional type on TSchema; TS can't structurally narrow\n return {\n messages: renderedMessages,\n sendMessage,\n append,\n reload,\n stop,\n isLoading,\n error,\n status,\n isSubscribed,\n connectionStatus,\n sessionGenerating,\n setMessages: setMessagesManually,\n clear,\n addToolResult,\n addToolApprovalResponse,\n queue,\n cancelQueued,\n runId,\n interrupts: interruptState.interrupts,\n pendingInterrupts: interruptState.pendingInterrupts,\n interruptErrors: interruptState.interruptErrors,\n resuming: interruptState.resuming,\n resolveInterrupts,\n cancelInterrupts,\n retryInterrupts,\n resumeInterruptsUnsafe,\n resumeInterrupts,\n partial,\n final,\n } as unknown as UseChatReturn<TTools, TSchema>\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"use-chat.js","names":[],"sources":["../../src/use-chat.ts"],"sourcesContent":["import { ChatClient } from '@tanstack/ai-client'\nimport { createChatDevtoolsBridge } from '@tanstack/ai-client/devtools'\nimport { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react'\nimport type {\n AnyClientTool,\n InferSchemaType,\n InterruptDefinition,\n ModelMessage,\n RunAgentResumeItem,\n SchemaInput,\n StreamChunk,\n} from '@tanstack/ai/client'\nimport type {\n ChatClientState,\n ResolvableChatInterrupt,\n ChatInterruptState,\n ChatResumeState,\n ConnectionStatus,\n InferredClientContext,\n QueuedMessage,\n SendMessageOptions,\n StructuredOutputPart,\n} from '@tanstack/ai-client'\n\nimport type {\n DeepPartial,\n MultimodalContent,\n UIMessage,\n UseChatOptions,\n UseChatReturn,\n} from './types'\n\nconst EMPTY_INTERRUPTS = Object.freeze([])\nconst EMPTY_INTERRUPT_ERRORS = Object.freeze([])\n\nexport function useChat<\n const TTools extends ReadonlyArray<AnyClientTool> = any,\n TSchema extends SchemaInput | undefined = undefined,\n TContext = InferredClientContext<TTools>,\n const TInterrupts extends ReadonlyArray<\n InterruptDefinition<any, any, any, any>\n > = readonly [],\n>(\n options: UseChatOptions<TTools, TSchema, TContext, TInterrupts>,\n): UseChatReturn<TTools, TSchema, TInterrupts> {\n // The hook's identity is its `threadId`. Reload with the same `threadId`\n // restores the same conversation. `hookId` is only a React recreation key\n // when no `threadId` is given. It is never sent on the wire.\n const hookId = useId()\n const clientId = options.threadId ?? hookId\n\n const [messages, setMessages] = useState<Array<UIMessage<TTools>>>(\n options.initialMessages || [],\n )\n const [isLoading, setIsLoading] = useState(false)\n const [error, setError] = useState<Error | undefined>(undefined)\n const [status, setStatus] = useState<ChatClientState>('ready')\n const [isSubscribed, setIsSubscribed] = useState(false)\n const [connectionStatus, setConnectionStatus] =\n useState<ConnectionStatus>('disconnected')\n const [sessionGenerating, setSessionGenerating] = useState(false)\n const [queue, setQueue] = useState<Array<QueuedMessage>>([])\n const [runId, setRunId] = useState<string | null>(null)\n const [interruptState, setInterruptState] = useState<\n ChatInterruptState<TTools, TInterrupts>\n >(() => ({\n interrupts: EMPTY_INTERRUPTS,\n pendingInterrupts: EMPTY_INTERRUPTS,\n interruptErrors: EMPTY_INTERRUPT_ERRORS,\n resuming: false,\n }))\n\n type Partial = DeepPartial<InferSchemaType<NonNullable<TSchema>>>\n type Final = InferSchemaType<NonNullable<TSchema>>\n\n // Track current messages in a ref to preserve them when client is recreated\n const messagesRef = useRef<Array<UIMessage<TTools>>>(\n options.initialMessages || [],\n )\n const isFirstMountRef = useRef(true)\n const subscribedRef = useRef(false)\n const activeClientRef = useRef<ChatClient | null>(null)\n const cleanupInvalidationRef = useRef<ReturnType<typeof setTimeout> | null>(\n null,\n )\n const cleanupDisposalRef = useRef<{\n client: ChatClient\n timeout: ReturnType<typeof setTimeout>\n } | null>(null)\n\n // Update ref synchronously during render so it's always current when useMemo runs.\n messagesRef.current = messages\n\n // Track current options in a ref to avoid recreating client when options change\n const optionsRef =\n useRef<UseChatOptions<TTools, TSchema, TContext, TInterrupts>>(options)\n optionsRef.current = options\n\n const syncResumeState = useCallback((target: ChatClient | null) => {\n if (!target) return\n setRunId(target.getCurrentRunId())\n setInterruptState(target.getInterruptState())\n }, [])\n\n // Create ChatClient instance with callbacks to sync state\n const { client, initialization } = useMemo(() => {\n const messagesToUse = options.initialMessages || []\n isFirstMountRef.current = false\n\n // Build options with conditional spreads for fields whose source\n // type is `T | undefined` but the ChatClient target uses a strict\n // optional (`field?: T`) — `exactOptionalPropertyTypes` rejects\n // assigning `undefined` to those, so we omit the key when absent.\n const initialOptions = optionsRef.current\n const transport = initialOptions.connection\n ? { connection: initialOptions.connection }\n : { fetcher: initialOptions.fetcher }\n\n const instanceHolder: {\n current: ChatClient<TTools, TContext, TInterrupts> | undefined\n } = { current: undefined }\n const getActiveInstance = () => {\n const currentInstance = instanceHolder.current\n if (!currentInstance || activeClientRef.current !== currentInstance) {\n return undefined\n }\n return currentInstance\n }\n // ChatClient may publish while its constructor is running or while async\n // persistence resolves before commit. Preserve those exact notifications\n // until this render commits; invoking them here would run state setters and\n // user callbacks for a client React may abandon.\n const initializationState = {\n ready: false,\n callbacks: [] as Array<() => void>,\n }\n const runOrQueueForActiveInstance = (callback: () => void) => {\n if (!initializationState.ready) {\n initializationState.callbacks.push(callback)\n return\n }\n const currentInstance = instanceHolder.current\n if (!currentInstance || activeClientRef.current !== currentInstance)\n return\n callback()\n }\n const instance = new ChatClient<TTools, TContext, TInterrupts>({\n devtoolsBridgeFactory: createChatDevtoolsBridge,\n ...transport,\n initialMessages: messagesToUse,\n ...(typeof initialOptions.threadId === 'string' &&\n initialOptions.persistence\n ? {\n persistence: initialOptions.persistence,\n threadId: initialOptions.threadId,\n }\n : {\n ...(initialOptions.threadId !== undefined && {\n threadId: initialOptions.threadId,\n }),\n }),\n ...(initialOptions.body !== undefined && { body: initialOptions.body }),\n ...(initialOptions.forwardedProps !== undefined && {\n forwardedProps: initialOptions.forwardedProps,\n }),\n ...(initialOptions.initialResumeSnapshot !== undefined && {\n initialResumeSnapshot: initialOptions.initialResumeSnapshot,\n }),\n ...(initialOptions.context !== undefined && {\n context: initialOptions.context,\n }),\n devtools: {\n ...initialOptions.devtools,\n framework: 'react',\n hookName: 'useChat',\n outputKind: initialOptions.outputSchema ? 'structured' : 'chat',\n },\n onResponse: (response) => {\n // ChatClient awaits this return value. Queuing would drop the promise.\n if (!getActiveInstance()) return\n return optionsRef.current.onResponse?.(response)\n },\n onChunk: (chunk: StreamChunk) => {\n runOrQueueForActiveInstance(() => {\n optionsRef.current.onChunk?.(chunk)\n })\n },\n onFinish: (message: UIMessage<TTools>) => {\n runOrQueueForActiveInstance(() => {\n optionsRef.current.onFinish?.(message)\n })\n },\n onError: (error: Error) => {\n runOrQueueForActiveInstance(() => {\n optionsRef.current.onError?.(error)\n })\n },\n ...(initialOptions.tools !== undefined && {\n tools: initialOptions.tools,\n }),\n ...(initialOptions.interrupts !== undefined && {\n interrupts: initialOptions.interrupts,\n }),\n onCustomEvent: (eventType, data, context) => {\n runOrQueueForActiveInstance(() => {\n optionsRef.current.onCustomEvent?.(eventType, data, context)\n })\n },\n ...(options.streamProcessor !== undefined && {\n streamProcessor: options.streamProcessor,\n }),\n onMessagesChange: (newMessages: Array<UIMessage<TTools>>) => {\n runOrQueueForActiveInstance(() => {\n setMessages(newMessages)\n })\n },\n onLoadingChange: (newIsLoading: boolean) => {\n runOrQueueForActiveInstance(() => {\n const currentInstance = getActiveInstance()\n if (!currentInstance) return\n setIsLoading(newIsLoading)\n syncResumeState(currentInstance)\n })\n },\n onErrorChange: (newError: Error | undefined) => {\n runOrQueueForActiveInstance(() => {\n setError(newError)\n })\n },\n onStatusChange: (status: ChatClientState) => {\n runOrQueueForActiveInstance(() => {\n setStatus(status)\n })\n },\n onSubscriptionChange: (nextIsSubscribed: boolean) => {\n runOrQueueForActiveInstance(() => {\n setIsSubscribed(nextIsSubscribed)\n })\n },\n onConnectionStatusChange: (nextStatus: ConnectionStatus) => {\n runOrQueueForActiveInstance(() => {\n setConnectionStatus(nextStatus)\n })\n },\n onSessionGeneratingChange: (isGenerating: boolean) => {\n runOrQueueForActiveInstance(() => {\n setSessionGenerating(isGenerating)\n })\n },\n ...(optionsRef.current.queue !== undefined && {\n queue: optionsRef.current.queue,\n }),\n onQueueChange: (nextQueue: Array<QueuedMessage>) => {\n runOrQueueForActiveInstance(() => {\n setQueue(nextQueue)\n })\n },\n onRunIdChange: (nextRunId) => {\n runOrQueueForActiveInstance(() => {\n setRunId(nextRunId)\n })\n },\n onResumeStateChange: (_nextResumeState, nextPendingInterrupts) => {\n runOrQueueForActiveInstance(() => {\n setInterruptState((current) => ({\n ...current,\n interrupts: nextPendingInterrupts,\n pendingInterrupts: nextPendingInterrupts,\n }))\n })\n },\n onInterruptStateChange: (nextInterruptState, context) => {\n runOrQueueForActiveInstance(() => {\n setInterruptState(nextInterruptState)\n optionsRef.current.onInterruptStateChange?.(\n nextInterruptState,\n context,\n )\n })\n },\n })\n instanceHolder.current = instance\n return { client: instance, initialization: initializationState }\n }, [clientId, syncResumeState])\n\n useEffect(() => {\n activeClientRef.current = client\n try {\n // Keep initialization closed while draining so callbacks published by a\n // queued callback are appended and delivered in the same commit.\n while (initialization.callbacks.length > 0) {\n if (activeClientRef.current !== client) {\n initialization.callbacks.length = 0\n break\n }\n initialization.callbacks.shift()?.()\n }\n } finally {\n // A throw from a queued user callback must not leave the queue closed.\n initialization.ready = true\n }\n }, [client, initialization])\n\n useEffect(() => {\n const clientMessages = client.getMessages()\n if (clientMessages !== messagesRef.current) {\n setMessages(clientMessages)\n }\n }, [client])\n\n // Sync each wire-payload slot in its own effect so an unrelated option\n // changing doesn't re-run the others. `updateOptions` declares strict-optional\n // fields and rejects explicit `undefined` under EOPT, so guard the optional\n // slots before passing them.\n useEffect(() => {\n client.updateOptions({ body: options.body })\n }, [client, options.body])\n\n useEffect(() => {\n if (options.forwardedProps !== undefined) {\n client.updateOptions({ forwardedProps: options.forwardedProps })\n }\n }, [client, options.forwardedProps])\n\n useEffect(() => {\n if (options.tools !== undefined) {\n client.updateOptions({ tools: options.tools })\n }\n }, [client, options.tools])\n\n useEffect(() => {\n client.updateOptions({ context: options.context })\n }, [client, options.context])\n\n useEffect(() => {\n if (options.queue !== undefined) {\n client.updateOptions({ queue: options.queue })\n }\n }, [client, options.queue])\n\n useEffect(() => {\n if (options.live) {\n client.subscribe()\n subscribedRef.current = true\n } else if (subscribedRef.current) {\n // Only tear down a subscription we actually started. Calling\n // `unsubscribe()` on initial mount (when `live` was never enabled) would\n // abort an in-flight delivery resume — `resumeInFlightRun` is kicked off\n // in the client constructor, and `unsubscribe()` cancels the shared\n // in-flight stream — so a mid-stream reload would drop its rejoin before\n // it delivers a single chunk. This is exactly why a reload froze instead\n // of continuing.\n client.unsubscribe()\n subscribedRef.current = false\n }\n }, [client, options.live])\n\n // ONLY THE VIEW ON SCREEN HOLDS A STREAM.\n //\n // A page can own many chats — 40 sandboxes, 40 conversations — and a browser\n // allows only ~6 connections per origin. One long-lived stream per chat reaches\n // that ceiling after a handful of views, and every request after it QUEUES:\n // measured, an in-page fetch took over two minutes while the same request from\n // outside the browser took 17ms. So the connection follows the view.\n //\n // Immediate, not deferred: the deferred teardown below can be skipped when the\n // same client remounts, which is right for disposal but useless for a\n // connection. `attach` is idempotent and `detach` keeps the transcript and the\n // resume pointer, so a Strict Mode remount is just detach-then-attach and the\n // run is picked straight back up from the durable log.\n useEffect(() => {\n client.attach()\n return () => {\n client.detach()\n }\n }, [client])\n\n useEffect(() => {\n if (cleanupDisposalRef.current?.client === client) {\n clearTimeout(cleanupDisposalRef.current.timeout)\n cleanupDisposalRef.current = null\n }\n if (cleanupInvalidationRef.current) {\n clearTimeout(cleanupInvalidationRef.current)\n cleanupInvalidationRef.current = null\n }\n client.mountDevtools()\n // Delivery-durability resume is transparent: the resumable SSE connection\n // adapter re-attaches via the browser's native Last-Event-ID on reconnect.\n // We only seed interrupt (state) resume from the client here.\n syncResumeState(client)\n\n return () => {\n cleanupInvalidationRef.current = setTimeout(() => {\n if (activeClientRef.current === client) {\n activeClientRef.current = null\n }\n cleanupInvalidationRef.current = null\n }, 0)\n // Soft cleanup only: do NOT stop/unsubscribe here. React Strict Mode\n // remounts fire this cleanup then re-attach the same client one tick\n // later; calling `stop()` would abort a constructor rejoin\n // (`resumeInFlightRun`) and can wipe the durable resume pointer before\n // the first chunk. Real teardown lives in the deferred dispose path\n // below, which only runs when the client is not remounted.\n // Subscribe/unsubscribe on `options.live` is still owned by the\n // dedicated effect above for live toggles.\n const disposal = {\n client,\n timeout: setTimeout(() => {\n if (optionsRef.current.live) {\n client.unsubscribe()\n } else {\n client.stop()\n }\n client.dispose()\n if (cleanupDisposalRef.current === disposal) {\n cleanupDisposalRef.current = null\n }\n }, 0),\n }\n cleanupDisposalRef.current = disposal\n }\n }, [client, syncResumeState])\n\n const sendMessage = useCallback(\n async (\n content: string | MultimodalContent,\n sendOptions?: SendMessageOptions,\n ) => {\n try {\n await client.sendMessage(content, undefined, sendOptions)\n } finally {\n syncResumeState(client)\n }\n },\n [client, syncResumeState],\n )\n\n const cancelQueued = useCallback(\n (id: string) => {\n client.cancelQueued(id)\n },\n [client, syncResumeState],\n )\n\n const append = useCallback(\n async (message: ModelMessage | UIMessage) => {\n try {\n await client.append(message)\n } finally {\n syncResumeState(client)\n }\n },\n [client, syncResumeState],\n )\n\n const reload = useCallback(async () => {\n try {\n await client.reload()\n } finally {\n syncResumeState(client)\n }\n }, [client, syncResumeState])\n\n const stop = useCallback(() => {\n client.stop()\n }, [client])\n\n const clear = useCallback(() => {\n client.clear()\n syncResumeState(client)\n }, [client, syncResumeState])\n\n const setMessagesManually = useCallback(\n (newMessages: Array<UIMessage<TTools>>) => {\n client.setMessagesManually(newMessages)\n },\n [client],\n )\n\n const addToolResult = useCallback(\n async (result: {\n toolCallId: string\n tool: string\n output: any\n state?: 'output-available' | 'output-error'\n errorText?: string\n }) => {\n await client.addToolResult(result)\n },\n [client],\n )\n\n const addToolApprovalResponse = useCallback(\n async (response: { id: string; approved: boolean }) => {\n await client.addToolApprovalResponse(response)\n syncResumeState(client)\n },\n [client, syncResumeState],\n )\n\n const resumeInterrupts = useCallback(\n async (resumeItems: Array<RunAgentResumeItem>, state?: ChatResumeState) => {\n const result = await client.resumeInterrupts(\n resumeItems,\n state ?? undefined,\n )\n syncResumeState(client)\n return result\n },\n [client, syncResumeState],\n )\n\n const resolveInterrupts = useCallback(\n (\n resolution:\n | boolean\n | ((\n interrupt: ResolvableChatInterrupt<TTools, TInterrupts>,\n ) => undefined),\n ) => {\n if (typeof resolution === 'boolean') {\n client.resolveInterrupts(resolution)\n } else {\n client.resolveInterrupts(resolution)\n }\n },\n [client],\n )\n\n const cancelInterrupts = useCallback(() => {\n client.cancelInterrupts()\n }, [client])\n\n const retryInterrupts = useCallback(() => {\n client.retryInterrupts()\n }, [client])\n\n const resumeInterruptsUnsafe = useCallback(\n (resumeItems: Array<RunAgentResumeItem>, state?: ChatResumeState) =>\n client.resumeInterruptsUnsafe(resumeItems, state),\n [client],\n )\n\n // The \"active\" structured-output part is the one on the assistant message\n // that follows the latest user message. No such message exists between\n // sendMessage() and the first chunk, so partial/final naturally read as\n // cleared. Historical parts on earlier assistant messages remain available\n // via `messages` directly.\n //\n // When there is NO user message yet (e.g. `initialMessages` contains only\n // a stale assistant turn or a system prompt) we deliberately return null\n // rather than scanning historical assistants — otherwise a `final` from a\n // previous session would leak into the hook value on first render.\n const renderedMessages = client.getMessages()\n\n const activeStructuredPart = useMemo<StructuredOutputPart | null>(() => {\n let lastUserIndex = -1\n for (let i = renderedMessages.length - 1; i >= 0; i--) {\n if (renderedMessages[i]?.role === 'user') {\n lastUserIndex = i\n break\n }\n }\n if (lastUserIndex === -1) return null\n for (let i = renderedMessages.length - 1; i > lastUserIndex; i--) {\n const m = renderedMessages[i]\n if (m?.role !== 'assistant') continue\n const part = m.parts.find(\n (p): p is StructuredOutputPart => p.type === 'structured-output',\n )\n if (part) return part\n }\n return null\n }, [renderedMessages])\n\n const partial = useMemo<Partial>(() => {\n if (!activeStructuredPart) return {} as Partial\n const v = activeStructuredPart.partial ?? activeStructuredPart.data\n return (v ?? {}) as Partial\n }, [activeStructuredPart])\n\n const final = useMemo<Final | null>(() => {\n if (!activeStructuredPart || activeStructuredPart.status !== 'complete') {\n return null\n }\n return activeStructuredPart.data as Final\n }, [activeStructuredPart])\n\n // The runtime shape unconditionally exposes partial/final; the public\n // return type hides them when no outputSchema was supplied. TS can't\n // structurally narrow across that conditional, so the `as` is the seam.\n // oxlint-disable-next-line eslint-js/no-restricted-syntax -- hook return shape diverges from generic UseChatReturn<TTools, TSchema> due to conditional type on TSchema; TS can't structurally narrow\n return {\n messages: renderedMessages,\n sendMessage,\n append,\n reload,\n stop,\n isLoading,\n error,\n status,\n isSubscribed,\n connectionStatus,\n sessionGenerating,\n setMessages: setMessagesManually,\n clear,\n addToolResult,\n addToolApprovalResponse,\n queue,\n cancelQueued,\n runId,\n interrupts: interruptState.interrupts,\n pendingInterrupts: interruptState.pendingInterrupts,\n interruptErrors: interruptState.interruptErrors,\n resuming: interruptState.resuming,\n resolveInterrupts,\n cancelInterrupts,\n retryInterrupts,\n resumeInterruptsUnsafe,\n resumeInterrupts,\n partial,\n final,\n } as unknown as UseChatReturn<TTools, TSchema, TInterrupts>\n}\n"],"mappings":"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"}
|
|
@@ -10,10 +10,6 @@ export interface UseGenerateAudioOptions<TOutput = AudioGenerationResult> {
|
|
|
10
10
|
connection?: ConnectConnectionAdapter;
|
|
11
11
|
/** Direct async function for audio generation */
|
|
12
12
|
fetcher?: GenerationFetcher<AudioGenerateInput, AudioGenerationResult>;
|
|
13
|
-
/**
|
|
14
|
-
* @deprecated Prefer `threadId`. Only allowed when `threadId` is omitted (see `GenerationPersistenceOptions`).
|
|
15
|
-
*/
|
|
16
|
-
id?: string;
|
|
17
13
|
/** Additional body parameters to send with connect-based adapter requests */
|
|
18
14
|
body?: Record<string, any>;
|
|
19
15
|
/** Display options for TanStack AI Devtools. */
|
|
@@ -37,8 +33,8 @@ export interface UseGenerateAudioOptions<TOutput = AudioGenerationResult> {
|
|
|
37
33
|
* id on the wire, which the protocol requires.
|
|
38
34
|
*
|
|
39
35
|
* **Required whenever `persistence` is set** — an app that cannot name the
|
|
40
|
-
* scope has nothing to restore to. Optional for ephemeral generations
|
|
41
|
-
*
|
|
36
|
+
* scope has nothing to restore to. Optional for ephemeral generations. If
|
|
37
|
+
* omitted, the client mints a wire id after mount.
|
|
42
38
|
*/
|
|
43
39
|
threadId?: string;
|
|
44
40
|
/**
|
|
@@ -128,6 +124,6 @@ export interface UseGenerateAudioReturn<TOutput = AudioGenerationResult> {
|
|
|
128
124
|
* }
|
|
129
125
|
* ```
|
|
130
126
|
*/
|
|
131
|
-
export declare function useGenerateAudio<TTransformed = void>(options: Omit<UseGenerateAudioOptions, 'onResult' | 'persistence' | 'threadId'
|
|
127
|
+
export declare function useGenerateAudio<TTransformed = void>(options: Omit<UseGenerateAudioOptions, 'onResult' | 'persistence' | 'threadId'> & {
|
|
132
128
|
onResult?: (result: AudioGenerationResult) => TTransformed;
|
|
133
129
|
} & GenerationPersistenceOptions): UseGenerateAudioReturn<InferGenerationOutputFromReturn<AudioGenerationResult, TTransformed>>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-generate-audio.js","names":[],"sources":["../../src/use-generate-audio.ts"],"sourcesContent":["import { useGeneration } from './use-generation'\nimport { reconstructAudioResult } from '@tanstack/ai-client'\nimport type { AudioGenerationResult, StreamChunk } from '@tanstack/ai'\nimport type {\n AIDevtoolsDisplayOptions,\n AudioGenerateInput,\n ConnectConnectionAdapter,\n GenerationClientState,\n GenerationFetcher,\n GenerationPersistenceOptions,\n InferGenerationOutputFromReturn,\n} from '@tanstack/ai-client'\n\n/**\n * Options for the useGenerateAudio hook.\n *\n * @template TOutput - The output type after optional transform (defaults to AudioGenerationResult)\n */\nexport interface UseGenerateAudioOptions<TOutput = AudioGenerationResult> {\n /** Connect-based adapter for streaming transport (SSE, HTTP stream, custom) */\n connection?: ConnectConnectionAdapter\n /** Direct async function for audio generation */\n fetcher?: GenerationFetcher<AudioGenerateInput, AudioGenerationResult>\n
|
|
1
|
+
{"version":3,"file":"use-generate-audio.js","names":[],"sources":["../../src/use-generate-audio.ts"],"sourcesContent":["import { useGeneration } from './use-generation'\nimport { reconstructAudioResult } from '@tanstack/ai-client'\nimport type { AudioGenerationResult, StreamChunk } from '@tanstack/ai'\nimport type {\n AIDevtoolsDisplayOptions,\n AudioGenerateInput,\n ConnectConnectionAdapter,\n GenerationClientState,\n GenerationFetcher,\n GenerationPersistenceOptions,\n InferGenerationOutputFromReturn,\n} from '@tanstack/ai-client'\n\n/**\n * Options for the useGenerateAudio hook.\n *\n * @template TOutput - The output type after optional transform (defaults to AudioGenerationResult)\n */\nexport interface UseGenerateAudioOptions<TOutput = AudioGenerationResult> {\n /** Connect-based adapter for streaming transport (SSE, HTTP stream, custom) */\n connection?: ConnectConnectionAdapter\n /** Direct async function for audio generation */\n fetcher?: GenerationFetcher<AudioGenerateInput, AudioGenerationResult>\n /** Additional body parameters to send with connect-based adapter requests */\n body?: Record<string, any>\n /** Display options for TanStack AI Devtools. */\n devtools?: AIDevtoolsDisplayOptions\n /**\n * How this generation persists across reloads.\n * - Omit / `false`: ephemeral, in-memory only.\n * - `true`: server-driven — on mount the client hydrates the last generation\n * for its `threadId` from the server (needs a connection with a\n * `hydrateGeneration` handler) and repaints it; it never auto-starts a run.\n */\n persistence?: boolean\n /**\n * The **scope** this generation belongs to: a stable, app-chosen name for the\n * slot successive runs fill — not a link to a chat conversation.\n *\n * The hook starts empty and produces many runs over its life; each gets its\n * own `runId`, but all belong to one scope. Persistence keys on this, so\n * derive it from your own domain and keep it identical across reloads (e.g.\n * `` `video-${videoId}-start-frame` ``). It is also sent as the AG-UI thread\n * id on the wire, which the protocol requires.\n *\n * **Required whenever `persistence` is set** — an app that cannot name the\n * scope has nothing to restore to. Optional for ephemeral generations. If\n * omitted, the client mints a wire id after mount.\n */\n threadId?: string\n /**\n * Server-driven hydration handler for `persistence: true` when the\n * connection doesn't carry one (e.g. alongside `fetcher`, or a `stream()` /\n * `rpcStream()` adapter built without handlers) — typically a one-line\n * server-function call. The connection's own handler takes precedence.\n */\n hydrateGeneration?: ConnectConnectionAdapter['hydrateGeneration']\n /**\n * Re-attach handler that replays a run still generating to completion on\n * mount, when the connection doesn't carry one. Without it, a restored\n * `running` snapshot surfaces as an (interrupted) error. The connection's\n * own handler takes precedence.\n */\n joinRun?: ConnectConnectionAdapter['joinRun']\n /**\n * Callback when audio is generated. Can optionally return a transformed value.\n *\n * - Return a non-null value to transform and store it as the result\n * - Return `null` to keep the previous result unchanged\n * - Return nothing (`void`) to store the raw result as-is\n */\n onResult?: (result: AudioGenerationResult) => TOutput | null | void\n /** Callback when an error occurs */\n onError?: (error: Error) => void\n /** Callback when progress is reported (0-100) */\n onProgress?: (progress: number, message?: string) => void\n /** Callback for each stream chunk (connect-based adapter mode only) */\n onChunk?: (chunk: StreamChunk) => void\n}\n\n/**\n * Return type for the useGenerateAudio hook.\n *\n * @template TOutput - The output type (after optional transform)\n */\nexport interface UseGenerateAudioReturn<TOutput = AudioGenerationResult> {\n /** Trigger audio generation */\n generate: (input: AudioGenerateInput) => Promise<void>\n /** The generation result containing audio, or null */\n result: TOutput | null\n /** Whether generation is in progress */\n isLoading: boolean\n /** Current error, if any */\n error: Error | undefined\n /** Current state of the generation */\n status: GenerationClientState\n /** Abort the current generation */\n stop: () => void\n /** Clear result, error, and return to idle */\n reset: () => void\n /**\n * The id of the generation job currently running, or `null` when nothing is in\n * flight. Each call to `generate` is one job with its own id. Pass it to your\n * own endpoint to cancel or poll the provider job — `stop()` only aborts the\n * local stream, it does not stop work already running on the provider.\n */\n runId: string | null\n}\n\n/**\n * React hook for generating audio (music, sound effects) using AI models.\n *\n * Supports two transport modes:\n * - **ConnectConnectionAdapter** — Streaming transport (SSE, HTTP stream, custom)\n * - **Fetcher** — Direct async function call\n *\n * @example\n * ```tsx\n * import { useGenerateAudio } from '@tanstack/ai-react'\n * import { fetchServerSentEvents } from '@tanstack/ai-client'\n *\n * function AudioGenerator() {\n * const { generate, result, isLoading, error, reset } = useGenerateAudio({\n * connection: fetchServerSentEvents('/api/generate/audio'),\n * })\n *\n * return (\n * <div>\n * <button onClick={() => generate({ prompt: 'An upbeat electronic track', duration: 10 })}>\n * Generate\n * </button>\n * {isLoading && <p>Generating...</p>}\n * {error && <p>Error: {error.message}</p>}\n * {result?.audio.url && <audio src={result.audio.url} controls />}\n * </div>\n * )\n * }\n * ```\n */\nexport function useGenerateAudio<TTransformed = void>(\n options: Omit<\n UseGenerateAudioOptions,\n 'onResult' | 'persistence' | 'threadId'\n > & {\n onResult?: (result: AudioGenerationResult) => TTransformed\n } & GenerationPersistenceOptions,\n): UseGenerateAudioReturn<\n InferGenerationOutputFromReturn<AudioGenerationResult, TTransformed>\n> {\n const devtools = {\n ...options.devtools,\n framework: 'react',\n hookName: 'useGenerateAudio',\n outputKind: 'audio' as const,\n }\n const generation = useGeneration<\n AudioGenerateInput,\n AudioGenerationResult,\n TTransformed\n >({\n ...options,\n devtools,\n reconstructResult: reconstructAudioResult,\n })\n\n return generation\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2IA,SAAgB,iBACd,SAQA;CACA,MAAM,WAAW;EACf,GAAG,QAAQ;EACX,WAAW;EACX,UAAU;EACV,YAAY;CACd;CAWA,OAVmB,cAIjB;EACA,GAAG;EACH;EACA,mBAAmB;CACrB,CAEO;AACT"}
|
|
@@ -10,10 +10,6 @@ export interface UseGenerateImageOptions<TOutput = ImageGenerationResult> {
|
|
|
10
10
|
connection?: ConnectConnectionAdapter;
|
|
11
11
|
/** Direct async function for image generation */
|
|
12
12
|
fetcher?: GenerationFetcher<ImageGenerateInput, ImageGenerationResult>;
|
|
13
|
-
/**
|
|
14
|
-
* @deprecated Prefer `threadId`. Only allowed when `threadId` is omitted (see `GenerationPersistenceOptions`).
|
|
15
|
-
*/
|
|
16
|
-
id?: string;
|
|
17
13
|
/** Additional body parameters to send with connect-based adapter requests */
|
|
18
14
|
body?: Record<string, any>;
|
|
19
15
|
/** Display options for TanStack AI Devtools. */
|
|
@@ -37,8 +33,8 @@ export interface UseGenerateImageOptions<TOutput = ImageGenerationResult> {
|
|
|
37
33
|
* id on the wire, which the protocol requires.
|
|
38
34
|
*
|
|
39
35
|
* **Required whenever `persistence` is set** — an app that cannot name the
|
|
40
|
-
* scope has nothing to restore to. Optional for ephemeral generations
|
|
41
|
-
*
|
|
36
|
+
* scope has nothing to restore to. Optional for ephemeral generations. If
|
|
37
|
+
* omitted, the client mints a wire id after mount.
|
|
42
38
|
*/
|
|
43
39
|
threadId?: string;
|
|
44
40
|
/**
|
|
@@ -130,6 +126,6 @@ export interface UseGenerateImageReturn<TOutput = ImageGenerationResult> {
|
|
|
130
126
|
* }
|
|
131
127
|
* ```
|
|
132
128
|
*/
|
|
133
|
-
export declare function useGenerateImage<TTransformed = void>(options: Omit<UseGenerateImageOptions, 'onResult' | 'persistence' | 'threadId'
|
|
129
|
+
export declare function useGenerateImage<TTransformed = void>(options: Omit<UseGenerateImageOptions, 'onResult' | 'persistence' | 'threadId'> & {
|
|
134
130
|
onResult?: (result: ImageGenerationResult) => TTransformed;
|
|
135
131
|
} & GenerationPersistenceOptions): UseGenerateImageReturn<InferGenerationOutputFromReturn<ImageGenerationResult, TTransformed>>;
|