@tanstack/ai-react 0.21.1 → 0.21.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -210,8 +210,9 @@ Official adapters include:
210
210
  | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------ |
211
211
  | [`@tanstack/ai-openrouter`](https://tanstack.com/ai/latest/docs/adapters/openrouter) | 300+ models through one OpenRouter API, with per-request cost tracking |
212
212
  | [`@tanstack/ai-openai`](https://tanstack.com/ai/latest/docs/adapters/openai) | OpenAI chat, image, video, speech, transcription, realtime, and provider tools |
213
- | [`@tanstack/ai-anthropic`](https://tanstack.com/ai/latest/docs/adapters/anthropic) | Anthropic Claude chat, thinking, tools, and structured outputs |
213
+ | [`@tanstack/ai-anthropic`](https://tanstack.com/ai/latest/docs/adapters/anthropic) | Anthropic Claude chat, thinking, tools, structured outputs, and Vertex Claude |
214
214
  | [`@tanstack/ai-gemini`](https://tanstack.com/ai/latest/docs/adapters/gemini) | Google Gemini chat, image, speech, and audio generation |
215
+ | [`@tanstack/ai-vertex`](https://tanstack.com/ai/latest/docs/adapters/vertex) | Gemini on Vertex AI with regional endpoints and Google Cloud credentials |
215
216
  | [`@tanstack/ai-ollama`](https://tanstack.com/ai/latest/docs/adapters/ollama) | Local Ollama models |
216
217
  | [`@tanstack/ai-grok`](https://tanstack.com/ai/latest/docs/adapters/grok) | xAI Grok chat, images, and realtime |
217
218
  | [`@tanstack/ai-groq`](https://tanstack.com/ai/latest/docs/adapters/groq) | Groq low-latency inference |
@@ -39,5 +39,7 @@ export interface UseAudioRecorderReturn<TOutput> {
39
39
  * sendMessage({ content: [rec.part] })
40
40
  * ```
41
41
  */
42
- export declare function useAudioRecorder<TOnComplete extends (recording: AudioRecording) => unknown>(options: UseAudioRecorderOptions<TOnComplete>): UseAudioRecorderReturn<InferAudioRecordingOutput<TOnComplete>>;
42
+ export declare function useAudioRecorder<TOnComplete extends (recording: AudioRecording) => unknown>(options: UseAudioRecorderOptions<TOnComplete> & {
43
+ onComplete: TOnComplete;
44
+ }): UseAudioRecorderReturn<InferAudioRecordingOutput<TOnComplete>>;
43
45
  export declare function useAudioRecorder(options?: UseAudioRecorderOptions<undefined>): UseAudioRecorderReturn<AudioRecording>;
@@ -1 +1 @@
1
- {"version":3,"file":"use-audio-recorder.js","names":[],"sources":["../../src/use-audio-recorder.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { AudioRecorder } from '@tanstack/ai-client'\nimport type {\n AudioRecorderOptions,\n AudioRecording,\n InferAudioRecordingOutput,\n} from '@tanstack/ai-client'\n\nexport type UseAudioRecorderOptions<TOnComplete> = AudioRecorderOptions & {\n /**\n * Optional transform applied to the recording when `stop()` resolves. Its\n * (awaited) return value becomes `recording` and the resolved value of\n * `stop()`. Return nothing to keep the raw `AudioRecording`.\n */\n onComplete?: TOnComplete\n}\n\nexport interface UseAudioRecorderReturn<TOutput> {\n /** Latest recording (transformed if `onComplete` provided), or null. */\n recording: TOutput | null\n /** True while actively capturing audio. */\n isRecording: boolean\n /** Whether the browser supports recording (getUserMedia + MediaRecorder). */\n isSupported: boolean\n /** Acquire the mic and begin recording. */\n start: () => Promise<void>\n /** Stop and resolve with the completed recording (transformed if `onComplete` provided). */\n stop: () => Promise<TOutput>\n /** Discard the in-progress recording and release the mic. */\n cancel: () => void\n}\n\n/**\n * React hook for recording an audio message. The resolved\n * {@link AudioRecording} carries `.part` (an audio content part for\n * `useChat.sendMessage`) and `.base64` (for the generation hooks).\n *\n * Errors are delivered via `onError`. `start()` and `stop()` also reject on\n * failure (and `stop()` rejects with `Recording cancelled` if the component\n * unmounts while a stop is in flight) — handle one channel, not both.\n *\n * @example\n * ```tsx\n * const { isRecording, start, stop, recording } = useAudioRecorder()\n * const { sendMessage } = useChat({ connection })\n * // ...\n * const rec = await stop()\n * sendMessage({ content: [rec.part] })\n * ```\n */\nexport function useAudioRecorder<\n TOnComplete extends (recording: AudioRecording) => unknown,\n>(\n options: UseAudioRecorderOptions<TOnComplete>,\n): UseAudioRecorderReturn<InferAudioRecordingOutput<TOnComplete>>\nexport function useAudioRecorder(\n options?: UseAudioRecorderOptions<undefined>,\n): UseAudioRecorderReturn<AudioRecording>\nexport function useAudioRecorder(\n options: UseAudioRecorderOptions<(recording: AudioRecording) => unknown> = {},\n): UseAudioRecorderReturn<unknown> {\n const [isRecording, setIsRecording] = useState(false)\n const [recording, setRecording] = useState<unknown>(null)\n // Read the freshest callbacks at fire time without recreating the recorder.\n const optionsRef = useRef(options)\n optionsRef.current = options\n\n const recorder = useMemo(\n () =>\n new AudioRecorder({\n ...(options.audio !== undefined && { audio: options.audio }),\n ...(options.mimeType !== undefined && { mimeType: options.mimeType }),\n onError: (err) => optionsRef.current.onError?.(err),\n }),\n // Recorder config (audio/mimeType) is captured once at mount, matching the\n // other hooks' create-once pattern.\n [],\n )\n\n useEffect(() => {\n const unsubscribe = recorder.subscribe((state) => {\n setIsRecording(state === 'recording')\n })\n return () => {\n unsubscribe()\n recorder.cancel()\n }\n }, [recorder])\n\n const start = useCallback(() => recorder.start(), [recorder])\n const stop = useCallback(async () => {\n const recording = await recorder.stop()\n const transformed = await optionsRef.current.onComplete?.(recording)\n // Only `undefined` (returning nothing) falls back to the raw recording, so\n // a transform that returns null is preserved — matching the inferred type,\n // which excludes only undefined/void/null from the transform's return.\n const output = transformed === undefined ? recording : transformed\n setRecording(() => output)\n return output\n }, [recorder])\n const cancel = useCallback(() => recorder.cancel(), [recorder])\n\n return {\n recording,\n isRecording,\n // recording is client-only; if SSR'd, gate UI on a mounted flag.\n isSupported: AudioRecorder.isSupported(),\n start,\n stop,\n cancel,\n }\n}\n"],"mappings":";;;AA0DA,SAAgB,iBACd,UAA2E,CAAC,GAC3C;CACjC,MAAM,CAAC,aAAa,kBAAkB,SAAS,KAAK;CACpD,MAAM,CAAC,WAAW,gBAAgB,SAAkB,IAAI;CAExD,MAAM,aAAa,OAAO,OAAO;CACjC,WAAW,UAAU;CAErB,MAAM,WAAW,cAEb,IAAI,cAAc;EAChB,GAAI,QAAQ,UAAU,KAAA,KAAa,EAAE,OAAO,QAAQ,MAAM;EAC1D,GAAI,QAAQ,aAAa,KAAA,KAAa,EAAE,UAAU,QAAQ,SAAS;EACnE,UAAU,QAAQ,WAAW,QAAQ,UAAU,GAAG;CACpD,CAAC,GAGH,CAAC,CACH;CAEA,gBAAgB;EACd,MAAM,cAAc,SAAS,WAAW,UAAU;GAChD,eAAe,UAAU,WAAW;EACtC,CAAC;EACD,aAAa;GACX,YAAY;GACZ,SAAS,OAAO;EAClB;CACF,GAAG,CAAC,QAAQ,CAAC;CAEb,MAAM,QAAQ,kBAAkB,SAAS,MAAM,GAAG,CAAC,QAAQ,CAAC;CAC5D,MAAM,OAAO,YAAY,YAAY;EACnC,MAAM,YAAY,MAAM,SAAS,KAAK;EACtC,MAAM,cAAc,MAAM,WAAW,QAAQ,aAAa,SAAS;EAInE,MAAM,SAAS,gBAAgB,KAAA,IAAY,YAAY;EACvD,mBAAmB,MAAM;EACzB,OAAO;CACT,GAAG,CAAC,QAAQ,CAAC;CACb,MAAM,SAAS,kBAAkB,SAAS,OAAO,GAAG,CAAC,QAAQ,CAAC;CAE9D,OAAO;EACL;EACA;EAEA,aAAa,cAAc,YAAY;EACvC;EACA;EACA;CACF;AACF"}
1
+ {"version":3,"file":"use-audio-recorder.js","names":[],"sources":["../../src/use-audio-recorder.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { AudioRecorder } from '@tanstack/ai-client'\nimport type {\n AudioRecorderOptions,\n AudioRecording,\n InferAudioRecordingOutput,\n} from '@tanstack/ai-client'\n\nexport type UseAudioRecorderOptions<TOnComplete> = AudioRecorderOptions & {\n /**\n * Optional transform applied to the recording when `stop()` resolves. Its\n * (awaited) return value becomes `recording` and the resolved value of\n * `stop()`. Return nothing to keep the raw `AudioRecording`.\n */\n onComplete?: TOnComplete\n}\n\nexport interface UseAudioRecorderReturn<TOutput> {\n /** Latest recording (transformed if `onComplete` provided), or null. */\n recording: TOutput | null\n /** True while actively capturing audio. */\n isRecording: boolean\n /** Whether the browser supports recording (getUserMedia + MediaRecorder). */\n isSupported: boolean\n /** Acquire the mic and begin recording. */\n start: () => Promise<void>\n /** Stop and resolve with the completed recording (transformed if `onComplete` provided). */\n stop: () => Promise<TOutput>\n /** Discard the in-progress recording and release the mic. */\n cancel: () => void\n}\n\n/**\n * React hook for recording an audio message. The resolved\n * {@link AudioRecording} carries `.part` (an audio content part for\n * `useChat.sendMessage`) and `.base64` (for the generation hooks).\n *\n * Errors are delivered via `onError`. `start()` and `stop()` also reject on\n * failure (and `stop()` rejects with `Recording cancelled` if the component\n * unmounts while a stop is in flight) — handle one channel, not both.\n *\n * @example\n * ```tsx\n * const { isRecording, start, stop, recording } = useAudioRecorder()\n * const { sendMessage } = useChat({ connection })\n * // ...\n * const rec = await stop()\n * sendMessage({ content: [rec.part] })\n * ```\n */\n// The transforming overload requires `onComplete`. Without that constraint an\n// options object carrying only unrelated keys (`useAudioRecorder({ onError })`)\n// still matches it, `TOnComplete` infers as `unknown`, and `recording`/`stop()`\n// collapse to `unknown` — so passing any option would silently cost you the\n// `AudioRecording` type. Requiring it here sends those calls to the second\n// overload instead (issue #1001).\nexport function useAudioRecorder<\n TOnComplete extends (recording: AudioRecording) => unknown,\n>(\n options: UseAudioRecorderOptions<TOnComplete> & { onComplete: TOnComplete },\n): UseAudioRecorderReturn<InferAudioRecordingOutput<TOnComplete>>\nexport function useAudioRecorder(\n options?: UseAudioRecorderOptions<undefined>,\n): UseAudioRecorderReturn<AudioRecording>\nexport function useAudioRecorder(\n options: UseAudioRecorderOptions<(recording: AudioRecording) => unknown> = {},\n): UseAudioRecorderReturn<unknown> {\n const [isRecording, setIsRecording] = useState(false)\n const [recording, setRecording] = useState<unknown>(null)\n // Read the freshest callbacks at fire time without recreating the recorder.\n const optionsRef = useRef(options)\n optionsRef.current = options\n\n const recorder = useMemo(\n () =>\n new AudioRecorder({\n ...(options.audio !== undefined && { audio: options.audio }),\n ...(options.mimeType !== undefined && { mimeType: options.mimeType }),\n onError: (err) => optionsRef.current.onError?.(err),\n }),\n // Recorder config (audio/mimeType) is captured once at mount, matching the\n // other hooks' create-once pattern.\n [],\n )\n\n useEffect(() => {\n const unsubscribe = recorder.subscribe((state) => {\n setIsRecording(state === 'recording')\n })\n return () => {\n unsubscribe()\n recorder.cancel()\n }\n }, [recorder])\n\n const start = useCallback(() => recorder.start(), [recorder])\n const stop = useCallback(async () => {\n const recording = await recorder.stop()\n const transformed = await optionsRef.current.onComplete?.(recording)\n // Only `undefined` (returning nothing) falls back to the raw recording, so\n // a transform that returns null is preserved — matching the inferred type,\n // which excludes only undefined/void/null from the transform's return.\n const output = transformed === undefined ? recording : transformed\n setRecording(() => output)\n return output\n }, [recorder])\n const cancel = useCallback(() => recorder.cancel(), [recorder])\n\n return {\n recording,\n isRecording,\n // recording is client-only; if SSR'd, gate UI on a mounted flag.\n isSupported: AudioRecorder.isSupported(),\n start,\n stop,\n cancel,\n }\n}\n"],"mappings":";;;AAgEA,SAAgB,iBACd,UAA2E,CAAC,GAC3C;CACjC,MAAM,CAAC,aAAa,kBAAkB,SAAS,KAAK;CACpD,MAAM,CAAC,WAAW,gBAAgB,SAAkB,IAAI;CAExD,MAAM,aAAa,OAAO,OAAO;CACjC,WAAW,UAAU;CAErB,MAAM,WAAW,cAEb,IAAI,cAAc;EAChB,GAAI,QAAQ,UAAU,KAAA,KAAa,EAAE,OAAO,QAAQ,MAAM;EAC1D,GAAI,QAAQ,aAAa,KAAA,KAAa,EAAE,UAAU,QAAQ,SAAS;EACnE,UAAU,QAAQ,WAAW,QAAQ,UAAU,GAAG;CACpD,CAAC,GAGH,CAAC,CACH;CAEA,gBAAgB;EACd,MAAM,cAAc,SAAS,WAAW,UAAU;GAChD,eAAe,UAAU,WAAW;EACtC,CAAC;EACD,aAAa;GACX,YAAY;GACZ,SAAS,OAAO;EAClB;CACF,GAAG,CAAC,QAAQ,CAAC;CAEb,MAAM,QAAQ,kBAAkB,SAAS,MAAM,GAAG,CAAC,QAAQ,CAAC;CAC5D,MAAM,OAAO,YAAY,YAAY;EACnC,MAAM,YAAY,MAAM,SAAS,KAAK;EACtC,MAAM,cAAc,MAAM,WAAW,QAAQ,aAAa,SAAS;EAInE,MAAM,SAAS,gBAAgB,KAAA,IAAY,YAAY;EACvD,mBAAmB,MAAM;EACzB,OAAO;CACT,GAAG,CAAC,QAAQ,CAAC;CACb,MAAM,SAAS,kBAAkB,SAAS,OAAO,GAAG,CAAC,QAAQ,CAAC;CAE9D,OAAO;EACL;EACA;EAEA,aAAa,cAAc,YAAY;EACvC;EACA;EACA;CACF;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/ai-react",
3
- "version": "0.21.1",
3
+ "version": "0.21.3",
4
4
  "description": "React hooks for TanStack AI streaming chat, realtime voice, structured outputs, and media generation.",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -49,13 +49,13 @@
49
49
  "media-generation"
50
50
  ],
51
51
  "dependencies": {
52
- "@tanstack/ai-client": "^0.25.1"
52
+ "@tanstack/ai-client": "^0.26.0"
53
53
  },
54
54
  "peerDependencies": {
55
55
  "@mcp-ui/client": "^7",
56
56
  "@types/react": ">=18.0.0",
57
57
  "react": ">=18.0.0",
58
- "@tanstack/ai": "^0.47.1"
58
+ "@tanstack/ai": "^0.48.0"
59
59
  },
60
60
  "peerDependenciesMeta": {
61
61
  "@mcp-ui/client": {
@@ -71,7 +71,7 @@
71
71
  "jsdom": "^27.4.0",
72
72
  "react": "^19.2.3",
73
73
  "vite": "^8.2.1",
74
- "@tanstack/ai": "0.47.1"
74
+ "@tanstack/ai": "0.48.0"
75
75
  },
76
76
  "scripts": {
77
77
  "clean": "premove ./build ./dist",
@@ -48,10 +48,16 @@ export interface UseAudioRecorderReturn<TOutput> {
48
48
  * sendMessage({ content: [rec.part] })
49
49
  * ```
50
50
  */
51
+ // The transforming overload requires `onComplete`. Without that constraint an
52
+ // options object carrying only unrelated keys (`useAudioRecorder({ onError })`)
53
+ // still matches it, `TOnComplete` infers as `unknown`, and `recording`/`stop()`
54
+ // collapse to `unknown` — so passing any option would silently cost you the
55
+ // `AudioRecording` type. Requiring it here sends those calls to the second
56
+ // overload instead (issue #1001).
51
57
  export function useAudioRecorder<
52
58
  TOnComplete extends (recording: AudioRecording) => unknown,
53
59
  >(
54
- options: UseAudioRecorderOptions<TOnComplete>,
60
+ options: UseAudioRecorderOptions<TOnComplete> & { onComplete: TOnComplete },
55
61
  ): UseAudioRecorderReturn<InferAudioRecordingOutput<TOnComplete>>
56
62
  export function useAudioRecorder(
57
63
  options?: UseAudioRecorderOptions<undefined>,