@tanstack/ai-remix 0.0.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/LICENSE +21 -0
- package/README.md +11 -0
- package/package.json +71 -0
- package/src/chat-ui/chat-input.tsx +125 -0
- package/src/chat-ui/chat-message.tsx +197 -0
- package/src/chat-ui/chat-messages.tsx +67 -0
- package/src/chat-ui/chat.tsx +48 -0
- package/src/chat-ui/create-chat-hook.ts +66 -0
- package/src/chat-ui/create-ui.tsx +516 -0
- package/src/chat-ui/text-part.tsx +29 -0
- package/src/chat-ui/thinking-part.tsx +44 -0
- package/src/chat-ui/tool-approval.tsx +94 -0
- package/src/create-audio-recorder.ts +106 -0
- package/src/create-byok.ts +29 -0
- package/src/create-chat.ts +421 -0
- package/src/create-generate-audio.ts +93 -0
- package/src/create-generate-image.ts +94 -0
- package/src/create-generate-speech.ts +93 -0
- package/src/create-generate-video.ts +347 -0
- package/src/create-generation.ts +333 -0
- package/src/create-mcp-app-bridge.ts +36 -0
- package/src/create-realtime-chat.ts +242 -0
- package/src/create-summarize.ts +96 -0
- package/src/create-transcription.ts +105 -0
- package/src/index.ts +92 -0
- package/src/realtime-types.ts +150 -0
- package/src/types.ts +313 -0
- package/src/ui.ts +41 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { createGeneration } from './create-generation.ts'
|
|
2
|
+
import { reconstructTranscriptionResult } from '@tanstack/ai-client'
|
|
3
|
+
import type { Handle } from 'remix/ui'
|
|
4
|
+
import type { TranscriptionResult } from '@tanstack/ai'
|
|
5
|
+
import type {
|
|
6
|
+
GenerationPersistenceOptions,
|
|
7
|
+
TranscriptionGenerateInput,
|
|
8
|
+
} from '@tanstack/ai-client'
|
|
9
|
+
import type {
|
|
10
|
+
CreateGenerationOptions,
|
|
11
|
+
CreateGenerationReturn,
|
|
12
|
+
} from './create-generation.ts'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Options for the createTranscription helper.
|
|
16
|
+
*
|
|
17
|
+
* Handle is the first argument of the helper. It is not part of this type.
|
|
18
|
+
*
|
|
19
|
+
* @template TOutput - The output type after optional transform (defaults to TranscriptionResult)
|
|
20
|
+
*/
|
|
21
|
+
export type CreateTranscriptionOptions<TOutput = TranscriptionResult> = Omit<
|
|
22
|
+
CreateGenerationOptions<
|
|
23
|
+
TranscriptionGenerateInput,
|
|
24
|
+
TranscriptionResult,
|
|
25
|
+
TOutput
|
|
26
|
+
>,
|
|
27
|
+
'onResult' | 'reconstructResult'
|
|
28
|
+
> & {
|
|
29
|
+
onResult?: (result: TranscriptionResult) => TOutput | null | void
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Return type for the createTranscription helper.
|
|
34
|
+
*
|
|
35
|
+
* @template TOutput - The output type (after optional transform)
|
|
36
|
+
*/
|
|
37
|
+
export type CreateTranscriptionReturn<TOutput = TranscriptionResult> =
|
|
38
|
+
CreateGenerationReturn<TOutput, TranscriptionGenerateInput>
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Creates an audio transcription helper for Remix setup.
|
|
42
|
+
*
|
|
43
|
+
* Call this in a Remix component setup function. Pass the component Handle as
|
|
44
|
+
* the first argument.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```tsx
|
|
48
|
+
* import { createTranscription } from '@tanstack/ai-remix'
|
|
49
|
+
* import { fetchServerSentEvents } from '@tanstack/ai-client'
|
|
50
|
+
* import type { Handle } from 'remix/ui'
|
|
51
|
+
*
|
|
52
|
+
* function Transcriber(handle: Handle) {
|
|
53
|
+
* const transcription = createTranscription(handle, {
|
|
54
|
+
* connection: fetchServerSentEvents('/api/transcribe'),
|
|
55
|
+
* })
|
|
56
|
+
*
|
|
57
|
+
* return () => (
|
|
58
|
+
* <div>
|
|
59
|
+
* <input
|
|
60
|
+
* type="file"
|
|
61
|
+
* accept="audio/*"
|
|
62
|
+
* onChange={(event) => {
|
|
63
|
+
* const file = event.currentTarget.files?.[0]
|
|
64
|
+
* if (!file) return
|
|
65
|
+
* const reader = new FileReader()
|
|
66
|
+
* reader.onload = () => {
|
|
67
|
+
* const audio = reader.result
|
|
68
|
+
* if (typeof audio === 'string') {
|
|
69
|
+
* transcription.generate({ audio, language: 'en' })
|
|
70
|
+
* }
|
|
71
|
+
* }
|
|
72
|
+
* reader.readAsDataURL(file)
|
|
73
|
+
* }}
|
|
74
|
+
* />
|
|
75
|
+
* {transcription.isLoading ? <p>Transcribing...</p> : null}
|
|
76
|
+
* {transcription.result ? <p>{transcription.result.text}</p> : null}
|
|
77
|
+
* </div>
|
|
78
|
+
* )
|
|
79
|
+
* }
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
export function createTranscription<TTransformed = void>(
|
|
83
|
+
handle: Pick<Handle, 'id' | 'update' | 'signal'>,
|
|
84
|
+
options: Omit<
|
|
85
|
+
CreateTranscriptionOptions,
|
|
86
|
+
'onResult' | 'persistence' | 'threadId'
|
|
87
|
+
> & {
|
|
88
|
+
onResult?: (result: TranscriptionResult) => TTransformed
|
|
89
|
+
} & GenerationPersistenceOptions,
|
|
90
|
+
) {
|
|
91
|
+
const devtools = {
|
|
92
|
+
...options.devtools,
|
|
93
|
+
hookName: 'createTranscription',
|
|
94
|
+
outputKind: 'text' as const,
|
|
95
|
+
}
|
|
96
|
+
return createGeneration<
|
|
97
|
+
TranscriptionGenerateInput,
|
|
98
|
+
TranscriptionResult,
|
|
99
|
+
TTransformed
|
|
100
|
+
>(handle, {
|
|
101
|
+
...options,
|
|
102
|
+
devtools,
|
|
103
|
+
reconstructResult: reconstructTranscriptionResult,
|
|
104
|
+
})
|
|
105
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
export { createChat } from './create-chat.ts'
|
|
2
|
+
export { createByok } from './create-byok.ts'
|
|
3
|
+
export { createRealtimeChat } from './create-realtime-chat.ts'
|
|
4
|
+
export { createMcpAppBridge } from './create-mcp-app-bridge.ts'
|
|
5
|
+
export type { CreateMcpAppBridgeOptions } from './create-mcp-app-bridge.ts'
|
|
6
|
+
export type {
|
|
7
|
+
DeepPartial,
|
|
8
|
+
CreateChatOptions,
|
|
9
|
+
CreateChatReturn,
|
|
10
|
+
UIMessage,
|
|
11
|
+
ChatRequestBody,
|
|
12
|
+
QueuedMessage,
|
|
13
|
+
SendMessageOptions,
|
|
14
|
+
WhenBusy,
|
|
15
|
+
QueueConfig,
|
|
16
|
+
QueueStrategy,
|
|
17
|
+
QueueOption,
|
|
18
|
+
} from './types.ts'
|
|
19
|
+
export type {
|
|
20
|
+
CreateRealtimeChatOptions,
|
|
21
|
+
CreateRealtimeChatReturn,
|
|
22
|
+
} from './realtime-types.ts'
|
|
23
|
+
|
|
24
|
+
export { createGeneration } from './create-generation.ts'
|
|
25
|
+
export type {
|
|
26
|
+
CreateGenerationOptions,
|
|
27
|
+
CreateGenerationReturn,
|
|
28
|
+
} from './create-generation.ts'
|
|
29
|
+
export { createGenerateImage } from './create-generate-image.ts'
|
|
30
|
+
export type {
|
|
31
|
+
CreateGenerateImageOptions,
|
|
32
|
+
CreateGenerateImageReturn,
|
|
33
|
+
} from './create-generate-image.ts'
|
|
34
|
+
export { createGenerateAudio } from './create-generate-audio.ts'
|
|
35
|
+
export type {
|
|
36
|
+
CreateGenerateAudioOptions,
|
|
37
|
+
CreateGenerateAudioReturn,
|
|
38
|
+
} from './create-generate-audio.ts'
|
|
39
|
+
export { createGenerateSpeech } from './create-generate-speech.ts'
|
|
40
|
+
export type {
|
|
41
|
+
CreateGenerateSpeechOptions,
|
|
42
|
+
CreateGenerateSpeechReturn,
|
|
43
|
+
} from './create-generate-speech.ts'
|
|
44
|
+
export { createTranscription } from './create-transcription.ts'
|
|
45
|
+
export type {
|
|
46
|
+
CreateTranscriptionOptions,
|
|
47
|
+
CreateTranscriptionReturn,
|
|
48
|
+
} from './create-transcription.ts'
|
|
49
|
+
export { createSummarize } from './create-summarize.ts'
|
|
50
|
+
export type {
|
|
51
|
+
CreateSummarizeOptions,
|
|
52
|
+
CreateSummarizeReturn,
|
|
53
|
+
} from './create-summarize.ts'
|
|
54
|
+
export { createGenerateVideo } from './create-generate-video.ts'
|
|
55
|
+
export type {
|
|
56
|
+
CreateGenerateVideoOptions,
|
|
57
|
+
CreateGenerateVideoReturn,
|
|
58
|
+
} from './create-generate-video.ts'
|
|
59
|
+
export { createAudioRecorder } from './create-audio-recorder.ts'
|
|
60
|
+
export type { CreateAudioRecorderOptions } from './create-audio-recorder.ts'
|
|
61
|
+
|
|
62
|
+
// Re-export from ai-client for convenience (mirror octane index.ts).
|
|
63
|
+
// createMcpAppBridge / CreateMcpAppBridgeOptions come from ./create-mcp-app-bridge.
|
|
64
|
+
export {
|
|
65
|
+
fetchServerSentEvents,
|
|
66
|
+
fetchHttpStream,
|
|
67
|
+
xhrServerSentEvents,
|
|
68
|
+
xhrHttpStream,
|
|
69
|
+
stream,
|
|
70
|
+
rpcStream,
|
|
71
|
+
createChatClientOptions,
|
|
72
|
+
type McpAppBridge,
|
|
73
|
+
type ChatFetcher,
|
|
74
|
+
type ChatFetcherInput,
|
|
75
|
+
type ChatFetcherOptions,
|
|
76
|
+
type ConnectionAdapter,
|
|
77
|
+
type ConnectConnectionAdapter,
|
|
78
|
+
type SubscribeConnectionAdapter,
|
|
79
|
+
type RunAgentInputContext,
|
|
80
|
+
type FetchConnectionOptions,
|
|
81
|
+
type XhrConnectionOptions,
|
|
82
|
+
type InferChatMessages,
|
|
83
|
+
type GenerationClientState,
|
|
84
|
+
type ImageGenerateInput,
|
|
85
|
+
type AudioGenerateInput,
|
|
86
|
+
type SpeechGenerateInput,
|
|
87
|
+
type TranscriptionGenerateInput,
|
|
88
|
+
type SummarizeGenerateInput,
|
|
89
|
+
type VideoGenerateInput,
|
|
90
|
+
type VideoGenerateResult,
|
|
91
|
+
type VideoStatusInfo,
|
|
92
|
+
} from '@tanstack/ai-client'
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AnyClientTool,
|
|
3
|
+
RealtimeMessage,
|
|
4
|
+
RealtimeMode,
|
|
5
|
+
RealtimeSessionConfig,
|
|
6
|
+
RealtimeStatus,
|
|
7
|
+
RealtimeToken,
|
|
8
|
+
UsageInfo,
|
|
9
|
+
} from '@tanstack/ai'
|
|
10
|
+
import type { RealtimeAdapter } from '@tanstack/ai-client'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Options for the createRealtimeChat helper.
|
|
14
|
+
*
|
|
15
|
+
* Called in Remix setup with Handle. Handle is not part of this type.
|
|
16
|
+
*/
|
|
17
|
+
export interface CreateRealtimeChatOptions {
|
|
18
|
+
/**
|
|
19
|
+
* Function to fetch a realtime token from the server.
|
|
20
|
+
* Called on connect and when token needs refresh.
|
|
21
|
+
*/
|
|
22
|
+
getToken: () => Promise<RealtimeToken>
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The realtime adapter to use (e.g., openaiRealtime())
|
|
26
|
+
*/
|
|
27
|
+
adapter: RealtimeAdapter
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Client-side tools with execution logic
|
|
31
|
+
*/
|
|
32
|
+
tools?: ReadonlyArray<AnyClientTool>
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Auto-play assistant audio (default: true)
|
|
36
|
+
*/
|
|
37
|
+
autoPlayback?: boolean
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Request microphone access on connect (default: true)
|
|
41
|
+
*/
|
|
42
|
+
autoCapture?: boolean
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* System instructions for the assistant
|
|
46
|
+
*/
|
|
47
|
+
instructions?: string
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Voice to use for audio output
|
|
51
|
+
*/
|
|
52
|
+
voice?: string
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Voice activity detection mode (default: 'server')
|
|
56
|
+
*/
|
|
57
|
+
vadMode?: 'server' | 'semantic' | 'manual'
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Output modalities for responses (e.g., ['audio', 'text'])
|
|
61
|
+
*/
|
|
62
|
+
outputModalities?: Array<'audio' | 'text'>
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Temperature for generation (provider-specific range)
|
|
66
|
+
*/
|
|
67
|
+
temperature?: number
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Maximum number of tokens in a response
|
|
71
|
+
*/
|
|
72
|
+
maxOutputTokens?: number | 'inf'
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Eagerness level for semantic VAD ('low', 'medium', 'high')
|
|
76
|
+
*/
|
|
77
|
+
semanticEagerness?: 'low' | 'medium' | 'high'
|
|
78
|
+
|
|
79
|
+
// Callbacks
|
|
80
|
+
onConnect?: () => void
|
|
81
|
+
onDisconnect?: () => void
|
|
82
|
+
onError?: (error: Error) => void
|
|
83
|
+
onMessage?: (message: RealtimeMessage) => void
|
|
84
|
+
onModeChange?: (mode: RealtimeMode) => void
|
|
85
|
+
onInterrupted?: () => void
|
|
86
|
+
onUsage?: (usage: UsageInfo) => void
|
|
87
|
+
onGoAway?: (timeLeft?: string) => void
|
|
88
|
+
onStatusChange?: (status: RealtimeStatus) => void
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Return type for the createRealtimeChat helper.
|
|
93
|
+
*
|
|
94
|
+
* Called in Remix setup with Handle. Fields are plain values.
|
|
95
|
+
*/
|
|
96
|
+
export interface CreateRealtimeChatReturn {
|
|
97
|
+
// Connection state
|
|
98
|
+
/** Current connection status */
|
|
99
|
+
status: RealtimeStatus
|
|
100
|
+
/** Current error, if any */
|
|
101
|
+
error: Error | null
|
|
102
|
+
/** Connect to the realtime session */
|
|
103
|
+
connect: () => Promise<void>
|
|
104
|
+
/** Disconnect from the realtime session */
|
|
105
|
+
disconnect: () => Promise<void>
|
|
106
|
+
|
|
107
|
+
// Conversation state
|
|
108
|
+
/** Current mode (idle, listening, thinking, speaking) */
|
|
109
|
+
mode: RealtimeMode
|
|
110
|
+
/** Conversation messages */
|
|
111
|
+
messages: Array<RealtimeMessage>
|
|
112
|
+
/** User transcript while speaking (before finalized) */
|
|
113
|
+
pendingUserTranscript: string | null
|
|
114
|
+
/** Assistant transcript while speaking (before finalized) */
|
|
115
|
+
pendingAssistantTranscript: string | null
|
|
116
|
+
|
|
117
|
+
// Voice control
|
|
118
|
+
/** Start listening for voice input (manual VAD mode) */
|
|
119
|
+
startListening: () => void
|
|
120
|
+
/** Stop listening for voice input (manual VAD mode) */
|
|
121
|
+
stopListening: () => void
|
|
122
|
+
/** Interrupt the current assistant response */
|
|
123
|
+
interrupt: () => void
|
|
124
|
+
|
|
125
|
+
// Text input
|
|
126
|
+
/** Send a text message instead of voice */
|
|
127
|
+
sendText: (text: string) => void
|
|
128
|
+
|
|
129
|
+
// Image input
|
|
130
|
+
/** Send an image to the conversation */
|
|
131
|
+
sendImage: (imageData: string, mimeType: string) => void
|
|
132
|
+
|
|
133
|
+
// Audio visualization (0-1 normalized)
|
|
134
|
+
/** Current input (microphone) volume level */
|
|
135
|
+
inputLevel: number
|
|
136
|
+
/** Current output (speaker) volume level */
|
|
137
|
+
outputLevel: number
|
|
138
|
+
/** Get frequency data for input audio visualization */
|
|
139
|
+
getInputFrequencyData: () => Uint8Array
|
|
140
|
+
/** Get frequency data for output audio visualization */
|
|
141
|
+
getOutputFrequencyData: () => Uint8Array
|
|
142
|
+
/** Get time domain data for input waveform */
|
|
143
|
+
getInputTimeDomainData: () => Uint8Array
|
|
144
|
+
/** Get time domain data for output waveform */
|
|
145
|
+
getOutputTimeDomainData: () => Uint8Array
|
|
146
|
+
|
|
147
|
+
// Session control
|
|
148
|
+
/** Update the active session and persist the configuration for reconnects. */
|
|
149
|
+
updateSession: (config: RealtimeSessionConfig) => void
|
|
150
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AnyClientTool,
|
|
3
|
+
InterruptDefinition,
|
|
4
|
+
InferSchemaType,
|
|
5
|
+
ModelMessage,
|
|
6
|
+
RunAgentResumeItem,
|
|
7
|
+
SchemaInput,
|
|
8
|
+
} from '@tanstack/ai/client'
|
|
9
|
+
import type {
|
|
10
|
+
AIDevtoolsDisplayOptions,
|
|
11
|
+
BoundInterrupts,
|
|
12
|
+
ChatClientOptions,
|
|
13
|
+
ChatClientState,
|
|
14
|
+
ResolvableChatInterrupt,
|
|
15
|
+
ChatInterruptState,
|
|
16
|
+
ChatRequestBody,
|
|
17
|
+
ChatResumeState,
|
|
18
|
+
ClientContextOptionFromTools,
|
|
19
|
+
ConnectionStatus,
|
|
20
|
+
DistributedOmit,
|
|
21
|
+
InferredClientContext,
|
|
22
|
+
MultimodalContent,
|
|
23
|
+
QueueConfig,
|
|
24
|
+
QueueOption,
|
|
25
|
+
QueueStrategy,
|
|
26
|
+
QueuedMessage,
|
|
27
|
+
SendMessageOptions,
|
|
28
|
+
UIMessage,
|
|
29
|
+
WhenBusy,
|
|
30
|
+
} from '@tanstack/ai-client'
|
|
31
|
+
|
|
32
|
+
// Re-export types from ai-client
|
|
33
|
+
export type {
|
|
34
|
+
ChatRequestBody,
|
|
35
|
+
MultimodalContent,
|
|
36
|
+
QueueConfig,
|
|
37
|
+
QueuedMessage,
|
|
38
|
+
QueueOption,
|
|
39
|
+
QueueStrategy,
|
|
40
|
+
SendMessageOptions,
|
|
41
|
+
UIMessage,
|
|
42
|
+
WhenBusy,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Recursive partial. Every property and every nested array element is optional.
|
|
47
|
+
* Used to type the in-flight `partial` value the helper exposes while a
|
|
48
|
+
* structured output stream is still arriving (the JSON has shape but is
|
|
49
|
+
* incomplete).
|
|
50
|
+
*/
|
|
51
|
+
export type DeepPartial<T> =
|
|
52
|
+
T extends ReadonlyArray<infer U>
|
|
53
|
+
? Array<DeepPartial<U>>
|
|
54
|
+
: T extends object
|
|
55
|
+
? { [K in keyof T]?: DeepPartial<T[K]> }
|
|
56
|
+
: T
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Options for the createChat helper.
|
|
60
|
+
*
|
|
61
|
+
* Call `createChat(handle, options)` in Remix setup with Handle from
|
|
62
|
+
* `remix/ui`. Handle is the first argument of the helper. It is not part of
|
|
63
|
+
* this type. The default id is `options.threadId ?? handle.id`.
|
|
64
|
+
*
|
|
65
|
+
* Pass either `connection` or `fetcher`. The XOR is enforced at the type
|
|
66
|
+
* level via `ChatTransport`.
|
|
67
|
+
*
|
|
68
|
+
* This extends ChatClientOptions but omits the state change callbacks that
|
|
69
|
+
* createChat manages internally:
|
|
70
|
+
* - `onMessagesChange` - Managed internally (exposed as `messages`)
|
|
71
|
+
* - `onLoadingChange` - Managed internally (exposed as `isLoading`)
|
|
72
|
+
* - `onErrorChange` - Managed internally (exposed as `error`)
|
|
73
|
+
* - `onStatusChange` - Managed internally (exposed as `status`)
|
|
74
|
+
*
|
|
75
|
+
* All other callbacks (onResponse, onChunk, onFinish, onError) are
|
|
76
|
+
* passed through to the underlying ChatClient and can be used for side effects.
|
|
77
|
+
*
|
|
78
|
+
* When `outputSchema` is supplied, the helper returns a typed `partial` (live
|
|
79
|
+
* progressive object, updated from `TEXT_MESSAGE_CONTENT` deltas via
|
|
80
|
+
* `parsePartialJSON`) and `final` (validated terminal payload from the
|
|
81
|
+
* `structured-output.complete` event). The schema is used purely for type
|
|
82
|
+
* inference on the client. Server-side validation still runs against the
|
|
83
|
+
* schema you pass to `chat({ outputSchema })` on the server route.
|
|
84
|
+
*
|
|
85
|
+
* Changing `connection` or `fetcher` updates the active ChatClient in place,
|
|
86
|
+
* preserving its state. Changing `threadId` creates a fresh client.
|
|
87
|
+
*/
|
|
88
|
+
export type CreateChatOptions<
|
|
89
|
+
TTools extends ReadonlyArray<AnyClientTool> = any,
|
|
90
|
+
TSchema extends SchemaInput | undefined = undefined,
|
|
91
|
+
TContext = InferredClientContext<TTools>,
|
|
92
|
+
TInterrupts extends ReadonlyArray<InterruptDefinition<any, any, any, any>> =
|
|
93
|
+
readonly [],
|
|
94
|
+
> = DistributedOmit<
|
|
95
|
+
ChatClientOptions<TTools, TContext, TInterrupts>,
|
|
96
|
+
| 'onMessagesChange'
|
|
97
|
+
| 'onLoadingChange'
|
|
98
|
+
| 'onErrorChange'
|
|
99
|
+
| 'onStatusChange'
|
|
100
|
+
| 'onSubscriptionChange'
|
|
101
|
+
| 'onConnectionStatusChange'
|
|
102
|
+
| 'onSessionGeneratingChange'
|
|
103
|
+
| 'onQueueChange'
|
|
104
|
+
| 'onResumeStateChange'
|
|
105
|
+
| 'onRunIdChange'
|
|
106
|
+
| 'context'
|
|
107
|
+
| 'devtools'
|
|
108
|
+
> & {
|
|
109
|
+
/** Display options for TanStack AI Devtools. */
|
|
110
|
+
devtools?: AIDevtoolsDisplayOptions
|
|
111
|
+
/**
|
|
112
|
+
* Opt into live subscription behavior when the helper is called in Remix
|
|
113
|
+
* setup with Handle. When enabled, the helper subscribes on setup and
|
|
114
|
+
* unsubscribes on dispose.
|
|
115
|
+
*/
|
|
116
|
+
live?: boolean
|
|
117
|
+
/**
|
|
118
|
+
* Standard-schema-compatible schema (Zod, Valibot, ArkType, or a plain JSON
|
|
119
|
+
* Schema). Used to infer the shape of `partial` and `final` in the return.
|
|
120
|
+
* The schema is **not** sent to the server. Server-side validation runs
|
|
121
|
+
* against the schema passed to `chat({ outputSchema })` on the server route.
|
|
122
|
+
*/
|
|
123
|
+
outputSchema?: TSchema
|
|
124
|
+
} & ClientContextOptionFromTools<TTools, TContext>
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Discriminated return shape from the createChat helper. When `outputSchema`
|
|
128
|
+
* is supplied, the helper adds typed `partial` / `final` fields. When it is
|
|
129
|
+
* omitted (default), the return is unchanged. Fields are plain values.
|
|
130
|
+
*/
|
|
131
|
+
export type CreateChatReturn<
|
|
132
|
+
TTools extends ReadonlyArray<AnyClientTool> = any,
|
|
133
|
+
TSchema extends SchemaInput | undefined = undefined,
|
|
134
|
+
TInterrupts extends ReadonlyArray<InterruptDefinition<any, any, any, any>> =
|
|
135
|
+
readonly [],
|
|
136
|
+
> = BaseCreateChatReturn<
|
|
137
|
+
TTools,
|
|
138
|
+
TSchema extends SchemaInput ? InferSchemaType<TSchema> : unknown,
|
|
139
|
+
TInterrupts
|
|
140
|
+
> &
|
|
141
|
+
(TSchema extends SchemaInput
|
|
142
|
+
? {
|
|
143
|
+
/**
|
|
144
|
+
* Live, progressively-parsed structured output. Updated from
|
|
145
|
+
* `TEXT_MESSAGE_CONTENT` deltas via `parsePartialJSON` while the stream
|
|
146
|
+
* is still arriving, and snapped to the validated payload when
|
|
147
|
+
* `structured-output.complete` fires. Resets on every new run
|
|
148
|
+
* (`sendMessage` / `reload`).
|
|
149
|
+
*/
|
|
150
|
+
partial: DeepPartial<InferSchemaType<TSchema>>
|
|
151
|
+
/**
|
|
152
|
+
* Final, schema-validated structured output. `null` until the terminal
|
|
153
|
+
* `structured-output.complete` event arrives. Resets on every new run.
|
|
154
|
+
*/
|
|
155
|
+
final: InferSchemaType<TSchema> | null
|
|
156
|
+
}
|
|
157
|
+
: Record<never, never>)
|
|
158
|
+
|
|
159
|
+
interface BaseCreateChatReturn<
|
|
160
|
+
TTools extends ReadonlyArray<AnyClientTool> = any,
|
|
161
|
+
TData = unknown,
|
|
162
|
+
TInterrupts extends ReadonlyArray<InterruptDefinition<any, any, any, any>> =
|
|
163
|
+
readonly [],
|
|
164
|
+
> {
|
|
165
|
+
/**
|
|
166
|
+
* Current messages in the conversation. When `outputSchema` is supplied,
|
|
167
|
+
* `messages[i].parts.find(p => p.type === 'structured-output')` is typed
|
|
168
|
+
* with the schema's inferred shape: `data: T`, `partial: DeepPartial<T>`.
|
|
169
|
+
*/
|
|
170
|
+
messages: Array<UIMessage<TTools, TData>>
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Send a message and get a response.
|
|
174
|
+
* Can be a simple string or multimodal content with images, audio, etc.
|
|
175
|
+
* By default, sends while busy are queued until the run settles successfully
|
|
176
|
+
* (`queue: 'drop'` restores the old drop-while-busy behavior).
|
|
177
|
+
* Pass `{ whenBusy }` to override the policy for a single send, or
|
|
178
|
+
* `{ body }` to merge per-call JSON into this request's `forwardedProps`.
|
|
179
|
+
*/
|
|
180
|
+
sendMessage: (
|
|
181
|
+
content: string | MultimodalContent,
|
|
182
|
+
options?: SendMessageOptions,
|
|
183
|
+
) => Promise<void>
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Pending messages queued while the client is busy (streaming, claiming a
|
|
187
|
+
* send, or draining). Separate from `messages` until they drain.
|
|
188
|
+
*/
|
|
189
|
+
queue: Array<QueuedMessage>
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Cancel a queued message before it drains. No-op if already sent.
|
|
193
|
+
*/
|
|
194
|
+
cancelQueued: (id: string) => void
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Append a message to the conversation
|
|
198
|
+
*/
|
|
199
|
+
append: (message: ModelMessage | UIMessage<TTools, TData>) => Promise<void>
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Add the result of a client-side tool execution
|
|
203
|
+
*/
|
|
204
|
+
addToolResult: (result: {
|
|
205
|
+
toolCallId: string
|
|
206
|
+
tool: string
|
|
207
|
+
output: any
|
|
208
|
+
state?: 'output-available' | 'output-error'
|
|
209
|
+
errorText?: string
|
|
210
|
+
}) => Promise<void>
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* @deprecated Use a bound `tool-approval` interrupt and
|
|
214
|
+
* `interrupt.resolveInterrupt`.
|
|
215
|
+
*/
|
|
216
|
+
addToolApprovalResponse: (response: {
|
|
217
|
+
id: string // approval.id, not toolCallId
|
|
218
|
+
approved: boolean
|
|
219
|
+
}) => Promise<void>
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* The id of the run this client has in flight (one it started or rejoined),
|
|
223
|
+
* or `null` when there is none (including while a run sits paused on an
|
|
224
|
+
* interrupt, waiting on approval).
|
|
225
|
+
*
|
|
226
|
+
* A run is one turn of the conversation, so this changes from turn to turn. A
|
|
227
|
+
* whole tool loop stays inside one run, while resuming after an interrupt
|
|
228
|
+
* continues the turn under a new id — so one user message can produce several
|
|
229
|
+
* run ids. Use it to talk to your own server about that run (cancel it, poll
|
|
230
|
+
* it, correlate a log line).
|
|
231
|
+
*/
|
|
232
|
+
runId: string | null
|
|
233
|
+
interrupts: BoundInterrupts<TTools, TInterrupts>
|
|
234
|
+
/** @deprecated Use `interrupts`. */
|
|
235
|
+
pendingInterrupts: BoundInterrupts<TTools, TInterrupts>
|
|
236
|
+
interruptErrors: ChatInterruptState<TTools, TInterrupts>['interruptErrors']
|
|
237
|
+
resuming: boolean
|
|
238
|
+
resolveInterrupts: {
|
|
239
|
+
(approved: boolean): void
|
|
240
|
+
(
|
|
241
|
+
resolver: (
|
|
242
|
+
interrupt: ResolvableChatInterrupt<TTools, TInterrupts>,
|
|
243
|
+
) => undefined,
|
|
244
|
+
): void
|
|
245
|
+
}
|
|
246
|
+
cancelInterrupts: () => void
|
|
247
|
+
retryInterrupts: () => void
|
|
248
|
+
resumeInterruptsUnsafe: (
|
|
249
|
+
resume: Array<RunAgentResumeItem>,
|
|
250
|
+
state?: ChatResumeState,
|
|
251
|
+
) => Promise<boolean>
|
|
252
|
+
/** @deprecated Use bound interrupt methods or `resumeInterruptsUnsafe`. */
|
|
253
|
+
resumeInterrupts: (
|
|
254
|
+
resume: Array<RunAgentResumeItem>,
|
|
255
|
+
state?: ChatResumeState,
|
|
256
|
+
) => Promise<boolean>
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Reload the last assistant message
|
|
260
|
+
*/
|
|
261
|
+
reload: () => Promise<void>
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Stop the current response generation
|
|
265
|
+
*/
|
|
266
|
+
stop: () => void
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Whether a response is currently being generated
|
|
270
|
+
*/
|
|
271
|
+
isLoading: boolean
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Current error, if any
|
|
275
|
+
*/
|
|
276
|
+
error: Error | undefined
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Current status of the chat client
|
|
280
|
+
*/
|
|
281
|
+
status: ChatClientState
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Whether the subscription loop is currently active
|
|
285
|
+
*/
|
|
286
|
+
isSubscribed: boolean
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Current connection lifecycle status
|
|
290
|
+
*/
|
|
291
|
+
connectionStatus: ConnectionStatus
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Whether the shared session is actively generating.
|
|
295
|
+
* Derived from stream run events (RUN_STARTED / RUN_FINISHED / RUN_ERROR).
|
|
296
|
+
* Unlike `isLoading` (request-local), this reflects shared generation
|
|
297
|
+
* activity visible to all subscribers (e.g. across tabs/devices).
|
|
298
|
+
*/
|
|
299
|
+
sessionGenerating: boolean
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Set messages manually
|
|
303
|
+
*/
|
|
304
|
+
setMessages: (messages: Array<UIMessage<TTools, TData>>) => void
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Clear all messages
|
|
308
|
+
*/
|
|
309
|
+
clear: () => void
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// createChatClientOptions and InferChatMessages live in @tanstack/ai-client
|
|
313
|
+
// and are re-exported from there.
|
package/src/ui.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Barrel entry for the `@tanstack/ai-remix/ui` subpath.
|
|
2
|
+
export {
|
|
3
|
+
createChatUI,
|
|
4
|
+
type ChatUIComponents,
|
|
5
|
+
type ChatUIFactoryConfig,
|
|
6
|
+
type ChatUIHost,
|
|
7
|
+
type ChatUIQueueItem,
|
|
8
|
+
type InputProps,
|
|
9
|
+
type InterruptProps,
|
|
10
|
+
type LayoutProps,
|
|
11
|
+
type MessageProps,
|
|
12
|
+
type PartProps,
|
|
13
|
+
type QueueProps,
|
|
14
|
+
type ToolProps,
|
|
15
|
+
} from './chat-ui/create-ui.tsx'
|
|
16
|
+
export { createChatHook } from './chat-ui/create-chat-hook.ts'
|
|
17
|
+
export { Chat, useChatContext, type ChatProps } from './chat-ui/chat.tsx'
|
|
18
|
+
export {
|
|
19
|
+
ChatMessages,
|
|
20
|
+
type ChatMessagesProps,
|
|
21
|
+
} from './chat-ui/chat-messages.tsx'
|
|
22
|
+
export {
|
|
23
|
+
ChatMessage,
|
|
24
|
+
type ChatMessageProps,
|
|
25
|
+
type ToolCallRenderProps,
|
|
26
|
+
} from './chat-ui/chat-message.tsx'
|
|
27
|
+
export {
|
|
28
|
+
ChatInput,
|
|
29
|
+
type ChatInputProps,
|
|
30
|
+
type ChatInputRenderProps,
|
|
31
|
+
} from './chat-ui/chat-input.tsx'
|
|
32
|
+
export {
|
|
33
|
+
ToolApproval,
|
|
34
|
+
type ToolApprovalProps,
|
|
35
|
+
type ToolApprovalRenderProps,
|
|
36
|
+
} from './chat-ui/tool-approval.tsx'
|
|
37
|
+
export { TextPart, type TextPartProps } from './chat-ui/text-part.tsx'
|
|
38
|
+
export {
|
|
39
|
+
ThinkingPart,
|
|
40
|
+
type ThinkingPartProps,
|
|
41
|
+
} from './chat-ui/thinking-part.tsx'
|