@tanstack/ai-svelte 0.13.15 → 0.14.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.
@@ -0,0 +1,37 @@
1
+ import type { AudioRecorderOptions, AudioRecording, InferAudioRecordingOutput } from '@tanstack/ai-client';
2
+ export type CreateAudioRecorderOptions<TOnComplete> = AudioRecorderOptions & {
3
+ /**
4
+ * Optional transform applied to the recording when `stop()` resolves. Its
5
+ * (awaited) return value becomes `recording` and the resolved value of
6
+ * `stop()`. Return nothing to keep the raw `AudioRecording`.
7
+ */
8
+ onComplete?: TOnComplete;
9
+ };
10
+ export interface CreateAudioRecorderReturn<TOutput> {
11
+ /** Reactive: latest recording (transformed if `onComplete` provided), or null. */
12
+ readonly recording: TOutput | null;
13
+ /** Reactive: true while actively capturing audio. */
14
+ readonly isRecording: boolean;
15
+ /** Whether the browser supports recording. */
16
+ readonly isSupported: boolean;
17
+ start: () => Promise<void>;
18
+ /** Stop and resolve with the completed recording (transformed if `onComplete` provided). */
19
+ stop: () => Promise<TOutput>;
20
+ /**
21
+ * Discard the in-progress recording and release the mic. Svelte 5 runes
22
+ * can't register automatic teardown here (matching `createChat`), so call
23
+ * this from your component's cleanup if a recording may still be active.
24
+ */
25
+ cancel: () => void;
26
+ }
27
+ /**
28
+ * Svelte 5 factory for recording an audio message. The resolved recording
29
+ * carries `.part` (for `createChat.sendMessage`) and `.base64` (for the
30
+ * generation factories).
31
+ *
32
+ * Errors are delivered via `onError`. `start()` and `stop()` also reject on
33
+ * failure (and `stop()` rejects with `Recording cancelled` if `cancel()` runs
34
+ * while a stop is in flight) — handle one channel, not both.
35
+ */
36
+ export declare function createAudioRecorder<TOnComplete extends (recording: AudioRecording) => unknown>(options: CreateAudioRecorderOptions<TOnComplete>): CreateAudioRecorderReturn<InferAudioRecordingOutput<TOnComplete>>;
37
+ export declare function createAudioRecorder(options?: CreateAudioRecorderOptions<undefined>): CreateAudioRecorderReturn<AudioRecording>;
@@ -0,0 +1,36 @@
1
+ import { AudioRecorder } from '@tanstack/ai-client';
2
+ export function createAudioRecorder(options = {}) {
3
+ const recorder = new AudioRecorder({
4
+ ...(options.audio !== undefined && { audio: options.audio }),
5
+ ...(options.mimeType !== undefined && { mimeType: options.mimeType }),
6
+ ...(options.onError !== undefined && { onError: options.onError }),
7
+ });
8
+ let isRecording = $state(false);
9
+ let recording = $state(null);
10
+ recorder.subscribe((state) => {
11
+ isRecording = state === 'recording';
12
+ });
13
+ const stop = async () => {
14
+ const rawRecording = await recorder.stop();
15
+ const transformed = await options.onComplete?.(rawRecording);
16
+ // Only `undefined` (returning nothing) keeps the raw recording; a returned
17
+ // null is a real value, matching the inferred output type.
18
+ const output = transformed === undefined ? rawRecording : transformed;
19
+ recording = output;
20
+ return output;
21
+ };
22
+ return {
23
+ get recording() {
24
+ return recording;
25
+ },
26
+ get isRecording() {
27
+ return isRecording;
28
+ },
29
+ get isSupported() {
30
+ return AudioRecorder.isSupported();
31
+ },
32
+ start: () => recorder.start(),
33
+ stop,
34
+ cancel: () => recorder.cancel(),
35
+ };
36
+ }
@@ -1,5 +1,5 @@
1
1
  import type { AudioGenerationResult, StreamChunk } from '@tanstack/ai';
2
- import type { AIDevtoolsDisplayOptions, AudioGenerateInput, ConnectConnectionAdapter, GenerationClientState, GenerationFetcher, InferGenerationOutput } from '@tanstack/ai-client';
2
+ import type { AIDevtoolsDisplayOptions, AudioGenerateInput, ConnectConnectionAdapter, GenerationClientState, GenerationFetcher, InferGenerationOutputFromReturn } from '@tanstack/ai-client';
3
3
  /**
4
4
  * Options for the createGenerateAudio function.
5
5
  *
@@ -80,6 +80,6 @@ export interface CreateGenerateAudioReturn<TOutput = AudioGenerationResult> {
80
80
  * </div>
81
81
  * ```
82
82
  */
83
- export declare function createGenerateAudio<TOnResult extends ((result: AudioGenerationResult) => any) | undefined = undefined>(options: Omit<CreateGenerateAudioOptions, 'onResult'> & {
84
- onResult?: TOnResult;
85
- }): CreateGenerateAudioReturn<InferGenerationOutput<AudioGenerationResult, TOnResult>>;
83
+ export declare function createGenerateAudio<TTransformed = void>(options: Omit<CreateGenerateAudioOptions, 'onResult'> & {
84
+ onResult?: (result: AudioGenerationResult) => TTransformed;
85
+ }): CreateGenerateAudioReturn<InferGenerationOutputFromReturn<AudioGenerationResult, TTransformed>>;
@@ -1,5 +1,5 @@
1
1
  import type { ImageGenerationResult, StreamChunk } from '@tanstack/ai';
2
- import type { AIDevtoolsDisplayOptions, ConnectConnectionAdapter, GenerationClientState, GenerationFetcher, ImageGenerateInput, InferGenerationOutput } from '@tanstack/ai-client';
2
+ import type { AIDevtoolsDisplayOptions, ConnectConnectionAdapter, GenerationClientState, GenerationFetcher, ImageGenerateInput, InferGenerationOutputFromReturn } from '@tanstack/ai-client';
3
3
  /**
4
4
  * Options for the createGenerateImage function.
5
5
  *
@@ -89,6 +89,6 @@ export interface CreateGenerateImageReturn<TOutput = ImageGenerationResult> {
89
89
  * </div>
90
90
  * ```
91
91
  */
92
- export declare function createGenerateImage<TOnResult extends ((result: ImageGenerationResult) => any) | undefined = undefined>(options: Omit<CreateGenerateImageOptions, 'onResult'> & {
93
- onResult?: TOnResult;
94
- }): CreateGenerateImageReturn<InferGenerationOutput<ImageGenerationResult, TOnResult>>;
92
+ export declare function createGenerateImage<TTransformed = void>(options: Omit<CreateGenerateImageOptions, 'onResult'> & {
93
+ onResult?: (result: ImageGenerationResult) => TTransformed;
94
+ }): CreateGenerateImageReturn<InferGenerationOutputFromReturn<ImageGenerationResult, TTransformed>>;
@@ -1,5 +1,5 @@
1
1
  import type { StreamChunk, TTSResult } from '@tanstack/ai';
2
- import type { AIDevtoolsDisplayOptions, ConnectConnectionAdapter, GenerationClientState, GenerationFetcher, InferGenerationOutput, SpeechGenerateInput } from '@tanstack/ai-client';
2
+ import type { AIDevtoolsDisplayOptions, ConnectConnectionAdapter, GenerationClientState, GenerationFetcher, InferGenerationOutputFromReturn, SpeechGenerateInput } from '@tanstack/ai-client';
3
3
  /**
4
4
  * Options for the createGenerateSpeech function.
5
5
  *
@@ -80,6 +80,6 @@ export interface CreateGenerateSpeechReturn<TOutput = TTSResult> {
80
80
  * </div>
81
81
  * ```
82
82
  */
83
- export declare function createGenerateSpeech<TOnResult extends ((result: TTSResult) => any) | undefined = undefined>(options: Omit<CreateGenerateSpeechOptions, 'onResult'> & {
84
- onResult?: TOnResult;
85
- }): CreateGenerateSpeechReturn<InferGenerationOutput<TTSResult, TOnResult>>;
83
+ export declare function createGenerateSpeech<TTransformed = void>(options: Omit<CreateGenerateSpeechOptions, 'onResult'> & {
84
+ onResult?: (result: TTSResult) => TTransformed;
85
+ }): CreateGenerateSpeechReturn<InferGenerationOutputFromReturn<TTSResult, TTransformed>>;
@@ -1,5 +1,5 @@
1
1
  import type { StreamChunk } from '@tanstack/ai';
2
- import type { AIDevtoolsDisplayOptions, ConnectConnectionAdapter, GenerationClientState, GenerationFetcher, InferGenerationOutput, VideoGenerateInput, VideoGenerateResult, VideoStatusInfo } from '@tanstack/ai-client';
2
+ import type { AIDevtoolsDisplayOptions, ConnectConnectionAdapter, GenerationClientState, GenerationFetcher, InferGenerationOutputFromReturn, VideoGenerateInput, VideoGenerateResult, VideoStatusInfo } from '@tanstack/ai-client';
3
3
  /**
4
4
  * Options for the createGenerateVideo function.
5
5
  *
@@ -94,6 +94,6 @@ export interface CreateGenerateVideoReturn<TOutput = VideoGenerateResult> {
94
94
  * </div>
95
95
  * ```
96
96
  */
97
- export declare function createGenerateVideo<TOnResult extends ((result: VideoGenerateResult) => any) | undefined = undefined>(options: Omit<CreateGenerateVideoOptions, 'onResult'> & {
98
- onResult?: TOnResult;
99
- }): CreateGenerateVideoReturn<InferGenerationOutput<VideoGenerateResult, TOnResult>>;
97
+ export declare function createGenerateVideo<TTransformed = void>(options: Omit<CreateGenerateVideoOptions, 'onResult'> & {
98
+ onResult?: (result: VideoGenerateResult) => TTransformed;
99
+ }): CreateGenerateVideoReturn<InferGenerationOutputFromReturn<VideoGenerateResult, TTransformed>>;
@@ -30,6 +30,9 @@ import { createVideoDevtoolsBridge } from '@tanstack/ai-client/devtools';
30
30
  * </div>
31
31
  * ```
32
32
  */
33
+ // `TTransformed` infers from the `onResult` return position so the callback
34
+ // parameter is typed as `VideoGenerateResult` and `result` narrows to the
35
+ // transform's return. See issue #848.
33
36
  export function createGenerateVideo(options) {
34
37
  const clientId = options.id ||
35
38
  `video-${Date.now()}-${Math.random().toString(36).substring(7)}`;
@@ -54,7 +57,10 @@ export function createGenerateVideo(options) {
54
57
  hookName: 'createGenerateVideo',
55
58
  outputKind: 'video',
56
59
  },
57
- onResult: (r) => options.onResult?.(r),
60
+ // The transform's raw return type (`TTransformed`) and the stored output
61
+ // (`TOutput`, with null/void/undefined stripped) are identical at runtime;
62
+ // the cast bridges the relationship that the conditional type hides.
63
+ onResult: ((r) => options.onResult?.(r)),
58
64
  onError: (e) => options.onError?.(e),
59
65
  onProgress: (p, m) => options.onProgress?.(p, m),
60
66
  onChunk: (c) => options.onChunk?.(c),
@@ -1,5 +1,5 @@
1
1
  import type { StreamChunk } from '@tanstack/ai';
2
- import type { AIDevtoolsDisplayOptions, ConnectConnectionAdapter, GenerationClientState, GenerationFetcher, InferGenerationOutput } from '@tanstack/ai-client';
2
+ import type { AIDevtoolsDisplayOptions, ConnectConnectionAdapter, GenerationClientState, GenerationFetcher, InferGenerationOutputFromReturn } from '@tanstack/ai-client';
3
3
  /**
4
4
  * Options for the createGeneration function.
5
5
  *
@@ -91,6 +91,6 @@ export interface CreateGenerationReturn<TOutput> {
91
91
  * </div>
92
92
  * ```
93
93
  */
94
- export declare function createGeneration<TInput extends Record<string, any>, TResult, TOnResult extends ((result: TResult) => any) | undefined = undefined>(options: Omit<CreateGenerationOptions<TInput, TResult>, 'onResult'> & {
95
- onResult?: TOnResult;
96
- }): CreateGenerationReturn<InferGenerationOutput<TResult, TOnResult>>;
94
+ export declare function createGeneration<TInput extends Record<string, any>, TResult, TTransformed = void>(options: Omit<CreateGenerationOptions<TInput, TResult>, 'onResult'> & {
95
+ onResult?: (result: TResult) => TTransformed;
96
+ }): CreateGenerationReturn<InferGenerationOutputFromReturn<TResult, TTransformed>>;
@@ -31,6 +31,12 @@ import { createGenerationDevtoolsBridge } from '@tanstack/ai-client/devtools';
31
31
  * </div>
32
32
  * ```
33
33
  */
34
+ // `TTransformed` infers from the `onResult` return position (a covariant
35
+ // inference site that works even for an optional nested property), which types
36
+ // the callback parameter as `TResult` and narrows `result`. Inferring the
37
+ // whole callback as a defaulted type parameter instead collapses to the
38
+ // default, leaving the parameter `any` — a hard error under `strict`. See
39
+ // issue #848.
34
40
  export function createGeneration(options) {
35
41
  const clientId = options.id || `gen-${Date.now()}-${Math.random().toString(36).substring(7)}`;
36
42
  // Create reactive state using Svelte 5 runes
@@ -52,7 +58,10 @@ export function createGeneration(options) {
52
58
  framework: 'svelte',
53
59
  hookName: 'createGeneration',
54
60
  },
55
- onResult: (r) => options.onResult?.(r),
61
+ // The transform's raw return type (`TTransformed`) and the stored output
62
+ // (`TOutput`, with null/void/undefined stripped) are identical at runtime;
63
+ // the cast bridges the relationship that the conditional type hides.
64
+ onResult: ((r) => options.onResult?.(r)),
56
65
  onError: (e) => options.onError?.(e),
57
66
  onProgress: (p, m) => options.onProgress?.(p, m),
58
67
  onChunk: (c) => options.onChunk?.(c),
@@ -1,5 +1,5 @@
1
1
  import type { StreamChunk, SummarizationResult } from '@tanstack/ai';
2
- import type { AIDevtoolsDisplayOptions, ConnectConnectionAdapter, GenerationClientState, GenerationFetcher, InferGenerationOutput, SummarizeGenerateInput } from '@tanstack/ai-client';
2
+ import type { AIDevtoolsDisplayOptions, ConnectConnectionAdapter, GenerationClientState, GenerationFetcher, InferGenerationOutputFromReturn, SummarizeGenerateInput } from '@tanstack/ai-client';
3
3
  /**
4
4
  * Options for the createSummarize function.
5
5
  *
@@ -84,6 +84,6 @@ export interface CreateSummarizeReturn<TOutput = SummarizationResult> {
84
84
  * </div>
85
85
  * ```
86
86
  */
87
- export declare function createSummarize<TOnResult extends ((result: SummarizationResult) => any) | undefined = undefined>(options: Omit<CreateSummarizeOptions, 'onResult'> & {
88
- onResult?: TOnResult;
89
- }): CreateSummarizeReturn<InferGenerationOutput<SummarizationResult, TOnResult>>;
87
+ export declare function createSummarize<TTransformed = void>(options: Omit<CreateSummarizeOptions, 'onResult'> & {
88
+ onResult?: (result: SummarizationResult) => TTransformed;
89
+ }): CreateSummarizeReturn<InferGenerationOutputFromReturn<SummarizationResult, TTransformed>>;
@@ -1,5 +1,5 @@
1
1
  import type { StreamChunk, TranscriptionResult } from '@tanstack/ai';
2
- import type { AIDevtoolsDisplayOptions, ConnectConnectionAdapter, GenerationClientState, GenerationFetcher, InferGenerationOutput, TranscriptionGenerateInput } from '@tanstack/ai-client';
2
+ import type { AIDevtoolsDisplayOptions, ConnectConnectionAdapter, GenerationClientState, GenerationFetcher, InferGenerationOutputFromReturn, TranscriptionGenerateInput } from '@tanstack/ai-client';
3
3
  /**
4
4
  * Options for the createTranscription function.
5
5
  *
@@ -89,6 +89,6 @@ export interface CreateTranscriptionReturn<TOutput = TranscriptionResult> {
89
89
  * </div>
90
90
  * ```
91
91
  */
92
- export declare function createTranscription<TOnResult extends ((result: TranscriptionResult) => any) | undefined = undefined>(options: Omit<CreateTranscriptionOptions, 'onResult'> & {
93
- onResult?: TOnResult;
94
- }): CreateTranscriptionReturn<InferGenerationOutput<TranscriptionResult, TOnResult>>;
92
+ export declare function createTranscription<TTransformed = void>(options: Omit<CreateTranscriptionOptions, 'onResult'> & {
93
+ onResult?: (result: TranscriptionResult) => TTransformed;
94
+ }): CreateTranscriptionReturn<InferGenerationOutputFromReturn<TranscriptionResult, TTransformed>>;
package/dist/index.d.ts CHANGED
@@ -14,4 +14,6 @@ export { createSummarize } from './create-summarize.svelte';
14
14
  export type { CreateSummarizeOptions, CreateSummarizeReturn, } from './create-summarize.svelte';
15
15
  export { createGenerateVideo } from './create-generate-video.svelte';
16
16
  export type { CreateGenerateVideoOptions, CreateGenerateVideoReturn, } from './create-generate-video.svelte';
17
+ export { createAudioRecorder } from './create-audio-recorder.svelte';
18
+ export type { CreateAudioRecorderOptions, CreateAudioRecorderReturn, } from './create-audio-recorder.svelte';
17
19
  export { fetchServerSentEvents, fetchHttpStream, xhrServerSentEvents, xhrHttpStream, stream, rpcStream, createChatClientOptions, clientTools, 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';
package/dist/index.js CHANGED
@@ -7,5 +7,6 @@ export { createGenerateSpeech } from './create-generate-speech.svelte';
7
7
  export { createTranscription } from './create-transcription.svelte';
8
8
  export { createSummarize } from './create-summarize.svelte';
9
9
  export { createGenerateVideo } from './create-generate-video.svelte';
10
+ export { createAudioRecorder } from './create-audio-recorder.svelte';
10
11
  // Re-export from ai-client for convenience
11
12
  export { fetchServerSentEvents, fetchHttpStream, xhrServerSentEvents, xhrHttpStream, stream, rpcStream, createChatClientOptions, clientTools, } from '@tanstack/ai-client';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/ai-svelte",
3
- "version": "0.13.15",
3
+ "version": "0.14.0",
4
4
  "description": "Svelte 5 bindings for TanStack AI streaming chat, structured outputs, and media generation.",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -46,11 +46,11 @@
46
46
  "media-generation"
47
47
  ],
48
48
  "dependencies": {
49
- "@tanstack/ai-client": "0.18.6"
49
+ "@tanstack/ai-client": "0.19.0"
50
50
  },
51
51
  "peerDependencies": {
52
52
  "svelte": "^5.0.0",
53
- "@tanstack/ai": "^0.37.0"
53
+ "@tanstack/ai": "^0.38.0"
54
54
  },
55
55
  "devDependencies": {
56
56
  "@standard-schema/spec": "^1.1.0",
@@ -63,7 +63,7 @@
63
63
  "svelte-check": "^4.2.0",
64
64
  "typescript": "5.9.3",
65
65
  "vite": "^7.3.3",
66
- "@tanstack/ai": "0.37.0"
66
+ "@tanstack/ai": "0.38.0"
67
67
  },
68
68
  "scripts": {
69
69
  "clean": "rm -rf ./dist",
@@ -0,0 +1,93 @@
1
+ import { AudioRecorder } from '@tanstack/ai-client'
2
+ import type {
3
+ AudioRecorderOptions,
4
+ AudioRecording,
5
+ InferAudioRecordingOutput,
6
+ } from '@tanstack/ai-client'
7
+
8
+ export type CreateAudioRecorderOptions<TOnComplete> = AudioRecorderOptions & {
9
+ /**
10
+ * Optional transform applied to the recording when `stop()` resolves. Its
11
+ * (awaited) return value becomes `recording` and the resolved value of
12
+ * `stop()`. Return nothing to keep the raw `AudioRecording`.
13
+ */
14
+ onComplete?: TOnComplete
15
+ }
16
+
17
+ export interface CreateAudioRecorderReturn<TOutput> {
18
+ /** Reactive: latest recording (transformed if `onComplete` provided), or null. */
19
+ readonly recording: TOutput | null
20
+ /** Reactive: true while actively capturing audio. */
21
+ readonly isRecording: boolean
22
+ /** Whether the browser supports recording. */
23
+ readonly isSupported: boolean
24
+ start: () => Promise<void>
25
+ /** Stop and resolve with the completed recording (transformed if `onComplete` provided). */
26
+ stop: () => Promise<TOutput>
27
+ /**
28
+ * Discard the in-progress recording and release the mic. Svelte 5 runes
29
+ * can't register automatic teardown here (matching `createChat`), so call
30
+ * this from your component's cleanup if a recording may still be active.
31
+ */
32
+ cancel: () => void
33
+ }
34
+
35
+ /**
36
+ * Svelte 5 factory for recording an audio message. The resolved recording
37
+ * carries `.part` (for `createChat.sendMessage`) and `.base64` (for the
38
+ * generation factories).
39
+ *
40
+ * Errors are delivered via `onError`. `start()` and `stop()` also reject on
41
+ * failure (and `stop()` rejects with `Recording cancelled` if `cancel()` runs
42
+ * while a stop is in flight) — handle one channel, not both.
43
+ */
44
+ export function createAudioRecorder<
45
+ TOnComplete extends (recording: AudioRecording) => unknown,
46
+ >(
47
+ options: CreateAudioRecorderOptions<TOnComplete>,
48
+ ): CreateAudioRecorderReturn<InferAudioRecordingOutput<TOnComplete>>
49
+ export function createAudioRecorder(
50
+ options?: CreateAudioRecorderOptions<undefined>,
51
+ ): CreateAudioRecorderReturn<AudioRecording>
52
+ export function createAudioRecorder(
53
+ options: CreateAudioRecorderOptions<
54
+ (recording: AudioRecording) => unknown
55
+ > = {},
56
+ ): CreateAudioRecorderReturn<unknown> {
57
+ const recorder = new AudioRecorder({
58
+ ...(options.audio !== undefined && { audio: options.audio }),
59
+ ...(options.mimeType !== undefined && { mimeType: options.mimeType }),
60
+ ...(options.onError !== undefined && { onError: options.onError }),
61
+ })
62
+ let isRecording = $state(false)
63
+ let recording = $state<unknown>(null)
64
+
65
+ recorder.subscribe((state) => {
66
+ isRecording = state === 'recording'
67
+ })
68
+
69
+ const stop = async (): Promise<unknown> => {
70
+ const rawRecording = await recorder.stop()
71
+ const transformed = await options.onComplete?.(rawRecording)
72
+ // Only `undefined` (returning nothing) keeps the raw recording; a returned
73
+ // null is a real value, matching the inferred output type.
74
+ const output = transformed === undefined ? rawRecording : transformed
75
+ recording = output
76
+ return output
77
+ }
78
+
79
+ return {
80
+ get recording() {
81
+ return recording
82
+ },
83
+ get isRecording() {
84
+ return isRecording
85
+ },
86
+ get isSupported() {
87
+ return AudioRecorder.isSupported()
88
+ },
89
+ start: () => recorder.start(),
90
+ stop,
91
+ cancel: () => recorder.cancel(),
92
+ }
93
+ }
@@ -6,7 +6,7 @@ import type {
6
6
  ConnectConnectionAdapter,
7
7
  GenerationClientState,
8
8
  GenerationFetcher,
9
- InferGenerationOutput,
9
+ InferGenerationOutputFromReturn,
10
10
  } from '@tanstack/ai-client'
11
11
 
12
12
  /**
@@ -91,15 +91,12 @@ export interface CreateGenerateAudioReturn<TOutput = AudioGenerationResult> {
91
91
  * </div>
92
92
  * ```
93
93
  */
94
- export function createGenerateAudio<
95
- TOnResult extends ((result: AudioGenerationResult) => any) | undefined =
96
- undefined,
97
- >(
94
+ export function createGenerateAudio<TTransformed = void>(
98
95
  options: Omit<CreateGenerateAudioOptions, 'onResult'> & {
99
- onResult?: TOnResult
96
+ onResult?: (result: AudioGenerationResult) => TTransformed
100
97
  },
101
98
  ): CreateGenerateAudioReturn<
102
- InferGenerationOutput<AudioGenerationResult, TOnResult>
99
+ InferGenerationOutputFromReturn<AudioGenerationResult, TTransformed>
103
100
  > {
104
101
  const devtools = {
105
102
  ...options.devtools,
@@ -110,7 +107,7 @@ export function createGenerateAudio<
110
107
  const gen = createGeneration<
111
108
  AudioGenerateInput,
112
109
  AudioGenerationResult,
113
- TOnResult
110
+ TTransformed
114
111
  >({
115
112
  ...options,
116
113
  devtools,
@@ -6,7 +6,7 @@ import type {
6
6
  GenerationClientState,
7
7
  GenerationFetcher,
8
8
  ImageGenerateInput,
9
- InferGenerationOutput,
9
+ InferGenerationOutputFromReturn,
10
10
  } from '@tanstack/ai-client'
11
11
 
12
12
  /**
@@ -100,15 +100,12 @@ export interface CreateGenerateImageReturn<TOutput = ImageGenerationResult> {
100
100
  * </div>
101
101
  * ```
102
102
  */
103
- export function createGenerateImage<
104
- TOnResult extends ((result: ImageGenerationResult) => any) | undefined =
105
- undefined,
106
- >(
103
+ export function createGenerateImage<TTransformed = void>(
107
104
  options: Omit<CreateGenerateImageOptions, 'onResult'> & {
108
- onResult?: TOnResult
105
+ onResult?: (result: ImageGenerationResult) => TTransformed
109
106
  },
110
107
  ): CreateGenerateImageReturn<
111
- InferGenerationOutput<ImageGenerationResult, TOnResult>
108
+ InferGenerationOutputFromReturn<ImageGenerationResult, TTransformed>
112
109
  > {
113
110
  const devtools = {
114
111
  ...options.devtools,
@@ -119,7 +116,7 @@ export function createGenerateImage<
119
116
  const gen = createGeneration<
120
117
  ImageGenerateInput,
121
118
  ImageGenerationResult,
122
- TOnResult
119
+ TTransformed
123
120
  >({
124
121
  ...options,
125
122
  devtools,
@@ -5,7 +5,7 @@ import type {
5
5
  ConnectConnectionAdapter,
6
6
  GenerationClientState,
7
7
  GenerationFetcher,
8
- InferGenerationOutput,
8
+ InferGenerationOutputFromReturn,
9
9
  SpeechGenerateInput,
10
10
  } from '@tanstack/ai-client'
11
11
 
@@ -91,20 +91,20 @@ export interface CreateGenerateSpeechReturn<TOutput = TTSResult> {
91
91
  * </div>
92
92
  * ```
93
93
  */
94
- export function createGenerateSpeech<
95
- TOnResult extends ((result: TTSResult) => any) | undefined = undefined,
96
- >(
94
+ export function createGenerateSpeech<TTransformed = void>(
97
95
  options: Omit<CreateGenerateSpeechOptions, 'onResult'> & {
98
- onResult?: TOnResult
96
+ onResult?: (result: TTSResult) => TTransformed
99
97
  },
100
- ): CreateGenerateSpeechReturn<InferGenerationOutput<TTSResult, TOnResult>> {
98
+ ): CreateGenerateSpeechReturn<
99
+ InferGenerationOutputFromReturn<TTSResult, TTransformed>
100
+ > {
101
101
  const devtools = {
102
102
  ...options.devtools,
103
103
  framework: 'svelte',
104
104
  hookName: 'createGenerateSpeech',
105
105
  outputKind: 'audio' as const,
106
106
  }
107
- const gen = createGeneration<SpeechGenerateInput, TTSResult, TOnResult>({
107
+ const gen = createGeneration<SpeechGenerateInput, TTSResult, TTransformed>({
108
108
  ...options,
109
109
  devtools,
110
110
  })
@@ -6,7 +6,7 @@ import type {
6
6
  ConnectConnectionAdapter,
7
7
  GenerationClientState,
8
8
  GenerationFetcher,
9
- InferGenerationOutput,
9
+ InferGenerationOutputFromReturn,
10
10
  VideoGenerateInput,
11
11
  VideoGenerateResult,
12
12
  VideoStatusInfo,
@@ -108,17 +108,20 @@ export interface CreateGenerateVideoReturn<TOutput = VideoGenerateResult> {
108
108
  * </div>
109
109
  * ```
110
110
  */
111
- export function createGenerateVideo<
112
- TOnResult extends ((result: VideoGenerateResult) => any) | undefined =
113
- undefined,
114
- >(
111
+ // `TTransformed` infers from the `onResult` return position so the callback
112
+ // parameter is typed as `VideoGenerateResult` and `result` narrows to the
113
+ // transform's return. See issue #848.
114
+ export function createGenerateVideo<TTransformed = void>(
115
115
  options: Omit<CreateGenerateVideoOptions, 'onResult'> & {
116
- onResult?: TOnResult
116
+ onResult?: (result: VideoGenerateResult) => TTransformed
117
117
  },
118
118
  ): CreateGenerateVideoReturn<
119
- InferGenerationOutput<VideoGenerateResult, TOnResult>
119
+ InferGenerationOutputFromReturn<VideoGenerateResult, TTransformed>
120
120
  > {
121
- type TOutput = InferGenerationOutput<VideoGenerateResult, TOnResult>
121
+ type TOutput = InferGenerationOutputFromReturn<
122
+ VideoGenerateResult,
123
+ TTransformed
124
+ >
122
125
  const clientId =
123
126
  options.id ||
124
127
  `video-${Date.now()}-${Math.random().toString(36).substring(7)}`
@@ -145,7 +148,12 @@ export function createGenerateVideo<
145
148
  hookName: 'createGenerateVideo',
146
149
  outputKind: 'video' as const,
147
150
  },
148
- onResult: (r: VideoGenerateResult) => options.onResult?.(r),
151
+ // The transform's raw return type (`TTransformed`) and the stored output
152
+ // (`TOutput`, with null/void/undefined stripped) are identical at runtime;
153
+ // the cast bridges the relationship that the conditional type hides.
154
+ onResult: ((r: VideoGenerateResult) => options.onResult?.(r)) as (
155
+ result: VideoGenerateResult,
156
+ ) => TOutput | null | void,
149
157
  onError: (e: Error) => options.onError?.(e),
150
158
  onProgress: (p: number, m?: string) => options.onProgress?.(p, m),
151
159
  onChunk: (c: StreamChunk) => options.onChunk?.(c),
@@ -7,7 +7,7 @@ import type {
7
7
  GenerationClientOptions,
8
8
  GenerationClientState,
9
9
  GenerationFetcher,
10
- InferGenerationOutput,
10
+ InferGenerationOutputFromReturn,
11
11
  } from '@tanstack/ai-client'
12
12
 
13
13
  /**
@@ -103,16 +103,24 @@ export interface CreateGenerationReturn<TOutput> {
103
103
  * </div>
104
104
  * ```
105
105
  */
106
+ // `TTransformed` infers from the `onResult` return position (a covariant
107
+ // inference site that works even for an optional nested property), which types
108
+ // the callback parameter as `TResult` and narrows `result`. Inferring the
109
+ // whole callback as a defaulted type parameter instead collapses to the
110
+ // default, leaving the parameter `any` — a hard error under `strict`. See
111
+ // issue #848.
106
112
  export function createGeneration<
107
113
  TInput extends Record<string, any>,
108
114
  TResult,
109
- TOnResult extends ((result: TResult) => any) | undefined = undefined,
115
+ TTransformed = void,
110
116
  >(
111
117
  options: Omit<CreateGenerationOptions<TInput, TResult>, 'onResult'> & {
112
- onResult?: TOnResult
118
+ onResult?: (result: TResult) => TTransformed
113
119
  },
114
- ): CreateGenerationReturn<InferGenerationOutput<TResult, TOnResult>> {
115
- type TOutput = InferGenerationOutput<TResult, TOnResult>
120
+ ): CreateGenerationReturn<
121
+ InferGenerationOutputFromReturn<TResult, TTransformed>
122
+ > {
123
+ type TOutput = InferGenerationOutputFromReturn<TResult, TTransformed>
116
124
  const clientId =
117
125
  options.id || `gen-${Date.now()}-${Math.random().toString(36).substring(7)}`
118
126
 
@@ -136,7 +144,12 @@ export function createGeneration<
136
144
  framework: 'svelte',
137
145
  hookName: 'createGeneration',
138
146
  },
139
- onResult: (r: TResult) => options.onResult?.(r),
147
+ // The transform's raw return type (`TTransformed`) and the stored output
148
+ // (`TOutput`, with null/void/undefined stripped) are identical at runtime;
149
+ // the cast bridges the relationship that the conditional type hides.
150
+ onResult: ((r: TResult) => options.onResult?.(r)) as (
151
+ result: TResult,
152
+ ) => TOutput | null | void,
140
153
  onError: (e: Error) => options.onError?.(e),
141
154
  onProgress: (p: number, m?: string) => options.onProgress?.(p, m),
142
155
  onChunk: (c: StreamChunk) => options.onChunk?.(c),
@@ -5,7 +5,7 @@ import type {
5
5
  ConnectConnectionAdapter,
6
6
  GenerationClientState,
7
7
  GenerationFetcher,
8
- InferGenerationOutput,
8
+ InferGenerationOutputFromReturn,
9
9
  SummarizeGenerateInput,
10
10
  } from '@tanstack/ai-client'
11
11
 
@@ -95,15 +95,12 @@ export interface CreateSummarizeReturn<TOutput = SummarizationResult> {
95
95
  * </div>
96
96
  * ```
97
97
  */
98
- export function createSummarize<
99
- TOnResult extends ((result: SummarizationResult) => any) | undefined =
100
- undefined,
101
- >(
98
+ export function createSummarize<TTransformed = void>(
102
99
  options: Omit<CreateSummarizeOptions, 'onResult'> & {
103
- onResult?: TOnResult
100
+ onResult?: (result: SummarizationResult) => TTransformed
104
101
  },
105
102
  ): CreateSummarizeReturn<
106
- InferGenerationOutput<SummarizationResult, TOnResult>
103
+ InferGenerationOutputFromReturn<SummarizationResult, TTransformed>
107
104
  > {
108
105
  const devtools = {
109
106
  ...options.devtools,
@@ -114,7 +111,7 @@ export function createSummarize<
114
111
  const gen = createGeneration<
115
112
  SummarizeGenerateInput,
116
113
  SummarizationResult,
117
- TOnResult
114
+ TTransformed
118
115
  >({
119
116
  ...options,
120
117
  devtools,
@@ -5,7 +5,7 @@ import type {
5
5
  ConnectConnectionAdapter,
6
6
  GenerationClientState,
7
7
  GenerationFetcher,
8
- InferGenerationOutput,
8
+ InferGenerationOutputFromReturn,
9
9
  TranscriptionGenerateInput,
10
10
  } from '@tanstack/ai-client'
11
11
 
@@ -100,15 +100,12 @@ export interface CreateTranscriptionReturn<TOutput = TranscriptionResult> {
100
100
  * </div>
101
101
  * ```
102
102
  */
103
- export function createTranscription<
104
- TOnResult extends ((result: TranscriptionResult) => any) | undefined =
105
- undefined,
106
- >(
103
+ export function createTranscription<TTransformed = void>(
107
104
  options: Omit<CreateTranscriptionOptions, 'onResult'> & {
108
- onResult?: TOnResult
105
+ onResult?: (result: TranscriptionResult) => TTransformed
109
106
  },
110
107
  ): CreateTranscriptionReturn<
111
- InferGenerationOutput<TranscriptionResult, TOnResult>
108
+ InferGenerationOutputFromReturn<TranscriptionResult, TTransformed>
112
109
  > {
113
110
  const devtools = {
114
111
  ...options.devtools,
@@ -119,7 +116,7 @@ export function createTranscription<
119
116
  const gen = createGeneration<
120
117
  TranscriptionGenerateInput,
121
118
  TranscriptionResult,
122
- TOnResult
119
+ TTransformed
123
120
  >({
124
121
  ...options,
125
122
  devtools,
package/src/index.ts CHANGED
@@ -50,6 +50,12 @@ export type {
50
50
  CreateGenerateVideoReturn,
51
51
  } from './create-generate-video.svelte'
52
52
 
53
+ export { createAudioRecorder } from './create-audio-recorder.svelte'
54
+ export type {
55
+ CreateAudioRecorderOptions,
56
+ CreateAudioRecorderReturn,
57
+ } from './create-audio-recorder.svelte'
58
+
53
59
  // Re-export from ai-client for convenience
54
60
  export {
55
61
  fetchServerSentEvents,