@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,155 @@
1
+ function applyRules(text, rules) {
2
+ let result = text;
3
+ for (const rule of rules) {
4
+ if ("pattern" in rule) {
5
+ result = result.replace(rule.pattern, rule.replacement);
6
+ } else {
7
+ result = rule.fn(result);
8
+ }
9
+ }
10
+ return result;
11
+ }
12
+ function contentGuardMiddleware(options) {
13
+ const {
14
+ rules,
15
+ strategy = "buffered",
16
+ bufferSize = 50,
17
+ blockOnMatch = false,
18
+ onFiltered
19
+ } = options;
20
+ if (strategy === "delta") {
21
+ return createDeltaStrategy(rules, blockOnMatch, onFiltered);
22
+ }
23
+ return createBufferedStrategy(rules, bufferSize, blockOnMatch, onFiltered);
24
+ }
25
+ function createDeltaStrategy(rules, blockOnMatch, onFiltered) {
26
+ return {
27
+ name: "content-guard",
28
+ onChunk(_ctx, chunk) {
29
+ if (chunk.type !== "TEXT_MESSAGE_CONTENT") return;
30
+ const original = chunk.delta;
31
+ const filtered = applyRules(original, rules);
32
+ if (filtered === original) return;
33
+ if (onFiltered) {
34
+ onFiltered({
35
+ messageId: chunk.messageId,
36
+ original,
37
+ filtered,
38
+ strategy: "delta"
39
+ });
40
+ }
41
+ if (blockOnMatch) return null;
42
+ return {
43
+ ...chunk,
44
+ delta: filtered,
45
+ content: void 0
46
+ };
47
+ }
48
+ };
49
+ }
50
+ function createBufferedStrategy(rules, bufferSize, blockOnMatch, onFiltered) {
51
+ let rawAccumulated = "";
52
+ let emittedFilteredLength = 0;
53
+ let lastMessageId = "";
54
+ function resetState() {
55
+ rawAccumulated = "";
56
+ emittedFilteredLength = 0;
57
+ lastMessageId = "";
58
+ }
59
+ function flushBuffer() {
60
+ if (rawAccumulated.length === 0) return null;
61
+ const filtered = applyRules(rawAccumulated, rules);
62
+ if (blockOnMatch && filtered !== rawAccumulated) {
63
+ if (onFiltered) {
64
+ onFiltered({
65
+ messageId: lastMessageId,
66
+ original: rawAccumulated,
67
+ filtered,
68
+ strategy: "buffered"
69
+ });
70
+ }
71
+ resetState();
72
+ return null;
73
+ }
74
+ const remaining = filtered.slice(emittedFilteredLength);
75
+ if (remaining.length > 0) {
76
+ if (filtered !== rawAccumulated && onFiltered) {
77
+ onFiltered({
78
+ messageId: lastMessageId,
79
+ original: rawAccumulated,
80
+ filtered,
81
+ strategy: "buffered"
82
+ });
83
+ }
84
+ const flushed = {
85
+ type: "TEXT_MESSAGE_CONTENT",
86
+ messageId: lastMessageId,
87
+ delta: remaining,
88
+ content: filtered,
89
+ timestamp: Date.now()
90
+ };
91
+ resetState();
92
+ return flushed;
93
+ }
94
+ resetState();
95
+ return null;
96
+ }
97
+ return {
98
+ name: "content-guard",
99
+ onStart() {
100
+ resetState();
101
+ },
102
+ onChunk(_ctx, chunk) {
103
+ if (chunk.type === "TEXT_MESSAGE_END" || chunk.type === "RUN_FINISHED") {
104
+ const flushed = flushBuffer();
105
+ if (flushed) return [flushed, chunk];
106
+ return;
107
+ }
108
+ if (chunk.type !== "TEXT_MESSAGE_CONTENT") return;
109
+ const pending = [];
110
+ if (lastMessageId && chunk.messageId !== lastMessageId) {
111
+ const flushed = flushBuffer();
112
+ if (flushed) pending.push(flushed);
113
+ }
114
+ rawAccumulated += chunk.delta;
115
+ lastMessageId = chunk.messageId;
116
+ const filtered = applyRules(rawAccumulated, rules);
117
+ const safeFilteredEnd = Math.max(0, filtered.length - bufferSize);
118
+ if (safeFilteredEnd <= emittedFilteredLength) {
119
+ return pending.length > 0 ? pending : null;
120
+ }
121
+ if (blockOnMatch && filtered !== rawAccumulated) {
122
+ if (onFiltered) {
123
+ onFiltered({
124
+ messageId: chunk.messageId,
125
+ original: rawAccumulated,
126
+ filtered,
127
+ strategy: "buffered"
128
+ });
129
+ }
130
+ return pending.length > 0 ? pending : null;
131
+ }
132
+ const newDelta = filtered.slice(emittedFilteredLength, safeFilteredEnd);
133
+ if (filtered !== rawAccumulated && onFiltered) {
134
+ onFiltered({
135
+ messageId: chunk.messageId,
136
+ original: rawAccumulated,
137
+ filtered,
138
+ strategy: "buffered"
139
+ });
140
+ }
141
+ emittedFilteredLength = safeFilteredEnd;
142
+ const emitChunk = {
143
+ ...chunk,
144
+ delta: newDelta,
145
+ content: filtered.slice(0, safeFilteredEnd)
146
+ };
147
+ pending.push(emitChunk);
148
+ return pending.length === 1 ? pending[0] : pending;
149
+ }
150
+ };
151
+ }
152
+ export {
153
+ contentGuardMiddleware
154
+ };
155
+ //# sourceMappingURL=content-guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-guard.js","sources":["../../../src/middlewares/content-guard.ts"],"sourcesContent":["import type {\n ChatMiddleware,\n ChatMiddlewareContext,\n} from '../activities/chat/middleware/types'\nimport type { StreamChunk } from '../types'\n\n/**\n * A content guard rule — either a regex pattern with replacement, or a transform function.\n */\nexport type ContentGuardRule =\n | { pattern: RegExp; replacement: string }\n | { fn: (text: string) => string }\n\n/**\n * Information passed to the onFiltered callback.\n */\nexport interface ContentFilteredInfo {\n /** The message ID being filtered */\n messageId: string\n /** The original text before filtering */\n original: string\n /** The filtered text after rules applied */\n filtered: string\n /** Which strategy was used */\n strategy: 'delta' | 'buffered'\n}\n\n/**\n * Options for the content guard middleware.\n */\nexport interface ContentGuardMiddlewareOptions {\n /**\n * Rules to apply to text content. Each rule is either a regex pattern\n * with a replacement string, or a custom transform function.\n * Rules are applied in order. Each rule receives the output of the previous.\n */\n rules: Array<ContentGuardRule>\n\n /**\n * Matching strategy:\n * - 'delta': Apply rules to each delta as it arrives. Fast, real-time,\n * but patterns spanning chunk boundaries may be missed.\n * - 'buffered': Accumulate content and apply rules to settled portions,\n * holding back a look-behind buffer to catch cross-boundary patterns.\n *\n * @default 'buffered'\n */\n strategy?: 'delta' | 'buffered'\n\n /**\n * Number of characters to hold back before emitting (buffered strategy only).\n * Should be at least as long as the longest pattern you expect to match.\n * Buffer is flushed when the stream ends.\n *\n * @default 50\n */\n bufferSize?: number\n\n /**\n * If true, drop the entire chunk when any rule changes the content.\n * @default false\n */\n blockOnMatch?: boolean\n\n /**\n * Callback when content is filtered by any rule.\n */\n onFiltered?: (info: ContentFilteredInfo) => void\n}\n\n/**\n * Apply all rules to a string, returning the transformed result.\n */\nfunction applyRules(text: string, rules: Array<ContentGuardRule>): string {\n let result = text\n for (const rule of rules) {\n if ('pattern' in rule) {\n result = result.replace(rule.pattern, rule.replacement)\n } else {\n result = rule.fn(result)\n }\n }\n return result\n}\n\n/**\n * Creates a middleware that filters or transforms streamed text content.\n *\n * @example\n * ```ts\n * import { contentGuardMiddleware } from '@tanstack/ai/middlewares'\n *\n * const guard = contentGuardMiddleware({\n * rules: [\n * { pattern: /\\b\\d{3}-\\d{2}-\\d{4}\\b/g, replacement: '[SSN REDACTED]' },\n * ],\n * strategy: 'buffered',\n * })\n * ```\n */\nexport function contentGuardMiddleware(\n options: ContentGuardMiddlewareOptions,\n): ChatMiddleware {\n const {\n rules,\n strategy = 'buffered',\n bufferSize = 50,\n blockOnMatch = false,\n onFiltered,\n } = options\n\n if (strategy === 'delta') {\n return createDeltaStrategy(rules, blockOnMatch, onFiltered)\n }\n return createBufferedStrategy(rules, bufferSize, blockOnMatch, onFiltered)\n}\n\nfunction createDeltaStrategy(\n rules: Array<ContentGuardRule>,\n blockOnMatch: boolean,\n onFiltered?: (info: ContentFilteredInfo) => void,\n): ChatMiddleware {\n return {\n name: 'content-guard',\n\n onChunk(_ctx: ChatMiddlewareContext, chunk: StreamChunk) {\n if (chunk.type !== 'TEXT_MESSAGE_CONTENT') return\n\n const original = chunk.delta\n const filtered = applyRules(original, rules)\n\n if (filtered === original) return // unchanged, pass through\n\n if (onFiltered) {\n onFiltered({\n messageId: chunk.messageId,\n original,\n filtered,\n strategy: 'delta',\n })\n }\n\n if (blockOnMatch) return null // drop chunk\n\n return {\n ...chunk,\n delta: filtered,\n content: undefined,\n } as StreamChunk\n },\n }\n}\n\nfunction createBufferedStrategy(\n rules: Array<ContentGuardRule>,\n bufferSize: number,\n blockOnMatch: boolean,\n onFiltered?: (info: ContentFilteredInfo) => void,\n): ChatMiddleware {\n let rawAccumulated = ''\n let emittedFilteredLength = 0\n let lastMessageId = ''\n\n function resetState() {\n rawAccumulated = ''\n emittedFilteredLength = 0\n lastMessageId = ''\n }\n\n function flushBuffer(): StreamChunk | null {\n if (rawAccumulated.length === 0) return null\n\n const filtered = applyRules(rawAccumulated, rules)\n\n if (blockOnMatch && filtered !== rawAccumulated) {\n if (onFiltered) {\n onFiltered({\n messageId: lastMessageId,\n original: rawAccumulated,\n filtered,\n strategy: 'buffered',\n })\n }\n resetState()\n return null\n }\n\n const remaining = filtered.slice(emittedFilteredLength)\n if (remaining.length > 0) {\n if (filtered !== rawAccumulated && onFiltered) {\n onFiltered({\n messageId: lastMessageId,\n original: rawAccumulated,\n filtered,\n strategy: 'buffered',\n })\n }\n\n const flushed = {\n type: 'TEXT_MESSAGE_CONTENT',\n messageId: lastMessageId,\n delta: remaining,\n content: filtered,\n timestamp: Date.now(),\n } as StreamChunk\n\n resetState()\n return flushed\n }\n\n resetState()\n return null\n }\n\n return {\n name: 'content-guard',\n\n onStart() {\n resetState()\n },\n\n onChunk(_ctx: ChatMiddlewareContext, chunk: StreamChunk) {\n // Flush buffer on stream end events\n if (chunk.type === 'TEXT_MESSAGE_END' || chunk.type === 'RUN_FINISHED') {\n const flushed = flushBuffer()\n if (flushed) return [flushed, chunk]\n return // pass through end event\n }\n\n if (chunk.type !== 'TEXT_MESSAGE_CONTENT') return // pass through\n\n // Flush buffer on message boundary change\n const pending: Array<StreamChunk> = []\n if (lastMessageId && chunk.messageId !== lastMessageId) {\n const flushed = flushBuffer()\n if (flushed) pending.push(flushed)\n }\n\n rawAccumulated += chunk.delta\n lastMessageId = chunk.messageId\n\n // Apply rules to full accumulated text, buffer in filtered space\n const filtered = applyRules(rawAccumulated, rules)\n const safeFilteredEnd = Math.max(0, filtered.length - bufferSize)\n\n if (safeFilteredEnd <= emittedFilteredLength) {\n return pending.length > 0 ? pending : null\n }\n\n if (blockOnMatch && filtered !== rawAccumulated) {\n if (onFiltered) {\n onFiltered({\n messageId: chunk.messageId,\n original: rawAccumulated,\n filtered,\n strategy: 'buffered',\n })\n }\n return pending.length > 0 ? pending : null\n }\n\n const newDelta = filtered.slice(emittedFilteredLength, safeFilteredEnd)\n\n if (filtered !== rawAccumulated && onFiltered) {\n onFiltered({\n messageId: chunk.messageId,\n original: rawAccumulated,\n filtered,\n strategy: 'buffered',\n })\n }\n\n emittedFilteredLength = safeFilteredEnd\n\n const emitChunk = {\n ...chunk,\n delta: newDelta,\n content: filtered.slice(0, safeFilteredEnd),\n } as StreamChunk\n\n pending.push(emitChunk)\n return pending.length === 1 ? pending[0]! : pending\n },\n }\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export { toolCacheMiddleware, type ToolCacheMiddlewareOptions, type ToolCacheStorage, type ToolCacheEntry, } from './tool-cache.js';
2
+ export { contentGuardMiddleware, type ContentGuardMiddlewareOptions, type ContentGuardRule, type ContentFilteredInfo, } from './content-guard.js';
@@ -0,0 +1,7 @@
1
+ import { contentGuardMiddleware } from "./content-guard.js";
2
+ import { toolCacheMiddleware } from "../activities/chat/middleware/tool-cache-middleware.js";
3
+ export {
4
+ contentGuardMiddleware,
5
+ toolCacheMiddleware
6
+ };
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;"}
@@ -0,0 +1 @@
1
+ export { toolCacheMiddleware, type ToolCacheMiddlewareOptions, type ToolCacheStorage, type ToolCacheEntry, } from '../activities/chat/middleware/tool-cache-middleware.js';
@@ -0,0 +1,30 @@
1
+ import { RealtimeToken, RealtimeTokenOptions } from './types.js';
2
+ export * from './types.js';
3
+ /**
4
+ * Generate a realtime token using the provided adapter.
5
+ *
6
+ * This function is used on the server to generate ephemeral tokens
7
+ * that clients can use to establish realtime connections.
8
+ *
9
+ * @param options - Token generation options including the adapter
10
+ * @returns Promise resolving to a RealtimeToken
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * import { realtimeToken } from '@tanstack/ai'
15
+ * import { openaiRealtimeToken } from '@tanstack/ai-openai'
16
+ *
17
+ * // Server function (TanStack Start example)
18
+ * export const getRealtimeToken = createServerFn()
19
+ * .handler(async () => {
20
+ * return realtimeToken({
21
+ * adapter: openaiRealtimeToken({
22
+ * model: 'gpt-4o-realtime-preview',
23
+ * voice: 'alloy',
24
+ * instructions: 'You are a helpful assistant...',
25
+ * }),
26
+ * })
27
+ * })
28
+ * ```
29
+ */
30
+ export declare function realtimeToken(options: RealtimeTokenOptions): Promise<RealtimeToken>;
@@ -0,0 +1,8 @@
1
+ async function realtimeToken(options) {
2
+ const { adapter } = options;
3
+ return adapter.generateToken();
4
+ }
5
+ export {
6
+ realtimeToken
7
+ };
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../../../src/realtime/index.ts"],"sourcesContent":["import type { RealtimeToken, RealtimeTokenOptions } from './types'\n\n// Re-export all types\nexport * from './types'\n\n/**\n * Generate a realtime token using the provided adapter.\n *\n * This function is used on the server to generate ephemeral tokens\n * that clients can use to establish realtime connections.\n *\n * @param options - Token generation options including the adapter\n * @returns Promise resolving to a RealtimeToken\n *\n * @example\n * ```typescript\n * import { realtimeToken } from '@tanstack/ai'\n * import { openaiRealtimeToken } from '@tanstack/ai-openai'\n *\n * // Server function (TanStack Start example)\n * export const getRealtimeToken = createServerFn()\n * .handler(async () => {\n * return realtimeToken({\n * adapter: openaiRealtimeToken({\n * model: 'gpt-4o-realtime-preview',\n * voice: 'alloy',\n * instructions: 'You are a helpful assistant...',\n * }),\n * })\n * })\n * ```\n */\nexport async function realtimeToken(\n options: RealtimeTokenOptions,\n): Promise<RealtimeToken> {\n const { adapter } = options\n return adapter.generateToken()\n}\n"],"names":[],"mappings":"AAgCA,eAAsB,cACpB,SACwB;AACxB,QAAM,EAAE,YAAY;AACpB,SAAO,QAAQ,cAAA;AACjB;"}
@@ -0,0 +1,234 @@
1
+ /**
2
+ * Voice activity detection configuration
3
+ */
4
+ export interface VADConfig {
5
+ /** Sensitivity threshold (0.0-1.0) */
6
+ threshold?: number;
7
+ /** Audio to include before speech detection (ms) */
8
+ prefixPaddingMs?: number;
9
+ /** Silence duration to end turn (ms) */
10
+ silenceDurationMs?: number;
11
+ }
12
+ /**
13
+ * Serializable tool descriptor for realtime session configuration.
14
+ * Contains only the metadata needed by providers, not Zod schemas or execute functions.
15
+ */
16
+ export interface RealtimeToolConfig {
17
+ name: string;
18
+ description: string;
19
+ inputSchema?: Record<string, any>;
20
+ }
21
+ /**
22
+ * Configuration for a realtime session
23
+ */
24
+ export interface RealtimeSessionConfig {
25
+ /** Model to use for the session */
26
+ model?: string;
27
+ /** Voice to use for audio output */
28
+ voice?: string;
29
+ /** System instructions for the assistant */
30
+ instructions?: string;
31
+ /** Tools available in the session */
32
+ tools?: Array<RealtimeToolConfig>;
33
+ /** VAD mode */
34
+ vadMode?: 'server' | 'semantic' | 'manual';
35
+ /** VAD configuration */
36
+ vadConfig?: VADConfig;
37
+ /** Output modalities for responses (e.g., ['audio', 'text'], ['text']) */
38
+ outputModalities?: Array<'audio' | 'text'>;
39
+ /** Temperature for generation (provider-specific range, e.g., 0.6-1.2 for OpenAI) */
40
+ temperature?: number;
41
+ /** Maximum number of tokens in a response */
42
+ maxOutputTokens?: number | 'inf';
43
+ /** Eagerness level for semantic VAD ('low', 'medium', 'high') */
44
+ semanticEagerness?: 'low' | 'medium' | 'high';
45
+ /** Provider-specific options */
46
+ providerOptions?: Record<string, any>;
47
+ }
48
+ /**
49
+ * Token returned by the server for client authentication
50
+ */
51
+ export interface RealtimeToken {
52
+ /** Provider identifier */
53
+ provider: string;
54
+ /** The ephemeral token value */
55
+ token: string;
56
+ /** Token expiration timestamp (ms since epoch) */
57
+ expiresAt: number;
58
+ /** Session configuration embedded in the token */
59
+ config: RealtimeSessionConfig;
60
+ }
61
+ /**
62
+ * Adapter interface for generating provider-specific tokens
63
+ */
64
+ export interface RealtimeTokenAdapter {
65
+ /** Provider identifier */
66
+ provider: string;
67
+ /** Generate an ephemeral token for client use */
68
+ generateToken: () => Promise<RealtimeToken>;
69
+ }
70
+ /**
71
+ * Options for the realtimeToken function
72
+ */
73
+ export interface RealtimeTokenOptions {
74
+ /** The token adapter to use */
75
+ adapter: RealtimeTokenAdapter;
76
+ }
77
+ /**
78
+ * Text content part in a realtime message
79
+ */
80
+ export interface RealtimeTextPart {
81
+ type: 'text';
82
+ content: string;
83
+ }
84
+ /**
85
+ * Audio content part in a realtime message
86
+ */
87
+ export interface RealtimeAudioPart {
88
+ type: 'audio';
89
+ /** Transcription of the audio */
90
+ transcript: string;
91
+ /** Raw audio data (optional, if stored) */
92
+ audioData?: ArrayBuffer;
93
+ /** Duration of the audio in milliseconds */
94
+ durationMs?: number;
95
+ }
96
+ /**
97
+ * Tool call part in a realtime message
98
+ */
99
+ export interface RealtimeToolCallPart {
100
+ type: 'tool-call';
101
+ id: string;
102
+ name: string;
103
+ arguments: string;
104
+ input?: unknown;
105
+ output?: unknown;
106
+ }
107
+ /**
108
+ * Tool result part in a realtime message
109
+ */
110
+ export interface RealtimeToolResultPart {
111
+ type: 'tool-result';
112
+ toolCallId: string;
113
+ content: string;
114
+ }
115
+ /**
116
+ * Image content part in a realtime message
117
+ */
118
+ export interface RealtimeImagePart {
119
+ type: 'image';
120
+ /** Base64-encoded image data or a URL */
121
+ data: string;
122
+ /** MIME type of the image (e.g., 'image/png', 'image/jpeg') */
123
+ mimeType: string;
124
+ }
125
+ /**
126
+ * Union of all realtime message parts
127
+ */
128
+ export type RealtimeMessagePart = RealtimeTextPart | RealtimeAudioPart | RealtimeToolCallPart | RealtimeToolResultPart | RealtimeImagePart;
129
+ /**
130
+ * A message in a realtime conversation
131
+ */
132
+ export interface RealtimeMessage {
133
+ /** Unique message identifier */
134
+ id: string;
135
+ /** Message role */
136
+ role: 'user' | 'assistant';
137
+ /** Timestamp when the message was created */
138
+ timestamp: number;
139
+ /** Content parts of the message */
140
+ parts: Array<RealtimeMessagePart>;
141
+ /** Whether this message was interrupted */
142
+ interrupted?: boolean;
143
+ /** Reference to audio buffer if stored */
144
+ audioId?: string;
145
+ /** Duration of the audio in milliseconds */
146
+ durationMs?: number;
147
+ }
148
+ /**
149
+ * Connection status of the realtime client
150
+ */
151
+ export type RealtimeStatus = 'idle' | 'connecting' | 'connected' | 'reconnecting' | 'error';
152
+ /**
153
+ * Current mode of the realtime session
154
+ */
155
+ export type RealtimeMode = 'idle' | 'listening' | 'thinking' | 'speaking';
156
+ /**
157
+ * Interface for accessing audio visualization data
158
+ */
159
+ export interface AudioVisualization {
160
+ /** Input volume level (0-1 normalized) */
161
+ readonly inputLevel: number;
162
+ /** Output volume level (0-1 normalized) */
163
+ readonly outputLevel: number;
164
+ /** Get frequency data for input audio visualization */
165
+ getInputFrequencyData: () => Uint8Array;
166
+ /** Get frequency data for output audio visualization */
167
+ getOutputFrequencyData: () => Uint8Array;
168
+ /** Get time domain data for input waveform */
169
+ getInputTimeDomainData: () => Uint8Array;
170
+ /** Get time domain data for output waveform */
171
+ getOutputTimeDomainData: () => Uint8Array;
172
+ /** Input sample rate */
173
+ readonly inputSampleRate: number;
174
+ /** Output sample rate */
175
+ readonly outputSampleRate: number;
176
+ /** Subscribe to raw input audio samples */
177
+ onInputAudio?: (callback: (samples: Float32Array, sampleRate: number) => void) => () => void;
178
+ /** Subscribe to raw output audio samples */
179
+ onOutputAudio?: (callback: (samples: Float32Array, sampleRate: number) => void) => () => void;
180
+ }
181
+ /**
182
+ * Events emitted by the realtime connection
183
+ */
184
+ export type RealtimeEvent = 'status_change' | 'mode_change' | 'transcript' | 'audio_chunk' | 'tool_call' | 'message_complete' | 'interrupted' | 'error';
185
+ /**
186
+ * Event payloads for realtime events
187
+ */
188
+ export interface RealtimeEventPayloads {
189
+ status_change: {
190
+ status: RealtimeStatus;
191
+ };
192
+ mode_change: {
193
+ mode: RealtimeMode;
194
+ };
195
+ transcript: {
196
+ role: 'user' | 'assistant';
197
+ transcript: string;
198
+ isFinal: boolean;
199
+ };
200
+ audio_chunk: {
201
+ data: ArrayBuffer;
202
+ sampleRate: number;
203
+ };
204
+ tool_call: {
205
+ toolCallId: string;
206
+ toolName: string;
207
+ input: unknown;
208
+ };
209
+ message_complete: {
210
+ message: RealtimeMessage;
211
+ };
212
+ interrupted: {
213
+ messageId?: string;
214
+ };
215
+ error: {
216
+ error: Error;
217
+ };
218
+ }
219
+ /**
220
+ * Handler type for realtime events
221
+ */
222
+ export type RealtimeEventHandler<TEvent extends RealtimeEvent> = (payload: RealtimeEventPayloads[TEvent]) => void;
223
+ /**
224
+ * Error codes for realtime errors
225
+ */
226
+ export type RealtimeErrorCode = 'TOKEN_EXPIRED' | 'CONNECTION_FAILED' | 'PERMISSION_DENIED' | 'PROVIDER_ERROR' | 'UNKNOWN';
227
+ /**
228
+ * Extended error with realtime-specific information
229
+ */
230
+ export interface RealtimeError extends Error {
231
+ code: RealtimeErrorCode;
232
+ provider?: string;
233
+ details?: unknown;
234
+ }
@@ -605,7 +605,7 @@ export interface TextOptions<TProviderOptionsSuperset extends Record<string, any
605
605
  * Based on the AG-UI specification for agent-user interaction.
606
606
  * @see https://docs.ag-ui.com/concepts/events
607
607
  */
608
- export type AGUIEventType = 'RUN_STARTED' | 'RUN_FINISHED' | 'RUN_ERROR' | 'TEXT_MESSAGE_START' | 'TEXT_MESSAGE_CONTENT' | 'TEXT_MESSAGE_END' | 'TOOL_CALL_START' | 'TOOL_CALL_ARGS' | 'TOOL_CALL_END' | 'STEP_STARTED' | 'STEP_FINISHED' | 'STATE_SNAPSHOT' | 'STATE_DELTA' | 'CUSTOM';
608
+ export type AGUIEventType = 'RUN_STARTED' | 'RUN_FINISHED' | 'RUN_ERROR' | 'TEXT_MESSAGE_START' | 'TEXT_MESSAGE_CONTENT' | 'TEXT_MESSAGE_END' | 'TOOL_CALL_START' | 'TOOL_CALL_ARGS' | 'TOOL_CALL_END' | 'STEP_STARTED' | 'STEP_FINISHED' | 'MESSAGES_SNAPSHOT' | 'STATE_SNAPSHOT' | 'STATE_DELTA' | 'CUSTOM';
609
609
  /**
610
610
  * Stream chunk/event types (AG-UI protocol).
611
611
  */
@@ -669,8 +669,8 @@ export interface TextMessageStartEvent extends BaseAGUIEvent {
669
669
  type: 'TEXT_MESSAGE_START';
670
670
  /** Unique identifier for this message */
671
671
  messageId: string;
672
- /** Role is always assistant for generated messages */
673
- role: 'assistant';
672
+ /** Role of the message sender */
673
+ role: 'user' | 'assistant' | 'system' | 'tool';
674
674
  }
675
675
  /**
676
676
  * Emitted when text content is generated (streaming tokens).
@@ -701,6 +701,8 @@ export interface ToolCallStartEvent extends BaseAGUIEvent {
701
701
  toolCallId: string;
702
702
  /** Name of the tool being called */
703
703
  toolName: string;
704
+ /** ID of the parent message that initiated this tool call */
705
+ parentMessageId?: string;
704
706
  /** Index for parallel tool calls */
705
707
  index?: number;
706
708
  }
@@ -752,6 +754,18 @@ export interface StepFinishedEvent extends BaseAGUIEvent {
752
754
  /** Full accumulated thinking content (optional, for debugging) */
753
755
  content?: string;
754
756
  }
757
+ /**
758
+ * Emitted to provide a snapshot of all messages in a conversation.
759
+ *
760
+ * Unlike StateSnapshot (which carries arbitrary application state),
761
+ * MessagesSnapshot specifically delivers the conversation transcript.
762
+ * This is a first-class AG-UI event type.
763
+ */
764
+ export interface MessagesSnapshotEvent extends BaseAGUIEvent {
765
+ type: 'MESSAGES_SNAPSHOT';
766
+ /** Complete array of messages in the conversation */
767
+ messages: Array<UIMessage>;
768
+ }
755
769
  /**
756
770
  * Emitted to provide a full state snapshot.
757
771
  */
@@ -781,7 +795,7 @@ export interface CustomEvent extends BaseAGUIEvent {
781
795
  /**
782
796
  * Union of all AG-UI events.
783
797
  */
784
- export type AGUIEvent = RunStartedEvent | RunFinishedEvent | RunErrorEvent | TextMessageStartEvent | TextMessageContentEvent | TextMessageEndEvent | ToolCallStartEvent | ToolCallArgsEvent | ToolCallEndEvent | StepStartedEvent | StepFinishedEvent | StateSnapshotEvent | StateDeltaEvent | CustomEvent;
798
+ export type AGUIEvent = RunStartedEvent | RunFinishedEvent | RunErrorEvent | TextMessageStartEvent | TextMessageContentEvent | TextMessageEndEvent | ToolCallStartEvent | ToolCallArgsEvent | ToolCallEndEvent | StepStartedEvent | StepFinishedEvent | MessagesSnapshotEvent | StateSnapshotEvent | StateDeltaEvent | CustomEvent;
785
799
  /**
786
800
  * Chunk returned by the SDK during streaming chat completions.
787
801
  * Uses the AG-UI protocol event format.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/ai",
3
- "version": "0.6.3",
3
+ "version": "0.8.1",
4
4
  "description": "Core TanStack AI library - Open source AI SDK",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -21,9 +21,9 @@
21
21
  "types": "./dist/esm/activities/index.d.ts",
22
22
  "import": "./dist/esm/activities/index.js"
23
23
  },
24
- "./event-client": {
25
- "types": "./dist/esm/event-client.d.ts",
26
- "import": "./dist/esm/event-client.js"
24
+ "./middlewares": {
25
+ "types": "./dist/esm/middlewares/index.d.ts",
26
+ "import": "./dist/esm/middlewares/index.js"
27
27
  }
28
28
  },
29
29
  "sideEffects": false,
@@ -43,8 +43,8 @@
43
43
  "embeddings"
44
44
  ],
45
45
  "dependencies": {
46
- "@tanstack/devtools-event-client": "^0.4.0",
47
- "partial-json": "^0.1.7"
46
+ "partial-json": "^0.1.7",
47
+ "@tanstack/ai-event-client": "0.1.1"
48
48
  },
49
49
  "devDependencies": {
50
50
  "@standard-schema/spec": "^1.1.0",