@tanstack/ai 0.6.3 → 0.8.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.
Files changed (68) hide show
  1. package/README.md +6 -6
  2. package/dist/esm/activities/chat/index.d.ts +20 -0
  3. package/dist/esm/activities/chat/index.js +248 -213
  4. package/dist/esm/activities/chat/index.js.map +1 -1
  5. package/dist/esm/activities/chat/middleware/compose.d.ts +66 -0
  6. package/dist/esm/activities/chat/middleware/compose.js +327 -0
  7. package/dist/esm/activities/chat/middleware/compose.js.map +1 -0
  8. package/dist/esm/activities/chat/middleware/index.d.ts +2 -0
  9. package/dist/esm/activities/chat/middleware/tool-cache-middleware.d.ts +89 -0
  10. package/dist/esm/activities/chat/middleware/tool-cache-middleware.js +76 -0
  11. package/dist/esm/activities/chat/middleware/tool-cache-middleware.js.map +1 -0
  12. package/dist/esm/activities/chat/middleware/types.d.ts +307 -0
  13. package/dist/esm/activities/chat/stream/processor.d.ts +64 -40
  14. package/dist/esm/activities/chat/stream/processor.js +466 -218
  15. package/dist/esm/activities/chat/stream/processor.js.map +1 -1
  16. package/dist/esm/activities/chat/stream/types.d.ts +17 -0
  17. package/dist/esm/activities/chat/tools/tool-calls.d.ts +16 -1
  18. package/dist/esm/activities/chat/tools/tool-calls.js +148 -64
  19. package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
  20. package/dist/esm/activities/generateImage/index.js +1 -1
  21. package/dist/esm/activities/generateImage/index.js.map +1 -1
  22. package/dist/esm/activities/generateSpeech/index.js +1 -1
  23. package/dist/esm/activities/generateSpeech/index.js.map +1 -1
  24. package/dist/esm/activities/generateTranscription/index.js +1 -1
  25. package/dist/esm/activities/generateTranscription/index.js.map +1 -1
  26. package/dist/esm/activities/generateVideo/index.js +1 -1
  27. package/dist/esm/activities/generateVideo/index.js.map +1 -1
  28. package/dist/esm/activities/summarize/index.js +1 -1
  29. package/dist/esm/activities/summarize/index.js.map +1 -1
  30. package/dist/esm/index.d.ts +3 -1
  31. package/dist/esm/index.js +2 -2
  32. package/dist/esm/middlewares/content-guard.d.ts +77 -0
  33. package/dist/esm/middlewares/content-guard.js +155 -0
  34. package/dist/esm/middlewares/content-guard.js.map +1 -0
  35. package/dist/esm/middlewares/index.d.ts +2 -0
  36. package/dist/esm/middlewares/index.js +7 -0
  37. package/dist/esm/middlewares/index.js.map +1 -0
  38. package/dist/esm/middlewares/tool-cache.d.ts +1 -0
  39. package/dist/esm/realtime/index.d.ts +30 -0
  40. package/dist/esm/realtime/index.js +8 -0
  41. package/dist/esm/realtime/index.js.map +1 -0
  42. package/dist/esm/realtime/types.d.ts +234 -0
  43. package/dist/esm/types.d.ts +18 -4
  44. package/package.json +6 -6
  45. package/src/activities/chat/index.ts +322 -256
  46. package/src/activities/chat/middleware/compose.ts +392 -0
  47. package/src/activities/chat/middleware/index.ts +17 -0
  48. package/src/activities/chat/middleware/tool-cache-middleware.ts +189 -0
  49. package/src/activities/chat/middleware/types.ts +419 -0
  50. package/src/activities/chat/stream/processor.ts +630 -259
  51. package/src/activities/chat/stream/types.ts +18 -0
  52. package/src/activities/chat/tools/tool-calls.ts +225 -87
  53. package/src/activities/generateImage/index.ts +1 -1
  54. package/src/activities/generateSpeech/index.ts +1 -1
  55. package/src/activities/generateTranscription/index.ts +1 -1
  56. package/src/activities/generateVideo/index.ts +1 -1
  57. package/src/activities/summarize/index.ts +1 -1
  58. package/src/index.ts +41 -2
  59. package/src/middlewares/content-guard.ts +285 -0
  60. package/src/middlewares/index.ts +13 -0
  61. package/src/middlewares/tool-cache.ts +6 -0
  62. package/src/realtime/index.ts +38 -0
  63. package/src/realtime/types.ts +294 -0
  64. package/src/types.ts +19 -2
  65. package/dist/esm/event-client.d.ts +0 -394
  66. package/dist/esm/event-client.js +0 -13
  67. package/dist/esm/event-client.js.map +0 -1
  68. package/src/event-client.ts +0 -497
@@ -0,0 +1,285 @@
1
+ import type {
2
+ ChatMiddleware,
3
+ ChatMiddlewareContext,
4
+ } from '../activities/chat/middleware/types'
5
+ import type { StreamChunk } from '../types'
6
+
7
+ /**
8
+ * A content guard rule — either a regex pattern with replacement, or a transform function.
9
+ */
10
+ export type ContentGuardRule =
11
+ | { pattern: RegExp; replacement: string }
12
+ | { fn: (text: string) => string }
13
+
14
+ /**
15
+ * Information passed to the onFiltered callback.
16
+ */
17
+ export interface ContentFilteredInfo {
18
+ /** The message ID being filtered */
19
+ messageId: string
20
+ /** The original text before filtering */
21
+ original: string
22
+ /** The filtered text after rules applied */
23
+ filtered: string
24
+ /** Which strategy was used */
25
+ strategy: 'delta' | 'buffered'
26
+ }
27
+
28
+ /**
29
+ * Options for the content guard middleware.
30
+ */
31
+ export interface ContentGuardMiddlewareOptions {
32
+ /**
33
+ * Rules to apply to text content. Each rule is either a regex pattern
34
+ * with a replacement string, or a custom transform function.
35
+ * Rules are applied in order. Each rule receives the output of the previous.
36
+ */
37
+ rules: Array<ContentGuardRule>
38
+
39
+ /**
40
+ * Matching strategy:
41
+ * - 'delta': Apply rules to each delta as it arrives. Fast, real-time,
42
+ * but patterns spanning chunk boundaries may be missed.
43
+ * - 'buffered': Accumulate content and apply rules to settled portions,
44
+ * holding back a look-behind buffer to catch cross-boundary patterns.
45
+ *
46
+ * @default 'buffered'
47
+ */
48
+ strategy?: 'delta' | 'buffered'
49
+
50
+ /**
51
+ * Number of characters to hold back before emitting (buffered strategy only).
52
+ * Should be at least as long as the longest pattern you expect to match.
53
+ * Buffer is flushed when the stream ends.
54
+ *
55
+ * @default 50
56
+ */
57
+ bufferSize?: number
58
+
59
+ /**
60
+ * If true, drop the entire chunk when any rule changes the content.
61
+ * @default false
62
+ */
63
+ blockOnMatch?: boolean
64
+
65
+ /**
66
+ * Callback when content is filtered by any rule.
67
+ */
68
+ onFiltered?: (info: ContentFilteredInfo) => void
69
+ }
70
+
71
+ /**
72
+ * Apply all rules to a string, returning the transformed result.
73
+ */
74
+ function applyRules(text: string, rules: Array<ContentGuardRule>): string {
75
+ let result = text
76
+ for (const rule of rules) {
77
+ if ('pattern' in rule) {
78
+ result = result.replace(rule.pattern, rule.replacement)
79
+ } else {
80
+ result = rule.fn(result)
81
+ }
82
+ }
83
+ return result
84
+ }
85
+
86
+ /**
87
+ * Creates a middleware that filters or transforms streamed text content.
88
+ *
89
+ * @example
90
+ * ```ts
91
+ * import { contentGuardMiddleware } from '@tanstack/ai/middlewares'
92
+ *
93
+ * const guard = contentGuardMiddleware({
94
+ * rules: [
95
+ * { pattern: /\b\d{3}-\d{2}-\d{4}\b/g, replacement: '[SSN REDACTED]' },
96
+ * ],
97
+ * strategy: 'buffered',
98
+ * })
99
+ * ```
100
+ */
101
+ export function contentGuardMiddleware(
102
+ options: ContentGuardMiddlewareOptions,
103
+ ): ChatMiddleware {
104
+ const {
105
+ rules,
106
+ strategy = 'buffered',
107
+ bufferSize = 50,
108
+ blockOnMatch = false,
109
+ onFiltered,
110
+ } = options
111
+
112
+ if (strategy === 'delta') {
113
+ return createDeltaStrategy(rules, blockOnMatch, onFiltered)
114
+ }
115
+ return createBufferedStrategy(rules, bufferSize, blockOnMatch, onFiltered)
116
+ }
117
+
118
+ function createDeltaStrategy(
119
+ rules: Array<ContentGuardRule>,
120
+ blockOnMatch: boolean,
121
+ onFiltered?: (info: ContentFilteredInfo) => void,
122
+ ): ChatMiddleware {
123
+ return {
124
+ name: 'content-guard',
125
+
126
+ onChunk(_ctx: ChatMiddlewareContext, chunk: StreamChunk) {
127
+ if (chunk.type !== 'TEXT_MESSAGE_CONTENT') return
128
+
129
+ const original = chunk.delta
130
+ const filtered = applyRules(original, rules)
131
+
132
+ if (filtered === original) return // unchanged, pass through
133
+
134
+ if (onFiltered) {
135
+ onFiltered({
136
+ messageId: chunk.messageId,
137
+ original,
138
+ filtered,
139
+ strategy: 'delta',
140
+ })
141
+ }
142
+
143
+ if (blockOnMatch) return null // drop chunk
144
+
145
+ return {
146
+ ...chunk,
147
+ delta: filtered,
148
+ content: undefined,
149
+ } as StreamChunk
150
+ },
151
+ }
152
+ }
153
+
154
+ function createBufferedStrategy(
155
+ rules: Array<ContentGuardRule>,
156
+ bufferSize: number,
157
+ blockOnMatch: boolean,
158
+ onFiltered?: (info: ContentFilteredInfo) => void,
159
+ ): ChatMiddleware {
160
+ let rawAccumulated = ''
161
+ let emittedFilteredLength = 0
162
+ let lastMessageId = ''
163
+
164
+ function resetState() {
165
+ rawAccumulated = ''
166
+ emittedFilteredLength = 0
167
+ lastMessageId = ''
168
+ }
169
+
170
+ function flushBuffer(): StreamChunk | null {
171
+ if (rawAccumulated.length === 0) return null
172
+
173
+ const filtered = applyRules(rawAccumulated, rules)
174
+
175
+ if (blockOnMatch && filtered !== rawAccumulated) {
176
+ if (onFiltered) {
177
+ onFiltered({
178
+ messageId: lastMessageId,
179
+ original: rawAccumulated,
180
+ filtered,
181
+ strategy: 'buffered',
182
+ })
183
+ }
184
+ resetState()
185
+ return null
186
+ }
187
+
188
+ const remaining = filtered.slice(emittedFilteredLength)
189
+ if (remaining.length > 0) {
190
+ if (filtered !== rawAccumulated && onFiltered) {
191
+ onFiltered({
192
+ messageId: lastMessageId,
193
+ original: rawAccumulated,
194
+ filtered,
195
+ strategy: 'buffered',
196
+ })
197
+ }
198
+
199
+ const flushed = {
200
+ type: 'TEXT_MESSAGE_CONTENT',
201
+ messageId: lastMessageId,
202
+ delta: remaining,
203
+ content: filtered,
204
+ timestamp: Date.now(),
205
+ } as StreamChunk
206
+
207
+ resetState()
208
+ return flushed
209
+ }
210
+
211
+ resetState()
212
+ return null
213
+ }
214
+
215
+ return {
216
+ name: 'content-guard',
217
+
218
+ onStart() {
219
+ resetState()
220
+ },
221
+
222
+ onChunk(_ctx: ChatMiddlewareContext, chunk: StreamChunk) {
223
+ // Flush buffer on stream end events
224
+ if (chunk.type === 'TEXT_MESSAGE_END' || chunk.type === 'RUN_FINISHED') {
225
+ const flushed = flushBuffer()
226
+ if (flushed) return [flushed, chunk]
227
+ return // pass through end event
228
+ }
229
+
230
+ if (chunk.type !== 'TEXT_MESSAGE_CONTENT') return // pass through
231
+
232
+ // Flush buffer on message boundary change
233
+ const pending: Array<StreamChunk> = []
234
+ if (lastMessageId && chunk.messageId !== lastMessageId) {
235
+ const flushed = flushBuffer()
236
+ if (flushed) pending.push(flushed)
237
+ }
238
+
239
+ rawAccumulated += chunk.delta
240
+ lastMessageId = chunk.messageId
241
+
242
+ // Apply rules to full accumulated text, buffer in filtered space
243
+ const filtered = applyRules(rawAccumulated, rules)
244
+ const safeFilteredEnd = Math.max(0, filtered.length - bufferSize)
245
+
246
+ if (safeFilteredEnd <= emittedFilteredLength) {
247
+ return pending.length > 0 ? pending : null
248
+ }
249
+
250
+ if (blockOnMatch && filtered !== rawAccumulated) {
251
+ if (onFiltered) {
252
+ onFiltered({
253
+ messageId: chunk.messageId,
254
+ original: rawAccumulated,
255
+ filtered,
256
+ strategy: 'buffered',
257
+ })
258
+ }
259
+ return pending.length > 0 ? pending : null
260
+ }
261
+
262
+ const newDelta = filtered.slice(emittedFilteredLength, safeFilteredEnd)
263
+
264
+ if (filtered !== rawAccumulated && onFiltered) {
265
+ onFiltered({
266
+ messageId: chunk.messageId,
267
+ original: rawAccumulated,
268
+ filtered,
269
+ strategy: 'buffered',
270
+ })
271
+ }
272
+
273
+ emittedFilteredLength = safeFilteredEnd
274
+
275
+ const emitChunk = {
276
+ ...chunk,
277
+ delta: newDelta,
278
+ content: filtered.slice(0, safeFilteredEnd),
279
+ } as StreamChunk
280
+
281
+ pending.push(emitChunk)
282
+ return pending.length === 1 ? pending[0]! : pending
283
+ },
284
+ }
285
+ }
@@ -0,0 +1,13 @@
1
+ export {
2
+ toolCacheMiddleware,
3
+ type ToolCacheMiddlewareOptions,
4
+ type ToolCacheStorage,
5
+ type ToolCacheEntry,
6
+ } from './tool-cache'
7
+
8
+ export {
9
+ contentGuardMiddleware,
10
+ type ContentGuardMiddlewareOptions,
11
+ type ContentGuardRule,
12
+ type ContentFilteredInfo,
13
+ } from './content-guard'
@@ -0,0 +1,6 @@
1
+ export {
2
+ toolCacheMiddleware,
3
+ type ToolCacheMiddlewareOptions,
4
+ type ToolCacheStorage,
5
+ type ToolCacheEntry,
6
+ } from '../activities/chat/middleware/tool-cache-middleware'
@@ -0,0 +1,38 @@
1
+ import type { RealtimeToken, RealtimeTokenOptions } from './types'
2
+
3
+ // Re-export all types
4
+ export * from './types'
5
+
6
+ /**
7
+ * Generate a realtime token using the provided adapter.
8
+ *
9
+ * This function is used on the server to generate ephemeral tokens
10
+ * that clients can use to establish realtime connections.
11
+ *
12
+ * @param options - Token generation options including the adapter
13
+ * @returns Promise resolving to a RealtimeToken
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * import { realtimeToken } from '@tanstack/ai'
18
+ * import { openaiRealtimeToken } from '@tanstack/ai-openai'
19
+ *
20
+ * // Server function (TanStack Start example)
21
+ * export const getRealtimeToken = createServerFn()
22
+ * .handler(async () => {
23
+ * return realtimeToken({
24
+ * adapter: openaiRealtimeToken({
25
+ * model: 'gpt-4o-realtime-preview',
26
+ * voice: 'alloy',
27
+ * instructions: 'You are a helpful assistant...',
28
+ * }),
29
+ * })
30
+ * })
31
+ * ```
32
+ */
33
+ export async function realtimeToken(
34
+ options: RealtimeTokenOptions,
35
+ ): Promise<RealtimeToken> {
36
+ const { adapter } = options
37
+ return adapter.generateToken()
38
+ }
@@ -0,0 +1,294 @@
1
+ // ============================================================================
2
+ // Token Types
3
+ // ============================================================================
4
+
5
+ /**
6
+ * Voice activity detection configuration
7
+ */
8
+ export interface VADConfig {
9
+ /** Sensitivity threshold (0.0-1.0) */
10
+ threshold?: number
11
+ /** Audio to include before speech detection (ms) */
12
+ prefixPaddingMs?: number
13
+ /** Silence duration to end turn (ms) */
14
+ silenceDurationMs?: number
15
+ }
16
+
17
+ /**
18
+ * Serializable tool descriptor for realtime session configuration.
19
+ * Contains only the metadata needed by providers, not Zod schemas or execute functions.
20
+ */
21
+ export interface RealtimeToolConfig {
22
+ name: string
23
+ description: string
24
+ inputSchema?: Record<string, any>
25
+ }
26
+
27
+ /**
28
+ * Configuration for a realtime session
29
+ */
30
+ export interface RealtimeSessionConfig {
31
+ /** Model to use for the session */
32
+ model?: string
33
+ /** Voice to use for audio output */
34
+ voice?: string
35
+ /** System instructions for the assistant */
36
+ instructions?: string
37
+ /** Tools available in the session */
38
+ tools?: Array<RealtimeToolConfig>
39
+ /** VAD mode */
40
+ vadMode?: 'server' | 'semantic' | 'manual'
41
+ /** VAD configuration */
42
+ vadConfig?: VADConfig
43
+ /** Output modalities for responses (e.g., ['audio', 'text'], ['text']) */
44
+ outputModalities?: Array<'audio' | 'text'>
45
+ /** Temperature for generation (provider-specific range, e.g., 0.6-1.2 for OpenAI) */
46
+ temperature?: number
47
+ /** Maximum number of tokens in a response */
48
+ maxOutputTokens?: number | 'inf'
49
+ /** Eagerness level for semantic VAD ('low', 'medium', 'high') */
50
+ semanticEagerness?: 'low' | 'medium' | 'high'
51
+ /** Provider-specific options */
52
+ providerOptions?: Record<string, any>
53
+ }
54
+
55
+ /**
56
+ * Token returned by the server for client authentication
57
+ */
58
+ export interface RealtimeToken {
59
+ /** Provider identifier */
60
+ provider: string
61
+ /** The ephemeral token value */
62
+ token: string
63
+ /** Token expiration timestamp (ms since epoch) */
64
+ expiresAt: number
65
+ /** Session configuration embedded in the token */
66
+ config: RealtimeSessionConfig
67
+ }
68
+
69
+ /**
70
+ * Adapter interface for generating provider-specific tokens
71
+ */
72
+ export interface RealtimeTokenAdapter {
73
+ /** Provider identifier */
74
+ provider: string
75
+ /** Generate an ephemeral token for client use */
76
+ generateToken: () => Promise<RealtimeToken>
77
+ }
78
+
79
+ /**
80
+ * Options for the realtimeToken function
81
+ */
82
+ export interface RealtimeTokenOptions {
83
+ /** The token adapter to use */
84
+ adapter: RealtimeTokenAdapter
85
+ }
86
+
87
+ // ============================================================================
88
+ // Message Types
89
+ // ============================================================================
90
+
91
+ /**
92
+ * Text content part in a realtime message
93
+ */
94
+ export interface RealtimeTextPart {
95
+ type: 'text'
96
+ content: string
97
+ }
98
+
99
+ /**
100
+ * Audio content part in a realtime message
101
+ */
102
+ export interface RealtimeAudioPart {
103
+ type: 'audio'
104
+ /** Transcription of the audio */
105
+ transcript: string
106
+ /** Raw audio data (optional, if stored) */
107
+ audioData?: ArrayBuffer
108
+ /** Duration of the audio in milliseconds */
109
+ durationMs?: number
110
+ }
111
+
112
+ /**
113
+ * Tool call part in a realtime message
114
+ */
115
+ export interface RealtimeToolCallPart {
116
+ type: 'tool-call'
117
+ id: string
118
+ name: string
119
+ arguments: string
120
+ input?: unknown
121
+ output?: unknown
122
+ }
123
+
124
+ /**
125
+ * Tool result part in a realtime message
126
+ */
127
+ export interface RealtimeToolResultPart {
128
+ type: 'tool-result'
129
+ toolCallId: string
130
+ content: string
131
+ }
132
+
133
+ /**
134
+ * Image content part in a realtime message
135
+ */
136
+ export interface RealtimeImagePart {
137
+ type: 'image'
138
+ /** Base64-encoded image data or a URL */
139
+ data: string
140
+ /** MIME type of the image (e.g., 'image/png', 'image/jpeg') */
141
+ mimeType: string
142
+ }
143
+
144
+ /**
145
+ * Union of all realtime message parts
146
+ */
147
+ export type RealtimeMessagePart =
148
+ | RealtimeTextPart
149
+ | RealtimeAudioPart
150
+ | RealtimeToolCallPart
151
+ | RealtimeToolResultPart
152
+ | RealtimeImagePart
153
+
154
+ /**
155
+ * A message in a realtime conversation
156
+ */
157
+ export interface RealtimeMessage {
158
+ /** Unique message identifier */
159
+ id: string
160
+ /** Message role */
161
+ role: 'user' | 'assistant'
162
+ /** Timestamp when the message was created */
163
+ timestamp: number
164
+ /** Content parts of the message */
165
+ parts: Array<RealtimeMessagePart>
166
+ /** Whether this message was interrupted */
167
+ interrupted?: boolean
168
+ /** Reference to audio buffer if stored */
169
+ audioId?: string
170
+ /** Duration of the audio in milliseconds */
171
+ durationMs?: number
172
+ }
173
+
174
+ // ============================================================================
175
+ // Status Types
176
+ // ============================================================================
177
+
178
+ /**
179
+ * Connection status of the realtime client
180
+ */
181
+ export type RealtimeStatus =
182
+ | 'idle'
183
+ | 'connecting'
184
+ | 'connected'
185
+ | 'reconnecting'
186
+ | 'error'
187
+
188
+ /**
189
+ * Current mode of the realtime session
190
+ */
191
+ export type RealtimeMode = 'idle' | 'listening' | 'thinking' | 'speaking'
192
+
193
+ // ============================================================================
194
+ // Audio Visualization Types
195
+ // ============================================================================
196
+
197
+ /**
198
+ * Interface for accessing audio visualization data
199
+ */
200
+ export interface AudioVisualization {
201
+ /** Input volume level (0-1 normalized) */
202
+ readonly inputLevel: number
203
+ /** Output volume level (0-1 normalized) */
204
+ readonly outputLevel: number
205
+
206
+ /** Get frequency data for input audio visualization */
207
+ getInputFrequencyData: () => Uint8Array
208
+ /** Get frequency data for output audio visualization */
209
+ getOutputFrequencyData: () => Uint8Array
210
+
211
+ /** Get time domain data for input waveform */
212
+ getInputTimeDomainData: () => Uint8Array
213
+ /** Get time domain data for output waveform */
214
+ getOutputTimeDomainData: () => Uint8Array
215
+
216
+ /** Input sample rate */
217
+ readonly inputSampleRate: number
218
+ /** Output sample rate */
219
+ readonly outputSampleRate: number
220
+
221
+ /** Subscribe to raw input audio samples */
222
+ onInputAudio?: (
223
+ callback: (samples: Float32Array, sampleRate: number) => void,
224
+ ) => () => void
225
+ /** Subscribe to raw output audio samples */
226
+ onOutputAudio?: (
227
+ callback: (samples: Float32Array, sampleRate: number) => void,
228
+ ) => () => void
229
+ }
230
+
231
+ // ============================================================================
232
+ // Event Types
233
+ // ============================================================================
234
+
235
+ /**
236
+ * Events emitted by the realtime connection
237
+ */
238
+ export type RealtimeEvent =
239
+ | 'status_change'
240
+ | 'mode_change'
241
+ | 'transcript'
242
+ | 'audio_chunk'
243
+ | 'tool_call'
244
+ | 'message_complete'
245
+ | 'interrupted'
246
+ | 'error'
247
+
248
+ /**
249
+ * Event payloads for realtime events
250
+ */
251
+ export interface RealtimeEventPayloads {
252
+ status_change: { status: RealtimeStatus }
253
+ mode_change: { mode: RealtimeMode }
254
+ transcript: {
255
+ role: 'user' | 'assistant'
256
+ transcript: string
257
+ isFinal: boolean
258
+ }
259
+ audio_chunk: { data: ArrayBuffer; sampleRate: number }
260
+ tool_call: { toolCallId: string; toolName: string; input: unknown }
261
+ message_complete: { message: RealtimeMessage }
262
+ interrupted: { messageId?: string }
263
+ error: { error: Error }
264
+ }
265
+
266
+ /**
267
+ * Handler type for realtime events
268
+ */
269
+ export type RealtimeEventHandler<TEvent extends RealtimeEvent> = (
270
+ payload: RealtimeEventPayloads[TEvent],
271
+ ) => void
272
+
273
+ // ============================================================================
274
+ // Error Types
275
+ // ============================================================================
276
+
277
+ /**
278
+ * Error codes for realtime errors
279
+ */
280
+ export type RealtimeErrorCode =
281
+ | 'TOKEN_EXPIRED'
282
+ | 'CONNECTION_FAILED'
283
+ | 'PERMISSION_DENIED'
284
+ | 'PROVIDER_ERROR'
285
+ | 'UNKNOWN'
286
+
287
+ /**
288
+ * Extended error with realtime-specific information
289
+ */
290
+ export interface RealtimeError extends Error {
291
+ code: RealtimeErrorCode
292
+ provider?: string
293
+ details?: unknown
294
+ }
package/src/types.ts CHANGED
@@ -730,6 +730,7 @@ export type AGUIEventType =
730
730
  | 'TOOL_CALL_END'
731
731
  | 'STEP_STARTED'
732
732
  | 'STEP_FINISHED'
733
+ | 'MESSAGES_SNAPSHOT'
733
734
  | 'STATE_SNAPSHOT'
734
735
  | 'STATE_DELTA'
735
736
  | 'CUSTOM'
@@ -806,8 +807,8 @@ export interface TextMessageStartEvent extends BaseAGUIEvent {
806
807
  type: 'TEXT_MESSAGE_START'
807
808
  /** Unique identifier for this message */
808
809
  messageId: string
809
- /** Role is always assistant for generated messages */
810
- role: 'assistant'
810
+ /** Role of the message sender */
811
+ role: 'user' | 'assistant' | 'system' | 'tool'
811
812
  }
812
813
 
813
814
  /**
@@ -841,6 +842,8 @@ export interface ToolCallStartEvent extends BaseAGUIEvent {
841
842
  toolCallId: string
842
843
  /** Name of the tool being called */
843
844
  toolName: string
845
+ /** ID of the parent message that initiated this tool call */
846
+ parentMessageId?: string
844
847
  /** Index for parallel tool calls */
845
848
  index?: number
846
849
  }
@@ -897,6 +900,19 @@ export interface StepFinishedEvent extends BaseAGUIEvent {
897
900
  content?: string
898
901
  }
899
902
 
903
+ /**
904
+ * Emitted to provide a snapshot of all messages in a conversation.
905
+ *
906
+ * Unlike StateSnapshot (which carries arbitrary application state),
907
+ * MessagesSnapshot specifically delivers the conversation transcript.
908
+ * This is a first-class AG-UI event type.
909
+ */
910
+ export interface MessagesSnapshotEvent extends BaseAGUIEvent {
911
+ type: 'MESSAGES_SNAPSHOT'
912
+ /** Complete array of messages in the conversation */
913
+ messages: Array<UIMessage>
914
+ }
915
+
900
916
  /**
901
917
  * Emitted to provide a full state snapshot.
902
918
  */
@@ -941,6 +957,7 @@ export type AGUIEvent =
941
957
  | ToolCallEndEvent
942
958
  | StepStartedEvent
943
959
  | StepFinishedEvent
960
+ | MessagesSnapshotEvent
944
961
  | StateSnapshotEvent
945
962
  | StateDeltaEvent
946
963
  | CustomEvent