@tanstack/ai-react 0.28.0 → 0.29.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 +1 -0
- package/dist/esm/index.d.ts +3 -3
- package/dist/esm/index.js +3 -3
- package/dist/esm/use-generate-audio.js +5 -9
- package/dist/esm/use-generate-audio.js.map +1 -1
- package/dist/esm/use-generate-image.js +5 -9
- package/dist/esm/use-generate-image.js.map +1 -1
- package/dist/esm/use-generate-speech.js +5 -9
- package/dist/esm/use-generate-speech.js.map +1 -1
- package/dist/esm/use-generation.d.ts +10 -1
- package/dist/esm/use-generation.js +8 -3
- package/dist/esm/use-generation.js.map +1 -1
- package/dist/esm/use-summarize.js +5 -9
- package/dist/esm/use-summarize.js.map +1 -1
- package/dist/esm/use-transcription.js +5 -9
- package/dist/esm/use-transcription.js.map +1 -1
- package/dist/esm/use-web-mcp-tools.d.ts +34 -7
- package/dist/esm/use-web-mcp-tools.js +42 -5
- package/dist/esm/use-web-mcp-tools.js.map +1 -1
- package/package.json +5 -4
- package/src/index.ts +15 -2
- package/src/use-generate-audio.ts +9 -13
- package/src/use-generate-image.ts +9 -13
- package/src/use-generate-speech.ts +9 -13
- package/src/use-generation.ts +34 -2
- package/src/use-summarize.ts +9 -13
- package/src/use-transcription.ts +9 -9
- package/src/use-web-mcp-tools.ts +69 -10
package/README.md
CHANGED
|
@@ -240,6 +240,7 @@ Official adapters include:
|
|
|
240
240
|
| [`@tanstack/ai-byteplus`](https://tanstack.com/ai/latest/docs/adapters/byteplus) | BytePlus Seed chat, Seedance video, Seedream image, and Seed Speech TTS/ASR |
|
|
241
241
|
| [`@tanstack/ai-fal`](https://tanstack.com/ai/latest/docs/adapters/fal) | fal.ai image, video, audio, speech, and transcription models |
|
|
242
242
|
| [`@tanstack/ai-reactor`](https://tanstack.com/ai/latest/docs/adapters/reactor) | Reactor live world and video generation (Orbis, Happy Oyster, LingBot, Helios, FastH3) |
|
|
243
|
+
| [`@tanstack/ai-worldlabs`](https://tanstack.com/ai/latest/docs/adapters/worldlabs) | World Labs Marble persistent 3D world generation |
|
|
243
244
|
| [`@tanstack/ai-cloudflare`](https://tanstack.com/ai/latest/docs/adapters/cloudflare) | Cloudflare Workers AI chat, embeddings, image, speech, transcription, and AI Gateway |
|
|
244
245
|
|
|
245
246
|
The adapter system is tree-shakeable by activity. Import `openaiText` for chat,
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -4,8 +4,8 @@ export { useByok } from './use-byok.js';
|
|
|
4
4
|
export { useRealtimeChat } from './use-realtime-chat.js';
|
|
5
5
|
export { useMcpAppBridge } from './use-mcp-app-bridge.js';
|
|
6
6
|
export type { UseMcpAppBridgeOptions } from './use-mcp-app-bridge.js';
|
|
7
|
-
export { useWebMCPTools } from './use-web-mcp-tools.js';
|
|
8
|
-
export type { UseWebMCPToolsOptions } from './use-web-mcp-tools.js';
|
|
7
|
+
export { usePageWebMCPTools, useRegisterWebMCPTools, useWebMCPTools, } from './use-web-mcp-tools.js';
|
|
8
|
+
export type { UsePageWebMCPToolsOptions, UseRegisterWebMCPToolsOptions, UseWebMCPToolsOptions, } from './use-web-mcp-tools.js';
|
|
9
9
|
export type { DeepPartial, UseChatOptions, UseChatReturn, UIMessage, ChatRequestBody, QueuedMessage, SendMessageOptions, WhenBusy, QueueConfig, QueueStrategy, QueueOption, } from './types.js';
|
|
10
10
|
export type { UseRealtimeChatOptions, UseRealtimeChatReturn, } from './realtime-types.js';
|
|
11
11
|
export { useGeneration } from './use-generation.js';
|
|
@@ -24,4 +24,4 @@ export { useGenerateVideo } from './use-generate-video.js';
|
|
|
24
24
|
export type { UseGenerateVideoOptions, UseGenerateVideoReturn, } from './use-generate-video.js';
|
|
25
25
|
export { useAudioRecorder } from './use-audio-recorder.js';
|
|
26
26
|
export type { UseAudioRecorderOptions, UseAudioRecorderReturn, } from './use-audio-recorder.js';
|
|
27
|
-
export { AudioRecorder, ChatClient, InterruptManager, RealtimeClient, GenerationClient, VideoGenerationClient, GENERATION_EVENTS, reconstructImageResult, reconstructAudioResult, reconstructSpeechResult, reconstructTranscriptionResult, reconstructSummarizeResult, UnsupportedResponseStreamError, createAIDevtoolsGenerationPreview, StreamTruncatedError, DurableStreamIncompleteError, StreamReconnectLimitError, uiMessageToModelMessages, modelMessageToUIMessage, modelMessagesToUIMessages, convertMessagesToModelMessages, normalizeToUIMessage, generateMessageId, StreamProcessor, ImmediateStrategy, PunctuationStrategy, BatchStrategy, WordBoundaryStrategy, CompositeStrategy, parsePartialJSON, PartialJSONParser, defaultJSONParser, registerWebMCPTools, clientTools, fetchServerSentEvents, localStoragePersistence, sessionStoragePersistence, indexedDBPersistence, StorageUnavailableError, type ChatClientPersistence, type ChatPersistedState, type ChatPersistenceOption, type ChatStorageAdapter, type WebStoragePersistenceOptions, type IndexedDBPersistenceOptions, fetchHttpStream, xhrServerSentEvents, xhrHttpStream, stream, rpcStream, webSocket, createChatClientOptions, createMcpAppBridge, type McpAppBridge, type CreateMcpAppBridgeOptions, type ChatFetcher, type ChatFetcherInput, type ChatFetcherOptions, type ConnectionAdapter, type ConnectConnectionAdapter, type SubscribeConnectionAdapter, type RunAgentInputContext, type FetchConnectionOptions, type XhrConnectionOptions, type WebSocketConnectionOptions, type InferChatMessages, type GenericInterrupt, type RegisteredGenericInterrupt, type GenerationClientState, type ImageGenerateInput, type AudioGenerateInput, type SpeechGenerateInput, type TranscriptionGenerateInput, type SummarizeGenerateInput, type VideoGenerateInput, type VideoGenerateResult, type VideoStatusInfo, } from '@tanstack/ai-client';
|
|
27
|
+
export { AudioRecorder, ChatClient, InterruptManager, RealtimeClient, GenerationClient, VideoGenerationClient, GENERATION_EVENTS, reconstructImageResult, reconstructAudioResult, reconstructSpeechResult, reconstructTranscriptionResult, reconstructSummarizeResult, UnsupportedResponseStreamError, createAIDevtoolsGenerationPreview, StreamTruncatedError, DurableStreamIncompleteError, StreamReconnectLimitError, uiMessageToModelMessages, modelMessageToUIMessage, modelMessagesToUIMessages, convertMessagesToModelMessages, normalizeToUIMessage, generateMessageId, StreamProcessor, ImmediateStrategy, PunctuationStrategy, BatchStrategy, WordBoundaryStrategy, CompositeStrategy, parsePartialJSON, PartialJSONParser, defaultJSONParser, registerWebMCPTools, getWebMCPTools, subscribeWebMCPTools, type GetWebMCPToolsOptions, type SubscribeWebMCPToolsOptions, type WebMCPPageTool, clientTools, fetchServerSentEvents, localStoragePersistence, sessionStoragePersistence, indexedDBPersistence, StorageUnavailableError, type ChatClientPersistence, type ChatPersistedState, type ChatPersistenceOption, type ChatStorageAdapter, type WebStoragePersistenceOptions, type IndexedDBPersistenceOptions, fetchHttpStream, xhrServerSentEvents, xhrHttpStream, stream, rpcStream, webSocket, createChatClientOptions, createMcpAppBridge, type McpAppBridge, type CreateMcpAppBridgeOptions, type ChatFetcher, type ChatFetcherInput, type ChatFetcherOptions, type ConnectionAdapter, type ConnectConnectionAdapter, type SubscribeConnectionAdapter, type RunAgentInputContext, type FetchConnectionOptions, type XhrConnectionOptions, type WebSocketConnectionOptions, type InferChatMessages, type GenericInterrupt, type RegisteredGenericInterrupt, type GenerationClientState, type ImageGenerateInput, type AudioGenerateInput, type SpeechGenerateInput, type TranscriptionGenerateInput, type SummarizeGenerateInput, type VideoGenerateInput, type VideoGenerateResult, type VideoStatusInfo, } from '@tanstack/ai-client';
|
package/dist/esm/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { createChatHook } from "./create-chat-hook.js";
|
|
|
3
3
|
import { useByok } from "./use-byok.js";
|
|
4
4
|
import { useRealtimeChat } from "./use-realtime-chat.js";
|
|
5
5
|
import { useMcpAppBridge } from "./use-mcp-app-bridge.js";
|
|
6
|
-
import { useWebMCPTools } from "./use-web-mcp-tools.js";
|
|
6
|
+
import { usePageWebMCPTools, useRegisterWebMCPTools, useWebMCPTools } from "./use-web-mcp-tools.js";
|
|
7
7
|
import { useGeneration } from "./use-generation.js";
|
|
8
8
|
import { useGenerateImage } from "./use-generate-image.js";
|
|
9
9
|
import { useGenerateAudio } from "./use-generate-audio.js";
|
|
@@ -12,5 +12,5 @@ import { useTranscription } from "./use-transcription.js";
|
|
|
12
12
|
import { useSummarize } from "./use-summarize.js";
|
|
13
13
|
import { useGenerateVideo } from "./use-generate-video.js";
|
|
14
14
|
import { useAudioRecorder } from "./use-audio-recorder.js";
|
|
15
|
-
import { AudioRecorder, BatchStrategy, ChatClient, CompositeStrategy, DurableStreamIncompleteError, GENERATION_EVENTS, GenerationClient, ImmediateStrategy, InterruptManager, PartialJSONParser, PunctuationStrategy, RealtimeClient, StorageUnavailableError, StreamProcessor, StreamReconnectLimitError, StreamTruncatedError, UnsupportedResponseStreamError, VideoGenerationClient, WordBoundaryStrategy, clientTools, convertMessagesToModelMessages, createAIDevtoolsGenerationPreview, createChatClientOptions, createMcpAppBridge, defaultJSONParser, fetchHttpStream, fetchServerSentEvents, generateMessageId, indexedDBPersistence, localStoragePersistence, modelMessageToUIMessage, modelMessagesToUIMessages, normalizeToUIMessage, parsePartialJSON, reconstructAudioResult, reconstructImageResult, reconstructSpeechResult, reconstructSummarizeResult, reconstructTranscriptionResult, registerWebMCPTools, rpcStream, sessionStoragePersistence, stream, uiMessageToModelMessages, webSocket, xhrHttpStream, xhrServerSentEvents } from "@tanstack/ai-client";
|
|
16
|
-
export { AudioRecorder, BatchStrategy, ChatClient, CompositeStrategy, DurableStreamIncompleteError, GENERATION_EVENTS, GenerationClient, ImmediateStrategy, InterruptManager, PartialJSONParser, PunctuationStrategy, RealtimeClient, StorageUnavailableError, StreamProcessor, StreamReconnectLimitError, StreamTruncatedError, UnsupportedResponseStreamError, VideoGenerationClient, WordBoundaryStrategy, clientTools, convertMessagesToModelMessages, createAIDevtoolsGenerationPreview, createChatClientOptions, createChatHook, createMcpAppBridge, defaultJSONParser, fetchHttpStream, fetchServerSentEvents, generateMessageId, indexedDBPersistence, localStoragePersistence, modelMessageToUIMessage, modelMessagesToUIMessages, normalizeToUIMessage, parsePartialJSON, reconstructAudioResult, reconstructImageResult, reconstructSpeechResult, reconstructSummarizeResult, reconstructTranscriptionResult, registerWebMCPTools, rpcStream, sessionStoragePersistence, stream, uiMessageToModelMessages, useAudioRecorder, useByok, useChat, useGenerateAudio, useGenerateImage, useGenerateSpeech, useGenerateVideo, useGeneration, useMcpAppBridge, useRealtimeChat, useSummarize, useTranscription, useWebMCPTools, webSocket, xhrHttpStream, xhrServerSentEvents };
|
|
15
|
+
import { AudioRecorder, BatchStrategy, ChatClient, CompositeStrategy, DurableStreamIncompleteError, GENERATION_EVENTS, GenerationClient, ImmediateStrategy, InterruptManager, PartialJSONParser, PunctuationStrategy, RealtimeClient, StorageUnavailableError, StreamProcessor, StreamReconnectLimitError, StreamTruncatedError, UnsupportedResponseStreamError, VideoGenerationClient, WordBoundaryStrategy, clientTools, convertMessagesToModelMessages, createAIDevtoolsGenerationPreview, createChatClientOptions, createMcpAppBridge, defaultJSONParser, fetchHttpStream, fetchServerSentEvents, generateMessageId, getWebMCPTools, indexedDBPersistence, localStoragePersistence, modelMessageToUIMessage, modelMessagesToUIMessages, normalizeToUIMessage, parsePartialJSON, reconstructAudioResult, reconstructImageResult, reconstructSpeechResult, reconstructSummarizeResult, reconstructTranscriptionResult, registerWebMCPTools, rpcStream, sessionStoragePersistence, stream, subscribeWebMCPTools, uiMessageToModelMessages, webSocket, xhrHttpStream, xhrServerSentEvents } from "@tanstack/ai-client";
|
|
16
|
+
export { AudioRecorder, BatchStrategy, ChatClient, CompositeStrategy, DurableStreamIncompleteError, GENERATION_EVENTS, GenerationClient, ImmediateStrategy, InterruptManager, PartialJSONParser, PunctuationStrategy, RealtimeClient, StorageUnavailableError, StreamProcessor, StreamReconnectLimitError, StreamTruncatedError, UnsupportedResponseStreamError, VideoGenerationClient, WordBoundaryStrategy, clientTools, convertMessagesToModelMessages, createAIDevtoolsGenerationPreview, createChatClientOptions, createChatHook, createMcpAppBridge, defaultJSONParser, fetchHttpStream, fetchServerSentEvents, generateMessageId, getWebMCPTools, indexedDBPersistence, localStoragePersistence, modelMessageToUIMessage, modelMessagesToUIMessages, normalizeToUIMessage, parsePartialJSON, reconstructAudioResult, reconstructImageResult, reconstructSpeechResult, reconstructSummarizeResult, reconstructTranscriptionResult, registerWebMCPTools, rpcStream, sessionStoragePersistence, stream, subscribeWebMCPTools, uiMessageToModelMessages, useAudioRecorder, useByok, useChat, useGenerateAudio, useGenerateImage, useGenerateSpeech, useGenerateVideo, useGeneration, useMcpAppBridge, usePageWebMCPTools, useRealtimeChat, useRegisterWebMCPTools, useSummarize, useTranscription, useWebMCPTools, webSocket, xhrHttpStream, xhrServerSentEvents };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useGenerationWithDevtoolsIdentity } from "./use-generation.js";
|
|
2
2
|
import { reconstructAudioResult } from "@tanstack/ai-client";
|
|
3
3
|
//#region src/use-generate-audio.ts
|
|
4
4
|
/**
|
|
@@ -32,16 +32,12 @@ import { reconstructAudioResult } from "@tanstack/ai-client";
|
|
|
32
32
|
* ```
|
|
33
33
|
*/
|
|
34
34
|
function useGenerateAudio(options) {
|
|
35
|
-
|
|
36
|
-
...options.devtools,
|
|
37
|
-
framework: "react",
|
|
38
|
-
hookName: "useGenerateAudio",
|
|
39
|
-
outputKind: "audio"
|
|
40
|
-
};
|
|
41
|
-
return useGeneration({
|
|
35
|
+
return useGenerationWithDevtoolsIdentity({
|
|
42
36
|
...options,
|
|
43
|
-
devtools,
|
|
44
37
|
reconstructResult: reconstructAudioResult
|
|
38
|
+
}, {
|
|
39
|
+
hookName: "useGenerateAudio",
|
|
40
|
+
outputKind: "audio"
|
|
45
41
|
});
|
|
46
42
|
}
|
|
47
43
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-generate-audio.js","names":[],"sources":["../../src/use-generate-audio.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"use-generate-audio.js","names":[],"sources":["../../src/use-generate-audio.ts"],"sourcesContent":["import { useGenerationWithDevtoolsIdentity } from './use-generation'\nimport { reconstructAudioResult } from '@tanstack/ai-client'\nimport type { AudioGenerationResult, StreamChunk } from '@tanstack/ai'\nimport type {\n AIDevtoolsDisplayOptions,\n AudioGenerateInput,\n ConnectConnectionAdapter,\n GenerationClientState,\n GenerationFetcher,\n GenerationPersistenceOptions,\n InferGenerationOutputFromReturn,\n} from '@tanstack/ai-client'\nimport type { ByokClient } from '@tanstack/ai-client/byok'\nimport type { ProviderId } from '@tanstack/ai/byok'\n\n/**\n * Options for the useGenerateAudio hook.\n *\n * @template TOutput - The output type after optional transform (defaults to AudioGenerationResult)\n */\nexport interface UseGenerateAudioOptions<TOutput = AudioGenerationResult> {\n /** Connect-based adapter for streaming transport (SSE, HTTP stream, custom) */\n connection?: ConnectConnectionAdapter\n /** Direct async function for audio generation */\n fetcher?: GenerationFetcher<AudioGenerateInput, AudioGenerationResult>\n /** Additional body parameters to send with connect-based adapter requests */\n body?: Record<string, any>\n /** Optional BYOK keyring. Keys go in `x-byok-*` headers, never the body. */\n byok?: ByokClient\n /** Optional provider id. If it returns a slug, only that key is sent. If no slug resolves (`byokProvider`, then `body.provider`), generate throws. */\n byokProvider?: () => ProviderId | undefined\n /** Display options for TanStack AI Devtools. */\n devtools?: AIDevtoolsDisplayOptions\n /**\n * How this generation persists across reloads.\n * - Omit / `false`: ephemeral, in-memory only.\n * - `true`: server-driven — on mount the client hydrates the last generation\n * for its `threadId` from the server (needs a connection with a\n * `hydrateGeneration` handler) and repaints it; it never auto-starts a run.\n */\n persistence?: boolean\n /**\n * The **scope** this generation belongs to: a stable, app-chosen name for the\n * slot successive runs fill — not a link to a chat conversation.\n *\n * The hook starts empty and produces many runs over its life; each gets its\n * own `runId`, but all belong to one scope. Persistence keys on this, so\n * derive it from your own domain and keep it identical across reloads (e.g.\n * `` `video-${videoId}-start-frame` ``). It is also sent as the AG-UI thread\n * id on the wire, which the protocol requires.\n *\n * **Required whenever `persistence` is set** — an app that cannot name the\n * scope has nothing to restore to. Optional for ephemeral generations. If\n * omitted, the client mints a wire id after mount.\n */\n threadId?: string\n /**\n * Server-driven hydration handler for `persistence: true` when the\n * connection doesn't carry one (e.g. alongside `fetcher`, or a `stream()` /\n * `rpcStream()` adapter built without handlers) — typically a one-line\n * server-function call. The connection's own handler takes precedence.\n */\n hydrateGeneration?: ConnectConnectionAdapter['hydrateGeneration']\n /**\n * Re-attach handler that replays a run still generating to completion on\n * mount, when the connection doesn't carry one. Without it, a restored\n * `running` snapshot surfaces as an (interrupted) error. The connection's\n * own handler takes precedence.\n */\n joinRun?: ConnectConnectionAdapter['joinRun']\n /**\n * Callback when audio is generated. Can optionally return a transformed value.\n *\n * - Return a non-null value to transform and store it as the result\n * - Return `null` to keep the previous result unchanged\n * - Return nothing (`void`) to store the raw result as-is\n */\n onResult?: (result: AudioGenerationResult) => TOutput | null | void\n /** Callback when an error occurs */\n onError?: (error: Error) => void\n /** Callback when progress is reported (0-100) */\n onProgress?: (progress: number, message?: string) => void\n /** Callback for each stream chunk (connect-based adapter mode only) */\n onChunk?: (chunk: StreamChunk) => void\n}\n\n/**\n * Return type for the useGenerateAudio hook.\n *\n * @template TOutput - The output type (after optional transform)\n */\nexport interface UseGenerateAudioReturn<TOutput = AudioGenerationResult> {\n /** Trigger audio generation */\n generate: (input: AudioGenerateInput) => Promise<void>\n /** The generation result containing audio, or null */\n result: TOutput | null\n /** Whether generation is in progress */\n isLoading: boolean\n /** Current error, if any */\n error: Error | undefined\n /** Current state of the generation */\n status: GenerationClientState\n /** Abort the current generation */\n stop: () => void\n /** Clear result, error, and return to idle */\n reset: () => void\n /**\n * The id of the generation job currently running, or `null` when nothing is in\n * flight. Each call to `generate` is one job with its own id. Pass it to your\n * own endpoint to cancel or poll the provider job — `stop()` only aborts the\n * local stream, it does not stop work already running on the provider.\n */\n runId: string | null\n}\n\n/**\n * React hook for generating audio (music, sound effects) using AI models.\n *\n * Supports two transport modes:\n * - **ConnectConnectionAdapter** — Streaming transport (SSE, HTTP stream, custom)\n * - **Fetcher** — Direct async function call\n *\n * @example\n * ```tsx\n * import { useGenerateAudio } from '@tanstack/ai-react'\n * import { fetchServerSentEvents } from '@tanstack/ai-client'\n *\n * function AudioGenerator() {\n * const { generate, result, isLoading, error, reset } = useGenerateAudio({\n * connection: fetchServerSentEvents('/api/generate/audio'),\n * })\n *\n * return (\n * <div>\n * <button onClick={() => generate({ prompt: 'An upbeat electronic track', duration: 10 })}>\n * Generate\n * </button>\n * {isLoading && <p>Generating...</p>}\n * {error && <p>Error: {error.message}</p>}\n * {result?.audio.url && <audio src={result.audio.url} controls />}\n * </div>\n * )\n * }\n * ```\n */\nexport function useGenerateAudio<TTransformed = void>(\n options: Omit<\n UseGenerateAudioOptions,\n 'onResult' | 'persistence' | 'threadId'\n > & {\n onResult?: (result: AudioGenerationResult) => TTransformed\n } & GenerationPersistenceOptions,\n): UseGenerateAudioReturn<\n InferGenerationOutputFromReturn<AudioGenerationResult, TTransformed>\n> {\n const generation = useGenerationWithDevtoolsIdentity<\n AudioGenerateInput,\n AudioGenerationResult,\n TTransformed\n >(\n { ...options, reconstructResult: reconstructAudioResult },\n {\n hookName: 'useGenerateAudio',\n outputKind: 'audio',\n },\n )\n\n return generation\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiJA,SAAgB,iBACd,SAQA;CAaA,OAZmB,kCAKjB;EAAE,GAAG;EAAS,mBAAmB;CAAuB,GACxD;EACE,UAAU;EACV,YAAY;CACd,CAGK;AACT"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useGenerationWithDevtoolsIdentity } from "./use-generation.js";
|
|
2
2
|
import { reconstructImageResult } from "@tanstack/ai-client";
|
|
3
3
|
//#region src/use-generate-image.ts
|
|
4
4
|
/**
|
|
@@ -34,16 +34,12 @@ import { reconstructImageResult } from "@tanstack/ai-client";
|
|
|
34
34
|
* ```
|
|
35
35
|
*/
|
|
36
36
|
function useGenerateImage(options) {
|
|
37
|
-
|
|
38
|
-
...options.devtools,
|
|
39
|
-
framework: "react",
|
|
40
|
-
hookName: "useGenerateImage",
|
|
41
|
-
outputKind: "image"
|
|
42
|
-
};
|
|
43
|
-
return useGeneration({
|
|
37
|
+
return useGenerationWithDevtoolsIdentity({
|
|
44
38
|
...options,
|
|
45
|
-
devtools,
|
|
46
39
|
reconstructResult: reconstructImageResult
|
|
40
|
+
}, {
|
|
41
|
+
hookName: "useGenerateImage",
|
|
42
|
+
outputKind: "image"
|
|
47
43
|
});
|
|
48
44
|
}
|
|
49
45
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-generate-image.js","names":[],"sources":["../../src/use-generate-image.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"use-generate-image.js","names":[],"sources":["../../src/use-generate-image.ts"],"sourcesContent":["import { useGenerationWithDevtoolsIdentity } from './use-generation'\nimport { reconstructImageResult } from '@tanstack/ai-client'\nimport type { ImageGenerationResult, StreamChunk } from '@tanstack/ai'\nimport type {\n AIDevtoolsDisplayOptions,\n ConnectConnectionAdapter,\n GenerationClientState,\n GenerationFetcher,\n GenerationPersistenceOptions,\n ImageGenerateInput,\n InferGenerationOutputFromReturn,\n} from '@tanstack/ai-client'\nimport type { ByokClient } from '@tanstack/ai-client/byok'\nimport type { ProviderId } from '@tanstack/ai/byok'\n\n/**\n * Options for the useGenerateImage hook.\n *\n * @template TOutput - The output type after optional transform (defaults to ImageGenerationResult)\n */\nexport interface UseGenerateImageOptions<TOutput = ImageGenerationResult> {\n /** Connect-based adapter for streaming transport (SSE, HTTP stream, custom) */\n connection?: ConnectConnectionAdapter\n /** Direct async function for image generation */\n fetcher?: GenerationFetcher<ImageGenerateInput, ImageGenerationResult>\n /** Additional body parameters to send with connect-based adapter requests */\n body?: Record<string, any>\n /** Optional BYOK keyring. Keys go in `x-byok-*` headers, never the body. */\n byok?: ByokClient\n /** Optional provider id. If it returns a slug, only that key is sent. If no slug resolves (`byokProvider`, then `body.provider`), generate throws. */\n byokProvider?: () => ProviderId | undefined\n /** Display options for TanStack AI Devtools. */\n devtools?: AIDevtoolsDisplayOptions\n /**\n * How this generation persists across reloads.\n * - Omit / `false`: ephemeral, in-memory only.\n * - `true`: server-driven — on mount the client hydrates the last generation\n * for its `threadId` from the server (needs a connection with a\n * `hydrateGeneration` handler) and repaints it; it never auto-starts a run.\n */\n persistence?: boolean\n /**\n * The **scope** this generation belongs to: a stable, app-chosen name for the\n * slot successive runs fill — not a link to a chat conversation.\n *\n * The hook starts empty and produces many runs over its life; each gets its\n * own `runId`, but all belong to one scope. Persistence keys on this, so\n * derive it from your own domain and keep it identical across reloads (e.g.\n * `` `video-${videoId}-start-frame` ``). It is also sent as the AG-UI thread\n * id on the wire, which the protocol requires.\n *\n * **Required whenever `persistence` is set** — an app that cannot name the\n * scope has nothing to restore to. Optional for ephemeral generations. If\n * omitted, the client mints a wire id after mount.\n */\n threadId?: string\n /**\n * Server-driven hydration handler for `persistence: true` when the\n * connection doesn't carry one (e.g. alongside `fetcher`, or a `stream()` /\n * `rpcStream()` adapter built without handlers) — typically a one-line\n * server-function call. The connection's own handler takes precedence.\n */\n hydrateGeneration?: ConnectConnectionAdapter['hydrateGeneration']\n /**\n * Re-attach handler that replays a run still generating to completion on\n * mount, when the connection doesn't carry one. Without it, a restored\n * `running` snapshot surfaces as an (interrupted) error. The connection's\n * own handler takes precedence.\n */\n joinRun?: ConnectConnectionAdapter['joinRun']\n /**\n * Callback when images are generated. Can optionally return a transformed value.\n *\n * - Return a non-null value to transform and store it as the result\n * - Return `null` to keep the previous result unchanged\n * - Return nothing (`void`) to store the raw result as-is\n */\n onResult?: (result: ImageGenerationResult) => TOutput | null | void\n /** Callback when an error occurs */\n onError?: (error: Error) => void\n /** Callback when progress is reported (0-100) */\n onProgress?: (progress: number, message?: string) => void\n /** Callback for each stream chunk (connect-based adapter mode only) */\n onChunk?: (chunk: StreamChunk) => void\n}\n\n/**\n * Return type for the useGenerateImage hook.\n *\n * @template TOutput - The output type (after optional transform)\n */\nexport interface UseGenerateImageReturn<TOutput = ImageGenerationResult> {\n /** Trigger image generation */\n generate: (input: ImageGenerateInput) => Promise<void>\n /** The generation result containing images, or null */\n result: TOutput | null\n /** Whether generation is in progress */\n isLoading: boolean\n /** Current error, if any */\n error: Error | undefined\n /** Current state of the generation */\n status: GenerationClientState\n /** Abort the current generation */\n stop: () => void\n /** Clear result, error, and return to idle */\n reset: () => void\n /**\n * The id of the generation job currently running, or `null` when nothing is in\n * flight. Each call to `generate` is one job with its own id. Pass it to your\n * own endpoint to cancel or poll the provider job — `stop()` only aborts the\n * local stream, it does not stop work already running on the provider.\n */\n runId: string | null\n}\n\n/**\n * React hook for generating images using AI models.\n *\n * Supports two transport modes:\n * - **ConnectConnectionAdapter** — Streaming transport (SSE, HTTP stream, custom)\n * - **Fetcher** — Direct async function call\n *\n * @example\n * ```tsx\n * import { useGenerateImage } from '@tanstack/ai-react'\n * import { fetchServerSentEvents } from '@tanstack/ai-client'\n *\n * function ImageGenerator() {\n * const { generate, result, isLoading, error, reset } = useGenerateImage({\n * connection: fetchServerSentEvents('/api/generate/image'),\n * })\n *\n * return (\n * <div>\n * <button onClick={() => generate({ prompt: 'A sunset over mountains' })}>\n * Generate\n * </button>\n * {isLoading && <p>Generating...</p>}\n * {error && <p>Error: {error.message}</p>}\n * {result?.images.map((img, i) => (\n * <img key={i} src={img.url || `data:image/png;base64,${img.b64Json}`} />\n * ))}\n * </div>\n * )\n * }\n * ```\n */\nexport function useGenerateImage<TTransformed = void>(\n options: Omit<\n UseGenerateImageOptions,\n 'onResult' | 'persistence' | 'threadId'\n > & {\n onResult?: (result: ImageGenerationResult) => TTransformed\n } & GenerationPersistenceOptions,\n): UseGenerateImageReturn<\n InferGenerationOutputFromReturn<ImageGenerationResult, TTransformed>\n> {\n const generation = useGenerationWithDevtoolsIdentity<\n ImageGenerateInput,\n ImageGenerationResult,\n TTransformed\n >(\n { ...options, reconstructResult: reconstructImageResult },\n {\n hookName: 'useGenerateImage',\n outputKind: 'image',\n },\n )\n\n return generation\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmJA,SAAgB,iBACd,SAQA;CAaA,OAZmB,kCAKjB;EAAE,GAAG;EAAS,mBAAmB;CAAuB,GACxD;EACE,UAAU;EACV,YAAY;CACd,CAGK;AACT"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useGenerationWithDevtoolsIdentity } from "./use-generation.js";
|
|
2
2
|
import { reconstructSpeechResult } from "@tanstack/ai-client";
|
|
3
3
|
//#region src/use-generate-speech.ts
|
|
4
4
|
/**
|
|
@@ -28,16 +28,12 @@ import { reconstructSpeechResult } from "@tanstack/ai-client";
|
|
|
28
28
|
* ```
|
|
29
29
|
*/
|
|
30
30
|
function useGenerateSpeech(options) {
|
|
31
|
-
|
|
32
|
-
...options.devtools,
|
|
33
|
-
framework: "react",
|
|
34
|
-
hookName: "useGenerateSpeech",
|
|
35
|
-
outputKind: "audio"
|
|
36
|
-
};
|
|
37
|
-
return useGeneration({
|
|
31
|
+
return useGenerationWithDevtoolsIdentity({
|
|
38
32
|
...options,
|
|
39
|
-
devtools,
|
|
40
33
|
reconstructResult: reconstructSpeechResult
|
|
34
|
+
}, {
|
|
35
|
+
hookName: "useGenerateSpeech",
|
|
36
|
+
outputKind: "audio"
|
|
41
37
|
});
|
|
42
38
|
}
|
|
43
39
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-generate-speech.js","names":[],"sources":["../../src/use-generate-speech.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"use-generate-speech.js","names":[],"sources":["../../src/use-generate-speech.ts"],"sourcesContent":["import { useGenerationWithDevtoolsIdentity } from './use-generation'\nimport { reconstructSpeechResult } from '@tanstack/ai-client'\nimport type { StreamChunk, TTSResult } from '@tanstack/ai'\nimport type {\n AIDevtoolsDisplayOptions,\n ConnectConnectionAdapter,\n GenerationClientState,\n GenerationFetcher,\n GenerationPersistenceOptions,\n InferGenerationOutputFromReturn,\n SpeechGenerateInput,\n} from '@tanstack/ai-client'\nimport type { ByokClient } from '@tanstack/ai-client/byok'\nimport type { ProviderId } from '@tanstack/ai/byok'\n\n/**\n * Options for the useGenerateSpeech hook.\n *\n * @template TOutput - The output type after optional transform (defaults to TTSResult)\n */\nexport interface UseGenerateSpeechOptions<TOutput = TTSResult> {\n /** Connect-based adapter for streaming transport (SSE, HTTP stream, custom) */\n connection?: ConnectConnectionAdapter\n /** Direct async function for speech generation */\n fetcher?: GenerationFetcher<SpeechGenerateInput, TTSResult>\n /** Additional body parameters to send with connect-based adapter requests */\n body?: Record<string, any>\n /** Optional BYOK keyring. Keys go in `x-byok-*` headers, never the body. */\n byok?: ByokClient\n /** Optional provider id. If it returns a slug, only that key is sent. If no slug resolves (`byokProvider`, then `body.provider`), generate throws. */\n byokProvider?: () => ProviderId | undefined\n /** Display options for TanStack AI Devtools. */\n devtools?: AIDevtoolsDisplayOptions\n /**\n * How this generation persists across reloads.\n * - Omit / `false`: ephemeral, in-memory only.\n * - `true`: server-driven — on mount the client hydrates the last generation\n * for its `threadId` from the server (needs a connection with a\n * `hydrateGeneration` handler) and repaints it; it never auto-starts a run.\n */\n persistence?: boolean\n /**\n * The **scope** this generation belongs to: a stable, app-chosen name for the\n * slot successive runs fill — not a link to a chat conversation.\n *\n * The hook starts empty and produces many runs over its life; each gets its\n * own `runId`, but all belong to one scope. Persistence keys on this, so\n * derive it from your own domain and keep it identical across reloads (e.g.\n * `` `video-${videoId}-start-frame` ``). It is also sent as the AG-UI thread\n * id on the wire, which the protocol requires.\n *\n * **Required whenever `persistence` is set** — an app that cannot name the\n * scope has nothing to restore to. Optional for ephemeral generations. If\n * omitted, the client mints a wire id after mount.\n */\n threadId?: string\n /**\n * Server-driven hydration handler for `persistence: true` when the\n * connection doesn't carry one (e.g. alongside `fetcher`, or a `stream()` /\n * `rpcStream()` adapter built without handlers) — typically a one-line\n * server-function call. The connection's own handler takes precedence.\n */\n hydrateGeneration?: ConnectConnectionAdapter['hydrateGeneration']\n /**\n * Re-attach handler that replays a run still generating to completion on\n * mount, when the connection doesn't carry one. Without it, a restored\n * `running` snapshot surfaces as an (interrupted) error. The connection's\n * own handler takes precedence.\n */\n joinRun?: ConnectConnectionAdapter['joinRun']\n /**\n * Callback when speech is generated. Can optionally return a transformed value.\n *\n * - Return a non-null value to transform and store it as the result\n * - Return `null` to keep the previous result unchanged\n * - Return nothing (`void`) to store the raw result as-is\n */\n onResult?: (result: TTSResult) => TOutput | null | void\n /** Callback when an error occurs */\n onError?: (error: Error) => void\n /** Callback when progress is reported (0-100) */\n onProgress?: (progress: number, message?: string) => void\n /** Callback for each stream chunk (connect-based adapter mode only) */\n onChunk?: (chunk: StreamChunk) => void\n}\n\n/**\n * Return type for the useGenerateSpeech hook.\n *\n * @template TOutput - The output type (after optional transform)\n */\nexport interface UseGenerateSpeechReturn<TOutput = TTSResult> {\n /** Trigger speech generation */\n generate: (input: SpeechGenerateInput) => Promise<void>\n /** The TTS result containing audio data, or null */\n result: TOutput | null\n /** Whether generation is in progress */\n isLoading: boolean\n /** Current error, if any */\n error: Error | undefined\n /** Current state of the generation */\n status: GenerationClientState\n /** Abort the current generation */\n stop: () => void\n /** Clear result, error, and return to idle */\n reset: () => void\n /**\n * The id of the generation job currently running, or `null` when nothing is in\n * flight. Each call to `generate` is one job with its own id. Pass it to your\n * own endpoint to cancel or poll the provider job — `stop()` only aborts the\n * local stream, it does not stop work already running on the provider.\n */\n runId: string | null\n}\n\n/**\n * React hook for generating speech (text-to-speech) using AI models.\n *\n * @example\n * ```tsx\n * import { useGenerateSpeech } from '@tanstack/ai-react'\n * import { fetchServerSentEvents } from '@tanstack/ai-client'\n *\n * function SpeechGenerator() {\n * const { generate, result, isLoading } = useGenerateSpeech({\n * connection: fetchServerSentEvents('/api/generate/speech'),\n * })\n *\n * return (\n * <div>\n * <button onClick={() => generate({ text: 'Hello world', voice: 'alloy' })}>\n * Generate Speech\n * </button>\n * {result && (\n * <audio src={`data:audio/${result.format};base64,${result.audio}`} controls />\n * )}\n * </div>\n * )\n * }\n * ```\n */\nexport function useGenerateSpeech<TTransformed = void>(\n options: Omit<\n UseGenerateSpeechOptions,\n 'onResult' | 'persistence' | 'threadId'\n > & {\n onResult?: (result: TTSResult) => TTransformed\n } & GenerationPersistenceOptions,\n): UseGenerateSpeechReturn<\n InferGenerationOutputFromReturn<TTSResult, TTransformed>\n> {\n const generation = useGenerationWithDevtoolsIdentity<\n SpeechGenerateInput,\n TTSResult,\n TTransformed\n >(\n { ...options, reconstructResult: reconstructSpeechResult },\n {\n hookName: 'useGenerateSpeech',\n outputKind: 'audio',\n },\n )\n\n return generation\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6IA,SAAgB,kBACd,SAQA;CAaA,OAZmB,kCAKjB;EAAE,GAAG;EAAS,mBAAmB;CAAwB,GACzD;EACE,UAAU;EACV,YAAY;CACd,CAGK;AACT"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { StreamChunk } from '@tanstack/ai';
|
|
2
|
-
import { AIDevtoolsDisplayOptions, ConnectConnectionAdapter, GenerationClientState, GenerationFetcher, GenerationPersistenceOptions, GenerationRestoredResult, InferGenerationOutputFromReturn } from '@tanstack/ai-client';
|
|
2
|
+
import { AIDevtoolsClientMetadata, AIDevtoolsDisplayOptions, ConnectConnectionAdapter, GenerationClientState, GenerationFetcher, GenerationPersistenceOptions, GenerationRestoredResult, InferGenerationOutputFromReturn } from '@tanstack/ai-client';
|
|
3
3
|
import { ByokClient } from '@tanstack/ai-client/byok';
|
|
4
4
|
import { ProviderId } from '@tanstack/ai/byok';
|
|
5
5
|
/**
|
|
@@ -133,3 +133,12 @@ export interface UseGenerationReturn<TOutput, TInput extends Record<string, any>
|
|
|
133
133
|
export declare function useGeneration<TInput extends Record<string, any>, TResult, TTransformed = void>(options: Omit<UseGenerationOptions<TInput, TResult>, 'onResult' | 'persistence' | 'threadId'> & {
|
|
134
134
|
onResult?: (result: TResult) => TTransformed;
|
|
135
135
|
} & GenerationPersistenceOptions): UseGenerationReturn<InferGenerationOutputFromReturn<TResult, TTransformed>, TInput>;
|
|
136
|
+
interface GenerationDevtoolsIdentity {
|
|
137
|
+
hookName: AIDevtoolsClientMetadata['hookName'];
|
|
138
|
+
outputKind?: AIDevtoolsClientMetadata['outputKind'];
|
|
139
|
+
}
|
|
140
|
+
/** @internal */
|
|
141
|
+
export declare function useGenerationWithDevtoolsIdentity<TInput extends Record<string, any>, TResult, TTransformed = void>(options: Omit<UseGenerationOptions<TInput, TResult>, 'onResult' | 'persistence' | 'threadId'> & {
|
|
142
|
+
onResult?: (result: TResult) => TTransformed;
|
|
143
|
+
} & GenerationPersistenceOptions, devtoolsIdentity: GenerationDevtoolsIdentity): UseGenerationReturn<InferGenerationOutputFromReturn<TResult, TTransformed>, TInput>;
|
|
144
|
+
export {};
|
|
@@ -22,6 +22,10 @@ import { useCallback, useEffect, useId, useMemo, useRef, useState } from "react"
|
|
|
22
22
|
* ```
|
|
23
23
|
*/
|
|
24
24
|
function useGeneration(options) {
|
|
25
|
+
return useGenerationWithDevtoolsIdentity(options, { hookName: "useGeneration" });
|
|
26
|
+
}
|
|
27
|
+
/** @internal */
|
|
28
|
+
function useGenerationWithDevtoolsIdentity(options, devtoolsIdentity) {
|
|
25
29
|
const hookId = useId();
|
|
26
30
|
const clientIdentity = options.threadId ?? hookId;
|
|
27
31
|
const [result, setResult] = useState(null);
|
|
@@ -43,9 +47,10 @@ function useGeneration(options) {
|
|
|
43
47
|
...opts.reconstructResult ? { reconstructResult: opts.reconstructResult } : {},
|
|
44
48
|
devtoolsBridgeFactory: createGenerationDevtoolsBridge,
|
|
45
49
|
devtools: {
|
|
46
|
-
|
|
50
|
+
...opts.devtools,
|
|
47
51
|
framework: "react",
|
|
48
|
-
|
|
52
|
+
hookName: devtoolsIdentity.hookName,
|
|
53
|
+
...devtoolsIdentity.outputKind !== void 0 && { outputKind: devtoolsIdentity.outputKind }
|
|
49
54
|
},
|
|
50
55
|
onResult: ((r) => optionsRef.current.onResult?.(r)),
|
|
51
56
|
onError: (e) => {
|
|
@@ -118,6 +123,6 @@ function useGeneration(options) {
|
|
|
118
123
|
};
|
|
119
124
|
}
|
|
120
125
|
//#endregion
|
|
121
|
-
export { useGeneration };
|
|
126
|
+
export { useGeneration, useGenerationWithDevtoolsIdentity };
|
|
122
127
|
|
|
123
128
|
//# sourceMappingURL=use-generation.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-generation.js","names":[],"sources":["../../src/use-generation.ts"],"sourcesContent":["import { GenerationClient } from '@tanstack/ai-client'\nimport { createGenerationDevtoolsBridge } from '@tanstack/ai-client/devtools'\nimport { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react'\nimport type { StreamChunk } from '@tanstack/ai'\nimport type {\n AIDevtoolsDisplayOptions,\n ConnectConnectionAdapter,\n GenerationClientOptions,\n GenerationClientState,\n GenerationFetcher,\n GenerationPersistenceOptions,\n GenerationRestoredResult,\n InferGenerationOutputFromReturn,\n} from '@tanstack/ai-client'\nimport type { ByokClient } from '@tanstack/ai-client/byok'\nimport type { ProviderId } from '@tanstack/ai/byok'\n\n/**\n * Options for the useGeneration hook.\n *\n * Accepts either a `connection` (streaming transport) or a `fetcher` (direct async call).\n *\n * @template TInput - The input type for the generation request\n * @template TResult - The result type returned by the generation\n * @template TOutput - The output type after optional transform (defaults to TResult)\n */\nexport interface UseGenerationOptions<TInput, TResult, TOutput = TResult> {\n /** Connect-based adapter for streaming transport (SSE, HTTP stream, custom) */\n connection?: ConnectConnectionAdapter\n /** Direct async function for one-shot generation (no streaming protocol needed) */\n fetcher?: GenerationFetcher<TInput, TResult>\n /** Additional body parameters to send with connect-based adapter requests */\n body?: Record<string, any>\n /** Optional BYOK keyring. Keys go in `x-byok-*` headers, never the body. */\n byok?: ByokClient\n /** Optional provider id. If it returns a slug, only that key is sent. If no slug resolves (`byokProvider`, then `body.provider`), generate throws. */\n byokProvider?: () => ProviderId | undefined\n /** Display options for TanStack AI Devtools. */\n devtools?: AIDevtoolsDisplayOptions\n /**\n * How this generation persists across reloads.\n * - Omit / `false`: ephemeral, in-memory only.\n * - `true`: server-driven — on mount the client hydrates the last generation\n * for its `threadId` from the server (needs a connection with a\n * `hydrateGeneration` handler) and repaints it; it never auto-starts a run.\n */\n persistence?: boolean\n /**\n * The **scope** this generation belongs to: a stable, app-chosen name for the\n * slot successive runs fill — not a link to a chat conversation.\n *\n * The hook starts empty and produces many runs over its life; each gets its\n * own `runId`, but all belong to one scope. Persistence keys on this, so\n * derive it from your own domain and keep it identical across reloads (e.g.\n * `` `video-${videoId}-start-frame` ``). It is also sent as the AG-UI thread\n * id on the wire, which the protocol requires.\n *\n * **Required whenever `persistence` is set** — an app that cannot name the\n * scope has nothing to restore to. Optional for ephemeral generations. If\n * omitted, the client mints a wire id after mount.\n */\n threadId?: string\n /**\n * Server-driven hydration handler for `persistence: true` when the\n * connection doesn't carry one (e.g. alongside `fetcher`, or a `stream()` /\n * `rpcStream()` adapter built without handlers) — typically a one-line\n * server-function call. The connection's own handler takes precedence.\n */\n hydrateGeneration?: ConnectConnectionAdapter['hydrateGeneration']\n /**\n * Re-attach handler that replays a run still generating to completion on\n * mount, when the connection doesn't carry one. Without it, a restored\n * `running` snapshot surfaces as an (interrupted) error. The connection's\n * own handler takes precedence.\n */\n joinRun?: ConnectConnectionAdapter['joinRun']\n /**\n * Callback when a result is received. Can optionally return a transformed value.\n *\n * - Return a non-null value to transform and store it as the result\n * - Return `null` to keep the previous result unchanged\n * - Return nothing (`void`) to store the raw result as-is\n */\n onResult?: (result: TResult) => TOutput | null | void\n /** Callback when an error occurs */\n onError?: (error: Error) => void\n /** Callback when progress is reported (0-100) */\n onProgress?: (progress: number, message?: string) => void\n /** Callback for each stream chunk (connect-based adapter mode only) */\n onChunk?: (chunk: StreamChunk) => void\n /**\n * @internal Rebuild a typed result from a restored snapshot, injected by each\n * specialized hook (image / speech / audio / transcription / summarize).\n * Forwarded to the client so a server-hydrate restore repaints `result`.\n */\n reconstructResult?: (restored: GenerationRestoredResult) => TResult | null\n}\n\n/**\n * Return type for the useGeneration hook.\n *\n * @template TOutput - The output type (after optional transform)\n * @template TInput - The input type accepted by `generate` (defaults to any object)\n */\nexport interface UseGenerationReturn<\n TOutput,\n TInput extends Record<string, any> = Record<string, any>,\n> {\n /** Trigger a generation request */\n generate: (input: TInput) => Promise<void>\n /** The generation result, or null if not yet generated */\n result: TOutput | null\n /** Whether a generation is currently in progress */\n isLoading: boolean\n /** Current error, if any */\n error: Error | undefined\n /** Current state of the generation client */\n status: GenerationClientState\n /** Abort the current generation */\n stop: () => void\n /** Clear result, error, and return to idle */\n reset: () => void\n /**\n * The id of the generation job currently running, or `null` when nothing is in\n * flight. Each call to `generate` is one job with its own id. Pass it to your\n * own endpoint to cancel or poll the provider job — `stop()` only aborts the\n * local stream, it does not stop work already running on the provider.\n */\n runId: string | null\n}\n\n/**\n * Generic React hook for one-shot generation tasks.\n *\n * This is the base hook used by `useGenerateImage`, `useGenerateSpeech`,\n * `useTranscription`, and `useSummarize`. You can also use it directly\n * for custom generation types.\n *\n * @template TInput - The input type for the generation request\n * @template TResult - The result type returned by the generation\n *\n * @example\n * ```tsx\n * const { generate, result, isLoading } = useGeneration<MyInput, MyResult>({\n * connection: fetchServerSentEvents('/api/generate/custom'),\n * })\n *\n * await generate({ prompt: 'Hello' })\n * ```\n */\n// `TTransformed` infers from the `onResult` return position (a covariant\n// inference site that works even for an optional nested property), which types\n// the callback parameter as `TResult` and narrows `result`. Inferring the\n// whole callback as a defaulted type parameter instead collapses to the\n// default, leaving the parameter `any` — a hard error under `strict`. See\n// issue #848.\nexport function useGeneration<\n TInput extends Record<string, any>,\n TResult,\n TTransformed = void,\n>(\n options: Omit<\n UseGenerationOptions<TInput, TResult>,\n 'onResult' | 'persistence' | 'threadId'\n > & {\n onResult?: (result: TResult) => TTransformed\n } & GenerationPersistenceOptions,\n): UseGenerationReturn<\n InferGenerationOutputFromReturn<TResult, TTransformed>,\n TInput\n> {\n type TOutput = InferGenerationOutputFromReturn<TResult, TTransformed>\n const hookId = useId()\n // The hook identity is `threadId`. `hookId` is only a React recreation key.\n const clientIdentity = options.threadId ?? hookId\n\n const [result, setResult] = useState<TOutput | null>(null)\n const [isLoading, setIsLoading] = useState(false)\n const [error, setError] = useState<Error | undefined>(undefined)\n const [status, setStatus] = useState<GenerationClientState>('idle')\n const [runId, setRunId] = useState<string | null>(null)\n\n const optionsRef = useRef(options)\n optionsRef.current = options\n const disposedRef = useRef(false)\n\n const client = useMemo(() => {\n const opts = optionsRef.current\n\n // Conditional spread for `body` (strict-optional in target;\n // local source is `Record<string, any> | undefined`). Callbacks\n // wrap optional ones in non-returning bodies so `?.()`'s\n // implicit `undefined` doesn't pollute the function return type.\n const clientOptions: Omit<\n GenerationClientOptions<TInput, TResult, TOutput>,\n 'persistence' | 'threadId'\n > = {\n body: opts.body,\n ...(opts.hydrateGeneration !== undefined && {\n hydrateGeneration: opts.hydrateGeneration,\n }),\n ...(opts.joinRun !== undefined && { joinRun: opts.joinRun }),\n ...(opts.byok !== undefined && { byok: opts.byok }),\n byokProvider: () => optionsRef.current.byokProvider?.(),\n ...(opts.reconstructResult\n ? { reconstructResult: opts.reconstructResult }\n : {}),\n devtoolsBridgeFactory: createGenerationDevtoolsBridge,\n devtools: {\n hookName: 'useGeneration',\n framework: 'react',\n ...opts.devtools,\n },\n // The transform's raw return type (`TTransformed`) and the stored output\n // (`TOutput`, with null/void/undefined stripped) are identical at runtime;\n // the cast bridges the relationship that the conditional type hides.\n onResult: ((r: TResult) => optionsRef.current.onResult?.(r)) as (\n result: TResult,\n ) => TOutput | null | void,\n onError: (e: Error) => {\n if (!disposedRef.current) optionsRef.current.onError?.(e)\n },\n onProgress: (p: number, m?: string) => {\n if (!disposedRef.current) optionsRef.current.onProgress?.(p, m)\n },\n onChunk: (c: StreamChunk) => {\n if (!disposedRef.current) optionsRef.current.onChunk?.(c)\n },\n onResultChange: (r) => {\n if (!disposedRef.current) setResult(r)\n },\n onLoadingChange: (l) => {\n if (!disposedRef.current) setIsLoading(l)\n },\n onErrorChange: (e) => {\n if (!disposedRef.current) setError(e)\n },\n onStatusChange: (s) => {\n if (!disposedRef.current) setStatus(s)\n },\n onResumeStateChange: (rs) => {\n if (!disposedRef.current) setRunId(rs?.runId ?? null)\n },\n }\n\n const persistenceProps =\n typeof opts.threadId === 'string' && opts.persistence\n ? {\n persistence: opts.persistence,\n threadId: opts.threadId,\n }\n : {\n ...(opts.threadId !== undefined && { threadId: opts.threadId }),\n }\n\n if (opts.connection) {\n return new GenerationClient<TInput, TResult, TOutput>({\n ...clientOptions,\n ...persistenceProps,\n connection: opts.connection,\n })\n }\n\n if (opts.fetcher) {\n return new GenerationClient<TInput, TResult, TOutput>({\n ...clientOptions,\n ...persistenceProps,\n fetcher: opts.fetcher,\n })\n }\n\n throw new Error(\n 'useGeneration requires either a connection or fetcher option',\n )\n }, [clientIdentity, hookId])\n\n // Sync body changes without recreating client\n useEffect(() => {\n // Conditional spread: target uses strict-optional `body?: T`.\n client.updateOptions({\n ...(options.body !== undefined && { body: options.body }),\n })\n }, [client, options.body])\n\n // Mount devtools and clean up on unmount. Generation runs are never\n // auto-started on mount — persisted state is only displayed. Mounting\n // revives the client after a StrictMode dispose → remount replay.\n useEffect(() => {\n disposedRef.current = false\n client.mountDevtools()\n\n return () => {\n disposedRef.current = true\n client.dispose()\n }\n }, [client])\n\n const generate = useCallback(\n async (input: TInput) => {\n await client.generate(input)\n },\n [client],\n )\n\n const stop = useCallback(() => {\n client.stop()\n }, [client])\n\n const reset = useCallback(() => {\n client.reset()\n }, [client])\n\n return {\n generate,\n result,\n isLoading,\n error,\n status,\n stop,\n reset,\n runId,\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AA4JA,SAAgB,cAKd,SASA;CAEA,MAAM,SAAS,MAAM;CAErB,MAAM,iBAAiB,QAAQ,YAAY;CAE3C,MAAM,CAAC,QAAQ,aAAa,SAAyB,IAAI;CACzD,MAAM,CAAC,WAAW,gBAAgB,SAAS,KAAK;CAChD,MAAM,CAAC,OAAO,YAAY,SAA4B,KAAA,CAAS;CAC/D,MAAM,CAAC,QAAQ,aAAa,SAAgC,MAAM;CAClE,MAAM,CAAC,OAAO,YAAY,SAAwB,IAAI;CAEtD,MAAM,aAAa,OAAO,OAAO;CACjC,WAAW,UAAU;CACrB,MAAM,cAAc,OAAO,KAAK;CAEhC,MAAM,SAAS,cAAc;EAC3B,MAAM,OAAO,WAAW;EAMxB,MAAM,gBAGF;GACF,MAAM,KAAK;GACX,GAAI,KAAK,sBAAsB,KAAA,KAAa,EAC1C,mBAAmB,KAAK,kBAC1B;GACA,GAAI,KAAK,YAAY,KAAA,KAAa,EAAE,SAAS,KAAK,QAAQ;GAC1D,GAAI,KAAK,SAAS,KAAA,KAAa,EAAE,MAAM,KAAK,KAAK;GACjD,oBAAoB,WAAW,QAAQ,eAAe;GACtD,GAAI,KAAK,oBACL,EAAE,mBAAmB,KAAK,kBAAkB,IAC5C,CAAC;GACL,uBAAuB;GACvB,UAAU;IACR,UAAU;IACV,WAAW;IACX,GAAG,KAAK;GACV;GAIA,YAAY,MAAe,WAAW,QAAQ,WAAW,CAAC;GAG1D,UAAU,MAAa;IACrB,IAAI,CAAC,YAAY,SAAS,WAAW,QAAQ,UAAU,CAAC;GAC1D;GACA,aAAa,GAAW,MAAe;IACrC,IAAI,CAAC,YAAY,SAAS,WAAW,QAAQ,aAAa,GAAG,CAAC;GAChE;GACA,UAAU,MAAmB;IAC3B,IAAI,CAAC,YAAY,SAAS,WAAW,QAAQ,UAAU,CAAC;GAC1D;GACA,iBAAiB,MAAM;IACrB,IAAI,CAAC,YAAY,SAAS,UAAU,CAAC;GACvC;GACA,kBAAkB,MAAM;IACtB,IAAI,CAAC,YAAY,SAAS,aAAa,CAAC;GAC1C;GACA,gBAAgB,MAAM;IACpB,IAAI,CAAC,YAAY,SAAS,SAAS,CAAC;GACtC;GACA,iBAAiB,MAAM;IACrB,IAAI,CAAC,YAAY,SAAS,UAAU,CAAC;GACvC;GACA,sBAAsB,OAAO;IAC3B,IAAI,CAAC,YAAY,SAAS,SAAS,IAAI,SAAS,IAAI;GACtD;EACF;EAEA,MAAM,mBACJ,OAAO,KAAK,aAAa,YAAY,KAAK,cACtC;GACE,aAAa,KAAK;GAClB,UAAU,KAAK;EACjB,IACA,EACE,GAAI,KAAK,aAAa,KAAA,KAAa,EAAE,UAAU,KAAK,SAAS,EAC/D;EAEN,IAAI,KAAK,YACP,OAAO,IAAI,iBAA2C;GACpD,GAAG;GACH,GAAG;GACH,YAAY,KAAK;EACnB,CAAC;EAGH,IAAI,KAAK,SACP,OAAO,IAAI,iBAA2C;GACpD,GAAG;GACH,GAAG;GACH,SAAS,KAAK;EAChB,CAAC;EAGH,MAAM,IAAI,MACR,8DACF;CACF,GAAG,CAAC,gBAAgB,MAAM,CAAC;CAG3B,gBAAgB;EAEd,OAAO,cAAc,EACnB,GAAI,QAAQ,SAAS,KAAA,KAAa,EAAE,MAAM,QAAQ,KAAK,EACzD,CAAC;CACH,GAAG,CAAC,QAAQ,QAAQ,IAAI,CAAC;CAKzB,gBAAgB;EACd,YAAY,UAAU;EACtB,OAAO,cAAc;EAErB,aAAa;GACX,YAAY,UAAU;GACtB,OAAO,QAAQ;EACjB;CACF,GAAG,CAAC,MAAM,CAAC;CAiBX,OAAO;EACL,UAhBe,YACf,OAAO,UAAkB;GACvB,MAAM,OAAO,SAAS,KAAK;EAC7B,GACA,CAAC,MAAM,CAYP;EACA;EACA;EACA;EACA;EACA,MAdW,kBAAkB;GAC7B,OAAO,KAAK;EACd,GAAG,CAAC,MAAM,CAYR;EACA,OAXY,kBAAkB;GAC9B,OAAO,MAAM;EACf,GAAG,CAAC,MAAM,CASR;EACA;CACF;AACF"}
|
|
1
|
+
{"version":3,"file":"use-generation.js","names":[],"sources":["../../src/use-generation.ts"],"sourcesContent":["import { GenerationClient } from '@tanstack/ai-client'\nimport { createGenerationDevtoolsBridge } from '@tanstack/ai-client/devtools'\nimport { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react'\nimport type { StreamChunk } from '@tanstack/ai'\nimport type {\n AIDevtoolsClientMetadata,\n AIDevtoolsDisplayOptions,\n ConnectConnectionAdapter,\n GenerationClientOptions,\n GenerationClientState,\n GenerationFetcher,\n GenerationPersistenceOptions,\n GenerationRestoredResult,\n InferGenerationOutputFromReturn,\n} from '@tanstack/ai-client'\nimport type { ByokClient } from '@tanstack/ai-client/byok'\nimport type { ProviderId } from '@tanstack/ai/byok'\n\n/**\n * Options for the useGeneration hook.\n *\n * Accepts either a `connection` (streaming transport) or a `fetcher` (direct async call).\n *\n * @template TInput - The input type for the generation request\n * @template TResult - The result type returned by the generation\n * @template TOutput - The output type after optional transform (defaults to TResult)\n */\nexport interface UseGenerationOptions<TInput, TResult, TOutput = TResult> {\n /** Connect-based adapter for streaming transport (SSE, HTTP stream, custom) */\n connection?: ConnectConnectionAdapter\n /** Direct async function for one-shot generation (no streaming protocol needed) */\n fetcher?: GenerationFetcher<TInput, TResult>\n /** Additional body parameters to send with connect-based adapter requests */\n body?: Record<string, any>\n /** Optional BYOK keyring. Keys go in `x-byok-*` headers, never the body. */\n byok?: ByokClient\n /** Optional provider id. If it returns a slug, only that key is sent. If no slug resolves (`byokProvider`, then `body.provider`), generate throws. */\n byokProvider?: () => ProviderId | undefined\n /** Display options for TanStack AI Devtools. */\n devtools?: AIDevtoolsDisplayOptions\n /**\n * How this generation persists across reloads.\n * - Omit / `false`: ephemeral, in-memory only.\n * - `true`: server-driven — on mount the client hydrates the last generation\n * for its `threadId` from the server (needs a connection with a\n * `hydrateGeneration` handler) and repaints it; it never auto-starts a run.\n */\n persistence?: boolean\n /**\n * The **scope** this generation belongs to: a stable, app-chosen name for the\n * slot successive runs fill — not a link to a chat conversation.\n *\n * The hook starts empty and produces many runs over its life; each gets its\n * own `runId`, but all belong to one scope. Persistence keys on this, so\n * derive it from your own domain and keep it identical across reloads (e.g.\n * `` `video-${videoId}-start-frame` ``). It is also sent as the AG-UI thread\n * id on the wire, which the protocol requires.\n *\n * **Required whenever `persistence` is set** — an app that cannot name the\n * scope has nothing to restore to. Optional for ephemeral generations. If\n * omitted, the client mints a wire id after mount.\n */\n threadId?: string\n /**\n * Server-driven hydration handler for `persistence: true` when the\n * connection doesn't carry one (e.g. alongside `fetcher`, or a `stream()` /\n * `rpcStream()` adapter built without handlers) — typically a one-line\n * server-function call. The connection's own handler takes precedence.\n */\n hydrateGeneration?: ConnectConnectionAdapter['hydrateGeneration']\n /**\n * Re-attach handler that replays a run still generating to completion on\n * mount, when the connection doesn't carry one. Without it, a restored\n * `running` snapshot surfaces as an (interrupted) error. The connection's\n * own handler takes precedence.\n */\n joinRun?: ConnectConnectionAdapter['joinRun']\n /**\n * Callback when a result is received. Can optionally return a transformed value.\n *\n * - Return a non-null value to transform and store it as the result\n * - Return `null` to keep the previous result unchanged\n * - Return nothing (`void`) to store the raw result as-is\n */\n onResult?: (result: TResult) => TOutput | null | void\n /** Callback when an error occurs */\n onError?: (error: Error) => void\n /** Callback when progress is reported (0-100) */\n onProgress?: (progress: number, message?: string) => void\n /** Callback for each stream chunk (connect-based adapter mode only) */\n onChunk?: (chunk: StreamChunk) => void\n /**\n * @internal Rebuild a typed result from a restored snapshot, injected by each\n * specialized hook (image / speech / audio / transcription / summarize).\n * Forwarded to the client so a server-hydrate restore repaints `result`.\n */\n reconstructResult?: (restored: GenerationRestoredResult) => TResult | null\n}\n\n/**\n * Return type for the useGeneration hook.\n *\n * @template TOutput - The output type (after optional transform)\n * @template TInput - The input type accepted by `generate` (defaults to any object)\n */\nexport interface UseGenerationReturn<\n TOutput,\n TInput extends Record<string, any> = Record<string, any>,\n> {\n /** Trigger a generation request */\n generate: (input: TInput) => Promise<void>\n /** The generation result, or null if not yet generated */\n result: TOutput | null\n /** Whether a generation is currently in progress */\n isLoading: boolean\n /** Current error, if any */\n error: Error | undefined\n /** Current state of the generation client */\n status: GenerationClientState\n /** Abort the current generation */\n stop: () => void\n /** Clear result, error, and return to idle */\n reset: () => void\n /**\n * The id of the generation job currently running, or `null` when nothing is in\n * flight. Each call to `generate` is one job with its own id. Pass it to your\n * own endpoint to cancel or poll the provider job — `stop()` only aborts the\n * local stream, it does not stop work already running on the provider.\n */\n runId: string | null\n}\n\n/**\n * Generic React hook for one-shot generation tasks.\n *\n * This is the base hook used by `useGenerateImage`, `useGenerateSpeech`,\n * `useTranscription`, and `useSummarize`. You can also use it directly\n * for custom generation types.\n *\n * @template TInput - The input type for the generation request\n * @template TResult - The result type returned by the generation\n *\n * @example\n * ```tsx\n * const { generate, result, isLoading } = useGeneration<MyInput, MyResult>({\n * connection: fetchServerSentEvents('/api/generate/custom'),\n * })\n *\n * await generate({ prompt: 'Hello' })\n * ```\n */\n// `TTransformed` infers from the `onResult` return position (a covariant\n// inference site that works even for an optional nested property), which types\n// the callback parameter as `TResult` and narrows `result`. Inferring the\n// whole callback as a defaulted type parameter instead collapses to the\n// default, leaving the parameter `any` — a hard error under `strict`. See\n// issue #848.\nexport function useGeneration<\n TInput extends Record<string, any>,\n TResult,\n TTransformed = void,\n>(\n options: Omit<\n UseGenerationOptions<TInput, TResult>,\n 'onResult' | 'persistence' | 'threadId'\n > & {\n onResult?: (result: TResult) => TTransformed\n } & GenerationPersistenceOptions,\n): UseGenerationReturn<\n InferGenerationOutputFromReturn<TResult, TTransformed>,\n TInput\n> {\n return useGenerationWithDevtoolsIdentity<TInput, TResult, TTransformed>(\n options,\n { hookName: 'useGeneration' },\n )\n}\n\ninterface GenerationDevtoolsIdentity {\n hookName: AIDevtoolsClientMetadata['hookName']\n outputKind?: AIDevtoolsClientMetadata['outputKind']\n}\n\n/** @internal */\nexport function useGenerationWithDevtoolsIdentity<\n TInput extends Record<string, any>,\n TResult,\n TTransformed = void,\n>(\n options: Omit<\n UseGenerationOptions<TInput, TResult>,\n 'onResult' | 'persistence' | 'threadId'\n > & {\n onResult?: (result: TResult) => TTransformed\n } & GenerationPersistenceOptions,\n devtoolsIdentity: GenerationDevtoolsIdentity,\n): UseGenerationReturn<\n InferGenerationOutputFromReturn<TResult, TTransformed>,\n TInput\n> {\n type TOutput = InferGenerationOutputFromReturn<TResult, TTransformed>\n const hookId = useId()\n // The hook identity is `threadId`. `hookId` is only a React recreation key.\n const clientIdentity = options.threadId ?? hookId\n\n const [result, setResult] = useState<TOutput | null>(null)\n const [isLoading, setIsLoading] = useState(false)\n const [error, setError] = useState<Error | undefined>(undefined)\n const [status, setStatus] = useState<GenerationClientState>('idle')\n const [runId, setRunId] = useState<string | null>(null)\n\n const optionsRef = useRef(options)\n optionsRef.current = options\n const disposedRef = useRef(false)\n\n const client = useMemo(() => {\n const opts = optionsRef.current\n\n // Conditional spread for `body` (strict-optional in target;\n // local source is `Record<string, any> | undefined`). Callbacks\n // wrap optional ones in non-returning bodies so `?.()`'s\n // implicit `undefined` doesn't pollute the function return type.\n const clientOptions: Omit<\n GenerationClientOptions<TInput, TResult, TOutput>,\n 'persistence' | 'threadId'\n > = {\n body: opts.body,\n ...(opts.hydrateGeneration !== undefined && {\n hydrateGeneration: opts.hydrateGeneration,\n }),\n ...(opts.joinRun !== undefined && { joinRun: opts.joinRun }),\n ...(opts.byok !== undefined && { byok: opts.byok }),\n byokProvider: () => optionsRef.current.byokProvider?.(),\n ...(opts.reconstructResult\n ? { reconstructResult: opts.reconstructResult }\n : {}),\n devtoolsBridgeFactory: createGenerationDevtoolsBridge,\n devtools: {\n ...opts.devtools,\n framework: 'react',\n hookName: devtoolsIdentity.hookName,\n ...(devtoolsIdentity.outputKind !== undefined && {\n outputKind: devtoolsIdentity.outputKind,\n }),\n },\n // The transform's raw return type (`TTransformed`) and the stored output\n // (`TOutput`, with null/void/undefined stripped) are identical at runtime;\n // the cast bridges the relationship that the conditional type hides.\n onResult: ((r: TResult) => optionsRef.current.onResult?.(r)) as (\n result: TResult,\n ) => TOutput | null | void,\n onError: (e: Error) => {\n if (!disposedRef.current) optionsRef.current.onError?.(e)\n },\n onProgress: (p: number, m?: string) => {\n if (!disposedRef.current) optionsRef.current.onProgress?.(p, m)\n },\n onChunk: (c: StreamChunk) => {\n if (!disposedRef.current) optionsRef.current.onChunk?.(c)\n },\n onResultChange: (r) => {\n if (!disposedRef.current) setResult(r)\n },\n onLoadingChange: (l) => {\n if (!disposedRef.current) setIsLoading(l)\n },\n onErrorChange: (e) => {\n if (!disposedRef.current) setError(e)\n },\n onStatusChange: (s) => {\n if (!disposedRef.current) setStatus(s)\n },\n onResumeStateChange: (rs) => {\n if (!disposedRef.current) setRunId(rs?.runId ?? null)\n },\n }\n\n const persistenceProps =\n typeof opts.threadId === 'string' && opts.persistence\n ? {\n persistence: opts.persistence,\n threadId: opts.threadId,\n }\n : {\n ...(opts.threadId !== undefined && { threadId: opts.threadId }),\n }\n\n if (opts.connection) {\n return new GenerationClient<TInput, TResult, TOutput>({\n ...clientOptions,\n ...persistenceProps,\n connection: opts.connection,\n })\n }\n\n if (opts.fetcher) {\n return new GenerationClient<TInput, TResult, TOutput>({\n ...clientOptions,\n ...persistenceProps,\n fetcher: opts.fetcher,\n })\n }\n\n throw new Error(\n 'useGeneration requires either a connection or fetcher option',\n )\n }, [clientIdentity, hookId])\n\n // Sync body changes without recreating client\n useEffect(() => {\n // Conditional spread: target uses strict-optional `body?: T`.\n client.updateOptions({\n ...(options.body !== undefined && { body: options.body }),\n })\n }, [client, options.body])\n\n // Mount devtools and clean up on unmount. Generation runs are never\n // auto-started on mount — persisted state is only displayed. Mounting\n // revives the client after a StrictMode dispose → remount replay.\n useEffect(() => {\n disposedRef.current = false\n client.mountDevtools()\n\n return () => {\n disposedRef.current = true\n client.dispose()\n }\n }, [client])\n\n const generate = useCallback(\n async (input: TInput) => {\n await client.generate(input)\n },\n [client],\n )\n\n const stop = useCallback(() => {\n client.stop()\n }, [client])\n\n const reset = useCallback(() => {\n client.reset()\n }, [client])\n\n return {\n generate,\n result,\n isLoading,\n error,\n status,\n stop,\n reset,\n runId,\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AA6JA,SAAgB,cAKd,SASA;CACA,OAAO,kCACL,SACA,EAAE,UAAU,gBAAgB,CAC9B;AACF;;AAQA,SAAgB,kCAKd,SAMA,kBAIA;CAEA,MAAM,SAAS,MAAM;CAErB,MAAM,iBAAiB,QAAQ,YAAY;CAE3C,MAAM,CAAC,QAAQ,aAAa,SAAyB,IAAI;CACzD,MAAM,CAAC,WAAW,gBAAgB,SAAS,KAAK;CAChD,MAAM,CAAC,OAAO,YAAY,SAA4B,KAAA,CAAS;CAC/D,MAAM,CAAC,QAAQ,aAAa,SAAgC,MAAM;CAClE,MAAM,CAAC,OAAO,YAAY,SAAwB,IAAI;CAEtD,MAAM,aAAa,OAAO,OAAO;CACjC,WAAW,UAAU;CACrB,MAAM,cAAc,OAAO,KAAK;CAEhC,MAAM,SAAS,cAAc;EAC3B,MAAM,OAAO,WAAW;EAMxB,MAAM,gBAGF;GACF,MAAM,KAAK;GACX,GAAI,KAAK,sBAAsB,KAAA,KAAa,EAC1C,mBAAmB,KAAK,kBAC1B;GACA,GAAI,KAAK,YAAY,KAAA,KAAa,EAAE,SAAS,KAAK,QAAQ;GAC1D,GAAI,KAAK,SAAS,KAAA,KAAa,EAAE,MAAM,KAAK,KAAK;GACjD,oBAAoB,WAAW,QAAQ,eAAe;GACtD,GAAI,KAAK,oBACL,EAAE,mBAAmB,KAAK,kBAAkB,IAC5C,CAAC;GACL,uBAAuB;GACvB,UAAU;IACR,GAAG,KAAK;IACR,WAAW;IACX,UAAU,iBAAiB;IAC3B,GAAI,iBAAiB,eAAe,KAAA,KAAa,EAC/C,YAAY,iBAAiB,WAC/B;GACF;GAIA,YAAY,MAAe,WAAW,QAAQ,WAAW,CAAC;GAG1D,UAAU,MAAa;IACrB,IAAI,CAAC,YAAY,SAAS,WAAW,QAAQ,UAAU,CAAC;GAC1D;GACA,aAAa,GAAW,MAAe;IACrC,IAAI,CAAC,YAAY,SAAS,WAAW,QAAQ,aAAa,GAAG,CAAC;GAChE;GACA,UAAU,MAAmB;IAC3B,IAAI,CAAC,YAAY,SAAS,WAAW,QAAQ,UAAU,CAAC;GAC1D;GACA,iBAAiB,MAAM;IACrB,IAAI,CAAC,YAAY,SAAS,UAAU,CAAC;GACvC;GACA,kBAAkB,MAAM;IACtB,IAAI,CAAC,YAAY,SAAS,aAAa,CAAC;GAC1C;GACA,gBAAgB,MAAM;IACpB,IAAI,CAAC,YAAY,SAAS,SAAS,CAAC;GACtC;GACA,iBAAiB,MAAM;IACrB,IAAI,CAAC,YAAY,SAAS,UAAU,CAAC;GACvC;GACA,sBAAsB,OAAO;IAC3B,IAAI,CAAC,YAAY,SAAS,SAAS,IAAI,SAAS,IAAI;GACtD;EACF;EAEA,MAAM,mBACJ,OAAO,KAAK,aAAa,YAAY,KAAK,cACtC;GACE,aAAa,KAAK;GAClB,UAAU,KAAK;EACjB,IACA,EACE,GAAI,KAAK,aAAa,KAAA,KAAa,EAAE,UAAU,KAAK,SAAS,EAC/D;EAEN,IAAI,KAAK,YACP,OAAO,IAAI,iBAA2C;GACpD,GAAG;GACH,GAAG;GACH,YAAY,KAAK;EACnB,CAAC;EAGH,IAAI,KAAK,SACP,OAAO,IAAI,iBAA2C;GACpD,GAAG;GACH,GAAG;GACH,SAAS,KAAK;EAChB,CAAC;EAGH,MAAM,IAAI,MACR,8DACF;CACF,GAAG,CAAC,gBAAgB,MAAM,CAAC;CAG3B,gBAAgB;EAEd,OAAO,cAAc,EACnB,GAAI,QAAQ,SAAS,KAAA,KAAa,EAAE,MAAM,QAAQ,KAAK,EACzD,CAAC;CACH,GAAG,CAAC,QAAQ,QAAQ,IAAI,CAAC;CAKzB,gBAAgB;EACd,YAAY,UAAU;EACtB,OAAO,cAAc;EAErB,aAAa;GACX,YAAY,UAAU;GACtB,OAAO,QAAQ;EACjB;CACF,GAAG,CAAC,MAAM,CAAC;CAiBX,OAAO;EACL,UAhBe,YACf,OAAO,UAAkB;GACvB,MAAM,OAAO,SAAS,KAAK;EAC7B,GACA,CAAC,MAAM,CAYP;EACA;EACA;EACA;EACA;EACA,MAdW,kBAAkB;GAC7B,OAAO,KAAK;EACd,GAAG,CAAC,MAAM,CAYR;EACA,OAXY,kBAAkB;GAC9B,OAAO,MAAM;EACf,GAAG,CAAC,MAAM,CASR;EACA;CACF;AACF"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useGenerationWithDevtoolsIdentity } from "./use-generation.js";
|
|
2
2
|
import { reconstructSummarizeResult } from "@tanstack/ai-client";
|
|
3
3
|
//#region src/use-summarize.ts
|
|
4
4
|
/**
|
|
@@ -31,16 +31,12 @@ import { reconstructSummarizeResult } from "@tanstack/ai-client";
|
|
|
31
31
|
* ```
|
|
32
32
|
*/
|
|
33
33
|
function useSummarize(options) {
|
|
34
|
-
|
|
35
|
-
...options.devtools,
|
|
36
|
-
framework: "react",
|
|
37
|
-
hookName: "useSummarize",
|
|
38
|
-
outputKind: "text"
|
|
39
|
-
};
|
|
40
|
-
return useGeneration({
|
|
34
|
+
return useGenerationWithDevtoolsIdentity({
|
|
41
35
|
...options,
|
|
42
|
-
devtools,
|
|
43
36
|
reconstructResult: reconstructSummarizeResult
|
|
37
|
+
}, {
|
|
38
|
+
hookName: "useSummarize",
|
|
39
|
+
outputKind: "text"
|
|
44
40
|
});
|
|
45
41
|
}
|
|
46
42
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-summarize.js","names":[],"sources":["../../src/use-summarize.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"use-summarize.js","names":[],"sources":["../../src/use-summarize.ts"],"sourcesContent":["import { useGenerationWithDevtoolsIdentity } from './use-generation'\nimport { reconstructSummarizeResult } from '@tanstack/ai-client'\nimport type { StreamChunk, SummarizationResult } from '@tanstack/ai'\nimport type {\n AIDevtoolsDisplayOptions,\n ConnectConnectionAdapter,\n GenerationClientState,\n GenerationFetcher,\n GenerationPersistenceOptions,\n InferGenerationOutputFromReturn,\n SummarizeGenerateInput,\n} from '@tanstack/ai-client'\nimport type { ByokClient } from '@tanstack/ai-client/byok'\nimport type { ProviderId } from '@tanstack/ai/byok'\n\n/**\n * Options for the useSummarize hook.\n *\n * @template TOutput - The output type after optional transform (defaults to SummarizationResult)\n */\nexport interface UseSummarizeOptions<TOutput = SummarizationResult> {\n /** Connect-based adapter for streaming transport (SSE, HTTP stream, custom) */\n connection?: ConnectConnectionAdapter\n /** Direct async function for summarization */\n fetcher?: GenerationFetcher<SummarizeGenerateInput, SummarizationResult>\n /** Additional body parameters to send with connect-based adapter requests */\n body?: Record<string, any>\n /** Optional BYOK keyring. Keys go in `x-byok-*` headers, never the body. */\n byok?: ByokClient\n /** Optional provider id. If it returns a slug, only that key is sent. If no slug resolves (`byokProvider`, then `body.provider`), generate throws. */\n byokProvider?: () => ProviderId | undefined\n /** Display options for TanStack AI Devtools. */\n devtools?: AIDevtoolsDisplayOptions\n /**\n * How this generation persists across reloads.\n * - Omit / `false`: ephemeral, in-memory only.\n * - `true`: server-driven — on mount the client hydrates the last generation\n * for its `threadId` from the server (needs a connection with a\n * `hydrateGeneration` handler) and repaints it; it never auto-starts a run.\n */\n persistence?: boolean\n /**\n * The **scope** this generation belongs to: a stable, app-chosen name for the\n * slot successive runs fill — not a link to a chat conversation.\n *\n * The hook starts empty and produces many runs over its life; each gets its\n * own `runId`, but all belong to one scope. Persistence keys on this, so\n * derive it from your own domain and keep it identical across reloads (e.g.\n * `` `video-${videoId}-start-frame` ``). It is also sent as the AG-UI thread\n * id on the wire, which the protocol requires.\n *\n * **Required whenever `persistence` is set** — an app that cannot name the\n * scope has nothing to restore to. Optional for ephemeral generations. If\n * omitted, the client mints a wire id after mount.\n */\n threadId?: string\n /**\n * Server-driven hydration handler for `persistence: true` when the\n * connection doesn't carry one (e.g. alongside `fetcher`, or a `stream()` /\n * `rpcStream()` adapter built without handlers) — typically a one-line\n * server-function call. The connection's own handler takes precedence.\n */\n hydrateGeneration?: ConnectConnectionAdapter['hydrateGeneration']\n /**\n * Re-attach handler that replays a run still generating to completion on\n * mount, when the connection doesn't carry one. Without it, a restored\n * `running` snapshot surfaces as an (interrupted) error. The connection's\n * own handler takes precedence.\n */\n joinRun?: ConnectConnectionAdapter['joinRun']\n /**\n * Callback when summarization is complete. Can optionally return a transformed value.\n *\n * - Return a non-null value to transform and store it as the result\n * - Return `null` to keep the previous result unchanged\n * - Return nothing (`void`) to store the raw result as-is\n */\n onResult?: (result: SummarizationResult) => TOutput | null | void\n /** Callback when an error occurs */\n onError?: (error: Error) => void\n /** Callback when progress is reported (0-100) */\n onProgress?: (progress: number, message?: string) => void\n /** Callback for each stream chunk (connect-based adapter mode only) */\n onChunk?: (chunk: StreamChunk) => void\n}\n\n/**\n * Return type for the useSummarize hook.\n *\n * @template TOutput - The output type (after optional transform)\n */\nexport interface UseSummarizeReturn<TOutput = SummarizationResult> {\n /** Trigger summarization */\n generate: (input: SummarizeGenerateInput) => Promise<void>\n /** The summarization result, or null */\n result: TOutput | null\n /** Whether summarization is in progress */\n isLoading: boolean\n /** Current error, if any */\n error: Error | undefined\n /** Current state of the generation */\n status: GenerationClientState\n /** Abort the current summarization */\n stop: () => void\n /** Clear result, error, and return to idle */\n reset: () => void\n /**\n * The id of the generation job currently running, or `null` when nothing is in\n * flight. Each call to `generate` is one job with its own id. Pass it to your\n * own endpoint to cancel or poll the provider job — `stop()` only aborts the\n * local stream, it does not stop work already running on the provider.\n */\n runId: string | null\n}\n\n/**\n * React hook for summarizing text using AI models.\n *\n * @example\n * ```tsx\n * import { useSummarize } from '@tanstack/ai-react'\n * import { fetchServerSentEvents } from '@tanstack/ai-client'\n *\n * function Summarizer() {\n * const { generate, result, isLoading } = useSummarize({\n * connection: fetchServerSentEvents('/api/summarize'),\n * })\n *\n * return (\n * <div>\n * <button onClick={() => generate({\n * text: 'Long article text...',\n * style: 'bullet-points',\n * maxLength: 200,\n * })}>\n * Summarize\n * </button>\n * {isLoading && <p>Summarizing...</p>}\n * {result && <p>{result.summary}</p>}\n * </div>\n * )\n * }\n * ```\n */\nexport function useSummarize<TTransformed = void>(\n options: Omit<\n UseSummarizeOptions,\n 'onResult' | 'persistence' | 'threadId'\n > & {\n onResult?: (result: SummarizationResult) => TTransformed\n } & GenerationPersistenceOptions,\n): UseSummarizeReturn<\n InferGenerationOutputFromReturn<SummarizationResult, TTransformed>\n> {\n const generation = useGenerationWithDevtoolsIdentity<\n SummarizeGenerateInput,\n SummarizationResult,\n TTransformed\n >(\n { ...options, reconstructResult: reconstructSummarizeResult },\n {\n hookName: 'useSummarize',\n outputKind: 'text',\n },\n )\n\n return generation\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgJA,SAAgB,aACd,SAQA;CAaA,OAZmB,kCAKjB;EAAE,GAAG;EAAS,mBAAmB;CAA2B,GAC5D;EACE,UAAU;EACV,YAAY;CACd,CAGK;AACT"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useGenerationWithDevtoolsIdentity } from "./use-generation.js";
|
|
2
2
|
import { reconstructTranscriptionResult } from "@tanstack/ai-client";
|
|
3
3
|
//#region src/use-transcription.ts
|
|
4
4
|
/**
|
|
@@ -36,16 +36,12 @@ import { reconstructTranscriptionResult } from "@tanstack/ai-client";
|
|
|
36
36
|
* ```
|
|
37
37
|
*/
|
|
38
38
|
function useTranscription(options) {
|
|
39
|
-
|
|
40
|
-
...options.devtools,
|
|
41
|
-
framework: "react",
|
|
42
|
-
hookName: "useTranscription",
|
|
43
|
-
outputKind: "text"
|
|
44
|
-
};
|
|
45
|
-
return useGeneration({
|
|
39
|
+
return useGenerationWithDevtoolsIdentity({
|
|
46
40
|
...options,
|
|
47
|
-
devtools,
|
|
48
41
|
reconstructResult: reconstructTranscriptionResult
|
|
42
|
+
}, {
|
|
43
|
+
hookName: "useTranscription",
|
|
44
|
+
outputKind: "text"
|
|
49
45
|
});
|
|
50
46
|
}
|
|
51
47
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-transcription.js","names":[],"sources":["../../src/use-transcription.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"use-transcription.js","names":[],"sources":["../../src/use-transcription.ts"],"sourcesContent":["import { useGenerationWithDevtoolsIdentity } from './use-generation'\nimport { reconstructTranscriptionResult } from '@tanstack/ai-client'\nimport type { StreamChunk, TranscriptionResult } from '@tanstack/ai'\nimport type {\n AIDevtoolsDisplayOptions,\n ConnectConnectionAdapter,\n GenerationClientState,\n GenerationFetcher,\n GenerationPersistenceOptions,\n InferGenerationOutputFromReturn,\n TranscriptionGenerateInput,\n} from '@tanstack/ai-client'\nimport type { ByokClient } from '@tanstack/ai-client/byok'\nimport type { ProviderId } from '@tanstack/ai/byok'\n\n/**\n * Options for the useTranscription hook.\n *\n * @template TOutput - The output type after optional transform (defaults to TranscriptionResult)\n */\nexport interface UseTranscriptionOptions<TOutput = TranscriptionResult> {\n /** Connect-based adapter for streaming transport (SSE, HTTP stream, custom) */\n connection?: ConnectConnectionAdapter\n /** Direct async function for transcription */\n fetcher?: GenerationFetcher<TranscriptionGenerateInput, TranscriptionResult>\n /** Additional body parameters to send with connect-based adapter requests */\n body?: Record<string, any>\n /** Optional BYOK keyring. Keys go in `x-byok-*` headers, never the body. */\n byok?: ByokClient\n /** Optional provider id. If it returns a slug, only that key is sent. If no slug resolves (`byokProvider`, then `body.provider`), generate throws. */\n byokProvider?: () => ProviderId | undefined\n /** Display options for TanStack AI Devtools. */\n devtools?: AIDevtoolsDisplayOptions\n /**\n * How this generation persists across reloads.\n * - Omit / `false`: ephemeral, in-memory only.\n * - `true`: server-driven — on mount the client hydrates the last generation\n * for its `threadId` from the server (needs a connection with a\n * `hydrateGeneration` handler) and repaints it; it never auto-starts a run.\n */\n persistence?: boolean\n /**\n * The **scope** this generation belongs to: a stable, app-chosen name for the\n * slot successive runs fill — not a link to a chat conversation.\n *\n * The hook starts empty and produces many runs over its life; each gets its\n * own `runId`, but all belong to one scope. Persistence keys on this, so\n * derive it from your own domain and keep it identical across reloads (e.g.\n * `` `video-${videoId}-start-frame` ``). It is also sent as the AG-UI thread\n * id on the wire, which the protocol requires.\n *\n * **Required whenever `persistence` is set** — an app that cannot name the\n * scope has nothing to restore to. Optional for ephemeral generations. If\n * omitted, the client mints a wire id after mount.\n */\n threadId?: string\n /**\n * Server-driven hydration handler for `persistence: true` when the\n * connection doesn't carry one (e.g. alongside `fetcher`, or a `stream()` /\n * `rpcStream()` adapter built without handlers) — typically a one-line\n * server-function call. The connection's own handler takes precedence.\n */\n hydrateGeneration?: ConnectConnectionAdapter['hydrateGeneration']\n /**\n * Re-attach handler that replays a run still generating to completion on\n * mount, when the connection doesn't carry one. Without it, a restored\n * `running` snapshot surfaces as an (interrupted) error. The connection's\n * own handler takes precedence.\n */\n joinRun?: ConnectConnectionAdapter['joinRun']\n /**\n * Callback when transcription is complete. Can optionally return a transformed value.\n *\n * - Return a non-null value to transform and store it as the result\n * - Return `null` to keep the previous result unchanged\n * - Return nothing (`void`) to store the raw result as-is\n */\n onResult?: (result: TranscriptionResult) => TOutput | null | void\n /** Callback when an error occurs */\n onError?: (error: Error) => void\n /** Callback when progress is reported (0-100) */\n onProgress?: (progress: number, message?: string) => void\n /** Callback for each stream chunk (connect-based adapter mode only) */\n onChunk?: (chunk: StreamChunk) => void\n}\n\n/**\n * Return type for the useTranscription hook.\n *\n * @template TOutput - The output type (after optional transform)\n */\nexport interface UseTranscriptionReturn<TOutput = TranscriptionResult> {\n /** Trigger transcription */\n generate: (input: TranscriptionGenerateInput) => Promise<void>\n /** The transcription result, or null */\n result: TOutput | null\n /** Whether transcription is in progress */\n isLoading: boolean\n /** Current error, if any */\n error: Error | undefined\n /** Current state of the generation */\n status: GenerationClientState\n /** Abort the current transcription */\n stop: () => void\n /** Clear result, error, and return to idle */\n reset: () => void\n /**\n * The id of the generation job currently running, or `null` when nothing is in\n * flight. Each call to `generate` is one job with its own id. Pass it to your\n * own endpoint to cancel or poll the provider job — `stop()` only aborts the\n * local stream, it does not stop work already running on the provider.\n */\n runId: string | null\n}\n\n/**\n * React hook for transcribing audio to text using AI models.\n *\n * @example\n * ```tsx\n * import { useTranscription } from '@tanstack/ai-react'\n * import { fetchServerSentEvents } from '@tanstack/ai-client'\n *\n * function Transcriber() {\n * const { generate, result, isLoading } = useTranscription({\n * connection: fetchServerSentEvents('/api/transcribe'),\n * })\n *\n * const handleFile = (e: React.ChangeEvent<HTMLInputElement>) => {\n * const file = e.target.files?.[0]\n * if (file) {\n * const reader = new FileReader()\n * reader.onload = () => {\n * generate({ audio: reader.result as string, language: 'en' })\n * }\n * reader.readAsDataURL(file)\n * }\n * }\n *\n * return (\n * <div>\n * <input type=\"file\" accept=\"audio/*\" onChange={handleFile} />\n * {isLoading && <p>Transcribing...</p>}\n * {result && <p>{result.text}</p>}\n * </div>\n * )\n * }\n * ```\n */\nexport function useTranscription<TTransformed = void>(\n options: Omit<\n UseTranscriptionOptions,\n 'onResult' | 'persistence' | 'threadId'\n > & {\n onResult?: (result: TranscriptionResult) => TTransformed\n } & GenerationPersistenceOptions,\n): UseTranscriptionReturn<\n InferGenerationOutputFromReturn<TranscriptionResult, TTransformed>\n> {\n const generation = useGenerationWithDevtoolsIdentity<\n TranscriptionGenerateInput,\n TranscriptionResult,\n TTransformed\n >(\n { ...options, reconstructResult: reconstructTranscriptionResult },\n {\n hookName: 'useTranscription',\n outputKind: 'text',\n },\n )\n\n return generation\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqJA,SAAgB,iBACd,SAQA;CAaA,OAZmB,kCAKjB;EAAE,GAAG;EAAS,mBAAmB;CAA+B,GAChE;EACE,UAAU;EACV,YAAY;CACd,CAGK;AACT"}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import { AnyClientTool, InferredClientContext, RegisterWebMCPToolsOptions } from '@tanstack/ai-client';
|
|
2
|
-
/** Options for the React {@link
|
|
3
|
-
export type
|
|
1
|
+
import { AnyClientTool, InferredClientContext, RegisterWebMCPToolsOptions, SubscribeWebMCPToolsOptions } from '@tanstack/ai-client';
|
|
2
|
+
/** Options for the React {@link useRegisterWebMCPTools} lifecycle hook. */
|
|
3
|
+
export type UseRegisterWebMCPToolsOptions<TTools extends ReadonlyArray<AnyClientTool>, TContext = InferredClientContext<TTools>> = Omit<RegisterWebMCPToolsOptions<TTools, TContext>, 'signal'> & {
|
|
4
4
|
/** Receives an asynchronous registration failure. */
|
|
5
5
|
onError?: (error: unknown) => void;
|
|
6
6
|
};
|
|
7
|
-
|
|
7
|
+
/** @deprecated Use `UseRegisterWebMCPToolsOptions`. Removed in 1.0.0. */
|
|
8
|
+
export type UseWebMCPToolsOptions<TTools extends ReadonlyArray<AnyClientTool>, TContext = InferredClientContext<TTools>> = UseRegisterWebMCPToolsOptions<TTools, TContext>;
|
|
9
|
+
type UseRegisterWebMCPToolsArguments<TTools extends ReadonlyArray<AnyClientTool>, TContext> = RegisterWebMCPToolsOptions<TTools, TContext> extends {
|
|
8
10
|
context: unknown;
|
|
9
|
-
} ? [options:
|
|
11
|
+
} ? [options: UseRegisterWebMCPToolsOptions<TTools, TContext>] : [options?: UseRegisterWebMCPToolsOptions<TTools, TContext>];
|
|
10
12
|
/**
|
|
11
13
|
* Registers client tools with WebMCP for the lifetime of a React component.
|
|
12
14
|
*
|
|
@@ -18,10 +20,35 @@ type UseWebMCPToolsArguments<TTools extends ReadonlyArray<AnyClientTool>, TConte
|
|
|
18
20
|
*
|
|
19
21
|
* @example
|
|
20
22
|
* ```tsx
|
|
21
|
-
*
|
|
23
|
+
* useRegisterWebMCPTools([searchProducts], {
|
|
22
24
|
* toolOptions: { searchProducts: { title: 'Search products' } },
|
|
23
25
|
* })
|
|
24
26
|
* ```
|
|
25
27
|
*/
|
|
26
|
-
export declare function
|
|
28
|
+
export declare function useRegisterWebMCPTools<const TTools extends ReadonlyArray<AnyClientTool>, TContext = InferredClientContext<TTools>>(tools: TTools, ...[options]: UseRegisterWebMCPToolsArguments<TTools, TContext>): void;
|
|
29
|
+
/**
|
|
30
|
+
* @deprecated Use `useRegisterWebMCPTools`. Removed in 1.0.0.
|
|
31
|
+
* @alias
|
|
32
|
+
*/
|
|
33
|
+
export declare const useWebMCPTools: typeof useRegisterWebMCPTools;
|
|
34
|
+
/** Options for {@link usePageWebMCPTools}. */
|
|
35
|
+
export type UsePageWebMCPToolsOptions = Omit<SubscribeWebMCPToolsOptions, 'signal'>;
|
|
36
|
+
/**
|
|
37
|
+
* Returns the WebMCP tools on the page as client tools for `useChat`.
|
|
38
|
+
*
|
|
39
|
+
* The list starts empty and updates when the page registers or removes a
|
|
40
|
+
* tool. The hook reads the latest `filter` and `onError` on each update.
|
|
41
|
+
* Unsupported browsers and server rendering return an empty array.
|
|
42
|
+
*
|
|
43
|
+
* @param options - A filter that skips tools, and an error callback.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```tsx
|
|
47
|
+
* const pageTools = usePageWebMCPTools({
|
|
48
|
+
* filter: (tool) => tool.origin === location.origin,
|
|
49
|
+
* })
|
|
50
|
+
* const chat = useChat({ connection, tools: pageTools })
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export declare function usePageWebMCPTools(options?: UsePageWebMCPToolsOptions): Array<AnyClientTool>;
|
|
27
54
|
export {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { registerWebMCPTools } from "@tanstack/ai-client";
|
|
2
|
-
import { useEffect } from "react";
|
|
1
|
+
import { registerWebMCPTools, subscribeWebMCPTools } from "@tanstack/ai-client";
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
3
|
//#region src/use-web-mcp-tools.ts
|
|
4
4
|
/**
|
|
5
5
|
* Registers client tools with WebMCP for the lifetime of a React component.
|
|
@@ -12,12 +12,12 @@ import { useEffect } from "react";
|
|
|
12
12
|
*
|
|
13
13
|
* @example
|
|
14
14
|
* ```tsx
|
|
15
|
-
*
|
|
15
|
+
* useRegisterWebMCPTools([searchProducts], {
|
|
16
16
|
* toolOptions: { searchProducts: { title: 'Search products' } },
|
|
17
17
|
* })
|
|
18
18
|
* ```
|
|
19
19
|
*/
|
|
20
|
-
function
|
|
20
|
+
function useRegisterWebMCPTools(tools, ...[options]) {
|
|
21
21
|
useEffect(() => {
|
|
22
22
|
const controller = new AbortController();
|
|
23
23
|
const { onError, ...registrationOptions } = options ?? {};
|
|
@@ -30,7 +30,44 @@ function useWebMCPTools(tools, ...[options]) {
|
|
|
30
30
|
return () => controller.abort();
|
|
31
31
|
}, [tools, options]);
|
|
32
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* @deprecated Use `useRegisterWebMCPTools`. Removed in 1.0.0.
|
|
35
|
+
* @alias
|
|
36
|
+
*/
|
|
37
|
+
var useWebMCPTools = useRegisterWebMCPTools;
|
|
38
|
+
/**
|
|
39
|
+
* Returns the WebMCP tools on the page as client tools for `useChat`.
|
|
40
|
+
*
|
|
41
|
+
* The list starts empty and updates when the page registers or removes a
|
|
42
|
+
* tool. The hook reads the latest `filter` and `onError` on each update.
|
|
43
|
+
* Unsupported browsers and server rendering return an empty array.
|
|
44
|
+
*
|
|
45
|
+
* @param options - A filter that skips tools, and an error callback.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```tsx
|
|
49
|
+
* const pageTools = usePageWebMCPTools({
|
|
50
|
+
* filter: (tool) => tool.origin === location.origin,
|
|
51
|
+
* })
|
|
52
|
+
* const chat = useChat({ connection, tools: pageTools })
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
function usePageWebMCPTools(options) {
|
|
56
|
+
const [tools, setTools] = useState([]);
|
|
57
|
+
const optionsRef = useRef(options);
|
|
58
|
+
optionsRef.current = options;
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
const controller = new AbortController();
|
|
61
|
+
subscribeWebMCPTools(setTools, {
|
|
62
|
+
signal: controller.signal,
|
|
63
|
+
filter: (tool) => optionsRef.current?.filter?.(tool) ?? true,
|
|
64
|
+
onError: (error) => optionsRef.current?.onError?.(error)
|
|
65
|
+
});
|
|
66
|
+
return () => controller.abort();
|
|
67
|
+
}, []);
|
|
68
|
+
return tools;
|
|
69
|
+
}
|
|
33
70
|
//#endregion
|
|
34
|
-
export { useWebMCPTools };
|
|
71
|
+
export { usePageWebMCPTools, useRegisterWebMCPTools, useWebMCPTools };
|
|
35
72
|
|
|
36
73
|
//# sourceMappingURL=use-web-mcp-tools.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-web-mcp-tools.js","names":[],"sources":["../../src/use-web-mcp-tools.ts"],"sourcesContent":["import { useEffect } from 'react'\nimport { registerWebMCPTools } from '@tanstack/ai-client'\nimport type {\n AnyClientTool,\n InferredClientContext,\n RegisterWebMCPToolsOptions,\n} from '@tanstack/ai-client'\n\n/** Options for the React {@link
|
|
1
|
+
{"version":3,"file":"use-web-mcp-tools.js","names":[],"sources":["../../src/use-web-mcp-tools.ts"],"sourcesContent":["import { useEffect, useRef, useState } from 'react'\nimport { registerWebMCPTools, subscribeWebMCPTools } from '@tanstack/ai-client'\nimport type {\n AnyClientTool,\n InferredClientContext,\n RegisterWebMCPToolsOptions,\n SubscribeWebMCPToolsOptions,\n} from '@tanstack/ai-client'\n\n/** Options for the React {@link useRegisterWebMCPTools} lifecycle hook. */\nexport type UseRegisterWebMCPToolsOptions<\n TTools extends ReadonlyArray<AnyClientTool>,\n TContext = InferredClientContext<TTools>,\n> = Omit<RegisterWebMCPToolsOptions<TTools, TContext>, 'signal'> & {\n /** Receives an asynchronous registration failure. */\n onError?: (error: unknown) => void\n}\n\n/** @deprecated Use `UseRegisterWebMCPToolsOptions`. Removed in 1.0.0. */\nexport type UseWebMCPToolsOptions<\n TTools extends ReadonlyArray<AnyClientTool>,\n TContext = InferredClientContext<TTools>,\n> = UseRegisterWebMCPToolsOptions<TTools, TContext>\n\ntype UseRegisterWebMCPToolsArguments<\n TTools extends ReadonlyArray<AnyClientTool>,\n TContext,\n> =\n RegisterWebMCPToolsOptions<TTools, TContext> extends { context: unknown }\n ? [options: UseRegisterWebMCPToolsOptions<TTools, TContext>]\n : [options?: UseRegisterWebMCPToolsOptions<TTools, TContext>]\n\n/**\n * Registers client tools with WebMCP for the lifetime of a React component.\n *\n * The hook replaces the registration when `tools` or `options` changes.\n * Unsupported browsers and server rendering do not register tools.\n *\n * @param tools - The executable client tools to expose through WebMCP.\n * @param options - Runtime context, per-tool options, and an error callback.\n *\n * @example\n * ```tsx\n * useRegisterWebMCPTools([searchProducts], {\n * toolOptions: { searchProducts: { title: 'Search products' } },\n * })\n * ```\n */\nexport function useRegisterWebMCPTools<\n const TTools extends ReadonlyArray<AnyClientTool>,\n TContext = InferredClientContext<TTools>,\n>(\n tools: TTools,\n ...[options]: UseRegisterWebMCPToolsArguments<TTools, TContext>\n) {\n useEffect(() => {\n const controller = new AbortController()\n const { onError, ...registrationOptions } = options ?? {}\n\n registerWebMCPTools(tools, {\n ...registrationOptions,\n signal: controller.signal,\n }).catch((error) => {\n if (!controller.signal.aborted) onError?.(error)\n })\n\n return () => controller.abort()\n }, [tools, options])\n}\n\n/**\n * @deprecated Use `useRegisterWebMCPTools`. Removed in 1.0.0.\n * @alias\n */\nexport const useWebMCPTools = useRegisterWebMCPTools\n\n/** Options for {@link usePageWebMCPTools}. */\nexport type UsePageWebMCPToolsOptions = Omit<\n SubscribeWebMCPToolsOptions,\n 'signal'\n>\n\n/**\n * Returns the WebMCP tools on the page as client tools for `useChat`.\n *\n * The list starts empty and updates when the page registers or removes a\n * tool. The hook reads the latest `filter` and `onError` on each update.\n * Unsupported browsers and server rendering return an empty array.\n *\n * @param options - A filter that skips tools, and an error callback.\n *\n * @example\n * ```tsx\n * const pageTools = usePageWebMCPTools({\n * filter: (tool) => tool.origin === location.origin,\n * })\n * const chat = useChat({ connection, tools: pageTools })\n * ```\n */\nexport function usePageWebMCPTools(\n options?: UsePageWebMCPToolsOptions,\n): Array<AnyClientTool> {\n const [tools, setTools] = useState<Array<AnyClientTool>>([])\n const optionsRef = useRef(options)\n optionsRef.current = options\n\n useEffect(() => {\n const controller = new AbortController()\n subscribeWebMCPTools(setTools, {\n signal: controller.signal,\n filter: (tool) => optionsRef.current?.filter?.(tool) ?? true,\n onError: (error) => optionsRef.current?.onError?.(error),\n })\n return () => controller.abort()\n }, [])\n\n return tools\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAgDA,SAAgB,uBAId,OACA,GAAG,CAAC,UACJ;CACA,gBAAgB;EACd,MAAM,aAAa,IAAI,gBAAgB;EACvC,MAAM,EAAE,SAAS,GAAG,wBAAwB,WAAW,CAAC;EAExD,oBAAoB,OAAO;GACzB,GAAG;GACH,QAAQ,WAAW;EACrB,CAAC,CAAC,CAAC,OAAO,UAAU;GAClB,IAAI,CAAC,WAAW,OAAO,SAAS,UAAU,KAAK;EACjD,CAAC;EAED,aAAa,WAAW,MAAM;CAChC,GAAG,CAAC,OAAO,OAAO,CAAC;AACrB;;;;;AAMA,IAAa,iBAAiB;;;;;;;;;;;;;;;;;;AAyB9B,SAAgB,mBACd,SACsB;CACtB,MAAM,CAAC,OAAO,YAAY,SAA+B,CAAC,CAAC;CAC3D,MAAM,aAAa,OAAO,OAAO;CACjC,WAAW,UAAU;CAErB,gBAAgB;EACd,MAAM,aAAa,IAAI,gBAAgB;EACvC,qBAAqB,UAAU;GAC7B,QAAQ,WAAW;GACnB,SAAS,SAAS,WAAW,SAAS,SAAS,IAAI,KAAK;GACxD,UAAU,UAAU,WAAW,SAAS,UAAU,KAAK;EACzD,CAAC;EACD,aAAa,WAAW,MAAM;CAChC,GAAG,CAAC,CAAC;CAEL,OAAO;AACT"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/ai-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.29.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",
|
|
@@ -58,14 +58,14 @@
|
|
|
58
58
|
],
|
|
59
59
|
"dependencies": {
|
|
60
60
|
"@tanstack/markdown": "^0.0.13",
|
|
61
|
-
"@tanstack/ai-client": "^0.
|
|
61
|
+
"@tanstack/ai-client": "^0.36.0"
|
|
62
62
|
},
|
|
63
63
|
"peerDependencies": {
|
|
64
64
|
"@mcp-ui/client": "^7",
|
|
65
65
|
"@types/react": ">=18.0.0",
|
|
66
66
|
"react": ">=18.0.0",
|
|
67
67
|
"react-dom": ">=18.0.0",
|
|
68
|
-
"@tanstack/ai": "^0.
|
|
68
|
+
"@tanstack/ai": "^0.63.0"
|
|
69
69
|
},
|
|
70
70
|
"peerDependenciesMeta": {
|
|
71
71
|
"@mcp-ui/client": {
|
|
@@ -85,12 +85,13 @@
|
|
|
85
85
|
"react": "^19.2.3",
|
|
86
86
|
"react-dom": "^19.2.3",
|
|
87
87
|
"vite": "^8.2.1",
|
|
88
|
-
"@tanstack/ai": "0.
|
|
88
|
+
"@tanstack/ai": "0.63.0"
|
|
89
89
|
},
|
|
90
90
|
"scripts": {
|
|
91
91
|
"clean": "premove ./build ./dist",
|
|
92
92
|
"lint:fix": "oxlint src --type-aware --fix",
|
|
93
93
|
"test:oxlint": "oxlint src --type-aware",
|
|
94
|
+
"test:coverage": "vitest run --coverage --coverage.include='src/**' --coverage.reporter=text-summary --coverage.reporter=json-summary",
|
|
94
95
|
"test:lib": "vitest run",
|
|
95
96
|
"test:lib:dev": "pnpm test:lib --watch",
|
|
96
97
|
"test:types": "tsc",
|
package/src/index.ts
CHANGED
|
@@ -4,8 +4,16 @@ export { useByok } from './use-byok'
|
|
|
4
4
|
export { useRealtimeChat } from './use-realtime-chat'
|
|
5
5
|
export { useMcpAppBridge } from './use-mcp-app-bridge'
|
|
6
6
|
export type { UseMcpAppBridgeOptions } from './use-mcp-app-bridge'
|
|
7
|
-
export {
|
|
8
|
-
|
|
7
|
+
export {
|
|
8
|
+
usePageWebMCPTools,
|
|
9
|
+
useRegisterWebMCPTools,
|
|
10
|
+
useWebMCPTools,
|
|
11
|
+
} from './use-web-mcp-tools'
|
|
12
|
+
export type {
|
|
13
|
+
UsePageWebMCPToolsOptions,
|
|
14
|
+
UseRegisterWebMCPToolsOptions,
|
|
15
|
+
UseWebMCPToolsOptions,
|
|
16
|
+
} from './use-web-mcp-tools'
|
|
9
17
|
export type {
|
|
10
18
|
DeepPartial,
|
|
11
19
|
UseChatOptions,
|
|
@@ -105,6 +113,11 @@ export {
|
|
|
105
113
|
PartialJSONParser,
|
|
106
114
|
defaultJSONParser,
|
|
107
115
|
registerWebMCPTools,
|
|
116
|
+
getWebMCPTools,
|
|
117
|
+
subscribeWebMCPTools,
|
|
118
|
+
type GetWebMCPToolsOptions,
|
|
119
|
+
type SubscribeWebMCPToolsOptions,
|
|
120
|
+
type WebMCPPageTool,
|
|
108
121
|
clientTools,
|
|
109
122
|
fetchServerSentEvents,
|
|
110
123
|
localStoragePersistence,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useGenerationWithDevtoolsIdentity } from './use-generation'
|
|
2
2
|
import { reconstructAudioResult } from '@tanstack/ai-client'
|
|
3
3
|
import type { AudioGenerationResult, StreamChunk } from '@tanstack/ai'
|
|
4
4
|
import type {
|
|
@@ -153,21 +153,17 @@ export function useGenerateAudio<TTransformed = void>(
|
|
|
153
153
|
): UseGenerateAudioReturn<
|
|
154
154
|
InferGenerationOutputFromReturn<AudioGenerationResult, TTransformed>
|
|
155
155
|
> {
|
|
156
|
-
const
|
|
157
|
-
...options.devtools,
|
|
158
|
-
framework: 'react',
|
|
159
|
-
hookName: 'useGenerateAudio',
|
|
160
|
-
outputKind: 'audio' as const,
|
|
161
|
-
}
|
|
162
|
-
const generation = useGeneration<
|
|
156
|
+
const generation = useGenerationWithDevtoolsIdentity<
|
|
163
157
|
AudioGenerateInput,
|
|
164
158
|
AudioGenerationResult,
|
|
165
159
|
TTransformed
|
|
166
|
-
>(
|
|
167
|
-
...options,
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
160
|
+
>(
|
|
161
|
+
{ ...options, reconstructResult: reconstructAudioResult },
|
|
162
|
+
{
|
|
163
|
+
hookName: 'useGenerateAudio',
|
|
164
|
+
outputKind: 'audio',
|
|
165
|
+
},
|
|
166
|
+
)
|
|
171
167
|
|
|
172
168
|
return generation
|
|
173
169
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useGenerationWithDevtoolsIdentity } from './use-generation'
|
|
2
2
|
import { reconstructImageResult } from '@tanstack/ai-client'
|
|
3
3
|
import type { ImageGenerationResult, StreamChunk } from '@tanstack/ai'
|
|
4
4
|
import type {
|
|
@@ -155,21 +155,17 @@ export function useGenerateImage<TTransformed = void>(
|
|
|
155
155
|
): UseGenerateImageReturn<
|
|
156
156
|
InferGenerationOutputFromReturn<ImageGenerationResult, TTransformed>
|
|
157
157
|
> {
|
|
158
|
-
const
|
|
159
|
-
...options.devtools,
|
|
160
|
-
framework: 'react',
|
|
161
|
-
hookName: 'useGenerateImage',
|
|
162
|
-
outputKind: 'image' as const,
|
|
163
|
-
}
|
|
164
|
-
const generation = useGeneration<
|
|
158
|
+
const generation = useGenerationWithDevtoolsIdentity<
|
|
165
159
|
ImageGenerateInput,
|
|
166
160
|
ImageGenerationResult,
|
|
167
161
|
TTransformed
|
|
168
|
-
>(
|
|
169
|
-
...options,
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
162
|
+
>(
|
|
163
|
+
{ ...options, reconstructResult: reconstructImageResult },
|
|
164
|
+
{
|
|
165
|
+
hookName: 'useGenerateImage',
|
|
166
|
+
outputKind: 'image',
|
|
167
|
+
},
|
|
168
|
+
)
|
|
173
169
|
|
|
174
170
|
return generation
|
|
175
171
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useGenerationWithDevtoolsIdentity } from './use-generation'
|
|
2
2
|
import { reconstructSpeechResult } from '@tanstack/ai-client'
|
|
3
3
|
import type { StreamChunk, TTSResult } from '@tanstack/ai'
|
|
4
4
|
import type {
|
|
@@ -149,21 +149,17 @@ export function useGenerateSpeech<TTransformed = void>(
|
|
|
149
149
|
): UseGenerateSpeechReturn<
|
|
150
150
|
InferGenerationOutputFromReturn<TTSResult, TTransformed>
|
|
151
151
|
> {
|
|
152
|
-
const
|
|
153
|
-
...options.devtools,
|
|
154
|
-
framework: 'react',
|
|
155
|
-
hookName: 'useGenerateSpeech',
|
|
156
|
-
outputKind: 'audio' as const,
|
|
157
|
-
}
|
|
158
|
-
const generation = useGeneration<
|
|
152
|
+
const generation = useGenerationWithDevtoolsIdentity<
|
|
159
153
|
SpeechGenerateInput,
|
|
160
154
|
TTSResult,
|
|
161
155
|
TTransformed
|
|
162
|
-
>(
|
|
163
|
-
...options,
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
156
|
+
>(
|
|
157
|
+
{ ...options, reconstructResult: reconstructSpeechResult },
|
|
158
|
+
{
|
|
159
|
+
hookName: 'useGenerateSpeech',
|
|
160
|
+
outputKind: 'audio',
|
|
161
|
+
},
|
|
162
|
+
)
|
|
167
163
|
|
|
168
164
|
return generation
|
|
169
165
|
}
|
package/src/use-generation.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { createGenerationDevtoolsBridge } from '@tanstack/ai-client/devtools'
|
|
|
3
3
|
import { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react'
|
|
4
4
|
import type { StreamChunk } from '@tanstack/ai'
|
|
5
5
|
import type {
|
|
6
|
+
AIDevtoolsClientMetadata,
|
|
6
7
|
AIDevtoolsDisplayOptions,
|
|
7
8
|
ConnectConnectionAdapter,
|
|
8
9
|
GenerationClientOptions,
|
|
@@ -168,6 +169,34 @@ export function useGeneration<
|
|
|
168
169
|
): UseGenerationReturn<
|
|
169
170
|
InferGenerationOutputFromReturn<TResult, TTransformed>,
|
|
170
171
|
TInput
|
|
172
|
+
> {
|
|
173
|
+
return useGenerationWithDevtoolsIdentity<TInput, TResult, TTransformed>(
|
|
174
|
+
options,
|
|
175
|
+
{ hookName: 'useGeneration' },
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
interface GenerationDevtoolsIdentity {
|
|
180
|
+
hookName: AIDevtoolsClientMetadata['hookName']
|
|
181
|
+
outputKind?: AIDevtoolsClientMetadata['outputKind']
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** @internal */
|
|
185
|
+
export function useGenerationWithDevtoolsIdentity<
|
|
186
|
+
TInput extends Record<string, any>,
|
|
187
|
+
TResult,
|
|
188
|
+
TTransformed = void,
|
|
189
|
+
>(
|
|
190
|
+
options: Omit<
|
|
191
|
+
UseGenerationOptions<TInput, TResult>,
|
|
192
|
+
'onResult' | 'persistence' | 'threadId'
|
|
193
|
+
> & {
|
|
194
|
+
onResult?: (result: TResult) => TTransformed
|
|
195
|
+
} & GenerationPersistenceOptions,
|
|
196
|
+
devtoolsIdentity: GenerationDevtoolsIdentity,
|
|
197
|
+
): UseGenerationReturn<
|
|
198
|
+
InferGenerationOutputFromReturn<TResult, TTransformed>,
|
|
199
|
+
TInput
|
|
171
200
|
> {
|
|
172
201
|
type TOutput = InferGenerationOutputFromReturn<TResult, TTransformed>
|
|
173
202
|
const hookId = useId()
|
|
@@ -207,9 +236,12 @@ export function useGeneration<
|
|
|
207
236
|
: {}),
|
|
208
237
|
devtoolsBridgeFactory: createGenerationDevtoolsBridge,
|
|
209
238
|
devtools: {
|
|
210
|
-
hookName: 'useGeneration',
|
|
211
|
-
framework: 'react',
|
|
212
239
|
...opts.devtools,
|
|
240
|
+
framework: 'react',
|
|
241
|
+
hookName: devtoolsIdentity.hookName,
|
|
242
|
+
...(devtoolsIdentity.outputKind !== undefined && {
|
|
243
|
+
outputKind: devtoolsIdentity.outputKind,
|
|
244
|
+
}),
|
|
213
245
|
},
|
|
214
246
|
// The transform's raw return type (`TTransformed`) and the stored output
|
|
215
247
|
// (`TOutput`, with null/void/undefined stripped) are identical at runtime;
|
package/src/use-summarize.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useGenerationWithDevtoolsIdentity } from './use-generation'
|
|
2
2
|
import { reconstructSummarizeResult } from '@tanstack/ai-client'
|
|
3
3
|
import type { StreamChunk, SummarizationResult } from '@tanstack/ai'
|
|
4
4
|
import type {
|
|
@@ -152,21 +152,17 @@ export function useSummarize<TTransformed = void>(
|
|
|
152
152
|
): UseSummarizeReturn<
|
|
153
153
|
InferGenerationOutputFromReturn<SummarizationResult, TTransformed>
|
|
154
154
|
> {
|
|
155
|
-
const
|
|
156
|
-
...options.devtools,
|
|
157
|
-
framework: 'react',
|
|
158
|
-
hookName: 'useSummarize',
|
|
159
|
-
outputKind: 'text' as const,
|
|
160
|
-
}
|
|
161
|
-
const generation = useGeneration<
|
|
155
|
+
const generation = useGenerationWithDevtoolsIdentity<
|
|
162
156
|
SummarizeGenerateInput,
|
|
163
157
|
SummarizationResult,
|
|
164
158
|
TTransformed
|
|
165
|
-
>(
|
|
166
|
-
...options,
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
159
|
+
>(
|
|
160
|
+
{ ...options, reconstructResult: reconstructSummarizeResult },
|
|
161
|
+
{
|
|
162
|
+
hookName: 'useSummarize',
|
|
163
|
+
outputKind: 'text',
|
|
164
|
+
},
|
|
165
|
+
)
|
|
170
166
|
|
|
171
167
|
return generation
|
|
172
168
|
}
|
package/src/use-transcription.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useGenerationWithDevtoolsIdentity } from './use-generation'
|
|
2
2
|
import { reconstructTranscriptionResult } from '@tanstack/ai-client'
|
|
3
3
|
import type { StreamChunk, TranscriptionResult } from '@tanstack/ai'
|
|
4
4
|
import type {
|
|
@@ -157,17 +157,17 @@ export function useTranscription<TTransformed = void>(
|
|
|
157
157
|
): UseTranscriptionReturn<
|
|
158
158
|
InferGenerationOutputFromReturn<TranscriptionResult, TTransformed>
|
|
159
159
|
> {
|
|
160
|
-
const
|
|
161
|
-
...options.devtools,
|
|
162
|
-
framework: 'react',
|
|
163
|
-
hookName: 'useTranscription',
|
|
164
|
-
outputKind: 'text' as const,
|
|
165
|
-
}
|
|
166
|
-
const generation = useGeneration<
|
|
160
|
+
const generation = useGenerationWithDevtoolsIdentity<
|
|
167
161
|
TranscriptionGenerateInput,
|
|
168
162
|
TranscriptionResult,
|
|
169
163
|
TTransformed
|
|
170
|
-
>(
|
|
164
|
+
>(
|
|
165
|
+
{ ...options, reconstructResult: reconstructTranscriptionResult },
|
|
166
|
+
{
|
|
167
|
+
hookName: 'useTranscription',
|
|
168
|
+
outputKind: 'text',
|
|
169
|
+
},
|
|
170
|
+
)
|
|
171
171
|
|
|
172
172
|
return generation
|
|
173
173
|
}
|
package/src/use-web-mcp-tools.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import { useEffect } from 'react'
|
|
2
|
-
import { registerWebMCPTools } from '@tanstack/ai-client'
|
|
1
|
+
import { useEffect, useRef, useState } from 'react'
|
|
2
|
+
import { registerWebMCPTools, subscribeWebMCPTools } from '@tanstack/ai-client'
|
|
3
3
|
import type {
|
|
4
4
|
AnyClientTool,
|
|
5
5
|
InferredClientContext,
|
|
6
6
|
RegisterWebMCPToolsOptions,
|
|
7
|
+
SubscribeWebMCPToolsOptions,
|
|
7
8
|
} from '@tanstack/ai-client'
|
|
8
9
|
|
|
9
|
-
/** Options for the React {@link
|
|
10
|
-
export type
|
|
10
|
+
/** Options for the React {@link useRegisterWebMCPTools} lifecycle hook. */
|
|
11
|
+
export type UseRegisterWebMCPToolsOptions<
|
|
11
12
|
TTools extends ReadonlyArray<AnyClientTool>,
|
|
12
13
|
TContext = InferredClientContext<TTools>,
|
|
13
14
|
> = Omit<RegisterWebMCPToolsOptions<TTools, TContext>, 'signal'> & {
|
|
@@ -15,13 +16,19 @@ export type UseWebMCPToolsOptions<
|
|
|
15
16
|
onError?: (error: unknown) => void
|
|
16
17
|
}
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
/** @deprecated Use `UseRegisterWebMCPToolsOptions`. Removed in 1.0.0. */
|
|
20
|
+
export type UseWebMCPToolsOptions<
|
|
21
|
+
TTools extends ReadonlyArray<AnyClientTool>,
|
|
22
|
+
TContext = InferredClientContext<TTools>,
|
|
23
|
+
> = UseRegisterWebMCPToolsOptions<TTools, TContext>
|
|
24
|
+
|
|
25
|
+
type UseRegisterWebMCPToolsArguments<
|
|
19
26
|
TTools extends ReadonlyArray<AnyClientTool>,
|
|
20
27
|
TContext,
|
|
21
28
|
> =
|
|
22
29
|
RegisterWebMCPToolsOptions<TTools, TContext> extends { context: unknown }
|
|
23
|
-
? [options:
|
|
24
|
-
: [options?:
|
|
30
|
+
? [options: UseRegisterWebMCPToolsOptions<TTools, TContext>]
|
|
31
|
+
: [options?: UseRegisterWebMCPToolsOptions<TTools, TContext>]
|
|
25
32
|
|
|
26
33
|
/**
|
|
27
34
|
* Registers client tools with WebMCP for the lifetime of a React component.
|
|
@@ -34,15 +41,18 @@ type UseWebMCPToolsArguments<
|
|
|
34
41
|
*
|
|
35
42
|
* @example
|
|
36
43
|
* ```tsx
|
|
37
|
-
*
|
|
44
|
+
* useRegisterWebMCPTools([searchProducts], {
|
|
38
45
|
* toolOptions: { searchProducts: { title: 'Search products' } },
|
|
39
46
|
* })
|
|
40
47
|
* ```
|
|
41
48
|
*/
|
|
42
|
-
export function
|
|
49
|
+
export function useRegisterWebMCPTools<
|
|
43
50
|
const TTools extends ReadonlyArray<AnyClientTool>,
|
|
44
51
|
TContext = InferredClientContext<TTools>,
|
|
45
|
-
>(
|
|
52
|
+
>(
|
|
53
|
+
tools: TTools,
|
|
54
|
+
...[options]: UseRegisterWebMCPToolsArguments<TTools, TContext>
|
|
55
|
+
) {
|
|
46
56
|
useEffect(() => {
|
|
47
57
|
const controller = new AbortController()
|
|
48
58
|
const { onError, ...registrationOptions } = options ?? {}
|
|
@@ -57,3 +67,52 @@ export function useWebMCPTools<
|
|
|
57
67
|
return () => controller.abort()
|
|
58
68
|
}, [tools, options])
|
|
59
69
|
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* @deprecated Use `useRegisterWebMCPTools`. Removed in 1.0.0.
|
|
73
|
+
* @alias
|
|
74
|
+
*/
|
|
75
|
+
export const useWebMCPTools = useRegisterWebMCPTools
|
|
76
|
+
|
|
77
|
+
/** Options for {@link usePageWebMCPTools}. */
|
|
78
|
+
export type UsePageWebMCPToolsOptions = Omit<
|
|
79
|
+
SubscribeWebMCPToolsOptions,
|
|
80
|
+
'signal'
|
|
81
|
+
>
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Returns the WebMCP tools on the page as client tools for `useChat`.
|
|
85
|
+
*
|
|
86
|
+
* The list starts empty and updates when the page registers or removes a
|
|
87
|
+
* tool. The hook reads the latest `filter` and `onError` on each update.
|
|
88
|
+
* Unsupported browsers and server rendering return an empty array.
|
|
89
|
+
*
|
|
90
|
+
* @param options - A filter that skips tools, and an error callback.
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```tsx
|
|
94
|
+
* const pageTools = usePageWebMCPTools({
|
|
95
|
+
* filter: (tool) => tool.origin === location.origin,
|
|
96
|
+
* })
|
|
97
|
+
* const chat = useChat({ connection, tools: pageTools })
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
export function usePageWebMCPTools(
|
|
101
|
+
options?: UsePageWebMCPToolsOptions,
|
|
102
|
+
): Array<AnyClientTool> {
|
|
103
|
+
const [tools, setTools] = useState<Array<AnyClientTool>>([])
|
|
104
|
+
const optionsRef = useRef(options)
|
|
105
|
+
optionsRef.current = options
|
|
106
|
+
|
|
107
|
+
useEffect(() => {
|
|
108
|
+
const controller = new AbortController()
|
|
109
|
+
subscribeWebMCPTools(setTools, {
|
|
110
|
+
signal: controller.signal,
|
|
111
|
+
filter: (tool) => optionsRef.current?.filter?.(tool) ?? true,
|
|
112
|
+
onError: (error) => optionsRef.current?.onError?.(error),
|
|
113
|
+
})
|
|
114
|
+
return () => controller.abort()
|
|
115
|
+
}, [])
|
|
116
|
+
|
|
117
|
+
return tools
|
|
118
|
+
}
|